From c716b9f295c0a46073f134a23735dd7b811745fe Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 21 Feb 2022 10:16:05 -0500 Subject: odb: initialize `object` before use Newer gcc is complaining about `object` being potentially not initialized; initialize it. --- src/odb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/odb.c b/src/odb.c index 14eff53c7..6d714ba54 100644 --- a/src/odb.c +++ b/src/odb.c @@ -1067,7 +1067,7 @@ int git_odb_expand_ids( int git_odb_read_header(size_t *len_p, git_object_t *type_p, git_odb *db, const git_oid *id) { int error; - git_odb_object *object; + git_odb_object *object = NULL; error = git_odb__read_header_or_object(&object, len_p, type_p, db, id); -- cgit v1.2.1 From 49e180c862dc7c6d1f62a53bf8756e25b3417968 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 15 Feb 2022 22:55:48 -0500 Subject: errors: expose `git_error_set` The `git_error_set` function is useful for callers who implement backends and advanced callbacks. Expose it. --- include/git2/errors.h | 18 ++++++++++++++++-- src/errors.h | 3 +-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/include/git2/errors.h b/include/git2/errors.h index 5a5f8c5a2..62f363507 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -130,7 +130,8 @@ GIT_EXTERN(const git_error *) git_error_last(void); GIT_EXTERN(void) git_error_clear(void); /** - * Set the error message string for this thread. + * Set the error message string for this thread, using `printf`-style + * formatting. * * This function is public so that custom ODB backends and the like can * relay an error message through libgit2. Most regular users of libgit2 @@ -143,7 +144,20 @@ GIT_EXTERN(void) git_error_clear(void); * * @param error_class One of the `git_error_t` enum above describing the * general subsystem that is responsible for the error. - * @param string The formatted error message to keep + * @param fmt The `printf`-style format string; subsequent arguments must + * be the arguments for the format string. + */ +GIT_EXTERN(void) git_error_set(int error_class, const char *fmt, ...) + GIT_FORMAT_PRINTF(2, 3); + +/** + * Set the error message string for this thread. This function is like + * `git_error_set` but takes a static string instead of a `printf`-style + * format. + * + * @param error_class One of the `git_error_t` enum above describing the + * general subsystem that is responsible for the error. + * @param string The error message to keep * @return 0 on success or -1 on failure */ GIT_EXTERN(int) git_error_set_str(int error_class, const char *string); diff --git a/src/errors.h b/src/errors.h index a2f60f752..772c7bad1 100644 --- a/src/errors.h +++ b/src/errors.h @@ -11,9 +11,8 @@ #include "common.h" /* - * Set the error message for this thread, formatting as needed. + * `vprintf`-style formatting for the error message for this thread. */ -void git_error_set(int error_class, const char *fmt, ...) GIT_FORMAT_PRINTF(2, 3); void git_error_vset(int error_class, const char *fmt, va_list ap); /** -- cgit v1.2.1 From ef4ab2988320005cbcb3db920e6b41f10b3c60cf Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 14 Nov 2021 08:47:40 -0500 Subject: refactor: `src` is now `src/libgit2` --- src/CMakeLists.txt | 303 +-- src/alloc.c | 56 - src/alloc.h | 40 - src/allocators/failalloc.c | 92 - src/allocators/failalloc.h | 23 - src/allocators/stdalloc.c | 150 -- src/allocators/stdalloc.h | 17 - src/allocators/win32_leakcheck.c | 118 - src/allocators/win32_leakcheck.h | 17 - src/annotated_commit.c | 240 -- src/annotated_commit.h | 52 - src/apply.c | 898 ------- src/apply.h | 25 - src/array.h | 124 - src/assert_safe.h | 58 - src/attr.c | 700 ----- src/attr.h | 15 - src/attr_file.c | 1027 -------- src/attr_file.h | 241 -- src/attrcache.c | 478 ---- src/attrcache.h | 56 - src/bitvec.h | 75 - src/blame.c | 557 ---- src/blame.h | 95 - src/blame_git.c | 685 ----- src/blame_git.h | 22 - src/blob.c | 528 ---- src/blob.h | 52 - src/branch.c | 818 ------ src/branch.h | 31 - src/buf.c | 126 - src/buf.h | 50 - src/cache.c | 253 -- src/cache.h | 69 - src/cc-compat.h | 106 - src/checkout.c | 2813 -------------------- src/checkout.h | 27 - src/cherrypick.c | 242 -- src/clone.c | 655 ----- src/clone.h | 20 - src/commit.c | 1042 -------- src/commit.h | 69 - src/commit_graph.c | 1224 --------- src/commit_graph.h | 169 -- src/commit_list.c | 208 -- src/commit_list.h | 57 - src/common.h | 214 -- src/config.c | 1566 ----------- src/config.h | 110 - src/config_backend.h | 96 - src/config_cache.c | 142 - src/config_entries.c | 237 -- src/config_entries.h | 24 - src/config_file.c | 1194 --------- src/config_mem.c | 220 -- src/config_parse.c | 580 ----- src/config_parse.h | 65 - src/config_snapshot.c | 207 -- src/crlf.c | 426 --- src/date.c | 898 ------- src/date.h | 33 - src/delta.c | 628 ----- src/delta.h | 136 - src/describe.c | 909 ------- src/diff.c | 389 --- src/diff.h | 67 - src/diff_driver.c | 522 ---- src/diff_driver.h | 52 - src/diff_file.c | 481 ---- src/diff_file.h | 63 - src/diff_generate.c | 1724 ------------ src/diff_generate.h | 130 - src/diff_parse.c | 107 - src/diff_parse.h | 20 - src/diff_print.c | 825 ------ src/diff_stats.c | 376 --- src/diff_stats.h | 18 - src/diff_tform.c | 1121 -------- src/diff_tform.h | 25 - src/diff_xdiff.c | 260 -- src/diff_xdiff.h | 35 - src/email.c | 315 --- src/email.h | 25 - src/errors.c | 238 -- src/errors.h | 80 - src/features.h.in | 53 - src/fetch.c | 211 -- src/fetch.h | 22 - src/fetchhead.c | 338 --- src/fetchhead.h | 35 - src/filebuf.c | 595 ----- src/filebuf.h | 94 - src/filter.c | 1191 --------- src/filter.h | 85 - src/fs_path.c | 1912 -------------- src/fs_path.h | 752 ------ src/futils.c | 1194 --------- src/futils.h | 402 --- src/graph.c | 249 -- src/hash.c | 142 - src/hash.h | 46 - src/hash/sha1.h | 40 - src/hash/sha1/collisiondetect.c | 48 - src/hash/sha1/collisiondetect.h | 19 - src/hash/sha1/common_crypto.c | 57 - src/hash/sha1/common_crypto.h | 19 - src/hash/sha1/generic.c | 300 --- src/hash/sha1/generic.h | 19 - src/hash/sha1/mbedtls.c | 46 - src/hash/sha1/mbedtls.h | 19 - src/hash/sha1/openssl.c | 59 - src/hash/sha1/openssl.h | 19 - src/hash/sha1/sha1dc/sha1.c | 1909 -------------- src/hash/sha1/sha1dc/sha1.h | 110 - src/hash/sha1/sha1dc/ubc_check.c | 372 --- src/hash/sha1/sha1dc/ubc_check.h | 52 - src/hash/sha1/win32.c | 333 --- src/hash/sha1/win32.h | 128 - src/hashsig.c | 375 --- src/ident.c | 139 - src/idxmap.c | 157 -- src/idxmap.h | 177 -- src/ignore.c | 652 ----- src/ignore.h | 65 - src/index.c | 3765 --------------------------- src/index.h | 194 -- src/indexer.c | 1413 ---------- src/indexer.h | 16 - src/integer.h | 218 -- src/iterator.c | 2439 ----------------- src/iterator.h | 322 --- src/khash.h | 615 ----- src/libgit2.c | 417 --- src/libgit2.h | 15 - src/libgit2/CMakeLists.txt | 305 +++ src/libgit2/alloc.c | 56 + src/libgit2/alloc.h | 40 + src/libgit2/allocators/failalloc.c | 92 + src/libgit2/allocators/failalloc.h | 23 + src/libgit2/allocators/stdalloc.c | 150 ++ src/libgit2/allocators/stdalloc.h | 17 + src/libgit2/allocators/win32_leakcheck.c | 118 + src/libgit2/allocators/win32_leakcheck.h | 17 + src/libgit2/annotated_commit.c | 240 ++ src/libgit2/annotated_commit.h | 52 + src/libgit2/apply.c | 898 +++++++ src/libgit2/apply.h | 25 + src/libgit2/array.h | 124 + src/libgit2/assert_safe.h | 58 + src/libgit2/attr.c | 700 +++++ src/libgit2/attr.h | 15 + src/libgit2/attr_file.c | 1027 ++++++++ src/libgit2/attr_file.h | 241 ++ src/libgit2/attrcache.c | 478 ++++ src/libgit2/attrcache.h | 56 + src/libgit2/bitvec.h | 75 + src/libgit2/blame.c | 557 ++++ src/libgit2/blame.h | 95 + src/libgit2/blame_git.c | 685 +++++ src/libgit2/blame_git.h | 22 + src/libgit2/blob.c | 528 ++++ src/libgit2/blob.h | 52 + src/libgit2/branch.c | 818 ++++++ src/libgit2/branch.h | 31 + src/libgit2/buf.c | 126 + src/libgit2/buf.h | 50 + src/libgit2/cache.c | 253 ++ src/libgit2/cache.h | 69 + src/libgit2/cc-compat.h | 106 + src/libgit2/checkout.c | 2813 ++++++++++++++++++++ src/libgit2/checkout.h | 27 + src/libgit2/cherrypick.c | 242 ++ src/libgit2/clone.c | 655 +++++ src/libgit2/clone.h | 20 + src/libgit2/commit.c | 1042 ++++++++ src/libgit2/commit.h | 69 + src/libgit2/commit_graph.c | 1224 +++++++++ src/libgit2/commit_graph.h | 169 ++ src/libgit2/commit_list.c | 208 ++ src/libgit2/commit_list.h | 57 + src/libgit2/common.h | 214 ++ src/libgit2/config.c | 1566 +++++++++++ src/libgit2/config.h | 110 + src/libgit2/config_backend.h | 96 + src/libgit2/config_cache.c | 142 + src/libgit2/config_entries.c | 237 ++ src/libgit2/config_entries.h | 24 + src/libgit2/config_file.c | 1194 +++++++++ src/libgit2/config_mem.c | 220 ++ src/libgit2/config_parse.c | 580 +++++ src/libgit2/config_parse.h | 65 + src/libgit2/config_snapshot.c | 207 ++ src/libgit2/crlf.c | 426 +++ src/libgit2/date.c | 898 +++++++ src/libgit2/date.h | 33 + src/libgit2/delta.c | 628 +++++ src/libgit2/delta.h | 136 + src/libgit2/describe.c | 909 +++++++ src/libgit2/diff.c | 389 +++ src/libgit2/diff.h | 67 + src/libgit2/diff_driver.c | 522 ++++ src/libgit2/diff_driver.h | 52 + src/libgit2/diff_file.c | 481 ++++ src/libgit2/diff_file.h | 63 + src/libgit2/diff_generate.c | 1724 ++++++++++++ src/libgit2/diff_generate.h | 130 + src/libgit2/diff_parse.c | 107 + src/libgit2/diff_parse.h | 20 + src/libgit2/diff_print.c | 825 ++++++ src/libgit2/diff_stats.c | 376 +++ src/libgit2/diff_stats.h | 18 + src/libgit2/diff_tform.c | 1121 ++++++++ src/libgit2/diff_tform.h | 25 + src/libgit2/diff_xdiff.c | 260 ++ src/libgit2/diff_xdiff.h | 35 + src/libgit2/email.c | 315 +++ src/libgit2/email.h | 25 + src/libgit2/errors.c | 238 ++ src/libgit2/errors.h | 80 + src/libgit2/features.h.in | 53 + src/libgit2/fetch.c | 211 ++ src/libgit2/fetch.h | 22 + src/libgit2/fetchhead.c | 338 +++ src/libgit2/fetchhead.h | 35 + src/libgit2/filebuf.c | 595 +++++ src/libgit2/filebuf.h | 94 + src/libgit2/filter.c | 1191 +++++++++ src/libgit2/filter.h | 85 + src/libgit2/fs_path.c | 1912 ++++++++++++++ src/libgit2/fs_path.h | 752 ++++++ src/libgit2/futils.c | 1194 +++++++++ src/libgit2/futils.h | 402 +++ src/libgit2/graph.c | 249 ++ src/libgit2/hash.c | 142 + src/libgit2/hash.h | 46 + src/libgit2/hash/sha1.h | 40 + src/libgit2/hash/sha1/collisiondetect.c | 48 + src/libgit2/hash/sha1/collisiondetect.h | 19 + src/libgit2/hash/sha1/common_crypto.c | 57 + src/libgit2/hash/sha1/common_crypto.h | 19 + src/libgit2/hash/sha1/generic.c | 300 +++ src/libgit2/hash/sha1/generic.h | 19 + src/libgit2/hash/sha1/mbedtls.c | 46 + src/libgit2/hash/sha1/mbedtls.h | 19 + src/libgit2/hash/sha1/openssl.c | 59 + src/libgit2/hash/sha1/openssl.h | 19 + src/libgit2/hash/sha1/sha1dc/sha1.c | 1909 ++++++++++++++ src/libgit2/hash/sha1/sha1dc/sha1.h | 110 + src/libgit2/hash/sha1/sha1dc/ubc_check.c | 372 +++ src/libgit2/hash/sha1/sha1dc/ubc_check.h | 52 + src/libgit2/hash/sha1/win32.c | 333 +++ src/libgit2/hash/sha1/win32.h | 128 + src/libgit2/hashsig.c | 375 +++ src/libgit2/ident.c | 139 + src/libgit2/idxmap.c | 157 ++ src/libgit2/idxmap.h | 177 ++ src/libgit2/ignore.c | 652 +++++ src/libgit2/ignore.h | 65 + src/libgit2/index.c | 3765 +++++++++++++++++++++++++++ src/libgit2/index.h | 194 ++ src/libgit2/indexer.c | 1413 ++++++++++ src/libgit2/indexer.h | 16 + src/libgit2/integer.h | 218 ++ src/libgit2/iterator.c | 2439 +++++++++++++++++ src/libgit2/iterator.h | 322 +++ src/libgit2/khash.h | 615 +++++ src/libgit2/libgit2.c | 417 +++ src/libgit2/libgit2.h | 15 + src/libgit2/mailmap.c | 500 ++++ src/libgit2/mailmap.h | 35 + src/libgit2/map.h | 46 + src/libgit2/merge.c | 3435 ++++++++++++++++++++++++ src/libgit2/merge.h | 204 ++ src/libgit2/merge_driver.c | 432 +++ src/libgit2/merge_driver.h | 62 + src/libgit2/merge_file.c | 327 +++ src/libgit2/message.c | 75 + src/libgit2/midx.c | 896 +++++++ src/libgit2/midx.h | 110 + src/libgit2/mwindow.c | 541 ++++ src/libgit2/mwindow.h | 54 + src/libgit2/net.c | 750 ++++++ src/libgit2/net.h | 78 + src/libgit2/netops.c | 125 + src/libgit2/netops.h | 68 + src/libgit2/notes.c | 809 ++++++ src/libgit2/notes.h | 32 + src/libgit2/object.c | 601 +++++ src/libgit2/object.h | 71 + src/libgit2/object_api.c | 148 ++ src/libgit2/odb.c | 1831 +++++++++++++ src/libgit2/odb.h | 149 ++ src/libgit2/odb_loose.c | 1182 +++++++++ src/libgit2/odb_mempack.c | 189 ++ src/libgit2/odb_pack.c | 921 +++++++ src/libgit2/offmap.c | 101 + src/libgit2/offmap.h | 133 + src/libgit2/oid.c | 463 ++++ src/libgit2/oid.h | 66 + src/libgit2/oidarray.c | 43 + src/libgit2/oidarray.h | 20 + src/libgit2/oidmap.c | 107 + src/libgit2/oidmap.h | 128 + src/libgit2/pack-objects.c | 1821 +++++++++++++ src/libgit2/pack-objects.h | 106 + src/libgit2/pack.c | 1629 ++++++++++++ src/libgit2/pack.h | 199 ++ src/libgit2/parse.c | 134 + src/libgit2/parse.h | 61 + src/libgit2/patch.c | 230 ++ src/libgit2/patch.h | 69 + src/libgit2/patch_generate.c | 915 +++++++ src/libgit2/patch_generate.h | 69 + src/libgit2/patch_parse.c | 1231 +++++++++ src/libgit2/patch_parse.h | 51 + src/libgit2/path.c | 374 +++ src/libgit2/path.h | 68 + src/libgit2/pathspec.c | 722 +++++ src/libgit2/pathspec.h | 76 + src/libgit2/pool.c | 260 ++ src/libgit2/pool.h | 146 ++ src/libgit2/posix.c | 303 +++ src/libgit2/posix.h | 196 ++ src/libgit2/pqueue.c | 125 + src/libgit2/pqueue.h | 59 + src/libgit2/proxy.c | 49 + src/libgit2/proxy.h | 17 + src/libgit2/push.c | 558 ++++ src/libgit2/push.h | 128 + src/libgit2/reader.c | 269 ++ src/libgit2/reader.h | 107 + src/libgit2/rebase.c | 1470 +++++++++++ src/libgit2/refdb.c | 424 +++ src/libgit2/refdb.h | 128 + src/libgit2/refdb_fs.c | 2464 ++++++++++++++++++ src/libgit2/reflog.c | 232 ++ src/libgit2/reflog.h | 41 + src/libgit2/refs.c | 1395 ++++++++++ src/libgit2/refs.h | 130 + src/libgit2/refspec.c | 420 +++ src/libgit2/refspec.h | 54 + src/libgit2/regexp.c | 221 ++ src/libgit2/regexp.h | 97 + src/libgit2/remote.c | 3084 ++++++++++++++++++++++ src/libgit2/remote.h | 60 + src/libgit2/repo_template.h | 58 + src/libgit2/repository.c | 3253 +++++++++++++++++++++++ src/libgit2/repository.h | 258 ++ src/libgit2/reset.c | 204 ++ src/libgit2/revert.c | 243 ++ src/libgit2/revparse.c | 949 +++++++ src/libgit2/revwalk.c | 820 ++++++ src/libgit2/revwalk.h | 73 + src/libgit2/runtime.c | 162 ++ src/libgit2/runtime.h | 62 + src/libgit2/settings.h | 11 + src/libgit2/signature.c | 339 +++ src/libgit2/signature.h | 23 + src/libgit2/sortedcache.c | 380 +++ src/libgit2/sortedcache.h | 182 ++ src/libgit2/stash.c | 1110 ++++++++ src/libgit2/status.c | 584 +++++ src/libgit2/status.h | 25 + src/libgit2/str.c | 1372 ++++++++++ src/libgit2/str.h | 357 +++ src/libgit2/strarray.c | 64 + src/libgit2/stream.h | 86 + src/libgit2/streams/mbedtls.c | 482 ++++ src/libgit2/streams/mbedtls.h | 23 + src/libgit2/streams/openssl.c | 747 ++++++ src/libgit2/streams/openssl.h | 31 + src/libgit2/streams/openssl_dynamic.c | 309 +++ src/libgit2/streams/openssl_dynamic.h | 348 +++ src/libgit2/streams/openssl_legacy.c | 203 ++ src/libgit2/streams/openssl_legacy.h | 63 + src/libgit2/streams/registry.c | 119 + src/libgit2/streams/registry.h | 19 + src/libgit2/streams/socket.c | 239 ++ src/libgit2/streams/socket.h | 23 + src/libgit2/streams/stransport.c | 326 +++ src/libgit2/streams/stransport.h | 21 + src/libgit2/streams/tls.c | 75 + src/libgit2/streams/tls.h | 31 + src/libgit2/strmap.c | 100 + src/libgit2/strmap.h | 131 + src/libgit2/strnlen.h | 24 + src/libgit2/submodule.c | 2380 +++++++++++++++++ src/libgit2/submodule.h | 164 ++ src/libgit2/sysdir.c | 363 +++ src/libgit2/sysdir.h | 113 + src/libgit2/tag.c | 570 ++++ src/libgit2/tag.h | 31 + src/libgit2/thread.c | 140 + src/libgit2/thread.h | 479 ++++ src/libgit2/threadstate.c | 84 + src/libgit2/threadstate.h | 24 + src/libgit2/trace.c | 25 + src/libgit2/trace.h | 51 + src/libgit2/trailer.c | 430 +++ src/libgit2/transaction.c | 395 +++ src/libgit2/transaction.h | 14 + src/libgit2/transport.c | 222 ++ src/libgit2/transports/auth.c | 74 + src/libgit2/transports/auth.h | 70 + src/libgit2/transports/auth_negotiate.c | 314 +++ src/libgit2/transports/auth_negotiate.h | 27 + src/libgit2/transports/auth_ntlm.c | 227 ++ src/libgit2/transports/auth_ntlm.h | 37 + src/libgit2/transports/credential.c | 486 ++++ src/libgit2/transports/credential_helpers.c | 68 + src/libgit2/transports/git.c | 361 +++ src/libgit2/transports/http.c | 760 ++++++ src/libgit2/transports/http.h | 28 + src/libgit2/transports/httpclient.c | 1579 +++++++++++ src/libgit2/transports/httpclient.h | 190 ++ src/libgit2/transports/local.c | 754 ++++++ src/libgit2/transports/smart.c | 472 ++++ src/libgit2/transports/smart.h | 193 ++ src/libgit2/transports/smart_pkt.c | 637 +++++ src/libgit2/transports/smart_protocol.c | 1094 ++++++++ src/libgit2/transports/ssh.c | 915 +++++++ src/libgit2/transports/ssh.h | 14 + src/libgit2/transports/winhttp.c | 1686 ++++++++++++ src/libgit2/tree-cache.c | 277 ++ src/libgit2/tree-cache.h | 38 + src/libgit2/tree.c | 1331 ++++++++++ src/libgit2/tree.h | 58 + src/libgit2/tsort.c | 382 +++ src/libgit2/unix/map.c | 76 + src/libgit2/unix/posix.h | 104 + src/libgit2/unix/pthread.h | 57 + src/libgit2/unix/realpath.c | 32 + src/libgit2/userdiff.h | 210 ++ src/libgit2/utf8.c | 150 ++ src/libgit2/utf8.h | 52 + src/libgit2/util.c | 819 ++++++ src/libgit2/util.h | 387 +++ src/libgit2/util/platform.h.in | 34 + src/libgit2/varint.c | 43 + src/libgit2/varint.h | 17 + src/libgit2/vector.c | 431 +++ src/libgit2/vector.h | 128 + src/libgit2/wildmatch.c | 320 +++ src/libgit2/wildmatch.h | 23 + src/libgit2/win32/dir.c | 122 + src/libgit2/win32/dir.h | 44 + src/libgit2/win32/error.c | 53 + src/libgit2/win32/error.h | 15 + src/libgit2/win32/findfile.c | 286 ++ src/libgit2/win32/findfile.h | 22 + src/libgit2/win32/git2.rc | 59 + src/libgit2/win32/map.c | 141 + src/libgit2/win32/mingw-compat.h | 23 + src/libgit2/win32/msvc-compat.h | 36 + src/libgit2/win32/path_w32.c | 642 +++++ src/libgit2/win32/path_w32.h | 91 + src/libgit2/win32/posix.h | 62 + src/libgit2/win32/posix_w32.c | 1047 ++++++++ src/libgit2/win32/precompiled.c | 1 + src/libgit2/win32/precompiled.h | 21 + src/libgit2/win32/reparse.h | 57 + src/libgit2/win32/thread.c | 262 ++ src/libgit2/win32/thread.h | 64 + src/libgit2/win32/utf-conv.c | 146 ++ src/libgit2/win32/utf-conv.h | 60 + src/libgit2/win32/version.h | 37 + src/libgit2/win32/w32_buffer.c | 57 + src/libgit2/win32/w32_buffer.h | 19 + src/libgit2/win32/w32_common.h | 48 + src/libgit2/win32/w32_leakcheck.c | 581 +++++ src/libgit2/win32/w32_leakcheck.h | 222 ++ src/libgit2/win32/w32_util.c | 126 + src/libgit2/win32/w32_util.h | 144 + src/libgit2/win32/win32-compat.h | 52 + src/libgit2/worktree.c | 652 +++++ src/libgit2/worktree.h | 39 + src/libgit2/xdiff/git-xdiff.h | 53 + src/libgit2/xdiff/xdiff.h | 150 ++ src/libgit2/xdiff/xdiffi.c | 1088 ++++++++ src/libgit2/xdiff/xdiffi.h | 64 + src/libgit2/xdiff/xemit.c | 330 +++ src/libgit2/xdiff/xemit.h | 36 + src/libgit2/xdiff/xhistogram.c | 381 +++ src/libgit2/xdiff/xinclude.h | 36 + src/libgit2/xdiff/xmacros.h | 54 + src/libgit2/xdiff/xmerge.c | 737 ++++++ src/libgit2/xdiff/xpatience.c | 382 +++ src/libgit2/xdiff/xprepare.c | 478 ++++ src/libgit2/xdiff/xprepare.h | 34 + src/libgit2/xdiff/xtypes.h | 67 + src/libgit2/xdiff/xutils.c | 434 +++ src/libgit2/xdiff/xutils.h | 47 + src/libgit2/zstream.c | 210 ++ src/libgit2/zstream.h | 54 + src/mailmap.c | 500 ---- src/mailmap.h | 35 - src/map.h | 46 - src/merge.c | 3435 ------------------------ src/merge.h | 204 -- src/merge_driver.c | 432 --- src/merge_driver.h | 62 - src/merge_file.c | 327 --- src/message.c | 75 - src/midx.c | 896 ------- src/midx.h | 110 - src/mwindow.c | 541 ---- src/mwindow.h | 54 - src/net.c | 750 ------ src/net.h | 78 - src/netops.c | 125 - src/netops.h | 68 - src/notes.c | 809 ------ src/notes.h | 32 - src/object.c | 601 ----- src/object.h | 71 - src/object_api.c | 148 -- src/odb.c | 1831 ------------- src/odb.h | 149 -- src/odb_loose.c | 1182 --------- src/odb_mempack.c | 189 -- src/odb_pack.c | 921 ------- src/offmap.c | 101 - src/offmap.h | 133 - src/oid.c | 463 ---- src/oid.h | 66 - src/oidarray.c | 43 - src/oidarray.h | 20 - src/oidmap.c | 107 - src/oidmap.h | 128 - src/pack-objects.c | 1821 ------------- src/pack-objects.h | 106 - src/pack.c | 1629 ------------ src/pack.h | 199 -- src/parse.c | 134 - src/parse.h | 61 - src/patch.c | 230 -- src/patch.h | 69 - src/patch_generate.c | 915 ------- src/patch_generate.h | 69 - src/patch_parse.c | 1231 --------- src/patch_parse.h | 51 - src/path.c | 374 --- src/path.h | 68 - src/pathspec.c | 722 ----- src/pathspec.h | 76 - src/pool.c | 260 -- src/pool.h | 146 -- src/posix.c | 303 --- src/posix.h | 196 -- src/pqueue.c | 125 - src/pqueue.h | 59 - src/proxy.c | 49 - src/proxy.h | 17 - src/push.c | 558 ---- src/push.h | 128 - src/reader.c | 269 -- src/reader.h | 107 - src/rebase.c | 1470 ----------- src/refdb.c | 424 --- src/refdb.h | 128 - src/refdb_fs.c | 2464 ------------------ src/reflog.c | 232 -- src/reflog.h | 41 - src/refs.c | 1395 ---------- src/refs.h | 130 - src/refspec.c | 420 --- src/refspec.h | 54 - src/regexp.c | 221 -- src/regexp.h | 97 - src/remote.c | 3084 ---------------------- src/remote.h | 60 - src/repo_template.h | 58 - src/repository.c | 3253 ----------------------- src/repository.h | 258 -- src/reset.c | 204 -- src/revert.c | 243 -- src/revparse.c | 949 ------- src/revwalk.c | 820 ------ src/revwalk.h | 73 - src/runtime.c | 162 -- src/runtime.h | 62 - src/settings.h | 11 - src/signature.c | 339 --- src/signature.h | 23 - src/sortedcache.c | 380 --- src/sortedcache.h | 182 -- src/stash.c | 1110 -------- src/status.c | 584 ----- src/status.h | 25 - src/str.c | 1372 ---------- src/str.h | 357 --- src/strarray.c | 64 - src/stream.h | 86 - src/streams/mbedtls.c | 482 ---- src/streams/mbedtls.h | 23 - src/streams/openssl.c | 747 ------ src/streams/openssl.h | 31 - src/streams/openssl_dynamic.c | 309 --- src/streams/openssl_dynamic.h | 348 --- src/streams/openssl_legacy.c | 203 -- src/streams/openssl_legacy.h | 63 - src/streams/registry.c | 119 - src/streams/registry.h | 19 - src/streams/socket.c | 239 -- src/streams/socket.h | 23 - src/streams/stransport.c | 326 --- src/streams/stransport.h | 21 - src/streams/tls.c | 75 - src/streams/tls.h | 31 - src/strmap.c | 100 - src/strmap.h | 131 - src/strnlen.h | 24 - src/submodule.c | 2380 ----------------- src/submodule.h | 164 -- src/sysdir.c | 363 --- src/sysdir.h | 113 - src/tag.c | 570 ---- src/tag.h | 31 - src/thread.c | 140 - src/thread.h | 479 ---- src/threadstate.c | 84 - src/threadstate.h | 24 - src/trace.c | 25 - src/trace.h | 51 - src/trailer.c | 430 --- src/transaction.c | 395 --- src/transaction.h | 14 - src/transport.c | 222 -- src/transports/auth.c | 74 - src/transports/auth.h | 70 - src/transports/auth_negotiate.c | 314 --- src/transports/auth_negotiate.h | 27 - src/transports/auth_ntlm.c | 227 -- src/transports/auth_ntlm.h | 37 - src/transports/credential.c | 486 ---- src/transports/credential_helpers.c | 68 - src/transports/git.c | 361 --- src/transports/http.c | 760 ------ src/transports/http.h | 28 - src/transports/httpclient.c | 1579 ----------- src/transports/httpclient.h | 190 -- src/transports/local.c | 754 ------ src/transports/smart.c | 472 ---- src/transports/smart.h | 193 -- src/transports/smart_pkt.c | 637 ----- src/transports/smart_protocol.c | 1094 -------- src/transports/ssh.c | 915 ------- src/transports/ssh.h | 14 - src/transports/winhttp.c | 1686 ------------ src/tree-cache.c | 277 -- src/tree-cache.h | 38 - src/tree.c | 1331 ---------- src/tree.h | 58 - src/tsort.c | 382 --- src/unix/map.c | 76 - src/unix/posix.h | 104 - src/unix/pthread.h | 57 - src/unix/realpath.c | 32 - src/userdiff.h | 210 -- src/utf8.c | 150 -- src/utf8.h | 52 - src/util.c | 819 ------ src/util.h | 387 --- src/varint.c | 43 - src/varint.h | 17 - src/vector.c | 431 --- src/vector.h | 128 - src/wildmatch.c | 320 --- src/wildmatch.h | 23 - src/win32/dir.c | 122 - src/win32/dir.h | 44 - src/win32/error.c | 53 - src/win32/error.h | 15 - src/win32/findfile.c | 286 -- src/win32/findfile.h | 22 - src/win32/git2.rc | 59 - src/win32/map.c | 141 - src/win32/mingw-compat.h | 23 - src/win32/msvc-compat.h | 36 - src/win32/path_w32.c | 642 ----- src/win32/path_w32.h | 91 - src/win32/posix.h | 62 - src/win32/posix_w32.c | 1047 -------- src/win32/precompiled.c | 1 - src/win32/precompiled.h | 21 - src/win32/reparse.h | 57 - src/win32/thread.c | 262 -- src/win32/thread.h | 64 - src/win32/utf-conv.c | 146 -- src/win32/utf-conv.h | 60 - src/win32/version.h | 37 - src/win32/w32_buffer.c | 57 - src/win32/w32_buffer.h | 19 - src/win32/w32_common.h | 48 - src/win32/w32_leakcheck.c | 581 ----- src/win32/w32_leakcheck.h | 222 -- src/win32/w32_util.c | 126 - src/win32/w32_util.h | 144 - src/win32/win32-compat.h | 52 - src/worktree.c | 652 ----- src/worktree.h | 39 - src/xdiff/git-xdiff.h | 53 - src/xdiff/xdiff.h | 150 -- src/xdiff/xdiffi.c | 1088 -------- src/xdiff/xdiffi.h | 64 - src/xdiff/xemit.c | 330 --- src/xdiff/xemit.h | 36 - src/xdiff/xhistogram.c | 381 --- src/xdiff/xinclude.h | 36 - src/xdiff/xmacros.h | 54 - src/xdiff/xmerge.c | 737 ------ src/xdiff/xpatience.c | 382 --- src/xdiff/xprepare.c | 478 ---- src/xdiff/xprepare.h | 34 - src/xdiff/xtypes.h | 67 - src/xdiff/xutils.c | 434 --- src/xdiff/xutils.h | 47 - src/zstream.c | 210 -- src/zstream.h | 54 - 719 files changed, 130294 insertions(+), 130252 deletions(-) delete mode 100644 src/alloc.c delete mode 100644 src/alloc.h delete mode 100644 src/allocators/failalloc.c delete mode 100644 src/allocators/failalloc.h delete mode 100644 src/allocators/stdalloc.c delete mode 100644 src/allocators/stdalloc.h delete mode 100644 src/allocators/win32_leakcheck.c delete mode 100644 src/allocators/win32_leakcheck.h delete mode 100644 src/annotated_commit.c delete mode 100644 src/annotated_commit.h delete mode 100644 src/apply.c delete mode 100644 src/apply.h delete mode 100644 src/array.h delete mode 100644 src/assert_safe.h delete mode 100644 src/attr.c delete mode 100644 src/attr.h delete mode 100644 src/attr_file.c delete mode 100644 src/attr_file.h delete mode 100644 src/attrcache.c delete mode 100644 src/attrcache.h delete mode 100644 src/bitvec.h delete mode 100644 src/blame.c delete mode 100644 src/blame.h delete mode 100644 src/blame_git.c delete mode 100644 src/blame_git.h delete mode 100644 src/blob.c delete mode 100644 src/blob.h delete mode 100644 src/branch.c delete mode 100644 src/branch.h delete mode 100644 src/buf.c delete mode 100644 src/buf.h delete mode 100644 src/cache.c delete mode 100644 src/cache.h delete mode 100644 src/cc-compat.h delete mode 100644 src/checkout.c delete mode 100644 src/checkout.h delete mode 100644 src/cherrypick.c delete mode 100644 src/clone.c delete mode 100644 src/clone.h delete mode 100644 src/commit.c delete mode 100644 src/commit.h delete mode 100644 src/commit_graph.c delete mode 100644 src/commit_graph.h delete mode 100644 src/commit_list.c delete mode 100644 src/commit_list.h delete mode 100644 src/common.h delete mode 100644 src/config.c delete mode 100644 src/config.h delete mode 100644 src/config_backend.h delete mode 100644 src/config_cache.c delete mode 100644 src/config_entries.c delete mode 100644 src/config_entries.h delete mode 100644 src/config_file.c delete mode 100644 src/config_mem.c delete mode 100644 src/config_parse.c delete mode 100644 src/config_parse.h delete mode 100644 src/config_snapshot.c delete mode 100644 src/crlf.c delete mode 100644 src/date.c delete mode 100644 src/date.h delete mode 100644 src/delta.c delete mode 100644 src/delta.h delete mode 100644 src/describe.c delete mode 100644 src/diff.c delete mode 100644 src/diff.h delete mode 100644 src/diff_driver.c delete mode 100644 src/diff_driver.h delete mode 100644 src/diff_file.c delete mode 100644 src/diff_file.h delete mode 100644 src/diff_generate.c delete mode 100644 src/diff_generate.h delete mode 100644 src/diff_parse.c delete mode 100644 src/diff_parse.h delete mode 100644 src/diff_print.c delete mode 100644 src/diff_stats.c delete mode 100644 src/diff_stats.h delete mode 100644 src/diff_tform.c delete mode 100644 src/diff_tform.h delete mode 100644 src/diff_xdiff.c delete mode 100644 src/diff_xdiff.h delete mode 100644 src/email.c delete mode 100644 src/email.h delete mode 100644 src/errors.c delete mode 100644 src/errors.h delete mode 100644 src/features.h.in delete mode 100644 src/fetch.c delete mode 100644 src/fetch.h delete mode 100644 src/fetchhead.c delete mode 100644 src/fetchhead.h delete mode 100644 src/filebuf.c delete mode 100644 src/filebuf.h delete mode 100644 src/filter.c delete mode 100644 src/filter.h delete mode 100644 src/fs_path.c delete mode 100644 src/fs_path.h delete mode 100644 src/futils.c delete mode 100644 src/futils.h delete mode 100644 src/graph.c delete mode 100644 src/hash.c delete mode 100644 src/hash.h delete mode 100644 src/hash/sha1.h delete mode 100644 src/hash/sha1/collisiondetect.c delete mode 100644 src/hash/sha1/collisiondetect.h delete mode 100644 src/hash/sha1/common_crypto.c delete mode 100644 src/hash/sha1/common_crypto.h delete mode 100644 src/hash/sha1/generic.c delete mode 100644 src/hash/sha1/generic.h delete mode 100644 src/hash/sha1/mbedtls.c delete mode 100644 src/hash/sha1/mbedtls.h delete mode 100644 src/hash/sha1/openssl.c delete mode 100644 src/hash/sha1/openssl.h delete mode 100644 src/hash/sha1/sha1dc/sha1.c delete mode 100644 src/hash/sha1/sha1dc/sha1.h delete mode 100644 src/hash/sha1/sha1dc/ubc_check.c delete mode 100644 src/hash/sha1/sha1dc/ubc_check.h delete mode 100644 src/hash/sha1/win32.c delete mode 100644 src/hash/sha1/win32.h delete mode 100644 src/hashsig.c delete mode 100644 src/ident.c delete mode 100644 src/idxmap.c delete mode 100644 src/idxmap.h delete mode 100644 src/ignore.c delete mode 100644 src/ignore.h delete mode 100644 src/index.c delete mode 100644 src/index.h delete mode 100644 src/indexer.c delete mode 100644 src/indexer.h delete mode 100644 src/integer.h delete mode 100644 src/iterator.c delete mode 100644 src/iterator.h delete mode 100644 src/khash.h delete mode 100644 src/libgit2.c delete mode 100644 src/libgit2.h create mode 100644 src/libgit2/CMakeLists.txt create mode 100644 src/libgit2/alloc.c create mode 100644 src/libgit2/alloc.h create mode 100644 src/libgit2/allocators/failalloc.c create mode 100644 src/libgit2/allocators/failalloc.h create mode 100644 src/libgit2/allocators/stdalloc.c create mode 100644 src/libgit2/allocators/stdalloc.h create mode 100644 src/libgit2/allocators/win32_leakcheck.c create mode 100644 src/libgit2/allocators/win32_leakcheck.h create mode 100644 src/libgit2/annotated_commit.c create mode 100644 src/libgit2/annotated_commit.h create mode 100644 src/libgit2/apply.c create mode 100644 src/libgit2/apply.h create mode 100644 src/libgit2/array.h create mode 100644 src/libgit2/assert_safe.h create mode 100644 src/libgit2/attr.c create mode 100644 src/libgit2/attr.h create mode 100644 src/libgit2/attr_file.c create mode 100644 src/libgit2/attr_file.h create mode 100644 src/libgit2/attrcache.c create mode 100644 src/libgit2/attrcache.h create mode 100644 src/libgit2/bitvec.h create mode 100644 src/libgit2/blame.c create mode 100644 src/libgit2/blame.h create mode 100644 src/libgit2/blame_git.c create mode 100644 src/libgit2/blame_git.h create mode 100644 src/libgit2/blob.c create mode 100644 src/libgit2/blob.h create mode 100644 src/libgit2/branch.c create mode 100644 src/libgit2/branch.h create mode 100644 src/libgit2/buf.c create mode 100644 src/libgit2/buf.h create mode 100644 src/libgit2/cache.c create mode 100644 src/libgit2/cache.h create mode 100644 src/libgit2/cc-compat.h create mode 100644 src/libgit2/checkout.c create mode 100644 src/libgit2/checkout.h create mode 100644 src/libgit2/cherrypick.c create mode 100644 src/libgit2/clone.c create mode 100644 src/libgit2/clone.h create mode 100644 src/libgit2/commit.c create mode 100644 src/libgit2/commit.h create mode 100644 src/libgit2/commit_graph.c create mode 100644 src/libgit2/commit_graph.h create mode 100644 src/libgit2/commit_list.c create mode 100644 src/libgit2/commit_list.h create mode 100644 src/libgit2/common.h create mode 100644 src/libgit2/config.c create mode 100644 src/libgit2/config.h create mode 100644 src/libgit2/config_backend.h create mode 100644 src/libgit2/config_cache.c create mode 100644 src/libgit2/config_entries.c create mode 100644 src/libgit2/config_entries.h create mode 100644 src/libgit2/config_file.c create mode 100644 src/libgit2/config_mem.c create mode 100644 src/libgit2/config_parse.c create mode 100644 src/libgit2/config_parse.h create mode 100644 src/libgit2/config_snapshot.c create mode 100644 src/libgit2/crlf.c create mode 100644 src/libgit2/date.c create mode 100644 src/libgit2/date.h create mode 100644 src/libgit2/delta.c create mode 100644 src/libgit2/delta.h create mode 100644 src/libgit2/describe.c create mode 100644 src/libgit2/diff.c create mode 100644 src/libgit2/diff.h create mode 100644 src/libgit2/diff_driver.c create mode 100644 src/libgit2/diff_driver.h create mode 100644 src/libgit2/diff_file.c create mode 100644 src/libgit2/diff_file.h create mode 100644 src/libgit2/diff_generate.c create mode 100644 src/libgit2/diff_generate.h create mode 100644 src/libgit2/diff_parse.c create mode 100644 src/libgit2/diff_parse.h create mode 100644 src/libgit2/diff_print.c create mode 100644 src/libgit2/diff_stats.c create mode 100644 src/libgit2/diff_stats.h create mode 100644 src/libgit2/diff_tform.c create mode 100644 src/libgit2/diff_tform.h create mode 100644 src/libgit2/diff_xdiff.c create mode 100644 src/libgit2/diff_xdiff.h create mode 100644 src/libgit2/email.c create mode 100644 src/libgit2/email.h create mode 100644 src/libgit2/errors.c create mode 100644 src/libgit2/errors.h create mode 100644 src/libgit2/features.h.in create mode 100644 src/libgit2/fetch.c create mode 100644 src/libgit2/fetch.h create mode 100644 src/libgit2/fetchhead.c create mode 100644 src/libgit2/fetchhead.h create mode 100644 src/libgit2/filebuf.c create mode 100644 src/libgit2/filebuf.h create mode 100644 src/libgit2/filter.c create mode 100644 src/libgit2/filter.h create mode 100644 src/libgit2/fs_path.c create mode 100644 src/libgit2/fs_path.h create mode 100644 src/libgit2/futils.c create mode 100644 src/libgit2/futils.h create mode 100644 src/libgit2/graph.c create mode 100644 src/libgit2/hash.c create mode 100644 src/libgit2/hash.h create mode 100644 src/libgit2/hash/sha1.h create mode 100644 src/libgit2/hash/sha1/collisiondetect.c create mode 100644 src/libgit2/hash/sha1/collisiondetect.h create mode 100644 src/libgit2/hash/sha1/common_crypto.c create mode 100644 src/libgit2/hash/sha1/common_crypto.h create mode 100644 src/libgit2/hash/sha1/generic.c create mode 100644 src/libgit2/hash/sha1/generic.h create mode 100644 src/libgit2/hash/sha1/mbedtls.c create mode 100644 src/libgit2/hash/sha1/mbedtls.h create mode 100644 src/libgit2/hash/sha1/openssl.c create mode 100644 src/libgit2/hash/sha1/openssl.h create mode 100644 src/libgit2/hash/sha1/sha1dc/sha1.c create mode 100644 src/libgit2/hash/sha1/sha1dc/sha1.h create mode 100644 src/libgit2/hash/sha1/sha1dc/ubc_check.c create mode 100644 src/libgit2/hash/sha1/sha1dc/ubc_check.h create mode 100644 src/libgit2/hash/sha1/win32.c create mode 100644 src/libgit2/hash/sha1/win32.h create mode 100644 src/libgit2/hashsig.c create mode 100644 src/libgit2/ident.c create mode 100644 src/libgit2/idxmap.c create mode 100644 src/libgit2/idxmap.h create mode 100644 src/libgit2/ignore.c create mode 100644 src/libgit2/ignore.h create mode 100644 src/libgit2/index.c create mode 100644 src/libgit2/index.h create mode 100644 src/libgit2/indexer.c create mode 100644 src/libgit2/indexer.h create mode 100644 src/libgit2/integer.h create mode 100644 src/libgit2/iterator.c create mode 100644 src/libgit2/iterator.h create mode 100644 src/libgit2/khash.h create mode 100644 src/libgit2/libgit2.c create mode 100644 src/libgit2/libgit2.h create mode 100644 src/libgit2/mailmap.c create mode 100644 src/libgit2/mailmap.h create mode 100644 src/libgit2/map.h create mode 100644 src/libgit2/merge.c create mode 100644 src/libgit2/merge.h create mode 100644 src/libgit2/merge_driver.c create mode 100644 src/libgit2/merge_driver.h create mode 100644 src/libgit2/merge_file.c create mode 100644 src/libgit2/message.c create mode 100644 src/libgit2/midx.c create mode 100644 src/libgit2/midx.h create mode 100644 src/libgit2/mwindow.c create mode 100644 src/libgit2/mwindow.h create mode 100644 src/libgit2/net.c create mode 100644 src/libgit2/net.h create mode 100644 src/libgit2/netops.c create mode 100644 src/libgit2/netops.h create mode 100644 src/libgit2/notes.c create mode 100644 src/libgit2/notes.h create mode 100644 src/libgit2/object.c create mode 100644 src/libgit2/object.h create mode 100644 src/libgit2/object_api.c create mode 100644 src/libgit2/odb.c create mode 100644 src/libgit2/odb.h create mode 100644 src/libgit2/odb_loose.c create mode 100644 src/libgit2/odb_mempack.c create mode 100644 src/libgit2/odb_pack.c create mode 100644 src/libgit2/offmap.c create mode 100644 src/libgit2/offmap.h create mode 100644 src/libgit2/oid.c create mode 100644 src/libgit2/oid.h create mode 100644 src/libgit2/oidarray.c create mode 100644 src/libgit2/oidarray.h create mode 100644 src/libgit2/oidmap.c create mode 100644 src/libgit2/oidmap.h create mode 100644 src/libgit2/pack-objects.c create mode 100644 src/libgit2/pack-objects.h create mode 100644 src/libgit2/pack.c create mode 100644 src/libgit2/pack.h create mode 100644 src/libgit2/parse.c create mode 100644 src/libgit2/parse.h create mode 100644 src/libgit2/patch.c create mode 100644 src/libgit2/patch.h create mode 100644 src/libgit2/patch_generate.c create mode 100644 src/libgit2/patch_generate.h create mode 100644 src/libgit2/patch_parse.c create mode 100644 src/libgit2/patch_parse.h create mode 100644 src/libgit2/path.c create mode 100644 src/libgit2/path.h create mode 100644 src/libgit2/pathspec.c create mode 100644 src/libgit2/pathspec.h create mode 100644 src/libgit2/pool.c create mode 100644 src/libgit2/pool.h create mode 100644 src/libgit2/posix.c create mode 100644 src/libgit2/posix.h create mode 100644 src/libgit2/pqueue.c create mode 100644 src/libgit2/pqueue.h create mode 100644 src/libgit2/proxy.c create mode 100644 src/libgit2/proxy.h create mode 100644 src/libgit2/push.c create mode 100644 src/libgit2/push.h create mode 100644 src/libgit2/reader.c create mode 100644 src/libgit2/reader.h create mode 100644 src/libgit2/rebase.c create mode 100644 src/libgit2/refdb.c create mode 100644 src/libgit2/refdb.h create mode 100644 src/libgit2/refdb_fs.c create mode 100644 src/libgit2/reflog.c create mode 100644 src/libgit2/reflog.h create mode 100644 src/libgit2/refs.c create mode 100644 src/libgit2/refs.h create mode 100644 src/libgit2/refspec.c create mode 100644 src/libgit2/refspec.h create mode 100644 src/libgit2/regexp.c create mode 100644 src/libgit2/regexp.h create mode 100644 src/libgit2/remote.c create mode 100644 src/libgit2/remote.h create mode 100644 src/libgit2/repo_template.h create mode 100644 src/libgit2/repository.c create mode 100644 src/libgit2/repository.h create mode 100644 src/libgit2/reset.c create mode 100644 src/libgit2/revert.c create mode 100644 src/libgit2/revparse.c create mode 100644 src/libgit2/revwalk.c create mode 100644 src/libgit2/revwalk.h create mode 100644 src/libgit2/runtime.c create mode 100644 src/libgit2/runtime.h create mode 100644 src/libgit2/settings.h create mode 100644 src/libgit2/signature.c create mode 100644 src/libgit2/signature.h create mode 100644 src/libgit2/sortedcache.c create mode 100644 src/libgit2/sortedcache.h create mode 100644 src/libgit2/stash.c create mode 100644 src/libgit2/status.c create mode 100644 src/libgit2/status.h create mode 100644 src/libgit2/str.c create mode 100644 src/libgit2/str.h create mode 100644 src/libgit2/strarray.c create mode 100644 src/libgit2/stream.h create mode 100644 src/libgit2/streams/mbedtls.c create mode 100644 src/libgit2/streams/mbedtls.h create mode 100644 src/libgit2/streams/openssl.c create mode 100644 src/libgit2/streams/openssl.h create mode 100644 src/libgit2/streams/openssl_dynamic.c create mode 100644 src/libgit2/streams/openssl_dynamic.h create mode 100644 src/libgit2/streams/openssl_legacy.c create mode 100644 src/libgit2/streams/openssl_legacy.h create mode 100644 src/libgit2/streams/registry.c create mode 100644 src/libgit2/streams/registry.h create mode 100644 src/libgit2/streams/socket.c create mode 100644 src/libgit2/streams/socket.h create mode 100644 src/libgit2/streams/stransport.c create mode 100644 src/libgit2/streams/stransport.h create mode 100644 src/libgit2/streams/tls.c create mode 100644 src/libgit2/streams/tls.h create mode 100644 src/libgit2/strmap.c create mode 100644 src/libgit2/strmap.h create mode 100644 src/libgit2/strnlen.h create mode 100644 src/libgit2/submodule.c create mode 100644 src/libgit2/submodule.h create mode 100644 src/libgit2/sysdir.c create mode 100644 src/libgit2/sysdir.h create mode 100644 src/libgit2/tag.c create mode 100644 src/libgit2/tag.h create mode 100644 src/libgit2/thread.c create mode 100644 src/libgit2/thread.h create mode 100644 src/libgit2/threadstate.c create mode 100644 src/libgit2/threadstate.h create mode 100644 src/libgit2/trace.c create mode 100644 src/libgit2/trace.h create mode 100644 src/libgit2/trailer.c create mode 100644 src/libgit2/transaction.c create mode 100644 src/libgit2/transaction.h create mode 100644 src/libgit2/transport.c create mode 100644 src/libgit2/transports/auth.c create mode 100644 src/libgit2/transports/auth.h create mode 100644 src/libgit2/transports/auth_negotiate.c create mode 100644 src/libgit2/transports/auth_negotiate.h create mode 100644 src/libgit2/transports/auth_ntlm.c create mode 100644 src/libgit2/transports/auth_ntlm.h create mode 100644 src/libgit2/transports/credential.c create mode 100644 src/libgit2/transports/credential_helpers.c create mode 100644 src/libgit2/transports/git.c create mode 100644 src/libgit2/transports/http.c create mode 100644 src/libgit2/transports/http.h create mode 100644 src/libgit2/transports/httpclient.c create mode 100644 src/libgit2/transports/httpclient.h create mode 100644 src/libgit2/transports/local.c create mode 100644 src/libgit2/transports/smart.c create mode 100644 src/libgit2/transports/smart.h create mode 100644 src/libgit2/transports/smart_pkt.c create mode 100644 src/libgit2/transports/smart_protocol.c create mode 100644 src/libgit2/transports/ssh.c create mode 100644 src/libgit2/transports/ssh.h create mode 100644 src/libgit2/transports/winhttp.c create mode 100644 src/libgit2/tree-cache.c create mode 100644 src/libgit2/tree-cache.h create mode 100644 src/libgit2/tree.c create mode 100644 src/libgit2/tree.h create mode 100644 src/libgit2/tsort.c create mode 100644 src/libgit2/unix/map.c create mode 100644 src/libgit2/unix/posix.h create mode 100644 src/libgit2/unix/pthread.h create mode 100644 src/libgit2/unix/realpath.c create mode 100644 src/libgit2/userdiff.h create mode 100644 src/libgit2/utf8.c create mode 100644 src/libgit2/utf8.h create mode 100644 src/libgit2/util.c create mode 100644 src/libgit2/util.h create mode 100644 src/libgit2/util/platform.h.in create mode 100644 src/libgit2/varint.c create mode 100644 src/libgit2/varint.h create mode 100644 src/libgit2/vector.c create mode 100644 src/libgit2/vector.h create mode 100644 src/libgit2/wildmatch.c create mode 100644 src/libgit2/wildmatch.h create mode 100644 src/libgit2/win32/dir.c create mode 100644 src/libgit2/win32/dir.h create mode 100644 src/libgit2/win32/error.c create mode 100644 src/libgit2/win32/error.h create mode 100644 src/libgit2/win32/findfile.c create mode 100644 src/libgit2/win32/findfile.h create mode 100644 src/libgit2/win32/git2.rc create mode 100644 src/libgit2/win32/map.c create mode 100644 src/libgit2/win32/mingw-compat.h create mode 100644 src/libgit2/win32/msvc-compat.h create mode 100644 src/libgit2/win32/path_w32.c create mode 100644 src/libgit2/win32/path_w32.h create mode 100644 src/libgit2/win32/posix.h create mode 100644 src/libgit2/win32/posix_w32.c create mode 100644 src/libgit2/win32/precompiled.c create mode 100644 src/libgit2/win32/precompiled.h create mode 100644 src/libgit2/win32/reparse.h create mode 100644 src/libgit2/win32/thread.c create mode 100644 src/libgit2/win32/thread.h create mode 100644 src/libgit2/win32/utf-conv.c create mode 100644 src/libgit2/win32/utf-conv.h create mode 100644 src/libgit2/win32/version.h create mode 100644 src/libgit2/win32/w32_buffer.c create mode 100644 src/libgit2/win32/w32_buffer.h create mode 100644 src/libgit2/win32/w32_common.h create mode 100644 src/libgit2/win32/w32_leakcheck.c create mode 100644 src/libgit2/win32/w32_leakcheck.h create mode 100644 src/libgit2/win32/w32_util.c create mode 100644 src/libgit2/win32/w32_util.h create mode 100644 src/libgit2/win32/win32-compat.h create mode 100644 src/libgit2/worktree.c create mode 100644 src/libgit2/worktree.h create mode 100644 src/libgit2/xdiff/git-xdiff.h create mode 100644 src/libgit2/xdiff/xdiff.h create mode 100644 src/libgit2/xdiff/xdiffi.c create mode 100644 src/libgit2/xdiff/xdiffi.h create mode 100644 src/libgit2/xdiff/xemit.c create mode 100644 src/libgit2/xdiff/xemit.h create mode 100644 src/libgit2/xdiff/xhistogram.c create mode 100644 src/libgit2/xdiff/xinclude.h create mode 100644 src/libgit2/xdiff/xmacros.h create mode 100644 src/libgit2/xdiff/xmerge.c create mode 100644 src/libgit2/xdiff/xpatience.c create mode 100644 src/libgit2/xdiff/xprepare.c create mode 100644 src/libgit2/xdiff/xprepare.h create mode 100644 src/libgit2/xdiff/xtypes.h create mode 100644 src/libgit2/xdiff/xutils.c create mode 100644 src/libgit2/xdiff/xutils.h create mode 100644 src/libgit2/zstream.c create mode 100644 src/libgit2/zstream.h delete mode 100644 src/mailmap.c delete mode 100644 src/mailmap.h delete mode 100644 src/map.h delete mode 100644 src/merge.c delete mode 100644 src/merge.h delete mode 100644 src/merge_driver.c delete mode 100644 src/merge_driver.h delete mode 100644 src/merge_file.c delete mode 100644 src/message.c delete mode 100644 src/midx.c delete mode 100644 src/midx.h delete mode 100644 src/mwindow.c delete mode 100644 src/mwindow.h delete mode 100644 src/net.c delete mode 100644 src/net.h delete mode 100644 src/netops.c delete mode 100644 src/netops.h delete mode 100644 src/notes.c delete mode 100644 src/notes.h delete mode 100644 src/object.c delete mode 100644 src/object.h delete mode 100644 src/object_api.c delete mode 100644 src/odb.c delete mode 100644 src/odb.h delete mode 100644 src/odb_loose.c delete mode 100644 src/odb_mempack.c delete mode 100644 src/odb_pack.c delete mode 100644 src/offmap.c delete mode 100644 src/offmap.h delete mode 100644 src/oid.c delete mode 100644 src/oid.h delete mode 100644 src/oidarray.c delete mode 100644 src/oidarray.h delete mode 100644 src/oidmap.c delete mode 100644 src/oidmap.h delete mode 100644 src/pack-objects.c delete mode 100644 src/pack-objects.h delete mode 100644 src/pack.c delete mode 100644 src/pack.h delete mode 100644 src/parse.c delete mode 100644 src/parse.h delete mode 100644 src/patch.c delete mode 100644 src/patch.h delete mode 100644 src/patch_generate.c delete mode 100644 src/patch_generate.h delete mode 100644 src/patch_parse.c delete mode 100644 src/patch_parse.h delete mode 100644 src/path.c delete mode 100644 src/path.h delete mode 100644 src/pathspec.c delete mode 100644 src/pathspec.h delete mode 100644 src/pool.c delete mode 100644 src/pool.h delete mode 100644 src/posix.c delete mode 100644 src/posix.h delete mode 100644 src/pqueue.c delete mode 100644 src/pqueue.h delete mode 100644 src/proxy.c delete mode 100644 src/proxy.h delete mode 100644 src/push.c delete mode 100644 src/push.h delete mode 100644 src/reader.c delete mode 100644 src/reader.h delete mode 100644 src/rebase.c delete mode 100644 src/refdb.c delete mode 100644 src/refdb.h delete mode 100644 src/refdb_fs.c delete mode 100644 src/reflog.c delete mode 100644 src/reflog.h delete mode 100644 src/refs.c delete mode 100644 src/refs.h delete mode 100644 src/refspec.c delete mode 100644 src/refspec.h delete mode 100644 src/regexp.c delete mode 100644 src/regexp.h delete mode 100644 src/remote.c delete mode 100644 src/remote.h delete mode 100644 src/repo_template.h delete mode 100644 src/repository.c delete mode 100644 src/repository.h delete mode 100644 src/reset.c delete mode 100644 src/revert.c delete mode 100644 src/revparse.c delete mode 100644 src/revwalk.c delete mode 100644 src/revwalk.h delete mode 100644 src/runtime.c delete mode 100644 src/runtime.h delete mode 100644 src/settings.h delete mode 100644 src/signature.c delete mode 100644 src/signature.h delete mode 100644 src/sortedcache.c delete mode 100644 src/sortedcache.h delete mode 100644 src/stash.c delete mode 100644 src/status.c delete mode 100644 src/status.h delete mode 100644 src/str.c delete mode 100644 src/str.h delete mode 100644 src/strarray.c delete mode 100644 src/stream.h delete mode 100644 src/streams/mbedtls.c delete mode 100644 src/streams/mbedtls.h delete mode 100644 src/streams/openssl.c delete mode 100644 src/streams/openssl.h delete mode 100644 src/streams/openssl_dynamic.c delete mode 100644 src/streams/openssl_dynamic.h delete mode 100644 src/streams/openssl_legacy.c delete mode 100644 src/streams/openssl_legacy.h delete mode 100644 src/streams/registry.c delete mode 100644 src/streams/registry.h delete mode 100644 src/streams/socket.c delete mode 100644 src/streams/socket.h delete mode 100644 src/streams/stransport.c delete mode 100644 src/streams/stransport.h delete mode 100644 src/streams/tls.c delete mode 100644 src/streams/tls.h delete mode 100644 src/strmap.c delete mode 100644 src/strmap.h delete mode 100644 src/strnlen.h delete mode 100644 src/submodule.c delete mode 100644 src/submodule.h delete mode 100644 src/sysdir.c delete mode 100644 src/sysdir.h delete mode 100644 src/tag.c delete mode 100644 src/tag.h delete mode 100644 src/thread.c delete mode 100644 src/thread.h delete mode 100644 src/threadstate.c delete mode 100644 src/threadstate.h delete mode 100644 src/trace.c delete mode 100644 src/trace.h delete mode 100644 src/trailer.c delete mode 100644 src/transaction.c delete mode 100644 src/transaction.h delete mode 100644 src/transport.c delete mode 100644 src/transports/auth.c delete mode 100644 src/transports/auth.h delete mode 100644 src/transports/auth_negotiate.c delete mode 100644 src/transports/auth_negotiate.h delete mode 100644 src/transports/auth_ntlm.c delete mode 100644 src/transports/auth_ntlm.h delete mode 100644 src/transports/credential.c delete mode 100644 src/transports/credential_helpers.c delete mode 100644 src/transports/git.c delete mode 100644 src/transports/http.c delete mode 100644 src/transports/http.h delete mode 100644 src/transports/httpclient.c delete mode 100644 src/transports/httpclient.h delete mode 100644 src/transports/local.c delete mode 100644 src/transports/smart.c delete mode 100644 src/transports/smart.h delete mode 100644 src/transports/smart_pkt.c delete mode 100644 src/transports/smart_protocol.c delete mode 100644 src/transports/ssh.c delete mode 100644 src/transports/ssh.h delete mode 100644 src/transports/winhttp.c delete mode 100644 src/tree-cache.c delete mode 100644 src/tree-cache.h delete mode 100644 src/tree.c delete mode 100644 src/tree.h delete mode 100644 src/tsort.c delete mode 100644 src/unix/map.c delete mode 100644 src/unix/posix.h delete mode 100644 src/unix/pthread.h delete mode 100644 src/unix/realpath.c delete mode 100644 src/userdiff.h delete mode 100644 src/utf8.c delete mode 100644 src/utf8.h delete mode 100644 src/util.c delete mode 100644 src/util.h delete mode 100644 src/varint.c delete mode 100644 src/varint.h delete mode 100644 src/vector.c delete mode 100644 src/vector.h delete mode 100644 src/wildmatch.c delete mode 100644 src/wildmatch.h delete mode 100644 src/win32/dir.c delete mode 100644 src/win32/dir.h delete mode 100644 src/win32/error.c delete mode 100644 src/win32/error.h delete mode 100644 src/win32/findfile.c delete mode 100644 src/win32/findfile.h delete mode 100644 src/win32/git2.rc delete mode 100644 src/win32/map.c delete mode 100644 src/win32/mingw-compat.h delete mode 100644 src/win32/msvc-compat.h delete mode 100644 src/win32/path_w32.c delete mode 100644 src/win32/path_w32.h delete mode 100644 src/win32/posix.h delete mode 100644 src/win32/posix_w32.c delete mode 100644 src/win32/precompiled.c delete mode 100644 src/win32/precompiled.h delete mode 100644 src/win32/reparse.h delete mode 100644 src/win32/thread.c delete mode 100644 src/win32/thread.h delete mode 100644 src/win32/utf-conv.c delete mode 100644 src/win32/utf-conv.h delete mode 100644 src/win32/version.h delete mode 100644 src/win32/w32_buffer.c delete mode 100644 src/win32/w32_buffer.h delete mode 100644 src/win32/w32_common.h delete mode 100644 src/win32/w32_leakcheck.c delete mode 100644 src/win32/w32_leakcheck.h delete mode 100644 src/win32/w32_util.c delete mode 100644 src/win32/w32_util.h delete mode 100644 src/win32/win32-compat.h delete mode 100644 src/worktree.c delete mode 100644 src/worktree.h delete mode 100644 src/xdiff/git-xdiff.h delete mode 100644 src/xdiff/xdiff.h delete mode 100644 src/xdiff/xdiffi.c delete mode 100644 src/xdiff/xdiffi.h delete mode 100644 src/xdiff/xemit.c delete mode 100644 src/xdiff/xemit.h delete mode 100644 src/xdiff/xhistogram.c delete mode 100644 src/xdiff/xinclude.h delete mode 100644 src/xdiff/xmacros.h delete mode 100644 src/xdiff/xmerge.c delete mode 100644 src/xdiff/xpatience.c delete mode 100644 src/xdiff/xprepare.c delete mode 100644 src/xdiff/xprepare.h delete mode 100644 src/xdiff/xtypes.h delete mode 100644 src/xdiff/xutils.c delete mode 100644 src/xdiff/xutils.h delete mode 100644 src/zstream.c delete mode 100644 src/zstream.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e7b54d036..f6924eff5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,307 +1,10 @@ -add_library(git2internal OBJECT) -set_target_properties(git2internal PROPERTIES C_STANDARD 90) -set_target_properties(git2internal PROPERTIES C_EXTENSIONS OFF) - - -if(DEPRECATE_HARD) - add_definitions(-DGIT_DEPRECATE_HARD) -endif() - -if(DEBUG_POOL) - set(GIT_DEBUG_POOL 1) -endif() -add_feature_info(debugpool GIT_DEBUG_POOL "debug pool allocator") - -if(DEBUG_STRICT_ALLOC) - set(GIT_DEBUG_STRICT_ALLOC 1) -endif() -add_feature_info(debugalloc GIT_DEBUG_STRICT_ALLOC "debug strict allocators") - -if(DEBUG_STRICT_OPEN) - set(GIT_DEBUG_STRICT_OPEN 1) -endif() -add_feature_info(debugopen GIT_DEBUG_STRICT_OPEN "path validation in open") - - -include(PkgBuildConfig) -include(SanitizeBool) - -# This variable will contain the libraries we need to put into -# libgit2.pc's Requires.private. That is, what we're linking to or -# what someone who's statically linking us needs to link to. -set(LIBGIT2_PC_REQUIRES "") -# This will be set later if we use the system's http-parser library or -# use iconv (OSX) and will be written to the Libs.private field in the -# pc file. -set(LIBGIT2_PC_LIBS "") - -set(LIBGIT2_INCLUDES - "${CMAKE_CURRENT_BINARY_DIR}" - "${PROJECT_SOURCE_DIR}/src" - "${PROJECT_SOURCE_DIR}/include") - -if(HAVE_FUTIMENS) - set(GIT_USE_FUTIMENS 1) -endif () -add_feature_info(futimens GIT_USE_FUTIMENS "futimens support") - -check_prototype_definition(qsort_r - "void qsort_r(void *base, size_t nmemb, size_t size, void *thunk, int (*compar)(void *, const void *, const void *))" - "" "stdlib.h" GIT_QSORT_R_BSD) - -check_prototype_definition(qsort_r - "void qsort_r(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void *arg)" - "" "stdlib.h" GIT_QSORT_R_GNU) - -check_function_exists(qsort_s GIT_QSORT_S) - -check_function_exists(getentropy GIT_RAND_GETENTROPY) - -# Find required dependencies - -if(WIN32) - list(APPEND LIBGIT2_SYSTEM_LIBS ws2_32) -elseif(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") - list(APPEND LIBGIT2_SYSTEM_LIBS socket nsl) - list(APPEND LIBGIT2_PC_LIBS "-lsocket" "-lnsl") -elseif(CMAKE_SYSTEM_NAME MATCHES "Haiku") - list(APPEND LIBGIT2_SYSTEM_LIBS network) - list(APPEND LIBGIT2_PC_LIBS "-lnetwork") -endif() - -check_library_exists(rt clock_gettime "time.h" NEED_LIBRT) -if(NEED_LIBRT) - list(APPEND LIBGIT2_SYSTEM_LIBS rt) - list(APPEND LIBGIT2_PC_LIBS "-lrt") -endif() - -if(USE_THREADS) - list(APPEND LIBGIT2_SYSTEM_LIBS ${CMAKE_THREAD_LIBS_INIT}) - list(APPEND LIBGIT2_PC_LIBS ${CMAKE_THREAD_LIBS_INIT}) -endif() -add_feature_info(threadsafe USE_THREADS "threadsafe support") - - -if(WIN32 AND EMBED_SSH_PATH) - file(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c") - list(SORT SRC_SSH) - target_sources(git2internal PRIVATE ${SRC_SSH}) - - list(APPEND LIBGIT2_SYSTEM_INCLUDES "${EMBED_SSH_PATH}/include") - file(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"") - set(GIT_SSH 1) -endif() - -include(SelectHTTPSBackend) -include(SelectHashes) -include(SelectHTTPParser) -include(SelectRegex) -include(SelectSSH) -include(SelectWinHTTP) -include(SelectZlib) - - -if(USE_SHA1 STREQUAL "CollisionDetection") - file(GLOB SRC_SHA1 hash/sha1/collisiondetect.* hash/sha1/sha1dc/*) -elseif(USE_SHA1 STREQUAL "OpenSSL") - file(GLOB SRC_SHA1 hash/sha1/openssl.*) -elseif(USE_SHA1 STREQUAL "CommonCrypto") - file(GLOB SRC_SHA1 hash/sha1/common_crypto.*) -elseif(USE_SHA1 STREQUAL "mbedTLS") - file(GLOB SRC_SHA1 hash/sha1/mbedtls.*) -elseif(USE_SHA1 STREQUAL "Win32") - file(GLOB SRC_SHA1 hash/sha1/win32.*) -elseif(USE_SHA1 STREQUAL "Generic") - file(GLOB SRC_SHA1 hash/sha1/generic.*) -endif() -list(APPEND SRC_SHA1 "hash/sha1.h") -target_sources(git2internal PRIVATE ${SRC_SHA1}) - -# Optional external dependency: ntlmclient -if(USE_NTLMCLIENT) - set(GIT_NTLM 1) - add_subdirectory("${PROJECT_SOURCE_DIR}/deps/ntlmclient" "${PROJECT_BINARY_DIR}/deps/ntlmclient") - list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/ntlmclient") - list(APPEND LIBGIT2_DEPENDENCY_OBJECTS "$") -endif() -add_feature_info(ntlmclient GIT_NTLM "NTLM authentication support for Unix") - -# Optional external dependency: GSSAPI - -include(SelectGSSAPI) - -# Optional external dependency: iconv -if(USE_ICONV) - find_package(Iconv) -endif() -if(ICONV_FOUND) - set(GIT_USE_ICONV 1) - list(APPEND LIBGIT2_SYSTEM_INCLUDES ${ICONV_INCLUDE_DIR}) - list(APPEND LIBGIT2_SYSTEM_LIBS ${ICONV_LIBRARIES}) - list(APPEND LIBGIT2_PC_LIBS ${ICONV_LIBRARIES}) -endif() -add_feature_info(iconv GIT_USE_ICONV "iconv encoding conversion support") - - -if(USE_THREADS) - if(NOT WIN32) - find_package(Threads REQUIRED) - endif() - - set(GIT_THREADS 1) -endif() - -if(USE_NSEC) - set(GIT_USE_NSEC 1) -endif() - -if(HAVE_STRUCT_STAT_ST_MTIM) - set(GIT_USE_STAT_MTIM 1) -elseif(HAVE_STRUCT_STAT_ST_MTIMESPEC) - set(GIT_USE_STAT_MTIMESPEC 1) -elseif(HAVE_STRUCT_STAT_ST_MTIME_NSEC) - set(GIT_USE_STAT_MTIME_NSEC 1) -endif() - -target_compile_definitions(git2internal PRIVATE _FILE_OFFSET_BITS=64) - -# Collect sourcefiles -file(GLOB SRC_H - "${PROJECT_SOURCE_DIR}/include/git2.h" - "${PROJECT_SOURCE_DIR}/include/git2/*.h" - "${PROJECT_SOURCE_DIR}/include/git2/sys/*.h") -list(SORT SRC_H) -target_sources(git2internal PRIVATE ${SRC_H}) - -# On Windows use specific platform sources -if(WIN32 AND NOT CYGWIN) - set(WIN_RC "win32/git2.rc") - - file(GLOB SRC_OS win32/*.c win32/*.h) - list(SORT SRC_OS) - target_sources(git2internal PRIVATE ${SRC_OS}) -elseif(AMIGA) - target_compile_definitions(git2internal PRIVATE NO_ADDRINFO NO_READDIR_R NO_MMAP) -else() - file(GLOB SRC_OS unix/*.c unix/*.h) - list(SORT SRC_OS) - target_sources(git2internal PRIVATE ${SRC_OS}) -endif() - -if(USE_LEAK_CHECKER STREQUAL "valgrind") - target_compile_definitions(git2internal PRIVATE VALGRIND) -endif() - -file(GLOB SRC_GIT2 *.c *.h - allocators/*.c allocators/*.h - streams/*.c streams/*.h - transports/*.c transports/*.h - xdiff/*.c xdiff/*.h) -list(SORT SRC_GIT2) -target_sources(git2internal PRIVATE ${SRC_GIT2}) - -if(APPLE) - # The old Secure Transport API has been deprecated in macOS 10.15. - set_source_files_properties(streams/stransport.c PROPERTIES COMPILE_FLAGS -Wno-deprecated) -endif() - -# the xdiff dependency is not (yet) warning-free, disable warnings as -# errors for the xdiff sources until we've sorted them out -if(MSVC) - set_source_files_properties(xdiff/xdiffi.c PROPERTIES COMPILE_FLAGS -WX-) - set_source_files_properties(xdiff/xemit.c PROPERTIES COMPILE_FLAGS -WX-) - set_source_files_properties(xdiff/xhistogram.c PROPERTIES COMPILE_FLAGS -WX-) - set_source_files_properties(xdiff/xmerge.c PROPERTIES COMPILE_FLAGS -WX-) - set_source_files_properties(xdiff/xutils.c PROPERTIES COMPILE_FLAGS -WX-) - set_source_files_properties(xdiff/xpatience.c PROPERTIES COMPILE_FLAGS -WX-) -else() - set_source_files_properties(xdiff/xdiffi.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare -Wno-unused-parameter") - set_source_files_properties(xdiff/xemit.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare -Wno-unused-parameter") - set_source_files_properties(xdiff/xhistogram.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") - set_source_files_properties(xdiff/xutils.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") - set_source_files_properties(xdiff/xpatience.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") -endif() - -# Determine architecture of the machine -if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(GIT_ARCH_64 1) -elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(GIT_ARCH_32 1) -elseif(CMAKE_SIZEOF_VOID_P) - message(FATAL_ERROR "Unsupported architecture (pointer size is ${CMAKE_SIZEOF_VOID_P} bytes)") -else() - message(FATAL_ERROR "Unsupported architecture (CMAKE_SIZEOF_VOID_P is unset)") -endif() - -configure_file(features.h.in git2/sys/features.h) - -ide_split_sources(git2internal) -list(APPEND LIBGIT2_OBJECTS $ ${LIBGIT2_DEPENDENCY_OBJECTS}) - -target_include_directories(git2internal PRIVATE ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES} PUBLIC ${PROJECT_SOURCE_DIR}/include) -target_include_directories(git2internal SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) +add_subdirectory(libgit2) +# re-export these to the root so that peer projects (tests, fuzzers, +# examples) can use them set(LIBGIT2_INCLUDES ${LIBGIT2_INCLUDES} PARENT_SCOPE) set(LIBGIT2_OBJECTS ${LIBGIT2_OBJECTS} PARENT_SCOPE) set(LIBGIT2_DEPENDENCY_INCLUDES ${LIBGIT2_DEPENDENCY_INCLUDES} PARENT_SCOPE) set(LIBGIT2_DEPENDENCY_OBJECTS ${LIBGIT2_DEPENDENCY_OBJECTS} PARENT_SCOPE) set(LIBGIT2_SYSTEM_INCLUDES ${LIBGIT2_SYSTEM_INCLUDES} PARENT_SCOPE) set(LIBGIT2_SYSTEM_LIBS ${LIBGIT2_SYSTEM_LIBS} PARENT_SCOPE) - -if(XCODE_VERSION) - # This is required for Xcode to actually link the libgit2 library - # when using only object libraries. - file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/dummy.c "") - list(APPEND LIBGIT2_OBJECTS ${CMAKE_CURRENT_BINARY_DIR}/dummy.c) -endif() - -# Compile and link libgit2 -add_library(git2 ${WIN_RC} ${LIBGIT2_OBJECTS}) -target_link_libraries(git2 ${LIBGIT2_SYSTEM_LIBS}) - -set_target_properties(git2 PROPERTIES C_STANDARD 90) -set_target_properties(git2 PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) -set_target_properties(git2 PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) -set_target_properties(git2 PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) - -# Workaround for Cmake bug #0011240 (see http://public.kitware.com/Bug/view.php?id=11240) -# Win64+MSVC+static libs = linker error -if(MSVC AND GIT_ARCH_64 AND NOT BUILD_SHARED_LIBS) - set_target_properties(git2 PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64") -endif() - -ide_split_sources(git2) - -if(SONAME) - set_target_properties(git2 PROPERTIES VERSION ${libgit2_VERSION}) - set_target_properties(git2 PROPERTIES SOVERSION "${libgit2_VERSION_MAJOR}.${libgit2_VERSION_MINOR}") - if(LIBGIT2_FILENAME) - target_compile_definitions(git2 PRIVATE LIBGIT2_FILENAME=\"${LIBGIT2_FILENAME}\") - set_target_properties(git2 PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME}) - elseif(DEFINED LIBGIT2_PREFIX) - set_target_properties(git2 PROPERTIES PREFIX "${LIBGIT2_PREFIX}") - endif() -endif() - -pkg_build_config(NAME libgit2 - VERSION ${libgit2_VERSION} - DESCRIPTION "The git library, take 2" - LIBS_SELF git2 - PRIVATE_LIBS ${LIBGIT2_PC_LIBS} - REQUIRES ${LIBGIT2_PC_REQUIRES} -) - -if(MSVC_IDE) - # Precompiled headers - set_target_properties(git2 PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") - set_source_files_properties(win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h") -endif() - -# Install -install(TARGETS git2 - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/git2 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -install(FILES ${PROJECT_SOURCE_DIR}/include/git2.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/alloc.c b/src/alloc.c deleted file mode 100644 index 2820d84a2..000000000 --- a/src/alloc.c +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "alloc.h" -#include "runtime.h" - -#include "allocators/failalloc.h" -#include "allocators/stdalloc.h" -#include "allocators/win32_leakcheck.h" - -/* Fail any allocation until git_libgit2_init is called. */ -git_allocator git__allocator = { - git_failalloc_malloc, - git_failalloc_calloc, - git_failalloc_strdup, - git_failalloc_strndup, - git_failalloc_substrdup, - git_failalloc_realloc, - git_failalloc_reallocarray, - git_failalloc_mallocarray, - git_failalloc_free -}; - -static int setup_default_allocator(void) -{ -#if defined(GIT_WIN32_LEAKCHECK) - return git_win32_leakcheck_init_allocator(&git__allocator); -#else - return git_stdalloc_init_allocator(&git__allocator); -#endif -} - -int git_allocator_global_init(void) -{ - /* - * We don't want to overwrite any allocator which has been set - * before the init function is called. - */ - if (git__allocator.gmalloc != git_failalloc_malloc) - return 0; - - return setup_default_allocator(); -} - -int git_allocator_setup(git_allocator *allocator) -{ - if (!allocator) - return setup_default_allocator(); - - memcpy(&git__allocator, allocator, sizeof(*allocator)); - return 0; -} diff --git a/src/alloc.h b/src/alloc.h deleted file mode 100644 index 04fb7e101..000000000 --- a/src/alloc.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_alloc_h__ -#define INCLUDE_alloc_h__ - -#include "git2/sys/alloc.h" - -extern git_allocator git__allocator; - -#define git__malloc(len) git__allocator.gmalloc(len, __FILE__, __LINE__) -#define git__calloc(nelem, elsize) git__allocator.gcalloc(nelem, elsize, __FILE__, __LINE__) -#define git__strdup(str) git__allocator.gstrdup(str, __FILE__, __LINE__) -#define git__strndup(str, n) git__allocator.gstrndup(str, n, __FILE__, __LINE__) -#define git__substrdup(str, n) git__allocator.gsubstrdup(str, n, __FILE__, __LINE__) -#define git__realloc(ptr, size) git__allocator.grealloc(ptr, size, __FILE__, __LINE__) -#define git__reallocarray(ptr, nelem, elsize) git__allocator.greallocarray(ptr, nelem, elsize, __FILE__, __LINE__) -#define git__mallocarray(nelem, elsize) git__allocator.gmallocarray(nelem, elsize, __FILE__, __LINE__) -#define git__free git__allocator.gfree - -/** - * This function is being called by our global setup routines to - * initialize the standard allocator. - */ -int git_allocator_global_init(void); - -/** - * Switch out libgit2's global memory allocator - * - * @param allocator The new allocator that should be used. All function pointers - * of it need to be set correctly. - * @return An error code or 0. - */ -int git_allocator_setup(git_allocator *allocator); - -#endif diff --git a/src/allocators/failalloc.c b/src/allocators/failalloc.c deleted file mode 100644 index 5257d1dec..000000000 --- a/src/allocators/failalloc.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "failalloc.h" - -void *git_failalloc_malloc(size_t len, const char *file, int line) -{ - GIT_UNUSED(len); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void *git_failalloc_calloc(size_t nelem, size_t elsize, const char *file, int line) -{ - GIT_UNUSED(nelem); - GIT_UNUSED(elsize); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -char *git_failalloc_strdup(const char *str, const char *file, int line) -{ - GIT_UNUSED(str); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -char *git_failalloc_strndup(const char *str, size_t n, const char *file, int line) -{ - GIT_UNUSED(str); - GIT_UNUSED(n); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -char *git_failalloc_substrdup(const char *start, size_t n, const char *file, int line) -{ - GIT_UNUSED(start); - GIT_UNUSED(n); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line) -{ - GIT_UNUSED(ptr); - GIT_UNUSED(size); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void *git_failalloc_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) -{ - GIT_UNUSED(ptr); - GIT_UNUSED(nelem); - GIT_UNUSED(elsize); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void *git_failalloc_mallocarray(size_t nelem, size_t elsize, const char *file, int line) -{ - GIT_UNUSED(nelem); - GIT_UNUSED(elsize); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void git_failalloc_free(void *ptr) -{ - GIT_UNUSED(ptr); -} diff --git a/src/allocators/failalloc.h b/src/allocators/failalloc.h deleted file mode 100644 index 6115e51e7..000000000 --- a/src/allocators/failalloc.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_allocators_failalloc_h__ -#define INCLUDE_allocators_failalloc_h__ - -#include "common.h" - -extern void *git_failalloc_malloc(size_t len, const char *file, int line); -extern void *git_failalloc_calloc(size_t nelem, size_t elsize, const char *file, int line); -extern char *git_failalloc_strdup(const char *str, const char *file, int line); -extern char *git_failalloc_strndup(const char *str, size_t n, const char *file, int line); -extern char *git_failalloc_substrdup(const char *start, size_t n, const char *file, int line); -extern void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line); -extern void *git_failalloc_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line); -extern void *git_failalloc_mallocarray(size_t nelem, size_t elsize, const char *file, int line); -extern void git_failalloc_free(void *ptr); - -#endif diff --git a/src/allocators/stdalloc.c b/src/allocators/stdalloc.c deleted file mode 100644 index 2b36d9f3d..000000000 --- a/src/allocators/stdalloc.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "stdalloc.h" - -static void *stdalloc__malloc(size_t len, const char *file, int line) -{ - void *ptr; - - GIT_UNUSED(file); - GIT_UNUSED(line); - -#ifdef GIT_DEBUG_STRICT_ALLOC - if (!len) - return NULL; -#endif - - ptr = malloc(len); - - if (!ptr) - git_error_set_oom(); - - return ptr; -} - -static void *stdalloc__calloc(size_t nelem, size_t elsize, const char *file, int line) -{ - void *ptr; - - GIT_UNUSED(file); - GIT_UNUSED(line); - -#ifdef GIT_DEBUG_STRICT_ALLOC - if (!elsize || !nelem) - return NULL; -#endif - - ptr = calloc(nelem, elsize); - - if (!ptr) - git_error_set_oom(); - - return ptr; -} - -static char *stdalloc__strdup(const char *str, const char *file, int line) -{ - char *ptr; - - GIT_UNUSED(file); - GIT_UNUSED(line); - - ptr = strdup(str); - - if (!ptr) - git_error_set_oom(); - - return ptr; -} - -static char *stdalloc__strndup(const char *str, size_t n, const char *file, int line) -{ - size_t length = 0, alloclength; - char *ptr; - - length = p_strnlen(str, n); - - if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) || - !(ptr = stdalloc__malloc(alloclength, file, line))) - return NULL; - - if (length) - memcpy(ptr, str, length); - - ptr[length] = '\0'; - - return ptr; -} - -static char *stdalloc__substrdup(const char *start, size_t n, const char *file, int line) -{ - char *ptr; - size_t alloclen; - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) || - !(ptr = stdalloc__malloc(alloclen, file, line))) - return NULL; - - memcpy(ptr, start, n); - ptr[n] = '\0'; - return ptr; -} - -static void *stdalloc__realloc(void *ptr, size_t size, const char *file, int line) -{ - void *new_ptr; - - GIT_UNUSED(file); - GIT_UNUSED(line); - -#ifdef GIT_DEBUG_STRICT_ALLOC - if (!size) - return NULL; -#endif - - new_ptr = realloc(ptr, size); - - if (!new_ptr) - git_error_set_oom(); - - return new_ptr; -} - -static void *stdalloc__reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) -{ - size_t newsize; - - if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize)) - return NULL; - - return stdalloc__realloc(ptr, newsize, file, line); -} - -static void *stdalloc__mallocarray(size_t nelem, size_t elsize, const char *file, int line) -{ - return stdalloc__reallocarray(NULL, nelem, elsize, file, line); -} - -static void stdalloc__free(void *ptr) -{ - free(ptr); -} - -int git_stdalloc_init_allocator(git_allocator *allocator) -{ - allocator->gmalloc = stdalloc__malloc; - allocator->gcalloc = stdalloc__calloc; - allocator->gstrdup = stdalloc__strdup; - allocator->gstrndup = stdalloc__strndup; - allocator->gsubstrdup = stdalloc__substrdup; - allocator->grealloc = stdalloc__realloc; - allocator->greallocarray = stdalloc__reallocarray; - allocator->gmallocarray = stdalloc__mallocarray; - allocator->gfree = stdalloc__free; - return 0; -} diff --git a/src/allocators/stdalloc.h b/src/allocators/stdalloc.h deleted file mode 100644 index fa23fe6e3..000000000 --- a/src/allocators/stdalloc.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_allocators_stdalloc_h__ -#define INCLUDE_allocators_stdalloc_h__ - -#include "common.h" - -#include "alloc.h" - -int git_stdalloc_init_allocator(git_allocator *allocator); - -#endif diff --git a/src/allocators/win32_leakcheck.c b/src/allocators/win32_leakcheck.c deleted file mode 100644 index fe06a14af..000000000 --- a/src/allocators/win32_leakcheck.c +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "win32_leakcheck.h" - -#if defined(GIT_WIN32_LEAKCHECK) - -#include "win32/w32_leakcheck.h" - -static void *leakcheck_malloc(size_t len, const char *file, int line) -{ - void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); - if (!ptr) git_error_set_oom(); - return ptr; -} - -static void *leakcheck_calloc(size_t nelem, size_t elsize, const char *file, int line) -{ - void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); - if (!ptr) git_error_set_oom(); - return ptr; -} - -static char *leakcheck_strdup(const char *str, const char *file, int line) -{ - char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); - if (!ptr) git_error_set_oom(); - return ptr; -} - -static char *leakcheck_strndup(const char *str, size_t n, const char *file, int line) -{ - size_t length = 0, alloclength; - char *ptr; - - length = p_strnlen(str, n); - - if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) || - !(ptr = leakcheck_malloc(alloclength, file, line))) - return NULL; - - if (length) - memcpy(ptr, str, length); - - ptr[length] = '\0'; - - return ptr; -} - -static char *leakcheck_substrdup(const char *start, size_t n, const char *file, int line) -{ - char *ptr; - size_t alloclen; - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) || - !(ptr = leakcheck_malloc(alloclen, file, line))) - return NULL; - - memcpy(ptr, start, n); - ptr[n] = '\0'; - return ptr; -} - -static void *leakcheck_realloc(void *ptr, size_t size, const char *file, int line) -{ - void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); - if (!new_ptr) git_error_set_oom(); - return new_ptr; -} - -static void *leakcheck_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) -{ - size_t newsize; - - if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize)) - return NULL; - - return leakcheck_realloc(ptr, newsize, file, line); -} - -static void *leakcheck_mallocarray(size_t nelem, size_t elsize, const char *file, int line) -{ - return leakcheck_reallocarray(NULL, nelem, elsize, file, line); -} - -static void leakcheck_free(void *ptr) -{ - free(ptr); -} - -int git_win32_leakcheck_init_allocator(git_allocator *allocator) -{ - allocator->gmalloc = leakcheck_malloc; - allocator->gcalloc = leakcheck_calloc; - allocator->gstrdup = leakcheck_strdup; - allocator->gstrndup = leakcheck_strndup; - allocator->gsubstrdup = leakcheck_substrdup; - allocator->grealloc = leakcheck_realloc; - allocator->greallocarray = leakcheck_reallocarray; - allocator->gmallocarray = leakcheck_mallocarray; - allocator->gfree = leakcheck_free; - return 0; -} - -#else - -int git_win32_leakcheck_init_allocator(git_allocator *allocator) -{ - GIT_UNUSED(allocator); - git_error_set(GIT_EINVALID, "leakcheck memory allocator not available"); - return -1; -} - -#endif diff --git a/src/allocators/win32_leakcheck.h b/src/allocators/win32_leakcheck.h deleted file mode 100644 index 089690f90..000000000 --- a/src/allocators/win32_leakcheck.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_allocators_win32_leakcheck_h -#define INCLUDE_allocators_win32_leakcheck_h - -#include "common.h" - -#include "alloc.h" - -int git_win32_leakcheck_init_allocator(git_allocator *allocator); - -#endif diff --git a/src/annotated_commit.c b/src/annotated_commit.c deleted file mode 100644 index e48947679..000000000 --- a/src/annotated_commit.c +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "annotated_commit.h" - -#include "refs.h" -#include "cache.h" - -#include "git2/commit.h" -#include "git2/refs.h" -#include "git2/repository.h" -#include "git2/annotated_commit.h" -#include "git2/revparse.h" -#include "git2/tree.h" -#include "git2/index.h" - -static int annotated_commit_init( - git_annotated_commit **out, - git_commit *commit, - const char *description) -{ - git_annotated_commit *annotated_commit; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(commit); - - *out = NULL; - - annotated_commit = git__calloc(1, sizeof(git_annotated_commit)); - GIT_ERROR_CHECK_ALLOC(annotated_commit); - - annotated_commit->type = GIT_ANNOTATED_COMMIT_REAL; - - if ((error = git_commit_dup(&annotated_commit->commit, commit)) < 0) - goto done; - - git_oid_fmt(annotated_commit->id_str, git_commit_id(commit)); - annotated_commit->id_str[GIT_OID_HEXSZ] = '\0'; - - if (!description) - description = annotated_commit->id_str; - - annotated_commit->description = git__strdup(description); - GIT_ERROR_CHECK_ALLOC(annotated_commit->description); - -done: - if (!error) - *out = annotated_commit; - - return error; -} - -static int annotated_commit_init_from_id( - git_annotated_commit **out, - git_repository *repo, - const git_oid *id, - const char *description) -{ - git_commit *commit = NULL; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(id); - - *out = NULL; - - if ((error = git_commit_lookup(&commit, repo, id)) < 0) - goto done; - - error = annotated_commit_init(out, commit, description); - -done: - git_commit_free(commit); - return error; -} - -int git_annotated_commit_lookup( - git_annotated_commit **out, - git_repository *repo, - const git_oid *id) -{ - return annotated_commit_init_from_id(out, repo, id, NULL); -} - -int git_annotated_commit_from_commit( - git_annotated_commit **out, - git_commit *commit) -{ - return annotated_commit_init(out, commit, NULL); -} - -int git_annotated_commit_from_revspec( - git_annotated_commit **out, - git_repository *repo, - const char *revspec) -{ - git_object *obj, *commit; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(revspec); - - if ((error = git_revparse_single(&obj, repo, revspec)) < 0) - return error; - - if ((error = git_object_peel(&commit, obj, GIT_OBJECT_COMMIT))) { - git_object_free(obj); - return error; - } - - error = annotated_commit_init(out, (git_commit *)commit, revspec); - - git_object_free(obj); - git_object_free(commit); - - return error; -} - -int git_annotated_commit_from_ref( - git_annotated_commit **out, - git_repository *repo, - const git_reference *ref) -{ - git_object *peeled; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(ref); - - *out = NULL; - - if ((error = git_reference_peel(&peeled, ref, GIT_OBJECT_COMMIT)) < 0) - return error; - - error = annotated_commit_init_from_id(out, - repo, - git_object_id(peeled), - git_reference_name(ref)); - - if (!error) { - (*out)->ref_name = git__strdup(git_reference_name(ref)); - GIT_ERROR_CHECK_ALLOC((*out)->ref_name); - } - - git_object_free(peeled); - return error; -} - -int git_annotated_commit_from_head( - git_annotated_commit **out, - git_repository *repo) -{ - git_reference *head; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0) - return -1; - - error = git_annotated_commit_from_ref(out, repo, head); - - git_reference_free(head); - return error; -} - -int git_annotated_commit_from_fetchhead( - git_annotated_commit **out, - git_repository *repo, - const char *branch_name, - const char *remote_url, - const git_oid *id) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(branch_name); - GIT_ASSERT_ARG(remote_url); - GIT_ASSERT_ARG(id); - - if (annotated_commit_init_from_id(out, repo, id, branch_name) < 0) - return -1; - - (*out)->ref_name = git__strdup(branch_name); - GIT_ERROR_CHECK_ALLOC((*out)->ref_name); - - (*out)->remote_url = git__strdup(remote_url); - GIT_ERROR_CHECK_ALLOC((*out)->remote_url); - - return 0; -} - - -const git_oid *git_annotated_commit_id( - const git_annotated_commit *annotated_commit) -{ - GIT_ASSERT_ARG_WITH_RETVAL(annotated_commit, NULL); - return git_commit_id(annotated_commit->commit); -} - -const char *git_annotated_commit_ref( - const git_annotated_commit *annotated_commit) -{ - GIT_ASSERT_ARG_WITH_RETVAL(annotated_commit, NULL); - return annotated_commit->ref_name; -} - -void git_annotated_commit_free(git_annotated_commit *annotated_commit) -{ - if (annotated_commit == NULL) - return; - - switch (annotated_commit->type) { - case GIT_ANNOTATED_COMMIT_REAL: - git_commit_free(annotated_commit->commit); - git_tree_free(annotated_commit->tree); - git__free((char *)annotated_commit->description); - git__free((char *)annotated_commit->ref_name); - git__free((char *)annotated_commit->remote_url); - break; - case GIT_ANNOTATED_COMMIT_VIRTUAL: - git_index_free(annotated_commit->index); - git_array_clear(annotated_commit->parents); - break; - default: - abort(); - } - - git__free(annotated_commit); -} diff --git a/src/annotated_commit.h b/src/annotated_commit.h deleted file mode 100644 index 444a2ed10..000000000 --- a/src/annotated_commit.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_annotated_commit_h__ -#define INCLUDE_annotated_commit_h__ - -#include "common.h" - -#include "oidarray.h" - -#include "git2/oid.h" - -typedef enum { - GIT_ANNOTATED_COMMIT_REAL = 1, - GIT_ANNOTATED_COMMIT_VIRTUAL = 2 -} git_annotated_commit_t; - -/** - * Internal structure for merge inputs. An annotated commit is generally - * "real" and backed by an actual commit in the repository, but merge will - * internally create "virtual" commits that are in-memory intermediate - * commits backed by an index. - */ -struct git_annotated_commit { - git_annotated_commit_t type; - - /* real commit */ - git_commit *commit; - git_tree *tree; - - /* virtual commit structure */ - git_index *index; - git_array_oid_t parents; - - /* how this commit was looked up */ - const char *description; - - const char *ref_name; - const char *remote_url; - - char id_str[GIT_OID_HEXSZ+1]; -}; - -extern int git_annotated_commit_from_head(git_annotated_commit **out, - git_repository *repo); -extern int git_annotated_commit_from_commit(git_annotated_commit **out, - git_commit *commit); - -#endif diff --git a/src/apply.c b/src/apply.c deleted file mode 100644 index 18304da4d..000000000 --- a/src/apply.c +++ /dev/null @@ -1,898 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "git2/apply.h" -#include "git2/patch.h" -#include "git2/filter.h" -#include "git2/blob.h" -#include "git2/index.h" -#include "git2/checkout.h" -#include "git2/repository.h" -#include "array.h" -#include "patch.h" -#include "futils.h" -#include "delta.h" -#include "zstream.h" -#include "reader.h" -#include "index.h" -#include "apply.h" - -typedef struct { - /* The lines that we allocate ourself are allocated out of the pool. - * (Lines may have been allocated out of the diff.) - */ - git_pool pool; - git_vector lines; -} patch_image; - -static int apply_err(const char *fmt, ...) GIT_FORMAT_PRINTF(1, 2); -static int apply_err(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - git_error_vset(GIT_ERROR_PATCH, fmt, ap); - va_end(ap); - - return GIT_EAPPLYFAIL; -} - -static void patch_line_init( - git_diff_line *out, - const char *in, - size_t in_len, - size_t in_offset) -{ - out->content = in; - out->content_len = in_len; - out->content_offset = in_offset; -} - -#define PATCH_IMAGE_INIT { GIT_POOL_INIT, GIT_VECTOR_INIT } - -static int patch_image_init_fromstr( - patch_image *out, const char *in, size_t in_len) -{ - git_diff_line *line; - const char *start, *end; - - memset(out, 0x0, sizeof(patch_image)); - - if (git_pool_init(&out->pool, sizeof(git_diff_line)) < 0) - return -1; - - if (!in_len) - return 0; - - for (start = in; start < in + in_len; start = end) { - end = memchr(start, '\n', in_len - (start - in)); - - if (end == NULL) - end = in + in_len; - - else if (end < in + in_len) - end++; - - line = git_pool_mallocz(&out->pool, 1); - GIT_ERROR_CHECK_ALLOC(line); - - if (git_vector_insert(&out->lines, line) < 0) - return -1; - - patch_line_init(line, start, (end - start), (start - in)); - } - - return 0; -} - -static void patch_image_free(patch_image *image) -{ - if (image == NULL) - return; - - git_pool_clear(&image->pool); - git_vector_free(&image->lines); -} - -static bool match_hunk( - patch_image *image, - patch_image *preimage, - size_t linenum) -{ - bool match = 0; - size_t i; - - /* Ensure this hunk is within the image boundaries. */ - if (git_vector_length(&preimage->lines) + linenum > - git_vector_length(&image->lines)) - return 0; - - match = 1; - - /* Check exact match. */ - for (i = 0; i < git_vector_length(&preimage->lines); i++) { - git_diff_line *preimage_line = git_vector_get(&preimage->lines, i); - git_diff_line *image_line = git_vector_get(&image->lines, linenum + i); - - if (preimage_line->content_len != image_line->content_len || - memcmp(preimage_line->content, image_line->content, image_line->content_len) != 0) { - match = 0; - break; - } - } - - return match; -} - -static bool find_hunk_linenum( - size_t *out, - patch_image *image, - patch_image *preimage, - size_t linenum) -{ - size_t max = git_vector_length(&image->lines); - bool match; - - if (linenum > max) - linenum = max; - - match = match_hunk(image, preimage, linenum); - - *out = linenum; - return match; -} - -static int update_hunk( - patch_image *image, - size_t linenum, - patch_image *preimage, - patch_image *postimage) -{ - size_t postlen = git_vector_length(&postimage->lines); - size_t prelen = git_vector_length(&preimage->lines); - size_t i; - int error = 0; - - if (postlen > prelen) - error = git_vector_insert_null( - &image->lines, linenum, (postlen - prelen)); - else if (prelen > postlen) - error = git_vector_remove_range( - &image->lines, linenum, (prelen - postlen)); - - if (error) { - git_error_set_oom(); - return -1; - } - - for (i = 0; i < git_vector_length(&postimage->lines); i++) { - image->lines.contents[linenum + i] = - git_vector_get(&postimage->lines, i); - } - - return 0; -} - -typedef struct { - git_apply_options opts; - size_t skipped_new_lines; - size_t skipped_old_lines; -} apply_hunks_ctx; - -static int apply_hunk( - patch_image *image, - git_patch *patch, - git_patch_hunk *hunk, - apply_hunks_ctx *ctx) -{ - patch_image preimage = PATCH_IMAGE_INIT, postimage = PATCH_IMAGE_INIT; - size_t line_num, i; - int error = 0; - - if (ctx->opts.hunk_cb) { - error = ctx->opts.hunk_cb(&hunk->hunk, ctx->opts.payload); - - if (error) { - if (error > 0) { - ctx->skipped_new_lines += hunk->hunk.new_lines; - ctx->skipped_old_lines += hunk->hunk.old_lines; - error = 0; - } - - goto done; - } - } - - for (i = 0; i < hunk->line_count; i++) { - size_t linenum = hunk->line_start + i; - git_diff_line *line = git_array_get(patch->lines, linenum), *prev; - - if (!line) { - error = apply_err("preimage does not contain line %"PRIuZ, linenum); - goto done; - } - - switch (line->origin) { - case GIT_DIFF_LINE_CONTEXT_EOFNL: - case GIT_DIFF_LINE_DEL_EOFNL: - case GIT_DIFF_LINE_ADD_EOFNL: - prev = i ? git_array_get(patch->lines, linenum - 1) : NULL; - if (prev && prev->content[prev->content_len - 1] == '\n') - prev->content_len -= 1; - break; - case GIT_DIFF_LINE_CONTEXT: - if ((error = git_vector_insert(&preimage.lines, line)) < 0 || - (error = git_vector_insert(&postimage.lines, line)) < 0) - goto done; - break; - case GIT_DIFF_LINE_DELETION: - if ((error = git_vector_insert(&preimage.lines, line)) < 0) - goto done; - break; - case GIT_DIFF_LINE_ADDITION: - if ((error = git_vector_insert(&postimage.lines, line)) < 0) - goto done; - break; - } - } - - if (hunk->hunk.new_start) { - line_num = hunk->hunk.new_start - - ctx->skipped_new_lines + - ctx->skipped_old_lines - - 1; - } else { - line_num = 0; - } - - if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) { - error = apply_err("hunk at line %d did not apply", - hunk->hunk.new_start); - goto done; - } - - error = update_hunk(image, line_num, &preimage, &postimage); - -done: - patch_image_free(&preimage); - patch_image_free(&postimage); - - return error; -} - -static int apply_hunks( - git_str *out, - const char *source, - size_t source_len, - git_patch *patch, - apply_hunks_ctx *ctx) -{ - git_patch_hunk *hunk; - git_diff_line *line; - patch_image image; - size_t i; - int error = 0; - - if ((error = patch_image_init_fromstr(&image, source, source_len)) < 0) - goto done; - - git_array_foreach(patch->hunks, i, hunk) { - if ((error = apply_hunk(&image, patch, hunk, ctx)) < 0) - goto done; - } - - git_vector_foreach(&image.lines, i, line) - git_str_put(out, line->content, line->content_len); - -done: - patch_image_free(&image); - - return error; -} - -static int apply_binary_delta( - git_str *out, - const char *source, - size_t source_len, - git_diff_binary_file *binary_file) -{ - git_str inflated = GIT_STR_INIT; - int error = 0; - - /* no diff means identical contents */ - if (binary_file->datalen == 0) - return git_str_put(out, source, source_len); - - error = git_zstream_inflatebuf(&inflated, - binary_file->data, binary_file->datalen); - - if (!error && inflated.size != binary_file->inflatedlen) { - error = apply_err("inflated delta does not match expected length"); - git_str_dispose(out); - } - - if (error < 0) - goto done; - - if (binary_file->type == GIT_DIFF_BINARY_DELTA) { - void *data; - size_t data_len; - - error = git_delta_apply(&data, &data_len, (void *)source, source_len, - (void *)inflated.ptr, inflated.size); - - out->ptr = data; - out->size = data_len; - out->asize = data_len; - } - else if (binary_file->type == GIT_DIFF_BINARY_LITERAL) { - git_str_swap(out, &inflated); - } - else { - error = apply_err("unknown binary delta type"); - goto done; - } - -done: - git_str_dispose(&inflated); - return error; -} - -static int apply_binary( - git_str *out, - const char *source, - size_t source_len, - git_patch *patch) -{ - git_str reverse = GIT_STR_INIT; - int error = 0; - - if (!patch->binary.contains_data) { - error = apply_err("patch does not contain binary data"); - goto done; - } - - if (!patch->binary.old_file.datalen && !patch->binary.new_file.datalen) - goto done; - - /* first, apply the new_file delta to the given source */ - if ((error = apply_binary_delta(out, source, source_len, - &patch->binary.new_file)) < 0) - goto done; - - /* second, apply the old_file delta to sanity check the result */ - if ((error = apply_binary_delta(&reverse, out->ptr, out->size, - &patch->binary.old_file)) < 0) - goto done; - - /* Verify that the resulting file with the reverse patch applied matches the source file */ - if (source_len != reverse.size || - (source_len && memcmp(source, reverse.ptr, source_len) != 0)) { - error = apply_err("binary patch did not apply cleanly"); - goto done; - } - -done: - if (error < 0) - git_str_dispose(out); - - git_str_dispose(&reverse); - return error; -} - -int git_apply__patch( - git_str *contents_out, - char **filename_out, - unsigned int *mode_out, - const char *source, - size_t source_len, - git_patch *patch, - const git_apply_options *given_opts) -{ - apply_hunks_ctx ctx = { GIT_APPLY_OPTIONS_INIT }; - char *filename = NULL; - unsigned int mode = 0; - int error = 0; - - GIT_ASSERT_ARG(contents_out); - GIT_ASSERT_ARG(filename_out); - GIT_ASSERT_ARG(mode_out); - GIT_ASSERT_ARG(source || !source_len); - GIT_ASSERT_ARG(patch); - - if (given_opts) - memcpy(&ctx.opts, given_opts, sizeof(git_apply_options)); - - *filename_out = NULL; - *mode_out = 0; - - if (patch->delta->status != GIT_DELTA_DELETED) { - const git_diff_file *newfile = &patch->delta->new_file; - - filename = git__strdup(newfile->path); - mode = newfile->mode ? - newfile->mode : GIT_FILEMODE_BLOB; - } - - if (patch->delta->flags & GIT_DIFF_FLAG_BINARY) - error = apply_binary(contents_out, source, source_len, patch); - else if (patch->hunks.size) - error = apply_hunks(contents_out, source, source_len, patch, &ctx); - else - error = git_str_put(contents_out, source, source_len); - - if (error) - goto done; - - if (patch->delta->status == GIT_DELTA_DELETED && - git_str_len(contents_out) > 0) { - error = apply_err("removal patch leaves file contents"); - goto done; - } - - *filename_out = filename; - *mode_out = mode; - -done: - if (error < 0) - git__free(filename); - - return error; -} - -static int apply_one( - git_repository *repo, - git_reader *preimage_reader, - git_index *preimage, - git_reader *postimage_reader, - git_index *postimage, - git_diff *diff, - git_strmap *removed_paths, - size_t i, - const git_apply_options *opts) -{ - git_patch *patch = NULL; - git_str pre_contents = GIT_STR_INIT, post_contents = GIT_STR_INIT; - const git_diff_delta *delta; - char *filename = NULL; - unsigned int mode; - git_oid pre_id, post_id; - git_filemode_t pre_filemode; - git_index_entry pre_entry, post_entry; - bool skip_preimage = false; - int error; - - if ((error = git_patch_from_diff(&patch, diff, i)) < 0) - goto done; - - delta = git_patch_get_delta(patch); - - if (opts->delta_cb) { - error = opts->delta_cb(delta, opts->payload); - - if (error) { - if (error > 0) - error = 0; - - goto done; - } - } - - /* - * Ensure that the file has not been deleted or renamed if we're - * applying a modification delta. - */ - if (delta->status != GIT_DELTA_RENAMED && - delta->status != GIT_DELTA_ADDED) { - if (git_strmap_exists(removed_paths, delta->old_file.path)) { - error = apply_err("path '%s' has been renamed or deleted", delta->old_file.path); - goto done; - } - } - - /* - * We may be applying a second delta to an already seen file. If so, - * use the already modified data in the postimage instead of the - * content from the index or working directory. (Don't do this in - * the case of a rename, which must be specified before additional - * deltas since we apply deltas to the target filename.) - */ - if (delta->status != GIT_DELTA_RENAMED) { - if ((error = git_reader_read(&pre_contents, &pre_id, &pre_filemode, - postimage_reader, delta->old_file.path)) == 0) { - skip_preimage = true; - } else if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } else { - goto done; - } - } - - if (!skip_preimage && delta->status != GIT_DELTA_ADDED) { - error = git_reader_read(&pre_contents, &pre_id, &pre_filemode, - preimage_reader, delta->old_file.path); - - /* ENOTFOUND means the preimage was not found; apply failed. */ - if (error == GIT_ENOTFOUND) - error = GIT_EAPPLYFAIL; - - /* When applying to BOTH, the index did not match the workdir. */ - if (error == GIT_READER_MISMATCH) - error = apply_err("%s: does not match index", delta->old_file.path); - - if (error < 0) - goto done; - - /* - * We need to populate the preimage data structure with the - * contents that we are using as the preimage for this file. - * This allows us to apply patches to files that have been - * modified in the working directory. During checkout, - * we will use this expected preimage as the baseline, and - * limit checkout to only the paths affected by patch - * application. (Without this, we would fail to write the - * postimage contents to any file that had been modified - * from HEAD on-disk, even if the patch application succeeded.) - * Use the contents from the delta where available - some - * fields may not be available, like the old file mode (eg in - * an exact rename situation) so trust the patch parsing to - * validate and use the preimage data in that case. - */ - if (preimage) { - memset(&pre_entry, 0, sizeof(git_index_entry)); - pre_entry.path = delta->old_file.path; - pre_entry.mode = delta->old_file.mode ? delta->old_file.mode : pre_filemode; - git_oid_cpy(&pre_entry.id, &pre_id); - - if ((error = git_index_add(preimage, &pre_entry)) < 0) - goto done; - } - } - - if (delta->status != GIT_DELTA_DELETED) { - if ((error = git_apply__patch(&post_contents, &filename, &mode, - pre_contents.ptr, pre_contents.size, patch, opts)) < 0 || - (error = git_blob_create_from_buffer(&post_id, repo, - post_contents.ptr, post_contents.size)) < 0) - goto done; - - memset(&post_entry, 0, sizeof(git_index_entry)); - post_entry.path = filename; - post_entry.mode = mode; - git_oid_cpy(&post_entry.id, &post_id); - - if ((error = git_index_add(postimage, &post_entry)) < 0) - goto done; - } - - if (delta->status == GIT_DELTA_RENAMED || - delta->status == GIT_DELTA_DELETED) - error = git_strmap_set(removed_paths, delta->old_file.path, (char *) delta->old_file.path); - - if (delta->status == GIT_DELTA_RENAMED || - delta->status == GIT_DELTA_ADDED) - git_strmap_delete(removed_paths, delta->new_file.path); - -done: - git_str_dispose(&pre_contents); - git_str_dispose(&post_contents); - git__free(filename); - git_patch_free(patch); - - return error; -} - -static int apply_deltas( - git_repository *repo, - git_reader *pre_reader, - git_index *preimage, - git_reader *post_reader, - git_index *postimage, - git_diff *diff, - const git_apply_options *opts) -{ - git_strmap *removed_paths; - size_t i; - int error = 0; - - if (git_strmap_new(&removed_paths) < 0) - return -1; - - for (i = 0; i < git_diff_num_deltas(diff); i++) { - if ((error = apply_one(repo, pre_reader, preimage, post_reader, postimage, diff, removed_paths, i, opts)) < 0) - goto done; - } - -done: - git_strmap_free(removed_paths); - return error; -} - -int git_apply_to_tree( - git_index **out, - git_repository *repo, - git_tree *preimage, - git_diff *diff, - const git_apply_options *given_opts) -{ - git_index *postimage = NULL; - git_reader *pre_reader = NULL, *post_reader = NULL; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - const git_diff_delta *delta; - size_t i; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(preimage); - GIT_ASSERT_ARG(diff); - - *out = NULL; - - if (given_opts) - memcpy(&opts, given_opts, sizeof(git_apply_options)); - - if ((error = git_reader_for_tree(&pre_reader, preimage)) < 0) - goto done; - - /* - * put the current tree into the postimage as-is - the diff will - * replace any entries contained therein - */ - if ((error = git_index_new(&postimage)) < 0 || - (error = git_index_read_tree(postimage, preimage)) < 0 || - (error = git_reader_for_index(&post_reader, repo, postimage)) < 0) - goto done; - - /* - * Remove the old paths from the index before applying diffs - - * we need to do a full pass to remove them before adding deltas, - * in order to handle rename situations. - */ - for (i = 0; i < git_diff_num_deltas(diff); i++) { - delta = git_diff_get_delta(diff, i); - - if (delta->status == GIT_DELTA_DELETED || - delta->status == GIT_DELTA_RENAMED) { - if ((error = git_index_remove(postimage, - delta->old_file.path, 0)) < 0) - goto done; - } - } - - if ((error = apply_deltas(repo, pre_reader, NULL, post_reader, postimage, diff, &opts)) < 0) - goto done; - - *out = postimage; - -done: - if (error < 0) - git_index_free(postimage); - - git_reader_free(pre_reader); - git_reader_free(post_reader); - - return error; -} - -static int git_apply__to_workdir( - git_repository *repo, - git_diff *diff, - git_index *preimage, - git_index *postimage, - git_apply_location_t location, - git_apply_options *opts) -{ - git_vector paths = GIT_VECTOR_INIT; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - const git_diff_delta *delta; - size_t i; - int error; - - GIT_UNUSED(opts); - - /* - * Limit checkout to the paths affected by the diff; this ensures - * that other modifications in the working directory are unaffected. - */ - if ((error = git_vector_init(&paths, git_diff_num_deltas(diff), NULL)) < 0) - goto done; - - for (i = 0; i < git_diff_num_deltas(diff); i++) { - delta = git_diff_get_delta(diff, i); - - if ((error = git_vector_insert(&paths, (void *)delta->old_file.path)) < 0) - goto done; - - if (strcmp(delta->old_file.path, delta->new_file.path) && - (error = git_vector_insert(&paths, (void *)delta->new_file.path)) < 0) - goto done; - } - - checkout_opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - checkout_opts.checkout_strategy |= GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; - checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_WRITE_INDEX; - - if (location == GIT_APPLY_LOCATION_WORKDIR) - checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; - - checkout_opts.paths.strings = (char **)paths.contents; - checkout_opts.paths.count = paths.length; - - checkout_opts.baseline_index = preimage; - - error = git_checkout_index(repo, postimage, &checkout_opts); - -done: - git_vector_free(&paths); - return error; -} - -static int git_apply__to_index( - git_repository *repo, - git_diff *diff, - git_index *preimage, - git_index *postimage, - git_apply_options *opts) -{ - git_index *index = NULL; - const git_diff_delta *delta; - const git_index_entry *entry; - size_t i; - int error; - - GIT_UNUSED(preimage); - GIT_UNUSED(opts); - - if ((error = git_repository_index(&index, repo)) < 0) - goto done; - - /* Remove deleted (or renamed) paths from the index. */ - for (i = 0; i < git_diff_num_deltas(diff); i++) { - delta = git_diff_get_delta(diff, i); - - if (delta->status == GIT_DELTA_DELETED || - delta->status == GIT_DELTA_RENAMED) { - if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0) - goto done; - } - } - - /* Then add the changes back to the index. */ - for (i = 0; i < git_index_entrycount(postimage); i++) { - entry = git_index_get_byindex(postimage, i); - - if ((error = git_index_add(index, entry)) < 0) - goto done; - } - -done: - git_index_free(index); - return error; -} - -int git_apply_options_init(git_apply_options *opts, unsigned int version) -{ - GIT_ASSERT_ARG(opts); - - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_apply_options, GIT_APPLY_OPTIONS_INIT); - return 0; -} - -/* - * Handle the three application options ("locations"): - * - * GIT_APPLY_LOCATION_WORKDIR: the default, emulates `git apply`. - * Applies the diff only to the workdir items and ignores the index - * entirely. - * - * GIT_APPLY_LOCATION_INDEX: emulates `git apply --cached`. - * Applies the diff only to the index items and ignores the workdir - * completely. - * - * GIT_APPLY_LOCATION_BOTH: emulates `git apply --index`. - * Applies the diff to both the index items and the working directory - * items. - */ - -int git_apply( - git_repository *repo, - git_diff *diff, - git_apply_location_t location, - const git_apply_options *given_opts) -{ - git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; - git_index *index = NULL, *preimage = NULL, *postimage = NULL; - git_reader *pre_reader = NULL, *post_reader = NULL; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - int error = GIT_EINVALID; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(diff); - - GIT_ERROR_CHECK_VERSION( - given_opts, GIT_APPLY_OPTIONS_VERSION, "git_apply_options"); - - if (given_opts) - memcpy(&opts, given_opts, sizeof(git_apply_options)); - - /* - * by default, we apply a patch directly to the working directory; - * in `--cached` or `--index` mode, we apply to the contents already - * in the index. - */ - switch (location) { - case GIT_APPLY_LOCATION_BOTH: - error = git_reader_for_workdir(&pre_reader, repo, true); - break; - case GIT_APPLY_LOCATION_INDEX: - error = git_reader_for_index(&pre_reader, repo, NULL); - break; - case GIT_APPLY_LOCATION_WORKDIR: - error = git_reader_for_workdir(&pre_reader, repo, false); - break; - default: - GIT_ASSERT(false); - } - - if (error < 0) - goto done; - - /* - * Build the preimage and postimage (differences). Note that - * this is not the complete preimage or postimage, it only - * contains the files affected by the patch. We want to avoid - * having the full repo index, so we will limit our checkout - * to only write these files that were affected by the diff. - */ - if ((error = git_index_new(&preimage)) < 0 || - (error = git_index_new(&postimage)) < 0 || - (error = git_reader_for_index(&post_reader, repo, postimage)) < 0) - goto done; - - if (!(opts.flags & GIT_APPLY_CHECK)) - if ((error = git_repository_index(&index, repo)) < 0 || - (error = git_indexwriter_init(&indexwriter, index)) < 0) - goto done; - - if ((error = apply_deltas(repo, pre_reader, preimage, post_reader, postimage, diff, &opts)) < 0) - goto done; - - if ((opts.flags & GIT_APPLY_CHECK)) - goto done; - - switch (location) { - case GIT_APPLY_LOCATION_BOTH: - error = git_apply__to_workdir(repo, diff, preimage, postimage, location, &opts); - break; - case GIT_APPLY_LOCATION_INDEX: - error = git_apply__to_index(repo, diff, preimage, postimage, &opts); - break; - case GIT_APPLY_LOCATION_WORKDIR: - error = git_apply__to_workdir(repo, diff, preimage, postimage, location, &opts); - break; - default: - GIT_ASSERT(false); - } - - if (error < 0) - goto done; - - error = git_indexwriter_commit(&indexwriter); - -done: - git_indexwriter_cleanup(&indexwriter); - git_index_free(postimage); - git_index_free(preimage); - git_index_free(index); - git_reader_free(pre_reader); - git_reader_free(post_reader); - - return error; -} diff --git a/src/apply.h b/src/apply.h deleted file mode 100644 index e990a7107..000000000 --- a/src/apply.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_apply_h__ -#define INCLUDE_apply_h__ - -#include "common.h" - -#include "git2/patch.h" -#include "git2/apply.h" -#include "str.h" - -extern int git_apply__patch( - git_str *out, - char **filename, - unsigned int *mode, - const char *source, - size_t source_len, - git_patch *patch, - const git_apply_options *opts); - -#endif diff --git a/src/array.h b/src/array.h deleted file mode 100644 index e97688b36..000000000 --- a/src/array.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_array_h__ -#define INCLUDE_array_h__ - -#include "common.h" - -/* - * Use this to declare a typesafe resizable array of items, a la: - * - * git_array_t(int) my_ints = GIT_ARRAY_INIT; - * ... - * int *i = git_array_alloc(my_ints); - * GIT_ERROR_CHECK_ALLOC(i); - * ... - * git_array_clear(my_ints); - * - * You may also want to do things like: - * - * typedef git_array_t(my_struct) my_struct_array_t; - */ -#define git_array_t(type) struct { type *ptr; size_t size, asize; } - -#define GIT_ARRAY_INIT { NULL, 0, 0 } - -#define git_array_init(a) \ - do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0) - -#define git_array_init_to_size(a, desired) \ - do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0) - -#define git_array_clear(a) \ - do { git__free((a).ptr); git_array_init(a); } while (0) - -#define GIT_ERROR_CHECK_ARRAY(a) GIT_ERROR_CHECK_ALLOC((a).ptr) - - -typedef git_array_t(char) git_array_generic_t; - -/* use a generic array for growth, return 0 on success */ -GIT_INLINE(int) git_array_grow(void *_a, size_t item_size) -{ - volatile git_array_generic_t *a = _a; - size_t new_size; - char *new_array; - - if (a->size < 8) { - new_size = 8; - } else { - if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, a->size, 3)) - goto on_oom; - new_size /= 2; - } - - if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL) - goto on_oom; - - a->ptr = new_array; - a->asize = new_size; - return 0; - -on_oom: - git_array_clear(*a); - return -1; -} - -#define git_array_alloc(a) \ - (((a).size < (a).asize || git_array_grow(&(a), sizeof(*(a).ptr)) == 0) ? \ - &(a).ptr[(a).size++] : (void *)NULL) - -#define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : (void *)NULL) - -#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : (void *)NULL) - -#define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : (void *)NULL) - -#define git_array_size(a) (a).size - -#define git_array_valid_index(a, i) ((i) < (a).size) - -#define git_array_foreach(a, i, element) \ - for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++) - -GIT_INLINE(int) git_array__search( - size_t *out, - void *array_ptr, - size_t item_size, - size_t array_len, - int (*compare)(const void *, const void *), - const void *key) -{ - size_t lim; - unsigned char *part, *array = array_ptr, *base = array_ptr; - int cmp = -1; - - for (lim = array_len; lim != 0; lim >>= 1) { - part = base + (lim >> 1) * item_size; - cmp = (*compare)(key, part); - - if (cmp == 0) { - base = part; - break; - } - if (cmp > 0) { /* key > p; take right partition */ - base = part + 1 * item_size; - lim--; - } /* else take left partition */ - } - - if (out) - *out = (base - array) / item_size; - - return (cmp == 0) ? 0 : GIT_ENOTFOUND; -} - -#define git_array_search(out, a, cmp, key) \ - git_array__search(out, (a).ptr, sizeof(*(a).ptr), (a).size, \ - (cmp), (key)) - -#endif diff --git a/src/assert_safe.h b/src/assert_safe.h deleted file mode 100644 index 8c261100f..000000000 --- a/src/assert_safe.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_assert_safe_h__ -#define INCLUDE_assert_safe_h__ - -/* - * In a debug build, we'll assert(3) for aide in debugging. In release - * builds, we will provide macros that will set an error message that - * indicate a failure and return. Note that memory leaks can occur in - * a release-mode assertion failure -- it is impractical to provide - * safe clean up routines in these very extreme failures, but care - * should be taken to not leak very large objects. - */ - -#if (defined(_DEBUG) || defined(GIT_ASSERT_HARD)) && GIT_ASSERT_HARD != 0 -# include - -# define GIT_ASSERT(expr) assert(expr) -# define GIT_ASSERT_ARG(expr) assert(expr) - -# define GIT_ASSERT_WITH_RETVAL(expr, fail) assert(expr) -# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) assert(expr) -#else - -/** Internal consistency check to stop the function. */ -# define GIT_ASSERT(expr) GIT_ASSERT_WITH_RETVAL(expr, -1) - -/** - * Assert that a consumer-provided argument is valid, setting an - * actionable error message and returning -1 if it is not. - */ -# define GIT_ASSERT_ARG(expr) GIT_ASSERT_ARG_WITH_RETVAL(expr, -1) - -/** Internal consistency check to return the `fail` param on failure. */ -# define GIT_ASSERT_WITH_RETVAL(expr, fail) \ - GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INTERNAL, "unrecoverable internal error", fail) - -/** - * Assert that a consumer-provided argument is valid, setting an - * actionable error message and returning the `fail` param if not. - */ -# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) \ - GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INVALID, "invalid argument", fail) - -# define GIT_ASSERT__WITH_RETVAL(expr, code, msg, fail) do { \ - if (!(expr)) { \ - git_error_set(code, "%s: '%s'", msg, #expr); \ - return fail; \ - } \ - } while(0) - -#endif /* GIT_ASSERT_HARD */ - -#endif diff --git a/src/attr.c b/src/attr.c deleted file mode 100644 index 1623b1d45..000000000 --- a/src/attr.c +++ /dev/null @@ -1,700 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "attr.h" - -#include "repository.h" -#include "sysdir.h" -#include "config.h" -#include "attr_file.h" -#include "ignore.h" -#include "git2/oid.h" -#include - -const char *git_attr__true = "[internal]__TRUE__"; -const char *git_attr__false = "[internal]__FALSE__"; -const char *git_attr__unset = "[internal]__UNSET__"; - -git_attr_value_t git_attr_value(const char *attr) -{ - if (attr == NULL || attr == git_attr__unset) - return GIT_ATTR_VALUE_UNSPECIFIED; - - if (attr == git_attr__true) - return GIT_ATTR_VALUE_TRUE; - - if (attr == git_attr__false) - return GIT_ATTR_VALUE_FALSE; - - return GIT_ATTR_VALUE_STRING; -} - -static int collect_attr_files( - git_repository *repo, - git_attr_session *attr_session, - git_attr_options *opts, - const char *path, - git_vector *files); - -static void release_attr_files(git_vector *files); - -int git_attr_get_ext( - const char **value, - git_repository *repo, - git_attr_options *opts, - const char *pathname, - const char *name) -{ - int error; - git_attr_path path; - git_vector files = GIT_VECTOR_INIT; - size_t i, j; - git_attr_file *file; - git_attr_name attr; - git_attr_rule *rule; - git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; - - GIT_ASSERT_ARG(value); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options"); - - *value = NULL; - - if (git_repository_is_bare(repo)) - dir_flag = GIT_DIR_FLAG_FALSE; - - if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) - return -1; - - if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0) - goto cleanup; - - memset(&attr, 0, sizeof(attr)); - attr.name = name; - attr.name_hash = git_attr_file__name_hash(name); - - git_vector_foreach(&files, i, file) { - - git_attr_file__foreach_matching_rule(file, &path, j, rule) { - size_t pos; - - if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) { - *value = ((git_attr_assignment *)git_vector_get( - &rule->assigns, pos))->value; - goto cleanup; - } - } - } - -cleanup: - release_attr_files(&files); - git_attr_path__free(&path); - - return error; -} - -int git_attr_get( - const char **value, - git_repository *repo, - uint32_t flags, - const char *pathname, - const char *name) -{ - git_attr_options opts = GIT_ATTR_OPTIONS_INIT; - - opts.flags = flags; - - return git_attr_get_ext(value, repo, &opts, pathname, name); -} - - -typedef struct { - git_attr_name name; - git_attr_assignment *found; -} attr_get_many_info; - -int git_attr_get_many_with_session( - const char **values, - git_repository *repo, - git_attr_session *attr_session, - git_attr_options *opts, - const char *pathname, - size_t num_attr, - const char **names) -{ - int error; - git_attr_path path; - git_vector files = GIT_VECTOR_INIT; - size_t i, j, k; - git_attr_file *file; - git_attr_rule *rule; - attr_get_many_info *info = NULL; - size_t num_found = 0; - git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; - - if (!num_attr) - return 0; - - GIT_ASSERT_ARG(values); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(pathname); - GIT_ASSERT_ARG(names); - GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options"); - - if (git_repository_is_bare(repo)) - dir_flag = GIT_DIR_FLAG_FALSE; - - if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) - return -1; - - if ((error = collect_attr_files(repo, attr_session, opts, pathname, &files)) < 0) - goto cleanup; - - info = git__calloc(num_attr, sizeof(attr_get_many_info)); - GIT_ERROR_CHECK_ALLOC(info); - - git_vector_foreach(&files, i, file) { - - git_attr_file__foreach_matching_rule(file, &path, j, rule) { - - for (k = 0; k < num_attr; k++) { - size_t pos; - - if (info[k].found != NULL) /* already found assignment */ - continue; - - if (!info[k].name.name) { - info[k].name.name = names[k]; - info[k].name.name_hash = git_attr_file__name_hash(names[k]); - } - - if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) { - info[k].found = (git_attr_assignment *) - git_vector_get(&rule->assigns, pos); - values[k] = info[k].found->value; - - if (++num_found == num_attr) - goto cleanup; - } - } - } - } - - for (k = 0; k < num_attr; k++) { - if (!info[k].found) - values[k] = NULL; - } - -cleanup: - release_attr_files(&files); - git_attr_path__free(&path); - git__free(info); - - return error; -} - -int git_attr_get_many( - const char **values, - git_repository *repo, - uint32_t flags, - const char *pathname, - size_t num_attr, - const char **names) -{ - git_attr_options opts = GIT_ATTR_OPTIONS_INIT; - - opts.flags = flags; - - return git_attr_get_many_with_session( - values, repo, NULL, &opts, pathname, num_attr, names); -} - -int git_attr_get_many_ext( - const char **values, - git_repository *repo, - git_attr_options *opts, - const char *pathname, - size_t num_attr, - const char **names) -{ - return git_attr_get_many_with_session( - values, repo, NULL, opts, pathname, num_attr, names); -} - -int git_attr_foreach( - git_repository *repo, - uint32_t flags, - const char *pathname, - int (*callback)(const char *name, const char *value, void *payload), - void *payload) -{ - git_attr_options opts = GIT_ATTR_OPTIONS_INIT; - - opts.flags = flags; - - return git_attr_foreach_ext(repo, &opts, pathname, callback, payload); -} - -int git_attr_foreach_ext( - git_repository *repo, - git_attr_options *opts, - const char *pathname, - int (*callback)(const char *name, const char *value, void *payload), - void *payload) -{ - int error; - git_attr_path path; - git_vector files = GIT_VECTOR_INIT; - size_t i, j, k; - git_attr_file *file; - git_attr_rule *rule; - git_attr_assignment *assign; - git_strmap *seen = NULL; - git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(callback); - GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options"); - - if (git_repository_is_bare(repo)) - dir_flag = GIT_DIR_FLAG_FALSE; - - if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) - return -1; - - if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0 || - (error = git_strmap_new(&seen)) < 0) - goto cleanup; - - git_vector_foreach(&files, i, file) { - - git_attr_file__foreach_matching_rule(file, &path, j, rule) { - - git_vector_foreach(&rule->assigns, k, assign) { - /* skip if higher priority assignment was already seen */ - if (git_strmap_exists(seen, assign->name)) - continue; - - if ((error = git_strmap_set(seen, assign->name, assign)) < 0) - goto cleanup; - - error = callback(assign->name, assign->value, payload); - if (error) { - git_error_set_after_callback(error); - goto cleanup; - } - } - } - } - -cleanup: - git_strmap_free(seen); - release_attr_files(&files); - git_attr_path__free(&path); - - return error; -} - -static int preload_attr_source( - git_repository *repo, - git_attr_session *attr_session, - git_attr_file_source *source) -{ - int error; - git_attr_file *preload = NULL; - - if (!source) - return 0; - - error = git_attr_cache__get(&preload, repo, attr_session, source, - git_attr_file__parse_buffer, true); - - if (!error) - git_attr_file__free(preload); - - return error; -} - -GIT_INLINE(int) preload_attr_file( - git_repository *repo, - git_attr_session *attr_session, - const char *base, - const char *filename) -{ - git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE }; - - if (!filename) - return 0; - - source.base = base; - source.filename = filename; - - return preload_attr_source(repo, attr_session, &source); -} - -static int system_attr_file( - git_str *out, - git_attr_session *attr_session) -{ - int error; - - if (!attr_session) { - error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM); - - if (error == GIT_ENOTFOUND) - git_error_clear(); - - return error; - } - - if (!attr_session->init_sysdir) { - error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM); - - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error) - return error; - - attr_session->init_sysdir = 1; - } - - if (attr_session->sysdir.size == 0) - return GIT_ENOTFOUND; - - /* We can safely provide a git_str with no allocation (asize == 0) to - * a consumer. This allows them to treat this as a regular `git_str`, - * but their call to `git_str_dispose` will not attempt to free it. - */ - git_str_attach_notowned( - out, attr_session->sysdir.ptr, attr_session->sysdir.size); - return 0; -} - -static int attr_setup( - git_repository *repo, - git_attr_session *attr_session, - git_attr_options *opts) -{ - git_str system = GIT_STR_INIT, info = GIT_STR_INIT; - git_attr_file_source index_source = { GIT_ATTR_FILE_SOURCE_INDEX, NULL, GIT_ATTR_FILE, NULL }; - git_attr_file_source head_source = { GIT_ATTR_FILE_SOURCE_HEAD, NULL, GIT_ATTR_FILE, NULL }; - git_attr_file_source commit_source = { GIT_ATTR_FILE_SOURCE_COMMIT, NULL, GIT_ATTR_FILE, NULL }; - git_index *idx = NULL; - const char *workdir; - int error = 0; - - if (attr_session && attr_session->init_setup) - return 0; - - if ((error = git_attr_cache__init(repo)) < 0) - return error; - - /* - * Preload attribute files that could contain macros so the - * definitions will be available for later file parsing. - */ - - if ((error = system_attr_file(&system, attr_session)) < 0 || - (error = preload_attr_file(repo, attr_session, NULL, system.ptr)) < 0) { - if (error != GIT_ENOTFOUND) - goto out; - - error = 0; - } - - if ((error = preload_attr_file(repo, attr_session, NULL, - git_repository_attr_cache(repo)->cfg_attr_file)) < 0) - goto out; - - if ((error = git_repository__item_path(&info, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || - (error = preload_attr_file(repo, attr_session, info.ptr, GIT_ATTR_FILE_INREPO)) < 0) { - if (error != GIT_ENOTFOUND) - goto out; - - error = 0; - } - - if ((workdir = git_repository_workdir(repo)) != NULL && - (error = preload_attr_file(repo, attr_session, workdir, GIT_ATTR_FILE)) < 0) - goto out; - - if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || - (error = preload_attr_source(repo, attr_session, &index_source)) < 0) - goto out; - - if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) && - (error = preload_attr_source(repo, attr_session, &head_source)) < 0) - goto out; - - if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_COMMIT) != 0)) { -#ifndef GIT_DEPRECATE_HARD - if (opts->commit_id) - commit_source.commit_id = opts->commit_id; - else -#endif - commit_source.commit_id = &opts->attr_commit_id; - - if ((error = preload_attr_source(repo, attr_session, &commit_source)) < 0) - goto out; - } - - if (attr_session) - attr_session->init_setup = 1; - -out: - git_str_dispose(&system); - git_str_dispose(&info); - - return error; -} - -int git_attr_add_macro( - git_repository *repo, - const char *name, - const char *values) -{ - int error; - git_attr_rule *macro = NULL; - git_pool *pool; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = git_attr_cache__init(repo)) < 0) - return error; - - macro = git__calloc(1, sizeof(git_attr_rule)); - GIT_ERROR_CHECK_ALLOC(macro); - - pool = &git_repository_attr_cache(repo)->pool; - - macro->match.pattern = git_pool_strdup(pool, name); - GIT_ERROR_CHECK_ALLOC(macro->match.pattern); - - macro->match.length = strlen(macro->match.pattern); - macro->match.flags = GIT_ATTR_FNMATCH_MACRO; - - error = git_attr_assignment__parse(repo, pool, ¯o->assigns, &values); - - if (!error) - error = git_attr_cache__insert_macro(repo, macro); - - if (error < 0) - git_attr_rule__free(macro); - - return error; -} - -typedef struct { - git_repository *repo; - git_attr_session *attr_session; - git_attr_options *opts; - const char *workdir; - git_index *index; - git_vector *files; -} attr_walk_up_info; - -static int attr_decide_sources( - uint32_t flags, - bool has_wd, - bool has_index, - git_attr_file_source_t *srcs) -{ - int count = 0; - - switch (flags & 0x03) { - case GIT_ATTR_CHECK_FILE_THEN_INDEX: - if (has_wd) - srcs[count++] = GIT_ATTR_FILE_SOURCE_FILE; - if (has_index) - srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX; - break; - case GIT_ATTR_CHECK_INDEX_THEN_FILE: - if (has_index) - srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX; - if (has_wd) - srcs[count++] = GIT_ATTR_FILE_SOURCE_FILE; - break; - case GIT_ATTR_CHECK_INDEX_ONLY: - if (has_index) - srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX; - break; - } - - if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) - srcs[count++] = GIT_ATTR_FILE_SOURCE_HEAD; - - if ((flags & GIT_ATTR_CHECK_INCLUDE_COMMIT) != 0) - srcs[count++] = GIT_ATTR_FILE_SOURCE_COMMIT; - - return count; -} - -static int push_attr_source( - git_repository *repo, - git_attr_session *attr_session, - git_vector *list, - git_attr_file_source *source, - bool allow_macros) -{ - int error = 0; - git_attr_file *file = NULL; - - error = git_attr_cache__get(&file, repo, attr_session, - source, - git_attr_file__parse_buffer, - allow_macros); - - if (error < 0) - return error; - - if (file != NULL) { - if ((error = git_vector_insert(list, file)) < 0) - git_attr_file__free(file); - } - - return error; -} - -GIT_INLINE(int) push_attr_file( - git_repository *repo, - git_attr_session *attr_session, - git_vector *list, - const char *base, - const char *filename) -{ - git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE, base, filename }; - return push_attr_source(repo, attr_session, list, &source, true); -} - -static int push_one_attr(void *ref, const char *path) -{ - attr_walk_up_info *info = (attr_walk_up_info *)ref; - git_attr_file_source_t src[GIT_ATTR_FILE_NUM_SOURCES]; - int error = 0, n_src, i; - bool allow_macros; - - n_src = attr_decide_sources(info->opts ? info->opts->flags : 0, - info->workdir != NULL, - info->index != NULL, - src); - - allow_macros = info->workdir ? !strcmp(info->workdir, path) : false; - - for (i = 0; !error && i < n_src; ++i) { - git_attr_file_source source = { src[i], path, GIT_ATTR_FILE }; - - if (src[i] == GIT_ATTR_FILE_SOURCE_COMMIT && info->opts) { -#ifndef GIT_DEPRECATE_HARD - if (info->opts->commit_id) - source.commit_id = info->opts->commit_id; - else -#endif - source.commit_id = &info->opts->attr_commit_id; - } - - error = push_attr_source(info->repo, info->attr_session, info->files, - &source, allow_macros); - } - - return error; -} - -static void release_attr_files(git_vector *files) -{ - size_t i; - git_attr_file *file; - - git_vector_foreach(files, i, file) { - git_attr_file__free(file); - files->contents[i] = NULL; - } - git_vector_free(files); -} - -static int collect_attr_files( - git_repository *repo, - git_attr_session *attr_session, - git_attr_options *opts, - const char *path, - git_vector *files) -{ - int error = 0; - git_str dir = GIT_STR_INIT, attrfile = GIT_STR_INIT; - const char *workdir = git_repository_workdir(repo); - attr_walk_up_info info = { NULL }; - - GIT_ASSERT(!git_fs_path_is_absolute(path)); - - if ((error = attr_setup(repo, attr_session, opts)) < 0) - return error; - - /* Resolve path in a non-bare repo */ - if (workdir != NULL) { - if (!(error = git_repository_workdir_path(&dir, repo, path))) - error = git_fs_path_find_dir(&dir); - } - else { - error = git_fs_path_dirname_r(&dir, path); - } - - if (error < 0) - goto cleanup; - - /* in precedence order highest to lowest: - * - $GIT_DIR/info/attributes - * - path components with .gitattributes - * - config core.attributesfile - * - $GIT_PREFIX/etc/gitattributes - */ - - if ((error = git_repository__item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || - (error = push_attr_file(repo, attr_session, files, attrfile.ptr, GIT_ATTR_FILE_INREPO)) < 0) { - if (error != GIT_ENOTFOUND) - goto cleanup; - } - - info.repo = repo; - info.attr_session = attr_session; - info.opts = opts; - info.workdir = workdir; - if (git_repository_index__weakptr(&info.index, repo) < 0) - git_error_clear(); /* no error even if there is no index */ - info.files = files; - - if (!strcmp(dir.ptr, ".")) - error = push_one_attr(&info, ""); - else - error = git_fs_path_walk_up(&dir, workdir, push_one_attr, &info); - - if (error < 0) - goto cleanup; - - if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) { - error = push_attr_file(repo, attr_session, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file); - if (error < 0) - goto cleanup; - } - - if (!opts || (opts->flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) { - error = system_attr_file(&dir, attr_session); - - if (!error) - error = push_attr_file(repo, attr_session, files, NULL, dir.ptr); - else if (error == GIT_ENOTFOUND) - error = 0; - } - - cleanup: - if (error < 0) - release_attr_files(files); - git_str_dispose(&attrfile); - git_str_dispose(&dir); - - return error; -} diff --git a/src/attr.h b/src/attr.h deleted file mode 100644 index 977565205..000000000 --- a/src/attr.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_attr_h__ -#define INCLUDE_attr_h__ - -#include "common.h" - -#include "attr_file.h" -#include "attrcache.h" - -#endif diff --git a/src/attr_file.c b/src/attr_file.c deleted file mode 100644 index 0eb881a9b..000000000 --- a/src/attr_file.c +++ /dev/null @@ -1,1027 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "attr_file.h" - -#include "repository.h" -#include "filebuf.h" -#include "attrcache.h" -#include "git2/blob.h" -#include "git2/tree.h" -#include "blob.h" -#include "index.h" -#include "wildmatch.h" -#include - -static void attr_file_free(git_attr_file *file) -{ - bool unlock = !git_mutex_lock(&file->lock); - git_attr_file__clear_rules(file, false); - git_pool_clear(&file->pool); - if (unlock) - git_mutex_unlock(&file->lock); - git_mutex_free(&file->lock); - - git__memzero(file, sizeof(*file)); - git__free(file); -} - -int git_attr_file__new( - git_attr_file **out, - git_attr_file_entry *entry, - git_attr_file_source *source) -{ - git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file)); - GIT_ERROR_CHECK_ALLOC(attrs); - - if (git_mutex_init(&attrs->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to initialize lock"); - goto on_error; - } - - if (git_pool_init(&attrs->pool, 1) < 0) - goto on_error; - - GIT_REFCOUNT_INC(attrs); - attrs->entry = entry; - memcpy(&attrs->source, source, sizeof(git_attr_file_source)); - *out = attrs; - return 0; - -on_error: - git__free(attrs); - return -1; -} - -int git_attr_file__clear_rules(git_attr_file *file, bool need_lock) -{ - unsigned int i; - git_attr_rule *rule; - - if (need_lock && git_mutex_lock(&file->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock attribute file"); - return -1; - } - - git_vector_foreach(&file->rules, i, rule) - git_attr_rule__free(rule); - git_vector_free(&file->rules); - - if (need_lock) - git_mutex_unlock(&file->lock); - - return 0; -} - -void git_attr_file__free(git_attr_file *file) -{ - if (!file) - return; - GIT_REFCOUNT_DEC(file, attr_file_free); -} - -static int attr_file_oid_from_index( - git_oid *oid, git_repository *repo, const char *path) -{ - int error; - git_index *idx; - size_t pos; - const git_index_entry *entry; - - if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || - (error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0) - return error; - - if (!(entry = git_index_get_byindex(idx, pos))) - return GIT_ENOTFOUND; - - *oid = entry->id; - return 0; -} - -int git_attr_file__load( - git_attr_file **out, - git_repository *repo, - git_attr_session *attr_session, - git_attr_file_entry *entry, - git_attr_file_source *source, - git_attr_file_parser parser, - bool allow_macros) -{ - int error = 0; - git_commit *commit = NULL; - git_tree *tree = NULL; - git_tree_entry *tree_entry = NULL; - git_blob *blob = NULL; - git_str content = GIT_STR_INIT; - const char *content_str; - git_attr_file *file; - struct stat st; - bool nonexistent = false; - int bom_offset; - git_str_bom_t bom; - git_oid id; - git_object_size_t blobsize; - - *out = NULL; - - switch (source->type) { - case GIT_ATTR_FILE_SOURCE_MEMORY: - /* in-memory attribute file doesn't need data */ - break; - case GIT_ATTR_FILE_SOURCE_INDEX: { - if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 || - (error = git_blob_lookup(&blob, repo, &id)) < 0) - return error; - - /* Do not assume that data straight from the ODB is NULL-terminated; - * copy the contents of a file to a buffer to work on */ - blobsize = git_blob_rawsize(blob); - - GIT_ERROR_CHECK_BLOBSIZE(blobsize); - git_str_put(&content, git_blob_rawcontent(blob), (size_t)blobsize); - break; - } - case GIT_ATTR_FILE_SOURCE_FILE: { - int fd = -1; - - /* For open or read errors, pretend that we got ENOTFOUND. */ - /* TODO: issue warning when warning API is available */ - - if (p_stat(entry->fullpath, &st) < 0 || - S_ISDIR(st.st_mode) || - (fd = git_futils_open_ro(entry->fullpath)) < 0 || - (error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0) - nonexistent = true; - - if (fd >= 0) - p_close(fd); - - break; - } - case GIT_ATTR_FILE_SOURCE_HEAD: - case GIT_ATTR_FILE_SOURCE_COMMIT: { - if (source->type == GIT_ATTR_FILE_SOURCE_COMMIT) { - if ((error = git_commit_lookup(&commit, repo, source->commit_id)) < 0 || - (error = git_commit_tree(&tree, commit)) < 0) - goto cleanup; - } else { - if ((error = git_repository_head_tree(&tree, repo)) < 0) - goto cleanup; - } - - if ((error = git_tree_entry_bypath(&tree_entry, tree, entry->path)) < 0) { - /* - * If the attributes file does not exist, we can - * cache an empty file for this commit to prevent - * needless future lookups. - */ - if (error == GIT_ENOTFOUND) { - error = 0; - break; - } - - goto cleanup; - } - - if ((error = git_blob_lookup(&blob, repo, git_tree_entry_id(tree_entry))) < 0) - goto cleanup; - - /* - * Do not assume that data straight from the ODB is NULL-terminated; - * copy the contents of a file to a buffer to work on. - */ - blobsize = git_blob_rawsize(blob); - - GIT_ERROR_CHECK_BLOBSIZE(blobsize); - if ((error = git_str_put(&content, - git_blob_rawcontent(blob), (size_t)blobsize)) < 0) - goto cleanup; - - break; - } - default: - git_error_set(GIT_ERROR_INVALID, "unknown file source %d", source->type); - return -1; - } - - if ((error = git_attr_file__new(&file, entry, source)) < 0) - goto cleanup; - - /* advance over a UTF8 BOM */ - content_str = git_str_cstr(&content); - bom_offset = git_str_detect_bom(&bom, &content); - - if (bom == GIT_STR_BOM_UTF8) - content_str += bom_offset; - - /* store the key of the attr_reader; don't bother with cache - * invalidation during the same attr reader session. - */ - if (attr_session) - file->session_key = attr_session->key; - - if (parser && (error = parser(repo, file, content_str, allow_macros)) < 0) { - git_attr_file__free(file); - goto cleanup; - } - - /* write cache breakers */ - if (nonexistent) - file->nonexistent = 1; - else if (source->type == GIT_ATTR_FILE_SOURCE_INDEX) - git_oid_cpy(&file->cache_data.oid, git_blob_id(blob)); - else if (source->type == GIT_ATTR_FILE_SOURCE_HEAD) - git_oid_cpy(&file->cache_data.oid, git_tree_id(tree)); - else if (source->type == GIT_ATTR_FILE_SOURCE_COMMIT) - git_oid_cpy(&file->cache_data.oid, git_tree_id(tree)); - else if (source->type == GIT_ATTR_FILE_SOURCE_FILE) - git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st); - /* else always cacheable */ - - *out = file; - -cleanup: - git_blob_free(blob); - git_tree_entry_free(tree_entry); - git_tree_free(tree); - git_commit_free(commit); - git_str_dispose(&content); - - return error; -} - -int git_attr_file__out_of_date( - git_repository *repo, - git_attr_session *attr_session, - git_attr_file *file, - git_attr_file_source *source) -{ - if (!file) - return 1; - - /* we are never out of date if we just created this data in the same - * attr_session; otherwise, nonexistent files must be invalidated - */ - if (attr_session && attr_session->key == file->session_key) - return 0; - else if (file->nonexistent) - return 1; - - switch (file->source.type) { - case GIT_ATTR_FILE_SOURCE_MEMORY: - return 0; - - case GIT_ATTR_FILE_SOURCE_FILE: - return git_futils_filestamp_check( - &file->cache_data.stamp, file->entry->fullpath); - - case GIT_ATTR_FILE_SOURCE_INDEX: { - int error; - git_oid id; - - if ((error = attr_file_oid_from_index( - &id, repo, file->entry->path)) < 0) - return error; - - return (git_oid__cmp(&file->cache_data.oid, &id) != 0); - } - - case GIT_ATTR_FILE_SOURCE_HEAD: { - git_tree *tree = NULL; - int error = git_repository_head_tree(&tree, repo); - - if (error < 0) - return error; - - error = (git_oid__cmp(&file->cache_data.oid, git_tree_id(tree)) != 0); - - git_tree_free(tree); - return error; - } - - case GIT_ATTR_FILE_SOURCE_COMMIT: { - git_commit *commit = NULL; - git_tree *tree = NULL; - int error; - - if ((error = git_commit_lookup(&commit, repo, source->commit_id)) < 0) - return error; - - error = git_commit_tree(&tree, commit); - git_commit_free(commit); - - if (error < 0) - return error; - - error = (git_oid__cmp(&file->cache_data.oid, git_tree_id(tree)) != 0); - - git_tree_free(tree); - return error; - } - - default: - git_error_set(GIT_ERROR_INVALID, "invalid file type %d", file->source.type); - return -1; - } -} - -static int sort_by_hash_and_name(const void *a_raw, const void *b_raw); -static void git_attr_rule__clear(git_attr_rule *rule); -static bool parse_optimized_patterns( - git_attr_fnmatch *spec, - git_pool *pool, - const char *pattern); - -int git_attr_file__parse_buffer( - git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros) -{ - const char *scan = data, *context = NULL; - git_attr_rule *rule = NULL; - int error = 0; - - /* If subdir file path, convert context for file paths */ - if (attrs->entry && git_fs_path_root(attrs->entry->path) < 0 && - !git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE)) - context = attrs->entry->path; - - if (git_mutex_lock(&attrs->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock attribute file"); - return -1; - } - - while (!error && *scan) { - /* Allocate rule if needed, otherwise re-use previous rule */ - if (!rule) { - rule = git__calloc(1, sizeof(*rule)); - GIT_ERROR_CHECK_ALLOC(rule); - } else - git_attr_rule__clear(rule); - - rule->match.flags = GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO; - - /* Parse the next "pattern attr attr attr" line */ - if ((error = git_attr_fnmatch__parse(&rule->match, &attrs->pool, context, &scan)) < 0 || - (error = git_attr_assignment__parse(repo, &attrs->pool, &rule->assigns, &scan)) < 0) - { - if (error != GIT_ENOTFOUND) - goto out; - error = 0; - continue; - } - - if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) { - /* TODO: warning if macro found in file below repo root */ - if (!allow_macros) - continue; - if ((error = git_attr_cache__insert_macro(repo, rule)) < 0) - goto out; - } else if ((error = git_vector_insert(&attrs->rules, rule)) < 0) - goto out; - - rule = NULL; - } - -out: - git_mutex_unlock(&attrs->lock); - git_attr_rule__free(rule); - - return error; -} - -uint32_t git_attr_file__name_hash(const char *name) -{ - uint32_t h = 5381; - int c; - - GIT_ASSERT_ARG(name); - - while ((c = (int)*name++) != 0) - h = ((h << 5) + h) + c; - return h; -} - -int git_attr_file__lookup_one( - git_attr_file *file, - git_attr_path *path, - const char *attr, - const char **value) -{ - size_t i; - git_attr_name name; - git_attr_rule *rule; - - *value = NULL; - - name.name = attr; - name.name_hash = git_attr_file__name_hash(attr); - - git_attr_file__foreach_matching_rule(file, path, i, rule) { - size_t pos; - - if (!git_vector_bsearch(&pos, &rule->assigns, &name)) { - *value = ((git_attr_assignment *) - git_vector_get(&rule->assigns, pos))->value; - break; - } - } - - return 0; -} - -int git_attr_file__load_standalone(git_attr_file **out, const char *path) -{ - git_str content = GIT_STR_INIT; - git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE }; - git_attr_file *file = NULL; - int error; - - if ((error = git_futils_readbuffer(&content, path)) < 0) - goto out; - - /* - * Because the cache entry is allocated from the file's own pool, we - * don't have to free it - freeing file+pool will free cache entry, too. - */ - - if ((error = git_attr_file__new(&file, NULL, &source)) < 0 || - (error = git_attr_file__parse_buffer(NULL, file, content.ptr, true)) < 0 || - (error = git_attr_cache__alloc_file_entry(&file->entry, NULL, NULL, path, &file->pool)) < 0) - goto out; - - *out = file; -out: - if (error < 0) - git_attr_file__free(file); - git_str_dispose(&content); - - return error; -} - -bool git_attr_fnmatch__match( - git_attr_fnmatch *match, - git_attr_path *path) -{ - const char *relpath = path->path; - const char *filename; - int flags = 0; - - /* - * If the rule was generated in a subdirectory, we must only - * use it for paths inside that directory. We can thus return - * a non-match if the prefixes don't match. - */ - if (match->containing_dir) { - if (match->flags & GIT_ATTR_FNMATCH_ICASE) { - if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length)) - return 0; - } else { - if (git__prefixcmp(path->path, match->containing_dir)) - return 0; - } - - relpath += match->containing_dir_length; - } - - if (match->flags & GIT_ATTR_FNMATCH_ICASE) - flags |= WM_CASEFOLD; - - if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) { - filename = relpath; - flags |= WM_PATHNAME; - } else { - filename = path->basename; - } - - if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) { - bool samename; - - /* - * for attribute checks or checks at the root of this match's - * containing_dir (or root of the repository if no containing_dir), - * do not match. - */ - if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) || - path->basename == relpath) - return false; - - /* fail match if this is a file with same name as ignored folder */ - samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ? - !strcasecmp(match->pattern, relpath) : - !strcmp(match->pattern, relpath); - - if (samename) - return false; - - return (wildmatch(match->pattern, relpath, flags) == WM_MATCH); - } - - return (wildmatch(match->pattern, filename, flags) == WM_MATCH); -} - -bool git_attr_rule__match( - git_attr_rule *rule, - git_attr_path *path) -{ - bool matched = git_attr_fnmatch__match(&rule->match, path); - - if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) - matched = !matched; - - return matched; -} - -git_attr_assignment *git_attr_rule__lookup_assignment( - git_attr_rule *rule, const char *name) -{ - size_t pos; - git_attr_name key; - key.name = name; - key.name_hash = git_attr_file__name_hash(name); - - if (git_vector_bsearch(&pos, &rule->assigns, &key)) - return NULL; - - return git_vector_get(&rule->assigns, pos); -} - -int git_attr_path__init( - git_attr_path *info, - const char *path, - const char *base, - git_dir_flag dir_flag) -{ - ssize_t root; - - /* build full path as best we can */ - git_str_init(&info->full, 0); - - if (git_fs_path_join_unrooted(&info->full, path, base, &root) < 0) - return -1; - - info->path = info->full.ptr + root; - - /* remove trailing slashes */ - while (info->full.size > 0) { - if (info->full.ptr[info->full.size - 1] != '/') - break; - info->full.size--; - } - info->full.ptr[info->full.size] = '\0'; - - /* skip leading slashes in path */ - while (*info->path == '/') - info->path++; - - /* find trailing basename component */ - info->basename = strrchr(info->path, '/'); - if (info->basename) - info->basename++; - if (!info->basename || !*info->basename) - info->basename = info->path; - - switch (dir_flag) - { - case GIT_DIR_FLAG_FALSE: - info->is_dir = 0; - break; - - case GIT_DIR_FLAG_TRUE: - info->is_dir = 1; - break; - - case GIT_DIR_FLAG_UNKNOWN: - default: - info->is_dir = (int)git_fs_path_isdir(info->full.ptr); - break; - } - - return 0; -} - -void git_attr_path__free(git_attr_path *info) -{ - git_str_dispose(&info->full); - info->path = NULL; - info->basename = NULL; -} - -/* - * From gitattributes(5): - * - * Patterns have the following format: - * - * - A blank line matches no files, so it can serve as a separator for - * readability. - * - * - A line starting with # serves as a comment. - * - * - An optional prefix ! which negates the pattern; any matching file - * excluded by a previous pattern will become included again. If a negated - * pattern matches, this will override lower precedence patterns sources. - * - * - If the pattern ends with a slash, it is removed for the purpose of the - * following description, but it would only find a match with a directory. In - * other words, foo/ will match a directory foo and paths underneath it, but - * will not match a regular file or a symbolic link foo (this is consistent - * with the way how pathspec works in general in git). - * - * - If the pattern does not contain a slash /, git treats it as a shell glob - * pattern and checks for a match against the pathname without leading - * directories. - * - * - Otherwise, git treats the pattern as a shell glob suitable for consumption - * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will - * not match a / in the pathname. For example, "Documentation/\*.html" matches - * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading - * slash matches the beginning of the pathname; for example, "/\*.c" matches - * "cat-file.c" but not "mozilla-sha1/sha1.c". - */ - -/* - * Determine the length of trailing spaces. Escaped spaces do not count as - * trailing whitespace. - */ -static size_t trailing_space_length(const char *p, size_t len) -{ - size_t n, i; - for (n = len; n; n--) { - if (p[n-1] != ' ' && p[n-1] != '\t') - break; - - /* - * Count escape-characters before space. In case where it's an - * even number of escape characters, then the escape char itself - * is escaped and the whitespace is an unescaped whitespace. - * Otherwise, the last escape char is not escaped and the - * whitespace in an escaped whitespace. - */ - i = n; - while (i > 1 && p[i-2] == '\\') - i--; - if ((n - i) % 2) - break; - } - return len - n; -} - -static size_t unescape_spaces(char *str) -{ - char *scan, *pos = str; - bool escaped = false; - - if (!str) - return 0; - - for (scan = str; *scan; scan++) { - if (!escaped && *scan == '\\') { - escaped = true; - continue; - } - - /* Only insert the escape character for escaped non-spaces */ - if (escaped && !git__isspace(*scan)) - *pos++ = '\\'; - - *pos++ = *scan; - escaped = false; - } - - if (pos != scan) - *pos = '\0'; - - return (pos - str); -} - -/* - * This will return 0 if the spec was filled out, - * GIT_ENOTFOUND if the fnmatch does not require matching, or - * another error code there was an actual problem. - */ -int git_attr_fnmatch__parse( - git_attr_fnmatch *spec, - git_pool *pool, - const char *context, - const char **base) -{ - const char *pattern, *scan; - int slash_count, allow_space; - bool escaped; - - GIT_ASSERT_ARG(spec); - GIT_ASSERT_ARG(base && *base); - - if (parse_optimized_patterns(spec, pool, *base)) - return 0; - - spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING); - allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0); - - pattern = *base; - - while (!allow_space && git__isspace(*pattern)) - pattern++; - - if (!*pattern || *pattern == '#' || *pattern == '\n' || - (*pattern == '\r' && *(pattern + 1) == '\n')) { - *base = git__next_line(pattern); - return GIT_ENOTFOUND; - } - - if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) { - if (strncmp(pattern, "[attr]", 6) == 0) { - spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO; - pattern += 6; - } - /* else a character range like [a-e]* which is accepted */ - } - - if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) { - spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; - pattern++; - } - - slash_count = 0; - escaped = false; - /* Scan until a non-escaped whitespace. */ - for (scan = pattern; *scan != '\0'; ++scan) { - char c = *scan; - - if (c == '\\' && !escaped) { - escaped = true; - continue; - } else if (git__isspace(c) && !escaped) { - if (!allow_space || (c != ' ' && c != '\t' && c != '\r')) - break; - } else if (c == '/') { - spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH; - slash_count++; - - if (slash_count == 1 && pattern == scan) - pattern++; - } else if (git__iswildcard(c) && !escaped) { - /* remember if we see an unescaped wildcard in pattern */ - spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD; - } - - escaped = false; - } - - *base = scan; - - if ((spec->length = scan - pattern) == 0) - return GIT_ENOTFOUND; - - /* - * Remove one trailing \r in case this is a CRLF delimited - * file, in the case of Icon\r\r\n, we still leave the first - * \r there to match against. - */ - if (pattern[spec->length - 1] == '\r') - if (--spec->length == 0) - return GIT_ENOTFOUND; - - /* Remove trailing spaces. */ - spec->length -= trailing_space_length(pattern, spec->length); - - if (spec->length == 0) - return GIT_ENOTFOUND; - - if (pattern[spec->length - 1] == '/') { - spec->length--; - spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY; - if (--slash_count <= 0) - spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH; - } - - if (context) { - char *slash = strrchr(context, '/'); - size_t len; - if (slash) { - /* include the slash for easier matching */ - len = slash - context + 1; - spec->containing_dir = git_pool_strndup(pool, context, len); - spec->containing_dir_length = len; - } - } - - spec->pattern = git_pool_strndup(pool, pattern, spec->length); - - if (!spec->pattern) { - *base = git__next_line(pattern); - return -1; - } else { - /* strip '\' that might have been used for internal whitespace */ - spec->length = unescape_spaces(spec->pattern); - } - - return 0; -} - -static bool parse_optimized_patterns( - git_attr_fnmatch *spec, - git_pool *pool, - const char *pattern) -{ - if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) { - spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL; - spec->pattern = git_pool_strndup(pool, pattern, 1); - spec->length = 1; - - return true; - } - - return false; -} - -static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) -{ - const git_attr_name *a = a_raw; - const git_attr_name *b = b_raw; - - if (b->name_hash < a->name_hash) - return 1; - else if (b->name_hash > a->name_hash) - return -1; - else - return strcmp(b->name, a->name); -} - -static void git_attr_assignment__free(git_attr_assignment *assign) -{ - /* name and value are stored in a git_pool associated with the - * git_attr_file, so they do not need to be freed here - */ - assign->name = NULL; - assign->value = NULL; - git__free(assign); -} - -static int merge_assignments(void **old_raw, void *new_raw) -{ - git_attr_assignment **old = (git_attr_assignment **)old_raw; - git_attr_assignment *new = (git_attr_assignment *)new_raw; - - GIT_REFCOUNT_DEC(*old, git_attr_assignment__free); - *old = new; - return GIT_EEXISTS; -} - -int git_attr_assignment__parse( - git_repository *repo, - git_pool *pool, - git_vector *assigns, - const char **base) -{ - int error; - const char *scan = *base; - git_attr_assignment *assign = NULL; - - GIT_ASSERT_ARG(assigns && !assigns->length); - - git_vector_set_cmp(assigns, sort_by_hash_and_name); - - while (*scan && *scan != '\n') { - const char *name_start, *value_start; - - /* skip leading blanks */ - while (git__isspace(*scan) && *scan != '\n') scan++; - - /* allocate assign if needed */ - if (!assign) { - assign = git__calloc(1, sizeof(git_attr_assignment)); - GIT_ERROR_CHECK_ALLOC(assign); - GIT_REFCOUNT_INC(assign); - } - - assign->name_hash = 5381; - assign->value = git_attr__true; - - /* look for magic name prefixes */ - if (*scan == '-') { - assign->value = git_attr__false; - scan++; - } else if (*scan == '!') { - assign->value = git_attr__unset; /* explicit unspecified state */ - scan++; - } else if (*scan == '#') /* comment rest of line */ - break; - - /* find the name */ - name_start = scan; - while (*scan && !git__isspace(*scan) && *scan != '=') { - assign->name_hash = - ((assign->name_hash << 5) + assign->name_hash) + *scan; - scan++; - } - if (scan == name_start) { - /* must have found lone prefix (" - ") or leading = ("=foo") - * or end of buffer -- advance until whitespace and continue - */ - while (*scan && !git__isspace(*scan)) scan++; - continue; - } - - /* allocate permanent storage for name */ - assign->name = git_pool_strndup(pool, name_start, scan - name_start); - GIT_ERROR_CHECK_ALLOC(assign->name); - - /* if there is an equals sign, find the value */ - if (*scan == '=') { - for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan); - - /* if we found a value, allocate permanent storage for it */ - if (scan > value_start) { - assign->value = git_pool_strndup(pool, value_start, scan - value_start); - GIT_ERROR_CHECK_ALLOC(assign->value); - } - } - - /* expand macros (if given a repo with a macro cache) */ - if (repo != NULL && assign->value == git_attr__true) { - git_attr_rule *macro = - git_attr_cache__lookup_macro(repo, assign->name); - - if (macro != NULL) { - unsigned int i; - git_attr_assignment *massign; - - git_vector_foreach(¯o->assigns, i, massign) { - GIT_REFCOUNT_INC(massign); - - error = git_vector_insert_sorted( - assigns, massign, &merge_assignments); - if (error < 0 && error != GIT_EEXISTS) { - git_attr_assignment__free(assign); - return error; - } - } - } - } - - /* insert allocated assign into vector */ - error = git_vector_insert_sorted(assigns, assign, &merge_assignments); - if (error < 0 && error != GIT_EEXISTS) - return error; - - /* clear assign since it is now "owned" by the vector */ - assign = NULL; - } - - if (assign != NULL) - git_attr_assignment__free(assign); - - *base = git__next_line(scan); - - return (assigns->length == 0) ? GIT_ENOTFOUND : 0; -} - -static void git_attr_rule__clear(git_attr_rule *rule) -{ - unsigned int i; - git_attr_assignment *assign; - - if (!rule) - return; - - if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) { - git_vector_foreach(&rule->assigns, i, assign) - GIT_REFCOUNT_DEC(assign, git_attr_assignment__free); - git_vector_free(&rule->assigns); - } - - /* match.pattern is stored in a git_pool, so no need to free */ - rule->match.pattern = NULL; - rule->match.length = 0; -} - -void git_attr_rule__free(git_attr_rule *rule) -{ - git_attr_rule__clear(rule); - git__free(rule); -} - -int git_attr_session__init(git_attr_session *session, git_repository *repo) -{ - GIT_ASSERT_ARG(repo); - - memset(session, 0, sizeof(*session)); - session->key = git_atomic32_inc(&repo->attr_session_key); - - return 0; -} - -void git_attr_session__free(git_attr_session *session) -{ - if (!session) - return; - - git_str_dispose(&session->sysdir); - git_str_dispose(&session->tmp); - - memset(session, 0, sizeof(git_attr_session)); -} diff --git a/src/attr_file.h b/src/attr_file.h deleted file mode 100644 index 08630d1a6..000000000 --- a/src/attr_file.h +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_attr_file_h__ -#define INCLUDE_attr_file_h__ - -#include "common.h" - -#include "git2/oid.h" -#include "git2/attr.h" -#include "vector.h" -#include "pool.h" -#include "str.h" -#include "futils.h" - -#define GIT_ATTR_FILE ".gitattributes" -#define GIT_ATTR_FILE_INREPO "attributes" -#define GIT_ATTR_FILE_SYSTEM "gitattributes" -#define GIT_ATTR_FILE_XDG "attributes" - -#define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0) -#define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1) -#define GIT_ATTR_FNMATCH_FULLPATH (1U << 2) -#define GIT_ATTR_FNMATCH_MACRO (1U << 3) -#define GIT_ATTR_FNMATCH_IGNORE (1U << 4) -#define GIT_ATTR_FNMATCH_HASWILD (1U << 5) -#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6) -#define GIT_ATTR_FNMATCH_ICASE (1U << 7) -#define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8) -#define GIT_ATTR_FNMATCH_ALLOWNEG (1U << 9) -#define GIT_ATTR_FNMATCH_ALLOWMACRO (1U << 10) - -#define GIT_ATTR_FNMATCH__INCOMING \ - (GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO) - -typedef enum { - GIT_ATTR_FILE_SOURCE_MEMORY = 0, - GIT_ATTR_FILE_SOURCE_FILE = 1, - GIT_ATTR_FILE_SOURCE_INDEX = 2, - GIT_ATTR_FILE_SOURCE_HEAD = 3, - GIT_ATTR_FILE_SOURCE_COMMIT = 4, - - GIT_ATTR_FILE_NUM_SOURCES = 5 -} git_attr_file_source_t; - -typedef struct { - /* The source location for the attribute file. */ - git_attr_file_source_t type; - - /* - * The filename of the attribute file to read (relative to the - * given base path). - */ - const char *base; - const char *filename; - - /* - * The commit ID when the given source type is a commit (or NULL - * for the repository's HEAD commit.) - */ - git_oid *commit_id; -} git_attr_file_source; - -extern const char *git_attr__true; -extern const char *git_attr__false; -extern const char *git_attr__unset; - -typedef struct { - char *pattern; - size_t length; - char *containing_dir; - size_t containing_dir_length; - unsigned int flags; -} git_attr_fnmatch; - -typedef struct { - git_attr_fnmatch match; - git_vector assigns; /* vector of */ -} git_attr_rule; - -typedef struct { - git_refcount unused; - const char *name; - uint32_t name_hash; -} git_attr_name; - -typedef struct { - git_refcount rc; /* for macros */ - char *name; - uint32_t name_hash; - const char *value; -} git_attr_assignment; - -typedef struct git_attr_file_entry git_attr_file_entry; - -typedef struct { - git_refcount rc; - git_mutex lock; - git_attr_file_entry *entry; - git_attr_file_source source; - git_vector rules; /* vector of or */ - git_pool pool; - unsigned int nonexistent:1; - int session_key; - union { - git_oid oid; - git_futils_filestamp stamp; - } cache_data; -} git_attr_file; - -struct git_attr_file_entry { - git_attr_file *file[GIT_ATTR_FILE_NUM_SOURCES]; - const char *path; /* points into fullpath */ - char fullpath[GIT_FLEX_ARRAY]; -}; - -typedef struct { - git_str full; - char *path; - char *basename; - int is_dir; -} git_attr_path; - -/* A git_attr_session can provide an "instance" of reading, to prevent cache - * invalidation during a single operation instance (like checkout). - */ - -typedef struct { - int key; - unsigned int init_setup:1, - init_sysdir:1; - git_str sysdir; - git_str tmp; -} git_attr_session; - -extern int git_attr_session__init(git_attr_session *attr_session, git_repository *repo); -extern void git_attr_session__free(git_attr_session *session); - -extern int git_attr_get_many_with_session( - const char **values_out, - git_repository *repo, - git_attr_session *attr_session, - git_attr_options *opts, - const char *path, - size_t num_attr, - const char **names); - -typedef int (*git_attr_file_parser)( - git_repository *repo, - git_attr_file *file, - const char *data, - bool allow_macros); - -/* - * git_attr_file API - */ - -int git_attr_file__new( - git_attr_file **out, - git_attr_file_entry *entry, - git_attr_file_source *source); - -void git_attr_file__free(git_attr_file *file); - -int git_attr_file__load( - git_attr_file **out, - git_repository *repo, - git_attr_session *attr_session, - git_attr_file_entry *ce, - git_attr_file_source *source, - git_attr_file_parser parser, - bool allow_macros); - -int git_attr_file__load_standalone( - git_attr_file **out, const char *path); - -int git_attr_file__out_of_date( - git_repository *repo, git_attr_session *session, git_attr_file *file, git_attr_file_source *source); - -int git_attr_file__parse_buffer( - git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros); - -int git_attr_file__clear_rules( - git_attr_file *file, bool need_lock); - -int git_attr_file__lookup_one( - git_attr_file *file, - git_attr_path *path, - const char *attr, - const char **value); - -/* loop over rules in file from bottom to top */ -#define git_attr_file__foreach_matching_rule(file, path, iter, rule) \ - git_vector_rforeach(&(file)->rules, (iter), (rule)) \ - if (git_attr_rule__match((rule), (path))) - -uint32_t git_attr_file__name_hash(const char *name); - - -/* - * other utilities - */ - -extern int git_attr_fnmatch__parse( - git_attr_fnmatch *spec, - git_pool *pool, - const char *source, - const char **base); - -extern bool git_attr_fnmatch__match( - git_attr_fnmatch *rule, - git_attr_path *path); - -extern void git_attr_rule__free(git_attr_rule *rule); - -extern bool git_attr_rule__match( - git_attr_rule *rule, - git_attr_path *path); - -extern git_attr_assignment *git_attr_rule__lookup_assignment( - git_attr_rule *rule, const char *name); - -typedef enum { GIT_DIR_FLAG_TRUE = 1, GIT_DIR_FLAG_FALSE = 0, GIT_DIR_FLAG_UNKNOWN = -1 } git_dir_flag; - -extern int git_attr_path__init( - git_attr_path *out, - const char *path, - const char *base, - git_dir_flag is_dir); -extern void git_attr_path__free(git_attr_path *info); - -extern int git_attr_assignment__parse( - git_repository *repo, /* needed to expand macros */ - git_pool *pool, - git_vector *assigns, - const char **scan); - -#endif diff --git a/src/attrcache.c b/src/attrcache.c deleted file mode 100644 index b16d95c3c..000000000 --- a/src/attrcache.c +++ /dev/null @@ -1,478 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "attrcache.h" - -#include "repository.h" -#include "attr_file.h" -#include "config.h" -#include "sysdir.h" -#include "ignore.h" -#include "path.h" - -GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache) -{ - GIT_UNUSED(cache); /* avoid warning if threading is off */ - - if (git_mutex_lock(&cache->lock) < 0) { - git_error_set(GIT_ERROR_OS, "unable to get attr cache lock"); - return -1; - } - return 0; -} - -GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache) -{ - GIT_UNUSED(cache); /* avoid warning if threading is off */ - git_mutex_unlock(&cache->lock); -} - -GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry( - git_attr_cache *cache, const char *path) -{ - return git_strmap_get(cache->files, path); -} - -int git_attr_cache__alloc_file_entry( - git_attr_file_entry **out, - git_repository *repo, - const char *base, - const char *path, - git_pool *pool) -{ - git_str fullpath_str = GIT_STR_INIT; - size_t baselen = 0, pathlen = strlen(path); - size_t cachesize = sizeof(git_attr_file_entry) + pathlen + 1; - git_attr_file_entry *ce; - - if (base != NULL && git_fs_path_root(path) < 0) { - baselen = strlen(base); - cachesize += baselen; - - if (baselen && base[baselen - 1] != '/') - cachesize++; - } - - ce = git_pool_mallocz(pool, cachesize); - GIT_ERROR_CHECK_ALLOC(ce); - - if (baselen) { - memcpy(ce->fullpath, base, baselen); - - if (base[baselen - 1] != '/') - ce->fullpath[baselen++] = '/'; - } - memcpy(&ce->fullpath[baselen], path, pathlen); - - fullpath_str.ptr = ce->fullpath; - fullpath_str.size = pathlen + baselen; - - if (git_path_validate_str_length(repo, &fullpath_str) < 0) - return -1; - - ce->path = &ce->fullpath[baselen]; - *out = ce; - - return 0; -} - -/* call with attrcache locked */ -static int attr_cache_make_entry( - git_attr_file_entry **out, git_repository *repo, const char *path) -{ - git_attr_cache *cache = git_repository_attr_cache(repo); - git_attr_file_entry *entry = NULL; - int error; - - if ((error = git_attr_cache__alloc_file_entry(&entry, repo, - git_repository_workdir(repo), path, &cache->pool)) < 0) - return error; - - if ((error = git_strmap_set(cache->files, entry->path, entry)) < 0) - return error; - - *out = entry; - return error; -} - -/* insert entry or replace existing if we raced with another thread */ -static int attr_cache_upsert(git_attr_cache *cache, git_attr_file *file) -{ - git_attr_file_entry *entry; - git_attr_file *old; - - if (attr_cache_lock(cache) < 0) - return -1; - - entry = attr_cache_lookup_entry(cache, file->entry->path); - - GIT_REFCOUNT_OWN(file, entry); - GIT_REFCOUNT_INC(file); - - /* - * Replace the existing value if another thread has - * created it in the meantime. - */ - old = git_atomic_swap(entry->file[file->source.type], file); - - if (old) { - GIT_REFCOUNT_OWN(old, NULL); - git_attr_file__free(old); - } - - attr_cache_unlock(cache); - return 0; -} - -static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file) -{ - int error = 0; - git_attr_file_entry *entry; - git_attr_file *oldfile = NULL; - - if (!file) - return 0; - - if ((error = attr_cache_lock(cache)) < 0) - return error; - - if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL) - oldfile = git_atomic_compare_and_swap(&entry->file[file->source.type], file, NULL); - - attr_cache_unlock(cache); - - if (oldfile == file) { - GIT_REFCOUNT_OWN(file, NULL); - git_attr_file__free(file); - } - - return error; -} - -/* Look up cache entry and file. - * - If entry is not present, create it while the cache is locked. - * - If file is present, increment refcount before returning it, so the - * cache can be unlocked and it won't go away. - */ -static int attr_cache_lookup( - git_attr_file **out_file, - git_attr_file_entry **out_entry, - git_repository *repo, - git_attr_session *attr_session, - git_attr_file_source *source) -{ - int error = 0; - git_str path = GIT_STR_INIT; - const char *wd = git_repository_workdir(repo); - const char *filename; - git_attr_cache *cache = git_repository_attr_cache(repo); - git_attr_file_entry *entry = NULL; - git_attr_file *file = NULL; - - /* join base and path as needed */ - if (source->base != NULL && git_fs_path_root(source->filename) < 0) { - git_str *p = attr_session ? &attr_session->tmp : &path; - - if (git_str_joinpath(p, source->base, source->filename) < 0 || - git_path_validate_str_length(repo, p) < 0) - return -1; - - filename = p->ptr; - } else { - filename = source->filename; - } - - if (wd && !git__prefixcmp(filename, wd)) - filename += strlen(wd); - - /* check cache for existing entry */ - if ((error = attr_cache_lock(cache)) < 0) - goto cleanup; - - entry = attr_cache_lookup_entry(cache, filename); - - if (!entry) { - error = attr_cache_make_entry(&entry, repo, filename); - } else if (entry->file[source->type] != NULL) { - file = entry->file[source->type]; - GIT_REFCOUNT_INC(file); - } - - attr_cache_unlock(cache); - -cleanup: - *out_file = file; - *out_entry = entry; - - git_str_dispose(&path); - return error; -} - -int git_attr_cache__get( - git_attr_file **out, - git_repository *repo, - git_attr_session *attr_session, - git_attr_file_source *source, - git_attr_file_parser parser, - bool allow_macros) -{ - int error = 0; - git_attr_cache *cache = git_repository_attr_cache(repo); - git_attr_file_entry *entry = NULL; - git_attr_file *file = NULL, *updated = NULL; - - if ((error = attr_cache_lookup(&file, &entry, repo, attr_session, source)) < 0) - return error; - - /* load file if we don't have one or if existing one is out of date */ - if (!file || - (error = git_attr_file__out_of_date(repo, attr_session, file, source)) > 0) - error = git_attr_file__load(&updated, repo, attr_session, - entry, source, parser, - allow_macros); - - /* if we loaded the file, insert into and/or update cache */ - if (updated) { - if ((error = attr_cache_upsert(cache, updated)) < 0) { - git_attr_file__free(updated); - } else { - git_attr_file__free(file); /* offset incref from lookup */ - file = updated; - } - } - - /* if file could not be loaded */ - if (error < 0) { - /* remove existing entry */ - if (file) { - attr_cache_remove(cache, file); - git_attr_file__free(file); /* offset incref from lookup */ - file = NULL; - } - /* no error if file simply doesn't exist */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - } - - *out = file; - return error; -} - -bool git_attr_cache__is_cached( - git_repository *repo, - git_attr_file_source_t source_type, - const char *filename) -{ - git_attr_cache *cache = git_repository_attr_cache(repo); - git_attr_file_entry *entry; - git_strmap *files; - - if (!cache || !(files = cache->files)) - return false; - - if ((entry = git_strmap_get(files, filename)) == NULL) - return false; - - return entry && (entry->file[source_type] != NULL); -} - - -static int attr_cache__lookup_path( - char **out, git_config *cfg, const char *key, const char *fallback) -{ - git_str buf = GIT_STR_INIT; - int error; - git_config_entry *entry = NULL; - - *out = NULL; - - if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0) - return error; - - if (entry) { - const char *cfgval = entry->value; - - /* expand leading ~/ as needed */ - if (cfgval && cfgval[0] == '~' && cfgval[1] == '/') { - if (! (error = git_sysdir_expand_global_file(&buf, &cfgval[2]))) - *out = git_str_detach(&buf); - } else if (cfgval) { - *out = git__strdup(cfgval); - } - } - else if (!git_sysdir_find_xdg_file(&buf, fallback)) { - *out = git_str_detach(&buf); - } - - git_config_entry_free(entry); - git_str_dispose(&buf); - - return error; -} - -static void attr_cache__free(git_attr_cache *cache) -{ - bool unlock; - - if (!cache) - return; - - unlock = (attr_cache_lock(cache) == 0); - - if (cache->files != NULL) { - git_attr_file_entry *entry; - git_attr_file *file; - int i; - - git_strmap_foreach_value(cache->files, entry, { - for (i = 0; i < GIT_ATTR_FILE_NUM_SOURCES; ++i) { - if ((file = git_atomic_swap(entry->file[i], NULL)) != NULL) { - GIT_REFCOUNT_OWN(file, NULL); - git_attr_file__free(file); - } - } - }); - git_strmap_free(cache->files); - } - - if (cache->macros != NULL) { - git_attr_rule *rule; - - git_strmap_foreach_value(cache->macros, rule, { - git_attr_rule__free(rule); - }); - git_strmap_free(cache->macros); - } - - git_pool_clear(&cache->pool); - - git__free(cache->cfg_attr_file); - cache->cfg_attr_file = NULL; - - git__free(cache->cfg_excl_file); - cache->cfg_excl_file = NULL; - - if (unlock) - attr_cache_unlock(cache); - git_mutex_free(&cache->lock); - - git__free(cache); -} - -int git_attr_cache__init(git_repository *repo) -{ - int ret = 0; - git_attr_cache *cache = git_repository_attr_cache(repo); - git_config *cfg = NULL; - - if (cache) - return 0; - - cache = git__calloc(1, sizeof(git_attr_cache)); - GIT_ERROR_CHECK_ALLOC(cache); - - /* set up lock */ - if (git_mutex_init(&cache->lock) < 0) { - git_error_set(GIT_ERROR_OS, "unable to initialize lock for attr cache"); - git__free(cache); - return -1; - } - - if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0) - goto cancel; - - /* cache config settings for attributes and ignores */ - ret = attr_cache__lookup_path( - &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG); - if (ret < 0) - goto cancel; - - ret = attr_cache__lookup_path( - &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG); - if (ret < 0) - goto cancel; - - /* allocate hashtable for attribute and ignore file contents, - * hashtable for attribute macros, and string pool - */ - if ((ret = git_strmap_new(&cache->files)) < 0 || - (ret = git_strmap_new(&cache->macros)) < 0 || - (ret = git_pool_init(&cache->pool, 1)) < 0) - goto cancel; - - if (git_atomic_compare_and_swap(&repo->attrcache, NULL, cache) != NULL) - goto cancel; /* raced with another thread, free this but no error */ - - git_config_free(cfg); - - /* insert default macros */ - return git_attr_add_macro(repo, "binary", "-diff -merge -text -crlf"); - -cancel: - attr_cache__free(cache); - git_config_free(cfg); - return ret; -} - -int git_attr_cache_flush(git_repository *repo) -{ - git_attr_cache *cache; - - /* this could be done less expensively, but for now, we'll just free - * the entire attrcache and let the next use reinitialize it... - */ - if (repo && (cache = git_atomic_swap(repo->attrcache, NULL)) != NULL) - attr_cache__free(cache); - - return 0; -} - -int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro) -{ - git_attr_cache *cache = git_repository_attr_cache(repo); - git_attr_rule *preexisting; - bool locked = false; - int error = 0; - - /* - * Callers assume that if we return success, that the - * macro will have been adopted by the attributes cache. - * Thus, we have to free the macro here if it's not being - * added to the cache. - * - * TODO: generate warning log if (macro->assigns.length == 0) - */ - if (macro->assigns.length == 0) { - git_attr_rule__free(macro); - goto out; - } - - if ((error = attr_cache_lock(cache)) < 0) - goto out; - locked = true; - - if ((preexisting = git_strmap_get(cache->macros, macro->match.pattern)) != NULL) - git_attr_rule__free(preexisting); - - if ((error = git_strmap_set(cache->macros, macro->match.pattern, macro)) < 0) - goto out; - -out: - if (locked) - attr_cache_unlock(cache); - return error; -} - -git_attr_rule *git_attr_cache__lookup_macro( - git_repository *repo, const char *name) -{ - git_strmap *macros = git_repository_attr_cache(repo)->macros; - - return git_strmap_get(macros, name); -} diff --git a/src/attrcache.h b/src/attrcache.h deleted file mode 100644 index b13e0e8f0..000000000 --- a/src/attrcache.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_attrcache_h__ -#define INCLUDE_attrcache_h__ - -#include "common.h" - -#include "attr_file.h" -#include "strmap.h" - -#define GIT_ATTR_CONFIG "core.attributesfile" -#define GIT_IGNORE_CONFIG "core.excludesfile" - -typedef struct { - char *cfg_attr_file; /* cached value of core.attributesfile */ - char *cfg_excl_file; /* cached value of core.excludesfile */ - git_strmap *files; /* hash path to git_attr_cache_entry records */ - git_strmap *macros; /* hash name to vector */ - git_mutex lock; - git_pool pool; -} git_attr_cache; - -extern int git_attr_cache__init(git_repository *repo); - -/* get file - loading and reload as needed */ -extern int git_attr_cache__get( - git_attr_file **file, - git_repository *repo, - git_attr_session *attr_session, - git_attr_file_source *source, - git_attr_file_parser parser, - bool allow_macros); - -extern bool git_attr_cache__is_cached( - git_repository *repo, - git_attr_file_source_t source_type, - const char *filename); - -extern int git_attr_cache__alloc_file_entry( - git_attr_file_entry **out, - git_repository *repo, - const char *base, - const char *path, - git_pool *pool); - -extern int git_attr_cache__insert_macro( - git_repository *repo, git_attr_rule *macro); - -extern git_attr_rule *git_attr_cache__lookup_macro( - git_repository *repo, const char *name); - -#endif diff --git a/src/bitvec.h b/src/bitvec.h deleted file mode 100644 index 544832d95..000000000 --- a/src/bitvec.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_bitvec_h__ -#define INCLUDE_bitvec_h__ - -#include "common.h" - -/* - * This is a silly little fixed length bit vector type that will store - * vectors of 64 bits or less directly in the structure and allocate - * memory for vectors longer than 64 bits. You can use the two versions - * transparently through the API and avoid heap allocation completely when - * using a short bit vector as a result. - */ -typedef struct { - size_t length; - union { - uint64_t *words; - uint64_t bits; - } u; -} git_bitvec; - -GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity) -{ - memset(bv, 0x0, sizeof(*bv)); - - if (capacity >= 64) { - bv->length = (capacity / 64) + 1; - bv->u.words = git__calloc(bv->length, sizeof(uint64_t)); - if (!bv->u.words) - return -1; - } - - return 0; -} - -#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64)) -#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits) - -GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on) -{ - uint64_t *word = GIT_BITVEC_WORD(bv, bit); - uint64_t mask = GIT_BITVEC_MASK(bit); - - if (on) - *word |= mask; - else - *word &= ~mask; -} - -GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit) -{ - uint64_t *word = GIT_BITVEC_WORD(bv, bit); - return (*word & GIT_BITVEC_MASK(bit)) != 0; -} - -GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv) -{ - if (!bv->length) - bv->u.bits = 0; - else - memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t)); -} - -GIT_INLINE(void) git_bitvec_free(git_bitvec *bv) -{ - if (bv->length) - git__free(bv->u.words); -} - -#endif diff --git a/src/blame.c b/src/blame.c deleted file mode 100644 index a6ab43efd..000000000 --- a/src/blame.c +++ /dev/null @@ -1,557 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "blame.h" - -#include "git2/commit.h" -#include "git2/revparse.h" -#include "git2/revwalk.h" -#include "git2/tree.h" -#include "git2/diff.h" -#include "git2/blob.h" -#include "git2/signature.h" -#include "git2/mailmap.h" -#include "util.h" -#include "repository.h" -#include "blame_git.h" - - -static int hunk_byfinalline_search_cmp(const void *key, const void *entry) -{ - git_blame_hunk *hunk = (git_blame_hunk*)entry; - - size_t lineno = *(size_t*)key; - size_t lines_in_hunk = hunk->lines_in_hunk; - size_t final_start_line_number = hunk->final_start_line_number; - - if (lineno < final_start_line_number) - return -1; - if (lineno >= final_start_line_number + lines_in_hunk) - return 1; - return 0; -} - -static int paths_cmp(const void *a, const void *b) { return git__strcmp((char*)a, (char*)b); } -static int hunk_cmp(const void *_a, const void *_b) -{ - git_blame_hunk *a = (git_blame_hunk*)_a, - *b = (git_blame_hunk*)_b; - - if (a->final_start_line_number > b->final_start_line_number) - return 1; - else if (a->final_start_line_number < b->final_start_line_number) - return -1; - else - return 0; -} - -static bool hunk_ends_at_or_before_line(git_blame_hunk *hunk, size_t line) -{ - return line >= (hunk->final_start_line_number + hunk->lines_in_hunk - 1); -} - -static bool hunk_starts_at_or_after_line(git_blame_hunk *hunk, size_t line) -{ - return line <= hunk->final_start_line_number; -} - -static git_blame_hunk *new_hunk( - size_t start, - size_t lines, - size_t orig_start, - const char *path) -{ - git_blame_hunk *hunk = git__calloc(1, sizeof(git_blame_hunk)); - if (!hunk) return NULL; - - hunk->lines_in_hunk = lines; - hunk->final_start_line_number = start; - hunk->orig_start_line_number = orig_start; - hunk->orig_path = path ? git__strdup(path) : NULL; - - return hunk; -} - -static void free_hunk(git_blame_hunk *hunk) -{ - git__free((void*)hunk->orig_path); - git_signature_free(hunk->final_signature); - git_signature_free(hunk->orig_signature); - git__free(hunk); -} - -static git_blame_hunk *dup_hunk(git_blame_hunk *hunk) -{ - git_blame_hunk *newhunk = new_hunk( - hunk->final_start_line_number, - hunk->lines_in_hunk, - hunk->orig_start_line_number, - hunk->orig_path); - - if (!newhunk) - return NULL; - - git_oid_cpy(&newhunk->orig_commit_id, &hunk->orig_commit_id); - git_oid_cpy(&newhunk->final_commit_id, &hunk->final_commit_id); - newhunk->boundary = hunk->boundary; - - if (git_signature_dup(&newhunk->final_signature, hunk->final_signature) < 0 || - git_signature_dup(&newhunk->orig_signature, hunk->orig_signature) < 0) { - free_hunk(newhunk); - return NULL; - } - - return newhunk; -} - -/* Starting with the hunk that includes start_line, shift all following hunks' - * final_start_line by shift_by lines */ -static void shift_hunks_by(git_vector *v, size_t start_line, int shift_by) -{ - size_t i; - - if (!git_vector_bsearch2(&i, v, hunk_byfinalline_search_cmp, &start_line)) { - for (; i < v->length; i++) { - git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i]; - hunk->final_start_line_number += shift_by; - } - } -} - -git_blame *git_blame__alloc( - git_repository *repo, - git_blame_options opts, - const char *path) -{ - git_blame *gbr = git__calloc(1, sizeof(git_blame)); - if (!gbr) - return NULL; - - gbr->repository = repo; - gbr->options = opts; - - if (git_vector_init(&gbr->hunks, 8, hunk_cmp) < 0 || - git_vector_init(&gbr->paths, 8, paths_cmp) < 0 || - (gbr->path = git__strdup(path)) == NULL || - git_vector_insert(&gbr->paths, git__strdup(path)) < 0) - { - git_blame_free(gbr); - return NULL; - } - - if (opts.flags & GIT_BLAME_USE_MAILMAP && - git_mailmap_from_repository(&gbr->mailmap, repo) < 0) { - git_blame_free(gbr); - return NULL; - } - - return gbr; -} - -void git_blame_free(git_blame *blame) -{ - size_t i; - git_blame_hunk *hunk; - - if (!blame) return; - - git_vector_foreach(&blame->hunks, i, hunk) - free_hunk(hunk); - git_vector_free(&blame->hunks); - - git_vector_free_deep(&blame->paths); - - git_array_clear(blame->line_index); - - git_mailmap_free(blame->mailmap); - - git__free(blame->path); - git_blob_free(blame->final_blob); - git__free(blame); -} - -uint32_t git_blame_get_hunk_count(git_blame *blame) -{ - GIT_ASSERT_ARG(blame); - return (uint32_t)blame->hunks.length; -} - -const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index) -{ - GIT_ASSERT_ARG_WITH_RETVAL(blame, NULL); - return (git_blame_hunk*)git_vector_get(&blame->hunks, index); -} - -const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, size_t lineno) -{ - size_t i, new_lineno = lineno; - - GIT_ASSERT_ARG_WITH_RETVAL(blame, NULL); - - if (!git_vector_bsearch2(&i, &blame->hunks, hunk_byfinalline_search_cmp, &new_lineno)) { - return git_blame_get_hunk_byindex(blame, (uint32_t)i); - } - - return NULL; -} - -static int normalize_options( - git_blame_options *out, - const git_blame_options *in, - git_repository *repo) -{ - git_blame_options dummy = GIT_BLAME_OPTIONS_INIT; - if (!in) in = &dummy; - - memcpy(out, in, sizeof(git_blame_options)); - - /* No newest_commit => HEAD */ - if (git_oid_is_zero(&out->newest_commit)) { - if (git_reference_name_to_id(&out->newest_commit, repo, "HEAD") < 0) { - return -1; - } - } - - /* min_line 0 really means 1 */ - if (!out->min_line) out->min_line = 1; - /* max_line 0 really means N, but we don't know N yet */ - - /* Fix up option implications */ - if (out->flags & GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES) - out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; - if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES) - out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; - if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES) - out->flags |= GIT_BLAME_TRACK_COPIES_SAME_FILE; - - return 0; -} - -static git_blame_hunk *split_hunk_in_vector( - git_vector *vec, - git_blame_hunk *hunk, - size_t rel_line, - bool return_new) -{ - size_t new_line_count; - git_blame_hunk *nh; - - /* Don't split if already at a boundary */ - if (rel_line <= 0 || - rel_line >= hunk->lines_in_hunk) - { - return hunk; - } - - new_line_count = hunk->lines_in_hunk - rel_line; - nh = new_hunk(hunk->final_start_line_number + rel_line, new_line_count, - hunk->orig_start_line_number + rel_line, hunk->orig_path); - - if (!nh) - return NULL; - - git_oid_cpy(&nh->final_commit_id, &hunk->final_commit_id); - git_oid_cpy(&nh->orig_commit_id, &hunk->orig_commit_id); - - /* Adjust hunk that was split */ - hunk->lines_in_hunk -= new_line_count; - git_vector_insert_sorted(vec, nh, NULL); - { - git_blame_hunk *ret = return_new ? nh : hunk; - return ret; - } -} - -/* - * Construct a list of char indices for where lines begin - * Adapted from core git: - * https://github.com/gitster/git/blob/be5c9fb9049ed470e7005f159bb923a5f4de1309/builtin/blame.c#L1760-L1789 - */ -static int index_blob_lines(git_blame *blame) -{ - const char *buf = blame->final_buf; - size_t len = blame->final_buf_size; - int num = 0, incomplete = 0, bol = 1; - size_t *i; - - if (len && buf[len-1] != '\n') - incomplete++; /* incomplete line at the end */ - while (len--) { - if (bol) { - i = git_array_alloc(blame->line_index); - GIT_ERROR_CHECK_ALLOC(i); - *i = buf - blame->final_buf; - bol = 0; - } - if (*buf++ == '\n') { - num++; - bol = 1; - } - } - i = git_array_alloc(blame->line_index); - GIT_ERROR_CHECK_ALLOC(i); - *i = buf - blame->final_buf; - blame->num_lines = num + incomplete; - return blame->num_lines; -} - -static git_blame_hunk *hunk_from_entry(git_blame__entry *e, git_blame *blame) -{ - git_blame_hunk *h = new_hunk( - e->lno+1, e->num_lines, e->s_lno+1, e->suspect->path); - - if (!h) - return NULL; - - git_oid_cpy(&h->final_commit_id, git_commit_id(e->suspect->commit)); - git_oid_cpy(&h->orig_commit_id, git_commit_id(e->suspect->commit)); - git_commit_author_with_mailmap( - &h->final_signature, e->suspect->commit, blame->mailmap); - git_signature_dup(&h->orig_signature, h->final_signature); - h->boundary = e->is_boundary ? 1 : 0; - return h; -} - -static int load_blob(git_blame *blame) -{ - int error; - - if (blame->final_blob) return 0; - - error = git_commit_lookup(&blame->final, blame->repository, &blame->options.newest_commit); - if (error < 0) - goto cleanup; - error = git_object_lookup_bypath((git_object**)&blame->final_blob, - (git_object*)blame->final, blame->path, GIT_OBJECT_BLOB); - -cleanup: - return error; -} - -static int blame_internal(git_blame *blame) -{ - int error; - git_blame__entry *ent = NULL; - git_blame__origin *o; - - if ((error = load_blob(blame)) < 0 || - (error = git_blame__get_origin(&o, blame, blame->final, blame->path)) < 0) - goto cleanup; - - if (git_blob_rawsize(blame->final_blob) > SIZE_MAX) { - git_error_set(GIT_ERROR_NOMEMORY, "blob is too large to blame"); - error = -1; - goto cleanup; - } - - blame->final_buf = git_blob_rawcontent(blame->final_blob); - blame->final_buf_size = (size_t)git_blob_rawsize(blame->final_blob); - - ent = git__calloc(1, sizeof(git_blame__entry)); - GIT_ERROR_CHECK_ALLOC(ent); - - ent->num_lines = index_blob_lines(blame); - ent->lno = blame->options.min_line - 1; - ent->num_lines = ent->num_lines - blame->options.min_line + 1; - if (blame->options.max_line > 0) - ent->num_lines = blame->options.max_line - blame->options.min_line + 1; - ent->s_lno = ent->lno; - ent->suspect = o; - - blame->ent = ent; - - error = git_blame__like_git(blame, blame->options.flags); - -cleanup: - for (ent = blame->ent; ent; ) { - git_blame__entry *e = ent->next; - git_blame_hunk *h = hunk_from_entry(ent, blame); - - git_vector_insert(&blame->hunks, h); - - git_blame__free_entry(ent); - ent = e; - } - - return error; -} - -/******************************************************************************* - * File blaming - ******************************************************************************/ - -int git_blame_file( - git_blame **out, - git_repository *repo, - const char *path, - git_blame_options *options) -{ - int error = -1; - git_blame_options normOptions = GIT_BLAME_OPTIONS_INIT; - git_blame *blame = NULL; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(path); - - if ((error = normalize_options(&normOptions, options, repo)) < 0) - goto on_error; - - blame = git_blame__alloc(repo, normOptions, path); - GIT_ERROR_CHECK_ALLOC(blame); - - if ((error = load_blob(blame)) < 0) - goto on_error; - - if ((error = blame_internal(blame)) < 0) - goto on_error; - - *out = blame; - return 0; - -on_error: - git_blame_free(blame); - return error; -} - -/******************************************************************************* - * Buffer blaming - *******************************************************************************/ - -static bool hunk_is_bufferblame(git_blame_hunk *hunk) -{ - return hunk && git_oid_is_zero(&hunk->final_commit_id); -} - -static int buffer_hunk_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - void *payload) -{ - git_blame *blame = (git_blame*)payload; - uint32_t wedge_line; - - GIT_UNUSED(delta); - - wedge_line = (hunk->old_lines == 0) ? hunk->new_start : hunk->old_start; - blame->current_diff_line = wedge_line; - - blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byline(blame, wedge_line); - if (!blame->current_hunk) { - /* Line added at the end of the file */ - blame->current_hunk = new_hunk(wedge_line, 0, wedge_line, blame->path); - GIT_ERROR_CHECK_ALLOC(blame->current_hunk); - - git_vector_insert(&blame->hunks, blame->current_hunk); - } else if (!hunk_starts_at_or_after_line(blame->current_hunk, wedge_line)){ - /* If this hunk doesn't start between existing hunks, split a hunk up so it does */ - blame->current_hunk = split_hunk_in_vector(&blame->hunks, blame->current_hunk, - wedge_line - blame->current_hunk->orig_start_line_number, true); - GIT_ERROR_CHECK_ALLOC(blame->current_hunk); - } - - return 0; -} - -static int ptrs_equal_cmp(const void *a, const void *b) { return ab ? 1 : 0; } -static int buffer_line_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *payload) -{ - git_blame *blame = (git_blame*)payload; - - GIT_UNUSED(delta); - GIT_UNUSED(hunk); - GIT_UNUSED(line); - - if (line->origin == GIT_DIFF_LINE_ADDITION) { - if (hunk_is_bufferblame(blame->current_hunk) && - hunk_ends_at_or_before_line(blame->current_hunk, blame->current_diff_line)) { - /* Append to the current buffer-blame hunk */ - blame->current_hunk->lines_in_hunk++; - shift_hunks_by(&blame->hunks, blame->current_diff_line+1, 1); - } else { - /* Create a new buffer-blame hunk with this line */ - shift_hunks_by(&blame->hunks, blame->current_diff_line, 1); - blame->current_hunk = new_hunk(blame->current_diff_line, 1, 0, blame->path); - GIT_ERROR_CHECK_ALLOC(blame->current_hunk); - - git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL); - } - blame->current_diff_line++; - } - - if (line->origin == GIT_DIFF_LINE_DELETION) { - /* Trim the line from the current hunk; remove it if it's now empty */ - size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk+1; - - if (--(blame->current_hunk->lines_in_hunk) == 0) { - size_t i; - shift_base--; - if (!git_vector_search2(&i, &blame->hunks, ptrs_equal_cmp, blame->current_hunk)) { - git_vector_remove(&blame->hunks, i); - free_hunk(blame->current_hunk); - blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, (uint32_t)i); - } - } - shift_hunks_by(&blame->hunks, shift_base, -1); - } - return 0; -} - -int git_blame_buffer( - git_blame **out, - git_blame *reference, - const char *buffer, - size_t buffer_len) -{ - git_blame *blame; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - size_t i; - git_blame_hunk *hunk; - - diffopts.context_lines = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(reference); - GIT_ASSERT_ARG(buffer && buffer_len); - - blame = git_blame__alloc(reference->repository, reference->options, reference->path); - GIT_ERROR_CHECK_ALLOC(blame); - - /* Duplicate all of the hunk structures in the reference blame */ - git_vector_foreach(&reference->hunks, i, hunk) { - git_blame_hunk *h = dup_hunk(hunk); - GIT_ERROR_CHECK_ALLOC(h); - - git_vector_insert(&blame->hunks, h); - } - - /* Diff to the reference blob */ - git_diff_blob_to_buffer(reference->final_blob, blame->path, - buffer, buffer_len, blame->path, &diffopts, - NULL, NULL, buffer_hunk_cb, buffer_line_cb, blame); - - *out = blame; - return 0; -} - -int git_blame_options_init(git_blame_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_blame_options, GIT_BLAME_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_blame_init_options(git_blame_options *opts, unsigned int version) -{ - return git_blame_options_init(opts, version); -} -#endif diff --git a/src/blame.h b/src/blame.h deleted file mode 100644 index 4141e2bb5..000000000 --- a/src/blame.h +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef INCLUDE_blame_h__ -#define INCLUDE_blame_h__ - -#include "common.h" - -#include "git2/blame.h" -#include "vector.h" -#include "diff.h" -#include "array.h" -#include "git2/oid.h" - -/* - * One blob in a commit that is being suspected - */ -typedef struct git_blame__origin { - int refcnt; - struct git_blame__origin *previous; - git_commit *commit; - git_blob *blob; - char path[GIT_FLEX_ARRAY]; -} git_blame__origin; - -/* - * Each group of lines is described by a git_blame__entry; it can be split - * as we pass blame to the parents. They form a linked list in the - * scoreboard structure, sorted by the target line number. - */ -typedef struct git_blame__entry { - struct git_blame__entry *prev; - struct git_blame__entry *next; - - /* the first line of this group in the final image; - * internally all line numbers are 0 based. - */ - size_t lno; - - /* how many lines this group has */ - size_t num_lines; - - /* the commit that introduced this group into the final image */ - git_blame__origin *suspect; - - /* true if the suspect is truly guilty; false while we have not - * checked if the group came from one of its parents. - */ - bool guilty; - - /* true if the entry has been scanned for copies in the current parent - */ - bool scanned; - - /* the line number of the first line of this group in the - * suspect's file; internally all line numbers are 0 based. - */ - size_t s_lno; - - /* how significant this entry is -- cached to avoid - * scanning the lines over and over. - */ - unsigned score; - - /* Whether this entry has been tracked to a boundary commit. - */ - bool is_boundary; -} git_blame__entry; - -struct git_blame { - char *path; - git_repository *repository; - git_mailmap *mailmap; - git_blame_options options; - - git_vector hunks; - git_vector paths; - - git_blob *final_blob; - git_array_t(size_t) line_index; - - size_t current_diff_line; - git_blame_hunk *current_hunk; - - /* Scoreboard fields */ - git_commit *final; - git_blame__entry *ent; - int num_lines; - const char *final_buf; - size_t final_buf_size; -}; - -git_blame *git_blame__alloc( - git_repository *repo, - git_blame_options opts, - const char *path); - -#endif diff --git a/src/blame_git.c b/src/blame_git.c deleted file mode 100644 index 2504b338a..000000000 --- a/src/blame_git.c +++ /dev/null @@ -1,685 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "blame_git.h" - -#include "commit.h" -#include "blob.h" -#include "xdiff/xinclude.h" -#include "diff_xdiff.h" - -/* - * Origin is refcounted and usually we keep the blob contents to be - * reused. - */ -static git_blame__origin *origin_incref(git_blame__origin *o) -{ - if (o) - o->refcnt++; - return o; -} - -static void origin_decref(git_blame__origin *o) -{ - if (o && --o->refcnt <= 0) { - if (o->previous) - origin_decref(o->previous); - git_blob_free(o->blob); - git_commit_free(o->commit); - git__free(o); - } -} - -/* Given a commit and a path in it, create a new origin structure. */ -static int make_origin(git_blame__origin **out, git_commit *commit, const char *path) -{ - git_blame__origin *o; - git_object *blob; - size_t path_len = strlen(path), alloc_len; - int error = 0; - - if ((error = git_object_lookup_bypath(&blob, (git_object*)commit, - path, GIT_OBJECT_BLOB)) < 0) - return error; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*o), path_len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); - o = git__calloc(1, alloc_len); - GIT_ERROR_CHECK_ALLOC(o); - - o->commit = commit; - o->blob = (git_blob *) blob; - o->refcnt = 1; - strcpy(o->path, path); - - *out = o; - - return 0; -} - -/* Locate an existing origin or create a new one. */ -int git_blame__get_origin( - git_blame__origin **out, - git_blame *blame, - git_commit *commit, - const char *path) -{ - git_blame__entry *e; - - for (e = blame->ent; e; e = e->next) { - if (e->suspect->commit == commit && !strcmp(e->suspect->path, path)) { - *out = origin_incref(e->suspect); - } - } - return make_origin(out, commit, path); -} - -typedef struct blame_chunk_cb_data { - git_blame *blame; - git_blame__origin *target; - git_blame__origin *parent; - long tlno; - long plno; -}blame_chunk_cb_data; - -static bool same_suspect(git_blame__origin *a, git_blame__origin *b) -{ - if (a == b) - return true; - if (git_oid_cmp(git_commit_id(a->commit), git_commit_id(b->commit))) - return false; - return 0 == strcmp(a->path, b->path); -} - -/* find the line number of the last line the target is suspected for */ -static bool find_last_in_target(size_t *out, git_blame *blame, git_blame__origin *target) -{ - git_blame__entry *e; - size_t last_in_target = 0; - bool found = false; - - *out = 0; - - for (e=blame->ent; e; e=e->next) { - if (e->guilty || !same_suspect(e->suspect, target)) - continue; - if (last_in_target < e->s_lno + e->num_lines) { - found = true; - last_in_target = e->s_lno + e->num_lines; - } - } - - *out = last_in_target; - return found; -} - -/* - * It is known that lines between tlno to same came from parent, and e - * has an overlap with that range. it also is known that parent's - * line plno corresponds to e's line tlno. - * - * <---- e -----> - * <------> (entirely within) - * <------------> (extends past) - * <------------> (starts before) - * <------------------> (entirely encloses) - * - * Split e into potentially three parts; before this chunk, the chunk - * to be blamed for the parent, and after that portion. - */ -static void split_overlap(git_blame__entry *split, git_blame__entry *e, - size_t tlno, size_t plno, size_t same, git_blame__origin *parent) -{ - size_t chunk_end_lno; - - if (e->s_lno < tlno) { - /* there is a pre-chunk part not blamed on the parent */ - split[0].suspect = origin_incref(e->suspect); - split[0].lno = e->lno; - split[0].s_lno = e->s_lno; - split[0].num_lines = tlno - e->s_lno; - split[1].lno = e->lno + tlno - e->s_lno; - split[1].s_lno = plno; - } else { - split[1].lno = e->lno; - split[1].s_lno = plno + (e->s_lno - tlno); - } - - if (same < e->s_lno + e->num_lines) { - /* there is a post-chunk part not blamed on parent */ - split[2].suspect = origin_incref(e->suspect); - split[2].lno = e->lno + (same - e->s_lno); - split[2].s_lno = e->s_lno + (same - e->s_lno); - split[2].num_lines = e->s_lno + e->num_lines - same; - chunk_end_lno = split[2].lno; - } else { - chunk_end_lno = e->lno + e->num_lines; - } - split[1].num_lines = chunk_end_lno - split[1].lno; - - /* - * if it turns out there is nothing to blame the parent for, forget about - * the splitting. !split[1].suspect signals this. - */ - if (split[1].num_lines < 1) - return; - split[1].suspect = origin_incref(parent); -} - -/* - * Link in a new blame entry to the scoreboard. Entries that cover the same - * line range have been removed from the scoreboard previously. - */ -static void add_blame_entry(git_blame *blame, git_blame__entry *e) -{ - git_blame__entry *ent, *prev = NULL; - - origin_incref(e->suspect); - - for (ent = blame->ent; ent && ent->lno < e->lno; ent = ent->next) - prev = ent; - - /* prev, if not NULL, is the last one that is below e */ - e->prev = prev; - if (prev) { - e->next = prev->next; - prev->next = e; - } else { - e->next = blame->ent; - blame->ent = e; - } - if (e->next) - e->next->prev = e; -} - -/* - * src typically is on-stack; we want to copy the information in it to - * a malloced blame_entry that is already on the linked list of the scoreboard. - * The origin of dst loses a refcnt while the origin of src gains one. - */ -static void dup_entry(git_blame__entry *dst, git_blame__entry *src) -{ - git_blame__entry *p, *n; - - p = dst->prev; - n = dst->next; - origin_incref(src->suspect); - origin_decref(dst->suspect); - memcpy(dst, src, sizeof(*src)); - dst->prev = p; - dst->next = n; - dst->score = 0; -} - -/* - * split_overlap() divided an existing blame e into up to three parts in split. - * Adjust the linked list of blames in the scoreboard to reflect the split. - */ -static int split_blame(git_blame *blame, git_blame__entry *split, git_blame__entry *e) -{ - git_blame__entry *new_entry; - - if (split[0].suspect && split[2].suspect) { - /* The first part (reuse storage for the existing entry e */ - dup_entry(e, &split[0]); - - /* The last part -- me */ - new_entry = git__malloc(sizeof(*new_entry)); - GIT_ERROR_CHECK_ALLOC(new_entry); - memcpy(new_entry, &(split[2]), sizeof(git_blame__entry)); - add_blame_entry(blame, new_entry); - - /* ... and the middle part -- parent */ - new_entry = git__malloc(sizeof(*new_entry)); - GIT_ERROR_CHECK_ALLOC(new_entry); - memcpy(new_entry, &(split[1]), sizeof(git_blame__entry)); - add_blame_entry(blame, new_entry); - } else if (!split[0].suspect && !split[2].suspect) { - /* - * The parent covers the entire area; reuse storage for e and replace it - * with the parent - */ - dup_entry(e, &split[1]); - } else if (split[0].suspect) { - /* me and then parent */ - dup_entry(e, &split[0]); - new_entry = git__malloc(sizeof(*new_entry)); - GIT_ERROR_CHECK_ALLOC(new_entry); - memcpy(new_entry, &(split[1]), sizeof(git_blame__entry)); - add_blame_entry(blame, new_entry); - } else { - /* parent and then me */ - dup_entry(e, &split[1]); - new_entry = git__malloc(sizeof(*new_entry)); - GIT_ERROR_CHECK_ALLOC(new_entry); - memcpy(new_entry, &(split[2]), sizeof(git_blame__entry)); - add_blame_entry(blame, new_entry); - } - - return 0; -} - -/* - * After splitting the blame, the origins used by the on-stack blame_entry - * should lose one refcnt each. - */ -static void decref_split(git_blame__entry *split) -{ - int i; - for (i=0; i<3; i++) - origin_decref(split[i].suspect); -} - -/* - * Helper for blame_chunk(). blame_entry e is known to overlap with the patch - * hunk; split it and pass blame to the parent. - */ -static int blame_overlap( - git_blame *blame, - git_blame__entry *e, - size_t tlno, - size_t plno, - size_t same, - git_blame__origin *parent) -{ - git_blame__entry split[3] = {{0}}; - - split_overlap(split, e, tlno, plno, same, parent); - if (split[1].suspect) - if (split_blame(blame, split, e) < 0) - return -1; - decref_split(split); - - return 0; -} - -/* - * Process one hunk from the patch between the current suspect for blame_entry - * e and its parent. Find and split the overlap, and pass blame to the - * overlapping part to the parent. - */ -static int blame_chunk( - git_blame *blame, - size_t tlno, - size_t plno, - size_t same, - git_blame__origin *target, - git_blame__origin *parent) -{ - git_blame__entry *e; - - for (e = blame->ent; e; e = e->next) { - if (e->guilty || !same_suspect(e->suspect, target)) - continue; - if (same <= e->s_lno) - continue; - if (tlno < e->s_lno + e->num_lines) { - if (blame_overlap(blame, e, tlno, plno, same, parent) < 0) - return -1; - } - } - - return 0; -} - -static int my_emit( - long start_a, long count_a, - long start_b, long count_b, - void *cb_data) -{ - blame_chunk_cb_data *d = (blame_chunk_cb_data *)cb_data; - - if (blame_chunk(d->blame, d->tlno, d->plno, start_b, d->target, d->parent) < 0) - return -1; - d->plno = start_a + count_a; - d->tlno = start_b + count_b; - - return 0; -} - -static void trim_common_tail(mmfile_t *a, mmfile_t *b, long ctx) -{ - const int blk = 1024; - long trimmed = 0, recovered = 0; - char *ap = a->ptr + a->size; - char *bp = b->ptr + b->size; - long smaller = (long)((a->size < b->size) ? a->size : b->size); - - if (ctx) - return; - - while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) { - trimmed += blk; - ap -= blk; - bp -= blk; - } - - while (recovered < trimmed) - if (ap[recovered++] == '\n') - break; - a->size -= trimmed - recovered; - b->size -= trimmed - recovered; -} - -static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data, git_blame_options *options) -{ - xdemitconf_t xecfg = {0}; - xdemitcb_t ecb = {0}; - xpparam_t xpp = {0}; - - if (options->flags & GIT_BLAME_IGNORE_WHITESPACE) - xpp.flags |= XDF_IGNORE_WHITESPACE; - - xecfg.hunk_func = my_emit; - ecb.priv = cb_data; - - trim_common_tail(&file_a, &file_b, 0); - - if (file_a.size > GIT_XDIFF_MAX_SIZE || - file_b.size > GIT_XDIFF_MAX_SIZE) { - git_error_set(GIT_ERROR_INVALID, "file too large to blame"); - return -1; - } - - return xdl_diff(&file_a, &file_b, &xpp, &xecfg, &ecb); -} - -static void fill_origin_blob(git_blame__origin *o, mmfile_t *file) -{ - memset(file, 0, sizeof(*file)); - if (o->blob) { - file->ptr = (char*)git_blob_rawcontent(o->blob); - file->size = (long)git_blob_rawsize(o->blob); - } -} - -static int pass_blame_to_parent( - git_blame *blame, - git_blame__origin *target, - git_blame__origin *parent) -{ - size_t last_in_target; - mmfile_t file_p, file_o; - blame_chunk_cb_data d = { blame, target, parent, 0, 0 }; - - if (!find_last_in_target(&last_in_target, blame, target)) - return 1; /* nothing remains for this target */ - - fill_origin_blob(parent, &file_p); - fill_origin_blob(target, &file_o); - - if (diff_hunks(file_p, file_o, &d, &blame->options) < 0) - return -1; - - /* The reset (i.e. anything after tlno) are the same as the parent */ - if (blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent) < 0) - return -1; - - return 0; -} - -static int paths_on_dup(void **old, void *new) -{ - GIT_UNUSED(old); - git__free(new); - return -1; -} - -static git_blame__origin *find_origin( - git_blame *blame, - git_commit *parent, - git_blame__origin *origin) -{ - git_blame__origin *porigin = NULL; - git_diff *difflist = NULL; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_tree *otree=NULL, *ptree=NULL; - - /* Get the trees from this commit and its parent */ - if (0 != git_commit_tree(&otree, origin->commit) || - 0 != git_commit_tree(&ptree, parent)) - goto cleanup; - - /* Configure the diff */ - diffopts.context_lines = 0; - diffopts.flags = GIT_DIFF_SKIP_BINARY_CHECK; - - /* Check to see if files we're interested have changed */ - diffopts.pathspec.count = blame->paths.length; - diffopts.pathspec.strings = (char**)blame->paths.contents; - if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts)) - goto cleanup; - - if (!git_diff_num_deltas(difflist)) { - /* No changes; copy data */ - git_blame__get_origin(&porigin, blame, parent, origin->path); - } else { - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - int i; - - /* Generate a full diff between the two trees */ - git_diff_free(difflist); - diffopts.pathspec.count = 0; - if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts)) - goto cleanup; - - /* Let diff find renames */ - findopts.flags = GIT_DIFF_FIND_RENAMES; - if (0 != git_diff_find_similar(difflist, &findopts)) - goto cleanup; - - /* Find one that matches */ - for (i=0; i<(int)git_diff_num_deltas(difflist); i++) { - const git_diff_delta *delta = git_diff_get_delta(difflist, i); - - if (!git_vector_bsearch(NULL, &blame->paths, delta->new_file.path)) - { - git_vector_insert_sorted(&blame->paths, (void*)git__strdup(delta->old_file.path), - paths_on_dup); - make_origin(&porigin, parent, delta->old_file.path); - } - } - } - -cleanup: - git_diff_free(difflist); - git_tree_free(otree); - git_tree_free(ptree); - return porigin; -} - -/* - * The blobs of origin and porigin exactly match, so everything origin is - * suspected for can be blamed on the parent. - */ -static int pass_whole_blame(git_blame *blame, - git_blame__origin *origin, git_blame__origin *porigin) -{ - git_blame__entry *e; - - if (!porigin->blob && - git_object_lookup((git_object**)&porigin->blob, blame->repository, - git_blob_id(origin->blob), GIT_OBJECT_BLOB) < 0) - return -1; - for (e=blame->ent; e; e=e->next) { - if (!same_suspect(e->suspect, origin)) - continue; - origin_incref(porigin); - origin_decref(e->suspect); - e->suspect = porigin; - } - - return 0; -} - -static int pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt) -{ - git_commit *commit = origin->commit; - int i, num_parents; - git_blame__origin *sg_buf[16]; - git_blame__origin *porigin, **sg_origin = sg_buf; - int ret, error = 0; - - num_parents = git_commit_parentcount(commit); - if (!git_oid_cmp(git_commit_id(commit), &blame->options.oldest_commit)) - /* Stop at oldest specified commit */ - num_parents = 0; - else if (opt & GIT_BLAME_FIRST_PARENT && num_parents > 1) - /* Limit search to the first parent */ - num_parents = 1; - - if (!num_parents) { - git_oid_cpy(&blame->options.oldest_commit, git_commit_id(commit)); - goto finish; - } else if (num_parents < (int)ARRAY_SIZE(sg_buf)) - memset(sg_buf, 0, sizeof(sg_buf)); - else { - sg_origin = git__calloc(num_parents, sizeof(*sg_origin)); - GIT_ERROR_CHECK_ALLOC(sg_origin); - } - - for (i=0; icommit, i)) < 0) - goto finish; - porigin = find_origin(blame, p, origin); - - if (!porigin) { - /* - * We only have to decrement the parent's - * reference count when no porigin has - * been created, as otherwise the commit - * is assigned to the created object. - */ - git_commit_free(p); - continue; - } - if (porigin->blob && origin->blob && - !git_oid_cmp(git_blob_id(porigin->blob), git_blob_id(origin->blob))) { - error = pass_whole_blame(blame, origin, porigin); - origin_decref(porigin); - goto finish; - } - for (j = same = 0; jblob), git_blob_id(porigin->blob))) { - same = 1; - break; - } - if (!same) - sg_origin[i] = porigin; - else - origin_decref(porigin); - } - - /* Standard blame */ - for (i=0; iprevious) { - origin_incref(porigin); - origin->previous = porigin; - } - - if ((ret = pass_blame_to_parent(blame, origin, porigin)) != 0) { - if (ret < 0) - error = -1; - - goto finish; - } - } - - /* TODO: optionally find moves in parents' files */ - - /* TODO: optionally find copies in parents' files */ - -finish: - for (i=0; i pair), - * merge them together. - */ -static void coalesce(git_blame *blame) -{ - git_blame__entry *ent, *next; - - for (ent=blame->ent; ent && (next = ent->next); ent = next) { - if (same_suspect(ent->suspect, next->suspect) && - ent->guilty == next->guilty && - ent->s_lno + ent->num_lines == next->s_lno) - { - ent->num_lines += next->num_lines; - ent->next = next->next; - if (ent->next) - ent->next->prev = ent; - origin_decref(next->suspect); - git__free(next); - ent->score = 0; - next = ent; /* again */ - } - } -} - -int git_blame__like_git(git_blame *blame, uint32_t opt) -{ - int error = 0; - - while (true) { - git_blame__entry *ent; - git_blame__origin *suspect = NULL; - - /* Find a suspect to break down */ - for (ent = blame->ent; !suspect && ent; ent = ent->next) - if (!ent->guilty) - suspect = ent->suspect; - if (!suspect) - break; - - /* We'll use this suspect later in the loop, so hold on to it for now. */ - origin_incref(suspect); - - if ((error = pass_blame(blame, suspect, opt)) < 0) - break; - - /* Take responsibility for the remaining entries */ - for (ent = blame->ent; ent; ent = ent->next) { - if (same_suspect(ent->suspect, suspect)) { - ent->guilty = true; - ent->is_boundary = !git_oid_cmp( - git_commit_id(suspect->commit), - &blame->options.oldest_commit); - } - } - origin_decref(suspect); - } - - if (!error) - coalesce(blame); - - return error; -} - -void git_blame__free_entry(git_blame__entry *ent) -{ - if (!ent) return; - origin_decref(ent->suspect); - git__free(ent); -} diff --git a/src/blame_git.h b/src/blame_git.h deleted file mode 100644 index 48b85a20d..000000000 --- a/src/blame_git.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_blame_git__ -#define INCLUDE_blame_git__ - -#include "common.h" - -#include "blame.h" - -int git_blame__get_origin( - git_blame__origin **out, - git_blame *sb, - git_commit *commit, - const char *path); -void git_blame__free_entry(git_blame__entry *ent); -int git_blame__like_git(git_blame *sb, uint32_t flags); - -#endif diff --git a/src/blob.c b/src/blob.c deleted file mode 100644 index 19ce8b3b5..000000000 --- a/src/blob.c +++ /dev/null @@ -1,528 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "blob.h" - -#include "git2/common.h" -#include "git2/object.h" -#include "git2/repository.h" -#include "git2/odb_backend.h" - -#include "buf.h" -#include "filebuf.h" -#include "filter.h" - -const void *git_blob_rawcontent(const git_blob *blob) -{ - GIT_ASSERT_ARG_WITH_RETVAL(blob, NULL); - - if (blob->raw) - return blob->data.raw.data; - else - return git_odb_object_data(blob->data.odb); -} - -git_object_size_t git_blob_rawsize(const git_blob *blob) -{ - GIT_ASSERT_ARG(blob); - - if (blob->raw) - return blob->data.raw.size; - else - return (git_object_size_t)git_odb_object_size(blob->data.odb); -} - -int git_blob__getbuf(git_str *buffer, git_blob *blob) -{ - git_object_size_t size = git_blob_rawsize(blob); - - GIT_ERROR_CHECK_BLOBSIZE(size); - return git_str_set(buffer, git_blob_rawcontent(blob), (size_t)size); -} - -void git_blob__free(void *_blob) -{ - git_blob *blob = (git_blob *) _blob; - if (!blob->raw) - git_odb_object_free(blob->data.odb); - git__free(blob); -} - -int git_blob__parse_raw(void *_blob, const char *data, size_t size) -{ - git_blob *blob = (git_blob *) _blob; - - GIT_ASSERT_ARG(blob); - - blob->raw = 1; - blob->data.raw.data = data; - blob->data.raw.size = size; - return 0; -} - -int git_blob__parse(void *_blob, git_odb_object *odb_obj) -{ - git_blob *blob = (git_blob *) _blob; - - GIT_ASSERT_ARG(blob); - - git_cached_obj_incref((git_cached_obj *)odb_obj); - blob->raw = 0; - blob->data.odb = odb_obj; - return 0; -} - -int git_blob_create_from_buffer( - git_oid *id, git_repository *repo, const void *buffer, size_t len) -{ - int error; - git_odb *odb; - git_odb_stream *stream; - - GIT_ASSERT_ARG(id); - GIT_ASSERT_ARG(repo); - - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || - (error = git_odb_open_wstream(&stream, odb, len, GIT_OBJECT_BLOB)) < 0) - return error; - - if ((error = git_odb_stream_write(stream, buffer, len)) == 0) - error = git_odb_stream_finalize_write(id, stream); - - git_odb_stream_free(stream); - return error; -} - -static int write_file_stream( - git_oid *id, git_odb *odb, const char *path, git_object_size_t file_size) -{ - int fd, error; - char buffer[FILEIO_BUFSIZE]; - git_odb_stream *stream = NULL; - ssize_t read_len = -1; - git_object_size_t written = 0; - - if ((error = git_odb_open_wstream( - &stream, odb, file_size, GIT_OBJECT_BLOB)) < 0) - return error; - - if ((fd = git_futils_open_ro(path)) < 0) { - git_odb_stream_free(stream); - return -1; - } - - while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { - error = git_odb_stream_write(stream, buffer, read_len); - written += read_len; - } - - p_close(fd); - - if (written != file_size || read_len < 0) { - git_error_set(GIT_ERROR_OS, "failed to read file into stream"); - error = -1; - } - - if (!error) - error = git_odb_stream_finalize_write(id, stream); - - git_odb_stream_free(stream); - return error; -} - -static int write_file_filtered( - git_oid *id, - git_object_size_t *size, - git_odb *odb, - const char *full_path, - git_filter_list *fl, - git_repository* repo) -{ - int error; - git_str tgt = GIT_STR_INIT; - - error = git_filter_list__apply_to_file(&tgt, fl, repo, full_path); - - /* Write the file to disk if it was properly filtered */ - if (!error) { - *size = tgt.size; - - error = git_odb_write(id, odb, tgt.ptr, tgt.size, GIT_OBJECT_BLOB); - } - - git_str_dispose(&tgt); - return error; -} - -static int write_symlink( - git_oid *id, git_odb *odb, const char *path, size_t link_size) -{ - char *link_data; - ssize_t read_len; - int error; - - link_data = git__malloc(link_size); - GIT_ERROR_CHECK_ALLOC(link_data); - - read_len = p_readlink(path, link_data, link_size); - if (read_len != (ssize_t)link_size) { - git_error_set(GIT_ERROR_OS, "failed to create blob: cannot read symlink '%s'", path); - git__free(link_data); - return -1; - } - - error = git_odb_write(id, odb, (void *)link_data, link_size, GIT_OBJECT_BLOB); - git__free(link_data); - return error; -} - -int git_blob__create_from_paths( - git_oid *id, - struct stat *out_st, - git_repository *repo, - const char *content_path, - const char *hint_path, - mode_t hint_mode, - bool try_load_filters) -{ - int error; - struct stat st; - git_odb *odb = NULL; - git_object_size_t size; - mode_t mode; - git_str path = GIT_STR_INIT; - - GIT_ASSERT_ARG(hint_path || !try_load_filters); - - if (!content_path) { - if (git_repository_workdir_path(&path, repo, hint_path) < 0) - return -1; - - content_path = path.ptr; - } - - if ((error = git_fs_path_lstat(content_path, &st)) < 0 || - (error = git_repository_odb(&odb, repo)) < 0) - goto done; - - if (S_ISDIR(st.st_mode)) { - git_error_set(GIT_ERROR_ODB, "cannot create blob from '%s': it is a directory", content_path); - error = GIT_EDIRECTORY; - goto done; - } - - if (out_st) - memcpy(out_st, &st, sizeof(st)); - - size = st.st_size; - mode = hint_mode ? hint_mode : st.st_mode; - - if (S_ISLNK(mode)) { - error = write_symlink(id, odb, content_path, (size_t)size); - } else { - git_filter_list *fl = NULL; - - if (try_load_filters) - /* Load the filters for writing this file to the ODB */ - error = git_filter_list_load( - &fl, repo, NULL, hint_path, - GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT); - - if (error < 0) - /* well, that didn't work */; - else if (fl == NULL) - /* No filters need to be applied to the document: we can stream - * directly from disk */ - error = write_file_stream(id, odb, content_path, size); - else { - /* We need to apply one or more filters */ - error = write_file_filtered(id, &size, odb, content_path, fl, repo); - - git_filter_list_free(fl); - } - - /* - * TODO: eventually support streaming filtered files, for files - * which are bigger than a given threshold. This is not a priority - * because applying a filter in streaming mode changes the final - * size of the blob, and without knowing its final size, the blob - * cannot be written in stream mode to the ODB. - * - * The plan is to do streaming writes to a tempfile on disk and then - * opening streaming that file to the ODB, using - * `write_file_stream`. - * - * CAREFULLY DESIGNED APIS YO - */ - } - -done: - git_odb_free(odb); - git_str_dispose(&path); - - return error; -} - -int git_blob_create_from_workdir( - git_oid *id, git_repository *repo, const char *path) -{ - return git_blob__create_from_paths(id, NULL, repo, NULL, path, 0, true); -} - -int git_blob_create_from_disk( - git_oid *id, git_repository *repo, const char *path) -{ - int error; - git_str full_path = GIT_STR_INIT; - const char *workdir, *hintpath = NULL; - - if ((error = git_fs_path_prettify(&full_path, path, NULL)) < 0) { - git_str_dispose(&full_path); - return error; - } - - workdir = git_repository_workdir(repo); - - if (workdir && !git__prefixcmp(full_path.ptr, workdir)) - hintpath = full_path.ptr + strlen(workdir); - - error = git_blob__create_from_paths( - id, NULL, repo, git_str_cstr(&full_path), hintpath, 0, !!hintpath); - - git_str_dispose(&full_path); - return error; -} - -typedef struct { - git_writestream parent; - git_filebuf fbuf; - git_repository *repo; - char *hintpath; -} blob_writestream; - -static int blob_writestream_close(git_writestream *_stream) -{ - blob_writestream *stream = (blob_writestream *) _stream; - - git_filebuf_cleanup(&stream->fbuf); - return 0; -} - -static void blob_writestream_free(git_writestream *_stream) -{ - blob_writestream *stream = (blob_writestream *) _stream; - - git_filebuf_cleanup(&stream->fbuf); - git__free(stream->hintpath); - git__free(stream); -} - -static int blob_writestream_write(git_writestream *_stream, const char *buffer, size_t len) -{ - blob_writestream *stream = (blob_writestream *) _stream; - - return git_filebuf_write(&stream->fbuf, buffer, len); -} - -int git_blob_create_from_stream(git_writestream **out, git_repository *repo, const char *hintpath) -{ - int error; - git_str path = GIT_STR_INIT; - blob_writestream *stream; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - stream = git__calloc(1, sizeof(blob_writestream)); - GIT_ERROR_CHECK_ALLOC(stream); - - if (hintpath) { - stream->hintpath = git__strdup(hintpath); - GIT_ERROR_CHECK_ALLOC(stream->hintpath); - } - - stream->repo = repo; - stream->parent.write = blob_writestream_write; - stream->parent.close = blob_writestream_close; - stream->parent.free = blob_writestream_free; - - if ((error = git_repository__item_path(&path, repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0 - || (error = git_str_joinpath(&path, path.ptr, "streamed")) < 0) - goto cleanup; - - if ((error = git_filebuf_open_withsize(&stream->fbuf, git_str_cstr(&path), GIT_FILEBUF_TEMPORARY, - 0666, 2 * 1024 * 1024)) < 0) - goto cleanup; - - *out = (git_writestream *) stream; - -cleanup: - if (error < 0) - blob_writestream_free((git_writestream *) stream); - - git_str_dispose(&path); - return error; -} - -int git_blob_create_from_stream_commit(git_oid *out, git_writestream *_stream) -{ - int error; - blob_writestream *stream = (blob_writestream *) _stream; - - /* - * We can make this more officient by avoiding writing to - * disk, but for now let's re-use the helper functions we - * have. - */ - if ((error = git_filebuf_flush(&stream->fbuf)) < 0) - goto cleanup; - - error = git_blob__create_from_paths(out, NULL, stream->repo, stream->fbuf.path_lock, - stream->hintpath, 0, !!stream->hintpath); - -cleanup: - blob_writestream_free(_stream); - return error; - -} - -int git_blob_is_binary(const git_blob *blob) -{ - git_str content = GIT_STR_INIT; - git_object_size_t size; - - GIT_ASSERT_ARG(blob); - - size = git_blob_rawsize(blob); - - git_str_attach_notowned(&content, git_blob_rawcontent(blob), - (size_t)min(size, GIT_FILTER_BYTES_TO_CHECK_NUL)); - return git_str_is_binary(&content); -} - -int git_blob_data_is_binary(const char *str, size_t len) -{ - git_str content = GIT_STR_INIT; - - git_str_attach_notowned(&content, str, len); - - return git_str_is_binary(&content); -} - -int git_blob_filter_options_init( - git_blob_filter_options *opts, - unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, - git_blob_filter_options, GIT_BLOB_FILTER_OPTIONS_INIT); - return 0; -} - -int git_blob_filter( - git_buf *out, - git_blob *blob, - const char *path, - git_blob_filter_options *given_opts) -{ - git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; - git_filter_options filter_opts = GIT_FILTER_OPTIONS_INIT; - git_filter_list *fl = NULL; - int error = 0; - - GIT_ASSERT_ARG(blob); - GIT_ASSERT_ARG(path); - GIT_ASSERT_ARG(out); - - GIT_ERROR_CHECK_VERSION( - given_opts, GIT_BLOB_FILTER_OPTIONS_VERSION, "git_blob_filter_options"); - - if (given_opts != NULL) - memcpy(&opts, given_opts, sizeof(git_blob_filter_options)); - - if ((opts.flags & GIT_BLOB_FILTER_CHECK_FOR_BINARY) != 0 && - git_blob_is_binary(blob)) - return 0; - - if ((opts.flags & GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES) != 0) - filter_opts.flags |= GIT_FILTER_NO_SYSTEM_ATTRIBUTES; - - if ((opts.flags & GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD) != 0) - filter_opts.flags |= GIT_FILTER_ATTRIBUTES_FROM_HEAD; - - if ((opts.flags & GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT) != 0) { - filter_opts.flags |= GIT_FILTER_ATTRIBUTES_FROM_COMMIT; - -#ifndef GIT_DEPRECATE_HARD - if (opts.commit_id) - git_oid_cpy(&filter_opts.attr_commit_id, opts.commit_id); - else -#endif - git_oid_cpy(&filter_opts.attr_commit_id, &opts.attr_commit_id); - } - - if (!(error = git_filter_list_load_ext( - &fl, git_blob_owner(blob), blob, path, - GIT_FILTER_TO_WORKTREE, &filter_opts))) { - - error = git_filter_list_apply_to_blob(out, fl, blob); - - git_filter_list_free(fl); - } - - return error; -} - -/* Deprecated functions */ - -#ifndef GIT_DEPRECATE_HARD -int git_blob_create_frombuffer( - git_oid *id, git_repository *repo, const void *buffer, size_t len) -{ - return git_blob_create_from_buffer(id, repo, buffer, len); -} - -int git_blob_create_fromworkdir(git_oid *id, git_repository *repo, const char *relative_path) -{ - return git_blob_create_from_workdir(id, repo, relative_path); -} - -int git_blob_create_fromdisk(git_oid *id, git_repository *repo, const char *path) -{ - return git_blob_create_from_disk(id, repo, path); -} - -int git_blob_create_fromstream( - git_writestream **out, - git_repository *repo, - const char *hintpath) -{ - return git_blob_create_from_stream(out, repo, hintpath); -} - -int git_blob_create_fromstream_commit( - git_oid *out, - git_writestream *stream) -{ - return git_blob_create_from_stream_commit(out, stream); -} - -int git_blob_filtered_content( - git_buf *out, - git_blob *blob, - const char *path, - int check_for_binary_data) -{ - git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; - - if (check_for_binary_data) - opts.flags |= GIT_BLOB_FILTER_CHECK_FOR_BINARY; - else - opts.flags &= ~GIT_BLOB_FILTER_CHECK_FOR_BINARY; - - return git_blob_filter(out, blob, path, &opts); -} -#endif diff --git a/src/blob.h b/src/blob.h deleted file mode 100644 index 9a5dda225..000000000 --- a/src/blob.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_blob_h__ -#define INCLUDE_blob_h__ - -#include "common.h" - -#include "git2/blob.h" -#include "repository.h" -#include "odb.h" -#include "futils.h" - -struct git_blob { - git_object object; - - union { - git_odb_object *odb; - struct { - const char *data; - git_object_size_t size; - } raw; - } data; - unsigned int raw:1; -}; - -#define GIT_ERROR_CHECK_BLOBSIZE(n) \ - do { \ - if (!git__is_sizet(n)) { \ - git_error_set(GIT_ERROR_NOMEMORY, "blob contents too large to fit in memory"); \ - return -1; \ - } \ - } while(0) - -void git_blob__free(void *blob); -int git_blob__parse(void *blob, git_odb_object *obj); -int git_blob__parse_raw(void *blob, const char *data, size_t size); -int git_blob__getbuf(git_str *buffer, git_blob *blob); - -extern int git_blob__create_from_paths( - git_oid *out_oid, - struct stat *out_st, - git_repository *repo, - const char *full_path, - const char *hint_path, - mode_t hint_mode, - bool apply_filters); - -#endif diff --git a/src/branch.c b/src/branch.c deleted file mode 100644 index 2e29af99d..000000000 --- a/src/branch.c +++ /dev/null @@ -1,818 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "branch.h" - -#include "buf.h" -#include "commit.h" -#include "tag.h" -#include "config.h" -#include "refspec.h" -#include "refs.h" -#include "remote.h" -#include "annotated_commit.h" -#include "worktree.h" - -#include "git2/branch.h" - -static int retrieve_branch_reference( - git_reference **branch_reference_out, - git_repository *repo, - const char *branch_name, - bool is_remote) -{ - git_reference *branch = NULL; - int error = 0; - char *prefix; - git_str ref_name = GIT_STR_INIT; - - prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR; - - if ((error = git_str_joinpath(&ref_name, prefix, branch_name)) < 0) - /* OOM */; - else if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0) - git_error_set( - GIT_ERROR_REFERENCE, "cannot locate %s branch '%s'", - is_remote ? "remote-tracking" : "local", branch_name); - - *branch_reference_out = branch; /* will be NULL on error */ - - git_str_dispose(&ref_name); - return error; -} - -static int not_a_local_branch(const char *reference_name) -{ - git_error_set( - GIT_ERROR_INVALID, - "reference '%s' is not a local branch.", reference_name); - return -1; -} - -static int create_branch( - git_reference **ref_out, - git_repository *repository, - const char *branch_name, - const git_commit *commit, - const char *from, - int force) -{ - int is_unmovable_head = 0; - git_reference *branch = NULL; - git_str canonical_branch_name = GIT_STR_INIT, - log_message = GIT_STR_INIT; - int error = -1; - int bare = git_repository_is_bare(repository); - - GIT_ASSERT_ARG(branch_name); - GIT_ASSERT_ARG(commit); - GIT_ASSERT_ARG(ref_out); - GIT_ASSERT_ARG(git_commit_owner(commit) == repository); - - if (!git__strcmp(branch_name, "HEAD")) { - git_error_set(GIT_ERROR_REFERENCE, "'HEAD' is not a valid branch name"); - error = -1; - goto cleanup; - } - - if (force && !bare && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) { - error = git_branch_is_head(branch); - git_reference_free(branch); - branch = NULL; - - if (error < 0) - goto cleanup; - - is_unmovable_head = error; - } - - if (is_unmovable_head && force) { - git_error_set(GIT_ERROR_REFERENCE, "cannot force update branch '%s' as it is " - "the current HEAD of the repository.", branch_name); - error = -1; - goto cleanup; - } - - if (git_str_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) - goto cleanup; - - if (git_str_printf(&log_message, "branch: Created from %s", from) < 0) - goto cleanup; - - error = git_reference_create(&branch, repository, - git_str_cstr(&canonical_branch_name), git_commit_id(commit), force, - git_str_cstr(&log_message)); - - if (!error) - *ref_out = branch; - -cleanup: - git_str_dispose(&canonical_branch_name); - git_str_dispose(&log_message); - return error; -} - -int git_branch_create( - git_reference **ref_out, - git_repository *repository, - const char *branch_name, - const git_commit *commit, - int force) -{ - char commit_id[GIT_OID_HEXSZ + 1]; - - git_oid_tostr(commit_id, GIT_OID_HEXSZ + 1, git_commit_id(commit)); - return create_branch(ref_out, repository, branch_name, commit, commit_id, force); -} - -int git_branch_create_from_annotated( - git_reference **ref_out, - git_repository *repository, - const char *branch_name, - const git_annotated_commit *commit, - int force) -{ - return create_branch(ref_out, - repository, branch_name, commit->commit, commit->description, force); -} - -static int branch_is_checked_out(git_repository *worktree, void *payload) -{ - git_reference *branch = (git_reference *) payload; - git_reference *head = NULL; - int error; - - if (git_repository_is_bare(worktree)) - return 0; - - if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0) { - if (error == GIT_ENOTFOUND) - error = 0; - goto out; - } - - if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC) - goto out; - - error = !git__strcmp(head->target.symbolic, branch->name); - -out: - git_reference_free(head); - return error; -} - -int git_branch_is_checked_out(const git_reference *branch) -{ - GIT_ASSERT_ARG(branch); - - if (!git_reference_is_branch(branch)) - return 0; - return git_repository_foreach_worktree(git_reference_owner(branch), - branch_is_checked_out, (void *)branch) == 1; -} - -int git_branch_delete(git_reference *branch) -{ - int is_head; - git_str config_section = GIT_STR_INIT; - int error = -1; - - GIT_ASSERT_ARG(branch); - - if (!git_reference_is_branch(branch) && !git_reference_is_remote(branch)) { - git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a valid branch.", - git_reference_name(branch)); - return GIT_ENOTFOUND; - } - - if ((is_head = git_branch_is_head(branch)) < 0) - return is_head; - - if (is_head) { - git_error_set(GIT_ERROR_REFERENCE, "cannot delete branch '%s' as it is " - "the current HEAD of the repository.", git_reference_name(branch)); - return -1; - } - - if (git_reference_is_branch(branch) && git_branch_is_checked_out(branch)) { - git_error_set(GIT_ERROR_REFERENCE, "Cannot delete branch '%s' as it is " - "the current HEAD of a linked repository.", git_reference_name(branch)); - return -1; - } - - if (git_str_join(&config_section, '.', "branch", - git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) - goto on_error; - - if (git_config_rename_section( - git_reference_owner(branch), git_str_cstr(&config_section), NULL) < 0) - goto on_error; - - error = git_reference_delete(branch); - -on_error: - git_str_dispose(&config_section); - return error; -} - -typedef struct { - git_reference_iterator *iter; - unsigned int flags; -} branch_iter; - -int git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *_iter) -{ - branch_iter *iter = (branch_iter *) _iter; - git_reference *ref; - int error; - - while ((error = git_reference_next(&ref, iter->iter)) == 0) { - if ((iter->flags & GIT_BRANCH_LOCAL) && - !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR)) { - *out = ref; - *out_type = GIT_BRANCH_LOCAL; - - return 0; - } else if ((iter->flags & GIT_BRANCH_REMOTE) && - !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) { - *out = ref; - *out_type = GIT_BRANCH_REMOTE; - - return 0; - } else { - git_reference_free(ref); - } - } - - return error; -} - -int git_branch_iterator_new( - git_branch_iterator **out, - git_repository *repo, - git_branch_t list_flags) -{ - branch_iter *iter; - - iter = git__calloc(1, sizeof(branch_iter)); - GIT_ERROR_CHECK_ALLOC(iter); - - iter->flags = list_flags; - - if (git_reference_iterator_new(&iter->iter, repo) < 0) { - git__free(iter); - return -1; - } - - *out = (git_branch_iterator *) iter; - - return 0; -} - -void git_branch_iterator_free(git_branch_iterator *_iter) -{ - branch_iter *iter = (branch_iter *) _iter; - - if (iter == NULL) - return; - - git_reference_iterator_free(iter->iter); - git__free(iter); -} - -int git_branch_move( - git_reference **out, - git_reference *branch, - const char *new_branch_name, - int force) -{ - git_str new_reference_name = GIT_STR_INIT, - old_config_section = GIT_STR_INIT, - new_config_section = GIT_STR_INIT, - log_message = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(branch); - GIT_ASSERT_ARG(new_branch_name); - - if (!git_reference_is_branch(branch)) - return not_a_local_branch(git_reference_name(branch)); - - if ((error = git_str_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) - goto done; - - if ((error = git_str_printf(&log_message, "branch: renamed %s to %s", - git_reference_name(branch), git_str_cstr(&new_reference_name))) < 0) - goto done; - - /* first update ref then config so failure won't trash config */ - - error = git_reference_rename( - out, branch, git_str_cstr(&new_reference_name), force, - git_str_cstr(&log_message)); - if (error < 0) - goto done; - - git_str_join(&old_config_section, '.', "branch", - git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)); - git_str_join(&new_config_section, '.', "branch", new_branch_name); - - error = git_config_rename_section( - git_reference_owner(branch), - git_str_cstr(&old_config_section), - git_str_cstr(&new_config_section)); - -done: - git_str_dispose(&new_reference_name); - git_str_dispose(&old_config_section); - git_str_dispose(&new_config_section); - git_str_dispose(&log_message); - - return error; -} - -int git_branch_lookup( - git_reference **ref_out, - git_repository *repo, - const char *branch_name, - git_branch_t branch_type) -{ - int error = -1; - - GIT_ASSERT_ARG(ref_out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(branch_name); - - switch (branch_type) { - case GIT_BRANCH_LOCAL: - case GIT_BRANCH_REMOTE: - error = retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE); - break; - case GIT_BRANCH_ALL: - error = retrieve_branch_reference(ref_out, repo, branch_name, false); - if (error == GIT_ENOTFOUND) - error = retrieve_branch_reference(ref_out, repo, branch_name, true); - break; - default: - GIT_ASSERT(false); - } - return error; -} - -int git_branch_name( - const char **out, - const git_reference *ref) -{ - const char *branch_name; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ref); - - branch_name = ref->name; - - if (git_reference_is_branch(ref)) { - branch_name += strlen(GIT_REFS_HEADS_DIR); - } else if (git_reference_is_remote(ref)) { - branch_name += strlen(GIT_REFS_REMOTES_DIR); - } else { - git_error_set(GIT_ERROR_INVALID, - "reference '%s' is neither a local nor a remote branch.", ref->name); - return -1; - } - *out = branch_name; - return 0; -} - -static int retrieve_upstream_configuration( - git_str *out, - const git_config *config, - const char *canonical_branch_name, - const char *format) -{ - git_str buf = GIT_STR_INIT; - int error; - - if (git_str_printf(&buf, format, - canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0) - return -1; - - error = git_config__get_string_buf(out, config, git_str_cstr(&buf)); - git_str_dispose(&buf); - return error; -} - -int git_branch_upstream_name( - git_buf *out, - git_repository *repo, - const char *refname) -{ - GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_name, repo, refname); -} - -int git_branch__upstream_name( - git_str *out, - git_repository *repo, - const char *refname) -{ - git_str remote_name = GIT_STR_INIT; - git_str merge_name = GIT_STR_INIT; - git_str buf = GIT_STR_INIT; - int error = -1; - git_remote *remote = NULL; - const git_refspec *refspec; - git_config *config; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(refname); - - if (!git_reference__is_branch(refname)) - return not_a_local_branch(refname); - - if ((error = git_repository_config_snapshot(&config, repo)) < 0) - return error; - - if ((error = retrieve_upstream_configuration( - &remote_name, config, refname, "branch.%s.remote")) < 0) - goto cleanup; - - if ((error = retrieve_upstream_configuration( - &merge_name, config, refname, "branch.%s.merge")) < 0) - goto cleanup; - - if (git_str_len(&remote_name) == 0 || git_str_len(&merge_name) == 0) { - git_error_set(GIT_ERROR_REFERENCE, - "branch '%s' does not have an upstream", refname); - error = GIT_ENOTFOUND; - goto cleanup; - } - - if (strcmp(".", git_str_cstr(&remote_name)) != 0) { - if ((error = git_remote_lookup(&remote, repo, git_str_cstr(&remote_name))) < 0) - goto cleanup; - - refspec = git_remote__matching_refspec(remote, git_str_cstr(&merge_name)); - if (!refspec) { - error = GIT_ENOTFOUND; - goto cleanup; - } - - if (git_refspec__transform(&buf, refspec, git_str_cstr(&merge_name)) < 0) - goto cleanup; - } else - if (git_str_set(&buf, git_str_cstr(&merge_name), git_str_len(&merge_name)) < 0) - goto cleanup; - - git_str_swap(out, &buf); - -cleanup: - git_config_free(config); - git_remote_free(remote); - git_str_dispose(&remote_name); - git_str_dispose(&merge_name); - git_str_dispose(&buf); - return error; -} - -static int git_branch_upstream_with_format( - git_str *out, - git_repository *repo, - const char *refname, - const char *format, - const char *format_name) -{ - git_config *cfg; - int error; - - if (!git_reference__is_branch(refname)) - return not_a_local_branch(refname); - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 || - (error = retrieve_upstream_configuration(out, cfg, refname, format)) < 0) - return error; - - if (git_str_len(out) == 0) { - git_error_set(GIT_ERROR_REFERENCE, "branch '%s' does not have an upstream %s", refname, format_name); - error = GIT_ENOTFOUND; - } - - return error; -} - -int git_branch_upstream_remote( - git_buf *out, - git_repository *repo, - const char *refname) -{ - GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_remote, repo, refname); -} - -int git_branch__upstream_remote( - git_str *out, - git_repository *repo, - const char *refname) -{ - return git_branch_upstream_with_format(out, repo, refname, "branch.%s.remote", "remote"); -} - -int git_branch_upstream_merge( - git_buf *out, - git_repository *repo, - const char *refname) -{ - GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_merge, repo, refname); -} - -int git_branch__upstream_merge( - git_str *out, - git_repository *repo, - const char *refname) -{ - return git_branch_upstream_with_format(out, repo, refname, "branch.%s.merge", "merge"); -} - -int git_branch_remote_name( - git_buf *out, - git_repository *repo, - const char *refname) -{ - GIT_BUF_WRAP_PRIVATE(out, git_branch__remote_name, repo, refname); -} - -int git_branch__remote_name( - git_str *out, - git_repository *repo, - const char *refname) -{ - git_strarray remote_list = {0}; - size_t i; - git_remote *remote; - const git_refspec *fetchspec; - int error = 0; - char *remote_name = NULL; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(refname); - - /* Verify that this is a remote branch */ - if (!git_reference__is_remote(refname)) { - git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a remote branch.", - refname); - error = GIT_ERROR; - goto cleanup; - } - - /* Get the remotes */ - if ((error = git_remote_list(&remote_list, repo)) < 0) - goto cleanup; - - /* Find matching remotes */ - for (i = 0; i < remote_list.count; i++) { - if ((error = git_remote_lookup(&remote, repo, remote_list.strings[i])) < 0) - continue; - - fetchspec = git_remote__matching_dst_refspec(remote, refname); - if (fetchspec) { - /* If we have not already set out yet, then set - * it to the matching remote name. Otherwise - * multiple remotes match this reference, and it - * is ambiguous. */ - if (!remote_name) { - remote_name = remote_list.strings[i]; - } else { - git_remote_free(remote); - - git_error_set(GIT_ERROR_REFERENCE, - "reference '%s' is ambiguous", refname); - error = GIT_EAMBIGUOUS; - goto cleanup; - } - } - - git_remote_free(remote); - } - - if (remote_name) { - git_str_clear(out); - error = git_str_puts(out, remote_name); - } else { - git_error_set(GIT_ERROR_REFERENCE, - "could not determine remote for '%s'", refname); - error = GIT_ENOTFOUND; - } - -cleanup: - if (error < 0) - git_str_dispose(out); - - git_strarray_dispose(&remote_list); - return error; -} - -int git_branch_upstream( - git_reference **tracking_out, - const git_reference *branch) -{ - int error; - git_str tracking_name = GIT_STR_INIT; - - if ((error = git_branch__upstream_name(&tracking_name, - git_reference_owner(branch), git_reference_name(branch))) < 0) - return error; - - error = git_reference_lookup( - tracking_out, - git_reference_owner(branch), - git_str_cstr(&tracking_name)); - - git_str_dispose(&tracking_name); - return error; -} - -static int unset_upstream(git_config *config, const char *shortname) -{ - git_str buf = GIT_STR_INIT; - - if (git_str_printf(&buf, "branch.%s.remote", shortname) < 0) - return -1; - - if (git_config_delete_entry(config, git_str_cstr(&buf)) < 0) - goto on_error; - - git_str_clear(&buf); - if (git_str_printf(&buf, "branch.%s.merge", shortname) < 0) - goto on_error; - - if (git_config_delete_entry(config, git_str_cstr(&buf)) < 0) - goto on_error; - - git_str_dispose(&buf); - return 0; - -on_error: - git_str_dispose(&buf); - return -1; -} - -int git_branch_set_upstream(git_reference *branch, const char *branch_name) -{ - git_str key = GIT_STR_INIT, remote_name = GIT_STR_INIT, merge_refspec = GIT_STR_INIT; - git_reference *upstream; - git_repository *repo; - git_remote *remote = NULL; - git_config *config; - const char *refname, *shortname; - int local, error; - const git_refspec *fetchspec; - - refname = git_reference_name(branch); - if (!git_reference__is_branch(refname)) - return not_a_local_branch(refname); - - if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0) - return -1; - - shortname = refname + strlen(GIT_REFS_HEADS_DIR); - - /* We're unsetting, delegate and bail-out */ - if (branch_name == NULL) - return unset_upstream(config, shortname); - - repo = git_reference_owner(branch); - - /* First we need to resolve name to a branch */ - if (git_branch_lookup(&upstream, repo, branch_name, GIT_BRANCH_LOCAL) == 0) - local = 1; - else if (git_branch_lookup(&upstream, repo, branch_name, GIT_BRANCH_REMOTE) == 0) - local = 0; - else { - git_error_set(GIT_ERROR_REFERENCE, - "cannot set upstream for branch '%s'", shortname); - return GIT_ENOTFOUND; - } - - /* - * If it's a local-tracking branch, its remote is "." (as "the local - * repository"), and the branch name is simply the refname. - * Otherwise we need to figure out what the remote-tracking branch's - * name on the remote is and use that. - */ - if (local) - error = git_str_puts(&remote_name, "."); - else - error = git_branch__remote_name(&remote_name, repo, git_reference_name(upstream)); - - if (error < 0) - goto on_error; - - /* Update the upstream branch config with the new name */ - if (git_str_printf(&key, "branch.%s.remote", shortname) < 0) - goto on_error; - - if (git_config_set_string(config, git_str_cstr(&key), git_str_cstr(&remote_name)) < 0) - goto on_error; - - if (local) { - /* A local branch uses the upstream refname directly */ - if (git_str_puts(&merge_refspec, git_reference_name(upstream)) < 0) - goto on_error; - } else { - /* We transform the upstream branch name according to the remote's refspecs */ - if (git_remote_lookup(&remote, repo, git_str_cstr(&remote_name)) < 0) - goto on_error; - - fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream)); - if (!fetchspec || git_refspec__rtransform(&merge_refspec, fetchspec, git_reference_name(upstream)) < 0) - goto on_error; - - git_remote_free(remote); - remote = NULL; - } - - /* Update the merge branch config with the refspec */ - git_str_clear(&key); - if (git_str_printf(&key, "branch.%s.merge", shortname) < 0) - goto on_error; - - if (git_config_set_string(config, git_str_cstr(&key), git_str_cstr(&merge_refspec)) < 0) - goto on_error; - - git_reference_free(upstream); - git_str_dispose(&key); - git_str_dispose(&remote_name); - git_str_dispose(&merge_refspec); - - return 0; - -on_error: - git_reference_free(upstream); - git_str_dispose(&key); - git_str_dispose(&remote_name); - git_str_dispose(&merge_refspec); - git_remote_free(remote); - - return -1; -} - -int git_branch_is_head( - const git_reference *branch) -{ - git_reference *head; - bool is_same = false; - int error; - - GIT_ASSERT_ARG(branch); - - if (!git_reference_is_branch(branch)) - return false; - - error = git_repository_head(&head, git_reference_owner(branch)); - - if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND) - return false; - - if (error < 0) - return -1; - - is_same = strcmp( - git_reference_name(branch), - git_reference_name(head)) == 0; - - git_reference_free(head); - - return is_same; -} - -int git_branch_name_is_valid(int *valid, const char *name) -{ - git_str ref_name = GIT_STR_INIT; - int error = 0; - - GIT_ASSERT(valid); - - *valid = 0; - - /* - * Discourage branch name starting with dash, - * https://github.com/git/git/commit/6348624010888b - * and discourage HEAD as branch name, - * https://github.com/git/git/commit/a625b092cc5994 - */ - if (!name || name[0] == '-' || !git__strcmp(name, "HEAD")) - goto done; - - if ((error = git_str_puts(&ref_name, GIT_REFS_HEADS_DIR)) < 0 || - (error = git_str_puts(&ref_name, name)) < 0) - goto done; - - error = git_reference_name_is_valid(valid, ref_name.ptr); - -done: - git_str_dispose(&ref_name); - return error; -} diff --git a/src/branch.h b/src/branch.h deleted file mode 100644 index b4db42a01..000000000 --- a/src/branch.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_branch_h__ -#define INCLUDE_branch_h__ - -#include "common.h" - -#include "str.h" - -int git_branch__remote_name( - git_str *out, - git_repository *repo, - const char *refname); -int git_branch__upstream_remote( - git_str *out, - git_repository *repo, - const char *refname); -int git_branch__upstream_merge( - git_str *out, - git_repository *repo, - const char *refname); -int git_branch__upstream_name( - git_str *tracking_name, - git_repository *repo, - const char *canonical_branch_name); - -#endif diff --git a/src/buf.c b/src/buf.c deleted file mode 100644 index 652f5dd52..000000000 --- a/src/buf.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "buf.h" -#include "common.h" - -int git_buf_sanitize(git_buf *buf) -{ - GIT_ASSERT_ARG(buf); - - if (buf->reserved > 0) - buf->ptr[0] = '\0'; - else - buf->ptr = git_str__initstr; - - buf->size = 0; - return 0; -} - -int git_buf_tostr(git_str *out, git_buf *buf) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(buf); - - if (git_buf_sanitize(buf) < 0) - return -1; - - out->ptr = buf->ptr; - out->asize = buf->reserved; - out->size = buf->size; - - buf->ptr = git_str__initstr; - buf->reserved = 0; - buf->size = 0; - - return 0; -} - -int git_buf_fromstr(git_buf *out, git_str *str) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(str); - - out->ptr = str->ptr; - out->reserved = str->asize; - out->size = str->size; - - str->ptr = git_str__initstr; - str->asize = 0; - str->size = 0; - - return 0; -} - -void git_buf_dispose(git_buf *buf) -{ - if (!buf) - return; - - if (buf->ptr != git_str__initstr) - git__free(buf->ptr); - - buf->ptr = git_str__initstr; - buf->reserved = 0; - buf->size = 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_buf_grow(git_buf *buffer, size_t target_size) -{ - char *newptr; - - if (buffer->reserved >= target_size) - return 0; - - if (buffer->ptr == git_str__initstr) - newptr = git__malloc(target_size); - else - newptr = git__realloc(buffer->ptr, target_size); - - if (!newptr) - return -1; - - buffer->ptr = newptr; - buffer->reserved = target_size; - return 0; -} - -int git_buf_set(git_buf *buffer, const void *data, size_t datalen) -{ - size_t alloclen; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, datalen, 1); - - if (git_buf_grow(buffer, alloclen) < 0) - return -1; - - memmove(buffer->ptr, data, datalen); - buffer->size = datalen; - buffer->ptr[buffer->size] = '\0'; - - return 0; -} - -int git_buf_is_binary(const git_buf *buf) -{ - git_str str = GIT_STR_INIT_CONST(buf->ptr, buf->size); - return git_str_is_binary(&str); -} - -int git_buf_contains_nul(const git_buf *buf) -{ - git_str str = GIT_STR_INIT_CONST(buf->ptr, buf->size); - return git_str_contains_nul(&str); -} - -void git_buf_free(git_buf *buffer) -{ - git_buf_dispose(buffer); -} - -#endif diff --git a/src/buf.h b/src/buf.h deleted file mode 100644 index 4bc7f2709..000000000 --- a/src/buf.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_buf_h__ -#define INCLUDE_buf_h__ - -#include "git2/buffer.h" -#include "common.h" - -/* - * Adapts a private API that takes a `git_str` into a public API that - * takes a `git_buf`. - */ - -#define GIT_BUF_WRAP_PRIVATE(buf, fn, ...) \ - { \ - git_str str = GIT_STR_INIT; \ - int error; \ - if ((error = git_buf_tostr(&str, buf)) == 0 && \ - (error = fn(&str, __VA_ARGS__)) == 0) \ - error = git_buf_fromstr(buf, &str); \ - git_str_dispose(&str); \ - return error; \ -} - -/** - * "Sanitizes" a buffer from user input. This simply ensures that the - * `git_buf` has nice defaults if the user didn't set the members to - * anything, so that if we return early we don't leave it populated - * with nonsense. - */ -extern int git_buf_sanitize(git_buf *from_user); - -/** - * Populate a `git_str` from a `git_buf` for passing to libgit2 internal - * functions. Sanitizes the given `git_buf` before proceeding. The - * `git_buf` will no longer point to this memory. - */ -extern int git_buf_tostr(git_str *out, git_buf *buf); - -/** - * Populate a `git_buf` from a `git_str` for returning to a user. - * The `git_str` will no longer point to this memory. - */ -extern int git_buf_fromstr(git_buf *out, git_str *str); - -#endif diff --git a/src/cache.c b/src/cache.c deleted file mode 100644 index 2f68e357c..000000000 --- a/src/cache.c +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "cache.h" - -#include "repository.h" -#include "commit.h" -#include "thread.h" -#include "util.h" -#include "odb.h" -#include "object.h" -#include "git2/oid.h" - -bool git_cache__enabled = true; -ssize_t git_cache__max_storage = (256 * 1024 * 1024); -git_atomic_ssize git_cache__current_storage = {0}; - -static size_t git_cache__max_object_size[8] = { - 0, /* GIT_OBJECT__EXT1 */ - 4096, /* GIT_OBJECT_COMMIT */ - 4096, /* GIT_OBJECT_TREE */ - 0, /* GIT_OBJECT_BLOB */ - 4096, /* GIT_OBJECT_TAG */ - 0, /* GIT_OBJECT__EXT2 */ - 0, /* GIT_OBJECT_OFS_DELTA */ - 0 /* GIT_OBJECT_REF_DELTA */ -}; - -int git_cache_set_max_object_size(git_object_t type, size_t size) -{ - if (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) { - git_error_set(GIT_ERROR_INVALID, "type out of range"); - return -1; - } - - git_cache__max_object_size[type] = size; - return 0; -} - -int git_cache_init(git_cache *cache) -{ - memset(cache, 0, sizeof(*cache)); - - if ((git_oidmap_new(&cache->map)) < 0) - return -1; - - if (git_rwlock_init(&cache->lock)) { - git_error_set(GIT_ERROR_OS, "failed to initialize cache rwlock"); - return -1; - } - - return 0; -} - -/* called with lock */ -static void clear_cache(git_cache *cache) -{ - git_cached_obj *evict = NULL; - - if (git_cache_size(cache) == 0) - return; - - git_oidmap_foreach_value(cache->map, evict, { - git_cached_obj_decref(evict); - }); - - git_oidmap_clear(cache->map); - git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory); - cache->used_memory = 0; -} - -void git_cache_clear(git_cache *cache) -{ - if (git_rwlock_wrlock(&cache->lock) < 0) - return; - - clear_cache(cache); - - git_rwlock_wrunlock(&cache->lock); -} - -void git_cache_dispose(git_cache *cache) -{ - git_cache_clear(cache); - git_oidmap_free(cache->map); - git_rwlock_free(&cache->lock); - git__memzero(cache, sizeof(*cache)); -} - -/* Called with lock */ -static void cache_evict_entries(git_cache *cache) -{ - size_t evict_count = git_cache_size(cache) / 2048, i; - ssize_t evicted_memory = 0; - - if (evict_count < 8) - evict_count = 8; - - /* do not infinite loop if there's not enough entries to evict */ - if (evict_count > git_cache_size(cache)) { - clear_cache(cache); - return; - } - - i = 0; - while (evict_count > 0) { - git_cached_obj *evict; - const git_oid *key; - - if (git_oidmap_iterate((void **) &evict, cache->map, &i, &key) == GIT_ITEROVER) - break; - - evict_count--; - evicted_memory += evict->size; - git_oidmap_delete(cache->map, key); - git_cached_obj_decref(evict); - } - - cache->used_memory -= evicted_memory; - git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory); -} - -static bool cache_should_store(git_object_t object_type, size_t object_size) -{ - size_t max_size = git_cache__max_object_size[object_type]; - return git_cache__enabled && object_size < max_size; -} - -static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags) -{ - git_cached_obj *entry; - - if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0) - return NULL; - - if ((entry = git_oidmap_get(cache->map, oid)) != NULL) { - if (flags && entry->flags != flags) { - entry = NULL; - } else { - git_cached_obj_incref(entry); - } - } - - git_rwlock_rdunlock(&cache->lock); - - return entry; -} - -static void *cache_store(git_cache *cache, git_cached_obj *entry) -{ - git_cached_obj *stored_entry; - - git_cached_obj_incref(entry); - - if (!git_cache__enabled && cache->used_memory > 0) { - git_cache_clear(cache); - return entry; - } - - if (!cache_should_store(entry->type, entry->size)) - return entry; - - if (git_rwlock_wrlock(&cache->lock) < 0) - return entry; - - /* soften the load on the cache */ - if (git_atomic_ssize_get(&git_cache__current_storage) > git_cache__max_storage) - cache_evict_entries(cache); - - /* not found */ - if ((stored_entry = git_oidmap_get(cache->map, &entry->oid)) == NULL) { - if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) { - git_cached_obj_incref(entry); - cache->used_memory += entry->size; - git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size); - } - } - /* found */ - else { - if (stored_entry->flags == entry->flags) { - git_cached_obj_decref(entry); - git_cached_obj_incref(stored_entry); - entry = stored_entry; - } else if (stored_entry->flags == GIT_CACHE_STORE_RAW && - entry->flags == GIT_CACHE_STORE_PARSED) { - if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) { - git_cached_obj_decref(stored_entry); - git_cached_obj_incref(entry); - } else { - git_cached_obj_decref(entry); - git_cached_obj_incref(stored_entry); - entry = stored_entry; - } - } else { - /* NO OP */ - } - } - - git_rwlock_wrunlock(&cache->lock); - return entry; -} - -void *git_cache_store_raw(git_cache *cache, git_odb_object *entry) -{ - entry->cached.flags = GIT_CACHE_STORE_RAW; - return cache_store(cache, (git_cached_obj *)entry); -} - -void *git_cache_store_parsed(git_cache *cache, git_object *entry) -{ - entry->cached.flags = GIT_CACHE_STORE_PARSED; - return cache_store(cache, (git_cached_obj *)entry); -} - -git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid) -{ - return cache_get(cache, oid, GIT_CACHE_STORE_RAW); -} - -git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid) -{ - return cache_get(cache, oid, GIT_CACHE_STORE_PARSED); -} - -void *git_cache_get_any(git_cache *cache, const git_oid *oid) -{ - return cache_get(cache, oid, GIT_CACHE_STORE_ANY); -} - -void git_cached_obj_decref(void *_obj) -{ - git_cached_obj *obj = _obj; - - if (git_atomic32_dec(&obj->refcount) == 0) { - switch (obj->flags) { - case GIT_CACHE_STORE_RAW: - git_odb_object__free(_obj); - break; - - case GIT_CACHE_STORE_PARSED: - git_object__free(_obj); - break; - - default: - git__free(_obj); - break; - } - } -} diff --git a/src/cache.h b/src/cache.h deleted file mode 100644 index 42c4fa80d..000000000 --- a/src/cache.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_cache_h__ -#define INCLUDE_cache_h__ - -#include "common.h" - -#include "git2/common.h" -#include "git2/oid.h" -#include "git2/odb.h" - -#include "thread.h" -#include "oidmap.h" - -enum { - GIT_CACHE_STORE_ANY = 0, - GIT_CACHE_STORE_RAW = 1, - GIT_CACHE_STORE_PARSED = 2 -}; - -typedef struct { - git_oid oid; - int16_t type; /* git_object_t value */ - uint16_t flags; /* GIT_CACHE_STORE value */ - size_t size; - git_atomic32 refcount; -} git_cached_obj; - -typedef struct { - git_oidmap *map; - git_rwlock lock; - ssize_t used_memory; -} git_cache; - -extern bool git_cache__enabled; -extern ssize_t git_cache__max_storage; -extern git_atomic_ssize git_cache__current_storage; - -int git_cache_set_max_object_size(git_object_t type, size_t size); - -int git_cache_init(git_cache *cache); -void git_cache_dispose(git_cache *cache); -void git_cache_clear(git_cache *cache); - -void *git_cache_store_raw(git_cache *cache, git_odb_object *entry); -void *git_cache_store_parsed(git_cache *cache, git_object *entry); - -git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid); -git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid); -void *git_cache_get_any(git_cache *cache, const git_oid *oid); - -GIT_INLINE(size_t) git_cache_size(git_cache *cache) -{ - return (size_t)git_oidmap_size(cache->map); -} - -GIT_INLINE(void) git_cached_obj_incref(void *_obj) -{ - git_cached_obj *obj = _obj; - git_atomic32_inc(&obj->refcount); -} - -void git_cached_obj_decref(void *_obj); - -#endif diff --git a/src/cc-compat.h b/src/cc-compat.h deleted file mode 100644 index a0971e86c..000000000 --- a/src/cc-compat.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_cc_compat_h__ -#define INCLUDE_cc_compat_h__ - -#include - -/* - * See if our compiler is known to support flexible array members. - */ -#ifndef GIT_FLEX_ARRAY -# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -# define GIT_FLEX_ARRAY /* empty */ -# elif defined(__GNUC__) -# if (__GNUC__ >= 3) -# define GIT_FLEX_ARRAY /* empty */ -# else -# define GIT_FLEX_ARRAY 0 /* older GNU extension */ -# endif -# endif - -/* Default to safer but a bit wasteful traditional style */ -# ifndef GIT_FLEX_ARRAY -# define GIT_FLEX_ARRAY 1 -# endif -#endif - -#if defined(__GNUC__) -# define GIT_ALIGN(x,size) x __attribute__ ((aligned(size))) -#elif defined(_MSC_VER) -# define GIT_ALIGN(x,size) __declspec(align(size)) x -#else -# define GIT_ALIGN(x,size) x -#endif - -#if defined(__GNUC__) -# define GIT_UNUSED(x) \ - do { \ - __typeof__(x) _unused __attribute__((unused)); \ - _unused = (x); \ - } while (0) -#else -# define GIT_UNUSED(x) ((void)(x)) -#endif - -/* Define the printf format specifier to use for size_t output */ -#if defined(_MSC_VER) || defined(__MINGW32__) - -/* Visual Studio 2012 and prior lack PRId64 entirely */ -# ifndef PRId64 -# define PRId64 "I64d" -# endif - -/* The first block is needed to avoid warnings on MingW amd64 */ -# if (SIZE_MAX == ULLONG_MAX) -# define PRIuZ "I64u" -# define PRIxZ "I64x" -# define PRIXZ "I64X" -# define PRIdZ "I64d" -# else -# define PRIuZ "Iu" -# define PRIxZ "Ix" -# define PRIXZ "IX" -# define PRIdZ "Id" -# endif - -#else -# define PRIuZ "zu" -# define PRIxZ "zx" -# define PRIXZ "zX" -# define PRIdZ "zd" -#endif - -/* Microsoft Visual C/C++ */ -#if defined(_MSC_VER) -/* disable "deprecated function" warnings */ -# pragma warning ( disable : 4996 ) -/* disable "conditional expression is constant" level 4 warnings */ -# pragma warning ( disable : 4127 ) -#endif - -#if defined (_MSC_VER) - typedef unsigned char bool; -# ifndef true -# define true 1 -# endif -# ifndef false -# define false 0 -# endif -#else -# include -#endif - -#ifndef va_copy -# ifdef __va_copy -# define va_copy(dst, src) __va_copy(dst, src) -# else -# define va_copy(dst, src) ((dst) = (src)) -# endif -#endif - -#endif diff --git a/src/checkout.c b/src/checkout.c deleted file mode 100644 index 6a4643196..000000000 --- a/src/checkout.c +++ /dev/null @@ -1,2813 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "checkout.h" - -#include "git2/repository.h" -#include "git2/refs.h" -#include "git2/tree.h" -#include "git2/blob.h" -#include "git2/config.h" -#include "git2/diff.h" -#include "git2/submodule.h" -#include "git2/sys/index.h" -#include "git2/sys/filter.h" -#include "git2/merge.h" - -#include "refs.h" -#include "repository.h" -#include "index.h" -#include "filter.h" -#include "blob.h" -#include "diff.h" -#include "diff_generate.h" -#include "pathspec.h" -#include "diff_xdiff.h" -#include "fs_path.h" -#include "attr.h" -#include "pool.h" -#include "strmap.h" -#include "path.h" - -/* See docs/checkout-internals.md for more information */ - -enum { - CHECKOUT_ACTION__NONE = 0, - CHECKOUT_ACTION__REMOVE = 1, - CHECKOUT_ACTION__UPDATE_BLOB = 2, - CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, - CHECKOUT_ACTION__CONFLICT = 8, - CHECKOUT_ACTION__REMOVE_CONFLICT = 16, - CHECKOUT_ACTION__UPDATE_CONFLICT = 32, - CHECKOUT_ACTION__MAX = 32, - CHECKOUT_ACTION__REMOVE_AND_UPDATE = - (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE) -}; - -typedef struct { - git_repository *repo; - git_iterator *target; - git_diff *diff; - git_checkout_options opts; - bool opts_free_baseline; - char *pfx; - git_index *index; - git_pool pool; - git_vector removes; - git_vector remove_conflicts; - git_vector update_conflicts; - git_vector *update_reuc; - git_vector *update_names; - git_str target_path; - size_t target_len; - git_str tmp; - unsigned int strategy; - int can_symlink; - int respect_filemode; - bool reload_submodules; - size_t total_steps; - size_t completed_steps; - git_checkout_perfdata perfdata; - git_strmap *mkdir_map; - git_attr_session attr_session; -} checkout_data; - -typedef struct { - const git_index_entry *ancestor; - const git_index_entry *ours; - const git_index_entry *theirs; - - unsigned int name_collision:1, - directoryfile:1, - one_to_two:1, - binary:1, - submodule:1; -} checkout_conflictdata; - -static int checkout_notify( - checkout_data *data, - git_checkout_notify_t why, - const git_diff_delta *delta, - const git_index_entry *wditem) -{ - git_diff_file wdfile; - const git_diff_file *baseline = NULL, *target = NULL, *workdir = NULL; - const char *path = NULL; - - if (!data->opts.notify_cb || - (why & data->opts.notify_flags) == 0) - return 0; - - if (wditem) { - memset(&wdfile, 0, sizeof(wdfile)); - - git_oid_cpy(&wdfile.id, &wditem->id); - wdfile.path = wditem->path; - wdfile.size = wditem->file_size; - wdfile.flags = GIT_DIFF_FLAG_VALID_ID; - wdfile.mode = wditem->mode; - - workdir = &wdfile; - - path = wditem->path; - } - - if (delta) { - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: - case GIT_DELTA_MODIFIED: - case GIT_DELTA_TYPECHANGE: - default: - baseline = &delta->old_file; - target = &delta->new_file; - break; - case GIT_DELTA_ADDED: - case GIT_DELTA_IGNORED: - case GIT_DELTA_UNTRACKED: - case GIT_DELTA_UNREADABLE: - target = &delta->new_file; - break; - case GIT_DELTA_DELETED: - baseline = &delta->old_file; - break; - } - - path = delta->old_file.path; - } - - { - int error = data->opts.notify_cb( - why, path, baseline, target, workdir, data->opts.notify_payload); - - return git_error_set_after_callback_function( - error, "git_checkout notification"); - } -} - -GIT_INLINE(bool) is_workdir_base_or_new( - const git_oid *workdir_id, - const git_diff_file *baseitem, - const git_diff_file *newitem) -{ - return (git_oid__cmp(&baseitem->id, workdir_id) == 0 || - git_oid__cmp(&newitem->id, workdir_id) == 0); -} - -GIT_INLINE(bool) is_filemode_changed(git_filemode_t a, git_filemode_t b, int respect_filemode) -{ - /* If core.filemode = false, ignore links in the repository and executable bit changes */ - if (!respect_filemode) { - if (a == S_IFLNK) - a = GIT_FILEMODE_BLOB; - if (b == S_IFLNK) - b = GIT_FILEMODE_BLOB; - - a &= ~0111; - b &= ~0111; - } - - return (a != b); -} - -static bool checkout_is_workdir_modified( - checkout_data *data, - const git_diff_file *baseitem, - const git_diff_file *newitem, - const git_index_entry *wditem) -{ - git_oid oid; - const git_index_entry *ie; - - /* handle "modified" submodule */ - if (wditem->mode == GIT_FILEMODE_COMMIT) { - git_submodule *sm; - unsigned int sm_status = 0; - const git_oid *sm_oid = NULL; - bool rval = false; - - if (git_submodule_lookup(&sm, data->repo, wditem->path) < 0) { - git_error_clear(); - return true; - } - - if (git_submodule_status(&sm_status, data->repo, wditem->path, GIT_SUBMODULE_IGNORE_UNSPECIFIED) < 0 || - GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) - rval = true; - else if ((sm_oid = git_submodule_wd_id(sm)) == NULL) - rval = false; - else - rval = (git_oid__cmp(&baseitem->id, sm_oid) != 0); - - git_submodule_free(sm); - return rval; - } - - /* - * Look at the cache to decide if the workdir is modified: if the - * cache contents match the workdir contents, then we do not need - * to examine the working directory directly, instead we can - * examine the cache to see if _it_ has been modified. This allows - * us to avoid touching the disk. - */ - ie = git_index_get_bypath(data->index, wditem->path, 0); - - if (ie != NULL && - !git_index_entry_newer_than_index(ie, data->index) && - git_index_time_eq(&wditem->mtime, &ie->mtime) && - wditem->file_size == ie->file_size && - !is_filemode_changed(wditem->mode, ie->mode, data->respect_filemode)) { - - /* The workdir is modified iff the index entry is modified */ - return !is_workdir_base_or_new(&ie->id, baseitem, newitem) || - is_filemode_changed(baseitem->mode, ie->mode, data->respect_filemode); - } - - /* depending on where base is coming from, we may or may not know - * the actual size of the data, so we can't rely on this shortcut. - */ - if (baseitem->size && wditem->file_size != baseitem->size) - return true; - - /* if the workdir item is a directory, it cannot be a modified file */ - if (S_ISDIR(wditem->mode)) - return false; - - if (is_filemode_changed(baseitem->mode, wditem->mode, data->respect_filemode)) - return true; - - if (git_diff__oid_for_entry(&oid, data->diff, wditem, wditem->mode, NULL) < 0) - return false; - - /* Allow the checkout if the workdir is not modified *or* if the checkout - * target's contents are already in the working directory. - */ - return !is_workdir_base_or_new(&oid, baseitem, newitem); -} - -#define CHECKOUT_ACTION_IF(FLAG,YES,NO) \ - ((data->strategy & GIT_CHECKOUT_##FLAG) ? CHECKOUT_ACTION__##YES : CHECKOUT_ACTION__##NO) - -static int checkout_action_common( - int *action, - checkout_data *data, - const git_diff_delta *delta, - const git_index_entry *wd) -{ - git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; - - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) - *action = (*action & ~CHECKOUT_ACTION__REMOVE); - - if ((*action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) { - if (S_ISGITLINK(delta->new_file.mode)) - *action = (*action & ~CHECKOUT_ACTION__UPDATE_BLOB) | - CHECKOUT_ACTION__UPDATE_SUBMODULE; - - /* to "update" a symlink, we must remove the old one first */ - if (delta->new_file.mode == GIT_FILEMODE_LINK && wd != NULL) - *action |= CHECKOUT_ACTION__REMOVE; - - /* if the file is on disk and doesn't match our mode, force update */ - if (wd && - GIT_PERMS_IS_EXEC(wd->mode) != GIT_PERMS_IS_EXEC(delta->new_file.mode)) - *action |= CHECKOUT_ACTION__REMOVE; - - notify = GIT_CHECKOUT_NOTIFY_UPDATED; - } - - if ((*action & CHECKOUT_ACTION__CONFLICT) != 0) - notify = GIT_CHECKOUT_NOTIFY_CONFLICT; - - return checkout_notify(data, notify, delta, wd); -} - -static int checkout_action_no_wd( - int *action, - checkout_data *data, - const git_diff_delta *delta) -{ - int error = 0; - - *action = CHECKOUT_ACTION__NONE; - - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: /* case 12 */ - error = checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL); - if (error) - return error; - *action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, NONE); - break; - case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */ - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); - break; - case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */ - *action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, CONFLICT); - break; - case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/ - if (delta->new_file.mode == GIT_FILEMODE_TREE) - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); - break; - case GIT_DELTA_DELETED: /* case 8 or 25 */ - *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE); - break; - default: /* impossible */ - break; - } - - return checkout_action_common(action, data, delta, NULL); -} - -static int checkout_target_fullpath( - git_str **out, checkout_data *data, const char *path) -{ - git_str_truncate(&data->target_path, data->target_len); - - if (path && git_str_puts(&data->target_path, path) < 0) - return -1; - - if (git_path_validate_str_length(data->repo, &data->target_path) < 0) - return -1; - - *out = &data->target_path; - - return 0; -} - -static bool wd_item_is_removable( - checkout_data *data, const git_index_entry *wd) -{ - git_str *full; - - if (wd->mode != GIT_FILEMODE_TREE) - return true; - - if (checkout_target_fullpath(&full, data, wd->path) < 0) - return false; - - return !full || !git_fs_path_contains(full, DOT_GIT); -} - -static int checkout_queue_remove(checkout_data *data, const char *path) -{ - char *copy = git_pool_strdup(&data->pool, path); - GIT_ERROR_CHECK_ALLOC(copy); - return git_vector_insert(&data->removes, copy); -} - -/* note that this advances the iterator over the wd item */ -static int checkout_action_wd_only( - checkout_data *data, - git_iterator *workdir, - const git_index_entry **wditem, - git_vector *pathspec) -{ - int error = 0; - bool remove = false; - git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; - const git_index_entry *wd = *wditem; - - if (!git_pathspec__match( - pathspec, wd->path, - (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, - git_iterator_ignore_case(workdir), NULL, NULL)) { - - if (wd->mode == GIT_FILEMODE_TREE) - return git_iterator_advance_into(wditem, workdir); - else - return git_iterator_advance(wditem, workdir); - } - - /* check if item is tracked in the index but not in the checkout diff */ - if (data->index != NULL) { - size_t pos; - - error = git_index__find_pos( - &pos, data->index, wd->path, 0, GIT_INDEX_STAGE_ANY); - - if (wd->mode != GIT_FILEMODE_TREE) { - if (!error) { /* found by git_index__find_pos call */ - notify = GIT_CHECKOUT_NOTIFY_DIRTY; - remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0); - } else if (error != GIT_ENOTFOUND) - return error; - else - error = 0; /* git_index__find_pos does not set error msg */ - } else { - /* for tree entries, we have to see if there are any index - * entries that are contained inside that tree - */ - const git_index_entry *e = git_index_get_byindex(data->index, pos); - - if (e != NULL && data->diff->pfxcomp(e->path, wd->path) == 0) - return git_iterator_advance_into(wditem, workdir); - } - } - - if (notify != GIT_CHECKOUT_NOTIFY_NONE) { - /* if we found something in the index, notify and advance */ - if ((error = checkout_notify(data, notify, NULL, wd)) != 0) - return error; - - if (remove && wd_item_is_removable(data, wd)) - error = checkout_queue_remove(data, wd->path); - - if (!error) - error = git_iterator_advance(wditem, workdir); - } else { - /* untracked or ignored - can't know which until we advance through */ - bool over = false, removable = wd_item_is_removable(data, wd); - git_iterator_status_t untracked_state; - - /* copy the entry for issuing notification callback later */ - git_index_entry saved_wd = *wd; - git_str_sets(&data->tmp, wd->path); - saved_wd.path = data->tmp.ptr; - - error = git_iterator_advance_over( - wditem, &untracked_state, workdir); - if (error == GIT_ITEROVER) - over = true; - else if (error < 0) - return error; - - if (untracked_state == GIT_ITERATOR_STATUS_IGNORED) { - notify = GIT_CHECKOUT_NOTIFY_IGNORED; - remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0); - } else { - notify = GIT_CHECKOUT_NOTIFY_UNTRACKED; - remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0); - } - - if ((error = checkout_notify(data, notify, NULL, &saved_wd)) != 0) - return error; - - if (remove && removable) - error = checkout_queue_remove(data, saved_wd.path); - - if (!error && over) /* restore ITEROVER if needed */ - error = GIT_ITEROVER; - } - - return error; -} - -static bool submodule_is_config_only( - checkout_data *data, - const char *path) -{ - git_submodule *sm = NULL; - unsigned int sm_loc = 0; - bool rval = false; - - if (git_submodule_lookup(&sm, data->repo, path) < 0) - return true; - - if (git_submodule_location(&sm_loc, sm) < 0 || - sm_loc == GIT_SUBMODULE_STATUS_IN_CONFIG) - rval = true; - - git_submodule_free(sm); - - return rval; -} - -static bool checkout_is_empty_dir(checkout_data *data, const char *path) -{ - git_str *fullpath; - - if (checkout_target_fullpath(&fullpath, data, path) < 0) - return false; - - return git_fs_path_is_empty_dir(fullpath->ptr); -} - -static int checkout_action_with_wd( - int *action, - checkout_data *data, - const git_diff_delta *delta, - git_iterator *workdir, - const git_index_entry *wd) -{ - *action = CHECKOUT_ACTION__NONE; - - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */ - if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) { - GIT_ERROR_CHECK_ERROR( - checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) ); - *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE); - } - break; - case GIT_DELTA_ADDED: /* case 3, 4 or 6 */ - if (git_iterator_current_is_ignored(workdir)) - *action = CHECKOUT_ACTION_IF(DONT_OVERWRITE_IGNORED, CONFLICT, UPDATE_BLOB); - else - *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT); - break; - case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */ - if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); - else - *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE); - break; - case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */ - if (wd->mode != GIT_FILEMODE_COMMIT && - checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) - *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT); - else - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); - break; - case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */ - if (delta->old_file.mode == GIT_FILEMODE_TREE) { - if (wd->mode == GIT_FILEMODE_TREE) - /* either deleting items in old tree will delete the wd dir, - * or we'll get a conflict when we attempt blob update... - */ - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); - else if (wd->mode == GIT_FILEMODE_COMMIT) { - /* workdir is possibly a "phantom" submodule - treat as a - * tree if the only submodule info came from the config - */ - if (submodule_is_config_only(data, wd->path)) - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); - else - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); - } else - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); - } - else if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); - else - *action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE); - - /* don't update if the typechange is to a tree */ - if (delta->new_file.mode == GIT_FILEMODE_TREE) - *action = (*action & ~CHECKOUT_ACTION__UPDATE_BLOB); - break; - default: /* impossible */ - break; - } - - return checkout_action_common(action, data, delta, wd); -} - -static int checkout_action_with_wd_blocker( - int *action, - checkout_data *data, - const git_diff_delta *delta, - const git_index_entry *wd) -{ - *action = CHECKOUT_ACTION__NONE; - - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: - /* should show delta as dirty / deleted */ - GIT_ERROR_CHECK_ERROR( - checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) ); - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE); - break; - case GIT_DELTA_ADDED: - case GIT_DELTA_MODIFIED: - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); - break; - case GIT_DELTA_DELETED: - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); - break; - case GIT_DELTA_TYPECHANGE: - /* not 100% certain about this... */ - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); - break; - default: /* impossible */ - break; - } - - return checkout_action_common(action, data, delta, wd); -} - -static int checkout_action_with_wd_dir( - int *action, - checkout_data *data, - const git_diff_delta *delta, - git_iterator *workdir, - const git_index_entry *wd) -{ - *action = CHECKOUT_ACTION__NONE; - - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */ - GIT_ERROR_CHECK_ERROR( - checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL)); - GIT_ERROR_CHECK_ERROR( - checkout_notify(data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd)); - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE); - break; - case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */ - case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */ - if (delta->old_file.mode == GIT_FILEMODE_COMMIT) - /* expected submodule (and maybe found one) */; - else if (delta->new_file.mode != GIT_FILEMODE_TREE) - *action = git_iterator_current_is_ignored(workdir) ? - CHECKOUT_ACTION_IF(DONT_OVERWRITE_IGNORED, CONFLICT, REMOVE_AND_UPDATE) : - CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); - break; - case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */ - if (delta->old_file.mode != GIT_FILEMODE_TREE) - GIT_ERROR_CHECK_ERROR( - checkout_notify(data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd)); - break; - case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */ - if (delta->old_file.mode == GIT_FILEMODE_TREE) { - /* For typechange from dir, remove dir and add blob, but it is - * not safe to remove dir if it contains modified files. - * However, safely removing child files will remove the parent - * directory if is it left empty, so we can defer removing the - * dir and it will succeed if no children are left. - */ - *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); - } - else if (delta->new_file.mode != GIT_FILEMODE_TREE) - /* For typechange to dir, dir is already created so no action */ - *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); - break; - default: /* impossible */ - break; - } - - return checkout_action_common(action, data, delta, wd); -} - -static int checkout_action_with_wd_dir_empty( - int *action, - checkout_data *data, - const git_diff_delta *delta) -{ - int error = checkout_action_no_wd(action, data, delta); - - /* We can always safely remove an empty directory. */ - if (error == 0 && *action != CHECKOUT_ACTION__NONE) - *action |= CHECKOUT_ACTION__REMOVE; - - return error; -} - -static int checkout_action( - int *action, - checkout_data *data, - git_diff_delta *delta, - git_iterator *workdir, - const git_index_entry **wditem, - git_vector *pathspec) -{ - int cmp = -1, error; - int (*strcomp)(const char *, const char *) = data->diff->strcomp; - int (*pfxcomp)(const char *str, const char *pfx) = data->diff->pfxcomp; - int (*advance)(const git_index_entry **, git_iterator *) = NULL; - - /* move workdir iterator to follow along with deltas */ - - while (1) { - const git_index_entry *wd = *wditem; - - if (!wd) - return checkout_action_no_wd(action, data, delta); - - cmp = strcomp(wd->path, delta->old_file.path); - - /* 1. wd before delta ("a/a" before "a/b") - * 2. wd prefixes delta & should expand ("a/" before "a/b") - * 3. wd prefixes delta & cannot expand ("a/b" before "a/b/c") - * 4. wd equals delta ("a/b" and "a/b") - * 5. wd after delta & delta prefixes wd ("a/b/c" after "a/b/" or "a/b") - * 6. wd after delta ("a/c" after "a/b") - */ - - if (cmp < 0) { - cmp = pfxcomp(delta->old_file.path, wd->path); - - if (cmp == 0) { - if (wd->mode == GIT_FILEMODE_TREE) { - /* case 2 - entry prefixed by workdir tree */ - error = git_iterator_advance_into(wditem, workdir); - if (error < 0 && error != GIT_ITEROVER) - goto done; - continue; - } - - /* case 3 maybe - wd contains non-dir where dir expected */ - if (delta->old_file.path[strlen(wd->path)] == '/') { - error = checkout_action_with_wd_blocker( - action, data, delta, wd); - advance = git_iterator_advance; - goto done; - } - } - - /* case 1 - handle wd item (if it matches pathspec) */ - error = checkout_action_wd_only(data, workdir, wditem, pathspec); - if (error && error != GIT_ITEROVER) - goto done; - continue; - } - - if (cmp == 0) { - /* case 4 */ - error = checkout_action_with_wd(action, data, delta, workdir, wd); - advance = git_iterator_advance; - goto done; - } - - cmp = pfxcomp(wd->path, delta->old_file.path); - - if (cmp == 0) { /* case 5 */ - if (wd->path[strlen(delta->old_file.path)] != '/') - return checkout_action_no_wd(action, data, delta); - - if (delta->status == GIT_DELTA_TYPECHANGE) { - if (delta->old_file.mode == GIT_FILEMODE_TREE) { - error = checkout_action_with_wd(action, data, delta, workdir, wd); - advance = git_iterator_advance_into; - goto done; - } - - if (delta->new_file.mode == GIT_FILEMODE_TREE || - delta->new_file.mode == GIT_FILEMODE_COMMIT || - delta->old_file.mode == GIT_FILEMODE_COMMIT) - { - error = checkout_action_with_wd(action, data, delta, workdir, wd); - advance = git_iterator_advance; - goto done; - } - } - - return checkout_is_empty_dir(data, wd->path) ? - checkout_action_with_wd_dir_empty(action, data, delta) : - checkout_action_with_wd_dir(action, data, delta, workdir, wd); - } - - /* case 6 - wd is after delta */ - return checkout_action_no_wd(action, data, delta); - } - -done: - if (!error && advance != NULL && - (error = advance(wditem, workdir)) < 0) { - *wditem = NULL; - if (error == GIT_ITEROVER) - error = 0; - } - - return error; -} - -static int checkout_remaining_wd_items( - checkout_data *data, - git_iterator *workdir, - const git_index_entry *wd, - git_vector *spec) -{ - int error = 0; - - while (wd && !error) - error = checkout_action_wd_only(data, workdir, &wd, spec); - - if (error == GIT_ITEROVER) - error = 0; - - return error; -} - -GIT_INLINE(int) checkout_idxentry_cmp( - const git_index_entry *a, - const git_index_entry *b) -{ - if (!a && !b) - return 0; - else if (!a && b) - return -1; - else if(a && !b) - return 1; - else - return strcmp(a->path, b->path); -} - -static int checkout_conflictdata_cmp(const void *a, const void *b) -{ - const checkout_conflictdata *ca = a; - const checkout_conflictdata *cb = b; - int diff; - - if ((diff = checkout_idxentry_cmp(ca->ancestor, cb->ancestor)) == 0 && - (diff = checkout_idxentry_cmp(ca->ours, cb->theirs)) == 0) - diff = checkout_idxentry_cmp(ca->theirs, cb->theirs); - - return diff; -} - -static int checkout_conflictdata_empty( - const git_vector *conflicts, size_t idx, void *payload) -{ - checkout_conflictdata *conflict; - - GIT_UNUSED(payload); - - if ((conflict = git_vector_get(conflicts, idx)) == NULL) - return -1; - - if (conflict->ancestor || conflict->ours || conflict->theirs) - return 0; - - git__free(conflict); - return 1; -} - -GIT_INLINE(bool) conflict_pathspec_match( - checkout_data *data, - git_iterator *workdir, - git_vector *pathspec, - const git_index_entry *ancestor, - const git_index_entry *ours, - const git_index_entry *theirs) -{ - /* if the pathspec matches ours *or* theirs, proceed */ - if (ours && git_pathspec__match(pathspec, ours->path, - (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, - git_iterator_ignore_case(workdir), NULL, NULL)) - return true; - - if (theirs && git_pathspec__match(pathspec, theirs->path, - (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, - git_iterator_ignore_case(workdir), NULL, NULL)) - return true; - - if (ancestor && git_pathspec__match(pathspec, ancestor->path, - (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, - git_iterator_ignore_case(workdir), NULL, NULL)) - return true; - - return false; -} - -GIT_INLINE(int) checkout_conflict_detect_submodule(checkout_conflictdata *conflict) -{ - conflict->submodule = ((conflict->ancestor && S_ISGITLINK(conflict->ancestor->mode)) || - (conflict->ours && S_ISGITLINK(conflict->ours->mode)) || - (conflict->theirs && S_ISGITLINK(conflict->theirs->mode))); - return 0; -} - -GIT_INLINE(int) checkout_conflict_detect_binary(git_repository *repo, checkout_conflictdata *conflict) -{ - git_blob *ancestor_blob = NULL, *our_blob = NULL, *their_blob = NULL; - int error = 0; - - if (conflict->submodule) - return 0; - - if (conflict->ancestor) { - if ((error = git_blob_lookup(&ancestor_blob, repo, &conflict->ancestor->id)) < 0) - goto done; - - conflict->binary = git_blob_is_binary(ancestor_blob); - } - - if (!conflict->binary && conflict->ours) { - if ((error = git_blob_lookup(&our_blob, repo, &conflict->ours->id)) < 0) - goto done; - - conflict->binary = git_blob_is_binary(our_blob); - } - - if (!conflict->binary && conflict->theirs) { - if ((error = git_blob_lookup(&their_blob, repo, &conflict->theirs->id)) < 0) - goto done; - - conflict->binary = git_blob_is_binary(their_blob); - } - -done: - git_blob_free(ancestor_blob); - git_blob_free(our_blob); - git_blob_free(their_blob); - - return error; -} - -static int checkout_conflict_append_update( - const git_index_entry *ancestor, - const git_index_entry *ours, - const git_index_entry *theirs, - void *payload) -{ - checkout_data *data = payload; - checkout_conflictdata *conflict; - int error; - - conflict = git__calloc(1, sizeof(checkout_conflictdata)); - GIT_ERROR_CHECK_ALLOC(conflict); - - conflict->ancestor = ancestor; - conflict->ours = ours; - conflict->theirs = theirs; - - if ((error = checkout_conflict_detect_submodule(conflict)) < 0 || - (error = checkout_conflict_detect_binary(data->repo, conflict)) < 0) - { - git__free(conflict); - return error; - } - - if (git_vector_insert(&data->update_conflicts, conflict)) - return -1; - - return 0; -} - -static int checkout_conflicts_foreach( - checkout_data *data, - git_index *index, - git_iterator *workdir, - git_vector *pathspec, - int (*cb)(const git_index_entry *, const git_index_entry *, const git_index_entry *, void *), - void *payload) -{ - git_index_conflict_iterator *iterator = NULL; - const git_index_entry *ancestor, *ours, *theirs; - int error = 0; - - if ((error = git_index_conflict_iterator_new(&iterator, index)) < 0) - goto done; - - /* Collect the conflicts */ - while ((error = git_index_conflict_next(&ancestor, &ours, &theirs, iterator)) == 0) { - if (!conflict_pathspec_match(data, workdir, pathspec, ancestor, ours, theirs)) - continue; - - if ((error = cb(ancestor, ours, theirs, payload)) < 0) - goto done; - } - - if (error == GIT_ITEROVER) - error = 0; - -done: - git_index_conflict_iterator_free(iterator); - - return error; -} - -static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, git_vector *pathspec) -{ - git_index *index; - - /* Only write conflicts from sources that have them: indexes. */ - if ((index = git_iterator_index(data->target)) == NULL) - return 0; - - data->update_conflicts._cmp = checkout_conflictdata_cmp; - - if (checkout_conflicts_foreach(data, index, workdir, pathspec, checkout_conflict_append_update, data) < 0) - return -1; - - /* Collect the REUC and NAME entries */ - data->update_reuc = &index->reuc; - data->update_names = &index->names; - - return 0; -} - -GIT_INLINE(int) checkout_conflicts_cmp_entry( - const char *path, - const git_index_entry *entry) -{ - return strcmp((const char *)path, entry->path); -} - -static int checkout_conflicts_cmp_ancestor(const void *p, const void *c) -{ - const char *path = p; - const checkout_conflictdata *conflict = c; - - if (!conflict->ancestor) - return 1; - - return checkout_conflicts_cmp_entry(path, conflict->ancestor); -} - -static checkout_conflictdata *checkout_conflicts_search_ancestor( - checkout_data *data, - const char *path) -{ - size_t pos; - - if (git_vector_bsearch2(&pos, &data->update_conflicts, checkout_conflicts_cmp_ancestor, path) < 0) - return NULL; - - return git_vector_get(&data->update_conflicts, pos); -} - -static checkout_conflictdata *checkout_conflicts_search_branch( - checkout_data *data, - const char *path) -{ - checkout_conflictdata *conflict; - size_t i; - - git_vector_foreach(&data->update_conflicts, i, conflict) { - int cmp = -1; - - if (conflict->ancestor) - break; - - if (conflict->ours) - cmp = checkout_conflicts_cmp_entry(path, conflict->ours); - else if (conflict->theirs) - cmp = checkout_conflicts_cmp_entry(path, conflict->theirs); - - if (cmp == 0) - return conflict; - } - - return NULL; -} - -static int checkout_conflicts_load_byname_entry( - checkout_conflictdata **ancestor_out, - checkout_conflictdata **ours_out, - checkout_conflictdata **theirs_out, - checkout_data *data, - const git_index_name_entry *name_entry) -{ - checkout_conflictdata *ancestor, *ours = NULL, *theirs = NULL; - int error = 0; - - *ancestor_out = NULL; - *ours_out = NULL; - *theirs_out = NULL; - - if (!name_entry->ancestor) { - git_error_set(GIT_ERROR_INDEX, "a NAME entry exists without an ancestor"); - error = -1; - goto done; - } - - if (!name_entry->ours && !name_entry->theirs) { - git_error_set(GIT_ERROR_INDEX, "a NAME entry exists without an ours or theirs"); - error = -1; - goto done; - } - - if ((ancestor = checkout_conflicts_search_ancestor(data, - name_entry->ancestor)) == NULL) { - git_error_set(GIT_ERROR_INDEX, - "a NAME entry referenced ancestor entry '%s' which does not exist in the main index", - name_entry->ancestor); - error = -1; - goto done; - } - - if (name_entry->ours) { - if (strcmp(name_entry->ancestor, name_entry->ours) == 0) - ours = ancestor; - else if ((ours = checkout_conflicts_search_branch(data, name_entry->ours)) == NULL || - ours->ours == NULL) { - git_error_set(GIT_ERROR_INDEX, - "a NAME entry referenced our entry '%s' which does not exist in the main index", - name_entry->ours); - error = -1; - goto done; - } - } - - if (name_entry->theirs) { - if (strcmp(name_entry->ancestor, name_entry->theirs) == 0) - theirs = ancestor; - else if (name_entry->ours && strcmp(name_entry->ours, name_entry->theirs) == 0) - theirs = ours; - else if ((theirs = checkout_conflicts_search_branch(data, name_entry->theirs)) == NULL || - theirs->theirs == NULL) { - git_error_set(GIT_ERROR_INDEX, - "a NAME entry referenced their entry '%s' which does not exist in the main index", - name_entry->theirs); - error = -1; - goto done; - } - } - - *ancestor_out = ancestor; - *ours_out = ours; - *theirs_out = theirs; - -done: - return error; -} - -static int checkout_conflicts_coalesce_renames( - checkout_data *data) -{ - git_index *index; - const git_index_name_entry *name_entry; - checkout_conflictdata *ancestor_conflict, *our_conflict, *their_conflict; - size_t i, names; - int error = 0; - - if ((index = git_iterator_index(data->target)) == NULL) - return 0; - - /* Juggle entries based on renames */ - names = git_index_name_entrycount(index); - - for (i = 0; i < names; i++) { - name_entry = git_index_name_get_byindex(index, i); - - if ((error = checkout_conflicts_load_byname_entry( - &ancestor_conflict, &our_conflict, &their_conflict, - data, name_entry)) < 0) - goto done; - - if (our_conflict && our_conflict != ancestor_conflict) { - ancestor_conflict->ours = our_conflict->ours; - our_conflict->ours = NULL; - - if (our_conflict->theirs) - our_conflict->name_collision = 1; - - if (our_conflict->name_collision) - ancestor_conflict->name_collision = 1; - } - - if (their_conflict && their_conflict != ancestor_conflict) { - ancestor_conflict->theirs = their_conflict->theirs; - their_conflict->theirs = NULL; - - if (their_conflict->ours) - their_conflict->name_collision = 1; - - if (their_conflict->name_collision) - ancestor_conflict->name_collision = 1; - } - - if (our_conflict && our_conflict != ancestor_conflict && - their_conflict && their_conflict != ancestor_conflict) - ancestor_conflict->one_to_two = 1; - } - - git_vector_remove_matching( - &data->update_conflicts, checkout_conflictdata_empty, NULL); - -done: - return error; -} - -static int checkout_conflicts_mark_directoryfile( - checkout_data *data) -{ - git_index *index; - checkout_conflictdata *conflict; - const git_index_entry *entry; - size_t i, j, len; - const char *path; - int prefixed, error = 0; - - if ((index = git_iterator_index(data->target)) == NULL) - return 0; - - len = git_index_entrycount(index); - - /* Find d/f conflicts */ - git_vector_foreach(&data->update_conflicts, i, conflict) { - if ((conflict->ours && conflict->theirs) || - (!conflict->ours && !conflict->theirs)) - continue; - - path = conflict->ours ? - conflict->ours->path : conflict->theirs->path; - - if ((error = git_index_find(&j, index, path)) < 0) { - if (error == GIT_ENOTFOUND) - git_error_set(GIT_ERROR_INDEX, - "index inconsistency, could not find entry for expected conflict '%s'", path); - - goto done; - } - - for (; j < len; j++) { - if ((entry = git_index_get_byindex(index, j)) == NULL) { - git_error_set(GIT_ERROR_INDEX, - "index inconsistency, truncated index while loading expected conflict '%s'", path); - error = -1; - goto done; - } - - prefixed = git_fs_path_equal_or_prefixed(path, entry->path, NULL); - - if (prefixed == GIT_FS_PATH_EQUAL) - continue; - - if (prefixed == GIT_FS_PATH_PREFIX) - conflict->directoryfile = 1; - - break; - } - } - -done: - return error; -} - -static int checkout_get_update_conflicts( - checkout_data *data, - git_iterator *workdir, - git_vector *pathspec) -{ - int error = 0; - - if (data->strategy & GIT_CHECKOUT_SKIP_UNMERGED) - return 0; - - if ((error = checkout_conflicts_load(data, workdir, pathspec)) < 0 || - (error = checkout_conflicts_coalesce_renames(data)) < 0 || - (error = checkout_conflicts_mark_directoryfile(data)) < 0) - goto done; - -done: - return error; -} - -static int checkout_conflict_append_remove( - const git_index_entry *ancestor, - const git_index_entry *ours, - const git_index_entry *theirs, - void *payload) -{ - checkout_data *data = payload; - const char *name; - - GIT_ASSERT_ARG(ancestor || ours || theirs); - - if (ancestor) - name = git__strdup(ancestor->path); - else if (ours) - name = git__strdup(ours->path); - else if (theirs) - name = git__strdup(theirs->path); - else - abort(); - - GIT_ERROR_CHECK_ALLOC(name); - - return git_vector_insert(&data->remove_conflicts, (char *)name); -} - -static int checkout_get_remove_conflicts( - checkout_data *data, - git_iterator *workdir, - git_vector *pathspec) -{ - if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0) - return 0; - - return checkout_conflicts_foreach(data, data->index, workdir, pathspec, checkout_conflict_append_remove, data); -} - -static int checkout_verify_paths( - git_repository *repo, - int action, - git_diff_delta *delta) -{ - unsigned int flags = GIT_PATH_REJECT_WORKDIR_DEFAULTS; - - if (action & CHECKOUT_ACTION__REMOVE) { - if (!git_path_is_valid(repo, delta->old_file.path, delta->old_file.mode, flags)) { - git_error_set(GIT_ERROR_CHECKOUT, "cannot remove invalid path '%s'", delta->old_file.path); - return -1; - } - } - - if (action & ~CHECKOUT_ACTION__REMOVE) { - if (!git_path_is_valid(repo, delta->new_file.path, delta->new_file.mode, flags)) { - git_error_set(GIT_ERROR_CHECKOUT, "cannot checkout to invalid path '%s'", delta->new_file.path); - return -1; - } - } - - return 0; -} - -static int checkout_get_actions( - uint32_t **actions_ptr, - size_t **counts_ptr, - checkout_data *data, - git_iterator *workdir) -{ - int error = 0, act; - const git_index_entry *wditem; - git_vector pathspec = GIT_VECTOR_INIT, *deltas; - git_pool pathpool; - git_diff_delta *delta; - size_t i, *counts = NULL; - uint32_t *actions = NULL; - - if (git_pool_init(&pathpool, 1) < 0) - return -1; - - if (data->opts.paths.count > 0 && - git_pathspec__vinit(&pathspec, &data->opts.paths, &pathpool) < 0) - return -1; - - if ((error = git_iterator_current(&wditem, workdir)) < 0 && - error != GIT_ITEROVER) - goto fail; - - deltas = &data->diff->deltas; - - *counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t)); - *actions_ptr = actions = git__calloc( - deltas->length ? deltas->length : 1, sizeof(uint32_t)); - if (!counts || !actions) { - error = -1; - goto fail; - } - - git_vector_foreach(deltas, i, delta) { - if ((error = checkout_action(&act, data, delta, workdir, &wditem, &pathspec)) == 0) - error = checkout_verify_paths(data->repo, act, delta); - - if (error != 0) - goto fail; - - actions[i] = act; - - if (act & CHECKOUT_ACTION__REMOVE) - counts[CHECKOUT_ACTION__REMOVE]++; - if (act & CHECKOUT_ACTION__UPDATE_BLOB) - counts[CHECKOUT_ACTION__UPDATE_BLOB]++; - if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE) - counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++; - if (act & CHECKOUT_ACTION__CONFLICT) - counts[CHECKOUT_ACTION__CONFLICT]++; - } - - error = checkout_remaining_wd_items(data, workdir, wditem, &pathspec); - if (error) - goto fail; - - counts[CHECKOUT_ACTION__REMOVE] += data->removes.length; - - if (counts[CHECKOUT_ACTION__CONFLICT] > 0 && - (data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0) { - git_error_set(GIT_ERROR_CHECKOUT, "%"PRIuZ" %s checkout", - counts[CHECKOUT_ACTION__CONFLICT], - counts[CHECKOUT_ACTION__CONFLICT] == 1 ? - "conflict prevents" : "conflicts prevent"); - error = GIT_ECONFLICT; - goto fail; - } - - - if ((error = checkout_get_remove_conflicts(data, workdir, &pathspec)) < 0 || - (error = checkout_get_update_conflicts(data, workdir, &pathspec)) < 0) - goto fail; - - counts[CHECKOUT_ACTION__REMOVE_CONFLICT] = git_vector_length(&data->remove_conflicts); - counts[CHECKOUT_ACTION__UPDATE_CONFLICT] = git_vector_length(&data->update_conflicts); - - git_pathspec__vfree(&pathspec); - git_pool_clear(&pathpool); - - return 0; - -fail: - *counts_ptr = NULL; - git__free(counts); - *actions_ptr = NULL; - git__free(actions); - - git_pathspec__vfree(&pathspec); - git_pool_clear(&pathpool); - - return error; -} - -static bool should_remove_existing(checkout_data *data) -{ - int ignorecase; - - if (git_repository__configmap_lookup(&ignorecase, data->repo, GIT_CONFIGMAP_IGNORECASE) < 0) { - ignorecase = 0; - } - - return (ignorecase && - (data->strategy & GIT_CHECKOUT_DONT_REMOVE_EXISTING) == 0); -} - -#define MKDIR_NORMAL \ - GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR -#define MKDIR_REMOVE_EXISTING \ - MKDIR_NORMAL | GIT_MKDIR_REMOVE_FILES | GIT_MKDIR_REMOVE_SYMLINKS - -static int checkout_mkdir( - checkout_data *data, - const char *path, - const char *base, - mode_t mode, - unsigned int flags) -{ - struct git_futils_mkdir_options mkdir_opts = {0}; - int error; - - mkdir_opts.dir_map = data->mkdir_map; - mkdir_opts.pool = &data->pool; - - error = git_futils_mkdir_relative( - path, base, mode, flags, &mkdir_opts); - - data->perfdata.mkdir_calls += mkdir_opts.perfdata.mkdir_calls; - data->perfdata.stat_calls += mkdir_opts.perfdata.stat_calls; - data->perfdata.chmod_calls += mkdir_opts.perfdata.chmod_calls; - - return error; -} - -static int mkpath2file( - checkout_data *data, const char *path, unsigned int mode) -{ - struct stat st; - bool remove_existing = should_remove_existing(data); - unsigned int flags = - (remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL) | - GIT_MKDIR_SKIP_LAST; - int error; - - if ((error = checkout_mkdir( - data, path, data->opts.target_directory, mode, flags)) < 0) - return error; - - if (remove_existing) { - data->perfdata.stat_calls++; - - if (p_lstat(path, &st) == 0) { - - /* Some file, symlink or folder already exists at this name. - * We would have removed it in remove_the_old unless we're on - * a case inensitive filesystem (or the user has asked us not - * to). Remove the similarly named file to write the new. - */ - error = git_futils_rmdir_r(path, NULL, GIT_RMDIR_REMOVE_FILES); - } else if (errno != ENOENT) { - git_error_set(GIT_ERROR_OS, "failed to stat '%s'", path); - return GIT_EEXISTS; - } else { - git_error_clear(); - } - } - - return error; -} - -struct checkout_stream { - git_writestream base; - const char *path; - int fd; - int open; -}; - -static int checkout_stream_write( - git_writestream *s, const char *buffer, size_t len) -{ - struct checkout_stream *stream = (struct checkout_stream *)s; - int ret; - - if ((ret = p_write(stream->fd, buffer, len)) < 0) - git_error_set(GIT_ERROR_OS, "could not write to '%s'", stream->path); - - return ret; -} - -static int checkout_stream_close(git_writestream *s) -{ - struct checkout_stream *stream = (struct checkout_stream *)s; - - GIT_ASSERT_ARG(stream); - GIT_ASSERT_ARG(stream->open); - - stream->open = 0; - return p_close(stream->fd); -} - -static void checkout_stream_free(git_writestream *s) -{ - GIT_UNUSED(s); -} - -static int blob_content_to_file( - checkout_data *data, - struct stat *st, - git_blob *blob, - const char *path, - const char *hint_path, - mode_t entry_filemode) -{ - int flags = data->opts.file_open_flags; - mode_t file_mode = data->opts.file_mode ? - data->opts.file_mode : entry_filemode; - git_filter_session filter_session = GIT_FILTER_SESSION_INIT; - struct checkout_stream writer; - mode_t mode; - git_filter_list *fl = NULL; - int fd; - int error = 0; - - GIT_ASSERT(hint_path != NULL); - - if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0) - return error; - - if (flags <= 0) - flags = O_CREAT | O_TRUNC | O_WRONLY; - if (!(mode = file_mode)) - mode = GIT_FILEMODE_BLOB; - - if ((fd = p_open(path, flags, mode)) < 0) { - git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path); - return fd; - } - - filter_session.attr_session = &data->attr_session; - filter_session.temp_buf = &data->tmp; - - if (!data->opts.disable_filters && - (error = git_filter_list__load( - &fl, data->repo, blob, hint_path, - GIT_FILTER_TO_WORKTREE, &filter_session))) { - p_close(fd); - return error; - } - - /* setup the writer */ - memset(&writer, 0, sizeof(struct checkout_stream)); - writer.base.write = checkout_stream_write; - writer.base.close = checkout_stream_close; - writer.base.free = checkout_stream_free; - writer.path = path; - writer.fd = fd; - writer.open = 1; - - error = git_filter_list_stream_blob(fl, blob, &writer.base); - - GIT_ASSERT(writer.open == 0); - - git_filter_list_free(fl); - - if (error < 0) - return error; - - if (st) { - data->perfdata.stat_calls++; - - if ((error = p_stat(path, st)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to stat '%s'", path); - return error; - } - - st->st_mode = entry_filemode; - } - - return 0; -} - -static int blob_content_to_link( - checkout_data *data, - struct stat *st, - git_blob *blob, - const char *path) -{ - git_str linktarget = GIT_STR_INIT; - int error; - - if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0) - return error; - - if ((error = git_blob__getbuf(&linktarget, blob)) < 0) - return error; - - if (data->can_symlink) { - if ((error = p_symlink(git_str_cstr(&linktarget), path)) < 0) - git_error_set(GIT_ERROR_OS, "could not create symlink %s", path); - } else { - error = git_futils_fake_symlink(git_str_cstr(&linktarget), path); - } - - if (!error) { - data->perfdata.stat_calls++; - - if ((error = p_lstat(path, st)) < 0) - git_error_set(GIT_ERROR_CHECKOUT, "could not stat symlink %s", path); - - st->st_mode = GIT_FILEMODE_LINK; - } - - git_str_dispose(&linktarget); - - return error; -} - -static int checkout_update_index( - checkout_data *data, - const git_diff_file *file, - struct stat *st) -{ - git_index_entry entry; - - if (!data->index) - return 0; - - memset(&entry, 0, sizeof(entry)); - entry.path = (char *)file->path; /* cast to prevent warning */ - git_index_entry__init_from_stat(&entry, st, true); - git_oid_cpy(&entry.id, &file->id); - - return git_index_add(data->index, &entry); -} - -static int checkout_submodule_update_index( - checkout_data *data, - const git_diff_file *file) -{ - git_str *fullpath; - struct stat st; - - /* update the index unless prevented */ - if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0) - return 0; - - if (checkout_target_fullpath(&fullpath, data, file->path) < 0) - return -1; - - data->perfdata.stat_calls++; - if (p_stat(fullpath->ptr, &st) < 0) { - git_error_set( - GIT_ERROR_CHECKOUT, "could not stat submodule %s\n", file->path); - return GIT_ENOTFOUND; - } - - st.st_mode = GIT_FILEMODE_COMMIT; - - return checkout_update_index(data, file, &st); -} - -static int checkout_submodule( - checkout_data *data, - const git_diff_file *file) -{ - bool remove_existing = should_remove_existing(data); - int error = 0; - - /* Until submodules are supported, UPDATE_ONLY means do nothing here */ - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) - return 0; - - if ((error = checkout_mkdir( - data, - file->path, data->opts.target_directory, data->opts.dir_mode, - remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL)) < 0) - return error; - - if ((error = git_submodule_lookup(NULL, data->repo, file->path)) < 0) { - /* I've observed repos with submodules in the tree that do not - * have a .gitmodules - core Git just makes an empty directory - */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - return checkout_submodule_update_index(data, file); - } - - return error; - } - - /* TODO: Support checkout_strategy options. Two circumstances: - * 1 - submodule already checked out, but we need to move the HEAD - * to the new OID, or - * 2 - submodule not checked out and we should recursively check it out - * - * Checkout will not execute a pull on the submodule, but a clone - * command should probably be able to. Do we need a submodule callback? - */ - - return checkout_submodule_update_index(data, file); -} - -static void report_progress( - checkout_data *data, - const char *path) -{ - if (data->opts.progress_cb) - data->opts.progress_cb( - path, data->completed_steps, data->total_steps, - data->opts.progress_payload); -} - -static int checkout_safe_for_update_only( - checkout_data *data, const char *path, mode_t expected_mode) -{ - struct stat st; - - data->perfdata.stat_calls++; - - if (p_lstat(path, &st) < 0) { - /* if doesn't exist, then no error and no update */ - if (errno == ENOENT || errno == ENOTDIR) - return 0; - - /* otherwise, stat error and no update */ - git_error_set(GIT_ERROR_OS, "failed to stat '%s'", path); - return -1; - } - - /* only safe for update if this is the same type of file */ - if ((st.st_mode & ~0777) == (expected_mode & ~0777)) - return 1; - - return 0; -} - -static int checkout_write_content( - checkout_data *data, - const git_oid *oid, - const char *full_path, - const char *hint_path, - unsigned int mode, - struct stat *st) -{ - int error = 0; - git_blob *blob; - - if ((error = git_blob_lookup(&blob, data->repo, oid)) < 0) - return error; - - if (S_ISLNK(mode)) - error = blob_content_to_link(data, st, blob, full_path); - else - error = blob_content_to_file(data, st, blob, full_path, hint_path, mode); - - git_blob_free(blob); - - /* if we try to create the blob and an existing directory blocks it from - * being written, then there must have been a typechange conflict in a - * parent directory - suppress the error and try to continue. - */ - if ((data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) != 0 && - (error == GIT_ENOTFOUND || error == GIT_EEXISTS)) - { - git_error_clear(); - error = 0; - } - - return error; -} - -static int checkout_blob( - checkout_data *data, - const git_diff_file *file) -{ - git_str *fullpath; - struct stat st; - int error = 0; - - if (checkout_target_fullpath(&fullpath, data, file->path) < 0) - return -1; - - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { - int rval = checkout_safe_for_update_only( - data, fullpath->ptr, file->mode); - - if (rval <= 0) - return rval; - } - - error = checkout_write_content( - data, &file->id, fullpath->ptr, file->path, file->mode, &st); - - /* update the index unless prevented */ - if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) - error = checkout_update_index(data, file, &st); - - /* update the submodule data if this was a new .gitmodules file */ - if (!error && strcmp(file->path, ".gitmodules") == 0) - data->reload_submodules = true; - - return error; -} - -static int checkout_remove_the_old( - unsigned int *actions, - checkout_data *data) -{ - int error = 0; - git_diff_delta *delta; - const char *str; - size_t i; - git_str *fullpath; - uint32_t flg = GIT_RMDIR_EMPTY_PARENTS | - GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS; - - if (data->opts.checkout_strategy & GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES) - flg |= GIT_RMDIR_SKIP_NONEMPTY; - - if (checkout_target_fullpath(&fullpath, data, NULL) < 0) - return -1; - - git_vector_foreach(&data->diff->deltas, i, delta) { - if (actions[i] & CHECKOUT_ACTION__REMOVE) { - error = git_futils_rmdir_r( - delta->old_file.path, fullpath->ptr, flg); - - if (error < 0) - return error; - - data->completed_steps++; - report_progress(data, delta->old_file.path); - - if ((actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) == 0 && - (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && - data->index != NULL) - { - (void)git_index_remove(data->index, delta->old_file.path, 0); - } - } - } - - git_vector_foreach(&data->removes, i, str) { - error = git_futils_rmdir_r(str, fullpath->ptr, flg); - if (error < 0) - return error; - - data->completed_steps++; - report_progress(data, str); - - if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && - data->index != NULL) - { - if (str[strlen(str) - 1] == '/') - (void)git_index_remove_directory(data->index, str, 0); - else - (void)git_index_remove(data->index, str, 0); - } - } - - return 0; -} - -static int checkout_create_the_new( - unsigned int *actions, - checkout_data *data) -{ - int error = 0; - git_diff_delta *delta; - size_t i; - - git_vector_foreach(&data->diff->deltas, i, delta) { - if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB && !S_ISLNK(delta->new_file.mode)) { - if ((error = checkout_blob(data, &delta->new_file)) < 0) - return error; - data->completed_steps++; - report_progress(data, delta->new_file.path); - } - } - - git_vector_foreach(&data->diff->deltas, i, delta) { - if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB && S_ISLNK(delta->new_file.mode)) { - if ((error = checkout_blob(data, &delta->new_file)) < 0) - return error; - data->completed_steps++; - report_progress(data, delta->new_file.path); - } - } - - return 0; -} - -static int checkout_create_submodules( - unsigned int *actions, - checkout_data *data) -{ - git_diff_delta *delta; - size_t i; - - git_vector_foreach(&data->diff->deltas, i, delta) { - if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) { - int error = checkout_submodule(data, &delta->new_file); - if (error < 0) - return error; - - data->completed_steps++; - report_progress(data, delta->new_file.path); - } - } - - return 0; -} - -static int checkout_lookup_head_tree(git_tree **out, git_repository *repo) -{ - int error = 0; - git_reference *ref = NULL; - git_object *head; - - if (!(error = git_repository_head(&ref, repo)) && - !(error = git_reference_peel(&head, ref, GIT_OBJECT_TREE))) - *out = (git_tree *)head; - - git_reference_free(ref); - - return error; -} - - -static int conflict_entry_name( - git_str *out, - const char *side_name, - const char *filename) -{ - if (git_str_puts(out, side_name) < 0 || - git_str_putc(out, ':') < 0 || - git_str_puts(out, filename) < 0) - return -1; - - return 0; -} - -static int checkout_path_suffixed(git_str *path, const char *suffix) -{ - size_t path_len; - int i = 0, error = 0; - - if ((error = git_str_putc(path, '~')) < 0 || (error = git_str_puts(path, suffix)) < 0) - return -1; - - path_len = git_str_len(path); - - while (git_fs_path_exists(git_str_cstr(path)) && i < INT_MAX) { - git_str_truncate(path, path_len); - - if ((error = git_str_putc(path, '_')) < 0 || - (error = git_str_printf(path, "%d", i)) < 0) - return error; - - i++; - } - - if (i == INT_MAX) { - git_str_truncate(path, path_len); - - git_error_set(GIT_ERROR_CHECKOUT, "could not write '%s': working directory file exists", path->ptr); - return GIT_EEXISTS; - } - - return 0; -} - -static int checkout_write_entry( - checkout_data *data, - checkout_conflictdata *conflict, - const git_index_entry *side) -{ - const char *hint_path = NULL, *suffix; - git_str *fullpath; - struct stat st; - int error; - - GIT_ASSERT(side == conflict->ours || side == conflict->theirs); - - if (checkout_target_fullpath(&fullpath, data, side->path) < 0) - return -1; - - if ((conflict->name_collision || conflict->directoryfile) && - (data->strategy & GIT_CHECKOUT_USE_OURS) == 0 && - (data->strategy & GIT_CHECKOUT_USE_THEIRS) == 0) { - - if (side == conflict->ours) - suffix = data->opts.our_label ? data->opts.our_label : - "ours"; - else - suffix = data->opts.their_label ? data->opts.their_label : - "theirs"; - - if (checkout_path_suffixed(fullpath, suffix) < 0) - return -1; - } - - hint_path = side->path; - - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && - (error = checkout_safe_for_update_only(data, fullpath->ptr, side->mode)) <= 0) - return error; - - if (!S_ISGITLINK(side->mode)) - return checkout_write_content(data, - &side->id, fullpath->ptr, hint_path, side->mode, &st); - - return 0; -} - -static int checkout_write_entries( - checkout_data *data, - checkout_conflictdata *conflict) -{ - int error = 0; - - if ((error = checkout_write_entry(data, conflict, conflict->ours)) >= 0) - error = checkout_write_entry(data, conflict, conflict->theirs); - - return error; -} - -static int checkout_merge_path( - git_str *out, - checkout_data *data, - checkout_conflictdata *conflict, - git_merge_file_result *result) -{ - const char *our_label_raw, *their_label_raw, *suffix; - int error = 0; - - if ((error = git_str_joinpath(out, data->opts.target_directory, result->path)) < 0 || - (error = git_path_validate_str_length(data->repo, out)) < 0) - return error; - - /* Most conflicts simply use the filename in the index */ - if (!conflict->name_collision) - return 0; - - /* Rename 2->1 conflicts need the branch name appended */ - our_label_raw = data->opts.our_label ? data->opts.our_label : "ours"; - their_label_raw = data->opts.their_label ? data->opts.their_label : "theirs"; - suffix = strcmp(result->path, conflict->ours->path) == 0 ? our_label_raw : their_label_raw; - - if ((error = checkout_path_suffixed(out, suffix)) < 0) - return error; - - return 0; -} - -static int checkout_write_merge( - checkout_data *data, - checkout_conflictdata *conflict) -{ - git_str our_label = GIT_STR_INIT, their_label = GIT_STR_INIT, - path_suffixed = GIT_STR_INIT, path_workdir = GIT_STR_INIT, - in_data = GIT_STR_INIT, out_data = GIT_STR_INIT; - git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; - git_merge_file_result result = {0}; - git_filebuf output = GIT_FILEBUF_INIT; - git_filter_list *fl = NULL; - git_filter_session filter_session = GIT_FILTER_SESSION_INIT; - int error = 0; - - if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_DIFF3) - opts.flags |= GIT_MERGE_FILE_STYLE_DIFF3; - - if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3) - opts.flags |= GIT_MERGE_FILE_STYLE_ZDIFF3; - - opts.ancestor_label = data->opts.ancestor_label ? - data->opts.ancestor_label : "ancestor"; - opts.our_label = data->opts.our_label ? - data->opts.our_label : "ours"; - opts.their_label = data->opts.their_label ? - data->opts.their_label : "theirs"; - - /* If all the paths are identical, decorate the diff3 file with the branch - * names. Otherwise, append branch_name:path. - */ - if (conflict->ours && conflict->theirs && - strcmp(conflict->ours->path, conflict->theirs->path) != 0) { - - if ((error = conflict_entry_name( - &our_label, opts.our_label, conflict->ours->path)) < 0 || - (error = conflict_entry_name( - &their_label, opts.their_label, conflict->theirs->path)) < 0) - goto done; - - opts.our_label = git_str_cstr(&our_label); - opts.their_label = git_str_cstr(&their_label); - } - - if ((error = git_merge_file_from_index(&result, data->repo, - conflict->ancestor, conflict->ours, conflict->theirs, &opts)) < 0) - goto done; - - if (result.path == NULL || result.mode == 0) { - git_error_set(GIT_ERROR_CHECKOUT, "could not merge contents of file"); - error = GIT_ECONFLICT; - goto done; - } - - if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0) - goto done; - - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && - (error = checkout_safe_for_update_only(data, git_str_cstr(&path_workdir), result.mode)) <= 0) - goto done; - - if (!data->opts.disable_filters) { - in_data.ptr = (char *)result.ptr; - in_data.size = result.len; - - filter_session.attr_session = &data->attr_session; - filter_session.temp_buf = &data->tmp; - - if ((error = git_filter_list__load( - &fl, data->repo, NULL, result.path, - GIT_FILTER_TO_WORKTREE, &filter_session)) < 0 || - (error = git_filter_list__convert_buf(&out_data, fl, &in_data)) < 0) - goto done; - } else { - out_data.ptr = (char *)result.ptr; - out_data.size = result.len; - } - - if ((error = mkpath2file(data, path_workdir.ptr, data->opts.dir_mode)) < 0 || - (error = git_filebuf_open(&output, git_str_cstr(&path_workdir), GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 || - (error = git_filebuf_write(&output, out_data.ptr, out_data.size)) < 0 || - (error = git_filebuf_commit(&output)) < 0) - goto done; - -done: - git_filter_list_free(fl); - - git_str_dispose(&out_data); - git_str_dispose(&our_label); - git_str_dispose(&their_label); - - git_merge_file_result_free(&result); - git_str_dispose(&path_workdir); - git_str_dispose(&path_suffixed); - - return error; -} - -static int checkout_conflict_add( - checkout_data *data, - const git_index_entry *conflict) -{ - int error = git_index_remove(data->index, conflict->path, 0); - - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - return error; - - return git_index_add(data->index, conflict); -} - -static int checkout_conflict_update_index( - checkout_data *data, - checkout_conflictdata *conflict) -{ - int error = 0; - - if (conflict->ancestor) - error = checkout_conflict_add(data, conflict->ancestor); - - if (!error && conflict->ours) - error = checkout_conflict_add(data, conflict->ours); - - if (!error && conflict->theirs) - error = checkout_conflict_add(data, conflict->theirs); - - return error; -} - -static int checkout_create_conflicts(checkout_data *data) -{ - checkout_conflictdata *conflict; - size_t i; - int error = 0; - - git_vector_foreach(&data->update_conflicts, i, conflict) { - - /* Both deleted: nothing to do */ - if (conflict->ours == NULL && conflict->theirs == NULL) - error = 0; - - else if ((data->strategy & GIT_CHECKOUT_USE_OURS) && - conflict->ours) - error = checkout_write_entry(data, conflict, conflict->ours); - else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) && - conflict->theirs) - error = checkout_write_entry(data, conflict, conflict->theirs); - - /* Ignore the other side of name collisions. */ - else if ((data->strategy & GIT_CHECKOUT_USE_OURS) && - !conflict->ours && conflict->name_collision) - error = 0; - else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) && - !conflict->theirs && conflict->name_collision) - error = 0; - - /* For modify/delete, name collisions and d/f conflicts, write - * the file (potentially with the name mangled. - */ - else if (conflict->ours != NULL && conflict->theirs == NULL) - error = checkout_write_entry(data, conflict, conflict->ours); - else if (conflict->ours == NULL && conflict->theirs != NULL) - error = checkout_write_entry(data, conflict, conflict->theirs); - - /* Add/add conflicts and rename 1->2 conflicts, write the - * ours/theirs sides (potentially name mangled). - */ - else if (conflict->one_to_two) - error = checkout_write_entries(data, conflict); - - /* If all sides are links, write the ours side */ - else if (S_ISLNK(conflict->ours->mode) && - S_ISLNK(conflict->theirs->mode)) - error = checkout_write_entry(data, conflict, conflict->ours); - /* Link/file conflicts, write the file side */ - else if (S_ISLNK(conflict->ours->mode)) - error = checkout_write_entry(data, conflict, conflict->theirs); - else if (S_ISLNK(conflict->theirs->mode)) - error = checkout_write_entry(data, conflict, conflict->ours); - - /* If any side is a gitlink, do nothing. */ - else if (conflict->submodule) - error = 0; - - /* If any side is binary, write the ours side */ - else if (conflict->binary) - error = checkout_write_entry(data, conflict, conflict->ours); - - else if (!error) - error = checkout_write_merge(data, conflict); - - /* Update the index extensions (REUC and NAME) if we're checking - * out a different index. (Otherwise just leave them there.) - */ - if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) - error = checkout_conflict_update_index(data, conflict); - - if (error) - break; - - data->completed_steps++; - report_progress(data, - conflict->ours ? conflict->ours->path : - (conflict->theirs ? conflict->theirs->path : conflict->ancestor->path)); - } - - return error; -} - -static int checkout_remove_conflicts(checkout_data *data) -{ - const char *conflict; - size_t i; - - git_vector_foreach(&data->remove_conflicts, i, conflict) { - if (git_index_conflict_remove(data->index, conflict) < 0) - return -1; - - data->completed_steps++; - } - - return 0; -} - -static int checkout_extensions_update_index(checkout_data *data) -{ - const git_index_reuc_entry *reuc_entry; - const git_index_name_entry *name_entry; - size_t i; - int error = 0; - - if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) - return 0; - - if (data->update_reuc) { - git_vector_foreach(data->update_reuc, i, reuc_entry) { - if ((error = git_index_reuc_add(data->index, reuc_entry->path, - reuc_entry->mode[0], &reuc_entry->oid[0], - reuc_entry->mode[1], &reuc_entry->oid[1], - reuc_entry->mode[2], &reuc_entry->oid[2])) < 0) - goto done; - } - } - - if (data->update_names) { - git_vector_foreach(data->update_names, i, name_entry) { - if ((error = git_index_name_add(data->index, name_entry->ancestor, - name_entry->ours, name_entry->theirs)) < 0) - goto done; - } - } - -done: - return error; -} - -static void checkout_data_clear(checkout_data *data) -{ - if (data->opts_free_baseline) { - git_tree_free(data->opts.baseline); - data->opts.baseline = NULL; - } - - git_vector_free(&data->removes); - git_pool_clear(&data->pool); - - git_vector_free_deep(&data->remove_conflicts); - git_vector_free_deep(&data->update_conflicts); - - git__free(data->pfx); - data->pfx = NULL; - - git_str_dispose(&data->target_path); - git_str_dispose(&data->tmp); - - git_index_free(data->index); - data->index = NULL; - - git_strmap_free(data->mkdir_map); - data->mkdir_map = NULL; - - git_attr_session__free(&data->attr_session); -} - -static int validate_target_directory(checkout_data *data) -{ - int error; - - if ((error = git_path_validate_length(data->repo, data->opts.target_directory)) < 0) - return error; - - if (git_fs_path_isdir(data->opts.target_directory)) - return 0; - - error = checkout_mkdir(data, data->opts.target_directory, NULL, - GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR); - - return error; -} - -static int checkout_data_init( - checkout_data *data, - git_iterator *target, - const git_checkout_options *proposed) -{ - int error = 0; - git_repository *repo = git_iterator_owner(target); - - memset(data, 0, sizeof(*data)); - - if (!repo) { - git_error_set(GIT_ERROR_CHECKOUT, "cannot checkout nothing"); - return -1; - } - - if ((!proposed || !proposed->target_directory) && - (error = git_repository__ensure_not_bare(repo, "checkout")) < 0) - return error; - - data->repo = repo; - data->target = target; - - GIT_ERROR_CHECK_VERSION( - proposed, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options"); - - if (!proposed) - GIT_INIT_STRUCTURE(&data->opts, GIT_CHECKOUT_OPTIONS_VERSION); - else - memmove(&data->opts, proposed, sizeof(git_checkout_options)); - - if (!data->opts.target_directory) - data->opts.target_directory = git_repository_workdir(repo); - else if ((error = validate_target_directory(data)) < 0) - goto cleanup; - - if ((error = git_repository_index(&data->index, data->repo)) < 0) - goto cleanup; - - /* refresh config and index content unless NO_REFRESH is given */ - if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) { - git_config *cfg; - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - goto cleanup; - - /* Reload the repository index (unless we're checking out the - * index; then it has the changes we're trying to check out - * and those should not be overwritten.) - */ - if (data->index != git_iterator_index(target)) { - if (data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) { - /* When forcing, we can blindly re-read the index */ - if ((error = git_index_read(data->index, false)) < 0) - goto cleanup; - } else { - /* - * When not being forced, we need to check for unresolved - * conflicts and unsaved changes in the index before - * proceeding. - */ - if (git_index_has_conflicts(data->index)) { - error = GIT_ECONFLICT; - git_error_set(GIT_ERROR_CHECKOUT, - "unresolved conflicts exist in the index"); - goto cleanup; - } - - if ((error = git_index_read_safely(data->index)) < 0) - goto cleanup; - } - - /* clean conflict data in the current index */ - git_index_name_clear(data->index); - git_index_reuc_clear(data->index); - } - } - - /* if you are forcing, allow all safe updates, plus recreate missing */ - if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0) - data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_RECREATE_MISSING; - - /* if the repository does not actually have an index file, then this - * is an initial checkout (perhaps from clone), so we allow safe updates - */ - if (!data->index->on_disk && - (data->opts.checkout_strategy & GIT_CHECKOUT_SAFE) != 0) - data->opts.checkout_strategy |= GIT_CHECKOUT_RECREATE_MISSING; - - data->strategy = data->opts.checkout_strategy; - - /* opts->disable_filters is false by default */ - - if (!data->opts.dir_mode) - data->opts.dir_mode = GIT_DIR_MODE; - - if (!data->opts.file_open_flags) - data->opts.file_open_flags = O_CREAT | O_TRUNC | O_WRONLY; - - data->pfx = git_pathspec_prefix(&data->opts.paths); - - if ((error = git_repository__configmap_lookup( - &data->can_symlink, repo, GIT_CONFIGMAP_SYMLINKS)) < 0) - goto cleanup; - - if ((error = git_repository__configmap_lookup( - &data->respect_filemode, repo, GIT_CONFIGMAP_FILEMODE)) < 0) - goto cleanup; - - if (!data->opts.baseline && !data->opts.baseline_index) { - data->opts_free_baseline = true; - error = 0; - - /* if we don't have an index, this is an initial checkout and - * should be against an empty baseline - */ - if (data->index->on_disk) - error = checkout_lookup_head_tree(&data->opts.baseline, repo); - - if (error == GIT_EUNBORNBRANCH) { - error = 0; - git_error_clear(); - } - - if (error < 0) - goto cleanup; - } - - if ((data->opts.checkout_strategy & - (GIT_CHECKOUT_CONFLICT_STYLE_MERGE | GIT_CHECKOUT_CONFLICT_STYLE_DIFF3)) == 0) { - git_config_entry *conflict_style = NULL; - git_config *cfg = NULL; - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 || - (error = git_config_get_entry(&conflict_style, cfg, "merge.conflictstyle")) < 0 || - error == GIT_ENOTFOUND) - ; - else if (error) - goto cleanup; - else if (strcmp(conflict_style->value, "merge") == 0) - data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_MERGE; - else if (strcmp(conflict_style->value, "diff3") == 0) - data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3; - else if (strcmp(conflict_style->value, "zdiff3") == 0) - data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3; - else { - git_error_set(GIT_ERROR_CHECKOUT, "unknown style '%s' given for 'merge.conflictstyle'", - conflict_style->value); - error = -1; - git_config_entry_free(conflict_style); - goto cleanup; - } - git_config_entry_free(conflict_style); - } - - if ((error = git_pool_init(&data->pool, 1)) < 0 || - (error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 || - (error = git_vector_init(&data->remove_conflicts, 0, NULL)) < 0 || - (error = git_vector_init(&data->update_conflicts, 0, NULL)) < 0 || - (error = git_str_puts(&data->target_path, data->opts.target_directory)) < 0 || - (error = git_fs_path_to_dir(&data->target_path)) < 0 || - (error = git_strmap_new(&data->mkdir_map)) < 0) - goto cleanup; - - data->target_len = git_str_len(&data->target_path); - - git_attr_session__init(&data->attr_session, data->repo); - -cleanup: - if (error < 0) - checkout_data_clear(data); - - return error; -} - -#define CHECKOUT_INDEX_DONT_WRITE_MASK \ - (GIT_CHECKOUT_DONT_UPDATE_INDEX | GIT_CHECKOUT_DONT_WRITE_INDEX) - -GIT_INLINE(void) setup_pathspecs( - git_iterator_options *iter_opts, - const git_checkout_options *checkout_opts) -{ - if (checkout_opts && - (checkout_opts->checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)) { - iter_opts->pathlist.count = checkout_opts->paths.count; - iter_opts->pathlist.strings = checkout_opts->paths.strings; - } -} - -int git_checkout_iterator( - git_iterator *target, - git_index *index, - const git_checkout_options *opts) -{ - int error = 0; - git_iterator *baseline = NULL, *workdir = NULL; - git_iterator_options baseline_opts = GIT_ITERATOR_OPTIONS_INIT, - workdir_opts = GIT_ITERATOR_OPTIONS_INIT; - checkout_data data = {0}; - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - uint32_t *actions = NULL; - size_t *counts = NULL; - - /* initialize structures and options */ - error = checkout_data_init(&data, target, opts); - if (error < 0) - return error; - - diff_opts.flags = - GIT_DIFF_INCLUDE_UNMODIFIED | - GIT_DIFF_INCLUDE_UNREADABLE | - GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */ - GIT_DIFF_INCLUDE_IGNORED | - GIT_DIFF_INCLUDE_TYPECHANGE | - GIT_DIFF_INCLUDE_TYPECHANGE_TREES | - GIT_DIFF_SKIP_BINARY_CHECK | - GIT_DIFF_INCLUDE_CASECHANGE; - if (data.opts.checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) - diff_opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; - if (data.opts.paths.count > 0) - diff_opts.pathspec = data.opts.paths; - - /* set up iterators */ - - workdir_opts.flags = git_iterator_ignore_case(target) ? - GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; - workdir_opts.flags |= GIT_ITERATOR_DONT_AUTOEXPAND; - workdir_opts.start = data.pfx; - workdir_opts.end = data.pfx; - - setup_pathspecs(&workdir_opts, opts); - - if ((error = git_iterator_reset_range(target, data.pfx, data.pfx)) < 0 || - (error = git_iterator_for_workdir_ext( - &workdir, data.repo, data.opts.target_directory, index, NULL, - &workdir_opts)) < 0) - goto cleanup; - - baseline_opts.flags = git_iterator_ignore_case(target) ? - GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; - baseline_opts.start = data.pfx; - baseline_opts.end = data.pfx; - - setup_pathspecs(&baseline_opts, opts); - - if (data.opts.baseline_index) { - if ((error = git_iterator_for_index( - &baseline, git_index_owner(data.opts.baseline_index), - data.opts.baseline_index, &baseline_opts)) < 0) - goto cleanup; - } else { - if ((error = git_iterator_for_tree( - &baseline, data.opts.baseline, &baseline_opts)) < 0) - goto cleanup; - } - - /* Should not have case insensitivity mismatch */ - GIT_ASSERT(git_iterator_ignore_case(workdir) == git_iterator_ignore_case(baseline)); - - /* Generate baseline-to-target diff which will include an entry for - * every possible update that might need to be made. - */ - if ((error = git_diff__from_iterators( - &data.diff, data.repo, baseline, target, &diff_opts)) < 0) - goto cleanup; - - /* Loop through diff (and working directory iterator) building a list of - * actions to be taken, plus look for conflicts and send notifications, - * then loop through conflicts. - */ - if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) != 0) - goto cleanup; - - if (data.strategy & GIT_CHECKOUT_DRY_RUN) - goto cleanup; - - data.total_steps = counts[CHECKOUT_ACTION__REMOVE] + - counts[CHECKOUT_ACTION__REMOVE_CONFLICT] + - counts[CHECKOUT_ACTION__UPDATE_BLOB] + - counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] + - counts[CHECKOUT_ACTION__UPDATE_CONFLICT]; - - report_progress(&data, NULL); /* establish 0 baseline */ - - /* To deal with some order dependencies, perform remaining checkout - * in three passes: removes, then update blobs, then update submodules. - */ - if (counts[CHECKOUT_ACTION__REMOVE] > 0 && - (error = checkout_remove_the_old(actions, &data)) < 0) - goto cleanup; - - if (counts[CHECKOUT_ACTION__REMOVE_CONFLICT] > 0 && - (error = checkout_remove_conflicts(&data)) < 0) - goto cleanup; - - if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 && - (error = checkout_create_the_new(actions, &data)) < 0) - goto cleanup; - - if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 && - (error = checkout_create_submodules(actions, &data)) < 0) - goto cleanup; - - if (counts[CHECKOUT_ACTION__UPDATE_CONFLICT] > 0 && - (error = checkout_create_conflicts(&data)) < 0) - goto cleanup; - - if (data.index != git_iterator_index(target) && - (error = checkout_extensions_update_index(&data)) < 0) - goto cleanup; - - GIT_ASSERT(data.completed_steps == data.total_steps); - - if (data.opts.perfdata_cb) - data.opts.perfdata_cb(&data.perfdata, data.opts.perfdata_payload); - -cleanup: - if (!error && data.index != NULL && - (data.strategy & CHECKOUT_INDEX_DONT_WRITE_MASK) == 0) - error = git_index_write(data.index); - - git_diff_free(data.diff); - git_iterator_free(workdir); - git_iterator_free(baseline); - git__free(actions); - git__free(counts); - checkout_data_clear(&data); - - return error; -} - -int git_checkout_index( - git_repository *repo, - git_index *index, - const git_checkout_options *opts) -{ - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error, owned = 0; - git_iterator *index_i; - - if (!index && !repo) { - git_error_set(GIT_ERROR_CHECKOUT, - "must provide either repository or index to checkout"); - return -1; - } - - if (index && repo && - git_index_owner(index) && - git_index_owner(index) != repo) { - git_error_set(GIT_ERROR_CHECKOUT, - "index to checkout does not match repository"); - return -1; - } else if(index && repo && !git_index_owner(index)) { - GIT_REFCOUNT_OWN(index, repo); - owned = 1; - } - - if (!repo) - repo = git_index_owner(index); - - if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) - return error; - GIT_REFCOUNT_INC(index); - - setup_pathspecs(&iter_opts, opts); - - if (!(error = git_iterator_for_index(&index_i, repo, index, &iter_opts))) - error = git_checkout_iterator(index_i, index, opts); - - if (owned) - GIT_REFCOUNT_OWN(index, NULL); - - git_iterator_free(index_i); - git_index_free(index); - - return error; -} - -int git_checkout_tree( - git_repository *repo, - const git_object *treeish, - const git_checkout_options *opts) -{ - int error; - git_index *index; - git_tree *tree = NULL; - git_iterator *tree_i = NULL; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - - if (!treeish && !repo) { - git_error_set(GIT_ERROR_CHECKOUT, - "must provide either repository or tree to checkout"); - return -1; - } - if (treeish && repo && git_object_owner(treeish) != repo) { - git_error_set(GIT_ERROR_CHECKOUT, - "object to checkout does not match repository"); - return -1; - } - - if (!repo) - repo = git_object_owner(treeish); - - if (treeish) { - if (git_object_peel((git_object **)&tree, treeish, GIT_OBJECT_TREE) < 0) { - git_error_set( - GIT_ERROR_CHECKOUT, "provided object cannot be peeled to a tree"); - return -1; - } - } - else { - if ((error = checkout_lookup_head_tree(&tree, repo)) < 0) { - if (error != GIT_EUNBORNBRANCH) - git_error_set( - GIT_ERROR_CHECKOUT, - "HEAD could not be peeled to a tree and no treeish given"); - return error; - } - } - - if ((error = git_repository_index(&index, repo)) < 0) - return error; - - setup_pathspecs(&iter_opts, opts); - - if (!(error = git_iterator_for_tree(&tree_i, tree, &iter_opts))) - error = git_checkout_iterator(tree_i, index, opts); - - git_iterator_free(tree_i); - git_index_free(index); - git_tree_free(tree); - - return error; -} - -int git_checkout_head( - git_repository *repo, - const git_checkout_options *opts) -{ - GIT_ASSERT_ARG(repo); - - return git_checkout_tree(repo, NULL, opts); -} - -int git_checkout_options_init(git_checkout_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_checkout_options, GIT_CHECKOUT_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_checkout_init_options(git_checkout_options *opts, unsigned int version) -{ - return git_checkout_options_init(opts, version); -} -#endif diff --git a/src/checkout.h b/src/checkout.h deleted file mode 100644 index 517fbf3b1..000000000 --- a/src/checkout.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_checkout_h__ -#define INCLUDE_checkout_h__ - -#include "common.h" - -#include "git2/checkout.h" -#include "iterator.h" - -#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12) - -/** - * Update the working directory to match the target iterator. The - * expected baseline value can be passed in via the checkout options - * or else will default to the HEAD commit. - */ -extern int git_checkout_iterator( - git_iterator *target, - git_index *index, - const git_checkout_options *opts); - -#endif diff --git a/src/cherrypick.c b/src/cherrypick.c deleted file mode 100644 index 9ec4962b9..000000000 --- a/src/cherrypick.c +++ /dev/null @@ -1,242 +0,0 @@ -/* -* Copyright (C) the libgit2 contributors. All rights reserved. -* -* This file is part of libgit2, distributed under the GNU GPL v2 with -* a Linking Exception. For full terms see the included COPYING file. -*/ - -#include "common.h" - -#include "repository.h" -#include "filebuf.h" -#include "merge.h" -#include "vector.h" -#include "index.h" - -#include "git2/types.h" -#include "git2/merge.h" -#include "git2/cherrypick.h" -#include "git2/commit.h" -#include "git2/sys/commit.h" - -#define GIT_CHERRYPICK_FILE_MODE 0666 - -static int write_cherrypick_head( - git_repository *repo, - const char *commit_oidstr) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - int error = 0; - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_CHERRYPICK_HEAD_FILE)) >= 0 && - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_CHERRYPICK_FILE_MODE)) >= 0 && - (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0) - error = git_filebuf_commit(&file); - - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int write_merge_msg( - git_repository *repo, - const char *commit_msg) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - int error = 0; - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_CHERRYPICK_FILE_MODE)) < 0 || - (error = git_filebuf_printf(&file, "%s", commit_msg)) < 0) - goto cleanup; - - error = git_filebuf_commit(&file); - -cleanup: - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int cherrypick_normalize_opts( - git_repository *repo, - git_cherrypick_options *opts, - const git_cherrypick_options *given, - const char *their_label) -{ - int error = 0; - unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_ALLOW_CONFLICTS; - - GIT_UNUSED(repo); - - if (given != NULL) - memcpy(opts, given, sizeof(git_cherrypick_options)); - else { - git_cherrypick_options default_opts = GIT_CHERRYPICK_OPTIONS_INIT; - memcpy(opts, &default_opts, sizeof(git_cherrypick_options)); - } - - if (!opts->checkout_opts.checkout_strategy) - opts->checkout_opts.checkout_strategy = default_checkout_strategy; - - if (!opts->checkout_opts.our_label) - opts->checkout_opts.our_label = "HEAD"; - - if (!opts->checkout_opts.their_label) - opts->checkout_opts.their_label = their_label; - - return error; -} - -static int cherrypick_state_cleanup(git_repository *repo) -{ - const char *state_files[] = { GIT_CHERRYPICK_HEAD_FILE, GIT_MERGE_MSG_FILE }; - - return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); -} - -static int cherrypick_seterr(git_commit *commit, const char *fmt) -{ - char commit_oidstr[GIT_OID_HEXSZ + 1]; - - git_error_set(GIT_ERROR_CHERRYPICK, fmt, - git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit))); - - return -1; -} - -int git_cherrypick_commit( - git_index **out, - git_repository *repo, - git_commit *cherrypick_commit, - git_commit *our_commit, - unsigned int mainline, - const git_merge_options *merge_opts) -{ - git_commit *parent_commit = NULL; - git_tree *parent_tree = NULL, *our_tree = NULL, *cherrypick_tree = NULL; - int parent = 0, error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(cherrypick_commit); - GIT_ASSERT_ARG(our_commit); - - if (git_commit_parentcount(cherrypick_commit) > 1) { - if (!mainline) - return cherrypick_seterr(cherrypick_commit, - "mainline branch is not specified but %s is a merge commit"); - - parent = mainline; - } else { - if (mainline) - return cherrypick_seterr(cherrypick_commit, - "mainline branch specified but %s is not a merge commit"); - - parent = git_commit_parentcount(cherrypick_commit); - } - - if (parent && - ((error = git_commit_parent(&parent_commit, cherrypick_commit, (parent - 1))) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0)) - goto done; - - if ((error = git_commit_tree(&cherrypick_tree, cherrypick_commit)) < 0 || - (error = git_commit_tree(&our_tree, our_commit)) < 0) - goto done; - - error = git_merge_trees(out, repo, parent_tree, our_tree, cherrypick_tree, merge_opts); - -done: - git_tree_free(parent_tree); - git_tree_free(our_tree); - git_tree_free(cherrypick_tree); - git_commit_free(parent_commit); - - return error; -} - -int git_cherrypick( - git_repository *repo, - git_commit *commit, - const git_cherrypick_options *given_opts) -{ - git_cherrypick_options opts; - git_reference *our_ref = NULL; - git_commit *our_commit = NULL; - char commit_oidstr[GIT_OID_HEXSZ + 1]; - const char *commit_msg, *commit_summary; - git_str their_label = GIT_STR_INIT; - git_index *index = NULL; - git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(commit); - - GIT_ERROR_CHECK_VERSION(given_opts, GIT_CHERRYPICK_OPTIONS_VERSION, "git_cherrypick_options"); - - if ((error = git_repository__ensure_not_bare(repo, "cherry-pick")) < 0) - return error; - - if ((commit_msg = git_commit_message(commit)) == NULL || - (commit_summary = git_commit_summary(commit)) == NULL) { - error = -1; - goto on_error; - } - - git_oid_nfmt(commit_oidstr, sizeof(commit_oidstr), git_commit_id(commit)); - - if ((error = write_merge_msg(repo, commit_msg)) < 0 || - (error = git_str_printf(&their_label, "%.7s... %s", commit_oidstr, commit_summary)) < 0 || - (error = cherrypick_normalize_opts(repo, &opts, given_opts, git_str_cstr(&their_label))) < 0 || - (error = git_indexwriter_init_for_operation(&indexwriter, repo, &opts.checkout_opts.checkout_strategy)) < 0 || - (error = write_cherrypick_head(repo, commit_oidstr)) < 0 || - (error = git_repository_head(&our_ref, repo)) < 0 || - (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJECT_COMMIT)) < 0 || - (error = git_cherrypick_commit(&index, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 || - (error = git_merge__check_result(repo, index)) < 0 || - (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 || - (error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 || - (error = git_indexwriter_commit(&indexwriter)) < 0) - goto on_error; - - goto done; - -on_error: - cherrypick_state_cleanup(repo); - -done: - git_indexwriter_cleanup(&indexwriter); - git_index_free(index); - git_commit_free(our_commit); - git_reference_free(our_ref); - git_str_dispose(&their_label); - - return error; -} - -int git_cherrypick_options_init( - git_cherrypick_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_cherrypick_options, GIT_CHERRYPICK_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_cherrypick_init_options( - git_cherrypick_options *opts, unsigned int version) -{ - return git_cherrypick_options_init(opts, version); -} -#endif diff --git a/src/clone.c b/src/clone.c deleted file mode 100644 index 1843875f8..000000000 --- a/src/clone.c +++ /dev/null @@ -1,655 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "clone.h" - -#include "git2/clone.h" -#include "git2/remote.h" -#include "git2/revparse.h" -#include "git2/branch.h" -#include "git2/config.h" -#include "git2/checkout.h" -#include "git2/commit.h" -#include "git2/tree.h" - -#include "remote.h" -#include "futils.h" -#include "refs.h" -#include "fs_path.h" -#include "repository.h" -#include "odb.h" - -static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link); - -static int create_branch( - git_reference **branch, - git_repository *repo, - const git_oid *target, - const char *name, - const char *log_message) -{ - git_commit *head_obj = NULL; - git_reference *branch_ref = NULL; - git_str refname = GIT_STR_INIT; - int error; - - /* Find the target commit */ - if ((error = git_commit_lookup(&head_obj, repo, target)) < 0) - return error; - - /* Create the new branch */ - if ((error = git_str_printf(&refname, GIT_REFS_HEADS_DIR "%s", name)) < 0) - return error; - - error = git_reference_create(&branch_ref, repo, git_str_cstr(&refname), target, 0, log_message); - git_str_dispose(&refname); - git_commit_free(head_obj); - - if (!error) - *branch = branch_ref; - else - git_reference_free(branch_ref); - - return error; -} - -static int setup_tracking_config( - git_repository *repo, - const char *branch_name, - const char *remote_name, - const char *merge_target) -{ - git_config *cfg; - git_str remote_key = GIT_STR_INIT, merge_key = GIT_STR_INIT; - int error = -1; - - if (git_repository_config__weakptr(&cfg, repo) < 0) - return -1; - - if (git_str_printf(&remote_key, "branch.%s.remote", branch_name) < 0) - goto cleanup; - - if (git_str_printf(&merge_key, "branch.%s.merge", branch_name) < 0) - goto cleanup; - - if (git_config_set_string(cfg, git_str_cstr(&remote_key), remote_name) < 0) - goto cleanup; - - if (git_config_set_string(cfg, git_str_cstr(&merge_key), merge_target) < 0) - goto cleanup; - - error = 0; - -cleanup: - git_str_dispose(&remote_key); - git_str_dispose(&merge_key); - return error; -} - -static int create_tracking_branch( - git_reference **branch, - git_repository *repo, - const git_oid *target, - const char *branch_name, - const char *log_message) -{ - int error; - - if ((error = create_branch(branch, repo, target, branch_name, log_message)) < 0) - return error; - - return setup_tracking_config( - repo, - branch_name, - GIT_REMOTE_ORIGIN, - git_reference_name(*branch)); -} - -static int update_head_to_new_branch( - git_repository *repo, - const git_oid *target, - const char *name, - const char *reflog_message) -{ - git_reference *tracking_branch = NULL; - int error; - - if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) - name += strlen(GIT_REFS_HEADS_DIR); - - error = create_tracking_branch(&tracking_branch, repo, target, name, - reflog_message); - - if (!error) - error = git_repository_set_head( - repo, git_reference_name(tracking_branch)); - - git_reference_free(tracking_branch); - - /* if it already existed, then the user's refspec created it for us, ignore it' */ - if (error == GIT_EEXISTS) - error = 0; - - return error; -} - -static int update_head_to_default(git_repository *repo) -{ - git_str initialbranch = GIT_STR_INIT; - const char *branch_name; - int error = 0; - - if ((error = git_repository_initialbranch(&initialbranch, repo)) < 0) - goto done; - - if (git__prefixcmp(initialbranch.ptr, GIT_REFS_HEADS_DIR) != 0) { - git_error_set(GIT_ERROR_INVALID, "invalid initial branch '%s'", initialbranch.ptr); - error = -1; - goto done; - } - - branch_name = initialbranch.ptr + strlen(GIT_REFS_HEADS_DIR); - - error = setup_tracking_config(repo, branch_name, GIT_REMOTE_ORIGIN, - initialbranch.ptr); - -done: - git_str_dispose(&initialbranch); - return error; -} - -static int update_remote_head( - git_repository *repo, - git_remote *remote, - git_str *target, - const char *reflog_message) -{ - git_refspec *refspec; - git_reference *remote_head = NULL; - git_str remote_head_name = GIT_STR_INIT; - git_str remote_branch_name = GIT_STR_INIT; - int error; - - /* Determine the remote tracking ref name from the local branch */ - refspec = git_remote__matching_refspec(remote, git_str_cstr(target)); - - if (refspec == NULL) { - git_error_set(GIT_ERROR_NET, "the remote's default branch does not fit the refspec configuration"); - error = GIT_EINVALIDSPEC; - goto cleanup; - } - - if ((error = git_refspec__transform( - &remote_branch_name, - refspec, - git_str_cstr(target))) < 0) - goto cleanup; - - if ((error = git_str_printf(&remote_head_name, - "%s%s/%s", - GIT_REFS_REMOTES_DIR, - git_remote_name(remote), - GIT_HEAD_FILE)) < 0) - goto cleanup; - - error = git_reference_symbolic_create( - &remote_head, - repo, - git_str_cstr(&remote_head_name), - git_str_cstr(&remote_branch_name), - true, - reflog_message); - -cleanup: - git_reference_free(remote_head); - git_str_dispose(&remote_branch_name); - git_str_dispose(&remote_head_name); - return error; -} - -static int update_head_to_remote( - git_repository *repo, - git_remote *remote, - const char *reflog_message) -{ - int error = 0; - size_t refs_len; - const git_remote_head *remote_head, **refs; - const git_oid *remote_head_id; - git_str branch = GIT_STR_INIT; - - if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0) - return error; - - /* We cloned an empty repository or one with an unborn HEAD */ - if (refs_len == 0 || strcmp(refs[0]->name, GIT_HEAD_FILE)) - return update_head_to_default(repo); - - /* We know we have HEAD, let's see where it points */ - remote_head = refs[0]; - GIT_ASSERT(remote_head); - - remote_head_id = &remote_head->oid; - - error = git_remote__default_branch(&branch, remote); - if (error == GIT_ENOTFOUND) { - error = git_repository_set_head_detached( - repo, remote_head_id); - goto cleanup; - } - - if ((error = update_remote_head(repo, remote, &branch, reflog_message)) < 0) - goto cleanup; - - error = update_head_to_new_branch( - repo, - remote_head_id, - git_str_cstr(&branch), - reflog_message); - -cleanup: - git_str_dispose(&branch); - - return error; -} - -static int update_head_to_branch( - git_repository *repo, - git_remote *remote, - const char *branch, - const char *reflog_message) -{ - int retcode; - git_str remote_branch_name = GIT_STR_INIT; - git_reference *remote_ref = NULL; - git_str default_branch = GIT_STR_INIT; - - GIT_ASSERT_ARG(remote); - GIT_ASSERT_ARG(branch); - - if ((retcode = git_str_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s", - git_remote_name(remote), branch)) < 0 ) - goto cleanup; - - if ((retcode = git_reference_lookup(&remote_ref, repo, git_str_cstr(&remote_branch_name))) < 0) - goto cleanup; - - if ((retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref), branch, - reflog_message)) < 0) - goto cleanup; - - if ((retcode = git_remote__default_branch(&default_branch, remote)) < 0) - goto cleanup; - - if (!git_remote__matching_refspec(remote, git_str_cstr(&default_branch))) - goto cleanup; - - retcode = update_remote_head(repo, remote, &default_branch, reflog_message); - -cleanup: - git_reference_free(remote_ref); - git_str_dispose(&remote_branch_name); - git_str_dispose(&default_branch); - return retcode; -} - -static int default_repository_create(git_repository **out, const char *path, int bare, void *payload) -{ - GIT_UNUSED(payload); - - return git_repository_init(out, path, bare); -} - -static int default_remote_create( - git_remote **out, - git_repository *repo, - const char *name, - const char *url, - void *payload) -{ - GIT_UNUSED(payload); - - return git_remote_create(out, repo, name, url); -} - -/* - * submodules? - */ - -static int create_and_configure_origin( - git_remote **out, - git_repository *repo, - const char *url, - const git_clone_options *options) -{ - int error; - git_remote *origin = NULL; - char buf[GIT_PATH_MAX]; - git_remote_create_cb remote_create = options->remote_cb; - void *payload = options->remote_cb_payload; - - /* If the path exists and is a dir, the url should be the absolute path */ - if (git_fs_path_root(url) < 0 && git_fs_path_exists(url) && git_fs_path_isdir(url)) { - if (p_realpath(url, buf) == NULL) - return -1; - - url = buf; - } - - if (!remote_create) { - remote_create = default_remote_create; - payload = NULL; - } - - if ((error = remote_create(&origin, repo, "origin", url, payload)) < 0) - goto on_error; - - *out = origin; - return 0; - -on_error: - git_remote_free(origin); - return error; -} - -static bool should_checkout( - git_repository *repo, - bool is_bare, - const git_checkout_options *opts) -{ - if (is_bare) - return false; - - if (!opts) - return false; - - if (opts->checkout_strategy == GIT_CHECKOUT_NONE) - return false; - - return !git_repository_head_unborn(repo); -} - -static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const char *reflog_message) -{ - int error; - - if (branch) - error = update_head_to_branch(repo, remote, branch, reflog_message); - /* Point HEAD to the same ref as the remote's head */ - else - error = update_head_to_remote(repo, remote, reflog_message); - - if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) - error = git_checkout_head(repo, co_opts); - - return error; -} - -static int clone_into(git_repository *repo, git_remote *_remote, const git_fetch_options *opts, const git_checkout_options *co_opts, const char *branch) -{ - int error; - git_str reflog_message = GIT_STR_INIT; - git_fetch_options fetch_opts; - git_remote *remote; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(_remote); - - if (!git_repository_is_empty(repo)) { - git_error_set(GIT_ERROR_INVALID, "the repository is not empty"); - return -1; - } - - if ((error = git_remote_dup(&remote, _remote)) < 0) - return error; - - memcpy(&fetch_opts, opts, sizeof(git_fetch_options)); - fetch_opts.update_fetchhead = 0; - fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; - git_str_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); - - if ((error = git_remote_fetch(remote, NULL, &fetch_opts, git_str_cstr(&reflog_message))) != 0) - goto cleanup; - - error = checkout_branch(repo, remote, co_opts, branch, git_str_cstr(&reflog_message)); - -cleanup: - git_remote_free(remote); - git_str_dispose(&reflog_message); - - return error; -} - -int git_clone__should_clone_local(const char *url_or_path, git_clone_local_t local) -{ - git_str fromurl = GIT_STR_INIT; - const char *path = url_or_path; - bool is_url, is_local; - - if (local == GIT_CLONE_NO_LOCAL) - return 0; - - if ((is_url = git_fs_path_is_local_file_url(url_or_path)) != 0) { - if (git_fs_path_fromurl(&fromurl, url_or_path) < 0) { - is_local = -1; - goto done; - } - - path = fromurl.ptr; - } - - is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) && - git_fs_path_isdir(path); - -done: - git_str_dispose(&fromurl); - return is_local; -} - -static int git__clone( - git_repository **out, - const char *url, - const char *local_path, - const git_clone_options *_options, - int use_existing) -{ - int error = 0; - git_repository *repo = NULL; - git_remote *origin; - git_clone_options options = GIT_CLONE_OPTIONS_INIT; - uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES; - git_repository_create_cb repository_cb; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(url); - GIT_ASSERT_ARG(local_path); - - if (_options) - memcpy(&options, _options, sizeof(git_clone_options)); - - GIT_ERROR_CHECK_VERSION(&options, GIT_CLONE_OPTIONS_VERSION, "git_clone_options"); - - /* Only clone to a new directory or an empty directory */ - if (git_fs_path_exists(local_path) && !use_existing && !git_fs_path_is_empty_dir(local_path)) { - git_error_set(GIT_ERROR_INVALID, - "'%s' exists and is not an empty directory", local_path); - return GIT_EEXISTS; - } - - /* Only remove the root directory on failure if we create it */ - if (git_fs_path_exists(local_path)) - rmdir_flags |= GIT_RMDIR_SKIP_ROOT; - - if (options.repository_cb) - repository_cb = options.repository_cb; - else - repository_cb = default_repository_create; - - if ((error = repository_cb(&repo, local_path, options.bare, options.repository_cb_payload)) < 0) - return error; - - if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { - int clone_local = git_clone__should_clone_local(url, options.local); - int link = options.local != GIT_CLONE_LOCAL_NO_LINKS; - - if (clone_local == 1) - error = clone_local_into( - repo, origin, &options.fetch_opts, &options.checkout_opts, - options.checkout_branch, link); - else if (clone_local == 0) - error = clone_into( - repo, origin, &options.fetch_opts, &options.checkout_opts, - options.checkout_branch); - else - error = -1; - - git_remote_free(origin); - } - - if (error != 0) { - git_error_state last_error = {0}; - git_error_state_capture(&last_error, error); - - git_repository_free(repo); - repo = NULL; - - (void)git_futils_rmdir_r(local_path, NULL, rmdir_flags); - - git_error_state_restore(&last_error); - } - - *out = repo; - return error; -} - -int git_clone( - git_repository **out, - const char *url, - const char *local_path, - const git_clone_options *_options) -{ - return git__clone(out, url, local_path, _options, 0); -} - -int git_clone__submodule( - git_repository **out, - const char *url, - const char *local_path, - const git_clone_options *_options) -{ - return git__clone(out, url, local_path, _options, 1); -} - -int git_clone_options_init(git_clone_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_clone_options, GIT_CLONE_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_clone_init_options(git_clone_options *opts, unsigned int version) -{ - return git_clone_options_init(opts, version); -} -#endif - -static bool can_link(const char *src, const char *dst, int link) -{ -#ifdef GIT_WIN32 - GIT_UNUSED(src); - GIT_UNUSED(dst); - GIT_UNUSED(link); - return false; -#else - - struct stat st_src, st_dst; - - if (!link) - return false; - - if (p_stat(src, &st_src) < 0) - return false; - - if (p_stat(dst, &st_dst) < 0) - return false; - - return st_src.st_dev == st_dst.st_dev; -#endif -} - -static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link) -{ - int error, flags; - git_repository *src; - git_str src_odb = GIT_STR_INIT, dst_odb = GIT_STR_INIT, src_path = GIT_STR_INIT; - git_str reflog_message = GIT_STR_INIT; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(remote); - - if (!git_repository_is_empty(repo)) { - git_error_set(GIT_ERROR_INVALID, "the repository is not empty"); - return -1; - } - - /* - * Let's figure out what path we should use for the source - * repo, if it's not rooted, the path should be relative to - * the repository's worktree/gitdir. - */ - if ((error = git_fs_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0) - return error; - - /* Copy .git/objects/ from the source to the target */ - if ((error = git_repository_open(&src, git_str_cstr(&src_path))) < 0) { - git_str_dispose(&src_path); - return error; - } - - if (git_repository__item_path(&src_odb, src, GIT_REPOSITORY_ITEM_OBJECTS) < 0 || - git_repository__item_path(&dst_odb, repo, GIT_REPOSITORY_ITEM_OBJECTS) < 0) { - error = -1; - goto cleanup; - } - - flags = 0; - if (can_link(git_repository_path(src), git_repository_path(repo), link)) - flags |= GIT_CPDIR_LINK_FILES; - - error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), - flags, GIT_OBJECT_DIR_MODE); - - /* - * can_link() doesn't catch all variations, so if we hit an - * error and did want to link, let's try again without trying - * to link. - */ - if (error < 0 && link) { - flags &= ~GIT_CPDIR_LINK_FILES; - error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), - flags, GIT_OBJECT_DIR_MODE); - } - - if (error < 0) - goto cleanup; - - git_str_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); - - if ((error = git_remote_fetch(remote, NULL, fetch_opts, git_str_cstr(&reflog_message))) != 0) - goto cleanup; - - error = checkout_branch(repo, remote, co_opts, branch, git_str_cstr(&reflog_message)); - -cleanup: - git_str_dispose(&reflog_message); - git_str_dispose(&src_path); - git_str_dispose(&src_odb); - git_str_dispose(&dst_odb); - git_repository_free(src); - return error; -} diff --git a/src/clone.h b/src/clone.h deleted file mode 100644 index 7d73cabd5..000000000 --- a/src/clone.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_clone_h__ -#define INCLUDE_clone_h__ - -#include "common.h" - -#include "git2/clone.h" - -extern int git_clone__submodule(git_repository **out, - const char *url, const char *local_path, - const git_clone_options *_options); - -extern int git_clone__should_clone_local(const char *url, git_clone_local_t local); - -#endif diff --git a/src/commit.c b/src/commit.c deleted file mode 100644 index b137463f3..000000000 --- a/src/commit.c +++ /dev/null @@ -1,1042 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "commit.h" - -#include "git2/common.h" -#include "git2/object.h" -#include "git2/repository.h" -#include "git2/signature.h" -#include "git2/mailmap.h" -#include "git2/sys/commit.h" - -#include "buf.h" -#include "odb.h" -#include "commit.h" -#include "signature.h" -#include "refs.h" -#include "object.h" -#include "array.h" -#include "oidarray.h" - -void git_commit__free(void *_commit) -{ - git_commit *commit = _commit; - - git_array_clear(commit->parent_ids); - - git_signature_free(commit->author); - git_signature_free(commit->committer); - - git__free(commit->raw_header); - git__free(commit->raw_message); - git__free(commit->message_encoding); - git__free(commit->summary); - git__free(commit->body); - - git__free(commit); -} - -static int git_commit__create_buffer_internal( - git_str *out, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_oid *tree, - git_array_oid_t *parents) -{ - size_t i = 0; - const git_oid *parent; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(tree); - - git_oid__writebuf(out, "tree ", tree); - - for (i = 0; i < git_array_size(*parents); i++) { - parent = git_array_get(*parents, i); - git_oid__writebuf(out, "parent ", parent); - } - - git_signature__writebuf(out, "author ", author); - git_signature__writebuf(out, "committer ", committer); - - if (message_encoding != NULL) - git_str_printf(out, "encoding %s\n", message_encoding); - - git_str_putc(out, '\n'); - - if (git_str_puts(out, message) < 0) - goto on_error; - - return 0; - -on_error: - git_str_dispose(out); - return -1; -} - -static int validate_tree_and_parents(git_array_oid_t *parents, git_repository *repo, const git_oid *tree, - git_commit_parent_callback parent_cb, void *parent_payload, - const git_oid *current_id, bool validate) -{ - size_t i; - int error; - git_oid *parent_cpy; - const git_oid *parent; - - if (validate && !git_object__is_valid(repo, tree, GIT_OBJECT_TREE)) - return -1; - - i = 0; - while ((parent = parent_cb(i, parent_payload)) != NULL) { - if (validate && !git_object__is_valid(repo, parent, GIT_OBJECT_COMMIT)) { - error = -1; - goto on_error; - } - - parent_cpy = git_array_alloc(*parents); - GIT_ERROR_CHECK_ALLOC(parent_cpy); - - git_oid_cpy(parent_cpy, parent); - i++; - } - - if (current_id && (parents->size == 0 || git_oid_cmp(current_id, git_array_get(*parents, 0)))) { - git_error_set(GIT_ERROR_OBJECT, "failed to create commit: current tip is not the first parent"); - error = GIT_EMODIFIED; - goto on_error; - } - - return 0; - -on_error: - git_array_clear(*parents); - return error; -} - -static int git_commit__create_internal( - git_oid *id, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_oid *tree, - git_commit_parent_callback parent_cb, - void *parent_payload, - bool validate) -{ - int error; - git_odb *odb; - git_reference *ref = NULL; - git_str buf = GIT_STR_INIT; - const git_oid *current_id = NULL; - git_array_oid_t parents = GIT_ARRAY_INIT; - - if (update_ref) { - error = git_reference_lookup_resolved(&ref, repo, update_ref, 10); - if (error < 0 && error != GIT_ENOTFOUND) - return error; - } - git_error_clear(); - - if (ref) - current_id = git_reference_target(ref); - - if ((error = validate_tree_and_parents(&parents, repo, tree, parent_cb, parent_payload, current_id, validate)) < 0) - goto cleanup; - - error = git_commit__create_buffer_internal(&buf, author, committer, - message_encoding, message, tree, - &parents); - - if (error < 0) - goto cleanup; - - if (git_repository_odb__weakptr(&odb, repo) < 0) - goto cleanup; - - if (git_odb__freshen(odb, tree) < 0) - goto cleanup; - - if (git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJECT_COMMIT) < 0) - goto cleanup; - - - if (update_ref != NULL) { - error = git_reference__update_for_commit( - repo, ref, update_ref, id, "commit"); - goto cleanup; - } - -cleanup: - git_array_clear(parents); - git_reference_free(ref); - git_str_dispose(&buf); - return error; -} - -int git_commit_create_from_callback( - git_oid *id, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_oid *tree, - git_commit_parent_callback parent_cb, - void *parent_payload) -{ - return git_commit__create_internal( - id, repo, update_ref, author, committer, message_encoding, message, - tree, parent_cb, parent_payload, true); -} - -typedef struct { - size_t total; - va_list args; -} commit_parent_varargs; - -static const git_oid *commit_parent_from_varargs(size_t curr, void *payload) -{ - commit_parent_varargs *data = payload; - const git_commit *commit; - if (curr >= data->total) - return NULL; - commit = va_arg(data->args, const git_commit *); - return commit ? git_commit_id(commit) : NULL; -} - -int git_commit_create_v( - git_oid *id, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree, - size_t parent_count, - ...) -{ - int error = 0; - commit_parent_varargs data; - - GIT_ASSERT_ARG(tree); - GIT_ASSERT_ARG(git_tree_owner(tree) == repo); - - data.total = parent_count; - va_start(data.args, parent_count); - - error = git_commit__create_internal( - id, repo, update_ref, author, committer, - message_encoding, message, git_tree_id(tree), - commit_parent_from_varargs, &data, false); - - va_end(data.args); - return error; -} - -typedef struct { - size_t total; - const git_oid **parents; -} commit_parent_oids; - -static const git_oid *commit_parent_from_ids(size_t curr, void *payload) -{ - commit_parent_oids *data = payload; - return (curr < data->total) ? data->parents[curr] : NULL; -} - -int git_commit_create_from_ids( - git_oid *id, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_oid *tree, - size_t parent_count, - const git_oid *parents[]) -{ - commit_parent_oids data = { parent_count, parents }; - - return git_commit__create_internal( - id, repo, update_ref, author, committer, - message_encoding, message, tree, - commit_parent_from_ids, &data, true); -} - -typedef struct { - size_t total; - const git_commit **parents; - git_repository *repo; -} commit_parent_data; - -static const git_oid *commit_parent_from_array(size_t curr, void *payload) -{ - commit_parent_data *data = payload; - const git_commit *commit; - if (curr >= data->total) - return NULL; - commit = data->parents[curr]; - if (git_commit_owner(commit) != data->repo) - return NULL; - return git_commit_id(commit); -} - -int git_commit_create( - git_oid *id, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree, - size_t parent_count, - const git_commit *parents[]) -{ - commit_parent_data data = { parent_count, parents, repo }; - - GIT_ASSERT_ARG(tree); - GIT_ASSERT_ARG(git_tree_owner(tree) == repo); - - return git_commit__create_internal( - id, repo, update_ref, author, committer, - message_encoding, message, git_tree_id(tree), - commit_parent_from_array, &data, false); -} - -static const git_oid *commit_parent_for_amend(size_t curr, void *payload) -{ - const git_commit *commit_to_amend = payload; - if (curr >= git_array_size(commit_to_amend->parent_ids)) - return NULL; - return git_array_get(commit_to_amend->parent_ids, curr); -} - -int git_commit_amend( - git_oid *id, - const git_commit *commit_to_amend, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree) -{ - git_repository *repo; - git_oid tree_id; - git_reference *ref; - int error; - - GIT_ASSERT_ARG(id); - GIT_ASSERT_ARG(commit_to_amend); - - repo = git_commit_owner(commit_to_amend); - - if (!author) - author = git_commit_author(commit_to_amend); - if (!committer) - committer = git_commit_committer(commit_to_amend); - if (!message_encoding) - message_encoding = git_commit_message_encoding(commit_to_amend); - if (!message) - message = git_commit_message(commit_to_amend); - - if (!tree) { - git_tree *old_tree; - GIT_ERROR_CHECK_ERROR( git_commit_tree(&old_tree, commit_to_amend) ); - git_oid_cpy(&tree_id, git_tree_id(old_tree)); - git_tree_free(old_tree); - } else { - GIT_ASSERT_ARG(git_tree_owner(tree) == repo); - git_oid_cpy(&tree_id, git_tree_id(tree)); - } - - if (update_ref) { - if ((error = git_reference_lookup_resolved(&ref, repo, update_ref, 5)) < 0) - return error; - - if (git_oid_cmp(git_commit_id(commit_to_amend), git_reference_target(ref))) { - git_reference_free(ref); - git_error_set(GIT_ERROR_REFERENCE, "commit to amend is not the tip of the given branch"); - return -1; - } - } - - error = git_commit__create_internal( - id, repo, NULL, author, committer, message_encoding, message, - &tree_id, commit_parent_for_amend, (void *)commit_to_amend, false); - - if (!error && update_ref) { - error = git_reference__update_for_commit( - repo, ref, NULL, id, "commit"); - git_reference_free(ref); - } - - return error; -} - -static int commit_parse(git_commit *commit, const char *data, size_t size, unsigned int flags) -{ - const char *buffer_start = data, *buffer; - const char *buffer_end = buffer_start + size; - git_oid parent_id; - size_t header_len; - git_signature dummy_sig; - int error; - - GIT_ASSERT_ARG(commit); - GIT_ASSERT_ARG(data); - - buffer = buffer_start; - - /* Allocate for one, which will allow not to realloc 90% of the time */ - git_array_init_to_size(commit->parent_ids, 1); - GIT_ERROR_CHECK_ARRAY(commit->parent_ids); - - /* The tree is always the first field */ - if (!(flags & GIT_COMMIT_PARSE_QUICK)) { - if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0) - goto bad_buffer; - } else { - size_t tree_len = strlen("tree ") + GIT_OID_HEXSZ + 1; - if (buffer + tree_len > buffer_end) - goto bad_buffer; - buffer += tree_len; - } - - /* - * TODO: commit grafts! - */ - - while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) { - git_oid *new_id = git_array_alloc(commit->parent_ids); - GIT_ERROR_CHECK_ALLOC(new_id); - - git_oid_cpy(new_id, &parent_id); - } - - if (!(flags & GIT_COMMIT_PARSE_QUICK)) { - commit->author = git__malloc(sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(commit->author); - - if ((error = git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n')) < 0) - return error; - } - - /* Some tools create multiple author fields, ignore the extra ones */ - while (!git__prefixncmp(buffer, buffer_end - buffer, "author ")) { - if ((error = git_signature__parse(&dummy_sig, &buffer, buffer_end, "author ", '\n')) < 0) - return error; - - git__free(dummy_sig.name); - git__free(dummy_sig.email); - } - - /* Always parse the committer; we need the commit time */ - commit->committer = git__malloc(sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(commit->committer); - - if ((error = git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n')) < 0) - return error; - - if (flags & GIT_COMMIT_PARSE_QUICK) - return 0; - - /* Parse add'l header entries */ - while (buffer < buffer_end) { - const char *eoln = buffer; - if (buffer[-1] == '\n' && buffer[0] == '\n') - break; - - while (eoln < buffer_end && *eoln != '\n') - ++eoln; - - if (git__prefixncmp(buffer, buffer_end - buffer, "encoding ") == 0) { - buffer += strlen("encoding "); - - commit->message_encoding = git__strndup(buffer, eoln - buffer); - GIT_ERROR_CHECK_ALLOC(commit->message_encoding); - } - - if (eoln < buffer_end && *eoln == '\n') - ++eoln; - buffer = eoln; - } - - header_len = buffer - buffer_start; - commit->raw_header = git__strndup(buffer_start, header_len); - GIT_ERROR_CHECK_ALLOC(commit->raw_header); - - /* point "buffer" to data after header, +1 for the final LF */ - buffer = buffer_start + header_len + 1; - - /* extract commit message */ - if (buffer <= buffer_end) - commit->raw_message = git__strndup(buffer, buffer_end - buffer); - else - commit->raw_message = git__strdup(""); - GIT_ERROR_CHECK_ALLOC(commit->raw_message); - - return 0; - -bad_buffer: - git_error_set(GIT_ERROR_OBJECT, "failed to parse bad commit object"); - return GIT_EINVALID; -} - -int git_commit__parse_raw(void *commit, const char *data, size_t size) -{ - return commit_parse(commit, data, size, 0); -} - -int git_commit__parse_ext(git_commit *commit, git_odb_object *odb_obj, unsigned int flags) -{ - return commit_parse(commit, git_odb_object_data(odb_obj), git_odb_object_size(odb_obj), flags); -} - -int git_commit__parse(void *_commit, git_odb_object *odb_obj) -{ - return git_commit__parse_ext(_commit, odb_obj, 0); -} - -#define GIT_COMMIT_GETTER(_rvalue, _name, _return, _invalid) \ - _rvalue git_commit_##_name(const git_commit *commit) \ - {\ - GIT_ASSERT_ARG_WITH_RETVAL(commit, _invalid); \ - return _return; \ - } - -GIT_COMMIT_GETTER(const git_signature *, author, commit->author, NULL) -GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer, NULL) -GIT_COMMIT_GETTER(const char *, message_raw, commit->raw_message, NULL) -GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding, NULL) -GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header, NULL) -GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time, INT64_MIN) -GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset, -1) -GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids), 0) -GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id, NULL) - -const char *git_commit_message(const git_commit *commit) -{ - const char *message; - - GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); - - message = commit->raw_message; - - /* trim leading newlines from raw message */ - while (*message && *message == '\n') - ++message; - - return message; -} - -const char *git_commit_summary(git_commit *commit) -{ - git_str summary = GIT_STR_INIT; - const char *msg, *space, *next; - bool space_contains_newline = false; - - GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); - - if (!commit->summary) { - for (msg = git_commit_message(commit), space = NULL; *msg; ++msg) { - char next_character = msg[0]; - /* stop processing at the end of the first paragraph */ - if (next_character == '\n') { - if (!msg[1]) - break; - if (msg[1] == '\n') - break; - /* stop processing if next line contains only whitespace */ - next = msg + 1; - while (*next && git__isspace_nonlf(*next)) { - ++next; - } - if (!*next || *next == '\n') - break; - } - /* record the beginning of contiguous whitespace runs */ - if (git__isspace(next_character)) { - if(space == NULL) { - space = msg; - space_contains_newline = false; - } - space_contains_newline |= next_character == '\n'; - } - /* the next character is non-space */ - else { - /* process any recorded whitespace */ - if (space) { - if(space_contains_newline) - git_str_putc(&summary, ' '); /* if the space contains a newline, collapse to ' ' */ - else - git_str_put(&summary, space, (msg - space)); /* otherwise copy it */ - space = NULL; - } - /* copy the next character */ - git_str_putc(&summary, next_character); - } - } - - commit->summary = git_str_detach(&summary); - if (!commit->summary) - commit->summary = git__strdup(""); - } - - return commit->summary; -} - -const char *git_commit_body(git_commit *commit) -{ - const char *msg, *end; - - GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); - - if (!commit->body) { - /* search for end of summary */ - for (msg = git_commit_message(commit); *msg; ++msg) - if (msg[0] == '\n' && (!msg[1] || msg[1] == '\n')) - break; - - /* trim leading and trailing whitespace */ - for (; *msg; ++msg) - if (!git__isspace(*msg)) - break; - for (end = msg + strlen(msg) - 1; msg <= end; --end) - if (!git__isspace(*end)) - break; - - if (*msg) - commit->body = git__strndup(msg, end - msg + 1); - } - - return commit->body; -} - -int git_commit_tree(git_tree **tree_out, const git_commit *commit) -{ - GIT_ASSERT_ARG(commit); - return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id); -} - -const git_oid *git_commit_parent_id( - const git_commit *commit, unsigned int n) -{ - GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); - - return git_array_get(commit->parent_ids, n); -} - -int git_commit_parent( - git_commit **parent, const git_commit *commit, unsigned int n) -{ - const git_oid *parent_id; - GIT_ASSERT_ARG(commit); - - parent_id = git_commit_parent_id(commit, n); - if (parent_id == NULL) { - git_error_set(GIT_ERROR_INVALID, "parent %u does not exist", n); - return GIT_ENOTFOUND; - } - - return git_commit_lookup(parent, commit->object.repo, parent_id); -} - -int git_commit_nth_gen_ancestor( - git_commit **ancestor, - const git_commit *commit, - unsigned int n) -{ - git_commit *current, *parent = NULL; - int error; - - GIT_ASSERT_ARG(ancestor); - GIT_ASSERT_ARG(commit); - - if (git_commit_dup(¤t, (git_commit *)commit) < 0) - return -1; - - if (n == 0) { - *ancestor = current; - return 0; - } - - while (n--) { - error = git_commit_parent(&parent, current, 0); - - git_commit_free(current); - - if (error < 0) - return error; - - current = parent; - } - - *ancestor = parent; - return 0; -} - -int git_commit_header_field( - git_buf *out, - const git_commit *commit, - const char *field) -{ - GIT_BUF_WRAP_PRIVATE(out, git_commit__header_field, commit, field); -} - -int git_commit__header_field( - git_str *out, - const git_commit *commit, - const char *field) -{ - const char *eol, *buf = commit->raw_header; - - git_str_clear(out); - - while ((eol = strchr(buf, '\n'))) { - /* We can skip continuations here */ - if (buf[0] == ' ') { - buf = eol + 1; - continue; - } - - /* Skip until we find the field we're after */ - if (git__prefixcmp(buf, field)) { - buf = eol + 1; - continue; - } - - buf += strlen(field); - /* Check that we're not matching a prefix but the field itself */ - if (buf[0] != ' ') { - buf = eol + 1; - continue; - } - - buf++; /* skip the SP */ - - git_str_put(out, buf, eol - buf); - if (git_str_oom(out)) - goto oom; - - /* If the next line starts with SP, it's multi-line, we must continue */ - while (eol[1] == ' ') { - git_str_putc(out, '\n'); - buf = eol + 2; - eol = strchr(buf, '\n'); - if (!eol) - goto malformed; - - git_str_put(out, buf, eol - buf); - } - - if (git_str_oom(out)) - goto oom; - - return 0; - } - - git_error_set(GIT_ERROR_OBJECT, "no such field '%s'", field); - return GIT_ENOTFOUND; - -malformed: - git_error_set(GIT_ERROR_OBJECT, "malformed header"); - return -1; -oom: - git_error_set_oom(); - return -1; -} - -int git_commit_extract_signature( - git_buf *signature_out, - git_buf *signed_data_out, - git_repository *repo, - git_oid *commit_id, - const char *field) -{ - git_str signature = GIT_STR_INIT, signed_data = GIT_STR_INIT; - int error; - - if ((error = git_buf_tostr(&signature, signature_out)) < 0 || - (error = git_buf_tostr(&signed_data, signed_data_out)) < 0 || - (error = git_commit__extract_signature(&signature, &signed_data, repo, commit_id, field)) < 0 || - (error = git_buf_fromstr(signature_out, &signature)) < 0 || - (error = git_buf_fromstr(signed_data_out, &signed_data)) < 0) - goto done; - -done: - git_str_dispose(&signature); - git_str_dispose(&signed_data); - return error; -} - -int git_commit__extract_signature( - git_str *signature, - git_str *signed_data, - git_repository *repo, - git_oid *commit_id, - const char *field) -{ - git_odb_object *obj; - git_odb *odb; - const char *buf; - const char *h, *eol; - int error; - - git_str_clear(signature); - git_str_clear(signed_data); - - if (!field) - field = "gpgsig"; - - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) - return error; - - if ((error = git_odb_read(&obj, odb, commit_id)) < 0) - return error; - - if (obj->cached.type != GIT_OBJECT_COMMIT) { - git_error_set(GIT_ERROR_INVALID, "the requested type does not match the type in the ODB"); - error = GIT_ENOTFOUND; - goto cleanup; - } - - buf = git_odb_object_data(obj); - - while ((h = strchr(buf, '\n')) && h[1] != '\0') { - h++; - if (git__prefixcmp(buf, field)) { - if (git_str_put(signed_data, buf, h - buf) < 0) - return -1; - - buf = h; - continue; - } - - h = buf; - h += strlen(field); - eol = strchr(h, '\n'); - if (h[0] != ' ') { - buf = h; - continue; - } - if (!eol) - goto malformed; - - h++; /* skip the SP */ - - git_str_put(signature, h, eol - h); - if (git_str_oom(signature)) - goto oom; - - /* If the next line starts with SP, it's multi-line, we must continue */ - while (eol[1] == ' ') { - git_str_putc(signature, '\n'); - h = eol + 2; - eol = strchr(h, '\n'); - if (!eol) - goto malformed; - - git_str_put(signature, h, eol - h); - } - - if (git_str_oom(signature)) - goto oom; - - error = git_str_puts(signed_data, eol+1); - git_odb_object_free(obj); - return error; - } - - git_error_set(GIT_ERROR_OBJECT, "this commit is not signed"); - error = GIT_ENOTFOUND; - goto cleanup; - -malformed: - git_error_set(GIT_ERROR_OBJECT, "malformed header"); - error = -1; - goto cleanup; -oom: - git_error_set_oom(); - error = -1; - goto cleanup; - -cleanup: - git_odb_object_free(obj); - git_str_clear(signature); - git_str_clear(signed_data); - return error; -} - -int git_commit_create_buffer( - git_buf *out, - git_repository *repo, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree, - size_t parent_count, - const git_commit *parents[]) -{ - GIT_BUF_WRAP_PRIVATE(out, git_commit__create_buffer, repo, - author, committer, message_encoding, message, - tree, parent_count, parents); -} - -int git_commit__create_buffer( - git_str *out, - git_repository *repo, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree, - size_t parent_count, - const git_commit *parents[]) -{ - int error; - commit_parent_data data = { parent_count, parents, repo }; - git_array_oid_t parents_arr = GIT_ARRAY_INIT; - const git_oid *tree_id; - - GIT_ASSERT_ARG(tree); - GIT_ASSERT_ARG(git_tree_owner(tree) == repo); - - tree_id = git_tree_id(tree); - - if ((error = validate_tree_and_parents(&parents_arr, repo, tree_id, commit_parent_from_array, &data, NULL, true)) < 0) - return error; - - error = git_commit__create_buffer_internal( - out, author, committer, - message_encoding, message, tree_id, - &parents_arr); - - git_array_clear(parents_arr); - return error; -} - -/** - * Append to 'out' properly marking continuations when there's a newline in 'content' - */ -static int format_header_field(git_str *out, const char *field, const char *content) -{ - const char *lf; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(field); - GIT_ASSERT_ARG(content); - - git_str_puts(out, field); - git_str_putc(out, ' '); - - while ((lf = strchr(content, '\n')) != NULL) { - git_str_put(out, content, lf - content); - git_str_puts(out, "\n "); - content = lf + 1; - } - - git_str_puts(out, content); - git_str_putc(out, '\n'); - - return git_str_oom(out) ? -1 : 0; -} - -static const git_oid *commit_parent_from_commit(size_t n, void *payload) -{ - const git_commit *commit = (const git_commit *) payload; - - return git_array_get(commit->parent_ids, n); - -} - -int git_commit_create_with_signature( - git_oid *out, - git_repository *repo, - const char *commit_content, - const char *signature, - const char *signature_field) -{ - git_odb *odb; - int error = 0; - const char *field; - const char *header_end; - git_str commit = GIT_STR_INIT; - git_commit *parsed; - git_array_oid_t parents = GIT_ARRAY_INIT; - - /* The first step is to verify that all the tree and parents exist */ - parsed = git__calloc(1, sizeof(git_commit)); - GIT_ERROR_CHECK_ALLOC(parsed); - if (commit_parse(parsed, commit_content, strlen(commit_content), 0) < 0) { - error = -1; - goto cleanup; - } - - if ((error = validate_tree_and_parents(&parents, repo, &parsed->tree_id, commit_parent_from_commit, parsed, NULL, true)) < 0) - goto cleanup; - - git_array_clear(parents); - - /* Then we start appending by identifying the end of the commit header */ - header_end = strstr(commit_content, "\n\n"); - if (!header_end) { - git_error_set(GIT_ERROR_INVALID, "malformed commit contents"); - error = -1; - goto cleanup; - } - - /* The header ends after the first LF */ - header_end++; - git_str_put(&commit, commit_content, header_end - commit_content); - - if (signature != NULL) { - field = signature_field ? signature_field : "gpgsig"; - - if ((error = format_header_field(&commit, field, signature)) < 0) - goto cleanup; - } - - git_str_puts(&commit, header_end); - - if (git_str_oom(&commit)) - return -1; - - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) - goto cleanup; - - if ((error = git_odb_write(out, odb, commit.ptr, commit.size, GIT_OBJECT_COMMIT)) < 0) - goto cleanup; - -cleanup: - git_commit__free(parsed); - git_str_dispose(&commit); - return error; -} - -int git_commit_committer_with_mailmap( - git_signature **out, const git_commit *commit, const git_mailmap *mailmap) -{ - return git_mailmap_resolve_signature(out, mailmap, commit->committer); -} - -int git_commit_author_with_mailmap( - git_signature **out, const git_commit *commit, const git_mailmap *mailmap) -{ - return git_mailmap_resolve_signature(out, mailmap, commit->author); -} diff --git a/src/commit.h b/src/commit.h deleted file mode 100644 index 7a2454e61..000000000 --- a/src/commit.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_commit_h__ -#define INCLUDE_commit_h__ - -#include "common.h" - -#include "git2/commit.h" -#include "tree.h" -#include "repository.h" -#include "array.h" - -#include - -struct git_commit { - git_object object; - - git_array_t(git_oid) parent_ids; - git_oid tree_id; - - git_signature *author; - git_signature *committer; - - char *message_encoding; - char *raw_message; - char *raw_header; - - char *summary; - char *body; -}; - -int git_commit__header_field( - git_str *out, - const git_commit *commit, - const char *field); - -int git_commit__extract_signature( - git_str *signature, - git_str *signed_data, - git_repository *repo, - git_oid *commit_id, - const char *field); - -int git_commit__create_buffer( - git_str *out, - git_repository *repo, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree, - size_t parent_count, - const git_commit *parents[]); - -void git_commit__free(void *commit); -int git_commit__parse(void *commit, git_odb_object *obj); -int git_commit__parse_raw(void *commit, const char *data, size_t size); - -typedef enum { - GIT_COMMIT_PARSE_QUICK = (1 << 0) /**< Only parse parents and committer info */ -} git_commit__parse_flags; - -int git_commit__parse_ext(git_commit *commit, git_odb_object *odb_obj, unsigned int flags); - -#endif diff --git a/src/commit_graph.c b/src/commit_graph.c deleted file mode 100644 index 70e866b92..000000000 --- a/src/commit_graph.c +++ /dev/null @@ -1,1224 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "commit_graph.h" - -#include "array.h" -#include "buf.h" -#include "filebuf.h" -#include "futils.h" -#include "hash.h" -#include "oidarray.h" -#include "oidmap.h" -#include "pack.h" -#include "repository.h" -#include "revwalk.h" - -#define GIT_COMMIT_GRAPH_MISSING_PARENT 0x70000000 -#define GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX 0x3FFFFFFF -#define GIT_COMMIT_GRAPH_GENERATION_NUMBER_INFINITY 0xFFFFFFFF - -#define COMMIT_GRAPH_SIGNATURE 0x43475048 /* "CGPH" */ -#define COMMIT_GRAPH_VERSION 1 -#define COMMIT_GRAPH_OBJECT_ID_VERSION 1 -struct git_commit_graph_header { - uint32_t signature; - uint8_t version; - uint8_t object_id_version; - uint8_t chunks; - uint8_t base_graph_files; -}; - -#define COMMIT_GRAPH_OID_FANOUT_ID 0x4f494446 /* "OIDF" */ -#define COMMIT_GRAPH_OID_LOOKUP_ID 0x4f49444c /* "OIDL" */ -#define COMMIT_GRAPH_COMMIT_DATA_ID 0x43444154 /* "CDAT" */ -#define COMMIT_GRAPH_EXTRA_EDGE_LIST_ID 0x45444745 /* "EDGE" */ -#define COMMIT_GRAPH_BLOOM_FILTER_INDEX_ID 0x42494458 /* "BIDX" */ -#define COMMIT_GRAPH_BLOOM_FILTER_DATA_ID 0x42444154 /* "BDAT" */ - -struct git_commit_graph_chunk { - off64_t offset; - size_t length; -}; - -typedef git_array_t(size_t) parent_index_array_t; - -struct packed_commit { - size_t index; - git_oid sha1; - git_oid tree_oid; - uint32_t generation; - git_time_t commit_time; - git_array_oid_t parents; - parent_index_array_t parent_indices; -}; - -static void packed_commit_free(struct packed_commit *p) -{ - if (!p) - return; - - git_array_clear(p->parents); - git_array_clear(p->parent_indices); - git__free(p); -} - -static struct packed_commit *packed_commit_new(git_commit *commit) -{ - unsigned int i, parentcount = git_commit_parentcount(commit); - struct packed_commit *p = git__calloc(1, sizeof(struct packed_commit)); - if (!p) - goto cleanup; - - git_array_init_to_size(p->parents, parentcount); - if (parentcount && !p->parents.ptr) - goto cleanup; - - if (git_oid_cpy(&p->sha1, git_commit_id(commit)) < 0) - goto cleanup; - if (git_oid_cpy(&p->tree_oid, git_commit_tree_id(commit)) < 0) - goto cleanup; - p->commit_time = git_commit_time(commit); - - for (i = 0; i < parentcount; ++i) { - git_oid *parent_id = git_array_alloc(p->parents); - if (!parent_id) - goto cleanup; - if (git_oid_cpy(parent_id, git_commit_parent_id(commit, i)) < 0) - goto cleanup; - } - - return p; - -cleanup: - packed_commit_free(p); - return NULL; -} - -typedef int (*commit_graph_write_cb)(const char *buf, size_t size, void *cb_data); - -static int commit_graph_error(const char *message) -{ - git_error_set(GIT_ERROR_ODB, "invalid commit-graph file - %s", message); - return -1; -} - -static int commit_graph_parse_oid_fanout( - git_commit_graph_file *file, - const unsigned char *data, - struct git_commit_graph_chunk *chunk_oid_fanout) -{ - uint32_t i, nr; - if (chunk_oid_fanout->offset == 0) - return commit_graph_error("missing OID Fanout chunk"); - if (chunk_oid_fanout->length == 0) - return commit_graph_error("empty OID Fanout chunk"); - if (chunk_oid_fanout->length != 256 * 4) - return commit_graph_error("OID Fanout chunk has wrong length"); - - file->oid_fanout = (const uint32_t *)(data + chunk_oid_fanout->offset); - nr = 0; - for (i = 0; i < 256; ++i) { - uint32_t n = ntohl(file->oid_fanout[i]); - if (n < nr) - return commit_graph_error("index is non-monotonic"); - nr = n; - } - file->num_commits = nr; - return 0; -} - -static int commit_graph_parse_oid_lookup( - git_commit_graph_file *file, - const unsigned char *data, - struct git_commit_graph_chunk *chunk_oid_lookup) -{ - uint32_t i; - git_oid *oid, *prev_oid, zero_oid = {{0}}; - - if (chunk_oid_lookup->offset == 0) - return commit_graph_error("missing OID Lookup chunk"); - if (chunk_oid_lookup->length == 0) - return commit_graph_error("empty OID Lookup chunk"); - if (chunk_oid_lookup->length != file->num_commits * GIT_OID_RAWSZ) - return commit_graph_error("OID Lookup chunk has wrong length"); - - file->oid_lookup = oid = (git_oid *)(data + chunk_oid_lookup->offset); - prev_oid = &zero_oid; - for (i = 0; i < file->num_commits; ++i, ++oid) { - if (git_oid_cmp(prev_oid, oid) >= 0) - return commit_graph_error("OID Lookup index is non-monotonic"); - prev_oid = oid; - } - - return 0; -} - -static int commit_graph_parse_commit_data( - git_commit_graph_file *file, - const unsigned char *data, - struct git_commit_graph_chunk *chunk_commit_data) -{ - if (chunk_commit_data->offset == 0) - return commit_graph_error("missing Commit Data chunk"); - if (chunk_commit_data->length == 0) - return commit_graph_error("empty Commit Data chunk"); - if (chunk_commit_data->length != file->num_commits * (GIT_OID_RAWSZ + 16)) - return commit_graph_error("Commit Data chunk has wrong length"); - - file->commit_data = data + chunk_commit_data->offset; - - return 0; -} - -static int commit_graph_parse_extra_edge_list( - git_commit_graph_file *file, - const unsigned char *data, - struct git_commit_graph_chunk *chunk_extra_edge_list) -{ - if (chunk_extra_edge_list->length == 0) - return 0; - if (chunk_extra_edge_list->length % 4 != 0) - return commit_graph_error("malformed Extra Edge List chunk"); - - file->extra_edge_list = data + chunk_extra_edge_list->offset; - file->num_extra_edge_list = chunk_extra_edge_list->length / 4; - - return 0; -} - -int git_commit_graph_file_parse( - git_commit_graph_file *file, - const unsigned char *data, - size_t size) -{ - struct git_commit_graph_header *hdr; - const unsigned char *chunk_hdr; - struct git_commit_graph_chunk *last_chunk; - uint32_t i; - off64_t last_chunk_offset, chunk_offset, trailer_offset; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size; - int error; - struct git_commit_graph_chunk chunk_oid_fanout = {0}, chunk_oid_lookup = {0}, - chunk_commit_data = {0}, chunk_extra_edge_list = {0}, - chunk_unsupported = {0}; - - GIT_ASSERT_ARG(file); - - if (size < sizeof(struct git_commit_graph_header) + GIT_OID_RAWSZ) - return commit_graph_error("commit-graph is too short"); - - hdr = ((struct git_commit_graph_header *)data); - - if (hdr->signature != htonl(COMMIT_GRAPH_SIGNATURE) || hdr->version != COMMIT_GRAPH_VERSION - || hdr->object_id_version != COMMIT_GRAPH_OBJECT_ID_VERSION) { - return commit_graph_error("unsupported commit-graph version"); - } - if (hdr->chunks == 0) - return commit_graph_error("no chunks in commit-graph"); - - /* - * The very first chunk's offset should be after the header, all the chunk - * headers, and a special zero chunk. - */ - last_chunk_offset = sizeof(struct git_commit_graph_header) + (1 + hdr->chunks) * 12; - trailer_offset = size - GIT_OID_RAWSZ; - checksum_size = GIT_HASH_SHA1_SIZE; - - if (trailer_offset < last_chunk_offset) - return commit_graph_error("wrong commit-graph size"); - memcpy(file->checksum, (data + trailer_offset), checksum_size); - - if (git_hash_buf(checksum, data, (size_t)trailer_offset, GIT_HASH_ALGORITHM_SHA1) < 0) - return commit_graph_error("could not calculate signature"); - if (memcmp(checksum, file->checksum, checksum_size) != 0) - return commit_graph_error("index signature mismatch"); - - chunk_hdr = data + sizeof(struct git_commit_graph_header); - last_chunk = NULL; - for (i = 0; i < hdr->chunks; ++i, chunk_hdr += 12) { - chunk_offset = ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 4)))) << 32 - | ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 8)))); - if (chunk_offset < last_chunk_offset) - return commit_graph_error("chunks are non-monotonic"); - if (chunk_offset >= trailer_offset) - return commit_graph_error("chunks extend beyond the trailer"); - if (last_chunk != NULL) - last_chunk->length = (size_t)(chunk_offset - last_chunk_offset); - last_chunk_offset = chunk_offset; - - switch (ntohl(*((uint32_t *)(chunk_hdr + 0)))) { - case COMMIT_GRAPH_OID_FANOUT_ID: - chunk_oid_fanout.offset = last_chunk_offset; - last_chunk = &chunk_oid_fanout; - break; - - case COMMIT_GRAPH_OID_LOOKUP_ID: - chunk_oid_lookup.offset = last_chunk_offset; - last_chunk = &chunk_oid_lookup; - break; - - case COMMIT_GRAPH_COMMIT_DATA_ID: - chunk_commit_data.offset = last_chunk_offset; - last_chunk = &chunk_commit_data; - break; - - case COMMIT_GRAPH_EXTRA_EDGE_LIST_ID: - chunk_extra_edge_list.offset = last_chunk_offset; - last_chunk = &chunk_extra_edge_list; - break; - - case COMMIT_GRAPH_BLOOM_FILTER_INDEX_ID: - case COMMIT_GRAPH_BLOOM_FILTER_DATA_ID: - chunk_unsupported.offset = last_chunk_offset; - last_chunk = &chunk_unsupported; - break; - - default: - return commit_graph_error("unrecognized chunk ID"); - } - } - last_chunk->length = (size_t)(trailer_offset - last_chunk_offset); - - error = commit_graph_parse_oid_fanout(file, data, &chunk_oid_fanout); - if (error < 0) - return error; - error = commit_graph_parse_oid_lookup(file, data, &chunk_oid_lookup); - if (error < 0) - return error; - error = commit_graph_parse_commit_data(file, data, &chunk_commit_data); - if (error < 0) - return error; - error = commit_graph_parse_extra_edge_list(file, data, &chunk_extra_edge_list); - if (error < 0) - return error; - - return 0; -} - -int git_commit_graph_new(git_commit_graph **cgraph_out, const char *objects_dir, bool open_file) -{ - git_commit_graph *cgraph = NULL; - int error = 0; - - GIT_ASSERT_ARG(cgraph_out); - GIT_ASSERT_ARG(objects_dir); - - cgraph = git__calloc(1, sizeof(git_commit_graph)); - GIT_ERROR_CHECK_ALLOC(cgraph); - - error = git_str_joinpath(&cgraph->filename, objects_dir, "info/commit-graph"); - if (error < 0) - goto error; - - if (open_file) { - error = git_commit_graph_file_open(&cgraph->file, git_str_cstr(&cgraph->filename)); - if (error < 0) - goto error; - cgraph->checked = 1; - } - - *cgraph_out = cgraph; - return 0; - -error: - git_commit_graph_free(cgraph); - return error; -} - -int git_commit_graph_open(git_commit_graph **cgraph_out, const char *objects_dir) -{ - return git_commit_graph_new(cgraph_out, objects_dir, true); -} - -int git_commit_graph_file_open(git_commit_graph_file **file_out, const char *path) -{ - git_commit_graph_file *file; - git_file fd = -1; - size_t cgraph_size; - struct stat st; - int error; - - /* TODO: properly open the file without access time using O_NOATIME */ - fd = git_futils_open_ro(path); - if (fd < 0) - return fd; - - if (p_fstat(fd, &st) < 0) { - p_close(fd); - git_error_set(GIT_ERROR_ODB, "commit-graph file not found - '%s'", path); - return GIT_ENOTFOUND; - } - - if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size)) { - p_close(fd); - git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path); - return GIT_ENOTFOUND; - } - cgraph_size = (size_t)st.st_size; - - file = git__calloc(1, sizeof(git_commit_graph_file)); - GIT_ERROR_CHECK_ALLOC(file); - - error = git_futils_mmap_ro(&file->graph_map, fd, 0, cgraph_size); - p_close(fd); - if (error < 0) { - git_commit_graph_file_free(file); - return error; - } - - if ((error = git_commit_graph_file_parse(file, file->graph_map.data, cgraph_size)) < 0) { - git_commit_graph_file_free(file); - return error; - } - - *file_out = file; - return 0; -} - -int git_commit_graph_get_file(git_commit_graph_file **file_out, git_commit_graph *cgraph) -{ - if (!cgraph->checked) { - int error = 0; - git_commit_graph_file *result = NULL; - - /* We only check once, no matter the result. */ - cgraph->checked = 1; - - /* Best effort */ - error = git_commit_graph_file_open(&result, git_str_cstr(&cgraph->filename)); - - if (error < 0) - return error; - - cgraph->file = result; - } - if (!cgraph->file) - return GIT_ENOTFOUND; - - *file_out = cgraph->file; - return 0; -} - -void git_commit_graph_refresh(git_commit_graph *cgraph) -{ - if (!cgraph->checked) - return; - - if (cgraph->file - && git_commit_graph_file_needs_refresh(cgraph->file, git_str_cstr(&cgraph->filename))) { - /* We just free the commit graph. The next time it is requested, it will be - * re-loaded. */ - git_commit_graph_file_free(cgraph->file); - cgraph->file = NULL; - } - /* Force a lazy re-check next time it is needed. */ - cgraph->checked = 0; -} - -static int git_commit_graph_entry_get_byindex( - git_commit_graph_entry *e, - const git_commit_graph_file *file, - size_t pos) -{ - const unsigned char *commit_data; - - GIT_ASSERT_ARG(e); - GIT_ASSERT_ARG(file); - - if (pos >= file->num_commits) { - git_error_set(GIT_ERROR_INVALID, "commit index %zu does not exist", pos); - return GIT_ENOTFOUND; - } - - commit_data = file->commit_data + pos * (GIT_OID_RAWSZ + 4 * sizeof(uint32_t)); - git_oid_cpy(&e->tree_oid, (const git_oid *)commit_data); - e->parent_indices[0] = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ))); - e->parent_indices[1] = ntohl( - *((uint32_t *)(commit_data + GIT_OID_RAWSZ + sizeof(uint32_t)))); - e->parent_count = (e->parent_indices[0] != GIT_COMMIT_GRAPH_MISSING_PARENT) - + (e->parent_indices[1] != GIT_COMMIT_GRAPH_MISSING_PARENT); - e->generation = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ + 2 * sizeof(uint32_t)))); - e->commit_time = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ + 3 * sizeof(uint32_t)))); - - e->commit_time |= (e->generation & UINT64_C(0x3)) << UINT64_C(32); - e->generation >>= 2u; - if (e->parent_indices[1] & 0x80000000u) { - uint32_t extra_edge_list_pos = e->parent_indices[1] & 0x7fffffff; - - /* Make sure we're not being sent out of bounds */ - if (extra_edge_list_pos >= file->num_extra_edge_list) { - git_error_set(GIT_ERROR_INVALID, - "commit %u does not exist", - extra_edge_list_pos); - return GIT_ENOTFOUND; - } - - e->extra_parents_index = extra_edge_list_pos; - while (extra_edge_list_pos < file->num_extra_edge_list - && (ntohl(*( - (uint32_t *)(file->extra_edge_list - + extra_edge_list_pos * sizeof(uint32_t)))) - & 0x80000000u) - == 0) { - extra_edge_list_pos++; - e->parent_count++; - } - } - git_oid_cpy(&e->sha1, &file->oid_lookup[pos]); - return 0; -} - -bool git_commit_graph_file_needs_refresh(const git_commit_graph_file *file, const char *path) -{ - git_file fd = -1; - struct stat st; - ssize_t bytes_read; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - - /* TODO: properly open the file without access time using O_NOATIME */ - fd = git_futils_open_ro(path); - if (fd < 0) - return true; - - if (p_fstat(fd, &st) < 0) { - p_close(fd); - return true; - } - - if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size) - || (size_t)st.st_size != file->graph_map.len) { - p_close(fd); - return true; - } - - bytes_read = p_pread(fd, checksum, checksum_size, st.st_size - checksum_size); - p_close(fd); - if (bytes_read != (ssize_t)checksum_size) - return true; - - return (memcmp(checksum, file->checksum, checksum_size) != 0); -} - -int git_commit_graph_entry_find( - git_commit_graph_entry *e, - const git_commit_graph_file *file, - const git_oid *short_oid, - size_t len) -{ - int pos, found = 0; - uint32_t hi, lo; - const git_oid *current = NULL; - - GIT_ASSERT_ARG(e); - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(short_oid); - - hi = ntohl(file->oid_fanout[(int)short_oid->id[0]]); - lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(file->oid_fanout[(int)short_oid->id[0] - 1])); - - pos = git_pack__lookup_sha1(file->oid_lookup, GIT_OID_RAWSZ, lo, hi, short_oid->id); - - if (pos >= 0) { - /* An object matching exactly the oid was found */ - found = 1; - current = file->oid_lookup + pos; - } else { - /* No object was found */ - /* pos refers to the object with the "closest" oid to short_oid */ - pos = -1 - pos; - if (pos < (int)file->num_commits) { - current = file->oid_lookup + pos; - - if (!git_oid_ncmp(short_oid, current, len)) - found = 1; - } - } - - if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)file->num_commits) { - /* Check for ambiguousity */ - const git_oid *next = current + 1; - - if (!git_oid_ncmp(short_oid, next, len)) { - found = 2; - } - } - - if (!found) - return git_odb__error_notfound( - "failed to find offset for commit-graph index entry", short_oid, len); - if (found > 1) - return git_odb__error_ambiguous( - "found multiple offsets for commit-graph index entry"); - - return git_commit_graph_entry_get_byindex(e, file, pos); -} - -int git_commit_graph_entry_parent( - git_commit_graph_entry *parent, - const git_commit_graph_file *file, - const git_commit_graph_entry *entry, - size_t n) -{ - GIT_ASSERT_ARG(parent); - GIT_ASSERT_ARG(file); - - if (n >= entry->parent_count) { - git_error_set(GIT_ERROR_INVALID, "parent index %zu does not exist", n); - return GIT_ENOTFOUND; - } - - if (n == 0 || (n == 1 && entry->parent_count == 2)) - return git_commit_graph_entry_get_byindex(parent, file, entry->parent_indices[n]); - - return git_commit_graph_entry_get_byindex( - parent, - file, - ntohl( - *(uint32_t *)(file->extra_edge_list - + (entry->extra_parents_index + n - 1) - * sizeof(uint32_t))) - & 0x7fffffff); -} - -int git_commit_graph_file_close(git_commit_graph_file *file) -{ - GIT_ASSERT_ARG(file); - - if (file->graph_map.data) - git_futils_mmap_free(&file->graph_map); - - return 0; -} - -void git_commit_graph_free(git_commit_graph *cgraph) -{ - if (!cgraph) - return; - - git_str_dispose(&cgraph->filename); - git_commit_graph_file_free(cgraph->file); - git__free(cgraph); -} - -void git_commit_graph_file_free(git_commit_graph_file *file) -{ - if (!file) - return; - - git_commit_graph_file_close(file); - git__free(file); -} - -static int packed_commit__cmp(const void *a_, const void *b_) -{ - const struct packed_commit *a = a_; - const struct packed_commit *b = b_; - return git_oid_cmp(&a->sha1, &b->sha1); -} - -int git_commit_graph_writer_new(git_commit_graph_writer **out, const char *objects_info_dir) -{ - git_commit_graph_writer *w = git__calloc(1, sizeof(git_commit_graph_writer)); - GIT_ERROR_CHECK_ALLOC(w); - - if (git_str_sets(&w->objects_info_dir, objects_info_dir) < 0) { - git__free(w); - return -1; - } - - if (git_vector_init(&w->commits, 0, packed_commit__cmp) < 0) { - git_str_dispose(&w->objects_info_dir); - git__free(w); - return -1; - } - - *out = w; - return 0; -} - -void git_commit_graph_writer_free(git_commit_graph_writer *w) -{ - struct packed_commit *packed_commit; - size_t i; - - if (!w) - return; - - git_vector_foreach (&w->commits, i, packed_commit) - packed_commit_free(packed_commit); - git_vector_free(&w->commits); - git_str_dispose(&w->objects_info_dir); - git__free(w); -} - -struct object_entry_cb_state { - git_repository *repo; - git_odb *db; - git_vector *commits; -}; - -static int object_entry__cb(const git_oid *id, void *data) -{ - struct object_entry_cb_state *state = (struct object_entry_cb_state *)data; - git_commit *commit = NULL; - struct packed_commit *packed_commit = NULL; - size_t header_len; - git_object_t header_type; - int error = 0; - - error = git_odb_read_header(&header_len, &header_type, state->db, id); - if (error < 0) - return error; - - if (header_type != GIT_OBJECT_COMMIT) - return 0; - - error = git_commit_lookup(&commit, state->repo, id); - if (error < 0) - return error; - - packed_commit = packed_commit_new(commit); - git_commit_free(commit); - GIT_ERROR_CHECK_ALLOC(packed_commit); - - error = git_vector_insert(state->commits, packed_commit); - if (error < 0) { - packed_commit_free(packed_commit); - return error; - } - - return 0; -} - -int git_commit_graph_writer_add_index_file( - git_commit_graph_writer *w, - git_repository *repo, - const char *idx_path) -{ - int error; - struct git_pack_file *p = NULL; - struct object_entry_cb_state state = {0}; - state.repo = repo; - state.commits = &w->commits; - - error = git_repository_odb(&state.db, repo); - if (error < 0) - goto cleanup; - - error = git_mwindow_get_pack(&p, idx_path); - if (error < 0) - goto cleanup; - - error = git_pack_foreach_entry(p, object_entry__cb, &state); - if (error < 0) - goto cleanup; - -cleanup: - if (p) - git_mwindow_put_pack(p); - git_odb_free(state.db); - return error; -} - -int git_commit_graph_writer_add_revwalk(git_commit_graph_writer *w, git_revwalk *walk) -{ - int error; - git_oid id; - git_repository *repo = git_revwalk_repository(walk); - git_commit *commit; - struct packed_commit *packed_commit; - - while ((git_revwalk_next(&id, walk)) == 0) { - error = git_commit_lookup(&commit, repo, &id); - if (error < 0) - return error; - - packed_commit = packed_commit_new(commit); - git_commit_free(commit); - GIT_ERROR_CHECK_ALLOC(packed_commit); - - error = git_vector_insert(&w->commits, packed_commit); - if (error < 0) { - packed_commit_free(packed_commit); - return error; - } - } - - return 0; -} - -enum generation_number_commit_state { - GENERATION_NUMBER_COMMIT_STATE_UNVISITED = 0, - GENERATION_NUMBER_COMMIT_STATE_ADDED = 1, - GENERATION_NUMBER_COMMIT_STATE_EXPANDED = 2, - GENERATION_NUMBER_COMMIT_STATE_VISITED = 3 -}; - -static int compute_generation_numbers(git_vector *commits) -{ - git_array_t(size_t) index_stack = GIT_ARRAY_INIT; - size_t i, j; - size_t *parent_idx; - enum generation_number_commit_state *commit_states = NULL; - struct packed_commit *child_packed_commit; - git_oidmap *packed_commit_map = NULL; - int error = 0; - - /* First populate the parent indices fields */ - error = git_oidmap_new(&packed_commit_map); - if (error < 0) - goto cleanup; - git_vector_foreach (commits, i, child_packed_commit) { - child_packed_commit->index = i; - error = git_oidmap_set( - packed_commit_map, &child_packed_commit->sha1, child_packed_commit); - if (error < 0) - goto cleanup; - } - - git_vector_foreach (commits, i, child_packed_commit) { - size_t parent_i, *parent_idx_ptr; - struct packed_commit *parent_packed_commit; - git_oid *parent_id; - git_array_init_to_size( - child_packed_commit->parent_indices, - git_array_size(child_packed_commit->parents)); - if (git_array_size(child_packed_commit->parents) - && !child_packed_commit->parent_indices.ptr) { - error = -1; - goto cleanup; - } - git_array_foreach (child_packed_commit->parents, parent_i, parent_id) { - parent_packed_commit = git_oidmap_get(packed_commit_map, parent_id); - if (!parent_packed_commit) { - git_error_set(GIT_ERROR_ODB, - "parent commit %s not found in commit graph", - git_oid_tostr_s(parent_id)); - error = GIT_ENOTFOUND; - goto cleanup; - } - parent_idx_ptr = git_array_alloc(child_packed_commit->parent_indices); - if (!parent_idx_ptr) { - error = -1; - goto cleanup; - } - *parent_idx_ptr = parent_packed_commit->index; - } - } - - /* - * We copy all the commits to the stack and then during visitation, - * each node can be added up to two times to the stack. - */ - git_array_init_to_size(index_stack, 3 * git_vector_length(commits)); - if (!index_stack.ptr) { - error = -1; - goto cleanup; - } - - commit_states = (enum generation_number_commit_state *)git__calloc( - git_vector_length(commits), sizeof(enum generation_number_commit_state)); - if (!commit_states) { - error = -1; - goto cleanup; - } - - /* - * Perform a Post-Order traversal so that all parent nodes are fully - * visited before the child node. - */ - git_vector_foreach (commits, i, child_packed_commit) - *(size_t *)git_array_alloc(index_stack) = i; - - while (git_array_size(index_stack)) { - size_t *index_ptr = git_array_pop(index_stack); - i = *index_ptr; - child_packed_commit = git_vector_get(commits, i); - - if (commit_states[i] == GENERATION_NUMBER_COMMIT_STATE_VISITED) { - /* This commit has already been fully visited. */ - continue; - } - if (commit_states[i] == GENERATION_NUMBER_COMMIT_STATE_EXPANDED) { - /* All of the commits parents have been visited. */ - child_packed_commit->generation = 0; - git_array_foreach (child_packed_commit->parent_indices, j, parent_idx) { - struct packed_commit *parent = git_vector_get(commits, *parent_idx); - if (child_packed_commit->generation < parent->generation) - child_packed_commit->generation = parent->generation; - } - if (child_packed_commit->generation - < GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX) { - ++child_packed_commit->generation; - } - commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_VISITED; - continue; - } - - /* - * This is the first time we see this commit. We need - * to visit all its parents before we can fully visit - * it. - */ - if (git_array_size(child_packed_commit->parent_indices) == 0) { - /* - * Special case: if the commit has no parents, there's - * no need to add it to the stack just to immediately - * remove it. - */ - commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_VISITED; - child_packed_commit->generation = 1; - continue; - } - - /* - * Add this current commit again so that it is visited - * again once all its children have been visited. - */ - *(size_t *)git_array_alloc(index_stack) = i; - git_array_foreach (child_packed_commit->parent_indices, j, parent_idx) { - if (commit_states[*parent_idx] - != GENERATION_NUMBER_COMMIT_STATE_UNVISITED) { - /* This commit has already been considered. */ - continue; - } - - commit_states[*parent_idx] = GENERATION_NUMBER_COMMIT_STATE_ADDED; - *(size_t *)git_array_alloc(index_stack) = *parent_idx; - } - commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_EXPANDED; - } - -cleanup: - git_oidmap_free(packed_commit_map); - git__free(commit_states); - git_array_clear(index_stack); - - return error; -} - -static int write_offset(off64_t offset, commit_graph_write_cb write_cb, void *cb_data) -{ - int error; - uint32_t word; - - word = htonl((uint32_t)((offset >> 32) & 0xffffffffu)); - error = write_cb((const char *)&word, sizeof(word), cb_data); - if (error < 0) - return error; - word = htonl((uint32_t)((offset >> 0) & 0xffffffffu)); - error = write_cb((const char *)&word, sizeof(word), cb_data); - if (error < 0) - return error; - - return 0; -} - -static int write_chunk_header( - int chunk_id, - off64_t offset, - commit_graph_write_cb write_cb, - void *cb_data) -{ - uint32_t word = htonl(chunk_id); - int error = write_cb((const char *)&word, sizeof(word), cb_data); - if (error < 0) - return error; - return write_offset(offset, write_cb, cb_data); -} - -static int commit_graph_write_buf(const char *buf, size_t size, void *data) -{ - git_str *b = (git_str *)data; - return git_str_put(b, buf, size); -} - -struct commit_graph_write_hash_context { - commit_graph_write_cb write_cb; - void *cb_data; - git_hash_ctx *ctx; -}; - -static int commit_graph_write_hash(const char *buf, size_t size, void *data) -{ - struct commit_graph_write_hash_context *ctx = data; - int error; - - error = git_hash_update(ctx->ctx, buf, size); - if (error < 0) - return error; - - return ctx->write_cb(buf, size, ctx->cb_data); -} - -static void packed_commit_free_dup(void *packed_commit) -{ - packed_commit_free(packed_commit); -} - -static int commit_graph_write( - git_commit_graph_writer *w, - commit_graph_write_cb write_cb, - void *cb_data) -{ - int error = 0; - size_t i; - struct packed_commit *packed_commit; - struct git_commit_graph_header hdr = {0}; - uint32_t oid_fanout_count; - uint32_t extra_edge_list_count; - uint32_t oid_fanout[256]; - off64_t offset; - git_str oid_lookup = GIT_STR_INIT, commit_data = GIT_STR_INIT, - extra_edge_list = GIT_STR_INIT; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size; - git_hash_ctx ctx; - struct commit_graph_write_hash_context hash_cb_data = {0}; - - hdr.signature = htonl(COMMIT_GRAPH_SIGNATURE); - hdr.version = COMMIT_GRAPH_VERSION; - hdr.object_id_version = COMMIT_GRAPH_OBJECT_ID_VERSION; - hdr.chunks = 0; - hdr.base_graph_files = 0; - hash_cb_data.write_cb = write_cb; - hash_cb_data.cb_data = cb_data; - hash_cb_data.ctx = &ctx; - - checksum_size = GIT_HASH_SHA1_SIZE; - error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1); - if (error < 0) - return error; - cb_data = &hash_cb_data; - write_cb = commit_graph_write_hash; - - /* Sort the commits. */ - git_vector_sort(&w->commits); - git_vector_uniq(&w->commits, packed_commit_free_dup); - error = compute_generation_numbers(&w->commits); - if (error < 0) - goto cleanup; - - /* Fill the OID Fanout table. */ - oid_fanout_count = 0; - for (i = 0; i < 256; i++) { - while (oid_fanout_count < git_vector_length(&w->commits) && - (packed_commit = (struct packed_commit *)git_vector_get(&w->commits, oid_fanout_count)) && - packed_commit->sha1.id[0] <= i) - ++oid_fanout_count; - oid_fanout[i] = htonl(oid_fanout_count); - } - - /* Fill the OID Lookup table. */ - git_vector_foreach (&w->commits, i, packed_commit) { - error = git_str_put(&oid_lookup, - (const char *)&packed_commit->sha1, sizeof(git_oid)); - if (error < 0) - goto cleanup; - } - - /* Fill the Commit Data and Extra Edge List tables. */ - extra_edge_list_count = 0; - git_vector_foreach (&w->commits, i, packed_commit) { - uint64_t commit_time; - uint32_t generation; - uint32_t word; - size_t *packed_index; - unsigned int parentcount = (unsigned int)git_array_size(packed_commit->parents); - - error = git_str_put(&commit_data, - (const char *)&packed_commit->tree_oid, - sizeof(git_oid)); - if (error < 0) - goto cleanup; - - if (parentcount == 0) { - word = htonl(GIT_COMMIT_GRAPH_MISSING_PARENT); - } else { - packed_index = git_array_get(packed_commit->parent_indices, 0); - word = htonl((uint32_t)*packed_index); - } - error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); - if (error < 0) - goto cleanup; - - if (parentcount < 2) { - word = htonl(GIT_COMMIT_GRAPH_MISSING_PARENT); - } else if (parentcount == 2) { - packed_index = git_array_get(packed_commit->parent_indices, 1); - word = htonl((uint32_t)*packed_index); - } else { - word = htonl(0x80000000u | extra_edge_list_count); - } - error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); - if (error < 0) - goto cleanup; - - if (parentcount > 2) { - unsigned int parent_i; - for (parent_i = 1; parent_i < parentcount; ++parent_i) { - packed_index = git_array_get( - packed_commit->parent_indices, parent_i); - word = htonl((uint32_t)(*packed_index | (parent_i + 1 == parentcount ? 0x80000000u : 0))); - - error = git_str_put(&extra_edge_list, - (const char *)&word, - sizeof(word)); - if (error < 0) - goto cleanup; - } - extra_edge_list_count += parentcount - 1; - } - - generation = packed_commit->generation; - commit_time = (uint64_t)packed_commit->commit_time; - if (generation > GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX) - generation = GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX; - word = ntohl((uint32_t)((generation << 2) | (((uint32_t)(commit_time >> 32)) & 0x3) )); - error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); - if (error < 0) - goto cleanup; - word = ntohl((uint32_t)(commit_time & 0xfffffffful)); - error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); - if (error < 0) - goto cleanup; - } - - /* Write the header. */ - hdr.chunks = 3; - if (git_str_len(&extra_edge_list) > 0) - hdr.chunks++; - error = write_cb((const char *)&hdr, sizeof(hdr), cb_data); - if (error < 0) - goto cleanup; - - /* Write the chunk headers. */ - offset = sizeof(hdr) + (hdr.chunks + 1) * 12; - error = write_chunk_header(COMMIT_GRAPH_OID_FANOUT_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += sizeof(oid_fanout); - error = write_chunk_header(COMMIT_GRAPH_OID_LOOKUP_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&oid_lookup); - error = write_chunk_header(COMMIT_GRAPH_COMMIT_DATA_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&commit_data); - if (git_str_len(&extra_edge_list) > 0) { - error = write_chunk_header( - COMMIT_GRAPH_EXTRA_EDGE_LIST_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&extra_edge_list); - } - error = write_chunk_header(0, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - - /* Write all the chunks. */ - error = write_cb((const char *)oid_fanout, sizeof(oid_fanout), cb_data); - if (error < 0) - goto cleanup; - error = write_cb(git_str_cstr(&oid_lookup), git_str_len(&oid_lookup), cb_data); - if (error < 0) - goto cleanup; - error = write_cb(git_str_cstr(&commit_data), git_str_len(&commit_data), cb_data); - if (error < 0) - goto cleanup; - error = write_cb(git_str_cstr(&extra_edge_list), git_str_len(&extra_edge_list), cb_data); - if (error < 0) - goto cleanup; - - /* Finalize the checksum and write the trailer. */ - error = git_hash_final(checksum, &ctx); - if (error < 0) - goto cleanup; - error = write_cb((char *)checksum, checksum_size, cb_data); - if (error < 0) - goto cleanup; - -cleanup: - git_str_dispose(&oid_lookup); - git_str_dispose(&commit_data); - git_str_dispose(&extra_edge_list); - git_hash_ctx_cleanup(&ctx); - return error; -} - -static int commit_graph_write_filebuf(const char *buf, size_t size, void *data) -{ - git_filebuf *f = (git_filebuf *)data; - return git_filebuf_write(f, buf, size); -} - -int git_commit_graph_writer_options_init( - git_commit_graph_writer_options *opts, - unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, - version, - git_commit_graph_writer_options, - GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT); - return 0; -} - -int git_commit_graph_writer_commit( - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts) -{ - int error; - int filebuf_flags = GIT_FILEBUF_DO_NOT_BUFFER; - git_str commit_graph_path = GIT_STR_INIT; - git_filebuf output = GIT_FILEBUF_INIT; - - /* TODO: support options and fill in defaults. */ - GIT_UNUSED(opts); - - error = git_str_joinpath( - &commit_graph_path, git_str_cstr(&w->objects_info_dir), "commit-graph"); - if (error < 0) - return error; - - if (git_repository__fsync_gitdir) - filebuf_flags |= GIT_FILEBUF_FSYNC; - error = git_filebuf_open(&output, git_str_cstr(&commit_graph_path), filebuf_flags, 0644); - git_str_dispose(&commit_graph_path); - if (error < 0) - return error; - - error = commit_graph_write(w, commit_graph_write_filebuf, &output); - if (error < 0) { - git_filebuf_cleanup(&output); - return error; - } - - return git_filebuf_commit(&output); -} - -int git_commit_graph_writer_dump( - git_buf *cgraph, - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts) -{ - GIT_BUF_WRAP_PRIVATE(cgraph, git_commit_graph__writer_dump, w, opts); -} - -int git_commit_graph__writer_dump( - git_str *cgraph, - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts) -{ - /* TODO: support options. */ - GIT_UNUSED(opts); - return commit_graph_write(w, commit_graph_write_buf, cgraph); -} diff --git a/src/commit_graph.h b/src/commit_graph.h deleted file mode 100644 index 45e125b9e..000000000 --- a/src/commit_graph.h +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_commit_graph_h__ -#define INCLUDE_commit_graph_h__ - -#include "common.h" - -#include "git2/types.h" -#include "git2/sys/commit_graph.h" - -#include "map.h" -#include "vector.h" -#include "oid.h" -#include "hash.h" - -/** - * A commit-graph file. - * - * This file contains metadata about commits, particularly the generation - * number for each one. This can help speed up graph operations without - * requiring a full graph traversal. - * - * Support for this feature was added in git 2.19. - */ -typedef struct git_commit_graph_file { - git_map graph_map; - - /* The OID Fanout table. */ - const uint32_t *oid_fanout; - /* The total number of commits in the graph. */ - uint32_t num_commits; - - /* The OID Lookup table. */ - git_oid *oid_lookup; - - /* - * The Commit Data table. Each entry contains the OID of the commit followed - * by two 8-byte fields in network byte order: - * - The indices of the first two parents (32 bits each). - * - The generation number (first 30 bits) and commit time in seconds since - * UNIX epoch (34 bits). - */ - const unsigned char *commit_data; - - /* - * The Extra Edge List table. Each 4-byte entry is a network byte order index - * of one of the i-th (i > 0) parents of commits in the `commit_data` table, - * when the commit has more than 2 parents. - */ - const unsigned char *extra_edge_list; - /* The number of entries in the Extra Edge List table. Each entry is 4 bytes wide. */ - size_t num_extra_edge_list; - - /* The trailer of the file. Contains the SHA1-checksum of the whole file. */ - unsigned char checksum[GIT_HASH_SHA1_SIZE]; -} git_commit_graph_file; - -/** - * An entry in the commit-graph file. Provides a subset of the information that - * can be obtained from the commit header. - */ -typedef struct git_commit_graph_entry { - /* The generation number of the commit within the graph */ - size_t generation; - - /* Time in seconds from UNIX epoch. */ - git_time_t commit_time; - - /* The number of parents of the commit. */ - size_t parent_count; - - /* - * The indices of the parent commits within the Commit Data table. The value - * of `GIT_COMMIT_GRAPH_MISSING_PARENT` indicates that no parent is in that - * position. - */ - size_t parent_indices[2]; - - /* The index within the Extra Edge List of any parent after the first two. */ - size_t extra_parents_index; - - /* The SHA-1 hash of the root tree of the commit. */ - git_oid tree_oid; - - /* The SHA-1 hash of the requested commit. */ - git_oid sha1; -} git_commit_graph_entry; - -/* A wrapper for git_commit_graph_file to enable lazy loading in the ODB. */ -struct git_commit_graph { - /* The path to the commit-graph file. Something like ".git/objects/info/commit-graph". */ - git_str filename; - - /* The underlying commit-graph file. */ - git_commit_graph_file *file; - - /* Whether the commit-graph file was already checked for validity. */ - bool checked; -}; - -/** Create a new commit-graph, optionally opening the underlying file. */ -int git_commit_graph_new(git_commit_graph **cgraph_out, const char *objects_dir, bool open_file); - -/** Open and validate a commit-graph file. */ -int git_commit_graph_file_open(git_commit_graph_file **file_out, const char *path); - -/* - * Attempt to get the git_commit_graph's commit-graph file. This object is - * still owned by the git_commit_graph. If the repository does not contain a commit graph, - * it will return GIT_ENOTFOUND. - * - * This function is not thread-safe. - */ -int git_commit_graph_get_file(git_commit_graph_file **file_out, git_commit_graph *cgraph); - -/* Marks the commit-graph file as needing a refresh. */ -void git_commit_graph_refresh(git_commit_graph *cgraph); - -/* - * A writer for `commit-graph` files. - */ -struct git_commit_graph_writer { - /* - * The path of the `objects/info` directory where the `commit-graph` will be - * stored. - */ - git_str objects_info_dir; - - /* The list of packed commits. */ - git_vector commits; -}; - -int git_commit_graph__writer_dump( - git_str *cgraph, - git_commit_graph_writer *w, - git_commit_graph_writer_options *opts); - -/* - * Returns whether the git_commit_graph_file needs to be reloaded since the - * contents of the commit-graph file have changed on disk. - */ -bool git_commit_graph_file_needs_refresh( - const git_commit_graph_file *file, const char *path); - -int git_commit_graph_entry_find( - git_commit_graph_entry *e, - const git_commit_graph_file *file, - const git_oid *short_oid, - size_t len); -int git_commit_graph_entry_parent( - git_commit_graph_entry *parent, - const git_commit_graph_file *file, - const git_commit_graph_entry *entry, - size_t n); -int git_commit_graph_file_close(git_commit_graph_file *cgraph); -void git_commit_graph_file_free(git_commit_graph_file *cgraph); - -/* This is exposed for use in the fuzzers. */ -int git_commit_graph_file_parse( - git_commit_graph_file *file, - const unsigned char *data, - size_t size); - -#endif diff --git a/src/commit_list.c b/src/commit_list.c deleted file mode 100644 index 4585508bc..000000000 --- a/src/commit_list.c +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "commit_list.h" - -#include "revwalk.h" -#include "pool.h" -#include "odb.h" -#include "commit.h" - -int git_commit_list_generation_cmp(const void *a, const void *b) -{ - uint32_t generation_a = ((git_commit_list_node *) a)->generation; - uint32_t generation_b = ((git_commit_list_node *) b)->generation; - - if (!generation_a || !generation_b) { - /* Fall back to comparing by timestamps if at least one commit lacks a generation. */ - return git_commit_list_time_cmp(a, b); - } - - if (generation_a < generation_b) - return 1; - if (generation_a > generation_b) - return -1; - - return 0; -} - -int git_commit_list_time_cmp(const void *a, const void *b) -{ - int64_t time_a = ((git_commit_list_node *) a)->time; - int64_t time_b = ((git_commit_list_node *) b)->time; - - if (time_a < time_b) - return 1; - if (time_a > time_b) - return -1; - - return 0; -} - -git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p) -{ - git_commit_list *new_list = git__malloc(sizeof(git_commit_list)); - if (new_list != NULL) { - new_list->item = item; - new_list->next = *list_p; - } - *list_p = new_list; - return new_list; -} - -git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p) -{ - git_commit_list **pp = list_p; - git_commit_list *p; - - while ((p = *pp) != NULL) { - if (git_commit_list_time_cmp(p->item, item) > 0) - break; - - pp = &p->next; - } - - return git_commit_list_insert(item, pp); -} - -git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk) -{ - return (git_commit_list_node *)git_pool_mallocz(&walk->commit_pool, 1); -} - -static git_commit_list_node **alloc_parents( - git_revwalk *walk, git_commit_list_node *commit, size_t n_parents) -{ - size_t bytes; - - if (n_parents <= PARENTS_PER_COMMIT) - return (git_commit_list_node **)((char *)commit + sizeof(git_commit_list_node)); - - if (git__multiply_sizet_overflow(&bytes, n_parents, sizeof(git_commit_list_node *))) - return NULL; - - return (git_commit_list_node **)git_pool_malloc(&walk->commit_pool, bytes); -} - - -void git_commit_list_free(git_commit_list **list_p) -{ - git_commit_list *list = *list_p; - - if (list == NULL) - return; - - while (list) { - git_commit_list *temp = list; - list = temp->next; - git__free(temp); - } - - *list_p = NULL; -} - -git_commit_list_node *git_commit_list_pop(git_commit_list **stack) -{ - git_commit_list *top = *stack; - git_commit_list_node *item = top ? top->item : NULL; - - if (top) { - *stack = top->next; - git__free(top); - } - return item; -} - -static int commit_quick_parse( - git_revwalk *walk, - git_commit_list_node *node, - git_odb_object *obj) -{ - git_oid *parent_oid; - git_commit *commit; - size_t i; - - commit = git__calloc(1, sizeof(*commit)); - GIT_ERROR_CHECK_ALLOC(commit); - commit->object.repo = walk->repo; - - if (git_commit__parse_ext(commit, obj, GIT_COMMIT_PARSE_QUICK) < 0) { - git__free(commit); - return -1; - } - - if (!git__is_uint16(git_array_size(commit->parent_ids))) { - git__free(commit); - git_error_set(GIT_ERROR_INVALID, "commit has more than 2^16 parents"); - return -1; - } - - node->generation = 0; - node->time = commit->committer->when.time; - node->out_degree = (uint16_t) git_array_size(commit->parent_ids); - node->parents = alloc_parents(walk, node, node->out_degree); - GIT_ERROR_CHECK_ALLOC(node->parents); - - git_array_foreach(commit->parent_ids, i, parent_oid) { - node->parents[i] = git_revwalk__commit_lookup(walk, parent_oid); - } - - git_commit__free(commit); - - node->parsed = 1; - - return 0; -} - -int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit) -{ - git_odb_object *obj; - git_commit_graph_file *cgraph_file = NULL; - int error; - - if (commit->parsed) - return 0; - - /* Let's try to use the commit graph first. */ - git_odb__get_commit_graph_file(&cgraph_file, walk->odb); - if (cgraph_file) { - git_commit_graph_entry e; - - error = git_commit_graph_entry_find(&e, cgraph_file, &commit->oid, GIT_OID_RAWSZ); - if (error == 0 && git__is_uint16(e.parent_count)) { - size_t i; - commit->generation = (uint32_t)e.generation; - commit->time = e.commit_time; - commit->out_degree = (uint16_t)e.parent_count; - commit->parents = alloc_parents(walk, commit, commit->out_degree); - GIT_ERROR_CHECK_ALLOC(commit->parents); - - for (i = 0; i < commit->out_degree; ++i) { - git_commit_graph_entry parent; - error = git_commit_graph_entry_parent(&parent, cgraph_file, &e, i); - if (error < 0) - return error; - commit->parents[i] = git_revwalk__commit_lookup(walk, &parent.sha1); - } - commit->parsed = 1; - return 0; - } - } - - if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0) - return error; - - if (obj->cached.type != GIT_OBJECT_COMMIT) { - git_error_set(GIT_ERROR_INVALID, "object is no commit object"); - error = -1; - } else - error = commit_quick_parse(walk, commit, obj); - - git_odb_object_free(obj); - return error; -} - diff --git a/src/commit_list.h b/src/commit_list.h deleted file mode 100644 index aad39f351..000000000 --- a/src/commit_list.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_commit_list_h__ -#define INCLUDE_commit_list_h__ - -#include "common.h" - -#include "git2/oid.h" - -#define PARENT1 (1 << 0) -#define PARENT2 (1 << 1) -#define RESULT (1 << 2) -#define STALE (1 << 3) -#define ALL_FLAGS (PARENT1 | PARENT2 | STALE | RESULT) - -#define PARENTS_PER_COMMIT 2 -#define COMMIT_ALLOC \ - (sizeof(git_commit_list_node) + PARENTS_PER_COMMIT * sizeof(git_commit_list_node *)) - -#define FLAG_BITS 4 - -typedef struct git_commit_list_node { - git_oid oid; - int64_t time; - uint32_t generation; - unsigned int seen:1, - uninteresting:1, - topo_delay:1, - parsed:1, - added:1, - flags : FLAG_BITS; - - uint16_t in_degree; - uint16_t out_degree; - - struct git_commit_list_node **parents; -} git_commit_list_node; - -typedef struct git_commit_list { - git_commit_list_node *item; - struct git_commit_list *next; -} git_commit_list; - -git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk); -int git_commit_list_generation_cmp(const void *a, const void *b); -int git_commit_list_time_cmp(const void *a, const void *b); -void git_commit_list_free(git_commit_list **list_p); -git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p); -git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p); -int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit); -git_commit_list_node *git_commit_list_pop(git_commit_list **stack); - -#endif diff --git a/src/common.h b/src/common.h deleted file mode 100644 index 549bddb59..000000000 --- a/src/common.h +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_common_h__ -#define INCLUDE_common_h__ - -#ifndef LIBGIT2_NO_FEATURES_H -# include "git2/sys/features.h" -#endif - -#include "git2/common.h" -#include "cc-compat.h" - -/** Declare a function as always inlined. */ -#if defined(_MSC_VER) -# define GIT_INLINE(type) static __inline type -#elif defined(__GNUC__) -# define GIT_INLINE(type) static __inline__ type -#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -# define GIT_INLINE(type) static inline type -#else -# define GIT_INLINE(type) static type -#endif - -/** Support for gcc/clang __has_builtin intrinsic */ -#ifndef __has_builtin -# define __has_builtin(x) 0 -#endif - -/** - * Declare that a function's return value must be used. - * - * Used mostly to guard against potential silent bugs at runtime. This is - * recommended to be added to functions that: - * - * - Allocate / reallocate memory. This prevents memory leaks or errors where - * buffers are expected to have grown to a certain size, but could not be - * resized. - * - Acquire locks. When a lock cannot be acquired, that will almost certainly - * cause a data race / undefined behavior. - */ -#if defined(__GNUC__) -# define GIT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) -#else -# define GIT_WARN_UNUSED_RESULT -#endif - -#include -#include -#include -#include -#include -#include - -#include -#include - -#ifdef GIT_WIN32 - -# include -# include -# include -# include -# include -# include "win32/msvc-compat.h" -# include "win32/mingw-compat.h" -# include "win32/w32_common.h" -# include "win32/win32-compat.h" -# include "win32/error.h" -# include "win32/version.h" -# ifdef GIT_THREADS -# include "win32/thread.h" -# endif - -#else - -# include -# include -# ifdef GIT_THREADS -# include -# include -# endif - -#define GIT_LIBGIT2_CALL -#define GIT_SYSTEM_CALL - -#ifdef GIT_USE_STAT_ATIMESPEC -# define st_atim st_atimespec -# define st_ctim st_ctimespec -# define st_mtim st_mtimespec -#endif - -# include - -#endif - -#include "git2/types.h" -#include "git2/errors.h" -#include "errors.h" -#include "thread.h" -#include "integer.h" -#include "assert_safe.h" -#include "utf8.h" - -/* - * Include the declarations for deprecated functions; this ensures - * that they're decorated with the proper extern/visibility attributes. - */ -#include "git2/deprecated.h" - -#include "posix.h" - -#define DEFAULT_BUFSIZE 65536 -#define FILEIO_BUFSIZE DEFAULT_BUFSIZE -#define FILTERIO_BUFSIZE DEFAULT_BUFSIZE -#define NETIO_BUFSIZE DEFAULT_BUFSIZE - -/** - * Check a pointer allocation result, returning -1 if it failed. - */ -#define GIT_ERROR_CHECK_ALLOC(ptr) do { \ - if ((ptr) == NULL) { return -1; } \ - } while(0) - -/** - * Check a string buffer allocation result, returning -1 if it failed. - */ -#define GIT_ERROR_CHECK_ALLOC_STR(buf) do { \ - if ((void *)(buf) == NULL || git_str_oom(buf)) { return -1; } \ - } while(0) - -/** - * Check a return value and propagate result if non-zero. - */ -#define GIT_ERROR_CHECK_ERROR(code) \ - do { int _err = (code); if (_err) return _err; } while (0) - -/** - * Check a versioned structure for validity - */ -GIT_INLINE(int) git_error__check_version(const void *structure, unsigned int expected_max, const char *name) -{ - unsigned int actual; - - if (!structure) - return 0; - - actual = *(const unsigned int*)structure; - if (actual > 0 && actual <= expected_max) - return 0; - - git_error_set(GIT_ERROR_INVALID, "invalid version %d on %s", actual, name); - return -1; -} -#define GIT_ERROR_CHECK_VERSION(S,V,N) if (git_error__check_version(S,V,N) < 0) return -1 - -/** - * Initialize a structure with a version. - */ -GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int version) -{ - memset(structure, 0, len); - *((int*)structure) = version; -} -#define GIT_INIT_STRUCTURE(S,V) git__init_structure(S, sizeof(*S), V) - -#define GIT_INIT_STRUCTURE_FROM_TEMPLATE(PTR,VERSION,TYPE,TPL) do { \ - TYPE _tmpl = TPL; \ - GIT_ERROR_CHECK_VERSION(&(VERSION), _tmpl.version, #TYPE); \ - memcpy((PTR), &_tmpl, sizeof(_tmpl)); } while (0) - - -/** Check for additive overflow, setting an error if would occur. */ -#define GIT_ADD_SIZET_OVERFLOW(out, one, two) \ - (git__add_sizet_overflow(out, one, two) ? (git_error_set_oom(), 1) : 0) - -/** Check for additive overflow, setting an error if would occur. */ -#define GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize) \ - (git__multiply_sizet_overflow(out, nelem, elsize) ? (git_error_set_oom(), 1) : 0) - -/** Check for additive overflow, failing if it would occur. */ -#define GIT_ERROR_CHECK_ALLOC_ADD(out, one, two) \ - if (GIT_ADD_SIZET_OVERFLOW(out, one, two)) { return -1; } - -#define GIT_ERROR_CHECK_ALLOC_ADD3(out, one, two, three) \ - if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), three)) { return -1; } - -#define GIT_ERROR_CHECK_ALLOC_ADD4(out, one, two, three, four) \ - if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), four)) { return -1; } - -#define GIT_ERROR_CHECK_ALLOC_ADD5(out, one, two, three, four, five) \ - if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), four) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), five)) { return -1; } - -/** Check for multiplicative overflow, failing if it would occur. */ -#define GIT_ERROR_CHECK_ALLOC_MULTIPLY(out, nelem, elsize) \ - if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; } - -/* NOTE: other git_error functions are in the public errors.h header file */ - -/* Forward declare git_str */ -typedef struct git_str git_str; - -#include "util.h" - -#endif diff --git a/src/config.c b/src/config.c deleted file mode 100644 index 88da34c5e..000000000 --- a/src/config.c +++ /dev/null @@ -1,1566 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "config.h" - -#include "git2/config.h" -#include "git2/sys/config.h" - -#include "buf.h" -#include "config_backend.h" -#include "regexp.h" -#include "sysdir.h" -#include "transaction.h" -#include "vector.h" -#if GIT_WIN32 -# include -#endif - -#include - -void git_config_entry_free(git_config_entry *entry) -{ - if (!entry) - return; - - entry->free(entry); -} - -typedef struct { - git_refcount rc; - - git_config_backend *backend; - git_config_level_t level; -} backend_internal; - -static void backend_internal_free(backend_internal *internal) -{ - git_config_backend *backend; - - backend = internal->backend; - backend->free(backend); - git__free(internal); -} - -static void config_free(git_config *cfg) -{ - size_t i; - backend_internal *internal; - - for (i = 0; i < cfg->backends.length; ++i) { - internal = git_vector_get(&cfg->backends, i); - GIT_REFCOUNT_DEC(internal, backend_internal_free); - } - - git_vector_free(&cfg->backends); - - git__memzero(cfg, sizeof(*cfg)); - git__free(cfg); -} - -void git_config_free(git_config *cfg) -{ - if (cfg == NULL) - return; - - GIT_REFCOUNT_DEC(cfg, config_free); -} - -static int config_backend_cmp(const void *a, const void *b) -{ - const backend_internal *bk_a = (const backend_internal *)(a); - const backend_internal *bk_b = (const backend_internal *)(b); - - return bk_b->level - bk_a->level; -} - -int git_config_new(git_config **out) -{ - git_config *cfg; - - cfg = git__malloc(sizeof(git_config)); - GIT_ERROR_CHECK_ALLOC(cfg); - - memset(cfg, 0x0, sizeof(git_config)); - - if (git_vector_init(&cfg->backends, 3, config_backend_cmp) < 0) { - git__free(cfg); - return -1; - } - - *out = cfg; - GIT_REFCOUNT_INC(cfg); - return 0; -} - -int git_config_add_file_ondisk( - git_config *cfg, - const char *path, - git_config_level_t level, - const git_repository *repo, - int force) -{ - git_config_backend *file = NULL; - struct stat st; - int res; - - GIT_ASSERT_ARG(cfg); - GIT_ASSERT_ARG(path); - - res = p_stat(path, &st); - if (res < 0 && errno != ENOENT && errno != ENOTDIR) { - git_error_set(GIT_ERROR_CONFIG, "failed to stat '%s'", path); - return -1; - } - - if (git_config_backend_from_file(&file, path) < 0) - return -1; - - if ((res = git_config_add_backend(cfg, file, level, repo, force)) < 0) { - /* - * free manually; the file is not owned by the config - * instance yet and will not be freed on cleanup - */ - file->free(file); - return res; - } - - return 0; -} - -int git_config_open_ondisk(git_config **out, const char *path) -{ - int error; - git_config *config; - - *out = NULL; - - if (git_config_new(&config) < 0) - return -1; - - if ((error = git_config_add_file_ondisk(config, path, GIT_CONFIG_LEVEL_LOCAL, NULL, 0)) < 0) - git_config_free(config); - else - *out = config; - - return error; -} - -int git_config_snapshot(git_config **out, git_config *in) -{ - int error = 0; - size_t i; - backend_internal *internal; - git_config *config; - - *out = NULL; - - if (git_config_new(&config) < 0) - return -1; - - git_vector_foreach(&in->backends, i, internal) { - git_config_backend *b; - - if ((error = internal->backend->snapshot(&b, internal->backend)) < 0) - break; - - if ((error = git_config_add_backend(config, b, internal->level, NULL, 0)) < 0) { - b->free(b); - break; - } - } - - if (error < 0) - git_config_free(config); - else - *out = config; - - return error; -} - -static int find_backend_by_level( - backend_internal **out, - const git_config *cfg, - git_config_level_t level) -{ - int pos = -1; - backend_internal *internal; - size_t i; - - /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config backend - * which has the highest level. As config backends are stored in a vector - * sorted by decreasing order of level, getting the backend at position 0 - * will do the job. - */ - if (level == GIT_CONFIG_HIGHEST_LEVEL) { - pos = 0; - } else { - git_vector_foreach(&cfg->backends, i, internal) { - if (internal->level == level) - pos = (int)i; - } - } - - if (pos == -1) { - git_error_set(GIT_ERROR_CONFIG, - "no configuration exists for the given level '%i'", (int)level); - return GIT_ENOTFOUND; - } - - *out = git_vector_get(&cfg->backends, pos); - - return 0; -} - -static int duplicate_level(void **old_raw, void *new_raw) -{ - backend_internal **old = (backend_internal **)old_raw; - - GIT_UNUSED(new_raw); - - git_error_set(GIT_ERROR_CONFIG, "there already exists a configuration for the given level (%i)", (int)(*old)->level); - return GIT_EEXISTS; -} - -static void try_remove_existing_backend( - git_config *cfg, - git_config_level_t level) -{ - int pos = -1; - backend_internal *internal; - size_t i; - - git_vector_foreach(&cfg->backends, i, internal) { - if (internal->level == level) - pos = (int)i; - } - - if (pos == -1) - return; - - internal = git_vector_get(&cfg->backends, pos); - - if (git_vector_remove(&cfg->backends, pos) < 0) - return; - - GIT_REFCOUNT_DEC(internal, backend_internal_free); -} - -static int git_config__add_internal( - git_config *cfg, - backend_internal *internal, - git_config_level_t level, - int force) -{ - int result; - - /* delete existing config backend for level if it exists */ - if (force) - try_remove_existing_backend(cfg, level); - - if ((result = git_vector_insert_sorted(&cfg->backends, - internal, &duplicate_level)) < 0) - return result; - - git_vector_sort(&cfg->backends); - internal->backend->cfg = cfg; - - GIT_REFCOUNT_INC(internal); - - return 0; -} - -int git_config_open_global(git_config **cfg_out, git_config *cfg) -{ - if (!git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_XDG)) - return 0; - - return git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_GLOBAL); -} - -int git_config_open_level( - git_config **cfg_out, - const git_config *cfg_parent, - git_config_level_t level) -{ - git_config *cfg; - backend_internal *internal; - int res; - - if ((res = find_backend_by_level(&internal, cfg_parent, level)) < 0) - return res; - - if ((res = git_config_new(&cfg)) < 0) - return res; - - if ((res = git_config__add_internal(cfg, internal, level, true)) < 0) { - git_config_free(cfg); - return res; - } - - *cfg_out = cfg; - - return 0; -} - -int git_config_add_backend( - git_config *cfg, - git_config_backend *backend, - git_config_level_t level, - const git_repository *repo, - int force) -{ - backend_internal *internal; - int result; - - GIT_ASSERT_ARG(cfg); - GIT_ASSERT_ARG(backend); - - GIT_ERROR_CHECK_VERSION(backend, GIT_CONFIG_BACKEND_VERSION, "git_config_backend"); - - if ((result = backend->open(backend, level, repo)) < 0) - return result; - - internal = git__malloc(sizeof(backend_internal)); - GIT_ERROR_CHECK_ALLOC(internal); - - memset(internal, 0x0, sizeof(backend_internal)); - - internal->backend = backend; - internal->level = level; - - if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) { - git__free(internal); - return result; - } - - return 0; -} - -/* - * Loop over all the variables - */ - -typedef struct { - git_config_iterator parent; - git_config_iterator *current; - const git_config *cfg; - git_regexp regex; - size_t i; -} all_iter; - -static int find_next_backend(size_t *out, const git_config *cfg, size_t i) -{ - backend_internal *internal; - - for (; i > 0; --i) { - internal = git_vector_get(&cfg->backends, i - 1); - if (!internal || !internal->backend) - continue; - - *out = i; - return 0; - } - - return -1; -} - -static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) -{ - all_iter *iter = (all_iter *) _iter; - backend_internal *internal; - git_config_backend *backend; - size_t i; - int error = 0; - - if (iter->current != NULL && - (error = iter->current->next(entry, iter->current)) == 0) { - return 0; - } - - if (error < 0 && error != GIT_ITEROVER) - return error; - - do { - if (find_next_backend(&i, iter->cfg, iter->i) < 0) - return GIT_ITEROVER; - - internal = git_vector_get(&iter->cfg->backends, i - 1); - backend = internal->backend; - iter->i = i - 1; - - if (iter->current) - iter->current->free(iter->current); - - iter->current = NULL; - error = backend->iterator(&iter->current, backend); - if (error == GIT_ENOTFOUND) - continue; - - if (error < 0) - return error; - - error = iter->current->next(entry, iter->current); - /* If this backend is empty, then keep going */ - if (error == GIT_ITEROVER) - continue; - - return error; - - } while(1); - - return GIT_ITEROVER; -} - -static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_iter) -{ - int error; - all_iter *iter = (all_iter *) _iter; - - /* - * We use the "normal" function to grab the next one across - * backends and then apply the regex - */ - while ((error = all_iter_next(entry, _iter)) == 0) { - /* skip non-matching keys if regexp was provided */ - if (git_regexp_match(&iter->regex, (*entry)->name) != 0) - continue; - - /* and simply return if we like the entry's name */ - return 0; - } - - return error; -} - -static void all_iter_free(git_config_iterator *_iter) -{ - all_iter *iter = (all_iter *) _iter; - - if (iter->current) - iter->current->free(iter->current); - - git__free(iter); -} - -static void all_iter_glob_free(git_config_iterator *_iter) -{ - all_iter *iter = (all_iter *) _iter; - - git_regexp_dispose(&iter->regex); - all_iter_free(_iter); -} - -int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) -{ - all_iter *iter; - - iter = git__calloc(1, sizeof(all_iter)); - GIT_ERROR_CHECK_ALLOC(iter); - - iter->parent.free = all_iter_free; - iter->parent.next = all_iter_next; - - iter->i = cfg->backends.length; - iter->cfg = cfg; - - *out = (git_config_iterator *) iter; - - return 0; -} - -int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp) -{ - all_iter *iter; - int result; - - if (regexp == NULL) - return git_config_iterator_new(out, cfg); - - iter = git__calloc(1, sizeof(all_iter)); - GIT_ERROR_CHECK_ALLOC(iter); - - if ((result = git_regexp_compile(&iter->regex, regexp, 0)) < 0) { - git__free(iter); - return -1; - } - - iter->parent.next = all_iter_glob_next; - iter->parent.free = all_iter_glob_free; - iter->i = cfg->backends.length; - iter->cfg = cfg; - - *out = (git_config_iterator *) iter; - - return 0; -} - -int git_config_foreach( - const git_config *cfg, git_config_foreach_cb cb, void *payload) -{ - return git_config_foreach_match(cfg, NULL, cb, payload); -} - -int git_config_backend_foreach_match( - git_config_backend *backend, - const char *regexp, - git_config_foreach_cb cb, - void *payload) -{ - git_config_entry *entry; - git_config_iterator *iter; - git_regexp regex; - int error = 0; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(cb); - - if (regexp && git_regexp_compile(®ex, regexp, 0) < 0) - return -1; - - if ((error = backend->iterator(&iter, backend)) < 0) { - iter = NULL; - return -1; - } - - while (!(iter->next(&entry, iter) < 0)) { - /* skip non-matching keys if regexp was provided */ - if (regexp && git_regexp_match(®ex, entry->name) != 0) - continue; - - /* abort iterator on non-zero return value */ - if ((error = cb(entry, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - if (regexp != NULL) - git_regexp_dispose(®ex); - - iter->free(iter); - - return error; -} - -int git_config_foreach_match( - const git_config *cfg, - const char *regexp, - git_config_foreach_cb cb, - void *payload) -{ - int error; - git_config_iterator *iter; - git_config_entry *entry; - - if ((error = git_config_iterator_glob_new(&iter, cfg, regexp)) < 0) - return error; - - while (!(error = git_config_next(&entry, iter))) { - if ((error = cb(entry, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - git_config_iterator_free(iter); - - if (error == GIT_ITEROVER) - error = 0; - - return error; -} - -/************** - * Setters - **************/ - -typedef enum { - BACKEND_USE_SET, - BACKEND_USE_DELETE -} backend_use; - -static const char *uses[] = { - "set", - "delete" -}; - -static int get_backend_for_use(git_config_backend **out, - git_config *cfg, const char *name, backend_use use) -{ - size_t i; - backend_internal *backend; - - *out = NULL; - - if (git_vector_length(&cfg->backends) == 0) { - git_error_set(GIT_ERROR_CONFIG, - "cannot %s value for '%s' when no config backends exist", - uses[use], name); - return GIT_ENOTFOUND; - } - - git_vector_foreach(&cfg->backends, i, backend) { - if (!backend->backend->readonly) { - *out = backend->backend; - return 0; - } - } - - git_error_set(GIT_ERROR_CONFIG, - "cannot %s value for '%s' when all config backends are readonly", - uses[use], name); - return GIT_ENOTFOUND; -} - -int git_config_delete_entry(git_config *cfg, const char *name) -{ - git_config_backend *backend; - - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; - - return backend->del(backend, name); -} - -int git_config_set_int64(git_config *cfg, const char *name, int64_t value) -{ - char str_value[32]; /* All numbers should fit in here */ - p_snprintf(str_value, sizeof(str_value), "%" PRId64, value); - return git_config_set_string(cfg, name, str_value); -} - -int git_config_set_int32(git_config *cfg, const char *name, int32_t value) -{ - return git_config_set_int64(cfg, name, (int64_t)value); -} - -int git_config_set_bool(git_config *cfg, const char *name, int value) -{ - return git_config_set_string(cfg, name, value ? "true" : "false"); -} - -int git_config_set_string(git_config *cfg, const char *name, const char *value) -{ - int error; - git_config_backend *backend; - - if (!value) { - git_error_set(GIT_ERROR_CONFIG, "the value to set cannot be NULL"); - return -1; - } - - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0) - return GIT_ENOTFOUND; - - error = backend->set(backend, name, value); - - if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL) - git_repository__configmap_lookup_cache_clear(GIT_REFCOUNT_OWNER(cfg)); - - return error; -} - -int git_config__update_entry( - git_config *config, - const char *key, - const char *value, - bool overwrite_existing, - bool only_if_existing) -{ - int error = 0; - git_config_entry *ce = NULL; - - if ((error = git_config__lookup_entry(&ce, config, key, false)) < 0) - return error; - - if (!ce && only_if_existing) /* entry doesn't exist */ - return 0; - if (ce && !overwrite_existing) /* entry would be overwritten */ - return 0; - if (value && ce && ce->value && !strcmp(ce->value, value)) /* no change */ - return 0; - if (!value && (!ce || !ce->value)) /* asked to delete absent entry */ - return 0; - - if (!value) - error = git_config_delete_entry(config, key); - else - error = git_config_set_string(config, key, value); - - git_config_entry_free(ce); - return error; -} - -/*********** - * Getters - ***********/ - -static int config_error_notfound(const char *name) -{ - git_error_set(GIT_ERROR_CONFIG, "config value '%s' was not found", name); - return GIT_ENOTFOUND; -} - -enum { - GET_ALL_ERRORS = 0, - GET_NO_MISSING = 1, - GET_NO_ERRORS = 2 -}; - -static int get_entry( - git_config_entry **out, - const git_config *cfg, - const char *name, - bool normalize_name, - int want_errors) -{ - int res = GIT_ENOTFOUND; - const char *key = name; - char *normalized = NULL; - size_t i; - backend_internal *internal; - - *out = NULL; - - if (normalize_name) { - if ((res = git_config__normalize_name(name, &normalized)) < 0) - goto cleanup; - key = normalized; - } - - res = GIT_ENOTFOUND; - git_vector_foreach(&cfg->backends, i, internal) { - if (!internal || !internal->backend) - continue; - - res = internal->backend->get(internal->backend, key, out); - if (res != GIT_ENOTFOUND) - break; - } - - git__free(normalized); - -cleanup: - if (res == GIT_ENOTFOUND) - res = (want_errors > GET_ALL_ERRORS) ? 0 : config_error_notfound(name); - else if (res && (want_errors == GET_NO_ERRORS)) { - git_error_clear(); - res = 0; - } - - return res; -} - -int git_config_get_entry( - git_config_entry **out, const git_config *cfg, const char *name) -{ - return get_entry(out, cfg, name, true, GET_ALL_ERRORS); -} - -int git_config__lookup_entry( - git_config_entry **out, - const git_config *cfg, - const char *key, - bool no_errors) -{ - return get_entry( - out, cfg, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING); -} - -int git_config_get_mapped( - int *out, - const git_config *cfg, - const char *name, - const git_configmap *maps, - size_t map_n) -{ - git_config_entry *entry; - int ret; - - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) - return ret; - - ret = git_config_lookup_map_value(out, maps, map_n, entry->value); - git_config_entry_free(entry); - - return ret; -} - -int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name) -{ - git_config_entry *entry; - int ret; - - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) - return ret; - - ret = git_config_parse_int64(out, entry->value); - git_config_entry_free(entry); - - return ret; -} - -int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name) -{ - git_config_entry *entry; - int ret; - - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) - return ret; - - ret = git_config_parse_int32(out, entry->value); - git_config_entry_free(entry); - - return ret; -} - -int git_config_get_bool(int *out, const git_config *cfg, const char *name) -{ - git_config_entry *entry; - int ret; - - if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) - return ret; - - ret = git_config_parse_bool(out, entry->value); - git_config_entry_free(entry); - - return ret; -} - -static int is_readonly(const git_config *cfg) -{ - size_t i; - backend_internal *internal; - - git_vector_foreach(&cfg->backends, i, internal) { - if (!internal || !internal->backend) - continue; - - if (!internal->backend->readonly) - return 0; - } - - return 1; -} - -static int git_config__parse_path(git_str *out, const char *value) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(value); - - if (value[0] == '~') { - if (value[1] != '\0' && value[1] != '/') { - git_error_set(GIT_ERROR_CONFIG, "retrieving a homedir by name is not supported"); - return -1; - } - - return git_sysdir_expand_global_file(out, value[1] ? &value[2] : NULL); - } - - return git_str_sets(out, value); -} - -int git_config_parse_path(git_buf *out, const char *value) -{ - GIT_BUF_WRAP_PRIVATE(out, git_config__parse_path, value); -} - -int git_config_get_path( - git_buf *out, - const git_config *cfg, - const char *name) -{ - GIT_BUF_WRAP_PRIVATE(out, git_config__get_path, cfg, name); -} - -int git_config__get_path( - git_str *out, - const git_config *cfg, - const char *name) -{ - git_config_entry *entry; - int error; - - if ((error = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) - return error; - - error = git_config__parse_path(out, entry->value); - git_config_entry_free(entry); - - return error; -} - -int git_config_get_string( - const char **out, const git_config *cfg, const char *name) -{ - git_config_entry *entry; - int ret; - - if (!is_readonly(cfg)) { - git_error_set(GIT_ERROR_CONFIG, "get_string called on a live config object"); - return -1; - } - - ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS); - *out = !ret ? (entry->value ? entry->value : "") : NULL; - - git_config_entry_free(entry); - - return ret; -} - -int git_config_get_string_buf( - git_buf *out, const git_config *cfg, const char *name) -{ - GIT_BUF_WRAP_PRIVATE(out, git_config__get_string_buf, cfg, name); -} - -int git_config__get_string_buf( - git_str *out, const git_config *cfg, const char *name) -{ - git_config_entry *entry; - int ret; - const char *str; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(cfg); - - ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS); - str = !ret ? (entry->value ? entry->value : "") : NULL; - - if (str) - ret = git_str_puts(out, str); - - git_config_entry_free(entry); - - return ret; -} - -char *git_config__get_string_force( - const git_config *cfg, const char *key, const char *fallback_value) -{ - git_config_entry *entry; - char *ret; - - get_entry(&entry, cfg, key, false, GET_NO_ERRORS); - ret = (entry && entry->value) ? git__strdup(entry->value) : fallback_value ? git__strdup(fallback_value) : NULL; - git_config_entry_free(entry); - - return ret; -} - -int git_config__get_bool_force( - const git_config *cfg, const char *key, int fallback_value) -{ - int val = fallback_value; - git_config_entry *entry; - - get_entry(&entry, cfg, key, false, GET_NO_ERRORS); - - if (entry && git_config_parse_bool(&val, entry->value) < 0) - git_error_clear(); - - git_config_entry_free(entry); - return val; -} - -int git_config__get_int_force( - const git_config *cfg, const char *key, int fallback_value) -{ - int32_t val = (int32_t)fallback_value; - git_config_entry *entry; - - get_entry(&entry, cfg, key, false, GET_NO_ERRORS); - - if (entry && git_config_parse_int32(&val, entry->value) < 0) - git_error_clear(); - - git_config_entry_free(entry); - return (int)val; -} - -int git_config_get_multivar_foreach( - const git_config *cfg, const char *name, const char *regexp, - git_config_foreach_cb cb, void *payload) -{ - int err, found; - git_config_iterator *iter; - git_config_entry *entry; - - if ((err = git_config_multivar_iterator_new(&iter, cfg, name, regexp)) < 0) - return err; - - found = 0; - while ((err = iter->next(&entry, iter)) == 0) { - found = 1; - - if ((err = cb(entry, payload)) != 0) { - git_error_set_after_callback(err); - break; - } - } - - iter->free(iter); - if (err == GIT_ITEROVER) - err = 0; - - if (found == 0 && err == 0) - err = config_error_notfound(name); - - return err; -} - -typedef struct { - git_config_iterator parent; - git_config_iterator *iter; - char *name; - git_regexp regex; - int have_regex; -} multivar_iter; - -static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_iter) -{ - multivar_iter *iter = (multivar_iter *) _iter; - int error = 0; - - while ((error = iter->iter->next(entry, iter->iter)) == 0) { - if (git__strcmp(iter->name, (*entry)->name)) - continue; - - if (!iter->have_regex) - return 0; - - if (git_regexp_match(&iter->regex, (*entry)->value) == 0) - return 0; - } - - return error; -} - -static void multivar_iter_free(git_config_iterator *_iter) -{ - multivar_iter *iter = (multivar_iter *) _iter; - - iter->iter->free(iter->iter); - - git__free(iter->name); - if (iter->have_regex) - git_regexp_dispose(&iter->regex); - git__free(iter); -} - -int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) -{ - multivar_iter *iter = NULL; - git_config_iterator *inner = NULL; - int error; - - if ((error = git_config_iterator_new(&inner, cfg)) < 0) - return error; - - iter = git__calloc(1, sizeof(multivar_iter)); - GIT_ERROR_CHECK_ALLOC(iter); - - if ((error = git_config__normalize_name(name, &iter->name)) < 0) - goto on_error; - - if (regexp != NULL) { - if ((error = git_regexp_compile(&iter->regex, regexp, 0)) < 0) - goto on_error; - - iter->have_regex = 1; - } - - iter->iter = inner; - iter->parent.free = multivar_iter_free; - iter->parent.next = multivar_iter_next; - - *out = (git_config_iterator *) iter; - - return 0; - -on_error: - - inner->free(inner); - git__free(iter); - return error; -} - -int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) -{ - git_config_backend *backend; - - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; - - return backend->set_multivar(backend, name, regexp, value); -} - -int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp) -{ - git_config_backend *backend; - - if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) - return GIT_ENOTFOUND; - - return backend->del_multivar(backend, name, regexp); -} - -int git_config_next(git_config_entry **entry, git_config_iterator *iter) -{ - return iter->next(entry, iter); -} - -void git_config_iterator_free(git_config_iterator *iter) -{ - if (iter == NULL) - return; - - iter->free(iter); -} - -int git_config_find_global(git_buf *path) -{ - GIT_BUF_WRAP_PRIVATE(path, git_sysdir_find_global_file, GIT_CONFIG_FILENAME_GLOBAL); -} - -int git_config__find_global(git_str *path) -{ - return git_sysdir_find_global_file(path, GIT_CONFIG_FILENAME_GLOBAL); -} - -int git_config_find_xdg(git_buf *path) -{ - GIT_BUF_WRAP_PRIVATE(path, git_sysdir_find_global_file, GIT_CONFIG_FILENAME_XDG); -} - -int git_config__find_xdg(git_str *path) -{ - return git_sysdir_find_xdg_file(path, GIT_CONFIG_FILENAME_XDG); -} - -int git_config_find_system(git_buf *path) -{ - GIT_BUF_WRAP_PRIVATE(path, git_sysdir_find_global_file, GIT_CONFIG_FILENAME_SYSTEM); -} - -int git_config__find_system(git_str *path) -{ - return git_sysdir_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM); -} - -int git_config_find_programdata(git_buf *path) -{ - git_str str = GIT_STR_INIT; - int error; - - if ((error = git_buf_tostr(&str, path)) == 0 && - (error = git_config__find_programdata(&str)) == 0) - error = git_buf_fromstr(path, &str); - - git_str_dispose(&str); - return error; -} - -int git_config__find_programdata(git_str *path) -{ - int ret; - - ret = git_sysdir_find_programdata_file(path, GIT_CONFIG_FILENAME_PROGRAMDATA); - - if (ret != GIT_OK) - return ret; - - return git_fs_path_validate_system_file_ownership(path->ptr); -} - -int git_config__global_location(git_str *buf) -{ - const git_str *paths; - const char *sep, *start; - - if (git_sysdir_get(&paths, GIT_SYSDIR_GLOBAL) < 0) - return -1; - - /* no paths, so give up */ - if (!paths || !git_str_len(paths)) - return -1; - - /* find unescaped separator or end of string */ - for (sep = start = git_str_cstr(paths); *sep; ++sep) { - if (*sep == GIT_PATH_LIST_SEPARATOR && - (sep <= start || sep[-1] != '\\')) - break; - } - - if (git_str_set(buf, start, (size_t)(sep - start)) < 0) - return -1; - - return git_str_joinpath(buf, buf->ptr, GIT_CONFIG_FILENAME_GLOBAL); -} - -int git_config_open_default(git_config **out) -{ - int error; - git_config *cfg = NULL; - git_str buf = GIT_STR_INIT; - - if ((error = git_config_new(&cfg)) < 0) - return error; - - if (!git_config__find_global(&buf) || - !git_config__global_location(&buf)) { - error = git_config_add_file_ondisk(cfg, buf.ptr, - GIT_CONFIG_LEVEL_GLOBAL, NULL, 0); - } - - if (!error && !git_config__find_xdg(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, - GIT_CONFIG_LEVEL_XDG, NULL, 0); - - if (!error && !git_config__find_system(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, - GIT_CONFIG_LEVEL_SYSTEM, NULL, 0); - - if (!error && !git_config__find_programdata(&buf)) - error = git_config_add_file_ondisk(cfg, buf.ptr, - GIT_CONFIG_LEVEL_PROGRAMDATA, NULL, 0); - - git_str_dispose(&buf); - - if (error) { - git_config_free(cfg); - cfg = NULL; - } - - *out = cfg; - - return error; -} - -int git_config_lock(git_transaction **out, git_config *cfg) -{ - int error; - git_config_backend *backend; - backend_internal *internal; - - GIT_ASSERT_ARG(cfg); - - internal = git_vector_get(&cfg->backends, 0); - if (!internal || !internal->backend) { - git_error_set(GIT_ERROR_CONFIG, "cannot lock; the config has no backends"); - return -1; - } - backend = internal->backend; - - if ((error = backend->lock(backend)) < 0) - return error; - - return git_transaction_config_new(out, cfg); -} - -int git_config_unlock(git_config *cfg, int commit) -{ - git_config_backend *backend; - backend_internal *internal; - - GIT_ASSERT_ARG(cfg); - - internal = git_vector_get(&cfg->backends, 0); - if (!internal || !internal->backend) { - git_error_set(GIT_ERROR_CONFIG, "cannot lock; the config has no backends"); - return -1; - } - - backend = internal->backend; - - return backend->unlock(backend, commit); -} - -/*********** - * Parsers - ***********/ - -int git_config_lookup_map_value( - int *out, - const git_configmap *maps, - size_t map_n, - const char *value) -{ - size_t i; - - for (i = 0; i < map_n; ++i) { - const git_configmap *m = maps + i; - - switch (m->type) { - case GIT_CONFIGMAP_FALSE: - case GIT_CONFIGMAP_TRUE: { - int bool_val; - - if (git_config_parse_bool(&bool_val, value) == 0 && - bool_val == (int)m->type) { - *out = m->map_value; - return 0; - } - break; - } - - case GIT_CONFIGMAP_INT32: - if (git_config_parse_int32(out, value) == 0) - return 0; - break; - - case GIT_CONFIGMAP_STRING: - if (value && strcasecmp(value, m->str_match) == 0) { - *out = m->map_value; - return 0; - } - break; - } - } - - git_error_set(GIT_ERROR_CONFIG, "failed to map '%s'", value); - return -1; -} - -int git_config_lookup_map_enum(git_configmap_t *type_out, const char **str_out, - const git_configmap *maps, size_t map_n, int enum_val) -{ - size_t i; - - for (i = 0; i < map_n; i++) { - const git_configmap *m = &maps[i]; - - if (m->map_value != enum_val) - continue; - - *type_out = m->type; - *str_out = m->str_match; - return 0; - } - - git_error_set(GIT_ERROR_CONFIG, "invalid enum value"); - return GIT_ENOTFOUND; -} - -int git_config_parse_bool(int *out, const char *value) -{ - if (git__parse_bool(out, value) == 0) - return 0; - - if (git_config_parse_int32(out, value) == 0) { - *out = !!(*out); - return 0; - } - - git_error_set(GIT_ERROR_CONFIG, "failed to parse '%s' as a boolean value", value); - return -1; -} - -int git_config_parse_int64(int64_t *out, const char *value) -{ - const char *num_end; - int64_t num; - - if (!value || git__strntol64(&num, value, strlen(value), &num_end, 0) < 0) - goto fail_parse; - - switch (*num_end) { - case 'g': - case 'G': - num *= 1024; - /* fallthrough */ - - case 'm': - case 'M': - num *= 1024; - /* fallthrough */ - - case 'k': - case 'K': - num *= 1024; - - /* check that that there are no more characters after the - * given modifier suffix */ - if (num_end[1] != '\0') - return -1; - - /* fallthrough */ - - case '\0': - *out = num; - return 0; - - default: - goto fail_parse; - } - -fail_parse: - git_error_set(GIT_ERROR_CONFIG, "failed to parse '%s' as an integer", value ? value : "(null)"); - return -1; -} - -int git_config_parse_int32(int32_t *out, const char *value) -{ - int64_t tmp; - int32_t truncate; - - if (git_config_parse_int64(&tmp, value) < 0) - goto fail_parse; - - truncate = tmp & 0xFFFFFFFF; - if (truncate != tmp) - goto fail_parse; - - *out = truncate; - return 0; - -fail_parse: - git_error_set(GIT_ERROR_CONFIG, "failed to parse '%s' as a 32-bit integer", value ? value : "(null)"); - return -1; -} - -static int normalize_section(char *start, char *end) -{ - char *scan; - - if (start == end) - return GIT_EINVALIDSPEC; - - /* Validate and downcase range */ - for (scan = start; *scan; ++scan) { - if (end && scan >= end) - break; - if (isalnum(*scan)) - *scan = (char)git__tolower(*scan); - else if (*scan != '-' || scan == start) - return GIT_EINVALIDSPEC; - } - - if (scan == start) - return GIT_EINVALIDSPEC; - - return 0; -} - - -/* Take something the user gave us and make it nice for our hash function */ -int git_config__normalize_name(const char *in, char **out) -{ - char *name, *fdot, *ldot; - - GIT_ASSERT_ARG(in); - GIT_ASSERT_ARG(out); - - name = git__strdup(in); - GIT_ERROR_CHECK_ALLOC(name); - - fdot = strchr(name, '.'); - ldot = strrchr(name, '.'); - - if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1]) - goto invalid; - - /* Validate and downcase up to first dot and after last dot */ - if (normalize_section(name, fdot) < 0 || - normalize_section(ldot + 1, NULL) < 0) - goto invalid; - - /* If there is a middle range, make sure it doesn't have newlines */ - while (fdot < ldot) - if (*fdot++ == '\n') - goto invalid; - - *out = name; - return 0; - -invalid: - git__free(name); - git_error_set(GIT_ERROR_CONFIG, "invalid config item name '%s'", in); - return GIT_EINVALIDSPEC; -} - -struct rename_data { - git_config *config; - git_str *name; - size_t old_len; -}; - -static int rename_config_entries_cb( - const git_config_entry *entry, - void *payload) -{ - int error = 0; - struct rename_data *data = (struct rename_data *)payload; - size_t base_len = git_str_len(data->name); - - if (base_len > 0 && - !(error = git_str_puts(data->name, entry->name + data->old_len))) - { - error = git_config_set_string( - data->config, git_str_cstr(data->name), entry->value); - - git_str_truncate(data->name, base_len); - } - - if (!error) - error = git_config_delete_entry(data->config, entry->name); - - return error; -} - -int git_config_rename_section( - git_repository *repo, - const char *old_section_name, - const char *new_section_name) -{ - git_config *config; - git_str pattern = GIT_STR_INIT, replace = GIT_STR_INIT; - int error = 0; - struct rename_data data; - - git_str_puts_escape_regex(&pattern, old_section_name); - - if ((error = git_str_puts(&pattern, "\\..+")) < 0) - goto cleanup; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - goto cleanup; - - data.config = config; - data.name = &replace; - data.old_len = strlen(old_section_name) + 1; - - if ((error = git_str_join(&replace, '.', new_section_name, "")) < 0) - goto cleanup; - - if (new_section_name != NULL && - (error = normalize_section(replace.ptr, strchr(replace.ptr, '.'))) < 0) - { - git_error_set( - GIT_ERROR_CONFIG, "invalid config section '%s'", new_section_name); - goto cleanup; - } - - error = git_config_foreach_match( - config, git_str_cstr(&pattern), rename_config_entries_cb, &data); - -cleanup: - git_str_dispose(&pattern); - git_str_dispose(&replace); - - return error; -} - -int git_config_init_backend(git_config_backend *backend, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - backend, version, git_config_backend, GIT_CONFIG_BACKEND_INIT); - return 0; -} diff --git a/src/config.h b/src/config.h deleted file mode 100644 index 01b84b157..000000000 --- a/src/config.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_config_h__ -#define INCLUDE_config_h__ - -#include "common.h" - -#include "git2.h" -#include "git2/config.h" -#include "vector.h" -#include "repository.h" - -#define GIT_CONFIG_FILENAME_PROGRAMDATA "config" -#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig" -#define GIT_CONFIG_FILENAME_GLOBAL ".gitconfig" -#define GIT_CONFIG_FILENAME_XDG "config" - -#define GIT_CONFIG_FILENAME_INREPO "config" -#define GIT_CONFIG_FILE_MODE 0666 - -struct git_config { - git_refcount rc; - git_vector backends; -}; - -extern int git_config__global_location(git_str *buf); - -extern int git_config__find_global(git_str *path); -extern int git_config__find_xdg(git_str *path); -extern int git_config__find_system(git_str *path); -extern int git_config__find_programdata(git_str *path); - -extern int git_config_rename_section( - git_repository *repo, - const char *old_section_name, /* eg "branch.dummy" */ - const char *new_section_name); /* NULL to drop the old section */ - -extern int git_config__normalize_name(const char *in, char **out); - -/* internal only: does not normalize key and sets out to NULL if not found */ -extern int git_config__lookup_entry( - git_config_entry **out, - const git_config *cfg, - const char *key, - bool no_errors); - -/* internal only: update and/or delete entry string with constraints */ -extern int git_config__update_entry( - git_config *cfg, - const char *key, - const char *value, - bool overwrite_existing, - bool only_if_existing); - -int git_config__get_path( - git_str *out, - const git_config *cfg, - const char *name); - -int git_config__get_string_buf( - git_str *out, const git_config *cfg, const char *name); - -/* - * Lookup functions that cannot fail. These functions look up a config - * value and return a fallback value if the value is missing or if any - * failures occur while trying to access the value. - */ - -extern char *git_config__get_string_force( - const git_config *cfg, const char *key, const char *fallback_value); - -extern int git_config__get_bool_force( - const git_config *cfg, const char *key, int fallback_value); - -extern int git_config__get_int_force( - const git_config *cfg, const char *key, int fallback_value); - -/* API for repository configmap-style lookups from config - not cached, but - * uses configmap value maps and fallbacks - */ -extern int git_config__configmap_lookup( - int *out, git_config *config, git_configmap_item item); - -/** - * The opposite of git_config_lookup_map_value, we take an enum value - * and map it to the string or bool value on the config. - */ -int git_config_lookup_map_enum(git_configmap_t *type_out, - const char **str_out, const git_configmap *maps, - size_t map_n, int enum_val); - -/** - * Unlock the backend with the highest priority - * - * Unlocking will allow other writers to update the configuration - * file. Optionally, any changes performed since the lock will be - * applied to the configuration. - * - * @param cfg the configuration - * @param commit boolean which indicates whether to commit any changes - * done since locking - * @return 0 or an error code - */ -GIT_EXTERN(int) git_config_unlock(git_config *cfg, int commit); - -#endif diff --git a/src/config_backend.h b/src/config_backend.h deleted file mode 100644 index dbb190514..000000000 --- a/src/config_backend.h +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_config_file_h__ -#define INCLUDE_config_file_h__ - -#include "common.h" - -#include "git2/sys/config.h" -#include "git2/config.h" - -/** - * Create a configuration file backend for ondisk files - * - * These are the normal `.gitconfig` files that Core Git - * processes. Note that you first have to add this file to a - * configuration object before you can query it for configuration - * variables. - * - * @param out the new backend - * @param path where the config file is located - */ -extern int git_config_backend_from_file(git_config_backend **out, const char *path); - -/** - * Create a readonly configuration file backend from another backend - * - * This copies the complete contents of the source backend to the - * new backend. The new backend will be completely read-only and - * cannot be modified. - * - * @param out the new snapshotted backend - * @param source the backend to copy - */ -extern int git_config_backend_snapshot(git_config_backend **out, git_config_backend *source); - -/** - * Create an in-memory configuration file backend - * - * @param out the new backend - * @param cfg the configuration that is to be parsed - * @param len the length of the string pointed to by `cfg` - */ -extern int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len); - -GIT_INLINE(int) git_config_backend_open(git_config_backend *cfg, unsigned int level, const git_repository *repo) -{ - return cfg->open(cfg, level, repo); -} - -GIT_INLINE(void) git_config_backend_free(git_config_backend *cfg) -{ - if (cfg) - cfg->free(cfg); -} - -GIT_INLINE(int) git_config_backend_get_string( - git_config_entry **out, git_config_backend *cfg, const char *name) -{ - return cfg->get(cfg, name, out); -} - -GIT_INLINE(int) git_config_backend_set_string( - git_config_backend *cfg, const char *name, const char *value) -{ - return cfg->set(cfg, name, value); -} - -GIT_INLINE(int) git_config_backend_delete( - git_config_backend *cfg, const char *name) -{ - return cfg->del(cfg, name); -} - -GIT_INLINE(int) git_config_backend_foreach( - git_config_backend *cfg, - int (*fn)(const git_config_entry *entry, void *data), - void *data) -{ - return git_config_backend_foreach_match(cfg, NULL, fn, data); -} - -GIT_INLINE(int) git_config_backend_lock(git_config_backend *cfg) -{ - return cfg->lock(cfg); -} - -GIT_INLINE(int) git_config_backend_unlock(git_config_backend *cfg, int success) -{ - return cfg->unlock(cfg, success); -} - -#endif diff --git a/src/config_cache.c b/src/config_cache.c deleted file mode 100644 index 4bb91f52b..000000000 --- a/src/config_cache.c +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "futils.h" -#include "repository.h" -#include "config.h" -#include "git2/config.h" -#include "vector.h" -#include "filter.h" - -struct map_data { - const char *name; - git_configmap *maps; - size_t map_count; - int default_value; -}; - -/* - * core.eol - * Sets the line ending type to use in the working directory for - * files that have the text property set. Alternatives are lf, crlf - * and native, which uses the platform's native line ending. The default - * value is native. See gitattributes(5) for more information on - * end-of-line conversion. - */ -static git_configmap _configmap_eol[] = { - {GIT_CONFIGMAP_FALSE, NULL, GIT_EOL_UNSET}, - {GIT_CONFIGMAP_STRING, "lf", GIT_EOL_LF}, - {GIT_CONFIGMAP_STRING, "crlf", GIT_EOL_CRLF}, - {GIT_CONFIGMAP_STRING, "native", GIT_EOL_NATIVE} -}; - -/* - * core.autocrlf - * Setting this variable to "true" is almost the same as setting - * the text attribute to "auto" on all files except that text files are - * not guaranteed to be normalized: files that contain CRLF in the - * repository will not be touched. Use this setting if you want to have - * CRLF line endings in your working directory even though the repository - * does not have normalized line endings. This variable can be set to input, - * in which case no output conversion is performed. - */ -static git_configmap _configmap_autocrlf[] = { - {GIT_CONFIGMAP_FALSE, NULL, GIT_AUTO_CRLF_FALSE}, - {GIT_CONFIGMAP_TRUE, NULL, GIT_AUTO_CRLF_TRUE}, - {GIT_CONFIGMAP_STRING, "input", GIT_AUTO_CRLF_INPUT} -}; - -static git_configmap _configmap_safecrlf[] = { - {GIT_CONFIGMAP_FALSE, NULL, GIT_SAFE_CRLF_FALSE}, - {GIT_CONFIGMAP_TRUE, NULL, GIT_SAFE_CRLF_FAIL}, - {GIT_CONFIGMAP_STRING, "warn", GIT_SAFE_CRLF_WARN} -}; - -static git_configmap _configmap_logallrefupdates[] = { - {GIT_CONFIGMAP_FALSE, NULL, GIT_LOGALLREFUPDATES_FALSE}, - {GIT_CONFIGMAP_TRUE, NULL, GIT_LOGALLREFUPDATES_TRUE}, - {GIT_CONFIGMAP_STRING, "always", GIT_LOGALLREFUPDATES_ALWAYS}, -}; - -/* - * Generic map for integer values - */ -static git_configmap _configmap_int[] = { - {GIT_CONFIGMAP_INT32, NULL, 0}, -}; - -static struct map_data _configmaps[] = { - {"core.autocrlf", _configmap_autocrlf, ARRAY_SIZE(_configmap_autocrlf), GIT_AUTO_CRLF_DEFAULT}, - {"core.eol", _configmap_eol, ARRAY_SIZE(_configmap_eol), GIT_EOL_DEFAULT}, - {"core.symlinks", NULL, 0, GIT_SYMLINKS_DEFAULT }, - {"core.ignorecase", NULL, 0, GIT_IGNORECASE_DEFAULT }, - {"core.filemode", NULL, 0, GIT_FILEMODE_DEFAULT }, - {"core.ignorestat", NULL, 0, GIT_IGNORESTAT_DEFAULT }, - {"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT }, - {"core.abbrev", _configmap_int, 1, GIT_ABBREV_DEFAULT }, - {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT }, - {"core.safecrlf", _configmap_safecrlf, ARRAY_SIZE(_configmap_safecrlf), GIT_SAFE_CRLF_DEFAULT}, - {"core.logallrefupdates", _configmap_logallrefupdates, ARRAY_SIZE(_configmap_logallrefupdates), GIT_LOGALLREFUPDATES_DEFAULT}, - {"core.protecthfs", NULL, 0, GIT_PROTECTHFS_DEFAULT }, - {"core.protectntfs", NULL, 0, GIT_PROTECTNTFS_DEFAULT }, - {"core.fsyncobjectfiles", NULL, 0, GIT_FSYNCOBJECTFILES_DEFAULT }, - {"core.longpaths", NULL, 0, GIT_LONGPATHS_DEFAULT }, -}; - -int git_config__configmap_lookup(int *out, git_config *config, git_configmap_item item) -{ - int error = 0; - struct map_data *data = &_configmaps[(int)item]; - git_config_entry *entry; - - if ((error = git_config__lookup_entry(&entry, config, data->name, false)) < 0) - return error; - - if (!entry) - *out = data->default_value; - else if (data->maps) - error = git_config_lookup_map_value( - out, data->maps, data->map_count, entry->value); - else - error = git_config_parse_bool(out, entry->value); - - git_config_entry_free(entry); - return error; -} - -int git_repository__configmap_lookup(int *out, git_repository *repo, git_configmap_item item) -{ - intptr_t value = (intptr_t)git_atomic_load(repo->configmap_cache[(int)item]); - - *out = (int)value; - - if (value == GIT_CONFIGMAP_NOT_CACHED) { - git_config *config; - intptr_t oldval = value; - int error; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0 || - (error = git_config__configmap_lookup(out, config, item)) < 0) - return error; - - value = *out; - git_atomic_compare_and_swap(&repo->configmap_cache[(int)item], (void *)oldval, (void *)value); - } - - return 0; -} - -void git_repository__configmap_lookup_cache_clear(git_repository *repo) -{ - int i; - - for (i = 0; i < GIT_CONFIGMAP_CACHE_MAX; ++i) - repo->configmap_cache[i] = GIT_CONFIGMAP_NOT_CACHED; -} - diff --git a/src/config_entries.c b/src/config_entries.c deleted file mode 100644 index 66aae096d..000000000 --- a/src/config_entries.c +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "config_entries.h" - -typedef struct config_entry_list { - struct config_entry_list *next; - struct config_entry_list *last; - git_config_entry *entry; -} config_entry_list; - -typedef struct { - git_config_entry *entry; - bool multivar; -} config_entry_map_head; - -typedef struct config_entries_iterator { - git_config_iterator parent; - git_config_entries *entries; - config_entry_list *head; -} config_entries_iterator; - -struct git_config_entries { - git_refcount rc; - git_strmap *map; - config_entry_list *list; -}; - -int git_config_entries_new(git_config_entries **out) -{ - git_config_entries *entries; - int error; - - entries = git__calloc(1, sizeof(git_config_entries)); - GIT_ERROR_CHECK_ALLOC(entries); - GIT_REFCOUNT_INC(entries); - - if ((error = git_strmap_new(&entries->map)) < 0) - git__free(entries); - else - *out = entries; - - return error; -} - -int git_config_entries_dup_entry(git_config_entries *entries, const git_config_entry *entry) -{ - git_config_entry *duplicated; - int error; - - duplicated = git__calloc(1, sizeof(git_config_entry)); - GIT_ERROR_CHECK_ALLOC(duplicated); - - duplicated->name = git__strdup(entry->name); - GIT_ERROR_CHECK_ALLOC(duplicated->name); - - if (entry->value) { - duplicated->value = git__strdup(entry->value); - GIT_ERROR_CHECK_ALLOC(duplicated->value); - } - duplicated->level = entry->level; - duplicated->include_depth = entry->include_depth; - - if ((error = git_config_entries_append(entries, duplicated)) < 0) - goto out; - -out: - if (error && duplicated) { - git__free((char *) duplicated->name); - git__free((char *) duplicated->value); - git__free(duplicated); - } - return error; -} - -int git_config_entries_dup(git_config_entries **out, git_config_entries *entries) -{ - git_config_entries *result = NULL; - config_entry_list *head; - int error; - - if ((error = git_config_entries_new(&result)) < 0) - goto out; - - for (head = entries->list; head; head = head->next) - if ((git_config_entries_dup_entry(result, head->entry)) < 0) - goto out; - - *out = result; - result = NULL; - -out: - git_config_entries_free(result); - return error; -} - -void git_config_entries_incref(git_config_entries *entries) -{ - GIT_REFCOUNT_INC(entries); -} - -static void config_entries_free(git_config_entries *entries) -{ - config_entry_list *list = NULL, *next; - config_entry_map_head *head; - - git_strmap_foreach_value(entries->map, head, - git__free((char *) head->entry->name); git__free(head) - ); - git_strmap_free(entries->map); - - list = entries->list; - while (list != NULL) { - next = list->next; - git__free((char *) list->entry->value); - git__free(list->entry); - git__free(list); - list = next; - } - - git__free(entries); -} - -void git_config_entries_free(git_config_entries *entries) -{ - if (entries) - GIT_REFCOUNT_DEC(entries, config_entries_free); -} - -int git_config_entries_append(git_config_entries *entries, git_config_entry *entry) -{ - config_entry_list *list_head; - config_entry_map_head *map_head; - - if ((map_head = git_strmap_get(entries->map, entry->name)) != NULL) { - map_head->multivar = true; - /* - * This is a micro-optimization for configuration files - * with a lot of same keys. As for multivars the entry's - * key will be the same for all entries, we can just free - * all except the first entry's name and just re-use it. - */ - git__free((char *) entry->name); - entry->name = map_head->entry->name; - } else { - map_head = git__calloc(1, sizeof(*map_head)); - if ((git_strmap_set(entries->map, entry->name, map_head)) < 0) - return -1; - } - map_head->entry = entry; - - list_head = git__calloc(1, sizeof(config_entry_list)); - GIT_ERROR_CHECK_ALLOC(list_head); - list_head->entry = entry; - - if (entries->list) - entries->list->last->next = list_head; - else - entries->list = list_head; - entries->list->last = list_head; - - return 0; -} - -int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key) -{ - config_entry_map_head *entry; - if ((entry = git_strmap_get(entries->map, key)) == NULL) - return GIT_ENOTFOUND; - *out = entry->entry; - return 0; -} - -int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key) -{ - config_entry_map_head *entry; - - if ((entry = git_strmap_get(entries->map, key)) == NULL) - return GIT_ENOTFOUND; - - if (entry->multivar) { - git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being a multivar"); - return -1; - } - - if (entry->entry->include_depth) { - git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being included"); - return -1; - } - - *out = entry->entry; - - return 0; -} - -static void config_iterator_free(git_config_iterator *iter) -{ - config_entries_iterator *it = (config_entries_iterator *) iter; - git_config_entries_free(it->entries); - git__free(it); -} - -static int config_iterator_next( - git_config_entry **entry, - git_config_iterator *iter) -{ - config_entries_iterator *it = (config_entries_iterator *) iter; - - if (!it->head) - return GIT_ITEROVER; - - *entry = it->head->entry; - it->head = it->head->next; - - return 0; -} - -int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries) -{ - config_entries_iterator *it; - - it = git__calloc(1, sizeof(config_entries_iterator)); - GIT_ERROR_CHECK_ALLOC(it); - it->parent.next = config_iterator_next; - it->parent.free = config_iterator_free; - it->head = entries->list; - it->entries = entries; - - git_config_entries_incref(entries); - *out = &it->parent; - - return 0; -} diff --git a/src/config_entries.h b/src/config_entries.h deleted file mode 100644 index 832379e74..000000000 --- a/src/config_entries.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "git2/sys/config.h" -#include "config.h" - -typedef struct git_config_entries git_config_entries; - -int git_config_entries_new(git_config_entries **out); -int git_config_entries_dup(git_config_entries **out, git_config_entries *entries); -int git_config_entries_dup_entry(git_config_entries *entries, const git_config_entry *entry); -void git_config_entries_incref(git_config_entries *entries); -void git_config_entries_free(git_config_entries *entries); -/* Add or append the new config option */ -int git_config_entries_append(git_config_entries *entries, git_config_entry *entry); -int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key); -int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key); -int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries); diff --git a/src/config_file.c b/src/config_file.c deleted file mode 100644 index 66fcb8ae2..000000000 --- a/src/config_file.c +++ /dev/null @@ -1,1194 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "config.h" - -#include "git2/config.h" -#include "git2/sys/config.h" - -#include "array.h" -#include "str.h" -#include "config_backend.h" -#include "config_entries.h" -#include "config_parse.h" -#include "filebuf.h" -#include "regexp.h" -#include "sysdir.h" -#include "wildmatch.h" -#include "hash.h" - -/* Max depth for [include] directives */ -#define MAX_INCLUDE_DEPTH 10 - -typedef struct config_file { - git_futils_filestamp stamp; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - char *path; - git_array_t(struct config_file) includes; -} config_file; - -typedef struct { - git_config_backend parent; - git_mutex values_mutex; - git_config_entries *entries; - const git_repository *repo; - git_config_level_t level; - - git_array_t(git_config_parser) readers; - - bool locked; - git_filebuf locked_buf; - git_str locked_content; - - config_file file; -} config_file_backend; - -typedef struct { - const git_repository *repo; - config_file *file; - git_config_entries *entries; - git_config_level_t level; - unsigned int depth; -} config_file_parse_data; - -static int config_file_read(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth); -static int config_file_read_buffer(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth, const char *buf, size_t buflen); -static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value); -static char *escape_value(const char *ptr); - -/** - * Take the current values map from the backend and increase its - * refcount. This is its own function to make sure we use the mutex to - * avoid the map pointer from changing under us. - */ -static int config_file_entries_take(git_config_entries **out, config_file_backend *b) -{ - int error; - - if ((error = git_mutex_lock(&b->values_mutex)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock config backend"); - return error; - } - - git_config_entries_incref(b->entries); - *out = b->entries; - - git_mutex_unlock(&b->values_mutex); - - return 0; -} - -static void config_file_clear(config_file *file) -{ - config_file *include; - uint32_t i; - - if (file == NULL) - return; - - git_array_foreach(file->includes, i, include) { - config_file_clear(include); - } - git_array_clear(file->includes); - - git__free(file->path); -} - -static int config_file_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo) -{ - config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - int res; - - b->level = level; - b->repo = repo; - - if ((res = git_config_entries_new(&b->entries)) < 0) - return res; - - if (!git_fs_path_exists(b->file.path)) - return 0; - - /* - * git silently ignores configuration files that are not - * readable. We emulate that behavior. This is particularly - * important for sandboxed applications on macOS where the - * git configuration files may not be readable. - */ - if (p_access(b->file.path, R_OK) < 0) - return GIT_ENOTFOUND; - - if (res < 0 || (res = config_file_read(b->entries, repo, &b->file, level, 0)) < 0) { - git_config_entries_free(b->entries); - b->entries = NULL; - } - - return res; -} - -static int config_file_is_modified(int *modified, config_file *file) -{ - config_file *include; - git_str buf = GIT_STR_INIT; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - uint32_t i; - int error = 0; - - *modified = 0; - - if (!git_futils_filestamp_check(&file->stamp, file->path)) - goto check_includes; - - if ((error = git_futils_readbuffer(&buf, file->path)) < 0) - goto out; - - if ((error = git_hash_buf(checksum, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0) - goto out; - - if (memcmp(checksum, file->checksum, GIT_HASH_SHA1_SIZE) != 0) { - *modified = 1; - goto out; - } - -check_includes: - git_array_foreach(file->includes, i, include) { - if ((error = config_file_is_modified(modified, include)) < 0 || *modified) - goto out; - } - -out: - git_str_dispose(&buf); - - return error; -} - -static void config_file_clear_includes(config_file_backend *cfg) -{ - config_file *include; - uint32_t i; - - git_array_foreach(cfg->file.includes, i, include) - config_file_clear(include); - git_array_clear(cfg->file.includes); -} - -static int config_file_set_entries(git_config_backend *cfg, git_config_entries *entries) -{ - config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *old = NULL; - int error; - - if (b->parent.readonly) { - git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); - return -1; - } - - if ((error = git_mutex_lock(&b->values_mutex)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock config backend"); - goto out; - } - - old = b->entries; - b->entries = entries; - - git_mutex_unlock(&b->values_mutex); - -out: - git_config_entries_free(old); - return error; -} - -static int config_file_refresh_from_buffer(git_config_backend *cfg, const char *buf, size_t buflen) -{ - config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries = NULL; - int error; - - config_file_clear_includes(b); - - if ((error = git_config_entries_new(&entries)) < 0 || - (error = config_file_read_buffer(entries, b->repo, &b->file, - b->level, 0, buf, buflen)) < 0 || - (error = config_file_set_entries(cfg, entries)) < 0) - goto out; - - entries = NULL; -out: - git_config_entries_free(entries); - return error; -} - -static int config_file_refresh(git_config_backend *cfg) -{ - config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries = NULL; - int error, modified; - - if (cfg->readonly) - return 0; - - if ((error = config_file_is_modified(&modified, &b->file)) < 0 && error != GIT_ENOTFOUND) - goto out; - - if (!modified) - return 0; - - config_file_clear_includes(b); - - if ((error = git_config_entries_new(&entries)) < 0 || - (error = config_file_read(entries, b->repo, &b->file, b->level, 0)) < 0 || - (error = config_file_set_entries(cfg, entries)) < 0) - goto out; - - entries = NULL; -out: - git_config_entries_free(entries); - - return (error == GIT_ENOTFOUND) ? 0 : error; -} - -static void config_file_free(git_config_backend *_backend) -{ - config_file_backend *backend = GIT_CONTAINER_OF(_backend, config_file_backend, parent); - - if (backend == NULL) - return; - - config_file_clear(&backend->file); - git_config_entries_free(backend->entries); - git_mutex_free(&backend->values_mutex); - git__free(backend); -} - -static int config_file_iterator( - git_config_iterator **iter, - struct git_config_backend *backend) -{ - config_file_backend *b = GIT_CONTAINER_OF(backend, config_file_backend, parent); - git_config_entries *dupped = NULL, *entries = NULL; - int error; - - if ((error = config_file_refresh(backend)) < 0 || - (error = config_file_entries_take(&entries, b)) < 0 || - (error = git_config_entries_dup(&dupped, entries)) < 0 || - (error = git_config_entries_iterator_new(iter, dupped)) < 0) - goto out; - -out: - /* Let iterator delete duplicated entries when it's done */ - git_config_entries_free(entries); - git_config_entries_free(dupped); - return error; -} - -static int config_file_snapshot(git_config_backend **out, git_config_backend *backend) -{ - return git_config_backend_snapshot(out, backend); -} - -static int config_file_set(git_config_backend *cfg, const char *name, const char *value) -{ - config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries; - git_config_entry *existing; - char *key, *esc_value = NULL; - int error; - - if ((error = git_config__normalize_name(name, &key)) < 0) - return error; - - if ((error = config_file_entries_take(&entries, b)) < 0) - return error; - - /* Check whether we'd be modifying an included or multivar key */ - if ((error = git_config_entries_get_unique(&existing, entries, key)) < 0) { - if (error != GIT_ENOTFOUND) - goto out; - error = 0; - } else if ((!existing->value && !value) || - (existing->value && value && !strcmp(existing->value, value))) { - /* don't update if old and new values already match */ - error = 0; - goto out; - } - - /* No early returns due to sanity checks, let's write it out and refresh */ - if (value) { - esc_value = escape_value(value); - GIT_ERROR_CHECK_ALLOC(esc_value); - } - - if ((error = config_file_write(b, name, key, NULL, esc_value)) < 0) - goto out; - -out: - git_config_entries_free(entries); - git__free(esc_value); - git__free(key); - return error; -} - -/* release the map containing the entry as an equivalent to freeing it */ -static void config_file_entry_free(git_config_entry *entry) -{ - git_config_entries *entries = (git_config_entries *) entry->payload; - git_config_entries_free(entries); -} - -/* - * Internal function that actually gets the value in string form - */ -static int config_file_get(git_config_backend *cfg, const char *key, git_config_entry **out) -{ - config_file_backend *h = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries = NULL; - git_config_entry *entry; - int error = 0; - - if (!h->parent.readonly && ((error = config_file_refresh(cfg)) < 0)) - return error; - - if ((error = config_file_entries_take(&entries, h)) < 0) - return error; - - if ((error = (git_config_entries_get(&entry, entries, key))) < 0) { - git_config_entries_free(entries); - return error; - } - - entry->free = config_file_entry_free; - entry->payload = entries; - *out = entry; - - return 0; -} - -static int config_file_set_multivar( - git_config_backend *cfg, const char *name, const char *regexp, const char *value) -{ - config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_regexp preg; - int result; - char *key; - - GIT_ASSERT_ARG(regexp); - - if ((result = git_config__normalize_name(name, &key)) < 0) - return result; - - if ((result = git_regexp_compile(&preg, regexp, 0)) < 0) - goto out; - - /* If we do have it, set call config_file_write() and reload */ - if ((result = config_file_write(b, name, key, &preg, value)) < 0) - goto out; - -out: - git__free(key); - git_regexp_dispose(&preg); - - return result; -} - -static int config_file_delete(git_config_backend *cfg, const char *name) -{ - config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries = NULL; - git_config_entry *entry; - char *key = NULL; - int error; - - if ((error = git_config__normalize_name(name, &key)) < 0) - goto out; - - if ((error = config_file_entries_take(&entries, b)) < 0) - goto out; - - /* Check whether we'd be modifying an included or multivar key */ - if ((error = git_config_entries_get_unique(&entry, entries, key)) < 0) { - if (error == GIT_ENOTFOUND) - git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name); - goto out; - } - - if ((error = config_file_write(b, name, entry->name, NULL, NULL)) < 0) - goto out; - -out: - git_config_entries_free(entries); - git__free(key); - return error; -} - -static int config_file_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) -{ - config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); - git_config_entries *entries = NULL; - git_config_entry *entry = NULL; - git_regexp preg = GIT_REGEX_INIT; - char *key = NULL; - int result; - - if ((result = git_config__normalize_name(name, &key)) < 0) - goto out; - - if ((result = config_file_entries_take(&entries, b)) < 0) - goto out; - - if ((result = git_config_entries_get(&entry, entries, key)) < 0) { - if (result == GIT_ENOTFOUND) - git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name); - goto out; - } - - if ((result = git_regexp_compile(&preg, regexp, 0)) < 0) - goto out; - - if ((result = config_file_write(b, name, key, &preg, NULL)) < 0) - goto out; - -out: - git_config_entries_free(entries); - git__free(key); - git_regexp_dispose(&preg); - return result; -} - -static int config_file_lock(git_config_backend *_cfg) -{ - config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent); - int error; - - if ((error = git_filebuf_open(&cfg->locked_buf, cfg->file.path, 0, GIT_CONFIG_FILE_MODE)) < 0) - return error; - - error = git_futils_readbuffer(&cfg->locked_content, cfg->file.path); - if (error < 0 && error != GIT_ENOTFOUND) { - git_filebuf_cleanup(&cfg->locked_buf); - return error; - } - - cfg->locked = true; - return 0; - -} - -static int config_file_unlock(git_config_backend *_cfg, int success) -{ - config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent); - int error = 0; - - if (success) { - git_filebuf_write(&cfg->locked_buf, cfg->locked_content.ptr, cfg->locked_content.size); - error = git_filebuf_commit(&cfg->locked_buf); - } - - git_filebuf_cleanup(&cfg->locked_buf); - git_str_dispose(&cfg->locked_content); - cfg->locked = false; - - return error; -} - -int git_config_backend_from_file(git_config_backend **out, const char *path) -{ - config_file_backend *backend; - - backend = git__calloc(1, sizeof(config_file_backend)); - GIT_ERROR_CHECK_ALLOC(backend); - - backend->parent.version = GIT_CONFIG_BACKEND_VERSION; - git_mutex_init(&backend->values_mutex); - - backend->file.path = git__strdup(path); - GIT_ERROR_CHECK_ALLOC(backend->file.path); - git_array_init(backend->file.includes); - - backend->parent.open = config_file_open; - backend->parent.get = config_file_get; - backend->parent.set = config_file_set; - backend->parent.set_multivar = config_file_set_multivar; - backend->parent.del = config_file_delete; - backend->parent.del_multivar = config_file_delete_multivar; - backend->parent.iterator = config_file_iterator; - backend->parent.snapshot = config_file_snapshot; - backend->parent.lock = config_file_lock; - backend->parent.unlock = config_file_unlock; - backend->parent.free = config_file_free; - - *out = (git_config_backend *)backend; - - return 0; -} - -static int included_path(git_str *out, const char *dir, const char *path) -{ - /* From the user's home */ - if (path[0] == '~' && path[1] == '/') - return git_sysdir_expand_global_file(out, &path[1]); - - return git_fs_path_join_unrooted(out, path, dir, NULL); -} - -/* Escape the values to write them to the file */ -static char *escape_value(const char *ptr) -{ - git_str buf; - size_t len; - const char *esc; - - GIT_ASSERT_ARG_WITH_RETVAL(ptr, NULL); - - len = strlen(ptr); - if (!len) - return git__calloc(1, sizeof(char)); - - if (git_str_init(&buf, len) < 0) - return NULL; - - while (*ptr != '\0') { - if ((esc = strchr(git_config_escaped, *ptr)) != NULL) { - git_str_putc(&buf, '\\'); - git_str_putc(&buf, git_config_escapes[esc - git_config_escaped]); - } else { - git_str_putc(&buf, *ptr); - } - ptr++; - } - - if (git_str_oom(&buf)) - return NULL; - - return git_str_detach(&buf); -} - -static int parse_include(config_file_parse_data *parse_data, const char *file) -{ - config_file *include; - git_str path = GIT_STR_INIT; - char *dir; - int result; - - if (!file) - return 0; - - if ((result = git_fs_path_dirname_r(&path, parse_data->file->path)) < 0) - return result; - - dir = git_str_detach(&path); - result = included_path(&path, dir, file); - git__free(dir); - - if (result < 0) - return result; - - include = git_array_alloc(parse_data->file->includes); - GIT_ERROR_CHECK_ALLOC(include); - memset(include, 0, sizeof(*include)); - git_array_init(include->includes); - include->path = git_str_detach(&path); - - result = config_file_read(parse_data->entries, parse_data->repo, include, - parse_data->level, parse_data->depth+1); - - if (result == GIT_ENOTFOUND) { - git_error_clear(); - result = 0; - } - - return result; -} - -static int do_match_gitdir( - int *matches, - const git_repository *repo, - const char *cfg_file, - const char *condition, - bool case_insensitive) -{ - git_str pattern = GIT_STR_INIT, gitdir = GIT_STR_INIT; - int error; - - if (condition[0] == '.' && git_fs_path_is_dirsep(condition[1])) { - git_fs_path_dirname_r(&pattern, cfg_file); - git_str_joinpath(&pattern, pattern.ptr, condition + 2); - } else if (condition[0] == '~' && git_fs_path_is_dirsep(condition[1])) - git_sysdir_expand_global_file(&pattern, condition + 1); - else if (!git_fs_path_is_absolute(condition)) - git_str_joinpath(&pattern, "**", condition); - else - git_str_sets(&pattern, condition); - - if (git_fs_path_is_dirsep(condition[strlen(condition) - 1])) - git_str_puts(&pattern, "**"); - - if (git_str_oom(&pattern)) { - error = -1; - goto out; - } - - if ((error = git_repository__item_path(&gitdir, repo, GIT_REPOSITORY_ITEM_GITDIR)) < 0) - goto out; - - if (git_fs_path_is_dirsep(gitdir.ptr[gitdir.size - 1])) - git_str_truncate(&gitdir, gitdir.size - 1); - - *matches = wildmatch(pattern.ptr, gitdir.ptr, - WM_PATHNAME | (case_insensitive ? WM_CASEFOLD : 0)) == WM_MATCH; -out: - git_str_dispose(&pattern); - git_str_dispose(&gitdir); - return error; -} - -static int conditional_match_gitdir( - int *matches, - const git_repository *repo, - const char *cfg_file, - const char *value) -{ - return do_match_gitdir(matches, repo, cfg_file, value, false); -} - -static int conditional_match_gitdir_i( - int *matches, - const git_repository *repo, - const char *cfg_file, - const char *value) -{ - return do_match_gitdir(matches, repo, cfg_file, value, true); -} - -static int conditional_match_onbranch( - int *matches, - const git_repository *repo, - const char *cfg_file, - const char *condition) -{ - git_str reference = GIT_STR_INIT, buf = GIT_STR_INIT; - int error; - - GIT_UNUSED(cfg_file); - - /* - * NOTE: you cannot use `git_repository_head` here. Looking up the - * HEAD reference will create the ODB, which causes us to read the - * repo's config for keys like core.precomposeUnicode. As we're - * just parsing the config right now, though, this would result in - * an endless recursion. - */ - - if ((error = git_str_joinpath(&buf, git_repository_path(repo), GIT_HEAD_FILE)) < 0 || - (error = git_futils_readbuffer(&reference, buf.ptr)) < 0) - goto out; - git_str_rtrim(&reference); - - if (git__strncmp(reference.ptr, GIT_SYMREF, strlen(GIT_SYMREF))) - goto out; - git_str_consume(&reference, reference.ptr + strlen(GIT_SYMREF)); - - if (git__strncmp(reference.ptr, GIT_REFS_HEADS_DIR, strlen(GIT_REFS_HEADS_DIR))) - goto out; - git_str_consume(&reference, reference.ptr + strlen(GIT_REFS_HEADS_DIR)); - - /* - * If the condition ends with a '/', then we should treat it as if - * it had '**' appended. - */ - if ((error = git_str_sets(&buf, condition)) < 0) - goto out; - if (git_fs_path_is_dirsep(condition[strlen(condition) - 1]) && - (error = git_str_puts(&buf, "**")) < 0) - goto out; - - *matches = wildmatch(buf.ptr, reference.ptr, WM_PATHNAME) == WM_MATCH; -out: - git_str_dispose(&reference); - git_str_dispose(&buf); - - return error; - -} - -static const struct { - const char *prefix; - int (*matches)(int *matches, const git_repository *repo, const char *cfg, const char *value); -} conditions[] = { - { "gitdir:", conditional_match_gitdir }, - { "gitdir/i:", conditional_match_gitdir_i }, - { "onbranch:", conditional_match_onbranch } -}; - -static int parse_conditional_include(config_file_parse_data *parse_data, const char *section, const char *file) -{ - char *condition; - size_t section_len, i; - int error = 0, matches; - - if (!parse_data->repo || !file) - return 0; - - section_len = strlen(section); - - /* - * We checked that the string starts with `includeIf.` and ends - * in `.path` to get here. Make sure it consists of more. - */ - if (section_len < CONST_STRLEN("includeIf.") + CONST_STRLEN(".path")) - return 0; - - condition = git__substrdup(section + CONST_STRLEN("includeIf."), - section_len - CONST_STRLEN("includeIf.") - CONST_STRLEN(".path")); - - GIT_ERROR_CHECK_ALLOC(condition); - - for (i = 0; i < ARRAY_SIZE(conditions); i++) { - if (git__prefixcmp(condition, conditions[i].prefix)) - continue; - - if ((error = conditions[i].matches(&matches, - parse_data->repo, - parse_data->file->path, - condition + strlen(conditions[i].prefix))) < 0) - break; - - if (matches) - error = parse_include(parse_data, file); - - break; - } - - git__free(condition); - return error; -} - -static int read_on_variable( - git_config_parser *reader, - const char *current_section, - const char *var_name, - const char *var_value, - const char *line, - size_t line_len, - void *data) -{ - config_file_parse_data *parse_data = (config_file_parse_data *)data; - git_str buf = GIT_STR_INIT; - git_config_entry *entry; - const char *c; - int result = 0; - - GIT_UNUSED(reader); - GIT_UNUSED(line); - GIT_UNUSED(line_len); - - if (current_section) { - /* TODO: Once warnings lang, we should likely warn - * here. Git appears to warn in most cases if it sees - * un-namespaced config options. - */ - git_str_puts(&buf, current_section); - git_str_putc(&buf, '.'); - } - - for (c = var_name; *c; c++) - git_str_putc(&buf, git__tolower(*c)); - - if (git_str_oom(&buf)) - return -1; - - entry = git__calloc(1, sizeof(git_config_entry)); - GIT_ERROR_CHECK_ALLOC(entry); - entry->name = git_str_detach(&buf); - entry->value = var_value ? git__strdup(var_value) : NULL; - entry->level = parse_data->level; - entry->include_depth = parse_data->depth; - - if ((result = git_config_entries_append(parse_data->entries, entry)) < 0) - return result; - - result = 0; - - /* Add or append the new config option */ - if (!git__strcmp(entry->name, "include.path")) - result = parse_include(parse_data, entry->value); - else if (!git__prefixcmp(entry->name, "includeif.") && - !git__suffixcmp(entry->name, ".path")) - result = parse_conditional_include(parse_data, entry->name, entry->value); - - return result; -} - -static int config_file_read_buffer( - git_config_entries *entries, - const git_repository *repo, - config_file *file, - git_config_level_t level, - int depth, - const char *buf, - size_t buflen) -{ - config_file_parse_data parse_data; - git_config_parser reader; - int error; - - if (depth >= MAX_INCLUDE_DEPTH) { - git_error_set(GIT_ERROR_CONFIG, "maximum config include depth reached"); - return -1; - } - - /* Initialize the reading position */ - reader.path = file->path; - git_parse_ctx_init(&reader.ctx, buf, buflen); - - /* If the file is empty, there's nothing for us to do */ - if (!reader.ctx.content || *reader.ctx.content == '\0') { - error = 0; - goto out; - } - - parse_data.repo = repo; - parse_data.file = file; - parse_data.entries = entries; - parse_data.level = level; - parse_data.depth = depth; - - error = git_config_parse(&reader, NULL, read_on_variable, NULL, NULL, &parse_data); - -out: - return error; -} - -static int config_file_read( - git_config_entries *entries, - const git_repository *repo, - config_file *file, - git_config_level_t level, - int depth) -{ - git_str contents = GIT_STR_INIT; - struct stat st; - int error; - - if (p_stat(file->path, &st) < 0) { - error = git_fs_path_set_error(errno, file->path, "stat"); - goto out; - } - - if ((error = git_futils_readbuffer(&contents, file->path)) < 0) - goto out; - - git_futils_filestamp_set_from_stat(&file->stamp, &st); - if ((error = git_hash_buf(file->checksum, contents.ptr, contents.size, GIT_HASH_ALGORITHM_SHA1)) < 0) - goto out; - - if ((error = config_file_read_buffer(entries, repo, file, level, depth, - contents.ptr, contents.size)) < 0) - goto out; - -out: - git_str_dispose(&contents); - return error; -} - -static int write_section(git_str *fbuf, const char *key) -{ - int result; - const char *dot; - git_str buf = GIT_STR_INIT; - - /* All of this just for [section "subsection"] */ - dot = strchr(key, '.'); - git_str_putc(&buf, '['); - if (dot == NULL) { - git_str_puts(&buf, key); - } else { - char *escaped; - git_str_put(&buf, key, dot - key); - escaped = escape_value(dot + 1); - GIT_ERROR_CHECK_ALLOC(escaped); - git_str_printf(&buf, " \"%s\"", escaped); - git__free(escaped); - } - git_str_puts(&buf, "]\n"); - - if (git_str_oom(&buf)) - return -1; - - result = git_str_put(fbuf, git_str_cstr(&buf), buf.size); - git_str_dispose(&buf); - - return result; -} - -static const char *quotes_for_value(const char *value) -{ - const char *ptr; - - if (value[0] == ' ' || value[0] == '\0') - return "\""; - - for (ptr = value; *ptr; ++ptr) { - if (*ptr == ';' || *ptr == '#') - return "\""; - } - - if (ptr[-1] == ' ') - return "\""; - - return ""; -} - -struct write_data { - git_str *buf; - git_str buffered_comment; - unsigned int in_section : 1, - preg_replaced : 1; - const char *orig_section; - const char *section; - const char *orig_name; - const char *name; - const git_regexp *preg; - const char *value; -}; - -static int write_line_to(git_str *buf, const char *line, size_t line_len) -{ - int result = git_str_put(buf, line, line_len); - - if (!result && line_len && line[line_len-1] != '\n') - result = git_str_printf(buf, "\n"); - - return result; -} - -static int write_line(struct write_data *write_data, const char *line, size_t line_len) -{ - return write_line_to(write_data->buf, line, line_len); -} - -static int write_value(struct write_data *write_data) -{ - const char *q; - int result; - - q = quotes_for_value(write_data->value); - result = git_str_printf(write_data->buf, - "\t%s = %s%s%s\n", write_data->orig_name, q, write_data->value, q); - - /* If we are updating a single name/value, we're done. Setting `value` - * to `NULL` will prevent us from trying to write it again later (in - * `write_on_section`) if we see the same section repeated. - */ - if (!write_data->preg) - write_data->value = NULL; - - return result; -} - -static int write_on_section( - git_config_parser *reader, - const char *current_section, - const char *line, - size_t line_len, - void *data) -{ - struct write_data *write_data = (struct write_data *)data; - int result = 0; - - GIT_UNUSED(reader); - - /* If we were previously in the correct section (but aren't anymore) - * and haven't written our value (for a simple name/value set, not - * a multivar), then append it to the end of the section before writing - * the new one. - */ - if (write_data->in_section && !write_data->preg && write_data->value) - result = write_value(write_data); - - write_data->in_section = strcmp(current_section, write_data->section) == 0; - - /* - * If there were comments just before this section, dump them as well. - */ - if (!result) { - result = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size); - git_str_clear(&write_data->buffered_comment); - } - - if (!result) - result = write_line(write_data, line, line_len); - - return result; -} - -static int write_on_variable( - git_config_parser *reader, - const char *current_section, - const char *var_name, - const char *var_value, - const char *line, - size_t line_len, - void *data) -{ - struct write_data *write_data = (struct write_data *)data; - bool has_matched = false; - int error; - - GIT_UNUSED(reader); - GIT_UNUSED(current_section); - - /* - * If there were comments just before this variable, let's dump them as well. - */ - if ((error = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) - return error; - - git_str_clear(&write_data->buffered_comment); - - /* See if we are to update this name/value pair; first examine name */ - if (write_data->in_section && - strcasecmp(write_data->name, var_name) == 0) - has_matched = true; - - /* If we have a regex to match the value, see if it matches */ - if (has_matched && write_data->preg != NULL) - has_matched = (git_regexp_match(write_data->preg, var_value) == 0); - - /* If this isn't the name/value we're looking for, simply dump the - * existing data back out and continue on. - */ - if (!has_matched) - return write_line(write_data, line, line_len); - - write_data->preg_replaced = 1; - - /* If value is NULL, we are deleting this value; write nothing. */ - if (!write_data->value) - return 0; - - return write_value(write_data); -} - -static int write_on_comment(git_config_parser *reader, const char *line, size_t line_len, void *data) -{ - struct write_data *write_data; - - GIT_UNUSED(reader); - - write_data = (struct write_data *)data; - return write_line_to(&write_data->buffered_comment, line, line_len); -} - -static int write_on_eof( - git_config_parser *reader, const char *current_section, void *data) -{ - struct write_data *write_data = (struct write_data *)data; - int result = 0; - - GIT_UNUSED(reader); - - /* - * If we've buffered comments when reaching EOF, make sure to dump them. - */ - if ((result = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) - return result; - - /* If we are at the EOF and have not written our value (again, for a - * simple name/value set, not a multivar) then we have never seen the - * section in question and should create a new section and write the - * value. - */ - if ((!write_data->preg || !write_data->preg_replaced) && write_data->value) { - /* write the section header unless we're already in it */ - if (!current_section || strcmp(current_section, write_data->section)) - result = write_section(write_data->buf, write_data->orig_section); - - if (!result) - result = write_value(write_data); - } - - return result; -} - -/* - * This is pretty much the parsing, except we write out anything we don't have - */ -static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value) - -{ - char *orig_section = NULL, *section = NULL, *orig_name, *name, *ldot; - git_str buf = GIT_STR_INIT, contents = GIT_STR_INIT; - git_config_parser parser = GIT_CONFIG_PARSER_INIT; - git_filebuf file = GIT_FILEBUF_INIT; - struct write_data write_data; - int error; - - memset(&write_data, 0, sizeof(write_data)); - - if (cfg->locked) { - error = git_str_puts(&contents, git_str_cstr(&cfg->locked_content) == NULL ? "" : git_str_cstr(&cfg->locked_content)); - } else { - if ((error = git_filebuf_open(&file, cfg->file.path, GIT_FILEBUF_HASH_CONTENTS, - GIT_CONFIG_FILE_MODE)) < 0) - goto done; - - /* We need to read in our own config file */ - error = git_futils_readbuffer(&contents, cfg->file.path); - } - if (error < 0 && error != GIT_ENOTFOUND) - goto done; - - if ((git_config_parser_init(&parser, cfg->file.path, contents.ptr, contents.size)) < 0) - goto done; - - ldot = strrchr(key, '.'); - name = ldot + 1; - section = git__strndup(key, ldot - key); - GIT_ERROR_CHECK_ALLOC(section); - - ldot = strrchr(orig_key, '.'); - orig_name = ldot + 1; - orig_section = git__strndup(orig_key, ldot - orig_key); - GIT_ERROR_CHECK_ALLOC(orig_section); - - write_data.buf = &buf; - write_data.orig_section = orig_section; - write_data.section = section; - write_data.orig_name = orig_name; - write_data.name = name; - write_data.preg = preg; - write_data.value = value; - - if ((error = git_config_parse(&parser, write_on_section, write_on_variable, - write_on_comment, write_on_eof, &write_data)) < 0) - goto done; - - if (cfg->locked) { - size_t len = buf.asize; - /* Update our copy with the modified contents */ - git_str_dispose(&cfg->locked_content); - git_str_attach(&cfg->locked_content, git_str_detach(&buf), len); - } else { - git_filebuf_write(&file, git_str_cstr(&buf), git_str_len(&buf)); - - if ((error = git_filebuf_commit(&file)) < 0) - goto done; - - if ((error = config_file_refresh_from_buffer(&cfg->parent, buf.ptr, buf.size)) < 0) - goto done; - } - -done: - git__free(section); - git__free(orig_section); - git_str_dispose(&write_data.buffered_comment); - git_str_dispose(&buf); - git_str_dispose(&contents); - git_filebuf_cleanup(&file); - git_config_parser_dispose(&parser); - - return error; -} diff --git a/src/config_mem.c b/src/config_mem.c deleted file mode 100644 index 560229cf5..000000000 --- a/src/config_mem.c +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "config.h" - -#include "config_backend.h" -#include "config_parse.h" -#include "config_entries.h" - -typedef struct { - git_config_backend parent; - git_config_entries *entries; - git_str cfg; -} config_memory_backend; - -typedef struct { - git_config_entries *entries; - git_config_level_t level; -} config_memory_parse_data; - -static int config_error_readonly(void) -{ - git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); - return -1; -} - -static int read_variable_cb( - git_config_parser *reader, - const char *current_section, - const char *var_name, - const char *var_value, - const char *line, - size_t line_len, - void *payload) -{ - config_memory_parse_data *parse_data = (config_memory_parse_data *) payload; - git_str buf = GIT_STR_INIT; - git_config_entry *entry; - const char *c; - int result; - - GIT_UNUSED(reader); - GIT_UNUSED(line); - GIT_UNUSED(line_len); - - if (current_section) { - /* TODO: Once warnings land, we should likely warn - * here. Git appears to warn in most cases if it sees - * un-namespaced config options. - */ - git_str_puts(&buf, current_section); - git_str_putc(&buf, '.'); - } - - for (c = var_name; *c; c++) - git_str_putc(&buf, git__tolower(*c)); - - if (git_str_oom(&buf)) - return -1; - - entry = git__calloc(1, sizeof(git_config_entry)); - GIT_ERROR_CHECK_ALLOC(entry); - entry->name = git_str_detach(&buf); - entry->value = var_value ? git__strdup(var_value) : NULL; - entry->level = parse_data->level; - entry->include_depth = 0; - - if ((result = git_config_entries_append(parse_data->entries, entry)) < 0) - return result; - - return result; -} - -static int config_memory_open(git_config_backend *backend, git_config_level_t level, const git_repository *repo) -{ - config_memory_backend *memory_backend = (config_memory_backend *) backend; - git_config_parser parser = GIT_PARSE_CTX_INIT; - config_memory_parse_data parse_data; - int error; - - GIT_UNUSED(repo); - - if ((error = git_config_parser_init(&parser, "in-memory", memory_backend->cfg.ptr, - memory_backend->cfg.size)) < 0) - goto out; - parse_data.entries = memory_backend->entries; - parse_data.level = level; - - if ((error = git_config_parse(&parser, NULL, read_variable_cb, NULL, NULL, &parse_data)) < 0) - goto out; - -out: - git_config_parser_dispose(&parser); - return error; -} - -static int config_memory_get(git_config_backend *backend, const char *key, git_config_entry **out) -{ - config_memory_backend *memory_backend = (config_memory_backend *) backend; - return git_config_entries_get(out, memory_backend->entries, key); -} - -static int config_memory_iterator( - git_config_iterator **iter, - git_config_backend *backend) -{ - config_memory_backend *memory_backend = (config_memory_backend *) backend; - git_config_entries *entries; - int error; - - if ((error = git_config_entries_dup(&entries, memory_backend->entries)) < 0) - goto out; - - if ((error = git_config_entries_iterator_new(iter, entries)) < 0) - goto out; - -out: - /* Let iterator delete duplicated entries when it's done */ - git_config_entries_free(entries); - return error; -} - -static int config_memory_set(git_config_backend *backend, const char *name, const char *value) -{ - GIT_UNUSED(backend); - GIT_UNUSED(name); - GIT_UNUSED(value); - return config_error_readonly(); -} - -static int config_memory_set_multivar( - git_config_backend *backend, const char *name, const char *regexp, const char *value) -{ - GIT_UNUSED(backend); - GIT_UNUSED(name); - GIT_UNUSED(regexp); - GIT_UNUSED(value); - return config_error_readonly(); -} - -static int config_memory_delete(git_config_backend *backend, const char *name) -{ - GIT_UNUSED(backend); - GIT_UNUSED(name); - return config_error_readonly(); -} - -static int config_memory_delete_multivar(git_config_backend *backend, const char *name, const char *regexp) -{ - GIT_UNUSED(backend); - GIT_UNUSED(name); - GIT_UNUSED(regexp); - return config_error_readonly(); -} - -static int config_memory_lock(git_config_backend *backend) -{ - GIT_UNUSED(backend); - return config_error_readonly(); -} - -static int config_memory_unlock(git_config_backend *backend, int success) -{ - GIT_UNUSED(backend); - GIT_UNUSED(success); - return config_error_readonly(); -} - -static void config_memory_free(git_config_backend *_backend) -{ - config_memory_backend *backend = (config_memory_backend *)_backend; - - if (backend == NULL) - return; - - git_config_entries_free(backend->entries); - git_str_dispose(&backend->cfg); - git__free(backend); -} - -int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len) -{ - config_memory_backend *backend; - - backend = git__calloc(1, sizeof(config_memory_backend)); - GIT_ERROR_CHECK_ALLOC(backend); - - if (git_config_entries_new(&backend->entries) < 0) { - git__free(backend); - return -1; - } - - if (git_str_set(&backend->cfg, cfg, len) < 0) { - git_config_entries_free(backend->entries); - git__free(backend); - return -1; - } - - backend->parent.version = GIT_CONFIG_BACKEND_VERSION; - backend->parent.readonly = 1; - backend->parent.open = config_memory_open; - backend->parent.get = config_memory_get; - backend->parent.set = config_memory_set; - backend->parent.set_multivar = config_memory_set_multivar; - backend->parent.del = config_memory_delete; - backend->parent.del_multivar = config_memory_delete_multivar; - backend->parent.iterator = config_memory_iterator; - backend->parent.lock = config_memory_lock; - backend->parent.unlock = config_memory_unlock; - backend->parent.snapshot = git_config_backend_snapshot; - backend->parent.free = config_memory_free; - - *out = (git_config_backend *)backend; - - return 0; -} diff --git a/src/config_parse.c b/src/config_parse.c deleted file mode 100644 index 06931368e..000000000 --- a/src/config_parse.c +++ /dev/null @@ -1,580 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "config_parse.h" - -#include - -const char *git_config_escapes = "ntb\"\\"; -const char *git_config_escaped = "\n\t\b\"\\"; - -static void set_parse_error(git_config_parser *reader, int col, const char *error_str) -{ - if (col) - git_error_set(GIT_ERROR_CONFIG, - "failed to parse config file: %s (in %s:%"PRIuZ", column %d)", - error_str, reader->path, reader->ctx.line_num, col); - else - git_error_set(GIT_ERROR_CONFIG, - "failed to parse config file: %s (in %s:%"PRIuZ")", - error_str, reader->path, reader->ctx.line_num); -} - - -GIT_INLINE(int) config_keychar(int c) -{ - return isalnum(c) || c == '-'; -} - -static int strip_comments(char *line, int in_quotes) -{ - int quote_count = in_quotes, backslash_count = 0; - char *ptr; - - for (ptr = line; *ptr; ++ptr) { - if (ptr[0] == '"' && ((ptr > line && ptr[-1] != '\\') || ptr == line)) - quote_count++; - - if ((ptr[0] == ';' || ptr[0] == '#') && - (quote_count % 2) == 0 && - (backslash_count % 2) == 0) { - ptr[0] = '\0'; - break; - } - - if (ptr[0] == '\\') - backslash_count++; - else - backslash_count = 0; - } - - /* skip any space at the end */ - while (ptr > line && git__isspace(ptr[-1])) { - ptr--; - } - ptr[0] = '\0'; - - return quote_count; -} - - -static int parse_subsection_header(git_config_parser *reader, const char *line, size_t pos, const char *base_name, char **section_name) -{ - int c, rpos; - const char *first_quote, *last_quote; - const char *line_start = line; - git_str buf = GIT_STR_INIT; - size_t quoted_len, alloc_len, base_name_len = strlen(base_name); - - /* Skip any additional whitespace before our section name */ - while (git__isspace(line[pos])) - pos++; - - /* We should be at the first quotation mark. */ - if (line[pos] != '"') { - set_parse_error(reader, 0, "missing quotation marks in section header"); - goto end_error; - } - - first_quote = &line[pos]; - last_quote = strrchr(line, '"'); - quoted_len = last_quote - first_quote; - - if ((last_quote - line) > INT_MAX) { - set_parse_error(reader, 0, "invalid section header, line too long"); - goto end_error; - } - - if (quoted_len == 0) { - set_parse_error(reader, 0, "missing closing quotation mark in section header"); - goto end_error; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, base_name_len, quoted_len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); - - if (git_str_grow(&buf, alloc_len) < 0 || - git_str_printf(&buf, "%s.", base_name) < 0) - goto end_error; - - rpos = 0; - - line = first_quote; - c = line[++rpos]; - - /* - * At the end of each iteration, whatever is stored in c will be - * added to the string. In case of error, jump to out - */ - do { - - switch (c) { - case 0: - set_parse_error(reader, 0, "unexpected end-of-line in section header"); - goto end_error; - - case '"': - goto end_parse; - - case '\\': - c = line[++rpos]; - - if (c == 0) { - set_parse_error(reader, rpos, "unexpected end-of-line in section header"); - goto end_error; - } - - default: - break; - } - - git_str_putc(&buf, (char)c); - c = line[++rpos]; - } while (line + rpos < last_quote); - -end_parse: - if (git_str_oom(&buf)) - goto end_error; - - if (line[rpos] != '"' || line[rpos + 1] != ']') { - set_parse_error(reader, rpos, "unexpected text after closing quotes"); - git_str_dispose(&buf); - return -1; - } - - *section_name = git_str_detach(&buf); - return (int)(&line[rpos + 2] - line_start); /* rpos is at the closing quote */ - -end_error: - git_str_dispose(&buf); - - return -1; -} - -static int parse_section_header(git_config_parser *reader, char **section_out) -{ - char *name, *name_end; - int name_length, c, pos; - int result; - char *line; - size_t line_len; - - git_parse_advance_ws(&reader->ctx); - line = git__strndup(reader->ctx.line, reader->ctx.line_len); - if (line == NULL) - return -1; - - /* find the end of the variable's name */ - name_end = strrchr(line, ']'); - if (name_end == NULL) { - git__free(line); - set_parse_error(reader, 0, "missing ']' in section header"); - return -1; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&line_len, (size_t)(name_end - line), 1); - name = git__malloc(line_len); - GIT_ERROR_CHECK_ALLOC(name); - - name_length = 0; - pos = 0; - - /* Make sure we were given a section header */ - c = line[pos++]; - GIT_ASSERT(c == '['); - - c = line[pos++]; - - do { - if (git__isspace(c)){ - name[name_length] = '\0'; - result = parse_subsection_header(reader, line, pos, name, section_out); - git__free(line); - git__free(name); - return result; - } - - if (!config_keychar(c) && c != '.') { - set_parse_error(reader, pos, "unexpected character in header"); - goto fail_parse; - } - - name[name_length++] = (char)git__tolower(c); - - } while ((c = line[pos++]) != ']'); - - if (line[pos - 1] != ']') { - set_parse_error(reader, pos, "unexpected end of file"); - goto fail_parse; - } - - git__free(line); - - name[name_length] = 0; - *section_out = name; - - return pos; - -fail_parse: - git__free(line); - git__free(name); - return -1; -} - -static int skip_bom(git_parse_ctx *parser) -{ - git_str buf = GIT_STR_INIT_CONST(parser->content, parser->content_len); - git_str_bom_t bom; - int bom_offset = git_str_detect_bom(&bom, &buf); - - if (bom == GIT_STR_BOM_UTF8) - git_parse_advance_chars(parser, bom_offset); - - /* TODO: reference implementation is pretty stupid with BoM */ - - return 0; -} - -/* - (* basic types *) - digit = "0".."9" - integer = digit { digit } - alphabet = "a".."z" + "A" .. "Z" - - section_char = alphabet | "." | "-" - extension_char = (* any character except newline *) - any_char = (* any character *) - variable_char = "alphabet" | "-" - - - (* actual grammar *) - config = { section } - - section = header { definition } - - header = "[" section [subsection | subsection_ext] "]" - - subsection = "." section - subsection_ext = "\"" extension "\"" - - section = section_char { section_char } - extension = extension_char { extension_char } - - definition = variable_name ["=" variable_value] "\n" - - variable_name = variable_char { variable_char } - variable_value = string | boolean | integer - - string = quoted_string | plain_string - quoted_string = "\"" plain_string "\"" - plain_string = { any_char } - - boolean = boolean_true | boolean_false - boolean_true = "yes" | "1" | "true" | "on" - boolean_false = "no" | "0" | "false" | "off" -*/ - -/* '\"' -> '"' etc */ -static int unescape_line( - char **out, bool *is_multi, const char *ptr, int quote_count) -{ - char *str, *fixed, *esc; - size_t ptr_len = strlen(ptr), alloc_len; - - *is_multi = false; - - if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, ptr_len, 1) || - (str = git__malloc(alloc_len)) == NULL) { - return -1; - } - - fixed = str; - - while (*ptr != '\0') { - if (*ptr == '"') { - quote_count++; - } else if (*ptr != '\\') { - *fixed++ = *ptr; - } else { - /* backslash, check the next char */ - ptr++; - /* if we're at the end, it's a multiline, so keep the backslash */ - if (*ptr == '\0') { - *is_multi = true; - goto done; - } - if ((esc = strchr(git_config_escapes, *ptr)) != NULL) { - *fixed++ = git_config_escaped[esc - git_config_escapes]; - } else { - git__free(str); - git_error_set(GIT_ERROR_CONFIG, "invalid escape at %s", ptr); - return -1; - } - } - ptr++; - } - -done: - *fixed = '\0'; - *out = str; - - return 0; -} - -static int parse_multiline_variable(git_config_parser *reader, git_str *value, int in_quotes, size_t *line_len) -{ - int quote_count; - bool multiline = true; - - while (multiline) { - char *line = NULL, *proc_line = NULL; - int error; - - /* Check that the next line exists */ - git_parse_advance_line(&reader->ctx); - line = git__strndup(reader->ctx.line, reader->ctx.line_len); - GIT_ERROR_CHECK_ALLOC(line); - if (GIT_ADD_SIZET_OVERFLOW(line_len, *line_len, reader->ctx.line_len)) { - error = -1; - goto out; - } - - /* - * We've reached the end of the file, there is no continuation. - * (this is not an error). - */ - if (line[0] == '\0') { - error = 0; - goto out; - } - - /* If it was just a comment, pretend it didn't exist */ - quote_count = strip_comments(line, in_quotes); - if (line[0] == '\0') - goto next; - - if ((error = unescape_line(&proc_line, &multiline, - line, in_quotes)) < 0) - goto out; - - /* Add this line to the multiline var */ - if ((error = git_str_puts(value, proc_line)) < 0) - goto out; - -next: - git__free(line); - git__free(proc_line); - in_quotes = quote_count; - continue; - -out: - git__free(line); - git__free(proc_line); - return error; - } - - return 0; -} - -GIT_INLINE(bool) is_namechar(char c) -{ - return isalnum(c) || c == '-'; -} - -static int parse_name( - char **name, const char **value, git_config_parser *reader, const char *line) -{ - const char *name_end = line, *value_start; - - *name = NULL; - *value = NULL; - - while (*name_end && is_namechar(*name_end)) - name_end++; - - if (line == name_end) { - set_parse_error(reader, 0, "invalid configuration key"); - return -1; - } - - value_start = name_end; - - while (*value_start && git__isspace(*value_start)) - value_start++; - - if (*value_start == '=') { - *value = value_start + 1; - } else if (*value_start) { - set_parse_error(reader, 0, "invalid configuration key"); - return -1; - } - - if ((*name = git__strndup(line, name_end - line)) == NULL) - return -1; - - return 0; -} - -static int parse_variable(git_config_parser *reader, char **var_name, char **var_value, size_t *line_len) -{ - const char *value_start = NULL; - char *line = NULL, *name = NULL, *value = NULL; - int quote_count, error; - bool multiline; - - *var_name = NULL; - *var_value = NULL; - - git_parse_advance_ws(&reader->ctx); - line = git__strndup(reader->ctx.line, reader->ctx.line_len); - GIT_ERROR_CHECK_ALLOC(line); - - quote_count = strip_comments(line, 0); - - if ((error = parse_name(&name, &value_start, reader, line)) < 0) - goto out; - - /* - * Now, let's try to parse the value - */ - if (value_start != NULL) { - while (git__isspace(value_start[0])) - value_start++; - - if ((error = unescape_line(&value, &multiline, value_start, 0)) < 0) - goto out; - - if (multiline) { - git_str multi_value = GIT_STR_INIT; - git_str_attach(&multi_value, value, 0); - value = NULL; - - if (parse_multiline_variable(reader, &multi_value, quote_count % 2, line_len) < 0 || - git_str_oom(&multi_value)) { - error = -1; - git_str_dispose(&multi_value); - goto out; - } - - value = git_str_detach(&multi_value); - } - } - - *var_name = name; - *var_value = value; - name = NULL; - value = NULL; - -out: - git__free(name); - git__free(value); - git__free(line); - return error; -} - -int git_config_parser_init(git_config_parser *out, const char *path, const char *data, size_t datalen) -{ - out->path = path; - return git_parse_ctx_init(&out->ctx, data, datalen); -} - -void git_config_parser_dispose(git_config_parser *parser) -{ - git_parse_ctx_clear(&parser->ctx); -} - -int git_config_parse( - git_config_parser *parser, - git_config_parser_section_cb on_section, - git_config_parser_variable_cb on_variable, - git_config_parser_comment_cb on_comment, - git_config_parser_eof_cb on_eof, - void *payload) -{ - git_parse_ctx *ctx; - char *current_section = NULL, *var_name = NULL, *var_value = NULL; - int result = 0; - - ctx = &parser->ctx; - - skip_bom(ctx); - - for (; ctx->remain_len > 0; git_parse_advance_line(ctx)) { - const char *line_start; - size_t line_len; - char c; - - restart: - line_start = ctx->line; - line_len = ctx->line_len; - - /* - * Get either first non-whitespace character or, if that does - * not exist, the first whitespace character. This is required - * to preserve whitespaces when writing back the file. - */ - if (git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE) < 0 && - git_parse_peek(&c, ctx, 0) < 0) - continue; - - switch (c) { - case '[': /* section header, new section begins */ - git__free(current_section); - current_section = NULL; - - result = parse_section_header(parser, ¤t_section); - if (result < 0) - break; - - git_parse_advance_chars(ctx, result); - - if (on_section) - result = on_section(parser, current_section, line_start, line_len, payload); - /* - * After we've parsed the section header we may not be - * done with the line. If there's still data in there, - * run the next loop with the rest of the current line - * instead of moving forward. - */ - - if (!git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE)) - goto restart; - - break; - - case '\n': /* comment or whitespace-only */ - case '\r': - case ' ': - case '\t': - case ';': - case '#': - if (on_comment) { - result = on_comment(parser, line_start, line_len, payload); - } - break; - - default: /* assume variable declaration */ - if ((result = parse_variable(parser, &var_name, &var_value, &line_len)) == 0 && on_variable) { - result = on_variable(parser, current_section, var_name, var_value, line_start, line_len, payload); - git__free(var_name); - git__free(var_value); - } - - break; - } - - if (result < 0) - goto out; - } - - if (on_eof) - result = on_eof(parser, current_section, payload); - -out: - git__free(current_section); - return result; -} diff --git a/src/config_parse.h b/src/config_parse.h deleted file mode 100644 index b791d3245..000000000 --- a/src/config_parse.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_config_parse_h__ -#define INCLUDE_config_parse_h__ - -#include "common.h" - -#include "array.h" -#include "futils.h" -#include "oid.h" -#include "parse.h" - -extern const char *git_config_escapes; -extern const char *git_config_escaped; - -typedef struct { - const char *path; - git_parse_ctx ctx; -} git_config_parser; - -#define GIT_CONFIG_PARSER_INIT { NULL, GIT_PARSE_CTX_INIT } - -typedef int (*git_config_parser_section_cb)( - git_config_parser *parser, - const char *current_section, - const char *line, - size_t line_len, - void *payload); - -typedef int (*git_config_parser_variable_cb)( - git_config_parser *parser, - const char *current_section, - const char *var_name, - const char *var_value, - const char *line, - size_t line_len, - void *payload); - -typedef int (*git_config_parser_comment_cb)( - git_config_parser *parser, - const char *line, - size_t line_len, - void *payload); - -typedef int (*git_config_parser_eof_cb)( - git_config_parser *parser, - const char *current_section, - void *payload); - -int git_config_parser_init(git_config_parser *out, const char *path, const char *data, size_t datalen); -void git_config_parser_dispose(git_config_parser *parser); - -int git_config_parse( - git_config_parser *parser, - git_config_parser_section_cb on_section, - git_config_parser_variable_cb on_variable, - git_config_parser_comment_cb on_comment, - git_config_parser_eof_cb on_eof, - void *payload); - -#endif diff --git a/src/config_snapshot.c b/src/config_snapshot.c deleted file mode 100644 index e295d2f7f..000000000 --- a/src/config_snapshot.c +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "config_backend.h" - -#include "config.h" -#include "config_entries.h" - -typedef struct { - git_config_backend parent; - git_mutex values_mutex; - git_config_entries *entries; - git_config_backend *source; -} config_snapshot_backend; - -static int config_error_readonly(void) -{ - git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); - return -1; -} - -static int config_snapshot_iterator( - git_config_iterator **iter, - struct git_config_backend *backend) -{ - config_snapshot_backend *b = GIT_CONTAINER_OF(backend, config_snapshot_backend, parent); - git_config_entries *entries = NULL; - int error; - - if ((error = git_config_entries_dup(&entries, b->entries)) < 0 || - (error = git_config_entries_iterator_new(iter, entries)) < 0) - goto out; - -out: - /* Let iterator delete duplicated entries when it's done */ - git_config_entries_free(entries); - return error; -} - -/* release the map containing the entry as an equivalent to freeing it */ -static void config_snapshot_entry_free(git_config_entry *entry) -{ - git_config_entries *entries = (git_config_entries *) entry->payload; - git_config_entries_free(entries); -} - -static int config_snapshot_get(git_config_backend *cfg, const char *key, git_config_entry **out) -{ - config_snapshot_backend *b = GIT_CONTAINER_OF(cfg, config_snapshot_backend, parent); - git_config_entries *entries = NULL; - git_config_entry *entry; - int error = 0; - - if (git_mutex_lock(&b->values_mutex) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock config backend"); - return -1; - } - - entries = b->entries; - git_config_entries_incref(entries); - git_mutex_unlock(&b->values_mutex); - - if ((error = (git_config_entries_get(&entry, entries, key))) < 0) { - git_config_entries_free(entries); - return error; - } - - entry->free = config_snapshot_entry_free; - entry->payload = entries; - *out = entry; - - return 0; -} - -static int config_snapshot_set(git_config_backend *cfg, const char *name, const char *value) -{ - GIT_UNUSED(cfg); - GIT_UNUSED(name); - GIT_UNUSED(value); - - return config_error_readonly(); -} - -static int config_snapshot_set_multivar( - git_config_backend *cfg, const char *name, const char *regexp, const char *value) -{ - GIT_UNUSED(cfg); - GIT_UNUSED(name); - GIT_UNUSED(regexp); - GIT_UNUSED(value); - - return config_error_readonly(); -} - -static int config_snapshot_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) -{ - GIT_UNUSED(cfg); - GIT_UNUSED(name); - GIT_UNUSED(regexp); - - return config_error_readonly(); -} - -static int config_snapshot_delete(git_config_backend *cfg, const char *name) -{ - GIT_UNUSED(cfg); - GIT_UNUSED(name); - - return config_error_readonly(); -} - -static int config_snapshot_lock(git_config_backend *_cfg) -{ - GIT_UNUSED(_cfg); - - return config_error_readonly(); -} - -static int config_snapshot_unlock(git_config_backend *_cfg, int success) -{ - GIT_UNUSED(_cfg); - GIT_UNUSED(success); - - return config_error_readonly(); -} - -static void config_snapshot_free(git_config_backend *_backend) -{ - config_snapshot_backend *backend = GIT_CONTAINER_OF(_backend, config_snapshot_backend, parent); - - if (backend == NULL) - return; - - git_config_entries_free(backend->entries); - git_mutex_free(&backend->values_mutex); - git__free(backend); -} - -static int config_snapshot_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo) -{ - config_snapshot_backend *b = GIT_CONTAINER_OF(cfg, config_snapshot_backend, parent); - git_config_entries *entries = NULL; - git_config_iterator *it = NULL; - git_config_entry *entry; - int error; - - /* We're just copying data, don't care about the level or repo*/ - GIT_UNUSED(level); - GIT_UNUSED(repo); - - if ((error = git_config_entries_new(&entries)) < 0 || - (error = b->source->iterator(&it, b->source)) < 0) - goto out; - - while ((error = git_config_next(&entry, it)) == 0) - if ((error = git_config_entries_dup_entry(entries, entry)) < 0) - goto out; - - if (error < 0) { - if (error != GIT_ITEROVER) - goto out; - error = 0; - } - - b->entries = entries; - -out: - git_config_iterator_free(it); - if (error) - git_config_entries_free(entries); - return error; -} - -int git_config_backend_snapshot(git_config_backend **out, git_config_backend *source) -{ - config_snapshot_backend *backend; - - backend = git__calloc(1, sizeof(config_snapshot_backend)); - GIT_ERROR_CHECK_ALLOC(backend); - - backend->parent.version = GIT_CONFIG_BACKEND_VERSION; - git_mutex_init(&backend->values_mutex); - - backend->source = source; - - backend->parent.readonly = 1; - backend->parent.version = GIT_CONFIG_BACKEND_VERSION; - backend->parent.open = config_snapshot_open; - backend->parent.get = config_snapshot_get; - backend->parent.set = config_snapshot_set; - backend->parent.set_multivar = config_snapshot_set_multivar; - backend->parent.snapshot = git_config_backend_snapshot; - backend->parent.del = config_snapshot_delete; - backend->parent.del_multivar = config_snapshot_delete_multivar; - backend->parent.iterator = config_snapshot_iterator; - backend->parent.lock = config_snapshot_lock; - backend->parent.unlock = config_snapshot_unlock; - backend->parent.free = config_snapshot_free; - - *out = &backend->parent; - - return 0; -} diff --git a/src/crlf.c b/src/crlf.c deleted file mode 100644 index 1e1f1e845..000000000 --- a/src/crlf.c +++ /dev/null @@ -1,426 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "git2/attr.h" -#include "git2/blob.h" -#include "git2/index.h" -#include "git2/sys/filter.h" - -#include "buf.h" -#include "futils.h" -#include "hash.h" -#include "filter.h" -#include "repository.h" - -typedef enum { - GIT_CRLF_UNDEFINED, - GIT_CRLF_BINARY, - GIT_CRLF_TEXT, - GIT_CRLF_TEXT_INPUT, - GIT_CRLF_TEXT_CRLF, - GIT_CRLF_AUTO, - GIT_CRLF_AUTO_INPUT, - GIT_CRLF_AUTO_CRLF -} git_crlf_t; - -struct crlf_attrs { - int attr_action; /* the .gitattributes setting */ - int crlf_action; /* the core.autocrlf setting */ - - int auto_crlf; - int safe_crlf; - int core_eol; -}; - -struct crlf_filter { - git_filter f; -}; - -static git_crlf_t check_crlf(const char *value) -{ - if (GIT_ATTR_IS_TRUE(value)) - return GIT_CRLF_TEXT; - else if (GIT_ATTR_IS_FALSE(value)) - return GIT_CRLF_BINARY; - else if (GIT_ATTR_IS_UNSPECIFIED(value)) - ; - else if (strcmp(value, "input") == 0) - return GIT_CRLF_TEXT_INPUT; - else if (strcmp(value, "auto") == 0) - return GIT_CRLF_AUTO; - - return GIT_CRLF_UNDEFINED; -} - -static git_configmap_value check_eol(const char *value) -{ - if (GIT_ATTR_IS_UNSPECIFIED(value)) - ; - else if (strcmp(value, "lf") == 0) - return GIT_EOL_LF; - else if (strcmp(value, "crlf") == 0) - return GIT_EOL_CRLF; - - return GIT_EOL_UNSET; -} - -static int has_cr_in_index(const git_filter_source *src) -{ - git_repository *repo = git_filter_source_repo(src); - const char *path = git_filter_source_path(src); - git_index *index; - const git_index_entry *entry; - git_blob *blob; - const void *blobcontent; - git_object_size_t blobsize; - bool found_cr; - - if (!path) - return false; - - if (git_repository_index__weakptr(&index, repo) < 0) { - git_error_clear(); - return false; - } - - if (!(entry = git_index_get_bypath(index, path, 0)) && - !(entry = git_index_get_bypath(index, path, 1))) - return false; - - if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */ - return true; - - if (git_blob_lookup(&blob, repo, &entry->id) < 0) - return false; - - blobcontent = git_blob_rawcontent(blob); - blobsize = git_blob_rawsize(blob); - if (!git__is_sizet(blobsize)) - blobsize = (size_t)-1; - - found_cr = (blobcontent != NULL && - blobsize > 0 && - memchr(blobcontent, '\r', (size_t)blobsize) != NULL); - - git_blob_free(blob); - return found_cr; -} - -static int text_eol_is_crlf(struct crlf_attrs *ca) -{ - if (ca->auto_crlf == GIT_AUTO_CRLF_TRUE) - return 1; - else if (ca->auto_crlf == GIT_AUTO_CRLF_INPUT) - return 0; - - if (ca->core_eol == GIT_EOL_CRLF) - return 1; - if (ca->core_eol == GIT_EOL_UNSET && GIT_EOL_NATIVE == GIT_EOL_CRLF) - return 1; - - return 0; -} - -static git_configmap_value output_eol(struct crlf_attrs *ca) -{ - switch (ca->crlf_action) { - case GIT_CRLF_BINARY: - return GIT_EOL_UNSET; - case GIT_CRLF_TEXT_CRLF: - return GIT_EOL_CRLF; - case GIT_CRLF_TEXT_INPUT: - return GIT_EOL_LF; - case GIT_CRLF_UNDEFINED: - case GIT_CRLF_AUTO_CRLF: - return GIT_EOL_CRLF; - case GIT_CRLF_AUTO_INPUT: - return GIT_EOL_LF; - case GIT_CRLF_TEXT: - case GIT_CRLF_AUTO: - return text_eol_is_crlf(ca) ? GIT_EOL_CRLF : GIT_EOL_LF; - } - - /* TODO: warn when available */ - return ca->core_eol; -} - -GIT_INLINE(int) check_safecrlf( - struct crlf_attrs *ca, - const git_filter_source *src, - git_str_text_stats *stats) -{ - const char *filename = git_filter_source_path(src); - - if (!ca->safe_crlf) - return 0; - - if (output_eol(ca) == GIT_EOL_LF) { - /* - * CRLFs would not be restored by checkout: - * check if we'd remove CRLFs - */ - if (stats->crlf) { - if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) { - /* TODO: issue a warning when available */ - } else { - if (filename && *filename) - git_error_set( - GIT_ERROR_FILTER, "CRLF would be replaced by LF in '%s'", - filename); - else - git_error_set( - GIT_ERROR_FILTER, "CRLF would be replaced by LF"); - - return -1; - } - } - } else if (output_eol(ca) == GIT_EOL_CRLF) { - /* - * CRLFs would be added by checkout: - * check if we have "naked" LFs - */ - if (stats->crlf != stats->lf) { - if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) { - /* TODO: issue a warning when available */ - } else { - if (filename && *filename) - git_error_set( - GIT_ERROR_FILTER, "LF would be replaced by CRLF in '%s'", - filename); - else - git_error_set( - GIT_ERROR_FILTER, "LF would be replaced by CRLF"); - - return -1; - } - } - } - - return 0; -} - -static int crlf_apply_to_odb( - struct crlf_attrs *ca, - git_str *to, - const git_str *from, - const git_filter_source *src) -{ - git_str_text_stats stats; - bool is_binary; - int error; - - /* Binary attribute? Empty file? Nothing to do */ - if (ca->crlf_action == GIT_CRLF_BINARY || from->size == 0) - return GIT_PASSTHROUGH; - - is_binary = git_str_gather_text_stats(&stats, from, false); - - /* Heuristics to see if we can skip the conversion. - * Straight from Core Git. - */ - if (ca->crlf_action == GIT_CRLF_AUTO || - ca->crlf_action == GIT_CRLF_AUTO_INPUT || - ca->crlf_action == GIT_CRLF_AUTO_CRLF) { - - if (is_binary) - return GIT_PASSTHROUGH; - - /* - * If the file in the index has any CR in it, do not convert. - * This is the new safer autocrlf handling. - */ - if (has_cr_in_index(src)) - return GIT_PASSTHROUGH; - } - - if ((error = check_safecrlf(ca, src, &stats)) < 0) - return error; - - /* If there are no CR characters to filter out, then just pass */ - if (!stats.crlf) - return GIT_PASSTHROUGH; - - /* Actually drop the carriage returns */ - return git_str_crlf_to_lf(to, from); -} - -static int crlf_apply_to_workdir( - struct crlf_attrs *ca, - git_str *to, - const git_str *from) -{ - git_str_text_stats stats; - bool is_binary; - - /* Empty file? Nothing to do. */ - if (git_str_len(from) == 0 || output_eol(ca) != GIT_EOL_CRLF) - return GIT_PASSTHROUGH; - - is_binary = git_str_gather_text_stats(&stats, from, false); - - /* If there are no LFs, or all LFs are part of a CRLF, nothing to do */ - if (stats.lf == 0 || stats.lf == stats.crlf) - return GIT_PASSTHROUGH; - - if (ca->crlf_action == GIT_CRLF_AUTO || - ca->crlf_action == GIT_CRLF_AUTO_INPUT || - ca->crlf_action == GIT_CRLF_AUTO_CRLF) { - - /* If we have any existing CR or CRLF line endings, do nothing */ - if (stats.cr > 0) - return GIT_PASSTHROUGH; - - /* Don't filter binary files */ - if (is_binary) - return GIT_PASSTHROUGH; - } - - return git_str_lf_to_crlf(to, from); -} - -static int convert_attrs( - struct crlf_attrs *ca, - const char **attr_values, - const git_filter_source *src) -{ - int error; - - memset(ca, 0, sizeof(struct crlf_attrs)); - - if ((error = git_repository__configmap_lookup(&ca->auto_crlf, - git_filter_source_repo(src), GIT_CONFIGMAP_AUTO_CRLF)) < 0 || - (error = git_repository__configmap_lookup(&ca->safe_crlf, - git_filter_source_repo(src), GIT_CONFIGMAP_SAFE_CRLF)) < 0 || - (error = git_repository__configmap_lookup(&ca->core_eol, - git_filter_source_repo(src), GIT_CONFIGMAP_EOL)) < 0) - return error; - - /* downgrade FAIL to WARN if ALLOW_UNSAFE option is used */ - if ((git_filter_source_flags(src) & GIT_FILTER_ALLOW_UNSAFE) && - ca->safe_crlf == GIT_SAFE_CRLF_FAIL) - ca->safe_crlf = GIT_SAFE_CRLF_WARN; - - if (attr_values) { - /* load the text attribute */ - ca->crlf_action = check_crlf(attr_values[2]); /* text */ - - if (ca->crlf_action == GIT_CRLF_UNDEFINED) - ca->crlf_action = check_crlf(attr_values[0]); /* crlf */ - - if (ca->crlf_action != GIT_CRLF_BINARY) { - /* load the eol attribute */ - int eol_attr = check_eol(attr_values[1]); - - if (ca->crlf_action == GIT_CRLF_AUTO && eol_attr == GIT_EOL_LF) - ca->crlf_action = GIT_CRLF_AUTO_INPUT; - else if (ca->crlf_action == GIT_CRLF_AUTO && eol_attr == GIT_EOL_CRLF) - ca->crlf_action = GIT_CRLF_AUTO_CRLF; - else if (eol_attr == GIT_EOL_LF) - ca->crlf_action = GIT_CRLF_TEXT_INPUT; - else if (eol_attr == GIT_EOL_CRLF) - ca->crlf_action = GIT_CRLF_TEXT_CRLF; - } - - ca->attr_action = ca->crlf_action; - } else { - ca->crlf_action = GIT_CRLF_UNDEFINED; - } - - if (ca->crlf_action == GIT_CRLF_TEXT) - ca->crlf_action = text_eol_is_crlf(ca) ? GIT_CRLF_TEXT_CRLF : GIT_CRLF_TEXT_INPUT; - if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_FALSE) - ca->crlf_action = GIT_CRLF_BINARY; - if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_TRUE) - ca->crlf_action = GIT_CRLF_AUTO_CRLF; - if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_INPUT) - ca->crlf_action = GIT_CRLF_AUTO_INPUT; - - return 0; -} - -static int crlf_check( - git_filter *self, - void **payload, /* points to NULL ptr on entry, may be set */ - const git_filter_source *src, - const char **attr_values) -{ - struct crlf_attrs ca; - - GIT_UNUSED(self); - - convert_attrs(&ca, attr_values, src); - - if (ca.crlf_action == GIT_CRLF_BINARY) - return GIT_PASSTHROUGH; - - *payload = git__malloc(sizeof(ca)); - GIT_ERROR_CHECK_ALLOC(*payload); - memcpy(*payload, &ca, sizeof(ca)); - - return 0; -} - -static int crlf_apply( - git_filter *self, - void **payload, /* may be read and/or set */ - git_str *to, - const git_str *from, - const git_filter_source *src) -{ - int error = 0; - - /* initialize payload in case `check` was bypassed */ - if (!*payload) { - if ((error = crlf_check(self, payload, src, NULL)) < 0) - return error; - } - - if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) - error = crlf_apply_to_workdir(*payload, to, from); - else - error = crlf_apply_to_odb(*payload, to, from, src); - - return error; -} - -static int crlf_stream( - git_writestream **out, - git_filter *self, - void **payload, - const git_filter_source *src, - git_writestream *next) -{ - return git_filter_buffered_stream_new(out, - self, crlf_apply, NULL, payload, src, next); -} - -static void crlf_cleanup( - git_filter *self, - void *payload) -{ - GIT_UNUSED(self); - git__free(payload); -} - -git_filter *git_crlf_filter_new(void) -{ - struct crlf_filter *f = git__calloc(1, sizeof(struct crlf_filter)); - if (f == NULL) - return NULL; - - f->f.version = GIT_FILTER_VERSION; - f->f.attributes = "crlf eol text"; - f->f.initialize = NULL; - f->f.shutdown = git_filter_free; - f->f.check = crlf_check; - f->f.stream = crlf_stream; - f->f.cleanup = crlf_cleanup; - - return (git_filter *)f; -} diff --git a/src/date.c b/src/date.c deleted file mode 100644 index 0e5ffc96b..000000000 --- a/src/date.c +++ /dev/null @@ -1,898 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ - -#include "common.h" - -#ifndef GIT_WIN32 -#include -#endif - -#include "util.h" -#include "posix.h" -#include "date.h" - -#include -#include - -typedef enum { - DATE_NORMAL = 0, - DATE_RELATIVE, - DATE_SHORT, - DATE_LOCAL, - DATE_ISO8601, - DATE_RFC2822, - DATE_RAW -} date_mode; - -/* - * This is like mktime, but without normalization of tm_wday and tm_yday. - */ -static git_time_t tm_to_time_t(const struct tm *tm) -{ - static const int mdays[] = { - 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 - }; - int year = tm->tm_year - 70; - int month = tm->tm_mon; - int day = tm->tm_mday; - - if (year < 0 || year > 129) /* algo only works for 1970-2099 */ - return -1; - if (month < 0 || month > 11) /* array bounds */ - return -1; - if (month < 2 || (year + 2) % 4) - day--; - if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0) - return -1; - return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL + - tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec; -} - -static const char *month_names[] = { - "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" -}; - -static const char *weekday_names[] = { - "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays" -}; - - - -/* - * Check these. And note how it doesn't do the summer-time conversion. - * - * In my world, it's always summer, and things are probably a bit off - * in other ways too. - */ -static const struct { - const char *name; - int offset; - int dst; -} timezone_names[] = { - { "IDLW", -12, 0, }, /* International Date Line West */ - { "NT", -11, 0, }, /* Nome */ - { "CAT", -10, 0, }, /* Central Alaska */ - { "HST", -10, 0, }, /* Hawaii Standard */ - { "HDT", -10, 1, }, /* Hawaii Daylight */ - { "YST", -9, 0, }, /* Yukon Standard */ - { "YDT", -9, 1, }, /* Yukon Daylight */ - { "PST", -8, 0, }, /* Pacific Standard */ - { "PDT", -8, 1, }, /* Pacific Daylight */ - { "MST", -7, 0, }, /* Mountain Standard */ - { "MDT", -7, 1, }, /* Mountain Daylight */ - { "CST", -6, 0, }, /* Central Standard */ - { "CDT", -6, 1, }, /* Central Daylight */ - { "EST", -5, 0, }, /* Eastern Standard */ - { "EDT", -5, 1, }, /* Eastern Daylight */ - { "AST", -3, 0, }, /* Atlantic Standard */ - { "ADT", -3, 1, }, /* Atlantic Daylight */ - { "WAT", -1, 0, }, /* West Africa */ - - { "GMT", 0, 0, }, /* Greenwich Mean */ - { "UTC", 0, 0, }, /* Universal (Coordinated) */ - { "Z", 0, 0, }, /* Zulu, alias for UTC */ - - { "WET", 0, 0, }, /* Western European */ - { "BST", 0, 1, }, /* British Summer */ - { "CET", +1, 0, }, /* Central European */ - { "MET", +1, 0, }, /* Middle European */ - { "MEWT", +1, 0, }, /* Middle European Winter */ - { "MEST", +1, 1, }, /* Middle European Summer */ - { "CEST", +1, 1, }, /* Central European Summer */ - { "MESZ", +1, 1, }, /* Middle European Summer */ - { "FWT", +1, 0, }, /* French Winter */ - { "FST", +1, 1, }, /* French Summer */ - { "EET", +2, 0, }, /* Eastern Europe */ - { "EEST", +2, 1, }, /* Eastern European Daylight */ - { "WAST", +7, 0, }, /* West Australian Standard */ - { "WADT", +7, 1, }, /* West Australian Daylight */ - { "CCT", +8, 0, }, /* China Coast */ - { "JST", +9, 0, }, /* Japan Standard */ - { "EAST", +10, 0, }, /* Eastern Australian Standard */ - { "EADT", +10, 1, }, /* Eastern Australian Daylight */ - { "GST", +10, 0, }, /* Guam Standard */ - { "NZT", +12, 0, }, /* New Zealand */ - { "NZST", +12, 0, }, /* New Zealand Standard */ - { "NZDT", +12, 1, }, /* New Zealand Daylight */ - { "IDLE", +12, 0, }, /* International Date Line East */ -}; - -static size_t match_string(const char *date, const char *str) -{ - size_t i = 0; - - for (i = 0; *date; date++, str++, i++) { - if (*date == *str) - continue; - if (toupper(*date) == toupper(*str)) - continue; - if (!isalnum(*date)) - break; - return 0; - } - return i; -} - -static int skip_alpha(const char *date) -{ - int i = 0; - do { - i++; - } while (isalpha(date[i])); - return i; -} - -/* -* Parse month, weekday, or timezone name -*/ -static size_t match_alpha(const char *date, struct tm *tm, int *offset) -{ - unsigned int i; - - for (i = 0; i < 12; i++) { - size_t match = match_string(date, month_names[i]); - if (match >= 3) { - tm->tm_mon = i; - return match; - } - } - - for (i = 0; i < 7; i++) { - size_t match = match_string(date, weekday_names[i]); - if (match >= 3) { - tm->tm_wday = i; - return match; - } - } - - for (i = 0; i < ARRAY_SIZE(timezone_names); i++) { - size_t match = match_string(date, timezone_names[i].name); - if (match >= 3 || match == strlen(timezone_names[i].name)) { - int off = timezone_names[i].offset; - - /* This is bogus, but we like summer */ - off += timezone_names[i].dst; - - /* Only use the tz name offset if we don't have anything better */ - if (*offset == -1) - *offset = 60*off; - - return match; - } - } - - if (match_string(date, "PM") == 2) { - tm->tm_hour = (tm->tm_hour % 12) + 12; - return 2; - } - - if (match_string(date, "AM") == 2) { - tm->tm_hour = (tm->tm_hour % 12) + 0; - return 2; - } - - /* BAD */ - return skip_alpha(date); -} - -static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm) -{ - if (month > 0 && month < 13 && day > 0 && day < 32) { - struct tm check = *tm; - struct tm *r = (now_tm ? &check : tm); - git_time_t specified; - - r->tm_mon = month - 1; - r->tm_mday = day; - if (year == -1) { - if (!now_tm) - return 1; - r->tm_year = now_tm->tm_year; - } - else if (year >= 1970 && year < 2100) - r->tm_year = year - 1900; - else if (year > 70 && year < 100) - r->tm_year = year; - else if (year < 38) - r->tm_year = year + 100; - else - return 0; - if (!now_tm) - return 1; - - specified = tm_to_time_t(r); - - /* Be it commit time or author time, it does not make - * sense to specify timestamp way into the future. Make - * sure it is not later than ten days from now... - */ - if (now + 10*24*3600 < specified) - return 0; - tm->tm_mon = r->tm_mon; - tm->tm_mday = r->tm_mday; - if (year != -1) - tm->tm_year = r->tm_year; - return 1; - } - return 0; -} - -static size_t match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm) -{ - time_t now; - struct tm now_tm; - struct tm *refuse_future; - long num2, num3; - - num2 = strtol(end+1, &end, 10); - num3 = -1; - if (*end == c && isdigit(end[1])) - num3 = strtol(end+1, &end, 10); - - /* Time? Date? */ - switch (c) { - case ':': - if (num3 < 0) - num3 = 0; - if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) { - tm->tm_hour = num; - tm->tm_min = num2; - tm->tm_sec = num3; - break; - } - return 0; - - case '-': - case '/': - case '.': - now = time(NULL); - refuse_future = NULL; - if (p_gmtime_r(&now, &now_tm)) - refuse_future = &now_tm; - - if (num > 70) { - /* yyyy-mm-dd? */ - if (is_date(num, num2, num3, refuse_future, now, tm)) - break; - /* yyyy-dd-mm? */ - if (is_date(num, num3, num2, refuse_future, now, tm)) - break; - } - /* Our eastern European friends say dd.mm.yy[yy] - * is the norm there, so giving precedence to - * mm/dd/yy[yy] form only when separator is not '.' - */ - if (c != '.' && - is_date(num3, num, num2, refuse_future, now, tm)) - break; - /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */ - if (is_date(num3, num2, num, refuse_future, now, tm)) - break; - /* Funny European mm.dd.yy */ - if (c == '.' && - is_date(num3, num, num2, refuse_future, now, tm)) - break; - return 0; - } - return end - date; -} - -/* - * Have we filled in any part of the time/date yet? - * We just do a binary 'and' to see if the sign bit - * is set in all the values. - */ -static int nodate(struct tm *tm) -{ - return (tm->tm_year & - tm->tm_mon & - tm->tm_mday & - tm->tm_hour & - tm->tm_min & - tm->tm_sec) < 0; -} - -/* - * We've seen a digit. Time? Year? Date? - */ -static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt) -{ - size_t n; - char *end; - unsigned long num; - - num = strtoul(date, &end, 10); - - /* - * Seconds since 1970? We trigger on that for any numbers with - * more than 8 digits. This is because we don't want to rule out - * numbers like 20070606 as a YYYYMMDD date. - */ - if (num >= 100000000 && nodate(tm)) { - time_t time = num; - if (p_gmtime_r(&time, tm)) { - *tm_gmt = 1; - return end - date; - } - } - - /* - * Check for special formats: num[-.:/]num[same]num - */ - switch (*end) { - case ':': - case '.': - case '/': - case '-': - if (isdigit(end[1])) { - size_t match = match_multi_number(num, *end, date, end, tm); - if (match) - return match; - } - } - - /* - * None of the special formats? Try to guess what - * the number meant. We use the number of digits - * to make a more educated guess.. - */ - n = 0; - do { - n++; - } while (isdigit(date[n])); - - /* Four-digit year or a timezone? */ - if (n == 4) { - if (num <= 1400 && *offset == -1) { - unsigned int minutes = num % 100; - unsigned int hours = num / 100; - *offset = hours*60 + minutes; - } else if (num > 1900 && num < 2100) - tm->tm_year = num - 1900; - return n; - } - - /* - * Ignore lots of numerals. We took care of 4-digit years above. - * Days or months must be one or two digits. - */ - if (n > 2) - return n; - - /* - * NOTE! We will give precedence to day-of-month over month or - * year numbers in the 1-12 range. So 05 is always "mday 5", - * unless we already have a mday.. - * - * IOW, 01 Apr 05 parses as "April 1st, 2005". - */ - if (num > 0 && num < 32 && tm->tm_mday < 0) { - tm->tm_mday = num; - return n; - } - - /* Two-digit year? */ - if (n == 2 && tm->tm_year < 0) { - if (num < 10 && tm->tm_mday >= 0) { - tm->tm_year = num + 100; - return n; - } - if (num >= 70) { - tm->tm_year = num; - return n; - } - } - - if (num > 0 && num < 13 && tm->tm_mon < 0) - tm->tm_mon = num-1; - - return n; -} - -static size_t match_tz(const char *date, int *offp) -{ - char *end; - int hour = strtoul(date + 1, &end, 10); - size_t n = end - (date + 1); - int min = 0; - - if (n == 4) { - /* hhmm */ - min = hour % 100; - hour = hour / 100; - } else if (n != 2) { - min = 99; /* random stuff */ - } else if (*end == ':') { - /* hh:mm? */ - min = strtoul(end + 1, &end, 10); - if (end - (date + 1) != 5) - min = 99; /* random stuff */ - } /* otherwise we parsed "hh" */ - - /* - * Don't accept any random stuff. Even though some places have - * offset larger than 12 hours (e.g. Pacific/Kiritimati is at - * UTC+14), there is something wrong if hour part is much - * larger than that. We might also want to check that the - * minutes are divisible by 15 or something too. (Offset of - * Kathmandu, Nepal is UTC+5:45) - */ - if (min < 60 && hour < 24) { - int offset = hour * 60 + min; - if (*date == '-') - offset = -offset; - *offp = offset; - } - return end - date; -} - -/* - * Parse a string like "0 +0000" as ancient timestamp near epoch, but - * only when it appears not as part of any other string. - */ -static int match_object_header_date(const char *date, git_time_t *timestamp, int *offset) -{ - char *end; - unsigned long stamp; - int ofs; - - if (*date < '0' || '9' <= *date) - return -1; - stamp = strtoul(date, &end, 10); - if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-')) - return -1; - date = end + 2; - ofs = strtol(date, &end, 10); - if ((*end != '\0' && (*end != '\n')) || end != date + 4) - return -1; - ofs = (ofs / 100) * 60 + (ofs % 100); - if (date[-1] == '-') - ofs = -ofs; - *timestamp = stamp; - *offset = ofs; - return 0; -} - -/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822 - (i.e. English) day/month names, and it doesn't work correctly with %z. */ -static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset) -{ - struct tm tm; - int tm_gmt; - git_time_t dummy_timestamp; - int dummy_offset; - - if (!timestamp) - timestamp = &dummy_timestamp; - if (!offset) - offset = &dummy_offset; - - memset(&tm, 0, sizeof(tm)); - tm.tm_year = -1; - tm.tm_mon = -1; - tm.tm_mday = -1; - tm.tm_isdst = -1; - tm.tm_hour = -1; - tm.tm_min = -1; - tm.tm_sec = -1; - *offset = -1; - tm_gmt = 0; - - if (*date == '@' && - !match_object_header_date(date + 1, timestamp, offset)) - return 0; /* success */ - for (;;) { - size_t match = 0; - unsigned char c = *date; - - /* Stop at end of string or newline */ - if (!c || c == '\n') - break; - - if (isalpha(c)) - match = match_alpha(date, &tm, offset); - else if (isdigit(c)) - match = match_digit(date, &tm, offset, &tm_gmt); - else if ((c == '-' || c == '+') && isdigit(date[1])) - match = match_tz(date, offset); - - if (!match) { - /* BAD */ - match = 1; - } - - date += match; - } - - /* mktime uses local timezone */ - *timestamp = tm_to_time_t(&tm); - if (*offset == -1) - *offset = (int)((time_t)*timestamp - mktime(&tm)) / 60; - - if (*timestamp == (git_time_t)-1) - return -1; - - if (!tm_gmt) - *timestamp -= *offset * 60; - return 0; /* success */ -} - - -/* - * Relative time update (eg "2 days ago"). If we haven't set the time - * yet, we need to set it from current time. - */ -static git_time_t update_tm(struct tm *tm, struct tm *now, unsigned long sec) -{ - time_t n; - - if (tm->tm_mday < 0) - tm->tm_mday = now->tm_mday; - if (tm->tm_mon < 0) - tm->tm_mon = now->tm_mon; - if (tm->tm_year < 0) { - tm->tm_year = now->tm_year; - if (tm->tm_mon > now->tm_mon) - tm->tm_year--; - } - - n = mktime(tm) - sec; - p_localtime_r(&n, tm); - return n; -} - -static void date_now(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - update_tm(tm, now, 0); -} - -static void date_yesterday(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - update_tm(tm, now, 24*60*60); -} - -static void date_time(struct tm *tm, struct tm *now, int hour) -{ - if (tm->tm_hour < hour) - date_yesterday(tm, now, NULL); - tm->tm_hour = hour; - tm->tm_min = 0; - tm->tm_sec = 0; -} - -static void date_midnight(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - date_time(tm, now, 0); -} - -static void date_noon(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - date_time(tm, now, 12); -} - -static void date_tea(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - date_time(tm, now, 17); -} - -static void date_pm(struct tm *tm, struct tm *now, int *num) -{ - int hour, n = *num; - *num = 0; - GIT_UNUSED(now); - - hour = tm->tm_hour; - if (n) { - hour = n; - tm->tm_min = 0; - tm->tm_sec = 0; - } - tm->tm_hour = (hour % 12) + 12; -} - -static void date_am(struct tm *tm, struct tm *now, int *num) -{ - int hour, n = *num; - *num = 0; - GIT_UNUSED(now); - - hour = tm->tm_hour; - if (n) { - hour = n; - tm->tm_min = 0; - tm->tm_sec = 0; - } - tm->tm_hour = (hour % 12); -} - -static void date_never(struct tm *tm, struct tm *now, int *num) -{ - time_t n = 0; - GIT_UNUSED(now); - GIT_UNUSED(num); - p_localtime_r(&n, tm); -} - -static const struct special { - const char *name; - void (*fn)(struct tm *, struct tm *, int *); -} special[] = { - { "yesterday", date_yesterday }, - { "noon", date_noon }, - { "midnight", date_midnight }, - { "tea", date_tea }, - { "PM", date_pm }, - { "AM", date_am }, - { "never", date_never }, - { "now", date_now }, - { NULL } -}; - -static const char *number_name[] = { - "zero", "one", "two", "three", "four", - "five", "six", "seven", "eight", "nine", "ten", -}; - -static const struct typelen { - const char *type; - int length; -} typelen[] = { - { "seconds", 1 }, - { "minutes", 60 }, - { "hours", 60*60 }, - { "days", 24*60*60 }, - { "weeks", 7*24*60*60 }, - { NULL } -}; - -static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched) -{ - const struct typelen *tl; - const struct special *s; - const char *end = date; - int i; - - while (isalpha(*++end)) - /* scan to non-alpha */; - - for (i = 0; i < 12; i++) { - size_t match = match_string(date, month_names[i]); - if (match >= 3) { - tm->tm_mon = i; - *touched = 1; - return end; - } - } - - for (s = special; s->name; s++) { - size_t len = strlen(s->name); - if (match_string(date, s->name) == len) { - s->fn(tm, now, num); - *touched = 1; - return end; - } - } - - if (!*num) { - for (i = 1; i < 11; i++) { - size_t len = strlen(number_name[i]); - if (match_string(date, number_name[i]) == len) { - *num = i; - *touched = 1; - return end; - } - } - if (match_string(date, "last") == 4) { - *num = 1; - *touched = 1; - } - return end; - } - - tl = typelen; - while (tl->type) { - size_t len = strlen(tl->type); - if (match_string(date, tl->type) >= len-1) { - update_tm(tm, now, tl->length * (unsigned long)*num); - *num = 0; - *touched = 1; - return end; - } - tl++; - } - - for (i = 0; i < 7; i++) { - size_t match = match_string(date, weekday_names[i]); - if (match >= 3) { - int diff, n = *num -1; - *num = 0; - - diff = tm->tm_wday - i; - if (diff <= 0) - n++; - diff += 7*n; - - update_tm(tm, now, diff * 24 * 60 * 60); - *touched = 1; - return end; - } - } - - if (match_string(date, "months") >= 5) { - int n; - update_tm(tm, now, 0); /* fill in date fields if needed */ - n = tm->tm_mon - *num; - *num = 0; - while (n < 0) { - n += 12; - tm->tm_year--; - } - tm->tm_mon = n; - *touched = 1; - return end; - } - - if (match_string(date, "years") >= 4) { - update_tm(tm, now, 0); /* fill in date fields if needed */ - tm->tm_year -= *num; - *num = 0; - *touched = 1; - return end; - } - - return end; -} - -static const char *approxidate_digit(const char *date, struct tm *tm, int *num) -{ - char *end; - unsigned long number = strtoul(date, &end, 10); - - switch (*end) { - case ':': - case '.': - case '/': - case '-': - if (isdigit(end[1])) { - size_t match = match_multi_number(number, *end, date, end, tm); - if (match) - return date + match; - } - } - - /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */ - if (date[0] != '0' || end - date <= 2) - *num = number; - return end; -} - -/* - * Do we have a pending number at the end, or when - * we see a new one? Let's assume it's a month day, - * as in "Dec 6, 1992" - */ -static void pending_number(struct tm *tm, int *num) -{ - int number = *num; - - if (number) { - *num = 0; - if (tm->tm_mday < 0 && number < 32) - tm->tm_mday = number; - else if (tm->tm_mon < 0 && number < 13) - tm->tm_mon = number-1; - else if (tm->tm_year < 0) { - if (number > 1969 && number < 2100) - tm->tm_year = number - 1900; - else if (number > 69 && number < 100) - tm->tm_year = number; - else if (number < 38) - tm->tm_year = 100 + number; - /* We mess up for number = 00 ? */ - } - } -} - -static git_time_t approxidate_str(const char *date, - time_t time_sec, - int *error_ret) -{ - int number = 0; - int touched = 0; - struct tm tm = {0}, now; - - p_localtime_r(&time_sec, &tm); - now = tm; - - tm.tm_year = -1; - tm.tm_mon = -1; - tm.tm_mday = -1; - - for (;;) { - unsigned char c = *date; - if (!c) - break; - date++; - if (isdigit(c)) { - pending_number(&tm, &number); - date = approxidate_digit(date-1, &tm, &number); - touched = 1; - continue; - } - if (isalpha(c)) - date = approxidate_alpha(date-1, &tm, &now, &number, &touched); - } - pending_number(&tm, &number); - if (!touched) - *error_ret = -1; - return update_tm(&tm, &now, 0); -} - -int git_date_parse(git_time_t *out, const char *date) -{ - time_t time_sec; - git_time_t timestamp; - int offset, error_ret=0; - - if (!parse_date_basic(date, ×tamp, &offset)) { - *out = timestamp; - return 0; - } - - if (time(&time_sec) == -1) - return -1; - - *out = approxidate_str(date, time_sec, &error_ret); - return error_ret; -} - -int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset) -{ - time_t t; - struct tm gmt; - - GIT_ASSERT_ARG(out); - - t = (time_t) (time + offset * 60); - - if (p_gmtime_r(&t, &gmt) == NULL) - return -1; - - return git_str_printf(out, "%.3s, %u %.3s %.4u %02u:%02u:%02u %+03d%02d", - weekday_names[gmt.tm_wday], - gmt.tm_mday, - month_names[gmt.tm_mon], - gmt.tm_year + 1900, - gmt.tm_hour, gmt.tm_min, gmt.tm_sec, - offset / 60, offset % 60); -} - diff --git a/src/date.h b/src/date.h deleted file mode 100644 index 7ebd3c30e..000000000 --- a/src/date.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_date_h__ -#define INCLUDE_date_h__ - -#include "util.h" -#include "str.h" - -/* - * Parse a string into a value as a git_time_t. - * - * Sample valid input: - * - "yesterday" - * - "July 17, 2003" - * - "2003-7-17 08:23" - */ -extern int git_date_parse(git_time_t *out, const char *date); - -/* - * Format a git_time as a RFC2822 string - * - * @param out buffer to store formatted date - * @param time the time to be formatted - * @param offset the timezone offset - * @return 0 if successful; -1 on error - */ -extern int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset); - -#endif diff --git a/src/delta.c b/src/delta.c deleted file mode 100644 index 2d2c5fa85..000000000 --- a/src/delta.c +++ /dev/null @@ -1,628 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "delta.h" - -/* maximum hash entry list for the same hash bucket */ -#define HASH_LIMIT 64 - -#define RABIN_SHIFT 23 -#define RABIN_WINDOW 16 - -static const unsigned int T[256] = { - 0x00000000, 0xab59b4d1, 0x56b369a2, 0xfdeadd73, 0x063f6795, 0xad66d344, - 0x508c0e37, 0xfbd5bae6, 0x0c7ecf2a, 0xa7277bfb, 0x5acda688, 0xf1941259, - 0x0a41a8bf, 0xa1181c6e, 0x5cf2c11d, 0xf7ab75cc, 0x18fd9e54, 0xb3a42a85, - 0x4e4ef7f6, 0xe5174327, 0x1ec2f9c1, 0xb59b4d10, 0x48719063, 0xe32824b2, - 0x1483517e, 0xbfdae5af, 0x423038dc, 0xe9698c0d, 0x12bc36eb, 0xb9e5823a, - 0x440f5f49, 0xef56eb98, 0x31fb3ca8, 0x9aa28879, 0x6748550a, 0xcc11e1db, - 0x37c45b3d, 0x9c9defec, 0x6177329f, 0xca2e864e, 0x3d85f382, 0x96dc4753, - 0x6b369a20, 0xc06f2ef1, 0x3bba9417, 0x90e320c6, 0x6d09fdb5, 0xc6504964, - 0x2906a2fc, 0x825f162d, 0x7fb5cb5e, 0xd4ec7f8f, 0x2f39c569, 0x846071b8, - 0x798aaccb, 0xd2d3181a, 0x25786dd6, 0x8e21d907, 0x73cb0474, 0xd892b0a5, - 0x23470a43, 0x881ebe92, 0x75f463e1, 0xdeadd730, 0x63f67950, 0xc8afcd81, - 0x354510f2, 0x9e1ca423, 0x65c91ec5, 0xce90aa14, 0x337a7767, 0x9823c3b6, - 0x6f88b67a, 0xc4d102ab, 0x393bdfd8, 0x92626b09, 0x69b7d1ef, 0xc2ee653e, - 0x3f04b84d, 0x945d0c9c, 0x7b0be704, 0xd05253d5, 0x2db88ea6, 0x86e13a77, - 0x7d348091, 0xd66d3440, 0x2b87e933, 0x80de5de2, 0x7775282e, 0xdc2c9cff, - 0x21c6418c, 0x8a9ff55d, 0x714a4fbb, 0xda13fb6a, 0x27f92619, 0x8ca092c8, - 0x520d45f8, 0xf954f129, 0x04be2c5a, 0xafe7988b, 0x5432226d, 0xff6b96bc, - 0x02814bcf, 0xa9d8ff1e, 0x5e738ad2, 0xf52a3e03, 0x08c0e370, 0xa39957a1, - 0x584ced47, 0xf3155996, 0x0eff84e5, 0xa5a63034, 0x4af0dbac, 0xe1a96f7d, - 0x1c43b20e, 0xb71a06df, 0x4ccfbc39, 0xe79608e8, 0x1a7cd59b, 0xb125614a, - 0x468e1486, 0xedd7a057, 0x103d7d24, 0xbb64c9f5, 0x40b17313, 0xebe8c7c2, - 0x16021ab1, 0xbd5bae60, 0x6cb54671, 0xc7ecf2a0, 0x3a062fd3, 0x915f9b02, - 0x6a8a21e4, 0xc1d39535, 0x3c394846, 0x9760fc97, 0x60cb895b, 0xcb923d8a, - 0x3678e0f9, 0x9d215428, 0x66f4eece, 0xcdad5a1f, 0x3047876c, 0x9b1e33bd, - 0x7448d825, 0xdf116cf4, 0x22fbb187, 0x89a20556, 0x7277bfb0, 0xd92e0b61, - 0x24c4d612, 0x8f9d62c3, 0x7836170f, 0xd36fa3de, 0x2e857ead, 0x85dcca7c, - 0x7e09709a, 0xd550c44b, 0x28ba1938, 0x83e3ade9, 0x5d4e7ad9, 0xf617ce08, - 0x0bfd137b, 0xa0a4a7aa, 0x5b711d4c, 0xf028a99d, 0x0dc274ee, 0xa69bc03f, - 0x5130b5f3, 0xfa690122, 0x0783dc51, 0xacda6880, 0x570fd266, 0xfc5666b7, - 0x01bcbbc4, 0xaae50f15, 0x45b3e48d, 0xeeea505c, 0x13008d2f, 0xb85939fe, - 0x438c8318, 0xe8d537c9, 0x153feaba, 0xbe665e6b, 0x49cd2ba7, 0xe2949f76, - 0x1f7e4205, 0xb427f6d4, 0x4ff24c32, 0xe4abf8e3, 0x19412590, 0xb2189141, - 0x0f433f21, 0xa41a8bf0, 0x59f05683, 0xf2a9e252, 0x097c58b4, 0xa225ec65, - 0x5fcf3116, 0xf49685c7, 0x033df00b, 0xa86444da, 0x558e99a9, 0xfed72d78, - 0x0502979e, 0xae5b234f, 0x53b1fe3c, 0xf8e84aed, 0x17bea175, 0xbce715a4, - 0x410dc8d7, 0xea547c06, 0x1181c6e0, 0xbad87231, 0x4732af42, 0xec6b1b93, - 0x1bc06e5f, 0xb099da8e, 0x4d7307fd, 0xe62ab32c, 0x1dff09ca, 0xb6a6bd1b, - 0x4b4c6068, 0xe015d4b9, 0x3eb80389, 0x95e1b758, 0x680b6a2b, 0xc352defa, - 0x3887641c, 0x93ded0cd, 0x6e340dbe, 0xc56db96f, 0x32c6cca3, 0x999f7872, - 0x6475a501, 0xcf2c11d0, 0x34f9ab36, 0x9fa01fe7, 0x624ac294, 0xc9137645, - 0x26459ddd, 0x8d1c290c, 0x70f6f47f, 0xdbaf40ae, 0x207afa48, 0x8b234e99, - 0x76c993ea, 0xdd90273b, 0x2a3b52f7, 0x8162e626, 0x7c883b55, 0xd7d18f84, - 0x2c043562, 0x875d81b3, 0x7ab75cc0, 0xd1eee811 -}; - -static const unsigned int U[256] = { - 0x00000000, 0x7eb5200d, 0x5633f4cb, 0x2886d4c6, 0x073e5d47, 0x798b7d4a, - 0x510da98c, 0x2fb88981, 0x0e7cba8e, 0x70c99a83, 0x584f4e45, 0x26fa6e48, - 0x0942e7c9, 0x77f7c7c4, 0x5f711302, 0x21c4330f, 0x1cf9751c, 0x624c5511, - 0x4aca81d7, 0x347fa1da, 0x1bc7285b, 0x65720856, 0x4df4dc90, 0x3341fc9d, - 0x1285cf92, 0x6c30ef9f, 0x44b63b59, 0x3a031b54, 0x15bb92d5, 0x6b0eb2d8, - 0x4388661e, 0x3d3d4613, 0x39f2ea38, 0x4747ca35, 0x6fc11ef3, 0x11743efe, - 0x3eccb77f, 0x40799772, 0x68ff43b4, 0x164a63b9, 0x378e50b6, 0x493b70bb, - 0x61bda47d, 0x1f088470, 0x30b00df1, 0x4e052dfc, 0x6683f93a, 0x1836d937, - 0x250b9f24, 0x5bbebf29, 0x73386bef, 0x0d8d4be2, 0x2235c263, 0x5c80e26e, - 0x740636a8, 0x0ab316a5, 0x2b7725aa, 0x55c205a7, 0x7d44d161, 0x03f1f16c, - 0x2c4978ed, 0x52fc58e0, 0x7a7a8c26, 0x04cfac2b, 0x73e5d470, 0x0d50f47d, - 0x25d620bb, 0x5b6300b6, 0x74db8937, 0x0a6ea93a, 0x22e87dfc, 0x5c5d5df1, - 0x7d996efe, 0x032c4ef3, 0x2baa9a35, 0x551fba38, 0x7aa733b9, 0x041213b4, - 0x2c94c772, 0x5221e77f, 0x6f1ca16c, 0x11a98161, 0x392f55a7, 0x479a75aa, - 0x6822fc2b, 0x1697dc26, 0x3e1108e0, 0x40a428ed, 0x61601be2, 0x1fd53bef, - 0x3753ef29, 0x49e6cf24, 0x665e46a5, 0x18eb66a8, 0x306db26e, 0x4ed89263, - 0x4a173e48, 0x34a21e45, 0x1c24ca83, 0x6291ea8e, 0x4d29630f, 0x339c4302, - 0x1b1a97c4, 0x65afb7c9, 0x446b84c6, 0x3adea4cb, 0x1258700d, 0x6ced5000, - 0x4355d981, 0x3de0f98c, 0x15662d4a, 0x6bd30d47, 0x56ee4b54, 0x285b6b59, - 0x00ddbf9f, 0x7e689f92, 0x51d01613, 0x2f65361e, 0x07e3e2d8, 0x7956c2d5, - 0x5892f1da, 0x2627d1d7, 0x0ea10511, 0x7014251c, 0x5facac9d, 0x21198c90, - 0x099f5856, 0x772a785b, 0x4c921c31, 0x32273c3c, 0x1aa1e8fa, 0x6414c8f7, - 0x4bac4176, 0x3519617b, 0x1d9fb5bd, 0x632a95b0, 0x42eea6bf, 0x3c5b86b2, - 0x14dd5274, 0x6a687279, 0x45d0fbf8, 0x3b65dbf5, 0x13e30f33, 0x6d562f3e, - 0x506b692d, 0x2ede4920, 0x06589de6, 0x78edbdeb, 0x5755346a, 0x29e01467, - 0x0166c0a1, 0x7fd3e0ac, 0x5e17d3a3, 0x20a2f3ae, 0x08242768, 0x76910765, - 0x59298ee4, 0x279caee9, 0x0f1a7a2f, 0x71af5a22, 0x7560f609, 0x0bd5d604, - 0x235302c2, 0x5de622cf, 0x725eab4e, 0x0ceb8b43, 0x246d5f85, 0x5ad87f88, - 0x7b1c4c87, 0x05a96c8a, 0x2d2fb84c, 0x539a9841, 0x7c2211c0, 0x029731cd, - 0x2a11e50b, 0x54a4c506, 0x69998315, 0x172ca318, 0x3faa77de, 0x411f57d3, - 0x6ea7de52, 0x1012fe5f, 0x38942a99, 0x46210a94, 0x67e5399b, 0x19501996, - 0x31d6cd50, 0x4f63ed5d, 0x60db64dc, 0x1e6e44d1, 0x36e89017, 0x485db01a, - 0x3f77c841, 0x41c2e84c, 0x69443c8a, 0x17f11c87, 0x38499506, 0x46fcb50b, - 0x6e7a61cd, 0x10cf41c0, 0x310b72cf, 0x4fbe52c2, 0x67388604, 0x198da609, - 0x36352f88, 0x48800f85, 0x6006db43, 0x1eb3fb4e, 0x238ebd5d, 0x5d3b9d50, - 0x75bd4996, 0x0b08699b, 0x24b0e01a, 0x5a05c017, 0x728314d1, 0x0c3634dc, - 0x2df207d3, 0x534727de, 0x7bc1f318, 0x0574d315, 0x2acc5a94, 0x54797a99, - 0x7cffae5f, 0x024a8e52, 0x06852279, 0x78300274, 0x50b6d6b2, 0x2e03f6bf, - 0x01bb7f3e, 0x7f0e5f33, 0x57888bf5, 0x293dabf8, 0x08f998f7, 0x764cb8fa, - 0x5eca6c3c, 0x207f4c31, 0x0fc7c5b0, 0x7172e5bd, 0x59f4317b, 0x27411176, - 0x1a7c5765, 0x64c97768, 0x4c4fa3ae, 0x32fa83a3, 0x1d420a22, 0x63f72a2f, - 0x4b71fee9, 0x35c4dee4, 0x1400edeb, 0x6ab5cde6, 0x42331920, 0x3c86392d, - 0x133eb0ac, 0x6d8b90a1, 0x450d4467, 0x3bb8646a -}; - -struct index_entry { - const unsigned char *ptr; - unsigned int val; - struct index_entry *next; -}; - -struct git_delta_index { - unsigned long memsize; - const void *src_buf; - size_t src_size; - unsigned int hash_mask; - struct index_entry *hash[GIT_FLEX_ARRAY]; -}; - -static int lookup_index_alloc( - void **out, unsigned long *out_len, size_t entries, size_t hash_count) -{ - size_t entries_len, hash_len, index_len; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&entries_len, entries, sizeof(struct index_entry)); - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&hash_len, hash_count, sizeof(struct index_entry *)); - - GIT_ERROR_CHECK_ALLOC_ADD(&index_len, sizeof(struct git_delta_index), entries_len); - GIT_ERROR_CHECK_ALLOC_ADD(&index_len, index_len, hash_len); - - if (!git__is_ulong(index_len)) { - git_error_set(GIT_ERROR_NOMEMORY, "overly large delta"); - return -1; - } - - *out = git__malloc(index_len); - GIT_ERROR_CHECK_ALLOC(*out); - - *out_len = (unsigned long)index_len; - return 0; -} - -int git_delta_index_init( - git_delta_index **out, const void *buf, size_t bufsize) -{ - unsigned int i, hsize, hmask, entries, prev_val, *hash_count; - const unsigned char *data, *buffer = buf; - struct git_delta_index *index; - struct index_entry *entry, **hash; - void *mem; - unsigned long memsize; - - *out = NULL; - - if (!buf || !bufsize) - return 0; - - /* Determine index hash size. Note that indexing skips the - first byte to allow for optimizing the rabin polynomial - initialization in create_delta(). */ - entries = (unsigned int)(bufsize - 1) / RABIN_WINDOW; - if (bufsize >= 0xffffffffUL) { - /* - * Current delta format can't encode offsets into - * reference buffer with more than 32 bits. - */ - entries = 0xfffffffeU / RABIN_WINDOW; - } - hsize = entries / 4; - for (i = 4; i < 31 && (1u << i) < hsize; i++); - hsize = 1 << i; - hmask = hsize - 1; - - if (lookup_index_alloc(&mem, &memsize, entries, hsize) < 0) - return -1; - - index = mem; - mem = index->hash; - hash = mem; - mem = hash + hsize; - entry = mem; - - index->memsize = memsize; - index->src_buf = buf; - index->src_size = bufsize; - index->hash_mask = hmask; - memset(hash, 0, hsize * sizeof(*hash)); - - /* allocate an array to count hash entries */ - hash_count = git__calloc(hsize, sizeof(*hash_count)); - if (!hash_count) { - git__free(index); - return -1; - } - - /* then populate the index */ - prev_val = ~0; - for (data = buffer + entries * RABIN_WINDOW - RABIN_WINDOW; - data >= buffer; - data -= RABIN_WINDOW) { - unsigned int val = 0; - for (i = 1; i <= RABIN_WINDOW; i++) - val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT]; - if (val == prev_val) { - /* keep the lowest of consecutive identical blocks */ - entry[-1].ptr = data + RABIN_WINDOW; - } else { - prev_val = val; - i = val & hmask; - entry->ptr = data + RABIN_WINDOW; - entry->val = val; - entry->next = hash[i]; - hash[i] = entry++; - hash_count[i]++; - } - } - - /* - * Determine a limit on the number of entries in the same hash - * bucket. This guard us against patological data sets causing - * really bad hash distribution with most entries in the same hash - * bucket that would bring us to O(m*n) computing costs (m and n - * corresponding to reference and target buffer sizes). - * - * Make sure none of the hash buckets has more entries than - * we're willing to test. Otherwise we cull the entry list - * uniformly to still preserve a good repartition across - * the reference buffer. - */ - for (i = 0; i < hsize; i++) { - if (hash_count[i] < HASH_LIMIT) - continue; - - entry = hash[i]; - do { - struct index_entry *keep = entry; - int skip = hash_count[i] / HASH_LIMIT / 2; - do { - entry = entry->next; - } while(--skip && entry); - keep->next = entry; - } while (entry); - } - git__free(hash_count); - - *out = index; - return 0; -} - -void git_delta_index_free(git_delta_index *index) -{ - git__free(index); -} - -size_t git_delta_index_size(git_delta_index *index) -{ - GIT_ASSERT_ARG(index); - - return index->memsize; -} - -/* - * The maximum size for any opcode sequence, including the initial header - * plus rabin window plus biggest copy. - */ -#define MAX_OP_SIZE (5 + 5 + 1 + RABIN_WINDOW + 7) - -int git_delta_create_from_index( - void **out, - size_t *out_len, - const struct git_delta_index *index, - const void *trg_buf, - size_t trg_size, - size_t max_size) -{ - unsigned int i, bufpos, bufsize, moff, msize, val; - int inscnt; - const unsigned char *ref_data, *ref_top, *data, *top; - unsigned char *buf; - - *out = NULL; - *out_len = 0; - - if (!trg_buf || !trg_size) - return 0; - - if (index->src_size > UINT_MAX || - trg_size > UINT_MAX || - max_size > (UINT_MAX - MAX_OP_SIZE - 1)) { - git_error_set(GIT_ERROR_INVALID, "buffer sizes too large for delta processing"); - return -1; - } - - bufpos = 0; - bufsize = 8192; - if (max_size && bufsize >= max_size) - bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1); - buf = git__malloc(bufsize); - GIT_ERROR_CHECK_ALLOC(buf); - - /* store reference buffer size */ - i = (unsigned int)index->src_size; - while (i >= 0x80) { - buf[bufpos++] = i | 0x80; - i >>= 7; - } - buf[bufpos++] = i; - - /* store target buffer size */ - i = (unsigned int)trg_size; - while (i >= 0x80) { - buf[bufpos++] = i | 0x80; - i >>= 7; - } - buf[bufpos++] = i; - - ref_data = index->src_buf; - ref_top = ref_data + index->src_size; - data = trg_buf; - top = (const unsigned char *) trg_buf + trg_size; - - bufpos++; - val = 0; - for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) { - buf[bufpos++] = *data; - val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT]; - } - inscnt = i; - - moff = 0; - msize = 0; - while (data < top) { - if (msize < 4096) { - struct index_entry *entry; - val ^= U[data[-RABIN_WINDOW]]; - val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT]; - i = val & index->hash_mask; - for (entry = index->hash[i]; entry; entry = entry->next) { - const unsigned char *ref = entry->ptr; - const unsigned char *src = data; - unsigned int ref_size = (unsigned int)(ref_top - ref); - if (entry->val != val) - continue; - if (ref_size > (unsigned int)(top - src)) - ref_size = (unsigned int)(top - src); - if (ref_size <= msize) - break; - while (ref_size-- && *src++ == *ref) - ref++; - if (msize < (unsigned int)(ref - entry->ptr)) { - /* this is our best match so far */ - msize = (unsigned int)(ref - entry->ptr); - moff = (unsigned int)(entry->ptr - ref_data); - if (msize >= 4096) /* good enough */ - break; - } - } - } - - if (msize < 4) { - if (!inscnt) - bufpos++; - buf[bufpos++] = *data++; - inscnt++; - if (inscnt == 0x7f) { - buf[bufpos - inscnt - 1] = inscnt; - inscnt = 0; - } - msize = 0; - } else { - unsigned int left; - unsigned char *op; - - if (inscnt) { - while (moff && ref_data[moff-1] == data[-1]) { - /* we can match one byte back */ - msize++; - moff--; - data--; - bufpos--; - if (--inscnt) - continue; - bufpos--; /* remove count slot */ - inscnt--; /* make it -1 */ - break; - } - buf[bufpos - inscnt - 1] = inscnt; - inscnt = 0; - } - - /* A copy op is currently limited to 64KB (pack v2) */ - left = (msize < 0x10000) ? 0 : (msize - 0x10000); - msize -= left; - - op = buf + bufpos++; - i = 0x80; - - if (moff & 0x000000ff) - buf[bufpos++] = moff >> 0, i |= 0x01; - if (moff & 0x0000ff00) - buf[bufpos++] = moff >> 8, i |= 0x02; - if (moff & 0x00ff0000) - buf[bufpos++] = moff >> 16, i |= 0x04; - if (moff & 0xff000000) - buf[bufpos++] = moff >> 24, i |= 0x08; - - if (msize & 0x00ff) - buf[bufpos++] = msize >> 0, i |= 0x10; - if (msize & 0xff00) - buf[bufpos++] = msize >> 8, i |= 0x20; - - *op = i; - - data += msize; - moff += msize; - msize = left; - - if (msize < 4096) { - int j; - val = 0; - for (j = -RABIN_WINDOW; j < 0; j++) - val = ((val << 8) | data[j]) - ^ T[val >> RABIN_SHIFT]; - } - } - - if (bufpos >= bufsize - MAX_OP_SIZE) { - void *tmp = buf; - bufsize = bufsize * 3 / 2; - if (max_size && bufsize >= max_size) - bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1); - if (max_size && bufpos > max_size) - break; - buf = git__realloc(buf, bufsize); - if (!buf) { - git__free(tmp); - return -1; - } - } - } - - if (inscnt) - buf[bufpos - inscnt - 1] = inscnt; - - if (max_size && bufpos > max_size) { - git_error_set(GIT_ERROR_NOMEMORY, "delta would be larger than maximum size"); - git__free(buf); - return GIT_EBUFS; - } - - *out_len = bufpos; - *out = buf; - return 0; -} - -/* -* Delta application was heavily cribbed from BinaryDelta.java in JGit, which -* itself was heavily cribbed from patch-delta.c in the -* GIT project. The original delta patching code was written by -* Nicolas Pitre . -*/ - -static int hdr_sz( - size_t *size, - const unsigned char **delta, - const unsigned char *end) -{ - const unsigned char *d = *delta; - size_t r = 0; - unsigned int c, shift = 0; - - do { - if (d == end) { - git_error_set(GIT_ERROR_INVALID, "truncated delta"); - return -1; - } - - c = *d++; - r |= (c & 0x7f) << shift; - shift += 7; - } while (c & 0x80); - *delta = d; - *size = r; - return 0; -} - -int git_delta_read_header( - size_t *base_out, - size_t *result_out, - const unsigned char *delta, - size_t delta_len) -{ - const unsigned char *delta_end = delta + delta_len; - if ((hdr_sz(base_out, &delta, delta_end) < 0) || - (hdr_sz(result_out, &delta, delta_end) < 0)) - return -1; - return 0; -} - -#define DELTA_HEADER_BUFFER_LEN 16 -int git_delta_read_header_fromstream( - size_t *base_sz, size_t *res_sz, git_packfile_stream *stream) -{ - static const size_t buffer_len = DELTA_HEADER_BUFFER_LEN; - unsigned char buffer[DELTA_HEADER_BUFFER_LEN]; - const unsigned char *delta, *delta_end; - size_t len; - ssize_t read; - - len = read = 0; - while (len < buffer_len) { - read = git_packfile_stream_read(stream, &buffer[len], buffer_len - len); - - if (read == 0) - break; - - if (read == GIT_EBUFS) - continue; - - len += read; - } - - delta = buffer; - delta_end = delta + len; - if ((hdr_sz(base_sz, &delta, delta_end) < 0) || - (hdr_sz(res_sz, &delta, delta_end) < 0)) - return -1; - - return 0; -} - -int git_delta_apply( - void **out, - size_t *out_len, - const unsigned char *base, - size_t base_len, - const unsigned char *delta, - size_t delta_len) -{ - const unsigned char *delta_end = delta + delta_len; - size_t base_sz, res_sz, alloc_sz; - unsigned char *res_dp; - - *out = NULL; - *out_len = 0; - - /* - * Check that the base size matches the data we were given; - * if not we would underflow while accessing data from the - * base object, resulting in data corruption or segfault. - */ - if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) { - git_error_set(GIT_ERROR_INVALID, "failed to apply delta: base size does not match given data"); - return -1; - } - - if (hdr_sz(&res_sz, &delta, delta_end) < 0) { - git_error_set(GIT_ERROR_INVALID, "failed to apply delta: base size does not match given data"); - return -1; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1); - res_dp = git__malloc(alloc_sz); - GIT_ERROR_CHECK_ALLOC(res_dp); - - res_dp[res_sz] = '\0'; - *out = res_dp; - *out_len = res_sz; - - while (delta < delta_end) { - unsigned char cmd = *delta++; - if (cmd & 0x80) { - /* cmd is a copy instruction; copy from the base. */ - size_t off = 0, len = 0, end; - -#define ADD_DELTA(o, shift) { if (delta < delta_end) (o) |= ((unsigned) *delta++ << shift); else goto fail; } - if (cmd & 0x01) ADD_DELTA(off, 0UL); - if (cmd & 0x02) ADD_DELTA(off, 8UL); - if (cmd & 0x04) ADD_DELTA(off, 16UL); - if (cmd & 0x08) ADD_DELTA(off, 24UL); - - if (cmd & 0x10) ADD_DELTA(len, 0UL); - if (cmd & 0x20) ADD_DELTA(len, 8UL); - if (cmd & 0x40) ADD_DELTA(len, 16UL); - if (!len) len = 0x10000; -#undef ADD_DELTA - - if (GIT_ADD_SIZET_OVERFLOW(&end, off, len) || - base_len < end || res_sz < len) - goto fail; - - memcpy(res_dp, base + off, len); - res_dp += len; - res_sz -= len; - - } else if (cmd) { - /* - * cmd is a literal insert instruction; copy from - * the delta stream itself. - */ - if (delta_end - delta < cmd || res_sz < cmd) - goto fail; - memcpy(res_dp, delta, cmd); - delta += cmd; - res_dp += cmd; - res_sz -= cmd; - - } else { - /* cmd == 0 is reserved for future encodings. */ - goto fail; - } - } - - if (delta != delta_end || res_sz) - goto fail; - return 0; - -fail: - git__free(*out); - - *out = NULL; - *out_len = 0; - - git_error_set(GIT_ERROR_INVALID, "failed to apply delta"); - return -1; -} diff --git a/src/delta.h b/src/delta.h deleted file mode 100644 index f61987304..000000000 --- a/src/delta.h +++ /dev/null @@ -1,136 +0,0 @@ -/* - * diff-delta code taken from git.git. See diff-delta.c for details. - * - */ -#ifndef INCLUDE_git_delta_h__ -#define INCLUDE_git_delta_h__ - -#include "common.h" - -#include "pack.h" - -typedef struct git_delta_index git_delta_index; - -/* - * git_delta_index_init: compute index data from given buffer - * - * This returns a pointer to a struct delta_index that should be passed to - * subsequent create_delta() calls, or to free_delta_index(). A NULL pointer - * is returned on failure. The given buffer must not be freed nor altered - * before free_delta_index() is called. The returned pointer must be freed - * using free_delta_index(). - */ -extern int git_delta_index_init( - git_delta_index **out, const void *buf, size_t bufsize); - -/* - * Free the index created by git_delta_index_init() - */ -extern void git_delta_index_free(git_delta_index *index); - -/* - * Returns memory usage of delta index. - */ -extern size_t git_delta_index_size(git_delta_index *index); - -/* - * create_delta: create a delta from given index for the given buffer - * - * This function may be called multiple times with different buffers using - * the same delta_index pointer. If max_delta_size is non-zero and the - * resulting delta is to be larger than max_delta_size then NULL is returned. - * On success, a non-NULL pointer to the buffer with the delta data is - * returned and *delta_size is updated with its size. The returned buffer - * must be freed by the caller. - */ -extern int git_delta_create_from_index( - void **out, - size_t *out_size, - const struct git_delta_index *index, - const void *buf, - size_t bufsize, - size_t max_delta_size); - -/* - * diff_delta: create a delta from source buffer to target buffer - * - * If max_delta_size is non-zero and the resulting delta is to be larger - * than max_delta_size then GIT_EBUFS is returned. On success, a non-NULL - * pointer to the buffer with the delta data is returned and *delta_size is - * updated with its size. The returned buffer must be freed by the caller. - */ -GIT_INLINE(int) git_delta( - void **out, size_t *out_len, - const void *src_buf, size_t src_bufsize, - const void *trg_buf, size_t trg_bufsize, - size_t max_delta_size) -{ - git_delta_index *index; - int error = 0; - - *out = NULL; - *out_len = 0; - - if ((error = git_delta_index_init(&index, src_buf, src_bufsize)) < 0) - return error; - - if (index) { - error = git_delta_create_from_index(out, out_len, - index, trg_buf, trg_bufsize, max_delta_size); - - git_delta_index_free(index); - } - - return error; -} - -/* the smallest possible delta size is 4 bytes */ -#define GIT_DELTA_SIZE_MIN 4 - -/** -* Apply a git binary delta to recover the original content. -* The caller is responsible for freeing the returned buffer. -* -* @param out the output buffer -* @param out_len the length of the output buffer -* @param base the base to copy from during copy instructions. -* @param base_len number of bytes available at base. -* @param delta the delta to execute copy/insert instructions from. -* @param delta_len total number of bytes in the delta. -* @return 0 on success or an error code -*/ -extern int git_delta_apply( - void **out, - size_t *out_len, - const unsigned char *base, - size_t base_len, - const unsigned char *delta, - size_t delta_len); - -/** -* Read the header of a git binary delta. -* -* @param base_out pointer to store the base size field. -* @param result_out pointer to store the result size field. -* @param delta the delta to execute copy/insert instructions from. -* @param delta_len total number of bytes in the delta. -* @return 0 on success or an error code -*/ -extern int git_delta_read_header( - size_t *base_out, - size_t *result_out, - const unsigned char *delta, - size_t delta_len); - -/** - * Read the header of a git binary delta - * - * This variant reads just enough from the packfile stream to read the - * delta header. - */ -extern int git_delta_read_header_fromstream( - size_t *base_out, - size_t *result_out, - git_packfile_stream *stream); - -#endif diff --git a/src/describe.c b/src/describe.c deleted file mode 100644 index 1033eac50..000000000 --- a/src/describe.c +++ /dev/null @@ -1,909 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "git2/describe.h" -#include "git2/strarray.h" -#include "git2/diff.h" -#include "git2/status.h" - -#include "buf.h" -#include "commit.h" -#include "commit_list.h" -#include "oidmap.h" -#include "refs.h" -#include "repository.h" -#include "revwalk.h" -#include "tag.h" -#include "vector.h" -#include "wildmatch.h" - -/* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */ - -struct commit_name { - git_tag *tag; - unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ - unsigned name_checked:1; - git_oid sha1; - char *path; - - /* Khash workaround. They original key has to still be reachable */ - git_oid peeled; -}; - -static void *oidmap_value_bykey(git_oidmap *map, const git_oid *key) -{ - return git_oidmap_get(map, key); -} - -static struct commit_name *find_commit_name( - git_oidmap *names, - const git_oid *peeled) -{ - return (struct commit_name *)(oidmap_value_bykey(names, peeled)); -} - -static int replace_name( - git_tag **tag, - git_repository *repo, - struct commit_name *e, - unsigned int prio, - const git_oid *sha1) -{ - git_time_t e_time = 0, t_time = 0; - - if (!e || e->prio < prio) - return 1; - - if (e->prio == 2 && prio == 2) { - /* Multiple annotated tags point to the same commit. - * Select one to keep based upon their tagger date. - */ - git_tag *t = NULL; - - if (!e->tag) { - if (git_tag_lookup(&t, repo, &e->sha1) < 0) - return 1; - e->tag = t; - } - - if (git_tag_lookup(&t, repo, sha1) < 0) - return 0; - - *tag = t; - - if (e->tag->tagger) - e_time = e->tag->tagger->when.time; - - if (t->tagger) - t_time = t->tagger->when.time; - - if (e_time < t_time) - return 1; - } - - return 0; -} - -static int add_to_known_names( - git_repository *repo, - git_oidmap *names, - const char *path, - const git_oid *peeled, - unsigned int prio, - const git_oid *sha1) -{ - struct commit_name *e = find_commit_name(names, peeled); - bool found = (e != NULL); - - git_tag *tag = NULL; - if (replace_name(&tag, repo, e, prio, sha1)) { - if (!found) { - e = git__malloc(sizeof(struct commit_name)); - GIT_ERROR_CHECK_ALLOC(e); - - e->path = NULL; - e->tag = NULL; - } - - if (e->tag) - git_tag_free(e->tag); - e->tag = tag; - e->prio = prio; - e->name_checked = 0; - git_oid_cpy(&e->sha1, sha1); - git__free(e->path); - e->path = git__strdup(path); - git_oid_cpy(&e->peeled, peeled); - - if (!found && git_oidmap_set(names, &e->peeled, e) < 0) - return -1; - } - else - git_tag_free(tag); - - return 0; -} - -static int retrieve_peeled_tag_or_object_oid( - git_oid *peeled_out, - git_oid *ref_target_out, - git_repository *repo, - const char *refname) -{ - git_reference *ref; - git_object *peeled = NULL; - int error; - - if ((error = git_reference_lookup_resolved(&ref, repo, refname, -1)) < 0) - return error; - - if ((error = git_reference_peel(&peeled, ref, GIT_OBJECT_ANY)) < 0) - goto cleanup; - - git_oid_cpy(ref_target_out, git_reference_target(ref)); - git_oid_cpy(peeled_out, git_object_id(peeled)); - - if (git_oid_cmp(ref_target_out, peeled_out) != 0) - error = 1; /* The reference was pointing to a annotated tag */ - else - error = 0; /* Any other object */ - -cleanup: - git_reference_free(ref); - git_object_free(peeled); - return error; -} - -struct git_describe_result { - int dirty; - int exact_match; - int fallback_to_id; - git_oid commit_id; - git_repository *repo; - struct commit_name *name; - struct possible_tag *tag; -}; - -struct get_name_data -{ - git_describe_options *opts; - git_repository *repo; - git_oidmap *names; - git_describe_result *result; -}; - -static int commit_name_dup(struct commit_name **out, struct commit_name *in) -{ - struct commit_name *name; - - name = git__malloc(sizeof(struct commit_name)); - GIT_ERROR_CHECK_ALLOC(name); - - memcpy(name, in, sizeof(struct commit_name)); - name->tag = NULL; - name->path = NULL; - - if (in->tag && git_tag_dup(&name->tag, in->tag) < 0) - return -1; - - name->path = git__strdup(in->path); - GIT_ERROR_CHECK_ALLOC(name->path); - - *out = name; - return 0; -} - -static int get_name(const char *refname, void *payload) -{ - struct get_name_data *data; - bool is_tag, is_annotated, all; - git_oid peeled, sha1; - unsigned int prio; - int error = 0; - - data = (struct get_name_data *)payload; - is_tag = !git__prefixcmp(refname, GIT_REFS_TAGS_DIR); - all = data->opts->describe_strategy == GIT_DESCRIBE_ALL; - - /* Reject anything outside refs/tags/ unless --all */ - if (!all && !is_tag) - return 0; - - /* Accept only tags that match the pattern, if given */ - if (data->opts->pattern && (!is_tag || wildmatch(data->opts->pattern, - refname + strlen(GIT_REFS_TAGS_DIR), 0))) - return 0; - - /* Is it annotated? */ - if ((error = retrieve_peeled_tag_or_object_oid( - &peeled, &sha1, data->repo, refname)) < 0) - return error; - - is_annotated = error; - - /* - * By default, we only use annotated tags, but with --tags - * we fall back to lightweight ones (even without --tags, - * we still remember lightweight ones, only to give hints - * in an error message). --all allows any refs to be used. - */ - if (is_annotated) - prio = 2; - else if (is_tag) - prio = 1; - else - prio = 0; - - add_to_known_names(data->repo, data->names, - all ? refname + strlen(GIT_REFS_DIR) : refname + strlen(GIT_REFS_TAGS_DIR), - &peeled, prio, &sha1); - return 0; -} - -struct possible_tag { - struct commit_name *name; - int depth; - int found_order; - unsigned flag_within; -}; - -static int possible_tag_dup(struct possible_tag **out, struct possible_tag *in) -{ - struct possible_tag *tag; - int error; - - tag = git__malloc(sizeof(struct possible_tag)); - GIT_ERROR_CHECK_ALLOC(tag); - - memcpy(tag, in, sizeof(struct possible_tag)); - tag->name = NULL; - - if ((error = commit_name_dup(&tag->name, in->name)) < 0) { - git__free(tag); - *out = NULL; - return error; - } - - *out = tag; - return 0; -} - -static int compare_pt(const void *a_, const void *b_) -{ - struct possible_tag *a = (struct possible_tag *)a_; - struct possible_tag *b = (struct possible_tag *)b_; - if (a->depth != b->depth) - return a->depth - b->depth; - if (a->found_order != b->found_order) - return a->found_order - b->found_order; - return 0; -} - -#define SEEN (1u << 0) - -static unsigned long finish_depth_computation( - git_pqueue *list, - git_revwalk *walk, - struct possible_tag *best) -{ - unsigned long seen_commits = 0; - int error, i; - - while (git_pqueue_size(list) > 0) { - git_commit_list_node *c = git_pqueue_pop(list); - seen_commits++; - if (c->flags & best->flag_within) { - size_t index = 0; - while (git_pqueue_size(list) > index) { - git_commit_list_node *i = git_pqueue_get(list, index); - if (!(i->flags & best->flag_within)) - break; - index++; - } - if (index > git_pqueue_size(list)) - break; - } else - best->depth++; - for (i = 0; i < c->out_degree; i++) { - git_commit_list_node *p = c->parents[i]; - if ((error = git_commit_list_parse(walk, p)) < 0) - return error; - if (!(p->flags & SEEN)) - if ((error = git_pqueue_insert(list, p)) < 0) - return error; - p->flags |= c->flags; - } - } - return seen_commits; -} - -static int display_name(git_str *buf, git_repository *repo, struct commit_name *n) -{ - if (n->prio == 2 && !n->tag) { - if (git_tag_lookup(&n->tag, repo, &n->sha1) < 0) { - git_error_set(GIT_ERROR_TAG, "annotated tag '%s' not available", n->path); - return -1; - } - } - - if (n->tag && !n->name_checked) { - if (!git_tag_name(n->tag)) { - git_error_set(GIT_ERROR_TAG, "annotated tag '%s' has no embedded name", n->path); - return -1; - } - - /* TODO: Cope with warnings - if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) - warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path); - */ - - n->name_checked = 1; - } - - if (n->tag) - git_str_printf(buf, "%s", git_tag_name(n->tag)); - else - git_str_printf(buf, "%s", n->path); - - return 0; -} - -static int find_unique_abbrev_size( - int *out, - git_repository *repo, - const git_oid *oid_in, - unsigned int abbreviated_size) -{ - size_t size = abbreviated_size; - git_odb *odb; - git_oid dummy; - int error; - - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) - return error; - - while (size < GIT_OID_HEXSZ) { - if ((error = git_odb_exists_prefix(&dummy, odb, oid_in, size)) == 0) { - *out = (int) size; - return 0; - } - - /* If the error wasn't that it's not unique, then it's a proper error */ - if (error != GIT_EAMBIGUOUS) - return error; - - /* Try again with a larger size */ - size++; - } - - /* If we didn't find any shorter prefix, we have to do the whole thing */ - *out = GIT_OID_HEXSZ; - - return 0; -} - -static int show_suffix( - git_str *buf, - int depth, - git_repository *repo, - const git_oid *id, - unsigned int abbrev_size) -{ - int error, size = 0; - - char hex_oid[GIT_OID_HEXSZ]; - - if ((error = find_unique_abbrev_size(&size, repo, id, abbrev_size)) < 0) - return error; - - git_oid_fmt(hex_oid, id); - - git_str_printf(buf, "-%d-g", depth); - - git_str_put(buf, hex_oid, size); - - return git_str_oom(buf) ? -1 : 0; -} - -#define MAX_CANDIDATES_TAGS FLAG_BITS - 1 - -static int describe_not_found(const git_oid *oid, const char *message_format) { - char oid_str[GIT_OID_HEXSZ + 1]; - git_oid_tostr(oid_str, sizeof(oid_str), oid); - - git_error_set(GIT_ERROR_DESCRIBE, message_format, oid_str); - return GIT_ENOTFOUND; -} - -static int describe( - struct get_name_data *data, - git_commit *commit) -{ - struct commit_name *n; - struct possible_tag *best; - bool all, tags; - git_revwalk *walk = NULL; - git_pqueue list; - git_commit_list_node *cmit, *gave_up_on = NULL; - git_vector all_matches = GIT_VECTOR_INIT; - unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; - unsigned long seen_commits = 0; /* TODO: Check long */ - unsigned int unannotated_cnt = 0; - int error; - - if (git_vector_init(&all_matches, MAX_CANDIDATES_TAGS, compare_pt) < 0) - return -1; - - if ((error = git_pqueue_init(&list, 0, 2, git_commit_list_time_cmp)) < 0) - goto cleanup; - - all = data->opts->describe_strategy == GIT_DESCRIBE_ALL; - tags = data->opts->describe_strategy == GIT_DESCRIBE_TAGS; - - git_oid_cpy(&data->result->commit_id, git_commit_id(commit)); - - n = find_commit_name(data->names, git_commit_id(commit)); - if (n && (tags || all || n->prio == 2)) { - /* - * Exact match to an existing ref. - */ - data->result->exact_match = 1; - if ((error = commit_name_dup(&data->result->name, n)) < 0) - goto cleanup; - - goto cleanup; - } - - if (!data->opts->max_candidates_tags) { - error = describe_not_found( - git_commit_id(commit), - "cannot describe - no tag exactly matches '%s'"); - - goto cleanup; - } - - if ((error = git_revwalk_new(&walk, git_commit_owner(commit))) < 0) - goto cleanup; - - if ((cmit = git_revwalk__commit_lookup(walk, git_commit_id(commit))) == NULL) - goto cleanup; - - if ((error = git_commit_list_parse(walk, cmit)) < 0) - goto cleanup; - - cmit->flags = SEEN; - - if ((error = git_pqueue_insert(&list, cmit)) < 0) - goto cleanup; - - while (git_pqueue_size(&list) > 0) - { - int i; - - git_commit_list_node *c = (git_commit_list_node *)git_pqueue_pop(&list); - seen_commits++; - - n = find_commit_name(data->names, &c->oid); - - if (n) { - if (!tags && !all && n->prio < 2) { - unannotated_cnt++; - } else if (match_cnt < data->opts->max_candidates_tags) { - struct possible_tag *t = git__malloc(sizeof(struct commit_name)); - GIT_ERROR_CHECK_ALLOC(t); - if ((error = git_vector_insert(&all_matches, t)) < 0) - goto cleanup; - - match_cnt++; - - t->name = n; - t->depth = seen_commits - 1; - t->flag_within = 1u << match_cnt; - t->found_order = match_cnt; - c->flags |= t->flag_within; - if (n->prio == 2) - annotated_cnt++; - } - else { - gave_up_on = c; - break; - } - } - - for (cur_match = 0; cur_match < match_cnt; cur_match++) { - struct possible_tag *t = git_vector_get(&all_matches, cur_match); - if (!(c->flags & t->flag_within)) - t->depth++; - } - - if (annotated_cnt && (git_pqueue_size(&list) == 0)) { - /* - if (debug) { - char oid_str[GIT_OID_HEXSZ + 1]; - git_oid_tostr(oid_str, sizeof(oid_str), &c->oid); - - fprintf(stderr, "finished search at %s\n", oid_str); - } - */ - break; - } - for (i = 0; i < c->out_degree; i++) { - git_commit_list_node *p = c->parents[i]; - if ((error = git_commit_list_parse(walk, p)) < 0) - goto cleanup; - if (!(p->flags & SEEN)) - if ((error = git_pqueue_insert(&list, p)) < 0) - goto cleanup; - p->flags |= c->flags; - - if (data->opts->only_follow_first_parent) - break; - } - } - - if (!match_cnt) { - if (data->opts->show_commit_oid_as_fallback) { - data->result->fallback_to_id = 1; - git_oid_cpy(&data->result->commit_id, &cmit->oid); - - goto cleanup; - } - if (unannotated_cnt) { - error = describe_not_found(git_commit_id(commit), - "cannot describe - " - "no annotated tags can describe '%s'; " - "however, there were unannotated tags."); - goto cleanup; - } - else { - error = describe_not_found(git_commit_id(commit), - "cannot describe - " - "no tags can describe '%s'."); - goto cleanup; - } - } - - git_vector_sort(&all_matches); - - best = (struct possible_tag *)git_vector_get(&all_matches, 0); - - if (gave_up_on) { - if ((error = git_pqueue_insert(&list, gave_up_on)) < 0) - goto cleanup; - seen_commits--; - } - if ((error = finish_depth_computation( - &list, walk, best)) < 0) - goto cleanup; - - seen_commits += error; - if ((error = possible_tag_dup(&data->result->tag, best)) < 0) - goto cleanup; - - /* - { - static const char *prio_names[] = { - "head", "lightweight", "annotated", - }; - - char oid_str[GIT_OID_HEXSZ + 1]; - - if (debug) { - for (cur_match = 0; cur_match < match_cnt; cur_match++) { - struct possible_tag *t = (struct possible_tag *)git_vector_get(&all_matches, cur_match); - fprintf(stderr, " %-11s %8d %s\n", - prio_names[t->name->prio], - t->depth, t->name->path); - } - fprintf(stderr, "traversed %lu commits\n", seen_commits); - if (gave_up_on) { - git_oid_tostr(oid_str, sizeof(oid_str), &gave_up_on->oid); - fprintf(stderr, - "more than %i tags found; listed %i most recent\n" - "gave up search at %s\n", - data->opts->max_candidates_tags, data->opts->max_candidates_tags, - oid_str); - } - } - } - */ - - git_oid_cpy(&data->result->commit_id, &cmit->oid); - -cleanup: - { - size_t i; - struct possible_tag *match; - git_vector_foreach(&all_matches, i, match) { - git__free(match); - } - } - git_vector_free(&all_matches); - git_pqueue_free(&list); - git_revwalk_free(walk); - return error; -} - -static int normalize_options( - git_describe_options *dst, - const git_describe_options *src) -{ - git_describe_options default_options = GIT_DESCRIBE_OPTIONS_INIT; - if (!src) src = &default_options; - - *dst = *src; - - if (dst->max_candidates_tags > GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS) - dst->max_candidates_tags = GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS; - - return 0; -} - -int git_describe_commit( - git_describe_result **result, - git_object *committish, - git_describe_options *opts) -{ - struct get_name_data data; - struct commit_name *name; - git_commit *commit; - int error = -1; - git_describe_options normalized; - - GIT_ASSERT_ARG(result); - GIT_ASSERT_ARG(committish); - - data.result = git__calloc(1, sizeof(git_describe_result)); - GIT_ERROR_CHECK_ALLOC(data.result); - data.result->repo = git_object_owner(committish); - - data.repo = git_object_owner(committish); - - if ((error = normalize_options(&normalized, opts)) < 0) - return error; - - GIT_ERROR_CHECK_VERSION( - &normalized, - GIT_DESCRIBE_OPTIONS_VERSION, - "git_describe_options"); - data.opts = &normalized; - - if ((error = git_oidmap_new(&data.names)) < 0) - return error; - - /** TODO: contains to be implemented */ - - if ((error = git_object_peel((git_object **)(&commit), committish, GIT_OBJECT_COMMIT)) < 0) - goto cleanup; - - if ((error = git_reference_foreach_name( - git_object_owner(committish), - get_name, &data)) < 0) - goto cleanup; - - if (git_oidmap_size(data.names) == 0 && !normalized.show_commit_oid_as_fallback) { - git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - " - "no reference found, cannot describe anything."); - error = -1; - goto cleanup; - } - - if ((error = describe(&data, commit)) < 0) - goto cleanup; - -cleanup: - git_commit_free(commit); - - git_oidmap_foreach_value(data.names, name, { - git_tag_free(name->tag); - git__free(name->path); - git__free(name); - }); - - git_oidmap_free(data.names); - - if (error < 0) - git_describe_result_free(data.result); - else - *result = data.result; - - return error; -} - -int git_describe_workdir( - git_describe_result **out, - git_repository *repo, - git_describe_options *opts) -{ - int error; - git_oid current_id; - git_status_list *status = NULL; - git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; - git_describe_result *result = NULL; - git_object *commit; - - if ((error = git_reference_name_to_id(¤t_id, repo, GIT_HEAD_FILE)) < 0) - return error; - - if ((error = git_object_lookup(&commit, repo, ¤t_id, GIT_OBJECT_COMMIT)) < 0) - return error; - - /* The first step is to perform a describe of HEAD, so we can leverage this */ - if ((error = git_describe_commit(&result, commit, opts)) < 0) - goto out; - - if ((error = git_status_list_new(&status, repo, &status_opts)) < 0) - goto out; - - - if (git_status_list_entrycount(status) > 0) - result->dirty = 1; - -out: - git_object_free(commit); - git_status_list_free(status); - - if (error < 0) - git_describe_result_free(result); - else - *out = result; - - return error; -} - -static int normalize_format_options( - git_describe_format_options *dst, - const git_describe_format_options *src) -{ - if (!src) { - git_describe_format_options_init(dst, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION); - return 0; - } - - memcpy(dst, src, sizeof(git_describe_format_options)); - return 0; -} - -static int git_describe__format( - git_str *out, - const git_describe_result *result, - const git_describe_format_options *given) -{ - int error; - git_repository *repo; - struct commit_name *name; - git_describe_format_options opts; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(result); - - GIT_ERROR_CHECK_VERSION(given, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION, "git_describe_format_options"); - normalize_format_options(&opts, given); - - if (opts.always_use_long_format && opts.abbreviated_size == 0) { - git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - " - "'always_use_long_format' is incompatible with a zero" - "'abbreviated_size'"); - return -1; - } - - - repo = result->repo; - - /* If we did find an exact match, then it's the easier method */ - if (result->exact_match) { - name = result->name; - if ((error = display_name(out, repo, name)) < 0) - return error; - - if (opts.always_use_long_format) { - const git_oid *id = name->tag ? git_tag_target_id(name->tag) : &result->commit_id; - if ((error = show_suffix(out, 0, repo, id, opts.abbreviated_size)) < 0) - return error; - } - - if (result->dirty && opts.dirty_suffix) - git_str_puts(out, opts.dirty_suffix); - - return git_str_oom(out) ? -1 : 0; - } - - /* If we didn't find *any* tags, we fall back to the commit's id */ - if (result->fallback_to_id) { - char hex_oid[GIT_OID_HEXSZ + 1] = {0}; - int size = 0; - - if ((error = find_unique_abbrev_size( - &size, repo, &result->commit_id, opts.abbreviated_size)) < 0) - return -1; - - git_oid_fmt(hex_oid, &result->commit_id); - git_str_put(out, hex_oid, size); - - if (result->dirty && opts.dirty_suffix) - git_str_puts(out, opts.dirty_suffix); - - return git_str_oom(out) ? -1 : 0; - } - - /* Lastly, if we found a matching tag, we show that */ - name = result->tag->name; - - if ((error = display_name(out, repo, name)) < 0) - return error; - - if (opts.abbreviated_size) { - if ((error = show_suffix(out, result->tag->depth, repo, - &result->commit_id, opts.abbreviated_size)) < 0) - return error; - } - - if (result->dirty && opts.dirty_suffix) { - git_str_puts(out, opts.dirty_suffix); - } - - return git_str_oom(out) ? -1 : 0; -} - -int git_describe_format( - git_buf *out, - const git_describe_result *result, - const git_describe_format_options *given) -{ - GIT_BUF_WRAP_PRIVATE(out, git_describe__format, result, given); -} - -void git_describe_result_free(git_describe_result *result) -{ - if (result == NULL) - return; - - if (result->name) { - git_tag_free(result->name->tag); - git__free(result->name->path); - git__free(result->name); - } - - if (result->tag) { - git_tag_free(result->tag->name->tag); - git__free(result->tag->name->path); - git__free(result->tag->name); - git__free(result->tag); - } - - git__free(result); -} - -int git_describe_options_init(git_describe_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_describe_options, GIT_DESCRIBE_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_describe_init_options(git_describe_options *opts, unsigned int version) -{ - return git_describe_options_init(opts, version); -} -#endif - -int git_describe_format_options_init(git_describe_format_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_describe_format_options, GIT_DESCRIBE_FORMAT_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_describe_init_format_options(git_describe_format_options *opts, unsigned int version) -{ - return git_describe_format_options_init(opts, version); -} -#endif diff --git a/src/diff.c b/src/diff.c deleted file mode 100644 index 9840d6050..000000000 --- a/src/diff.c +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "diff.h" - -#include "common.h" -#include "buf.h" -#include "patch.h" -#include "email.h" -#include "commit.h" -#include "index.h" -#include "diff_generate.h" - -#include "git2/version.h" -#include "git2/email.h" - -struct patch_id_args { - git_hash_ctx ctx; - git_oid result; - int first_file; -}; - -GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta) -{ - const char *str = delta->old_file.path; - - if (!str || - delta->status == GIT_DELTA_ADDED || - delta->status == GIT_DELTA_RENAMED || - delta->status == GIT_DELTA_COPIED) - str = delta->new_file.path; - - return str; -} - -int git_diff_delta__cmp(const void *a, const void *b) -{ - const git_diff_delta *da = a, *db = b; - int val = strcmp(diff_delta__path(da), diff_delta__path(db)); - return val ? val : ((int)da->status - (int)db->status); -} - -int git_diff_delta__casecmp(const void *a, const void *b) -{ - const git_diff_delta *da = a, *db = b; - int val = strcasecmp(diff_delta__path(da), diff_delta__path(db)); - return val ? val : ((int)da->status - (int)db->status); -} - -int git_diff__entry_cmp(const void *a, const void *b) -{ - const git_index_entry *entry_a = a; - const git_index_entry *entry_b = b; - - return strcmp(entry_a->path, entry_b->path); -} - -int git_diff__entry_icmp(const void *a, const void *b) -{ - const git_index_entry *entry_a = a; - const git_index_entry *entry_b = b; - - return strcasecmp(entry_a->path, entry_b->path); -} - -void git_diff_free(git_diff *diff) -{ - if (!diff) - return; - - GIT_REFCOUNT_DEC(diff, diff->free_fn); -} - -void git_diff_addref(git_diff *diff) -{ - GIT_REFCOUNT_INC(diff); -} - -size_t git_diff_num_deltas(const git_diff *diff) -{ - GIT_ASSERT_ARG(diff); - return diff->deltas.length; -} - -size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type) -{ - size_t i, count = 0; - const git_diff_delta *delta; - - GIT_ASSERT_ARG(diff); - - git_vector_foreach(&diff->deltas, i, delta) { - count += (delta->status == type); - } - - return count; -} - -const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx) -{ - GIT_ASSERT_ARG_WITH_RETVAL(diff, NULL); - return git_vector_get(&diff->deltas, idx); -} - -int git_diff_is_sorted_icase(const git_diff *diff) -{ - return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0; -} - -int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff) -{ - GIT_ASSERT_ARG(out); - GIT_ERROR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata"); - out->stat_calls = diff->perf.stat_calls; - out->oid_calculations = diff->perf.oid_calculations; - return 0; -} - -int git_diff_foreach( - git_diff *diff, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb data_cb, - void *payload) -{ - int error = 0; - git_diff_delta *delta; - size_t idx; - - GIT_ASSERT_ARG(diff); - - git_vector_foreach(&diff->deltas, idx, delta) { - git_patch *patch; - - /* check flags against patch status */ - if (git_diff_delta__should_skip(&diff->opts, delta)) - continue; - - if ((error = git_patch_from_diff(&patch, diff, idx)) != 0) - break; - - error = git_patch__invoke_callbacks(patch, file_cb, binary_cb, - hunk_cb, data_cb, payload); - git_patch_free(patch); - - if (error) - break; - } - - return error; -} - -#ifndef GIT_DEPRECATE_HARD - -int git_diff_format_email( - git_buf *out, - git_diff *diff, - const git_diff_format_email_options *opts) -{ - git_email_create_options email_create_opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - git_str email = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diff); - GIT_ASSERT_ARG(opts && opts->summary && opts->id && opts->author); - - GIT_ERROR_CHECK_VERSION(opts, - GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, - "git_format_email_options"); - - /* This is a `git_buf` special case; subsequent calls append. */ - email.ptr = out->ptr; - email.asize = out->reserved; - email.size = out->size; - - out->ptr = git_str__initstr; - out->reserved = 0; - out->size = 0; - - if ((opts->flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0) - email_create_opts.subject_prefix = ""; - - error = git_email__append_from_diff(&email, diff, opts->patch_no, - opts->total_patches, opts->id, opts->summary, opts->body, - opts->author, &email_create_opts); - - if (error < 0) - goto done; - - error = git_buf_fromstr(out, &email); - -done: - git_str_dispose(&email); - return error; -} - -int git_diff_commit_as_email( - git_buf *out, - git_repository *repo, - git_commit *commit, - size_t patch_no, - size_t total_patches, - uint32_t flags, - const git_diff_options *diff_opts) -{ - git_diff *diff = NULL; - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - const git_oid *commit_id; - const char *summary, *body; - const git_signature *author; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(commit); - - commit_id = git_commit_id(commit); - summary = git_commit_summary(commit); - body = git_commit_body(commit); - author = git_commit_author(commit); - - if ((flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0) - opts.subject_prefix = ""; - - if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0) - return error; - - error = git_email_create_from_diff(out, diff, patch_no, total_patches, commit_id, summary, body, author, &opts); - - git_diff_free(diff); - return error; -} - -int git_diff_init_options(git_diff_options *opts, unsigned int version) -{ - return git_diff_options_init(opts, version); -} - -int git_diff_find_init_options( - git_diff_find_options *opts, unsigned int version) -{ - return git_diff_find_options_init(opts, version); -} - -int git_diff_format_email_options_init( - git_diff_format_email_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_diff_format_email_options, - GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT); - return 0; -} - -int git_diff_format_email_init_options( - git_diff_format_email_options *opts, unsigned int version) -{ - return git_diff_format_email_options_init(opts, version); -} - -#endif - -int git_diff_options_init(git_diff_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT); - return 0; -} - -int git_diff_find_options_init( - git_diff_find_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT); - return 0; -} - -static int flush_hunk(git_oid *result, git_hash_ctx *ctx) -{ - git_oid hash; - unsigned short carry = 0; - int error, i; - - if ((error = git_hash_final(hash.id, ctx)) < 0 || - (error = git_hash_init(ctx)) < 0) - return error; - - for (i = 0; i < GIT_OID_RAWSZ; i++) { - carry += result->id[i] + hash.id[i]; - result->id[i] = (unsigned char)carry; - carry >>= 8; - } - - return 0; -} - -static void strip_spaces(git_str *buf) -{ - char *src = buf->ptr, *dst = buf->ptr; - char c; - size_t len = 0; - - while ((c = *src++) != '\0') { - if (!git__isspace(c)) { - *dst++ = c; - len++; - } - } - - git_str_truncate(buf, len); -} - -static int diff_patchid_print_callback_to_buf( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *payload) -{ - struct patch_id_args *args = (struct patch_id_args *) payload; - git_str buf = GIT_STR_INIT; - int error = 0; - - if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL || - line->origin == GIT_DIFF_LINE_ADD_EOFNL || - line->origin == GIT_DIFF_LINE_DEL_EOFNL) - goto out; - - if ((error = git_diff_print_callback__to_buf(delta, hunk, - line, &buf)) < 0) - goto out; - - strip_spaces(&buf); - - if (line->origin == GIT_DIFF_LINE_FILE_HDR && - !args->first_file && - (error = flush_hunk(&args->result, &args->ctx) < 0)) - goto out; - - if ((error = git_hash_update(&args->ctx, buf.ptr, buf.size)) < 0) - goto out; - - if (line->origin == GIT_DIFF_LINE_FILE_HDR && args->first_file) - args->first_file = 0; - -out: - git_str_dispose(&buf); - return error; -} - -int git_diff_patchid_options_init(git_diff_patchid_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_diff_patchid_options, GIT_DIFF_PATCHID_OPTIONS_INIT); - return 0; -} - -int git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opts) -{ - struct patch_id_args args; - int error; - - GIT_ERROR_CHECK_VERSION( - opts, GIT_DIFF_PATCHID_OPTIONS_VERSION, "git_diff_patchid_options"); - - memset(&args, 0, sizeof(args)); - args.first_file = 1; - if ((error = git_hash_ctx_init(&args.ctx, GIT_HASH_ALGORITHM_SHA1)) < 0) - goto out; - - if ((error = git_diff_print(diff, - GIT_DIFF_FORMAT_PATCH_ID, - diff_patchid_print_callback_to_buf, - &args)) < 0) - goto out; - - if ((error = (flush_hunk(&args.result, &args.ctx))) < 0) - goto out; - - git_oid_cpy(out, &args.result); - -out: - git_hash_ctx_cleanup(&args.ctx); - return error; -} diff --git a/src/diff.h b/src/diff.h deleted file mode 100644 index 2cc35e65b..000000000 --- a/src/diff.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_diff_h__ -#define INCLUDE_diff_h__ - -#include "common.h" - -#include "git2/diff.h" -#include "git2/patch.h" -#include "git2/sys/diff.h" -#include "git2/oid.h" - -#include "vector.h" -#include "iterator.h" -#include "repository.h" -#include "pool.h" -#include "odb.h" - -#define DIFF_OLD_PREFIX_DEFAULT "a/" -#define DIFF_NEW_PREFIX_DEFAULT "b/" - -typedef enum { - GIT_DIFF_TYPE_UNKNOWN = 0, - GIT_DIFF_TYPE_GENERATED = 1, - GIT_DIFF_TYPE_PARSED = 2 -} git_diff_origin_t; - -struct git_diff { - git_refcount rc; - git_repository *repo; - git_attr_session attrsession; - git_diff_origin_t type; - git_diff_options opts; - git_vector deltas; /* vector of git_diff_delta */ - git_pool pool; - git_iterator_t old_src; - git_iterator_t new_src; - git_diff_perfdata perf; - - int (*strcomp)(const char *, const char *); - int (*strncomp)(const char *, const char *, size_t); - int (*pfxcomp)(const char *str, const char *pfx); - int (*entrycomp)(const void *a, const void *b); - - int (*patch_fn)(git_patch **out, git_diff *diff, size_t idx); - void (*free_fn)(git_diff *diff); -}; - -extern int git_diff_delta__format_file_header( - git_str *out, - const git_diff_delta *delta, - const char *oldpfx, - const char *newpfx, - int oid_strlen, - bool print_index); - -extern int git_diff_delta__cmp(const void *a, const void *b); -extern int git_diff_delta__casecmp(const void *a, const void *b); - -extern int git_diff__entry_cmp(const void *a, const void *b); -extern int git_diff__entry_icmp(const void *a, const void *b); - -#endif diff --git a/src/diff_driver.c b/src/diff_driver.c deleted file mode 100644 index 5f25fdb44..000000000 --- a/src/diff_driver.c +++ /dev/null @@ -1,522 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "diff_driver.h" - -#include "git2/attr.h" - -#include "common.h" -#include "diff.h" -#include "strmap.h" -#include "map.h" -#include "config.h" -#include "regexp.h" -#include "repository.h" - -typedef enum { - DIFF_DRIVER_AUTO = 0, - DIFF_DRIVER_BINARY = 1, - DIFF_DRIVER_TEXT = 2, - DIFF_DRIVER_PATTERNLIST = 3 -} git_diff_driver_t; - -typedef struct { - git_regexp re; - int flags; -} git_diff_driver_pattern; - -enum { - REG_NEGATE = (1 << 15) /* get out of the way of existing flags */ -}; - -/* data for finding function context for a given file type */ -struct git_diff_driver { - git_diff_driver_t type; - uint32_t binary_flags; - uint32_t other_flags; - git_array_t(git_diff_driver_pattern) fn_patterns; - git_regexp word_pattern; - char name[GIT_FLEX_ARRAY]; -}; - -#include "userdiff.h" - -struct git_diff_driver_registry { - git_strmap *drivers; -}; - -#define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY) - -static git_diff_driver diff_driver_auto = { DIFF_DRIVER_AUTO, 0, 0 }; -static git_diff_driver diff_driver_binary = { DIFF_DRIVER_BINARY, GIT_DIFF_FORCE_BINARY, 0 }; -static git_diff_driver diff_driver_text = { DIFF_DRIVER_TEXT, GIT_DIFF_FORCE_TEXT, 0 }; - -git_diff_driver_registry *git_diff_driver_registry_new(void) -{ - git_diff_driver_registry *reg = - git__calloc(1, sizeof(git_diff_driver_registry)); - if (!reg) - return NULL; - - if (git_strmap_new(®->drivers) < 0) { - git_diff_driver_registry_free(reg); - return NULL; - } - - return reg; -} - -void git_diff_driver_registry_free(git_diff_driver_registry *reg) -{ - git_diff_driver *drv; - - if (!reg) - return; - - git_strmap_foreach_value(reg->drivers, drv, git_diff_driver_free(drv)); - git_strmap_free(reg->drivers); - git__free(reg); -} - -static int diff_driver_add_patterns( - git_diff_driver *drv, const char *regex_str, int regex_flags) -{ - int error = 0; - const char *scan, *end; - git_diff_driver_pattern *pat = NULL; - git_str buf = GIT_STR_INIT; - - for (scan = regex_str; scan; scan = end) { - /* get pattern to fill in */ - if ((pat = git_array_alloc(drv->fn_patterns)) == NULL) { - return -1; - } - - pat->flags = regex_flags; - if (*scan == '!') { - pat->flags |= REG_NEGATE; - ++scan; - } - - if ((end = strchr(scan, '\n')) != NULL) { - error = git_str_set(&buf, scan, end - scan); - end++; - } else { - error = git_str_sets(&buf, scan); - } - if (error < 0) - break; - - if ((error = git_regexp_compile(&pat->re, buf.ptr, regex_flags)) != 0) { - /* - * TODO: issue a warning - */ - } - } - - if (error && pat != NULL) - (void)git_array_pop(drv->fn_patterns); /* release last item */ - git_str_dispose(&buf); - - /* We want to ignore bad patterns, so return success regardless */ - return 0; -} - -static int diff_driver_xfuncname(const git_config_entry *entry, void *payload) -{ - return diff_driver_add_patterns(payload, entry->value, 0); -} - -static int diff_driver_funcname(const git_config_entry *entry, void *payload) -{ - return diff_driver_add_patterns(payload, entry->value, 0); -} - -static git_diff_driver_registry *git_repository_driver_registry( - git_repository *repo) -{ - git_diff_driver_registry *reg = git_atomic_load(repo->diff_drivers), *newreg; - if (reg) - return reg; - - newreg = git_diff_driver_registry_new(); - if (!newreg) { - git_error_set(GIT_ERROR_REPOSITORY, "unable to create diff driver registry"); - return newreg; - } - reg = git_atomic_compare_and_swap(&repo->diff_drivers, NULL, newreg); - if (!reg) { - reg = newreg; - } else { - /* if we race, free losing allocation */ - git_diff_driver_registry_free(newreg); - } - return reg; -} - -static int diff_driver_alloc( - git_diff_driver **out, size_t *namelen_out, const char *name) -{ - git_diff_driver *driver; - size_t driverlen = sizeof(git_diff_driver), - namelen = strlen(name), - alloclen; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, driverlen, namelen); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - - driver = git__calloc(1, alloclen); - GIT_ERROR_CHECK_ALLOC(driver); - - memcpy(driver->name, name, namelen); - - *out = driver; - - if (namelen_out) - *namelen_out = namelen; - - return 0; -} - -static int git_diff_driver_builtin( - git_diff_driver **out, - git_diff_driver_registry *reg, - const char *driver_name) -{ - git_diff_driver_definition *ddef = NULL; - git_diff_driver *drv = NULL; - int error = 0; - size_t idx; - - for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) { - if (!strcasecmp(driver_name, builtin_defs[idx].name)) { - ddef = &builtin_defs[idx]; - break; - } - } - if (!ddef) - goto done; - - if ((error = diff_driver_alloc(&drv, NULL, ddef->name)) < 0) - goto done; - - drv->type = DIFF_DRIVER_PATTERNLIST; - - if (ddef->fns && - (error = diff_driver_add_patterns( - drv, ddef->fns, ddef->flags)) < 0) - goto done; - - if (ddef->words && - (error = git_regexp_compile(&drv->word_pattern, ddef->words, ddef->flags)) < 0) - goto done; - - if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0) - goto done; - -done: - if (error && drv) - git_diff_driver_free(drv); - else - *out = drv; - - return error; -} - -static int git_diff_driver_load( - git_diff_driver **out, git_repository *repo, const char *driver_name) -{ - int error = 0; - git_diff_driver_registry *reg; - git_diff_driver *drv; - size_t namelen; - git_config *cfg = NULL; - git_str name = GIT_STR_INIT; - git_config_entry *ce = NULL; - bool found_driver = false; - - if ((reg = git_repository_driver_registry(repo)) == NULL) - return -1; - - if ((drv = git_strmap_get(reg->drivers, driver_name)) != NULL) { - *out = drv; - return 0; - } - - if ((error = diff_driver_alloc(&drv, &namelen, driver_name)) < 0) - goto done; - - drv->type = DIFF_DRIVER_AUTO; - - /* if you can't read config for repo, just use default driver */ - if (git_repository_config_snapshot(&cfg, repo) < 0) { - git_error_clear(); - goto done; - } - - if ((error = git_str_printf(&name, "diff.%s.binary", driver_name)) < 0) - goto done; - - switch (git_config__get_bool_force(cfg, name.ptr, -1)) { - case true: - /* if diff..binary is true, just return the binary driver */ - *out = &diff_driver_binary; - goto done; - case false: - /* if diff..binary is false, force binary checks off */ - /* but still may have custom function context patterns, etc. */ - drv->binary_flags = GIT_DIFF_FORCE_TEXT; - found_driver = true; - break; - default: - /* diff..binary unspecified or "auto", so just continue */ - break; - } - - /* TODO: warn if diff..command or diff..textconv are set */ - - git_str_truncate(&name, namelen + strlen("diff..")); - if ((error = git_str_PUTS(&name, "xfuncname")) < 0) - goto done; - - if ((error = git_config_get_multivar_foreach( - cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) { - if (error != GIT_ENOTFOUND) - goto done; - git_error_clear(); /* no diff..xfuncname, so just continue */ - } - - git_str_truncate(&name, namelen + strlen("diff..")); - if ((error = git_str_PUTS(&name, "funcname")) < 0) - goto done; - - if ((error = git_config_get_multivar_foreach( - cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) { - if (error != GIT_ENOTFOUND) - goto done; - git_error_clear(); /* no diff..funcname, so just continue */ - } - - /* if we found any patterns, set driver type to use correct callback */ - if (git_array_size(drv->fn_patterns) > 0) { - drv->type = DIFF_DRIVER_PATTERNLIST; - found_driver = true; - } - - git_str_truncate(&name, namelen + strlen("diff..")); - if ((error = git_str_PUTS(&name, "wordregex")) < 0) - goto done; - - if ((error = git_config__lookup_entry(&ce, cfg, name.ptr, false)) < 0) - goto done; - if (!ce || !ce->value) - /* no diff..wordregex, so just continue */; - else if (!(error = git_regexp_compile(&drv->word_pattern, ce->value, 0))) - found_driver = true; - else { - /* TODO: warn about bad regex instead of failure */ - goto done; - } - - /* TODO: look up diff..algorithm to turn on minimal / patience - * diff in drv->other_flags - */ - - /* if no driver config found at all, fall back on AUTO driver */ - if (!found_driver) - goto done; - - /* store driver in registry */ - if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0) - goto done; - - *out = drv; - -done: - git_config_entry_free(ce); - git_str_dispose(&name); - git_config_free(cfg); - - if (!*out) { - int error2 = git_diff_driver_builtin(out, reg, driver_name); - if (!error) - error = error2; - } - - if (drv && drv != *out) - git_diff_driver_free(drv); - - return error; -} - -int git_diff_driver_lookup( - git_diff_driver **out, git_repository *repo, - git_attr_session *attrsession, const char *path) -{ - int error = 0; - const char *values[1], *attrs[] = { "diff" }; - - GIT_ASSERT_ARG(out); - *out = NULL; - - if (!repo || !path || !strlen(path)) - /* just use the auto value */; - else if ((error = git_attr_get_many_with_session(values, repo, - attrsession, 0, path, 1, attrs)) < 0) - /* return error below */; - - else if (GIT_ATTR_IS_UNSPECIFIED(values[0])) - /* just use the auto value */; - else if (GIT_ATTR_IS_FALSE(values[0])) - *out = &diff_driver_binary; - else if (GIT_ATTR_IS_TRUE(values[0])) - *out = &diff_driver_text; - - /* otherwise look for driver information in config and build driver */ - else if ((error = git_diff_driver_load(out, repo, values[0])) < 0) { - if (error == GIT_ENOTFOUND) { - error = 0; - git_error_clear(); - } - } - - if (!*out) - *out = &diff_driver_auto; - - return error; -} - -void git_diff_driver_free(git_diff_driver *driver) -{ - git_diff_driver_pattern *pat; - - if (!driver) - return; - - while ((pat = git_array_pop(driver->fn_patterns)) != NULL) - git_regexp_dispose(&pat->re); - git_array_clear(driver->fn_patterns); - - git_regexp_dispose(&driver->word_pattern); - - git__free(driver); -} - -void git_diff_driver_update_options( - uint32_t *option_flags, git_diff_driver *driver) -{ - if ((*option_flags & FORCE_DIFFABLE) == 0) - *option_flags |= driver->binary_flags; - - *option_flags |= driver->other_flags; -} - -int git_diff_driver_content_is_binary( - git_diff_driver *driver, const char *content, size_t content_len) -{ - git_str search = GIT_STR_INIT; - - GIT_UNUSED(driver); - - git_str_attach_notowned(&search, content, - min(content_len, GIT_FILTER_BYTES_TO_CHECK_NUL)); - - /* TODO: provide encoding / binary detection callbacks that can - * be UTF-8 aware, etc. For now, instead of trying to be smart, - * let's just use the simple NUL-byte detection that core git uses. - */ - - /* previously was: if (git_str_is_binary(&search)) */ - if (git_str_contains_nul(&search)) - return 1; - - return 0; -} - -static int diff_context_line__simple( - git_diff_driver *driver, git_str *line) -{ - char firstch = line->ptr[0]; - GIT_UNUSED(driver); - return (git__isalpha(firstch) || firstch == '_' || firstch == '$'); -} - -static int diff_context_line__pattern_match( - git_diff_driver *driver, git_str *line) -{ - size_t i, maxi = git_array_size(driver->fn_patterns); - git_regmatch pmatch[2]; - - for (i = 0; i < maxi; ++i) { - git_diff_driver_pattern *pat = git_array_get(driver->fn_patterns, i); - - if (!git_regexp_search(&pat->re, line->ptr, 2, pmatch)) { - if (pat->flags & REG_NEGATE) - return false; - - /* use pmatch data to trim line data */ - i = (pmatch[1].start >= 0) ? 1 : 0; - git_str_consume(line, git_str_cstr(line) + pmatch[i].start); - git_str_truncate(line, pmatch[i].end - pmatch[i].start); - git_str_rtrim(line); - - return true; - } - } - - return false; -} - -static long diff_context_find( - const char *line, - long line_len, - char *out, - long out_size, - void *payload) -{ - git_diff_find_context_payload *ctxt = payload; - - if (git_str_set(&ctxt->line, line, (size_t)line_len) < 0) - return -1; - git_str_rtrim(&ctxt->line); - - if (!ctxt->line.size) - return -1; - - if (!ctxt->match_line || !ctxt->match_line(ctxt->driver, &ctxt->line)) - return -1; - - if (out_size > (long)ctxt->line.size) - out_size = (long)ctxt->line.size; - memcpy(out, ctxt->line.ptr, (size_t)out_size); - - return out_size; -} - -void git_diff_find_context_init( - git_diff_find_context_fn *findfn_out, - git_diff_find_context_payload *payload_out, - git_diff_driver *driver) -{ - *findfn_out = driver ? diff_context_find : NULL; - - memset(payload_out, 0, sizeof(*payload_out)); - if (driver) { - payload_out->driver = driver; - payload_out->match_line = (driver->type == DIFF_DRIVER_PATTERNLIST) ? - diff_context_line__pattern_match : diff_context_line__simple; - git_str_init(&payload_out->line, 0); - } -} - -void git_diff_find_context_clear(git_diff_find_context_payload *payload) -{ - if (payload) { - git_str_dispose(&payload->line); - payload->driver = NULL; - } -} diff --git a/src/diff_driver.h b/src/diff_driver.h deleted file mode 100644 index 03711e89e..000000000 --- a/src/diff_driver.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_diff_driver_h__ -#define INCLUDE_diff_driver_h__ - -#include "common.h" - -#include "attr_file.h" -#include "str.h" - -typedef struct git_diff_driver_registry git_diff_driver_registry; - -git_diff_driver_registry *git_diff_driver_registry_new(void); -void git_diff_driver_registry_free(git_diff_driver_registry *); - -typedef struct git_diff_driver git_diff_driver; - -int git_diff_driver_lookup(git_diff_driver **, git_repository *, - git_attr_session *attrsession, const char *); -void git_diff_driver_free(git_diff_driver *); - -/* diff option flags to force off and on for this driver */ -void git_diff_driver_update_options(uint32_t *option_flags, git_diff_driver *); - -/* returns -1 meaning "unknown", 0 meaning not binary, 1 meaning binary */ -int git_diff_driver_content_is_binary( - git_diff_driver *, const char *content, size_t content_len); - -typedef long (*git_diff_find_context_fn)( - const char *, long, char *, long, void *); - -typedef int (*git_diff_find_context_line)( - git_diff_driver *, git_str *); - -typedef struct { - git_diff_driver *driver; - git_diff_find_context_line match_line; - git_str line; -} git_diff_find_context_payload; - -void git_diff_find_context_init( - git_diff_find_context_fn *findfn_out, - git_diff_find_context_payload *payload_out, - git_diff_driver *driver); - -void git_diff_find_context_clear(git_diff_find_context_payload *); - -#endif diff --git a/src/diff_file.c b/src/diff_file.c deleted file mode 100644 index c7e9fbeee..000000000 --- a/src/diff_file.c +++ /dev/null @@ -1,481 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "diff_file.h" - -#include "git2/blob.h" -#include "git2/submodule.h" -#include "diff.h" -#include "diff_generate.h" -#include "odb.h" -#include "futils.h" -#include "filter.h" - -#define DIFF_MAX_FILESIZE 0x20000000 - -static bool diff_file_content_binary_by_size(git_diff_file_content *fc) -{ - /* if we have diff opts, check max_size vs file size */ - if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) == 0 && - fc->opts_max_size > 0 && - fc->file->size > fc->opts_max_size) - fc->file->flags |= GIT_DIFF_FLAG_BINARY; - - return ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0); -} - -static void diff_file_content_binary_by_content(git_diff_file_content *fc) -{ - if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) - return; - - switch (git_diff_driver_content_is_binary( - fc->driver, fc->map.data, fc->map.len)) { - case 0: fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; break; - case 1: fc->file->flags |= GIT_DIFF_FLAG_BINARY; break; - default: break; - } -} - -static int diff_file_content_init_common( - git_diff_file_content *fc, const git_diff_options *opts) -{ - fc->opts_flags = opts ? opts->flags : GIT_DIFF_NORMAL; - - if (opts && opts->max_size >= 0) - fc->opts_max_size = opts->max_size ? - opts->max_size : DIFF_MAX_FILESIZE; - - if (fc->src == GIT_ITERATOR_EMPTY) - fc->src = GIT_ITERATOR_TREE; - - if (!fc->driver && - git_diff_driver_lookup(&fc->driver, fc->repo, - NULL, fc->file->path) < 0) - return -1; - - /* give driver a chance to modify options */ - git_diff_driver_update_options(&fc->opts_flags, fc->driver); - - /* make sure file is conceivable mmap-able */ - if ((size_t)fc->file->size != fc->file->size) - fc->file->flags |= GIT_DIFF_FLAG_BINARY; - /* check if user is forcing text diff the file */ - else if (fc->opts_flags & GIT_DIFF_FORCE_TEXT) { - fc->file->flags &= ~GIT_DIFF_FLAG_BINARY; - fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; - } - /* check if user is forcing binary diff the file */ - else if (fc->opts_flags & GIT_DIFF_FORCE_BINARY) { - fc->file->flags &= ~GIT_DIFF_FLAG_NOT_BINARY; - fc->file->flags |= GIT_DIFF_FLAG_BINARY; - } - - diff_file_content_binary_by_size(fc); - - if ((fc->flags & GIT_DIFF_FLAG__NO_DATA) != 0) { - fc->flags |= GIT_DIFF_FLAG__LOADED; - fc->map.len = 0; - fc->map.data = ""; - } - - if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0) - diff_file_content_binary_by_content(fc); - - return 0; -} - -int git_diff_file_content__init_from_diff( - git_diff_file_content *fc, - git_diff *diff, - git_diff_delta *delta, - bool use_old) -{ - bool has_data = true; - - memset(fc, 0, sizeof(*fc)); - fc->repo = diff->repo; - fc->file = use_old ? &delta->old_file : &delta->new_file; - fc->src = use_old ? diff->old_src : diff->new_src; - - if (git_diff_driver_lookup(&fc->driver, fc->repo, - &diff->attrsession, fc->file->path) < 0) - return -1; - - switch (delta->status) { - case GIT_DELTA_ADDED: - has_data = !use_old; break; - case GIT_DELTA_DELETED: - has_data = use_old; break; - case GIT_DELTA_UNTRACKED: - has_data = !use_old && - (diff->opts.flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) != 0; - break; - case GIT_DELTA_UNREADABLE: - case GIT_DELTA_MODIFIED: - case GIT_DELTA_COPIED: - case GIT_DELTA_RENAMED: - break; - default: - has_data = false; - break; - } - - if (!has_data) - fc->flags |= GIT_DIFF_FLAG__NO_DATA; - - return diff_file_content_init_common(fc, &diff->opts); -} - -int git_diff_file_content__init_from_src( - git_diff_file_content *fc, - git_repository *repo, - const git_diff_options *opts, - const git_diff_file_content_src *src, - git_diff_file *as_file) -{ - memset(fc, 0, sizeof(*fc)); - fc->repo = repo; - fc->file = as_file; - - if (!src->blob && !src->buf) { - fc->flags |= GIT_DIFF_FLAG__NO_DATA; - } else { - fc->flags |= GIT_DIFF_FLAG__LOADED; - fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; - fc->file->mode = GIT_FILEMODE_BLOB; - - if (src->blob) { - git_blob_dup((git_blob **)&fc->blob, (git_blob *) src->blob); - fc->file->size = git_blob_rawsize(src->blob); - git_oid_cpy(&fc->file->id, git_blob_id(src->blob)); - fc->file->id_abbrev = GIT_OID_HEXSZ; - - fc->map.len = (size_t)fc->file->size; - fc->map.data = (char *)git_blob_rawcontent(src->blob); - - fc->flags |= GIT_DIFF_FLAG__FREE_BLOB; - } else { - int error; - if ((error = git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJECT_BLOB)) < 0) - return error; - fc->file->size = src->buflen; - fc->file->id_abbrev = GIT_OID_HEXSZ; - - fc->map.len = src->buflen; - fc->map.data = (char *)src->buf; - } - } - - return diff_file_content_init_common(fc, opts); -} - -static int diff_file_content_commit_to_str( - git_diff_file_content *fc, bool check_status) -{ - char oid[GIT_OID_HEXSZ+1]; - git_str content = GIT_STR_INIT; - const char *status = ""; - - if (check_status) { - int error = 0; - git_submodule *sm = NULL; - unsigned int sm_status = 0; - const git_oid *sm_head; - - if ((error = git_submodule_lookup(&sm, fc->repo, fc->file->path)) < 0) { - /* GIT_EEXISTS means a "submodule" that has not been git added */ - if (error == GIT_EEXISTS) { - git_error_clear(); - error = 0; - } - return error; - } - - if ((error = git_submodule_status(&sm_status, fc->repo, fc->file->path, GIT_SUBMODULE_IGNORE_UNSPECIFIED)) < 0) { - git_submodule_free(sm); - return error; - } - - /* update OID if we didn't have it previously */ - if ((fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0 && - ((sm_head = git_submodule_wd_id(sm)) != NULL || - (sm_head = git_submodule_head_id(sm)) != NULL)) - { - git_oid_cpy(&fc->file->id, sm_head); - fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; - } - - if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) - status = "-dirty"; - - git_submodule_free(sm); - } - - git_oid_tostr(oid, sizeof(oid), &fc->file->id); - if (git_str_printf(&content, "Subproject commit %s%s\n", oid, status) < 0) - return -1; - - fc->map.len = git_str_len(&content); - fc->map.data = git_str_detach(&content); - fc->flags |= GIT_DIFF_FLAG__FREE_DATA; - - return 0; -} - -static int diff_file_content_load_blob( - git_diff_file_content *fc, - git_diff_options *opts) -{ - int error = 0; - git_odb_object *odb_obj = NULL; - - if (git_oid_is_zero(&fc->file->id)) - return 0; - - if (fc->file->mode == GIT_FILEMODE_COMMIT) - return diff_file_content_commit_to_str(fc, false); - - /* if we don't know size, try to peek at object header first */ - if (!fc->file->size) { - if ((error = git_diff_file__resolve_zero_size( - fc->file, &odb_obj, fc->repo)) < 0) - return error; - } - - if ((opts->flags & GIT_DIFF_SHOW_BINARY) == 0 && - diff_file_content_binary_by_size(fc)) - return 0; - - if (odb_obj != NULL) { - error = git_object__from_odb_object( - (git_object **)&fc->blob, fc->repo, odb_obj, GIT_OBJECT_BLOB); - git_odb_object_free(odb_obj); - } else { - error = git_blob_lookup( - (git_blob **)&fc->blob, fc->repo, &fc->file->id); - } - - if (!error) { - fc->flags |= GIT_DIFF_FLAG__FREE_BLOB; - fc->map.data = (void *)git_blob_rawcontent(fc->blob); - fc->map.len = (size_t)git_blob_rawsize(fc->blob); - } - - return error; -} - -static int diff_file_content_load_workdir_symlink_fake( - git_diff_file_content *fc, git_str *path) -{ - git_str target = GIT_STR_INIT; - int error; - - if ((error = git_futils_readbuffer(&target, path->ptr)) < 0) - return error; - - fc->map.len = git_str_len(&target); - fc->map.data = git_str_detach(&target); - fc->flags |= GIT_DIFF_FLAG__FREE_DATA; - - git_str_dispose(&target); - return error; -} - -static int diff_file_content_load_workdir_symlink( - git_diff_file_content *fc, git_str *path) -{ - ssize_t alloc_len, read_len; - int symlink_supported, error; - - if ((error = git_repository__configmap_lookup( - &symlink_supported, fc->repo, GIT_CONFIGMAP_SYMLINKS)) < 0) - return -1; - - if (!symlink_supported) - return diff_file_content_load_workdir_symlink_fake(fc, path); - - /* link path on disk could be UTF-16, so prepare a buffer that is - * big enough to handle some UTF-8 data expansion - */ - alloc_len = (ssize_t)(fc->file->size * 2) + 1; - - fc->map.data = git__calloc(alloc_len, sizeof(char)); - GIT_ERROR_CHECK_ALLOC(fc->map.data); - - fc->flags |= GIT_DIFF_FLAG__FREE_DATA; - - read_len = p_readlink(git_str_cstr(path), fc->map.data, alloc_len); - if (read_len < 0) { - git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", fc->file->path); - return -1; - } - - fc->map.len = read_len; - return 0; -} - -static int diff_file_content_load_workdir_file( - git_diff_file_content *fc, - git_str *path, - git_diff_options *diff_opts) -{ - int error = 0; - git_filter_list *fl = NULL; - git_file fd = git_futils_open_ro(git_str_cstr(path)); - git_str raw = GIT_STR_INIT; - git_object_size_t new_file_size = 0; - - if (fd < 0) - return fd; - - error = git_futils_filesize(&new_file_size, fd); - - if (error < 0) - goto cleanup; - - if (!(fc->file->flags & GIT_DIFF_FLAG_VALID_SIZE)) { - fc->file->size = new_file_size; - fc->file->flags |= GIT_DIFF_FLAG_VALID_SIZE; - } else if (fc->file->size != new_file_size) { - git_error_set(GIT_ERROR_FILESYSTEM, "file changed before we could read it"); - error = -1; - goto cleanup; - } - - if ((diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0 && - diff_file_content_binary_by_size(fc)) - goto cleanup; - - if ((error = git_filter_list_load( - &fl, fc->repo, NULL, fc->file->path, - GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)) < 0) - goto cleanup; - - /* if there are no filters, try to mmap the file */ - if (fl == NULL) { - if (!(error = git_futils_mmap_ro( - &fc->map, fd, 0, (size_t)fc->file->size))) { - fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA; - goto cleanup; - } - - /* if mmap failed, fall through to try readbuffer below */ - git_error_clear(); - } - - if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size))) { - git_str out = GIT_STR_INIT; - - error = git_filter_list__convert_buf(&out, fl, &raw); - - if (!error) { - fc->map.len = out.size; - fc->map.data = out.ptr; - fc->flags |= GIT_DIFF_FLAG__FREE_DATA; - } - } - -cleanup: - git_filter_list_free(fl); - p_close(fd); - - return error; -} - -static int diff_file_content_load_workdir( - git_diff_file_content *fc, - git_diff_options *diff_opts) -{ - int error = 0; - git_str path = GIT_STR_INIT; - - if (fc->file->mode == GIT_FILEMODE_COMMIT) - return diff_file_content_commit_to_str(fc, true); - - if (fc->file->mode == GIT_FILEMODE_TREE) - return 0; - - if (git_repository_workdir_path(&path, fc->repo, fc->file->path) < 0) - return -1; - - if (S_ISLNK(fc->file->mode)) - error = diff_file_content_load_workdir_symlink(fc, &path); - else - error = diff_file_content_load_workdir_file(fc, &path, diff_opts); - - /* once data is loaded, update OID if we didn't have it previously */ - if (!error && (fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0) { - error = git_odb_hash( - &fc->file->id, fc->map.data, fc->map.len, GIT_OBJECT_BLOB); - fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; - } - - git_str_dispose(&path); - return error; -} - -int git_diff_file_content__load( - git_diff_file_content *fc, - git_diff_options *diff_opts) -{ - int error = 0; - - if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0) - return 0; - - if ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0 && - (diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0) - return 0; - - if (fc->src == GIT_ITERATOR_WORKDIR) - error = diff_file_content_load_workdir(fc, diff_opts); - else - error = diff_file_content_load_blob(fc, diff_opts); - if (error) - return error; - - fc->flags |= GIT_DIFF_FLAG__LOADED; - - diff_file_content_binary_by_content(fc); - - return 0; -} - -void git_diff_file_content__unload(git_diff_file_content *fc) -{ - if ((fc->flags & GIT_DIFF_FLAG__LOADED) == 0) - return; - - if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) { - git__free(fc->map.data); - fc->map.data = ""; - fc->map.len = 0; - fc->flags &= ~GIT_DIFF_FLAG__FREE_DATA; - } - else if (fc->flags & GIT_DIFF_FLAG__UNMAP_DATA) { - git_futils_mmap_free(&fc->map); - fc->map.data = ""; - fc->map.len = 0; - fc->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA; - } - - if (fc->flags & GIT_DIFF_FLAG__FREE_BLOB) { - git_blob_free((git_blob *)fc->blob); - fc->blob = NULL; - fc->flags &= ~GIT_DIFF_FLAG__FREE_BLOB; - } - - fc->flags &= ~GIT_DIFF_FLAG__LOADED; -} - -void git_diff_file_content__clear(git_diff_file_content *fc) -{ - git_diff_file_content__unload(fc); - - /* for now, nothing else to do */ -} diff --git a/src/diff_file.h b/src/diff_file.h deleted file mode 100644 index 8d743e821..000000000 --- a/src/diff_file.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_diff_file_h__ -#define INCLUDE_diff_file_h__ - -#include "common.h" - -#include "diff.h" -#include "diff_driver.h" -#include "map.h" - -/* expanded information for one side of a delta */ -typedef struct { - git_repository *repo; - git_diff_file *file; - git_diff_driver *driver; - uint32_t flags; - uint32_t opts_flags; - git_object_size_t opts_max_size; - git_iterator_t src; - const git_blob *blob; - git_map map; -} git_diff_file_content; - -extern int git_diff_file_content__init_from_diff( - git_diff_file_content *fc, - git_diff *diff, - git_diff_delta *delta, - bool use_old); - -typedef struct { - const git_blob *blob; - const void *buf; - size_t buflen; - const char *as_path; -} git_diff_file_content_src; - -#define GIT_DIFF_FILE_CONTENT_SRC__BLOB(BLOB,PATH) { (BLOB),NULL,0,(PATH) } -#define GIT_DIFF_FILE_CONTENT_SRC__BUF(BUF,LEN,PATH) { NULL,(BUF),(LEN),(PATH) } - -extern int git_diff_file_content__init_from_src( - git_diff_file_content *fc, - git_repository *repo, - const git_diff_options *opts, - const git_diff_file_content_src *src, - git_diff_file *as_file); - -/* this loads the blob/file-on-disk as needed */ -extern int git_diff_file_content__load( - git_diff_file_content *fc, - git_diff_options *diff_opts); - -/* this releases the blob/file-in-memory */ -extern void git_diff_file_content__unload(git_diff_file_content *fc); - -/* this unloads and also releases any other resources */ -extern void git_diff_file_content__clear(git_diff_file_content *fc); - -#endif diff --git a/src/diff_generate.c b/src/diff_generate.c deleted file mode 100644 index cfaefba66..000000000 --- a/src/diff_generate.c +++ /dev/null @@ -1,1724 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "diff_generate.h" - -#include "diff.h" -#include "patch_generate.h" -#include "futils.h" -#include "config.h" -#include "attr_file.h" -#include "filter.h" -#include "pathspec.h" -#include "index.h" -#include "odb.h" -#include "submodule.h" - -#define DIFF_FLAG_IS_SET(DIFF,FLAG) \ - (((DIFF)->base.opts.flags & (FLAG)) != 0) -#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \ - (((DIFF)->base.opts.flags & (FLAG)) == 0) -#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->base.opts.flags = \ - (VAL) ? ((DIFF)->base.opts.flags | (FLAG)) : \ - ((DIFF)->base.opts.flags & ~(FLAG)) - -typedef struct { - struct git_diff base; - - git_vector pathspec; - - uint32_t diffcaps; - bool index_updated; -} git_diff_generated; - -static git_diff_delta *diff_delta__alloc( - git_diff_generated *diff, - git_delta_t status, - const char *path) -{ - git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); - if (!delta) - return NULL; - - delta->old_file.path = git_pool_strdup(&diff->base.pool, path); - if (delta->old_file.path == NULL) { - git__free(delta); - return NULL; - } - - delta->new_file.path = delta->old_file.path; - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - switch (status) { - case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; - case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; - default: break; /* leave other status values alone */ - } - } - delta->status = status; - - return delta; -} - -static int diff_insert_delta( - git_diff_generated *diff, - git_diff_delta *delta, - const char *matched_pathspec) -{ - int error = 0; - - if (diff->base.opts.notify_cb) { - error = diff->base.opts.notify_cb( - &diff->base, delta, matched_pathspec, diff->base.opts.payload); - - if (error) { - git__free(delta); - - if (error > 0) /* positive value means to skip this delta */ - return 0; - else /* negative value means to cancel diff */ - return git_error_set_after_callback_function(error, "git_diff"); - } - } - - if ((error = git_vector_insert(&diff->base.deltas, delta)) < 0) - git__free(delta); - - return error; -} - -static bool diff_pathspec_match( - const char **matched_pathspec, - git_diff_generated *diff, - const git_index_entry *entry) -{ - bool disable_pathspec_match = - DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH); - - /* If we're disabling fnmatch, then the iterator has already applied - * the filters to the files for us and we don't have to do anything. - * However, this only applies to *files* - the iterator will include - * directories that we need to recurse into when not autoexpanding, - * so we still need to apply the pathspec match to directories. - */ - if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) && - disable_pathspec_match) { - *matched_pathspec = entry->path; - return true; - } - - return git_pathspec__match( - &diff->pathspec, entry->path, disable_pathspec_match, - DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), - matched_pathspec, NULL); -} - -static void diff_delta__flag_known_size(git_diff_file *file) -{ - /* - * If we don't know the ID, that can only come from the workdir - * iterator, which means we *do* know the file size. This is a - * leaky abstraction, but alas. Otherwise, we test against the - * empty blob id. - */ - if (file->size || - !(file->flags & GIT_DIFF_FLAG_VALID_ID) || - git_oid_equal(&file->id, &git_oid__empty_blob_sha1)) - file->flags |= GIT_DIFF_FLAG_VALID_SIZE; -} - -static void diff_delta__flag_known_sizes(git_diff_delta *delta) -{ - diff_delta__flag_known_size(&delta->old_file); - diff_delta__flag_known_size(&delta->new_file); -} - -static int diff_delta__from_one( - git_diff_generated *diff, - git_delta_t status, - const git_index_entry *oitem, - const git_index_entry *nitem) -{ - const git_index_entry *entry = nitem; - bool has_old = false; - git_diff_delta *delta; - const char *matched_pathspec; - - GIT_ASSERT_ARG((oitem != NULL) ^ (nitem != NULL)); - - if (oitem) { - entry = oitem; - has_old = true; - } - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) - has_old = !has_old; - - if ((entry->flags & GIT_INDEX_ENTRY_VALID) != 0) - return 0; - - if (status == GIT_DELTA_IGNORED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) - return 0; - - if (status == GIT_DELTA_UNTRACKED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) - return 0; - - if (status == GIT_DELTA_UNREADABLE && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE)) - return 0; - - if (!diff_pathspec_match(&matched_pathspec, diff, entry)) - return 0; - - delta = diff_delta__alloc(diff, status, entry->path); - GIT_ERROR_CHECK_ALLOC(delta); - - /* This fn is just for single-sided diffs */ - GIT_ASSERT(status != GIT_DELTA_MODIFIED); - delta->nfiles = 1; - - if (has_old) { - delta->old_file.mode = entry->mode; - delta->old_file.size = entry->file_size; - delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; - git_oid_cpy(&delta->old_file.id, &entry->id); - delta->old_file.id_abbrev = GIT_OID_HEXSZ; - } else /* ADDED, IGNORED, UNTRACKED */ { - delta->new_file.mode = entry->mode; - delta->new_file.size = entry->file_size; - delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; - git_oid_cpy(&delta->new_file.id, &entry->id); - delta->new_file.id_abbrev = GIT_OID_HEXSZ; - } - - delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - if (has_old || !git_oid_is_zero(&delta->new_file.id)) - delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - diff_delta__flag_known_sizes(delta); - - return diff_insert_delta(diff, delta, matched_pathspec); -} - -static int diff_delta__from_two( - git_diff_generated *diff, - git_delta_t status, - const git_index_entry *old_entry, - uint32_t old_mode, - const git_index_entry *new_entry, - uint32_t new_mode, - const git_oid *new_id, - const char *matched_pathspec) -{ - const git_oid *old_id = &old_entry->id; - git_diff_delta *delta; - const char *canonical_path = old_entry->path; - - if (status == GIT_DELTA_UNMODIFIED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) - return 0; - - if (!new_id) - new_id = &new_entry->id; - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - uint32_t temp_mode = old_mode; - const git_index_entry *temp_entry = old_entry; - const git_oid *temp_id = old_id; - - old_entry = new_entry; - new_entry = temp_entry; - old_mode = new_mode; - new_mode = temp_mode; - old_id = new_id; - new_id = temp_id; - } - - delta = diff_delta__alloc(diff, status, canonical_path); - GIT_ERROR_CHECK_ALLOC(delta); - delta->nfiles = 2; - - if (!git_index_entry_is_conflict(old_entry)) { - delta->old_file.size = old_entry->file_size; - delta->old_file.mode = old_mode; - git_oid_cpy(&delta->old_file.id, old_id); - delta->old_file.id_abbrev = GIT_OID_HEXSZ; - delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID | - GIT_DIFF_FLAG_EXISTS; - } - - if (!git_index_entry_is_conflict(new_entry)) { - git_oid_cpy(&delta->new_file.id, new_id); - delta->new_file.id_abbrev = GIT_OID_HEXSZ; - delta->new_file.size = new_entry->file_size; - delta->new_file.mode = new_mode; - delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; - delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; - - if (!git_oid_is_zero(&new_entry->id)) - delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; - } - - diff_delta__flag_known_sizes(delta); - - return diff_insert_delta(diff, delta, matched_pathspec); -} - -static git_diff_delta *diff_delta__last_for_item( - git_diff_generated *diff, - const git_index_entry *item) -{ - git_diff_delta *delta = git_vector_last(&diff->base.deltas); - if (!delta) - return NULL; - - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: - case GIT_DELTA_DELETED: - if (git_oid__cmp(&delta->old_file.id, &item->id) == 0) - return delta; - break; - case GIT_DELTA_ADDED: - if (git_oid__cmp(&delta->new_file.id, &item->id) == 0) - return delta; - break; - case GIT_DELTA_UNREADABLE: - case GIT_DELTA_UNTRACKED: - if (diff->base.strcomp(delta->new_file.path, item->path) == 0 && - git_oid__cmp(&delta->new_file.id, &item->id) == 0) - return delta; - break; - case GIT_DELTA_MODIFIED: - if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 || - (delta->new_file.mode == item->mode && - git_oid__cmp(&delta->new_file.id, &item->id) == 0)) - return delta; - break; - default: - break; - } - - return NULL; -} - -static char *diff_strdup_prefix(git_pool *pool, const char *prefix) -{ - size_t len = strlen(prefix); - - /* append '/' at end if needed */ - if (len > 0 && prefix[len - 1] != '/') - return git_pool_strcat(pool, prefix, "/"); - else - return git_pool_strndup(pool, prefix, len + 1); -} - -GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta) -{ - return delta->old_file.path ? - delta->old_file.path : delta->new_file.path; -} - -static int diff_delta_i2w_cmp(const void *a, const void *b) -{ - const git_diff_delta *da = a, *db = b; - int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); - return val ? val : ((int)da->status - (int)db->status); -} - -static int diff_delta_i2w_casecmp(const void *a, const void *b) -{ - const git_diff_delta *da = a, *db = b; - int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); - return val ? val : ((int)da->status - (int)db->status); -} - -bool git_diff_delta__should_skip( - const git_diff_options *opts, const git_diff_delta *delta) -{ - uint32_t flags = opts ? opts->flags : 0; - - if (delta->status == GIT_DELTA_UNMODIFIED && - (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) - return true; - - if (delta->status == GIT_DELTA_IGNORED && - (flags & GIT_DIFF_INCLUDE_IGNORED) == 0) - return true; - - if (delta->status == GIT_DELTA_UNTRACKED && - (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) - return true; - - if (delta->status == GIT_DELTA_UNREADABLE && - (flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0) - return true; - - return false; -} - - -static const char *diff_mnemonic_prefix( - git_iterator_t type, bool left_side) -{ - const char *pfx = ""; - - switch (type) { - case GIT_ITERATOR_EMPTY: pfx = "c"; break; - case GIT_ITERATOR_TREE: pfx = "c"; break; - case GIT_ITERATOR_INDEX: pfx = "i"; break; - case GIT_ITERATOR_WORKDIR: pfx = "w"; break; - case GIT_ITERATOR_FS: pfx = left_side ? "1" : "2"; break; - default: break; - } - - /* note: without a deeper look at pathspecs, there is no easy way - * to get the (o)bject / (w)ork tree mnemonics working... - */ - - return pfx; -} - -static void diff_set_ignore_case(git_diff *diff, bool ignore_case) -{ - if (!ignore_case) { - diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE; - - diff->strcomp = git__strcmp; - diff->strncomp = git__strncmp; - diff->pfxcomp = git__prefixcmp; - diff->entrycomp = git_diff__entry_cmp; - - git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp); - } else { - diff->opts.flags |= GIT_DIFF_IGNORE_CASE; - - diff->strcomp = git__strcasecmp; - diff->strncomp = git__strncasecmp; - diff->pfxcomp = git__prefixcmp_icase; - diff->entrycomp = git_diff__entry_icmp; - - git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp); - } - - git_vector_sort(&diff->deltas); -} - -static void diff_generated_free(git_diff *d) -{ - git_diff_generated *diff = (git_diff_generated *)d; - - git_attr_session__free(&diff->base.attrsession); - git_vector_free_deep(&diff->base.deltas); - - git_pathspec__vfree(&diff->pathspec); - git_pool_clear(&diff->base.pool); - - git__memzero(diff, sizeof(*diff)); - git__free(diff); -} - -static git_diff_generated *diff_generated_alloc( - git_repository *repo, - git_iterator *old_iter, - git_iterator *new_iter) -{ - git_diff_generated *diff; - git_diff_options dflt = GIT_DIFF_OPTIONS_INIT; - - GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(old_iter, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(new_iter, NULL); - - if ((diff = git__calloc(1, sizeof(git_diff_generated))) == NULL) - return NULL; - - GIT_REFCOUNT_INC(&diff->base); - diff->base.type = GIT_DIFF_TYPE_GENERATED; - diff->base.repo = repo; - diff->base.old_src = old_iter->type; - diff->base.new_src = new_iter->type; - diff->base.patch_fn = git_patch_generated_from_diff; - diff->base.free_fn = diff_generated_free; - git_attr_session__init(&diff->base.attrsession, repo); - memcpy(&diff->base.opts, &dflt, sizeof(git_diff_options)); - - if (git_pool_init(&diff->base.pool, 1) < 0 || - git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) { - git_diff_free(&diff->base); - return NULL; - } - - /* Use case-insensitive compare if either iterator has - * the ignore_case bit set */ - diff_set_ignore_case( - &diff->base, - git_iterator_ignore_case(old_iter) || - git_iterator_ignore_case(new_iter)); - - return diff; -} - -static int diff_generated_apply_options( - git_diff_generated *diff, - const git_diff_options *opts) -{ - git_config *cfg = NULL; - git_repository *repo = diff->base.repo; - git_pool *pool = &diff->base.pool; - int val; - - if (opts) { - /* copy user options (except case sensitivity info from iterators) */ - bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE); - memcpy(&diff->base.opts, opts, sizeof(diff->base.opts)); - DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase); - - /* initialize pathspec from options */ - if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0) - return -1; - } - - /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES)) - diff->base.opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; - - /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT)) - diff->base.opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - - /* load config values that affect diff behavior */ - if ((val = git_repository_config_snapshot(&cfg, repo)) < 0) - return val; - - if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_SYMLINKS) && val) - diff->diffcaps |= GIT_DIFFCAPS_HAS_SYMLINKS; - - if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_IGNORESTAT) && val) - diff->diffcaps |= GIT_DIFFCAPS_IGNORE_STAT; - - if ((diff->base.opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 && - !git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_FILEMODE) && val) - diff->diffcaps |= GIT_DIFFCAPS_TRUST_MODE_BITS; - - if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_TRUSTCTIME) && val) - diff->diffcaps |= GIT_DIFFCAPS_TRUST_CTIME; - - /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ - - /* If not given explicit `opts`, check `diff.xyz` configs */ - if (!opts) { - int context = git_config__get_int_force(cfg, "diff.context", 3); - diff->base.opts.context_lines = context >= 0 ? (uint32_t)context : 3; - - /* add other defaults here */ - } - - /* Reverse src info if diff is reversed */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - git_iterator_t tmp_src = diff->base.old_src; - diff->base.old_src = diff->base.new_src; - diff->base.new_src = tmp_src; - } - - /* Unset UPDATE_INDEX unless diffing workdir and index */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && - (!(diff->base.old_src == GIT_ITERATOR_WORKDIR || - diff->base.new_src == GIT_ITERATOR_WORKDIR) || - !(diff->base.old_src == GIT_ITERATOR_INDEX || - diff->base.new_src == GIT_ITERATOR_INDEX))) - diff->base.opts.flags &= ~GIT_DIFF_UPDATE_INDEX; - - /* if ignore_submodules not explicitly set, check diff config */ - if (diff->base.opts.ignore_submodules <= 0) { - git_config_entry *entry; - git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true); - - if (entry && git_submodule_parse_ignore( - &diff->base.opts.ignore_submodules, entry->value) < 0) - git_error_clear(); - git_config_entry_free(entry); - } - - /* if either prefix is not set, figure out appropriate value */ - if (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) { - const char *use_old = DIFF_OLD_PREFIX_DEFAULT; - const char *use_new = DIFF_NEW_PREFIX_DEFAULT; - - if (git_config__get_bool_force(cfg, "diff.noprefix", 0)) - use_old = use_new = ""; - else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) { - use_old = diff_mnemonic_prefix(diff->base.old_src, true); - use_new = diff_mnemonic_prefix(diff->base.new_src, false); - } - - if (!diff->base.opts.old_prefix) - diff->base.opts.old_prefix = use_old; - if (!diff->base.opts.new_prefix) - diff->base.opts.new_prefix = use_new; - } - - /* strdup prefix from pool so we're not dependent on external data */ - diff->base.opts.old_prefix = diff_strdup_prefix(pool, diff->base.opts.old_prefix); - diff->base.opts.new_prefix = diff_strdup_prefix(pool, diff->base.opts.new_prefix); - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - const char *tmp_prefix = diff->base.opts.old_prefix; - diff->base.opts.old_prefix = diff->base.opts.new_prefix; - diff->base.opts.new_prefix = tmp_prefix; - } - - git_config_free(cfg); - - /* check strdup results for error */ - return (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) ? -1 : 0; -} - -int git_diff__oid_for_file( - git_oid *out, - git_diff *diff, - const char *path, - uint16_t mode, - git_object_size_t size) -{ - git_index_entry entry; - - if (size > UINT32_MAX) { - git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", path); - return -1; - } - - memset(&entry, 0, sizeof(entry)); - entry.mode = mode; - entry.file_size = (uint32_t)size; - entry.path = (char *)path; - - return git_diff__oid_for_entry(out, diff, &entry, mode, NULL); -} - -int git_diff__oid_for_entry( - git_oid *out, - git_diff *d, - const git_index_entry *src, - uint16_t mode, - const git_oid *update_match) -{ - git_diff_generated *diff; - git_str full_path = GIT_STR_INIT; - git_index_entry entry = *src; - git_filter_list *fl = NULL; - int error = 0; - - GIT_ASSERT(d->type == GIT_DIFF_TYPE_GENERATED); - diff = (git_diff_generated *)d; - - memset(out, 0, sizeof(*out)); - - if (git_repository_workdir_path(&full_path, diff->base.repo, entry.path) < 0) - return -1; - - if (!mode) { - struct stat st; - - diff->base.perf.stat_calls++; - - if (p_stat(full_path.ptr, &st) < 0) { - error = git_fs_path_set_error(errno, entry.path, "stat"); - git_str_dispose(&full_path); - return error; - } - - git_index_entry__init_from_stat(&entry, - &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0); - } - - /* calculate OID for file if possible */ - if (S_ISGITLINK(mode)) { - git_submodule *sm; - - if (!git_submodule_lookup(&sm, diff->base.repo, entry.path)) { - const git_oid *sm_oid = git_submodule_wd_id(sm); - if (sm_oid) - git_oid_cpy(out, sm_oid); - git_submodule_free(sm); - } else { - /* if submodule lookup failed probably just in an intermediate - * state where some init hasn't happened, so ignore the error - */ - git_error_clear(); - } - } else if (S_ISLNK(mode)) { - error = git_odb__hashlink(out, full_path.ptr); - diff->base.perf.oid_calculations++; - } else if (!git__is_sizet(entry.file_size)) { - git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", - entry.path); - error = -1; - } else if (!(error = git_filter_list_load(&fl, - diff->base.repo, NULL, entry.path, - GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE))) - { - int fd = git_futils_open_ro(full_path.ptr); - if (fd < 0) - error = fd; - else { - error = git_odb__hashfd_filtered( - out, fd, (size_t)entry.file_size, GIT_OBJECT_BLOB, fl); - p_close(fd); - diff->base.perf.oid_calculations++; - } - - git_filter_list_free(fl); - } - - /* update index for entry if requested */ - if (!error && update_match && git_oid_equal(out, update_match)) { - git_index *idx; - git_index_entry updated_entry; - - memcpy(&updated_entry, &entry, sizeof(git_index_entry)); - updated_entry.mode = mode; - git_oid_cpy(&updated_entry.id, out); - - if (!(error = git_repository_index__weakptr(&idx, - diff->base.repo))) { - error = git_index_add(idx, &updated_entry); - diff->index_updated = true; - } - } - - git_str_dispose(&full_path); - return error; -} - -typedef struct { - git_repository *repo; - git_iterator *old_iter; - git_iterator *new_iter; - const git_index_entry *oitem; - const git_index_entry *nitem; - git_strmap *submodule_cache; - bool submodule_cache_initialized; -} diff_in_progress; - -#define MODE_BITS_MASK 0000777 - -static int maybe_modified_submodule( - git_delta_t *status, - git_oid *found_oid, - git_diff_generated *diff, - diff_in_progress *info) -{ - int error = 0; - git_submodule *sub; - unsigned int sm_status = 0; - git_submodule_ignore_t ign = diff->base.opts.ignore_submodules; - git_strmap *submodule_cache = NULL; - - *status = GIT_DELTA_UNMODIFIED; - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) || - ign == GIT_SUBMODULE_IGNORE_ALL) - return 0; - - if (diff->base.repo->submodule_cache != NULL) { - submodule_cache = diff->base.repo->submodule_cache; - } else { - if (!info->submodule_cache_initialized) { - info->submodule_cache_initialized = true; - /* - * Try to cache the submodule information to avoid having to parse it for - * every submodule. It is okay if it fails, the cache will still be NULL - * and the submodules will be attempted to be looked up individually. - */ - git_submodule_cache_init(&info->submodule_cache, diff->base.repo); - } - submodule_cache = info->submodule_cache; - } - - if ((error = git_submodule__lookup_with_cache( - &sub, diff->base.repo, info->nitem->path, submodule_cache)) < 0) { - - /* GIT_EEXISTS means dir with .git in it was found - ignore it */ - if (error == GIT_EEXISTS) { - git_error_clear(); - error = 0; - } - return error; - } - - if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) - /* ignore it */; - else if ((error = git_submodule__status( - &sm_status, NULL, NULL, found_oid, sub, ign)) < 0) - /* return error below */; - - /* check IS_WD_UNMODIFIED because this case is only used - * when the new side of the diff is the working directory - */ - else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) - *status = GIT_DELTA_MODIFIED; - - /* now that we have a HEAD OID, check if HEAD moved */ - else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 && - !git_oid_equal(&info->oitem->id, found_oid)) - *status = GIT_DELTA_MODIFIED; - - git_submodule_free(sub); - return error; -} - -static int maybe_modified( - git_diff_generated *diff, - diff_in_progress *info) -{ - git_oid noid; - git_delta_t status = GIT_DELTA_MODIFIED; - const git_index_entry *oitem = info->oitem; - const git_index_entry *nitem = info->nitem; - unsigned int omode = oitem->mode; - unsigned int nmode = nitem->mode; - bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_WORKDIR); - bool modified_uncertain = false; - const char *matched_pathspec; - int error = 0; - - if (!diff_pathspec_match(&matched_pathspec, diff, oitem)) - return 0; - - memset(&noid, 0, sizeof(noid)); - - /* on platforms with no symlinks, preserve mode of existing symlinks */ - if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir && - !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) - nmode = omode; - - /* on platforms with no execmode, just preserve old mode */ - if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) && - (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) && - new_is_workdir) - nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); - - /* if one side is a conflict, mark the whole delta as conflicted */ - if (git_index_entry_is_conflict(oitem) || - git_index_entry_is_conflict(nitem)) { - status = GIT_DELTA_CONFLICTED; - - /* support "assume unchanged" (poorly, b/c we still stat everything) */ - } else if ((oitem->flags & GIT_INDEX_ENTRY_VALID) != 0) { - status = GIT_DELTA_UNMODIFIED; - - /* support "skip worktree" index bit */ - } else if ((oitem->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE) != 0) { - status = GIT_DELTA_UNMODIFIED; - - /* if basic type of file changed, then split into delete and add */ - } else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) { - status = GIT_DELTA_TYPECHANGE; - } - - else if (nmode == GIT_FILEMODE_UNREADABLE) { - if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) - error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem); - return error; - } - - else { - if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) - error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); - return error; - } - - /* if oids and modes match (and are valid), then file is unmodified */ - } else if (git_oid_equal(&oitem->id, &nitem->id) && - omode == nmode && - !git_oid_is_zero(&oitem->id)) { - status = GIT_DELTA_UNMODIFIED; - - /* if we have an unknown OID and a workdir iterator, then check some - * circumstances that can accelerate things or need special handling - */ - } else if (git_oid_is_zero(&nitem->id) && new_is_workdir) { - bool use_ctime = - ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0); - git_index *index = git_iterator_index(info->new_iter); - - status = GIT_DELTA_UNMODIFIED; - - if (S_ISGITLINK(nmode)) { - if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0) - return error; - } - - /* if the stat data looks different, then mark modified - this just - * means that the OID will be recalculated below to confirm change - */ - else if (omode != nmode || oitem->file_size != nitem->file_size) { - status = GIT_DELTA_MODIFIED; - modified_uncertain = - (oitem->file_size <= 0 && nitem->file_size > 0); - } - else if (!git_index_time_eq(&oitem->mtime, &nitem->mtime) || - (use_ctime && !git_index_time_eq(&oitem->ctime, &nitem->ctime)) || - oitem->ino != nitem->ino || - oitem->uid != nitem->uid || - oitem->gid != nitem->gid || - git_index_entry_newer_than_index(nitem, index)) - { - status = GIT_DELTA_MODIFIED; - modified_uncertain = true; - } - - /* if mode is GITLINK and submodules are ignored, then skip */ - } else if (S_ISGITLINK(nmode) && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) { - status = GIT_DELTA_UNMODIFIED; - } - - /* if we got here and decided that the files are modified, but we - * haven't calculated the OID of the new item, then calculate it now - */ - if (modified_uncertain && git_oid_is_zero(&nitem->id)) { - const git_oid *update_check = - DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ? - &oitem->id : NULL; - - if ((error = git_diff__oid_for_entry( - &noid, &diff->base, nitem, nmode, update_check)) < 0) - return error; - - /* if oid matches, then mark unmodified (except submodules, where - * the filesystem content may be modified even if the oid still - * matches between the index and the workdir HEAD) - */ - if (omode == nmode && !S_ISGITLINK(omode) && - git_oid_equal(&oitem->id, &noid)) - status = GIT_DELTA_UNMODIFIED; - } - - /* If we want case changes, then break this into a delete of the old - * and an add of the new so that consumers can act accordingly (eg, - * checkout will update the case on disk.) - */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) && - strcmp(oitem->path, nitem->path) != 0) { - - if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) - error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); - - return error; - } - - return diff_delta__from_two( - diff, status, oitem, omode, nitem, nmode, - git_oid_is_zero(&noid) ? NULL : &noid, matched_pathspec); -} - -static bool entry_is_prefixed( - git_diff_generated *diff, - const git_index_entry *item, - const git_index_entry *prefix_item) -{ - size_t pathlen; - - if (!item || diff->base.pfxcomp(item->path, prefix_item->path) != 0) - return false; - - pathlen = strlen(prefix_item->path); - - return (prefix_item->path[pathlen - 1] == '/' || - item->path[pathlen] == '\0' || - item->path[pathlen] == '/'); -} - -static int iterator_current( - const git_index_entry **entry, - git_iterator *iterator) -{ - int error; - - if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) { - *entry = NULL; - error = 0; - } - - return error; -} - -static int iterator_advance( - const git_index_entry **entry, - git_iterator *iterator) -{ - const git_index_entry *prev_entry = *entry; - int cmp, error; - - /* if we're looking for conflicts, we only want to report - * one conflict for each file, instead of all three sides. - * so if this entry is a conflict for this file, and the - * previous one was a conflict for the same file, skip it. - */ - while ((error = git_iterator_advance(entry, iterator)) == 0) { - if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) || - !git_index_entry_is_conflict(prev_entry) || - !git_index_entry_is_conflict(*entry)) - break; - - cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ? - strcasecmp(prev_entry->path, (*entry)->path) : - strcmp(prev_entry->path, (*entry)->path); - - if (cmp) - break; - } - - if (error == GIT_ITEROVER) { - *entry = NULL; - error = 0; - } - - return error; -} - -static int iterator_advance_into( - const git_index_entry **entry, - git_iterator *iterator) -{ - int error; - - if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) { - *entry = NULL; - error = 0; - } - - return error; -} - -static int iterator_advance_over( - const git_index_entry **entry, - git_iterator_status_t *status, - git_iterator *iterator) -{ - int error = git_iterator_advance_over(entry, status, iterator); - - if (error == GIT_ITEROVER) { - *entry = NULL; - error = 0; - } - - return error; -} - -static int handle_unmatched_new_item( - git_diff_generated *diff, diff_in_progress *info) -{ - int error = 0; - const git_index_entry *nitem = info->nitem; - git_delta_t delta_type = GIT_DELTA_UNTRACKED; - bool contains_oitem; - - /* check if this is a prefix of the other side */ - contains_oitem = entry_is_prefixed(diff, info->oitem, nitem); - - /* update delta_type if this item is conflicted */ - if (git_index_entry_is_conflict(nitem)) - delta_type = GIT_DELTA_CONFLICTED; - - /* update delta_type if this item is ignored */ - else if (git_iterator_current_is_ignored(info->new_iter)) - delta_type = GIT_DELTA_IGNORED; - - if (nitem->mode == GIT_FILEMODE_TREE) { - bool recurse_into_dir = contains_oitem; - - /* check if user requests recursion into this type of dir */ - recurse_into_dir = contains_oitem || - (delta_type == GIT_DELTA_UNTRACKED && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) || - (delta_type == GIT_DELTA_IGNORED && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)); - - /* do not advance into directories that contain a .git file */ - if (recurse_into_dir && !contains_oitem) { - git_str *full = NULL; - if (git_iterator_current_workdir_path(&full, info->new_iter) < 0) - return -1; - if (full && git_fs_path_contains(full, DOT_GIT)) { - /* TODO: warning if not a valid git repository */ - recurse_into_dir = false; - } - } - - /* still have to look into untracked directories to match core git - - * with no untracked files, directory is treated as ignored - */ - if (!recurse_into_dir && - delta_type == GIT_DELTA_UNTRACKED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS)) - { - git_diff_delta *last; - git_iterator_status_t untracked_state; - - /* attempt to insert record for this directory */ - if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) - return error; - - /* if delta wasn't created (because of rules), just skip ahead */ - last = diff_delta__last_for_item(diff, nitem); - if (!last) - return iterator_advance(&info->nitem, info->new_iter); - - /* iterate into dir looking for an actual untracked file */ - if ((error = iterator_advance_over( - &info->nitem, &untracked_state, info->new_iter)) < 0) - return error; - - /* if we found nothing that matched our pathlist filter, exclude */ - if (untracked_state == GIT_ITERATOR_STATUS_FILTERED) { - git_vector_pop(&diff->base.deltas); - git__free(last); - } - - /* if we found nothing or just ignored items, update the record */ - if (untracked_state == GIT_ITERATOR_STATUS_IGNORED || - untracked_state == GIT_ITERATOR_STATUS_EMPTY) { - last->status = GIT_DELTA_IGNORED; - - /* remove the record if we don't want ignored records */ - if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) { - git_vector_pop(&diff->base.deltas); - git__free(last); - } - } - - return 0; - } - - /* try to advance into directory if necessary */ - if (recurse_into_dir) { - error = iterator_advance_into(&info->nitem, info->new_iter); - - /* if directory is empty, can't advance into it, so skip it */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = iterator_advance(&info->nitem, info->new_iter); - } - - return error; - } - } - - else if (delta_type == GIT_DELTA_IGNORED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) && - git_iterator_current_tree_is_ignored(info->new_iter)) - /* item contained in ignored directory, so skip over it */ - return iterator_advance(&info->nitem, info->new_iter); - - else if (info->new_iter->type != GIT_ITERATOR_WORKDIR) { - if (delta_type != GIT_DELTA_CONFLICTED) - delta_type = GIT_DELTA_ADDED; - } - - else if (nitem->mode == GIT_FILEMODE_COMMIT) { - /* ignore things that are not actual submodules */ - if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) { - git_error_clear(); - delta_type = GIT_DELTA_IGNORED; - - /* if this contains a tracked item, treat as normal TREE */ - if (contains_oitem) { - error = iterator_advance_into(&info->nitem, info->new_iter); - if (error != GIT_ENOTFOUND) - return error; - - git_error_clear(); - return iterator_advance(&info->nitem, info->new_iter); - } - } - } - - else if (nitem->mode == GIT_FILEMODE_UNREADABLE) { - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED)) - delta_type = GIT_DELTA_UNTRACKED; - else - delta_type = GIT_DELTA_UNREADABLE; - } - - /* Actually create the record for this item if necessary */ - if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) - return error; - - /* If user requested TYPECHANGE records, then check for that instead of - * just generating an ADDED/UNTRACKED record - */ - if (delta_type != GIT_DELTA_IGNORED && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && - contains_oitem) - { - /* this entry was prefixed with a tree - make TYPECHANGE */ - git_diff_delta *last = diff_delta__last_for_item(diff, nitem); - if (last) { - last->status = GIT_DELTA_TYPECHANGE; - last->old_file.mode = GIT_FILEMODE_TREE; - } - } - - return iterator_advance(&info->nitem, info->new_iter); -} - -static int handle_unmatched_old_item( - git_diff_generated *diff, diff_in_progress *info) -{ - git_delta_t delta_type = GIT_DELTA_DELETED; - int error; - - /* update delta_type if this item is conflicted */ - if (git_index_entry_is_conflict(info->oitem)) - delta_type = GIT_DELTA_CONFLICTED; - - if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0) - return error; - - /* if we are generating TYPECHANGE records then check for that - * instead of just generating a DELETE record - */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && - entry_is_prefixed(diff, info->nitem, info->oitem)) - { - /* this entry has become a tree! convert to TYPECHANGE */ - git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem); - if (last) { - last->status = GIT_DELTA_TYPECHANGE; - last->new_file.mode = GIT_FILEMODE_TREE; - } - - /* If new_iter is a workdir iterator, then this situation - * will certainly be followed by a series of untracked items. - * Unless RECURSE_UNTRACKED_DIRS is set, skip over them... - */ - if (S_ISDIR(info->nitem->mode) && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) - return iterator_advance(&info->nitem, info->new_iter); - } - - return iterator_advance(&info->oitem, info->old_iter); -} - -static int handle_matched_item( - git_diff_generated *diff, diff_in_progress *info) -{ - int error = 0; - - if ((error = maybe_modified(diff, info)) < 0) - return error; - - if (!(error = iterator_advance(&info->oitem, info->old_iter))) - error = iterator_advance(&info->nitem, info->new_iter); - - return error; -} - -int git_diff__from_iterators( - git_diff **out, - git_repository *repo, - git_iterator *old_iter, - git_iterator *new_iter, - const git_diff_options *opts) -{ - git_diff_generated *diff; - diff_in_progress info = {0}; - int error = 0; - - *out = NULL; - - diff = diff_generated_alloc(repo, old_iter, new_iter); - GIT_ERROR_CHECK_ALLOC(diff); - - info.repo = repo; - info.old_iter = old_iter; - info.new_iter = new_iter; - - /* make iterators have matching icase behavior */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) { - if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 || - (error = git_iterator_set_ignore_case(new_iter, true)) < 0) - goto cleanup; - } - - /* finish initialization */ - if ((error = diff_generated_apply_options(diff, opts)) < 0) - goto cleanup; - - if ((error = iterator_current(&info.oitem, old_iter)) < 0 || - (error = iterator_current(&info.nitem, new_iter)) < 0) - goto cleanup; - - /* run iterators building diffs */ - while (!error && (info.oitem || info.nitem)) { - int cmp; - - /* report progress */ - if (opts && opts->progress_cb) { - if ((error = opts->progress_cb(&diff->base, - info.oitem ? info.oitem->path : NULL, - info.nitem ? info.nitem->path : NULL, - opts->payload))) - break; - } - - cmp = info.oitem ? - (info.nitem ? diff->base.entrycomp(info.oitem, info.nitem) : -1) : 1; - - /* create DELETED records for old items not matched in new */ - if (cmp < 0) - error = handle_unmatched_old_item(diff, &info); - - /* create ADDED, TRACKED, or IGNORED records for new items not - * matched in old (and/or descend into directories as needed) - */ - else if (cmp > 0) - error = handle_unmatched_new_item(diff, &info); - - /* otherwise item paths match, so create MODIFIED record - * (or ADDED and DELETED pair if type changed) - */ - else - error = handle_matched_item(diff, &info); - } - - diff->base.perf.stat_calls += - old_iter->stat_calls + new_iter->stat_calls; - -cleanup: - if (!error) - *out = &diff->base; - else - git_diff_free(&diff->base); - if (info.submodule_cache) - git_submodule_cache_free(info.submodule_cache); - - return error; -} - -static int diff_prepare_iterator_opts(char **prefix, git_iterator_options *a, int aflags, - git_iterator_options *b, int bflags, - const git_diff_options *opts) -{ - GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); - - *prefix = NULL; - - if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { - a->pathlist.strings = opts->pathspec.strings; - a->pathlist.count = opts->pathspec.count; - b->pathlist.strings = opts->pathspec.strings; - b->pathlist.count = opts->pathspec.count; - } else if (opts) { - *prefix = git_pathspec_prefix(&opts->pathspec); - GIT_ERROR_CHECK_ALLOC(prefix); - } - - a->flags = aflags; - b->flags = bflags; - a->start = b->start = *prefix; - a->end = b->end = *prefix; - - return 0; -} - -int git_diff_tree_to_tree( - git_diff **out, - git_repository *repo, - git_tree *old_tree, - git_tree *new_tree, - const git_diff_options *opts) -{ - git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE; - git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, - b_opts = GIT_ITERATOR_OPTIONS_INIT; - git_iterator *a = NULL, *b = NULL; - git_diff *diff = NULL; - char *prefix = NULL; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - /* for tree to tree diff, be case sensitive even if the index is - * currently case insensitive, unless the user explicitly asked - * for case insensitivity - */ - if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0) - iflag = GIT_ITERATOR_IGNORE_CASE; - - if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 || - (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || - (error = git_iterator_for_tree(&b, new_tree, &b_opts)) < 0 || - (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) - goto out; - - *out = diff; - diff = NULL; -out: - git_iterator_free(a); - git_iterator_free(b); - git_diff_free(diff); - git__free(prefix); - - return error; -} - -static int diff_load_index(git_index **index, git_repository *repo) -{ - int error = git_repository_index__weakptr(index, repo); - - /* reload the repository index when user did not pass one in */ - if (!error && git_index_read(*index, false) < 0) - git_error_clear(); - - return error; -} - -int git_diff_tree_to_index( - git_diff **out, - git_repository *repo, - git_tree *old_tree, - git_index *index, - const git_diff_options *opts) -{ - git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_CONFLICTS; - git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, - b_opts = GIT_ITERATOR_OPTIONS_INIT; - git_iterator *a = NULL, *b = NULL; - git_diff *diff = NULL; - char *prefix = NULL; - bool index_ignore_case = false; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - if (!index && (error = diff_load_index(&index, repo)) < 0) - return error; - - index_ignore_case = index->ignore_case; - - if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 || - (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || - (error = git_iterator_for_index(&b, repo, index, &b_opts)) < 0 || - (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) - goto out; - - /* if index is in case-insensitive order, re-sort deltas to match */ - if (index_ignore_case) - diff_set_ignore_case(diff, true); - - *out = diff; - diff = NULL; -out: - git_iterator_free(a); - git_iterator_free(b); - git_diff_free(diff); - git__free(prefix); - - return error; -} - -int git_diff_index_to_workdir( - git_diff **out, - git_repository *repo, - git_index *index, - const git_diff_options *opts) -{ - git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, - b_opts = GIT_ITERATOR_OPTIONS_INIT; - git_iterator *a = NULL, *b = NULL; - git_diff *diff = NULL; - char *prefix = NULL; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - if (!index && (error = diff_load_index(&index, repo)) < 0) - return error; - - if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_INCLUDE_CONFLICTS, - &b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts)) < 0 || - (error = git_iterator_for_index(&a, repo, index, &a_opts)) < 0 || - (error = git_iterator_for_workdir(&b, repo, index, NULL, &b_opts)) < 0 || - (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) - goto out; - - if ((diff->opts.flags & GIT_DIFF_UPDATE_INDEX) && ((git_diff_generated *)diff)->index_updated) - if ((error = git_index_write(index)) < 0) - goto out; - - *out = diff; - diff = NULL; -out: - git_iterator_free(a); - git_iterator_free(b); - git_diff_free(diff); - git__free(prefix); - - return error; -} - -int git_diff_tree_to_workdir( - git_diff **out, - git_repository *repo, - git_tree *old_tree, - const git_diff_options *opts) -{ - git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, - b_opts = GIT_ITERATOR_OPTIONS_INIT; - git_iterator *a = NULL, *b = NULL; - git_diff *diff = NULL; - char *prefix = NULL; - git_index *index; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, 0, - &b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts) < 0) || - (error = git_repository_index__weakptr(&index, repo)) < 0 || - (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || - (error = git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts)) < 0 || - (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) - goto out; - - *out = diff; - diff = NULL; -out: - git_iterator_free(a); - git_iterator_free(b); - git_diff_free(diff); - git__free(prefix); - - return error; -} - -int git_diff_tree_to_workdir_with_index( - git_diff **out, - git_repository *repo, - git_tree *tree, - const git_diff_options *opts) -{ - git_diff *d1 = NULL, *d2 = NULL; - git_index *index = NULL; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - if ((error = diff_load_index(&index, repo)) < 0) - return error; - - if (!(error = git_diff_tree_to_index(&d1, repo, tree, index, opts)) && - !(error = git_diff_index_to_workdir(&d2, repo, index, opts))) - error = git_diff_merge(d1, d2); - - git_diff_free(d2); - - if (error) { - git_diff_free(d1); - d1 = NULL; - } - - *out = d1; - return error; -} - -int git_diff_index_to_index( - git_diff **out, - git_repository *repo, - git_index *old_index, - git_index *new_index, - const git_diff_options *opts) -{ - git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, - b_opts = GIT_ITERATOR_OPTIONS_INIT; - git_iterator *a = NULL, *b = NULL; - git_diff *diff = NULL; - char *prefix = NULL; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(old_index); - GIT_ASSERT_ARG(new_index); - - *out = NULL; - - if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_DONT_IGNORE_CASE, - &b_opts, GIT_ITERATOR_DONT_IGNORE_CASE, opts) < 0) || - (error = git_iterator_for_index(&a, repo, old_index, &a_opts)) < 0 || - (error = git_iterator_for_index(&b, repo, new_index, &b_opts)) < 0 || - (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) - goto out; - - /* if index is in case-insensitive order, re-sort deltas to match */ - if (old_index->ignore_case || new_index->ignore_case) - diff_set_ignore_case(diff, true); - - *out = diff; - diff = NULL; -out: - git_iterator_free(a); - git_iterator_free(b); - git_diff_free(diff); - git__free(prefix); - - return error; -} - -int git_diff__paired_foreach( - git_diff *head2idx, - git_diff *idx2wd, - int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload), - void *payload) -{ - int cmp, error = 0; - git_diff_delta *h2i, *i2w; - size_t i, j, i_max, j_max; - int (*strcomp)(const char *, const char *) = git__strcmp; - bool h2i_icase, i2w_icase, icase_mismatch; - - i_max = head2idx ? head2idx->deltas.length : 0; - j_max = idx2wd ? idx2wd->deltas.length : 0; - if (!i_max && !j_max) - return 0; - - /* At some point, tree-to-index diffs will probably never ignore case, - * even if that isn't true now. Index-to-workdir diffs may or may not - * ignore case, but the index filename for the idx2wd diff should - * still be using the canonical case-preserving name. - * - * Therefore the main thing we need to do here is make sure the diffs - * are traversed in a compatible order. To do this, we temporarily - * resort a mismatched diff to get the order correct. - * - * In order to traverse renames in the index->workdir, we need to - * ensure that we compare the index name on both sides, so we - * always sort by the old name in the i2w list. - */ - h2i_icase = head2idx != NULL && git_diff_is_sorted_icase(head2idx); - i2w_icase = idx2wd != NULL && git_diff_is_sorted_icase(idx2wd); - - icase_mismatch = - (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase); - - if (icase_mismatch && h2i_icase) { - git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); - git_vector_sort(&head2idx->deltas); - } - - if (i2w_icase && !icase_mismatch) { - strcomp = git__strcasecmp; - - git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_casecmp); - git_vector_sort(&idx2wd->deltas); - } else if (idx2wd != NULL) { - git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_cmp); - git_vector_sort(&idx2wd->deltas); - } - - for (i = 0, j = 0; i < i_max || j < j_max; ) { - h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL; - i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL; - - cmp = !i2w ? -1 : !h2i ? 1 : - strcomp(h2i->new_file.path, i2w->old_file.path); - - if (cmp < 0) { - i++; i2w = NULL; - } else if (cmp > 0) { - j++; h2i = NULL; - } else { - i++; j++; - } - - if ((error = cb(h2i, i2w, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - /* restore case-insensitive delta sort */ - if (icase_mismatch && h2i_icase) { - git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); - git_vector_sort(&head2idx->deltas); - } - - /* restore idx2wd sort by new path */ - if (idx2wd != NULL) { - git_vector_set_cmp(&idx2wd->deltas, - i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp); - git_vector_sort(&idx2wd->deltas); - } - - return error; -} - -int git_diff__commit( - git_diff **out, - git_repository *repo, - const git_commit *commit, - const git_diff_options *opts) -{ - git_commit *parent = NULL; - git_diff *commit_diff = NULL; - git_tree *old_tree = NULL, *new_tree = NULL; - size_t parents; - int error = 0; - - *out = NULL; - - if ((parents = git_commit_parentcount(commit)) > 1) { - char commit_oidstr[GIT_OID_HEXSZ + 1]; - - error = -1; - git_error_set(GIT_ERROR_INVALID, "commit %s is a merge commit", - git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit))); - goto on_error; - } - - if (parents > 0) - if ((error = git_commit_parent(&parent, commit, 0)) < 0 || - (error = git_commit_tree(&old_tree, parent)) < 0) - goto on_error; - - if ((error = git_commit_tree(&new_tree, commit)) < 0 || - (error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0) - goto on_error; - - *out = commit_diff; - -on_error: - git_tree_free(new_tree); - git_tree_free(old_tree); - git_commit_free(parent); - - return error; -} - diff --git a/src/diff_generate.h b/src/diff_generate.h deleted file mode 100644 index b782f29c6..000000000 --- a/src/diff_generate.h +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_diff_generate_h__ -#define INCLUDE_diff_generate_h__ - -#include "common.h" - -#include "diff.h" -#include "pool.h" -#include "index.h" - -enum { - GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */ - GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */ - GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */ - GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */ - GIT_DIFFCAPS_USE_DEV = (1 << 4) /* use st_dev? */ -}; - -#define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY) -#define DIFF_FLAGS_NOT_BINARY (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA) - -enum { - GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */ - GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */ - GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */ - GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */ - GIT_DIFF_FLAG__FREE_BLOB = (1 << 11), /* release the blob when done */ - GIT_DIFF_FLAG__LOADED = (1 << 12), /* file data has been loaded */ - - GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */ - GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */ - GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18), - GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19), - GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20) -}; - -#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF) - -#define GIT_DIFF__VERBOSE (1 << 30) - -extern void git_diff_addref(git_diff *diff); - -extern bool git_diff_delta__should_skip( - const git_diff_options *opts, const git_diff_delta *delta); - -extern int git_diff__from_iterators( - git_diff **diff_ptr, - git_repository *repo, - git_iterator *old_iter, - git_iterator *new_iter, - const git_diff_options *opts); - -extern int git_diff__commit( - git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts); - -extern int git_diff__paired_foreach( - git_diff *idx2head, - git_diff *wd2idx, - int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), - void *payload); - -/* Merge two `git_diff`s according to the callback given by `cb`. */ - -typedef git_diff_delta *(*git_diff__merge_cb)( - const git_diff_delta *left, - const git_diff_delta *right, - git_pool *pool); - -extern int git_diff__merge( - git_diff *onto, const git_diff *from, git_diff__merge_cb cb); - -extern git_diff_delta *git_diff__merge_like_cgit( - const git_diff_delta *a, - const git_diff_delta *b, - git_pool *pool); - -/* Duplicate a `git_diff_delta` out of the `git_pool` */ -extern git_diff_delta *git_diff__delta_dup( - const git_diff_delta *d, git_pool *pool); - -extern int git_diff__oid_for_file( - git_oid *out, - git_diff *diff, - const char *path, - uint16_t mode, - git_object_size_t size); - -extern int git_diff__oid_for_entry( - git_oid *out, - git_diff *diff, - const git_index_entry *src, - uint16_t mode, - const git_oid *update_match); - -/* - * Sometimes a git_diff_file will have a zero size; this attempts to - * fill in the size without loading the blob if possible. If that is - * not possible, then it will return the git_odb_object that had to be - * loaded and the caller can use it or dispose of it as needed. - */ -GIT_INLINE(int) git_diff_file__resolve_zero_size( - git_diff_file *file, git_odb_object **odb_obj, git_repository *repo) -{ - int error; - git_odb *odb; - size_t len; - git_object_t type; - - if ((error = git_repository_odb(&odb, repo)) < 0) - return error; - - error = git_odb__read_header_or_object( - odb_obj, &len, &type, odb, &file->id); - - git_odb_free(odb); - - if (!error) { - file->size = (git_object_size_t)len; - file->flags |= GIT_DIFF_FLAG_VALID_SIZE; - } - - return error; -} - -#endif diff --git a/src/diff_parse.c b/src/diff_parse.c deleted file mode 100644 index 75e41a544..000000000 --- a/src/diff_parse.c +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "diff_parse.h" - -#include "diff.h" -#include "patch.h" -#include "patch_parse.h" - -static void diff_parsed_free(git_diff *d) -{ - git_diff_parsed *diff = (git_diff_parsed *)d; - git_patch *patch; - size_t i; - - git_vector_foreach(&diff->patches, i, patch) - git_patch_free(patch); - - git_vector_free(&diff->patches); - - git_vector_free(&diff->base.deltas); - git_pool_clear(&diff->base.pool); - - git__memzero(diff, sizeof(*diff)); - git__free(diff); -} - -static git_diff_parsed *diff_parsed_alloc(void) -{ - git_diff_parsed *diff; - - if ((diff = git__calloc(1, sizeof(git_diff_parsed))) == NULL) - return NULL; - - GIT_REFCOUNT_INC(&diff->base); - diff->base.type = GIT_DIFF_TYPE_PARSED; - diff->base.strcomp = git__strcmp; - diff->base.strncomp = git__strncmp; - diff->base.pfxcomp = git__prefixcmp; - diff->base.entrycomp = git_diff__entry_cmp; - diff->base.patch_fn = git_patch_parsed_from_diff; - diff->base.free_fn = diff_parsed_free; - - if (git_diff_options_init(&diff->base.opts, GIT_DIFF_OPTIONS_VERSION) < 0) { - git__free(diff); - return NULL; - } - - diff->base.opts.flags &= ~GIT_DIFF_IGNORE_CASE; - - if (git_pool_init(&diff->base.pool, 1) < 0 || - git_vector_init(&diff->patches, 0, NULL) < 0 || - git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) { - git_diff_free(&diff->base); - return NULL; - } - - git_vector_set_cmp(&diff->base.deltas, git_diff_delta__cmp); - - return diff; -} - -int git_diff_from_buffer( - git_diff **out, - const char *content, - size_t content_len) -{ - git_diff_parsed *diff; - git_patch *patch; - git_patch_parse_ctx *ctx = NULL; - int error = 0; - - *out = NULL; - - diff = diff_parsed_alloc(); - GIT_ERROR_CHECK_ALLOC(diff); - - ctx = git_patch_parse_ctx_init(content, content_len, NULL); - GIT_ERROR_CHECK_ALLOC(ctx); - - while (ctx->parse_ctx.remain_len) { - if ((error = git_patch_parse(&patch, ctx)) < 0) - break; - - git_vector_insert(&diff->patches, patch); - git_vector_insert(&diff->base.deltas, patch->delta); - } - - if (error == GIT_ENOTFOUND && git_vector_length(&diff->patches) > 0) { - git_error_clear(); - error = 0; - } - - git_patch_parse_ctx_free(ctx); - - if (error < 0) - git_diff_free(&diff->base); - else - *out = &diff->base; - - return error; -} - diff --git a/src/diff_parse.h b/src/diff_parse.h deleted file mode 100644 index 876782128..000000000 --- a/src/diff_parse.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_diff_parse_h__ -#define INCLUDE_diff_parse_h__ - -#include "common.h" - -#include "diff.h" - -typedef struct { - struct git_diff base; - - git_vector patches; -} git_diff_parsed; - -#endif diff --git a/src/diff_print.c b/src/diff_print.c deleted file mode 100644 index 03d25b087..000000000 --- a/src/diff_print.c +++ /dev/null @@ -1,825 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "buf.h" -#include "diff.h" -#include "diff_file.h" -#include "patch_generate.h" -#include "futils.h" -#include "zstream.h" -#include "blob.h" -#include "delta.h" -#include "git2/sys/diff.h" - -typedef struct { - git_diff_format_t format; - git_diff_line_cb print_cb; - void *payload; - - git_str *buf; - git_diff_line line; - - const char *old_prefix; - const char *new_prefix; - uint32_t flags; - int id_strlen; - - int (*strcomp)(const char *, const char *); -} diff_print_info; - -static int diff_print_info_init__common( - diff_print_info *pi, - git_str *out, - git_repository *repo, - git_diff_format_t format, - git_diff_line_cb cb, - void *payload) -{ - pi->format = format; - pi->print_cb = cb; - pi->payload = payload; - pi->buf = out; - - if (!pi->id_strlen) { - if (!repo) - pi->id_strlen = GIT_ABBREV_DEFAULT; - else if (git_repository__configmap_lookup(&pi->id_strlen, repo, GIT_CONFIGMAP_ABBREV) < 0) - return -1; - } - - if (pi->id_strlen > GIT_OID_HEXSZ) - pi->id_strlen = GIT_OID_HEXSZ; - - memset(&pi->line, 0, sizeof(pi->line)); - pi->line.old_lineno = -1; - pi->line.new_lineno = -1; - pi->line.num_lines = 1; - - return 0; -} - -static int diff_print_info_init_fromdiff( - diff_print_info *pi, - git_str *out, - git_diff *diff, - git_diff_format_t format, - git_diff_line_cb cb, - void *payload) -{ - git_repository *repo = diff ? diff->repo : NULL; - - memset(pi, 0, sizeof(diff_print_info)); - - if (diff) { - pi->flags = diff->opts.flags; - pi->id_strlen = diff->opts.id_abbrev; - pi->old_prefix = diff->opts.old_prefix; - pi->new_prefix = diff->opts.new_prefix; - - pi->strcomp = diff->strcomp; - } - - return diff_print_info_init__common(pi, out, repo, format, cb, payload); -} - -static int diff_print_info_init_frompatch( - diff_print_info *pi, - git_str *out, - git_patch *patch, - git_diff_format_t format, - git_diff_line_cb cb, - void *payload) -{ - GIT_ASSERT_ARG(patch); - - memset(pi, 0, sizeof(diff_print_info)); - - pi->flags = patch->diff_opts.flags; - pi->id_strlen = patch->diff_opts.id_abbrev; - pi->old_prefix = patch->diff_opts.old_prefix; - pi->new_prefix = patch->diff_opts.new_prefix; - - return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload); -} - -static char diff_pick_suffix(int mode) -{ - if (S_ISDIR(mode)) - return '/'; - else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */ - /* in git, modes are very regular, so we must have 0100755 mode */ - return '*'; - else - return ' '; -} - -char git_diff_status_char(git_delta_t status) -{ - char code; - - switch (status) { - case GIT_DELTA_ADDED: code = 'A'; break; - case GIT_DELTA_DELETED: code = 'D'; break; - case GIT_DELTA_MODIFIED: code = 'M'; break; - case GIT_DELTA_RENAMED: code = 'R'; break; - case GIT_DELTA_COPIED: code = 'C'; break; - case GIT_DELTA_IGNORED: code = 'I'; break; - case GIT_DELTA_UNTRACKED: code = '?'; break; - case GIT_DELTA_TYPECHANGE: code = 'T'; break; - case GIT_DELTA_UNREADABLE: code = 'X'; break; - default: code = ' '; break; - } - - return code; -} - -static int diff_print_one_name_only( - const git_diff_delta *delta, float progress, void *data) -{ - diff_print_info *pi = data; - git_str *out = pi->buf; - - GIT_UNUSED(progress); - - if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && - delta->status == GIT_DELTA_UNMODIFIED) - return 0; - - git_str_clear(out); - git_str_puts(out, delta->new_file.path); - git_str_putc(out, '\n'); - if (git_str_oom(out)) - return -1; - - pi->line.origin = GIT_DIFF_LINE_FILE_HDR; - pi->line.content = git_str_cstr(out); - pi->line.content_len = git_str_len(out); - - return pi->print_cb(delta, NULL, &pi->line, pi->payload); -} - -static int diff_print_one_name_status( - const git_diff_delta *delta, float progress, void *data) -{ - diff_print_info *pi = data; - git_str *out = pi->buf; - char old_suffix, new_suffix, code = git_diff_status_char(delta->status); - int(*strcomp)(const char *, const char *) = pi->strcomp ? - pi->strcomp : git__strcmp; - - GIT_UNUSED(progress); - - if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') - return 0; - - old_suffix = diff_pick_suffix(delta->old_file.mode); - new_suffix = diff_pick_suffix(delta->new_file.mode); - - git_str_clear(out); - - if (delta->old_file.path != delta->new_file.path && - strcomp(delta->old_file.path,delta->new_file.path) != 0) - git_str_printf(out, "%c\t%s%c %s%c\n", code, - delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); - else if (delta->old_file.mode != delta->new_file.mode && - delta->old_file.mode != 0 && delta->new_file.mode != 0) - git_str_printf(out, "%c\t%s%c %s%c\n", code, - delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); - else if (old_suffix != ' ') - git_str_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); - else - git_str_printf(out, "%c\t%s\n", code, delta->old_file.path); - if (git_str_oom(out)) - return -1; - - pi->line.origin = GIT_DIFF_LINE_FILE_HDR; - pi->line.content = git_str_cstr(out); - pi->line.content_len = git_str_len(out); - - return pi->print_cb(delta, NULL, &pi->line, pi->payload); -} - -static int diff_print_one_raw( - const git_diff_delta *delta, float progress, void *data) -{ - diff_print_info *pi = data; - git_str *out = pi->buf; - int id_abbrev; - char code = git_diff_status_char(delta->status); - char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; - - GIT_UNUSED(progress); - - if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') - return 0; - - git_str_clear(out); - - id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev : - delta->new_file.id_abbrev; - - if (pi->id_strlen > id_abbrev) { - git_error_set(GIT_ERROR_PATCH, - "the patch input contains %d id characters (cannot print %d)", - id_abbrev, pi->id_strlen); - return -1; - } - - git_oid_tostr(start_oid, pi->id_strlen + 1, &delta->old_file.id); - git_oid_tostr(end_oid, pi->id_strlen + 1, &delta->new_file.id); - - git_str_printf( - out, (pi->id_strlen <= GIT_OID_HEXSZ) ? - ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c", - delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code); - - if (delta->similarity > 0) - git_str_printf(out, "%03u", delta->similarity); - - if (delta->old_file.path != delta->new_file.path) - git_str_printf( - out, "\t%s %s\n", delta->old_file.path, delta->new_file.path); - else - git_str_printf( - out, "\t%s\n", delta->old_file.path ? - delta->old_file.path : delta->new_file.path); - - if (git_str_oom(out)) - return -1; - - pi->line.origin = GIT_DIFF_LINE_FILE_HDR; - pi->line.content = git_str_cstr(out); - pi->line.content_len = git_str_len(out); - - return pi->print_cb(delta, NULL, &pi->line, pi->payload); -} - -static int diff_print_modes( - git_str *out, const git_diff_delta *delta) -{ - git_str_printf(out, "old mode %o\n", delta->old_file.mode); - git_str_printf(out, "new mode %o\n", delta->new_file.mode); - - return git_str_oom(out) ? -1 : 0; -} - -static int diff_print_oid_range( - git_str *out, const git_diff_delta *delta, int id_strlen, - bool print_index) -{ - char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; - - if (delta->old_file.mode && - id_strlen > delta->old_file.id_abbrev) { - git_error_set(GIT_ERROR_PATCH, - "the patch input contains %d id characters (cannot print %d)", - delta->old_file.id_abbrev, id_strlen); - return -1; - } - - if ((delta->new_file.mode && - id_strlen > delta->new_file.id_abbrev)) { - git_error_set(GIT_ERROR_PATCH, - "the patch input contains %d id characters (cannot print %d)", - delta->new_file.id_abbrev, id_strlen); - return -1; - } - - git_oid_tostr(start_oid, id_strlen + 1, &delta->old_file.id); - git_oid_tostr(end_oid, id_strlen + 1, &delta->new_file.id); - - if (delta->old_file.mode == delta->new_file.mode) { - if (print_index) - git_str_printf(out, "index %s..%s %o\n", - start_oid, end_oid, delta->old_file.mode); - } else { - if (delta->old_file.mode == 0) - git_str_printf(out, "new file mode %o\n", delta->new_file.mode); - else if (delta->new_file.mode == 0) - git_str_printf(out, "deleted file mode %o\n", delta->old_file.mode); - else - diff_print_modes(out, delta); - - if (print_index) - git_str_printf(out, "index %s..%s\n", start_oid, end_oid); - } - - return git_str_oom(out) ? -1 : 0; -} - -static int diff_delta_format_path( - git_str *out, const char *prefix, const char *filename) -{ - if (git_str_joinpath(out, prefix, filename) < 0) - return -1; - - return git_str_quote(out); -} - -static int diff_delta_format_with_paths( - git_str *out, - const git_diff_delta *delta, - const char *template, - const char *oldpath, - const char *newpath) -{ - if (git_oid_is_zero(&delta->old_file.id)) - oldpath = "/dev/null"; - - if (git_oid_is_zero(&delta->new_file.id)) - newpath = "/dev/null"; - - return git_str_printf(out, template, oldpath, newpath); -} - -static int diff_delta_format_similarity_header( - git_str *out, - const git_diff_delta *delta) -{ - git_str old_path = GIT_STR_INIT, new_path = GIT_STR_INIT; - const char *type; - int error = 0; - - if (delta->similarity > 100) { - git_error_set(GIT_ERROR_PATCH, "invalid similarity %d", delta->similarity); - error = -1; - goto done; - } - - GIT_ASSERT(delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED); - if (delta->status == GIT_DELTA_RENAMED) - type = "rename"; - else - type = "copy"; - - if ((error = git_str_puts(&old_path, delta->old_file.path)) < 0 || - (error = git_str_puts(&new_path, delta->new_file.path)) < 0 || - (error = git_str_quote(&old_path)) < 0 || - (error = git_str_quote(&new_path)) < 0) - goto done; - - git_str_printf(out, - "similarity index %d%%\n" - "%s from %s\n" - "%s to %s\n", - delta->similarity, - type, old_path.ptr, - type, new_path.ptr); - - if (git_str_oom(out)) - error = -1; - -done: - git_str_dispose(&old_path); - git_str_dispose(&new_path); - - return error; -} - -static bool delta_is_unchanged(const git_diff_delta *delta) -{ - if (git_oid_is_zero(&delta->old_file.id) && - git_oid_is_zero(&delta->new_file.id)) - return true; - - if (delta->old_file.mode == GIT_FILEMODE_COMMIT || - delta->new_file.mode == GIT_FILEMODE_COMMIT) - return false; - - if (git_oid_equal(&delta->old_file.id, &delta->new_file.id)) - return true; - - return false; -} - -int git_diff_delta__format_file_header( - git_str *out, - const git_diff_delta *delta, - const char *oldpfx, - const char *newpfx, - int id_strlen, - bool print_index) -{ - git_str old_path = GIT_STR_INIT, new_path = GIT_STR_INIT; - bool unchanged = delta_is_unchanged(delta); - int error = 0; - - if (!oldpfx) - oldpfx = DIFF_OLD_PREFIX_DEFAULT; - if (!newpfx) - newpfx = DIFF_NEW_PREFIX_DEFAULT; - if (!id_strlen) - id_strlen = GIT_ABBREV_DEFAULT; - - if ((error = diff_delta_format_path( - &old_path, oldpfx, delta->old_file.path)) < 0 || - (error = diff_delta_format_path( - &new_path, newpfx, delta->new_file.path)) < 0) - goto done; - - git_str_clear(out); - - git_str_printf(out, "diff --git %s %s\n", - old_path.ptr, new_path.ptr); - - if (unchanged && delta->old_file.mode != delta->new_file.mode) - diff_print_modes(out, delta); - - if (delta->status == GIT_DELTA_RENAMED || - (delta->status == GIT_DELTA_COPIED && unchanged)) { - if ((error = diff_delta_format_similarity_header(out, delta)) < 0) - goto done; - } - - if (!unchanged) { - if ((error = diff_print_oid_range(out, delta, - id_strlen, print_index)) < 0) - goto done; - - if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) - diff_delta_format_with_paths(out, delta, - "--- %s\n+++ %s\n", old_path.ptr, new_path.ptr); - } - - if (git_str_oom(out)) - error = -1; - -done: - git_str_dispose(&old_path); - git_str_dispose(&new_path); - - return error; -} - -static int format_binary( - diff_print_info *pi, - git_diff_binary_t type, - const char *data, - size_t datalen, - size_t inflatedlen) -{ - const char *typename = type == GIT_DIFF_BINARY_DELTA ? - "delta" : "literal"; - const char *scan, *end; - - git_str_printf(pi->buf, "%s %" PRIuZ "\n", typename, inflatedlen); - pi->line.num_lines++; - - for (scan = data, end = data + datalen; scan < end; ) { - size_t chunk_len = end - scan; - if (chunk_len > 52) - chunk_len = 52; - - if (chunk_len <= 26) - git_str_putc(pi->buf, (char)chunk_len + 'A' - 1); - else - git_str_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1); - - git_str_encode_base85(pi->buf, scan, chunk_len); - git_str_putc(pi->buf, '\n'); - - if (git_str_oom(pi->buf)) - return -1; - - scan += chunk_len; - pi->line.num_lines++; - } - git_str_putc(pi->buf, '\n'); - - if (git_str_oom(pi->buf)) - return -1; - - return 0; -} - -static int diff_print_patch_file_binary_noshow( - diff_print_info *pi, git_diff_delta *delta, - const char *old_pfx, const char *new_pfx) -{ - git_str old_path = GIT_STR_INIT, new_path = GIT_STR_INIT; - int error; - - if ((error = diff_delta_format_path(&old_path, old_pfx, delta->old_file.path)) < 0 || - (error = diff_delta_format_path(&new_path, new_pfx, delta->new_file.path)) < 0 || - (error = diff_delta_format_with_paths(pi->buf, delta, "Binary files %s and %s differ\n", - old_path.ptr, new_path.ptr)) < 0) - goto done; - - pi->line.num_lines = 1; - -done: - git_str_dispose(&old_path); - git_str_dispose(&new_path); - return error; -} - -static int diff_print_patch_file_binary( - diff_print_info *pi, git_diff_delta *delta, - const char *old_pfx, const char *new_pfx, - const git_diff_binary *binary) -{ - size_t pre_binary_size; - int error; - - if (delta->status == GIT_DELTA_UNMODIFIED) - return 0; - - if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0 || !binary->contains_data) - return diff_print_patch_file_binary_noshow( - pi, delta, old_pfx, new_pfx); - - pre_binary_size = pi->buf->size; - git_str_printf(pi->buf, "GIT binary patch\n"); - pi->line.num_lines++; - - if ((error = format_binary(pi, binary->new_file.type, binary->new_file.data, - binary->new_file.datalen, binary->new_file.inflatedlen)) < 0 || - (error = format_binary(pi, binary->old_file.type, binary->old_file.data, - binary->old_file.datalen, binary->old_file.inflatedlen)) < 0) { - if (error == GIT_EBUFS) { - git_error_clear(); - git_str_truncate(pi->buf, pre_binary_size); - - return diff_print_patch_file_binary_noshow( - pi, delta, old_pfx, new_pfx); - } - } - - pi->line.num_lines++; - return error; -} - -static int diff_print_patch_file( - const git_diff_delta *delta, float progress, void *data) -{ - int error; - diff_print_info *pi = data; - const char *oldpfx = - pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT; - const char *newpfx = - pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; - - bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) || - (pi->flags & GIT_DIFF_FORCE_BINARY); - bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY); - int id_strlen = pi->id_strlen; - bool print_index = (pi->format != GIT_DIFF_FORMAT_PATCH_ID); - - if (binary && show_binary) - id_strlen = delta->old_file.id_abbrev ? delta->old_file.id_abbrev : - delta->new_file.id_abbrev; - - GIT_UNUSED(progress); - - if (S_ISDIR(delta->new_file.mode) || - delta->status == GIT_DELTA_UNMODIFIED || - delta->status == GIT_DELTA_IGNORED || - delta->status == GIT_DELTA_UNREADABLE || - (delta->status == GIT_DELTA_UNTRACKED && - (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0)) - return 0; - - if ((error = git_diff_delta__format_file_header(pi->buf, delta, oldpfx, newpfx, - id_strlen, print_index)) < 0) - return error; - - pi->line.origin = GIT_DIFF_LINE_FILE_HDR; - pi->line.content = git_str_cstr(pi->buf); - pi->line.content_len = git_str_len(pi->buf); - - return pi->print_cb(delta, NULL, &pi->line, pi->payload); -} - -static int diff_print_patch_binary( - const git_diff_delta *delta, - const git_diff_binary *binary, - void *data) -{ - diff_print_info *pi = data; - const char *old_pfx = - pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT; - const char *new_pfx = - pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; - int error; - - git_str_clear(pi->buf); - - if ((error = diff_print_patch_file_binary( - pi, (git_diff_delta *)delta, old_pfx, new_pfx, binary)) < 0) - return error; - - pi->line.origin = GIT_DIFF_LINE_BINARY; - pi->line.content = git_str_cstr(pi->buf); - pi->line.content_len = git_str_len(pi->buf); - - return pi->print_cb(delta, NULL, &pi->line, pi->payload); -} - -static int diff_print_patch_hunk( - const git_diff_delta *d, - const git_diff_hunk *h, - void *data) -{ - diff_print_info *pi = data; - - if (S_ISDIR(d->new_file.mode)) - return 0; - - pi->line.origin = GIT_DIFF_LINE_HUNK_HDR; - pi->line.content = h->header; - pi->line.content_len = h->header_len; - - return pi->print_cb(d, h, &pi->line, pi->payload); -} - -static int diff_print_patch_line( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *data) -{ - diff_print_info *pi = data; - - if (S_ISDIR(delta->new_file.mode)) - return 0; - - return pi->print_cb(delta, hunk, line, pi->payload); -} - -/* print a git_diff to an output callback */ -int git_diff_print( - git_diff *diff, - git_diff_format_t format, - git_diff_line_cb print_cb, - void *payload) -{ - int error; - git_str buf = GIT_STR_INIT; - diff_print_info pi; - git_diff_file_cb print_file = NULL; - git_diff_binary_cb print_binary = NULL; - git_diff_hunk_cb print_hunk = NULL; - git_diff_line_cb print_line = NULL; - - switch (format) { - case GIT_DIFF_FORMAT_PATCH: - print_file = diff_print_patch_file; - print_binary = diff_print_patch_binary; - print_hunk = diff_print_patch_hunk; - print_line = diff_print_patch_line; - break; - case GIT_DIFF_FORMAT_PATCH_ID: - print_file = diff_print_patch_file; - print_binary = diff_print_patch_binary; - print_line = diff_print_patch_line; - break; - case GIT_DIFF_FORMAT_PATCH_HEADER: - print_file = diff_print_patch_file; - break; - case GIT_DIFF_FORMAT_RAW: - print_file = diff_print_one_raw; - break; - case GIT_DIFF_FORMAT_NAME_ONLY: - print_file = diff_print_one_name_only; - break; - case GIT_DIFF_FORMAT_NAME_STATUS: - print_file = diff_print_one_name_status; - break; - default: - git_error_set(GIT_ERROR_INVALID, "unknown diff output format (%d)", format); - return -1; - } - - if ((error = diff_print_info_init_fromdiff(&pi, &buf, diff, format, print_cb, payload)) < 0) - goto out; - - if ((error = git_diff_foreach(diff, print_file, print_binary, print_hunk, print_line, &pi)) != 0) { - git_error_set_after_callback_function(error, "git_diff_print"); - goto out; - } - -out: - git_str_dispose(&buf); - return error; -} - -int git_diff_print_callback__to_buf( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *payload) -{ - git_str *output = payload; - GIT_UNUSED(delta); GIT_UNUSED(hunk); - - if (!output) { - git_error_set(GIT_ERROR_INVALID, "buffer pointer must be provided"); - return -1; - } - - if (line->origin == GIT_DIFF_LINE_ADDITION || - line->origin == GIT_DIFF_LINE_DELETION || - line->origin == GIT_DIFF_LINE_CONTEXT) - git_str_putc(output, line->origin); - - return git_str_put(output, line->content, line->content_len); -} - -int git_diff_print_callback__to_file_handle( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *payload) -{ - FILE *fp = payload ? payload : stdout; - int error; - - GIT_UNUSED(delta); - GIT_UNUSED(hunk); - - if (line->origin == GIT_DIFF_LINE_CONTEXT || - line->origin == GIT_DIFF_LINE_ADDITION || - line->origin == GIT_DIFF_LINE_DELETION) { - while ((error = fputc(line->origin, fp)) == EINTR) - continue; - if (error) { - git_error_set(GIT_ERROR_OS, "could not write status"); - return -1; - } - } - - if (fwrite(line->content, line->content_len, 1, fp) != 1) { - git_error_set(GIT_ERROR_OS, "could not write line"); - return -1; - } - - return 0; -} - -/* print a git_diff to a git_str */ -int git_diff_to_buf(git_buf *out, git_diff *diff, git_diff_format_t format) -{ - git_str str = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diff); - - if ((error = git_buf_tostr(&str, out)) < 0 || - (error = git_diff_print(diff, format, git_diff_print_callback__to_buf, &str)) < 0) - goto done; - - error = git_buf_fromstr(out, &str); - -done: - git_str_dispose(&str); - return error; -} - -/* print a git_patch to an output callback */ -int git_patch_print( - git_patch *patch, - git_diff_line_cb print_cb, - void *payload) -{ - git_str temp = GIT_STR_INIT; - diff_print_info pi; - int error; - - GIT_ASSERT_ARG(patch); - GIT_ASSERT_ARG(print_cb); - - if ((error = diff_print_info_init_frompatch(&pi, &temp, patch, - GIT_DIFF_FORMAT_PATCH, print_cb, payload)) < 0) - goto out; - - if ((error = git_patch__invoke_callbacks(patch, diff_print_patch_file, diff_print_patch_binary, - diff_print_patch_hunk, diff_print_patch_line, &pi)) < 0) { - git_error_set_after_callback_function(error, "git_patch_print"); - goto out; - } - -out: - git_str_dispose(&temp); - return error; -} - -/* print a git_patch to a git_str */ -int git_patch_to_buf(git_buf *out, git_patch *patch) -{ - GIT_BUF_WRAP_PRIVATE(out, git_patch__to_buf, patch); -} - -int git_patch__to_buf(git_str *out, git_patch *patch) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(patch); - - return git_patch_print(patch, git_diff_print_callback__to_buf, out); -} diff --git a/src/diff_stats.c b/src/diff_stats.c deleted file mode 100644 index 259939844..000000000 --- a/src/diff_stats.c +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "diff_stats.h" - -#include "buf.h" -#include "common.h" -#include "vector.h" -#include "diff.h" -#include "patch_generate.h" - -#define DIFF_RENAME_FILE_SEPARATOR " => " -#define STATS_FULL_MIN_SCALE 7 - -typedef struct { - size_t insertions; - size_t deletions; -} diff_file_stats; - -struct git_diff_stats { - git_diff *diff; - diff_file_stats *filestats; - - size_t files_changed; - size_t insertions; - size_t deletions; - size_t renames; - - size_t max_name; - size_t max_filestat; - int max_digits; -}; - -static int digits_for_value(size_t val) -{ - int count = 1; - size_t placevalue = 10; - - while (val >= placevalue) { - ++count; - placevalue *= 10; - } - - return count; -} - -static int diff_file_stats_full_to_buf( - git_str *out, - const git_diff_delta *delta, - const diff_file_stats *filestat, - const git_diff_stats *stats, - size_t width) -{ - const char *old_path = NULL, *new_path = NULL, *adddel_path = NULL; - size_t padding; - git_object_size_t old_size, new_size; - - old_path = delta->old_file.path; - new_path = delta->new_file.path; - old_size = delta->old_file.size; - new_size = delta->new_file.size; - - if (old_path && new_path && strcmp(old_path, new_path) != 0) { - size_t common_dirlen; - int error; - - padding = stats->max_name - strlen(old_path) - strlen(new_path); - - if ((common_dirlen = git_fs_path_common_dirlen(old_path, new_path)) && - common_dirlen <= INT_MAX) { - error = git_str_printf(out, " %.*s{%s"DIFF_RENAME_FILE_SEPARATOR"%s}", - (int) common_dirlen, old_path, - old_path + common_dirlen, - new_path + common_dirlen); - } else { - error = git_str_printf(out, " %s" DIFF_RENAME_FILE_SEPARATOR "%s", - old_path, new_path); - } - - if (error < 0) - goto on_error; - } else { - adddel_path = new_path ? new_path : old_path; - if (git_str_printf(out, " %s", adddel_path) < 0) - goto on_error; - - padding = stats->max_name - strlen(adddel_path); - - if (stats->renames > 0) - padding += strlen(DIFF_RENAME_FILE_SEPARATOR); - } - - if (git_str_putcn(out, ' ', padding) < 0 || - git_str_puts(out, " | ") < 0) - goto on_error; - - if (delta->flags & GIT_DIFF_FLAG_BINARY) { - if (git_str_printf(out, - "Bin %" PRId64 " -> %" PRId64 " bytes", old_size, new_size) < 0) - goto on_error; - } - else { - if (git_str_printf(out, - "%*" PRIuZ, stats->max_digits, - filestat->insertions + filestat->deletions) < 0) - goto on_error; - - if (filestat->insertions || filestat->deletions) { - if (git_str_putc(out, ' ') < 0) - goto on_error; - - if (!width) { - if (git_str_putcn(out, '+', filestat->insertions) < 0 || - git_str_putcn(out, '-', filestat->deletions) < 0) - goto on_error; - } else { - size_t total = filestat->insertions + filestat->deletions; - size_t full = (total * width + stats->max_filestat / 2) / - stats->max_filestat; - size_t plus = full * filestat->insertions / total; - size_t minus = full - plus; - - if (git_str_putcn(out, '+', max(plus, 1)) < 0 || - git_str_putcn(out, '-', max(minus, 1)) < 0) - goto on_error; - } - } - } - - git_str_putc(out, '\n'); - -on_error: - return (git_str_oom(out) ? -1 : 0); -} - -static int diff_file_stats_number_to_buf( - git_str *out, - const git_diff_delta *delta, - const diff_file_stats *filestats) -{ - int error; - const char *path = delta->new_file.path; - - if (delta->flags & GIT_DIFF_FLAG_BINARY) - error = git_str_printf(out, "%-8c" "%-8c" "%s\n", '-', '-', path); - else - error = git_str_printf(out, "%-8" PRIuZ "%-8" PRIuZ "%s\n", - filestats->insertions, filestats->deletions, path); - - return error; -} - -static int diff_file_stats_summary_to_buf( - git_str *out, - const git_diff_delta *delta) -{ - if (delta->old_file.mode != delta->new_file.mode) { - if (delta->old_file.mode == 0) { - git_str_printf(out, " create mode %06o %s\n", - delta->new_file.mode, delta->new_file.path); - } - else if (delta->new_file.mode == 0) { - git_str_printf(out, " delete mode %06o %s\n", - delta->old_file.mode, delta->old_file.path); - } - else { - git_str_printf(out, " mode change %06o => %06o %s\n", - delta->old_file.mode, delta->new_file.mode, delta->new_file.path); - } - } - - return 0; -} - -int git_diff_get_stats( - git_diff_stats **out, - git_diff *diff) -{ - size_t i, deltas; - size_t total_insertions = 0, total_deletions = 0; - git_diff_stats *stats = NULL; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diff); - - stats = git__calloc(1, sizeof(git_diff_stats)); - GIT_ERROR_CHECK_ALLOC(stats); - - deltas = git_diff_num_deltas(diff); - - stats->filestats = git__calloc(deltas, sizeof(diff_file_stats)); - if (!stats->filestats) { - git__free(stats); - return -1; - } - - stats->diff = diff; - GIT_REFCOUNT_INC(diff); - - for (i = 0; i < deltas && !error; ++i) { - git_patch *patch = NULL; - size_t add = 0, remove = 0, namelen; - const git_diff_delta *delta; - - if ((error = git_patch_from_diff(&patch, diff, i)) < 0) - break; - - /* keep a count of renames because it will affect formatting */ - delta = patch->delta; - - /* TODO ugh */ - namelen = strlen(delta->new_file.path); - if (delta->old_file.path && strcmp(delta->old_file.path, delta->new_file.path) != 0) { - namelen += strlen(delta->old_file.path); - stats->renames++; - } - - /* and, of course, count the line stats */ - error = git_patch_line_stats(NULL, &add, &remove, patch); - - git_patch_free(patch); - - stats->filestats[i].insertions = add; - stats->filestats[i].deletions = remove; - - total_insertions += add; - total_deletions += remove; - - if (stats->max_name < namelen) - stats->max_name = namelen; - if (stats->max_filestat < add + remove) - stats->max_filestat = add + remove; - } - - stats->files_changed = deltas; - stats->insertions = total_insertions; - stats->deletions = total_deletions; - stats->max_digits = digits_for_value(stats->max_filestat + 1); - - if (error < 0) { - git_diff_stats_free(stats); - stats = NULL; - } - - *out = stats; - return error; -} - -size_t git_diff_stats_files_changed( - const git_diff_stats *stats) -{ - GIT_ASSERT_ARG(stats); - - return stats->files_changed; -} - -size_t git_diff_stats_insertions( - const git_diff_stats *stats) -{ - GIT_ASSERT_ARG(stats); - - return stats->insertions; -} - -size_t git_diff_stats_deletions( - const git_diff_stats *stats) -{ - GIT_ASSERT_ARG(stats); - - return stats->deletions; -} - -int git_diff_stats_to_buf( - git_buf *out, - const git_diff_stats *stats, - git_diff_stats_format_t format, - size_t width) -{ - GIT_BUF_WRAP_PRIVATE(out, git_diff__stats_to_buf, stats, format, width); -} - -int git_diff__stats_to_buf( - git_str *out, - const git_diff_stats *stats, - git_diff_stats_format_t format, - size_t width) -{ - int error = 0; - size_t i; - const git_diff_delta *delta; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(stats); - - if (format & GIT_DIFF_STATS_NUMBER) { - for (i = 0; i < stats->files_changed; ++i) { - if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) - continue; - - error = diff_file_stats_number_to_buf( - out, delta, &stats->filestats[i]); - if (error < 0) - return error; - } - } - - if (format & GIT_DIFF_STATS_FULL) { - if (width > 0) { - if (width > stats->max_name + stats->max_digits + 5) - width -= (stats->max_name + stats->max_digits + 5); - if (width < STATS_FULL_MIN_SCALE) - width = STATS_FULL_MIN_SCALE; - } - if (width > stats->max_filestat) - width = 0; - - for (i = 0; i < stats->files_changed; ++i) { - if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) - continue; - - error = diff_file_stats_full_to_buf( - out, delta, &stats->filestats[i], stats, width); - if (error < 0) - return error; - } - } - - if (format & GIT_DIFF_STATS_FULL || format & GIT_DIFF_STATS_SHORT) { - git_str_printf( - out, " %" PRIuZ " file%s changed", - stats->files_changed, stats->files_changed != 1 ? "s" : ""); - - if (stats->insertions || stats->deletions == 0) - git_str_printf( - out, ", %" PRIuZ " insertion%s(+)", - stats->insertions, stats->insertions != 1 ? "s" : ""); - - if (stats->deletions || stats->insertions == 0) - git_str_printf( - out, ", %" PRIuZ " deletion%s(-)", - stats->deletions, stats->deletions != 1 ? "s" : ""); - - git_str_putc(out, '\n'); - - if (git_str_oom(out)) - return -1; - } - - if (format & GIT_DIFF_STATS_INCLUDE_SUMMARY) { - for (i = 0; i < stats->files_changed; ++i) { - if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) - continue; - - error = diff_file_stats_summary_to_buf(out, delta); - if (error < 0) - return error; - } - } - - return error; -} - -void git_diff_stats_free(git_diff_stats *stats) -{ - if (stats == NULL) - return; - - git_diff_free(stats->diff); /* bumped refcount in constructor */ - git__free(stats->filestats); - git__free(stats); -} diff --git a/src/diff_stats.h b/src/diff_stats.h deleted file mode 100644 index c71862b4e..000000000 --- a/src/diff_stats.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_diff_stats_h__ -#define INCLUDE_diff_stats_h__ - -#include "common.h" - -int git_diff__stats_to_buf( - git_str *out, - const git_diff_stats *stats, - git_diff_stats_format_t format, - size_t width); - -#endif diff --git a/src/diff_tform.c b/src/diff_tform.c deleted file mode 100644 index 913d649b0..000000000 --- a/src/diff_tform.c +++ /dev/null @@ -1,1121 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "diff_tform.h" - -#include "git2/config.h" -#include "git2/blob.h" -#include "git2/sys/hashsig.h" - -#include "diff.h" -#include "diff_generate.h" -#include "fs_path.h" -#include "futils.h" -#include "config.h" - -git_diff_delta *git_diff__delta_dup( - const git_diff_delta *d, git_pool *pool) -{ - git_diff_delta *delta = git__malloc(sizeof(git_diff_delta)); - if (!delta) - return NULL; - - memcpy(delta, d, sizeof(git_diff_delta)); - GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags); - - if (d->old_file.path != NULL) { - delta->old_file.path = git_pool_strdup(pool, d->old_file.path); - if (delta->old_file.path == NULL) - goto fail; - } - - if (d->new_file.path != d->old_file.path && d->new_file.path != NULL) { - delta->new_file.path = git_pool_strdup(pool, d->new_file.path); - if (delta->new_file.path == NULL) - goto fail; - } else { - delta->new_file.path = delta->old_file.path; - } - - return delta; - -fail: - git__free(delta); - return NULL; -} - -git_diff_delta *git_diff__merge_like_cgit( - const git_diff_delta *a, - const git_diff_delta *b, - git_pool *pool) -{ - git_diff_delta *dup; - - /* Emulate C git for merging two diffs (a la 'git diff '). - * - * When C git does a diff between the work dir and a tree, it actually - * diffs with the index but uses the workdir contents. This emulates - * those choices so we can emulate the type of diff. - * - * We have three file descriptions here, let's call them: - * f1 = a->old_file - * f2 = a->new_file AND b->old_file - * f3 = b->new_file - */ - - /* If one of the diffs is a conflict, just dup it */ - if (b->status == GIT_DELTA_CONFLICTED) - return git_diff__delta_dup(b, pool); - if (a->status == GIT_DELTA_CONFLICTED) - return git_diff__delta_dup(a, pool); - - /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */ - if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED) - return git_diff__delta_dup(a, pool); - - /* otherwise, base this diff on the 'b' diff */ - if ((dup = git_diff__delta_dup(b, pool)) == NULL) - return NULL; - - /* If 'a' status is uninteresting, then we're done */ - if (a->status == GIT_DELTA_UNMODIFIED || - a->status == GIT_DELTA_UNTRACKED || - a->status == GIT_DELTA_UNREADABLE) - return dup; - - GIT_ASSERT_WITH_RETVAL(b->status != GIT_DELTA_UNMODIFIED, NULL); - - /* A cgit exception is that the diff of a file that is only in the - * index (i.e. not in HEAD nor workdir) is given as empty. - */ - if (dup->status == GIT_DELTA_DELETED) { - if (a->status == GIT_DELTA_ADDED) { - dup->status = GIT_DELTA_UNMODIFIED; - dup->nfiles = 2; - } - /* else don't overwrite DELETE status */ - } else { - dup->status = a->status; - dup->nfiles = a->nfiles; - } - - git_oid_cpy(&dup->old_file.id, &a->old_file.id); - dup->old_file.mode = a->old_file.mode; - dup->old_file.size = a->old_file.size; - dup->old_file.flags = a->old_file.flags; - - return dup; -} - -int git_diff__merge( - git_diff *onto, const git_diff *from, git_diff__merge_cb cb) -{ - int error = 0; - git_pool onto_pool; - git_vector onto_new; - git_diff_delta *delta; - bool ignore_case, reversed; - unsigned int i, j; - - GIT_ASSERT_ARG(onto); - GIT_ASSERT_ARG(from); - - if (!from->deltas.length) - return 0; - - ignore_case = ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0); - reversed = ((onto->opts.flags & GIT_DIFF_REVERSE) != 0); - - if (ignore_case != ((from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0) || - reversed != ((from->opts.flags & GIT_DIFF_REVERSE) != 0)) { - git_error_set(GIT_ERROR_INVALID, - "attempt to merge diffs created with conflicting options"); - return -1; - } - - if (git_vector_init(&onto_new, onto->deltas.length, git_diff_delta__cmp) < 0 || - git_pool_init(&onto_pool, 1) < 0) - return -1; - - for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) { - git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i); - const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j); - int cmp = !f ? -1 : !o ? 1 : - STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path); - - if (cmp < 0) { - delta = git_diff__delta_dup(o, &onto_pool); - i++; - } else if (cmp > 0) { - delta = git_diff__delta_dup(f, &onto_pool); - j++; - } else { - const git_diff_delta *left = reversed ? f : o; - const git_diff_delta *right = reversed ? o : f; - - delta = cb(left, right, &onto_pool); - i++; - j++; - } - - /* the ignore rules for the target may not match the source - * or the result of a merged delta could be skippable... - */ - if (delta && git_diff_delta__should_skip(&onto->opts, delta)) { - git__free(delta); - continue; - } - - if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0) - break; - } - - if (!error) { - git_vector_swap(&onto->deltas, &onto_new); - git_pool_swap(&onto->pool, &onto_pool); - - if ((onto->opts.flags & GIT_DIFF_REVERSE) != 0) - onto->old_src = from->old_src; - else - onto->new_src = from->new_src; - - /* prefix strings also come from old pool, so recreate those.*/ - onto->opts.old_prefix = - git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix); - onto->opts.new_prefix = - git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix); - } - - git_vector_free_deep(&onto_new); - git_pool_clear(&onto_pool); - - return error; -} - -int git_diff_merge(git_diff *onto, const git_diff *from) -{ - return git_diff__merge(onto, from, git_diff__merge_like_cgit); -} - -int git_diff_find_similar__hashsig_for_file( - void **out, const git_diff_file *f, const char *path, void *p) -{ - git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p; - - GIT_UNUSED(f); - return git_hashsig_create_fromfile((git_hashsig **)out, path, opt); -} - -int git_diff_find_similar__hashsig_for_buf( - void **out, const git_diff_file *f, const char *buf, size_t len, void *p) -{ - git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p; - - GIT_UNUSED(f); - return git_hashsig_create((git_hashsig **)out, buf, len, opt); -} - -void git_diff_find_similar__hashsig_free(void *sig, void *payload) -{ - GIT_UNUSED(payload); - git_hashsig_free(sig); -} - -int git_diff_find_similar__calc_similarity( - int *score, void *siga, void *sigb, void *payload) -{ - int error; - - GIT_UNUSED(payload); - error = git_hashsig_compare(siga, sigb); - if (error < 0) - return error; - - *score = error; - return 0; -} - -#define DEFAULT_THRESHOLD 50 -#define DEFAULT_BREAK_REWRITE_THRESHOLD 60 -#define DEFAULT_RENAME_LIMIT 1000 - -static int normalize_find_opts( - git_diff *diff, - git_diff_find_options *opts, - const git_diff_find_options *given) -{ - git_config *cfg = NULL; - git_hashsig_option_t hashsig_opts; - - GIT_ERROR_CHECK_VERSION(given, GIT_DIFF_FIND_OPTIONS_VERSION, "git_diff_find_options"); - - if (diff->repo != NULL && - git_repository_config__weakptr(&cfg, diff->repo) < 0) - return -1; - - if (given) - memcpy(opts, given, sizeof(*opts)); - - if (!given || - (given->flags & GIT_DIFF_FIND_ALL) == GIT_DIFF_FIND_BY_CONFIG) - { - if (cfg) { - char *rule = - git_config__get_string_force(cfg, "diff.renames", "true"); - int boolval; - - if (!git__parse_bool(&boolval, rule) && !boolval) - /* don't set FIND_RENAMES if bool value is false */; - else if (!strcasecmp(rule, "copies") || !strcasecmp(rule, "copy")) - opts->flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; - else - opts->flags |= GIT_DIFF_FIND_RENAMES; - - git__free(rule); - } else { - /* set default flag */ - opts->flags |= GIT_DIFF_FIND_RENAMES; - } - } - - /* some flags imply others */ - - if (opts->flags & GIT_DIFF_FIND_EXACT_MATCH_ONLY) { - /* if we are only looking for exact matches, then don't turn - * MODIFIED items into ADD/DELETE pairs because it's too picky - */ - opts->flags &= ~(GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES); - - /* similarly, don't look for self-rewrites to split */ - opts->flags &= ~GIT_DIFF_FIND_RENAMES_FROM_REWRITES; - } - - if (opts->flags & GIT_DIFF_FIND_RENAMES_FROM_REWRITES) - opts->flags |= GIT_DIFF_FIND_RENAMES; - - if (opts->flags & GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED) - opts->flags |= GIT_DIFF_FIND_COPIES; - - if (opts->flags & GIT_DIFF_BREAK_REWRITES) - opts->flags |= GIT_DIFF_FIND_REWRITES; - -#define USE_DEFAULT(X) ((X) == 0 || (X) > 100) - - if (USE_DEFAULT(opts->rename_threshold)) - opts->rename_threshold = DEFAULT_THRESHOLD; - - if (USE_DEFAULT(opts->rename_from_rewrite_threshold)) - opts->rename_from_rewrite_threshold = DEFAULT_THRESHOLD; - - if (USE_DEFAULT(opts->copy_threshold)) - opts->copy_threshold = DEFAULT_THRESHOLD; - - if (USE_DEFAULT(opts->break_rewrite_threshold)) - opts->break_rewrite_threshold = DEFAULT_BREAK_REWRITE_THRESHOLD; - -#undef USE_DEFAULT - - if (!opts->rename_limit) { - if (cfg) { - opts->rename_limit = git_config__get_int_force( - cfg, "diff.renamelimit", DEFAULT_RENAME_LIMIT); - } - - if (opts->rename_limit <= 0) - opts->rename_limit = DEFAULT_RENAME_LIMIT; - } - - /* assign the internal metric with whitespace flag as payload */ - if (!opts->metric) { - opts->metric = git__malloc(sizeof(git_diff_similarity_metric)); - GIT_ERROR_CHECK_ALLOC(opts->metric); - - opts->metric->file_signature = git_diff_find_similar__hashsig_for_file; - opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf; - opts->metric->free_signature = git_diff_find_similar__hashsig_free; - opts->metric->similarity = git_diff_find_similar__calc_similarity; - - if (opts->flags & GIT_DIFF_FIND_IGNORE_WHITESPACE) - hashsig_opts = GIT_HASHSIG_IGNORE_WHITESPACE; - else if (opts->flags & GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE) - hashsig_opts = GIT_HASHSIG_NORMAL; - else - hashsig_opts = GIT_HASHSIG_SMART_WHITESPACE; - hashsig_opts |= GIT_HASHSIG_ALLOW_SMALL_FILES; - opts->metric->payload = (void *)hashsig_opts; - } - - return 0; -} - -static int insert_delete_side_of_split( - git_diff *diff, git_vector *onto, const git_diff_delta *delta) -{ - /* make new record for DELETED side of split */ - git_diff_delta *deleted = git_diff__delta_dup(delta, &diff->pool); - GIT_ERROR_CHECK_ALLOC(deleted); - - deleted->status = GIT_DELTA_DELETED; - deleted->nfiles = 1; - memset(&deleted->new_file, 0, sizeof(deleted->new_file)); - deleted->new_file.path = deleted->old_file.path; - deleted->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - return git_vector_insert(onto, deleted); -} - -static int apply_splits_and_deletes( - git_diff *diff, size_t expected_size, bool actually_split) -{ - git_vector onto = GIT_VECTOR_INIT; - size_t i; - git_diff_delta *delta; - - if (git_vector_init(&onto, expected_size, diff->deltas._cmp) < 0) - return -1; - - /* build new delta list without TO_DELETE and splitting TO_SPLIT */ - git_vector_foreach(&diff->deltas, i, delta) { - if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0) - continue; - - if ((delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0 && actually_split) { - delta->similarity = 0; - - if (insert_delete_side_of_split(diff, &onto, delta) < 0) - goto on_error; - - if (diff->new_src == GIT_ITERATOR_WORKDIR) - delta->status = GIT_DELTA_UNTRACKED; - else - delta->status = GIT_DELTA_ADDED; - delta->nfiles = 1; - memset(&delta->old_file, 0, sizeof(delta->old_file)); - delta->old_file.path = delta->new_file.path; - delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; - } - - /* clean up delta before inserting into new list */ - GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags); - - if (delta->status != GIT_DELTA_COPIED && - delta->status != GIT_DELTA_RENAMED && - (delta->status != GIT_DELTA_MODIFIED || actually_split)) - delta->similarity = 0; - - /* insert into new list */ - if (git_vector_insert(&onto, delta) < 0) - goto on_error; - } - - /* cannot return an error past this point */ - - /* free deltas from old list that didn't make it to the new one */ - git_vector_foreach(&diff->deltas, i, delta) { - if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0) - git__free(delta); - } - - /* swap new delta list into place */ - git_vector_swap(&diff->deltas, &onto); - git_vector_free(&onto); - git_vector_sort(&diff->deltas); - - return 0; - -on_error: - git_vector_free_deep(&onto); - - return -1; -} - -GIT_INLINE(git_diff_file *) similarity_get_file(git_diff *diff, size_t idx) -{ - git_diff_delta *delta = git_vector_get(&diff->deltas, idx / 2); - return (idx & 1) ? &delta->new_file : &delta->old_file; -} - -typedef struct { - size_t idx; - git_iterator_t src; - git_repository *repo; - git_diff_file *file; - git_str data; - git_odb_object *odb_obj; - git_blob *blob; -} similarity_info; - -static int similarity_init( - similarity_info *info, git_diff *diff, size_t file_idx) -{ - info->idx = file_idx; - info->src = (file_idx & 1) ? diff->new_src : diff->old_src; - info->repo = diff->repo; - info->file = similarity_get_file(diff, file_idx); - info->odb_obj = NULL; - info->blob = NULL; - git_str_init(&info->data, 0); - - if ((info->file->flags & GIT_DIFF_FLAG_VALID_SIZE) || - info->src == GIT_ITERATOR_WORKDIR) - return 0; - - return git_diff_file__resolve_zero_size( - info->file, &info->odb_obj, info->repo); -} - -static int similarity_sig( - similarity_info *info, - const git_diff_find_options *opts, - void **cache) -{ - int error = 0; - git_diff_file *file = info->file; - - if (info->src == GIT_ITERATOR_WORKDIR) { - if ((error = git_repository_workdir_path( - &info->data, info->repo, file->path)) < 0) - return error; - - /* if path is not a regular file, just skip this item */ - if (!git_fs_path_isfile(info->data.ptr)) - return 0; - - /* TODO: apply wd-to-odb filters to file data if necessary */ - - error = opts->metric->file_signature( - &cache[info->idx], info->file, - info->data.ptr, opts->metric->payload); - } else { - /* if we didn't initially know the size, we might have an odb_obj - * around from earlier, so convert that, otherwise load the blob now - */ - if (info->odb_obj != NULL) - error = git_object__from_odb_object( - (git_object **)&info->blob, info->repo, - info->odb_obj, GIT_OBJECT_BLOB); - else - error = git_blob_lookup(&info->blob, info->repo, &file->id); - - if (error < 0) { - /* if lookup fails, just skip this item in similarity calc */ - git_error_clear(); - } else { - size_t sz; - - /* index size may not be actual blob size if filtered */ - if (file->size != git_blob_rawsize(info->blob)) - file->size = git_blob_rawsize(info->blob); - - sz = git__is_sizet(file->size) ? (size_t)file->size : (size_t)-1; - - error = opts->metric->buffer_signature( - &cache[info->idx], info->file, - git_blob_rawcontent(info->blob), sz, opts->metric->payload); - } - } - - return error; -} - -static void similarity_unload(similarity_info *info) -{ - if (info->odb_obj) - git_odb_object_free(info->odb_obj); - - if (info->blob) - git_blob_free(info->blob); - else - git_str_dispose(&info->data); -} - -#define FLAG_SET(opts,flag_name) (((opts)->flags & flag_name) != 0) - -/* - score < 0 means files cannot be compared - * - score >= 100 means files are exact match - * - score == 0 means files are completely different - */ -static int similarity_measure( - int *score, - git_diff *diff, - const git_diff_find_options *opts, - void **cache, - size_t a_idx, - size_t b_idx) -{ - git_diff_file *a_file = similarity_get_file(diff, a_idx); - git_diff_file *b_file = similarity_get_file(diff, b_idx); - bool exact_match = FLAG_SET(opts, GIT_DIFF_FIND_EXACT_MATCH_ONLY); - int error = 0; - similarity_info a_info, b_info; - - *score = -1; - - /* don't try to compare things that aren't files */ - if (!GIT_MODE_ISBLOB(a_file->mode) || !GIT_MODE_ISBLOB(b_file->mode)) - return 0; - - /* if exact match is requested, force calculation of missing OIDs now */ - if (exact_match) { - if (git_oid_is_zero(&a_file->id) && - diff->old_src == GIT_ITERATOR_WORKDIR && - !git_diff__oid_for_file(&a_file->id, - diff, a_file->path, a_file->mode, a_file->size)) - a_file->flags |= GIT_DIFF_FLAG_VALID_ID; - - if (git_oid_is_zero(&b_file->id) && - diff->new_src == GIT_ITERATOR_WORKDIR && - !git_diff__oid_for_file(&b_file->id, - diff, b_file->path, b_file->mode, b_file->size)) - b_file->flags |= GIT_DIFF_FLAG_VALID_ID; - } - - /* check OID match as a quick test */ - if (git_oid__cmp(&a_file->id, &b_file->id) == 0) { - *score = 100; - return 0; - } - - /* don't calculate signatures if we are doing exact match */ - if (exact_match) { - *score = 0; - return 0; - } - - memset(&a_info, 0, sizeof(a_info)); - memset(&b_info, 0, sizeof(b_info)); - - /* set up similarity data (will try to update missing file sizes) */ - if (!cache[a_idx] && (error = similarity_init(&a_info, diff, a_idx)) < 0) - return error; - if (!cache[b_idx] && (error = similarity_init(&b_info, diff, b_idx)) < 0) - goto cleanup; - - /* check if file sizes are nowhere near each other */ - if (a_file->size > 127 && - b_file->size > 127 && - (a_file->size > (b_file->size << 3) || - b_file->size > (a_file->size << 3))) - goto cleanup; - - /* update signature cache if needed */ - if (!cache[a_idx]) { - if ((error = similarity_sig(&a_info, opts, cache)) < 0) - goto cleanup; - } - if (!cache[b_idx]) { - if ((error = similarity_sig(&b_info, opts, cache)) < 0) - goto cleanup; - } - - /* calculate similarity provided that the metric choose to process - * both the a and b files (some may not if file is too big, etc). - */ - if (cache[a_idx] && cache[b_idx]) - error = opts->metric->similarity( - score, cache[a_idx], cache[b_idx], opts->metric->payload); - -cleanup: - similarity_unload(&a_info); - similarity_unload(&b_info); - - return error; -} - -static int calc_self_similarity( - git_diff *diff, - const git_diff_find_options *opts, - size_t delta_idx, - void **cache) -{ - int error, similarity = -1; - git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); - - if ((delta->flags & GIT_DIFF_FLAG__HAS_SELF_SIMILARITY) != 0) - return 0; - - error = similarity_measure( - &similarity, diff, opts, cache, 2 * delta_idx, 2 * delta_idx + 1); - if (error < 0) - return error; - - if (similarity >= 0) { - delta->similarity = (uint16_t)similarity; - delta->flags |= GIT_DIFF_FLAG__HAS_SELF_SIMILARITY; - } - - return 0; -} - -static bool is_rename_target( - git_diff *diff, - const git_diff_find_options *opts, - size_t delta_idx, - void **cache) -{ - git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); - - /* skip things that aren't plain blobs */ - if (!GIT_MODE_ISBLOB(delta->new_file.mode)) - return false; - - /* only consider ADDED, RENAMED, COPIED, and split MODIFIED as - * targets; maybe include UNTRACKED if requested. - */ - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: - case GIT_DELTA_DELETED: - case GIT_DELTA_IGNORED: - case GIT_DELTA_CONFLICTED: - return false; - - case GIT_DELTA_MODIFIED: - if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) && - !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES)) - return false; - - if (calc_self_similarity(diff, opts, delta_idx, cache) < 0) - return false; - - if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) && - delta->similarity < opts->break_rewrite_threshold) { - delta->flags |= GIT_DIFF_FLAG__TO_SPLIT; - break; - } - if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) && - delta->similarity < opts->rename_from_rewrite_threshold) { - delta->flags |= GIT_DIFF_FLAG__TO_SPLIT; - break; - } - - return false; - - case GIT_DELTA_UNTRACKED: - if (!FLAG_SET(opts, GIT_DIFF_FIND_FOR_UNTRACKED)) - return false; - break; - - default: /* all other status values should be checked */ - break; - } - - delta->flags |= GIT_DIFF_FLAG__IS_RENAME_TARGET; - return true; -} - -static bool is_rename_source( - git_diff *diff, - const git_diff_find_options *opts, - size_t delta_idx, - void **cache) -{ - git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); - - /* skip things that aren't blobs */ - if (!GIT_MODE_ISBLOB(delta->old_file.mode)) - return false; - - switch (delta->status) { - case GIT_DELTA_ADDED: - case GIT_DELTA_UNTRACKED: - case GIT_DELTA_UNREADABLE: - case GIT_DELTA_IGNORED: - case GIT_DELTA_CONFLICTED: - return false; - - case GIT_DELTA_DELETED: - case GIT_DELTA_TYPECHANGE: - break; - - case GIT_DELTA_UNMODIFIED: - if (!FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED)) - return false; - if (FLAG_SET(opts, GIT_DIFF_FIND_REMOVE_UNMODIFIED)) - delta->flags |= GIT_DIFF_FLAG__TO_DELETE; - break; - - default: /* MODIFIED, RENAMED, COPIED */ - /* if we're finding copies, this could be a source */ - if (FLAG_SET(opts, GIT_DIFF_FIND_COPIES)) - break; - - /* otherwise, this is only a source if we can split it */ - if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) && - !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES)) - return false; - - if (calc_self_similarity(diff, opts, delta_idx, cache) < 0) - return false; - - if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) && - delta->similarity < opts->break_rewrite_threshold) { - delta->flags |= GIT_DIFF_FLAG__TO_SPLIT; - break; - } - - if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) && - delta->similarity < opts->rename_from_rewrite_threshold) - break; - - return false; - } - - delta->flags |= GIT_DIFF_FLAG__IS_RENAME_SOURCE; - return true; -} - -GIT_INLINE(bool) delta_is_split(git_diff_delta *delta) -{ - return (delta->status == GIT_DELTA_TYPECHANGE || - (delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0); -} - -GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta) -{ - return (delta->status == GIT_DELTA_ADDED || - delta->status == GIT_DELTA_UNTRACKED || - delta->status == GIT_DELTA_UNREADABLE || - delta->status == GIT_DELTA_IGNORED); -} - -GIT_INLINE(void) delta_make_rename( - git_diff_delta *to, const git_diff_delta *from, uint16_t similarity) -{ - to->status = GIT_DELTA_RENAMED; - to->similarity = similarity; - to->nfiles = 2; - memcpy(&to->old_file, &from->old_file, sizeof(to->old_file)); - to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; -} - -typedef struct { - size_t idx; - uint16_t similarity; -} diff_find_match; - -int git_diff_find_similar( - git_diff *diff, - const git_diff_find_options *given_opts) -{ - size_t s, t; - int error = 0, result; - uint16_t similarity; - git_diff_delta *src, *tgt; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - size_t num_deltas, num_srcs = 0, num_tgts = 0; - size_t tried_srcs = 0, tried_tgts = 0; - size_t num_rewrites = 0, num_updates = 0, num_bumped = 0; - size_t sigcache_size; - void **sigcache = NULL; /* cache of similarity metric file signatures */ - diff_find_match *tgt2src = NULL; - diff_find_match *src2tgt = NULL; - diff_find_match *tgt2src_copy = NULL; - diff_find_match *best_match; - git_diff_file swap; - - GIT_ASSERT_ARG(diff); - - if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0) - return error; - - num_deltas = diff->deltas.length; - - /* TODO: maybe abort if deltas.length > rename_limit ??? */ - if (!num_deltas || !git__is_uint32(num_deltas)) - goto cleanup; - - /* No flags set; nothing to do */ - if ((opts.flags & GIT_DIFF_FIND_ALL) == 0) - goto cleanup; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&sigcache_size, num_deltas, 2); - sigcache = git__calloc(sigcache_size, sizeof(void *)); - GIT_ERROR_CHECK_ALLOC(sigcache); - - /* Label rename sources and targets - * - * This will also set self-similarity scores for MODIFIED files and - * mark them for splitting if break-rewrites is enabled - */ - git_vector_foreach(&diff->deltas, t, tgt) { - if (is_rename_source(diff, &opts, t, sigcache)) - ++num_srcs; - - if (is_rename_target(diff, &opts, t, sigcache)) - ++num_tgts; - - if ((tgt->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) - num_rewrites++; - } - - /* if there are no candidate srcs or tgts, we're done */ - if (!num_srcs || !num_tgts) - goto cleanup; - - src2tgt = git__calloc(num_deltas, sizeof(diff_find_match)); - GIT_ERROR_CHECK_ALLOC(src2tgt); - tgt2src = git__calloc(num_deltas, sizeof(diff_find_match)); - GIT_ERROR_CHECK_ALLOC(tgt2src); - - if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) { - tgt2src_copy = git__calloc(num_deltas, sizeof(diff_find_match)); - GIT_ERROR_CHECK_ALLOC(tgt2src_copy); - } - - /* - * Find best-fit matches for rename / copy candidates - */ - -find_best_matches: - tried_tgts = num_bumped = 0; - - git_vector_foreach(&diff->deltas, t, tgt) { - /* skip things that are not rename targets */ - if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) - continue; - - tried_srcs = 0; - - git_vector_foreach(&diff->deltas, s, src) { - /* skip things that are not rename sources */ - if ((src->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0) - continue; - - /* calculate similarity for this pair and find best match */ - if (s == t) - result = -1; /* don't measure self-similarity here */ - else if ((error = similarity_measure( - &result, diff, &opts, sigcache, 2 * s, 2 * t + 1)) < 0) - goto cleanup; - - if (result < 0) - continue; - similarity = (uint16_t)result; - - /* is this a better rename? */ - if (tgt2src[t].similarity < similarity && - src2tgt[s].similarity < similarity) - { - /* eject old mapping */ - if (src2tgt[s].similarity > 0) { - tgt2src[src2tgt[s].idx].similarity = 0; - num_bumped++; - } - if (tgt2src[t].similarity > 0) { - src2tgt[tgt2src[t].idx].similarity = 0; - num_bumped++; - } - - /* write new mapping */ - tgt2src[t].idx = s; - tgt2src[t].similarity = similarity; - src2tgt[s].idx = t; - src2tgt[s].similarity = similarity; - } - - /* keep best absolute match for copies */ - if (tgt2src_copy != NULL && - tgt2src_copy[t].similarity < similarity) - { - tgt2src_copy[t].idx = s; - tgt2src_copy[t].similarity = similarity; - } - - if (++tried_srcs >= num_srcs) - break; - - /* cap on maximum targets we'll examine (per "tgt" file) */ - if (tried_srcs > opts.rename_limit) - break; - } - - if (++tried_tgts >= num_tgts) - break; - } - - if (num_bumped > 0) /* try again if we bumped some items */ - goto find_best_matches; - - /* - * Rewrite the diffs with renames / copies - */ - - git_vector_foreach(&diff->deltas, t, tgt) { - /* skip things that are not rename targets */ - if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) - continue; - - /* check if this delta was the target of a similarity */ - if (tgt2src[t].similarity) - best_match = &tgt2src[t]; - else if (tgt2src_copy && tgt2src_copy[t].similarity) - best_match = &tgt2src_copy[t]; - else - continue; - - s = best_match->idx; - src = GIT_VECTOR_GET(&diff->deltas, s); - - /* possible scenarios: - * 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME - * 2. from DELETE to SPLIT/TYPECHANGE = RENAME + DELETE - * 3. from SPLIT/TYPECHANGE to ADD/UNTRACK/IGNORE = ADD + RENAME - * 4. from SPLIT/TYPECHANGE to SPLIT/TYPECHANGE = RENAME + SPLIT - * 5. from OTHER to ADD/UNTRACK/IGNORE = OTHER + COPY - */ - - if (src->status == GIT_DELTA_DELETED) { - - if (delta_is_new_only(tgt)) { - - if (best_match->similarity < opts.rename_threshold) - continue; - - delta_make_rename(tgt, src, best_match->similarity); - - src->flags |= GIT_DIFF_FLAG__TO_DELETE; - num_rewrites++; - } else { - GIT_ASSERT(delta_is_split(tgt)); - - if (best_match->similarity < opts.rename_from_rewrite_threshold) - continue; - - memcpy(&swap, &tgt->old_file, sizeof(swap)); - - delta_make_rename(tgt, src, best_match->similarity); - num_rewrites--; - - GIT_ASSERT(src->status == GIT_DELTA_DELETED); - memcpy(&src->old_file, &swap, sizeof(src->old_file)); - memset(&src->new_file, 0, sizeof(src->new_file)); - src->new_file.path = src->old_file.path; - src->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - num_updates++; - - if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { - /* what used to be at src t is now at src s */ - tgt2src[src2tgt[t].idx].idx = s; - } - } - } - - else if (delta_is_split(src)) { - - if (delta_is_new_only(tgt)) { - - if (best_match->similarity < opts.rename_threshold) - continue; - - delta_make_rename(tgt, src, best_match->similarity); - - src->status = (diff->new_src == GIT_ITERATOR_WORKDIR) ? - GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED; - src->nfiles = 1; - memset(&src->old_file, 0, sizeof(src->old_file)); - src->old_file.path = src->new_file.path; - src->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; - num_rewrites--; - - num_updates++; - } else { - GIT_ASSERT(delta_is_split(src)); - - if (best_match->similarity < opts.rename_from_rewrite_threshold) - continue; - - memcpy(&swap, &tgt->old_file, sizeof(swap)); - - delta_make_rename(tgt, src, best_match->similarity); - num_rewrites--; - num_updates++; - - memcpy(&src->old_file, &swap, sizeof(src->old_file)); - - /* if we've just swapped the new element into the correct - * place, clear the SPLIT and RENAME_TARGET flags - */ - if (tgt2src[s].idx == t && - tgt2src[s].similarity > - opts.rename_from_rewrite_threshold) { - src->status = GIT_DELTA_RENAMED; - src->similarity = tgt2src[s].similarity; - tgt2src[s].similarity = 0; - src->flags &= ~(GIT_DIFF_FLAG__TO_SPLIT | GIT_DIFF_FLAG__IS_RENAME_TARGET); - num_rewrites--; - } - /* otherwise, if we just overwrote a source, update mapping */ - else if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { - /* what used to be at src t is now at src s */ - tgt2src[src2tgt[t].idx].idx = s; - } - - num_updates++; - } - } - - else if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) { - if (tgt2src_copy[t].similarity < opts.copy_threshold) - continue; - - /* always use best possible source for copy */ - best_match = &tgt2src_copy[t]; - src = GIT_VECTOR_GET(&diff->deltas, best_match->idx); - - if (delta_is_split(tgt)) { - error = insert_delete_side_of_split(diff, &diff->deltas, tgt); - if (error < 0) - goto cleanup; - num_rewrites--; - } - - if (!delta_is_split(tgt) && !delta_is_new_only(tgt)) - continue; - - tgt->status = GIT_DELTA_COPIED; - tgt->similarity = best_match->similarity; - tgt->nfiles = 2; - memcpy(&tgt->old_file, &src->old_file, sizeof(tgt->old_file)); - tgt->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; - - num_updates++; - } - } - - /* - * Actually split and delete entries as needed - */ - - if (num_rewrites > 0 || num_updates > 0) - error = apply_splits_and_deletes( - diff, diff->deltas.length - num_rewrites, - FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES) && - !FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY)); - -cleanup: - git__free(tgt2src); - git__free(src2tgt); - git__free(tgt2src_copy); - - if (sigcache) { - for (t = 0; t < num_deltas * 2; ++t) { - if (sigcache[t] != NULL) - opts.metric->free_signature(sigcache[t], opts.metric->payload); - } - git__free(sigcache); - } - - if (!given_opts || !given_opts->metric) - git__free(opts.metric); - - return error; -} - -#undef FLAG_SET diff --git a/src/diff_tform.h b/src/diff_tform.h deleted file mode 100644 index 7abb8b3fe..000000000 --- a/src/diff_tform.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_diff_tform_h__ -#define INCLUDE_diff_tform_h__ - -#include "common.h" - -#include "diff_file.h" - -extern int git_diff_find_similar__hashsig_for_file( - void **out, const git_diff_file *f, const char *path, void *p); - -extern int git_diff_find_similar__hashsig_for_buf( - void **out, const git_diff_file *f, const char *buf, size_t len, void *p); - -extern void git_diff_find_similar__hashsig_free(void *sig, void *payload); - -extern int git_diff_find_similar__calc_similarity( - int *score, void *siga, void *sigb, void *payload); - -#endif diff --git a/src/diff_xdiff.c b/src/diff_xdiff.c deleted file mode 100644 index 3f6eccac1..000000000 --- a/src/diff_xdiff.c +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "diff_xdiff.h" - -#include "git2/errors.h" -#include "diff.h" -#include "diff_driver.h" -#include "patch_generate.h" - -static int git_xdiff_scan_int(const char **str, int *value) -{ - const char *scan = *str; - int v = 0, digits = 0; - /* find next digit */ - for (scan = *str; *scan && !git__isdigit(*scan); scan++); - /* parse next number */ - for (; git__isdigit(*scan); scan++, digits++) - v = (v * 10) + (*scan - '0'); - *str = scan; - *value = v; - return (digits > 0) ? 0 : -1; -} - -static int git_xdiff_parse_hunk(git_diff_hunk *hunk, const char *header) -{ - /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ - if (*header != '@') - goto fail; - if (git_xdiff_scan_int(&header, &hunk->old_start) < 0) - goto fail; - if (*header == ',') { - if (git_xdiff_scan_int(&header, &hunk->old_lines) < 0) - goto fail; - } else - hunk->old_lines = 1; - if (git_xdiff_scan_int(&header, &hunk->new_start) < 0) - goto fail; - if (*header == ',') { - if (git_xdiff_scan_int(&header, &hunk->new_lines) < 0) - goto fail; - } else - hunk->new_lines = 1; - if (hunk->old_start < 0 || hunk->new_start < 0) - goto fail; - - return 0; - -fail: - git_error_set(GIT_ERROR_INVALID, "malformed hunk header from xdiff"); - return -1; -} - -typedef struct { - git_xdiff_output *xo; - git_patch_generated *patch; - git_diff_hunk hunk; - int old_lineno, new_lineno; - mmfile_t xd_old_data, xd_new_data; -} git_xdiff_info; - -static int diff_update_lines( - git_xdiff_info *info, - git_diff_line *line, - const char *content, - size_t content_len) -{ - const char *scan = content, *scan_end = content + content_len; - - for (line->num_lines = 0; scan < scan_end; ++scan) - if (*scan == '\n') - ++line->num_lines; - - line->content = content; - line->content_len = content_len; - - /* expect " "/"-"/"+", then data */ - switch (line->origin) { - case GIT_DIFF_LINE_ADDITION: - case GIT_DIFF_LINE_DEL_EOFNL: - line->old_lineno = -1; - line->new_lineno = info->new_lineno; - info->new_lineno += (int)line->num_lines; - break; - case GIT_DIFF_LINE_DELETION: - case GIT_DIFF_LINE_ADD_EOFNL: - line->old_lineno = info->old_lineno; - line->new_lineno = -1; - info->old_lineno += (int)line->num_lines; - break; - case GIT_DIFF_LINE_CONTEXT: - case GIT_DIFF_LINE_CONTEXT_EOFNL: - line->old_lineno = info->old_lineno; - line->new_lineno = info->new_lineno; - info->old_lineno += (int)line->num_lines; - info->new_lineno += (int)line->num_lines; - break; - default: - git_error_set(GIT_ERROR_INVALID, "unknown diff line origin %02x", - (unsigned int)line->origin); - return -1; - } - - return 0; -} - -static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) -{ - git_xdiff_info *info = priv; - git_patch_generated *patch = info->patch; - const git_diff_delta *delta = patch->base.delta; - git_patch_generated_output *output = &info->xo->output; - git_diff_line line; - size_t buffer_len; - - if (len == 1) { - output->error = git_xdiff_parse_hunk(&info->hunk, bufs[0].ptr); - if (output->error < 0) - return output->error; - - info->hunk.header_len = bufs[0].size; - if (info->hunk.header_len >= sizeof(info->hunk.header)) - info->hunk.header_len = sizeof(info->hunk.header) - 1; - - /* Sanitize the hunk header in case there is invalid Unicode */ - buffer_len = git_utf8_valid_buf_length(bufs[0].ptr, info->hunk.header_len); - /* Sanitizing the hunk header may delete the newline, so add it back again if there is room */ - if (buffer_len < info->hunk.header_len) { - bufs[0].ptr[buffer_len] = '\n'; - buffer_len += 1; - info->hunk.header_len = buffer_len; - } - - memcpy(info->hunk.header, bufs[0].ptr, info->hunk.header_len); - info->hunk.header[info->hunk.header_len] = '\0'; - - if (output->hunk_cb != NULL && - (output->error = output->hunk_cb( - delta, &info->hunk, output->payload))) - return output->error; - - info->old_lineno = info->hunk.old_start; - info->new_lineno = info->hunk.new_start; - } - - if (len == 2 || len == 3) { - /* expect " "/"-"/"+", then data */ - line.origin = - (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : - (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : - GIT_DIFF_LINE_CONTEXT; - - if (line.origin == GIT_DIFF_LINE_ADDITION) - line.content_offset = bufs[1].ptr - info->xd_new_data.ptr; - else if (line.origin == GIT_DIFF_LINE_DELETION) - line.content_offset = bufs[1].ptr - info->xd_old_data.ptr; - else - line.content_offset = -1; - - output->error = diff_update_lines( - info, &line, bufs[1].ptr, bufs[1].size); - - if (!output->error && output->data_cb != NULL) - output->error = output->data_cb( - delta, &info->hunk, &line, output->payload); - } - - if (len == 3 && !output->error) { - /* If we have a '+' and a third buf, then we have added a line - * without a newline and the old code had one, so DEL_EOFNL. - * If we have a '-' and a third buf, then we have removed a line - * with out a newline but added a blank line, so ADD_EOFNL. - */ - line.origin = - (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL : - (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL : - GIT_DIFF_LINE_CONTEXT_EOFNL; - - line.content_offset = -1; - - output->error = diff_update_lines( - info, &line, bufs[2].ptr, bufs[2].size); - - if (!output->error && output->data_cb != NULL) - output->error = output->data_cb( - delta, &info->hunk, &line, output->payload); - } - - return output->error; -} - -static int git_xdiff(git_patch_generated_output *output, git_patch_generated *patch) -{ - git_xdiff_output *xo = (git_xdiff_output *)output; - git_xdiff_info info; - git_diff_find_context_payload findctxt; - - memset(&info, 0, sizeof(info)); - info.patch = patch; - info.xo = xo; - - xo->callback.priv = &info; - - git_diff_find_context_init( - &xo->config.find_func, &findctxt, git_patch_generated_driver(patch)); - xo->config.find_func_priv = &findctxt; - - if (xo->config.find_func != NULL) - xo->config.flags |= XDL_EMIT_FUNCNAMES; - else - xo->config.flags &= ~XDL_EMIT_FUNCNAMES; - - /* TODO: check ofile.opts_flags to see if driver-specific per-file - * updates are needed to xo->params.flags - */ - - if (git_patch_generated_old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch) < 0 || - git_patch_generated_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch) < 0) - return -1; - - xdl_diff(&info.xd_old_data, &info.xd_new_data, - &xo->params, &xo->config, &xo->callback); - - git_diff_find_context_clear(&findctxt); - - return xo->output.error; -} - -void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts) -{ - uint32_t flags = opts ? opts->flags : 0; - - xo->output.diff_cb = git_xdiff; - - xo->config.ctxlen = opts ? opts->context_lines : 3; - xo->config.interhunkctxlen = opts ? opts->interhunk_lines : 0; - - if (flags & GIT_DIFF_IGNORE_WHITESPACE) - xo->params.flags |= XDF_WHITESPACE_FLAGS; - if (flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE) - xo->params.flags |= XDF_IGNORE_WHITESPACE_CHANGE; - if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL) - xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; - if (flags & GIT_DIFF_INDENT_HEURISTIC) - xo->params.flags |= XDF_INDENT_HEURISTIC; - - if (flags & GIT_DIFF_PATIENCE) - xo->params.flags |= XDF_PATIENCE_DIFF; - if (flags & GIT_DIFF_MINIMAL) - xo->params.flags |= XDF_NEED_MINIMAL; - - if (flags & GIT_DIFF_IGNORE_BLANK_LINES) - xo->params.flags |= XDF_IGNORE_BLANK_LINES; - - xo->callback.out_line = git_xdiff_cb; -} diff --git a/src/diff_xdiff.h b/src/diff_xdiff.h deleted file mode 100644 index 9b303e9dc..000000000 --- a/src/diff_xdiff.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_diff_xdiff_h__ -#define INCLUDE_diff_xdiff_h__ - -#include "common.h" - -#include "diff.h" -#include "xdiff/xdiff.h" -#include "patch_generate.h" - -/* xdiff cannot cope with large files. these files should not be passed to - * xdiff. callers should treat these large files as binary. - */ -#define GIT_XDIFF_MAX_SIZE (INT64_C(1024) * 1024 * 1023) - -/* A git_xdiff_output is a git_patch_generate_output with extra fields - * necessary to use libxdiff. Calling git_xdiff_init() will set the diff_cb - * field of the output to use xdiff to generate the diffs. - */ -typedef struct { - git_patch_generated_output output; - - xdemitconf_t config; - xpparam_t params; - xdemitcb_t callback; -} git_xdiff_output; - -void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts); - -#endif diff --git a/src/email.c b/src/email.c deleted file mode 100644 index e19a2928c..000000000 --- a/src/email.c +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "email.h" - -#include "common.h" -#include "buf.h" -#include "diff_generate.h" -#include "diff_stats.h" -#include "patch.h" -#include "date.h" - -#include "git2/email.h" -#include "git2/patch.h" -#include "git2/version.h" - -/* - * Git uses a "magic" timestamp to indicate that an email message - * is from `git format-patch` (or our equivalent). - */ -#define EMAIL_TIMESTAMP "Mon Sep 17 00:00:00 2001" - -GIT_INLINE(int) include_prefix( - size_t patch_count, - git_email_create_options *opts) -{ - return ((!opts->subject_prefix || *opts->subject_prefix) || - (opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 || - opts->reroll_number || - (patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS))); -} - -static int append_prefix( - git_str *out, - size_t patch_idx, - size_t patch_count, - git_email_create_options *opts) -{ - const char *subject_prefix = opts->subject_prefix ? - opts->subject_prefix : "PATCH"; - - git_str_putc(out, '['); - - if (*subject_prefix) - git_str_puts(out, subject_prefix); - - if (opts->reroll_number) { - if (*subject_prefix) - git_str_putc(out, ' '); - - git_str_printf(out, "v%" PRIuZ, opts->reroll_number); - } - - if ((opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 || - (patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS))) { - size_t start_number = opts->start_number ? - opts->start_number : 1; - - if (*subject_prefix || opts->reroll_number) - git_str_putc(out, ' '); - - git_str_printf(out, "%" PRIuZ "/%" PRIuZ, - patch_idx + (start_number - 1), - patch_count + (start_number - 1)); - } - - git_str_puts(out, "]"); - - return git_str_oom(out) ? -1 : 0; -} - -static int append_date( - git_str *out, - const git_time *date) -{ - int error; - - if ((error = git_str_printf(out, "Date: ")) == 0 && - (error = git_date_rfc2822_fmt(out, date->time, date->offset)) == 0) - error = git_str_putc(out, '\n'); - - return error; -} - -static int append_subject( - git_str *out, - size_t patch_idx, - size_t patch_count, - const char *summary, - git_email_create_options *opts) -{ - bool prefix = include_prefix(patch_count, opts); - size_t summary_len = summary ? strlen(summary) : 0; - int error; - - if (summary_len) { - const char *nl = strchr(summary, '\n'); - - if (nl) - summary_len = (nl - summary); - } - - if ((error = git_str_puts(out, "Subject: ")) < 0) - return error; - - if (prefix && - (error = append_prefix(out, patch_idx, patch_count, opts)) < 0) - return error; - - if (prefix && summary_len && (error = git_str_putc(out, ' ')) < 0) - return error; - - if (summary_len && - (error = git_str_put(out, summary, summary_len)) < 0) - return error; - - return git_str_putc(out, '\n'); -} - -static int append_header( - git_str *out, - size_t patch_idx, - size_t patch_count, - const git_oid *commit_id, - const char *summary, - const git_signature *author, - git_email_create_options *opts) -{ - char id[GIT_OID_HEXSZ]; - int error; - - if ((error = git_oid_fmt(id, commit_id)) < 0 || - (error = git_str_printf(out, "From %.*s %s\n", GIT_OID_HEXSZ, id, EMAIL_TIMESTAMP)) < 0 || - (error = git_str_printf(out, "From: %s <%s>\n", author->name, author->email)) < 0 || - (error = append_date(out, &author->when)) < 0 || - (error = append_subject(out, patch_idx, patch_count, summary, opts)) < 0) - return error; - - if ((error = git_str_putc(out, '\n')) < 0) - return error; - - return 0; -} - -static int append_body(git_str *out, const char *body) -{ - size_t body_len; - int error; - - if (!body) - return 0; - - body_len = strlen(body); - - if ((error = git_str_puts(out, body)) < 0) - return error; - - if (body_len && body[body_len - 1] != '\n') - error = git_str_putc(out, '\n'); - - return error; -} - -static int append_diffstat(git_str *out, git_diff *diff) -{ - git_diff_stats *stats = NULL; - unsigned int format_flags; - int error; - - format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY; - - if ((error = git_diff_get_stats(&stats, diff)) == 0 && - (error = git_diff__stats_to_buf(out, stats, format_flags, 0)) == 0) - error = git_str_putc(out, '\n'); - - git_diff_stats_free(stats); - return error; -} - -static int append_patches(git_str *out, git_diff *diff) -{ - size_t i, deltas; - int error = 0; - - deltas = git_diff_num_deltas(diff); - - for (i = 0; i < deltas; ++i) { - git_patch *patch = NULL; - - if ((error = git_patch_from_diff(&patch, diff, i)) >= 0) - error = git_patch__to_buf(out, patch); - - git_patch_free(patch); - - if (error < 0) - break; - } - - return error; -} - -int git_email__append_from_diff( - git_str *out, - git_diff *diff, - size_t patch_idx, - size_t patch_count, - const git_oid *commit_id, - const char *summary, - const char *body, - const git_signature *author, - const git_email_create_options *given_opts) -{ - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diff); - GIT_ASSERT_ARG(!patch_idx || patch_idx <= patch_count); - GIT_ASSERT_ARG(commit_id); - GIT_ASSERT_ARG(author); - - GIT_ERROR_CHECK_VERSION(given_opts, - GIT_EMAIL_CREATE_OPTIONS_VERSION, - "git_email_create_options"); - - if (given_opts) - memcpy(&opts, given_opts, sizeof(git_email_create_options)); - - if ((error = append_header(out, patch_idx, patch_count, commit_id, summary, author, &opts)) == 0 && - (error = append_body(out, body)) == 0 && - (error = git_str_puts(out, "---\n")) == 0 && - (error = append_diffstat(out, diff)) == 0 && - (error = append_patches(out, diff)) == 0) - error = git_str_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n"); - - return error; -} - -int git_email_create_from_diff( - git_buf *out, - git_diff *diff, - size_t patch_idx, - size_t patch_count, - const git_oid *commit_id, - const char *summary, - const char *body, - const git_signature *author, - const git_email_create_options *given_opts) -{ - git_str email = GIT_STR_INIT; - int error; - - git_buf_tostr(&email, out); - - error = git_email__append_from_diff(&email, diff, patch_idx, - patch_count, commit_id, summary, body, author, - given_opts); - - if (error == 0) - error = git_buf_fromstr(out, &email); - - git_str_dispose(&email); - return error; -} - -int git_email_create_from_commit( - git_buf *out, - git_commit *commit, - const git_email_create_options *given_opts) -{ - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - git_diff *diff = NULL; - git_repository *repo; - git_diff_options *diff_opts; - git_diff_find_options *find_opts; - const git_signature *author; - const char *summary, *body; - const git_oid *commit_id; - int error = -1; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(commit); - - GIT_ERROR_CHECK_VERSION(given_opts, - GIT_EMAIL_CREATE_OPTIONS_VERSION, - "git_email_create_options"); - - if (given_opts) - memcpy(&opts, given_opts, sizeof(git_email_create_options)); - - repo = git_commit_owner(commit); - author = git_commit_author(commit); - summary = git_commit_summary(commit); - body = git_commit_body(commit); - commit_id = git_commit_id(commit); - diff_opts = &opts.diff_opts; - find_opts = &opts.diff_find_opts; - - if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0) - goto done; - - if ((opts.flags & GIT_EMAIL_CREATE_NO_RENAMES) == 0 && - (error = git_diff_find_similar(diff, find_opts)) < 0) - goto done; - - error = git_email_create_from_diff(out, diff, 1, 1, commit_id, summary, body, author, &opts); - -done: - git_diff_free(diff); - return error; -} diff --git a/src/email.h b/src/email.h deleted file mode 100644 index 083e56d5c..000000000 --- a/src/email.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_email_h__ -#define INCLUDE_email_h__ - -#include "common.h" - -#include "git2/email.h" - -extern int git_email__append_from_diff( - git_str *out, - git_diff *diff, - size_t patch_idx, - size_t patch_count, - const git_oid *commit_id, - const char *summary, - const char *body, - const git_signature *author, - const git_email_create_options *given_opts); - -#endif diff --git a/src/errors.c b/src/errors.c deleted file mode 100644 index 3614b9ce5..000000000 --- a/src/errors.c +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "threadstate.h" -#include "posix.h" -#include "str.h" -#include "libgit2.h" - -/******************************************** - * New error handling - ********************************************/ - -static git_error g_git_oom_error = { - "Out of memory", - GIT_ERROR_NOMEMORY -}; - -static git_error g_git_uninitialized_error = { - "libgit2 has not been initialized; you must call git_libgit2_init", - GIT_ERROR_INVALID -}; - -static void set_error_from_buffer(int error_class) -{ - git_error *error = &GIT_THREADSTATE->error_t; - git_str *buf = &GIT_THREADSTATE->error_buf; - - error->message = buf->ptr; - error->klass = error_class; - - GIT_THREADSTATE->last_error = error; -} - -static void set_error(int error_class, char *string) -{ - git_str *buf = &GIT_THREADSTATE->error_buf; - - git_str_clear(buf); - if (string) { - git_str_puts(buf, string); - git__free(string); - } - - set_error_from_buffer(error_class); -} - -void git_error_set_oom(void) -{ - GIT_THREADSTATE->last_error = &g_git_oom_error; -} - -void git_error_set(int error_class, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - git_error_vset(error_class, fmt, ap); - va_end(ap); -} - -void git_error_vset(int error_class, const char *fmt, va_list ap) -{ -#ifdef GIT_WIN32 - DWORD win32_error_code = (error_class == GIT_ERROR_OS) ? GetLastError() : 0; -#endif - int error_code = (error_class == GIT_ERROR_OS) ? errno : 0; - git_str *buf = &GIT_THREADSTATE->error_buf; - - git_str_clear(buf); - if (fmt) { - git_str_vprintf(buf, fmt, ap); - if (error_class == GIT_ERROR_OS) - git_str_PUTS(buf, ": "); - } - - if (error_class == GIT_ERROR_OS) { -#ifdef GIT_WIN32 - char * win32_error = git_win32_get_error_message(win32_error_code); - if (win32_error) { - git_str_puts(buf, win32_error); - git__free(win32_error); - - SetLastError(0); - } - else -#endif - if (error_code) - git_str_puts(buf, strerror(error_code)); - - if (error_code) - errno = 0; - } - - if (!git_str_oom(buf)) - set_error_from_buffer(error_class); -} - -int git_error_set_str(int error_class, const char *string) -{ - git_str *buf = &GIT_THREADSTATE->error_buf; - - GIT_ASSERT_ARG(string); - - git_str_clear(buf); - git_str_puts(buf, string); - - if (git_str_oom(buf)) - return -1; - - set_error_from_buffer(error_class); - return 0; -} - -void git_error_clear(void) -{ - if (GIT_THREADSTATE->last_error != NULL) { - set_error(0, NULL); - GIT_THREADSTATE->last_error = NULL; - } - - errno = 0; -#ifdef GIT_WIN32 - SetLastError(0); -#endif -} - -const git_error *git_error_last(void) -{ - /* If the library is not initialized, return a static error. */ - if (!git_libgit2_init_count()) - return &g_git_uninitialized_error; - - return GIT_THREADSTATE->last_error; -} - -int git_error_state_capture(git_error_state *state, int error_code) -{ - git_error *error = GIT_THREADSTATE->last_error; - git_str *error_buf = &GIT_THREADSTATE->error_buf; - - memset(state, 0, sizeof(git_error_state)); - - if (!error_code) - return 0; - - state->error_code = error_code; - state->oom = (error == &g_git_oom_error); - - if (error) { - state->error_msg.klass = error->klass; - - if (state->oom) - state->error_msg.message = g_git_oom_error.message; - else - state->error_msg.message = git_str_detach(error_buf); - } - - git_error_clear(); - return error_code; -} - -int git_error_state_restore(git_error_state *state) -{ - int ret = 0; - - git_error_clear(); - - if (state && state->error_msg.message) { - if (state->oom) - git_error_set_oom(); - else - set_error(state->error_msg.klass, state->error_msg.message); - - ret = state->error_code; - memset(state, 0, sizeof(git_error_state)); - } - - return ret; -} - -void git_error_state_free(git_error_state *state) -{ - if (!state) - return; - - if (!state->oom) - git__free(state->error_msg.message); - - memset(state, 0, sizeof(git_error_state)); -} - -int git_error_system_last(void) -{ -#ifdef GIT_WIN32 - return GetLastError(); -#else - return errno; -#endif -} - -void git_error_system_set(int code) -{ -#ifdef GIT_WIN32 - SetLastError(code); -#else - errno = code; -#endif -} - -/* Deprecated error values and functions */ - -#ifndef GIT_DEPRECATE_HARD -const git_error *giterr_last(void) -{ - return git_error_last(); -} - -void giterr_clear(void) -{ - git_error_clear(); -} - -void giterr_set_str(int error_class, const char *string) -{ - git_error_set_str(error_class, string); -} - -void giterr_set_oom(void) -{ - git_error_set_oom(); -} -#endif diff --git a/src/errors.h b/src/errors.h deleted file mode 100644 index 772c7bad1..000000000 --- a/src/errors.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_errors_h__ -#define INCLUDE_errors_h__ - -#include "common.h" - -/* - * `vprintf`-style formatting for the error message for this thread. - */ -void git_error_vset(int error_class, const char *fmt, va_list ap); - -/** - * Set error message for user callback if needed. - * - * If the error code in non-zero and no error message is set, this - * sets a generic error message. - * - * @return This always returns the `error_code` parameter. - */ -GIT_INLINE(int) git_error_set_after_callback_function( - int error_code, const char *action) -{ - if (error_code) { - const git_error *e = git_error_last(); - if (!e || !e->message) - git_error_set(e ? e->klass : GIT_ERROR_CALLBACK, - "%s callback returned %d", action, error_code); - } - return error_code; -} - -#ifdef GIT_WIN32 -#define git_error_set_after_callback(code) \ - git_error_set_after_callback_function((code), __FUNCTION__) -#else -#define git_error_set_after_callback(code) \ - git_error_set_after_callback_function((code), __func__) -#endif - -/** - * Gets the system error code for this thread. - */ -int git_error_system_last(void); - -/** - * Sets the system error code for this thread. - */ -void git_error_system_set(int code); - -/** - * Structure to preserve libgit2 error state - */ -typedef struct { - int error_code; - unsigned int oom : 1; - git_error error_msg; -} git_error_state; - -/** - * Capture current error state to restore later, returning error code. - * If `error_code` is zero, this does not clear the current error state. - * You must either restore this error state, or free it. - */ -extern int git_error_state_capture(git_error_state *state, int error_code); - -/** - * Restore error state to a previous value, returning saved error code. - */ -extern int git_error_state_restore(git_error_state *state); - -/** Free an error state. */ -extern void git_error_state_free(git_error_state *state); - -#endif diff --git a/src/features.h.in b/src/features.h.in deleted file mode 100644 index f920135da..000000000 --- a/src/features.h.in +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef INCLUDE_features_h__ -#define INCLUDE_features_h__ - -#cmakedefine GIT_DEBUG_POOL 1 -#cmakedefine GIT_DEBUG_STRICT_ALLOC 1 -#cmakedefine GIT_DEBUG_STRICT_OPEN 1 - -#cmakedefine GIT_THREADS 1 -#cmakedefine GIT_WIN32_LEAKCHECK 1 - -#cmakedefine GIT_ARCH_64 1 -#cmakedefine GIT_ARCH_32 1 - -#cmakedefine GIT_USE_ICONV 1 -#cmakedefine GIT_USE_NSEC 1 -#cmakedefine GIT_USE_STAT_MTIM 1 -#cmakedefine GIT_USE_STAT_MTIMESPEC 1 -#cmakedefine GIT_USE_STAT_MTIME_NSEC 1 -#cmakedefine GIT_USE_FUTIMENS 1 - -#cmakedefine GIT_REGEX_REGCOMP_L -#cmakedefine GIT_REGEX_REGCOMP -#cmakedefine GIT_REGEX_PCRE -#cmakedefine GIT_REGEX_PCRE2 -#cmakedefine GIT_REGEX_BUILTIN 1 - -#cmakedefine GIT_QSORT_R_BSD -#cmakedefine GIT_QSORT_R_GNU -#cmakedefine GIT_QSORT_S - -#cmakedefine GIT_SSH 1 -#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1 - -#cmakedefine GIT_NTLM 1 -#cmakedefine GIT_GSSAPI 1 -#cmakedefine GIT_GSSFRAMEWORK 1 - -#cmakedefine GIT_WINHTTP 1 -#cmakedefine GIT_HTTPS 1 -#cmakedefine GIT_OPENSSL 1 -#cmakedefine GIT_OPENSSL_DYNAMIC 1 -#cmakedefine GIT_SECURE_TRANSPORT 1 -#cmakedefine GIT_MBEDTLS 1 - -#cmakedefine GIT_SHA1_COLLISIONDETECT 1 -#cmakedefine GIT_SHA1_WIN32 1 -#cmakedefine GIT_SHA1_COMMON_CRYPTO 1 -#cmakedefine GIT_SHA1_OPENSSL 1 -#cmakedefine GIT_SHA1_MBEDTLS 1 - -#cmakedefine GIT_RAND_GETENTROPY 1 - -#endif diff --git a/src/fetch.c b/src/fetch.c deleted file mode 100644 index 03d38452c..000000000 --- a/src/fetch.c +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "fetch.h" - -#include "git2/oid.h" -#include "git2/refs.h" -#include "git2/revwalk.h" -#include "git2/transport.h" -#include "git2/sys/remote.h" - -#include "remote.h" -#include "refspec.h" -#include "pack.h" -#include "netops.h" -#include "repository.h" -#include "refs.h" - -static int maybe_want(git_remote *remote, git_remote_head *head, git_refspec *tagspec, git_remote_autotag_option_t tagopt) -{ - int match = 0, valid; - - if (git_reference_name_is_valid(&valid, head->name) < 0) - return -1; - - if (!valid) - return 0; - - if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { - /* - * If tagopt is --tags, always request tags - * in addition to the remote's refspecs - */ - if (git_refspec_src_matches(tagspec, head->name)) - match = 1; - } - - if (!match && git_remote__matching_refspec(remote, head->name)) - match = 1; - - if (!match) - return 0; - - return git_vector_insert(&remote->refs, head); -} - -static int mark_local(git_remote *remote) -{ - git_remote_head *head; - git_odb *odb; - size_t i; - - if (git_repository_odb__weakptr(&odb, remote->repo) < 0) - return -1; - - git_vector_foreach(&remote->refs, i, head) { - /* If we have the object, mark it so we don't ask for it */ - if (git_odb_exists(odb, &head->oid)) - head->local = 1; - else - remote->need_pack = 1; - } - - return 0; -} - -static int maybe_want_oid(git_remote *remote, git_refspec *spec) -{ - git_remote_head *oid_head; - - oid_head = git__calloc(1, sizeof(git_remote_head)); - GIT_ERROR_CHECK_ALLOC(oid_head); - - git_oid_fromstr(&oid_head->oid, spec->src); - oid_head->name = git__strdup(spec->dst); - GIT_ERROR_CHECK_ALLOC(oid_head->name); - - if (git_vector_insert(&remote->local_heads, oid_head) < 0 || - git_vector_insert(&remote->refs, oid_head) < 0) - return -1; - - return 0; -} - -static int filter_wants(git_remote *remote, const git_fetch_options *opts) -{ - git_remote_head **heads; - git_refspec tagspec, head, *spec; - int error = 0; - git_odb *odb; - size_t i, heads_len; - unsigned int remote_caps; - unsigned int oid_mask = GIT_REMOTE_CAPABILITY_TIP_OID | - GIT_REMOTE_CAPABILITY_REACHABLE_OID; - git_remote_autotag_option_t tagopt = remote->download_tags; - - if (opts && opts->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED) - tagopt = opts->download_tags; - - git_vector_clear(&remote->refs); - if ((error = git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true)) < 0) - return error; - - /* - * The fetch refspec can be NULL, and what this means is that the - * user didn't specify one. This is fine, as it means that we're - * not interested in any particular branch but just the remote's - * HEAD, which will be stored in FETCH_HEAD after the fetch. - */ - if (remote->active_refspecs.length == 0) { - if ((error = git_refspec__parse(&head, "HEAD", true)) < 0) - goto cleanup; - - error = git_refspec__dwim_one(&remote->active_refspecs, &head, &remote->refs); - git_refspec__dispose(&head); - - if (error < 0) - goto cleanup; - } - - if ((error = git_repository_odb__weakptr(&odb, remote->repo)) < 0) - goto cleanup; - - if ((error = git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote)) < 0 || - (error = git_remote_capabilities(&remote_caps, remote)) < 0) - goto cleanup; - - /* Handle remote heads */ - for (i = 0; i < heads_len; i++) { - if ((error = maybe_want(remote, heads[i], &tagspec, tagopt)) < 0) - goto cleanup; - } - - /* Handle explicitly specified OID specs */ - git_vector_foreach(&remote->active_refspecs, i, spec) { - if (!git_oid__is_hexstr(spec->src)) - continue; - - if (!(remote_caps & oid_mask)) { - git_error_set(GIT_ERROR_INVALID, "cannot fetch a specific object from the remote repository"); - error = -1; - goto cleanup; - } - - if ((error = maybe_want_oid(remote, spec)) < 0) - goto cleanup; - } - - error = mark_local(remote); - -cleanup: - git_refspec__dispose(&tagspec); - - return error; -} - -/* - * In this first version, we push all our refs in and start sending - * them out. When we get an ACK we hide that commit and continue - * traversing until we're done - */ -int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts) -{ - git_transport *t = remote->transport; - - remote->need_pack = 0; - - if (filter_wants(remote, opts) < 0) - return -1; - - /* Don't try to negotiate when we don't want anything */ - if (!remote->need_pack) - return 0; - - /* - * Now we have everything set up so we can start tell the - * server what we want and what we have. - */ - return t->negotiate_fetch(t, - remote->repo, - (const git_remote_head * const *)remote->refs.contents, - remote->refs.length); -} - -int git_fetch_download_pack(git_remote *remote) -{ - git_transport *t = remote->transport; - - if (!remote->need_pack) - return 0; - - return t->download_pack(t, remote->repo, &remote->stats); -} - -int git_fetch_options_init(git_fetch_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_fetch_options, GIT_FETCH_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_fetch_init_options(git_fetch_options *opts, unsigned int version) -{ - return git_fetch_options_init(opts, version); -} -#endif diff --git a/src/fetch.h b/src/fetch.h deleted file mode 100644 index 10b6731f0..000000000 --- a/src/fetch.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_fetch_h__ -#define INCLUDE_fetch_h__ - -#include "common.h" - -#include "git2/remote.h" - -#include "netops.h" - -int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts); - -int git_fetch_download_pack(git_remote *remote); - -int git_fetch_setup_walk(git_revwalk **out, git_repository *repo); - -#endif diff --git a/src/fetchhead.c b/src/fetchhead.c deleted file mode 100644 index 6511124ef..000000000 --- a/src/fetchhead.c +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "fetchhead.h" - -#include "git2/types.h" -#include "git2/oid.h" - -#include "str.h" -#include "futils.h" -#include "filebuf.h" -#include "refs.h" -#include "net.h" -#include "repository.h" - -int git_fetchhead_ref_cmp(const void *a, const void *b) -{ - const git_fetchhead_ref *one = (const git_fetchhead_ref *)a; - const git_fetchhead_ref *two = (const git_fetchhead_ref *)b; - - if (one->is_merge && !two->is_merge) - return -1; - if (two->is_merge && !one->is_merge) - return 1; - - if (one->ref_name && two->ref_name) - return strcmp(one->ref_name, two->ref_name); - else if (one->ref_name) - return -1; - else if (two->ref_name) - return 1; - - return 0; -} - -static char *sanitized_remote_url(const char *remote_url) -{ - git_net_url url = GIT_NET_URL_INIT; - char *sanitized = NULL; - int error; - - if (git_net_url_parse(&url, remote_url) == 0) { - git_str buf = GIT_STR_INIT; - - git__free(url.username); - git__free(url.password); - url.username = url.password = NULL; - - if ((error = git_net_url_fmt(&buf, &url)) < 0) - goto fallback; - - sanitized = git_str_detach(&buf); - } - -fallback: - if (!sanitized) - sanitized = git__strdup(remote_url); - - git_net_url_dispose(&url); - return sanitized; -} - -int git_fetchhead_ref_create( - git_fetchhead_ref **out, - git_oid *oid, - unsigned int is_merge, - const char *ref_name, - const char *remote_url) -{ - git_fetchhead_ref *fetchhead_ref; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(oid); - - *out = NULL; - - fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref)); - GIT_ERROR_CHECK_ALLOC(fetchhead_ref); - - memset(fetchhead_ref, 0x0, sizeof(git_fetchhead_ref)); - - git_oid_cpy(&fetchhead_ref->oid, oid); - fetchhead_ref->is_merge = is_merge; - - if (ref_name) { - fetchhead_ref->ref_name = git__strdup(ref_name); - GIT_ERROR_CHECK_ALLOC(fetchhead_ref->ref_name); - } - - if (remote_url) { - fetchhead_ref->remote_url = sanitized_remote_url(remote_url); - GIT_ERROR_CHECK_ALLOC(fetchhead_ref->remote_url); - } - - *out = fetchhead_ref; - - return 0; -} - -static int fetchhead_ref_write( - git_filebuf *file, - git_fetchhead_ref *fetchhead_ref) -{ - char oid[GIT_OID_HEXSZ + 1]; - const char *type, *name; - int head = 0; - - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(fetchhead_ref); - - git_oid_fmt(oid, &fetchhead_ref->oid); - oid[GIT_OID_HEXSZ] = '\0'; - - if (git__prefixcmp(fetchhead_ref->ref_name, GIT_REFS_HEADS_DIR) == 0) { - type = "branch "; - name = fetchhead_ref->ref_name + strlen(GIT_REFS_HEADS_DIR); - } else if(git__prefixcmp(fetchhead_ref->ref_name, - GIT_REFS_TAGS_DIR) == 0) { - type = "tag "; - name = fetchhead_ref->ref_name + strlen(GIT_REFS_TAGS_DIR); - } else if (!git__strcmp(fetchhead_ref->ref_name, GIT_HEAD_FILE)) { - head = 1; - } else { - type = ""; - name = fetchhead_ref->ref_name; - } - - if (head) - return git_filebuf_printf(file, "%s\t\t%s\n", oid, fetchhead_ref->remote_url); - - return git_filebuf_printf(file, "%s\t%s\t%s'%s' of %s\n", - oid, - (fetchhead_ref->is_merge) ? "" : "not-for-merge", - type, - name, - fetchhead_ref->remote_url); -} - -int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str path = GIT_STR_INIT; - unsigned int i; - git_fetchhead_ref *fetchhead_ref; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(fetchhead_refs); - - if (git_str_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0) - return -1; - - if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_APPEND, GIT_REFS_FILE_MODE) < 0) { - git_str_dispose(&path); - return -1; - } - - git_str_dispose(&path); - - git_vector_sort(fetchhead_refs); - - git_vector_foreach(fetchhead_refs, i, fetchhead_ref) - fetchhead_ref_write(&file, fetchhead_ref); - - return git_filebuf_commit(&file); -} - -static int fetchhead_ref_parse( - git_oid *oid, - unsigned int *is_merge, - git_str *ref_name, - const char **remote_url, - char *line, - size_t line_num) -{ - char *oid_str, *is_merge_str, *desc, *name = NULL; - const char *type = NULL; - int error = 0; - - *remote_url = NULL; - - if (!*line) { - git_error_set(GIT_ERROR_FETCHHEAD, - "empty line in FETCH_HEAD line %"PRIuZ, line_num); - return -1; - } - - /* Compat with old git clients that wrote FETCH_HEAD like a loose ref. */ - if ((oid_str = git__strsep(&line, "\t")) == NULL) { - oid_str = line; - line += strlen(line); - - *is_merge = 1; - } - - if (strlen(oid_str) != GIT_OID_HEXSZ) { - git_error_set(GIT_ERROR_FETCHHEAD, - "invalid object ID in FETCH_HEAD line %"PRIuZ, line_num); - return -1; - } - - if (git_oid_fromstr(oid, oid_str) < 0) { - const git_error *oid_err = git_error_last(); - const char *err_msg = oid_err ? oid_err->message : "invalid object ID"; - - git_error_set(GIT_ERROR_FETCHHEAD, "%s in FETCH_HEAD line %"PRIuZ, - err_msg, line_num); - return -1; - } - - /* Parse new data from newer git clients */ - if (*line) { - if ((is_merge_str = git__strsep(&line, "\t")) == NULL) { - git_error_set(GIT_ERROR_FETCHHEAD, - "invalid description data in FETCH_HEAD line %"PRIuZ, line_num); - return -1; - } - - if (*is_merge_str == '\0') - *is_merge = 1; - else if (strcmp(is_merge_str, "not-for-merge") == 0) - *is_merge = 0; - else { - git_error_set(GIT_ERROR_FETCHHEAD, - "invalid for-merge entry in FETCH_HEAD line %"PRIuZ, line_num); - return -1; - } - - if ((desc = line) == NULL) { - git_error_set(GIT_ERROR_FETCHHEAD, - "invalid description in FETCH_HEAD line %"PRIuZ, line_num); - return -1; - } - - if (git__prefixcmp(desc, "branch '") == 0) { - type = GIT_REFS_HEADS_DIR; - name = desc + 8; - } else if (git__prefixcmp(desc, "tag '") == 0) { - type = GIT_REFS_TAGS_DIR; - name = desc + 5; - } else if (git__prefixcmp(desc, "'") == 0) - name = desc + 1; - - if (name) { - if ((desc = strstr(name, "' ")) == NULL || - git__prefixcmp(desc, "' of ") != 0) { - git_error_set(GIT_ERROR_FETCHHEAD, - "invalid description in FETCH_HEAD line %"PRIuZ, line_num); - return -1; - } - - *desc = '\0'; - desc += 5; - } - - *remote_url = desc; - } - - git_str_clear(ref_name); - - if (type) - git_str_join(ref_name, '/', type, name); - else if(name) - git_str_puts(ref_name, name); - - return error; -} - -int git_repository_fetchhead_foreach(git_repository *repo, - git_repository_fetchhead_foreach_cb cb, - void *payload) -{ - git_str path = GIT_STR_INIT, file = GIT_STR_INIT, name = GIT_STR_INIT; - const char *ref_name; - git_oid oid; - const char *remote_url; - unsigned int is_merge = 0; - char *buffer, *line; - size_t line_num = 0; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(cb); - - if (git_str_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0) - return -1; - - if ((error = git_futils_readbuffer(&file, git_str_cstr(&path))) < 0) - goto done; - - buffer = file.ptr; - - while ((line = git__strsep(&buffer, "\n")) != NULL) { - ++line_num; - - if ((error = fetchhead_ref_parse( - &oid, &is_merge, &name, &remote_url, line, line_num)) < 0) - goto done; - - if (git_str_len(&name) > 0) - ref_name = git_str_cstr(&name); - else - ref_name = NULL; - - error = cb(ref_name, remote_url, &oid, is_merge, payload); - if (error) { - git_error_set_after_callback(error); - goto done; - } - } - - if (*buffer) { - git_error_set(GIT_ERROR_FETCHHEAD, "no EOL at line %"PRIuZ, line_num+1); - error = -1; - goto done; - } - -done: - git_str_dispose(&file); - git_str_dispose(&path); - git_str_dispose(&name); - - return error; -} - -void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref) -{ - if (fetchhead_ref == NULL) - return; - - git__free(fetchhead_ref->remote_url); - git__free(fetchhead_ref->ref_name); - git__free(fetchhead_ref); -} - diff --git a/src/fetchhead.h b/src/fetchhead.h deleted file mode 100644 index 9e5171010..000000000 --- a/src/fetchhead.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_fetchhead_h__ -#define INCLUDE_fetchhead_h__ - -#include "common.h" - -#include "oid.h" -#include "vector.h" - -typedef struct git_fetchhead_ref { - git_oid oid; - unsigned int is_merge; - char *ref_name; - char *remote_url; -} git_fetchhead_ref; - -int git_fetchhead_ref_create( - git_fetchhead_ref **fetchhead_ref_out, - git_oid *oid, - unsigned int is_merge, - const char *ref_name, - const char *remote_url); - -int git_fetchhead_ref_cmp(const void *a, const void *b); - -int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs); - -void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref); - -#endif diff --git a/src/filebuf.c b/src/filebuf.c deleted file mode 100644 index eafcba3bd..000000000 --- a/src/filebuf.c +++ /dev/null @@ -1,595 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "filebuf.h" - -#include "futils.h" - -static const size_t WRITE_BUFFER_SIZE = (4096 * 2); - -enum buferr_t { - BUFERR_OK = 0, - BUFERR_WRITE, - BUFERR_ZLIB, - BUFERR_MEM -}; - -#define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; } - -static int verify_last_error(git_filebuf *file) -{ - switch (file->last_error) { - case BUFERR_WRITE: - git_error_set(GIT_ERROR_OS, "failed to write out file"); - return -1; - - case BUFERR_MEM: - git_error_set_oom(); - return -1; - - case BUFERR_ZLIB: - git_error_set(GIT_ERROR_ZLIB, - "Buffer error when writing out ZLib data"); - return -1; - - default: - return 0; - } -} - -static int lock_file(git_filebuf *file, int flags, mode_t mode) -{ - if (git_fs_path_exists(file->path_lock) == true) { - git_error_clear(); /* actual OS error code just confuses */ - git_error_set(GIT_ERROR_OS, - "failed to lock file '%s' for writing", file->path_lock); - return GIT_ELOCKED; - } - - /* create path to the file buffer is required */ - if (flags & GIT_FILEBUF_CREATE_LEADING_DIRS) { - /* XXX: Should dirmode here be configurable? Or is 0777 always fine? */ - file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, mode); - } else { - file->fd = git_futils_creat_locked(file->path_lock, mode); - } - - if (file->fd < 0) - return file->fd; - - file->fd_is_open = true; - - if ((flags & GIT_FILEBUF_APPEND) && git_fs_path_exists(file->path_original) == true) { - git_file source; - char buffer[FILEIO_BUFSIZE]; - ssize_t read_bytes; - int error = 0; - - source = p_open(file->path_original, O_RDONLY); - if (source < 0) { - git_error_set(GIT_ERROR_OS, - "failed to open file '%s' for reading", - file->path_original); - return -1; - } - - while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) { - if ((error = p_write(file->fd, buffer, read_bytes)) < 0) - break; - if (file->compute_digest) - git_hash_update(&file->digest, buffer, read_bytes); - } - - p_close(source); - - if (read_bytes < 0) { - git_error_set(GIT_ERROR_OS, "failed to read file '%s'", file->path_original); - return -1; - } else if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to write file '%s'", file->path_lock); - return -1; - } - } - - return 0; -} - -void git_filebuf_cleanup(git_filebuf *file) -{ - if (file->fd_is_open && file->fd >= 0) - p_close(file->fd); - - if (file->created_lock && !file->did_rename && file->path_lock && git_fs_path_exists(file->path_lock)) - p_unlink(file->path_lock); - - if (file->compute_digest) { - git_hash_ctx_cleanup(&file->digest); - file->compute_digest = 0; - } - - if (file->buffer) - git__free(file->buffer); - - /* use the presence of z_buf to decide if we need to deflateEnd */ - if (file->z_buf) { - git__free(file->z_buf); - deflateEnd(&file->zs); - } - - if (file->path_original) - git__free(file->path_original); - if (file->path_lock) - git__free(file->path_lock); - - memset(file, 0x0, sizeof(git_filebuf)); - file->fd = -1; -} - -GIT_INLINE(int) flush_buffer(git_filebuf *file) -{ - int result = file->write(file, file->buffer, file->buf_pos); - file->buf_pos = 0; - return result; -} - -int git_filebuf_flush(git_filebuf *file) -{ - return flush_buffer(file); -} - -static int write_normal(git_filebuf *file, void *source, size_t len) -{ - if (len > 0) { - if (p_write(file->fd, (void *)source, len) < 0) { - file->last_error = BUFERR_WRITE; - return -1; - } - - if (file->compute_digest) - git_hash_update(&file->digest, source, len); - } - - return 0; -} - -static int write_deflate(git_filebuf *file, void *source, size_t len) -{ - z_stream *zs = &file->zs; - - if (len > 0 || file->flush_mode == Z_FINISH) { - zs->next_in = source; - zs->avail_in = (uInt)len; - - do { - size_t have; - - zs->next_out = file->z_buf; - zs->avail_out = (uInt)file->buf_size; - - if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) { - file->last_error = BUFERR_ZLIB; - return -1; - } - - have = file->buf_size - (size_t)zs->avail_out; - - if (p_write(file->fd, file->z_buf, have) < 0) { - file->last_error = BUFERR_WRITE; - return -1; - } - - } while (zs->avail_out == 0); - - GIT_ASSERT(zs->avail_in == 0); - - if (file->compute_digest) - git_hash_update(&file->digest, source, len); - } - - return 0; -} - -#define MAX_SYMLINK_DEPTH 5 - -static int resolve_symlink(git_str *out, const char *path) -{ - int i, error, root; - ssize_t ret; - struct stat st; - git_str curpath = GIT_STR_INIT, target = GIT_STR_INIT; - - if ((error = git_str_grow(&target, GIT_PATH_MAX + 1)) < 0 || - (error = git_str_puts(&curpath, path)) < 0) - return error; - - for (i = 0; i < MAX_SYMLINK_DEPTH; i++) { - error = p_lstat(curpath.ptr, &st); - if (error < 0 && errno == ENOENT) { - error = git_str_puts(out, curpath.ptr); - goto cleanup; - } - - if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to stat '%s'", curpath.ptr); - error = -1; - goto cleanup; - } - - if (!S_ISLNK(st.st_mode)) { - error = git_str_puts(out, curpath.ptr); - goto cleanup; - } - - ret = p_readlink(curpath.ptr, target.ptr, GIT_PATH_MAX); - if (ret < 0) { - git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", curpath.ptr); - error = -1; - goto cleanup; - } - - if (ret == GIT_PATH_MAX) { - git_error_set(GIT_ERROR_INVALID, "symlink target too long"); - error = -1; - goto cleanup; - } - - /* readlink(2) won't NUL-terminate for us */ - target.ptr[ret] = '\0'; - target.size = ret; - - root = git_fs_path_root(target.ptr); - if (root >= 0) { - if ((error = git_str_sets(&curpath, target.ptr)) < 0) - goto cleanup; - } else { - git_str dir = GIT_STR_INIT; - - if ((error = git_fs_path_dirname_r(&dir, curpath.ptr)) < 0) - goto cleanup; - - git_str_swap(&curpath, &dir); - git_str_dispose(&dir); - - if ((error = git_fs_path_apply_relative(&curpath, target.ptr)) < 0) - goto cleanup; - } - } - - git_error_set(GIT_ERROR_INVALID, "maximum symlink depth reached"); - error = -1; - -cleanup: - git_str_dispose(&curpath); - git_str_dispose(&target); - return error; -} - -int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode) -{ - return git_filebuf_open_withsize(file, path, flags, mode, WRITE_BUFFER_SIZE); -} - -int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size) -{ - int compression, error = -1; - size_t path_len, alloc_len; - - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(path); - GIT_ASSERT(file->buffer == NULL); - - memset(file, 0x0, sizeof(git_filebuf)); - - if (flags & GIT_FILEBUF_DO_NOT_BUFFER) - file->do_not_buffer = true; - - if (flags & GIT_FILEBUF_FSYNC) - file->do_fsync = true; - - file->buf_size = size; - file->buf_pos = 0; - file->fd = -1; - file->last_error = BUFERR_OK; - - /* Allocate the main cache buffer */ - if (!file->do_not_buffer) { - file->buffer = git__malloc(file->buf_size); - GIT_ERROR_CHECK_ALLOC(file->buffer); - } - - /* If we are hashing on-write, allocate a new hash context */ - if (flags & GIT_FILEBUF_HASH_CONTENTS) { - file->compute_digest = 1; - - if (git_hash_ctx_init(&file->digest, GIT_HASH_ALGORITHM_SHA1) < 0) - goto cleanup; - } - - compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT; - - /* If we are deflating on-write, */ - if (compression != 0) { - /* Initialize the ZLib stream */ - if (deflateInit(&file->zs, compression) != Z_OK) { - git_error_set(GIT_ERROR_ZLIB, "failed to initialize zlib"); - goto cleanup; - } - - /* Allocate the Zlib cache buffer */ - file->z_buf = git__malloc(file->buf_size); - GIT_ERROR_CHECK_ALLOC(file->z_buf); - - /* Never flush */ - file->flush_mode = Z_NO_FLUSH; - file->write = &write_deflate; - } else { - file->write = &write_normal; - } - - /* If we are writing to a temp file */ - if (flags & GIT_FILEBUF_TEMPORARY) { - git_str tmp_path = GIT_STR_INIT; - - /* Open the file as temporary for locking */ - file->fd = git_futils_mktmp(&tmp_path, path, mode); - - if (file->fd < 0) { - git_str_dispose(&tmp_path); - goto cleanup; - } - file->fd_is_open = true; - file->created_lock = true; - - /* No original path */ - file->path_original = NULL; - file->path_lock = git_str_detach(&tmp_path); - GIT_ERROR_CHECK_ALLOC(file->path_lock); - } else { - git_str resolved_path = GIT_STR_INIT; - - if ((error = resolve_symlink(&resolved_path, path)) < 0) - goto cleanup; - - /* Save the original path of the file */ - path_len = resolved_path.size; - file->path_original = git_str_detach(&resolved_path); - - /* create the locking path by appending ".lock" to the original */ - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, path_len, GIT_FILELOCK_EXTLENGTH); - file->path_lock = git__malloc(alloc_len); - GIT_ERROR_CHECK_ALLOC(file->path_lock); - - memcpy(file->path_lock, file->path_original, path_len); - memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH); - - if (git_fs_path_isdir(file->path_original)) { - git_error_set(GIT_ERROR_FILESYSTEM, "path '%s' is a directory", file->path_original); - error = GIT_EDIRECTORY; - goto cleanup; - } - - /* open the file for locking */ - if ((error = lock_file(file, flags, mode)) < 0) - goto cleanup; - - file->created_lock = true; - } - - return 0; - -cleanup: - git_filebuf_cleanup(file); - return error; -} - -int git_filebuf_hash(unsigned char *out, git_filebuf *file) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(file->compute_digest); - - flush_buffer(file); - - if (verify_last_error(file) < 0) - return -1; - - git_hash_final(out, &file->digest); - git_hash_ctx_cleanup(&file->digest); - file->compute_digest = 0; - - return 0; -} - -int git_filebuf_commit_at(git_filebuf *file, const char *path) -{ - git__free(file->path_original); - file->path_original = git__strdup(path); - GIT_ERROR_CHECK_ALLOC(file->path_original); - - return git_filebuf_commit(file); -} - -int git_filebuf_commit(git_filebuf *file) -{ - /* temporary files cannot be committed */ - GIT_ASSERT_ARG(file); - GIT_ASSERT(file->path_original); - - file->flush_mode = Z_FINISH; - flush_buffer(file); - - if (verify_last_error(file) < 0) - goto on_error; - - file->fd_is_open = false; - - if (file->do_fsync && p_fsync(file->fd) < 0) { - git_error_set(GIT_ERROR_OS, "failed to fsync '%s'", file->path_lock); - goto on_error; - } - - if (p_close(file->fd) < 0) { - git_error_set(GIT_ERROR_OS, "failed to close file at '%s'", file->path_lock); - goto on_error; - } - - file->fd = -1; - - if (p_rename(file->path_lock, file->path_original) < 0) { - git_error_set(GIT_ERROR_OS, "failed to rename lockfile to '%s'", file->path_original); - goto on_error; - } - - if (file->do_fsync && git_futils_fsync_parent(file->path_original) < 0) - goto on_error; - - file->did_rename = true; - - git_filebuf_cleanup(file); - return 0; - -on_error: - git_filebuf_cleanup(file); - return -1; -} - -GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len) -{ - memcpy(file->buffer + file->buf_pos, buf, len); - file->buf_pos += len; -} - -int git_filebuf_write(git_filebuf *file, const void *buff, size_t len) -{ - const unsigned char *buf = buff; - - ENSURE_BUF_OK(file); - - if (file->do_not_buffer) - return file->write(file, (void *)buff, len); - - for (;;) { - size_t space_left = file->buf_size - file->buf_pos; - - /* cache if it's small */ - if (space_left > len) { - add_to_cache(file, buf, len); - return 0; - } - - add_to_cache(file, buf, space_left); - if (flush_buffer(file) < 0) - return -1; - - len -= space_left; - buf += space_left; - } -} - -int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len) -{ - size_t space_left = file->buf_size - file->buf_pos; - - *buffer = NULL; - - ENSURE_BUF_OK(file); - - if (len > file->buf_size) { - file->last_error = BUFERR_MEM; - return -1; - } - - if (space_left <= len) { - if (flush_buffer(file) < 0) - return -1; - } - - *buffer = (file->buffer + file->buf_pos); - file->buf_pos += len; - - return 0; -} - -int git_filebuf_printf(git_filebuf *file, const char *format, ...) -{ - va_list arglist; - size_t space_left, len, alloclen; - int written, res; - char *tmp_buffer; - - ENSURE_BUF_OK(file); - - space_left = file->buf_size - file->buf_pos; - - do { - va_start(arglist, format); - written = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); - va_end(arglist); - - if (written < 0) { - file->last_error = BUFERR_MEM; - return -1; - } - - len = written; - if (len + 1 <= space_left) { - file->buf_pos += len; - return 0; - } - - if (flush_buffer(file) < 0) - return -1; - - space_left = file->buf_size - file->buf_pos; - - } while (len + 1 <= space_left); - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, len, 1) || - !(tmp_buffer = git__malloc(alloclen))) { - file->last_error = BUFERR_MEM; - return -1; - } - - va_start(arglist, format); - written = p_vsnprintf(tmp_buffer, len + 1, format, arglist); - va_end(arglist); - - if (written < 0) { - git__free(tmp_buffer); - file->last_error = BUFERR_MEM; - return -1; - } - - res = git_filebuf_write(file, tmp_buffer, len); - git__free(tmp_buffer); - - return res; -} - -int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file) -{ - int res; - struct stat st; - - if (file->fd_is_open) - res = p_fstat(file->fd, &st); - else - res = p_stat(file->path_original, &st); - - if (res < 0) { - git_error_set(GIT_ERROR_OS, "could not get stat info for '%s'", - file->path_original); - return res; - } - - if (mtime) - *mtime = st.st_mtime; - if (size) - *size = (size_t)st.st_size; - - return 0; -} diff --git a/src/filebuf.h b/src/filebuf.h deleted file mode 100644 index adbb19936..000000000 --- a/src/filebuf.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_filebuf_h__ -#define INCLUDE_filebuf_h__ - -#include "common.h" - -#include "futils.h" -#include "hash.h" -#include - -#ifdef GIT_THREADS -# define GIT_FILEBUF_THREADS -#endif - -#define GIT_FILEBUF_HASH_CONTENTS (1 << 0) -#define GIT_FILEBUF_APPEND (1 << 2) -#define GIT_FILEBUF_CREATE_LEADING_DIRS (1 << 3) -#define GIT_FILEBUF_TEMPORARY (1 << 4) -#define GIT_FILEBUF_DO_NOT_BUFFER (1 << 5) -#define GIT_FILEBUF_FSYNC (1 << 6) -#define GIT_FILEBUF_DEFLATE_SHIFT (7) - -#define GIT_FILELOCK_EXTENSION ".lock\0" -#define GIT_FILELOCK_EXTLENGTH 6 - -typedef struct git_filebuf git_filebuf; -struct git_filebuf { - char *path_original; - char *path_lock; - - int (*write)(git_filebuf *file, void *source, size_t len); - - bool compute_digest; - git_hash_ctx digest; - - unsigned char *buffer; - unsigned char *z_buf; - - z_stream zs; - int flush_mode; - - size_t buf_size, buf_pos; - git_file fd; - bool fd_is_open; - bool created_lock; - bool did_rename; - bool do_not_buffer; - bool do_fsync; - int last_error; -}; - -#define GIT_FILEBUF_INIT {0} - -/* - * The git_filebuf object lifecycle is: - * - Allocate git_filebuf, preferably using GIT_FILEBUF_INIT. - * - * - Call git_filebuf_open() to initialize the filebuf for use. - * - * - Make as many calls to git_filebuf_write(), git_filebuf_printf(), - * git_filebuf_reserve() as you like. The error codes for these - * functions don't need to be checked. They are stored internally - * by the file buffer. - * - * - While you are writing, you may call git_filebuf_hash() to get - * the hash of all you have written so far. This function will - * fail if any of the previous writes to the buffer failed. - * - * - To close the git_filebuf, you may call git_filebuf_commit() or - * git_filebuf_commit_at() to save the file, or - * git_filebuf_cleanup() to abandon the file. All of these will - * free the git_filebuf object. Likewise, all of these will fail - * if any of the previous writes to the buffer failed, and set - * an error code accordingly. - */ -int git_filebuf_write(git_filebuf *lock, const void *buff, size_t len); -int git_filebuf_reserve(git_filebuf *file, void **buff, size_t len); -int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); - -int git_filebuf_open(git_filebuf *lock, const char *path, int flags, mode_t mode); -int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size); -int git_filebuf_commit(git_filebuf *lock); -int git_filebuf_commit_at(git_filebuf *lock, const char *path); -void git_filebuf_cleanup(git_filebuf *lock); -int git_filebuf_hash(unsigned char *out, git_filebuf *file); -int git_filebuf_flush(git_filebuf *file); -int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file); - -#endif diff --git a/src/filter.c b/src/filter.c deleted file mode 100644 index 2712e8c60..000000000 --- a/src/filter.c +++ /dev/null @@ -1,1191 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "filter.h" - -#include "buf.h" -#include "common.h" -#include "futils.h" -#include "hash.h" -#include "repository.h" -#include "runtime.h" -#include "git2/sys/filter.h" -#include "git2/config.h" -#include "blob.h" -#include "attr_file.h" -#include "array.h" -#include "path.h" - -struct git_filter_source { - git_repository *repo; - const char *path; - git_oid oid; /* zero if unknown (which is likely) */ - uint16_t filemode; /* zero if unknown */ - git_filter_mode_t mode; - git_filter_options options; -}; - -typedef struct { - const char *filter_name; - git_filter *filter; - void *payload; -} git_filter_entry; - -struct git_filter_list { - git_array_t(git_filter_entry) filters; - git_filter_source source; - git_str *temp_buf; - char path[GIT_FLEX_ARRAY]; -}; - -typedef struct { - char *filter_name; - git_filter *filter; - int priority; - int initialized; - size_t nattrs, nmatches; - char *attrdata; - const char *attrs[GIT_FLEX_ARRAY]; -} git_filter_def; - -static int filter_def_priority_cmp(const void *a, const void *b) -{ - int pa = ((const git_filter_def *)a)->priority; - int pb = ((const git_filter_def *)b)->priority; - return (pa < pb) ? -1 : (pa > pb) ? 1 : 0; -} - -struct git_filter_registry { - git_rwlock lock; - git_vector filters; -}; - -static struct git_filter_registry filter_registry; - -static void git_filter_global_shutdown(void); - - -static int filter_def_scan_attrs( - git_str *attrs, size_t *nattr, size_t *nmatch, const char *attr_str) -{ - const char *start, *scan = attr_str; - int has_eq; - - *nattr = *nmatch = 0; - - if (!scan) - return 0; - - while (*scan) { - while (git__isspace(*scan)) scan++; - - for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) { - if (*scan == '=') - has_eq = 1; - } - - if (scan > start) { - (*nattr)++; - if (has_eq || *start == '-' || *start == '+' || *start == '!') - (*nmatch)++; - - if (has_eq) - git_str_putc(attrs, '='); - git_str_put(attrs, start, scan - start); - git_str_putc(attrs, '\0'); - } - } - - return 0; -} - -static void filter_def_set_attrs(git_filter_def *fdef) -{ - char *scan = fdef->attrdata; - size_t i; - - for (i = 0; i < fdef->nattrs; ++i) { - const char *name, *value; - - switch (*scan) { - case '=': - name = scan + 1; - for (scan++; *scan != '='; scan++) /* find '=' */; - *scan++ = '\0'; - value = scan; - break; - case '-': - name = scan + 1; value = git_attr__false; break; - case '+': - name = scan + 1; value = git_attr__true; break; - case '!': - name = scan + 1; value = git_attr__unset; break; - default: - name = scan; value = NULL; break; - } - - fdef->attrs[i] = name; - fdef->attrs[i + fdef->nattrs] = value; - - scan += strlen(scan) + 1; - } -} - -static int filter_def_name_key_check(const void *key, const void *fdef) -{ - const char *name = - fdef ? ((const git_filter_def *)fdef)->filter_name : NULL; - return name ? git__strcmp(key, name) : -1; -} - -static int filter_def_filter_key_check(const void *key, const void *fdef) -{ - const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL; - return (key == filter) ? 0 : -1; -} - -/* Note: callers must lock the registry before calling this function */ -static int filter_registry_insert( - const char *name, git_filter *filter, int priority) -{ - git_filter_def *fdef; - size_t nattr = 0, nmatch = 0, alloc_len; - git_str attrs = GIT_STR_INIT; - - if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0) - return -1; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, nattr, 2); - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, alloc_len, sizeof(char *)); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, sizeof(git_filter_def)); - - fdef = git__calloc(1, alloc_len); - GIT_ERROR_CHECK_ALLOC(fdef); - - fdef->filter_name = git__strdup(name); - GIT_ERROR_CHECK_ALLOC(fdef->filter_name); - - fdef->filter = filter; - fdef->priority = priority; - fdef->nattrs = nattr; - fdef->nmatches = nmatch; - fdef->attrdata = git_str_detach(&attrs); - - filter_def_set_attrs(fdef); - - if (git_vector_insert(&filter_registry.filters, fdef) < 0) { - git__free(fdef->filter_name); - git__free(fdef->attrdata); - git__free(fdef); - return -1; - } - - git_vector_sort(&filter_registry.filters); - return 0; -} - -int git_filter_global_init(void) -{ - git_filter *crlf = NULL, *ident = NULL; - int error = 0; - - if (git_rwlock_init(&filter_registry.lock) < 0) - return -1; - - if ((error = git_vector_init(&filter_registry.filters, 2, - filter_def_priority_cmp)) < 0) - goto done; - - if ((crlf = git_crlf_filter_new()) == NULL || - filter_registry_insert( - GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0 || - (ident = git_ident_filter_new()) == NULL || - filter_registry_insert( - GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0) - error = -1; - - if (!error) - error = git_runtime_shutdown_register(git_filter_global_shutdown); - -done: - if (error) { - git_filter_free(crlf); - git_filter_free(ident); - } - - return error; -} - -static void git_filter_global_shutdown(void) -{ - size_t pos; - git_filter_def *fdef; - - if (git_rwlock_wrlock(&filter_registry.lock) < 0) - return; - - git_vector_foreach(&filter_registry.filters, pos, fdef) { - if (fdef->filter && fdef->filter->shutdown) { - fdef->filter->shutdown(fdef->filter); - fdef->initialized = false; - } - - git__free(fdef->filter_name); - git__free(fdef->attrdata); - git__free(fdef); - } - - git_vector_free(&filter_registry.filters); - - git_rwlock_wrunlock(&filter_registry.lock); - git_rwlock_free(&filter_registry.lock); -} - -/* Note: callers must lock the registry before calling this function */ -static int filter_registry_find(size_t *pos, const char *name) -{ - return git_vector_search2( - pos, &filter_registry.filters, filter_def_name_key_check, name); -} - -/* Note: callers must lock the registry before calling this function */ -static git_filter_def *filter_registry_lookup(size_t *pos, const char *name) -{ - git_filter_def *fdef = NULL; - - if (!filter_registry_find(pos, name)) - fdef = git_vector_get(&filter_registry.filters, *pos); - - return fdef; -} - - -int git_filter_register( - const char *name, git_filter *filter, int priority) -{ - int error; - - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(filter); - - if (git_rwlock_wrlock(&filter_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); - return -1; - } - - if (!filter_registry_find(NULL, name)) { - git_error_set( - GIT_ERROR_FILTER, "attempt to reregister existing filter '%s'", name); - error = GIT_EEXISTS; - goto done; - } - - error = filter_registry_insert(name, filter, priority); - -done: - git_rwlock_wrunlock(&filter_registry.lock); - return error; -} - -int git_filter_unregister(const char *name) -{ - size_t pos; - git_filter_def *fdef; - int error = 0; - - GIT_ASSERT_ARG(name); - - /* cannot unregister default filters */ - if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) { - git_error_set(GIT_ERROR_FILTER, "cannot unregister filter '%s'", name); - return -1; - } - - if (git_rwlock_wrlock(&filter_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); - return -1; - } - - if ((fdef = filter_registry_lookup(&pos, name)) == NULL) { - git_error_set(GIT_ERROR_FILTER, "cannot find filter '%s' to unregister", name); - error = GIT_ENOTFOUND; - goto done; - } - - git_vector_remove(&filter_registry.filters, pos); - - if (fdef->initialized && fdef->filter && fdef->filter->shutdown) { - fdef->filter->shutdown(fdef->filter); - fdef->initialized = false; - } - - git__free(fdef->filter_name); - git__free(fdef->attrdata); - git__free(fdef); - -done: - git_rwlock_wrunlock(&filter_registry.lock); - return error; -} - -static int filter_initialize(git_filter_def *fdef) -{ - int error = 0; - - if (!fdef->initialized && fdef->filter && fdef->filter->initialize) { - if ((error = fdef->filter->initialize(fdef->filter)) < 0) - return error; - } - - fdef->initialized = true; - return 0; -} - -git_filter *git_filter_lookup(const char *name) -{ - size_t pos; - git_filter_def *fdef; - git_filter *filter = NULL; - - if (git_rwlock_rdlock(&filter_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); - return NULL; - } - - if ((fdef = filter_registry_lookup(&pos, name)) == NULL || - (!fdef->initialized && filter_initialize(fdef) < 0)) - goto done; - - filter = fdef->filter; - -done: - git_rwlock_rdunlock(&filter_registry.lock); - return filter; -} - -void git_filter_free(git_filter *filter) -{ - git__free(filter); -} - -git_repository *git_filter_source_repo(const git_filter_source *src) -{ - return src->repo; -} - -const char *git_filter_source_path(const git_filter_source *src) -{ - return src->path; -} - -uint16_t git_filter_source_filemode(const git_filter_source *src) -{ - return src->filemode; -} - -const git_oid *git_filter_source_id(const git_filter_source *src) -{ - return git_oid_is_zero(&src->oid) ? NULL : &src->oid; -} - -git_filter_mode_t git_filter_source_mode(const git_filter_source *src) -{ - return src->mode; -} - -uint32_t git_filter_source_flags(const git_filter_source *src) -{ - return src->options.flags; -} - -static int filter_list_new( - git_filter_list **out, const git_filter_source *src) -{ - git_filter_list *fl = NULL; - size_t pathlen = src->path ? strlen(src->path) : 0, alloclen; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_filter_list), pathlen); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - - fl = git__calloc(1, alloclen); - GIT_ERROR_CHECK_ALLOC(fl); - - if (src->path) - memcpy(fl->path, src->path, pathlen); - fl->source.repo = src->repo; - fl->source.path = fl->path; - fl->source.mode = src->mode; - - memcpy(&fl->source.options, &src->options, sizeof(git_filter_options)); - - *out = fl; - return 0; -} - -static int filter_list_check_attributes( - const char ***out, - git_repository *repo, - git_filter_session *filter_session, - git_filter_def *fdef, - const git_filter_source *src) -{ - const char **strs = git__calloc(fdef->nattrs, sizeof(const char *)); - git_attr_options attr_opts = GIT_ATTR_OPTIONS_INIT; - size_t i; - int error; - - GIT_ERROR_CHECK_ALLOC(strs); - - if ((src->options.flags & GIT_FILTER_NO_SYSTEM_ATTRIBUTES) != 0) - attr_opts.flags |= GIT_ATTR_CHECK_NO_SYSTEM; - - if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_HEAD) != 0) - attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_HEAD; - - if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_COMMIT) != 0) { - attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_COMMIT; - -#ifndef GIT_DEPRECATE_HARD - if (src->options.commit_id) - git_oid_cpy(&attr_opts.attr_commit_id, src->options.commit_id); - else -#endif - git_oid_cpy(&attr_opts.attr_commit_id, &src->options.attr_commit_id); - } - - error = git_attr_get_many_with_session( - strs, repo, filter_session->attr_session, &attr_opts, src->path, fdef->nattrs, fdef->attrs); - - /* if no values were found but no matches are needed, it's okay! */ - if (error == GIT_ENOTFOUND && !fdef->nmatches) { - git_error_clear(); - git__free((void *)strs); - return 0; - } - - for (i = 0; !error && i < fdef->nattrs; ++i) { - const char *want = fdef->attrs[fdef->nattrs + i]; - git_attr_value_t want_type, found_type; - - if (!want) - continue; - - want_type = git_attr_value(want); - found_type = git_attr_value(strs[i]); - - if (want_type != found_type) - error = GIT_ENOTFOUND; - else if (want_type == GIT_ATTR_VALUE_STRING && - strcmp(want, strs[i]) && - strcmp(want, "*")) - error = GIT_ENOTFOUND; - } - - if (error) - git__free((void *)strs); - else - *out = strs; - - return error; -} - -int git_filter_list_new( - git_filter_list **out, - git_repository *repo, - git_filter_mode_t mode, - uint32_t flags) -{ - git_filter_source src = { 0 }; - src.repo = repo; - src.path = NULL; - src.mode = mode; - src.options.flags = flags; - return filter_list_new(out, &src); -} - -int git_filter_list__load( - git_filter_list **filters, - git_repository *repo, - git_blob *blob, /* can be NULL */ - const char *path, - git_filter_mode_t mode, - git_filter_session *filter_session) -{ - int error = 0; - git_filter_list *fl = NULL; - git_filter_source src = { 0 }; - git_filter_entry *fe; - size_t idx; - git_filter_def *fdef; - - if (git_rwlock_rdlock(&filter_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); - return -1; - } - - src.repo = repo; - src.path = path; - src.mode = mode; - - memcpy(&src.options, &filter_session->options, sizeof(git_filter_options)); - - if (blob) - git_oid_cpy(&src.oid, git_blob_id(blob)); - - git_vector_foreach(&filter_registry.filters, idx, fdef) { - const char **values = NULL; - void *payload = NULL; - - if (!fdef || !fdef->filter) - continue; - - if (fdef->nattrs > 0) { - error = filter_list_check_attributes( - &values, repo, - filter_session, fdef, &src); - - if (error == GIT_ENOTFOUND) { - error = 0; - continue; - } else if (error < 0) - break; - } - - if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) - break; - - if (fdef->filter->check) - error = fdef->filter->check( - fdef->filter, &payload, &src, values); - - git__free((void *)values); - - if (error == GIT_PASSTHROUGH) - error = 0; - else if (error < 0) - break; - else { - if (!fl) { - if ((error = filter_list_new(&fl, &src)) < 0) - break; - - fl->temp_buf = filter_session->temp_buf; - } - - fe = git_array_alloc(fl->filters); - GIT_ERROR_CHECK_ALLOC(fe); - - fe->filter = fdef->filter; - fe->filter_name = fdef->filter_name; - fe->payload = payload; - } - } - - git_rwlock_rdunlock(&filter_registry.lock); - - if (error && fl != NULL) { - git_array_clear(fl->filters); - git__free(fl); - fl = NULL; - } - - *filters = fl; - return error; -} - -int git_filter_list_load_ext( - git_filter_list **filters, - git_repository *repo, - git_blob *blob, /* can be NULL */ - const char *path, - git_filter_mode_t mode, - git_filter_options *opts) -{ - git_filter_session filter_session = GIT_FILTER_SESSION_INIT; - - if (opts) - memcpy(&filter_session.options, opts, sizeof(git_filter_options)); - - return git_filter_list__load( - filters, repo, blob, path, mode, &filter_session); -} - -int git_filter_list_load( - git_filter_list **filters, - git_repository *repo, - git_blob *blob, /* can be NULL */ - const char *path, - git_filter_mode_t mode, - uint32_t flags) -{ - git_filter_session filter_session = GIT_FILTER_SESSION_INIT; - - filter_session.options.flags = flags; - - return git_filter_list__load( - filters, repo, blob, path, mode, &filter_session); -} - -void git_filter_list_free(git_filter_list *fl) -{ - uint32_t i; - - if (!fl) - return; - - for (i = 0; i < git_array_size(fl->filters); ++i) { - git_filter_entry *fe = git_array_get(fl->filters, i); - if (fe->filter->cleanup) - fe->filter->cleanup(fe->filter, fe->payload); - } - - git_array_clear(fl->filters); - git__free(fl); -} - -int git_filter_list_contains( - git_filter_list *fl, - const char *name) -{ - size_t i; - - GIT_ASSERT_ARG(name); - - if (!fl) - return 0; - - for (i = 0; i < fl->filters.size; i++) { - if (strcmp(fl->filters.ptr[i].filter_name, name) == 0) - return 1; - } - - return 0; -} - -int git_filter_list_push( - git_filter_list *fl, git_filter *filter, void *payload) -{ - int error = 0; - size_t pos; - git_filter_def *fdef = NULL; - git_filter_entry *fe; - - GIT_ASSERT_ARG(fl); - GIT_ASSERT_ARG(filter); - - if (git_rwlock_rdlock(&filter_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); - return -1; - } - - if (git_vector_search2( - &pos, &filter_registry.filters, - filter_def_filter_key_check, filter) == 0) - fdef = git_vector_get(&filter_registry.filters, pos); - - git_rwlock_rdunlock(&filter_registry.lock); - - if (fdef == NULL) { - git_error_set(GIT_ERROR_FILTER, "cannot use an unregistered filter"); - return -1; - } - - if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) - return error; - - fe = git_array_alloc(fl->filters); - GIT_ERROR_CHECK_ALLOC(fe); - fe->filter = filter; - fe->payload = payload; - - return 0; -} - -size_t git_filter_list_length(const git_filter_list *fl) -{ - return fl ? git_array_size(fl->filters) : 0; -} - -struct buf_stream { - git_writestream parent; - git_str *target; - bool complete; -}; - -static int buf_stream_write( - git_writestream *s, const char *buffer, size_t len) -{ - struct buf_stream *buf_stream = (struct buf_stream *)s; - GIT_ASSERT_ARG(buf_stream); - GIT_ASSERT(buf_stream->complete == 0); - - return git_str_put(buf_stream->target, buffer, len); -} - -static int buf_stream_close(git_writestream *s) -{ - struct buf_stream *buf_stream = (struct buf_stream *)s; - GIT_ASSERT_ARG(buf_stream); - - GIT_ASSERT(buf_stream->complete == 0); - buf_stream->complete = 1; - - return 0; -} - -static void buf_stream_free(git_writestream *s) -{ - GIT_UNUSED(s); -} - -static void buf_stream_init(struct buf_stream *writer, git_str *target) -{ - memset(writer, 0, sizeof(struct buf_stream)); - - writer->parent.write = buf_stream_write; - writer->parent.close = buf_stream_close; - writer->parent.free = buf_stream_free; - writer->target = target; - - git_str_clear(target); -} - -int git_filter_list_apply_to_buffer( - git_buf *out, - git_filter_list *filters, - const char *in, - size_t in_len) -{ - GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_buffer, filters, in, in_len); -} - -int git_filter_list__apply_to_buffer( - git_str *out, - git_filter_list *filters, - const char *in, - size_t in_len) -{ - struct buf_stream writer; - int error; - - buf_stream_init(&writer, out); - - if ((error = git_filter_list_stream_buffer(filters, - in, in_len, &writer.parent)) < 0) - return error; - - GIT_ASSERT(writer.complete); - return error; -} - -int git_filter_list__convert_buf( - git_str *out, - git_filter_list *filters, - git_str *in) -{ - int error; - - if (!filters || git_filter_list_length(filters) == 0) { - git_str_swap(out, in); - git_str_dispose(in); - return 0; - } - - error = git_filter_list__apply_to_buffer(out, filters, - in->ptr, in->size); - - if (!error) - git_str_dispose(in); - - return error; -} - -int git_filter_list_apply_to_file( - git_buf *out, - git_filter_list *filters, - git_repository *repo, - const char *path) -{ - GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_file, filters, repo, path); -} - -int git_filter_list__apply_to_file( - git_str *out, - git_filter_list *filters, - git_repository *repo, - const char *path) -{ - struct buf_stream writer; - int error; - - buf_stream_init(&writer, out); - - if ((error = git_filter_list_stream_file( - filters, repo, path, &writer.parent)) < 0) - return error; - - GIT_ASSERT(writer.complete); - return error; -} - -static int buf_from_blob(git_str *out, git_blob *blob) -{ - git_object_size_t rawsize = git_blob_rawsize(blob); - - if (!git__is_sizet(rawsize)) { - git_error_set(GIT_ERROR_OS, "blob is too large to filter"); - return -1; - } - - git_str_attach_notowned(out, git_blob_rawcontent(blob), (size_t)rawsize); - return 0; -} - -int git_filter_list_apply_to_blob( - git_buf *out, - git_filter_list *filters, - git_blob *blob) -{ - GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_blob, filters, blob); -} - -int git_filter_list__apply_to_blob( - git_str *out, - git_filter_list *filters, - git_blob *blob) -{ - struct buf_stream writer; - int error; - - buf_stream_init(&writer, out); - - if ((error = git_filter_list_stream_blob( - filters, blob, &writer.parent)) < 0) - return error; - - GIT_ASSERT(writer.complete); - return error; -} - -struct buffered_stream { - git_writestream parent; - git_filter *filter; - int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *); - int (*legacy_write_fn)(git_filter *, void **, git_buf *, const git_buf *, const git_filter_source *); - const git_filter_source *source; - void **payload; - git_str input; - git_str temp_buf; - git_str *output; - git_writestream *target; -}; - -static int buffered_stream_write( - git_writestream *s, const char *buffer, size_t len) -{ - struct buffered_stream *buffered_stream = (struct buffered_stream *)s; - GIT_ASSERT_ARG(buffered_stream); - - return git_str_put(&buffered_stream->input, buffer, len); -} - -static int buffered_stream_close(git_writestream *s) -{ - struct buffered_stream *buffered_stream = (struct buffered_stream *)s; - git_str *writebuf; - git_error_state error_state = {0}; - int error; - - GIT_ASSERT_ARG(buffered_stream); - - error = buffered_stream->write_fn( - buffered_stream->filter, - buffered_stream->payload, - buffered_stream->output, - &buffered_stream->input, - buffered_stream->source); - - if (error == GIT_PASSTHROUGH) { - writebuf = &buffered_stream->input; - } else if (error == 0) { - writebuf = buffered_stream->output; - } else { - /* close stream before erroring out taking care - * to preserve the original error */ - git_error_state_capture(&error_state, error); - buffered_stream->target->close(buffered_stream->target); - git_error_state_restore(&error_state); - return error; - } - - if ((error = buffered_stream->target->write( - buffered_stream->target, writebuf->ptr, writebuf->size)) == 0) - error = buffered_stream->target->close(buffered_stream->target); - - return error; -} - -static void buffered_stream_free(git_writestream *s) -{ - struct buffered_stream *buffered_stream = (struct buffered_stream *)s; - - if (buffered_stream) { - git_str_dispose(&buffered_stream->input); - git_str_dispose(&buffered_stream->temp_buf); - git__free(buffered_stream); - } -} - -int git_filter_buffered_stream_new( - git_writestream **out, - git_filter *filter, - int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *), - git_str *temp_buf, - void **payload, - const git_filter_source *source, - git_writestream *target) -{ - struct buffered_stream *buffered_stream = git__calloc(1, sizeof(struct buffered_stream)); - GIT_ERROR_CHECK_ALLOC(buffered_stream); - - buffered_stream->parent.write = buffered_stream_write; - buffered_stream->parent.close = buffered_stream_close; - buffered_stream->parent.free = buffered_stream_free; - buffered_stream->filter = filter; - buffered_stream->write_fn = write_fn; - buffered_stream->output = temp_buf ? temp_buf : &buffered_stream->temp_buf; - buffered_stream->payload = payload; - buffered_stream->source = source; - buffered_stream->target = target; - - if (temp_buf) - git_str_clear(temp_buf); - - *out = (git_writestream *)buffered_stream; - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -static int buffered_legacy_stream_new( - git_writestream **out, - git_filter *filter, - int (*legacy_write_fn)(git_filter *, void **, git_buf *, const git_buf *, const git_filter_source *), - git_str *temp_buf, - void **payload, - const git_filter_source *source, - git_writestream *target) -{ - struct buffered_stream *buffered_stream = git__calloc(1, sizeof(struct buffered_stream)); - GIT_ERROR_CHECK_ALLOC(buffered_stream); - - buffered_stream->parent.write = buffered_stream_write; - buffered_stream->parent.close = buffered_stream_close; - buffered_stream->parent.free = buffered_stream_free; - buffered_stream->filter = filter; - buffered_stream->legacy_write_fn = legacy_write_fn; - buffered_stream->output = temp_buf ? temp_buf : &buffered_stream->temp_buf; - buffered_stream->payload = payload; - buffered_stream->source = source; - buffered_stream->target = target; - - if (temp_buf) - git_str_clear(temp_buf); - - *out = (git_writestream *)buffered_stream; - return 0; -} -#endif - -static int setup_stream( - git_writestream **out, - git_filter_entry *fe, - git_filter_list *filters, - git_writestream *last_stream) -{ -#ifndef GIT_DEPRECATE_HARD - GIT_ASSERT(fe->filter->stream || fe->filter->apply); - - /* - * If necessary, create a stream that proxies the traditional - * application. - */ - if (!fe->filter->stream) { - /* Create a stream that proxies the one-shot apply */ - return buffered_legacy_stream_new(out, - fe->filter, fe->filter->apply, filters->temp_buf, - &fe->payload, &filters->source, last_stream); - } -#endif - - GIT_ASSERT(fe->filter->stream); - return fe->filter->stream(out, fe->filter, - &fe->payload, &filters->source, last_stream); -} - -static int stream_list_init( - git_writestream **out, - git_vector *streams, - git_filter_list *filters, - git_writestream *target) -{ - git_writestream *last_stream = target; - size_t i; - int error = 0; - - *out = NULL; - - if (!filters) { - *out = target; - return 0; - } - - /* Create filters last to first to get the chaining direction */ - for (i = 0; i < git_array_size(filters->filters); ++i) { - size_t filter_idx = (filters->source.mode == GIT_FILTER_TO_WORKTREE) ? - git_array_size(filters->filters) - 1 - i : i; - - git_filter_entry *fe = git_array_get(filters->filters, filter_idx); - git_writestream *filter_stream; - - error = setup_stream(&filter_stream, fe, filters, last_stream); - - if (error < 0) - goto out; - - git_vector_insert(streams, filter_stream); - last_stream = filter_stream; - } - -out: - if (error) - last_stream->close(last_stream); - else - *out = last_stream; - - return error; -} - -static void filter_streams_free(git_vector *streams) -{ - git_writestream *stream; - size_t i; - - git_vector_foreach(streams, i, stream) - stream->free(stream); - git_vector_free(streams); -} - -int git_filter_list_stream_file( - git_filter_list *filters, - git_repository *repo, - const char *path, - git_writestream *target) -{ - char buf[FILTERIO_BUFSIZE]; - git_str abspath = GIT_STR_INIT; - const char *base = repo ? git_repository_workdir(repo) : NULL; - git_vector filter_streams = GIT_VECTOR_INIT; - git_writestream *stream_start; - ssize_t readlen; - int fd = -1, error, initialized = 0; - - if ((error = stream_list_init( - &stream_start, &filter_streams, filters, target)) < 0 || - (error = git_fs_path_join_unrooted(&abspath, path, base, NULL)) < 0 || - (error = git_path_validate_str_length(repo, &abspath)) < 0) - goto done; - - initialized = 1; - - if ((fd = git_futils_open_ro(abspath.ptr)) < 0) { - error = fd; - goto done; - } - - while ((readlen = p_read(fd, buf, sizeof(buf))) > 0) { - if ((error = stream_start->write(stream_start, buf, readlen)) < 0) - goto done; - } - - if (readlen < 0) - error = -1; - -done: - if (initialized) - error |= stream_start->close(stream_start); - - if (fd >= 0) - p_close(fd); - filter_streams_free(&filter_streams); - git_str_dispose(&abspath); - return error; -} - -int git_filter_list_stream_buffer( - git_filter_list *filters, - const char *buffer, - size_t len, - git_writestream *target) -{ - git_vector filter_streams = GIT_VECTOR_INIT; - git_writestream *stream_start; - int error, initialized = 0; - - if ((error = stream_list_init(&stream_start, &filter_streams, filters, target)) < 0) - goto out; - initialized = 1; - - if ((error = stream_start->write(stream_start, buffer, len)) < 0) - goto out; - -out: - if (initialized) - error |= stream_start->close(stream_start); - - filter_streams_free(&filter_streams); - return error; -} - -int git_filter_list_stream_blob( - git_filter_list *filters, - git_blob *blob, - git_writestream *target) -{ - git_str in = GIT_STR_INIT; - - if (buf_from_blob(&in, blob) < 0) - return -1; - - if (filters) - git_oid_cpy(&filters->source.oid, git_blob_id(blob)); - - return git_filter_list_stream_buffer(filters, in.ptr, in.size, target); -} - -int git_filter_init(git_filter *filter, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE(filter, version, git_filter, GIT_FILTER_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD - -int git_filter_list_stream_data( - git_filter_list *filters, - git_buf *data, - git_writestream *target) -{ - return git_filter_list_stream_buffer(filters, data->ptr, data->size, target); -} - -int git_filter_list_apply_to_data( - git_buf *tgt, git_filter_list *filters, git_buf *src) -{ - return git_filter_list_apply_to_buffer(tgt, filters, src->ptr, src->size); -} - -#endif diff --git a/src/filter.h b/src/filter.h deleted file mode 100644 index 58cb4b424..000000000 --- a/src/filter.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_filter_h__ -#define INCLUDE_filter_h__ - -#include "common.h" - -#include "attr_file.h" -#include "git2/filter.h" -#include "git2/sys/filter.h" - -/* Amount of file to examine for NUL byte when checking binary-ness */ -#define GIT_FILTER_BYTES_TO_CHECK_NUL 8000 - -typedef struct { - git_filter_options options; - git_attr_session *attr_session; - git_str *temp_buf; -} git_filter_session; - -#define GIT_FILTER_SESSION_INIT {GIT_FILTER_OPTIONS_INIT, 0} - -extern int git_filter_global_init(void); - -extern void git_filter_free(git_filter *filter); - -extern int git_filter_list__load( - git_filter_list **filters, - git_repository *repo, - git_blob *blob, /* can be NULL */ - const char *path, - git_filter_mode_t mode, - git_filter_session *filter_session); - -int git_filter_list__apply_to_buffer( - git_str *out, - git_filter_list *filters, - const char *in, - size_t in_len); -int git_filter_list__apply_to_file( - git_str *out, - git_filter_list *filters, - git_repository *repo, - const char *path); -int git_filter_list__apply_to_blob( - git_str *out, - git_filter_list *filters, - git_blob *blob); - -/* - * The given input buffer will be converted to the given output buffer. - * The input buffer will be freed (_if_ it was allocated). - */ -extern int git_filter_list__convert_buf( - git_str *out, - git_filter_list *filters, - git_str *in); - -extern int git_filter_list__apply_to_file( - git_str *out, - git_filter_list *filters, - git_repository *repo, - const char *path); - -/* - * Available filters - */ - -extern git_filter *git_crlf_filter_new(void); -extern git_filter *git_ident_filter_new(void); - -extern int git_filter_buffered_stream_new( - git_writestream **out, - git_filter *filter, - int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *), - git_str *temp_buf, - void **payload, - const git_filter_source *source, - git_writestream *target); - -#endif diff --git a/src/fs_path.c b/src/fs_path.c deleted file mode 100644 index 7a657778a..000000000 --- a/src/fs_path.c +++ /dev/null @@ -1,1912 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "fs_path.h" - -#include "posix.h" -#include "repository.h" -#ifdef GIT_WIN32 -#include "win32/posix.h" -#include "win32/w32_buffer.h" -#include "win32/w32_util.h" -#include "win32/version.h" -#include -#else -#include -#endif -#include -#include - -static int dos_drive_prefix_length(const char *path) -{ - int i; - - /* - * Does it start with an ASCII letter (i.e. highest bit not set), - * followed by a colon? - */ - if (!(0x80 & (unsigned char)*path)) - return *path && path[1] == ':' ? 2 : 0; - - /* - * While drive letters must be letters of the English alphabet, it is - * possible to assign virtually _any_ Unicode character via `subst` as - * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff - * like this: - * - * subst ֍: %USERPROFILE%\Desktop - */ - for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++) - ; /* skip first UTF-8 character */ - return path[i] == ':' ? i + 1 : 0; -} - -#ifdef GIT_WIN32 -static bool looks_like_network_computer_name(const char *path, int pos) -{ - if (pos < 3) - return false; - - if (path[0] != '/' || path[1] != '/') - return false; - - while (pos-- > 2) { - if (path[pos] == '/') - return false; - } - - return true; -} -#endif - -/* - * Based on the Android implementation, BSD licensed. - * http://android.git.kernel.org/ - * - * Copyright (C) 2008 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -int git_fs_path_basename_r(git_str *buffer, const char *path) -{ - const char *endp, *startp; - int len, result; - - /* Empty or NULL string gets treated as "." */ - if (path == NULL || *path == '\0') { - startp = "."; - len = 1; - goto Exit; - } - - /* Strip trailing slashes */ - endp = path + strlen(path) - 1; - while (endp > path && *endp == '/') - endp--; - - /* All slashes becomes "/" */ - if (endp == path && *endp == '/') { - startp = "/"; - len = 1; - goto Exit; - } - - /* Find the start of the base */ - startp = endp; - while (startp > path && *(startp - 1) != '/') - startp--; - - /* Cast is safe because max path < max int */ - len = (int)(endp - startp + 1); - -Exit: - result = len; - - if (buffer != NULL && git_str_set(buffer, startp, len) < 0) - return -1; - - return result; -} - -/* - * Determine if the path is a Windows prefix and, if so, returns - * its actual length. If it is not a prefix, returns -1. - */ -static int win32_prefix_length(const char *path, int len) -{ -#ifndef GIT_WIN32 - GIT_UNUSED(path); - GIT_UNUSED(len); -#else - /* - * Mimic unix behavior where '/.git' returns '/': 'C:/.git' - * will return 'C:/' here - */ - if (dos_drive_prefix_length(path) == len) - return len; - - /* - * Similarly checks if we're dealing with a network computer name - * '//computername/.git' will return '//computername/' - */ - if (looks_like_network_computer_name(path, len)) - return len; -#endif - - return -1; -} - -/* - * Based on the Android implementation, BSD licensed. - * Check http://android.git.kernel.org/ - */ -int git_fs_path_dirname_r(git_str *buffer, const char *path) -{ - const char *endp; - int is_prefix = 0, len; - - /* Empty or NULL string gets treated as "." */ - if (path == NULL || *path == '\0') { - path = "."; - len = 1; - goto Exit; - } - - /* Strip trailing slashes */ - endp = path + strlen(path) - 1; - while (endp > path && *endp == '/') - endp--; - - if (endp - path + 1 > INT_MAX) { - git_error_set(GIT_ERROR_INVALID, "path too long"); - len = -1; - goto Exit; - } - - if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) { - is_prefix = 1; - goto Exit; - } - - /* Find the start of the dir */ - while (endp > path && *endp != '/') - endp--; - - /* Either the dir is "/" or there are no slashes */ - if (endp == path) { - path = (*endp == '/') ? "/" : "."; - len = 1; - goto Exit; - } - - do { - endp--; - } while (endp > path && *endp == '/'); - - if (endp - path + 1 > INT_MAX) { - git_error_set(GIT_ERROR_INVALID, "path too long"); - len = -1; - goto Exit; - } - - if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) { - is_prefix = 1; - goto Exit; - } - - /* Cast is safe because max path < max int */ - len = (int)(endp - path + 1); - -Exit: - if (buffer) { - if (git_str_set(buffer, path, len) < 0) - return -1; - if (is_prefix && git_str_putc(buffer, '/') < 0) - return -1; - } - - return len; -} - - -char *git_fs_path_dirname(const char *path) -{ - git_str buf = GIT_STR_INIT; - char *dirname; - - git_fs_path_dirname_r(&buf, path); - dirname = git_str_detach(&buf); - git_str_dispose(&buf); /* avoid memleak if error occurs */ - - return dirname; -} - -char *git_fs_path_basename(const char *path) -{ - git_str buf = GIT_STR_INIT; - char *basename; - - git_fs_path_basename_r(&buf, path); - basename = git_str_detach(&buf); - git_str_dispose(&buf); /* avoid memleak if error occurs */ - - return basename; -} - -size_t git_fs_path_basename_offset(git_str *buffer) -{ - ssize_t slash; - - if (!buffer || buffer->size <= 0) - return 0; - - slash = git_str_rfind_next(buffer, '/'); - - if (slash >= 0 && buffer->ptr[slash] == '/') - return (size_t)(slash + 1); - - return 0; -} - -int git_fs_path_root(const char *path) -{ - int offset = 0, prefix_len; - - /* Does the root of the path look like a windows drive ? */ - if ((prefix_len = dos_drive_prefix_length(path))) - offset += prefix_len; - -#ifdef GIT_WIN32 - /* Are we dealing with a windows network path? */ - else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') || - (path[0] == '\\' && path[1] == '\\' && path[2] != '\\')) - { - offset += 2; - - /* Skip the computer name segment */ - while (path[offset] && path[offset] != '/' && path[offset] != '\\') - offset++; - } - - if (path[offset] == '\\') - return offset; -#endif - - if (path[offset] == '/') - return offset; - - return -1; /* Not a real error - signals that path is not rooted */ -} - -static void path_trim_slashes(git_str *path) -{ - int ceiling = git_fs_path_root(path->ptr) + 1; - - if (ceiling < 0) - return; - - while (path->size > (size_t)ceiling) { - if (path->ptr[path->size-1] != '/') - break; - - path->ptr[path->size-1] = '\0'; - path->size--; - } -} - -int git_fs_path_join_unrooted( - git_str *path_out, const char *path, const char *base, ssize_t *root_at) -{ - ssize_t root; - - GIT_ASSERT_ARG(path_out); - GIT_ASSERT_ARG(path); - - root = (ssize_t)git_fs_path_root(path); - - if (base != NULL && root < 0) { - if (git_str_joinpath(path_out, base, path) < 0) - return -1; - - root = (ssize_t)strlen(base); - } else { - if (git_str_sets(path_out, path) < 0) - return -1; - - if (root < 0) - root = 0; - else if (base) - git_fs_path_equal_or_prefixed(base, path, &root); - } - - if (root_at) - *root_at = root; - - return 0; -} - -void git_fs_path_squash_slashes(git_str *path) -{ - char *p, *q; - - if (path->size == 0) - return; - - for (p = path->ptr, q = path->ptr; *q; p++, q++) { - *p = *q; - - while (*q == '/' && *(q+1) == '/') { - path->size--; - q++; - } - } - - *p = '\0'; -} - -int git_fs_path_prettify(git_str *path_out, const char *path, const char *base) -{ - char buf[GIT_PATH_MAX]; - - GIT_ASSERT_ARG(path_out); - GIT_ASSERT_ARG(path); - - /* construct path if needed */ - if (base != NULL && git_fs_path_root(path) < 0) { - if (git_str_joinpath(path_out, base, path) < 0) - return -1; - path = path_out->ptr; - } - - if (p_realpath(path, buf) == NULL) { - /* git_error_set resets the errno when dealing with a GIT_ERROR_OS kind of error */ - int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1; - git_error_set(GIT_ERROR_OS, "failed to resolve path '%s'", path); - - git_str_clear(path_out); - - return error; - } - - return git_str_sets(path_out, buf); -} - -int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base) -{ - int error = git_fs_path_prettify(path_out, path, base); - return (error < 0) ? error : git_fs_path_to_dir(path_out); -} - -int git_fs_path_to_dir(git_str *path) -{ - if (path->asize > 0 && - git_str_len(path) > 0 && - path->ptr[git_str_len(path) - 1] != '/') - git_str_putc(path, '/'); - - return git_str_oom(path) ? -1 : 0; -} - -void git_fs_path_string_to_dir(char *path, size_t size) -{ - size_t end = strlen(path); - - if (end && path[end - 1] != '/' && end < size) { - path[end] = '/'; - path[end + 1] = '\0'; - } -} - -int git__percent_decode(git_str *decoded_out, const char *input) -{ - int len, hi, lo, i; - - GIT_ASSERT_ARG(decoded_out); - GIT_ASSERT_ARG(input); - - len = (int)strlen(input); - git_str_clear(decoded_out); - - for(i = 0; i < len; i++) - { - char c = input[i]; - - if (c != '%') - goto append; - - if (i >= len - 2) - goto append; - - hi = git__fromhex(input[i + 1]); - lo = git__fromhex(input[i + 2]); - - if (hi < 0 || lo < 0) - goto append; - - c = (char)(hi << 4 | lo); - i += 2; - -append: - if (git_str_putc(decoded_out, c) < 0) - return -1; - } - - return 0; -} - -static int error_invalid_local_file_uri(const char *uri) -{ - git_error_set(GIT_ERROR_CONFIG, "'%s' is not a valid local file URI", uri); - return -1; -} - -static int local_file_url_prefixlen(const char *file_url) -{ - int len = -1; - - if (git__prefixcmp(file_url, "file://") == 0) { - if (file_url[7] == '/') - len = 8; - else if (git__prefixcmp(file_url + 7, "localhost/") == 0) - len = 17; - } - - return len; -} - -bool git_fs_path_is_local_file_url(const char *file_url) -{ - return (local_file_url_prefixlen(file_url) > 0); -} - -int git_fs_path_fromurl(git_str *local_path_out, const char *file_url) -{ - int offset; - - GIT_ASSERT_ARG(local_path_out); - GIT_ASSERT_ARG(file_url); - - if ((offset = local_file_url_prefixlen(file_url)) < 0 || - file_url[offset] == '\0' || file_url[offset] == '/') - return error_invalid_local_file_uri(file_url); - -#ifndef GIT_WIN32 - offset--; /* A *nix absolute path starts with a forward slash */ -#endif - - git_str_clear(local_path_out); - return git__percent_decode(local_path_out, file_url + offset); -} - -int git_fs_path_walk_up( - git_str *path, - const char *ceiling, - int (*cb)(void *data, const char *), - void *data) -{ - int error = 0; - git_str iter; - ssize_t stop = 0, scan; - char oldc = '\0'; - - GIT_ASSERT_ARG(path); - GIT_ASSERT_ARG(cb); - - if (ceiling != NULL) { - if (git__prefixcmp(path->ptr, ceiling) == 0) - stop = (ssize_t)strlen(ceiling); - else - stop = git_str_len(path); - } - scan = git_str_len(path); - - /* empty path: yield only once */ - if (!scan) { - error = cb(data, ""); - if (error) - git_error_set_after_callback(error); - return error; - } - - iter.ptr = path->ptr; - iter.size = git_str_len(path); - iter.asize = path->asize; - - while (scan >= stop) { - error = cb(data, iter.ptr); - iter.ptr[scan] = oldc; - - if (error) { - git_error_set_after_callback(error); - break; - } - - scan = git_str_rfind_next(&iter, '/'); - if (scan >= 0) { - scan++; - oldc = iter.ptr[scan]; - iter.size = scan; - iter.ptr[scan] = '\0'; - } - } - - if (scan >= 0) - iter.ptr[scan] = oldc; - - /* relative path: yield for the last component */ - if (!error && stop == 0 && iter.ptr[0] != '/') { - error = cb(data, ""); - if (error) - git_error_set_after_callback(error); - } - - return error; -} - -bool git_fs_path_exists(const char *path) -{ - GIT_ASSERT_ARG_WITH_RETVAL(path, false); - return p_access(path, F_OK) == 0; -} - -bool git_fs_path_isdir(const char *path) -{ - struct stat st; - if (p_stat(path, &st) < 0) - return false; - - return S_ISDIR(st.st_mode) != 0; -} - -bool git_fs_path_isfile(const char *path) -{ - struct stat st; - - GIT_ASSERT_ARG_WITH_RETVAL(path, false); - if (p_stat(path, &st) < 0) - return false; - - return S_ISREG(st.st_mode) != 0; -} - -bool git_fs_path_islink(const char *path) -{ - struct stat st; - - GIT_ASSERT_ARG_WITH_RETVAL(path, false); - if (p_lstat(path, &st) < 0) - return false; - - return S_ISLNK(st.st_mode) != 0; -} - -#ifdef GIT_WIN32 - -bool git_fs_path_is_empty_dir(const char *path) -{ - git_win32_path filter_w; - bool empty = false; - - if (git_win32__findfirstfile_filter(filter_w, path)) { - WIN32_FIND_DATAW findData; - HANDLE hFind = FindFirstFileW(filter_w, &findData); - - /* FindFirstFile will fail if there are no children to the given - * path, which can happen if the given path is a file (and obviously - * has no children) or if the given path is an empty mount point. - * (Most directories have at least directory entries '.' and '..', - * but ridiculously another volume mounted in another drive letter's - * path space do not, and thus have nothing to enumerate.) If - * FindFirstFile fails, check if this is a directory-like thing - * (a mount point). - */ - if (hFind == INVALID_HANDLE_VALUE) - return git_fs_path_isdir(path); - - /* If the find handle was created successfully, then it's a directory */ - empty = true; - - do { - /* Allow the enumeration to return . and .. and still be considered - * empty. In the special case of drive roots (i.e. C:\) where . and - * .. do not occur, we can still consider the path to be an empty - * directory if there's nothing there. */ - if (!git_fs_path_is_dot_or_dotdotW(findData.cFileName)) { - empty = false; - break; - } - } while (FindNextFileW(hFind, &findData)); - - FindClose(hFind); - } - - return empty; -} - -#else - -static int path_found_entry(void *payload, git_str *path) -{ - GIT_UNUSED(payload); - return !git_fs_path_is_dot_or_dotdot(path->ptr); -} - -bool git_fs_path_is_empty_dir(const char *path) -{ - int error; - git_str dir = GIT_STR_INIT; - - if (!git_fs_path_isdir(path)) - return false; - - if ((error = git_str_sets(&dir, path)) != 0) - git_error_clear(); - else - error = git_fs_path_direach(&dir, 0, path_found_entry, NULL); - - git_str_dispose(&dir); - - return !error; -} - -#endif - -int git_fs_path_set_error(int errno_value, const char *path, const char *action) -{ - switch (errno_value) { - case ENOENT: - case ENOTDIR: - git_error_set(GIT_ERROR_OS, "could not find '%s' to %s", path, action); - return GIT_ENOTFOUND; - - case EINVAL: - case ENAMETOOLONG: - git_error_set(GIT_ERROR_OS, "invalid path for filesystem '%s'", path); - return GIT_EINVALIDSPEC; - - case EEXIST: - git_error_set(GIT_ERROR_OS, "failed %s - '%s' already exists", action, path); - return GIT_EEXISTS; - - case EACCES: - git_error_set(GIT_ERROR_OS, "failed %s - '%s' is locked", action, path); - return GIT_ELOCKED; - - default: - git_error_set(GIT_ERROR_OS, "could not %s '%s'", action, path); - return -1; - } -} - -int git_fs_path_lstat(const char *path, struct stat *st) -{ - if (p_lstat(path, st) == 0) - return 0; - - return git_fs_path_set_error(errno, path, "stat"); -} - -static bool _check_dir_contents( - git_str *dir, - const char *sub, - bool (*predicate)(const char *)) -{ - bool result; - size_t dir_size = git_str_len(dir); - size_t sub_size = strlen(sub); - size_t alloc_size; - - /* leave base valid even if we could not make space for subdir */ - if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, dir_size, sub_size) || - GIT_ADD_SIZET_OVERFLOW(&alloc_size, alloc_size, 2) || - git_str_try_grow(dir, alloc_size, false) < 0) - return false; - - /* save excursion */ - if (git_str_joinpath(dir, dir->ptr, sub) < 0) - return false; - - result = predicate(dir->ptr); - - /* restore path */ - git_str_truncate(dir, dir_size); - return result; -} - -bool git_fs_path_contains(git_str *dir, const char *item) -{ - return _check_dir_contents(dir, item, &git_fs_path_exists); -} - -bool git_fs_path_contains_dir(git_str *base, const char *subdir) -{ - return _check_dir_contents(base, subdir, &git_fs_path_isdir); -} - -bool git_fs_path_contains_file(git_str *base, const char *file) -{ - return _check_dir_contents(base, file, &git_fs_path_isfile); -} - -int git_fs_path_find_dir(git_str *dir) -{ - int error = 0; - char buf[GIT_PATH_MAX]; - - if (p_realpath(dir->ptr, buf) != NULL) - error = git_str_sets(dir, buf); - - /* call dirname if this is not a directory */ - if (!error) /* && git_fs_path_isdir(dir->ptr) == false) */ - error = (git_fs_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0; - - if (!error) - error = git_fs_path_to_dir(dir); - - return error; -} - -int git_fs_path_resolve_relative(git_str *path, size_t ceiling) -{ - char *base, *to, *from, *next; - size_t len; - - GIT_ERROR_CHECK_ALLOC_STR(path); - - if (ceiling > path->size) - ceiling = path->size; - - /* recognize drive prefixes, etc. that should not be backed over */ - if (ceiling == 0) - ceiling = git_fs_path_root(path->ptr) + 1; - - /* recognize URL prefixes that should not be backed over */ - if (ceiling == 0) { - for (next = path->ptr; *next && git__isalpha(*next); ++next); - if (next[0] == ':' && next[1] == '/' && next[2] == '/') - ceiling = (next + 3) - path->ptr; - } - - base = to = from = path->ptr + ceiling; - - while (*from) { - for (next = from; *next && *next != '/'; ++next); - - len = next - from; - - if (len == 1 && from[0] == '.') - /* do nothing with singleton dot */; - - else if (len == 2 && from[0] == '.' && from[1] == '.') { - /* error out if trying to up one from a hard base */ - if (to == base && ceiling != 0) { - git_error_set(GIT_ERROR_INVALID, - "cannot strip root component off url"); - return -1; - } - - /* no more path segments to strip, - * use '../' as a new base path */ - if (to == base) { - if (*next == '/') - len++; - - if (to != from) - memmove(to, from, len); - - to += len; - /* this is now the base, can't back up from a - * relative prefix */ - base = to; - } else { - /* back up a path segment */ - while (to > base && to[-1] == '/') to--; - while (to > base && to[-1] != '/') to--; - } - } else { - if (*next == '/' && *from != '/') - len++; - - if (to != from) - memmove(to, from, len); - - to += len; - } - - from += len; - - while (*from == '/') from++; - } - - *to = '\0'; - - path->size = to - path->ptr; - - return 0; -} - -int git_fs_path_apply_relative(git_str *target, const char *relpath) -{ - return git_str_joinpath(target, git_str_cstr(target), relpath) || - git_fs_path_resolve_relative(target, 0); -} - -int git_fs_path_cmp( - const char *name1, size_t len1, int isdir1, - const char *name2, size_t len2, int isdir2, - int (*compare)(const char *, const char *, size_t)) -{ - unsigned char c1, c2; - size_t len = len1 < len2 ? len1 : len2; - int cmp; - - cmp = compare(name1, name2, len); - if (cmp) - return cmp; - - c1 = name1[len]; - c2 = name2[len]; - - if (c1 == '\0' && isdir1) - c1 = '/'; - - if (c2 == '\0' && isdir2) - c2 = '/'; - - return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; -} - -size_t git_fs_path_common_dirlen(const char *one, const char *two) -{ - const char *p, *q, *dirsep = NULL; - - for (p = one, q = two; *p && *q; p++, q++) { - if (*p == '/' && *q == '/') - dirsep = p; - else if (*p != *q) - break; - } - - return dirsep ? (dirsep - one) + 1 : 0; -} - -int git_fs_path_make_relative(git_str *path, const char *parent) -{ - const char *p, *q, *p_dirsep, *q_dirsep; - size_t plen = path->size, newlen, alloclen, depth = 1, i, offset; - - for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) { - if (*p == '/' && *q == '/') { - p_dirsep = p; - q_dirsep = q; - } - else if (*p != *q) - break; - } - - /* need at least 1 common path segment */ - if ((p_dirsep == path->ptr || q_dirsep == parent) && - (*p_dirsep != '/' || *q_dirsep != '/')) { - git_error_set(GIT_ERROR_INVALID, - "%s is not a parent of %s", parent, path->ptr); - return GIT_ENOTFOUND; - } - - if (*p == '/' && !*q) - p++; - else if (!*p && *q == '/') - q++; - else if (!*p && !*q) - return git_str_clear(path), 0; - else { - p = p_dirsep + 1; - q = q_dirsep + 1; - } - - plen -= (p - path->ptr); - - if (!*q) - return git_str_set(path, p, plen); - - for (; (q = strchr(q, '/')) && *(q + 1); q++) - depth++; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3); - GIT_ERROR_CHECK_ALLOC_ADD(&newlen, newlen, plen); - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, newlen, 1); - - /* save the offset as we might realllocate the pointer */ - offset = p - path->ptr; - if (git_str_try_grow(path, alloclen, 1) < 0) - return -1; - p = path->ptr + offset; - - memmove(path->ptr + (depth * 3), p, plen + 1); - - for (i = 0; i < depth; i++) - memcpy(path->ptr + (i * 3), "../", 3); - - path->size = newlen; - return 0; -} - -bool git_fs_path_has_non_ascii(const char *path, size_t pathlen) -{ - const uint8_t *scan = (const uint8_t *)path, *end; - - for (end = scan + pathlen; scan < end; ++scan) - if (*scan & 0x80) - return true; - - return false; -} - -#ifdef GIT_USE_ICONV - -int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic) -{ - git_str_init(&ic->buf, 0); - ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING); - return 0; -} - -void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic) -{ - if (ic) { - if (ic->map != (iconv_t)-1) - iconv_close(ic->map); - git_str_dispose(&ic->buf); - } -} - -int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen) -{ - char *nfd = (char*)*in, *nfc; - size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, alloclen, rv; - int retry = 1; - - if (!ic || ic->map == (iconv_t)-1 || - !git_fs_path_has_non_ascii(*in, *inlen)) - return 0; - - git_str_clear(&ic->buf); - - while (1) { - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, wantlen, 1); - if (git_str_grow(&ic->buf, alloclen) < 0) - return -1; - - nfc = ic->buf.ptr + ic->buf.size; - nfclen = ic->buf.asize - ic->buf.size; - - rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen); - - ic->buf.size = (nfc - ic->buf.ptr); - - if (rv != (size_t)-1) - break; - - /* if we cannot convert the data (probably because iconv thinks - * it is not valid UTF-8 source data), then use original data - */ - if (errno != E2BIG) - return 0; - - /* make space for 2x the remaining data to be converted - * (with per retry overhead to avoid infinite loops) - */ - wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4); - - if (retry++ > 4) - goto fail; - } - - ic->buf.ptr[ic->buf.size] = '\0'; - - *in = ic->buf.ptr; - *inlen = ic->buf.size; - - return 0; - -fail: - git_error_set(GIT_ERROR_OS, "unable to convert unicode path data"); - return -1; -} - -static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; -static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; - -/* Check if the platform is decomposing unicode data for us. We will - * emulate core Git and prefer to use precomposed unicode data internally - * on these platforms, composing the decomposed unicode on the fly. - * - * This mainly happens on the Mac where HDFS stores filenames as - * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will - * return decomposed unicode from readdir() even when the actual - * filesystem is storing precomposed unicode. - */ -bool git_fs_path_does_decompose_unicode(const char *root) -{ - git_str nfc_path = GIT_STR_INIT; - git_str nfd_path = GIT_STR_INIT; - int fd; - bool found_decomposed = false; - size_t orig_len; - const char *trailer; - - /* Create a file using a precomposed path and then try to find it - * using the decomposed name. If the lookup fails, then we will mark - * that we should precompose unicode for this repository. - */ - if (git_str_joinpath(&nfc_path, root, nfc_file) < 0) - goto done; - - /* record original path length before trailer */ - orig_len = nfc_path.size; - - if ((fd = git_futils_mktmp(&nfc_path, nfc_path.ptr, 0666)) < 0) - goto done; - p_close(fd); - - trailer = nfc_path.ptr + orig_len; - - /* try to look up as NFD path */ - if (git_str_joinpath(&nfd_path, root, nfd_file) < 0 || - git_str_puts(&nfd_path, trailer) < 0) - goto done; - - found_decomposed = git_fs_path_exists(nfd_path.ptr); - - /* remove temporary file (using original precomposed path) */ - (void)p_unlink(nfc_path.ptr); - -done: - git_str_dispose(&nfc_path); - git_str_dispose(&nfd_path); - return found_decomposed; -} - -#else - -bool git_fs_path_does_decompose_unicode(const char *root) -{ - GIT_UNUSED(root); - return false; -} - -#endif - -#if defined(__sun) || defined(__GNU__) -typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1]; -#else -typedef struct dirent path_dirent_data; -#endif - -int git_fs_path_direach( - git_str *path, - uint32_t flags, - int (*fn)(void *, git_str *), - void *arg) -{ - int error = 0; - ssize_t wd_len; - DIR *dir; - struct dirent *de; - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT; -#endif - - GIT_UNUSED(flags); - - if (git_fs_path_to_dir(path) < 0) - return -1; - - wd_len = git_str_len(path); - - if ((dir = opendir(path->ptr)) == NULL) { - git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path->ptr); - if (errno == ENOENT) - return GIT_ENOTFOUND; - - return -1; - } - -#ifdef GIT_USE_ICONV - if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0) - (void)git_fs_path_iconv_init_precompose(&ic); -#endif - - while ((de = readdir(dir)) != NULL) { - const char *de_path = de->d_name; - size_t de_len = strlen(de_path); - - if (git_fs_path_is_dot_or_dotdot(de_path)) - continue; - -#ifdef GIT_USE_ICONV - if ((error = git_fs_path_iconv(&ic, &de_path, &de_len)) < 0) - break; -#endif - - if ((error = git_str_put(path, de_path, de_len)) < 0) - break; - - git_error_clear(); - error = fn(arg, path); - - git_str_truncate(path, wd_len); /* restore path */ - - /* Only set our own error if the callback did not set one already */ - if (error != 0) { - if (!git_error_last()) - git_error_set_after_callback(error); - - break; - } - } - - closedir(dir); - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_clear(&ic); -#endif - - return error; -} - -#if defined(GIT_WIN32) && !defined(__MINGW32__) - -/* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7 - * and better. - */ -#ifndef FIND_FIRST_EX_LARGE_FETCH -# define FIND_FIRST_EX_LARGE_FETCH 2 -#endif - -int git_fs_path_diriter_init( - git_fs_path_diriter *diriter, - const char *path, - unsigned int flags) -{ - git_win32_path path_filter; - - static int is_win7_or_later = -1; - if (is_win7_or_later < 0) - is_win7_or_later = git_has_win32_version(6, 1, 0); - - GIT_ASSERT_ARG(diriter); - GIT_ASSERT_ARG(path); - - memset(diriter, 0, sizeof(git_fs_path_diriter)); - diriter->handle = INVALID_HANDLE_VALUE; - - if (git_str_puts(&diriter->path_utf8, path) < 0) - return -1; - - path_trim_slashes(&diriter->path_utf8); - - if (diriter->path_utf8.size == 0) { - git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path); - return -1; - } - - if ((diriter->parent_len = git_win32_path_from_utf8(diriter->path, diriter->path_utf8.ptr)) < 0 || - !git_win32__findfirstfile_filter(path_filter, diriter->path_utf8.ptr)) { - git_error_set(GIT_ERROR_OS, "could not parse the directory path '%s'", path); - return -1; - } - - diriter->handle = FindFirstFileExW( - path_filter, - is_win7_or_later ? FindExInfoBasic : FindExInfoStandard, - &diriter->current, - FindExSearchNameMatch, - NULL, - is_win7_or_later ? FIND_FIRST_EX_LARGE_FETCH : 0); - - if (diriter->handle == INVALID_HANDLE_VALUE) { - git_error_set(GIT_ERROR_OS, "could not open directory '%s'", path); - return -1; - } - - diriter->parent_utf8_len = diriter->path_utf8.size; - diriter->flags = flags; - return 0; -} - -static int diriter_update_paths(git_fs_path_diriter *diriter) -{ - size_t filename_len, path_len; - - filename_len = wcslen(diriter->current.cFileName); - - if (GIT_ADD_SIZET_OVERFLOW(&path_len, diriter->parent_len, filename_len) || - GIT_ADD_SIZET_OVERFLOW(&path_len, path_len, 2)) - return -1; - - if (path_len > GIT_WIN_PATH_UTF16) { - git_error_set(GIT_ERROR_FILESYSTEM, - "invalid path '%.*ls\\%ls' (path too long)", - diriter->parent_len, diriter->path, diriter->current.cFileName); - return -1; - } - - diriter->path[diriter->parent_len] = L'\\'; - memcpy(&diriter->path[diriter->parent_len+1], - diriter->current.cFileName, filename_len * sizeof(wchar_t)); - diriter->path[path_len-1] = L'\0'; - - git_str_truncate(&diriter->path_utf8, diriter->parent_utf8_len); - - if (diriter->parent_utf8_len > 0 && - diriter->path_utf8.ptr[diriter->parent_utf8_len-1] != '/') - git_str_putc(&diriter->path_utf8, '/'); - - git_str_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len); - - if (git_str_oom(&diriter->path_utf8)) - return -1; - - return 0; -} - -int git_fs_path_diriter_next(git_fs_path_diriter *diriter) -{ - bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT); - - do { - /* Our first time through, we already have the data from - * FindFirstFileW. Use it, otherwise get the next file. - */ - if (!diriter->needs_next) - diriter->needs_next = 1; - else if (!FindNextFileW(diriter->handle, &diriter->current)) - return GIT_ITEROVER; - } while (skip_dot && git_fs_path_is_dot_or_dotdotW(diriter->current.cFileName)); - - if (diriter_update_paths(diriter) < 0) - return -1; - - return 0; -} - -int git_fs_path_diriter_filename( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(out_len); - GIT_ASSERT_ARG(diriter); - GIT_ASSERT(diriter->path_utf8.size > diriter->parent_utf8_len); - - *out = &diriter->path_utf8.ptr[diriter->parent_utf8_len+1]; - *out_len = diriter->path_utf8.size - diriter->parent_utf8_len - 1; - return 0; -} - -int git_fs_path_diriter_fullpath( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(out_len); - GIT_ASSERT_ARG(diriter); - - *out = diriter->path_utf8.ptr; - *out_len = diriter->path_utf8.size; - return 0; -} - -int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diriter); - - return git_win32__file_attribute_to_stat(out, - (WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current, - diriter->path); -} - -void git_fs_path_diriter_free(git_fs_path_diriter *diriter) -{ - if (diriter == NULL) - return; - - git_str_dispose(&diriter->path_utf8); - - if (diriter->handle != INVALID_HANDLE_VALUE) { - FindClose(diriter->handle); - diriter->handle = INVALID_HANDLE_VALUE; - } -} - -#else - -int git_fs_path_diriter_init( - git_fs_path_diriter *diriter, - const char *path, - unsigned int flags) -{ - GIT_ASSERT_ARG(diriter); - GIT_ASSERT_ARG(path); - - memset(diriter, 0, sizeof(git_fs_path_diriter)); - - if (git_str_puts(&diriter->path, path) < 0) - return -1; - - path_trim_slashes(&diriter->path); - - if (diriter->path.size == 0) { - git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path); - return -1; - } - - if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) { - git_str_dispose(&diriter->path); - - git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path); - return -1; - } - -#ifdef GIT_USE_ICONV - if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0) - (void)git_fs_path_iconv_init_precompose(&diriter->ic); -#endif - - diriter->parent_len = diriter->path.size; - diriter->flags = flags; - - return 0; -} - -int git_fs_path_diriter_next(git_fs_path_diriter *diriter) -{ - struct dirent *de; - const char *filename; - size_t filename_len; - bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT); - int error = 0; - - GIT_ASSERT_ARG(diriter); - - errno = 0; - - do { - if ((de = readdir(diriter->dir)) == NULL) { - if (!errno) - return GIT_ITEROVER; - - git_error_set(GIT_ERROR_OS, - "could not read directory '%s'", diriter->path.ptr); - return -1; - } - } while (skip_dot && git_fs_path_is_dot_or_dotdot(de->d_name)); - - filename = de->d_name; - filename_len = strlen(filename); - -#ifdef GIT_USE_ICONV - if ((diriter->flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0 && - (error = git_fs_path_iconv(&diriter->ic, &filename, &filename_len)) < 0) - return error; -#endif - - git_str_truncate(&diriter->path, diriter->parent_len); - - if (diriter->parent_len > 0 && - diriter->path.ptr[diriter->parent_len-1] != '/') - git_str_putc(&diriter->path, '/'); - - git_str_put(&diriter->path, filename, filename_len); - - if (git_str_oom(&diriter->path)) - return -1; - - return error; -} - -int git_fs_path_diriter_filename( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(out_len); - GIT_ASSERT_ARG(diriter); - GIT_ASSERT(diriter->path.size > diriter->parent_len); - - *out = &diriter->path.ptr[diriter->parent_len+1]; - *out_len = diriter->path.size - diriter->parent_len - 1; - return 0; -} - -int git_fs_path_diriter_fullpath( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(out_len); - GIT_ASSERT_ARG(diriter); - - *out = diriter->path.ptr; - *out_len = diriter->path.size; - return 0; -} - -int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diriter); - - return git_fs_path_lstat(diriter->path.ptr, out); -} - -void git_fs_path_diriter_free(git_fs_path_diriter *diriter) -{ - if (diriter == NULL) - return; - - if (diriter->dir) { - closedir(diriter->dir); - diriter->dir = NULL; - } - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_clear(&diriter->ic); -#endif - - git_str_dispose(&diriter->path); -} - -#endif - -int git_fs_path_dirload( - git_vector *contents, - const char *path, - size_t prefix_len, - uint32_t flags) -{ - git_fs_path_diriter iter = GIT_FS_PATH_DIRITER_INIT; - const char *name; - size_t name_len; - char *dup; - int error; - - GIT_ASSERT_ARG(contents); - GIT_ASSERT_ARG(path); - - if ((error = git_fs_path_diriter_init(&iter, path, flags)) < 0) - return error; - - while ((error = git_fs_path_diriter_next(&iter)) == 0) { - if ((error = git_fs_path_diriter_fullpath(&name, &name_len, &iter)) < 0) - break; - - GIT_ASSERT(name_len > prefix_len); - - dup = git__strndup(name + prefix_len, name_len - prefix_len); - GIT_ERROR_CHECK_ALLOC(dup); - - if ((error = git_vector_insert(contents, dup)) < 0) - break; - } - - if (error == GIT_ITEROVER) - error = 0; - - git_fs_path_diriter_free(&iter); - return error; -} - -int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path) -{ - if (git_fs_path_is_local_file_url(url_or_path)) - return git_fs_path_fromurl(local_path_out, url_or_path); - else - return git_str_sets(local_path_out, url_or_path); -} - -/* Reject paths like AUX or COM1, or those versions that end in a dot or - * colon. ("AUX." or "AUX:") - */ -GIT_INLINE(bool) validate_dospath( - const char *component, - size_t len, - const char dospath[3], - bool trailing_num) -{ - size_t last = trailing_num ? 4 : 3; - - if (len < last || git__strncasecmp(component, dospath, 3) != 0) - return true; - - if (trailing_num && (component[3] < '1' || component[3] > '9')) - return true; - - return (len > last && - component[last] != '.' && - component[last] != ':'); -} - -GIT_INLINE(bool) validate_char(unsigned char c, unsigned int flags) -{ - if ((flags & GIT_FS_PATH_REJECT_BACKSLASH) && c == '\\') - return false; - - if ((flags & GIT_FS_PATH_REJECT_SLASH) && c == '/') - return false; - - if (flags & GIT_FS_PATH_REJECT_NT_CHARS) { - if (c < 32) - return false; - - switch (c) { - case '<': - case '>': - case ':': - case '"': - case '|': - case '?': - case '*': - return false; - } - } - - return true; -} - -/* - * We fundamentally don't like some paths when dealing with user-inputted - * strings (to avoid escaping a sandbox): we don't want dot or dot-dot - * anywhere, we want to avoid writing weird paths on Windows that can't - * be handled by tools that use the non-\\?\ APIs, we don't want slashes - * or double slashes at the end of paths that can make them ambiguous. - * - * For checkout, we don't want to recurse into ".git" either. - */ -static bool validate_component( - const char *component, - size_t len, - unsigned int flags) -{ - if (len == 0) - return !(flags & GIT_FS_PATH_REJECT_EMPTY_COMPONENT); - - if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) && - len == 1 && component[0] == '.') - return false; - - if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) && - len == 2 && component[0] == '.' && component[1] == '.') - return false; - - if ((flags & GIT_FS_PATH_REJECT_TRAILING_DOT) && - component[len - 1] == '.') - return false; - - if ((flags & GIT_FS_PATH_REJECT_TRAILING_SPACE) && - component[len - 1] == ' ') - return false; - - if ((flags & GIT_FS_PATH_REJECT_TRAILING_COLON) && - component[len - 1] == ':') - return false; - - if (flags & GIT_FS_PATH_REJECT_DOS_PATHS) { - if (!validate_dospath(component, len, "CON", false) || - !validate_dospath(component, len, "PRN", false) || - !validate_dospath(component, len, "AUX", false) || - !validate_dospath(component, len, "NUL", false) || - !validate_dospath(component, len, "COM", true) || - !validate_dospath(component, len, "LPT", true)) - return false; - } - - return true; -} - -#ifdef GIT_WIN32 -GIT_INLINE(bool) validate_length( - const char *path, - size_t len, - size_t utf8_char_len) -{ - GIT_UNUSED(path); - GIT_UNUSED(len); - - return (utf8_char_len <= MAX_PATH); -} -#endif - -bool git_fs_path_str_is_valid_ext( - const git_str *path, - unsigned int flags, - bool (*validate_char_cb)(char ch, void *payload), - bool (*validate_component_cb)(const char *component, size_t len, void *payload), - bool (*validate_length_cb)(const char *path, size_t len, size_t utf8_char_len), - void *payload) -{ - const char *start, *c; - size_t len = 0; - - if (!flags) - return true; - - for (start = c = path->ptr; *c && len < path->size; c++, len++) { - if (!validate_char(*c, flags)) - return false; - - if (validate_char_cb && !validate_char_cb(*c, payload)) - return false; - - if (*c != '/') - continue; - - if (!validate_component(start, (c - start), flags)) - return false; - - if (validate_component_cb && - !validate_component_cb(start, (c - start), payload)) - return false; - - start = c + 1; - } - - /* - * We want to support paths specified as either `const char *` - * or `git_str *`; we pass size as `SIZE_MAX` when we use a - * `const char *` to avoid a `strlen`. Ensure that we didn't - * have a NUL in the buffer if there was a non-SIZE_MAX length. - */ - if (path->size != SIZE_MAX && len != path->size) - return false; - - if (!validate_component(start, (c - start), flags)) - return false; - - if (validate_component_cb && - !validate_component_cb(start, (c - start), payload)) - return false; - -#ifdef GIT_WIN32 - if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS) != 0) { - size_t utf8_len = git_utf8_char_length(path->ptr, len); - - if (!validate_length(path->ptr, len, utf8_len)) - return false; - - if (validate_length_cb && - !validate_length_cb(path->ptr, len, utf8_len)) - return false; - } -#else - GIT_UNUSED(validate_length_cb); -#endif - - return true; -} - -int git_fs_path_validate_str_length_with_suffix( - git_str *path, - size_t suffix_len) -{ -#ifdef GIT_WIN32 - size_t utf8_len = git_utf8_char_length(path->ptr, path->size); - size_t total_len; - - if (GIT_ADD_SIZET_OVERFLOW(&total_len, utf8_len, suffix_len) || - total_len > MAX_PATH) { - - git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%.*s'", - (int)path->size, path->ptr); - return -1; - } -#else - GIT_UNUSED(path); - GIT_UNUSED(suffix_len); -#endif - - return 0; -} - -int git_fs_path_normalize_slashes(git_str *out, const char *path) -{ - int error; - char *p; - - if ((error = git_str_puts(out, path)) < 0) - return error; - - for (p = out->ptr; *p; p++) { - if (*p == '\\') - *p = '/'; - } - - return 0; -} - -bool git_fs_path_supports_symlinks(const char *dir) -{ - git_str path = GIT_STR_INIT; - bool supported = false; - struct stat st; - int fd; - - if ((fd = git_futils_mktmp(&path, dir, 0666)) < 0 || - p_close(fd) < 0 || - p_unlink(path.ptr) < 0 || - p_symlink("testing", path.ptr) < 0 || - p_lstat(path.ptr, &st) < 0) - goto done; - - supported = (S_ISLNK(st.st_mode) != 0); -done: - if (path.size) - (void)p_unlink(path.ptr); - git_str_dispose(&path); - return supported; -} - -int git_fs_path_validate_system_file_ownership(const char *path) -{ -#ifndef GIT_WIN32 - GIT_UNUSED(path); - return GIT_OK; -#else - git_win32_path buf; - PSID owner_sid; - PSECURITY_DESCRIPTOR descriptor = NULL; - HANDLE token; - TOKEN_USER *info = NULL; - DWORD err, len; - int ret; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - err = GetNamedSecurityInfoW(buf, SE_FILE_OBJECT, - OWNER_SECURITY_INFORMATION | - DACL_SECURITY_INFORMATION, - &owner_sid, NULL, NULL, NULL, &descriptor); - - if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { - ret = GIT_ENOTFOUND; - goto cleanup; - } - - if (err != ERROR_SUCCESS) { - git_error_set(GIT_ERROR_OS, "failed to get security information"); - ret = GIT_ERROR; - goto cleanup; - } - - if (!IsValidSid(owner_sid)) { - git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is unknown"); - ret = GIT_ERROR; - goto cleanup; - } - - if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) || - IsWellKnownSid(owner_sid, WinLocalSystemSid)) { - ret = GIT_OK; - goto cleanup; - } - - /* Obtain current user's SID */ - if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) && - !GetTokenInformation(token, TokenUser, NULL, 0, &len)) { - info = git__malloc(len); - GIT_ERROR_CHECK_ALLOC(info); - if (!GetTokenInformation(token, TokenUser, info, len, &len)) { - git__free(info); - info = NULL; - } - } - - /* - * If the file is owned by the same account that is running the current - * process, it's okay to read from that file. - */ - if (info && EqualSid(owner_sid, info->User.Sid)) - ret = GIT_OK; - else { - git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is not valid"); - ret = GIT_ERROR; - } - git__free(info); - -cleanup: - if (descriptor) - LocalFree(descriptor); - - return ret; -#endif -} - -int git_fs_path_find_executable(git_str *fullpath, const char *executable) -{ -#ifdef GIT_WIN32 - git_win32_path fullpath_w, executable_w; - int error; - - if (git__utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0) - return -1; - - error = git_win32_path_find_executable(fullpath_w, executable_w); - - if (error == 0) - error = git_str_put_w(fullpath, fullpath_w, wcslen(fullpath_w)); - - return error; -#else - git_str path = GIT_STR_INIT; - const char *current_dir, *term; - bool found = false; - - if (git__getenv(&path, "PATH") < 0) - return -1; - - current_dir = path.ptr; - - while (*current_dir) { - if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR))) - term = strchr(current_dir, '\0'); - - git_str_clear(fullpath); - if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 || - git_str_putc(fullpath, '/') < 0 || - git_str_puts(fullpath, executable) < 0) - return -1; - - if (git_fs_path_isfile(fullpath->ptr)) { - found = true; - break; - } - - current_dir = term; - - while (*current_dir == GIT_PATH_LIST_SEPARATOR) - current_dir++; - } - - git_str_dispose(&path); - - if (found) - return 0; - - git_str_clear(fullpath); - return GIT_ENOTFOUND; -#endif -} diff --git a/src/fs_path.h b/src/fs_path.h deleted file mode 100644 index 222c44abc..000000000 --- a/src/fs_path.h +++ /dev/null @@ -1,752 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_fs_path_h__ -#define INCLUDE_fs_path_h__ - -#include "common.h" - -#include "posix.h" -#include "str.h" -#include "vector.h" -#include "utf8.h" - -/** - * Path manipulation utils - * - * These are path utilities that munge paths without actually - * looking at the real filesystem. - */ - -/* - * The dirname() function shall take a pointer to a character string - * that contains a pathname, and return a pointer to a string that is a - * pathname of the parent directory of that file. Trailing '/' characters - * in the path are not counted as part of the path. - * - * If path does not contain a '/', then dirname() shall return a pointer to - * the string ".". If path is a null pointer or points to an empty string, - * dirname() shall return a pointer to the string "." . - * - * The `git_fs_path_dirname` implementation is thread safe. The returned - * string must be manually free'd. - * - * The `git_fs_path_dirname_r` implementation writes the dirname to a `git_str` - * if the buffer pointer is not NULL. - * It returns an error code < 0 if there is an allocation error, otherwise - * the length of the dirname (which will be > 0). - */ -extern char *git_fs_path_dirname(const char *path); -extern int git_fs_path_dirname_r(git_str *buffer, const char *path); - -/* - * This function returns the basename of the file, which is the last - * part of its full name given by fname, with the drive letter and - * leading directories stripped off. For example, the basename of - * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo. - * - * Trailing slashes and backslashes are significant: the basename of - * c:/foo/bar/ is an empty string after the rightmost slash. - * - * The `git_fs_path_basename` implementation is thread safe. The returned - * string must be manually free'd. - * - * The `git_fs_path_basename_r` implementation writes the basename to a `git_str`. - * It returns an error code < 0 if there is an allocation error, otherwise - * the length of the basename (which will be >= 0). - */ -extern char *git_fs_path_basename(const char *path); -extern int git_fs_path_basename_r(git_str *buffer, const char *path); - -/* Return the offset of the start of the basename. Unlike the other - * basename functions, this returns 0 if the path is empty. - */ -extern size_t git_fs_path_basename_offset(git_str *buffer); - -/** - * Find offset to root of path if path has one. - * - * This will return a number >= 0 which is the offset to the start of the - * path, if the path is rooted (i.e. "/rooted/path" returns 0 and - * "c:/windows/rooted/path" returns 2). If the path is not rooted, this - * returns -1. - */ -extern int git_fs_path_root(const char *path); - -/** - * Ensure path has a trailing '/'. - */ -extern int git_fs_path_to_dir(git_str *path); - -/** - * Ensure string has a trailing '/' if there is space for it. - */ -extern void git_fs_path_string_to_dir(char *path, size_t size); - -/** - * Taken from git.git; returns nonzero if the given path is "." or "..". - */ -GIT_INLINE(int) git_fs_path_is_dot_or_dotdot(const char *name) -{ - return (name[0] == '.' && - (name[1] == '\0' || - (name[1] == '.' && name[2] == '\0'))); -} - -#ifdef GIT_WIN32 -GIT_INLINE(int) git_fs_path_is_dot_or_dotdotW(const wchar_t *name) -{ - return (name[0] == L'.' && - (name[1] == L'\0' || - (name[1] == L'.' && name[2] == L'\0'))); -} - -#define git_fs_path_is_absolute(p) \ - (git__isalpha((p)[0]) && (p)[1] == ':' && ((p)[2] == '\\' || (p)[2] == '/')) - -#define git_fs_path_is_dirsep(p) \ - ((p) == '/' || (p) == '\\') - -/** - * Convert backslashes in path to forward slashes. - */ -GIT_INLINE(void) git_fs_path_mkposix(char *path) -{ - while (*path) { - if (*path == '\\') - *path = '/'; - - path++; - } -} -#else -# define git_fs_path_mkposix(p) /* blank */ - -#define git_fs_path_is_absolute(p) \ - ((p)[0] == '/') - -#define git_fs_path_is_dirsep(p) \ - ((p) == '/') - -#endif - -/** - * Check if string is a relative path (i.e. starts with "./" or "../") - */ -GIT_INLINE(int) git_fs_path_is_relative(const char *p) -{ - return (p[0] == '.' && (p[1] == '/' || (p[1] == '.' && p[2] == '/'))); -} - -/** - * Check if string is at end of path segment (i.e. looking at '/' or '\0') - */ -GIT_INLINE(int) git_fs_path_at_end_of_segment(const char *p) -{ - return !*p || *p == '/'; -} - -extern int git__percent_decode(git_str *decoded_out, const char *input); - -/** - * Extract path from file:// URL. - */ -extern int git_fs_path_fromurl(git_str *local_path_out, const char *file_url); - - -/** - * Path filesystem utils - * - * These are path utilities that actually access the filesystem. - */ - -/** - * Check if a file exists and can be accessed. - * @return true or false - */ -extern bool git_fs_path_exists(const char *path); - -/** - * Check if the given path points to a directory. - * @return true or false - */ -extern bool git_fs_path_isdir(const char *path); - -/** - * Check if the given path points to a regular file. - * @return true or false - */ -extern bool git_fs_path_isfile(const char *path); - -/** - * Check if the given path points to a symbolic link. - * @return true or false - */ -extern bool git_fs_path_islink(const char *path); - -/** - * Check if the given path is a directory, and is empty. - */ -extern bool git_fs_path_is_empty_dir(const char *path); - -/** - * Stat a file and/or link and set error if needed. - */ -extern int git_fs_path_lstat(const char *path, struct stat *st); - -/** - * Check if the parent directory contains the item. - * - * @param dir Directory to check. - * @param item Item that might be in the directory. - * @return 0 if item exists in directory, <0 otherwise. - */ -extern bool git_fs_path_contains(git_str *dir, const char *item); - -/** - * Check if the given path contains the given subdirectory. - * - * @param parent Directory path that might contain subdir - * @param subdir Subdirectory name to look for in parent - * @return true if subdirectory exists, false otherwise. - */ -extern bool git_fs_path_contains_dir(git_str *parent, const char *subdir); - -/** - * Determine the common directory length between two paths, including - * the final path separator. For example, given paths 'a/b/c/1.txt - * and 'a/b/c/d/2.txt', the common directory is 'a/b/c/', and this - * will return the length of the string 'a/b/c/', which is 6. - * - * @param one The first path - * @param two The second path - * @return The length of the common directory - */ -extern size_t git_fs_path_common_dirlen(const char *one, const char *two); - -/** - * Make the path relative to the given parent path. - * - * @param path The path to make relative - * @param parent The parent path to make path relative to - * @return 0 if path was made relative, GIT_ENOTFOUND - * if there was not common root between the paths, - * or <0. - */ -extern int git_fs_path_make_relative(git_str *path, const char *parent); - -/** - * Check if the given path contains the given file. - * - * @param dir Directory path that might contain file - * @param file File name to look for in parent - * @return true if file exists, false otherwise. - */ -extern bool git_fs_path_contains_file(git_str *dir, const char *file); - -/** - * Prepend base to unrooted path or just copy path over. - * - * This will optionally return the index into the path where the "root" - * is, either the end of the base directory prefix or the path root. - */ -extern int git_fs_path_join_unrooted( - git_str *path_out, const char *path, const char *base, ssize_t *root_at); - -/** - * Removes multiple occurrences of '/' in a row, squashing them into a - * single '/'. - */ -extern void git_fs_path_squash_slashes(git_str *path); - -/** - * Clean up path, prepending base if it is not already rooted. - */ -extern int git_fs_path_prettify(git_str *path_out, const char *path, const char *base); - -/** - * Clean up path, prepending base if it is not already rooted and - * appending a slash. - */ -extern int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base); - -/** - * Get a directory from a path. - * - * If path is a directory, this acts like `git_fs_path_prettify_dir` - * (cleaning up path and appending a '/'). If path is a normal file, - * this prettifies it, then removed the filename a la dirname and - * appends the trailing '/'. If the path does not exist, it is - * treated like a regular filename. - */ -extern int git_fs_path_find_dir(git_str *dir); - -/** - * Resolve relative references within a path. - * - * This eliminates "./" and "../" relative references inside a path, - * as well as condensing multiple slashes into single ones. It will - * not touch the path before the "ceiling" length. - * - * Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL - * prefix and not touch that part of the path. - */ -extern int git_fs_path_resolve_relative(git_str *path, size_t ceiling); - -/** - * Apply a relative path to base path. - * - * Note that the base path could be a filename or a URL and this - * should still work. The relative path is walked segment by segment - * with three rules: series of slashes will be condensed to a single - * slash, "." will be eaten with no change, and ".." will remove a - * segment from the base path. - */ -extern int git_fs_path_apply_relative(git_str *target, const char *relpath); - -enum { - GIT_FS_PATH_DIR_IGNORE_CASE = (1u << 0), - GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1), - GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT = (1u << 2), -}; - -/** - * Walk each directory entry, except '.' and '..', calling fn(state). - * - * @param pathbuf Buffer the function reads the initial directory - * path from, and updates with each successive entry's name. - * @param flags Combination of GIT_FS_PATH_DIR flags. - * @param callback Callback for each entry. Passed the `payload` and each - * successive path inside the directory as a full path. This may - * safely append text to the pathbuf if needed. Return non-zero to - * cancel iteration (and return value will be propagated back). - * @param payload Passed to callback as first argument. - * @return 0 on success or error code from OS error or from callback - */ -extern int git_fs_path_direach( - git_str *pathbuf, - uint32_t flags, - int (*callback)(void *payload, git_str *path), - void *payload); - -/** - * Sort function to order two paths - */ -extern int git_fs_path_cmp( - const char *name1, size_t len1, int isdir1, - const char *name2, size_t len2, int isdir2, - int (*compare)(const char *, const char *, size_t)); - -/** - * Invoke callback up path directory by directory until the ceiling is - * reached (inclusive of a final call at the root_path). - * - * Returning anything other than 0 from the callback function - * will stop the iteration and propagate the error to the caller. - * - * @param pathbuf Buffer the function reads the directory from and - * and updates with each successive name. - * @param ceiling Prefix of path at which to stop walking up. If NULL, - * this will walk all the way up to the root. If not a prefix of - * pathbuf, the callback will be invoked a single time on the - * original input path. - * @param callback Function to invoke on each path. Passed the `payload` - * and the buffer containing the current path. The path should not - * be modified in any way. Return non-zero to stop iteration. - * @param payload Passed to fn as the first ath. - */ -extern int git_fs_path_walk_up( - git_str *pathbuf, - const char *ceiling, - int (*callback)(void *payload, const char *path), - void *payload); - - -enum { - GIT_FS_PATH_NOTEQUAL = 0, - GIT_FS_PATH_EQUAL = 1, - GIT_FS_PATH_PREFIX = 2 -}; - -/* - * Determines if a path is equal to or potentially a child of another. - * @param parent The possible parent - * @param child The possible child - */ -GIT_INLINE(int) git_fs_path_equal_or_prefixed( - const char *parent, - const char *child, - ssize_t *prefixlen) -{ - const char *p = parent, *c = child; - int lastslash = 0; - - while (*p && *c) { - lastslash = (*p == '/'); - - if (*p++ != *c++) - return GIT_FS_PATH_NOTEQUAL; - } - - if (*p != '\0') - return GIT_FS_PATH_NOTEQUAL; - - if (*c == '\0') { - if (prefixlen) - *prefixlen = p - parent; - - return GIT_FS_PATH_EQUAL; - } - - if (*c == '/' || lastslash) { - if (prefixlen) - *prefixlen = (p - parent) - lastslash; - - return GIT_FS_PATH_PREFIX; - } - - return GIT_FS_PATH_NOTEQUAL; -} - -/* translate errno to libgit2 error code and set error message */ -extern int git_fs_path_set_error( - int errno_value, const char *path, const char *action); - -/* check if non-ascii characters are present in filename */ -extern bool git_fs_path_has_non_ascii(const char *path, size_t pathlen); - -#define GIT_PATH_REPO_ENCODING "UTF-8" - -#ifdef __APPLE__ -#define GIT_PATH_NATIVE_ENCODING "UTF-8-MAC" -#else -#define GIT_PATH_NATIVE_ENCODING "UTF-8" -#endif - -#ifdef GIT_USE_ICONV - -#include - -typedef struct { - iconv_t map; - git_str buf; -} git_fs_path_iconv_t; - -#define GIT_PATH_ICONV_INIT { (iconv_t)-1, GIT_STR_INIT } - -/* Init iconv data for converting decomposed UTF-8 to precomposed */ -extern int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic); - -/* Clear allocated iconv data */ -extern void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic); - -/* - * Rewrite `in` buffer using iconv map if necessary, replacing `in` - * pointer internal iconv buffer if rewrite happened. The `in` pointer - * will be left unchanged if no rewrite was needed. - */ -extern int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen); - -#endif /* GIT_USE_ICONV */ - -extern bool git_fs_path_does_decompose_unicode(const char *root); - - -typedef struct git_fs_path_diriter git_fs_path_diriter; - -#if defined(GIT_WIN32) && !defined(__MINGW32__) - -struct git_fs_path_diriter -{ - git_win32_path path; - size_t parent_len; - - git_str path_utf8; - size_t parent_utf8_len; - - HANDLE handle; - - unsigned int flags; - - WIN32_FIND_DATAW current; - unsigned int needs_next; -}; - -#define GIT_FS_PATH_DIRITER_INIT { {0}, 0, GIT_STR_INIT, 0, INVALID_HANDLE_VALUE } - -#else - -struct git_fs_path_diriter -{ - git_str path; - size_t parent_len; - - unsigned int flags; - - DIR *dir; - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_t ic; -#endif -}; - -#define GIT_FS_PATH_DIRITER_INIT { GIT_STR_INIT } - -#endif - -/** - * Initialize a directory iterator. - * - * @param diriter Pointer to a diriter structure that will be setup. - * @param path The path that will be iterated over - * @param flags Directory reader flags - * @return 0 or an error code - */ -extern int git_fs_path_diriter_init( - git_fs_path_diriter *diriter, - const char *path, - unsigned int flags); - -/** - * Advance the directory iterator. Will return GIT_ITEROVER when - * the iteration has completed successfully. - * - * @param diriter The directory iterator - * @return 0, GIT_ITEROVER, or an error code - */ -extern int git_fs_path_diriter_next(git_fs_path_diriter *diriter); - -/** - * Returns the file name of the current item in the iterator. - * - * @param out Pointer to store the path in - * @param out_len Pointer to store the length of the path in - * @param diriter The directory iterator - * @return 0 or an error code - */ -extern int git_fs_path_diriter_filename( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter); - -/** - * Returns the full path of the current item in the iterator; that - * is the current filename plus the path of the directory that the - * iterator was constructed with. - * - * @param out Pointer to store the path in - * @param out_len Pointer to store the length of the path in - * @param diriter The directory iterator - * @return 0 or an error code - */ -extern int git_fs_path_diriter_fullpath( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter); - -/** - * Performs an `lstat` on the current item in the iterator. - * - * @param out Pointer to store the stat data in - * @param diriter The directory iterator - * @return 0 or an error code - */ -extern int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter); - -/** - * Closes the directory iterator. - * - * @param diriter The directory iterator - */ -extern void git_fs_path_diriter_free(git_fs_path_diriter *diriter); - -/** - * Load all directory entries (except '.' and '..') into a vector. - * - * For cases where `git_fs_path_direach()` is not appropriate, this - * allows you to load the filenames in a directory into a vector - * of strings. That vector can then be sorted, iterated, or whatever. - * Remember to free alloc of the allocated strings when you are done. - * - * @param contents Vector to fill with directory entry names. - * @param path The directory to read from. - * @param prefix_len When inserting entries, the trailing part of path - * will be prefixed after this length. I.e. given path "/a/b" and - * prefix_len 3, the entries will look like "b/e1", "b/e2", etc. - * @param flags Combination of GIT_FS_PATH_DIR flags. - */ -extern int git_fs_path_dirload( - git_vector *contents, - const char *path, - size_t prefix_len, - uint32_t flags); - - -/* Used for paths to repositories on the filesystem */ -extern bool git_fs_path_is_local_file_url(const char *file_url); -extern int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path); - -/* Flags to determine path validity in `git_fs_path_isvalid` */ -#define GIT_FS_PATH_REJECT_EMPTY_COMPONENT (1 << 0) -#define GIT_FS_PATH_REJECT_TRAVERSAL (1 << 1) -#define GIT_FS_PATH_REJECT_SLASH (1 << 2) -#define GIT_FS_PATH_REJECT_BACKSLASH (1 << 3) -#define GIT_FS_PATH_REJECT_TRAILING_DOT (1 << 4) -#define GIT_FS_PATH_REJECT_TRAILING_SPACE (1 << 5) -#define GIT_FS_PATH_REJECT_TRAILING_COLON (1 << 6) -#define GIT_FS_PATH_REJECT_DOS_PATHS (1 << 7) -#define GIT_FS_PATH_REJECT_NT_CHARS (1 << 8) -#define GIT_FS_PATH_REJECT_LONG_PATHS (1 << 9) - -#define GIT_FS_PATH_REJECT_MAX (1 << 9) - -/* Default path safety for writing files to disk: since we use the - * Win32 "File Namespace" APIs ("\\?\") we need to protect from - * paths that the normal Win32 APIs would not write. - */ -#ifdef GIT_WIN32 -# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \ - GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \ - GIT_FS_PATH_REJECT_TRAVERSAL | \ - GIT_FS_PATH_REJECT_BACKSLASH | \ - GIT_FS_PATH_REJECT_TRAILING_DOT | \ - GIT_FS_PATH_REJECT_TRAILING_SPACE | \ - GIT_FS_PATH_REJECT_TRAILING_COLON | \ - GIT_FS_PATH_REJECT_DOS_PATHS | \ - GIT_FS_PATH_REJECT_NT_CHARS -#else -# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \ - GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \ - GIT_FS_PATH_REJECT_TRAVERSAL -#endif - -/** - * Validate a filesystem path; with custom callbacks per-character and - * per-path component. - */ -extern bool git_fs_path_str_is_valid_ext( - const git_str *path, - unsigned int flags, - bool (*validate_char_cb)(char ch, void *payload), - bool (*validate_component_cb)(const char *component, size_t len, void *payload), - bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len), - void *payload); - -GIT_INLINE(bool) git_fs_path_is_valid_ext( - const char *path, - unsigned int flags, - bool (*validate_char_cb)(char ch, void *payload), - bool (*validate_component_cb)(const char *component, size_t len, void *payload), - bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len), - void *payload) -{ - const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); - return git_fs_path_str_is_valid_ext( - &str, - flags, - validate_char_cb, - validate_component_cb, - validate_length_cb, - payload); -} - -/** - * Validate a filesystem path. This ensures that the given path is legal - * and does not contain any "unsafe" components like path traversal ('.' - * or '..'), characters that are inappropriate for lesser filesystems - * (trailing ' ' or ':' characters), or filenames ("component names") - * that are not supported ('AUX', 'COM1"). - */ -GIT_INLINE(bool) git_fs_path_is_valid( - const char *path, - unsigned int flags) -{ - const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); - return git_fs_path_str_is_valid_ext(&str, flags, NULL, NULL, NULL, NULL); -} - -/** Validate a filesystem path in a `git_str`. */ -GIT_INLINE(bool) git_fs_path_str_is_valid( - const git_str *path, - unsigned int flags) -{ - return git_fs_path_str_is_valid_ext(path, flags, NULL, NULL, NULL, NULL); -} - -extern int git_fs_path_validate_str_length_with_suffix( - git_str *path, - size_t suffix_len); - -/** - * Validate an on-disk path, taking into account that it will have a - * suffix appended (eg, `.lock`). - */ -GIT_INLINE(int) git_fs_path_validate_filesystem_with_suffix( - const char *path, - size_t path_len, - size_t suffix_len) -{ -#ifdef GIT_WIN32 - size_t path_chars, total_chars; - - path_chars = git_utf8_char_length(path, path_len); - - if (GIT_ADD_SIZET_OVERFLOW(&total_chars, path_chars, suffix_len) || - total_chars > MAX_PATH) { - git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%s'", path); - return -1; - } - return 0; -#else - GIT_UNUSED(path); - GIT_UNUSED(path_len); - GIT_UNUSED(suffix_len); - return 0; -#endif -} - -/** - * Validate an path on the filesystem. This ensures that the given - * path is valid for the operating system/platform; for example, this - * will ensure that the given absolute path is smaller than MAX_PATH on - * Windows. - * - * For paths within the working directory, you should use ensure that - * `core.longpaths` is obeyed. Use `git_fs_path_validate_workdir`. - */ -GIT_INLINE(int) git_fs_path_validate_filesystem( - const char *path, - size_t path_len) -{ - return git_fs_path_validate_filesystem_with_suffix(path, path_len, 0); -} - -/** - * Convert any backslashes into slashes - */ -int git_fs_path_normalize_slashes(git_str *out, const char *path); - -bool git_fs_path_supports_symlinks(const char *dir); - -/** - * Validate a system file's ownership - * - * Verify that the file in question is owned by an administrator or system - * account, or at least by the current user. - * - * This function returns 0 if successful. If the file is not owned by any of - * these, or any other if there have been problems determining the file - * ownership, it returns -1. - */ -int git_fs_path_validate_system_file_ownership(const char *path); - -/** - * Search the current PATH for the given executable, returning the full - * path if it is found. - */ -int git_fs_path_find_executable(git_str *fullpath, const char *executable); - -#endif diff --git a/src/futils.c b/src/futils.c deleted file mode 100644 index 42c35955e..000000000 --- a/src/futils.c +++ /dev/null @@ -1,1194 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "futils.h" - -#include "runtime.h" -#include "strmap.h" -#include "hash.h" -#include "rand.h" - -#include -#if GIT_WIN32 -#include "win32/findfile.h" -#endif - -#define GIT_FILEMODE_DEFAULT 0100666 - -int git_futils_mkpath2file(const char *file_path, const mode_t mode) -{ - return git_futils_mkdir( - file_path, mode, - GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); -} - -int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode) -{ - const int open_flags = O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC; - unsigned int tries = 32; - int fd; - - while (tries--) { - uint64_t rand = git_rand_next(); - - git_str_sets(path_out, filename); - git_str_puts(path_out, "_git2_"); - git_str_encode_hexstr(path_out, (void *)&rand, sizeof(uint64_t)); - - if (git_str_oom(path_out)) - return -1; - - /* Note that we open with O_CREAT | O_EXCL */ - if ((fd = p_open(path_out->ptr, open_flags, mode)) >= 0) - return fd; - } - - git_error_set(GIT_ERROR_OS, - "failed to create temporary file '%s'", path_out->ptr); - git_str_dispose(path_out); - return -1; -} - -int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode) -{ - int fd; - - if (git_futils_mkpath2file(path, dirmode) < 0) - return -1; - - fd = p_creat(path, mode); - if (fd < 0) { - git_error_set(GIT_ERROR_OS, "failed to create file '%s'", path); - return -1; - } - - return fd; -} - -int git_futils_creat_locked(const char *path, const mode_t mode) -{ - int fd = p_open(path, O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, - mode); - - if (fd < 0) { - int error = errno; - git_error_set(GIT_ERROR_OS, "failed to create locked file '%s'", path); - switch (error) { - case EEXIST: - return GIT_ELOCKED; - case ENOENT: - return GIT_ENOTFOUND; - default: - return -1; - } - } - - return fd; -} - -int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode) -{ - if (git_futils_mkpath2file(path, dirmode) < 0) - return -1; - - return git_futils_creat_locked(path, mode); -} - -int git_futils_open_ro(const char *path) -{ - int fd = p_open(path, O_RDONLY); - if (fd < 0) - return git_fs_path_set_error(errno, path, "open"); - return fd; -} - -int git_futils_truncate(const char *path, int mode) -{ - int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode); - if (fd < 0) - return git_fs_path_set_error(errno, path, "open"); - - close(fd); - return 0; -} - -int git_futils_filesize(uint64_t *out, git_file fd) -{ - struct stat sb; - - if (p_fstat(fd, &sb)) { - git_error_set(GIT_ERROR_OS, "failed to stat file descriptor"); - return -1; - } - - if (sb.st_size < 0) { - git_error_set(GIT_ERROR_INVALID, "invalid file size"); - return -1; - } - - *out = sb.st_size; - return 0; -} - -mode_t git_futils_canonical_mode(mode_t raw_mode) -{ - if (S_ISREG(raw_mode)) - return S_IFREG | GIT_PERMS_CANONICAL(raw_mode); - else if (S_ISLNK(raw_mode)) - return S_IFLNK; - else if (S_ISGITLINK(raw_mode)) - return S_IFGITLINK; - else if (S_ISDIR(raw_mode)) - return S_IFDIR; - else - return 0; -} - -int git_futils_readbuffer_fd(git_str *buf, git_file fd, size_t len) -{ - ssize_t read_size = 0; - size_t alloc_len; - - git_str_clear(buf); - - if (!git__is_ssizet(len)) { - git_error_set(GIT_ERROR_INVALID, "read too large"); - return -1; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1); - if (git_str_grow(buf, alloc_len) < 0) - return -1; - - /* p_read loops internally to read len bytes */ - read_size = p_read(fd, buf->ptr, len); - - if (read_size != (ssize_t)len) { - git_error_set(GIT_ERROR_OS, "failed to read descriptor"); - git_str_dispose(buf); - return -1; - } - - buf->ptr[read_size] = '\0'; - buf->size = read_size; - - return 0; -} - -int git_futils_readbuffer_updated( - git_str *out, - const char *path, - unsigned char checksum[GIT_HASH_SHA1_SIZE], - int *updated) -{ - int error; - git_file fd; - struct stat st; - git_str buf = GIT_STR_INIT; - unsigned char checksum_new[GIT_HASH_SHA1_SIZE]; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(path && *path); - - if (updated != NULL) - *updated = 0; - - if (p_stat(path, &st) < 0) - return git_fs_path_set_error(errno, path, "stat"); - - - if (S_ISDIR(st.st_mode)) { - git_error_set(GIT_ERROR_INVALID, "requested file is a directory"); - return GIT_ENOTFOUND; - } - - if (!git__is_sizet(st.st_size+1)) { - git_error_set(GIT_ERROR_OS, "invalid regular file stat for '%s'", path); - return -1; - } - - if ((fd = git_futils_open_ro(path)) < 0) - return fd; - - if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) { - p_close(fd); - return -1; - } - - p_close(fd); - - if (checksum) { - if ((error = git_hash_buf(checksum_new, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0) { - git_str_dispose(&buf); - return error; - } - - /* - * If we were given a checksum, we only want to use it if it's different - */ - if (!memcmp(checksum, checksum_new, GIT_HASH_SHA1_SIZE)) { - git_str_dispose(&buf); - if (updated) - *updated = 0; - - return 0; - } - - memcpy(checksum, checksum_new, GIT_HASH_SHA1_SIZE); - } - - /* - * If we're here, the file did change, or the user didn't have an old version - */ - if (updated != NULL) - *updated = 1; - - git_str_swap(out, &buf); - git_str_dispose(&buf); - - return 0; -} - -int git_futils_readbuffer(git_str *buf, const char *path) -{ - return git_futils_readbuffer_updated(buf, path, NULL, NULL); -} - -int git_futils_writebuffer( - const git_str *buf, const char *path, int flags, mode_t mode) -{ - int fd, do_fsync = 0, error = 0; - - if (!flags) - flags = O_CREAT | O_TRUNC | O_WRONLY; - - if ((flags & O_FSYNC) != 0) - do_fsync = 1; - - flags &= ~O_FSYNC; - - if (!mode) - mode = GIT_FILEMODE_DEFAULT; - - if ((fd = p_open(path, flags, mode)) < 0) { - git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path); - return fd; - } - - if ((error = p_write(fd, git_str_cstr(buf), git_str_len(buf))) < 0) { - git_error_set(GIT_ERROR_OS, "could not write to '%s'", path); - (void)p_close(fd); - return error; - } - - if (do_fsync && (error = p_fsync(fd)) < 0) { - git_error_set(GIT_ERROR_OS, "could not fsync '%s'", path); - p_close(fd); - return error; - } - - if ((error = p_close(fd)) < 0) { - git_error_set(GIT_ERROR_OS, "error while closing '%s'", path); - return error; - } - - if (do_fsync && (flags & O_CREAT)) - error = git_futils_fsync_parent(path); - - return error; -} - -int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) -{ - if (git_futils_mkpath2file(to, dirmode) < 0) - return -1; - - if (p_rename(from, to) < 0) { - git_error_set(GIT_ERROR_OS, "failed to rename '%s' to '%s'", from, to); - return -1; - } - - return 0; -} - -int git_futils_mmap_ro(git_map *out, git_file fd, off64_t begin, size_t len) -{ - return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin); -} - -int git_futils_mmap_ro_file(git_map *out, const char *path) -{ - git_file fd = git_futils_open_ro(path); - uint64_t len; - int result; - - if (fd < 0) - return fd; - - if ((result = git_futils_filesize(&len, fd)) < 0) - goto out; - - if (!git__is_sizet(len)) { - git_error_set(GIT_ERROR_OS, "file `%s` too large to mmap", path); - result = -1; - goto out; - } - - result = git_futils_mmap_ro(out, fd, 0, (size_t)len); -out: - p_close(fd); - return result; -} - -void git_futils_mmap_free(git_map *out) -{ - p_munmap(out); -} - -GIT_INLINE(int) mkdir_validate_dir( - const char *path, - struct stat *st, - mode_t mode, - uint32_t flags, - struct git_futils_mkdir_options *opts) -{ - /* with exclusive create, existing dir is an error */ - if ((flags & GIT_MKDIR_EXCL) != 0) { - git_error_set(GIT_ERROR_FILESYSTEM, - "failed to make directory '%s': directory exists", path); - return GIT_EEXISTS; - } - - if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) || - (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) { - if (p_unlink(path) < 0) { - git_error_set(GIT_ERROR_OS, "failed to remove %s '%s'", - S_ISLNK(st->st_mode) ? "symlink" : "file", path); - return GIT_EEXISTS; - } - - opts->perfdata.mkdir_calls++; - - if (p_mkdir(path, mode) < 0) { - git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path); - return GIT_EEXISTS; - } - } - - else if (S_ISLNK(st->st_mode)) { - /* Re-stat the target, make sure it's a directory */ - opts->perfdata.stat_calls++; - - if (p_stat(path, st) < 0) { - git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path); - return GIT_EEXISTS; - } - } - - else if (!S_ISDIR(st->st_mode)) { - git_error_set(GIT_ERROR_FILESYSTEM, - "failed to make directory '%s': directory exists", path); - return GIT_EEXISTS; - } - - return 0; -} - -GIT_INLINE(int) mkdir_validate_mode( - const char *path, - struct stat *st, - bool terminal_path, - mode_t mode, - uint32_t flags, - struct git_futils_mkdir_options *opts) -{ - if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) || - (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) { - - opts->perfdata.chmod_calls++; - - if (p_chmod(path, mode) < 0) { - git_error_set(GIT_ERROR_OS, "failed to set permissions on '%s'", path); - return -1; - } - } - - return 0; -} - -GIT_INLINE(int) mkdir_canonicalize( - git_str *path, - uint32_t flags) -{ - ssize_t root_len; - - if (path->size == 0) { - git_error_set(GIT_ERROR_OS, "attempt to create empty path"); - return -1; - } - - /* Trim trailing slashes (except the root) */ - if ((root_len = git_fs_path_root(path->ptr)) < 0) - root_len = 0; - else - root_len++; - - while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/') - path->ptr[--path->size] = '\0'; - - /* if we are not supposed to made the last element, truncate it */ - if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) { - git_fs_path_dirname_r(path, path->ptr); - flags |= GIT_MKDIR_SKIP_LAST; - } - if ((flags & GIT_MKDIR_SKIP_LAST) != 0) { - git_fs_path_dirname_r(path, path->ptr); - } - - /* We were either given the root path (or trimmed it to - * the root), we don't have anything to do. - */ - if (path->size <= (size_t)root_len) - git_str_clear(path); - - return 0; -} - -int git_futils_mkdir( - const char *path, - mode_t mode, - uint32_t flags) -{ - git_str make_path = GIT_STR_INIT, parent_path = GIT_STR_INIT; - const char *relative; - struct git_futils_mkdir_options opts = { 0 }; - struct stat st; - size_t depth = 0; - int len = 0, root_len, error; - - if ((error = git_str_puts(&make_path, path)) < 0 || - (error = mkdir_canonicalize(&make_path, flags)) < 0 || - (error = git_str_puts(&parent_path, make_path.ptr)) < 0 || - make_path.size == 0) - goto done; - - root_len = git_fs_path_root(make_path.ptr); - - /* find the first parent directory that exists. this will be used - * as the base to dirname_relative. - */ - for (relative = make_path.ptr; parent_path.size; ) { - error = p_lstat(parent_path.ptr, &st); - - if (error == 0) { - break; - } else if (errno != ENOENT) { - git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr); - error = -1; - goto done; - } - - depth++; - - /* examine the parent of the current path */ - if ((len = git_fs_path_dirname_r(&parent_path, parent_path.ptr)) < 0) { - error = len; - goto done; - } - - GIT_ASSERT(len); - - /* - * We've walked all the given path's parents and it's either relative - * (the parent is simply '.') or rooted (the length is less than or - * equal to length of the root path). The path may be less than the - * root path length on Windows, where `C:` == `C:/`. - */ - if ((len == 1 && parent_path.ptr[0] == '.') || - (len == 1 && parent_path.ptr[0] == '/') || - len <= root_len) { - relative = make_path.ptr; - break; - } - - relative = make_path.ptr + len + 1; - - /* not recursive? just make this directory relative to its parent. */ - if ((flags & GIT_MKDIR_PATH) == 0) - break; - } - - /* we found an item at the location we're trying to create, - * validate it. - */ - if (depth == 0) { - error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts); - - if (!error) - error = mkdir_validate_mode( - make_path.ptr, &st, true, mode, flags, &opts); - - goto done; - } - - /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when - * canonicalizing `make_path`. - */ - flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST); - - error = git_futils_mkdir_relative(relative, - parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts); - -done: - git_str_dispose(&make_path); - git_str_dispose(&parent_path); - return error; -} - -int git_futils_mkdir_r(const char *path, const mode_t mode) -{ - return git_futils_mkdir(path, mode, GIT_MKDIR_PATH); -} - -int git_futils_mkdir_relative( - const char *relative_path, - const char *base, - mode_t mode, - uint32_t flags, - struct git_futils_mkdir_options *opts) -{ - git_str make_path = GIT_STR_INIT; - ssize_t root = 0, min_root_len; - char lastch = '/', *tail; - struct stat st; - struct git_futils_mkdir_options empty_opts = {0}; - int error; - - if (!opts) - opts = &empty_opts; - - /* build path and find "root" where we should start calling mkdir */ - if (git_fs_path_join_unrooted(&make_path, relative_path, base, &root) < 0) - return -1; - - if ((error = mkdir_canonicalize(&make_path, flags)) < 0 || - make_path.size == 0) - goto done; - - /* if we are not supposed to make the whole path, reset root */ - if ((flags & GIT_MKDIR_PATH) == 0) - root = git_str_rfind(&make_path, '/'); - - /* advance root past drive name or network mount prefix */ - min_root_len = git_fs_path_root(make_path.ptr); - if (root < min_root_len) - root = min_root_len; - while (root >= 0 && make_path.ptr[root] == '/') - ++root; - - /* clip root to make_path length */ - if (root > (ssize_t)make_path.size) - root = (ssize_t)make_path.size; /* i.e. NUL byte of string */ - if (root < 0) - root = 0; - - /* walk down tail of path making each directory */ - for (tail = &make_path.ptr[root]; *tail; *tail = lastch) { - bool mkdir_attempted = false; - - /* advance tail to include next path component */ - while (*tail == '/') - tail++; - while (*tail && *tail != '/') - tail++; - - /* truncate path at next component */ - lastch = *tail; - *tail = '\0'; - st.st_mode = 0; - - if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr)) - continue; - - /* See what's going on with this path component */ - opts->perfdata.stat_calls++; - -retry_lstat: - if (p_lstat(make_path.ptr, &st) < 0) { - if (mkdir_attempted || errno != ENOENT) { - git_error_set(GIT_ERROR_OS, "cannot access component in path '%s'", make_path.ptr); - error = -1; - goto done; - } - - git_error_clear(); - opts->perfdata.mkdir_calls++; - mkdir_attempted = true; - if (p_mkdir(make_path.ptr, mode) < 0) { - if (errno == EEXIST) - goto retry_lstat; - git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", make_path.ptr); - error = -1; - goto done; - } - } else { - if ((error = mkdir_validate_dir( - make_path.ptr, &st, mode, flags, opts)) < 0) - goto done; - } - - /* chmod if requested and necessary */ - if ((error = mkdir_validate_mode( - make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0) - goto done; - - if (opts->dir_map && opts->pool) { - char *cache_path; - size_t alloc_size; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1); - cache_path = git_pool_malloc(opts->pool, alloc_size); - GIT_ERROR_CHECK_ALLOC(cache_path); - - memcpy(cache_path, make_path.ptr, make_path.size + 1); - - if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0) - goto done; - } - } - - error = 0; - - /* check that full path really is a directory if requested & needed */ - if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 && - lastch != '\0') { - opts->perfdata.stat_calls++; - - if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) { - git_error_set(GIT_ERROR_OS, "path is not a directory '%s'", - make_path.ptr); - error = GIT_ENOTFOUND; - } - } - -done: - git_str_dispose(&make_path); - return error; -} - -typedef struct { - const char *base; - size_t baselen; - uint32_t flags; - int depth; -} futils__rmdir_data; - -#define FUTILS_MAX_DEPTH 100 - -static int futils__error_cannot_rmdir(const char *path, const char *filemsg) -{ - if (filemsg) - git_error_set(GIT_ERROR_OS, "could not remove directory '%s': %s", - path, filemsg); - else - git_error_set(GIT_ERROR_OS, "could not remove directory '%s'", path); - - return -1; -} - -static int futils__rm_first_parent(git_str *path, const char *ceiling) -{ - int error = GIT_ENOTFOUND; - struct stat st; - - while (error == GIT_ENOTFOUND) { - git_str_rtruncate_at_char(path, '/'); - - if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0) - error = 0; - else if (p_lstat_posixly(path->ptr, &st) == 0) { - if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) - error = p_unlink(path->ptr); - else if (!S_ISDIR(st.st_mode)) - error = -1; /* fail to remove non-regular file */ - } else if (errno != ENOTDIR) - error = -1; - } - - if (error) - futils__error_cannot_rmdir(path->ptr, "cannot remove parent"); - - return error; -} - -static int futils__rmdir_recurs_foreach(void *opaque, git_str *path) -{ - int error = 0; - futils__rmdir_data *data = opaque; - struct stat st; - - if (data->depth > FUTILS_MAX_DEPTH) - error = futils__error_cannot_rmdir( - path->ptr, "directory nesting too deep"); - - else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) { - if (errno == ENOENT) - error = 0; - else if (errno == ENOTDIR) { - /* asked to remove a/b/c/d/e and a/b is a normal file */ - if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0) - error = futils__rm_first_parent(path, data->base); - else - futils__error_cannot_rmdir( - path->ptr, "parent is not directory"); - } - else - error = git_fs_path_set_error(errno, path->ptr, "rmdir"); - } - - else if (S_ISDIR(st.st_mode)) { - data->depth++; - - error = git_fs_path_direach(path, 0, futils__rmdir_recurs_foreach, data); - - data->depth--; - - if (error < 0) - return error; - - if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0) - return error; - - if ((error = p_rmdir(path->ptr)) < 0) { - if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 && - (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY)) - error = 0; - else - error = git_fs_path_set_error(errno, path->ptr, "rmdir"); - } - } - - else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) { - if (p_unlink(path->ptr) < 0) - error = git_fs_path_set_error(errno, path->ptr, "remove"); - } - - else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0) - error = futils__error_cannot_rmdir(path->ptr, "still present"); - - return error; -} - -static int futils__rmdir_empty_parent(void *opaque, const char *path) -{ - futils__rmdir_data *data = opaque; - int error = 0; - - if (strlen(path) <= data->baselen) - error = GIT_ITEROVER; - - else if (p_rmdir(path) < 0) { - int en = errno; - - if (en == ENOENT || en == ENOTDIR) { - /* do nothing */ - } else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 && - en == EBUSY) { - error = git_fs_path_set_error(errno, path, "rmdir"); - } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) { - error = GIT_ITEROVER; - } else { - error = git_fs_path_set_error(errno, path, "rmdir"); - } - } - - return error; -} - -int git_futils_rmdir_r( - const char *path, const char *base, uint32_t flags) -{ - int error; - git_str fullpath = GIT_STR_INIT; - futils__rmdir_data data; - - /* build path and find "root" where we should start calling mkdir */ - if (git_fs_path_join_unrooted(&fullpath, path, base, NULL) < 0) - return -1; - - memset(&data, 0, sizeof(data)); - data.base = base ? base : ""; - data.baselen = base ? strlen(base) : 0; - data.flags = flags; - - error = futils__rmdir_recurs_foreach(&data, &fullpath); - - /* remove now-empty parents if requested */ - if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0) - error = git_fs_path_walk_up( - &fullpath, base, futils__rmdir_empty_parent, &data); - - if (error == GIT_ITEROVER) { - git_error_clear(); - error = 0; - } - - git_str_dispose(&fullpath); - - return error; -} - -int git_futils_fake_symlink(const char *target, const char *path) -{ - int retcode = GIT_ERROR; - int fd = git_futils_creat_withpath(path, 0755, 0644); - if (fd >= 0) { - retcode = p_write(fd, target, strlen(target)); - p_close(fd); - } - return retcode; -} - -static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done) -{ - int error = 0; - char buffer[FILEIO_BUFSIZE]; - ssize_t len = 0; - - while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0) - /* p_write() does not have the same semantics as write(). It loops - * internally and will return 0 when it has completed writing. - */ - error = p_write(ofd, buffer, len); - - if (len < 0) { - git_error_set(GIT_ERROR_OS, "read error while copying file"); - error = (int)len; - } - - if (error < 0) - git_error_set(GIT_ERROR_OS, "write error while copying file"); - - if (close_fd_when_done) { - p_close(ifd); - p_close(ofd); - } - - return error; -} - -int git_futils_cp(const char *from, const char *to, mode_t filemode) -{ - int ifd, ofd; - - if ((ifd = git_futils_open_ro(from)) < 0) - return ifd; - - if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) { - p_close(ifd); - return git_fs_path_set_error(errno, to, "open for writing"); - } - - return cp_by_fd(ifd, ofd, true); -} - -int git_futils_touch(const char *path, time_t *when) -{ - struct p_timeval times[2]; - int ret; - - times[0].tv_sec = times[1].tv_sec = when ? *when : time(NULL); - times[0].tv_usec = times[1].tv_usec = 0; - - ret = p_utimes(path, times); - - return (ret < 0) ? git_fs_path_set_error(errno, path, "touch") : 0; -} - -static int cp_link(const char *from, const char *to, size_t link_size) -{ - int error = 0; - ssize_t read_len; - char *link_data; - size_t alloc_size; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1); - link_data = git__malloc(alloc_size); - GIT_ERROR_CHECK_ALLOC(link_data); - - read_len = p_readlink(from, link_data, link_size); - if (read_len != (ssize_t)link_size) { - git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", from); - error = -1; - } - else { - link_data[read_len] = '\0'; - - if (p_symlink(link_data, to) < 0) { - git_error_set(GIT_ERROR_OS, "could not symlink '%s' as '%s'", - link_data, to); - error = -1; - } - } - - git__free(link_data); - return error; -} - -typedef struct { - const char *to_root; - git_str to; - ssize_t from_prefix; - uint32_t flags; - uint32_t mkdir_flags; - mode_t dirmode; -} cp_r_info; - -#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10) - -static int _cp_r_mkdir(cp_r_info *info, git_str *from) -{ - int error = 0; - - /* create root directory the first time we need to create a directory */ - if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) { - error = git_futils_mkdir( - info->to_root, info->dirmode, - (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0); - - info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT; - } - - /* create directory with root as base to prevent excess chmods */ - if (!error) - error = git_futils_mkdir_relative( - from->ptr + info->from_prefix, info->to_root, - info->dirmode, info->mkdir_flags, NULL); - - return error; -} - -static int _cp_r_callback(void *ref, git_str *from) -{ - int error = 0; - cp_r_info *info = ref; - struct stat from_st, to_st; - bool exists = false; - - if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 && - from->ptr[git_fs_path_basename_offset(from)] == '.') - return 0; - - if ((error = git_str_joinpath( - &info->to, info->to_root, from->ptr + info->from_prefix)) < 0) - return error; - - if (!(error = git_fs_path_lstat(info->to.ptr, &to_st))) - exists = true; - else if (error != GIT_ENOTFOUND) - return error; - else { - git_error_clear(); - error = 0; - } - - if ((error = git_fs_path_lstat(from->ptr, &from_st)) < 0) - return error; - - if (S_ISDIR(from_st.st_mode)) { - mode_t oldmode = info->dirmode; - - /* if we are not chmod'ing, then overwrite dirmode */ - if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0) - info->dirmode = from_st.st_mode; - - /* make directory now if CREATE_EMPTY_DIRS is requested and needed */ - if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0) - error = _cp_r_mkdir(info, from); - - /* recurse onto target directory */ - if (!error && (!exists || S_ISDIR(to_st.st_mode))) - error = git_fs_path_direach(from, 0, _cp_r_callback, info); - - if (oldmode != 0) - info->dirmode = oldmode; - - return error; - } - - if (exists) { - if ((info->flags & GIT_CPDIR_OVERWRITE) == 0) - return 0; - - if (p_unlink(info->to.ptr) < 0) { - git_error_set(GIT_ERROR_OS, "cannot overwrite existing file '%s'", - info->to.ptr); - return GIT_EEXISTS; - } - } - - /* Done if this isn't a regular file or a symlink */ - if (!S_ISREG(from_st.st_mode) && - (!S_ISLNK(from_st.st_mode) || - (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0)) - return 0; - - /* Make container directory on demand if needed */ - if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 && - (error = _cp_r_mkdir(info, from)) < 0) - return error; - - /* make symlink or regular file */ - if (info->flags & GIT_CPDIR_LINK_FILES) { - if ((error = p_link(from->ptr, info->to.ptr)) < 0) - git_error_set(GIT_ERROR_OS, "failed to link '%s'", from->ptr); - } else if (S_ISLNK(from_st.st_mode)) { - error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); - } else { - mode_t usemode = from_st.st_mode; - - if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) - usemode = GIT_PERMS_FOR_WRITE(usemode); - - error = git_futils_cp(from->ptr, info->to.ptr, usemode); - } - - return error; -} - -int git_futils_cp_r( - const char *from, - const char *to, - uint32_t flags, - mode_t dirmode) -{ - int error; - git_str path = GIT_STR_INIT; - cp_r_info info; - - if (git_str_joinpath(&path, from, "") < 0) /* ensure trailing slash */ - return -1; - - memset(&info, 0, sizeof(info)); - info.to_root = to; - info.flags = flags; - info.dirmode = dirmode; - info.from_prefix = path.size; - git_str_init(&info.to, 0); - - /* precalculate mkdir flags */ - if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) { - /* if not creating empty dirs, then use mkdir to create the path on - * demand right before files are copied. - */ - info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST; - if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) - info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH; - } else { - /* otherwise, we will do simple mkdir as directories are encountered */ - info.mkdir_flags = - ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0; - } - - error = _cp_r_callback(&info, &path); - - git_str_dispose(&path); - git_str_dispose(&info.to); - - return error; -} - -int git_futils_filestamp_check( - git_futils_filestamp *stamp, const char *path) -{ - struct stat st; - - /* if the stamp is NULL, then always reload */ - if (stamp == NULL) - return 1; - - if (p_stat(path, &st) < 0) - return GIT_ENOTFOUND; - - if (stamp->mtime.tv_sec == st.st_mtime && -#if defined(GIT_USE_NSEC) - stamp->mtime.tv_nsec == st.st_mtime_nsec && -#endif - stamp->size == (uint64_t)st.st_size && - stamp->ino == (unsigned int)st.st_ino) - return 0; - - stamp->mtime.tv_sec = st.st_mtime; -#if defined(GIT_USE_NSEC) - stamp->mtime.tv_nsec = st.st_mtime_nsec; -#endif - stamp->size = (uint64_t)st.st_size; - stamp->ino = (unsigned int)st.st_ino; - - return 1; -} - -void git_futils_filestamp_set( - git_futils_filestamp *target, const git_futils_filestamp *source) -{ - if (source) - memcpy(target, source, sizeof(*target)); - else - memset(target, 0, sizeof(*target)); -} - - -void git_futils_filestamp_set_from_stat( - git_futils_filestamp *stamp, struct stat *st) -{ - if (st) { - stamp->mtime.tv_sec = st->st_mtime; -#if defined(GIT_USE_NSEC) - stamp->mtime.tv_nsec = st->st_mtime_nsec; -#else - stamp->mtime.tv_nsec = 0; -#endif - stamp->size = (uint64_t)st->st_size; - stamp->ino = (unsigned int)st->st_ino; - } else { - memset(stamp, 0, sizeof(*stamp)); - } -} - -int git_futils_fsync_dir(const char *path) -{ -#ifdef GIT_WIN32 - GIT_UNUSED(path); - return 0; -#else - int fd, error = -1; - - if ((fd = p_open(path, O_RDONLY)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to open directory '%s' for fsync", path); - return -1; - } - - if ((error = p_fsync(fd)) < 0) - git_error_set(GIT_ERROR_OS, "failed to fsync directory '%s'", path); - - p_close(fd); - return error; -#endif -} - -int git_futils_fsync_parent(const char *path) -{ - char *parent; - int error; - - if ((parent = git_fs_path_dirname(path)) == NULL) - return -1; - - error = git_futils_fsync_dir(parent); - git__free(parent); - return error; -} diff --git a/src/futils.h b/src/futils.h deleted file mode 100644 index a82ec41cc..000000000 --- a/src/futils.h +++ /dev/null @@ -1,402 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_futils_h__ -#define INCLUDE_futils_h__ - -#include "common.h" - -#include "map.h" -#include "posix.h" -#include "fs_path.h" -#include "pool.h" -#include "strmap.h" -#include "hash.h" - -/** - * Filebuffer methods - * - * Read whole files into an in-memory buffer for processing - */ -extern int git_futils_readbuffer(git_str *obj, const char *path); -extern int git_futils_readbuffer_updated( - git_str *obj, - const char *path, - unsigned char checksum[GIT_HASH_SHA1_SIZE], - int *updated); -extern int git_futils_readbuffer_fd(git_str *obj, git_file fd, size_t len); - -/* Additional constants for `git_futils_writebuffer`'s `open_flags`. We - * support these internally and they will be removed before the `open` call. - */ -#ifndef O_FSYNC -# define O_FSYNC (1 << 31) -#endif - -extern int git_futils_writebuffer( - const git_str *buf, const char *path, int open_flags, mode_t mode); - -/** - * File utils - * - * These are custom filesystem-related helper methods. They are - * rather high level, and wrap the underlying POSIX methods - * - * All these methods return 0 on success, - * or an error code on failure and an error message is set. - */ - -/** - * Create and open a file, while also - * creating all the folders in its path - */ -extern int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode); - -/** - * Create and open a process-locked file - */ -extern int git_futils_creat_locked(const char *path, const mode_t mode); - -/** - * Create and open a process-locked file, while - * also creating all the folders in its path - */ -extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode); - -/** - * Create a path recursively. - */ -extern int git_futils_mkdir_r(const char *path, const mode_t mode); - -/** - * Flags to pass to `git_futils_mkdir`. - * - * * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists. - * * GIT_MKDIR_PATH says to make all components in the path. - * * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation - * * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path - * * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path - * * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path - * * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST - * * GIT_MKDIR_REMOVE_FILES says to remove files and recreate dirs - * * GIT_MKDIR_REMOVE_SYMLINKS says to remove symlinks and recreate dirs - * - * Note that the chmod options will be executed even if the directory already - * exists, unless GIT_MKDIR_EXCL is given. - */ -typedef enum { - GIT_MKDIR_EXCL = 1, - GIT_MKDIR_PATH = 2, - GIT_MKDIR_CHMOD = 4, - GIT_MKDIR_CHMOD_PATH = 8, - GIT_MKDIR_SKIP_LAST = 16, - GIT_MKDIR_SKIP_LAST2 = 32, - GIT_MKDIR_VERIFY_DIR = 64, - GIT_MKDIR_REMOVE_FILES = 128, - GIT_MKDIR_REMOVE_SYMLINKS = 256 -} git_futils_mkdir_flags; - -struct git_futils_mkdir_perfdata -{ - size_t stat_calls; - size_t mkdir_calls; - size_t chmod_calls; -}; - -struct git_futils_mkdir_options -{ - git_strmap *dir_map; - git_pool *pool; - struct git_futils_mkdir_perfdata perfdata; -}; - -/** - * Create a directory or entire path. - * - * This makes a directory (and the entire path leading up to it if requested), - * and optionally chmods the directory immediately after (or each part of the - * path if requested). - * - * @param path The path to create, relative to base. - * @param base Root for relative path. These directories will never be made. - * @param mode The mode to use for created directories. - * @param flags Combination of the mkdir flags above. - * @param opts Extended options, or null. - * @return 0 on success, else error code - */ -extern int git_futils_mkdir_relative(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts); - -/** - * Create a directory or entire path. Similar to `git_futils_mkdir_relative` - * without performance data. - */ -extern int git_futils_mkdir(const char *path, mode_t mode, uint32_t flags); - -/** - * Create all the folders required to contain - * the full path of a file - */ -extern int git_futils_mkpath2file(const char *path, const mode_t mode); - -/** - * Flags to pass to `git_futils_rmdir_r`. - * - * * GIT_RMDIR_EMPTY_HIERARCHY - the default; remove hierarchy of empty - * dirs and generate error if any files are found. - * * GIT_RMDIR_REMOVE_FILES - attempt to remove files in the hierarchy. - * * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error. - * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base - * if removing this item leaves them empty - * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR - * * GIT_RMDIR_SKIP_ROOT - don't remove root directory itself - */ -typedef enum { - GIT_RMDIR_EMPTY_HIERARCHY = 0, - GIT_RMDIR_REMOVE_FILES = (1 << 0), - GIT_RMDIR_SKIP_NONEMPTY = (1 << 1), - GIT_RMDIR_EMPTY_PARENTS = (1 << 2), - GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3), - GIT_RMDIR_SKIP_ROOT = (1 << 4) -} git_futils_rmdir_flags; - -/** - * Remove path and any files and directories beneath it. - * - * @param path Path to the top level directory to process. - * @param base Root for relative path. - * @param flags Combination of git_futils_rmdir_flags values - * @return 0 on success; -1 on error. - */ -extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags); - -/** - * Create and open a temporary file with a `_git2_` suffix in a - * protected directory; the file created will created will honor - * the current `umask`. Writes the filename into path_out. - * - * This function uses a high-quality PRNG seeded by the system's - * entropy pool _where available_ and falls back to a simple seed - * (time plus system information) when not. This is suitable for - * writing within a protected directory, but the system's safe - * temporary file creation functions should be preferred where - * available when writing into world-writable (temp) directories. - * - * @return On success, an open file descriptor, else an error code < 0. - */ -extern int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode); - -/** - * Move a file on the filesystem, create the - * destination path if it doesn't exist - */ -extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode); - -/** - * Copy a file - * - * The filemode will be used for the newly created file. - */ -extern int git_futils_cp( - const char *from, - const char *to, - mode_t filemode); - -/** - * Set the files atime and mtime to the given time, or the current time - * if `ts` is NULL. - */ -extern int git_futils_touch(const char *path, time_t *when); - -/** - * Flags that can be passed to `git_futils_cp_r`. - * - * - GIT_CPDIR_CREATE_EMPTY_DIRS: create directories even if there are no - * files under them (otherwise directories will only be created lazily - * when a file inside them is copied). - * - GIT_CPDIR_COPY_SYMLINKS: copy symlinks, otherwise they are ignored. - * - GIT_CPDIR_COPY_DOTFILES: copy files with leading '.', otherwise ignored. - * - GIT_CPDIR_OVERWRITE: overwrite pre-existing files with source content, - * otherwise they are silently skipped. - * - GIT_CPDIR_CHMOD_DIRS: explicitly chmod directories to `dirmode` - * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the - * source file to the target; with this flag, always use 0666 (or 0777 if - * source has exec bits set) for target. - * - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files - */ -typedef enum { - GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0), - GIT_CPDIR_COPY_SYMLINKS = (1u << 1), - GIT_CPDIR_COPY_DOTFILES = (1u << 2), - GIT_CPDIR_OVERWRITE = (1u << 3), - GIT_CPDIR_CHMOD_DIRS = (1u << 4), - GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5), - GIT_CPDIR_LINK_FILES = (1u << 6) -} git_futils_cpdir_flags; - -/** - * Copy a directory tree. - * - * This copies directories and files from one root to another. You can - * pass a combination of GIT_CPDIR flags as defined above. - * - * If you pass the CHMOD flag, then the dirmode will be applied to all - * directories that are created during the copy, overriding the natural - * permissions. If you do not pass the CHMOD flag, then the dirmode - * will actually be copied from the source files and the `dirmode` arg - * will be ignored. - */ -extern int git_futils_cp_r( - const char *from, - const char *to, - uint32_t flags, - mode_t dirmode); - -/** - * Open a file readonly and set error if needed. - */ -extern int git_futils_open_ro(const char *path); - -/** - * Truncate a file, creating it if it doesn't exist. - */ -extern int git_futils_truncate(const char *path, int mode); - -/** - * Get the filesize in bytes of a file - */ -extern int git_futils_filesize(uint64_t *out, git_file fd); - -#define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0100) != 0) -#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644) -#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0777 : 0666) - -#define GIT_MODE_PERMS_MASK 0777 -#define GIT_MODE_TYPE_MASK 0170000 -#define GIT_MODE_TYPE(MODE) ((MODE) & GIT_MODE_TYPE_MASK) -#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB)) - -/** - * Convert a mode_t from the OS to a legal git mode_t value. - */ -extern mode_t git_futils_canonical_mode(mode_t raw_mode); - - -/** - * Read-only map all or part of a file into memory. - * When possible this function should favor a virtual memory - * style mapping over some form of malloc()+read(), as the - * data access will be random and is not likely to touch the - * majority of the region requested. - * - * @param out buffer to populate with the mapping information. - * @param fd open descriptor to configure the mapping from. - * @param begin first byte to map, this should be page aligned. - * @param len number of bytes to map. - * @return - * - 0 on success; - * - -1 on error. - */ -extern int git_futils_mmap_ro( - git_map *out, - git_file fd, - off64_t begin, - size_t len); - -/** - * Read-only map an entire file. - * - * @param out buffer to populate with the mapping information. - * @param path path to file to be opened. - * @return - * - 0 on success; - * - GIT_ENOTFOUND if not found; - * - -1 on an unspecified OS related error. - */ -extern int git_futils_mmap_ro_file( - git_map *out, - const char *path); - -/** - * Release the memory associated with a previous memory mapping. - * @param map the mapping description previously configured. - */ -extern void git_futils_mmap_free(git_map *map); - -/** - * Create a "fake" symlink (text file containing the target path). - * - * @param target original symlink target - * @param path symlink file to be created - * @return 0 on success, -1 on error - */ -extern int git_futils_fake_symlink(const char *target, const char *path); - -/** - * A file stamp represents a snapshot of information about a file that can - * be used to test if the file changes. This portable implementation is - * based on stat data about that file, but it is possible that OS specific - * versions could be implemented in the future. - */ -typedef struct { - struct timespec mtime; - uint64_t size; - unsigned int ino; -} git_futils_filestamp; - -/** - * Compare stat information for file with reference info. - * - * This function updates the file stamp to current data for the given path - * and returns 0 if the file is up-to-date relative to the prior setting, - * 1 if the file has been changed, or GIT_ENOTFOUND if the file doesn't - * exist. This will not call git_error_set, so you must set the error if you - * plan to return an error. - * - * @param stamp File stamp to be checked - * @param path Path to stat and check if changed - * @return 0 if up-to-date, 1 if out-of-date, GIT_ENOTFOUND if cannot stat - */ -extern int git_futils_filestamp_check( - git_futils_filestamp *stamp, const char *path); - -/** - * Set or reset file stamp data - * - * This writes the target file stamp. If the source is NULL, this will set - * the target stamp to values that will definitely be out of date. If the - * source is not NULL, this copies the source values to the target. - * - * @param tgt File stamp to write to - * @param src File stamp to copy from or NULL to clear the target - */ -extern void git_futils_filestamp_set( - git_futils_filestamp *tgt, const git_futils_filestamp *src); - -/** - * Set file stamp data from stat structure - */ -extern void git_futils_filestamp_set_from_stat( - git_futils_filestamp *stamp, struct stat *st); - -/** - * `fsync` the parent directory of the given path, if `fsync` is - * supported for directories on this platform. - * - * @param path Path of the directory to sync. - * @return 0 on success, -1 on error - */ -extern int git_futils_fsync_dir(const char *path); - -/** - * `fsync` the parent directory of the given path, if `fsync` is - * supported for directories on this platform. - * - * @param path Path of the file whose parent directory should be synced. - * @return 0 on success, -1 on error - */ -extern int git_futils_fsync_parent(const char *path); - -#endif diff --git a/src/graph.c b/src/graph.c deleted file mode 100644 index 35e914f74..000000000 --- a/src/graph.c +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "revwalk.h" -#include "merge.h" -#include "git2/graph.h" - -static int interesting(git_pqueue *list, git_commit_list *roots) -{ - unsigned int i; - - for (i = 0; i < git_pqueue_size(list); i++) { - git_commit_list_node *commit = git_pqueue_get(list, i); - if ((commit->flags & STALE) == 0) - return 1; - } - - while(roots) { - if ((roots->item->flags & STALE) == 0) - return 1; - roots = roots->next; - } - - return 0; -} - -static int mark_parents(git_revwalk *walk, git_commit_list_node *one, - git_commit_list_node *two) -{ - unsigned int i; - git_commit_list *roots = NULL; - git_pqueue list; - - /* if the commit is repeated, we have a our merge base already */ - if (one == two) { - one->flags |= PARENT1 | PARENT2 | RESULT; - return 0; - } - - if (git_pqueue_init(&list, 0, 2, git_commit_list_generation_cmp) < 0) - return -1; - - if (git_commit_list_parse(walk, one) < 0) - goto on_error; - one->flags |= PARENT1; - if (git_pqueue_insert(&list, one) < 0) - goto on_error; - - if (git_commit_list_parse(walk, two) < 0) - goto on_error; - two->flags |= PARENT2; - if (git_pqueue_insert(&list, two) < 0) - goto on_error; - - /* as long as there are non-STALE commits */ - while (interesting(&list, roots)) { - git_commit_list_node *commit = git_pqueue_pop(&list); - unsigned int flags; - - if (commit == NULL) - break; - - flags = commit->flags & (PARENT1 | PARENT2 | STALE); - if (flags == (PARENT1 | PARENT2)) { - if (!(commit->flags & RESULT)) - commit->flags |= RESULT; - /* we mark the parents of a merge stale */ - flags |= STALE; - } - - for (i = 0; i < commit->out_degree; i++) { - git_commit_list_node *p = commit->parents[i]; - if ((p->flags & flags) == flags) - continue; - - if (git_commit_list_parse(walk, p) < 0) - goto on_error; - - p->flags |= flags; - if (git_pqueue_insert(&list, p) < 0) - goto on_error; - } - - /* Keep track of root commits, to make sure the path gets marked */ - if (commit->out_degree == 0) { - if (git_commit_list_insert(commit, &roots) == NULL) - goto on_error; - } - } - - git_commit_list_free(&roots); - git_pqueue_free(&list); - return 0; - -on_error: - git_commit_list_free(&roots); - git_pqueue_free(&list); - return -1; -} - - -static int ahead_behind(git_commit_list_node *one, git_commit_list_node *two, - size_t *ahead, size_t *behind) -{ - git_commit_list_node *commit; - git_pqueue pq; - int error = 0, i; - *ahead = 0; - *behind = 0; - - if (git_pqueue_init(&pq, 0, 2, git_commit_list_time_cmp) < 0) - return -1; - - if ((error = git_pqueue_insert(&pq, one)) < 0 || - (error = git_pqueue_insert(&pq, two)) < 0) - goto done; - - while ((commit = git_pqueue_pop(&pq)) != NULL) { - if (commit->flags & RESULT || - (commit->flags & (PARENT1 | PARENT2)) == (PARENT1 | PARENT2)) - continue; - else if (commit->flags & PARENT1) - (*ahead)++; - else if (commit->flags & PARENT2) - (*behind)++; - - for (i = 0; i < commit->out_degree; i++) { - git_commit_list_node *p = commit->parents[i]; - if ((error = git_pqueue_insert(&pq, p)) < 0) - goto done; - } - commit->flags |= RESULT; - } - -done: - git_pqueue_free(&pq); - return error; -} - -int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo, - const git_oid *local, const git_oid *upstream) -{ - git_revwalk *walk; - git_commit_list_node *commit_u, *commit_l; - - if (git_revwalk_new(&walk, repo) < 0) - return -1; - - commit_u = git_revwalk__commit_lookup(walk, upstream); - if (commit_u == NULL) - goto on_error; - - commit_l = git_revwalk__commit_lookup(walk, local); - if (commit_l == NULL) - goto on_error; - - if (mark_parents(walk, commit_l, commit_u) < 0) - goto on_error; - if (ahead_behind(commit_l, commit_u, ahead, behind) < 0) - goto on_error; - - git_revwalk_free(walk); - - return 0; - -on_error: - git_revwalk_free(walk); - return -1; -} - -int git_graph_descendant_of(git_repository *repo, const git_oid *commit, const git_oid *ancestor) -{ - if (git_oid_equal(commit, ancestor)) - return 0; - - return git_graph_reachable_from_any(repo, ancestor, commit, 1); -} - -int git_graph_reachable_from_any( - git_repository *repo, - const git_oid *commit_id, - const git_oid descendant_array[], - size_t length) -{ - git_revwalk *walk = NULL; - git_vector list; - git_commit_list *result = NULL; - git_commit_list_node *commit; - size_t i; - uint32_t minimum_generation = 0xffffffff; - int error = 0; - - if (!length) - return 0; - - for (i = 0; i < length; ++i) { - if (git_oid_equal(commit_id, &descendant_array[i])) - return 1; - } - - if ((error = git_vector_init(&list, length + 1, NULL)) < 0) - return error; - - if ((error = git_revwalk_new(&walk, repo)) < 0) - goto done; - - for (i = 0; i < length; i++) { - commit = git_revwalk__commit_lookup(walk, &descendant_array[i]); - if (commit == NULL) { - error = -1; - goto done; - } - - git_vector_insert(&list, commit); - if (minimum_generation > commit->generation) - minimum_generation = commit->generation; - } - - commit = git_revwalk__commit_lookup(walk, commit_id); - if (commit == NULL) { - error = -1; - goto done; - } - - if (minimum_generation > commit->generation) - minimum_generation = commit->generation; - - if ((error = git_merge__bases_many(&result, walk, commit, &list, minimum_generation)) < 0) - goto done; - - if (result) { - error = git_oid_equal(commit_id, &result->item->oid); - } else { - /* No merge-base found, it's not a descendant */ - error = 0; - } - -done: - git_commit_list_free(&result); - git_vector_free(&list); - git_revwalk_free(walk); - return error; -} diff --git a/src/hash.c b/src/hash.c deleted file mode 100644 index 98ceb05d2..000000000 --- a/src/hash.c +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "hash.h" - -int git_hash_global_init(void) -{ - return git_hash_sha1_global_init(); -} - -int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm) -{ - int error; - - switch (algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - error = git_hash_sha1_ctx_init(&ctx->ctx.sha1); - break; - default: - git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); - error = -1; - } - - ctx->algorithm = algorithm; - return error; -} - -void git_hash_ctx_cleanup(git_hash_ctx *ctx) -{ - switch (ctx->algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - git_hash_sha1_ctx_cleanup(&ctx->ctx.sha1); - return; - default: - /* unreachable */ ; - } -} - -int git_hash_init(git_hash_ctx *ctx) -{ - switch (ctx->algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - return git_hash_sha1_init(&ctx->ctx.sha1); - default: - /* unreachable */ ; - } - - git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); - return -1; -} - -int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) -{ - switch (ctx->algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - return git_hash_sha1_update(&ctx->ctx.sha1, data, len); - default: - /* unreachable */ ; - } - - git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); - return -1; -} - -int git_hash_final(unsigned char *out, git_hash_ctx *ctx) -{ - switch (ctx->algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - return git_hash_sha1_final(out, &ctx->ctx.sha1); - default: - /* unreachable */ ; - } - - git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); - return -1; -} - -int git_hash_buf( - unsigned char *out, - const void *data, - size_t len, - git_hash_algorithm_t algorithm) -{ - git_hash_ctx ctx; - int error = 0; - - if (git_hash_ctx_init(&ctx, algorithm) < 0) - return -1; - - if ((error = git_hash_update(&ctx, data, len)) >= 0) - error = git_hash_final(out, &ctx); - - git_hash_ctx_cleanup(&ctx); - - return error; -} - -int git_hash_vec( - unsigned char *out, - git_str_vec *vec, - size_t n, - git_hash_algorithm_t algorithm) -{ - git_hash_ctx ctx; - size_t i; - int error = 0; - - if (git_hash_ctx_init(&ctx, algorithm) < 0) - return -1; - - for (i = 0; i < n; i++) { - if ((error = git_hash_update(&ctx, vec[i].data, vec[i].len)) < 0) - goto done; - } - - error = git_hash_final(out, &ctx); - -done: - git_hash_ctx_cleanup(&ctx); - - return error; -} - -int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len) -{ - static char hex[] = "0123456789abcdef"; - char *str = out; - size_t i; - - for (i = 0; i < hash_len; i++) { - *str++ = hex[hash[i] >> 4]; - *str++ = hex[hash[i] & 0x0f]; - } - - *str++ = '\0'; - - return 0; -} diff --git a/src/hash.h b/src/hash.h deleted file mode 100644 index 507c1cb25..000000000 --- a/src/hash.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_h__ -#define INCLUDE_hash_h__ - -#include "common.h" - -#include "hash/sha1.h" - -typedef struct { - void *data; - size_t len; -} git_str_vec; - -typedef enum { - GIT_HASH_ALGORITHM_NONE = 0, - GIT_HASH_ALGORITHM_SHA1 -} git_hash_algorithm_t; - -typedef struct git_hash_ctx { - union { - git_hash_sha1_ctx sha1; - } ctx; - git_hash_algorithm_t algorithm; -} git_hash_ctx; - -int git_hash_global_init(void); - -int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm); -void git_hash_ctx_cleanup(git_hash_ctx *ctx); - -int git_hash_init(git_hash_ctx *c); -int git_hash_update(git_hash_ctx *c, const void *data, size_t len); -int git_hash_final(unsigned char *out, git_hash_ctx *c); - -int git_hash_buf(unsigned char *out, const void *data, size_t len, git_hash_algorithm_t algorithm); -int git_hash_vec(unsigned char *out, git_str_vec *vec, size_t n, git_hash_algorithm_t algorithm); - -int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len); - -#endif diff --git a/src/hash/sha1.h b/src/hash/sha1.h deleted file mode 100644 index 4b4dae3f8..000000000 --- a/src/hash/sha1.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_h__ -#define INCLUDE_hash_sha1_h__ - -#include "common.h" - -typedef struct git_hash_sha1_ctx git_hash_sha1_ctx; - -#if defined(GIT_SHA1_COLLISIONDETECT) -# include "sha1/collisiondetect.h" -#elif defined(GIT_SHA1_COMMON_CRYPTO) -# include "sha1/common_crypto.h" -#elif defined(GIT_SHA1_OPENSSL) -# include "sha1/openssl.h" -#elif defined(GIT_SHA1_WIN32) -# include "sha1/win32.h" -#elif defined(GIT_SHA1_MBEDTLS) -# include "sha1/mbedtls.h" -#else -# include "sha1/generic.h" -#endif - -#define GIT_HASH_SHA1_SIZE 20 - -int git_hash_sha1_global_init(void); - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx); -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx); - -int git_hash_sha1_init(git_hash_sha1_ctx *c); -int git_hash_sha1_update(git_hash_sha1_ctx *c, const void *data, size_t len); -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *c); - -#endif diff --git a/src/hash/sha1/collisiondetect.c b/src/hash/sha1/collisiondetect.c deleted file mode 100644 index ec7059c4c..000000000 --- a/src/hash/sha1/collisiondetect.c +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "collisiondetect.h" - -int git_hash_sha1_global_init(void) -{ - return 0; -} - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) -{ - return git_hash_sha1_init(ctx); -} - -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) -{ - GIT_UNUSED(ctx); -} - -int git_hash_sha1_init(git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - SHA1DCInit(&ctx->c); - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - GIT_ASSERT_ARG(ctx); - SHA1DCUpdate(&ctx->c, data, len); - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - if (SHA1DCFinal(out, &ctx->c)) { - git_error_set(GIT_ERROR_SHA1, "SHA1 collision attack detected"); - return -1; - } - - return 0; -} diff --git a/src/hash/sha1/collisiondetect.h b/src/hash/sha1/collisiondetect.h deleted file mode 100644 index eb88e86c1..000000000 --- a/src/hash/sha1/collisiondetect.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_collisiondetect_h__ -#define INCLUDE_hash_sha1_collisiondetect_h__ - -#include "hash/sha1.h" - -#include "sha1dc/sha1.h" - -struct git_hash_sha1_ctx { - SHA1_CTX c; -}; - -#endif diff --git a/src/hash/sha1/common_crypto.c b/src/hash/sha1/common_crypto.c deleted file mode 100644 index 9d608f449..000000000 --- a/src/hash/sha1/common_crypto.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common_crypto.h" - -#define CC_LONG_MAX ((CC_LONG)-1) - -int git_hash_sha1_global_init(void) -{ - return 0; -} - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) -{ - return git_hash_sha1_init(ctx); -} - -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) -{ - GIT_UNUSED(ctx); -} - -int git_hash_sha1_init(git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - CC_SHA1_Init(&ctx->c); - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) -{ - const unsigned char *data = _data; - - GIT_ASSERT_ARG(ctx); - - while (len > 0) { - CC_LONG chunk = (len > CC_LONG_MAX) ? CC_LONG_MAX : (CC_LONG)len; - - CC_SHA1_Update(&ctx->c, data, chunk); - - data += chunk; - len -= chunk; - } - - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - CC_SHA1_Final(out, &ctx->c); - return 0; -} diff --git a/src/hash/sha1/common_crypto.h b/src/hash/sha1/common_crypto.h deleted file mode 100644 index a5fcfb33e..000000000 --- a/src/hash/sha1/common_crypto.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_common_crypto_h__ -#define INCLUDE_hash_sha1_common_crypto_h__ - -#include "hash/sha1.h" - -#include - -struct git_hash_sha1_ctx { - CC_SHA1_CTX c; -}; - -#endif diff --git a/src/hash/sha1/generic.c b/src/hash/sha1/generic.c deleted file mode 100644 index 85b34c578..000000000 --- a/src/hash/sha1/generic.c +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "generic.h" - -#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) - -/* - * Force usage of rol or ror by selecting the one with the smaller constant. - * It _can_ generate slightly smaller code (a constant of 1 is special), but - * perhaps more importantly it's possibly faster on any uarch that does a - * rotate with a loop. - */ - -#define SHA_ASM(op, x, n) (__extension__ ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; })) -#define SHA_ROL(x,n) SHA_ASM("rol", x, n) -#define SHA_ROR(x,n) SHA_ASM("ror", x, n) - -#else - -#define SHA_ROT(X,l,r) (((X) << (l)) | ((X) >> (r))) -#define SHA_ROL(X,n) SHA_ROT(X,n,32-(n)) -#define SHA_ROR(X,n) SHA_ROT(X,32-(n),n) - -#endif - -/* - * If you have 32 registers or more, the compiler can (and should) - * try to change the array[] accesses into registers. However, on - * machines with less than ~25 registers, that won't really work, - * and at least gcc will make an unholy mess of it. - * - * So to avoid that mess which just slows things down, we force - * the stores to memory to actually happen (we might be better off - * with a 'W(t)=(val);asm("":"+m" (W(t))' there instead, as - * suggested by Artur Skawina - that will also make gcc unable to - * try to do the silly "optimize away loads" part because it won't - * see what the value will be). - * - * Ben Herrenschmidt reports that on PPC, the C version comes close - * to the optimized asm with this (ie on PPC you don't want that - * 'volatile', since there are lots of registers). - * - * On ARM we get the best code generation by forcing a full memory barrier - * between each SHA_ROUND, otherwise gcc happily get wild with spilling and - * the stack frame size simply explode and performance goes down the drain. - */ - -#if defined(__i386__) || defined(__x86_64__) - #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val)) -#elif defined(__GNUC__) && defined(__arm__) - #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0) -#else - #define setW(x, val) (W(x) = (val)) -#endif - -/* - * Performance might be improved if the CPU architecture is OK with - * unaligned 32-bit loads and a fast ntohl() is available. - * Otherwise fall back to byte loads and shifts which is portable, - * and is faster on architectures with memory alignment issues. - */ - -#if defined(__i386__) || defined(__x86_64__) || \ - defined(_M_IX86) || defined(_M_X64) || \ - defined(__ppc__) || defined(__ppc64__) || \ - defined(__powerpc__) || defined(__powerpc64__) || \ - defined(__s390__) || defined(__s390x__) - -#define get_be32(p) ntohl(*(const unsigned int *)(p)) -#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0) - -#else - -#define get_be32(p) ( \ - (*((const unsigned char *)(p) + 0) << 24) | \ - (*((const unsigned char *)(p) + 1) << 16) | \ - (*((const unsigned char *)(p) + 2) << 8) | \ - (*((const unsigned char *)(p) + 3) << 0) ) -#define put_be32(p, v) do { \ - unsigned int __v = (v); \ - *((unsigned char *)(p) + 0) = __v >> 24; \ - *((unsigned char *)(p) + 1) = __v >> 16; \ - *((unsigned char *)(p) + 2) = __v >> 8; \ - *((unsigned char *)(p) + 3) = __v >> 0; } while (0) - -#endif - -/* This "rolls" over the 512-bit array */ -#define W(x) (array[(x)&15]) - -/* - * Where do we get the source from? The first 16 iterations get it from - * the input data, the next mix it from the 512-bit array. - */ -#define SHA_SRC(t) get_be32(data + t) -#define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1) - -#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \ - unsigned int TEMP = input(t); setW(t, TEMP); \ - E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \ - B = SHA_ROR(B, 2); } while (0) - -#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) -#define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) -#define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E ) -#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E ) -#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E ) - -static void hash__block(git_hash_sha1_ctx *ctx, const unsigned int *data) -{ - unsigned int A,B,C,D,E; - unsigned int array[16]; - - A = ctx->H[0]; - B = ctx->H[1]; - C = ctx->H[2]; - D = ctx->H[3]; - E = ctx->H[4]; - - /* Round 1 - iterations 0-16 take their input from 'data' */ - T_0_15( 0, A, B, C, D, E); - T_0_15( 1, E, A, B, C, D); - T_0_15( 2, D, E, A, B, C); - T_0_15( 3, C, D, E, A, B); - T_0_15( 4, B, C, D, E, A); - T_0_15( 5, A, B, C, D, E); - T_0_15( 6, E, A, B, C, D); - T_0_15( 7, D, E, A, B, C); - T_0_15( 8, C, D, E, A, B); - T_0_15( 9, B, C, D, E, A); - T_0_15(10, A, B, C, D, E); - T_0_15(11, E, A, B, C, D); - T_0_15(12, D, E, A, B, C); - T_0_15(13, C, D, E, A, B); - T_0_15(14, B, C, D, E, A); - T_0_15(15, A, B, C, D, E); - - /* Round 1 - tail. Input from 512-bit mixing array */ - T_16_19(16, E, A, B, C, D); - T_16_19(17, D, E, A, B, C); - T_16_19(18, C, D, E, A, B); - T_16_19(19, B, C, D, E, A); - - /* Round 2 */ - T_20_39(20, A, B, C, D, E); - T_20_39(21, E, A, B, C, D); - T_20_39(22, D, E, A, B, C); - T_20_39(23, C, D, E, A, B); - T_20_39(24, B, C, D, E, A); - T_20_39(25, A, B, C, D, E); - T_20_39(26, E, A, B, C, D); - T_20_39(27, D, E, A, B, C); - T_20_39(28, C, D, E, A, B); - T_20_39(29, B, C, D, E, A); - T_20_39(30, A, B, C, D, E); - T_20_39(31, E, A, B, C, D); - T_20_39(32, D, E, A, B, C); - T_20_39(33, C, D, E, A, B); - T_20_39(34, B, C, D, E, A); - T_20_39(35, A, B, C, D, E); - T_20_39(36, E, A, B, C, D); - T_20_39(37, D, E, A, B, C); - T_20_39(38, C, D, E, A, B); - T_20_39(39, B, C, D, E, A); - - /* Round 3 */ - T_40_59(40, A, B, C, D, E); - T_40_59(41, E, A, B, C, D); - T_40_59(42, D, E, A, B, C); - T_40_59(43, C, D, E, A, B); - T_40_59(44, B, C, D, E, A); - T_40_59(45, A, B, C, D, E); - T_40_59(46, E, A, B, C, D); - T_40_59(47, D, E, A, B, C); - T_40_59(48, C, D, E, A, B); - T_40_59(49, B, C, D, E, A); - T_40_59(50, A, B, C, D, E); - T_40_59(51, E, A, B, C, D); - T_40_59(52, D, E, A, B, C); - T_40_59(53, C, D, E, A, B); - T_40_59(54, B, C, D, E, A); - T_40_59(55, A, B, C, D, E); - T_40_59(56, E, A, B, C, D); - T_40_59(57, D, E, A, B, C); - T_40_59(58, C, D, E, A, B); - T_40_59(59, B, C, D, E, A); - - /* Round 4 */ - T_60_79(60, A, B, C, D, E); - T_60_79(61, E, A, B, C, D); - T_60_79(62, D, E, A, B, C); - T_60_79(63, C, D, E, A, B); - T_60_79(64, B, C, D, E, A); - T_60_79(65, A, B, C, D, E); - T_60_79(66, E, A, B, C, D); - T_60_79(67, D, E, A, B, C); - T_60_79(68, C, D, E, A, B); - T_60_79(69, B, C, D, E, A); - T_60_79(70, A, B, C, D, E); - T_60_79(71, E, A, B, C, D); - T_60_79(72, D, E, A, B, C); - T_60_79(73, C, D, E, A, B); - T_60_79(74, B, C, D, E, A); - T_60_79(75, A, B, C, D, E); - T_60_79(76, E, A, B, C, D); - T_60_79(77, D, E, A, B, C); - T_60_79(78, C, D, E, A, B); - T_60_79(79, B, C, D, E, A); - - ctx->H[0] += A; - ctx->H[1] += B; - ctx->H[2] += C; - ctx->H[3] += D; - ctx->H[4] += E; -} - -int git_hash_sha1_global_init(void) -{ - return 0; -} - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) -{ - return git_hash_sha1_init(ctx); -} - -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) -{ - GIT_UNUSED(ctx); -} - -int git_hash_sha1_init(git_hash_sha1_ctx *ctx) -{ - ctx->size = 0; - - /* Initialize H with the magic constants (see FIPS180 for constants) */ - ctx->H[0] = 0x67452301; - ctx->H[1] = 0xefcdab89; - ctx->H[2] = 0x98badcfe; - ctx->H[3] = 0x10325476; - ctx->H[4] = 0xc3d2e1f0; - - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - unsigned int lenW = ctx->size & 63; - - ctx->size += len; - - /* Read the data into W and process blocks as they get full */ - if (lenW) { - unsigned int left = 64 - lenW; - if (len < left) - left = (unsigned int)len; - memcpy(lenW + (char *)ctx->W, data, left); - lenW = (lenW + left) & 63; - len -= left; - data = ((const char *)data + left); - if (lenW) - return 0; - hash__block(ctx, ctx->W); - } - while (len >= 64) { - hash__block(ctx, data); - data = ((const char *)data + 64); - len -= 64; - } - if (len) - memcpy(ctx->W, data, len); - - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - static const unsigned char pad[64] = { 0x80 }; - unsigned int padlen[2]; - int i; - - /* Pad with a binary 1 (ie 0x80), then zeroes, then length */ - padlen[0] = htonl((uint32_t)(ctx->size >> 29)); - padlen[1] = htonl((uint32_t)(ctx->size << 3)); - - i = ctx->size & 63; - git_hash_sha1_update(ctx, pad, 1+ (63 & (55 - i))); - git_hash_sha1_update(ctx, padlen, 8); - - /* Output hash */ - for (i = 0; i < 5; i++) - put_be32(out + i*4, ctx->H[i]); - - return 0; -} diff --git a/src/hash/sha1/generic.h b/src/hash/sha1/generic.h deleted file mode 100644 index 53fc0823e..000000000 --- a/src/hash/sha1/generic.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_generic_h__ -#define INCLUDE_hash_sha1_generic_h__ - -#include "hash/sha1.h" - -struct git_hash_sha1_ctx { - uint64_t size; - unsigned int H[5]; - unsigned int W[16]; -}; - -#endif diff --git a/src/hash/sha1/mbedtls.c b/src/hash/sha1/mbedtls.c deleted file mode 100644 index 56016bec8..000000000 --- a/src/hash/sha1/mbedtls.c +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "mbedtls.h" - -int git_hash_sha1_global_init(void) -{ - return 0; -} - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) -{ - return git_hash_sha1_init(ctx); -} - -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) -{ - if (ctx) - mbedtls_sha1_free(&ctx->c); -} - -int git_hash_sha1_init(git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - mbedtls_sha1_init(&ctx->c); - mbedtls_sha1_starts(&ctx->c); - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - GIT_ASSERT_ARG(ctx); - mbedtls_sha1_update(&ctx->c, data, len); - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - mbedtls_sha1_finish(&ctx->c, out); - return 0; -} diff --git a/src/hash/sha1/mbedtls.h b/src/hash/sha1/mbedtls.h deleted file mode 100644 index 15f7462a4..000000000 --- a/src/hash/sha1/mbedtls.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_mbedtls_h__ -#define INCLUDE_hash_sha1_mbedtls_h__ - -#include "hash/sha1.h" - -#include - -struct git_hash_sha1_ctx { - mbedtls_sha1_context c; -}; - -#endif /* INCLUDE_hash_sha1_mbedtls_h__ */ diff --git a/src/hash/sha1/openssl.c b/src/hash/sha1/openssl.c deleted file mode 100644 index 64bf99b3c..000000000 --- a/src/hash/sha1/openssl.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "openssl.h" - -int git_hash_sha1_global_init(void) -{ - return 0; -} - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) -{ - return git_hash_sha1_init(ctx); -} - -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) -{ - GIT_UNUSED(ctx); -} - -int git_hash_sha1_init(git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - - if (SHA1_Init(&ctx->c) != 1) { - git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to initialize hash context"); - return -1; - } - - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - GIT_ASSERT_ARG(ctx); - - if (SHA1_Update(&ctx->c, data, len) != 1) { - git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to update hash"); - return -1; - } - - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - - if (SHA1_Final(out, &ctx->c) != 1) { - git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to finalize hash"); - return -1; - } - - return 0; -} diff --git a/src/hash/sha1/openssl.h b/src/hash/sha1/openssl.h deleted file mode 100644 index a223ca03e..000000000 --- a/src/hash/sha1/openssl.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_openssl_h__ -#define INCLUDE_hash_sha1_openssl_h__ - -#include "hash/sha1.h" - -#include - -struct git_hash_sha1_ctx { - SHA_CTX c; -}; - -#endif diff --git a/src/hash/sha1/sha1dc/sha1.c b/src/hash/sha1/sha1dc/sha1.c deleted file mode 100644 index 929822728..000000000 --- a/src/hash/sha1/sha1dc/sha1.c +++ /dev/null @@ -1,1909 +0,0 @@ -/*** -* Copyright 2017 Marc Stevens , Dan Shumow (danshu@microsoft.com) -* Distributed under the MIT Software License. -* See accompanying file LICENSE.txt or copy at -* https://opensource.org/licenses/MIT -***/ - -#ifndef SHA1DC_NO_STANDARD_INCLUDES -#include -#include -#include -#include -#include /* make sure macros like _BIG_ENDIAN visible */ -#endif - -#ifdef SHA1DC_CUSTOM_INCLUDE_SHA1_C -#include SHA1DC_CUSTOM_INCLUDE_SHA1_C -#endif - -#ifndef SHA1DC_INIT_SAFE_HASH_DEFAULT -#define SHA1DC_INIT_SAFE_HASH_DEFAULT 1 -#endif - -#include "sha1.h" -#include "ubc_check.h" - -#if (defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \ - defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || \ - defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || \ - defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || defined(__INTEL__) || \ - defined(__386) || defined(_M_X64) || defined(_M_AMD64)) -#define SHA1DC_ON_INTEL_LIKE_PROCESSOR -#endif - -/* - Because Little-Endian architectures are most common, - we only set SHA1DC_BIGENDIAN if one of these conditions is met. - Note that all MSFT platforms are little endian, - so none of these will be defined under the MSC compiler. - If you are compiling on a big endian platform and your compiler does not define one of these, - you will have to add whatever macros your tool chain defines to indicate Big-Endianness. - */ - -#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) -/* - * Should detect Big Endian under GCC since at least 4.6.0 (gcc svn - * rev #165881). See - * https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html - * - * This also works under clang since 3.2, it copied the GCC-ism. See - * clang.git's 3b198a97d2 ("Preprocessor: add __BYTE_ORDER__ - * predefined macro", 2012-07-27) - */ -#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -#define SHA1DC_BIGENDIAN -#endif - -/* Not under GCC-alike */ -#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) -/* - * Should detect Big Endian under glibc.git since 14245eb70e ("entered - * into RCS", 1992-11-25). Defined in which will have been - * brought in by standard headers. See glibc.git and - * https://sourceforge.net/p/predef/wiki/Endianness/ - */ -#if __BYTE_ORDER == __BIG_ENDIAN -#define SHA1DC_BIGENDIAN -#endif - -/* Not under GCC-alike or glibc */ -#elif defined(_BYTE_ORDER) && defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) -/* - * *BSD and newlib (embedded linux, cygwin, etc). - * the defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) part prevents - * this condition from matching with Solaris/sparc. - * (Solaris defines only one endian macro) - */ -#if _BYTE_ORDER == _BIG_ENDIAN -#define SHA1DC_BIGENDIAN -#endif - -/* Not under GCC-alike or glibc or *BSD or newlib */ -#elif (defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ - defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || \ - defined(__sparc)) -/* - * Should define Big Endian for a whitelist of known processors. See - * https://sourceforge.net/p/predef/wiki/Endianness/ and - * http://www.oracle.com/technetwork/server-storage/solaris/portingtosolaris-138514.html - */ -#define SHA1DC_BIGENDIAN - -/* Not under GCC-alike or glibc or *BSD or newlib or */ -#elif (defined(_AIX) || defined(__hpux)) - -/* - * Defines Big Endian on a whitelist of OSs that are known to be Big - * Endian-only. See - * https://public-inbox.org/git/93056823-2740-d072-1ebd-46b440b33d7e@felt.demon.nl/ - */ -#define SHA1DC_BIGENDIAN - -/* Not under GCC-alike or glibc or *BSD or newlib or or */ -#elif defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) -/* - * As a last resort before we do anything else we're not 100% sure - * about below, we blacklist specific processors here. We could add - * more, see e.g. https://wiki.debian.org/ArchitectureSpecificsMemo - */ -#else /* Not under GCC-alike or glibc or *BSD or newlib or or or */ - -/* We do nothing more here for now */ -/*#error "Uncomment this to see if you fall through all the detection"*/ - -#endif /* Big Endian detection */ - -#if (defined(SHA1DC_FORCE_LITTLEENDIAN) && defined(SHA1DC_BIGENDIAN)) -#undef SHA1DC_BIGENDIAN -#endif -#if (defined(SHA1DC_FORCE_BIGENDIAN) && !defined(SHA1DC_BIGENDIAN)) -#define SHA1DC_BIGENDIAN -#endif -/*ENDIANNESS SELECTION*/ - -#ifndef SHA1DC_FORCE_ALIGNED_ACCESS -#if defined(SHA1DC_FORCE_UNALIGNED_ACCESS) || defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) -#define SHA1DC_ALLOW_UNALIGNED_ACCESS -#endif /*UNALIGNED ACCESS DETECTION*/ -#endif /*FORCE ALIGNED ACCESS*/ - -#define rotate_right(x,n) (((x)>>(n))|((x)<<(32-(n)))) -#define rotate_left(x,n) (((x)<<(n))|((x)>>(32-(n)))) - -#define sha1_bswap32(x) \ - {x = ((x << 8) & 0xFF00FF00) | ((x >> 8) & 0xFF00FF); x = (x << 16) | (x >> 16);} - -#define sha1_mix(W, t) (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1)) - -#ifdef SHA1DC_BIGENDIAN - #define sha1_load(m, t, temp) { temp = m[t]; } -#else - #define sha1_load(m, t, temp) { temp = m[t]; sha1_bswap32(temp); } -#endif - -#define sha1_store(W, t, x) *(volatile uint32_t *)&W[t] = x - -#define sha1_f1(b,c,d) ((d)^((b)&((c)^(d)))) -#define sha1_f2(b,c,d) ((b)^(c)^(d)) -#define sha1_f3(b,c,d) (((b)&(c))+((d)&((b)^(c)))) -#define sha1_f4(b,c,d) ((b)^(c)^(d)) - -#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, m, t) \ - { e += rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; b = rotate_left(b, 30); } -#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, m, t) \ - { e += rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; b = rotate_left(b, 30); } -#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, m, t) \ - { e += rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; b = rotate_left(b, 30); } -#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, m, t) \ - { e += rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; b = rotate_left(b, 30); } - -#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, m, t) \ - { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; } -#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, m, t) \ - { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; } -#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, m, t) \ - { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; } -#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, m, t) \ - { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; } - -#define SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, t, temp) \ - {sha1_load(m, t, temp); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30);} - -#define SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(a, b, c, d, e, W, t, temp) \ - {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30); } - -#define SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, t, temp) \ - {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1; b = rotate_left(b, 30); } - -#define SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, t, temp) \ - {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC; b = rotate_left(b, 30); } - -#define SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, t, temp) \ - {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6; b = rotate_left(b, 30); } - - -#define SHA1_STORE_STATE(i) states[i][0] = a; states[i][1] = b; states[i][2] = c; states[i][3] = d; states[i][4] = e; - -#ifdef BUILDNOCOLLDETECTSHA1COMPRESSION -void sha1_compression(uint32_t ihv[5], const uint32_t m[16]) -{ - uint32_t W[80]; - uint32_t a,b,c,d,e; - unsigned i; - - memcpy(W, m, 16 * 4); - for (i = 16; i < 80; ++i) - W[i] = sha1_mix(W, i); - - a = ihv[0]; b = ihv[1]; c = ihv[2]; d = ihv[3]; e = ihv[4]; - - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); - - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); - - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); - - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); - - ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; -} -#endif /*BUILDNOCOLLDETECTSHA1COMPRESSION*/ - - -static void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]) -{ - uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; - - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); - - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); - - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); - - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); - - ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; -} - - - -void sha1_compression_states(uint32_t ihv[5], const uint32_t m[16], uint32_t W[80], uint32_t states[80][5]) -{ - uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; - uint32_t temp; - -#ifdef DOSTORESTATE00 - SHA1_STORE_STATE(0) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 0, temp); - -#ifdef DOSTORESTATE01 - SHA1_STORE_STATE(1) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 1, temp); - -#ifdef DOSTORESTATE02 - SHA1_STORE_STATE(2) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 2, temp); - -#ifdef DOSTORESTATE03 - SHA1_STORE_STATE(3) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 3, temp); - -#ifdef DOSTORESTATE04 - SHA1_STORE_STATE(4) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 4, temp); - -#ifdef DOSTORESTATE05 - SHA1_STORE_STATE(5) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 5, temp); - -#ifdef DOSTORESTATE06 - SHA1_STORE_STATE(6) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 6, temp); - -#ifdef DOSTORESTATE07 - SHA1_STORE_STATE(7) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 7, temp); - -#ifdef DOSTORESTATE08 - SHA1_STORE_STATE(8) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 8, temp); - -#ifdef DOSTORESTATE09 - SHA1_STORE_STATE(9) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 9, temp); - -#ifdef DOSTORESTATE10 - SHA1_STORE_STATE(10) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 10, temp); - -#ifdef DOSTORESTATE11 - SHA1_STORE_STATE(11) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 11, temp); - -#ifdef DOSTORESTATE12 - SHA1_STORE_STATE(12) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 12, temp); - -#ifdef DOSTORESTATE13 - SHA1_STORE_STATE(13) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 13, temp); - -#ifdef DOSTORESTATE14 - SHA1_STORE_STATE(14) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 14, temp); - -#ifdef DOSTORESTATE15 - SHA1_STORE_STATE(15) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 15, temp); - -#ifdef DOSTORESTATE16 - SHA1_STORE_STATE(16) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(e, a, b, c, d, W, 16, temp); - -#ifdef DOSTORESTATE17 - SHA1_STORE_STATE(17) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(d, e, a, b, c, W, 17, temp); - -#ifdef DOSTORESTATE18 - SHA1_STORE_STATE(18) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(c, d, e, a, b, W, 18, temp); - -#ifdef DOSTORESTATE19 - SHA1_STORE_STATE(19) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(b, c, d, e, a, W, 19, temp); - - - -#ifdef DOSTORESTATE20 - SHA1_STORE_STATE(20) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 20, temp); - -#ifdef DOSTORESTATE21 - SHA1_STORE_STATE(21) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 21, temp); - -#ifdef DOSTORESTATE22 - SHA1_STORE_STATE(22) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 22, temp); - -#ifdef DOSTORESTATE23 - SHA1_STORE_STATE(23) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 23, temp); - -#ifdef DOSTORESTATE24 - SHA1_STORE_STATE(24) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 24, temp); - -#ifdef DOSTORESTATE25 - SHA1_STORE_STATE(25) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 25, temp); - -#ifdef DOSTORESTATE26 - SHA1_STORE_STATE(26) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 26, temp); - -#ifdef DOSTORESTATE27 - SHA1_STORE_STATE(27) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 27, temp); - -#ifdef DOSTORESTATE28 - SHA1_STORE_STATE(28) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 28, temp); - -#ifdef DOSTORESTATE29 - SHA1_STORE_STATE(29) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 29, temp); - -#ifdef DOSTORESTATE30 - SHA1_STORE_STATE(30) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 30, temp); - -#ifdef DOSTORESTATE31 - SHA1_STORE_STATE(31) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 31, temp); - -#ifdef DOSTORESTATE32 - SHA1_STORE_STATE(32) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 32, temp); - -#ifdef DOSTORESTATE33 - SHA1_STORE_STATE(33) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 33, temp); - -#ifdef DOSTORESTATE34 - SHA1_STORE_STATE(34) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 34, temp); - -#ifdef DOSTORESTATE35 - SHA1_STORE_STATE(35) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 35, temp); - -#ifdef DOSTORESTATE36 - SHA1_STORE_STATE(36) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 36, temp); - -#ifdef DOSTORESTATE37 - SHA1_STORE_STATE(37) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 37, temp); - -#ifdef DOSTORESTATE38 - SHA1_STORE_STATE(38) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 38, temp); - -#ifdef DOSTORESTATE39 - SHA1_STORE_STATE(39) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 39, temp); - - - -#ifdef DOSTORESTATE40 - SHA1_STORE_STATE(40) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 40, temp); - -#ifdef DOSTORESTATE41 - SHA1_STORE_STATE(41) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 41, temp); - -#ifdef DOSTORESTATE42 - SHA1_STORE_STATE(42) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 42, temp); - -#ifdef DOSTORESTATE43 - SHA1_STORE_STATE(43) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 43, temp); - -#ifdef DOSTORESTATE44 - SHA1_STORE_STATE(44) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 44, temp); - -#ifdef DOSTORESTATE45 - SHA1_STORE_STATE(45) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 45, temp); - -#ifdef DOSTORESTATE46 - SHA1_STORE_STATE(46) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 46, temp); - -#ifdef DOSTORESTATE47 - SHA1_STORE_STATE(47) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 47, temp); - -#ifdef DOSTORESTATE48 - SHA1_STORE_STATE(48) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 48, temp); - -#ifdef DOSTORESTATE49 - SHA1_STORE_STATE(49) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 49, temp); - -#ifdef DOSTORESTATE50 - SHA1_STORE_STATE(50) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 50, temp); - -#ifdef DOSTORESTATE51 - SHA1_STORE_STATE(51) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 51, temp); - -#ifdef DOSTORESTATE52 - SHA1_STORE_STATE(52) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 52, temp); - -#ifdef DOSTORESTATE53 - SHA1_STORE_STATE(53) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 53, temp); - -#ifdef DOSTORESTATE54 - SHA1_STORE_STATE(54) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 54, temp); - -#ifdef DOSTORESTATE55 - SHA1_STORE_STATE(55) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 55, temp); - -#ifdef DOSTORESTATE56 - SHA1_STORE_STATE(56) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 56, temp); - -#ifdef DOSTORESTATE57 - SHA1_STORE_STATE(57) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 57, temp); - -#ifdef DOSTORESTATE58 - SHA1_STORE_STATE(58) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 58, temp); - -#ifdef DOSTORESTATE59 - SHA1_STORE_STATE(59) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 59, temp); - - - - -#ifdef DOSTORESTATE60 - SHA1_STORE_STATE(60) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 60, temp); - -#ifdef DOSTORESTATE61 - SHA1_STORE_STATE(61) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 61, temp); - -#ifdef DOSTORESTATE62 - SHA1_STORE_STATE(62) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 62, temp); - -#ifdef DOSTORESTATE63 - SHA1_STORE_STATE(63) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 63, temp); - -#ifdef DOSTORESTATE64 - SHA1_STORE_STATE(64) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 64, temp); - -#ifdef DOSTORESTATE65 - SHA1_STORE_STATE(65) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 65, temp); - -#ifdef DOSTORESTATE66 - SHA1_STORE_STATE(66) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 66, temp); - -#ifdef DOSTORESTATE67 - SHA1_STORE_STATE(67) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 67, temp); - -#ifdef DOSTORESTATE68 - SHA1_STORE_STATE(68) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 68, temp); - -#ifdef DOSTORESTATE69 - SHA1_STORE_STATE(69) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 69, temp); - -#ifdef DOSTORESTATE70 - SHA1_STORE_STATE(70) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 70, temp); - -#ifdef DOSTORESTATE71 - SHA1_STORE_STATE(71) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 71, temp); - -#ifdef DOSTORESTATE72 - SHA1_STORE_STATE(72) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 72, temp); - -#ifdef DOSTORESTATE73 - SHA1_STORE_STATE(73) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 73, temp); - -#ifdef DOSTORESTATE74 - SHA1_STORE_STATE(74) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 74, temp); - -#ifdef DOSTORESTATE75 - SHA1_STORE_STATE(75) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 75, temp); - -#ifdef DOSTORESTATE76 - SHA1_STORE_STATE(76) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 76, temp); - -#ifdef DOSTORESTATE77 - SHA1_STORE_STATE(77) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 77, temp); - -#ifdef DOSTORESTATE78 - SHA1_STORE_STATE(78) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 78, temp); - -#ifdef DOSTORESTATE79 - SHA1_STORE_STATE(79) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 79, temp); - - - - ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; -} - - - - -#define SHA1_RECOMPRESS(t) \ -static void sha1recompress_fast_ ## t (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) \ -{ \ - uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; \ - if (t > 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 79); \ - if (t > 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 78); \ - if (t > 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 77); \ - if (t > 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 76); \ - if (t > 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 75); \ - if (t > 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 74); \ - if (t > 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 73); \ - if (t > 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 72); \ - if (t > 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 71); \ - if (t > 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 70); \ - if (t > 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 69); \ - if (t > 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 68); \ - if (t > 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 67); \ - if (t > 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 66); \ - if (t > 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 65); \ - if (t > 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 64); \ - if (t > 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 63); \ - if (t > 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 62); \ - if (t > 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 61); \ - if (t > 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 60); \ - if (t > 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 59); \ - if (t > 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 58); \ - if (t > 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 57); \ - if (t > 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 56); \ - if (t > 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 55); \ - if (t > 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 54); \ - if (t > 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 53); \ - if (t > 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 52); \ - if (t > 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 51); \ - if (t > 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 50); \ - if (t > 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 49); \ - if (t > 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 48); \ - if (t > 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 47); \ - if (t > 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 46); \ - if (t > 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 45); \ - if (t > 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 44); \ - if (t > 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 43); \ - if (t > 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 42); \ - if (t > 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 41); \ - if (t > 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 40); \ - if (t > 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 39); \ - if (t > 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 38); \ - if (t > 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 37); \ - if (t > 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 36); \ - if (t > 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 35); \ - if (t > 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 34); \ - if (t > 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 33); \ - if (t > 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 32); \ - if (t > 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 31); \ - if (t > 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 30); \ - if (t > 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 29); \ - if (t > 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 28); \ - if (t > 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 27); \ - if (t > 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 26); \ - if (t > 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 25); \ - if (t > 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 24); \ - if (t > 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 23); \ - if (t > 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 22); \ - if (t > 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 21); \ - if (t > 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 20); \ - if (t > 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 19); \ - if (t > 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 18); \ - if (t > 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 17); \ - if (t > 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 16); \ - if (t > 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 15); \ - if (t > 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 14); \ - if (t > 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 13); \ - if (t > 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 12); \ - if (t > 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 11); \ - if (t > 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 10); \ - if (t > 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 9); \ - if (t > 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 8); \ - if (t > 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 7); \ - if (t > 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 6); \ - if (t > 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 5); \ - if (t > 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 4); \ - if (t > 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 3); \ - if (t > 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 2); \ - if (t > 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 1); \ - if (t > 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 0); \ - ihvin[0] = a; ihvin[1] = b; ihvin[2] = c; ihvin[3] = d; ihvin[4] = e; \ - a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; \ - if (t <= 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 0); \ - if (t <= 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 1); \ - if (t <= 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 2); \ - if (t <= 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 3); \ - if (t <= 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 4); \ - if (t <= 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 5); \ - if (t <= 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 6); \ - if (t <= 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 7); \ - if (t <= 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 8); \ - if (t <= 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 9); \ - if (t <= 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 10); \ - if (t <= 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 11); \ - if (t <= 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 12); \ - if (t <= 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 13); \ - if (t <= 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 14); \ - if (t <= 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 15); \ - if (t <= 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 16); \ - if (t <= 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 17); \ - if (t <= 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 18); \ - if (t <= 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 19); \ - if (t <= 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 20); \ - if (t <= 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 21); \ - if (t <= 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 22); \ - if (t <= 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 23); \ - if (t <= 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 24); \ - if (t <= 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 25); \ - if (t <= 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 26); \ - if (t <= 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 27); \ - if (t <= 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 28); \ - if (t <= 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 29); \ - if (t <= 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 30); \ - if (t <= 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 31); \ - if (t <= 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 32); \ - if (t <= 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 33); \ - if (t <= 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 34); \ - if (t <= 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 35); \ - if (t <= 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 36); \ - if (t <= 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 37); \ - if (t <= 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 38); \ - if (t <= 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 39); \ - if (t <= 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 40); \ - if (t <= 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 41); \ - if (t <= 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 42); \ - if (t <= 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 43); \ - if (t <= 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 44); \ - if (t <= 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 45); \ - if (t <= 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 46); \ - if (t <= 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 47); \ - if (t <= 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 48); \ - if (t <= 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 49); \ - if (t <= 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 50); \ - if (t <= 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 51); \ - if (t <= 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 52); \ - if (t <= 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 53); \ - if (t <= 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 54); \ - if (t <= 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 55); \ - if (t <= 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 56); \ - if (t <= 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 57); \ - if (t <= 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 58); \ - if (t <= 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 59); \ - if (t <= 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 60); \ - if (t <= 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 61); \ - if (t <= 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 62); \ - if (t <= 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 63); \ - if (t <= 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 64); \ - if (t <= 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 65); \ - if (t <= 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 66); \ - if (t <= 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 67); \ - if (t <= 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 68); \ - if (t <= 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 69); \ - if (t <= 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 70); \ - if (t <= 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 71); \ - if (t <= 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 72); \ - if (t <= 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 73); \ - if (t <= 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 74); \ - if (t <= 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 75); \ - if (t <= 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 76); \ - if (t <= 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 77); \ - if (t <= 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 78); \ - if (t <= 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 79); \ - ihvout[0] = ihvin[0] + a; ihvout[1] = ihvin[1] + b; ihvout[2] = ihvin[2] + c; ihvout[3] = ihvin[3] + d; ihvout[4] = ihvin[4] + e; \ -} - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4127) /* Compiler complains about the checks in the above macro being constant. */ -#endif - -#ifdef DOSTORESTATE0 -SHA1_RECOMPRESS(0) -#endif - -#ifdef DOSTORESTATE1 -SHA1_RECOMPRESS(1) -#endif - -#ifdef DOSTORESTATE2 -SHA1_RECOMPRESS(2) -#endif - -#ifdef DOSTORESTATE3 -SHA1_RECOMPRESS(3) -#endif - -#ifdef DOSTORESTATE4 -SHA1_RECOMPRESS(4) -#endif - -#ifdef DOSTORESTATE5 -SHA1_RECOMPRESS(5) -#endif - -#ifdef DOSTORESTATE6 -SHA1_RECOMPRESS(6) -#endif - -#ifdef DOSTORESTATE7 -SHA1_RECOMPRESS(7) -#endif - -#ifdef DOSTORESTATE8 -SHA1_RECOMPRESS(8) -#endif - -#ifdef DOSTORESTATE9 -SHA1_RECOMPRESS(9) -#endif - -#ifdef DOSTORESTATE10 -SHA1_RECOMPRESS(10) -#endif - -#ifdef DOSTORESTATE11 -SHA1_RECOMPRESS(11) -#endif - -#ifdef DOSTORESTATE12 -SHA1_RECOMPRESS(12) -#endif - -#ifdef DOSTORESTATE13 -SHA1_RECOMPRESS(13) -#endif - -#ifdef DOSTORESTATE14 -SHA1_RECOMPRESS(14) -#endif - -#ifdef DOSTORESTATE15 -SHA1_RECOMPRESS(15) -#endif - -#ifdef DOSTORESTATE16 -SHA1_RECOMPRESS(16) -#endif - -#ifdef DOSTORESTATE17 -SHA1_RECOMPRESS(17) -#endif - -#ifdef DOSTORESTATE18 -SHA1_RECOMPRESS(18) -#endif - -#ifdef DOSTORESTATE19 -SHA1_RECOMPRESS(19) -#endif - -#ifdef DOSTORESTATE20 -SHA1_RECOMPRESS(20) -#endif - -#ifdef DOSTORESTATE21 -SHA1_RECOMPRESS(21) -#endif - -#ifdef DOSTORESTATE22 -SHA1_RECOMPRESS(22) -#endif - -#ifdef DOSTORESTATE23 -SHA1_RECOMPRESS(23) -#endif - -#ifdef DOSTORESTATE24 -SHA1_RECOMPRESS(24) -#endif - -#ifdef DOSTORESTATE25 -SHA1_RECOMPRESS(25) -#endif - -#ifdef DOSTORESTATE26 -SHA1_RECOMPRESS(26) -#endif - -#ifdef DOSTORESTATE27 -SHA1_RECOMPRESS(27) -#endif - -#ifdef DOSTORESTATE28 -SHA1_RECOMPRESS(28) -#endif - -#ifdef DOSTORESTATE29 -SHA1_RECOMPRESS(29) -#endif - -#ifdef DOSTORESTATE30 -SHA1_RECOMPRESS(30) -#endif - -#ifdef DOSTORESTATE31 -SHA1_RECOMPRESS(31) -#endif - -#ifdef DOSTORESTATE32 -SHA1_RECOMPRESS(32) -#endif - -#ifdef DOSTORESTATE33 -SHA1_RECOMPRESS(33) -#endif - -#ifdef DOSTORESTATE34 -SHA1_RECOMPRESS(34) -#endif - -#ifdef DOSTORESTATE35 -SHA1_RECOMPRESS(35) -#endif - -#ifdef DOSTORESTATE36 -SHA1_RECOMPRESS(36) -#endif - -#ifdef DOSTORESTATE37 -SHA1_RECOMPRESS(37) -#endif - -#ifdef DOSTORESTATE38 -SHA1_RECOMPRESS(38) -#endif - -#ifdef DOSTORESTATE39 -SHA1_RECOMPRESS(39) -#endif - -#ifdef DOSTORESTATE40 -SHA1_RECOMPRESS(40) -#endif - -#ifdef DOSTORESTATE41 -SHA1_RECOMPRESS(41) -#endif - -#ifdef DOSTORESTATE42 -SHA1_RECOMPRESS(42) -#endif - -#ifdef DOSTORESTATE43 -SHA1_RECOMPRESS(43) -#endif - -#ifdef DOSTORESTATE44 -SHA1_RECOMPRESS(44) -#endif - -#ifdef DOSTORESTATE45 -SHA1_RECOMPRESS(45) -#endif - -#ifdef DOSTORESTATE46 -SHA1_RECOMPRESS(46) -#endif - -#ifdef DOSTORESTATE47 -SHA1_RECOMPRESS(47) -#endif - -#ifdef DOSTORESTATE48 -SHA1_RECOMPRESS(48) -#endif - -#ifdef DOSTORESTATE49 -SHA1_RECOMPRESS(49) -#endif - -#ifdef DOSTORESTATE50 -SHA1_RECOMPRESS(50) -#endif - -#ifdef DOSTORESTATE51 -SHA1_RECOMPRESS(51) -#endif - -#ifdef DOSTORESTATE52 -SHA1_RECOMPRESS(52) -#endif - -#ifdef DOSTORESTATE53 -SHA1_RECOMPRESS(53) -#endif - -#ifdef DOSTORESTATE54 -SHA1_RECOMPRESS(54) -#endif - -#ifdef DOSTORESTATE55 -SHA1_RECOMPRESS(55) -#endif - -#ifdef DOSTORESTATE56 -SHA1_RECOMPRESS(56) -#endif - -#ifdef DOSTORESTATE57 -SHA1_RECOMPRESS(57) -#endif - -#ifdef DOSTORESTATE58 -SHA1_RECOMPRESS(58) -#endif - -#ifdef DOSTORESTATE59 -SHA1_RECOMPRESS(59) -#endif - -#ifdef DOSTORESTATE60 -SHA1_RECOMPRESS(60) -#endif - -#ifdef DOSTORESTATE61 -SHA1_RECOMPRESS(61) -#endif - -#ifdef DOSTORESTATE62 -SHA1_RECOMPRESS(62) -#endif - -#ifdef DOSTORESTATE63 -SHA1_RECOMPRESS(63) -#endif - -#ifdef DOSTORESTATE64 -SHA1_RECOMPRESS(64) -#endif - -#ifdef DOSTORESTATE65 -SHA1_RECOMPRESS(65) -#endif - -#ifdef DOSTORESTATE66 -SHA1_RECOMPRESS(66) -#endif - -#ifdef DOSTORESTATE67 -SHA1_RECOMPRESS(67) -#endif - -#ifdef DOSTORESTATE68 -SHA1_RECOMPRESS(68) -#endif - -#ifdef DOSTORESTATE69 -SHA1_RECOMPRESS(69) -#endif - -#ifdef DOSTORESTATE70 -SHA1_RECOMPRESS(70) -#endif - -#ifdef DOSTORESTATE71 -SHA1_RECOMPRESS(71) -#endif - -#ifdef DOSTORESTATE72 -SHA1_RECOMPRESS(72) -#endif - -#ifdef DOSTORESTATE73 -SHA1_RECOMPRESS(73) -#endif - -#ifdef DOSTORESTATE74 -SHA1_RECOMPRESS(74) -#endif - -#ifdef DOSTORESTATE75 -SHA1_RECOMPRESS(75) -#endif - -#ifdef DOSTORESTATE76 -SHA1_RECOMPRESS(76) -#endif - -#ifdef DOSTORESTATE77 -SHA1_RECOMPRESS(77) -#endif - -#ifdef DOSTORESTATE78 -SHA1_RECOMPRESS(78) -#endif - -#ifdef DOSTORESTATE79 -SHA1_RECOMPRESS(79) -#endif - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -static void sha1_recompression_step(uint32_t step, uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) -{ - switch (step) - { -#ifdef DOSTORESTATE0 - case 0: - sha1recompress_fast_0(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE1 - case 1: - sha1recompress_fast_1(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE2 - case 2: - sha1recompress_fast_2(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE3 - case 3: - sha1recompress_fast_3(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE4 - case 4: - sha1recompress_fast_4(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE5 - case 5: - sha1recompress_fast_5(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE6 - case 6: - sha1recompress_fast_6(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE7 - case 7: - sha1recompress_fast_7(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE8 - case 8: - sha1recompress_fast_8(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE9 - case 9: - sha1recompress_fast_9(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE10 - case 10: - sha1recompress_fast_10(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE11 - case 11: - sha1recompress_fast_11(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE12 - case 12: - sha1recompress_fast_12(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE13 - case 13: - sha1recompress_fast_13(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE14 - case 14: - sha1recompress_fast_14(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE15 - case 15: - sha1recompress_fast_15(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE16 - case 16: - sha1recompress_fast_16(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE17 - case 17: - sha1recompress_fast_17(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE18 - case 18: - sha1recompress_fast_18(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE19 - case 19: - sha1recompress_fast_19(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE20 - case 20: - sha1recompress_fast_20(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE21 - case 21: - sha1recompress_fast_21(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE22 - case 22: - sha1recompress_fast_22(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE23 - case 23: - sha1recompress_fast_23(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE24 - case 24: - sha1recompress_fast_24(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE25 - case 25: - sha1recompress_fast_25(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE26 - case 26: - sha1recompress_fast_26(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE27 - case 27: - sha1recompress_fast_27(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE28 - case 28: - sha1recompress_fast_28(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE29 - case 29: - sha1recompress_fast_29(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE30 - case 30: - sha1recompress_fast_30(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE31 - case 31: - sha1recompress_fast_31(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE32 - case 32: - sha1recompress_fast_32(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE33 - case 33: - sha1recompress_fast_33(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE34 - case 34: - sha1recompress_fast_34(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE35 - case 35: - sha1recompress_fast_35(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE36 - case 36: - sha1recompress_fast_36(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE37 - case 37: - sha1recompress_fast_37(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE38 - case 38: - sha1recompress_fast_38(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE39 - case 39: - sha1recompress_fast_39(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE40 - case 40: - sha1recompress_fast_40(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE41 - case 41: - sha1recompress_fast_41(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE42 - case 42: - sha1recompress_fast_42(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE43 - case 43: - sha1recompress_fast_43(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE44 - case 44: - sha1recompress_fast_44(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE45 - case 45: - sha1recompress_fast_45(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE46 - case 46: - sha1recompress_fast_46(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE47 - case 47: - sha1recompress_fast_47(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE48 - case 48: - sha1recompress_fast_48(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE49 - case 49: - sha1recompress_fast_49(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE50 - case 50: - sha1recompress_fast_50(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE51 - case 51: - sha1recompress_fast_51(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE52 - case 52: - sha1recompress_fast_52(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE53 - case 53: - sha1recompress_fast_53(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE54 - case 54: - sha1recompress_fast_54(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE55 - case 55: - sha1recompress_fast_55(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE56 - case 56: - sha1recompress_fast_56(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE57 - case 57: - sha1recompress_fast_57(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE58 - case 58: - sha1recompress_fast_58(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE59 - case 59: - sha1recompress_fast_59(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE60 - case 60: - sha1recompress_fast_60(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE61 - case 61: - sha1recompress_fast_61(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE62 - case 62: - sha1recompress_fast_62(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE63 - case 63: - sha1recompress_fast_63(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE64 - case 64: - sha1recompress_fast_64(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE65 - case 65: - sha1recompress_fast_65(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE66 - case 66: - sha1recompress_fast_66(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE67 - case 67: - sha1recompress_fast_67(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE68 - case 68: - sha1recompress_fast_68(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE69 - case 69: - sha1recompress_fast_69(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE70 - case 70: - sha1recompress_fast_70(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE71 - case 71: - sha1recompress_fast_71(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE72 - case 72: - sha1recompress_fast_72(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE73 - case 73: - sha1recompress_fast_73(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE74 - case 74: - sha1recompress_fast_74(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE75 - case 75: - sha1recompress_fast_75(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE76 - case 76: - sha1recompress_fast_76(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE77 - case 77: - sha1recompress_fast_77(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE78 - case 78: - sha1recompress_fast_78(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE79 - case 79: - sha1recompress_fast_79(ihvin, ihvout, me2, state); - break; -#endif - default: - abort(); - } - -} - - - -static void sha1_process(SHA1_CTX *ctx, const uint32_t block[16]) -{ - unsigned i, j; - uint32_t ubc_dv_mask[DVMASKSIZE] = { 0xFFFFFFFF }; - uint32_t ihvtmp[5]; - - ctx->ihv1[0] = ctx->ihv[0]; - ctx->ihv1[1] = ctx->ihv[1]; - ctx->ihv1[2] = ctx->ihv[2]; - ctx->ihv1[3] = ctx->ihv[3]; - ctx->ihv1[4] = ctx->ihv[4]; - - sha1_compression_states(ctx->ihv, block, ctx->m1, ctx->states); - - if (ctx->detect_coll) - { - if (ctx->ubc_check) - { - ubc_check(ctx->m1, ubc_dv_mask); - } - - if (ubc_dv_mask[0] != 0) - { - for (i = 0; sha1_dvs[i].dvType != 0; ++i) - { - if (ubc_dv_mask[0] & ((uint32_t)(1) << sha1_dvs[i].maskb)) - { - for (j = 0; j < 80; ++j) - ctx->m2[j] = ctx->m1[j] ^ sha1_dvs[i].dm[j]; - - sha1_recompression_step(sha1_dvs[i].testt, ctx->ihv2, ihvtmp, ctx->m2, ctx->states[sha1_dvs[i].testt]); - - /* to verify SHA-1 collision detection code with collisions for reduced-step SHA-1 */ - if ((0 == ((ihvtmp[0] ^ ctx->ihv[0]) | (ihvtmp[1] ^ ctx->ihv[1]) | (ihvtmp[2] ^ ctx->ihv[2]) | (ihvtmp[3] ^ ctx->ihv[3]) | (ihvtmp[4] ^ ctx->ihv[4]))) - || (ctx->reduced_round_coll && 0==((ctx->ihv1[0] ^ ctx->ihv2[0]) | (ctx->ihv1[1] ^ ctx->ihv2[1]) | (ctx->ihv1[2] ^ ctx->ihv2[2]) | (ctx->ihv1[3] ^ ctx->ihv2[3]) | (ctx->ihv1[4] ^ ctx->ihv2[4])))) - { - ctx->found_collision = 1; - - if (ctx->safe_hash) - { - sha1_compression_W(ctx->ihv, ctx->m1); - sha1_compression_W(ctx->ihv, ctx->m1); - } - - break; - } - } - } - } - } -} - -void SHA1DCInit(SHA1_CTX *ctx) -{ - ctx->total = 0; - ctx->ihv[0] = 0x67452301; - ctx->ihv[1] = 0xEFCDAB89; - ctx->ihv[2] = 0x98BADCFE; - ctx->ihv[3] = 0x10325476; - ctx->ihv[4] = 0xC3D2E1F0; - ctx->found_collision = 0; - ctx->safe_hash = SHA1DC_INIT_SAFE_HASH_DEFAULT; - ctx->ubc_check = 1; - ctx->detect_coll = 1; - ctx->reduced_round_coll = 0; - ctx->callback = NULL; -} - -void SHA1DCSetSafeHash(SHA1_CTX *ctx, int safehash) -{ - if (safehash) - ctx->safe_hash = 1; - else - ctx->safe_hash = 0; -} - - -void SHA1DCSetUseUBC(SHA1_CTX *ctx, int ubc_check) -{ - if (ubc_check) - ctx->ubc_check = 1; - else - ctx->ubc_check = 0; -} - -void SHA1DCSetUseDetectColl(SHA1_CTX *ctx, int detect_coll) -{ - if (detect_coll) - ctx->detect_coll = 1; - else - ctx->detect_coll = 0; -} - -void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX *ctx, int reduced_round_coll) -{ - if (reduced_round_coll) - ctx->reduced_round_coll = 1; - else - ctx->reduced_round_coll = 0; -} - -void SHA1DCSetCallback(SHA1_CTX *ctx, collision_block_callback callback) -{ - ctx->callback = callback; -} - -void SHA1DCUpdate(SHA1_CTX *ctx, const char *buf, size_t len) -{ - unsigned left, fill; - - if (len == 0) - return; - - left = ctx->total & 63; - fill = 64 - left; - - if (left && len >= fill) - { - ctx->total += fill; - memcpy(ctx->buffer + left, buf, fill); - sha1_process(ctx, (uint32_t*)(ctx->buffer)); - buf += fill; - len -= fill; - left = 0; - } - while (len >= 64) - { - ctx->total += 64; - -#if defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) - sha1_process(ctx, (uint32_t*)(buf)); -#else - memcpy(ctx->buffer, buf, 64); - sha1_process(ctx, (uint32_t*)(ctx->buffer)); -#endif /* defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) */ - buf += 64; - len -= 64; - } - if (len > 0) - { - ctx->total += len; - memcpy(ctx->buffer + left, buf, len); - } -} - -static const unsigned char sha1_padding[64] = -{ - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -int SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx) -{ - uint32_t last = ctx->total & 63; - uint32_t padn = (last < 56) ? (56 - last) : (120 - last); - uint64_t total; - SHA1DCUpdate(ctx, (const char*)(sha1_padding), padn); - - total = ctx->total - padn; - total <<= 3; - ctx->buffer[56] = (unsigned char)(total >> 56); - ctx->buffer[57] = (unsigned char)(total >> 48); - ctx->buffer[58] = (unsigned char)(total >> 40); - ctx->buffer[59] = (unsigned char)(total >> 32); - ctx->buffer[60] = (unsigned char)(total >> 24); - ctx->buffer[61] = (unsigned char)(total >> 16); - ctx->buffer[62] = (unsigned char)(total >> 8); - ctx->buffer[63] = (unsigned char)(total); - sha1_process(ctx, (uint32_t*)(ctx->buffer)); - output[0] = (unsigned char)(ctx->ihv[0] >> 24); - output[1] = (unsigned char)(ctx->ihv[0] >> 16); - output[2] = (unsigned char)(ctx->ihv[0] >> 8); - output[3] = (unsigned char)(ctx->ihv[0]); - output[4] = (unsigned char)(ctx->ihv[1] >> 24); - output[5] = (unsigned char)(ctx->ihv[1] >> 16); - output[6] = (unsigned char)(ctx->ihv[1] >> 8); - output[7] = (unsigned char)(ctx->ihv[1]); - output[8] = (unsigned char)(ctx->ihv[2] >> 24); - output[9] = (unsigned char)(ctx->ihv[2] >> 16); - output[10] = (unsigned char)(ctx->ihv[2] >> 8); - output[11] = (unsigned char)(ctx->ihv[2]); - output[12] = (unsigned char)(ctx->ihv[3] >> 24); - output[13] = (unsigned char)(ctx->ihv[3] >> 16); - output[14] = (unsigned char)(ctx->ihv[3] >> 8); - output[15] = (unsigned char)(ctx->ihv[3]); - output[16] = (unsigned char)(ctx->ihv[4] >> 24); - output[17] = (unsigned char)(ctx->ihv[4] >> 16); - output[18] = (unsigned char)(ctx->ihv[4] >> 8); - output[19] = (unsigned char)(ctx->ihv[4]); - return ctx->found_collision; -} - -#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C -#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C -#endif diff --git a/src/hash/sha1/sha1dc/sha1.h b/src/hash/sha1/sha1dc/sha1.h deleted file mode 100644 index 1e4e94be5..000000000 --- a/src/hash/sha1/sha1dc/sha1.h +++ /dev/null @@ -1,110 +0,0 @@ -/*** -* Copyright 2017 Marc Stevens , Dan Shumow -* Distributed under the MIT Software License. -* See accompanying file LICENSE.txt or copy at -* https://opensource.org/licenses/MIT -***/ - -#ifndef SHA1DC_SHA1_H -#define SHA1DC_SHA1_H - -#if defined(__cplusplus) -extern "C" { -#endif - -#ifndef SHA1DC_NO_STANDARD_INCLUDES -#include -#endif - -/* sha-1 compression function that takes an already expanded message, and additionally store intermediate states */ -/* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is defined in ubc_check.h */ -void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]); - -/* -// Function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]). -// Where 0 <= T < 80 -// me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference.) -// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block. -// The function will return: -// ihvin: The reconstructed input chaining value. -// ihvout: The reconstructed output chaining value. -*/ -typedef void(*sha1_recompression_type)(uint32_t*, uint32_t*, const uint32_t*, const uint32_t*); - -/* A callback function type that can be set to be called when a collision block has been found: */ -/* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */ -typedef void(*collision_block_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); - -/* The SHA-1 context. */ -typedef struct { - uint64_t total; - uint32_t ihv[5]; - unsigned char buffer[64]; - int found_collision; - int safe_hash; - int detect_coll; - int ubc_check; - int reduced_round_coll; - collision_block_callback callback; - - uint32_t ihv1[5]; - uint32_t ihv2[5]; - uint32_t m1[80]; - uint32_t m2[80]; - uint32_t states[80][5]; -} SHA1_CTX; - -/* Initialize SHA-1 context. */ -void SHA1DCInit(SHA1_CTX*); - -/* - Function to enable safe SHA-1 hashing: - Collision attacks are thwarted by hashing a detected near-collision block 3 times. - Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: - The best collision attacks against SHA-1 have complexity about 2^60, - thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be 2^180. - An attacker would be better off using a generic birthday search of complexity 2^80. - - Enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected, - but it will result in a different SHA-1 hash for messages where a collision attack was detected. - This will automatically invalidate SHA-1 based digital signature forgeries. - Enabled by default. -*/ -void SHA1DCSetSafeHash(SHA1_CTX*, int); - -/* - Function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up). - Enabled by default - */ -void SHA1DCSetUseUBC(SHA1_CTX*, int); - -/* - Function to disable or enable the use of Collision Detection. - Enabled by default. - */ -void SHA1DCSetUseDetectColl(SHA1_CTX*, int); - -/* function to disable or enable the detection of reduced-round SHA-1 collisions */ -/* disabled by default */ -void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX*, int); - -/* function to set a callback function, pass NULL to disable */ -/* by default no callback set */ -void SHA1DCSetCallback(SHA1_CTX*, collision_block_callback); - -/* update SHA-1 context with buffer contents */ -void SHA1DCUpdate(SHA1_CTX*, const char*, size_t); - -/* obtain SHA-1 hash from SHA-1 context */ -/* returns: 0 = no collision detected, otherwise = collision found => warn user for active attack */ -int SHA1DCFinal(unsigned char[20], SHA1_CTX*); - -#if defined(__cplusplus) -} -#endif - -#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H -#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H -#endif - -#endif diff --git a/src/hash/sha1/sha1dc/ubc_check.c b/src/hash/sha1/sha1dc/ubc_check.c deleted file mode 100644 index b3beff2af..000000000 --- a/src/hash/sha1/sha1dc/ubc_check.c +++ /dev/null @@ -1,372 +0,0 @@ -/*** -* Copyright 2017 Marc Stevens , Dan Shumow -* Distributed under the MIT Software License. -* See accompanying file LICENSE.txt or copy at -* https://opensource.org/licenses/MIT -***/ - -/* -// this file was generated by the 'parse_bitrel' program in the tools section -// using the data files from directory 'tools/data/3565' -// -// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check -// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) -// dm[80] is the expanded message block XOR-difference defined by the DV -// testt is the step to do the recompression from for collision detection -// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check -// -// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs -// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met -// thus one needs to do the recompression check for each DV that has its bit set -// -// ubc_check is programmatically generated and the unavoidable bitconditions have been hardcoded -// a directly verifiable version named ubc_check_verify can be found in ubc_check_verify.c -// ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in the tools section -*/ - -#ifndef SHA1DC_NO_STANDARD_INCLUDES -#include -#endif -#ifdef SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C -#include SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C -#endif -#include "ubc_check.h" - -static const uint32_t DV_I_43_0_bit = (uint32_t)(1) << 0; -static const uint32_t DV_I_44_0_bit = (uint32_t)(1) << 1; -static const uint32_t DV_I_45_0_bit = (uint32_t)(1) << 2; -static const uint32_t DV_I_46_0_bit = (uint32_t)(1) << 3; -static const uint32_t DV_I_46_2_bit = (uint32_t)(1) << 4; -static const uint32_t DV_I_47_0_bit = (uint32_t)(1) << 5; -static const uint32_t DV_I_47_2_bit = (uint32_t)(1) << 6; -static const uint32_t DV_I_48_0_bit = (uint32_t)(1) << 7; -static const uint32_t DV_I_48_2_bit = (uint32_t)(1) << 8; -static const uint32_t DV_I_49_0_bit = (uint32_t)(1) << 9; -static const uint32_t DV_I_49_2_bit = (uint32_t)(1) << 10; -static const uint32_t DV_I_50_0_bit = (uint32_t)(1) << 11; -static const uint32_t DV_I_50_2_bit = (uint32_t)(1) << 12; -static const uint32_t DV_I_51_0_bit = (uint32_t)(1) << 13; -static const uint32_t DV_I_51_2_bit = (uint32_t)(1) << 14; -static const uint32_t DV_I_52_0_bit = (uint32_t)(1) << 15; -static const uint32_t DV_II_45_0_bit = (uint32_t)(1) << 16; -static const uint32_t DV_II_46_0_bit = (uint32_t)(1) << 17; -static const uint32_t DV_II_46_2_bit = (uint32_t)(1) << 18; -static const uint32_t DV_II_47_0_bit = (uint32_t)(1) << 19; -static const uint32_t DV_II_48_0_bit = (uint32_t)(1) << 20; -static const uint32_t DV_II_49_0_bit = (uint32_t)(1) << 21; -static const uint32_t DV_II_49_2_bit = (uint32_t)(1) << 22; -static const uint32_t DV_II_50_0_bit = (uint32_t)(1) << 23; -static const uint32_t DV_II_50_2_bit = (uint32_t)(1) << 24; -static const uint32_t DV_II_51_0_bit = (uint32_t)(1) << 25; -static const uint32_t DV_II_51_2_bit = (uint32_t)(1) << 26; -static const uint32_t DV_II_52_0_bit = (uint32_t)(1) << 27; -static const uint32_t DV_II_53_0_bit = (uint32_t)(1) << 28; -static const uint32_t DV_II_54_0_bit = (uint32_t)(1) << 29; -static const uint32_t DV_II_55_0_bit = (uint32_t)(1) << 30; -static const uint32_t DV_II_56_0_bit = (uint32_t)(1) << 31; - -dv_info_t sha1_dvs[] = -{ - {1,43,0,58,0,0, { 0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161,0x80000599 } } -, {1,44,0,58,0,1, { 0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161 } } -, {1,45,0,58,0,2, { 0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803 } } -, {1,46,0,58,0,3, { 0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c } } -, {1,46,2,58,0,4, { 0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a,0x00000132 } } -, {1,47,0,58,0,5, { 0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6 } } -, {1,47,2,58,0,6, { 0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a } } -, {1,48,0,58,0,7, { 0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408 } } -, {1,48,2,58,0,8, { 0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020 } } -, {1,49,0,58,0,9, { 0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164 } } -, {1,49,2,58,0,10, { 0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590 } } -, {1,50,0,65,0,11, { 0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018 } } -, {1,50,2,65,0,12, { 0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060 } } -, {1,51,0,65,0,13, { 0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202 } } -, {1,51,2,65,0,14, { 0xa0000003,0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a } } -, {1,52,0,65,0,15, { 0x04000010,0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012 } } -, {2,45,0,58,0,16, { 0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054,0x00000967 } } -, {2,46,0,58,0,17, { 0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054 } } -, {2,46,2,58,0,18, { 0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6,0x0000106a,0x00000b90,0x00000152 } } -, {2,47,0,58,0,19, { 0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4 } } -, {2,48,0,58,0,20, { 0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a } } -, {2,49,0,58,0,21, { 0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d } } -, {2,49,2,58,0,22, { 0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6 } } -, {2,50,0,65,0,23, { 0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b } } -, {2,50,2,65,0,24, { 0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c } } -, {2,51,0,65,0,25, { 0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b } } -, {2,51,2,65,0,26, { 0x00000043,0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e } } -, {2,52,0,65,0,27, { 0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014 } } -, {2,53,0,65,0,28, { 0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089 } } -, {2,54,0,65,0,29, { 0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107 } } -, {2,55,0,65,0,30, { 0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b } } -, {2,56,0,65,0,31, { 0x2600001a,0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046 } } -, {0,0,0,0,0,0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}} -}; -void ubc_check(const uint32_t W[80], uint32_t dvmask[1]) -{ - uint32_t mask = ~((uint32_t)(0)); - mask &= (((((W[44]^W[45])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_I_51_0_bit|DV_I_52_0_bit|DV_II_45_0_bit|DV_II_46_0_bit|DV_II_50_0_bit|DV_II_51_0_bit)); - mask &= (((((W[49]^W[50])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_II_45_0_bit|DV_II_50_0_bit|DV_II_51_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); - mask &= (((((W[48]^W[49])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_52_0_bit|DV_II_49_0_bit|DV_II_50_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); - mask &= ((((W[47]^(W[50]>>25))&(1<<4))-(1<<4)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); - mask &= (((((W[47]^W[48])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_51_0_bit|DV_II_48_0_bit|DV_II_49_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); - mask &= (((((W[46]>>4)^(W[49]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_50_0_bit|DV_II_55_0_bit)); - mask &= (((((W[46]^W[47])>>29)&1)-1) | ~(DV_I_43_0_bit|DV_I_50_0_bit|DV_II_47_0_bit|DV_II_48_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); - mask &= (((((W[45]>>4)^(W[48]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_49_0_bit|DV_II_54_0_bit)); - mask &= (((((W[45]^W[46])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_51_0_bit|DV_II_52_0_bit)); - mask &= (((((W[44]>>4)^(W[47]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_53_0_bit)); - mask &= (((((W[43]>>4)^(W[46]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_52_0_bit)); - mask &= (((((W[43]^W[44])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_I_50_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_49_0_bit|DV_II_50_0_bit)); - mask &= (((((W[42]>>4)^(W[45]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_51_0_bit)); - mask &= (((((W[41]>>4)^(W[44]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_50_0_bit)); - mask &= (((((W[40]^W[41])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_47_0_bit|DV_I_48_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_56_0_bit)); - mask &= (((((W[54]^W[55])>>29)&1)-1) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_50_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); - mask &= (((((W[53]^W[54])>>29)&1)-1) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_49_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); - mask &= (((((W[52]^W[53])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); - mask &= ((((W[50]^(W[53]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_48_0_bit|DV_II_54_0_bit)); - mask &= (((((W[50]^W[51])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_II_46_0_bit|DV_II_51_0_bit|DV_II_52_0_bit|DV_II_56_0_bit)); - mask &= ((((W[49]^(W[52]>>25))&(1<<4))-(1<<4)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_47_0_bit|DV_II_53_0_bit)); - mask &= ((((W[48]^(W[51]>>25))&(1<<4))-(1<<4)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_52_0_bit)); - mask &= (((((W[42]^W[43])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); - mask &= (((((W[41]^W[42])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_48_0_bit)); - mask &= (((((W[40]>>4)^(W[43]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_50_0_bit|DV_II_49_0_bit|DV_II_56_0_bit)); - mask &= (((((W[39]>>4)^(W[42]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_49_0_bit|DV_II_48_0_bit|DV_II_55_0_bit)); - if (mask & (DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) - mask &= (((((W[38]>>4)^(W[41]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); - mask &= (((((W[37]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_47_0_bit|DV_II_46_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); - if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)) - mask &= (((((W[55]^W[56])>>29)&1)-1) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); - if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)) - mask &= ((((W[52]^(W[55]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)); - if (mask & (DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)) - mask &= ((((W[51]^(W[54]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)); - if (mask & (DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)) - mask &= (((((W[51]^W[52])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); - if (mask & (DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)) - mask &= (((((W[36]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)); - if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)) - mask &= ((0-(((W[53]^W[56])>>29)&1)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); - if (mask & (DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)) - mask &= ((0-(((W[51]^W[54])>>29)&1)) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)); - if (mask & (DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)) - mask &= ((0-(((W[50]^W[52])>>29)&1)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)); - if (mask & (DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)) - mask &= ((0-(((W[49]^W[51])>>29)&1)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)); - if (mask & (DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)) - mask &= ((0-(((W[48]^W[50])>>29)&1)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)); - if (mask & (DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)) - mask &= ((0-(((W[47]^W[49])>>29)&1)) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)); - if (mask & (DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)) - mask &= ((0-(((W[46]^W[48])>>29)&1)) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)); - mask &= ((((W[45]^W[47])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit|DV_I_51_2_bit)); - if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)) - mask &= ((0-(((W[45]^W[47])>>29)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)); - mask &= (((((W[44]^W[46])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit|DV_I_50_2_bit)); - if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)) - mask &= ((0-(((W[44]^W[46])>>29)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)); - mask &= ((0-((W[41]^(W[42]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_II_46_2_bit|DV_II_51_2_bit)); - mask &= ((0-((W[40]^(W[41]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_51_2_bit|DV_II_50_2_bit)); - if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)) - mask &= ((0-(((W[40]^W[42])>>4)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)); - mask &= ((0-((W[39]^(W[40]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_50_2_bit|DV_II_49_2_bit)); - if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)) - mask &= ((0-(((W[39]^W[41])>>4)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)); - if (mask & (DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) - mask &= ((0-(((W[38]^W[40])>>4)&1)) | ~(DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); - if (mask & (DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)) - mask &= ((0-(((W[37]^W[39])>>4)&1)) | ~(DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); - mask &= ((0-((W[36]^(W[37]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_50_2_bit|DV_II_46_2_bit)); - if (mask & (DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)) - mask &= (((((W[35]>>4)^(W[39]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)); - if (mask & (DV_I_48_0_bit|DV_II_48_0_bit)) - mask &= ((0-((W[63]^(W[64]>>5))&(1<<0))) | ~(DV_I_48_0_bit|DV_II_48_0_bit)); - if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) - mask &= ((0-((W[63]^(W[64]>>5))&(1<<1))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); - if (mask & (DV_I_47_0_bit|DV_II_47_0_bit)) - mask &= ((0-((W[62]^(W[63]>>5))&(1<<0))) | ~(DV_I_47_0_bit|DV_II_47_0_bit)); - if (mask & (DV_I_46_0_bit|DV_II_46_0_bit)) - mask &= ((0-((W[61]^(W[62]>>5))&(1<<0))) | ~(DV_I_46_0_bit|DV_II_46_0_bit)); - mask &= ((0-((W[61]^(W[62]>>5))&(1<<2))) | ~(DV_I_46_2_bit|DV_II_46_2_bit)); - if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) - mask &= ((0-((W[60]^(W[61]>>5))&(1<<0))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); - if (mask & (DV_II_51_0_bit|DV_II_54_0_bit)) - mask &= (((((W[58]^W[59])>>29)&1)-1) | ~(DV_II_51_0_bit|DV_II_54_0_bit)); - if (mask & (DV_II_50_0_bit|DV_II_53_0_bit)) - mask &= (((((W[57]^W[58])>>29)&1)-1) | ~(DV_II_50_0_bit|DV_II_53_0_bit)); - if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) - mask &= ((((W[56]^(W[59]>>25))&(1<<4))-(1<<4)) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); - if (mask & (DV_II_51_0_bit|DV_II_52_0_bit)) - mask &= ((0-(((W[56]^W[59])>>29)&1)) | ~(DV_II_51_0_bit|DV_II_52_0_bit)); - if (mask & (DV_II_49_0_bit|DV_II_52_0_bit)) - mask &= (((((W[56]^W[57])>>29)&1)-1) | ~(DV_II_49_0_bit|DV_II_52_0_bit)); - if (mask & (DV_II_51_0_bit|DV_II_53_0_bit)) - mask &= ((((W[55]^(W[58]>>25))&(1<<4))-(1<<4)) | ~(DV_II_51_0_bit|DV_II_53_0_bit)); - if (mask & (DV_II_50_0_bit|DV_II_52_0_bit)) - mask &= ((((W[54]^(W[57]>>25))&(1<<4))-(1<<4)) | ~(DV_II_50_0_bit|DV_II_52_0_bit)); - if (mask & (DV_II_49_0_bit|DV_II_51_0_bit)) - mask &= ((((W[53]^(W[56]>>25))&(1<<4))-(1<<4)) | ~(DV_II_49_0_bit|DV_II_51_0_bit)); - mask &= ((((W[51]^(W[50]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); - mask &= ((((W[48]^W[50])&(1<<6))-(1<<6)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); - if (mask & (DV_I_51_0_bit|DV_I_52_0_bit)) - mask &= ((0-(((W[48]^W[55])>>29)&1)) | ~(DV_I_51_0_bit|DV_I_52_0_bit)); - mask &= ((((W[47]^W[49])&(1<<6))-(1<<6)) | ~(DV_I_49_2_bit|DV_I_51_2_bit)); - mask &= ((((W[48]^(W[47]>>5))&(1<<1))-(1<<1)) | ~(DV_I_47_2_bit|DV_II_51_2_bit)); - mask &= ((((W[46]^W[48])&(1<<6))-(1<<6)) | ~(DV_I_48_2_bit|DV_I_50_2_bit)); - mask &= ((((W[47]^(W[46]>>5))&(1<<1))-(1<<1)) | ~(DV_I_46_2_bit|DV_II_50_2_bit)); - mask &= ((0-((W[44]^(W[45]>>5))&(1<<1))) | ~(DV_I_51_2_bit|DV_II_49_2_bit)); - mask &= ((((W[43]^W[45])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit)); - mask &= (((((W[42]^W[44])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit)); - mask &= ((((W[43]^(W[42]>>5))&(1<<1))-(1<<1)) | ~(DV_II_46_2_bit|DV_II_51_2_bit)); - mask &= ((((W[42]^(W[41]>>5))&(1<<1))-(1<<1)) | ~(DV_I_51_2_bit|DV_II_50_2_bit)); - mask &= ((((W[41]^(W[40]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_49_2_bit)); - if (mask & (DV_I_52_0_bit|DV_II_51_0_bit)) - mask &= ((((W[39]^(W[43]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_51_0_bit)); - if (mask & (DV_I_51_0_bit|DV_II_50_0_bit)) - mask &= ((((W[38]^(W[42]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_50_0_bit)); - if (mask & (DV_I_48_2_bit|DV_I_51_2_bit)) - mask &= ((0-((W[37]^(W[38]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_I_51_2_bit)); - if (mask & (DV_I_50_0_bit|DV_II_49_0_bit)) - mask &= ((((W[37]^(W[41]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_II_49_0_bit)); - if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) - mask &= ((0-((W[36]^W[38])&(1<<4))) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); - mask &= ((0-((W[35]^(W[36]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_49_2_bit)); - if (mask & (DV_I_51_0_bit|DV_II_47_0_bit)) - mask &= ((((W[35]^(W[39]>>25))&(1<<3))-(1<<3)) | ~(DV_I_51_0_bit|DV_II_47_0_bit)); -if (mask) { - - if (mask & DV_I_43_0_bit) - if ( - !((W[61]^(W[62]>>5)) & (1<<1)) - || !(!((W[59]^(W[63]>>25)) & (1<<5))) - || !((W[58]^(W[63]>>30)) & (1<<0)) - ) mask &= ~DV_I_43_0_bit; - if (mask & DV_I_44_0_bit) - if ( - !((W[62]^(W[63]>>5)) & (1<<1)) - || !(!((W[60]^(W[64]>>25)) & (1<<5))) - || !((W[59]^(W[64]>>30)) & (1<<0)) - ) mask &= ~DV_I_44_0_bit; - if (mask & DV_I_46_2_bit) - mask &= ((~((W[40]^W[42])>>2)) | ~DV_I_46_2_bit); - if (mask & DV_I_47_2_bit) - if ( - !((W[62]^(W[63]>>5)) & (1<<2)) - || !(!((W[41]^W[43]) & (1<<6))) - ) mask &= ~DV_I_47_2_bit; - if (mask & DV_I_48_2_bit) - if ( - !((W[63]^(W[64]>>5)) & (1<<2)) - || !(!((W[48]^(W[49]<<5)) & (1<<6))) - ) mask &= ~DV_I_48_2_bit; - if (mask & DV_I_49_2_bit) - if ( - !(!((W[49]^(W[50]<<5)) & (1<<6))) - || !((W[42]^W[50]) & (1<<1)) - || !(!((W[39]^(W[40]<<5)) & (1<<6))) - || !((W[38]^W[40]) & (1<<1)) - ) mask &= ~DV_I_49_2_bit; - if (mask & DV_I_50_0_bit) - mask &= ((((W[36]^W[37])<<7)) | ~DV_I_50_0_bit); - if (mask & DV_I_50_2_bit) - mask &= ((((W[43]^W[51])<<11)) | ~DV_I_50_2_bit); - if (mask & DV_I_51_0_bit) - mask &= ((((W[37]^W[38])<<9)) | ~DV_I_51_0_bit); - if (mask & DV_I_51_2_bit) - if ( - !(!((W[51]^(W[52]<<5)) & (1<<6))) - || !(!((W[49]^W[51]) & (1<<6))) - || !(!((W[37]^(W[37]>>5)) & (1<<1))) - || !(!((W[35]^(W[39]>>25)) & (1<<5))) - ) mask &= ~DV_I_51_2_bit; - if (mask & DV_I_52_0_bit) - mask &= ((((W[38]^W[39])<<11)) | ~DV_I_52_0_bit); - if (mask & DV_II_46_2_bit) - mask &= ((((W[47]^W[51])<<17)) | ~DV_II_46_2_bit); - if (mask & DV_II_48_0_bit) - if ( - !(!((W[36]^(W[40]>>25)) & (1<<3))) - || !((W[35]^(W[40]<<2)) & (1<<30)) - ) mask &= ~DV_II_48_0_bit; - if (mask & DV_II_49_0_bit) - if ( - !(!((W[37]^(W[41]>>25)) & (1<<3))) - || !((W[36]^(W[41]<<2)) & (1<<30)) - ) mask &= ~DV_II_49_0_bit; - if (mask & DV_II_49_2_bit) - if ( - !(!((W[53]^(W[54]<<5)) & (1<<6))) - || !(!((W[51]^W[53]) & (1<<6))) - || !((W[50]^W[54]) & (1<<1)) - || !(!((W[45]^(W[46]<<5)) & (1<<6))) - || !(!((W[37]^(W[41]>>25)) & (1<<5))) - || !((W[36]^(W[41]>>30)) & (1<<0)) - ) mask &= ~DV_II_49_2_bit; - if (mask & DV_II_50_0_bit) - if ( - !((W[55]^W[58]) & (1<<29)) - || !(!((W[38]^(W[42]>>25)) & (1<<3))) - || !((W[37]^(W[42]<<2)) & (1<<30)) - ) mask &= ~DV_II_50_0_bit; - if (mask & DV_II_50_2_bit) - if ( - !(!((W[54]^(W[55]<<5)) & (1<<6))) - || !(!((W[52]^W[54]) & (1<<6))) - || !((W[51]^W[55]) & (1<<1)) - || !((W[45]^W[47]) & (1<<1)) - || !(!((W[38]^(W[42]>>25)) & (1<<5))) - || !((W[37]^(W[42]>>30)) & (1<<0)) - ) mask &= ~DV_II_50_2_bit; - if (mask & DV_II_51_0_bit) - if ( - !(!((W[39]^(W[43]>>25)) & (1<<3))) - || !((W[38]^(W[43]<<2)) & (1<<30)) - ) mask &= ~DV_II_51_0_bit; - if (mask & DV_II_51_2_bit) - if ( - !(!((W[55]^(W[56]<<5)) & (1<<6))) - || !(!((W[53]^W[55]) & (1<<6))) - || !((W[52]^W[56]) & (1<<1)) - || !((W[46]^W[48]) & (1<<1)) - || !(!((W[39]^(W[43]>>25)) & (1<<5))) - || !((W[38]^(W[43]>>30)) & (1<<0)) - ) mask &= ~DV_II_51_2_bit; - if (mask & DV_II_52_0_bit) - if ( - !(!((W[59]^W[60]) & (1<<29))) - || !(!((W[40]^(W[44]>>25)) & (1<<3))) - || !(!((W[40]^(W[44]>>25)) & (1<<4))) - || !((W[39]^(W[44]<<2)) & (1<<30)) - ) mask &= ~DV_II_52_0_bit; - if (mask & DV_II_53_0_bit) - if ( - !((W[58]^W[61]) & (1<<29)) - || !(!((W[57]^(W[61]>>25)) & (1<<4))) - || !(!((W[41]^(W[45]>>25)) & (1<<3))) - || !(!((W[41]^(W[45]>>25)) & (1<<4))) - ) mask &= ~DV_II_53_0_bit; - if (mask & DV_II_54_0_bit) - if ( - !(!((W[58]^(W[62]>>25)) & (1<<4))) - || !(!((W[42]^(W[46]>>25)) & (1<<3))) - || !(!((W[42]^(W[46]>>25)) & (1<<4))) - ) mask &= ~DV_II_54_0_bit; - if (mask & DV_II_55_0_bit) - if ( - !(!((W[59]^(W[63]>>25)) & (1<<4))) - || !(!((W[57]^(W[59]>>25)) & (1<<4))) - || !(!((W[43]^(W[47]>>25)) & (1<<3))) - || !(!((W[43]^(W[47]>>25)) & (1<<4))) - ) mask &= ~DV_II_55_0_bit; - if (mask & DV_II_56_0_bit) - if ( - !(!((W[60]^(W[64]>>25)) & (1<<4))) - || !(!((W[44]^(W[48]>>25)) & (1<<3))) - || !(!((W[44]^(W[48]>>25)) & (1<<4))) - ) mask &= ~DV_II_56_0_bit; -} - - dvmask[0]=mask; -} - -#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C -#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C -#endif diff --git a/src/hash/sha1/sha1dc/ubc_check.h b/src/hash/sha1/sha1dc/ubc_check.h deleted file mode 100644 index d7e17dc73..000000000 --- a/src/hash/sha1/sha1dc/ubc_check.h +++ /dev/null @@ -1,52 +0,0 @@ -/*** -* Copyright 2017 Marc Stevens , Dan Shumow -* Distributed under the MIT Software License. -* See accompanying file LICENSE.txt or copy at -* https://opensource.org/licenses/MIT -***/ - -/* -// this file was generated by the 'parse_bitrel' program in the tools section -// using the data files from directory 'tools/data/3565' -// -// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check -// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) -// dm[80] is the expanded message block XOR-difference defined by the DV -// testt is the step to do the recompression from for collision detection -// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check -// -// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs -// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met -// thus one needs to do the recompression check for each DV that has its bit set -*/ - -#ifndef SHA1DC_UBC_CHECK_H -#define SHA1DC_UBC_CHECK_H - -#if defined(__cplusplus) -extern "C" { -#endif - -#ifndef SHA1DC_NO_STANDARD_INCLUDES -#include -#endif - -#define DVMASKSIZE 1 -typedef struct { int dvType; int dvK; int dvB; int testt; int maski; int maskb; uint32_t dm[80]; } dv_info_t; -extern dv_info_t sha1_dvs[]; -void ubc_check(const uint32_t W[80], uint32_t dvmask[DVMASKSIZE]); - -#define DOSTORESTATE58 -#define DOSTORESTATE65 - -#define CHECK_DVMASK(_DVMASK) (0 != _DVMASK[0]) - -#if defined(__cplusplus) -} -#endif - -#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H -#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H -#endif - -#endif diff --git a/src/hash/sha1/win32.c b/src/hash/sha1/win32.c deleted file mode 100644 index b89dfbad8..000000000 --- a/src/hash/sha1/win32.c +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "win32.h" - -#include "runtime.h" - -#include -#include - -#define GIT_HASH_CNG_DLL_NAME "bcrypt.dll" - -/* BCRYPT_SHA1_ALGORITHM */ -#define GIT_HASH_CNG_HASH_TYPE L"SHA1" - -/* BCRYPT_OBJECT_LENGTH */ -#define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength" - -/* BCRYPT_HASH_REUSEABLE_FLAGS */ -#define GIT_HASH_CNG_HASH_REUSABLE 0x00000020 - -static git_hash_prov hash_prov = {0}; - -/* Hash initialization */ - -/* Initialize CNG, if available */ -GIT_INLINE(int) hash_cng_prov_init(void) -{ - char dll_path[MAX_PATH]; - DWORD dll_path_len, size_len; - - /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */ - if (!git_has_win32_version(6, 0, 1)) { - git_error_set(GIT_ERROR_SHA1, "CryptoNG is not supported on this platform"); - return -1; - } - - /* Load bcrypt.dll explicitly from the system directory */ - if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || - dll_path_len > MAX_PATH || - StringCchCat(dll_path, MAX_PATH, "\\") < 0 || - StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 || - (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) { - git_error_set(GIT_ERROR_SHA1, "CryptoNG library could not be loaded"); - return -1; - } - - /* Load the function addresses */ - if ((hash_prov.prov.cng.open_algorithm_provider = (hash_win32_cng_open_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptOpenAlgorithmProvider")) == NULL || - (hash_prov.prov.cng.get_property = (hash_win32_cng_get_property_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptGetProperty")) == NULL || - (hash_prov.prov.cng.create_hash = (hash_win32_cng_create_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCreateHash")) == NULL || - (hash_prov.prov.cng.finish_hash = (hash_win32_cng_finish_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptFinishHash")) == NULL || - (hash_prov.prov.cng.hash_data = (hash_win32_cng_hash_data_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptHashData")) == NULL || - (hash_prov.prov.cng.destroy_hash = (hash_win32_cng_destroy_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptDestroyHash")) == NULL || - (hash_prov.prov.cng.close_algorithm_provider = (hash_win32_cng_close_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCloseAlgorithmProvider")) == NULL) { - FreeLibrary(hash_prov.prov.cng.dll); - - git_error_set(GIT_ERROR_OS, "CryptoNG functions could not be loaded"); - return -1; - } - - /* Load the SHA1 algorithm */ - if (hash_prov.prov.cng.open_algorithm_provider(&hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0) { - FreeLibrary(hash_prov.prov.cng.dll); - - git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized"); - return -1; - } - - /* Get storage space for the hash object */ - if (hash_prov.prov.cng.get_property(hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_prov.prov.cng.hash_object_size, sizeof(DWORD), &size_len, 0) < 0) { - hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); - FreeLibrary(hash_prov.prov.cng.dll); - - git_error_set(GIT_ERROR_OS, "algorithm handle could not be found"); - return -1; - } - - hash_prov.type = CNG; - return 0; -} - -GIT_INLINE(void) hash_cng_prov_shutdown(void) -{ - hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); - FreeLibrary(hash_prov.prov.cng.dll); - - hash_prov.type = INVALID; -} - -/* Initialize CryptoAPI */ -GIT_INLINE(int) hash_cryptoapi_prov_init() -{ - if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { - git_error_set(GIT_ERROR_OS, "legacy hash context could not be started"); - return -1; - } - - hash_prov.type = CRYPTOAPI; - return 0; -} - -GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void) -{ - CryptReleaseContext(hash_prov.prov.cryptoapi.handle, 0); - - hash_prov.type = INVALID; -} - -static void sha1_shutdown(void) -{ - if (hash_prov.type == CNG) - hash_cng_prov_shutdown(); - else if(hash_prov.type == CRYPTOAPI) - hash_cryptoapi_prov_shutdown(); -} - -int git_hash_sha1_global_init(void) -{ - int error = 0; - - if (hash_prov.type != INVALID) - return 0; - - if ((error = hash_cng_prov_init()) < 0) - error = hash_cryptoapi_prov_init(); - - if (!error) - error = git_runtime_shutdown_register(sha1_shutdown); - - return error; -} - -/* CryptoAPI: available in Windows XP and newer */ - -GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_sha1_ctx *ctx) -{ - ctx->type = CRYPTOAPI; - ctx->prov = &hash_prov; - - return git_hash_sha1_init(ctx); -} - -GIT_INLINE(int) hash_cryptoapi_init(git_hash_sha1_ctx *ctx) -{ - if (ctx->ctx.cryptoapi.valid) - CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); - - if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) { - ctx->ctx.cryptoapi.valid = 0; - git_error_set(GIT_ERROR_OS, "legacy hash implementation could not be created"); - return -1; - } - - ctx->ctx.cryptoapi.valid = 1; - return 0; -} - -GIT_INLINE(int) hash_cryptoapi_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) -{ - const BYTE *data = (BYTE *)_data; - - GIT_ASSERT(ctx->ctx.cryptoapi.valid); - - while (len > 0) { - DWORD chunk = (len > MAXDWORD) ? MAXDWORD : (DWORD)len; - - if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, data, chunk, 0)) { - git_error_set(GIT_ERROR_OS, "legacy hash data could not be updated"); - return -1; - } - - data += chunk; - len -= chunk; - } - - return 0; -} - -GIT_INLINE(int) hash_cryptoapi_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - DWORD len = GIT_HASH_SHA1_SIZE; - int error = 0; - - GIT_ASSERT(ctx->ctx.cryptoapi.valid); - - if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out, &len, 0)) { - git_error_set(GIT_ERROR_OS, "legacy hash data could not be finished"); - error = -1; - } - - CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); - ctx->ctx.cryptoapi.valid = 0; - - return error; -} - -GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_sha1_ctx *ctx) -{ - if (ctx->ctx.cryptoapi.valid) - CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); -} - -/* CNG: Available in Windows Server 2008 and newer */ - -GIT_INLINE(int) hash_ctx_cng_init(git_hash_sha1_ctx *ctx) -{ - if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL) - return -1; - - if (hash_prov.prov.cng.create_hash(hash_prov.prov.cng.handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_prov.prov.cng.hash_object_size, NULL, 0, 0) < 0) { - git__free(ctx->ctx.cng.hash_object); - - git_error_set(GIT_ERROR_OS, "hash implementation could not be created"); - return -1; - } - - ctx->type = CNG; - ctx->prov = &hash_prov; - - return 0; -} - -GIT_INLINE(int) hash_cng_init(git_hash_sha1_ctx *ctx) -{ - BYTE hash[GIT_OID_RAWSZ]; - - if (!ctx->ctx.cng.updated) - return 0; - - /* CNG needs to be finished to restart */ - if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0) { - git_error_set(GIT_ERROR_OS, "hash implementation could not be finished"); - return -1; - } - - ctx->ctx.cng.updated = 0; - - return 0; -} - -GIT_INLINE(int) hash_cng_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) -{ - PBYTE data = (PBYTE)_data; - - while (len > 0) { - ULONG chunk = (len > ULONG_MAX) ? ULONG_MAX : (ULONG)len; - - if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, data, chunk, 0) < 0) { - git_error_set(GIT_ERROR_OS, "hash could not be updated"); - return -1; - } - - data += chunk; - len -= chunk; - } - - return 0; -} - -GIT_INLINE(int) hash_cng_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out, GIT_HASH_SHA1_SIZE, 0) < 0) { - git_error_set(GIT_ERROR_OS, "hash could not be finished"); - return -1; - } - - ctx->ctx.cng.updated = 0; - - return 0; -} - -GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_sha1_ctx *ctx) -{ - ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle); - git__free(ctx->ctx.cng.hash_object); -} - -/* Indirection between CryptoAPI and CNG */ - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) -{ - int error = 0; - - GIT_ASSERT_ARG(ctx); - - /* - * When compiled with GIT_THREADS, the global hash_prov data is - * initialized with git_libgit2_init. Otherwise, it must be initialized - * at first use. - */ - if (hash_prov.type == INVALID && (error = git_hash_sha1_global_init()) < 0) - return error; - - memset(ctx, 0x0, sizeof(git_hash_sha1_ctx)); - - return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx); -} - -int git_hash_sha1_init(git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(ctx->type); - return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx); -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(ctx->type); - return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len); -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(ctx->type); - return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx); -} - -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) -{ - if (!ctx) - return; - else if (ctx->type == CNG) - hash_ctx_cng_cleanup(ctx); - else if(ctx->type == CRYPTOAPI) - hash_ctx_cryptoapi_cleanup(ctx); -} diff --git a/src/hash/sha1/win32.h b/src/hash/sha1/win32.h deleted file mode 100644 index 791d20a42..000000000 --- a/src/hash/sha1/win32.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_win32_h__ -#define INCLUDE_hash_sha1_win32_h__ - -#include "hash/sha1.h" - -#include -#include - -enum hash_win32_prov_type { - INVALID = 0, - CRYPTOAPI, - CNG -}; - -/* - * CryptoAPI is available for hashing on Windows XP and newer. - */ - -struct hash_cryptoapi_prov { - HCRYPTPROV handle; -}; - -/* - * CNG (bcrypt.dll) is significantly more performant than CryptoAPI and is - * preferred, however it is only available on Windows 2008 and newer and - * must therefore be dynamically loaded, and we must inline constants that - * would not exist when building in pre-Windows 2008 environments. - */ - -/* Function declarations for CNG */ -typedef NTSTATUS (WINAPI *hash_win32_cng_open_algorithm_provider_fn)( - HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm, - LPCWSTR pszAlgId, - LPCWSTR pszImplementation, - DWORD dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_get_property_fn)( - HANDLE /* BCRYPT_HANDLE */ hObject, - LPCWSTR pszProperty, - PUCHAR pbOutput, - ULONG cbOutput, - ULONG *pcbResult, - ULONG dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_create_hash_fn)( - HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, - HANDLE /* BCRYPT_HASH_HANDLE */ *phHash, - PUCHAR pbHashObject, ULONG cbHashObject, - PUCHAR pbSecret, - ULONG cbSecret, - ULONG dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_finish_hash_fn)( - HANDLE /* BCRYPT_HASH_HANDLE */ hHash, - PUCHAR pbOutput, - ULONG cbOutput, - ULONG dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_hash_data_fn)( - HANDLE /* BCRYPT_HASH_HANDLE */ hHash, - PUCHAR pbInput, - ULONG cbInput, - ULONG dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_destroy_hash_fn)( - HANDLE /* BCRYPT_HASH_HANDLE */ hHash); - -typedef NTSTATUS (WINAPI *hash_win32_cng_close_algorithm_provider_fn)( - HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, - ULONG dwFlags); - -struct hash_cng_prov { - /* DLL for CNG */ - HINSTANCE dll; - - /* Function pointers for CNG */ - hash_win32_cng_open_algorithm_provider_fn open_algorithm_provider; - hash_win32_cng_get_property_fn get_property; - hash_win32_cng_create_hash_fn create_hash; - hash_win32_cng_finish_hash_fn finish_hash; - hash_win32_cng_hash_data_fn hash_data; - hash_win32_cng_destroy_hash_fn destroy_hash; - hash_win32_cng_close_algorithm_provider_fn close_algorithm_provider; - - HANDLE /* BCRYPT_ALG_HANDLE */ handle; - DWORD hash_object_size; -}; - -typedef struct { - enum hash_win32_prov_type type; - - union { - struct hash_cryptoapi_prov cryptoapi; - struct hash_cng_prov cng; - } prov; -} git_hash_prov; - -/* Hash contexts */ - -struct hash_cryptoapi_ctx { - bool valid; - HCRYPTHASH hash_handle; -}; - -struct hash_cng_ctx { - bool updated; - HANDLE /* BCRYPT_HASH_HANDLE */ hash_handle; - PBYTE hash_object; -}; - -struct git_hash_sha1_ctx { - enum hash_win32_prov_type type; - git_hash_prov *prov; - - union { - struct hash_cryptoapi_ctx cryptoapi; - struct hash_cng_ctx cng; - } ctx; -}; - -#endif diff --git a/src/hashsig.c b/src/hashsig.c deleted file mode 100644 index 6b4fb8352..000000000 --- a/src/hashsig.c +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "git2/sys/hashsig.h" -#include "futils.h" -#include "util.h" - -typedef uint32_t hashsig_t; -typedef uint64_t hashsig_state; - -#define HASHSIG_SCALE 100 - -#define HASHSIG_MAX_RUN 80 -#define HASHSIG_HASH_START INT64_C(0x012345678ABCDEF0) -#define HASHSIG_HASH_SHIFT 5 - -#define HASHSIG_HASH_MIX(S,CH) \ - (S) = ((S) << HASHSIG_HASH_SHIFT) - (S) + (hashsig_state)(CH) - -#define HASHSIG_HEAP_SIZE ((1 << 7) - 1) -#define HASHSIG_HEAP_MIN_SIZE 4 - -typedef int (*hashsig_cmp)(const void *a, const void *b, void *); - -typedef struct { - int size, asize; - hashsig_cmp cmp; - hashsig_t values[HASHSIG_HEAP_SIZE]; -} hashsig_heap; - -struct git_hashsig { - hashsig_heap mins; - hashsig_heap maxs; - size_t lines; - git_hashsig_option_t opt; -}; - -#define HEAP_LCHILD_OF(I) (((I)<<1)+1) -#define HEAP_RCHILD_OF(I) (((I)<<1)+2) -#define HEAP_PARENT_OF(I) (((I)-1)>>1) - -static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp) -{ - h->size = 0; - h->asize = HASHSIG_HEAP_SIZE; - h->cmp = cmp; -} - -static int hashsig_cmp_max(const void *a, const void *b, void *payload) -{ - hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b; - GIT_UNUSED(payload); - return (av < bv) ? -1 : (av > bv) ? 1 : 0; -} - -static int hashsig_cmp_min(const void *a, const void *b, void *payload) -{ - hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b; - GIT_UNUSED(payload); - return (av > bv) ? -1 : (av < bv) ? 1 : 0; -} - -static void hashsig_heap_up(hashsig_heap *h, int el) -{ - int parent_el = HEAP_PARENT_OF(el); - - while (el > 0 && h->cmp(&h->values[parent_el], &h->values[el], NULL) > 0) { - hashsig_t t = h->values[el]; - h->values[el] = h->values[parent_el]; - h->values[parent_el] = t; - - el = parent_el; - parent_el = HEAP_PARENT_OF(el); - } -} - -static void hashsig_heap_down(hashsig_heap *h, int el) -{ - hashsig_t v, lv, rv; - - /* 'el < h->size / 2' tests if el is bottom row of heap */ - - while (el < h->size / 2) { - int lel = HEAP_LCHILD_OF(el), rel = HEAP_RCHILD_OF(el), swapel; - - v = h->values[el]; - lv = h->values[lel]; - rv = h->values[rel]; - - if (h->cmp(&v, &lv, NULL) < 0 && h->cmp(&v, &rv, NULL) < 0) - break; - - swapel = (h->cmp(&lv, &rv, NULL) < 0) ? lel : rel; - - h->values[el] = h->values[swapel]; - h->values[swapel] = v; - - el = swapel; - } -} - -static void hashsig_heap_sort(hashsig_heap *h) -{ - /* only need to do this at the end for signature comparison */ - git__qsort_r(h->values, h->size, sizeof(hashsig_t), h->cmp, NULL); -} - -static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val) -{ - /* if heap is not full, insert new element */ - if (h->size < h->asize) { - h->values[h->size++] = val; - hashsig_heap_up(h, h->size - 1); - } - - /* if heap is full, pop top if new element should replace it */ - else if (h->cmp(&val, &h->values[0], NULL) > 0) { - h->size--; - h->values[0] = h->values[h->size]; - hashsig_heap_down(h, 0); - } - -} - -typedef struct { - int use_ignores; - uint8_t ignore_ch[256]; -} hashsig_in_progress; - -static int hashsig_in_progress_init( - hashsig_in_progress *prog, git_hashsig *sig) -{ - int i; - - /* no more than one can be set */ - GIT_ASSERT(!(sig->opt & GIT_HASHSIG_IGNORE_WHITESPACE) || - !(sig->opt & GIT_HASHSIG_SMART_WHITESPACE)); - - if (sig->opt & GIT_HASHSIG_IGNORE_WHITESPACE) { - for (i = 0; i < 256; ++i) - prog->ignore_ch[i] = git__isspace_nonlf(i); - prog->use_ignores = 1; - } else if (sig->opt & GIT_HASHSIG_SMART_WHITESPACE) { - for (i = 0; i < 256; ++i) - prog->ignore_ch[i] = git__isspace(i); - prog->use_ignores = 1; - } else { - memset(prog, 0, sizeof(*prog)); - } - - return 0; -} - -static int hashsig_add_hashes( - git_hashsig *sig, - const uint8_t *data, - size_t size, - hashsig_in_progress *prog) -{ - const uint8_t *scan = data, *end = data + size; - hashsig_state state = HASHSIG_HASH_START; - int use_ignores = prog->use_ignores, len; - uint8_t ch; - - while (scan < end) { - state = HASHSIG_HASH_START; - - for (len = 0; scan < end && len < HASHSIG_MAX_RUN; ) { - ch = *scan; - - if (use_ignores) - for (; scan < end && git__isspace_nonlf(ch); ch = *scan) - ++scan; - else if (sig->opt & - (GIT_HASHSIG_IGNORE_WHITESPACE | GIT_HASHSIG_SMART_WHITESPACE)) - for (; scan < end && ch == '\r'; ch = *scan) - ++scan; - - /* peek at next character to decide what to do next */ - if (sig->opt & GIT_HASHSIG_SMART_WHITESPACE) - use_ignores = (ch == '\n'); - - if (scan >= end) - break; - ++scan; - - /* check run terminator */ - if (ch == '\n' || ch == '\0') { - sig->lines++; - break; - } - - ++len; - HASHSIG_HASH_MIX(state, ch); - } - - if (len > 0) { - hashsig_heap_insert(&sig->mins, (hashsig_t)state); - hashsig_heap_insert(&sig->maxs, (hashsig_t)state); - - while (scan < end && (*scan == '\n' || !*scan)) - ++scan; - } - } - - prog->use_ignores = use_ignores; - - return 0; -} - -static int hashsig_finalize_hashes(git_hashsig *sig) -{ - if (sig->mins.size < HASHSIG_HEAP_MIN_SIZE && - !(sig->opt & GIT_HASHSIG_ALLOW_SMALL_FILES)) { - git_error_set(GIT_ERROR_INVALID, - "file too small for similarity signature calculation"); - return GIT_EBUFS; - } - - hashsig_heap_sort(&sig->mins); - hashsig_heap_sort(&sig->maxs); - - return 0; -} - -static git_hashsig *hashsig_alloc(git_hashsig_option_t opts) -{ - git_hashsig *sig = git__calloc(1, sizeof(git_hashsig)); - if (!sig) - return NULL; - - hashsig_heap_init(&sig->mins, hashsig_cmp_min); - hashsig_heap_init(&sig->maxs, hashsig_cmp_max); - sig->opt = opts; - - return sig; -} - -int git_hashsig_create( - git_hashsig **out, - const char *buf, - size_t buflen, - git_hashsig_option_t opts) -{ - int error; - hashsig_in_progress prog; - git_hashsig *sig = hashsig_alloc(opts); - GIT_ERROR_CHECK_ALLOC(sig); - - if ((error = hashsig_in_progress_init(&prog, sig)) < 0) - return error; - - error = hashsig_add_hashes(sig, (const uint8_t *)buf, buflen, &prog); - - if (!error) - error = hashsig_finalize_hashes(sig); - - if (!error) - *out = sig; - else - git_hashsig_free(sig); - - return error; -} - -int git_hashsig_create_fromfile( - git_hashsig **out, - const char *path, - git_hashsig_option_t opts) -{ - uint8_t buf[0x1000]; - ssize_t buflen = 0; - int error = 0, fd; - hashsig_in_progress prog; - git_hashsig *sig = hashsig_alloc(opts); - GIT_ERROR_CHECK_ALLOC(sig); - - if ((fd = git_futils_open_ro(path)) < 0) { - git__free(sig); - return fd; - } - - if ((error = hashsig_in_progress_init(&prog, sig)) < 0) { - p_close(fd); - return error; - } - - while (!error) { - if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) { - if ((error = (int)buflen) < 0) - git_error_set(GIT_ERROR_OS, - "read error on '%s' calculating similarity hashes", path); - break; - } - - error = hashsig_add_hashes(sig, buf, buflen, &prog); - } - - p_close(fd); - - if (!error) - error = hashsig_finalize_hashes(sig); - - if (!error) - *out = sig; - else - git_hashsig_free(sig); - - return error; -} - -void git_hashsig_free(git_hashsig *sig) -{ - git__free(sig); -} - -static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b) -{ - int matches = 0, i, j, cmp; - - GIT_ASSERT_WITH_RETVAL(a->cmp == b->cmp, 0); - - /* hash heaps are sorted - just look for overlap vs total */ - - for (i = 0, j = 0; i < a->size && j < b->size; ) { - cmp = a->cmp(&a->values[i], &b->values[j], NULL); - - if (cmp < 0) - ++i; - else if (cmp > 0) - ++j; - else { - ++i; ++j; ++matches; - } - } - - return HASHSIG_SCALE * (matches * 2) / (a->size + b->size); -} - -int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b) -{ - /* if we have no elements in either file then each file is either - * empty or blank. if we're ignoring whitespace then the files are - * similar, otherwise they're dissimilar. - */ - if (a->mins.size == 0 && b->mins.size == 0) { - if ((!a->lines && !b->lines) || - (a->opt & GIT_HASHSIG_IGNORE_WHITESPACE)) - return HASHSIG_SCALE; - else - return 0; - } - - /* if we have fewer than the maximum number of elements, then just use - * one array since the two arrays will be the same - */ - if (a->mins.size < HASHSIG_HEAP_SIZE) { - return hashsig_heap_compare(&a->mins, &b->mins); - } else { - int mins, maxs; - - if ((mins = hashsig_heap_compare(&a->mins, &b->mins)) < 0) - return mins; - if ((maxs = hashsig_heap_compare(&a->maxs, &b->maxs)) < 0) - return maxs; - - return (mins + maxs) / 2; - } -} diff --git a/src/ident.c b/src/ident.c deleted file mode 100644 index 53095864e..000000000 --- a/src/ident.c +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "git2/sys/filter.h" -#include "filter.h" -#include "str.h" - -static int ident_find_id( - const char **id_start, const char **id_end, const char *start, size_t len) -{ - const char *end = start + len, *found = NULL; - - while (len > 3 && (found = memchr(start, '$', len)) != NULL) { - size_t remaining = (size_t)(end - found) - 1; - if (remaining < 3) - return GIT_ENOTFOUND; - - start = found + 1; - len = remaining; - - if (start[0] == 'I' && start[1] == 'd') - break; - } - - if (len < 3 || !found) - return GIT_ENOTFOUND; - *id_start = found; - - if ((found = memchr(start + 2, '$', len - 2)) == NULL) - return GIT_ENOTFOUND; - - *id_end = found + 1; - return 0; -} - -static int ident_insert_id( - git_str *to, const git_str *from, const git_filter_source *src) -{ - char oid[GIT_OID_HEXSZ+1]; - const char *id_start, *id_end, *from_end = from->ptr + from->size; - size_t need_size; - - /* replace $Id$ with blob id */ - - if (!git_filter_source_id(src)) - return GIT_PASSTHROUGH; - - git_oid_tostr(oid, sizeof(oid), git_filter_source_id(src)); - - if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0) - return GIT_PASSTHROUGH; - - need_size = (size_t)(id_start - from->ptr) + - 5 /* "$Id: " */ + GIT_OID_HEXSZ + 2 /* " $" */ + - (size_t)(from_end - id_end); - - if (git_str_grow(to, need_size) < 0) - return -1; - - git_str_set(to, from->ptr, (size_t)(id_start - from->ptr)); - git_str_put(to, "$Id: ", 5); - git_str_put(to, oid, GIT_OID_HEXSZ); - git_str_put(to, " $", 2); - git_str_put(to, id_end, (size_t)(from_end - id_end)); - - return git_str_oom(to) ? -1 : 0; -} - -static int ident_remove_id( - git_str *to, const git_str *from) -{ - const char *id_start, *id_end, *from_end = from->ptr + from->size; - size_t need_size; - - if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0) - return GIT_PASSTHROUGH; - - need_size = (size_t)(id_start - from->ptr) + - 4 /* "$Id$" */ + (size_t)(from_end - id_end); - - if (git_str_grow(to, need_size) < 0) - return -1; - - git_str_set(to, from->ptr, (size_t)(id_start - from->ptr)); - git_str_put(to, "$Id$", 4); - git_str_put(to, id_end, (size_t)(from_end - id_end)); - - return git_str_oom(to) ? -1 : 0; -} - -static int ident_apply( - git_filter *self, - void **payload, - git_str *to, - const git_str *from, - const git_filter_source *src) -{ - GIT_UNUSED(self); GIT_UNUSED(payload); - - /* Don't filter binary files */ - if (git_str_is_binary(from)) - return GIT_PASSTHROUGH; - - if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) - return ident_insert_id(to, from, src); - else - return ident_remove_id(to, from); -} - -static int ident_stream( - git_writestream **out, - git_filter *self, - void **payload, - const git_filter_source *src, - git_writestream *next) -{ - return git_filter_buffered_stream_new(out, - self, ident_apply, NULL, payload, src, next); -} - -git_filter *git_ident_filter_new(void) -{ - git_filter *f = git__calloc(1, sizeof(git_filter)); - if (f == NULL) - return NULL; - - f->version = GIT_FILTER_VERSION; - f->attributes = "+ident"; /* apply to files with ident attribute set */ - f->shutdown = git_filter_free; - f->stream = ident_stream; - - return f; -} diff --git a/src/idxmap.c b/src/idxmap.c deleted file mode 100644 index bc23608f2..000000000 --- a/src/idxmap.c +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "idxmap.h" - -#define kmalloc git__malloc -#define kcalloc git__calloc -#define krealloc git__realloc -#define kreallocarray git__reallocarray -#define kfree git__free -#include "khash.h" - -__KHASH_TYPE(idx, const git_index_entry *, git_index_entry *) -__KHASH_TYPE(idxicase, const git_index_entry *, git_index_entry *) - -/* This is __ac_X31_hash_string but with tolower and it takes the entry's stage into account */ -static kh_inline khint_t idxentry_hash(const git_index_entry *e) -{ - const char *s = e->path; - khint_t h = (khint_t)git__tolower(*s); - if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)git__tolower(*s); - return h + GIT_INDEX_ENTRY_STAGE(e); -} - -#define idxentry_equal(a, b) (GIT_INDEX_ENTRY_STAGE(a) == GIT_INDEX_ENTRY_STAGE(b) && strcmp(a->path, b->path) == 0) -#define idxentry_icase_equal(a, b) (GIT_INDEX_ENTRY_STAGE(a) == GIT_INDEX_ENTRY_STAGE(b) && strcasecmp(a->path, b->path) == 0) - -__KHASH_IMPL(idx, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_equal) -__KHASH_IMPL(idxicase, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_icase_equal) - -int git_idxmap_new(git_idxmap **out) -{ - *out = kh_init(idx); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -int git_idxmap_icase_new(git_idxmap_icase **out) -{ - *out = kh_init(idxicase); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -void git_idxmap_free(git_idxmap *map) -{ - kh_destroy(idx, map); -} - -void git_idxmap_icase_free(git_idxmap_icase *map) -{ - kh_destroy(idxicase, map); -} - -void git_idxmap_clear(git_idxmap *map) -{ - kh_clear(idx, map); -} - -void git_idxmap_icase_clear(git_idxmap_icase *map) -{ - kh_clear(idxicase, map); -} - -int git_idxmap_resize(git_idxmap *map, size_t size) -{ - if (!git__is_uint32(size) || - kh_resize(idx, map, (khiter_t)size) < 0) { - git_error_set_oom(); - return -1; - } - return 0; -} - -int git_idxmap_icase_resize(git_idxmap_icase *map, size_t size) -{ - if (!git__is_uint32(size) || - kh_resize(idxicase, map, (khiter_t)size) < 0) { - git_error_set_oom(); - return -1; - } - return 0; -} - -void *git_idxmap_get(git_idxmap *map, const git_index_entry *key) -{ - size_t idx = kh_get(idx, map, key); - if (idx == kh_end(map) || !kh_exist(map, idx)) - return NULL; - return kh_val(map, idx); -} - -int git_idxmap_set(git_idxmap *map, const git_index_entry *key, void *value) -{ - size_t idx; - int rval; - - idx = kh_put(idx, map, key, &rval); - if (rval < 0) - return -1; - - if (rval == 0) - kh_key(map, idx) = key; - - kh_val(map, idx) = value; - - return 0; -} - -int git_idxmap_icase_set(git_idxmap_icase *map, const git_index_entry *key, void *value) -{ - size_t idx; - int rval; - - idx = kh_put(idxicase, map, key, &rval); - if (rval < 0) - return -1; - - if (rval == 0) - kh_key(map, idx) = key; - - kh_val(map, idx) = value; - - return 0; -} - -void *git_idxmap_icase_get(git_idxmap_icase *map, const git_index_entry *key) -{ - size_t idx = kh_get(idxicase, map, key); - if (idx == kh_end(map) || !kh_exist(map, idx)) - return NULL; - return kh_val(map, idx); -} - -int git_idxmap_delete(git_idxmap *map, const git_index_entry *key) -{ - khiter_t idx = kh_get(idx, map, key); - if (idx == kh_end(map)) - return GIT_ENOTFOUND; - kh_del(idx, map, idx); - return 0; -} - -int git_idxmap_icase_delete(git_idxmap_icase *map, const git_index_entry *key) -{ - khiter_t idx = kh_get(idxicase, map, key); - if (idx == kh_end(map)) - return GIT_ENOTFOUND; - kh_del(idxicase, map, idx); - return 0; -} diff --git a/src/idxmap.h b/src/idxmap.h deleted file mode 100644 index 76170ef32..000000000 --- a/src/idxmap.h +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_idxmap_h__ -#define INCLUDE_idxmap_h__ - -#include "common.h" - -#include "git2/index.h" - -/** A map with `git_index_entry`s as key. */ -typedef struct kh_idx_s git_idxmap; -/** A map with case-insensitive `git_index_entry`s as key */ -typedef struct kh_idxicase_s git_idxmap_icase; - -/** - * Allocate a new index entry map. - * - * @param out Pointer to the map that shall be allocated. - * @return 0 on success, an error code if allocation has failed. - */ -int git_idxmap_new(git_idxmap **out); - -/** - * Allocate a new case-insensitive index entry map. - * - * @param out Pointer to the map that shall be allocated. - * @return 0 on success, an error code if allocation has failed. - */ -int git_idxmap_icase_new(git_idxmap_icase **out); - -/** - * Free memory associated with the map. - * - * Note that this function will _not_ free values added to this - * map. - * - * @param map Pointer to the map that is to be free'd. May be - * `NULL`. - */ -void git_idxmap_free(git_idxmap *map); - -/** - * Free memory associated with the map. - * - * Note that this function will _not_ free values added to this - * map. - * - * @param map Pointer to the map that is to be free'd. May be - * `NULL`. - */ -void git_idxmap_icase_free(git_idxmap_icase *map); - -/** - * Clear all entries from the map. - * - * This function will remove all entries from the associated map. - * Memory associated with it will not be released, though. - * - * @param map Pointer to the map that shall be cleared. May be - * `NULL`. - */ -void git_idxmap_clear(git_idxmap *map); - -/** - * Clear all entries from the map. - * - * This function will remove all entries from the associated map. - * Memory associated with it will not be released, though. - * - * @param map Pointer to the map that shall be cleared. May be - * `NULL`. - */ -void git_idxmap_icase_clear(git_idxmap_icase *map); - -/** - * Resize the map by allocating more memory. - * - * @param map map that shall be resized - * @param size count of entries that the map shall hold - * @return `0` if the map was successfully resized, a negative - * error code otherwise - */ -int git_idxmap_resize(git_idxmap *map, size_t size); - -/** - * Resize the map by allocating more memory. - * - * @param map map that shall be resized - * @param size count of entries that the map shall hold - * @return `0` if the map was successfully resized, a negative - * error code otherwise - */ -int git_idxmap_icase_resize(git_idxmap_icase *map, size_t size); - -/** - * Return value associated with the given key. - * - * @param map map to search key in - * @param key key to search for; the index entry will be searched - * for by its case-sensitive path - * @return value associated with the given key or NULL if the key was not found - */ -void *git_idxmap_get(git_idxmap *map, const git_index_entry *key); - -/** - * Return value associated with the given key. - * - * @param map map to search key in - * @param key key to search for; the index entry will be searched - * for by its case-insensitive path - * @return value associated with the given key or NULL if the key was not found - */ -void *git_idxmap_icase_get(git_idxmap_icase *map, const git_index_entry *key); - -/** - * Set the entry for key to value. - * - * If the map has no corresponding entry for the given key, a new - * entry will be created with the given value. If an entry exists - * already, its value will be updated to match the given value. - * - * @param map map to create new entry in - * @param key key to set - * @param value value to associate the key with; may be NULL - * @return zero if the key was successfully set, a negative error - * code otherwise - */ -int git_idxmap_set(git_idxmap *map, const git_index_entry *key, void *value); - -/** - * Set the entry for key to value. - * - * If the map has no corresponding entry for the given key, a new - * entry will be created with the given value. If an entry exists - * already, its value will be updated to match the given value. - * - * @param map map to create new entry in - * @param key key to set - * @param value value to associate the key with; may be NULL - * @return zero if the key was successfully set, a negative error - * code otherwise - */ -int git_idxmap_icase_set(git_idxmap_icase *map, const git_index_entry *key, void *value); - -/** - * Delete an entry from the map. - * - * Delete the given key and its value from the map. If no such - * key exists, this will do nothing. - * - * @param map map to delete key in - * @param key key to delete - * @return `0` if the key has been deleted, GIT_ENOTFOUND if no - * such key was found, a negative code in case of an - * error - */ -int git_idxmap_delete(git_idxmap *map, const git_index_entry *key); - -/** - * Delete an entry from the map. - * - * Delete the given key and its value from the map. If no such - * key exists, this will do nothing. - * - * @param map map to delete key in - * @param key key to delete - * @return `0` if the key has been deleted, GIT_ENOTFOUND if no - * such key was found, a negative code in case of an - * error - */ -int git_idxmap_icase_delete(git_idxmap_icase *map, const git_index_entry *key); - -#endif diff --git a/src/ignore.c b/src/ignore.c deleted file mode 100644 index cee58d7f1..000000000 --- a/src/ignore.c +++ /dev/null @@ -1,652 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "ignore.h" - -#include "git2/ignore.h" -#include "common.h" -#include "attrcache.h" -#include "fs_path.h" -#include "config.h" -#include "wildmatch.h" -#include "path.h" - -#define GIT_IGNORE_INTERNAL "[internal]exclude" - -#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n" - -/** - * A negative ignore pattern can negate a positive one without - * wildcards if it is a basename only and equals the basename of - * the positive pattern. Thus - * - * foo/bar - * !bar - * - * would result in foo/bar being unignored again while - * - * moo/foo/bar - * !foo/bar - * - * would do nothing. The reverse also holds true: a positive - * basename pattern can be negated by unignoring the basename in - * subdirectories. Thus - * - * bar - * !foo/bar - * - * would result in foo/bar being unignored again. As with the - * first case, - * - * foo/bar - * !moo/foo/bar - * - * would do nothing, again. - */ -static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) -{ - int (*cmp)(const char *, const char *, size_t); - git_attr_fnmatch *longer, *shorter; - char *p; - - if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 - || (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) - return false; - - if (neg->flags & GIT_ATTR_FNMATCH_ICASE) - cmp = git__strncasecmp; - else - cmp = git__strncmp; - - /* If lengths match we need to have an exact match */ - if (rule->length == neg->length) { - return cmp(rule->pattern, neg->pattern, rule->length) == 0; - } else if (rule->length < neg->length) { - shorter = rule; - longer = neg; - } else { - shorter = neg; - longer = rule; - } - - /* Otherwise, we need to check if the shorter - * rule is a basename only (that is, it contains - * no path separator) and, if so, if it - * matches the tail of the longer rule */ - p = longer->pattern + longer->length - shorter->length; - - if (p[-1] != '/') - return false; - if (memchr(shorter->pattern, '/', shorter->length) != NULL) - return false; - - return cmp(p, shorter->pattern, shorter->length) == 0; -} - -/** - * A negative ignore can only unignore a file which is given explicitly before, thus - * - * foo - * !foo/bar - * - * does not unignore 'foo/bar' as it's not in the list. However - * - * foo/ - * !foo/bar - * - * does unignore 'foo/bar', as it is contained within the 'foo/' rule. - */ -static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match) -{ - int error = 0, wildmatch_flags, effective_flags; - size_t i; - git_attr_fnmatch *rule; - char *path; - git_str buf = GIT_STR_INIT; - - *out = 0; - - wildmatch_flags = WM_PATHNAME; - if (match->flags & GIT_ATTR_FNMATCH_ICASE) - wildmatch_flags |= WM_CASEFOLD; - - /* path of the file relative to the workdir, so we match the rules in subdirs */ - if (match->containing_dir) { - git_str_puts(&buf, match->containing_dir); - } - if (git_str_puts(&buf, match->pattern) < 0) - return -1; - - path = git_str_detach(&buf); - - git_vector_foreach(rules, i, rule) { - if (!(rule->flags & GIT_ATTR_FNMATCH_HASWILD)) { - if (does_negate_pattern(rule, match)) { - error = 0; - *out = 1; - goto out; - } - else - continue; - } - - git_str_clear(&buf); - if (rule->containing_dir) - git_str_puts(&buf, rule->containing_dir); - git_str_puts(&buf, rule->pattern); - - if (git_str_oom(&buf)) - goto out; - - /* - * if rule isn't for full path we match without PATHNAME flag - * as lines like *.txt should match something like dir/test.txt - * requiring * to also match / - */ - effective_flags = wildmatch_flags; - if (!(rule->flags & GIT_ATTR_FNMATCH_FULLPATH)) - effective_flags &= ~WM_PATHNAME; - - /* if we found a match, we want to keep this rule */ - if ((wildmatch(git_str_cstr(&buf), path, effective_flags)) == WM_MATCH) { - *out = 1; - error = 0; - goto out; - } - } - - error = 0; - -out: - git__free(path); - git_str_dispose(&buf); - return error; -} - -static int parse_ignore_file( - git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros) -{ - int error = 0; - int ignore_case = false; - const char *scan = data, *context = NULL; - git_attr_fnmatch *match = NULL; - - GIT_UNUSED(allow_macros); - - if (git_repository__configmap_lookup(&ignore_case, repo, GIT_CONFIGMAP_IGNORECASE) < 0) - git_error_clear(); - - /* if subdir file path, convert context for file paths */ - if (attrs->entry && - git_fs_path_root(attrs->entry->path) < 0 && - !git__suffixcmp(attrs->entry->path, "/" GIT_IGNORE_FILE)) - context = attrs->entry->path; - - if (git_mutex_lock(&attrs->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock ignore file"); - return -1; - } - - while (!error && *scan) { - int valid_rule = 1; - - if (!match && !(match = git__calloc(1, sizeof(*match)))) { - error = -1; - break; - } - - match->flags = - GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; - - if (!(error = git_attr_fnmatch__parse( - match, &attrs->pool, context, &scan))) - { - match->flags |= GIT_ATTR_FNMATCH_IGNORE; - - if (ignore_case) - match->flags |= GIT_ATTR_FNMATCH_ICASE; - - scan = git__next_line(scan); - - /* - * If a negative match doesn't actually do anything, - * throw it away. As we cannot always verify whether a - * rule containing wildcards negates another rule, we - * do not optimize away these rules, though. - * */ - if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE - && !(match->flags & GIT_ATTR_FNMATCH_HASWILD)) - error = does_negate_rule(&valid_rule, &attrs->rules, match); - - if (!error && valid_rule) - error = git_vector_insert(&attrs->rules, match); - } - - if (error != 0 || !valid_rule) { - match->pattern = NULL; - - if (error == GIT_ENOTFOUND) - error = 0; - } else { - match = NULL; /* vector now "owns" the match */ - } - } - - git_mutex_unlock(&attrs->lock); - git__free(match); - - return error; -} - -static int push_ignore_file( - git_ignores *ignores, - git_vector *which_list, - const char *base, - const char *filename) -{ - git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE, base, filename }; - git_attr_file *file = NULL; - int error = 0; - - error = git_attr_cache__get(&file, ignores->repo, NULL, &source, parse_ignore_file, false); - - if (error < 0) - return error; - - if (file != NULL) { - if ((error = git_vector_insert(which_list, file)) < 0) - git_attr_file__free(file); - } - - return error; -} - -static int push_one_ignore(void *payload, const char *path) -{ - git_ignores *ign = payload; - ign->depth++; - return push_ignore_file(ign, &ign->ign_path, path, GIT_IGNORE_FILE); -} - -static int get_internal_ignores(git_attr_file **out, git_repository *repo) -{ - git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_MEMORY, NULL, GIT_IGNORE_INTERNAL }; - int error; - - if ((error = git_attr_cache__init(repo)) < 0) - return error; - - error = git_attr_cache__get(out, repo, NULL, &source, NULL, false); - - /* if internal rules list is empty, insert default rules */ - if (!error && !(*out)->rules.length) - error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES, false); - - return error; -} - -int git_ignore__for_path( - git_repository *repo, - const char *path, - git_ignores *ignores) -{ - int error = 0; - const char *workdir = git_repository_workdir(repo); - git_str infopath = GIT_STR_INIT; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(ignores); - GIT_ASSERT_ARG(path); - - memset(ignores, 0, sizeof(*ignores)); - ignores->repo = repo; - - /* Read the ignore_case flag */ - if ((error = git_repository__configmap_lookup( - &ignores->ignore_case, repo, GIT_CONFIGMAP_IGNORECASE)) < 0) - goto cleanup; - - if ((error = git_attr_cache__init(repo)) < 0) - goto cleanup; - - /* given a unrooted path in a non-bare repo, resolve it */ - if (workdir && git_fs_path_root(path) < 0) { - git_str local = GIT_STR_INIT; - - if ((error = git_fs_path_dirname_r(&local, path)) < 0 || - (error = git_fs_path_resolve_relative(&local, 0)) < 0 || - (error = git_fs_path_to_dir(&local)) < 0 || - (error = git_str_joinpath(&ignores->dir, workdir, local.ptr)) < 0 || - (error = git_path_validate_str_length(repo, &ignores->dir)) < 0) { - /* Nothing, we just want to stop on the first error */ - } - - git_str_dispose(&local); - } else { - if (!(error = git_str_joinpath(&ignores->dir, path, ""))) - error = git_path_validate_str_length(NULL, &ignores->dir); - } - - if (error < 0) - goto cleanup; - - if (workdir && !git__prefixcmp(ignores->dir.ptr, workdir)) - ignores->dir_root = strlen(workdir); - - /* set up internals */ - if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0) - goto cleanup; - - /* load .gitignore up the path */ - if (workdir != NULL) { - error = git_fs_path_walk_up( - &ignores->dir, workdir, push_one_ignore, ignores); - if (error < 0) - goto cleanup; - } - - /* load .git/info/exclude if possible */ - if ((error = git_repository__item_path(&infopath, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || - (error = push_ignore_file(ignores, &ignores->ign_global, infopath.ptr, GIT_IGNORE_FILE_INREPO)) < 0) { - if (error != GIT_ENOTFOUND) - goto cleanup; - error = 0; - } - - /* load core.excludesfile */ - if (git_repository_attr_cache(repo)->cfg_excl_file != NULL) - error = push_ignore_file( - ignores, &ignores->ign_global, NULL, - git_repository_attr_cache(repo)->cfg_excl_file); - -cleanup: - git_str_dispose(&infopath); - if (error < 0) - git_ignore__free(ignores); - - return error; -} - -int git_ignore__push_dir(git_ignores *ign, const char *dir) -{ - if (git_str_joinpath(&ign->dir, ign->dir.ptr, dir) < 0) - return -1; - - ign->depth++; - - return push_ignore_file( - ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); -} - -int git_ignore__pop_dir(git_ignores *ign) -{ - if (ign->ign_path.length > 0) { - git_attr_file *file = git_vector_last(&ign->ign_path); - const char *start = file->entry->path, *end; - - /* - ign->dir looks something like "/home/user/a/b/" (or "a/b/c/d/") - * - file->path looks something like "a/b/.gitignore - * - * We are popping the last directory off ign->dir. We also want - * to remove the file from the vector if the popped directory - * matches the ignore path. We need to test if the "a/b" part of - * the file key matches the path we are about to pop. - */ - - if ((end = strrchr(start, '/')) != NULL) { - size_t dirlen = (end - start) + 1; - const char *relpath = ign->dir.ptr + ign->dir_root; - size_t pathlen = ign->dir.size - ign->dir_root; - - if (pathlen == dirlen && !memcmp(relpath, start, dirlen)) { - git_vector_pop(&ign->ign_path); - git_attr_file__free(file); - } - } - } - - if (--ign->depth > 0) { - git_str_rtruncate_at_char(&ign->dir, '/'); - git_fs_path_to_dir(&ign->dir); - } - - return 0; -} - -void git_ignore__free(git_ignores *ignores) -{ - unsigned int i; - git_attr_file *file; - - git_attr_file__free(ignores->ign_internal); - - git_vector_foreach(&ignores->ign_path, i, file) { - git_attr_file__free(file); - ignores->ign_path.contents[i] = NULL; - } - git_vector_free(&ignores->ign_path); - - git_vector_foreach(&ignores->ign_global, i, file) { - git_attr_file__free(file); - ignores->ign_global.contents[i] = NULL; - } - git_vector_free(&ignores->ign_global); - - git_str_dispose(&ignores->dir); -} - -static bool ignore_lookup_in_rules( - int *ignored, git_attr_file *file, git_attr_path *path) -{ - size_t j; - git_attr_fnmatch *match; - - git_vector_rforeach(&file->rules, j, match) { - if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && - path->is_dir == GIT_DIR_FLAG_FALSE) - continue; - if (git_attr_fnmatch__match(match, path)) { - *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ? - GIT_IGNORE_TRUE : GIT_IGNORE_FALSE; - return true; - } - } - - return false; -} - -int git_ignore__lookup( - int *out, git_ignores *ignores, const char *pathname, git_dir_flag dir_flag) -{ - size_t i; - git_attr_file *file; - git_attr_path path; - - *out = GIT_IGNORE_NOTFOUND; - - if (git_attr_path__init( - &path, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0) - return -1; - - /* first process builtins - success means path was found */ - if (ignore_lookup_in_rules(out, ignores->ign_internal, &path)) - goto cleanup; - - /* next process files in the path. - * this process has to process ignores in reverse order - * to ensure correct prioritization of rules - */ - git_vector_rforeach(&ignores->ign_path, i, file) { - if (ignore_lookup_in_rules(out, file, &path)) - goto cleanup; - } - - /* last process global ignores */ - git_vector_foreach(&ignores->ign_global, i, file) { - if (ignore_lookup_in_rules(out, file, &path)) - goto cleanup; - } - -cleanup: - git_attr_path__free(&path); - return 0; -} - -int git_ignore_add_rule(git_repository *repo, const char *rules) -{ - int error; - git_attr_file *ign_internal = NULL; - - if ((error = get_internal_ignores(&ign_internal, repo)) < 0) - return error; - - error = parse_ignore_file(repo, ign_internal, rules, false); - git_attr_file__free(ign_internal); - - return error; -} - -int git_ignore_clear_internal_rules(git_repository *repo) -{ - int error; - git_attr_file *ign_internal; - - if ((error = get_internal_ignores(&ign_internal, repo)) < 0) - return error; - - if (!(error = git_attr_file__clear_rules(ign_internal, true))) - error = parse_ignore_file( - repo, ign_internal, GIT_IGNORE_DEFAULT_RULES, false); - - git_attr_file__free(ign_internal); - return error; -} - -int git_ignore_path_is_ignored( - int *ignored, - git_repository *repo, - const char *pathname) -{ - int error; - const char *workdir; - git_attr_path path; - git_ignores ignores; - unsigned int i; - git_attr_file *file; - git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(ignored); - GIT_ASSERT_ARG(pathname); - - workdir = git_repository_workdir(repo); - - memset(&path, 0, sizeof(path)); - memset(&ignores, 0, sizeof(ignores)); - - if (!git__suffixcmp(pathname, "/")) - dir_flag = GIT_DIR_FLAG_TRUE; - else if (git_repository_is_bare(repo)) - dir_flag = GIT_DIR_FLAG_FALSE; - - if ((error = git_attr_path__init(&path, pathname, workdir, dir_flag)) < 0 || - (error = git_ignore__for_path(repo, path.path, &ignores)) < 0) - goto cleanup; - - while (1) { - /* first process builtins - success means path was found */ - if (ignore_lookup_in_rules(ignored, ignores.ign_internal, &path)) - goto cleanup; - - /* next process files in the path */ - git_vector_foreach(&ignores.ign_path, i, file) { - if (ignore_lookup_in_rules(ignored, file, &path)) - goto cleanup; - } - - /* last process global ignores */ - git_vector_foreach(&ignores.ign_global, i, file) { - if (ignore_lookup_in_rules(ignored, file, &path)) - goto cleanup; - } - - /* move up one directory */ - if (path.basename == path.path) - break; - path.basename[-1] = '\0'; - while (path.basename > path.path && *path.basename != '/') - path.basename--; - if (path.basename > path.path) - path.basename++; - path.is_dir = 1; - - if ((error = git_ignore__pop_dir(&ignores)) < 0) - break; - } - - *ignored = 0; - -cleanup: - git_attr_path__free(&path); - git_ignore__free(&ignores); - return error; -} - -int git_ignore__check_pathspec_for_exact_ignores( - git_repository *repo, - git_vector *vspec, - bool no_fnmatch) -{ - int error = 0; - size_t i; - git_attr_fnmatch *match; - int ignored; - git_str path = GIT_STR_INIT; - const char *filename; - git_index *idx; - - if ((error = git_repository__ensure_not_bare( - repo, "validate pathspec")) < 0 || - (error = git_repository_index(&idx, repo)) < 0) - return error; - - git_vector_foreach(vspec, i, match) { - /* skip wildcard matches (if they are being used) */ - if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 && - !no_fnmatch) - continue; - - filename = match->pattern; - - /* if file is already in the index, it's fine */ - if (git_index_get_bypath(idx, filename, 0) != NULL) - continue; - - if ((error = git_repository_workdir_path(&path, repo, filename)) < 0) - break; - - /* is there a file on disk that matches this exactly? */ - if (!git_fs_path_isfile(path.ptr)) - continue; - - /* is that file ignored? */ - if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0) - break; - - if (ignored) { - git_error_set(GIT_ERROR_INVALID, "pathspec contains ignored file '%s'", - filename); - error = GIT_EINVALIDSPEC; - break; - } - } - - git_index_free(idx); - git_str_dispose(&path); - - return error; -} diff --git a/src/ignore.h b/src/ignore.h deleted file mode 100644 index aa5ca62b7..000000000 --- a/src/ignore.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_ignore_h__ -#define INCLUDE_ignore_h__ - -#include "common.h" - -#include "repository.h" -#include "vector.h" -#include "attr_file.h" - -#define GIT_IGNORE_FILE ".gitignore" -#define GIT_IGNORE_FILE_INREPO "exclude" -#define GIT_IGNORE_FILE_XDG "ignore" - -/* The git_ignores structure maintains three sets of ignores: - * - internal ignores - * - per directory ignores - * - global ignores (at lower priority than the others) - * As you traverse from one directory to another, you can push and pop - * directories onto git_ignores list efficiently. - */ -typedef struct { - git_repository *repo; - git_str dir; /* current directory reflected in ign_path */ - git_attr_file *ign_internal; - git_vector ign_path; - git_vector ign_global; - size_t dir_root; /* offset in dir to repo root */ - int ignore_case; - int depth; -} git_ignores; - -extern int git_ignore__for_path( - git_repository *repo, const char *path, git_ignores *ign); - -extern int git_ignore__push_dir(git_ignores *ign, const char *dir); - -extern int git_ignore__pop_dir(git_ignores *ign); - -extern void git_ignore__free(git_ignores *ign); - -enum { - GIT_IGNORE_UNCHECKED = -2, - GIT_IGNORE_NOTFOUND = -1, - GIT_IGNORE_FALSE = 0, - GIT_IGNORE_TRUE = 1 -}; - -extern int git_ignore__lookup(int *out, git_ignores *ign, const char *path, git_dir_flag dir_flag); - -/* command line Git sometimes generates an error message if given a - * pathspec that contains an exact match to an ignored file (provided - * --force isn't also given). This makes it easy to check it that has - * happened. Returns GIT_EINVALIDSPEC if the pathspec contains ignored - * exact matches (that are not already present in the index). - */ -extern int git_ignore__check_pathspec_for_exact_ignores( - git_repository *repo, git_vector *pathspec, bool no_fnmatch); - -#endif diff --git a/src/index.c b/src/index.c deleted file mode 100644 index aa97c6421..000000000 --- a/src/index.c +++ /dev/null @@ -1,3765 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "index.h" - -#include - -#include "repository.h" -#include "tree.h" -#include "tree-cache.h" -#include "hash.h" -#include "iterator.h" -#include "pathspec.h" -#include "ignore.h" -#include "blob.h" -#include "idxmap.h" -#include "diff.h" -#include "varint.h" -#include "path.h" - -#include "git2/odb.h" -#include "git2/oid.h" -#include "git2/blob.h" -#include "git2/config.h" -#include "git2/sys/index.h" - -static int index_apply_to_wd_diff(git_index *index, int action, const git_strarray *paths, - unsigned int flags, - git_index_matched_path_cb cb, void *payload); - -#define minimal_entry_size (offsetof(struct entry_short, path)) - -static const size_t INDEX_HEADER_SIZE = 12; - -static const unsigned int INDEX_VERSION_NUMBER_DEFAULT = 2; -static const unsigned int INDEX_VERSION_NUMBER_LB = 2; -static const unsigned int INDEX_VERSION_NUMBER_EXT = 3; -static const unsigned int INDEX_VERSION_NUMBER_COMP = 4; -static const unsigned int INDEX_VERSION_NUMBER_UB = 4; - -static const unsigned int INDEX_HEADER_SIG = 0x44495243; -static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'}; -static const char INDEX_EXT_UNMERGED_SIG[] = {'R', 'E', 'U', 'C'}; -static const char INDEX_EXT_CONFLICT_NAME_SIG[] = {'N', 'A', 'M', 'E'}; - -#define INDEX_OWNER(idx) ((git_repository *)(GIT_REFCOUNT_OWNER(idx))) - -struct index_header { - uint32_t signature; - uint32_t version; - uint32_t entry_count; -}; - -struct index_extension { - char signature[4]; - uint32_t extension_size; -}; - -struct entry_time { - uint32_t seconds; - uint32_t nanoseconds; -}; - -struct entry_short { - struct entry_time ctime; - struct entry_time mtime; - uint32_t dev; - uint32_t ino; - uint32_t mode; - uint32_t uid; - uint32_t gid; - uint32_t file_size; - git_oid oid; - uint16_t flags; - char path[1]; /* arbitrary length */ -}; - -struct entry_long { - struct entry_time ctime; - struct entry_time mtime; - uint32_t dev; - uint32_t ino; - uint32_t mode; - uint32_t uid; - uint32_t gid; - uint32_t file_size; - git_oid oid; - uint16_t flags; - uint16_t flags_extended; - char path[1]; /* arbitrary length */ -}; - -struct entry_srch_key { - const char *path; - size_t pathlen; - int stage; -}; - -struct entry_internal { - git_index_entry entry; - size_t pathlen; - char path[GIT_FLEX_ARRAY]; -}; - -struct reuc_entry_internal { - git_index_reuc_entry entry; - size_t pathlen; - char path[GIT_FLEX_ARRAY]; -}; - -bool git_index__enforce_unsaved_safety = false; - -/* local declarations */ -static int read_extension(size_t *read_len, git_index *index, const char *buffer, size_t buffer_size); -static int read_header(struct index_header *dest, const void *buffer); - -static int parse_index(git_index *index, const char *buffer, size_t buffer_size); -static bool is_index_extended(git_index *index); -static int write_index(unsigned char checksum[GIT_HASH_SHA1_SIZE], size_t *checksum_size, git_index *index, git_filebuf *file); - -static void index_entry_free(git_index_entry *entry); -static void index_entry_reuc_free(git_index_reuc_entry *reuc); - -GIT_INLINE(int) index_map_set(git_idxmap *map, git_index_entry *e, bool ignore_case) -{ - if (ignore_case) - return git_idxmap_icase_set((git_idxmap_icase *) map, e, e); - else - return git_idxmap_set(map, e, e); -} - -GIT_INLINE(int) index_map_delete(git_idxmap *map, git_index_entry *e, bool ignore_case) -{ - if (ignore_case) - return git_idxmap_icase_delete((git_idxmap_icase *) map, e); - else - return git_idxmap_delete(map, e); -} - -GIT_INLINE(int) index_map_resize(git_idxmap *map, size_t count, bool ignore_case) -{ - if (ignore_case) - return git_idxmap_icase_resize((git_idxmap_icase *) map, count); - else - return git_idxmap_resize(map, count); -} - -int git_index_entry_srch(const void *key, const void *array_member) -{ - const struct entry_srch_key *srch_key = key; - const struct entry_internal *entry = array_member; - int cmp; - size_t len1, len2, len; - - len1 = srch_key->pathlen; - len2 = entry->pathlen; - len = len1 < len2 ? len1 : len2; - - cmp = memcmp(srch_key->path, entry->path, len); - if (cmp) - return cmp; - if (len1 < len2) - return -1; - if (len1 > len2) - return 1; - - if (srch_key->stage != GIT_INDEX_STAGE_ANY) - return srch_key->stage - GIT_INDEX_ENTRY_STAGE(&entry->entry); - - return 0; -} - -int git_index_entry_isrch(const void *key, const void *array_member) -{ - const struct entry_srch_key *srch_key = key; - const struct entry_internal *entry = array_member; - int cmp; - size_t len1, len2, len; - - len1 = srch_key->pathlen; - len2 = entry->pathlen; - len = len1 < len2 ? len1 : len2; - - cmp = strncasecmp(srch_key->path, entry->path, len); - - if (cmp) - return cmp; - if (len1 < len2) - return -1; - if (len1 > len2) - return 1; - - if (srch_key->stage != GIT_INDEX_STAGE_ANY) - return srch_key->stage - GIT_INDEX_ENTRY_STAGE(&entry->entry); - - return 0; -} - -static int index_entry_srch_path(const void *path, const void *array_member) -{ - const git_index_entry *entry = array_member; - - return strcmp((const char *)path, entry->path); -} - -static int index_entry_isrch_path(const void *path, const void *array_member) -{ - const git_index_entry *entry = array_member; - - return strcasecmp((const char *)path, entry->path); -} - -int git_index_entry_cmp(const void *a, const void *b) -{ - int diff; - const git_index_entry *entry_a = a; - const git_index_entry *entry_b = b; - - diff = strcmp(entry_a->path, entry_b->path); - - if (diff == 0) - diff = (GIT_INDEX_ENTRY_STAGE(entry_a) - GIT_INDEX_ENTRY_STAGE(entry_b)); - - return diff; -} - -int git_index_entry_icmp(const void *a, const void *b) -{ - int diff; - const git_index_entry *entry_a = a; - const git_index_entry *entry_b = b; - - diff = strcasecmp(entry_a->path, entry_b->path); - - if (diff == 0) - diff = (GIT_INDEX_ENTRY_STAGE(entry_a) - GIT_INDEX_ENTRY_STAGE(entry_b)); - - return diff; -} - -static int conflict_name_cmp(const void *a, const void *b) -{ - const git_index_name_entry *name_a = a; - const git_index_name_entry *name_b = b; - - if (name_a->ancestor && !name_b->ancestor) - return 1; - - if (!name_a->ancestor && name_b->ancestor) - return -1; - - if (name_a->ancestor) - return strcmp(name_a->ancestor, name_b->ancestor); - - if (!name_a->ours || !name_b->ours) - return 0; - - return strcmp(name_a->ours, name_b->ours); -} - -/** - * TODO: enable this when resolving case insensitive conflicts - */ -#if 0 -static int conflict_name_icmp(const void *a, const void *b) -{ - const git_index_name_entry *name_a = a; - const git_index_name_entry *name_b = b; - - if (name_a->ancestor && !name_b->ancestor) - return 1; - - if (!name_a->ancestor && name_b->ancestor) - return -1; - - if (name_a->ancestor) - return strcasecmp(name_a->ancestor, name_b->ancestor); - - if (!name_a->ours || !name_b->ours) - return 0; - - return strcasecmp(name_a->ours, name_b->ours); -} -#endif - -static int reuc_srch(const void *key, const void *array_member) -{ - const git_index_reuc_entry *reuc = array_member; - - return strcmp(key, reuc->path); -} - -static int reuc_isrch(const void *key, const void *array_member) -{ - const git_index_reuc_entry *reuc = array_member; - - return strcasecmp(key, reuc->path); -} - -static int reuc_cmp(const void *a, const void *b) -{ - const git_index_reuc_entry *info_a = a; - const git_index_reuc_entry *info_b = b; - - return strcmp(info_a->path, info_b->path); -} - -static int reuc_icmp(const void *a, const void *b) -{ - const git_index_reuc_entry *info_a = a; - const git_index_reuc_entry *info_b = b; - - return strcasecmp(info_a->path, info_b->path); -} - -static void index_entry_reuc_free(git_index_reuc_entry *reuc) -{ - git__free(reuc); -} - -static void index_entry_free(git_index_entry *entry) -{ - if (!entry) - return; - - memset(&entry->id, 0, sizeof(entry->id)); - git__free(entry); -} - -unsigned int git_index__create_mode(unsigned int mode) -{ - if (S_ISLNK(mode)) - return S_IFLNK; - - if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR)) - return (S_IFLNK | S_IFDIR); - - return S_IFREG | GIT_PERMS_CANONICAL(mode); -} - -static unsigned int index_merge_mode( - git_index *index, git_index_entry *existing, unsigned int mode) -{ - if (index->no_symlinks && S_ISREG(mode) && - existing && S_ISLNK(existing->mode)) - return existing->mode; - - if (index->distrust_filemode && S_ISREG(mode)) - return (existing && S_ISREG(existing->mode)) ? - existing->mode : git_index__create_mode(0666); - - return git_index__create_mode(mode); -} - -GIT_INLINE(int) index_find_in_entries( - size_t *out, git_vector *entries, git_vector_cmp entry_srch, - const char *path, size_t path_len, int stage) -{ - struct entry_srch_key srch_key; - srch_key.path = path; - srch_key.pathlen = !path_len ? strlen(path) : path_len; - srch_key.stage = stage; - return git_vector_bsearch2(out, entries, entry_srch, &srch_key); -} - -GIT_INLINE(int) index_find( - size_t *out, git_index *index, - const char *path, size_t path_len, int stage) -{ - git_vector_sort(&index->entries); - - return index_find_in_entries( - out, &index->entries, index->entries_search, path, path_len, stage); -} - -void git_index__set_ignore_case(git_index *index, bool ignore_case) -{ - index->ignore_case = ignore_case; - - if (ignore_case) { - index->entries_cmp_path = git__strcasecmp_cb; - index->entries_search = git_index_entry_isrch; - index->entries_search_path = index_entry_isrch_path; - index->reuc_search = reuc_isrch; - } else { - index->entries_cmp_path = git__strcmp_cb; - index->entries_search = git_index_entry_srch; - index->entries_search_path = index_entry_srch_path; - index->reuc_search = reuc_srch; - } - - git_vector_set_cmp(&index->entries, - ignore_case ? git_index_entry_icmp : git_index_entry_cmp); - git_vector_sort(&index->entries); - - git_vector_set_cmp(&index->reuc, ignore_case ? reuc_icmp : reuc_cmp); - git_vector_sort(&index->reuc); -} - -int git_index_open(git_index **index_out, const char *index_path) -{ - git_index *index; - int error = -1; - - GIT_ASSERT_ARG(index_out); - - index = git__calloc(1, sizeof(git_index)); - GIT_ERROR_CHECK_ALLOC(index); - - if (git_pool_init(&index->tree_pool, 1) < 0) - goto fail; - - if (index_path != NULL) { - index->index_file_path = git__strdup(index_path); - if (!index->index_file_path) - goto fail; - - /* Check if index file is stored on disk already */ - if (git_fs_path_exists(index->index_file_path) == true) - index->on_disk = 1; - } - - if (git_vector_init(&index->entries, 32, git_index_entry_cmp) < 0 || - git_idxmap_new(&index->entries_map) < 0 || - git_vector_init(&index->names, 8, conflict_name_cmp) < 0 || - git_vector_init(&index->reuc, 8, reuc_cmp) < 0 || - git_vector_init(&index->deleted, 8, git_index_entry_cmp) < 0) - goto fail; - - index->entries_cmp_path = git__strcmp_cb; - index->entries_search = git_index_entry_srch; - index->entries_search_path = index_entry_srch_path; - index->reuc_search = reuc_srch; - index->version = INDEX_VERSION_NUMBER_DEFAULT; - - if (index_path != NULL && (error = git_index_read(index, true)) < 0) - goto fail; - - *index_out = index; - GIT_REFCOUNT_INC(index); - - return 0; - -fail: - git_pool_clear(&index->tree_pool); - git_index_free(index); - return error; -} - -int git_index_new(git_index **out) -{ - return git_index_open(out, NULL); -} - -static void index_free(git_index *index) -{ - /* index iterators increment the refcount of the index, so if we - * get here then there should be no outstanding iterators. - */ - if (git_atomic32_get(&index->readers)) - return; - - git_index_clear(index); - git_idxmap_free(index->entries_map); - git_vector_free(&index->entries); - git_vector_free(&index->names); - git_vector_free(&index->reuc); - git_vector_free(&index->deleted); - - git__free(index->index_file_path); - - git__memzero(index, sizeof(*index)); - git__free(index); -} - -void git_index_free(git_index *index) -{ - if (index == NULL) - return; - - GIT_REFCOUNT_DEC(index, index_free); -} - -/* call with locked index */ -static void index_free_deleted(git_index *index) -{ - int readers = (int)git_atomic32_get(&index->readers); - size_t i; - - if (readers > 0 || !index->deleted.length) - return; - - for (i = 0; i < index->deleted.length; ++i) { - git_index_entry *ie = git_atomic_swap(index->deleted.contents[i], NULL); - index_entry_free(ie); - } - - git_vector_clear(&index->deleted); -} - -/* call with locked index */ -static int index_remove_entry(git_index *index, size_t pos) -{ - int error = 0; - git_index_entry *entry = git_vector_get(&index->entries, pos); - - if (entry != NULL) { - git_tree_cache_invalidate_path(index->tree, entry->path); - index_map_delete(index->entries_map, entry, index->ignore_case); - } - - error = git_vector_remove(&index->entries, pos); - - if (!error) { - if (git_atomic32_get(&index->readers) > 0) { - error = git_vector_insert(&index->deleted, entry); - } else { - index_entry_free(entry); - } - - index->dirty = 1; - } - - return error; -} - -int git_index_clear(git_index *index) -{ - int error = 0; - - GIT_ASSERT_ARG(index); - - index->dirty = 1; - index->tree = NULL; - git_pool_clear(&index->tree_pool); - - git_idxmap_clear(index->entries_map); - while (!error && index->entries.length > 0) - error = index_remove_entry(index, index->entries.length - 1); - - if (error) - goto done; - - index_free_deleted(index); - - if ((error = git_index_name_clear(index)) < 0 || - (error = git_index_reuc_clear(index)) < 0) - goto done; - - git_futils_filestamp_set(&index->stamp, NULL); - -done: - return error; -} - -static int create_index_error(int error, const char *msg) -{ - git_error_set_str(GIT_ERROR_INDEX, msg); - return error; -} - -int git_index_set_caps(git_index *index, int caps) -{ - unsigned int old_ignore_case; - - GIT_ASSERT_ARG(index); - - old_ignore_case = index->ignore_case; - - if (caps == GIT_INDEX_CAPABILITY_FROM_OWNER) { - git_repository *repo = INDEX_OWNER(index); - int val; - - if (!repo) - return create_index_error( - -1, "cannot access repository to set index caps"); - - if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_IGNORECASE)) - index->ignore_case = (val != 0); - if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_FILEMODE)) - index->distrust_filemode = (val == 0); - if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_SYMLINKS)) - index->no_symlinks = (val == 0); - } - else { - index->ignore_case = ((caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0); - index->distrust_filemode = ((caps & GIT_INDEX_CAPABILITY_NO_FILEMODE) != 0); - index->no_symlinks = ((caps & GIT_INDEX_CAPABILITY_NO_SYMLINKS) != 0); - } - - if (old_ignore_case != index->ignore_case) { - git_index__set_ignore_case(index, (bool)index->ignore_case); - } - - return 0; -} - -int git_index_caps(const git_index *index) -{ - return ((index->ignore_case ? GIT_INDEX_CAPABILITY_IGNORE_CASE : 0) | - (index->distrust_filemode ? GIT_INDEX_CAPABILITY_NO_FILEMODE : 0) | - (index->no_symlinks ? GIT_INDEX_CAPABILITY_NO_SYMLINKS : 0)); -} - -#ifndef GIT_DEPRECATE_HARD -const git_oid *git_index_checksum(git_index *index) -{ - return (git_oid *)index->checksum; -} -#endif - -/** - * Returns 1 for changed, 0 for not changed and <0 for errors - */ -static int compare_checksum(git_index *index) -{ - int fd; - ssize_t bytes_read; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - - if ((fd = p_open(index->index_file_path, O_RDONLY)) < 0) - return fd; - - if (p_lseek(fd, (0 - (ssize_t)checksum_size), SEEK_END) < 0) { - p_close(fd); - git_error_set(GIT_ERROR_OS, "failed to seek to end of file"); - return -1; - } - - bytes_read = p_read(fd, checksum, checksum_size); - p_close(fd); - - if (bytes_read < (ssize_t)checksum_size) - return -1; - - return !!memcmp(checksum, index->checksum, checksum_size); -} - -int git_index_read(git_index *index, int force) -{ - int error = 0, updated; - git_str buffer = GIT_STR_INIT; - git_futils_filestamp stamp = index->stamp; - - if (!index->index_file_path) - return create_index_error(-1, - "failed to read index: The index is in-memory only"); - - index->on_disk = git_fs_path_exists(index->index_file_path); - - if (!index->on_disk) { - if (force && (error = git_index_clear(index)) < 0) - return error; - - index->dirty = 0; - return 0; - } - - if ((updated = git_futils_filestamp_check(&stamp, index->index_file_path) < 0) || - ((updated = compare_checksum(index)) < 0)) { - git_error_set( - GIT_ERROR_INDEX, - "failed to read index: '%s' no longer exists", - index->index_file_path); - return updated; - } - - if (!updated && !force) - return 0; - - error = git_futils_readbuffer(&buffer, index->index_file_path); - if (error < 0) - return error; - - index->tree = NULL; - git_pool_clear(&index->tree_pool); - - error = git_index_clear(index); - - if (!error) - error = parse_index(index, buffer.ptr, buffer.size); - - if (!error) { - git_futils_filestamp_set(&index->stamp, &stamp); - index->dirty = 0; - } - - git_str_dispose(&buffer); - return error; -} - -int git_index_read_safely(git_index *index) -{ - if (git_index__enforce_unsaved_safety && index->dirty) { - git_error_set(GIT_ERROR_INDEX, - "the index has unsaved changes that would be overwritten by this operation"); - return GIT_EINDEXDIRTY; - } - - return git_index_read(index, false); -} - -static bool is_racy_entry(git_index *index, const git_index_entry *entry) -{ - /* Git special-cases submodules in the check */ - if (S_ISGITLINK(entry->mode)) - return false; - - return git_index_entry_newer_than_index(entry, index); -} - -/* - * Force the next diff to take a look at those entries which have the - * same timestamp as the current index. - */ -static int truncate_racily_clean(git_index *index) -{ - size_t i; - int error; - git_index_entry *entry; - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_vector paths = GIT_VECTOR_INIT; - git_diff_delta *delta; - - /* Nothing to do if there's no repo to talk about */ - if (!INDEX_OWNER(index)) - return 0; - - /* If there's no workdir, we can't know where to even check */ - if (!git_repository_workdir(INDEX_OWNER(index))) - return 0; - - diff_opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_DISABLE_PATHSPEC_MATCH; - git_vector_foreach(&index->entries, i, entry) { - if ((entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE) == 0 && - is_racy_entry(index, entry)) - git_vector_insert(&paths, (char *)entry->path); - } - - if (paths.length == 0) - goto done; - - diff_opts.pathspec.count = paths.length; - diff_opts.pathspec.strings = (char **)paths.contents; - - if ((error = git_diff_index_to_workdir(&diff, INDEX_OWNER(index), index, &diff_opts)) < 0) - return error; - - git_vector_foreach(&diff->deltas, i, delta) { - entry = (git_index_entry *)git_index_get_bypath(index, delta->old_file.path, 0); - - /* Ensure that we have a stage 0 for this file (ie, it's not a - * conflict), otherwise smudging it is quite pointless. - */ - if (entry) { - entry->file_size = 0; - index->dirty = 1; - } - } - -done: - git_diff_free(diff); - git_vector_free(&paths); - return 0; -} - -unsigned git_index_version(git_index *index) -{ - GIT_ASSERT_ARG(index); - - return index->version; -} - -int git_index_set_version(git_index *index, unsigned int version) -{ - GIT_ASSERT_ARG(index); - - if (version < INDEX_VERSION_NUMBER_LB || - version > INDEX_VERSION_NUMBER_UB) { - git_error_set(GIT_ERROR_INDEX, "invalid version number"); - return -1; - } - - index->version = version; - - return 0; -} - -int git_index_write(git_index *index) -{ - git_indexwriter writer = GIT_INDEXWRITER_INIT; - int error; - - truncate_racily_clean(index); - - if ((error = git_indexwriter_init(&writer, index)) == 0 && - (error = git_indexwriter_commit(&writer)) == 0) - index->dirty = 0; - - git_indexwriter_cleanup(&writer); - - return error; -} - -const char *git_index_path(const git_index *index) -{ - GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); - return index->index_file_path; -} - -int git_index_write_tree(git_oid *oid, git_index *index) -{ - git_repository *repo; - - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(index); - - repo = INDEX_OWNER(index); - - if (repo == NULL) - return create_index_error(-1, "Failed to write tree. " - "the index file is not backed up by an existing repository"); - - return git_tree__write_index(oid, index, repo); -} - -int git_index_write_tree_to( - git_oid *oid, git_index *index, git_repository *repo) -{ - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(repo); - - return git_tree__write_index(oid, index, repo); -} - -size_t git_index_entrycount(const git_index *index) -{ - GIT_ASSERT_ARG(index); - - return index->entries.length; -} - -const git_index_entry *git_index_get_byindex( - git_index *index, size_t n) -{ - GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); - - git_vector_sort(&index->entries); - return git_vector_get(&index->entries, n); -} - -const git_index_entry *git_index_get_bypath( - git_index *index, const char *path, int stage) -{ - git_index_entry key = {{ 0 }}; - git_index_entry *value; - - GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); - - key.path = path; - GIT_INDEX_ENTRY_STAGE_SET(&key, stage); - - if (index->ignore_case) - value = git_idxmap_icase_get((git_idxmap_icase *) index->entries_map, &key); - else - value = git_idxmap_get(index->entries_map, &key); - - if (!value) { - git_error_set(GIT_ERROR_INDEX, "index does not contain '%s'", path); - return NULL; - } - - return value; -} - -void git_index_entry__init_from_stat( - git_index_entry *entry, struct stat *st, bool trust_mode) -{ - entry->ctime.seconds = (int32_t)st->st_ctime; - entry->mtime.seconds = (int32_t)st->st_mtime; -#if defined(GIT_USE_NSEC) - entry->mtime.nanoseconds = st->st_mtime_nsec; - entry->ctime.nanoseconds = st->st_ctime_nsec; -#endif - entry->dev = st->st_rdev; - entry->ino = st->st_ino; - entry->mode = (!trust_mode && S_ISREG(st->st_mode)) ? - git_index__create_mode(0666) : git_index__create_mode(st->st_mode); - entry->uid = st->st_uid; - entry->gid = st->st_gid; - entry->file_size = (uint32_t)st->st_size; -} - -static void index_entry_adjust_namemask( - git_index_entry *entry, - size_t path_length) -{ - entry->flags &= ~GIT_INDEX_ENTRY_NAMEMASK; - - if (path_length < GIT_INDEX_ENTRY_NAMEMASK) - entry->flags |= path_length & GIT_INDEX_ENTRY_NAMEMASK; - else - entry->flags |= GIT_INDEX_ENTRY_NAMEMASK; -} - -/* When `from_workdir` is true, we will validate the paths to avoid placing - * paths that are invalid for the working directory on the current filesystem - * (eg, on Windows, we will disallow `GIT~1`, `AUX`, `COM1`, etc). This - * function will *always* prevent `.git` and directory traversal `../` from - * being added to the index. - */ -static int index_entry_create( - git_index_entry **out, - git_repository *repo, - const char *path, - struct stat *st, - bool from_workdir) -{ - size_t pathlen = strlen(path), alloclen; - struct entry_internal *entry; - unsigned int path_valid_flags = GIT_PATH_REJECT_INDEX_DEFAULTS; - uint16_t mode = 0; - - /* always reject placing `.git` in the index and directory traversal. - * when requested, disallow platform-specific filenames and upgrade to - * the platform-specific `.git` tests (eg, `git~1`, etc). - */ - if (from_workdir) - path_valid_flags |= GIT_PATH_REJECT_WORKDIR_DEFAULTS; - if (st) - mode = st->st_mode; - - if (!git_path_is_valid(repo, path, mode, path_valid_flags)) { - git_error_set(GIT_ERROR_INDEX, "invalid path: '%s'", path); - return -1; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(struct entry_internal), pathlen); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - entry = git__calloc(1, alloclen); - GIT_ERROR_CHECK_ALLOC(entry); - - entry->pathlen = pathlen; - memcpy(entry->path, path, pathlen); - entry->entry.path = entry->path; - - *out = (git_index_entry *)entry; - return 0; -} - -static int index_entry_init( - git_index_entry **entry_out, - git_index *index, - const char *rel_path) -{ - int error = 0; - git_index_entry *entry = NULL; - git_str path = GIT_STR_INIT; - struct stat st; - git_oid oid; - git_repository *repo; - - if (INDEX_OWNER(index) == NULL) - return create_index_error(-1, - "could not initialize index entry. " - "Index is not backed up by an existing repository."); - - /* - * FIXME: this is duplicated with the work in - * git_blob__create_from_paths. It should accept an optional stat - * structure so we can pass in the one we have to do here. - */ - repo = INDEX_OWNER(index); - if (git_repository__ensure_not_bare(repo, "create blob from file") < 0) - return GIT_EBAREREPO; - - if (git_repository_workdir_path(&path, repo, rel_path) < 0) - return -1; - - error = git_fs_path_lstat(path.ptr, &st); - git_str_dispose(&path); - - if (error < 0) - return error; - - if (index_entry_create(&entry, INDEX_OWNER(index), rel_path, &st, true) < 0) - return -1; - - /* write the blob to disk and get the oid and stat info */ - error = git_blob__create_from_paths( - &oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true); - - if (error < 0) { - index_entry_free(entry); - return error; - } - - entry->id = oid; - git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode); - - *entry_out = (git_index_entry *)entry; - return 0; -} - -static git_index_reuc_entry *reuc_entry_alloc(const char *path) -{ - size_t pathlen = strlen(path), - structlen = sizeof(struct reuc_entry_internal), - alloclen; - struct reuc_entry_internal *entry; - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, structlen, pathlen) || - GIT_ADD_SIZET_OVERFLOW(&alloclen, alloclen, 1)) - return NULL; - - entry = git__calloc(1, alloclen); - if (!entry) - return NULL; - - entry->pathlen = pathlen; - memcpy(entry->path, path, pathlen); - entry->entry.path = entry->path; - - return (git_index_reuc_entry *)entry; -} - -static int index_entry_reuc_init(git_index_reuc_entry **reuc_out, - const char *path, - int ancestor_mode, const git_oid *ancestor_oid, - int our_mode, const git_oid *our_oid, - int their_mode, const git_oid *their_oid) -{ - git_index_reuc_entry *reuc = NULL; - - GIT_ASSERT_ARG(reuc_out); - GIT_ASSERT_ARG(path); - - *reuc_out = reuc = reuc_entry_alloc(path); - GIT_ERROR_CHECK_ALLOC(reuc); - - if ((reuc->mode[0] = ancestor_mode) > 0) { - GIT_ASSERT(ancestor_oid); - git_oid_cpy(&reuc->oid[0], ancestor_oid); - } - - if ((reuc->mode[1] = our_mode) > 0) { - GIT_ASSERT(our_oid); - git_oid_cpy(&reuc->oid[1], our_oid); - } - - if ((reuc->mode[2] = their_mode) > 0) { - GIT_ASSERT(their_oid); - git_oid_cpy(&reuc->oid[2], their_oid); - } - - return 0; -} - -static void index_entry_cpy( - git_index_entry *tgt, - const git_index_entry *src) -{ - const char *tgt_path = tgt->path; - memcpy(tgt, src, sizeof(*tgt)); - tgt->path = tgt_path; -} - -static int index_entry_dup( - git_index_entry **out, - git_index *index, - const git_index_entry *src) -{ - if (index_entry_create(out, INDEX_OWNER(index), src->path, NULL, false) < 0) - return -1; - - index_entry_cpy(*out, src); - return 0; -} - -static void index_entry_cpy_nocache( - git_index_entry *tgt, - const git_index_entry *src) -{ - git_oid_cpy(&tgt->id, &src->id); - tgt->mode = src->mode; - tgt->flags = src->flags; - tgt->flags_extended = (src->flags_extended & GIT_INDEX_ENTRY_EXTENDED_FLAGS); -} - -static int index_entry_dup_nocache( - git_index_entry **out, - git_index *index, - const git_index_entry *src) -{ - if (index_entry_create(out, INDEX_OWNER(index), src->path, NULL, false) < 0) - return -1; - - index_entry_cpy_nocache(*out, src); - return 0; -} - -static int has_file_name(git_index *index, - const git_index_entry *entry, size_t pos, int ok_to_replace) -{ - size_t len = strlen(entry->path); - int stage = GIT_INDEX_ENTRY_STAGE(entry); - const char *name = entry->path; - - while (pos < index->entries.length) { - struct entry_internal *p = index->entries.contents[pos++]; - - if (len >= p->pathlen) - break; - if (memcmp(name, p->path, len)) - break; - if (GIT_INDEX_ENTRY_STAGE(&p->entry) != stage) - continue; - if (p->path[len] != '/') - continue; - if (!ok_to_replace) - return -1; - - if (index_remove_entry(index, --pos) < 0) - break; - } - return 0; -} - -/* - * Do we have another file with a pathname that is a proper - * subset of the name we're trying to add? - */ -static int has_dir_name(git_index *index, - const git_index_entry *entry, int ok_to_replace) -{ - int stage = GIT_INDEX_ENTRY_STAGE(entry); - const char *name = entry->path; - const char *slash = name + strlen(name); - - for (;;) { - size_t len, pos; - - for (;;) { - if (*--slash == '/') - break; - if (slash <= entry->path) - return 0; - } - len = slash - name; - - if (!index_find(&pos, index, name, len, stage)) { - if (!ok_to_replace) - return -1; - - if (index_remove_entry(index, pos) < 0) - break; - continue; - } - - /* - * Trivial optimization: if we find an entry that - * already matches the sub-directory, then we know - * we're ok, and we can exit. - */ - for (; pos < index->entries.length; ++pos) { - struct entry_internal *p = index->entries.contents[pos]; - - if (p->pathlen <= len || - p->path[len] != '/' || - memcmp(p->path, name, len)) - break; /* not our subdirectory */ - - if (GIT_INDEX_ENTRY_STAGE(&p->entry) == stage) - return 0; - } - } - - return 0; -} - -static int check_file_directory_collision(git_index *index, - git_index_entry *entry, size_t pos, int ok_to_replace) -{ - if (has_file_name(index, entry, pos, ok_to_replace) < 0 || - has_dir_name(index, entry, ok_to_replace) < 0) { - git_error_set(GIT_ERROR_INDEX, - "'%s' appears as both a file and a directory", entry->path); - return -1; - } - - return 0; -} - -static int canonicalize_directory_path( - git_index *index, - git_index_entry *entry, - git_index_entry *existing) -{ - const git_index_entry *match, *best = NULL; - char *search, *sep; - size_t pos, search_len, best_len; - - if (!index->ignore_case) - return 0; - - /* item already exists in the index, simply re-use the existing case */ - if (existing) { - memcpy((char *)entry->path, existing->path, strlen(existing->path)); - return 0; - } - - /* nothing to do */ - if (strchr(entry->path, '/') == NULL) - return 0; - - if ((search = git__strdup(entry->path)) == NULL) - return -1; - - /* starting at the parent directory and descending to the root, find the - * common parent directory. - */ - while (!best && (sep = strrchr(search, '/'))) { - sep[1] = '\0'; - - search_len = strlen(search); - - git_vector_bsearch2( - &pos, &index->entries, index->entries_search_path, search); - - while ((match = git_vector_get(&index->entries, pos))) { - if (GIT_INDEX_ENTRY_STAGE(match) != 0) { - /* conflicts do not contribute to canonical paths */ - } else if (strncmp(search, match->path, search_len) == 0) { - /* prefer an exact match to the input filename */ - best = match; - best_len = search_len; - break; - } else if (strncasecmp(search, match->path, search_len) == 0) { - /* continue walking, there may be a path with an exact - * (case sensitive) match later in the index, but use this - * as the best match until that happens. - */ - if (!best) { - best = match; - best_len = search_len; - } - } else { - break; - } - - pos++; - } - - sep[0] = '\0'; - } - - if (best) - memcpy((char *)entry->path, best->path, best_len); - - git__free(search); - return 0; -} - -static int index_no_dups(void **old, void *new) -{ - const git_index_entry *entry = new; - GIT_UNUSED(old); - git_error_set(GIT_ERROR_INDEX, "'%s' appears multiple times at stage %d", - entry->path, GIT_INDEX_ENTRY_STAGE(entry)); - return GIT_EEXISTS; -} - -static void index_existing_and_best( - git_index_entry **existing, - size_t *existing_position, - git_index_entry **best, - git_index *index, - const git_index_entry *entry) -{ - git_index_entry *e; - size_t pos; - int error; - - error = index_find(&pos, - index, entry->path, 0, GIT_INDEX_ENTRY_STAGE(entry)); - - if (error == 0) { - *existing = index->entries.contents[pos]; - *existing_position = pos; - *best = index->entries.contents[pos]; - return; - } - - *existing = NULL; - *existing_position = 0; - *best = NULL; - - if (GIT_INDEX_ENTRY_STAGE(entry) == 0) { - for (; pos < index->entries.length; pos++) { - int (*strcomp)(const char *a, const char *b) = - index->ignore_case ? git__strcasecmp : git__strcmp; - - e = index->entries.contents[pos]; - - if (strcomp(entry->path, e->path) != 0) - break; - - if (GIT_INDEX_ENTRY_STAGE(e) == GIT_INDEX_STAGE_ANCESTOR) { - *best = e; - continue; - } else { - *best = e; - break; - } - } - } -} - -/* index_insert takes ownership of the new entry - if it can't insert - * it, then it will return an error **and also free the entry**. When - * it replaces an existing entry, it will update the entry_ptr with the - * actual entry in the index (and free the passed in one). - * - * trust_path is whether we use the given path, or whether (on case - * insensitive systems only) we try to canonicalize the given path to - * be within an existing directory. - * - * trust_mode is whether we trust the mode in entry_ptr. - * - * trust_id is whether we trust the id or it should be validated. - */ -static int index_insert( - git_index *index, - git_index_entry **entry_ptr, - int replace, - bool trust_path, - bool trust_mode, - bool trust_id) -{ - git_index_entry *existing, *best, *entry; - size_t path_length, position; - int error; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(entry_ptr); - - entry = *entry_ptr; - - /* Make sure that the path length flag is correct */ - path_length = ((struct entry_internal *)entry)->pathlen; - index_entry_adjust_namemask(entry, path_length); - - /* This entry is now up-to-date and should not be checked for raciness */ - entry->flags_extended |= GIT_INDEX_ENTRY_UPTODATE; - - git_vector_sort(&index->entries); - - /* - * Look if an entry with this path already exists, either staged, or (if - * this entry is a regular staged item) as the "ours" side of a conflict. - */ - index_existing_and_best(&existing, &position, &best, index, entry); - - /* Update the file mode */ - entry->mode = trust_mode ? - git_index__create_mode(entry->mode) : - index_merge_mode(index, best, entry->mode); - - /* Canonicalize the directory name */ - if (!trust_path && (error = canonicalize_directory_path(index, entry, best)) < 0) - goto out; - - /* Ensure that the given id exists (unless it's a submodule) */ - if (!trust_id && INDEX_OWNER(index) && - (entry->mode & GIT_FILEMODE_COMMIT) != GIT_FILEMODE_COMMIT) { - - if (!git_object__is_valid(INDEX_OWNER(index), &entry->id, - git_object__type_from_filemode(entry->mode))) { - error = -1; - goto out; - } - } - - /* Look for tree / blob name collisions, removing conflicts if requested */ - if ((error = check_file_directory_collision(index, entry, position, replace)) < 0) - goto out; - - /* - * If we are replacing an existing item, overwrite the existing entry - * and return it in place of the passed in one. - */ - if (existing) { - if (replace) { - index_entry_cpy(existing, entry); - - if (trust_path) - memcpy((char *)existing->path, entry->path, strlen(entry->path)); - } - - index_entry_free(entry); - *entry_ptr = existing; - } else { - /* - * If replace is not requested or no existing entry exists, insert - * at the sorted position. (Since we re-sort after each insert to - * check for dups, this is actually cheaper in the long run.) - */ - if ((error = git_vector_insert_sorted(&index->entries, entry, index_no_dups)) < 0 || - (error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) - goto out; - } - - index->dirty = 1; - -out: - if (error < 0) { - index_entry_free(*entry_ptr); - *entry_ptr = NULL; - } - - return error; -} - -static int index_conflict_to_reuc(git_index *index, const char *path) -{ - const git_index_entry *conflict_entries[3]; - int ancestor_mode, our_mode, their_mode; - git_oid const *ancestor_oid, *our_oid, *their_oid; - int ret; - - if ((ret = git_index_conflict_get(&conflict_entries[0], - &conflict_entries[1], &conflict_entries[2], index, path)) < 0) - return ret; - - ancestor_mode = conflict_entries[0] == NULL ? 0 : conflict_entries[0]->mode; - our_mode = conflict_entries[1] == NULL ? 0 : conflict_entries[1]->mode; - their_mode = conflict_entries[2] == NULL ? 0 : conflict_entries[2]->mode; - - ancestor_oid = conflict_entries[0] == NULL ? NULL : &conflict_entries[0]->id; - our_oid = conflict_entries[1] == NULL ? NULL : &conflict_entries[1]->id; - their_oid = conflict_entries[2] == NULL ? NULL : &conflict_entries[2]->id; - - if ((ret = git_index_reuc_add(index, path, ancestor_mode, ancestor_oid, - our_mode, our_oid, their_mode, their_oid)) >= 0) - ret = git_index_conflict_remove(index, path); - - return ret; -} - -GIT_INLINE(bool) is_file_or_link(const int filemode) -{ - return (filemode == GIT_FILEMODE_BLOB || - filemode == GIT_FILEMODE_BLOB_EXECUTABLE || - filemode == GIT_FILEMODE_LINK); -} - -GIT_INLINE(bool) valid_filemode(const int filemode) -{ - return (is_file_or_link(filemode) || filemode == GIT_FILEMODE_COMMIT); -} - -int git_index_add_from_buffer( - git_index *index, const git_index_entry *source_entry, - const void *buffer, size_t len) -{ - git_index_entry *entry = NULL; - int error = 0; - git_oid id; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(source_entry && source_entry->path); - - if (INDEX_OWNER(index) == NULL) - return create_index_error(-1, - "could not initialize index entry. " - "Index is not backed up by an existing repository."); - - if (!is_file_or_link(source_entry->mode)) { - git_error_set(GIT_ERROR_INDEX, "invalid filemode"); - return -1; - } - - if (len > UINT32_MAX) { - git_error_set(GIT_ERROR_INDEX, "buffer is too large"); - return -1; - } - - if (index_entry_dup(&entry, index, source_entry) < 0) - return -1; - - error = git_blob_create_from_buffer(&id, INDEX_OWNER(index), buffer, len); - if (error < 0) { - index_entry_free(entry); - return error; - } - - git_oid_cpy(&entry->id, &id); - entry->file_size = (uint32_t)len; - - if ((error = index_insert(index, &entry, 1, true, true, true)) < 0) - return error; - - /* Adding implies conflict was resolved, move conflict entries to REUC */ - if ((error = index_conflict_to_reuc(index, entry->path)) < 0 && error != GIT_ENOTFOUND) - return error; - - git_tree_cache_invalidate_path(index->tree, entry->path); - return 0; -} - -static int add_repo_as_submodule(git_index_entry **out, git_index *index, const char *path) -{ - git_repository *sub; - git_str abspath = GIT_STR_INIT; - git_repository *repo = INDEX_OWNER(index); - git_reference *head; - git_index_entry *entry; - struct stat st; - int error; - - if ((error = git_repository_workdir_path(&abspath, repo, path)) < 0) - return error; - - if ((error = p_stat(abspath.ptr, &st)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to stat repository dir"); - return -1; - } - - if (index_entry_create(&entry, INDEX_OWNER(index), path, &st, true) < 0) - return -1; - - git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode); - - if ((error = git_repository_open(&sub, abspath.ptr)) < 0) - return error; - - if ((error = git_repository_head(&head, sub)) < 0) - return error; - - git_oid_cpy(&entry->id, git_reference_target(head)); - entry->mode = GIT_FILEMODE_COMMIT; - - git_reference_free(head); - git_repository_free(sub); - git_str_dispose(&abspath); - - *out = entry; - return 0; -} - -int git_index_add_bypath(git_index *index, const char *path) -{ - git_index_entry *entry = NULL; - int ret; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - - if ((ret = index_entry_init(&entry, index, path)) == 0) - ret = index_insert(index, &entry, 1, false, false, true); - - /* If we were given a directory, let's see if it's a submodule */ - if (ret < 0 && ret != GIT_EDIRECTORY) - return ret; - - if (ret == GIT_EDIRECTORY) { - git_submodule *sm; - git_error_state err; - - git_error_state_capture(&err, ret); - - ret = git_submodule_lookup(&sm, INDEX_OWNER(index), path); - if (ret == GIT_ENOTFOUND) - return git_error_state_restore(&err); - - git_error_state_free(&err); - - /* - * EEXISTS means that there is a repository at that path, but it's not known - * as a submodule. We add its HEAD as an entry and don't register it. - */ - if (ret == GIT_EEXISTS) { - if ((ret = add_repo_as_submodule(&entry, index, path)) < 0) - return ret; - - if ((ret = index_insert(index, &entry, 1, false, false, true)) < 0) - return ret; - } else if (ret < 0) { - return ret; - } else { - ret = git_submodule_add_to_index(sm, false); - git_submodule_free(sm); - return ret; - } - } - - /* Adding implies conflict was resolved, move conflict entries to REUC */ - if ((ret = index_conflict_to_reuc(index, path)) < 0 && ret != GIT_ENOTFOUND) - return ret; - - git_tree_cache_invalidate_path(index->tree, entry->path); - return 0; -} - -int git_index_remove_bypath(git_index *index, const char *path) -{ - int ret; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - - if (((ret = git_index_remove(index, path, 0)) < 0 && - ret != GIT_ENOTFOUND) || - ((ret = index_conflict_to_reuc(index, path)) < 0 && - ret != GIT_ENOTFOUND)) - return ret; - - if (ret == GIT_ENOTFOUND) - git_error_clear(); - - return 0; -} - -int git_index__fill(git_index *index, const git_vector *source_entries) -{ - const git_index_entry *source_entry = NULL; - int error = 0; - size_t i; - - GIT_ASSERT_ARG(index); - - if (!source_entries->length) - return 0; - - if (git_vector_size_hint(&index->entries, source_entries->length) < 0 || - index_map_resize(index->entries_map, (size_t)(source_entries->length * 1.3), - index->ignore_case) < 0) - return -1; - - git_vector_foreach(source_entries, i, source_entry) { - git_index_entry *entry = NULL; - - if ((error = index_entry_dup(&entry, index, source_entry)) < 0) - break; - - index_entry_adjust_namemask(entry, ((struct entry_internal *)entry)->pathlen); - entry->flags_extended |= GIT_INDEX_ENTRY_UPTODATE; - entry->mode = git_index__create_mode(entry->mode); - - if ((error = git_vector_insert(&index->entries, entry)) < 0) - break; - - if ((error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) - break; - - index->dirty = 1; - } - - if (!error) - git_vector_sort(&index->entries); - - return error; -} - - -int git_index_add(git_index *index, const git_index_entry *source_entry) -{ - git_index_entry *entry = NULL; - int ret; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(source_entry && source_entry->path); - - if (!valid_filemode(source_entry->mode)) { - git_error_set(GIT_ERROR_INDEX, "invalid entry mode"); - return -1; - } - - if ((ret = index_entry_dup(&entry, index, source_entry)) < 0 || - (ret = index_insert(index, &entry, 1, true, true, false)) < 0) - return ret; - - git_tree_cache_invalidate_path(index->tree, entry->path); - return 0; -} - -int git_index_remove(git_index *index, const char *path, int stage) -{ - int error; - size_t position; - git_index_entry remove_key = {{ 0 }}; - - remove_key.path = path; - GIT_INDEX_ENTRY_STAGE_SET(&remove_key, stage); - - index_map_delete(index->entries_map, &remove_key, index->ignore_case); - - if (index_find(&position, index, path, 0, stage) < 0) { - git_error_set( - GIT_ERROR_INDEX, "index does not contain %s at stage %d", path, stage); - error = GIT_ENOTFOUND; - } else { - error = index_remove_entry(index, position); - } - - return error; -} - -int git_index_remove_directory(git_index *index, const char *dir, int stage) -{ - git_str pfx = GIT_STR_INIT; - int error = 0; - size_t pos; - git_index_entry *entry; - - if (!(error = git_str_sets(&pfx, dir)) && - !(error = git_fs_path_to_dir(&pfx))) - index_find(&pos, index, pfx.ptr, pfx.size, GIT_INDEX_STAGE_ANY); - - while (!error) { - entry = git_vector_get(&index->entries, pos); - if (!entry || git__prefixcmp(entry->path, pfx.ptr) != 0) - break; - - if (GIT_INDEX_ENTRY_STAGE(entry) != stage) { - ++pos; - continue; - } - - error = index_remove_entry(index, pos); - - /* removed entry at 'pos' so we don't need to increment */ - } - - git_str_dispose(&pfx); - - return error; -} - -int git_index_find_prefix(size_t *at_pos, git_index *index, const char *prefix) -{ - int error = 0; - size_t pos; - const git_index_entry *entry; - - index_find(&pos, index, prefix, strlen(prefix), GIT_INDEX_STAGE_ANY); - entry = git_vector_get(&index->entries, pos); - if (!entry || git__prefixcmp(entry->path, prefix) != 0) - error = GIT_ENOTFOUND; - - if (!error && at_pos) - *at_pos = pos; - - return error; -} - -int git_index__find_pos( - size_t *out, git_index *index, const char *path, size_t path_len, int stage) -{ - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - return index_find(out, index, path, path_len, stage); -} - -int git_index_find(size_t *at_pos, git_index *index, const char *path) -{ - size_t pos; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - - if (git_vector_bsearch2( - &pos, &index->entries, index->entries_search_path, path) < 0) { - git_error_set(GIT_ERROR_INDEX, "index does not contain %s", path); - return GIT_ENOTFOUND; - } - - /* Since our binary search only looked at path, we may be in the - * middle of a list of stages. - */ - for (; pos > 0; --pos) { - const git_index_entry *prev = git_vector_get(&index->entries, pos - 1); - - if (index->entries_cmp_path(prev->path, path) != 0) - break; - } - - if (at_pos) - *at_pos = pos; - - return 0; -} - -int git_index_conflict_add(git_index *index, - const git_index_entry *ancestor_entry, - const git_index_entry *our_entry, - const git_index_entry *their_entry) -{ - git_index_entry *entries[3] = { 0 }; - unsigned short i; - int ret = 0; - - GIT_ASSERT_ARG(index); - - if ((ancestor_entry && - (ret = index_entry_dup(&entries[0], index, ancestor_entry)) < 0) || - (our_entry && - (ret = index_entry_dup(&entries[1], index, our_entry)) < 0) || - (their_entry && - (ret = index_entry_dup(&entries[2], index, their_entry)) < 0)) - goto on_error; - - /* Validate entries */ - for (i = 0; i < 3; i++) { - if (entries[i] && !valid_filemode(entries[i]->mode)) { - git_error_set(GIT_ERROR_INDEX, "invalid filemode for stage %d entry", - i + 1); - ret = -1; - goto on_error; - } - } - - /* Remove existing index entries for each path */ - for (i = 0; i < 3; i++) { - if (entries[i] == NULL) - continue; - - if ((ret = git_index_remove(index, entries[i]->path, 0)) != 0) { - if (ret != GIT_ENOTFOUND) - goto on_error; - - git_error_clear(); - ret = 0; - } - } - - /* Add the conflict entries */ - for (i = 0; i < 3; i++) { - if (entries[i] == NULL) - continue; - - /* Make sure stage is correct */ - GIT_INDEX_ENTRY_STAGE_SET(entries[i], i + 1); - - if ((ret = index_insert(index, &entries[i], 1, true, true, false)) < 0) - goto on_error; - - entries[i] = NULL; /* don't free if later entry fails */ - } - - return 0; - -on_error: - for (i = 0; i < 3; i++) { - if (entries[i] != NULL) - index_entry_free(entries[i]); - } - - return ret; -} - -static int index_conflict__get_byindex( - const git_index_entry **ancestor_out, - const git_index_entry **our_out, - const git_index_entry **their_out, - git_index *index, - size_t n) -{ - const git_index_entry *conflict_entry; - const char *path = NULL; - size_t count; - int stage, len = 0; - - GIT_ASSERT_ARG(ancestor_out); - GIT_ASSERT_ARG(our_out); - GIT_ASSERT_ARG(their_out); - GIT_ASSERT_ARG(index); - - *ancestor_out = NULL; - *our_out = NULL; - *their_out = NULL; - - for (count = git_index_entrycount(index); n < count; ++n) { - conflict_entry = git_vector_get(&index->entries, n); - - if (path && index->entries_cmp_path(conflict_entry->path, path) != 0) - break; - - stage = GIT_INDEX_ENTRY_STAGE(conflict_entry); - path = conflict_entry->path; - - switch (stage) { - case 3: - *their_out = conflict_entry; - len++; - break; - case 2: - *our_out = conflict_entry; - len++; - break; - case 1: - *ancestor_out = conflict_entry; - len++; - break; - default: - break; - }; - } - - return len; -} - -int git_index_conflict_get( - const git_index_entry **ancestor_out, - const git_index_entry **our_out, - const git_index_entry **their_out, - git_index *index, - const char *path) -{ - size_t pos; - int len = 0; - - GIT_ASSERT_ARG(ancestor_out); - GIT_ASSERT_ARG(our_out); - GIT_ASSERT_ARG(their_out); - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - - *ancestor_out = NULL; - *our_out = NULL; - *their_out = NULL; - - if (git_index_find(&pos, index, path) < 0) - return GIT_ENOTFOUND; - - if ((len = index_conflict__get_byindex( - ancestor_out, our_out, their_out, index, pos)) < 0) - return len; - else if (len == 0) - return GIT_ENOTFOUND; - - return 0; -} - -static int index_conflict_remove(git_index *index, const char *path) -{ - size_t pos = 0; - git_index_entry *conflict_entry; - int error = 0; - - if (path != NULL && git_index_find(&pos, index, path) < 0) - return GIT_ENOTFOUND; - - while ((conflict_entry = git_vector_get(&index->entries, pos)) != NULL) { - - if (path != NULL && - index->entries_cmp_path(conflict_entry->path, path) != 0) - break; - - if (GIT_INDEX_ENTRY_STAGE(conflict_entry) == 0) { - pos++; - continue; - } - - if ((error = index_remove_entry(index, pos)) < 0) - break; - } - - return error; -} - -int git_index_conflict_remove(git_index *index, const char *path) -{ - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - return index_conflict_remove(index, path); -} - -int git_index_conflict_cleanup(git_index *index) -{ - GIT_ASSERT_ARG(index); - return index_conflict_remove(index, NULL); -} - -int git_index_has_conflicts(const git_index *index) -{ - size_t i; - git_index_entry *entry; - - GIT_ASSERT_ARG(index); - - git_vector_foreach(&index->entries, i, entry) { - if (GIT_INDEX_ENTRY_STAGE(entry) > 0) - return 1; - } - - return 0; -} - -int git_index_iterator_new( - git_index_iterator **iterator_out, - git_index *index) -{ - git_index_iterator *it; - int error; - - GIT_ASSERT_ARG(iterator_out); - GIT_ASSERT_ARG(index); - - it = git__calloc(1, sizeof(git_index_iterator)); - GIT_ERROR_CHECK_ALLOC(it); - - if ((error = git_index_snapshot_new(&it->snap, index)) < 0) { - git__free(it); - return error; - } - - it->index = index; - - *iterator_out = it; - return 0; -} - -int git_index_iterator_next( - const git_index_entry **out, - git_index_iterator *it) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(it); - - if (it->cur >= git_vector_length(&it->snap)) - return GIT_ITEROVER; - - *out = (git_index_entry *)git_vector_get(&it->snap, it->cur++); - return 0; -} - -void git_index_iterator_free(git_index_iterator *it) -{ - if (it == NULL) - return; - - git_index_snapshot_release(&it->snap, it->index); - git__free(it); -} - -int git_index_conflict_iterator_new( - git_index_conflict_iterator **iterator_out, - git_index *index) -{ - git_index_conflict_iterator *it = NULL; - - GIT_ASSERT_ARG(iterator_out); - GIT_ASSERT_ARG(index); - - it = git__calloc(1, sizeof(git_index_conflict_iterator)); - GIT_ERROR_CHECK_ALLOC(it); - - it->index = index; - - *iterator_out = it; - return 0; -} - -int git_index_conflict_next( - const git_index_entry **ancestor_out, - const git_index_entry **our_out, - const git_index_entry **their_out, - git_index_conflict_iterator *iterator) -{ - const git_index_entry *entry; - int len; - - GIT_ASSERT_ARG(ancestor_out); - GIT_ASSERT_ARG(our_out); - GIT_ASSERT_ARG(their_out); - GIT_ASSERT_ARG(iterator); - - *ancestor_out = NULL; - *our_out = NULL; - *their_out = NULL; - - while (iterator->cur < iterator->index->entries.length) { - entry = git_index_get_byindex(iterator->index, iterator->cur); - - if (git_index_entry_is_conflict(entry)) { - if ((len = index_conflict__get_byindex( - ancestor_out, - our_out, - their_out, - iterator->index, - iterator->cur)) < 0) - return len; - - iterator->cur += len; - return 0; - } - - iterator->cur++; - } - - return GIT_ITEROVER; -} - -void git_index_conflict_iterator_free(git_index_conflict_iterator *iterator) -{ - if (iterator == NULL) - return; - - git__free(iterator); -} - -size_t git_index_name_entrycount(git_index *index) -{ - GIT_ASSERT_ARG(index); - return index->names.length; -} - -const git_index_name_entry *git_index_name_get_byindex( - git_index *index, size_t n) -{ - GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); - - git_vector_sort(&index->names); - return git_vector_get(&index->names, n); -} - -static void index_name_entry_free(git_index_name_entry *ne) -{ - if (!ne) - return; - git__free(ne->ancestor); - git__free(ne->ours); - git__free(ne->theirs); - git__free(ne); -} - -int git_index_name_add(git_index *index, - const char *ancestor, const char *ours, const char *theirs) -{ - git_index_name_entry *conflict_name; - - GIT_ASSERT_ARG((ancestor && ours) || (ancestor && theirs) || (ours && theirs)); - - conflict_name = git__calloc(1, sizeof(git_index_name_entry)); - GIT_ERROR_CHECK_ALLOC(conflict_name); - - if ((ancestor && !(conflict_name->ancestor = git__strdup(ancestor))) || - (ours && !(conflict_name->ours = git__strdup(ours))) || - (theirs && !(conflict_name->theirs = git__strdup(theirs))) || - git_vector_insert(&index->names, conflict_name) < 0) - { - index_name_entry_free(conflict_name); - return -1; - } - - index->dirty = 1; - return 0; -} - -int git_index_name_clear(git_index *index) -{ - size_t i; - git_index_name_entry *conflict_name; - - GIT_ASSERT_ARG(index); - - git_vector_foreach(&index->names, i, conflict_name) - index_name_entry_free(conflict_name); - - git_vector_clear(&index->names); - - index->dirty = 1; - - return 0; -} - -size_t git_index_reuc_entrycount(git_index *index) -{ - GIT_ASSERT_ARG(index); - return index->reuc.length; -} - -static int index_reuc_on_dup(void **old, void *new) -{ - index_entry_reuc_free(*old); - *old = new; - return GIT_EEXISTS; -} - -static int index_reuc_insert( - git_index *index, - git_index_reuc_entry *reuc) -{ - int res; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(reuc && reuc->path != NULL); - GIT_ASSERT(git_vector_is_sorted(&index->reuc)); - - res = git_vector_insert_sorted(&index->reuc, reuc, &index_reuc_on_dup); - index->dirty = 1; - - return res == GIT_EEXISTS ? 0 : res; -} - -int git_index_reuc_add(git_index *index, const char *path, - int ancestor_mode, const git_oid *ancestor_oid, - int our_mode, const git_oid *our_oid, - int their_mode, const git_oid *their_oid) -{ - git_index_reuc_entry *reuc = NULL; - int error = 0; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(path); - - if ((error = index_entry_reuc_init(&reuc, path, ancestor_mode, - ancestor_oid, our_mode, our_oid, their_mode, their_oid)) < 0 || - (error = index_reuc_insert(index, reuc)) < 0) - index_entry_reuc_free(reuc); - - return error; -} - -int git_index_reuc_find(size_t *at_pos, git_index *index, const char *path) -{ - return git_vector_bsearch2(at_pos, &index->reuc, index->reuc_search, path); -} - -const git_index_reuc_entry *git_index_reuc_get_bypath( - git_index *index, const char *path) -{ - size_t pos; - - GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(path, NULL); - - if (!index->reuc.length) - return NULL; - - GIT_ASSERT_WITH_RETVAL(git_vector_is_sorted(&index->reuc), NULL); - - if (git_index_reuc_find(&pos, index, path) < 0) - return NULL; - - return git_vector_get(&index->reuc, pos); -} - -const git_index_reuc_entry *git_index_reuc_get_byindex( - git_index *index, size_t n) -{ - GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); - GIT_ASSERT_WITH_RETVAL(git_vector_is_sorted(&index->reuc), NULL); - - return git_vector_get(&index->reuc, n); -} - -int git_index_reuc_remove(git_index *index, size_t position) -{ - int error; - git_index_reuc_entry *reuc; - - GIT_ASSERT_ARG(index); - GIT_ASSERT(git_vector_is_sorted(&index->reuc)); - - reuc = git_vector_get(&index->reuc, position); - error = git_vector_remove(&index->reuc, position); - - if (!error) - index_entry_reuc_free(reuc); - - index->dirty = 1; - return error; -} - -int git_index_reuc_clear(git_index *index) -{ - size_t i; - - GIT_ASSERT_ARG(index); - - for (i = 0; i < index->reuc.length; ++i) - index_entry_reuc_free(git_atomic_swap(index->reuc.contents[i], NULL)); - - git_vector_clear(&index->reuc); - - index->dirty = 1; - - return 0; -} - -static int index_error_invalid(const char *message) -{ - git_error_set(GIT_ERROR_INDEX, "invalid data in index - %s", message); - return -1; -} - -static int read_reuc(git_index *index, const char *buffer, size_t size) -{ - const char *endptr; - size_t len; - int i; - - /* If called multiple times, the vector might already be initialized */ - if (index->reuc._alloc_size == 0 && - git_vector_init(&index->reuc, 16, reuc_cmp) < 0) - return -1; - - while (size) { - git_index_reuc_entry *lost; - - len = p_strnlen(buffer, size) + 1; - if (size <= len) - return index_error_invalid("reading reuc entries"); - - lost = reuc_entry_alloc(buffer); - GIT_ERROR_CHECK_ALLOC(lost); - - size -= len; - buffer += len; - - /* read 3 ASCII octal numbers for stage entries */ - for (i = 0; i < 3; i++) { - int64_t tmp; - - if (git__strntol64(&tmp, buffer, size, &endptr, 8) < 0 || - !endptr || endptr == buffer || *endptr || - tmp < 0 || tmp > UINT32_MAX) { - index_entry_reuc_free(lost); - return index_error_invalid("reading reuc entry stage"); - } - - lost->mode[i] = (uint32_t)tmp; - - len = (endptr + 1) - buffer; - if (size <= len) { - index_entry_reuc_free(lost); - return index_error_invalid("reading reuc entry stage"); - } - - size -= len; - buffer += len; - } - - /* read up to 3 OIDs for stage entries */ - for (i = 0; i < 3; i++) { - if (!lost->mode[i]) - continue; - if (size < 20) { - index_entry_reuc_free(lost); - return index_error_invalid("reading reuc entry oid"); - } - - git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer); - size -= 20; - buffer += 20; - } - - /* entry was read successfully - insert into reuc vector */ - if (git_vector_insert(&index->reuc, lost) < 0) - return -1; - } - - /* entries are guaranteed to be sorted on-disk */ - git_vector_set_sorted(&index->reuc, true); - - return 0; -} - - -static int read_conflict_names(git_index *index, const char *buffer, size_t size) -{ - size_t len; - - /* This gets called multiple times, the vector might already be initialized */ - if (index->names._alloc_size == 0 && - git_vector_init(&index->names, 16, conflict_name_cmp) < 0) - return -1; - -#define read_conflict_name(ptr) \ - len = p_strnlen(buffer, size) + 1; \ - if (size < len) { \ - index_error_invalid("reading conflict name entries"); \ - goto out_err; \ - } \ - if (len == 1) \ - ptr = NULL; \ - else { \ - ptr = git__malloc(len); \ - GIT_ERROR_CHECK_ALLOC(ptr); \ - memcpy(ptr, buffer, len); \ - } \ - \ - buffer += len; \ - size -= len; - - while (size) { - git_index_name_entry *conflict_name = git__calloc(1, sizeof(git_index_name_entry)); - GIT_ERROR_CHECK_ALLOC(conflict_name); - - read_conflict_name(conflict_name->ancestor); - read_conflict_name(conflict_name->ours); - read_conflict_name(conflict_name->theirs); - - if (git_vector_insert(&index->names, conflict_name) < 0) - goto out_err; - - continue; - -out_err: - git__free(conflict_name->ancestor); - git__free(conflict_name->ours); - git__free(conflict_name->theirs); - git__free(conflict_name); - return -1; - } - -#undef read_conflict_name - - /* entries are guaranteed to be sorted on-disk */ - git_vector_set_sorted(&index->names, true); - - return 0; -} - -static size_t index_entry_size(size_t path_len, size_t varint_len, uint32_t flags) -{ - if (varint_len) { - if (flags & GIT_INDEX_ENTRY_EXTENDED) - return offsetof(struct entry_long, path) + path_len + 1 + varint_len; - else - return offsetof(struct entry_short, path) + path_len + 1 + varint_len; - } else { -#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7) - if (flags & GIT_INDEX_ENTRY_EXTENDED) - return entry_size(struct entry_long, path_len); - else - return entry_size(struct entry_short, path_len); -#undef entry_size - } -} - -static int read_entry( - git_index_entry **out, - size_t *out_size, - git_index *index, - const void *buffer, - size_t buffer_size, - const char *last) -{ - size_t path_length, entry_size; - const char *path_ptr; - struct entry_short source; - git_index_entry entry = {{0}}; - bool compressed = index->version >= INDEX_VERSION_NUMBER_COMP; - char *tmp_path = NULL; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - - if (checksum_size + minimal_entry_size > buffer_size) - return -1; - - /* buffer is not guaranteed to be aligned */ - memcpy(&source, buffer, sizeof(struct entry_short)); - - entry.ctime.seconds = (git_time_t)ntohl(source.ctime.seconds); - entry.ctime.nanoseconds = ntohl(source.ctime.nanoseconds); - entry.mtime.seconds = (git_time_t)ntohl(source.mtime.seconds); - entry.mtime.nanoseconds = ntohl(source.mtime.nanoseconds); - entry.dev = ntohl(source.dev); - entry.ino = ntohl(source.ino); - entry.mode = ntohl(source.mode); - entry.uid = ntohl(source.uid); - entry.gid = ntohl(source.gid); - entry.file_size = ntohl(source.file_size); - git_oid_cpy(&entry.id, &source.oid); - entry.flags = ntohs(source.flags); - - if (entry.flags & GIT_INDEX_ENTRY_EXTENDED) { - uint16_t flags_raw; - size_t flags_offset; - - flags_offset = offsetof(struct entry_long, flags_extended); - memcpy(&flags_raw, (const char *) buffer + flags_offset, - sizeof(flags_raw)); - flags_raw = ntohs(flags_raw); - - memcpy(&entry.flags_extended, &flags_raw, sizeof(flags_raw)); - path_ptr = (const char *) buffer + offsetof(struct entry_long, path); - } else - path_ptr = (const char *) buffer + offsetof(struct entry_short, path); - - if (!compressed) { - path_length = entry.flags & GIT_INDEX_ENTRY_NAMEMASK; - - /* if this is a very long string, we must find its - * real length without overflowing */ - if (path_length == 0xFFF) { - const char *path_end; - - path_end = memchr(path_ptr, '\0', buffer_size); - if (path_end == NULL) - return -1; - - path_length = path_end - path_ptr; - } - - entry_size = index_entry_size(path_length, 0, entry.flags); - entry.path = (char *)path_ptr; - } else { - size_t varint_len, last_len, prefix_len, suffix_len, path_len; - uintmax_t strip_len; - - strip_len = git_decode_varint((const unsigned char *)path_ptr, &varint_len); - last_len = strlen(last); - - if (varint_len == 0 || last_len < strip_len) - return index_error_invalid("incorrect prefix length"); - - prefix_len = last_len - (size_t)strip_len; - suffix_len = strlen(path_ptr + varint_len); - - GIT_ERROR_CHECK_ALLOC_ADD(&path_len, prefix_len, suffix_len); - GIT_ERROR_CHECK_ALLOC_ADD(&path_len, path_len, 1); - - if (path_len > GIT_PATH_MAX) - return index_error_invalid("unreasonable path length"); - - tmp_path = git__malloc(path_len); - GIT_ERROR_CHECK_ALLOC(tmp_path); - - memcpy(tmp_path, last, prefix_len); - memcpy(tmp_path + prefix_len, path_ptr + varint_len, suffix_len + 1); - entry_size = index_entry_size(suffix_len, varint_len, entry.flags); - entry.path = tmp_path; - } - - if (entry_size == 0) - return -1; - - if (checksum_size + entry_size > buffer_size) - return -1; - - if (index_entry_dup(out, index, &entry) < 0) { - git__free(tmp_path); - return -1; - } - - git__free(tmp_path); - *out_size = entry_size; - return 0; -} - -static int read_header(struct index_header *dest, const void *buffer) -{ - const struct index_header *source = buffer; - - dest->signature = ntohl(source->signature); - if (dest->signature != INDEX_HEADER_SIG) - return index_error_invalid("incorrect header signature"); - - dest->version = ntohl(source->version); - if (dest->version < INDEX_VERSION_NUMBER_LB || - dest->version > INDEX_VERSION_NUMBER_UB) - return index_error_invalid("incorrect header version"); - - dest->entry_count = ntohl(source->entry_count); - return 0; -} - -static int read_extension(size_t *read_len, git_index *index, const char *buffer, size_t buffer_size) -{ - struct index_extension dest; - size_t total_size; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - - /* buffer is not guaranteed to be aligned */ - memcpy(&dest, buffer, sizeof(struct index_extension)); - dest.extension_size = ntohl(dest.extension_size); - - total_size = dest.extension_size + sizeof(struct index_extension); - - if (dest.extension_size > total_size || - buffer_size < total_size || - buffer_size - total_size < checksum_size) { - index_error_invalid("extension is truncated"); - return -1; - } - - /* optional extension */ - if (dest.signature[0] >= 'A' && dest.signature[0] <= 'Z') { - /* tree cache */ - if (memcmp(dest.signature, INDEX_EXT_TREECACHE_SIG, 4) == 0) { - if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size, &index->tree_pool) < 0) - return -1; - } else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) { - if (read_reuc(index, buffer + 8, dest.extension_size) < 0) - return -1; - } else if (memcmp(dest.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4) == 0) { - if (read_conflict_names(index, buffer + 8, dest.extension_size) < 0) - return -1; - } - /* else, unsupported extension. We cannot parse this, but we can skip - * it by returning `total_size */ - } else { - /* we cannot handle non-ignorable extensions; - * in fact they aren't even defined in the standard */ - git_error_set(GIT_ERROR_INDEX, "unsupported mandatory extension: '%.4s'", dest.signature); - return -1; - } - - *read_len = total_size; - - return 0; -} - -static int parse_index(git_index *index, const char *buffer, size_t buffer_size) -{ - int error = 0; - unsigned int i; - struct index_header header = { 0 }; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - const char *last = NULL; - const char *empty = ""; - -#define seek_forward(_increase) { \ - if (_increase >= buffer_size) { \ - error = index_error_invalid("ran out of data while parsing"); \ - goto done; } \ - buffer += _increase; \ - buffer_size -= _increase;\ -} - - if (buffer_size < INDEX_HEADER_SIZE + checksum_size) - return index_error_invalid("insufficient buffer space"); - - /* Precalculate the SHA1 of the files's contents -- we'll match it to - * the provided SHA1 in the footer */ - git_hash_buf(checksum, buffer, buffer_size - checksum_size, GIT_HASH_ALGORITHM_SHA1); - - /* Parse header */ - if ((error = read_header(&header, buffer)) < 0) - return error; - - index->version = header.version; - if (index->version >= INDEX_VERSION_NUMBER_COMP) - last = empty; - - seek_forward(INDEX_HEADER_SIZE); - - GIT_ASSERT(!index->entries.length); - - if ((error = index_map_resize(index->entries_map, header.entry_count, index->ignore_case)) < 0) - return error; - - /* Parse all the entries */ - for (i = 0; i < header.entry_count && buffer_size > checksum_size; ++i) { - git_index_entry *entry = NULL; - size_t entry_size; - - if ((error = read_entry(&entry, &entry_size, index, buffer, buffer_size, last)) < 0) { - error = index_error_invalid("invalid entry"); - goto done; - } - - if ((error = git_vector_insert(&index->entries, entry)) < 0) { - index_entry_free(entry); - goto done; - } - - if ((error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) { - index_entry_free(entry); - goto done; - } - error = 0; - - if (index->version >= INDEX_VERSION_NUMBER_COMP) - last = entry->path; - - seek_forward(entry_size); - } - - if (i != header.entry_count) { - error = index_error_invalid("header entries changed while parsing"); - goto done; - } - - /* There's still space for some extensions! */ - while (buffer_size > checksum_size) { - size_t extension_size; - - if ((error = read_extension(&extension_size, index, buffer, buffer_size)) < 0) { - goto done; - } - - seek_forward(extension_size); - } - - if (buffer_size != checksum_size) { - error = index_error_invalid( - "buffer size does not match index footer size"); - goto done; - } - - /* 160-bit SHA-1 over the content of the index file before this checksum. */ - if (memcmp(checksum, buffer, checksum_size) != 0) { - error = index_error_invalid( - "calculated checksum does not match expected"); - goto done; - } - - memcpy(index->checksum, checksum, checksum_size); - -#undef seek_forward - - /* Entries are stored case-sensitively on disk, so re-sort now if - * in-memory index is supposed to be case-insensitive - */ - git_vector_set_sorted(&index->entries, !index->ignore_case); - git_vector_sort(&index->entries); - - index->dirty = 0; -done: - return error; -} - -static bool is_index_extended(git_index *index) -{ - size_t i, extended; - git_index_entry *entry; - - extended = 0; - - git_vector_foreach(&index->entries, i, entry) { - entry->flags &= ~GIT_INDEX_ENTRY_EXTENDED; - if (entry->flags_extended & GIT_INDEX_ENTRY_EXTENDED_FLAGS) { - extended++; - entry->flags |= GIT_INDEX_ENTRY_EXTENDED; - } - } - - return (extended > 0); -} - -static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const char *last) -{ - void *mem = NULL; - struct entry_short ondisk; - size_t path_len, disk_size; - int varint_len = 0; - char *path; - const char *path_start = entry->path; - size_t same_len = 0; - - path_len = ((struct entry_internal *)entry)->pathlen; - - if (last) { - const char *last_c = last; - - while (*path_start == *last_c) { - if (!*path_start || !*last_c) - break; - ++path_start; - ++last_c; - ++same_len; - } - path_len -= same_len; - varint_len = git_encode_varint(NULL, 0, strlen(last) - same_len); - } - - disk_size = index_entry_size(path_len, varint_len, entry->flags); - - if (git_filebuf_reserve(file, &mem, disk_size) < 0) - return -1; - - memset(mem, 0x0, disk_size); - - /** - * Yes, we have to truncate. - * - * The on-disk format for Index entries clearly defines - * the time and size fields to be 4 bytes each -- so even if - * we store these values with 8 bytes on-memory, they must - * be truncated to 4 bytes before writing to disk. - * - * In 2038 I will be either too dead or too rich to care about this - */ - ondisk.ctime.seconds = htonl((uint32_t)entry->ctime.seconds); - ondisk.mtime.seconds = htonl((uint32_t)entry->mtime.seconds); - ondisk.ctime.nanoseconds = htonl(entry->ctime.nanoseconds); - ondisk.mtime.nanoseconds = htonl(entry->mtime.nanoseconds); - ondisk.dev = htonl(entry->dev); - ondisk.ino = htonl(entry->ino); - ondisk.mode = htonl(entry->mode); - ondisk.uid = htonl(entry->uid); - ondisk.gid = htonl(entry->gid); - ondisk.file_size = htonl((uint32_t)entry->file_size); - - git_oid_cpy(&ondisk.oid, &entry->id); - - ondisk.flags = htons(entry->flags); - - if (entry->flags & GIT_INDEX_ENTRY_EXTENDED) { - const size_t path_offset = offsetof(struct entry_long, path); - struct entry_long ondisk_ext; - memcpy(&ondisk_ext, &ondisk, sizeof(struct entry_short)); - ondisk_ext.flags_extended = htons(entry->flags_extended & - GIT_INDEX_ENTRY_EXTENDED_FLAGS); - memcpy(mem, &ondisk_ext, path_offset); - path = (char *)mem + path_offset; - disk_size -= path_offset; - } else { - const size_t path_offset = offsetof(struct entry_short, path); - memcpy(mem, &ondisk, path_offset); - path = (char *)mem + path_offset; - disk_size -= path_offset; - } - - if (last) { - varint_len = git_encode_varint((unsigned char *) path, - disk_size, strlen(last) - same_len); - GIT_ASSERT(varint_len > 0); - - path += varint_len; - disk_size -= varint_len; - - /* - * If using path compression, we are not allowed - * to have additional trailing NULs. - */ - GIT_ASSERT(disk_size == path_len + 1); - } else { - /* - * If no path compression is used, we do have - * NULs as padding. As such, simply assert that - * we have enough space left to write the path. - */ - GIT_ASSERT(disk_size > path_len); - } - - memcpy(path, path_start, path_len + 1); - - return 0; -} - -static int write_entries(git_index *index, git_filebuf *file) -{ - int error = 0; - size_t i; - git_vector case_sorted = GIT_VECTOR_INIT, *entries = NULL; - git_index_entry *entry; - const char *last = NULL; - - /* If index->entries is sorted case-insensitively, then we need - * to re-sort it case-sensitively before writing */ - if (index->ignore_case) { - if ((error = git_vector_dup(&case_sorted, &index->entries, git_index_entry_cmp)) < 0) - goto done; - - git_vector_sort(&case_sorted); - entries = &case_sorted; - } else { - entries = &index->entries; - } - - if (index->version >= INDEX_VERSION_NUMBER_COMP) - last = ""; - - git_vector_foreach(entries, i, entry) { - if ((error = write_disk_entry(file, entry, last)) < 0) - break; - if (index->version >= INDEX_VERSION_NUMBER_COMP) - last = entry->path; - } - -done: - git_vector_free(&case_sorted); - return error; -} - -static int write_extension(git_filebuf *file, struct index_extension *header, git_str *data) -{ - struct index_extension ondisk; - - memset(&ondisk, 0x0, sizeof(struct index_extension)); - memcpy(&ondisk, header, 4); - ondisk.extension_size = htonl(header->extension_size); - - git_filebuf_write(file, &ondisk, sizeof(struct index_extension)); - return git_filebuf_write(file, data->ptr, data->size); -} - -static int create_name_extension_data(git_str *name_buf, git_index_name_entry *conflict_name) -{ - int error = 0; - - if (conflict_name->ancestor == NULL) - error = git_str_put(name_buf, "\0", 1); - else - error = git_str_put(name_buf, conflict_name->ancestor, strlen(conflict_name->ancestor) + 1); - - if (error != 0) - goto on_error; - - if (conflict_name->ours == NULL) - error = git_str_put(name_buf, "\0", 1); - else - error = git_str_put(name_buf, conflict_name->ours, strlen(conflict_name->ours) + 1); - - if (error != 0) - goto on_error; - - if (conflict_name->theirs == NULL) - error = git_str_put(name_buf, "\0", 1); - else - error = git_str_put(name_buf, conflict_name->theirs, strlen(conflict_name->theirs) + 1); - -on_error: - return error; -} - -static int write_name_extension(git_index *index, git_filebuf *file) -{ - git_str name_buf = GIT_STR_INIT; - git_vector *out = &index->names; - git_index_name_entry *conflict_name; - struct index_extension extension; - size_t i; - int error = 0; - - git_vector_foreach(out, i, conflict_name) { - if ((error = create_name_extension_data(&name_buf, conflict_name)) < 0) - goto done; - } - - memset(&extension, 0x0, sizeof(struct index_extension)); - memcpy(&extension.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4); - extension.extension_size = (uint32_t)name_buf.size; - - error = write_extension(file, &extension, &name_buf); - - git_str_dispose(&name_buf); - -done: - return error; -} - -static int create_reuc_extension_data(git_str *reuc_buf, git_index_reuc_entry *reuc) -{ - int i; - int error = 0; - - if ((error = git_str_put(reuc_buf, reuc->path, strlen(reuc->path) + 1)) < 0) - return error; - - for (i = 0; i < 3; i++) { - if ((error = git_str_printf(reuc_buf, "%o", reuc->mode[i])) < 0 || - (error = git_str_put(reuc_buf, "\0", 1)) < 0) - return error; - } - - for (i = 0; i < 3; i++) { - if (reuc->mode[i] && (error = git_str_put(reuc_buf, (char *)&reuc->oid[i].id, GIT_OID_RAWSZ)) < 0) - return error; - } - - return 0; -} - -static int write_reuc_extension(git_index *index, git_filebuf *file) -{ - git_str reuc_buf = GIT_STR_INIT; - git_vector *out = &index->reuc; - git_index_reuc_entry *reuc; - struct index_extension extension; - size_t i; - int error = 0; - - git_vector_foreach(out, i, reuc) { - if ((error = create_reuc_extension_data(&reuc_buf, reuc)) < 0) - goto done; - } - - memset(&extension, 0x0, sizeof(struct index_extension)); - memcpy(&extension.signature, INDEX_EXT_UNMERGED_SIG, 4); - extension.extension_size = (uint32_t)reuc_buf.size; - - error = write_extension(file, &extension, &reuc_buf); - - git_str_dispose(&reuc_buf); - -done: - return error; -} - -static int write_tree_extension(git_index *index, git_filebuf *file) -{ - struct index_extension extension; - git_str buf = GIT_STR_INIT; - int error; - - if (index->tree == NULL) - return 0; - - if ((error = git_tree_cache_write(&buf, index->tree)) < 0) - return error; - - memset(&extension, 0x0, sizeof(struct index_extension)); - memcpy(&extension.signature, INDEX_EXT_TREECACHE_SIG, 4); - extension.extension_size = (uint32_t)buf.size; - - error = write_extension(file, &extension, &buf); - - git_str_dispose(&buf); - - return error; -} - -static void clear_uptodate(git_index *index) -{ - git_index_entry *entry; - size_t i; - - git_vector_foreach(&index->entries, i, entry) - entry->flags_extended &= ~GIT_INDEX_ENTRY_UPTODATE; -} - -static int write_index( - unsigned char checksum[GIT_HASH_SHA1_SIZE], - size_t *checksum_size, - git_index *index, - git_filebuf *file) -{ - struct index_header header; - bool is_extended; - uint32_t index_version_number; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(file); - - *checksum_size = GIT_HASH_SHA1_SIZE; - - if (index->version <= INDEX_VERSION_NUMBER_EXT) { - is_extended = is_index_extended(index); - index_version_number = is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER_LB; - } else { - index_version_number = index->version; - } - - header.signature = htonl(INDEX_HEADER_SIG); - header.version = htonl(index_version_number); - header.entry_count = htonl((uint32_t)index->entries.length); - - if (git_filebuf_write(file, &header, sizeof(struct index_header)) < 0) - return -1; - - if (write_entries(index, file) < 0) - return -1; - - /* write the tree cache extension */ - if (index->tree != NULL && write_tree_extension(index, file) < 0) - return -1; - - /* write the rename conflict extension */ - if (index->names.length > 0 && write_name_extension(index, file) < 0) - return -1; - - /* write the reuc extension */ - if (index->reuc.length > 0 && write_reuc_extension(index, file) < 0) - return -1; - - /* get out the hash for all the contents we've appended to the file */ - git_filebuf_hash(checksum, file); - - /* write it at the end of the file */ - if (git_filebuf_write(file, checksum, *checksum_size) < 0) - return -1; - - /* file entries are no longer up to date */ - clear_uptodate(index); - - return 0; -} - -int git_index_entry_stage(const git_index_entry *entry) -{ - return GIT_INDEX_ENTRY_STAGE(entry); -} - -int git_index_entry_is_conflict(const git_index_entry *entry) -{ - return (GIT_INDEX_ENTRY_STAGE(entry) > 0); -} - -typedef struct read_tree_data { - git_index *index; - git_vector *old_entries; - git_vector *new_entries; - git_vector_cmp entry_cmp; - git_tree_cache *tree; -} read_tree_data; - -static int read_tree_cb( - const char *root, const git_tree_entry *tentry, void *payload) -{ - read_tree_data *data = payload; - git_index_entry *entry = NULL, *old_entry; - git_str path = GIT_STR_INIT; - size_t pos; - - if (git_tree_entry__is_tree(tentry)) - return 0; - - if (git_str_joinpath(&path, root, tentry->filename) < 0) - return -1; - - if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr, NULL, false) < 0) - return -1; - - entry->mode = tentry->attr; - git_oid_cpy(&entry->id, git_tree_entry_id(tentry)); - - /* look for corresponding old entry and copy data to new entry */ - if (data->old_entries != NULL && - !index_find_in_entries( - &pos, data->old_entries, data->entry_cmp, path.ptr, 0, 0) && - (old_entry = git_vector_get(data->old_entries, pos)) != NULL && - entry->mode == old_entry->mode && - git_oid_equal(&entry->id, &old_entry->id)) - { - index_entry_cpy(entry, old_entry); - entry->flags_extended = 0; - } - - index_entry_adjust_namemask(entry, path.size); - git_str_dispose(&path); - - if (git_vector_insert(data->new_entries, entry) < 0) { - index_entry_free(entry); - return -1; - } - - return 0; -} - -int git_index_read_tree(git_index *index, const git_tree *tree) -{ - int error = 0; - git_vector entries = GIT_VECTOR_INIT; - git_idxmap *entries_map; - read_tree_data data; - size_t i; - git_index_entry *e; - - if (git_idxmap_new(&entries_map) < 0) - return -1; - - git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */ - - data.index = index; - data.old_entries = &index->entries; - data.new_entries = &entries; - data.entry_cmp = index->entries_search; - - index->tree = NULL; - git_pool_clear(&index->tree_pool); - - git_vector_sort(&index->entries); - - if ((error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data)) < 0) - goto cleanup; - - if ((error = index_map_resize(entries_map, entries.length, index->ignore_case)) < 0) - goto cleanup; - - git_vector_foreach(&entries, i, e) { - if ((error = index_map_set(entries_map, e, index->ignore_case)) < 0) { - git_error_set(GIT_ERROR_INDEX, "failed to insert entry into map"); - return error; - } - } - - error = 0; - - git_vector_sort(&entries); - - if ((error = git_index_clear(index)) < 0) { - /* well, this isn't good */; - } else { - git_vector_swap(&entries, &index->entries); - entries_map = git_atomic_swap(index->entries_map, entries_map); - } - - index->dirty = 1; - -cleanup: - git_vector_free(&entries); - git_idxmap_free(entries_map); - if (error < 0) - return error; - - error = git_tree_cache_read_tree(&index->tree, tree, &index->tree_pool); - - return error; -} - -static int git_index_read_iterator( - git_index *index, - git_iterator *new_iterator, - size_t new_length_hint) -{ - git_vector new_entries = GIT_VECTOR_INIT, - remove_entries = GIT_VECTOR_INIT; - git_idxmap *new_entries_map = NULL; - git_iterator *index_iterator = NULL; - git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *old_entry, *new_entry; - git_index_entry *entry; - size_t i; - int error; - - GIT_ASSERT((new_iterator->flags & GIT_ITERATOR_DONT_IGNORE_CASE)); - - if ((error = git_vector_init(&new_entries, new_length_hint, index->entries._cmp)) < 0 || - (error = git_vector_init(&remove_entries, index->entries.length, NULL)) < 0 || - (error = git_idxmap_new(&new_entries_map)) < 0) - goto done; - - if (new_length_hint && (error = index_map_resize(new_entries_map, new_length_hint, - index->ignore_case)) < 0) - goto done; - - opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_CONFLICTS; - - if ((error = git_iterator_for_index(&index_iterator, - git_index_owner(index), index, &opts)) < 0 || - ((error = git_iterator_current(&old_entry, index_iterator)) < 0 && - error != GIT_ITEROVER) || - ((error = git_iterator_current(&new_entry, new_iterator)) < 0 && - error != GIT_ITEROVER)) - goto done; - - while (true) { - git_index_entry - *dup_entry = NULL, - *add_entry = NULL, - *remove_entry = NULL; - int diff; - - error = 0; - - if (old_entry && new_entry) - diff = git_index_entry_cmp(old_entry, new_entry); - else if (!old_entry && new_entry) - diff = 1; - else if (old_entry && !new_entry) - diff = -1; - else - break; - - if (diff < 0) { - remove_entry = (git_index_entry *)old_entry; - } else if (diff > 0) { - dup_entry = (git_index_entry *)new_entry; - } else { - /* Path and stage are equal, if the OID is equal, keep it to - * keep the stat cache data. - */ - if (git_oid_equal(&old_entry->id, &new_entry->id) && - old_entry->mode == new_entry->mode) { - add_entry = (git_index_entry *)old_entry; - } else { - dup_entry = (git_index_entry *)new_entry; - remove_entry = (git_index_entry *)old_entry; - } - } - - if (dup_entry) { - if ((error = index_entry_dup_nocache(&add_entry, index, dup_entry)) < 0) - goto done; - - index_entry_adjust_namemask(add_entry, - ((struct entry_internal *)add_entry)->pathlen); - } - - /* invalidate this path in the tree cache if this is new (to - * invalidate the parent trees) - */ - if (dup_entry && !remove_entry && index->tree) - git_tree_cache_invalidate_path(index->tree, dup_entry->path); - - if (add_entry) { - if ((error = git_vector_insert(&new_entries, add_entry)) == 0) - error = index_map_set(new_entries_map, add_entry, - index->ignore_case); - } - - if (remove_entry && error >= 0) - error = git_vector_insert(&remove_entries, remove_entry); - - if (error < 0) { - git_error_set(GIT_ERROR_INDEX, "failed to insert entry"); - goto done; - } - - if (diff <= 0) { - if ((error = git_iterator_advance(&old_entry, index_iterator)) < 0 && - error != GIT_ITEROVER) - goto done; - } - - if (diff >= 0) { - if ((error = git_iterator_advance(&new_entry, new_iterator)) < 0 && - error != GIT_ITEROVER) - goto done; - } - } - - if ((error = git_index_name_clear(index)) < 0 || - (error = git_index_reuc_clear(index)) < 0) - goto done; - - git_vector_swap(&new_entries, &index->entries); - new_entries_map = git_atomic_swap(index->entries_map, new_entries_map); - - git_vector_foreach(&remove_entries, i, entry) { - if (index->tree) - git_tree_cache_invalidate_path(index->tree, entry->path); - - index_entry_free(entry); - } - - clear_uptodate(index); - - index->dirty = 1; - error = 0; - -done: - git_idxmap_free(new_entries_map); - git_vector_free(&new_entries); - git_vector_free(&remove_entries); - git_iterator_free(index_iterator); - return error; -} - -int git_index_read_index( - git_index *index, - const git_index *new_index) -{ - git_iterator *new_iterator = NULL; - git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; - int error; - - opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_CONFLICTS; - - if ((error = git_iterator_for_index(&new_iterator, - git_index_owner(new_index), (git_index *)new_index, &opts)) < 0 || - (error = git_index_read_iterator(index, new_iterator, - new_index->entries.length)) < 0) - goto done; - -done: - git_iterator_free(new_iterator); - return error; -} - -git_repository *git_index_owner(const git_index *index) -{ - return INDEX_OWNER(index); -} - -enum { - INDEX_ACTION_NONE = 0, - INDEX_ACTION_UPDATE = 1, - INDEX_ACTION_REMOVE = 2, - INDEX_ACTION_ADDALL = 3 -}; - -int git_index_add_all( - git_index *index, - const git_strarray *paths, - unsigned int flags, - git_index_matched_path_cb cb, - void *payload) -{ - int error; - git_repository *repo; - git_iterator *wditer = NULL; - git_pathspec ps; - bool no_fnmatch = (flags & GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH) != 0; - - GIT_ASSERT_ARG(index); - - repo = INDEX_OWNER(index); - if ((error = git_repository__ensure_not_bare(repo, "index add all")) < 0) - return error; - - if ((error = git_pathspec__init(&ps, paths)) < 0) - return error; - - /* optionally check that pathspec doesn't mention any ignored files */ - if ((flags & GIT_INDEX_ADD_CHECK_PATHSPEC) != 0 && - (flags & GIT_INDEX_ADD_FORCE) == 0 && - (error = git_ignore__check_pathspec_for_exact_ignores( - repo, &ps.pathspec, no_fnmatch)) < 0) - goto cleanup; - - error = index_apply_to_wd_diff(index, INDEX_ACTION_ADDALL, paths, flags, cb, payload); - - if (error) - git_error_set_after_callback(error); - -cleanup: - git_iterator_free(wditer); - git_pathspec__clear(&ps); - - return error; -} - -struct foreach_diff_data { - git_index *index; - const git_pathspec *pathspec; - unsigned int flags; - git_index_matched_path_cb cb; - void *payload; -}; - -static int apply_each_file(const git_diff_delta *delta, float progress, void *payload) -{ - struct foreach_diff_data *data = payload; - const char *match, *path; - int error = 0; - - GIT_UNUSED(progress); - - path = delta->old_file.path; - - /* We only want those which match the pathspecs */ - if (!git_pathspec__match( - &data->pathspec->pathspec, path, false, (bool)data->index->ignore_case, - &match, NULL)) - return 0; - - if (data->cb) - error = data->cb(path, match, data->payload); - - if (error > 0) /* skip this entry */ - return 0; - if (error < 0) /* actual error */ - return error; - - /* If the workdir item does not exist, remove it from the index. */ - if ((delta->new_file.flags & GIT_DIFF_FLAG_EXISTS) == 0) - error = git_index_remove_bypath(data->index, path); - else - error = git_index_add_bypath(data->index, delta->new_file.path); - - return error; -} - -static int index_apply_to_wd_diff(git_index *index, int action, const git_strarray *paths, - unsigned int flags, - git_index_matched_path_cb cb, void *payload) -{ - int error; - git_diff *diff; - git_pathspec ps; - git_repository *repo; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - struct foreach_diff_data data = { - index, - NULL, - flags, - cb, - payload, - }; - - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(action == INDEX_ACTION_UPDATE || action == INDEX_ACTION_ADDALL); - - repo = INDEX_OWNER(index); - - if (!repo) { - return create_index_error(-1, - "cannot run update; the index is not backed up by a repository."); - } - - /* - * We do the matching ourselves instead of passing the list to - * diff because we want to tell the callback which one - * matched, which we do not know if we ask diff to filter for us. - */ - if ((error = git_pathspec__init(&ps, paths)) < 0) - return error; - - opts.flags = GIT_DIFF_INCLUDE_TYPECHANGE; - if (action == INDEX_ACTION_ADDALL) { - opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_RECURSE_UNTRACKED_DIRS; - - if (flags == GIT_INDEX_ADD_FORCE) - opts.flags |= GIT_DIFF_INCLUDE_IGNORED; - } - - if ((error = git_diff_index_to_workdir(&diff, repo, index, &opts)) < 0) - goto cleanup; - - data.pathspec = &ps; - error = git_diff_foreach(diff, apply_each_file, NULL, NULL, NULL, &data); - git_diff_free(diff); - - if (error) /* make sure error is set if callback stopped iteration */ - git_error_set_after_callback(error); - -cleanup: - git_pathspec__clear(&ps); - return error; -} - -static int index_apply_to_all( - git_index *index, - int action, - const git_strarray *paths, - git_index_matched_path_cb cb, - void *payload) -{ - int error = 0; - size_t i; - git_pathspec ps; - const char *match; - git_str path = GIT_STR_INIT; - - GIT_ASSERT_ARG(index); - - if ((error = git_pathspec__init(&ps, paths)) < 0) - return error; - - git_vector_sort(&index->entries); - - for (i = 0; !error && i < index->entries.length; ++i) { - git_index_entry *entry = git_vector_get(&index->entries, i); - - /* check if path actually matches */ - if (!git_pathspec__match( - &ps.pathspec, entry->path, false, (bool)index->ignore_case, - &match, NULL)) - continue; - - /* issue notification callback if requested */ - if (cb && (error = cb(entry->path, match, payload)) != 0) { - if (error > 0) { /* return > 0 means skip this one */ - error = 0; - continue; - } - if (error < 0) /* return < 0 means abort */ - break; - } - - /* index manipulation may alter entry, so don't depend on it */ - if ((error = git_str_sets(&path, entry->path)) < 0) - break; - - switch (action) { - case INDEX_ACTION_NONE: - break; - case INDEX_ACTION_UPDATE: - error = git_index_add_bypath(index, path.ptr); - - if (error == GIT_ENOTFOUND) { - git_error_clear(); - - error = git_index_remove_bypath(index, path.ptr); - - if (!error) /* back up foreach if we removed this */ - i--; - } - break; - case INDEX_ACTION_REMOVE: - if (!(error = git_index_remove_bypath(index, path.ptr))) - i--; /* back up foreach if we removed this */ - break; - default: - git_error_set(GIT_ERROR_INVALID, "unknown index action %d", action); - error = -1; - break; - } - } - - git_str_dispose(&path); - git_pathspec__clear(&ps); - - return error; -} - -int git_index_remove_all( - git_index *index, - const git_strarray *pathspec, - git_index_matched_path_cb cb, - void *payload) -{ - int error = index_apply_to_all( - index, INDEX_ACTION_REMOVE, pathspec, cb, payload); - - if (error) /* make sure error is set if callback stopped iteration */ - git_error_set_after_callback(error); - - return error; -} - -int git_index_update_all( - git_index *index, - const git_strarray *pathspec, - git_index_matched_path_cb cb, - void *payload) -{ - int error = index_apply_to_wd_diff(index, INDEX_ACTION_UPDATE, pathspec, 0, cb, payload); - if (error) /* make sure error is set if callback stopped iteration */ - git_error_set_after_callback(error); - - return error; -} - -int git_index_snapshot_new(git_vector *snap, git_index *index) -{ - int error; - - GIT_REFCOUNT_INC(index); - - git_atomic32_inc(&index->readers); - git_vector_sort(&index->entries); - - error = git_vector_dup(snap, &index->entries, index->entries._cmp); - - if (error < 0) - git_index_snapshot_release(snap, index); - - return error; -} - -void git_index_snapshot_release(git_vector *snap, git_index *index) -{ - git_vector_free(snap); - - git_atomic32_dec(&index->readers); - - git_index_free(index); -} - -int git_index_snapshot_find( - size_t *out, git_vector *entries, git_vector_cmp entry_srch, - const char *path, size_t path_len, int stage) -{ - return index_find_in_entries(out, entries, entry_srch, path, path_len, stage); -} - -int git_indexwriter_init( - git_indexwriter *writer, - git_index *index) -{ - int error; - - GIT_REFCOUNT_INC(index); - - writer->index = index; - - if (!index->index_file_path) - return create_index_error(-1, - "failed to write index: The index is in-memory only"); - - if ((error = git_filebuf_open( - &writer->file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS, GIT_INDEX_FILE_MODE)) < 0) { - - if (error == GIT_ELOCKED) - git_error_set(GIT_ERROR_INDEX, "the index is locked; this might be due to a concurrent or crashed process"); - - return error; - } - - writer->should_write = 1; - - return 0; -} - -int git_indexwriter_init_for_operation( - git_indexwriter *writer, - git_repository *repo, - unsigned int *checkout_strategy) -{ - git_index *index; - int error; - - if ((error = git_repository_index__weakptr(&index, repo)) < 0 || - (error = git_indexwriter_init(writer, index)) < 0) - return error; - - writer->should_write = (*checkout_strategy & GIT_CHECKOUT_DONT_WRITE_INDEX) == 0; - *checkout_strategy |= GIT_CHECKOUT_DONT_WRITE_INDEX; - - return 0; -} - -int git_indexwriter_commit(git_indexwriter *writer) -{ - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size; - int error; - - if (!writer->should_write) - return 0; - - git_vector_sort(&writer->index->entries); - git_vector_sort(&writer->index->reuc); - - if ((error = write_index(checksum, &checksum_size, writer->index, &writer->file)) < 0) { - git_indexwriter_cleanup(writer); - return error; - } - - if ((error = git_filebuf_commit(&writer->file)) < 0) - return error; - - if ((error = git_futils_filestamp_check( - &writer->index->stamp, writer->index->index_file_path)) < 0) { - git_error_set(GIT_ERROR_OS, "could not read index timestamp"); - return -1; - } - - writer->index->dirty = 0; - writer->index->on_disk = 1; - memcpy(writer->index->checksum, checksum, checksum_size); - - git_index_free(writer->index); - writer->index = NULL; - - return 0; -} - -void git_indexwriter_cleanup(git_indexwriter *writer) -{ - git_filebuf_cleanup(&writer->file); - - git_index_free(writer->index); - writer->index = NULL; -} - -/* Deprecated functions */ - -#ifndef GIT_DEPRECATE_HARD -int git_index_add_frombuffer( - git_index *index, const git_index_entry *source_entry, - const void *buffer, size_t len) -{ - return git_index_add_from_buffer(index, source_entry, buffer, len); -} -#endif diff --git a/src/index.h b/src/index.h deleted file mode 100644 index 71bb096f7..000000000 --- a/src/index.h +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_index_h__ -#define INCLUDE_index_h__ - -#include "common.h" - -#include "futils.h" -#include "filebuf.h" -#include "vector.h" -#include "idxmap.h" -#include "tree-cache.h" -#include "git2/odb.h" -#include "git2/index.h" - -#define GIT_INDEX_FILE "index" -#define GIT_INDEX_FILE_MODE 0666 - -extern bool git_index__enforce_unsaved_safety; - -struct git_index { - git_refcount rc; - - char *index_file_path; - git_futils_filestamp stamp; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - - git_vector entries; - git_idxmap *entries_map; - - git_vector deleted; /* deleted entries if readers > 0 */ - git_atomic32 readers; /* number of active iterators */ - - unsigned int on_disk:1; - unsigned int ignore_case:1; - unsigned int distrust_filemode:1; - unsigned int no_symlinks:1; - unsigned int dirty:1; /* whether we have unsaved changes */ - - git_tree_cache *tree; - git_pool tree_pool; - - git_vector names; - git_vector reuc; - - git_vector_cmp entries_cmp_path; - git_vector_cmp entries_search; - git_vector_cmp entries_search_path; - git_vector_cmp reuc_search; - - unsigned int version; -}; - -struct git_index_iterator { - git_index *index; - git_vector snap; - size_t cur; -}; - -struct git_index_conflict_iterator { - git_index *index; - size_t cur; -}; - -extern void git_index_entry__init_from_stat( - git_index_entry *entry, struct stat *st, bool trust_mode); - -/* Index entry comparison functions for array sorting */ -extern int git_index_entry_cmp(const void *a, const void *b); -extern int git_index_entry_icmp(const void *a, const void *b); - -/* Index entry search functions for search using a search spec */ -extern int git_index_entry_srch(const void *a, const void *b); -extern int git_index_entry_isrch(const void *a, const void *b); - -/* Index time handling functions */ -GIT_INLINE(bool) git_index_time_eq(const git_index_time *one, const git_index_time *two) -{ - if (one->seconds != two->seconds) - return false; - -#ifdef GIT_USE_NSEC - if (one->nanoseconds != two->nanoseconds) - return false; -#endif - - return true; -} - -/* - * Test if the given index time is newer than the given existing index entry. - * If the timestamps are exactly equivalent, then the given index time is - * considered "racily newer" than the existing index entry. - */ -GIT_INLINE(bool) git_index_entry_newer_than_index( - const git_index_entry *entry, git_index *index) -{ - /* If we never read the index, we can't have this race either */ - if (!index || index->stamp.mtime.tv_sec == 0) - return false; - - /* If the timestamp is the same or newer than the index, it's racy */ -#if defined(GIT_USE_NSEC) - if ((int32_t)index->stamp.mtime.tv_sec < entry->mtime.seconds) - return true; - else if ((int32_t)index->stamp.mtime.tv_sec > entry->mtime.seconds) - return false; - else - return (uint32_t)index->stamp.mtime.tv_nsec <= entry->mtime.nanoseconds; -#else - return ((int32_t)index->stamp.mtime.tv_sec) <= entry->mtime.seconds; -#endif -} - -/* Search index for `path`, returning GIT_ENOTFOUND if it does not exist - * (but not setting an error message). - * - * `at_pos` is set to the position where it is or would be inserted. - * Pass `path_len` as strlen of path or 0 to call strlen internally. - */ -extern int git_index__find_pos( - size_t *at_pos, git_index *index, const char *path, size_t path_len, int stage); - -extern int git_index__fill(git_index *index, const git_vector *source_entries); - -extern void git_index__set_ignore_case(git_index *index, bool ignore_case); - -extern unsigned int git_index__create_mode(unsigned int mode); - -GIT_INLINE(const git_futils_filestamp *) git_index__filestamp(git_index *index) -{ - return &index->stamp; -} - -GIT_INLINE(unsigned char *) git_index__checksum(git_index *index) -{ - return index->checksum; -} - -/* Copy the current entries vector *and* increment the index refcount. - * Call `git_index__release_snapshot` when done. - */ -extern int git_index_snapshot_new(git_vector *snap, git_index *index); -extern void git_index_snapshot_release(git_vector *snap, git_index *index); - -/* Allow searching in a snapshot; entries must already be sorted! */ -extern int git_index_snapshot_find( - size_t *at_pos, git_vector *snap, git_vector_cmp entry_srch, - const char *path, size_t path_len, int stage); - -/* Replace an index with a new index */ -int git_index_read_index(git_index *index, const git_index *new_index); - -GIT_INLINE(int) git_index_is_dirty(git_index *index) -{ - return index->dirty; -} - -extern int git_index_read_safely(git_index *index); - -typedef struct { - git_index *index; - git_filebuf file; - unsigned int should_write:1; -} git_indexwriter; - -#define GIT_INDEXWRITER_INIT { NULL, GIT_FILEBUF_INIT } - -/* Lock the index for eventual writing. */ -extern int git_indexwriter_init(git_indexwriter *writer, git_index *index); - -/* Lock the index for eventual writing by a repository operation: a merge, - * revert, cherry-pick or a rebase. Note that the given checkout strategy - * will be updated for the operation's use so that checkout will not write - * the index. - */ -extern int git_indexwriter_init_for_operation( - git_indexwriter *writer, - git_repository *repo, - unsigned int *checkout_strategy); - -/* Write the index and unlock it. */ -extern int git_indexwriter_commit(git_indexwriter *writer); - -/* Cleanup an index writing session, unlocking the file (if it is still - * locked and freeing any data structures. - */ -extern void git_indexwriter_cleanup(git_indexwriter *writer); - -#endif diff --git a/src/indexer.c b/src/indexer.c deleted file mode 100644 index f9a32e7ac..000000000 --- a/src/indexer.c +++ /dev/null @@ -1,1413 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "indexer.h" - -#include "git2/indexer.h" -#include "git2/object.h" - -#include "commit.h" -#include "tree.h" -#include "tag.h" -#include "pack.h" -#include "mwindow.h" -#include "posix.h" -#include "pack.h" -#include "filebuf.h" -#include "oid.h" -#include "oidarray.h" -#include "oidmap.h" -#include "zstream.h" -#include "object.h" - -size_t git_indexer__max_objects = UINT32_MAX; - -#define UINT31_MAX (0x7FFFFFFF) - -struct entry { - git_oid oid; - uint32_t crc; - uint32_t offset; - uint64_t offset_long; -}; - -struct git_indexer { - unsigned int parsed_header :1, - pack_committed :1, - have_stream :1, - have_delta :1, - do_fsync :1, - do_verify :1; - struct git_pack_header hdr; - struct git_pack_file *pack; - unsigned int mode; - off64_t off; - off64_t entry_start; - git_object_t entry_type; - git_str entry_data; - git_packfile_stream stream; - size_t nr_objects; - git_vector objects; - git_vector deltas; - unsigned int fanout[256]; - git_hash_ctx hash_ctx; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - char name[(GIT_HASH_SHA1_SIZE * 2) + 1]; - git_indexer_progress_cb progress_cb; - void *progress_payload; - char objbuf[8*1024]; - - /* OIDs referenced from pack objects. Used for verification. */ - git_oidmap *expected_oids; - - /* Needed to look up objects which we want to inject to fix a thin pack */ - git_odb *odb; - - /* Fields for calculating the packfile trailer (hash of everything before it) */ - char inbuf[GIT_OID_RAWSZ]; - size_t inbuf_len; - git_hash_ctx trailer; -}; - -struct delta_info { - off64_t delta_off; -}; - -#ifndef GIT_DEPRECATE_HARD -const git_oid *git_indexer_hash(const git_indexer *idx) -{ - return (git_oid *)idx->checksum; -} -#endif - -const char *git_indexer_name(const git_indexer *idx) -{ - return idx->name; -} - -static int parse_header(struct git_pack_header *hdr, struct git_pack_file *pack) -{ - int error; - git_map map; - - if ((error = p_mmap(&map, sizeof(*hdr), GIT_PROT_READ, GIT_MAP_SHARED, pack->mwf.fd, 0)) < 0) - return error; - - memcpy(hdr, map.data, sizeof(*hdr)); - p_munmap(&map); - - /* Verify we recognize this pack file format. */ - if (hdr->hdr_signature != ntohl(PACK_SIGNATURE)) { - git_error_set(GIT_ERROR_INDEXER, "wrong pack signature"); - return -1; - } - - if (!pack_version_ok(hdr->hdr_version)) { - git_error_set(GIT_ERROR_INDEXER, "wrong pack version"); - return -1; - } - - return 0; -} - -static int objects_cmp(const void *a, const void *b) -{ - const struct entry *entrya = a; - const struct entry *entryb = b; - - return git_oid__cmp(&entrya->oid, &entryb->oid); -} - -int git_indexer_options_init(git_indexer_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_indexer_options, GIT_INDEXER_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_indexer_init_options(git_indexer_options *opts, unsigned int version) -{ - return git_indexer_options_init(opts, version); -} -#endif - -int git_indexer_new( - git_indexer **out, - const char *prefix, - unsigned int mode, - git_odb *odb, - git_indexer_options *in_opts) -{ - git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; - git_indexer *idx; - git_str path = GIT_STR_INIT, tmp_path = GIT_STR_INIT; - static const char suff[] = "/pack"; - int error, fd = -1; - - if (in_opts) - memcpy(&opts, in_opts, sizeof(opts)); - - idx = git__calloc(1, sizeof(git_indexer)); - GIT_ERROR_CHECK_ALLOC(idx); - idx->odb = odb; - idx->progress_cb = opts.progress_cb; - idx->progress_payload = opts.progress_cb_payload; - idx->mode = mode ? mode : GIT_PACK_FILE_MODE; - git_str_init(&idx->entry_data, 0); - - if ((error = git_hash_ctx_init(&idx->hash_ctx, GIT_HASH_ALGORITHM_SHA1)) < 0 || - (error = git_hash_ctx_init(&idx->trailer, GIT_HASH_ALGORITHM_SHA1)) < 0 || - (error = git_oidmap_new(&idx->expected_oids)) < 0) - goto cleanup; - - idx->do_verify = opts.verify; - - if (git_repository__fsync_gitdir) - idx->do_fsync = 1; - - error = git_str_joinpath(&path, prefix, suff); - if (error < 0) - goto cleanup; - - fd = git_futils_mktmp(&tmp_path, git_str_cstr(&path), idx->mode); - git_str_dispose(&path); - if (fd < 0) - goto cleanup; - - error = git_packfile_alloc(&idx->pack, git_str_cstr(&tmp_path)); - git_str_dispose(&tmp_path); - - if (error < 0) - goto cleanup; - - idx->pack->mwf.fd = fd; - if ((error = git_mwindow_file_register(&idx->pack->mwf)) < 0) - goto cleanup; - - *out = idx; - return 0; - -cleanup: - if (fd != -1) - p_close(fd); - - if (git_str_len(&tmp_path) > 0) - p_unlink(git_str_cstr(&tmp_path)); - - if (idx->pack != NULL) - p_unlink(idx->pack->pack_name); - - git_str_dispose(&path); - git_str_dispose(&tmp_path); - git__free(idx); - return -1; -} - -void git_indexer__set_fsync(git_indexer *idx, int do_fsync) -{ - idx->do_fsync = !!do_fsync; -} - -/* Try to store the delta so we can try to resolve it later */ -static int store_delta(git_indexer *idx) -{ - struct delta_info *delta; - - delta = git__calloc(1, sizeof(struct delta_info)); - GIT_ERROR_CHECK_ALLOC(delta); - delta->delta_off = idx->entry_start; - - if (git_vector_insert(&idx->deltas, delta) < 0) - return -1; - - return 0; -} - -static int hash_header(git_hash_ctx *ctx, off64_t len, git_object_t type) -{ - char buffer[64]; - size_t hdrlen; - int error; - - if ((error = git_odb__format_object_header(&hdrlen, - buffer, sizeof(buffer), (size_t)len, type)) < 0) - return error; - - return git_hash_update(ctx, buffer, hdrlen); -} - -static int hash_object_stream(git_indexer*idx, git_packfile_stream *stream) -{ - ssize_t read; - - GIT_ASSERT_ARG(idx); - GIT_ASSERT_ARG(stream); - - do { - if ((read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf))) < 0) - break; - - if (idx->do_verify) - git_str_put(&idx->entry_data, idx->objbuf, read); - - git_hash_update(&idx->hash_ctx, idx->objbuf, read); - } while (read > 0); - - if (read < 0) - return (int)read; - - return 0; -} - -/* In order to create the packfile stream, we need to skip over the delta base description */ -static int advance_delta_offset(git_indexer *idx, git_object_t type) -{ - git_mwindow *w = NULL; - - GIT_ASSERT_ARG(type == GIT_OBJECT_REF_DELTA || type == GIT_OBJECT_OFS_DELTA); - - if (type == GIT_OBJECT_REF_DELTA) { - idx->off += GIT_OID_RAWSZ; - } else { - off64_t base_off; - int error = get_delta_base(&base_off, idx->pack, &w, &idx->off, type, idx->entry_start); - git_mwindow_close(&w); - if (error < 0) - return error; - } - - return 0; -} - -/* Read from the stream and discard any output */ -static int read_object_stream(git_indexer *idx, git_packfile_stream *stream) -{ - ssize_t read; - - GIT_ASSERT_ARG(stream); - - do { - read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf)); - } while (read > 0); - - if (read < 0) - return (int)read; - - return 0; -} - -static int crc_object(uint32_t *crc_out, git_mwindow_file *mwf, off64_t start, off64_t size) -{ - void *ptr; - uint32_t crc; - unsigned int left, len; - git_mwindow *w = NULL; - - crc = crc32(0L, Z_NULL, 0); - while (size) { - ptr = git_mwindow_open(mwf, &w, start, (size_t)size, &left); - if (ptr == NULL) - return -1; - - len = min(left, (unsigned int)size); - crc = crc32(crc, ptr, len); - size -= len; - start += len; - git_mwindow_close(&w); - } - - *crc_out = htonl(crc); - return 0; -} - -static int add_expected_oid(git_indexer *idx, const git_oid *oid) -{ - /* - * If we know about that object because it is stored in our ODB or - * because we have already processed it as part of our pack file, we do - * not have to expect it. - */ - if ((!idx->odb || !git_odb_exists(idx->odb, oid)) && - !git_oidmap_exists(idx->pack->idx_cache, oid) && - !git_oidmap_exists(idx->expected_oids, oid)) { - git_oid *dup = git__malloc(sizeof(*oid)); - GIT_ERROR_CHECK_ALLOC(dup); - git_oid_cpy(dup, oid); - return git_oidmap_set(idx->expected_oids, dup, dup); - } - - return 0; -} - -static int check_object_connectivity(git_indexer *idx, const git_rawobj *obj) -{ - git_object *object; - git_oid *expected; - int error = 0; - - if (obj->type != GIT_OBJECT_BLOB && - obj->type != GIT_OBJECT_TREE && - obj->type != GIT_OBJECT_COMMIT && - obj->type != GIT_OBJECT_TAG) - return 0; - - if (git_object__from_raw(&object, obj->data, obj->len, obj->type) < 0) { - /* - * parse_raw returns EINVALID on invalid data; downgrade - * that to a normal -1 error code. - */ - error = -1; - goto out; - } - - if ((expected = git_oidmap_get(idx->expected_oids, &object->cached.oid)) != NULL) { - git_oidmap_delete(idx->expected_oids, &object->cached.oid); - git__free(expected); - } - - /* - * Check whether this is a known object. If so, we can just continue as - * we assume that the ODB has a complete graph. - */ - if (idx->odb && git_odb_exists(idx->odb, &object->cached.oid)) - return 0; - - switch (obj->type) { - case GIT_OBJECT_TREE: - { - git_tree *tree = (git_tree *) object; - git_tree_entry *entry; - size_t i; - - git_array_foreach(tree->entries, i, entry) - if (add_expected_oid(idx, entry->oid) < 0) - goto out; - - break; - } - case GIT_OBJECT_COMMIT: - { - git_commit *commit = (git_commit *) object; - git_oid *parent_oid; - size_t i; - - git_array_foreach(commit->parent_ids, i, parent_oid) - if (add_expected_oid(idx, parent_oid) < 0) - goto out; - - if (add_expected_oid(idx, &commit->tree_id) < 0) - goto out; - - break; - } - case GIT_OBJECT_TAG: - { - git_tag *tag = (git_tag *) object; - - if (add_expected_oid(idx, &tag->target) < 0) - goto out; - - break; - } - case GIT_OBJECT_BLOB: - default: - break; - } - -out: - git_object_free(object); - - return error; -} - -static int store_object(git_indexer *idx) -{ - int i, error; - git_oid oid; - struct entry *entry; - off64_t entry_size; - struct git_pack_entry *pentry; - off64_t entry_start = idx->entry_start; - - entry = git__calloc(1, sizeof(*entry)); - GIT_ERROR_CHECK_ALLOC(entry); - - pentry = git__calloc(1, sizeof(struct git_pack_entry)); - GIT_ERROR_CHECK_ALLOC(pentry); - - if (git_hash_final(oid.id, &idx->hash_ctx)) { - git__free(pentry); - goto on_error; - } - entry_size = idx->off - entry_start; - if (entry_start > UINT31_MAX) { - entry->offset = UINT32_MAX; - entry->offset_long = entry_start; - } else { - entry->offset = (uint32_t)entry_start; - } - - if (idx->do_verify) { - git_rawobj rawobj = { - idx->entry_data.ptr, - idx->entry_data.size, - idx->entry_type - }; - - if ((error = check_object_connectivity(idx, &rawobj)) < 0) - goto on_error; - } - - git_oid_cpy(&pentry->sha1, &oid); - pentry->offset = entry_start; - - if (git_oidmap_exists(idx->pack->idx_cache, &pentry->sha1)) { - git_error_set(GIT_ERROR_INDEXER, "duplicate object %s found in pack", git_oid_tostr_s(&pentry->sha1)); - git__free(pentry); - goto on_error; - } - - if ((error = git_oidmap_set(idx->pack->idx_cache, &pentry->sha1, pentry)) < 0) { - git__free(pentry); - git_error_set_oom(); - goto on_error; - } - - git_oid_cpy(&entry->oid, &oid); - - if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0) - goto on_error; - - /* Add the object to the list */ - if (git_vector_insert(&idx->objects, entry) < 0) - goto on_error; - - for (i = oid.id[0]; i < 256; ++i) { - idx->fanout[i]++; - } - - return 0; - -on_error: - git__free(entry); - - return -1; -} - -GIT_INLINE(bool) has_entry(git_indexer *idx, git_oid *id) -{ - return git_oidmap_exists(idx->pack->idx_cache, id); -} - -static int save_entry(git_indexer *idx, struct entry *entry, struct git_pack_entry *pentry, off64_t entry_start) -{ - int i; - - if (entry_start > UINT31_MAX) { - entry->offset = UINT32_MAX; - entry->offset_long = entry_start; - } else { - entry->offset = (uint32_t)entry_start; - } - - pentry->offset = entry_start; - - if (git_oidmap_exists(idx->pack->idx_cache, &pentry->sha1) || - git_oidmap_set(idx->pack->idx_cache, &pentry->sha1, pentry) < 0) { - git_error_set(GIT_ERROR_INDEXER, "cannot insert object into pack"); - return -1; - } - - /* Add the object to the list */ - if (git_vector_insert(&idx->objects, entry) < 0) - return -1; - - for (i = entry->oid.id[0]; i < 256; ++i) { - idx->fanout[i]++; - } - - return 0; -} - -static int hash_and_save(git_indexer *idx, git_rawobj *obj, off64_t entry_start) -{ - git_oid oid; - size_t entry_size; - struct entry *entry; - struct git_pack_entry *pentry = NULL; - - entry = git__calloc(1, sizeof(*entry)); - GIT_ERROR_CHECK_ALLOC(entry); - - if (git_odb__hashobj(&oid, obj) < 0) { - git_error_set(GIT_ERROR_INDEXER, "failed to hash object"); - goto on_error; - } - - pentry = git__calloc(1, sizeof(struct git_pack_entry)); - GIT_ERROR_CHECK_ALLOC(pentry); - - git_oid_cpy(&pentry->sha1, &oid); - git_oid_cpy(&entry->oid, &oid); - entry->crc = crc32(0L, Z_NULL, 0); - - entry_size = (size_t)(idx->off - entry_start); - if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0) - goto on_error; - - return save_entry(idx, entry, pentry, entry_start); - -on_error: - git__free(pentry); - git__free(entry); - git__free(obj->data); - return -1; -} - -static int do_progress_callback(git_indexer *idx, git_indexer_progress *stats) -{ - if (idx->progress_cb) - return git_error_set_after_callback_function( - idx->progress_cb(stats, idx->progress_payload), - "indexer progress"); - return 0; -} - -/* Hash everything but the last 20B of input */ -static void hash_partially(git_indexer *idx, const uint8_t *data, size_t size) -{ - size_t to_expell, to_keep; - - if (size == 0) - return; - - /* Easy case, dump the buffer and the data minus the last 20 bytes */ - if (size >= GIT_OID_RAWSZ) { - git_hash_update(&idx->trailer, idx->inbuf, idx->inbuf_len); - git_hash_update(&idx->trailer, data, size - GIT_OID_RAWSZ); - - data += size - GIT_OID_RAWSZ; - memcpy(idx->inbuf, data, GIT_OID_RAWSZ); - idx->inbuf_len = GIT_OID_RAWSZ; - return; - } - - /* We can just append */ - if (idx->inbuf_len + size <= GIT_OID_RAWSZ) { - memcpy(idx->inbuf + idx->inbuf_len, data, size); - idx->inbuf_len += size; - return; - } - - /* We need to partially drain the buffer and then append */ - to_keep = GIT_OID_RAWSZ - size; - to_expell = idx->inbuf_len - to_keep; - - git_hash_update(&idx->trailer, idx->inbuf, to_expell); - - memmove(idx->inbuf, idx->inbuf + to_expell, to_keep); - memcpy(idx->inbuf + to_keep, data, size); - idx->inbuf_len += size - to_expell; -} - -#if defined(NO_MMAP) || !defined(GIT_WIN32) - -static int write_at(git_indexer *idx, const void *data, off64_t offset, size_t size) -{ - size_t remaining_size = size; - const char *ptr = (const char *)data; - - /* Handle data size larger that ssize_t */ - while (remaining_size > 0) { - ssize_t nb; - HANDLE_EINTR(nb, p_pwrite(idx->pack->mwf.fd, (void *)ptr, - remaining_size, offset)); - if (nb <= 0) - return -1; - - ptr += nb; - offset += nb; - remaining_size -= nb; - } - - return 0; -} - -static int append_to_pack(git_indexer *idx, const void *data, size_t size) -{ - if (write_at(idx, data, idx->pack->mwf.size, size) < 0) { - git_error_set(GIT_ERROR_OS, "cannot extend packfile '%s'", idx->pack->pack_name); - return -1; - } - - return 0; -} - -#else - -/* - * Windows may keep different views to a networked file for the mmap- and - * open-accessed versions of a file, so any writes done through - * `write(2)`/`pwrite(2)` may not be reflected on the data that `mmap(2)` is - * able to read. - */ - -static int write_at(git_indexer *idx, const void *data, off64_t offset, size_t size) -{ - git_file fd = idx->pack->mwf.fd; - size_t mmap_alignment; - size_t page_offset; - off64_t page_start; - unsigned char *map_data; - git_map map; - int error; - - GIT_ASSERT_ARG(data); - GIT_ASSERT_ARG(size); - - if ((error = git__mmap_alignment(&mmap_alignment)) < 0) - return error; - - /* the offset needs to be at the mmap boundary for the platform */ - page_offset = offset % mmap_alignment; - page_start = offset - page_offset; - - if ((error = p_mmap(&map, page_offset + size, GIT_PROT_WRITE, GIT_MAP_SHARED, fd, page_start)) < 0) - return error; - - map_data = (unsigned char *)map.data; - memcpy(map_data + page_offset, data, size); - p_munmap(&map); - - return 0; -} - -static int append_to_pack(git_indexer *idx, const void *data, size_t size) -{ - off64_t new_size; - size_t mmap_alignment; - size_t page_offset; - off64_t page_start; - off64_t current_size = idx->pack->mwf.size; - int error; - - if (!size) - return 0; - - if ((error = git__mmap_alignment(&mmap_alignment)) < 0) - return error; - - /* Write a single byte to force the file system to allocate space now or - * report an error, since we can't report errors when writing using mmap. - * Round the size up to the nearest page so that we only need to perform file - * I/O when we add a page, instead of whenever we write even a single byte. */ - new_size = current_size + size; - page_offset = new_size % mmap_alignment; - page_start = new_size - page_offset; - - if (p_pwrite(idx->pack->mwf.fd, data, 1, page_start + mmap_alignment - 1) < 0) { - git_error_set(GIT_ERROR_OS, "cannot extend packfile '%s'", idx->pack->pack_name); - return -1; - } - - return write_at(idx, data, idx->pack->mwf.size, size); -} - -#endif - -static int read_stream_object(git_indexer *idx, git_indexer_progress *stats) -{ - git_packfile_stream *stream = &idx->stream; - off64_t entry_start = idx->off; - size_t entry_size; - git_object_t type; - git_mwindow *w = NULL; - int error; - - if (idx->pack->mwf.size <= idx->off + 20) - return GIT_EBUFS; - - if (!idx->have_stream) { - error = git_packfile_unpack_header(&entry_size, &type, idx->pack, &w, &idx->off); - if (error == GIT_EBUFS) { - idx->off = entry_start; - return error; - } - if (error < 0) - return error; - - git_mwindow_close(&w); - idx->entry_start = entry_start; - git_hash_init(&idx->hash_ctx); - git_str_clear(&idx->entry_data); - - if (type == GIT_OBJECT_REF_DELTA || type == GIT_OBJECT_OFS_DELTA) { - error = advance_delta_offset(idx, type); - if (error == GIT_EBUFS) { - idx->off = entry_start; - return error; - } - if (error < 0) - return error; - - idx->have_delta = 1; - } else { - idx->have_delta = 0; - - error = hash_header(&idx->hash_ctx, entry_size, type); - if (error < 0) - return error; - } - - idx->have_stream = 1; - idx->entry_type = type; - - error = git_packfile_stream_open(stream, idx->pack, idx->off); - if (error < 0) - return error; - } - - if (idx->have_delta) { - error = read_object_stream(idx, stream); - } else { - error = hash_object_stream(idx, stream); - } - - idx->off = stream->curpos; - if (error == GIT_EBUFS) - return error; - - /* We want to free the stream reasorces no matter what here */ - idx->have_stream = 0; - git_packfile_stream_dispose(stream); - - if (error < 0) - return error; - - if (idx->have_delta) { - error = store_delta(idx); - } else { - error = store_object(idx); - } - - if (error < 0) - return error; - - if (!idx->have_delta) { - stats->indexed_objects++; - } - stats->received_objects++; - - if ((error = do_progress_callback(idx, stats)) != 0) - return error; - - return 0; -} - -int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_indexer_progress *stats) -{ - int error = -1; - struct git_pack_header *hdr = &idx->hdr; - git_mwindow_file *mwf = &idx->pack->mwf; - - GIT_ASSERT_ARG(idx); - GIT_ASSERT_ARG(data); - GIT_ASSERT_ARG(stats); - - if ((error = append_to_pack(idx, data, size)) < 0) - return error; - - hash_partially(idx, data, (int)size); - - /* Make sure we set the new size of the pack */ - idx->pack->mwf.size += size; - - if (!idx->parsed_header) { - unsigned int total_objects; - - if ((unsigned)idx->pack->mwf.size < sizeof(struct git_pack_header)) - return 0; - - if ((error = parse_header(&idx->hdr, idx->pack)) < 0) - return error; - - idx->parsed_header = 1; - idx->nr_objects = ntohl(hdr->hdr_entries); - idx->off = sizeof(struct git_pack_header); - - if (idx->nr_objects <= git_indexer__max_objects) { - total_objects = (unsigned int)idx->nr_objects; - } else { - git_error_set(GIT_ERROR_INDEXER, "too many objects"); - return -1; - } - - if (git_oidmap_new(&idx->pack->idx_cache) < 0) - return -1; - - idx->pack->has_cache = 1; - if (git_vector_init(&idx->objects, total_objects, objects_cmp) < 0) - return -1; - - if (git_vector_init(&idx->deltas, total_objects / 2, NULL) < 0) - return -1; - - stats->received_objects = 0; - stats->local_objects = 0; - stats->total_deltas = 0; - stats->indexed_deltas = 0; - stats->indexed_objects = 0; - stats->total_objects = total_objects; - - if ((error = do_progress_callback(idx, stats)) != 0) - return error; - } - - /* Now that we have data in the pack, let's try to parse it */ - - /* As the file grows any windows we try to use will be out of date */ - if ((error = git_mwindow_free_all(mwf)) < 0) - goto on_error; - - while (stats->indexed_objects < idx->nr_objects) { - if ((error = read_stream_object(idx, stats)) != 0) { - if (error == GIT_EBUFS) - break; - else - goto on_error; - } - } - - return 0; - -on_error: - git_mwindow_free_all(mwf); - return error; -} - -static int index_path(git_str *path, git_indexer *idx, const char *suffix) -{ - const char prefix[] = "pack-"; - size_t slash = (size_t)path->size; - - /* search backwards for '/' */ - while (slash > 0 && path->ptr[slash - 1] != '/') - slash--; - - if (git_str_grow(path, slash + 1 + strlen(prefix) + - GIT_OID_HEXSZ + strlen(suffix) + 1) < 0) - return -1; - - git_str_truncate(path, slash); - git_str_puts(path, prefix); - git_str_puts(path, idx->name); - git_str_puts(path, suffix); - - return git_str_oom(path) ? -1 : 0; -} - -/** - * Rewind the packfile by the trailer, as we might need to fix the - * packfile by injecting objects at the tail and must overwrite it. - */ -static int seek_back_trailer(git_indexer *idx) -{ - idx->pack->mwf.size -= GIT_OID_RAWSZ; - return git_mwindow_free_all(&idx->pack->mwf); -} - -static int inject_object(git_indexer *idx, git_oid *id) -{ - git_odb_object *obj = NULL; - struct entry *entry = NULL; - struct git_pack_entry *pentry = NULL; - unsigned char empty_checksum[GIT_HASH_SHA1_SIZE] = {0}; - unsigned char hdr[64]; - git_str buf = GIT_STR_INIT; - off64_t entry_start; - const void *data; - size_t len, hdr_len; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - int error; - - if ((error = seek_back_trailer(idx)) < 0) - goto cleanup; - - entry_start = idx->pack->mwf.size; - - if ((error = git_odb_read(&obj, idx->odb, id)) < 0) { - git_error_set(GIT_ERROR_INDEXER, "missing delta bases"); - goto cleanup; - } - - data = git_odb_object_data(obj); - len = git_odb_object_size(obj); - - entry = git__calloc(1, sizeof(*entry)); - GIT_ERROR_CHECK_ALLOC(entry); - - entry->crc = crc32(0L, Z_NULL, 0); - - /* Write out the object header */ - if ((error = git_packfile__object_header(&hdr_len, hdr, len, git_odb_object_type(obj))) < 0 || - (error = append_to_pack(idx, hdr, hdr_len)) < 0) - goto cleanup; - - idx->pack->mwf.size += hdr_len; - entry->crc = crc32(entry->crc, hdr, (uInt)hdr_len); - - if ((error = git_zstream_deflatebuf(&buf, data, len)) < 0) - goto cleanup; - - /* And then the compressed object */ - if ((error = append_to_pack(idx, buf.ptr, buf.size)) < 0) - goto cleanup; - - idx->pack->mwf.size += buf.size; - entry->crc = htonl(crc32(entry->crc, (unsigned char *)buf.ptr, (uInt)buf.size)); - git_str_dispose(&buf); - - /* Write a fake trailer so the pack functions play ball */ - - if ((error = append_to_pack(idx, empty_checksum, checksum_size)) < 0) - goto cleanup; - - idx->pack->mwf.size += GIT_OID_RAWSZ; - - pentry = git__calloc(1, sizeof(struct git_pack_entry)); - GIT_ERROR_CHECK_ALLOC(pentry); - - git_oid_cpy(&pentry->sha1, id); - git_oid_cpy(&entry->oid, id); - idx->off = entry_start + hdr_len + len; - - error = save_entry(idx, entry, pentry, entry_start); - -cleanup: - if (error) { - git__free(entry); - git__free(pentry); - } - - git_odb_object_free(obj); - return error; -} - -static int fix_thin_pack(git_indexer *idx, git_indexer_progress *stats) -{ - int error, found_ref_delta = 0; - unsigned int i; - struct delta_info *delta; - size_t size; - git_object_t type; - git_mwindow *w = NULL; - off64_t curpos = 0; - unsigned char *base_info; - unsigned int left = 0; - git_oid base; - - GIT_ASSERT(git_vector_length(&idx->deltas) > 0); - - if (idx->odb == NULL) { - git_error_set(GIT_ERROR_INDEXER, "cannot fix a thin pack without an ODB"); - return -1; - } - - /* Loop until we find the first REF delta */ - git_vector_foreach(&idx->deltas, i, delta) { - if (!delta) - continue; - - curpos = delta->delta_off; - error = git_packfile_unpack_header(&size, &type, idx->pack, &w, &curpos); - if (error < 0) - return error; - - if (type == GIT_OBJECT_REF_DELTA) { - found_ref_delta = 1; - break; - } - } - - if (!found_ref_delta) { - git_error_set(GIT_ERROR_INDEXER, "no REF_DELTA found, cannot inject object"); - return -1; - } - - /* curpos now points to the base information, which is an OID */ - base_info = git_mwindow_open(&idx->pack->mwf, &w, curpos, GIT_OID_RAWSZ, &left); - if (base_info == NULL) { - git_error_set(GIT_ERROR_INDEXER, "failed to map delta information"); - return -1; - } - - git_oid_fromraw(&base, base_info); - git_mwindow_close(&w); - - if (has_entry(idx, &base)) - return 0; - - if (inject_object(idx, &base) < 0) - return -1; - - stats->local_objects++; - - return 0; -} - -static int resolve_deltas(git_indexer *idx, git_indexer_progress *stats) -{ - unsigned int i; - int error; - struct delta_info *delta; - int progressed = 0, non_null = 0, progress_cb_result; - - while (idx->deltas.length > 0) { - progressed = 0; - non_null = 0; - git_vector_foreach(&idx->deltas, i, delta) { - git_rawobj obj = {0}; - - if (!delta) - continue; - - non_null = 1; - idx->off = delta->delta_off; - if ((error = git_packfile_unpack(&obj, idx->pack, &idx->off)) < 0) { - if (error == GIT_PASSTHROUGH) { - /* We have not seen the base object, we'll try again later. */ - continue; - } - return -1; - } - - if (idx->do_verify && check_object_connectivity(idx, &obj) < 0) - /* TODO: error? continue? */ - continue; - - if (hash_and_save(idx, &obj, delta->delta_off) < 0) - continue; - - git__free(obj.data); - stats->indexed_objects++; - stats->indexed_deltas++; - progressed = 1; - if ((progress_cb_result = do_progress_callback(idx, stats)) < 0) - return progress_cb_result; - - /* remove from the list */ - git_vector_set(NULL, &idx->deltas, i, NULL); - git__free(delta); - } - - /* if none were actually set, we're done */ - if (!non_null) - break; - - if (!progressed && (fix_thin_pack(idx, stats) < 0)) { - return -1; - } - } - - return 0; -} - -static int update_header_and_rehash(git_indexer *idx, git_indexer_progress *stats) -{ - void *ptr; - size_t chunk = 1024*1024; - off64_t hashed = 0; - git_mwindow *w = NULL; - git_mwindow_file *mwf; - unsigned int left; - - mwf = &idx->pack->mwf; - - git_hash_init(&idx->trailer); - - - /* Update the header to include the number of local objects we injected */ - idx->hdr.hdr_entries = htonl(stats->total_objects + stats->local_objects); - if (write_at(idx, &idx->hdr, 0, sizeof(struct git_pack_header)) < 0) - return -1; - - /* - * We now use the same technique as before to determine the - * hash. We keep reading up to the end and let - * hash_partially() keep the existing trailer out of the - * calculation. - */ - if (git_mwindow_free_all(mwf) < 0) - return -1; - - idx->inbuf_len = 0; - while (hashed < mwf->size) { - ptr = git_mwindow_open(mwf, &w, hashed, chunk, &left); - if (ptr == NULL) - return -1; - - hash_partially(idx, ptr, left); - hashed += left; - - git_mwindow_close(&w); - } - - return 0; -} - -int git_indexer_commit(git_indexer *idx, git_indexer_progress *stats) -{ - git_mwindow *w = NULL; - unsigned int i, long_offsets = 0, left; - int error; - struct git_pack_idx_header hdr; - git_str filename = GIT_STR_INIT; - struct entry *entry; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - git_filebuf index_file = {0}; - void *packfile_trailer; - size_t checksum_size = GIT_HASH_SHA1_SIZE; - bool mismatch; - - if (!idx->parsed_header) { - git_error_set(GIT_ERROR_INDEXER, "incomplete pack header"); - return -1; - } - - /* Test for this before resolve_deltas(), as it plays with idx->off */ - if (idx->off + (ssize_t)checksum_size < idx->pack->mwf.size) { - git_error_set(GIT_ERROR_INDEXER, "unexpected data at the end of the pack"); - return -1; - } - if (idx->off + (ssize_t)checksum_size > idx->pack->mwf.size) { - git_error_set(GIT_ERROR_INDEXER, "missing trailer at the end of the pack"); - return -1; - } - - packfile_trailer = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - checksum_size, checksum_size, &left); - if (packfile_trailer == NULL) { - git_mwindow_close(&w); - goto on_error; - } - - /* Compare the packfile trailer as it was sent to us and what we calculated */ - git_hash_final(checksum, &idx->trailer); - mismatch = !!memcmp(checksum, packfile_trailer, checksum_size); - git_mwindow_close(&w); - - if (mismatch) { - git_error_set(GIT_ERROR_INDEXER, "packfile trailer mismatch"); - return -1; - } - - /* Freeze the number of deltas */ - stats->total_deltas = stats->total_objects - stats->indexed_objects; - - if ((error = resolve_deltas(idx, stats)) < 0) - return error; - - if (stats->indexed_objects != stats->total_objects) { - git_error_set(GIT_ERROR_INDEXER, "early EOF"); - return -1; - } - - if (stats->local_objects > 0) { - if (update_header_and_rehash(idx, stats) < 0) - return -1; - - git_hash_final(checksum, &idx->trailer); - write_at(idx, checksum, idx->pack->mwf.size - checksum_size, checksum_size); - } - - /* - * Is the resulting graph fully connected or are we still - * missing some objects? In the second case, we can - * bail out due to an incomplete and thus corrupt - * packfile. - */ - if (git_oidmap_size(idx->expected_oids) > 0) { - git_error_set(GIT_ERROR_INDEXER, "packfile is missing %"PRIuZ" objects", - git_oidmap_size(idx->expected_oids)); - return -1; - } - - git_vector_sort(&idx->objects); - - /* Use the trailer hash as the pack file name to ensure - * files with different contents have different names */ - memcpy(idx->checksum, checksum, checksum_size); - if (git_hash_fmt(idx->name, checksum, checksum_size) < 0) - return -1; - - git_str_sets(&filename, idx->pack->pack_name); - git_str_shorten(&filename, strlen("pack")); - git_str_puts(&filename, "idx"); - if (git_str_oom(&filename)) - return -1; - - if (git_filebuf_open(&index_file, filename.ptr, - GIT_FILEBUF_HASH_CONTENTS | - (idx->do_fsync ? GIT_FILEBUF_FSYNC : 0), - idx->mode) < 0) - goto on_error; - - /* Write out the header */ - hdr.idx_signature = htonl(PACK_IDX_SIGNATURE); - hdr.idx_version = htonl(2); - git_filebuf_write(&index_file, &hdr, sizeof(hdr)); - - /* Write out the fanout table */ - for (i = 0; i < 256; ++i) { - uint32_t n = htonl(idx->fanout[i]); - git_filebuf_write(&index_file, &n, sizeof(n)); - } - - /* Write out the object names (SHA-1 hashes) */ - git_vector_foreach(&idx->objects, i, entry) { - git_filebuf_write(&index_file, &entry->oid, sizeof(git_oid)); - } - - /* Write out the CRC32 values */ - git_vector_foreach(&idx->objects, i, entry) { - git_filebuf_write(&index_file, &entry->crc, sizeof(uint32_t)); - } - - /* Write out the offsets */ - git_vector_foreach(&idx->objects, i, entry) { - uint32_t n; - - if (entry->offset == UINT32_MAX) - n = htonl(0x80000000 | long_offsets++); - else - n = htonl(entry->offset); - - git_filebuf_write(&index_file, &n, sizeof(uint32_t)); - } - - /* Write out the long offsets */ - git_vector_foreach(&idx->objects, i, entry) { - uint32_t split[2]; - - if (entry->offset != UINT32_MAX) - continue; - - split[0] = htonl(entry->offset_long >> 32); - split[1] = htonl(entry->offset_long & 0xffffffff); - - git_filebuf_write(&index_file, &split, sizeof(uint32_t) * 2); - } - - /* Write out the packfile trailer to the index */ - if (git_filebuf_write(&index_file, checksum, checksum_size) < 0) - goto on_error; - - /* Write out the hash of the idx */ - if (git_filebuf_hash(checksum, &index_file) < 0) - goto on_error; - - git_filebuf_write(&index_file, checksum, checksum_size); - - /* Figure out what the final name should be */ - if (index_path(&filename, idx, ".idx") < 0) - goto on_error; - - /* Commit file */ - if (git_filebuf_commit_at(&index_file, filename.ptr) < 0) - goto on_error; - - if (git_mwindow_free_all(&idx->pack->mwf) < 0) - goto on_error; - -#if !defined(NO_MMAP) && defined(GIT_WIN32) - /* - * Some non-Windows remote filesystems fail when truncating files if the - * file permissions change after opening the file (done by p_mkstemp). - * - * Truncation is only needed when mmap is used to undo rounding up to next - * page_size in append_to_pack. - */ - if (p_ftruncate(idx->pack->mwf.fd, idx->pack->mwf.size) < 0) { - git_error_set(GIT_ERROR_OS, "failed to truncate pack file '%s'", idx->pack->pack_name); - return -1; - } -#endif - - if (idx->do_fsync && p_fsync(idx->pack->mwf.fd) < 0) { - git_error_set(GIT_ERROR_OS, "failed to fsync packfile"); - goto on_error; - } - - /* We need to close the descriptor here so Windows doesn't choke on commit_at */ - if (p_close(idx->pack->mwf.fd) < 0) { - git_error_set(GIT_ERROR_OS, "failed to close packfile"); - goto on_error; - } - - idx->pack->mwf.fd = -1; - - if (index_path(&filename, idx, ".pack") < 0) - goto on_error; - - /* And don't forget to rename the packfile to its new place. */ - if (p_rename(idx->pack->pack_name, git_str_cstr(&filename)) < 0) - goto on_error; - - /* And fsync the parent directory if we're asked to. */ - if (idx->do_fsync && - git_futils_fsync_parent(git_str_cstr(&filename)) < 0) - goto on_error; - - idx->pack_committed = 1; - - git_str_dispose(&filename); - return 0; - -on_error: - git_mwindow_free_all(&idx->pack->mwf); - git_filebuf_cleanup(&index_file); - git_str_dispose(&filename); - return -1; -} - -void git_indexer_free(git_indexer *idx) -{ - const git_oid *key; - git_oid *value; - size_t iter; - - if (idx == NULL) - return; - - if (idx->have_stream) - git_packfile_stream_dispose(&idx->stream); - - git_vector_free_deep(&idx->objects); - - if (idx->pack->idx_cache) { - struct git_pack_entry *pentry; - git_oidmap_foreach_value(idx->pack->idx_cache, pentry, { - git__free(pentry); - }); - - git_oidmap_free(idx->pack->idx_cache); - } - - git_vector_free_deep(&idx->deltas); - - git_packfile_free(idx->pack, !idx->pack_committed); - - iter = 0; - while (git_oidmap_iterate((void **) &value, idx->expected_oids, &iter, &key) == 0) - git__free(value); - - git_hash_ctx_cleanup(&idx->trailer); - git_hash_ctx_cleanup(&idx->hash_ctx); - git_str_dispose(&idx->entry_data); - git_oidmap_free(idx->expected_oids); - git__free(idx); -} diff --git a/src/indexer.h b/src/indexer.h deleted file mode 100644 index 8ee6115a6..000000000 --- a/src/indexer.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_indexer_h__ -#define INCLUDE_indexer_h__ - -#include "common.h" - -#include "git2/indexer.h" - -extern void git_indexer__set_fsync(git_indexer *idx, int do_fsync); - -#endif diff --git a/src/integer.h b/src/integer.h deleted file mode 100644 index 63277177b..000000000 --- a/src/integer.h +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_integer_h__ -#define INCLUDE_integer_h__ - -/** @return true if p fits into the range of a size_t */ -GIT_INLINE(int) git__is_sizet(int64_t p) -{ - size_t r = (size_t)p; - return p == (int64_t)r; -} - -/** @return true if p fits into the range of an ssize_t */ -GIT_INLINE(int) git__is_ssizet(size_t p) -{ - ssize_t r = (ssize_t)p; - return p == (size_t)r; -} - -/** @return true if p fits into the range of a uint16_t */ -GIT_INLINE(int) git__is_uint16(size_t p) -{ - uint16_t r = (uint16_t)p; - return p == (size_t)r; -} - -/** @return true if p fits into the range of a uint32_t */ -GIT_INLINE(int) git__is_uint32(size_t p) -{ - uint32_t r = (uint32_t)p; - return p == (size_t)r; -} - -/** @return true if p fits into the range of an unsigned long */ -GIT_INLINE(int) git__is_ulong(int64_t p) -{ - unsigned long r = (unsigned long)p; - return p == (int64_t)r; -} - -/** @return true if p fits into the range of an int */ -GIT_INLINE(int) git__is_int(int64_t p) -{ - int r = (int)p; - return p == (int64_t)r; -} - -/* Use clang/gcc compiler intrinsics whenever possible */ -#if (__has_builtin(__builtin_add_overflow) || \ - (defined(__GNUC__) && (__GNUC__ >= 5))) - -# if (SIZE_MAX == UINT_MAX) -# define git__add_sizet_overflow(out, one, two) \ - __builtin_uadd_overflow(one, two, out) -# define git__multiply_sizet_overflow(out, one, two) \ - __builtin_umul_overflow(one, two, out) -# elif (SIZE_MAX == ULONG_MAX) -# define git__add_sizet_overflow(out, one, two) \ - __builtin_uaddl_overflow(one, two, out) -# define git__multiply_sizet_overflow(out, one, two) \ - __builtin_umull_overflow(one, two, out) -# elif (SIZE_MAX == ULLONG_MAX) -# define git__add_sizet_overflow(out, one, two) \ - __builtin_uaddll_overflow(one, two, out) -# define git__multiply_sizet_overflow(out, one, two) \ - __builtin_umulll_overflow(one, two, out) -# else -# error compiler has add with overflow intrinsics but SIZE_MAX is unknown -# endif - -# define git__add_int_overflow(out, one, two) \ - __builtin_sadd_overflow(one, two, out) -# define git__sub_int_overflow(out, one, two) \ - __builtin_ssub_overflow(one, two, out) - -# define git__add_int64_overflow(out, one, two) \ - __builtin_add_overflow(one, two, out) - -/* clang on 32-bit systems produces an undefined reference to `__mulodi4`. */ -# if !defined(__clang__) || !defined(GIT_ARCH_32) -# define git__multiply_int64_overflow(out, one, two) \ - __builtin_mul_overflow(one, two, out) -# endif - -/* Use Microsoft's safe integer handling functions where available */ -#elif defined(_MSC_VER) - -# define ENABLE_INTSAFE_SIGNED_FUNCTIONS -# include - -# define git__add_sizet_overflow(out, one, two) \ - (SizeTAdd(one, two, out) != S_OK) -# define git__multiply_sizet_overflow(out, one, two) \ - (SizeTMult(one, two, out) != S_OK) - -#define git__add_int_overflow(out, one, two) \ - (IntAdd(one, two, out) != S_OK) -#define git__sub_int_overflow(out, one, two) \ - (IntSub(one, two, out) != S_OK) - -#define git__add_int64_overflow(out, one, two) \ - (LongLongAdd(one, two, out) != S_OK) -#define git__multiply_int64_overflow(out, one, two) \ - (LongLongMult(one, two, out) != S_OK) - -#else - -/** - * Sets `one + two` into `out`, unless the arithmetic would overflow. - * @return false if the result fits in a `size_t`, true on overflow. - */ -GIT_INLINE(bool) git__add_sizet_overflow(size_t *out, size_t one, size_t two) -{ - if (SIZE_MAX - one < two) - return true; - *out = one + two; - return false; -} - -/** - * Sets `one * two` into `out`, unless the arithmetic would overflow. - * @return false if the result fits in a `size_t`, true on overflow. - */ -GIT_INLINE(bool) git__multiply_sizet_overflow(size_t *out, size_t one, size_t two) -{ - if (one && SIZE_MAX / one < two) - return true; - *out = one * two; - return false; -} - -GIT_INLINE(bool) git__add_int_overflow(int *out, int one, int two) -{ - if ((two > 0 && one > (INT_MAX - two)) || - (two < 0 && one < (INT_MIN - two))) - return true; - *out = one + two; - return false; -} - -GIT_INLINE(bool) git__sub_int_overflow(int *out, int one, int two) -{ - if ((two > 0 && one < (INT_MIN + two)) || - (two < 0 && one > (INT_MAX + two))) - return true; - *out = one - two; - return false; -} - -GIT_INLINE(bool) git__add_int64_overflow(int64_t *out, int64_t one, int64_t two) -{ - if ((two > 0 && one > (INT64_MAX - two)) || - (two < 0 && one < (INT64_MIN - two))) - return true; - *out = one + two; - return false; -} - -#endif - -/* If we could not provide an intrinsic implementation for this, provide a (slow) fallback. */ -#if !defined(git__multiply_int64_overflow) -GIT_INLINE(bool) git__multiply_int64_overflow(int64_t *out, int64_t one, int64_t two) -{ - /* - * Detects whether `INT64_MAX < (one * two) || INT64_MIN > (one * two)`, - * without incurring in undefined behavior. That is done by performing the - * comparison with a division instead of a multiplication, which translates - * to `INT64_MAX / one < two || INT64_MIN / one > two`. Some caveats: - * - * - The comparison sign is inverted when both sides of the inequality are - * multiplied/divided by a negative number, so if `one < 0` the comparison - * needs to be flipped. - * - `INT64_MAX / -1` itself overflows (or traps), so that case should be - * avoided. - * - Since the overflow flag is defined as the discrepance between the result - * of performing the multiplication in a signed integer at twice the width - * of the operands, and the truncated+sign-extended version of that same - * result, there are four cases where the result is the opposite of what - * would be expected: - * * `INT64_MIN * -1` / `-1 * INT64_MIN` - * * `INT64_MIN * 1 / `1 * INT64_MIN` - */ - if (one && two) { - if (one > 0 && two > 0) { - if (INT64_MAX / one < two) - return true; - } else if (one < 0 && two < 0) { - if ((one == -1 && two == INT64_MIN) || - (two == -1 && one == INT64_MIN)) { - *out = INT64_MIN; - return false; - } - if (INT64_MAX / one > two) - return true; - } else if (one > 0 && two < 0) { - if ((one == 1 && two == INT64_MIN) || - (INT64_MIN / one > two)) - return true; - } else if (one == -1) { - if (INT64_MIN / two > one) - return true; - } else { - if ((one == INT64_MIN && two == 1) || - (INT64_MIN / one < two)) - return true; - } - } - *out = one * two; - return false; -} -#endif - -#endif diff --git a/src/iterator.c b/src/iterator.c deleted file mode 100644 index 15bb63dc8..000000000 --- a/src/iterator.c +++ /dev/null @@ -1,2439 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "iterator.h" - -#include "tree.h" -#include "index.h" -#include "path.h" - -#define GIT_ITERATOR_FIRST_ACCESS (1 << 15) -#define GIT_ITERATOR_HONOR_IGNORES (1 << 16) -#define GIT_ITERATOR_IGNORE_DOT_GIT (1 << 17) - -#define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0) -#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE) -#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES) -#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND) -#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND) -#define iterator__include_conflicts(I) iterator__flag(I,INCLUDE_CONFLICTS) -#define iterator__has_been_accessed(I) iterator__flag(I,FIRST_ACCESS) -#define iterator__honor_ignores(I) iterator__flag(I,HONOR_IGNORES) -#define iterator__ignore_dot_git(I) iterator__flag(I,IGNORE_DOT_GIT) -#define iterator__descend_symlinks(I) iterator__flag(I,DESCEND_SYMLINKS) - - -static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case) -{ - if (ignore_case) - iter->flags |= GIT_ITERATOR_IGNORE_CASE; - else - iter->flags &= ~GIT_ITERATOR_IGNORE_CASE; - - iter->strcomp = ignore_case ? git__strcasecmp : git__strcmp; - iter->strncomp = ignore_case ? git__strncasecmp : git__strncmp; - iter->prefixcomp = ignore_case ? git__prefixcmp_icase : git__prefixcmp; - iter->entry_srch = ignore_case ? git_index_entry_isrch : git_index_entry_srch; - - git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp); -} - -static int iterator_range_init( - git_iterator *iter, const char *start, const char *end) -{ - if (start && *start) { - iter->start = git__strdup(start); - GIT_ERROR_CHECK_ALLOC(iter->start); - - iter->start_len = strlen(iter->start); - } - - if (end && *end) { - iter->end = git__strdup(end); - GIT_ERROR_CHECK_ALLOC(iter->end); - - iter->end_len = strlen(iter->end); - } - - iter->started = (iter->start == NULL); - iter->ended = false; - - return 0; -} - -static void iterator_range_free(git_iterator *iter) -{ - if (iter->start) { - git__free(iter->start); - iter->start = NULL; - iter->start_len = 0; - } - - if (iter->end) { - git__free(iter->end); - iter->end = NULL; - iter->end_len = 0; - } -} - -static int iterator_reset_range( - git_iterator *iter, const char *start, const char *end) -{ - iterator_range_free(iter); - return iterator_range_init(iter, start, end); -} - -static int iterator_pathlist_init(git_iterator *iter, git_strarray *pathlist) -{ - size_t i; - - if (git_vector_init(&iter->pathlist, pathlist->count, NULL) < 0) - return -1; - - for (i = 0; i < pathlist->count; i++) { - if (!pathlist->strings[i]) - continue; - - if (git_vector_insert(&iter->pathlist, pathlist->strings[i]) < 0) - return -1; - } - - return 0; -} - -static int iterator_init_common( - git_iterator *iter, - git_repository *repo, - git_index *index, - git_iterator_options *given_opts) -{ - static git_iterator_options default_opts = GIT_ITERATOR_OPTIONS_INIT; - git_iterator_options *options = given_opts ? given_opts : &default_opts; - bool ignore_case; - int precompose; - int error; - - iter->repo = repo; - iter->index = index; - iter->flags = options->flags; - - if ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) { - ignore_case = true; - } else if ((iter->flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0) { - ignore_case = false; - } else if (repo) { - git_index *index; - - if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0) - return error; - - ignore_case = !!index->ignore_case; - - if (ignore_case == 1) - iter->flags |= GIT_ITERATOR_IGNORE_CASE; - else - iter->flags |= GIT_ITERATOR_DONT_IGNORE_CASE; - } else { - ignore_case = false; - } - - /* try to look up precompose and set flag if appropriate */ - if (repo && - (iter->flags & GIT_ITERATOR_PRECOMPOSE_UNICODE) == 0 && - (iter->flags & GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE) == 0) { - - if (git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) < 0) - git_error_clear(); - else if (precompose) - iter->flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; - } - - if ((iter->flags & GIT_ITERATOR_DONT_AUTOEXPAND)) - iter->flags |= GIT_ITERATOR_INCLUDE_TREES; - - if ((error = iterator_range_init(iter, options->start, options->end)) < 0 || - (error = iterator_pathlist_init(iter, &options->pathlist)) < 0) - return error; - - iterator_set_ignore_case(iter, ignore_case); - return 0; -} - -static void iterator_clear(git_iterator *iter) -{ - iter->started = false; - iter->ended = false; - iter->stat_calls = 0; - iter->pathlist_walk_idx = 0; - iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS; -} - -GIT_INLINE(bool) iterator_has_started( - git_iterator *iter, const char *path, bool is_submodule) -{ - size_t path_len; - - if (iter->start == NULL || iter->started == true) - return true; - - /* the starting path is generally a prefix - we have started once we - * are prefixed by this path - */ - iter->started = (iter->prefixcomp(path, iter->start) >= 0); - - if (iter->started) - return true; - - path_len = strlen(path); - - /* if, however, we are a submodule, then we support `start` being - * suffixed with a `/` for crazy legacy reasons. match `submod` - * with a start path of `submod/`. - */ - if (is_submodule && iter->start_len && path_len == iter->start_len - 1 && - iter->start[iter->start_len-1] == '/') - return true; - - /* if, however, our current path is a directory, and our starting path - * is _beneath_ that directory, then recurse into the directory (even - * though we have not yet "started") - */ - if (path_len > 0 && path[path_len-1] == '/' && - iter->strncomp(path, iter->start, path_len) == 0) - return true; - - return false; -} - -GIT_INLINE(bool) iterator_has_ended(git_iterator *iter, const char *path) -{ - if (iter->end == NULL) - return false; - else if (iter->ended) - return true; - - iter->ended = (iter->prefixcomp(path, iter->end) > 0); - return iter->ended; -} - -/* walker for the index and tree iterator that allows it to walk the sorted - * pathlist entries alongside sorted iterator entries. - */ -static bool iterator_pathlist_next_is(git_iterator *iter, const char *path) -{ - char *p; - size_t path_len, p_len, cmp_len, i; - int cmp; - - if (iter->pathlist.length == 0) - return true; - - git_vector_sort(&iter->pathlist); - - path_len = strlen(path); - - /* for comparison, drop the trailing slash on the current '/' */ - if (path_len && path[path_len-1] == '/') - path_len--; - - for (i = iter->pathlist_walk_idx; i < iter->pathlist.length; i++) { - p = iter->pathlist.contents[i]; - p_len = strlen(p); - - if (p_len && p[p_len-1] == '/') - p_len--; - - cmp_len = min(path_len, p_len); - - /* see if the pathlist entry is a prefix of this path */ - cmp = iter->strncomp(p, path, cmp_len); - - /* prefix match - see if there's an exact match, or if we were - * given a path that matches the directory - */ - if (cmp == 0) { - /* if this pathlist entry is not suffixed with a '/' then - * it matches a path that is a file or a directory. - * (eg, pathlist = "foo" and path is "foo" or "foo/" or - * "foo/something") - */ - if (p[cmp_len] == '\0' && - (path[cmp_len] == '\0' || path[cmp_len] == '/')) - return true; - - /* if this pathlist entry _is_ suffixed with a '/' then - * it matches only paths that are directories. - * (eg, pathlist = "foo/" and path is "foo/" or "foo/something") - */ - if (p[cmp_len] == '/' && path[cmp_len] == '/') - return true; - } - - /* this pathlist entry sorts before the given path, try the next */ - else if (cmp < 0) { - iter->pathlist_walk_idx++; - continue; - } - - /* this pathlist sorts after the given path, no match. */ - else if (cmp > 0) { - break; - } - } - - return false; -} - -typedef enum { - ITERATOR_PATHLIST_NONE = 0, - ITERATOR_PATHLIST_IS_FILE = 1, - ITERATOR_PATHLIST_IS_DIR = 2, - ITERATOR_PATHLIST_IS_PARENT = 3, - ITERATOR_PATHLIST_FULL = 4 -} iterator_pathlist_search_t; - -static iterator_pathlist_search_t iterator_pathlist_search( - git_iterator *iter, const char *path, size_t path_len) -{ - const char *p; - size_t idx; - int error; - - if (iter->pathlist.length == 0) - return ITERATOR_PATHLIST_FULL; - - git_vector_sort(&iter->pathlist); - - error = git_vector_bsearch2(&idx, &iter->pathlist, - (git_vector_cmp)iter->strcomp, path); - - /* the given path was found in the pathlist. since the pathlist only - * matches directories when they're suffixed with a '/', analyze the - * path string to determine whether it's a directory or not. - */ - if (error == 0) { - if (path_len && path[path_len-1] == '/') - return ITERATOR_PATHLIST_IS_DIR; - - return ITERATOR_PATHLIST_IS_FILE; - } - - /* at this point, the path we're examining may be a directory (though we - * don't know that yet, since we're avoiding a stat unless it's necessary) - * so walk the pathlist looking for the given path with a '/' after it, - */ - while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) { - if (iter->prefixcomp(p, path) != 0) - break; - - /* an exact match would have been matched by the bsearch above */ - GIT_ASSERT_WITH_RETVAL(p[path_len], ITERATOR_PATHLIST_NONE); - - /* is this a literal directory entry (eg `foo/`) or a file beneath */ - if (p[path_len] == '/') { - return (p[path_len+1] == '\0') ? - ITERATOR_PATHLIST_IS_DIR : - ITERATOR_PATHLIST_IS_PARENT; - } - - if (p[path_len] > '/') - break; - - idx++; - } - - return ITERATOR_PATHLIST_NONE; -} - -/* Empty iterator */ - -static int empty_iterator_noop(const git_index_entry **e, git_iterator *i) -{ - GIT_UNUSED(i); - - if (e) - *e = NULL; - - return GIT_ITEROVER; -} - -static int empty_iterator_advance_over( - const git_index_entry **e, - git_iterator_status_t *s, - git_iterator *i) -{ - *s = GIT_ITERATOR_STATUS_EMPTY; - return empty_iterator_noop(e, i); -} - -static int empty_iterator_reset(git_iterator *i) -{ - GIT_UNUSED(i); - return 0; -} - -static void empty_iterator_free(git_iterator *i) -{ - GIT_UNUSED(i); -} - -typedef struct { - git_iterator base; - git_iterator_callbacks cb; -} empty_iterator; - -int git_iterator_for_nothing( - git_iterator **out, - git_iterator_options *options) -{ - empty_iterator *iter; - - static git_iterator_callbacks callbacks = { - empty_iterator_noop, - empty_iterator_noop, - empty_iterator_noop, - empty_iterator_advance_over, - empty_iterator_reset, - empty_iterator_free - }; - - *out = NULL; - - iter = git__calloc(1, sizeof(empty_iterator)); - GIT_ERROR_CHECK_ALLOC(iter); - - iter->base.type = GIT_ITERATOR_EMPTY; - iter->base.cb = &callbacks; - iter->base.flags = options->flags; - - *out = &iter->base; - return 0; -} - -/* Tree iterator */ - -typedef struct { - git_tree_entry *tree_entry; - const char *parent_path; -} tree_iterator_entry; - -typedef struct { - git_tree *tree; - - /* path to this particular frame (folder) */ - git_str path; - - /* a sorted list of the entries for this frame (folder), these are - * actually pointers to the iterator's entry pool. - */ - git_vector entries; - tree_iterator_entry *current; - - size_t next_idx; - - /* on case insensitive iterations, we also have an array of other - * paths that were case insensitively equal to this one, and their - * tree objects. we have coalesced the tree entries into this frame. - * a child `tree_iterator_entry` will contain a pointer to its actual - * parent path. - */ - git_vector similar_trees; - git_array_t(git_str) similar_paths; -} tree_iterator_frame; - -typedef struct { - git_iterator base; - git_tree *root; - git_array_t(tree_iterator_frame) frames; - - git_index_entry entry; - git_str entry_path; - - /* a pool of entries to reduce the number of allocations */ - git_pool entry_pool; -} tree_iterator; - -GIT_INLINE(tree_iterator_frame *) tree_iterator_parent_frame( - tree_iterator *iter) -{ - return iter->frames.size > 1 ? - &iter->frames.ptr[iter->frames.size-2] : NULL; -} - -GIT_INLINE(tree_iterator_frame *) tree_iterator_current_frame( - tree_iterator *iter) -{ - return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL; -} - -GIT_INLINE(int) tree_entry_cmp( - const git_tree_entry *a, const git_tree_entry *b, bool icase) -{ - return git_fs_path_cmp( - a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE, - b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE, - icase ? git__strncasecmp : git__strncmp); -} - -GIT_INLINE(int) tree_iterator_entry_cmp_icase( - const void *ptr_a, const void *ptr_b) -{ - const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a; - const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b; - - return tree_entry_cmp(a->tree_entry, b->tree_entry, true); -} - -static int tree_iterator_entry_sort_icase(const void *ptr_a, const void *ptr_b) -{ - const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a; - const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b; - - int c = tree_entry_cmp(a->tree_entry, b->tree_entry, true); - - /* stabilize the sort order for filenames that are (case insensitively) - * the same by examining the parent path (case sensitively) before - * falling back to a case sensitive sort of the filename. - */ - if (!c && a->parent_path != b->parent_path) - c = git__strcmp(a->parent_path, b->parent_path); - - if (!c) - c = tree_entry_cmp(a->tree_entry, b->tree_entry, false); - - return c; -} - -static int tree_iterator_compute_path( - git_str *out, - tree_iterator_entry *entry) -{ - git_str_clear(out); - - if (entry->parent_path) - git_str_joinpath(out, entry->parent_path, entry->tree_entry->filename); - else - git_str_puts(out, entry->tree_entry->filename); - - if (git_tree_entry__is_tree(entry->tree_entry)) - git_str_putc(out, '/'); - - if (git_str_oom(out)) - return -1; - - return 0; -} - -static int tree_iterator_frame_init( - tree_iterator *iter, - git_tree *tree, - tree_iterator_entry *frame_entry) -{ - tree_iterator_frame *new_frame = NULL; - tree_iterator_entry *new_entry; - git_tree *dup = NULL; - git_tree_entry *tree_entry; - git_vector_cmp cmp; - size_t i; - int error = 0; - - new_frame = git_array_alloc(iter->frames); - GIT_ERROR_CHECK_ALLOC(new_frame); - - if ((error = git_tree_dup(&dup, tree)) < 0) - goto done; - - memset(new_frame, 0x0, sizeof(tree_iterator_frame)); - new_frame->tree = dup; - - if (frame_entry && - (error = tree_iterator_compute_path(&new_frame->path, frame_entry)) < 0) - goto done; - - cmp = iterator__ignore_case(&iter->base) ? - tree_iterator_entry_sort_icase : NULL; - - if ((error = git_vector_init(&new_frame->entries, - dup->entries.size, cmp)) < 0) - goto done; - - git_array_foreach(dup->entries, i, tree_entry) { - if ((new_entry = git_pool_malloc(&iter->entry_pool, 1)) == NULL) { - git_error_set_oom(); - error = -1; - goto done; - } - - new_entry->tree_entry = tree_entry; - new_entry->parent_path = new_frame->path.ptr; - - if ((error = git_vector_insert(&new_frame->entries, new_entry)) < 0) - goto done; - } - - git_vector_set_sorted(&new_frame->entries, - !iterator__ignore_case(&iter->base)); - -done: - if (error < 0) { - git_tree_free(dup); - git_array_pop(iter->frames); - } - - return error; -} - -GIT_INLINE(tree_iterator_entry *) tree_iterator_current_entry( - tree_iterator_frame *frame) -{ - return frame->current; -} - -GIT_INLINE(int) tree_iterator_frame_push_neighbors( - tree_iterator *iter, - tree_iterator_frame *parent_frame, - tree_iterator_frame *frame, - const char *filename) -{ - tree_iterator_entry *entry, *new_entry; - git_tree *tree = NULL; - git_tree_entry *tree_entry; - git_str *path; - size_t new_size, i; - int error = 0; - - while (parent_frame->next_idx < parent_frame->entries.length) { - entry = parent_frame->entries.contents[parent_frame->next_idx]; - - if (strcasecmp(filename, entry->tree_entry->filename) != 0) - break; - - if ((error = git_tree_lookup(&tree, - iter->base.repo, entry->tree_entry->oid)) < 0) - break; - - if (git_vector_insert(&parent_frame->similar_trees, tree) < 0) - break; - - path = git_array_alloc(parent_frame->similar_paths); - GIT_ERROR_CHECK_ALLOC(path); - - memset(path, 0, sizeof(git_str)); - - if ((error = tree_iterator_compute_path(path, entry)) < 0) - break; - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, - frame->entries.length, tree->entries.size); - git_vector_size_hint(&frame->entries, new_size); - - git_array_foreach(tree->entries, i, tree_entry) { - new_entry = git_pool_malloc(&iter->entry_pool, 1); - GIT_ERROR_CHECK_ALLOC(new_entry); - - new_entry->tree_entry = tree_entry; - new_entry->parent_path = path->ptr; - - if ((error = git_vector_insert(&frame->entries, new_entry)) < 0) - break; - } - - if (error) - break; - - parent_frame->next_idx++; - } - - return error; -} - -GIT_INLINE(int) tree_iterator_frame_push( - tree_iterator *iter, tree_iterator_entry *entry) -{ - tree_iterator_frame *parent_frame, *frame; - git_tree *tree = NULL; - int error; - - if ((error = git_tree_lookup(&tree, - iter->base.repo, entry->tree_entry->oid)) < 0 || - (error = tree_iterator_frame_init(iter, tree, entry)) < 0) - goto done; - - parent_frame = tree_iterator_parent_frame(iter); - frame = tree_iterator_current_frame(iter); - - /* if we're case insensitive, then we may have another directory that - * is (case insensitively) equal to this one. coalesce those children - * into this tree. - */ - if (iterator__ignore_case(&iter->base)) - error = tree_iterator_frame_push_neighbors(iter, - parent_frame, frame, entry->tree_entry->filename); - -done: - git_tree_free(tree); - return error; -} - -static int tree_iterator_frame_pop(tree_iterator *iter) -{ - tree_iterator_frame *frame; - git_str *buf = NULL; - git_tree *tree; - size_t i; - - GIT_ASSERT(iter->frames.size); - - frame = git_array_pop(iter->frames); - - git_vector_free(&frame->entries); - git_tree_free(frame->tree); - - do { - buf = git_array_pop(frame->similar_paths); - git_str_dispose(buf); - } while (buf != NULL); - - git_array_clear(frame->similar_paths); - - git_vector_foreach(&frame->similar_trees, i, tree) - git_tree_free(tree); - - git_vector_free(&frame->similar_trees); - - git_str_dispose(&frame->path); - - return 0; -} - -static int tree_iterator_current( - const git_index_entry **out, git_iterator *i) -{ - tree_iterator *iter = (tree_iterator *)i; - - if (!iterator__has_been_accessed(i)) - return iter->base.cb->advance(out, i); - - if (!iter->frames.size) { - *out = NULL; - return GIT_ITEROVER; - } - - *out = &iter->entry; - return 0; -} - -static void tree_iterator_set_current( - tree_iterator *iter, - tree_iterator_frame *frame, - tree_iterator_entry *entry) -{ - git_tree_entry *tree_entry = entry->tree_entry; - - frame->current = entry; - - memset(&iter->entry, 0x0, sizeof(git_index_entry)); - - iter->entry.mode = tree_entry->attr; - iter->entry.path = iter->entry_path.ptr; - git_oid_cpy(&iter->entry.id, tree_entry->oid); -} - -static int tree_iterator_advance(const git_index_entry **out, git_iterator *i) -{ - tree_iterator *iter = (tree_iterator *)i; - int error = 0; - - iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; - - /* examine tree entries until we find the next one to return */ - while (true) { - tree_iterator_entry *prev_entry, *entry; - tree_iterator_frame *frame; - bool is_tree; - - if ((frame = tree_iterator_current_frame(iter)) == NULL) { - error = GIT_ITEROVER; - break; - } - - /* no more entries in this frame. pop the frame out */ - if (frame->next_idx == frame->entries.length) { - if ((error = tree_iterator_frame_pop(iter)) < 0) - break; - - continue; - } - - /* we may have coalesced the contents of case-insensitively same-named - * directories, so do the sort now. - */ - if (frame->next_idx == 0 && !git_vector_is_sorted(&frame->entries)) - git_vector_sort(&frame->entries); - - /* we have more entries in the current frame, that's our next entry */ - prev_entry = tree_iterator_current_entry(frame); - entry = frame->entries.contents[frame->next_idx]; - frame->next_idx++; - - /* we can have collisions when iterating case insensitively. (eg, - * 'A/a' and 'a/A'). squash this one if it's already been seen. - */ - if (iterator__ignore_case(&iter->base) && - prev_entry && - tree_iterator_entry_cmp_icase(prev_entry, entry) == 0) - continue; - - if ((error = tree_iterator_compute_path(&iter->entry_path, entry)) < 0) - break; - - /* if this path is before our start, advance over this entry */ - if (!iterator_has_started(&iter->base, iter->entry_path.ptr, false)) - continue; - - /* if this path is after our end, stop */ - if (iterator_has_ended(&iter->base, iter->entry_path.ptr)) { - error = GIT_ITEROVER; - break; - } - - /* if we have a list of paths we're interested in, examine it */ - if (!iterator_pathlist_next_is(&iter->base, iter->entry_path.ptr)) - continue; - - is_tree = git_tree_entry__is_tree(entry->tree_entry); - - /* if we are *not* including trees then advance over this entry */ - if (is_tree && !iterator__include_trees(iter)) { - - /* if we've found a tree (and are not returning it to the caller) - * and we are autoexpanding, then we want to return the first - * child. push the new directory and advance. - */ - if (iterator__do_autoexpand(iter)) { - if ((error = tree_iterator_frame_push(iter, entry)) < 0) - break; - } - - continue; - } - - tree_iterator_set_current(iter, frame, entry); - - /* if we are autoexpanding, then push this as a new frame, so that - * the next call to `advance` will dive into this directory. - */ - if (is_tree && iterator__do_autoexpand(iter)) - error = tree_iterator_frame_push(iter, entry); - - break; - } - - if (out) - *out = (error == 0) ? &iter->entry : NULL; - - return error; -} - -static int tree_iterator_advance_into( - const git_index_entry **out, git_iterator *i) -{ - tree_iterator *iter = (tree_iterator *)i; - tree_iterator_frame *frame; - tree_iterator_entry *prev_entry; - int error; - - if (out) - *out = NULL; - - if ((frame = tree_iterator_current_frame(iter)) == NULL) - return GIT_ITEROVER; - - /* get the last seen entry */ - prev_entry = tree_iterator_current_entry(frame); - - /* it's legal to call advance_into when auto-expand is on. in this case, - * we will have pushed a new (empty) frame on to the stack for this - * new directory. since it's empty, its current_entry should be null. - */ - GIT_ASSERT(iterator__do_autoexpand(i) ^ (prev_entry != NULL)); - - if (prev_entry) { - if (!git_tree_entry__is_tree(prev_entry->tree_entry)) - return 0; - - if ((error = tree_iterator_frame_push(iter, prev_entry)) < 0) - return error; - } - - /* we've advanced into the directory in question, let advance - * find the first entry - */ - return tree_iterator_advance(out, i); -} - -static int tree_iterator_advance_over( - const git_index_entry **out, - git_iterator_status_t *status, - git_iterator *i) -{ - *status = GIT_ITERATOR_STATUS_NORMAL; - return git_iterator_advance(out, i); -} - -static void tree_iterator_clear(tree_iterator *iter) -{ - while (iter->frames.size) - tree_iterator_frame_pop(iter); - - git_array_clear(iter->frames); - - git_pool_clear(&iter->entry_pool); - git_str_clear(&iter->entry_path); - - iterator_clear(&iter->base); -} - -static int tree_iterator_init(tree_iterator *iter) -{ - int error; - - if ((error = git_pool_init(&iter->entry_pool, sizeof(tree_iterator_entry))) < 0 || - (error = tree_iterator_frame_init(iter, iter->root, NULL)) < 0) - return error; - - iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; - - return 0; -} - -static int tree_iterator_reset(git_iterator *i) -{ - tree_iterator *iter = (tree_iterator *)i; - - tree_iterator_clear(iter); - return tree_iterator_init(iter); -} - -static void tree_iterator_free(git_iterator *i) -{ - tree_iterator *iter = (tree_iterator *)i; - - tree_iterator_clear(iter); - - git_tree_free(iter->root); - git_str_dispose(&iter->entry_path); -} - -int git_iterator_for_tree( - git_iterator **out, - git_tree *tree, - git_iterator_options *options) -{ - tree_iterator *iter; - int error; - - static git_iterator_callbacks callbacks = { - tree_iterator_current, - tree_iterator_advance, - tree_iterator_advance_into, - tree_iterator_advance_over, - tree_iterator_reset, - tree_iterator_free - }; - - *out = NULL; - - if (tree == NULL) - return git_iterator_for_nothing(out, options); - - iter = git__calloc(1, sizeof(tree_iterator)); - GIT_ERROR_CHECK_ALLOC(iter); - - iter->base.type = GIT_ITERATOR_TREE; - iter->base.cb = &callbacks; - - if ((error = iterator_init_common(&iter->base, - git_tree_owner(tree), NULL, options)) < 0 || - (error = git_tree_dup(&iter->root, tree)) < 0 || - (error = tree_iterator_init(iter)) < 0) - goto on_error; - - *out = &iter->base; - return 0; - -on_error: - git_iterator_free(&iter->base); - return error; -} - -int git_iterator_current_tree_entry( - const git_tree_entry **tree_entry, git_iterator *i) -{ - tree_iterator *iter; - tree_iterator_frame *frame; - tree_iterator_entry *entry; - - GIT_ASSERT(i->type == GIT_ITERATOR_TREE); - - iter = (tree_iterator *)i; - - frame = tree_iterator_current_frame(iter); - entry = tree_iterator_current_entry(frame); - - *tree_entry = entry->tree_entry; - return 0; -} - -int git_iterator_current_parent_tree( - const git_tree **parent_tree, git_iterator *i, size_t depth) -{ - tree_iterator *iter; - tree_iterator_frame *frame; - - GIT_ASSERT(i->type == GIT_ITERATOR_TREE); - - iter = (tree_iterator *)i; - - GIT_ASSERT(depth < iter->frames.size); - frame = &iter->frames.ptr[iter->frames.size-depth-1]; - - *parent_tree = frame->tree; - return 0; -} - -/* Filesystem iterator */ - -typedef struct { - struct stat st; - size_t path_len; - iterator_pathlist_search_t match; - git_oid id; - char path[GIT_FLEX_ARRAY]; -} filesystem_iterator_entry; - -typedef struct { - git_vector entries; - git_pool entry_pool; - size_t next_idx; - - size_t path_len; - int is_ignored; -} filesystem_iterator_frame; - -typedef struct { - git_iterator base; - char *root; - size_t root_len; - - unsigned int dirload_flags; - - git_tree *tree; - git_index *index; - git_vector index_snapshot; - - git_array_t(filesystem_iterator_frame) frames; - git_ignores ignores; - - /* info about the current entry */ - git_index_entry entry; - git_str current_path; - int current_is_ignored; - - /* temporary buffer for advance_over */ - git_str tmp_buf; -} filesystem_iterator; - - -GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_parent_frame( - filesystem_iterator *iter) -{ - return iter->frames.size > 1 ? - &iter->frames.ptr[iter->frames.size-2] : NULL; -} - -GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_current_frame( - filesystem_iterator *iter) -{ - return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL; -} - -GIT_INLINE(filesystem_iterator_entry *) filesystem_iterator_current_entry( - filesystem_iterator_frame *frame) -{ - return frame->next_idx == 0 ? - NULL : frame->entries.contents[frame->next_idx-1]; -} - -static int filesystem_iterator_entry_cmp(const void *_a, const void *_b) -{ - const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a; - const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b; - - return git__strcmp(a->path, b->path); -} - -static int filesystem_iterator_entry_cmp_icase(const void *_a, const void *_b) -{ - const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a; - const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b; - - return git__strcasecmp(a->path, b->path); -} - -#define FILESYSTEM_MAX_DEPTH 100 - -/** - * Figure out if an entry is a submodule. - * - * We consider it a submodule if the path is listed as a submodule in - * either the tree or the index. - */ -static int filesystem_iterator_is_submodule( - bool *out, filesystem_iterator *iter, const char *path, size_t path_len) -{ - bool is_submodule = false; - int error; - - *out = false; - - /* first see if this path is a submodule in HEAD */ - if (iter->tree) { - git_tree_entry *entry; - - error = git_tree_entry_bypath(&entry, iter->tree, path); - - if (error < 0 && error != GIT_ENOTFOUND) - return error; - - if (!error) { - is_submodule = (entry->attr == GIT_FILEMODE_COMMIT); - git_tree_entry_free(entry); - } - } - - if (!is_submodule && iter->base.index) { - size_t pos; - - error = git_index_snapshot_find(&pos, - &iter->index_snapshot, iter->base.entry_srch, path, path_len, 0); - - if (error < 0 && error != GIT_ENOTFOUND) - return error; - - if (!error) { - git_index_entry *e = git_vector_get(&iter->index_snapshot, pos); - is_submodule = (e->mode == GIT_FILEMODE_COMMIT); - } - } - - *out = is_submodule; - return 0; -} - -static void filesystem_iterator_frame_push_ignores( - filesystem_iterator *iter, - filesystem_iterator_entry *frame_entry, - filesystem_iterator_frame *new_frame) -{ - filesystem_iterator_frame *previous_frame; - const char *path = frame_entry ? frame_entry->path : ""; - - if (!iterator__honor_ignores(&iter->base)) - return; - - if (git_ignore__lookup(&new_frame->is_ignored, - &iter->ignores, path, GIT_DIR_FLAG_TRUE) < 0) { - git_error_clear(); - new_frame->is_ignored = GIT_IGNORE_NOTFOUND; - } - - /* if this is not the top level directory... */ - if (frame_entry) { - const char *relative_path; - - previous_frame = filesystem_iterator_parent_frame(iter); - - /* push new ignores for files in this directory */ - relative_path = frame_entry->path + previous_frame->path_len; - - /* inherit ignored from parent if no rule specified */ - if (new_frame->is_ignored <= GIT_IGNORE_NOTFOUND) - new_frame->is_ignored = previous_frame->is_ignored; - - git_ignore__push_dir(&iter->ignores, relative_path); - } -} - -static void filesystem_iterator_frame_pop_ignores( - filesystem_iterator *iter) -{ - if (iterator__honor_ignores(&iter->base)) - git_ignore__pop_dir(&iter->ignores); -} - -GIT_INLINE(bool) filesystem_iterator_examine_path( - bool *is_dir_out, - iterator_pathlist_search_t *match_out, - filesystem_iterator *iter, - filesystem_iterator_entry *frame_entry, - const char *path, - size_t path_len) -{ - bool is_dir = 0; - iterator_pathlist_search_t match = ITERATOR_PATHLIST_FULL; - - *is_dir_out = false; - *match_out = ITERATOR_PATHLIST_NONE; - - if (iter->base.start_len) { - int cmp = iter->base.strncomp(path, iter->base.start, path_len); - - /* we haven't stat'ed `path` yet, so we don't yet know if it's a - * directory or not. special case if the current path may be a - * directory that matches the start prefix. - */ - if (cmp == 0) { - if (iter->base.start[path_len] == '/') - is_dir = true; - - else if (iter->base.start[path_len] != '\0') - cmp = -1; - } - - if (cmp < 0) - return false; - } - - if (iter->base.end_len) { - int cmp = iter->base.strncomp(path, iter->base.end, iter->base.end_len); - - if (cmp > 0) - return false; - } - - /* if we have a pathlist that we're limiting to, examine this path now - * to avoid a `stat` if we're not interested in the path. - */ - if (iter->base.pathlist.length) { - /* if our parent was explicitly included, so too are we */ - if (frame_entry && frame_entry->match != ITERATOR_PATHLIST_IS_PARENT) - match = ITERATOR_PATHLIST_FULL; - else - match = iterator_pathlist_search(&iter->base, path, path_len); - - if (match == ITERATOR_PATHLIST_NONE) - return false; - - /* Ensure that the pathlist entry lines up with what we expected */ - if (match == ITERATOR_PATHLIST_IS_DIR || - match == ITERATOR_PATHLIST_IS_PARENT) - is_dir = true; - } - - *is_dir_out = is_dir; - *match_out = match; - return true; -} - -GIT_INLINE(bool) filesystem_iterator_is_dot_git( - filesystem_iterator *iter, const char *path, size_t path_len) -{ - size_t len; - - if (!iterator__ignore_dot_git(&iter->base)) - return false; - - if ((len = path_len) < 4) - return false; - - if (path[len - 1] == '/') - len--; - - if (git__tolower(path[len - 1]) != 't' || - git__tolower(path[len - 2]) != 'i' || - git__tolower(path[len - 3]) != 'g' || - git__tolower(path[len - 4]) != '.') - return false; - - return (len == 4 || path[len - 5] == '/'); -} - -static int filesystem_iterator_entry_hash( - filesystem_iterator *iter, - filesystem_iterator_entry *entry) -{ - git_str fullpath = GIT_STR_INIT; - int error; - - if (S_ISDIR(entry->st.st_mode)) { - memset(&entry->id, 0, GIT_OID_RAWSZ); - return 0; - } - - if (iter->base.type == GIT_ITERATOR_WORKDIR) - return git_repository_hashfile(&entry->id, - iter->base.repo, entry->path, GIT_OBJECT_BLOB, NULL); - - if (!(error = git_str_joinpath(&fullpath, iter->root, entry->path)) && - !(error = git_path_validate_str_length(iter->base.repo, &fullpath))) - error = git_odb_hashfile(&entry->id, fullpath.ptr, GIT_OBJECT_BLOB); - - git_str_dispose(&fullpath); - return error; -} - -static int filesystem_iterator_entry_init( - filesystem_iterator_entry **out, - filesystem_iterator *iter, - filesystem_iterator_frame *frame, - const char *path, - size_t path_len, - struct stat *statbuf, - iterator_pathlist_search_t pathlist_match) -{ - filesystem_iterator_entry *entry; - size_t entry_size; - int error = 0; - - *out = NULL; - - /* Make sure to append two bytes, one for the path's null - * termination, one for a possible trailing '/' for folders. - */ - GIT_ERROR_CHECK_ALLOC_ADD(&entry_size, - sizeof(filesystem_iterator_entry), path_len); - GIT_ERROR_CHECK_ALLOC_ADD(&entry_size, entry_size, 2); - - entry = git_pool_malloc(&frame->entry_pool, entry_size); - GIT_ERROR_CHECK_ALLOC(entry); - - entry->path_len = path_len; - entry->match = pathlist_match; - memcpy(entry->path, path, path_len); - memcpy(&entry->st, statbuf, sizeof(struct stat)); - - /* Suffix directory paths with a '/' */ - if (S_ISDIR(entry->st.st_mode)) - entry->path[entry->path_len++] = '/'; - - entry->path[entry->path_len] = '\0'; - - if (iter->base.flags & GIT_ITERATOR_INCLUDE_HASH) - error = filesystem_iterator_entry_hash(iter, entry); - - if (!error) - *out = entry; - - return error; -} - -static int filesystem_iterator_frame_push( - filesystem_iterator *iter, - filesystem_iterator_entry *frame_entry) -{ - filesystem_iterator_frame *new_frame = NULL; - git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT; - git_str root = GIT_STR_INIT; - const char *path; - filesystem_iterator_entry *entry; - struct stat statbuf; - size_t path_len; - int error; - - if (iter->frames.size == FILESYSTEM_MAX_DEPTH) { - git_error_set(GIT_ERROR_REPOSITORY, - "directory nesting too deep (%"PRIuZ")", iter->frames.size); - return -1; - } - - new_frame = git_array_alloc(iter->frames); - GIT_ERROR_CHECK_ALLOC(new_frame); - - memset(new_frame, 0, sizeof(filesystem_iterator_frame)); - - if (frame_entry) - git_str_joinpath(&root, iter->root, frame_entry->path); - else - git_str_puts(&root, iter->root); - - if (git_str_oom(&root) || - git_path_validate_str_length(iter->base.repo, &root) < 0) { - error = -1; - goto done; - } - - new_frame->path_len = frame_entry ? frame_entry->path_len : 0; - - /* Any error here is equivalent to the dir not existing, skip over it */ - if ((error = git_fs_path_diriter_init( - &diriter, root.ptr, iter->dirload_flags)) < 0) { - error = GIT_ENOTFOUND; - goto done; - } - - if ((error = git_vector_init(&new_frame->entries, 64, - iterator__ignore_case(&iter->base) ? - filesystem_iterator_entry_cmp_icase : - filesystem_iterator_entry_cmp)) < 0) - goto done; - - if ((error = git_pool_init(&new_frame->entry_pool, 1)) < 0) - goto done; - - /* check if this directory is ignored */ - filesystem_iterator_frame_push_ignores(iter, frame_entry, new_frame); - - while ((error = git_fs_path_diriter_next(&diriter)) == 0) { - iterator_pathlist_search_t pathlist_match = ITERATOR_PATHLIST_FULL; - git_str path_str = GIT_STR_INIT; - bool dir_expected = false; - - if ((error = git_fs_path_diriter_fullpath(&path, &path_len, &diriter)) < 0) - goto done; - - path_str.ptr = (char *)path; - path_str.size = path_len; - - if ((error = git_path_validate_str_length(iter->base.repo, &path_str)) < 0) - goto done; - - GIT_ASSERT(path_len > iter->root_len); - - /* remove the prefix if requested */ - path += iter->root_len; - path_len -= iter->root_len; - - /* examine start / end and the pathlist to see if this path is in it. - * note that since we haven't yet stat'ed the path, we cannot know - * whether it's a directory yet or not, so this can give us an - * expected type (S_IFDIR or S_IFREG) that we should examine) - */ - if (!filesystem_iterator_examine_path(&dir_expected, &pathlist_match, - iter, frame_entry, path, path_len)) - continue; - - /* TODO: don't need to stat if assume unchanged for this path and - * we have an index, we can just copy the data out of it. - */ - - if ((error = git_fs_path_diriter_stat(&statbuf, &diriter)) < 0) { - /* file was removed between readdir and lstat */ - if (error == GIT_ENOTFOUND) - continue; - - /* treat the file as unreadable */ - memset(&statbuf, 0, sizeof(statbuf)); - statbuf.st_mode = GIT_FILEMODE_UNREADABLE; - - error = 0; - } - - iter->base.stat_calls++; - - /* Ignore wacky things in the filesystem */ - if (!S_ISDIR(statbuf.st_mode) && - !S_ISREG(statbuf.st_mode) && - !S_ISLNK(statbuf.st_mode) && - statbuf.st_mode != GIT_FILEMODE_UNREADABLE) - continue; - - if (filesystem_iterator_is_dot_git(iter, path, path_len)) - continue; - - /* convert submodules to GITLINK and remove trailing slashes */ - if (S_ISDIR(statbuf.st_mode)) { - bool submodule = false; - - if ((error = filesystem_iterator_is_submodule(&submodule, - iter, path, path_len)) < 0) - goto done; - - if (submodule) - statbuf.st_mode = GIT_FILEMODE_COMMIT; - } - - /* Ensure that the pathlist entry lines up with what we expected */ - else if (dir_expected) - continue; - - if ((error = filesystem_iterator_entry_init(&entry, - iter, new_frame, path, path_len, &statbuf, pathlist_match)) < 0) - goto done; - - git_vector_insert(&new_frame->entries, entry); - } - - if (error == GIT_ITEROVER) - error = 0; - - /* sort now that directory suffix is added */ - git_vector_sort(&new_frame->entries); - -done: - if (error < 0) - git_array_pop(iter->frames); - - git_str_dispose(&root); - git_fs_path_diriter_free(&diriter); - return error; -} - -GIT_INLINE(int) filesystem_iterator_frame_pop(filesystem_iterator *iter) -{ - filesystem_iterator_frame *frame; - - GIT_ASSERT(iter->frames.size); - - frame = git_array_pop(iter->frames); - filesystem_iterator_frame_pop_ignores(iter); - - git_pool_clear(&frame->entry_pool); - git_vector_free(&frame->entries); - - return 0; -} - -static void filesystem_iterator_set_current( - filesystem_iterator *iter, - filesystem_iterator_entry *entry) -{ - /* - * Index entries are limited to 32 bit timestamps. We can safely - * cast this since workdir times are only used in the cache; any - * mismatch will cause a hash recomputation which is unfortunate - * but affects only people who set their filetimes to 2038. - * (Same with the file size.) - */ - iter->entry.ctime.seconds = (int32_t)entry->st.st_ctime; - iter->entry.mtime.seconds = (int32_t)entry->st.st_mtime; - -#if defined(GIT_USE_NSEC) - iter->entry.ctime.nanoseconds = entry->st.st_ctime_nsec; - iter->entry.mtime.nanoseconds = entry->st.st_mtime_nsec; -#else - iter->entry.ctime.nanoseconds = 0; - iter->entry.mtime.nanoseconds = 0; -#endif - - iter->entry.dev = entry->st.st_dev; - iter->entry.ino = entry->st.st_ino; - iter->entry.mode = git_futils_canonical_mode(entry->st.st_mode); - iter->entry.uid = entry->st.st_uid; - iter->entry.gid = entry->st.st_gid; - iter->entry.file_size = (uint32_t)entry->st.st_size; - - if (iter->base.flags & GIT_ITERATOR_INCLUDE_HASH) - git_oid_cpy(&iter->entry.id, &entry->id); - - iter->entry.path = entry->path; - - iter->current_is_ignored = GIT_IGNORE_UNCHECKED; -} - -static int filesystem_iterator_current( - const git_index_entry **out, git_iterator *i) -{ - filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - - if (!iterator__has_been_accessed(i)) - return iter->base.cb->advance(out, i); - - if (!iter->frames.size) { - *out = NULL; - return GIT_ITEROVER; - } - - *out = &iter->entry; - return 0; -} - -static int filesystem_iterator_is_dir( - bool *is_dir, - const filesystem_iterator *iter, - const filesystem_iterator_entry *entry) -{ - struct stat st; - git_str fullpath = GIT_STR_INIT; - int error = 0; - - if (S_ISDIR(entry->st.st_mode)) { - *is_dir = 1; - goto done; - } - - if (!iterator__descend_symlinks(iter) || !S_ISLNK(entry->st.st_mode)) { - *is_dir = 0; - goto done; - } - - if ((error = git_str_joinpath(&fullpath, iter->root, entry->path)) < 0 || - (error = git_path_validate_str_length(iter->base.repo, &fullpath)) < 0 || - (error = p_stat(fullpath.ptr, &st)) < 0) - goto done; - - *is_dir = S_ISDIR(st.st_mode); - -done: - git_str_dispose(&fullpath); - return error; -} - -static int filesystem_iterator_advance( - const git_index_entry **out, git_iterator *i) -{ - filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - bool is_dir; - int error = 0; - - iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; - - /* examine filesystem entries until we find the next one to return */ - while (true) { - filesystem_iterator_frame *frame; - filesystem_iterator_entry *entry; - - if ((frame = filesystem_iterator_current_frame(iter)) == NULL) { - error = GIT_ITEROVER; - break; - } - - /* no more entries in this frame. pop the frame out */ - if (frame->next_idx == frame->entries.length) { - filesystem_iterator_frame_pop(iter); - continue; - } - - /* we have more entries in the current frame, that's our next entry */ - entry = frame->entries.contents[frame->next_idx]; - frame->next_idx++; - - if ((error = filesystem_iterator_is_dir(&is_dir, iter, entry)) < 0) - break; - - if (is_dir) { - if (iterator__do_autoexpand(iter)) { - error = filesystem_iterator_frame_push(iter, entry); - - /* may get GIT_ENOTFOUND due to races or permission problems - * that we want to quietly swallow - */ - if (error == GIT_ENOTFOUND) - continue; - else if (error < 0) - break; - } - - if (!iterator__include_trees(iter)) - continue; - } - - filesystem_iterator_set_current(iter, entry); - break; - } - - if (out) - *out = (error == 0) ? &iter->entry : NULL; - - return error; -} - -static int filesystem_iterator_advance_into( - const git_index_entry **out, git_iterator *i) -{ - filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - filesystem_iterator_frame *frame; - filesystem_iterator_entry *prev_entry; - int error; - - if (out) - *out = NULL; - - if ((frame = filesystem_iterator_current_frame(iter)) == NULL) - return GIT_ITEROVER; - - /* get the last seen entry */ - prev_entry = filesystem_iterator_current_entry(frame); - - /* it's legal to call advance_into when auto-expand is on. in this case, - * we will have pushed a new (empty) frame on to the stack for this - * new directory. since it's empty, its current_entry should be null. - */ - GIT_ASSERT(iterator__do_autoexpand(i) ^ (prev_entry != NULL)); - - if (prev_entry) { - if (prev_entry->st.st_mode != GIT_FILEMODE_COMMIT && - !S_ISDIR(prev_entry->st.st_mode)) - return 0; - - if ((error = filesystem_iterator_frame_push(iter, prev_entry)) < 0) - return error; - } - - /* we've advanced into the directory in question, let advance - * find the first entry - */ - return filesystem_iterator_advance(out, i); -} - -int git_iterator_current_workdir_path(git_str **out, git_iterator *i) -{ - filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - const git_index_entry *entry; - - if (i->type != GIT_ITERATOR_FS && - i->type != GIT_ITERATOR_WORKDIR) { - *out = NULL; - return 0; - } - - git_str_truncate(&iter->current_path, iter->root_len); - - if (git_iterator_current(&entry, i) < 0 || - git_str_puts(&iter->current_path, entry->path) < 0) - return -1; - - *out = &iter->current_path; - return 0; -} - -GIT_INLINE(git_dir_flag) entry_dir_flag(git_index_entry *entry) -{ -#if defined(GIT_WIN32) && !defined(__MINGW32__) - return (entry && entry->mode) ? - (S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE) : - GIT_DIR_FLAG_UNKNOWN; -#else - GIT_UNUSED(entry); - return GIT_DIR_FLAG_UNKNOWN; -#endif -} - -static void filesystem_iterator_update_ignored(filesystem_iterator *iter) -{ - filesystem_iterator_frame *frame; - git_dir_flag dir_flag = entry_dir_flag(&iter->entry); - - if (git_ignore__lookup(&iter->current_is_ignored, - &iter->ignores, iter->entry.path, dir_flag) < 0) { - git_error_clear(); - iter->current_is_ignored = GIT_IGNORE_NOTFOUND; - } - - /* use ignore from containing frame stack */ - if (iter->current_is_ignored <= GIT_IGNORE_NOTFOUND) { - frame = filesystem_iterator_current_frame(iter); - iter->current_is_ignored = frame->is_ignored; - } -} - -GIT_INLINE(bool) filesystem_iterator_current_is_ignored( - filesystem_iterator *iter) -{ - if (iter->current_is_ignored == GIT_IGNORE_UNCHECKED) - filesystem_iterator_update_ignored(iter); - - return (iter->current_is_ignored == GIT_IGNORE_TRUE); -} - -bool git_iterator_current_is_ignored(git_iterator *i) -{ - filesystem_iterator *iter = NULL; - - if (i->type != GIT_ITERATOR_WORKDIR) - return false; - - iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - - return filesystem_iterator_current_is_ignored(iter); -} - -bool git_iterator_current_tree_is_ignored(git_iterator *i) -{ - filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - filesystem_iterator_frame *frame; - - if (i->type != GIT_ITERATOR_WORKDIR) - return false; - - frame = filesystem_iterator_current_frame(iter); - return (frame->is_ignored == GIT_IGNORE_TRUE); -} - -static int filesystem_iterator_advance_over( - const git_index_entry **out, - git_iterator_status_t *status, - git_iterator *i) -{ - filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - filesystem_iterator_frame *current_frame; - filesystem_iterator_entry *current_entry; - const git_index_entry *entry = NULL; - const char *base; - int error = 0; - - *out = NULL; - *status = GIT_ITERATOR_STATUS_NORMAL; - - GIT_ASSERT(iterator__has_been_accessed(i)); - - current_frame = filesystem_iterator_current_frame(iter); - GIT_ASSERT(current_frame); - - current_entry = filesystem_iterator_current_entry(current_frame); - GIT_ASSERT(current_entry); - - if ((error = git_iterator_current(&entry, i)) < 0) - return error; - - if (!S_ISDIR(entry->mode)) { - if (filesystem_iterator_current_is_ignored(iter)) - *status = GIT_ITERATOR_STATUS_IGNORED; - - return filesystem_iterator_advance(out, i); - } - - git_str_clear(&iter->tmp_buf); - if ((error = git_str_puts(&iter->tmp_buf, entry->path)) < 0) - return error; - - base = iter->tmp_buf.ptr; - - /* scan inside the directory looking for files. if we find nothing, - * we will remain EMPTY. if we find any ignored item, upgrade EMPTY to - * IGNORED. if we find a real actual item, upgrade all the way to NORMAL - * and then stop. - * - * however, if we're here looking for a pathlist item (but are not - * actually in the pathlist ourselves) then start at FILTERED instead of - * EMPTY. callers then know that this path was not something they asked - * about. - */ - *status = current_entry->match == ITERATOR_PATHLIST_IS_PARENT ? - GIT_ITERATOR_STATUS_FILTERED : GIT_ITERATOR_STATUS_EMPTY; - - while (entry && !iter->base.prefixcomp(entry->path, base)) { - if (filesystem_iterator_current_is_ignored(iter)) { - /* if we found an explicitly ignored item, then update from - * EMPTY to IGNORED - */ - *status = GIT_ITERATOR_STATUS_IGNORED; - } else if (S_ISDIR(entry->mode)) { - error = filesystem_iterator_advance_into(&entry, i); - - if (!error) - continue; - - /* this directory disappeared, ignore it */ - else if (error == GIT_ENOTFOUND) - error = 0; - - /* a real error occurred */ - else - break; - } else { - /* we found a non-ignored item, treat parent as untracked */ - *status = GIT_ITERATOR_STATUS_NORMAL; - break; - } - - if ((error = git_iterator_advance(&entry, i)) < 0) - break; - } - - /* wrap up scan back to base directory */ - while (entry && !iter->base.prefixcomp(entry->path, base)) { - if ((error = git_iterator_advance(&entry, i)) < 0) - break; - } - - if (!error) - *out = entry; - - return error; -} - -static void filesystem_iterator_clear(filesystem_iterator *iter) -{ - while (iter->frames.size) - filesystem_iterator_frame_pop(iter); - - git_array_clear(iter->frames); - git_ignore__free(&iter->ignores); - - git_str_dispose(&iter->tmp_buf); - - iterator_clear(&iter->base); -} - -static int filesystem_iterator_init(filesystem_iterator *iter) -{ - int error; - - if (iterator__honor_ignores(&iter->base) && - (error = git_ignore__for_path(iter->base.repo, - ".gitignore", &iter->ignores)) < 0) - return error; - - if ((error = filesystem_iterator_frame_push(iter, NULL)) < 0) - return error; - - iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; - - return 0; -} - -static int filesystem_iterator_reset(git_iterator *i) -{ - filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - - filesystem_iterator_clear(iter); - return filesystem_iterator_init(iter); -} - -static void filesystem_iterator_free(git_iterator *i) -{ - filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); - git__free(iter->root); - git_str_dispose(&iter->current_path); - git_tree_free(iter->tree); - if (iter->index) - git_index_snapshot_release(&iter->index_snapshot, iter->index); - filesystem_iterator_clear(iter); -} - -static int iterator_for_filesystem( - git_iterator **out, - git_repository *repo, - const char *root, - git_index *index, - git_tree *tree, - git_iterator_t type, - git_iterator_options *options) -{ - filesystem_iterator *iter; - size_t root_len; - int error; - - static git_iterator_callbacks callbacks = { - filesystem_iterator_current, - filesystem_iterator_advance, - filesystem_iterator_advance_into, - filesystem_iterator_advance_over, - filesystem_iterator_reset, - filesystem_iterator_free - }; - - *out = NULL; - - if (root == NULL) - return git_iterator_for_nothing(out, options); - - iter = git__calloc(1, sizeof(filesystem_iterator)); - GIT_ERROR_CHECK_ALLOC(iter); - - iter->base.type = type; - iter->base.cb = &callbacks; - - root_len = strlen(root); - - iter->root = git__malloc(root_len+2); - GIT_ERROR_CHECK_ALLOC(iter->root); - - memcpy(iter->root, root, root_len); - - if (root_len == 0 || root[root_len-1] != '/') { - iter->root[root_len] = '/'; - root_len++; - } - iter->root[root_len] = '\0'; - iter->root_len = root_len; - - if ((error = git_str_puts(&iter->current_path, iter->root)) < 0) - goto on_error; - - if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0) - goto on_error; - - if (tree && (error = git_tree_dup(&iter->tree, tree)) < 0) - goto on_error; - - if (index && - (error = git_index_snapshot_new(&iter->index_snapshot, index)) < 0) - goto on_error; - - iter->index = index; - iter->dirload_flags = - (iterator__ignore_case(&iter->base) ? - GIT_FS_PATH_DIR_IGNORE_CASE : 0) | - (iterator__flag(&iter->base, PRECOMPOSE_UNICODE) ? - GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE : 0); - - if ((error = filesystem_iterator_init(iter)) < 0) - goto on_error; - - *out = &iter->base; - return 0; - -on_error: - git_iterator_free(&iter->base); - return error; -} - -int git_iterator_for_filesystem( - git_iterator **out, - const char *root, - git_iterator_options *options) -{ - return iterator_for_filesystem(out, - NULL, root, NULL, NULL, GIT_ITERATOR_FS, options); -} - -int git_iterator_for_workdir_ext( - git_iterator **out, - git_repository *repo, - const char *repo_workdir, - git_index *index, - git_tree *tree, - git_iterator_options *given_opts) -{ - git_iterator_options options = GIT_ITERATOR_OPTIONS_INIT; - - if (!repo_workdir) { - if (git_repository__ensure_not_bare(repo, "scan working directory") < 0) - return GIT_EBAREREPO; - - repo_workdir = git_repository_workdir(repo); - } - - /* upgrade to a workdir iterator, adding necessary internal flags */ - if (given_opts) - memcpy(&options, given_opts, sizeof(git_iterator_options)); - - options.flags |= GIT_ITERATOR_HONOR_IGNORES | - GIT_ITERATOR_IGNORE_DOT_GIT; - - return iterator_for_filesystem(out, - repo, repo_workdir, index, tree, GIT_ITERATOR_WORKDIR, &options); -} - - -/* Index iterator */ - - -typedef struct { - git_iterator base; - git_vector entries; - size_t next_idx; - - /* the pseudotree entry */ - git_index_entry tree_entry; - git_str tree_buf; - bool skip_tree; - - const git_index_entry *entry; -} index_iterator; - -static int index_iterator_current( - const git_index_entry **out, git_iterator *i) -{ - index_iterator *iter = (index_iterator *)i; - - if (!iterator__has_been_accessed(i)) - return iter->base.cb->advance(out, i); - - if (iter->entry == NULL) { - *out = NULL; - return GIT_ITEROVER; - } - - *out = iter->entry; - return 0; -} - -static bool index_iterator_create_pseudotree( - const git_index_entry **out, - index_iterator *iter, - const char *path) -{ - const char *prev_path, *relative_path, *dirsep; - size_t common_len; - - prev_path = iter->entry ? iter->entry->path : ""; - - /* determine if the new path is in a different directory from the old */ - common_len = git_fs_path_common_dirlen(prev_path, path); - relative_path = path + common_len; - - if ((dirsep = strchr(relative_path, '/')) == NULL) - return false; - - git_str_clear(&iter->tree_buf); - git_str_put(&iter->tree_buf, path, (dirsep - path) + 1); - - iter->tree_entry.mode = GIT_FILEMODE_TREE; - iter->tree_entry.path = iter->tree_buf.ptr; - - *out = &iter->tree_entry; - return true; -} - -static int index_iterator_skip_pseudotree(index_iterator *iter) -{ - GIT_ASSERT(iterator__has_been_accessed(&iter->base)); - GIT_ASSERT(S_ISDIR(iter->entry->mode)); - - while (true) { - const git_index_entry *next_entry = NULL; - - if (++iter->next_idx >= iter->entries.length) - return GIT_ITEROVER; - - next_entry = iter->entries.contents[iter->next_idx]; - - if (iter->base.strncomp(iter->tree_buf.ptr, next_entry->path, - iter->tree_buf.size) != 0) - break; - } - - iter->skip_tree = false; - return 0; -} - -static int index_iterator_advance( - const git_index_entry **out, git_iterator *i) -{ - index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); - const git_index_entry *entry = NULL; - bool is_submodule; - int error = 0; - - iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; - - while (true) { - if (iter->next_idx >= iter->entries.length) { - error = GIT_ITEROVER; - break; - } - - /* we were not asked to expand this pseudotree. advance over it. */ - if (iter->skip_tree) { - index_iterator_skip_pseudotree(iter); - continue; - } - - entry = iter->entries.contents[iter->next_idx]; - is_submodule = S_ISGITLINK(entry->mode); - - if (!iterator_has_started(&iter->base, entry->path, is_submodule)) { - iter->next_idx++; - continue; - } - - if (iterator_has_ended(&iter->base, entry->path)) { - error = GIT_ITEROVER; - break; - } - - /* if we have a list of paths we're interested in, examine it */ - if (!iterator_pathlist_next_is(&iter->base, entry->path)) { - iter->next_idx++; - continue; - } - - /* if this is a conflict, skip it unless we're including conflicts */ - if (git_index_entry_is_conflict(entry) && - !iterator__include_conflicts(&iter->base)) { - iter->next_idx++; - continue; - } - - /* we've found what will be our next _file_ entry. but if we are - * returning trees entries, we may need to return a pseudotree - * entry that will contain this. don't advance over this entry, - * though, we still need to return it on the next `advance`. - */ - if (iterator__include_trees(&iter->base) && - index_iterator_create_pseudotree(&entry, iter, entry->path)) { - - /* Note whether this pseudo tree should be expanded or not */ - iter->skip_tree = iterator__dont_autoexpand(&iter->base); - break; - } - - iter->next_idx++; - break; - } - - iter->entry = (error == 0) ? entry : NULL; - - if (out) - *out = iter->entry; - - return error; -} - -static int index_iterator_advance_into( - const git_index_entry **out, git_iterator *i) -{ - index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); - - if (! S_ISDIR(iter->tree_entry.mode)) { - if (out) - *out = NULL; - - return 0; - } - - iter->skip_tree = false; - return index_iterator_advance(out, i); -} - -static int index_iterator_advance_over( - const git_index_entry **out, - git_iterator_status_t *status, - git_iterator *i) -{ - index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); - const git_index_entry *entry; - int error; - - if ((error = index_iterator_current(&entry, i)) < 0) - return error; - - if (S_ISDIR(entry->mode)) - index_iterator_skip_pseudotree(iter); - - *status = GIT_ITERATOR_STATUS_NORMAL; - return index_iterator_advance(out, i); -} - -static void index_iterator_clear(index_iterator *iter) -{ - iterator_clear(&iter->base); -} - -static int index_iterator_init(index_iterator *iter) -{ - iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; - iter->next_idx = 0; - iter->skip_tree = false; - return 0; -} - -static int index_iterator_reset(git_iterator *i) -{ - index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); - - index_iterator_clear(iter); - return index_iterator_init(iter); -} - -static void index_iterator_free(git_iterator *i) -{ - index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); - - git_index_snapshot_release(&iter->entries, iter->base.index); - git_str_dispose(&iter->tree_buf); -} - -int git_iterator_for_index( - git_iterator **out, - git_repository *repo, - git_index *index, - git_iterator_options *options) -{ - index_iterator *iter; - int error; - - static git_iterator_callbacks callbacks = { - index_iterator_current, - index_iterator_advance, - index_iterator_advance_into, - index_iterator_advance_over, - index_iterator_reset, - index_iterator_free - }; - - *out = NULL; - - if (index == NULL) - return git_iterator_for_nothing(out, options); - - iter = git__calloc(1, sizeof(index_iterator)); - GIT_ERROR_CHECK_ALLOC(iter); - - iter->base.type = GIT_ITERATOR_INDEX; - iter->base.cb = &callbacks; - - if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0 || - (error = git_index_snapshot_new(&iter->entries, index)) < 0 || - (error = index_iterator_init(iter)) < 0) - goto on_error; - - git_vector_set_cmp(&iter->entries, iterator__ignore_case(&iter->base) ? - git_index_entry_icmp : git_index_entry_cmp); - git_vector_sort(&iter->entries); - - *out = &iter->base; - return 0; - -on_error: - git_iterator_free(&iter->base); - return error; -} - - -/* Iterator API */ - -int git_iterator_reset_range( - git_iterator *i, const char *start, const char *end) -{ - if (iterator_reset_range(i, start, end) < 0) - return -1; - - return i->cb->reset(i); -} - -int git_iterator_set_ignore_case(git_iterator *i, bool ignore_case) -{ - GIT_ASSERT(!iterator__has_been_accessed(i)); - iterator_set_ignore_case(i, ignore_case); - return 0; -} - -void git_iterator_free(git_iterator *iter) -{ - if (iter == NULL) - return; - - iter->cb->free(iter); - - git_vector_free(&iter->pathlist); - git__free(iter->start); - git__free(iter->end); - - memset(iter, 0, sizeof(*iter)); - - git__free(iter); -} - -int git_iterator_foreach( - git_iterator *iterator, - git_iterator_foreach_cb cb, - void *data) -{ - const git_index_entry *iterator_item; - int error = 0; - - if ((error = git_iterator_current(&iterator_item, iterator)) < 0) - goto done; - - if ((error = cb(iterator_item, data)) != 0) - goto done; - - while (true) { - if ((error = git_iterator_advance(&iterator_item, iterator)) < 0) - goto done; - - if ((error = cb(iterator_item, data)) != 0) - goto done; - } - -done: - if (error == GIT_ITEROVER) - error = 0; - - return error; -} - -int git_iterator_walk( - git_iterator **iterators, - size_t cnt, - git_iterator_walk_cb cb, - void *data) -{ - const git_index_entry **iterator_item; /* next in each iterator */ - const git_index_entry **cur_items; /* current path in each iter */ - const git_index_entry *first_match; - size_t i, j; - int error = 0; - - iterator_item = git__calloc(cnt, sizeof(git_index_entry *)); - cur_items = git__calloc(cnt, sizeof(git_index_entry *)); - - GIT_ERROR_CHECK_ALLOC(iterator_item); - GIT_ERROR_CHECK_ALLOC(cur_items); - - /* Set up the iterators */ - for (i = 0; i < cnt; i++) { - error = git_iterator_current(&iterator_item[i], iterators[i]); - - if (error < 0 && error != GIT_ITEROVER) - goto done; - } - - while (true) { - for (i = 0; i < cnt; i++) - cur_items[i] = NULL; - - first_match = NULL; - - /* Find the next path(s) to consume from each iterator */ - for (i = 0; i < cnt; i++) { - if (iterator_item[i] == NULL) - continue; - - if (first_match == NULL) { - first_match = iterator_item[i]; - cur_items[i] = iterator_item[i]; - } else { - int path_diff = git_index_entry_cmp(iterator_item[i], first_match); - - if (path_diff < 0) { - /* Found an index entry that sorts before the one we're - * looking at. Forget that we've seen the other and - * look at the other iterators for this path. - */ - for (j = 0; j < i; j++) - cur_items[j] = NULL; - - first_match = iterator_item[i]; - cur_items[i] = iterator_item[i]; - } else if (path_diff == 0) { - cur_items[i] = iterator_item[i]; - } - } - } - - if (first_match == NULL) - break; - - if ((error = cb(cur_items, data)) != 0) - goto done; - - /* Advance each iterator that participated */ - for (i = 0; i < cnt; i++) { - if (cur_items[i] == NULL) - continue; - - error = git_iterator_advance(&iterator_item[i], iterators[i]); - - if (error < 0 && error != GIT_ITEROVER) - goto done; - } - } - -done: - git__free((git_index_entry **)iterator_item); - git__free((git_index_entry **)cur_items); - - if (error == GIT_ITEROVER) - error = 0; - - return error; -} diff --git a/src/iterator.h b/src/iterator.h deleted file mode 100644 index 6bb8489d0..000000000 --- a/src/iterator.h +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_iterator_h__ -#define INCLUDE_iterator_h__ - -#include "common.h" - -#include "git2/index.h" -#include "vector.h" -#include "str.h" -#include "ignore.h" - -typedef struct git_iterator git_iterator; - -typedef enum { - GIT_ITERATOR_EMPTY = 0, - GIT_ITERATOR_TREE = 1, - GIT_ITERATOR_INDEX = 2, - GIT_ITERATOR_WORKDIR = 3, - GIT_ITERATOR_FS = 4 -} git_iterator_t; - -typedef enum { - /** ignore case for entry sort order */ - GIT_ITERATOR_IGNORE_CASE = (1u << 0), - /** force case sensitivity for entry sort order */ - GIT_ITERATOR_DONT_IGNORE_CASE = (1u << 1), - /** return tree items in addition to blob items */ - GIT_ITERATOR_INCLUDE_TREES = (1u << 2), - /** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */ - GIT_ITERATOR_DONT_AUTOEXPAND = (1u << 3), - /** convert precomposed unicode to decomposed unicode */ - GIT_ITERATOR_PRECOMPOSE_UNICODE = (1u << 4), - /** never convert precomposed unicode to decomposed unicode */ - GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE = (1u << 5), - /** include conflicts */ - GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 6), - /** descend into symlinked directories */ - GIT_ITERATOR_DESCEND_SYMLINKS = (1u << 7), - /** hash files in workdir or filesystem iterators */ - GIT_ITERATOR_INCLUDE_HASH = (1u << 8) -} git_iterator_flag_t; - -typedef enum { - GIT_ITERATOR_STATUS_NORMAL = 0, - GIT_ITERATOR_STATUS_IGNORED = 1, - GIT_ITERATOR_STATUS_EMPTY = 2, - GIT_ITERATOR_STATUS_FILTERED = 3 -} git_iterator_status_t; - -typedef struct { - const char *start; - const char *end; - - /* paths to include in the iterator (literal). if set, any paths not - * listed here will be excluded from iteration. - */ - git_strarray pathlist; - - /* flags, from above */ - unsigned int flags; -} git_iterator_options; - -#define GIT_ITERATOR_OPTIONS_INIT {0} - -typedef struct { - int (*current)(const git_index_entry **, git_iterator *); - int (*advance)(const git_index_entry **, git_iterator *); - int (*advance_into)(const git_index_entry **, git_iterator *); - int (*advance_over)( - const git_index_entry **, git_iterator_status_t *, git_iterator *); - int (*reset)(git_iterator *); - void (*free)(git_iterator *); -} git_iterator_callbacks; - -struct git_iterator { - git_iterator_t type; - git_iterator_callbacks *cb; - - git_repository *repo; - git_index *index; - - char *start; - size_t start_len; - - char *end; - size_t end_len; - - bool started; - bool ended; - git_vector pathlist; - size_t pathlist_walk_idx; - int (*strcomp)(const char *a, const char *b); - int (*strncomp)(const char *a, const char *b, size_t n); - int (*prefixcomp)(const char *str, const char *prefix); - int (*entry_srch)(const void *key, const void *array_member); - size_t stat_calls; - unsigned int flags; -}; - -extern int git_iterator_for_nothing( - git_iterator **out, - git_iterator_options *options); - -/* tree iterators will match the ignore_case value from the index of the - * repository, unless you override with a non-zero flag value - */ -extern int git_iterator_for_tree( - git_iterator **out, - git_tree *tree, - git_iterator_options *options); - -/* index iterators will take the ignore_case value from the index; the - * ignore_case flags are not used - */ -extern int git_iterator_for_index( - git_iterator **out, - git_repository *repo, - git_index *index, - git_iterator_options *options); - -extern int git_iterator_for_workdir_ext( - git_iterator **out, - git_repository *repo, - const char *repo_workdir, - git_index *index, - git_tree *tree, - git_iterator_options *options); - -/* workdir iterators will match the ignore_case value from the index of the - * repository, unless you override with a non-zero flag value - */ -GIT_INLINE(int) git_iterator_for_workdir( - git_iterator **out, - git_repository *repo, - git_index *index, - git_tree *tree, - git_iterator_options *options) -{ - return git_iterator_for_workdir_ext(out, repo, NULL, index, tree, options); -} - -/* for filesystem iterators, you have to explicitly pass in the ignore_case - * behavior that you desire - */ -extern int git_iterator_for_filesystem( - git_iterator **out, - const char *root, - git_iterator_options *options); - -extern void git_iterator_free(git_iterator *iter); - -/* Return a git_index_entry structure for the current value the iterator - * is looking at or NULL if the iterator is at the end. - * - * The entry may noy be fully populated. Tree iterators will only have a - * value mode, OID, and path. Workdir iterators will not have an OID (but - * you can use `git_iterator_current_oid()` to calculate it on demand). - * - * You do not need to free the entry. It is still "owned" by the iterator. - * Once you call `git_iterator_advance()` then the old entry is no longer - * guaranteed to be valid - it may be freed or just overwritten in place. - */ -GIT_INLINE(int) git_iterator_current( - const git_index_entry **entry, git_iterator *iter) -{ - return iter->cb->current(entry, iter); -} - -/** - * Advance to the next item for the iterator. - * - * If GIT_ITERATOR_INCLUDE_TREES is set, this may be a tree item. If - * GIT_ITERATOR_DONT_AUTOEXPAND is set, calling this again when on a tree - * item will skip over all the items under that tree. - */ -GIT_INLINE(int) git_iterator_advance( - const git_index_entry **entry, git_iterator *iter) -{ - return iter->cb->advance(entry, iter); -} - -/** - * Iterate into a tree item (when GIT_ITERATOR_DONT_AUTOEXPAND is set). - * - * git_iterator_advance() steps through all items being iterated over - * (either with or without trees, depending on GIT_ITERATOR_INCLUDE_TREES), - * but if GIT_ITERATOR_DONT_AUTOEXPAND is set, it will skip to the next - * sibling of a tree instead of going to the first child of the tree. In - * that case, use this function to advance to the first child of the tree. - * - * If the current item is not a tree, this is a no-op. - * - * For filesystem and working directory iterators, a tree (i.e. directory) - * can be empty. In that case, this function returns GIT_ENOTFOUND and - * does not advance. That can't happen for tree and index iterators. - */ -GIT_INLINE(int) git_iterator_advance_into( - const git_index_entry **entry, git_iterator *iter) -{ - return iter->cb->advance_into(entry, iter); -} - -/* Advance over a directory and check if it contains no files or just - * ignored files. - * - * In a tree or the index, all directories will contain files, but in the - * working directory it is possible to have an empty directory tree or a - * tree that only contains ignored files. Many Git operations treat these - * cases specially. This advances over a directory (presumably an - * untracked directory) but checks during the scan if there are any files - * and any non-ignored files. - */ -GIT_INLINE(int) git_iterator_advance_over( - const git_index_entry **entry, - git_iterator_status_t *status, - git_iterator *iter) -{ - return iter->cb->advance_over(entry, status, iter); -} - -/** - * Go back to the start of the iteration. - */ -GIT_INLINE(int) git_iterator_reset(git_iterator *iter) -{ - return iter->cb->reset(iter); -} - -/** - * Go back to the start of the iteration after updating the `start` and - * `end` pathname boundaries of the iteration. - */ -extern int git_iterator_reset_range( - git_iterator *iter, const char *start, const char *end); - -GIT_INLINE(git_iterator_t) git_iterator_type(git_iterator *iter) -{ - return iter->type; -} - -GIT_INLINE(git_repository *) git_iterator_owner(git_iterator *iter) -{ - return iter->repo; -} - -GIT_INLINE(git_index *) git_iterator_index(git_iterator *iter) -{ - return iter->index; -} - -GIT_INLINE(git_iterator_flag_t) git_iterator_flags(git_iterator *iter) -{ - return iter->flags; -} - -GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter) -{ - return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0); -} - -extern int git_iterator_set_ignore_case( - git_iterator *iter, bool ignore_case); - -extern int git_iterator_current_tree_entry( - const git_tree_entry **entry_out, git_iterator *iter); - -extern int git_iterator_current_parent_tree( - const git_tree **tree_out, git_iterator *iter, size_t depth); - -extern bool git_iterator_current_is_ignored(git_iterator *iter); - -extern bool git_iterator_current_tree_is_ignored(git_iterator *iter); - -/** - * Get full path of the current item from a workdir iterator. This will - * return NULL for a non-workdir iterator. The git_str is still owned by - * the iterator; this is exposed just for efficiency. - */ -extern int git_iterator_current_workdir_path( - git_str **path, git_iterator *iter); - -/** - * Retrieve the index stored in the iterator. - * - * Only implemented for the workdir and index iterators. - */ -extern git_index *git_iterator_index(git_iterator *iter); - -typedef int (*git_iterator_foreach_cb)( - const git_index_entry *entry, - void *data); - -/** - * Walk the given iterator and invoke the callback for each path - * contained in the iterator. - */ -extern int git_iterator_foreach( - git_iterator *iterator, - git_iterator_foreach_cb cb, - void *data); - -typedef int (*git_iterator_walk_cb)( - const git_index_entry **entries, - void *data); - -/** - * Walk the given iterators in lock-step. The given callback will be - * called for each unique path, with the index entry in each iterator - * (or NULL if the given iterator does not contain that path). - */ -extern int git_iterator_walk( - git_iterator **iterators, - size_t cnt, - git_iterator_walk_cb cb, - void *data); - -#endif diff --git a/src/khash.h b/src/khash.h deleted file mode 100644 index c9b7f131f..000000000 --- a/src/khash.h +++ /dev/null @@ -1,615 +0,0 @@ -/* The MIT License - - Copyright (c) 2008, 2009, 2011 by Attractive Chaos - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - -/* - An example: - -#include "khash.h" -KHASH_MAP_INIT_INT(32, char) -int main() { - int ret, is_missing; - khiter_t k; - khash_t(32) *h = kh_init(32); - k = kh_put(32, h, 5, &ret); - kh_value(h, k) = 10; - k = kh_get(32, h, 10); - is_missing = (k == kh_end(h)); - k = kh_get(32, h, 5); - kh_del(32, h, k); - for (k = kh_begin(h); k != kh_end(h); ++k) - if (kh_exist(h, k)) kh_value(h, k) = 1; - kh_destroy(32, h); - return 0; -} -*/ - -/* - 2013-05-02 (0.2.8): - - * Use quadratic probing. When the capacity is power of 2, stepping function - i*(i+1)/2 guarantees to traverse each bucket. It is better than double - hashing on cache performance and is more robust than linear probing. - - In theory, double hashing should be more robust than quadratic probing. - However, my implementation is probably not for large hash tables, because - the second hash function is closely tied to the first hash function, - which reduce the effectiveness of double hashing. - - Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php - - 2011-12-29 (0.2.7): - - * Minor code clean up; no actual effect. - - 2011-09-16 (0.2.6): - - * The capacity is a power of 2. This seems to dramatically improve the - speed for simple keys. Thank Zilong Tan for the suggestion. Reference: - - - http://code.google.com/p/ulib/ - - http://nothings.org/computer/judy/ - - * Allow to optionally use linear probing which usually has better - performance for random input. Double hashing is still the default as it - is more robust to certain non-random input. - - * Added Wang's integer hash function (not used by default). This hash - function is more robust to certain non-random input. - - 2011-02-14 (0.2.5): - - * Allow to declare global functions. - - 2009-09-26 (0.2.4): - - * Improve portability - - 2008-09-19 (0.2.3): - - * Corrected the example - * Improved interfaces - - 2008-09-11 (0.2.2): - - * Improved speed a little in kh_put() - - 2008-09-10 (0.2.1): - - * Added kh_clear() - * Fixed a compiling error - - 2008-09-02 (0.2.0): - - * Changed to token concatenation which increases flexibility. - - 2008-08-31 (0.1.2): - - * Fixed a bug in kh_get(), which has not been tested previously. - - 2008-08-31 (0.1.1): - - * Added destructor -*/ - - -#ifndef __AC_KHASH_H -#define __AC_KHASH_H - -/*! - @header - - Generic hash table library. - */ - -#define AC_VERSION_KHASH_H "0.2.8" - -#include -#include -#include - -/* compiler specific configuration */ - -typedef uint32_t khint32_t; -typedef uint64_t khint64_t; - -#ifndef kh_inline -#ifdef _MSC_VER -#define kh_inline __inline -#elif defined(__GNUC__) -#define kh_inline __inline__ -#else -#define kh_inline -#endif -#endif /* kh_inline */ - -typedef khint32_t khint_t; -typedef khint_t khiter_t; - -#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) -#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) -#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) -#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) -#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) -#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) -#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) - -#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) - -#ifndef kroundup32 -#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) -#endif - -#ifndef kcalloc -#define kcalloc(N,Z) calloc(N,Z) -#endif -#ifndef kmalloc -#define kmalloc(Z) malloc(Z) -#endif -#ifndef krealloc -#define krealloc(P,Z) realloc(P,Z) -#endif -#ifndef kreallocarray -#define kreallocarray(P,N,Z) ((SIZE_MAX - N < Z) ? NULL : krealloc(P, (N*Z))) -#endif -#ifndef kfree -#define kfree(P) free(P) -#endif - -static const double __ac_HASH_UPPER = 0.77; - -#define __KHASH_TYPE(name, khkey_t, khval_t) \ - typedef struct kh_##name##_s { \ - khint_t n_buckets, size, n_occupied, upper_bound; \ - khint32_t *flags; \ - khkey_t *keys; \ - khval_t *vals; \ - } kh_##name##_t; - -#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ - extern kh_##name##_t *kh_init_##name(void); \ - extern void kh_destroy_##name(kh_##name##_t *h); \ - extern void kh_clear_##name(kh_##name##_t *h); \ - extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ - extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ - extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ - extern void kh_del_##name(kh_##name##_t *h, khint_t x); - -#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - SCOPE kh_##name##_t *kh_init_##name(void) { \ - return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ - } \ - SCOPE void kh_destroy_##name(kh_##name##_t *h) \ - { \ - if (h) { \ - kfree((void *)h->keys); kfree(h->flags); \ - kfree((void *)h->vals); \ - kfree(h); \ - } \ - } \ - SCOPE void kh_clear_##name(kh_##name##_t *h) \ - { \ - if (h && h->flags) { \ - memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ - h->size = h->n_occupied = 0; \ - } \ - } \ - SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ - { \ - if (h->n_buckets) { \ - khint_t k, i, last, mask, step = 0; \ - mask = h->n_buckets - 1; \ - k = __hash_func(key); i = k & mask; \ - last = i; \ - while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ - i = (i + (++step)) & mask; \ - if (i == last) return h->n_buckets; \ - } \ - return __ac_iseither(h->flags, i)? h->n_buckets : i; \ - } else return 0; \ - } \ - SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ - { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ - khint32_t *new_flags = 0; \ - khint_t j = 1; \ - { \ - kroundup32(new_n_buckets); \ - if (new_n_buckets < 4) new_n_buckets = 4; \ - if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ - else { /* hash table size to be changed (shrink or expand); rehash */ \ - new_flags = (khint32_t*)kreallocarray(NULL, __ac_fsize(new_n_buckets), sizeof(khint32_t)); \ - if (!new_flags) return -1; \ - memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ - if (h->n_buckets < new_n_buckets) { /* expand */ \ - khkey_t *new_keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ - if (!new_keys) { kfree(new_flags); return -1; } \ - h->keys = new_keys; \ - if (kh_is_map) { \ - khval_t *new_vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ - if (!new_vals) { kfree(new_flags); return -1; } \ - h->vals = new_vals; \ - } \ - } /* otherwise shrink */ \ - } \ - } \ - if (j) { /* rehashing is needed */ \ - for (j = 0; j != h->n_buckets; ++j) { \ - if (__ac_iseither(h->flags, j) == 0) { \ - khkey_t key = h->keys[j]; \ - khval_t val; \ - khint_t new_mask; \ - new_mask = new_n_buckets - 1; \ - if (kh_is_map) val = h->vals[j]; \ - __ac_set_isdel_true(h->flags, j); \ - while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ - khint_t k, i, step = 0; \ - k = __hash_func(key); \ - i = k & new_mask; \ - while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ - __ac_set_isempty_false(new_flags, i); \ - if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ - { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ - if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ - __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ - } else { /* write the element and jump out of the loop */ \ - h->keys[i] = key; \ - if (kh_is_map) h->vals[i] = val; \ - break; \ - } \ - } \ - } \ - } \ - if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ - h->keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ - if (kh_is_map) h->vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ - } \ - kfree(h->flags); /* free the working space */ \ - h->flags = new_flags; \ - h->n_buckets = new_n_buckets; \ - h->n_occupied = h->size; \ - h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ - } \ - return 0; \ - } \ - SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ - { \ - khint_t x; \ - if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ - if (h->n_buckets > (h->size<<1)) { \ - if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ - *ret = -1; return h->n_buckets; \ - } \ - } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ - *ret = -1; return h->n_buckets; \ - } \ - } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ - { \ - khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ - x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ - if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ - else { \ - last = i; \ - while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ - if (__ac_isdel(h->flags, i)) site = i; \ - i = (i + (++step)) & mask; \ - if (i == last) { x = site; break; } \ - } \ - if (x == h->n_buckets) { \ - if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ - else x = i; \ - } \ - } \ - } \ - if (__ac_isempty(h->flags, x)) { /* not present at all */ \ - h->keys[x] = key; \ - __ac_set_isboth_false(h->flags, x); \ - ++h->size; ++h->n_occupied; \ - *ret = 1; \ - } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ - h->keys[x] = key; \ - __ac_set_isboth_false(h->flags, x); \ - ++h->size; \ - *ret = 2; \ - } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ - return x; \ - } \ - SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ - { \ - if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ - __ac_set_isdel_true(h->flags, x); \ - --h->size; \ - } \ - } - -#define KHASH_DECLARE(name, khkey_t, khval_t) \ - __KHASH_TYPE(name, khkey_t, khval_t) \ - __KHASH_PROTOTYPES(name, khkey_t, khval_t) - -#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - __KHASH_TYPE(name, khkey_t, khval_t) \ - __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) - -#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) - -/* --- BEGIN OF HASH FUNCTIONS --- */ - -/*! @function - @abstract Integer hash function - @param key The integer [khint32_t] - @return The hash value [khint_t] - */ -#define kh_int_hash_func(key) (khint32_t)(key) -/*! @function - @abstract Integer comparison function - */ -#define kh_int_hash_equal(a, b) ((a) == (b)) -/*! @function - @abstract 64-bit integer hash function - @param key The integer [khint64_t] - @return The hash value [khint_t] - */ -#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) -/*! @function - @abstract 64-bit integer comparison function - */ -#define kh_int64_hash_equal(a, b) ((a) == (b)) -/*! @function - @abstract const char* hash function - @param s Pointer to a null terminated string - @return The hash value - */ -static kh_inline khint_t __ac_X31_hash_string(const char *s) -{ - khint_t h = (khint_t)*s; - if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; - return h; -} -/*! @function - @abstract Another interface to const char* hash function - @param key Pointer to a null terminated string [const char*] - @return The hash value [khint_t] - */ -#define kh_str_hash_func(key) __ac_X31_hash_string(key) -/*! @function - @abstract Const char* comparison function - */ -#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) - -static kh_inline khint_t __ac_Wang_hash(khint_t key) -{ - key += ~(key << 15); - key ^= (key >> 10); - key += (key << 3); - key ^= (key >> 6); - key += ~(key << 11); - key ^= (key >> 16); - return key; -} -#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) - -/* --- END OF HASH FUNCTIONS --- */ - -/* Other convenient macros... */ - -/*! - @abstract Type of the hash table. - @param name Name of the hash table [symbol] - */ -#define khash_t(name) kh_##name##_t - -/*! @function - @abstract Initiate a hash table. - @param name Name of the hash table [symbol] - @return Pointer to the hash table [khash_t(name)*] - */ -#define kh_init(name) kh_init_##name() - -/*! @function - @abstract Destroy a hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - */ -#define kh_destroy(name, h) kh_destroy_##name(h) - -/*! @function - @abstract Reset a hash table without deallocating memory. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - */ -#define kh_clear(name, h) kh_clear_##name(h) - -/*! @function - @abstract Resize a hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param s New size [khint_t] - */ -#define kh_resize(name, h, s) kh_resize_##name(h, s) - -/*! @function - @abstract Insert a key to the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Key [type of keys] - @param r Extra return code: -1 if the operation failed; - 0 if the key is present in the hash table; - 1 if the bucket is empty (never used); 2 if the element in - the bucket has been deleted [int*] - @return Iterator to the inserted element [khint_t] - */ -#define kh_put(name, h, k, r) kh_put_##name(h, k, r) - -/*! @function - @abstract Retrieve a key from the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Key [type of keys] - @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] - */ -#define kh_get(name, h, k) kh_get_##name(h, k) - -/*! @function - @abstract Remove a key from the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Iterator to the element to be deleted [khint_t] - */ -#define kh_del(name, h, k) kh_del_##name(h, k) - -/*! @function - @abstract Test whether a bucket contains data. - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return 1 if containing data; 0 otherwise [int] - */ -#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) - -/*! @function - @abstract Get key given an iterator - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return Key [type of keys] - */ -#define kh_key(h, x) ((h)->keys[x]) - -/*! @function - @abstract Get value given an iterator - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return Value [type of values] - @discussion For hash sets, calling this results in segfault. - */ -#define kh_val(h, x) ((h)->vals[x]) - -/*! @function - @abstract Alias of kh_val() - */ -#define kh_value(h, x) ((h)->vals[x]) - -/*! @function - @abstract Get the start iterator - @param h Pointer to the hash table [khash_t(name)*] - @return The start iterator [khint_t] - */ -#define kh_begin(h) (khint_t)(0) - -/*! @function - @abstract Get the end iterator - @param h Pointer to the hash table [khash_t(name)*] - @return The end iterator [khint_t] - */ -#define kh_end(h) ((h)->n_buckets) - -/*! @function - @abstract Get the number of elements in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @return Number of elements in the hash table [khint_t] - */ -#define kh_size(h) ((h)->size) - -/*! @function - @abstract Get the number of buckets in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @return Number of buckets in the hash table [khint_t] - */ -#define kh_n_buckets(h) ((h)->n_buckets) - -/*! @function - @abstract Iterate over the entries in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @param kvar Variable to which key will be assigned - @param vvar Variable to which value will be assigned - @param code Block of code to execute - */ -#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ - for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ - if (!kh_exist(h,__i)) continue; \ - (kvar) = kh_key(h,__i); \ - (vvar) = kh_val(h,__i); \ - code; \ - } } - -/*! @function - @abstract Iterate over the values in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @param vvar Variable to which value will be assigned - @param code Block of code to execute - */ -#define kh_foreach_value(h, vvar, code) { khint_t __i; \ - for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ - if (!kh_exist(h,__i)) continue; \ - (vvar) = kh_val(h,__i); \ - code; \ - } } - -/* More convenient interfaces */ - -/*! @function - @abstract Instantiate a hash set containing integer keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_INT(name) \ - KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing integer keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_INT(name, khval_t) \ - KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing 64-bit integer keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_INT64(name) \ - KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing 64-bit integer keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_INT64(name, khval_t) \ - KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) - -typedef const char *kh_cstr_t; -/*! @function - @abstract Instantiate a hash map containing const char* keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_STR(name) \ - KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing const char* keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_STR(name, khval_t) \ - KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) - -#endif /* __AC_KHASH_H */ diff --git a/src/libgit2.c b/src/libgit2.c deleted file mode 100644 index efad3bf6d..000000000 --- a/src/libgit2.c +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "libgit2.h" - -#include -#include "alloc.h" -#include "buf.h" -#include "cache.h" -#include "common.h" -#include "filter.h" -#include "hash.h" -#include "index.h" -#include "merge_driver.h" -#include "pool.h" -#include "mwindow.h" -#include "object.h" -#include "odb.h" -#include "rand.h" -#include "refs.h" -#include "runtime.h" -#include "sysdir.h" -#include "thread.h" -#include "threadstate.h" -#include "git2/global.h" -#include "streams/registry.h" -#include "streams/mbedtls.h" -#include "streams/openssl.h" -#include "transports/smart.h" -#include "transports/http.h" -#include "transports/ssh.h" - -#ifdef GIT_WIN32 -# include "win32/w32_leakcheck.h" -#endif - -/* Declarations for tuneable settings */ -extern size_t git_mwindow__window_size; -extern size_t git_mwindow__mapped_limit; -extern size_t git_mwindow__file_limit; -extern size_t git_indexer__max_objects; -extern bool git_disable_pack_keep_file_checks; -extern int git_odb__packed_priority; -extern int git_odb__loose_priority; - -char *git__user_agent; -char *git__ssl_ciphers; - -static void libgit2_settings_global_shutdown(void) -{ - git__free(git__user_agent); - git__free(git__ssl_ciphers); - git_repository__free_extensions(); -} - -static int git_libgit2_settings_global_init(void) -{ - return git_runtime_shutdown_register(libgit2_settings_global_shutdown); -} - -int git_libgit2_init(void) -{ - static git_runtime_init_fn init_fns[] = { -#ifdef GIT_WIN32 - git_win32_leakcheck_global_init, -#endif - git_allocator_global_init, - git_threadstate_global_init, - git_threads_global_init, - git_rand_global_init, - git_hash_global_init, - git_sysdir_global_init, - git_filter_global_init, - git_merge_driver_global_init, - git_transport_ssh_global_init, - git_stream_registry_global_init, - git_openssl_stream_global_init, - git_mbedtls_stream_global_init, - git_mwindow_global_init, - git_pool_global_init, - git_libgit2_settings_global_init - }; - - return git_runtime_init(init_fns, ARRAY_SIZE(init_fns)); -} - -int git_libgit2_init_count(void) -{ - return git_runtime_init_count(); -} - -int git_libgit2_shutdown(void) -{ - return git_runtime_shutdown(); -} - -int git_libgit2_version(int *major, int *minor, int *rev) -{ - *major = LIBGIT2_VER_MAJOR; - *minor = LIBGIT2_VER_MINOR; - *rev = LIBGIT2_VER_REVISION; - - return 0; -} - -const char *git_libgit2_prerelease(void) -{ - return LIBGIT2_VER_PRERELEASE; -} - -int git_libgit2_features(void) -{ - return 0 -#ifdef GIT_THREADS - | GIT_FEATURE_THREADS -#endif -#ifdef GIT_HTTPS - | GIT_FEATURE_HTTPS -#endif -#if defined(GIT_SSH) - | GIT_FEATURE_SSH -#endif -#if defined(GIT_USE_NSEC) - | GIT_FEATURE_NSEC -#endif - ; -} - -static int config_level_to_sysdir(int *out, int config_level) -{ - switch (config_level) { - case GIT_CONFIG_LEVEL_SYSTEM: - *out = GIT_SYSDIR_SYSTEM; - return 0; - case GIT_CONFIG_LEVEL_XDG: - *out = GIT_SYSDIR_XDG; - return 0; - case GIT_CONFIG_LEVEL_GLOBAL: - *out = GIT_SYSDIR_GLOBAL; - return 0; - case GIT_CONFIG_LEVEL_PROGRAMDATA: - *out = GIT_SYSDIR_PROGRAMDATA; - return 0; - default: - break; - } - - git_error_set( - GIT_ERROR_INVALID, "invalid config path selector %d", config_level); - return -1; -} - -const char *git_libgit2__user_agent(void) -{ - return git__user_agent; -} - -const char *git_libgit2__ssl_ciphers(void) -{ - return git__ssl_ciphers; -} - -int git_libgit2_opts(int key, ...) -{ - int error = 0; - va_list ap; - - va_start(ap, key); - - switch (key) { - case GIT_OPT_SET_MWINDOW_SIZE: - git_mwindow__window_size = va_arg(ap, size_t); - break; - - case GIT_OPT_GET_MWINDOW_SIZE: - *(va_arg(ap, size_t *)) = git_mwindow__window_size; - break; - - case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT: - git_mwindow__mapped_limit = va_arg(ap, size_t); - break; - - case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: - *(va_arg(ap, size_t *)) = git_mwindow__mapped_limit; - break; - - case GIT_OPT_SET_MWINDOW_FILE_LIMIT: - git_mwindow__file_limit = va_arg(ap, size_t); - break; - - case GIT_OPT_GET_MWINDOW_FILE_LIMIT: - *(va_arg(ap, size_t *)) = git_mwindow__file_limit; - break; - - case GIT_OPT_GET_SEARCH_PATH: - { - int sysdir = va_arg(ap, int); - git_buf *out = va_arg(ap, git_buf *); - git_str str = GIT_STR_INIT; - const git_str *tmp; - int level; - - if ((error = git_buf_tostr(&str, out)) < 0 || - (error = config_level_to_sysdir(&level, sysdir)) < 0 || - (error = git_sysdir_get(&tmp, level)) < 0 || - (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) - break; - - error = git_buf_fromstr(out, &str); - } - break; - - case GIT_OPT_SET_SEARCH_PATH: - { - int level; - - if ((error = config_level_to_sysdir(&level, va_arg(ap, int))) >= 0) - error = git_sysdir_set(level, va_arg(ap, const char *)); - } - break; - - case GIT_OPT_SET_CACHE_OBJECT_LIMIT: - { - git_object_t type = (git_object_t)va_arg(ap, int); - size_t size = va_arg(ap, size_t); - error = git_cache_set_max_object_size(type, size); - break; - } - - case GIT_OPT_SET_CACHE_MAX_SIZE: - git_cache__max_storage = va_arg(ap, ssize_t); - break; - - case GIT_OPT_ENABLE_CACHING: - git_cache__enabled = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_GET_CACHED_MEMORY: - *(va_arg(ap, ssize_t *)) = git_cache__current_storage.val; - *(va_arg(ap, ssize_t *)) = git_cache__max_storage; - break; - - case GIT_OPT_GET_TEMPLATE_PATH: - { - git_buf *out = va_arg(ap, git_buf *); - git_str str = GIT_STR_INIT; - const git_str *tmp; - - if ((error = git_buf_tostr(&str, out)) < 0 || - (error = git_sysdir_get(&tmp, GIT_SYSDIR_TEMPLATE)) < 0 || - (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) - break; - - error = git_buf_fromstr(out, &str); - } - break; - - case GIT_OPT_SET_TEMPLATE_PATH: - error = git_sysdir_set(GIT_SYSDIR_TEMPLATE, va_arg(ap, const char *)); - break; - - case GIT_OPT_SET_SSL_CERT_LOCATIONS: -#ifdef GIT_OPENSSL - { - const char *file = va_arg(ap, const char *); - const char *path = va_arg(ap, const char *); - error = git_openssl__set_cert_location(file, path); - } -#elif defined(GIT_MBEDTLS) - { - const char *file = va_arg(ap, const char *); - const char *path = va_arg(ap, const char *); - error = git_mbedtls__set_cert_location(file, path); - } -#else - git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support certificate locations"); - error = -1; -#endif - break; - case GIT_OPT_SET_USER_AGENT: - git__free(git__user_agent); - git__user_agent = git__strdup(va_arg(ap, const char *)); - if (!git__user_agent) { - git_error_set_oom(); - error = -1; - } - - break; - - case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: - git_object__strict_input_validation = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION: - git_reference__enable_symbolic_ref_target_validation = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_SET_SSL_CIPHERS: -#if (GIT_OPENSSL || GIT_MBEDTLS) - { - git__free(git__ssl_ciphers); - git__ssl_ciphers = git__strdup(va_arg(ap, const char *)); - if (!git__ssl_ciphers) { - git_error_set_oom(); - error = -1; - } - } -#else - git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support custom ciphers"); - error = -1; -#endif - break; - - case GIT_OPT_GET_USER_AGENT: - { - git_buf *out = va_arg(ap, git_buf *); - git_str str = GIT_STR_INIT; - - if ((error = git_buf_tostr(&str, out)) < 0 || - (error = git_str_puts(&str, git__user_agent)) < 0) - break; - - error = git_buf_fromstr(out, &str); - } - break; - - case GIT_OPT_ENABLE_OFS_DELTA: - git_smart__ofs_delta_enabled = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_ENABLE_FSYNC_GITDIR: - git_repository__fsync_gitdir = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_GET_WINDOWS_SHAREMODE: -#ifdef GIT_WIN32 - *(va_arg(ap, unsigned long *)) = git_win32__createfile_sharemode; -#endif - break; - - case GIT_OPT_SET_WINDOWS_SHAREMODE: -#ifdef GIT_WIN32 - git_win32__createfile_sharemode = va_arg(ap, unsigned long); -#endif - break; - - case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: - git_odb__strict_hash_verification = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_SET_ALLOCATOR: - error = git_allocator_setup(va_arg(ap, git_allocator *)); - break; - - case GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY: - git_index__enforce_unsaved_safety = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_SET_PACK_MAX_OBJECTS: - git_indexer__max_objects = va_arg(ap, size_t); - break; - - case GIT_OPT_GET_PACK_MAX_OBJECTS: - *(va_arg(ap, size_t *)) = git_indexer__max_objects; - break; - - case GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS: - git_disable_pack_keep_file_checks = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE: - git_http__expect_continue = (va_arg(ap, int) != 0); - break; - - case GIT_OPT_SET_ODB_PACKED_PRIORITY: - git_odb__packed_priority = va_arg(ap, int); - break; - - case GIT_OPT_SET_ODB_LOOSE_PRIORITY: - git_odb__loose_priority = va_arg(ap, int); - break; - - case GIT_OPT_SET_EXTENSIONS: - { - const char **extensions = va_arg(ap, const char **); - size_t len = va_arg(ap, size_t); - error = git_repository__set_extensions(extensions, len); - } - break; - - case GIT_OPT_GET_EXTENSIONS: - { - git_strarray *out = va_arg(ap, git_strarray *); - char **extensions; - size_t len; - - if ((error = git_repository__extensions(&extensions, &len)) < 0) - break; - - out->strings = extensions; - out->count = len; - } - break; - - default: - git_error_set(GIT_ERROR_INVALID, "invalid option key"); - error = -1; - } - - va_end(ap); - - return error; -} diff --git a/src/libgit2.h b/src/libgit2.h deleted file mode 100644 index a898367ae..000000000 --- a/src/libgit2.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_libgit2_h__ -#define INCLUDE_libgit2_h__ - -extern int git_libgit2_init_count(void); - -extern const char *git_libgit2__user_agent(void); -extern const char *git_libgit2__ssl_ciphers(void); - -#endif diff --git a/src/libgit2/CMakeLists.txt b/src/libgit2/CMakeLists.txt new file mode 100644 index 000000000..e6cdddd49 --- /dev/null +++ b/src/libgit2/CMakeLists.txt @@ -0,0 +1,305 @@ +add_library(git2internal OBJECT) +set_target_properties(git2internal PROPERTIES C_STANDARD 90) +set_target_properties(git2internal PROPERTIES C_EXTENSIONS OFF) + + +if(DEPRECATE_HARD) + add_definitions(-DGIT_DEPRECATE_HARD) +endif() + +if(DEBUG_POOL) + set(GIT_DEBUG_POOL 1) +endif() +add_feature_info(debugpool GIT_DEBUG_POOL "debug pool allocator") + +if(DEBUG_STRICT_ALLOC) + set(GIT_DEBUG_STRICT_ALLOC 1) +endif() +add_feature_info(debugalloc GIT_DEBUG_STRICT_ALLOC "debug strict allocators") + +if(DEBUG_STRICT_OPEN) + set(GIT_DEBUG_STRICT_OPEN 1) +endif() +add_feature_info(debugopen GIT_DEBUG_STRICT_OPEN "path validation in open") + + +include(PkgBuildConfig) +include(SanitizeBool) + +# This variable will contain the libraries we need to put into +# libgit2.pc's Requires.private. That is, what we're linking to or +# what someone who's statically linking us needs to link to. +set(LIBGIT2_PC_REQUIRES "") +# This will be set later if we use the system's http-parser library or +# use iconv (OSX) and will be written to the Libs.private field in the +# pc file. +set(LIBGIT2_PC_LIBS "") + +set(LIBGIT2_INCLUDES + "${CMAKE_CURRENT_BINARY_DIR}" + "${PROJECT_SOURCE_DIR}/src/libgit2" + "${PROJECT_SOURCE_DIR}/include") + +if(HAVE_FUTIMENS) + set(GIT_USE_FUTIMENS 1) +endif () +add_feature_info(futimens GIT_USE_FUTIMENS "futimens support") + +check_prototype_definition(qsort_r + "void qsort_r(void *base, size_t nmemb, size_t size, void *thunk, int (*compar)(void *, const void *, const void *))" + "" "stdlib.h" GIT_QSORT_R_BSD) + +check_prototype_definition(qsort_r + "void qsort_r(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void *arg)" + "" "stdlib.h" GIT_QSORT_R_GNU) + +check_function_exists(qsort_s GIT_QSORT_S) + +# Find required dependencies + +if(WIN32) + list(APPEND LIBGIT2_SYSTEM_LIBS ws2_32) +elseif(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + list(APPEND LIBGIT2_SYSTEM_LIBS socket nsl) + list(APPEND LIBGIT2_PC_LIBS "-lsocket" "-lnsl") +elseif(CMAKE_SYSTEM_NAME MATCHES "Haiku") + list(APPEND LIBGIT2_SYSTEM_LIBS network) + list(APPEND LIBGIT2_PC_LIBS "-lnetwork") +endif() + +check_library_exists(rt clock_gettime "time.h" NEED_LIBRT) +if(NEED_LIBRT) + list(APPEND LIBGIT2_SYSTEM_LIBS rt) + list(APPEND LIBGIT2_PC_LIBS "-lrt") +endif() + +if(USE_THREADS) + list(APPEND LIBGIT2_SYSTEM_LIBS ${CMAKE_THREAD_LIBS_INIT}) + list(APPEND LIBGIT2_PC_LIBS ${CMAKE_THREAD_LIBS_INIT}) +endif() +add_feature_info(threadsafe USE_THREADS "threadsafe support") + + +if(WIN32 AND EMBED_SSH_PATH) + file(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c") + list(SORT SRC_SSH) + target_sources(git2internal PRIVATE ${SRC_SSH}) + + list(APPEND LIBGIT2_SYSTEM_INCLUDES "${EMBED_SSH_PATH}/include") + file(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"") + set(GIT_SSH 1) +endif() + +include(SelectHTTPSBackend) +include(SelectHashes) +include(SelectHTTPParser) +include(SelectRegex) +include(SelectSSH) +include(SelectWinHTTP) +include(SelectZlib) + + +if(USE_SHA1 STREQUAL "CollisionDetection") + file(GLOB SRC_SHA1 hash/sha1/collisiondetect.* hash/sha1/sha1dc/*) +elseif(USE_SHA1 STREQUAL "OpenSSL") + file(GLOB SRC_SHA1 hash/sha1/openssl.*) +elseif(USE_SHA1 STREQUAL "CommonCrypto") + file(GLOB SRC_SHA1 hash/sha1/common_crypto.*) +elseif(USE_SHA1 STREQUAL "mbedTLS") + file(GLOB SRC_SHA1 hash/sha1/mbedtls.*) +elseif(USE_SHA1 STREQUAL "Win32") + file(GLOB SRC_SHA1 hash/sha1/win32.*) +elseif(USE_SHA1 STREQUAL "Generic") + file(GLOB SRC_SHA1 hash/sha1/generic.*) +endif() +list(APPEND SRC_SHA1 "hash/sha1.h") +target_sources(git2internal PRIVATE ${SRC_SHA1}) + +# Optional external dependency: ntlmclient +if(USE_NTLMCLIENT) + set(GIT_NTLM 1) + add_subdirectory("${PROJECT_SOURCE_DIR}/deps/ntlmclient" "${PROJECT_BINARY_DIR}/deps/ntlmclient") + list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/ntlmclient") + list(APPEND LIBGIT2_DEPENDENCY_OBJECTS "$") +endif() +add_feature_info(ntlmclient GIT_NTLM "NTLM authentication support for Unix") + +# Optional external dependency: GSSAPI + +include(SelectGSSAPI) + +# Optional external dependency: iconv +if(USE_ICONV) + find_package(Iconv) +endif() +if(ICONV_FOUND) + set(GIT_USE_ICONV 1) + list(APPEND LIBGIT2_SYSTEM_INCLUDES ${ICONV_INCLUDE_DIR}) + list(APPEND LIBGIT2_SYSTEM_LIBS ${ICONV_LIBRARIES}) + list(APPEND LIBGIT2_PC_LIBS ${ICONV_LIBRARIES}) +endif() +add_feature_info(iconv GIT_USE_ICONV "iconv encoding conversion support") + + +if(USE_THREADS) + if(NOT WIN32) + find_package(Threads REQUIRED) + endif() + + set(GIT_THREADS 1) +endif() + +if(USE_NSEC) + set(GIT_USE_NSEC 1) +endif() + +if(HAVE_STRUCT_STAT_ST_MTIM) + set(GIT_USE_STAT_MTIM 1) +elseif(HAVE_STRUCT_STAT_ST_MTIMESPEC) + set(GIT_USE_STAT_MTIMESPEC 1) +elseif(HAVE_STRUCT_STAT_ST_MTIME_NSEC) + set(GIT_USE_STAT_MTIME_NSEC 1) +endif() + +target_compile_definitions(git2internal PRIVATE _FILE_OFFSET_BITS=64) + +# Collect sourcefiles +file(GLOB SRC_H + "${PROJECT_SOURCE_DIR}/include/git2.h" + "${PROJECT_SOURCE_DIR}/include/git2/*.h" + "${PROJECT_SOURCE_DIR}/include/git2/sys/*.h") +list(SORT SRC_H) +target_sources(git2internal PRIVATE ${SRC_H}) + +# On Windows use specific platform sources +if(WIN32 AND NOT CYGWIN) + set(WIN_RC "win32/git2.rc") + + file(GLOB SRC_OS win32/*.c win32/*.h) + list(SORT SRC_OS) + target_sources(git2internal PRIVATE ${SRC_OS}) +elseif(AMIGA) + target_compile_definitions(git2internal PRIVATE NO_ADDRINFO NO_READDIR_R NO_MMAP) +else() + file(GLOB SRC_OS unix/*.c unix/*.h) + list(SORT SRC_OS) + target_sources(git2internal PRIVATE ${SRC_OS}) +endif() + +if(USE_LEAK_CHECKER STREQUAL "valgrind") + target_compile_definitions(git2internal PRIVATE VALGRIND) +endif() + +file(GLOB SRC_GIT2 *.c *.h + allocators/*.c allocators/*.h + streams/*.c streams/*.h + transports/*.c transports/*.h + xdiff/*.c xdiff/*.h) +list(SORT SRC_GIT2) +target_sources(git2internal PRIVATE ${SRC_GIT2}) + +if(APPLE) + # The old Secure Transport API has been deprecated in macOS 10.15. + set_source_files_properties(streams/stransport.c PROPERTIES COMPILE_FLAGS -Wno-deprecated) +endif() + +# the xdiff dependency is not (yet) warning-free, disable warnings as +# errors for the xdiff sources until we've sorted them out + if(MSVC) + set_source_files_properties(xdiff/xdiffi.c PROPERTIES COMPILE_FLAGS -WX-) + set_source_files_properties(xdiff/xemit.c PROPERTIES COMPILE_FLAGS -WX-) + set_source_files_properties(xdiff/xhistogram.c PROPERTIES COMPILE_FLAGS -WX-) + set_source_files_properties(xdiff/xmerge.c PROPERTIES COMPILE_FLAGS -WX-) + set_source_files_properties(xdiff/xutils.c PROPERTIES COMPILE_FLAGS -WX-) + set_source_files_properties(xdiff/xpatience.c PROPERTIES COMPILE_FLAGS -WX-) +else() + set_source_files_properties(xdiff/xdiffi.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare -Wno-unused-parameter") + set_source_files_properties(xdiff/xemit.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare -Wno-unused-parameter") + set_source_files_properties(xdiff/xhistogram.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") + set_source_files_properties(xdiff/xutils.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") + set_source_files_properties(xdiff/xpatience.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") + endif() + +# Determine architecture of the machine +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(GIT_ARCH_64 1) +elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(GIT_ARCH_32 1) +elseif(CMAKE_SIZEOF_VOID_P) + message(FATAL_ERROR "Unsupported architecture (pointer size is ${CMAKE_SIZEOF_VOID_P} bytes)") +else() + message(FATAL_ERROR "Unsupported architecture (CMAKE_SIZEOF_VOID_P is unset)") +endif() + +configure_file(features.h.in git2/sys/features.h) + +ide_split_sources(git2internal) +list(APPEND LIBGIT2_OBJECTS $ ${LIBGIT2_DEPENDENCY_OBJECTS}) + +target_include_directories(git2internal PRIVATE ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES} PUBLIC ${PROJECT_SOURCE_DIR}/include) +target_include_directories(git2internal SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) + +set(LIBGIT2_INCLUDES ${LIBGIT2_INCLUDES} PARENT_SCOPE) +set(LIBGIT2_OBJECTS ${LIBGIT2_OBJECTS} PARENT_SCOPE) +set(LIBGIT2_DEPENDENCY_INCLUDES ${LIBGIT2_DEPENDENCY_INCLUDES} PARENT_SCOPE) +set(LIBGIT2_DEPENDENCY_OBJECTS ${LIBGIT2_DEPENDENCY_OBJECTS} PARENT_SCOPE) +set(LIBGIT2_SYSTEM_INCLUDES ${LIBGIT2_SYSTEM_INCLUDES} PARENT_SCOPE) +set(LIBGIT2_SYSTEM_LIBS ${LIBGIT2_SYSTEM_LIBS} PARENT_SCOPE) + +if(XCODE_VERSION) + # This is required for Xcode to actually link the libgit2 library + # when using only object libraries. + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/dummy.c "") + list(APPEND LIBGIT2_OBJECTS ${CMAKE_CURRENT_BINARY_DIR}/dummy.c) +endif() + +# Compile and link libgit2 +add_library(git2 ${WIN_RC} ${LIBGIT2_OBJECTS}) +target_link_libraries(git2 ${LIBGIT2_SYSTEM_LIBS}) + +set_target_properties(git2 PROPERTIES C_STANDARD 90) +set_target_properties(git2 PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +set_target_properties(git2 PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +set_target_properties(git2 PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) + +# Workaround for Cmake bug #0011240 (see http://public.kitware.com/Bug/view.php?id=11240) +# Win64+MSVC+static libs = linker error +if(MSVC AND GIT_ARCH_64 AND NOT BUILD_SHARED_LIBS) + set_target_properties(git2 PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64") +endif() + +ide_split_sources(git2) + +if(SONAME) + set_target_properties(git2 PROPERTIES VERSION ${libgit2_VERSION}) + set_target_properties(git2 PROPERTIES SOVERSION "${libgit2_VERSION_MAJOR}.${libgit2_VERSION_MINOR}") + if(LIBGIT2_FILENAME) + target_compile_definitions(git2 PRIVATE LIBGIT2_FILENAME=\"${LIBGIT2_FILENAME}\") + set_target_properties(git2 PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME}) + elseif(DEFINED LIBGIT2_PREFIX) + set_target_properties(git2 PROPERTIES PREFIX "${LIBGIT2_PREFIX}") + endif() +endif() + +pkg_build_config(NAME libgit2 + VERSION ${libgit2_VERSION} + DESCRIPTION "The git library, take 2" + LIBS_SELF git2 + PRIVATE_LIBS ${LIBGIT2_PC_LIBS} + REQUIRES ${LIBGIT2_PC_REQUIRES} +) + +if(MSVC_IDE) + # Precompiled headers + set_target_properties(git2 PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") + set_source_files_properties(win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h") +endif() + +# Install +install(TARGETS git2 + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/git2 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +install(FILES ${PROJECT_SOURCE_DIR}/include/git2.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/libgit2/alloc.c b/src/libgit2/alloc.c new file mode 100644 index 000000000..2820d84a2 --- /dev/null +++ b/src/libgit2/alloc.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "alloc.h" +#include "runtime.h" + +#include "allocators/failalloc.h" +#include "allocators/stdalloc.h" +#include "allocators/win32_leakcheck.h" + +/* Fail any allocation until git_libgit2_init is called. */ +git_allocator git__allocator = { + git_failalloc_malloc, + git_failalloc_calloc, + git_failalloc_strdup, + git_failalloc_strndup, + git_failalloc_substrdup, + git_failalloc_realloc, + git_failalloc_reallocarray, + git_failalloc_mallocarray, + git_failalloc_free +}; + +static int setup_default_allocator(void) +{ +#if defined(GIT_WIN32_LEAKCHECK) + return git_win32_leakcheck_init_allocator(&git__allocator); +#else + return git_stdalloc_init_allocator(&git__allocator); +#endif +} + +int git_allocator_global_init(void) +{ + /* + * We don't want to overwrite any allocator which has been set + * before the init function is called. + */ + if (git__allocator.gmalloc != git_failalloc_malloc) + return 0; + + return setup_default_allocator(); +} + +int git_allocator_setup(git_allocator *allocator) +{ + if (!allocator) + return setup_default_allocator(); + + memcpy(&git__allocator, allocator, sizeof(*allocator)); + return 0; +} diff --git a/src/libgit2/alloc.h b/src/libgit2/alloc.h new file mode 100644 index 000000000..04fb7e101 --- /dev/null +++ b/src/libgit2/alloc.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_alloc_h__ +#define INCLUDE_alloc_h__ + +#include "git2/sys/alloc.h" + +extern git_allocator git__allocator; + +#define git__malloc(len) git__allocator.gmalloc(len, __FILE__, __LINE__) +#define git__calloc(nelem, elsize) git__allocator.gcalloc(nelem, elsize, __FILE__, __LINE__) +#define git__strdup(str) git__allocator.gstrdup(str, __FILE__, __LINE__) +#define git__strndup(str, n) git__allocator.gstrndup(str, n, __FILE__, __LINE__) +#define git__substrdup(str, n) git__allocator.gsubstrdup(str, n, __FILE__, __LINE__) +#define git__realloc(ptr, size) git__allocator.grealloc(ptr, size, __FILE__, __LINE__) +#define git__reallocarray(ptr, nelem, elsize) git__allocator.greallocarray(ptr, nelem, elsize, __FILE__, __LINE__) +#define git__mallocarray(nelem, elsize) git__allocator.gmallocarray(nelem, elsize, __FILE__, __LINE__) +#define git__free git__allocator.gfree + +/** + * This function is being called by our global setup routines to + * initialize the standard allocator. + */ +int git_allocator_global_init(void); + +/** + * Switch out libgit2's global memory allocator + * + * @param allocator The new allocator that should be used. All function pointers + * of it need to be set correctly. + * @return An error code or 0. + */ +int git_allocator_setup(git_allocator *allocator); + +#endif diff --git a/src/libgit2/allocators/failalloc.c b/src/libgit2/allocators/failalloc.c new file mode 100644 index 000000000..5257d1dec --- /dev/null +++ b/src/libgit2/allocators/failalloc.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "failalloc.h" + +void *git_failalloc_malloc(size_t len, const char *file, int line) +{ + GIT_UNUSED(len); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void *git_failalloc_calloc(size_t nelem, size_t elsize, const char *file, int line) +{ + GIT_UNUSED(nelem); + GIT_UNUSED(elsize); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +char *git_failalloc_strdup(const char *str, const char *file, int line) +{ + GIT_UNUSED(str); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +char *git_failalloc_strndup(const char *str, size_t n, const char *file, int line) +{ + GIT_UNUSED(str); + GIT_UNUSED(n); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +char *git_failalloc_substrdup(const char *start, size_t n, const char *file, int line) +{ + GIT_UNUSED(start); + GIT_UNUSED(n); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line) +{ + GIT_UNUSED(ptr); + GIT_UNUSED(size); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void *git_failalloc_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) +{ + GIT_UNUSED(ptr); + GIT_UNUSED(nelem); + GIT_UNUSED(elsize); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void *git_failalloc_mallocarray(size_t nelem, size_t elsize, const char *file, int line) +{ + GIT_UNUSED(nelem); + GIT_UNUSED(elsize); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void git_failalloc_free(void *ptr) +{ + GIT_UNUSED(ptr); +} diff --git a/src/libgit2/allocators/failalloc.h b/src/libgit2/allocators/failalloc.h new file mode 100644 index 000000000..6115e51e7 --- /dev/null +++ b/src/libgit2/allocators/failalloc.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_allocators_failalloc_h__ +#define INCLUDE_allocators_failalloc_h__ + +#include "common.h" + +extern void *git_failalloc_malloc(size_t len, const char *file, int line); +extern void *git_failalloc_calloc(size_t nelem, size_t elsize, const char *file, int line); +extern char *git_failalloc_strdup(const char *str, const char *file, int line); +extern char *git_failalloc_strndup(const char *str, size_t n, const char *file, int line); +extern char *git_failalloc_substrdup(const char *start, size_t n, const char *file, int line); +extern void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line); +extern void *git_failalloc_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line); +extern void *git_failalloc_mallocarray(size_t nelem, size_t elsize, const char *file, int line); +extern void git_failalloc_free(void *ptr); + +#endif diff --git a/src/libgit2/allocators/stdalloc.c b/src/libgit2/allocators/stdalloc.c new file mode 100644 index 000000000..2b36d9f3d --- /dev/null +++ b/src/libgit2/allocators/stdalloc.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "stdalloc.h" + +static void *stdalloc__malloc(size_t len, const char *file, int line) +{ + void *ptr; + + GIT_UNUSED(file); + GIT_UNUSED(line); + +#ifdef GIT_DEBUG_STRICT_ALLOC + if (!len) + return NULL; +#endif + + ptr = malloc(len); + + if (!ptr) + git_error_set_oom(); + + return ptr; +} + +static void *stdalloc__calloc(size_t nelem, size_t elsize, const char *file, int line) +{ + void *ptr; + + GIT_UNUSED(file); + GIT_UNUSED(line); + +#ifdef GIT_DEBUG_STRICT_ALLOC + if (!elsize || !nelem) + return NULL; +#endif + + ptr = calloc(nelem, elsize); + + if (!ptr) + git_error_set_oom(); + + return ptr; +} + +static char *stdalloc__strdup(const char *str, const char *file, int line) +{ + char *ptr; + + GIT_UNUSED(file); + GIT_UNUSED(line); + + ptr = strdup(str); + + if (!ptr) + git_error_set_oom(); + + return ptr; +} + +static char *stdalloc__strndup(const char *str, size_t n, const char *file, int line) +{ + size_t length = 0, alloclength; + char *ptr; + + length = p_strnlen(str, n); + + if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) || + !(ptr = stdalloc__malloc(alloclength, file, line))) + return NULL; + + if (length) + memcpy(ptr, str, length); + + ptr[length] = '\0'; + + return ptr; +} + +static char *stdalloc__substrdup(const char *start, size_t n, const char *file, int line) +{ + char *ptr; + size_t alloclen; + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) || + !(ptr = stdalloc__malloc(alloclen, file, line))) + return NULL; + + memcpy(ptr, start, n); + ptr[n] = '\0'; + return ptr; +} + +static void *stdalloc__realloc(void *ptr, size_t size, const char *file, int line) +{ + void *new_ptr; + + GIT_UNUSED(file); + GIT_UNUSED(line); + +#ifdef GIT_DEBUG_STRICT_ALLOC + if (!size) + return NULL; +#endif + + new_ptr = realloc(ptr, size); + + if (!new_ptr) + git_error_set_oom(); + + return new_ptr; +} + +static void *stdalloc__reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) +{ + size_t newsize; + + if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize)) + return NULL; + + return stdalloc__realloc(ptr, newsize, file, line); +} + +static void *stdalloc__mallocarray(size_t nelem, size_t elsize, const char *file, int line) +{ + return stdalloc__reallocarray(NULL, nelem, elsize, file, line); +} + +static void stdalloc__free(void *ptr) +{ + free(ptr); +} + +int git_stdalloc_init_allocator(git_allocator *allocator) +{ + allocator->gmalloc = stdalloc__malloc; + allocator->gcalloc = stdalloc__calloc; + allocator->gstrdup = stdalloc__strdup; + allocator->gstrndup = stdalloc__strndup; + allocator->gsubstrdup = stdalloc__substrdup; + allocator->grealloc = stdalloc__realloc; + allocator->greallocarray = stdalloc__reallocarray; + allocator->gmallocarray = stdalloc__mallocarray; + allocator->gfree = stdalloc__free; + return 0; +} diff --git a/src/libgit2/allocators/stdalloc.h b/src/libgit2/allocators/stdalloc.h new file mode 100644 index 000000000..fa23fe6e3 --- /dev/null +++ b/src/libgit2/allocators/stdalloc.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_allocators_stdalloc_h__ +#define INCLUDE_allocators_stdalloc_h__ + +#include "common.h" + +#include "alloc.h" + +int git_stdalloc_init_allocator(git_allocator *allocator); + +#endif diff --git a/src/libgit2/allocators/win32_leakcheck.c b/src/libgit2/allocators/win32_leakcheck.c new file mode 100644 index 000000000..fe06a14af --- /dev/null +++ b/src/libgit2/allocators/win32_leakcheck.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "win32_leakcheck.h" + +#if defined(GIT_WIN32_LEAKCHECK) + +#include "win32/w32_leakcheck.h" + +static void *leakcheck_malloc(size_t len, const char *file, int line) +{ + void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); + if (!ptr) git_error_set_oom(); + return ptr; +} + +static void *leakcheck_calloc(size_t nelem, size_t elsize, const char *file, int line) +{ + void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); + if (!ptr) git_error_set_oom(); + return ptr; +} + +static char *leakcheck_strdup(const char *str, const char *file, int line) +{ + char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); + if (!ptr) git_error_set_oom(); + return ptr; +} + +static char *leakcheck_strndup(const char *str, size_t n, const char *file, int line) +{ + size_t length = 0, alloclength; + char *ptr; + + length = p_strnlen(str, n); + + if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) || + !(ptr = leakcheck_malloc(alloclength, file, line))) + return NULL; + + if (length) + memcpy(ptr, str, length); + + ptr[length] = '\0'; + + return ptr; +} + +static char *leakcheck_substrdup(const char *start, size_t n, const char *file, int line) +{ + char *ptr; + size_t alloclen; + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) || + !(ptr = leakcheck_malloc(alloclen, file, line))) + return NULL; + + memcpy(ptr, start, n); + ptr[n] = '\0'; + return ptr; +} + +static void *leakcheck_realloc(void *ptr, size_t size, const char *file, int line) +{ + void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); + if (!new_ptr) git_error_set_oom(); + return new_ptr; +} + +static void *leakcheck_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) +{ + size_t newsize; + + if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize)) + return NULL; + + return leakcheck_realloc(ptr, newsize, file, line); +} + +static void *leakcheck_mallocarray(size_t nelem, size_t elsize, const char *file, int line) +{ + return leakcheck_reallocarray(NULL, nelem, elsize, file, line); +} + +static void leakcheck_free(void *ptr) +{ + free(ptr); +} + +int git_win32_leakcheck_init_allocator(git_allocator *allocator) +{ + allocator->gmalloc = leakcheck_malloc; + allocator->gcalloc = leakcheck_calloc; + allocator->gstrdup = leakcheck_strdup; + allocator->gstrndup = leakcheck_strndup; + allocator->gsubstrdup = leakcheck_substrdup; + allocator->grealloc = leakcheck_realloc; + allocator->greallocarray = leakcheck_reallocarray; + allocator->gmallocarray = leakcheck_mallocarray; + allocator->gfree = leakcheck_free; + return 0; +} + +#else + +int git_win32_leakcheck_init_allocator(git_allocator *allocator) +{ + GIT_UNUSED(allocator); + git_error_set(GIT_EINVALID, "leakcheck memory allocator not available"); + return -1; +} + +#endif diff --git a/src/libgit2/allocators/win32_leakcheck.h b/src/libgit2/allocators/win32_leakcheck.h new file mode 100644 index 000000000..089690f90 --- /dev/null +++ b/src/libgit2/allocators/win32_leakcheck.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_allocators_win32_leakcheck_h +#define INCLUDE_allocators_win32_leakcheck_h + +#include "common.h" + +#include "alloc.h" + +int git_win32_leakcheck_init_allocator(git_allocator *allocator); + +#endif diff --git a/src/libgit2/annotated_commit.c b/src/libgit2/annotated_commit.c new file mode 100644 index 000000000..e48947679 --- /dev/null +++ b/src/libgit2/annotated_commit.c @@ -0,0 +1,240 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "annotated_commit.h" + +#include "refs.h" +#include "cache.h" + +#include "git2/commit.h" +#include "git2/refs.h" +#include "git2/repository.h" +#include "git2/annotated_commit.h" +#include "git2/revparse.h" +#include "git2/tree.h" +#include "git2/index.h" + +static int annotated_commit_init( + git_annotated_commit **out, + git_commit *commit, + const char *description) +{ + git_annotated_commit *annotated_commit; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(commit); + + *out = NULL; + + annotated_commit = git__calloc(1, sizeof(git_annotated_commit)); + GIT_ERROR_CHECK_ALLOC(annotated_commit); + + annotated_commit->type = GIT_ANNOTATED_COMMIT_REAL; + + if ((error = git_commit_dup(&annotated_commit->commit, commit)) < 0) + goto done; + + git_oid_fmt(annotated_commit->id_str, git_commit_id(commit)); + annotated_commit->id_str[GIT_OID_HEXSZ] = '\0'; + + if (!description) + description = annotated_commit->id_str; + + annotated_commit->description = git__strdup(description); + GIT_ERROR_CHECK_ALLOC(annotated_commit->description); + +done: + if (!error) + *out = annotated_commit; + + return error; +} + +static int annotated_commit_init_from_id( + git_annotated_commit **out, + git_repository *repo, + const git_oid *id, + const char *description) +{ + git_commit *commit = NULL; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(id); + + *out = NULL; + + if ((error = git_commit_lookup(&commit, repo, id)) < 0) + goto done; + + error = annotated_commit_init(out, commit, description); + +done: + git_commit_free(commit); + return error; +} + +int git_annotated_commit_lookup( + git_annotated_commit **out, + git_repository *repo, + const git_oid *id) +{ + return annotated_commit_init_from_id(out, repo, id, NULL); +} + +int git_annotated_commit_from_commit( + git_annotated_commit **out, + git_commit *commit) +{ + return annotated_commit_init(out, commit, NULL); +} + +int git_annotated_commit_from_revspec( + git_annotated_commit **out, + git_repository *repo, + const char *revspec) +{ + git_object *obj, *commit; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(revspec); + + if ((error = git_revparse_single(&obj, repo, revspec)) < 0) + return error; + + if ((error = git_object_peel(&commit, obj, GIT_OBJECT_COMMIT))) { + git_object_free(obj); + return error; + } + + error = annotated_commit_init(out, (git_commit *)commit, revspec); + + git_object_free(obj); + git_object_free(commit); + + return error; +} + +int git_annotated_commit_from_ref( + git_annotated_commit **out, + git_repository *repo, + const git_reference *ref) +{ + git_object *peeled; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(ref); + + *out = NULL; + + if ((error = git_reference_peel(&peeled, ref, GIT_OBJECT_COMMIT)) < 0) + return error; + + error = annotated_commit_init_from_id(out, + repo, + git_object_id(peeled), + git_reference_name(ref)); + + if (!error) { + (*out)->ref_name = git__strdup(git_reference_name(ref)); + GIT_ERROR_CHECK_ALLOC((*out)->ref_name); + } + + git_object_free(peeled); + return error; +} + +int git_annotated_commit_from_head( + git_annotated_commit **out, + git_repository *repo) +{ + git_reference *head; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0) + return -1; + + error = git_annotated_commit_from_ref(out, repo, head); + + git_reference_free(head); + return error; +} + +int git_annotated_commit_from_fetchhead( + git_annotated_commit **out, + git_repository *repo, + const char *branch_name, + const char *remote_url, + const git_oid *id) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(branch_name); + GIT_ASSERT_ARG(remote_url); + GIT_ASSERT_ARG(id); + + if (annotated_commit_init_from_id(out, repo, id, branch_name) < 0) + return -1; + + (*out)->ref_name = git__strdup(branch_name); + GIT_ERROR_CHECK_ALLOC((*out)->ref_name); + + (*out)->remote_url = git__strdup(remote_url); + GIT_ERROR_CHECK_ALLOC((*out)->remote_url); + + return 0; +} + + +const git_oid *git_annotated_commit_id( + const git_annotated_commit *annotated_commit) +{ + GIT_ASSERT_ARG_WITH_RETVAL(annotated_commit, NULL); + return git_commit_id(annotated_commit->commit); +} + +const char *git_annotated_commit_ref( + const git_annotated_commit *annotated_commit) +{ + GIT_ASSERT_ARG_WITH_RETVAL(annotated_commit, NULL); + return annotated_commit->ref_name; +} + +void git_annotated_commit_free(git_annotated_commit *annotated_commit) +{ + if (annotated_commit == NULL) + return; + + switch (annotated_commit->type) { + case GIT_ANNOTATED_COMMIT_REAL: + git_commit_free(annotated_commit->commit); + git_tree_free(annotated_commit->tree); + git__free((char *)annotated_commit->description); + git__free((char *)annotated_commit->ref_name); + git__free((char *)annotated_commit->remote_url); + break; + case GIT_ANNOTATED_COMMIT_VIRTUAL: + git_index_free(annotated_commit->index); + git_array_clear(annotated_commit->parents); + break; + default: + abort(); + } + + git__free(annotated_commit); +} diff --git a/src/libgit2/annotated_commit.h b/src/libgit2/annotated_commit.h new file mode 100644 index 000000000..444a2ed10 --- /dev/null +++ b/src/libgit2/annotated_commit.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_annotated_commit_h__ +#define INCLUDE_annotated_commit_h__ + +#include "common.h" + +#include "oidarray.h" + +#include "git2/oid.h" + +typedef enum { + GIT_ANNOTATED_COMMIT_REAL = 1, + GIT_ANNOTATED_COMMIT_VIRTUAL = 2 +} git_annotated_commit_t; + +/** + * Internal structure for merge inputs. An annotated commit is generally + * "real" and backed by an actual commit in the repository, but merge will + * internally create "virtual" commits that are in-memory intermediate + * commits backed by an index. + */ +struct git_annotated_commit { + git_annotated_commit_t type; + + /* real commit */ + git_commit *commit; + git_tree *tree; + + /* virtual commit structure */ + git_index *index; + git_array_oid_t parents; + + /* how this commit was looked up */ + const char *description; + + const char *ref_name; + const char *remote_url; + + char id_str[GIT_OID_HEXSZ+1]; +}; + +extern int git_annotated_commit_from_head(git_annotated_commit **out, + git_repository *repo); +extern int git_annotated_commit_from_commit(git_annotated_commit **out, + git_commit *commit); + +#endif diff --git a/src/libgit2/apply.c b/src/libgit2/apply.c new file mode 100644 index 000000000..18304da4d --- /dev/null +++ b/src/libgit2/apply.c @@ -0,0 +1,898 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2/apply.h" +#include "git2/patch.h" +#include "git2/filter.h" +#include "git2/blob.h" +#include "git2/index.h" +#include "git2/checkout.h" +#include "git2/repository.h" +#include "array.h" +#include "patch.h" +#include "futils.h" +#include "delta.h" +#include "zstream.h" +#include "reader.h" +#include "index.h" +#include "apply.h" + +typedef struct { + /* The lines that we allocate ourself are allocated out of the pool. + * (Lines may have been allocated out of the diff.) + */ + git_pool pool; + git_vector lines; +} patch_image; + +static int apply_err(const char *fmt, ...) GIT_FORMAT_PRINTF(1, 2); +static int apply_err(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + git_error_vset(GIT_ERROR_PATCH, fmt, ap); + va_end(ap); + + return GIT_EAPPLYFAIL; +} + +static void patch_line_init( + git_diff_line *out, + const char *in, + size_t in_len, + size_t in_offset) +{ + out->content = in; + out->content_len = in_len; + out->content_offset = in_offset; +} + +#define PATCH_IMAGE_INIT { GIT_POOL_INIT, GIT_VECTOR_INIT } + +static int patch_image_init_fromstr( + patch_image *out, const char *in, size_t in_len) +{ + git_diff_line *line; + const char *start, *end; + + memset(out, 0x0, sizeof(patch_image)); + + if (git_pool_init(&out->pool, sizeof(git_diff_line)) < 0) + return -1; + + if (!in_len) + return 0; + + for (start = in; start < in + in_len; start = end) { + end = memchr(start, '\n', in_len - (start - in)); + + if (end == NULL) + end = in + in_len; + + else if (end < in + in_len) + end++; + + line = git_pool_mallocz(&out->pool, 1); + GIT_ERROR_CHECK_ALLOC(line); + + if (git_vector_insert(&out->lines, line) < 0) + return -1; + + patch_line_init(line, start, (end - start), (start - in)); + } + + return 0; +} + +static void patch_image_free(patch_image *image) +{ + if (image == NULL) + return; + + git_pool_clear(&image->pool); + git_vector_free(&image->lines); +} + +static bool match_hunk( + patch_image *image, + patch_image *preimage, + size_t linenum) +{ + bool match = 0; + size_t i; + + /* Ensure this hunk is within the image boundaries. */ + if (git_vector_length(&preimage->lines) + linenum > + git_vector_length(&image->lines)) + return 0; + + match = 1; + + /* Check exact match. */ + for (i = 0; i < git_vector_length(&preimage->lines); i++) { + git_diff_line *preimage_line = git_vector_get(&preimage->lines, i); + git_diff_line *image_line = git_vector_get(&image->lines, linenum + i); + + if (preimage_line->content_len != image_line->content_len || + memcmp(preimage_line->content, image_line->content, image_line->content_len) != 0) { + match = 0; + break; + } + } + + return match; +} + +static bool find_hunk_linenum( + size_t *out, + patch_image *image, + patch_image *preimage, + size_t linenum) +{ + size_t max = git_vector_length(&image->lines); + bool match; + + if (linenum > max) + linenum = max; + + match = match_hunk(image, preimage, linenum); + + *out = linenum; + return match; +} + +static int update_hunk( + patch_image *image, + size_t linenum, + patch_image *preimage, + patch_image *postimage) +{ + size_t postlen = git_vector_length(&postimage->lines); + size_t prelen = git_vector_length(&preimage->lines); + size_t i; + int error = 0; + + if (postlen > prelen) + error = git_vector_insert_null( + &image->lines, linenum, (postlen - prelen)); + else if (prelen > postlen) + error = git_vector_remove_range( + &image->lines, linenum, (prelen - postlen)); + + if (error) { + git_error_set_oom(); + return -1; + } + + for (i = 0; i < git_vector_length(&postimage->lines); i++) { + image->lines.contents[linenum + i] = + git_vector_get(&postimage->lines, i); + } + + return 0; +} + +typedef struct { + git_apply_options opts; + size_t skipped_new_lines; + size_t skipped_old_lines; +} apply_hunks_ctx; + +static int apply_hunk( + patch_image *image, + git_patch *patch, + git_patch_hunk *hunk, + apply_hunks_ctx *ctx) +{ + patch_image preimage = PATCH_IMAGE_INIT, postimage = PATCH_IMAGE_INIT; + size_t line_num, i; + int error = 0; + + if (ctx->opts.hunk_cb) { + error = ctx->opts.hunk_cb(&hunk->hunk, ctx->opts.payload); + + if (error) { + if (error > 0) { + ctx->skipped_new_lines += hunk->hunk.new_lines; + ctx->skipped_old_lines += hunk->hunk.old_lines; + error = 0; + } + + goto done; + } + } + + for (i = 0; i < hunk->line_count; i++) { + size_t linenum = hunk->line_start + i; + git_diff_line *line = git_array_get(patch->lines, linenum), *prev; + + if (!line) { + error = apply_err("preimage does not contain line %"PRIuZ, linenum); + goto done; + } + + switch (line->origin) { + case GIT_DIFF_LINE_CONTEXT_EOFNL: + case GIT_DIFF_LINE_DEL_EOFNL: + case GIT_DIFF_LINE_ADD_EOFNL: + prev = i ? git_array_get(patch->lines, linenum - 1) : NULL; + if (prev && prev->content[prev->content_len - 1] == '\n') + prev->content_len -= 1; + break; + case GIT_DIFF_LINE_CONTEXT: + if ((error = git_vector_insert(&preimage.lines, line)) < 0 || + (error = git_vector_insert(&postimage.lines, line)) < 0) + goto done; + break; + case GIT_DIFF_LINE_DELETION: + if ((error = git_vector_insert(&preimage.lines, line)) < 0) + goto done; + break; + case GIT_DIFF_LINE_ADDITION: + if ((error = git_vector_insert(&postimage.lines, line)) < 0) + goto done; + break; + } + } + + if (hunk->hunk.new_start) { + line_num = hunk->hunk.new_start - + ctx->skipped_new_lines + + ctx->skipped_old_lines - + 1; + } else { + line_num = 0; + } + + if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) { + error = apply_err("hunk at line %d did not apply", + hunk->hunk.new_start); + goto done; + } + + error = update_hunk(image, line_num, &preimage, &postimage); + +done: + patch_image_free(&preimage); + patch_image_free(&postimage); + + return error; +} + +static int apply_hunks( + git_str *out, + const char *source, + size_t source_len, + git_patch *patch, + apply_hunks_ctx *ctx) +{ + git_patch_hunk *hunk; + git_diff_line *line; + patch_image image; + size_t i; + int error = 0; + + if ((error = patch_image_init_fromstr(&image, source, source_len)) < 0) + goto done; + + git_array_foreach(patch->hunks, i, hunk) { + if ((error = apply_hunk(&image, patch, hunk, ctx)) < 0) + goto done; + } + + git_vector_foreach(&image.lines, i, line) + git_str_put(out, line->content, line->content_len); + +done: + patch_image_free(&image); + + return error; +} + +static int apply_binary_delta( + git_str *out, + const char *source, + size_t source_len, + git_diff_binary_file *binary_file) +{ + git_str inflated = GIT_STR_INIT; + int error = 0; + + /* no diff means identical contents */ + if (binary_file->datalen == 0) + return git_str_put(out, source, source_len); + + error = git_zstream_inflatebuf(&inflated, + binary_file->data, binary_file->datalen); + + if (!error && inflated.size != binary_file->inflatedlen) { + error = apply_err("inflated delta does not match expected length"); + git_str_dispose(out); + } + + if (error < 0) + goto done; + + if (binary_file->type == GIT_DIFF_BINARY_DELTA) { + void *data; + size_t data_len; + + error = git_delta_apply(&data, &data_len, (void *)source, source_len, + (void *)inflated.ptr, inflated.size); + + out->ptr = data; + out->size = data_len; + out->asize = data_len; + } + else if (binary_file->type == GIT_DIFF_BINARY_LITERAL) { + git_str_swap(out, &inflated); + } + else { + error = apply_err("unknown binary delta type"); + goto done; + } + +done: + git_str_dispose(&inflated); + return error; +} + +static int apply_binary( + git_str *out, + const char *source, + size_t source_len, + git_patch *patch) +{ + git_str reverse = GIT_STR_INIT; + int error = 0; + + if (!patch->binary.contains_data) { + error = apply_err("patch does not contain binary data"); + goto done; + } + + if (!patch->binary.old_file.datalen && !patch->binary.new_file.datalen) + goto done; + + /* first, apply the new_file delta to the given source */ + if ((error = apply_binary_delta(out, source, source_len, + &patch->binary.new_file)) < 0) + goto done; + + /* second, apply the old_file delta to sanity check the result */ + if ((error = apply_binary_delta(&reverse, out->ptr, out->size, + &patch->binary.old_file)) < 0) + goto done; + + /* Verify that the resulting file with the reverse patch applied matches the source file */ + if (source_len != reverse.size || + (source_len && memcmp(source, reverse.ptr, source_len) != 0)) { + error = apply_err("binary patch did not apply cleanly"); + goto done; + } + +done: + if (error < 0) + git_str_dispose(out); + + git_str_dispose(&reverse); + return error; +} + +int git_apply__patch( + git_str *contents_out, + char **filename_out, + unsigned int *mode_out, + const char *source, + size_t source_len, + git_patch *patch, + const git_apply_options *given_opts) +{ + apply_hunks_ctx ctx = { GIT_APPLY_OPTIONS_INIT }; + char *filename = NULL; + unsigned int mode = 0; + int error = 0; + + GIT_ASSERT_ARG(contents_out); + GIT_ASSERT_ARG(filename_out); + GIT_ASSERT_ARG(mode_out); + GIT_ASSERT_ARG(source || !source_len); + GIT_ASSERT_ARG(patch); + + if (given_opts) + memcpy(&ctx.opts, given_opts, sizeof(git_apply_options)); + + *filename_out = NULL; + *mode_out = 0; + + if (patch->delta->status != GIT_DELTA_DELETED) { + const git_diff_file *newfile = &patch->delta->new_file; + + filename = git__strdup(newfile->path); + mode = newfile->mode ? + newfile->mode : GIT_FILEMODE_BLOB; + } + + if (patch->delta->flags & GIT_DIFF_FLAG_BINARY) + error = apply_binary(contents_out, source, source_len, patch); + else if (patch->hunks.size) + error = apply_hunks(contents_out, source, source_len, patch, &ctx); + else + error = git_str_put(contents_out, source, source_len); + + if (error) + goto done; + + if (patch->delta->status == GIT_DELTA_DELETED && + git_str_len(contents_out) > 0) { + error = apply_err("removal patch leaves file contents"); + goto done; + } + + *filename_out = filename; + *mode_out = mode; + +done: + if (error < 0) + git__free(filename); + + return error; +} + +static int apply_one( + git_repository *repo, + git_reader *preimage_reader, + git_index *preimage, + git_reader *postimage_reader, + git_index *postimage, + git_diff *diff, + git_strmap *removed_paths, + size_t i, + const git_apply_options *opts) +{ + git_patch *patch = NULL; + git_str pre_contents = GIT_STR_INIT, post_contents = GIT_STR_INIT; + const git_diff_delta *delta; + char *filename = NULL; + unsigned int mode; + git_oid pre_id, post_id; + git_filemode_t pre_filemode; + git_index_entry pre_entry, post_entry; + bool skip_preimage = false; + int error; + + if ((error = git_patch_from_diff(&patch, diff, i)) < 0) + goto done; + + delta = git_patch_get_delta(patch); + + if (opts->delta_cb) { + error = opts->delta_cb(delta, opts->payload); + + if (error) { + if (error > 0) + error = 0; + + goto done; + } + } + + /* + * Ensure that the file has not been deleted or renamed if we're + * applying a modification delta. + */ + if (delta->status != GIT_DELTA_RENAMED && + delta->status != GIT_DELTA_ADDED) { + if (git_strmap_exists(removed_paths, delta->old_file.path)) { + error = apply_err("path '%s' has been renamed or deleted", delta->old_file.path); + goto done; + } + } + + /* + * We may be applying a second delta to an already seen file. If so, + * use the already modified data in the postimage instead of the + * content from the index or working directory. (Don't do this in + * the case of a rename, which must be specified before additional + * deltas since we apply deltas to the target filename.) + */ + if (delta->status != GIT_DELTA_RENAMED) { + if ((error = git_reader_read(&pre_contents, &pre_id, &pre_filemode, + postimage_reader, delta->old_file.path)) == 0) { + skip_preimage = true; + } else if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } else { + goto done; + } + } + + if (!skip_preimage && delta->status != GIT_DELTA_ADDED) { + error = git_reader_read(&pre_contents, &pre_id, &pre_filemode, + preimage_reader, delta->old_file.path); + + /* ENOTFOUND means the preimage was not found; apply failed. */ + if (error == GIT_ENOTFOUND) + error = GIT_EAPPLYFAIL; + + /* When applying to BOTH, the index did not match the workdir. */ + if (error == GIT_READER_MISMATCH) + error = apply_err("%s: does not match index", delta->old_file.path); + + if (error < 0) + goto done; + + /* + * We need to populate the preimage data structure with the + * contents that we are using as the preimage for this file. + * This allows us to apply patches to files that have been + * modified in the working directory. During checkout, + * we will use this expected preimage as the baseline, and + * limit checkout to only the paths affected by patch + * application. (Without this, we would fail to write the + * postimage contents to any file that had been modified + * from HEAD on-disk, even if the patch application succeeded.) + * Use the contents from the delta where available - some + * fields may not be available, like the old file mode (eg in + * an exact rename situation) so trust the patch parsing to + * validate and use the preimage data in that case. + */ + if (preimage) { + memset(&pre_entry, 0, sizeof(git_index_entry)); + pre_entry.path = delta->old_file.path; + pre_entry.mode = delta->old_file.mode ? delta->old_file.mode : pre_filemode; + git_oid_cpy(&pre_entry.id, &pre_id); + + if ((error = git_index_add(preimage, &pre_entry)) < 0) + goto done; + } + } + + if (delta->status != GIT_DELTA_DELETED) { + if ((error = git_apply__patch(&post_contents, &filename, &mode, + pre_contents.ptr, pre_contents.size, patch, opts)) < 0 || + (error = git_blob_create_from_buffer(&post_id, repo, + post_contents.ptr, post_contents.size)) < 0) + goto done; + + memset(&post_entry, 0, sizeof(git_index_entry)); + post_entry.path = filename; + post_entry.mode = mode; + git_oid_cpy(&post_entry.id, &post_id); + + if ((error = git_index_add(postimage, &post_entry)) < 0) + goto done; + } + + if (delta->status == GIT_DELTA_RENAMED || + delta->status == GIT_DELTA_DELETED) + error = git_strmap_set(removed_paths, delta->old_file.path, (char *) delta->old_file.path); + + if (delta->status == GIT_DELTA_RENAMED || + delta->status == GIT_DELTA_ADDED) + git_strmap_delete(removed_paths, delta->new_file.path); + +done: + git_str_dispose(&pre_contents); + git_str_dispose(&post_contents); + git__free(filename); + git_patch_free(patch); + + return error; +} + +static int apply_deltas( + git_repository *repo, + git_reader *pre_reader, + git_index *preimage, + git_reader *post_reader, + git_index *postimage, + git_diff *diff, + const git_apply_options *opts) +{ + git_strmap *removed_paths; + size_t i; + int error = 0; + + if (git_strmap_new(&removed_paths) < 0) + return -1; + + for (i = 0; i < git_diff_num_deltas(diff); i++) { + if ((error = apply_one(repo, pre_reader, preimage, post_reader, postimage, diff, removed_paths, i, opts)) < 0) + goto done; + } + +done: + git_strmap_free(removed_paths); + return error; +} + +int git_apply_to_tree( + git_index **out, + git_repository *repo, + git_tree *preimage, + git_diff *diff, + const git_apply_options *given_opts) +{ + git_index *postimage = NULL; + git_reader *pre_reader = NULL, *post_reader = NULL; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + const git_diff_delta *delta; + size_t i; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(preimage); + GIT_ASSERT_ARG(diff); + + *out = NULL; + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_apply_options)); + + if ((error = git_reader_for_tree(&pre_reader, preimage)) < 0) + goto done; + + /* + * put the current tree into the postimage as-is - the diff will + * replace any entries contained therein + */ + if ((error = git_index_new(&postimage)) < 0 || + (error = git_index_read_tree(postimage, preimage)) < 0 || + (error = git_reader_for_index(&post_reader, repo, postimage)) < 0) + goto done; + + /* + * Remove the old paths from the index before applying diffs - + * we need to do a full pass to remove them before adding deltas, + * in order to handle rename situations. + */ + for (i = 0; i < git_diff_num_deltas(diff); i++) { + delta = git_diff_get_delta(diff, i); + + if (delta->status == GIT_DELTA_DELETED || + delta->status == GIT_DELTA_RENAMED) { + if ((error = git_index_remove(postimage, + delta->old_file.path, 0)) < 0) + goto done; + } + } + + if ((error = apply_deltas(repo, pre_reader, NULL, post_reader, postimage, diff, &opts)) < 0) + goto done; + + *out = postimage; + +done: + if (error < 0) + git_index_free(postimage); + + git_reader_free(pre_reader); + git_reader_free(post_reader); + + return error; +} + +static int git_apply__to_workdir( + git_repository *repo, + git_diff *diff, + git_index *preimage, + git_index *postimage, + git_apply_location_t location, + git_apply_options *opts) +{ + git_vector paths = GIT_VECTOR_INIT; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + const git_diff_delta *delta; + size_t i; + int error; + + GIT_UNUSED(opts); + + /* + * Limit checkout to the paths affected by the diff; this ensures + * that other modifications in the working directory are unaffected. + */ + if ((error = git_vector_init(&paths, git_diff_num_deltas(diff), NULL)) < 0) + goto done; + + for (i = 0; i < git_diff_num_deltas(diff); i++) { + delta = git_diff_get_delta(diff, i); + + if ((error = git_vector_insert(&paths, (void *)delta->old_file.path)) < 0) + goto done; + + if (strcmp(delta->old_file.path, delta->new_file.path) && + (error = git_vector_insert(&paths, (void *)delta->new_file.path)) < 0) + goto done; + } + + checkout_opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + checkout_opts.checkout_strategy |= GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; + checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_WRITE_INDEX; + + if (location == GIT_APPLY_LOCATION_WORKDIR) + checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; + + checkout_opts.paths.strings = (char **)paths.contents; + checkout_opts.paths.count = paths.length; + + checkout_opts.baseline_index = preimage; + + error = git_checkout_index(repo, postimage, &checkout_opts); + +done: + git_vector_free(&paths); + return error; +} + +static int git_apply__to_index( + git_repository *repo, + git_diff *diff, + git_index *preimage, + git_index *postimage, + git_apply_options *opts) +{ + git_index *index = NULL; + const git_diff_delta *delta; + const git_index_entry *entry; + size_t i; + int error; + + GIT_UNUSED(preimage); + GIT_UNUSED(opts); + + if ((error = git_repository_index(&index, repo)) < 0) + goto done; + + /* Remove deleted (or renamed) paths from the index. */ + for (i = 0; i < git_diff_num_deltas(diff); i++) { + delta = git_diff_get_delta(diff, i); + + if (delta->status == GIT_DELTA_DELETED || + delta->status == GIT_DELTA_RENAMED) { + if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0) + goto done; + } + } + + /* Then add the changes back to the index. */ + for (i = 0; i < git_index_entrycount(postimage); i++) { + entry = git_index_get_byindex(postimage, i); + + if ((error = git_index_add(index, entry)) < 0) + goto done; + } + +done: + git_index_free(index); + return error; +} + +int git_apply_options_init(git_apply_options *opts, unsigned int version) +{ + GIT_ASSERT_ARG(opts); + + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_apply_options, GIT_APPLY_OPTIONS_INIT); + return 0; +} + +/* + * Handle the three application options ("locations"): + * + * GIT_APPLY_LOCATION_WORKDIR: the default, emulates `git apply`. + * Applies the diff only to the workdir items and ignores the index + * entirely. + * + * GIT_APPLY_LOCATION_INDEX: emulates `git apply --cached`. + * Applies the diff only to the index items and ignores the workdir + * completely. + * + * GIT_APPLY_LOCATION_BOTH: emulates `git apply --index`. + * Applies the diff to both the index items and the working directory + * items. + */ + +int git_apply( + git_repository *repo, + git_diff *diff, + git_apply_location_t location, + const git_apply_options *given_opts) +{ + git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; + git_index *index = NULL, *preimage = NULL, *postimage = NULL; + git_reader *pre_reader = NULL, *post_reader = NULL; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + int error = GIT_EINVALID; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(diff); + + GIT_ERROR_CHECK_VERSION( + given_opts, GIT_APPLY_OPTIONS_VERSION, "git_apply_options"); + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_apply_options)); + + /* + * by default, we apply a patch directly to the working directory; + * in `--cached` or `--index` mode, we apply to the contents already + * in the index. + */ + switch (location) { + case GIT_APPLY_LOCATION_BOTH: + error = git_reader_for_workdir(&pre_reader, repo, true); + break; + case GIT_APPLY_LOCATION_INDEX: + error = git_reader_for_index(&pre_reader, repo, NULL); + break; + case GIT_APPLY_LOCATION_WORKDIR: + error = git_reader_for_workdir(&pre_reader, repo, false); + break; + default: + GIT_ASSERT(false); + } + + if (error < 0) + goto done; + + /* + * Build the preimage and postimage (differences). Note that + * this is not the complete preimage or postimage, it only + * contains the files affected by the patch. We want to avoid + * having the full repo index, so we will limit our checkout + * to only write these files that were affected by the diff. + */ + if ((error = git_index_new(&preimage)) < 0 || + (error = git_index_new(&postimage)) < 0 || + (error = git_reader_for_index(&post_reader, repo, postimage)) < 0) + goto done; + + if (!(opts.flags & GIT_APPLY_CHECK)) + if ((error = git_repository_index(&index, repo)) < 0 || + (error = git_indexwriter_init(&indexwriter, index)) < 0) + goto done; + + if ((error = apply_deltas(repo, pre_reader, preimage, post_reader, postimage, diff, &opts)) < 0) + goto done; + + if ((opts.flags & GIT_APPLY_CHECK)) + goto done; + + switch (location) { + case GIT_APPLY_LOCATION_BOTH: + error = git_apply__to_workdir(repo, diff, preimage, postimage, location, &opts); + break; + case GIT_APPLY_LOCATION_INDEX: + error = git_apply__to_index(repo, diff, preimage, postimage, &opts); + break; + case GIT_APPLY_LOCATION_WORKDIR: + error = git_apply__to_workdir(repo, diff, preimage, postimage, location, &opts); + break; + default: + GIT_ASSERT(false); + } + + if (error < 0) + goto done; + + error = git_indexwriter_commit(&indexwriter); + +done: + git_indexwriter_cleanup(&indexwriter); + git_index_free(postimage); + git_index_free(preimage); + git_index_free(index); + git_reader_free(pre_reader); + git_reader_free(post_reader); + + return error; +} diff --git a/src/libgit2/apply.h b/src/libgit2/apply.h new file mode 100644 index 000000000..e990a7107 --- /dev/null +++ b/src/libgit2/apply.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_apply_h__ +#define INCLUDE_apply_h__ + +#include "common.h" + +#include "git2/patch.h" +#include "git2/apply.h" +#include "str.h" + +extern int git_apply__patch( + git_str *out, + char **filename, + unsigned int *mode, + const char *source, + size_t source_len, + git_patch *patch, + const git_apply_options *opts); + +#endif diff --git a/src/libgit2/array.h b/src/libgit2/array.h new file mode 100644 index 000000000..e97688b36 --- /dev/null +++ b/src/libgit2/array.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_array_h__ +#define INCLUDE_array_h__ + +#include "common.h" + +/* + * Use this to declare a typesafe resizable array of items, a la: + * + * git_array_t(int) my_ints = GIT_ARRAY_INIT; + * ... + * int *i = git_array_alloc(my_ints); + * GIT_ERROR_CHECK_ALLOC(i); + * ... + * git_array_clear(my_ints); + * + * You may also want to do things like: + * + * typedef git_array_t(my_struct) my_struct_array_t; + */ +#define git_array_t(type) struct { type *ptr; size_t size, asize; } + +#define GIT_ARRAY_INIT { NULL, 0, 0 } + +#define git_array_init(a) \ + do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0) + +#define git_array_init_to_size(a, desired) \ + do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0) + +#define git_array_clear(a) \ + do { git__free((a).ptr); git_array_init(a); } while (0) + +#define GIT_ERROR_CHECK_ARRAY(a) GIT_ERROR_CHECK_ALLOC((a).ptr) + + +typedef git_array_t(char) git_array_generic_t; + +/* use a generic array for growth, return 0 on success */ +GIT_INLINE(int) git_array_grow(void *_a, size_t item_size) +{ + volatile git_array_generic_t *a = _a; + size_t new_size; + char *new_array; + + if (a->size < 8) { + new_size = 8; + } else { + if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, a->size, 3)) + goto on_oom; + new_size /= 2; + } + + if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL) + goto on_oom; + + a->ptr = new_array; + a->asize = new_size; + return 0; + +on_oom: + git_array_clear(*a); + return -1; +} + +#define git_array_alloc(a) \ + (((a).size < (a).asize || git_array_grow(&(a), sizeof(*(a).ptr)) == 0) ? \ + &(a).ptr[(a).size++] : (void *)NULL) + +#define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : (void *)NULL) + +#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : (void *)NULL) + +#define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : (void *)NULL) + +#define git_array_size(a) (a).size + +#define git_array_valid_index(a, i) ((i) < (a).size) + +#define git_array_foreach(a, i, element) \ + for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++) + +GIT_INLINE(int) git_array__search( + size_t *out, + void *array_ptr, + size_t item_size, + size_t array_len, + int (*compare)(const void *, const void *), + const void *key) +{ + size_t lim; + unsigned char *part, *array = array_ptr, *base = array_ptr; + int cmp = -1; + + for (lim = array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1) * item_size; + cmp = (*compare)(key, part); + + if (cmp == 0) { + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ + base = part + 1 * item_size; + lim--; + } /* else take left partition */ + } + + if (out) + *out = (base - array) / item_size; + + return (cmp == 0) ? 0 : GIT_ENOTFOUND; +} + +#define git_array_search(out, a, cmp, key) \ + git_array__search(out, (a).ptr, sizeof(*(a).ptr), (a).size, \ + (cmp), (key)) + +#endif diff --git a/src/libgit2/assert_safe.h b/src/libgit2/assert_safe.h new file mode 100644 index 000000000..8c261100f --- /dev/null +++ b/src/libgit2/assert_safe.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_assert_safe_h__ +#define INCLUDE_assert_safe_h__ + +/* + * In a debug build, we'll assert(3) for aide in debugging. In release + * builds, we will provide macros that will set an error message that + * indicate a failure and return. Note that memory leaks can occur in + * a release-mode assertion failure -- it is impractical to provide + * safe clean up routines in these very extreme failures, but care + * should be taken to not leak very large objects. + */ + +#if (defined(_DEBUG) || defined(GIT_ASSERT_HARD)) && GIT_ASSERT_HARD != 0 +# include + +# define GIT_ASSERT(expr) assert(expr) +# define GIT_ASSERT_ARG(expr) assert(expr) + +# define GIT_ASSERT_WITH_RETVAL(expr, fail) assert(expr) +# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) assert(expr) +#else + +/** Internal consistency check to stop the function. */ +# define GIT_ASSERT(expr) GIT_ASSERT_WITH_RETVAL(expr, -1) + +/** + * Assert that a consumer-provided argument is valid, setting an + * actionable error message and returning -1 if it is not. + */ +# define GIT_ASSERT_ARG(expr) GIT_ASSERT_ARG_WITH_RETVAL(expr, -1) + +/** Internal consistency check to return the `fail` param on failure. */ +# define GIT_ASSERT_WITH_RETVAL(expr, fail) \ + GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INTERNAL, "unrecoverable internal error", fail) + +/** + * Assert that a consumer-provided argument is valid, setting an + * actionable error message and returning the `fail` param if not. + */ +# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) \ + GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INVALID, "invalid argument", fail) + +# define GIT_ASSERT__WITH_RETVAL(expr, code, msg, fail) do { \ + if (!(expr)) { \ + git_error_set(code, "%s: '%s'", msg, #expr); \ + return fail; \ + } \ + } while(0) + +#endif /* GIT_ASSERT_HARD */ + +#endif diff --git a/src/libgit2/attr.c b/src/libgit2/attr.c new file mode 100644 index 000000000..1623b1d45 --- /dev/null +++ b/src/libgit2/attr.c @@ -0,0 +1,700 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "attr.h" + +#include "repository.h" +#include "sysdir.h" +#include "config.h" +#include "attr_file.h" +#include "ignore.h" +#include "git2/oid.h" +#include + +const char *git_attr__true = "[internal]__TRUE__"; +const char *git_attr__false = "[internal]__FALSE__"; +const char *git_attr__unset = "[internal]__UNSET__"; + +git_attr_value_t git_attr_value(const char *attr) +{ + if (attr == NULL || attr == git_attr__unset) + return GIT_ATTR_VALUE_UNSPECIFIED; + + if (attr == git_attr__true) + return GIT_ATTR_VALUE_TRUE; + + if (attr == git_attr__false) + return GIT_ATTR_VALUE_FALSE; + + return GIT_ATTR_VALUE_STRING; +} + +static int collect_attr_files( + git_repository *repo, + git_attr_session *attr_session, + git_attr_options *opts, + const char *path, + git_vector *files); + +static void release_attr_files(git_vector *files); + +int git_attr_get_ext( + const char **value, + git_repository *repo, + git_attr_options *opts, + const char *pathname, + const char *name) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + size_t i, j; + git_attr_file *file; + git_attr_name attr; + git_attr_rule *rule; + git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; + + GIT_ASSERT_ARG(value); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options"); + + *value = NULL; + + if (git_repository_is_bare(repo)) + dir_flag = GIT_DIR_FLAG_FALSE; + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) + return -1; + + if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0) + goto cleanup; + + memset(&attr, 0, sizeof(attr)); + attr.name = name; + attr.name_hash = git_attr_file__name_hash(name); + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + size_t pos; + + if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) { + *value = ((git_attr_assignment *)git_vector_get( + &rule->assigns, pos))->value; + goto cleanup; + } + } + } + +cleanup: + release_attr_files(&files); + git_attr_path__free(&path); + + return error; +} + +int git_attr_get( + const char **value, + git_repository *repo, + uint32_t flags, + const char *pathname, + const char *name) +{ + git_attr_options opts = GIT_ATTR_OPTIONS_INIT; + + opts.flags = flags; + + return git_attr_get_ext(value, repo, &opts, pathname, name); +} + + +typedef struct { + git_attr_name name; + git_attr_assignment *found; +} attr_get_many_info; + +int git_attr_get_many_with_session( + const char **values, + git_repository *repo, + git_attr_session *attr_session, + git_attr_options *opts, + const char *pathname, + size_t num_attr, + const char **names) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + size_t i, j, k; + git_attr_file *file; + git_attr_rule *rule; + attr_get_many_info *info = NULL; + size_t num_found = 0; + git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; + + if (!num_attr) + return 0; + + GIT_ASSERT_ARG(values); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(pathname); + GIT_ASSERT_ARG(names); + GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options"); + + if (git_repository_is_bare(repo)) + dir_flag = GIT_DIR_FLAG_FALSE; + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) + return -1; + + if ((error = collect_attr_files(repo, attr_session, opts, pathname, &files)) < 0) + goto cleanup; + + info = git__calloc(num_attr, sizeof(attr_get_many_info)); + GIT_ERROR_CHECK_ALLOC(info); + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + + for (k = 0; k < num_attr; k++) { + size_t pos; + + if (info[k].found != NULL) /* already found assignment */ + continue; + + if (!info[k].name.name) { + info[k].name.name = names[k]; + info[k].name.name_hash = git_attr_file__name_hash(names[k]); + } + + if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) { + info[k].found = (git_attr_assignment *) + git_vector_get(&rule->assigns, pos); + values[k] = info[k].found->value; + + if (++num_found == num_attr) + goto cleanup; + } + } + } + } + + for (k = 0; k < num_attr; k++) { + if (!info[k].found) + values[k] = NULL; + } + +cleanup: + release_attr_files(&files); + git_attr_path__free(&path); + git__free(info); + + return error; +} + +int git_attr_get_many( + const char **values, + git_repository *repo, + uint32_t flags, + const char *pathname, + size_t num_attr, + const char **names) +{ + git_attr_options opts = GIT_ATTR_OPTIONS_INIT; + + opts.flags = flags; + + return git_attr_get_many_with_session( + values, repo, NULL, &opts, pathname, num_attr, names); +} + +int git_attr_get_many_ext( + const char **values, + git_repository *repo, + git_attr_options *opts, + const char *pathname, + size_t num_attr, + const char **names) +{ + return git_attr_get_many_with_session( + values, repo, NULL, opts, pathname, num_attr, names); +} + +int git_attr_foreach( + git_repository *repo, + uint32_t flags, + const char *pathname, + int (*callback)(const char *name, const char *value, void *payload), + void *payload) +{ + git_attr_options opts = GIT_ATTR_OPTIONS_INIT; + + opts.flags = flags; + + return git_attr_foreach_ext(repo, &opts, pathname, callback, payload); +} + +int git_attr_foreach_ext( + git_repository *repo, + git_attr_options *opts, + const char *pathname, + int (*callback)(const char *name, const char *value, void *payload), + void *payload) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + size_t i, j, k; + git_attr_file *file; + git_attr_rule *rule; + git_attr_assignment *assign; + git_strmap *seen = NULL; + git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(callback); + GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options"); + + if (git_repository_is_bare(repo)) + dir_flag = GIT_DIR_FLAG_FALSE; + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0) + return -1; + + if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0 || + (error = git_strmap_new(&seen)) < 0) + goto cleanup; + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + + git_vector_foreach(&rule->assigns, k, assign) { + /* skip if higher priority assignment was already seen */ + if (git_strmap_exists(seen, assign->name)) + continue; + + if ((error = git_strmap_set(seen, assign->name, assign)) < 0) + goto cleanup; + + error = callback(assign->name, assign->value, payload); + if (error) { + git_error_set_after_callback(error); + goto cleanup; + } + } + } + } + +cleanup: + git_strmap_free(seen); + release_attr_files(&files); + git_attr_path__free(&path); + + return error; +} + +static int preload_attr_source( + git_repository *repo, + git_attr_session *attr_session, + git_attr_file_source *source) +{ + int error; + git_attr_file *preload = NULL; + + if (!source) + return 0; + + error = git_attr_cache__get(&preload, repo, attr_session, source, + git_attr_file__parse_buffer, true); + + if (!error) + git_attr_file__free(preload); + + return error; +} + +GIT_INLINE(int) preload_attr_file( + git_repository *repo, + git_attr_session *attr_session, + const char *base, + const char *filename) +{ + git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE }; + + if (!filename) + return 0; + + source.base = base; + source.filename = filename; + + return preload_attr_source(repo, attr_session, &source); +} + +static int system_attr_file( + git_str *out, + git_attr_session *attr_session) +{ + int error; + + if (!attr_session) { + error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM); + + if (error == GIT_ENOTFOUND) + git_error_clear(); + + return error; + } + + if (!attr_session->init_sysdir) { + error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM); + + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error) + return error; + + attr_session->init_sysdir = 1; + } + + if (attr_session->sysdir.size == 0) + return GIT_ENOTFOUND; + + /* We can safely provide a git_str with no allocation (asize == 0) to + * a consumer. This allows them to treat this as a regular `git_str`, + * but their call to `git_str_dispose` will not attempt to free it. + */ + git_str_attach_notowned( + out, attr_session->sysdir.ptr, attr_session->sysdir.size); + return 0; +} + +static int attr_setup( + git_repository *repo, + git_attr_session *attr_session, + git_attr_options *opts) +{ + git_str system = GIT_STR_INIT, info = GIT_STR_INIT; + git_attr_file_source index_source = { GIT_ATTR_FILE_SOURCE_INDEX, NULL, GIT_ATTR_FILE, NULL }; + git_attr_file_source head_source = { GIT_ATTR_FILE_SOURCE_HEAD, NULL, GIT_ATTR_FILE, NULL }; + git_attr_file_source commit_source = { GIT_ATTR_FILE_SOURCE_COMMIT, NULL, GIT_ATTR_FILE, NULL }; + git_index *idx = NULL; + const char *workdir; + int error = 0; + + if (attr_session && attr_session->init_setup) + return 0; + + if ((error = git_attr_cache__init(repo)) < 0) + return error; + + /* + * Preload attribute files that could contain macros so the + * definitions will be available for later file parsing. + */ + + if ((error = system_attr_file(&system, attr_session)) < 0 || + (error = preload_attr_file(repo, attr_session, NULL, system.ptr)) < 0) { + if (error != GIT_ENOTFOUND) + goto out; + + error = 0; + } + + if ((error = preload_attr_file(repo, attr_session, NULL, + git_repository_attr_cache(repo)->cfg_attr_file)) < 0) + goto out; + + if ((error = git_repository__item_path(&info, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || + (error = preload_attr_file(repo, attr_session, info.ptr, GIT_ATTR_FILE_INREPO)) < 0) { + if (error != GIT_ENOTFOUND) + goto out; + + error = 0; + } + + if ((workdir = git_repository_workdir(repo)) != NULL && + (error = preload_attr_file(repo, attr_session, workdir, GIT_ATTR_FILE)) < 0) + goto out; + + if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || + (error = preload_attr_source(repo, attr_session, &index_source)) < 0) + goto out; + + if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) && + (error = preload_attr_source(repo, attr_session, &head_source)) < 0) + goto out; + + if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_COMMIT) != 0)) { +#ifndef GIT_DEPRECATE_HARD + if (opts->commit_id) + commit_source.commit_id = opts->commit_id; + else +#endif + commit_source.commit_id = &opts->attr_commit_id; + + if ((error = preload_attr_source(repo, attr_session, &commit_source)) < 0) + goto out; + } + + if (attr_session) + attr_session->init_setup = 1; + +out: + git_str_dispose(&system); + git_str_dispose(&info); + + return error; +} + +int git_attr_add_macro( + git_repository *repo, + const char *name, + const char *values) +{ + int error; + git_attr_rule *macro = NULL; + git_pool *pool; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = git_attr_cache__init(repo)) < 0) + return error; + + macro = git__calloc(1, sizeof(git_attr_rule)); + GIT_ERROR_CHECK_ALLOC(macro); + + pool = &git_repository_attr_cache(repo)->pool; + + macro->match.pattern = git_pool_strdup(pool, name); + GIT_ERROR_CHECK_ALLOC(macro->match.pattern); + + macro->match.length = strlen(macro->match.pattern); + macro->match.flags = GIT_ATTR_FNMATCH_MACRO; + + error = git_attr_assignment__parse(repo, pool, ¯o->assigns, &values); + + if (!error) + error = git_attr_cache__insert_macro(repo, macro); + + if (error < 0) + git_attr_rule__free(macro); + + return error; +} + +typedef struct { + git_repository *repo; + git_attr_session *attr_session; + git_attr_options *opts; + const char *workdir; + git_index *index; + git_vector *files; +} attr_walk_up_info; + +static int attr_decide_sources( + uint32_t flags, + bool has_wd, + bool has_index, + git_attr_file_source_t *srcs) +{ + int count = 0; + + switch (flags & 0x03) { + case GIT_ATTR_CHECK_FILE_THEN_INDEX: + if (has_wd) + srcs[count++] = GIT_ATTR_FILE_SOURCE_FILE; + if (has_index) + srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX; + break; + case GIT_ATTR_CHECK_INDEX_THEN_FILE: + if (has_index) + srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX; + if (has_wd) + srcs[count++] = GIT_ATTR_FILE_SOURCE_FILE; + break; + case GIT_ATTR_CHECK_INDEX_ONLY: + if (has_index) + srcs[count++] = GIT_ATTR_FILE_SOURCE_INDEX; + break; + } + + if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) + srcs[count++] = GIT_ATTR_FILE_SOURCE_HEAD; + + if ((flags & GIT_ATTR_CHECK_INCLUDE_COMMIT) != 0) + srcs[count++] = GIT_ATTR_FILE_SOURCE_COMMIT; + + return count; +} + +static int push_attr_source( + git_repository *repo, + git_attr_session *attr_session, + git_vector *list, + git_attr_file_source *source, + bool allow_macros) +{ + int error = 0; + git_attr_file *file = NULL; + + error = git_attr_cache__get(&file, repo, attr_session, + source, + git_attr_file__parse_buffer, + allow_macros); + + if (error < 0) + return error; + + if (file != NULL) { + if ((error = git_vector_insert(list, file)) < 0) + git_attr_file__free(file); + } + + return error; +} + +GIT_INLINE(int) push_attr_file( + git_repository *repo, + git_attr_session *attr_session, + git_vector *list, + const char *base, + const char *filename) +{ + git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE, base, filename }; + return push_attr_source(repo, attr_session, list, &source, true); +} + +static int push_one_attr(void *ref, const char *path) +{ + attr_walk_up_info *info = (attr_walk_up_info *)ref; + git_attr_file_source_t src[GIT_ATTR_FILE_NUM_SOURCES]; + int error = 0, n_src, i; + bool allow_macros; + + n_src = attr_decide_sources(info->opts ? info->opts->flags : 0, + info->workdir != NULL, + info->index != NULL, + src); + + allow_macros = info->workdir ? !strcmp(info->workdir, path) : false; + + for (i = 0; !error && i < n_src; ++i) { + git_attr_file_source source = { src[i], path, GIT_ATTR_FILE }; + + if (src[i] == GIT_ATTR_FILE_SOURCE_COMMIT && info->opts) { +#ifndef GIT_DEPRECATE_HARD + if (info->opts->commit_id) + source.commit_id = info->opts->commit_id; + else +#endif + source.commit_id = &info->opts->attr_commit_id; + } + + error = push_attr_source(info->repo, info->attr_session, info->files, + &source, allow_macros); + } + + return error; +} + +static void release_attr_files(git_vector *files) +{ + size_t i; + git_attr_file *file; + + git_vector_foreach(files, i, file) { + git_attr_file__free(file); + files->contents[i] = NULL; + } + git_vector_free(files); +} + +static int collect_attr_files( + git_repository *repo, + git_attr_session *attr_session, + git_attr_options *opts, + const char *path, + git_vector *files) +{ + int error = 0; + git_str dir = GIT_STR_INIT, attrfile = GIT_STR_INIT; + const char *workdir = git_repository_workdir(repo); + attr_walk_up_info info = { NULL }; + + GIT_ASSERT(!git_fs_path_is_absolute(path)); + + if ((error = attr_setup(repo, attr_session, opts)) < 0) + return error; + + /* Resolve path in a non-bare repo */ + if (workdir != NULL) { + if (!(error = git_repository_workdir_path(&dir, repo, path))) + error = git_fs_path_find_dir(&dir); + } + else { + error = git_fs_path_dirname_r(&dir, path); + } + + if (error < 0) + goto cleanup; + + /* in precedence order highest to lowest: + * - $GIT_DIR/info/attributes + * - path components with .gitattributes + * - config core.attributesfile + * - $GIT_PREFIX/etc/gitattributes + */ + + if ((error = git_repository__item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || + (error = push_attr_file(repo, attr_session, files, attrfile.ptr, GIT_ATTR_FILE_INREPO)) < 0) { + if (error != GIT_ENOTFOUND) + goto cleanup; + } + + info.repo = repo; + info.attr_session = attr_session; + info.opts = opts; + info.workdir = workdir; + if (git_repository_index__weakptr(&info.index, repo) < 0) + git_error_clear(); /* no error even if there is no index */ + info.files = files; + + if (!strcmp(dir.ptr, ".")) + error = push_one_attr(&info, ""); + else + error = git_fs_path_walk_up(&dir, workdir, push_one_attr, &info); + + if (error < 0) + goto cleanup; + + if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) { + error = push_attr_file(repo, attr_session, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file); + if (error < 0) + goto cleanup; + } + + if (!opts || (opts->flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) { + error = system_attr_file(&dir, attr_session); + + if (!error) + error = push_attr_file(repo, attr_session, files, NULL, dir.ptr); + else if (error == GIT_ENOTFOUND) + error = 0; + } + + cleanup: + if (error < 0) + release_attr_files(files); + git_str_dispose(&attrfile); + git_str_dispose(&dir); + + return error; +} diff --git a/src/libgit2/attr.h b/src/libgit2/attr.h new file mode 100644 index 000000000..977565205 --- /dev/null +++ b/src/libgit2/attr.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_attr_h__ +#define INCLUDE_attr_h__ + +#include "common.h" + +#include "attr_file.h" +#include "attrcache.h" + +#endif diff --git a/src/libgit2/attr_file.c b/src/libgit2/attr_file.c new file mode 100644 index 000000000..0eb881a9b --- /dev/null +++ b/src/libgit2/attr_file.c @@ -0,0 +1,1027 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "attr_file.h" + +#include "repository.h" +#include "filebuf.h" +#include "attrcache.h" +#include "git2/blob.h" +#include "git2/tree.h" +#include "blob.h" +#include "index.h" +#include "wildmatch.h" +#include + +static void attr_file_free(git_attr_file *file) +{ + bool unlock = !git_mutex_lock(&file->lock); + git_attr_file__clear_rules(file, false); + git_pool_clear(&file->pool); + if (unlock) + git_mutex_unlock(&file->lock); + git_mutex_free(&file->lock); + + git__memzero(file, sizeof(*file)); + git__free(file); +} + +int git_attr_file__new( + git_attr_file **out, + git_attr_file_entry *entry, + git_attr_file_source *source) +{ + git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file)); + GIT_ERROR_CHECK_ALLOC(attrs); + + if (git_mutex_init(&attrs->lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to initialize lock"); + goto on_error; + } + + if (git_pool_init(&attrs->pool, 1) < 0) + goto on_error; + + GIT_REFCOUNT_INC(attrs); + attrs->entry = entry; + memcpy(&attrs->source, source, sizeof(git_attr_file_source)); + *out = attrs; + return 0; + +on_error: + git__free(attrs); + return -1; +} + +int git_attr_file__clear_rules(git_attr_file *file, bool need_lock) +{ + unsigned int i; + git_attr_rule *rule; + + if (need_lock && git_mutex_lock(&file->lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock attribute file"); + return -1; + } + + git_vector_foreach(&file->rules, i, rule) + git_attr_rule__free(rule); + git_vector_free(&file->rules); + + if (need_lock) + git_mutex_unlock(&file->lock); + + return 0; +} + +void git_attr_file__free(git_attr_file *file) +{ + if (!file) + return; + GIT_REFCOUNT_DEC(file, attr_file_free); +} + +static int attr_file_oid_from_index( + git_oid *oid, git_repository *repo, const char *path) +{ + int error; + git_index *idx; + size_t pos; + const git_index_entry *entry; + + if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || + (error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0) + return error; + + if (!(entry = git_index_get_byindex(idx, pos))) + return GIT_ENOTFOUND; + + *oid = entry->id; + return 0; +} + +int git_attr_file__load( + git_attr_file **out, + git_repository *repo, + git_attr_session *attr_session, + git_attr_file_entry *entry, + git_attr_file_source *source, + git_attr_file_parser parser, + bool allow_macros) +{ + int error = 0; + git_commit *commit = NULL; + git_tree *tree = NULL; + git_tree_entry *tree_entry = NULL; + git_blob *blob = NULL; + git_str content = GIT_STR_INIT; + const char *content_str; + git_attr_file *file; + struct stat st; + bool nonexistent = false; + int bom_offset; + git_str_bom_t bom; + git_oid id; + git_object_size_t blobsize; + + *out = NULL; + + switch (source->type) { + case GIT_ATTR_FILE_SOURCE_MEMORY: + /* in-memory attribute file doesn't need data */ + break; + case GIT_ATTR_FILE_SOURCE_INDEX: { + if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 || + (error = git_blob_lookup(&blob, repo, &id)) < 0) + return error; + + /* Do not assume that data straight from the ODB is NULL-terminated; + * copy the contents of a file to a buffer to work on */ + blobsize = git_blob_rawsize(blob); + + GIT_ERROR_CHECK_BLOBSIZE(blobsize); + git_str_put(&content, git_blob_rawcontent(blob), (size_t)blobsize); + break; + } + case GIT_ATTR_FILE_SOURCE_FILE: { + int fd = -1; + + /* For open or read errors, pretend that we got ENOTFOUND. */ + /* TODO: issue warning when warning API is available */ + + if (p_stat(entry->fullpath, &st) < 0 || + S_ISDIR(st.st_mode) || + (fd = git_futils_open_ro(entry->fullpath)) < 0 || + (error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0) + nonexistent = true; + + if (fd >= 0) + p_close(fd); + + break; + } + case GIT_ATTR_FILE_SOURCE_HEAD: + case GIT_ATTR_FILE_SOURCE_COMMIT: { + if (source->type == GIT_ATTR_FILE_SOURCE_COMMIT) { + if ((error = git_commit_lookup(&commit, repo, source->commit_id)) < 0 || + (error = git_commit_tree(&tree, commit)) < 0) + goto cleanup; + } else { + if ((error = git_repository_head_tree(&tree, repo)) < 0) + goto cleanup; + } + + if ((error = git_tree_entry_bypath(&tree_entry, tree, entry->path)) < 0) { + /* + * If the attributes file does not exist, we can + * cache an empty file for this commit to prevent + * needless future lookups. + */ + if (error == GIT_ENOTFOUND) { + error = 0; + break; + } + + goto cleanup; + } + + if ((error = git_blob_lookup(&blob, repo, git_tree_entry_id(tree_entry))) < 0) + goto cleanup; + + /* + * Do not assume that data straight from the ODB is NULL-terminated; + * copy the contents of a file to a buffer to work on. + */ + blobsize = git_blob_rawsize(blob); + + GIT_ERROR_CHECK_BLOBSIZE(blobsize); + if ((error = git_str_put(&content, + git_blob_rawcontent(blob), (size_t)blobsize)) < 0) + goto cleanup; + + break; + } + default: + git_error_set(GIT_ERROR_INVALID, "unknown file source %d", source->type); + return -1; + } + + if ((error = git_attr_file__new(&file, entry, source)) < 0) + goto cleanup; + + /* advance over a UTF8 BOM */ + content_str = git_str_cstr(&content); + bom_offset = git_str_detect_bom(&bom, &content); + + if (bom == GIT_STR_BOM_UTF8) + content_str += bom_offset; + + /* store the key of the attr_reader; don't bother with cache + * invalidation during the same attr reader session. + */ + if (attr_session) + file->session_key = attr_session->key; + + if (parser && (error = parser(repo, file, content_str, allow_macros)) < 0) { + git_attr_file__free(file); + goto cleanup; + } + + /* write cache breakers */ + if (nonexistent) + file->nonexistent = 1; + else if (source->type == GIT_ATTR_FILE_SOURCE_INDEX) + git_oid_cpy(&file->cache_data.oid, git_blob_id(blob)); + else if (source->type == GIT_ATTR_FILE_SOURCE_HEAD) + git_oid_cpy(&file->cache_data.oid, git_tree_id(tree)); + else if (source->type == GIT_ATTR_FILE_SOURCE_COMMIT) + git_oid_cpy(&file->cache_data.oid, git_tree_id(tree)); + else if (source->type == GIT_ATTR_FILE_SOURCE_FILE) + git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st); + /* else always cacheable */ + + *out = file; + +cleanup: + git_blob_free(blob); + git_tree_entry_free(tree_entry); + git_tree_free(tree); + git_commit_free(commit); + git_str_dispose(&content); + + return error; +} + +int git_attr_file__out_of_date( + git_repository *repo, + git_attr_session *attr_session, + git_attr_file *file, + git_attr_file_source *source) +{ + if (!file) + return 1; + + /* we are never out of date if we just created this data in the same + * attr_session; otherwise, nonexistent files must be invalidated + */ + if (attr_session && attr_session->key == file->session_key) + return 0; + else if (file->nonexistent) + return 1; + + switch (file->source.type) { + case GIT_ATTR_FILE_SOURCE_MEMORY: + return 0; + + case GIT_ATTR_FILE_SOURCE_FILE: + return git_futils_filestamp_check( + &file->cache_data.stamp, file->entry->fullpath); + + case GIT_ATTR_FILE_SOURCE_INDEX: { + int error; + git_oid id; + + if ((error = attr_file_oid_from_index( + &id, repo, file->entry->path)) < 0) + return error; + + return (git_oid__cmp(&file->cache_data.oid, &id) != 0); + } + + case GIT_ATTR_FILE_SOURCE_HEAD: { + git_tree *tree = NULL; + int error = git_repository_head_tree(&tree, repo); + + if (error < 0) + return error; + + error = (git_oid__cmp(&file->cache_data.oid, git_tree_id(tree)) != 0); + + git_tree_free(tree); + return error; + } + + case GIT_ATTR_FILE_SOURCE_COMMIT: { + git_commit *commit = NULL; + git_tree *tree = NULL; + int error; + + if ((error = git_commit_lookup(&commit, repo, source->commit_id)) < 0) + return error; + + error = git_commit_tree(&tree, commit); + git_commit_free(commit); + + if (error < 0) + return error; + + error = (git_oid__cmp(&file->cache_data.oid, git_tree_id(tree)) != 0); + + git_tree_free(tree); + return error; + } + + default: + git_error_set(GIT_ERROR_INVALID, "invalid file type %d", file->source.type); + return -1; + } +} + +static int sort_by_hash_and_name(const void *a_raw, const void *b_raw); +static void git_attr_rule__clear(git_attr_rule *rule); +static bool parse_optimized_patterns( + git_attr_fnmatch *spec, + git_pool *pool, + const char *pattern); + +int git_attr_file__parse_buffer( + git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros) +{ + const char *scan = data, *context = NULL; + git_attr_rule *rule = NULL; + int error = 0; + + /* If subdir file path, convert context for file paths */ + if (attrs->entry && git_fs_path_root(attrs->entry->path) < 0 && + !git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE)) + context = attrs->entry->path; + + if (git_mutex_lock(&attrs->lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock attribute file"); + return -1; + } + + while (!error && *scan) { + /* Allocate rule if needed, otherwise re-use previous rule */ + if (!rule) { + rule = git__calloc(1, sizeof(*rule)); + GIT_ERROR_CHECK_ALLOC(rule); + } else + git_attr_rule__clear(rule); + + rule->match.flags = GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO; + + /* Parse the next "pattern attr attr attr" line */ + if ((error = git_attr_fnmatch__parse(&rule->match, &attrs->pool, context, &scan)) < 0 || + (error = git_attr_assignment__parse(repo, &attrs->pool, &rule->assigns, &scan)) < 0) + { + if (error != GIT_ENOTFOUND) + goto out; + error = 0; + continue; + } + + if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) { + /* TODO: warning if macro found in file below repo root */ + if (!allow_macros) + continue; + if ((error = git_attr_cache__insert_macro(repo, rule)) < 0) + goto out; + } else if ((error = git_vector_insert(&attrs->rules, rule)) < 0) + goto out; + + rule = NULL; + } + +out: + git_mutex_unlock(&attrs->lock); + git_attr_rule__free(rule); + + return error; +} + +uint32_t git_attr_file__name_hash(const char *name) +{ + uint32_t h = 5381; + int c; + + GIT_ASSERT_ARG(name); + + while ((c = (int)*name++) != 0) + h = ((h << 5) + h) + c; + return h; +} + +int git_attr_file__lookup_one( + git_attr_file *file, + git_attr_path *path, + const char *attr, + const char **value) +{ + size_t i; + git_attr_name name; + git_attr_rule *rule; + + *value = NULL; + + name.name = attr; + name.name_hash = git_attr_file__name_hash(attr); + + git_attr_file__foreach_matching_rule(file, path, i, rule) { + size_t pos; + + if (!git_vector_bsearch(&pos, &rule->assigns, &name)) { + *value = ((git_attr_assignment *) + git_vector_get(&rule->assigns, pos))->value; + break; + } + } + + return 0; +} + +int git_attr_file__load_standalone(git_attr_file **out, const char *path) +{ + git_str content = GIT_STR_INIT; + git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE }; + git_attr_file *file = NULL; + int error; + + if ((error = git_futils_readbuffer(&content, path)) < 0) + goto out; + + /* + * Because the cache entry is allocated from the file's own pool, we + * don't have to free it - freeing file+pool will free cache entry, too. + */ + + if ((error = git_attr_file__new(&file, NULL, &source)) < 0 || + (error = git_attr_file__parse_buffer(NULL, file, content.ptr, true)) < 0 || + (error = git_attr_cache__alloc_file_entry(&file->entry, NULL, NULL, path, &file->pool)) < 0) + goto out; + + *out = file; +out: + if (error < 0) + git_attr_file__free(file); + git_str_dispose(&content); + + return error; +} + +bool git_attr_fnmatch__match( + git_attr_fnmatch *match, + git_attr_path *path) +{ + const char *relpath = path->path; + const char *filename; + int flags = 0; + + /* + * If the rule was generated in a subdirectory, we must only + * use it for paths inside that directory. We can thus return + * a non-match if the prefixes don't match. + */ + if (match->containing_dir) { + if (match->flags & GIT_ATTR_FNMATCH_ICASE) { + if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length)) + return 0; + } else { + if (git__prefixcmp(path->path, match->containing_dir)) + return 0; + } + + relpath += match->containing_dir_length; + } + + if (match->flags & GIT_ATTR_FNMATCH_ICASE) + flags |= WM_CASEFOLD; + + if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) { + filename = relpath; + flags |= WM_PATHNAME; + } else { + filename = path->basename; + } + + if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) { + bool samename; + + /* + * for attribute checks or checks at the root of this match's + * containing_dir (or root of the repository if no containing_dir), + * do not match. + */ + if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) || + path->basename == relpath) + return false; + + /* fail match if this is a file with same name as ignored folder */ + samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ? + !strcasecmp(match->pattern, relpath) : + !strcmp(match->pattern, relpath); + + if (samename) + return false; + + return (wildmatch(match->pattern, relpath, flags) == WM_MATCH); + } + + return (wildmatch(match->pattern, filename, flags) == WM_MATCH); +} + +bool git_attr_rule__match( + git_attr_rule *rule, + git_attr_path *path) +{ + bool matched = git_attr_fnmatch__match(&rule->match, path); + + if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) + matched = !matched; + + return matched; +} + +git_attr_assignment *git_attr_rule__lookup_assignment( + git_attr_rule *rule, const char *name) +{ + size_t pos; + git_attr_name key; + key.name = name; + key.name_hash = git_attr_file__name_hash(name); + + if (git_vector_bsearch(&pos, &rule->assigns, &key)) + return NULL; + + return git_vector_get(&rule->assigns, pos); +} + +int git_attr_path__init( + git_attr_path *info, + const char *path, + const char *base, + git_dir_flag dir_flag) +{ + ssize_t root; + + /* build full path as best we can */ + git_str_init(&info->full, 0); + + if (git_fs_path_join_unrooted(&info->full, path, base, &root) < 0) + return -1; + + info->path = info->full.ptr + root; + + /* remove trailing slashes */ + while (info->full.size > 0) { + if (info->full.ptr[info->full.size - 1] != '/') + break; + info->full.size--; + } + info->full.ptr[info->full.size] = '\0'; + + /* skip leading slashes in path */ + while (*info->path == '/') + info->path++; + + /* find trailing basename component */ + info->basename = strrchr(info->path, '/'); + if (info->basename) + info->basename++; + if (!info->basename || !*info->basename) + info->basename = info->path; + + switch (dir_flag) + { + case GIT_DIR_FLAG_FALSE: + info->is_dir = 0; + break; + + case GIT_DIR_FLAG_TRUE: + info->is_dir = 1; + break; + + case GIT_DIR_FLAG_UNKNOWN: + default: + info->is_dir = (int)git_fs_path_isdir(info->full.ptr); + break; + } + + return 0; +} + +void git_attr_path__free(git_attr_path *info) +{ + git_str_dispose(&info->full); + info->path = NULL; + info->basename = NULL; +} + +/* + * From gitattributes(5): + * + * Patterns have the following format: + * + * - A blank line matches no files, so it can serve as a separator for + * readability. + * + * - A line starting with # serves as a comment. + * + * - An optional prefix ! which negates the pattern; any matching file + * excluded by a previous pattern will become included again. If a negated + * pattern matches, this will override lower precedence patterns sources. + * + * - If the pattern ends with a slash, it is removed for the purpose of the + * following description, but it would only find a match with a directory. In + * other words, foo/ will match a directory foo and paths underneath it, but + * will not match a regular file or a symbolic link foo (this is consistent + * with the way how pathspec works in general in git). + * + * - If the pattern does not contain a slash /, git treats it as a shell glob + * pattern and checks for a match against the pathname without leading + * directories. + * + * - Otherwise, git treats the pattern as a shell glob suitable for consumption + * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will + * not match a / in the pathname. For example, "Documentation/\*.html" matches + * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading + * slash matches the beginning of the pathname; for example, "/\*.c" matches + * "cat-file.c" but not "mozilla-sha1/sha1.c". + */ + +/* + * Determine the length of trailing spaces. Escaped spaces do not count as + * trailing whitespace. + */ +static size_t trailing_space_length(const char *p, size_t len) +{ + size_t n, i; + for (n = len; n; n--) { + if (p[n-1] != ' ' && p[n-1] != '\t') + break; + + /* + * Count escape-characters before space. In case where it's an + * even number of escape characters, then the escape char itself + * is escaped and the whitespace is an unescaped whitespace. + * Otherwise, the last escape char is not escaped and the + * whitespace in an escaped whitespace. + */ + i = n; + while (i > 1 && p[i-2] == '\\') + i--; + if ((n - i) % 2) + break; + } + return len - n; +} + +static size_t unescape_spaces(char *str) +{ + char *scan, *pos = str; + bool escaped = false; + + if (!str) + return 0; + + for (scan = str; *scan; scan++) { + if (!escaped && *scan == '\\') { + escaped = true; + continue; + } + + /* Only insert the escape character for escaped non-spaces */ + if (escaped && !git__isspace(*scan)) + *pos++ = '\\'; + + *pos++ = *scan; + escaped = false; + } + + if (pos != scan) + *pos = '\0'; + + return (pos - str); +} + +/* + * This will return 0 if the spec was filled out, + * GIT_ENOTFOUND if the fnmatch does not require matching, or + * another error code there was an actual problem. + */ +int git_attr_fnmatch__parse( + git_attr_fnmatch *spec, + git_pool *pool, + const char *context, + const char **base) +{ + const char *pattern, *scan; + int slash_count, allow_space; + bool escaped; + + GIT_ASSERT_ARG(spec); + GIT_ASSERT_ARG(base && *base); + + if (parse_optimized_patterns(spec, pool, *base)) + return 0; + + spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING); + allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0); + + pattern = *base; + + while (!allow_space && git__isspace(*pattern)) + pattern++; + + if (!*pattern || *pattern == '#' || *pattern == '\n' || + (*pattern == '\r' && *(pattern + 1) == '\n')) { + *base = git__next_line(pattern); + return GIT_ENOTFOUND; + } + + if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) { + if (strncmp(pattern, "[attr]", 6) == 0) { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO; + pattern += 6; + } + /* else a character range like [a-e]* which is accepted */ + } + + if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; + pattern++; + } + + slash_count = 0; + escaped = false; + /* Scan until a non-escaped whitespace. */ + for (scan = pattern; *scan != '\0'; ++scan) { + char c = *scan; + + if (c == '\\' && !escaped) { + escaped = true; + continue; + } else if (git__isspace(c) && !escaped) { + if (!allow_space || (c != ' ' && c != '\t' && c != '\r')) + break; + } else if (c == '/') { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH; + slash_count++; + + if (slash_count == 1 && pattern == scan) + pattern++; + } else if (git__iswildcard(c) && !escaped) { + /* remember if we see an unescaped wildcard in pattern */ + spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD; + } + + escaped = false; + } + + *base = scan; + + if ((spec->length = scan - pattern) == 0) + return GIT_ENOTFOUND; + + /* + * Remove one trailing \r in case this is a CRLF delimited + * file, in the case of Icon\r\r\n, we still leave the first + * \r there to match against. + */ + if (pattern[spec->length - 1] == '\r') + if (--spec->length == 0) + return GIT_ENOTFOUND; + + /* Remove trailing spaces. */ + spec->length -= trailing_space_length(pattern, spec->length); + + if (spec->length == 0) + return GIT_ENOTFOUND; + + if (pattern[spec->length - 1] == '/') { + spec->length--; + spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY; + if (--slash_count <= 0) + spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH; + } + + if (context) { + char *slash = strrchr(context, '/'); + size_t len; + if (slash) { + /* include the slash for easier matching */ + len = slash - context + 1; + spec->containing_dir = git_pool_strndup(pool, context, len); + spec->containing_dir_length = len; + } + } + + spec->pattern = git_pool_strndup(pool, pattern, spec->length); + + if (!spec->pattern) { + *base = git__next_line(pattern); + return -1; + } else { + /* strip '\' that might have been used for internal whitespace */ + spec->length = unescape_spaces(spec->pattern); + } + + return 0; +} + +static bool parse_optimized_patterns( + git_attr_fnmatch *spec, + git_pool *pool, + const char *pattern) +{ + if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) { + spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL; + spec->pattern = git_pool_strndup(pool, pattern, 1); + spec->length = 1; + + return true; + } + + return false; +} + +static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) +{ + const git_attr_name *a = a_raw; + const git_attr_name *b = b_raw; + + if (b->name_hash < a->name_hash) + return 1; + else if (b->name_hash > a->name_hash) + return -1; + else + return strcmp(b->name, a->name); +} + +static void git_attr_assignment__free(git_attr_assignment *assign) +{ + /* name and value are stored in a git_pool associated with the + * git_attr_file, so they do not need to be freed here + */ + assign->name = NULL; + assign->value = NULL; + git__free(assign); +} + +static int merge_assignments(void **old_raw, void *new_raw) +{ + git_attr_assignment **old = (git_attr_assignment **)old_raw; + git_attr_assignment *new = (git_attr_assignment *)new_raw; + + GIT_REFCOUNT_DEC(*old, git_attr_assignment__free); + *old = new; + return GIT_EEXISTS; +} + +int git_attr_assignment__parse( + git_repository *repo, + git_pool *pool, + git_vector *assigns, + const char **base) +{ + int error; + const char *scan = *base; + git_attr_assignment *assign = NULL; + + GIT_ASSERT_ARG(assigns && !assigns->length); + + git_vector_set_cmp(assigns, sort_by_hash_and_name); + + while (*scan && *scan != '\n') { + const char *name_start, *value_start; + + /* skip leading blanks */ + while (git__isspace(*scan) && *scan != '\n') scan++; + + /* allocate assign if needed */ + if (!assign) { + assign = git__calloc(1, sizeof(git_attr_assignment)); + GIT_ERROR_CHECK_ALLOC(assign); + GIT_REFCOUNT_INC(assign); + } + + assign->name_hash = 5381; + assign->value = git_attr__true; + + /* look for magic name prefixes */ + if (*scan == '-') { + assign->value = git_attr__false; + scan++; + } else if (*scan == '!') { + assign->value = git_attr__unset; /* explicit unspecified state */ + scan++; + } else if (*scan == '#') /* comment rest of line */ + break; + + /* find the name */ + name_start = scan; + while (*scan && !git__isspace(*scan) && *scan != '=') { + assign->name_hash = + ((assign->name_hash << 5) + assign->name_hash) + *scan; + scan++; + } + if (scan == name_start) { + /* must have found lone prefix (" - ") or leading = ("=foo") + * or end of buffer -- advance until whitespace and continue + */ + while (*scan && !git__isspace(*scan)) scan++; + continue; + } + + /* allocate permanent storage for name */ + assign->name = git_pool_strndup(pool, name_start, scan - name_start); + GIT_ERROR_CHECK_ALLOC(assign->name); + + /* if there is an equals sign, find the value */ + if (*scan == '=') { + for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan); + + /* if we found a value, allocate permanent storage for it */ + if (scan > value_start) { + assign->value = git_pool_strndup(pool, value_start, scan - value_start); + GIT_ERROR_CHECK_ALLOC(assign->value); + } + } + + /* expand macros (if given a repo with a macro cache) */ + if (repo != NULL && assign->value == git_attr__true) { + git_attr_rule *macro = + git_attr_cache__lookup_macro(repo, assign->name); + + if (macro != NULL) { + unsigned int i; + git_attr_assignment *massign; + + git_vector_foreach(¯o->assigns, i, massign) { + GIT_REFCOUNT_INC(massign); + + error = git_vector_insert_sorted( + assigns, massign, &merge_assignments); + if (error < 0 && error != GIT_EEXISTS) { + git_attr_assignment__free(assign); + return error; + } + } + } + } + + /* insert allocated assign into vector */ + error = git_vector_insert_sorted(assigns, assign, &merge_assignments); + if (error < 0 && error != GIT_EEXISTS) + return error; + + /* clear assign since it is now "owned" by the vector */ + assign = NULL; + } + + if (assign != NULL) + git_attr_assignment__free(assign); + + *base = git__next_line(scan); + + return (assigns->length == 0) ? GIT_ENOTFOUND : 0; +} + +static void git_attr_rule__clear(git_attr_rule *rule) +{ + unsigned int i; + git_attr_assignment *assign; + + if (!rule) + return; + + if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) { + git_vector_foreach(&rule->assigns, i, assign) + GIT_REFCOUNT_DEC(assign, git_attr_assignment__free); + git_vector_free(&rule->assigns); + } + + /* match.pattern is stored in a git_pool, so no need to free */ + rule->match.pattern = NULL; + rule->match.length = 0; +} + +void git_attr_rule__free(git_attr_rule *rule) +{ + git_attr_rule__clear(rule); + git__free(rule); +} + +int git_attr_session__init(git_attr_session *session, git_repository *repo) +{ + GIT_ASSERT_ARG(repo); + + memset(session, 0, sizeof(*session)); + session->key = git_atomic32_inc(&repo->attr_session_key); + + return 0; +} + +void git_attr_session__free(git_attr_session *session) +{ + if (!session) + return; + + git_str_dispose(&session->sysdir); + git_str_dispose(&session->tmp); + + memset(session, 0, sizeof(git_attr_session)); +} diff --git a/src/libgit2/attr_file.h b/src/libgit2/attr_file.h new file mode 100644 index 000000000..08630d1a6 --- /dev/null +++ b/src/libgit2/attr_file.h @@ -0,0 +1,241 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_attr_file_h__ +#define INCLUDE_attr_file_h__ + +#include "common.h" + +#include "git2/oid.h" +#include "git2/attr.h" +#include "vector.h" +#include "pool.h" +#include "str.h" +#include "futils.h" + +#define GIT_ATTR_FILE ".gitattributes" +#define GIT_ATTR_FILE_INREPO "attributes" +#define GIT_ATTR_FILE_SYSTEM "gitattributes" +#define GIT_ATTR_FILE_XDG "attributes" + +#define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0) +#define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1) +#define GIT_ATTR_FNMATCH_FULLPATH (1U << 2) +#define GIT_ATTR_FNMATCH_MACRO (1U << 3) +#define GIT_ATTR_FNMATCH_IGNORE (1U << 4) +#define GIT_ATTR_FNMATCH_HASWILD (1U << 5) +#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6) +#define GIT_ATTR_FNMATCH_ICASE (1U << 7) +#define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8) +#define GIT_ATTR_FNMATCH_ALLOWNEG (1U << 9) +#define GIT_ATTR_FNMATCH_ALLOWMACRO (1U << 10) + +#define GIT_ATTR_FNMATCH__INCOMING \ + (GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO) + +typedef enum { + GIT_ATTR_FILE_SOURCE_MEMORY = 0, + GIT_ATTR_FILE_SOURCE_FILE = 1, + GIT_ATTR_FILE_SOURCE_INDEX = 2, + GIT_ATTR_FILE_SOURCE_HEAD = 3, + GIT_ATTR_FILE_SOURCE_COMMIT = 4, + + GIT_ATTR_FILE_NUM_SOURCES = 5 +} git_attr_file_source_t; + +typedef struct { + /* The source location for the attribute file. */ + git_attr_file_source_t type; + + /* + * The filename of the attribute file to read (relative to the + * given base path). + */ + const char *base; + const char *filename; + + /* + * The commit ID when the given source type is a commit (or NULL + * for the repository's HEAD commit.) + */ + git_oid *commit_id; +} git_attr_file_source; + +extern const char *git_attr__true; +extern const char *git_attr__false; +extern const char *git_attr__unset; + +typedef struct { + char *pattern; + size_t length; + char *containing_dir; + size_t containing_dir_length; + unsigned int flags; +} git_attr_fnmatch; + +typedef struct { + git_attr_fnmatch match; + git_vector assigns; /* vector of */ +} git_attr_rule; + +typedef struct { + git_refcount unused; + const char *name; + uint32_t name_hash; +} git_attr_name; + +typedef struct { + git_refcount rc; /* for macros */ + char *name; + uint32_t name_hash; + const char *value; +} git_attr_assignment; + +typedef struct git_attr_file_entry git_attr_file_entry; + +typedef struct { + git_refcount rc; + git_mutex lock; + git_attr_file_entry *entry; + git_attr_file_source source; + git_vector rules; /* vector of or */ + git_pool pool; + unsigned int nonexistent:1; + int session_key; + union { + git_oid oid; + git_futils_filestamp stamp; + } cache_data; +} git_attr_file; + +struct git_attr_file_entry { + git_attr_file *file[GIT_ATTR_FILE_NUM_SOURCES]; + const char *path; /* points into fullpath */ + char fullpath[GIT_FLEX_ARRAY]; +}; + +typedef struct { + git_str full; + char *path; + char *basename; + int is_dir; +} git_attr_path; + +/* A git_attr_session can provide an "instance" of reading, to prevent cache + * invalidation during a single operation instance (like checkout). + */ + +typedef struct { + int key; + unsigned int init_setup:1, + init_sysdir:1; + git_str sysdir; + git_str tmp; +} git_attr_session; + +extern int git_attr_session__init(git_attr_session *attr_session, git_repository *repo); +extern void git_attr_session__free(git_attr_session *session); + +extern int git_attr_get_many_with_session( + const char **values_out, + git_repository *repo, + git_attr_session *attr_session, + git_attr_options *opts, + const char *path, + size_t num_attr, + const char **names); + +typedef int (*git_attr_file_parser)( + git_repository *repo, + git_attr_file *file, + const char *data, + bool allow_macros); + +/* + * git_attr_file API + */ + +int git_attr_file__new( + git_attr_file **out, + git_attr_file_entry *entry, + git_attr_file_source *source); + +void git_attr_file__free(git_attr_file *file); + +int git_attr_file__load( + git_attr_file **out, + git_repository *repo, + git_attr_session *attr_session, + git_attr_file_entry *ce, + git_attr_file_source *source, + git_attr_file_parser parser, + bool allow_macros); + +int git_attr_file__load_standalone( + git_attr_file **out, const char *path); + +int git_attr_file__out_of_date( + git_repository *repo, git_attr_session *session, git_attr_file *file, git_attr_file_source *source); + +int git_attr_file__parse_buffer( + git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros); + +int git_attr_file__clear_rules( + git_attr_file *file, bool need_lock); + +int git_attr_file__lookup_one( + git_attr_file *file, + git_attr_path *path, + const char *attr, + const char **value); + +/* loop over rules in file from bottom to top */ +#define git_attr_file__foreach_matching_rule(file, path, iter, rule) \ + git_vector_rforeach(&(file)->rules, (iter), (rule)) \ + if (git_attr_rule__match((rule), (path))) + +uint32_t git_attr_file__name_hash(const char *name); + + +/* + * other utilities + */ + +extern int git_attr_fnmatch__parse( + git_attr_fnmatch *spec, + git_pool *pool, + const char *source, + const char **base); + +extern bool git_attr_fnmatch__match( + git_attr_fnmatch *rule, + git_attr_path *path); + +extern void git_attr_rule__free(git_attr_rule *rule); + +extern bool git_attr_rule__match( + git_attr_rule *rule, + git_attr_path *path); + +extern git_attr_assignment *git_attr_rule__lookup_assignment( + git_attr_rule *rule, const char *name); + +typedef enum { GIT_DIR_FLAG_TRUE = 1, GIT_DIR_FLAG_FALSE = 0, GIT_DIR_FLAG_UNKNOWN = -1 } git_dir_flag; + +extern int git_attr_path__init( + git_attr_path *out, + const char *path, + const char *base, + git_dir_flag is_dir); +extern void git_attr_path__free(git_attr_path *info); + +extern int git_attr_assignment__parse( + git_repository *repo, /* needed to expand macros */ + git_pool *pool, + git_vector *assigns, + const char **scan); + +#endif diff --git a/src/libgit2/attrcache.c b/src/libgit2/attrcache.c new file mode 100644 index 000000000..b16d95c3c --- /dev/null +++ b/src/libgit2/attrcache.c @@ -0,0 +1,478 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "attrcache.h" + +#include "repository.h" +#include "attr_file.h" +#include "config.h" +#include "sysdir.h" +#include "ignore.h" +#include "path.h" + +GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache) +{ + GIT_UNUSED(cache); /* avoid warning if threading is off */ + + if (git_mutex_lock(&cache->lock) < 0) { + git_error_set(GIT_ERROR_OS, "unable to get attr cache lock"); + return -1; + } + return 0; +} + +GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache) +{ + GIT_UNUSED(cache); /* avoid warning if threading is off */ + git_mutex_unlock(&cache->lock); +} + +GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry( + git_attr_cache *cache, const char *path) +{ + return git_strmap_get(cache->files, path); +} + +int git_attr_cache__alloc_file_entry( + git_attr_file_entry **out, + git_repository *repo, + const char *base, + const char *path, + git_pool *pool) +{ + git_str fullpath_str = GIT_STR_INIT; + size_t baselen = 0, pathlen = strlen(path); + size_t cachesize = sizeof(git_attr_file_entry) + pathlen + 1; + git_attr_file_entry *ce; + + if (base != NULL && git_fs_path_root(path) < 0) { + baselen = strlen(base); + cachesize += baselen; + + if (baselen && base[baselen - 1] != '/') + cachesize++; + } + + ce = git_pool_mallocz(pool, cachesize); + GIT_ERROR_CHECK_ALLOC(ce); + + if (baselen) { + memcpy(ce->fullpath, base, baselen); + + if (base[baselen - 1] != '/') + ce->fullpath[baselen++] = '/'; + } + memcpy(&ce->fullpath[baselen], path, pathlen); + + fullpath_str.ptr = ce->fullpath; + fullpath_str.size = pathlen + baselen; + + if (git_path_validate_str_length(repo, &fullpath_str) < 0) + return -1; + + ce->path = &ce->fullpath[baselen]; + *out = ce; + + return 0; +} + +/* call with attrcache locked */ +static int attr_cache_make_entry( + git_attr_file_entry **out, git_repository *repo, const char *path) +{ + git_attr_cache *cache = git_repository_attr_cache(repo); + git_attr_file_entry *entry = NULL; + int error; + + if ((error = git_attr_cache__alloc_file_entry(&entry, repo, + git_repository_workdir(repo), path, &cache->pool)) < 0) + return error; + + if ((error = git_strmap_set(cache->files, entry->path, entry)) < 0) + return error; + + *out = entry; + return error; +} + +/* insert entry or replace existing if we raced with another thread */ +static int attr_cache_upsert(git_attr_cache *cache, git_attr_file *file) +{ + git_attr_file_entry *entry; + git_attr_file *old; + + if (attr_cache_lock(cache) < 0) + return -1; + + entry = attr_cache_lookup_entry(cache, file->entry->path); + + GIT_REFCOUNT_OWN(file, entry); + GIT_REFCOUNT_INC(file); + + /* + * Replace the existing value if another thread has + * created it in the meantime. + */ + old = git_atomic_swap(entry->file[file->source.type], file); + + if (old) { + GIT_REFCOUNT_OWN(old, NULL); + git_attr_file__free(old); + } + + attr_cache_unlock(cache); + return 0; +} + +static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file) +{ + int error = 0; + git_attr_file_entry *entry; + git_attr_file *oldfile = NULL; + + if (!file) + return 0; + + if ((error = attr_cache_lock(cache)) < 0) + return error; + + if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL) + oldfile = git_atomic_compare_and_swap(&entry->file[file->source.type], file, NULL); + + attr_cache_unlock(cache); + + if (oldfile == file) { + GIT_REFCOUNT_OWN(file, NULL); + git_attr_file__free(file); + } + + return error; +} + +/* Look up cache entry and file. + * - If entry is not present, create it while the cache is locked. + * - If file is present, increment refcount before returning it, so the + * cache can be unlocked and it won't go away. + */ +static int attr_cache_lookup( + git_attr_file **out_file, + git_attr_file_entry **out_entry, + git_repository *repo, + git_attr_session *attr_session, + git_attr_file_source *source) +{ + int error = 0; + git_str path = GIT_STR_INIT; + const char *wd = git_repository_workdir(repo); + const char *filename; + git_attr_cache *cache = git_repository_attr_cache(repo); + git_attr_file_entry *entry = NULL; + git_attr_file *file = NULL; + + /* join base and path as needed */ + if (source->base != NULL && git_fs_path_root(source->filename) < 0) { + git_str *p = attr_session ? &attr_session->tmp : &path; + + if (git_str_joinpath(p, source->base, source->filename) < 0 || + git_path_validate_str_length(repo, p) < 0) + return -1; + + filename = p->ptr; + } else { + filename = source->filename; + } + + if (wd && !git__prefixcmp(filename, wd)) + filename += strlen(wd); + + /* check cache for existing entry */ + if ((error = attr_cache_lock(cache)) < 0) + goto cleanup; + + entry = attr_cache_lookup_entry(cache, filename); + + if (!entry) { + error = attr_cache_make_entry(&entry, repo, filename); + } else if (entry->file[source->type] != NULL) { + file = entry->file[source->type]; + GIT_REFCOUNT_INC(file); + } + + attr_cache_unlock(cache); + +cleanup: + *out_file = file; + *out_entry = entry; + + git_str_dispose(&path); + return error; +} + +int git_attr_cache__get( + git_attr_file **out, + git_repository *repo, + git_attr_session *attr_session, + git_attr_file_source *source, + git_attr_file_parser parser, + bool allow_macros) +{ + int error = 0; + git_attr_cache *cache = git_repository_attr_cache(repo); + git_attr_file_entry *entry = NULL; + git_attr_file *file = NULL, *updated = NULL; + + if ((error = attr_cache_lookup(&file, &entry, repo, attr_session, source)) < 0) + return error; + + /* load file if we don't have one or if existing one is out of date */ + if (!file || + (error = git_attr_file__out_of_date(repo, attr_session, file, source)) > 0) + error = git_attr_file__load(&updated, repo, attr_session, + entry, source, parser, + allow_macros); + + /* if we loaded the file, insert into and/or update cache */ + if (updated) { + if ((error = attr_cache_upsert(cache, updated)) < 0) { + git_attr_file__free(updated); + } else { + git_attr_file__free(file); /* offset incref from lookup */ + file = updated; + } + } + + /* if file could not be loaded */ + if (error < 0) { + /* remove existing entry */ + if (file) { + attr_cache_remove(cache, file); + git_attr_file__free(file); /* offset incref from lookup */ + file = NULL; + } + /* no error if file simply doesn't exist */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + } + + *out = file; + return error; +} + +bool git_attr_cache__is_cached( + git_repository *repo, + git_attr_file_source_t source_type, + const char *filename) +{ + git_attr_cache *cache = git_repository_attr_cache(repo); + git_attr_file_entry *entry; + git_strmap *files; + + if (!cache || !(files = cache->files)) + return false; + + if ((entry = git_strmap_get(files, filename)) == NULL) + return false; + + return entry && (entry->file[source_type] != NULL); +} + + +static int attr_cache__lookup_path( + char **out, git_config *cfg, const char *key, const char *fallback) +{ + git_str buf = GIT_STR_INIT; + int error; + git_config_entry *entry = NULL; + + *out = NULL; + + if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0) + return error; + + if (entry) { + const char *cfgval = entry->value; + + /* expand leading ~/ as needed */ + if (cfgval && cfgval[0] == '~' && cfgval[1] == '/') { + if (! (error = git_sysdir_expand_global_file(&buf, &cfgval[2]))) + *out = git_str_detach(&buf); + } else if (cfgval) { + *out = git__strdup(cfgval); + } + } + else if (!git_sysdir_find_xdg_file(&buf, fallback)) { + *out = git_str_detach(&buf); + } + + git_config_entry_free(entry); + git_str_dispose(&buf); + + return error; +} + +static void attr_cache__free(git_attr_cache *cache) +{ + bool unlock; + + if (!cache) + return; + + unlock = (attr_cache_lock(cache) == 0); + + if (cache->files != NULL) { + git_attr_file_entry *entry; + git_attr_file *file; + int i; + + git_strmap_foreach_value(cache->files, entry, { + for (i = 0; i < GIT_ATTR_FILE_NUM_SOURCES; ++i) { + if ((file = git_atomic_swap(entry->file[i], NULL)) != NULL) { + GIT_REFCOUNT_OWN(file, NULL); + git_attr_file__free(file); + } + } + }); + git_strmap_free(cache->files); + } + + if (cache->macros != NULL) { + git_attr_rule *rule; + + git_strmap_foreach_value(cache->macros, rule, { + git_attr_rule__free(rule); + }); + git_strmap_free(cache->macros); + } + + git_pool_clear(&cache->pool); + + git__free(cache->cfg_attr_file); + cache->cfg_attr_file = NULL; + + git__free(cache->cfg_excl_file); + cache->cfg_excl_file = NULL; + + if (unlock) + attr_cache_unlock(cache); + git_mutex_free(&cache->lock); + + git__free(cache); +} + +int git_attr_cache__init(git_repository *repo) +{ + int ret = 0; + git_attr_cache *cache = git_repository_attr_cache(repo); + git_config *cfg = NULL; + + if (cache) + return 0; + + cache = git__calloc(1, sizeof(git_attr_cache)); + GIT_ERROR_CHECK_ALLOC(cache); + + /* set up lock */ + if (git_mutex_init(&cache->lock) < 0) { + git_error_set(GIT_ERROR_OS, "unable to initialize lock for attr cache"); + git__free(cache); + return -1; + } + + if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0) + goto cancel; + + /* cache config settings for attributes and ignores */ + ret = attr_cache__lookup_path( + &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG); + if (ret < 0) + goto cancel; + + ret = attr_cache__lookup_path( + &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG); + if (ret < 0) + goto cancel; + + /* allocate hashtable for attribute and ignore file contents, + * hashtable for attribute macros, and string pool + */ + if ((ret = git_strmap_new(&cache->files)) < 0 || + (ret = git_strmap_new(&cache->macros)) < 0 || + (ret = git_pool_init(&cache->pool, 1)) < 0) + goto cancel; + + if (git_atomic_compare_and_swap(&repo->attrcache, NULL, cache) != NULL) + goto cancel; /* raced with another thread, free this but no error */ + + git_config_free(cfg); + + /* insert default macros */ + return git_attr_add_macro(repo, "binary", "-diff -merge -text -crlf"); + +cancel: + attr_cache__free(cache); + git_config_free(cfg); + return ret; +} + +int git_attr_cache_flush(git_repository *repo) +{ + git_attr_cache *cache; + + /* this could be done less expensively, but for now, we'll just free + * the entire attrcache and let the next use reinitialize it... + */ + if (repo && (cache = git_atomic_swap(repo->attrcache, NULL)) != NULL) + attr_cache__free(cache); + + return 0; +} + +int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro) +{ + git_attr_cache *cache = git_repository_attr_cache(repo); + git_attr_rule *preexisting; + bool locked = false; + int error = 0; + + /* + * Callers assume that if we return success, that the + * macro will have been adopted by the attributes cache. + * Thus, we have to free the macro here if it's not being + * added to the cache. + * + * TODO: generate warning log if (macro->assigns.length == 0) + */ + if (macro->assigns.length == 0) { + git_attr_rule__free(macro); + goto out; + } + + if ((error = attr_cache_lock(cache)) < 0) + goto out; + locked = true; + + if ((preexisting = git_strmap_get(cache->macros, macro->match.pattern)) != NULL) + git_attr_rule__free(preexisting); + + if ((error = git_strmap_set(cache->macros, macro->match.pattern, macro)) < 0) + goto out; + +out: + if (locked) + attr_cache_unlock(cache); + return error; +} + +git_attr_rule *git_attr_cache__lookup_macro( + git_repository *repo, const char *name) +{ + git_strmap *macros = git_repository_attr_cache(repo)->macros; + + return git_strmap_get(macros, name); +} diff --git a/src/libgit2/attrcache.h b/src/libgit2/attrcache.h new file mode 100644 index 000000000..b13e0e8f0 --- /dev/null +++ b/src/libgit2/attrcache.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_attrcache_h__ +#define INCLUDE_attrcache_h__ + +#include "common.h" + +#include "attr_file.h" +#include "strmap.h" + +#define GIT_ATTR_CONFIG "core.attributesfile" +#define GIT_IGNORE_CONFIG "core.excludesfile" + +typedef struct { + char *cfg_attr_file; /* cached value of core.attributesfile */ + char *cfg_excl_file; /* cached value of core.excludesfile */ + git_strmap *files; /* hash path to git_attr_cache_entry records */ + git_strmap *macros; /* hash name to vector */ + git_mutex lock; + git_pool pool; +} git_attr_cache; + +extern int git_attr_cache__init(git_repository *repo); + +/* get file - loading and reload as needed */ +extern int git_attr_cache__get( + git_attr_file **file, + git_repository *repo, + git_attr_session *attr_session, + git_attr_file_source *source, + git_attr_file_parser parser, + bool allow_macros); + +extern bool git_attr_cache__is_cached( + git_repository *repo, + git_attr_file_source_t source_type, + const char *filename); + +extern int git_attr_cache__alloc_file_entry( + git_attr_file_entry **out, + git_repository *repo, + const char *base, + const char *path, + git_pool *pool); + +extern int git_attr_cache__insert_macro( + git_repository *repo, git_attr_rule *macro); + +extern git_attr_rule *git_attr_cache__lookup_macro( + git_repository *repo, const char *name); + +#endif diff --git a/src/libgit2/bitvec.h b/src/libgit2/bitvec.h new file mode 100644 index 000000000..544832d95 --- /dev/null +++ b/src/libgit2/bitvec.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_bitvec_h__ +#define INCLUDE_bitvec_h__ + +#include "common.h" + +/* + * This is a silly little fixed length bit vector type that will store + * vectors of 64 bits or less directly in the structure and allocate + * memory for vectors longer than 64 bits. You can use the two versions + * transparently through the API and avoid heap allocation completely when + * using a short bit vector as a result. + */ +typedef struct { + size_t length; + union { + uint64_t *words; + uint64_t bits; + } u; +} git_bitvec; + +GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity) +{ + memset(bv, 0x0, sizeof(*bv)); + + if (capacity >= 64) { + bv->length = (capacity / 64) + 1; + bv->u.words = git__calloc(bv->length, sizeof(uint64_t)); + if (!bv->u.words) + return -1; + } + + return 0; +} + +#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64)) +#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits) + +GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on) +{ + uint64_t *word = GIT_BITVEC_WORD(bv, bit); + uint64_t mask = GIT_BITVEC_MASK(bit); + + if (on) + *word |= mask; + else + *word &= ~mask; +} + +GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit) +{ + uint64_t *word = GIT_BITVEC_WORD(bv, bit); + return (*word & GIT_BITVEC_MASK(bit)) != 0; +} + +GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv) +{ + if (!bv->length) + bv->u.bits = 0; + else + memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t)); +} + +GIT_INLINE(void) git_bitvec_free(git_bitvec *bv) +{ + if (bv->length) + git__free(bv->u.words); +} + +#endif diff --git a/src/libgit2/blame.c b/src/libgit2/blame.c new file mode 100644 index 000000000..a6ab43efd --- /dev/null +++ b/src/libgit2/blame.c @@ -0,0 +1,557 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "blame.h" + +#include "git2/commit.h" +#include "git2/revparse.h" +#include "git2/revwalk.h" +#include "git2/tree.h" +#include "git2/diff.h" +#include "git2/blob.h" +#include "git2/signature.h" +#include "git2/mailmap.h" +#include "util.h" +#include "repository.h" +#include "blame_git.h" + + +static int hunk_byfinalline_search_cmp(const void *key, const void *entry) +{ + git_blame_hunk *hunk = (git_blame_hunk*)entry; + + size_t lineno = *(size_t*)key; + size_t lines_in_hunk = hunk->lines_in_hunk; + size_t final_start_line_number = hunk->final_start_line_number; + + if (lineno < final_start_line_number) + return -1; + if (lineno >= final_start_line_number + lines_in_hunk) + return 1; + return 0; +} + +static int paths_cmp(const void *a, const void *b) { return git__strcmp((char*)a, (char*)b); } +static int hunk_cmp(const void *_a, const void *_b) +{ + git_blame_hunk *a = (git_blame_hunk*)_a, + *b = (git_blame_hunk*)_b; + + if (a->final_start_line_number > b->final_start_line_number) + return 1; + else if (a->final_start_line_number < b->final_start_line_number) + return -1; + else + return 0; +} + +static bool hunk_ends_at_or_before_line(git_blame_hunk *hunk, size_t line) +{ + return line >= (hunk->final_start_line_number + hunk->lines_in_hunk - 1); +} + +static bool hunk_starts_at_or_after_line(git_blame_hunk *hunk, size_t line) +{ + return line <= hunk->final_start_line_number; +} + +static git_blame_hunk *new_hunk( + size_t start, + size_t lines, + size_t orig_start, + const char *path) +{ + git_blame_hunk *hunk = git__calloc(1, sizeof(git_blame_hunk)); + if (!hunk) return NULL; + + hunk->lines_in_hunk = lines; + hunk->final_start_line_number = start; + hunk->orig_start_line_number = orig_start; + hunk->orig_path = path ? git__strdup(path) : NULL; + + return hunk; +} + +static void free_hunk(git_blame_hunk *hunk) +{ + git__free((void*)hunk->orig_path); + git_signature_free(hunk->final_signature); + git_signature_free(hunk->orig_signature); + git__free(hunk); +} + +static git_blame_hunk *dup_hunk(git_blame_hunk *hunk) +{ + git_blame_hunk *newhunk = new_hunk( + hunk->final_start_line_number, + hunk->lines_in_hunk, + hunk->orig_start_line_number, + hunk->orig_path); + + if (!newhunk) + return NULL; + + git_oid_cpy(&newhunk->orig_commit_id, &hunk->orig_commit_id); + git_oid_cpy(&newhunk->final_commit_id, &hunk->final_commit_id); + newhunk->boundary = hunk->boundary; + + if (git_signature_dup(&newhunk->final_signature, hunk->final_signature) < 0 || + git_signature_dup(&newhunk->orig_signature, hunk->orig_signature) < 0) { + free_hunk(newhunk); + return NULL; + } + + return newhunk; +} + +/* Starting with the hunk that includes start_line, shift all following hunks' + * final_start_line by shift_by lines */ +static void shift_hunks_by(git_vector *v, size_t start_line, int shift_by) +{ + size_t i; + + if (!git_vector_bsearch2(&i, v, hunk_byfinalline_search_cmp, &start_line)) { + for (; i < v->length; i++) { + git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i]; + hunk->final_start_line_number += shift_by; + } + } +} + +git_blame *git_blame__alloc( + git_repository *repo, + git_blame_options opts, + const char *path) +{ + git_blame *gbr = git__calloc(1, sizeof(git_blame)); + if (!gbr) + return NULL; + + gbr->repository = repo; + gbr->options = opts; + + if (git_vector_init(&gbr->hunks, 8, hunk_cmp) < 0 || + git_vector_init(&gbr->paths, 8, paths_cmp) < 0 || + (gbr->path = git__strdup(path)) == NULL || + git_vector_insert(&gbr->paths, git__strdup(path)) < 0) + { + git_blame_free(gbr); + return NULL; + } + + if (opts.flags & GIT_BLAME_USE_MAILMAP && + git_mailmap_from_repository(&gbr->mailmap, repo) < 0) { + git_blame_free(gbr); + return NULL; + } + + return gbr; +} + +void git_blame_free(git_blame *blame) +{ + size_t i; + git_blame_hunk *hunk; + + if (!blame) return; + + git_vector_foreach(&blame->hunks, i, hunk) + free_hunk(hunk); + git_vector_free(&blame->hunks); + + git_vector_free_deep(&blame->paths); + + git_array_clear(blame->line_index); + + git_mailmap_free(blame->mailmap); + + git__free(blame->path); + git_blob_free(blame->final_blob); + git__free(blame); +} + +uint32_t git_blame_get_hunk_count(git_blame *blame) +{ + GIT_ASSERT_ARG(blame); + return (uint32_t)blame->hunks.length; +} + +const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index) +{ + GIT_ASSERT_ARG_WITH_RETVAL(blame, NULL); + return (git_blame_hunk*)git_vector_get(&blame->hunks, index); +} + +const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, size_t lineno) +{ + size_t i, new_lineno = lineno; + + GIT_ASSERT_ARG_WITH_RETVAL(blame, NULL); + + if (!git_vector_bsearch2(&i, &blame->hunks, hunk_byfinalline_search_cmp, &new_lineno)) { + return git_blame_get_hunk_byindex(blame, (uint32_t)i); + } + + return NULL; +} + +static int normalize_options( + git_blame_options *out, + const git_blame_options *in, + git_repository *repo) +{ + git_blame_options dummy = GIT_BLAME_OPTIONS_INIT; + if (!in) in = &dummy; + + memcpy(out, in, sizeof(git_blame_options)); + + /* No newest_commit => HEAD */ + if (git_oid_is_zero(&out->newest_commit)) { + if (git_reference_name_to_id(&out->newest_commit, repo, "HEAD") < 0) { + return -1; + } + } + + /* min_line 0 really means 1 */ + if (!out->min_line) out->min_line = 1; + /* max_line 0 really means N, but we don't know N yet */ + + /* Fix up option implications */ + if (out->flags & GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES) + out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; + if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES) + out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; + if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES) + out->flags |= GIT_BLAME_TRACK_COPIES_SAME_FILE; + + return 0; +} + +static git_blame_hunk *split_hunk_in_vector( + git_vector *vec, + git_blame_hunk *hunk, + size_t rel_line, + bool return_new) +{ + size_t new_line_count; + git_blame_hunk *nh; + + /* Don't split if already at a boundary */ + if (rel_line <= 0 || + rel_line >= hunk->lines_in_hunk) + { + return hunk; + } + + new_line_count = hunk->lines_in_hunk - rel_line; + nh = new_hunk(hunk->final_start_line_number + rel_line, new_line_count, + hunk->orig_start_line_number + rel_line, hunk->orig_path); + + if (!nh) + return NULL; + + git_oid_cpy(&nh->final_commit_id, &hunk->final_commit_id); + git_oid_cpy(&nh->orig_commit_id, &hunk->orig_commit_id); + + /* Adjust hunk that was split */ + hunk->lines_in_hunk -= new_line_count; + git_vector_insert_sorted(vec, nh, NULL); + { + git_blame_hunk *ret = return_new ? nh : hunk; + return ret; + } +} + +/* + * Construct a list of char indices for where lines begin + * Adapted from core git: + * https://github.com/gitster/git/blob/be5c9fb9049ed470e7005f159bb923a5f4de1309/builtin/blame.c#L1760-L1789 + */ +static int index_blob_lines(git_blame *blame) +{ + const char *buf = blame->final_buf; + size_t len = blame->final_buf_size; + int num = 0, incomplete = 0, bol = 1; + size_t *i; + + if (len && buf[len-1] != '\n') + incomplete++; /* incomplete line at the end */ + while (len--) { + if (bol) { + i = git_array_alloc(blame->line_index); + GIT_ERROR_CHECK_ALLOC(i); + *i = buf - blame->final_buf; + bol = 0; + } + if (*buf++ == '\n') { + num++; + bol = 1; + } + } + i = git_array_alloc(blame->line_index); + GIT_ERROR_CHECK_ALLOC(i); + *i = buf - blame->final_buf; + blame->num_lines = num + incomplete; + return blame->num_lines; +} + +static git_blame_hunk *hunk_from_entry(git_blame__entry *e, git_blame *blame) +{ + git_blame_hunk *h = new_hunk( + e->lno+1, e->num_lines, e->s_lno+1, e->suspect->path); + + if (!h) + return NULL; + + git_oid_cpy(&h->final_commit_id, git_commit_id(e->suspect->commit)); + git_oid_cpy(&h->orig_commit_id, git_commit_id(e->suspect->commit)); + git_commit_author_with_mailmap( + &h->final_signature, e->suspect->commit, blame->mailmap); + git_signature_dup(&h->orig_signature, h->final_signature); + h->boundary = e->is_boundary ? 1 : 0; + return h; +} + +static int load_blob(git_blame *blame) +{ + int error; + + if (blame->final_blob) return 0; + + error = git_commit_lookup(&blame->final, blame->repository, &blame->options.newest_commit); + if (error < 0) + goto cleanup; + error = git_object_lookup_bypath((git_object**)&blame->final_blob, + (git_object*)blame->final, blame->path, GIT_OBJECT_BLOB); + +cleanup: + return error; +} + +static int blame_internal(git_blame *blame) +{ + int error; + git_blame__entry *ent = NULL; + git_blame__origin *o; + + if ((error = load_blob(blame)) < 0 || + (error = git_blame__get_origin(&o, blame, blame->final, blame->path)) < 0) + goto cleanup; + + if (git_blob_rawsize(blame->final_blob) > SIZE_MAX) { + git_error_set(GIT_ERROR_NOMEMORY, "blob is too large to blame"); + error = -1; + goto cleanup; + } + + blame->final_buf = git_blob_rawcontent(blame->final_blob); + blame->final_buf_size = (size_t)git_blob_rawsize(blame->final_blob); + + ent = git__calloc(1, sizeof(git_blame__entry)); + GIT_ERROR_CHECK_ALLOC(ent); + + ent->num_lines = index_blob_lines(blame); + ent->lno = blame->options.min_line - 1; + ent->num_lines = ent->num_lines - blame->options.min_line + 1; + if (blame->options.max_line > 0) + ent->num_lines = blame->options.max_line - blame->options.min_line + 1; + ent->s_lno = ent->lno; + ent->suspect = o; + + blame->ent = ent; + + error = git_blame__like_git(blame, blame->options.flags); + +cleanup: + for (ent = blame->ent; ent; ) { + git_blame__entry *e = ent->next; + git_blame_hunk *h = hunk_from_entry(ent, blame); + + git_vector_insert(&blame->hunks, h); + + git_blame__free_entry(ent); + ent = e; + } + + return error; +} + +/******************************************************************************* + * File blaming + ******************************************************************************/ + +int git_blame_file( + git_blame **out, + git_repository *repo, + const char *path, + git_blame_options *options) +{ + int error = -1; + git_blame_options normOptions = GIT_BLAME_OPTIONS_INIT; + git_blame *blame = NULL; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(path); + + if ((error = normalize_options(&normOptions, options, repo)) < 0) + goto on_error; + + blame = git_blame__alloc(repo, normOptions, path); + GIT_ERROR_CHECK_ALLOC(blame); + + if ((error = load_blob(blame)) < 0) + goto on_error; + + if ((error = blame_internal(blame)) < 0) + goto on_error; + + *out = blame; + return 0; + +on_error: + git_blame_free(blame); + return error; +} + +/******************************************************************************* + * Buffer blaming + *******************************************************************************/ + +static bool hunk_is_bufferblame(git_blame_hunk *hunk) +{ + return hunk && git_oid_is_zero(&hunk->final_commit_id); +} + +static int buffer_hunk_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + void *payload) +{ + git_blame *blame = (git_blame*)payload; + uint32_t wedge_line; + + GIT_UNUSED(delta); + + wedge_line = (hunk->old_lines == 0) ? hunk->new_start : hunk->old_start; + blame->current_diff_line = wedge_line; + + blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byline(blame, wedge_line); + if (!blame->current_hunk) { + /* Line added at the end of the file */ + blame->current_hunk = new_hunk(wedge_line, 0, wedge_line, blame->path); + GIT_ERROR_CHECK_ALLOC(blame->current_hunk); + + git_vector_insert(&blame->hunks, blame->current_hunk); + } else if (!hunk_starts_at_or_after_line(blame->current_hunk, wedge_line)){ + /* If this hunk doesn't start between existing hunks, split a hunk up so it does */ + blame->current_hunk = split_hunk_in_vector(&blame->hunks, blame->current_hunk, + wedge_line - blame->current_hunk->orig_start_line_number, true); + GIT_ERROR_CHECK_ALLOC(blame->current_hunk); + } + + return 0; +} + +static int ptrs_equal_cmp(const void *a, const void *b) { return ab ? 1 : 0; } +static int buffer_line_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + git_blame *blame = (git_blame*)payload; + + GIT_UNUSED(delta); + GIT_UNUSED(hunk); + GIT_UNUSED(line); + + if (line->origin == GIT_DIFF_LINE_ADDITION) { + if (hunk_is_bufferblame(blame->current_hunk) && + hunk_ends_at_or_before_line(blame->current_hunk, blame->current_diff_line)) { + /* Append to the current buffer-blame hunk */ + blame->current_hunk->lines_in_hunk++; + shift_hunks_by(&blame->hunks, blame->current_diff_line+1, 1); + } else { + /* Create a new buffer-blame hunk with this line */ + shift_hunks_by(&blame->hunks, blame->current_diff_line, 1); + blame->current_hunk = new_hunk(blame->current_diff_line, 1, 0, blame->path); + GIT_ERROR_CHECK_ALLOC(blame->current_hunk); + + git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL); + } + blame->current_diff_line++; + } + + if (line->origin == GIT_DIFF_LINE_DELETION) { + /* Trim the line from the current hunk; remove it if it's now empty */ + size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk+1; + + if (--(blame->current_hunk->lines_in_hunk) == 0) { + size_t i; + shift_base--; + if (!git_vector_search2(&i, &blame->hunks, ptrs_equal_cmp, blame->current_hunk)) { + git_vector_remove(&blame->hunks, i); + free_hunk(blame->current_hunk); + blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, (uint32_t)i); + } + } + shift_hunks_by(&blame->hunks, shift_base, -1); + } + return 0; +} + +int git_blame_buffer( + git_blame **out, + git_blame *reference, + const char *buffer, + size_t buffer_len) +{ + git_blame *blame; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + size_t i; + git_blame_hunk *hunk; + + diffopts.context_lines = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(reference); + GIT_ASSERT_ARG(buffer && buffer_len); + + blame = git_blame__alloc(reference->repository, reference->options, reference->path); + GIT_ERROR_CHECK_ALLOC(blame); + + /* Duplicate all of the hunk structures in the reference blame */ + git_vector_foreach(&reference->hunks, i, hunk) { + git_blame_hunk *h = dup_hunk(hunk); + GIT_ERROR_CHECK_ALLOC(h); + + git_vector_insert(&blame->hunks, h); + } + + /* Diff to the reference blob */ + git_diff_blob_to_buffer(reference->final_blob, blame->path, + buffer, buffer_len, blame->path, &diffopts, + NULL, NULL, buffer_hunk_cb, buffer_line_cb, blame); + + *out = blame; + return 0; +} + +int git_blame_options_init(git_blame_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_blame_options, GIT_BLAME_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_blame_init_options(git_blame_options *opts, unsigned int version) +{ + return git_blame_options_init(opts, version); +} +#endif diff --git a/src/libgit2/blame.h b/src/libgit2/blame.h new file mode 100644 index 000000000..4141e2bb5 --- /dev/null +++ b/src/libgit2/blame.h @@ -0,0 +1,95 @@ +#ifndef INCLUDE_blame_h__ +#define INCLUDE_blame_h__ + +#include "common.h" + +#include "git2/blame.h" +#include "vector.h" +#include "diff.h" +#include "array.h" +#include "git2/oid.h" + +/* + * One blob in a commit that is being suspected + */ +typedef struct git_blame__origin { + int refcnt; + struct git_blame__origin *previous; + git_commit *commit; + git_blob *blob; + char path[GIT_FLEX_ARRAY]; +} git_blame__origin; + +/* + * Each group of lines is described by a git_blame__entry; it can be split + * as we pass blame to the parents. They form a linked list in the + * scoreboard structure, sorted by the target line number. + */ +typedef struct git_blame__entry { + struct git_blame__entry *prev; + struct git_blame__entry *next; + + /* the first line of this group in the final image; + * internally all line numbers are 0 based. + */ + size_t lno; + + /* how many lines this group has */ + size_t num_lines; + + /* the commit that introduced this group into the final image */ + git_blame__origin *suspect; + + /* true if the suspect is truly guilty; false while we have not + * checked if the group came from one of its parents. + */ + bool guilty; + + /* true if the entry has been scanned for copies in the current parent + */ + bool scanned; + + /* the line number of the first line of this group in the + * suspect's file; internally all line numbers are 0 based. + */ + size_t s_lno; + + /* how significant this entry is -- cached to avoid + * scanning the lines over and over. + */ + unsigned score; + + /* Whether this entry has been tracked to a boundary commit. + */ + bool is_boundary; +} git_blame__entry; + +struct git_blame { + char *path; + git_repository *repository; + git_mailmap *mailmap; + git_blame_options options; + + git_vector hunks; + git_vector paths; + + git_blob *final_blob; + git_array_t(size_t) line_index; + + size_t current_diff_line; + git_blame_hunk *current_hunk; + + /* Scoreboard fields */ + git_commit *final; + git_blame__entry *ent; + int num_lines; + const char *final_buf; + size_t final_buf_size; +}; + +git_blame *git_blame__alloc( + git_repository *repo, + git_blame_options opts, + const char *path); + +#endif diff --git a/src/libgit2/blame_git.c b/src/libgit2/blame_git.c new file mode 100644 index 000000000..2504b338a --- /dev/null +++ b/src/libgit2/blame_git.c @@ -0,0 +1,685 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "blame_git.h" + +#include "commit.h" +#include "blob.h" +#include "xdiff/xinclude.h" +#include "diff_xdiff.h" + +/* + * Origin is refcounted and usually we keep the blob contents to be + * reused. + */ +static git_blame__origin *origin_incref(git_blame__origin *o) +{ + if (o) + o->refcnt++; + return o; +} + +static void origin_decref(git_blame__origin *o) +{ + if (o && --o->refcnt <= 0) { + if (o->previous) + origin_decref(o->previous); + git_blob_free(o->blob); + git_commit_free(o->commit); + git__free(o); + } +} + +/* Given a commit and a path in it, create a new origin structure. */ +static int make_origin(git_blame__origin **out, git_commit *commit, const char *path) +{ + git_blame__origin *o; + git_object *blob; + size_t path_len = strlen(path), alloc_len; + int error = 0; + + if ((error = git_object_lookup_bypath(&blob, (git_object*)commit, + path, GIT_OBJECT_BLOB)) < 0) + return error; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*o), path_len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); + o = git__calloc(1, alloc_len); + GIT_ERROR_CHECK_ALLOC(o); + + o->commit = commit; + o->blob = (git_blob *) blob; + o->refcnt = 1; + strcpy(o->path, path); + + *out = o; + + return 0; +} + +/* Locate an existing origin or create a new one. */ +int git_blame__get_origin( + git_blame__origin **out, + git_blame *blame, + git_commit *commit, + const char *path) +{ + git_blame__entry *e; + + for (e = blame->ent; e; e = e->next) { + if (e->suspect->commit == commit && !strcmp(e->suspect->path, path)) { + *out = origin_incref(e->suspect); + } + } + return make_origin(out, commit, path); +} + +typedef struct blame_chunk_cb_data { + git_blame *blame; + git_blame__origin *target; + git_blame__origin *parent; + long tlno; + long plno; +}blame_chunk_cb_data; + +static bool same_suspect(git_blame__origin *a, git_blame__origin *b) +{ + if (a == b) + return true; + if (git_oid_cmp(git_commit_id(a->commit), git_commit_id(b->commit))) + return false; + return 0 == strcmp(a->path, b->path); +} + +/* find the line number of the last line the target is suspected for */ +static bool find_last_in_target(size_t *out, git_blame *blame, git_blame__origin *target) +{ + git_blame__entry *e; + size_t last_in_target = 0; + bool found = false; + + *out = 0; + + for (e=blame->ent; e; e=e->next) { + if (e->guilty || !same_suspect(e->suspect, target)) + continue; + if (last_in_target < e->s_lno + e->num_lines) { + found = true; + last_in_target = e->s_lno + e->num_lines; + } + } + + *out = last_in_target; + return found; +} + +/* + * It is known that lines between tlno to same came from parent, and e + * has an overlap with that range. it also is known that parent's + * line plno corresponds to e's line tlno. + * + * <---- e -----> + * <------> (entirely within) + * <------------> (extends past) + * <------------> (starts before) + * <------------------> (entirely encloses) + * + * Split e into potentially three parts; before this chunk, the chunk + * to be blamed for the parent, and after that portion. + */ +static void split_overlap(git_blame__entry *split, git_blame__entry *e, + size_t tlno, size_t plno, size_t same, git_blame__origin *parent) +{ + size_t chunk_end_lno; + + if (e->s_lno < tlno) { + /* there is a pre-chunk part not blamed on the parent */ + split[0].suspect = origin_incref(e->suspect); + split[0].lno = e->lno; + split[0].s_lno = e->s_lno; + split[0].num_lines = tlno - e->s_lno; + split[1].lno = e->lno + tlno - e->s_lno; + split[1].s_lno = plno; + } else { + split[1].lno = e->lno; + split[1].s_lno = plno + (e->s_lno - tlno); + } + + if (same < e->s_lno + e->num_lines) { + /* there is a post-chunk part not blamed on parent */ + split[2].suspect = origin_incref(e->suspect); + split[2].lno = e->lno + (same - e->s_lno); + split[2].s_lno = e->s_lno + (same - e->s_lno); + split[2].num_lines = e->s_lno + e->num_lines - same; + chunk_end_lno = split[2].lno; + } else { + chunk_end_lno = e->lno + e->num_lines; + } + split[1].num_lines = chunk_end_lno - split[1].lno; + + /* + * if it turns out there is nothing to blame the parent for, forget about + * the splitting. !split[1].suspect signals this. + */ + if (split[1].num_lines < 1) + return; + split[1].suspect = origin_incref(parent); +} + +/* + * Link in a new blame entry to the scoreboard. Entries that cover the same + * line range have been removed from the scoreboard previously. + */ +static void add_blame_entry(git_blame *blame, git_blame__entry *e) +{ + git_blame__entry *ent, *prev = NULL; + + origin_incref(e->suspect); + + for (ent = blame->ent; ent && ent->lno < e->lno; ent = ent->next) + prev = ent; + + /* prev, if not NULL, is the last one that is below e */ + e->prev = prev; + if (prev) { + e->next = prev->next; + prev->next = e; + } else { + e->next = blame->ent; + blame->ent = e; + } + if (e->next) + e->next->prev = e; +} + +/* + * src typically is on-stack; we want to copy the information in it to + * a malloced blame_entry that is already on the linked list of the scoreboard. + * The origin of dst loses a refcnt while the origin of src gains one. + */ +static void dup_entry(git_blame__entry *dst, git_blame__entry *src) +{ + git_blame__entry *p, *n; + + p = dst->prev; + n = dst->next; + origin_incref(src->suspect); + origin_decref(dst->suspect); + memcpy(dst, src, sizeof(*src)); + dst->prev = p; + dst->next = n; + dst->score = 0; +} + +/* + * split_overlap() divided an existing blame e into up to three parts in split. + * Adjust the linked list of blames in the scoreboard to reflect the split. + */ +static int split_blame(git_blame *blame, git_blame__entry *split, git_blame__entry *e) +{ + git_blame__entry *new_entry; + + if (split[0].suspect && split[2].suspect) { + /* The first part (reuse storage for the existing entry e */ + dup_entry(e, &split[0]); + + /* The last part -- me */ + new_entry = git__malloc(sizeof(*new_entry)); + GIT_ERROR_CHECK_ALLOC(new_entry); + memcpy(new_entry, &(split[2]), sizeof(git_blame__entry)); + add_blame_entry(blame, new_entry); + + /* ... and the middle part -- parent */ + new_entry = git__malloc(sizeof(*new_entry)); + GIT_ERROR_CHECK_ALLOC(new_entry); + memcpy(new_entry, &(split[1]), sizeof(git_blame__entry)); + add_blame_entry(blame, new_entry); + } else if (!split[0].suspect && !split[2].suspect) { + /* + * The parent covers the entire area; reuse storage for e and replace it + * with the parent + */ + dup_entry(e, &split[1]); + } else if (split[0].suspect) { + /* me and then parent */ + dup_entry(e, &split[0]); + new_entry = git__malloc(sizeof(*new_entry)); + GIT_ERROR_CHECK_ALLOC(new_entry); + memcpy(new_entry, &(split[1]), sizeof(git_blame__entry)); + add_blame_entry(blame, new_entry); + } else { + /* parent and then me */ + dup_entry(e, &split[1]); + new_entry = git__malloc(sizeof(*new_entry)); + GIT_ERROR_CHECK_ALLOC(new_entry); + memcpy(new_entry, &(split[2]), sizeof(git_blame__entry)); + add_blame_entry(blame, new_entry); + } + + return 0; +} + +/* + * After splitting the blame, the origins used by the on-stack blame_entry + * should lose one refcnt each. + */ +static void decref_split(git_blame__entry *split) +{ + int i; + for (i=0; i<3; i++) + origin_decref(split[i].suspect); +} + +/* + * Helper for blame_chunk(). blame_entry e is known to overlap with the patch + * hunk; split it and pass blame to the parent. + */ +static int blame_overlap( + git_blame *blame, + git_blame__entry *e, + size_t tlno, + size_t plno, + size_t same, + git_blame__origin *parent) +{ + git_blame__entry split[3] = {{0}}; + + split_overlap(split, e, tlno, plno, same, parent); + if (split[1].suspect) + if (split_blame(blame, split, e) < 0) + return -1; + decref_split(split); + + return 0; +} + +/* + * Process one hunk from the patch between the current suspect for blame_entry + * e and its parent. Find and split the overlap, and pass blame to the + * overlapping part to the parent. + */ +static int blame_chunk( + git_blame *blame, + size_t tlno, + size_t plno, + size_t same, + git_blame__origin *target, + git_blame__origin *parent) +{ + git_blame__entry *e; + + for (e = blame->ent; e; e = e->next) { + if (e->guilty || !same_suspect(e->suspect, target)) + continue; + if (same <= e->s_lno) + continue; + if (tlno < e->s_lno + e->num_lines) { + if (blame_overlap(blame, e, tlno, plno, same, parent) < 0) + return -1; + } + } + + return 0; +} + +static int my_emit( + long start_a, long count_a, + long start_b, long count_b, + void *cb_data) +{ + blame_chunk_cb_data *d = (blame_chunk_cb_data *)cb_data; + + if (blame_chunk(d->blame, d->tlno, d->plno, start_b, d->target, d->parent) < 0) + return -1; + d->plno = start_a + count_a; + d->tlno = start_b + count_b; + + return 0; +} + +static void trim_common_tail(mmfile_t *a, mmfile_t *b, long ctx) +{ + const int blk = 1024; + long trimmed = 0, recovered = 0; + char *ap = a->ptr + a->size; + char *bp = b->ptr + b->size; + long smaller = (long)((a->size < b->size) ? a->size : b->size); + + if (ctx) + return; + + while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) { + trimmed += blk; + ap -= blk; + bp -= blk; + } + + while (recovered < trimmed) + if (ap[recovered++] == '\n') + break; + a->size -= trimmed - recovered; + b->size -= trimmed - recovered; +} + +static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data, git_blame_options *options) +{ + xdemitconf_t xecfg = {0}; + xdemitcb_t ecb = {0}; + xpparam_t xpp = {0}; + + if (options->flags & GIT_BLAME_IGNORE_WHITESPACE) + xpp.flags |= XDF_IGNORE_WHITESPACE; + + xecfg.hunk_func = my_emit; + ecb.priv = cb_data; + + trim_common_tail(&file_a, &file_b, 0); + + if (file_a.size > GIT_XDIFF_MAX_SIZE || + file_b.size > GIT_XDIFF_MAX_SIZE) { + git_error_set(GIT_ERROR_INVALID, "file too large to blame"); + return -1; + } + + return xdl_diff(&file_a, &file_b, &xpp, &xecfg, &ecb); +} + +static void fill_origin_blob(git_blame__origin *o, mmfile_t *file) +{ + memset(file, 0, sizeof(*file)); + if (o->blob) { + file->ptr = (char*)git_blob_rawcontent(o->blob); + file->size = (long)git_blob_rawsize(o->blob); + } +} + +static int pass_blame_to_parent( + git_blame *blame, + git_blame__origin *target, + git_blame__origin *parent) +{ + size_t last_in_target; + mmfile_t file_p, file_o; + blame_chunk_cb_data d = { blame, target, parent, 0, 0 }; + + if (!find_last_in_target(&last_in_target, blame, target)) + return 1; /* nothing remains for this target */ + + fill_origin_blob(parent, &file_p); + fill_origin_blob(target, &file_o); + + if (diff_hunks(file_p, file_o, &d, &blame->options) < 0) + return -1; + + /* The reset (i.e. anything after tlno) are the same as the parent */ + if (blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent) < 0) + return -1; + + return 0; +} + +static int paths_on_dup(void **old, void *new) +{ + GIT_UNUSED(old); + git__free(new); + return -1; +} + +static git_blame__origin *find_origin( + git_blame *blame, + git_commit *parent, + git_blame__origin *origin) +{ + git_blame__origin *porigin = NULL; + git_diff *difflist = NULL; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_tree *otree=NULL, *ptree=NULL; + + /* Get the trees from this commit and its parent */ + if (0 != git_commit_tree(&otree, origin->commit) || + 0 != git_commit_tree(&ptree, parent)) + goto cleanup; + + /* Configure the diff */ + diffopts.context_lines = 0; + diffopts.flags = GIT_DIFF_SKIP_BINARY_CHECK; + + /* Check to see if files we're interested have changed */ + diffopts.pathspec.count = blame->paths.length; + diffopts.pathspec.strings = (char**)blame->paths.contents; + if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts)) + goto cleanup; + + if (!git_diff_num_deltas(difflist)) { + /* No changes; copy data */ + git_blame__get_origin(&porigin, blame, parent, origin->path); + } else { + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + int i; + + /* Generate a full diff between the two trees */ + git_diff_free(difflist); + diffopts.pathspec.count = 0; + if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts)) + goto cleanup; + + /* Let diff find renames */ + findopts.flags = GIT_DIFF_FIND_RENAMES; + if (0 != git_diff_find_similar(difflist, &findopts)) + goto cleanup; + + /* Find one that matches */ + for (i=0; i<(int)git_diff_num_deltas(difflist); i++) { + const git_diff_delta *delta = git_diff_get_delta(difflist, i); + + if (!git_vector_bsearch(NULL, &blame->paths, delta->new_file.path)) + { + git_vector_insert_sorted(&blame->paths, (void*)git__strdup(delta->old_file.path), + paths_on_dup); + make_origin(&porigin, parent, delta->old_file.path); + } + } + } + +cleanup: + git_diff_free(difflist); + git_tree_free(otree); + git_tree_free(ptree); + return porigin; +} + +/* + * The blobs of origin and porigin exactly match, so everything origin is + * suspected for can be blamed on the parent. + */ +static int pass_whole_blame(git_blame *blame, + git_blame__origin *origin, git_blame__origin *porigin) +{ + git_blame__entry *e; + + if (!porigin->blob && + git_object_lookup((git_object**)&porigin->blob, blame->repository, + git_blob_id(origin->blob), GIT_OBJECT_BLOB) < 0) + return -1; + for (e=blame->ent; e; e=e->next) { + if (!same_suspect(e->suspect, origin)) + continue; + origin_incref(porigin); + origin_decref(e->suspect); + e->suspect = porigin; + } + + return 0; +} + +static int pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt) +{ + git_commit *commit = origin->commit; + int i, num_parents; + git_blame__origin *sg_buf[16]; + git_blame__origin *porigin, **sg_origin = sg_buf; + int ret, error = 0; + + num_parents = git_commit_parentcount(commit); + if (!git_oid_cmp(git_commit_id(commit), &blame->options.oldest_commit)) + /* Stop at oldest specified commit */ + num_parents = 0; + else if (opt & GIT_BLAME_FIRST_PARENT && num_parents > 1) + /* Limit search to the first parent */ + num_parents = 1; + + if (!num_parents) { + git_oid_cpy(&blame->options.oldest_commit, git_commit_id(commit)); + goto finish; + } else if (num_parents < (int)ARRAY_SIZE(sg_buf)) + memset(sg_buf, 0, sizeof(sg_buf)); + else { + sg_origin = git__calloc(num_parents, sizeof(*sg_origin)); + GIT_ERROR_CHECK_ALLOC(sg_origin); + } + + for (i=0; icommit, i)) < 0) + goto finish; + porigin = find_origin(blame, p, origin); + + if (!porigin) { + /* + * We only have to decrement the parent's + * reference count when no porigin has + * been created, as otherwise the commit + * is assigned to the created object. + */ + git_commit_free(p); + continue; + } + if (porigin->blob && origin->blob && + !git_oid_cmp(git_blob_id(porigin->blob), git_blob_id(origin->blob))) { + error = pass_whole_blame(blame, origin, porigin); + origin_decref(porigin); + goto finish; + } + for (j = same = 0; jblob), git_blob_id(porigin->blob))) { + same = 1; + break; + } + if (!same) + sg_origin[i] = porigin; + else + origin_decref(porigin); + } + + /* Standard blame */ + for (i=0; iprevious) { + origin_incref(porigin); + origin->previous = porigin; + } + + if ((ret = pass_blame_to_parent(blame, origin, porigin)) != 0) { + if (ret < 0) + error = -1; + + goto finish; + } + } + + /* TODO: optionally find moves in parents' files */ + + /* TODO: optionally find copies in parents' files */ + +finish: + for (i=0; i pair), + * merge them together. + */ +static void coalesce(git_blame *blame) +{ + git_blame__entry *ent, *next; + + for (ent=blame->ent; ent && (next = ent->next); ent = next) { + if (same_suspect(ent->suspect, next->suspect) && + ent->guilty == next->guilty && + ent->s_lno + ent->num_lines == next->s_lno) + { + ent->num_lines += next->num_lines; + ent->next = next->next; + if (ent->next) + ent->next->prev = ent; + origin_decref(next->suspect); + git__free(next); + ent->score = 0; + next = ent; /* again */ + } + } +} + +int git_blame__like_git(git_blame *blame, uint32_t opt) +{ + int error = 0; + + while (true) { + git_blame__entry *ent; + git_blame__origin *suspect = NULL; + + /* Find a suspect to break down */ + for (ent = blame->ent; !suspect && ent; ent = ent->next) + if (!ent->guilty) + suspect = ent->suspect; + if (!suspect) + break; + + /* We'll use this suspect later in the loop, so hold on to it for now. */ + origin_incref(suspect); + + if ((error = pass_blame(blame, suspect, opt)) < 0) + break; + + /* Take responsibility for the remaining entries */ + for (ent = blame->ent; ent; ent = ent->next) { + if (same_suspect(ent->suspect, suspect)) { + ent->guilty = true; + ent->is_boundary = !git_oid_cmp( + git_commit_id(suspect->commit), + &blame->options.oldest_commit); + } + } + origin_decref(suspect); + } + + if (!error) + coalesce(blame); + + return error; +} + +void git_blame__free_entry(git_blame__entry *ent) +{ + if (!ent) return; + origin_decref(ent->suspect); + git__free(ent); +} diff --git a/src/libgit2/blame_git.h b/src/libgit2/blame_git.h new file mode 100644 index 000000000..48b85a20d --- /dev/null +++ b/src/libgit2/blame_git.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_blame_git__ +#define INCLUDE_blame_git__ + +#include "common.h" + +#include "blame.h" + +int git_blame__get_origin( + git_blame__origin **out, + git_blame *sb, + git_commit *commit, + const char *path); +void git_blame__free_entry(git_blame__entry *ent); +int git_blame__like_git(git_blame *sb, uint32_t flags); + +#endif diff --git a/src/libgit2/blob.c b/src/libgit2/blob.c new file mode 100644 index 000000000..19ce8b3b5 --- /dev/null +++ b/src/libgit2/blob.c @@ -0,0 +1,528 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "blob.h" + +#include "git2/common.h" +#include "git2/object.h" +#include "git2/repository.h" +#include "git2/odb_backend.h" + +#include "buf.h" +#include "filebuf.h" +#include "filter.h" + +const void *git_blob_rawcontent(const git_blob *blob) +{ + GIT_ASSERT_ARG_WITH_RETVAL(blob, NULL); + + if (blob->raw) + return blob->data.raw.data; + else + return git_odb_object_data(blob->data.odb); +} + +git_object_size_t git_blob_rawsize(const git_blob *blob) +{ + GIT_ASSERT_ARG(blob); + + if (blob->raw) + return blob->data.raw.size; + else + return (git_object_size_t)git_odb_object_size(blob->data.odb); +} + +int git_blob__getbuf(git_str *buffer, git_blob *blob) +{ + git_object_size_t size = git_blob_rawsize(blob); + + GIT_ERROR_CHECK_BLOBSIZE(size); + return git_str_set(buffer, git_blob_rawcontent(blob), (size_t)size); +} + +void git_blob__free(void *_blob) +{ + git_blob *blob = (git_blob *) _blob; + if (!blob->raw) + git_odb_object_free(blob->data.odb); + git__free(blob); +} + +int git_blob__parse_raw(void *_blob, const char *data, size_t size) +{ + git_blob *blob = (git_blob *) _blob; + + GIT_ASSERT_ARG(blob); + + blob->raw = 1; + blob->data.raw.data = data; + blob->data.raw.size = size; + return 0; +} + +int git_blob__parse(void *_blob, git_odb_object *odb_obj) +{ + git_blob *blob = (git_blob *) _blob; + + GIT_ASSERT_ARG(blob); + + git_cached_obj_incref((git_cached_obj *)odb_obj); + blob->raw = 0; + blob->data.odb = odb_obj; + return 0; +} + +int git_blob_create_from_buffer( + git_oid *id, git_repository *repo, const void *buffer, size_t len) +{ + int error; + git_odb *odb; + git_odb_stream *stream; + + GIT_ASSERT_ARG(id); + GIT_ASSERT_ARG(repo); + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || + (error = git_odb_open_wstream(&stream, odb, len, GIT_OBJECT_BLOB)) < 0) + return error; + + if ((error = git_odb_stream_write(stream, buffer, len)) == 0) + error = git_odb_stream_finalize_write(id, stream); + + git_odb_stream_free(stream); + return error; +} + +static int write_file_stream( + git_oid *id, git_odb *odb, const char *path, git_object_size_t file_size) +{ + int fd, error; + char buffer[FILEIO_BUFSIZE]; + git_odb_stream *stream = NULL; + ssize_t read_len = -1; + git_object_size_t written = 0; + + if ((error = git_odb_open_wstream( + &stream, odb, file_size, GIT_OBJECT_BLOB)) < 0) + return error; + + if ((fd = git_futils_open_ro(path)) < 0) { + git_odb_stream_free(stream); + return -1; + } + + while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { + error = git_odb_stream_write(stream, buffer, read_len); + written += read_len; + } + + p_close(fd); + + if (written != file_size || read_len < 0) { + git_error_set(GIT_ERROR_OS, "failed to read file into stream"); + error = -1; + } + + if (!error) + error = git_odb_stream_finalize_write(id, stream); + + git_odb_stream_free(stream); + return error; +} + +static int write_file_filtered( + git_oid *id, + git_object_size_t *size, + git_odb *odb, + const char *full_path, + git_filter_list *fl, + git_repository* repo) +{ + int error; + git_str tgt = GIT_STR_INIT; + + error = git_filter_list__apply_to_file(&tgt, fl, repo, full_path); + + /* Write the file to disk if it was properly filtered */ + if (!error) { + *size = tgt.size; + + error = git_odb_write(id, odb, tgt.ptr, tgt.size, GIT_OBJECT_BLOB); + } + + git_str_dispose(&tgt); + return error; +} + +static int write_symlink( + git_oid *id, git_odb *odb, const char *path, size_t link_size) +{ + char *link_data; + ssize_t read_len; + int error; + + link_data = git__malloc(link_size); + GIT_ERROR_CHECK_ALLOC(link_data); + + read_len = p_readlink(path, link_data, link_size); + if (read_len != (ssize_t)link_size) { + git_error_set(GIT_ERROR_OS, "failed to create blob: cannot read symlink '%s'", path); + git__free(link_data); + return -1; + } + + error = git_odb_write(id, odb, (void *)link_data, link_size, GIT_OBJECT_BLOB); + git__free(link_data); + return error; +} + +int git_blob__create_from_paths( + git_oid *id, + struct stat *out_st, + git_repository *repo, + const char *content_path, + const char *hint_path, + mode_t hint_mode, + bool try_load_filters) +{ + int error; + struct stat st; + git_odb *odb = NULL; + git_object_size_t size; + mode_t mode; + git_str path = GIT_STR_INIT; + + GIT_ASSERT_ARG(hint_path || !try_load_filters); + + if (!content_path) { + if (git_repository_workdir_path(&path, repo, hint_path) < 0) + return -1; + + content_path = path.ptr; + } + + if ((error = git_fs_path_lstat(content_path, &st)) < 0 || + (error = git_repository_odb(&odb, repo)) < 0) + goto done; + + if (S_ISDIR(st.st_mode)) { + git_error_set(GIT_ERROR_ODB, "cannot create blob from '%s': it is a directory", content_path); + error = GIT_EDIRECTORY; + goto done; + } + + if (out_st) + memcpy(out_st, &st, sizeof(st)); + + size = st.st_size; + mode = hint_mode ? hint_mode : st.st_mode; + + if (S_ISLNK(mode)) { + error = write_symlink(id, odb, content_path, (size_t)size); + } else { + git_filter_list *fl = NULL; + + if (try_load_filters) + /* Load the filters for writing this file to the ODB */ + error = git_filter_list_load( + &fl, repo, NULL, hint_path, + GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT); + + if (error < 0) + /* well, that didn't work */; + else if (fl == NULL) + /* No filters need to be applied to the document: we can stream + * directly from disk */ + error = write_file_stream(id, odb, content_path, size); + else { + /* We need to apply one or more filters */ + error = write_file_filtered(id, &size, odb, content_path, fl, repo); + + git_filter_list_free(fl); + } + + /* + * TODO: eventually support streaming filtered files, for files + * which are bigger than a given threshold. This is not a priority + * because applying a filter in streaming mode changes the final + * size of the blob, and without knowing its final size, the blob + * cannot be written in stream mode to the ODB. + * + * The plan is to do streaming writes to a tempfile on disk and then + * opening streaming that file to the ODB, using + * `write_file_stream`. + * + * CAREFULLY DESIGNED APIS YO + */ + } + +done: + git_odb_free(odb); + git_str_dispose(&path); + + return error; +} + +int git_blob_create_from_workdir( + git_oid *id, git_repository *repo, const char *path) +{ + return git_blob__create_from_paths(id, NULL, repo, NULL, path, 0, true); +} + +int git_blob_create_from_disk( + git_oid *id, git_repository *repo, const char *path) +{ + int error; + git_str full_path = GIT_STR_INIT; + const char *workdir, *hintpath = NULL; + + if ((error = git_fs_path_prettify(&full_path, path, NULL)) < 0) { + git_str_dispose(&full_path); + return error; + } + + workdir = git_repository_workdir(repo); + + if (workdir && !git__prefixcmp(full_path.ptr, workdir)) + hintpath = full_path.ptr + strlen(workdir); + + error = git_blob__create_from_paths( + id, NULL, repo, git_str_cstr(&full_path), hintpath, 0, !!hintpath); + + git_str_dispose(&full_path); + return error; +} + +typedef struct { + git_writestream parent; + git_filebuf fbuf; + git_repository *repo; + char *hintpath; +} blob_writestream; + +static int blob_writestream_close(git_writestream *_stream) +{ + blob_writestream *stream = (blob_writestream *) _stream; + + git_filebuf_cleanup(&stream->fbuf); + return 0; +} + +static void blob_writestream_free(git_writestream *_stream) +{ + blob_writestream *stream = (blob_writestream *) _stream; + + git_filebuf_cleanup(&stream->fbuf); + git__free(stream->hintpath); + git__free(stream); +} + +static int blob_writestream_write(git_writestream *_stream, const char *buffer, size_t len) +{ + blob_writestream *stream = (blob_writestream *) _stream; + + return git_filebuf_write(&stream->fbuf, buffer, len); +} + +int git_blob_create_from_stream(git_writestream **out, git_repository *repo, const char *hintpath) +{ + int error; + git_str path = GIT_STR_INIT; + blob_writestream *stream; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + stream = git__calloc(1, sizeof(blob_writestream)); + GIT_ERROR_CHECK_ALLOC(stream); + + if (hintpath) { + stream->hintpath = git__strdup(hintpath); + GIT_ERROR_CHECK_ALLOC(stream->hintpath); + } + + stream->repo = repo; + stream->parent.write = blob_writestream_write; + stream->parent.close = blob_writestream_close; + stream->parent.free = blob_writestream_free; + + if ((error = git_repository__item_path(&path, repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0 + || (error = git_str_joinpath(&path, path.ptr, "streamed")) < 0) + goto cleanup; + + if ((error = git_filebuf_open_withsize(&stream->fbuf, git_str_cstr(&path), GIT_FILEBUF_TEMPORARY, + 0666, 2 * 1024 * 1024)) < 0) + goto cleanup; + + *out = (git_writestream *) stream; + +cleanup: + if (error < 0) + blob_writestream_free((git_writestream *) stream); + + git_str_dispose(&path); + return error; +} + +int git_blob_create_from_stream_commit(git_oid *out, git_writestream *_stream) +{ + int error; + blob_writestream *stream = (blob_writestream *) _stream; + + /* + * We can make this more officient by avoiding writing to + * disk, but for now let's re-use the helper functions we + * have. + */ + if ((error = git_filebuf_flush(&stream->fbuf)) < 0) + goto cleanup; + + error = git_blob__create_from_paths(out, NULL, stream->repo, stream->fbuf.path_lock, + stream->hintpath, 0, !!stream->hintpath); + +cleanup: + blob_writestream_free(_stream); + return error; + +} + +int git_blob_is_binary(const git_blob *blob) +{ + git_str content = GIT_STR_INIT; + git_object_size_t size; + + GIT_ASSERT_ARG(blob); + + size = git_blob_rawsize(blob); + + git_str_attach_notowned(&content, git_blob_rawcontent(blob), + (size_t)min(size, GIT_FILTER_BYTES_TO_CHECK_NUL)); + return git_str_is_binary(&content); +} + +int git_blob_data_is_binary(const char *str, size_t len) +{ + git_str content = GIT_STR_INIT; + + git_str_attach_notowned(&content, str, len); + + return git_str_is_binary(&content); +} + +int git_blob_filter_options_init( + git_blob_filter_options *opts, + unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, + git_blob_filter_options, GIT_BLOB_FILTER_OPTIONS_INIT); + return 0; +} + +int git_blob_filter( + git_buf *out, + git_blob *blob, + const char *path, + git_blob_filter_options *given_opts) +{ + git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; + git_filter_options filter_opts = GIT_FILTER_OPTIONS_INIT; + git_filter_list *fl = NULL; + int error = 0; + + GIT_ASSERT_ARG(blob); + GIT_ASSERT_ARG(path); + GIT_ASSERT_ARG(out); + + GIT_ERROR_CHECK_VERSION( + given_opts, GIT_BLOB_FILTER_OPTIONS_VERSION, "git_blob_filter_options"); + + if (given_opts != NULL) + memcpy(&opts, given_opts, sizeof(git_blob_filter_options)); + + if ((opts.flags & GIT_BLOB_FILTER_CHECK_FOR_BINARY) != 0 && + git_blob_is_binary(blob)) + return 0; + + if ((opts.flags & GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES) != 0) + filter_opts.flags |= GIT_FILTER_NO_SYSTEM_ATTRIBUTES; + + if ((opts.flags & GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD) != 0) + filter_opts.flags |= GIT_FILTER_ATTRIBUTES_FROM_HEAD; + + if ((opts.flags & GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT) != 0) { + filter_opts.flags |= GIT_FILTER_ATTRIBUTES_FROM_COMMIT; + +#ifndef GIT_DEPRECATE_HARD + if (opts.commit_id) + git_oid_cpy(&filter_opts.attr_commit_id, opts.commit_id); + else +#endif + git_oid_cpy(&filter_opts.attr_commit_id, &opts.attr_commit_id); + } + + if (!(error = git_filter_list_load_ext( + &fl, git_blob_owner(blob), blob, path, + GIT_FILTER_TO_WORKTREE, &filter_opts))) { + + error = git_filter_list_apply_to_blob(out, fl, blob); + + git_filter_list_free(fl); + } + + return error; +} + +/* Deprecated functions */ + +#ifndef GIT_DEPRECATE_HARD +int git_blob_create_frombuffer( + git_oid *id, git_repository *repo, const void *buffer, size_t len) +{ + return git_blob_create_from_buffer(id, repo, buffer, len); +} + +int git_blob_create_fromworkdir(git_oid *id, git_repository *repo, const char *relative_path) +{ + return git_blob_create_from_workdir(id, repo, relative_path); +} + +int git_blob_create_fromdisk(git_oid *id, git_repository *repo, const char *path) +{ + return git_blob_create_from_disk(id, repo, path); +} + +int git_blob_create_fromstream( + git_writestream **out, + git_repository *repo, + const char *hintpath) +{ + return git_blob_create_from_stream(out, repo, hintpath); +} + +int git_blob_create_fromstream_commit( + git_oid *out, + git_writestream *stream) +{ + return git_blob_create_from_stream_commit(out, stream); +} + +int git_blob_filtered_content( + git_buf *out, + git_blob *blob, + const char *path, + int check_for_binary_data) +{ + git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; + + if (check_for_binary_data) + opts.flags |= GIT_BLOB_FILTER_CHECK_FOR_BINARY; + else + opts.flags &= ~GIT_BLOB_FILTER_CHECK_FOR_BINARY; + + return git_blob_filter(out, blob, path, &opts); +} +#endif diff --git a/src/libgit2/blob.h b/src/libgit2/blob.h new file mode 100644 index 000000000..9a5dda225 --- /dev/null +++ b/src/libgit2/blob.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_blob_h__ +#define INCLUDE_blob_h__ + +#include "common.h" + +#include "git2/blob.h" +#include "repository.h" +#include "odb.h" +#include "futils.h" + +struct git_blob { + git_object object; + + union { + git_odb_object *odb; + struct { + const char *data; + git_object_size_t size; + } raw; + } data; + unsigned int raw:1; +}; + +#define GIT_ERROR_CHECK_BLOBSIZE(n) \ + do { \ + if (!git__is_sizet(n)) { \ + git_error_set(GIT_ERROR_NOMEMORY, "blob contents too large to fit in memory"); \ + return -1; \ + } \ + } while(0) + +void git_blob__free(void *blob); +int git_blob__parse(void *blob, git_odb_object *obj); +int git_blob__parse_raw(void *blob, const char *data, size_t size); +int git_blob__getbuf(git_str *buffer, git_blob *blob); + +extern int git_blob__create_from_paths( + git_oid *out_oid, + struct stat *out_st, + git_repository *repo, + const char *full_path, + const char *hint_path, + mode_t hint_mode, + bool apply_filters); + +#endif diff --git a/src/libgit2/branch.c b/src/libgit2/branch.c new file mode 100644 index 000000000..2e29af99d --- /dev/null +++ b/src/libgit2/branch.c @@ -0,0 +1,818 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "branch.h" + +#include "buf.h" +#include "commit.h" +#include "tag.h" +#include "config.h" +#include "refspec.h" +#include "refs.h" +#include "remote.h" +#include "annotated_commit.h" +#include "worktree.h" + +#include "git2/branch.h" + +static int retrieve_branch_reference( + git_reference **branch_reference_out, + git_repository *repo, + const char *branch_name, + bool is_remote) +{ + git_reference *branch = NULL; + int error = 0; + char *prefix; + git_str ref_name = GIT_STR_INIT; + + prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR; + + if ((error = git_str_joinpath(&ref_name, prefix, branch_name)) < 0) + /* OOM */; + else if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0) + git_error_set( + GIT_ERROR_REFERENCE, "cannot locate %s branch '%s'", + is_remote ? "remote-tracking" : "local", branch_name); + + *branch_reference_out = branch; /* will be NULL on error */ + + git_str_dispose(&ref_name); + return error; +} + +static int not_a_local_branch(const char *reference_name) +{ + git_error_set( + GIT_ERROR_INVALID, + "reference '%s' is not a local branch.", reference_name); + return -1; +} + +static int create_branch( + git_reference **ref_out, + git_repository *repository, + const char *branch_name, + const git_commit *commit, + const char *from, + int force) +{ + int is_unmovable_head = 0; + git_reference *branch = NULL; + git_str canonical_branch_name = GIT_STR_INIT, + log_message = GIT_STR_INIT; + int error = -1; + int bare = git_repository_is_bare(repository); + + GIT_ASSERT_ARG(branch_name); + GIT_ASSERT_ARG(commit); + GIT_ASSERT_ARG(ref_out); + GIT_ASSERT_ARG(git_commit_owner(commit) == repository); + + if (!git__strcmp(branch_name, "HEAD")) { + git_error_set(GIT_ERROR_REFERENCE, "'HEAD' is not a valid branch name"); + error = -1; + goto cleanup; + } + + if (force && !bare && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) { + error = git_branch_is_head(branch); + git_reference_free(branch); + branch = NULL; + + if (error < 0) + goto cleanup; + + is_unmovable_head = error; + } + + if (is_unmovable_head && force) { + git_error_set(GIT_ERROR_REFERENCE, "cannot force update branch '%s' as it is " + "the current HEAD of the repository.", branch_name); + error = -1; + goto cleanup; + } + + if (git_str_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) + goto cleanup; + + if (git_str_printf(&log_message, "branch: Created from %s", from) < 0) + goto cleanup; + + error = git_reference_create(&branch, repository, + git_str_cstr(&canonical_branch_name), git_commit_id(commit), force, + git_str_cstr(&log_message)); + + if (!error) + *ref_out = branch; + +cleanup: + git_str_dispose(&canonical_branch_name); + git_str_dispose(&log_message); + return error; +} + +int git_branch_create( + git_reference **ref_out, + git_repository *repository, + const char *branch_name, + const git_commit *commit, + int force) +{ + char commit_id[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(commit_id, GIT_OID_HEXSZ + 1, git_commit_id(commit)); + return create_branch(ref_out, repository, branch_name, commit, commit_id, force); +} + +int git_branch_create_from_annotated( + git_reference **ref_out, + git_repository *repository, + const char *branch_name, + const git_annotated_commit *commit, + int force) +{ + return create_branch(ref_out, + repository, branch_name, commit->commit, commit->description, force); +} + +static int branch_is_checked_out(git_repository *worktree, void *payload) +{ + git_reference *branch = (git_reference *) payload; + git_reference *head = NULL; + int error; + + if (git_repository_is_bare(worktree)) + return 0; + + if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0) { + if (error == GIT_ENOTFOUND) + error = 0; + goto out; + } + + if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC) + goto out; + + error = !git__strcmp(head->target.symbolic, branch->name); + +out: + git_reference_free(head); + return error; +} + +int git_branch_is_checked_out(const git_reference *branch) +{ + GIT_ASSERT_ARG(branch); + + if (!git_reference_is_branch(branch)) + return 0; + return git_repository_foreach_worktree(git_reference_owner(branch), + branch_is_checked_out, (void *)branch) == 1; +} + +int git_branch_delete(git_reference *branch) +{ + int is_head; + git_str config_section = GIT_STR_INIT; + int error = -1; + + GIT_ASSERT_ARG(branch); + + if (!git_reference_is_branch(branch) && !git_reference_is_remote(branch)) { + git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a valid branch.", + git_reference_name(branch)); + return GIT_ENOTFOUND; + } + + if ((is_head = git_branch_is_head(branch)) < 0) + return is_head; + + if (is_head) { + git_error_set(GIT_ERROR_REFERENCE, "cannot delete branch '%s' as it is " + "the current HEAD of the repository.", git_reference_name(branch)); + return -1; + } + + if (git_reference_is_branch(branch) && git_branch_is_checked_out(branch)) { + git_error_set(GIT_ERROR_REFERENCE, "Cannot delete branch '%s' as it is " + "the current HEAD of a linked repository.", git_reference_name(branch)); + return -1; + } + + if (git_str_join(&config_section, '.', "branch", + git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) + goto on_error; + + if (git_config_rename_section( + git_reference_owner(branch), git_str_cstr(&config_section), NULL) < 0) + goto on_error; + + error = git_reference_delete(branch); + +on_error: + git_str_dispose(&config_section); + return error; +} + +typedef struct { + git_reference_iterator *iter; + unsigned int flags; +} branch_iter; + +int git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *_iter) +{ + branch_iter *iter = (branch_iter *) _iter; + git_reference *ref; + int error; + + while ((error = git_reference_next(&ref, iter->iter)) == 0) { + if ((iter->flags & GIT_BRANCH_LOCAL) && + !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR)) { + *out = ref; + *out_type = GIT_BRANCH_LOCAL; + + return 0; + } else if ((iter->flags & GIT_BRANCH_REMOTE) && + !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) { + *out = ref; + *out_type = GIT_BRANCH_REMOTE; + + return 0; + } else { + git_reference_free(ref); + } + } + + return error; +} + +int git_branch_iterator_new( + git_branch_iterator **out, + git_repository *repo, + git_branch_t list_flags) +{ + branch_iter *iter; + + iter = git__calloc(1, sizeof(branch_iter)); + GIT_ERROR_CHECK_ALLOC(iter); + + iter->flags = list_flags; + + if (git_reference_iterator_new(&iter->iter, repo) < 0) { + git__free(iter); + return -1; + } + + *out = (git_branch_iterator *) iter; + + return 0; +} + +void git_branch_iterator_free(git_branch_iterator *_iter) +{ + branch_iter *iter = (branch_iter *) _iter; + + if (iter == NULL) + return; + + git_reference_iterator_free(iter->iter); + git__free(iter); +} + +int git_branch_move( + git_reference **out, + git_reference *branch, + const char *new_branch_name, + int force) +{ + git_str new_reference_name = GIT_STR_INIT, + old_config_section = GIT_STR_INIT, + new_config_section = GIT_STR_INIT, + log_message = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(branch); + GIT_ASSERT_ARG(new_branch_name); + + if (!git_reference_is_branch(branch)) + return not_a_local_branch(git_reference_name(branch)); + + if ((error = git_str_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) + goto done; + + if ((error = git_str_printf(&log_message, "branch: renamed %s to %s", + git_reference_name(branch), git_str_cstr(&new_reference_name))) < 0) + goto done; + + /* first update ref then config so failure won't trash config */ + + error = git_reference_rename( + out, branch, git_str_cstr(&new_reference_name), force, + git_str_cstr(&log_message)); + if (error < 0) + goto done; + + git_str_join(&old_config_section, '.', "branch", + git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)); + git_str_join(&new_config_section, '.', "branch", new_branch_name); + + error = git_config_rename_section( + git_reference_owner(branch), + git_str_cstr(&old_config_section), + git_str_cstr(&new_config_section)); + +done: + git_str_dispose(&new_reference_name); + git_str_dispose(&old_config_section); + git_str_dispose(&new_config_section); + git_str_dispose(&log_message); + + return error; +} + +int git_branch_lookup( + git_reference **ref_out, + git_repository *repo, + const char *branch_name, + git_branch_t branch_type) +{ + int error = -1; + + GIT_ASSERT_ARG(ref_out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(branch_name); + + switch (branch_type) { + case GIT_BRANCH_LOCAL: + case GIT_BRANCH_REMOTE: + error = retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE); + break; + case GIT_BRANCH_ALL: + error = retrieve_branch_reference(ref_out, repo, branch_name, false); + if (error == GIT_ENOTFOUND) + error = retrieve_branch_reference(ref_out, repo, branch_name, true); + break; + default: + GIT_ASSERT(false); + } + return error; +} + +int git_branch_name( + const char **out, + const git_reference *ref) +{ + const char *branch_name; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ref); + + branch_name = ref->name; + + if (git_reference_is_branch(ref)) { + branch_name += strlen(GIT_REFS_HEADS_DIR); + } else if (git_reference_is_remote(ref)) { + branch_name += strlen(GIT_REFS_REMOTES_DIR); + } else { + git_error_set(GIT_ERROR_INVALID, + "reference '%s' is neither a local nor a remote branch.", ref->name); + return -1; + } + *out = branch_name; + return 0; +} + +static int retrieve_upstream_configuration( + git_str *out, + const git_config *config, + const char *canonical_branch_name, + const char *format) +{ + git_str buf = GIT_STR_INIT; + int error; + + if (git_str_printf(&buf, format, + canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0) + return -1; + + error = git_config__get_string_buf(out, config, git_str_cstr(&buf)); + git_str_dispose(&buf); + return error; +} + +int git_branch_upstream_name( + git_buf *out, + git_repository *repo, + const char *refname) +{ + GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_name, repo, refname); +} + +int git_branch__upstream_name( + git_str *out, + git_repository *repo, + const char *refname) +{ + git_str remote_name = GIT_STR_INIT; + git_str merge_name = GIT_STR_INIT; + git_str buf = GIT_STR_INIT; + int error = -1; + git_remote *remote = NULL; + const git_refspec *refspec; + git_config *config; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(refname); + + if (!git_reference__is_branch(refname)) + return not_a_local_branch(refname); + + if ((error = git_repository_config_snapshot(&config, repo)) < 0) + return error; + + if ((error = retrieve_upstream_configuration( + &remote_name, config, refname, "branch.%s.remote")) < 0) + goto cleanup; + + if ((error = retrieve_upstream_configuration( + &merge_name, config, refname, "branch.%s.merge")) < 0) + goto cleanup; + + if (git_str_len(&remote_name) == 0 || git_str_len(&merge_name) == 0) { + git_error_set(GIT_ERROR_REFERENCE, + "branch '%s' does not have an upstream", refname); + error = GIT_ENOTFOUND; + goto cleanup; + } + + if (strcmp(".", git_str_cstr(&remote_name)) != 0) { + if ((error = git_remote_lookup(&remote, repo, git_str_cstr(&remote_name))) < 0) + goto cleanup; + + refspec = git_remote__matching_refspec(remote, git_str_cstr(&merge_name)); + if (!refspec) { + error = GIT_ENOTFOUND; + goto cleanup; + } + + if (git_refspec__transform(&buf, refspec, git_str_cstr(&merge_name)) < 0) + goto cleanup; + } else + if (git_str_set(&buf, git_str_cstr(&merge_name), git_str_len(&merge_name)) < 0) + goto cleanup; + + git_str_swap(out, &buf); + +cleanup: + git_config_free(config); + git_remote_free(remote); + git_str_dispose(&remote_name); + git_str_dispose(&merge_name); + git_str_dispose(&buf); + return error; +} + +static int git_branch_upstream_with_format( + git_str *out, + git_repository *repo, + const char *refname, + const char *format, + const char *format_name) +{ + git_config *cfg; + int error; + + if (!git_reference__is_branch(refname)) + return not_a_local_branch(refname); + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 || + (error = retrieve_upstream_configuration(out, cfg, refname, format)) < 0) + return error; + + if (git_str_len(out) == 0) { + git_error_set(GIT_ERROR_REFERENCE, "branch '%s' does not have an upstream %s", refname, format_name); + error = GIT_ENOTFOUND; + } + + return error; +} + +int git_branch_upstream_remote( + git_buf *out, + git_repository *repo, + const char *refname) +{ + GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_remote, repo, refname); +} + +int git_branch__upstream_remote( + git_str *out, + git_repository *repo, + const char *refname) +{ + return git_branch_upstream_with_format(out, repo, refname, "branch.%s.remote", "remote"); +} + +int git_branch_upstream_merge( + git_buf *out, + git_repository *repo, + const char *refname) +{ + GIT_BUF_WRAP_PRIVATE(out, git_branch__upstream_merge, repo, refname); +} + +int git_branch__upstream_merge( + git_str *out, + git_repository *repo, + const char *refname) +{ + return git_branch_upstream_with_format(out, repo, refname, "branch.%s.merge", "merge"); +} + +int git_branch_remote_name( + git_buf *out, + git_repository *repo, + const char *refname) +{ + GIT_BUF_WRAP_PRIVATE(out, git_branch__remote_name, repo, refname); +} + +int git_branch__remote_name( + git_str *out, + git_repository *repo, + const char *refname) +{ + git_strarray remote_list = {0}; + size_t i; + git_remote *remote; + const git_refspec *fetchspec; + int error = 0; + char *remote_name = NULL; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(refname); + + /* Verify that this is a remote branch */ + if (!git_reference__is_remote(refname)) { + git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a remote branch.", + refname); + error = GIT_ERROR; + goto cleanup; + } + + /* Get the remotes */ + if ((error = git_remote_list(&remote_list, repo)) < 0) + goto cleanup; + + /* Find matching remotes */ + for (i = 0; i < remote_list.count; i++) { + if ((error = git_remote_lookup(&remote, repo, remote_list.strings[i])) < 0) + continue; + + fetchspec = git_remote__matching_dst_refspec(remote, refname); + if (fetchspec) { + /* If we have not already set out yet, then set + * it to the matching remote name. Otherwise + * multiple remotes match this reference, and it + * is ambiguous. */ + if (!remote_name) { + remote_name = remote_list.strings[i]; + } else { + git_remote_free(remote); + + git_error_set(GIT_ERROR_REFERENCE, + "reference '%s' is ambiguous", refname); + error = GIT_EAMBIGUOUS; + goto cleanup; + } + } + + git_remote_free(remote); + } + + if (remote_name) { + git_str_clear(out); + error = git_str_puts(out, remote_name); + } else { + git_error_set(GIT_ERROR_REFERENCE, + "could not determine remote for '%s'", refname); + error = GIT_ENOTFOUND; + } + +cleanup: + if (error < 0) + git_str_dispose(out); + + git_strarray_dispose(&remote_list); + return error; +} + +int git_branch_upstream( + git_reference **tracking_out, + const git_reference *branch) +{ + int error; + git_str tracking_name = GIT_STR_INIT; + + if ((error = git_branch__upstream_name(&tracking_name, + git_reference_owner(branch), git_reference_name(branch))) < 0) + return error; + + error = git_reference_lookup( + tracking_out, + git_reference_owner(branch), + git_str_cstr(&tracking_name)); + + git_str_dispose(&tracking_name); + return error; +} + +static int unset_upstream(git_config *config, const char *shortname) +{ + git_str buf = GIT_STR_INIT; + + if (git_str_printf(&buf, "branch.%s.remote", shortname) < 0) + return -1; + + if (git_config_delete_entry(config, git_str_cstr(&buf)) < 0) + goto on_error; + + git_str_clear(&buf); + if (git_str_printf(&buf, "branch.%s.merge", shortname) < 0) + goto on_error; + + if (git_config_delete_entry(config, git_str_cstr(&buf)) < 0) + goto on_error; + + git_str_dispose(&buf); + return 0; + +on_error: + git_str_dispose(&buf); + return -1; +} + +int git_branch_set_upstream(git_reference *branch, const char *branch_name) +{ + git_str key = GIT_STR_INIT, remote_name = GIT_STR_INIT, merge_refspec = GIT_STR_INIT; + git_reference *upstream; + git_repository *repo; + git_remote *remote = NULL; + git_config *config; + const char *refname, *shortname; + int local, error; + const git_refspec *fetchspec; + + refname = git_reference_name(branch); + if (!git_reference__is_branch(refname)) + return not_a_local_branch(refname); + + if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0) + return -1; + + shortname = refname + strlen(GIT_REFS_HEADS_DIR); + + /* We're unsetting, delegate and bail-out */ + if (branch_name == NULL) + return unset_upstream(config, shortname); + + repo = git_reference_owner(branch); + + /* First we need to resolve name to a branch */ + if (git_branch_lookup(&upstream, repo, branch_name, GIT_BRANCH_LOCAL) == 0) + local = 1; + else if (git_branch_lookup(&upstream, repo, branch_name, GIT_BRANCH_REMOTE) == 0) + local = 0; + else { + git_error_set(GIT_ERROR_REFERENCE, + "cannot set upstream for branch '%s'", shortname); + return GIT_ENOTFOUND; + } + + /* + * If it's a local-tracking branch, its remote is "." (as "the local + * repository"), and the branch name is simply the refname. + * Otherwise we need to figure out what the remote-tracking branch's + * name on the remote is and use that. + */ + if (local) + error = git_str_puts(&remote_name, "."); + else + error = git_branch__remote_name(&remote_name, repo, git_reference_name(upstream)); + + if (error < 0) + goto on_error; + + /* Update the upstream branch config with the new name */ + if (git_str_printf(&key, "branch.%s.remote", shortname) < 0) + goto on_error; + + if (git_config_set_string(config, git_str_cstr(&key), git_str_cstr(&remote_name)) < 0) + goto on_error; + + if (local) { + /* A local branch uses the upstream refname directly */ + if (git_str_puts(&merge_refspec, git_reference_name(upstream)) < 0) + goto on_error; + } else { + /* We transform the upstream branch name according to the remote's refspecs */ + if (git_remote_lookup(&remote, repo, git_str_cstr(&remote_name)) < 0) + goto on_error; + + fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream)); + if (!fetchspec || git_refspec__rtransform(&merge_refspec, fetchspec, git_reference_name(upstream)) < 0) + goto on_error; + + git_remote_free(remote); + remote = NULL; + } + + /* Update the merge branch config with the refspec */ + git_str_clear(&key); + if (git_str_printf(&key, "branch.%s.merge", shortname) < 0) + goto on_error; + + if (git_config_set_string(config, git_str_cstr(&key), git_str_cstr(&merge_refspec)) < 0) + goto on_error; + + git_reference_free(upstream); + git_str_dispose(&key); + git_str_dispose(&remote_name); + git_str_dispose(&merge_refspec); + + return 0; + +on_error: + git_reference_free(upstream); + git_str_dispose(&key); + git_str_dispose(&remote_name); + git_str_dispose(&merge_refspec); + git_remote_free(remote); + + return -1; +} + +int git_branch_is_head( + const git_reference *branch) +{ + git_reference *head; + bool is_same = false; + int error; + + GIT_ASSERT_ARG(branch); + + if (!git_reference_is_branch(branch)) + return false; + + error = git_repository_head(&head, git_reference_owner(branch)); + + if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND) + return false; + + if (error < 0) + return -1; + + is_same = strcmp( + git_reference_name(branch), + git_reference_name(head)) == 0; + + git_reference_free(head); + + return is_same; +} + +int git_branch_name_is_valid(int *valid, const char *name) +{ + git_str ref_name = GIT_STR_INIT; + int error = 0; + + GIT_ASSERT(valid); + + *valid = 0; + + /* + * Discourage branch name starting with dash, + * https://github.com/git/git/commit/6348624010888b + * and discourage HEAD as branch name, + * https://github.com/git/git/commit/a625b092cc5994 + */ + if (!name || name[0] == '-' || !git__strcmp(name, "HEAD")) + goto done; + + if ((error = git_str_puts(&ref_name, GIT_REFS_HEADS_DIR)) < 0 || + (error = git_str_puts(&ref_name, name)) < 0) + goto done; + + error = git_reference_name_is_valid(valid, ref_name.ptr); + +done: + git_str_dispose(&ref_name); + return error; +} diff --git a/src/libgit2/branch.h b/src/libgit2/branch.h new file mode 100644 index 000000000..b4db42a01 --- /dev/null +++ b/src/libgit2/branch.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_branch_h__ +#define INCLUDE_branch_h__ + +#include "common.h" + +#include "str.h" + +int git_branch__remote_name( + git_str *out, + git_repository *repo, + const char *refname); +int git_branch__upstream_remote( + git_str *out, + git_repository *repo, + const char *refname); +int git_branch__upstream_merge( + git_str *out, + git_repository *repo, + const char *refname); +int git_branch__upstream_name( + git_str *tracking_name, + git_repository *repo, + const char *canonical_branch_name); + +#endif diff --git a/src/libgit2/buf.c b/src/libgit2/buf.c new file mode 100644 index 000000000..652f5dd52 --- /dev/null +++ b/src/libgit2/buf.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "buf.h" +#include "common.h" + +int git_buf_sanitize(git_buf *buf) +{ + GIT_ASSERT_ARG(buf); + + if (buf->reserved > 0) + buf->ptr[0] = '\0'; + else + buf->ptr = git_str__initstr; + + buf->size = 0; + return 0; +} + +int git_buf_tostr(git_str *out, git_buf *buf) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(buf); + + if (git_buf_sanitize(buf) < 0) + return -1; + + out->ptr = buf->ptr; + out->asize = buf->reserved; + out->size = buf->size; + + buf->ptr = git_str__initstr; + buf->reserved = 0; + buf->size = 0; + + return 0; +} + +int git_buf_fromstr(git_buf *out, git_str *str) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(str); + + out->ptr = str->ptr; + out->reserved = str->asize; + out->size = str->size; + + str->ptr = git_str__initstr; + str->asize = 0; + str->size = 0; + + return 0; +} + +void git_buf_dispose(git_buf *buf) +{ + if (!buf) + return; + + if (buf->ptr != git_str__initstr) + git__free(buf->ptr); + + buf->ptr = git_str__initstr; + buf->reserved = 0; + buf->size = 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_buf_grow(git_buf *buffer, size_t target_size) +{ + char *newptr; + + if (buffer->reserved >= target_size) + return 0; + + if (buffer->ptr == git_str__initstr) + newptr = git__malloc(target_size); + else + newptr = git__realloc(buffer->ptr, target_size); + + if (!newptr) + return -1; + + buffer->ptr = newptr; + buffer->reserved = target_size; + return 0; +} + +int git_buf_set(git_buf *buffer, const void *data, size_t datalen) +{ + size_t alloclen; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, datalen, 1); + + if (git_buf_grow(buffer, alloclen) < 0) + return -1; + + memmove(buffer->ptr, data, datalen); + buffer->size = datalen; + buffer->ptr[buffer->size] = '\0'; + + return 0; +} + +int git_buf_is_binary(const git_buf *buf) +{ + git_str str = GIT_STR_INIT_CONST(buf->ptr, buf->size); + return git_str_is_binary(&str); +} + +int git_buf_contains_nul(const git_buf *buf) +{ + git_str str = GIT_STR_INIT_CONST(buf->ptr, buf->size); + return git_str_contains_nul(&str); +} + +void git_buf_free(git_buf *buffer) +{ + git_buf_dispose(buffer); +} + +#endif diff --git a/src/libgit2/buf.h b/src/libgit2/buf.h new file mode 100644 index 000000000..4bc7f2709 --- /dev/null +++ b/src/libgit2/buf.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_buf_h__ +#define INCLUDE_buf_h__ + +#include "git2/buffer.h" +#include "common.h" + +/* + * Adapts a private API that takes a `git_str` into a public API that + * takes a `git_buf`. + */ + +#define GIT_BUF_WRAP_PRIVATE(buf, fn, ...) \ + { \ + git_str str = GIT_STR_INIT; \ + int error; \ + if ((error = git_buf_tostr(&str, buf)) == 0 && \ + (error = fn(&str, __VA_ARGS__)) == 0) \ + error = git_buf_fromstr(buf, &str); \ + git_str_dispose(&str); \ + return error; \ +} + +/** + * "Sanitizes" a buffer from user input. This simply ensures that the + * `git_buf` has nice defaults if the user didn't set the members to + * anything, so that if we return early we don't leave it populated + * with nonsense. + */ +extern int git_buf_sanitize(git_buf *from_user); + +/** + * Populate a `git_str` from a `git_buf` for passing to libgit2 internal + * functions. Sanitizes the given `git_buf` before proceeding. The + * `git_buf` will no longer point to this memory. + */ +extern int git_buf_tostr(git_str *out, git_buf *buf); + +/** + * Populate a `git_buf` from a `git_str` for returning to a user. + * The `git_str` will no longer point to this memory. + */ +extern int git_buf_fromstr(git_buf *out, git_str *str); + +#endif diff --git a/src/libgit2/cache.c b/src/libgit2/cache.c new file mode 100644 index 000000000..2f68e357c --- /dev/null +++ b/src/libgit2/cache.c @@ -0,0 +1,253 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "cache.h" + +#include "repository.h" +#include "commit.h" +#include "thread.h" +#include "util.h" +#include "odb.h" +#include "object.h" +#include "git2/oid.h" + +bool git_cache__enabled = true; +ssize_t git_cache__max_storage = (256 * 1024 * 1024); +git_atomic_ssize git_cache__current_storage = {0}; + +static size_t git_cache__max_object_size[8] = { + 0, /* GIT_OBJECT__EXT1 */ + 4096, /* GIT_OBJECT_COMMIT */ + 4096, /* GIT_OBJECT_TREE */ + 0, /* GIT_OBJECT_BLOB */ + 4096, /* GIT_OBJECT_TAG */ + 0, /* GIT_OBJECT__EXT2 */ + 0, /* GIT_OBJECT_OFS_DELTA */ + 0 /* GIT_OBJECT_REF_DELTA */ +}; + +int git_cache_set_max_object_size(git_object_t type, size_t size) +{ + if (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) { + git_error_set(GIT_ERROR_INVALID, "type out of range"); + return -1; + } + + git_cache__max_object_size[type] = size; + return 0; +} + +int git_cache_init(git_cache *cache) +{ + memset(cache, 0, sizeof(*cache)); + + if ((git_oidmap_new(&cache->map)) < 0) + return -1; + + if (git_rwlock_init(&cache->lock)) { + git_error_set(GIT_ERROR_OS, "failed to initialize cache rwlock"); + return -1; + } + + return 0; +} + +/* called with lock */ +static void clear_cache(git_cache *cache) +{ + git_cached_obj *evict = NULL; + + if (git_cache_size(cache) == 0) + return; + + git_oidmap_foreach_value(cache->map, evict, { + git_cached_obj_decref(evict); + }); + + git_oidmap_clear(cache->map); + git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory); + cache->used_memory = 0; +} + +void git_cache_clear(git_cache *cache) +{ + if (git_rwlock_wrlock(&cache->lock) < 0) + return; + + clear_cache(cache); + + git_rwlock_wrunlock(&cache->lock); +} + +void git_cache_dispose(git_cache *cache) +{ + git_cache_clear(cache); + git_oidmap_free(cache->map); + git_rwlock_free(&cache->lock); + git__memzero(cache, sizeof(*cache)); +} + +/* Called with lock */ +static void cache_evict_entries(git_cache *cache) +{ + size_t evict_count = git_cache_size(cache) / 2048, i; + ssize_t evicted_memory = 0; + + if (evict_count < 8) + evict_count = 8; + + /* do not infinite loop if there's not enough entries to evict */ + if (evict_count > git_cache_size(cache)) { + clear_cache(cache); + return; + } + + i = 0; + while (evict_count > 0) { + git_cached_obj *evict; + const git_oid *key; + + if (git_oidmap_iterate((void **) &evict, cache->map, &i, &key) == GIT_ITEROVER) + break; + + evict_count--; + evicted_memory += evict->size; + git_oidmap_delete(cache->map, key); + git_cached_obj_decref(evict); + } + + cache->used_memory -= evicted_memory; + git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory); +} + +static bool cache_should_store(git_object_t object_type, size_t object_size) +{ + size_t max_size = git_cache__max_object_size[object_type]; + return git_cache__enabled && object_size < max_size; +} + +static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags) +{ + git_cached_obj *entry; + + if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0) + return NULL; + + if ((entry = git_oidmap_get(cache->map, oid)) != NULL) { + if (flags && entry->flags != flags) { + entry = NULL; + } else { + git_cached_obj_incref(entry); + } + } + + git_rwlock_rdunlock(&cache->lock); + + return entry; +} + +static void *cache_store(git_cache *cache, git_cached_obj *entry) +{ + git_cached_obj *stored_entry; + + git_cached_obj_incref(entry); + + if (!git_cache__enabled && cache->used_memory > 0) { + git_cache_clear(cache); + return entry; + } + + if (!cache_should_store(entry->type, entry->size)) + return entry; + + if (git_rwlock_wrlock(&cache->lock) < 0) + return entry; + + /* soften the load on the cache */ + if (git_atomic_ssize_get(&git_cache__current_storage) > git_cache__max_storage) + cache_evict_entries(cache); + + /* not found */ + if ((stored_entry = git_oidmap_get(cache->map, &entry->oid)) == NULL) { + if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) { + git_cached_obj_incref(entry); + cache->used_memory += entry->size; + git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size); + } + } + /* found */ + else { + if (stored_entry->flags == entry->flags) { + git_cached_obj_decref(entry); + git_cached_obj_incref(stored_entry); + entry = stored_entry; + } else if (stored_entry->flags == GIT_CACHE_STORE_RAW && + entry->flags == GIT_CACHE_STORE_PARSED) { + if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) { + git_cached_obj_decref(stored_entry); + git_cached_obj_incref(entry); + } else { + git_cached_obj_decref(entry); + git_cached_obj_incref(stored_entry); + entry = stored_entry; + } + } else { + /* NO OP */ + } + } + + git_rwlock_wrunlock(&cache->lock); + return entry; +} + +void *git_cache_store_raw(git_cache *cache, git_odb_object *entry) +{ + entry->cached.flags = GIT_CACHE_STORE_RAW; + return cache_store(cache, (git_cached_obj *)entry); +} + +void *git_cache_store_parsed(git_cache *cache, git_object *entry) +{ + entry->cached.flags = GIT_CACHE_STORE_PARSED; + return cache_store(cache, (git_cached_obj *)entry); +} + +git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid) +{ + return cache_get(cache, oid, GIT_CACHE_STORE_RAW); +} + +git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid) +{ + return cache_get(cache, oid, GIT_CACHE_STORE_PARSED); +} + +void *git_cache_get_any(git_cache *cache, const git_oid *oid) +{ + return cache_get(cache, oid, GIT_CACHE_STORE_ANY); +} + +void git_cached_obj_decref(void *_obj) +{ + git_cached_obj *obj = _obj; + + if (git_atomic32_dec(&obj->refcount) == 0) { + switch (obj->flags) { + case GIT_CACHE_STORE_RAW: + git_odb_object__free(_obj); + break; + + case GIT_CACHE_STORE_PARSED: + git_object__free(_obj); + break; + + default: + git__free(_obj); + break; + } + } +} diff --git a/src/libgit2/cache.h b/src/libgit2/cache.h new file mode 100644 index 000000000..42c4fa80d --- /dev/null +++ b/src/libgit2/cache.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_cache_h__ +#define INCLUDE_cache_h__ + +#include "common.h" + +#include "git2/common.h" +#include "git2/oid.h" +#include "git2/odb.h" + +#include "thread.h" +#include "oidmap.h" + +enum { + GIT_CACHE_STORE_ANY = 0, + GIT_CACHE_STORE_RAW = 1, + GIT_CACHE_STORE_PARSED = 2 +}; + +typedef struct { + git_oid oid; + int16_t type; /* git_object_t value */ + uint16_t flags; /* GIT_CACHE_STORE value */ + size_t size; + git_atomic32 refcount; +} git_cached_obj; + +typedef struct { + git_oidmap *map; + git_rwlock lock; + ssize_t used_memory; +} git_cache; + +extern bool git_cache__enabled; +extern ssize_t git_cache__max_storage; +extern git_atomic_ssize git_cache__current_storage; + +int git_cache_set_max_object_size(git_object_t type, size_t size); + +int git_cache_init(git_cache *cache); +void git_cache_dispose(git_cache *cache); +void git_cache_clear(git_cache *cache); + +void *git_cache_store_raw(git_cache *cache, git_odb_object *entry); +void *git_cache_store_parsed(git_cache *cache, git_object *entry); + +git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid); +git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid); +void *git_cache_get_any(git_cache *cache, const git_oid *oid); + +GIT_INLINE(size_t) git_cache_size(git_cache *cache) +{ + return (size_t)git_oidmap_size(cache->map); +} + +GIT_INLINE(void) git_cached_obj_incref(void *_obj) +{ + git_cached_obj *obj = _obj; + git_atomic32_inc(&obj->refcount); +} + +void git_cached_obj_decref(void *_obj); + +#endif diff --git a/src/libgit2/cc-compat.h b/src/libgit2/cc-compat.h new file mode 100644 index 000000000..a0971e86c --- /dev/null +++ b/src/libgit2/cc-compat.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_cc_compat_h__ +#define INCLUDE_cc_compat_h__ + +#include + +/* + * See if our compiler is known to support flexible array members. + */ +#ifndef GIT_FLEX_ARRAY +# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define GIT_FLEX_ARRAY /* empty */ +# elif defined(__GNUC__) +# if (__GNUC__ >= 3) +# define GIT_FLEX_ARRAY /* empty */ +# else +# define GIT_FLEX_ARRAY 0 /* older GNU extension */ +# endif +# endif + +/* Default to safer but a bit wasteful traditional style */ +# ifndef GIT_FLEX_ARRAY +# define GIT_FLEX_ARRAY 1 +# endif +#endif + +#if defined(__GNUC__) +# define GIT_ALIGN(x,size) x __attribute__ ((aligned(size))) +#elif defined(_MSC_VER) +# define GIT_ALIGN(x,size) __declspec(align(size)) x +#else +# define GIT_ALIGN(x,size) x +#endif + +#if defined(__GNUC__) +# define GIT_UNUSED(x) \ + do { \ + __typeof__(x) _unused __attribute__((unused)); \ + _unused = (x); \ + } while (0) +#else +# define GIT_UNUSED(x) ((void)(x)) +#endif + +/* Define the printf format specifier to use for size_t output */ +#if defined(_MSC_VER) || defined(__MINGW32__) + +/* Visual Studio 2012 and prior lack PRId64 entirely */ +# ifndef PRId64 +# define PRId64 "I64d" +# endif + +/* The first block is needed to avoid warnings on MingW amd64 */ +# if (SIZE_MAX == ULLONG_MAX) +# define PRIuZ "I64u" +# define PRIxZ "I64x" +# define PRIXZ "I64X" +# define PRIdZ "I64d" +# else +# define PRIuZ "Iu" +# define PRIxZ "Ix" +# define PRIXZ "IX" +# define PRIdZ "Id" +# endif + +#else +# define PRIuZ "zu" +# define PRIxZ "zx" +# define PRIXZ "zX" +# define PRIdZ "zd" +#endif + +/* Microsoft Visual C/C++ */ +#if defined(_MSC_VER) +/* disable "deprecated function" warnings */ +# pragma warning ( disable : 4996 ) +/* disable "conditional expression is constant" level 4 warnings */ +# pragma warning ( disable : 4127 ) +#endif + +#if defined (_MSC_VER) + typedef unsigned char bool; +# ifndef true +# define true 1 +# endif +# ifndef false +# define false 0 +# endif +#else +# include +#endif + +#ifndef va_copy +# ifdef __va_copy +# define va_copy(dst, src) __va_copy(dst, src) +# else +# define va_copy(dst, src) ((dst) = (src)) +# endif +#endif + +#endif diff --git a/src/libgit2/checkout.c b/src/libgit2/checkout.c new file mode 100644 index 000000000..6a4643196 --- /dev/null +++ b/src/libgit2/checkout.c @@ -0,0 +1,2813 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "checkout.h" + +#include "git2/repository.h" +#include "git2/refs.h" +#include "git2/tree.h" +#include "git2/blob.h" +#include "git2/config.h" +#include "git2/diff.h" +#include "git2/submodule.h" +#include "git2/sys/index.h" +#include "git2/sys/filter.h" +#include "git2/merge.h" + +#include "refs.h" +#include "repository.h" +#include "index.h" +#include "filter.h" +#include "blob.h" +#include "diff.h" +#include "diff_generate.h" +#include "pathspec.h" +#include "diff_xdiff.h" +#include "fs_path.h" +#include "attr.h" +#include "pool.h" +#include "strmap.h" +#include "path.h" + +/* See docs/checkout-internals.md for more information */ + +enum { + CHECKOUT_ACTION__NONE = 0, + CHECKOUT_ACTION__REMOVE = 1, + CHECKOUT_ACTION__UPDATE_BLOB = 2, + CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, + CHECKOUT_ACTION__CONFLICT = 8, + CHECKOUT_ACTION__REMOVE_CONFLICT = 16, + CHECKOUT_ACTION__UPDATE_CONFLICT = 32, + CHECKOUT_ACTION__MAX = 32, + CHECKOUT_ACTION__REMOVE_AND_UPDATE = + (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE) +}; + +typedef struct { + git_repository *repo; + git_iterator *target; + git_diff *diff; + git_checkout_options opts; + bool opts_free_baseline; + char *pfx; + git_index *index; + git_pool pool; + git_vector removes; + git_vector remove_conflicts; + git_vector update_conflicts; + git_vector *update_reuc; + git_vector *update_names; + git_str target_path; + size_t target_len; + git_str tmp; + unsigned int strategy; + int can_symlink; + int respect_filemode; + bool reload_submodules; + size_t total_steps; + size_t completed_steps; + git_checkout_perfdata perfdata; + git_strmap *mkdir_map; + git_attr_session attr_session; +} checkout_data; + +typedef struct { + const git_index_entry *ancestor; + const git_index_entry *ours; + const git_index_entry *theirs; + + unsigned int name_collision:1, + directoryfile:1, + one_to_two:1, + binary:1, + submodule:1; +} checkout_conflictdata; + +static int checkout_notify( + checkout_data *data, + git_checkout_notify_t why, + const git_diff_delta *delta, + const git_index_entry *wditem) +{ + git_diff_file wdfile; + const git_diff_file *baseline = NULL, *target = NULL, *workdir = NULL; + const char *path = NULL; + + if (!data->opts.notify_cb || + (why & data->opts.notify_flags) == 0) + return 0; + + if (wditem) { + memset(&wdfile, 0, sizeof(wdfile)); + + git_oid_cpy(&wdfile.id, &wditem->id); + wdfile.path = wditem->path; + wdfile.size = wditem->file_size; + wdfile.flags = GIT_DIFF_FLAG_VALID_ID; + wdfile.mode = wditem->mode; + + workdir = &wdfile; + + path = wditem->path; + } + + if (delta) { + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: + case GIT_DELTA_MODIFIED: + case GIT_DELTA_TYPECHANGE: + default: + baseline = &delta->old_file; + target = &delta->new_file; + break; + case GIT_DELTA_ADDED: + case GIT_DELTA_IGNORED: + case GIT_DELTA_UNTRACKED: + case GIT_DELTA_UNREADABLE: + target = &delta->new_file; + break; + case GIT_DELTA_DELETED: + baseline = &delta->old_file; + break; + } + + path = delta->old_file.path; + } + + { + int error = data->opts.notify_cb( + why, path, baseline, target, workdir, data->opts.notify_payload); + + return git_error_set_after_callback_function( + error, "git_checkout notification"); + } +} + +GIT_INLINE(bool) is_workdir_base_or_new( + const git_oid *workdir_id, + const git_diff_file *baseitem, + const git_diff_file *newitem) +{ + return (git_oid__cmp(&baseitem->id, workdir_id) == 0 || + git_oid__cmp(&newitem->id, workdir_id) == 0); +} + +GIT_INLINE(bool) is_filemode_changed(git_filemode_t a, git_filemode_t b, int respect_filemode) +{ + /* If core.filemode = false, ignore links in the repository and executable bit changes */ + if (!respect_filemode) { + if (a == S_IFLNK) + a = GIT_FILEMODE_BLOB; + if (b == S_IFLNK) + b = GIT_FILEMODE_BLOB; + + a &= ~0111; + b &= ~0111; + } + + return (a != b); +} + +static bool checkout_is_workdir_modified( + checkout_data *data, + const git_diff_file *baseitem, + const git_diff_file *newitem, + const git_index_entry *wditem) +{ + git_oid oid; + const git_index_entry *ie; + + /* handle "modified" submodule */ + if (wditem->mode == GIT_FILEMODE_COMMIT) { + git_submodule *sm; + unsigned int sm_status = 0; + const git_oid *sm_oid = NULL; + bool rval = false; + + if (git_submodule_lookup(&sm, data->repo, wditem->path) < 0) { + git_error_clear(); + return true; + } + + if (git_submodule_status(&sm_status, data->repo, wditem->path, GIT_SUBMODULE_IGNORE_UNSPECIFIED) < 0 || + GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) + rval = true; + else if ((sm_oid = git_submodule_wd_id(sm)) == NULL) + rval = false; + else + rval = (git_oid__cmp(&baseitem->id, sm_oid) != 0); + + git_submodule_free(sm); + return rval; + } + + /* + * Look at the cache to decide if the workdir is modified: if the + * cache contents match the workdir contents, then we do not need + * to examine the working directory directly, instead we can + * examine the cache to see if _it_ has been modified. This allows + * us to avoid touching the disk. + */ + ie = git_index_get_bypath(data->index, wditem->path, 0); + + if (ie != NULL && + !git_index_entry_newer_than_index(ie, data->index) && + git_index_time_eq(&wditem->mtime, &ie->mtime) && + wditem->file_size == ie->file_size && + !is_filemode_changed(wditem->mode, ie->mode, data->respect_filemode)) { + + /* The workdir is modified iff the index entry is modified */ + return !is_workdir_base_or_new(&ie->id, baseitem, newitem) || + is_filemode_changed(baseitem->mode, ie->mode, data->respect_filemode); + } + + /* depending on where base is coming from, we may or may not know + * the actual size of the data, so we can't rely on this shortcut. + */ + if (baseitem->size && wditem->file_size != baseitem->size) + return true; + + /* if the workdir item is a directory, it cannot be a modified file */ + if (S_ISDIR(wditem->mode)) + return false; + + if (is_filemode_changed(baseitem->mode, wditem->mode, data->respect_filemode)) + return true; + + if (git_diff__oid_for_entry(&oid, data->diff, wditem, wditem->mode, NULL) < 0) + return false; + + /* Allow the checkout if the workdir is not modified *or* if the checkout + * target's contents are already in the working directory. + */ + return !is_workdir_base_or_new(&oid, baseitem, newitem); +} + +#define CHECKOUT_ACTION_IF(FLAG,YES,NO) \ + ((data->strategy & GIT_CHECKOUT_##FLAG) ? CHECKOUT_ACTION__##YES : CHECKOUT_ACTION__##NO) + +static int checkout_action_common( + int *action, + checkout_data *data, + const git_diff_delta *delta, + const git_index_entry *wd) +{ + git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) + *action = (*action & ~CHECKOUT_ACTION__REMOVE); + + if ((*action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) { + if (S_ISGITLINK(delta->new_file.mode)) + *action = (*action & ~CHECKOUT_ACTION__UPDATE_BLOB) | + CHECKOUT_ACTION__UPDATE_SUBMODULE; + + /* to "update" a symlink, we must remove the old one first */ + if (delta->new_file.mode == GIT_FILEMODE_LINK && wd != NULL) + *action |= CHECKOUT_ACTION__REMOVE; + + /* if the file is on disk and doesn't match our mode, force update */ + if (wd && + GIT_PERMS_IS_EXEC(wd->mode) != GIT_PERMS_IS_EXEC(delta->new_file.mode)) + *action |= CHECKOUT_ACTION__REMOVE; + + notify = GIT_CHECKOUT_NOTIFY_UPDATED; + } + + if ((*action & CHECKOUT_ACTION__CONFLICT) != 0) + notify = GIT_CHECKOUT_NOTIFY_CONFLICT; + + return checkout_notify(data, notify, delta, wd); +} + +static int checkout_action_no_wd( + int *action, + checkout_data *data, + const git_diff_delta *delta) +{ + int error = 0; + + *action = CHECKOUT_ACTION__NONE; + + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: /* case 12 */ + error = checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL); + if (error) + return error; + *action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, NONE); + break; + case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */ + *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + break; + case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */ + *action = CHECKOUT_ACTION_IF(RECREATE_MISSING, UPDATE_BLOB, CONFLICT); + break; + case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/ + if (delta->new_file.mode == GIT_FILEMODE_TREE) + *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + break; + case GIT_DELTA_DELETED: /* case 8 or 25 */ + *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE); + break; + default: /* impossible */ + break; + } + + return checkout_action_common(action, data, delta, NULL); +} + +static int checkout_target_fullpath( + git_str **out, checkout_data *data, const char *path) +{ + git_str_truncate(&data->target_path, data->target_len); + + if (path && git_str_puts(&data->target_path, path) < 0) + return -1; + + if (git_path_validate_str_length(data->repo, &data->target_path) < 0) + return -1; + + *out = &data->target_path; + + return 0; +} + +static bool wd_item_is_removable( + checkout_data *data, const git_index_entry *wd) +{ + git_str *full; + + if (wd->mode != GIT_FILEMODE_TREE) + return true; + + if (checkout_target_fullpath(&full, data, wd->path) < 0) + return false; + + return !full || !git_fs_path_contains(full, DOT_GIT); +} + +static int checkout_queue_remove(checkout_data *data, const char *path) +{ + char *copy = git_pool_strdup(&data->pool, path); + GIT_ERROR_CHECK_ALLOC(copy); + return git_vector_insert(&data->removes, copy); +} + +/* note that this advances the iterator over the wd item */ +static int checkout_action_wd_only( + checkout_data *data, + git_iterator *workdir, + const git_index_entry **wditem, + git_vector *pathspec) +{ + int error = 0; + bool remove = false; + git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; + const git_index_entry *wd = *wditem; + + if (!git_pathspec__match( + pathspec, wd->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) { + + if (wd->mode == GIT_FILEMODE_TREE) + return git_iterator_advance_into(wditem, workdir); + else + return git_iterator_advance(wditem, workdir); + } + + /* check if item is tracked in the index but not in the checkout diff */ + if (data->index != NULL) { + size_t pos; + + error = git_index__find_pos( + &pos, data->index, wd->path, 0, GIT_INDEX_STAGE_ANY); + + if (wd->mode != GIT_FILEMODE_TREE) { + if (!error) { /* found by git_index__find_pos call */ + notify = GIT_CHECKOUT_NOTIFY_DIRTY; + remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0); + } else if (error != GIT_ENOTFOUND) + return error; + else + error = 0; /* git_index__find_pos does not set error msg */ + } else { + /* for tree entries, we have to see if there are any index + * entries that are contained inside that tree + */ + const git_index_entry *e = git_index_get_byindex(data->index, pos); + + if (e != NULL && data->diff->pfxcomp(e->path, wd->path) == 0) + return git_iterator_advance_into(wditem, workdir); + } + } + + if (notify != GIT_CHECKOUT_NOTIFY_NONE) { + /* if we found something in the index, notify and advance */ + if ((error = checkout_notify(data, notify, NULL, wd)) != 0) + return error; + + if (remove && wd_item_is_removable(data, wd)) + error = checkout_queue_remove(data, wd->path); + + if (!error) + error = git_iterator_advance(wditem, workdir); + } else { + /* untracked or ignored - can't know which until we advance through */ + bool over = false, removable = wd_item_is_removable(data, wd); + git_iterator_status_t untracked_state; + + /* copy the entry for issuing notification callback later */ + git_index_entry saved_wd = *wd; + git_str_sets(&data->tmp, wd->path); + saved_wd.path = data->tmp.ptr; + + error = git_iterator_advance_over( + wditem, &untracked_state, workdir); + if (error == GIT_ITEROVER) + over = true; + else if (error < 0) + return error; + + if (untracked_state == GIT_ITERATOR_STATUS_IGNORED) { + notify = GIT_CHECKOUT_NOTIFY_IGNORED; + remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0); + } else { + notify = GIT_CHECKOUT_NOTIFY_UNTRACKED; + remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0); + } + + if ((error = checkout_notify(data, notify, NULL, &saved_wd)) != 0) + return error; + + if (remove && removable) + error = checkout_queue_remove(data, saved_wd.path); + + if (!error && over) /* restore ITEROVER if needed */ + error = GIT_ITEROVER; + } + + return error; +} + +static bool submodule_is_config_only( + checkout_data *data, + const char *path) +{ + git_submodule *sm = NULL; + unsigned int sm_loc = 0; + bool rval = false; + + if (git_submodule_lookup(&sm, data->repo, path) < 0) + return true; + + if (git_submodule_location(&sm_loc, sm) < 0 || + sm_loc == GIT_SUBMODULE_STATUS_IN_CONFIG) + rval = true; + + git_submodule_free(sm); + + return rval; +} + +static bool checkout_is_empty_dir(checkout_data *data, const char *path) +{ + git_str *fullpath; + + if (checkout_target_fullpath(&fullpath, data, path) < 0) + return false; + + return git_fs_path_is_empty_dir(fullpath->ptr); +} + +static int checkout_action_with_wd( + int *action, + checkout_data *data, + const git_diff_delta *delta, + git_iterator *workdir, + const git_index_entry *wd) +{ + *action = CHECKOUT_ACTION__NONE; + + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */ + if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) { + GIT_ERROR_CHECK_ERROR( + checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) ); + *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE); + } + break; + case GIT_DELTA_ADDED: /* case 3, 4 or 6 */ + if (git_iterator_current_is_ignored(workdir)) + *action = CHECKOUT_ACTION_IF(DONT_OVERWRITE_IGNORED, CONFLICT, UPDATE_BLOB); + else + *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT); + break; + case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */ + if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); + else + *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE); + break; + case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */ + if (wd->mode != GIT_FILEMODE_COMMIT && + checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) + *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT); + else + *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + break; + case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */ + if (delta->old_file.mode == GIT_FILEMODE_TREE) { + if (wd->mode == GIT_FILEMODE_TREE) + /* either deleting items in old tree will delete the wd dir, + * or we'll get a conflict when we attempt blob update... + */ + *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + else if (wd->mode == GIT_FILEMODE_COMMIT) { + /* workdir is possibly a "phantom" submodule - treat as a + * tree if the only submodule info came from the config + */ + if (submodule_is_config_only(data, wd->path)) + *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + else + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); + } else + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); + } + else if (checkout_is_workdir_modified(data, &delta->old_file, &delta->new_file, wd)) + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); + else + *action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE); + + /* don't update if the typechange is to a tree */ + if (delta->new_file.mode == GIT_FILEMODE_TREE) + *action = (*action & ~CHECKOUT_ACTION__UPDATE_BLOB); + break; + default: /* impossible */ + break; + } + + return checkout_action_common(action, data, delta, wd); +} + +static int checkout_action_with_wd_blocker( + int *action, + checkout_data *data, + const git_diff_delta *delta, + const git_index_entry *wd) +{ + *action = CHECKOUT_ACTION__NONE; + + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: + /* should show delta as dirty / deleted */ + GIT_ERROR_CHECK_ERROR( + checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) ); + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE); + break; + case GIT_DELTA_ADDED: + case GIT_DELTA_MODIFIED: + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); + break; + case GIT_DELTA_DELETED: + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT); + break; + case GIT_DELTA_TYPECHANGE: + /* not 100% certain about this... */ + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); + break; + default: /* impossible */ + break; + } + + return checkout_action_common(action, data, delta, wd); +} + +static int checkout_action_with_wd_dir( + int *action, + checkout_data *data, + const git_diff_delta *delta, + git_iterator *workdir, + const git_index_entry *wd) +{ + *action = CHECKOUT_ACTION__NONE; + + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */ + GIT_ERROR_CHECK_ERROR( + checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL)); + GIT_ERROR_CHECK_ERROR( + checkout_notify(data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd)); + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE); + break; + case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */ + case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */ + if (delta->old_file.mode == GIT_FILEMODE_COMMIT) + /* expected submodule (and maybe found one) */; + else if (delta->new_file.mode != GIT_FILEMODE_TREE) + *action = git_iterator_current_is_ignored(workdir) ? + CHECKOUT_ACTION_IF(DONT_OVERWRITE_IGNORED, CONFLICT, REMOVE_AND_UPDATE) : + CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); + break; + case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */ + if (delta->old_file.mode != GIT_FILEMODE_TREE) + GIT_ERROR_CHECK_ERROR( + checkout_notify(data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd)); + break; + case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */ + if (delta->old_file.mode == GIT_FILEMODE_TREE) { + /* For typechange from dir, remove dir and add blob, but it is + * not safe to remove dir if it contains modified files. + * However, safely removing child files will remove the parent + * directory if is it left empty, so we can defer removing the + * dir and it will succeed if no children are left. + */ + *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); + } + else if (delta->new_file.mode != GIT_FILEMODE_TREE) + /* For typechange to dir, dir is already created so no action */ + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT); + break; + default: /* impossible */ + break; + } + + return checkout_action_common(action, data, delta, wd); +} + +static int checkout_action_with_wd_dir_empty( + int *action, + checkout_data *data, + const git_diff_delta *delta) +{ + int error = checkout_action_no_wd(action, data, delta); + + /* We can always safely remove an empty directory. */ + if (error == 0 && *action != CHECKOUT_ACTION__NONE) + *action |= CHECKOUT_ACTION__REMOVE; + + return error; +} + +static int checkout_action( + int *action, + checkout_data *data, + git_diff_delta *delta, + git_iterator *workdir, + const git_index_entry **wditem, + git_vector *pathspec) +{ + int cmp = -1, error; + int (*strcomp)(const char *, const char *) = data->diff->strcomp; + int (*pfxcomp)(const char *str, const char *pfx) = data->diff->pfxcomp; + int (*advance)(const git_index_entry **, git_iterator *) = NULL; + + /* move workdir iterator to follow along with deltas */ + + while (1) { + const git_index_entry *wd = *wditem; + + if (!wd) + return checkout_action_no_wd(action, data, delta); + + cmp = strcomp(wd->path, delta->old_file.path); + + /* 1. wd before delta ("a/a" before "a/b") + * 2. wd prefixes delta & should expand ("a/" before "a/b") + * 3. wd prefixes delta & cannot expand ("a/b" before "a/b/c") + * 4. wd equals delta ("a/b" and "a/b") + * 5. wd after delta & delta prefixes wd ("a/b/c" after "a/b/" or "a/b") + * 6. wd after delta ("a/c" after "a/b") + */ + + if (cmp < 0) { + cmp = pfxcomp(delta->old_file.path, wd->path); + + if (cmp == 0) { + if (wd->mode == GIT_FILEMODE_TREE) { + /* case 2 - entry prefixed by workdir tree */ + error = git_iterator_advance_into(wditem, workdir); + if (error < 0 && error != GIT_ITEROVER) + goto done; + continue; + } + + /* case 3 maybe - wd contains non-dir where dir expected */ + if (delta->old_file.path[strlen(wd->path)] == '/') { + error = checkout_action_with_wd_blocker( + action, data, delta, wd); + advance = git_iterator_advance; + goto done; + } + } + + /* case 1 - handle wd item (if it matches pathspec) */ + error = checkout_action_wd_only(data, workdir, wditem, pathspec); + if (error && error != GIT_ITEROVER) + goto done; + continue; + } + + if (cmp == 0) { + /* case 4 */ + error = checkout_action_with_wd(action, data, delta, workdir, wd); + advance = git_iterator_advance; + goto done; + } + + cmp = pfxcomp(wd->path, delta->old_file.path); + + if (cmp == 0) { /* case 5 */ + if (wd->path[strlen(delta->old_file.path)] != '/') + return checkout_action_no_wd(action, data, delta); + + if (delta->status == GIT_DELTA_TYPECHANGE) { + if (delta->old_file.mode == GIT_FILEMODE_TREE) { + error = checkout_action_with_wd(action, data, delta, workdir, wd); + advance = git_iterator_advance_into; + goto done; + } + + if (delta->new_file.mode == GIT_FILEMODE_TREE || + delta->new_file.mode == GIT_FILEMODE_COMMIT || + delta->old_file.mode == GIT_FILEMODE_COMMIT) + { + error = checkout_action_with_wd(action, data, delta, workdir, wd); + advance = git_iterator_advance; + goto done; + } + } + + return checkout_is_empty_dir(data, wd->path) ? + checkout_action_with_wd_dir_empty(action, data, delta) : + checkout_action_with_wd_dir(action, data, delta, workdir, wd); + } + + /* case 6 - wd is after delta */ + return checkout_action_no_wd(action, data, delta); + } + +done: + if (!error && advance != NULL && + (error = advance(wditem, workdir)) < 0) { + *wditem = NULL; + if (error == GIT_ITEROVER) + error = 0; + } + + return error; +} + +static int checkout_remaining_wd_items( + checkout_data *data, + git_iterator *workdir, + const git_index_entry *wd, + git_vector *spec) +{ + int error = 0; + + while (wd && !error) + error = checkout_action_wd_only(data, workdir, &wd, spec); + + if (error == GIT_ITEROVER) + error = 0; + + return error; +} + +GIT_INLINE(int) checkout_idxentry_cmp( + const git_index_entry *a, + const git_index_entry *b) +{ + if (!a && !b) + return 0; + else if (!a && b) + return -1; + else if(a && !b) + return 1; + else + return strcmp(a->path, b->path); +} + +static int checkout_conflictdata_cmp(const void *a, const void *b) +{ + const checkout_conflictdata *ca = a; + const checkout_conflictdata *cb = b; + int diff; + + if ((diff = checkout_idxentry_cmp(ca->ancestor, cb->ancestor)) == 0 && + (diff = checkout_idxentry_cmp(ca->ours, cb->theirs)) == 0) + diff = checkout_idxentry_cmp(ca->theirs, cb->theirs); + + return diff; +} + +static int checkout_conflictdata_empty( + const git_vector *conflicts, size_t idx, void *payload) +{ + checkout_conflictdata *conflict; + + GIT_UNUSED(payload); + + if ((conflict = git_vector_get(conflicts, idx)) == NULL) + return -1; + + if (conflict->ancestor || conflict->ours || conflict->theirs) + return 0; + + git__free(conflict); + return 1; +} + +GIT_INLINE(bool) conflict_pathspec_match( + checkout_data *data, + git_iterator *workdir, + git_vector *pathspec, + const git_index_entry *ancestor, + const git_index_entry *ours, + const git_index_entry *theirs) +{ + /* if the pathspec matches ours *or* theirs, proceed */ + if (ours && git_pathspec__match(pathspec, ours->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) + return true; + + if (theirs && git_pathspec__match(pathspec, theirs->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) + return true; + + if (ancestor && git_pathspec__match(pathspec, ancestor->path, + (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, + git_iterator_ignore_case(workdir), NULL, NULL)) + return true; + + return false; +} + +GIT_INLINE(int) checkout_conflict_detect_submodule(checkout_conflictdata *conflict) +{ + conflict->submodule = ((conflict->ancestor && S_ISGITLINK(conflict->ancestor->mode)) || + (conflict->ours && S_ISGITLINK(conflict->ours->mode)) || + (conflict->theirs && S_ISGITLINK(conflict->theirs->mode))); + return 0; +} + +GIT_INLINE(int) checkout_conflict_detect_binary(git_repository *repo, checkout_conflictdata *conflict) +{ + git_blob *ancestor_blob = NULL, *our_blob = NULL, *their_blob = NULL; + int error = 0; + + if (conflict->submodule) + return 0; + + if (conflict->ancestor) { + if ((error = git_blob_lookup(&ancestor_blob, repo, &conflict->ancestor->id)) < 0) + goto done; + + conflict->binary = git_blob_is_binary(ancestor_blob); + } + + if (!conflict->binary && conflict->ours) { + if ((error = git_blob_lookup(&our_blob, repo, &conflict->ours->id)) < 0) + goto done; + + conflict->binary = git_blob_is_binary(our_blob); + } + + if (!conflict->binary && conflict->theirs) { + if ((error = git_blob_lookup(&their_blob, repo, &conflict->theirs->id)) < 0) + goto done; + + conflict->binary = git_blob_is_binary(their_blob); + } + +done: + git_blob_free(ancestor_blob); + git_blob_free(our_blob); + git_blob_free(their_blob); + + return error; +} + +static int checkout_conflict_append_update( + const git_index_entry *ancestor, + const git_index_entry *ours, + const git_index_entry *theirs, + void *payload) +{ + checkout_data *data = payload; + checkout_conflictdata *conflict; + int error; + + conflict = git__calloc(1, sizeof(checkout_conflictdata)); + GIT_ERROR_CHECK_ALLOC(conflict); + + conflict->ancestor = ancestor; + conflict->ours = ours; + conflict->theirs = theirs; + + if ((error = checkout_conflict_detect_submodule(conflict)) < 0 || + (error = checkout_conflict_detect_binary(data->repo, conflict)) < 0) + { + git__free(conflict); + return error; + } + + if (git_vector_insert(&data->update_conflicts, conflict)) + return -1; + + return 0; +} + +static int checkout_conflicts_foreach( + checkout_data *data, + git_index *index, + git_iterator *workdir, + git_vector *pathspec, + int (*cb)(const git_index_entry *, const git_index_entry *, const git_index_entry *, void *), + void *payload) +{ + git_index_conflict_iterator *iterator = NULL; + const git_index_entry *ancestor, *ours, *theirs; + int error = 0; + + if ((error = git_index_conflict_iterator_new(&iterator, index)) < 0) + goto done; + + /* Collect the conflicts */ + while ((error = git_index_conflict_next(&ancestor, &ours, &theirs, iterator)) == 0) { + if (!conflict_pathspec_match(data, workdir, pathspec, ancestor, ours, theirs)) + continue; + + if ((error = cb(ancestor, ours, theirs, payload)) < 0) + goto done; + } + + if (error == GIT_ITEROVER) + error = 0; + +done: + git_index_conflict_iterator_free(iterator); + + return error; +} + +static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, git_vector *pathspec) +{ + git_index *index; + + /* Only write conflicts from sources that have them: indexes. */ + if ((index = git_iterator_index(data->target)) == NULL) + return 0; + + data->update_conflicts._cmp = checkout_conflictdata_cmp; + + if (checkout_conflicts_foreach(data, index, workdir, pathspec, checkout_conflict_append_update, data) < 0) + return -1; + + /* Collect the REUC and NAME entries */ + data->update_reuc = &index->reuc; + data->update_names = &index->names; + + return 0; +} + +GIT_INLINE(int) checkout_conflicts_cmp_entry( + const char *path, + const git_index_entry *entry) +{ + return strcmp((const char *)path, entry->path); +} + +static int checkout_conflicts_cmp_ancestor(const void *p, const void *c) +{ + const char *path = p; + const checkout_conflictdata *conflict = c; + + if (!conflict->ancestor) + return 1; + + return checkout_conflicts_cmp_entry(path, conflict->ancestor); +} + +static checkout_conflictdata *checkout_conflicts_search_ancestor( + checkout_data *data, + const char *path) +{ + size_t pos; + + if (git_vector_bsearch2(&pos, &data->update_conflicts, checkout_conflicts_cmp_ancestor, path) < 0) + return NULL; + + return git_vector_get(&data->update_conflicts, pos); +} + +static checkout_conflictdata *checkout_conflicts_search_branch( + checkout_data *data, + const char *path) +{ + checkout_conflictdata *conflict; + size_t i; + + git_vector_foreach(&data->update_conflicts, i, conflict) { + int cmp = -1; + + if (conflict->ancestor) + break; + + if (conflict->ours) + cmp = checkout_conflicts_cmp_entry(path, conflict->ours); + else if (conflict->theirs) + cmp = checkout_conflicts_cmp_entry(path, conflict->theirs); + + if (cmp == 0) + return conflict; + } + + return NULL; +} + +static int checkout_conflicts_load_byname_entry( + checkout_conflictdata **ancestor_out, + checkout_conflictdata **ours_out, + checkout_conflictdata **theirs_out, + checkout_data *data, + const git_index_name_entry *name_entry) +{ + checkout_conflictdata *ancestor, *ours = NULL, *theirs = NULL; + int error = 0; + + *ancestor_out = NULL; + *ours_out = NULL; + *theirs_out = NULL; + + if (!name_entry->ancestor) { + git_error_set(GIT_ERROR_INDEX, "a NAME entry exists without an ancestor"); + error = -1; + goto done; + } + + if (!name_entry->ours && !name_entry->theirs) { + git_error_set(GIT_ERROR_INDEX, "a NAME entry exists without an ours or theirs"); + error = -1; + goto done; + } + + if ((ancestor = checkout_conflicts_search_ancestor(data, + name_entry->ancestor)) == NULL) { + git_error_set(GIT_ERROR_INDEX, + "a NAME entry referenced ancestor entry '%s' which does not exist in the main index", + name_entry->ancestor); + error = -1; + goto done; + } + + if (name_entry->ours) { + if (strcmp(name_entry->ancestor, name_entry->ours) == 0) + ours = ancestor; + else if ((ours = checkout_conflicts_search_branch(data, name_entry->ours)) == NULL || + ours->ours == NULL) { + git_error_set(GIT_ERROR_INDEX, + "a NAME entry referenced our entry '%s' which does not exist in the main index", + name_entry->ours); + error = -1; + goto done; + } + } + + if (name_entry->theirs) { + if (strcmp(name_entry->ancestor, name_entry->theirs) == 0) + theirs = ancestor; + else if (name_entry->ours && strcmp(name_entry->ours, name_entry->theirs) == 0) + theirs = ours; + else if ((theirs = checkout_conflicts_search_branch(data, name_entry->theirs)) == NULL || + theirs->theirs == NULL) { + git_error_set(GIT_ERROR_INDEX, + "a NAME entry referenced their entry '%s' which does not exist in the main index", + name_entry->theirs); + error = -1; + goto done; + } + } + + *ancestor_out = ancestor; + *ours_out = ours; + *theirs_out = theirs; + +done: + return error; +} + +static int checkout_conflicts_coalesce_renames( + checkout_data *data) +{ + git_index *index; + const git_index_name_entry *name_entry; + checkout_conflictdata *ancestor_conflict, *our_conflict, *their_conflict; + size_t i, names; + int error = 0; + + if ((index = git_iterator_index(data->target)) == NULL) + return 0; + + /* Juggle entries based on renames */ + names = git_index_name_entrycount(index); + + for (i = 0; i < names; i++) { + name_entry = git_index_name_get_byindex(index, i); + + if ((error = checkout_conflicts_load_byname_entry( + &ancestor_conflict, &our_conflict, &their_conflict, + data, name_entry)) < 0) + goto done; + + if (our_conflict && our_conflict != ancestor_conflict) { + ancestor_conflict->ours = our_conflict->ours; + our_conflict->ours = NULL; + + if (our_conflict->theirs) + our_conflict->name_collision = 1; + + if (our_conflict->name_collision) + ancestor_conflict->name_collision = 1; + } + + if (their_conflict && their_conflict != ancestor_conflict) { + ancestor_conflict->theirs = their_conflict->theirs; + their_conflict->theirs = NULL; + + if (their_conflict->ours) + their_conflict->name_collision = 1; + + if (their_conflict->name_collision) + ancestor_conflict->name_collision = 1; + } + + if (our_conflict && our_conflict != ancestor_conflict && + their_conflict && their_conflict != ancestor_conflict) + ancestor_conflict->one_to_two = 1; + } + + git_vector_remove_matching( + &data->update_conflicts, checkout_conflictdata_empty, NULL); + +done: + return error; +} + +static int checkout_conflicts_mark_directoryfile( + checkout_data *data) +{ + git_index *index; + checkout_conflictdata *conflict; + const git_index_entry *entry; + size_t i, j, len; + const char *path; + int prefixed, error = 0; + + if ((index = git_iterator_index(data->target)) == NULL) + return 0; + + len = git_index_entrycount(index); + + /* Find d/f conflicts */ + git_vector_foreach(&data->update_conflicts, i, conflict) { + if ((conflict->ours && conflict->theirs) || + (!conflict->ours && !conflict->theirs)) + continue; + + path = conflict->ours ? + conflict->ours->path : conflict->theirs->path; + + if ((error = git_index_find(&j, index, path)) < 0) { + if (error == GIT_ENOTFOUND) + git_error_set(GIT_ERROR_INDEX, + "index inconsistency, could not find entry for expected conflict '%s'", path); + + goto done; + } + + for (; j < len; j++) { + if ((entry = git_index_get_byindex(index, j)) == NULL) { + git_error_set(GIT_ERROR_INDEX, + "index inconsistency, truncated index while loading expected conflict '%s'", path); + error = -1; + goto done; + } + + prefixed = git_fs_path_equal_or_prefixed(path, entry->path, NULL); + + if (prefixed == GIT_FS_PATH_EQUAL) + continue; + + if (prefixed == GIT_FS_PATH_PREFIX) + conflict->directoryfile = 1; + + break; + } + } + +done: + return error; +} + +static int checkout_get_update_conflicts( + checkout_data *data, + git_iterator *workdir, + git_vector *pathspec) +{ + int error = 0; + + if (data->strategy & GIT_CHECKOUT_SKIP_UNMERGED) + return 0; + + if ((error = checkout_conflicts_load(data, workdir, pathspec)) < 0 || + (error = checkout_conflicts_coalesce_renames(data)) < 0 || + (error = checkout_conflicts_mark_directoryfile(data)) < 0) + goto done; + +done: + return error; +} + +static int checkout_conflict_append_remove( + const git_index_entry *ancestor, + const git_index_entry *ours, + const git_index_entry *theirs, + void *payload) +{ + checkout_data *data = payload; + const char *name; + + GIT_ASSERT_ARG(ancestor || ours || theirs); + + if (ancestor) + name = git__strdup(ancestor->path); + else if (ours) + name = git__strdup(ours->path); + else if (theirs) + name = git__strdup(theirs->path); + else + abort(); + + GIT_ERROR_CHECK_ALLOC(name); + + return git_vector_insert(&data->remove_conflicts, (char *)name); +} + +static int checkout_get_remove_conflicts( + checkout_data *data, + git_iterator *workdir, + git_vector *pathspec) +{ + if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0) + return 0; + + return checkout_conflicts_foreach(data, data->index, workdir, pathspec, checkout_conflict_append_remove, data); +} + +static int checkout_verify_paths( + git_repository *repo, + int action, + git_diff_delta *delta) +{ + unsigned int flags = GIT_PATH_REJECT_WORKDIR_DEFAULTS; + + if (action & CHECKOUT_ACTION__REMOVE) { + if (!git_path_is_valid(repo, delta->old_file.path, delta->old_file.mode, flags)) { + git_error_set(GIT_ERROR_CHECKOUT, "cannot remove invalid path '%s'", delta->old_file.path); + return -1; + } + } + + if (action & ~CHECKOUT_ACTION__REMOVE) { + if (!git_path_is_valid(repo, delta->new_file.path, delta->new_file.mode, flags)) { + git_error_set(GIT_ERROR_CHECKOUT, "cannot checkout to invalid path '%s'", delta->new_file.path); + return -1; + } + } + + return 0; +} + +static int checkout_get_actions( + uint32_t **actions_ptr, + size_t **counts_ptr, + checkout_data *data, + git_iterator *workdir) +{ + int error = 0, act; + const git_index_entry *wditem; + git_vector pathspec = GIT_VECTOR_INIT, *deltas; + git_pool pathpool; + git_diff_delta *delta; + size_t i, *counts = NULL; + uint32_t *actions = NULL; + + if (git_pool_init(&pathpool, 1) < 0) + return -1; + + if (data->opts.paths.count > 0 && + git_pathspec__vinit(&pathspec, &data->opts.paths, &pathpool) < 0) + return -1; + + if ((error = git_iterator_current(&wditem, workdir)) < 0 && + error != GIT_ITEROVER) + goto fail; + + deltas = &data->diff->deltas; + + *counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t)); + *actions_ptr = actions = git__calloc( + deltas->length ? deltas->length : 1, sizeof(uint32_t)); + if (!counts || !actions) { + error = -1; + goto fail; + } + + git_vector_foreach(deltas, i, delta) { + if ((error = checkout_action(&act, data, delta, workdir, &wditem, &pathspec)) == 0) + error = checkout_verify_paths(data->repo, act, delta); + + if (error != 0) + goto fail; + + actions[i] = act; + + if (act & CHECKOUT_ACTION__REMOVE) + counts[CHECKOUT_ACTION__REMOVE]++; + if (act & CHECKOUT_ACTION__UPDATE_BLOB) + counts[CHECKOUT_ACTION__UPDATE_BLOB]++; + if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE) + counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++; + if (act & CHECKOUT_ACTION__CONFLICT) + counts[CHECKOUT_ACTION__CONFLICT]++; + } + + error = checkout_remaining_wd_items(data, workdir, wditem, &pathspec); + if (error) + goto fail; + + counts[CHECKOUT_ACTION__REMOVE] += data->removes.length; + + if (counts[CHECKOUT_ACTION__CONFLICT] > 0 && + (data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0) { + git_error_set(GIT_ERROR_CHECKOUT, "%"PRIuZ" %s checkout", + counts[CHECKOUT_ACTION__CONFLICT], + counts[CHECKOUT_ACTION__CONFLICT] == 1 ? + "conflict prevents" : "conflicts prevent"); + error = GIT_ECONFLICT; + goto fail; + } + + + if ((error = checkout_get_remove_conflicts(data, workdir, &pathspec)) < 0 || + (error = checkout_get_update_conflicts(data, workdir, &pathspec)) < 0) + goto fail; + + counts[CHECKOUT_ACTION__REMOVE_CONFLICT] = git_vector_length(&data->remove_conflicts); + counts[CHECKOUT_ACTION__UPDATE_CONFLICT] = git_vector_length(&data->update_conflicts); + + git_pathspec__vfree(&pathspec); + git_pool_clear(&pathpool); + + return 0; + +fail: + *counts_ptr = NULL; + git__free(counts); + *actions_ptr = NULL; + git__free(actions); + + git_pathspec__vfree(&pathspec); + git_pool_clear(&pathpool); + + return error; +} + +static bool should_remove_existing(checkout_data *data) +{ + int ignorecase; + + if (git_repository__configmap_lookup(&ignorecase, data->repo, GIT_CONFIGMAP_IGNORECASE) < 0) { + ignorecase = 0; + } + + return (ignorecase && + (data->strategy & GIT_CHECKOUT_DONT_REMOVE_EXISTING) == 0); +} + +#define MKDIR_NORMAL \ + GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR +#define MKDIR_REMOVE_EXISTING \ + MKDIR_NORMAL | GIT_MKDIR_REMOVE_FILES | GIT_MKDIR_REMOVE_SYMLINKS + +static int checkout_mkdir( + checkout_data *data, + const char *path, + const char *base, + mode_t mode, + unsigned int flags) +{ + struct git_futils_mkdir_options mkdir_opts = {0}; + int error; + + mkdir_opts.dir_map = data->mkdir_map; + mkdir_opts.pool = &data->pool; + + error = git_futils_mkdir_relative( + path, base, mode, flags, &mkdir_opts); + + data->perfdata.mkdir_calls += mkdir_opts.perfdata.mkdir_calls; + data->perfdata.stat_calls += mkdir_opts.perfdata.stat_calls; + data->perfdata.chmod_calls += mkdir_opts.perfdata.chmod_calls; + + return error; +} + +static int mkpath2file( + checkout_data *data, const char *path, unsigned int mode) +{ + struct stat st; + bool remove_existing = should_remove_existing(data); + unsigned int flags = + (remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL) | + GIT_MKDIR_SKIP_LAST; + int error; + + if ((error = checkout_mkdir( + data, path, data->opts.target_directory, mode, flags)) < 0) + return error; + + if (remove_existing) { + data->perfdata.stat_calls++; + + if (p_lstat(path, &st) == 0) { + + /* Some file, symlink or folder already exists at this name. + * We would have removed it in remove_the_old unless we're on + * a case inensitive filesystem (or the user has asked us not + * to). Remove the similarly named file to write the new. + */ + error = git_futils_rmdir_r(path, NULL, GIT_RMDIR_REMOVE_FILES); + } else if (errno != ENOENT) { + git_error_set(GIT_ERROR_OS, "failed to stat '%s'", path); + return GIT_EEXISTS; + } else { + git_error_clear(); + } + } + + return error; +} + +struct checkout_stream { + git_writestream base; + const char *path; + int fd; + int open; +}; + +static int checkout_stream_write( + git_writestream *s, const char *buffer, size_t len) +{ + struct checkout_stream *stream = (struct checkout_stream *)s; + int ret; + + if ((ret = p_write(stream->fd, buffer, len)) < 0) + git_error_set(GIT_ERROR_OS, "could not write to '%s'", stream->path); + + return ret; +} + +static int checkout_stream_close(git_writestream *s) +{ + struct checkout_stream *stream = (struct checkout_stream *)s; + + GIT_ASSERT_ARG(stream); + GIT_ASSERT_ARG(stream->open); + + stream->open = 0; + return p_close(stream->fd); +} + +static void checkout_stream_free(git_writestream *s) +{ + GIT_UNUSED(s); +} + +static int blob_content_to_file( + checkout_data *data, + struct stat *st, + git_blob *blob, + const char *path, + const char *hint_path, + mode_t entry_filemode) +{ + int flags = data->opts.file_open_flags; + mode_t file_mode = data->opts.file_mode ? + data->opts.file_mode : entry_filemode; + git_filter_session filter_session = GIT_FILTER_SESSION_INIT; + struct checkout_stream writer; + mode_t mode; + git_filter_list *fl = NULL; + int fd; + int error = 0; + + GIT_ASSERT(hint_path != NULL); + + if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0) + return error; + + if (flags <= 0) + flags = O_CREAT | O_TRUNC | O_WRONLY; + if (!(mode = file_mode)) + mode = GIT_FILEMODE_BLOB; + + if ((fd = p_open(path, flags, mode)) < 0) { + git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path); + return fd; + } + + filter_session.attr_session = &data->attr_session; + filter_session.temp_buf = &data->tmp; + + if (!data->opts.disable_filters && + (error = git_filter_list__load( + &fl, data->repo, blob, hint_path, + GIT_FILTER_TO_WORKTREE, &filter_session))) { + p_close(fd); + return error; + } + + /* setup the writer */ + memset(&writer, 0, sizeof(struct checkout_stream)); + writer.base.write = checkout_stream_write; + writer.base.close = checkout_stream_close; + writer.base.free = checkout_stream_free; + writer.path = path; + writer.fd = fd; + writer.open = 1; + + error = git_filter_list_stream_blob(fl, blob, &writer.base); + + GIT_ASSERT(writer.open == 0); + + git_filter_list_free(fl); + + if (error < 0) + return error; + + if (st) { + data->perfdata.stat_calls++; + + if ((error = p_stat(path, st)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to stat '%s'", path); + return error; + } + + st->st_mode = entry_filemode; + } + + return 0; +} + +static int blob_content_to_link( + checkout_data *data, + struct stat *st, + git_blob *blob, + const char *path) +{ + git_str linktarget = GIT_STR_INIT; + int error; + + if ((error = mkpath2file(data, path, data->opts.dir_mode)) < 0) + return error; + + if ((error = git_blob__getbuf(&linktarget, blob)) < 0) + return error; + + if (data->can_symlink) { + if ((error = p_symlink(git_str_cstr(&linktarget), path)) < 0) + git_error_set(GIT_ERROR_OS, "could not create symlink %s", path); + } else { + error = git_futils_fake_symlink(git_str_cstr(&linktarget), path); + } + + if (!error) { + data->perfdata.stat_calls++; + + if ((error = p_lstat(path, st)) < 0) + git_error_set(GIT_ERROR_CHECKOUT, "could not stat symlink %s", path); + + st->st_mode = GIT_FILEMODE_LINK; + } + + git_str_dispose(&linktarget); + + return error; +} + +static int checkout_update_index( + checkout_data *data, + const git_diff_file *file, + struct stat *st) +{ + git_index_entry entry; + + if (!data->index) + return 0; + + memset(&entry, 0, sizeof(entry)); + entry.path = (char *)file->path; /* cast to prevent warning */ + git_index_entry__init_from_stat(&entry, st, true); + git_oid_cpy(&entry.id, &file->id); + + return git_index_add(data->index, &entry); +} + +static int checkout_submodule_update_index( + checkout_data *data, + const git_diff_file *file) +{ + git_str *fullpath; + struct stat st; + + /* update the index unless prevented */ + if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0) + return 0; + + if (checkout_target_fullpath(&fullpath, data, file->path) < 0) + return -1; + + data->perfdata.stat_calls++; + if (p_stat(fullpath->ptr, &st) < 0) { + git_error_set( + GIT_ERROR_CHECKOUT, "could not stat submodule %s\n", file->path); + return GIT_ENOTFOUND; + } + + st.st_mode = GIT_FILEMODE_COMMIT; + + return checkout_update_index(data, file, &st); +} + +static int checkout_submodule( + checkout_data *data, + const git_diff_file *file) +{ + bool remove_existing = should_remove_existing(data); + int error = 0; + + /* Until submodules are supported, UPDATE_ONLY means do nothing here */ + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) + return 0; + + if ((error = checkout_mkdir( + data, + file->path, data->opts.target_directory, data->opts.dir_mode, + remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL)) < 0) + return error; + + if ((error = git_submodule_lookup(NULL, data->repo, file->path)) < 0) { + /* I've observed repos with submodules in the tree that do not + * have a .gitmodules - core Git just makes an empty directory + */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + return checkout_submodule_update_index(data, file); + } + + return error; + } + + /* TODO: Support checkout_strategy options. Two circumstances: + * 1 - submodule already checked out, but we need to move the HEAD + * to the new OID, or + * 2 - submodule not checked out and we should recursively check it out + * + * Checkout will not execute a pull on the submodule, but a clone + * command should probably be able to. Do we need a submodule callback? + */ + + return checkout_submodule_update_index(data, file); +} + +static void report_progress( + checkout_data *data, + const char *path) +{ + if (data->opts.progress_cb) + data->opts.progress_cb( + path, data->completed_steps, data->total_steps, + data->opts.progress_payload); +} + +static int checkout_safe_for_update_only( + checkout_data *data, const char *path, mode_t expected_mode) +{ + struct stat st; + + data->perfdata.stat_calls++; + + if (p_lstat(path, &st) < 0) { + /* if doesn't exist, then no error and no update */ + if (errno == ENOENT || errno == ENOTDIR) + return 0; + + /* otherwise, stat error and no update */ + git_error_set(GIT_ERROR_OS, "failed to stat '%s'", path); + return -1; + } + + /* only safe for update if this is the same type of file */ + if ((st.st_mode & ~0777) == (expected_mode & ~0777)) + return 1; + + return 0; +} + +static int checkout_write_content( + checkout_data *data, + const git_oid *oid, + const char *full_path, + const char *hint_path, + unsigned int mode, + struct stat *st) +{ + int error = 0; + git_blob *blob; + + if ((error = git_blob_lookup(&blob, data->repo, oid)) < 0) + return error; + + if (S_ISLNK(mode)) + error = blob_content_to_link(data, st, blob, full_path); + else + error = blob_content_to_file(data, st, blob, full_path, hint_path, mode); + + git_blob_free(blob); + + /* if we try to create the blob and an existing directory blocks it from + * being written, then there must have been a typechange conflict in a + * parent directory - suppress the error and try to continue. + */ + if ((data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) != 0 && + (error == GIT_ENOTFOUND || error == GIT_EEXISTS)) + { + git_error_clear(); + error = 0; + } + + return error; +} + +static int checkout_blob( + checkout_data *data, + const git_diff_file *file) +{ + git_str *fullpath; + struct stat st; + int error = 0; + + if (checkout_target_fullpath(&fullpath, data, file->path) < 0) + return -1; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { + int rval = checkout_safe_for_update_only( + data, fullpath->ptr, file->mode); + + if (rval <= 0) + return rval; + } + + error = checkout_write_content( + data, &file->id, fullpath->ptr, file->path, file->mode, &st); + + /* update the index unless prevented */ + if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) + error = checkout_update_index(data, file, &st); + + /* update the submodule data if this was a new .gitmodules file */ + if (!error && strcmp(file->path, ".gitmodules") == 0) + data->reload_submodules = true; + + return error; +} + +static int checkout_remove_the_old( + unsigned int *actions, + checkout_data *data) +{ + int error = 0; + git_diff_delta *delta; + const char *str; + size_t i; + git_str *fullpath; + uint32_t flg = GIT_RMDIR_EMPTY_PARENTS | + GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS; + + if (data->opts.checkout_strategy & GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES) + flg |= GIT_RMDIR_SKIP_NONEMPTY; + + if (checkout_target_fullpath(&fullpath, data, NULL) < 0) + return -1; + + git_vector_foreach(&data->diff->deltas, i, delta) { + if (actions[i] & CHECKOUT_ACTION__REMOVE) { + error = git_futils_rmdir_r( + delta->old_file.path, fullpath->ptr, flg); + + if (error < 0) + return error; + + data->completed_steps++; + report_progress(data, delta->old_file.path); + + if ((actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) == 0 && + (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && + data->index != NULL) + { + (void)git_index_remove(data->index, delta->old_file.path, 0); + } + } + } + + git_vector_foreach(&data->removes, i, str) { + error = git_futils_rmdir_r(str, fullpath->ptr, flg); + if (error < 0) + return error; + + data->completed_steps++; + report_progress(data, str); + + if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 && + data->index != NULL) + { + if (str[strlen(str) - 1] == '/') + (void)git_index_remove_directory(data->index, str, 0); + else + (void)git_index_remove(data->index, str, 0); + } + } + + return 0; +} + +static int checkout_create_the_new( + unsigned int *actions, + checkout_data *data) +{ + int error = 0; + git_diff_delta *delta; + size_t i; + + git_vector_foreach(&data->diff->deltas, i, delta) { + if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB && !S_ISLNK(delta->new_file.mode)) { + if ((error = checkout_blob(data, &delta->new_file)) < 0) + return error; + data->completed_steps++; + report_progress(data, delta->new_file.path); + } + } + + git_vector_foreach(&data->diff->deltas, i, delta) { + if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB && S_ISLNK(delta->new_file.mode)) { + if ((error = checkout_blob(data, &delta->new_file)) < 0) + return error; + data->completed_steps++; + report_progress(data, delta->new_file.path); + } + } + + return 0; +} + +static int checkout_create_submodules( + unsigned int *actions, + checkout_data *data) +{ + git_diff_delta *delta; + size_t i; + + git_vector_foreach(&data->diff->deltas, i, delta) { + if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) { + int error = checkout_submodule(data, &delta->new_file); + if (error < 0) + return error; + + data->completed_steps++; + report_progress(data, delta->new_file.path); + } + } + + return 0; +} + +static int checkout_lookup_head_tree(git_tree **out, git_repository *repo) +{ + int error = 0; + git_reference *ref = NULL; + git_object *head; + + if (!(error = git_repository_head(&ref, repo)) && + !(error = git_reference_peel(&head, ref, GIT_OBJECT_TREE))) + *out = (git_tree *)head; + + git_reference_free(ref); + + return error; +} + + +static int conflict_entry_name( + git_str *out, + const char *side_name, + const char *filename) +{ + if (git_str_puts(out, side_name) < 0 || + git_str_putc(out, ':') < 0 || + git_str_puts(out, filename) < 0) + return -1; + + return 0; +} + +static int checkout_path_suffixed(git_str *path, const char *suffix) +{ + size_t path_len; + int i = 0, error = 0; + + if ((error = git_str_putc(path, '~')) < 0 || (error = git_str_puts(path, suffix)) < 0) + return -1; + + path_len = git_str_len(path); + + while (git_fs_path_exists(git_str_cstr(path)) && i < INT_MAX) { + git_str_truncate(path, path_len); + + if ((error = git_str_putc(path, '_')) < 0 || + (error = git_str_printf(path, "%d", i)) < 0) + return error; + + i++; + } + + if (i == INT_MAX) { + git_str_truncate(path, path_len); + + git_error_set(GIT_ERROR_CHECKOUT, "could not write '%s': working directory file exists", path->ptr); + return GIT_EEXISTS; + } + + return 0; +} + +static int checkout_write_entry( + checkout_data *data, + checkout_conflictdata *conflict, + const git_index_entry *side) +{ + const char *hint_path = NULL, *suffix; + git_str *fullpath; + struct stat st; + int error; + + GIT_ASSERT(side == conflict->ours || side == conflict->theirs); + + if (checkout_target_fullpath(&fullpath, data, side->path) < 0) + return -1; + + if ((conflict->name_collision || conflict->directoryfile) && + (data->strategy & GIT_CHECKOUT_USE_OURS) == 0 && + (data->strategy & GIT_CHECKOUT_USE_THEIRS) == 0) { + + if (side == conflict->ours) + suffix = data->opts.our_label ? data->opts.our_label : + "ours"; + else + suffix = data->opts.their_label ? data->opts.their_label : + "theirs"; + + if (checkout_path_suffixed(fullpath, suffix) < 0) + return -1; + } + + hint_path = side->path; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && + (error = checkout_safe_for_update_only(data, fullpath->ptr, side->mode)) <= 0) + return error; + + if (!S_ISGITLINK(side->mode)) + return checkout_write_content(data, + &side->id, fullpath->ptr, hint_path, side->mode, &st); + + return 0; +} + +static int checkout_write_entries( + checkout_data *data, + checkout_conflictdata *conflict) +{ + int error = 0; + + if ((error = checkout_write_entry(data, conflict, conflict->ours)) >= 0) + error = checkout_write_entry(data, conflict, conflict->theirs); + + return error; +} + +static int checkout_merge_path( + git_str *out, + checkout_data *data, + checkout_conflictdata *conflict, + git_merge_file_result *result) +{ + const char *our_label_raw, *their_label_raw, *suffix; + int error = 0; + + if ((error = git_str_joinpath(out, data->opts.target_directory, result->path)) < 0 || + (error = git_path_validate_str_length(data->repo, out)) < 0) + return error; + + /* Most conflicts simply use the filename in the index */ + if (!conflict->name_collision) + return 0; + + /* Rename 2->1 conflicts need the branch name appended */ + our_label_raw = data->opts.our_label ? data->opts.our_label : "ours"; + their_label_raw = data->opts.their_label ? data->opts.their_label : "theirs"; + suffix = strcmp(result->path, conflict->ours->path) == 0 ? our_label_raw : their_label_raw; + + if ((error = checkout_path_suffixed(out, suffix)) < 0) + return error; + + return 0; +} + +static int checkout_write_merge( + checkout_data *data, + checkout_conflictdata *conflict) +{ + git_str our_label = GIT_STR_INIT, their_label = GIT_STR_INIT, + path_suffixed = GIT_STR_INIT, path_workdir = GIT_STR_INIT, + in_data = GIT_STR_INIT, out_data = GIT_STR_INIT; + git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + git_filebuf output = GIT_FILEBUF_INIT; + git_filter_list *fl = NULL; + git_filter_session filter_session = GIT_FILTER_SESSION_INIT; + int error = 0; + + if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_DIFF3) + opts.flags |= GIT_MERGE_FILE_STYLE_DIFF3; + + if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3) + opts.flags |= GIT_MERGE_FILE_STYLE_ZDIFF3; + + opts.ancestor_label = data->opts.ancestor_label ? + data->opts.ancestor_label : "ancestor"; + opts.our_label = data->opts.our_label ? + data->opts.our_label : "ours"; + opts.their_label = data->opts.their_label ? + data->opts.their_label : "theirs"; + + /* If all the paths are identical, decorate the diff3 file with the branch + * names. Otherwise, append branch_name:path. + */ + if (conflict->ours && conflict->theirs && + strcmp(conflict->ours->path, conflict->theirs->path) != 0) { + + if ((error = conflict_entry_name( + &our_label, opts.our_label, conflict->ours->path)) < 0 || + (error = conflict_entry_name( + &their_label, opts.their_label, conflict->theirs->path)) < 0) + goto done; + + opts.our_label = git_str_cstr(&our_label); + opts.their_label = git_str_cstr(&their_label); + } + + if ((error = git_merge_file_from_index(&result, data->repo, + conflict->ancestor, conflict->ours, conflict->theirs, &opts)) < 0) + goto done; + + if (result.path == NULL || result.mode == 0) { + git_error_set(GIT_ERROR_CHECKOUT, "could not merge contents of file"); + error = GIT_ECONFLICT; + goto done; + } + + if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0) + goto done; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && + (error = checkout_safe_for_update_only(data, git_str_cstr(&path_workdir), result.mode)) <= 0) + goto done; + + if (!data->opts.disable_filters) { + in_data.ptr = (char *)result.ptr; + in_data.size = result.len; + + filter_session.attr_session = &data->attr_session; + filter_session.temp_buf = &data->tmp; + + if ((error = git_filter_list__load( + &fl, data->repo, NULL, result.path, + GIT_FILTER_TO_WORKTREE, &filter_session)) < 0 || + (error = git_filter_list__convert_buf(&out_data, fl, &in_data)) < 0) + goto done; + } else { + out_data.ptr = (char *)result.ptr; + out_data.size = result.len; + } + + if ((error = mkpath2file(data, path_workdir.ptr, data->opts.dir_mode)) < 0 || + (error = git_filebuf_open(&output, git_str_cstr(&path_workdir), GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 || + (error = git_filebuf_write(&output, out_data.ptr, out_data.size)) < 0 || + (error = git_filebuf_commit(&output)) < 0) + goto done; + +done: + git_filter_list_free(fl); + + git_str_dispose(&out_data); + git_str_dispose(&our_label); + git_str_dispose(&their_label); + + git_merge_file_result_free(&result); + git_str_dispose(&path_workdir); + git_str_dispose(&path_suffixed); + + return error; +} + +static int checkout_conflict_add( + checkout_data *data, + const git_index_entry *conflict) +{ + int error = git_index_remove(data->index, conflict->path, 0); + + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + return error; + + return git_index_add(data->index, conflict); +} + +static int checkout_conflict_update_index( + checkout_data *data, + checkout_conflictdata *conflict) +{ + int error = 0; + + if (conflict->ancestor) + error = checkout_conflict_add(data, conflict->ancestor); + + if (!error && conflict->ours) + error = checkout_conflict_add(data, conflict->ours); + + if (!error && conflict->theirs) + error = checkout_conflict_add(data, conflict->theirs); + + return error; +} + +static int checkout_create_conflicts(checkout_data *data) +{ + checkout_conflictdata *conflict; + size_t i; + int error = 0; + + git_vector_foreach(&data->update_conflicts, i, conflict) { + + /* Both deleted: nothing to do */ + if (conflict->ours == NULL && conflict->theirs == NULL) + error = 0; + + else if ((data->strategy & GIT_CHECKOUT_USE_OURS) && + conflict->ours) + error = checkout_write_entry(data, conflict, conflict->ours); + else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) && + conflict->theirs) + error = checkout_write_entry(data, conflict, conflict->theirs); + + /* Ignore the other side of name collisions. */ + else if ((data->strategy & GIT_CHECKOUT_USE_OURS) && + !conflict->ours && conflict->name_collision) + error = 0; + else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) && + !conflict->theirs && conflict->name_collision) + error = 0; + + /* For modify/delete, name collisions and d/f conflicts, write + * the file (potentially with the name mangled. + */ + else if (conflict->ours != NULL && conflict->theirs == NULL) + error = checkout_write_entry(data, conflict, conflict->ours); + else if (conflict->ours == NULL && conflict->theirs != NULL) + error = checkout_write_entry(data, conflict, conflict->theirs); + + /* Add/add conflicts and rename 1->2 conflicts, write the + * ours/theirs sides (potentially name mangled). + */ + else if (conflict->one_to_two) + error = checkout_write_entries(data, conflict); + + /* If all sides are links, write the ours side */ + else if (S_ISLNK(conflict->ours->mode) && + S_ISLNK(conflict->theirs->mode)) + error = checkout_write_entry(data, conflict, conflict->ours); + /* Link/file conflicts, write the file side */ + else if (S_ISLNK(conflict->ours->mode)) + error = checkout_write_entry(data, conflict, conflict->theirs); + else if (S_ISLNK(conflict->theirs->mode)) + error = checkout_write_entry(data, conflict, conflict->ours); + + /* If any side is a gitlink, do nothing. */ + else if (conflict->submodule) + error = 0; + + /* If any side is binary, write the ours side */ + else if (conflict->binary) + error = checkout_write_entry(data, conflict, conflict->ours); + + else if (!error) + error = checkout_write_merge(data, conflict); + + /* Update the index extensions (REUC and NAME) if we're checking + * out a different index. (Otherwise just leave them there.) + */ + if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) + error = checkout_conflict_update_index(data, conflict); + + if (error) + break; + + data->completed_steps++; + report_progress(data, + conflict->ours ? conflict->ours->path : + (conflict->theirs ? conflict->theirs->path : conflict->ancestor->path)); + } + + return error; +} + +static int checkout_remove_conflicts(checkout_data *data) +{ + const char *conflict; + size_t i; + + git_vector_foreach(&data->remove_conflicts, i, conflict) { + if (git_index_conflict_remove(data->index, conflict) < 0) + return -1; + + data->completed_steps++; + } + + return 0; +} + +static int checkout_extensions_update_index(checkout_data *data) +{ + const git_index_reuc_entry *reuc_entry; + const git_index_name_entry *name_entry; + size_t i; + int error = 0; + + if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) + return 0; + + if (data->update_reuc) { + git_vector_foreach(data->update_reuc, i, reuc_entry) { + if ((error = git_index_reuc_add(data->index, reuc_entry->path, + reuc_entry->mode[0], &reuc_entry->oid[0], + reuc_entry->mode[1], &reuc_entry->oid[1], + reuc_entry->mode[2], &reuc_entry->oid[2])) < 0) + goto done; + } + } + + if (data->update_names) { + git_vector_foreach(data->update_names, i, name_entry) { + if ((error = git_index_name_add(data->index, name_entry->ancestor, + name_entry->ours, name_entry->theirs)) < 0) + goto done; + } + } + +done: + return error; +} + +static void checkout_data_clear(checkout_data *data) +{ + if (data->opts_free_baseline) { + git_tree_free(data->opts.baseline); + data->opts.baseline = NULL; + } + + git_vector_free(&data->removes); + git_pool_clear(&data->pool); + + git_vector_free_deep(&data->remove_conflicts); + git_vector_free_deep(&data->update_conflicts); + + git__free(data->pfx); + data->pfx = NULL; + + git_str_dispose(&data->target_path); + git_str_dispose(&data->tmp); + + git_index_free(data->index); + data->index = NULL; + + git_strmap_free(data->mkdir_map); + data->mkdir_map = NULL; + + git_attr_session__free(&data->attr_session); +} + +static int validate_target_directory(checkout_data *data) +{ + int error; + + if ((error = git_path_validate_length(data->repo, data->opts.target_directory)) < 0) + return error; + + if (git_fs_path_isdir(data->opts.target_directory)) + return 0; + + error = checkout_mkdir(data, data->opts.target_directory, NULL, + GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR); + + return error; +} + +static int checkout_data_init( + checkout_data *data, + git_iterator *target, + const git_checkout_options *proposed) +{ + int error = 0; + git_repository *repo = git_iterator_owner(target); + + memset(data, 0, sizeof(*data)); + + if (!repo) { + git_error_set(GIT_ERROR_CHECKOUT, "cannot checkout nothing"); + return -1; + } + + if ((!proposed || !proposed->target_directory) && + (error = git_repository__ensure_not_bare(repo, "checkout")) < 0) + return error; + + data->repo = repo; + data->target = target; + + GIT_ERROR_CHECK_VERSION( + proposed, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options"); + + if (!proposed) + GIT_INIT_STRUCTURE(&data->opts, GIT_CHECKOUT_OPTIONS_VERSION); + else + memmove(&data->opts, proposed, sizeof(git_checkout_options)); + + if (!data->opts.target_directory) + data->opts.target_directory = git_repository_workdir(repo); + else if ((error = validate_target_directory(data)) < 0) + goto cleanup; + + if ((error = git_repository_index(&data->index, data->repo)) < 0) + goto cleanup; + + /* refresh config and index content unless NO_REFRESH is given */ + if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) { + git_config *cfg; + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + goto cleanup; + + /* Reload the repository index (unless we're checking out the + * index; then it has the changes we're trying to check out + * and those should not be overwritten.) + */ + if (data->index != git_iterator_index(target)) { + if (data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) { + /* When forcing, we can blindly re-read the index */ + if ((error = git_index_read(data->index, false)) < 0) + goto cleanup; + } else { + /* + * When not being forced, we need to check for unresolved + * conflicts and unsaved changes in the index before + * proceeding. + */ + if (git_index_has_conflicts(data->index)) { + error = GIT_ECONFLICT; + git_error_set(GIT_ERROR_CHECKOUT, + "unresolved conflicts exist in the index"); + goto cleanup; + } + + if ((error = git_index_read_safely(data->index)) < 0) + goto cleanup; + } + + /* clean conflict data in the current index */ + git_index_name_clear(data->index); + git_index_reuc_clear(data->index); + } + } + + /* if you are forcing, allow all safe updates, plus recreate missing */ + if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0) + data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_RECREATE_MISSING; + + /* if the repository does not actually have an index file, then this + * is an initial checkout (perhaps from clone), so we allow safe updates + */ + if (!data->index->on_disk && + (data->opts.checkout_strategy & GIT_CHECKOUT_SAFE) != 0) + data->opts.checkout_strategy |= GIT_CHECKOUT_RECREATE_MISSING; + + data->strategy = data->opts.checkout_strategy; + + /* opts->disable_filters is false by default */ + + if (!data->opts.dir_mode) + data->opts.dir_mode = GIT_DIR_MODE; + + if (!data->opts.file_open_flags) + data->opts.file_open_flags = O_CREAT | O_TRUNC | O_WRONLY; + + data->pfx = git_pathspec_prefix(&data->opts.paths); + + if ((error = git_repository__configmap_lookup( + &data->can_symlink, repo, GIT_CONFIGMAP_SYMLINKS)) < 0) + goto cleanup; + + if ((error = git_repository__configmap_lookup( + &data->respect_filemode, repo, GIT_CONFIGMAP_FILEMODE)) < 0) + goto cleanup; + + if (!data->opts.baseline && !data->opts.baseline_index) { + data->opts_free_baseline = true; + error = 0; + + /* if we don't have an index, this is an initial checkout and + * should be against an empty baseline + */ + if (data->index->on_disk) + error = checkout_lookup_head_tree(&data->opts.baseline, repo); + + if (error == GIT_EUNBORNBRANCH) { + error = 0; + git_error_clear(); + } + + if (error < 0) + goto cleanup; + } + + if ((data->opts.checkout_strategy & + (GIT_CHECKOUT_CONFLICT_STYLE_MERGE | GIT_CHECKOUT_CONFLICT_STYLE_DIFF3)) == 0) { + git_config_entry *conflict_style = NULL; + git_config *cfg = NULL; + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 || + (error = git_config_get_entry(&conflict_style, cfg, "merge.conflictstyle")) < 0 || + error == GIT_ENOTFOUND) + ; + else if (error) + goto cleanup; + else if (strcmp(conflict_style->value, "merge") == 0) + data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_MERGE; + else if (strcmp(conflict_style->value, "diff3") == 0) + data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3; + else if (strcmp(conflict_style->value, "zdiff3") == 0) + data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3; + else { + git_error_set(GIT_ERROR_CHECKOUT, "unknown style '%s' given for 'merge.conflictstyle'", + conflict_style->value); + error = -1; + git_config_entry_free(conflict_style); + goto cleanup; + } + git_config_entry_free(conflict_style); + } + + if ((error = git_pool_init(&data->pool, 1)) < 0 || + (error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 || + (error = git_vector_init(&data->remove_conflicts, 0, NULL)) < 0 || + (error = git_vector_init(&data->update_conflicts, 0, NULL)) < 0 || + (error = git_str_puts(&data->target_path, data->opts.target_directory)) < 0 || + (error = git_fs_path_to_dir(&data->target_path)) < 0 || + (error = git_strmap_new(&data->mkdir_map)) < 0) + goto cleanup; + + data->target_len = git_str_len(&data->target_path); + + git_attr_session__init(&data->attr_session, data->repo); + +cleanup: + if (error < 0) + checkout_data_clear(data); + + return error; +} + +#define CHECKOUT_INDEX_DONT_WRITE_MASK \ + (GIT_CHECKOUT_DONT_UPDATE_INDEX | GIT_CHECKOUT_DONT_WRITE_INDEX) + +GIT_INLINE(void) setup_pathspecs( + git_iterator_options *iter_opts, + const git_checkout_options *checkout_opts) +{ + if (checkout_opts && + (checkout_opts->checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)) { + iter_opts->pathlist.count = checkout_opts->paths.count; + iter_opts->pathlist.strings = checkout_opts->paths.strings; + } +} + +int git_checkout_iterator( + git_iterator *target, + git_index *index, + const git_checkout_options *opts) +{ + int error = 0; + git_iterator *baseline = NULL, *workdir = NULL; + git_iterator_options baseline_opts = GIT_ITERATOR_OPTIONS_INIT, + workdir_opts = GIT_ITERATOR_OPTIONS_INIT; + checkout_data data = {0}; + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + uint32_t *actions = NULL; + size_t *counts = NULL; + + /* initialize structures and options */ + error = checkout_data_init(&data, target, opts); + if (error < 0) + return error; + + diff_opts.flags = + GIT_DIFF_INCLUDE_UNMODIFIED | + GIT_DIFF_INCLUDE_UNREADABLE | + GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */ + GIT_DIFF_INCLUDE_IGNORED | + GIT_DIFF_INCLUDE_TYPECHANGE | + GIT_DIFF_INCLUDE_TYPECHANGE_TREES | + GIT_DIFF_SKIP_BINARY_CHECK | + GIT_DIFF_INCLUDE_CASECHANGE; + if (data.opts.checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) + diff_opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; + if (data.opts.paths.count > 0) + diff_opts.pathspec = data.opts.paths; + + /* set up iterators */ + + workdir_opts.flags = git_iterator_ignore_case(target) ? + GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; + workdir_opts.flags |= GIT_ITERATOR_DONT_AUTOEXPAND; + workdir_opts.start = data.pfx; + workdir_opts.end = data.pfx; + + setup_pathspecs(&workdir_opts, opts); + + if ((error = git_iterator_reset_range(target, data.pfx, data.pfx)) < 0 || + (error = git_iterator_for_workdir_ext( + &workdir, data.repo, data.opts.target_directory, index, NULL, + &workdir_opts)) < 0) + goto cleanup; + + baseline_opts.flags = git_iterator_ignore_case(target) ? + GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; + baseline_opts.start = data.pfx; + baseline_opts.end = data.pfx; + + setup_pathspecs(&baseline_opts, opts); + + if (data.opts.baseline_index) { + if ((error = git_iterator_for_index( + &baseline, git_index_owner(data.opts.baseline_index), + data.opts.baseline_index, &baseline_opts)) < 0) + goto cleanup; + } else { + if ((error = git_iterator_for_tree( + &baseline, data.opts.baseline, &baseline_opts)) < 0) + goto cleanup; + } + + /* Should not have case insensitivity mismatch */ + GIT_ASSERT(git_iterator_ignore_case(workdir) == git_iterator_ignore_case(baseline)); + + /* Generate baseline-to-target diff which will include an entry for + * every possible update that might need to be made. + */ + if ((error = git_diff__from_iterators( + &data.diff, data.repo, baseline, target, &diff_opts)) < 0) + goto cleanup; + + /* Loop through diff (and working directory iterator) building a list of + * actions to be taken, plus look for conflicts and send notifications, + * then loop through conflicts. + */ + if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) != 0) + goto cleanup; + + if (data.strategy & GIT_CHECKOUT_DRY_RUN) + goto cleanup; + + data.total_steps = counts[CHECKOUT_ACTION__REMOVE] + + counts[CHECKOUT_ACTION__REMOVE_CONFLICT] + + counts[CHECKOUT_ACTION__UPDATE_BLOB] + + counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] + + counts[CHECKOUT_ACTION__UPDATE_CONFLICT]; + + report_progress(&data, NULL); /* establish 0 baseline */ + + /* To deal with some order dependencies, perform remaining checkout + * in three passes: removes, then update blobs, then update submodules. + */ + if (counts[CHECKOUT_ACTION__REMOVE] > 0 && + (error = checkout_remove_the_old(actions, &data)) < 0) + goto cleanup; + + if (counts[CHECKOUT_ACTION__REMOVE_CONFLICT] > 0 && + (error = checkout_remove_conflicts(&data)) < 0) + goto cleanup; + + if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 && + (error = checkout_create_the_new(actions, &data)) < 0) + goto cleanup; + + if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 && + (error = checkout_create_submodules(actions, &data)) < 0) + goto cleanup; + + if (counts[CHECKOUT_ACTION__UPDATE_CONFLICT] > 0 && + (error = checkout_create_conflicts(&data)) < 0) + goto cleanup; + + if (data.index != git_iterator_index(target) && + (error = checkout_extensions_update_index(&data)) < 0) + goto cleanup; + + GIT_ASSERT(data.completed_steps == data.total_steps); + + if (data.opts.perfdata_cb) + data.opts.perfdata_cb(&data.perfdata, data.opts.perfdata_payload); + +cleanup: + if (!error && data.index != NULL && + (data.strategy & CHECKOUT_INDEX_DONT_WRITE_MASK) == 0) + error = git_index_write(data.index); + + git_diff_free(data.diff); + git_iterator_free(workdir); + git_iterator_free(baseline); + git__free(actions); + git__free(counts); + checkout_data_clear(&data); + + return error; +} + +int git_checkout_index( + git_repository *repo, + git_index *index, + const git_checkout_options *opts) +{ + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error, owned = 0; + git_iterator *index_i; + + if (!index && !repo) { + git_error_set(GIT_ERROR_CHECKOUT, + "must provide either repository or index to checkout"); + return -1; + } + + if (index && repo && + git_index_owner(index) && + git_index_owner(index) != repo) { + git_error_set(GIT_ERROR_CHECKOUT, + "index to checkout does not match repository"); + return -1; + } else if(index && repo && !git_index_owner(index)) { + GIT_REFCOUNT_OWN(index, repo); + owned = 1; + } + + if (!repo) + repo = git_index_owner(index); + + if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) + return error; + GIT_REFCOUNT_INC(index); + + setup_pathspecs(&iter_opts, opts); + + if (!(error = git_iterator_for_index(&index_i, repo, index, &iter_opts))) + error = git_checkout_iterator(index_i, index, opts); + + if (owned) + GIT_REFCOUNT_OWN(index, NULL); + + git_iterator_free(index_i); + git_index_free(index); + + return error; +} + +int git_checkout_tree( + git_repository *repo, + const git_object *treeish, + const git_checkout_options *opts) +{ + int error; + git_index *index; + git_tree *tree = NULL; + git_iterator *tree_i = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + + if (!treeish && !repo) { + git_error_set(GIT_ERROR_CHECKOUT, + "must provide either repository or tree to checkout"); + return -1; + } + if (treeish && repo && git_object_owner(treeish) != repo) { + git_error_set(GIT_ERROR_CHECKOUT, + "object to checkout does not match repository"); + return -1; + } + + if (!repo) + repo = git_object_owner(treeish); + + if (treeish) { + if (git_object_peel((git_object **)&tree, treeish, GIT_OBJECT_TREE) < 0) { + git_error_set( + GIT_ERROR_CHECKOUT, "provided object cannot be peeled to a tree"); + return -1; + } + } + else { + if ((error = checkout_lookup_head_tree(&tree, repo)) < 0) { + if (error != GIT_EUNBORNBRANCH) + git_error_set( + GIT_ERROR_CHECKOUT, + "HEAD could not be peeled to a tree and no treeish given"); + return error; + } + } + + if ((error = git_repository_index(&index, repo)) < 0) + return error; + + setup_pathspecs(&iter_opts, opts); + + if (!(error = git_iterator_for_tree(&tree_i, tree, &iter_opts))) + error = git_checkout_iterator(tree_i, index, opts); + + git_iterator_free(tree_i); + git_index_free(index); + git_tree_free(tree); + + return error; +} + +int git_checkout_head( + git_repository *repo, + const git_checkout_options *opts) +{ + GIT_ASSERT_ARG(repo); + + return git_checkout_tree(repo, NULL, opts); +} + +int git_checkout_options_init(git_checkout_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_checkout_options, GIT_CHECKOUT_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_checkout_init_options(git_checkout_options *opts, unsigned int version) +{ + return git_checkout_options_init(opts, version); +} +#endif diff --git a/src/libgit2/checkout.h b/src/libgit2/checkout.h new file mode 100644 index 000000000..517fbf3b1 --- /dev/null +++ b/src/libgit2/checkout.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_checkout_h__ +#define INCLUDE_checkout_h__ + +#include "common.h" + +#include "git2/checkout.h" +#include "iterator.h" + +#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12) + +/** + * Update the working directory to match the target iterator. The + * expected baseline value can be passed in via the checkout options + * or else will default to the HEAD commit. + */ +extern int git_checkout_iterator( + git_iterator *target, + git_index *index, + const git_checkout_options *opts); + +#endif diff --git a/src/libgit2/cherrypick.c b/src/libgit2/cherrypick.c new file mode 100644 index 000000000..9ec4962b9 --- /dev/null +++ b/src/libgit2/cherrypick.c @@ -0,0 +1,242 @@ +/* +* Copyright (C) the libgit2 contributors. All rights reserved. +* +* This file is part of libgit2, distributed under the GNU GPL v2 with +* a Linking Exception. For full terms see the included COPYING file. +*/ + +#include "common.h" + +#include "repository.h" +#include "filebuf.h" +#include "merge.h" +#include "vector.h" +#include "index.h" + +#include "git2/types.h" +#include "git2/merge.h" +#include "git2/cherrypick.h" +#include "git2/commit.h" +#include "git2/sys/commit.h" + +#define GIT_CHERRYPICK_FILE_MODE 0666 + +static int write_cherrypick_head( + git_repository *repo, + const char *commit_oidstr) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + int error = 0; + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_CHERRYPICK_HEAD_FILE)) >= 0 && + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_CHERRYPICK_FILE_MODE)) >= 0 && + (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0) + error = git_filebuf_commit(&file); + + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int write_merge_msg( + git_repository *repo, + const char *commit_msg) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + int error = 0; + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_CHERRYPICK_FILE_MODE)) < 0 || + (error = git_filebuf_printf(&file, "%s", commit_msg)) < 0) + goto cleanup; + + error = git_filebuf_commit(&file); + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int cherrypick_normalize_opts( + git_repository *repo, + git_cherrypick_options *opts, + const git_cherrypick_options *given, + const char *their_label) +{ + int error = 0; + unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_ALLOW_CONFLICTS; + + GIT_UNUSED(repo); + + if (given != NULL) + memcpy(opts, given, sizeof(git_cherrypick_options)); + else { + git_cherrypick_options default_opts = GIT_CHERRYPICK_OPTIONS_INIT; + memcpy(opts, &default_opts, sizeof(git_cherrypick_options)); + } + + if (!opts->checkout_opts.checkout_strategy) + opts->checkout_opts.checkout_strategy = default_checkout_strategy; + + if (!opts->checkout_opts.our_label) + opts->checkout_opts.our_label = "HEAD"; + + if (!opts->checkout_opts.their_label) + opts->checkout_opts.their_label = their_label; + + return error; +} + +static int cherrypick_state_cleanup(git_repository *repo) +{ + const char *state_files[] = { GIT_CHERRYPICK_HEAD_FILE, GIT_MERGE_MSG_FILE }; + + return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); +} + +static int cherrypick_seterr(git_commit *commit, const char *fmt) +{ + char commit_oidstr[GIT_OID_HEXSZ + 1]; + + git_error_set(GIT_ERROR_CHERRYPICK, fmt, + git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit))); + + return -1; +} + +int git_cherrypick_commit( + git_index **out, + git_repository *repo, + git_commit *cherrypick_commit, + git_commit *our_commit, + unsigned int mainline, + const git_merge_options *merge_opts) +{ + git_commit *parent_commit = NULL; + git_tree *parent_tree = NULL, *our_tree = NULL, *cherrypick_tree = NULL; + int parent = 0, error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(cherrypick_commit); + GIT_ASSERT_ARG(our_commit); + + if (git_commit_parentcount(cherrypick_commit) > 1) { + if (!mainline) + return cherrypick_seterr(cherrypick_commit, + "mainline branch is not specified but %s is a merge commit"); + + parent = mainline; + } else { + if (mainline) + return cherrypick_seterr(cherrypick_commit, + "mainline branch specified but %s is not a merge commit"); + + parent = git_commit_parentcount(cherrypick_commit); + } + + if (parent && + ((error = git_commit_parent(&parent_commit, cherrypick_commit, (parent - 1))) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0)) + goto done; + + if ((error = git_commit_tree(&cherrypick_tree, cherrypick_commit)) < 0 || + (error = git_commit_tree(&our_tree, our_commit)) < 0) + goto done; + + error = git_merge_trees(out, repo, parent_tree, our_tree, cherrypick_tree, merge_opts); + +done: + git_tree_free(parent_tree); + git_tree_free(our_tree); + git_tree_free(cherrypick_tree); + git_commit_free(parent_commit); + + return error; +} + +int git_cherrypick( + git_repository *repo, + git_commit *commit, + const git_cherrypick_options *given_opts) +{ + git_cherrypick_options opts; + git_reference *our_ref = NULL; + git_commit *our_commit = NULL; + char commit_oidstr[GIT_OID_HEXSZ + 1]; + const char *commit_msg, *commit_summary; + git_str their_label = GIT_STR_INIT; + git_index *index = NULL; + git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(commit); + + GIT_ERROR_CHECK_VERSION(given_opts, GIT_CHERRYPICK_OPTIONS_VERSION, "git_cherrypick_options"); + + if ((error = git_repository__ensure_not_bare(repo, "cherry-pick")) < 0) + return error; + + if ((commit_msg = git_commit_message(commit)) == NULL || + (commit_summary = git_commit_summary(commit)) == NULL) { + error = -1; + goto on_error; + } + + git_oid_nfmt(commit_oidstr, sizeof(commit_oidstr), git_commit_id(commit)); + + if ((error = write_merge_msg(repo, commit_msg)) < 0 || + (error = git_str_printf(&their_label, "%.7s... %s", commit_oidstr, commit_summary)) < 0 || + (error = cherrypick_normalize_opts(repo, &opts, given_opts, git_str_cstr(&their_label))) < 0 || + (error = git_indexwriter_init_for_operation(&indexwriter, repo, &opts.checkout_opts.checkout_strategy)) < 0 || + (error = write_cherrypick_head(repo, commit_oidstr)) < 0 || + (error = git_repository_head(&our_ref, repo)) < 0 || + (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJECT_COMMIT)) < 0 || + (error = git_cherrypick_commit(&index, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 || + (error = git_merge__check_result(repo, index)) < 0 || + (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 || + (error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 || + (error = git_indexwriter_commit(&indexwriter)) < 0) + goto on_error; + + goto done; + +on_error: + cherrypick_state_cleanup(repo); + +done: + git_indexwriter_cleanup(&indexwriter); + git_index_free(index); + git_commit_free(our_commit); + git_reference_free(our_ref); + git_str_dispose(&their_label); + + return error; +} + +int git_cherrypick_options_init( + git_cherrypick_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_cherrypick_options, GIT_CHERRYPICK_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_cherrypick_init_options( + git_cherrypick_options *opts, unsigned int version) +{ + return git_cherrypick_options_init(opts, version); +} +#endif diff --git a/src/libgit2/clone.c b/src/libgit2/clone.c new file mode 100644 index 000000000..1843875f8 --- /dev/null +++ b/src/libgit2/clone.c @@ -0,0 +1,655 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "clone.h" + +#include "git2/clone.h" +#include "git2/remote.h" +#include "git2/revparse.h" +#include "git2/branch.h" +#include "git2/config.h" +#include "git2/checkout.h" +#include "git2/commit.h" +#include "git2/tree.h" + +#include "remote.h" +#include "futils.h" +#include "refs.h" +#include "fs_path.h" +#include "repository.h" +#include "odb.h" + +static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link); + +static int create_branch( + git_reference **branch, + git_repository *repo, + const git_oid *target, + const char *name, + const char *log_message) +{ + git_commit *head_obj = NULL; + git_reference *branch_ref = NULL; + git_str refname = GIT_STR_INIT; + int error; + + /* Find the target commit */ + if ((error = git_commit_lookup(&head_obj, repo, target)) < 0) + return error; + + /* Create the new branch */ + if ((error = git_str_printf(&refname, GIT_REFS_HEADS_DIR "%s", name)) < 0) + return error; + + error = git_reference_create(&branch_ref, repo, git_str_cstr(&refname), target, 0, log_message); + git_str_dispose(&refname); + git_commit_free(head_obj); + + if (!error) + *branch = branch_ref; + else + git_reference_free(branch_ref); + + return error; +} + +static int setup_tracking_config( + git_repository *repo, + const char *branch_name, + const char *remote_name, + const char *merge_target) +{ + git_config *cfg; + git_str remote_key = GIT_STR_INIT, merge_key = GIT_STR_INIT; + int error = -1; + + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; + + if (git_str_printf(&remote_key, "branch.%s.remote", branch_name) < 0) + goto cleanup; + + if (git_str_printf(&merge_key, "branch.%s.merge", branch_name) < 0) + goto cleanup; + + if (git_config_set_string(cfg, git_str_cstr(&remote_key), remote_name) < 0) + goto cleanup; + + if (git_config_set_string(cfg, git_str_cstr(&merge_key), merge_target) < 0) + goto cleanup; + + error = 0; + +cleanup: + git_str_dispose(&remote_key); + git_str_dispose(&merge_key); + return error; +} + +static int create_tracking_branch( + git_reference **branch, + git_repository *repo, + const git_oid *target, + const char *branch_name, + const char *log_message) +{ + int error; + + if ((error = create_branch(branch, repo, target, branch_name, log_message)) < 0) + return error; + + return setup_tracking_config( + repo, + branch_name, + GIT_REMOTE_ORIGIN, + git_reference_name(*branch)); +} + +static int update_head_to_new_branch( + git_repository *repo, + const git_oid *target, + const char *name, + const char *reflog_message) +{ + git_reference *tracking_branch = NULL; + int error; + + if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) + name += strlen(GIT_REFS_HEADS_DIR); + + error = create_tracking_branch(&tracking_branch, repo, target, name, + reflog_message); + + if (!error) + error = git_repository_set_head( + repo, git_reference_name(tracking_branch)); + + git_reference_free(tracking_branch); + + /* if it already existed, then the user's refspec created it for us, ignore it' */ + if (error == GIT_EEXISTS) + error = 0; + + return error; +} + +static int update_head_to_default(git_repository *repo) +{ + git_str initialbranch = GIT_STR_INIT; + const char *branch_name; + int error = 0; + + if ((error = git_repository_initialbranch(&initialbranch, repo)) < 0) + goto done; + + if (git__prefixcmp(initialbranch.ptr, GIT_REFS_HEADS_DIR) != 0) { + git_error_set(GIT_ERROR_INVALID, "invalid initial branch '%s'", initialbranch.ptr); + error = -1; + goto done; + } + + branch_name = initialbranch.ptr + strlen(GIT_REFS_HEADS_DIR); + + error = setup_tracking_config(repo, branch_name, GIT_REMOTE_ORIGIN, + initialbranch.ptr); + +done: + git_str_dispose(&initialbranch); + return error; +} + +static int update_remote_head( + git_repository *repo, + git_remote *remote, + git_str *target, + const char *reflog_message) +{ + git_refspec *refspec; + git_reference *remote_head = NULL; + git_str remote_head_name = GIT_STR_INIT; + git_str remote_branch_name = GIT_STR_INIT; + int error; + + /* Determine the remote tracking ref name from the local branch */ + refspec = git_remote__matching_refspec(remote, git_str_cstr(target)); + + if (refspec == NULL) { + git_error_set(GIT_ERROR_NET, "the remote's default branch does not fit the refspec configuration"); + error = GIT_EINVALIDSPEC; + goto cleanup; + } + + if ((error = git_refspec__transform( + &remote_branch_name, + refspec, + git_str_cstr(target))) < 0) + goto cleanup; + + if ((error = git_str_printf(&remote_head_name, + "%s%s/%s", + GIT_REFS_REMOTES_DIR, + git_remote_name(remote), + GIT_HEAD_FILE)) < 0) + goto cleanup; + + error = git_reference_symbolic_create( + &remote_head, + repo, + git_str_cstr(&remote_head_name), + git_str_cstr(&remote_branch_name), + true, + reflog_message); + +cleanup: + git_reference_free(remote_head); + git_str_dispose(&remote_branch_name); + git_str_dispose(&remote_head_name); + return error; +} + +static int update_head_to_remote( + git_repository *repo, + git_remote *remote, + const char *reflog_message) +{ + int error = 0; + size_t refs_len; + const git_remote_head *remote_head, **refs; + const git_oid *remote_head_id; + git_str branch = GIT_STR_INIT; + + if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0) + return error; + + /* We cloned an empty repository or one with an unborn HEAD */ + if (refs_len == 0 || strcmp(refs[0]->name, GIT_HEAD_FILE)) + return update_head_to_default(repo); + + /* We know we have HEAD, let's see where it points */ + remote_head = refs[0]; + GIT_ASSERT(remote_head); + + remote_head_id = &remote_head->oid; + + error = git_remote__default_branch(&branch, remote); + if (error == GIT_ENOTFOUND) { + error = git_repository_set_head_detached( + repo, remote_head_id); + goto cleanup; + } + + if ((error = update_remote_head(repo, remote, &branch, reflog_message)) < 0) + goto cleanup; + + error = update_head_to_new_branch( + repo, + remote_head_id, + git_str_cstr(&branch), + reflog_message); + +cleanup: + git_str_dispose(&branch); + + return error; +} + +static int update_head_to_branch( + git_repository *repo, + git_remote *remote, + const char *branch, + const char *reflog_message) +{ + int retcode; + git_str remote_branch_name = GIT_STR_INIT; + git_reference *remote_ref = NULL; + git_str default_branch = GIT_STR_INIT; + + GIT_ASSERT_ARG(remote); + GIT_ASSERT_ARG(branch); + + if ((retcode = git_str_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s", + git_remote_name(remote), branch)) < 0 ) + goto cleanup; + + if ((retcode = git_reference_lookup(&remote_ref, repo, git_str_cstr(&remote_branch_name))) < 0) + goto cleanup; + + if ((retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref), branch, + reflog_message)) < 0) + goto cleanup; + + if ((retcode = git_remote__default_branch(&default_branch, remote)) < 0) + goto cleanup; + + if (!git_remote__matching_refspec(remote, git_str_cstr(&default_branch))) + goto cleanup; + + retcode = update_remote_head(repo, remote, &default_branch, reflog_message); + +cleanup: + git_reference_free(remote_ref); + git_str_dispose(&remote_branch_name); + git_str_dispose(&default_branch); + return retcode; +} + +static int default_repository_create(git_repository **out, const char *path, int bare, void *payload) +{ + GIT_UNUSED(payload); + + return git_repository_init(out, path, bare); +} + +static int default_remote_create( + git_remote **out, + git_repository *repo, + const char *name, + const char *url, + void *payload) +{ + GIT_UNUSED(payload); + + return git_remote_create(out, repo, name, url); +} + +/* + * submodules? + */ + +static int create_and_configure_origin( + git_remote **out, + git_repository *repo, + const char *url, + const git_clone_options *options) +{ + int error; + git_remote *origin = NULL; + char buf[GIT_PATH_MAX]; + git_remote_create_cb remote_create = options->remote_cb; + void *payload = options->remote_cb_payload; + + /* If the path exists and is a dir, the url should be the absolute path */ + if (git_fs_path_root(url) < 0 && git_fs_path_exists(url) && git_fs_path_isdir(url)) { + if (p_realpath(url, buf) == NULL) + return -1; + + url = buf; + } + + if (!remote_create) { + remote_create = default_remote_create; + payload = NULL; + } + + if ((error = remote_create(&origin, repo, "origin", url, payload)) < 0) + goto on_error; + + *out = origin; + return 0; + +on_error: + git_remote_free(origin); + return error; +} + +static bool should_checkout( + git_repository *repo, + bool is_bare, + const git_checkout_options *opts) +{ + if (is_bare) + return false; + + if (!opts) + return false; + + if (opts->checkout_strategy == GIT_CHECKOUT_NONE) + return false; + + return !git_repository_head_unborn(repo); +} + +static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const char *reflog_message) +{ + int error; + + if (branch) + error = update_head_to_branch(repo, remote, branch, reflog_message); + /* Point HEAD to the same ref as the remote's head */ + else + error = update_head_to_remote(repo, remote, reflog_message); + + if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) + error = git_checkout_head(repo, co_opts); + + return error; +} + +static int clone_into(git_repository *repo, git_remote *_remote, const git_fetch_options *opts, const git_checkout_options *co_opts, const char *branch) +{ + int error; + git_str reflog_message = GIT_STR_INIT; + git_fetch_options fetch_opts; + git_remote *remote; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(_remote); + + if (!git_repository_is_empty(repo)) { + git_error_set(GIT_ERROR_INVALID, "the repository is not empty"); + return -1; + } + + if ((error = git_remote_dup(&remote, _remote)) < 0) + return error; + + memcpy(&fetch_opts, opts, sizeof(git_fetch_options)); + fetch_opts.update_fetchhead = 0; + fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; + git_str_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); + + if ((error = git_remote_fetch(remote, NULL, &fetch_opts, git_str_cstr(&reflog_message))) != 0) + goto cleanup; + + error = checkout_branch(repo, remote, co_opts, branch, git_str_cstr(&reflog_message)); + +cleanup: + git_remote_free(remote); + git_str_dispose(&reflog_message); + + return error; +} + +int git_clone__should_clone_local(const char *url_or_path, git_clone_local_t local) +{ + git_str fromurl = GIT_STR_INIT; + const char *path = url_or_path; + bool is_url, is_local; + + if (local == GIT_CLONE_NO_LOCAL) + return 0; + + if ((is_url = git_fs_path_is_local_file_url(url_or_path)) != 0) { + if (git_fs_path_fromurl(&fromurl, url_or_path) < 0) { + is_local = -1; + goto done; + } + + path = fromurl.ptr; + } + + is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) && + git_fs_path_isdir(path); + +done: + git_str_dispose(&fromurl); + return is_local; +} + +static int git__clone( + git_repository **out, + const char *url, + const char *local_path, + const git_clone_options *_options, + int use_existing) +{ + int error = 0; + git_repository *repo = NULL; + git_remote *origin; + git_clone_options options = GIT_CLONE_OPTIONS_INIT; + uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES; + git_repository_create_cb repository_cb; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(url); + GIT_ASSERT_ARG(local_path); + + if (_options) + memcpy(&options, _options, sizeof(git_clone_options)); + + GIT_ERROR_CHECK_VERSION(&options, GIT_CLONE_OPTIONS_VERSION, "git_clone_options"); + + /* Only clone to a new directory or an empty directory */ + if (git_fs_path_exists(local_path) && !use_existing && !git_fs_path_is_empty_dir(local_path)) { + git_error_set(GIT_ERROR_INVALID, + "'%s' exists and is not an empty directory", local_path); + return GIT_EEXISTS; + } + + /* Only remove the root directory on failure if we create it */ + if (git_fs_path_exists(local_path)) + rmdir_flags |= GIT_RMDIR_SKIP_ROOT; + + if (options.repository_cb) + repository_cb = options.repository_cb; + else + repository_cb = default_repository_create; + + if ((error = repository_cb(&repo, local_path, options.bare, options.repository_cb_payload)) < 0) + return error; + + if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { + int clone_local = git_clone__should_clone_local(url, options.local); + int link = options.local != GIT_CLONE_LOCAL_NO_LINKS; + + if (clone_local == 1) + error = clone_local_into( + repo, origin, &options.fetch_opts, &options.checkout_opts, + options.checkout_branch, link); + else if (clone_local == 0) + error = clone_into( + repo, origin, &options.fetch_opts, &options.checkout_opts, + options.checkout_branch); + else + error = -1; + + git_remote_free(origin); + } + + if (error != 0) { + git_error_state last_error = {0}; + git_error_state_capture(&last_error, error); + + git_repository_free(repo); + repo = NULL; + + (void)git_futils_rmdir_r(local_path, NULL, rmdir_flags); + + git_error_state_restore(&last_error); + } + + *out = repo; + return error; +} + +int git_clone( + git_repository **out, + const char *url, + const char *local_path, + const git_clone_options *_options) +{ + return git__clone(out, url, local_path, _options, 0); +} + +int git_clone__submodule( + git_repository **out, + const char *url, + const char *local_path, + const git_clone_options *_options) +{ + return git__clone(out, url, local_path, _options, 1); +} + +int git_clone_options_init(git_clone_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_clone_options, GIT_CLONE_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_clone_init_options(git_clone_options *opts, unsigned int version) +{ + return git_clone_options_init(opts, version); +} +#endif + +static bool can_link(const char *src, const char *dst, int link) +{ +#ifdef GIT_WIN32 + GIT_UNUSED(src); + GIT_UNUSED(dst); + GIT_UNUSED(link); + return false; +#else + + struct stat st_src, st_dst; + + if (!link) + return false; + + if (p_stat(src, &st_src) < 0) + return false; + + if (p_stat(dst, &st_dst) < 0) + return false; + + return st_src.st_dev == st_dst.st_dev; +#endif +} + +static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link) +{ + int error, flags; + git_repository *src; + git_str src_odb = GIT_STR_INIT, dst_odb = GIT_STR_INIT, src_path = GIT_STR_INIT; + git_str reflog_message = GIT_STR_INIT; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(remote); + + if (!git_repository_is_empty(repo)) { + git_error_set(GIT_ERROR_INVALID, "the repository is not empty"); + return -1; + } + + /* + * Let's figure out what path we should use for the source + * repo, if it's not rooted, the path should be relative to + * the repository's worktree/gitdir. + */ + if ((error = git_fs_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0) + return error; + + /* Copy .git/objects/ from the source to the target */ + if ((error = git_repository_open(&src, git_str_cstr(&src_path))) < 0) { + git_str_dispose(&src_path); + return error; + } + + if (git_repository__item_path(&src_odb, src, GIT_REPOSITORY_ITEM_OBJECTS) < 0 || + git_repository__item_path(&dst_odb, repo, GIT_REPOSITORY_ITEM_OBJECTS) < 0) { + error = -1; + goto cleanup; + } + + flags = 0; + if (can_link(git_repository_path(src), git_repository_path(repo), link)) + flags |= GIT_CPDIR_LINK_FILES; + + error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), + flags, GIT_OBJECT_DIR_MODE); + + /* + * can_link() doesn't catch all variations, so if we hit an + * error and did want to link, let's try again without trying + * to link. + */ + if (error < 0 && link) { + flags &= ~GIT_CPDIR_LINK_FILES; + error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), + flags, GIT_OBJECT_DIR_MODE); + } + + if (error < 0) + goto cleanup; + + git_str_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); + + if ((error = git_remote_fetch(remote, NULL, fetch_opts, git_str_cstr(&reflog_message))) != 0) + goto cleanup; + + error = checkout_branch(repo, remote, co_opts, branch, git_str_cstr(&reflog_message)); + +cleanup: + git_str_dispose(&reflog_message); + git_str_dispose(&src_path); + git_str_dispose(&src_odb); + git_str_dispose(&dst_odb); + git_repository_free(src); + return error; +} diff --git a/src/libgit2/clone.h b/src/libgit2/clone.h new file mode 100644 index 000000000..7d73cabd5 --- /dev/null +++ b/src/libgit2/clone.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_clone_h__ +#define INCLUDE_clone_h__ + +#include "common.h" + +#include "git2/clone.h" + +extern int git_clone__submodule(git_repository **out, + const char *url, const char *local_path, + const git_clone_options *_options); + +extern int git_clone__should_clone_local(const char *url, git_clone_local_t local); + +#endif diff --git a/src/libgit2/commit.c b/src/libgit2/commit.c new file mode 100644 index 000000000..b137463f3 --- /dev/null +++ b/src/libgit2/commit.c @@ -0,0 +1,1042 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "commit.h" + +#include "git2/common.h" +#include "git2/object.h" +#include "git2/repository.h" +#include "git2/signature.h" +#include "git2/mailmap.h" +#include "git2/sys/commit.h" + +#include "buf.h" +#include "odb.h" +#include "commit.h" +#include "signature.h" +#include "refs.h" +#include "object.h" +#include "array.h" +#include "oidarray.h" + +void git_commit__free(void *_commit) +{ + git_commit *commit = _commit; + + git_array_clear(commit->parent_ids); + + git_signature_free(commit->author); + git_signature_free(commit->committer); + + git__free(commit->raw_header); + git__free(commit->raw_message); + git__free(commit->message_encoding); + git__free(commit->summary); + git__free(commit->body); + + git__free(commit); +} + +static int git_commit__create_buffer_internal( + git_str *out, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_oid *tree, + git_array_oid_t *parents) +{ + size_t i = 0; + const git_oid *parent; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(tree); + + git_oid__writebuf(out, "tree ", tree); + + for (i = 0; i < git_array_size(*parents); i++) { + parent = git_array_get(*parents, i); + git_oid__writebuf(out, "parent ", parent); + } + + git_signature__writebuf(out, "author ", author); + git_signature__writebuf(out, "committer ", committer); + + if (message_encoding != NULL) + git_str_printf(out, "encoding %s\n", message_encoding); + + git_str_putc(out, '\n'); + + if (git_str_puts(out, message) < 0) + goto on_error; + + return 0; + +on_error: + git_str_dispose(out); + return -1; +} + +static int validate_tree_and_parents(git_array_oid_t *parents, git_repository *repo, const git_oid *tree, + git_commit_parent_callback parent_cb, void *parent_payload, + const git_oid *current_id, bool validate) +{ + size_t i; + int error; + git_oid *parent_cpy; + const git_oid *parent; + + if (validate && !git_object__is_valid(repo, tree, GIT_OBJECT_TREE)) + return -1; + + i = 0; + while ((parent = parent_cb(i, parent_payload)) != NULL) { + if (validate && !git_object__is_valid(repo, parent, GIT_OBJECT_COMMIT)) { + error = -1; + goto on_error; + } + + parent_cpy = git_array_alloc(*parents); + GIT_ERROR_CHECK_ALLOC(parent_cpy); + + git_oid_cpy(parent_cpy, parent); + i++; + } + + if (current_id && (parents->size == 0 || git_oid_cmp(current_id, git_array_get(*parents, 0)))) { + git_error_set(GIT_ERROR_OBJECT, "failed to create commit: current tip is not the first parent"); + error = GIT_EMODIFIED; + goto on_error; + } + + return 0; + +on_error: + git_array_clear(*parents); + return error; +} + +static int git_commit__create_internal( + git_oid *id, + git_repository *repo, + const char *update_ref, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_oid *tree, + git_commit_parent_callback parent_cb, + void *parent_payload, + bool validate) +{ + int error; + git_odb *odb; + git_reference *ref = NULL; + git_str buf = GIT_STR_INIT; + const git_oid *current_id = NULL; + git_array_oid_t parents = GIT_ARRAY_INIT; + + if (update_ref) { + error = git_reference_lookup_resolved(&ref, repo, update_ref, 10); + if (error < 0 && error != GIT_ENOTFOUND) + return error; + } + git_error_clear(); + + if (ref) + current_id = git_reference_target(ref); + + if ((error = validate_tree_and_parents(&parents, repo, tree, parent_cb, parent_payload, current_id, validate)) < 0) + goto cleanup; + + error = git_commit__create_buffer_internal(&buf, author, committer, + message_encoding, message, tree, + &parents); + + if (error < 0) + goto cleanup; + + if (git_repository_odb__weakptr(&odb, repo) < 0) + goto cleanup; + + if (git_odb__freshen(odb, tree) < 0) + goto cleanup; + + if (git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJECT_COMMIT) < 0) + goto cleanup; + + + if (update_ref != NULL) { + error = git_reference__update_for_commit( + repo, ref, update_ref, id, "commit"); + goto cleanup; + } + +cleanup: + git_array_clear(parents); + git_reference_free(ref); + git_str_dispose(&buf); + return error; +} + +int git_commit_create_from_callback( + git_oid *id, + git_repository *repo, + const char *update_ref, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_oid *tree, + git_commit_parent_callback parent_cb, + void *parent_payload) +{ + return git_commit__create_internal( + id, repo, update_ref, author, committer, message_encoding, message, + tree, parent_cb, parent_payload, true); +} + +typedef struct { + size_t total; + va_list args; +} commit_parent_varargs; + +static const git_oid *commit_parent_from_varargs(size_t curr, void *payload) +{ + commit_parent_varargs *data = payload; + const git_commit *commit; + if (curr >= data->total) + return NULL; + commit = va_arg(data->args, const git_commit *); + return commit ? git_commit_id(commit) : NULL; +} + +int git_commit_create_v( + git_oid *id, + git_repository *repo, + const char *update_ref, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + ...) +{ + int error = 0; + commit_parent_varargs data; + + GIT_ASSERT_ARG(tree); + GIT_ASSERT_ARG(git_tree_owner(tree) == repo); + + data.total = parent_count; + va_start(data.args, parent_count); + + error = git_commit__create_internal( + id, repo, update_ref, author, committer, + message_encoding, message, git_tree_id(tree), + commit_parent_from_varargs, &data, false); + + va_end(data.args); + return error; +} + +typedef struct { + size_t total; + const git_oid **parents; +} commit_parent_oids; + +static const git_oid *commit_parent_from_ids(size_t curr, void *payload) +{ + commit_parent_oids *data = payload; + return (curr < data->total) ? data->parents[curr] : NULL; +} + +int git_commit_create_from_ids( + git_oid *id, + git_repository *repo, + const char *update_ref, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_oid *tree, + size_t parent_count, + const git_oid *parents[]) +{ + commit_parent_oids data = { parent_count, parents }; + + return git_commit__create_internal( + id, repo, update_ref, author, committer, + message_encoding, message, tree, + commit_parent_from_ids, &data, true); +} + +typedef struct { + size_t total; + const git_commit **parents; + git_repository *repo; +} commit_parent_data; + +static const git_oid *commit_parent_from_array(size_t curr, void *payload) +{ + commit_parent_data *data = payload; + const git_commit *commit; + if (curr >= data->total) + return NULL; + commit = data->parents[curr]; + if (git_commit_owner(commit) != data->repo) + return NULL; + return git_commit_id(commit); +} + +int git_commit_create( + git_oid *id, + git_repository *repo, + const char *update_ref, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[]) +{ + commit_parent_data data = { parent_count, parents, repo }; + + GIT_ASSERT_ARG(tree); + GIT_ASSERT_ARG(git_tree_owner(tree) == repo); + + return git_commit__create_internal( + id, repo, update_ref, author, committer, + message_encoding, message, git_tree_id(tree), + commit_parent_from_array, &data, false); +} + +static const git_oid *commit_parent_for_amend(size_t curr, void *payload) +{ + const git_commit *commit_to_amend = payload; + if (curr >= git_array_size(commit_to_amend->parent_ids)) + return NULL; + return git_array_get(commit_to_amend->parent_ids, curr); +} + +int git_commit_amend( + git_oid *id, + const git_commit *commit_to_amend, + const char *update_ref, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree) +{ + git_repository *repo; + git_oid tree_id; + git_reference *ref; + int error; + + GIT_ASSERT_ARG(id); + GIT_ASSERT_ARG(commit_to_amend); + + repo = git_commit_owner(commit_to_amend); + + if (!author) + author = git_commit_author(commit_to_amend); + if (!committer) + committer = git_commit_committer(commit_to_amend); + if (!message_encoding) + message_encoding = git_commit_message_encoding(commit_to_amend); + if (!message) + message = git_commit_message(commit_to_amend); + + if (!tree) { + git_tree *old_tree; + GIT_ERROR_CHECK_ERROR( git_commit_tree(&old_tree, commit_to_amend) ); + git_oid_cpy(&tree_id, git_tree_id(old_tree)); + git_tree_free(old_tree); + } else { + GIT_ASSERT_ARG(git_tree_owner(tree) == repo); + git_oid_cpy(&tree_id, git_tree_id(tree)); + } + + if (update_ref) { + if ((error = git_reference_lookup_resolved(&ref, repo, update_ref, 5)) < 0) + return error; + + if (git_oid_cmp(git_commit_id(commit_to_amend), git_reference_target(ref))) { + git_reference_free(ref); + git_error_set(GIT_ERROR_REFERENCE, "commit to amend is not the tip of the given branch"); + return -1; + } + } + + error = git_commit__create_internal( + id, repo, NULL, author, committer, message_encoding, message, + &tree_id, commit_parent_for_amend, (void *)commit_to_amend, false); + + if (!error && update_ref) { + error = git_reference__update_for_commit( + repo, ref, NULL, id, "commit"); + git_reference_free(ref); + } + + return error; +} + +static int commit_parse(git_commit *commit, const char *data, size_t size, unsigned int flags) +{ + const char *buffer_start = data, *buffer; + const char *buffer_end = buffer_start + size; + git_oid parent_id; + size_t header_len; + git_signature dummy_sig; + int error; + + GIT_ASSERT_ARG(commit); + GIT_ASSERT_ARG(data); + + buffer = buffer_start; + + /* Allocate for one, which will allow not to realloc 90% of the time */ + git_array_init_to_size(commit->parent_ids, 1); + GIT_ERROR_CHECK_ARRAY(commit->parent_ids); + + /* The tree is always the first field */ + if (!(flags & GIT_COMMIT_PARSE_QUICK)) { + if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0) + goto bad_buffer; + } else { + size_t tree_len = strlen("tree ") + GIT_OID_HEXSZ + 1; + if (buffer + tree_len > buffer_end) + goto bad_buffer; + buffer += tree_len; + } + + /* + * TODO: commit grafts! + */ + + while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) { + git_oid *new_id = git_array_alloc(commit->parent_ids); + GIT_ERROR_CHECK_ALLOC(new_id); + + git_oid_cpy(new_id, &parent_id); + } + + if (!(flags & GIT_COMMIT_PARSE_QUICK)) { + commit->author = git__malloc(sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(commit->author); + + if ((error = git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n')) < 0) + return error; + } + + /* Some tools create multiple author fields, ignore the extra ones */ + while (!git__prefixncmp(buffer, buffer_end - buffer, "author ")) { + if ((error = git_signature__parse(&dummy_sig, &buffer, buffer_end, "author ", '\n')) < 0) + return error; + + git__free(dummy_sig.name); + git__free(dummy_sig.email); + } + + /* Always parse the committer; we need the commit time */ + commit->committer = git__malloc(sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(commit->committer); + + if ((error = git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n')) < 0) + return error; + + if (flags & GIT_COMMIT_PARSE_QUICK) + return 0; + + /* Parse add'l header entries */ + while (buffer < buffer_end) { + const char *eoln = buffer; + if (buffer[-1] == '\n' && buffer[0] == '\n') + break; + + while (eoln < buffer_end && *eoln != '\n') + ++eoln; + + if (git__prefixncmp(buffer, buffer_end - buffer, "encoding ") == 0) { + buffer += strlen("encoding "); + + commit->message_encoding = git__strndup(buffer, eoln - buffer); + GIT_ERROR_CHECK_ALLOC(commit->message_encoding); + } + + if (eoln < buffer_end && *eoln == '\n') + ++eoln; + buffer = eoln; + } + + header_len = buffer - buffer_start; + commit->raw_header = git__strndup(buffer_start, header_len); + GIT_ERROR_CHECK_ALLOC(commit->raw_header); + + /* point "buffer" to data after header, +1 for the final LF */ + buffer = buffer_start + header_len + 1; + + /* extract commit message */ + if (buffer <= buffer_end) + commit->raw_message = git__strndup(buffer, buffer_end - buffer); + else + commit->raw_message = git__strdup(""); + GIT_ERROR_CHECK_ALLOC(commit->raw_message); + + return 0; + +bad_buffer: + git_error_set(GIT_ERROR_OBJECT, "failed to parse bad commit object"); + return GIT_EINVALID; +} + +int git_commit__parse_raw(void *commit, const char *data, size_t size) +{ + return commit_parse(commit, data, size, 0); +} + +int git_commit__parse_ext(git_commit *commit, git_odb_object *odb_obj, unsigned int flags) +{ + return commit_parse(commit, git_odb_object_data(odb_obj), git_odb_object_size(odb_obj), flags); +} + +int git_commit__parse(void *_commit, git_odb_object *odb_obj) +{ + return git_commit__parse_ext(_commit, odb_obj, 0); +} + +#define GIT_COMMIT_GETTER(_rvalue, _name, _return, _invalid) \ + _rvalue git_commit_##_name(const git_commit *commit) \ + {\ + GIT_ASSERT_ARG_WITH_RETVAL(commit, _invalid); \ + return _return; \ + } + +GIT_COMMIT_GETTER(const git_signature *, author, commit->author, NULL) +GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer, NULL) +GIT_COMMIT_GETTER(const char *, message_raw, commit->raw_message, NULL) +GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding, NULL) +GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header, NULL) +GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time, INT64_MIN) +GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset, -1) +GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids), 0) +GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id, NULL) + +const char *git_commit_message(const git_commit *commit) +{ + const char *message; + + GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); + + message = commit->raw_message; + + /* trim leading newlines from raw message */ + while (*message && *message == '\n') + ++message; + + return message; +} + +const char *git_commit_summary(git_commit *commit) +{ + git_str summary = GIT_STR_INIT; + const char *msg, *space, *next; + bool space_contains_newline = false; + + GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); + + if (!commit->summary) { + for (msg = git_commit_message(commit), space = NULL; *msg; ++msg) { + char next_character = msg[0]; + /* stop processing at the end of the first paragraph */ + if (next_character == '\n') { + if (!msg[1]) + break; + if (msg[1] == '\n') + break; + /* stop processing if next line contains only whitespace */ + next = msg + 1; + while (*next && git__isspace_nonlf(*next)) { + ++next; + } + if (!*next || *next == '\n') + break; + } + /* record the beginning of contiguous whitespace runs */ + if (git__isspace(next_character)) { + if(space == NULL) { + space = msg; + space_contains_newline = false; + } + space_contains_newline |= next_character == '\n'; + } + /* the next character is non-space */ + else { + /* process any recorded whitespace */ + if (space) { + if(space_contains_newline) + git_str_putc(&summary, ' '); /* if the space contains a newline, collapse to ' ' */ + else + git_str_put(&summary, space, (msg - space)); /* otherwise copy it */ + space = NULL; + } + /* copy the next character */ + git_str_putc(&summary, next_character); + } + } + + commit->summary = git_str_detach(&summary); + if (!commit->summary) + commit->summary = git__strdup(""); + } + + return commit->summary; +} + +const char *git_commit_body(git_commit *commit) +{ + const char *msg, *end; + + GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); + + if (!commit->body) { + /* search for end of summary */ + for (msg = git_commit_message(commit); *msg; ++msg) + if (msg[0] == '\n' && (!msg[1] || msg[1] == '\n')) + break; + + /* trim leading and trailing whitespace */ + for (; *msg; ++msg) + if (!git__isspace(*msg)) + break; + for (end = msg + strlen(msg) - 1; msg <= end; --end) + if (!git__isspace(*end)) + break; + + if (*msg) + commit->body = git__strndup(msg, end - msg + 1); + } + + return commit->body; +} + +int git_commit_tree(git_tree **tree_out, const git_commit *commit) +{ + GIT_ASSERT_ARG(commit); + return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id); +} + +const git_oid *git_commit_parent_id( + const git_commit *commit, unsigned int n) +{ + GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); + + return git_array_get(commit->parent_ids, n); +} + +int git_commit_parent( + git_commit **parent, const git_commit *commit, unsigned int n) +{ + const git_oid *parent_id; + GIT_ASSERT_ARG(commit); + + parent_id = git_commit_parent_id(commit, n); + if (parent_id == NULL) { + git_error_set(GIT_ERROR_INVALID, "parent %u does not exist", n); + return GIT_ENOTFOUND; + } + + return git_commit_lookup(parent, commit->object.repo, parent_id); +} + +int git_commit_nth_gen_ancestor( + git_commit **ancestor, + const git_commit *commit, + unsigned int n) +{ + git_commit *current, *parent = NULL; + int error; + + GIT_ASSERT_ARG(ancestor); + GIT_ASSERT_ARG(commit); + + if (git_commit_dup(¤t, (git_commit *)commit) < 0) + return -1; + + if (n == 0) { + *ancestor = current; + return 0; + } + + while (n--) { + error = git_commit_parent(&parent, current, 0); + + git_commit_free(current); + + if (error < 0) + return error; + + current = parent; + } + + *ancestor = parent; + return 0; +} + +int git_commit_header_field( + git_buf *out, + const git_commit *commit, + const char *field) +{ + GIT_BUF_WRAP_PRIVATE(out, git_commit__header_field, commit, field); +} + +int git_commit__header_field( + git_str *out, + const git_commit *commit, + const char *field) +{ + const char *eol, *buf = commit->raw_header; + + git_str_clear(out); + + while ((eol = strchr(buf, '\n'))) { + /* We can skip continuations here */ + if (buf[0] == ' ') { + buf = eol + 1; + continue; + } + + /* Skip until we find the field we're after */ + if (git__prefixcmp(buf, field)) { + buf = eol + 1; + continue; + } + + buf += strlen(field); + /* Check that we're not matching a prefix but the field itself */ + if (buf[0] != ' ') { + buf = eol + 1; + continue; + } + + buf++; /* skip the SP */ + + git_str_put(out, buf, eol - buf); + if (git_str_oom(out)) + goto oom; + + /* If the next line starts with SP, it's multi-line, we must continue */ + while (eol[1] == ' ') { + git_str_putc(out, '\n'); + buf = eol + 2; + eol = strchr(buf, '\n'); + if (!eol) + goto malformed; + + git_str_put(out, buf, eol - buf); + } + + if (git_str_oom(out)) + goto oom; + + return 0; + } + + git_error_set(GIT_ERROR_OBJECT, "no such field '%s'", field); + return GIT_ENOTFOUND; + +malformed: + git_error_set(GIT_ERROR_OBJECT, "malformed header"); + return -1; +oom: + git_error_set_oom(); + return -1; +} + +int git_commit_extract_signature( + git_buf *signature_out, + git_buf *signed_data_out, + git_repository *repo, + git_oid *commit_id, + const char *field) +{ + git_str signature = GIT_STR_INIT, signed_data = GIT_STR_INIT; + int error; + + if ((error = git_buf_tostr(&signature, signature_out)) < 0 || + (error = git_buf_tostr(&signed_data, signed_data_out)) < 0 || + (error = git_commit__extract_signature(&signature, &signed_data, repo, commit_id, field)) < 0 || + (error = git_buf_fromstr(signature_out, &signature)) < 0 || + (error = git_buf_fromstr(signed_data_out, &signed_data)) < 0) + goto done; + +done: + git_str_dispose(&signature); + git_str_dispose(&signed_data); + return error; +} + +int git_commit__extract_signature( + git_str *signature, + git_str *signed_data, + git_repository *repo, + git_oid *commit_id, + const char *field) +{ + git_odb_object *obj; + git_odb *odb; + const char *buf; + const char *h, *eol; + int error; + + git_str_clear(signature); + git_str_clear(signed_data); + + if (!field) + field = "gpgsig"; + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) + return error; + + if ((error = git_odb_read(&obj, odb, commit_id)) < 0) + return error; + + if (obj->cached.type != GIT_OBJECT_COMMIT) { + git_error_set(GIT_ERROR_INVALID, "the requested type does not match the type in the ODB"); + error = GIT_ENOTFOUND; + goto cleanup; + } + + buf = git_odb_object_data(obj); + + while ((h = strchr(buf, '\n')) && h[1] != '\0') { + h++; + if (git__prefixcmp(buf, field)) { + if (git_str_put(signed_data, buf, h - buf) < 0) + return -1; + + buf = h; + continue; + } + + h = buf; + h += strlen(field); + eol = strchr(h, '\n'); + if (h[0] != ' ') { + buf = h; + continue; + } + if (!eol) + goto malformed; + + h++; /* skip the SP */ + + git_str_put(signature, h, eol - h); + if (git_str_oom(signature)) + goto oom; + + /* If the next line starts with SP, it's multi-line, we must continue */ + while (eol[1] == ' ') { + git_str_putc(signature, '\n'); + h = eol + 2; + eol = strchr(h, '\n'); + if (!eol) + goto malformed; + + git_str_put(signature, h, eol - h); + } + + if (git_str_oom(signature)) + goto oom; + + error = git_str_puts(signed_data, eol+1); + git_odb_object_free(obj); + return error; + } + + git_error_set(GIT_ERROR_OBJECT, "this commit is not signed"); + error = GIT_ENOTFOUND; + goto cleanup; + +malformed: + git_error_set(GIT_ERROR_OBJECT, "malformed header"); + error = -1; + goto cleanup; +oom: + git_error_set_oom(); + error = -1; + goto cleanup; + +cleanup: + git_odb_object_free(obj); + git_str_clear(signature); + git_str_clear(signed_data); + return error; +} + +int git_commit_create_buffer( + git_buf *out, + git_repository *repo, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[]) +{ + GIT_BUF_WRAP_PRIVATE(out, git_commit__create_buffer, repo, + author, committer, message_encoding, message, + tree, parent_count, parents); +} + +int git_commit__create_buffer( + git_str *out, + git_repository *repo, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[]) +{ + int error; + commit_parent_data data = { parent_count, parents, repo }; + git_array_oid_t parents_arr = GIT_ARRAY_INIT; + const git_oid *tree_id; + + GIT_ASSERT_ARG(tree); + GIT_ASSERT_ARG(git_tree_owner(tree) == repo); + + tree_id = git_tree_id(tree); + + if ((error = validate_tree_and_parents(&parents_arr, repo, tree_id, commit_parent_from_array, &data, NULL, true)) < 0) + return error; + + error = git_commit__create_buffer_internal( + out, author, committer, + message_encoding, message, tree_id, + &parents_arr); + + git_array_clear(parents_arr); + return error; +} + +/** + * Append to 'out' properly marking continuations when there's a newline in 'content' + */ +static int format_header_field(git_str *out, const char *field, const char *content) +{ + const char *lf; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(field); + GIT_ASSERT_ARG(content); + + git_str_puts(out, field); + git_str_putc(out, ' '); + + while ((lf = strchr(content, '\n')) != NULL) { + git_str_put(out, content, lf - content); + git_str_puts(out, "\n "); + content = lf + 1; + } + + git_str_puts(out, content); + git_str_putc(out, '\n'); + + return git_str_oom(out) ? -1 : 0; +} + +static const git_oid *commit_parent_from_commit(size_t n, void *payload) +{ + const git_commit *commit = (const git_commit *) payload; + + return git_array_get(commit->parent_ids, n); + +} + +int git_commit_create_with_signature( + git_oid *out, + git_repository *repo, + const char *commit_content, + const char *signature, + const char *signature_field) +{ + git_odb *odb; + int error = 0; + const char *field; + const char *header_end; + git_str commit = GIT_STR_INIT; + git_commit *parsed; + git_array_oid_t parents = GIT_ARRAY_INIT; + + /* The first step is to verify that all the tree and parents exist */ + parsed = git__calloc(1, sizeof(git_commit)); + GIT_ERROR_CHECK_ALLOC(parsed); + if (commit_parse(parsed, commit_content, strlen(commit_content), 0) < 0) { + error = -1; + goto cleanup; + } + + if ((error = validate_tree_and_parents(&parents, repo, &parsed->tree_id, commit_parent_from_commit, parsed, NULL, true)) < 0) + goto cleanup; + + git_array_clear(parents); + + /* Then we start appending by identifying the end of the commit header */ + header_end = strstr(commit_content, "\n\n"); + if (!header_end) { + git_error_set(GIT_ERROR_INVALID, "malformed commit contents"); + error = -1; + goto cleanup; + } + + /* The header ends after the first LF */ + header_end++; + git_str_put(&commit, commit_content, header_end - commit_content); + + if (signature != NULL) { + field = signature_field ? signature_field : "gpgsig"; + + if ((error = format_header_field(&commit, field, signature)) < 0) + goto cleanup; + } + + git_str_puts(&commit, header_end); + + if (git_str_oom(&commit)) + return -1; + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) + goto cleanup; + + if ((error = git_odb_write(out, odb, commit.ptr, commit.size, GIT_OBJECT_COMMIT)) < 0) + goto cleanup; + +cleanup: + git_commit__free(parsed); + git_str_dispose(&commit); + return error; +} + +int git_commit_committer_with_mailmap( + git_signature **out, const git_commit *commit, const git_mailmap *mailmap) +{ + return git_mailmap_resolve_signature(out, mailmap, commit->committer); +} + +int git_commit_author_with_mailmap( + git_signature **out, const git_commit *commit, const git_mailmap *mailmap) +{ + return git_mailmap_resolve_signature(out, mailmap, commit->author); +} diff --git a/src/libgit2/commit.h b/src/libgit2/commit.h new file mode 100644 index 000000000..7a2454e61 --- /dev/null +++ b/src/libgit2/commit.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_commit_h__ +#define INCLUDE_commit_h__ + +#include "common.h" + +#include "git2/commit.h" +#include "tree.h" +#include "repository.h" +#include "array.h" + +#include + +struct git_commit { + git_object object; + + git_array_t(git_oid) parent_ids; + git_oid tree_id; + + git_signature *author; + git_signature *committer; + + char *message_encoding; + char *raw_message; + char *raw_header; + + char *summary; + char *body; +}; + +int git_commit__header_field( + git_str *out, + const git_commit *commit, + const char *field); + +int git_commit__extract_signature( + git_str *signature, + git_str *signed_data, + git_repository *repo, + git_oid *commit_id, + const char *field); + +int git_commit__create_buffer( + git_str *out, + git_repository *repo, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[]); + +void git_commit__free(void *commit); +int git_commit__parse(void *commit, git_odb_object *obj); +int git_commit__parse_raw(void *commit, const char *data, size_t size); + +typedef enum { + GIT_COMMIT_PARSE_QUICK = (1 << 0) /**< Only parse parents and committer info */ +} git_commit__parse_flags; + +int git_commit__parse_ext(git_commit *commit, git_odb_object *odb_obj, unsigned int flags); + +#endif diff --git a/src/libgit2/commit_graph.c b/src/libgit2/commit_graph.c new file mode 100644 index 000000000..70e866b92 --- /dev/null +++ b/src/libgit2/commit_graph.c @@ -0,0 +1,1224 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "commit_graph.h" + +#include "array.h" +#include "buf.h" +#include "filebuf.h" +#include "futils.h" +#include "hash.h" +#include "oidarray.h" +#include "oidmap.h" +#include "pack.h" +#include "repository.h" +#include "revwalk.h" + +#define GIT_COMMIT_GRAPH_MISSING_PARENT 0x70000000 +#define GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX 0x3FFFFFFF +#define GIT_COMMIT_GRAPH_GENERATION_NUMBER_INFINITY 0xFFFFFFFF + +#define COMMIT_GRAPH_SIGNATURE 0x43475048 /* "CGPH" */ +#define COMMIT_GRAPH_VERSION 1 +#define COMMIT_GRAPH_OBJECT_ID_VERSION 1 +struct git_commit_graph_header { + uint32_t signature; + uint8_t version; + uint8_t object_id_version; + uint8_t chunks; + uint8_t base_graph_files; +}; + +#define COMMIT_GRAPH_OID_FANOUT_ID 0x4f494446 /* "OIDF" */ +#define COMMIT_GRAPH_OID_LOOKUP_ID 0x4f49444c /* "OIDL" */ +#define COMMIT_GRAPH_COMMIT_DATA_ID 0x43444154 /* "CDAT" */ +#define COMMIT_GRAPH_EXTRA_EDGE_LIST_ID 0x45444745 /* "EDGE" */ +#define COMMIT_GRAPH_BLOOM_FILTER_INDEX_ID 0x42494458 /* "BIDX" */ +#define COMMIT_GRAPH_BLOOM_FILTER_DATA_ID 0x42444154 /* "BDAT" */ + +struct git_commit_graph_chunk { + off64_t offset; + size_t length; +}; + +typedef git_array_t(size_t) parent_index_array_t; + +struct packed_commit { + size_t index; + git_oid sha1; + git_oid tree_oid; + uint32_t generation; + git_time_t commit_time; + git_array_oid_t parents; + parent_index_array_t parent_indices; +}; + +static void packed_commit_free(struct packed_commit *p) +{ + if (!p) + return; + + git_array_clear(p->parents); + git_array_clear(p->parent_indices); + git__free(p); +} + +static struct packed_commit *packed_commit_new(git_commit *commit) +{ + unsigned int i, parentcount = git_commit_parentcount(commit); + struct packed_commit *p = git__calloc(1, sizeof(struct packed_commit)); + if (!p) + goto cleanup; + + git_array_init_to_size(p->parents, parentcount); + if (parentcount && !p->parents.ptr) + goto cleanup; + + if (git_oid_cpy(&p->sha1, git_commit_id(commit)) < 0) + goto cleanup; + if (git_oid_cpy(&p->tree_oid, git_commit_tree_id(commit)) < 0) + goto cleanup; + p->commit_time = git_commit_time(commit); + + for (i = 0; i < parentcount; ++i) { + git_oid *parent_id = git_array_alloc(p->parents); + if (!parent_id) + goto cleanup; + if (git_oid_cpy(parent_id, git_commit_parent_id(commit, i)) < 0) + goto cleanup; + } + + return p; + +cleanup: + packed_commit_free(p); + return NULL; +} + +typedef int (*commit_graph_write_cb)(const char *buf, size_t size, void *cb_data); + +static int commit_graph_error(const char *message) +{ + git_error_set(GIT_ERROR_ODB, "invalid commit-graph file - %s", message); + return -1; +} + +static int commit_graph_parse_oid_fanout( + git_commit_graph_file *file, + const unsigned char *data, + struct git_commit_graph_chunk *chunk_oid_fanout) +{ + uint32_t i, nr; + if (chunk_oid_fanout->offset == 0) + return commit_graph_error("missing OID Fanout chunk"); + if (chunk_oid_fanout->length == 0) + return commit_graph_error("empty OID Fanout chunk"); + if (chunk_oid_fanout->length != 256 * 4) + return commit_graph_error("OID Fanout chunk has wrong length"); + + file->oid_fanout = (const uint32_t *)(data + chunk_oid_fanout->offset); + nr = 0; + for (i = 0; i < 256; ++i) { + uint32_t n = ntohl(file->oid_fanout[i]); + if (n < nr) + return commit_graph_error("index is non-monotonic"); + nr = n; + } + file->num_commits = nr; + return 0; +} + +static int commit_graph_parse_oid_lookup( + git_commit_graph_file *file, + const unsigned char *data, + struct git_commit_graph_chunk *chunk_oid_lookup) +{ + uint32_t i; + git_oid *oid, *prev_oid, zero_oid = {{0}}; + + if (chunk_oid_lookup->offset == 0) + return commit_graph_error("missing OID Lookup chunk"); + if (chunk_oid_lookup->length == 0) + return commit_graph_error("empty OID Lookup chunk"); + if (chunk_oid_lookup->length != file->num_commits * GIT_OID_RAWSZ) + return commit_graph_error("OID Lookup chunk has wrong length"); + + file->oid_lookup = oid = (git_oid *)(data + chunk_oid_lookup->offset); + prev_oid = &zero_oid; + for (i = 0; i < file->num_commits; ++i, ++oid) { + if (git_oid_cmp(prev_oid, oid) >= 0) + return commit_graph_error("OID Lookup index is non-monotonic"); + prev_oid = oid; + } + + return 0; +} + +static int commit_graph_parse_commit_data( + git_commit_graph_file *file, + const unsigned char *data, + struct git_commit_graph_chunk *chunk_commit_data) +{ + if (chunk_commit_data->offset == 0) + return commit_graph_error("missing Commit Data chunk"); + if (chunk_commit_data->length == 0) + return commit_graph_error("empty Commit Data chunk"); + if (chunk_commit_data->length != file->num_commits * (GIT_OID_RAWSZ + 16)) + return commit_graph_error("Commit Data chunk has wrong length"); + + file->commit_data = data + chunk_commit_data->offset; + + return 0; +} + +static int commit_graph_parse_extra_edge_list( + git_commit_graph_file *file, + const unsigned char *data, + struct git_commit_graph_chunk *chunk_extra_edge_list) +{ + if (chunk_extra_edge_list->length == 0) + return 0; + if (chunk_extra_edge_list->length % 4 != 0) + return commit_graph_error("malformed Extra Edge List chunk"); + + file->extra_edge_list = data + chunk_extra_edge_list->offset; + file->num_extra_edge_list = chunk_extra_edge_list->length / 4; + + return 0; +} + +int git_commit_graph_file_parse( + git_commit_graph_file *file, + const unsigned char *data, + size_t size) +{ + struct git_commit_graph_header *hdr; + const unsigned char *chunk_hdr; + struct git_commit_graph_chunk *last_chunk; + uint32_t i; + off64_t last_chunk_offset, chunk_offset, trailer_offset; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size; + int error; + struct git_commit_graph_chunk chunk_oid_fanout = {0}, chunk_oid_lookup = {0}, + chunk_commit_data = {0}, chunk_extra_edge_list = {0}, + chunk_unsupported = {0}; + + GIT_ASSERT_ARG(file); + + if (size < sizeof(struct git_commit_graph_header) + GIT_OID_RAWSZ) + return commit_graph_error("commit-graph is too short"); + + hdr = ((struct git_commit_graph_header *)data); + + if (hdr->signature != htonl(COMMIT_GRAPH_SIGNATURE) || hdr->version != COMMIT_GRAPH_VERSION + || hdr->object_id_version != COMMIT_GRAPH_OBJECT_ID_VERSION) { + return commit_graph_error("unsupported commit-graph version"); + } + if (hdr->chunks == 0) + return commit_graph_error("no chunks in commit-graph"); + + /* + * The very first chunk's offset should be after the header, all the chunk + * headers, and a special zero chunk. + */ + last_chunk_offset = sizeof(struct git_commit_graph_header) + (1 + hdr->chunks) * 12; + trailer_offset = size - GIT_OID_RAWSZ; + checksum_size = GIT_HASH_SHA1_SIZE; + + if (trailer_offset < last_chunk_offset) + return commit_graph_error("wrong commit-graph size"); + memcpy(file->checksum, (data + trailer_offset), checksum_size); + + if (git_hash_buf(checksum, data, (size_t)trailer_offset, GIT_HASH_ALGORITHM_SHA1) < 0) + return commit_graph_error("could not calculate signature"); + if (memcmp(checksum, file->checksum, checksum_size) != 0) + return commit_graph_error("index signature mismatch"); + + chunk_hdr = data + sizeof(struct git_commit_graph_header); + last_chunk = NULL; + for (i = 0; i < hdr->chunks; ++i, chunk_hdr += 12) { + chunk_offset = ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 4)))) << 32 + | ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 8)))); + if (chunk_offset < last_chunk_offset) + return commit_graph_error("chunks are non-monotonic"); + if (chunk_offset >= trailer_offset) + return commit_graph_error("chunks extend beyond the trailer"); + if (last_chunk != NULL) + last_chunk->length = (size_t)(chunk_offset - last_chunk_offset); + last_chunk_offset = chunk_offset; + + switch (ntohl(*((uint32_t *)(chunk_hdr + 0)))) { + case COMMIT_GRAPH_OID_FANOUT_ID: + chunk_oid_fanout.offset = last_chunk_offset; + last_chunk = &chunk_oid_fanout; + break; + + case COMMIT_GRAPH_OID_LOOKUP_ID: + chunk_oid_lookup.offset = last_chunk_offset; + last_chunk = &chunk_oid_lookup; + break; + + case COMMIT_GRAPH_COMMIT_DATA_ID: + chunk_commit_data.offset = last_chunk_offset; + last_chunk = &chunk_commit_data; + break; + + case COMMIT_GRAPH_EXTRA_EDGE_LIST_ID: + chunk_extra_edge_list.offset = last_chunk_offset; + last_chunk = &chunk_extra_edge_list; + break; + + case COMMIT_GRAPH_BLOOM_FILTER_INDEX_ID: + case COMMIT_GRAPH_BLOOM_FILTER_DATA_ID: + chunk_unsupported.offset = last_chunk_offset; + last_chunk = &chunk_unsupported; + break; + + default: + return commit_graph_error("unrecognized chunk ID"); + } + } + last_chunk->length = (size_t)(trailer_offset - last_chunk_offset); + + error = commit_graph_parse_oid_fanout(file, data, &chunk_oid_fanout); + if (error < 0) + return error; + error = commit_graph_parse_oid_lookup(file, data, &chunk_oid_lookup); + if (error < 0) + return error; + error = commit_graph_parse_commit_data(file, data, &chunk_commit_data); + if (error < 0) + return error; + error = commit_graph_parse_extra_edge_list(file, data, &chunk_extra_edge_list); + if (error < 0) + return error; + + return 0; +} + +int git_commit_graph_new(git_commit_graph **cgraph_out, const char *objects_dir, bool open_file) +{ + git_commit_graph *cgraph = NULL; + int error = 0; + + GIT_ASSERT_ARG(cgraph_out); + GIT_ASSERT_ARG(objects_dir); + + cgraph = git__calloc(1, sizeof(git_commit_graph)); + GIT_ERROR_CHECK_ALLOC(cgraph); + + error = git_str_joinpath(&cgraph->filename, objects_dir, "info/commit-graph"); + if (error < 0) + goto error; + + if (open_file) { + error = git_commit_graph_file_open(&cgraph->file, git_str_cstr(&cgraph->filename)); + if (error < 0) + goto error; + cgraph->checked = 1; + } + + *cgraph_out = cgraph; + return 0; + +error: + git_commit_graph_free(cgraph); + return error; +} + +int git_commit_graph_open(git_commit_graph **cgraph_out, const char *objects_dir) +{ + return git_commit_graph_new(cgraph_out, objects_dir, true); +} + +int git_commit_graph_file_open(git_commit_graph_file **file_out, const char *path) +{ + git_commit_graph_file *file; + git_file fd = -1; + size_t cgraph_size; + struct stat st; + int error; + + /* TODO: properly open the file without access time using O_NOATIME */ + fd = git_futils_open_ro(path); + if (fd < 0) + return fd; + + if (p_fstat(fd, &st) < 0) { + p_close(fd); + git_error_set(GIT_ERROR_ODB, "commit-graph file not found - '%s'", path); + return GIT_ENOTFOUND; + } + + if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size)) { + p_close(fd); + git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path); + return GIT_ENOTFOUND; + } + cgraph_size = (size_t)st.st_size; + + file = git__calloc(1, sizeof(git_commit_graph_file)); + GIT_ERROR_CHECK_ALLOC(file); + + error = git_futils_mmap_ro(&file->graph_map, fd, 0, cgraph_size); + p_close(fd); + if (error < 0) { + git_commit_graph_file_free(file); + return error; + } + + if ((error = git_commit_graph_file_parse(file, file->graph_map.data, cgraph_size)) < 0) { + git_commit_graph_file_free(file); + return error; + } + + *file_out = file; + return 0; +} + +int git_commit_graph_get_file(git_commit_graph_file **file_out, git_commit_graph *cgraph) +{ + if (!cgraph->checked) { + int error = 0; + git_commit_graph_file *result = NULL; + + /* We only check once, no matter the result. */ + cgraph->checked = 1; + + /* Best effort */ + error = git_commit_graph_file_open(&result, git_str_cstr(&cgraph->filename)); + + if (error < 0) + return error; + + cgraph->file = result; + } + if (!cgraph->file) + return GIT_ENOTFOUND; + + *file_out = cgraph->file; + return 0; +} + +void git_commit_graph_refresh(git_commit_graph *cgraph) +{ + if (!cgraph->checked) + return; + + if (cgraph->file + && git_commit_graph_file_needs_refresh(cgraph->file, git_str_cstr(&cgraph->filename))) { + /* We just free the commit graph. The next time it is requested, it will be + * re-loaded. */ + git_commit_graph_file_free(cgraph->file); + cgraph->file = NULL; + } + /* Force a lazy re-check next time it is needed. */ + cgraph->checked = 0; +} + +static int git_commit_graph_entry_get_byindex( + git_commit_graph_entry *e, + const git_commit_graph_file *file, + size_t pos) +{ + const unsigned char *commit_data; + + GIT_ASSERT_ARG(e); + GIT_ASSERT_ARG(file); + + if (pos >= file->num_commits) { + git_error_set(GIT_ERROR_INVALID, "commit index %zu does not exist", pos); + return GIT_ENOTFOUND; + } + + commit_data = file->commit_data + pos * (GIT_OID_RAWSZ + 4 * sizeof(uint32_t)); + git_oid_cpy(&e->tree_oid, (const git_oid *)commit_data); + e->parent_indices[0] = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ))); + e->parent_indices[1] = ntohl( + *((uint32_t *)(commit_data + GIT_OID_RAWSZ + sizeof(uint32_t)))); + e->parent_count = (e->parent_indices[0] != GIT_COMMIT_GRAPH_MISSING_PARENT) + + (e->parent_indices[1] != GIT_COMMIT_GRAPH_MISSING_PARENT); + e->generation = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ + 2 * sizeof(uint32_t)))); + e->commit_time = ntohl(*((uint32_t *)(commit_data + GIT_OID_RAWSZ + 3 * sizeof(uint32_t)))); + + e->commit_time |= (e->generation & UINT64_C(0x3)) << UINT64_C(32); + e->generation >>= 2u; + if (e->parent_indices[1] & 0x80000000u) { + uint32_t extra_edge_list_pos = e->parent_indices[1] & 0x7fffffff; + + /* Make sure we're not being sent out of bounds */ + if (extra_edge_list_pos >= file->num_extra_edge_list) { + git_error_set(GIT_ERROR_INVALID, + "commit %u does not exist", + extra_edge_list_pos); + return GIT_ENOTFOUND; + } + + e->extra_parents_index = extra_edge_list_pos; + while (extra_edge_list_pos < file->num_extra_edge_list + && (ntohl(*( + (uint32_t *)(file->extra_edge_list + + extra_edge_list_pos * sizeof(uint32_t)))) + & 0x80000000u) + == 0) { + extra_edge_list_pos++; + e->parent_count++; + } + } + git_oid_cpy(&e->sha1, &file->oid_lookup[pos]); + return 0; +} + +bool git_commit_graph_file_needs_refresh(const git_commit_graph_file *file, const char *path) +{ + git_file fd = -1; + struct stat st; + ssize_t bytes_read; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + + /* TODO: properly open the file without access time using O_NOATIME */ + fd = git_futils_open_ro(path); + if (fd < 0) + return true; + + if (p_fstat(fd, &st) < 0) { + p_close(fd); + return true; + } + + if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size) + || (size_t)st.st_size != file->graph_map.len) { + p_close(fd); + return true; + } + + bytes_read = p_pread(fd, checksum, checksum_size, st.st_size - checksum_size); + p_close(fd); + if (bytes_read != (ssize_t)checksum_size) + return true; + + return (memcmp(checksum, file->checksum, checksum_size) != 0); +} + +int git_commit_graph_entry_find( + git_commit_graph_entry *e, + const git_commit_graph_file *file, + const git_oid *short_oid, + size_t len) +{ + int pos, found = 0; + uint32_t hi, lo; + const git_oid *current = NULL; + + GIT_ASSERT_ARG(e); + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(short_oid); + + hi = ntohl(file->oid_fanout[(int)short_oid->id[0]]); + lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(file->oid_fanout[(int)short_oid->id[0] - 1])); + + pos = git_pack__lookup_sha1(file->oid_lookup, GIT_OID_RAWSZ, lo, hi, short_oid->id); + + if (pos >= 0) { + /* An object matching exactly the oid was found */ + found = 1; + current = file->oid_lookup + pos; + } else { + /* No object was found */ + /* pos refers to the object with the "closest" oid to short_oid */ + pos = -1 - pos; + if (pos < (int)file->num_commits) { + current = file->oid_lookup + pos; + + if (!git_oid_ncmp(short_oid, current, len)) + found = 1; + } + } + + if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)file->num_commits) { + /* Check for ambiguousity */ + const git_oid *next = current + 1; + + if (!git_oid_ncmp(short_oid, next, len)) { + found = 2; + } + } + + if (!found) + return git_odb__error_notfound( + "failed to find offset for commit-graph index entry", short_oid, len); + if (found > 1) + return git_odb__error_ambiguous( + "found multiple offsets for commit-graph index entry"); + + return git_commit_graph_entry_get_byindex(e, file, pos); +} + +int git_commit_graph_entry_parent( + git_commit_graph_entry *parent, + const git_commit_graph_file *file, + const git_commit_graph_entry *entry, + size_t n) +{ + GIT_ASSERT_ARG(parent); + GIT_ASSERT_ARG(file); + + if (n >= entry->parent_count) { + git_error_set(GIT_ERROR_INVALID, "parent index %zu does not exist", n); + return GIT_ENOTFOUND; + } + + if (n == 0 || (n == 1 && entry->parent_count == 2)) + return git_commit_graph_entry_get_byindex(parent, file, entry->parent_indices[n]); + + return git_commit_graph_entry_get_byindex( + parent, + file, + ntohl( + *(uint32_t *)(file->extra_edge_list + + (entry->extra_parents_index + n - 1) + * sizeof(uint32_t))) + & 0x7fffffff); +} + +int git_commit_graph_file_close(git_commit_graph_file *file) +{ + GIT_ASSERT_ARG(file); + + if (file->graph_map.data) + git_futils_mmap_free(&file->graph_map); + + return 0; +} + +void git_commit_graph_free(git_commit_graph *cgraph) +{ + if (!cgraph) + return; + + git_str_dispose(&cgraph->filename); + git_commit_graph_file_free(cgraph->file); + git__free(cgraph); +} + +void git_commit_graph_file_free(git_commit_graph_file *file) +{ + if (!file) + return; + + git_commit_graph_file_close(file); + git__free(file); +} + +static int packed_commit__cmp(const void *a_, const void *b_) +{ + const struct packed_commit *a = a_; + const struct packed_commit *b = b_; + return git_oid_cmp(&a->sha1, &b->sha1); +} + +int git_commit_graph_writer_new(git_commit_graph_writer **out, const char *objects_info_dir) +{ + git_commit_graph_writer *w = git__calloc(1, sizeof(git_commit_graph_writer)); + GIT_ERROR_CHECK_ALLOC(w); + + if (git_str_sets(&w->objects_info_dir, objects_info_dir) < 0) { + git__free(w); + return -1; + } + + if (git_vector_init(&w->commits, 0, packed_commit__cmp) < 0) { + git_str_dispose(&w->objects_info_dir); + git__free(w); + return -1; + } + + *out = w; + return 0; +} + +void git_commit_graph_writer_free(git_commit_graph_writer *w) +{ + struct packed_commit *packed_commit; + size_t i; + + if (!w) + return; + + git_vector_foreach (&w->commits, i, packed_commit) + packed_commit_free(packed_commit); + git_vector_free(&w->commits); + git_str_dispose(&w->objects_info_dir); + git__free(w); +} + +struct object_entry_cb_state { + git_repository *repo; + git_odb *db; + git_vector *commits; +}; + +static int object_entry__cb(const git_oid *id, void *data) +{ + struct object_entry_cb_state *state = (struct object_entry_cb_state *)data; + git_commit *commit = NULL; + struct packed_commit *packed_commit = NULL; + size_t header_len; + git_object_t header_type; + int error = 0; + + error = git_odb_read_header(&header_len, &header_type, state->db, id); + if (error < 0) + return error; + + if (header_type != GIT_OBJECT_COMMIT) + return 0; + + error = git_commit_lookup(&commit, state->repo, id); + if (error < 0) + return error; + + packed_commit = packed_commit_new(commit); + git_commit_free(commit); + GIT_ERROR_CHECK_ALLOC(packed_commit); + + error = git_vector_insert(state->commits, packed_commit); + if (error < 0) { + packed_commit_free(packed_commit); + return error; + } + + return 0; +} + +int git_commit_graph_writer_add_index_file( + git_commit_graph_writer *w, + git_repository *repo, + const char *idx_path) +{ + int error; + struct git_pack_file *p = NULL; + struct object_entry_cb_state state = {0}; + state.repo = repo; + state.commits = &w->commits; + + error = git_repository_odb(&state.db, repo); + if (error < 0) + goto cleanup; + + error = git_mwindow_get_pack(&p, idx_path); + if (error < 0) + goto cleanup; + + error = git_pack_foreach_entry(p, object_entry__cb, &state); + if (error < 0) + goto cleanup; + +cleanup: + if (p) + git_mwindow_put_pack(p); + git_odb_free(state.db); + return error; +} + +int git_commit_graph_writer_add_revwalk(git_commit_graph_writer *w, git_revwalk *walk) +{ + int error; + git_oid id; + git_repository *repo = git_revwalk_repository(walk); + git_commit *commit; + struct packed_commit *packed_commit; + + while ((git_revwalk_next(&id, walk)) == 0) { + error = git_commit_lookup(&commit, repo, &id); + if (error < 0) + return error; + + packed_commit = packed_commit_new(commit); + git_commit_free(commit); + GIT_ERROR_CHECK_ALLOC(packed_commit); + + error = git_vector_insert(&w->commits, packed_commit); + if (error < 0) { + packed_commit_free(packed_commit); + return error; + } + } + + return 0; +} + +enum generation_number_commit_state { + GENERATION_NUMBER_COMMIT_STATE_UNVISITED = 0, + GENERATION_NUMBER_COMMIT_STATE_ADDED = 1, + GENERATION_NUMBER_COMMIT_STATE_EXPANDED = 2, + GENERATION_NUMBER_COMMIT_STATE_VISITED = 3 +}; + +static int compute_generation_numbers(git_vector *commits) +{ + git_array_t(size_t) index_stack = GIT_ARRAY_INIT; + size_t i, j; + size_t *parent_idx; + enum generation_number_commit_state *commit_states = NULL; + struct packed_commit *child_packed_commit; + git_oidmap *packed_commit_map = NULL; + int error = 0; + + /* First populate the parent indices fields */ + error = git_oidmap_new(&packed_commit_map); + if (error < 0) + goto cleanup; + git_vector_foreach (commits, i, child_packed_commit) { + child_packed_commit->index = i; + error = git_oidmap_set( + packed_commit_map, &child_packed_commit->sha1, child_packed_commit); + if (error < 0) + goto cleanup; + } + + git_vector_foreach (commits, i, child_packed_commit) { + size_t parent_i, *parent_idx_ptr; + struct packed_commit *parent_packed_commit; + git_oid *parent_id; + git_array_init_to_size( + child_packed_commit->parent_indices, + git_array_size(child_packed_commit->parents)); + if (git_array_size(child_packed_commit->parents) + && !child_packed_commit->parent_indices.ptr) { + error = -1; + goto cleanup; + } + git_array_foreach (child_packed_commit->parents, parent_i, parent_id) { + parent_packed_commit = git_oidmap_get(packed_commit_map, parent_id); + if (!parent_packed_commit) { + git_error_set(GIT_ERROR_ODB, + "parent commit %s not found in commit graph", + git_oid_tostr_s(parent_id)); + error = GIT_ENOTFOUND; + goto cleanup; + } + parent_idx_ptr = git_array_alloc(child_packed_commit->parent_indices); + if (!parent_idx_ptr) { + error = -1; + goto cleanup; + } + *parent_idx_ptr = parent_packed_commit->index; + } + } + + /* + * We copy all the commits to the stack and then during visitation, + * each node can be added up to two times to the stack. + */ + git_array_init_to_size(index_stack, 3 * git_vector_length(commits)); + if (!index_stack.ptr) { + error = -1; + goto cleanup; + } + + commit_states = (enum generation_number_commit_state *)git__calloc( + git_vector_length(commits), sizeof(enum generation_number_commit_state)); + if (!commit_states) { + error = -1; + goto cleanup; + } + + /* + * Perform a Post-Order traversal so that all parent nodes are fully + * visited before the child node. + */ + git_vector_foreach (commits, i, child_packed_commit) + *(size_t *)git_array_alloc(index_stack) = i; + + while (git_array_size(index_stack)) { + size_t *index_ptr = git_array_pop(index_stack); + i = *index_ptr; + child_packed_commit = git_vector_get(commits, i); + + if (commit_states[i] == GENERATION_NUMBER_COMMIT_STATE_VISITED) { + /* This commit has already been fully visited. */ + continue; + } + if (commit_states[i] == GENERATION_NUMBER_COMMIT_STATE_EXPANDED) { + /* All of the commits parents have been visited. */ + child_packed_commit->generation = 0; + git_array_foreach (child_packed_commit->parent_indices, j, parent_idx) { + struct packed_commit *parent = git_vector_get(commits, *parent_idx); + if (child_packed_commit->generation < parent->generation) + child_packed_commit->generation = parent->generation; + } + if (child_packed_commit->generation + < GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX) { + ++child_packed_commit->generation; + } + commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_VISITED; + continue; + } + + /* + * This is the first time we see this commit. We need + * to visit all its parents before we can fully visit + * it. + */ + if (git_array_size(child_packed_commit->parent_indices) == 0) { + /* + * Special case: if the commit has no parents, there's + * no need to add it to the stack just to immediately + * remove it. + */ + commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_VISITED; + child_packed_commit->generation = 1; + continue; + } + + /* + * Add this current commit again so that it is visited + * again once all its children have been visited. + */ + *(size_t *)git_array_alloc(index_stack) = i; + git_array_foreach (child_packed_commit->parent_indices, j, parent_idx) { + if (commit_states[*parent_idx] + != GENERATION_NUMBER_COMMIT_STATE_UNVISITED) { + /* This commit has already been considered. */ + continue; + } + + commit_states[*parent_idx] = GENERATION_NUMBER_COMMIT_STATE_ADDED; + *(size_t *)git_array_alloc(index_stack) = *parent_idx; + } + commit_states[i] = GENERATION_NUMBER_COMMIT_STATE_EXPANDED; + } + +cleanup: + git_oidmap_free(packed_commit_map); + git__free(commit_states); + git_array_clear(index_stack); + + return error; +} + +static int write_offset(off64_t offset, commit_graph_write_cb write_cb, void *cb_data) +{ + int error; + uint32_t word; + + word = htonl((uint32_t)((offset >> 32) & 0xffffffffu)); + error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + word = htonl((uint32_t)((offset >> 0) & 0xffffffffu)); + error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + + return 0; +} + +static int write_chunk_header( + int chunk_id, + off64_t offset, + commit_graph_write_cb write_cb, + void *cb_data) +{ + uint32_t word = htonl(chunk_id); + int error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + return write_offset(offset, write_cb, cb_data); +} + +static int commit_graph_write_buf(const char *buf, size_t size, void *data) +{ + git_str *b = (git_str *)data; + return git_str_put(b, buf, size); +} + +struct commit_graph_write_hash_context { + commit_graph_write_cb write_cb; + void *cb_data; + git_hash_ctx *ctx; +}; + +static int commit_graph_write_hash(const char *buf, size_t size, void *data) +{ + struct commit_graph_write_hash_context *ctx = data; + int error; + + error = git_hash_update(ctx->ctx, buf, size); + if (error < 0) + return error; + + return ctx->write_cb(buf, size, ctx->cb_data); +} + +static void packed_commit_free_dup(void *packed_commit) +{ + packed_commit_free(packed_commit); +} + +static int commit_graph_write( + git_commit_graph_writer *w, + commit_graph_write_cb write_cb, + void *cb_data) +{ + int error = 0; + size_t i; + struct packed_commit *packed_commit; + struct git_commit_graph_header hdr = {0}; + uint32_t oid_fanout_count; + uint32_t extra_edge_list_count; + uint32_t oid_fanout[256]; + off64_t offset; + git_str oid_lookup = GIT_STR_INIT, commit_data = GIT_STR_INIT, + extra_edge_list = GIT_STR_INIT; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size; + git_hash_ctx ctx; + struct commit_graph_write_hash_context hash_cb_data = {0}; + + hdr.signature = htonl(COMMIT_GRAPH_SIGNATURE); + hdr.version = COMMIT_GRAPH_VERSION; + hdr.object_id_version = COMMIT_GRAPH_OBJECT_ID_VERSION; + hdr.chunks = 0; + hdr.base_graph_files = 0; + hash_cb_data.write_cb = write_cb; + hash_cb_data.cb_data = cb_data; + hash_cb_data.ctx = &ctx; + + checksum_size = GIT_HASH_SHA1_SIZE; + error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1); + if (error < 0) + return error; + cb_data = &hash_cb_data; + write_cb = commit_graph_write_hash; + + /* Sort the commits. */ + git_vector_sort(&w->commits); + git_vector_uniq(&w->commits, packed_commit_free_dup); + error = compute_generation_numbers(&w->commits); + if (error < 0) + goto cleanup; + + /* Fill the OID Fanout table. */ + oid_fanout_count = 0; + for (i = 0; i < 256; i++) { + while (oid_fanout_count < git_vector_length(&w->commits) && + (packed_commit = (struct packed_commit *)git_vector_get(&w->commits, oid_fanout_count)) && + packed_commit->sha1.id[0] <= i) + ++oid_fanout_count; + oid_fanout[i] = htonl(oid_fanout_count); + } + + /* Fill the OID Lookup table. */ + git_vector_foreach (&w->commits, i, packed_commit) { + error = git_str_put(&oid_lookup, + (const char *)&packed_commit->sha1, sizeof(git_oid)); + if (error < 0) + goto cleanup; + } + + /* Fill the Commit Data and Extra Edge List tables. */ + extra_edge_list_count = 0; + git_vector_foreach (&w->commits, i, packed_commit) { + uint64_t commit_time; + uint32_t generation; + uint32_t word; + size_t *packed_index; + unsigned int parentcount = (unsigned int)git_array_size(packed_commit->parents); + + error = git_str_put(&commit_data, + (const char *)&packed_commit->tree_oid, + sizeof(git_oid)); + if (error < 0) + goto cleanup; + + if (parentcount == 0) { + word = htonl(GIT_COMMIT_GRAPH_MISSING_PARENT); + } else { + packed_index = git_array_get(packed_commit->parent_indices, 0); + word = htonl((uint32_t)*packed_index); + } + error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + + if (parentcount < 2) { + word = htonl(GIT_COMMIT_GRAPH_MISSING_PARENT); + } else if (parentcount == 2) { + packed_index = git_array_get(packed_commit->parent_indices, 1); + word = htonl((uint32_t)*packed_index); + } else { + word = htonl(0x80000000u | extra_edge_list_count); + } + error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + + if (parentcount > 2) { + unsigned int parent_i; + for (parent_i = 1; parent_i < parentcount; ++parent_i) { + packed_index = git_array_get( + packed_commit->parent_indices, parent_i); + word = htonl((uint32_t)(*packed_index | (parent_i + 1 == parentcount ? 0x80000000u : 0))); + + error = git_str_put(&extra_edge_list, + (const char *)&word, + sizeof(word)); + if (error < 0) + goto cleanup; + } + extra_edge_list_count += parentcount - 1; + } + + generation = packed_commit->generation; + commit_time = (uint64_t)packed_commit->commit_time; + if (generation > GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX) + generation = GIT_COMMIT_GRAPH_GENERATION_NUMBER_MAX; + word = ntohl((uint32_t)((generation << 2) | (((uint32_t)(commit_time >> 32)) & 0x3) )); + error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + word = ntohl((uint32_t)(commit_time & 0xfffffffful)); + error = git_str_put(&commit_data, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + } + + /* Write the header. */ + hdr.chunks = 3; + if (git_str_len(&extra_edge_list) > 0) + hdr.chunks++; + error = write_cb((const char *)&hdr, sizeof(hdr), cb_data); + if (error < 0) + goto cleanup; + + /* Write the chunk headers. */ + offset = sizeof(hdr) + (hdr.chunks + 1) * 12; + error = write_chunk_header(COMMIT_GRAPH_OID_FANOUT_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += sizeof(oid_fanout); + error = write_chunk_header(COMMIT_GRAPH_OID_LOOKUP_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&oid_lookup); + error = write_chunk_header(COMMIT_GRAPH_COMMIT_DATA_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&commit_data); + if (git_str_len(&extra_edge_list) > 0) { + error = write_chunk_header( + COMMIT_GRAPH_EXTRA_EDGE_LIST_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&extra_edge_list); + } + error = write_chunk_header(0, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + + /* Write all the chunks. */ + error = write_cb((const char *)oid_fanout, sizeof(oid_fanout), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_str_cstr(&oid_lookup), git_str_len(&oid_lookup), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_str_cstr(&commit_data), git_str_len(&commit_data), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_str_cstr(&extra_edge_list), git_str_len(&extra_edge_list), cb_data); + if (error < 0) + goto cleanup; + + /* Finalize the checksum and write the trailer. */ + error = git_hash_final(checksum, &ctx); + if (error < 0) + goto cleanup; + error = write_cb((char *)checksum, checksum_size, cb_data); + if (error < 0) + goto cleanup; + +cleanup: + git_str_dispose(&oid_lookup); + git_str_dispose(&commit_data); + git_str_dispose(&extra_edge_list); + git_hash_ctx_cleanup(&ctx); + return error; +} + +static int commit_graph_write_filebuf(const char *buf, size_t size, void *data) +{ + git_filebuf *f = (git_filebuf *)data; + return git_filebuf_write(f, buf, size); +} + +int git_commit_graph_writer_options_init( + git_commit_graph_writer_options *opts, + unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, + version, + git_commit_graph_writer_options, + GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT); + return 0; +} + +int git_commit_graph_writer_commit( + git_commit_graph_writer *w, + git_commit_graph_writer_options *opts) +{ + int error; + int filebuf_flags = GIT_FILEBUF_DO_NOT_BUFFER; + git_str commit_graph_path = GIT_STR_INIT; + git_filebuf output = GIT_FILEBUF_INIT; + + /* TODO: support options and fill in defaults. */ + GIT_UNUSED(opts); + + error = git_str_joinpath( + &commit_graph_path, git_str_cstr(&w->objects_info_dir), "commit-graph"); + if (error < 0) + return error; + + if (git_repository__fsync_gitdir) + filebuf_flags |= GIT_FILEBUF_FSYNC; + error = git_filebuf_open(&output, git_str_cstr(&commit_graph_path), filebuf_flags, 0644); + git_str_dispose(&commit_graph_path); + if (error < 0) + return error; + + error = commit_graph_write(w, commit_graph_write_filebuf, &output); + if (error < 0) { + git_filebuf_cleanup(&output); + return error; + } + + return git_filebuf_commit(&output); +} + +int git_commit_graph_writer_dump( + git_buf *cgraph, + git_commit_graph_writer *w, + git_commit_graph_writer_options *opts) +{ + GIT_BUF_WRAP_PRIVATE(cgraph, git_commit_graph__writer_dump, w, opts); +} + +int git_commit_graph__writer_dump( + git_str *cgraph, + git_commit_graph_writer *w, + git_commit_graph_writer_options *opts) +{ + /* TODO: support options. */ + GIT_UNUSED(opts); + return commit_graph_write(w, commit_graph_write_buf, cgraph); +} diff --git a/src/libgit2/commit_graph.h b/src/libgit2/commit_graph.h new file mode 100644 index 000000000..45e125b9e --- /dev/null +++ b/src/libgit2/commit_graph.h @@ -0,0 +1,169 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_commit_graph_h__ +#define INCLUDE_commit_graph_h__ + +#include "common.h" + +#include "git2/types.h" +#include "git2/sys/commit_graph.h" + +#include "map.h" +#include "vector.h" +#include "oid.h" +#include "hash.h" + +/** + * A commit-graph file. + * + * This file contains metadata about commits, particularly the generation + * number for each one. This can help speed up graph operations without + * requiring a full graph traversal. + * + * Support for this feature was added in git 2.19. + */ +typedef struct git_commit_graph_file { + git_map graph_map; + + /* The OID Fanout table. */ + const uint32_t *oid_fanout; + /* The total number of commits in the graph. */ + uint32_t num_commits; + + /* The OID Lookup table. */ + git_oid *oid_lookup; + + /* + * The Commit Data table. Each entry contains the OID of the commit followed + * by two 8-byte fields in network byte order: + * - The indices of the first two parents (32 bits each). + * - The generation number (first 30 bits) and commit time in seconds since + * UNIX epoch (34 bits). + */ + const unsigned char *commit_data; + + /* + * The Extra Edge List table. Each 4-byte entry is a network byte order index + * of one of the i-th (i > 0) parents of commits in the `commit_data` table, + * when the commit has more than 2 parents. + */ + const unsigned char *extra_edge_list; + /* The number of entries in the Extra Edge List table. Each entry is 4 bytes wide. */ + size_t num_extra_edge_list; + + /* The trailer of the file. Contains the SHA1-checksum of the whole file. */ + unsigned char checksum[GIT_HASH_SHA1_SIZE]; +} git_commit_graph_file; + +/** + * An entry in the commit-graph file. Provides a subset of the information that + * can be obtained from the commit header. + */ +typedef struct git_commit_graph_entry { + /* The generation number of the commit within the graph */ + size_t generation; + + /* Time in seconds from UNIX epoch. */ + git_time_t commit_time; + + /* The number of parents of the commit. */ + size_t parent_count; + + /* + * The indices of the parent commits within the Commit Data table. The value + * of `GIT_COMMIT_GRAPH_MISSING_PARENT` indicates that no parent is in that + * position. + */ + size_t parent_indices[2]; + + /* The index within the Extra Edge List of any parent after the first two. */ + size_t extra_parents_index; + + /* The SHA-1 hash of the root tree of the commit. */ + git_oid tree_oid; + + /* The SHA-1 hash of the requested commit. */ + git_oid sha1; +} git_commit_graph_entry; + +/* A wrapper for git_commit_graph_file to enable lazy loading in the ODB. */ +struct git_commit_graph { + /* The path to the commit-graph file. Something like ".git/objects/info/commit-graph". */ + git_str filename; + + /* The underlying commit-graph file. */ + git_commit_graph_file *file; + + /* Whether the commit-graph file was already checked for validity. */ + bool checked; +}; + +/** Create a new commit-graph, optionally opening the underlying file. */ +int git_commit_graph_new(git_commit_graph **cgraph_out, const char *objects_dir, bool open_file); + +/** Open and validate a commit-graph file. */ +int git_commit_graph_file_open(git_commit_graph_file **file_out, const char *path); + +/* + * Attempt to get the git_commit_graph's commit-graph file. This object is + * still owned by the git_commit_graph. If the repository does not contain a commit graph, + * it will return GIT_ENOTFOUND. + * + * This function is not thread-safe. + */ +int git_commit_graph_get_file(git_commit_graph_file **file_out, git_commit_graph *cgraph); + +/* Marks the commit-graph file as needing a refresh. */ +void git_commit_graph_refresh(git_commit_graph *cgraph); + +/* + * A writer for `commit-graph` files. + */ +struct git_commit_graph_writer { + /* + * The path of the `objects/info` directory where the `commit-graph` will be + * stored. + */ + git_str objects_info_dir; + + /* The list of packed commits. */ + git_vector commits; +}; + +int git_commit_graph__writer_dump( + git_str *cgraph, + git_commit_graph_writer *w, + git_commit_graph_writer_options *opts); + +/* + * Returns whether the git_commit_graph_file needs to be reloaded since the + * contents of the commit-graph file have changed on disk. + */ +bool git_commit_graph_file_needs_refresh( + const git_commit_graph_file *file, const char *path); + +int git_commit_graph_entry_find( + git_commit_graph_entry *e, + const git_commit_graph_file *file, + const git_oid *short_oid, + size_t len); +int git_commit_graph_entry_parent( + git_commit_graph_entry *parent, + const git_commit_graph_file *file, + const git_commit_graph_entry *entry, + size_t n); +int git_commit_graph_file_close(git_commit_graph_file *cgraph); +void git_commit_graph_file_free(git_commit_graph_file *cgraph); + +/* This is exposed for use in the fuzzers. */ +int git_commit_graph_file_parse( + git_commit_graph_file *file, + const unsigned char *data, + size_t size); + +#endif diff --git a/src/libgit2/commit_list.c b/src/libgit2/commit_list.c new file mode 100644 index 000000000..4585508bc --- /dev/null +++ b/src/libgit2/commit_list.c @@ -0,0 +1,208 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "commit_list.h" + +#include "revwalk.h" +#include "pool.h" +#include "odb.h" +#include "commit.h" + +int git_commit_list_generation_cmp(const void *a, const void *b) +{ + uint32_t generation_a = ((git_commit_list_node *) a)->generation; + uint32_t generation_b = ((git_commit_list_node *) b)->generation; + + if (!generation_a || !generation_b) { + /* Fall back to comparing by timestamps if at least one commit lacks a generation. */ + return git_commit_list_time_cmp(a, b); + } + + if (generation_a < generation_b) + return 1; + if (generation_a > generation_b) + return -1; + + return 0; +} + +int git_commit_list_time_cmp(const void *a, const void *b) +{ + int64_t time_a = ((git_commit_list_node *) a)->time; + int64_t time_b = ((git_commit_list_node *) b)->time; + + if (time_a < time_b) + return 1; + if (time_a > time_b) + return -1; + + return 0; +} + +git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p) +{ + git_commit_list *new_list = git__malloc(sizeof(git_commit_list)); + if (new_list != NULL) { + new_list->item = item; + new_list->next = *list_p; + } + *list_p = new_list; + return new_list; +} + +git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p) +{ + git_commit_list **pp = list_p; + git_commit_list *p; + + while ((p = *pp) != NULL) { + if (git_commit_list_time_cmp(p->item, item) > 0) + break; + + pp = &p->next; + } + + return git_commit_list_insert(item, pp); +} + +git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk) +{ + return (git_commit_list_node *)git_pool_mallocz(&walk->commit_pool, 1); +} + +static git_commit_list_node **alloc_parents( + git_revwalk *walk, git_commit_list_node *commit, size_t n_parents) +{ + size_t bytes; + + if (n_parents <= PARENTS_PER_COMMIT) + return (git_commit_list_node **)((char *)commit + sizeof(git_commit_list_node)); + + if (git__multiply_sizet_overflow(&bytes, n_parents, sizeof(git_commit_list_node *))) + return NULL; + + return (git_commit_list_node **)git_pool_malloc(&walk->commit_pool, bytes); +} + + +void git_commit_list_free(git_commit_list **list_p) +{ + git_commit_list *list = *list_p; + + if (list == NULL) + return; + + while (list) { + git_commit_list *temp = list; + list = temp->next; + git__free(temp); + } + + *list_p = NULL; +} + +git_commit_list_node *git_commit_list_pop(git_commit_list **stack) +{ + git_commit_list *top = *stack; + git_commit_list_node *item = top ? top->item : NULL; + + if (top) { + *stack = top->next; + git__free(top); + } + return item; +} + +static int commit_quick_parse( + git_revwalk *walk, + git_commit_list_node *node, + git_odb_object *obj) +{ + git_oid *parent_oid; + git_commit *commit; + size_t i; + + commit = git__calloc(1, sizeof(*commit)); + GIT_ERROR_CHECK_ALLOC(commit); + commit->object.repo = walk->repo; + + if (git_commit__parse_ext(commit, obj, GIT_COMMIT_PARSE_QUICK) < 0) { + git__free(commit); + return -1; + } + + if (!git__is_uint16(git_array_size(commit->parent_ids))) { + git__free(commit); + git_error_set(GIT_ERROR_INVALID, "commit has more than 2^16 parents"); + return -1; + } + + node->generation = 0; + node->time = commit->committer->when.time; + node->out_degree = (uint16_t) git_array_size(commit->parent_ids); + node->parents = alloc_parents(walk, node, node->out_degree); + GIT_ERROR_CHECK_ALLOC(node->parents); + + git_array_foreach(commit->parent_ids, i, parent_oid) { + node->parents[i] = git_revwalk__commit_lookup(walk, parent_oid); + } + + git_commit__free(commit); + + node->parsed = 1; + + return 0; +} + +int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit) +{ + git_odb_object *obj; + git_commit_graph_file *cgraph_file = NULL; + int error; + + if (commit->parsed) + return 0; + + /* Let's try to use the commit graph first. */ + git_odb__get_commit_graph_file(&cgraph_file, walk->odb); + if (cgraph_file) { + git_commit_graph_entry e; + + error = git_commit_graph_entry_find(&e, cgraph_file, &commit->oid, GIT_OID_RAWSZ); + if (error == 0 && git__is_uint16(e.parent_count)) { + size_t i; + commit->generation = (uint32_t)e.generation; + commit->time = e.commit_time; + commit->out_degree = (uint16_t)e.parent_count; + commit->parents = alloc_parents(walk, commit, commit->out_degree); + GIT_ERROR_CHECK_ALLOC(commit->parents); + + for (i = 0; i < commit->out_degree; ++i) { + git_commit_graph_entry parent; + error = git_commit_graph_entry_parent(&parent, cgraph_file, &e, i); + if (error < 0) + return error; + commit->parents[i] = git_revwalk__commit_lookup(walk, &parent.sha1); + } + commit->parsed = 1; + return 0; + } + } + + if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0) + return error; + + if (obj->cached.type != GIT_OBJECT_COMMIT) { + git_error_set(GIT_ERROR_INVALID, "object is no commit object"); + error = -1; + } else + error = commit_quick_parse(walk, commit, obj); + + git_odb_object_free(obj); + return error; +} + diff --git a/src/libgit2/commit_list.h b/src/libgit2/commit_list.h new file mode 100644 index 000000000..aad39f351 --- /dev/null +++ b/src/libgit2/commit_list.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_commit_list_h__ +#define INCLUDE_commit_list_h__ + +#include "common.h" + +#include "git2/oid.h" + +#define PARENT1 (1 << 0) +#define PARENT2 (1 << 1) +#define RESULT (1 << 2) +#define STALE (1 << 3) +#define ALL_FLAGS (PARENT1 | PARENT2 | STALE | RESULT) + +#define PARENTS_PER_COMMIT 2 +#define COMMIT_ALLOC \ + (sizeof(git_commit_list_node) + PARENTS_PER_COMMIT * sizeof(git_commit_list_node *)) + +#define FLAG_BITS 4 + +typedef struct git_commit_list_node { + git_oid oid; + int64_t time; + uint32_t generation; + unsigned int seen:1, + uninteresting:1, + topo_delay:1, + parsed:1, + added:1, + flags : FLAG_BITS; + + uint16_t in_degree; + uint16_t out_degree; + + struct git_commit_list_node **parents; +} git_commit_list_node; + +typedef struct git_commit_list { + git_commit_list_node *item; + struct git_commit_list *next; +} git_commit_list; + +git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk); +int git_commit_list_generation_cmp(const void *a, const void *b); +int git_commit_list_time_cmp(const void *a, const void *b); +void git_commit_list_free(git_commit_list **list_p); +git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p); +git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p); +int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit); +git_commit_list_node *git_commit_list_pop(git_commit_list **stack); + +#endif diff --git a/src/libgit2/common.h b/src/libgit2/common.h new file mode 100644 index 000000000..549bddb59 --- /dev/null +++ b/src/libgit2/common.h @@ -0,0 +1,214 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_common_h__ +#define INCLUDE_common_h__ + +#ifndef LIBGIT2_NO_FEATURES_H +# include "git2/sys/features.h" +#endif + +#include "git2/common.h" +#include "cc-compat.h" + +/** Declare a function as always inlined. */ +#if defined(_MSC_VER) +# define GIT_INLINE(type) static __inline type +#elif defined(__GNUC__) +# define GIT_INLINE(type) static __inline__ type +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define GIT_INLINE(type) static inline type +#else +# define GIT_INLINE(type) static type +#endif + +/** Support for gcc/clang __has_builtin intrinsic */ +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + +/** + * Declare that a function's return value must be used. + * + * Used mostly to guard against potential silent bugs at runtime. This is + * recommended to be added to functions that: + * + * - Allocate / reallocate memory. This prevents memory leaks or errors where + * buffers are expected to have grown to a certain size, but could not be + * resized. + * - Acquire locks. When a lock cannot be acquired, that will almost certainly + * cause a data race / undefined behavior. + */ +#if defined(__GNUC__) +# define GIT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +# define GIT_WARN_UNUSED_RESULT +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef GIT_WIN32 + +# include +# include +# include +# include +# include +# include "win32/msvc-compat.h" +# include "win32/mingw-compat.h" +# include "win32/w32_common.h" +# include "win32/win32-compat.h" +# include "win32/error.h" +# include "win32/version.h" +# ifdef GIT_THREADS +# include "win32/thread.h" +# endif + +#else + +# include +# include +# ifdef GIT_THREADS +# include +# include +# endif + +#define GIT_LIBGIT2_CALL +#define GIT_SYSTEM_CALL + +#ifdef GIT_USE_STAT_ATIMESPEC +# define st_atim st_atimespec +# define st_ctim st_ctimespec +# define st_mtim st_mtimespec +#endif + +# include + +#endif + +#include "git2/types.h" +#include "git2/errors.h" +#include "errors.h" +#include "thread.h" +#include "integer.h" +#include "assert_safe.h" +#include "utf8.h" + +/* + * Include the declarations for deprecated functions; this ensures + * that they're decorated with the proper extern/visibility attributes. + */ +#include "git2/deprecated.h" + +#include "posix.h" + +#define DEFAULT_BUFSIZE 65536 +#define FILEIO_BUFSIZE DEFAULT_BUFSIZE +#define FILTERIO_BUFSIZE DEFAULT_BUFSIZE +#define NETIO_BUFSIZE DEFAULT_BUFSIZE + +/** + * Check a pointer allocation result, returning -1 if it failed. + */ +#define GIT_ERROR_CHECK_ALLOC(ptr) do { \ + if ((ptr) == NULL) { return -1; } \ + } while(0) + +/** + * Check a string buffer allocation result, returning -1 if it failed. + */ +#define GIT_ERROR_CHECK_ALLOC_STR(buf) do { \ + if ((void *)(buf) == NULL || git_str_oom(buf)) { return -1; } \ + } while(0) + +/** + * Check a return value and propagate result if non-zero. + */ +#define GIT_ERROR_CHECK_ERROR(code) \ + do { int _err = (code); if (_err) return _err; } while (0) + +/** + * Check a versioned structure for validity + */ +GIT_INLINE(int) git_error__check_version(const void *structure, unsigned int expected_max, const char *name) +{ + unsigned int actual; + + if (!structure) + return 0; + + actual = *(const unsigned int*)structure; + if (actual > 0 && actual <= expected_max) + return 0; + + git_error_set(GIT_ERROR_INVALID, "invalid version %d on %s", actual, name); + return -1; +} +#define GIT_ERROR_CHECK_VERSION(S,V,N) if (git_error__check_version(S,V,N) < 0) return -1 + +/** + * Initialize a structure with a version. + */ +GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int version) +{ + memset(structure, 0, len); + *((int*)structure) = version; +} +#define GIT_INIT_STRUCTURE(S,V) git__init_structure(S, sizeof(*S), V) + +#define GIT_INIT_STRUCTURE_FROM_TEMPLATE(PTR,VERSION,TYPE,TPL) do { \ + TYPE _tmpl = TPL; \ + GIT_ERROR_CHECK_VERSION(&(VERSION), _tmpl.version, #TYPE); \ + memcpy((PTR), &_tmpl, sizeof(_tmpl)); } while (0) + + +/** Check for additive overflow, setting an error if would occur. */ +#define GIT_ADD_SIZET_OVERFLOW(out, one, two) \ + (git__add_sizet_overflow(out, one, two) ? (git_error_set_oom(), 1) : 0) + +/** Check for additive overflow, setting an error if would occur. */ +#define GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize) \ + (git__multiply_sizet_overflow(out, nelem, elsize) ? (git_error_set_oom(), 1) : 0) + +/** Check for additive overflow, failing if it would occur. */ +#define GIT_ERROR_CHECK_ALLOC_ADD(out, one, two) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two)) { return -1; } + +#define GIT_ERROR_CHECK_ALLOC_ADD3(out, one, two, three) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three)) { return -1; } + +#define GIT_ERROR_CHECK_ALLOC_ADD4(out, one, two, three, four) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), four)) { return -1; } + +#define GIT_ERROR_CHECK_ALLOC_ADD5(out, one, two, three, four, five) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), four) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), five)) { return -1; } + +/** Check for multiplicative overflow, failing if it would occur. */ +#define GIT_ERROR_CHECK_ALLOC_MULTIPLY(out, nelem, elsize) \ + if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; } + +/* NOTE: other git_error functions are in the public errors.h header file */ + +/* Forward declare git_str */ +typedef struct git_str git_str; + +#include "util.h" + +#endif diff --git a/src/libgit2/config.c b/src/libgit2/config.c new file mode 100644 index 000000000..88da34c5e --- /dev/null +++ b/src/libgit2/config.c @@ -0,0 +1,1566 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "config.h" + +#include "git2/config.h" +#include "git2/sys/config.h" + +#include "buf.h" +#include "config_backend.h" +#include "regexp.h" +#include "sysdir.h" +#include "transaction.h" +#include "vector.h" +#if GIT_WIN32 +# include +#endif + +#include + +void git_config_entry_free(git_config_entry *entry) +{ + if (!entry) + return; + + entry->free(entry); +} + +typedef struct { + git_refcount rc; + + git_config_backend *backend; + git_config_level_t level; +} backend_internal; + +static void backend_internal_free(backend_internal *internal) +{ + git_config_backend *backend; + + backend = internal->backend; + backend->free(backend); + git__free(internal); +} + +static void config_free(git_config *cfg) +{ + size_t i; + backend_internal *internal; + + for (i = 0; i < cfg->backends.length; ++i) { + internal = git_vector_get(&cfg->backends, i); + GIT_REFCOUNT_DEC(internal, backend_internal_free); + } + + git_vector_free(&cfg->backends); + + git__memzero(cfg, sizeof(*cfg)); + git__free(cfg); +} + +void git_config_free(git_config *cfg) +{ + if (cfg == NULL) + return; + + GIT_REFCOUNT_DEC(cfg, config_free); +} + +static int config_backend_cmp(const void *a, const void *b) +{ + const backend_internal *bk_a = (const backend_internal *)(a); + const backend_internal *bk_b = (const backend_internal *)(b); + + return bk_b->level - bk_a->level; +} + +int git_config_new(git_config **out) +{ + git_config *cfg; + + cfg = git__malloc(sizeof(git_config)); + GIT_ERROR_CHECK_ALLOC(cfg); + + memset(cfg, 0x0, sizeof(git_config)); + + if (git_vector_init(&cfg->backends, 3, config_backend_cmp) < 0) { + git__free(cfg); + return -1; + } + + *out = cfg; + GIT_REFCOUNT_INC(cfg); + return 0; +} + +int git_config_add_file_ondisk( + git_config *cfg, + const char *path, + git_config_level_t level, + const git_repository *repo, + int force) +{ + git_config_backend *file = NULL; + struct stat st; + int res; + + GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(path); + + res = p_stat(path, &st); + if (res < 0 && errno != ENOENT && errno != ENOTDIR) { + git_error_set(GIT_ERROR_CONFIG, "failed to stat '%s'", path); + return -1; + } + + if (git_config_backend_from_file(&file, path) < 0) + return -1; + + if ((res = git_config_add_backend(cfg, file, level, repo, force)) < 0) { + /* + * free manually; the file is not owned by the config + * instance yet and will not be freed on cleanup + */ + file->free(file); + return res; + } + + return 0; +} + +int git_config_open_ondisk(git_config **out, const char *path) +{ + int error; + git_config *config; + + *out = NULL; + + if (git_config_new(&config) < 0) + return -1; + + if ((error = git_config_add_file_ondisk(config, path, GIT_CONFIG_LEVEL_LOCAL, NULL, 0)) < 0) + git_config_free(config); + else + *out = config; + + return error; +} + +int git_config_snapshot(git_config **out, git_config *in) +{ + int error = 0; + size_t i; + backend_internal *internal; + git_config *config; + + *out = NULL; + + if (git_config_new(&config) < 0) + return -1; + + git_vector_foreach(&in->backends, i, internal) { + git_config_backend *b; + + if ((error = internal->backend->snapshot(&b, internal->backend)) < 0) + break; + + if ((error = git_config_add_backend(config, b, internal->level, NULL, 0)) < 0) { + b->free(b); + break; + } + } + + if (error < 0) + git_config_free(config); + else + *out = config; + + return error; +} + +static int find_backend_by_level( + backend_internal **out, + const git_config *cfg, + git_config_level_t level) +{ + int pos = -1; + backend_internal *internal; + size_t i; + + /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config backend + * which has the highest level. As config backends are stored in a vector + * sorted by decreasing order of level, getting the backend at position 0 + * will do the job. + */ + if (level == GIT_CONFIG_HIGHEST_LEVEL) { + pos = 0; + } else { + git_vector_foreach(&cfg->backends, i, internal) { + if (internal->level == level) + pos = (int)i; + } + } + + if (pos == -1) { + git_error_set(GIT_ERROR_CONFIG, + "no configuration exists for the given level '%i'", (int)level); + return GIT_ENOTFOUND; + } + + *out = git_vector_get(&cfg->backends, pos); + + return 0; +} + +static int duplicate_level(void **old_raw, void *new_raw) +{ + backend_internal **old = (backend_internal **)old_raw; + + GIT_UNUSED(new_raw); + + git_error_set(GIT_ERROR_CONFIG, "there already exists a configuration for the given level (%i)", (int)(*old)->level); + return GIT_EEXISTS; +} + +static void try_remove_existing_backend( + git_config *cfg, + git_config_level_t level) +{ + int pos = -1; + backend_internal *internal; + size_t i; + + git_vector_foreach(&cfg->backends, i, internal) { + if (internal->level == level) + pos = (int)i; + } + + if (pos == -1) + return; + + internal = git_vector_get(&cfg->backends, pos); + + if (git_vector_remove(&cfg->backends, pos) < 0) + return; + + GIT_REFCOUNT_DEC(internal, backend_internal_free); +} + +static int git_config__add_internal( + git_config *cfg, + backend_internal *internal, + git_config_level_t level, + int force) +{ + int result; + + /* delete existing config backend for level if it exists */ + if (force) + try_remove_existing_backend(cfg, level); + + if ((result = git_vector_insert_sorted(&cfg->backends, + internal, &duplicate_level)) < 0) + return result; + + git_vector_sort(&cfg->backends); + internal->backend->cfg = cfg; + + GIT_REFCOUNT_INC(internal); + + return 0; +} + +int git_config_open_global(git_config **cfg_out, git_config *cfg) +{ + if (!git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_XDG)) + return 0; + + return git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_GLOBAL); +} + +int git_config_open_level( + git_config **cfg_out, + const git_config *cfg_parent, + git_config_level_t level) +{ + git_config *cfg; + backend_internal *internal; + int res; + + if ((res = find_backend_by_level(&internal, cfg_parent, level)) < 0) + return res; + + if ((res = git_config_new(&cfg)) < 0) + return res; + + if ((res = git_config__add_internal(cfg, internal, level, true)) < 0) { + git_config_free(cfg); + return res; + } + + *cfg_out = cfg; + + return 0; +} + +int git_config_add_backend( + git_config *cfg, + git_config_backend *backend, + git_config_level_t level, + const git_repository *repo, + int force) +{ + backend_internal *internal; + int result; + + GIT_ASSERT_ARG(cfg); + GIT_ASSERT_ARG(backend); + + GIT_ERROR_CHECK_VERSION(backend, GIT_CONFIG_BACKEND_VERSION, "git_config_backend"); + + if ((result = backend->open(backend, level, repo)) < 0) + return result; + + internal = git__malloc(sizeof(backend_internal)); + GIT_ERROR_CHECK_ALLOC(internal); + + memset(internal, 0x0, sizeof(backend_internal)); + + internal->backend = backend; + internal->level = level; + + if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) { + git__free(internal); + return result; + } + + return 0; +} + +/* + * Loop over all the variables + */ + +typedef struct { + git_config_iterator parent; + git_config_iterator *current; + const git_config *cfg; + git_regexp regex; + size_t i; +} all_iter; + +static int find_next_backend(size_t *out, const git_config *cfg, size_t i) +{ + backend_internal *internal; + + for (; i > 0; --i) { + internal = git_vector_get(&cfg->backends, i - 1); + if (!internal || !internal->backend) + continue; + + *out = i; + return 0; + } + + return -1; +} + +static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter) +{ + all_iter *iter = (all_iter *) _iter; + backend_internal *internal; + git_config_backend *backend; + size_t i; + int error = 0; + + if (iter->current != NULL && + (error = iter->current->next(entry, iter->current)) == 0) { + return 0; + } + + if (error < 0 && error != GIT_ITEROVER) + return error; + + do { + if (find_next_backend(&i, iter->cfg, iter->i) < 0) + return GIT_ITEROVER; + + internal = git_vector_get(&iter->cfg->backends, i - 1); + backend = internal->backend; + iter->i = i - 1; + + if (iter->current) + iter->current->free(iter->current); + + iter->current = NULL; + error = backend->iterator(&iter->current, backend); + if (error == GIT_ENOTFOUND) + continue; + + if (error < 0) + return error; + + error = iter->current->next(entry, iter->current); + /* If this backend is empty, then keep going */ + if (error == GIT_ITEROVER) + continue; + + return error; + + } while(1); + + return GIT_ITEROVER; +} + +static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_iter) +{ + int error; + all_iter *iter = (all_iter *) _iter; + + /* + * We use the "normal" function to grab the next one across + * backends and then apply the regex + */ + while ((error = all_iter_next(entry, _iter)) == 0) { + /* skip non-matching keys if regexp was provided */ + if (git_regexp_match(&iter->regex, (*entry)->name) != 0) + continue; + + /* and simply return if we like the entry's name */ + return 0; + } + + return error; +} + +static void all_iter_free(git_config_iterator *_iter) +{ + all_iter *iter = (all_iter *) _iter; + + if (iter->current) + iter->current->free(iter->current); + + git__free(iter); +} + +static void all_iter_glob_free(git_config_iterator *_iter) +{ + all_iter *iter = (all_iter *) _iter; + + git_regexp_dispose(&iter->regex); + all_iter_free(_iter); +} + +int git_config_iterator_new(git_config_iterator **out, const git_config *cfg) +{ + all_iter *iter; + + iter = git__calloc(1, sizeof(all_iter)); + GIT_ERROR_CHECK_ALLOC(iter); + + iter->parent.free = all_iter_free; + iter->parent.next = all_iter_next; + + iter->i = cfg->backends.length; + iter->cfg = cfg; + + *out = (git_config_iterator *) iter; + + return 0; +} + +int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp) +{ + all_iter *iter; + int result; + + if (regexp == NULL) + return git_config_iterator_new(out, cfg); + + iter = git__calloc(1, sizeof(all_iter)); + GIT_ERROR_CHECK_ALLOC(iter); + + if ((result = git_regexp_compile(&iter->regex, regexp, 0)) < 0) { + git__free(iter); + return -1; + } + + iter->parent.next = all_iter_glob_next; + iter->parent.free = all_iter_glob_free; + iter->i = cfg->backends.length; + iter->cfg = cfg; + + *out = (git_config_iterator *) iter; + + return 0; +} + +int git_config_foreach( + const git_config *cfg, git_config_foreach_cb cb, void *payload) +{ + return git_config_foreach_match(cfg, NULL, cb, payload); +} + +int git_config_backend_foreach_match( + git_config_backend *backend, + const char *regexp, + git_config_foreach_cb cb, + void *payload) +{ + git_config_entry *entry; + git_config_iterator *iter; + git_regexp regex; + int error = 0; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(cb); + + if (regexp && git_regexp_compile(®ex, regexp, 0) < 0) + return -1; + + if ((error = backend->iterator(&iter, backend)) < 0) { + iter = NULL; + return -1; + } + + while (!(iter->next(&entry, iter) < 0)) { + /* skip non-matching keys if regexp was provided */ + if (regexp && git_regexp_match(®ex, entry->name) != 0) + continue; + + /* abort iterator on non-zero return value */ + if ((error = cb(entry, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + if (regexp != NULL) + git_regexp_dispose(®ex); + + iter->free(iter); + + return error; +} + +int git_config_foreach_match( + const git_config *cfg, + const char *regexp, + git_config_foreach_cb cb, + void *payload) +{ + int error; + git_config_iterator *iter; + git_config_entry *entry; + + if ((error = git_config_iterator_glob_new(&iter, cfg, regexp)) < 0) + return error; + + while (!(error = git_config_next(&entry, iter))) { + if ((error = cb(entry, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + git_config_iterator_free(iter); + + if (error == GIT_ITEROVER) + error = 0; + + return error; +} + +/************** + * Setters + **************/ + +typedef enum { + BACKEND_USE_SET, + BACKEND_USE_DELETE +} backend_use; + +static const char *uses[] = { + "set", + "delete" +}; + +static int get_backend_for_use(git_config_backend **out, + git_config *cfg, const char *name, backend_use use) +{ + size_t i; + backend_internal *backend; + + *out = NULL; + + if (git_vector_length(&cfg->backends) == 0) { + git_error_set(GIT_ERROR_CONFIG, + "cannot %s value for '%s' when no config backends exist", + uses[use], name); + return GIT_ENOTFOUND; + } + + git_vector_foreach(&cfg->backends, i, backend) { + if (!backend->backend->readonly) { + *out = backend->backend; + return 0; + } + } + + git_error_set(GIT_ERROR_CONFIG, + "cannot %s value for '%s' when all config backends are readonly", + uses[use], name); + return GIT_ENOTFOUND; +} + +int git_config_delete_entry(git_config *cfg, const char *name) +{ + git_config_backend *backend; + + if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) + return GIT_ENOTFOUND; + + return backend->del(backend, name); +} + +int git_config_set_int64(git_config *cfg, const char *name, int64_t value) +{ + char str_value[32]; /* All numbers should fit in here */ + p_snprintf(str_value, sizeof(str_value), "%" PRId64, value); + return git_config_set_string(cfg, name, str_value); +} + +int git_config_set_int32(git_config *cfg, const char *name, int32_t value) +{ + return git_config_set_int64(cfg, name, (int64_t)value); +} + +int git_config_set_bool(git_config *cfg, const char *name, int value) +{ + return git_config_set_string(cfg, name, value ? "true" : "false"); +} + +int git_config_set_string(git_config *cfg, const char *name, const char *value) +{ + int error; + git_config_backend *backend; + + if (!value) { + git_error_set(GIT_ERROR_CONFIG, "the value to set cannot be NULL"); + return -1; + } + + if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_SET) < 0) + return GIT_ENOTFOUND; + + error = backend->set(backend, name, value); + + if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL) + git_repository__configmap_lookup_cache_clear(GIT_REFCOUNT_OWNER(cfg)); + + return error; +} + +int git_config__update_entry( + git_config *config, + const char *key, + const char *value, + bool overwrite_existing, + bool only_if_existing) +{ + int error = 0; + git_config_entry *ce = NULL; + + if ((error = git_config__lookup_entry(&ce, config, key, false)) < 0) + return error; + + if (!ce && only_if_existing) /* entry doesn't exist */ + return 0; + if (ce && !overwrite_existing) /* entry would be overwritten */ + return 0; + if (value && ce && ce->value && !strcmp(ce->value, value)) /* no change */ + return 0; + if (!value && (!ce || !ce->value)) /* asked to delete absent entry */ + return 0; + + if (!value) + error = git_config_delete_entry(config, key); + else + error = git_config_set_string(config, key, value); + + git_config_entry_free(ce); + return error; +} + +/*********** + * Getters + ***********/ + +static int config_error_notfound(const char *name) +{ + git_error_set(GIT_ERROR_CONFIG, "config value '%s' was not found", name); + return GIT_ENOTFOUND; +} + +enum { + GET_ALL_ERRORS = 0, + GET_NO_MISSING = 1, + GET_NO_ERRORS = 2 +}; + +static int get_entry( + git_config_entry **out, + const git_config *cfg, + const char *name, + bool normalize_name, + int want_errors) +{ + int res = GIT_ENOTFOUND; + const char *key = name; + char *normalized = NULL; + size_t i; + backend_internal *internal; + + *out = NULL; + + if (normalize_name) { + if ((res = git_config__normalize_name(name, &normalized)) < 0) + goto cleanup; + key = normalized; + } + + res = GIT_ENOTFOUND; + git_vector_foreach(&cfg->backends, i, internal) { + if (!internal || !internal->backend) + continue; + + res = internal->backend->get(internal->backend, key, out); + if (res != GIT_ENOTFOUND) + break; + } + + git__free(normalized); + +cleanup: + if (res == GIT_ENOTFOUND) + res = (want_errors > GET_ALL_ERRORS) ? 0 : config_error_notfound(name); + else if (res && (want_errors == GET_NO_ERRORS)) { + git_error_clear(); + res = 0; + } + + return res; +} + +int git_config_get_entry( + git_config_entry **out, const git_config *cfg, const char *name) +{ + return get_entry(out, cfg, name, true, GET_ALL_ERRORS); +} + +int git_config__lookup_entry( + git_config_entry **out, + const git_config *cfg, + const char *key, + bool no_errors) +{ + return get_entry( + out, cfg, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING); +} + +int git_config_get_mapped( + int *out, + const git_config *cfg, + const char *name, + const git_configmap *maps, + size_t map_n) +{ + git_config_entry *entry; + int ret; + + if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + return ret; + + ret = git_config_lookup_map_value(out, maps, map_n, entry->value); + git_config_entry_free(entry); + + return ret; +} + +int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name) +{ + git_config_entry *entry; + int ret; + + if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + return ret; + + ret = git_config_parse_int64(out, entry->value); + git_config_entry_free(entry); + + return ret; +} + +int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name) +{ + git_config_entry *entry; + int ret; + + if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + return ret; + + ret = git_config_parse_int32(out, entry->value); + git_config_entry_free(entry); + + return ret; +} + +int git_config_get_bool(int *out, const git_config *cfg, const char *name) +{ + git_config_entry *entry; + int ret; + + if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + return ret; + + ret = git_config_parse_bool(out, entry->value); + git_config_entry_free(entry); + + return ret; +} + +static int is_readonly(const git_config *cfg) +{ + size_t i; + backend_internal *internal; + + git_vector_foreach(&cfg->backends, i, internal) { + if (!internal || !internal->backend) + continue; + + if (!internal->backend->readonly) + return 0; + } + + return 1; +} + +static int git_config__parse_path(git_str *out, const char *value) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(value); + + if (value[0] == '~') { + if (value[1] != '\0' && value[1] != '/') { + git_error_set(GIT_ERROR_CONFIG, "retrieving a homedir by name is not supported"); + return -1; + } + + return git_sysdir_expand_global_file(out, value[1] ? &value[2] : NULL); + } + + return git_str_sets(out, value); +} + +int git_config_parse_path(git_buf *out, const char *value) +{ + GIT_BUF_WRAP_PRIVATE(out, git_config__parse_path, value); +} + +int git_config_get_path( + git_buf *out, + const git_config *cfg, + const char *name) +{ + GIT_BUF_WRAP_PRIVATE(out, git_config__get_path, cfg, name); +} + +int git_config__get_path( + git_str *out, + const git_config *cfg, + const char *name) +{ + git_config_entry *entry; + int error; + + if ((error = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0) + return error; + + error = git_config__parse_path(out, entry->value); + git_config_entry_free(entry); + + return error; +} + +int git_config_get_string( + const char **out, const git_config *cfg, const char *name) +{ + git_config_entry *entry; + int ret; + + if (!is_readonly(cfg)) { + git_error_set(GIT_ERROR_CONFIG, "get_string called on a live config object"); + return -1; + } + + ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS); + *out = !ret ? (entry->value ? entry->value : "") : NULL; + + git_config_entry_free(entry); + + return ret; +} + +int git_config_get_string_buf( + git_buf *out, const git_config *cfg, const char *name) +{ + GIT_BUF_WRAP_PRIVATE(out, git_config__get_string_buf, cfg, name); +} + +int git_config__get_string_buf( + git_str *out, const git_config *cfg, const char *name) +{ + git_config_entry *entry; + int ret; + const char *str; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(cfg); + + ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS); + str = !ret ? (entry->value ? entry->value : "") : NULL; + + if (str) + ret = git_str_puts(out, str); + + git_config_entry_free(entry); + + return ret; +} + +char *git_config__get_string_force( + const git_config *cfg, const char *key, const char *fallback_value) +{ + git_config_entry *entry; + char *ret; + + get_entry(&entry, cfg, key, false, GET_NO_ERRORS); + ret = (entry && entry->value) ? git__strdup(entry->value) : fallback_value ? git__strdup(fallback_value) : NULL; + git_config_entry_free(entry); + + return ret; +} + +int git_config__get_bool_force( + const git_config *cfg, const char *key, int fallback_value) +{ + int val = fallback_value; + git_config_entry *entry; + + get_entry(&entry, cfg, key, false, GET_NO_ERRORS); + + if (entry && git_config_parse_bool(&val, entry->value) < 0) + git_error_clear(); + + git_config_entry_free(entry); + return val; +} + +int git_config__get_int_force( + const git_config *cfg, const char *key, int fallback_value) +{ + int32_t val = (int32_t)fallback_value; + git_config_entry *entry; + + get_entry(&entry, cfg, key, false, GET_NO_ERRORS); + + if (entry && git_config_parse_int32(&val, entry->value) < 0) + git_error_clear(); + + git_config_entry_free(entry); + return (int)val; +} + +int git_config_get_multivar_foreach( + const git_config *cfg, const char *name, const char *regexp, + git_config_foreach_cb cb, void *payload) +{ + int err, found; + git_config_iterator *iter; + git_config_entry *entry; + + if ((err = git_config_multivar_iterator_new(&iter, cfg, name, regexp)) < 0) + return err; + + found = 0; + while ((err = iter->next(&entry, iter)) == 0) { + found = 1; + + if ((err = cb(entry, payload)) != 0) { + git_error_set_after_callback(err); + break; + } + } + + iter->free(iter); + if (err == GIT_ITEROVER) + err = 0; + + if (found == 0 && err == 0) + err = config_error_notfound(name); + + return err; +} + +typedef struct { + git_config_iterator parent; + git_config_iterator *iter; + char *name; + git_regexp regex; + int have_regex; +} multivar_iter; + +static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_iter) +{ + multivar_iter *iter = (multivar_iter *) _iter; + int error = 0; + + while ((error = iter->iter->next(entry, iter->iter)) == 0) { + if (git__strcmp(iter->name, (*entry)->name)) + continue; + + if (!iter->have_regex) + return 0; + + if (git_regexp_match(&iter->regex, (*entry)->value) == 0) + return 0; + } + + return error; +} + +static void multivar_iter_free(git_config_iterator *_iter) +{ + multivar_iter *iter = (multivar_iter *) _iter; + + iter->iter->free(iter->iter); + + git__free(iter->name); + if (iter->have_regex) + git_regexp_dispose(&iter->regex); + git__free(iter); +} + +int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp) +{ + multivar_iter *iter = NULL; + git_config_iterator *inner = NULL; + int error; + + if ((error = git_config_iterator_new(&inner, cfg)) < 0) + return error; + + iter = git__calloc(1, sizeof(multivar_iter)); + GIT_ERROR_CHECK_ALLOC(iter); + + if ((error = git_config__normalize_name(name, &iter->name)) < 0) + goto on_error; + + if (regexp != NULL) { + if ((error = git_regexp_compile(&iter->regex, regexp, 0)) < 0) + goto on_error; + + iter->have_regex = 1; + } + + iter->iter = inner; + iter->parent.free = multivar_iter_free; + iter->parent.next = multivar_iter_next; + + *out = (git_config_iterator *) iter; + + return 0; + +on_error: + + inner->free(inner); + git__free(iter); + return error; +} + +int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) +{ + git_config_backend *backend; + + if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) + return GIT_ENOTFOUND; + + return backend->set_multivar(backend, name, regexp, value); +} + +int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp) +{ + git_config_backend *backend; + + if (get_backend_for_use(&backend, cfg, name, BACKEND_USE_DELETE) < 0) + return GIT_ENOTFOUND; + + return backend->del_multivar(backend, name, regexp); +} + +int git_config_next(git_config_entry **entry, git_config_iterator *iter) +{ + return iter->next(entry, iter); +} + +void git_config_iterator_free(git_config_iterator *iter) +{ + if (iter == NULL) + return; + + iter->free(iter); +} + +int git_config_find_global(git_buf *path) +{ + GIT_BUF_WRAP_PRIVATE(path, git_sysdir_find_global_file, GIT_CONFIG_FILENAME_GLOBAL); +} + +int git_config__find_global(git_str *path) +{ + return git_sysdir_find_global_file(path, GIT_CONFIG_FILENAME_GLOBAL); +} + +int git_config_find_xdg(git_buf *path) +{ + GIT_BUF_WRAP_PRIVATE(path, git_sysdir_find_global_file, GIT_CONFIG_FILENAME_XDG); +} + +int git_config__find_xdg(git_str *path) +{ + return git_sysdir_find_xdg_file(path, GIT_CONFIG_FILENAME_XDG); +} + +int git_config_find_system(git_buf *path) +{ + GIT_BUF_WRAP_PRIVATE(path, git_sysdir_find_global_file, GIT_CONFIG_FILENAME_SYSTEM); +} + +int git_config__find_system(git_str *path) +{ + return git_sysdir_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM); +} + +int git_config_find_programdata(git_buf *path) +{ + git_str str = GIT_STR_INIT; + int error; + + if ((error = git_buf_tostr(&str, path)) == 0 && + (error = git_config__find_programdata(&str)) == 0) + error = git_buf_fromstr(path, &str); + + git_str_dispose(&str); + return error; +} + +int git_config__find_programdata(git_str *path) +{ + int ret; + + ret = git_sysdir_find_programdata_file(path, GIT_CONFIG_FILENAME_PROGRAMDATA); + + if (ret != GIT_OK) + return ret; + + return git_fs_path_validate_system_file_ownership(path->ptr); +} + +int git_config__global_location(git_str *buf) +{ + const git_str *paths; + const char *sep, *start; + + if (git_sysdir_get(&paths, GIT_SYSDIR_GLOBAL) < 0) + return -1; + + /* no paths, so give up */ + if (!paths || !git_str_len(paths)) + return -1; + + /* find unescaped separator or end of string */ + for (sep = start = git_str_cstr(paths); *sep; ++sep) { + if (*sep == GIT_PATH_LIST_SEPARATOR && + (sep <= start || sep[-1] != '\\')) + break; + } + + if (git_str_set(buf, start, (size_t)(sep - start)) < 0) + return -1; + + return git_str_joinpath(buf, buf->ptr, GIT_CONFIG_FILENAME_GLOBAL); +} + +int git_config_open_default(git_config **out) +{ + int error; + git_config *cfg = NULL; + git_str buf = GIT_STR_INIT; + + if ((error = git_config_new(&cfg)) < 0) + return error; + + if (!git_config__find_global(&buf) || + !git_config__global_location(&buf)) { + error = git_config_add_file_ondisk(cfg, buf.ptr, + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0); + } + + if (!error && !git_config__find_xdg(&buf)) + error = git_config_add_file_ondisk(cfg, buf.ptr, + GIT_CONFIG_LEVEL_XDG, NULL, 0); + + if (!error && !git_config__find_system(&buf)) + error = git_config_add_file_ondisk(cfg, buf.ptr, + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0); + + if (!error && !git_config__find_programdata(&buf)) + error = git_config_add_file_ondisk(cfg, buf.ptr, + GIT_CONFIG_LEVEL_PROGRAMDATA, NULL, 0); + + git_str_dispose(&buf); + + if (error) { + git_config_free(cfg); + cfg = NULL; + } + + *out = cfg; + + return error; +} + +int git_config_lock(git_transaction **out, git_config *cfg) +{ + int error; + git_config_backend *backend; + backend_internal *internal; + + GIT_ASSERT_ARG(cfg); + + internal = git_vector_get(&cfg->backends, 0); + if (!internal || !internal->backend) { + git_error_set(GIT_ERROR_CONFIG, "cannot lock; the config has no backends"); + return -1; + } + backend = internal->backend; + + if ((error = backend->lock(backend)) < 0) + return error; + + return git_transaction_config_new(out, cfg); +} + +int git_config_unlock(git_config *cfg, int commit) +{ + git_config_backend *backend; + backend_internal *internal; + + GIT_ASSERT_ARG(cfg); + + internal = git_vector_get(&cfg->backends, 0); + if (!internal || !internal->backend) { + git_error_set(GIT_ERROR_CONFIG, "cannot lock; the config has no backends"); + return -1; + } + + backend = internal->backend; + + return backend->unlock(backend, commit); +} + +/*********** + * Parsers + ***********/ + +int git_config_lookup_map_value( + int *out, + const git_configmap *maps, + size_t map_n, + const char *value) +{ + size_t i; + + for (i = 0; i < map_n; ++i) { + const git_configmap *m = maps + i; + + switch (m->type) { + case GIT_CONFIGMAP_FALSE: + case GIT_CONFIGMAP_TRUE: { + int bool_val; + + if (git_config_parse_bool(&bool_val, value) == 0 && + bool_val == (int)m->type) { + *out = m->map_value; + return 0; + } + break; + } + + case GIT_CONFIGMAP_INT32: + if (git_config_parse_int32(out, value) == 0) + return 0; + break; + + case GIT_CONFIGMAP_STRING: + if (value && strcasecmp(value, m->str_match) == 0) { + *out = m->map_value; + return 0; + } + break; + } + } + + git_error_set(GIT_ERROR_CONFIG, "failed to map '%s'", value); + return -1; +} + +int git_config_lookup_map_enum(git_configmap_t *type_out, const char **str_out, + const git_configmap *maps, size_t map_n, int enum_val) +{ + size_t i; + + for (i = 0; i < map_n; i++) { + const git_configmap *m = &maps[i]; + + if (m->map_value != enum_val) + continue; + + *type_out = m->type; + *str_out = m->str_match; + return 0; + } + + git_error_set(GIT_ERROR_CONFIG, "invalid enum value"); + return GIT_ENOTFOUND; +} + +int git_config_parse_bool(int *out, const char *value) +{ + if (git__parse_bool(out, value) == 0) + return 0; + + if (git_config_parse_int32(out, value) == 0) { + *out = !!(*out); + return 0; + } + + git_error_set(GIT_ERROR_CONFIG, "failed to parse '%s' as a boolean value", value); + return -1; +} + +int git_config_parse_int64(int64_t *out, const char *value) +{ + const char *num_end; + int64_t num; + + if (!value || git__strntol64(&num, value, strlen(value), &num_end, 0) < 0) + goto fail_parse; + + switch (*num_end) { + case 'g': + case 'G': + num *= 1024; + /* fallthrough */ + + case 'm': + case 'M': + num *= 1024; + /* fallthrough */ + + case 'k': + case 'K': + num *= 1024; + + /* check that that there are no more characters after the + * given modifier suffix */ + if (num_end[1] != '\0') + return -1; + + /* fallthrough */ + + case '\0': + *out = num; + return 0; + + default: + goto fail_parse; + } + +fail_parse: + git_error_set(GIT_ERROR_CONFIG, "failed to parse '%s' as an integer", value ? value : "(null)"); + return -1; +} + +int git_config_parse_int32(int32_t *out, const char *value) +{ + int64_t tmp; + int32_t truncate; + + if (git_config_parse_int64(&tmp, value) < 0) + goto fail_parse; + + truncate = tmp & 0xFFFFFFFF; + if (truncate != tmp) + goto fail_parse; + + *out = truncate; + return 0; + +fail_parse: + git_error_set(GIT_ERROR_CONFIG, "failed to parse '%s' as a 32-bit integer", value ? value : "(null)"); + return -1; +} + +static int normalize_section(char *start, char *end) +{ + char *scan; + + if (start == end) + return GIT_EINVALIDSPEC; + + /* Validate and downcase range */ + for (scan = start; *scan; ++scan) { + if (end && scan >= end) + break; + if (isalnum(*scan)) + *scan = (char)git__tolower(*scan); + else if (*scan != '-' || scan == start) + return GIT_EINVALIDSPEC; + } + + if (scan == start) + return GIT_EINVALIDSPEC; + + return 0; +} + + +/* Take something the user gave us and make it nice for our hash function */ +int git_config__normalize_name(const char *in, char **out) +{ + char *name, *fdot, *ldot; + + GIT_ASSERT_ARG(in); + GIT_ASSERT_ARG(out); + + name = git__strdup(in); + GIT_ERROR_CHECK_ALLOC(name); + + fdot = strchr(name, '.'); + ldot = strrchr(name, '.'); + + if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1]) + goto invalid; + + /* Validate and downcase up to first dot and after last dot */ + if (normalize_section(name, fdot) < 0 || + normalize_section(ldot + 1, NULL) < 0) + goto invalid; + + /* If there is a middle range, make sure it doesn't have newlines */ + while (fdot < ldot) + if (*fdot++ == '\n') + goto invalid; + + *out = name; + return 0; + +invalid: + git__free(name); + git_error_set(GIT_ERROR_CONFIG, "invalid config item name '%s'", in); + return GIT_EINVALIDSPEC; +} + +struct rename_data { + git_config *config; + git_str *name; + size_t old_len; +}; + +static int rename_config_entries_cb( + const git_config_entry *entry, + void *payload) +{ + int error = 0; + struct rename_data *data = (struct rename_data *)payload; + size_t base_len = git_str_len(data->name); + + if (base_len > 0 && + !(error = git_str_puts(data->name, entry->name + data->old_len))) + { + error = git_config_set_string( + data->config, git_str_cstr(data->name), entry->value); + + git_str_truncate(data->name, base_len); + } + + if (!error) + error = git_config_delete_entry(data->config, entry->name); + + return error; +} + +int git_config_rename_section( + git_repository *repo, + const char *old_section_name, + const char *new_section_name) +{ + git_config *config; + git_str pattern = GIT_STR_INIT, replace = GIT_STR_INIT; + int error = 0; + struct rename_data data; + + git_str_puts_escape_regex(&pattern, old_section_name); + + if ((error = git_str_puts(&pattern, "\\..+")) < 0) + goto cleanup; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + goto cleanup; + + data.config = config; + data.name = &replace; + data.old_len = strlen(old_section_name) + 1; + + if ((error = git_str_join(&replace, '.', new_section_name, "")) < 0) + goto cleanup; + + if (new_section_name != NULL && + (error = normalize_section(replace.ptr, strchr(replace.ptr, '.'))) < 0) + { + git_error_set( + GIT_ERROR_CONFIG, "invalid config section '%s'", new_section_name); + goto cleanup; + } + + error = git_config_foreach_match( + config, git_str_cstr(&pattern), rename_config_entries_cb, &data); + +cleanup: + git_str_dispose(&pattern); + git_str_dispose(&replace); + + return error; +} + +int git_config_init_backend(git_config_backend *backend, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + backend, version, git_config_backend, GIT_CONFIG_BACKEND_INIT); + return 0; +} diff --git a/src/libgit2/config.h b/src/libgit2/config.h new file mode 100644 index 000000000..01b84b157 --- /dev/null +++ b/src/libgit2/config.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_config_h__ +#define INCLUDE_config_h__ + +#include "common.h" + +#include "git2.h" +#include "git2/config.h" +#include "vector.h" +#include "repository.h" + +#define GIT_CONFIG_FILENAME_PROGRAMDATA "config" +#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig" +#define GIT_CONFIG_FILENAME_GLOBAL ".gitconfig" +#define GIT_CONFIG_FILENAME_XDG "config" + +#define GIT_CONFIG_FILENAME_INREPO "config" +#define GIT_CONFIG_FILE_MODE 0666 + +struct git_config { + git_refcount rc; + git_vector backends; +}; + +extern int git_config__global_location(git_str *buf); + +extern int git_config__find_global(git_str *path); +extern int git_config__find_xdg(git_str *path); +extern int git_config__find_system(git_str *path); +extern int git_config__find_programdata(git_str *path); + +extern int git_config_rename_section( + git_repository *repo, + const char *old_section_name, /* eg "branch.dummy" */ + const char *new_section_name); /* NULL to drop the old section */ + +extern int git_config__normalize_name(const char *in, char **out); + +/* internal only: does not normalize key and sets out to NULL if not found */ +extern int git_config__lookup_entry( + git_config_entry **out, + const git_config *cfg, + const char *key, + bool no_errors); + +/* internal only: update and/or delete entry string with constraints */ +extern int git_config__update_entry( + git_config *cfg, + const char *key, + const char *value, + bool overwrite_existing, + bool only_if_existing); + +int git_config__get_path( + git_str *out, + const git_config *cfg, + const char *name); + +int git_config__get_string_buf( + git_str *out, const git_config *cfg, const char *name); + +/* + * Lookup functions that cannot fail. These functions look up a config + * value and return a fallback value if the value is missing or if any + * failures occur while trying to access the value. + */ + +extern char *git_config__get_string_force( + const git_config *cfg, const char *key, const char *fallback_value); + +extern int git_config__get_bool_force( + const git_config *cfg, const char *key, int fallback_value); + +extern int git_config__get_int_force( + const git_config *cfg, const char *key, int fallback_value); + +/* API for repository configmap-style lookups from config - not cached, but + * uses configmap value maps and fallbacks + */ +extern int git_config__configmap_lookup( + int *out, git_config *config, git_configmap_item item); + +/** + * The opposite of git_config_lookup_map_value, we take an enum value + * and map it to the string or bool value on the config. + */ +int git_config_lookup_map_enum(git_configmap_t *type_out, + const char **str_out, const git_configmap *maps, + size_t map_n, int enum_val); + +/** + * Unlock the backend with the highest priority + * + * Unlocking will allow other writers to update the configuration + * file. Optionally, any changes performed since the lock will be + * applied to the configuration. + * + * @param cfg the configuration + * @param commit boolean which indicates whether to commit any changes + * done since locking + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_unlock(git_config *cfg, int commit); + +#endif diff --git a/src/libgit2/config_backend.h b/src/libgit2/config_backend.h new file mode 100644 index 000000000..dbb190514 --- /dev/null +++ b/src/libgit2/config_backend.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_config_file_h__ +#define INCLUDE_config_file_h__ + +#include "common.h" + +#include "git2/sys/config.h" +#include "git2/config.h" + +/** + * Create a configuration file backend for ondisk files + * + * These are the normal `.gitconfig` files that Core Git + * processes. Note that you first have to add this file to a + * configuration object before you can query it for configuration + * variables. + * + * @param out the new backend + * @param path where the config file is located + */ +extern int git_config_backend_from_file(git_config_backend **out, const char *path); + +/** + * Create a readonly configuration file backend from another backend + * + * This copies the complete contents of the source backend to the + * new backend. The new backend will be completely read-only and + * cannot be modified. + * + * @param out the new snapshotted backend + * @param source the backend to copy + */ +extern int git_config_backend_snapshot(git_config_backend **out, git_config_backend *source); + +/** + * Create an in-memory configuration file backend + * + * @param out the new backend + * @param cfg the configuration that is to be parsed + * @param len the length of the string pointed to by `cfg` + */ +extern int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len); + +GIT_INLINE(int) git_config_backend_open(git_config_backend *cfg, unsigned int level, const git_repository *repo) +{ + return cfg->open(cfg, level, repo); +} + +GIT_INLINE(void) git_config_backend_free(git_config_backend *cfg) +{ + if (cfg) + cfg->free(cfg); +} + +GIT_INLINE(int) git_config_backend_get_string( + git_config_entry **out, git_config_backend *cfg, const char *name) +{ + return cfg->get(cfg, name, out); +} + +GIT_INLINE(int) git_config_backend_set_string( + git_config_backend *cfg, const char *name, const char *value) +{ + return cfg->set(cfg, name, value); +} + +GIT_INLINE(int) git_config_backend_delete( + git_config_backend *cfg, const char *name) +{ + return cfg->del(cfg, name); +} + +GIT_INLINE(int) git_config_backend_foreach( + git_config_backend *cfg, + int (*fn)(const git_config_entry *entry, void *data), + void *data) +{ + return git_config_backend_foreach_match(cfg, NULL, fn, data); +} + +GIT_INLINE(int) git_config_backend_lock(git_config_backend *cfg) +{ + return cfg->lock(cfg); +} + +GIT_INLINE(int) git_config_backend_unlock(git_config_backend *cfg, int success) +{ + return cfg->unlock(cfg, success); +} + +#endif diff --git a/src/libgit2/config_cache.c b/src/libgit2/config_cache.c new file mode 100644 index 000000000..4bb91f52b --- /dev/null +++ b/src/libgit2/config_cache.c @@ -0,0 +1,142 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "futils.h" +#include "repository.h" +#include "config.h" +#include "git2/config.h" +#include "vector.h" +#include "filter.h" + +struct map_data { + const char *name; + git_configmap *maps; + size_t map_count; + int default_value; +}; + +/* + * core.eol + * Sets the line ending type to use in the working directory for + * files that have the text property set. Alternatives are lf, crlf + * and native, which uses the platform's native line ending. The default + * value is native. See gitattributes(5) for more information on + * end-of-line conversion. + */ +static git_configmap _configmap_eol[] = { + {GIT_CONFIGMAP_FALSE, NULL, GIT_EOL_UNSET}, + {GIT_CONFIGMAP_STRING, "lf", GIT_EOL_LF}, + {GIT_CONFIGMAP_STRING, "crlf", GIT_EOL_CRLF}, + {GIT_CONFIGMAP_STRING, "native", GIT_EOL_NATIVE} +}; + +/* + * core.autocrlf + * Setting this variable to "true" is almost the same as setting + * the text attribute to "auto" on all files except that text files are + * not guaranteed to be normalized: files that contain CRLF in the + * repository will not be touched. Use this setting if you want to have + * CRLF line endings in your working directory even though the repository + * does not have normalized line endings. This variable can be set to input, + * in which case no output conversion is performed. + */ +static git_configmap _configmap_autocrlf[] = { + {GIT_CONFIGMAP_FALSE, NULL, GIT_AUTO_CRLF_FALSE}, + {GIT_CONFIGMAP_TRUE, NULL, GIT_AUTO_CRLF_TRUE}, + {GIT_CONFIGMAP_STRING, "input", GIT_AUTO_CRLF_INPUT} +}; + +static git_configmap _configmap_safecrlf[] = { + {GIT_CONFIGMAP_FALSE, NULL, GIT_SAFE_CRLF_FALSE}, + {GIT_CONFIGMAP_TRUE, NULL, GIT_SAFE_CRLF_FAIL}, + {GIT_CONFIGMAP_STRING, "warn", GIT_SAFE_CRLF_WARN} +}; + +static git_configmap _configmap_logallrefupdates[] = { + {GIT_CONFIGMAP_FALSE, NULL, GIT_LOGALLREFUPDATES_FALSE}, + {GIT_CONFIGMAP_TRUE, NULL, GIT_LOGALLREFUPDATES_TRUE}, + {GIT_CONFIGMAP_STRING, "always", GIT_LOGALLREFUPDATES_ALWAYS}, +}; + +/* + * Generic map for integer values + */ +static git_configmap _configmap_int[] = { + {GIT_CONFIGMAP_INT32, NULL, 0}, +}; + +static struct map_data _configmaps[] = { + {"core.autocrlf", _configmap_autocrlf, ARRAY_SIZE(_configmap_autocrlf), GIT_AUTO_CRLF_DEFAULT}, + {"core.eol", _configmap_eol, ARRAY_SIZE(_configmap_eol), GIT_EOL_DEFAULT}, + {"core.symlinks", NULL, 0, GIT_SYMLINKS_DEFAULT }, + {"core.ignorecase", NULL, 0, GIT_IGNORECASE_DEFAULT }, + {"core.filemode", NULL, 0, GIT_FILEMODE_DEFAULT }, + {"core.ignorestat", NULL, 0, GIT_IGNORESTAT_DEFAULT }, + {"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT }, + {"core.abbrev", _configmap_int, 1, GIT_ABBREV_DEFAULT }, + {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT }, + {"core.safecrlf", _configmap_safecrlf, ARRAY_SIZE(_configmap_safecrlf), GIT_SAFE_CRLF_DEFAULT}, + {"core.logallrefupdates", _configmap_logallrefupdates, ARRAY_SIZE(_configmap_logallrefupdates), GIT_LOGALLREFUPDATES_DEFAULT}, + {"core.protecthfs", NULL, 0, GIT_PROTECTHFS_DEFAULT }, + {"core.protectntfs", NULL, 0, GIT_PROTECTNTFS_DEFAULT }, + {"core.fsyncobjectfiles", NULL, 0, GIT_FSYNCOBJECTFILES_DEFAULT }, + {"core.longpaths", NULL, 0, GIT_LONGPATHS_DEFAULT }, +}; + +int git_config__configmap_lookup(int *out, git_config *config, git_configmap_item item) +{ + int error = 0; + struct map_data *data = &_configmaps[(int)item]; + git_config_entry *entry; + + if ((error = git_config__lookup_entry(&entry, config, data->name, false)) < 0) + return error; + + if (!entry) + *out = data->default_value; + else if (data->maps) + error = git_config_lookup_map_value( + out, data->maps, data->map_count, entry->value); + else + error = git_config_parse_bool(out, entry->value); + + git_config_entry_free(entry); + return error; +} + +int git_repository__configmap_lookup(int *out, git_repository *repo, git_configmap_item item) +{ + intptr_t value = (intptr_t)git_atomic_load(repo->configmap_cache[(int)item]); + + *out = (int)value; + + if (value == GIT_CONFIGMAP_NOT_CACHED) { + git_config *config; + intptr_t oldval = value; + int error; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0 || + (error = git_config__configmap_lookup(out, config, item)) < 0) + return error; + + value = *out; + git_atomic_compare_and_swap(&repo->configmap_cache[(int)item], (void *)oldval, (void *)value); + } + + return 0; +} + +void git_repository__configmap_lookup_cache_clear(git_repository *repo) +{ + int i; + + for (i = 0; i < GIT_CONFIGMAP_CACHE_MAX; ++i) + repo->configmap_cache[i] = GIT_CONFIGMAP_NOT_CACHED; +} + diff --git a/src/libgit2/config_entries.c b/src/libgit2/config_entries.c new file mode 100644 index 000000000..66aae096d --- /dev/null +++ b/src/libgit2/config_entries.c @@ -0,0 +1,237 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "config_entries.h" + +typedef struct config_entry_list { + struct config_entry_list *next; + struct config_entry_list *last; + git_config_entry *entry; +} config_entry_list; + +typedef struct { + git_config_entry *entry; + bool multivar; +} config_entry_map_head; + +typedef struct config_entries_iterator { + git_config_iterator parent; + git_config_entries *entries; + config_entry_list *head; +} config_entries_iterator; + +struct git_config_entries { + git_refcount rc; + git_strmap *map; + config_entry_list *list; +}; + +int git_config_entries_new(git_config_entries **out) +{ + git_config_entries *entries; + int error; + + entries = git__calloc(1, sizeof(git_config_entries)); + GIT_ERROR_CHECK_ALLOC(entries); + GIT_REFCOUNT_INC(entries); + + if ((error = git_strmap_new(&entries->map)) < 0) + git__free(entries); + else + *out = entries; + + return error; +} + +int git_config_entries_dup_entry(git_config_entries *entries, const git_config_entry *entry) +{ + git_config_entry *duplicated; + int error; + + duplicated = git__calloc(1, sizeof(git_config_entry)); + GIT_ERROR_CHECK_ALLOC(duplicated); + + duplicated->name = git__strdup(entry->name); + GIT_ERROR_CHECK_ALLOC(duplicated->name); + + if (entry->value) { + duplicated->value = git__strdup(entry->value); + GIT_ERROR_CHECK_ALLOC(duplicated->value); + } + duplicated->level = entry->level; + duplicated->include_depth = entry->include_depth; + + if ((error = git_config_entries_append(entries, duplicated)) < 0) + goto out; + +out: + if (error && duplicated) { + git__free((char *) duplicated->name); + git__free((char *) duplicated->value); + git__free(duplicated); + } + return error; +} + +int git_config_entries_dup(git_config_entries **out, git_config_entries *entries) +{ + git_config_entries *result = NULL; + config_entry_list *head; + int error; + + if ((error = git_config_entries_new(&result)) < 0) + goto out; + + for (head = entries->list; head; head = head->next) + if ((git_config_entries_dup_entry(result, head->entry)) < 0) + goto out; + + *out = result; + result = NULL; + +out: + git_config_entries_free(result); + return error; +} + +void git_config_entries_incref(git_config_entries *entries) +{ + GIT_REFCOUNT_INC(entries); +} + +static void config_entries_free(git_config_entries *entries) +{ + config_entry_list *list = NULL, *next; + config_entry_map_head *head; + + git_strmap_foreach_value(entries->map, head, + git__free((char *) head->entry->name); git__free(head) + ); + git_strmap_free(entries->map); + + list = entries->list; + while (list != NULL) { + next = list->next; + git__free((char *) list->entry->value); + git__free(list->entry); + git__free(list); + list = next; + } + + git__free(entries); +} + +void git_config_entries_free(git_config_entries *entries) +{ + if (entries) + GIT_REFCOUNT_DEC(entries, config_entries_free); +} + +int git_config_entries_append(git_config_entries *entries, git_config_entry *entry) +{ + config_entry_list *list_head; + config_entry_map_head *map_head; + + if ((map_head = git_strmap_get(entries->map, entry->name)) != NULL) { + map_head->multivar = true; + /* + * This is a micro-optimization for configuration files + * with a lot of same keys. As for multivars the entry's + * key will be the same for all entries, we can just free + * all except the first entry's name and just re-use it. + */ + git__free((char *) entry->name); + entry->name = map_head->entry->name; + } else { + map_head = git__calloc(1, sizeof(*map_head)); + if ((git_strmap_set(entries->map, entry->name, map_head)) < 0) + return -1; + } + map_head->entry = entry; + + list_head = git__calloc(1, sizeof(config_entry_list)); + GIT_ERROR_CHECK_ALLOC(list_head); + list_head->entry = entry; + + if (entries->list) + entries->list->last->next = list_head; + else + entries->list = list_head; + entries->list->last = list_head; + + return 0; +} + +int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key) +{ + config_entry_map_head *entry; + if ((entry = git_strmap_get(entries->map, key)) == NULL) + return GIT_ENOTFOUND; + *out = entry->entry; + return 0; +} + +int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key) +{ + config_entry_map_head *entry; + + if ((entry = git_strmap_get(entries->map, key)) == NULL) + return GIT_ENOTFOUND; + + if (entry->multivar) { + git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being a multivar"); + return -1; + } + + if (entry->entry->include_depth) { + git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being included"); + return -1; + } + + *out = entry->entry; + + return 0; +} + +static void config_iterator_free(git_config_iterator *iter) +{ + config_entries_iterator *it = (config_entries_iterator *) iter; + git_config_entries_free(it->entries); + git__free(it); +} + +static int config_iterator_next( + git_config_entry **entry, + git_config_iterator *iter) +{ + config_entries_iterator *it = (config_entries_iterator *) iter; + + if (!it->head) + return GIT_ITEROVER; + + *entry = it->head->entry; + it->head = it->head->next; + + return 0; +} + +int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries) +{ + config_entries_iterator *it; + + it = git__calloc(1, sizeof(config_entries_iterator)); + GIT_ERROR_CHECK_ALLOC(it); + it->parent.next = config_iterator_next; + it->parent.free = config_iterator_free; + it->head = entries->list; + it->entries = entries; + + git_config_entries_incref(entries); + *out = &it->parent; + + return 0; +} diff --git a/src/libgit2/config_entries.h b/src/libgit2/config_entries.h new file mode 100644 index 000000000..832379e74 --- /dev/null +++ b/src/libgit2/config_entries.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "git2/sys/config.h" +#include "config.h" + +typedef struct git_config_entries git_config_entries; + +int git_config_entries_new(git_config_entries **out); +int git_config_entries_dup(git_config_entries **out, git_config_entries *entries); +int git_config_entries_dup_entry(git_config_entries *entries, const git_config_entry *entry); +void git_config_entries_incref(git_config_entries *entries); +void git_config_entries_free(git_config_entries *entries); +/* Add or append the new config option */ +int git_config_entries_append(git_config_entries *entries, git_config_entry *entry); +int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key); +int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key); +int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries); diff --git a/src/libgit2/config_file.c b/src/libgit2/config_file.c new file mode 100644 index 000000000..66fcb8ae2 --- /dev/null +++ b/src/libgit2/config_file.c @@ -0,0 +1,1194 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "config.h" + +#include "git2/config.h" +#include "git2/sys/config.h" + +#include "array.h" +#include "str.h" +#include "config_backend.h" +#include "config_entries.h" +#include "config_parse.h" +#include "filebuf.h" +#include "regexp.h" +#include "sysdir.h" +#include "wildmatch.h" +#include "hash.h" + +/* Max depth for [include] directives */ +#define MAX_INCLUDE_DEPTH 10 + +typedef struct config_file { + git_futils_filestamp stamp; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + char *path; + git_array_t(struct config_file) includes; +} config_file; + +typedef struct { + git_config_backend parent; + git_mutex values_mutex; + git_config_entries *entries; + const git_repository *repo; + git_config_level_t level; + + git_array_t(git_config_parser) readers; + + bool locked; + git_filebuf locked_buf; + git_str locked_content; + + config_file file; +} config_file_backend; + +typedef struct { + const git_repository *repo; + config_file *file; + git_config_entries *entries; + git_config_level_t level; + unsigned int depth; +} config_file_parse_data; + +static int config_file_read(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth); +static int config_file_read_buffer(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth, const char *buf, size_t buflen); +static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value); +static char *escape_value(const char *ptr); + +/** + * Take the current values map from the backend and increase its + * refcount. This is its own function to make sure we use the mutex to + * avoid the map pointer from changing under us. + */ +static int config_file_entries_take(git_config_entries **out, config_file_backend *b) +{ + int error; + + if ((error = git_mutex_lock(&b->values_mutex)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock config backend"); + return error; + } + + git_config_entries_incref(b->entries); + *out = b->entries; + + git_mutex_unlock(&b->values_mutex); + + return 0; +} + +static void config_file_clear(config_file *file) +{ + config_file *include; + uint32_t i; + + if (file == NULL) + return; + + git_array_foreach(file->includes, i, include) { + config_file_clear(include); + } + git_array_clear(file->includes); + + git__free(file->path); +} + +static int config_file_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + int res; + + b->level = level; + b->repo = repo; + + if ((res = git_config_entries_new(&b->entries)) < 0) + return res; + + if (!git_fs_path_exists(b->file.path)) + return 0; + + /* + * git silently ignores configuration files that are not + * readable. We emulate that behavior. This is particularly + * important for sandboxed applications on macOS where the + * git configuration files may not be readable. + */ + if (p_access(b->file.path, R_OK) < 0) + return GIT_ENOTFOUND; + + if (res < 0 || (res = config_file_read(b->entries, repo, &b->file, level, 0)) < 0) { + git_config_entries_free(b->entries); + b->entries = NULL; + } + + return res; +} + +static int config_file_is_modified(int *modified, config_file *file) +{ + config_file *include; + git_str buf = GIT_STR_INIT; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + uint32_t i; + int error = 0; + + *modified = 0; + + if (!git_futils_filestamp_check(&file->stamp, file->path)) + goto check_includes; + + if ((error = git_futils_readbuffer(&buf, file->path)) < 0) + goto out; + + if ((error = git_hash_buf(checksum, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0) + goto out; + + if (memcmp(checksum, file->checksum, GIT_HASH_SHA1_SIZE) != 0) { + *modified = 1; + goto out; + } + +check_includes: + git_array_foreach(file->includes, i, include) { + if ((error = config_file_is_modified(modified, include)) < 0 || *modified) + goto out; + } + +out: + git_str_dispose(&buf); + + return error; +} + +static void config_file_clear_includes(config_file_backend *cfg) +{ + config_file *include; + uint32_t i; + + git_array_foreach(cfg->file.includes, i, include) + config_file_clear(include); + git_array_clear(cfg->file.includes); +} + +static int config_file_set_entries(git_config_backend *cfg, git_config_entries *entries) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_config_entries *old = NULL; + int error; + + if (b->parent.readonly) { + git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); + return -1; + } + + if ((error = git_mutex_lock(&b->values_mutex)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock config backend"); + goto out; + } + + old = b->entries; + b->entries = entries; + + git_mutex_unlock(&b->values_mutex); + +out: + git_config_entries_free(old); + return error; +} + +static int config_file_refresh_from_buffer(git_config_backend *cfg, const char *buf, size_t buflen) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_config_entries *entries = NULL; + int error; + + config_file_clear_includes(b); + + if ((error = git_config_entries_new(&entries)) < 0 || + (error = config_file_read_buffer(entries, b->repo, &b->file, + b->level, 0, buf, buflen)) < 0 || + (error = config_file_set_entries(cfg, entries)) < 0) + goto out; + + entries = NULL; +out: + git_config_entries_free(entries); + return error; +} + +static int config_file_refresh(git_config_backend *cfg) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_config_entries *entries = NULL; + int error, modified; + + if (cfg->readonly) + return 0; + + if ((error = config_file_is_modified(&modified, &b->file)) < 0 && error != GIT_ENOTFOUND) + goto out; + + if (!modified) + return 0; + + config_file_clear_includes(b); + + if ((error = git_config_entries_new(&entries)) < 0 || + (error = config_file_read(entries, b->repo, &b->file, b->level, 0)) < 0 || + (error = config_file_set_entries(cfg, entries)) < 0) + goto out; + + entries = NULL; +out: + git_config_entries_free(entries); + + return (error == GIT_ENOTFOUND) ? 0 : error; +} + +static void config_file_free(git_config_backend *_backend) +{ + config_file_backend *backend = GIT_CONTAINER_OF(_backend, config_file_backend, parent); + + if (backend == NULL) + return; + + config_file_clear(&backend->file); + git_config_entries_free(backend->entries); + git_mutex_free(&backend->values_mutex); + git__free(backend); +} + +static int config_file_iterator( + git_config_iterator **iter, + struct git_config_backend *backend) +{ + config_file_backend *b = GIT_CONTAINER_OF(backend, config_file_backend, parent); + git_config_entries *dupped = NULL, *entries = NULL; + int error; + + if ((error = config_file_refresh(backend)) < 0 || + (error = config_file_entries_take(&entries, b)) < 0 || + (error = git_config_entries_dup(&dupped, entries)) < 0 || + (error = git_config_entries_iterator_new(iter, dupped)) < 0) + goto out; + +out: + /* Let iterator delete duplicated entries when it's done */ + git_config_entries_free(entries); + git_config_entries_free(dupped); + return error; +} + +static int config_file_snapshot(git_config_backend **out, git_config_backend *backend) +{ + return git_config_backend_snapshot(out, backend); +} + +static int config_file_set(git_config_backend *cfg, const char *name, const char *value) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_config_entries *entries; + git_config_entry *existing; + char *key, *esc_value = NULL; + int error; + + if ((error = git_config__normalize_name(name, &key)) < 0) + return error; + + if ((error = config_file_entries_take(&entries, b)) < 0) + return error; + + /* Check whether we'd be modifying an included or multivar key */ + if ((error = git_config_entries_get_unique(&existing, entries, key)) < 0) { + if (error != GIT_ENOTFOUND) + goto out; + error = 0; + } else if ((!existing->value && !value) || + (existing->value && value && !strcmp(existing->value, value))) { + /* don't update if old and new values already match */ + error = 0; + goto out; + } + + /* No early returns due to sanity checks, let's write it out and refresh */ + if (value) { + esc_value = escape_value(value); + GIT_ERROR_CHECK_ALLOC(esc_value); + } + + if ((error = config_file_write(b, name, key, NULL, esc_value)) < 0) + goto out; + +out: + git_config_entries_free(entries); + git__free(esc_value); + git__free(key); + return error; +} + +/* release the map containing the entry as an equivalent to freeing it */ +static void config_file_entry_free(git_config_entry *entry) +{ + git_config_entries *entries = (git_config_entries *) entry->payload; + git_config_entries_free(entries); +} + +/* + * Internal function that actually gets the value in string form + */ +static int config_file_get(git_config_backend *cfg, const char *key, git_config_entry **out) +{ + config_file_backend *h = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_config_entries *entries = NULL; + git_config_entry *entry; + int error = 0; + + if (!h->parent.readonly && ((error = config_file_refresh(cfg)) < 0)) + return error; + + if ((error = config_file_entries_take(&entries, h)) < 0) + return error; + + if ((error = (git_config_entries_get(&entry, entries, key))) < 0) { + git_config_entries_free(entries); + return error; + } + + entry->free = config_file_entry_free; + entry->payload = entries; + *out = entry; + + return 0; +} + +static int config_file_set_multivar( + git_config_backend *cfg, const char *name, const char *regexp, const char *value) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_regexp preg; + int result; + char *key; + + GIT_ASSERT_ARG(regexp); + + if ((result = git_config__normalize_name(name, &key)) < 0) + return result; + + if ((result = git_regexp_compile(&preg, regexp, 0)) < 0) + goto out; + + /* If we do have it, set call config_file_write() and reload */ + if ((result = config_file_write(b, name, key, &preg, value)) < 0) + goto out; + +out: + git__free(key); + git_regexp_dispose(&preg); + + return result; +} + +static int config_file_delete(git_config_backend *cfg, const char *name) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_config_entries *entries = NULL; + git_config_entry *entry; + char *key = NULL; + int error; + + if ((error = git_config__normalize_name(name, &key)) < 0) + goto out; + + if ((error = config_file_entries_take(&entries, b)) < 0) + goto out; + + /* Check whether we'd be modifying an included or multivar key */ + if ((error = git_config_entries_get_unique(&entry, entries, key)) < 0) { + if (error == GIT_ENOTFOUND) + git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name); + goto out; + } + + if ((error = config_file_write(b, name, entry->name, NULL, NULL)) < 0) + goto out; + +out: + git_config_entries_free(entries); + git__free(key); + return error; +} + +static int config_file_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_config_entries *entries = NULL; + git_config_entry *entry = NULL; + git_regexp preg = GIT_REGEX_INIT; + char *key = NULL; + int result; + + if ((result = git_config__normalize_name(name, &key)) < 0) + goto out; + + if ((result = config_file_entries_take(&entries, b)) < 0) + goto out; + + if ((result = git_config_entries_get(&entry, entries, key)) < 0) { + if (result == GIT_ENOTFOUND) + git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name); + goto out; + } + + if ((result = git_regexp_compile(&preg, regexp, 0)) < 0) + goto out; + + if ((result = config_file_write(b, name, key, &preg, NULL)) < 0) + goto out; + +out: + git_config_entries_free(entries); + git__free(key); + git_regexp_dispose(&preg); + return result; +} + +static int config_file_lock(git_config_backend *_cfg) +{ + config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent); + int error; + + if ((error = git_filebuf_open(&cfg->locked_buf, cfg->file.path, 0, GIT_CONFIG_FILE_MODE)) < 0) + return error; + + error = git_futils_readbuffer(&cfg->locked_content, cfg->file.path); + if (error < 0 && error != GIT_ENOTFOUND) { + git_filebuf_cleanup(&cfg->locked_buf); + return error; + } + + cfg->locked = true; + return 0; + +} + +static int config_file_unlock(git_config_backend *_cfg, int success) +{ + config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent); + int error = 0; + + if (success) { + git_filebuf_write(&cfg->locked_buf, cfg->locked_content.ptr, cfg->locked_content.size); + error = git_filebuf_commit(&cfg->locked_buf); + } + + git_filebuf_cleanup(&cfg->locked_buf); + git_str_dispose(&cfg->locked_content); + cfg->locked = false; + + return error; +} + +int git_config_backend_from_file(git_config_backend **out, const char *path) +{ + config_file_backend *backend; + + backend = git__calloc(1, sizeof(config_file_backend)); + GIT_ERROR_CHECK_ALLOC(backend); + + backend->parent.version = GIT_CONFIG_BACKEND_VERSION; + git_mutex_init(&backend->values_mutex); + + backend->file.path = git__strdup(path); + GIT_ERROR_CHECK_ALLOC(backend->file.path); + git_array_init(backend->file.includes); + + backend->parent.open = config_file_open; + backend->parent.get = config_file_get; + backend->parent.set = config_file_set; + backend->parent.set_multivar = config_file_set_multivar; + backend->parent.del = config_file_delete; + backend->parent.del_multivar = config_file_delete_multivar; + backend->parent.iterator = config_file_iterator; + backend->parent.snapshot = config_file_snapshot; + backend->parent.lock = config_file_lock; + backend->parent.unlock = config_file_unlock; + backend->parent.free = config_file_free; + + *out = (git_config_backend *)backend; + + return 0; +} + +static int included_path(git_str *out, const char *dir, const char *path) +{ + /* From the user's home */ + if (path[0] == '~' && path[1] == '/') + return git_sysdir_expand_global_file(out, &path[1]); + + return git_fs_path_join_unrooted(out, path, dir, NULL); +} + +/* Escape the values to write them to the file */ +static char *escape_value(const char *ptr) +{ + git_str buf; + size_t len; + const char *esc; + + GIT_ASSERT_ARG_WITH_RETVAL(ptr, NULL); + + len = strlen(ptr); + if (!len) + return git__calloc(1, sizeof(char)); + + if (git_str_init(&buf, len) < 0) + return NULL; + + while (*ptr != '\0') { + if ((esc = strchr(git_config_escaped, *ptr)) != NULL) { + git_str_putc(&buf, '\\'); + git_str_putc(&buf, git_config_escapes[esc - git_config_escaped]); + } else { + git_str_putc(&buf, *ptr); + } + ptr++; + } + + if (git_str_oom(&buf)) + return NULL; + + return git_str_detach(&buf); +} + +static int parse_include(config_file_parse_data *parse_data, const char *file) +{ + config_file *include; + git_str path = GIT_STR_INIT; + char *dir; + int result; + + if (!file) + return 0; + + if ((result = git_fs_path_dirname_r(&path, parse_data->file->path)) < 0) + return result; + + dir = git_str_detach(&path); + result = included_path(&path, dir, file); + git__free(dir); + + if (result < 0) + return result; + + include = git_array_alloc(parse_data->file->includes); + GIT_ERROR_CHECK_ALLOC(include); + memset(include, 0, sizeof(*include)); + git_array_init(include->includes); + include->path = git_str_detach(&path); + + result = config_file_read(parse_data->entries, parse_data->repo, include, + parse_data->level, parse_data->depth+1); + + if (result == GIT_ENOTFOUND) { + git_error_clear(); + result = 0; + } + + return result; +} + +static int do_match_gitdir( + int *matches, + const git_repository *repo, + const char *cfg_file, + const char *condition, + bool case_insensitive) +{ + git_str pattern = GIT_STR_INIT, gitdir = GIT_STR_INIT; + int error; + + if (condition[0] == '.' && git_fs_path_is_dirsep(condition[1])) { + git_fs_path_dirname_r(&pattern, cfg_file); + git_str_joinpath(&pattern, pattern.ptr, condition + 2); + } else if (condition[0] == '~' && git_fs_path_is_dirsep(condition[1])) + git_sysdir_expand_global_file(&pattern, condition + 1); + else if (!git_fs_path_is_absolute(condition)) + git_str_joinpath(&pattern, "**", condition); + else + git_str_sets(&pattern, condition); + + if (git_fs_path_is_dirsep(condition[strlen(condition) - 1])) + git_str_puts(&pattern, "**"); + + if (git_str_oom(&pattern)) { + error = -1; + goto out; + } + + if ((error = git_repository__item_path(&gitdir, repo, GIT_REPOSITORY_ITEM_GITDIR)) < 0) + goto out; + + if (git_fs_path_is_dirsep(gitdir.ptr[gitdir.size - 1])) + git_str_truncate(&gitdir, gitdir.size - 1); + + *matches = wildmatch(pattern.ptr, gitdir.ptr, + WM_PATHNAME | (case_insensitive ? WM_CASEFOLD : 0)) == WM_MATCH; +out: + git_str_dispose(&pattern); + git_str_dispose(&gitdir); + return error; +} + +static int conditional_match_gitdir( + int *matches, + const git_repository *repo, + const char *cfg_file, + const char *value) +{ + return do_match_gitdir(matches, repo, cfg_file, value, false); +} + +static int conditional_match_gitdir_i( + int *matches, + const git_repository *repo, + const char *cfg_file, + const char *value) +{ + return do_match_gitdir(matches, repo, cfg_file, value, true); +} + +static int conditional_match_onbranch( + int *matches, + const git_repository *repo, + const char *cfg_file, + const char *condition) +{ + git_str reference = GIT_STR_INIT, buf = GIT_STR_INIT; + int error; + + GIT_UNUSED(cfg_file); + + /* + * NOTE: you cannot use `git_repository_head` here. Looking up the + * HEAD reference will create the ODB, which causes us to read the + * repo's config for keys like core.precomposeUnicode. As we're + * just parsing the config right now, though, this would result in + * an endless recursion. + */ + + if ((error = git_str_joinpath(&buf, git_repository_path(repo), GIT_HEAD_FILE)) < 0 || + (error = git_futils_readbuffer(&reference, buf.ptr)) < 0) + goto out; + git_str_rtrim(&reference); + + if (git__strncmp(reference.ptr, GIT_SYMREF, strlen(GIT_SYMREF))) + goto out; + git_str_consume(&reference, reference.ptr + strlen(GIT_SYMREF)); + + if (git__strncmp(reference.ptr, GIT_REFS_HEADS_DIR, strlen(GIT_REFS_HEADS_DIR))) + goto out; + git_str_consume(&reference, reference.ptr + strlen(GIT_REFS_HEADS_DIR)); + + /* + * If the condition ends with a '/', then we should treat it as if + * it had '**' appended. + */ + if ((error = git_str_sets(&buf, condition)) < 0) + goto out; + if (git_fs_path_is_dirsep(condition[strlen(condition) - 1]) && + (error = git_str_puts(&buf, "**")) < 0) + goto out; + + *matches = wildmatch(buf.ptr, reference.ptr, WM_PATHNAME) == WM_MATCH; +out: + git_str_dispose(&reference); + git_str_dispose(&buf); + + return error; + +} + +static const struct { + const char *prefix; + int (*matches)(int *matches, const git_repository *repo, const char *cfg, const char *value); +} conditions[] = { + { "gitdir:", conditional_match_gitdir }, + { "gitdir/i:", conditional_match_gitdir_i }, + { "onbranch:", conditional_match_onbranch } +}; + +static int parse_conditional_include(config_file_parse_data *parse_data, const char *section, const char *file) +{ + char *condition; + size_t section_len, i; + int error = 0, matches; + + if (!parse_data->repo || !file) + return 0; + + section_len = strlen(section); + + /* + * We checked that the string starts with `includeIf.` and ends + * in `.path` to get here. Make sure it consists of more. + */ + if (section_len < CONST_STRLEN("includeIf.") + CONST_STRLEN(".path")) + return 0; + + condition = git__substrdup(section + CONST_STRLEN("includeIf."), + section_len - CONST_STRLEN("includeIf.") - CONST_STRLEN(".path")); + + GIT_ERROR_CHECK_ALLOC(condition); + + for (i = 0; i < ARRAY_SIZE(conditions); i++) { + if (git__prefixcmp(condition, conditions[i].prefix)) + continue; + + if ((error = conditions[i].matches(&matches, + parse_data->repo, + parse_data->file->path, + condition + strlen(conditions[i].prefix))) < 0) + break; + + if (matches) + error = parse_include(parse_data, file); + + break; + } + + git__free(condition); + return error; +} + +static int read_on_variable( + git_config_parser *reader, + const char *current_section, + const char *var_name, + const char *var_value, + const char *line, + size_t line_len, + void *data) +{ + config_file_parse_data *parse_data = (config_file_parse_data *)data; + git_str buf = GIT_STR_INIT; + git_config_entry *entry; + const char *c; + int result = 0; + + GIT_UNUSED(reader); + GIT_UNUSED(line); + GIT_UNUSED(line_len); + + if (current_section) { + /* TODO: Once warnings lang, we should likely warn + * here. Git appears to warn in most cases if it sees + * un-namespaced config options. + */ + git_str_puts(&buf, current_section); + git_str_putc(&buf, '.'); + } + + for (c = var_name; *c; c++) + git_str_putc(&buf, git__tolower(*c)); + + if (git_str_oom(&buf)) + return -1; + + entry = git__calloc(1, sizeof(git_config_entry)); + GIT_ERROR_CHECK_ALLOC(entry); + entry->name = git_str_detach(&buf); + entry->value = var_value ? git__strdup(var_value) : NULL; + entry->level = parse_data->level; + entry->include_depth = parse_data->depth; + + if ((result = git_config_entries_append(parse_data->entries, entry)) < 0) + return result; + + result = 0; + + /* Add or append the new config option */ + if (!git__strcmp(entry->name, "include.path")) + result = parse_include(parse_data, entry->value); + else if (!git__prefixcmp(entry->name, "includeif.") && + !git__suffixcmp(entry->name, ".path")) + result = parse_conditional_include(parse_data, entry->name, entry->value); + + return result; +} + +static int config_file_read_buffer( + git_config_entries *entries, + const git_repository *repo, + config_file *file, + git_config_level_t level, + int depth, + const char *buf, + size_t buflen) +{ + config_file_parse_data parse_data; + git_config_parser reader; + int error; + + if (depth >= MAX_INCLUDE_DEPTH) { + git_error_set(GIT_ERROR_CONFIG, "maximum config include depth reached"); + return -1; + } + + /* Initialize the reading position */ + reader.path = file->path; + git_parse_ctx_init(&reader.ctx, buf, buflen); + + /* If the file is empty, there's nothing for us to do */ + if (!reader.ctx.content || *reader.ctx.content == '\0') { + error = 0; + goto out; + } + + parse_data.repo = repo; + parse_data.file = file; + parse_data.entries = entries; + parse_data.level = level; + parse_data.depth = depth; + + error = git_config_parse(&reader, NULL, read_on_variable, NULL, NULL, &parse_data); + +out: + return error; +} + +static int config_file_read( + git_config_entries *entries, + const git_repository *repo, + config_file *file, + git_config_level_t level, + int depth) +{ + git_str contents = GIT_STR_INIT; + struct stat st; + int error; + + if (p_stat(file->path, &st) < 0) { + error = git_fs_path_set_error(errno, file->path, "stat"); + goto out; + } + + if ((error = git_futils_readbuffer(&contents, file->path)) < 0) + goto out; + + git_futils_filestamp_set_from_stat(&file->stamp, &st); + if ((error = git_hash_buf(file->checksum, contents.ptr, contents.size, GIT_HASH_ALGORITHM_SHA1)) < 0) + goto out; + + if ((error = config_file_read_buffer(entries, repo, file, level, depth, + contents.ptr, contents.size)) < 0) + goto out; + +out: + git_str_dispose(&contents); + return error; +} + +static int write_section(git_str *fbuf, const char *key) +{ + int result; + const char *dot; + git_str buf = GIT_STR_INIT; + + /* All of this just for [section "subsection"] */ + dot = strchr(key, '.'); + git_str_putc(&buf, '['); + if (dot == NULL) { + git_str_puts(&buf, key); + } else { + char *escaped; + git_str_put(&buf, key, dot - key); + escaped = escape_value(dot + 1); + GIT_ERROR_CHECK_ALLOC(escaped); + git_str_printf(&buf, " \"%s\"", escaped); + git__free(escaped); + } + git_str_puts(&buf, "]\n"); + + if (git_str_oom(&buf)) + return -1; + + result = git_str_put(fbuf, git_str_cstr(&buf), buf.size); + git_str_dispose(&buf); + + return result; +} + +static const char *quotes_for_value(const char *value) +{ + const char *ptr; + + if (value[0] == ' ' || value[0] == '\0') + return "\""; + + for (ptr = value; *ptr; ++ptr) { + if (*ptr == ';' || *ptr == '#') + return "\""; + } + + if (ptr[-1] == ' ') + return "\""; + + return ""; +} + +struct write_data { + git_str *buf; + git_str buffered_comment; + unsigned int in_section : 1, + preg_replaced : 1; + const char *orig_section; + const char *section; + const char *orig_name; + const char *name; + const git_regexp *preg; + const char *value; +}; + +static int write_line_to(git_str *buf, const char *line, size_t line_len) +{ + int result = git_str_put(buf, line, line_len); + + if (!result && line_len && line[line_len-1] != '\n') + result = git_str_printf(buf, "\n"); + + return result; +} + +static int write_line(struct write_data *write_data, const char *line, size_t line_len) +{ + return write_line_to(write_data->buf, line, line_len); +} + +static int write_value(struct write_data *write_data) +{ + const char *q; + int result; + + q = quotes_for_value(write_data->value); + result = git_str_printf(write_data->buf, + "\t%s = %s%s%s\n", write_data->orig_name, q, write_data->value, q); + + /* If we are updating a single name/value, we're done. Setting `value` + * to `NULL` will prevent us from trying to write it again later (in + * `write_on_section`) if we see the same section repeated. + */ + if (!write_data->preg) + write_data->value = NULL; + + return result; +} + +static int write_on_section( + git_config_parser *reader, + const char *current_section, + const char *line, + size_t line_len, + void *data) +{ + struct write_data *write_data = (struct write_data *)data; + int result = 0; + + GIT_UNUSED(reader); + + /* If we were previously in the correct section (but aren't anymore) + * and haven't written our value (for a simple name/value set, not + * a multivar), then append it to the end of the section before writing + * the new one. + */ + if (write_data->in_section && !write_data->preg && write_data->value) + result = write_value(write_data); + + write_data->in_section = strcmp(current_section, write_data->section) == 0; + + /* + * If there were comments just before this section, dump them as well. + */ + if (!result) { + result = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size); + git_str_clear(&write_data->buffered_comment); + } + + if (!result) + result = write_line(write_data, line, line_len); + + return result; +} + +static int write_on_variable( + git_config_parser *reader, + const char *current_section, + const char *var_name, + const char *var_value, + const char *line, + size_t line_len, + void *data) +{ + struct write_data *write_data = (struct write_data *)data; + bool has_matched = false; + int error; + + GIT_UNUSED(reader); + GIT_UNUSED(current_section); + + /* + * If there were comments just before this variable, let's dump them as well. + */ + if ((error = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) + return error; + + git_str_clear(&write_data->buffered_comment); + + /* See if we are to update this name/value pair; first examine name */ + if (write_data->in_section && + strcasecmp(write_data->name, var_name) == 0) + has_matched = true; + + /* If we have a regex to match the value, see if it matches */ + if (has_matched && write_data->preg != NULL) + has_matched = (git_regexp_match(write_data->preg, var_value) == 0); + + /* If this isn't the name/value we're looking for, simply dump the + * existing data back out and continue on. + */ + if (!has_matched) + return write_line(write_data, line, line_len); + + write_data->preg_replaced = 1; + + /* If value is NULL, we are deleting this value; write nothing. */ + if (!write_data->value) + return 0; + + return write_value(write_data); +} + +static int write_on_comment(git_config_parser *reader, const char *line, size_t line_len, void *data) +{ + struct write_data *write_data; + + GIT_UNUSED(reader); + + write_data = (struct write_data *)data; + return write_line_to(&write_data->buffered_comment, line, line_len); +} + +static int write_on_eof( + git_config_parser *reader, const char *current_section, void *data) +{ + struct write_data *write_data = (struct write_data *)data; + int result = 0; + + GIT_UNUSED(reader); + + /* + * If we've buffered comments when reaching EOF, make sure to dump them. + */ + if ((result = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) + return result; + + /* If we are at the EOF and have not written our value (again, for a + * simple name/value set, not a multivar) then we have never seen the + * section in question and should create a new section and write the + * value. + */ + if ((!write_data->preg || !write_data->preg_replaced) && write_data->value) { + /* write the section header unless we're already in it */ + if (!current_section || strcmp(current_section, write_data->section)) + result = write_section(write_data->buf, write_data->orig_section); + + if (!result) + result = write_value(write_data); + } + + return result; +} + +/* + * This is pretty much the parsing, except we write out anything we don't have + */ +static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value) + +{ + char *orig_section = NULL, *section = NULL, *orig_name, *name, *ldot; + git_str buf = GIT_STR_INIT, contents = GIT_STR_INIT; + git_config_parser parser = GIT_CONFIG_PARSER_INIT; + git_filebuf file = GIT_FILEBUF_INIT; + struct write_data write_data; + int error; + + memset(&write_data, 0, sizeof(write_data)); + + if (cfg->locked) { + error = git_str_puts(&contents, git_str_cstr(&cfg->locked_content) == NULL ? "" : git_str_cstr(&cfg->locked_content)); + } else { + if ((error = git_filebuf_open(&file, cfg->file.path, GIT_FILEBUF_HASH_CONTENTS, + GIT_CONFIG_FILE_MODE)) < 0) + goto done; + + /* We need to read in our own config file */ + error = git_futils_readbuffer(&contents, cfg->file.path); + } + if (error < 0 && error != GIT_ENOTFOUND) + goto done; + + if ((git_config_parser_init(&parser, cfg->file.path, contents.ptr, contents.size)) < 0) + goto done; + + ldot = strrchr(key, '.'); + name = ldot + 1; + section = git__strndup(key, ldot - key); + GIT_ERROR_CHECK_ALLOC(section); + + ldot = strrchr(orig_key, '.'); + orig_name = ldot + 1; + orig_section = git__strndup(orig_key, ldot - orig_key); + GIT_ERROR_CHECK_ALLOC(orig_section); + + write_data.buf = &buf; + write_data.orig_section = orig_section; + write_data.section = section; + write_data.orig_name = orig_name; + write_data.name = name; + write_data.preg = preg; + write_data.value = value; + + if ((error = git_config_parse(&parser, write_on_section, write_on_variable, + write_on_comment, write_on_eof, &write_data)) < 0) + goto done; + + if (cfg->locked) { + size_t len = buf.asize; + /* Update our copy with the modified contents */ + git_str_dispose(&cfg->locked_content); + git_str_attach(&cfg->locked_content, git_str_detach(&buf), len); + } else { + git_filebuf_write(&file, git_str_cstr(&buf), git_str_len(&buf)); + + if ((error = git_filebuf_commit(&file)) < 0) + goto done; + + if ((error = config_file_refresh_from_buffer(&cfg->parent, buf.ptr, buf.size)) < 0) + goto done; + } + +done: + git__free(section); + git__free(orig_section); + git_str_dispose(&write_data.buffered_comment); + git_str_dispose(&buf); + git_str_dispose(&contents); + git_filebuf_cleanup(&file); + git_config_parser_dispose(&parser); + + return error; +} diff --git a/src/libgit2/config_mem.c b/src/libgit2/config_mem.c new file mode 100644 index 000000000..560229cf5 --- /dev/null +++ b/src/libgit2/config_mem.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "config.h" + +#include "config_backend.h" +#include "config_parse.h" +#include "config_entries.h" + +typedef struct { + git_config_backend parent; + git_config_entries *entries; + git_str cfg; +} config_memory_backend; + +typedef struct { + git_config_entries *entries; + git_config_level_t level; +} config_memory_parse_data; + +static int config_error_readonly(void) +{ + git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); + return -1; +} + +static int read_variable_cb( + git_config_parser *reader, + const char *current_section, + const char *var_name, + const char *var_value, + const char *line, + size_t line_len, + void *payload) +{ + config_memory_parse_data *parse_data = (config_memory_parse_data *) payload; + git_str buf = GIT_STR_INIT; + git_config_entry *entry; + const char *c; + int result; + + GIT_UNUSED(reader); + GIT_UNUSED(line); + GIT_UNUSED(line_len); + + if (current_section) { + /* TODO: Once warnings land, we should likely warn + * here. Git appears to warn in most cases if it sees + * un-namespaced config options. + */ + git_str_puts(&buf, current_section); + git_str_putc(&buf, '.'); + } + + for (c = var_name; *c; c++) + git_str_putc(&buf, git__tolower(*c)); + + if (git_str_oom(&buf)) + return -1; + + entry = git__calloc(1, sizeof(git_config_entry)); + GIT_ERROR_CHECK_ALLOC(entry); + entry->name = git_str_detach(&buf); + entry->value = var_value ? git__strdup(var_value) : NULL; + entry->level = parse_data->level; + entry->include_depth = 0; + + if ((result = git_config_entries_append(parse_data->entries, entry)) < 0) + return result; + + return result; +} + +static int config_memory_open(git_config_backend *backend, git_config_level_t level, const git_repository *repo) +{ + config_memory_backend *memory_backend = (config_memory_backend *) backend; + git_config_parser parser = GIT_PARSE_CTX_INIT; + config_memory_parse_data parse_data; + int error; + + GIT_UNUSED(repo); + + if ((error = git_config_parser_init(&parser, "in-memory", memory_backend->cfg.ptr, + memory_backend->cfg.size)) < 0) + goto out; + parse_data.entries = memory_backend->entries; + parse_data.level = level; + + if ((error = git_config_parse(&parser, NULL, read_variable_cb, NULL, NULL, &parse_data)) < 0) + goto out; + +out: + git_config_parser_dispose(&parser); + return error; +} + +static int config_memory_get(git_config_backend *backend, const char *key, git_config_entry **out) +{ + config_memory_backend *memory_backend = (config_memory_backend *) backend; + return git_config_entries_get(out, memory_backend->entries, key); +} + +static int config_memory_iterator( + git_config_iterator **iter, + git_config_backend *backend) +{ + config_memory_backend *memory_backend = (config_memory_backend *) backend; + git_config_entries *entries; + int error; + + if ((error = git_config_entries_dup(&entries, memory_backend->entries)) < 0) + goto out; + + if ((error = git_config_entries_iterator_new(iter, entries)) < 0) + goto out; + +out: + /* Let iterator delete duplicated entries when it's done */ + git_config_entries_free(entries); + return error; +} + +static int config_memory_set(git_config_backend *backend, const char *name, const char *value) +{ + GIT_UNUSED(backend); + GIT_UNUSED(name); + GIT_UNUSED(value); + return config_error_readonly(); +} + +static int config_memory_set_multivar( + git_config_backend *backend, const char *name, const char *regexp, const char *value) +{ + GIT_UNUSED(backend); + GIT_UNUSED(name); + GIT_UNUSED(regexp); + GIT_UNUSED(value); + return config_error_readonly(); +} + +static int config_memory_delete(git_config_backend *backend, const char *name) +{ + GIT_UNUSED(backend); + GIT_UNUSED(name); + return config_error_readonly(); +} + +static int config_memory_delete_multivar(git_config_backend *backend, const char *name, const char *regexp) +{ + GIT_UNUSED(backend); + GIT_UNUSED(name); + GIT_UNUSED(regexp); + return config_error_readonly(); +} + +static int config_memory_lock(git_config_backend *backend) +{ + GIT_UNUSED(backend); + return config_error_readonly(); +} + +static int config_memory_unlock(git_config_backend *backend, int success) +{ + GIT_UNUSED(backend); + GIT_UNUSED(success); + return config_error_readonly(); +} + +static void config_memory_free(git_config_backend *_backend) +{ + config_memory_backend *backend = (config_memory_backend *)_backend; + + if (backend == NULL) + return; + + git_config_entries_free(backend->entries); + git_str_dispose(&backend->cfg); + git__free(backend); +} + +int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len) +{ + config_memory_backend *backend; + + backend = git__calloc(1, sizeof(config_memory_backend)); + GIT_ERROR_CHECK_ALLOC(backend); + + if (git_config_entries_new(&backend->entries) < 0) { + git__free(backend); + return -1; + } + + if (git_str_set(&backend->cfg, cfg, len) < 0) { + git_config_entries_free(backend->entries); + git__free(backend); + return -1; + } + + backend->parent.version = GIT_CONFIG_BACKEND_VERSION; + backend->parent.readonly = 1; + backend->parent.open = config_memory_open; + backend->parent.get = config_memory_get; + backend->parent.set = config_memory_set; + backend->parent.set_multivar = config_memory_set_multivar; + backend->parent.del = config_memory_delete; + backend->parent.del_multivar = config_memory_delete_multivar; + backend->parent.iterator = config_memory_iterator; + backend->parent.lock = config_memory_lock; + backend->parent.unlock = config_memory_unlock; + backend->parent.snapshot = git_config_backend_snapshot; + backend->parent.free = config_memory_free; + + *out = (git_config_backend *)backend; + + return 0; +} diff --git a/src/libgit2/config_parse.c b/src/libgit2/config_parse.c new file mode 100644 index 000000000..06931368e --- /dev/null +++ b/src/libgit2/config_parse.c @@ -0,0 +1,580 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "config_parse.h" + +#include + +const char *git_config_escapes = "ntb\"\\"; +const char *git_config_escaped = "\n\t\b\"\\"; + +static void set_parse_error(git_config_parser *reader, int col, const char *error_str) +{ + if (col) + git_error_set(GIT_ERROR_CONFIG, + "failed to parse config file: %s (in %s:%"PRIuZ", column %d)", + error_str, reader->path, reader->ctx.line_num, col); + else + git_error_set(GIT_ERROR_CONFIG, + "failed to parse config file: %s (in %s:%"PRIuZ")", + error_str, reader->path, reader->ctx.line_num); +} + + +GIT_INLINE(int) config_keychar(int c) +{ + return isalnum(c) || c == '-'; +} + +static int strip_comments(char *line, int in_quotes) +{ + int quote_count = in_quotes, backslash_count = 0; + char *ptr; + + for (ptr = line; *ptr; ++ptr) { + if (ptr[0] == '"' && ((ptr > line && ptr[-1] != '\\') || ptr == line)) + quote_count++; + + if ((ptr[0] == ';' || ptr[0] == '#') && + (quote_count % 2) == 0 && + (backslash_count % 2) == 0) { + ptr[0] = '\0'; + break; + } + + if (ptr[0] == '\\') + backslash_count++; + else + backslash_count = 0; + } + + /* skip any space at the end */ + while (ptr > line && git__isspace(ptr[-1])) { + ptr--; + } + ptr[0] = '\0'; + + return quote_count; +} + + +static int parse_subsection_header(git_config_parser *reader, const char *line, size_t pos, const char *base_name, char **section_name) +{ + int c, rpos; + const char *first_quote, *last_quote; + const char *line_start = line; + git_str buf = GIT_STR_INIT; + size_t quoted_len, alloc_len, base_name_len = strlen(base_name); + + /* Skip any additional whitespace before our section name */ + while (git__isspace(line[pos])) + pos++; + + /* We should be at the first quotation mark. */ + if (line[pos] != '"') { + set_parse_error(reader, 0, "missing quotation marks in section header"); + goto end_error; + } + + first_quote = &line[pos]; + last_quote = strrchr(line, '"'); + quoted_len = last_quote - first_quote; + + if ((last_quote - line) > INT_MAX) { + set_parse_error(reader, 0, "invalid section header, line too long"); + goto end_error; + } + + if (quoted_len == 0) { + set_parse_error(reader, 0, "missing closing quotation mark in section header"); + goto end_error; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, base_name_len, quoted_len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); + + if (git_str_grow(&buf, alloc_len) < 0 || + git_str_printf(&buf, "%s.", base_name) < 0) + goto end_error; + + rpos = 0; + + line = first_quote; + c = line[++rpos]; + + /* + * At the end of each iteration, whatever is stored in c will be + * added to the string. In case of error, jump to out + */ + do { + + switch (c) { + case 0: + set_parse_error(reader, 0, "unexpected end-of-line in section header"); + goto end_error; + + case '"': + goto end_parse; + + case '\\': + c = line[++rpos]; + + if (c == 0) { + set_parse_error(reader, rpos, "unexpected end-of-line in section header"); + goto end_error; + } + + default: + break; + } + + git_str_putc(&buf, (char)c); + c = line[++rpos]; + } while (line + rpos < last_quote); + +end_parse: + if (git_str_oom(&buf)) + goto end_error; + + if (line[rpos] != '"' || line[rpos + 1] != ']') { + set_parse_error(reader, rpos, "unexpected text after closing quotes"); + git_str_dispose(&buf); + return -1; + } + + *section_name = git_str_detach(&buf); + return (int)(&line[rpos + 2] - line_start); /* rpos is at the closing quote */ + +end_error: + git_str_dispose(&buf); + + return -1; +} + +static int parse_section_header(git_config_parser *reader, char **section_out) +{ + char *name, *name_end; + int name_length, c, pos; + int result; + char *line; + size_t line_len; + + git_parse_advance_ws(&reader->ctx); + line = git__strndup(reader->ctx.line, reader->ctx.line_len); + if (line == NULL) + return -1; + + /* find the end of the variable's name */ + name_end = strrchr(line, ']'); + if (name_end == NULL) { + git__free(line); + set_parse_error(reader, 0, "missing ']' in section header"); + return -1; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&line_len, (size_t)(name_end - line), 1); + name = git__malloc(line_len); + GIT_ERROR_CHECK_ALLOC(name); + + name_length = 0; + pos = 0; + + /* Make sure we were given a section header */ + c = line[pos++]; + GIT_ASSERT(c == '['); + + c = line[pos++]; + + do { + if (git__isspace(c)){ + name[name_length] = '\0'; + result = parse_subsection_header(reader, line, pos, name, section_out); + git__free(line); + git__free(name); + return result; + } + + if (!config_keychar(c) && c != '.') { + set_parse_error(reader, pos, "unexpected character in header"); + goto fail_parse; + } + + name[name_length++] = (char)git__tolower(c); + + } while ((c = line[pos++]) != ']'); + + if (line[pos - 1] != ']') { + set_parse_error(reader, pos, "unexpected end of file"); + goto fail_parse; + } + + git__free(line); + + name[name_length] = 0; + *section_out = name; + + return pos; + +fail_parse: + git__free(line); + git__free(name); + return -1; +} + +static int skip_bom(git_parse_ctx *parser) +{ + git_str buf = GIT_STR_INIT_CONST(parser->content, parser->content_len); + git_str_bom_t bom; + int bom_offset = git_str_detect_bom(&bom, &buf); + + if (bom == GIT_STR_BOM_UTF8) + git_parse_advance_chars(parser, bom_offset); + + /* TODO: reference implementation is pretty stupid with BoM */ + + return 0; +} + +/* + (* basic types *) + digit = "0".."9" + integer = digit { digit } + alphabet = "a".."z" + "A" .. "Z" + + section_char = alphabet | "." | "-" + extension_char = (* any character except newline *) + any_char = (* any character *) + variable_char = "alphabet" | "-" + + + (* actual grammar *) + config = { section } + + section = header { definition } + + header = "[" section [subsection | subsection_ext] "]" + + subsection = "." section + subsection_ext = "\"" extension "\"" + + section = section_char { section_char } + extension = extension_char { extension_char } + + definition = variable_name ["=" variable_value] "\n" + + variable_name = variable_char { variable_char } + variable_value = string | boolean | integer + + string = quoted_string | plain_string + quoted_string = "\"" plain_string "\"" + plain_string = { any_char } + + boolean = boolean_true | boolean_false + boolean_true = "yes" | "1" | "true" | "on" + boolean_false = "no" | "0" | "false" | "off" +*/ + +/* '\"' -> '"' etc */ +static int unescape_line( + char **out, bool *is_multi, const char *ptr, int quote_count) +{ + char *str, *fixed, *esc; + size_t ptr_len = strlen(ptr), alloc_len; + + *is_multi = false; + + if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, ptr_len, 1) || + (str = git__malloc(alloc_len)) == NULL) { + return -1; + } + + fixed = str; + + while (*ptr != '\0') { + if (*ptr == '"') { + quote_count++; + } else if (*ptr != '\\') { + *fixed++ = *ptr; + } else { + /* backslash, check the next char */ + ptr++; + /* if we're at the end, it's a multiline, so keep the backslash */ + if (*ptr == '\0') { + *is_multi = true; + goto done; + } + if ((esc = strchr(git_config_escapes, *ptr)) != NULL) { + *fixed++ = git_config_escaped[esc - git_config_escapes]; + } else { + git__free(str); + git_error_set(GIT_ERROR_CONFIG, "invalid escape at %s", ptr); + return -1; + } + } + ptr++; + } + +done: + *fixed = '\0'; + *out = str; + + return 0; +} + +static int parse_multiline_variable(git_config_parser *reader, git_str *value, int in_quotes, size_t *line_len) +{ + int quote_count; + bool multiline = true; + + while (multiline) { + char *line = NULL, *proc_line = NULL; + int error; + + /* Check that the next line exists */ + git_parse_advance_line(&reader->ctx); + line = git__strndup(reader->ctx.line, reader->ctx.line_len); + GIT_ERROR_CHECK_ALLOC(line); + if (GIT_ADD_SIZET_OVERFLOW(line_len, *line_len, reader->ctx.line_len)) { + error = -1; + goto out; + } + + /* + * We've reached the end of the file, there is no continuation. + * (this is not an error). + */ + if (line[0] == '\0') { + error = 0; + goto out; + } + + /* If it was just a comment, pretend it didn't exist */ + quote_count = strip_comments(line, in_quotes); + if (line[0] == '\0') + goto next; + + if ((error = unescape_line(&proc_line, &multiline, + line, in_quotes)) < 0) + goto out; + + /* Add this line to the multiline var */ + if ((error = git_str_puts(value, proc_line)) < 0) + goto out; + +next: + git__free(line); + git__free(proc_line); + in_quotes = quote_count; + continue; + +out: + git__free(line); + git__free(proc_line); + return error; + } + + return 0; +} + +GIT_INLINE(bool) is_namechar(char c) +{ + return isalnum(c) || c == '-'; +} + +static int parse_name( + char **name, const char **value, git_config_parser *reader, const char *line) +{ + const char *name_end = line, *value_start; + + *name = NULL; + *value = NULL; + + while (*name_end && is_namechar(*name_end)) + name_end++; + + if (line == name_end) { + set_parse_error(reader, 0, "invalid configuration key"); + return -1; + } + + value_start = name_end; + + while (*value_start && git__isspace(*value_start)) + value_start++; + + if (*value_start == '=') { + *value = value_start + 1; + } else if (*value_start) { + set_parse_error(reader, 0, "invalid configuration key"); + return -1; + } + + if ((*name = git__strndup(line, name_end - line)) == NULL) + return -1; + + return 0; +} + +static int parse_variable(git_config_parser *reader, char **var_name, char **var_value, size_t *line_len) +{ + const char *value_start = NULL; + char *line = NULL, *name = NULL, *value = NULL; + int quote_count, error; + bool multiline; + + *var_name = NULL; + *var_value = NULL; + + git_parse_advance_ws(&reader->ctx); + line = git__strndup(reader->ctx.line, reader->ctx.line_len); + GIT_ERROR_CHECK_ALLOC(line); + + quote_count = strip_comments(line, 0); + + if ((error = parse_name(&name, &value_start, reader, line)) < 0) + goto out; + + /* + * Now, let's try to parse the value + */ + if (value_start != NULL) { + while (git__isspace(value_start[0])) + value_start++; + + if ((error = unescape_line(&value, &multiline, value_start, 0)) < 0) + goto out; + + if (multiline) { + git_str multi_value = GIT_STR_INIT; + git_str_attach(&multi_value, value, 0); + value = NULL; + + if (parse_multiline_variable(reader, &multi_value, quote_count % 2, line_len) < 0 || + git_str_oom(&multi_value)) { + error = -1; + git_str_dispose(&multi_value); + goto out; + } + + value = git_str_detach(&multi_value); + } + } + + *var_name = name; + *var_value = value; + name = NULL; + value = NULL; + +out: + git__free(name); + git__free(value); + git__free(line); + return error; +} + +int git_config_parser_init(git_config_parser *out, const char *path, const char *data, size_t datalen) +{ + out->path = path; + return git_parse_ctx_init(&out->ctx, data, datalen); +} + +void git_config_parser_dispose(git_config_parser *parser) +{ + git_parse_ctx_clear(&parser->ctx); +} + +int git_config_parse( + git_config_parser *parser, + git_config_parser_section_cb on_section, + git_config_parser_variable_cb on_variable, + git_config_parser_comment_cb on_comment, + git_config_parser_eof_cb on_eof, + void *payload) +{ + git_parse_ctx *ctx; + char *current_section = NULL, *var_name = NULL, *var_value = NULL; + int result = 0; + + ctx = &parser->ctx; + + skip_bom(ctx); + + for (; ctx->remain_len > 0; git_parse_advance_line(ctx)) { + const char *line_start; + size_t line_len; + char c; + + restart: + line_start = ctx->line; + line_len = ctx->line_len; + + /* + * Get either first non-whitespace character or, if that does + * not exist, the first whitespace character. This is required + * to preserve whitespaces when writing back the file. + */ + if (git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE) < 0 && + git_parse_peek(&c, ctx, 0) < 0) + continue; + + switch (c) { + case '[': /* section header, new section begins */ + git__free(current_section); + current_section = NULL; + + result = parse_section_header(parser, ¤t_section); + if (result < 0) + break; + + git_parse_advance_chars(ctx, result); + + if (on_section) + result = on_section(parser, current_section, line_start, line_len, payload); + /* + * After we've parsed the section header we may not be + * done with the line. If there's still data in there, + * run the next loop with the rest of the current line + * instead of moving forward. + */ + + if (!git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE)) + goto restart; + + break; + + case '\n': /* comment or whitespace-only */ + case '\r': + case ' ': + case '\t': + case ';': + case '#': + if (on_comment) { + result = on_comment(parser, line_start, line_len, payload); + } + break; + + default: /* assume variable declaration */ + if ((result = parse_variable(parser, &var_name, &var_value, &line_len)) == 0 && on_variable) { + result = on_variable(parser, current_section, var_name, var_value, line_start, line_len, payload); + git__free(var_name); + git__free(var_value); + } + + break; + } + + if (result < 0) + goto out; + } + + if (on_eof) + result = on_eof(parser, current_section, payload); + +out: + git__free(current_section); + return result; +} diff --git a/src/libgit2/config_parse.h b/src/libgit2/config_parse.h new file mode 100644 index 000000000..b791d3245 --- /dev/null +++ b/src/libgit2/config_parse.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_config_parse_h__ +#define INCLUDE_config_parse_h__ + +#include "common.h" + +#include "array.h" +#include "futils.h" +#include "oid.h" +#include "parse.h" + +extern const char *git_config_escapes; +extern const char *git_config_escaped; + +typedef struct { + const char *path; + git_parse_ctx ctx; +} git_config_parser; + +#define GIT_CONFIG_PARSER_INIT { NULL, GIT_PARSE_CTX_INIT } + +typedef int (*git_config_parser_section_cb)( + git_config_parser *parser, + const char *current_section, + const char *line, + size_t line_len, + void *payload); + +typedef int (*git_config_parser_variable_cb)( + git_config_parser *parser, + const char *current_section, + const char *var_name, + const char *var_value, + const char *line, + size_t line_len, + void *payload); + +typedef int (*git_config_parser_comment_cb)( + git_config_parser *parser, + const char *line, + size_t line_len, + void *payload); + +typedef int (*git_config_parser_eof_cb)( + git_config_parser *parser, + const char *current_section, + void *payload); + +int git_config_parser_init(git_config_parser *out, const char *path, const char *data, size_t datalen); +void git_config_parser_dispose(git_config_parser *parser); + +int git_config_parse( + git_config_parser *parser, + git_config_parser_section_cb on_section, + git_config_parser_variable_cb on_variable, + git_config_parser_comment_cb on_comment, + git_config_parser_eof_cb on_eof, + void *payload); + +#endif diff --git a/src/libgit2/config_snapshot.c b/src/libgit2/config_snapshot.c new file mode 100644 index 000000000..e295d2f7f --- /dev/null +++ b/src/libgit2/config_snapshot.c @@ -0,0 +1,207 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "config_backend.h" + +#include "config.h" +#include "config_entries.h" + +typedef struct { + git_config_backend parent; + git_mutex values_mutex; + git_config_entries *entries; + git_config_backend *source; +} config_snapshot_backend; + +static int config_error_readonly(void) +{ + git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); + return -1; +} + +static int config_snapshot_iterator( + git_config_iterator **iter, + struct git_config_backend *backend) +{ + config_snapshot_backend *b = GIT_CONTAINER_OF(backend, config_snapshot_backend, parent); + git_config_entries *entries = NULL; + int error; + + if ((error = git_config_entries_dup(&entries, b->entries)) < 0 || + (error = git_config_entries_iterator_new(iter, entries)) < 0) + goto out; + +out: + /* Let iterator delete duplicated entries when it's done */ + git_config_entries_free(entries); + return error; +} + +/* release the map containing the entry as an equivalent to freeing it */ +static void config_snapshot_entry_free(git_config_entry *entry) +{ + git_config_entries *entries = (git_config_entries *) entry->payload; + git_config_entries_free(entries); +} + +static int config_snapshot_get(git_config_backend *cfg, const char *key, git_config_entry **out) +{ + config_snapshot_backend *b = GIT_CONTAINER_OF(cfg, config_snapshot_backend, parent); + git_config_entries *entries = NULL; + git_config_entry *entry; + int error = 0; + + if (git_mutex_lock(&b->values_mutex) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock config backend"); + return -1; + } + + entries = b->entries; + git_config_entries_incref(entries); + git_mutex_unlock(&b->values_mutex); + + if ((error = (git_config_entries_get(&entry, entries, key))) < 0) { + git_config_entries_free(entries); + return error; + } + + entry->free = config_snapshot_entry_free; + entry->payload = entries; + *out = entry; + + return 0; +} + +static int config_snapshot_set(git_config_backend *cfg, const char *name, const char *value) +{ + GIT_UNUSED(cfg); + GIT_UNUSED(name); + GIT_UNUSED(value); + + return config_error_readonly(); +} + +static int config_snapshot_set_multivar( + git_config_backend *cfg, const char *name, const char *regexp, const char *value) +{ + GIT_UNUSED(cfg); + GIT_UNUSED(name); + GIT_UNUSED(regexp); + GIT_UNUSED(value); + + return config_error_readonly(); +} + +static int config_snapshot_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) +{ + GIT_UNUSED(cfg); + GIT_UNUSED(name); + GIT_UNUSED(regexp); + + return config_error_readonly(); +} + +static int config_snapshot_delete(git_config_backend *cfg, const char *name) +{ + GIT_UNUSED(cfg); + GIT_UNUSED(name); + + return config_error_readonly(); +} + +static int config_snapshot_lock(git_config_backend *_cfg) +{ + GIT_UNUSED(_cfg); + + return config_error_readonly(); +} + +static int config_snapshot_unlock(git_config_backend *_cfg, int success) +{ + GIT_UNUSED(_cfg); + GIT_UNUSED(success); + + return config_error_readonly(); +} + +static void config_snapshot_free(git_config_backend *_backend) +{ + config_snapshot_backend *backend = GIT_CONTAINER_OF(_backend, config_snapshot_backend, parent); + + if (backend == NULL) + return; + + git_config_entries_free(backend->entries); + git_mutex_free(&backend->values_mutex); + git__free(backend); +} + +static int config_snapshot_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo) +{ + config_snapshot_backend *b = GIT_CONTAINER_OF(cfg, config_snapshot_backend, parent); + git_config_entries *entries = NULL; + git_config_iterator *it = NULL; + git_config_entry *entry; + int error; + + /* We're just copying data, don't care about the level or repo*/ + GIT_UNUSED(level); + GIT_UNUSED(repo); + + if ((error = git_config_entries_new(&entries)) < 0 || + (error = b->source->iterator(&it, b->source)) < 0) + goto out; + + while ((error = git_config_next(&entry, it)) == 0) + if ((error = git_config_entries_dup_entry(entries, entry)) < 0) + goto out; + + if (error < 0) { + if (error != GIT_ITEROVER) + goto out; + error = 0; + } + + b->entries = entries; + +out: + git_config_iterator_free(it); + if (error) + git_config_entries_free(entries); + return error; +} + +int git_config_backend_snapshot(git_config_backend **out, git_config_backend *source) +{ + config_snapshot_backend *backend; + + backend = git__calloc(1, sizeof(config_snapshot_backend)); + GIT_ERROR_CHECK_ALLOC(backend); + + backend->parent.version = GIT_CONFIG_BACKEND_VERSION; + git_mutex_init(&backend->values_mutex); + + backend->source = source; + + backend->parent.readonly = 1; + backend->parent.version = GIT_CONFIG_BACKEND_VERSION; + backend->parent.open = config_snapshot_open; + backend->parent.get = config_snapshot_get; + backend->parent.set = config_snapshot_set; + backend->parent.set_multivar = config_snapshot_set_multivar; + backend->parent.snapshot = git_config_backend_snapshot; + backend->parent.del = config_snapshot_delete; + backend->parent.del_multivar = config_snapshot_delete_multivar; + backend->parent.iterator = config_snapshot_iterator; + backend->parent.lock = config_snapshot_lock; + backend->parent.unlock = config_snapshot_unlock; + backend->parent.free = config_snapshot_free; + + *out = &backend->parent; + + return 0; +} diff --git a/src/libgit2/crlf.c b/src/libgit2/crlf.c new file mode 100644 index 000000000..1e1f1e845 --- /dev/null +++ b/src/libgit2/crlf.c @@ -0,0 +1,426 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "git2/attr.h" +#include "git2/blob.h" +#include "git2/index.h" +#include "git2/sys/filter.h" + +#include "buf.h" +#include "futils.h" +#include "hash.h" +#include "filter.h" +#include "repository.h" + +typedef enum { + GIT_CRLF_UNDEFINED, + GIT_CRLF_BINARY, + GIT_CRLF_TEXT, + GIT_CRLF_TEXT_INPUT, + GIT_CRLF_TEXT_CRLF, + GIT_CRLF_AUTO, + GIT_CRLF_AUTO_INPUT, + GIT_CRLF_AUTO_CRLF +} git_crlf_t; + +struct crlf_attrs { + int attr_action; /* the .gitattributes setting */ + int crlf_action; /* the core.autocrlf setting */ + + int auto_crlf; + int safe_crlf; + int core_eol; +}; + +struct crlf_filter { + git_filter f; +}; + +static git_crlf_t check_crlf(const char *value) +{ + if (GIT_ATTR_IS_TRUE(value)) + return GIT_CRLF_TEXT; + else if (GIT_ATTR_IS_FALSE(value)) + return GIT_CRLF_BINARY; + else if (GIT_ATTR_IS_UNSPECIFIED(value)) + ; + else if (strcmp(value, "input") == 0) + return GIT_CRLF_TEXT_INPUT; + else if (strcmp(value, "auto") == 0) + return GIT_CRLF_AUTO; + + return GIT_CRLF_UNDEFINED; +} + +static git_configmap_value check_eol(const char *value) +{ + if (GIT_ATTR_IS_UNSPECIFIED(value)) + ; + else if (strcmp(value, "lf") == 0) + return GIT_EOL_LF; + else if (strcmp(value, "crlf") == 0) + return GIT_EOL_CRLF; + + return GIT_EOL_UNSET; +} + +static int has_cr_in_index(const git_filter_source *src) +{ + git_repository *repo = git_filter_source_repo(src); + const char *path = git_filter_source_path(src); + git_index *index; + const git_index_entry *entry; + git_blob *blob; + const void *blobcontent; + git_object_size_t blobsize; + bool found_cr; + + if (!path) + return false; + + if (git_repository_index__weakptr(&index, repo) < 0) { + git_error_clear(); + return false; + } + + if (!(entry = git_index_get_bypath(index, path, 0)) && + !(entry = git_index_get_bypath(index, path, 1))) + return false; + + if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */ + return true; + + if (git_blob_lookup(&blob, repo, &entry->id) < 0) + return false; + + blobcontent = git_blob_rawcontent(blob); + blobsize = git_blob_rawsize(blob); + if (!git__is_sizet(blobsize)) + blobsize = (size_t)-1; + + found_cr = (blobcontent != NULL && + blobsize > 0 && + memchr(blobcontent, '\r', (size_t)blobsize) != NULL); + + git_blob_free(blob); + return found_cr; +} + +static int text_eol_is_crlf(struct crlf_attrs *ca) +{ + if (ca->auto_crlf == GIT_AUTO_CRLF_TRUE) + return 1; + else if (ca->auto_crlf == GIT_AUTO_CRLF_INPUT) + return 0; + + if (ca->core_eol == GIT_EOL_CRLF) + return 1; + if (ca->core_eol == GIT_EOL_UNSET && GIT_EOL_NATIVE == GIT_EOL_CRLF) + return 1; + + return 0; +} + +static git_configmap_value output_eol(struct crlf_attrs *ca) +{ + switch (ca->crlf_action) { + case GIT_CRLF_BINARY: + return GIT_EOL_UNSET; + case GIT_CRLF_TEXT_CRLF: + return GIT_EOL_CRLF; + case GIT_CRLF_TEXT_INPUT: + return GIT_EOL_LF; + case GIT_CRLF_UNDEFINED: + case GIT_CRLF_AUTO_CRLF: + return GIT_EOL_CRLF; + case GIT_CRLF_AUTO_INPUT: + return GIT_EOL_LF; + case GIT_CRLF_TEXT: + case GIT_CRLF_AUTO: + return text_eol_is_crlf(ca) ? GIT_EOL_CRLF : GIT_EOL_LF; + } + + /* TODO: warn when available */ + return ca->core_eol; +} + +GIT_INLINE(int) check_safecrlf( + struct crlf_attrs *ca, + const git_filter_source *src, + git_str_text_stats *stats) +{ + const char *filename = git_filter_source_path(src); + + if (!ca->safe_crlf) + return 0; + + if (output_eol(ca) == GIT_EOL_LF) { + /* + * CRLFs would not be restored by checkout: + * check if we'd remove CRLFs + */ + if (stats->crlf) { + if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) { + /* TODO: issue a warning when available */ + } else { + if (filename && *filename) + git_error_set( + GIT_ERROR_FILTER, "CRLF would be replaced by LF in '%s'", + filename); + else + git_error_set( + GIT_ERROR_FILTER, "CRLF would be replaced by LF"); + + return -1; + } + } + } else if (output_eol(ca) == GIT_EOL_CRLF) { + /* + * CRLFs would be added by checkout: + * check if we have "naked" LFs + */ + if (stats->crlf != stats->lf) { + if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) { + /* TODO: issue a warning when available */ + } else { + if (filename && *filename) + git_error_set( + GIT_ERROR_FILTER, "LF would be replaced by CRLF in '%s'", + filename); + else + git_error_set( + GIT_ERROR_FILTER, "LF would be replaced by CRLF"); + + return -1; + } + } + } + + return 0; +} + +static int crlf_apply_to_odb( + struct crlf_attrs *ca, + git_str *to, + const git_str *from, + const git_filter_source *src) +{ + git_str_text_stats stats; + bool is_binary; + int error; + + /* Binary attribute? Empty file? Nothing to do */ + if (ca->crlf_action == GIT_CRLF_BINARY || from->size == 0) + return GIT_PASSTHROUGH; + + is_binary = git_str_gather_text_stats(&stats, from, false); + + /* Heuristics to see if we can skip the conversion. + * Straight from Core Git. + */ + if (ca->crlf_action == GIT_CRLF_AUTO || + ca->crlf_action == GIT_CRLF_AUTO_INPUT || + ca->crlf_action == GIT_CRLF_AUTO_CRLF) { + + if (is_binary) + return GIT_PASSTHROUGH; + + /* + * If the file in the index has any CR in it, do not convert. + * This is the new safer autocrlf handling. + */ + if (has_cr_in_index(src)) + return GIT_PASSTHROUGH; + } + + if ((error = check_safecrlf(ca, src, &stats)) < 0) + return error; + + /* If there are no CR characters to filter out, then just pass */ + if (!stats.crlf) + return GIT_PASSTHROUGH; + + /* Actually drop the carriage returns */ + return git_str_crlf_to_lf(to, from); +} + +static int crlf_apply_to_workdir( + struct crlf_attrs *ca, + git_str *to, + const git_str *from) +{ + git_str_text_stats stats; + bool is_binary; + + /* Empty file? Nothing to do. */ + if (git_str_len(from) == 0 || output_eol(ca) != GIT_EOL_CRLF) + return GIT_PASSTHROUGH; + + is_binary = git_str_gather_text_stats(&stats, from, false); + + /* If there are no LFs, or all LFs are part of a CRLF, nothing to do */ + if (stats.lf == 0 || stats.lf == stats.crlf) + return GIT_PASSTHROUGH; + + if (ca->crlf_action == GIT_CRLF_AUTO || + ca->crlf_action == GIT_CRLF_AUTO_INPUT || + ca->crlf_action == GIT_CRLF_AUTO_CRLF) { + + /* If we have any existing CR or CRLF line endings, do nothing */ + if (stats.cr > 0) + return GIT_PASSTHROUGH; + + /* Don't filter binary files */ + if (is_binary) + return GIT_PASSTHROUGH; + } + + return git_str_lf_to_crlf(to, from); +} + +static int convert_attrs( + struct crlf_attrs *ca, + const char **attr_values, + const git_filter_source *src) +{ + int error; + + memset(ca, 0, sizeof(struct crlf_attrs)); + + if ((error = git_repository__configmap_lookup(&ca->auto_crlf, + git_filter_source_repo(src), GIT_CONFIGMAP_AUTO_CRLF)) < 0 || + (error = git_repository__configmap_lookup(&ca->safe_crlf, + git_filter_source_repo(src), GIT_CONFIGMAP_SAFE_CRLF)) < 0 || + (error = git_repository__configmap_lookup(&ca->core_eol, + git_filter_source_repo(src), GIT_CONFIGMAP_EOL)) < 0) + return error; + + /* downgrade FAIL to WARN if ALLOW_UNSAFE option is used */ + if ((git_filter_source_flags(src) & GIT_FILTER_ALLOW_UNSAFE) && + ca->safe_crlf == GIT_SAFE_CRLF_FAIL) + ca->safe_crlf = GIT_SAFE_CRLF_WARN; + + if (attr_values) { + /* load the text attribute */ + ca->crlf_action = check_crlf(attr_values[2]); /* text */ + + if (ca->crlf_action == GIT_CRLF_UNDEFINED) + ca->crlf_action = check_crlf(attr_values[0]); /* crlf */ + + if (ca->crlf_action != GIT_CRLF_BINARY) { + /* load the eol attribute */ + int eol_attr = check_eol(attr_values[1]); + + if (ca->crlf_action == GIT_CRLF_AUTO && eol_attr == GIT_EOL_LF) + ca->crlf_action = GIT_CRLF_AUTO_INPUT; + else if (ca->crlf_action == GIT_CRLF_AUTO && eol_attr == GIT_EOL_CRLF) + ca->crlf_action = GIT_CRLF_AUTO_CRLF; + else if (eol_attr == GIT_EOL_LF) + ca->crlf_action = GIT_CRLF_TEXT_INPUT; + else if (eol_attr == GIT_EOL_CRLF) + ca->crlf_action = GIT_CRLF_TEXT_CRLF; + } + + ca->attr_action = ca->crlf_action; + } else { + ca->crlf_action = GIT_CRLF_UNDEFINED; + } + + if (ca->crlf_action == GIT_CRLF_TEXT) + ca->crlf_action = text_eol_is_crlf(ca) ? GIT_CRLF_TEXT_CRLF : GIT_CRLF_TEXT_INPUT; + if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_FALSE) + ca->crlf_action = GIT_CRLF_BINARY; + if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_TRUE) + ca->crlf_action = GIT_CRLF_AUTO_CRLF; + if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_INPUT) + ca->crlf_action = GIT_CRLF_AUTO_INPUT; + + return 0; +} + +static int crlf_check( + git_filter *self, + void **payload, /* points to NULL ptr on entry, may be set */ + const git_filter_source *src, + const char **attr_values) +{ + struct crlf_attrs ca; + + GIT_UNUSED(self); + + convert_attrs(&ca, attr_values, src); + + if (ca.crlf_action == GIT_CRLF_BINARY) + return GIT_PASSTHROUGH; + + *payload = git__malloc(sizeof(ca)); + GIT_ERROR_CHECK_ALLOC(*payload); + memcpy(*payload, &ca, sizeof(ca)); + + return 0; +} + +static int crlf_apply( + git_filter *self, + void **payload, /* may be read and/or set */ + git_str *to, + const git_str *from, + const git_filter_source *src) +{ + int error = 0; + + /* initialize payload in case `check` was bypassed */ + if (!*payload) { + if ((error = crlf_check(self, payload, src, NULL)) < 0) + return error; + } + + if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) + error = crlf_apply_to_workdir(*payload, to, from); + else + error = crlf_apply_to_odb(*payload, to, from, src); + + return error; +} + +static int crlf_stream( + git_writestream **out, + git_filter *self, + void **payload, + const git_filter_source *src, + git_writestream *next) +{ + return git_filter_buffered_stream_new(out, + self, crlf_apply, NULL, payload, src, next); +} + +static void crlf_cleanup( + git_filter *self, + void *payload) +{ + GIT_UNUSED(self); + git__free(payload); +} + +git_filter *git_crlf_filter_new(void) +{ + struct crlf_filter *f = git__calloc(1, sizeof(struct crlf_filter)); + if (f == NULL) + return NULL; + + f->f.version = GIT_FILTER_VERSION; + f->f.attributes = "crlf eol text"; + f->f.initialize = NULL; + f->f.shutdown = git_filter_free; + f->f.check = crlf_check; + f->f.stream = crlf_stream; + f->f.cleanup = crlf_cleanup; + + return (git_filter *)f; +} diff --git a/src/libgit2/date.c b/src/libgit2/date.c new file mode 100644 index 000000000..0e5ffc96b --- /dev/null +++ b/src/libgit2/date.c @@ -0,0 +1,898 @@ +/* + * GIT - The information manager from hell + * + * Copyright (C) Linus Torvalds, 2005 + */ + +#include "common.h" + +#ifndef GIT_WIN32 +#include +#endif + +#include "util.h" +#include "posix.h" +#include "date.h" + +#include +#include + +typedef enum { + DATE_NORMAL = 0, + DATE_RELATIVE, + DATE_SHORT, + DATE_LOCAL, + DATE_ISO8601, + DATE_RFC2822, + DATE_RAW +} date_mode; + +/* + * This is like mktime, but without normalization of tm_wday and tm_yday. + */ +static git_time_t tm_to_time_t(const struct tm *tm) +{ + static const int mdays[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 + }; + int year = tm->tm_year - 70; + int month = tm->tm_mon; + int day = tm->tm_mday; + + if (year < 0 || year > 129) /* algo only works for 1970-2099 */ + return -1; + if (month < 0 || month > 11) /* array bounds */ + return -1; + if (month < 2 || (year + 2) % 4) + day--; + if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0) + return -1; + return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL + + tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec; +} + +static const char *month_names[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" +}; + +static const char *weekday_names[] = { + "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays" +}; + + + +/* + * Check these. And note how it doesn't do the summer-time conversion. + * + * In my world, it's always summer, and things are probably a bit off + * in other ways too. + */ +static const struct { + const char *name; + int offset; + int dst; +} timezone_names[] = { + { "IDLW", -12, 0, }, /* International Date Line West */ + { "NT", -11, 0, }, /* Nome */ + { "CAT", -10, 0, }, /* Central Alaska */ + { "HST", -10, 0, }, /* Hawaii Standard */ + { "HDT", -10, 1, }, /* Hawaii Daylight */ + { "YST", -9, 0, }, /* Yukon Standard */ + { "YDT", -9, 1, }, /* Yukon Daylight */ + { "PST", -8, 0, }, /* Pacific Standard */ + { "PDT", -8, 1, }, /* Pacific Daylight */ + { "MST", -7, 0, }, /* Mountain Standard */ + { "MDT", -7, 1, }, /* Mountain Daylight */ + { "CST", -6, 0, }, /* Central Standard */ + { "CDT", -6, 1, }, /* Central Daylight */ + { "EST", -5, 0, }, /* Eastern Standard */ + { "EDT", -5, 1, }, /* Eastern Daylight */ + { "AST", -3, 0, }, /* Atlantic Standard */ + { "ADT", -3, 1, }, /* Atlantic Daylight */ + { "WAT", -1, 0, }, /* West Africa */ + + { "GMT", 0, 0, }, /* Greenwich Mean */ + { "UTC", 0, 0, }, /* Universal (Coordinated) */ + { "Z", 0, 0, }, /* Zulu, alias for UTC */ + + { "WET", 0, 0, }, /* Western European */ + { "BST", 0, 1, }, /* British Summer */ + { "CET", +1, 0, }, /* Central European */ + { "MET", +1, 0, }, /* Middle European */ + { "MEWT", +1, 0, }, /* Middle European Winter */ + { "MEST", +1, 1, }, /* Middle European Summer */ + { "CEST", +1, 1, }, /* Central European Summer */ + { "MESZ", +1, 1, }, /* Middle European Summer */ + { "FWT", +1, 0, }, /* French Winter */ + { "FST", +1, 1, }, /* French Summer */ + { "EET", +2, 0, }, /* Eastern Europe */ + { "EEST", +2, 1, }, /* Eastern European Daylight */ + { "WAST", +7, 0, }, /* West Australian Standard */ + { "WADT", +7, 1, }, /* West Australian Daylight */ + { "CCT", +8, 0, }, /* China Coast */ + { "JST", +9, 0, }, /* Japan Standard */ + { "EAST", +10, 0, }, /* Eastern Australian Standard */ + { "EADT", +10, 1, }, /* Eastern Australian Daylight */ + { "GST", +10, 0, }, /* Guam Standard */ + { "NZT", +12, 0, }, /* New Zealand */ + { "NZST", +12, 0, }, /* New Zealand Standard */ + { "NZDT", +12, 1, }, /* New Zealand Daylight */ + { "IDLE", +12, 0, }, /* International Date Line East */ +}; + +static size_t match_string(const char *date, const char *str) +{ + size_t i = 0; + + for (i = 0; *date; date++, str++, i++) { + if (*date == *str) + continue; + if (toupper(*date) == toupper(*str)) + continue; + if (!isalnum(*date)) + break; + return 0; + } + return i; +} + +static int skip_alpha(const char *date) +{ + int i = 0; + do { + i++; + } while (isalpha(date[i])); + return i; +} + +/* +* Parse month, weekday, or timezone name +*/ +static size_t match_alpha(const char *date, struct tm *tm, int *offset) +{ + unsigned int i; + + for (i = 0; i < 12; i++) { + size_t match = match_string(date, month_names[i]); + if (match >= 3) { + tm->tm_mon = i; + return match; + } + } + + for (i = 0; i < 7; i++) { + size_t match = match_string(date, weekday_names[i]); + if (match >= 3) { + tm->tm_wday = i; + return match; + } + } + + for (i = 0; i < ARRAY_SIZE(timezone_names); i++) { + size_t match = match_string(date, timezone_names[i].name); + if (match >= 3 || match == strlen(timezone_names[i].name)) { + int off = timezone_names[i].offset; + + /* This is bogus, but we like summer */ + off += timezone_names[i].dst; + + /* Only use the tz name offset if we don't have anything better */ + if (*offset == -1) + *offset = 60*off; + + return match; + } + } + + if (match_string(date, "PM") == 2) { + tm->tm_hour = (tm->tm_hour % 12) + 12; + return 2; + } + + if (match_string(date, "AM") == 2) { + tm->tm_hour = (tm->tm_hour % 12) + 0; + return 2; + } + + /* BAD */ + return skip_alpha(date); +} + +static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm) +{ + if (month > 0 && month < 13 && day > 0 && day < 32) { + struct tm check = *tm; + struct tm *r = (now_tm ? &check : tm); + git_time_t specified; + + r->tm_mon = month - 1; + r->tm_mday = day; + if (year == -1) { + if (!now_tm) + return 1; + r->tm_year = now_tm->tm_year; + } + else if (year >= 1970 && year < 2100) + r->tm_year = year - 1900; + else if (year > 70 && year < 100) + r->tm_year = year; + else if (year < 38) + r->tm_year = year + 100; + else + return 0; + if (!now_tm) + return 1; + + specified = tm_to_time_t(r); + + /* Be it commit time or author time, it does not make + * sense to specify timestamp way into the future. Make + * sure it is not later than ten days from now... + */ + if (now + 10*24*3600 < specified) + return 0; + tm->tm_mon = r->tm_mon; + tm->tm_mday = r->tm_mday; + if (year != -1) + tm->tm_year = r->tm_year; + return 1; + } + return 0; +} + +static size_t match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm) +{ + time_t now; + struct tm now_tm; + struct tm *refuse_future; + long num2, num3; + + num2 = strtol(end+1, &end, 10); + num3 = -1; + if (*end == c && isdigit(end[1])) + num3 = strtol(end+1, &end, 10); + + /* Time? Date? */ + switch (c) { + case ':': + if (num3 < 0) + num3 = 0; + if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) { + tm->tm_hour = num; + tm->tm_min = num2; + tm->tm_sec = num3; + break; + } + return 0; + + case '-': + case '/': + case '.': + now = time(NULL); + refuse_future = NULL; + if (p_gmtime_r(&now, &now_tm)) + refuse_future = &now_tm; + + if (num > 70) { + /* yyyy-mm-dd? */ + if (is_date(num, num2, num3, refuse_future, now, tm)) + break; + /* yyyy-dd-mm? */ + if (is_date(num, num3, num2, refuse_future, now, tm)) + break; + } + /* Our eastern European friends say dd.mm.yy[yy] + * is the norm there, so giving precedence to + * mm/dd/yy[yy] form only when separator is not '.' + */ + if (c != '.' && + is_date(num3, num, num2, refuse_future, now, tm)) + break; + /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */ + if (is_date(num3, num2, num, refuse_future, now, tm)) + break; + /* Funny European mm.dd.yy */ + if (c == '.' && + is_date(num3, num, num2, refuse_future, now, tm)) + break; + return 0; + } + return end - date; +} + +/* + * Have we filled in any part of the time/date yet? + * We just do a binary 'and' to see if the sign bit + * is set in all the values. + */ +static int nodate(struct tm *tm) +{ + return (tm->tm_year & + tm->tm_mon & + tm->tm_mday & + tm->tm_hour & + tm->tm_min & + tm->tm_sec) < 0; +} + +/* + * We've seen a digit. Time? Year? Date? + */ +static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt) +{ + size_t n; + char *end; + unsigned long num; + + num = strtoul(date, &end, 10); + + /* + * Seconds since 1970? We trigger on that for any numbers with + * more than 8 digits. This is because we don't want to rule out + * numbers like 20070606 as a YYYYMMDD date. + */ + if (num >= 100000000 && nodate(tm)) { + time_t time = num; + if (p_gmtime_r(&time, tm)) { + *tm_gmt = 1; + return end - date; + } + } + + /* + * Check for special formats: num[-.:/]num[same]num + */ + switch (*end) { + case ':': + case '.': + case '/': + case '-': + if (isdigit(end[1])) { + size_t match = match_multi_number(num, *end, date, end, tm); + if (match) + return match; + } + } + + /* + * None of the special formats? Try to guess what + * the number meant. We use the number of digits + * to make a more educated guess.. + */ + n = 0; + do { + n++; + } while (isdigit(date[n])); + + /* Four-digit year or a timezone? */ + if (n == 4) { + if (num <= 1400 && *offset == -1) { + unsigned int minutes = num % 100; + unsigned int hours = num / 100; + *offset = hours*60 + minutes; + } else if (num > 1900 && num < 2100) + tm->tm_year = num - 1900; + return n; + } + + /* + * Ignore lots of numerals. We took care of 4-digit years above. + * Days or months must be one or two digits. + */ + if (n > 2) + return n; + + /* + * NOTE! We will give precedence to day-of-month over month or + * year numbers in the 1-12 range. So 05 is always "mday 5", + * unless we already have a mday.. + * + * IOW, 01 Apr 05 parses as "April 1st, 2005". + */ + if (num > 0 && num < 32 && tm->tm_mday < 0) { + tm->tm_mday = num; + return n; + } + + /* Two-digit year? */ + if (n == 2 && tm->tm_year < 0) { + if (num < 10 && tm->tm_mday >= 0) { + tm->tm_year = num + 100; + return n; + } + if (num >= 70) { + tm->tm_year = num; + return n; + } + } + + if (num > 0 && num < 13 && tm->tm_mon < 0) + tm->tm_mon = num-1; + + return n; +} + +static size_t match_tz(const char *date, int *offp) +{ + char *end; + int hour = strtoul(date + 1, &end, 10); + size_t n = end - (date + 1); + int min = 0; + + if (n == 4) { + /* hhmm */ + min = hour % 100; + hour = hour / 100; + } else if (n != 2) { + min = 99; /* random stuff */ + } else if (*end == ':') { + /* hh:mm? */ + min = strtoul(end + 1, &end, 10); + if (end - (date + 1) != 5) + min = 99; /* random stuff */ + } /* otherwise we parsed "hh" */ + + /* + * Don't accept any random stuff. Even though some places have + * offset larger than 12 hours (e.g. Pacific/Kiritimati is at + * UTC+14), there is something wrong if hour part is much + * larger than that. We might also want to check that the + * minutes are divisible by 15 or something too. (Offset of + * Kathmandu, Nepal is UTC+5:45) + */ + if (min < 60 && hour < 24) { + int offset = hour * 60 + min; + if (*date == '-') + offset = -offset; + *offp = offset; + } + return end - date; +} + +/* + * Parse a string like "0 +0000" as ancient timestamp near epoch, but + * only when it appears not as part of any other string. + */ +static int match_object_header_date(const char *date, git_time_t *timestamp, int *offset) +{ + char *end; + unsigned long stamp; + int ofs; + + if (*date < '0' || '9' <= *date) + return -1; + stamp = strtoul(date, &end, 10); + if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-')) + return -1; + date = end + 2; + ofs = strtol(date, &end, 10); + if ((*end != '\0' && (*end != '\n')) || end != date + 4) + return -1; + ofs = (ofs / 100) * 60 + (ofs % 100); + if (date[-1] == '-') + ofs = -ofs; + *timestamp = stamp; + *offset = ofs; + return 0; +} + +/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822 + (i.e. English) day/month names, and it doesn't work correctly with %z. */ +static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset) +{ + struct tm tm; + int tm_gmt; + git_time_t dummy_timestamp; + int dummy_offset; + + if (!timestamp) + timestamp = &dummy_timestamp; + if (!offset) + offset = &dummy_offset; + + memset(&tm, 0, sizeof(tm)); + tm.tm_year = -1; + tm.tm_mon = -1; + tm.tm_mday = -1; + tm.tm_isdst = -1; + tm.tm_hour = -1; + tm.tm_min = -1; + tm.tm_sec = -1; + *offset = -1; + tm_gmt = 0; + + if (*date == '@' && + !match_object_header_date(date + 1, timestamp, offset)) + return 0; /* success */ + for (;;) { + size_t match = 0; + unsigned char c = *date; + + /* Stop at end of string or newline */ + if (!c || c == '\n') + break; + + if (isalpha(c)) + match = match_alpha(date, &tm, offset); + else if (isdigit(c)) + match = match_digit(date, &tm, offset, &tm_gmt); + else if ((c == '-' || c == '+') && isdigit(date[1])) + match = match_tz(date, offset); + + if (!match) { + /* BAD */ + match = 1; + } + + date += match; + } + + /* mktime uses local timezone */ + *timestamp = tm_to_time_t(&tm); + if (*offset == -1) + *offset = (int)((time_t)*timestamp - mktime(&tm)) / 60; + + if (*timestamp == (git_time_t)-1) + return -1; + + if (!tm_gmt) + *timestamp -= *offset * 60; + return 0; /* success */ +} + + +/* + * Relative time update (eg "2 days ago"). If we haven't set the time + * yet, we need to set it from current time. + */ +static git_time_t update_tm(struct tm *tm, struct tm *now, unsigned long sec) +{ + time_t n; + + if (tm->tm_mday < 0) + tm->tm_mday = now->tm_mday; + if (tm->tm_mon < 0) + tm->tm_mon = now->tm_mon; + if (tm->tm_year < 0) { + tm->tm_year = now->tm_year; + if (tm->tm_mon > now->tm_mon) + tm->tm_year--; + } + + n = mktime(tm) - sec; + p_localtime_r(&n, tm); + return n; +} + +static void date_now(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + update_tm(tm, now, 0); +} + +static void date_yesterday(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + update_tm(tm, now, 24*60*60); +} + +static void date_time(struct tm *tm, struct tm *now, int hour) +{ + if (tm->tm_hour < hour) + date_yesterday(tm, now, NULL); + tm->tm_hour = hour; + tm->tm_min = 0; + tm->tm_sec = 0; +} + +static void date_midnight(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + date_time(tm, now, 0); +} + +static void date_noon(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + date_time(tm, now, 12); +} + +static void date_tea(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + date_time(tm, now, 17); +} + +static void date_pm(struct tm *tm, struct tm *now, int *num) +{ + int hour, n = *num; + *num = 0; + GIT_UNUSED(now); + + hour = tm->tm_hour; + if (n) { + hour = n; + tm->tm_min = 0; + tm->tm_sec = 0; + } + tm->tm_hour = (hour % 12) + 12; +} + +static void date_am(struct tm *tm, struct tm *now, int *num) +{ + int hour, n = *num; + *num = 0; + GIT_UNUSED(now); + + hour = tm->tm_hour; + if (n) { + hour = n; + tm->tm_min = 0; + tm->tm_sec = 0; + } + tm->tm_hour = (hour % 12); +} + +static void date_never(struct tm *tm, struct tm *now, int *num) +{ + time_t n = 0; + GIT_UNUSED(now); + GIT_UNUSED(num); + p_localtime_r(&n, tm); +} + +static const struct special { + const char *name; + void (*fn)(struct tm *, struct tm *, int *); +} special[] = { + { "yesterday", date_yesterday }, + { "noon", date_noon }, + { "midnight", date_midnight }, + { "tea", date_tea }, + { "PM", date_pm }, + { "AM", date_am }, + { "never", date_never }, + { "now", date_now }, + { NULL } +}; + +static const char *number_name[] = { + "zero", "one", "two", "three", "four", + "five", "six", "seven", "eight", "nine", "ten", +}; + +static const struct typelen { + const char *type; + int length; +} typelen[] = { + { "seconds", 1 }, + { "minutes", 60 }, + { "hours", 60*60 }, + { "days", 24*60*60 }, + { "weeks", 7*24*60*60 }, + { NULL } +}; + +static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched) +{ + const struct typelen *tl; + const struct special *s; + const char *end = date; + int i; + + while (isalpha(*++end)) + /* scan to non-alpha */; + + for (i = 0; i < 12; i++) { + size_t match = match_string(date, month_names[i]); + if (match >= 3) { + tm->tm_mon = i; + *touched = 1; + return end; + } + } + + for (s = special; s->name; s++) { + size_t len = strlen(s->name); + if (match_string(date, s->name) == len) { + s->fn(tm, now, num); + *touched = 1; + return end; + } + } + + if (!*num) { + for (i = 1; i < 11; i++) { + size_t len = strlen(number_name[i]); + if (match_string(date, number_name[i]) == len) { + *num = i; + *touched = 1; + return end; + } + } + if (match_string(date, "last") == 4) { + *num = 1; + *touched = 1; + } + return end; + } + + tl = typelen; + while (tl->type) { + size_t len = strlen(tl->type); + if (match_string(date, tl->type) >= len-1) { + update_tm(tm, now, tl->length * (unsigned long)*num); + *num = 0; + *touched = 1; + return end; + } + tl++; + } + + for (i = 0; i < 7; i++) { + size_t match = match_string(date, weekday_names[i]); + if (match >= 3) { + int diff, n = *num -1; + *num = 0; + + diff = tm->tm_wday - i; + if (diff <= 0) + n++; + diff += 7*n; + + update_tm(tm, now, diff * 24 * 60 * 60); + *touched = 1; + return end; + } + } + + if (match_string(date, "months") >= 5) { + int n; + update_tm(tm, now, 0); /* fill in date fields if needed */ + n = tm->tm_mon - *num; + *num = 0; + while (n < 0) { + n += 12; + tm->tm_year--; + } + tm->tm_mon = n; + *touched = 1; + return end; + } + + if (match_string(date, "years") >= 4) { + update_tm(tm, now, 0); /* fill in date fields if needed */ + tm->tm_year -= *num; + *num = 0; + *touched = 1; + return end; + } + + return end; +} + +static const char *approxidate_digit(const char *date, struct tm *tm, int *num) +{ + char *end; + unsigned long number = strtoul(date, &end, 10); + + switch (*end) { + case ':': + case '.': + case '/': + case '-': + if (isdigit(end[1])) { + size_t match = match_multi_number(number, *end, date, end, tm); + if (match) + return date + match; + } + } + + /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */ + if (date[0] != '0' || end - date <= 2) + *num = number; + return end; +} + +/* + * Do we have a pending number at the end, or when + * we see a new one? Let's assume it's a month day, + * as in "Dec 6, 1992" + */ +static void pending_number(struct tm *tm, int *num) +{ + int number = *num; + + if (number) { + *num = 0; + if (tm->tm_mday < 0 && number < 32) + tm->tm_mday = number; + else if (tm->tm_mon < 0 && number < 13) + tm->tm_mon = number-1; + else if (tm->tm_year < 0) { + if (number > 1969 && number < 2100) + tm->tm_year = number - 1900; + else if (number > 69 && number < 100) + tm->tm_year = number; + else if (number < 38) + tm->tm_year = 100 + number; + /* We mess up for number = 00 ? */ + } + } +} + +static git_time_t approxidate_str(const char *date, + time_t time_sec, + int *error_ret) +{ + int number = 0; + int touched = 0; + struct tm tm = {0}, now; + + p_localtime_r(&time_sec, &tm); + now = tm; + + tm.tm_year = -1; + tm.tm_mon = -1; + tm.tm_mday = -1; + + for (;;) { + unsigned char c = *date; + if (!c) + break; + date++; + if (isdigit(c)) { + pending_number(&tm, &number); + date = approxidate_digit(date-1, &tm, &number); + touched = 1; + continue; + } + if (isalpha(c)) + date = approxidate_alpha(date-1, &tm, &now, &number, &touched); + } + pending_number(&tm, &number); + if (!touched) + *error_ret = -1; + return update_tm(&tm, &now, 0); +} + +int git_date_parse(git_time_t *out, const char *date) +{ + time_t time_sec; + git_time_t timestamp; + int offset, error_ret=0; + + if (!parse_date_basic(date, ×tamp, &offset)) { + *out = timestamp; + return 0; + } + + if (time(&time_sec) == -1) + return -1; + + *out = approxidate_str(date, time_sec, &error_ret); + return error_ret; +} + +int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset) +{ + time_t t; + struct tm gmt; + + GIT_ASSERT_ARG(out); + + t = (time_t) (time + offset * 60); + + if (p_gmtime_r(&t, &gmt) == NULL) + return -1; + + return git_str_printf(out, "%.3s, %u %.3s %.4u %02u:%02u:%02u %+03d%02d", + weekday_names[gmt.tm_wday], + gmt.tm_mday, + month_names[gmt.tm_mon], + gmt.tm_year + 1900, + gmt.tm_hour, gmt.tm_min, gmt.tm_sec, + offset / 60, offset % 60); +} + diff --git a/src/libgit2/date.h b/src/libgit2/date.h new file mode 100644 index 000000000..7ebd3c30e --- /dev/null +++ b/src/libgit2/date.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_date_h__ +#define INCLUDE_date_h__ + +#include "util.h" +#include "str.h" + +/* + * Parse a string into a value as a git_time_t. + * + * Sample valid input: + * - "yesterday" + * - "July 17, 2003" + * - "2003-7-17 08:23" + */ +extern int git_date_parse(git_time_t *out, const char *date); + +/* + * Format a git_time as a RFC2822 string + * + * @param out buffer to store formatted date + * @param time the time to be formatted + * @param offset the timezone offset + * @return 0 if successful; -1 on error + */ +extern int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset); + +#endif diff --git a/src/libgit2/delta.c b/src/libgit2/delta.c new file mode 100644 index 000000000..2d2c5fa85 --- /dev/null +++ b/src/libgit2/delta.c @@ -0,0 +1,628 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "delta.h" + +/* maximum hash entry list for the same hash bucket */ +#define HASH_LIMIT 64 + +#define RABIN_SHIFT 23 +#define RABIN_WINDOW 16 + +static const unsigned int T[256] = { + 0x00000000, 0xab59b4d1, 0x56b369a2, 0xfdeadd73, 0x063f6795, 0xad66d344, + 0x508c0e37, 0xfbd5bae6, 0x0c7ecf2a, 0xa7277bfb, 0x5acda688, 0xf1941259, + 0x0a41a8bf, 0xa1181c6e, 0x5cf2c11d, 0xf7ab75cc, 0x18fd9e54, 0xb3a42a85, + 0x4e4ef7f6, 0xe5174327, 0x1ec2f9c1, 0xb59b4d10, 0x48719063, 0xe32824b2, + 0x1483517e, 0xbfdae5af, 0x423038dc, 0xe9698c0d, 0x12bc36eb, 0xb9e5823a, + 0x440f5f49, 0xef56eb98, 0x31fb3ca8, 0x9aa28879, 0x6748550a, 0xcc11e1db, + 0x37c45b3d, 0x9c9defec, 0x6177329f, 0xca2e864e, 0x3d85f382, 0x96dc4753, + 0x6b369a20, 0xc06f2ef1, 0x3bba9417, 0x90e320c6, 0x6d09fdb5, 0xc6504964, + 0x2906a2fc, 0x825f162d, 0x7fb5cb5e, 0xd4ec7f8f, 0x2f39c569, 0x846071b8, + 0x798aaccb, 0xd2d3181a, 0x25786dd6, 0x8e21d907, 0x73cb0474, 0xd892b0a5, + 0x23470a43, 0x881ebe92, 0x75f463e1, 0xdeadd730, 0x63f67950, 0xc8afcd81, + 0x354510f2, 0x9e1ca423, 0x65c91ec5, 0xce90aa14, 0x337a7767, 0x9823c3b6, + 0x6f88b67a, 0xc4d102ab, 0x393bdfd8, 0x92626b09, 0x69b7d1ef, 0xc2ee653e, + 0x3f04b84d, 0x945d0c9c, 0x7b0be704, 0xd05253d5, 0x2db88ea6, 0x86e13a77, + 0x7d348091, 0xd66d3440, 0x2b87e933, 0x80de5de2, 0x7775282e, 0xdc2c9cff, + 0x21c6418c, 0x8a9ff55d, 0x714a4fbb, 0xda13fb6a, 0x27f92619, 0x8ca092c8, + 0x520d45f8, 0xf954f129, 0x04be2c5a, 0xafe7988b, 0x5432226d, 0xff6b96bc, + 0x02814bcf, 0xa9d8ff1e, 0x5e738ad2, 0xf52a3e03, 0x08c0e370, 0xa39957a1, + 0x584ced47, 0xf3155996, 0x0eff84e5, 0xa5a63034, 0x4af0dbac, 0xe1a96f7d, + 0x1c43b20e, 0xb71a06df, 0x4ccfbc39, 0xe79608e8, 0x1a7cd59b, 0xb125614a, + 0x468e1486, 0xedd7a057, 0x103d7d24, 0xbb64c9f5, 0x40b17313, 0xebe8c7c2, + 0x16021ab1, 0xbd5bae60, 0x6cb54671, 0xc7ecf2a0, 0x3a062fd3, 0x915f9b02, + 0x6a8a21e4, 0xc1d39535, 0x3c394846, 0x9760fc97, 0x60cb895b, 0xcb923d8a, + 0x3678e0f9, 0x9d215428, 0x66f4eece, 0xcdad5a1f, 0x3047876c, 0x9b1e33bd, + 0x7448d825, 0xdf116cf4, 0x22fbb187, 0x89a20556, 0x7277bfb0, 0xd92e0b61, + 0x24c4d612, 0x8f9d62c3, 0x7836170f, 0xd36fa3de, 0x2e857ead, 0x85dcca7c, + 0x7e09709a, 0xd550c44b, 0x28ba1938, 0x83e3ade9, 0x5d4e7ad9, 0xf617ce08, + 0x0bfd137b, 0xa0a4a7aa, 0x5b711d4c, 0xf028a99d, 0x0dc274ee, 0xa69bc03f, + 0x5130b5f3, 0xfa690122, 0x0783dc51, 0xacda6880, 0x570fd266, 0xfc5666b7, + 0x01bcbbc4, 0xaae50f15, 0x45b3e48d, 0xeeea505c, 0x13008d2f, 0xb85939fe, + 0x438c8318, 0xe8d537c9, 0x153feaba, 0xbe665e6b, 0x49cd2ba7, 0xe2949f76, + 0x1f7e4205, 0xb427f6d4, 0x4ff24c32, 0xe4abf8e3, 0x19412590, 0xb2189141, + 0x0f433f21, 0xa41a8bf0, 0x59f05683, 0xf2a9e252, 0x097c58b4, 0xa225ec65, + 0x5fcf3116, 0xf49685c7, 0x033df00b, 0xa86444da, 0x558e99a9, 0xfed72d78, + 0x0502979e, 0xae5b234f, 0x53b1fe3c, 0xf8e84aed, 0x17bea175, 0xbce715a4, + 0x410dc8d7, 0xea547c06, 0x1181c6e0, 0xbad87231, 0x4732af42, 0xec6b1b93, + 0x1bc06e5f, 0xb099da8e, 0x4d7307fd, 0xe62ab32c, 0x1dff09ca, 0xb6a6bd1b, + 0x4b4c6068, 0xe015d4b9, 0x3eb80389, 0x95e1b758, 0x680b6a2b, 0xc352defa, + 0x3887641c, 0x93ded0cd, 0x6e340dbe, 0xc56db96f, 0x32c6cca3, 0x999f7872, + 0x6475a501, 0xcf2c11d0, 0x34f9ab36, 0x9fa01fe7, 0x624ac294, 0xc9137645, + 0x26459ddd, 0x8d1c290c, 0x70f6f47f, 0xdbaf40ae, 0x207afa48, 0x8b234e99, + 0x76c993ea, 0xdd90273b, 0x2a3b52f7, 0x8162e626, 0x7c883b55, 0xd7d18f84, + 0x2c043562, 0x875d81b3, 0x7ab75cc0, 0xd1eee811 +}; + +static const unsigned int U[256] = { + 0x00000000, 0x7eb5200d, 0x5633f4cb, 0x2886d4c6, 0x073e5d47, 0x798b7d4a, + 0x510da98c, 0x2fb88981, 0x0e7cba8e, 0x70c99a83, 0x584f4e45, 0x26fa6e48, + 0x0942e7c9, 0x77f7c7c4, 0x5f711302, 0x21c4330f, 0x1cf9751c, 0x624c5511, + 0x4aca81d7, 0x347fa1da, 0x1bc7285b, 0x65720856, 0x4df4dc90, 0x3341fc9d, + 0x1285cf92, 0x6c30ef9f, 0x44b63b59, 0x3a031b54, 0x15bb92d5, 0x6b0eb2d8, + 0x4388661e, 0x3d3d4613, 0x39f2ea38, 0x4747ca35, 0x6fc11ef3, 0x11743efe, + 0x3eccb77f, 0x40799772, 0x68ff43b4, 0x164a63b9, 0x378e50b6, 0x493b70bb, + 0x61bda47d, 0x1f088470, 0x30b00df1, 0x4e052dfc, 0x6683f93a, 0x1836d937, + 0x250b9f24, 0x5bbebf29, 0x73386bef, 0x0d8d4be2, 0x2235c263, 0x5c80e26e, + 0x740636a8, 0x0ab316a5, 0x2b7725aa, 0x55c205a7, 0x7d44d161, 0x03f1f16c, + 0x2c4978ed, 0x52fc58e0, 0x7a7a8c26, 0x04cfac2b, 0x73e5d470, 0x0d50f47d, + 0x25d620bb, 0x5b6300b6, 0x74db8937, 0x0a6ea93a, 0x22e87dfc, 0x5c5d5df1, + 0x7d996efe, 0x032c4ef3, 0x2baa9a35, 0x551fba38, 0x7aa733b9, 0x041213b4, + 0x2c94c772, 0x5221e77f, 0x6f1ca16c, 0x11a98161, 0x392f55a7, 0x479a75aa, + 0x6822fc2b, 0x1697dc26, 0x3e1108e0, 0x40a428ed, 0x61601be2, 0x1fd53bef, + 0x3753ef29, 0x49e6cf24, 0x665e46a5, 0x18eb66a8, 0x306db26e, 0x4ed89263, + 0x4a173e48, 0x34a21e45, 0x1c24ca83, 0x6291ea8e, 0x4d29630f, 0x339c4302, + 0x1b1a97c4, 0x65afb7c9, 0x446b84c6, 0x3adea4cb, 0x1258700d, 0x6ced5000, + 0x4355d981, 0x3de0f98c, 0x15662d4a, 0x6bd30d47, 0x56ee4b54, 0x285b6b59, + 0x00ddbf9f, 0x7e689f92, 0x51d01613, 0x2f65361e, 0x07e3e2d8, 0x7956c2d5, + 0x5892f1da, 0x2627d1d7, 0x0ea10511, 0x7014251c, 0x5facac9d, 0x21198c90, + 0x099f5856, 0x772a785b, 0x4c921c31, 0x32273c3c, 0x1aa1e8fa, 0x6414c8f7, + 0x4bac4176, 0x3519617b, 0x1d9fb5bd, 0x632a95b0, 0x42eea6bf, 0x3c5b86b2, + 0x14dd5274, 0x6a687279, 0x45d0fbf8, 0x3b65dbf5, 0x13e30f33, 0x6d562f3e, + 0x506b692d, 0x2ede4920, 0x06589de6, 0x78edbdeb, 0x5755346a, 0x29e01467, + 0x0166c0a1, 0x7fd3e0ac, 0x5e17d3a3, 0x20a2f3ae, 0x08242768, 0x76910765, + 0x59298ee4, 0x279caee9, 0x0f1a7a2f, 0x71af5a22, 0x7560f609, 0x0bd5d604, + 0x235302c2, 0x5de622cf, 0x725eab4e, 0x0ceb8b43, 0x246d5f85, 0x5ad87f88, + 0x7b1c4c87, 0x05a96c8a, 0x2d2fb84c, 0x539a9841, 0x7c2211c0, 0x029731cd, + 0x2a11e50b, 0x54a4c506, 0x69998315, 0x172ca318, 0x3faa77de, 0x411f57d3, + 0x6ea7de52, 0x1012fe5f, 0x38942a99, 0x46210a94, 0x67e5399b, 0x19501996, + 0x31d6cd50, 0x4f63ed5d, 0x60db64dc, 0x1e6e44d1, 0x36e89017, 0x485db01a, + 0x3f77c841, 0x41c2e84c, 0x69443c8a, 0x17f11c87, 0x38499506, 0x46fcb50b, + 0x6e7a61cd, 0x10cf41c0, 0x310b72cf, 0x4fbe52c2, 0x67388604, 0x198da609, + 0x36352f88, 0x48800f85, 0x6006db43, 0x1eb3fb4e, 0x238ebd5d, 0x5d3b9d50, + 0x75bd4996, 0x0b08699b, 0x24b0e01a, 0x5a05c017, 0x728314d1, 0x0c3634dc, + 0x2df207d3, 0x534727de, 0x7bc1f318, 0x0574d315, 0x2acc5a94, 0x54797a99, + 0x7cffae5f, 0x024a8e52, 0x06852279, 0x78300274, 0x50b6d6b2, 0x2e03f6bf, + 0x01bb7f3e, 0x7f0e5f33, 0x57888bf5, 0x293dabf8, 0x08f998f7, 0x764cb8fa, + 0x5eca6c3c, 0x207f4c31, 0x0fc7c5b0, 0x7172e5bd, 0x59f4317b, 0x27411176, + 0x1a7c5765, 0x64c97768, 0x4c4fa3ae, 0x32fa83a3, 0x1d420a22, 0x63f72a2f, + 0x4b71fee9, 0x35c4dee4, 0x1400edeb, 0x6ab5cde6, 0x42331920, 0x3c86392d, + 0x133eb0ac, 0x6d8b90a1, 0x450d4467, 0x3bb8646a +}; + +struct index_entry { + const unsigned char *ptr; + unsigned int val; + struct index_entry *next; +}; + +struct git_delta_index { + unsigned long memsize; + const void *src_buf; + size_t src_size; + unsigned int hash_mask; + struct index_entry *hash[GIT_FLEX_ARRAY]; +}; + +static int lookup_index_alloc( + void **out, unsigned long *out_len, size_t entries, size_t hash_count) +{ + size_t entries_len, hash_len, index_len; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&entries_len, entries, sizeof(struct index_entry)); + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&hash_len, hash_count, sizeof(struct index_entry *)); + + GIT_ERROR_CHECK_ALLOC_ADD(&index_len, sizeof(struct git_delta_index), entries_len); + GIT_ERROR_CHECK_ALLOC_ADD(&index_len, index_len, hash_len); + + if (!git__is_ulong(index_len)) { + git_error_set(GIT_ERROR_NOMEMORY, "overly large delta"); + return -1; + } + + *out = git__malloc(index_len); + GIT_ERROR_CHECK_ALLOC(*out); + + *out_len = (unsigned long)index_len; + return 0; +} + +int git_delta_index_init( + git_delta_index **out, const void *buf, size_t bufsize) +{ + unsigned int i, hsize, hmask, entries, prev_val, *hash_count; + const unsigned char *data, *buffer = buf; + struct git_delta_index *index; + struct index_entry *entry, **hash; + void *mem; + unsigned long memsize; + + *out = NULL; + + if (!buf || !bufsize) + return 0; + + /* Determine index hash size. Note that indexing skips the + first byte to allow for optimizing the rabin polynomial + initialization in create_delta(). */ + entries = (unsigned int)(bufsize - 1) / RABIN_WINDOW; + if (bufsize >= 0xffffffffUL) { + /* + * Current delta format can't encode offsets into + * reference buffer with more than 32 bits. + */ + entries = 0xfffffffeU / RABIN_WINDOW; + } + hsize = entries / 4; + for (i = 4; i < 31 && (1u << i) < hsize; i++); + hsize = 1 << i; + hmask = hsize - 1; + + if (lookup_index_alloc(&mem, &memsize, entries, hsize) < 0) + return -1; + + index = mem; + mem = index->hash; + hash = mem; + mem = hash + hsize; + entry = mem; + + index->memsize = memsize; + index->src_buf = buf; + index->src_size = bufsize; + index->hash_mask = hmask; + memset(hash, 0, hsize * sizeof(*hash)); + + /* allocate an array to count hash entries */ + hash_count = git__calloc(hsize, sizeof(*hash_count)); + if (!hash_count) { + git__free(index); + return -1; + } + + /* then populate the index */ + prev_val = ~0; + for (data = buffer + entries * RABIN_WINDOW - RABIN_WINDOW; + data >= buffer; + data -= RABIN_WINDOW) { + unsigned int val = 0; + for (i = 1; i <= RABIN_WINDOW; i++) + val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT]; + if (val == prev_val) { + /* keep the lowest of consecutive identical blocks */ + entry[-1].ptr = data + RABIN_WINDOW; + } else { + prev_val = val; + i = val & hmask; + entry->ptr = data + RABIN_WINDOW; + entry->val = val; + entry->next = hash[i]; + hash[i] = entry++; + hash_count[i]++; + } + } + + /* + * Determine a limit on the number of entries in the same hash + * bucket. This guard us against patological data sets causing + * really bad hash distribution with most entries in the same hash + * bucket that would bring us to O(m*n) computing costs (m and n + * corresponding to reference and target buffer sizes). + * + * Make sure none of the hash buckets has more entries than + * we're willing to test. Otherwise we cull the entry list + * uniformly to still preserve a good repartition across + * the reference buffer. + */ + for (i = 0; i < hsize; i++) { + if (hash_count[i] < HASH_LIMIT) + continue; + + entry = hash[i]; + do { + struct index_entry *keep = entry; + int skip = hash_count[i] / HASH_LIMIT / 2; + do { + entry = entry->next; + } while(--skip && entry); + keep->next = entry; + } while (entry); + } + git__free(hash_count); + + *out = index; + return 0; +} + +void git_delta_index_free(git_delta_index *index) +{ + git__free(index); +} + +size_t git_delta_index_size(git_delta_index *index) +{ + GIT_ASSERT_ARG(index); + + return index->memsize; +} + +/* + * The maximum size for any opcode sequence, including the initial header + * plus rabin window plus biggest copy. + */ +#define MAX_OP_SIZE (5 + 5 + 1 + RABIN_WINDOW + 7) + +int git_delta_create_from_index( + void **out, + size_t *out_len, + const struct git_delta_index *index, + const void *trg_buf, + size_t trg_size, + size_t max_size) +{ + unsigned int i, bufpos, bufsize, moff, msize, val; + int inscnt; + const unsigned char *ref_data, *ref_top, *data, *top; + unsigned char *buf; + + *out = NULL; + *out_len = 0; + + if (!trg_buf || !trg_size) + return 0; + + if (index->src_size > UINT_MAX || + trg_size > UINT_MAX || + max_size > (UINT_MAX - MAX_OP_SIZE - 1)) { + git_error_set(GIT_ERROR_INVALID, "buffer sizes too large for delta processing"); + return -1; + } + + bufpos = 0; + bufsize = 8192; + if (max_size && bufsize >= max_size) + bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1); + buf = git__malloc(bufsize); + GIT_ERROR_CHECK_ALLOC(buf); + + /* store reference buffer size */ + i = (unsigned int)index->src_size; + while (i >= 0x80) { + buf[bufpos++] = i | 0x80; + i >>= 7; + } + buf[bufpos++] = i; + + /* store target buffer size */ + i = (unsigned int)trg_size; + while (i >= 0x80) { + buf[bufpos++] = i | 0x80; + i >>= 7; + } + buf[bufpos++] = i; + + ref_data = index->src_buf; + ref_top = ref_data + index->src_size; + data = trg_buf; + top = (const unsigned char *) trg_buf + trg_size; + + bufpos++; + val = 0; + for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) { + buf[bufpos++] = *data; + val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT]; + } + inscnt = i; + + moff = 0; + msize = 0; + while (data < top) { + if (msize < 4096) { + struct index_entry *entry; + val ^= U[data[-RABIN_WINDOW]]; + val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT]; + i = val & index->hash_mask; + for (entry = index->hash[i]; entry; entry = entry->next) { + const unsigned char *ref = entry->ptr; + const unsigned char *src = data; + unsigned int ref_size = (unsigned int)(ref_top - ref); + if (entry->val != val) + continue; + if (ref_size > (unsigned int)(top - src)) + ref_size = (unsigned int)(top - src); + if (ref_size <= msize) + break; + while (ref_size-- && *src++ == *ref) + ref++; + if (msize < (unsigned int)(ref - entry->ptr)) { + /* this is our best match so far */ + msize = (unsigned int)(ref - entry->ptr); + moff = (unsigned int)(entry->ptr - ref_data); + if (msize >= 4096) /* good enough */ + break; + } + } + } + + if (msize < 4) { + if (!inscnt) + bufpos++; + buf[bufpos++] = *data++; + inscnt++; + if (inscnt == 0x7f) { + buf[bufpos - inscnt - 1] = inscnt; + inscnt = 0; + } + msize = 0; + } else { + unsigned int left; + unsigned char *op; + + if (inscnt) { + while (moff && ref_data[moff-1] == data[-1]) { + /* we can match one byte back */ + msize++; + moff--; + data--; + bufpos--; + if (--inscnt) + continue; + bufpos--; /* remove count slot */ + inscnt--; /* make it -1 */ + break; + } + buf[bufpos - inscnt - 1] = inscnt; + inscnt = 0; + } + + /* A copy op is currently limited to 64KB (pack v2) */ + left = (msize < 0x10000) ? 0 : (msize - 0x10000); + msize -= left; + + op = buf + bufpos++; + i = 0x80; + + if (moff & 0x000000ff) + buf[bufpos++] = moff >> 0, i |= 0x01; + if (moff & 0x0000ff00) + buf[bufpos++] = moff >> 8, i |= 0x02; + if (moff & 0x00ff0000) + buf[bufpos++] = moff >> 16, i |= 0x04; + if (moff & 0xff000000) + buf[bufpos++] = moff >> 24, i |= 0x08; + + if (msize & 0x00ff) + buf[bufpos++] = msize >> 0, i |= 0x10; + if (msize & 0xff00) + buf[bufpos++] = msize >> 8, i |= 0x20; + + *op = i; + + data += msize; + moff += msize; + msize = left; + + if (msize < 4096) { + int j; + val = 0; + for (j = -RABIN_WINDOW; j < 0; j++) + val = ((val << 8) | data[j]) + ^ T[val >> RABIN_SHIFT]; + } + } + + if (bufpos >= bufsize - MAX_OP_SIZE) { + void *tmp = buf; + bufsize = bufsize * 3 / 2; + if (max_size && bufsize >= max_size) + bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1); + if (max_size && bufpos > max_size) + break; + buf = git__realloc(buf, bufsize); + if (!buf) { + git__free(tmp); + return -1; + } + } + } + + if (inscnt) + buf[bufpos - inscnt - 1] = inscnt; + + if (max_size && bufpos > max_size) { + git_error_set(GIT_ERROR_NOMEMORY, "delta would be larger than maximum size"); + git__free(buf); + return GIT_EBUFS; + } + + *out_len = bufpos; + *out = buf; + return 0; +} + +/* +* Delta application was heavily cribbed from BinaryDelta.java in JGit, which +* itself was heavily cribbed from patch-delta.c in the +* GIT project. The original delta patching code was written by +* Nicolas Pitre . +*/ + +static int hdr_sz( + size_t *size, + const unsigned char **delta, + const unsigned char *end) +{ + const unsigned char *d = *delta; + size_t r = 0; + unsigned int c, shift = 0; + + do { + if (d == end) { + git_error_set(GIT_ERROR_INVALID, "truncated delta"); + return -1; + } + + c = *d++; + r |= (c & 0x7f) << shift; + shift += 7; + } while (c & 0x80); + *delta = d; + *size = r; + return 0; +} + +int git_delta_read_header( + size_t *base_out, + size_t *result_out, + const unsigned char *delta, + size_t delta_len) +{ + const unsigned char *delta_end = delta + delta_len; + if ((hdr_sz(base_out, &delta, delta_end) < 0) || + (hdr_sz(result_out, &delta, delta_end) < 0)) + return -1; + return 0; +} + +#define DELTA_HEADER_BUFFER_LEN 16 +int git_delta_read_header_fromstream( + size_t *base_sz, size_t *res_sz, git_packfile_stream *stream) +{ + static const size_t buffer_len = DELTA_HEADER_BUFFER_LEN; + unsigned char buffer[DELTA_HEADER_BUFFER_LEN]; + const unsigned char *delta, *delta_end; + size_t len; + ssize_t read; + + len = read = 0; + while (len < buffer_len) { + read = git_packfile_stream_read(stream, &buffer[len], buffer_len - len); + + if (read == 0) + break; + + if (read == GIT_EBUFS) + continue; + + len += read; + } + + delta = buffer; + delta_end = delta + len; + if ((hdr_sz(base_sz, &delta, delta_end) < 0) || + (hdr_sz(res_sz, &delta, delta_end) < 0)) + return -1; + + return 0; +} + +int git_delta_apply( + void **out, + size_t *out_len, + const unsigned char *base, + size_t base_len, + const unsigned char *delta, + size_t delta_len) +{ + const unsigned char *delta_end = delta + delta_len; + size_t base_sz, res_sz, alloc_sz; + unsigned char *res_dp; + + *out = NULL; + *out_len = 0; + + /* + * Check that the base size matches the data we were given; + * if not we would underflow while accessing data from the + * base object, resulting in data corruption or segfault. + */ + if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) { + git_error_set(GIT_ERROR_INVALID, "failed to apply delta: base size does not match given data"); + return -1; + } + + if (hdr_sz(&res_sz, &delta, delta_end) < 0) { + git_error_set(GIT_ERROR_INVALID, "failed to apply delta: base size does not match given data"); + return -1; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1); + res_dp = git__malloc(alloc_sz); + GIT_ERROR_CHECK_ALLOC(res_dp); + + res_dp[res_sz] = '\0'; + *out = res_dp; + *out_len = res_sz; + + while (delta < delta_end) { + unsigned char cmd = *delta++; + if (cmd & 0x80) { + /* cmd is a copy instruction; copy from the base. */ + size_t off = 0, len = 0, end; + +#define ADD_DELTA(o, shift) { if (delta < delta_end) (o) |= ((unsigned) *delta++ << shift); else goto fail; } + if (cmd & 0x01) ADD_DELTA(off, 0UL); + if (cmd & 0x02) ADD_DELTA(off, 8UL); + if (cmd & 0x04) ADD_DELTA(off, 16UL); + if (cmd & 0x08) ADD_DELTA(off, 24UL); + + if (cmd & 0x10) ADD_DELTA(len, 0UL); + if (cmd & 0x20) ADD_DELTA(len, 8UL); + if (cmd & 0x40) ADD_DELTA(len, 16UL); + if (!len) len = 0x10000; +#undef ADD_DELTA + + if (GIT_ADD_SIZET_OVERFLOW(&end, off, len) || + base_len < end || res_sz < len) + goto fail; + + memcpy(res_dp, base + off, len); + res_dp += len; + res_sz -= len; + + } else if (cmd) { + /* + * cmd is a literal insert instruction; copy from + * the delta stream itself. + */ + if (delta_end - delta < cmd || res_sz < cmd) + goto fail; + memcpy(res_dp, delta, cmd); + delta += cmd; + res_dp += cmd; + res_sz -= cmd; + + } else { + /* cmd == 0 is reserved for future encodings. */ + goto fail; + } + } + + if (delta != delta_end || res_sz) + goto fail; + return 0; + +fail: + git__free(*out); + + *out = NULL; + *out_len = 0; + + git_error_set(GIT_ERROR_INVALID, "failed to apply delta"); + return -1; +} diff --git a/src/libgit2/delta.h b/src/libgit2/delta.h new file mode 100644 index 000000000..f61987304 --- /dev/null +++ b/src/libgit2/delta.h @@ -0,0 +1,136 @@ +/* + * diff-delta code taken from git.git. See diff-delta.c for details. + * + */ +#ifndef INCLUDE_git_delta_h__ +#define INCLUDE_git_delta_h__ + +#include "common.h" + +#include "pack.h" + +typedef struct git_delta_index git_delta_index; + +/* + * git_delta_index_init: compute index data from given buffer + * + * This returns a pointer to a struct delta_index that should be passed to + * subsequent create_delta() calls, or to free_delta_index(). A NULL pointer + * is returned on failure. The given buffer must not be freed nor altered + * before free_delta_index() is called. The returned pointer must be freed + * using free_delta_index(). + */ +extern int git_delta_index_init( + git_delta_index **out, const void *buf, size_t bufsize); + +/* + * Free the index created by git_delta_index_init() + */ +extern void git_delta_index_free(git_delta_index *index); + +/* + * Returns memory usage of delta index. + */ +extern size_t git_delta_index_size(git_delta_index *index); + +/* + * create_delta: create a delta from given index for the given buffer + * + * This function may be called multiple times with different buffers using + * the same delta_index pointer. If max_delta_size is non-zero and the + * resulting delta is to be larger than max_delta_size then NULL is returned. + * On success, a non-NULL pointer to the buffer with the delta data is + * returned and *delta_size is updated with its size. The returned buffer + * must be freed by the caller. + */ +extern int git_delta_create_from_index( + void **out, + size_t *out_size, + const struct git_delta_index *index, + const void *buf, + size_t bufsize, + size_t max_delta_size); + +/* + * diff_delta: create a delta from source buffer to target buffer + * + * If max_delta_size is non-zero and the resulting delta is to be larger + * than max_delta_size then GIT_EBUFS is returned. On success, a non-NULL + * pointer to the buffer with the delta data is returned and *delta_size is + * updated with its size. The returned buffer must be freed by the caller. + */ +GIT_INLINE(int) git_delta( + void **out, size_t *out_len, + const void *src_buf, size_t src_bufsize, + const void *trg_buf, size_t trg_bufsize, + size_t max_delta_size) +{ + git_delta_index *index; + int error = 0; + + *out = NULL; + *out_len = 0; + + if ((error = git_delta_index_init(&index, src_buf, src_bufsize)) < 0) + return error; + + if (index) { + error = git_delta_create_from_index(out, out_len, + index, trg_buf, trg_bufsize, max_delta_size); + + git_delta_index_free(index); + } + + return error; +} + +/* the smallest possible delta size is 4 bytes */ +#define GIT_DELTA_SIZE_MIN 4 + +/** +* Apply a git binary delta to recover the original content. +* The caller is responsible for freeing the returned buffer. +* +* @param out the output buffer +* @param out_len the length of the output buffer +* @param base the base to copy from during copy instructions. +* @param base_len number of bytes available at base. +* @param delta the delta to execute copy/insert instructions from. +* @param delta_len total number of bytes in the delta. +* @return 0 on success or an error code +*/ +extern int git_delta_apply( + void **out, + size_t *out_len, + const unsigned char *base, + size_t base_len, + const unsigned char *delta, + size_t delta_len); + +/** +* Read the header of a git binary delta. +* +* @param base_out pointer to store the base size field. +* @param result_out pointer to store the result size field. +* @param delta the delta to execute copy/insert instructions from. +* @param delta_len total number of bytes in the delta. +* @return 0 on success or an error code +*/ +extern int git_delta_read_header( + size_t *base_out, + size_t *result_out, + const unsigned char *delta, + size_t delta_len); + +/** + * Read the header of a git binary delta + * + * This variant reads just enough from the packfile stream to read the + * delta header. + */ +extern int git_delta_read_header_fromstream( + size_t *base_out, + size_t *result_out, + git_packfile_stream *stream); + +#endif diff --git a/src/libgit2/describe.c b/src/libgit2/describe.c new file mode 100644 index 000000000..1033eac50 --- /dev/null +++ b/src/libgit2/describe.c @@ -0,0 +1,909 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "git2/describe.h" +#include "git2/strarray.h" +#include "git2/diff.h" +#include "git2/status.h" + +#include "buf.h" +#include "commit.h" +#include "commit_list.h" +#include "oidmap.h" +#include "refs.h" +#include "repository.h" +#include "revwalk.h" +#include "tag.h" +#include "vector.h" +#include "wildmatch.h" + +/* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */ + +struct commit_name { + git_tag *tag; + unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ + unsigned name_checked:1; + git_oid sha1; + char *path; + + /* Khash workaround. They original key has to still be reachable */ + git_oid peeled; +}; + +static void *oidmap_value_bykey(git_oidmap *map, const git_oid *key) +{ + return git_oidmap_get(map, key); +} + +static struct commit_name *find_commit_name( + git_oidmap *names, + const git_oid *peeled) +{ + return (struct commit_name *)(oidmap_value_bykey(names, peeled)); +} + +static int replace_name( + git_tag **tag, + git_repository *repo, + struct commit_name *e, + unsigned int prio, + const git_oid *sha1) +{ + git_time_t e_time = 0, t_time = 0; + + if (!e || e->prio < prio) + return 1; + + if (e->prio == 2 && prio == 2) { + /* Multiple annotated tags point to the same commit. + * Select one to keep based upon their tagger date. + */ + git_tag *t = NULL; + + if (!e->tag) { + if (git_tag_lookup(&t, repo, &e->sha1) < 0) + return 1; + e->tag = t; + } + + if (git_tag_lookup(&t, repo, sha1) < 0) + return 0; + + *tag = t; + + if (e->tag->tagger) + e_time = e->tag->tagger->when.time; + + if (t->tagger) + t_time = t->tagger->when.time; + + if (e_time < t_time) + return 1; + } + + return 0; +} + +static int add_to_known_names( + git_repository *repo, + git_oidmap *names, + const char *path, + const git_oid *peeled, + unsigned int prio, + const git_oid *sha1) +{ + struct commit_name *e = find_commit_name(names, peeled); + bool found = (e != NULL); + + git_tag *tag = NULL; + if (replace_name(&tag, repo, e, prio, sha1)) { + if (!found) { + e = git__malloc(sizeof(struct commit_name)); + GIT_ERROR_CHECK_ALLOC(e); + + e->path = NULL; + e->tag = NULL; + } + + if (e->tag) + git_tag_free(e->tag); + e->tag = tag; + e->prio = prio; + e->name_checked = 0; + git_oid_cpy(&e->sha1, sha1); + git__free(e->path); + e->path = git__strdup(path); + git_oid_cpy(&e->peeled, peeled); + + if (!found && git_oidmap_set(names, &e->peeled, e) < 0) + return -1; + } + else + git_tag_free(tag); + + return 0; +} + +static int retrieve_peeled_tag_or_object_oid( + git_oid *peeled_out, + git_oid *ref_target_out, + git_repository *repo, + const char *refname) +{ + git_reference *ref; + git_object *peeled = NULL; + int error; + + if ((error = git_reference_lookup_resolved(&ref, repo, refname, -1)) < 0) + return error; + + if ((error = git_reference_peel(&peeled, ref, GIT_OBJECT_ANY)) < 0) + goto cleanup; + + git_oid_cpy(ref_target_out, git_reference_target(ref)); + git_oid_cpy(peeled_out, git_object_id(peeled)); + + if (git_oid_cmp(ref_target_out, peeled_out) != 0) + error = 1; /* The reference was pointing to a annotated tag */ + else + error = 0; /* Any other object */ + +cleanup: + git_reference_free(ref); + git_object_free(peeled); + return error; +} + +struct git_describe_result { + int dirty; + int exact_match; + int fallback_to_id; + git_oid commit_id; + git_repository *repo; + struct commit_name *name; + struct possible_tag *tag; +}; + +struct get_name_data +{ + git_describe_options *opts; + git_repository *repo; + git_oidmap *names; + git_describe_result *result; +}; + +static int commit_name_dup(struct commit_name **out, struct commit_name *in) +{ + struct commit_name *name; + + name = git__malloc(sizeof(struct commit_name)); + GIT_ERROR_CHECK_ALLOC(name); + + memcpy(name, in, sizeof(struct commit_name)); + name->tag = NULL; + name->path = NULL; + + if (in->tag && git_tag_dup(&name->tag, in->tag) < 0) + return -1; + + name->path = git__strdup(in->path); + GIT_ERROR_CHECK_ALLOC(name->path); + + *out = name; + return 0; +} + +static int get_name(const char *refname, void *payload) +{ + struct get_name_data *data; + bool is_tag, is_annotated, all; + git_oid peeled, sha1; + unsigned int prio; + int error = 0; + + data = (struct get_name_data *)payload; + is_tag = !git__prefixcmp(refname, GIT_REFS_TAGS_DIR); + all = data->opts->describe_strategy == GIT_DESCRIBE_ALL; + + /* Reject anything outside refs/tags/ unless --all */ + if (!all && !is_tag) + return 0; + + /* Accept only tags that match the pattern, if given */ + if (data->opts->pattern && (!is_tag || wildmatch(data->opts->pattern, + refname + strlen(GIT_REFS_TAGS_DIR), 0))) + return 0; + + /* Is it annotated? */ + if ((error = retrieve_peeled_tag_or_object_oid( + &peeled, &sha1, data->repo, refname)) < 0) + return error; + + is_annotated = error; + + /* + * By default, we only use annotated tags, but with --tags + * we fall back to lightweight ones (even without --tags, + * we still remember lightweight ones, only to give hints + * in an error message). --all allows any refs to be used. + */ + if (is_annotated) + prio = 2; + else if (is_tag) + prio = 1; + else + prio = 0; + + add_to_known_names(data->repo, data->names, + all ? refname + strlen(GIT_REFS_DIR) : refname + strlen(GIT_REFS_TAGS_DIR), + &peeled, prio, &sha1); + return 0; +} + +struct possible_tag { + struct commit_name *name; + int depth; + int found_order; + unsigned flag_within; +}; + +static int possible_tag_dup(struct possible_tag **out, struct possible_tag *in) +{ + struct possible_tag *tag; + int error; + + tag = git__malloc(sizeof(struct possible_tag)); + GIT_ERROR_CHECK_ALLOC(tag); + + memcpy(tag, in, sizeof(struct possible_tag)); + tag->name = NULL; + + if ((error = commit_name_dup(&tag->name, in->name)) < 0) { + git__free(tag); + *out = NULL; + return error; + } + + *out = tag; + return 0; +} + +static int compare_pt(const void *a_, const void *b_) +{ + struct possible_tag *a = (struct possible_tag *)a_; + struct possible_tag *b = (struct possible_tag *)b_; + if (a->depth != b->depth) + return a->depth - b->depth; + if (a->found_order != b->found_order) + return a->found_order - b->found_order; + return 0; +} + +#define SEEN (1u << 0) + +static unsigned long finish_depth_computation( + git_pqueue *list, + git_revwalk *walk, + struct possible_tag *best) +{ + unsigned long seen_commits = 0; + int error, i; + + while (git_pqueue_size(list) > 0) { + git_commit_list_node *c = git_pqueue_pop(list); + seen_commits++; + if (c->flags & best->flag_within) { + size_t index = 0; + while (git_pqueue_size(list) > index) { + git_commit_list_node *i = git_pqueue_get(list, index); + if (!(i->flags & best->flag_within)) + break; + index++; + } + if (index > git_pqueue_size(list)) + break; + } else + best->depth++; + for (i = 0; i < c->out_degree; i++) { + git_commit_list_node *p = c->parents[i]; + if ((error = git_commit_list_parse(walk, p)) < 0) + return error; + if (!(p->flags & SEEN)) + if ((error = git_pqueue_insert(list, p)) < 0) + return error; + p->flags |= c->flags; + } + } + return seen_commits; +} + +static int display_name(git_str *buf, git_repository *repo, struct commit_name *n) +{ + if (n->prio == 2 && !n->tag) { + if (git_tag_lookup(&n->tag, repo, &n->sha1) < 0) { + git_error_set(GIT_ERROR_TAG, "annotated tag '%s' not available", n->path); + return -1; + } + } + + if (n->tag && !n->name_checked) { + if (!git_tag_name(n->tag)) { + git_error_set(GIT_ERROR_TAG, "annotated tag '%s' has no embedded name", n->path); + return -1; + } + + /* TODO: Cope with warnings + if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) + warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path); + */ + + n->name_checked = 1; + } + + if (n->tag) + git_str_printf(buf, "%s", git_tag_name(n->tag)); + else + git_str_printf(buf, "%s", n->path); + + return 0; +} + +static int find_unique_abbrev_size( + int *out, + git_repository *repo, + const git_oid *oid_in, + unsigned int abbreviated_size) +{ + size_t size = abbreviated_size; + git_odb *odb; + git_oid dummy; + int error; + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) + return error; + + while (size < GIT_OID_HEXSZ) { + if ((error = git_odb_exists_prefix(&dummy, odb, oid_in, size)) == 0) { + *out = (int) size; + return 0; + } + + /* If the error wasn't that it's not unique, then it's a proper error */ + if (error != GIT_EAMBIGUOUS) + return error; + + /* Try again with a larger size */ + size++; + } + + /* If we didn't find any shorter prefix, we have to do the whole thing */ + *out = GIT_OID_HEXSZ; + + return 0; +} + +static int show_suffix( + git_str *buf, + int depth, + git_repository *repo, + const git_oid *id, + unsigned int abbrev_size) +{ + int error, size = 0; + + char hex_oid[GIT_OID_HEXSZ]; + + if ((error = find_unique_abbrev_size(&size, repo, id, abbrev_size)) < 0) + return error; + + git_oid_fmt(hex_oid, id); + + git_str_printf(buf, "-%d-g", depth); + + git_str_put(buf, hex_oid, size); + + return git_str_oom(buf) ? -1 : 0; +} + +#define MAX_CANDIDATES_TAGS FLAG_BITS - 1 + +static int describe_not_found(const git_oid *oid, const char *message_format) { + char oid_str[GIT_OID_HEXSZ + 1]; + git_oid_tostr(oid_str, sizeof(oid_str), oid); + + git_error_set(GIT_ERROR_DESCRIBE, message_format, oid_str); + return GIT_ENOTFOUND; +} + +static int describe( + struct get_name_data *data, + git_commit *commit) +{ + struct commit_name *n; + struct possible_tag *best; + bool all, tags; + git_revwalk *walk = NULL; + git_pqueue list; + git_commit_list_node *cmit, *gave_up_on = NULL; + git_vector all_matches = GIT_VECTOR_INIT; + unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; + unsigned long seen_commits = 0; /* TODO: Check long */ + unsigned int unannotated_cnt = 0; + int error; + + if (git_vector_init(&all_matches, MAX_CANDIDATES_TAGS, compare_pt) < 0) + return -1; + + if ((error = git_pqueue_init(&list, 0, 2, git_commit_list_time_cmp)) < 0) + goto cleanup; + + all = data->opts->describe_strategy == GIT_DESCRIBE_ALL; + tags = data->opts->describe_strategy == GIT_DESCRIBE_TAGS; + + git_oid_cpy(&data->result->commit_id, git_commit_id(commit)); + + n = find_commit_name(data->names, git_commit_id(commit)); + if (n && (tags || all || n->prio == 2)) { + /* + * Exact match to an existing ref. + */ + data->result->exact_match = 1; + if ((error = commit_name_dup(&data->result->name, n)) < 0) + goto cleanup; + + goto cleanup; + } + + if (!data->opts->max_candidates_tags) { + error = describe_not_found( + git_commit_id(commit), + "cannot describe - no tag exactly matches '%s'"); + + goto cleanup; + } + + if ((error = git_revwalk_new(&walk, git_commit_owner(commit))) < 0) + goto cleanup; + + if ((cmit = git_revwalk__commit_lookup(walk, git_commit_id(commit))) == NULL) + goto cleanup; + + if ((error = git_commit_list_parse(walk, cmit)) < 0) + goto cleanup; + + cmit->flags = SEEN; + + if ((error = git_pqueue_insert(&list, cmit)) < 0) + goto cleanup; + + while (git_pqueue_size(&list) > 0) + { + int i; + + git_commit_list_node *c = (git_commit_list_node *)git_pqueue_pop(&list); + seen_commits++; + + n = find_commit_name(data->names, &c->oid); + + if (n) { + if (!tags && !all && n->prio < 2) { + unannotated_cnt++; + } else if (match_cnt < data->opts->max_candidates_tags) { + struct possible_tag *t = git__malloc(sizeof(struct commit_name)); + GIT_ERROR_CHECK_ALLOC(t); + if ((error = git_vector_insert(&all_matches, t)) < 0) + goto cleanup; + + match_cnt++; + + t->name = n; + t->depth = seen_commits - 1; + t->flag_within = 1u << match_cnt; + t->found_order = match_cnt; + c->flags |= t->flag_within; + if (n->prio == 2) + annotated_cnt++; + } + else { + gave_up_on = c; + break; + } + } + + for (cur_match = 0; cur_match < match_cnt; cur_match++) { + struct possible_tag *t = git_vector_get(&all_matches, cur_match); + if (!(c->flags & t->flag_within)) + t->depth++; + } + + if (annotated_cnt && (git_pqueue_size(&list) == 0)) { + /* + if (debug) { + char oid_str[GIT_OID_HEXSZ + 1]; + git_oid_tostr(oid_str, sizeof(oid_str), &c->oid); + + fprintf(stderr, "finished search at %s\n", oid_str); + } + */ + break; + } + for (i = 0; i < c->out_degree; i++) { + git_commit_list_node *p = c->parents[i]; + if ((error = git_commit_list_parse(walk, p)) < 0) + goto cleanup; + if (!(p->flags & SEEN)) + if ((error = git_pqueue_insert(&list, p)) < 0) + goto cleanup; + p->flags |= c->flags; + + if (data->opts->only_follow_first_parent) + break; + } + } + + if (!match_cnt) { + if (data->opts->show_commit_oid_as_fallback) { + data->result->fallback_to_id = 1; + git_oid_cpy(&data->result->commit_id, &cmit->oid); + + goto cleanup; + } + if (unannotated_cnt) { + error = describe_not_found(git_commit_id(commit), + "cannot describe - " + "no annotated tags can describe '%s'; " + "however, there were unannotated tags."); + goto cleanup; + } + else { + error = describe_not_found(git_commit_id(commit), + "cannot describe - " + "no tags can describe '%s'."); + goto cleanup; + } + } + + git_vector_sort(&all_matches); + + best = (struct possible_tag *)git_vector_get(&all_matches, 0); + + if (gave_up_on) { + if ((error = git_pqueue_insert(&list, gave_up_on)) < 0) + goto cleanup; + seen_commits--; + } + if ((error = finish_depth_computation( + &list, walk, best)) < 0) + goto cleanup; + + seen_commits += error; + if ((error = possible_tag_dup(&data->result->tag, best)) < 0) + goto cleanup; + + /* + { + static const char *prio_names[] = { + "head", "lightweight", "annotated", + }; + + char oid_str[GIT_OID_HEXSZ + 1]; + + if (debug) { + for (cur_match = 0; cur_match < match_cnt; cur_match++) { + struct possible_tag *t = (struct possible_tag *)git_vector_get(&all_matches, cur_match); + fprintf(stderr, " %-11s %8d %s\n", + prio_names[t->name->prio], + t->depth, t->name->path); + } + fprintf(stderr, "traversed %lu commits\n", seen_commits); + if (gave_up_on) { + git_oid_tostr(oid_str, sizeof(oid_str), &gave_up_on->oid); + fprintf(stderr, + "more than %i tags found; listed %i most recent\n" + "gave up search at %s\n", + data->opts->max_candidates_tags, data->opts->max_candidates_tags, + oid_str); + } + } + } + */ + + git_oid_cpy(&data->result->commit_id, &cmit->oid); + +cleanup: + { + size_t i; + struct possible_tag *match; + git_vector_foreach(&all_matches, i, match) { + git__free(match); + } + } + git_vector_free(&all_matches); + git_pqueue_free(&list); + git_revwalk_free(walk); + return error; +} + +static int normalize_options( + git_describe_options *dst, + const git_describe_options *src) +{ + git_describe_options default_options = GIT_DESCRIBE_OPTIONS_INIT; + if (!src) src = &default_options; + + *dst = *src; + + if (dst->max_candidates_tags > GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS) + dst->max_candidates_tags = GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS; + + return 0; +} + +int git_describe_commit( + git_describe_result **result, + git_object *committish, + git_describe_options *opts) +{ + struct get_name_data data; + struct commit_name *name; + git_commit *commit; + int error = -1; + git_describe_options normalized; + + GIT_ASSERT_ARG(result); + GIT_ASSERT_ARG(committish); + + data.result = git__calloc(1, sizeof(git_describe_result)); + GIT_ERROR_CHECK_ALLOC(data.result); + data.result->repo = git_object_owner(committish); + + data.repo = git_object_owner(committish); + + if ((error = normalize_options(&normalized, opts)) < 0) + return error; + + GIT_ERROR_CHECK_VERSION( + &normalized, + GIT_DESCRIBE_OPTIONS_VERSION, + "git_describe_options"); + data.opts = &normalized; + + if ((error = git_oidmap_new(&data.names)) < 0) + return error; + + /** TODO: contains to be implemented */ + + if ((error = git_object_peel((git_object **)(&commit), committish, GIT_OBJECT_COMMIT)) < 0) + goto cleanup; + + if ((error = git_reference_foreach_name( + git_object_owner(committish), + get_name, &data)) < 0) + goto cleanup; + + if (git_oidmap_size(data.names) == 0 && !normalized.show_commit_oid_as_fallback) { + git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - " + "no reference found, cannot describe anything."); + error = -1; + goto cleanup; + } + + if ((error = describe(&data, commit)) < 0) + goto cleanup; + +cleanup: + git_commit_free(commit); + + git_oidmap_foreach_value(data.names, name, { + git_tag_free(name->tag); + git__free(name->path); + git__free(name); + }); + + git_oidmap_free(data.names); + + if (error < 0) + git_describe_result_free(data.result); + else + *result = data.result; + + return error; +} + +int git_describe_workdir( + git_describe_result **out, + git_repository *repo, + git_describe_options *opts) +{ + int error; + git_oid current_id; + git_status_list *status = NULL; + git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; + git_describe_result *result = NULL; + git_object *commit; + + if ((error = git_reference_name_to_id(¤t_id, repo, GIT_HEAD_FILE)) < 0) + return error; + + if ((error = git_object_lookup(&commit, repo, ¤t_id, GIT_OBJECT_COMMIT)) < 0) + return error; + + /* The first step is to perform a describe of HEAD, so we can leverage this */ + if ((error = git_describe_commit(&result, commit, opts)) < 0) + goto out; + + if ((error = git_status_list_new(&status, repo, &status_opts)) < 0) + goto out; + + + if (git_status_list_entrycount(status) > 0) + result->dirty = 1; + +out: + git_object_free(commit); + git_status_list_free(status); + + if (error < 0) + git_describe_result_free(result); + else + *out = result; + + return error; +} + +static int normalize_format_options( + git_describe_format_options *dst, + const git_describe_format_options *src) +{ + if (!src) { + git_describe_format_options_init(dst, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION); + return 0; + } + + memcpy(dst, src, sizeof(git_describe_format_options)); + return 0; +} + +static int git_describe__format( + git_str *out, + const git_describe_result *result, + const git_describe_format_options *given) +{ + int error; + git_repository *repo; + struct commit_name *name; + git_describe_format_options opts; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(result); + + GIT_ERROR_CHECK_VERSION(given, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION, "git_describe_format_options"); + normalize_format_options(&opts, given); + + if (opts.always_use_long_format && opts.abbreviated_size == 0) { + git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - " + "'always_use_long_format' is incompatible with a zero" + "'abbreviated_size'"); + return -1; + } + + + repo = result->repo; + + /* If we did find an exact match, then it's the easier method */ + if (result->exact_match) { + name = result->name; + if ((error = display_name(out, repo, name)) < 0) + return error; + + if (opts.always_use_long_format) { + const git_oid *id = name->tag ? git_tag_target_id(name->tag) : &result->commit_id; + if ((error = show_suffix(out, 0, repo, id, opts.abbreviated_size)) < 0) + return error; + } + + if (result->dirty && opts.dirty_suffix) + git_str_puts(out, opts.dirty_suffix); + + return git_str_oom(out) ? -1 : 0; + } + + /* If we didn't find *any* tags, we fall back to the commit's id */ + if (result->fallback_to_id) { + char hex_oid[GIT_OID_HEXSZ + 1] = {0}; + int size = 0; + + if ((error = find_unique_abbrev_size( + &size, repo, &result->commit_id, opts.abbreviated_size)) < 0) + return -1; + + git_oid_fmt(hex_oid, &result->commit_id); + git_str_put(out, hex_oid, size); + + if (result->dirty && opts.dirty_suffix) + git_str_puts(out, opts.dirty_suffix); + + return git_str_oom(out) ? -1 : 0; + } + + /* Lastly, if we found a matching tag, we show that */ + name = result->tag->name; + + if ((error = display_name(out, repo, name)) < 0) + return error; + + if (opts.abbreviated_size) { + if ((error = show_suffix(out, result->tag->depth, repo, + &result->commit_id, opts.abbreviated_size)) < 0) + return error; + } + + if (result->dirty && opts.dirty_suffix) { + git_str_puts(out, opts.dirty_suffix); + } + + return git_str_oom(out) ? -1 : 0; +} + +int git_describe_format( + git_buf *out, + const git_describe_result *result, + const git_describe_format_options *given) +{ + GIT_BUF_WRAP_PRIVATE(out, git_describe__format, result, given); +} + +void git_describe_result_free(git_describe_result *result) +{ + if (result == NULL) + return; + + if (result->name) { + git_tag_free(result->name->tag); + git__free(result->name->path); + git__free(result->name); + } + + if (result->tag) { + git_tag_free(result->tag->name->tag); + git__free(result->tag->name->path); + git__free(result->tag->name); + git__free(result->tag); + } + + git__free(result); +} + +int git_describe_options_init(git_describe_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_describe_options, GIT_DESCRIBE_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_describe_init_options(git_describe_options *opts, unsigned int version) +{ + return git_describe_options_init(opts, version); +} +#endif + +int git_describe_format_options_init(git_describe_format_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_describe_format_options, GIT_DESCRIBE_FORMAT_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_describe_init_format_options(git_describe_format_options *opts, unsigned int version) +{ + return git_describe_format_options_init(opts, version); +} +#endif diff --git a/src/libgit2/diff.c b/src/libgit2/diff.c new file mode 100644 index 000000000..9840d6050 --- /dev/null +++ b/src/libgit2/diff.c @@ -0,0 +1,389 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "diff.h" + +#include "common.h" +#include "buf.h" +#include "patch.h" +#include "email.h" +#include "commit.h" +#include "index.h" +#include "diff_generate.h" + +#include "git2/version.h" +#include "git2/email.h" + +struct patch_id_args { + git_hash_ctx ctx; + git_oid result; + int first_file; +}; + +GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta) +{ + const char *str = delta->old_file.path; + + if (!str || + delta->status == GIT_DELTA_ADDED || + delta->status == GIT_DELTA_RENAMED || + delta->status == GIT_DELTA_COPIED) + str = delta->new_file.path; + + return str; +} + +int git_diff_delta__cmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcmp(diff_delta__path(da), diff_delta__path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + +int git_diff_delta__casecmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcasecmp(diff_delta__path(da), diff_delta__path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + +int git_diff__entry_cmp(const void *a, const void *b) +{ + const git_index_entry *entry_a = a; + const git_index_entry *entry_b = b; + + return strcmp(entry_a->path, entry_b->path); +} + +int git_diff__entry_icmp(const void *a, const void *b) +{ + const git_index_entry *entry_a = a; + const git_index_entry *entry_b = b; + + return strcasecmp(entry_a->path, entry_b->path); +} + +void git_diff_free(git_diff *diff) +{ + if (!diff) + return; + + GIT_REFCOUNT_DEC(diff, diff->free_fn); +} + +void git_diff_addref(git_diff *diff) +{ + GIT_REFCOUNT_INC(diff); +} + +size_t git_diff_num_deltas(const git_diff *diff) +{ + GIT_ASSERT_ARG(diff); + return diff->deltas.length; +} + +size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type) +{ + size_t i, count = 0; + const git_diff_delta *delta; + + GIT_ASSERT_ARG(diff); + + git_vector_foreach(&diff->deltas, i, delta) { + count += (delta->status == type); + } + + return count; +} + +const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx) +{ + GIT_ASSERT_ARG_WITH_RETVAL(diff, NULL); + return git_vector_get(&diff->deltas, idx); +} + +int git_diff_is_sorted_icase(const git_diff *diff) +{ + return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0; +} + +int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff) +{ + GIT_ASSERT_ARG(out); + GIT_ERROR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata"); + out->stat_calls = diff->perf.stat_calls; + out->oid_calculations = diff->perf.oid_calculations; + return 0; +} + +int git_diff_foreach( + git_diff *diff, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb data_cb, + void *payload) +{ + int error = 0; + git_diff_delta *delta; + size_t idx; + + GIT_ASSERT_ARG(diff); + + git_vector_foreach(&diff->deltas, idx, delta) { + git_patch *patch; + + /* check flags against patch status */ + if (git_diff_delta__should_skip(&diff->opts, delta)) + continue; + + if ((error = git_patch_from_diff(&patch, diff, idx)) != 0) + break; + + error = git_patch__invoke_callbacks(patch, file_cb, binary_cb, + hunk_cb, data_cb, payload); + git_patch_free(patch); + + if (error) + break; + } + + return error; +} + +#ifndef GIT_DEPRECATE_HARD + +int git_diff_format_email( + git_buf *out, + git_diff *diff, + const git_diff_format_email_options *opts) +{ + git_email_create_options email_create_opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + git_str email = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diff); + GIT_ASSERT_ARG(opts && opts->summary && opts->id && opts->author); + + GIT_ERROR_CHECK_VERSION(opts, + GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, + "git_format_email_options"); + + /* This is a `git_buf` special case; subsequent calls append. */ + email.ptr = out->ptr; + email.asize = out->reserved; + email.size = out->size; + + out->ptr = git_str__initstr; + out->reserved = 0; + out->size = 0; + + if ((opts->flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0) + email_create_opts.subject_prefix = ""; + + error = git_email__append_from_diff(&email, diff, opts->patch_no, + opts->total_patches, opts->id, opts->summary, opts->body, + opts->author, &email_create_opts); + + if (error < 0) + goto done; + + error = git_buf_fromstr(out, &email); + +done: + git_str_dispose(&email); + return error; +} + +int git_diff_commit_as_email( + git_buf *out, + git_repository *repo, + git_commit *commit, + size_t patch_no, + size_t total_patches, + uint32_t flags, + const git_diff_options *diff_opts) +{ + git_diff *diff = NULL; + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + const git_oid *commit_id; + const char *summary, *body; + const git_signature *author; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(commit); + + commit_id = git_commit_id(commit); + summary = git_commit_summary(commit); + body = git_commit_body(commit); + author = git_commit_author(commit); + + if ((flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0) + opts.subject_prefix = ""; + + if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0) + return error; + + error = git_email_create_from_diff(out, diff, patch_no, total_patches, commit_id, summary, body, author, &opts); + + git_diff_free(diff); + return error; +} + +int git_diff_init_options(git_diff_options *opts, unsigned int version) +{ + return git_diff_options_init(opts, version); +} + +int git_diff_find_init_options( + git_diff_find_options *opts, unsigned int version) +{ + return git_diff_find_options_init(opts, version); +} + +int git_diff_format_email_options_init( + git_diff_format_email_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_diff_format_email_options, + GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT); + return 0; +} + +int git_diff_format_email_init_options( + git_diff_format_email_options *opts, unsigned int version) +{ + return git_diff_format_email_options_init(opts, version); +} + +#endif + +int git_diff_options_init(git_diff_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT); + return 0; +} + +int git_diff_find_options_init( + git_diff_find_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT); + return 0; +} + +static int flush_hunk(git_oid *result, git_hash_ctx *ctx) +{ + git_oid hash; + unsigned short carry = 0; + int error, i; + + if ((error = git_hash_final(hash.id, ctx)) < 0 || + (error = git_hash_init(ctx)) < 0) + return error; + + for (i = 0; i < GIT_OID_RAWSZ; i++) { + carry += result->id[i] + hash.id[i]; + result->id[i] = (unsigned char)carry; + carry >>= 8; + } + + return 0; +} + +static void strip_spaces(git_str *buf) +{ + char *src = buf->ptr, *dst = buf->ptr; + char c; + size_t len = 0; + + while ((c = *src++) != '\0') { + if (!git__isspace(c)) { + *dst++ = c; + len++; + } + } + + git_str_truncate(buf, len); +} + +static int diff_patchid_print_callback_to_buf( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + struct patch_id_args *args = (struct patch_id_args *) payload; + git_str buf = GIT_STR_INIT; + int error = 0; + + if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL || + line->origin == GIT_DIFF_LINE_ADD_EOFNL || + line->origin == GIT_DIFF_LINE_DEL_EOFNL) + goto out; + + if ((error = git_diff_print_callback__to_buf(delta, hunk, + line, &buf)) < 0) + goto out; + + strip_spaces(&buf); + + if (line->origin == GIT_DIFF_LINE_FILE_HDR && + !args->first_file && + (error = flush_hunk(&args->result, &args->ctx) < 0)) + goto out; + + if ((error = git_hash_update(&args->ctx, buf.ptr, buf.size)) < 0) + goto out; + + if (line->origin == GIT_DIFF_LINE_FILE_HDR && args->first_file) + args->first_file = 0; + +out: + git_str_dispose(&buf); + return error; +} + +int git_diff_patchid_options_init(git_diff_patchid_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_diff_patchid_options, GIT_DIFF_PATCHID_OPTIONS_INIT); + return 0; +} + +int git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opts) +{ + struct patch_id_args args; + int error; + + GIT_ERROR_CHECK_VERSION( + opts, GIT_DIFF_PATCHID_OPTIONS_VERSION, "git_diff_patchid_options"); + + memset(&args, 0, sizeof(args)); + args.first_file = 1; + if ((error = git_hash_ctx_init(&args.ctx, GIT_HASH_ALGORITHM_SHA1)) < 0) + goto out; + + if ((error = git_diff_print(diff, + GIT_DIFF_FORMAT_PATCH_ID, + diff_patchid_print_callback_to_buf, + &args)) < 0) + goto out; + + if ((error = (flush_hunk(&args.result, &args.ctx))) < 0) + goto out; + + git_oid_cpy(out, &args.result); + +out: + git_hash_ctx_cleanup(&args.ctx); + return error; +} diff --git a/src/libgit2/diff.h b/src/libgit2/diff.h new file mode 100644 index 000000000..2cc35e65b --- /dev/null +++ b/src/libgit2/diff.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_diff_h__ +#define INCLUDE_diff_h__ + +#include "common.h" + +#include "git2/diff.h" +#include "git2/patch.h" +#include "git2/sys/diff.h" +#include "git2/oid.h" + +#include "vector.h" +#include "iterator.h" +#include "repository.h" +#include "pool.h" +#include "odb.h" + +#define DIFF_OLD_PREFIX_DEFAULT "a/" +#define DIFF_NEW_PREFIX_DEFAULT "b/" + +typedef enum { + GIT_DIFF_TYPE_UNKNOWN = 0, + GIT_DIFF_TYPE_GENERATED = 1, + GIT_DIFF_TYPE_PARSED = 2 +} git_diff_origin_t; + +struct git_diff { + git_refcount rc; + git_repository *repo; + git_attr_session attrsession; + git_diff_origin_t type; + git_diff_options opts; + git_vector deltas; /* vector of git_diff_delta */ + git_pool pool; + git_iterator_t old_src; + git_iterator_t new_src; + git_diff_perfdata perf; + + int (*strcomp)(const char *, const char *); + int (*strncomp)(const char *, const char *, size_t); + int (*pfxcomp)(const char *str, const char *pfx); + int (*entrycomp)(const void *a, const void *b); + + int (*patch_fn)(git_patch **out, git_diff *diff, size_t idx); + void (*free_fn)(git_diff *diff); +}; + +extern int git_diff_delta__format_file_header( + git_str *out, + const git_diff_delta *delta, + const char *oldpfx, + const char *newpfx, + int oid_strlen, + bool print_index); + +extern int git_diff_delta__cmp(const void *a, const void *b); +extern int git_diff_delta__casecmp(const void *a, const void *b); + +extern int git_diff__entry_cmp(const void *a, const void *b); +extern int git_diff__entry_icmp(const void *a, const void *b); + +#endif diff --git a/src/libgit2/diff_driver.c b/src/libgit2/diff_driver.c new file mode 100644 index 000000000..5f25fdb44 --- /dev/null +++ b/src/libgit2/diff_driver.c @@ -0,0 +1,522 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "diff_driver.h" + +#include "git2/attr.h" + +#include "common.h" +#include "diff.h" +#include "strmap.h" +#include "map.h" +#include "config.h" +#include "regexp.h" +#include "repository.h" + +typedef enum { + DIFF_DRIVER_AUTO = 0, + DIFF_DRIVER_BINARY = 1, + DIFF_DRIVER_TEXT = 2, + DIFF_DRIVER_PATTERNLIST = 3 +} git_diff_driver_t; + +typedef struct { + git_regexp re; + int flags; +} git_diff_driver_pattern; + +enum { + REG_NEGATE = (1 << 15) /* get out of the way of existing flags */ +}; + +/* data for finding function context for a given file type */ +struct git_diff_driver { + git_diff_driver_t type; + uint32_t binary_flags; + uint32_t other_flags; + git_array_t(git_diff_driver_pattern) fn_patterns; + git_regexp word_pattern; + char name[GIT_FLEX_ARRAY]; +}; + +#include "userdiff.h" + +struct git_diff_driver_registry { + git_strmap *drivers; +}; + +#define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY) + +static git_diff_driver diff_driver_auto = { DIFF_DRIVER_AUTO, 0, 0 }; +static git_diff_driver diff_driver_binary = { DIFF_DRIVER_BINARY, GIT_DIFF_FORCE_BINARY, 0 }; +static git_diff_driver diff_driver_text = { DIFF_DRIVER_TEXT, GIT_DIFF_FORCE_TEXT, 0 }; + +git_diff_driver_registry *git_diff_driver_registry_new(void) +{ + git_diff_driver_registry *reg = + git__calloc(1, sizeof(git_diff_driver_registry)); + if (!reg) + return NULL; + + if (git_strmap_new(®->drivers) < 0) { + git_diff_driver_registry_free(reg); + return NULL; + } + + return reg; +} + +void git_diff_driver_registry_free(git_diff_driver_registry *reg) +{ + git_diff_driver *drv; + + if (!reg) + return; + + git_strmap_foreach_value(reg->drivers, drv, git_diff_driver_free(drv)); + git_strmap_free(reg->drivers); + git__free(reg); +} + +static int diff_driver_add_patterns( + git_diff_driver *drv, const char *regex_str, int regex_flags) +{ + int error = 0; + const char *scan, *end; + git_diff_driver_pattern *pat = NULL; + git_str buf = GIT_STR_INIT; + + for (scan = regex_str; scan; scan = end) { + /* get pattern to fill in */ + if ((pat = git_array_alloc(drv->fn_patterns)) == NULL) { + return -1; + } + + pat->flags = regex_flags; + if (*scan == '!') { + pat->flags |= REG_NEGATE; + ++scan; + } + + if ((end = strchr(scan, '\n')) != NULL) { + error = git_str_set(&buf, scan, end - scan); + end++; + } else { + error = git_str_sets(&buf, scan); + } + if (error < 0) + break; + + if ((error = git_regexp_compile(&pat->re, buf.ptr, regex_flags)) != 0) { + /* + * TODO: issue a warning + */ + } + } + + if (error && pat != NULL) + (void)git_array_pop(drv->fn_patterns); /* release last item */ + git_str_dispose(&buf); + + /* We want to ignore bad patterns, so return success regardless */ + return 0; +} + +static int diff_driver_xfuncname(const git_config_entry *entry, void *payload) +{ + return diff_driver_add_patterns(payload, entry->value, 0); +} + +static int diff_driver_funcname(const git_config_entry *entry, void *payload) +{ + return diff_driver_add_patterns(payload, entry->value, 0); +} + +static git_diff_driver_registry *git_repository_driver_registry( + git_repository *repo) +{ + git_diff_driver_registry *reg = git_atomic_load(repo->diff_drivers), *newreg; + if (reg) + return reg; + + newreg = git_diff_driver_registry_new(); + if (!newreg) { + git_error_set(GIT_ERROR_REPOSITORY, "unable to create diff driver registry"); + return newreg; + } + reg = git_atomic_compare_and_swap(&repo->diff_drivers, NULL, newreg); + if (!reg) { + reg = newreg; + } else { + /* if we race, free losing allocation */ + git_diff_driver_registry_free(newreg); + } + return reg; +} + +static int diff_driver_alloc( + git_diff_driver **out, size_t *namelen_out, const char *name) +{ + git_diff_driver *driver; + size_t driverlen = sizeof(git_diff_driver), + namelen = strlen(name), + alloclen; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, driverlen, namelen); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + + driver = git__calloc(1, alloclen); + GIT_ERROR_CHECK_ALLOC(driver); + + memcpy(driver->name, name, namelen); + + *out = driver; + + if (namelen_out) + *namelen_out = namelen; + + return 0; +} + +static int git_diff_driver_builtin( + git_diff_driver **out, + git_diff_driver_registry *reg, + const char *driver_name) +{ + git_diff_driver_definition *ddef = NULL; + git_diff_driver *drv = NULL; + int error = 0; + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) { + if (!strcasecmp(driver_name, builtin_defs[idx].name)) { + ddef = &builtin_defs[idx]; + break; + } + } + if (!ddef) + goto done; + + if ((error = diff_driver_alloc(&drv, NULL, ddef->name)) < 0) + goto done; + + drv->type = DIFF_DRIVER_PATTERNLIST; + + if (ddef->fns && + (error = diff_driver_add_patterns( + drv, ddef->fns, ddef->flags)) < 0) + goto done; + + if (ddef->words && + (error = git_regexp_compile(&drv->word_pattern, ddef->words, ddef->flags)) < 0) + goto done; + + if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0) + goto done; + +done: + if (error && drv) + git_diff_driver_free(drv); + else + *out = drv; + + return error; +} + +static int git_diff_driver_load( + git_diff_driver **out, git_repository *repo, const char *driver_name) +{ + int error = 0; + git_diff_driver_registry *reg; + git_diff_driver *drv; + size_t namelen; + git_config *cfg = NULL; + git_str name = GIT_STR_INIT; + git_config_entry *ce = NULL; + bool found_driver = false; + + if ((reg = git_repository_driver_registry(repo)) == NULL) + return -1; + + if ((drv = git_strmap_get(reg->drivers, driver_name)) != NULL) { + *out = drv; + return 0; + } + + if ((error = diff_driver_alloc(&drv, &namelen, driver_name)) < 0) + goto done; + + drv->type = DIFF_DRIVER_AUTO; + + /* if you can't read config for repo, just use default driver */ + if (git_repository_config_snapshot(&cfg, repo) < 0) { + git_error_clear(); + goto done; + } + + if ((error = git_str_printf(&name, "diff.%s.binary", driver_name)) < 0) + goto done; + + switch (git_config__get_bool_force(cfg, name.ptr, -1)) { + case true: + /* if diff..binary is true, just return the binary driver */ + *out = &diff_driver_binary; + goto done; + case false: + /* if diff..binary is false, force binary checks off */ + /* but still may have custom function context patterns, etc. */ + drv->binary_flags = GIT_DIFF_FORCE_TEXT; + found_driver = true; + break; + default: + /* diff..binary unspecified or "auto", so just continue */ + break; + } + + /* TODO: warn if diff..command or diff..textconv are set */ + + git_str_truncate(&name, namelen + strlen("diff..")); + if ((error = git_str_PUTS(&name, "xfuncname")) < 0) + goto done; + + if ((error = git_config_get_multivar_foreach( + cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) { + if (error != GIT_ENOTFOUND) + goto done; + git_error_clear(); /* no diff..xfuncname, so just continue */ + } + + git_str_truncate(&name, namelen + strlen("diff..")); + if ((error = git_str_PUTS(&name, "funcname")) < 0) + goto done; + + if ((error = git_config_get_multivar_foreach( + cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) { + if (error != GIT_ENOTFOUND) + goto done; + git_error_clear(); /* no diff..funcname, so just continue */ + } + + /* if we found any patterns, set driver type to use correct callback */ + if (git_array_size(drv->fn_patterns) > 0) { + drv->type = DIFF_DRIVER_PATTERNLIST; + found_driver = true; + } + + git_str_truncate(&name, namelen + strlen("diff..")); + if ((error = git_str_PUTS(&name, "wordregex")) < 0) + goto done; + + if ((error = git_config__lookup_entry(&ce, cfg, name.ptr, false)) < 0) + goto done; + if (!ce || !ce->value) + /* no diff..wordregex, so just continue */; + else if (!(error = git_regexp_compile(&drv->word_pattern, ce->value, 0))) + found_driver = true; + else { + /* TODO: warn about bad regex instead of failure */ + goto done; + } + + /* TODO: look up diff..algorithm to turn on minimal / patience + * diff in drv->other_flags + */ + + /* if no driver config found at all, fall back on AUTO driver */ + if (!found_driver) + goto done; + + /* store driver in registry */ + if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0) + goto done; + + *out = drv; + +done: + git_config_entry_free(ce); + git_str_dispose(&name); + git_config_free(cfg); + + if (!*out) { + int error2 = git_diff_driver_builtin(out, reg, driver_name); + if (!error) + error = error2; + } + + if (drv && drv != *out) + git_diff_driver_free(drv); + + return error; +} + +int git_diff_driver_lookup( + git_diff_driver **out, git_repository *repo, + git_attr_session *attrsession, const char *path) +{ + int error = 0; + const char *values[1], *attrs[] = { "diff" }; + + GIT_ASSERT_ARG(out); + *out = NULL; + + if (!repo || !path || !strlen(path)) + /* just use the auto value */; + else if ((error = git_attr_get_many_with_session(values, repo, + attrsession, 0, path, 1, attrs)) < 0) + /* return error below */; + + else if (GIT_ATTR_IS_UNSPECIFIED(values[0])) + /* just use the auto value */; + else if (GIT_ATTR_IS_FALSE(values[0])) + *out = &diff_driver_binary; + else if (GIT_ATTR_IS_TRUE(values[0])) + *out = &diff_driver_text; + + /* otherwise look for driver information in config and build driver */ + else if ((error = git_diff_driver_load(out, repo, values[0])) < 0) { + if (error == GIT_ENOTFOUND) { + error = 0; + git_error_clear(); + } + } + + if (!*out) + *out = &diff_driver_auto; + + return error; +} + +void git_diff_driver_free(git_diff_driver *driver) +{ + git_diff_driver_pattern *pat; + + if (!driver) + return; + + while ((pat = git_array_pop(driver->fn_patterns)) != NULL) + git_regexp_dispose(&pat->re); + git_array_clear(driver->fn_patterns); + + git_regexp_dispose(&driver->word_pattern); + + git__free(driver); +} + +void git_diff_driver_update_options( + uint32_t *option_flags, git_diff_driver *driver) +{ + if ((*option_flags & FORCE_DIFFABLE) == 0) + *option_flags |= driver->binary_flags; + + *option_flags |= driver->other_flags; +} + +int git_diff_driver_content_is_binary( + git_diff_driver *driver, const char *content, size_t content_len) +{ + git_str search = GIT_STR_INIT; + + GIT_UNUSED(driver); + + git_str_attach_notowned(&search, content, + min(content_len, GIT_FILTER_BYTES_TO_CHECK_NUL)); + + /* TODO: provide encoding / binary detection callbacks that can + * be UTF-8 aware, etc. For now, instead of trying to be smart, + * let's just use the simple NUL-byte detection that core git uses. + */ + + /* previously was: if (git_str_is_binary(&search)) */ + if (git_str_contains_nul(&search)) + return 1; + + return 0; +} + +static int diff_context_line__simple( + git_diff_driver *driver, git_str *line) +{ + char firstch = line->ptr[0]; + GIT_UNUSED(driver); + return (git__isalpha(firstch) || firstch == '_' || firstch == '$'); +} + +static int diff_context_line__pattern_match( + git_diff_driver *driver, git_str *line) +{ + size_t i, maxi = git_array_size(driver->fn_patterns); + git_regmatch pmatch[2]; + + for (i = 0; i < maxi; ++i) { + git_diff_driver_pattern *pat = git_array_get(driver->fn_patterns, i); + + if (!git_regexp_search(&pat->re, line->ptr, 2, pmatch)) { + if (pat->flags & REG_NEGATE) + return false; + + /* use pmatch data to trim line data */ + i = (pmatch[1].start >= 0) ? 1 : 0; + git_str_consume(line, git_str_cstr(line) + pmatch[i].start); + git_str_truncate(line, pmatch[i].end - pmatch[i].start); + git_str_rtrim(line); + + return true; + } + } + + return false; +} + +static long diff_context_find( + const char *line, + long line_len, + char *out, + long out_size, + void *payload) +{ + git_diff_find_context_payload *ctxt = payload; + + if (git_str_set(&ctxt->line, line, (size_t)line_len) < 0) + return -1; + git_str_rtrim(&ctxt->line); + + if (!ctxt->line.size) + return -1; + + if (!ctxt->match_line || !ctxt->match_line(ctxt->driver, &ctxt->line)) + return -1; + + if (out_size > (long)ctxt->line.size) + out_size = (long)ctxt->line.size; + memcpy(out, ctxt->line.ptr, (size_t)out_size); + + return out_size; +} + +void git_diff_find_context_init( + git_diff_find_context_fn *findfn_out, + git_diff_find_context_payload *payload_out, + git_diff_driver *driver) +{ + *findfn_out = driver ? diff_context_find : NULL; + + memset(payload_out, 0, sizeof(*payload_out)); + if (driver) { + payload_out->driver = driver; + payload_out->match_line = (driver->type == DIFF_DRIVER_PATTERNLIST) ? + diff_context_line__pattern_match : diff_context_line__simple; + git_str_init(&payload_out->line, 0); + } +} + +void git_diff_find_context_clear(git_diff_find_context_payload *payload) +{ + if (payload) { + git_str_dispose(&payload->line); + payload->driver = NULL; + } +} diff --git a/src/libgit2/diff_driver.h b/src/libgit2/diff_driver.h new file mode 100644 index 000000000..03711e89e --- /dev/null +++ b/src/libgit2/diff_driver.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_diff_driver_h__ +#define INCLUDE_diff_driver_h__ + +#include "common.h" + +#include "attr_file.h" +#include "str.h" + +typedef struct git_diff_driver_registry git_diff_driver_registry; + +git_diff_driver_registry *git_diff_driver_registry_new(void); +void git_diff_driver_registry_free(git_diff_driver_registry *); + +typedef struct git_diff_driver git_diff_driver; + +int git_diff_driver_lookup(git_diff_driver **, git_repository *, + git_attr_session *attrsession, const char *); +void git_diff_driver_free(git_diff_driver *); + +/* diff option flags to force off and on for this driver */ +void git_diff_driver_update_options(uint32_t *option_flags, git_diff_driver *); + +/* returns -1 meaning "unknown", 0 meaning not binary, 1 meaning binary */ +int git_diff_driver_content_is_binary( + git_diff_driver *, const char *content, size_t content_len); + +typedef long (*git_diff_find_context_fn)( + const char *, long, char *, long, void *); + +typedef int (*git_diff_find_context_line)( + git_diff_driver *, git_str *); + +typedef struct { + git_diff_driver *driver; + git_diff_find_context_line match_line; + git_str line; +} git_diff_find_context_payload; + +void git_diff_find_context_init( + git_diff_find_context_fn *findfn_out, + git_diff_find_context_payload *payload_out, + git_diff_driver *driver); + +void git_diff_find_context_clear(git_diff_find_context_payload *); + +#endif diff --git a/src/libgit2/diff_file.c b/src/libgit2/diff_file.c new file mode 100644 index 000000000..c7e9fbeee --- /dev/null +++ b/src/libgit2/diff_file.c @@ -0,0 +1,481 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "diff_file.h" + +#include "git2/blob.h" +#include "git2/submodule.h" +#include "diff.h" +#include "diff_generate.h" +#include "odb.h" +#include "futils.h" +#include "filter.h" + +#define DIFF_MAX_FILESIZE 0x20000000 + +static bool diff_file_content_binary_by_size(git_diff_file_content *fc) +{ + /* if we have diff opts, check max_size vs file size */ + if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) == 0 && + fc->opts_max_size > 0 && + fc->file->size > fc->opts_max_size) + fc->file->flags |= GIT_DIFF_FLAG_BINARY; + + return ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0); +} + +static void diff_file_content_binary_by_content(git_diff_file_content *fc) +{ + if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) + return; + + switch (git_diff_driver_content_is_binary( + fc->driver, fc->map.data, fc->map.len)) { + case 0: fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; break; + case 1: fc->file->flags |= GIT_DIFF_FLAG_BINARY; break; + default: break; + } +} + +static int diff_file_content_init_common( + git_diff_file_content *fc, const git_diff_options *opts) +{ + fc->opts_flags = opts ? opts->flags : GIT_DIFF_NORMAL; + + if (opts && opts->max_size >= 0) + fc->opts_max_size = opts->max_size ? + opts->max_size : DIFF_MAX_FILESIZE; + + if (fc->src == GIT_ITERATOR_EMPTY) + fc->src = GIT_ITERATOR_TREE; + + if (!fc->driver && + git_diff_driver_lookup(&fc->driver, fc->repo, + NULL, fc->file->path) < 0) + return -1; + + /* give driver a chance to modify options */ + git_diff_driver_update_options(&fc->opts_flags, fc->driver); + + /* make sure file is conceivable mmap-able */ + if ((size_t)fc->file->size != fc->file->size) + fc->file->flags |= GIT_DIFF_FLAG_BINARY; + /* check if user is forcing text diff the file */ + else if (fc->opts_flags & GIT_DIFF_FORCE_TEXT) { + fc->file->flags &= ~GIT_DIFF_FLAG_BINARY; + fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; + } + /* check if user is forcing binary diff the file */ + else if (fc->opts_flags & GIT_DIFF_FORCE_BINARY) { + fc->file->flags &= ~GIT_DIFF_FLAG_NOT_BINARY; + fc->file->flags |= GIT_DIFF_FLAG_BINARY; + } + + diff_file_content_binary_by_size(fc); + + if ((fc->flags & GIT_DIFF_FLAG__NO_DATA) != 0) { + fc->flags |= GIT_DIFF_FLAG__LOADED; + fc->map.len = 0; + fc->map.data = ""; + } + + if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0) + diff_file_content_binary_by_content(fc); + + return 0; +} + +int git_diff_file_content__init_from_diff( + git_diff_file_content *fc, + git_diff *diff, + git_diff_delta *delta, + bool use_old) +{ + bool has_data = true; + + memset(fc, 0, sizeof(*fc)); + fc->repo = diff->repo; + fc->file = use_old ? &delta->old_file : &delta->new_file; + fc->src = use_old ? diff->old_src : diff->new_src; + + if (git_diff_driver_lookup(&fc->driver, fc->repo, + &diff->attrsession, fc->file->path) < 0) + return -1; + + switch (delta->status) { + case GIT_DELTA_ADDED: + has_data = !use_old; break; + case GIT_DELTA_DELETED: + has_data = use_old; break; + case GIT_DELTA_UNTRACKED: + has_data = !use_old && + (diff->opts.flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) != 0; + break; + case GIT_DELTA_UNREADABLE: + case GIT_DELTA_MODIFIED: + case GIT_DELTA_COPIED: + case GIT_DELTA_RENAMED: + break; + default: + has_data = false; + break; + } + + if (!has_data) + fc->flags |= GIT_DIFF_FLAG__NO_DATA; + + return diff_file_content_init_common(fc, &diff->opts); +} + +int git_diff_file_content__init_from_src( + git_diff_file_content *fc, + git_repository *repo, + const git_diff_options *opts, + const git_diff_file_content_src *src, + git_diff_file *as_file) +{ + memset(fc, 0, sizeof(*fc)); + fc->repo = repo; + fc->file = as_file; + + if (!src->blob && !src->buf) { + fc->flags |= GIT_DIFF_FLAG__NO_DATA; + } else { + fc->flags |= GIT_DIFF_FLAG__LOADED; + fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; + fc->file->mode = GIT_FILEMODE_BLOB; + + if (src->blob) { + git_blob_dup((git_blob **)&fc->blob, (git_blob *) src->blob); + fc->file->size = git_blob_rawsize(src->blob); + git_oid_cpy(&fc->file->id, git_blob_id(src->blob)); + fc->file->id_abbrev = GIT_OID_HEXSZ; + + fc->map.len = (size_t)fc->file->size; + fc->map.data = (char *)git_blob_rawcontent(src->blob); + + fc->flags |= GIT_DIFF_FLAG__FREE_BLOB; + } else { + int error; + if ((error = git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJECT_BLOB)) < 0) + return error; + fc->file->size = src->buflen; + fc->file->id_abbrev = GIT_OID_HEXSZ; + + fc->map.len = src->buflen; + fc->map.data = (char *)src->buf; + } + } + + return diff_file_content_init_common(fc, opts); +} + +static int diff_file_content_commit_to_str( + git_diff_file_content *fc, bool check_status) +{ + char oid[GIT_OID_HEXSZ+1]; + git_str content = GIT_STR_INIT; + const char *status = ""; + + if (check_status) { + int error = 0; + git_submodule *sm = NULL; + unsigned int sm_status = 0; + const git_oid *sm_head; + + if ((error = git_submodule_lookup(&sm, fc->repo, fc->file->path)) < 0) { + /* GIT_EEXISTS means a "submodule" that has not been git added */ + if (error == GIT_EEXISTS) { + git_error_clear(); + error = 0; + } + return error; + } + + if ((error = git_submodule_status(&sm_status, fc->repo, fc->file->path, GIT_SUBMODULE_IGNORE_UNSPECIFIED)) < 0) { + git_submodule_free(sm); + return error; + } + + /* update OID if we didn't have it previously */ + if ((fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0 && + ((sm_head = git_submodule_wd_id(sm)) != NULL || + (sm_head = git_submodule_head_id(sm)) != NULL)) + { + git_oid_cpy(&fc->file->id, sm_head); + fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; + } + + if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) + status = "-dirty"; + + git_submodule_free(sm); + } + + git_oid_tostr(oid, sizeof(oid), &fc->file->id); + if (git_str_printf(&content, "Subproject commit %s%s\n", oid, status) < 0) + return -1; + + fc->map.len = git_str_len(&content); + fc->map.data = git_str_detach(&content); + fc->flags |= GIT_DIFF_FLAG__FREE_DATA; + + return 0; +} + +static int diff_file_content_load_blob( + git_diff_file_content *fc, + git_diff_options *opts) +{ + int error = 0; + git_odb_object *odb_obj = NULL; + + if (git_oid_is_zero(&fc->file->id)) + return 0; + + if (fc->file->mode == GIT_FILEMODE_COMMIT) + return diff_file_content_commit_to_str(fc, false); + + /* if we don't know size, try to peek at object header first */ + if (!fc->file->size) { + if ((error = git_diff_file__resolve_zero_size( + fc->file, &odb_obj, fc->repo)) < 0) + return error; + } + + if ((opts->flags & GIT_DIFF_SHOW_BINARY) == 0 && + diff_file_content_binary_by_size(fc)) + return 0; + + if (odb_obj != NULL) { + error = git_object__from_odb_object( + (git_object **)&fc->blob, fc->repo, odb_obj, GIT_OBJECT_BLOB); + git_odb_object_free(odb_obj); + } else { + error = git_blob_lookup( + (git_blob **)&fc->blob, fc->repo, &fc->file->id); + } + + if (!error) { + fc->flags |= GIT_DIFF_FLAG__FREE_BLOB; + fc->map.data = (void *)git_blob_rawcontent(fc->blob); + fc->map.len = (size_t)git_blob_rawsize(fc->blob); + } + + return error; +} + +static int diff_file_content_load_workdir_symlink_fake( + git_diff_file_content *fc, git_str *path) +{ + git_str target = GIT_STR_INIT; + int error; + + if ((error = git_futils_readbuffer(&target, path->ptr)) < 0) + return error; + + fc->map.len = git_str_len(&target); + fc->map.data = git_str_detach(&target); + fc->flags |= GIT_DIFF_FLAG__FREE_DATA; + + git_str_dispose(&target); + return error; +} + +static int diff_file_content_load_workdir_symlink( + git_diff_file_content *fc, git_str *path) +{ + ssize_t alloc_len, read_len; + int symlink_supported, error; + + if ((error = git_repository__configmap_lookup( + &symlink_supported, fc->repo, GIT_CONFIGMAP_SYMLINKS)) < 0) + return -1; + + if (!symlink_supported) + return diff_file_content_load_workdir_symlink_fake(fc, path); + + /* link path on disk could be UTF-16, so prepare a buffer that is + * big enough to handle some UTF-8 data expansion + */ + alloc_len = (ssize_t)(fc->file->size * 2) + 1; + + fc->map.data = git__calloc(alloc_len, sizeof(char)); + GIT_ERROR_CHECK_ALLOC(fc->map.data); + + fc->flags |= GIT_DIFF_FLAG__FREE_DATA; + + read_len = p_readlink(git_str_cstr(path), fc->map.data, alloc_len); + if (read_len < 0) { + git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", fc->file->path); + return -1; + } + + fc->map.len = read_len; + return 0; +} + +static int diff_file_content_load_workdir_file( + git_diff_file_content *fc, + git_str *path, + git_diff_options *diff_opts) +{ + int error = 0; + git_filter_list *fl = NULL; + git_file fd = git_futils_open_ro(git_str_cstr(path)); + git_str raw = GIT_STR_INIT; + git_object_size_t new_file_size = 0; + + if (fd < 0) + return fd; + + error = git_futils_filesize(&new_file_size, fd); + + if (error < 0) + goto cleanup; + + if (!(fc->file->flags & GIT_DIFF_FLAG_VALID_SIZE)) { + fc->file->size = new_file_size; + fc->file->flags |= GIT_DIFF_FLAG_VALID_SIZE; + } else if (fc->file->size != new_file_size) { + git_error_set(GIT_ERROR_FILESYSTEM, "file changed before we could read it"); + error = -1; + goto cleanup; + } + + if ((diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0 && + diff_file_content_binary_by_size(fc)) + goto cleanup; + + if ((error = git_filter_list_load( + &fl, fc->repo, NULL, fc->file->path, + GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)) < 0) + goto cleanup; + + /* if there are no filters, try to mmap the file */ + if (fl == NULL) { + if (!(error = git_futils_mmap_ro( + &fc->map, fd, 0, (size_t)fc->file->size))) { + fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA; + goto cleanup; + } + + /* if mmap failed, fall through to try readbuffer below */ + git_error_clear(); + } + + if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size))) { + git_str out = GIT_STR_INIT; + + error = git_filter_list__convert_buf(&out, fl, &raw); + + if (!error) { + fc->map.len = out.size; + fc->map.data = out.ptr; + fc->flags |= GIT_DIFF_FLAG__FREE_DATA; + } + } + +cleanup: + git_filter_list_free(fl); + p_close(fd); + + return error; +} + +static int diff_file_content_load_workdir( + git_diff_file_content *fc, + git_diff_options *diff_opts) +{ + int error = 0; + git_str path = GIT_STR_INIT; + + if (fc->file->mode == GIT_FILEMODE_COMMIT) + return diff_file_content_commit_to_str(fc, true); + + if (fc->file->mode == GIT_FILEMODE_TREE) + return 0; + + if (git_repository_workdir_path(&path, fc->repo, fc->file->path) < 0) + return -1; + + if (S_ISLNK(fc->file->mode)) + error = diff_file_content_load_workdir_symlink(fc, &path); + else + error = diff_file_content_load_workdir_file(fc, &path, diff_opts); + + /* once data is loaded, update OID if we didn't have it previously */ + if (!error && (fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0) { + error = git_odb_hash( + &fc->file->id, fc->map.data, fc->map.len, GIT_OBJECT_BLOB); + fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; + } + + git_str_dispose(&path); + return error; +} + +int git_diff_file_content__load( + git_diff_file_content *fc, + git_diff_options *diff_opts) +{ + int error = 0; + + if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0) + return 0; + + if ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0 && + (diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0) + return 0; + + if (fc->src == GIT_ITERATOR_WORKDIR) + error = diff_file_content_load_workdir(fc, diff_opts); + else + error = diff_file_content_load_blob(fc, diff_opts); + if (error) + return error; + + fc->flags |= GIT_DIFF_FLAG__LOADED; + + diff_file_content_binary_by_content(fc); + + return 0; +} + +void git_diff_file_content__unload(git_diff_file_content *fc) +{ + if ((fc->flags & GIT_DIFF_FLAG__LOADED) == 0) + return; + + if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) { + git__free(fc->map.data); + fc->map.data = ""; + fc->map.len = 0; + fc->flags &= ~GIT_DIFF_FLAG__FREE_DATA; + } + else if (fc->flags & GIT_DIFF_FLAG__UNMAP_DATA) { + git_futils_mmap_free(&fc->map); + fc->map.data = ""; + fc->map.len = 0; + fc->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA; + } + + if (fc->flags & GIT_DIFF_FLAG__FREE_BLOB) { + git_blob_free((git_blob *)fc->blob); + fc->blob = NULL; + fc->flags &= ~GIT_DIFF_FLAG__FREE_BLOB; + } + + fc->flags &= ~GIT_DIFF_FLAG__LOADED; +} + +void git_diff_file_content__clear(git_diff_file_content *fc) +{ + git_diff_file_content__unload(fc); + + /* for now, nothing else to do */ +} diff --git a/src/libgit2/diff_file.h b/src/libgit2/diff_file.h new file mode 100644 index 000000000..8d743e821 --- /dev/null +++ b/src/libgit2/diff_file.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_diff_file_h__ +#define INCLUDE_diff_file_h__ + +#include "common.h" + +#include "diff.h" +#include "diff_driver.h" +#include "map.h" + +/* expanded information for one side of a delta */ +typedef struct { + git_repository *repo; + git_diff_file *file; + git_diff_driver *driver; + uint32_t flags; + uint32_t opts_flags; + git_object_size_t opts_max_size; + git_iterator_t src; + const git_blob *blob; + git_map map; +} git_diff_file_content; + +extern int git_diff_file_content__init_from_diff( + git_diff_file_content *fc, + git_diff *diff, + git_diff_delta *delta, + bool use_old); + +typedef struct { + const git_blob *blob; + const void *buf; + size_t buflen; + const char *as_path; +} git_diff_file_content_src; + +#define GIT_DIFF_FILE_CONTENT_SRC__BLOB(BLOB,PATH) { (BLOB),NULL,0,(PATH) } +#define GIT_DIFF_FILE_CONTENT_SRC__BUF(BUF,LEN,PATH) { NULL,(BUF),(LEN),(PATH) } + +extern int git_diff_file_content__init_from_src( + git_diff_file_content *fc, + git_repository *repo, + const git_diff_options *opts, + const git_diff_file_content_src *src, + git_diff_file *as_file); + +/* this loads the blob/file-on-disk as needed */ +extern int git_diff_file_content__load( + git_diff_file_content *fc, + git_diff_options *diff_opts); + +/* this releases the blob/file-in-memory */ +extern void git_diff_file_content__unload(git_diff_file_content *fc); + +/* this unloads and also releases any other resources */ +extern void git_diff_file_content__clear(git_diff_file_content *fc); + +#endif diff --git a/src/libgit2/diff_generate.c b/src/libgit2/diff_generate.c new file mode 100644 index 000000000..cfaefba66 --- /dev/null +++ b/src/libgit2/diff_generate.c @@ -0,0 +1,1724 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "diff_generate.h" + +#include "diff.h" +#include "patch_generate.h" +#include "futils.h" +#include "config.h" +#include "attr_file.h" +#include "filter.h" +#include "pathspec.h" +#include "index.h" +#include "odb.h" +#include "submodule.h" + +#define DIFF_FLAG_IS_SET(DIFF,FLAG) \ + (((DIFF)->base.opts.flags & (FLAG)) != 0) +#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \ + (((DIFF)->base.opts.flags & (FLAG)) == 0) +#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->base.opts.flags = \ + (VAL) ? ((DIFF)->base.opts.flags | (FLAG)) : \ + ((DIFF)->base.opts.flags & ~(FLAG)) + +typedef struct { + struct git_diff base; + + git_vector pathspec; + + uint32_t diffcaps; + bool index_updated; +} git_diff_generated; + +static git_diff_delta *diff_delta__alloc( + git_diff_generated *diff, + git_delta_t status, + const char *path) +{ + git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); + if (!delta) + return NULL; + + delta->old_file.path = git_pool_strdup(&diff->base.pool, path); + if (delta->old_file.path == NULL) { + git__free(delta); + return NULL; + } + + delta->new_file.path = delta->old_file.path; + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + switch (status) { + case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; + case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; + default: break; /* leave other status values alone */ + } + } + delta->status = status; + + return delta; +} + +static int diff_insert_delta( + git_diff_generated *diff, + git_diff_delta *delta, + const char *matched_pathspec) +{ + int error = 0; + + if (diff->base.opts.notify_cb) { + error = diff->base.opts.notify_cb( + &diff->base, delta, matched_pathspec, diff->base.opts.payload); + + if (error) { + git__free(delta); + + if (error > 0) /* positive value means to skip this delta */ + return 0; + else /* negative value means to cancel diff */ + return git_error_set_after_callback_function(error, "git_diff"); + } + } + + if ((error = git_vector_insert(&diff->base.deltas, delta)) < 0) + git__free(delta); + + return error; +} + +static bool diff_pathspec_match( + const char **matched_pathspec, + git_diff_generated *diff, + const git_index_entry *entry) +{ + bool disable_pathspec_match = + DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH); + + /* If we're disabling fnmatch, then the iterator has already applied + * the filters to the files for us and we don't have to do anything. + * However, this only applies to *files* - the iterator will include + * directories that we need to recurse into when not autoexpanding, + * so we still need to apply the pathspec match to directories. + */ + if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) && + disable_pathspec_match) { + *matched_pathspec = entry->path; + return true; + } + + return git_pathspec__match( + &diff->pathspec, entry->path, disable_pathspec_match, + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), + matched_pathspec, NULL); +} + +static void diff_delta__flag_known_size(git_diff_file *file) +{ + /* + * If we don't know the ID, that can only come from the workdir + * iterator, which means we *do* know the file size. This is a + * leaky abstraction, but alas. Otherwise, we test against the + * empty blob id. + */ + if (file->size || + !(file->flags & GIT_DIFF_FLAG_VALID_ID) || + git_oid_equal(&file->id, &git_oid__empty_blob_sha1)) + file->flags |= GIT_DIFF_FLAG_VALID_SIZE; +} + +static void diff_delta__flag_known_sizes(git_diff_delta *delta) +{ + diff_delta__flag_known_size(&delta->old_file); + diff_delta__flag_known_size(&delta->new_file); +} + +static int diff_delta__from_one( + git_diff_generated *diff, + git_delta_t status, + const git_index_entry *oitem, + const git_index_entry *nitem) +{ + const git_index_entry *entry = nitem; + bool has_old = false; + git_diff_delta *delta; + const char *matched_pathspec; + + GIT_ASSERT_ARG((oitem != NULL) ^ (nitem != NULL)); + + if (oitem) { + entry = oitem; + has_old = true; + } + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) + has_old = !has_old; + + if ((entry->flags & GIT_INDEX_ENTRY_VALID) != 0) + return 0; + + if (status == GIT_DELTA_IGNORED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) + return 0; + + if (status == GIT_DELTA_UNTRACKED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) + return 0; + + if (status == GIT_DELTA_UNREADABLE && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE)) + return 0; + + if (!diff_pathspec_match(&matched_pathspec, diff, entry)) + return 0; + + delta = diff_delta__alloc(diff, status, entry->path); + GIT_ERROR_CHECK_ALLOC(delta); + + /* This fn is just for single-sided diffs */ + GIT_ASSERT(status != GIT_DELTA_MODIFIED); + delta->nfiles = 1; + + if (has_old) { + delta->old_file.mode = entry->mode; + delta->old_file.size = entry->file_size; + delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; + git_oid_cpy(&delta->old_file.id, &entry->id); + delta->old_file.id_abbrev = GIT_OID_HEXSZ; + } else /* ADDED, IGNORED, UNTRACKED */ { + delta->new_file.mode = entry->mode; + delta->new_file.size = entry->file_size; + delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; + git_oid_cpy(&delta->new_file.id, &entry->id); + delta->new_file.id_abbrev = GIT_OID_HEXSZ; + } + + delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + if (has_old || !git_oid_is_zero(&delta->new_file.id)) + delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + diff_delta__flag_known_sizes(delta); + + return diff_insert_delta(diff, delta, matched_pathspec); +} + +static int diff_delta__from_two( + git_diff_generated *diff, + git_delta_t status, + const git_index_entry *old_entry, + uint32_t old_mode, + const git_index_entry *new_entry, + uint32_t new_mode, + const git_oid *new_id, + const char *matched_pathspec) +{ + const git_oid *old_id = &old_entry->id; + git_diff_delta *delta; + const char *canonical_path = old_entry->path; + + if (status == GIT_DELTA_UNMODIFIED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) + return 0; + + if (!new_id) + new_id = &new_entry->id; + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + uint32_t temp_mode = old_mode; + const git_index_entry *temp_entry = old_entry; + const git_oid *temp_id = old_id; + + old_entry = new_entry; + new_entry = temp_entry; + old_mode = new_mode; + new_mode = temp_mode; + old_id = new_id; + new_id = temp_id; + } + + delta = diff_delta__alloc(diff, status, canonical_path); + GIT_ERROR_CHECK_ALLOC(delta); + delta->nfiles = 2; + + if (!git_index_entry_is_conflict(old_entry)) { + delta->old_file.size = old_entry->file_size; + delta->old_file.mode = old_mode; + git_oid_cpy(&delta->old_file.id, old_id); + delta->old_file.id_abbrev = GIT_OID_HEXSZ; + delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID | + GIT_DIFF_FLAG_EXISTS; + } + + if (!git_index_entry_is_conflict(new_entry)) { + git_oid_cpy(&delta->new_file.id, new_id); + delta->new_file.id_abbrev = GIT_OID_HEXSZ; + delta->new_file.size = new_entry->file_size; + delta->new_file.mode = new_mode; + delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; + delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; + + if (!git_oid_is_zero(&new_entry->id)) + delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + } + + diff_delta__flag_known_sizes(delta); + + return diff_insert_delta(diff, delta, matched_pathspec); +} + +static git_diff_delta *diff_delta__last_for_item( + git_diff_generated *diff, + const git_index_entry *item) +{ + git_diff_delta *delta = git_vector_last(&diff->base.deltas); + if (!delta) + return NULL; + + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: + case GIT_DELTA_DELETED: + if (git_oid__cmp(&delta->old_file.id, &item->id) == 0) + return delta; + break; + case GIT_DELTA_ADDED: + if (git_oid__cmp(&delta->new_file.id, &item->id) == 0) + return delta; + break; + case GIT_DELTA_UNREADABLE: + case GIT_DELTA_UNTRACKED: + if (diff->base.strcomp(delta->new_file.path, item->path) == 0 && + git_oid__cmp(&delta->new_file.id, &item->id) == 0) + return delta; + break; + case GIT_DELTA_MODIFIED: + if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 || + (delta->new_file.mode == item->mode && + git_oid__cmp(&delta->new_file.id, &item->id) == 0)) + return delta; + break; + default: + break; + } + + return NULL; +} + +static char *diff_strdup_prefix(git_pool *pool, const char *prefix) +{ + size_t len = strlen(prefix); + + /* append '/' at end if needed */ + if (len > 0 && prefix[len - 1] != '/') + return git_pool_strcat(pool, prefix, "/"); + else + return git_pool_strndup(pool, prefix, len + 1); +} + +GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta) +{ + return delta->old_file.path ? + delta->old_file.path : delta->new_file.path; +} + +static int diff_delta_i2w_cmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + +static int diff_delta_i2w_casecmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + +bool git_diff_delta__should_skip( + const git_diff_options *opts, const git_diff_delta *delta) +{ + uint32_t flags = opts ? opts->flags : 0; + + if (delta->status == GIT_DELTA_UNMODIFIED && + (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + return true; + + if (delta->status == GIT_DELTA_IGNORED && + (flags & GIT_DIFF_INCLUDE_IGNORED) == 0) + return true; + + if (delta->status == GIT_DELTA_UNTRACKED && + (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) + return true; + + if (delta->status == GIT_DELTA_UNREADABLE && + (flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0) + return true; + + return false; +} + + +static const char *diff_mnemonic_prefix( + git_iterator_t type, bool left_side) +{ + const char *pfx = ""; + + switch (type) { + case GIT_ITERATOR_EMPTY: pfx = "c"; break; + case GIT_ITERATOR_TREE: pfx = "c"; break; + case GIT_ITERATOR_INDEX: pfx = "i"; break; + case GIT_ITERATOR_WORKDIR: pfx = "w"; break; + case GIT_ITERATOR_FS: pfx = left_side ? "1" : "2"; break; + default: break; + } + + /* note: without a deeper look at pathspecs, there is no easy way + * to get the (o)bject / (w)ork tree mnemonics working... + */ + + return pfx; +} + +static void diff_set_ignore_case(git_diff *diff, bool ignore_case) +{ + if (!ignore_case) { + diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE; + + diff->strcomp = git__strcmp; + diff->strncomp = git__strncmp; + diff->pfxcomp = git__prefixcmp; + diff->entrycomp = git_diff__entry_cmp; + + git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp); + } else { + diff->opts.flags |= GIT_DIFF_IGNORE_CASE; + + diff->strcomp = git__strcasecmp; + diff->strncomp = git__strncasecmp; + diff->pfxcomp = git__prefixcmp_icase; + diff->entrycomp = git_diff__entry_icmp; + + git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp); + } + + git_vector_sort(&diff->deltas); +} + +static void diff_generated_free(git_diff *d) +{ + git_diff_generated *diff = (git_diff_generated *)d; + + git_attr_session__free(&diff->base.attrsession); + git_vector_free_deep(&diff->base.deltas); + + git_pathspec__vfree(&diff->pathspec); + git_pool_clear(&diff->base.pool); + + git__memzero(diff, sizeof(*diff)); + git__free(diff); +} + +static git_diff_generated *diff_generated_alloc( + git_repository *repo, + git_iterator *old_iter, + git_iterator *new_iter) +{ + git_diff_generated *diff; + git_diff_options dflt = GIT_DIFF_OPTIONS_INIT; + + GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(old_iter, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(new_iter, NULL); + + if ((diff = git__calloc(1, sizeof(git_diff_generated))) == NULL) + return NULL; + + GIT_REFCOUNT_INC(&diff->base); + diff->base.type = GIT_DIFF_TYPE_GENERATED; + diff->base.repo = repo; + diff->base.old_src = old_iter->type; + diff->base.new_src = new_iter->type; + diff->base.patch_fn = git_patch_generated_from_diff; + diff->base.free_fn = diff_generated_free; + git_attr_session__init(&diff->base.attrsession, repo); + memcpy(&diff->base.opts, &dflt, sizeof(git_diff_options)); + + if (git_pool_init(&diff->base.pool, 1) < 0 || + git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) { + git_diff_free(&diff->base); + return NULL; + } + + /* Use case-insensitive compare if either iterator has + * the ignore_case bit set */ + diff_set_ignore_case( + &diff->base, + git_iterator_ignore_case(old_iter) || + git_iterator_ignore_case(new_iter)); + + return diff; +} + +static int diff_generated_apply_options( + git_diff_generated *diff, + const git_diff_options *opts) +{ + git_config *cfg = NULL; + git_repository *repo = diff->base.repo; + git_pool *pool = &diff->base.pool; + int val; + + if (opts) { + /* copy user options (except case sensitivity info from iterators) */ + bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE); + memcpy(&diff->base.opts, opts, sizeof(diff->base.opts)); + DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase); + + /* initialize pathspec from options */ + if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0) + return -1; + } + + /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES)) + diff->base.opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; + + /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT)) + diff->base.opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + + /* load config values that affect diff behavior */ + if ((val = git_repository_config_snapshot(&cfg, repo)) < 0) + return val; + + if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_SYMLINKS) && val) + diff->diffcaps |= GIT_DIFFCAPS_HAS_SYMLINKS; + + if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_IGNORESTAT) && val) + diff->diffcaps |= GIT_DIFFCAPS_IGNORE_STAT; + + if ((diff->base.opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 && + !git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_FILEMODE) && val) + diff->diffcaps |= GIT_DIFFCAPS_TRUST_MODE_BITS; + + if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_TRUSTCTIME) && val) + diff->diffcaps |= GIT_DIFFCAPS_TRUST_CTIME; + + /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ + + /* If not given explicit `opts`, check `diff.xyz` configs */ + if (!opts) { + int context = git_config__get_int_force(cfg, "diff.context", 3); + diff->base.opts.context_lines = context >= 0 ? (uint32_t)context : 3; + + /* add other defaults here */ + } + + /* Reverse src info if diff is reversed */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + git_iterator_t tmp_src = diff->base.old_src; + diff->base.old_src = diff->base.new_src; + diff->base.new_src = tmp_src; + } + + /* Unset UPDATE_INDEX unless diffing workdir and index */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && + (!(diff->base.old_src == GIT_ITERATOR_WORKDIR || + diff->base.new_src == GIT_ITERATOR_WORKDIR) || + !(diff->base.old_src == GIT_ITERATOR_INDEX || + diff->base.new_src == GIT_ITERATOR_INDEX))) + diff->base.opts.flags &= ~GIT_DIFF_UPDATE_INDEX; + + /* if ignore_submodules not explicitly set, check diff config */ + if (diff->base.opts.ignore_submodules <= 0) { + git_config_entry *entry; + git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true); + + if (entry && git_submodule_parse_ignore( + &diff->base.opts.ignore_submodules, entry->value) < 0) + git_error_clear(); + git_config_entry_free(entry); + } + + /* if either prefix is not set, figure out appropriate value */ + if (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) { + const char *use_old = DIFF_OLD_PREFIX_DEFAULT; + const char *use_new = DIFF_NEW_PREFIX_DEFAULT; + + if (git_config__get_bool_force(cfg, "diff.noprefix", 0)) + use_old = use_new = ""; + else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) { + use_old = diff_mnemonic_prefix(diff->base.old_src, true); + use_new = diff_mnemonic_prefix(diff->base.new_src, false); + } + + if (!diff->base.opts.old_prefix) + diff->base.opts.old_prefix = use_old; + if (!diff->base.opts.new_prefix) + diff->base.opts.new_prefix = use_new; + } + + /* strdup prefix from pool so we're not dependent on external data */ + diff->base.opts.old_prefix = diff_strdup_prefix(pool, diff->base.opts.old_prefix); + diff->base.opts.new_prefix = diff_strdup_prefix(pool, diff->base.opts.new_prefix); + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + const char *tmp_prefix = diff->base.opts.old_prefix; + diff->base.opts.old_prefix = diff->base.opts.new_prefix; + diff->base.opts.new_prefix = tmp_prefix; + } + + git_config_free(cfg); + + /* check strdup results for error */ + return (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) ? -1 : 0; +} + +int git_diff__oid_for_file( + git_oid *out, + git_diff *diff, + const char *path, + uint16_t mode, + git_object_size_t size) +{ + git_index_entry entry; + + if (size > UINT32_MAX) { + git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", path); + return -1; + } + + memset(&entry, 0, sizeof(entry)); + entry.mode = mode; + entry.file_size = (uint32_t)size; + entry.path = (char *)path; + + return git_diff__oid_for_entry(out, diff, &entry, mode, NULL); +} + +int git_diff__oid_for_entry( + git_oid *out, + git_diff *d, + const git_index_entry *src, + uint16_t mode, + const git_oid *update_match) +{ + git_diff_generated *diff; + git_str full_path = GIT_STR_INIT; + git_index_entry entry = *src; + git_filter_list *fl = NULL; + int error = 0; + + GIT_ASSERT(d->type == GIT_DIFF_TYPE_GENERATED); + diff = (git_diff_generated *)d; + + memset(out, 0, sizeof(*out)); + + if (git_repository_workdir_path(&full_path, diff->base.repo, entry.path) < 0) + return -1; + + if (!mode) { + struct stat st; + + diff->base.perf.stat_calls++; + + if (p_stat(full_path.ptr, &st) < 0) { + error = git_fs_path_set_error(errno, entry.path, "stat"); + git_str_dispose(&full_path); + return error; + } + + git_index_entry__init_from_stat(&entry, + &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0); + } + + /* calculate OID for file if possible */ + if (S_ISGITLINK(mode)) { + git_submodule *sm; + + if (!git_submodule_lookup(&sm, diff->base.repo, entry.path)) { + const git_oid *sm_oid = git_submodule_wd_id(sm); + if (sm_oid) + git_oid_cpy(out, sm_oid); + git_submodule_free(sm); + } else { + /* if submodule lookup failed probably just in an intermediate + * state where some init hasn't happened, so ignore the error + */ + git_error_clear(); + } + } else if (S_ISLNK(mode)) { + error = git_odb__hashlink(out, full_path.ptr); + diff->base.perf.oid_calculations++; + } else if (!git__is_sizet(entry.file_size)) { + git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", + entry.path); + error = -1; + } else if (!(error = git_filter_list_load(&fl, + diff->base.repo, NULL, entry.path, + GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE))) + { + int fd = git_futils_open_ro(full_path.ptr); + if (fd < 0) + error = fd; + else { + error = git_odb__hashfd_filtered( + out, fd, (size_t)entry.file_size, GIT_OBJECT_BLOB, fl); + p_close(fd); + diff->base.perf.oid_calculations++; + } + + git_filter_list_free(fl); + } + + /* update index for entry if requested */ + if (!error && update_match && git_oid_equal(out, update_match)) { + git_index *idx; + git_index_entry updated_entry; + + memcpy(&updated_entry, &entry, sizeof(git_index_entry)); + updated_entry.mode = mode; + git_oid_cpy(&updated_entry.id, out); + + if (!(error = git_repository_index__weakptr(&idx, + diff->base.repo))) { + error = git_index_add(idx, &updated_entry); + diff->index_updated = true; + } + } + + git_str_dispose(&full_path); + return error; +} + +typedef struct { + git_repository *repo; + git_iterator *old_iter; + git_iterator *new_iter; + const git_index_entry *oitem; + const git_index_entry *nitem; + git_strmap *submodule_cache; + bool submodule_cache_initialized; +} diff_in_progress; + +#define MODE_BITS_MASK 0000777 + +static int maybe_modified_submodule( + git_delta_t *status, + git_oid *found_oid, + git_diff_generated *diff, + diff_in_progress *info) +{ + int error = 0; + git_submodule *sub; + unsigned int sm_status = 0; + git_submodule_ignore_t ign = diff->base.opts.ignore_submodules; + git_strmap *submodule_cache = NULL; + + *status = GIT_DELTA_UNMODIFIED; + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) || + ign == GIT_SUBMODULE_IGNORE_ALL) + return 0; + + if (diff->base.repo->submodule_cache != NULL) { + submodule_cache = diff->base.repo->submodule_cache; + } else { + if (!info->submodule_cache_initialized) { + info->submodule_cache_initialized = true; + /* + * Try to cache the submodule information to avoid having to parse it for + * every submodule. It is okay if it fails, the cache will still be NULL + * and the submodules will be attempted to be looked up individually. + */ + git_submodule_cache_init(&info->submodule_cache, diff->base.repo); + } + submodule_cache = info->submodule_cache; + } + + if ((error = git_submodule__lookup_with_cache( + &sub, diff->base.repo, info->nitem->path, submodule_cache)) < 0) { + + /* GIT_EEXISTS means dir with .git in it was found - ignore it */ + if (error == GIT_EEXISTS) { + git_error_clear(); + error = 0; + } + return error; + } + + if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) + /* ignore it */; + else if ((error = git_submodule__status( + &sm_status, NULL, NULL, found_oid, sub, ign)) < 0) + /* return error below */; + + /* check IS_WD_UNMODIFIED because this case is only used + * when the new side of the diff is the working directory + */ + else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) + *status = GIT_DELTA_MODIFIED; + + /* now that we have a HEAD OID, check if HEAD moved */ + else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 && + !git_oid_equal(&info->oitem->id, found_oid)) + *status = GIT_DELTA_MODIFIED; + + git_submodule_free(sub); + return error; +} + +static int maybe_modified( + git_diff_generated *diff, + diff_in_progress *info) +{ + git_oid noid; + git_delta_t status = GIT_DELTA_MODIFIED; + const git_index_entry *oitem = info->oitem; + const git_index_entry *nitem = info->nitem; + unsigned int omode = oitem->mode; + unsigned int nmode = nitem->mode; + bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_WORKDIR); + bool modified_uncertain = false; + const char *matched_pathspec; + int error = 0; + + if (!diff_pathspec_match(&matched_pathspec, diff, oitem)) + return 0; + + memset(&noid, 0, sizeof(noid)); + + /* on platforms with no symlinks, preserve mode of existing symlinks */ + if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir && + !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) + nmode = omode; + + /* on platforms with no execmode, just preserve old mode */ + if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) && + (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) && + new_is_workdir) + nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); + + /* if one side is a conflict, mark the whole delta as conflicted */ + if (git_index_entry_is_conflict(oitem) || + git_index_entry_is_conflict(nitem)) { + status = GIT_DELTA_CONFLICTED; + + /* support "assume unchanged" (poorly, b/c we still stat everything) */ + } else if ((oitem->flags & GIT_INDEX_ENTRY_VALID) != 0) { + status = GIT_DELTA_UNMODIFIED; + + /* support "skip worktree" index bit */ + } else if ((oitem->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE) != 0) { + status = GIT_DELTA_UNMODIFIED; + + /* if basic type of file changed, then split into delete and add */ + } else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) { + status = GIT_DELTA_TYPECHANGE; + } + + else if (nmode == GIT_FILEMODE_UNREADABLE) { + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) + error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem); + return error; + } + + else { + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) + error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); + return error; + } + + /* if oids and modes match (and are valid), then file is unmodified */ + } else if (git_oid_equal(&oitem->id, &nitem->id) && + omode == nmode && + !git_oid_is_zero(&oitem->id)) { + status = GIT_DELTA_UNMODIFIED; + + /* if we have an unknown OID and a workdir iterator, then check some + * circumstances that can accelerate things or need special handling + */ + } else if (git_oid_is_zero(&nitem->id) && new_is_workdir) { + bool use_ctime = + ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0); + git_index *index = git_iterator_index(info->new_iter); + + status = GIT_DELTA_UNMODIFIED; + + if (S_ISGITLINK(nmode)) { + if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0) + return error; + } + + /* if the stat data looks different, then mark modified - this just + * means that the OID will be recalculated below to confirm change + */ + else if (omode != nmode || oitem->file_size != nitem->file_size) { + status = GIT_DELTA_MODIFIED; + modified_uncertain = + (oitem->file_size <= 0 && nitem->file_size > 0); + } + else if (!git_index_time_eq(&oitem->mtime, &nitem->mtime) || + (use_ctime && !git_index_time_eq(&oitem->ctime, &nitem->ctime)) || + oitem->ino != nitem->ino || + oitem->uid != nitem->uid || + oitem->gid != nitem->gid || + git_index_entry_newer_than_index(nitem, index)) + { + status = GIT_DELTA_MODIFIED; + modified_uncertain = true; + } + + /* if mode is GITLINK and submodules are ignored, then skip */ + } else if (S_ISGITLINK(nmode) && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) { + status = GIT_DELTA_UNMODIFIED; + } + + /* if we got here and decided that the files are modified, but we + * haven't calculated the OID of the new item, then calculate it now + */ + if (modified_uncertain && git_oid_is_zero(&nitem->id)) { + const git_oid *update_check = + DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ? + &oitem->id : NULL; + + if ((error = git_diff__oid_for_entry( + &noid, &diff->base, nitem, nmode, update_check)) < 0) + return error; + + /* if oid matches, then mark unmodified (except submodules, where + * the filesystem content may be modified even if the oid still + * matches between the index and the workdir HEAD) + */ + if (omode == nmode && !S_ISGITLINK(omode) && + git_oid_equal(&oitem->id, &noid)) + status = GIT_DELTA_UNMODIFIED; + } + + /* If we want case changes, then break this into a delete of the old + * and an add of the new so that consumers can act accordingly (eg, + * checkout will update the case on disk.) + */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) && + strcmp(oitem->path, nitem->path) != 0) { + + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) + error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); + + return error; + } + + return diff_delta__from_two( + diff, status, oitem, omode, nitem, nmode, + git_oid_is_zero(&noid) ? NULL : &noid, matched_pathspec); +} + +static bool entry_is_prefixed( + git_diff_generated *diff, + const git_index_entry *item, + const git_index_entry *prefix_item) +{ + size_t pathlen; + + if (!item || diff->base.pfxcomp(item->path, prefix_item->path) != 0) + return false; + + pathlen = strlen(prefix_item->path); + + return (prefix_item->path[pathlen - 1] == '/' || + item->path[pathlen] == '\0' || + item->path[pathlen] == '/'); +} + +static int iterator_current( + const git_index_entry **entry, + git_iterator *iterator) +{ + int error; + + if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int iterator_advance( + const git_index_entry **entry, + git_iterator *iterator) +{ + const git_index_entry *prev_entry = *entry; + int cmp, error; + + /* if we're looking for conflicts, we only want to report + * one conflict for each file, instead of all three sides. + * so if this entry is a conflict for this file, and the + * previous one was a conflict for the same file, skip it. + */ + while ((error = git_iterator_advance(entry, iterator)) == 0) { + if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) || + !git_index_entry_is_conflict(prev_entry) || + !git_index_entry_is_conflict(*entry)) + break; + + cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ? + strcasecmp(prev_entry->path, (*entry)->path) : + strcmp(prev_entry->path, (*entry)->path); + + if (cmp) + break; + } + + if (error == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int iterator_advance_into( + const git_index_entry **entry, + git_iterator *iterator) +{ + int error; + + if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int iterator_advance_over( + const git_index_entry **entry, + git_iterator_status_t *status, + git_iterator *iterator) +{ + int error = git_iterator_advance_over(entry, status, iterator); + + if (error == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int handle_unmatched_new_item( + git_diff_generated *diff, diff_in_progress *info) +{ + int error = 0; + const git_index_entry *nitem = info->nitem; + git_delta_t delta_type = GIT_DELTA_UNTRACKED; + bool contains_oitem; + + /* check if this is a prefix of the other side */ + contains_oitem = entry_is_prefixed(diff, info->oitem, nitem); + + /* update delta_type if this item is conflicted */ + if (git_index_entry_is_conflict(nitem)) + delta_type = GIT_DELTA_CONFLICTED; + + /* update delta_type if this item is ignored */ + else if (git_iterator_current_is_ignored(info->new_iter)) + delta_type = GIT_DELTA_IGNORED; + + if (nitem->mode == GIT_FILEMODE_TREE) { + bool recurse_into_dir = contains_oitem; + + /* check if user requests recursion into this type of dir */ + recurse_into_dir = contains_oitem || + (delta_type == GIT_DELTA_UNTRACKED && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) || + (delta_type == GIT_DELTA_IGNORED && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)); + + /* do not advance into directories that contain a .git file */ + if (recurse_into_dir && !contains_oitem) { + git_str *full = NULL; + if (git_iterator_current_workdir_path(&full, info->new_iter) < 0) + return -1; + if (full && git_fs_path_contains(full, DOT_GIT)) { + /* TODO: warning if not a valid git repository */ + recurse_into_dir = false; + } + } + + /* still have to look into untracked directories to match core git - + * with no untracked files, directory is treated as ignored + */ + if (!recurse_into_dir && + delta_type == GIT_DELTA_UNTRACKED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS)) + { + git_diff_delta *last; + git_iterator_status_t untracked_state; + + /* attempt to insert record for this directory */ + if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) + return error; + + /* if delta wasn't created (because of rules), just skip ahead */ + last = diff_delta__last_for_item(diff, nitem); + if (!last) + return iterator_advance(&info->nitem, info->new_iter); + + /* iterate into dir looking for an actual untracked file */ + if ((error = iterator_advance_over( + &info->nitem, &untracked_state, info->new_iter)) < 0) + return error; + + /* if we found nothing that matched our pathlist filter, exclude */ + if (untracked_state == GIT_ITERATOR_STATUS_FILTERED) { + git_vector_pop(&diff->base.deltas); + git__free(last); + } + + /* if we found nothing or just ignored items, update the record */ + if (untracked_state == GIT_ITERATOR_STATUS_IGNORED || + untracked_state == GIT_ITERATOR_STATUS_EMPTY) { + last->status = GIT_DELTA_IGNORED; + + /* remove the record if we don't want ignored records */ + if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) { + git_vector_pop(&diff->base.deltas); + git__free(last); + } + } + + return 0; + } + + /* try to advance into directory if necessary */ + if (recurse_into_dir) { + error = iterator_advance_into(&info->nitem, info->new_iter); + + /* if directory is empty, can't advance into it, so skip it */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = iterator_advance(&info->nitem, info->new_iter); + } + + return error; + } + } + + else if (delta_type == GIT_DELTA_IGNORED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) && + git_iterator_current_tree_is_ignored(info->new_iter)) + /* item contained in ignored directory, so skip over it */ + return iterator_advance(&info->nitem, info->new_iter); + + else if (info->new_iter->type != GIT_ITERATOR_WORKDIR) { + if (delta_type != GIT_DELTA_CONFLICTED) + delta_type = GIT_DELTA_ADDED; + } + + else if (nitem->mode == GIT_FILEMODE_COMMIT) { + /* ignore things that are not actual submodules */ + if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) { + git_error_clear(); + delta_type = GIT_DELTA_IGNORED; + + /* if this contains a tracked item, treat as normal TREE */ + if (contains_oitem) { + error = iterator_advance_into(&info->nitem, info->new_iter); + if (error != GIT_ENOTFOUND) + return error; + + git_error_clear(); + return iterator_advance(&info->nitem, info->new_iter); + } + } + } + + else if (nitem->mode == GIT_FILEMODE_UNREADABLE) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED)) + delta_type = GIT_DELTA_UNTRACKED; + else + delta_type = GIT_DELTA_UNREADABLE; + } + + /* Actually create the record for this item if necessary */ + if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) + return error; + + /* If user requested TYPECHANGE records, then check for that instead of + * just generating an ADDED/UNTRACKED record + */ + if (delta_type != GIT_DELTA_IGNORED && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && + contains_oitem) + { + /* this entry was prefixed with a tree - make TYPECHANGE */ + git_diff_delta *last = diff_delta__last_for_item(diff, nitem); + if (last) { + last->status = GIT_DELTA_TYPECHANGE; + last->old_file.mode = GIT_FILEMODE_TREE; + } + } + + return iterator_advance(&info->nitem, info->new_iter); +} + +static int handle_unmatched_old_item( + git_diff_generated *diff, diff_in_progress *info) +{ + git_delta_t delta_type = GIT_DELTA_DELETED; + int error; + + /* update delta_type if this item is conflicted */ + if (git_index_entry_is_conflict(info->oitem)) + delta_type = GIT_DELTA_CONFLICTED; + + if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0) + return error; + + /* if we are generating TYPECHANGE records then check for that + * instead of just generating a DELETE record + */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && + entry_is_prefixed(diff, info->nitem, info->oitem)) + { + /* this entry has become a tree! convert to TYPECHANGE */ + git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem); + if (last) { + last->status = GIT_DELTA_TYPECHANGE; + last->new_file.mode = GIT_FILEMODE_TREE; + } + + /* If new_iter is a workdir iterator, then this situation + * will certainly be followed by a series of untracked items. + * Unless RECURSE_UNTRACKED_DIRS is set, skip over them... + */ + if (S_ISDIR(info->nitem->mode) && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) + return iterator_advance(&info->nitem, info->new_iter); + } + + return iterator_advance(&info->oitem, info->old_iter); +} + +static int handle_matched_item( + git_diff_generated *diff, diff_in_progress *info) +{ + int error = 0; + + if ((error = maybe_modified(diff, info)) < 0) + return error; + + if (!(error = iterator_advance(&info->oitem, info->old_iter))) + error = iterator_advance(&info->nitem, info->new_iter); + + return error; +} + +int git_diff__from_iterators( + git_diff **out, + git_repository *repo, + git_iterator *old_iter, + git_iterator *new_iter, + const git_diff_options *opts) +{ + git_diff_generated *diff; + diff_in_progress info = {0}; + int error = 0; + + *out = NULL; + + diff = diff_generated_alloc(repo, old_iter, new_iter); + GIT_ERROR_CHECK_ALLOC(diff); + + info.repo = repo; + info.old_iter = old_iter; + info.new_iter = new_iter; + + /* make iterators have matching icase behavior */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) { + if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 || + (error = git_iterator_set_ignore_case(new_iter, true)) < 0) + goto cleanup; + } + + /* finish initialization */ + if ((error = diff_generated_apply_options(diff, opts)) < 0) + goto cleanup; + + if ((error = iterator_current(&info.oitem, old_iter)) < 0 || + (error = iterator_current(&info.nitem, new_iter)) < 0) + goto cleanup; + + /* run iterators building diffs */ + while (!error && (info.oitem || info.nitem)) { + int cmp; + + /* report progress */ + if (opts && opts->progress_cb) { + if ((error = opts->progress_cb(&diff->base, + info.oitem ? info.oitem->path : NULL, + info.nitem ? info.nitem->path : NULL, + opts->payload))) + break; + } + + cmp = info.oitem ? + (info.nitem ? diff->base.entrycomp(info.oitem, info.nitem) : -1) : 1; + + /* create DELETED records for old items not matched in new */ + if (cmp < 0) + error = handle_unmatched_old_item(diff, &info); + + /* create ADDED, TRACKED, or IGNORED records for new items not + * matched in old (and/or descend into directories as needed) + */ + else if (cmp > 0) + error = handle_unmatched_new_item(diff, &info); + + /* otherwise item paths match, so create MODIFIED record + * (or ADDED and DELETED pair if type changed) + */ + else + error = handle_matched_item(diff, &info); + } + + diff->base.perf.stat_calls += + old_iter->stat_calls + new_iter->stat_calls; + +cleanup: + if (!error) + *out = &diff->base; + else + git_diff_free(&diff->base); + if (info.submodule_cache) + git_submodule_cache_free(info.submodule_cache); + + return error; +} + +static int diff_prepare_iterator_opts(char **prefix, git_iterator_options *a, int aflags, + git_iterator_options *b, int bflags, + const git_diff_options *opts) +{ + GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); + + *prefix = NULL; + + if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { + a->pathlist.strings = opts->pathspec.strings; + a->pathlist.count = opts->pathspec.count; + b->pathlist.strings = opts->pathspec.strings; + b->pathlist.count = opts->pathspec.count; + } else if (opts) { + *prefix = git_pathspec_prefix(&opts->pathspec); + GIT_ERROR_CHECK_ALLOC(prefix); + } + + a->flags = aflags; + b->flags = bflags; + a->start = b->start = *prefix; + a->end = b->end = *prefix; + + return 0; +} + +int git_diff_tree_to_tree( + git_diff **out, + git_repository *repo, + git_tree *old_tree, + git_tree *new_tree, + const git_diff_options *opts) +{ + git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE; + git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, + b_opts = GIT_ITERATOR_OPTIONS_INIT; + git_iterator *a = NULL, *b = NULL; + git_diff *diff = NULL; + char *prefix = NULL; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + /* for tree to tree diff, be case sensitive even if the index is + * currently case insensitive, unless the user explicitly asked + * for case insensitivity + */ + if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0) + iflag = GIT_ITERATOR_IGNORE_CASE; + + if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 || + (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || + (error = git_iterator_for_tree(&b, new_tree, &b_opts)) < 0 || + (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) + goto out; + + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); + + return error; +} + +static int diff_load_index(git_index **index, git_repository *repo) +{ + int error = git_repository_index__weakptr(index, repo); + + /* reload the repository index when user did not pass one in */ + if (!error && git_index_read(*index, false) < 0) + git_error_clear(); + + return error; +} + +int git_diff_tree_to_index( + git_diff **out, + git_repository *repo, + git_tree *old_tree, + git_index *index, + const git_diff_options *opts) +{ + git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_INCLUDE_CONFLICTS; + git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, + b_opts = GIT_ITERATOR_OPTIONS_INIT; + git_iterator *a = NULL, *b = NULL; + git_diff *diff = NULL; + char *prefix = NULL; + bool index_ignore_case = false; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + if (!index && (error = diff_load_index(&index, repo)) < 0) + return error; + + index_ignore_case = index->ignore_case; + + if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 || + (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || + (error = git_iterator_for_index(&b, repo, index, &b_opts)) < 0 || + (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) + goto out; + + /* if index is in case-insensitive order, re-sort deltas to match */ + if (index_ignore_case) + diff_set_ignore_case(diff, true); + + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); + + return error; +} + +int git_diff_index_to_workdir( + git_diff **out, + git_repository *repo, + git_index *index, + const git_diff_options *opts) +{ + git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, + b_opts = GIT_ITERATOR_OPTIONS_INIT; + git_iterator *a = NULL, *b = NULL; + git_diff *diff = NULL; + char *prefix = NULL; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + if (!index && (error = diff_load_index(&index, repo)) < 0) + return error; + + if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_INCLUDE_CONFLICTS, + &b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts)) < 0 || + (error = git_iterator_for_index(&a, repo, index, &a_opts)) < 0 || + (error = git_iterator_for_workdir(&b, repo, index, NULL, &b_opts)) < 0 || + (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) + goto out; + + if ((diff->opts.flags & GIT_DIFF_UPDATE_INDEX) && ((git_diff_generated *)diff)->index_updated) + if ((error = git_index_write(index)) < 0) + goto out; + + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); + + return error; +} + +int git_diff_tree_to_workdir( + git_diff **out, + git_repository *repo, + git_tree *old_tree, + const git_diff_options *opts) +{ + git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, + b_opts = GIT_ITERATOR_OPTIONS_INIT; + git_iterator *a = NULL, *b = NULL; + git_diff *diff = NULL; + char *prefix = NULL; + git_index *index; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, 0, + &b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts) < 0) || + (error = git_repository_index__weakptr(&index, repo)) < 0 || + (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || + (error = git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts)) < 0 || + (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) + goto out; + + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); + + return error; +} + +int git_diff_tree_to_workdir_with_index( + git_diff **out, + git_repository *repo, + git_tree *tree, + const git_diff_options *opts) +{ + git_diff *d1 = NULL, *d2 = NULL; + git_index *index = NULL; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + if ((error = diff_load_index(&index, repo)) < 0) + return error; + + if (!(error = git_diff_tree_to_index(&d1, repo, tree, index, opts)) && + !(error = git_diff_index_to_workdir(&d2, repo, index, opts))) + error = git_diff_merge(d1, d2); + + git_diff_free(d2); + + if (error) { + git_diff_free(d1); + d1 = NULL; + } + + *out = d1; + return error; +} + +int git_diff_index_to_index( + git_diff **out, + git_repository *repo, + git_index *old_index, + git_index *new_index, + const git_diff_options *opts) +{ + git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, + b_opts = GIT_ITERATOR_OPTIONS_INIT; + git_iterator *a = NULL, *b = NULL; + git_diff *diff = NULL; + char *prefix = NULL; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(old_index); + GIT_ASSERT_ARG(new_index); + + *out = NULL; + + if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_DONT_IGNORE_CASE, + &b_opts, GIT_ITERATOR_DONT_IGNORE_CASE, opts) < 0) || + (error = git_iterator_for_index(&a, repo, old_index, &a_opts)) < 0 || + (error = git_iterator_for_index(&b, repo, new_index, &b_opts)) < 0 || + (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) + goto out; + + /* if index is in case-insensitive order, re-sort deltas to match */ + if (old_index->ignore_case || new_index->ignore_case) + diff_set_ignore_case(diff, true); + + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); + + return error; +} + +int git_diff__paired_foreach( + git_diff *head2idx, + git_diff *idx2wd, + int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload), + void *payload) +{ + int cmp, error = 0; + git_diff_delta *h2i, *i2w; + size_t i, j, i_max, j_max; + int (*strcomp)(const char *, const char *) = git__strcmp; + bool h2i_icase, i2w_icase, icase_mismatch; + + i_max = head2idx ? head2idx->deltas.length : 0; + j_max = idx2wd ? idx2wd->deltas.length : 0; + if (!i_max && !j_max) + return 0; + + /* At some point, tree-to-index diffs will probably never ignore case, + * even if that isn't true now. Index-to-workdir diffs may or may not + * ignore case, but the index filename for the idx2wd diff should + * still be using the canonical case-preserving name. + * + * Therefore the main thing we need to do here is make sure the diffs + * are traversed in a compatible order. To do this, we temporarily + * resort a mismatched diff to get the order correct. + * + * In order to traverse renames in the index->workdir, we need to + * ensure that we compare the index name on both sides, so we + * always sort by the old name in the i2w list. + */ + h2i_icase = head2idx != NULL && git_diff_is_sorted_icase(head2idx); + i2w_icase = idx2wd != NULL && git_diff_is_sorted_icase(idx2wd); + + icase_mismatch = + (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase); + + if (icase_mismatch && h2i_icase) { + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); + git_vector_sort(&head2idx->deltas); + } + + if (i2w_icase && !icase_mismatch) { + strcomp = git__strcasecmp; + + git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_casecmp); + git_vector_sort(&idx2wd->deltas); + } else if (idx2wd != NULL) { + git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_cmp); + git_vector_sort(&idx2wd->deltas); + } + + for (i = 0, j = 0; i < i_max || j < j_max; ) { + h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL; + i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL; + + cmp = !i2w ? -1 : !h2i ? 1 : + strcomp(h2i->new_file.path, i2w->old_file.path); + + if (cmp < 0) { + i++; i2w = NULL; + } else if (cmp > 0) { + j++; h2i = NULL; + } else { + i++; j++; + } + + if ((error = cb(h2i, i2w, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + /* restore case-insensitive delta sort */ + if (icase_mismatch && h2i_icase) { + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); + git_vector_sort(&head2idx->deltas); + } + + /* restore idx2wd sort by new path */ + if (idx2wd != NULL) { + git_vector_set_cmp(&idx2wd->deltas, + i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp); + git_vector_sort(&idx2wd->deltas); + } + + return error; +} + +int git_diff__commit( + git_diff **out, + git_repository *repo, + const git_commit *commit, + const git_diff_options *opts) +{ + git_commit *parent = NULL; + git_diff *commit_diff = NULL; + git_tree *old_tree = NULL, *new_tree = NULL; + size_t parents; + int error = 0; + + *out = NULL; + + if ((parents = git_commit_parentcount(commit)) > 1) { + char commit_oidstr[GIT_OID_HEXSZ + 1]; + + error = -1; + git_error_set(GIT_ERROR_INVALID, "commit %s is a merge commit", + git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit))); + goto on_error; + } + + if (parents > 0) + if ((error = git_commit_parent(&parent, commit, 0)) < 0 || + (error = git_commit_tree(&old_tree, parent)) < 0) + goto on_error; + + if ((error = git_commit_tree(&new_tree, commit)) < 0 || + (error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0) + goto on_error; + + *out = commit_diff; + +on_error: + git_tree_free(new_tree); + git_tree_free(old_tree); + git_commit_free(parent); + + return error; +} + diff --git a/src/libgit2/diff_generate.h b/src/libgit2/diff_generate.h new file mode 100644 index 000000000..b782f29c6 --- /dev/null +++ b/src/libgit2/diff_generate.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_diff_generate_h__ +#define INCLUDE_diff_generate_h__ + +#include "common.h" + +#include "diff.h" +#include "pool.h" +#include "index.h" + +enum { + GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */ + GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */ + GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */ + GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */ + GIT_DIFFCAPS_USE_DEV = (1 << 4) /* use st_dev? */ +}; + +#define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY) +#define DIFF_FLAGS_NOT_BINARY (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA) + +enum { + GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */ + GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */ + GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */ + GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */ + GIT_DIFF_FLAG__FREE_BLOB = (1 << 11), /* release the blob when done */ + GIT_DIFF_FLAG__LOADED = (1 << 12), /* file data has been loaded */ + + GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */ + GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */ + GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18), + GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19), + GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20) +}; + +#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF) + +#define GIT_DIFF__VERBOSE (1 << 30) + +extern void git_diff_addref(git_diff *diff); + +extern bool git_diff_delta__should_skip( + const git_diff_options *opts, const git_diff_delta *delta); + +extern int git_diff__from_iterators( + git_diff **diff_ptr, + git_repository *repo, + git_iterator *old_iter, + git_iterator *new_iter, + const git_diff_options *opts); + +extern int git_diff__commit( + git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts); + +extern int git_diff__paired_foreach( + git_diff *idx2head, + git_diff *wd2idx, + int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), + void *payload); + +/* Merge two `git_diff`s according to the callback given by `cb`. */ + +typedef git_diff_delta *(*git_diff__merge_cb)( + const git_diff_delta *left, + const git_diff_delta *right, + git_pool *pool); + +extern int git_diff__merge( + git_diff *onto, const git_diff *from, git_diff__merge_cb cb); + +extern git_diff_delta *git_diff__merge_like_cgit( + const git_diff_delta *a, + const git_diff_delta *b, + git_pool *pool); + +/* Duplicate a `git_diff_delta` out of the `git_pool` */ +extern git_diff_delta *git_diff__delta_dup( + const git_diff_delta *d, git_pool *pool); + +extern int git_diff__oid_for_file( + git_oid *out, + git_diff *diff, + const char *path, + uint16_t mode, + git_object_size_t size); + +extern int git_diff__oid_for_entry( + git_oid *out, + git_diff *diff, + const git_index_entry *src, + uint16_t mode, + const git_oid *update_match); + +/* + * Sometimes a git_diff_file will have a zero size; this attempts to + * fill in the size without loading the blob if possible. If that is + * not possible, then it will return the git_odb_object that had to be + * loaded and the caller can use it or dispose of it as needed. + */ +GIT_INLINE(int) git_diff_file__resolve_zero_size( + git_diff_file *file, git_odb_object **odb_obj, git_repository *repo) +{ + int error; + git_odb *odb; + size_t len; + git_object_t type; + + if ((error = git_repository_odb(&odb, repo)) < 0) + return error; + + error = git_odb__read_header_or_object( + odb_obj, &len, &type, odb, &file->id); + + git_odb_free(odb); + + if (!error) { + file->size = (git_object_size_t)len; + file->flags |= GIT_DIFF_FLAG_VALID_SIZE; + } + + return error; +} + +#endif diff --git a/src/libgit2/diff_parse.c b/src/libgit2/diff_parse.c new file mode 100644 index 000000000..75e41a544 --- /dev/null +++ b/src/libgit2/diff_parse.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "diff_parse.h" + +#include "diff.h" +#include "patch.h" +#include "patch_parse.h" + +static void diff_parsed_free(git_diff *d) +{ + git_diff_parsed *diff = (git_diff_parsed *)d; + git_patch *patch; + size_t i; + + git_vector_foreach(&diff->patches, i, patch) + git_patch_free(patch); + + git_vector_free(&diff->patches); + + git_vector_free(&diff->base.deltas); + git_pool_clear(&diff->base.pool); + + git__memzero(diff, sizeof(*diff)); + git__free(diff); +} + +static git_diff_parsed *diff_parsed_alloc(void) +{ + git_diff_parsed *diff; + + if ((diff = git__calloc(1, sizeof(git_diff_parsed))) == NULL) + return NULL; + + GIT_REFCOUNT_INC(&diff->base); + diff->base.type = GIT_DIFF_TYPE_PARSED; + diff->base.strcomp = git__strcmp; + diff->base.strncomp = git__strncmp; + diff->base.pfxcomp = git__prefixcmp; + diff->base.entrycomp = git_diff__entry_cmp; + diff->base.patch_fn = git_patch_parsed_from_diff; + diff->base.free_fn = diff_parsed_free; + + if (git_diff_options_init(&diff->base.opts, GIT_DIFF_OPTIONS_VERSION) < 0) { + git__free(diff); + return NULL; + } + + diff->base.opts.flags &= ~GIT_DIFF_IGNORE_CASE; + + if (git_pool_init(&diff->base.pool, 1) < 0 || + git_vector_init(&diff->patches, 0, NULL) < 0 || + git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) { + git_diff_free(&diff->base); + return NULL; + } + + git_vector_set_cmp(&diff->base.deltas, git_diff_delta__cmp); + + return diff; +} + +int git_diff_from_buffer( + git_diff **out, + const char *content, + size_t content_len) +{ + git_diff_parsed *diff; + git_patch *patch; + git_patch_parse_ctx *ctx = NULL; + int error = 0; + + *out = NULL; + + diff = diff_parsed_alloc(); + GIT_ERROR_CHECK_ALLOC(diff); + + ctx = git_patch_parse_ctx_init(content, content_len, NULL); + GIT_ERROR_CHECK_ALLOC(ctx); + + while (ctx->parse_ctx.remain_len) { + if ((error = git_patch_parse(&patch, ctx)) < 0) + break; + + git_vector_insert(&diff->patches, patch); + git_vector_insert(&diff->base.deltas, patch->delta); + } + + if (error == GIT_ENOTFOUND && git_vector_length(&diff->patches) > 0) { + git_error_clear(); + error = 0; + } + + git_patch_parse_ctx_free(ctx); + + if (error < 0) + git_diff_free(&diff->base); + else + *out = &diff->base; + + return error; +} + diff --git a/src/libgit2/diff_parse.h b/src/libgit2/diff_parse.h new file mode 100644 index 000000000..876782128 --- /dev/null +++ b/src/libgit2/diff_parse.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_diff_parse_h__ +#define INCLUDE_diff_parse_h__ + +#include "common.h" + +#include "diff.h" + +typedef struct { + struct git_diff base; + + git_vector patches; +} git_diff_parsed; + +#endif diff --git a/src/libgit2/diff_print.c b/src/libgit2/diff_print.c new file mode 100644 index 000000000..03d25b087 --- /dev/null +++ b/src/libgit2/diff_print.c @@ -0,0 +1,825 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "buf.h" +#include "diff.h" +#include "diff_file.h" +#include "patch_generate.h" +#include "futils.h" +#include "zstream.h" +#include "blob.h" +#include "delta.h" +#include "git2/sys/diff.h" + +typedef struct { + git_diff_format_t format; + git_diff_line_cb print_cb; + void *payload; + + git_str *buf; + git_diff_line line; + + const char *old_prefix; + const char *new_prefix; + uint32_t flags; + int id_strlen; + + int (*strcomp)(const char *, const char *); +} diff_print_info; + +static int diff_print_info_init__common( + diff_print_info *pi, + git_str *out, + git_repository *repo, + git_diff_format_t format, + git_diff_line_cb cb, + void *payload) +{ + pi->format = format; + pi->print_cb = cb; + pi->payload = payload; + pi->buf = out; + + if (!pi->id_strlen) { + if (!repo) + pi->id_strlen = GIT_ABBREV_DEFAULT; + else if (git_repository__configmap_lookup(&pi->id_strlen, repo, GIT_CONFIGMAP_ABBREV) < 0) + return -1; + } + + if (pi->id_strlen > GIT_OID_HEXSZ) + pi->id_strlen = GIT_OID_HEXSZ; + + memset(&pi->line, 0, sizeof(pi->line)); + pi->line.old_lineno = -1; + pi->line.new_lineno = -1; + pi->line.num_lines = 1; + + return 0; +} + +static int diff_print_info_init_fromdiff( + diff_print_info *pi, + git_str *out, + git_diff *diff, + git_diff_format_t format, + git_diff_line_cb cb, + void *payload) +{ + git_repository *repo = diff ? diff->repo : NULL; + + memset(pi, 0, sizeof(diff_print_info)); + + if (diff) { + pi->flags = diff->opts.flags; + pi->id_strlen = diff->opts.id_abbrev; + pi->old_prefix = diff->opts.old_prefix; + pi->new_prefix = diff->opts.new_prefix; + + pi->strcomp = diff->strcomp; + } + + return diff_print_info_init__common(pi, out, repo, format, cb, payload); +} + +static int diff_print_info_init_frompatch( + diff_print_info *pi, + git_str *out, + git_patch *patch, + git_diff_format_t format, + git_diff_line_cb cb, + void *payload) +{ + GIT_ASSERT_ARG(patch); + + memset(pi, 0, sizeof(diff_print_info)); + + pi->flags = patch->diff_opts.flags; + pi->id_strlen = patch->diff_opts.id_abbrev; + pi->old_prefix = patch->diff_opts.old_prefix; + pi->new_prefix = patch->diff_opts.new_prefix; + + return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload); +} + +static char diff_pick_suffix(int mode) +{ + if (S_ISDIR(mode)) + return '/'; + else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */ + /* in git, modes are very regular, so we must have 0100755 mode */ + return '*'; + else + return ' '; +} + +char git_diff_status_char(git_delta_t status) +{ + char code; + + switch (status) { + case GIT_DELTA_ADDED: code = 'A'; break; + case GIT_DELTA_DELETED: code = 'D'; break; + case GIT_DELTA_MODIFIED: code = 'M'; break; + case GIT_DELTA_RENAMED: code = 'R'; break; + case GIT_DELTA_COPIED: code = 'C'; break; + case GIT_DELTA_IGNORED: code = 'I'; break; + case GIT_DELTA_UNTRACKED: code = '?'; break; + case GIT_DELTA_TYPECHANGE: code = 'T'; break; + case GIT_DELTA_UNREADABLE: code = 'X'; break; + default: code = ' '; break; + } + + return code; +} + +static int diff_print_one_name_only( + const git_diff_delta *delta, float progress, void *data) +{ + diff_print_info *pi = data; + git_str *out = pi->buf; + + GIT_UNUSED(progress); + + if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && + delta->status == GIT_DELTA_UNMODIFIED) + return 0; + + git_str_clear(out); + git_str_puts(out, delta->new_file.path); + git_str_putc(out, '\n'); + if (git_str_oom(out)) + return -1; + + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_str_cstr(out); + pi->line.content_len = git_str_len(out); + + return pi->print_cb(delta, NULL, &pi->line, pi->payload); +} + +static int diff_print_one_name_status( + const git_diff_delta *delta, float progress, void *data) +{ + diff_print_info *pi = data; + git_str *out = pi->buf; + char old_suffix, new_suffix, code = git_diff_status_char(delta->status); + int(*strcomp)(const char *, const char *) = pi->strcomp ? + pi->strcomp : git__strcmp; + + GIT_UNUSED(progress); + + if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') + return 0; + + old_suffix = diff_pick_suffix(delta->old_file.mode); + new_suffix = diff_pick_suffix(delta->new_file.mode); + + git_str_clear(out); + + if (delta->old_file.path != delta->new_file.path && + strcomp(delta->old_file.path,delta->new_file.path) != 0) + git_str_printf(out, "%c\t%s%c %s%c\n", code, + delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); + else if (delta->old_file.mode != delta->new_file.mode && + delta->old_file.mode != 0 && delta->new_file.mode != 0) + git_str_printf(out, "%c\t%s%c %s%c\n", code, + delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); + else if (old_suffix != ' ') + git_str_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); + else + git_str_printf(out, "%c\t%s\n", code, delta->old_file.path); + if (git_str_oom(out)) + return -1; + + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_str_cstr(out); + pi->line.content_len = git_str_len(out); + + return pi->print_cb(delta, NULL, &pi->line, pi->payload); +} + +static int diff_print_one_raw( + const git_diff_delta *delta, float progress, void *data) +{ + diff_print_info *pi = data; + git_str *out = pi->buf; + int id_abbrev; + char code = git_diff_status_char(delta->status); + char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; + + GIT_UNUSED(progress); + + if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') + return 0; + + git_str_clear(out); + + id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev : + delta->new_file.id_abbrev; + + if (pi->id_strlen > id_abbrev) { + git_error_set(GIT_ERROR_PATCH, + "the patch input contains %d id characters (cannot print %d)", + id_abbrev, pi->id_strlen); + return -1; + } + + git_oid_tostr(start_oid, pi->id_strlen + 1, &delta->old_file.id); + git_oid_tostr(end_oid, pi->id_strlen + 1, &delta->new_file.id); + + git_str_printf( + out, (pi->id_strlen <= GIT_OID_HEXSZ) ? + ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c", + delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code); + + if (delta->similarity > 0) + git_str_printf(out, "%03u", delta->similarity); + + if (delta->old_file.path != delta->new_file.path) + git_str_printf( + out, "\t%s %s\n", delta->old_file.path, delta->new_file.path); + else + git_str_printf( + out, "\t%s\n", delta->old_file.path ? + delta->old_file.path : delta->new_file.path); + + if (git_str_oom(out)) + return -1; + + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_str_cstr(out); + pi->line.content_len = git_str_len(out); + + return pi->print_cb(delta, NULL, &pi->line, pi->payload); +} + +static int diff_print_modes( + git_str *out, const git_diff_delta *delta) +{ + git_str_printf(out, "old mode %o\n", delta->old_file.mode); + git_str_printf(out, "new mode %o\n", delta->new_file.mode); + + return git_str_oom(out) ? -1 : 0; +} + +static int diff_print_oid_range( + git_str *out, const git_diff_delta *delta, int id_strlen, + bool print_index) +{ + char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; + + if (delta->old_file.mode && + id_strlen > delta->old_file.id_abbrev) { + git_error_set(GIT_ERROR_PATCH, + "the patch input contains %d id characters (cannot print %d)", + delta->old_file.id_abbrev, id_strlen); + return -1; + } + + if ((delta->new_file.mode && + id_strlen > delta->new_file.id_abbrev)) { + git_error_set(GIT_ERROR_PATCH, + "the patch input contains %d id characters (cannot print %d)", + delta->new_file.id_abbrev, id_strlen); + return -1; + } + + git_oid_tostr(start_oid, id_strlen + 1, &delta->old_file.id); + git_oid_tostr(end_oid, id_strlen + 1, &delta->new_file.id); + + if (delta->old_file.mode == delta->new_file.mode) { + if (print_index) + git_str_printf(out, "index %s..%s %o\n", + start_oid, end_oid, delta->old_file.mode); + } else { + if (delta->old_file.mode == 0) + git_str_printf(out, "new file mode %o\n", delta->new_file.mode); + else if (delta->new_file.mode == 0) + git_str_printf(out, "deleted file mode %o\n", delta->old_file.mode); + else + diff_print_modes(out, delta); + + if (print_index) + git_str_printf(out, "index %s..%s\n", start_oid, end_oid); + } + + return git_str_oom(out) ? -1 : 0; +} + +static int diff_delta_format_path( + git_str *out, const char *prefix, const char *filename) +{ + if (git_str_joinpath(out, prefix, filename) < 0) + return -1; + + return git_str_quote(out); +} + +static int diff_delta_format_with_paths( + git_str *out, + const git_diff_delta *delta, + const char *template, + const char *oldpath, + const char *newpath) +{ + if (git_oid_is_zero(&delta->old_file.id)) + oldpath = "/dev/null"; + + if (git_oid_is_zero(&delta->new_file.id)) + newpath = "/dev/null"; + + return git_str_printf(out, template, oldpath, newpath); +} + +static int diff_delta_format_similarity_header( + git_str *out, + const git_diff_delta *delta) +{ + git_str old_path = GIT_STR_INIT, new_path = GIT_STR_INIT; + const char *type; + int error = 0; + + if (delta->similarity > 100) { + git_error_set(GIT_ERROR_PATCH, "invalid similarity %d", delta->similarity); + error = -1; + goto done; + } + + GIT_ASSERT(delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED); + if (delta->status == GIT_DELTA_RENAMED) + type = "rename"; + else + type = "copy"; + + if ((error = git_str_puts(&old_path, delta->old_file.path)) < 0 || + (error = git_str_puts(&new_path, delta->new_file.path)) < 0 || + (error = git_str_quote(&old_path)) < 0 || + (error = git_str_quote(&new_path)) < 0) + goto done; + + git_str_printf(out, + "similarity index %d%%\n" + "%s from %s\n" + "%s to %s\n", + delta->similarity, + type, old_path.ptr, + type, new_path.ptr); + + if (git_str_oom(out)) + error = -1; + +done: + git_str_dispose(&old_path); + git_str_dispose(&new_path); + + return error; +} + +static bool delta_is_unchanged(const git_diff_delta *delta) +{ + if (git_oid_is_zero(&delta->old_file.id) && + git_oid_is_zero(&delta->new_file.id)) + return true; + + if (delta->old_file.mode == GIT_FILEMODE_COMMIT || + delta->new_file.mode == GIT_FILEMODE_COMMIT) + return false; + + if (git_oid_equal(&delta->old_file.id, &delta->new_file.id)) + return true; + + return false; +} + +int git_diff_delta__format_file_header( + git_str *out, + const git_diff_delta *delta, + const char *oldpfx, + const char *newpfx, + int id_strlen, + bool print_index) +{ + git_str old_path = GIT_STR_INIT, new_path = GIT_STR_INIT; + bool unchanged = delta_is_unchanged(delta); + int error = 0; + + if (!oldpfx) + oldpfx = DIFF_OLD_PREFIX_DEFAULT; + if (!newpfx) + newpfx = DIFF_NEW_PREFIX_DEFAULT; + if (!id_strlen) + id_strlen = GIT_ABBREV_DEFAULT; + + if ((error = diff_delta_format_path( + &old_path, oldpfx, delta->old_file.path)) < 0 || + (error = diff_delta_format_path( + &new_path, newpfx, delta->new_file.path)) < 0) + goto done; + + git_str_clear(out); + + git_str_printf(out, "diff --git %s %s\n", + old_path.ptr, new_path.ptr); + + if (unchanged && delta->old_file.mode != delta->new_file.mode) + diff_print_modes(out, delta); + + if (delta->status == GIT_DELTA_RENAMED || + (delta->status == GIT_DELTA_COPIED && unchanged)) { + if ((error = diff_delta_format_similarity_header(out, delta)) < 0) + goto done; + } + + if (!unchanged) { + if ((error = diff_print_oid_range(out, delta, + id_strlen, print_index)) < 0) + goto done; + + if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) + diff_delta_format_with_paths(out, delta, + "--- %s\n+++ %s\n", old_path.ptr, new_path.ptr); + } + + if (git_str_oom(out)) + error = -1; + +done: + git_str_dispose(&old_path); + git_str_dispose(&new_path); + + return error; +} + +static int format_binary( + diff_print_info *pi, + git_diff_binary_t type, + const char *data, + size_t datalen, + size_t inflatedlen) +{ + const char *typename = type == GIT_DIFF_BINARY_DELTA ? + "delta" : "literal"; + const char *scan, *end; + + git_str_printf(pi->buf, "%s %" PRIuZ "\n", typename, inflatedlen); + pi->line.num_lines++; + + for (scan = data, end = data + datalen; scan < end; ) { + size_t chunk_len = end - scan; + if (chunk_len > 52) + chunk_len = 52; + + if (chunk_len <= 26) + git_str_putc(pi->buf, (char)chunk_len + 'A' - 1); + else + git_str_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1); + + git_str_encode_base85(pi->buf, scan, chunk_len); + git_str_putc(pi->buf, '\n'); + + if (git_str_oom(pi->buf)) + return -1; + + scan += chunk_len; + pi->line.num_lines++; + } + git_str_putc(pi->buf, '\n'); + + if (git_str_oom(pi->buf)) + return -1; + + return 0; +} + +static int diff_print_patch_file_binary_noshow( + diff_print_info *pi, git_diff_delta *delta, + const char *old_pfx, const char *new_pfx) +{ + git_str old_path = GIT_STR_INIT, new_path = GIT_STR_INIT; + int error; + + if ((error = diff_delta_format_path(&old_path, old_pfx, delta->old_file.path)) < 0 || + (error = diff_delta_format_path(&new_path, new_pfx, delta->new_file.path)) < 0 || + (error = diff_delta_format_with_paths(pi->buf, delta, "Binary files %s and %s differ\n", + old_path.ptr, new_path.ptr)) < 0) + goto done; + + pi->line.num_lines = 1; + +done: + git_str_dispose(&old_path); + git_str_dispose(&new_path); + return error; +} + +static int diff_print_patch_file_binary( + diff_print_info *pi, git_diff_delta *delta, + const char *old_pfx, const char *new_pfx, + const git_diff_binary *binary) +{ + size_t pre_binary_size; + int error; + + if (delta->status == GIT_DELTA_UNMODIFIED) + return 0; + + if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0 || !binary->contains_data) + return diff_print_patch_file_binary_noshow( + pi, delta, old_pfx, new_pfx); + + pre_binary_size = pi->buf->size; + git_str_printf(pi->buf, "GIT binary patch\n"); + pi->line.num_lines++; + + if ((error = format_binary(pi, binary->new_file.type, binary->new_file.data, + binary->new_file.datalen, binary->new_file.inflatedlen)) < 0 || + (error = format_binary(pi, binary->old_file.type, binary->old_file.data, + binary->old_file.datalen, binary->old_file.inflatedlen)) < 0) { + if (error == GIT_EBUFS) { + git_error_clear(); + git_str_truncate(pi->buf, pre_binary_size); + + return diff_print_patch_file_binary_noshow( + pi, delta, old_pfx, new_pfx); + } + } + + pi->line.num_lines++; + return error; +} + +static int diff_print_patch_file( + const git_diff_delta *delta, float progress, void *data) +{ + int error; + diff_print_info *pi = data; + const char *oldpfx = + pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT; + const char *newpfx = + pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; + + bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) || + (pi->flags & GIT_DIFF_FORCE_BINARY); + bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY); + int id_strlen = pi->id_strlen; + bool print_index = (pi->format != GIT_DIFF_FORMAT_PATCH_ID); + + if (binary && show_binary) + id_strlen = delta->old_file.id_abbrev ? delta->old_file.id_abbrev : + delta->new_file.id_abbrev; + + GIT_UNUSED(progress); + + if (S_ISDIR(delta->new_file.mode) || + delta->status == GIT_DELTA_UNMODIFIED || + delta->status == GIT_DELTA_IGNORED || + delta->status == GIT_DELTA_UNREADABLE || + (delta->status == GIT_DELTA_UNTRACKED && + (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0)) + return 0; + + if ((error = git_diff_delta__format_file_header(pi->buf, delta, oldpfx, newpfx, + id_strlen, print_index)) < 0) + return error; + + pi->line.origin = GIT_DIFF_LINE_FILE_HDR; + pi->line.content = git_str_cstr(pi->buf); + pi->line.content_len = git_str_len(pi->buf); + + return pi->print_cb(delta, NULL, &pi->line, pi->payload); +} + +static int diff_print_patch_binary( + const git_diff_delta *delta, + const git_diff_binary *binary, + void *data) +{ + diff_print_info *pi = data; + const char *old_pfx = + pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT; + const char *new_pfx = + pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; + int error; + + git_str_clear(pi->buf); + + if ((error = diff_print_patch_file_binary( + pi, (git_diff_delta *)delta, old_pfx, new_pfx, binary)) < 0) + return error; + + pi->line.origin = GIT_DIFF_LINE_BINARY; + pi->line.content = git_str_cstr(pi->buf); + pi->line.content_len = git_str_len(pi->buf); + + return pi->print_cb(delta, NULL, &pi->line, pi->payload); +} + +static int diff_print_patch_hunk( + const git_diff_delta *d, + const git_diff_hunk *h, + void *data) +{ + diff_print_info *pi = data; + + if (S_ISDIR(d->new_file.mode)) + return 0; + + pi->line.origin = GIT_DIFF_LINE_HUNK_HDR; + pi->line.content = h->header; + pi->line.content_len = h->header_len; + + return pi->print_cb(d, h, &pi->line, pi->payload); +} + +static int diff_print_patch_line( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *data) +{ + diff_print_info *pi = data; + + if (S_ISDIR(delta->new_file.mode)) + return 0; + + return pi->print_cb(delta, hunk, line, pi->payload); +} + +/* print a git_diff to an output callback */ +int git_diff_print( + git_diff *diff, + git_diff_format_t format, + git_diff_line_cb print_cb, + void *payload) +{ + int error; + git_str buf = GIT_STR_INIT; + diff_print_info pi; + git_diff_file_cb print_file = NULL; + git_diff_binary_cb print_binary = NULL; + git_diff_hunk_cb print_hunk = NULL; + git_diff_line_cb print_line = NULL; + + switch (format) { + case GIT_DIFF_FORMAT_PATCH: + print_file = diff_print_patch_file; + print_binary = diff_print_patch_binary; + print_hunk = diff_print_patch_hunk; + print_line = diff_print_patch_line; + break; + case GIT_DIFF_FORMAT_PATCH_ID: + print_file = diff_print_patch_file; + print_binary = diff_print_patch_binary; + print_line = diff_print_patch_line; + break; + case GIT_DIFF_FORMAT_PATCH_HEADER: + print_file = diff_print_patch_file; + break; + case GIT_DIFF_FORMAT_RAW: + print_file = diff_print_one_raw; + break; + case GIT_DIFF_FORMAT_NAME_ONLY: + print_file = diff_print_one_name_only; + break; + case GIT_DIFF_FORMAT_NAME_STATUS: + print_file = diff_print_one_name_status; + break; + default: + git_error_set(GIT_ERROR_INVALID, "unknown diff output format (%d)", format); + return -1; + } + + if ((error = diff_print_info_init_fromdiff(&pi, &buf, diff, format, print_cb, payload)) < 0) + goto out; + + if ((error = git_diff_foreach(diff, print_file, print_binary, print_hunk, print_line, &pi)) != 0) { + git_error_set_after_callback_function(error, "git_diff_print"); + goto out; + } + +out: + git_str_dispose(&buf); + return error; +} + +int git_diff_print_callback__to_buf( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + git_str *output = payload; + GIT_UNUSED(delta); GIT_UNUSED(hunk); + + if (!output) { + git_error_set(GIT_ERROR_INVALID, "buffer pointer must be provided"); + return -1; + } + + if (line->origin == GIT_DIFF_LINE_ADDITION || + line->origin == GIT_DIFF_LINE_DELETION || + line->origin == GIT_DIFF_LINE_CONTEXT) + git_str_putc(output, line->origin); + + return git_str_put(output, line->content, line->content_len); +} + +int git_diff_print_callback__to_file_handle( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + FILE *fp = payload ? payload : stdout; + int error; + + GIT_UNUSED(delta); + GIT_UNUSED(hunk); + + if (line->origin == GIT_DIFF_LINE_CONTEXT || + line->origin == GIT_DIFF_LINE_ADDITION || + line->origin == GIT_DIFF_LINE_DELETION) { + while ((error = fputc(line->origin, fp)) == EINTR) + continue; + if (error) { + git_error_set(GIT_ERROR_OS, "could not write status"); + return -1; + } + } + + if (fwrite(line->content, line->content_len, 1, fp) != 1) { + git_error_set(GIT_ERROR_OS, "could not write line"); + return -1; + } + + return 0; +} + +/* print a git_diff to a git_str */ +int git_diff_to_buf(git_buf *out, git_diff *diff, git_diff_format_t format) +{ + git_str str = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diff); + + if ((error = git_buf_tostr(&str, out)) < 0 || + (error = git_diff_print(diff, format, git_diff_print_callback__to_buf, &str)) < 0) + goto done; + + error = git_buf_fromstr(out, &str); + +done: + git_str_dispose(&str); + return error; +} + +/* print a git_patch to an output callback */ +int git_patch_print( + git_patch *patch, + git_diff_line_cb print_cb, + void *payload) +{ + git_str temp = GIT_STR_INIT; + diff_print_info pi; + int error; + + GIT_ASSERT_ARG(patch); + GIT_ASSERT_ARG(print_cb); + + if ((error = diff_print_info_init_frompatch(&pi, &temp, patch, + GIT_DIFF_FORMAT_PATCH, print_cb, payload)) < 0) + goto out; + + if ((error = git_patch__invoke_callbacks(patch, diff_print_patch_file, diff_print_patch_binary, + diff_print_patch_hunk, diff_print_patch_line, &pi)) < 0) { + git_error_set_after_callback_function(error, "git_patch_print"); + goto out; + } + +out: + git_str_dispose(&temp); + return error; +} + +/* print a git_patch to a git_str */ +int git_patch_to_buf(git_buf *out, git_patch *patch) +{ + GIT_BUF_WRAP_PRIVATE(out, git_patch__to_buf, patch); +} + +int git_patch__to_buf(git_str *out, git_patch *patch) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(patch); + + return git_patch_print(patch, git_diff_print_callback__to_buf, out); +} diff --git a/src/libgit2/diff_stats.c b/src/libgit2/diff_stats.c new file mode 100644 index 000000000..259939844 --- /dev/null +++ b/src/libgit2/diff_stats.c @@ -0,0 +1,376 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "diff_stats.h" + +#include "buf.h" +#include "common.h" +#include "vector.h" +#include "diff.h" +#include "patch_generate.h" + +#define DIFF_RENAME_FILE_SEPARATOR " => " +#define STATS_FULL_MIN_SCALE 7 + +typedef struct { + size_t insertions; + size_t deletions; +} diff_file_stats; + +struct git_diff_stats { + git_diff *diff; + diff_file_stats *filestats; + + size_t files_changed; + size_t insertions; + size_t deletions; + size_t renames; + + size_t max_name; + size_t max_filestat; + int max_digits; +}; + +static int digits_for_value(size_t val) +{ + int count = 1; + size_t placevalue = 10; + + while (val >= placevalue) { + ++count; + placevalue *= 10; + } + + return count; +} + +static int diff_file_stats_full_to_buf( + git_str *out, + const git_diff_delta *delta, + const diff_file_stats *filestat, + const git_diff_stats *stats, + size_t width) +{ + const char *old_path = NULL, *new_path = NULL, *adddel_path = NULL; + size_t padding; + git_object_size_t old_size, new_size; + + old_path = delta->old_file.path; + new_path = delta->new_file.path; + old_size = delta->old_file.size; + new_size = delta->new_file.size; + + if (old_path && new_path && strcmp(old_path, new_path) != 0) { + size_t common_dirlen; + int error; + + padding = stats->max_name - strlen(old_path) - strlen(new_path); + + if ((common_dirlen = git_fs_path_common_dirlen(old_path, new_path)) && + common_dirlen <= INT_MAX) { + error = git_str_printf(out, " %.*s{%s"DIFF_RENAME_FILE_SEPARATOR"%s}", + (int) common_dirlen, old_path, + old_path + common_dirlen, + new_path + common_dirlen); + } else { + error = git_str_printf(out, " %s" DIFF_RENAME_FILE_SEPARATOR "%s", + old_path, new_path); + } + + if (error < 0) + goto on_error; + } else { + adddel_path = new_path ? new_path : old_path; + if (git_str_printf(out, " %s", adddel_path) < 0) + goto on_error; + + padding = stats->max_name - strlen(adddel_path); + + if (stats->renames > 0) + padding += strlen(DIFF_RENAME_FILE_SEPARATOR); + } + + if (git_str_putcn(out, ' ', padding) < 0 || + git_str_puts(out, " | ") < 0) + goto on_error; + + if (delta->flags & GIT_DIFF_FLAG_BINARY) { + if (git_str_printf(out, + "Bin %" PRId64 " -> %" PRId64 " bytes", old_size, new_size) < 0) + goto on_error; + } + else { + if (git_str_printf(out, + "%*" PRIuZ, stats->max_digits, + filestat->insertions + filestat->deletions) < 0) + goto on_error; + + if (filestat->insertions || filestat->deletions) { + if (git_str_putc(out, ' ') < 0) + goto on_error; + + if (!width) { + if (git_str_putcn(out, '+', filestat->insertions) < 0 || + git_str_putcn(out, '-', filestat->deletions) < 0) + goto on_error; + } else { + size_t total = filestat->insertions + filestat->deletions; + size_t full = (total * width + stats->max_filestat / 2) / + stats->max_filestat; + size_t plus = full * filestat->insertions / total; + size_t minus = full - plus; + + if (git_str_putcn(out, '+', max(plus, 1)) < 0 || + git_str_putcn(out, '-', max(minus, 1)) < 0) + goto on_error; + } + } + } + + git_str_putc(out, '\n'); + +on_error: + return (git_str_oom(out) ? -1 : 0); +} + +static int diff_file_stats_number_to_buf( + git_str *out, + const git_diff_delta *delta, + const diff_file_stats *filestats) +{ + int error; + const char *path = delta->new_file.path; + + if (delta->flags & GIT_DIFF_FLAG_BINARY) + error = git_str_printf(out, "%-8c" "%-8c" "%s\n", '-', '-', path); + else + error = git_str_printf(out, "%-8" PRIuZ "%-8" PRIuZ "%s\n", + filestats->insertions, filestats->deletions, path); + + return error; +} + +static int diff_file_stats_summary_to_buf( + git_str *out, + const git_diff_delta *delta) +{ + if (delta->old_file.mode != delta->new_file.mode) { + if (delta->old_file.mode == 0) { + git_str_printf(out, " create mode %06o %s\n", + delta->new_file.mode, delta->new_file.path); + } + else if (delta->new_file.mode == 0) { + git_str_printf(out, " delete mode %06o %s\n", + delta->old_file.mode, delta->old_file.path); + } + else { + git_str_printf(out, " mode change %06o => %06o %s\n", + delta->old_file.mode, delta->new_file.mode, delta->new_file.path); + } + } + + return 0; +} + +int git_diff_get_stats( + git_diff_stats **out, + git_diff *diff) +{ + size_t i, deltas; + size_t total_insertions = 0, total_deletions = 0; + git_diff_stats *stats = NULL; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diff); + + stats = git__calloc(1, sizeof(git_diff_stats)); + GIT_ERROR_CHECK_ALLOC(stats); + + deltas = git_diff_num_deltas(diff); + + stats->filestats = git__calloc(deltas, sizeof(diff_file_stats)); + if (!stats->filestats) { + git__free(stats); + return -1; + } + + stats->diff = diff; + GIT_REFCOUNT_INC(diff); + + for (i = 0; i < deltas && !error; ++i) { + git_patch *patch = NULL; + size_t add = 0, remove = 0, namelen; + const git_diff_delta *delta; + + if ((error = git_patch_from_diff(&patch, diff, i)) < 0) + break; + + /* keep a count of renames because it will affect formatting */ + delta = patch->delta; + + /* TODO ugh */ + namelen = strlen(delta->new_file.path); + if (delta->old_file.path && strcmp(delta->old_file.path, delta->new_file.path) != 0) { + namelen += strlen(delta->old_file.path); + stats->renames++; + } + + /* and, of course, count the line stats */ + error = git_patch_line_stats(NULL, &add, &remove, patch); + + git_patch_free(patch); + + stats->filestats[i].insertions = add; + stats->filestats[i].deletions = remove; + + total_insertions += add; + total_deletions += remove; + + if (stats->max_name < namelen) + stats->max_name = namelen; + if (stats->max_filestat < add + remove) + stats->max_filestat = add + remove; + } + + stats->files_changed = deltas; + stats->insertions = total_insertions; + stats->deletions = total_deletions; + stats->max_digits = digits_for_value(stats->max_filestat + 1); + + if (error < 0) { + git_diff_stats_free(stats); + stats = NULL; + } + + *out = stats; + return error; +} + +size_t git_diff_stats_files_changed( + const git_diff_stats *stats) +{ + GIT_ASSERT_ARG(stats); + + return stats->files_changed; +} + +size_t git_diff_stats_insertions( + const git_diff_stats *stats) +{ + GIT_ASSERT_ARG(stats); + + return stats->insertions; +} + +size_t git_diff_stats_deletions( + const git_diff_stats *stats) +{ + GIT_ASSERT_ARG(stats); + + return stats->deletions; +} + +int git_diff_stats_to_buf( + git_buf *out, + const git_diff_stats *stats, + git_diff_stats_format_t format, + size_t width) +{ + GIT_BUF_WRAP_PRIVATE(out, git_diff__stats_to_buf, stats, format, width); +} + +int git_diff__stats_to_buf( + git_str *out, + const git_diff_stats *stats, + git_diff_stats_format_t format, + size_t width) +{ + int error = 0; + size_t i; + const git_diff_delta *delta; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(stats); + + if (format & GIT_DIFF_STATS_NUMBER) { + for (i = 0; i < stats->files_changed; ++i) { + if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) + continue; + + error = diff_file_stats_number_to_buf( + out, delta, &stats->filestats[i]); + if (error < 0) + return error; + } + } + + if (format & GIT_DIFF_STATS_FULL) { + if (width > 0) { + if (width > stats->max_name + stats->max_digits + 5) + width -= (stats->max_name + stats->max_digits + 5); + if (width < STATS_FULL_MIN_SCALE) + width = STATS_FULL_MIN_SCALE; + } + if (width > stats->max_filestat) + width = 0; + + for (i = 0; i < stats->files_changed; ++i) { + if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) + continue; + + error = diff_file_stats_full_to_buf( + out, delta, &stats->filestats[i], stats, width); + if (error < 0) + return error; + } + } + + if (format & GIT_DIFF_STATS_FULL || format & GIT_DIFF_STATS_SHORT) { + git_str_printf( + out, " %" PRIuZ " file%s changed", + stats->files_changed, stats->files_changed != 1 ? "s" : ""); + + if (stats->insertions || stats->deletions == 0) + git_str_printf( + out, ", %" PRIuZ " insertion%s(+)", + stats->insertions, stats->insertions != 1 ? "s" : ""); + + if (stats->deletions || stats->insertions == 0) + git_str_printf( + out, ", %" PRIuZ " deletion%s(-)", + stats->deletions, stats->deletions != 1 ? "s" : ""); + + git_str_putc(out, '\n'); + + if (git_str_oom(out)) + return -1; + } + + if (format & GIT_DIFF_STATS_INCLUDE_SUMMARY) { + for (i = 0; i < stats->files_changed; ++i) { + if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) + continue; + + error = diff_file_stats_summary_to_buf(out, delta); + if (error < 0) + return error; + } + } + + return error; +} + +void git_diff_stats_free(git_diff_stats *stats) +{ + if (stats == NULL) + return; + + git_diff_free(stats->diff); /* bumped refcount in constructor */ + git__free(stats->filestats); + git__free(stats); +} diff --git a/src/libgit2/diff_stats.h b/src/libgit2/diff_stats.h new file mode 100644 index 000000000..c71862b4e --- /dev/null +++ b/src/libgit2/diff_stats.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_diff_stats_h__ +#define INCLUDE_diff_stats_h__ + +#include "common.h" + +int git_diff__stats_to_buf( + git_str *out, + const git_diff_stats *stats, + git_diff_stats_format_t format, + size_t width); + +#endif diff --git a/src/libgit2/diff_tform.c b/src/libgit2/diff_tform.c new file mode 100644 index 000000000..913d649b0 --- /dev/null +++ b/src/libgit2/diff_tform.c @@ -0,0 +1,1121 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "diff_tform.h" + +#include "git2/config.h" +#include "git2/blob.h" +#include "git2/sys/hashsig.h" + +#include "diff.h" +#include "diff_generate.h" +#include "fs_path.h" +#include "futils.h" +#include "config.h" + +git_diff_delta *git_diff__delta_dup( + const git_diff_delta *d, git_pool *pool) +{ + git_diff_delta *delta = git__malloc(sizeof(git_diff_delta)); + if (!delta) + return NULL; + + memcpy(delta, d, sizeof(git_diff_delta)); + GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags); + + if (d->old_file.path != NULL) { + delta->old_file.path = git_pool_strdup(pool, d->old_file.path); + if (delta->old_file.path == NULL) + goto fail; + } + + if (d->new_file.path != d->old_file.path && d->new_file.path != NULL) { + delta->new_file.path = git_pool_strdup(pool, d->new_file.path); + if (delta->new_file.path == NULL) + goto fail; + } else { + delta->new_file.path = delta->old_file.path; + } + + return delta; + +fail: + git__free(delta); + return NULL; +} + +git_diff_delta *git_diff__merge_like_cgit( + const git_diff_delta *a, + const git_diff_delta *b, + git_pool *pool) +{ + git_diff_delta *dup; + + /* Emulate C git for merging two diffs (a la 'git diff '). + * + * When C git does a diff between the work dir and a tree, it actually + * diffs with the index but uses the workdir contents. This emulates + * those choices so we can emulate the type of diff. + * + * We have three file descriptions here, let's call them: + * f1 = a->old_file + * f2 = a->new_file AND b->old_file + * f3 = b->new_file + */ + + /* If one of the diffs is a conflict, just dup it */ + if (b->status == GIT_DELTA_CONFLICTED) + return git_diff__delta_dup(b, pool); + if (a->status == GIT_DELTA_CONFLICTED) + return git_diff__delta_dup(a, pool); + + /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */ + if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED) + return git_diff__delta_dup(a, pool); + + /* otherwise, base this diff on the 'b' diff */ + if ((dup = git_diff__delta_dup(b, pool)) == NULL) + return NULL; + + /* If 'a' status is uninteresting, then we're done */ + if (a->status == GIT_DELTA_UNMODIFIED || + a->status == GIT_DELTA_UNTRACKED || + a->status == GIT_DELTA_UNREADABLE) + return dup; + + GIT_ASSERT_WITH_RETVAL(b->status != GIT_DELTA_UNMODIFIED, NULL); + + /* A cgit exception is that the diff of a file that is only in the + * index (i.e. not in HEAD nor workdir) is given as empty. + */ + if (dup->status == GIT_DELTA_DELETED) { + if (a->status == GIT_DELTA_ADDED) { + dup->status = GIT_DELTA_UNMODIFIED; + dup->nfiles = 2; + } + /* else don't overwrite DELETE status */ + } else { + dup->status = a->status; + dup->nfiles = a->nfiles; + } + + git_oid_cpy(&dup->old_file.id, &a->old_file.id); + dup->old_file.mode = a->old_file.mode; + dup->old_file.size = a->old_file.size; + dup->old_file.flags = a->old_file.flags; + + return dup; +} + +int git_diff__merge( + git_diff *onto, const git_diff *from, git_diff__merge_cb cb) +{ + int error = 0; + git_pool onto_pool; + git_vector onto_new; + git_diff_delta *delta; + bool ignore_case, reversed; + unsigned int i, j; + + GIT_ASSERT_ARG(onto); + GIT_ASSERT_ARG(from); + + if (!from->deltas.length) + return 0; + + ignore_case = ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0); + reversed = ((onto->opts.flags & GIT_DIFF_REVERSE) != 0); + + if (ignore_case != ((from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0) || + reversed != ((from->opts.flags & GIT_DIFF_REVERSE) != 0)) { + git_error_set(GIT_ERROR_INVALID, + "attempt to merge diffs created with conflicting options"); + return -1; + } + + if (git_vector_init(&onto_new, onto->deltas.length, git_diff_delta__cmp) < 0 || + git_pool_init(&onto_pool, 1) < 0) + return -1; + + for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) { + git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i); + const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j); + int cmp = !f ? -1 : !o ? 1 : + STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path); + + if (cmp < 0) { + delta = git_diff__delta_dup(o, &onto_pool); + i++; + } else if (cmp > 0) { + delta = git_diff__delta_dup(f, &onto_pool); + j++; + } else { + const git_diff_delta *left = reversed ? f : o; + const git_diff_delta *right = reversed ? o : f; + + delta = cb(left, right, &onto_pool); + i++; + j++; + } + + /* the ignore rules for the target may not match the source + * or the result of a merged delta could be skippable... + */ + if (delta && git_diff_delta__should_skip(&onto->opts, delta)) { + git__free(delta); + continue; + } + + if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0) + break; + } + + if (!error) { + git_vector_swap(&onto->deltas, &onto_new); + git_pool_swap(&onto->pool, &onto_pool); + + if ((onto->opts.flags & GIT_DIFF_REVERSE) != 0) + onto->old_src = from->old_src; + else + onto->new_src = from->new_src; + + /* prefix strings also come from old pool, so recreate those.*/ + onto->opts.old_prefix = + git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix); + onto->opts.new_prefix = + git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix); + } + + git_vector_free_deep(&onto_new); + git_pool_clear(&onto_pool); + + return error; +} + +int git_diff_merge(git_diff *onto, const git_diff *from) +{ + return git_diff__merge(onto, from, git_diff__merge_like_cgit); +} + +int git_diff_find_similar__hashsig_for_file( + void **out, const git_diff_file *f, const char *path, void *p) +{ + git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p; + + GIT_UNUSED(f); + return git_hashsig_create_fromfile((git_hashsig **)out, path, opt); +} + +int git_diff_find_similar__hashsig_for_buf( + void **out, const git_diff_file *f, const char *buf, size_t len, void *p) +{ + git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p; + + GIT_UNUSED(f); + return git_hashsig_create((git_hashsig **)out, buf, len, opt); +} + +void git_diff_find_similar__hashsig_free(void *sig, void *payload) +{ + GIT_UNUSED(payload); + git_hashsig_free(sig); +} + +int git_diff_find_similar__calc_similarity( + int *score, void *siga, void *sigb, void *payload) +{ + int error; + + GIT_UNUSED(payload); + error = git_hashsig_compare(siga, sigb); + if (error < 0) + return error; + + *score = error; + return 0; +} + +#define DEFAULT_THRESHOLD 50 +#define DEFAULT_BREAK_REWRITE_THRESHOLD 60 +#define DEFAULT_RENAME_LIMIT 1000 + +static int normalize_find_opts( + git_diff *diff, + git_diff_find_options *opts, + const git_diff_find_options *given) +{ + git_config *cfg = NULL; + git_hashsig_option_t hashsig_opts; + + GIT_ERROR_CHECK_VERSION(given, GIT_DIFF_FIND_OPTIONS_VERSION, "git_diff_find_options"); + + if (diff->repo != NULL && + git_repository_config__weakptr(&cfg, diff->repo) < 0) + return -1; + + if (given) + memcpy(opts, given, sizeof(*opts)); + + if (!given || + (given->flags & GIT_DIFF_FIND_ALL) == GIT_DIFF_FIND_BY_CONFIG) + { + if (cfg) { + char *rule = + git_config__get_string_force(cfg, "diff.renames", "true"); + int boolval; + + if (!git__parse_bool(&boolval, rule) && !boolval) + /* don't set FIND_RENAMES if bool value is false */; + else if (!strcasecmp(rule, "copies") || !strcasecmp(rule, "copy")) + opts->flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; + else + opts->flags |= GIT_DIFF_FIND_RENAMES; + + git__free(rule); + } else { + /* set default flag */ + opts->flags |= GIT_DIFF_FIND_RENAMES; + } + } + + /* some flags imply others */ + + if (opts->flags & GIT_DIFF_FIND_EXACT_MATCH_ONLY) { + /* if we are only looking for exact matches, then don't turn + * MODIFIED items into ADD/DELETE pairs because it's too picky + */ + opts->flags &= ~(GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES); + + /* similarly, don't look for self-rewrites to split */ + opts->flags &= ~GIT_DIFF_FIND_RENAMES_FROM_REWRITES; + } + + if (opts->flags & GIT_DIFF_FIND_RENAMES_FROM_REWRITES) + opts->flags |= GIT_DIFF_FIND_RENAMES; + + if (opts->flags & GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED) + opts->flags |= GIT_DIFF_FIND_COPIES; + + if (opts->flags & GIT_DIFF_BREAK_REWRITES) + opts->flags |= GIT_DIFF_FIND_REWRITES; + +#define USE_DEFAULT(X) ((X) == 0 || (X) > 100) + + if (USE_DEFAULT(opts->rename_threshold)) + opts->rename_threshold = DEFAULT_THRESHOLD; + + if (USE_DEFAULT(opts->rename_from_rewrite_threshold)) + opts->rename_from_rewrite_threshold = DEFAULT_THRESHOLD; + + if (USE_DEFAULT(opts->copy_threshold)) + opts->copy_threshold = DEFAULT_THRESHOLD; + + if (USE_DEFAULT(opts->break_rewrite_threshold)) + opts->break_rewrite_threshold = DEFAULT_BREAK_REWRITE_THRESHOLD; + +#undef USE_DEFAULT + + if (!opts->rename_limit) { + if (cfg) { + opts->rename_limit = git_config__get_int_force( + cfg, "diff.renamelimit", DEFAULT_RENAME_LIMIT); + } + + if (opts->rename_limit <= 0) + opts->rename_limit = DEFAULT_RENAME_LIMIT; + } + + /* assign the internal metric with whitespace flag as payload */ + if (!opts->metric) { + opts->metric = git__malloc(sizeof(git_diff_similarity_metric)); + GIT_ERROR_CHECK_ALLOC(opts->metric); + + opts->metric->file_signature = git_diff_find_similar__hashsig_for_file; + opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf; + opts->metric->free_signature = git_diff_find_similar__hashsig_free; + opts->metric->similarity = git_diff_find_similar__calc_similarity; + + if (opts->flags & GIT_DIFF_FIND_IGNORE_WHITESPACE) + hashsig_opts = GIT_HASHSIG_IGNORE_WHITESPACE; + else if (opts->flags & GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE) + hashsig_opts = GIT_HASHSIG_NORMAL; + else + hashsig_opts = GIT_HASHSIG_SMART_WHITESPACE; + hashsig_opts |= GIT_HASHSIG_ALLOW_SMALL_FILES; + opts->metric->payload = (void *)hashsig_opts; + } + + return 0; +} + +static int insert_delete_side_of_split( + git_diff *diff, git_vector *onto, const git_diff_delta *delta) +{ + /* make new record for DELETED side of split */ + git_diff_delta *deleted = git_diff__delta_dup(delta, &diff->pool); + GIT_ERROR_CHECK_ALLOC(deleted); + + deleted->status = GIT_DELTA_DELETED; + deleted->nfiles = 1; + memset(&deleted->new_file, 0, sizeof(deleted->new_file)); + deleted->new_file.path = deleted->old_file.path; + deleted->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + return git_vector_insert(onto, deleted); +} + +static int apply_splits_and_deletes( + git_diff *diff, size_t expected_size, bool actually_split) +{ + git_vector onto = GIT_VECTOR_INIT; + size_t i; + git_diff_delta *delta; + + if (git_vector_init(&onto, expected_size, diff->deltas._cmp) < 0) + return -1; + + /* build new delta list without TO_DELETE and splitting TO_SPLIT */ + git_vector_foreach(&diff->deltas, i, delta) { + if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0) + continue; + + if ((delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0 && actually_split) { + delta->similarity = 0; + + if (insert_delete_side_of_split(diff, &onto, delta) < 0) + goto on_error; + + if (diff->new_src == GIT_ITERATOR_WORKDIR) + delta->status = GIT_DELTA_UNTRACKED; + else + delta->status = GIT_DELTA_ADDED; + delta->nfiles = 1; + memset(&delta->old_file, 0, sizeof(delta->old_file)); + delta->old_file.path = delta->new_file.path; + delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; + } + + /* clean up delta before inserting into new list */ + GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags); + + if (delta->status != GIT_DELTA_COPIED && + delta->status != GIT_DELTA_RENAMED && + (delta->status != GIT_DELTA_MODIFIED || actually_split)) + delta->similarity = 0; + + /* insert into new list */ + if (git_vector_insert(&onto, delta) < 0) + goto on_error; + } + + /* cannot return an error past this point */ + + /* free deltas from old list that didn't make it to the new one */ + git_vector_foreach(&diff->deltas, i, delta) { + if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0) + git__free(delta); + } + + /* swap new delta list into place */ + git_vector_swap(&diff->deltas, &onto); + git_vector_free(&onto); + git_vector_sort(&diff->deltas); + + return 0; + +on_error: + git_vector_free_deep(&onto); + + return -1; +} + +GIT_INLINE(git_diff_file *) similarity_get_file(git_diff *diff, size_t idx) +{ + git_diff_delta *delta = git_vector_get(&diff->deltas, idx / 2); + return (idx & 1) ? &delta->new_file : &delta->old_file; +} + +typedef struct { + size_t idx; + git_iterator_t src; + git_repository *repo; + git_diff_file *file; + git_str data; + git_odb_object *odb_obj; + git_blob *blob; +} similarity_info; + +static int similarity_init( + similarity_info *info, git_diff *diff, size_t file_idx) +{ + info->idx = file_idx; + info->src = (file_idx & 1) ? diff->new_src : diff->old_src; + info->repo = diff->repo; + info->file = similarity_get_file(diff, file_idx); + info->odb_obj = NULL; + info->blob = NULL; + git_str_init(&info->data, 0); + + if ((info->file->flags & GIT_DIFF_FLAG_VALID_SIZE) || + info->src == GIT_ITERATOR_WORKDIR) + return 0; + + return git_diff_file__resolve_zero_size( + info->file, &info->odb_obj, info->repo); +} + +static int similarity_sig( + similarity_info *info, + const git_diff_find_options *opts, + void **cache) +{ + int error = 0; + git_diff_file *file = info->file; + + if (info->src == GIT_ITERATOR_WORKDIR) { + if ((error = git_repository_workdir_path( + &info->data, info->repo, file->path)) < 0) + return error; + + /* if path is not a regular file, just skip this item */ + if (!git_fs_path_isfile(info->data.ptr)) + return 0; + + /* TODO: apply wd-to-odb filters to file data if necessary */ + + error = opts->metric->file_signature( + &cache[info->idx], info->file, + info->data.ptr, opts->metric->payload); + } else { + /* if we didn't initially know the size, we might have an odb_obj + * around from earlier, so convert that, otherwise load the blob now + */ + if (info->odb_obj != NULL) + error = git_object__from_odb_object( + (git_object **)&info->blob, info->repo, + info->odb_obj, GIT_OBJECT_BLOB); + else + error = git_blob_lookup(&info->blob, info->repo, &file->id); + + if (error < 0) { + /* if lookup fails, just skip this item in similarity calc */ + git_error_clear(); + } else { + size_t sz; + + /* index size may not be actual blob size if filtered */ + if (file->size != git_blob_rawsize(info->blob)) + file->size = git_blob_rawsize(info->blob); + + sz = git__is_sizet(file->size) ? (size_t)file->size : (size_t)-1; + + error = opts->metric->buffer_signature( + &cache[info->idx], info->file, + git_blob_rawcontent(info->blob), sz, opts->metric->payload); + } + } + + return error; +} + +static void similarity_unload(similarity_info *info) +{ + if (info->odb_obj) + git_odb_object_free(info->odb_obj); + + if (info->blob) + git_blob_free(info->blob); + else + git_str_dispose(&info->data); +} + +#define FLAG_SET(opts,flag_name) (((opts)->flags & flag_name) != 0) + +/* - score < 0 means files cannot be compared + * - score >= 100 means files are exact match + * - score == 0 means files are completely different + */ +static int similarity_measure( + int *score, + git_diff *diff, + const git_diff_find_options *opts, + void **cache, + size_t a_idx, + size_t b_idx) +{ + git_diff_file *a_file = similarity_get_file(diff, a_idx); + git_diff_file *b_file = similarity_get_file(diff, b_idx); + bool exact_match = FLAG_SET(opts, GIT_DIFF_FIND_EXACT_MATCH_ONLY); + int error = 0; + similarity_info a_info, b_info; + + *score = -1; + + /* don't try to compare things that aren't files */ + if (!GIT_MODE_ISBLOB(a_file->mode) || !GIT_MODE_ISBLOB(b_file->mode)) + return 0; + + /* if exact match is requested, force calculation of missing OIDs now */ + if (exact_match) { + if (git_oid_is_zero(&a_file->id) && + diff->old_src == GIT_ITERATOR_WORKDIR && + !git_diff__oid_for_file(&a_file->id, + diff, a_file->path, a_file->mode, a_file->size)) + a_file->flags |= GIT_DIFF_FLAG_VALID_ID; + + if (git_oid_is_zero(&b_file->id) && + diff->new_src == GIT_ITERATOR_WORKDIR && + !git_diff__oid_for_file(&b_file->id, + diff, b_file->path, b_file->mode, b_file->size)) + b_file->flags |= GIT_DIFF_FLAG_VALID_ID; + } + + /* check OID match as a quick test */ + if (git_oid__cmp(&a_file->id, &b_file->id) == 0) { + *score = 100; + return 0; + } + + /* don't calculate signatures if we are doing exact match */ + if (exact_match) { + *score = 0; + return 0; + } + + memset(&a_info, 0, sizeof(a_info)); + memset(&b_info, 0, sizeof(b_info)); + + /* set up similarity data (will try to update missing file sizes) */ + if (!cache[a_idx] && (error = similarity_init(&a_info, diff, a_idx)) < 0) + return error; + if (!cache[b_idx] && (error = similarity_init(&b_info, diff, b_idx)) < 0) + goto cleanup; + + /* check if file sizes are nowhere near each other */ + if (a_file->size > 127 && + b_file->size > 127 && + (a_file->size > (b_file->size << 3) || + b_file->size > (a_file->size << 3))) + goto cleanup; + + /* update signature cache if needed */ + if (!cache[a_idx]) { + if ((error = similarity_sig(&a_info, opts, cache)) < 0) + goto cleanup; + } + if (!cache[b_idx]) { + if ((error = similarity_sig(&b_info, opts, cache)) < 0) + goto cleanup; + } + + /* calculate similarity provided that the metric choose to process + * both the a and b files (some may not if file is too big, etc). + */ + if (cache[a_idx] && cache[b_idx]) + error = opts->metric->similarity( + score, cache[a_idx], cache[b_idx], opts->metric->payload); + +cleanup: + similarity_unload(&a_info); + similarity_unload(&b_info); + + return error; +} + +static int calc_self_similarity( + git_diff *diff, + const git_diff_find_options *opts, + size_t delta_idx, + void **cache) +{ + int error, similarity = -1; + git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); + + if ((delta->flags & GIT_DIFF_FLAG__HAS_SELF_SIMILARITY) != 0) + return 0; + + error = similarity_measure( + &similarity, diff, opts, cache, 2 * delta_idx, 2 * delta_idx + 1); + if (error < 0) + return error; + + if (similarity >= 0) { + delta->similarity = (uint16_t)similarity; + delta->flags |= GIT_DIFF_FLAG__HAS_SELF_SIMILARITY; + } + + return 0; +} + +static bool is_rename_target( + git_diff *diff, + const git_diff_find_options *opts, + size_t delta_idx, + void **cache) +{ + git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); + + /* skip things that aren't plain blobs */ + if (!GIT_MODE_ISBLOB(delta->new_file.mode)) + return false; + + /* only consider ADDED, RENAMED, COPIED, and split MODIFIED as + * targets; maybe include UNTRACKED if requested. + */ + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: + case GIT_DELTA_DELETED: + case GIT_DELTA_IGNORED: + case GIT_DELTA_CONFLICTED: + return false; + + case GIT_DELTA_MODIFIED: + if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) && + !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES)) + return false; + + if (calc_self_similarity(diff, opts, delta_idx, cache) < 0) + return false; + + if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) && + delta->similarity < opts->break_rewrite_threshold) { + delta->flags |= GIT_DIFF_FLAG__TO_SPLIT; + break; + } + if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) && + delta->similarity < opts->rename_from_rewrite_threshold) { + delta->flags |= GIT_DIFF_FLAG__TO_SPLIT; + break; + } + + return false; + + case GIT_DELTA_UNTRACKED: + if (!FLAG_SET(opts, GIT_DIFF_FIND_FOR_UNTRACKED)) + return false; + break; + + default: /* all other status values should be checked */ + break; + } + + delta->flags |= GIT_DIFF_FLAG__IS_RENAME_TARGET; + return true; +} + +static bool is_rename_source( + git_diff *diff, + const git_diff_find_options *opts, + size_t delta_idx, + void **cache) +{ + git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx); + + /* skip things that aren't blobs */ + if (!GIT_MODE_ISBLOB(delta->old_file.mode)) + return false; + + switch (delta->status) { + case GIT_DELTA_ADDED: + case GIT_DELTA_UNTRACKED: + case GIT_DELTA_UNREADABLE: + case GIT_DELTA_IGNORED: + case GIT_DELTA_CONFLICTED: + return false; + + case GIT_DELTA_DELETED: + case GIT_DELTA_TYPECHANGE: + break; + + case GIT_DELTA_UNMODIFIED: + if (!FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED)) + return false; + if (FLAG_SET(opts, GIT_DIFF_FIND_REMOVE_UNMODIFIED)) + delta->flags |= GIT_DIFF_FLAG__TO_DELETE; + break; + + default: /* MODIFIED, RENAMED, COPIED */ + /* if we're finding copies, this could be a source */ + if (FLAG_SET(opts, GIT_DIFF_FIND_COPIES)) + break; + + /* otherwise, this is only a source if we can split it */ + if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) && + !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES)) + return false; + + if (calc_self_similarity(diff, opts, delta_idx, cache) < 0) + return false; + + if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) && + delta->similarity < opts->break_rewrite_threshold) { + delta->flags |= GIT_DIFF_FLAG__TO_SPLIT; + break; + } + + if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) && + delta->similarity < opts->rename_from_rewrite_threshold) + break; + + return false; + } + + delta->flags |= GIT_DIFF_FLAG__IS_RENAME_SOURCE; + return true; +} + +GIT_INLINE(bool) delta_is_split(git_diff_delta *delta) +{ + return (delta->status == GIT_DELTA_TYPECHANGE || + (delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0); +} + +GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta) +{ + return (delta->status == GIT_DELTA_ADDED || + delta->status == GIT_DELTA_UNTRACKED || + delta->status == GIT_DELTA_UNREADABLE || + delta->status == GIT_DELTA_IGNORED); +} + +GIT_INLINE(void) delta_make_rename( + git_diff_delta *to, const git_diff_delta *from, uint16_t similarity) +{ + to->status = GIT_DELTA_RENAMED; + to->similarity = similarity; + to->nfiles = 2; + memcpy(&to->old_file, &from->old_file, sizeof(to->old_file)); + to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; +} + +typedef struct { + size_t idx; + uint16_t similarity; +} diff_find_match; + +int git_diff_find_similar( + git_diff *diff, + const git_diff_find_options *given_opts) +{ + size_t s, t; + int error = 0, result; + uint16_t similarity; + git_diff_delta *src, *tgt; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + size_t num_deltas, num_srcs = 0, num_tgts = 0; + size_t tried_srcs = 0, tried_tgts = 0; + size_t num_rewrites = 0, num_updates = 0, num_bumped = 0; + size_t sigcache_size; + void **sigcache = NULL; /* cache of similarity metric file signatures */ + diff_find_match *tgt2src = NULL; + diff_find_match *src2tgt = NULL; + diff_find_match *tgt2src_copy = NULL; + diff_find_match *best_match; + git_diff_file swap; + + GIT_ASSERT_ARG(diff); + + if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0) + return error; + + num_deltas = diff->deltas.length; + + /* TODO: maybe abort if deltas.length > rename_limit ??? */ + if (!num_deltas || !git__is_uint32(num_deltas)) + goto cleanup; + + /* No flags set; nothing to do */ + if ((opts.flags & GIT_DIFF_FIND_ALL) == 0) + goto cleanup; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&sigcache_size, num_deltas, 2); + sigcache = git__calloc(sigcache_size, sizeof(void *)); + GIT_ERROR_CHECK_ALLOC(sigcache); + + /* Label rename sources and targets + * + * This will also set self-similarity scores for MODIFIED files and + * mark them for splitting if break-rewrites is enabled + */ + git_vector_foreach(&diff->deltas, t, tgt) { + if (is_rename_source(diff, &opts, t, sigcache)) + ++num_srcs; + + if (is_rename_target(diff, &opts, t, sigcache)) + ++num_tgts; + + if ((tgt->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) + num_rewrites++; + } + + /* if there are no candidate srcs or tgts, we're done */ + if (!num_srcs || !num_tgts) + goto cleanup; + + src2tgt = git__calloc(num_deltas, sizeof(diff_find_match)); + GIT_ERROR_CHECK_ALLOC(src2tgt); + tgt2src = git__calloc(num_deltas, sizeof(diff_find_match)); + GIT_ERROR_CHECK_ALLOC(tgt2src); + + if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) { + tgt2src_copy = git__calloc(num_deltas, sizeof(diff_find_match)); + GIT_ERROR_CHECK_ALLOC(tgt2src_copy); + } + + /* + * Find best-fit matches for rename / copy candidates + */ + +find_best_matches: + tried_tgts = num_bumped = 0; + + git_vector_foreach(&diff->deltas, t, tgt) { + /* skip things that are not rename targets */ + if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) + continue; + + tried_srcs = 0; + + git_vector_foreach(&diff->deltas, s, src) { + /* skip things that are not rename sources */ + if ((src->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0) + continue; + + /* calculate similarity for this pair and find best match */ + if (s == t) + result = -1; /* don't measure self-similarity here */ + else if ((error = similarity_measure( + &result, diff, &opts, sigcache, 2 * s, 2 * t + 1)) < 0) + goto cleanup; + + if (result < 0) + continue; + similarity = (uint16_t)result; + + /* is this a better rename? */ + if (tgt2src[t].similarity < similarity && + src2tgt[s].similarity < similarity) + { + /* eject old mapping */ + if (src2tgt[s].similarity > 0) { + tgt2src[src2tgt[s].idx].similarity = 0; + num_bumped++; + } + if (tgt2src[t].similarity > 0) { + src2tgt[tgt2src[t].idx].similarity = 0; + num_bumped++; + } + + /* write new mapping */ + tgt2src[t].idx = s; + tgt2src[t].similarity = similarity; + src2tgt[s].idx = t; + src2tgt[s].similarity = similarity; + } + + /* keep best absolute match for copies */ + if (tgt2src_copy != NULL && + tgt2src_copy[t].similarity < similarity) + { + tgt2src_copy[t].idx = s; + tgt2src_copy[t].similarity = similarity; + } + + if (++tried_srcs >= num_srcs) + break; + + /* cap on maximum targets we'll examine (per "tgt" file) */ + if (tried_srcs > opts.rename_limit) + break; + } + + if (++tried_tgts >= num_tgts) + break; + } + + if (num_bumped > 0) /* try again if we bumped some items */ + goto find_best_matches; + + /* + * Rewrite the diffs with renames / copies + */ + + git_vector_foreach(&diff->deltas, t, tgt) { + /* skip things that are not rename targets */ + if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0) + continue; + + /* check if this delta was the target of a similarity */ + if (tgt2src[t].similarity) + best_match = &tgt2src[t]; + else if (tgt2src_copy && tgt2src_copy[t].similarity) + best_match = &tgt2src_copy[t]; + else + continue; + + s = best_match->idx; + src = GIT_VECTOR_GET(&diff->deltas, s); + + /* possible scenarios: + * 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME + * 2. from DELETE to SPLIT/TYPECHANGE = RENAME + DELETE + * 3. from SPLIT/TYPECHANGE to ADD/UNTRACK/IGNORE = ADD + RENAME + * 4. from SPLIT/TYPECHANGE to SPLIT/TYPECHANGE = RENAME + SPLIT + * 5. from OTHER to ADD/UNTRACK/IGNORE = OTHER + COPY + */ + + if (src->status == GIT_DELTA_DELETED) { + + if (delta_is_new_only(tgt)) { + + if (best_match->similarity < opts.rename_threshold) + continue; + + delta_make_rename(tgt, src, best_match->similarity); + + src->flags |= GIT_DIFF_FLAG__TO_DELETE; + num_rewrites++; + } else { + GIT_ASSERT(delta_is_split(tgt)); + + if (best_match->similarity < opts.rename_from_rewrite_threshold) + continue; + + memcpy(&swap, &tgt->old_file, sizeof(swap)); + + delta_make_rename(tgt, src, best_match->similarity); + num_rewrites--; + + GIT_ASSERT(src->status == GIT_DELTA_DELETED); + memcpy(&src->old_file, &swap, sizeof(src->old_file)); + memset(&src->new_file, 0, sizeof(src->new_file)); + src->new_file.path = src->old_file.path; + src->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + num_updates++; + + if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { + /* what used to be at src t is now at src s */ + tgt2src[src2tgt[t].idx].idx = s; + } + } + } + + else if (delta_is_split(src)) { + + if (delta_is_new_only(tgt)) { + + if (best_match->similarity < opts.rename_threshold) + continue; + + delta_make_rename(tgt, src, best_match->similarity); + + src->status = (diff->new_src == GIT_ITERATOR_WORKDIR) ? + GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED; + src->nfiles = 1; + memset(&src->old_file, 0, sizeof(src->old_file)); + src->old_file.path = src->new_file.path; + src->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; + num_rewrites--; + + num_updates++; + } else { + GIT_ASSERT(delta_is_split(src)); + + if (best_match->similarity < opts.rename_from_rewrite_threshold) + continue; + + memcpy(&swap, &tgt->old_file, sizeof(swap)); + + delta_make_rename(tgt, src, best_match->similarity); + num_rewrites--; + num_updates++; + + memcpy(&src->old_file, &swap, sizeof(src->old_file)); + + /* if we've just swapped the new element into the correct + * place, clear the SPLIT and RENAME_TARGET flags + */ + if (tgt2src[s].idx == t && + tgt2src[s].similarity > + opts.rename_from_rewrite_threshold) { + src->status = GIT_DELTA_RENAMED; + src->similarity = tgt2src[s].similarity; + tgt2src[s].similarity = 0; + src->flags &= ~(GIT_DIFF_FLAG__TO_SPLIT | GIT_DIFF_FLAG__IS_RENAME_TARGET); + num_rewrites--; + } + /* otherwise, if we just overwrote a source, update mapping */ + else if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) { + /* what used to be at src t is now at src s */ + tgt2src[src2tgt[t].idx].idx = s; + } + + num_updates++; + } + } + + else if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) { + if (tgt2src_copy[t].similarity < opts.copy_threshold) + continue; + + /* always use best possible source for copy */ + best_match = &tgt2src_copy[t]; + src = GIT_VECTOR_GET(&diff->deltas, best_match->idx); + + if (delta_is_split(tgt)) { + error = insert_delete_side_of_split(diff, &diff->deltas, tgt); + if (error < 0) + goto cleanup; + num_rewrites--; + } + + if (!delta_is_split(tgt) && !delta_is_new_only(tgt)) + continue; + + tgt->status = GIT_DELTA_COPIED; + tgt->similarity = best_match->similarity; + tgt->nfiles = 2; + memcpy(&tgt->old_file, &src->old_file, sizeof(tgt->old_file)); + tgt->flags &= ~GIT_DIFF_FLAG__TO_SPLIT; + + num_updates++; + } + } + + /* + * Actually split and delete entries as needed + */ + + if (num_rewrites > 0 || num_updates > 0) + error = apply_splits_and_deletes( + diff, diff->deltas.length - num_rewrites, + FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES) && + !FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY)); + +cleanup: + git__free(tgt2src); + git__free(src2tgt); + git__free(tgt2src_copy); + + if (sigcache) { + for (t = 0; t < num_deltas * 2; ++t) { + if (sigcache[t] != NULL) + opts.metric->free_signature(sigcache[t], opts.metric->payload); + } + git__free(sigcache); + } + + if (!given_opts || !given_opts->metric) + git__free(opts.metric); + + return error; +} + +#undef FLAG_SET diff --git a/src/libgit2/diff_tform.h b/src/libgit2/diff_tform.h new file mode 100644 index 000000000..7abb8b3fe --- /dev/null +++ b/src/libgit2/diff_tform.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_diff_tform_h__ +#define INCLUDE_diff_tform_h__ + +#include "common.h" + +#include "diff_file.h" + +extern int git_diff_find_similar__hashsig_for_file( + void **out, const git_diff_file *f, const char *path, void *p); + +extern int git_diff_find_similar__hashsig_for_buf( + void **out, const git_diff_file *f, const char *buf, size_t len, void *p); + +extern void git_diff_find_similar__hashsig_free(void *sig, void *payload); + +extern int git_diff_find_similar__calc_similarity( + int *score, void *siga, void *sigb, void *payload); + +#endif diff --git a/src/libgit2/diff_xdiff.c b/src/libgit2/diff_xdiff.c new file mode 100644 index 000000000..3f6eccac1 --- /dev/null +++ b/src/libgit2/diff_xdiff.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "diff_xdiff.h" + +#include "git2/errors.h" +#include "diff.h" +#include "diff_driver.h" +#include "patch_generate.h" + +static int git_xdiff_scan_int(const char **str, int *value) +{ + const char *scan = *str; + int v = 0, digits = 0; + /* find next digit */ + for (scan = *str; *scan && !git__isdigit(*scan); scan++); + /* parse next number */ + for (; git__isdigit(*scan); scan++, digits++) + v = (v * 10) + (*scan - '0'); + *str = scan; + *value = v; + return (digits > 0) ? 0 : -1; +} + +static int git_xdiff_parse_hunk(git_diff_hunk *hunk, const char *header) +{ + /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ + if (*header != '@') + goto fail; + if (git_xdiff_scan_int(&header, &hunk->old_start) < 0) + goto fail; + if (*header == ',') { + if (git_xdiff_scan_int(&header, &hunk->old_lines) < 0) + goto fail; + } else + hunk->old_lines = 1; + if (git_xdiff_scan_int(&header, &hunk->new_start) < 0) + goto fail; + if (*header == ',') { + if (git_xdiff_scan_int(&header, &hunk->new_lines) < 0) + goto fail; + } else + hunk->new_lines = 1; + if (hunk->old_start < 0 || hunk->new_start < 0) + goto fail; + + return 0; + +fail: + git_error_set(GIT_ERROR_INVALID, "malformed hunk header from xdiff"); + return -1; +} + +typedef struct { + git_xdiff_output *xo; + git_patch_generated *patch; + git_diff_hunk hunk; + int old_lineno, new_lineno; + mmfile_t xd_old_data, xd_new_data; +} git_xdiff_info; + +static int diff_update_lines( + git_xdiff_info *info, + git_diff_line *line, + const char *content, + size_t content_len) +{ + const char *scan = content, *scan_end = content + content_len; + + for (line->num_lines = 0; scan < scan_end; ++scan) + if (*scan == '\n') + ++line->num_lines; + + line->content = content; + line->content_len = content_len; + + /* expect " "/"-"/"+", then data */ + switch (line->origin) { + case GIT_DIFF_LINE_ADDITION: + case GIT_DIFF_LINE_DEL_EOFNL: + line->old_lineno = -1; + line->new_lineno = info->new_lineno; + info->new_lineno += (int)line->num_lines; + break; + case GIT_DIFF_LINE_DELETION: + case GIT_DIFF_LINE_ADD_EOFNL: + line->old_lineno = info->old_lineno; + line->new_lineno = -1; + info->old_lineno += (int)line->num_lines; + break; + case GIT_DIFF_LINE_CONTEXT: + case GIT_DIFF_LINE_CONTEXT_EOFNL: + line->old_lineno = info->old_lineno; + line->new_lineno = info->new_lineno; + info->old_lineno += (int)line->num_lines; + info->new_lineno += (int)line->num_lines; + break; + default: + git_error_set(GIT_ERROR_INVALID, "unknown diff line origin %02x", + (unsigned int)line->origin); + return -1; + } + + return 0; +} + +static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) +{ + git_xdiff_info *info = priv; + git_patch_generated *patch = info->patch; + const git_diff_delta *delta = patch->base.delta; + git_patch_generated_output *output = &info->xo->output; + git_diff_line line; + size_t buffer_len; + + if (len == 1) { + output->error = git_xdiff_parse_hunk(&info->hunk, bufs[0].ptr); + if (output->error < 0) + return output->error; + + info->hunk.header_len = bufs[0].size; + if (info->hunk.header_len >= sizeof(info->hunk.header)) + info->hunk.header_len = sizeof(info->hunk.header) - 1; + + /* Sanitize the hunk header in case there is invalid Unicode */ + buffer_len = git_utf8_valid_buf_length(bufs[0].ptr, info->hunk.header_len); + /* Sanitizing the hunk header may delete the newline, so add it back again if there is room */ + if (buffer_len < info->hunk.header_len) { + bufs[0].ptr[buffer_len] = '\n'; + buffer_len += 1; + info->hunk.header_len = buffer_len; + } + + memcpy(info->hunk.header, bufs[0].ptr, info->hunk.header_len); + info->hunk.header[info->hunk.header_len] = '\0'; + + if (output->hunk_cb != NULL && + (output->error = output->hunk_cb( + delta, &info->hunk, output->payload))) + return output->error; + + info->old_lineno = info->hunk.old_start; + info->new_lineno = info->hunk.new_start; + } + + if (len == 2 || len == 3) { + /* expect " "/"-"/"+", then data */ + line.origin = + (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : + (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : + GIT_DIFF_LINE_CONTEXT; + + if (line.origin == GIT_DIFF_LINE_ADDITION) + line.content_offset = bufs[1].ptr - info->xd_new_data.ptr; + else if (line.origin == GIT_DIFF_LINE_DELETION) + line.content_offset = bufs[1].ptr - info->xd_old_data.ptr; + else + line.content_offset = -1; + + output->error = diff_update_lines( + info, &line, bufs[1].ptr, bufs[1].size); + + if (!output->error && output->data_cb != NULL) + output->error = output->data_cb( + delta, &info->hunk, &line, output->payload); + } + + if (len == 3 && !output->error) { + /* If we have a '+' and a third buf, then we have added a line + * without a newline and the old code had one, so DEL_EOFNL. + * If we have a '-' and a third buf, then we have removed a line + * with out a newline but added a blank line, so ADD_EOFNL. + */ + line.origin = + (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL : + (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL : + GIT_DIFF_LINE_CONTEXT_EOFNL; + + line.content_offset = -1; + + output->error = diff_update_lines( + info, &line, bufs[2].ptr, bufs[2].size); + + if (!output->error && output->data_cb != NULL) + output->error = output->data_cb( + delta, &info->hunk, &line, output->payload); + } + + return output->error; +} + +static int git_xdiff(git_patch_generated_output *output, git_patch_generated *patch) +{ + git_xdiff_output *xo = (git_xdiff_output *)output; + git_xdiff_info info; + git_diff_find_context_payload findctxt; + + memset(&info, 0, sizeof(info)); + info.patch = patch; + info.xo = xo; + + xo->callback.priv = &info; + + git_diff_find_context_init( + &xo->config.find_func, &findctxt, git_patch_generated_driver(patch)); + xo->config.find_func_priv = &findctxt; + + if (xo->config.find_func != NULL) + xo->config.flags |= XDL_EMIT_FUNCNAMES; + else + xo->config.flags &= ~XDL_EMIT_FUNCNAMES; + + /* TODO: check ofile.opts_flags to see if driver-specific per-file + * updates are needed to xo->params.flags + */ + + if (git_patch_generated_old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch) < 0 || + git_patch_generated_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch) < 0) + return -1; + + xdl_diff(&info.xd_old_data, &info.xd_new_data, + &xo->params, &xo->config, &xo->callback); + + git_diff_find_context_clear(&findctxt); + + return xo->output.error; +} + +void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts) +{ + uint32_t flags = opts ? opts->flags : 0; + + xo->output.diff_cb = git_xdiff; + + xo->config.ctxlen = opts ? opts->context_lines : 3; + xo->config.interhunkctxlen = opts ? opts->interhunk_lines : 0; + + if (flags & GIT_DIFF_IGNORE_WHITESPACE) + xo->params.flags |= XDF_WHITESPACE_FLAGS; + if (flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE) + xo->params.flags |= XDF_IGNORE_WHITESPACE_CHANGE; + if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL) + xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; + if (flags & GIT_DIFF_INDENT_HEURISTIC) + xo->params.flags |= XDF_INDENT_HEURISTIC; + + if (flags & GIT_DIFF_PATIENCE) + xo->params.flags |= XDF_PATIENCE_DIFF; + if (flags & GIT_DIFF_MINIMAL) + xo->params.flags |= XDF_NEED_MINIMAL; + + if (flags & GIT_DIFF_IGNORE_BLANK_LINES) + xo->params.flags |= XDF_IGNORE_BLANK_LINES; + + xo->callback.out_line = git_xdiff_cb; +} diff --git a/src/libgit2/diff_xdiff.h b/src/libgit2/diff_xdiff.h new file mode 100644 index 000000000..9b303e9dc --- /dev/null +++ b/src/libgit2/diff_xdiff.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_diff_xdiff_h__ +#define INCLUDE_diff_xdiff_h__ + +#include "common.h" + +#include "diff.h" +#include "xdiff/xdiff.h" +#include "patch_generate.h" + +/* xdiff cannot cope with large files. these files should not be passed to + * xdiff. callers should treat these large files as binary. + */ +#define GIT_XDIFF_MAX_SIZE (INT64_C(1024) * 1024 * 1023) + +/* A git_xdiff_output is a git_patch_generate_output with extra fields + * necessary to use libxdiff. Calling git_xdiff_init() will set the diff_cb + * field of the output to use xdiff to generate the diffs. + */ +typedef struct { + git_patch_generated_output output; + + xdemitconf_t config; + xpparam_t params; + xdemitcb_t callback; +} git_xdiff_output; + +void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts); + +#endif diff --git a/src/libgit2/email.c b/src/libgit2/email.c new file mode 100644 index 000000000..e19a2928c --- /dev/null +++ b/src/libgit2/email.c @@ -0,0 +1,315 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "email.h" + +#include "common.h" +#include "buf.h" +#include "diff_generate.h" +#include "diff_stats.h" +#include "patch.h" +#include "date.h" + +#include "git2/email.h" +#include "git2/patch.h" +#include "git2/version.h" + +/* + * Git uses a "magic" timestamp to indicate that an email message + * is from `git format-patch` (or our equivalent). + */ +#define EMAIL_TIMESTAMP "Mon Sep 17 00:00:00 2001" + +GIT_INLINE(int) include_prefix( + size_t patch_count, + git_email_create_options *opts) +{ + return ((!opts->subject_prefix || *opts->subject_prefix) || + (opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 || + opts->reroll_number || + (patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS))); +} + +static int append_prefix( + git_str *out, + size_t patch_idx, + size_t patch_count, + git_email_create_options *opts) +{ + const char *subject_prefix = opts->subject_prefix ? + opts->subject_prefix : "PATCH"; + + git_str_putc(out, '['); + + if (*subject_prefix) + git_str_puts(out, subject_prefix); + + if (opts->reroll_number) { + if (*subject_prefix) + git_str_putc(out, ' '); + + git_str_printf(out, "v%" PRIuZ, opts->reroll_number); + } + + if ((opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 || + (patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS))) { + size_t start_number = opts->start_number ? + opts->start_number : 1; + + if (*subject_prefix || opts->reroll_number) + git_str_putc(out, ' '); + + git_str_printf(out, "%" PRIuZ "/%" PRIuZ, + patch_idx + (start_number - 1), + patch_count + (start_number - 1)); + } + + git_str_puts(out, "]"); + + return git_str_oom(out) ? -1 : 0; +} + +static int append_date( + git_str *out, + const git_time *date) +{ + int error; + + if ((error = git_str_printf(out, "Date: ")) == 0 && + (error = git_date_rfc2822_fmt(out, date->time, date->offset)) == 0) + error = git_str_putc(out, '\n'); + + return error; +} + +static int append_subject( + git_str *out, + size_t patch_idx, + size_t patch_count, + const char *summary, + git_email_create_options *opts) +{ + bool prefix = include_prefix(patch_count, opts); + size_t summary_len = summary ? strlen(summary) : 0; + int error; + + if (summary_len) { + const char *nl = strchr(summary, '\n'); + + if (nl) + summary_len = (nl - summary); + } + + if ((error = git_str_puts(out, "Subject: ")) < 0) + return error; + + if (prefix && + (error = append_prefix(out, patch_idx, patch_count, opts)) < 0) + return error; + + if (prefix && summary_len && (error = git_str_putc(out, ' ')) < 0) + return error; + + if (summary_len && + (error = git_str_put(out, summary, summary_len)) < 0) + return error; + + return git_str_putc(out, '\n'); +} + +static int append_header( + git_str *out, + size_t patch_idx, + size_t patch_count, + const git_oid *commit_id, + const char *summary, + const git_signature *author, + git_email_create_options *opts) +{ + char id[GIT_OID_HEXSZ]; + int error; + + if ((error = git_oid_fmt(id, commit_id)) < 0 || + (error = git_str_printf(out, "From %.*s %s\n", GIT_OID_HEXSZ, id, EMAIL_TIMESTAMP)) < 0 || + (error = git_str_printf(out, "From: %s <%s>\n", author->name, author->email)) < 0 || + (error = append_date(out, &author->when)) < 0 || + (error = append_subject(out, patch_idx, patch_count, summary, opts)) < 0) + return error; + + if ((error = git_str_putc(out, '\n')) < 0) + return error; + + return 0; +} + +static int append_body(git_str *out, const char *body) +{ + size_t body_len; + int error; + + if (!body) + return 0; + + body_len = strlen(body); + + if ((error = git_str_puts(out, body)) < 0) + return error; + + if (body_len && body[body_len - 1] != '\n') + error = git_str_putc(out, '\n'); + + return error; +} + +static int append_diffstat(git_str *out, git_diff *diff) +{ + git_diff_stats *stats = NULL; + unsigned int format_flags; + int error; + + format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY; + + if ((error = git_diff_get_stats(&stats, diff)) == 0 && + (error = git_diff__stats_to_buf(out, stats, format_flags, 0)) == 0) + error = git_str_putc(out, '\n'); + + git_diff_stats_free(stats); + return error; +} + +static int append_patches(git_str *out, git_diff *diff) +{ + size_t i, deltas; + int error = 0; + + deltas = git_diff_num_deltas(diff); + + for (i = 0; i < deltas; ++i) { + git_patch *patch = NULL; + + if ((error = git_patch_from_diff(&patch, diff, i)) >= 0) + error = git_patch__to_buf(out, patch); + + git_patch_free(patch); + + if (error < 0) + break; + } + + return error; +} + +int git_email__append_from_diff( + git_str *out, + git_diff *diff, + size_t patch_idx, + size_t patch_count, + const git_oid *commit_id, + const char *summary, + const char *body, + const git_signature *author, + const git_email_create_options *given_opts) +{ + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diff); + GIT_ASSERT_ARG(!patch_idx || patch_idx <= patch_count); + GIT_ASSERT_ARG(commit_id); + GIT_ASSERT_ARG(author); + + GIT_ERROR_CHECK_VERSION(given_opts, + GIT_EMAIL_CREATE_OPTIONS_VERSION, + "git_email_create_options"); + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_email_create_options)); + + if ((error = append_header(out, patch_idx, patch_count, commit_id, summary, author, &opts)) == 0 && + (error = append_body(out, body)) == 0 && + (error = git_str_puts(out, "---\n")) == 0 && + (error = append_diffstat(out, diff)) == 0 && + (error = append_patches(out, diff)) == 0) + error = git_str_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n"); + + return error; +} + +int git_email_create_from_diff( + git_buf *out, + git_diff *diff, + size_t patch_idx, + size_t patch_count, + const git_oid *commit_id, + const char *summary, + const char *body, + const git_signature *author, + const git_email_create_options *given_opts) +{ + git_str email = GIT_STR_INIT; + int error; + + git_buf_tostr(&email, out); + + error = git_email__append_from_diff(&email, diff, patch_idx, + patch_count, commit_id, summary, body, author, + given_opts); + + if (error == 0) + error = git_buf_fromstr(out, &email); + + git_str_dispose(&email); + return error; +} + +int git_email_create_from_commit( + git_buf *out, + git_commit *commit, + const git_email_create_options *given_opts) +{ + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + git_diff *diff = NULL; + git_repository *repo; + git_diff_options *diff_opts; + git_diff_find_options *find_opts; + const git_signature *author; + const char *summary, *body; + const git_oid *commit_id; + int error = -1; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(commit); + + GIT_ERROR_CHECK_VERSION(given_opts, + GIT_EMAIL_CREATE_OPTIONS_VERSION, + "git_email_create_options"); + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_email_create_options)); + + repo = git_commit_owner(commit); + author = git_commit_author(commit); + summary = git_commit_summary(commit); + body = git_commit_body(commit); + commit_id = git_commit_id(commit); + diff_opts = &opts.diff_opts; + find_opts = &opts.diff_find_opts; + + if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0) + goto done; + + if ((opts.flags & GIT_EMAIL_CREATE_NO_RENAMES) == 0 && + (error = git_diff_find_similar(diff, find_opts)) < 0) + goto done; + + error = git_email_create_from_diff(out, diff, 1, 1, commit_id, summary, body, author, &opts); + +done: + git_diff_free(diff); + return error; +} diff --git a/src/libgit2/email.h b/src/libgit2/email.h new file mode 100644 index 000000000..083e56d5c --- /dev/null +++ b/src/libgit2/email.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_email_h__ +#define INCLUDE_email_h__ + +#include "common.h" + +#include "git2/email.h" + +extern int git_email__append_from_diff( + git_str *out, + git_diff *diff, + size_t patch_idx, + size_t patch_count, + const git_oid *commit_id, + const char *summary, + const char *body, + const git_signature *author, + const git_email_create_options *given_opts); + +#endif diff --git a/src/libgit2/errors.c b/src/libgit2/errors.c new file mode 100644 index 000000000..3614b9ce5 --- /dev/null +++ b/src/libgit2/errors.c @@ -0,0 +1,238 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "threadstate.h" +#include "posix.h" +#include "str.h" +#include "libgit2.h" + +/******************************************** + * New error handling + ********************************************/ + +static git_error g_git_oom_error = { + "Out of memory", + GIT_ERROR_NOMEMORY +}; + +static git_error g_git_uninitialized_error = { + "libgit2 has not been initialized; you must call git_libgit2_init", + GIT_ERROR_INVALID +}; + +static void set_error_from_buffer(int error_class) +{ + git_error *error = &GIT_THREADSTATE->error_t; + git_str *buf = &GIT_THREADSTATE->error_buf; + + error->message = buf->ptr; + error->klass = error_class; + + GIT_THREADSTATE->last_error = error; +} + +static void set_error(int error_class, char *string) +{ + git_str *buf = &GIT_THREADSTATE->error_buf; + + git_str_clear(buf); + if (string) { + git_str_puts(buf, string); + git__free(string); + } + + set_error_from_buffer(error_class); +} + +void git_error_set_oom(void) +{ + GIT_THREADSTATE->last_error = &g_git_oom_error; +} + +void git_error_set(int error_class, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + git_error_vset(error_class, fmt, ap); + va_end(ap); +} + +void git_error_vset(int error_class, const char *fmt, va_list ap) +{ +#ifdef GIT_WIN32 + DWORD win32_error_code = (error_class == GIT_ERROR_OS) ? GetLastError() : 0; +#endif + int error_code = (error_class == GIT_ERROR_OS) ? errno : 0; + git_str *buf = &GIT_THREADSTATE->error_buf; + + git_str_clear(buf); + if (fmt) { + git_str_vprintf(buf, fmt, ap); + if (error_class == GIT_ERROR_OS) + git_str_PUTS(buf, ": "); + } + + if (error_class == GIT_ERROR_OS) { +#ifdef GIT_WIN32 + char * win32_error = git_win32_get_error_message(win32_error_code); + if (win32_error) { + git_str_puts(buf, win32_error); + git__free(win32_error); + + SetLastError(0); + } + else +#endif + if (error_code) + git_str_puts(buf, strerror(error_code)); + + if (error_code) + errno = 0; + } + + if (!git_str_oom(buf)) + set_error_from_buffer(error_class); +} + +int git_error_set_str(int error_class, const char *string) +{ + git_str *buf = &GIT_THREADSTATE->error_buf; + + GIT_ASSERT_ARG(string); + + git_str_clear(buf); + git_str_puts(buf, string); + + if (git_str_oom(buf)) + return -1; + + set_error_from_buffer(error_class); + return 0; +} + +void git_error_clear(void) +{ + if (GIT_THREADSTATE->last_error != NULL) { + set_error(0, NULL); + GIT_THREADSTATE->last_error = NULL; + } + + errno = 0; +#ifdef GIT_WIN32 + SetLastError(0); +#endif +} + +const git_error *git_error_last(void) +{ + /* If the library is not initialized, return a static error. */ + if (!git_libgit2_init_count()) + return &g_git_uninitialized_error; + + return GIT_THREADSTATE->last_error; +} + +int git_error_state_capture(git_error_state *state, int error_code) +{ + git_error *error = GIT_THREADSTATE->last_error; + git_str *error_buf = &GIT_THREADSTATE->error_buf; + + memset(state, 0, sizeof(git_error_state)); + + if (!error_code) + return 0; + + state->error_code = error_code; + state->oom = (error == &g_git_oom_error); + + if (error) { + state->error_msg.klass = error->klass; + + if (state->oom) + state->error_msg.message = g_git_oom_error.message; + else + state->error_msg.message = git_str_detach(error_buf); + } + + git_error_clear(); + return error_code; +} + +int git_error_state_restore(git_error_state *state) +{ + int ret = 0; + + git_error_clear(); + + if (state && state->error_msg.message) { + if (state->oom) + git_error_set_oom(); + else + set_error(state->error_msg.klass, state->error_msg.message); + + ret = state->error_code; + memset(state, 0, sizeof(git_error_state)); + } + + return ret; +} + +void git_error_state_free(git_error_state *state) +{ + if (!state) + return; + + if (!state->oom) + git__free(state->error_msg.message); + + memset(state, 0, sizeof(git_error_state)); +} + +int git_error_system_last(void) +{ +#ifdef GIT_WIN32 + return GetLastError(); +#else + return errno; +#endif +} + +void git_error_system_set(int code) +{ +#ifdef GIT_WIN32 + SetLastError(code); +#else + errno = code; +#endif +} + +/* Deprecated error values and functions */ + +#ifndef GIT_DEPRECATE_HARD +const git_error *giterr_last(void) +{ + return git_error_last(); +} + +void giterr_clear(void) +{ + git_error_clear(); +} + +void giterr_set_str(int error_class, const char *string) +{ + git_error_set_str(error_class, string); +} + +void giterr_set_oom(void) +{ + git_error_set_oom(); +} +#endif diff --git a/src/libgit2/errors.h b/src/libgit2/errors.h new file mode 100644 index 000000000..772c7bad1 --- /dev/null +++ b/src/libgit2/errors.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_errors_h__ +#define INCLUDE_errors_h__ + +#include "common.h" + +/* + * `vprintf`-style formatting for the error message for this thread. + */ +void git_error_vset(int error_class, const char *fmt, va_list ap); + +/** + * Set error message for user callback if needed. + * + * If the error code in non-zero and no error message is set, this + * sets a generic error message. + * + * @return This always returns the `error_code` parameter. + */ +GIT_INLINE(int) git_error_set_after_callback_function( + int error_code, const char *action) +{ + if (error_code) { + const git_error *e = git_error_last(); + if (!e || !e->message) + git_error_set(e ? e->klass : GIT_ERROR_CALLBACK, + "%s callback returned %d", action, error_code); + } + return error_code; +} + +#ifdef GIT_WIN32 +#define git_error_set_after_callback(code) \ + git_error_set_after_callback_function((code), __FUNCTION__) +#else +#define git_error_set_after_callback(code) \ + git_error_set_after_callback_function((code), __func__) +#endif + +/** + * Gets the system error code for this thread. + */ +int git_error_system_last(void); + +/** + * Sets the system error code for this thread. + */ +void git_error_system_set(int code); + +/** + * Structure to preserve libgit2 error state + */ +typedef struct { + int error_code; + unsigned int oom : 1; + git_error error_msg; +} git_error_state; + +/** + * Capture current error state to restore later, returning error code. + * If `error_code` is zero, this does not clear the current error state. + * You must either restore this error state, or free it. + */ +extern int git_error_state_capture(git_error_state *state, int error_code); + +/** + * Restore error state to a previous value, returning saved error code. + */ +extern int git_error_state_restore(git_error_state *state); + +/** Free an error state. */ +extern void git_error_state_free(git_error_state *state); + +#endif diff --git a/src/libgit2/features.h.in b/src/libgit2/features.h.in new file mode 100644 index 000000000..f920135da --- /dev/null +++ b/src/libgit2/features.h.in @@ -0,0 +1,53 @@ +#ifndef INCLUDE_features_h__ +#define INCLUDE_features_h__ + +#cmakedefine GIT_DEBUG_POOL 1 +#cmakedefine GIT_DEBUG_STRICT_ALLOC 1 +#cmakedefine GIT_DEBUG_STRICT_OPEN 1 + +#cmakedefine GIT_THREADS 1 +#cmakedefine GIT_WIN32_LEAKCHECK 1 + +#cmakedefine GIT_ARCH_64 1 +#cmakedefine GIT_ARCH_32 1 + +#cmakedefine GIT_USE_ICONV 1 +#cmakedefine GIT_USE_NSEC 1 +#cmakedefine GIT_USE_STAT_MTIM 1 +#cmakedefine GIT_USE_STAT_MTIMESPEC 1 +#cmakedefine GIT_USE_STAT_MTIME_NSEC 1 +#cmakedefine GIT_USE_FUTIMENS 1 + +#cmakedefine GIT_REGEX_REGCOMP_L +#cmakedefine GIT_REGEX_REGCOMP +#cmakedefine GIT_REGEX_PCRE +#cmakedefine GIT_REGEX_PCRE2 +#cmakedefine GIT_REGEX_BUILTIN 1 + +#cmakedefine GIT_QSORT_R_BSD +#cmakedefine GIT_QSORT_R_GNU +#cmakedefine GIT_QSORT_S + +#cmakedefine GIT_SSH 1 +#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1 + +#cmakedefine GIT_NTLM 1 +#cmakedefine GIT_GSSAPI 1 +#cmakedefine GIT_GSSFRAMEWORK 1 + +#cmakedefine GIT_WINHTTP 1 +#cmakedefine GIT_HTTPS 1 +#cmakedefine GIT_OPENSSL 1 +#cmakedefine GIT_OPENSSL_DYNAMIC 1 +#cmakedefine GIT_SECURE_TRANSPORT 1 +#cmakedefine GIT_MBEDTLS 1 + +#cmakedefine GIT_SHA1_COLLISIONDETECT 1 +#cmakedefine GIT_SHA1_WIN32 1 +#cmakedefine GIT_SHA1_COMMON_CRYPTO 1 +#cmakedefine GIT_SHA1_OPENSSL 1 +#cmakedefine GIT_SHA1_MBEDTLS 1 + +#cmakedefine GIT_RAND_GETENTROPY 1 + +#endif diff --git a/src/libgit2/fetch.c b/src/libgit2/fetch.c new file mode 100644 index 000000000..03d38452c --- /dev/null +++ b/src/libgit2/fetch.c @@ -0,0 +1,211 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "fetch.h" + +#include "git2/oid.h" +#include "git2/refs.h" +#include "git2/revwalk.h" +#include "git2/transport.h" +#include "git2/sys/remote.h" + +#include "remote.h" +#include "refspec.h" +#include "pack.h" +#include "netops.h" +#include "repository.h" +#include "refs.h" + +static int maybe_want(git_remote *remote, git_remote_head *head, git_refspec *tagspec, git_remote_autotag_option_t tagopt) +{ + int match = 0, valid; + + if (git_reference_name_is_valid(&valid, head->name) < 0) + return -1; + + if (!valid) + return 0; + + if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { + /* + * If tagopt is --tags, always request tags + * in addition to the remote's refspecs + */ + if (git_refspec_src_matches(tagspec, head->name)) + match = 1; + } + + if (!match && git_remote__matching_refspec(remote, head->name)) + match = 1; + + if (!match) + return 0; + + return git_vector_insert(&remote->refs, head); +} + +static int mark_local(git_remote *remote) +{ + git_remote_head *head; + git_odb *odb; + size_t i; + + if (git_repository_odb__weakptr(&odb, remote->repo) < 0) + return -1; + + git_vector_foreach(&remote->refs, i, head) { + /* If we have the object, mark it so we don't ask for it */ + if (git_odb_exists(odb, &head->oid)) + head->local = 1; + else + remote->need_pack = 1; + } + + return 0; +} + +static int maybe_want_oid(git_remote *remote, git_refspec *spec) +{ + git_remote_head *oid_head; + + oid_head = git__calloc(1, sizeof(git_remote_head)); + GIT_ERROR_CHECK_ALLOC(oid_head); + + git_oid_fromstr(&oid_head->oid, spec->src); + oid_head->name = git__strdup(spec->dst); + GIT_ERROR_CHECK_ALLOC(oid_head->name); + + if (git_vector_insert(&remote->local_heads, oid_head) < 0 || + git_vector_insert(&remote->refs, oid_head) < 0) + return -1; + + return 0; +} + +static int filter_wants(git_remote *remote, const git_fetch_options *opts) +{ + git_remote_head **heads; + git_refspec tagspec, head, *spec; + int error = 0; + git_odb *odb; + size_t i, heads_len; + unsigned int remote_caps; + unsigned int oid_mask = GIT_REMOTE_CAPABILITY_TIP_OID | + GIT_REMOTE_CAPABILITY_REACHABLE_OID; + git_remote_autotag_option_t tagopt = remote->download_tags; + + if (opts && opts->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED) + tagopt = opts->download_tags; + + git_vector_clear(&remote->refs); + if ((error = git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true)) < 0) + return error; + + /* + * The fetch refspec can be NULL, and what this means is that the + * user didn't specify one. This is fine, as it means that we're + * not interested in any particular branch but just the remote's + * HEAD, which will be stored in FETCH_HEAD after the fetch. + */ + if (remote->active_refspecs.length == 0) { + if ((error = git_refspec__parse(&head, "HEAD", true)) < 0) + goto cleanup; + + error = git_refspec__dwim_one(&remote->active_refspecs, &head, &remote->refs); + git_refspec__dispose(&head); + + if (error < 0) + goto cleanup; + } + + if ((error = git_repository_odb__weakptr(&odb, remote->repo)) < 0) + goto cleanup; + + if ((error = git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote)) < 0 || + (error = git_remote_capabilities(&remote_caps, remote)) < 0) + goto cleanup; + + /* Handle remote heads */ + for (i = 0; i < heads_len; i++) { + if ((error = maybe_want(remote, heads[i], &tagspec, tagopt)) < 0) + goto cleanup; + } + + /* Handle explicitly specified OID specs */ + git_vector_foreach(&remote->active_refspecs, i, spec) { + if (!git_oid__is_hexstr(spec->src)) + continue; + + if (!(remote_caps & oid_mask)) { + git_error_set(GIT_ERROR_INVALID, "cannot fetch a specific object from the remote repository"); + error = -1; + goto cleanup; + } + + if ((error = maybe_want_oid(remote, spec)) < 0) + goto cleanup; + } + + error = mark_local(remote); + +cleanup: + git_refspec__dispose(&tagspec); + + return error; +} + +/* + * In this first version, we push all our refs in and start sending + * them out. When we get an ACK we hide that commit and continue + * traversing until we're done + */ +int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts) +{ + git_transport *t = remote->transport; + + remote->need_pack = 0; + + if (filter_wants(remote, opts) < 0) + return -1; + + /* Don't try to negotiate when we don't want anything */ + if (!remote->need_pack) + return 0; + + /* + * Now we have everything set up so we can start tell the + * server what we want and what we have. + */ + return t->negotiate_fetch(t, + remote->repo, + (const git_remote_head * const *)remote->refs.contents, + remote->refs.length); +} + +int git_fetch_download_pack(git_remote *remote) +{ + git_transport *t = remote->transport; + + if (!remote->need_pack) + return 0; + + return t->download_pack(t, remote->repo, &remote->stats); +} + +int git_fetch_options_init(git_fetch_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_fetch_options, GIT_FETCH_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_fetch_init_options(git_fetch_options *opts, unsigned int version) +{ + return git_fetch_options_init(opts, version); +} +#endif diff --git a/src/libgit2/fetch.h b/src/libgit2/fetch.h new file mode 100644 index 000000000..10b6731f0 --- /dev/null +++ b/src/libgit2/fetch.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_fetch_h__ +#define INCLUDE_fetch_h__ + +#include "common.h" + +#include "git2/remote.h" + +#include "netops.h" + +int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts); + +int git_fetch_download_pack(git_remote *remote); + +int git_fetch_setup_walk(git_revwalk **out, git_repository *repo); + +#endif diff --git a/src/libgit2/fetchhead.c b/src/libgit2/fetchhead.c new file mode 100644 index 000000000..6511124ef --- /dev/null +++ b/src/libgit2/fetchhead.c @@ -0,0 +1,338 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "fetchhead.h" + +#include "git2/types.h" +#include "git2/oid.h" + +#include "str.h" +#include "futils.h" +#include "filebuf.h" +#include "refs.h" +#include "net.h" +#include "repository.h" + +int git_fetchhead_ref_cmp(const void *a, const void *b) +{ + const git_fetchhead_ref *one = (const git_fetchhead_ref *)a; + const git_fetchhead_ref *two = (const git_fetchhead_ref *)b; + + if (one->is_merge && !two->is_merge) + return -1; + if (two->is_merge && !one->is_merge) + return 1; + + if (one->ref_name && two->ref_name) + return strcmp(one->ref_name, two->ref_name); + else if (one->ref_name) + return -1; + else if (two->ref_name) + return 1; + + return 0; +} + +static char *sanitized_remote_url(const char *remote_url) +{ + git_net_url url = GIT_NET_URL_INIT; + char *sanitized = NULL; + int error; + + if (git_net_url_parse(&url, remote_url) == 0) { + git_str buf = GIT_STR_INIT; + + git__free(url.username); + git__free(url.password); + url.username = url.password = NULL; + + if ((error = git_net_url_fmt(&buf, &url)) < 0) + goto fallback; + + sanitized = git_str_detach(&buf); + } + +fallback: + if (!sanitized) + sanitized = git__strdup(remote_url); + + git_net_url_dispose(&url); + return sanitized; +} + +int git_fetchhead_ref_create( + git_fetchhead_ref **out, + git_oid *oid, + unsigned int is_merge, + const char *ref_name, + const char *remote_url) +{ + git_fetchhead_ref *fetchhead_ref; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(oid); + + *out = NULL; + + fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref)); + GIT_ERROR_CHECK_ALLOC(fetchhead_ref); + + memset(fetchhead_ref, 0x0, sizeof(git_fetchhead_ref)); + + git_oid_cpy(&fetchhead_ref->oid, oid); + fetchhead_ref->is_merge = is_merge; + + if (ref_name) { + fetchhead_ref->ref_name = git__strdup(ref_name); + GIT_ERROR_CHECK_ALLOC(fetchhead_ref->ref_name); + } + + if (remote_url) { + fetchhead_ref->remote_url = sanitized_remote_url(remote_url); + GIT_ERROR_CHECK_ALLOC(fetchhead_ref->remote_url); + } + + *out = fetchhead_ref; + + return 0; +} + +static int fetchhead_ref_write( + git_filebuf *file, + git_fetchhead_ref *fetchhead_ref) +{ + char oid[GIT_OID_HEXSZ + 1]; + const char *type, *name; + int head = 0; + + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(fetchhead_ref); + + git_oid_fmt(oid, &fetchhead_ref->oid); + oid[GIT_OID_HEXSZ] = '\0'; + + if (git__prefixcmp(fetchhead_ref->ref_name, GIT_REFS_HEADS_DIR) == 0) { + type = "branch "; + name = fetchhead_ref->ref_name + strlen(GIT_REFS_HEADS_DIR); + } else if(git__prefixcmp(fetchhead_ref->ref_name, + GIT_REFS_TAGS_DIR) == 0) { + type = "tag "; + name = fetchhead_ref->ref_name + strlen(GIT_REFS_TAGS_DIR); + } else if (!git__strcmp(fetchhead_ref->ref_name, GIT_HEAD_FILE)) { + head = 1; + } else { + type = ""; + name = fetchhead_ref->ref_name; + } + + if (head) + return git_filebuf_printf(file, "%s\t\t%s\n", oid, fetchhead_ref->remote_url); + + return git_filebuf_printf(file, "%s\t%s\t%s'%s' of %s\n", + oid, + (fetchhead_ref->is_merge) ? "" : "not-for-merge", + type, + name, + fetchhead_ref->remote_url); +} + +int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str path = GIT_STR_INIT; + unsigned int i; + git_fetchhead_ref *fetchhead_ref; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(fetchhead_refs); + + if (git_str_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0) + return -1; + + if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_APPEND, GIT_REFS_FILE_MODE) < 0) { + git_str_dispose(&path); + return -1; + } + + git_str_dispose(&path); + + git_vector_sort(fetchhead_refs); + + git_vector_foreach(fetchhead_refs, i, fetchhead_ref) + fetchhead_ref_write(&file, fetchhead_ref); + + return git_filebuf_commit(&file); +} + +static int fetchhead_ref_parse( + git_oid *oid, + unsigned int *is_merge, + git_str *ref_name, + const char **remote_url, + char *line, + size_t line_num) +{ + char *oid_str, *is_merge_str, *desc, *name = NULL; + const char *type = NULL; + int error = 0; + + *remote_url = NULL; + + if (!*line) { + git_error_set(GIT_ERROR_FETCHHEAD, + "empty line in FETCH_HEAD line %"PRIuZ, line_num); + return -1; + } + + /* Compat with old git clients that wrote FETCH_HEAD like a loose ref. */ + if ((oid_str = git__strsep(&line, "\t")) == NULL) { + oid_str = line; + line += strlen(line); + + *is_merge = 1; + } + + if (strlen(oid_str) != GIT_OID_HEXSZ) { + git_error_set(GIT_ERROR_FETCHHEAD, + "invalid object ID in FETCH_HEAD line %"PRIuZ, line_num); + return -1; + } + + if (git_oid_fromstr(oid, oid_str) < 0) { + const git_error *oid_err = git_error_last(); + const char *err_msg = oid_err ? oid_err->message : "invalid object ID"; + + git_error_set(GIT_ERROR_FETCHHEAD, "%s in FETCH_HEAD line %"PRIuZ, + err_msg, line_num); + return -1; + } + + /* Parse new data from newer git clients */ + if (*line) { + if ((is_merge_str = git__strsep(&line, "\t")) == NULL) { + git_error_set(GIT_ERROR_FETCHHEAD, + "invalid description data in FETCH_HEAD line %"PRIuZ, line_num); + return -1; + } + + if (*is_merge_str == '\0') + *is_merge = 1; + else if (strcmp(is_merge_str, "not-for-merge") == 0) + *is_merge = 0; + else { + git_error_set(GIT_ERROR_FETCHHEAD, + "invalid for-merge entry in FETCH_HEAD line %"PRIuZ, line_num); + return -1; + } + + if ((desc = line) == NULL) { + git_error_set(GIT_ERROR_FETCHHEAD, + "invalid description in FETCH_HEAD line %"PRIuZ, line_num); + return -1; + } + + if (git__prefixcmp(desc, "branch '") == 0) { + type = GIT_REFS_HEADS_DIR; + name = desc + 8; + } else if (git__prefixcmp(desc, "tag '") == 0) { + type = GIT_REFS_TAGS_DIR; + name = desc + 5; + } else if (git__prefixcmp(desc, "'") == 0) + name = desc + 1; + + if (name) { + if ((desc = strstr(name, "' ")) == NULL || + git__prefixcmp(desc, "' of ") != 0) { + git_error_set(GIT_ERROR_FETCHHEAD, + "invalid description in FETCH_HEAD line %"PRIuZ, line_num); + return -1; + } + + *desc = '\0'; + desc += 5; + } + + *remote_url = desc; + } + + git_str_clear(ref_name); + + if (type) + git_str_join(ref_name, '/', type, name); + else if(name) + git_str_puts(ref_name, name); + + return error; +} + +int git_repository_fetchhead_foreach(git_repository *repo, + git_repository_fetchhead_foreach_cb cb, + void *payload) +{ + git_str path = GIT_STR_INIT, file = GIT_STR_INIT, name = GIT_STR_INIT; + const char *ref_name; + git_oid oid; + const char *remote_url; + unsigned int is_merge = 0; + char *buffer, *line; + size_t line_num = 0; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(cb); + + if (git_str_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0) + return -1; + + if ((error = git_futils_readbuffer(&file, git_str_cstr(&path))) < 0) + goto done; + + buffer = file.ptr; + + while ((line = git__strsep(&buffer, "\n")) != NULL) { + ++line_num; + + if ((error = fetchhead_ref_parse( + &oid, &is_merge, &name, &remote_url, line, line_num)) < 0) + goto done; + + if (git_str_len(&name) > 0) + ref_name = git_str_cstr(&name); + else + ref_name = NULL; + + error = cb(ref_name, remote_url, &oid, is_merge, payload); + if (error) { + git_error_set_after_callback(error); + goto done; + } + } + + if (*buffer) { + git_error_set(GIT_ERROR_FETCHHEAD, "no EOL at line %"PRIuZ, line_num+1); + error = -1; + goto done; + } + +done: + git_str_dispose(&file); + git_str_dispose(&path); + git_str_dispose(&name); + + return error; +} + +void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref) +{ + if (fetchhead_ref == NULL) + return; + + git__free(fetchhead_ref->remote_url); + git__free(fetchhead_ref->ref_name); + git__free(fetchhead_ref); +} + diff --git a/src/libgit2/fetchhead.h b/src/libgit2/fetchhead.h new file mode 100644 index 000000000..9e5171010 --- /dev/null +++ b/src/libgit2/fetchhead.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_fetchhead_h__ +#define INCLUDE_fetchhead_h__ + +#include "common.h" + +#include "oid.h" +#include "vector.h" + +typedef struct git_fetchhead_ref { + git_oid oid; + unsigned int is_merge; + char *ref_name; + char *remote_url; +} git_fetchhead_ref; + +int git_fetchhead_ref_create( + git_fetchhead_ref **fetchhead_ref_out, + git_oid *oid, + unsigned int is_merge, + const char *ref_name, + const char *remote_url); + +int git_fetchhead_ref_cmp(const void *a, const void *b); + +int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs); + +void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref); + +#endif diff --git a/src/libgit2/filebuf.c b/src/libgit2/filebuf.c new file mode 100644 index 000000000..eafcba3bd --- /dev/null +++ b/src/libgit2/filebuf.c @@ -0,0 +1,595 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "filebuf.h" + +#include "futils.h" + +static const size_t WRITE_BUFFER_SIZE = (4096 * 2); + +enum buferr_t { + BUFERR_OK = 0, + BUFERR_WRITE, + BUFERR_ZLIB, + BUFERR_MEM +}; + +#define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; } + +static int verify_last_error(git_filebuf *file) +{ + switch (file->last_error) { + case BUFERR_WRITE: + git_error_set(GIT_ERROR_OS, "failed to write out file"); + return -1; + + case BUFERR_MEM: + git_error_set_oom(); + return -1; + + case BUFERR_ZLIB: + git_error_set(GIT_ERROR_ZLIB, + "Buffer error when writing out ZLib data"); + return -1; + + default: + return 0; + } +} + +static int lock_file(git_filebuf *file, int flags, mode_t mode) +{ + if (git_fs_path_exists(file->path_lock) == true) { + git_error_clear(); /* actual OS error code just confuses */ + git_error_set(GIT_ERROR_OS, + "failed to lock file '%s' for writing", file->path_lock); + return GIT_ELOCKED; + } + + /* create path to the file buffer is required */ + if (flags & GIT_FILEBUF_CREATE_LEADING_DIRS) { + /* XXX: Should dirmode here be configurable? Or is 0777 always fine? */ + file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, mode); + } else { + file->fd = git_futils_creat_locked(file->path_lock, mode); + } + + if (file->fd < 0) + return file->fd; + + file->fd_is_open = true; + + if ((flags & GIT_FILEBUF_APPEND) && git_fs_path_exists(file->path_original) == true) { + git_file source; + char buffer[FILEIO_BUFSIZE]; + ssize_t read_bytes; + int error = 0; + + source = p_open(file->path_original, O_RDONLY); + if (source < 0) { + git_error_set(GIT_ERROR_OS, + "failed to open file '%s' for reading", + file->path_original); + return -1; + } + + while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) { + if ((error = p_write(file->fd, buffer, read_bytes)) < 0) + break; + if (file->compute_digest) + git_hash_update(&file->digest, buffer, read_bytes); + } + + p_close(source); + + if (read_bytes < 0) { + git_error_set(GIT_ERROR_OS, "failed to read file '%s'", file->path_original); + return -1; + } else if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to write file '%s'", file->path_lock); + return -1; + } + } + + return 0; +} + +void git_filebuf_cleanup(git_filebuf *file) +{ + if (file->fd_is_open && file->fd >= 0) + p_close(file->fd); + + if (file->created_lock && !file->did_rename && file->path_lock && git_fs_path_exists(file->path_lock)) + p_unlink(file->path_lock); + + if (file->compute_digest) { + git_hash_ctx_cleanup(&file->digest); + file->compute_digest = 0; + } + + if (file->buffer) + git__free(file->buffer); + + /* use the presence of z_buf to decide if we need to deflateEnd */ + if (file->z_buf) { + git__free(file->z_buf); + deflateEnd(&file->zs); + } + + if (file->path_original) + git__free(file->path_original); + if (file->path_lock) + git__free(file->path_lock); + + memset(file, 0x0, sizeof(git_filebuf)); + file->fd = -1; +} + +GIT_INLINE(int) flush_buffer(git_filebuf *file) +{ + int result = file->write(file, file->buffer, file->buf_pos); + file->buf_pos = 0; + return result; +} + +int git_filebuf_flush(git_filebuf *file) +{ + return flush_buffer(file); +} + +static int write_normal(git_filebuf *file, void *source, size_t len) +{ + if (len > 0) { + if (p_write(file->fd, (void *)source, len) < 0) { + file->last_error = BUFERR_WRITE; + return -1; + } + + if (file->compute_digest) + git_hash_update(&file->digest, source, len); + } + + return 0; +} + +static int write_deflate(git_filebuf *file, void *source, size_t len) +{ + z_stream *zs = &file->zs; + + if (len > 0 || file->flush_mode == Z_FINISH) { + zs->next_in = source; + zs->avail_in = (uInt)len; + + do { + size_t have; + + zs->next_out = file->z_buf; + zs->avail_out = (uInt)file->buf_size; + + if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) { + file->last_error = BUFERR_ZLIB; + return -1; + } + + have = file->buf_size - (size_t)zs->avail_out; + + if (p_write(file->fd, file->z_buf, have) < 0) { + file->last_error = BUFERR_WRITE; + return -1; + } + + } while (zs->avail_out == 0); + + GIT_ASSERT(zs->avail_in == 0); + + if (file->compute_digest) + git_hash_update(&file->digest, source, len); + } + + return 0; +} + +#define MAX_SYMLINK_DEPTH 5 + +static int resolve_symlink(git_str *out, const char *path) +{ + int i, error, root; + ssize_t ret; + struct stat st; + git_str curpath = GIT_STR_INIT, target = GIT_STR_INIT; + + if ((error = git_str_grow(&target, GIT_PATH_MAX + 1)) < 0 || + (error = git_str_puts(&curpath, path)) < 0) + return error; + + for (i = 0; i < MAX_SYMLINK_DEPTH; i++) { + error = p_lstat(curpath.ptr, &st); + if (error < 0 && errno == ENOENT) { + error = git_str_puts(out, curpath.ptr); + goto cleanup; + } + + if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to stat '%s'", curpath.ptr); + error = -1; + goto cleanup; + } + + if (!S_ISLNK(st.st_mode)) { + error = git_str_puts(out, curpath.ptr); + goto cleanup; + } + + ret = p_readlink(curpath.ptr, target.ptr, GIT_PATH_MAX); + if (ret < 0) { + git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", curpath.ptr); + error = -1; + goto cleanup; + } + + if (ret == GIT_PATH_MAX) { + git_error_set(GIT_ERROR_INVALID, "symlink target too long"); + error = -1; + goto cleanup; + } + + /* readlink(2) won't NUL-terminate for us */ + target.ptr[ret] = '\0'; + target.size = ret; + + root = git_fs_path_root(target.ptr); + if (root >= 0) { + if ((error = git_str_sets(&curpath, target.ptr)) < 0) + goto cleanup; + } else { + git_str dir = GIT_STR_INIT; + + if ((error = git_fs_path_dirname_r(&dir, curpath.ptr)) < 0) + goto cleanup; + + git_str_swap(&curpath, &dir); + git_str_dispose(&dir); + + if ((error = git_fs_path_apply_relative(&curpath, target.ptr)) < 0) + goto cleanup; + } + } + + git_error_set(GIT_ERROR_INVALID, "maximum symlink depth reached"); + error = -1; + +cleanup: + git_str_dispose(&curpath); + git_str_dispose(&target); + return error; +} + +int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode) +{ + return git_filebuf_open_withsize(file, path, flags, mode, WRITE_BUFFER_SIZE); +} + +int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size) +{ + int compression, error = -1; + size_t path_len, alloc_len; + + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(path); + GIT_ASSERT(file->buffer == NULL); + + memset(file, 0x0, sizeof(git_filebuf)); + + if (flags & GIT_FILEBUF_DO_NOT_BUFFER) + file->do_not_buffer = true; + + if (flags & GIT_FILEBUF_FSYNC) + file->do_fsync = true; + + file->buf_size = size; + file->buf_pos = 0; + file->fd = -1; + file->last_error = BUFERR_OK; + + /* Allocate the main cache buffer */ + if (!file->do_not_buffer) { + file->buffer = git__malloc(file->buf_size); + GIT_ERROR_CHECK_ALLOC(file->buffer); + } + + /* If we are hashing on-write, allocate a new hash context */ + if (flags & GIT_FILEBUF_HASH_CONTENTS) { + file->compute_digest = 1; + + if (git_hash_ctx_init(&file->digest, GIT_HASH_ALGORITHM_SHA1) < 0) + goto cleanup; + } + + compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT; + + /* If we are deflating on-write, */ + if (compression != 0) { + /* Initialize the ZLib stream */ + if (deflateInit(&file->zs, compression) != Z_OK) { + git_error_set(GIT_ERROR_ZLIB, "failed to initialize zlib"); + goto cleanup; + } + + /* Allocate the Zlib cache buffer */ + file->z_buf = git__malloc(file->buf_size); + GIT_ERROR_CHECK_ALLOC(file->z_buf); + + /* Never flush */ + file->flush_mode = Z_NO_FLUSH; + file->write = &write_deflate; + } else { + file->write = &write_normal; + } + + /* If we are writing to a temp file */ + if (flags & GIT_FILEBUF_TEMPORARY) { + git_str tmp_path = GIT_STR_INIT; + + /* Open the file as temporary for locking */ + file->fd = git_futils_mktmp(&tmp_path, path, mode); + + if (file->fd < 0) { + git_str_dispose(&tmp_path); + goto cleanup; + } + file->fd_is_open = true; + file->created_lock = true; + + /* No original path */ + file->path_original = NULL; + file->path_lock = git_str_detach(&tmp_path); + GIT_ERROR_CHECK_ALLOC(file->path_lock); + } else { + git_str resolved_path = GIT_STR_INIT; + + if ((error = resolve_symlink(&resolved_path, path)) < 0) + goto cleanup; + + /* Save the original path of the file */ + path_len = resolved_path.size; + file->path_original = git_str_detach(&resolved_path); + + /* create the locking path by appending ".lock" to the original */ + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, path_len, GIT_FILELOCK_EXTLENGTH); + file->path_lock = git__malloc(alloc_len); + GIT_ERROR_CHECK_ALLOC(file->path_lock); + + memcpy(file->path_lock, file->path_original, path_len); + memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH); + + if (git_fs_path_isdir(file->path_original)) { + git_error_set(GIT_ERROR_FILESYSTEM, "path '%s' is a directory", file->path_original); + error = GIT_EDIRECTORY; + goto cleanup; + } + + /* open the file for locking */ + if ((error = lock_file(file, flags, mode)) < 0) + goto cleanup; + + file->created_lock = true; + } + + return 0; + +cleanup: + git_filebuf_cleanup(file); + return error; +} + +int git_filebuf_hash(unsigned char *out, git_filebuf *file) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(file->compute_digest); + + flush_buffer(file); + + if (verify_last_error(file) < 0) + return -1; + + git_hash_final(out, &file->digest); + git_hash_ctx_cleanup(&file->digest); + file->compute_digest = 0; + + return 0; +} + +int git_filebuf_commit_at(git_filebuf *file, const char *path) +{ + git__free(file->path_original); + file->path_original = git__strdup(path); + GIT_ERROR_CHECK_ALLOC(file->path_original); + + return git_filebuf_commit(file); +} + +int git_filebuf_commit(git_filebuf *file) +{ + /* temporary files cannot be committed */ + GIT_ASSERT_ARG(file); + GIT_ASSERT(file->path_original); + + file->flush_mode = Z_FINISH; + flush_buffer(file); + + if (verify_last_error(file) < 0) + goto on_error; + + file->fd_is_open = false; + + if (file->do_fsync && p_fsync(file->fd) < 0) { + git_error_set(GIT_ERROR_OS, "failed to fsync '%s'", file->path_lock); + goto on_error; + } + + if (p_close(file->fd) < 0) { + git_error_set(GIT_ERROR_OS, "failed to close file at '%s'", file->path_lock); + goto on_error; + } + + file->fd = -1; + + if (p_rename(file->path_lock, file->path_original) < 0) { + git_error_set(GIT_ERROR_OS, "failed to rename lockfile to '%s'", file->path_original); + goto on_error; + } + + if (file->do_fsync && git_futils_fsync_parent(file->path_original) < 0) + goto on_error; + + file->did_rename = true; + + git_filebuf_cleanup(file); + return 0; + +on_error: + git_filebuf_cleanup(file); + return -1; +} + +GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len) +{ + memcpy(file->buffer + file->buf_pos, buf, len); + file->buf_pos += len; +} + +int git_filebuf_write(git_filebuf *file, const void *buff, size_t len) +{ + const unsigned char *buf = buff; + + ENSURE_BUF_OK(file); + + if (file->do_not_buffer) + return file->write(file, (void *)buff, len); + + for (;;) { + size_t space_left = file->buf_size - file->buf_pos; + + /* cache if it's small */ + if (space_left > len) { + add_to_cache(file, buf, len); + return 0; + } + + add_to_cache(file, buf, space_left); + if (flush_buffer(file) < 0) + return -1; + + len -= space_left; + buf += space_left; + } +} + +int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len) +{ + size_t space_left = file->buf_size - file->buf_pos; + + *buffer = NULL; + + ENSURE_BUF_OK(file); + + if (len > file->buf_size) { + file->last_error = BUFERR_MEM; + return -1; + } + + if (space_left <= len) { + if (flush_buffer(file) < 0) + return -1; + } + + *buffer = (file->buffer + file->buf_pos); + file->buf_pos += len; + + return 0; +} + +int git_filebuf_printf(git_filebuf *file, const char *format, ...) +{ + va_list arglist; + size_t space_left, len, alloclen; + int written, res; + char *tmp_buffer; + + ENSURE_BUF_OK(file); + + space_left = file->buf_size - file->buf_pos; + + do { + va_start(arglist, format); + written = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); + va_end(arglist); + + if (written < 0) { + file->last_error = BUFERR_MEM; + return -1; + } + + len = written; + if (len + 1 <= space_left) { + file->buf_pos += len; + return 0; + } + + if (flush_buffer(file) < 0) + return -1; + + space_left = file->buf_size - file->buf_pos; + + } while (len + 1 <= space_left); + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, len, 1) || + !(tmp_buffer = git__malloc(alloclen))) { + file->last_error = BUFERR_MEM; + return -1; + } + + va_start(arglist, format); + written = p_vsnprintf(tmp_buffer, len + 1, format, arglist); + va_end(arglist); + + if (written < 0) { + git__free(tmp_buffer); + file->last_error = BUFERR_MEM; + return -1; + } + + res = git_filebuf_write(file, tmp_buffer, len); + git__free(tmp_buffer); + + return res; +} + +int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file) +{ + int res; + struct stat st; + + if (file->fd_is_open) + res = p_fstat(file->fd, &st); + else + res = p_stat(file->path_original, &st); + + if (res < 0) { + git_error_set(GIT_ERROR_OS, "could not get stat info for '%s'", + file->path_original); + return res; + } + + if (mtime) + *mtime = st.st_mtime; + if (size) + *size = (size_t)st.st_size; + + return 0; +} diff --git a/src/libgit2/filebuf.h b/src/libgit2/filebuf.h new file mode 100644 index 000000000..adbb19936 --- /dev/null +++ b/src/libgit2/filebuf.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_filebuf_h__ +#define INCLUDE_filebuf_h__ + +#include "common.h" + +#include "futils.h" +#include "hash.h" +#include + +#ifdef GIT_THREADS +# define GIT_FILEBUF_THREADS +#endif + +#define GIT_FILEBUF_HASH_CONTENTS (1 << 0) +#define GIT_FILEBUF_APPEND (1 << 2) +#define GIT_FILEBUF_CREATE_LEADING_DIRS (1 << 3) +#define GIT_FILEBUF_TEMPORARY (1 << 4) +#define GIT_FILEBUF_DO_NOT_BUFFER (1 << 5) +#define GIT_FILEBUF_FSYNC (1 << 6) +#define GIT_FILEBUF_DEFLATE_SHIFT (7) + +#define GIT_FILELOCK_EXTENSION ".lock\0" +#define GIT_FILELOCK_EXTLENGTH 6 + +typedef struct git_filebuf git_filebuf; +struct git_filebuf { + char *path_original; + char *path_lock; + + int (*write)(git_filebuf *file, void *source, size_t len); + + bool compute_digest; + git_hash_ctx digest; + + unsigned char *buffer; + unsigned char *z_buf; + + z_stream zs; + int flush_mode; + + size_t buf_size, buf_pos; + git_file fd; + bool fd_is_open; + bool created_lock; + bool did_rename; + bool do_not_buffer; + bool do_fsync; + int last_error; +}; + +#define GIT_FILEBUF_INIT {0} + +/* + * The git_filebuf object lifecycle is: + * - Allocate git_filebuf, preferably using GIT_FILEBUF_INIT. + * + * - Call git_filebuf_open() to initialize the filebuf for use. + * + * - Make as many calls to git_filebuf_write(), git_filebuf_printf(), + * git_filebuf_reserve() as you like. The error codes for these + * functions don't need to be checked. They are stored internally + * by the file buffer. + * + * - While you are writing, you may call git_filebuf_hash() to get + * the hash of all you have written so far. This function will + * fail if any of the previous writes to the buffer failed. + * + * - To close the git_filebuf, you may call git_filebuf_commit() or + * git_filebuf_commit_at() to save the file, or + * git_filebuf_cleanup() to abandon the file. All of these will + * free the git_filebuf object. Likewise, all of these will fail + * if any of the previous writes to the buffer failed, and set + * an error code accordingly. + */ +int git_filebuf_write(git_filebuf *lock, const void *buff, size_t len); +int git_filebuf_reserve(git_filebuf *file, void **buff, size_t len); +int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); + +int git_filebuf_open(git_filebuf *lock, const char *path, int flags, mode_t mode); +int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size); +int git_filebuf_commit(git_filebuf *lock); +int git_filebuf_commit_at(git_filebuf *lock, const char *path); +void git_filebuf_cleanup(git_filebuf *lock); +int git_filebuf_hash(unsigned char *out, git_filebuf *file); +int git_filebuf_flush(git_filebuf *file); +int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file); + +#endif diff --git a/src/libgit2/filter.c b/src/libgit2/filter.c new file mode 100644 index 000000000..2712e8c60 --- /dev/null +++ b/src/libgit2/filter.c @@ -0,0 +1,1191 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "filter.h" + +#include "buf.h" +#include "common.h" +#include "futils.h" +#include "hash.h" +#include "repository.h" +#include "runtime.h" +#include "git2/sys/filter.h" +#include "git2/config.h" +#include "blob.h" +#include "attr_file.h" +#include "array.h" +#include "path.h" + +struct git_filter_source { + git_repository *repo; + const char *path; + git_oid oid; /* zero if unknown (which is likely) */ + uint16_t filemode; /* zero if unknown */ + git_filter_mode_t mode; + git_filter_options options; +}; + +typedef struct { + const char *filter_name; + git_filter *filter; + void *payload; +} git_filter_entry; + +struct git_filter_list { + git_array_t(git_filter_entry) filters; + git_filter_source source; + git_str *temp_buf; + char path[GIT_FLEX_ARRAY]; +}; + +typedef struct { + char *filter_name; + git_filter *filter; + int priority; + int initialized; + size_t nattrs, nmatches; + char *attrdata; + const char *attrs[GIT_FLEX_ARRAY]; +} git_filter_def; + +static int filter_def_priority_cmp(const void *a, const void *b) +{ + int pa = ((const git_filter_def *)a)->priority; + int pb = ((const git_filter_def *)b)->priority; + return (pa < pb) ? -1 : (pa > pb) ? 1 : 0; +} + +struct git_filter_registry { + git_rwlock lock; + git_vector filters; +}; + +static struct git_filter_registry filter_registry; + +static void git_filter_global_shutdown(void); + + +static int filter_def_scan_attrs( + git_str *attrs, size_t *nattr, size_t *nmatch, const char *attr_str) +{ + const char *start, *scan = attr_str; + int has_eq; + + *nattr = *nmatch = 0; + + if (!scan) + return 0; + + while (*scan) { + while (git__isspace(*scan)) scan++; + + for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) { + if (*scan == '=') + has_eq = 1; + } + + if (scan > start) { + (*nattr)++; + if (has_eq || *start == '-' || *start == '+' || *start == '!') + (*nmatch)++; + + if (has_eq) + git_str_putc(attrs, '='); + git_str_put(attrs, start, scan - start); + git_str_putc(attrs, '\0'); + } + } + + return 0; +} + +static void filter_def_set_attrs(git_filter_def *fdef) +{ + char *scan = fdef->attrdata; + size_t i; + + for (i = 0; i < fdef->nattrs; ++i) { + const char *name, *value; + + switch (*scan) { + case '=': + name = scan + 1; + for (scan++; *scan != '='; scan++) /* find '=' */; + *scan++ = '\0'; + value = scan; + break; + case '-': + name = scan + 1; value = git_attr__false; break; + case '+': + name = scan + 1; value = git_attr__true; break; + case '!': + name = scan + 1; value = git_attr__unset; break; + default: + name = scan; value = NULL; break; + } + + fdef->attrs[i] = name; + fdef->attrs[i + fdef->nattrs] = value; + + scan += strlen(scan) + 1; + } +} + +static int filter_def_name_key_check(const void *key, const void *fdef) +{ + const char *name = + fdef ? ((const git_filter_def *)fdef)->filter_name : NULL; + return name ? git__strcmp(key, name) : -1; +} + +static int filter_def_filter_key_check(const void *key, const void *fdef) +{ + const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL; + return (key == filter) ? 0 : -1; +} + +/* Note: callers must lock the registry before calling this function */ +static int filter_registry_insert( + const char *name, git_filter *filter, int priority) +{ + git_filter_def *fdef; + size_t nattr = 0, nmatch = 0, alloc_len; + git_str attrs = GIT_STR_INIT; + + if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0) + return -1; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, nattr, 2); + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, alloc_len, sizeof(char *)); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, sizeof(git_filter_def)); + + fdef = git__calloc(1, alloc_len); + GIT_ERROR_CHECK_ALLOC(fdef); + + fdef->filter_name = git__strdup(name); + GIT_ERROR_CHECK_ALLOC(fdef->filter_name); + + fdef->filter = filter; + fdef->priority = priority; + fdef->nattrs = nattr; + fdef->nmatches = nmatch; + fdef->attrdata = git_str_detach(&attrs); + + filter_def_set_attrs(fdef); + + if (git_vector_insert(&filter_registry.filters, fdef) < 0) { + git__free(fdef->filter_name); + git__free(fdef->attrdata); + git__free(fdef); + return -1; + } + + git_vector_sort(&filter_registry.filters); + return 0; +} + +int git_filter_global_init(void) +{ + git_filter *crlf = NULL, *ident = NULL; + int error = 0; + + if (git_rwlock_init(&filter_registry.lock) < 0) + return -1; + + if ((error = git_vector_init(&filter_registry.filters, 2, + filter_def_priority_cmp)) < 0) + goto done; + + if ((crlf = git_crlf_filter_new()) == NULL || + filter_registry_insert( + GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0 || + (ident = git_ident_filter_new()) == NULL || + filter_registry_insert( + GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0) + error = -1; + + if (!error) + error = git_runtime_shutdown_register(git_filter_global_shutdown); + +done: + if (error) { + git_filter_free(crlf); + git_filter_free(ident); + } + + return error; +} + +static void git_filter_global_shutdown(void) +{ + size_t pos; + git_filter_def *fdef; + + if (git_rwlock_wrlock(&filter_registry.lock) < 0) + return; + + git_vector_foreach(&filter_registry.filters, pos, fdef) { + if (fdef->filter && fdef->filter->shutdown) { + fdef->filter->shutdown(fdef->filter); + fdef->initialized = false; + } + + git__free(fdef->filter_name); + git__free(fdef->attrdata); + git__free(fdef); + } + + git_vector_free(&filter_registry.filters); + + git_rwlock_wrunlock(&filter_registry.lock); + git_rwlock_free(&filter_registry.lock); +} + +/* Note: callers must lock the registry before calling this function */ +static int filter_registry_find(size_t *pos, const char *name) +{ + return git_vector_search2( + pos, &filter_registry.filters, filter_def_name_key_check, name); +} + +/* Note: callers must lock the registry before calling this function */ +static git_filter_def *filter_registry_lookup(size_t *pos, const char *name) +{ + git_filter_def *fdef = NULL; + + if (!filter_registry_find(pos, name)) + fdef = git_vector_get(&filter_registry.filters, *pos); + + return fdef; +} + + +int git_filter_register( + const char *name, git_filter *filter, int priority) +{ + int error; + + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(filter); + + if (git_rwlock_wrlock(&filter_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); + return -1; + } + + if (!filter_registry_find(NULL, name)) { + git_error_set( + GIT_ERROR_FILTER, "attempt to reregister existing filter '%s'", name); + error = GIT_EEXISTS; + goto done; + } + + error = filter_registry_insert(name, filter, priority); + +done: + git_rwlock_wrunlock(&filter_registry.lock); + return error; +} + +int git_filter_unregister(const char *name) +{ + size_t pos; + git_filter_def *fdef; + int error = 0; + + GIT_ASSERT_ARG(name); + + /* cannot unregister default filters */ + if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) { + git_error_set(GIT_ERROR_FILTER, "cannot unregister filter '%s'", name); + return -1; + } + + if (git_rwlock_wrlock(&filter_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); + return -1; + } + + if ((fdef = filter_registry_lookup(&pos, name)) == NULL) { + git_error_set(GIT_ERROR_FILTER, "cannot find filter '%s' to unregister", name); + error = GIT_ENOTFOUND; + goto done; + } + + git_vector_remove(&filter_registry.filters, pos); + + if (fdef->initialized && fdef->filter && fdef->filter->shutdown) { + fdef->filter->shutdown(fdef->filter); + fdef->initialized = false; + } + + git__free(fdef->filter_name); + git__free(fdef->attrdata); + git__free(fdef); + +done: + git_rwlock_wrunlock(&filter_registry.lock); + return error; +} + +static int filter_initialize(git_filter_def *fdef) +{ + int error = 0; + + if (!fdef->initialized && fdef->filter && fdef->filter->initialize) { + if ((error = fdef->filter->initialize(fdef->filter)) < 0) + return error; + } + + fdef->initialized = true; + return 0; +} + +git_filter *git_filter_lookup(const char *name) +{ + size_t pos; + git_filter_def *fdef; + git_filter *filter = NULL; + + if (git_rwlock_rdlock(&filter_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); + return NULL; + } + + if ((fdef = filter_registry_lookup(&pos, name)) == NULL || + (!fdef->initialized && filter_initialize(fdef) < 0)) + goto done; + + filter = fdef->filter; + +done: + git_rwlock_rdunlock(&filter_registry.lock); + return filter; +} + +void git_filter_free(git_filter *filter) +{ + git__free(filter); +} + +git_repository *git_filter_source_repo(const git_filter_source *src) +{ + return src->repo; +} + +const char *git_filter_source_path(const git_filter_source *src) +{ + return src->path; +} + +uint16_t git_filter_source_filemode(const git_filter_source *src) +{ + return src->filemode; +} + +const git_oid *git_filter_source_id(const git_filter_source *src) +{ + return git_oid_is_zero(&src->oid) ? NULL : &src->oid; +} + +git_filter_mode_t git_filter_source_mode(const git_filter_source *src) +{ + return src->mode; +} + +uint32_t git_filter_source_flags(const git_filter_source *src) +{ + return src->options.flags; +} + +static int filter_list_new( + git_filter_list **out, const git_filter_source *src) +{ + git_filter_list *fl = NULL; + size_t pathlen = src->path ? strlen(src->path) : 0, alloclen; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_filter_list), pathlen); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + + fl = git__calloc(1, alloclen); + GIT_ERROR_CHECK_ALLOC(fl); + + if (src->path) + memcpy(fl->path, src->path, pathlen); + fl->source.repo = src->repo; + fl->source.path = fl->path; + fl->source.mode = src->mode; + + memcpy(&fl->source.options, &src->options, sizeof(git_filter_options)); + + *out = fl; + return 0; +} + +static int filter_list_check_attributes( + const char ***out, + git_repository *repo, + git_filter_session *filter_session, + git_filter_def *fdef, + const git_filter_source *src) +{ + const char **strs = git__calloc(fdef->nattrs, sizeof(const char *)); + git_attr_options attr_opts = GIT_ATTR_OPTIONS_INIT; + size_t i; + int error; + + GIT_ERROR_CHECK_ALLOC(strs); + + if ((src->options.flags & GIT_FILTER_NO_SYSTEM_ATTRIBUTES) != 0) + attr_opts.flags |= GIT_ATTR_CHECK_NO_SYSTEM; + + if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_HEAD) != 0) + attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_HEAD; + + if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_COMMIT) != 0) { + attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_COMMIT; + +#ifndef GIT_DEPRECATE_HARD + if (src->options.commit_id) + git_oid_cpy(&attr_opts.attr_commit_id, src->options.commit_id); + else +#endif + git_oid_cpy(&attr_opts.attr_commit_id, &src->options.attr_commit_id); + } + + error = git_attr_get_many_with_session( + strs, repo, filter_session->attr_session, &attr_opts, src->path, fdef->nattrs, fdef->attrs); + + /* if no values were found but no matches are needed, it's okay! */ + if (error == GIT_ENOTFOUND && !fdef->nmatches) { + git_error_clear(); + git__free((void *)strs); + return 0; + } + + for (i = 0; !error && i < fdef->nattrs; ++i) { + const char *want = fdef->attrs[fdef->nattrs + i]; + git_attr_value_t want_type, found_type; + + if (!want) + continue; + + want_type = git_attr_value(want); + found_type = git_attr_value(strs[i]); + + if (want_type != found_type) + error = GIT_ENOTFOUND; + else if (want_type == GIT_ATTR_VALUE_STRING && + strcmp(want, strs[i]) && + strcmp(want, "*")) + error = GIT_ENOTFOUND; + } + + if (error) + git__free((void *)strs); + else + *out = strs; + + return error; +} + +int git_filter_list_new( + git_filter_list **out, + git_repository *repo, + git_filter_mode_t mode, + uint32_t flags) +{ + git_filter_source src = { 0 }; + src.repo = repo; + src.path = NULL; + src.mode = mode; + src.options.flags = flags; + return filter_list_new(out, &src); +} + +int git_filter_list__load( + git_filter_list **filters, + git_repository *repo, + git_blob *blob, /* can be NULL */ + const char *path, + git_filter_mode_t mode, + git_filter_session *filter_session) +{ + int error = 0; + git_filter_list *fl = NULL; + git_filter_source src = { 0 }; + git_filter_entry *fe; + size_t idx; + git_filter_def *fdef; + + if (git_rwlock_rdlock(&filter_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); + return -1; + } + + src.repo = repo; + src.path = path; + src.mode = mode; + + memcpy(&src.options, &filter_session->options, sizeof(git_filter_options)); + + if (blob) + git_oid_cpy(&src.oid, git_blob_id(blob)); + + git_vector_foreach(&filter_registry.filters, idx, fdef) { + const char **values = NULL; + void *payload = NULL; + + if (!fdef || !fdef->filter) + continue; + + if (fdef->nattrs > 0) { + error = filter_list_check_attributes( + &values, repo, + filter_session, fdef, &src); + + if (error == GIT_ENOTFOUND) { + error = 0; + continue; + } else if (error < 0) + break; + } + + if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) + break; + + if (fdef->filter->check) + error = fdef->filter->check( + fdef->filter, &payload, &src, values); + + git__free((void *)values); + + if (error == GIT_PASSTHROUGH) + error = 0; + else if (error < 0) + break; + else { + if (!fl) { + if ((error = filter_list_new(&fl, &src)) < 0) + break; + + fl->temp_buf = filter_session->temp_buf; + } + + fe = git_array_alloc(fl->filters); + GIT_ERROR_CHECK_ALLOC(fe); + + fe->filter = fdef->filter; + fe->filter_name = fdef->filter_name; + fe->payload = payload; + } + } + + git_rwlock_rdunlock(&filter_registry.lock); + + if (error && fl != NULL) { + git_array_clear(fl->filters); + git__free(fl); + fl = NULL; + } + + *filters = fl; + return error; +} + +int git_filter_list_load_ext( + git_filter_list **filters, + git_repository *repo, + git_blob *blob, /* can be NULL */ + const char *path, + git_filter_mode_t mode, + git_filter_options *opts) +{ + git_filter_session filter_session = GIT_FILTER_SESSION_INIT; + + if (opts) + memcpy(&filter_session.options, opts, sizeof(git_filter_options)); + + return git_filter_list__load( + filters, repo, blob, path, mode, &filter_session); +} + +int git_filter_list_load( + git_filter_list **filters, + git_repository *repo, + git_blob *blob, /* can be NULL */ + const char *path, + git_filter_mode_t mode, + uint32_t flags) +{ + git_filter_session filter_session = GIT_FILTER_SESSION_INIT; + + filter_session.options.flags = flags; + + return git_filter_list__load( + filters, repo, blob, path, mode, &filter_session); +} + +void git_filter_list_free(git_filter_list *fl) +{ + uint32_t i; + + if (!fl) + return; + + for (i = 0; i < git_array_size(fl->filters); ++i) { + git_filter_entry *fe = git_array_get(fl->filters, i); + if (fe->filter->cleanup) + fe->filter->cleanup(fe->filter, fe->payload); + } + + git_array_clear(fl->filters); + git__free(fl); +} + +int git_filter_list_contains( + git_filter_list *fl, + const char *name) +{ + size_t i; + + GIT_ASSERT_ARG(name); + + if (!fl) + return 0; + + for (i = 0; i < fl->filters.size; i++) { + if (strcmp(fl->filters.ptr[i].filter_name, name) == 0) + return 1; + } + + return 0; +} + +int git_filter_list_push( + git_filter_list *fl, git_filter *filter, void *payload) +{ + int error = 0; + size_t pos; + git_filter_def *fdef = NULL; + git_filter_entry *fe; + + GIT_ASSERT_ARG(fl); + GIT_ASSERT_ARG(filter); + + if (git_rwlock_rdlock(&filter_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock filter registry"); + return -1; + } + + if (git_vector_search2( + &pos, &filter_registry.filters, + filter_def_filter_key_check, filter) == 0) + fdef = git_vector_get(&filter_registry.filters, pos); + + git_rwlock_rdunlock(&filter_registry.lock); + + if (fdef == NULL) { + git_error_set(GIT_ERROR_FILTER, "cannot use an unregistered filter"); + return -1; + } + + if (!fdef->initialized && (error = filter_initialize(fdef)) < 0) + return error; + + fe = git_array_alloc(fl->filters); + GIT_ERROR_CHECK_ALLOC(fe); + fe->filter = filter; + fe->payload = payload; + + return 0; +} + +size_t git_filter_list_length(const git_filter_list *fl) +{ + return fl ? git_array_size(fl->filters) : 0; +} + +struct buf_stream { + git_writestream parent; + git_str *target; + bool complete; +}; + +static int buf_stream_write( + git_writestream *s, const char *buffer, size_t len) +{ + struct buf_stream *buf_stream = (struct buf_stream *)s; + GIT_ASSERT_ARG(buf_stream); + GIT_ASSERT(buf_stream->complete == 0); + + return git_str_put(buf_stream->target, buffer, len); +} + +static int buf_stream_close(git_writestream *s) +{ + struct buf_stream *buf_stream = (struct buf_stream *)s; + GIT_ASSERT_ARG(buf_stream); + + GIT_ASSERT(buf_stream->complete == 0); + buf_stream->complete = 1; + + return 0; +} + +static void buf_stream_free(git_writestream *s) +{ + GIT_UNUSED(s); +} + +static void buf_stream_init(struct buf_stream *writer, git_str *target) +{ + memset(writer, 0, sizeof(struct buf_stream)); + + writer->parent.write = buf_stream_write; + writer->parent.close = buf_stream_close; + writer->parent.free = buf_stream_free; + writer->target = target; + + git_str_clear(target); +} + +int git_filter_list_apply_to_buffer( + git_buf *out, + git_filter_list *filters, + const char *in, + size_t in_len) +{ + GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_buffer, filters, in, in_len); +} + +int git_filter_list__apply_to_buffer( + git_str *out, + git_filter_list *filters, + const char *in, + size_t in_len) +{ + struct buf_stream writer; + int error; + + buf_stream_init(&writer, out); + + if ((error = git_filter_list_stream_buffer(filters, + in, in_len, &writer.parent)) < 0) + return error; + + GIT_ASSERT(writer.complete); + return error; +} + +int git_filter_list__convert_buf( + git_str *out, + git_filter_list *filters, + git_str *in) +{ + int error; + + if (!filters || git_filter_list_length(filters) == 0) { + git_str_swap(out, in); + git_str_dispose(in); + return 0; + } + + error = git_filter_list__apply_to_buffer(out, filters, + in->ptr, in->size); + + if (!error) + git_str_dispose(in); + + return error; +} + +int git_filter_list_apply_to_file( + git_buf *out, + git_filter_list *filters, + git_repository *repo, + const char *path) +{ + GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_file, filters, repo, path); +} + +int git_filter_list__apply_to_file( + git_str *out, + git_filter_list *filters, + git_repository *repo, + const char *path) +{ + struct buf_stream writer; + int error; + + buf_stream_init(&writer, out); + + if ((error = git_filter_list_stream_file( + filters, repo, path, &writer.parent)) < 0) + return error; + + GIT_ASSERT(writer.complete); + return error; +} + +static int buf_from_blob(git_str *out, git_blob *blob) +{ + git_object_size_t rawsize = git_blob_rawsize(blob); + + if (!git__is_sizet(rawsize)) { + git_error_set(GIT_ERROR_OS, "blob is too large to filter"); + return -1; + } + + git_str_attach_notowned(out, git_blob_rawcontent(blob), (size_t)rawsize); + return 0; +} + +int git_filter_list_apply_to_blob( + git_buf *out, + git_filter_list *filters, + git_blob *blob) +{ + GIT_BUF_WRAP_PRIVATE(out, git_filter_list__apply_to_blob, filters, blob); +} + +int git_filter_list__apply_to_blob( + git_str *out, + git_filter_list *filters, + git_blob *blob) +{ + struct buf_stream writer; + int error; + + buf_stream_init(&writer, out); + + if ((error = git_filter_list_stream_blob( + filters, blob, &writer.parent)) < 0) + return error; + + GIT_ASSERT(writer.complete); + return error; +} + +struct buffered_stream { + git_writestream parent; + git_filter *filter; + int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *); + int (*legacy_write_fn)(git_filter *, void **, git_buf *, const git_buf *, const git_filter_source *); + const git_filter_source *source; + void **payload; + git_str input; + git_str temp_buf; + git_str *output; + git_writestream *target; +}; + +static int buffered_stream_write( + git_writestream *s, const char *buffer, size_t len) +{ + struct buffered_stream *buffered_stream = (struct buffered_stream *)s; + GIT_ASSERT_ARG(buffered_stream); + + return git_str_put(&buffered_stream->input, buffer, len); +} + +static int buffered_stream_close(git_writestream *s) +{ + struct buffered_stream *buffered_stream = (struct buffered_stream *)s; + git_str *writebuf; + git_error_state error_state = {0}; + int error; + + GIT_ASSERT_ARG(buffered_stream); + + error = buffered_stream->write_fn( + buffered_stream->filter, + buffered_stream->payload, + buffered_stream->output, + &buffered_stream->input, + buffered_stream->source); + + if (error == GIT_PASSTHROUGH) { + writebuf = &buffered_stream->input; + } else if (error == 0) { + writebuf = buffered_stream->output; + } else { + /* close stream before erroring out taking care + * to preserve the original error */ + git_error_state_capture(&error_state, error); + buffered_stream->target->close(buffered_stream->target); + git_error_state_restore(&error_state); + return error; + } + + if ((error = buffered_stream->target->write( + buffered_stream->target, writebuf->ptr, writebuf->size)) == 0) + error = buffered_stream->target->close(buffered_stream->target); + + return error; +} + +static void buffered_stream_free(git_writestream *s) +{ + struct buffered_stream *buffered_stream = (struct buffered_stream *)s; + + if (buffered_stream) { + git_str_dispose(&buffered_stream->input); + git_str_dispose(&buffered_stream->temp_buf); + git__free(buffered_stream); + } +} + +int git_filter_buffered_stream_new( + git_writestream **out, + git_filter *filter, + int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *), + git_str *temp_buf, + void **payload, + const git_filter_source *source, + git_writestream *target) +{ + struct buffered_stream *buffered_stream = git__calloc(1, sizeof(struct buffered_stream)); + GIT_ERROR_CHECK_ALLOC(buffered_stream); + + buffered_stream->parent.write = buffered_stream_write; + buffered_stream->parent.close = buffered_stream_close; + buffered_stream->parent.free = buffered_stream_free; + buffered_stream->filter = filter; + buffered_stream->write_fn = write_fn; + buffered_stream->output = temp_buf ? temp_buf : &buffered_stream->temp_buf; + buffered_stream->payload = payload; + buffered_stream->source = source; + buffered_stream->target = target; + + if (temp_buf) + git_str_clear(temp_buf); + + *out = (git_writestream *)buffered_stream; + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +static int buffered_legacy_stream_new( + git_writestream **out, + git_filter *filter, + int (*legacy_write_fn)(git_filter *, void **, git_buf *, const git_buf *, const git_filter_source *), + git_str *temp_buf, + void **payload, + const git_filter_source *source, + git_writestream *target) +{ + struct buffered_stream *buffered_stream = git__calloc(1, sizeof(struct buffered_stream)); + GIT_ERROR_CHECK_ALLOC(buffered_stream); + + buffered_stream->parent.write = buffered_stream_write; + buffered_stream->parent.close = buffered_stream_close; + buffered_stream->parent.free = buffered_stream_free; + buffered_stream->filter = filter; + buffered_stream->legacy_write_fn = legacy_write_fn; + buffered_stream->output = temp_buf ? temp_buf : &buffered_stream->temp_buf; + buffered_stream->payload = payload; + buffered_stream->source = source; + buffered_stream->target = target; + + if (temp_buf) + git_str_clear(temp_buf); + + *out = (git_writestream *)buffered_stream; + return 0; +} +#endif + +static int setup_stream( + git_writestream **out, + git_filter_entry *fe, + git_filter_list *filters, + git_writestream *last_stream) +{ +#ifndef GIT_DEPRECATE_HARD + GIT_ASSERT(fe->filter->stream || fe->filter->apply); + + /* + * If necessary, create a stream that proxies the traditional + * application. + */ + if (!fe->filter->stream) { + /* Create a stream that proxies the one-shot apply */ + return buffered_legacy_stream_new(out, + fe->filter, fe->filter->apply, filters->temp_buf, + &fe->payload, &filters->source, last_stream); + } +#endif + + GIT_ASSERT(fe->filter->stream); + return fe->filter->stream(out, fe->filter, + &fe->payload, &filters->source, last_stream); +} + +static int stream_list_init( + git_writestream **out, + git_vector *streams, + git_filter_list *filters, + git_writestream *target) +{ + git_writestream *last_stream = target; + size_t i; + int error = 0; + + *out = NULL; + + if (!filters) { + *out = target; + return 0; + } + + /* Create filters last to first to get the chaining direction */ + for (i = 0; i < git_array_size(filters->filters); ++i) { + size_t filter_idx = (filters->source.mode == GIT_FILTER_TO_WORKTREE) ? + git_array_size(filters->filters) - 1 - i : i; + + git_filter_entry *fe = git_array_get(filters->filters, filter_idx); + git_writestream *filter_stream; + + error = setup_stream(&filter_stream, fe, filters, last_stream); + + if (error < 0) + goto out; + + git_vector_insert(streams, filter_stream); + last_stream = filter_stream; + } + +out: + if (error) + last_stream->close(last_stream); + else + *out = last_stream; + + return error; +} + +static void filter_streams_free(git_vector *streams) +{ + git_writestream *stream; + size_t i; + + git_vector_foreach(streams, i, stream) + stream->free(stream); + git_vector_free(streams); +} + +int git_filter_list_stream_file( + git_filter_list *filters, + git_repository *repo, + const char *path, + git_writestream *target) +{ + char buf[FILTERIO_BUFSIZE]; + git_str abspath = GIT_STR_INIT; + const char *base = repo ? git_repository_workdir(repo) : NULL; + git_vector filter_streams = GIT_VECTOR_INIT; + git_writestream *stream_start; + ssize_t readlen; + int fd = -1, error, initialized = 0; + + if ((error = stream_list_init( + &stream_start, &filter_streams, filters, target)) < 0 || + (error = git_fs_path_join_unrooted(&abspath, path, base, NULL)) < 0 || + (error = git_path_validate_str_length(repo, &abspath)) < 0) + goto done; + + initialized = 1; + + if ((fd = git_futils_open_ro(abspath.ptr)) < 0) { + error = fd; + goto done; + } + + while ((readlen = p_read(fd, buf, sizeof(buf))) > 0) { + if ((error = stream_start->write(stream_start, buf, readlen)) < 0) + goto done; + } + + if (readlen < 0) + error = -1; + +done: + if (initialized) + error |= stream_start->close(stream_start); + + if (fd >= 0) + p_close(fd); + filter_streams_free(&filter_streams); + git_str_dispose(&abspath); + return error; +} + +int git_filter_list_stream_buffer( + git_filter_list *filters, + const char *buffer, + size_t len, + git_writestream *target) +{ + git_vector filter_streams = GIT_VECTOR_INIT; + git_writestream *stream_start; + int error, initialized = 0; + + if ((error = stream_list_init(&stream_start, &filter_streams, filters, target)) < 0) + goto out; + initialized = 1; + + if ((error = stream_start->write(stream_start, buffer, len)) < 0) + goto out; + +out: + if (initialized) + error |= stream_start->close(stream_start); + + filter_streams_free(&filter_streams); + return error; +} + +int git_filter_list_stream_blob( + git_filter_list *filters, + git_blob *blob, + git_writestream *target) +{ + git_str in = GIT_STR_INIT; + + if (buf_from_blob(&in, blob) < 0) + return -1; + + if (filters) + git_oid_cpy(&filters->source.oid, git_blob_id(blob)); + + return git_filter_list_stream_buffer(filters, in.ptr, in.size, target); +} + +int git_filter_init(git_filter *filter, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE(filter, version, git_filter, GIT_FILTER_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD + +int git_filter_list_stream_data( + git_filter_list *filters, + git_buf *data, + git_writestream *target) +{ + return git_filter_list_stream_buffer(filters, data->ptr, data->size, target); +} + +int git_filter_list_apply_to_data( + git_buf *tgt, git_filter_list *filters, git_buf *src) +{ + return git_filter_list_apply_to_buffer(tgt, filters, src->ptr, src->size); +} + +#endif diff --git a/src/libgit2/filter.h b/src/libgit2/filter.h new file mode 100644 index 000000000..58cb4b424 --- /dev/null +++ b/src/libgit2/filter.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_filter_h__ +#define INCLUDE_filter_h__ + +#include "common.h" + +#include "attr_file.h" +#include "git2/filter.h" +#include "git2/sys/filter.h" + +/* Amount of file to examine for NUL byte when checking binary-ness */ +#define GIT_FILTER_BYTES_TO_CHECK_NUL 8000 + +typedef struct { + git_filter_options options; + git_attr_session *attr_session; + git_str *temp_buf; +} git_filter_session; + +#define GIT_FILTER_SESSION_INIT {GIT_FILTER_OPTIONS_INIT, 0} + +extern int git_filter_global_init(void); + +extern void git_filter_free(git_filter *filter); + +extern int git_filter_list__load( + git_filter_list **filters, + git_repository *repo, + git_blob *blob, /* can be NULL */ + const char *path, + git_filter_mode_t mode, + git_filter_session *filter_session); + +int git_filter_list__apply_to_buffer( + git_str *out, + git_filter_list *filters, + const char *in, + size_t in_len); +int git_filter_list__apply_to_file( + git_str *out, + git_filter_list *filters, + git_repository *repo, + const char *path); +int git_filter_list__apply_to_blob( + git_str *out, + git_filter_list *filters, + git_blob *blob); + +/* + * The given input buffer will be converted to the given output buffer. + * The input buffer will be freed (_if_ it was allocated). + */ +extern int git_filter_list__convert_buf( + git_str *out, + git_filter_list *filters, + git_str *in); + +extern int git_filter_list__apply_to_file( + git_str *out, + git_filter_list *filters, + git_repository *repo, + const char *path); + +/* + * Available filters + */ + +extern git_filter *git_crlf_filter_new(void); +extern git_filter *git_ident_filter_new(void); + +extern int git_filter_buffered_stream_new( + git_writestream **out, + git_filter *filter, + int (*write_fn)(git_filter *, void **, git_str *, const git_str *, const git_filter_source *), + git_str *temp_buf, + void **payload, + const git_filter_source *source, + git_writestream *target); + +#endif diff --git a/src/libgit2/fs_path.c b/src/libgit2/fs_path.c new file mode 100644 index 000000000..7a657778a --- /dev/null +++ b/src/libgit2/fs_path.c @@ -0,0 +1,1912 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "fs_path.h" + +#include "posix.h" +#include "repository.h" +#ifdef GIT_WIN32 +#include "win32/posix.h" +#include "win32/w32_buffer.h" +#include "win32/w32_util.h" +#include "win32/version.h" +#include +#else +#include +#endif +#include +#include + +static int dos_drive_prefix_length(const char *path) +{ + int i; + + /* + * Does it start with an ASCII letter (i.e. highest bit not set), + * followed by a colon? + */ + if (!(0x80 & (unsigned char)*path)) + return *path && path[1] == ':' ? 2 : 0; + + /* + * While drive letters must be letters of the English alphabet, it is + * possible to assign virtually _any_ Unicode character via `subst` as + * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff + * like this: + * + * subst ֍: %USERPROFILE%\Desktop + */ + for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++) + ; /* skip first UTF-8 character */ + return path[i] == ':' ? i + 1 : 0; +} + +#ifdef GIT_WIN32 +static bool looks_like_network_computer_name(const char *path, int pos) +{ + if (pos < 3) + return false; + + if (path[0] != '/' || path[1] != '/') + return false; + + while (pos-- > 2) { + if (path[pos] == '/') + return false; + } + + return true; +} +#endif + +/* + * Based on the Android implementation, BSD licensed. + * http://android.git.kernel.org/ + * + * Copyright (C) 2008 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +int git_fs_path_basename_r(git_str *buffer, const char *path) +{ + const char *endp, *startp; + int len, result; + + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + startp = "."; + len = 1; + goto Exit; + } + + /* Strip trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + /* All slashes becomes "/" */ + if (endp == path && *endp == '/') { + startp = "/"; + len = 1; + goto Exit; + } + + /* Find the start of the base */ + startp = endp; + while (startp > path && *(startp - 1) != '/') + startp--; + + /* Cast is safe because max path < max int */ + len = (int)(endp - startp + 1); + +Exit: + result = len; + + if (buffer != NULL && git_str_set(buffer, startp, len) < 0) + return -1; + + return result; +} + +/* + * Determine if the path is a Windows prefix and, if so, returns + * its actual length. If it is not a prefix, returns -1. + */ +static int win32_prefix_length(const char *path, int len) +{ +#ifndef GIT_WIN32 + GIT_UNUSED(path); + GIT_UNUSED(len); +#else + /* + * Mimic unix behavior where '/.git' returns '/': 'C:/.git' + * will return 'C:/' here + */ + if (dos_drive_prefix_length(path) == len) + return len; + + /* + * Similarly checks if we're dealing with a network computer name + * '//computername/.git' will return '//computername/' + */ + if (looks_like_network_computer_name(path, len)) + return len; +#endif + + return -1; +} + +/* + * Based on the Android implementation, BSD licensed. + * Check http://android.git.kernel.org/ + */ +int git_fs_path_dirname_r(git_str *buffer, const char *path) +{ + const char *endp; + int is_prefix = 0, len; + + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + path = "."; + len = 1; + goto Exit; + } + + /* Strip trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + if (endp - path + 1 > INT_MAX) { + git_error_set(GIT_ERROR_INVALID, "path too long"); + len = -1; + goto Exit; + } + + if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) { + is_prefix = 1; + goto Exit; + } + + /* Find the start of the dir */ + while (endp > path && *endp != '/') + endp--; + + /* Either the dir is "/" or there are no slashes */ + if (endp == path) { + path = (*endp == '/') ? "/" : "."; + len = 1; + goto Exit; + } + + do { + endp--; + } while (endp > path && *endp == '/'); + + if (endp - path + 1 > INT_MAX) { + git_error_set(GIT_ERROR_INVALID, "path too long"); + len = -1; + goto Exit; + } + + if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) { + is_prefix = 1; + goto Exit; + } + + /* Cast is safe because max path < max int */ + len = (int)(endp - path + 1); + +Exit: + if (buffer) { + if (git_str_set(buffer, path, len) < 0) + return -1; + if (is_prefix && git_str_putc(buffer, '/') < 0) + return -1; + } + + return len; +} + + +char *git_fs_path_dirname(const char *path) +{ + git_str buf = GIT_STR_INIT; + char *dirname; + + git_fs_path_dirname_r(&buf, path); + dirname = git_str_detach(&buf); + git_str_dispose(&buf); /* avoid memleak if error occurs */ + + return dirname; +} + +char *git_fs_path_basename(const char *path) +{ + git_str buf = GIT_STR_INIT; + char *basename; + + git_fs_path_basename_r(&buf, path); + basename = git_str_detach(&buf); + git_str_dispose(&buf); /* avoid memleak if error occurs */ + + return basename; +} + +size_t git_fs_path_basename_offset(git_str *buffer) +{ + ssize_t slash; + + if (!buffer || buffer->size <= 0) + return 0; + + slash = git_str_rfind_next(buffer, '/'); + + if (slash >= 0 && buffer->ptr[slash] == '/') + return (size_t)(slash + 1); + + return 0; +} + +int git_fs_path_root(const char *path) +{ + int offset = 0, prefix_len; + + /* Does the root of the path look like a windows drive ? */ + if ((prefix_len = dos_drive_prefix_length(path))) + offset += prefix_len; + +#ifdef GIT_WIN32 + /* Are we dealing with a windows network path? */ + else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') || + (path[0] == '\\' && path[1] == '\\' && path[2] != '\\')) + { + offset += 2; + + /* Skip the computer name segment */ + while (path[offset] && path[offset] != '/' && path[offset] != '\\') + offset++; + } + + if (path[offset] == '\\') + return offset; +#endif + + if (path[offset] == '/') + return offset; + + return -1; /* Not a real error - signals that path is not rooted */ +} + +static void path_trim_slashes(git_str *path) +{ + int ceiling = git_fs_path_root(path->ptr) + 1; + + if (ceiling < 0) + return; + + while (path->size > (size_t)ceiling) { + if (path->ptr[path->size-1] != '/') + break; + + path->ptr[path->size-1] = '\0'; + path->size--; + } +} + +int git_fs_path_join_unrooted( + git_str *path_out, const char *path, const char *base, ssize_t *root_at) +{ + ssize_t root; + + GIT_ASSERT_ARG(path_out); + GIT_ASSERT_ARG(path); + + root = (ssize_t)git_fs_path_root(path); + + if (base != NULL && root < 0) { + if (git_str_joinpath(path_out, base, path) < 0) + return -1; + + root = (ssize_t)strlen(base); + } else { + if (git_str_sets(path_out, path) < 0) + return -1; + + if (root < 0) + root = 0; + else if (base) + git_fs_path_equal_or_prefixed(base, path, &root); + } + + if (root_at) + *root_at = root; + + return 0; +} + +void git_fs_path_squash_slashes(git_str *path) +{ + char *p, *q; + + if (path->size == 0) + return; + + for (p = path->ptr, q = path->ptr; *q; p++, q++) { + *p = *q; + + while (*q == '/' && *(q+1) == '/') { + path->size--; + q++; + } + } + + *p = '\0'; +} + +int git_fs_path_prettify(git_str *path_out, const char *path, const char *base) +{ + char buf[GIT_PATH_MAX]; + + GIT_ASSERT_ARG(path_out); + GIT_ASSERT_ARG(path); + + /* construct path if needed */ + if (base != NULL && git_fs_path_root(path) < 0) { + if (git_str_joinpath(path_out, base, path) < 0) + return -1; + path = path_out->ptr; + } + + if (p_realpath(path, buf) == NULL) { + /* git_error_set resets the errno when dealing with a GIT_ERROR_OS kind of error */ + int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1; + git_error_set(GIT_ERROR_OS, "failed to resolve path '%s'", path); + + git_str_clear(path_out); + + return error; + } + + return git_str_sets(path_out, buf); +} + +int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base) +{ + int error = git_fs_path_prettify(path_out, path, base); + return (error < 0) ? error : git_fs_path_to_dir(path_out); +} + +int git_fs_path_to_dir(git_str *path) +{ + if (path->asize > 0 && + git_str_len(path) > 0 && + path->ptr[git_str_len(path) - 1] != '/') + git_str_putc(path, '/'); + + return git_str_oom(path) ? -1 : 0; +} + +void git_fs_path_string_to_dir(char *path, size_t size) +{ + size_t end = strlen(path); + + if (end && path[end - 1] != '/' && end < size) { + path[end] = '/'; + path[end + 1] = '\0'; + } +} + +int git__percent_decode(git_str *decoded_out, const char *input) +{ + int len, hi, lo, i; + + GIT_ASSERT_ARG(decoded_out); + GIT_ASSERT_ARG(input); + + len = (int)strlen(input); + git_str_clear(decoded_out); + + for(i = 0; i < len; i++) + { + char c = input[i]; + + if (c != '%') + goto append; + + if (i >= len - 2) + goto append; + + hi = git__fromhex(input[i + 1]); + lo = git__fromhex(input[i + 2]); + + if (hi < 0 || lo < 0) + goto append; + + c = (char)(hi << 4 | lo); + i += 2; + +append: + if (git_str_putc(decoded_out, c) < 0) + return -1; + } + + return 0; +} + +static int error_invalid_local_file_uri(const char *uri) +{ + git_error_set(GIT_ERROR_CONFIG, "'%s' is not a valid local file URI", uri); + return -1; +} + +static int local_file_url_prefixlen(const char *file_url) +{ + int len = -1; + + if (git__prefixcmp(file_url, "file://") == 0) { + if (file_url[7] == '/') + len = 8; + else if (git__prefixcmp(file_url + 7, "localhost/") == 0) + len = 17; + } + + return len; +} + +bool git_fs_path_is_local_file_url(const char *file_url) +{ + return (local_file_url_prefixlen(file_url) > 0); +} + +int git_fs_path_fromurl(git_str *local_path_out, const char *file_url) +{ + int offset; + + GIT_ASSERT_ARG(local_path_out); + GIT_ASSERT_ARG(file_url); + + if ((offset = local_file_url_prefixlen(file_url)) < 0 || + file_url[offset] == '\0' || file_url[offset] == '/') + return error_invalid_local_file_uri(file_url); + +#ifndef GIT_WIN32 + offset--; /* A *nix absolute path starts with a forward slash */ +#endif + + git_str_clear(local_path_out); + return git__percent_decode(local_path_out, file_url + offset); +} + +int git_fs_path_walk_up( + git_str *path, + const char *ceiling, + int (*cb)(void *data, const char *), + void *data) +{ + int error = 0; + git_str iter; + ssize_t stop = 0, scan; + char oldc = '\0'; + + GIT_ASSERT_ARG(path); + GIT_ASSERT_ARG(cb); + + if (ceiling != NULL) { + if (git__prefixcmp(path->ptr, ceiling) == 0) + stop = (ssize_t)strlen(ceiling); + else + stop = git_str_len(path); + } + scan = git_str_len(path); + + /* empty path: yield only once */ + if (!scan) { + error = cb(data, ""); + if (error) + git_error_set_after_callback(error); + return error; + } + + iter.ptr = path->ptr; + iter.size = git_str_len(path); + iter.asize = path->asize; + + while (scan >= stop) { + error = cb(data, iter.ptr); + iter.ptr[scan] = oldc; + + if (error) { + git_error_set_after_callback(error); + break; + } + + scan = git_str_rfind_next(&iter, '/'); + if (scan >= 0) { + scan++; + oldc = iter.ptr[scan]; + iter.size = scan; + iter.ptr[scan] = '\0'; + } + } + + if (scan >= 0) + iter.ptr[scan] = oldc; + + /* relative path: yield for the last component */ + if (!error && stop == 0 && iter.ptr[0] != '/') { + error = cb(data, ""); + if (error) + git_error_set_after_callback(error); + } + + return error; +} + +bool git_fs_path_exists(const char *path) +{ + GIT_ASSERT_ARG_WITH_RETVAL(path, false); + return p_access(path, F_OK) == 0; +} + +bool git_fs_path_isdir(const char *path) +{ + struct stat st; + if (p_stat(path, &st) < 0) + return false; + + return S_ISDIR(st.st_mode) != 0; +} + +bool git_fs_path_isfile(const char *path) +{ + struct stat st; + + GIT_ASSERT_ARG_WITH_RETVAL(path, false); + if (p_stat(path, &st) < 0) + return false; + + return S_ISREG(st.st_mode) != 0; +} + +bool git_fs_path_islink(const char *path) +{ + struct stat st; + + GIT_ASSERT_ARG_WITH_RETVAL(path, false); + if (p_lstat(path, &st) < 0) + return false; + + return S_ISLNK(st.st_mode) != 0; +} + +#ifdef GIT_WIN32 + +bool git_fs_path_is_empty_dir(const char *path) +{ + git_win32_path filter_w; + bool empty = false; + + if (git_win32__findfirstfile_filter(filter_w, path)) { + WIN32_FIND_DATAW findData; + HANDLE hFind = FindFirstFileW(filter_w, &findData); + + /* FindFirstFile will fail if there are no children to the given + * path, which can happen if the given path is a file (and obviously + * has no children) or if the given path is an empty mount point. + * (Most directories have at least directory entries '.' and '..', + * but ridiculously another volume mounted in another drive letter's + * path space do not, and thus have nothing to enumerate.) If + * FindFirstFile fails, check if this is a directory-like thing + * (a mount point). + */ + if (hFind == INVALID_HANDLE_VALUE) + return git_fs_path_isdir(path); + + /* If the find handle was created successfully, then it's a directory */ + empty = true; + + do { + /* Allow the enumeration to return . and .. and still be considered + * empty. In the special case of drive roots (i.e. C:\) where . and + * .. do not occur, we can still consider the path to be an empty + * directory if there's nothing there. */ + if (!git_fs_path_is_dot_or_dotdotW(findData.cFileName)) { + empty = false; + break; + } + } while (FindNextFileW(hFind, &findData)); + + FindClose(hFind); + } + + return empty; +} + +#else + +static int path_found_entry(void *payload, git_str *path) +{ + GIT_UNUSED(payload); + return !git_fs_path_is_dot_or_dotdot(path->ptr); +} + +bool git_fs_path_is_empty_dir(const char *path) +{ + int error; + git_str dir = GIT_STR_INIT; + + if (!git_fs_path_isdir(path)) + return false; + + if ((error = git_str_sets(&dir, path)) != 0) + git_error_clear(); + else + error = git_fs_path_direach(&dir, 0, path_found_entry, NULL); + + git_str_dispose(&dir); + + return !error; +} + +#endif + +int git_fs_path_set_error(int errno_value, const char *path, const char *action) +{ + switch (errno_value) { + case ENOENT: + case ENOTDIR: + git_error_set(GIT_ERROR_OS, "could not find '%s' to %s", path, action); + return GIT_ENOTFOUND; + + case EINVAL: + case ENAMETOOLONG: + git_error_set(GIT_ERROR_OS, "invalid path for filesystem '%s'", path); + return GIT_EINVALIDSPEC; + + case EEXIST: + git_error_set(GIT_ERROR_OS, "failed %s - '%s' already exists", action, path); + return GIT_EEXISTS; + + case EACCES: + git_error_set(GIT_ERROR_OS, "failed %s - '%s' is locked", action, path); + return GIT_ELOCKED; + + default: + git_error_set(GIT_ERROR_OS, "could not %s '%s'", action, path); + return -1; + } +} + +int git_fs_path_lstat(const char *path, struct stat *st) +{ + if (p_lstat(path, st) == 0) + return 0; + + return git_fs_path_set_error(errno, path, "stat"); +} + +static bool _check_dir_contents( + git_str *dir, + const char *sub, + bool (*predicate)(const char *)) +{ + bool result; + size_t dir_size = git_str_len(dir); + size_t sub_size = strlen(sub); + size_t alloc_size; + + /* leave base valid even if we could not make space for subdir */ + if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, dir_size, sub_size) || + GIT_ADD_SIZET_OVERFLOW(&alloc_size, alloc_size, 2) || + git_str_try_grow(dir, alloc_size, false) < 0) + return false; + + /* save excursion */ + if (git_str_joinpath(dir, dir->ptr, sub) < 0) + return false; + + result = predicate(dir->ptr); + + /* restore path */ + git_str_truncate(dir, dir_size); + return result; +} + +bool git_fs_path_contains(git_str *dir, const char *item) +{ + return _check_dir_contents(dir, item, &git_fs_path_exists); +} + +bool git_fs_path_contains_dir(git_str *base, const char *subdir) +{ + return _check_dir_contents(base, subdir, &git_fs_path_isdir); +} + +bool git_fs_path_contains_file(git_str *base, const char *file) +{ + return _check_dir_contents(base, file, &git_fs_path_isfile); +} + +int git_fs_path_find_dir(git_str *dir) +{ + int error = 0; + char buf[GIT_PATH_MAX]; + + if (p_realpath(dir->ptr, buf) != NULL) + error = git_str_sets(dir, buf); + + /* call dirname if this is not a directory */ + if (!error) /* && git_fs_path_isdir(dir->ptr) == false) */ + error = (git_fs_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0; + + if (!error) + error = git_fs_path_to_dir(dir); + + return error; +} + +int git_fs_path_resolve_relative(git_str *path, size_t ceiling) +{ + char *base, *to, *from, *next; + size_t len; + + GIT_ERROR_CHECK_ALLOC_STR(path); + + if (ceiling > path->size) + ceiling = path->size; + + /* recognize drive prefixes, etc. that should not be backed over */ + if (ceiling == 0) + ceiling = git_fs_path_root(path->ptr) + 1; + + /* recognize URL prefixes that should not be backed over */ + if (ceiling == 0) { + for (next = path->ptr; *next && git__isalpha(*next); ++next); + if (next[0] == ':' && next[1] == '/' && next[2] == '/') + ceiling = (next + 3) - path->ptr; + } + + base = to = from = path->ptr + ceiling; + + while (*from) { + for (next = from; *next && *next != '/'; ++next); + + len = next - from; + + if (len == 1 && from[0] == '.') + /* do nothing with singleton dot */; + + else if (len == 2 && from[0] == '.' && from[1] == '.') { + /* error out if trying to up one from a hard base */ + if (to == base && ceiling != 0) { + git_error_set(GIT_ERROR_INVALID, + "cannot strip root component off url"); + return -1; + } + + /* no more path segments to strip, + * use '../' as a new base path */ + if (to == base) { + if (*next == '/') + len++; + + if (to != from) + memmove(to, from, len); + + to += len; + /* this is now the base, can't back up from a + * relative prefix */ + base = to; + } else { + /* back up a path segment */ + while (to > base && to[-1] == '/') to--; + while (to > base && to[-1] != '/') to--; + } + } else { + if (*next == '/' && *from != '/') + len++; + + if (to != from) + memmove(to, from, len); + + to += len; + } + + from += len; + + while (*from == '/') from++; + } + + *to = '\0'; + + path->size = to - path->ptr; + + return 0; +} + +int git_fs_path_apply_relative(git_str *target, const char *relpath) +{ + return git_str_joinpath(target, git_str_cstr(target), relpath) || + git_fs_path_resolve_relative(target, 0); +} + +int git_fs_path_cmp( + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2, + int (*compare)(const char *, const char *, size_t)) +{ + unsigned char c1, c2; + size_t len = len1 < len2 ? len1 : len2; + int cmp; + + cmp = compare(name1, name2, len); + if (cmp) + return cmp; + + c1 = name1[len]; + c2 = name2[len]; + + if (c1 == '\0' && isdir1) + c1 = '/'; + + if (c2 == '\0' && isdir2) + c2 = '/'; + + return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; +} + +size_t git_fs_path_common_dirlen(const char *one, const char *two) +{ + const char *p, *q, *dirsep = NULL; + + for (p = one, q = two; *p && *q; p++, q++) { + if (*p == '/' && *q == '/') + dirsep = p; + else if (*p != *q) + break; + } + + return dirsep ? (dirsep - one) + 1 : 0; +} + +int git_fs_path_make_relative(git_str *path, const char *parent) +{ + const char *p, *q, *p_dirsep, *q_dirsep; + size_t plen = path->size, newlen, alloclen, depth = 1, i, offset; + + for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) { + if (*p == '/' && *q == '/') { + p_dirsep = p; + q_dirsep = q; + } + else if (*p != *q) + break; + } + + /* need at least 1 common path segment */ + if ((p_dirsep == path->ptr || q_dirsep == parent) && + (*p_dirsep != '/' || *q_dirsep != '/')) { + git_error_set(GIT_ERROR_INVALID, + "%s is not a parent of %s", parent, path->ptr); + return GIT_ENOTFOUND; + } + + if (*p == '/' && !*q) + p++; + else if (!*p && *q == '/') + q++; + else if (!*p && !*q) + return git_str_clear(path), 0; + else { + p = p_dirsep + 1; + q = q_dirsep + 1; + } + + plen -= (p - path->ptr); + + if (!*q) + return git_str_set(path, p, plen); + + for (; (q = strchr(q, '/')) && *(q + 1); q++) + depth++; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3); + GIT_ERROR_CHECK_ALLOC_ADD(&newlen, newlen, plen); + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, newlen, 1); + + /* save the offset as we might realllocate the pointer */ + offset = p - path->ptr; + if (git_str_try_grow(path, alloclen, 1) < 0) + return -1; + p = path->ptr + offset; + + memmove(path->ptr + (depth * 3), p, plen + 1); + + for (i = 0; i < depth; i++) + memcpy(path->ptr + (i * 3), "../", 3); + + path->size = newlen; + return 0; +} + +bool git_fs_path_has_non_ascii(const char *path, size_t pathlen) +{ + const uint8_t *scan = (const uint8_t *)path, *end; + + for (end = scan + pathlen; scan < end; ++scan) + if (*scan & 0x80) + return true; + + return false; +} + +#ifdef GIT_USE_ICONV + +int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic) +{ + git_str_init(&ic->buf, 0); + ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING); + return 0; +} + +void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic) +{ + if (ic) { + if (ic->map != (iconv_t)-1) + iconv_close(ic->map); + git_str_dispose(&ic->buf); + } +} + +int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen) +{ + char *nfd = (char*)*in, *nfc; + size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, alloclen, rv; + int retry = 1; + + if (!ic || ic->map == (iconv_t)-1 || + !git_fs_path_has_non_ascii(*in, *inlen)) + return 0; + + git_str_clear(&ic->buf); + + while (1) { + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, wantlen, 1); + if (git_str_grow(&ic->buf, alloclen) < 0) + return -1; + + nfc = ic->buf.ptr + ic->buf.size; + nfclen = ic->buf.asize - ic->buf.size; + + rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen); + + ic->buf.size = (nfc - ic->buf.ptr); + + if (rv != (size_t)-1) + break; + + /* if we cannot convert the data (probably because iconv thinks + * it is not valid UTF-8 source data), then use original data + */ + if (errno != E2BIG) + return 0; + + /* make space for 2x the remaining data to be converted + * (with per retry overhead to avoid infinite loops) + */ + wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4); + + if (retry++ > 4) + goto fail; + } + + ic->buf.ptr[ic->buf.size] = '\0'; + + *in = ic->buf.ptr; + *inlen = ic->buf.size; + + return 0; + +fail: + git_error_set(GIT_ERROR_OS, "unable to convert unicode path data"); + return -1; +} + +static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; +static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; + +/* Check if the platform is decomposing unicode data for us. We will + * emulate core Git and prefer to use precomposed unicode data internally + * on these platforms, composing the decomposed unicode on the fly. + * + * This mainly happens on the Mac where HDFS stores filenames as + * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will + * return decomposed unicode from readdir() even when the actual + * filesystem is storing precomposed unicode. + */ +bool git_fs_path_does_decompose_unicode(const char *root) +{ + git_str nfc_path = GIT_STR_INIT; + git_str nfd_path = GIT_STR_INIT; + int fd; + bool found_decomposed = false; + size_t orig_len; + const char *trailer; + + /* Create a file using a precomposed path and then try to find it + * using the decomposed name. If the lookup fails, then we will mark + * that we should precompose unicode for this repository. + */ + if (git_str_joinpath(&nfc_path, root, nfc_file) < 0) + goto done; + + /* record original path length before trailer */ + orig_len = nfc_path.size; + + if ((fd = git_futils_mktmp(&nfc_path, nfc_path.ptr, 0666)) < 0) + goto done; + p_close(fd); + + trailer = nfc_path.ptr + orig_len; + + /* try to look up as NFD path */ + if (git_str_joinpath(&nfd_path, root, nfd_file) < 0 || + git_str_puts(&nfd_path, trailer) < 0) + goto done; + + found_decomposed = git_fs_path_exists(nfd_path.ptr); + + /* remove temporary file (using original precomposed path) */ + (void)p_unlink(nfc_path.ptr); + +done: + git_str_dispose(&nfc_path); + git_str_dispose(&nfd_path); + return found_decomposed; +} + +#else + +bool git_fs_path_does_decompose_unicode(const char *root) +{ + GIT_UNUSED(root); + return false; +} + +#endif + +#if defined(__sun) || defined(__GNU__) +typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1]; +#else +typedef struct dirent path_dirent_data; +#endif + +int git_fs_path_direach( + git_str *path, + uint32_t flags, + int (*fn)(void *, git_str *), + void *arg) +{ + int error = 0; + ssize_t wd_len; + DIR *dir; + struct dirent *de; + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT; +#endif + + GIT_UNUSED(flags); + + if (git_fs_path_to_dir(path) < 0) + return -1; + + wd_len = git_str_len(path); + + if ((dir = opendir(path->ptr)) == NULL) { + git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path->ptr); + if (errno == ENOENT) + return GIT_ENOTFOUND; + + return -1; + } + +#ifdef GIT_USE_ICONV + if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0) + (void)git_fs_path_iconv_init_precompose(&ic); +#endif + + while ((de = readdir(dir)) != NULL) { + const char *de_path = de->d_name; + size_t de_len = strlen(de_path); + + if (git_fs_path_is_dot_or_dotdot(de_path)) + continue; + +#ifdef GIT_USE_ICONV + if ((error = git_fs_path_iconv(&ic, &de_path, &de_len)) < 0) + break; +#endif + + if ((error = git_str_put(path, de_path, de_len)) < 0) + break; + + git_error_clear(); + error = fn(arg, path); + + git_str_truncate(path, wd_len); /* restore path */ + + /* Only set our own error if the callback did not set one already */ + if (error != 0) { + if (!git_error_last()) + git_error_set_after_callback(error); + + break; + } + } + + closedir(dir); + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_clear(&ic); +#endif + + return error; +} + +#if defined(GIT_WIN32) && !defined(__MINGW32__) + +/* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7 + * and better. + */ +#ifndef FIND_FIRST_EX_LARGE_FETCH +# define FIND_FIRST_EX_LARGE_FETCH 2 +#endif + +int git_fs_path_diriter_init( + git_fs_path_diriter *diriter, + const char *path, + unsigned int flags) +{ + git_win32_path path_filter; + + static int is_win7_or_later = -1; + if (is_win7_or_later < 0) + is_win7_or_later = git_has_win32_version(6, 1, 0); + + GIT_ASSERT_ARG(diriter); + GIT_ASSERT_ARG(path); + + memset(diriter, 0, sizeof(git_fs_path_diriter)); + diriter->handle = INVALID_HANDLE_VALUE; + + if (git_str_puts(&diriter->path_utf8, path) < 0) + return -1; + + path_trim_slashes(&diriter->path_utf8); + + if (diriter->path_utf8.size == 0) { + git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path); + return -1; + } + + if ((diriter->parent_len = git_win32_path_from_utf8(diriter->path, diriter->path_utf8.ptr)) < 0 || + !git_win32__findfirstfile_filter(path_filter, diriter->path_utf8.ptr)) { + git_error_set(GIT_ERROR_OS, "could not parse the directory path '%s'", path); + return -1; + } + + diriter->handle = FindFirstFileExW( + path_filter, + is_win7_or_later ? FindExInfoBasic : FindExInfoStandard, + &diriter->current, + FindExSearchNameMatch, + NULL, + is_win7_or_later ? FIND_FIRST_EX_LARGE_FETCH : 0); + + if (diriter->handle == INVALID_HANDLE_VALUE) { + git_error_set(GIT_ERROR_OS, "could not open directory '%s'", path); + return -1; + } + + diriter->parent_utf8_len = diriter->path_utf8.size; + diriter->flags = flags; + return 0; +} + +static int diriter_update_paths(git_fs_path_diriter *diriter) +{ + size_t filename_len, path_len; + + filename_len = wcslen(diriter->current.cFileName); + + if (GIT_ADD_SIZET_OVERFLOW(&path_len, diriter->parent_len, filename_len) || + GIT_ADD_SIZET_OVERFLOW(&path_len, path_len, 2)) + return -1; + + if (path_len > GIT_WIN_PATH_UTF16) { + git_error_set(GIT_ERROR_FILESYSTEM, + "invalid path '%.*ls\\%ls' (path too long)", + diriter->parent_len, diriter->path, diriter->current.cFileName); + return -1; + } + + diriter->path[diriter->parent_len] = L'\\'; + memcpy(&diriter->path[diriter->parent_len+1], + diriter->current.cFileName, filename_len * sizeof(wchar_t)); + diriter->path[path_len-1] = L'\0'; + + git_str_truncate(&diriter->path_utf8, diriter->parent_utf8_len); + + if (diriter->parent_utf8_len > 0 && + diriter->path_utf8.ptr[diriter->parent_utf8_len-1] != '/') + git_str_putc(&diriter->path_utf8, '/'); + + git_str_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len); + + if (git_str_oom(&diriter->path_utf8)) + return -1; + + return 0; +} + +int git_fs_path_diriter_next(git_fs_path_diriter *diriter) +{ + bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT); + + do { + /* Our first time through, we already have the data from + * FindFirstFileW. Use it, otherwise get the next file. + */ + if (!diriter->needs_next) + diriter->needs_next = 1; + else if (!FindNextFileW(diriter->handle, &diriter->current)) + return GIT_ITEROVER; + } while (skip_dot && git_fs_path_is_dot_or_dotdotW(diriter->current.cFileName)); + + if (diriter_update_paths(diriter) < 0) + return -1; + + return 0; +} + +int git_fs_path_diriter_filename( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(out_len); + GIT_ASSERT_ARG(diriter); + GIT_ASSERT(diriter->path_utf8.size > diriter->parent_utf8_len); + + *out = &diriter->path_utf8.ptr[diriter->parent_utf8_len+1]; + *out_len = diriter->path_utf8.size - diriter->parent_utf8_len - 1; + return 0; +} + +int git_fs_path_diriter_fullpath( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(out_len); + GIT_ASSERT_ARG(diriter); + + *out = diriter->path_utf8.ptr; + *out_len = diriter->path_utf8.size; + return 0; +} + +int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diriter); + + return git_win32__file_attribute_to_stat(out, + (WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current, + diriter->path); +} + +void git_fs_path_diriter_free(git_fs_path_diriter *diriter) +{ + if (diriter == NULL) + return; + + git_str_dispose(&diriter->path_utf8); + + if (diriter->handle != INVALID_HANDLE_VALUE) { + FindClose(diriter->handle); + diriter->handle = INVALID_HANDLE_VALUE; + } +} + +#else + +int git_fs_path_diriter_init( + git_fs_path_diriter *diriter, + const char *path, + unsigned int flags) +{ + GIT_ASSERT_ARG(diriter); + GIT_ASSERT_ARG(path); + + memset(diriter, 0, sizeof(git_fs_path_diriter)); + + if (git_str_puts(&diriter->path, path) < 0) + return -1; + + path_trim_slashes(&diriter->path); + + if (diriter->path.size == 0) { + git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path); + return -1; + } + + if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) { + git_str_dispose(&diriter->path); + + git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path); + return -1; + } + +#ifdef GIT_USE_ICONV + if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0) + (void)git_fs_path_iconv_init_precompose(&diriter->ic); +#endif + + diriter->parent_len = diriter->path.size; + diriter->flags = flags; + + return 0; +} + +int git_fs_path_diriter_next(git_fs_path_diriter *diriter) +{ + struct dirent *de; + const char *filename; + size_t filename_len; + bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT); + int error = 0; + + GIT_ASSERT_ARG(diriter); + + errno = 0; + + do { + if ((de = readdir(diriter->dir)) == NULL) { + if (!errno) + return GIT_ITEROVER; + + git_error_set(GIT_ERROR_OS, + "could not read directory '%s'", diriter->path.ptr); + return -1; + } + } while (skip_dot && git_fs_path_is_dot_or_dotdot(de->d_name)); + + filename = de->d_name; + filename_len = strlen(filename); + +#ifdef GIT_USE_ICONV + if ((diriter->flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0 && + (error = git_fs_path_iconv(&diriter->ic, &filename, &filename_len)) < 0) + return error; +#endif + + git_str_truncate(&diriter->path, diriter->parent_len); + + if (diriter->parent_len > 0 && + diriter->path.ptr[diriter->parent_len-1] != '/') + git_str_putc(&diriter->path, '/'); + + git_str_put(&diriter->path, filename, filename_len); + + if (git_str_oom(&diriter->path)) + return -1; + + return error; +} + +int git_fs_path_diriter_filename( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(out_len); + GIT_ASSERT_ARG(diriter); + GIT_ASSERT(diriter->path.size > diriter->parent_len); + + *out = &diriter->path.ptr[diriter->parent_len+1]; + *out_len = diriter->path.size - diriter->parent_len - 1; + return 0; +} + +int git_fs_path_diriter_fullpath( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(out_len); + GIT_ASSERT_ARG(diriter); + + *out = diriter->path.ptr; + *out_len = diriter->path.size; + return 0; +} + +int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diriter); + + return git_fs_path_lstat(diriter->path.ptr, out); +} + +void git_fs_path_diriter_free(git_fs_path_diriter *diriter) +{ + if (diriter == NULL) + return; + + if (diriter->dir) { + closedir(diriter->dir); + diriter->dir = NULL; + } + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_clear(&diriter->ic); +#endif + + git_str_dispose(&diriter->path); +} + +#endif + +int git_fs_path_dirload( + git_vector *contents, + const char *path, + size_t prefix_len, + uint32_t flags) +{ + git_fs_path_diriter iter = GIT_FS_PATH_DIRITER_INIT; + const char *name; + size_t name_len; + char *dup; + int error; + + GIT_ASSERT_ARG(contents); + GIT_ASSERT_ARG(path); + + if ((error = git_fs_path_diriter_init(&iter, path, flags)) < 0) + return error; + + while ((error = git_fs_path_diriter_next(&iter)) == 0) { + if ((error = git_fs_path_diriter_fullpath(&name, &name_len, &iter)) < 0) + break; + + GIT_ASSERT(name_len > prefix_len); + + dup = git__strndup(name + prefix_len, name_len - prefix_len); + GIT_ERROR_CHECK_ALLOC(dup); + + if ((error = git_vector_insert(contents, dup)) < 0) + break; + } + + if (error == GIT_ITEROVER) + error = 0; + + git_fs_path_diriter_free(&iter); + return error; +} + +int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path) +{ + if (git_fs_path_is_local_file_url(url_or_path)) + return git_fs_path_fromurl(local_path_out, url_or_path); + else + return git_str_sets(local_path_out, url_or_path); +} + +/* Reject paths like AUX or COM1, or those versions that end in a dot or + * colon. ("AUX." or "AUX:") + */ +GIT_INLINE(bool) validate_dospath( + const char *component, + size_t len, + const char dospath[3], + bool trailing_num) +{ + size_t last = trailing_num ? 4 : 3; + + if (len < last || git__strncasecmp(component, dospath, 3) != 0) + return true; + + if (trailing_num && (component[3] < '1' || component[3] > '9')) + return true; + + return (len > last && + component[last] != '.' && + component[last] != ':'); +} + +GIT_INLINE(bool) validate_char(unsigned char c, unsigned int flags) +{ + if ((flags & GIT_FS_PATH_REJECT_BACKSLASH) && c == '\\') + return false; + + if ((flags & GIT_FS_PATH_REJECT_SLASH) && c == '/') + return false; + + if (flags & GIT_FS_PATH_REJECT_NT_CHARS) { + if (c < 32) + return false; + + switch (c) { + case '<': + case '>': + case ':': + case '"': + case '|': + case '?': + case '*': + return false; + } + } + + return true; +} + +/* + * We fundamentally don't like some paths when dealing with user-inputted + * strings (to avoid escaping a sandbox): we don't want dot or dot-dot + * anywhere, we want to avoid writing weird paths on Windows that can't + * be handled by tools that use the non-\\?\ APIs, we don't want slashes + * or double slashes at the end of paths that can make them ambiguous. + * + * For checkout, we don't want to recurse into ".git" either. + */ +static bool validate_component( + const char *component, + size_t len, + unsigned int flags) +{ + if (len == 0) + return !(flags & GIT_FS_PATH_REJECT_EMPTY_COMPONENT); + + if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) && + len == 1 && component[0] == '.') + return false; + + if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) && + len == 2 && component[0] == '.' && component[1] == '.') + return false; + + if ((flags & GIT_FS_PATH_REJECT_TRAILING_DOT) && + component[len - 1] == '.') + return false; + + if ((flags & GIT_FS_PATH_REJECT_TRAILING_SPACE) && + component[len - 1] == ' ') + return false; + + if ((flags & GIT_FS_PATH_REJECT_TRAILING_COLON) && + component[len - 1] == ':') + return false; + + if (flags & GIT_FS_PATH_REJECT_DOS_PATHS) { + if (!validate_dospath(component, len, "CON", false) || + !validate_dospath(component, len, "PRN", false) || + !validate_dospath(component, len, "AUX", false) || + !validate_dospath(component, len, "NUL", false) || + !validate_dospath(component, len, "COM", true) || + !validate_dospath(component, len, "LPT", true)) + return false; + } + + return true; +} + +#ifdef GIT_WIN32 +GIT_INLINE(bool) validate_length( + const char *path, + size_t len, + size_t utf8_char_len) +{ + GIT_UNUSED(path); + GIT_UNUSED(len); + + return (utf8_char_len <= MAX_PATH); +} +#endif + +bool git_fs_path_str_is_valid_ext( + const git_str *path, + unsigned int flags, + bool (*validate_char_cb)(char ch, void *payload), + bool (*validate_component_cb)(const char *component, size_t len, void *payload), + bool (*validate_length_cb)(const char *path, size_t len, size_t utf8_char_len), + void *payload) +{ + const char *start, *c; + size_t len = 0; + + if (!flags) + return true; + + for (start = c = path->ptr; *c && len < path->size; c++, len++) { + if (!validate_char(*c, flags)) + return false; + + if (validate_char_cb && !validate_char_cb(*c, payload)) + return false; + + if (*c != '/') + continue; + + if (!validate_component(start, (c - start), flags)) + return false; + + if (validate_component_cb && + !validate_component_cb(start, (c - start), payload)) + return false; + + start = c + 1; + } + + /* + * We want to support paths specified as either `const char *` + * or `git_str *`; we pass size as `SIZE_MAX` when we use a + * `const char *` to avoid a `strlen`. Ensure that we didn't + * have a NUL in the buffer if there was a non-SIZE_MAX length. + */ + if (path->size != SIZE_MAX && len != path->size) + return false; + + if (!validate_component(start, (c - start), flags)) + return false; + + if (validate_component_cb && + !validate_component_cb(start, (c - start), payload)) + return false; + +#ifdef GIT_WIN32 + if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS) != 0) { + size_t utf8_len = git_utf8_char_length(path->ptr, len); + + if (!validate_length(path->ptr, len, utf8_len)) + return false; + + if (validate_length_cb && + !validate_length_cb(path->ptr, len, utf8_len)) + return false; + } +#else + GIT_UNUSED(validate_length_cb); +#endif + + return true; +} + +int git_fs_path_validate_str_length_with_suffix( + git_str *path, + size_t suffix_len) +{ +#ifdef GIT_WIN32 + size_t utf8_len = git_utf8_char_length(path->ptr, path->size); + size_t total_len; + + if (GIT_ADD_SIZET_OVERFLOW(&total_len, utf8_len, suffix_len) || + total_len > MAX_PATH) { + + git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%.*s'", + (int)path->size, path->ptr); + return -1; + } +#else + GIT_UNUSED(path); + GIT_UNUSED(suffix_len); +#endif + + return 0; +} + +int git_fs_path_normalize_slashes(git_str *out, const char *path) +{ + int error; + char *p; + + if ((error = git_str_puts(out, path)) < 0) + return error; + + for (p = out->ptr; *p; p++) { + if (*p == '\\') + *p = '/'; + } + + return 0; +} + +bool git_fs_path_supports_symlinks(const char *dir) +{ + git_str path = GIT_STR_INIT; + bool supported = false; + struct stat st; + int fd; + + if ((fd = git_futils_mktmp(&path, dir, 0666)) < 0 || + p_close(fd) < 0 || + p_unlink(path.ptr) < 0 || + p_symlink("testing", path.ptr) < 0 || + p_lstat(path.ptr, &st) < 0) + goto done; + + supported = (S_ISLNK(st.st_mode) != 0); +done: + if (path.size) + (void)p_unlink(path.ptr); + git_str_dispose(&path); + return supported; +} + +int git_fs_path_validate_system_file_ownership(const char *path) +{ +#ifndef GIT_WIN32 + GIT_UNUSED(path); + return GIT_OK; +#else + git_win32_path buf; + PSID owner_sid; + PSECURITY_DESCRIPTOR descriptor = NULL; + HANDLE token; + TOKEN_USER *info = NULL; + DWORD err, len; + int ret; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + err = GetNamedSecurityInfoW(buf, SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION, + &owner_sid, NULL, NULL, NULL, &descriptor); + + if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { + ret = GIT_ENOTFOUND; + goto cleanup; + } + + if (err != ERROR_SUCCESS) { + git_error_set(GIT_ERROR_OS, "failed to get security information"); + ret = GIT_ERROR; + goto cleanup; + } + + if (!IsValidSid(owner_sid)) { + git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is unknown"); + ret = GIT_ERROR; + goto cleanup; + } + + if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) || + IsWellKnownSid(owner_sid, WinLocalSystemSid)) { + ret = GIT_OK; + goto cleanup; + } + + /* Obtain current user's SID */ + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) && + !GetTokenInformation(token, TokenUser, NULL, 0, &len)) { + info = git__malloc(len); + GIT_ERROR_CHECK_ALLOC(info); + if (!GetTokenInformation(token, TokenUser, info, len, &len)) { + git__free(info); + info = NULL; + } + } + + /* + * If the file is owned by the same account that is running the current + * process, it's okay to read from that file. + */ + if (info && EqualSid(owner_sid, info->User.Sid)) + ret = GIT_OK; + else { + git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is not valid"); + ret = GIT_ERROR; + } + git__free(info); + +cleanup: + if (descriptor) + LocalFree(descriptor); + + return ret; +#endif +} + +int git_fs_path_find_executable(git_str *fullpath, const char *executable) +{ +#ifdef GIT_WIN32 + git_win32_path fullpath_w, executable_w; + int error; + + if (git__utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0) + return -1; + + error = git_win32_path_find_executable(fullpath_w, executable_w); + + if (error == 0) + error = git_str_put_w(fullpath, fullpath_w, wcslen(fullpath_w)); + + return error; +#else + git_str path = GIT_STR_INIT; + const char *current_dir, *term; + bool found = false; + + if (git__getenv(&path, "PATH") < 0) + return -1; + + current_dir = path.ptr; + + while (*current_dir) { + if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR))) + term = strchr(current_dir, '\0'); + + git_str_clear(fullpath); + if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 || + git_str_putc(fullpath, '/') < 0 || + git_str_puts(fullpath, executable) < 0) + return -1; + + if (git_fs_path_isfile(fullpath->ptr)) { + found = true; + break; + } + + current_dir = term; + + while (*current_dir == GIT_PATH_LIST_SEPARATOR) + current_dir++; + } + + git_str_dispose(&path); + + if (found) + return 0; + + git_str_clear(fullpath); + return GIT_ENOTFOUND; +#endif +} diff --git a/src/libgit2/fs_path.h b/src/libgit2/fs_path.h new file mode 100644 index 000000000..222c44abc --- /dev/null +++ b/src/libgit2/fs_path.h @@ -0,0 +1,752 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_fs_path_h__ +#define INCLUDE_fs_path_h__ + +#include "common.h" + +#include "posix.h" +#include "str.h" +#include "vector.h" +#include "utf8.h" + +/** + * Path manipulation utils + * + * These are path utilities that munge paths without actually + * looking at the real filesystem. + */ + +/* + * The dirname() function shall take a pointer to a character string + * that contains a pathname, and return a pointer to a string that is a + * pathname of the parent directory of that file. Trailing '/' characters + * in the path are not counted as part of the path. + * + * If path does not contain a '/', then dirname() shall return a pointer to + * the string ".". If path is a null pointer or points to an empty string, + * dirname() shall return a pointer to the string "." . + * + * The `git_fs_path_dirname` implementation is thread safe. The returned + * string must be manually free'd. + * + * The `git_fs_path_dirname_r` implementation writes the dirname to a `git_str` + * if the buffer pointer is not NULL. + * It returns an error code < 0 if there is an allocation error, otherwise + * the length of the dirname (which will be > 0). + */ +extern char *git_fs_path_dirname(const char *path); +extern int git_fs_path_dirname_r(git_str *buffer, const char *path); + +/* + * This function returns the basename of the file, which is the last + * part of its full name given by fname, with the drive letter and + * leading directories stripped off. For example, the basename of + * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo. + * + * Trailing slashes and backslashes are significant: the basename of + * c:/foo/bar/ is an empty string after the rightmost slash. + * + * The `git_fs_path_basename` implementation is thread safe. The returned + * string must be manually free'd. + * + * The `git_fs_path_basename_r` implementation writes the basename to a `git_str`. + * It returns an error code < 0 if there is an allocation error, otherwise + * the length of the basename (which will be >= 0). + */ +extern char *git_fs_path_basename(const char *path); +extern int git_fs_path_basename_r(git_str *buffer, const char *path); + +/* Return the offset of the start of the basename. Unlike the other + * basename functions, this returns 0 if the path is empty. + */ +extern size_t git_fs_path_basename_offset(git_str *buffer); + +/** + * Find offset to root of path if path has one. + * + * This will return a number >= 0 which is the offset to the start of the + * path, if the path is rooted (i.e. "/rooted/path" returns 0 and + * "c:/windows/rooted/path" returns 2). If the path is not rooted, this + * returns -1. + */ +extern int git_fs_path_root(const char *path); + +/** + * Ensure path has a trailing '/'. + */ +extern int git_fs_path_to_dir(git_str *path); + +/** + * Ensure string has a trailing '/' if there is space for it. + */ +extern void git_fs_path_string_to_dir(char *path, size_t size); + +/** + * Taken from git.git; returns nonzero if the given path is "." or "..". + */ +GIT_INLINE(int) git_fs_path_is_dot_or_dotdot(const char *name) +{ + return (name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0'))); +} + +#ifdef GIT_WIN32 +GIT_INLINE(int) git_fs_path_is_dot_or_dotdotW(const wchar_t *name) +{ + return (name[0] == L'.' && + (name[1] == L'\0' || + (name[1] == L'.' && name[2] == L'\0'))); +} + +#define git_fs_path_is_absolute(p) \ + (git__isalpha((p)[0]) && (p)[1] == ':' && ((p)[2] == '\\' || (p)[2] == '/')) + +#define git_fs_path_is_dirsep(p) \ + ((p) == '/' || (p) == '\\') + +/** + * Convert backslashes in path to forward slashes. + */ +GIT_INLINE(void) git_fs_path_mkposix(char *path) +{ + while (*path) { + if (*path == '\\') + *path = '/'; + + path++; + } +} +#else +# define git_fs_path_mkposix(p) /* blank */ + +#define git_fs_path_is_absolute(p) \ + ((p)[0] == '/') + +#define git_fs_path_is_dirsep(p) \ + ((p) == '/') + +#endif + +/** + * Check if string is a relative path (i.e. starts with "./" or "../") + */ +GIT_INLINE(int) git_fs_path_is_relative(const char *p) +{ + return (p[0] == '.' && (p[1] == '/' || (p[1] == '.' && p[2] == '/'))); +} + +/** + * Check if string is at end of path segment (i.e. looking at '/' or '\0') + */ +GIT_INLINE(int) git_fs_path_at_end_of_segment(const char *p) +{ + return !*p || *p == '/'; +} + +extern int git__percent_decode(git_str *decoded_out, const char *input); + +/** + * Extract path from file:// URL. + */ +extern int git_fs_path_fromurl(git_str *local_path_out, const char *file_url); + + +/** + * Path filesystem utils + * + * These are path utilities that actually access the filesystem. + */ + +/** + * Check if a file exists and can be accessed. + * @return true or false + */ +extern bool git_fs_path_exists(const char *path); + +/** + * Check if the given path points to a directory. + * @return true or false + */ +extern bool git_fs_path_isdir(const char *path); + +/** + * Check if the given path points to a regular file. + * @return true or false + */ +extern bool git_fs_path_isfile(const char *path); + +/** + * Check if the given path points to a symbolic link. + * @return true or false + */ +extern bool git_fs_path_islink(const char *path); + +/** + * Check if the given path is a directory, and is empty. + */ +extern bool git_fs_path_is_empty_dir(const char *path); + +/** + * Stat a file and/or link and set error if needed. + */ +extern int git_fs_path_lstat(const char *path, struct stat *st); + +/** + * Check if the parent directory contains the item. + * + * @param dir Directory to check. + * @param item Item that might be in the directory. + * @return 0 if item exists in directory, <0 otherwise. + */ +extern bool git_fs_path_contains(git_str *dir, const char *item); + +/** + * Check if the given path contains the given subdirectory. + * + * @param parent Directory path that might contain subdir + * @param subdir Subdirectory name to look for in parent + * @return true if subdirectory exists, false otherwise. + */ +extern bool git_fs_path_contains_dir(git_str *parent, const char *subdir); + +/** + * Determine the common directory length between two paths, including + * the final path separator. For example, given paths 'a/b/c/1.txt + * and 'a/b/c/d/2.txt', the common directory is 'a/b/c/', and this + * will return the length of the string 'a/b/c/', which is 6. + * + * @param one The first path + * @param two The second path + * @return The length of the common directory + */ +extern size_t git_fs_path_common_dirlen(const char *one, const char *two); + +/** + * Make the path relative to the given parent path. + * + * @param path The path to make relative + * @param parent The parent path to make path relative to + * @return 0 if path was made relative, GIT_ENOTFOUND + * if there was not common root between the paths, + * or <0. + */ +extern int git_fs_path_make_relative(git_str *path, const char *parent); + +/** + * Check if the given path contains the given file. + * + * @param dir Directory path that might contain file + * @param file File name to look for in parent + * @return true if file exists, false otherwise. + */ +extern bool git_fs_path_contains_file(git_str *dir, const char *file); + +/** + * Prepend base to unrooted path or just copy path over. + * + * This will optionally return the index into the path where the "root" + * is, either the end of the base directory prefix or the path root. + */ +extern int git_fs_path_join_unrooted( + git_str *path_out, const char *path, const char *base, ssize_t *root_at); + +/** + * Removes multiple occurrences of '/' in a row, squashing them into a + * single '/'. + */ +extern void git_fs_path_squash_slashes(git_str *path); + +/** + * Clean up path, prepending base if it is not already rooted. + */ +extern int git_fs_path_prettify(git_str *path_out, const char *path, const char *base); + +/** + * Clean up path, prepending base if it is not already rooted and + * appending a slash. + */ +extern int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base); + +/** + * Get a directory from a path. + * + * If path is a directory, this acts like `git_fs_path_prettify_dir` + * (cleaning up path and appending a '/'). If path is a normal file, + * this prettifies it, then removed the filename a la dirname and + * appends the trailing '/'. If the path does not exist, it is + * treated like a regular filename. + */ +extern int git_fs_path_find_dir(git_str *dir); + +/** + * Resolve relative references within a path. + * + * This eliminates "./" and "../" relative references inside a path, + * as well as condensing multiple slashes into single ones. It will + * not touch the path before the "ceiling" length. + * + * Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL + * prefix and not touch that part of the path. + */ +extern int git_fs_path_resolve_relative(git_str *path, size_t ceiling); + +/** + * Apply a relative path to base path. + * + * Note that the base path could be a filename or a URL and this + * should still work. The relative path is walked segment by segment + * with three rules: series of slashes will be condensed to a single + * slash, "." will be eaten with no change, and ".." will remove a + * segment from the base path. + */ +extern int git_fs_path_apply_relative(git_str *target, const char *relpath); + +enum { + GIT_FS_PATH_DIR_IGNORE_CASE = (1u << 0), + GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1), + GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT = (1u << 2), +}; + +/** + * Walk each directory entry, except '.' and '..', calling fn(state). + * + * @param pathbuf Buffer the function reads the initial directory + * path from, and updates with each successive entry's name. + * @param flags Combination of GIT_FS_PATH_DIR flags. + * @param callback Callback for each entry. Passed the `payload` and each + * successive path inside the directory as a full path. This may + * safely append text to the pathbuf if needed. Return non-zero to + * cancel iteration (and return value will be propagated back). + * @param payload Passed to callback as first argument. + * @return 0 on success or error code from OS error or from callback + */ +extern int git_fs_path_direach( + git_str *pathbuf, + uint32_t flags, + int (*callback)(void *payload, git_str *path), + void *payload); + +/** + * Sort function to order two paths + */ +extern int git_fs_path_cmp( + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2, + int (*compare)(const char *, const char *, size_t)); + +/** + * Invoke callback up path directory by directory until the ceiling is + * reached (inclusive of a final call at the root_path). + * + * Returning anything other than 0 from the callback function + * will stop the iteration and propagate the error to the caller. + * + * @param pathbuf Buffer the function reads the directory from and + * and updates with each successive name. + * @param ceiling Prefix of path at which to stop walking up. If NULL, + * this will walk all the way up to the root. If not a prefix of + * pathbuf, the callback will be invoked a single time on the + * original input path. + * @param callback Function to invoke on each path. Passed the `payload` + * and the buffer containing the current path. The path should not + * be modified in any way. Return non-zero to stop iteration. + * @param payload Passed to fn as the first ath. + */ +extern int git_fs_path_walk_up( + git_str *pathbuf, + const char *ceiling, + int (*callback)(void *payload, const char *path), + void *payload); + + +enum { + GIT_FS_PATH_NOTEQUAL = 0, + GIT_FS_PATH_EQUAL = 1, + GIT_FS_PATH_PREFIX = 2 +}; + +/* + * Determines if a path is equal to or potentially a child of another. + * @param parent The possible parent + * @param child The possible child + */ +GIT_INLINE(int) git_fs_path_equal_or_prefixed( + const char *parent, + const char *child, + ssize_t *prefixlen) +{ + const char *p = parent, *c = child; + int lastslash = 0; + + while (*p && *c) { + lastslash = (*p == '/'); + + if (*p++ != *c++) + return GIT_FS_PATH_NOTEQUAL; + } + + if (*p != '\0') + return GIT_FS_PATH_NOTEQUAL; + + if (*c == '\0') { + if (prefixlen) + *prefixlen = p - parent; + + return GIT_FS_PATH_EQUAL; + } + + if (*c == '/' || lastslash) { + if (prefixlen) + *prefixlen = (p - parent) - lastslash; + + return GIT_FS_PATH_PREFIX; + } + + return GIT_FS_PATH_NOTEQUAL; +} + +/* translate errno to libgit2 error code and set error message */ +extern int git_fs_path_set_error( + int errno_value, const char *path, const char *action); + +/* check if non-ascii characters are present in filename */ +extern bool git_fs_path_has_non_ascii(const char *path, size_t pathlen); + +#define GIT_PATH_REPO_ENCODING "UTF-8" + +#ifdef __APPLE__ +#define GIT_PATH_NATIVE_ENCODING "UTF-8-MAC" +#else +#define GIT_PATH_NATIVE_ENCODING "UTF-8" +#endif + +#ifdef GIT_USE_ICONV + +#include + +typedef struct { + iconv_t map; + git_str buf; +} git_fs_path_iconv_t; + +#define GIT_PATH_ICONV_INIT { (iconv_t)-1, GIT_STR_INIT } + +/* Init iconv data for converting decomposed UTF-8 to precomposed */ +extern int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic); + +/* Clear allocated iconv data */ +extern void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic); + +/* + * Rewrite `in` buffer using iconv map if necessary, replacing `in` + * pointer internal iconv buffer if rewrite happened. The `in` pointer + * will be left unchanged if no rewrite was needed. + */ +extern int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen); + +#endif /* GIT_USE_ICONV */ + +extern bool git_fs_path_does_decompose_unicode(const char *root); + + +typedef struct git_fs_path_diriter git_fs_path_diriter; + +#if defined(GIT_WIN32) && !defined(__MINGW32__) + +struct git_fs_path_diriter +{ + git_win32_path path; + size_t parent_len; + + git_str path_utf8; + size_t parent_utf8_len; + + HANDLE handle; + + unsigned int flags; + + WIN32_FIND_DATAW current; + unsigned int needs_next; +}; + +#define GIT_FS_PATH_DIRITER_INIT { {0}, 0, GIT_STR_INIT, 0, INVALID_HANDLE_VALUE } + +#else + +struct git_fs_path_diriter +{ + git_str path; + size_t parent_len; + + unsigned int flags; + + DIR *dir; + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_t ic; +#endif +}; + +#define GIT_FS_PATH_DIRITER_INIT { GIT_STR_INIT } + +#endif + +/** + * Initialize a directory iterator. + * + * @param diriter Pointer to a diriter structure that will be setup. + * @param path The path that will be iterated over + * @param flags Directory reader flags + * @return 0 or an error code + */ +extern int git_fs_path_diriter_init( + git_fs_path_diriter *diriter, + const char *path, + unsigned int flags); + +/** + * Advance the directory iterator. Will return GIT_ITEROVER when + * the iteration has completed successfully. + * + * @param diriter The directory iterator + * @return 0, GIT_ITEROVER, or an error code + */ +extern int git_fs_path_diriter_next(git_fs_path_diriter *diriter); + +/** + * Returns the file name of the current item in the iterator. + * + * @param out Pointer to store the path in + * @param out_len Pointer to store the length of the path in + * @param diriter The directory iterator + * @return 0 or an error code + */ +extern int git_fs_path_diriter_filename( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter); + +/** + * Returns the full path of the current item in the iterator; that + * is the current filename plus the path of the directory that the + * iterator was constructed with. + * + * @param out Pointer to store the path in + * @param out_len Pointer to store the length of the path in + * @param diriter The directory iterator + * @return 0 or an error code + */ +extern int git_fs_path_diriter_fullpath( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter); + +/** + * Performs an `lstat` on the current item in the iterator. + * + * @param out Pointer to store the stat data in + * @param diriter The directory iterator + * @return 0 or an error code + */ +extern int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter); + +/** + * Closes the directory iterator. + * + * @param diriter The directory iterator + */ +extern void git_fs_path_diriter_free(git_fs_path_diriter *diriter); + +/** + * Load all directory entries (except '.' and '..') into a vector. + * + * For cases where `git_fs_path_direach()` is not appropriate, this + * allows you to load the filenames in a directory into a vector + * of strings. That vector can then be sorted, iterated, or whatever. + * Remember to free alloc of the allocated strings when you are done. + * + * @param contents Vector to fill with directory entry names. + * @param path The directory to read from. + * @param prefix_len When inserting entries, the trailing part of path + * will be prefixed after this length. I.e. given path "/a/b" and + * prefix_len 3, the entries will look like "b/e1", "b/e2", etc. + * @param flags Combination of GIT_FS_PATH_DIR flags. + */ +extern int git_fs_path_dirload( + git_vector *contents, + const char *path, + size_t prefix_len, + uint32_t flags); + + +/* Used for paths to repositories on the filesystem */ +extern bool git_fs_path_is_local_file_url(const char *file_url); +extern int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path); + +/* Flags to determine path validity in `git_fs_path_isvalid` */ +#define GIT_FS_PATH_REJECT_EMPTY_COMPONENT (1 << 0) +#define GIT_FS_PATH_REJECT_TRAVERSAL (1 << 1) +#define GIT_FS_PATH_REJECT_SLASH (1 << 2) +#define GIT_FS_PATH_REJECT_BACKSLASH (1 << 3) +#define GIT_FS_PATH_REJECT_TRAILING_DOT (1 << 4) +#define GIT_FS_PATH_REJECT_TRAILING_SPACE (1 << 5) +#define GIT_FS_PATH_REJECT_TRAILING_COLON (1 << 6) +#define GIT_FS_PATH_REJECT_DOS_PATHS (1 << 7) +#define GIT_FS_PATH_REJECT_NT_CHARS (1 << 8) +#define GIT_FS_PATH_REJECT_LONG_PATHS (1 << 9) + +#define GIT_FS_PATH_REJECT_MAX (1 << 9) + +/* Default path safety for writing files to disk: since we use the + * Win32 "File Namespace" APIs ("\\?\") we need to protect from + * paths that the normal Win32 APIs would not write. + */ +#ifdef GIT_WIN32 +# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \ + GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \ + GIT_FS_PATH_REJECT_TRAVERSAL | \ + GIT_FS_PATH_REJECT_BACKSLASH | \ + GIT_FS_PATH_REJECT_TRAILING_DOT | \ + GIT_FS_PATH_REJECT_TRAILING_SPACE | \ + GIT_FS_PATH_REJECT_TRAILING_COLON | \ + GIT_FS_PATH_REJECT_DOS_PATHS | \ + GIT_FS_PATH_REJECT_NT_CHARS +#else +# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \ + GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \ + GIT_FS_PATH_REJECT_TRAVERSAL +#endif + +/** + * Validate a filesystem path; with custom callbacks per-character and + * per-path component. + */ +extern bool git_fs_path_str_is_valid_ext( + const git_str *path, + unsigned int flags, + bool (*validate_char_cb)(char ch, void *payload), + bool (*validate_component_cb)(const char *component, size_t len, void *payload), + bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len), + void *payload); + +GIT_INLINE(bool) git_fs_path_is_valid_ext( + const char *path, + unsigned int flags, + bool (*validate_char_cb)(char ch, void *payload), + bool (*validate_component_cb)(const char *component, size_t len, void *payload), + bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len), + void *payload) +{ + const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); + return git_fs_path_str_is_valid_ext( + &str, + flags, + validate_char_cb, + validate_component_cb, + validate_length_cb, + payload); +} + +/** + * Validate a filesystem path. This ensures that the given path is legal + * and does not contain any "unsafe" components like path traversal ('.' + * or '..'), characters that are inappropriate for lesser filesystems + * (trailing ' ' or ':' characters), or filenames ("component names") + * that are not supported ('AUX', 'COM1"). + */ +GIT_INLINE(bool) git_fs_path_is_valid( + const char *path, + unsigned int flags) +{ + const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); + return git_fs_path_str_is_valid_ext(&str, flags, NULL, NULL, NULL, NULL); +} + +/** Validate a filesystem path in a `git_str`. */ +GIT_INLINE(bool) git_fs_path_str_is_valid( + const git_str *path, + unsigned int flags) +{ + return git_fs_path_str_is_valid_ext(path, flags, NULL, NULL, NULL, NULL); +} + +extern int git_fs_path_validate_str_length_with_suffix( + git_str *path, + size_t suffix_len); + +/** + * Validate an on-disk path, taking into account that it will have a + * suffix appended (eg, `.lock`). + */ +GIT_INLINE(int) git_fs_path_validate_filesystem_with_suffix( + const char *path, + size_t path_len, + size_t suffix_len) +{ +#ifdef GIT_WIN32 + size_t path_chars, total_chars; + + path_chars = git_utf8_char_length(path, path_len); + + if (GIT_ADD_SIZET_OVERFLOW(&total_chars, path_chars, suffix_len) || + total_chars > MAX_PATH) { + git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%s'", path); + return -1; + } + return 0; +#else + GIT_UNUSED(path); + GIT_UNUSED(path_len); + GIT_UNUSED(suffix_len); + return 0; +#endif +} + +/** + * Validate an path on the filesystem. This ensures that the given + * path is valid for the operating system/platform; for example, this + * will ensure that the given absolute path is smaller than MAX_PATH on + * Windows. + * + * For paths within the working directory, you should use ensure that + * `core.longpaths` is obeyed. Use `git_fs_path_validate_workdir`. + */ +GIT_INLINE(int) git_fs_path_validate_filesystem( + const char *path, + size_t path_len) +{ + return git_fs_path_validate_filesystem_with_suffix(path, path_len, 0); +} + +/** + * Convert any backslashes into slashes + */ +int git_fs_path_normalize_slashes(git_str *out, const char *path); + +bool git_fs_path_supports_symlinks(const char *dir); + +/** + * Validate a system file's ownership + * + * Verify that the file in question is owned by an administrator or system + * account, or at least by the current user. + * + * This function returns 0 if successful. If the file is not owned by any of + * these, or any other if there have been problems determining the file + * ownership, it returns -1. + */ +int git_fs_path_validate_system_file_ownership(const char *path); + +/** + * Search the current PATH for the given executable, returning the full + * path if it is found. + */ +int git_fs_path_find_executable(git_str *fullpath, const char *executable); + +#endif diff --git a/src/libgit2/futils.c b/src/libgit2/futils.c new file mode 100644 index 000000000..42c35955e --- /dev/null +++ b/src/libgit2/futils.c @@ -0,0 +1,1194 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "futils.h" + +#include "runtime.h" +#include "strmap.h" +#include "hash.h" +#include "rand.h" + +#include +#if GIT_WIN32 +#include "win32/findfile.h" +#endif + +#define GIT_FILEMODE_DEFAULT 0100666 + +int git_futils_mkpath2file(const char *file_path, const mode_t mode) +{ + return git_futils_mkdir( + file_path, mode, + GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); +} + +int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode) +{ + const int open_flags = O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC; + unsigned int tries = 32; + int fd; + + while (tries--) { + uint64_t rand = git_rand_next(); + + git_str_sets(path_out, filename); + git_str_puts(path_out, "_git2_"); + git_str_encode_hexstr(path_out, (void *)&rand, sizeof(uint64_t)); + + if (git_str_oom(path_out)) + return -1; + + /* Note that we open with O_CREAT | O_EXCL */ + if ((fd = p_open(path_out->ptr, open_flags, mode)) >= 0) + return fd; + } + + git_error_set(GIT_ERROR_OS, + "failed to create temporary file '%s'", path_out->ptr); + git_str_dispose(path_out); + return -1; +} + +int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode) +{ + int fd; + + if (git_futils_mkpath2file(path, dirmode) < 0) + return -1; + + fd = p_creat(path, mode); + if (fd < 0) { + git_error_set(GIT_ERROR_OS, "failed to create file '%s'", path); + return -1; + } + + return fd; +} + +int git_futils_creat_locked(const char *path, const mode_t mode) +{ + int fd = p_open(path, O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, + mode); + + if (fd < 0) { + int error = errno; + git_error_set(GIT_ERROR_OS, "failed to create locked file '%s'", path); + switch (error) { + case EEXIST: + return GIT_ELOCKED; + case ENOENT: + return GIT_ENOTFOUND; + default: + return -1; + } + } + + return fd; +} + +int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode) +{ + if (git_futils_mkpath2file(path, dirmode) < 0) + return -1; + + return git_futils_creat_locked(path, mode); +} + +int git_futils_open_ro(const char *path) +{ + int fd = p_open(path, O_RDONLY); + if (fd < 0) + return git_fs_path_set_error(errno, path, "open"); + return fd; +} + +int git_futils_truncate(const char *path, int mode) +{ + int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode); + if (fd < 0) + return git_fs_path_set_error(errno, path, "open"); + + close(fd); + return 0; +} + +int git_futils_filesize(uint64_t *out, git_file fd) +{ + struct stat sb; + + if (p_fstat(fd, &sb)) { + git_error_set(GIT_ERROR_OS, "failed to stat file descriptor"); + return -1; + } + + if (sb.st_size < 0) { + git_error_set(GIT_ERROR_INVALID, "invalid file size"); + return -1; + } + + *out = sb.st_size; + return 0; +} + +mode_t git_futils_canonical_mode(mode_t raw_mode) +{ + if (S_ISREG(raw_mode)) + return S_IFREG | GIT_PERMS_CANONICAL(raw_mode); + else if (S_ISLNK(raw_mode)) + return S_IFLNK; + else if (S_ISGITLINK(raw_mode)) + return S_IFGITLINK; + else if (S_ISDIR(raw_mode)) + return S_IFDIR; + else + return 0; +} + +int git_futils_readbuffer_fd(git_str *buf, git_file fd, size_t len) +{ + ssize_t read_size = 0; + size_t alloc_len; + + git_str_clear(buf); + + if (!git__is_ssizet(len)) { + git_error_set(GIT_ERROR_INVALID, "read too large"); + return -1; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1); + if (git_str_grow(buf, alloc_len) < 0) + return -1; + + /* p_read loops internally to read len bytes */ + read_size = p_read(fd, buf->ptr, len); + + if (read_size != (ssize_t)len) { + git_error_set(GIT_ERROR_OS, "failed to read descriptor"); + git_str_dispose(buf); + return -1; + } + + buf->ptr[read_size] = '\0'; + buf->size = read_size; + + return 0; +} + +int git_futils_readbuffer_updated( + git_str *out, + const char *path, + unsigned char checksum[GIT_HASH_SHA1_SIZE], + int *updated) +{ + int error; + git_file fd; + struct stat st; + git_str buf = GIT_STR_INIT; + unsigned char checksum_new[GIT_HASH_SHA1_SIZE]; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(path && *path); + + if (updated != NULL) + *updated = 0; + + if (p_stat(path, &st) < 0) + return git_fs_path_set_error(errno, path, "stat"); + + + if (S_ISDIR(st.st_mode)) { + git_error_set(GIT_ERROR_INVALID, "requested file is a directory"); + return GIT_ENOTFOUND; + } + + if (!git__is_sizet(st.st_size+1)) { + git_error_set(GIT_ERROR_OS, "invalid regular file stat for '%s'", path); + return -1; + } + + if ((fd = git_futils_open_ro(path)) < 0) + return fd; + + if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) { + p_close(fd); + return -1; + } + + p_close(fd); + + if (checksum) { + if ((error = git_hash_buf(checksum_new, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0) { + git_str_dispose(&buf); + return error; + } + + /* + * If we were given a checksum, we only want to use it if it's different + */ + if (!memcmp(checksum, checksum_new, GIT_HASH_SHA1_SIZE)) { + git_str_dispose(&buf); + if (updated) + *updated = 0; + + return 0; + } + + memcpy(checksum, checksum_new, GIT_HASH_SHA1_SIZE); + } + + /* + * If we're here, the file did change, or the user didn't have an old version + */ + if (updated != NULL) + *updated = 1; + + git_str_swap(out, &buf); + git_str_dispose(&buf); + + return 0; +} + +int git_futils_readbuffer(git_str *buf, const char *path) +{ + return git_futils_readbuffer_updated(buf, path, NULL, NULL); +} + +int git_futils_writebuffer( + const git_str *buf, const char *path, int flags, mode_t mode) +{ + int fd, do_fsync = 0, error = 0; + + if (!flags) + flags = O_CREAT | O_TRUNC | O_WRONLY; + + if ((flags & O_FSYNC) != 0) + do_fsync = 1; + + flags &= ~O_FSYNC; + + if (!mode) + mode = GIT_FILEMODE_DEFAULT; + + if ((fd = p_open(path, flags, mode)) < 0) { + git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path); + return fd; + } + + if ((error = p_write(fd, git_str_cstr(buf), git_str_len(buf))) < 0) { + git_error_set(GIT_ERROR_OS, "could not write to '%s'", path); + (void)p_close(fd); + return error; + } + + if (do_fsync && (error = p_fsync(fd)) < 0) { + git_error_set(GIT_ERROR_OS, "could not fsync '%s'", path); + p_close(fd); + return error; + } + + if ((error = p_close(fd)) < 0) { + git_error_set(GIT_ERROR_OS, "error while closing '%s'", path); + return error; + } + + if (do_fsync && (flags & O_CREAT)) + error = git_futils_fsync_parent(path); + + return error; +} + +int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) +{ + if (git_futils_mkpath2file(to, dirmode) < 0) + return -1; + + if (p_rename(from, to) < 0) { + git_error_set(GIT_ERROR_OS, "failed to rename '%s' to '%s'", from, to); + return -1; + } + + return 0; +} + +int git_futils_mmap_ro(git_map *out, git_file fd, off64_t begin, size_t len) +{ + return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin); +} + +int git_futils_mmap_ro_file(git_map *out, const char *path) +{ + git_file fd = git_futils_open_ro(path); + uint64_t len; + int result; + + if (fd < 0) + return fd; + + if ((result = git_futils_filesize(&len, fd)) < 0) + goto out; + + if (!git__is_sizet(len)) { + git_error_set(GIT_ERROR_OS, "file `%s` too large to mmap", path); + result = -1; + goto out; + } + + result = git_futils_mmap_ro(out, fd, 0, (size_t)len); +out: + p_close(fd); + return result; +} + +void git_futils_mmap_free(git_map *out) +{ + p_munmap(out); +} + +GIT_INLINE(int) mkdir_validate_dir( + const char *path, + struct stat *st, + mode_t mode, + uint32_t flags, + struct git_futils_mkdir_options *opts) +{ + /* with exclusive create, existing dir is an error */ + if ((flags & GIT_MKDIR_EXCL) != 0) { + git_error_set(GIT_ERROR_FILESYSTEM, + "failed to make directory '%s': directory exists", path); + return GIT_EEXISTS; + } + + if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) || + (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) { + if (p_unlink(path) < 0) { + git_error_set(GIT_ERROR_OS, "failed to remove %s '%s'", + S_ISLNK(st->st_mode) ? "symlink" : "file", path); + return GIT_EEXISTS; + } + + opts->perfdata.mkdir_calls++; + + if (p_mkdir(path, mode) < 0) { + git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path); + return GIT_EEXISTS; + } + } + + else if (S_ISLNK(st->st_mode)) { + /* Re-stat the target, make sure it's a directory */ + opts->perfdata.stat_calls++; + + if (p_stat(path, st) < 0) { + git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path); + return GIT_EEXISTS; + } + } + + else if (!S_ISDIR(st->st_mode)) { + git_error_set(GIT_ERROR_FILESYSTEM, + "failed to make directory '%s': directory exists", path); + return GIT_EEXISTS; + } + + return 0; +} + +GIT_INLINE(int) mkdir_validate_mode( + const char *path, + struct stat *st, + bool terminal_path, + mode_t mode, + uint32_t flags, + struct git_futils_mkdir_options *opts) +{ + if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) || + (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) { + + opts->perfdata.chmod_calls++; + + if (p_chmod(path, mode) < 0) { + git_error_set(GIT_ERROR_OS, "failed to set permissions on '%s'", path); + return -1; + } + } + + return 0; +} + +GIT_INLINE(int) mkdir_canonicalize( + git_str *path, + uint32_t flags) +{ + ssize_t root_len; + + if (path->size == 0) { + git_error_set(GIT_ERROR_OS, "attempt to create empty path"); + return -1; + } + + /* Trim trailing slashes (except the root) */ + if ((root_len = git_fs_path_root(path->ptr)) < 0) + root_len = 0; + else + root_len++; + + while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/') + path->ptr[--path->size] = '\0'; + + /* if we are not supposed to made the last element, truncate it */ + if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) { + git_fs_path_dirname_r(path, path->ptr); + flags |= GIT_MKDIR_SKIP_LAST; + } + if ((flags & GIT_MKDIR_SKIP_LAST) != 0) { + git_fs_path_dirname_r(path, path->ptr); + } + + /* We were either given the root path (or trimmed it to + * the root), we don't have anything to do. + */ + if (path->size <= (size_t)root_len) + git_str_clear(path); + + return 0; +} + +int git_futils_mkdir( + const char *path, + mode_t mode, + uint32_t flags) +{ + git_str make_path = GIT_STR_INIT, parent_path = GIT_STR_INIT; + const char *relative; + struct git_futils_mkdir_options opts = { 0 }; + struct stat st; + size_t depth = 0; + int len = 0, root_len, error; + + if ((error = git_str_puts(&make_path, path)) < 0 || + (error = mkdir_canonicalize(&make_path, flags)) < 0 || + (error = git_str_puts(&parent_path, make_path.ptr)) < 0 || + make_path.size == 0) + goto done; + + root_len = git_fs_path_root(make_path.ptr); + + /* find the first parent directory that exists. this will be used + * as the base to dirname_relative. + */ + for (relative = make_path.ptr; parent_path.size; ) { + error = p_lstat(parent_path.ptr, &st); + + if (error == 0) { + break; + } else if (errno != ENOENT) { + git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr); + error = -1; + goto done; + } + + depth++; + + /* examine the parent of the current path */ + if ((len = git_fs_path_dirname_r(&parent_path, parent_path.ptr)) < 0) { + error = len; + goto done; + } + + GIT_ASSERT(len); + + /* + * We've walked all the given path's parents and it's either relative + * (the parent is simply '.') or rooted (the length is less than or + * equal to length of the root path). The path may be less than the + * root path length on Windows, where `C:` == `C:/`. + */ + if ((len == 1 && parent_path.ptr[0] == '.') || + (len == 1 && parent_path.ptr[0] == '/') || + len <= root_len) { + relative = make_path.ptr; + break; + } + + relative = make_path.ptr + len + 1; + + /* not recursive? just make this directory relative to its parent. */ + if ((flags & GIT_MKDIR_PATH) == 0) + break; + } + + /* we found an item at the location we're trying to create, + * validate it. + */ + if (depth == 0) { + error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts); + + if (!error) + error = mkdir_validate_mode( + make_path.ptr, &st, true, mode, flags, &opts); + + goto done; + } + + /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when + * canonicalizing `make_path`. + */ + flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST); + + error = git_futils_mkdir_relative(relative, + parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts); + +done: + git_str_dispose(&make_path); + git_str_dispose(&parent_path); + return error; +} + +int git_futils_mkdir_r(const char *path, const mode_t mode) +{ + return git_futils_mkdir(path, mode, GIT_MKDIR_PATH); +} + +int git_futils_mkdir_relative( + const char *relative_path, + const char *base, + mode_t mode, + uint32_t flags, + struct git_futils_mkdir_options *opts) +{ + git_str make_path = GIT_STR_INIT; + ssize_t root = 0, min_root_len; + char lastch = '/', *tail; + struct stat st; + struct git_futils_mkdir_options empty_opts = {0}; + int error; + + if (!opts) + opts = &empty_opts; + + /* build path and find "root" where we should start calling mkdir */ + if (git_fs_path_join_unrooted(&make_path, relative_path, base, &root) < 0) + return -1; + + if ((error = mkdir_canonicalize(&make_path, flags)) < 0 || + make_path.size == 0) + goto done; + + /* if we are not supposed to make the whole path, reset root */ + if ((flags & GIT_MKDIR_PATH) == 0) + root = git_str_rfind(&make_path, '/'); + + /* advance root past drive name or network mount prefix */ + min_root_len = git_fs_path_root(make_path.ptr); + if (root < min_root_len) + root = min_root_len; + while (root >= 0 && make_path.ptr[root] == '/') + ++root; + + /* clip root to make_path length */ + if (root > (ssize_t)make_path.size) + root = (ssize_t)make_path.size; /* i.e. NUL byte of string */ + if (root < 0) + root = 0; + + /* walk down tail of path making each directory */ + for (tail = &make_path.ptr[root]; *tail; *tail = lastch) { + bool mkdir_attempted = false; + + /* advance tail to include next path component */ + while (*tail == '/') + tail++; + while (*tail && *tail != '/') + tail++; + + /* truncate path at next component */ + lastch = *tail; + *tail = '\0'; + st.st_mode = 0; + + if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr)) + continue; + + /* See what's going on with this path component */ + opts->perfdata.stat_calls++; + +retry_lstat: + if (p_lstat(make_path.ptr, &st) < 0) { + if (mkdir_attempted || errno != ENOENT) { + git_error_set(GIT_ERROR_OS, "cannot access component in path '%s'", make_path.ptr); + error = -1; + goto done; + } + + git_error_clear(); + opts->perfdata.mkdir_calls++; + mkdir_attempted = true; + if (p_mkdir(make_path.ptr, mode) < 0) { + if (errno == EEXIST) + goto retry_lstat; + git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", make_path.ptr); + error = -1; + goto done; + } + } else { + if ((error = mkdir_validate_dir( + make_path.ptr, &st, mode, flags, opts)) < 0) + goto done; + } + + /* chmod if requested and necessary */ + if ((error = mkdir_validate_mode( + make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0) + goto done; + + if (opts->dir_map && opts->pool) { + char *cache_path; + size_t alloc_size; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1); + cache_path = git_pool_malloc(opts->pool, alloc_size); + GIT_ERROR_CHECK_ALLOC(cache_path); + + memcpy(cache_path, make_path.ptr, make_path.size + 1); + + if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0) + goto done; + } + } + + error = 0; + + /* check that full path really is a directory if requested & needed */ + if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 && + lastch != '\0') { + opts->perfdata.stat_calls++; + + if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) { + git_error_set(GIT_ERROR_OS, "path is not a directory '%s'", + make_path.ptr); + error = GIT_ENOTFOUND; + } + } + +done: + git_str_dispose(&make_path); + return error; +} + +typedef struct { + const char *base; + size_t baselen; + uint32_t flags; + int depth; +} futils__rmdir_data; + +#define FUTILS_MAX_DEPTH 100 + +static int futils__error_cannot_rmdir(const char *path, const char *filemsg) +{ + if (filemsg) + git_error_set(GIT_ERROR_OS, "could not remove directory '%s': %s", + path, filemsg); + else + git_error_set(GIT_ERROR_OS, "could not remove directory '%s'", path); + + return -1; +} + +static int futils__rm_first_parent(git_str *path, const char *ceiling) +{ + int error = GIT_ENOTFOUND; + struct stat st; + + while (error == GIT_ENOTFOUND) { + git_str_rtruncate_at_char(path, '/'); + + if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0) + error = 0; + else if (p_lstat_posixly(path->ptr, &st) == 0) { + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) + error = p_unlink(path->ptr); + else if (!S_ISDIR(st.st_mode)) + error = -1; /* fail to remove non-regular file */ + } else if (errno != ENOTDIR) + error = -1; + } + + if (error) + futils__error_cannot_rmdir(path->ptr, "cannot remove parent"); + + return error; +} + +static int futils__rmdir_recurs_foreach(void *opaque, git_str *path) +{ + int error = 0; + futils__rmdir_data *data = opaque; + struct stat st; + + if (data->depth > FUTILS_MAX_DEPTH) + error = futils__error_cannot_rmdir( + path->ptr, "directory nesting too deep"); + + else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) { + if (errno == ENOENT) + error = 0; + else if (errno == ENOTDIR) { + /* asked to remove a/b/c/d/e and a/b is a normal file */ + if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0) + error = futils__rm_first_parent(path, data->base); + else + futils__error_cannot_rmdir( + path->ptr, "parent is not directory"); + } + else + error = git_fs_path_set_error(errno, path->ptr, "rmdir"); + } + + else if (S_ISDIR(st.st_mode)) { + data->depth++; + + error = git_fs_path_direach(path, 0, futils__rmdir_recurs_foreach, data); + + data->depth--; + + if (error < 0) + return error; + + if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0) + return error; + + if ((error = p_rmdir(path->ptr)) < 0) { + if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 && + (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY)) + error = 0; + else + error = git_fs_path_set_error(errno, path->ptr, "rmdir"); + } + } + + else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) { + if (p_unlink(path->ptr) < 0) + error = git_fs_path_set_error(errno, path->ptr, "remove"); + } + + else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0) + error = futils__error_cannot_rmdir(path->ptr, "still present"); + + return error; +} + +static int futils__rmdir_empty_parent(void *opaque, const char *path) +{ + futils__rmdir_data *data = opaque; + int error = 0; + + if (strlen(path) <= data->baselen) + error = GIT_ITEROVER; + + else if (p_rmdir(path) < 0) { + int en = errno; + + if (en == ENOENT || en == ENOTDIR) { + /* do nothing */ + } else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 && + en == EBUSY) { + error = git_fs_path_set_error(errno, path, "rmdir"); + } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) { + error = GIT_ITEROVER; + } else { + error = git_fs_path_set_error(errno, path, "rmdir"); + } + } + + return error; +} + +int git_futils_rmdir_r( + const char *path, const char *base, uint32_t flags) +{ + int error; + git_str fullpath = GIT_STR_INIT; + futils__rmdir_data data; + + /* build path and find "root" where we should start calling mkdir */ + if (git_fs_path_join_unrooted(&fullpath, path, base, NULL) < 0) + return -1; + + memset(&data, 0, sizeof(data)); + data.base = base ? base : ""; + data.baselen = base ? strlen(base) : 0; + data.flags = flags; + + error = futils__rmdir_recurs_foreach(&data, &fullpath); + + /* remove now-empty parents if requested */ + if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0) + error = git_fs_path_walk_up( + &fullpath, base, futils__rmdir_empty_parent, &data); + + if (error == GIT_ITEROVER) { + git_error_clear(); + error = 0; + } + + git_str_dispose(&fullpath); + + return error; +} + +int git_futils_fake_symlink(const char *target, const char *path) +{ + int retcode = GIT_ERROR; + int fd = git_futils_creat_withpath(path, 0755, 0644); + if (fd >= 0) { + retcode = p_write(fd, target, strlen(target)); + p_close(fd); + } + return retcode; +} + +static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done) +{ + int error = 0; + char buffer[FILEIO_BUFSIZE]; + ssize_t len = 0; + + while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0) + /* p_write() does not have the same semantics as write(). It loops + * internally and will return 0 when it has completed writing. + */ + error = p_write(ofd, buffer, len); + + if (len < 0) { + git_error_set(GIT_ERROR_OS, "read error while copying file"); + error = (int)len; + } + + if (error < 0) + git_error_set(GIT_ERROR_OS, "write error while copying file"); + + if (close_fd_when_done) { + p_close(ifd); + p_close(ofd); + } + + return error; +} + +int git_futils_cp(const char *from, const char *to, mode_t filemode) +{ + int ifd, ofd; + + if ((ifd = git_futils_open_ro(from)) < 0) + return ifd; + + if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) { + p_close(ifd); + return git_fs_path_set_error(errno, to, "open for writing"); + } + + return cp_by_fd(ifd, ofd, true); +} + +int git_futils_touch(const char *path, time_t *when) +{ + struct p_timeval times[2]; + int ret; + + times[0].tv_sec = times[1].tv_sec = when ? *when : time(NULL); + times[0].tv_usec = times[1].tv_usec = 0; + + ret = p_utimes(path, times); + + return (ret < 0) ? git_fs_path_set_error(errno, path, "touch") : 0; +} + +static int cp_link(const char *from, const char *to, size_t link_size) +{ + int error = 0; + ssize_t read_len; + char *link_data; + size_t alloc_size; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1); + link_data = git__malloc(alloc_size); + GIT_ERROR_CHECK_ALLOC(link_data); + + read_len = p_readlink(from, link_data, link_size); + if (read_len != (ssize_t)link_size) { + git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", from); + error = -1; + } + else { + link_data[read_len] = '\0'; + + if (p_symlink(link_data, to) < 0) { + git_error_set(GIT_ERROR_OS, "could not symlink '%s' as '%s'", + link_data, to); + error = -1; + } + } + + git__free(link_data); + return error; +} + +typedef struct { + const char *to_root; + git_str to; + ssize_t from_prefix; + uint32_t flags; + uint32_t mkdir_flags; + mode_t dirmode; +} cp_r_info; + +#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10) + +static int _cp_r_mkdir(cp_r_info *info, git_str *from) +{ + int error = 0; + + /* create root directory the first time we need to create a directory */ + if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) { + error = git_futils_mkdir( + info->to_root, info->dirmode, + (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0); + + info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT; + } + + /* create directory with root as base to prevent excess chmods */ + if (!error) + error = git_futils_mkdir_relative( + from->ptr + info->from_prefix, info->to_root, + info->dirmode, info->mkdir_flags, NULL); + + return error; +} + +static int _cp_r_callback(void *ref, git_str *from) +{ + int error = 0; + cp_r_info *info = ref; + struct stat from_st, to_st; + bool exists = false; + + if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 && + from->ptr[git_fs_path_basename_offset(from)] == '.') + return 0; + + if ((error = git_str_joinpath( + &info->to, info->to_root, from->ptr + info->from_prefix)) < 0) + return error; + + if (!(error = git_fs_path_lstat(info->to.ptr, &to_st))) + exists = true; + else if (error != GIT_ENOTFOUND) + return error; + else { + git_error_clear(); + error = 0; + } + + if ((error = git_fs_path_lstat(from->ptr, &from_st)) < 0) + return error; + + if (S_ISDIR(from_st.st_mode)) { + mode_t oldmode = info->dirmode; + + /* if we are not chmod'ing, then overwrite dirmode */ + if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0) + info->dirmode = from_st.st_mode; + + /* make directory now if CREATE_EMPTY_DIRS is requested and needed */ + if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0) + error = _cp_r_mkdir(info, from); + + /* recurse onto target directory */ + if (!error && (!exists || S_ISDIR(to_st.st_mode))) + error = git_fs_path_direach(from, 0, _cp_r_callback, info); + + if (oldmode != 0) + info->dirmode = oldmode; + + return error; + } + + if (exists) { + if ((info->flags & GIT_CPDIR_OVERWRITE) == 0) + return 0; + + if (p_unlink(info->to.ptr) < 0) { + git_error_set(GIT_ERROR_OS, "cannot overwrite existing file '%s'", + info->to.ptr); + return GIT_EEXISTS; + } + } + + /* Done if this isn't a regular file or a symlink */ + if (!S_ISREG(from_st.st_mode) && + (!S_ISLNK(from_st.st_mode) || + (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0)) + return 0; + + /* Make container directory on demand if needed */ + if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 && + (error = _cp_r_mkdir(info, from)) < 0) + return error; + + /* make symlink or regular file */ + if (info->flags & GIT_CPDIR_LINK_FILES) { + if ((error = p_link(from->ptr, info->to.ptr)) < 0) + git_error_set(GIT_ERROR_OS, "failed to link '%s'", from->ptr); + } else if (S_ISLNK(from_st.st_mode)) { + error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); + } else { + mode_t usemode = from_st.st_mode; + + if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) + usemode = GIT_PERMS_FOR_WRITE(usemode); + + error = git_futils_cp(from->ptr, info->to.ptr, usemode); + } + + return error; +} + +int git_futils_cp_r( + const char *from, + const char *to, + uint32_t flags, + mode_t dirmode) +{ + int error; + git_str path = GIT_STR_INIT; + cp_r_info info; + + if (git_str_joinpath(&path, from, "") < 0) /* ensure trailing slash */ + return -1; + + memset(&info, 0, sizeof(info)); + info.to_root = to; + info.flags = flags; + info.dirmode = dirmode; + info.from_prefix = path.size; + git_str_init(&info.to, 0); + + /* precalculate mkdir flags */ + if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) { + /* if not creating empty dirs, then use mkdir to create the path on + * demand right before files are copied. + */ + info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST; + if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) + info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH; + } else { + /* otherwise, we will do simple mkdir as directories are encountered */ + info.mkdir_flags = + ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0; + } + + error = _cp_r_callback(&info, &path); + + git_str_dispose(&path); + git_str_dispose(&info.to); + + return error; +} + +int git_futils_filestamp_check( + git_futils_filestamp *stamp, const char *path) +{ + struct stat st; + + /* if the stamp is NULL, then always reload */ + if (stamp == NULL) + return 1; + + if (p_stat(path, &st) < 0) + return GIT_ENOTFOUND; + + if (stamp->mtime.tv_sec == st.st_mtime && +#if defined(GIT_USE_NSEC) + stamp->mtime.tv_nsec == st.st_mtime_nsec && +#endif + stamp->size == (uint64_t)st.st_size && + stamp->ino == (unsigned int)st.st_ino) + return 0; + + stamp->mtime.tv_sec = st.st_mtime; +#if defined(GIT_USE_NSEC) + stamp->mtime.tv_nsec = st.st_mtime_nsec; +#endif + stamp->size = (uint64_t)st.st_size; + stamp->ino = (unsigned int)st.st_ino; + + return 1; +} + +void git_futils_filestamp_set( + git_futils_filestamp *target, const git_futils_filestamp *source) +{ + if (source) + memcpy(target, source, sizeof(*target)); + else + memset(target, 0, sizeof(*target)); +} + + +void git_futils_filestamp_set_from_stat( + git_futils_filestamp *stamp, struct stat *st) +{ + if (st) { + stamp->mtime.tv_sec = st->st_mtime; +#if defined(GIT_USE_NSEC) + stamp->mtime.tv_nsec = st->st_mtime_nsec; +#else + stamp->mtime.tv_nsec = 0; +#endif + stamp->size = (uint64_t)st->st_size; + stamp->ino = (unsigned int)st->st_ino; + } else { + memset(stamp, 0, sizeof(*stamp)); + } +} + +int git_futils_fsync_dir(const char *path) +{ +#ifdef GIT_WIN32 + GIT_UNUSED(path); + return 0; +#else + int fd, error = -1; + + if ((fd = p_open(path, O_RDONLY)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to open directory '%s' for fsync", path); + return -1; + } + + if ((error = p_fsync(fd)) < 0) + git_error_set(GIT_ERROR_OS, "failed to fsync directory '%s'", path); + + p_close(fd); + return error; +#endif +} + +int git_futils_fsync_parent(const char *path) +{ + char *parent; + int error; + + if ((parent = git_fs_path_dirname(path)) == NULL) + return -1; + + error = git_futils_fsync_dir(parent); + git__free(parent); + return error; +} diff --git a/src/libgit2/futils.h b/src/libgit2/futils.h new file mode 100644 index 000000000..a82ec41cc --- /dev/null +++ b/src/libgit2/futils.h @@ -0,0 +1,402 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_futils_h__ +#define INCLUDE_futils_h__ + +#include "common.h" + +#include "map.h" +#include "posix.h" +#include "fs_path.h" +#include "pool.h" +#include "strmap.h" +#include "hash.h" + +/** + * Filebuffer methods + * + * Read whole files into an in-memory buffer for processing + */ +extern int git_futils_readbuffer(git_str *obj, const char *path); +extern int git_futils_readbuffer_updated( + git_str *obj, + const char *path, + unsigned char checksum[GIT_HASH_SHA1_SIZE], + int *updated); +extern int git_futils_readbuffer_fd(git_str *obj, git_file fd, size_t len); + +/* Additional constants for `git_futils_writebuffer`'s `open_flags`. We + * support these internally and they will be removed before the `open` call. + */ +#ifndef O_FSYNC +# define O_FSYNC (1 << 31) +#endif + +extern int git_futils_writebuffer( + const git_str *buf, const char *path, int open_flags, mode_t mode); + +/** + * File utils + * + * These are custom filesystem-related helper methods. They are + * rather high level, and wrap the underlying POSIX methods + * + * All these methods return 0 on success, + * or an error code on failure and an error message is set. + */ + +/** + * Create and open a file, while also + * creating all the folders in its path + */ +extern int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode); + +/** + * Create and open a process-locked file + */ +extern int git_futils_creat_locked(const char *path, const mode_t mode); + +/** + * Create and open a process-locked file, while + * also creating all the folders in its path + */ +extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode); + +/** + * Create a path recursively. + */ +extern int git_futils_mkdir_r(const char *path, const mode_t mode); + +/** + * Flags to pass to `git_futils_mkdir`. + * + * * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists. + * * GIT_MKDIR_PATH says to make all components in the path. + * * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation + * * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path + * * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path + * * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path + * * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST + * * GIT_MKDIR_REMOVE_FILES says to remove files and recreate dirs + * * GIT_MKDIR_REMOVE_SYMLINKS says to remove symlinks and recreate dirs + * + * Note that the chmod options will be executed even if the directory already + * exists, unless GIT_MKDIR_EXCL is given. + */ +typedef enum { + GIT_MKDIR_EXCL = 1, + GIT_MKDIR_PATH = 2, + GIT_MKDIR_CHMOD = 4, + GIT_MKDIR_CHMOD_PATH = 8, + GIT_MKDIR_SKIP_LAST = 16, + GIT_MKDIR_SKIP_LAST2 = 32, + GIT_MKDIR_VERIFY_DIR = 64, + GIT_MKDIR_REMOVE_FILES = 128, + GIT_MKDIR_REMOVE_SYMLINKS = 256 +} git_futils_mkdir_flags; + +struct git_futils_mkdir_perfdata +{ + size_t stat_calls; + size_t mkdir_calls; + size_t chmod_calls; +}; + +struct git_futils_mkdir_options +{ + git_strmap *dir_map; + git_pool *pool; + struct git_futils_mkdir_perfdata perfdata; +}; + +/** + * Create a directory or entire path. + * + * This makes a directory (and the entire path leading up to it if requested), + * and optionally chmods the directory immediately after (or each part of the + * path if requested). + * + * @param path The path to create, relative to base. + * @param base Root for relative path. These directories will never be made. + * @param mode The mode to use for created directories. + * @param flags Combination of the mkdir flags above. + * @param opts Extended options, or null. + * @return 0 on success, else error code + */ +extern int git_futils_mkdir_relative(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts); + +/** + * Create a directory or entire path. Similar to `git_futils_mkdir_relative` + * without performance data. + */ +extern int git_futils_mkdir(const char *path, mode_t mode, uint32_t flags); + +/** + * Create all the folders required to contain + * the full path of a file + */ +extern int git_futils_mkpath2file(const char *path, const mode_t mode); + +/** + * Flags to pass to `git_futils_rmdir_r`. + * + * * GIT_RMDIR_EMPTY_HIERARCHY - the default; remove hierarchy of empty + * dirs and generate error if any files are found. + * * GIT_RMDIR_REMOVE_FILES - attempt to remove files in the hierarchy. + * * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error. + * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base + * if removing this item leaves them empty + * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR + * * GIT_RMDIR_SKIP_ROOT - don't remove root directory itself + */ +typedef enum { + GIT_RMDIR_EMPTY_HIERARCHY = 0, + GIT_RMDIR_REMOVE_FILES = (1 << 0), + GIT_RMDIR_SKIP_NONEMPTY = (1 << 1), + GIT_RMDIR_EMPTY_PARENTS = (1 << 2), + GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3), + GIT_RMDIR_SKIP_ROOT = (1 << 4) +} git_futils_rmdir_flags; + +/** + * Remove path and any files and directories beneath it. + * + * @param path Path to the top level directory to process. + * @param base Root for relative path. + * @param flags Combination of git_futils_rmdir_flags values + * @return 0 on success; -1 on error. + */ +extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags); + +/** + * Create and open a temporary file with a `_git2_` suffix in a + * protected directory; the file created will created will honor + * the current `umask`. Writes the filename into path_out. + * + * This function uses a high-quality PRNG seeded by the system's + * entropy pool _where available_ and falls back to a simple seed + * (time plus system information) when not. This is suitable for + * writing within a protected directory, but the system's safe + * temporary file creation functions should be preferred where + * available when writing into world-writable (temp) directories. + * + * @return On success, an open file descriptor, else an error code < 0. + */ +extern int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode); + +/** + * Move a file on the filesystem, create the + * destination path if it doesn't exist + */ +extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode); + +/** + * Copy a file + * + * The filemode will be used for the newly created file. + */ +extern int git_futils_cp( + const char *from, + const char *to, + mode_t filemode); + +/** + * Set the files atime and mtime to the given time, or the current time + * if `ts` is NULL. + */ +extern int git_futils_touch(const char *path, time_t *when); + +/** + * Flags that can be passed to `git_futils_cp_r`. + * + * - GIT_CPDIR_CREATE_EMPTY_DIRS: create directories even if there are no + * files under them (otherwise directories will only be created lazily + * when a file inside them is copied). + * - GIT_CPDIR_COPY_SYMLINKS: copy symlinks, otherwise they are ignored. + * - GIT_CPDIR_COPY_DOTFILES: copy files with leading '.', otherwise ignored. + * - GIT_CPDIR_OVERWRITE: overwrite pre-existing files with source content, + * otherwise they are silently skipped. + * - GIT_CPDIR_CHMOD_DIRS: explicitly chmod directories to `dirmode` + * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the + * source file to the target; with this flag, always use 0666 (or 0777 if + * source has exec bits set) for target. + * - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files + */ +typedef enum { + GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0), + GIT_CPDIR_COPY_SYMLINKS = (1u << 1), + GIT_CPDIR_COPY_DOTFILES = (1u << 2), + GIT_CPDIR_OVERWRITE = (1u << 3), + GIT_CPDIR_CHMOD_DIRS = (1u << 4), + GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5), + GIT_CPDIR_LINK_FILES = (1u << 6) +} git_futils_cpdir_flags; + +/** + * Copy a directory tree. + * + * This copies directories and files from one root to another. You can + * pass a combination of GIT_CPDIR flags as defined above. + * + * If you pass the CHMOD flag, then the dirmode will be applied to all + * directories that are created during the copy, overriding the natural + * permissions. If you do not pass the CHMOD flag, then the dirmode + * will actually be copied from the source files and the `dirmode` arg + * will be ignored. + */ +extern int git_futils_cp_r( + const char *from, + const char *to, + uint32_t flags, + mode_t dirmode); + +/** + * Open a file readonly and set error if needed. + */ +extern int git_futils_open_ro(const char *path); + +/** + * Truncate a file, creating it if it doesn't exist. + */ +extern int git_futils_truncate(const char *path, int mode); + +/** + * Get the filesize in bytes of a file + */ +extern int git_futils_filesize(uint64_t *out, git_file fd); + +#define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0100) != 0) +#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644) +#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0777 : 0666) + +#define GIT_MODE_PERMS_MASK 0777 +#define GIT_MODE_TYPE_MASK 0170000 +#define GIT_MODE_TYPE(MODE) ((MODE) & GIT_MODE_TYPE_MASK) +#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB)) + +/** + * Convert a mode_t from the OS to a legal git mode_t value. + */ +extern mode_t git_futils_canonical_mode(mode_t raw_mode); + + +/** + * Read-only map all or part of a file into memory. + * When possible this function should favor a virtual memory + * style mapping over some form of malloc()+read(), as the + * data access will be random and is not likely to touch the + * majority of the region requested. + * + * @param out buffer to populate with the mapping information. + * @param fd open descriptor to configure the mapping from. + * @param begin first byte to map, this should be page aligned. + * @param len number of bytes to map. + * @return + * - 0 on success; + * - -1 on error. + */ +extern int git_futils_mmap_ro( + git_map *out, + git_file fd, + off64_t begin, + size_t len); + +/** + * Read-only map an entire file. + * + * @param out buffer to populate with the mapping information. + * @param path path to file to be opened. + * @return + * - 0 on success; + * - GIT_ENOTFOUND if not found; + * - -1 on an unspecified OS related error. + */ +extern int git_futils_mmap_ro_file( + git_map *out, + const char *path); + +/** + * Release the memory associated with a previous memory mapping. + * @param map the mapping description previously configured. + */ +extern void git_futils_mmap_free(git_map *map); + +/** + * Create a "fake" symlink (text file containing the target path). + * + * @param target original symlink target + * @param path symlink file to be created + * @return 0 on success, -1 on error + */ +extern int git_futils_fake_symlink(const char *target, const char *path); + +/** + * A file stamp represents a snapshot of information about a file that can + * be used to test if the file changes. This portable implementation is + * based on stat data about that file, but it is possible that OS specific + * versions could be implemented in the future. + */ +typedef struct { + struct timespec mtime; + uint64_t size; + unsigned int ino; +} git_futils_filestamp; + +/** + * Compare stat information for file with reference info. + * + * This function updates the file stamp to current data for the given path + * and returns 0 if the file is up-to-date relative to the prior setting, + * 1 if the file has been changed, or GIT_ENOTFOUND if the file doesn't + * exist. This will not call git_error_set, so you must set the error if you + * plan to return an error. + * + * @param stamp File stamp to be checked + * @param path Path to stat and check if changed + * @return 0 if up-to-date, 1 if out-of-date, GIT_ENOTFOUND if cannot stat + */ +extern int git_futils_filestamp_check( + git_futils_filestamp *stamp, const char *path); + +/** + * Set or reset file stamp data + * + * This writes the target file stamp. If the source is NULL, this will set + * the target stamp to values that will definitely be out of date. If the + * source is not NULL, this copies the source values to the target. + * + * @param tgt File stamp to write to + * @param src File stamp to copy from or NULL to clear the target + */ +extern void git_futils_filestamp_set( + git_futils_filestamp *tgt, const git_futils_filestamp *src); + +/** + * Set file stamp data from stat structure + */ +extern void git_futils_filestamp_set_from_stat( + git_futils_filestamp *stamp, struct stat *st); + +/** + * `fsync` the parent directory of the given path, if `fsync` is + * supported for directories on this platform. + * + * @param path Path of the directory to sync. + * @return 0 on success, -1 on error + */ +extern int git_futils_fsync_dir(const char *path); + +/** + * `fsync` the parent directory of the given path, if `fsync` is + * supported for directories on this platform. + * + * @param path Path of the file whose parent directory should be synced. + * @return 0 on success, -1 on error + */ +extern int git_futils_fsync_parent(const char *path); + +#endif diff --git a/src/libgit2/graph.c b/src/libgit2/graph.c new file mode 100644 index 000000000..35e914f74 --- /dev/null +++ b/src/libgit2/graph.c @@ -0,0 +1,249 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "revwalk.h" +#include "merge.h" +#include "git2/graph.h" + +static int interesting(git_pqueue *list, git_commit_list *roots) +{ + unsigned int i; + + for (i = 0; i < git_pqueue_size(list); i++) { + git_commit_list_node *commit = git_pqueue_get(list, i); + if ((commit->flags & STALE) == 0) + return 1; + } + + while(roots) { + if ((roots->item->flags & STALE) == 0) + return 1; + roots = roots->next; + } + + return 0; +} + +static int mark_parents(git_revwalk *walk, git_commit_list_node *one, + git_commit_list_node *two) +{ + unsigned int i; + git_commit_list *roots = NULL; + git_pqueue list; + + /* if the commit is repeated, we have a our merge base already */ + if (one == two) { + one->flags |= PARENT1 | PARENT2 | RESULT; + return 0; + } + + if (git_pqueue_init(&list, 0, 2, git_commit_list_generation_cmp) < 0) + return -1; + + if (git_commit_list_parse(walk, one) < 0) + goto on_error; + one->flags |= PARENT1; + if (git_pqueue_insert(&list, one) < 0) + goto on_error; + + if (git_commit_list_parse(walk, two) < 0) + goto on_error; + two->flags |= PARENT2; + if (git_pqueue_insert(&list, two) < 0) + goto on_error; + + /* as long as there are non-STALE commits */ + while (interesting(&list, roots)) { + git_commit_list_node *commit = git_pqueue_pop(&list); + unsigned int flags; + + if (commit == NULL) + break; + + flags = commit->flags & (PARENT1 | PARENT2 | STALE); + if (flags == (PARENT1 | PARENT2)) { + if (!(commit->flags & RESULT)) + commit->flags |= RESULT; + /* we mark the parents of a merge stale */ + flags |= STALE; + } + + for (i = 0; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + if ((p->flags & flags) == flags) + continue; + + if (git_commit_list_parse(walk, p) < 0) + goto on_error; + + p->flags |= flags; + if (git_pqueue_insert(&list, p) < 0) + goto on_error; + } + + /* Keep track of root commits, to make sure the path gets marked */ + if (commit->out_degree == 0) { + if (git_commit_list_insert(commit, &roots) == NULL) + goto on_error; + } + } + + git_commit_list_free(&roots); + git_pqueue_free(&list); + return 0; + +on_error: + git_commit_list_free(&roots); + git_pqueue_free(&list); + return -1; +} + + +static int ahead_behind(git_commit_list_node *one, git_commit_list_node *two, + size_t *ahead, size_t *behind) +{ + git_commit_list_node *commit; + git_pqueue pq; + int error = 0, i; + *ahead = 0; + *behind = 0; + + if (git_pqueue_init(&pq, 0, 2, git_commit_list_time_cmp) < 0) + return -1; + + if ((error = git_pqueue_insert(&pq, one)) < 0 || + (error = git_pqueue_insert(&pq, two)) < 0) + goto done; + + while ((commit = git_pqueue_pop(&pq)) != NULL) { + if (commit->flags & RESULT || + (commit->flags & (PARENT1 | PARENT2)) == (PARENT1 | PARENT2)) + continue; + else if (commit->flags & PARENT1) + (*ahead)++; + else if (commit->flags & PARENT2) + (*behind)++; + + for (i = 0; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + if ((error = git_pqueue_insert(&pq, p)) < 0) + goto done; + } + commit->flags |= RESULT; + } + +done: + git_pqueue_free(&pq); + return error; +} + +int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo, + const git_oid *local, const git_oid *upstream) +{ + git_revwalk *walk; + git_commit_list_node *commit_u, *commit_l; + + if (git_revwalk_new(&walk, repo) < 0) + return -1; + + commit_u = git_revwalk__commit_lookup(walk, upstream); + if (commit_u == NULL) + goto on_error; + + commit_l = git_revwalk__commit_lookup(walk, local); + if (commit_l == NULL) + goto on_error; + + if (mark_parents(walk, commit_l, commit_u) < 0) + goto on_error; + if (ahead_behind(commit_l, commit_u, ahead, behind) < 0) + goto on_error; + + git_revwalk_free(walk); + + return 0; + +on_error: + git_revwalk_free(walk); + return -1; +} + +int git_graph_descendant_of(git_repository *repo, const git_oid *commit, const git_oid *ancestor) +{ + if (git_oid_equal(commit, ancestor)) + return 0; + + return git_graph_reachable_from_any(repo, ancestor, commit, 1); +} + +int git_graph_reachable_from_any( + git_repository *repo, + const git_oid *commit_id, + const git_oid descendant_array[], + size_t length) +{ + git_revwalk *walk = NULL; + git_vector list; + git_commit_list *result = NULL; + git_commit_list_node *commit; + size_t i; + uint32_t minimum_generation = 0xffffffff; + int error = 0; + + if (!length) + return 0; + + for (i = 0; i < length; ++i) { + if (git_oid_equal(commit_id, &descendant_array[i])) + return 1; + } + + if ((error = git_vector_init(&list, length + 1, NULL)) < 0) + return error; + + if ((error = git_revwalk_new(&walk, repo)) < 0) + goto done; + + for (i = 0; i < length; i++) { + commit = git_revwalk__commit_lookup(walk, &descendant_array[i]); + if (commit == NULL) { + error = -1; + goto done; + } + + git_vector_insert(&list, commit); + if (minimum_generation > commit->generation) + minimum_generation = commit->generation; + } + + commit = git_revwalk__commit_lookup(walk, commit_id); + if (commit == NULL) { + error = -1; + goto done; + } + + if (minimum_generation > commit->generation) + minimum_generation = commit->generation; + + if ((error = git_merge__bases_many(&result, walk, commit, &list, minimum_generation)) < 0) + goto done; + + if (result) { + error = git_oid_equal(commit_id, &result->item->oid); + } else { + /* No merge-base found, it's not a descendant */ + error = 0; + } + +done: + git_commit_list_free(&result); + git_vector_free(&list); + git_revwalk_free(walk); + return error; +} diff --git a/src/libgit2/hash.c b/src/libgit2/hash.c new file mode 100644 index 000000000..98ceb05d2 --- /dev/null +++ b/src/libgit2/hash.c @@ -0,0 +1,142 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "hash.h" + +int git_hash_global_init(void) +{ + return git_hash_sha1_global_init(); +} + +int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm) +{ + int error; + + switch (algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + error = git_hash_sha1_ctx_init(&ctx->ctx.sha1); + break; + default: + git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); + error = -1; + } + + ctx->algorithm = algorithm; + return error; +} + +void git_hash_ctx_cleanup(git_hash_ctx *ctx) +{ + switch (ctx->algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + git_hash_sha1_ctx_cleanup(&ctx->ctx.sha1); + return; + default: + /* unreachable */ ; + } +} + +int git_hash_init(git_hash_ctx *ctx) +{ + switch (ctx->algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + return git_hash_sha1_init(&ctx->ctx.sha1); + default: + /* unreachable */ ; + } + + git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); + return -1; +} + +int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +{ + switch (ctx->algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + return git_hash_sha1_update(&ctx->ctx.sha1, data, len); + default: + /* unreachable */ ; + } + + git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); + return -1; +} + +int git_hash_final(unsigned char *out, git_hash_ctx *ctx) +{ + switch (ctx->algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + return git_hash_sha1_final(out, &ctx->ctx.sha1); + default: + /* unreachable */ ; + } + + git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); + return -1; +} + +int git_hash_buf( + unsigned char *out, + const void *data, + size_t len, + git_hash_algorithm_t algorithm) +{ + git_hash_ctx ctx; + int error = 0; + + if (git_hash_ctx_init(&ctx, algorithm) < 0) + return -1; + + if ((error = git_hash_update(&ctx, data, len)) >= 0) + error = git_hash_final(out, &ctx); + + git_hash_ctx_cleanup(&ctx); + + return error; +} + +int git_hash_vec( + unsigned char *out, + git_str_vec *vec, + size_t n, + git_hash_algorithm_t algorithm) +{ + git_hash_ctx ctx; + size_t i; + int error = 0; + + if (git_hash_ctx_init(&ctx, algorithm) < 0) + return -1; + + for (i = 0; i < n; i++) { + if ((error = git_hash_update(&ctx, vec[i].data, vec[i].len)) < 0) + goto done; + } + + error = git_hash_final(out, &ctx); + +done: + git_hash_ctx_cleanup(&ctx); + + return error; +} + +int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len) +{ + static char hex[] = "0123456789abcdef"; + char *str = out; + size_t i; + + for (i = 0; i < hash_len; i++) { + *str++ = hex[hash[i] >> 4]; + *str++ = hex[hash[i] & 0x0f]; + } + + *str++ = '\0'; + + return 0; +} diff --git a/src/libgit2/hash.h b/src/libgit2/hash.h new file mode 100644 index 000000000..507c1cb25 --- /dev/null +++ b/src/libgit2/hash.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_h__ +#define INCLUDE_hash_h__ + +#include "common.h" + +#include "hash/sha1.h" + +typedef struct { + void *data; + size_t len; +} git_str_vec; + +typedef enum { + GIT_HASH_ALGORITHM_NONE = 0, + GIT_HASH_ALGORITHM_SHA1 +} git_hash_algorithm_t; + +typedef struct git_hash_ctx { + union { + git_hash_sha1_ctx sha1; + } ctx; + git_hash_algorithm_t algorithm; +} git_hash_ctx; + +int git_hash_global_init(void); + +int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm); +void git_hash_ctx_cleanup(git_hash_ctx *ctx); + +int git_hash_init(git_hash_ctx *c); +int git_hash_update(git_hash_ctx *c, const void *data, size_t len); +int git_hash_final(unsigned char *out, git_hash_ctx *c); + +int git_hash_buf(unsigned char *out, const void *data, size_t len, git_hash_algorithm_t algorithm); +int git_hash_vec(unsigned char *out, git_str_vec *vec, size_t n, git_hash_algorithm_t algorithm); + +int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len); + +#endif diff --git a/src/libgit2/hash/sha1.h b/src/libgit2/hash/sha1.h new file mode 100644 index 000000000..4b4dae3f8 --- /dev/null +++ b/src/libgit2/hash/sha1.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_h__ +#define INCLUDE_hash_sha1_h__ + +#include "common.h" + +typedef struct git_hash_sha1_ctx git_hash_sha1_ctx; + +#if defined(GIT_SHA1_COLLISIONDETECT) +# include "sha1/collisiondetect.h" +#elif defined(GIT_SHA1_COMMON_CRYPTO) +# include "sha1/common_crypto.h" +#elif defined(GIT_SHA1_OPENSSL) +# include "sha1/openssl.h" +#elif defined(GIT_SHA1_WIN32) +# include "sha1/win32.h" +#elif defined(GIT_SHA1_MBEDTLS) +# include "sha1/mbedtls.h" +#else +# include "sha1/generic.h" +#endif + +#define GIT_HASH_SHA1_SIZE 20 + +int git_hash_sha1_global_init(void); + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx); +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx); + +int git_hash_sha1_init(git_hash_sha1_ctx *c); +int git_hash_sha1_update(git_hash_sha1_ctx *c, const void *data, size_t len); +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *c); + +#endif diff --git a/src/libgit2/hash/sha1/collisiondetect.c b/src/libgit2/hash/sha1/collisiondetect.c new file mode 100644 index 000000000..ec7059c4c --- /dev/null +++ b/src/libgit2/hash/sha1/collisiondetect.c @@ -0,0 +1,48 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "collisiondetect.h" + +int git_hash_sha1_global_init(void) +{ + return 0; +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + SHA1DCInit(&ctx->c); + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + SHA1DCUpdate(&ctx->c, data, len); + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + if (SHA1DCFinal(out, &ctx->c)) { + git_error_set(GIT_ERROR_SHA1, "SHA1 collision attack detected"); + return -1; + } + + return 0; +} diff --git a/src/libgit2/hash/sha1/collisiondetect.h b/src/libgit2/hash/sha1/collisiondetect.h new file mode 100644 index 000000000..eb88e86c1 --- /dev/null +++ b/src/libgit2/hash/sha1/collisiondetect.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_collisiondetect_h__ +#define INCLUDE_hash_sha1_collisiondetect_h__ + +#include "hash/sha1.h" + +#include "sha1dc/sha1.h" + +struct git_hash_sha1_ctx { + SHA1_CTX c; +}; + +#endif diff --git a/src/libgit2/hash/sha1/common_crypto.c b/src/libgit2/hash/sha1/common_crypto.c new file mode 100644 index 000000000..9d608f449 --- /dev/null +++ b/src/libgit2/hash/sha1/common_crypto.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common_crypto.h" + +#define CC_LONG_MAX ((CC_LONG)-1) + +int git_hash_sha1_global_init(void) +{ + return 0; +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + CC_SHA1_Init(&ctx->c); + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) +{ + const unsigned char *data = _data; + + GIT_ASSERT_ARG(ctx); + + while (len > 0) { + CC_LONG chunk = (len > CC_LONG_MAX) ? CC_LONG_MAX : (CC_LONG)len; + + CC_SHA1_Update(&ctx->c, data, chunk); + + data += chunk; + len -= chunk; + } + + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + CC_SHA1_Final(out, &ctx->c); + return 0; +} diff --git a/src/libgit2/hash/sha1/common_crypto.h b/src/libgit2/hash/sha1/common_crypto.h new file mode 100644 index 000000000..a5fcfb33e --- /dev/null +++ b/src/libgit2/hash/sha1/common_crypto.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_common_crypto_h__ +#define INCLUDE_hash_sha1_common_crypto_h__ + +#include "hash/sha1.h" + +#include + +struct git_hash_sha1_ctx { + CC_SHA1_CTX c; +}; + +#endif diff --git a/src/libgit2/hash/sha1/generic.c b/src/libgit2/hash/sha1/generic.c new file mode 100644 index 000000000..85b34c578 --- /dev/null +++ b/src/libgit2/hash/sha1/generic.c @@ -0,0 +1,300 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "generic.h" + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + +/* + * Force usage of rol or ror by selecting the one with the smaller constant. + * It _can_ generate slightly smaller code (a constant of 1 is special), but + * perhaps more importantly it's possibly faster on any uarch that does a + * rotate with a loop. + */ + +#define SHA_ASM(op, x, n) (__extension__ ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; })) +#define SHA_ROL(x,n) SHA_ASM("rol", x, n) +#define SHA_ROR(x,n) SHA_ASM("ror", x, n) + +#else + +#define SHA_ROT(X,l,r) (((X) << (l)) | ((X) >> (r))) +#define SHA_ROL(X,n) SHA_ROT(X,n,32-(n)) +#define SHA_ROR(X,n) SHA_ROT(X,32-(n),n) + +#endif + +/* + * If you have 32 registers or more, the compiler can (and should) + * try to change the array[] accesses into registers. However, on + * machines with less than ~25 registers, that won't really work, + * and at least gcc will make an unholy mess of it. + * + * So to avoid that mess which just slows things down, we force + * the stores to memory to actually happen (we might be better off + * with a 'W(t)=(val);asm("":"+m" (W(t))' there instead, as + * suggested by Artur Skawina - that will also make gcc unable to + * try to do the silly "optimize away loads" part because it won't + * see what the value will be). + * + * Ben Herrenschmidt reports that on PPC, the C version comes close + * to the optimized asm with this (ie on PPC you don't want that + * 'volatile', since there are lots of registers). + * + * On ARM we get the best code generation by forcing a full memory barrier + * between each SHA_ROUND, otherwise gcc happily get wild with spilling and + * the stack frame size simply explode and performance goes down the drain. + */ + +#if defined(__i386__) || defined(__x86_64__) + #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val)) +#elif defined(__GNUC__) && defined(__arm__) + #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0) +#else + #define setW(x, val) (W(x) = (val)) +#endif + +/* + * Performance might be improved if the CPU architecture is OK with + * unaligned 32-bit loads and a fast ntohl() is available. + * Otherwise fall back to byte loads and shifts which is portable, + * and is faster on architectures with memory alignment issues. + */ + +#if defined(__i386__) || defined(__x86_64__) || \ + defined(_M_IX86) || defined(_M_X64) || \ + defined(__ppc__) || defined(__ppc64__) || \ + defined(__powerpc__) || defined(__powerpc64__) || \ + defined(__s390__) || defined(__s390x__) + +#define get_be32(p) ntohl(*(const unsigned int *)(p)) +#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0) + +#else + +#define get_be32(p) ( \ + (*((const unsigned char *)(p) + 0) << 24) | \ + (*((const unsigned char *)(p) + 1) << 16) | \ + (*((const unsigned char *)(p) + 2) << 8) | \ + (*((const unsigned char *)(p) + 3) << 0) ) +#define put_be32(p, v) do { \ + unsigned int __v = (v); \ + *((unsigned char *)(p) + 0) = __v >> 24; \ + *((unsigned char *)(p) + 1) = __v >> 16; \ + *((unsigned char *)(p) + 2) = __v >> 8; \ + *((unsigned char *)(p) + 3) = __v >> 0; } while (0) + +#endif + +/* This "rolls" over the 512-bit array */ +#define W(x) (array[(x)&15]) + +/* + * Where do we get the source from? The first 16 iterations get it from + * the input data, the next mix it from the 512-bit array. + */ +#define SHA_SRC(t) get_be32(data + t) +#define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1) + +#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \ + unsigned int TEMP = input(t); setW(t, TEMP); \ + E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \ + B = SHA_ROR(B, 2); } while (0) + +#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) +#define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) +#define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E ) +#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E ) +#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E ) + +static void hash__block(git_hash_sha1_ctx *ctx, const unsigned int *data) +{ + unsigned int A,B,C,D,E; + unsigned int array[16]; + + A = ctx->H[0]; + B = ctx->H[1]; + C = ctx->H[2]; + D = ctx->H[3]; + E = ctx->H[4]; + + /* Round 1 - iterations 0-16 take their input from 'data' */ + T_0_15( 0, A, B, C, D, E); + T_0_15( 1, E, A, B, C, D); + T_0_15( 2, D, E, A, B, C); + T_0_15( 3, C, D, E, A, B); + T_0_15( 4, B, C, D, E, A); + T_0_15( 5, A, B, C, D, E); + T_0_15( 6, E, A, B, C, D); + T_0_15( 7, D, E, A, B, C); + T_0_15( 8, C, D, E, A, B); + T_0_15( 9, B, C, D, E, A); + T_0_15(10, A, B, C, D, E); + T_0_15(11, E, A, B, C, D); + T_0_15(12, D, E, A, B, C); + T_0_15(13, C, D, E, A, B); + T_0_15(14, B, C, D, E, A); + T_0_15(15, A, B, C, D, E); + + /* Round 1 - tail. Input from 512-bit mixing array */ + T_16_19(16, E, A, B, C, D); + T_16_19(17, D, E, A, B, C); + T_16_19(18, C, D, E, A, B); + T_16_19(19, B, C, D, E, A); + + /* Round 2 */ + T_20_39(20, A, B, C, D, E); + T_20_39(21, E, A, B, C, D); + T_20_39(22, D, E, A, B, C); + T_20_39(23, C, D, E, A, B); + T_20_39(24, B, C, D, E, A); + T_20_39(25, A, B, C, D, E); + T_20_39(26, E, A, B, C, D); + T_20_39(27, D, E, A, B, C); + T_20_39(28, C, D, E, A, B); + T_20_39(29, B, C, D, E, A); + T_20_39(30, A, B, C, D, E); + T_20_39(31, E, A, B, C, D); + T_20_39(32, D, E, A, B, C); + T_20_39(33, C, D, E, A, B); + T_20_39(34, B, C, D, E, A); + T_20_39(35, A, B, C, D, E); + T_20_39(36, E, A, B, C, D); + T_20_39(37, D, E, A, B, C); + T_20_39(38, C, D, E, A, B); + T_20_39(39, B, C, D, E, A); + + /* Round 3 */ + T_40_59(40, A, B, C, D, E); + T_40_59(41, E, A, B, C, D); + T_40_59(42, D, E, A, B, C); + T_40_59(43, C, D, E, A, B); + T_40_59(44, B, C, D, E, A); + T_40_59(45, A, B, C, D, E); + T_40_59(46, E, A, B, C, D); + T_40_59(47, D, E, A, B, C); + T_40_59(48, C, D, E, A, B); + T_40_59(49, B, C, D, E, A); + T_40_59(50, A, B, C, D, E); + T_40_59(51, E, A, B, C, D); + T_40_59(52, D, E, A, B, C); + T_40_59(53, C, D, E, A, B); + T_40_59(54, B, C, D, E, A); + T_40_59(55, A, B, C, D, E); + T_40_59(56, E, A, B, C, D); + T_40_59(57, D, E, A, B, C); + T_40_59(58, C, D, E, A, B); + T_40_59(59, B, C, D, E, A); + + /* Round 4 */ + T_60_79(60, A, B, C, D, E); + T_60_79(61, E, A, B, C, D); + T_60_79(62, D, E, A, B, C); + T_60_79(63, C, D, E, A, B); + T_60_79(64, B, C, D, E, A); + T_60_79(65, A, B, C, D, E); + T_60_79(66, E, A, B, C, D); + T_60_79(67, D, E, A, B, C); + T_60_79(68, C, D, E, A, B); + T_60_79(69, B, C, D, E, A); + T_60_79(70, A, B, C, D, E); + T_60_79(71, E, A, B, C, D); + T_60_79(72, D, E, A, B, C); + T_60_79(73, C, D, E, A, B); + T_60_79(74, B, C, D, E, A); + T_60_79(75, A, B, C, D, E); + T_60_79(76, E, A, B, C, D); + T_60_79(77, D, E, A, B, C); + T_60_79(78, C, D, E, A, B); + T_60_79(79, B, C, D, E, A); + + ctx->H[0] += A; + ctx->H[1] += B; + ctx->H[2] += C; + ctx->H[3] += D; + ctx->H[4] += E; +} + +int git_hash_sha1_global_init(void) +{ + return 0; +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + ctx->size = 0; + + /* Initialize H with the magic constants (see FIPS180 for constants) */ + ctx->H[0] = 0x67452301; + ctx->H[1] = 0xefcdab89; + ctx->H[2] = 0x98badcfe; + ctx->H[3] = 0x10325476; + ctx->H[4] = 0xc3d2e1f0; + + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + unsigned int lenW = ctx->size & 63; + + ctx->size += len; + + /* Read the data into W and process blocks as they get full */ + if (lenW) { + unsigned int left = 64 - lenW; + if (len < left) + left = (unsigned int)len; + memcpy(lenW + (char *)ctx->W, data, left); + lenW = (lenW + left) & 63; + len -= left; + data = ((const char *)data + left); + if (lenW) + return 0; + hash__block(ctx, ctx->W); + } + while (len >= 64) { + hash__block(ctx, data); + data = ((const char *)data + 64); + len -= 64; + } + if (len) + memcpy(ctx->W, data, len); + + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + static const unsigned char pad[64] = { 0x80 }; + unsigned int padlen[2]; + int i; + + /* Pad with a binary 1 (ie 0x80), then zeroes, then length */ + padlen[0] = htonl((uint32_t)(ctx->size >> 29)); + padlen[1] = htonl((uint32_t)(ctx->size << 3)); + + i = ctx->size & 63; + git_hash_sha1_update(ctx, pad, 1+ (63 & (55 - i))); + git_hash_sha1_update(ctx, padlen, 8); + + /* Output hash */ + for (i = 0; i < 5; i++) + put_be32(out + i*4, ctx->H[i]); + + return 0; +} diff --git a/src/libgit2/hash/sha1/generic.h b/src/libgit2/hash/sha1/generic.h new file mode 100644 index 000000000..53fc0823e --- /dev/null +++ b/src/libgit2/hash/sha1/generic.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_generic_h__ +#define INCLUDE_hash_sha1_generic_h__ + +#include "hash/sha1.h" + +struct git_hash_sha1_ctx { + uint64_t size; + unsigned int H[5]; + unsigned int W[16]; +}; + +#endif diff --git a/src/libgit2/hash/sha1/mbedtls.c b/src/libgit2/hash/sha1/mbedtls.c new file mode 100644 index 000000000..56016bec8 --- /dev/null +++ b/src/libgit2/hash/sha1/mbedtls.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "mbedtls.h" + +int git_hash_sha1_global_init(void) +{ + return 0; +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + if (ctx) + mbedtls_sha1_free(&ctx->c); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + mbedtls_sha1_init(&ctx->c); + mbedtls_sha1_starts(&ctx->c); + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + mbedtls_sha1_update(&ctx->c, data, len); + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + mbedtls_sha1_finish(&ctx->c, out); + return 0; +} diff --git a/src/libgit2/hash/sha1/mbedtls.h b/src/libgit2/hash/sha1/mbedtls.h new file mode 100644 index 000000000..15f7462a4 --- /dev/null +++ b/src/libgit2/hash/sha1/mbedtls.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_mbedtls_h__ +#define INCLUDE_hash_sha1_mbedtls_h__ + +#include "hash/sha1.h" + +#include + +struct git_hash_sha1_ctx { + mbedtls_sha1_context c; +}; + +#endif /* INCLUDE_hash_sha1_mbedtls_h__ */ diff --git a/src/libgit2/hash/sha1/openssl.c b/src/libgit2/hash/sha1/openssl.c new file mode 100644 index 000000000..64bf99b3c --- /dev/null +++ b/src/libgit2/hash/sha1/openssl.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "openssl.h" + +int git_hash_sha1_global_init(void) +{ + return 0; +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + + if (SHA1_Init(&ctx->c) != 1) { + git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to initialize hash context"); + return -1; + } + + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + + if (SHA1_Update(&ctx->c, data, len) != 1) { + git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to update hash"); + return -1; + } + + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + + if (SHA1_Final(out, &ctx->c) != 1) { + git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to finalize hash"); + return -1; + } + + return 0; +} diff --git a/src/libgit2/hash/sha1/openssl.h b/src/libgit2/hash/sha1/openssl.h new file mode 100644 index 000000000..a223ca03e --- /dev/null +++ b/src/libgit2/hash/sha1/openssl.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_openssl_h__ +#define INCLUDE_hash_sha1_openssl_h__ + +#include "hash/sha1.h" + +#include + +struct git_hash_sha1_ctx { + SHA_CTX c; +}; + +#endif diff --git a/src/libgit2/hash/sha1/sha1dc/sha1.c b/src/libgit2/hash/sha1/sha1dc/sha1.c new file mode 100644 index 000000000..929822728 --- /dev/null +++ b/src/libgit2/hash/sha1/sha1dc/sha1.c @@ -0,0 +1,1909 @@ +/*** +* Copyright 2017 Marc Stevens , Dan Shumow (danshu@microsoft.com) +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include +#include +#include +#include +#include /* make sure macros like _BIG_ENDIAN visible */ +#endif + +#ifdef SHA1DC_CUSTOM_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_INCLUDE_SHA1_C +#endif + +#ifndef SHA1DC_INIT_SAFE_HASH_DEFAULT +#define SHA1DC_INIT_SAFE_HASH_DEFAULT 1 +#endif + +#include "sha1.h" +#include "ubc_check.h" + +#if (defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \ + defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || \ + defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || defined(__INTEL__) || \ + defined(__386) || defined(_M_X64) || defined(_M_AMD64)) +#define SHA1DC_ON_INTEL_LIKE_PROCESSOR +#endif + +/* + Because Little-Endian architectures are most common, + we only set SHA1DC_BIGENDIAN if one of these conditions is met. + Note that all MSFT platforms are little endian, + so none of these will be defined under the MSC compiler. + If you are compiling on a big endian platform and your compiler does not define one of these, + you will have to add whatever macros your tool chain defines to indicate Big-Endianness. + */ + +#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) +/* + * Should detect Big Endian under GCC since at least 4.6.0 (gcc svn + * rev #165881). See + * https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html + * + * This also works under clang since 3.2, it copied the GCC-ism. See + * clang.git's 3b198a97d2 ("Preprocessor: add __BYTE_ORDER__ + * predefined macro", 2012-07-27) + */ +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike */ +#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) +/* + * Should detect Big Endian under glibc.git since 14245eb70e ("entered + * into RCS", 1992-11-25). Defined in which will have been + * brought in by standard headers. See glibc.git and + * https://sourceforge.net/p/predef/wiki/Endianness/ + */ +#if __BYTE_ORDER == __BIG_ENDIAN +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike or glibc */ +#elif defined(_BYTE_ORDER) && defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) +/* + * *BSD and newlib (embedded linux, cygwin, etc). + * the defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) part prevents + * this condition from matching with Solaris/sparc. + * (Solaris defines only one endian macro) + */ +#if _BYTE_ORDER == _BIG_ENDIAN +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike or glibc or *BSD or newlib */ +#elif (defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ + defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || \ + defined(__sparc)) +/* + * Should define Big Endian for a whitelist of known processors. See + * https://sourceforge.net/p/predef/wiki/Endianness/ and + * http://www.oracle.com/technetwork/server-storage/solaris/portingtosolaris-138514.html + */ +#define SHA1DC_BIGENDIAN + +/* Not under GCC-alike or glibc or *BSD or newlib or */ +#elif (defined(_AIX) || defined(__hpux)) + +/* + * Defines Big Endian on a whitelist of OSs that are known to be Big + * Endian-only. See + * https://public-inbox.org/git/93056823-2740-d072-1ebd-46b440b33d7e@felt.demon.nl/ + */ +#define SHA1DC_BIGENDIAN + +/* Not under GCC-alike or glibc or *BSD or newlib or or */ +#elif defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) +/* + * As a last resort before we do anything else we're not 100% sure + * about below, we blacklist specific processors here. We could add + * more, see e.g. https://wiki.debian.org/ArchitectureSpecificsMemo + */ +#else /* Not under GCC-alike or glibc or *BSD or newlib or or or */ + +/* We do nothing more here for now */ +/*#error "Uncomment this to see if you fall through all the detection"*/ + +#endif /* Big Endian detection */ + +#if (defined(SHA1DC_FORCE_LITTLEENDIAN) && defined(SHA1DC_BIGENDIAN)) +#undef SHA1DC_BIGENDIAN +#endif +#if (defined(SHA1DC_FORCE_BIGENDIAN) && !defined(SHA1DC_BIGENDIAN)) +#define SHA1DC_BIGENDIAN +#endif +/*ENDIANNESS SELECTION*/ + +#ifndef SHA1DC_FORCE_ALIGNED_ACCESS +#if defined(SHA1DC_FORCE_UNALIGNED_ACCESS) || defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) +#define SHA1DC_ALLOW_UNALIGNED_ACCESS +#endif /*UNALIGNED ACCESS DETECTION*/ +#endif /*FORCE ALIGNED ACCESS*/ + +#define rotate_right(x,n) (((x)>>(n))|((x)<<(32-(n)))) +#define rotate_left(x,n) (((x)<<(n))|((x)>>(32-(n)))) + +#define sha1_bswap32(x) \ + {x = ((x << 8) & 0xFF00FF00) | ((x >> 8) & 0xFF00FF); x = (x << 16) | (x >> 16);} + +#define sha1_mix(W, t) (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1)) + +#ifdef SHA1DC_BIGENDIAN + #define sha1_load(m, t, temp) { temp = m[t]; } +#else + #define sha1_load(m, t, temp) { temp = m[t]; sha1_bswap32(temp); } +#endif + +#define sha1_store(W, t, x) *(volatile uint32_t *)&W[t] = x + +#define sha1_f1(b,c,d) ((d)^((b)&((c)^(d)))) +#define sha1_f2(b,c,d) ((b)^(c)^(d)) +#define sha1_f3(b,c,d) (((b)&(c))+((d)&((b)^(c)))) +#define sha1_f4(b,c,d) ((b)^(c)^(d)) + +#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; b = rotate_left(b, 30); } +#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; b = rotate_left(b, 30); } +#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; b = rotate_left(b, 30); } +#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; b = rotate_left(b, 30); } + +#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; } +#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; } +#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; } +#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; } + +#define SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, t, temp) \ + {sha1_load(m, t, temp); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30);} + +#define SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30); } + +#define SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1; b = rotate_left(b, 30); } + +#define SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC; b = rotate_left(b, 30); } + +#define SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6; b = rotate_left(b, 30); } + + +#define SHA1_STORE_STATE(i) states[i][0] = a; states[i][1] = b; states[i][2] = c; states[i][3] = d; states[i][4] = e; + +#ifdef BUILDNOCOLLDETECTSHA1COMPRESSION +void sha1_compression(uint32_t ihv[5], const uint32_t m[16]) +{ + uint32_t W[80]; + uint32_t a,b,c,d,e; + unsigned i; + + memcpy(W, m, 16 * 4); + for (i = 16; i < 80; ++i) + W[i] = sha1_mix(W, i); + + a = ihv[0]; b = ihv[1]; c = ihv[2]; d = ihv[3]; e = ihv[4]; + + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); + + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); + + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); + + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); + + ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; +} +#endif /*BUILDNOCOLLDETECTSHA1COMPRESSION*/ + + +static void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]) +{ + uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; + + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); + + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); + + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); + + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); + + ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; +} + + + +void sha1_compression_states(uint32_t ihv[5], const uint32_t m[16], uint32_t W[80], uint32_t states[80][5]) +{ + uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; + uint32_t temp; + +#ifdef DOSTORESTATE00 + SHA1_STORE_STATE(0) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 0, temp); + +#ifdef DOSTORESTATE01 + SHA1_STORE_STATE(1) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 1, temp); + +#ifdef DOSTORESTATE02 + SHA1_STORE_STATE(2) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 2, temp); + +#ifdef DOSTORESTATE03 + SHA1_STORE_STATE(3) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 3, temp); + +#ifdef DOSTORESTATE04 + SHA1_STORE_STATE(4) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 4, temp); + +#ifdef DOSTORESTATE05 + SHA1_STORE_STATE(5) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 5, temp); + +#ifdef DOSTORESTATE06 + SHA1_STORE_STATE(6) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 6, temp); + +#ifdef DOSTORESTATE07 + SHA1_STORE_STATE(7) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 7, temp); + +#ifdef DOSTORESTATE08 + SHA1_STORE_STATE(8) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 8, temp); + +#ifdef DOSTORESTATE09 + SHA1_STORE_STATE(9) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 9, temp); + +#ifdef DOSTORESTATE10 + SHA1_STORE_STATE(10) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 10, temp); + +#ifdef DOSTORESTATE11 + SHA1_STORE_STATE(11) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 11, temp); + +#ifdef DOSTORESTATE12 + SHA1_STORE_STATE(12) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 12, temp); + +#ifdef DOSTORESTATE13 + SHA1_STORE_STATE(13) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 13, temp); + +#ifdef DOSTORESTATE14 + SHA1_STORE_STATE(14) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 14, temp); + +#ifdef DOSTORESTATE15 + SHA1_STORE_STATE(15) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 15, temp); + +#ifdef DOSTORESTATE16 + SHA1_STORE_STATE(16) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(e, a, b, c, d, W, 16, temp); + +#ifdef DOSTORESTATE17 + SHA1_STORE_STATE(17) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(d, e, a, b, c, W, 17, temp); + +#ifdef DOSTORESTATE18 + SHA1_STORE_STATE(18) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(c, d, e, a, b, W, 18, temp); + +#ifdef DOSTORESTATE19 + SHA1_STORE_STATE(19) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(b, c, d, e, a, W, 19, temp); + + + +#ifdef DOSTORESTATE20 + SHA1_STORE_STATE(20) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 20, temp); + +#ifdef DOSTORESTATE21 + SHA1_STORE_STATE(21) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 21, temp); + +#ifdef DOSTORESTATE22 + SHA1_STORE_STATE(22) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 22, temp); + +#ifdef DOSTORESTATE23 + SHA1_STORE_STATE(23) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 23, temp); + +#ifdef DOSTORESTATE24 + SHA1_STORE_STATE(24) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 24, temp); + +#ifdef DOSTORESTATE25 + SHA1_STORE_STATE(25) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 25, temp); + +#ifdef DOSTORESTATE26 + SHA1_STORE_STATE(26) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 26, temp); + +#ifdef DOSTORESTATE27 + SHA1_STORE_STATE(27) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 27, temp); + +#ifdef DOSTORESTATE28 + SHA1_STORE_STATE(28) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 28, temp); + +#ifdef DOSTORESTATE29 + SHA1_STORE_STATE(29) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 29, temp); + +#ifdef DOSTORESTATE30 + SHA1_STORE_STATE(30) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 30, temp); + +#ifdef DOSTORESTATE31 + SHA1_STORE_STATE(31) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 31, temp); + +#ifdef DOSTORESTATE32 + SHA1_STORE_STATE(32) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 32, temp); + +#ifdef DOSTORESTATE33 + SHA1_STORE_STATE(33) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 33, temp); + +#ifdef DOSTORESTATE34 + SHA1_STORE_STATE(34) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 34, temp); + +#ifdef DOSTORESTATE35 + SHA1_STORE_STATE(35) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 35, temp); + +#ifdef DOSTORESTATE36 + SHA1_STORE_STATE(36) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 36, temp); + +#ifdef DOSTORESTATE37 + SHA1_STORE_STATE(37) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 37, temp); + +#ifdef DOSTORESTATE38 + SHA1_STORE_STATE(38) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 38, temp); + +#ifdef DOSTORESTATE39 + SHA1_STORE_STATE(39) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 39, temp); + + + +#ifdef DOSTORESTATE40 + SHA1_STORE_STATE(40) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 40, temp); + +#ifdef DOSTORESTATE41 + SHA1_STORE_STATE(41) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 41, temp); + +#ifdef DOSTORESTATE42 + SHA1_STORE_STATE(42) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 42, temp); + +#ifdef DOSTORESTATE43 + SHA1_STORE_STATE(43) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 43, temp); + +#ifdef DOSTORESTATE44 + SHA1_STORE_STATE(44) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 44, temp); + +#ifdef DOSTORESTATE45 + SHA1_STORE_STATE(45) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 45, temp); + +#ifdef DOSTORESTATE46 + SHA1_STORE_STATE(46) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 46, temp); + +#ifdef DOSTORESTATE47 + SHA1_STORE_STATE(47) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 47, temp); + +#ifdef DOSTORESTATE48 + SHA1_STORE_STATE(48) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 48, temp); + +#ifdef DOSTORESTATE49 + SHA1_STORE_STATE(49) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 49, temp); + +#ifdef DOSTORESTATE50 + SHA1_STORE_STATE(50) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 50, temp); + +#ifdef DOSTORESTATE51 + SHA1_STORE_STATE(51) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 51, temp); + +#ifdef DOSTORESTATE52 + SHA1_STORE_STATE(52) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 52, temp); + +#ifdef DOSTORESTATE53 + SHA1_STORE_STATE(53) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 53, temp); + +#ifdef DOSTORESTATE54 + SHA1_STORE_STATE(54) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 54, temp); + +#ifdef DOSTORESTATE55 + SHA1_STORE_STATE(55) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 55, temp); + +#ifdef DOSTORESTATE56 + SHA1_STORE_STATE(56) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 56, temp); + +#ifdef DOSTORESTATE57 + SHA1_STORE_STATE(57) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 57, temp); + +#ifdef DOSTORESTATE58 + SHA1_STORE_STATE(58) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 58, temp); + +#ifdef DOSTORESTATE59 + SHA1_STORE_STATE(59) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 59, temp); + + + + +#ifdef DOSTORESTATE60 + SHA1_STORE_STATE(60) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 60, temp); + +#ifdef DOSTORESTATE61 + SHA1_STORE_STATE(61) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 61, temp); + +#ifdef DOSTORESTATE62 + SHA1_STORE_STATE(62) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 62, temp); + +#ifdef DOSTORESTATE63 + SHA1_STORE_STATE(63) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 63, temp); + +#ifdef DOSTORESTATE64 + SHA1_STORE_STATE(64) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 64, temp); + +#ifdef DOSTORESTATE65 + SHA1_STORE_STATE(65) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 65, temp); + +#ifdef DOSTORESTATE66 + SHA1_STORE_STATE(66) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 66, temp); + +#ifdef DOSTORESTATE67 + SHA1_STORE_STATE(67) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 67, temp); + +#ifdef DOSTORESTATE68 + SHA1_STORE_STATE(68) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 68, temp); + +#ifdef DOSTORESTATE69 + SHA1_STORE_STATE(69) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 69, temp); + +#ifdef DOSTORESTATE70 + SHA1_STORE_STATE(70) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 70, temp); + +#ifdef DOSTORESTATE71 + SHA1_STORE_STATE(71) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 71, temp); + +#ifdef DOSTORESTATE72 + SHA1_STORE_STATE(72) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 72, temp); + +#ifdef DOSTORESTATE73 + SHA1_STORE_STATE(73) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 73, temp); + +#ifdef DOSTORESTATE74 + SHA1_STORE_STATE(74) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 74, temp); + +#ifdef DOSTORESTATE75 + SHA1_STORE_STATE(75) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 75, temp); + +#ifdef DOSTORESTATE76 + SHA1_STORE_STATE(76) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 76, temp); + +#ifdef DOSTORESTATE77 + SHA1_STORE_STATE(77) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 77, temp); + +#ifdef DOSTORESTATE78 + SHA1_STORE_STATE(78) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 78, temp); + +#ifdef DOSTORESTATE79 + SHA1_STORE_STATE(79) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 79, temp); + + + + ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; +} + + + + +#define SHA1_RECOMPRESS(t) \ +static void sha1recompress_fast_ ## t (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) \ +{ \ + uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; \ + if (t > 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 79); \ + if (t > 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 78); \ + if (t > 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 77); \ + if (t > 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 76); \ + if (t > 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 75); \ + if (t > 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 74); \ + if (t > 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 73); \ + if (t > 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 72); \ + if (t > 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 71); \ + if (t > 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 70); \ + if (t > 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 69); \ + if (t > 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 68); \ + if (t > 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 67); \ + if (t > 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 66); \ + if (t > 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 65); \ + if (t > 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 64); \ + if (t > 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 63); \ + if (t > 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 62); \ + if (t > 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 61); \ + if (t > 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 60); \ + if (t > 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 59); \ + if (t > 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 58); \ + if (t > 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 57); \ + if (t > 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 56); \ + if (t > 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 55); \ + if (t > 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 54); \ + if (t > 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 53); \ + if (t > 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 52); \ + if (t > 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 51); \ + if (t > 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 50); \ + if (t > 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 49); \ + if (t > 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 48); \ + if (t > 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 47); \ + if (t > 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 46); \ + if (t > 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 45); \ + if (t > 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 44); \ + if (t > 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 43); \ + if (t > 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 42); \ + if (t > 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 41); \ + if (t > 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 40); \ + if (t > 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 39); \ + if (t > 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 38); \ + if (t > 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 37); \ + if (t > 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 36); \ + if (t > 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 35); \ + if (t > 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 34); \ + if (t > 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 33); \ + if (t > 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 32); \ + if (t > 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 31); \ + if (t > 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 30); \ + if (t > 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 29); \ + if (t > 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 28); \ + if (t > 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 27); \ + if (t > 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 26); \ + if (t > 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 25); \ + if (t > 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 24); \ + if (t > 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 23); \ + if (t > 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 22); \ + if (t > 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 21); \ + if (t > 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 20); \ + if (t > 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 19); \ + if (t > 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 18); \ + if (t > 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 17); \ + if (t > 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 16); \ + if (t > 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 15); \ + if (t > 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 14); \ + if (t > 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 13); \ + if (t > 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 12); \ + if (t > 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 11); \ + if (t > 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 10); \ + if (t > 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 9); \ + if (t > 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 8); \ + if (t > 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 7); \ + if (t > 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 6); \ + if (t > 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 5); \ + if (t > 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 4); \ + if (t > 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 3); \ + if (t > 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 2); \ + if (t > 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 1); \ + if (t > 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 0); \ + ihvin[0] = a; ihvin[1] = b; ihvin[2] = c; ihvin[3] = d; ihvin[4] = e; \ + a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; \ + if (t <= 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 0); \ + if (t <= 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 1); \ + if (t <= 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 2); \ + if (t <= 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 3); \ + if (t <= 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 4); \ + if (t <= 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 5); \ + if (t <= 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 6); \ + if (t <= 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 7); \ + if (t <= 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 8); \ + if (t <= 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 9); \ + if (t <= 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 10); \ + if (t <= 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 11); \ + if (t <= 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 12); \ + if (t <= 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 13); \ + if (t <= 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 14); \ + if (t <= 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 15); \ + if (t <= 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 16); \ + if (t <= 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 17); \ + if (t <= 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 18); \ + if (t <= 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 19); \ + if (t <= 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 20); \ + if (t <= 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 21); \ + if (t <= 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 22); \ + if (t <= 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 23); \ + if (t <= 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 24); \ + if (t <= 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 25); \ + if (t <= 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 26); \ + if (t <= 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 27); \ + if (t <= 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 28); \ + if (t <= 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 29); \ + if (t <= 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 30); \ + if (t <= 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 31); \ + if (t <= 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 32); \ + if (t <= 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 33); \ + if (t <= 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 34); \ + if (t <= 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 35); \ + if (t <= 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 36); \ + if (t <= 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 37); \ + if (t <= 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 38); \ + if (t <= 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 39); \ + if (t <= 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 40); \ + if (t <= 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 41); \ + if (t <= 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 42); \ + if (t <= 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 43); \ + if (t <= 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 44); \ + if (t <= 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 45); \ + if (t <= 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 46); \ + if (t <= 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 47); \ + if (t <= 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 48); \ + if (t <= 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 49); \ + if (t <= 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 50); \ + if (t <= 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 51); \ + if (t <= 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 52); \ + if (t <= 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 53); \ + if (t <= 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 54); \ + if (t <= 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 55); \ + if (t <= 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 56); \ + if (t <= 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 57); \ + if (t <= 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 58); \ + if (t <= 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 59); \ + if (t <= 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 60); \ + if (t <= 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 61); \ + if (t <= 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 62); \ + if (t <= 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 63); \ + if (t <= 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 64); \ + if (t <= 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 65); \ + if (t <= 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 66); \ + if (t <= 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 67); \ + if (t <= 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 68); \ + if (t <= 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 69); \ + if (t <= 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 70); \ + if (t <= 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 71); \ + if (t <= 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 72); \ + if (t <= 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 73); \ + if (t <= 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 74); \ + if (t <= 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 75); \ + if (t <= 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 76); \ + if (t <= 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 77); \ + if (t <= 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 78); \ + if (t <= 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 79); \ + ihvout[0] = ihvin[0] + a; ihvout[1] = ihvin[1] + b; ihvout[2] = ihvin[2] + c; ihvout[3] = ihvin[3] + d; ihvout[4] = ihvin[4] + e; \ +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4127) /* Compiler complains about the checks in the above macro being constant. */ +#endif + +#ifdef DOSTORESTATE0 +SHA1_RECOMPRESS(0) +#endif + +#ifdef DOSTORESTATE1 +SHA1_RECOMPRESS(1) +#endif + +#ifdef DOSTORESTATE2 +SHA1_RECOMPRESS(2) +#endif + +#ifdef DOSTORESTATE3 +SHA1_RECOMPRESS(3) +#endif + +#ifdef DOSTORESTATE4 +SHA1_RECOMPRESS(4) +#endif + +#ifdef DOSTORESTATE5 +SHA1_RECOMPRESS(5) +#endif + +#ifdef DOSTORESTATE6 +SHA1_RECOMPRESS(6) +#endif + +#ifdef DOSTORESTATE7 +SHA1_RECOMPRESS(7) +#endif + +#ifdef DOSTORESTATE8 +SHA1_RECOMPRESS(8) +#endif + +#ifdef DOSTORESTATE9 +SHA1_RECOMPRESS(9) +#endif + +#ifdef DOSTORESTATE10 +SHA1_RECOMPRESS(10) +#endif + +#ifdef DOSTORESTATE11 +SHA1_RECOMPRESS(11) +#endif + +#ifdef DOSTORESTATE12 +SHA1_RECOMPRESS(12) +#endif + +#ifdef DOSTORESTATE13 +SHA1_RECOMPRESS(13) +#endif + +#ifdef DOSTORESTATE14 +SHA1_RECOMPRESS(14) +#endif + +#ifdef DOSTORESTATE15 +SHA1_RECOMPRESS(15) +#endif + +#ifdef DOSTORESTATE16 +SHA1_RECOMPRESS(16) +#endif + +#ifdef DOSTORESTATE17 +SHA1_RECOMPRESS(17) +#endif + +#ifdef DOSTORESTATE18 +SHA1_RECOMPRESS(18) +#endif + +#ifdef DOSTORESTATE19 +SHA1_RECOMPRESS(19) +#endif + +#ifdef DOSTORESTATE20 +SHA1_RECOMPRESS(20) +#endif + +#ifdef DOSTORESTATE21 +SHA1_RECOMPRESS(21) +#endif + +#ifdef DOSTORESTATE22 +SHA1_RECOMPRESS(22) +#endif + +#ifdef DOSTORESTATE23 +SHA1_RECOMPRESS(23) +#endif + +#ifdef DOSTORESTATE24 +SHA1_RECOMPRESS(24) +#endif + +#ifdef DOSTORESTATE25 +SHA1_RECOMPRESS(25) +#endif + +#ifdef DOSTORESTATE26 +SHA1_RECOMPRESS(26) +#endif + +#ifdef DOSTORESTATE27 +SHA1_RECOMPRESS(27) +#endif + +#ifdef DOSTORESTATE28 +SHA1_RECOMPRESS(28) +#endif + +#ifdef DOSTORESTATE29 +SHA1_RECOMPRESS(29) +#endif + +#ifdef DOSTORESTATE30 +SHA1_RECOMPRESS(30) +#endif + +#ifdef DOSTORESTATE31 +SHA1_RECOMPRESS(31) +#endif + +#ifdef DOSTORESTATE32 +SHA1_RECOMPRESS(32) +#endif + +#ifdef DOSTORESTATE33 +SHA1_RECOMPRESS(33) +#endif + +#ifdef DOSTORESTATE34 +SHA1_RECOMPRESS(34) +#endif + +#ifdef DOSTORESTATE35 +SHA1_RECOMPRESS(35) +#endif + +#ifdef DOSTORESTATE36 +SHA1_RECOMPRESS(36) +#endif + +#ifdef DOSTORESTATE37 +SHA1_RECOMPRESS(37) +#endif + +#ifdef DOSTORESTATE38 +SHA1_RECOMPRESS(38) +#endif + +#ifdef DOSTORESTATE39 +SHA1_RECOMPRESS(39) +#endif + +#ifdef DOSTORESTATE40 +SHA1_RECOMPRESS(40) +#endif + +#ifdef DOSTORESTATE41 +SHA1_RECOMPRESS(41) +#endif + +#ifdef DOSTORESTATE42 +SHA1_RECOMPRESS(42) +#endif + +#ifdef DOSTORESTATE43 +SHA1_RECOMPRESS(43) +#endif + +#ifdef DOSTORESTATE44 +SHA1_RECOMPRESS(44) +#endif + +#ifdef DOSTORESTATE45 +SHA1_RECOMPRESS(45) +#endif + +#ifdef DOSTORESTATE46 +SHA1_RECOMPRESS(46) +#endif + +#ifdef DOSTORESTATE47 +SHA1_RECOMPRESS(47) +#endif + +#ifdef DOSTORESTATE48 +SHA1_RECOMPRESS(48) +#endif + +#ifdef DOSTORESTATE49 +SHA1_RECOMPRESS(49) +#endif + +#ifdef DOSTORESTATE50 +SHA1_RECOMPRESS(50) +#endif + +#ifdef DOSTORESTATE51 +SHA1_RECOMPRESS(51) +#endif + +#ifdef DOSTORESTATE52 +SHA1_RECOMPRESS(52) +#endif + +#ifdef DOSTORESTATE53 +SHA1_RECOMPRESS(53) +#endif + +#ifdef DOSTORESTATE54 +SHA1_RECOMPRESS(54) +#endif + +#ifdef DOSTORESTATE55 +SHA1_RECOMPRESS(55) +#endif + +#ifdef DOSTORESTATE56 +SHA1_RECOMPRESS(56) +#endif + +#ifdef DOSTORESTATE57 +SHA1_RECOMPRESS(57) +#endif + +#ifdef DOSTORESTATE58 +SHA1_RECOMPRESS(58) +#endif + +#ifdef DOSTORESTATE59 +SHA1_RECOMPRESS(59) +#endif + +#ifdef DOSTORESTATE60 +SHA1_RECOMPRESS(60) +#endif + +#ifdef DOSTORESTATE61 +SHA1_RECOMPRESS(61) +#endif + +#ifdef DOSTORESTATE62 +SHA1_RECOMPRESS(62) +#endif + +#ifdef DOSTORESTATE63 +SHA1_RECOMPRESS(63) +#endif + +#ifdef DOSTORESTATE64 +SHA1_RECOMPRESS(64) +#endif + +#ifdef DOSTORESTATE65 +SHA1_RECOMPRESS(65) +#endif + +#ifdef DOSTORESTATE66 +SHA1_RECOMPRESS(66) +#endif + +#ifdef DOSTORESTATE67 +SHA1_RECOMPRESS(67) +#endif + +#ifdef DOSTORESTATE68 +SHA1_RECOMPRESS(68) +#endif + +#ifdef DOSTORESTATE69 +SHA1_RECOMPRESS(69) +#endif + +#ifdef DOSTORESTATE70 +SHA1_RECOMPRESS(70) +#endif + +#ifdef DOSTORESTATE71 +SHA1_RECOMPRESS(71) +#endif + +#ifdef DOSTORESTATE72 +SHA1_RECOMPRESS(72) +#endif + +#ifdef DOSTORESTATE73 +SHA1_RECOMPRESS(73) +#endif + +#ifdef DOSTORESTATE74 +SHA1_RECOMPRESS(74) +#endif + +#ifdef DOSTORESTATE75 +SHA1_RECOMPRESS(75) +#endif + +#ifdef DOSTORESTATE76 +SHA1_RECOMPRESS(76) +#endif + +#ifdef DOSTORESTATE77 +SHA1_RECOMPRESS(77) +#endif + +#ifdef DOSTORESTATE78 +SHA1_RECOMPRESS(78) +#endif + +#ifdef DOSTORESTATE79 +SHA1_RECOMPRESS(79) +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +static void sha1_recompression_step(uint32_t step, uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) +{ + switch (step) + { +#ifdef DOSTORESTATE0 + case 0: + sha1recompress_fast_0(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE1 + case 1: + sha1recompress_fast_1(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE2 + case 2: + sha1recompress_fast_2(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE3 + case 3: + sha1recompress_fast_3(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE4 + case 4: + sha1recompress_fast_4(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE5 + case 5: + sha1recompress_fast_5(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE6 + case 6: + sha1recompress_fast_6(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE7 + case 7: + sha1recompress_fast_7(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE8 + case 8: + sha1recompress_fast_8(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE9 + case 9: + sha1recompress_fast_9(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE10 + case 10: + sha1recompress_fast_10(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE11 + case 11: + sha1recompress_fast_11(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE12 + case 12: + sha1recompress_fast_12(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE13 + case 13: + sha1recompress_fast_13(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE14 + case 14: + sha1recompress_fast_14(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE15 + case 15: + sha1recompress_fast_15(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE16 + case 16: + sha1recompress_fast_16(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE17 + case 17: + sha1recompress_fast_17(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE18 + case 18: + sha1recompress_fast_18(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE19 + case 19: + sha1recompress_fast_19(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE20 + case 20: + sha1recompress_fast_20(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE21 + case 21: + sha1recompress_fast_21(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE22 + case 22: + sha1recompress_fast_22(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE23 + case 23: + sha1recompress_fast_23(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE24 + case 24: + sha1recompress_fast_24(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE25 + case 25: + sha1recompress_fast_25(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE26 + case 26: + sha1recompress_fast_26(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE27 + case 27: + sha1recompress_fast_27(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE28 + case 28: + sha1recompress_fast_28(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE29 + case 29: + sha1recompress_fast_29(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE30 + case 30: + sha1recompress_fast_30(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE31 + case 31: + sha1recompress_fast_31(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE32 + case 32: + sha1recompress_fast_32(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE33 + case 33: + sha1recompress_fast_33(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE34 + case 34: + sha1recompress_fast_34(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE35 + case 35: + sha1recompress_fast_35(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE36 + case 36: + sha1recompress_fast_36(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE37 + case 37: + sha1recompress_fast_37(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE38 + case 38: + sha1recompress_fast_38(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE39 + case 39: + sha1recompress_fast_39(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE40 + case 40: + sha1recompress_fast_40(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE41 + case 41: + sha1recompress_fast_41(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE42 + case 42: + sha1recompress_fast_42(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE43 + case 43: + sha1recompress_fast_43(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE44 + case 44: + sha1recompress_fast_44(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE45 + case 45: + sha1recompress_fast_45(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE46 + case 46: + sha1recompress_fast_46(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE47 + case 47: + sha1recompress_fast_47(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE48 + case 48: + sha1recompress_fast_48(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE49 + case 49: + sha1recompress_fast_49(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE50 + case 50: + sha1recompress_fast_50(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE51 + case 51: + sha1recompress_fast_51(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE52 + case 52: + sha1recompress_fast_52(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE53 + case 53: + sha1recompress_fast_53(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE54 + case 54: + sha1recompress_fast_54(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE55 + case 55: + sha1recompress_fast_55(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE56 + case 56: + sha1recompress_fast_56(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE57 + case 57: + sha1recompress_fast_57(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE58 + case 58: + sha1recompress_fast_58(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE59 + case 59: + sha1recompress_fast_59(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE60 + case 60: + sha1recompress_fast_60(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE61 + case 61: + sha1recompress_fast_61(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE62 + case 62: + sha1recompress_fast_62(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE63 + case 63: + sha1recompress_fast_63(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE64 + case 64: + sha1recompress_fast_64(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE65 + case 65: + sha1recompress_fast_65(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE66 + case 66: + sha1recompress_fast_66(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE67 + case 67: + sha1recompress_fast_67(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE68 + case 68: + sha1recompress_fast_68(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE69 + case 69: + sha1recompress_fast_69(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE70 + case 70: + sha1recompress_fast_70(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE71 + case 71: + sha1recompress_fast_71(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE72 + case 72: + sha1recompress_fast_72(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE73 + case 73: + sha1recompress_fast_73(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE74 + case 74: + sha1recompress_fast_74(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE75 + case 75: + sha1recompress_fast_75(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE76 + case 76: + sha1recompress_fast_76(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE77 + case 77: + sha1recompress_fast_77(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE78 + case 78: + sha1recompress_fast_78(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE79 + case 79: + sha1recompress_fast_79(ihvin, ihvout, me2, state); + break; +#endif + default: + abort(); + } + +} + + + +static void sha1_process(SHA1_CTX *ctx, const uint32_t block[16]) +{ + unsigned i, j; + uint32_t ubc_dv_mask[DVMASKSIZE] = { 0xFFFFFFFF }; + uint32_t ihvtmp[5]; + + ctx->ihv1[0] = ctx->ihv[0]; + ctx->ihv1[1] = ctx->ihv[1]; + ctx->ihv1[2] = ctx->ihv[2]; + ctx->ihv1[3] = ctx->ihv[3]; + ctx->ihv1[4] = ctx->ihv[4]; + + sha1_compression_states(ctx->ihv, block, ctx->m1, ctx->states); + + if (ctx->detect_coll) + { + if (ctx->ubc_check) + { + ubc_check(ctx->m1, ubc_dv_mask); + } + + if (ubc_dv_mask[0] != 0) + { + for (i = 0; sha1_dvs[i].dvType != 0; ++i) + { + if (ubc_dv_mask[0] & ((uint32_t)(1) << sha1_dvs[i].maskb)) + { + for (j = 0; j < 80; ++j) + ctx->m2[j] = ctx->m1[j] ^ sha1_dvs[i].dm[j]; + + sha1_recompression_step(sha1_dvs[i].testt, ctx->ihv2, ihvtmp, ctx->m2, ctx->states[sha1_dvs[i].testt]); + + /* to verify SHA-1 collision detection code with collisions for reduced-step SHA-1 */ + if ((0 == ((ihvtmp[0] ^ ctx->ihv[0]) | (ihvtmp[1] ^ ctx->ihv[1]) | (ihvtmp[2] ^ ctx->ihv[2]) | (ihvtmp[3] ^ ctx->ihv[3]) | (ihvtmp[4] ^ ctx->ihv[4]))) + || (ctx->reduced_round_coll && 0==((ctx->ihv1[0] ^ ctx->ihv2[0]) | (ctx->ihv1[1] ^ ctx->ihv2[1]) | (ctx->ihv1[2] ^ ctx->ihv2[2]) | (ctx->ihv1[3] ^ ctx->ihv2[3]) | (ctx->ihv1[4] ^ ctx->ihv2[4])))) + { + ctx->found_collision = 1; + + if (ctx->safe_hash) + { + sha1_compression_W(ctx->ihv, ctx->m1); + sha1_compression_W(ctx->ihv, ctx->m1); + } + + break; + } + } + } + } + } +} + +void SHA1DCInit(SHA1_CTX *ctx) +{ + ctx->total = 0; + ctx->ihv[0] = 0x67452301; + ctx->ihv[1] = 0xEFCDAB89; + ctx->ihv[2] = 0x98BADCFE; + ctx->ihv[3] = 0x10325476; + ctx->ihv[4] = 0xC3D2E1F0; + ctx->found_collision = 0; + ctx->safe_hash = SHA1DC_INIT_SAFE_HASH_DEFAULT; + ctx->ubc_check = 1; + ctx->detect_coll = 1; + ctx->reduced_round_coll = 0; + ctx->callback = NULL; +} + +void SHA1DCSetSafeHash(SHA1_CTX *ctx, int safehash) +{ + if (safehash) + ctx->safe_hash = 1; + else + ctx->safe_hash = 0; +} + + +void SHA1DCSetUseUBC(SHA1_CTX *ctx, int ubc_check) +{ + if (ubc_check) + ctx->ubc_check = 1; + else + ctx->ubc_check = 0; +} + +void SHA1DCSetUseDetectColl(SHA1_CTX *ctx, int detect_coll) +{ + if (detect_coll) + ctx->detect_coll = 1; + else + ctx->detect_coll = 0; +} + +void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX *ctx, int reduced_round_coll) +{ + if (reduced_round_coll) + ctx->reduced_round_coll = 1; + else + ctx->reduced_round_coll = 0; +} + +void SHA1DCSetCallback(SHA1_CTX *ctx, collision_block_callback callback) +{ + ctx->callback = callback; +} + +void SHA1DCUpdate(SHA1_CTX *ctx, const char *buf, size_t len) +{ + unsigned left, fill; + + if (len == 0) + return; + + left = ctx->total & 63; + fill = 64 - left; + + if (left && len >= fill) + { + ctx->total += fill; + memcpy(ctx->buffer + left, buf, fill); + sha1_process(ctx, (uint32_t*)(ctx->buffer)); + buf += fill; + len -= fill; + left = 0; + } + while (len >= 64) + { + ctx->total += 64; + +#if defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) + sha1_process(ctx, (uint32_t*)(buf)); +#else + memcpy(ctx->buffer, buf, 64); + sha1_process(ctx, (uint32_t*)(ctx->buffer)); +#endif /* defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) */ + buf += 64; + len -= 64; + } + if (len > 0) + { + ctx->total += len; + memcpy(ctx->buffer + left, buf, len); + } +} + +static const unsigned char sha1_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +int SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx) +{ + uint32_t last = ctx->total & 63; + uint32_t padn = (last < 56) ? (56 - last) : (120 - last); + uint64_t total; + SHA1DCUpdate(ctx, (const char*)(sha1_padding), padn); + + total = ctx->total - padn; + total <<= 3; + ctx->buffer[56] = (unsigned char)(total >> 56); + ctx->buffer[57] = (unsigned char)(total >> 48); + ctx->buffer[58] = (unsigned char)(total >> 40); + ctx->buffer[59] = (unsigned char)(total >> 32); + ctx->buffer[60] = (unsigned char)(total >> 24); + ctx->buffer[61] = (unsigned char)(total >> 16); + ctx->buffer[62] = (unsigned char)(total >> 8); + ctx->buffer[63] = (unsigned char)(total); + sha1_process(ctx, (uint32_t*)(ctx->buffer)); + output[0] = (unsigned char)(ctx->ihv[0] >> 24); + output[1] = (unsigned char)(ctx->ihv[0] >> 16); + output[2] = (unsigned char)(ctx->ihv[0] >> 8); + output[3] = (unsigned char)(ctx->ihv[0]); + output[4] = (unsigned char)(ctx->ihv[1] >> 24); + output[5] = (unsigned char)(ctx->ihv[1] >> 16); + output[6] = (unsigned char)(ctx->ihv[1] >> 8); + output[7] = (unsigned char)(ctx->ihv[1]); + output[8] = (unsigned char)(ctx->ihv[2] >> 24); + output[9] = (unsigned char)(ctx->ihv[2] >> 16); + output[10] = (unsigned char)(ctx->ihv[2] >> 8); + output[11] = (unsigned char)(ctx->ihv[2]); + output[12] = (unsigned char)(ctx->ihv[3] >> 24); + output[13] = (unsigned char)(ctx->ihv[3] >> 16); + output[14] = (unsigned char)(ctx->ihv[3] >> 8); + output[15] = (unsigned char)(ctx->ihv[3]); + output[16] = (unsigned char)(ctx->ihv[4] >> 24); + output[17] = (unsigned char)(ctx->ihv[4] >> 16); + output[18] = (unsigned char)(ctx->ihv[4] >> 8); + output[19] = (unsigned char)(ctx->ihv[4]); + return ctx->found_collision; +} + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#endif diff --git a/src/libgit2/hash/sha1/sha1dc/sha1.h b/src/libgit2/hash/sha1/sha1dc/sha1.h new file mode 100644 index 000000000..1e4e94be5 --- /dev/null +++ b/src/libgit2/hash/sha1/sha1dc/sha1.h @@ -0,0 +1,110 @@ +/*** +* Copyright 2017 Marc Stevens , Dan Shumow +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +#ifndef SHA1DC_SHA1_H +#define SHA1DC_SHA1_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include +#endif + +/* sha-1 compression function that takes an already expanded message, and additionally store intermediate states */ +/* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is defined in ubc_check.h */ +void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]); + +/* +// Function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]). +// Where 0 <= T < 80 +// me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference.) +// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block. +// The function will return: +// ihvin: The reconstructed input chaining value. +// ihvout: The reconstructed output chaining value. +*/ +typedef void(*sha1_recompression_type)(uint32_t*, uint32_t*, const uint32_t*, const uint32_t*); + +/* A callback function type that can be set to be called when a collision block has been found: */ +/* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */ +typedef void(*collision_block_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); + +/* The SHA-1 context. */ +typedef struct { + uint64_t total; + uint32_t ihv[5]; + unsigned char buffer[64]; + int found_collision; + int safe_hash; + int detect_coll; + int ubc_check; + int reduced_round_coll; + collision_block_callback callback; + + uint32_t ihv1[5]; + uint32_t ihv2[5]; + uint32_t m1[80]; + uint32_t m2[80]; + uint32_t states[80][5]; +} SHA1_CTX; + +/* Initialize SHA-1 context. */ +void SHA1DCInit(SHA1_CTX*); + +/* + Function to enable safe SHA-1 hashing: + Collision attacks are thwarted by hashing a detected near-collision block 3 times. + Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: + The best collision attacks against SHA-1 have complexity about 2^60, + thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be 2^180. + An attacker would be better off using a generic birthday search of complexity 2^80. + + Enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected, + but it will result in a different SHA-1 hash for messages where a collision attack was detected. + This will automatically invalidate SHA-1 based digital signature forgeries. + Enabled by default. +*/ +void SHA1DCSetSafeHash(SHA1_CTX*, int); + +/* + Function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up). + Enabled by default + */ +void SHA1DCSetUseUBC(SHA1_CTX*, int); + +/* + Function to disable or enable the use of Collision Detection. + Enabled by default. + */ +void SHA1DCSetUseDetectColl(SHA1_CTX*, int); + +/* function to disable or enable the detection of reduced-round SHA-1 collisions */ +/* disabled by default */ +void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX*, int); + +/* function to set a callback function, pass NULL to disable */ +/* by default no callback set */ +void SHA1DCSetCallback(SHA1_CTX*, collision_block_callback); + +/* update SHA-1 context with buffer contents */ +void SHA1DCUpdate(SHA1_CTX*, const char*, size_t); + +/* obtain SHA-1 hash from SHA-1 context */ +/* returns: 0 = no collision detected, otherwise = collision found => warn user for active attack */ +int SHA1DCFinal(unsigned char[20], SHA1_CTX*); + +#if defined(__cplusplus) +} +#endif + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#endif + +#endif diff --git a/src/libgit2/hash/sha1/sha1dc/ubc_check.c b/src/libgit2/hash/sha1/sha1dc/ubc_check.c new file mode 100644 index 000000000..b3beff2af --- /dev/null +++ b/src/libgit2/hash/sha1/sha1dc/ubc_check.c @@ -0,0 +1,372 @@ +/*** +* Copyright 2017 Marc Stevens , Dan Shumow +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +/* +// this file was generated by the 'parse_bitrel' program in the tools section +// using the data files from directory 'tools/data/3565' +// +// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check +// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) +// dm[80] is the expanded message block XOR-difference defined by the DV +// testt is the step to do the recompression from for collision detection +// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check +// +// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs +// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met +// thus one needs to do the recompression check for each DV that has its bit set +// +// ubc_check is programmatically generated and the unavoidable bitconditions have been hardcoded +// a directly verifiable version named ubc_check_verify can be found in ubc_check_verify.c +// ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in the tools section +*/ + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include +#endif +#ifdef SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#endif +#include "ubc_check.h" + +static const uint32_t DV_I_43_0_bit = (uint32_t)(1) << 0; +static const uint32_t DV_I_44_0_bit = (uint32_t)(1) << 1; +static const uint32_t DV_I_45_0_bit = (uint32_t)(1) << 2; +static const uint32_t DV_I_46_0_bit = (uint32_t)(1) << 3; +static const uint32_t DV_I_46_2_bit = (uint32_t)(1) << 4; +static const uint32_t DV_I_47_0_bit = (uint32_t)(1) << 5; +static const uint32_t DV_I_47_2_bit = (uint32_t)(1) << 6; +static const uint32_t DV_I_48_0_bit = (uint32_t)(1) << 7; +static const uint32_t DV_I_48_2_bit = (uint32_t)(1) << 8; +static const uint32_t DV_I_49_0_bit = (uint32_t)(1) << 9; +static const uint32_t DV_I_49_2_bit = (uint32_t)(1) << 10; +static const uint32_t DV_I_50_0_bit = (uint32_t)(1) << 11; +static const uint32_t DV_I_50_2_bit = (uint32_t)(1) << 12; +static const uint32_t DV_I_51_0_bit = (uint32_t)(1) << 13; +static const uint32_t DV_I_51_2_bit = (uint32_t)(1) << 14; +static const uint32_t DV_I_52_0_bit = (uint32_t)(1) << 15; +static const uint32_t DV_II_45_0_bit = (uint32_t)(1) << 16; +static const uint32_t DV_II_46_0_bit = (uint32_t)(1) << 17; +static const uint32_t DV_II_46_2_bit = (uint32_t)(1) << 18; +static const uint32_t DV_II_47_0_bit = (uint32_t)(1) << 19; +static const uint32_t DV_II_48_0_bit = (uint32_t)(1) << 20; +static const uint32_t DV_II_49_0_bit = (uint32_t)(1) << 21; +static const uint32_t DV_II_49_2_bit = (uint32_t)(1) << 22; +static const uint32_t DV_II_50_0_bit = (uint32_t)(1) << 23; +static const uint32_t DV_II_50_2_bit = (uint32_t)(1) << 24; +static const uint32_t DV_II_51_0_bit = (uint32_t)(1) << 25; +static const uint32_t DV_II_51_2_bit = (uint32_t)(1) << 26; +static const uint32_t DV_II_52_0_bit = (uint32_t)(1) << 27; +static const uint32_t DV_II_53_0_bit = (uint32_t)(1) << 28; +static const uint32_t DV_II_54_0_bit = (uint32_t)(1) << 29; +static const uint32_t DV_II_55_0_bit = (uint32_t)(1) << 30; +static const uint32_t DV_II_56_0_bit = (uint32_t)(1) << 31; + +dv_info_t sha1_dvs[] = +{ + {1,43,0,58,0,0, { 0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161,0x80000599 } } +, {1,44,0,58,0,1, { 0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161 } } +, {1,45,0,58,0,2, { 0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803 } } +, {1,46,0,58,0,3, { 0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c } } +, {1,46,2,58,0,4, { 0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a,0x00000132 } } +, {1,47,0,58,0,5, { 0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6 } } +, {1,47,2,58,0,6, { 0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a } } +, {1,48,0,58,0,7, { 0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408 } } +, {1,48,2,58,0,8, { 0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020 } } +, {1,49,0,58,0,9, { 0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164 } } +, {1,49,2,58,0,10, { 0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590 } } +, {1,50,0,65,0,11, { 0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018 } } +, {1,50,2,65,0,12, { 0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060 } } +, {1,51,0,65,0,13, { 0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202 } } +, {1,51,2,65,0,14, { 0xa0000003,0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a } } +, {1,52,0,65,0,15, { 0x04000010,0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012 } } +, {2,45,0,58,0,16, { 0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054,0x00000967 } } +, {2,46,0,58,0,17, { 0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054 } } +, {2,46,2,58,0,18, { 0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6,0x0000106a,0x00000b90,0x00000152 } } +, {2,47,0,58,0,19, { 0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4 } } +, {2,48,0,58,0,20, { 0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a } } +, {2,49,0,58,0,21, { 0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d } } +, {2,49,2,58,0,22, { 0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6 } } +, {2,50,0,65,0,23, { 0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b } } +, {2,50,2,65,0,24, { 0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c } } +, {2,51,0,65,0,25, { 0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b } } +, {2,51,2,65,0,26, { 0x00000043,0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e } } +, {2,52,0,65,0,27, { 0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014 } } +, {2,53,0,65,0,28, { 0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089 } } +, {2,54,0,65,0,29, { 0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107 } } +, {2,55,0,65,0,30, { 0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b } } +, {2,56,0,65,0,31, { 0x2600001a,0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046 } } +, {0,0,0,0,0,0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}} +}; +void ubc_check(const uint32_t W[80], uint32_t dvmask[1]) +{ + uint32_t mask = ~((uint32_t)(0)); + mask &= (((((W[44]^W[45])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_I_51_0_bit|DV_I_52_0_bit|DV_II_45_0_bit|DV_II_46_0_bit|DV_II_50_0_bit|DV_II_51_0_bit)); + mask &= (((((W[49]^W[50])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_II_45_0_bit|DV_II_50_0_bit|DV_II_51_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); + mask &= (((((W[48]^W[49])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_52_0_bit|DV_II_49_0_bit|DV_II_50_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); + mask &= ((((W[47]^(W[50]>>25))&(1<<4))-(1<<4)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); + mask &= (((((W[47]^W[48])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_51_0_bit|DV_II_48_0_bit|DV_II_49_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); + mask &= (((((W[46]>>4)^(W[49]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_50_0_bit|DV_II_55_0_bit)); + mask &= (((((W[46]^W[47])>>29)&1)-1) | ~(DV_I_43_0_bit|DV_I_50_0_bit|DV_II_47_0_bit|DV_II_48_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); + mask &= (((((W[45]>>4)^(W[48]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_49_0_bit|DV_II_54_0_bit)); + mask &= (((((W[45]^W[46])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_51_0_bit|DV_II_52_0_bit)); + mask &= (((((W[44]>>4)^(W[47]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_53_0_bit)); + mask &= (((((W[43]>>4)^(W[46]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_52_0_bit)); + mask &= (((((W[43]^W[44])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_I_50_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_49_0_bit|DV_II_50_0_bit)); + mask &= (((((W[42]>>4)^(W[45]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_51_0_bit)); + mask &= (((((W[41]>>4)^(W[44]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_50_0_bit)); + mask &= (((((W[40]^W[41])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_47_0_bit|DV_I_48_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_56_0_bit)); + mask &= (((((W[54]^W[55])>>29)&1)-1) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_50_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); + mask &= (((((W[53]^W[54])>>29)&1)-1) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_49_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); + mask &= (((((W[52]^W[53])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); + mask &= ((((W[50]^(W[53]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_48_0_bit|DV_II_54_0_bit)); + mask &= (((((W[50]^W[51])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_II_46_0_bit|DV_II_51_0_bit|DV_II_52_0_bit|DV_II_56_0_bit)); + mask &= ((((W[49]^(W[52]>>25))&(1<<4))-(1<<4)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_47_0_bit|DV_II_53_0_bit)); + mask &= ((((W[48]^(W[51]>>25))&(1<<4))-(1<<4)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_52_0_bit)); + mask &= (((((W[42]^W[43])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); + mask &= (((((W[41]^W[42])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_48_0_bit)); + mask &= (((((W[40]>>4)^(W[43]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_50_0_bit|DV_II_49_0_bit|DV_II_56_0_bit)); + mask &= (((((W[39]>>4)^(W[42]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_49_0_bit|DV_II_48_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) + mask &= (((((W[38]>>4)^(W[41]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); + mask &= (((((W[37]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_47_0_bit|DV_II_46_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)) + mask &= (((((W[55]^W[56])>>29)&1)-1) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); + if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)) + mask &= ((((W[52]^(W[55]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)); + if (mask & (DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)) + mask &= ((((W[51]^(W[54]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)) + mask &= (((((W[51]^W[52])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); + if (mask & (DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)) + mask &= (((((W[36]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)); + if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)) + mask &= ((0-(((W[53]^W[56])>>29)&1)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); + if (mask & (DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)) + mask &= ((0-(((W[51]^W[54])>>29)&1)) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)); + if (mask & (DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)) + mask &= ((0-(((W[50]^W[52])>>29)&1)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)); + if (mask & (DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)) + mask &= ((0-(((W[49]^W[51])>>29)&1)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)); + if (mask & (DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)) + mask &= ((0-(((W[48]^W[50])>>29)&1)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)); + if (mask & (DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)) + mask &= ((0-(((W[47]^W[49])>>29)&1)) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)); + if (mask & (DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)) + mask &= ((0-(((W[46]^W[48])>>29)&1)) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)); + mask &= ((((W[45]^W[47])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit|DV_I_51_2_bit)); + if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)) + mask &= ((0-(((W[45]^W[47])>>29)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)); + mask &= (((((W[44]^W[46])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit|DV_I_50_2_bit)); + if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)) + mask &= ((0-(((W[44]^W[46])>>29)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)); + mask &= ((0-((W[41]^(W[42]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_II_46_2_bit|DV_II_51_2_bit)); + mask &= ((0-((W[40]^(W[41]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_51_2_bit|DV_II_50_2_bit)); + if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)) + mask &= ((0-(((W[40]^W[42])>>4)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)); + mask &= ((0-((W[39]^(W[40]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_50_2_bit|DV_II_49_2_bit)); + if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)) + mask &= ((0-(((W[39]^W[41])>>4)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) + mask &= ((0-(((W[38]^W[40])>>4)&1)) | ~(DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); + if (mask & (DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)) + mask &= ((0-(((W[37]^W[39])>>4)&1)) | ~(DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); + mask &= ((0-((W[36]^(W[37]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_50_2_bit|DV_II_46_2_bit)); + if (mask & (DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)) + mask &= (((((W[35]>>4)^(W[39]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)); + if (mask & (DV_I_48_0_bit|DV_II_48_0_bit)) + mask &= ((0-((W[63]^(W[64]>>5))&(1<<0))) | ~(DV_I_48_0_bit|DV_II_48_0_bit)); + if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) + mask &= ((0-((W[63]^(W[64]>>5))&(1<<1))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); + if (mask & (DV_I_47_0_bit|DV_II_47_0_bit)) + mask &= ((0-((W[62]^(W[63]>>5))&(1<<0))) | ~(DV_I_47_0_bit|DV_II_47_0_bit)); + if (mask & (DV_I_46_0_bit|DV_II_46_0_bit)) + mask &= ((0-((W[61]^(W[62]>>5))&(1<<0))) | ~(DV_I_46_0_bit|DV_II_46_0_bit)); + mask &= ((0-((W[61]^(W[62]>>5))&(1<<2))) | ~(DV_I_46_2_bit|DV_II_46_2_bit)); + if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) + mask &= ((0-((W[60]^(W[61]>>5))&(1<<0))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); + if (mask & (DV_II_51_0_bit|DV_II_54_0_bit)) + mask &= (((((W[58]^W[59])>>29)&1)-1) | ~(DV_II_51_0_bit|DV_II_54_0_bit)); + if (mask & (DV_II_50_0_bit|DV_II_53_0_bit)) + mask &= (((((W[57]^W[58])>>29)&1)-1) | ~(DV_II_50_0_bit|DV_II_53_0_bit)); + if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) + mask &= ((((W[56]^(W[59]>>25))&(1<<4))-(1<<4)) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); + if (mask & (DV_II_51_0_bit|DV_II_52_0_bit)) + mask &= ((0-(((W[56]^W[59])>>29)&1)) | ~(DV_II_51_0_bit|DV_II_52_0_bit)); + if (mask & (DV_II_49_0_bit|DV_II_52_0_bit)) + mask &= (((((W[56]^W[57])>>29)&1)-1) | ~(DV_II_49_0_bit|DV_II_52_0_bit)); + if (mask & (DV_II_51_0_bit|DV_II_53_0_bit)) + mask &= ((((W[55]^(W[58]>>25))&(1<<4))-(1<<4)) | ~(DV_II_51_0_bit|DV_II_53_0_bit)); + if (mask & (DV_II_50_0_bit|DV_II_52_0_bit)) + mask &= ((((W[54]^(W[57]>>25))&(1<<4))-(1<<4)) | ~(DV_II_50_0_bit|DV_II_52_0_bit)); + if (mask & (DV_II_49_0_bit|DV_II_51_0_bit)) + mask &= ((((W[53]^(W[56]>>25))&(1<<4))-(1<<4)) | ~(DV_II_49_0_bit|DV_II_51_0_bit)); + mask &= ((((W[51]^(W[50]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); + mask &= ((((W[48]^W[50])&(1<<6))-(1<<6)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); + if (mask & (DV_I_51_0_bit|DV_I_52_0_bit)) + mask &= ((0-(((W[48]^W[55])>>29)&1)) | ~(DV_I_51_0_bit|DV_I_52_0_bit)); + mask &= ((((W[47]^W[49])&(1<<6))-(1<<6)) | ~(DV_I_49_2_bit|DV_I_51_2_bit)); + mask &= ((((W[48]^(W[47]>>5))&(1<<1))-(1<<1)) | ~(DV_I_47_2_bit|DV_II_51_2_bit)); + mask &= ((((W[46]^W[48])&(1<<6))-(1<<6)) | ~(DV_I_48_2_bit|DV_I_50_2_bit)); + mask &= ((((W[47]^(W[46]>>5))&(1<<1))-(1<<1)) | ~(DV_I_46_2_bit|DV_II_50_2_bit)); + mask &= ((0-((W[44]^(W[45]>>5))&(1<<1))) | ~(DV_I_51_2_bit|DV_II_49_2_bit)); + mask &= ((((W[43]^W[45])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit)); + mask &= (((((W[42]^W[44])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit)); + mask &= ((((W[43]^(W[42]>>5))&(1<<1))-(1<<1)) | ~(DV_II_46_2_bit|DV_II_51_2_bit)); + mask &= ((((W[42]^(W[41]>>5))&(1<<1))-(1<<1)) | ~(DV_I_51_2_bit|DV_II_50_2_bit)); + mask &= ((((W[41]^(W[40]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_49_2_bit)); + if (mask & (DV_I_52_0_bit|DV_II_51_0_bit)) + mask &= ((((W[39]^(W[43]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_51_0_bit)); + if (mask & (DV_I_51_0_bit|DV_II_50_0_bit)) + mask &= ((((W[38]^(W[42]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_50_0_bit)); + if (mask & (DV_I_48_2_bit|DV_I_51_2_bit)) + mask &= ((0-((W[37]^(W[38]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_I_51_2_bit)); + if (mask & (DV_I_50_0_bit|DV_II_49_0_bit)) + mask &= ((((W[37]^(W[41]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_II_49_0_bit)); + if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) + mask &= ((0-((W[36]^W[38])&(1<<4))) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); + mask &= ((0-((W[35]^(W[36]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_49_2_bit)); + if (mask & (DV_I_51_0_bit|DV_II_47_0_bit)) + mask &= ((((W[35]^(W[39]>>25))&(1<<3))-(1<<3)) | ~(DV_I_51_0_bit|DV_II_47_0_bit)); +if (mask) { + + if (mask & DV_I_43_0_bit) + if ( + !((W[61]^(W[62]>>5)) & (1<<1)) + || !(!((W[59]^(W[63]>>25)) & (1<<5))) + || !((W[58]^(W[63]>>30)) & (1<<0)) + ) mask &= ~DV_I_43_0_bit; + if (mask & DV_I_44_0_bit) + if ( + !((W[62]^(W[63]>>5)) & (1<<1)) + || !(!((W[60]^(W[64]>>25)) & (1<<5))) + || !((W[59]^(W[64]>>30)) & (1<<0)) + ) mask &= ~DV_I_44_0_bit; + if (mask & DV_I_46_2_bit) + mask &= ((~((W[40]^W[42])>>2)) | ~DV_I_46_2_bit); + if (mask & DV_I_47_2_bit) + if ( + !((W[62]^(W[63]>>5)) & (1<<2)) + || !(!((W[41]^W[43]) & (1<<6))) + ) mask &= ~DV_I_47_2_bit; + if (mask & DV_I_48_2_bit) + if ( + !((W[63]^(W[64]>>5)) & (1<<2)) + || !(!((W[48]^(W[49]<<5)) & (1<<6))) + ) mask &= ~DV_I_48_2_bit; + if (mask & DV_I_49_2_bit) + if ( + !(!((W[49]^(W[50]<<5)) & (1<<6))) + || !((W[42]^W[50]) & (1<<1)) + || !(!((W[39]^(W[40]<<5)) & (1<<6))) + || !((W[38]^W[40]) & (1<<1)) + ) mask &= ~DV_I_49_2_bit; + if (mask & DV_I_50_0_bit) + mask &= ((((W[36]^W[37])<<7)) | ~DV_I_50_0_bit); + if (mask & DV_I_50_2_bit) + mask &= ((((W[43]^W[51])<<11)) | ~DV_I_50_2_bit); + if (mask & DV_I_51_0_bit) + mask &= ((((W[37]^W[38])<<9)) | ~DV_I_51_0_bit); + if (mask & DV_I_51_2_bit) + if ( + !(!((W[51]^(W[52]<<5)) & (1<<6))) + || !(!((W[49]^W[51]) & (1<<6))) + || !(!((W[37]^(W[37]>>5)) & (1<<1))) + || !(!((W[35]^(W[39]>>25)) & (1<<5))) + ) mask &= ~DV_I_51_2_bit; + if (mask & DV_I_52_0_bit) + mask &= ((((W[38]^W[39])<<11)) | ~DV_I_52_0_bit); + if (mask & DV_II_46_2_bit) + mask &= ((((W[47]^W[51])<<17)) | ~DV_II_46_2_bit); + if (mask & DV_II_48_0_bit) + if ( + !(!((W[36]^(W[40]>>25)) & (1<<3))) + || !((W[35]^(W[40]<<2)) & (1<<30)) + ) mask &= ~DV_II_48_0_bit; + if (mask & DV_II_49_0_bit) + if ( + !(!((W[37]^(W[41]>>25)) & (1<<3))) + || !((W[36]^(W[41]<<2)) & (1<<30)) + ) mask &= ~DV_II_49_0_bit; + if (mask & DV_II_49_2_bit) + if ( + !(!((W[53]^(W[54]<<5)) & (1<<6))) + || !(!((W[51]^W[53]) & (1<<6))) + || !((W[50]^W[54]) & (1<<1)) + || !(!((W[45]^(W[46]<<5)) & (1<<6))) + || !(!((W[37]^(W[41]>>25)) & (1<<5))) + || !((W[36]^(W[41]>>30)) & (1<<0)) + ) mask &= ~DV_II_49_2_bit; + if (mask & DV_II_50_0_bit) + if ( + !((W[55]^W[58]) & (1<<29)) + || !(!((W[38]^(W[42]>>25)) & (1<<3))) + || !((W[37]^(W[42]<<2)) & (1<<30)) + ) mask &= ~DV_II_50_0_bit; + if (mask & DV_II_50_2_bit) + if ( + !(!((W[54]^(W[55]<<5)) & (1<<6))) + || !(!((W[52]^W[54]) & (1<<6))) + || !((W[51]^W[55]) & (1<<1)) + || !((W[45]^W[47]) & (1<<1)) + || !(!((W[38]^(W[42]>>25)) & (1<<5))) + || !((W[37]^(W[42]>>30)) & (1<<0)) + ) mask &= ~DV_II_50_2_bit; + if (mask & DV_II_51_0_bit) + if ( + !(!((W[39]^(W[43]>>25)) & (1<<3))) + || !((W[38]^(W[43]<<2)) & (1<<30)) + ) mask &= ~DV_II_51_0_bit; + if (mask & DV_II_51_2_bit) + if ( + !(!((W[55]^(W[56]<<5)) & (1<<6))) + || !(!((W[53]^W[55]) & (1<<6))) + || !((W[52]^W[56]) & (1<<1)) + || !((W[46]^W[48]) & (1<<1)) + || !(!((W[39]^(W[43]>>25)) & (1<<5))) + || !((W[38]^(W[43]>>30)) & (1<<0)) + ) mask &= ~DV_II_51_2_bit; + if (mask & DV_II_52_0_bit) + if ( + !(!((W[59]^W[60]) & (1<<29))) + || !(!((W[40]^(W[44]>>25)) & (1<<3))) + || !(!((W[40]^(W[44]>>25)) & (1<<4))) + || !((W[39]^(W[44]<<2)) & (1<<30)) + ) mask &= ~DV_II_52_0_bit; + if (mask & DV_II_53_0_bit) + if ( + !((W[58]^W[61]) & (1<<29)) + || !(!((W[57]^(W[61]>>25)) & (1<<4))) + || !(!((W[41]^(W[45]>>25)) & (1<<3))) + || !(!((W[41]^(W[45]>>25)) & (1<<4))) + ) mask &= ~DV_II_53_0_bit; + if (mask & DV_II_54_0_bit) + if ( + !(!((W[58]^(W[62]>>25)) & (1<<4))) + || !(!((W[42]^(W[46]>>25)) & (1<<3))) + || !(!((W[42]^(W[46]>>25)) & (1<<4))) + ) mask &= ~DV_II_54_0_bit; + if (mask & DV_II_55_0_bit) + if ( + !(!((W[59]^(W[63]>>25)) & (1<<4))) + || !(!((W[57]^(W[59]>>25)) & (1<<4))) + || !(!((W[43]^(W[47]>>25)) & (1<<3))) + || !(!((W[43]^(W[47]>>25)) & (1<<4))) + ) mask &= ~DV_II_55_0_bit; + if (mask & DV_II_56_0_bit) + if ( + !(!((W[60]^(W[64]>>25)) & (1<<4))) + || !(!((W[44]^(W[48]>>25)) & (1<<3))) + || !(!((W[44]^(W[48]>>25)) & (1<<4))) + ) mask &= ~DV_II_56_0_bit; +} + + dvmask[0]=mask; +} + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#endif diff --git a/src/libgit2/hash/sha1/sha1dc/ubc_check.h b/src/libgit2/hash/sha1/sha1dc/ubc_check.h new file mode 100644 index 000000000..d7e17dc73 --- /dev/null +++ b/src/libgit2/hash/sha1/sha1dc/ubc_check.h @@ -0,0 +1,52 @@ +/*** +* Copyright 2017 Marc Stevens , Dan Shumow +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +/* +// this file was generated by the 'parse_bitrel' program in the tools section +// using the data files from directory 'tools/data/3565' +// +// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check +// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) +// dm[80] is the expanded message block XOR-difference defined by the DV +// testt is the step to do the recompression from for collision detection +// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check +// +// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs +// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met +// thus one needs to do the recompression check for each DV that has its bit set +*/ + +#ifndef SHA1DC_UBC_CHECK_H +#define SHA1DC_UBC_CHECK_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include +#endif + +#define DVMASKSIZE 1 +typedef struct { int dvType; int dvK; int dvB; int testt; int maski; int maskb; uint32_t dm[80]; } dv_info_t; +extern dv_info_t sha1_dvs[]; +void ubc_check(const uint32_t W[80], uint32_t dvmask[DVMASKSIZE]); + +#define DOSTORESTATE58 +#define DOSTORESTATE65 + +#define CHECK_DVMASK(_DVMASK) (0 != _DVMASK[0]) + +#if defined(__cplusplus) +} +#endif + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#endif + +#endif diff --git a/src/libgit2/hash/sha1/win32.c b/src/libgit2/hash/sha1/win32.c new file mode 100644 index 000000000..b89dfbad8 --- /dev/null +++ b/src/libgit2/hash/sha1/win32.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "win32.h" + +#include "runtime.h" + +#include +#include + +#define GIT_HASH_CNG_DLL_NAME "bcrypt.dll" + +/* BCRYPT_SHA1_ALGORITHM */ +#define GIT_HASH_CNG_HASH_TYPE L"SHA1" + +/* BCRYPT_OBJECT_LENGTH */ +#define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength" + +/* BCRYPT_HASH_REUSEABLE_FLAGS */ +#define GIT_HASH_CNG_HASH_REUSABLE 0x00000020 + +static git_hash_prov hash_prov = {0}; + +/* Hash initialization */ + +/* Initialize CNG, if available */ +GIT_INLINE(int) hash_cng_prov_init(void) +{ + char dll_path[MAX_PATH]; + DWORD dll_path_len, size_len; + + /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */ + if (!git_has_win32_version(6, 0, 1)) { + git_error_set(GIT_ERROR_SHA1, "CryptoNG is not supported on this platform"); + return -1; + } + + /* Load bcrypt.dll explicitly from the system directory */ + if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || + dll_path_len > MAX_PATH || + StringCchCat(dll_path, MAX_PATH, "\\") < 0 || + StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 || + (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) { + git_error_set(GIT_ERROR_SHA1, "CryptoNG library could not be loaded"); + return -1; + } + + /* Load the function addresses */ + if ((hash_prov.prov.cng.open_algorithm_provider = (hash_win32_cng_open_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptOpenAlgorithmProvider")) == NULL || + (hash_prov.prov.cng.get_property = (hash_win32_cng_get_property_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptGetProperty")) == NULL || + (hash_prov.prov.cng.create_hash = (hash_win32_cng_create_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCreateHash")) == NULL || + (hash_prov.prov.cng.finish_hash = (hash_win32_cng_finish_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptFinishHash")) == NULL || + (hash_prov.prov.cng.hash_data = (hash_win32_cng_hash_data_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptHashData")) == NULL || + (hash_prov.prov.cng.destroy_hash = (hash_win32_cng_destroy_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptDestroyHash")) == NULL || + (hash_prov.prov.cng.close_algorithm_provider = (hash_win32_cng_close_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCloseAlgorithmProvider")) == NULL) { + FreeLibrary(hash_prov.prov.cng.dll); + + git_error_set(GIT_ERROR_OS, "CryptoNG functions could not be loaded"); + return -1; + } + + /* Load the SHA1 algorithm */ + if (hash_prov.prov.cng.open_algorithm_provider(&hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0) { + FreeLibrary(hash_prov.prov.cng.dll); + + git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized"); + return -1; + } + + /* Get storage space for the hash object */ + if (hash_prov.prov.cng.get_property(hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_prov.prov.cng.hash_object_size, sizeof(DWORD), &size_len, 0) < 0) { + hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); + FreeLibrary(hash_prov.prov.cng.dll); + + git_error_set(GIT_ERROR_OS, "algorithm handle could not be found"); + return -1; + } + + hash_prov.type = CNG; + return 0; +} + +GIT_INLINE(void) hash_cng_prov_shutdown(void) +{ + hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); + FreeLibrary(hash_prov.prov.cng.dll); + + hash_prov.type = INVALID; +} + +/* Initialize CryptoAPI */ +GIT_INLINE(int) hash_cryptoapi_prov_init() +{ + if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + git_error_set(GIT_ERROR_OS, "legacy hash context could not be started"); + return -1; + } + + hash_prov.type = CRYPTOAPI; + return 0; +} + +GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void) +{ + CryptReleaseContext(hash_prov.prov.cryptoapi.handle, 0); + + hash_prov.type = INVALID; +} + +static void sha1_shutdown(void) +{ + if (hash_prov.type == CNG) + hash_cng_prov_shutdown(); + else if(hash_prov.type == CRYPTOAPI) + hash_cryptoapi_prov_shutdown(); +} + +int git_hash_sha1_global_init(void) +{ + int error = 0; + + if (hash_prov.type != INVALID) + return 0; + + if ((error = hash_cng_prov_init()) < 0) + error = hash_cryptoapi_prov_init(); + + if (!error) + error = git_runtime_shutdown_register(sha1_shutdown); + + return error; +} + +/* CryptoAPI: available in Windows XP and newer */ + +GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_sha1_ctx *ctx) +{ + ctx->type = CRYPTOAPI; + ctx->prov = &hash_prov; + + return git_hash_sha1_init(ctx); +} + +GIT_INLINE(int) hash_cryptoapi_init(git_hash_sha1_ctx *ctx) +{ + if (ctx->ctx.cryptoapi.valid) + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); + + if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) { + ctx->ctx.cryptoapi.valid = 0; + git_error_set(GIT_ERROR_OS, "legacy hash implementation could not be created"); + return -1; + } + + ctx->ctx.cryptoapi.valid = 1; + return 0; +} + +GIT_INLINE(int) hash_cryptoapi_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) +{ + const BYTE *data = (BYTE *)_data; + + GIT_ASSERT(ctx->ctx.cryptoapi.valid); + + while (len > 0) { + DWORD chunk = (len > MAXDWORD) ? MAXDWORD : (DWORD)len; + + if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, data, chunk, 0)) { + git_error_set(GIT_ERROR_OS, "legacy hash data could not be updated"); + return -1; + } + + data += chunk; + len -= chunk; + } + + return 0; +} + +GIT_INLINE(int) hash_cryptoapi_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + DWORD len = GIT_HASH_SHA1_SIZE; + int error = 0; + + GIT_ASSERT(ctx->ctx.cryptoapi.valid); + + if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out, &len, 0)) { + git_error_set(GIT_ERROR_OS, "legacy hash data could not be finished"); + error = -1; + } + + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); + ctx->ctx.cryptoapi.valid = 0; + + return error; +} + +GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_sha1_ctx *ctx) +{ + if (ctx->ctx.cryptoapi.valid) + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); +} + +/* CNG: Available in Windows Server 2008 and newer */ + +GIT_INLINE(int) hash_ctx_cng_init(git_hash_sha1_ctx *ctx) +{ + if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL) + return -1; + + if (hash_prov.prov.cng.create_hash(hash_prov.prov.cng.handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_prov.prov.cng.hash_object_size, NULL, 0, 0) < 0) { + git__free(ctx->ctx.cng.hash_object); + + git_error_set(GIT_ERROR_OS, "hash implementation could not be created"); + return -1; + } + + ctx->type = CNG; + ctx->prov = &hash_prov; + + return 0; +} + +GIT_INLINE(int) hash_cng_init(git_hash_sha1_ctx *ctx) +{ + BYTE hash[GIT_OID_RAWSZ]; + + if (!ctx->ctx.cng.updated) + return 0; + + /* CNG needs to be finished to restart */ + if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0) { + git_error_set(GIT_ERROR_OS, "hash implementation could not be finished"); + return -1; + } + + ctx->ctx.cng.updated = 0; + + return 0; +} + +GIT_INLINE(int) hash_cng_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) +{ + PBYTE data = (PBYTE)_data; + + while (len > 0) { + ULONG chunk = (len > ULONG_MAX) ? ULONG_MAX : (ULONG)len; + + if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, data, chunk, 0) < 0) { + git_error_set(GIT_ERROR_OS, "hash could not be updated"); + return -1; + } + + data += chunk; + len -= chunk; + } + + return 0; +} + +GIT_INLINE(int) hash_cng_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out, GIT_HASH_SHA1_SIZE, 0) < 0) { + git_error_set(GIT_ERROR_OS, "hash could not be finished"); + return -1; + } + + ctx->ctx.cng.updated = 0; + + return 0; +} + +GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_sha1_ctx *ctx) +{ + ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle); + git__free(ctx->ctx.cng.hash_object); +} + +/* Indirection between CryptoAPI and CNG */ + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + int error = 0; + + GIT_ASSERT_ARG(ctx); + + /* + * When compiled with GIT_THREADS, the global hash_prov data is + * initialized with git_libgit2_init. Otherwise, it must be initialized + * at first use. + */ + if (hash_prov.type == INVALID && (error = git_hash_sha1_global_init()) < 0) + return error; + + memset(ctx, 0x0, sizeof(git_hash_sha1_ctx)); + + return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + GIT_ASSERT_ARG(ctx->type); + return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx); +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + GIT_ASSERT_ARG(ctx->type); + return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len); +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + GIT_ASSERT_ARG(ctx->type); + return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + if (!ctx) + return; + else if (ctx->type == CNG) + hash_ctx_cng_cleanup(ctx); + else if(ctx->type == CRYPTOAPI) + hash_ctx_cryptoapi_cleanup(ctx); +} diff --git a/src/libgit2/hash/sha1/win32.h b/src/libgit2/hash/sha1/win32.h new file mode 100644 index 000000000..791d20a42 --- /dev/null +++ b/src/libgit2/hash/sha1/win32.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_win32_h__ +#define INCLUDE_hash_sha1_win32_h__ + +#include "hash/sha1.h" + +#include +#include + +enum hash_win32_prov_type { + INVALID = 0, + CRYPTOAPI, + CNG +}; + +/* + * CryptoAPI is available for hashing on Windows XP and newer. + */ + +struct hash_cryptoapi_prov { + HCRYPTPROV handle; +}; + +/* + * CNG (bcrypt.dll) is significantly more performant than CryptoAPI and is + * preferred, however it is only available on Windows 2008 and newer and + * must therefore be dynamically loaded, and we must inline constants that + * would not exist when building in pre-Windows 2008 environments. + */ + +/* Function declarations for CNG */ +typedef NTSTATUS (WINAPI *hash_win32_cng_open_algorithm_provider_fn)( + HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm, + LPCWSTR pszAlgId, + LPCWSTR pszImplementation, + DWORD dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_get_property_fn)( + HANDLE /* BCRYPT_HANDLE */ hObject, + LPCWSTR pszProperty, + PUCHAR pbOutput, + ULONG cbOutput, + ULONG *pcbResult, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_create_hash_fn)( + HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, + HANDLE /* BCRYPT_HASH_HANDLE */ *phHash, + PUCHAR pbHashObject, ULONG cbHashObject, + PUCHAR pbSecret, + ULONG cbSecret, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_finish_hash_fn)( + HANDLE /* BCRYPT_HASH_HANDLE */ hHash, + PUCHAR pbOutput, + ULONG cbOutput, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_hash_data_fn)( + HANDLE /* BCRYPT_HASH_HANDLE */ hHash, + PUCHAR pbInput, + ULONG cbInput, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_destroy_hash_fn)( + HANDLE /* BCRYPT_HASH_HANDLE */ hHash); + +typedef NTSTATUS (WINAPI *hash_win32_cng_close_algorithm_provider_fn)( + HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, + ULONG dwFlags); + +struct hash_cng_prov { + /* DLL for CNG */ + HINSTANCE dll; + + /* Function pointers for CNG */ + hash_win32_cng_open_algorithm_provider_fn open_algorithm_provider; + hash_win32_cng_get_property_fn get_property; + hash_win32_cng_create_hash_fn create_hash; + hash_win32_cng_finish_hash_fn finish_hash; + hash_win32_cng_hash_data_fn hash_data; + hash_win32_cng_destroy_hash_fn destroy_hash; + hash_win32_cng_close_algorithm_provider_fn close_algorithm_provider; + + HANDLE /* BCRYPT_ALG_HANDLE */ handle; + DWORD hash_object_size; +}; + +typedef struct { + enum hash_win32_prov_type type; + + union { + struct hash_cryptoapi_prov cryptoapi; + struct hash_cng_prov cng; + } prov; +} git_hash_prov; + +/* Hash contexts */ + +struct hash_cryptoapi_ctx { + bool valid; + HCRYPTHASH hash_handle; +}; + +struct hash_cng_ctx { + bool updated; + HANDLE /* BCRYPT_HASH_HANDLE */ hash_handle; + PBYTE hash_object; +}; + +struct git_hash_sha1_ctx { + enum hash_win32_prov_type type; + git_hash_prov *prov; + + union { + struct hash_cryptoapi_ctx cryptoapi; + struct hash_cng_ctx cng; + } ctx; +}; + +#endif diff --git a/src/libgit2/hashsig.c b/src/libgit2/hashsig.c new file mode 100644 index 000000000..6b4fb8352 --- /dev/null +++ b/src/libgit2/hashsig.c @@ -0,0 +1,375 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "git2/sys/hashsig.h" +#include "futils.h" +#include "util.h" + +typedef uint32_t hashsig_t; +typedef uint64_t hashsig_state; + +#define HASHSIG_SCALE 100 + +#define HASHSIG_MAX_RUN 80 +#define HASHSIG_HASH_START INT64_C(0x012345678ABCDEF0) +#define HASHSIG_HASH_SHIFT 5 + +#define HASHSIG_HASH_MIX(S,CH) \ + (S) = ((S) << HASHSIG_HASH_SHIFT) - (S) + (hashsig_state)(CH) + +#define HASHSIG_HEAP_SIZE ((1 << 7) - 1) +#define HASHSIG_HEAP_MIN_SIZE 4 + +typedef int (*hashsig_cmp)(const void *a, const void *b, void *); + +typedef struct { + int size, asize; + hashsig_cmp cmp; + hashsig_t values[HASHSIG_HEAP_SIZE]; +} hashsig_heap; + +struct git_hashsig { + hashsig_heap mins; + hashsig_heap maxs; + size_t lines; + git_hashsig_option_t opt; +}; + +#define HEAP_LCHILD_OF(I) (((I)<<1)+1) +#define HEAP_RCHILD_OF(I) (((I)<<1)+2) +#define HEAP_PARENT_OF(I) (((I)-1)>>1) + +static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp) +{ + h->size = 0; + h->asize = HASHSIG_HEAP_SIZE; + h->cmp = cmp; +} + +static int hashsig_cmp_max(const void *a, const void *b, void *payload) +{ + hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b; + GIT_UNUSED(payload); + return (av < bv) ? -1 : (av > bv) ? 1 : 0; +} + +static int hashsig_cmp_min(const void *a, const void *b, void *payload) +{ + hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b; + GIT_UNUSED(payload); + return (av > bv) ? -1 : (av < bv) ? 1 : 0; +} + +static void hashsig_heap_up(hashsig_heap *h, int el) +{ + int parent_el = HEAP_PARENT_OF(el); + + while (el > 0 && h->cmp(&h->values[parent_el], &h->values[el], NULL) > 0) { + hashsig_t t = h->values[el]; + h->values[el] = h->values[parent_el]; + h->values[parent_el] = t; + + el = parent_el; + parent_el = HEAP_PARENT_OF(el); + } +} + +static void hashsig_heap_down(hashsig_heap *h, int el) +{ + hashsig_t v, lv, rv; + + /* 'el < h->size / 2' tests if el is bottom row of heap */ + + while (el < h->size / 2) { + int lel = HEAP_LCHILD_OF(el), rel = HEAP_RCHILD_OF(el), swapel; + + v = h->values[el]; + lv = h->values[lel]; + rv = h->values[rel]; + + if (h->cmp(&v, &lv, NULL) < 0 && h->cmp(&v, &rv, NULL) < 0) + break; + + swapel = (h->cmp(&lv, &rv, NULL) < 0) ? lel : rel; + + h->values[el] = h->values[swapel]; + h->values[swapel] = v; + + el = swapel; + } +} + +static void hashsig_heap_sort(hashsig_heap *h) +{ + /* only need to do this at the end for signature comparison */ + git__qsort_r(h->values, h->size, sizeof(hashsig_t), h->cmp, NULL); +} + +static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val) +{ + /* if heap is not full, insert new element */ + if (h->size < h->asize) { + h->values[h->size++] = val; + hashsig_heap_up(h, h->size - 1); + } + + /* if heap is full, pop top if new element should replace it */ + else if (h->cmp(&val, &h->values[0], NULL) > 0) { + h->size--; + h->values[0] = h->values[h->size]; + hashsig_heap_down(h, 0); + } + +} + +typedef struct { + int use_ignores; + uint8_t ignore_ch[256]; +} hashsig_in_progress; + +static int hashsig_in_progress_init( + hashsig_in_progress *prog, git_hashsig *sig) +{ + int i; + + /* no more than one can be set */ + GIT_ASSERT(!(sig->opt & GIT_HASHSIG_IGNORE_WHITESPACE) || + !(sig->opt & GIT_HASHSIG_SMART_WHITESPACE)); + + if (sig->opt & GIT_HASHSIG_IGNORE_WHITESPACE) { + for (i = 0; i < 256; ++i) + prog->ignore_ch[i] = git__isspace_nonlf(i); + prog->use_ignores = 1; + } else if (sig->opt & GIT_HASHSIG_SMART_WHITESPACE) { + for (i = 0; i < 256; ++i) + prog->ignore_ch[i] = git__isspace(i); + prog->use_ignores = 1; + } else { + memset(prog, 0, sizeof(*prog)); + } + + return 0; +} + +static int hashsig_add_hashes( + git_hashsig *sig, + const uint8_t *data, + size_t size, + hashsig_in_progress *prog) +{ + const uint8_t *scan = data, *end = data + size; + hashsig_state state = HASHSIG_HASH_START; + int use_ignores = prog->use_ignores, len; + uint8_t ch; + + while (scan < end) { + state = HASHSIG_HASH_START; + + for (len = 0; scan < end && len < HASHSIG_MAX_RUN; ) { + ch = *scan; + + if (use_ignores) + for (; scan < end && git__isspace_nonlf(ch); ch = *scan) + ++scan; + else if (sig->opt & + (GIT_HASHSIG_IGNORE_WHITESPACE | GIT_HASHSIG_SMART_WHITESPACE)) + for (; scan < end && ch == '\r'; ch = *scan) + ++scan; + + /* peek at next character to decide what to do next */ + if (sig->opt & GIT_HASHSIG_SMART_WHITESPACE) + use_ignores = (ch == '\n'); + + if (scan >= end) + break; + ++scan; + + /* check run terminator */ + if (ch == '\n' || ch == '\0') { + sig->lines++; + break; + } + + ++len; + HASHSIG_HASH_MIX(state, ch); + } + + if (len > 0) { + hashsig_heap_insert(&sig->mins, (hashsig_t)state); + hashsig_heap_insert(&sig->maxs, (hashsig_t)state); + + while (scan < end && (*scan == '\n' || !*scan)) + ++scan; + } + } + + prog->use_ignores = use_ignores; + + return 0; +} + +static int hashsig_finalize_hashes(git_hashsig *sig) +{ + if (sig->mins.size < HASHSIG_HEAP_MIN_SIZE && + !(sig->opt & GIT_HASHSIG_ALLOW_SMALL_FILES)) { + git_error_set(GIT_ERROR_INVALID, + "file too small for similarity signature calculation"); + return GIT_EBUFS; + } + + hashsig_heap_sort(&sig->mins); + hashsig_heap_sort(&sig->maxs); + + return 0; +} + +static git_hashsig *hashsig_alloc(git_hashsig_option_t opts) +{ + git_hashsig *sig = git__calloc(1, sizeof(git_hashsig)); + if (!sig) + return NULL; + + hashsig_heap_init(&sig->mins, hashsig_cmp_min); + hashsig_heap_init(&sig->maxs, hashsig_cmp_max); + sig->opt = opts; + + return sig; +} + +int git_hashsig_create( + git_hashsig **out, + const char *buf, + size_t buflen, + git_hashsig_option_t opts) +{ + int error; + hashsig_in_progress prog; + git_hashsig *sig = hashsig_alloc(opts); + GIT_ERROR_CHECK_ALLOC(sig); + + if ((error = hashsig_in_progress_init(&prog, sig)) < 0) + return error; + + error = hashsig_add_hashes(sig, (const uint8_t *)buf, buflen, &prog); + + if (!error) + error = hashsig_finalize_hashes(sig); + + if (!error) + *out = sig; + else + git_hashsig_free(sig); + + return error; +} + +int git_hashsig_create_fromfile( + git_hashsig **out, + const char *path, + git_hashsig_option_t opts) +{ + uint8_t buf[0x1000]; + ssize_t buflen = 0; + int error = 0, fd; + hashsig_in_progress prog; + git_hashsig *sig = hashsig_alloc(opts); + GIT_ERROR_CHECK_ALLOC(sig); + + if ((fd = git_futils_open_ro(path)) < 0) { + git__free(sig); + return fd; + } + + if ((error = hashsig_in_progress_init(&prog, sig)) < 0) { + p_close(fd); + return error; + } + + while (!error) { + if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) { + if ((error = (int)buflen) < 0) + git_error_set(GIT_ERROR_OS, + "read error on '%s' calculating similarity hashes", path); + break; + } + + error = hashsig_add_hashes(sig, buf, buflen, &prog); + } + + p_close(fd); + + if (!error) + error = hashsig_finalize_hashes(sig); + + if (!error) + *out = sig; + else + git_hashsig_free(sig); + + return error; +} + +void git_hashsig_free(git_hashsig *sig) +{ + git__free(sig); +} + +static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b) +{ + int matches = 0, i, j, cmp; + + GIT_ASSERT_WITH_RETVAL(a->cmp == b->cmp, 0); + + /* hash heaps are sorted - just look for overlap vs total */ + + for (i = 0, j = 0; i < a->size && j < b->size; ) { + cmp = a->cmp(&a->values[i], &b->values[j], NULL); + + if (cmp < 0) + ++i; + else if (cmp > 0) + ++j; + else { + ++i; ++j; ++matches; + } + } + + return HASHSIG_SCALE * (matches * 2) / (a->size + b->size); +} + +int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b) +{ + /* if we have no elements in either file then each file is either + * empty or blank. if we're ignoring whitespace then the files are + * similar, otherwise they're dissimilar. + */ + if (a->mins.size == 0 && b->mins.size == 0) { + if ((!a->lines && !b->lines) || + (a->opt & GIT_HASHSIG_IGNORE_WHITESPACE)) + return HASHSIG_SCALE; + else + return 0; + } + + /* if we have fewer than the maximum number of elements, then just use + * one array since the two arrays will be the same + */ + if (a->mins.size < HASHSIG_HEAP_SIZE) { + return hashsig_heap_compare(&a->mins, &b->mins); + } else { + int mins, maxs; + + if ((mins = hashsig_heap_compare(&a->mins, &b->mins)) < 0) + return mins; + if ((maxs = hashsig_heap_compare(&a->maxs, &b->maxs)) < 0) + return maxs; + + return (mins + maxs) / 2; + } +} diff --git a/src/libgit2/ident.c b/src/libgit2/ident.c new file mode 100644 index 000000000..53095864e --- /dev/null +++ b/src/libgit2/ident.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "git2/sys/filter.h" +#include "filter.h" +#include "str.h" + +static int ident_find_id( + const char **id_start, const char **id_end, const char *start, size_t len) +{ + const char *end = start + len, *found = NULL; + + while (len > 3 && (found = memchr(start, '$', len)) != NULL) { + size_t remaining = (size_t)(end - found) - 1; + if (remaining < 3) + return GIT_ENOTFOUND; + + start = found + 1; + len = remaining; + + if (start[0] == 'I' && start[1] == 'd') + break; + } + + if (len < 3 || !found) + return GIT_ENOTFOUND; + *id_start = found; + + if ((found = memchr(start + 2, '$', len - 2)) == NULL) + return GIT_ENOTFOUND; + + *id_end = found + 1; + return 0; +} + +static int ident_insert_id( + git_str *to, const git_str *from, const git_filter_source *src) +{ + char oid[GIT_OID_HEXSZ+1]; + const char *id_start, *id_end, *from_end = from->ptr + from->size; + size_t need_size; + + /* replace $Id$ with blob id */ + + if (!git_filter_source_id(src)) + return GIT_PASSTHROUGH; + + git_oid_tostr(oid, sizeof(oid), git_filter_source_id(src)); + + if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0) + return GIT_PASSTHROUGH; + + need_size = (size_t)(id_start - from->ptr) + + 5 /* "$Id: " */ + GIT_OID_HEXSZ + 2 /* " $" */ + + (size_t)(from_end - id_end); + + if (git_str_grow(to, need_size) < 0) + return -1; + + git_str_set(to, from->ptr, (size_t)(id_start - from->ptr)); + git_str_put(to, "$Id: ", 5); + git_str_put(to, oid, GIT_OID_HEXSZ); + git_str_put(to, " $", 2); + git_str_put(to, id_end, (size_t)(from_end - id_end)); + + return git_str_oom(to) ? -1 : 0; +} + +static int ident_remove_id( + git_str *to, const git_str *from) +{ + const char *id_start, *id_end, *from_end = from->ptr + from->size; + size_t need_size; + + if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0) + return GIT_PASSTHROUGH; + + need_size = (size_t)(id_start - from->ptr) + + 4 /* "$Id$" */ + (size_t)(from_end - id_end); + + if (git_str_grow(to, need_size) < 0) + return -1; + + git_str_set(to, from->ptr, (size_t)(id_start - from->ptr)); + git_str_put(to, "$Id$", 4); + git_str_put(to, id_end, (size_t)(from_end - id_end)); + + return git_str_oom(to) ? -1 : 0; +} + +static int ident_apply( + git_filter *self, + void **payload, + git_str *to, + const git_str *from, + const git_filter_source *src) +{ + GIT_UNUSED(self); GIT_UNUSED(payload); + + /* Don't filter binary files */ + if (git_str_is_binary(from)) + return GIT_PASSTHROUGH; + + if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) + return ident_insert_id(to, from, src); + else + return ident_remove_id(to, from); +} + +static int ident_stream( + git_writestream **out, + git_filter *self, + void **payload, + const git_filter_source *src, + git_writestream *next) +{ + return git_filter_buffered_stream_new(out, + self, ident_apply, NULL, payload, src, next); +} + +git_filter *git_ident_filter_new(void) +{ + git_filter *f = git__calloc(1, sizeof(git_filter)); + if (f == NULL) + return NULL; + + f->version = GIT_FILTER_VERSION; + f->attributes = "+ident"; /* apply to files with ident attribute set */ + f->shutdown = git_filter_free; + f->stream = ident_stream; + + return f; +} diff --git a/src/libgit2/idxmap.c b/src/libgit2/idxmap.c new file mode 100644 index 000000000..bc23608f2 --- /dev/null +++ b/src/libgit2/idxmap.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "idxmap.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kreallocarray git__reallocarray +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(idx, const git_index_entry *, git_index_entry *) +__KHASH_TYPE(idxicase, const git_index_entry *, git_index_entry *) + +/* This is __ac_X31_hash_string but with tolower and it takes the entry's stage into account */ +static kh_inline khint_t idxentry_hash(const git_index_entry *e) +{ + const char *s = e->path; + khint_t h = (khint_t)git__tolower(*s); + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)git__tolower(*s); + return h + GIT_INDEX_ENTRY_STAGE(e); +} + +#define idxentry_equal(a, b) (GIT_INDEX_ENTRY_STAGE(a) == GIT_INDEX_ENTRY_STAGE(b) && strcmp(a->path, b->path) == 0) +#define idxentry_icase_equal(a, b) (GIT_INDEX_ENTRY_STAGE(a) == GIT_INDEX_ENTRY_STAGE(b) && strcasecmp(a->path, b->path) == 0) + +__KHASH_IMPL(idx, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_equal) +__KHASH_IMPL(idxicase, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_icase_equal) + +int git_idxmap_new(git_idxmap **out) +{ + *out = kh_init(idx); + GIT_ERROR_CHECK_ALLOC(*out); + + return 0; +} + +int git_idxmap_icase_new(git_idxmap_icase **out) +{ + *out = kh_init(idxicase); + GIT_ERROR_CHECK_ALLOC(*out); + + return 0; +} + +void git_idxmap_free(git_idxmap *map) +{ + kh_destroy(idx, map); +} + +void git_idxmap_icase_free(git_idxmap_icase *map) +{ + kh_destroy(idxicase, map); +} + +void git_idxmap_clear(git_idxmap *map) +{ + kh_clear(idx, map); +} + +void git_idxmap_icase_clear(git_idxmap_icase *map) +{ + kh_clear(idxicase, map); +} + +int git_idxmap_resize(git_idxmap *map, size_t size) +{ + if (!git__is_uint32(size) || + kh_resize(idx, map, (khiter_t)size) < 0) { + git_error_set_oom(); + return -1; + } + return 0; +} + +int git_idxmap_icase_resize(git_idxmap_icase *map, size_t size) +{ + if (!git__is_uint32(size) || + kh_resize(idxicase, map, (khiter_t)size) < 0) { + git_error_set_oom(); + return -1; + } + return 0; +} + +void *git_idxmap_get(git_idxmap *map, const git_index_entry *key) +{ + size_t idx = kh_get(idx, map, key); + if (idx == kh_end(map) || !kh_exist(map, idx)) + return NULL; + return kh_val(map, idx); +} + +int git_idxmap_set(git_idxmap *map, const git_index_entry *key, void *value) +{ + size_t idx; + int rval; + + idx = kh_put(idx, map, key, &rval); + if (rval < 0) + return -1; + + if (rval == 0) + kh_key(map, idx) = key; + + kh_val(map, idx) = value; + + return 0; +} + +int git_idxmap_icase_set(git_idxmap_icase *map, const git_index_entry *key, void *value) +{ + size_t idx; + int rval; + + idx = kh_put(idxicase, map, key, &rval); + if (rval < 0) + return -1; + + if (rval == 0) + kh_key(map, idx) = key; + + kh_val(map, idx) = value; + + return 0; +} + +void *git_idxmap_icase_get(git_idxmap_icase *map, const git_index_entry *key) +{ + size_t idx = kh_get(idxicase, map, key); + if (idx == kh_end(map) || !kh_exist(map, idx)) + return NULL; + return kh_val(map, idx); +} + +int git_idxmap_delete(git_idxmap *map, const git_index_entry *key) +{ + khiter_t idx = kh_get(idx, map, key); + if (idx == kh_end(map)) + return GIT_ENOTFOUND; + kh_del(idx, map, idx); + return 0; +} + +int git_idxmap_icase_delete(git_idxmap_icase *map, const git_index_entry *key) +{ + khiter_t idx = kh_get(idxicase, map, key); + if (idx == kh_end(map)) + return GIT_ENOTFOUND; + kh_del(idxicase, map, idx); + return 0; +} diff --git a/src/libgit2/idxmap.h b/src/libgit2/idxmap.h new file mode 100644 index 000000000..76170ef32 --- /dev/null +++ b/src/libgit2/idxmap.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_idxmap_h__ +#define INCLUDE_idxmap_h__ + +#include "common.h" + +#include "git2/index.h" + +/** A map with `git_index_entry`s as key. */ +typedef struct kh_idx_s git_idxmap; +/** A map with case-insensitive `git_index_entry`s as key */ +typedef struct kh_idxicase_s git_idxmap_icase; + +/** + * Allocate a new index entry map. + * + * @param out Pointer to the map that shall be allocated. + * @return 0 on success, an error code if allocation has failed. + */ +int git_idxmap_new(git_idxmap **out); + +/** + * Allocate a new case-insensitive index entry map. + * + * @param out Pointer to the map that shall be allocated. + * @return 0 on success, an error code if allocation has failed. + */ +int git_idxmap_icase_new(git_idxmap_icase **out); + +/** + * Free memory associated with the map. + * + * Note that this function will _not_ free values added to this + * map. + * + * @param map Pointer to the map that is to be free'd. May be + * `NULL`. + */ +void git_idxmap_free(git_idxmap *map); + +/** + * Free memory associated with the map. + * + * Note that this function will _not_ free values added to this + * map. + * + * @param map Pointer to the map that is to be free'd. May be + * `NULL`. + */ +void git_idxmap_icase_free(git_idxmap_icase *map); + +/** + * Clear all entries from the map. + * + * This function will remove all entries from the associated map. + * Memory associated with it will not be released, though. + * + * @param map Pointer to the map that shall be cleared. May be + * `NULL`. + */ +void git_idxmap_clear(git_idxmap *map); + +/** + * Clear all entries from the map. + * + * This function will remove all entries from the associated map. + * Memory associated with it will not be released, though. + * + * @param map Pointer to the map that shall be cleared. May be + * `NULL`. + */ +void git_idxmap_icase_clear(git_idxmap_icase *map); + +/** + * Resize the map by allocating more memory. + * + * @param map map that shall be resized + * @param size count of entries that the map shall hold + * @return `0` if the map was successfully resized, a negative + * error code otherwise + */ +int git_idxmap_resize(git_idxmap *map, size_t size); + +/** + * Resize the map by allocating more memory. + * + * @param map map that shall be resized + * @param size count of entries that the map shall hold + * @return `0` if the map was successfully resized, a negative + * error code otherwise + */ +int git_idxmap_icase_resize(git_idxmap_icase *map, size_t size); + +/** + * Return value associated with the given key. + * + * @param map map to search key in + * @param key key to search for; the index entry will be searched + * for by its case-sensitive path + * @return value associated with the given key or NULL if the key was not found + */ +void *git_idxmap_get(git_idxmap *map, const git_index_entry *key); + +/** + * Return value associated with the given key. + * + * @param map map to search key in + * @param key key to search for; the index entry will be searched + * for by its case-insensitive path + * @return value associated with the given key or NULL if the key was not found + */ +void *git_idxmap_icase_get(git_idxmap_icase *map, const git_index_entry *key); + +/** + * Set the entry for key to value. + * + * If the map has no corresponding entry for the given key, a new + * entry will be created with the given value. If an entry exists + * already, its value will be updated to match the given value. + * + * @param map map to create new entry in + * @param key key to set + * @param value value to associate the key with; may be NULL + * @return zero if the key was successfully set, a negative error + * code otherwise + */ +int git_idxmap_set(git_idxmap *map, const git_index_entry *key, void *value); + +/** + * Set the entry for key to value. + * + * If the map has no corresponding entry for the given key, a new + * entry will be created with the given value. If an entry exists + * already, its value will be updated to match the given value. + * + * @param map map to create new entry in + * @param key key to set + * @param value value to associate the key with; may be NULL + * @return zero if the key was successfully set, a negative error + * code otherwise + */ +int git_idxmap_icase_set(git_idxmap_icase *map, const git_index_entry *key, void *value); + +/** + * Delete an entry from the map. + * + * Delete the given key and its value from the map. If no such + * key exists, this will do nothing. + * + * @param map map to delete key in + * @param key key to delete + * @return `0` if the key has been deleted, GIT_ENOTFOUND if no + * such key was found, a negative code in case of an + * error + */ +int git_idxmap_delete(git_idxmap *map, const git_index_entry *key); + +/** + * Delete an entry from the map. + * + * Delete the given key and its value from the map. If no such + * key exists, this will do nothing. + * + * @param map map to delete key in + * @param key key to delete + * @return `0` if the key has been deleted, GIT_ENOTFOUND if no + * such key was found, a negative code in case of an + * error + */ +int git_idxmap_icase_delete(git_idxmap_icase *map, const git_index_entry *key); + +#endif diff --git a/src/libgit2/ignore.c b/src/libgit2/ignore.c new file mode 100644 index 000000000..cee58d7f1 --- /dev/null +++ b/src/libgit2/ignore.c @@ -0,0 +1,652 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "ignore.h" + +#include "git2/ignore.h" +#include "common.h" +#include "attrcache.h" +#include "fs_path.h" +#include "config.h" +#include "wildmatch.h" +#include "path.h" + +#define GIT_IGNORE_INTERNAL "[internal]exclude" + +#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n" + +/** + * A negative ignore pattern can negate a positive one without + * wildcards if it is a basename only and equals the basename of + * the positive pattern. Thus + * + * foo/bar + * !bar + * + * would result in foo/bar being unignored again while + * + * moo/foo/bar + * !foo/bar + * + * would do nothing. The reverse also holds true: a positive + * basename pattern can be negated by unignoring the basename in + * subdirectories. Thus + * + * bar + * !foo/bar + * + * would result in foo/bar being unignored again. As with the + * first case, + * + * foo/bar + * !moo/foo/bar + * + * would do nothing, again. + */ +static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) +{ + int (*cmp)(const char *, const char *, size_t); + git_attr_fnmatch *longer, *shorter; + char *p; + + if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 + || (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) + return false; + + if (neg->flags & GIT_ATTR_FNMATCH_ICASE) + cmp = git__strncasecmp; + else + cmp = git__strncmp; + + /* If lengths match we need to have an exact match */ + if (rule->length == neg->length) { + return cmp(rule->pattern, neg->pattern, rule->length) == 0; + } else if (rule->length < neg->length) { + shorter = rule; + longer = neg; + } else { + shorter = neg; + longer = rule; + } + + /* Otherwise, we need to check if the shorter + * rule is a basename only (that is, it contains + * no path separator) and, if so, if it + * matches the tail of the longer rule */ + p = longer->pattern + longer->length - shorter->length; + + if (p[-1] != '/') + return false; + if (memchr(shorter->pattern, '/', shorter->length) != NULL) + return false; + + return cmp(p, shorter->pattern, shorter->length) == 0; +} + +/** + * A negative ignore can only unignore a file which is given explicitly before, thus + * + * foo + * !foo/bar + * + * does not unignore 'foo/bar' as it's not in the list. However + * + * foo/ + * !foo/bar + * + * does unignore 'foo/bar', as it is contained within the 'foo/' rule. + */ +static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match) +{ + int error = 0, wildmatch_flags, effective_flags; + size_t i; + git_attr_fnmatch *rule; + char *path; + git_str buf = GIT_STR_INIT; + + *out = 0; + + wildmatch_flags = WM_PATHNAME; + if (match->flags & GIT_ATTR_FNMATCH_ICASE) + wildmatch_flags |= WM_CASEFOLD; + + /* path of the file relative to the workdir, so we match the rules in subdirs */ + if (match->containing_dir) { + git_str_puts(&buf, match->containing_dir); + } + if (git_str_puts(&buf, match->pattern) < 0) + return -1; + + path = git_str_detach(&buf); + + git_vector_foreach(rules, i, rule) { + if (!(rule->flags & GIT_ATTR_FNMATCH_HASWILD)) { + if (does_negate_pattern(rule, match)) { + error = 0; + *out = 1; + goto out; + } + else + continue; + } + + git_str_clear(&buf); + if (rule->containing_dir) + git_str_puts(&buf, rule->containing_dir); + git_str_puts(&buf, rule->pattern); + + if (git_str_oom(&buf)) + goto out; + + /* + * if rule isn't for full path we match without PATHNAME flag + * as lines like *.txt should match something like dir/test.txt + * requiring * to also match / + */ + effective_flags = wildmatch_flags; + if (!(rule->flags & GIT_ATTR_FNMATCH_FULLPATH)) + effective_flags &= ~WM_PATHNAME; + + /* if we found a match, we want to keep this rule */ + if ((wildmatch(git_str_cstr(&buf), path, effective_flags)) == WM_MATCH) { + *out = 1; + error = 0; + goto out; + } + } + + error = 0; + +out: + git__free(path); + git_str_dispose(&buf); + return error; +} + +static int parse_ignore_file( + git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros) +{ + int error = 0; + int ignore_case = false; + const char *scan = data, *context = NULL; + git_attr_fnmatch *match = NULL; + + GIT_UNUSED(allow_macros); + + if (git_repository__configmap_lookup(&ignore_case, repo, GIT_CONFIGMAP_IGNORECASE) < 0) + git_error_clear(); + + /* if subdir file path, convert context for file paths */ + if (attrs->entry && + git_fs_path_root(attrs->entry->path) < 0 && + !git__suffixcmp(attrs->entry->path, "/" GIT_IGNORE_FILE)) + context = attrs->entry->path; + + if (git_mutex_lock(&attrs->lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock ignore file"); + return -1; + } + + while (!error && *scan) { + int valid_rule = 1; + + if (!match && !(match = git__calloc(1, sizeof(*match)))) { + error = -1; + break; + } + + match->flags = + GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; + + if (!(error = git_attr_fnmatch__parse( + match, &attrs->pool, context, &scan))) + { + match->flags |= GIT_ATTR_FNMATCH_IGNORE; + + if (ignore_case) + match->flags |= GIT_ATTR_FNMATCH_ICASE; + + scan = git__next_line(scan); + + /* + * If a negative match doesn't actually do anything, + * throw it away. As we cannot always verify whether a + * rule containing wildcards negates another rule, we + * do not optimize away these rules, though. + * */ + if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE + && !(match->flags & GIT_ATTR_FNMATCH_HASWILD)) + error = does_negate_rule(&valid_rule, &attrs->rules, match); + + if (!error && valid_rule) + error = git_vector_insert(&attrs->rules, match); + } + + if (error != 0 || !valid_rule) { + match->pattern = NULL; + + if (error == GIT_ENOTFOUND) + error = 0; + } else { + match = NULL; /* vector now "owns" the match */ + } + } + + git_mutex_unlock(&attrs->lock); + git__free(match); + + return error; +} + +static int push_ignore_file( + git_ignores *ignores, + git_vector *which_list, + const char *base, + const char *filename) +{ + git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE, base, filename }; + git_attr_file *file = NULL; + int error = 0; + + error = git_attr_cache__get(&file, ignores->repo, NULL, &source, parse_ignore_file, false); + + if (error < 0) + return error; + + if (file != NULL) { + if ((error = git_vector_insert(which_list, file)) < 0) + git_attr_file__free(file); + } + + return error; +} + +static int push_one_ignore(void *payload, const char *path) +{ + git_ignores *ign = payload; + ign->depth++; + return push_ignore_file(ign, &ign->ign_path, path, GIT_IGNORE_FILE); +} + +static int get_internal_ignores(git_attr_file **out, git_repository *repo) +{ + git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_MEMORY, NULL, GIT_IGNORE_INTERNAL }; + int error; + + if ((error = git_attr_cache__init(repo)) < 0) + return error; + + error = git_attr_cache__get(out, repo, NULL, &source, NULL, false); + + /* if internal rules list is empty, insert default rules */ + if (!error && !(*out)->rules.length) + error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES, false); + + return error; +} + +int git_ignore__for_path( + git_repository *repo, + const char *path, + git_ignores *ignores) +{ + int error = 0; + const char *workdir = git_repository_workdir(repo); + git_str infopath = GIT_STR_INIT; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(ignores); + GIT_ASSERT_ARG(path); + + memset(ignores, 0, sizeof(*ignores)); + ignores->repo = repo; + + /* Read the ignore_case flag */ + if ((error = git_repository__configmap_lookup( + &ignores->ignore_case, repo, GIT_CONFIGMAP_IGNORECASE)) < 0) + goto cleanup; + + if ((error = git_attr_cache__init(repo)) < 0) + goto cleanup; + + /* given a unrooted path in a non-bare repo, resolve it */ + if (workdir && git_fs_path_root(path) < 0) { + git_str local = GIT_STR_INIT; + + if ((error = git_fs_path_dirname_r(&local, path)) < 0 || + (error = git_fs_path_resolve_relative(&local, 0)) < 0 || + (error = git_fs_path_to_dir(&local)) < 0 || + (error = git_str_joinpath(&ignores->dir, workdir, local.ptr)) < 0 || + (error = git_path_validate_str_length(repo, &ignores->dir)) < 0) { + /* Nothing, we just want to stop on the first error */ + } + + git_str_dispose(&local); + } else { + if (!(error = git_str_joinpath(&ignores->dir, path, ""))) + error = git_path_validate_str_length(NULL, &ignores->dir); + } + + if (error < 0) + goto cleanup; + + if (workdir && !git__prefixcmp(ignores->dir.ptr, workdir)) + ignores->dir_root = strlen(workdir); + + /* set up internals */ + if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0) + goto cleanup; + + /* load .gitignore up the path */ + if (workdir != NULL) { + error = git_fs_path_walk_up( + &ignores->dir, workdir, push_one_ignore, ignores); + if (error < 0) + goto cleanup; + } + + /* load .git/info/exclude if possible */ + if ((error = git_repository__item_path(&infopath, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || + (error = push_ignore_file(ignores, &ignores->ign_global, infopath.ptr, GIT_IGNORE_FILE_INREPO)) < 0) { + if (error != GIT_ENOTFOUND) + goto cleanup; + error = 0; + } + + /* load core.excludesfile */ + if (git_repository_attr_cache(repo)->cfg_excl_file != NULL) + error = push_ignore_file( + ignores, &ignores->ign_global, NULL, + git_repository_attr_cache(repo)->cfg_excl_file); + +cleanup: + git_str_dispose(&infopath); + if (error < 0) + git_ignore__free(ignores); + + return error; +} + +int git_ignore__push_dir(git_ignores *ign, const char *dir) +{ + if (git_str_joinpath(&ign->dir, ign->dir.ptr, dir) < 0) + return -1; + + ign->depth++; + + return push_ignore_file( + ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); +} + +int git_ignore__pop_dir(git_ignores *ign) +{ + if (ign->ign_path.length > 0) { + git_attr_file *file = git_vector_last(&ign->ign_path); + const char *start = file->entry->path, *end; + + /* - ign->dir looks something like "/home/user/a/b/" (or "a/b/c/d/") + * - file->path looks something like "a/b/.gitignore + * + * We are popping the last directory off ign->dir. We also want + * to remove the file from the vector if the popped directory + * matches the ignore path. We need to test if the "a/b" part of + * the file key matches the path we are about to pop. + */ + + if ((end = strrchr(start, '/')) != NULL) { + size_t dirlen = (end - start) + 1; + const char *relpath = ign->dir.ptr + ign->dir_root; + size_t pathlen = ign->dir.size - ign->dir_root; + + if (pathlen == dirlen && !memcmp(relpath, start, dirlen)) { + git_vector_pop(&ign->ign_path); + git_attr_file__free(file); + } + } + } + + if (--ign->depth > 0) { + git_str_rtruncate_at_char(&ign->dir, '/'); + git_fs_path_to_dir(&ign->dir); + } + + return 0; +} + +void git_ignore__free(git_ignores *ignores) +{ + unsigned int i; + git_attr_file *file; + + git_attr_file__free(ignores->ign_internal); + + git_vector_foreach(&ignores->ign_path, i, file) { + git_attr_file__free(file); + ignores->ign_path.contents[i] = NULL; + } + git_vector_free(&ignores->ign_path); + + git_vector_foreach(&ignores->ign_global, i, file) { + git_attr_file__free(file); + ignores->ign_global.contents[i] = NULL; + } + git_vector_free(&ignores->ign_global); + + git_str_dispose(&ignores->dir); +} + +static bool ignore_lookup_in_rules( + int *ignored, git_attr_file *file, git_attr_path *path) +{ + size_t j; + git_attr_fnmatch *match; + + git_vector_rforeach(&file->rules, j, match) { + if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && + path->is_dir == GIT_DIR_FLAG_FALSE) + continue; + if (git_attr_fnmatch__match(match, path)) { + *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ? + GIT_IGNORE_TRUE : GIT_IGNORE_FALSE; + return true; + } + } + + return false; +} + +int git_ignore__lookup( + int *out, git_ignores *ignores, const char *pathname, git_dir_flag dir_flag) +{ + size_t i; + git_attr_file *file; + git_attr_path path; + + *out = GIT_IGNORE_NOTFOUND; + + if (git_attr_path__init( + &path, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0) + return -1; + + /* first process builtins - success means path was found */ + if (ignore_lookup_in_rules(out, ignores->ign_internal, &path)) + goto cleanup; + + /* next process files in the path. + * this process has to process ignores in reverse order + * to ensure correct prioritization of rules + */ + git_vector_rforeach(&ignores->ign_path, i, file) { + if (ignore_lookup_in_rules(out, file, &path)) + goto cleanup; + } + + /* last process global ignores */ + git_vector_foreach(&ignores->ign_global, i, file) { + if (ignore_lookup_in_rules(out, file, &path)) + goto cleanup; + } + +cleanup: + git_attr_path__free(&path); + return 0; +} + +int git_ignore_add_rule(git_repository *repo, const char *rules) +{ + int error; + git_attr_file *ign_internal = NULL; + + if ((error = get_internal_ignores(&ign_internal, repo)) < 0) + return error; + + error = parse_ignore_file(repo, ign_internal, rules, false); + git_attr_file__free(ign_internal); + + return error; +} + +int git_ignore_clear_internal_rules(git_repository *repo) +{ + int error; + git_attr_file *ign_internal; + + if ((error = get_internal_ignores(&ign_internal, repo)) < 0) + return error; + + if (!(error = git_attr_file__clear_rules(ign_internal, true))) + error = parse_ignore_file( + repo, ign_internal, GIT_IGNORE_DEFAULT_RULES, false); + + git_attr_file__free(ign_internal); + return error; +} + +int git_ignore_path_is_ignored( + int *ignored, + git_repository *repo, + const char *pathname) +{ + int error; + const char *workdir; + git_attr_path path; + git_ignores ignores; + unsigned int i; + git_attr_file *file; + git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(ignored); + GIT_ASSERT_ARG(pathname); + + workdir = git_repository_workdir(repo); + + memset(&path, 0, sizeof(path)); + memset(&ignores, 0, sizeof(ignores)); + + if (!git__suffixcmp(pathname, "/")) + dir_flag = GIT_DIR_FLAG_TRUE; + else if (git_repository_is_bare(repo)) + dir_flag = GIT_DIR_FLAG_FALSE; + + if ((error = git_attr_path__init(&path, pathname, workdir, dir_flag)) < 0 || + (error = git_ignore__for_path(repo, path.path, &ignores)) < 0) + goto cleanup; + + while (1) { + /* first process builtins - success means path was found */ + if (ignore_lookup_in_rules(ignored, ignores.ign_internal, &path)) + goto cleanup; + + /* next process files in the path */ + git_vector_foreach(&ignores.ign_path, i, file) { + if (ignore_lookup_in_rules(ignored, file, &path)) + goto cleanup; + } + + /* last process global ignores */ + git_vector_foreach(&ignores.ign_global, i, file) { + if (ignore_lookup_in_rules(ignored, file, &path)) + goto cleanup; + } + + /* move up one directory */ + if (path.basename == path.path) + break; + path.basename[-1] = '\0'; + while (path.basename > path.path && *path.basename != '/') + path.basename--; + if (path.basename > path.path) + path.basename++; + path.is_dir = 1; + + if ((error = git_ignore__pop_dir(&ignores)) < 0) + break; + } + + *ignored = 0; + +cleanup: + git_attr_path__free(&path); + git_ignore__free(&ignores); + return error; +} + +int git_ignore__check_pathspec_for_exact_ignores( + git_repository *repo, + git_vector *vspec, + bool no_fnmatch) +{ + int error = 0; + size_t i; + git_attr_fnmatch *match; + int ignored; + git_str path = GIT_STR_INIT; + const char *filename; + git_index *idx; + + if ((error = git_repository__ensure_not_bare( + repo, "validate pathspec")) < 0 || + (error = git_repository_index(&idx, repo)) < 0) + return error; + + git_vector_foreach(vspec, i, match) { + /* skip wildcard matches (if they are being used) */ + if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 && + !no_fnmatch) + continue; + + filename = match->pattern; + + /* if file is already in the index, it's fine */ + if (git_index_get_bypath(idx, filename, 0) != NULL) + continue; + + if ((error = git_repository_workdir_path(&path, repo, filename)) < 0) + break; + + /* is there a file on disk that matches this exactly? */ + if (!git_fs_path_isfile(path.ptr)) + continue; + + /* is that file ignored? */ + if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0) + break; + + if (ignored) { + git_error_set(GIT_ERROR_INVALID, "pathspec contains ignored file '%s'", + filename); + error = GIT_EINVALIDSPEC; + break; + } + } + + git_index_free(idx); + git_str_dispose(&path); + + return error; +} diff --git a/src/libgit2/ignore.h b/src/libgit2/ignore.h new file mode 100644 index 000000000..aa5ca62b7 --- /dev/null +++ b/src/libgit2/ignore.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_ignore_h__ +#define INCLUDE_ignore_h__ + +#include "common.h" + +#include "repository.h" +#include "vector.h" +#include "attr_file.h" + +#define GIT_IGNORE_FILE ".gitignore" +#define GIT_IGNORE_FILE_INREPO "exclude" +#define GIT_IGNORE_FILE_XDG "ignore" + +/* The git_ignores structure maintains three sets of ignores: + * - internal ignores + * - per directory ignores + * - global ignores (at lower priority than the others) + * As you traverse from one directory to another, you can push and pop + * directories onto git_ignores list efficiently. + */ +typedef struct { + git_repository *repo; + git_str dir; /* current directory reflected in ign_path */ + git_attr_file *ign_internal; + git_vector ign_path; + git_vector ign_global; + size_t dir_root; /* offset in dir to repo root */ + int ignore_case; + int depth; +} git_ignores; + +extern int git_ignore__for_path( + git_repository *repo, const char *path, git_ignores *ign); + +extern int git_ignore__push_dir(git_ignores *ign, const char *dir); + +extern int git_ignore__pop_dir(git_ignores *ign); + +extern void git_ignore__free(git_ignores *ign); + +enum { + GIT_IGNORE_UNCHECKED = -2, + GIT_IGNORE_NOTFOUND = -1, + GIT_IGNORE_FALSE = 0, + GIT_IGNORE_TRUE = 1 +}; + +extern int git_ignore__lookup(int *out, git_ignores *ign, const char *path, git_dir_flag dir_flag); + +/* command line Git sometimes generates an error message if given a + * pathspec that contains an exact match to an ignored file (provided + * --force isn't also given). This makes it easy to check it that has + * happened. Returns GIT_EINVALIDSPEC if the pathspec contains ignored + * exact matches (that are not already present in the index). + */ +extern int git_ignore__check_pathspec_for_exact_ignores( + git_repository *repo, git_vector *pathspec, bool no_fnmatch); + +#endif diff --git a/src/libgit2/index.c b/src/libgit2/index.c new file mode 100644 index 000000000..aa97c6421 --- /dev/null +++ b/src/libgit2/index.c @@ -0,0 +1,3765 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "index.h" + +#include + +#include "repository.h" +#include "tree.h" +#include "tree-cache.h" +#include "hash.h" +#include "iterator.h" +#include "pathspec.h" +#include "ignore.h" +#include "blob.h" +#include "idxmap.h" +#include "diff.h" +#include "varint.h" +#include "path.h" + +#include "git2/odb.h" +#include "git2/oid.h" +#include "git2/blob.h" +#include "git2/config.h" +#include "git2/sys/index.h" + +static int index_apply_to_wd_diff(git_index *index, int action, const git_strarray *paths, + unsigned int flags, + git_index_matched_path_cb cb, void *payload); + +#define minimal_entry_size (offsetof(struct entry_short, path)) + +static const size_t INDEX_HEADER_SIZE = 12; + +static const unsigned int INDEX_VERSION_NUMBER_DEFAULT = 2; +static const unsigned int INDEX_VERSION_NUMBER_LB = 2; +static const unsigned int INDEX_VERSION_NUMBER_EXT = 3; +static const unsigned int INDEX_VERSION_NUMBER_COMP = 4; +static const unsigned int INDEX_VERSION_NUMBER_UB = 4; + +static const unsigned int INDEX_HEADER_SIG = 0x44495243; +static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'}; +static const char INDEX_EXT_UNMERGED_SIG[] = {'R', 'E', 'U', 'C'}; +static const char INDEX_EXT_CONFLICT_NAME_SIG[] = {'N', 'A', 'M', 'E'}; + +#define INDEX_OWNER(idx) ((git_repository *)(GIT_REFCOUNT_OWNER(idx))) + +struct index_header { + uint32_t signature; + uint32_t version; + uint32_t entry_count; +}; + +struct index_extension { + char signature[4]; + uint32_t extension_size; +}; + +struct entry_time { + uint32_t seconds; + uint32_t nanoseconds; +}; + +struct entry_short { + struct entry_time ctime; + struct entry_time mtime; + uint32_t dev; + uint32_t ino; + uint32_t mode; + uint32_t uid; + uint32_t gid; + uint32_t file_size; + git_oid oid; + uint16_t flags; + char path[1]; /* arbitrary length */ +}; + +struct entry_long { + struct entry_time ctime; + struct entry_time mtime; + uint32_t dev; + uint32_t ino; + uint32_t mode; + uint32_t uid; + uint32_t gid; + uint32_t file_size; + git_oid oid; + uint16_t flags; + uint16_t flags_extended; + char path[1]; /* arbitrary length */ +}; + +struct entry_srch_key { + const char *path; + size_t pathlen; + int stage; +}; + +struct entry_internal { + git_index_entry entry; + size_t pathlen; + char path[GIT_FLEX_ARRAY]; +}; + +struct reuc_entry_internal { + git_index_reuc_entry entry; + size_t pathlen; + char path[GIT_FLEX_ARRAY]; +}; + +bool git_index__enforce_unsaved_safety = false; + +/* local declarations */ +static int read_extension(size_t *read_len, git_index *index, const char *buffer, size_t buffer_size); +static int read_header(struct index_header *dest, const void *buffer); + +static int parse_index(git_index *index, const char *buffer, size_t buffer_size); +static bool is_index_extended(git_index *index); +static int write_index(unsigned char checksum[GIT_HASH_SHA1_SIZE], size_t *checksum_size, git_index *index, git_filebuf *file); + +static void index_entry_free(git_index_entry *entry); +static void index_entry_reuc_free(git_index_reuc_entry *reuc); + +GIT_INLINE(int) index_map_set(git_idxmap *map, git_index_entry *e, bool ignore_case) +{ + if (ignore_case) + return git_idxmap_icase_set((git_idxmap_icase *) map, e, e); + else + return git_idxmap_set(map, e, e); +} + +GIT_INLINE(int) index_map_delete(git_idxmap *map, git_index_entry *e, bool ignore_case) +{ + if (ignore_case) + return git_idxmap_icase_delete((git_idxmap_icase *) map, e); + else + return git_idxmap_delete(map, e); +} + +GIT_INLINE(int) index_map_resize(git_idxmap *map, size_t count, bool ignore_case) +{ + if (ignore_case) + return git_idxmap_icase_resize((git_idxmap_icase *) map, count); + else + return git_idxmap_resize(map, count); +} + +int git_index_entry_srch(const void *key, const void *array_member) +{ + const struct entry_srch_key *srch_key = key; + const struct entry_internal *entry = array_member; + int cmp; + size_t len1, len2, len; + + len1 = srch_key->pathlen; + len2 = entry->pathlen; + len = len1 < len2 ? len1 : len2; + + cmp = memcmp(srch_key->path, entry->path, len); + if (cmp) + return cmp; + if (len1 < len2) + return -1; + if (len1 > len2) + return 1; + + if (srch_key->stage != GIT_INDEX_STAGE_ANY) + return srch_key->stage - GIT_INDEX_ENTRY_STAGE(&entry->entry); + + return 0; +} + +int git_index_entry_isrch(const void *key, const void *array_member) +{ + const struct entry_srch_key *srch_key = key; + const struct entry_internal *entry = array_member; + int cmp; + size_t len1, len2, len; + + len1 = srch_key->pathlen; + len2 = entry->pathlen; + len = len1 < len2 ? len1 : len2; + + cmp = strncasecmp(srch_key->path, entry->path, len); + + if (cmp) + return cmp; + if (len1 < len2) + return -1; + if (len1 > len2) + return 1; + + if (srch_key->stage != GIT_INDEX_STAGE_ANY) + return srch_key->stage - GIT_INDEX_ENTRY_STAGE(&entry->entry); + + return 0; +} + +static int index_entry_srch_path(const void *path, const void *array_member) +{ + const git_index_entry *entry = array_member; + + return strcmp((const char *)path, entry->path); +} + +static int index_entry_isrch_path(const void *path, const void *array_member) +{ + const git_index_entry *entry = array_member; + + return strcasecmp((const char *)path, entry->path); +} + +int git_index_entry_cmp(const void *a, const void *b) +{ + int diff; + const git_index_entry *entry_a = a; + const git_index_entry *entry_b = b; + + diff = strcmp(entry_a->path, entry_b->path); + + if (diff == 0) + diff = (GIT_INDEX_ENTRY_STAGE(entry_a) - GIT_INDEX_ENTRY_STAGE(entry_b)); + + return diff; +} + +int git_index_entry_icmp(const void *a, const void *b) +{ + int diff; + const git_index_entry *entry_a = a; + const git_index_entry *entry_b = b; + + diff = strcasecmp(entry_a->path, entry_b->path); + + if (diff == 0) + diff = (GIT_INDEX_ENTRY_STAGE(entry_a) - GIT_INDEX_ENTRY_STAGE(entry_b)); + + return diff; +} + +static int conflict_name_cmp(const void *a, const void *b) +{ + const git_index_name_entry *name_a = a; + const git_index_name_entry *name_b = b; + + if (name_a->ancestor && !name_b->ancestor) + return 1; + + if (!name_a->ancestor && name_b->ancestor) + return -1; + + if (name_a->ancestor) + return strcmp(name_a->ancestor, name_b->ancestor); + + if (!name_a->ours || !name_b->ours) + return 0; + + return strcmp(name_a->ours, name_b->ours); +} + +/** + * TODO: enable this when resolving case insensitive conflicts + */ +#if 0 +static int conflict_name_icmp(const void *a, const void *b) +{ + const git_index_name_entry *name_a = a; + const git_index_name_entry *name_b = b; + + if (name_a->ancestor && !name_b->ancestor) + return 1; + + if (!name_a->ancestor && name_b->ancestor) + return -1; + + if (name_a->ancestor) + return strcasecmp(name_a->ancestor, name_b->ancestor); + + if (!name_a->ours || !name_b->ours) + return 0; + + return strcasecmp(name_a->ours, name_b->ours); +} +#endif + +static int reuc_srch(const void *key, const void *array_member) +{ + const git_index_reuc_entry *reuc = array_member; + + return strcmp(key, reuc->path); +} + +static int reuc_isrch(const void *key, const void *array_member) +{ + const git_index_reuc_entry *reuc = array_member; + + return strcasecmp(key, reuc->path); +} + +static int reuc_cmp(const void *a, const void *b) +{ + const git_index_reuc_entry *info_a = a; + const git_index_reuc_entry *info_b = b; + + return strcmp(info_a->path, info_b->path); +} + +static int reuc_icmp(const void *a, const void *b) +{ + const git_index_reuc_entry *info_a = a; + const git_index_reuc_entry *info_b = b; + + return strcasecmp(info_a->path, info_b->path); +} + +static void index_entry_reuc_free(git_index_reuc_entry *reuc) +{ + git__free(reuc); +} + +static void index_entry_free(git_index_entry *entry) +{ + if (!entry) + return; + + memset(&entry->id, 0, sizeof(entry->id)); + git__free(entry); +} + +unsigned int git_index__create_mode(unsigned int mode) +{ + if (S_ISLNK(mode)) + return S_IFLNK; + + if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR)) + return (S_IFLNK | S_IFDIR); + + return S_IFREG | GIT_PERMS_CANONICAL(mode); +} + +static unsigned int index_merge_mode( + git_index *index, git_index_entry *existing, unsigned int mode) +{ + if (index->no_symlinks && S_ISREG(mode) && + existing && S_ISLNK(existing->mode)) + return existing->mode; + + if (index->distrust_filemode && S_ISREG(mode)) + return (existing && S_ISREG(existing->mode)) ? + existing->mode : git_index__create_mode(0666); + + return git_index__create_mode(mode); +} + +GIT_INLINE(int) index_find_in_entries( + size_t *out, git_vector *entries, git_vector_cmp entry_srch, + const char *path, size_t path_len, int stage) +{ + struct entry_srch_key srch_key; + srch_key.path = path; + srch_key.pathlen = !path_len ? strlen(path) : path_len; + srch_key.stage = stage; + return git_vector_bsearch2(out, entries, entry_srch, &srch_key); +} + +GIT_INLINE(int) index_find( + size_t *out, git_index *index, + const char *path, size_t path_len, int stage) +{ + git_vector_sort(&index->entries); + + return index_find_in_entries( + out, &index->entries, index->entries_search, path, path_len, stage); +} + +void git_index__set_ignore_case(git_index *index, bool ignore_case) +{ + index->ignore_case = ignore_case; + + if (ignore_case) { + index->entries_cmp_path = git__strcasecmp_cb; + index->entries_search = git_index_entry_isrch; + index->entries_search_path = index_entry_isrch_path; + index->reuc_search = reuc_isrch; + } else { + index->entries_cmp_path = git__strcmp_cb; + index->entries_search = git_index_entry_srch; + index->entries_search_path = index_entry_srch_path; + index->reuc_search = reuc_srch; + } + + git_vector_set_cmp(&index->entries, + ignore_case ? git_index_entry_icmp : git_index_entry_cmp); + git_vector_sort(&index->entries); + + git_vector_set_cmp(&index->reuc, ignore_case ? reuc_icmp : reuc_cmp); + git_vector_sort(&index->reuc); +} + +int git_index_open(git_index **index_out, const char *index_path) +{ + git_index *index; + int error = -1; + + GIT_ASSERT_ARG(index_out); + + index = git__calloc(1, sizeof(git_index)); + GIT_ERROR_CHECK_ALLOC(index); + + if (git_pool_init(&index->tree_pool, 1) < 0) + goto fail; + + if (index_path != NULL) { + index->index_file_path = git__strdup(index_path); + if (!index->index_file_path) + goto fail; + + /* Check if index file is stored on disk already */ + if (git_fs_path_exists(index->index_file_path) == true) + index->on_disk = 1; + } + + if (git_vector_init(&index->entries, 32, git_index_entry_cmp) < 0 || + git_idxmap_new(&index->entries_map) < 0 || + git_vector_init(&index->names, 8, conflict_name_cmp) < 0 || + git_vector_init(&index->reuc, 8, reuc_cmp) < 0 || + git_vector_init(&index->deleted, 8, git_index_entry_cmp) < 0) + goto fail; + + index->entries_cmp_path = git__strcmp_cb; + index->entries_search = git_index_entry_srch; + index->entries_search_path = index_entry_srch_path; + index->reuc_search = reuc_srch; + index->version = INDEX_VERSION_NUMBER_DEFAULT; + + if (index_path != NULL && (error = git_index_read(index, true)) < 0) + goto fail; + + *index_out = index; + GIT_REFCOUNT_INC(index); + + return 0; + +fail: + git_pool_clear(&index->tree_pool); + git_index_free(index); + return error; +} + +int git_index_new(git_index **out) +{ + return git_index_open(out, NULL); +} + +static void index_free(git_index *index) +{ + /* index iterators increment the refcount of the index, so if we + * get here then there should be no outstanding iterators. + */ + if (git_atomic32_get(&index->readers)) + return; + + git_index_clear(index); + git_idxmap_free(index->entries_map); + git_vector_free(&index->entries); + git_vector_free(&index->names); + git_vector_free(&index->reuc); + git_vector_free(&index->deleted); + + git__free(index->index_file_path); + + git__memzero(index, sizeof(*index)); + git__free(index); +} + +void git_index_free(git_index *index) +{ + if (index == NULL) + return; + + GIT_REFCOUNT_DEC(index, index_free); +} + +/* call with locked index */ +static void index_free_deleted(git_index *index) +{ + int readers = (int)git_atomic32_get(&index->readers); + size_t i; + + if (readers > 0 || !index->deleted.length) + return; + + for (i = 0; i < index->deleted.length; ++i) { + git_index_entry *ie = git_atomic_swap(index->deleted.contents[i], NULL); + index_entry_free(ie); + } + + git_vector_clear(&index->deleted); +} + +/* call with locked index */ +static int index_remove_entry(git_index *index, size_t pos) +{ + int error = 0; + git_index_entry *entry = git_vector_get(&index->entries, pos); + + if (entry != NULL) { + git_tree_cache_invalidate_path(index->tree, entry->path); + index_map_delete(index->entries_map, entry, index->ignore_case); + } + + error = git_vector_remove(&index->entries, pos); + + if (!error) { + if (git_atomic32_get(&index->readers) > 0) { + error = git_vector_insert(&index->deleted, entry); + } else { + index_entry_free(entry); + } + + index->dirty = 1; + } + + return error; +} + +int git_index_clear(git_index *index) +{ + int error = 0; + + GIT_ASSERT_ARG(index); + + index->dirty = 1; + index->tree = NULL; + git_pool_clear(&index->tree_pool); + + git_idxmap_clear(index->entries_map); + while (!error && index->entries.length > 0) + error = index_remove_entry(index, index->entries.length - 1); + + if (error) + goto done; + + index_free_deleted(index); + + if ((error = git_index_name_clear(index)) < 0 || + (error = git_index_reuc_clear(index)) < 0) + goto done; + + git_futils_filestamp_set(&index->stamp, NULL); + +done: + return error; +} + +static int create_index_error(int error, const char *msg) +{ + git_error_set_str(GIT_ERROR_INDEX, msg); + return error; +} + +int git_index_set_caps(git_index *index, int caps) +{ + unsigned int old_ignore_case; + + GIT_ASSERT_ARG(index); + + old_ignore_case = index->ignore_case; + + if (caps == GIT_INDEX_CAPABILITY_FROM_OWNER) { + git_repository *repo = INDEX_OWNER(index); + int val; + + if (!repo) + return create_index_error( + -1, "cannot access repository to set index caps"); + + if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_IGNORECASE)) + index->ignore_case = (val != 0); + if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_FILEMODE)) + index->distrust_filemode = (val == 0); + if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_SYMLINKS)) + index->no_symlinks = (val == 0); + } + else { + index->ignore_case = ((caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0); + index->distrust_filemode = ((caps & GIT_INDEX_CAPABILITY_NO_FILEMODE) != 0); + index->no_symlinks = ((caps & GIT_INDEX_CAPABILITY_NO_SYMLINKS) != 0); + } + + if (old_ignore_case != index->ignore_case) { + git_index__set_ignore_case(index, (bool)index->ignore_case); + } + + return 0; +} + +int git_index_caps(const git_index *index) +{ + return ((index->ignore_case ? GIT_INDEX_CAPABILITY_IGNORE_CASE : 0) | + (index->distrust_filemode ? GIT_INDEX_CAPABILITY_NO_FILEMODE : 0) | + (index->no_symlinks ? GIT_INDEX_CAPABILITY_NO_SYMLINKS : 0)); +} + +#ifndef GIT_DEPRECATE_HARD +const git_oid *git_index_checksum(git_index *index) +{ + return (git_oid *)index->checksum; +} +#endif + +/** + * Returns 1 for changed, 0 for not changed and <0 for errors + */ +static int compare_checksum(git_index *index) +{ + int fd; + ssize_t bytes_read; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + + if ((fd = p_open(index->index_file_path, O_RDONLY)) < 0) + return fd; + + if (p_lseek(fd, (0 - (ssize_t)checksum_size), SEEK_END) < 0) { + p_close(fd); + git_error_set(GIT_ERROR_OS, "failed to seek to end of file"); + return -1; + } + + bytes_read = p_read(fd, checksum, checksum_size); + p_close(fd); + + if (bytes_read < (ssize_t)checksum_size) + return -1; + + return !!memcmp(checksum, index->checksum, checksum_size); +} + +int git_index_read(git_index *index, int force) +{ + int error = 0, updated; + git_str buffer = GIT_STR_INIT; + git_futils_filestamp stamp = index->stamp; + + if (!index->index_file_path) + return create_index_error(-1, + "failed to read index: The index is in-memory only"); + + index->on_disk = git_fs_path_exists(index->index_file_path); + + if (!index->on_disk) { + if (force && (error = git_index_clear(index)) < 0) + return error; + + index->dirty = 0; + return 0; + } + + if ((updated = git_futils_filestamp_check(&stamp, index->index_file_path) < 0) || + ((updated = compare_checksum(index)) < 0)) { + git_error_set( + GIT_ERROR_INDEX, + "failed to read index: '%s' no longer exists", + index->index_file_path); + return updated; + } + + if (!updated && !force) + return 0; + + error = git_futils_readbuffer(&buffer, index->index_file_path); + if (error < 0) + return error; + + index->tree = NULL; + git_pool_clear(&index->tree_pool); + + error = git_index_clear(index); + + if (!error) + error = parse_index(index, buffer.ptr, buffer.size); + + if (!error) { + git_futils_filestamp_set(&index->stamp, &stamp); + index->dirty = 0; + } + + git_str_dispose(&buffer); + return error; +} + +int git_index_read_safely(git_index *index) +{ + if (git_index__enforce_unsaved_safety && index->dirty) { + git_error_set(GIT_ERROR_INDEX, + "the index has unsaved changes that would be overwritten by this operation"); + return GIT_EINDEXDIRTY; + } + + return git_index_read(index, false); +} + +static bool is_racy_entry(git_index *index, const git_index_entry *entry) +{ + /* Git special-cases submodules in the check */ + if (S_ISGITLINK(entry->mode)) + return false; + + return git_index_entry_newer_than_index(entry, index); +} + +/* + * Force the next diff to take a look at those entries which have the + * same timestamp as the current index. + */ +static int truncate_racily_clean(git_index *index) +{ + size_t i; + int error; + git_index_entry *entry; + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_vector paths = GIT_VECTOR_INIT; + git_diff_delta *delta; + + /* Nothing to do if there's no repo to talk about */ + if (!INDEX_OWNER(index)) + return 0; + + /* If there's no workdir, we can't know where to even check */ + if (!git_repository_workdir(INDEX_OWNER(index))) + return 0; + + diff_opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_DISABLE_PATHSPEC_MATCH; + git_vector_foreach(&index->entries, i, entry) { + if ((entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE) == 0 && + is_racy_entry(index, entry)) + git_vector_insert(&paths, (char *)entry->path); + } + + if (paths.length == 0) + goto done; + + diff_opts.pathspec.count = paths.length; + diff_opts.pathspec.strings = (char **)paths.contents; + + if ((error = git_diff_index_to_workdir(&diff, INDEX_OWNER(index), index, &diff_opts)) < 0) + return error; + + git_vector_foreach(&diff->deltas, i, delta) { + entry = (git_index_entry *)git_index_get_bypath(index, delta->old_file.path, 0); + + /* Ensure that we have a stage 0 for this file (ie, it's not a + * conflict), otherwise smudging it is quite pointless. + */ + if (entry) { + entry->file_size = 0; + index->dirty = 1; + } + } + +done: + git_diff_free(diff); + git_vector_free(&paths); + return 0; +} + +unsigned git_index_version(git_index *index) +{ + GIT_ASSERT_ARG(index); + + return index->version; +} + +int git_index_set_version(git_index *index, unsigned int version) +{ + GIT_ASSERT_ARG(index); + + if (version < INDEX_VERSION_NUMBER_LB || + version > INDEX_VERSION_NUMBER_UB) { + git_error_set(GIT_ERROR_INDEX, "invalid version number"); + return -1; + } + + index->version = version; + + return 0; +} + +int git_index_write(git_index *index) +{ + git_indexwriter writer = GIT_INDEXWRITER_INIT; + int error; + + truncate_racily_clean(index); + + if ((error = git_indexwriter_init(&writer, index)) == 0 && + (error = git_indexwriter_commit(&writer)) == 0) + index->dirty = 0; + + git_indexwriter_cleanup(&writer); + + return error; +} + +const char *git_index_path(const git_index *index) +{ + GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); + return index->index_file_path; +} + +int git_index_write_tree(git_oid *oid, git_index *index) +{ + git_repository *repo; + + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(index); + + repo = INDEX_OWNER(index); + + if (repo == NULL) + return create_index_error(-1, "Failed to write tree. " + "the index file is not backed up by an existing repository"); + + return git_tree__write_index(oid, index, repo); +} + +int git_index_write_tree_to( + git_oid *oid, git_index *index, git_repository *repo) +{ + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(repo); + + return git_tree__write_index(oid, index, repo); +} + +size_t git_index_entrycount(const git_index *index) +{ + GIT_ASSERT_ARG(index); + + return index->entries.length; +} + +const git_index_entry *git_index_get_byindex( + git_index *index, size_t n) +{ + GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); + + git_vector_sort(&index->entries); + return git_vector_get(&index->entries, n); +} + +const git_index_entry *git_index_get_bypath( + git_index *index, const char *path, int stage) +{ + git_index_entry key = {{ 0 }}; + git_index_entry *value; + + GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); + + key.path = path; + GIT_INDEX_ENTRY_STAGE_SET(&key, stage); + + if (index->ignore_case) + value = git_idxmap_icase_get((git_idxmap_icase *) index->entries_map, &key); + else + value = git_idxmap_get(index->entries_map, &key); + + if (!value) { + git_error_set(GIT_ERROR_INDEX, "index does not contain '%s'", path); + return NULL; + } + + return value; +} + +void git_index_entry__init_from_stat( + git_index_entry *entry, struct stat *st, bool trust_mode) +{ + entry->ctime.seconds = (int32_t)st->st_ctime; + entry->mtime.seconds = (int32_t)st->st_mtime; +#if defined(GIT_USE_NSEC) + entry->mtime.nanoseconds = st->st_mtime_nsec; + entry->ctime.nanoseconds = st->st_ctime_nsec; +#endif + entry->dev = st->st_rdev; + entry->ino = st->st_ino; + entry->mode = (!trust_mode && S_ISREG(st->st_mode)) ? + git_index__create_mode(0666) : git_index__create_mode(st->st_mode); + entry->uid = st->st_uid; + entry->gid = st->st_gid; + entry->file_size = (uint32_t)st->st_size; +} + +static void index_entry_adjust_namemask( + git_index_entry *entry, + size_t path_length) +{ + entry->flags &= ~GIT_INDEX_ENTRY_NAMEMASK; + + if (path_length < GIT_INDEX_ENTRY_NAMEMASK) + entry->flags |= path_length & GIT_INDEX_ENTRY_NAMEMASK; + else + entry->flags |= GIT_INDEX_ENTRY_NAMEMASK; +} + +/* When `from_workdir` is true, we will validate the paths to avoid placing + * paths that are invalid for the working directory on the current filesystem + * (eg, on Windows, we will disallow `GIT~1`, `AUX`, `COM1`, etc). This + * function will *always* prevent `.git` and directory traversal `../` from + * being added to the index. + */ +static int index_entry_create( + git_index_entry **out, + git_repository *repo, + const char *path, + struct stat *st, + bool from_workdir) +{ + size_t pathlen = strlen(path), alloclen; + struct entry_internal *entry; + unsigned int path_valid_flags = GIT_PATH_REJECT_INDEX_DEFAULTS; + uint16_t mode = 0; + + /* always reject placing `.git` in the index and directory traversal. + * when requested, disallow platform-specific filenames and upgrade to + * the platform-specific `.git` tests (eg, `git~1`, etc). + */ + if (from_workdir) + path_valid_flags |= GIT_PATH_REJECT_WORKDIR_DEFAULTS; + if (st) + mode = st->st_mode; + + if (!git_path_is_valid(repo, path, mode, path_valid_flags)) { + git_error_set(GIT_ERROR_INDEX, "invalid path: '%s'", path); + return -1; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(struct entry_internal), pathlen); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + entry = git__calloc(1, alloclen); + GIT_ERROR_CHECK_ALLOC(entry); + + entry->pathlen = pathlen; + memcpy(entry->path, path, pathlen); + entry->entry.path = entry->path; + + *out = (git_index_entry *)entry; + return 0; +} + +static int index_entry_init( + git_index_entry **entry_out, + git_index *index, + const char *rel_path) +{ + int error = 0; + git_index_entry *entry = NULL; + git_str path = GIT_STR_INIT; + struct stat st; + git_oid oid; + git_repository *repo; + + if (INDEX_OWNER(index) == NULL) + return create_index_error(-1, + "could not initialize index entry. " + "Index is not backed up by an existing repository."); + + /* + * FIXME: this is duplicated with the work in + * git_blob__create_from_paths. It should accept an optional stat + * structure so we can pass in the one we have to do here. + */ + repo = INDEX_OWNER(index); + if (git_repository__ensure_not_bare(repo, "create blob from file") < 0) + return GIT_EBAREREPO; + + if (git_repository_workdir_path(&path, repo, rel_path) < 0) + return -1; + + error = git_fs_path_lstat(path.ptr, &st); + git_str_dispose(&path); + + if (error < 0) + return error; + + if (index_entry_create(&entry, INDEX_OWNER(index), rel_path, &st, true) < 0) + return -1; + + /* write the blob to disk and get the oid and stat info */ + error = git_blob__create_from_paths( + &oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true); + + if (error < 0) { + index_entry_free(entry); + return error; + } + + entry->id = oid; + git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode); + + *entry_out = (git_index_entry *)entry; + return 0; +} + +static git_index_reuc_entry *reuc_entry_alloc(const char *path) +{ + size_t pathlen = strlen(path), + structlen = sizeof(struct reuc_entry_internal), + alloclen; + struct reuc_entry_internal *entry; + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, structlen, pathlen) || + GIT_ADD_SIZET_OVERFLOW(&alloclen, alloclen, 1)) + return NULL; + + entry = git__calloc(1, alloclen); + if (!entry) + return NULL; + + entry->pathlen = pathlen; + memcpy(entry->path, path, pathlen); + entry->entry.path = entry->path; + + return (git_index_reuc_entry *)entry; +} + +static int index_entry_reuc_init(git_index_reuc_entry **reuc_out, + const char *path, + int ancestor_mode, const git_oid *ancestor_oid, + int our_mode, const git_oid *our_oid, + int their_mode, const git_oid *their_oid) +{ + git_index_reuc_entry *reuc = NULL; + + GIT_ASSERT_ARG(reuc_out); + GIT_ASSERT_ARG(path); + + *reuc_out = reuc = reuc_entry_alloc(path); + GIT_ERROR_CHECK_ALLOC(reuc); + + if ((reuc->mode[0] = ancestor_mode) > 0) { + GIT_ASSERT(ancestor_oid); + git_oid_cpy(&reuc->oid[0], ancestor_oid); + } + + if ((reuc->mode[1] = our_mode) > 0) { + GIT_ASSERT(our_oid); + git_oid_cpy(&reuc->oid[1], our_oid); + } + + if ((reuc->mode[2] = their_mode) > 0) { + GIT_ASSERT(their_oid); + git_oid_cpy(&reuc->oid[2], their_oid); + } + + return 0; +} + +static void index_entry_cpy( + git_index_entry *tgt, + const git_index_entry *src) +{ + const char *tgt_path = tgt->path; + memcpy(tgt, src, sizeof(*tgt)); + tgt->path = tgt_path; +} + +static int index_entry_dup( + git_index_entry **out, + git_index *index, + const git_index_entry *src) +{ + if (index_entry_create(out, INDEX_OWNER(index), src->path, NULL, false) < 0) + return -1; + + index_entry_cpy(*out, src); + return 0; +} + +static void index_entry_cpy_nocache( + git_index_entry *tgt, + const git_index_entry *src) +{ + git_oid_cpy(&tgt->id, &src->id); + tgt->mode = src->mode; + tgt->flags = src->flags; + tgt->flags_extended = (src->flags_extended & GIT_INDEX_ENTRY_EXTENDED_FLAGS); +} + +static int index_entry_dup_nocache( + git_index_entry **out, + git_index *index, + const git_index_entry *src) +{ + if (index_entry_create(out, INDEX_OWNER(index), src->path, NULL, false) < 0) + return -1; + + index_entry_cpy_nocache(*out, src); + return 0; +} + +static int has_file_name(git_index *index, + const git_index_entry *entry, size_t pos, int ok_to_replace) +{ + size_t len = strlen(entry->path); + int stage = GIT_INDEX_ENTRY_STAGE(entry); + const char *name = entry->path; + + while (pos < index->entries.length) { + struct entry_internal *p = index->entries.contents[pos++]; + + if (len >= p->pathlen) + break; + if (memcmp(name, p->path, len)) + break; + if (GIT_INDEX_ENTRY_STAGE(&p->entry) != stage) + continue; + if (p->path[len] != '/') + continue; + if (!ok_to_replace) + return -1; + + if (index_remove_entry(index, --pos) < 0) + break; + } + return 0; +} + +/* + * Do we have another file with a pathname that is a proper + * subset of the name we're trying to add? + */ +static int has_dir_name(git_index *index, + const git_index_entry *entry, int ok_to_replace) +{ + int stage = GIT_INDEX_ENTRY_STAGE(entry); + const char *name = entry->path; + const char *slash = name + strlen(name); + + for (;;) { + size_t len, pos; + + for (;;) { + if (*--slash == '/') + break; + if (slash <= entry->path) + return 0; + } + len = slash - name; + + if (!index_find(&pos, index, name, len, stage)) { + if (!ok_to_replace) + return -1; + + if (index_remove_entry(index, pos) < 0) + break; + continue; + } + + /* + * Trivial optimization: if we find an entry that + * already matches the sub-directory, then we know + * we're ok, and we can exit. + */ + for (; pos < index->entries.length; ++pos) { + struct entry_internal *p = index->entries.contents[pos]; + + if (p->pathlen <= len || + p->path[len] != '/' || + memcmp(p->path, name, len)) + break; /* not our subdirectory */ + + if (GIT_INDEX_ENTRY_STAGE(&p->entry) == stage) + return 0; + } + } + + return 0; +} + +static int check_file_directory_collision(git_index *index, + git_index_entry *entry, size_t pos, int ok_to_replace) +{ + if (has_file_name(index, entry, pos, ok_to_replace) < 0 || + has_dir_name(index, entry, ok_to_replace) < 0) { + git_error_set(GIT_ERROR_INDEX, + "'%s' appears as both a file and a directory", entry->path); + return -1; + } + + return 0; +} + +static int canonicalize_directory_path( + git_index *index, + git_index_entry *entry, + git_index_entry *existing) +{ + const git_index_entry *match, *best = NULL; + char *search, *sep; + size_t pos, search_len, best_len; + + if (!index->ignore_case) + return 0; + + /* item already exists in the index, simply re-use the existing case */ + if (existing) { + memcpy((char *)entry->path, existing->path, strlen(existing->path)); + return 0; + } + + /* nothing to do */ + if (strchr(entry->path, '/') == NULL) + return 0; + + if ((search = git__strdup(entry->path)) == NULL) + return -1; + + /* starting at the parent directory and descending to the root, find the + * common parent directory. + */ + while (!best && (sep = strrchr(search, '/'))) { + sep[1] = '\0'; + + search_len = strlen(search); + + git_vector_bsearch2( + &pos, &index->entries, index->entries_search_path, search); + + while ((match = git_vector_get(&index->entries, pos))) { + if (GIT_INDEX_ENTRY_STAGE(match) != 0) { + /* conflicts do not contribute to canonical paths */ + } else if (strncmp(search, match->path, search_len) == 0) { + /* prefer an exact match to the input filename */ + best = match; + best_len = search_len; + break; + } else if (strncasecmp(search, match->path, search_len) == 0) { + /* continue walking, there may be a path with an exact + * (case sensitive) match later in the index, but use this + * as the best match until that happens. + */ + if (!best) { + best = match; + best_len = search_len; + } + } else { + break; + } + + pos++; + } + + sep[0] = '\0'; + } + + if (best) + memcpy((char *)entry->path, best->path, best_len); + + git__free(search); + return 0; +} + +static int index_no_dups(void **old, void *new) +{ + const git_index_entry *entry = new; + GIT_UNUSED(old); + git_error_set(GIT_ERROR_INDEX, "'%s' appears multiple times at stage %d", + entry->path, GIT_INDEX_ENTRY_STAGE(entry)); + return GIT_EEXISTS; +} + +static void index_existing_and_best( + git_index_entry **existing, + size_t *existing_position, + git_index_entry **best, + git_index *index, + const git_index_entry *entry) +{ + git_index_entry *e; + size_t pos; + int error; + + error = index_find(&pos, + index, entry->path, 0, GIT_INDEX_ENTRY_STAGE(entry)); + + if (error == 0) { + *existing = index->entries.contents[pos]; + *existing_position = pos; + *best = index->entries.contents[pos]; + return; + } + + *existing = NULL; + *existing_position = 0; + *best = NULL; + + if (GIT_INDEX_ENTRY_STAGE(entry) == 0) { + for (; pos < index->entries.length; pos++) { + int (*strcomp)(const char *a, const char *b) = + index->ignore_case ? git__strcasecmp : git__strcmp; + + e = index->entries.contents[pos]; + + if (strcomp(entry->path, e->path) != 0) + break; + + if (GIT_INDEX_ENTRY_STAGE(e) == GIT_INDEX_STAGE_ANCESTOR) { + *best = e; + continue; + } else { + *best = e; + break; + } + } + } +} + +/* index_insert takes ownership of the new entry - if it can't insert + * it, then it will return an error **and also free the entry**. When + * it replaces an existing entry, it will update the entry_ptr with the + * actual entry in the index (and free the passed in one). + * + * trust_path is whether we use the given path, or whether (on case + * insensitive systems only) we try to canonicalize the given path to + * be within an existing directory. + * + * trust_mode is whether we trust the mode in entry_ptr. + * + * trust_id is whether we trust the id or it should be validated. + */ +static int index_insert( + git_index *index, + git_index_entry **entry_ptr, + int replace, + bool trust_path, + bool trust_mode, + bool trust_id) +{ + git_index_entry *existing, *best, *entry; + size_t path_length, position; + int error; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(entry_ptr); + + entry = *entry_ptr; + + /* Make sure that the path length flag is correct */ + path_length = ((struct entry_internal *)entry)->pathlen; + index_entry_adjust_namemask(entry, path_length); + + /* This entry is now up-to-date and should not be checked for raciness */ + entry->flags_extended |= GIT_INDEX_ENTRY_UPTODATE; + + git_vector_sort(&index->entries); + + /* + * Look if an entry with this path already exists, either staged, or (if + * this entry is a regular staged item) as the "ours" side of a conflict. + */ + index_existing_and_best(&existing, &position, &best, index, entry); + + /* Update the file mode */ + entry->mode = trust_mode ? + git_index__create_mode(entry->mode) : + index_merge_mode(index, best, entry->mode); + + /* Canonicalize the directory name */ + if (!trust_path && (error = canonicalize_directory_path(index, entry, best)) < 0) + goto out; + + /* Ensure that the given id exists (unless it's a submodule) */ + if (!trust_id && INDEX_OWNER(index) && + (entry->mode & GIT_FILEMODE_COMMIT) != GIT_FILEMODE_COMMIT) { + + if (!git_object__is_valid(INDEX_OWNER(index), &entry->id, + git_object__type_from_filemode(entry->mode))) { + error = -1; + goto out; + } + } + + /* Look for tree / blob name collisions, removing conflicts if requested */ + if ((error = check_file_directory_collision(index, entry, position, replace)) < 0) + goto out; + + /* + * If we are replacing an existing item, overwrite the existing entry + * and return it in place of the passed in one. + */ + if (existing) { + if (replace) { + index_entry_cpy(existing, entry); + + if (trust_path) + memcpy((char *)existing->path, entry->path, strlen(entry->path)); + } + + index_entry_free(entry); + *entry_ptr = existing; + } else { + /* + * If replace is not requested or no existing entry exists, insert + * at the sorted position. (Since we re-sort after each insert to + * check for dups, this is actually cheaper in the long run.) + */ + if ((error = git_vector_insert_sorted(&index->entries, entry, index_no_dups)) < 0 || + (error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) + goto out; + } + + index->dirty = 1; + +out: + if (error < 0) { + index_entry_free(*entry_ptr); + *entry_ptr = NULL; + } + + return error; +} + +static int index_conflict_to_reuc(git_index *index, const char *path) +{ + const git_index_entry *conflict_entries[3]; + int ancestor_mode, our_mode, their_mode; + git_oid const *ancestor_oid, *our_oid, *their_oid; + int ret; + + if ((ret = git_index_conflict_get(&conflict_entries[0], + &conflict_entries[1], &conflict_entries[2], index, path)) < 0) + return ret; + + ancestor_mode = conflict_entries[0] == NULL ? 0 : conflict_entries[0]->mode; + our_mode = conflict_entries[1] == NULL ? 0 : conflict_entries[1]->mode; + their_mode = conflict_entries[2] == NULL ? 0 : conflict_entries[2]->mode; + + ancestor_oid = conflict_entries[0] == NULL ? NULL : &conflict_entries[0]->id; + our_oid = conflict_entries[1] == NULL ? NULL : &conflict_entries[1]->id; + their_oid = conflict_entries[2] == NULL ? NULL : &conflict_entries[2]->id; + + if ((ret = git_index_reuc_add(index, path, ancestor_mode, ancestor_oid, + our_mode, our_oid, their_mode, their_oid)) >= 0) + ret = git_index_conflict_remove(index, path); + + return ret; +} + +GIT_INLINE(bool) is_file_or_link(const int filemode) +{ + return (filemode == GIT_FILEMODE_BLOB || + filemode == GIT_FILEMODE_BLOB_EXECUTABLE || + filemode == GIT_FILEMODE_LINK); +} + +GIT_INLINE(bool) valid_filemode(const int filemode) +{ + return (is_file_or_link(filemode) || filemode == GIT_FILEMODE_COMMIT); +} + +int git_index_add_from_buffer( + git_index *index, const git_index_entry *source_entry, + const void *buffer, size_t len) +{ + git_index_entry *entry = NULL; + int error = 0; + git_oid id; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(source_entry && source_entry->path); + + if (INDEX_OWNER(index) == NULL) + return create_index_error(-1, + "could not initialize index entry. " + "Index is not backed up by an existing repository."); + + if (!is_file_or_link(source_entry->mode)) { + git_error_set(GIT_ERROR_INDEX, "invalid filemode"); + return -1; + } + + if (len > UINT32_MAX) { + git_error_set(GIT_ERROR_INDEX, "buffer is too large"); + return -1; + } + + if (index_entry_dup(&entry, index, source_entry) < 0) + return -1; + + error = git_blob_create_from_buffer(&id, INDEX_OWNER(index), buffer, len); + if (error < 0) { + index_entry_free(entry); + return error; + } + + git_oid_cpy(&entry->id, &id); + entry->file_size = (uint32_t)len; + + if ((error = index_insert(index, &entry, 1, true, true, true)) < 0) + return error; + + /* Adding implies conflict was resolved, move conflict entries to REUC */ + if ((error = index_conflict_to_reuc(index, entry->path)) < 0 && error != GIT_ENOTFOUND) + return error; + + git_tree_cache_invalidate_path(index->tree, entry->path); + return 0; +} + +static int add_repo_as_submodule(git_index_entry **out, git_index *index, const char *path) +{ + git_repository *sub; + git_str abspath = GIT_STR_INIT; + git_repository *repo = INDEX_OWNER(index); + git_reference *head; + git_index_entry *entry; + struct stat st; + int error; + + if ((error = git_repository_workdir_path(&abspath, repo, path)) < 0) + return error; + + if ((error = p_stat(abspath.ptr, &st)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to stat repository dir"); + return -1; + } + + if (index_entry_create(&entry, INDEX_OWNER(index), path, &st, true) < 0) + return -1; + + git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode); + + if ((error = git_repository_open(&sub, abspath.ptr)) < 0) + return error; + + if ((error = git_repository_head(&head, sub)) < 0) + return error; + + git_oid_cpy(&entry->id, git_reference_target(head)); + entry->mode = GIT_FILEMODE_COMMIT; + + git_reference_free(head); + git_repository_free(sub); + git_str_dispose(&abspath); + + *out = entry; + return 0; +} + +int git_index_add_bypath(git_index *index, const char *path) +{ + git_index_entry *entry = NULL; + int ret; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + + if ((ret = index_entry_init(&entry, index, path)) == 0) + ret = index_insert(index, &entry, 1, false, false, true); + + /* If we were given a directory, let's see if it's a submodule */ + if (ret < 0 && ret != GIT_EDIRECTORY) + return ret; + + if (ret == GIT_EDIRECTORY) { + git_submodule *sm; + git_error_state err; + + git_error_state_capture(&err, ret); + + ret = git_submodule_lookup(&sm, INDEX_OWNER(index), path); + if (ret == GIT_ENOTFOUND) + return git_error_state_restore(&err); + + git_error_state_free(&err); + + /* + * EEXISTS means that there is a repository at that path, but it's not known + * as a submodule. We add its HEAD as an entry and don't register it. + */ + if (ret == GIT_EEXISTS) { + if ((ret = add_repo_as_submodule(&entry, index, path)) < 0) + return ret; + + if ((ret = index_insert(index, &entry, 1, false, false, true)) < 0) + return ret; + } else if (ret < 0) { + return ret; + } else { + ret = git_submodule_add_to_index(sm, false); + git_submodule_free(sm); + return ret; + } + } + + /* Adding implies conflict was resolved, move conflict entries to REUC */ + if ((ret = index_conflict_to_reuc(index, path)) < 0 && ret != GIT_ENOTFOUND) + return ret; + + git_tree_cache_invalidate_path(index->tree, entry->path); + return 0; +} + +int git_index_remove_bypath(git_index *index, const char *path) +{ + int ret; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + + if (((ret = git_index_remove(index, path, 0)) < 0 && + ret != GIT_ENOTFOUND) || + ((ret = index_conflict_to_reuc(index, path)) < 0 && + ret != GIT_ENOTFOUND)) + return ret; + + if (ret == GIT_ENOTFOUND) + git_error_clear(); + + return 0; +} + +int git_index__fill(git_index *index, const git_vector *source_entries) +{ + const git_index_entry *source_entry = NULL; + int error = 0; + size_t i; + + GIT_ASSERT_ARG(index); + + if (!source_entries->length) + return 0; + + if (git_vector_size_hint(&index->entries, source_entries->length) < 0 || + index_map_resize(index->entries_map, (size_t)(source_entries->length * 1.3), + index->ignore_case) < 0) + return -1; + + git_vector_foreach(source_entries, i, source_entry) { + git_index_entry *entry = NULL; + + if ((error = index_entry_dup(&entry, index, source_entry)) < 0) + break; + + index_entry_adjust_namemask(entry, ((struct entry_internal *)entry)->pathlen); + entry->flags_extended |= GIT_INDEX_ENTRY_UPTODATE; + entry->mode = git_index__create_mode(entry->mode); + + if ((error = git_vector_insert(&index->entries, entry)) < 0) + break; + + if ((error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) + break; + + index->dirty = 1; + } + + if (!error) + git_vector_sort(&index->entries); + + return error; +} + + +int git_index_add(git_index *index, const git_index_entry *source_entry) +{ + git_index_entry *entry = NULL; + int ret; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(source_entry && source_entry->path); + + if (!valid_filemode(source_entry->mode)) { + git_error_set(GIT_ERROR_INDEX, "invalid entry mode"); + return -1; + } + + if ((ret = index_entry_dup(&entry, index, source_entry)) < 0 || + (ret = index_insert(index, &entry, 1, true, true, false)) < 0) + return ret; + + git_tree_cache_invalidate_path(index->tree, entry->path); + return 0; +} + +int git_index_remove(git_index *index, const char *path, int stage) +{ + int error; + size_t position; + git_index_entry remove_key = {{ 0 }}; + + remove_key.path = path; + GIT_INDEX_ENTRY_STAGE_SET(&remove_key, stage); + + index_map_delete(index->entries_map, &remove_key, index->ignore_case); + + if (index_find(&position, index, path, 0, stage) < 0) { + git_error_set( + GIT_ERROR_INDEX, "index does not contain %s at stage %d", path, stage); + error = GIT_ENOTFOUND; + } else { + error = index_remove_entry(index, position); + } + + return error; +} + +int git_index_remove_directory(git_index *index, const char *dir, int stage) +{ + git_str pfx = GIT_STR_INIT; + int error = 0; + size_t pos; + git_index_entry *entry; + + if (!(error = git_str_sets(&pfx, dir)) && + !(error = git_fs_path_to_dir(&pfx))) + index_find(&pos, index, pfx.ptr, pfx.size, GIT_INDEX_STAGE_ANY); + + while (!error) { + entry = git_vector_get(&index->entries, pos); + if (!entry || git__prefixcmp(entry->path, pfx.ptr) != 0) + break; + + if (GIT_INDEX_ENTRY_STAGE(entry) != stage) { + ++pos; + continue; + } + + error = index_remove_entry(index, pos); + + /* removed entry at 'pos' so we don't need to increment */ + } + + git_str_dispose(&pfx); + + return error; +} + +int git_index_find_prefix(size_t *at_pos, git_index *index, const char *prefix) +{ + int error = 0; + size_t pos; + const git_index_entry *entry; + + index_find(&pos, index, prefix, strlen(prefix), GIT_INDEX_STAGE_ANY); + entry = git_vector_get(&index->entries, pos); + if (!entry || git__prefixcmp(entry->path, prefix) != 0) + error = GIT_ENOTFOUND; + + if (!error && at_pos) + *at_pos = pos; + + return error; +} + +int git_index__find_pos( + size_t *out, git_index *index, const char *path, size_t path_len, int stage) +{ + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + return index_find(out, index, path, path_len, stage); +} + +int git_index_find(size_t *at_pos, git_index *index, const char *path) +{ + size_t pos; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + + if (git_vector_bsearch2( + &pos, &index->entries, index->entries_search_path, path) < 0) { + git_error_set(GIT_ERROR_INDEX, "index does not contain %s", path); + return GIT_ENOTFOUND; + } + + /* Since our binary search only looked at path, we may be in the + * middle of a list of stages. + */ + for (; pos > 0; --pos) { + const git_index_entry *prev = git_vector_get(&index->entries, pos - 1); + + if (index->entries_cmp_path(prev->path, path) != 0) + break; + } + + if (at_pos) + *at_pos = pos; + + return 0; +} + +int git_index_conflict_add(git_index *index, + const git_index_entry *ancestor_entry, + const git_index_entry *our_entry, + const git_index_entry *their_entry) +{ + git_index_entry *entries[3] = { 0 }; + unsigned short i; + int ret = 0; + + GIT_ASSERT_ARG(index); + + if ((ancestor_entry && + (ret = index_entry_dup(&entries[0], index, ancestor_entry)) < 0) || + (our_entry && + (ret = index_entry_dup(&entries[1], index, our_entry)) < 0) || + (their_entry && + (ret = index_entry_dup(&entries[2], index, their_entry)) < 0)) + goto on_error; + + /* Validate entries */ + for (i = 0; i < 3; i++) { + if (entries[i] && !valid_filemode(entries[i]->mode)) { + git_error_set(GIT_ERROR_INDEX, "invalid filemode for stage %d entry", + i + 1); + ret = -1; + goto on_error; + } + } + + /* Remove existing index entries for each path */ + for (i = 0; i < 3; i++) { + if (entries[i] == NULL) + continue; + + if ((ret = git_index_remove(index, entries[i]->path, 0)) != 0) { + if (ret != GIT_ENOTFOUND) + goto on_error; + + git_error_clear(); + ret = 0; + } + } + + /* Add the conflict entries */ + for (i = 0; i < 3; i++) { + if (entries[i] == NULL) + continue; + + /* Make sure stage is correct */ + GIT_INDEX_ENTRY_STAGE_SET(entries[i], i + 1); + + if ((ret = index_insert(index, &entries[i], 1, true, true, false)) < 0) + goto on_error; + + entries[i] = NULL; /* don't free if later entry fails */ + } + + return 0; + +on_error: + for (i = 0; i < 3; i++) { + if (entries[i] != NULL) + index_entry_free(entries[i]); + } + + return ret; +} + +static int index_conflict__get_byindex( + const git_index_entry **ancestor_out, + const git_index_entry **our_out, + const git_index_entry **their_out, + git_index *index, + size_t n) +{ + const git_index_entry *conflict_entry; + const char *path = NULL; + size_t count; + int stage, len = 0; + + GIT_ASSERT_ARG(ancestor_out); + GIT_ASSERT_ARG(our_out); + GIT_ASSERT_ARG(their_out); + GIT_ASSERT_ARG(index); + + *ancestor_out = NULL; + *our_out = NULL; + *their_out = NULL; + + for (count = git_index_entrycount(index); n < count; ++n) { + conflict_entry = git_vector_get(&index->entries, n); + + if (path && index->entries_cmp_path(conflict_entry->path, path) != 0) + break; + + stage = GIT_INDEX_ENTRY_STAGE(conflict_entry); + path = conflict_entry->path; + + switch (stage) { + case 3: + *their_out = conflict_entry; + len++; + break; + case 2: + *our_out = conflict_entry; + len++; + break; + case 1: + *ancestor_out = conflict_entry; + len++; + break; + default: + break; + }; + } + + return len; +} + +int git_index_conflict_get( + const git_index_entry **ancestor_out, + const git_index_entry **our_out, + const git_index_entry **their_out, + git_index *index, + const char *path) +{ + size_t pos; + int len = 0; + + GIT_ASSERT_ARG(ancestor_out); + GIT_ASSERT_ARG(our_out); + GIT_ASSERT_ARG(their_out); + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + + *ancestor_out = NULL; + *our_out = NULL; + *their_out = NULL; + + if (git_index_find(&pos, index, path) < 0) + return GIT_ENOTFOUND; + + if ((len = index_conflict__get_byindex( + ancestor_out, our_out, their_out, index, pos)) < 0) + return len; + else if (len == 0) + return GIT_ENOTFOUND; + + return 0; +} + +static int index_conflict_remove(git_index *index, const char *path) +{ + size_t pos = 0; + git_index_entry *conflict_entry; + int error = 0; + + if (path != NULL && git_index_find(&pos, index, path) < 0) + return GIT_ENOTFOUND; + + while ((conflict_entry = git_vector_get(&index->entries, pos)) != NULL) { + + if (path != NULL && + index->entries_cmp_path(conflict_entry->path, path) != 0) + break; + + if (GIT_INDEX_ENTRY_STAGE(conflict_entry) == 0) { + pos++; + continue; + } + + if ((error = index_remove_entry(index, pos)) < 0) + break; + } + + return error; +} + +int git_index_conflict_remove(git_index *index, const char *path) +{ + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + return index_conflict_remove(index, path); +} + +int git_index_conflict_cleanup(git_index *index) +{ + GIT_ASSERT_ARG(index); + return index_conflict_remove(index, NULL); +} + +int git_index_has_conflicts(const git_index *index) +{ + size_t i; + git_index_entry *entry; + + GIT_ASSERT_ARG(index); + + git_vector_foreach(&index->entries, i, entry) { + if (GIT_INDEX_ENTRY_STAGE(entry) > 0) + return 1; + } + + return 0; +} + +int git_index_iterator_new( + git_index_iterator **iterator_out, + git_index *index) +{ + git_index_iterator *it; + int error; + + GIT_ASSERT_ARG(iterator_out); + GIT_ASSERT_ARG(index); + + it = git__calloc(1, sizeof(git_index_iterator)); + GIT_ERROR_CHECK_ALLOC(it); + + if ((error = git_index_snapshot_new(&it->snap, index)) < 0) { + git__free(it); + return error; + } + + it->index = index; + + *iterator_out = it; + return 0; +} + +int git_index_iterator_next( + const git_index_entry **out, + git_index_iterator *it) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(it); + + if (it->cur >= git_vector_length(&it->snap)) + return GIT_ITEROVER; + + *out = (git_index_entry *)git_vector_get(&it->snap, it->cur++); + return 0; +} + +void git_index_iterator_free(git_index_iterator *it) +{ + if (it == NULL) + return; + + git_index_snapshot_release(&it->snap, it->index); + git__free(it); +} + +int git_index_conflict_iterator_new( + git_index_conflict_iterator **iterator_out, + git_index *index) +{ + git_index_conflict_iterator *it = NULL; + + GIT_ASSERT_ARG(iterator_out); + GIT_ASSERT_ARG(index); + + it = git__calloc(1, sizeof(git_index_conflict_iterator)); + GIT_ERROR_CHECK_ALLOC(it); + + it->index = index; + + *iterator_out = it; + return 0; +} + +int git_index_conflict_next( + const git_index_entry **ancestor_out, + const git_index_entry **our_out, + const git_index_entry **their_out, + git_index_conflict_iterator *iterator) +{ + const git_index_entry *entry; + int len; + + GIT_ASSERT_ARG(ancestor_out); + GIT_ASSERT_ARG(our_out); + GIT_ASSERT_ARG(their_out); + GIT_ASSERT_ARG(iterator); + + *ancestor_out = NULL; + *our_out = NULL; + *their_out = NULL; + + while (iterator->cur < iterator->index->entries.length) { + entry = git_index_get_byindex(iterator->index, iterator->cur); + + if (git_index_entry_is_conflict(entry)) { + if ((len = index_conflict__get_byindex( + ancestor_out, + our_out, + their_out, + iterator->index, + iterator->cur)) < 0) + return len; + + iterator->cur += len; + return 0; + } + + iterator->cur++; + } + + return GIT_ITEROVER; +} + +void git_index_conflict_iterator_free(git_index_conflict_iterator *iterator) +{ + if (iterator == NULL) + return; + + git__free(iterator); +} + +size_t git_index_name_entrycount(git_index *index) +{ + GIT_ASSERT_ARG(index); + return index->names.length; +} + +const git_index_name_entry *git_index_name_get_byindex( + git_index *index, size_t n) +{ + GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); + + git_vector_sort(&index->names); + return git_vector_get(&index->names, n); +} + +static void index_name_entry_free(git_index_name_entry *ne) +{ + if (!ne) + return; + git__free(ne->ancestor); + git__free(ne->ours); + git__free(ne->theirs); + git__free(ne); +} + +int git_index_name_add(git_index *index, + const char *ancestor, const char *ours, const char *theirs) +{ + git_index_name_entry *conflict_name; + + GIT_ASSERT_ARG((ancestor && ours) || (ancestor && theirs) || (ours && theirs)); + + conflict_name = git__calloc(1, sizeof(git_index_name_entry)); + GIT_ERROR_CHECK_ALLOC(conflict_name); + + if ((ancestor && !(conflict_name->ancestor = git__strdup(ancestor))) || + (ours && !(conflict_name->ours = git__strdup(ours))) || + (theirs && !(conflict_name->theirs = git__strdup(theirs))) || + git_vector_insert(&index->names, conflict_name) < 0) + { + index_name_entry_free(conflict_name); + return -1; + } + + index->dirty = 1; + return 0; +} + +int git_index_name_clear(git_index *index) +{ + size_t i; + git_index_name_entry *conflict_name; + + GIT_ASSERT_ARG(index); + + git_vector_foreach(&index->names, i, conflict_name) + index_name_entry_free(conflict_name); + + git_vector_clear(&index->names); + + index->dirty = 1; + + return 0; +} + +size_t git_index_reuc_entrycount(git_index *index) +{ + GIT_ASSERT_ARG(index); + return index->reuc.length; +} + +static int index_reuc_on_dup(void **old, void *new) +{ + index_entry_reuc_free(*old); + *old = new; + return GIT_EEXISTS; +} + +static int index_reuc_insert( + git_index *index, + git_index_reuc_entry *reuc) +{ + int res; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(reuc && reuc->path != NULL); + GIT_ASSERT(git_vector_is_sorted(&index->reuc)); + + res = git_vector_insert_sorted(&index->reuc, reuc, &index_reuc_on_dup); + index->dirty = 1; + + return res == GIT_EEXISTS ? 0 : res; +} + +int git_index_reuc_add(git_index *index, const char *path, + int ancestor_mode, const git_oid *ancestor_oid, + int our_mode, const git_oid *our_oid, + int their_mode, const git_oid *their_oid) +{ + git_index_reuc_entry *reuc = NULL; + int error = 0; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(path); + + if ((error = index_entry_reuc_init(&reuc, path, ancestor_mode, + ancestor_oid, our_mode, our_oid, their_mode, their_oid)) < 0 || + (error = index_reuc_insert(index, reuc)) < 0) + index_entry_reuc_free(reuc); + + return error; +} + +int git_index_reuc_find(size_t *at_pos, git_index *index, const char *path) +{ + return git_vector_bsearch2(at_pos, &index->reuc, index->reuc_search, path); +} + +const git_index_reuc_entry *git_index_reuc_get_bypath( + git_index *index, const char *path) +{ + size_t pos; + + GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(path, NULL); + + if (!index->reuc.length) + return NULL; + + GIT_ASSERT_WITH_RETVAL(git_vector_is_sorted(&index->reuc), NULL); + + if (git_index_reuc_find(&pos, index, path) < 0) + return NULL; + + return git_vector_get(&index->reuc, pos); +} + +const git_index_reuc_entry *git_index_reuc_get_byindex( + git_index *index, size_t n) +{ + GIT_ASSERT_ARG_WITH_RETVAL(index, NULL); + GIT_ASSERT_WITH_RETVAL(git_vector_is_sorted(&index->reuc), NULL); + + return git_vector_get(&index->reuc, n); +} + +int git_index_reuc_remove(git_index *index, size_t position) +{ + int error; + git_index_reuc_entry *reuc; + + GIT_ASSERT_ARG(index); + GIT_ASSERT(git_vector_is_sorted(&index->reuc)); + + reuc = git_vector_get(&index->reuc, position); + error = git_vector_remove(&index->reuc, position); + + if (!error) + index_entry_reuc_free(reuc); + + index->dirty = 1; + return error; +} + +int git_index_reuc_clear(git_index *index) +{ + size_t i; + + GIT_ASSERT_ARG(index); + + for (i = 0; i < index->reuc.length; ++i) + index_entry_reuc_free(git_atomic_swap(index->reuc.contents[i], NULL)); + + git_vector_clear(&index->reuc); + + index->dirty = 1; + + return 0; +} + +static int index_error_invalid(const char *message) +{ + git_error_set(GIT_ERROR_INDEX, "invalid data in index - %s", message); + return -1; +} + +static int read_reuc(git_index *index, const char *buffer, size_t size) +{ + const char *endptr; + size_t len; + int i; + + /* If called multiple times, the vector might already be initialized */ + if (index->reuc._alloc_size == 0 && + git_vector_init(&index->reuc, 16, reuc_cmp) < 0) + return -1; + + while (size) { + git_index_reuc_entry *lost; + + len = p_strnlen(buffer, size) + 1; + if (size <= len) + return index_error_invalid("reading reuc entries"); + + lost = reuc_entry_alloc(buffer); + GIT_ERROR_CHECK_ALLOC(lost); + + size -= len; + buffer += len; + + /* read 3 ASCII octal numbers for stage entries */ + for (i = 0; i < 3; i++) { + int64_t tmp; + + if (git__strntol64(&tmp, buffer, size, &endptr, 8) < 0 || + !endptr || endptr == buffer || *endptr || + tmp < 0 || tmp > UINT32_MAX) { + index_entry_reuc_free(lost); + return index_error_invalid("reading reuc entry stage"); + } + + lost->mode[i] = (uint32_t)tmp; + + len = (endptr + 1) - buffer; + if (size <= len) { + index_entry_reuc_free(lost); + return index_error_invalid("reading reuc entry stage"); + } + + size -= len; + buffer += len; + } + + /* read up to 3 OIDs for stage entries */ + for (i = 0; i < 3; i++) { + if (!lost->mode[i]) + continue; + if (size < 20) { + index_entry_reuc_free(lost); + return index_error_invalid("reading reuc entry oid"); + } + + git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer); + size -= 20; + buffer += 20; + } + + /* entry was read successfully - insert into reuc vector */ + if (git_vector_insert(&index->reuc, lost) < 0) + return -1; + } + + /* entries are guaranteed to be sorted on-disk */ + git_vector_set_sorted(&index->reuc, true); + + return 0; +} + + +static int read_conflict_names(git_index *index, const char *buffer, size_t size) +{ + size_t len; + + /* This gets called multiple times, the vector might already be initialized */ + if (index->names._alloc_size == 0 && + git_vector_init(&index->names, 16, conflict_name_cmp) < 0) + return -1; + +#define read_conflict_name(ptr) \ + len = p_strnlen(buffer, size) + 1; \ + if (size < len) { \ + index_error_invalid("reading conflict name entries"); \ + goto out_err; \ + } \ + if (len == 1) \ + ptr = NULL; \ + else { \ + ptr = git__malloc(len); \ + GIT_ERROR_CHECK_ALLOC(ptr); \ + memcpy(ptr, buffer, len); \ + } \ + \ + buffer += len; \ + size -= len; + + while (size) { + git_index_name_entry *conflict_name = git__calloc(1, sizeof(git_index_name_entry)); + GIT_ERROR_CHECK_ALLOC(conflict_name); + + read_conflict_name(conflict_name->ancestor); + read_conflict_name(conflict_name->ours); + read_conflict_name(conflict_name->theirs); + + if (git_vector_insert(&index->names, conflict_name) < 0) + goto out_err; + + continue; + +out_err: + git__free(conflict_name->ancestor); + git__free(conflict_name->ours); + git__free(conflict_name->theirs); + git__free(conflict_name); + return -1; + } + +#undef read_conflict_name + + /* entries are guaranteed to be sorted on-disk */ + git_vector_set_sorted(&index->names, true); + + return 0; +} + +static size_t index_entry_size(size_t path_len, size_t varint_len, uint32_t flags) +{ + if (varint_len) { + if (flags & GIT_INDEX_ENTRY_EXTENDED) + return offsetof(struct entry_long, path) + path_len + 1 + varint_len; + else + return offsetof(struct entry_short, path) + path_len + 1 + varint_len; + } else { +#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7) + if (flags & GIT_INDEX_ENTRY_EXTENDED) + return entry_size(struct entry_long, path_len); + else + return entry_size(struct entry_short, path_len); +#undef entry_size + } +} + +static int read_entry( + git_index_entry **out, + size_t *out_size, + git_index *index, + const void *buffer, + size_t buffer_size, + const char *last) +{ + size_t path_length, entry_size; + const char *path_ptr; + struct entry_short source; + git_index_entry entry = {{0}}; + bool compressed = index->version >= INDEX_VERSION_NUMBER_COMP; + char *tmp_path = NULL; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + + if (checksum_size + minimal_entry_size > buffer_size) + return -1; + + /* buffer is not guaranteed to be aligned */ + memcpy(&source, buffer, sizeof(struct entry_short)); + + entry.ctime.seconds = (git_time_t)ntohl(source.ctime.seconds); + entry.ctime.nanoseconds = ntohl(source.ctime.nanoseconds); + entry.mtime.seconds = (git_time_t)ntohl(source.mtime.seconds); + entry.mtime.nanoseconds = ntohl(source.mtime.nanoseconds); + entry.dev = ntohl(source.dev); + entry.ino = ntohl(source.ino); + entry.mode = ntohl(source.mode); + entry.uid = ntohl(source.uid); + entry.gid = ntohl(source.gid); + entry.file_size = ntohl(source.file_size); + git_oid_cpy(&entry.id, &source.oid); + entry.flags = ntohs(source.flags); + + if (entry.flags & GIT_INDEX_ENTRY_EXTENDED) { + uint16_t flags_raw; + size_t flags_offset; + + flags_offset = offsetof(struct entry_long, flags_extended); + memcpy(&flags_raw, (const char *) buffer + flags_offset, + sizeof(flags_raw)); + flags_raw = ntohs(flags_raw); + + memcpy(&entry.flags_extended, &flags_raw, sizeof(flags_raw)); + path_ptr = (const char *) buffer + offsetof(struct entry_long, path); + } else + path_ptr = (const char *) buffer + offsetof(struct entry_short, path); + + if (!compressed) { + path_length = entry.flags & GIT_INDEX_ENTRY_NAMEMASK; + + /* if this is a very long string, we must find its + * real length without overflowing */ + if (path_length == 0xFFF) { + const char *path_end; + + path_end = memchr(path_ptr, '\0', buffer_size); + if (path_end == NULL) + return -1; + + path_length = path_end - path_ptr; + } + + entry_size = index_entry_size(path_length, 0, entry.flags); + entry.path = (char *)path_ptr; + } else { + size_t varint_len, last_len, prefix_len, suffix_len, path_len; + uintmax_t strip_len; + + strip_len = git_decode_varint((const unsigned char *)path_ptr, &varint_len); + last_len = strlen(last); + + if (varint_len == 0 || last_len < strip_len) + return index_error_invalid("incorrect prefix length"); + + prefix_len = last_len - (size_t)strip_len; + suffix_len = strlen(path_ptr + varint_len); + + GIT_ERROR_CHECK_ALLOC_ADD(&path_len, prefix_len, suffix_len); + GIT_ERROR_CHECK_ALLOC_ADD(&path_len, path_len, 1); + + if (path_len > GIT_PATH_MAX) + return index_error_invalid("unreasonable path length"); + + tmp_path = git__malloc(path_len); + GIT_ERROR_CHECK_ALLOC(tmp_path); + + memcpy(tmp_path, last, prefix_len); + memcpy(tmp_path + prefix_len, path_ptr + varint_len, suffix_len + 1); + entry_size = index_entry_size(suffix_len, varint_len, entry.flags); + entry.path = tmp_path; + } + + if (entry_size == 0) + return -1; + + if (checksum_size + entry_size > buffer_size) + return -1; + + if (index_entry_dup(out, index, &entry) < 0) { + git__free(tmp_path); + return -1; + } + + git__free(tmp_path); + *out_size = entry_size; + return 0; +} + +static int read_header(struct index_header *dest, const void *buffer) +{ + const struct index_header *source = buffer; + + dest->signature = ntohl(source->signature); + if (dest->signature != INDEX_HEADER_SIG) + return index_error_invalid("incorrect header signature"); + + dest->version = ntohl(source->version); + if (dest->version < INDEX_VERSION_NUMBER_LB || + dest->version > INDEX_VERSION_NUMBER_UB) + return index_error_invalid("incorrect header version"); + + dest->entry_count = ntohl(source->entry_count); + return 0; +} + +static int read_extension(size_t *read_len, git_index *index, const char *buffer, size_t buffer_size) +{ + struct index_extension dest; + size_t total_size; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + + /* buffer is not guaranteed to be aligned */ + memcpy(&dest, buffer, sizeof(struct index_extension)); + dest.extension_size = ntohl(dest.extension_size); + + total_size = dest.extension_size + sizeof(struct index_extension); + + if (dest.extension_size > total_size || + buffer_size < total_size || + buffer_size - total_size < checksum_size) { + index_error_invalid("extension is truncated"); + return -1; + } + + /* optional extension */ + if (dest.signature[0] >= 'A' && dest.signature[0] <= 'Z') { + /* tree cache */ + if (memcmp(dest.signature, INDEX_EXT_TREECACHE_SIG, 4) == 0) { + if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size, &index->tree_pool) < 0) + return -1; + } else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) { + if (read_reuc(index, buffer + 8, dest.extension_size) < 0) + return -1; + } else if (memcmp(dest.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4) == 0) { + if (read_conflict_names(index, buffer + 8, dest.extension_size) < 0) + return -1; + } + /* else, unsupported extension. We cannot parse this, but we can skip + * it by returning `total_size */ + } else { + /* we cannot handle non-ignorable extensions; + * in fact they aren't even defined in the standard */ + git_error_set(GIT_ERROR_INDEX, "unsupported mandatory extension: '%.4s'", dest.signature); + return -1; + } + + *read_len = total_size; + + return 0; +} + +static int parse_index(git_index *index, const char *buffer, size_t buffer_size) +{ + int error = 0; + unsigned int i; + struct index_header header = { 0 }; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + const char *last = NULL; + const char *empty = ""; + +#define seek_forward(_increase) { \ + if (_increase >= buffer_size) { \ + error = index_error_invalid("ran out of data while parsing"); \ + goto done; } \ + buffer += _increase; \ + buffer_size -= _increase;\ +} + + if (buffer_size < INDEX_HEADER_SIZE + checksum_size) + return index_error_invalid("insufficient buffer space"); + + /* Precalculate the SHA1 of the files's contents -- we'll match it to + * the provided SHA1 in the footer */ + git_hash_buf(checksum, buffer, buffer_size - checksum_size, GIT_HASH_ALGORITHM_SHA1); + + /* Parse header */ + if ((error = read_header(&header, buffer)) < 0) + return error; + + index->version = header.version; + if (index->version >= INDEX_VERSION_NUMBER_COMP) + last = empty; + + seek_forward(INDEX_HEADER_SIZE); + + GIT_ASSERT(!index->entries.length); + + if ((error = index_map_resize(index->entries_map, header.entry_count, index->ignore_case)) < 0) + return error; + + /* Parse all the entries */ + for (i = 0; i < header.entry_count && buffer_size > checksum_size; ++i) { + git_index_entry *entry = NULL; + size_t entry_size; + + if ((error = read_entry(&entry, &entry_size, index, buffer, buffer_size, last)) < 0) { + error = index_error_invalid("invalid entry"); + goto done; + } + + if ((error = git_vector_insert(&index->entries, entry)) < 0) { + index_entry_free(entry); + goto done; + } + + if ((error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) { + index_entry_free(entry); + goto done; + } + error = 0; + + if (index->version >= INDEX_VERSION_NUMBER_COMP) + last = entry->path; + + seek_forward(entry_size); + } + + if (i != header.entry_count) { + error = index_error_invalid("header entries changed while parsing"); + goto done; + } + + /* There's still space for some extensions! */ + while (buffer_size > checksum_size) { + size_t extension_size; + + if ((error = read_extension(&extension_size, index, buffer, buffer_size)) < 0) { + goto done; + } + + seek_forward(extension_size); + } + + if (buffer_size != checksum_size) { + error = index_error_invalid( + "buffer size does not match index footer size"); + goto done; + } + + /* 160-bit SHA-1 over the content of the index file before this checksum. */ + if (memcmp(checksum, buffer, checksum_size) != 0) { + error = index_error_invalid( + "calculated checksum does not match expected"); + goto done; + } + + memcpy(index->checksum, checksum, checksum_size); + +#undef seek_forward + + /* Entries are stored case-sensitively on disk, so re-sort now if + * in-memory index is supposed to be case-insensitive + */ + git_vector_set_sorted(&index->entries, !index->ignore_case); + git_vector_sort(&index->entries); + + index->dirty = 0; +done: + return error; +} + +static bool is_index_extended(git_index *index) +{ + size_t i, extended; + git_index_entry *entry; + + extended = 0; + + git_vector_foreach(&index->entries, i, entry) { + entry->flags &= ~GIT_INDEX_ENTRY_EXTENDED; + if (entry->flags_extended & GIT_INDEX_ENTRY_EXTENDED_FLAGS) { + extended++; + entry->flags |= GIT_INDEX_ENTRY_EXTENDED; + } + } + + return (extended > 0); +} + +static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const char *last) +{ + void *mem = NULL; + struct entry_short ondisk; + size_t path_len, disk_size; + int varint_len = 0; + char *path; + const char *path_start = entry->path; + size_t same_len = 0; + + path_len = ((struct entry_internal *)entry)->pathlen; + + if (last) { + const char *last_c = last; + + while (*path_start == *last_c) { + if (!*path_start || !*last_c) + break; + ++path_start; + ++last_c; + ++same_len; + } + path_len -= same_len; + varint_len = git_encode_varint(NULL, 0, strlen(last) - same_len); + } + + disk_size = index_entry_size(path_len, varint_len, entry->flags); + + if (git_filebuf_reserve(file, &mem, disk_size) < 0) + return -1; + + memset(mem, 0x0, disk_size); + + /** + * Yes, we have to truncate. + * + * The on-disk format for Index entries clearly defines + * the time and size fields to be 4 bytes each -- so even if + * we store these values with 8 bytes on-memory, they must + * be truncated to 4 bytes before writing to disk. + * + * In 2038 I will be either too dead or too rich to care about this + */ + ondisk.ctime.seconds = htonl((uint32_t)entry->ctime.seconds); + ondisk.mtime.seconds = htonl((uint32_t)entry->mtime.seconds); + ondisk.ctime.nanoseconds = htonl(entry->ctime.nanoseconds); + ondisk.mtime.nanoseconds = htonl(entry->mtime.nanoseconds); + ondisk.dev = htonl(entry->dev); + ondisk.ino = htonl(entry->ino); + ondisk.mode = htonl(entry->mode); + ondisk.uid = htonl(entry->uid); + ondisk.gid = htonl(entry->gid); + ondisk.file_size = htonl((uint32_t)entry->file_size); + + git_oid_cpy(&ondisk.oid, &entry->id); + + ondisk.flags = htons(entry->flags); + + if (entry->flags & GIT_INDEX_ENTRY_EXTENDED) { + const size_t path_offset = offsetof(struct entry_long, path); + struct entry_long ondisk_ext; + memcpy(&ondisk_ext, &ondisk, sizeof(struct entry_short)); + ondisk_ext.flags_extended = htons(entry->flags_extended & + GIT_INDEX_ENTRY_EXTENDED_FLAGS); + memcpy(mem, &ondisk_ext, path_offset); + path = (char *)mem + path_offset; + disk_size -= path_offset; + } else { + const size_t path_offset = offsetof(struct entry_short, path); + memcpy(mem, &ondisk, path_offset); + path = (char *)mem + path_offset; + disk_size -= path_offset; + } + + if (last) { + varint_len = git_encode_varint((unsigned char *) path, + disk_size, strlen(last) - same_len); + GIT_ASSERT(varint_len > 0); + + path += varint_len; + disk_size -= varint_len; + + /* + * If using path compression, we are not allowed + * to have additional trailing NULs. + */ + GIT_ASSERT(disk_size == path_len + 1); + } else { + /* + * If no path compression is used, we do have + * NULs as padding. As such, simply assert that + * we have enough space left to write the path. + */ + GIT_ASSERT(disk_size > path_len); + } + + memcpy(path, path_start, path_len + 1); + + return 0; +} + +static int write_entries(git_index *index, git_filebuf *file) +{ + int error = 0; + size_t i; + git_vector case_sorted = GIT_VECTOR_INIT, *entries = NULL; + git_index_entry *entry; + const char *last = NULL; + + /* If index->entries is sorted case-insensitively, then we need + * to re-sort it case-sensitively before writing */ + if (index->ignore_case) { + if ((error = git_vector_dup(&case_sorted, &index->entries, git_index_entry_cmp)) < 0) + goto done; + + git_vector_sort(&case_sorted); + entries = &case_sorted; + } else { + entries = &index->entries; + } + + if (index->version >= INDEX_VERSION_NUMBER_COMP) + last = ""; + + git_vector_foreach(entries, i, entry) { + if ((error = write_disk_entry(file, entry, last)) < 0) + break; + if (index->version >= INDEX_VERSION_NUMBER_COMP) + last = entry->path; + } + +done: + git_vector_free(&case_sorted); + return error; +} + +static int write_extension(git_filebuf *file, struct index_extension *header, git_str *data) +{ + struct index_extension ondisk; + + memset(&ondisk, 0x0, sizeof(struct index_extension)); + memcpy(&ondisk, header, 4); + ondisk.extension_size = htonl(header->extension_size); + + git_filebuf_write(file, &ondisk, sizeof(struct index_extension)); + return git_filebuf_write(file, data->ptr, data->size); +} + +static int create_name_extension_data(git_str *name_buf, git_index_name_entry *conflict_name) +{ + int error = 0; + + if (conflict_name->ancestor == NULL) + error = git_str_put(name_buf, "\0", 1); + else + error = git_str_put(name_buf, conflict_name->ancestor, strlen(conflict_name->ancestor) + 1); + + if (error != 0) + goto on_error; + + if (conflict_name->ours == NULL) + error = git_str_put(name_buf, "\0", 1); + else + error = git_str_put(name_buf, conflict_name->ours, strlen(conflict_name->ours) + 1); + + if (error != 0) + goto on_error; + + if (conflict_name->theirs == NULL) + error = git_str_put(name_buf, "\0", 1); + else + error = git_str_put(name_buf, conflict_name->theirs, strlen(conflict_name->theirs) + 1); + +on_error: + return error; +} + +static int write_name_extension(git_index *index, git_filebuf *file) +{ + git_str name_buf = GIT_STR_INIT; + git_vector *out = &index->names; + git_index_name_entry *conflict_name; + struct index_extension extension; + size_t i; + int error = 0; + + git_vector_foreach(out, i, conflict_name) { + if ((error = create_name_extension_data(&name_buf, conflict_name)) < 0) + goto done; + } + + memset(&extension, 0x0, sizeof(struct index_extension)); + memcpy(&extension.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4); + extension.extension_size = (uint32_t)name_buf.size; + + error = write_extension(file, &extension, &name_buf); + + git_str_dispose(&name_buf); + +done: + return error; +} + +static int create_reuc_extension_data(git_str *reuc_buf, git_index_reuc_entry *reuc) +{ + int i; + int error = 0; + + if ((error = git_str_put(reuc_buf, reuc->path, strlen(reuc->path) + 1)) < 0) + return error; + + for (i = 0; i < 3; i++) { + if ((error = git_str_printf(reuc_buf, "%o", reuc->mode[i])) < 0 || + (error = git_str_put(reuc_buf, "\0", 1)) < 0) + return error; + } + + for (i = 0; i < 3; i++) { + if (reuc->mode[i] && (error = git_str_put(reuc_buf, (char *)&reuc->oid[i].id, GIT_OID_RAWSZ)) < 0) + return error; + } + + return 0; +} + +static int write_reuc_extension(git_index *index, git_filebuf *file) +{ + git_str reuc_buf = GIT_STR_INIT; + git_vector *out = &index->reuc; + git_index_reuc_entry *reuc; + struct index_extension extension; + size_t i; + int error = 0; + + git_vector_foreach(out, i, reuc) { + if ((error = create_reuc_extension_data(&reuc_buf, reuc)) < 0) + goto done; + } + + memset(&extension, 0x0, sizeof(struct index_extension)); + memcpy(&extension.signature, INDEX_EXT_UNMERGED_SIG, 4); + extension.extension_size = (uint32_t)reuc_buf.size; + + error = write_extension(file, &extension, &reuc_buf); + + git_str_dispose(&reuc_buf); + +done: + return error; +} + +static int write_tree_extension(git_index *index, git_filebuf *file) +{ + struct index_extension extension; + git_str buf = GIT_STR_INIT; + int error; + + if (index->tree == NULL) + return 0; + + if ((error = git_tree_cache_write(&buf, index->tree)) < 0) + return error; + + memset(&extension, 0x0, sizeof(struct index_extension)); + memcpy(&extension.signature, INDEX_EXT_TREECACHE_SIG, 4); + extension.extension_size = (uint32_t)buf.size; + + error = write_extension(file, &extension, &buf); + + git_str_dispose(&buf); + + return error; +} + +static void clear_uptodate(git_index *index) +{ + git_index_entry *entry; + size_t i; + + git_vector_foreach(&index->entries, i, entry) + entry->flags_extended &= ~GIT_INDEX_ENTRY_UPTODATE; +} + +static int write_index( + unsigned char checksum[GIT_HASH_SHA1_SIZE], + size_t *checksum_size, + git_index *index, + git_filebuf *file) +{ + struct index_header header; + bool is_extended; + uint32_t index_version_number; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(file); + + *checksum_size = GIT_HASH_SHA1_SIZE; + + if (index->version <= INDEX_VERSION_NUMBER_EXT) { + is_extended = is_index_extended(index); + index_version_number = is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER_LB; + } else { + index_version_number = index->version; + } + + header.signature = htonl(INDEX_HEADER_SIG); + header.version = htonl(index_version_number); + header.entry_count = htonl((uint32_t)index->entries.length); + + if (git_filebuf_write(file, &header, sizeof(struct index_header)) < 0) + return -1; + + if (write_entries(index, file) < 0) + return -1; + + /* write the tree cache extension */ + if (index->tree != NULL && write_tree_extension(index, file) < 0) + return -1; + + /* write the rename conflict extension */ + if (index->names.length > 0 && write_name_extension(index, file) < 0) + return -1; + + /* write the reuc extension */ + if (index->reuc.length > 0 && write_reuc_extension(index, file) < 0) + return -1; + + /* get out the hash for all the contents we've appended to the file */ + git_filebuf_hash(checksum, file); + + /* write it at the end of the file */ + if (git_filebuf_write(file, checksum, *checksum_size) < 0) + return -1; + + /* file entries are no longer up to date */ + clear_uptodate(index); + + return 0; +} + +int git_index_entry_stage(const git_index_entry *entry) +{ + return GIT_INDEX_ENTRY_STAGE(entry); +} + +int git_index_entry_is_conflict(const git_index_entry *entry) +{ + return (GIT_INDEX_ENTRY_STAGE(entry) > 0); +} + +typedef struct read_tree_data { + git_index *index; + git_vector *old_entries; + git_vector *new_entries; + git_vector_cmp entry_cmp; + git_tree_cache *tree; +} read_tree_data; + +static int read_tree_cb( + const char *root, const git_tree_entry *tentry, void *payload) +{ + read_tree_data *data = payload; + git_index_entry *entry = NULL, *old_entry; + git_str path = GIT_STR_INIT; + size_t pos; + + if (git_tree_entry__is_tree(tentry)) + return 0; + + if (git_str_joinpath(&path, root, tentry->filename) < 0) + return -1; + + if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr, NULL, false) < 0) + return -1; + + entry->mode = tentry->attr; + git_oid_cpy(&entry->id, git_tree_entry_id(tentry)); + + /* look for corresponding old entry and copy data to new entry */ + if (data->old_entries != NULL && + !index_find_in_entries( + &pos, data->old_entries, data->entry_cmp, path.ptr, 0, 0) && + (old_entry = git_vector_get(data->old_entries, pos)) != NULL && + entry->mode == old_entry->mode && + git_oid_equal(&entry->id, &old_entry->id)) + { + index_entry_cpy(entry, old_entry); + entry->flags_extended = 0; + } + + index_entry_adjust_namemask(entry, path.size); + git_str_dispose(&path); + + if (git_vector_insert(data->new_entries, entry) < 0) { + index_entry_free(entry); + return -1; + } + + return 0; +} + +int git_index_read_tree(git_index *index, const git_tree *tree) +{ + int error = 0; + git_vector entries = GIT_VECTOR_INIT; + git_idxmap *entries_map; + read_tree_data data; + size_t i; + git_index_entry *e; + + if (git_idxmap_new(&entries_map) < 0) + return -1; + + git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */ + + data.index = index; + data.old_entries = &index->entries; + data.new_entries = &entries; + data.entry_cmp = index->entries_search; + + index->tree = NULL; + git_pool_clear(&index->tree_pool); + + git_vector_sort(&index->entries); + + if ((error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data)) < 0) + goto cleanup; + + if ((error = index_map_resize(entries_map, entries.length, index->ignore_case)) < 0) + goto cleanup; + + git_vector_foreach(&entries, i, e) { + if ((error = index_map_set(entries_map, e, index->ignore_case)) < 0) { + git_error_set(GIT_ERROR_INDEX, "failed to insert entry into map"); + return error; + } + } + + error = 0; + + git_vector_sort(&entries); + + if ((error = git_index_clear(index)) < 0) { + /* well, this isn't good */; + } else { + git_vector_swap(&entries, &index->entries); + entries_map = git_atomic_swap(index->entries_map, entries_map); + } + + index->dirty = 1; + +cleanup: + git_vector_free(&entries); + git_idxmap_free(entries_map); + if (error < 0) + return error; + + error = git_tree_cache_read_tree(&index->tree, tree, &index->tree_pool); + + return error; +} + +static int git_index_read_iterator( + git_index *index, + git_iterator *new_iterator, + size_t new_length_hint) +{ + git_vector new_entries = GIT_VECTOR_INIT, + remove_entries = GIT_VECTOR_INIT; + git_idxmap *new_entries_map = NULL; + git_iterator *index_iterator = NULL; + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *old_entry, *new_entry; + git_index_entry *entry; + size_t i; + int error; + + GIT_ASSERT((new_iterator->flags & GIT_ITERATOR_DONT_IGNORE_CASE)); + + if ((error = git_vector_init(&new_entries, new_length_hint, index->entries._cmp)) < 0 || + (error = git_vector_init(&remove_entries, index->entries.length, NULL)) < 0 || + (error = git_idxmap_new(&new_entries_map)) < 0) + goto done; + + if (new_length_hint && (error = index_map_resize(new_entries_map, new_length_hint, + index->ignore_case)) < 0) + goto done; + + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_INCLUDE_CONFLICTS; + + if ((error = git_iterator_for_index(&index_iterator, + git_index_owner(index), index, &opts)) < 0 || + ((error = git_iterator_current(&old_entry, index_iterator)) < 0 && + error != GIT_ITEROVER) || + ((error = git_iterator_current(&new_entry, new_iterator)) < 0 && + error != GIT_ITEROVER)) + goto done; + + while (true) { + git_index_entry + *dup_entry = NULL, + *add_entry = NULL, + *remove_entry = NULL; + int diff; + + error = 0; + + if (old_entry && new_entry) + diff = git_index_entry_cmp(old_entry, new_entry); + else if (!old_entry && new_entry) + diff = 1; + else if (old_entry && !new_entry) + diff = -1; + else + break; + + if (diff < 0) { + remove_entry = (git_index_entry *)old_entry; + } else if (diff > 0) { + dup_entry = (git_index_entry *)new_entry; + } else { + /* Path and stage are equal, if the OID is equal, keep it to + * keep the stat cache data. + */ + if (git_oid_equal(&old_entry->id, &new_entry->id) && + old_entry->mode == new_entry->mode) { + add_entry = (git_index_entry *)old_entry; + } else { + dup_entry = (git_index_entry *)new_entry; + remove_entry = (git_index_entry *)old_entry; + } + } + + if (dup_entry) { + if ((error = index_entry_dup_nocache(&add_entry, index, dup_entry)) < 0) + goto done; + + index_entry_adjust_namemask(add_entry, + ((struct entry_internal *)add_entry)->pathlen); + } + + /* invalidate this path in the tree cache if this is new (to + * invalidate the parent trees) + */ + if (dup_entry && !remove_entry && index->tree) + git_tree_cache_invalidate_path(index->tree, dup_entry->path); + + if (add_entry) { + if ((error = git_vector_insert(&new_entries, add_entry)) == 0) + error = index_map_set(new_entries_map, add_entry, + index->ignore_case); + } + + if (remove_entry && error >= 0) + error = git_vector_insert(&remove_entries, remove_entry); + + if (error < 0) { + git_error_set(GIT_ERROR_INDEX, "failed to insert entry"); + goto done; + } + + if (diff <= 0) { + if ((error = git_iterator_advance(&old_entry, index_iterator)) < 0 && + error != GIT_ITEROVER) + goto done; + } + + if (diff >= 0) { + if ((error = git_iterator_advance(&new_entry, new_iterator)) < 0 && + error != GIT_ITEROVER) + goto done; + } + } + + if ((error = git_index_name_clear(index)) < 0 || + (error = git_index_reuc_clear(index)) < 0) + goto done; + + git_vector_swap(&new_entries, &index->entries); + new_entries_map = git_atomic_swap(index->entries_map, new_entries_map); + + git_vector_foreach(&remove_entries, i, entry) { + if (index->tree) + git_tree_cache_invalidate_path(index->tree, entry->path); + + index_entry_free(entry); + } + + clear_uptodate(index); + + index->dirty = 1; + error = 0; + +done: + git_idxmap_free(new_entries_map); + git_vector_free(&new_entries); + git_vector_free(&remove_entries); + git_iterator_free(index_iterator); + return error; +} + +int git_index_read_index( + git_index *index, + const git_index *new_index) +{ + git_iterator *new_iterator = NULL; + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + int error; + + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_INCLUDE_CONFLICTS; + + if ((error = git_iterator_for_index(&new_iterator, + git_index_owner(new_index), (git_index *)new_index, &opts)) < 0 || + (error = git_index_read_iterator(index, new_iterator, + new_index->entries.length)) < 0) + goto done; + +done: + git_iterator_free(new_iterator); + return error; +} + +git_repository *git_index_owner(const git_index *index) +{ + return INDEX_OWNER(index); +} + +enum { + INDEX_ACTION_NONE = 0, + INDEX_ACTION_UPDATE = 1, + INDEX_ACTION_REMOVE = 2, + INDEX_ACTION_ADDALL = 3 +}; + +int git_index_add_all( + git_index *index, + const git_strarray *paths, + unsigned int flags, + git_index_matched_path_cb cb, + void *payload) +{ + int error; + git_repository *repo; + git_iterator *wditer = NULL; + git_pathspec ps; + bool no_fnmatch = (flags & GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH) != 0; + + GIT_ASSERT_ARG(index); + + repo = INDEX_OWNER(index); + if ((error = git_repository__ensure_not_bare(repo, "index add all")) < 0) + return error; + + if ((error = git_pathspec__init(&ps, paths)) < 0) + return error; + + /* optionally check that pathspec doesn't mention any ignored files */ + if ((flags & GIT_INDEX_ADD_CHECK_PATHSPEC) != 0 && + (flags & GIT_INDEX_ADD_FORCE) == 0 && + (error = git_ignore__check_pathspec_for_exact_ignores( + repo, &ps.pathspec, no_fnmatch)) < 0) + goto cleanup; + + error = index_apply_to_wd_diff(index, INDEX_ACTION_ADDALL, paths, flags, cb, payload); + + if (error) + git_error_set_after_callback(error); + +cleanup: + git_iterator_free(wditer); + git_pathspec__clear(&ps); + + return error; +} + +struct foreach_diff_data { + git_index *index; + const git_pathspec *pathspec; + unsigned int flags; + git_index_matched_path_cb cb; + void *payload; +}; + +static int apply_each_file(const git_diff_delta *delta, float progress, void *payload) +{ + struct foreach_diff_data *data = payload; + const char *match, *path; + int error = 0; + + GIT_UNUSED(progress); + + path = delta->old_file.path; + + /* We only want those which match the pathspecs */ + if (!git_pathspec__match( + &data->pathspec->pathspec, path, false, (bool)data->index->ignore_case, + &match, NULL)) + return 0; + + if (data->cb) + error = data->cb(path, match, data->payload); + + if (error > 0) /* skip this entry */ + return 0; + if (error < 0) /* actual error */ + return error; + + /* If the workdir item does not exist, remove it from the index. */ + if ((delta->new_file.flags & GIT_DIFF_FLAG_EXISTS) == 0) + error = git_index_remove_bypath(data->index, path); + else + error = git_index_add_bypath(data->index, delta->new_file.path); + + return error; +} + +static int index_apply_to_wd_diff(git_index *index, int action, const git_strarray *paths, + unsigned int flags, + git_index_matched_path_cb cb, void *payload) +{ + int error; + git_diff *diff; + git_pathspec ps; + git_repository *repo; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + struct foreach_diff_data data = { + index, + NULL, + flags, + cb, + payload, + }; + + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(action == INDEX_ACTION_UPDATE || action == INDEX_ACTION_ADDALL); + + repo = INDEX_OWNER(index); + + if (!repo) { + return create_index_error(-1, + "cannot run update; the index is not backed up by a repository."); + } + + /* + * We do the matching ourselves instead of passing the list to + * diff because we want to tell the callback which one + * matched, which we do not know if we ask diff to filter for us. + */ + if ((error = git_pathspec__init(&ps, paths)) < 0) + return error; + + opts.flags = GIT_DIFF_INCLUDE_TYPECHANGE; + if (action == INDEX_ACTION_ADDALL) { + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_RECURSE_UNTRACKED_DIRS; + + if (flags == GIT_INDEX_ADD_FORCE) + opts.flags |= GIT_DIFF_INCLUDE_IGNORED; + } + + if ((error = git_diff_index_to_workdir(&diff, repo, index, &opts)) < 0) + goto cleanup; + + data.pathspec = &ps; + error = git_diff_foreach(diff, apply_each_file, NULL, NULL, NULL, &data); + git_diff_free(diff); + + if (error) /* make sure error is set if callback stopped iteration */ + git_error_set_after_callback(error); + +cleanup: + git_pathspec__clear(&ps); + return error; +} + +static int index_apply_to_all( + git_index *index, + int action, + const git_strarray *paths, + git_index_matched_path_cb cb, + void *payload) +{ + int error = 0; + size_t i; + git_pathspec ps; + const char *match; + git_str path = GIT_STR_INIT; + + GIT_ASSERT_ARG(index); + + if ((error = git_pathspec__init(&ps, paths)) < 0) + return error; + + git_vector_sort(&index->entries); + + for (i = 0; !error && i < index->entries.length; ++i) { + git_index_entry *entry = git_vector_get(&index->entries, i); + + /* check if path actually matches */ + if (!git_pathspec__match( + &ps.pathspec, entry->path, false, (bool)index->ignore_case, + &match, NULL)) + continue; + + /* issue notification callback if requested */ + if (cb && (error = cb(entry->path, match, payload)) != 0) { + if (error > 0) { /* return > 0 means skip this one */ + error = 0; + continue; + } + if (error < 0) /* return < 0 means abort */ + break; + } + + /* index manipulation may alter entry, so don't depend on it */ + if ((error = git_str_sets(&path, entry->path)) < 0) + break; + + switch (action) { + case INDEX_ACTION_NONE: + break; + case INDEX_ACTION_UPDATE: + error = git_index_add_bypath(index, path.ptr); + + if (error == GIT_ENOTFOUND) { + git_error_clear(); + + error = git_index_remove_bypath(index, path.ptr); + + if (!error) /* back up foreach if we removed this */ + i--; + } + break; + case INDEX_ACTION_REMOVE: + if (!(error = git_index_remove_bypath(index, path.ptr))) + i--; /* back up foreach if we removed this */ + break; + default: + git_error_set(GIT_ERROR_INVALID, "unknown index action %d", action); + error = -1; + break; + } + } + + git_str_dispose(&path); + git_pathspec__clear(&ps); + + return error; +} + +int git_index_remove_all( + git_index *index, + const git_strarray *pathspec, + git_index_matched_path_cb cb, + void *payload) +{ + int error = index_apply_to_all( + index, INDEX_ACTION_REMOVE, pathspec, cb, payload); + + if (error) /* make sure error is set if callback stopped iteration */ + git_error_set_after_callback(error); + + return error; +} + +int git_index_update_all( + git_index *index, + const git_strarray *pathspec, + git_index_matched_path_cb cb, + void *payload) +{ + int error = index_apply_to_wd_diff(index, INDEX_ACTION_UPDATE, pathspec, 0, cb, payload); + if (error) /* make sure error is set if callback stopped iteration */ + git_error_set_after_callback(error); + + return error; +} + +int git_index_snapshot_new(git_vector *snap, git_index *index) +{ + int error; + + GIT_REFCOUNT_INC(index); + + git_atomic32_inc(&index->readers); + git_vector_sort(&index->entries); + + error = git_vector_dup(snap, &index->entries, index->entries._cmp); + + if (error < 0) + git_index_snapshot_release(snap, index); + + return error; +} + +void git_index_snapshot_release(git_vector *snap, git_index *index) +{ + git_vector_free(snap); + + git_atomic32_dec(&index->readers); + + git_index_free(index); +} + +int git_index_snapshot_find( + size_t *out, git_vector *entries, git_vector_cmp entry_srch, + const char *path, size_t path_len, int stage) +{ + return index_find_in_entries(out, entries, entry_srch, path, path_len, stage); +} + +int git_indexwriter_init( + git_indexwriter *writer, + git_index *index) +{ + int error; + + GIT_REFCOUNT_INC(index); + + writer->index = index; + + if (!index->index_file_path) + return create_index_error(-1, + "failed to write index: The index is in-memory only"); + + if ((error = git_filebuf_open( + &writer->file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS, GIT_INDEX_FILE_MODE)) < 0) { + + if (error == GIT_ELOCKED) + git_error_set(GIT_ERROR_INDEX, "the index is locked; this might be due to a concurrent or crashed process"); + + return error; + } + + writer->should_write = 1; + + return 0; +} + +int git_indexwriter_init_for_operation( + git_indexwriter *writer, + git_repository *repo, + unsigned int *checkout_strategy) +{ + git_index *index; + int error; + + if ((error = git_repository_index__weakptr(&index, repo)) < 0 || + (error = git_indexwriter_init(writer, index)) < 0) + return error; + + writer->should_write = (*checkout_strategy & GIT_CHECKOUT_DONT_WRITE_INDEX) == 0; + *checkout_strategy |= GIT_CHECKOUT_DONT_WRITE_INDEX; + + return 0; +} + +int git_indexwriter_commit(git_indexwriter *writer) +{ + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size; + int error; + + if (!writer->should_write) + return 0; + + git_vector_sort(&writer->index->entries); + git_vector_sort(&writer->index->reuc); + + if ((error = write_index(checksum, &checksum_size, writer->index, &writer->file)) < 0) { + git_indexwriter_cleanup(writer); + return error; + } + + if ((error = git_filebuf_commit(&writer->file)) < 0) + return error; + + if ((error = git_futils_filestamp_check( + &writer->index->stamp, writer->index->index_file_path)) < 0) { + git_error_set(GIT_ERROR_OS, "could not read index timestamp"); + return -1; + } + + writer->index->dirty = 0; + writer->index->on_disk = 1; + memcpy(writer->index->checksum, checksum, checksum_size); + + git_index_free(writer->index); + writer->index = NULL; + + return 0; +} + +void git_indexwriter_cleanup(git_indexwriter *writer) +{ + git_filebuf_cleanup(&writer->file); + + git_index_free(writer->index); + writer->index = NULL; +} + +/* Deprecated functions */ + +#ifndef GIT_DEPRECATE_HARD +int git_index_add_frombuffer( + git_index *index, const git_index_entry *source_entry, + const void *buffer, size_t len) +{ + return git_index_add_from_buffer(index, source_entry, buffer, len); +} +#endif diff --git a/src/libgit2/index.h b/src/libgit2/index.h new file mode 100644 index 000000000..71bb096f7 --- /dev/null +++ b/src/libgit2/index.h @@ -0,0 +1,194 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_index_h__ +#define INCLUDE_index_h__ + +#include "common.h" + +#include "futils.h" +#include "filebuf.h" +#include "vector.h" +#include "idxmap.h" +#include "tree-cache.h" +#include "git2/odb.h" +#include "git2/index.h" + +#define GIT_INDEX_FILE "index" +#define GIT_INDEX_FILE_MODE 0666 + +extern bool git_index__enforce_unsaved_safety; + +struct git_index { + git_refcount rc; + + char *index_file_path; + git_futils_filestamp stamp; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + + git_vector entries; + git_idxmap *entries_map; + + git_vector deleted; /* deleted entries if readers > 0 */ + git_atomic32 readers; /* number of active iterators */ + + unsigned int on_disk:1; + unsigned int ignore_case:1; + unsigned int distrust_filemode:1; + unsigned int no_symlinks:1; + unsigned int dirty:1; /* whether we have unsaved changes */ + + git_tree_cache *tree; + git_pool tree_pool; + + git_vector names; + git_vector reuc; + + git_vector_cmp entries_cmp_path; + git_vector_cmp entries_search; + git_vector_cmp entries_search_path; + git_vector_cmp reuc_search; + + unsigned int version; +}; + +struct git_index_iterator { + git_index *index; + git_vector snap; + size_t cur; +}; + +struct git_index_conflict_iterator { + git_index *index; + size_t cur; +}; + +extern void git_index_entry__init_from_stat( + git_index_entry *entry, struct stat *st, bool trust_mode); + +/* Index entry comparison functions for array sorting */ +extern int git_index_entry_cmp(const void *a, const void *b); +extern int git_index_entry_icmp(const void *a, const void *b); + +/* Index entry search functions for search using a search spec */ +extern int git_index_entry_srch(const void *a, const void *b); +extern int git_index_entry_isrch(const void *a, const void *b); + +/* Index time handling functions */ +GIT_INLINE(bool) git_index_time_eq(const git_index_time *one, const git_index_time *two) +{ + if (one->seconds != two->seconds) + return false; + +#ifdef GIT_USE_NSEC + if (one->nanoseconds != two->nanoseconds) + return false; +#endif + + return true; +} + +/* + * Test if the given index time is newer than the given existing index entry. + * If the timestamps are exactly equivalent, then the given index time is + * considered "racily newer" than the existing index entry. + */ +GIT_INLINE(bool) git_index_entry_newer_than_index( + const git_index_entry *entry, git_index *index) +{ + /* If we never read the index, we can't have this race either */ + if (!index || index->stamp.mtime.tv_sec == 0) + return false; + + /* If the timestamp is the same or newer than the index, it's racy */ +#if defined(GIT_USE_NSEC) + if ((int32_t)index->stamp.mtime.tv_sec < entry->mtime.seconds) + return true; + else if ((int32_t)index->stamp.mtime.tv_sec > entry->mtime.seconds) + return false; + else + return (uint32_t)index->stamp.mtime.tv_nsec <= entry->mtime.nanoseconds; +#else + return ((int32_t)index->stamp.mtime.tv_sec) <= entry->mtime.seconds; +#endif +} + +/* Search index for `path`, returning GIT_ENOTFOUND if it does not exist + * (but not setting an error message). + * + * `at_pos` is set to the position where it is or would be inserted. + * Pass `path_len` as strlen of path or 0 to call strlen internally. + */ +extern int git_index__find_pos( + size_t *at_pos, git_index *index, const char *path, size_t path_len, int stage); + +extern int git_index__fill(git_index *index, const git_vector *source_entries); + +extern void git_index__set_ignore_case(git_index *index, bool ignore_case); + +extern unsigned int git_index__create_mode(unsigned int mode); + +GIT_INLINE(const git_futils_filestamp *) git_index__filestamp(git_index *index) +{ + return &index->stamp; +} + +GIT_INLINE(unsigned char *) git_index__checksum(git_index *index) +{ + return index->checksum; +} + +/* Copy the current entries vector *and* increment the index refcount. + * Call `git_index__release_snapshot` when done. + */ +extern int git_index_snapshot_new(git_vector *snap, git_index *index); +extern void git_index_snapshot_release(git_vector *snap, git_index *index); + +/* Allow searching in a snapshot; entries must already be sorted! */ +extern int git_index_snapshot_find( + size_t *at_pos, git_vector *snap, git_vector_cmp entry_srch, + const char *path, size_t path_len, int stage); + +/* Replace an index with a new index */ +int git_index_read_index(git_index *index, const git_index *new_index); + +GIT_INLINE(int) git_index_is_dirty(git_index *index) +{ + return index->dirty; +} + +extern int git_index_read_safely(git_index *index); + +typedef struct { + git_index *index; + git_filebuf file; + unsigned int should_write:1; +} git_indexwriter; + +#define GIT_INDEXWRITER_INIT { NULL, GIT_FILEBUF_INIT } + +/* Lock the index for eventual writing. */ +extern int git_indexwriter_init(git_indexwriter *writer, git_index *index); + +/* Lock the index for eventual writing by a repository operation: a merge, + * revert, cherry-pick or a rebase. Note that the given checkout strategy + * will be updated for the operation's use so that checkout will not write + * the index. + */ +extern int git_indexwriter_init_for_operation( + git_indexwriter *writer, + git_repository *repo, + unsigned int *checkout_strategy); + +/* Write the index and unlock it. */ +extern int git_indexwriter_commit(git_indexwriter *writer); + +/* Cleanup an index writing session, unlocking the file (if it is still + * locked and freeing any data structures. + */ +extern void git_indexwriter_cleanup(git_indexwriter *writer); + +#endif diff --git a/src/libgit2/indexer.c b/src/libgit2/indexer.c new file mode 100644 index 000000000..f9a32e7ac --- /dev/null +++ b/src/libgit2/indexer.c @@ -0,0 +1,1413 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "indexer.h" + +#include "git2/indexer.h" +#include "git2/object.h" + +#include "commit.h" +#include "tree.h" +#include "tag.h" +#include "pack.h" +#include "mwindow.h" +#include "posix.h" +#include "pack.h" +#include "filebuf.h" +#include "oid.h" +#include "oidarray.h" +#include "oidmap.h" +#include "zstream.h" +#include "object.h" + +size_t git_indexer__max_objects = UINT32_MAX; + +#define UINT31_MAX (0x7FFFFFFF) + +struct entry { + git_oid oid; + uint32_t crc; + uint32_t offset; + uint64_t offset_long; +}; + +struct git_indexer { + unsigned int parsed_header :1, + pack_committed :1, + have_stream :1, + have_delta :1, + do_fsync :1, + do_verify :1; + struct git_pack_header hdr; + struct git_pack_file *pack; + unsigned int mode; + off64_t off; + off64_t entry_start; + git_object_t entry_type; + git_str entry_data; + git_packfile_stream stream; + size_t nr_objects; + git_vector objects; + git_vector deltas; + unsigned int fanout[256]; + git_hash_ctx hash_ctx; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + char name[(GIT_HASH_SHA1_SIZE * 2) + 1]; + git_indexer_progress_cb progress_cb; + void *progress_payload; + char objbuf[8*1024]; + + /* OIDs referenced from pack objects. Used for verification. */ + git_oidmap *expected_oids; + + /* Needed to look up objects which we want to inject to fix a thin pack */ + git_odb *odb; + + /* Fields for calculating the packfile trailer (hash of everything before it) */ + char inbuf[GIT_OID_RAWSZ]; + size_t inbuf_len; + git_hash_ctx trailer; +}; + +struct delta_info { + off64_t delta_off; +}; + +#ifndef GIT_DEPRECATE_HARD +const git_oid *git_indexer_hash(const git_indexer *idx) +{ + return (git_oid *)idx->checksum; +} +#endif + +const char *git_indexer_name(const git_indexer *idx) +{ + return idx->name; +} + +static int parse_header(struct git_pack_header *hdr, struct git_pack_file *pack) +{ + int error; + git_map map; + + if ((error = p_mmap(&map, sizeof(*hdr), GIT_PROT_READ, GIT_MAP_SHARED, pack->mwf.fd, 0)) < 0) + return error; + + memcpy(hdr, map.data, sizeof(*hdr)); + p_munmap(&map); + + /* Verify we recognize this pack file format. */ + if (hdr->hdr_signature != ntohl(PACK_SIGNATURE)) { + git_error_set(GIT_ERROR_INDEXER, "wrong pack signature"); + return -1; + } + + if (!pack_version_ok(hdr->hdr_version)) { + git_error_set(GIT_ERROR_INDEXER, "wrong pack version"); + return -1; + } + + return 0; +} + +static int objects_cmp(const void *a, const void *b) +{ + const struct entry *entrya = a; + const struct entry *entryb = b; + + return git_oid__cmp(&entrya->oid, &entryb->oid); +} + +int git_indexer_options_init(git_indexer_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_indexer_options, GIT_INDEXER_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_indexer_init_options(git_indexer_options *opts, unsigned int version) +{ + return git_indexer_options_init(opts, version); +} +#endif + +int git_indexer_new( + git_indexer **out, + const char *prefix, + unsigned int mode, + git_odb *odb, + git_indexer_options *in_opts) +{ + git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; + git_indexer *idx; + git_str path = GIT_STR_INIT, tmp_path = GIT_STR_INIT; + static const char suff[] = "/pack"; + int error, fd = -1; + + if (in_opts) + memcpy(&opts, in_opts, sizeof(opts)); + + idx = git__calloc(1, sizeof(git_indexer)); + GIT_ERROR_CHECK_ALLOC(idx); + idx->odb = odb; + idx->progress_cb = opts.progress_cb; + idx->progress_payload = opts.progress_cb_payload; + idx->mode = mode ? mode : GIT_PACK_FILE_MODE; + git_str_init(&idx->entry_data, 0); + + if ((error = git_hash_ctx_init(&idx->hash_ctx, GIT_HASH_ALGORITHM_SHA1)) < 0 || + (error = git_hash_ctx_init(&idx->trailer, GIT_HASH_ALGORITHM_SHA1)) < 0 || + (error = git_oidmap_new(&idx->expected_oids)) < 0) + goto cleanup; + + idx->do_verify = opts.verify; + + if (git_repository__fsync_gitdir) + idx->do_fsync = 1; + + error = git_str_joinpath(&path, prefix, suff); + if (error < 0) + goto cleanup; + + fd = git_futils_mktmp(&tmp_path, git_str_cstr(&path), idx->mode); + git_str_dispose(&path); + if (fd < 0) + goto cleanup; + + error = git_packfile_alloc(&idx->pack, git_str_cstr(&tmp_path)); + git_str_dispose(&tmp_path); + + if (error < 0) + goto cleanup; + + idx->pack->mwf.fd = fd; + if ((error = git_mwindow_file_register(&idx->pack->mwf)) < 0) + goto cleanup; + + *out = idx; + return 0; + +cleanup: + if (fd != -1) + p_close(fd); + + if (git_str_len(&tmp_path) > 0) + p_unlink(git_str_cstr(&tmp_path)); + + if (idx->pack != NULL) + p_unlink(idx->pack->pack_name); + + git_str_dispose(&path); + git_str_dispose(&tmp_path); + git__free(idx); + return -1; +} + +void git_indexer__set_fsync(git_indexer *idx, int do_fsync) +{ + idx->do_fsync = !!do_fsync; +} + +/* Try to store the delta so we can try to resolve it later */ +static int store_delta(git_indexer *idx) +{ + struct delta_info *delta; + + delta = git__calloc(1, sizeof(struct delta_info)); + GIT_ERROR_CHECK_ALLOC(delta); + delta->delta_off = idx->entry_start; + + if (git_vector_insert(&idx->deltas, delta) < 0) + return -1; + + return 0; +} + +static int hash_header(git_hash_ctx *ctx, off64_t len, git_object_t type) +{ + char buffer[64]; + size_t hdrlen; + int error; + + if ((error = git_odb__format_object_header(&hdrlen, + buffer, sizeof(buffer), (size_t)len, type)) < 0) + return error; + + return git_hash_update(ctx, buffer, hdrlen); +} + +static int hash_object_stream(git_indexer*idx, git_packfile_stream *stream) +{ + ssize_t read; + + GIT_ASSERT_ARG(idx); + GIT_ASSERT_ARG(stream); + + do { + if ((read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf))) < 0) + break; + + if (idx->do_verify) + git_str_put(&idx->entry_data, idx->objbuf, read); + + git_hash_update(&idx->hash_ctx, idx->objbuf, read); + } while (read > 0); + + if (read < 0) + return (int)read; + + return 0; +} + +/* In order to create the packfile stream, we need to skip over the delta base description */ +static int advance_delta_offset(git_indexer *idx, git_object_t type) +{ + git_mwindow *w = NULL; + + GIT_ASSERT_ARG(type == GIT_OBJECT_REF_DELTA || type == GIT_OBJECT_OFS_DELTA); + + if (type == GIT_OBJECT_REF_DELTA) { + idx->off += GIT_OID_RAWSZ; + } else { + off64_t base_off; + int error = get_delta_base(&base_off, idx->pack, &w, &idx->off, type, idx->entry_start); + git_mwindow_close(&w); + if (error < 0) + return error; + } + + return 0; +} + +/* Read from the stream and discard any output */ +static int read_object_stream(git_indexer *idx, git_packfile_stream *stream) +{ + ssize_t read; + + GIT_ASSERT_ARG(stream); + + do { + read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf)); + } while (read > 0); + + if (read < 0) + return (int)read; + + return 0; +} + +static int crc_object(uint32_t *crc_out, git_mwindow_file *mwf, off64_t start, off64_t size) +{ + void *ptr; + uint32_t crc; + unsigned int left, len; + git_mwindow *w = NULL; + + crc = crc32(0L, Z_NULL, 0); + while (size) { + ptr = git_mwindow_open(mwf, &w, start, (size_t)size, &left); + if (ptr == NULL) + return -1; + + len = min(left, (unsigned int)size); + crc = crc32(crc, ptr, len); + size -= len; + start += len; + git_mwindow_close(&w); + } + + *crc_out = htonl(crc); + return 0; +} + +static int add_expected_oid(git_indexer *idx, const git_oid *oid) +{ + /* + * If we know about that object because it is stored in our ODB or + * because we have already processed it as part of our pack file, we do + * not have to expect it. + */ + if ((!idx->odb || !git_odb_exists(idx->odb, oid)) && + !git_oidmap_exists(idx->pack->idx_cache, oid) && + !git_oidmap_exists(idx->expected_oids, oid)) { + git_oid *dup = git__malloc(sizeof(*oid)); + GIT_ERROR_CHECK_ALLOC(dup); + git_oid_cpy(dup, oid); + return git_oidmap_set(idx->expected_oids, dup, dup); + } + + return 0; +} + +static int check_object_connectivity(git_indexer *idx, const git_rawobj *obj) +{ + git_object *object; + git_oid *expected; + int error = 0; + + if (obj->type != GIT_OBJECT_BLOB && + obj->type != GIT_OBJECT_TREE && + obj->type != GIT_OBJECT_COMMIT && + obj->type != GIT_OBJECT_TAG) + return 0; + + if (git_object__from_raw(&object, obj->data, obj->len, obj->type) < 0) { + /* + * parse_raw returns EINVALID on invalid data; downgrade + * that to a normal -1 error code. + */ + error = -1; + goto out; + } + + if ((expected = git_oidmap_get(idx->expected_oids, &object->cached.oid)) != NULL) { + git_oidmap_delete(idx->expected_oids, &object->cached.oid); + git__free(expected); + } + + /* + * Check whether this is a known object. If so, we can just continue as + * we assume that the ODB has a complete graph. + */ + if (idx->odb && git_odb_exists(idx->odb, &object->cached.oid)) + return 0; + + switch (obj->type) { + case GIT_OBJECT_TREE: + { + git_tree *tree = (git_tree *) object; + git_tree_entry *entry; + size_t i; + + git_array_foreach(tree->entries, i, entry) + if (add_expected_oid(idx, entry->oid) < 0) + goto out; + + break; + } + case GIT_OBJECT_COMMIT: + { + git_commit *commit = (git_commit *) object; + git_oid *parent_oid; + size_t i; + + git_array_foreach(commit->parent_ids, i, parent_oid) + if (add_expected_oid(idx, parent_oid) < 0) + goto out; + + if (add_expected_oid(idx, &commit->tree_id) < 0) + goto out; + + break; + } + case GIT_OBJECT_TAG: + { + git_tag *tag = (git_tag *) object; + + if (add_expected_oid(idx, &tag->target) < 0) + goto out; + + break; + } + case GIT_OBJECT_BLOB: + default: + break; + } + +out: + git_object_free(object); + + return error; +} + +static int store_object(git_indexer *idx) +{ + int i, error; + git_oid oid; + struct entry *entry; + off64_t entry_size; + struct git_pack_entry *pentry; + off64_t entry_start = idx->entry_start; + + entry = git__calloc(1, sizeof(*entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + pentry = git__calloc(1, sizeof(struct git_pack_entry)); + GIT_ERROR_CHECK_ALLOC(pentry); + + if (git_hash_final(oid.id, &idx->hash_ctx)) { + git__free(pentry); + goto on_error; + } + entry_size = idx->off - entry_start; + if (entry_start > UINT31_MAX) { + entry->offset = UINT32_MAX; + entry->offset_long = entry_start; + } else { + entry->offset = (uint32_t)entry_start; + } + + if (idx->do_verify) { + git_rawobj rawobj = { + idx->entry_data.ptr, + idx->entry_data.size, + idx->entry_type + }; + + if ((error = check_object_connectivity(idx, &rawobj)) < 0) + goto on_error; + } + + git_oid_cpy(&pentry->sha1, &oid); + pentry->offset = entry_start; + + if (git_oidmap_exists(idx->pack->idx_cache, &pentry->sha1)) { + git_error_set(GIT_ERROR_INDEXER, "duplicate object %s found in pack", git_oid_tostr_s(&pentry->sha1)); + git__free(pentry); + goto on_error; + } + + if ((error = git_oidmap_set(idx->pack->idx_cache, &pentry->sha1, pentry)) < 0) { + git__free(pentry); + git_error_set_oom(); + goto on_error; + } + + git_oid_cpy(&entry->oid, &oid); + + if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0) + goto on_error; + + /* Add the object to the list */ + if (git_vector_insert(&idx->objects, entry) < 0) + goto on_error; + + for (i = oid.id[0]; i < 256; ++i) { + idx->fanout[i]++; + } + + return 0; + +on_error: + git__free(entry); + + return -1; +} + +GIT_INLINE(bool) has_entry(git_indexer *idx, git_oid *id) +{ + return git_oidmap_exists(idx->pack->idx_cache, id); +} + +static int save_entry(git_indexer *idx, struct entry *entry, struct git_pack_entry *pentry, off64_t entry_start) +{ + int i; + + if (entry_start > UINT31_MAX) { + entry->offset = UINT32_MAX; + entry->offset_long = entry_start; + } else { + entry->offset = (uint32_t)entry_start; + } + + pentry->offset = entry_start; + + if (git_oidmap_exists(idx->pack->idx_cache, &pentry->sha1) || + git_oidmap_set(idx->pack->idx_cache, &pentry->sha1, pentry) < 0) { + git_error_set(GIT_ERROR_INDEXER, "cannot insert object into pack"); + return -1; + } + + /* Add the object to the list */ + if (git_vector_insert(&idx->objects, entry) < 0) + return -1; + + for (i = entry->oid.id[0]; i < 256; ++i) { + idx->fanout[i]++; + } + + return 0; +} + +static int hash_and_save(git_indexer *idx, git_rawobj *obj, off64_t entry_start) +{ + git_oid oid; + size_t entry_size; + struct entry *entry; + struct git_pack_entry *pentry = NULL; + + entry = git__calloc(1, sizeof(*entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + if (git_odb__hashobj(&oid, obj) < 0) { + git_error_set(GIT_ERROR_INDEXER, "failed to hash object"); + goto on_error; + } + + pentry = git__calloc(1, sizeof(struct git_pack_entry)); + GIT_ERROR_CHECK_ALLOC(pentry); + + git_oid_cpy(&pentry->sha1, &oid); + git_oid_cpy(&entry->oid, &oid); + entry->crc = crc32(0L, Z_NULL, 0); + + entry_size = (size_t)(idx->off - entry_start); + if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0) + goto on_error; + + return save_entry(idx, entry, pentry, entry_start); + +on_error: + git__free(pentry); + git__free(entry); + git__free(obj->data); + return -1; +} + +static int do_progress_callback(git_indexer *idx, git_indexer_progress *stats) +{ + if (idx->progress_cb) + return git_error_set_after_callback_function( + idx->progress_cb(stats, idx->progress_payload), + "indexer progress"); + return 0; +} + +/* Hash everything but the last 20B of input */ +static void hash_partially(git_indexer *idx, const uint8_t *data, size_t size) +{ + size_t to_expell, to_keep; + + if (size == 0) + return; + + /* Easy case, dump the buffer and the data minus the last 20 bytes */ + if (size >= GIT_OID_RAWSZ) { + git_hash_update(&idx->trailer, idx->inbuf, idx->inbuf_len); + git_hash_update(&idx->trailer, data, size - GIT_OID_RAWSZ); + + data += size - GIT_OID_RAWSZ; + memcpy(idx->inbuf, data, GIT_OID_RAWSZ); + idx->inbuf_len = GIT_OID_RAWSZ; + return; + } + + /* We can just append */ + if (idx->inbuf_len + size <= GIT_OID_RAWSZ) { + memcpy(idx->inbuf + idx->inbuf_len, data, size); + idx->inbuf_len += size; + return; + } + + /* We need to partially drain the buffer and then append */ + to_keep = GIT_OID_RAWSZ - size; + to_expell = idx->inbuf_len - to_keep; + + git_hash_update(&idx->trailer, idx->inbuf, to_expell); + + memmove(idx->inbuf, idx->inbuf + to_expell, to_keep); + memcpy(idx->inbuf + to_keep, data, size); + idx->inbuf_len += size - to_expell; +} + +#if defined(NO_MMAP) || !defined(GIT_WIN32) + +static int write_at(git_indexer *idx, const void *data, off64_t offset, size_t size) +{ + size_t remaining_size = size; + const char *ptr = (const char *)data; + + /* Handle data size larger that ssize_t */ + while (remaining_size > 0) { + ssize_t nb; + HANDLE_EINTR(nb, p_pwrite(idx->pack->mwf.fd, (void *)ptr, + remaining_size, offset)); + if (nb <= 0) + return -1; + + ptr += nb; + offset += nb; + remaining_size -= nb; + } + + return 0; +} + +static int append_to_pack(git_indexer *idx, const void *data, size_t size) +{ + if (write_at(idx, data, idx->pack->mwf.size, size) < 0) { + git_error_set(GIT_ERROR_OS, "cannot extend packfile '%s'", idx->pack->pack_name); + return -1; + } + + return 0; +} + +#else + +/* + * Windows may keep different views to a networked file for the mmap- and + * open-accessed versions of a file, so any writes done through + * `write(2)`/`pwrite(2)` may not be reflected on the data that `mmap(2)` is + * able to read. + */ + +static int write_at(git_indexer *idx, const void *data, off64_t offset, size_t size) +{ + git_file fd = idx->pack->mwf.fd; + size_t mmap_alignment; + size_t page_offset; + off64_t page_start; + unsigned char *map_data; + git_map map; + int error; + + GIT_ASSERT_ARG(data); + GIT_ASSERT_ARG(size); + + if ((error = git__mmap_alignment(&mmap_alignment)) < 0) + return error; + + /* the offset needs to be at the mmap boundary for the platform */ + page_offset = offset % mmap_alignment; + page_start = offset - page_offset; + + if ((error = p_mmap(&map, page_offset + size, GIT_PROT_WRITE, GIT_MAP_SHARED, fd, page_start)) < 0) + return error; + + map_data = (unsigned char *)map.data; + memcpy(map_data + page_offset, data, size); + p_munmap(&map); + + return 0; +} + +static int append_to_pack(git_indexer *idx, const void *data, size_t size) +{ + off64_t new_size; + size_t mmap_alignment; + size_t page_offset; + off64_t page_start; + off64_t current_size = idx->pack->mwf.size; + int error; + + if (!size) + return 0; + + if ((error = git__mmap_alignment(&mmap_alignment)) < 0) + return error; + + /* Write a single byte to force the file system to allocate space now or + * report an error, since we can't report errors when writing using mmap. + * Round the size up to the nearest page so that we only need to perform file + * I/O when we add a page, instead of whenever we write even a single byte. */ + new_size = current_size + size; + page_offset = new_size % mmap_alignment; + page_start = new_size - page_offset; + + if (p_pwrite(idx->pack->mwf.fd, data, 1, page_start + mmap_alignment - 1) < 0) { + git_error_set(GIT_ERROR_OS, "cannot extend packfile '%s'", idx->pack->pack_name); + return -1; + } + + return write_at(idx, data, idx->pack->mwf.size, size); +} + +#endif + +static int read_stream_object(git_indexer *idx, git_indexer_progress *stats) +{ + git_packfile_stream *stream = &idx->stream; + off64_t entry_start = idx->off; + size_t entry_size; + git_object_t type; + git_mwindow *w = NULL; + int error; + + if (idx->pack->mwf.size <= idx->off + 20) + return GIT_EBUFS; + + if (!idx->have_stream) { + error = git_packfile_unpack_header(&entry_size, &type, idx->pack, &w, &idx->off); + if (error == GIT_EBUFS) { + idx->off = entry_start; + return error; + } + if (error < 0) + return error; + + git_mwindow_close(&w); + idx->entry_start = entry_start; + git_hash_init(&idx->hash_ctx); + git_str_clear(&idx->entry_data); + + if (type == GIT_OBJECT_REF_DELTA || type == GIT_OBJECT_OFS_DELTA) { + error = advance_delta_offset(idx, type); + if (error == GIT_EBUFS) { + idx->off = entry_start; + return error; + } + if (error < 0) + return error; + + idx->have_delta = 1; + } else { + idx->have_delta = 0; + + error = hash_header(&idx->hash_ctx, entry_size, type); + if (error < 0) + return error; + } + + idx->have_stream = 1; + idx->entry_type = type; + + error = git_packfile_stream_open(stream, idx->pack, idx->off); + if (error < 0) + return error; + } + + if (idx->have_delta) { + error = read_object_stream(idx, stream); + } else { + error = hash_object_stream(idx, stream); + } + + idx->off = stream->curpos; + if (error == GIT_EBUFS) + return error; + + /* We want to free the stream reasorces no matter what here */ + idx->have_stream = 0; + git_packfile_stream_dispose(stream); + + if (error < 0) + return error; + + if (idx->have_delta) { + error = store_delta(idx); + } else { + error = store_object(idx); + } + + if (error < 0) + return error; + + if (!idx->have_delta) { + stats->indexed_objects++; + } + stats->received_objects++; + + if ((error = do_progress_callback(idx, stats)) != 0) + return error; + + return 0; +} + +int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_indexer_progress *stats) +{ + int error = -1; + struct git_pack_header *hdr = &idx->hdr; + git_mwindow_file *mwf = &idx->pack->mwf; + + GIT_ASSERT_ARG(idx); + GIT_ASSERT_ARG(data); + GIT_ASSERT_ARG(stats); + + if ((error = append_to_pack(idx, data, size)) < 0) + return error; + + hash_partially(idx, data, (int)size); + + /* Make sure we set the new size of the pack */ + idx->pack->mwf.size += size; + + if (!idx->parsed_header) { + unsigned int total_objects; + + if ((unsigned)idx->pack->mwf.size < sizeof(struct git_pack_header)) + return 0; + + if ((error = parse_header(&idx->hdr, idx->pack)) < 0) + return error; + + idx->parsed_header = 1; + idx->nr_objects = ntohl(hdr->hdr_entries); + idx->off = sizeof(struct git_pack_header); + + if (idx->nr_objects <= git_indexer__max_objects) { + total_objects = (unsigned int)idx->nr_objects; + } else { + git_error_set(GIT_ERROR_INDEXER, "too many objects"); + return -1; + } + + if (git_oidmap_new(&idx->pack->idx_cache) < 0) + return -1; + + idx->pack->has_cache = 1; + if (git_vector_init(&idx->objects, total_objects, objects_cmp) < 0) + return -1; + + if (git_vector_init(&idx->deltas, total_objects / 2, NULL) < 0) + return -1; + + stats->received_objects = 0; + stats->local_objects = 0; + stats->total_deltas = 0; + stats->indexed_deltas = 0; + stats->indexed_objects = 0; + stats->total_objects = total_objects; + + if ((error = do_progress_callback(idx, stats)) != 0) + return error; + } + + /* Now that we have data in the pack, let's try to parse it */ + + /* As the file grows any windows we try to use will be out of date */ + if ((error = git_mwindow_free_all(mwf)) < 0) + goto on_error; + + while (stats->indexed_objects < idx->nr_objects) { + if ((error = read_stream_object(idx, stats)) != 0) { + if (error == GIT_EBUFS) + break; + else + goto on_error; + } + } + + return 0; + +on_error: + git_mwindow_free_all(mwf); + return error; +} + +static int index_path(git_str *path, git_indexer *idx, const char *suffix) +{ + const char prefix[] = "pack-"; + size_t slash = (size_t)path->size; + + /* search backwards for '/' */ + while (slash > 0 && path->ptr[slash - 1] != '/') + slash--; + + if (git_str_grow(path, slash + 1 + strlen(prefix) + + GIT_OID_HEXSZ + strlen(suffix) + 1) < 0) + return -1; + + git_str_truncate(path, slash); + git_str_puts(path, prefix); + git_str_puts(path, idx->name); + git_str_puts(path, suffix); + + return git_str_oom(path) ? -1 : 0; +} + +/** + * Rewind the packfile by the trailer, as we might need to fix the + * packfile by injecting objects at the tail and must overwrite it. + */ +static int seek_back_trailer(git_indexer *idx) +{ + idx->pack->mwf.size -= GIT_OID_RAWSZ; + return git_mwindow_free_all(&idx->pack->mwf); +} + +static int inject_object(git_indexer *idx, git_oid *id) +{ + git_odb_object *obj = NULL; + struct entry *entry = NULL; + struct git_pack_entry *pentry = NULL; + unsigned char empty_checksum[GIT_HASH_SHA1_SIZE] = {0}; + unsigned char hdr[64]; + git_str buf = GIT_STR_INIT; + off64_t entry_start; + const void *data; + size_t len, hdr_len; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + int error; + + if ((error = seek_back_trailer(idx)) < 0) + goto cleanup; + + entry_start = idx->pack->mwf.size; + + if ((error = git_odb_read(&obj, idx->odb, id)) < 0) { + git_error_set(GIT_ERROR_INDEXER, "missing delta bases"); + goto cleanup; + } + + data = git_odb_object_data(obj); + len = git_odb_object_size(obj); + + entry = git__calloc(1, sizeof(*entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + entry->crc = crc32(0L, Z_NULL, 0); + + /* Write out the object header */ + if ((error = git_packfile__object_header(&hdr_len, hdr, len, git_odb_object_type(obj))) < 0 || + (error = append_to_pack(idx, hdr, hdr_len)) < 0) + goto cleanup; + + idx->pack->mwf.size += hdr_len; + entry->crc = crc32(entry->crc, hdr, (uInt)hdr_len); + + if ((error = git_zstream_deflatebuf(&buf, data, len)) < 0) + goto cleanup; + + /* And then the compressed object */ + if ((error = append_to_pack(idx, buf.ptr, buf.size)) < 0) + goto cleanup; + + idx->pack->mwf.size += buf.size; + entry->crc = htonl(crc32(entry->crc, (unsigned char *)buf.ptr, (uInt)buf.size)); + git_str_dispose(&buf); + + /* Write a fake trailer so the pack functions play ball */ + + if ((error = append_to_pack(idx, empty_checksum, checksum_size)) < 0) + goto cleanup; + + idx->pack->mwf.size += GIT_OID_RAWSZ; + + pentry = git__calloc(1, sizeof(struct git_pack_entry)); + GIT_ERROR_CHECK_ALLOC(pentry); + + git_oid_cpy(&pentry->sha1, id); + git_oid_cpy(&entry->oid, id); + idx->off = entry_start + hdr_len + len; + + error = save_entry(idx, entry, pentry, entry_start); + +cleanup: + if (error) { + git__free(entry); + git__free(pentry); + } + + git_odb_object_free(obj); + return error; +} + +static int fix_thin_pack(git_indexer *idx, git_indexer_progress *stats) +{ + int error, found_ref_delta = 0; + unsigned int i; + struct delta_info *delta; + size_t size; + git_object_t type; + git_mwindow *w = NULL; + off64_t curpos = 0; + unsigned char *base_info; + unsigned int left = 0; + git_oid base; + + GIT_ASSERT(git_vector_length(&idx->deltas) > 0); + + if (idx->odb == NULL) { + git_error_set(GIT_ERROR_INDEXER, "cannot fix a thin pack without an ODB"); + return -1; + } + + /* Loop until we find the first REF delta */ + git_vector_foreach(&idx->deltas, i, delta) { + if (!delta) + continue; + + curpos = delta->delta_off; + error = git_packfile_unpack_header(&size, &type, idx->pack, &w, &curpos); + if (error < 0) + return error; + + if (type == GIT_OBJECT_REF_DELTA) { + found_ref_delta = 1; + break; + } + } + + if (!found_ref_delta) { + git_error_set(GIT_ERROR_INDEXER, "no REF_DELTA found, cannot inject object"); + return -1; + } + + /* curpos now points to the base information, which is an OID */ + base_info = git_mwindow_open(&idx->pack->mwf, &w, curpos, GIT_OID_RAWSZ, &left); + if (base_info == NULL) { + git_error_set(GIT_ERROR_INDEXER, "failed to map delta information"); + return -1; + } + + git_oid_fromraw(&base, base_info); + git_mwindow_close(&w); + + if (has_entry(idx, &base)) + return 0; + + if (inject_object(idx, &base) < 0) + return -1; + + stats->local_objects++; + + return 0; +} + +static int resolve_deltas(git_indexer *idx, git_indexer_progress *stats) +{ + unsigned int i; + int error; + struct delta_info *delta; + int progressed = 0, non_null = 0, progress_cb_result; + + while (idx->deltas.length > 0) { + progressed = 0; + non_null = 0; + git_vector_foreach(&idx->deltas, i, delta) { + git_rawobj obj = {0}; + + if (!delta) + continue; + + non_null = 1; + idx->off = delta->delta_off; + if ((error = git_packfile_unpack(&obj, idx->pack, &idx->off)) < 0) { + if (error == GIT_PASSTHROUGH) { + /* We have not seen the base object, we'll try again later. */ + continue; + } + return -1; + } + + if (idx->do_verify && check_object_connectivity(idx, &obj) < 0) + /* TODO: error? continue? */ + continue; + + if (hash_and_save(idx, &obj, delta->delta_off) < 0) + continue; + + git__free(obj.data); + stats->indexed_objects++; + stats->indexed_deltas++; + progressed = 1; + if ((progress_cb_result = do_progress_callback(idx, stats)) < 0) + return progress_cb_result; + + /* remove from the list */ + git_vector_set(NULL, &idx->deltas, i, NULL); + git__free(delta); + } + + /* if none were actually set, we're done */ + if (!non_null) + break; + + if (!progressed && (fix_thin_pack(idx, stats) < 0)) { + return -1; + } + } + + return 0; +} + +static int update_header_and_rehash(git_indexer *idx, git_indexer_progress *stats) +{ + void *ptr; + size_t chunk = 1024*1024; + off64_t hashed = 0; + git_mwindow *w = NULL; + git_mwindow_file *mwf; + unsigned int left; + + mwf = &idx->pack->mwf; + + git_hash_init(&idx->trailer); + + + /* Update the header to include the number of local objects we injected */ + idx->hdr.hdr_entries = htonl(stats->total_objects + stats->local_objects); + if (write_at(idx, &idx->hdr, 0, sizeof(struct git_pack_header)) < 0) + return -1; + + /* + * We now use the same technique as before to determine the + * hash. We keep reading up to the end and let + * hash_partially() keep the existing trailer out of the + * calculation. + */ + if (git_mwindow_free_all(mwf) < 0) + return -1; + + idx->inbuf_len = 0; + while (hashed < mwf->size) { + ptr = git_mwindow_open(mwf, &w, hashed, chunk, &left); + if (ptr == NULL) + return -1; + + hash_partially(idx, ptr, left); + hashed += left; + + git_mwindow_close(&w); + } + + return 0; +} + +int git_indexer_commit(git_indexer *idx, git_indexer_progress *stats) +{ + git_mwindow *w = NULL; + unsigned int i, long_offsets = 0, left; + int error; + struct git_pack_idx_header hdr; + git_str filename = GIT_STR_INIT; + struct entry *entry; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + git_filebuf index_file = {0}; + void *packfile_trailer; + size_t checksum_size = GIT_HASH_SHA1_SIZE; + bool mismatch; + + if (!idx->parsed_header) { + git_error_set(GIT_ERROR_INDEXER, "incomplete pack header"); + return -1; + } + + /* Test for this before resolve_deltas(), as it plays with idx->off */ + if (idx->off + (ssize_t)checksum_size < idx->pack->mwf.size) { + git_error_set(GIT_ERROR_INDEXER, "unexpected data at the end of the pack"); + return -1; + } + if (idx->off + (ssize_t)checksum_size > idx->pack->mwf.size) { + git_error_set(GIT_ERROR_INDEXER, "missing trailer at the end of the pack"); + return -1; + } + + packfile_trailer = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - checksum_size, checksum_size, &left); + if (packfile_trailer == NULL) { + git_mwindow_close(&w); + goto on_error; + } + + /* Compare the packfile trailer as it was sent to us and what we calculated */ + git_hash_final(checksum, &idx->trailer); + mismatch = !!memcmp(checksum, packfile_trailer, checksum_size); + git_mwindow_close(&w); + + if (mismatch) { + git_error_set(GIT_ERROR_INDEXER, "packfile trailer mismatch"); + return -1; + } + + /* Freeze the number of deltas */ + stats->total_deltas = stats->total_objects - stats->indexed_objects; + + if ((error = resolve_deltas(idx, stats)) < 0) + return error; + + if (stats->indexed_objects != stats->total_objects) { + git_error_set(GIT_ERROR_INDEXER, "early EOF"); + return -1; + } + + if (stats->local_objects > 0) { + if (update_header_and_rehash(idx, stats) < 0) + return -1; + + git_hash_final(checksum, &idx->trailer); + write_at(idx, checksum, idx->pack->mwf.size - checksum_size, checksum_size); + } + + /* + * Is the resulting graph fully connected or are we still + * missing some objects? In the second case, we can + * bail out due to an incomplete and thus corrupt + * packfile. + */ + if (git_oidmap_size(idx->expected_oids) > 0) { + git_error_set(GIT_ERROR_INDEXER, "packfile is missing %"PRIuZ" objects", + git_oidmap_size(idx->expected_oids)); + return -1; + } + + git_vector_sort(&idx->objects); + + /* Use the trailer hash as the pack file name to ensure + * files with different contents have different names */ + memcpy(idx->checksum, checksum, checksum_size); + if (git_hash_fmt(idx->name, checksum, checksum_size) < 0) + return -1; + + git_str_sets(&filename, idx->pack->pack_name); + git_str_shorten(&filename, strlen("pack")); + git_str_puts(&filename, "idx"); + if (git_str_oom(&filename)) + return -1; + + if (git_filebuf_open(&index_file, filename.ptr, + GIT_FILEBUF_HASH_CONTENTS | + (idx->do_fsync ? GIT_FILEBUF_FSYNC : 0), + idx->mode) < 0) + goto on_error; + + /* Write out the header */ + hdr.idx_signature = htonl(PACK_IDX_SIGNATURE); + hdr.idx_version = htonl(2); + git_filebuf_write(&index_file, &hdr, sizeof(hdr)); + + /* Write out the fanout table */ + for (i = 0; i < 256; ++i) { + uint32_t n = htonl(idx->fanout[i]); + git_filebuf_write(&index_file, &n, sizeof(n)); + } + + /* Write out the object names (SHA-1 hashes) */ + git_vector_foreach(&idx->objects, i, entry) { + git_filebuf_write(&index_file, &entry->oid, sizeof(git_oid)); + } + + /* Write out the CRC32 values */ + git_vector_foreach(&idx->objects, i, entry) { + git_filebuf_write(&index_file, &entry->crc, sizeof(uint32_t)); + } + + /* Write out the offsets */ + git_vector_foreach(&idx->objects, i, entry) { + uint32_t n; + + if (entry->offset == UINT32_MAX) + n = htonl(0x80000000 | long_offsets++); + else + n = htonl(entry->offset); + + git_filebuf_write(&index_file, &n, sizeof(uint32_t)); + } + + /* Write out the long offsets */ + git_vector_foreach(&idx->objects, i, entry) { + uint32_t split[2]; + + if (entry->offset != UINT32_MAX) + continue; + + split[0] = htonl(entry->offset_long >> 32); + split[1] = htonl(entry->offset_long & 0xffffffff); + + git_filebuf_write(&index_file, &split, sizeof(uint32_t) * 2); + } + + /* Write out the packfile trailer to the index */ + if (git_filebuf_write(&index_file, checksum, checksum_size) < 0) + goto on_error; + + /* Write out the hash of the idx */ + if (git_filebuf_hash(checksum, &index_file) < 0) + goto on_error; + + git_filebuf_write(&index_file, checksum, checksum_size); + + /* Figure out what the final name should be */ + if (index_path(&filename, idx, ".idx") < 0) + goto on_error; + + /* Commit file */ + if (git_filebuf_commit_at(&index_file, filename.ptr) < 0) + goto on_error; + + if (git_mwindow_free_all(&idx->pack->mwf) < 0) + goto on_error; + +#if !defined(NO_MMAP) && defined(GIT_WIN32) + /* + * Some non-Windows remote filesystems fail when truncating files if the + * file permissions change after opening the file (done by p_mkstemp). + * + * Truncation is only needed when mmap is used to undo rounding up to next + * page_size in append_to_pack. + */ + if (p_ftruncate(idx->pack->mwf.fd, idx->pack->mwf.size) < 0) { + git_error_set(GIT_ERROR_OS, "failed to truncate pack file '%s'", idx->pack->pack_name); + return -1; + } +#endif + + if (idx->do_fsync && p_fsync(idx->pack->mwf.fd) < 0) { + git_error_set(GIT_ERROR_OS, "failed to fsync packfile"); + goto on_error; + } + + /* We need to close the descriptor here so Windows doesn't choke on commit_at */ + if (p_close(idx->pack->mwf.fd) < 0) { + git_error_set(GIT_ERROR_OS, "failed to close packfile"); + goto on_error; + } + + idx->pack->mwf.fd = -1; + + if (index_path(&filename, idx, ".pack") < 0) + goto on_error; + + /* And don't forget to rename the packfile to its new place. */ + if (p_rename(idx->pack->pack_name, git_str_cstr(&filename)) < 0) + goto on_error; + + /* And fsync the parent directory if we're asked to. */ + if (idx->do_fsync && + git_futils_fsync_parent(git_str_cstr(&filename)) < 0) + goto on_error; + + idx->pack_committed = 1; + + git_str_dispose(&filename); + return 0; + +on_error: + git_mwindow_free_all(&idx->pack->mwf); + git_filebuf_cleanup(&index_file); + git_str_dispose(&filename); + return -1; +} + +void git_indexer_free(git_indexer *idx) +{ + const git_oid *key; + git_oid *value; + size_t iter; + + if (idx == NULL) + return; + + if (idx->have_stream) + git_packfile_stream_dispose(&idx->stream); + + git_vector_free_deep(&idx->objects); + + if (idx->pack->idx_cache) { + struct git_pack_entry *pentry; + git_oidmap_foreach_value(idx->pack->idx_cache, pentry, { + git__free(pentry); + }); + + git_oidmap_free(idx->pack->idx_cache); + } + + git_vector_free_deep(&idx->deltas); + + git_packfile_free(idx->pack, !idx->pack_committed); + + iter = 0; + while (git_oidmap_iterate((void **) &value, idx->expected_oids, &iter, &key) == 0) + git__free(value); + + git_hash_ctx_cleanup(&idx->trailer); + git_hash_ctx_cleanup(&idx->hash_ctx); + git_str_dispose(&idx->entry_data); + git_oidmap_free(idx->expected_oids); + git__free(idx); +} diff --git a/src/libgit2/indexer.h b/src/libgit2/indexer.h new file mode 100644 index 000000000..8ee6115a6 --- /dev/null +++ b/src/libgit2/indexer.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_indexer_h__ +#define INCLUDE_indexer_h__ + +#include "common.h" + +#include "git2/indexer.h" + +extern void git_indexer__set_fsync(git_indexer *idx, int do_fsync); + +#endif diff --git a/src/libgit2/integer.h b/src/libgit2/integer.h new file mode 100644 index 000000000..63277177b --- /dev/null +++ b/src/libgit2/integer.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_integer_h__ +#define INCLUDE_integer_h__ + +/** @return true if p fits into the range of a size_t */ +GIT_INLINE(int) git__is_sizet(int64_t p) +{ + size_t r = (size_t)p; + return p == (int64_t)r; +} + +/** @return true if p fits into the range of an ssize_t */ +GIT_INLINE(int) git__is_ssizet(size_t p) +{ + ssize_t r = (ssize_t)p; + return p == (size_t)r; +} + +/** @return true if p fits into the range of a uint16_t */ +GIT_INLINE(int) git__is_uint16(size_t p) +{ + uint16_t r = (uint16_t)p; + return p == (size_t)r; +} + +/** @return true if p fits into the range of a uint32_t */ +GIT_INLINE(int) git__is_uint32(size_t p) +{ + uint32_t r = (uint32_t)p; + return p == (size_t)r; +} + +/** @return true if p fits into the range of an unsigned long */ +GIT_INLINE(int) git__is_ulong(int64_t p) +{ + unsigned long r = (unsigned long)p; + return p == (int64_t)r; +} + +/** @return true if p fits into the range of an int */ +GIT_INLINE(int) git__is_int(int64_t p) +{ + int r = (int)p; + return p == (int64_t)r; +} + +/* Use clang/gcc compiler intrinsics whenever possible */ +#if (__has_builtin(__builtin_add_overflow) || \ + (defined(__GNUC__) && (__GNUC__ >= 5))) + +# if (SIZE_MAX == UINT_MAX) +# define git__add_sizet_overflow(out, one, two) \ + __builtin_uadd_overflow(one, two, out) +# define git__multiply_sizet_overflow(out, one, two) \ + __builtin_umul_overflow(one, two, out) +# elif (SIZE_MAX == ULONG_MAX) +# define git__add_sizet_overflow(out, one, two) \ + __builtin_uaddl_overflow(one, two, out) +# define git__multiply_sizet_overflow(out, one, two) \ + __builtin_umull_overflow(one, two, out) +# elif (SIZE_MAX == ULLONG_MAX) +# define git__add_sizet_overflow(out, one, two) \ + __builtin_uaddll_overflow(one, two, out) +# define git__multiply_sizet_overflow(out, one, two) \ + __builtin_umulll_overflow(one, two, out) +# else +# error compiler has add with overflow intrinsics but SIZE_MAX is unknown +# endif + +# define git__add_int_overflow(out, one, two) \ + __builtin_sadd_overflow(one, two, out) +# define git__sub_int_overflow(out, one, two) \ + __builtin_ssub_overflow(one, two, out) + +# define git__add_int64_overflow(out, one, two) \ + __builtin_add_overflow(one, two, out) + +/* clang on 32-bit systems produces an undefined reference to `__mulodi4`. */ +# if !defined(__clang__) || !defined(GIT_ARCH_32) +# define git__multiply_int64_overflow(out, one, two) \ + __builtin_mul_overflow(one, two, out) +# endif + +/* Use Microsoft's safe integer handling functions where available */ +#elif defined(_MSC_VER) + +# define ENABLE_INTSAFE_SIGNED_FUNCTIONS +# include + +# define git__add_sizet_overflow(out, one, two) \ + (SizeTAdd(one, two, out) != S_OK) +# define git__multiply_sizet_overflow(out, one, two) \ + (SizeTMult(one, two, out) != S_OK) + +#define git__add_int_overflow(out, one, two) \ + (IntAdd(one, two, out) != S_OK) +#define git__sub_int_overflow(out, one, two) \ + (IntSub(one, two, out) != S_OK) + +#define git__add_int64_overflow(out, one, two) \ + (LongLongAdd(one, two, out) != S_OK) +#define git__multiply_int64_overflow(out, one, two) \ + (LongLongMult(one, two, out) != S_OK) + +#else + +/** + * Sets `one + two` into `out`, unless the arithmetic would overflow. + * @return false if the result fits in a `size_t`, true on overflow. + */ +GIT_INLINE(bool) git__add_sizet_overflow(size_t *out, size_t one, size_t two) +{ + if (SIZE_MAX - one < two) + return true; + *out = one + two; + return false; +} + +/** + * Sets `one * two` into `out`, unless the arithmetic would overflow. + * @return false if the result fits in a `size_t`, true on overflow. + */ +GIT_INLINE(bool) git__multiply_sizet_overflow(size_t *out, size_t one, size_t two) +{ + if (one && SIZE_MAX / one < two) + return true; + *out = one * two; + return false; +} + +GIT_INLINE(bool) git__add_int_overflow(int *out, int one, int two) +{ + if ((two > 0 && one > (INT_MAX - two)) || + (two < 0 && one < (INT_MIN - two))) + return true; + *out = one + two; + return false; +} + +GIT_INLINE(bool) git__sub_int_overflow(int *out, int one, int two) +{ + if ((two > 0 && one < (INT_MIN + two)) || + (two < 0 && one > (INT_MAX + two))) + return true; + *out = one - two; + return false; +} + +GIT_INLINE(bool) git__add_int64_overflow(int64_t *out, int64_t one, int64_t two) +{ + if ((two > 0 && one > (INT64_MAX - two)) || + (two < 0 && one < (INT64_MIN - two))) + return true; + *out = one + two; + return false; +} + +#endif + +/* If we could not provide an intrinsic implementation for this, provide a (slow) fallback. */ +#if !defined(git__multiply_int64_overflow) +GIT_INLINE(bool) git__multiply_int64_overflow(int64_t *out, int64_t one, int64_t two) +{ + /* + * Detects whether `INT64_MAX < (one * two) || INT64_MIN > (one * two)`, + * without incurring in undefined behavior. That is done by performing the + * comparison with a division instead of a multiplication, which translates + * to `INT64_MAX / one < two || INT64_MIN / one > two`. Some caveats: + * + * - The comparison sign is inverted when both sides of the inequality are + * multiplied/divided by a negative number, so if `one < 0` the comparison + * needs to be flipped. + * - `INT64_MAX / -1` itself overflows (or traps), so that case should be + * avoided. + * - Since the overflow flag is defined as the discrepance between the result + * of performing the multiplication in a signed integer at twice the width + * of the operands, and the truncated+sign-extended version of that same + * result, there are four cases where the result is the opposite of what + * would be expected: + * * `INT64_MIN * -1` / `-1 * INT64_MIN` + * * `INT64_MIN * 1 / `1 * INT64_MIN` + */ + if (one && two) { + if (one > 0 && two > 0) { + if (INT64_MAX / one < two) + return true; + } else if (one < 0 && two < 0) { + if ((one == -1 && two == INT64_MIN) || + (two == -1 && one == INT64_MIN)) { + *out = INT64_MIN; + return false; + } + if (INT64_MAX / one > two) + return true; + } else if (one > 0 && two < 0) { + if ((one == 1 && two == INT64_MIN) || + (INT64_MIN / one > two)) + return true; + } else if (one == -1) { + if (INT64_MIN / two > one) + return true; + } else { + if ((one == INT64_MIN && two == 1) || + (INT64_MIN / one < two)) + return true; + } + } + *out = one * two; + return false; +} +#endif + +#endif diff --git a/src/libgit2/iterator.c b/src/libgit2/iterator.c new file mode 100644 index 000000000..15bb63dc8 --- /dev/null +++ b/src/libgit2/iterator.c @@ -0,0 +1,2439 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "iterator.h" + +#include "tree.h" +#include "index.h" +#include "path.h" + +#define GIT_ITERATOR_FIRST_ACCESS (1 << 15) +#define GIT_ITERATOR_HONOR_IGNORES (1 << 16) +#define GIT_ITERATOR_IGNORE_DOT_GIT (1 << 17) + +#define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0) +#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE) +#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES) +#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND) +#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND) +#define iterator__include_conflicts(I) iterator__flag(I,INCLUDE_CONFLICTS) +#define iterator__has_been_accessed(I) iterator__flag(I,FIRST_ACCESS) +#define iterator__honor_ignores(I) iterator__flag(I,HONOR_IGNORES) +#define iterator__ignore_dot_git(I) iterator__flag(I,IGNORE_DOT_GIT) +#define iterator__descend_symlinks(I) iterator__flag(I,DESCEND_SYMLINKS) + + +static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case) +{ + if (ignore_case) + iter->flags |= GIT_ITERATOR_IGNORE_CASE; + else + iter->flags &= ~GIT_ITERATOR_IGNORE_CASE; + + iter->strcomp = ignore_case ? git__strcasecmp : git__strcmp; + iter->strncomp = ignore_case ? git__strncasecmp : git__strncmp; + iter->prefixcomp = ignore_case ? git__prefixcmp_icase : git__prefixcmp; + iter->entry_srch = ignore_case ? git_index_entry_isrch : git_index_entry_srch; + + git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp); +} + +static int iterator_range_init( + git_iterator *iter, const char *start, const char *end) +{ + if (start && *start) { + iter->start = git__strdup(start); + GIT_ERROR_CHECK_ALLOC(iter->start); + + iter->start_len = strlen(iter->start); + } + + if (end && *end) { + iter->end = git__strdup(end); + GIT_ERROR_CHECK_ALLOC(iter->end); + + iter->end_len = strlen(iter->end); + } + + iter->started = (iter->start == NULL); + iter->ended = false; + + return 0; +} + +static void iterator_range_free(git_iterator *iter) +{ + if (iter->start) { + git__free(iter->start); + iter->start = NULL; + iter->start_len = 0; + } + + if (iter->end) { + git__free(iter->end); + iter->end = NULL; + iter->end_len = 0; + } +} + +static int iterator_reset_range( + git_iterator *iter, const char *start, const char *end) +{ + iterator_range_free(iter); + return iterator_range_init(iter, start, end); +} + +static int iterator_pathlist_init(git_iterator *iter, git_strarray *pathlist) +{ + size_t i; + + if (git_vector_init(&iter->pathlist, pathlist->count, NULL) < 0) + return -1; + + for (i = 0; i < pathlist->count; i++) { + if (!pathlist->strings[i]) + continue; + + if (git_vector_insert(&iter->pathlist, pathlist->strings[i]) < 0) + return -1; + } + + return 0; +} + +static int iterator_init_common( + git_iterator *iter, + git_repository *repo, + git_index *index, + git_iterator_options *given_opts) +{ + static git_iterator_options default_opts = GIT_ITERATOR_OPTIONS_INIT; + git_iterator_options *options = given_opts ? given_opts : &default_opts; + bool ignore_case; + int precompose; + int error; + + iter->repo = repo; + iter->index = index; + iter->flags = options->flags; + + if ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) { + ignore_case = true; + } else if ((iter->flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0) { + ignore_case = false; + } else if (repo) { + git_index *index; + + if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0) + return error; + + ignore_case = !!index->ignore_case; + + if (ignore_case == 1) + iter->flags |= GIT_ITERATOR_IGNORE_CASE; + else + iter->flags |= GIT_ITERATOR_DONT_IGNORE_CASE; + } else { + ignore_case = false; + } + + /* try to look up precompose and set flag if appropriate */ + if (repo && + (iter->flags & GIT_ITERATOR_PRECOMPOSE_UNICODE) == 0 && + (iter->flags & GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE) == 0) { + + if (git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) < 0) + git_error_clear(); + else if (precompose) + iter->flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; + } + + if ((iter->flags & GIT_ITERATOR_DONT_AUTOEXPAND)) + iter->flags |= GIT_ITERATOR_INCLUDE_TREES; + + if ((error = iterator_range_init(iter, options->start, options->end)) < 0 || + (error = iterator_pathlist_init(iter, &options->pathlist)) < 0) + return error; + + iterator_set_ignore_case(iter, ignore_case); + return 0; +} + +static void iterator_clear(git_iterator *iter) +{ + iter->started = false; + iter->ended = false; + iter->stat_calls = 0; + iter->pathlist_walk_idx = 0; + iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS; +} + +GIT_INLINE(bool) iterator_has_started( + git_iterator *iter, const char *path, bool is_submodule) +{ + size_t path_len; + + if (iter->start == NULL || iter->started == true) + return true; + + /* the starting path is generally a prefix - we have started once we + * are prefixed by this path + */ + iter->started = (iter->prefixcomp(path, iter->start) >= 0); + + if (iter->started) + return true; + + path_len = strlen(path); + + /* if, however, we are a submodule, then we support `start` being + * suffixed with a `/` for crazy legacy reasons. match `submod` + * with a start path of `submod/`. + */ + if (is_submodule && iter->start_len && path_len == iter->start_len - 1 && + iter->start[iter->start_len-1] == '/') + return true; + + /* if, however, our current path is a directory, and our starting path + * is _beneath_ that directory, then recurse into the directory (even + * though we have not yet "started") + */ + if (path_len > 0 && path[path_len-1] == '/' && + iter->strncomp(path, iter->start, path_len) == 0) + return true; + + return false; +} + +GIT_INLINE(bool) iterator_has_ended(git_iterator *iter, const char *path) +{ + if (iter->end == NULL) + return false; + else if (iter->ended) + return true; + + iter->ended = (iter->prefixcomp(path, iter->end) > 0); + return iter->ended; +} + +/* walker for the index and tree iterator that allows it to walk the sorted + * pathlist entries alongside sorted iterator entries. + */ +static bool iterator_pathlist_next_is(git_iterator *iter, const char *path) +{ + char *p; + size_t path_len, p_len, cmp_len, i; + int cmp; + + if (iter->pathlist.length == 0) + return true; + + git_vector_sort(&iter->pathlist); + + path_len = strlen(path); + + /* for comparison, drop the trailing slash on the current '/' */ + if (path_len && path[path_len-1] == '/') + path_len--; + + for (i = iter->pathlist_walk_idx; i < iter->pathlist.length; i++) { + p = iter->pathlist.contents[i]; + p_len = strlen(p); + + if (p_len && p[p_len-1] == '/') + p_len--; + + cmp_len = min(path_len, p_len); + + /* see if the pathlist entry is a prefix of this path */ + cmp = iter->strncomp(p, path, cmp_len); + + /* prefix match - see if there's an exact match, or if we were + * given a path that matches the directory + */ + if (cmp == 0) { + /* if this pathlist entry is not suffixed with a '/' then + * it matches a path that is a file or a directory. + * (eg, pathlist = "foo" and path is "foo" or "foo/" or + * "foo/something") + */ + if (p[cmp_len] == '\0' && + (path[cmp_len] == '\0' || path[cmp_len] == '/')) + return true; + + /* if this pathlist entry _is_ suffixed with a '/' then + * it matches only paths that are directories. + * (eg, pathlist = "foo/" and path is "foo/" or "foo/something") + */ + if (p[cmp_len] == '/' && path[cmp_len] == '/') + return true; + } + + /* this pathlist entry sorts before the given path, try the next */ + else if (cmp < 0) { + iter->pathlist_walk_idx++; + continue; + } + + /* this pathlist sorts after the given path, no match. */ + else if (cmp > 0) { + break; + } + } + + return false; +} + +typedef enum { + ITERATOR_PATHLIST_NONE = 0, + ITERATOR_PATHLIST_IS_FILE = 1, + ITERATOR_PATHLIST_IS_DIR = 2, + ITERATOR_PATHLIST_IS_PARENT = 3, + ITERATOR_PATHLIST_FULL = 4 +} iterator_pathlist_search_t; + +static iterator_pathlist_search_t iterator_pathlist_search( + git_iterator *iter, const char *path, size_t path_len) +{ + const char *p; + size_t idx; + int error; + + if (iter->pathlist.length == 0) + return ITERATOR_PATHLIST_FULL; + + git_vector_sort(&iter->pathlist); + + error = git_vector_bsearch2(&idx, &iter->pathlist, + (git_vector_cmp)iter->strcomp, path); + + /* the given path was found in the pathlist. since the pathlist only + * matches directories when they're suffixed with a '/', analyze the + * path string to determine whether it's a directory or not. + */ + if (error == 0) { + if (path_len && path[path_len-1] == '/') + return ITERATOR_PATHLIST_IS_DIR; + + return ITERATOR_PATHLIST_IS_FILE; + } + + /* at this point, the path we're examining may be a directory (though we + * don't know that yet, since we're avoiding a stat unless it's necessary) + * so walk the pathlist looking for the given path with a '/' after it, + */ + while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) { + if (iter->prefixcomp(p, path) != 0) + break; + + /* an exact match would have been matched by the bsearch above */ + GIT_ASSERT_WITH_RETVAL(p[path_len], ITERATOR_PATHLIST_NONE); + + /* is this a literal directory entry (eg `foo/`) or a file beneath */ + if (p[path_len] == '/') { + return (p[path_len+1] == '\0') ? + ITERATOR_PATHLIST_IS_DIR : + ITERATOR_PATHLIST_IS_PARENT; + } + + if (p[path_len] > '/') + break; + + idx++; + } + + return ITERATOR_PATHLIST_NONE; +} + +/* Empty iterator */ + +static int empty_iterator_noop(const git_index_entry **e, git_iterator *i) +{ + GIT_UNUSED(i); + + if (e) + *e = NULL; + + return GIT_ITEROVER; +} + +static int empty_iterator_advance_over( + const git_index_entry **e, + git_iterator_status_t *s, + git_iterator *i) +{ + *s = GIT_ITERATOR_STATUS_EMPTY; + return empty_iterator_noop(e, i); +} + +static int empty_iterator_reset(git_iterator *i) +{ + GIT_UNUSED(i); + return 0; +} + +static void empty_iterator_free(git_iterator *i) +{ + GIT_UNUSED(i); +} + +typedef struct { + git_iterator base; + git_iterator_callbacks cb; +} empty_iterator; + +int git_iterator_for_nothing( + git_iterator **out, + git_iterator_options *options) +{ + empty_iterator *iter; + + static git_iterator_callbacks callbacks = { + empty_iterator_noop, + empty_iterator_noop, + empty_iterator_noop, + empty_iterator_advance_over, + empty_iterator_reset, + empty_iterator_free + }; + + *out = NULL; + + iter = git__calloc(1, sizeof(empty_iterator)); + GIT_ERROR_CHECK_ALLOC(iter); + + iter->base.type = GIT_ITERATOR_EMPTY; + iter->base.cb = &callbacks; + iter->base.flags = options->flags; + + *out = &iter->base; + return 0; +} + +/* Tree iterator */ + +typedef struct { + git_tree_entry *tree_entry; + const char *parent_path; +} tree_iterator_entry; + +typedef struct { + git_tree *tree; + + /* path to this particular frame (folder) */ + git_str path; + + /* a sorted list of the entries for this frame (folder), these are + * actually pointers to the iterator's entry pool. + */ + git_vector entries; + tree_iterator_entry *current; + + size_t next_idx; + + /* on case insensitive iterations, we also have an array of other + * paths that were case insensitively equal to this one, and their + * tree objects. we have coalesced the tree entries into this frame. + * a child `tree_iterator_entry` will contain a pointer to its actual + * parent path. + */ + git_vector similar_trees; + git_array_t(git_str) similar_paths; +} tree_iterator_frame; + +typedef struct { + git_iterator base; + git_tree *root; + git_array_t(tree_iterator_frame) frames; + + git_index_entry entry; + git_str entry_path; + + /* a pool of entries to reduce the number of allocations */ + git_pool entry_pool; +} tree_iterator; + +GIT_INLINE(tree_iterator_frame *) tree_iterator_parent_frame( + tree_iterator *iter) +{ + return iter->frames.size > 1 ? + &iter->frames.ptr[iter->frames.size-2] : NULL; +} + +GIT_INLINE(tree_iterator_frame *) tree_iterator_current_frame( + tree_iterator *iter) +{ + return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL; +} + +GIT_INLINE(int) tree_entry_cmp( + const git_tree_entry *a, const git_tree_entry *b, bool icase) +{ + return git_fs_path_cmp( + a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE, + b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE, + icase ? git__strncasecmp : git__strncmp); +} + +GIT_INLINE(int) tree_iterator_entry_cmp_icase( + const void *ptr_a, const void *ptr_b) +{ + const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a; + const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b; + + return tree_entry_cmp(a->tree_entry, b->tree_entry, true); +} + +static int tree_iterator_entry_sort_icase(const void *ptr_a, const void *ptr_b) +{ + const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a; + const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b; + + int c = tree_entry_cmp(a->tree_entry, b->tree_entry, true); + + /* stabilize the sort order for filenames that are (case insensitively) + * the same by examining the parent path (case sensitively) before + * falling back to a case sensitive sort of the filename. + */ + if (!c && a->parent_path != b->parent_path) + c = git__strcmp(a->parent_path, b->parent_path); + + if (!c) + c = tree_entry_cmp(a->tree_entry, b->tree_entry, false); + + return c; +} + +static int tree_iterator_compute_path( + git_str *out, + tree_iterator_entry *entry) +{ + git_str_clear(out); + + if (entry->parent_path) + git_str_joinpath(out, entry->parent_path, entry->tree_entry->filename); + else + git_str_puts(out, entry->tree_entry->filename); + + if (git_tree_entry__is_tree(entry->tree_entry)) + git_str_putc(out, '/'); + + if (git_str_oom(out)) + return -1; + + return 0; +} + +static int tree_iterator_frame_init( + tree_iterator *iter, + git_tree *tree, + tree_iterator_entry *frame_entry) +{ + tree_iterator_frame *new_frame = NULL; + tree_iterator_entry *new_entry; + git_tree *dup = NULL; + git_tree_entry *tree_entry; + git_vector_cmp cmp; + size_t i; + int error = 0; + + new_frame = git_array_alloc(iter->frames); + GIT_ERROR_CHECK_ALLOC(new_frame); + + if ((error = git_tree_dup(&dup, tree)) < 0) + goto done; + + memset(new_frame, 0x0, sizeof(tree_iterator_frame)); + new_frame->tree = dup; + + if (frame_entry && + (error = tree_iterator_compute_path(&new_frame->path, frame_entry)) < 0) + goto done; + + cmp = iterator__ignore_case(&iter->base) ? + tree_iterator_entry_sort_icase : NULL; + + if ((error = git_vector_init(&new_frame->entries, + dup->entries.size, cmp)) < 0) + goto done; + + git_array_foreach(dup->entries, i, tree_entry) { + if ((new_entry = git_pool_malloc(&iter->entry_pool, 1)) == NULL) { + git_error_set_oom(); + error = -1; + goto done; + } + + new_entry->tree_entry = tree_entry; + new_entry->parent_path = new_frame->path.ptr; + + if ((error = git_vector_insert(&new_frame->entries, new_entry)) < 0) + goto done; + } + + git_vector_set_sorted(&new_frame->entries, + !iterator__ignore_case(&iter->base)); + +done: + if (error < 0) { + git_tree_free(dup); + git_array_pop(iter->frames); + } + + return error; +} + +GIT_INLINE(tree_iterator_entry *) tree_iterator_current_entry( + tree_iterator_frame *frame) +{ + return frame->current; +} + +GIT_INLINE(int) tree_iterator_frame_push_neighbors( + tree_iterator *iter, + tree_iterator_frame *parent_frame, + tree_iterator_frame *frame, + const char *filename) +{ + tree_iterator_entry *entry, *new_entry; + git_tree *tree = NULL; + git_tree_entry *tree_entry; + git_str *path; + size_t new_size, i; + int error = 0; + + while (parent_frame->next_idx < parent_frame->entries.length) { + entry = parent_frame->entries.contents[parent_frame->next_idx]; + + if (strcasecmp(filename, entry->tree_entry->filename) != 0) + break; + + if ((error = git_tree_lookup(&tree, + iter->base.repo, entry->tree_entry->oid)) < 0) + break; + + if (git_vector_insert(&parent_frame->similar_trees, tree) < 0) + break; + + path = git_array_alloc(parent_frame->similar_paths); + GIT_ERROR_CHECK_ALLOC(path); + + memset(path, 0, sizeof(git_str)); + + if ((error = tree_iterator_compute_path(path, entry)) < 0) + break; + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, + frame->entries.length, tree->entries.size); + git_vector_size_hint(&frame->entries, new_size); + + git_array_foreach(tree->entries, i, tree_entry) { + new_entry = git_pool_malloc(&iter->entry_pool, 1); + GIT_ERROR_CHECK_ALLOC(new_entry); + + new_entry->tree_entry = tree_entry; + new_entry->parent_path = path->ptr; + + if ((error = git_vector_insert(&frame->entries, new_entry)) < 0) + break; + } + + if (error) + break; + + parent_frame->next_idx++; + } + + return error; +} + +GIT_INLINE(int) tree_iterator_frame_push( + tree_iterator *iter, tree_iterator_entry *entry) +{ + tree_iterator_frame *parent_frame, *frame; + git_tree *tree = NULL; + int error; + + if ((error = git_tree_lookup(&tree, + iter->base.repo, entry->tree_entry->oid)) < 0 || + (error = tree_iterator_frame_init(iter, tree, entry)) < 0) + goto done; + + parent_frame = tree_iterator_parent_frame(iter); + frame = tree_iterator_current_frame(iter); + + /* if we're case insensitive, then we may have another directory that + * is (case insensitively) equal to this one. coalesce those children + * into this tree. + */ + if (iterator__ignore_case(&iter->base)) + error = tree_iterator_frame_push_neighbors(iter, + parent_frame, frame, entry->tree_entry->filename); + +done: + git_tree_free(tree); + return error; +} + +static int tree_iterator_frame_pop(tree_iterator *iter) +{ + tree_iterator_frame *frame; + git_str *buf = NULL; + git_tree *tree; + size_t i; + + GIT_ASSERT(iter->frames.size); + + frame = git_array_pop(iter->frames); + + git_vector_free(&frame->entries); + git_tree_free(frame->tree); + + do { + buf = git_array_pop(frame->similar_paths); + git_str_dispose(buf); + } while (buf != NULL); + + git_array_clear(frame->similar_paths); + + git_vector_foreach(&frame->similar_trees, i, tree) + git_tree_free(tree); + + git_vector_free(&frame->similar_trees); + + git_str_dispose(&frame->path); + + return 0; +} + +static int tree_iterator_current( + const git_index_entry **out, git_iterator *i) +{ + tree_iterator *iter = (tree_iterator *)i; + + if (!iterator__has_been_accessed(i)) + return iter->base.cb->advance(out, i); + + if (!iter->frames.size) { + *out = NULL; + return GIT_ITEROVER; + } + + *out = &iter->entry; + return 0; +} + +static void tree_iterator_set_current( + tree_iterator *iter, + tree_iterator_frame *frame, + tree_iterator_entry *entry) +{ + git_tree_entry *tree_entry = entry->tree_entry; + + frame->current = entry; + + memset(&iter->entry, 0x0, sizeof(git_index_entry)); + + iter->entry.mode = tree_entry->attr; + iter->entry.path = iter->entry_path.ptr; + git_oid_cpy(&iter->entry.id, tree_entry->oid); +} + +static int tree_iterator_advance(const git_index_entry **out, git_iterator *i) +{ + tree_iterator *iter = (tree_iterator *)i; + int error = 0; + + iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; + + /* examine tree entries until we find the next one to return */ + while (true) { + tree_iterator_entry *prev_entry, *entry; + tree_iterator_frame *frame; + bool is_tree; + + if ((frame = tree_iterator_current_frame(iter)) == NULL) { + error = GIT_ITEROVER; + break; + } + + /* no more entries in this frame. pop the frame out */ + if (frame->next_idx == frame->entries.length) { + if ((error = tree_iterator_frame_pop(iter)) < 0) + break; + + continue; + } + + /* we may have coalesced the contents of case-insensitively same-named + * directories, so do the sort now. + */ + if (frame->next_idx == 0 && !git_vector_is_sorted(&frame->entries)) + git_vector_sort(&frame->entries); + + /* we have more entries in the current frame, that's our next entry */ + prev_entry = tree_iterator_current_entry(frame); + entry = frame->entries.contents[frame->next_idx]; + frame->next_idx++; + + /* we can have collisions when iterating case insensitively. (eg, + * 'A/a' and 'a/A'). squash this one if it's already been seen. + */ + if (iterator__ignore_case(&iter->base) && + prev_entry && + tree_iterator_entry_cmp_icase(prev_entry, entry) == 0) + continue; + + if ((error = tree_iterator_compute_path(&iter->entry_path, entry)) < 0) + break; + + /* if this path is before our start, advance over this entry */ + if (!iterator_has_started(&iter->base, iter->entry_path.ptr, false)) + continue; + + /* if this path is after our end, stop */ + if (iterator_has_ended(&iter->base, iter->entry_path.ptr)) { + error = GIT_ITEROVER; + break; + } + + /* if we have a list of paths we're interested in, examine it */ + if (!iterator_pathlist_next_is(&iter->base, iter->entry_path.ptr)) + continue; + + is_tree = git_tree_entry__is_tree(entry->tree_entry); + + /* if we are *not* including trees then advance over this entry */ + if (is_tree && !iterator__include_trees(iter)) { + + /* if we've found a tree (and are not returning it to the caller) + * and we are autoexpanding, then we want to return the first + * child. push the new directory and advance. + */ + if (iterator__do_autoexpand(iter)) { + if ((error = tree_iterator_frame_push(iter, entry)) < 0) + break; + } + + continue; + } + + tree_iterator_set_current(iter, frame, entry); + + /* if we are autoexpanding, then push this as a new frame, so that + * the next call to `advance` will dive into this directory. + */ + if (is_tree && iterator__do_autoexpand(iter)) + error = tree_iterator_frame_push(iter, entry); + + break; + } + + if (out) + *out = (error == 0) ? &iter->entry : NULL; + + return error; +} + +static int tree_iterator_advance_into( + const git_index_entry **out, git_iterator *i) +{ + tree_iterator *iter = (tree_iterator *)i; + tree_iterator_frame *frame; + tree_iterator_entry *prev_entry; + int error; + + if (out) + *out = NULL; + + if ((frame = tree_iterator_current_frame(iter)) == NULL) + return GIT_ITEROVER; + + /* get the last seen entry */ + prev_entry = tree_iterator_current_entry(frame); + + /* it's legal to call advance_into when auto-expand is on. in this case, + * we will have pushed a new (empty) frame on to the stack for this + * new directory. since it's empty, its current_entry should be null. + */ + GIT_ASSERT(iterator__do_autoexpand(i) ^ (prev_entry != NULL)); + + if (prev_entry) { + if (!git_tree_entry__is_tree(prev_entry->tree_entry)) + return 0; + + if ((error = tree_iterator_frame_push(iter, prev_entry)) < 0) + return error; + } + + /* we've advanced into the directory in question, let advance + * find the first entry + */ + return tree_iterator_advance(out, i); +} + +static int tree_iterator_advance_over( + const git_index_entry **out, + git_iterator_status_t *status, + git_iterator *i) +{ + *status = GIT_ITERATOR_STATUS_NORMAL; + return git_iterator_advance(out, i); +} + +static void tree_iterator_clear(tree_iterator *iter) +{ + while (iter->frames.size) + tree_iterator_frame_pop(iter); + + git_array_clear(iter->frames); + + git_pool_clear(&iter->entry_pool); + git_str_clear(&iter->entry_path); + + iterator_clear(&iter->base); +} + +static int tree_iterator_init(tree_iterator *iter) +{ + int error; + + if ((error = git_pool_init(&iter->entry_pool, sizeof(tree_iterator_entry))) < 0 || + (error = tree_iterator_frame_init(iter, iter->root, NULL)) < 0) + return error; + + iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; + + return 0; +} + +static int tree_iterator_reset(git_iterator *i) +{ + tree_iterator *iter = (tree_iterator *)i; + + tree_iterator_clear(iter); + return tree_iterator_init(iter); +} + +static void tree_iterator_free(git_iterator *i) +{ + tree_iterator *iter = (tree_iterator *)i; + + tree_iterator_clear(iter); + + git_tree_free(iter->root); + git_str_dispose(&iter->entry_path); +} + +int git_iterator_for_tree( + git_iterator **out, + git_tree *tree, + git_iterator_options *options) +{ + tree_iterator *iter; + int error; + + static git_iterator_callbacks callbacks = { + tree_iterator_current, + tree_iterator_advance, + tree_iterator_advance_into, + tree_iterator_advance_over, + tree_iterator_reset, + tree_iterator_free + }; + + *out = NULL; + + if (tree == NULL) + return git_iterator_for_nothing(out, options); + + iter = git__calloc(1, sizeof(tree_iterator)); + GIT_ERROR_CHECK_ALLOC(iter); + + iter->base.type = GIT_ITERATOR_TREE; + iter->base.cb = &callbacks; + + if ((error = iterator_init_common(&iter->base, + git_tree_owner(tree), NULL, options)) < 0 || + (error = git_tree_dup(&iter->root, tree)) < 0 || + (error = tree_iterator_init(iter)) < 0) + goto on_error; + + *out = &iter->base; + return 0; + +on_error: + git_iterator_free(&iter->base); + return error; +} + +int git_iterator_current_tree_entry( + const git_tree_entry **tree_entry, git_iterator *i) +{ + tree_iterator *iter; + tree_iterator_frame *frame; + tree_iterator_entry *entry; + + GIT_ASSERT(i->type == GIT_ITERATOR_TREE); + + iter = (tree_iterator *)i; + + frame = tree_iterator_current_frame(iter); + entry = tree_iterator_current_entry(frame); + + *tree_entry = entry->tree_entry; + return 0; +} + +int git_iterator_current_parent_tree( + const git_tree **parent_tree, git_iterator *i, size_t depth) +{ + tree_iterator *iter; + tree_iterator_frame *frame; + + GIT_ASSERT(i->type == GIT_ITERATOR_TREE); + + iter = (tree_iterator *)i; + + GIT_ASSERT(depth < iter->frames.size); + frame = &iter->frames.ptr[iter->frames.size-depth-1]; + + *parent_tree = frame->tree; + return 0; +} + +/* Filesystem iterator */ + +typedef struct { + struct stat st; + size_t path_len; + iterator_pathlist_search_t match; + git_oid id; + char path[GIT_FLEX_ARRAY]; +} filesystem_iterator_entry; + +typedef struct { + git_vector entries; + git_pool entry_pool; + size_t next_idx; + + size_t path_len; + int is_ignored; +} filesystem_iterator_frame; + +typedef struct { + git_iterator base; + char *root; + size_t root_len; + + unsigned int dirload_flags; + + git_tree *tree; + git_index *index; + git_vector index_snapshot; + + git_array_t(filesystem_iterator_frame) frames; + git_ignores ignores; + + /* info about the current entry */ + git_index_entry entry; + git_str current_path; + int current_is_ignored; + + /* temporary buffer for advance_over */ + git_str tmp_buf; +} filesystem_iterator; + + +GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_parent_frame( + filesystem_iterator *iter) +{ + return iter->frames.size > 1 ? + &iter->frames.ptr[iter->frames.size-2] : NULL; +} + +GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_current_frame( + filesystem_iterator *iter) +{ + return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL; +} + +GIT_INLINE(filesystem_iterator_entry *) filesystem_iterator_current_entry( + filesystem_iterator_frame *frame) +{ + return frame->next_idx == 0 ? + NULL : frame->entries.contents[frame->next_idx-1]; +} + +static int filesystem_iterator_entry_cmp(const void *_a, const void *_b) +{ + const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a; + const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b; + + return git__strcmp(a->path, b->path); +} + +static int filesystem_iterator_entry_cmp_icase(const void *_a, const void *_b) +{ + const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a; + const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b; + + return git__strcasecmp(a->path, b->path); +} + +#define FILESYSTEM_MAX_DEPTH 100 + +/** + * Figure out if an entry is a submodule. + * + * We consider it a submodule if the path is listed as a submodule in + * either the tree or the index. + */ +static int filesystem_iterator_is_submodule( + bool *out, filesystem_iterator *iter, const char *path, size_t path_len) +{ + bool is_submodule = false; + int error; + + *out = false; + + /* first see if this path is a submodule in HEAD */ + if (iter->tree) { + git_tree_entry *entry; + + error = git_tree_entry_bypath(&entry, iter->tree, path); + + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + if (!error) { + is_submodule = (entry->attr == GIT_FILEMODE_COMMIT); + git_tree_entry_free(entry); + } + } + + if (!is_submodule && iter->base.index) { + size_t pos; + + error = git_index_snapshot_find(&pos, + &iter->index_snapshot, iter->base.entry_srch, path, path_len, 0); + + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + if (!error) { + git_index_entry *e = git_vector_get(&iter->index_snapshot, pos); + is_submodule = (e->mode == GIT_FILEMODE_COMMIT); + } + } + + *out = is_submodule; + return 0; +} + +static void filesystem_iterator_frame_push_ignores( + filesystem_iterator *iter, + filesystem_iterator_entry *frame_entry, + filesystem_iterator_frame *new_frame) +{ + filesystem_iterator_frame *previous_frame; + const char *path = frame_entry ? frame_entry->path : ""; + + if (!iterator__honor_ignores(&iter->base)) + return; + + if (git_ignore__lookup(&new_frame->is_ignored, + &iter->ignores, path, GIT_DIR_FLAG_TRUE) < 0) { + git_error_clear(); + new_frame->is_ignored = GIT_IGNORE_NOTFOUND; + } + + /* if this is not the top level directory... */ + if (frame_entry) { + const char *relative_path; + + previous_frame = filesystem_iterator_parent_frame(iter); + + /* push new ignores for files in this directory */ + relative_path = frame_entry->path + previous_frame->path_len; + + /* inherit ignored from parent if no rule specified */ + if (new_frame->is_ignored <= GIT_IGNORE_NOTFOUND) + new_frame->is_ignored = previous_frame->is_ignored; + + git_ignore__push_dir(&iter->ignores, relative_path); + } +} + +static void filesystem_iterator_frame_pop_ignores( + filesystem_iterator *iter) +{ + if (iterator__honor_ignores(&iter->base)) + git_ignore__pop_dir(&iter->ignores); +} + +GIT_INLINE(bool) filesystem_iterator_examine_path( + bool *is_dir_out, + iterator_pathlist_search_t *match_out, + filesystem_iterator *iter, + filesystem_iterator_entry *frame_entry, + const char *path, + size_t path_len) +{ + bool is_dir = 0; + iterator_pathlist_search_t match = ITERATOR_PATHLIST_FULL; + + *is_dir_out = false; + *match_out = ITERATOR_PATHLIST_NONE; + + if (iter->base.start_len) { + int cmp = iter->base.strncomp(path, iter->base.start, path_len); + + /* we haven't stat'ed `path` yet, so we don't yet know if it's a + * directory or not. special case if the current path may be a + * directory that matches the start prefix. + */ + if (cmp == 0) { + if (iter->base.start[path_len] == '/') + is_dir = true; + + else if (iter->base.start[path_len] != '\0') + cmp = -1; + } + + if (cmp < 0) + return false; + } + + if (iter->base.end_len) { + int cmp = iter->base.strncomp(path, iter->base.end, iter->base.end_len); + + if (cmp > 0) + return false; + } + + /* if we have a pathlist that we're limiting to, examine this path now + * to avoid a `stat` if we're not interested in the path. + */ + if (iter->base.pathlist.length) { + /* if our parent was explicitly included, so too are we */ + if (frame_entry && frame_entry->match != ITERATOR_PATHLIST_IS_PARENT) + match = ITERATOR_PATHLIST_FULL; + else + match = iterator_pathlist_search(&iter->base, path, path_len); + + if (match == ITERATOR_PATHLIST_NONE) + return false; + + /* Ensure that the pathlist entry lines up with what we expected */ + if (match == ITERATOR_PATHLIST_IS_DIR || + match == ITERATOR_PATHLIST_IS_PARENT) + is_dir = true; + } + + *is_dir_out = is_dir; + *match_out = match; + return true; +} + +GIT_INLINE(bool) filesystem_iterator_is_dot_git( + filesystem_iterator *iter, const char *path, size_t path_len) +{ + size_t len; + + if (!iterator__ignore_dot_git(&iter->base)) + return false; + + if ((len = path_len) < 4) + return false; + + if (path[len - 1] == '/') + len--; + + if (git__tolower(path[len - 1]) != 't' || + git__tolower(path[len - 2]) != 'i' || + git__tolower(path[len - 3]) != 'g' || + git__tolower(path[len - 4]) != '.') + return false; + + return (len == 4 || path[len - 5] == '/'); +} + +static int filesystem_iterator_entry_hash( + filesystem_iterator *iter, + filesystem_iterator_entry *entry) +{ + git_str fullpath = GIT_STR_INIT; + int error; + + if (S_ISDIR(entry->st.st_mode)) { + memset(&entry->id, 0, GIT_OID_RAWSZ); + return 0; + } + + if (iter->base.type == GIT_ITERATOR_WORKDIR) + return git_repository_hashfile(&entry->id, + iter->base.repo, entry->path, GIT_OBJECT_BLOB, NULL); + + if (!(error = git_str_joinpath(&fullpath, iter->root, entry->path)) && + !(error = git_path_validate_str_length(iter->base.repo, &fullpath))) + error = git_odb_hashfile(&entry->id, fullpath.ptr, GIT_OBJECT_BLOB); + + git_str_dispose(&fullpath); + return error; +} + +static int filesystem_iterator_entry_init( + filesystem_iterator_entry **out, + filesystem_iterator *iter, + filesystem_iterator_frame *frame, + const char *path, + size_t path_len, + struct stat *statbuf, + iterator_pathlist_search_t pathlist_match) +{ + filesystem_iterator_entry *entry; + size_t entry_size; + int error = 0; + + *out = NULL; + + /* Make sure to append two bytes, one for the path's null + * termination, one for a possible trailing '/' for folders. + */ + GIT_ERROR_CHECK_ALLOC_ADD(&entry_size, + sizeof(filesystem_iterator_entry), path_len); + GIT_ERROR_CHECK_ALLOC_ADD(&entry_size, entry_size, 2); + + entry = git_pool_malloc(&frame->entry_pool, entry_size); + GIT_ERROR_CHECK_ALLOC(entry); + + entry->path_len = path_len; + entry->match = pathlist_match; + memcpy(entry->path, path, path_len); + memcpy(&entry->st, statbuf, sizeof(struct stat)); + + /* Suffix directory paths with a '/' */ + if (S_ISDIR(entry->st.st_mode)) + entry->path[entry->path_len++] = '/'; + + entry->path[entry->path_len] = '\0'; + + if (iter->base.flags & GIT_ITERATOR_INCLUDE_HASH) + error = filesystem_iterator_entry_hash(iter, entry); + + if (!error) + *out = entry; + + return error; +} + +static int filesystem_iterator_frame_push( + filesystem_iterator *iter, + filesystem_iterator_entry *frame_entry) +{ + filesystem_iterator_frame *new_frame = NULL; + git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT; + git_str root = GIT_STR_INIT; + const char *path; + filesystem_iterator_entry *entry; + struct stat statbuf; + size_t path_len; + int error; + + if (iter->frames.size == FILESYSTEM_MAX_DEPTH) { + git_error_set(GIT_ERROR_REPOSITORY, + "directory nesting too deep (%"PRIuZ")", iter->frames.size); + return -1; + } + + new_frame = git_array_alloc(iter->frames); + GIT_ERROR_CHECK_ALLOC(new_frame); + + memset(new_frame, 0, sizeof(filesystem_iterator_frame)); + + if (frame_entry) + git_str_joinpath(&root, iter->root, frame_entry->path); + else + git_str_puts(&root, iter->root); + + if (git_str_oom(&root) || + git_path_validate_str_length(iter->base.repo, &root) < 0) { + error = -1; + goto done; + } + + new_frame->path_len = frame_entry ? frame_entry->path_len : 0; + + /* Any error here is equivalent to the dir not existing, skip over it */ + if ((error = git_fs_path_diriter_init( + &diriter, root.ptr, iter->dirload_flags)) < 0) { + error = GIT_ENOTFOUND; + goto done; + } + + if ((error = git_vector_init(&new_frame->entries, 64, + iterator__ignore_case(&iter->base) ? + filesystem_iterator_entry_cmp_icase : + filesystem_iterator_entry_cmp)) < 0) + goto done; + + if ((error = git_pool_init(&new_frame->entry_pool, 1)) < 0) + goto done; + + /* check if this directory is ignored */ + filesystem_iterator_frame_push_ignores(iter, frame_entry, new_frame); + + while ((error = git_fs_path_diriter_next(&diriter)) == 0) { + iterator_pathlist_search_t pathlist_match = ITERATOR_PATHLIST_FULL; + git_str path_str = GIT_STR_INIT; + bool dir_expected = false; + + if ((error = git_fs_path_diriter_fullpath(&path, &path_len, &diriter)) < 0) + goto done; + + path_str.ptr = (char *)path; + path_str.size = path_len; + + if ((error = git_path_validate_str_length(iter->base.repo, &path_str)) < 0) + goto done; + + GIT_ASSERT(path_len > iter->root_len); + + /* remove the prefix if requested */ + path += iter->root_len; + path_len -= iter->root_len; + + /* examine start / end and the pathlist to see if this path is in it. + * note that since we haven't yet stat'ed the path, we cannot know + * whether it's a directory yet or not, so this can give us an + * expected type (S_IFDIR or S_IFREG) that we should examine) + */ + if (!filesystem_iterator_examine_path(&dir_expected, &pathlist_match, + iter, frame_entry, path, path_len)) + continue; + + /* TODO: don't need to stat if assume unchanged for this path and + * we have an index, we can just copy the data out of it. + */ + + if ((error = git_fs_path_diriter_stat(&statbuf, &diriter)) < 0) { + /* file was removed between readdir and lstat */ + if (error == GIT_ENOTFOUND) + continue; + + /* treat the file as unreadable */ + memset(&statbuf, 0, sizeof(statbuf)); + statbuf.st_mode = GIT_FILEMODE_UNREADABLE; + + error = 0; + } + + iter->base.stat_calls++; + + /* Ignore wacky things in the filesystem */ + if (!S_ISDIR(statbuf.st_mode) && + !S_ISREG(statbuf.st_mode) && + !S_ISLNK(statbuf.st_mode) && + statbuf.st_mode != GIT_FILEMODE_UNREADABLE) + continue; + + if (filesystem_iterator_is_dot_git(iter, path, path_len)) + continue; + + /* convert submodules to GITLINK and remove trailing slashes */ + if (S_ISDIR(statbuf.st_mode)) { + bool submodule = false; + + if ((error = filesystem_iterator_is_submodule(&submodule, + iter, path, path_len)) < 0) + goto done; + + if (submodule) + statbuf.st_mode = GIT_FILEMODE_COMMIT; + } + + /* Ensure that the pathlist entry lines up with what we expected */ + else if (dir_expected) + continue; + + if ((error = filesystem_iterator_entry_init(&entry, + iter, new_frame, path, path_len, &statbuf, pathlist_match)) < 0) + goto done; + + git_vector_insert(&new_frame->entries, entry); + } + + if (error == GIT_ITEROVER) + error = 0; + + /* sort now that directory suffix is added */ + git_vector_sort(&new_frame->entries); + +done: + if (error < 0) + git_array_pop(iter->frames); + + git_str_dispose(&root); + git_fs_path_diriter_free(&diriter); + return error; +} + +GIT_INLINE(int) filesystem_iterator_frame_pop(filesystem_iterator *iter) +{ + filesystem_iterator_frame *frame; + + GIT_ASSERT(iter->frames.size); + + frame = git_array_pop(iter->frames); + filesystem_iterator_frame_pop_ignores(iter); + + git_pool_clear(&frame->entry_pool); + git_vector_free(&frame->entries); + + return 0; +} + +static void filesystem_iterator_set_current( + filesystem_iterator *iter, + filesystem_iterator_entry *entry) +{ + /* + * Index entries are limited to 32 bit timestamps. We can safely + * cast this since workdir times are only used in the cache; any + * mismatch will cause a hash recomputation which is unfortunate + * but affects only people who set their filetimes to 2038. + * (Same with the file size.) + */ + iter->entry.ctime.seconds = (int32_t)entry->st.st_ctime; + iter->entry.mtime.seconds = (int32_t)entry->st.st_mtime; + +#if defined(GIT_USE_NSEC) + iter->entry.ctime.nanoseconds = entry->st.st_ctime_nsec; + iter->entry.mtime.nanoseconds = entry->st.st_mtime_nsec; +#else + iter->entry.ctime.nanoseconds = 0; + iter->entry.mtime.nanoseconds = 0; +#endif + + iter->entry.dev = entry->st.st_dev; + iter->entry.ino = entry->st.st_ino; + iter->entry.mode = git_futils_canonical_mode(entry->st.st_mode); + iter->entry.uid = entry->st.st_uid; + iter->entry.gid = entry->st.st_gid; + iter->entry.file_size = (uint32_t)entry->st.st_size; + + if (iter->base.flags & GIT_ITERATOR_INCLUDE_HASH) + git_oid_cpy(&iter->entry.id, &entry->id); + + iter->entry.path = entry->path; + + iter->current_is_ignored = GIT_IGNORE_UNCHECKED; +} + +static int filesystem_iterator_current( + const git_index_entry **out, git_iterator *i) +{ + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + + if (!iterator__has_been_accessed(i)) + return iter->base.cb->advance(out, i); + + if (!iter->frames.size) { + *out = NULL; + return GIT_ITEROVER; + } + + *out = &iter->entry; + return 0; +} + +static int filesystem_iterator_is_dir( + bool *is_dir, + const filesystem_iterator *iter, + const filesystem_iterator_entry *entry) +{ + struct stat st; + git_str fullpath = GIT_STR_INIT; + int error = 0; + + if (S_ISDIR(entry->st.st_mode)) { + *is_dir = 1; + goto done; + } + + if (!iterator__descend_symlinks(iter) || !S_ISLNK(entry->st.st_mode)) { + *is_dir = 0; + goto done; + } + + if ((error = git_str_joinpath(&fullpath, iter->root, entry->path)) < 0 || + (error = git_path_validate_str_length(iter->base.repo, &fullpath)) < 0 || + (error = p_stat(fullpath.ptr, &st)) < 0) + goto done; + + *is_dir = S_ISDIR(st.st_mode); + +done: + git_str_dispose(&fullpath); + return error; +} + +static int filesystem_iterator_advance( + const git_index_entry **out, git_iterator *i) +{ + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + bool is_dir; + int error = 0; + + iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; + + /* examine filesystem entries until we find the next one to return */ + while (true) { + filesystem_iterator_frame *frame; + filesystem_iterator_entry *entry; + + if ((frame = filesystem_iterator_current_frame(iter)) == NULL) { + error = GIT_ITEROVER; + break; + } + + /* no more entries in this frame. pop the frame out */ + if (frame->next_idx == frame->entries.length) { + filesystem_iterator_frame_pop(iter); + continue; + } + + /* we have more entries in the current frame, that's our next entry */ + entry = frame->entries.contents[frame->next_idx]; + frame->next_idx++; + + if ((error = filesystem_iterator_is_dir(&is_dir, iter, entry)) < 0) + break; + + if (is_dir) { + if (iterator__do_autoexpand(iter)) { + error = filesystem_iterator_frame_push(iter, entry); + + /* may get GIT_ENOTFOUND due to races or permission problems + * that we want to quietly swallow + */ + if (error == GIT_ENOTFOUND) + continue; + else if (error < 0) + break; + } + + if (!iterator__include_trees(iter)) + continue; + } + + filesystem_iterator_set_current(iter, entry); + break; + } + + if (out) + *out = (error == 0) ? &iter->entry : NULL; + + return error; +} + +static int filesystem_iterator_advance_into( + const git_index_entry **out, git_iterator *i) +{ + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + filesystem_iterator_frame *frame; + filesystem_iterator_entry *prev_entry; + int error; + + if (out) + *out = NULL; + + if ((frame = filesystem_iterator_current_frame(iter)) == NULL) + return GIT_ITEROVER; + + /* get the last seen entry */ + prev_entry = filesystem_iterator_current_entry(frame); + + /* it's legal to call advance_into when auto-expand is on. in this case, + * we will have pushed a new (empty) frame on to the stack for this + * new directory. since it's empty, its current_entry should be null. + */ + GIT_ASSERT(iterator__do_autoexpand(i) ^ (prev_entry != NULL)); + + if (prev_entry) { + if (prev_entry->st.st_mode != GIT_FILEMODE_COMMIT && + !S_ISDIR(prev_entry->st.st_mode)) + return 0; + + if ((error = filesystem_iterator_frame_push(iter, prev_entry)) < 0) + return error; + } + + /* we've advanced into the directory in question, let advance + * find the first entry + */ + return filesystem_iterator_advance(out, i); +} + +int git_iterator_current_workdir_path(git_str **out, git_iterator *i) +{ + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + const git_index_entry *entry; + + if (i->type != GIT_ITERATOR_FS && + i->type != GIT_ITERATOR_WORKDIR) { + *out = NULL; + return 0; + } + + git_str_truncate(&iter->current_path, iter->root_len); + + if (git_iterator_current(&entry, i) < 0 || + git_str_puts(&iter->current_path, entry->path) < 0) + return -1; + + *out = &iter->current_path; + return 0; +} + +GIT_INLINE(git_dir_flag) entry_dir_flag(git_index_entry *entry) +{ +#if defined(GIT_WIN32) && !defined(__MINGW32__) + return (entry && entry->mode) ? + (S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE) : + GIT_DIR_FLAG_UNKNOWN; +#else + GIT_UNUSED(entry); + return GIT_DIR_FLAG_UNKNOWN; +#endif +} + +static void filesystem_iterator_update_ignored(filesystem_iterator *iter) +{ + filesystem_iterator_frame *frame; + git_dir_flag dir_flag = entry_dir_flag(&iter->entry); + + if (git_ignore__lookup(&iter->current_is_ignored, + &iter->ignores, iter->entry.path, dir_flag) < 0) { + git_error_clear(); + iter->current_is_ignored = GIT_IGNORE_NOTFOUND; + } + + /* use ignore from containing frame stack */ + if (iter->current_is_ignored <= GIT_IGNORE_NOTFOUND) { + frame = filesystem_iterator_current_frame(iter); + iter->current_is_ignored = frame->is_ignored; + } +} + +GIT_INLINE(bool) filesystem_iterator_current_is_ignored( + filesystem_iterator *iter) +{ + if (iter->current_is_ignored == GIT_IGNORE_UNCHECKED) + filesystem_iterator_update_ignored(iter); + + return (iter->current_is_ignored == GIT_IGNORE_TRUE); +} + +bool git_iterator_current_is_ignored(git_iterator *i) +{ + filesystem_iterator *iter = NULL; + + if (i->type != GIT_ITERATOR_WORKDIR) + return false; + + iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + + return filesystem_iterator_current_is_ignored(iter); +} + +bool git_iterator_current_tree_is_ignored(git_iterator *i) +{ + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + filesystem_iterator_frame *frame; + + if (i->type != GIT_ITERATOR_WORKDIR) + return false; + + frame = filesystem_iterator_current_frame(iter); + return (frame->is_ignored == GIT_IGNORE_TRUE); +} + +static int filesystem_iterator_advance_over( + const git_index_entry **out, + git_iterator_status_t *status, + git_iterator *i) +{ + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + filesystem_iterator_frame *current_frame; + filesystem_iterator_entry *current_entry; + const git_index_entry *entry = NULL; + const char *base; + int error = 0; + + *out = NULL; + *status = GIT_ITERATOR_STATUS_NORMAL; + + GIT_ASSERT(iterator__has_been_accessed(i)); + + current_frame = filesystem_iterator_current_frame(iter); + GIT_ASSERT(current_frame); + + current_entry = filesystem_iterator_current_entry(current_frame); + GIT_ASSERT(current_entry); + + if ((error = git_iterator_current(&entry, i)) < 0) + return error; + + if (!S_ISDIR(entry->mode)) { + if (filesystem_iterator_current_is_ignored(iter)) + *status = GIT_ITERATOR_STATUS_IGNORED; + + return filesystem_iterator_advance(out, i); + } + + git_str_clear(&iter->tmp_buf); + if ((error = git_str_puts(&iter->tmp_buf, entry->path)) < 0) + return error; + + base = iter->tmp_buf.ptr; + + /* scan inside the directory looking for files. if we find nothing, + * we will remain EMPTY. if we find any ignored item, upgrade EMPTY to + * IGNORED. if we find a real actual item, upgrade all the way to NORMAL + * and then stop. + * + * however, if we're here looking for a pathlist item (but are not + * actually in the pathlist ourselves) then start at FILTERED instead of + * EMPTY. callers then know that this path was not something they asked + * about. + */ + *status = current_entry->match == ITERATOR_PATHLIST_IS_PARENT ? + GIT_ITERATOR_STATUS_FILTERED : GIT_ITERATOR_STATUS_EMPTY; + + while (entry && !iter->base.prefixcomp(entry->path, base)) { + if (filesystem_iterator_current_is_ignored(iter)) { + /* if we found an explicitly ignored item, then update from + * EMPTY to IGNORED + */ + *status = GIT_ITERATOR_STATUS_IGNORED; + } else if (S_ISDIR(entry->mode)) { + error = filesystem_iterator_advance_into(&entry, i); + + if (!error) + continue; + + /* this directory disappeared, ignore it */ + else if (error == GIT_ENOTFOUND) + error = 0; + + /* a real error occurred */ + else + break; + } else { + /* we found a non-ignored item, treat parent as untracked */ + *status = GIT_ITERATOR_STATUS_NORMAL; + break; + } + + if ((error = git_iterator_advance(&entry, i)) < 0) + break; + } + + /* wrap up scan back to base directory */ + while (entry && !iter->base.prefixcomp(entry->path, base)) { + if ((error = git_iterator_advance(&entry, i)) < 0) + break; + } + + if (!error) + *out = entry; + + return error; +} + +static void filesystem_iterator_clear(filesystem_iterator *iter) +{ + while (iter->frames.size) + filesystem_iterator_frame_pop(iter); + + git_array_clear(iter->frames); + git_ignore__free(&iter->ignores); + + git_str_dispose(&iter->tmp_buf); + + iterator_clear(&iter->base); +} + +static int filesystem_iterator_init(filesystem_iterator *iter) +{ + int error; + + if (iterator__honor_ignores(&iter->base) && + (error = git_ignore__for_path(iter->base.repo, + ".gitignore", &iter->ignores)) < 0) + return error; + + if ((error = filesystem_iterator_frame_push(iter, NULL)) < 0) + return error; + + iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; + + return 0; +} + +static int filesystem_iterator_reset(git_iterator *i) +{ + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + + filesystem_iterator_clear(iter); + return filesystem_iterator_init(iter); +} + +static void filesystem_iterator_free(git_iterator *i) +{ + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + git__free(iter->root); + git_str_dispose(&iter->current_path); + git_tree_free(iter->tree); + if (iter->index) + git_index_snapshot_release(&iter->index_snapshot, iter->index); + filesystem_iterator_clear(iter); +} + +static int iterator_for_filesystem( + git_iterator **out, + git_repository *repo, + const char *root, + git_index *index, + git_tree *tree, + git_iterator_t type, + git_iterator_options *options) +{ + filesystem_iterator *iter; + size_t root_len; + int error; + + static git_iterator_callbacks callbacks = { + filesystem_iterator_current, + filesystem_iterator_advance, + filesystem_iterator_advance_into, + filesystem_iterator_advance_over, + filesystem_iterator_reset, + filesystem_iterator_free + }; + + *out = NULL; + + if (root == NULL) + return git_iterator_for_nothing(out, options); + + iter = git__calloc(1, sizeof(filesystem_iterator)); + GIT_ERROR_CHECK_ALLOC(iter); + + iter->base.type = type; + iter->base.cb = &callbacks; + + root_len = strlen(root); + + iter->root = git__malloc(root_len+2); + GIT_ERROR_CHECK_ALLOC(iter->root); + + memcpy(iter->root, root, root_len); + + if (root_len == 0 || root[root_len-1] != '/') { + iter->root[root_len] = '/'; + root_len++; + } + iter->root[root_len] = '\0'; + iter->root_len = root_len; + + if ((error = git_str_puts(&iter->current_path, iter->root)) < 0) + goto on_error; + + if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0) + goto on_error; + + if (tree && (error = git_tree_dup(&iter->tree, tree)) < 0) + goto on_error; + + if (index && + (error = git_index_snapshot_new(&iter->index_snapshot, index)) < 0) + goto on_error; + + iter->index = index; + iter->dirload_flags = + (iterator__ignore_case(&iter->base) ? + GIT_FS_PATH_DIR_IGNORE_CASE : 0) | + (iterator__flag(&iter->base, PRECOMPOSE_UNICODE) ? + GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE : 0); + + if ((error = filesystem_iterator_init(iter)) < 0) + goto on_error; + + *out = &iter->base; + return 0; + +on_error: + git_iterator_free(&iter->base); + return error; +} + +int git_iterator_for_filesystem( + git_iterator **out, + const char *root, + git_iterator_options *options) +{ + return iterator_for_filesystem(out, + NULL, root, NULL, NULL, GIT_ITERATOR_FS, options); +} + +int git_iterator_for_workdir_ext( + git_iterator **out, + git_repository *repo, + const char *repo_workdir, + git_index *index, + git_tree *tree, + git_iterator_options *given_opts) +{ + git_iterator_options options = GIT_ITERATOR_OPTIONS_INIT; + + if (!repo_workdir) { + if (git_repository__ensure_not_bare(repo, "scan working directory") < 0) + return GIT_EBAREREPO; + + repo_workdir = git_repository_workdir(repo); + } + + /* upgrade to a workdir iterator, adding necessary internal flags */ + if (given_opts) + memcpy(&options, given_opts, sizeof(git_iterator_options)); + + options.flags |= GIT_ITERATOR_HONOR_IGNORES | + GIT_ITERATOR_IGNORE_DOT_GIT; + + return iterator_for_filesystem(out, + repo, repo_workdir, index, tree, GIT_ITERATOR_WORKDIR, &options); +} + + +/* Index iterator */ + + +typedef struct { + git_iterator base; + git_vector entries; + size_t next_idx; + + /* the pseudotree entry */ + git_index_entry tree_entry; + git_str tree_buf; + bool skip_tree; + + const git_index_entry *entry; +} index_iterator; + +static int index_iterator_current( + const git_index_entry **out, git_iterator *i) +{ + index_iterator *iter = (index_iterator *)i; + + if (!iterator__has_been_accessed(i)) + return iter->base.cb->advance(out, i); + + if (iter->entry == NULL) { + *out = NULL; + return GIT_ITEROVER; + } + + *out = iter->entry; + return 0; +} + +static bool index_iterator_create_pseudotree( + const git_index_entry **out, + index_iterator *iter, + const char *path) +{ + const char *prev_path, *relative_path, *dirsep; + size_t common_len; + + prev_path = iter->entry ? iter->entry->path : ""; + + /* determine if the new path is in a different directory from the old */ + common_len = git_fs_path_common_dirlen(prev_path, path); + relative_path = path + common_len; + + if ((dirsep = strchr(relative_path, '/')) == NULL) + return false; + + git_str_clear(&iter->tree_buf); + git_str_put(&iter->tree_buf, path, (dirsep - path) + 1); + + iter->tree_entry.mode = GIT_FILEMODE_TREE; + iter->tree_entry.path = iter->tree_buf.ptr; + + *out = &iter->tree_entry; + return true; +} + +static int index_iterator_skip_pseudotree(index_iterator *iter) +{ + GIT_ASSERT(iterator__has_been_accessed(&iter->base)); + GIT_ASSERT(S_ISDIR(iter->entry->mode)); + + while (true) { + const git_index_entry *next_entry = NULL; + + if (++iter->next_idx >= iter->entries.length) + return GIT_ITEROVER; + + next_entry = iter->entries.contents[iter->next_idx]; + + if (iter->base.strncomp(iter->tree_buf.ptr, next_entry->path, + iter->tree_buf.size) != 0) + break; + } + + iter->skip_tree = false; + return 0; +} + +static int index_iterator_advance( + const git_index_entry **out, git_iterator *i) +{ + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); + const git_index_entry *entry = NULL; + bool is_submodule; + int error = 0; + + iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; + + while (true) { + if (iter->next_idx >= iter->entries.length) { + error = GIT_ITEROVER; + break; + } + + /* we were not asked to expand this pseudotree. advance over it. */ + if (iter->skip_tree) { + index_iterator_skip_pseudotree(iter); + continue; + } + + entry = iter->entries.contents[iter->next_idx]; + is_submodule = S_ISGITLINK(entry->mode); + + if (!iterator_has_started(&iter->base, entry->path, is_submodule)) { + iter->next_idx++; + continue; + } + + if (iterator_has_ended(&iter->base, entry->path)) { + error = GIT_ITEROVER; + break; + } + + /* if we have a list of paths we're interested in, examine it */ + if (!iterator_pathlist_next_is(&iter->base, entry->path)) { + iter->next_idx++; + continue; + } + + /* if this is a conflict, skip it unless we're including conflicts */ + if (git_index_entry_is_conflict(entry) && + !iterator__include_conflicts(&iter->base)) { + iter->next_idx++; + continue; + } + + /* we've found what will be our next _file_ entry. but if we are + * returning trees entries, we may need to return a pseudotree + * entry that will contain this. don't advance over this entry, + * though, we still need to return it on the next `advance`. + */ + if (iterator__include_trees(&iter->base) && + index_iterator_create_pseudotree(&entry, iter, entry->path)) { + + /* Note whether this pseudo tree should be expanded or not */ + iter->skip_tree = iterator__dont_autoexpand(&iter->base); + break; + } + + iter->next_idx++; + break; + } + + iter->entry = (error == 0) ? entry : NULL; + + if (out) + *out = iter->entry; + + return error; +} + +static int index_iterator_advance_into( + const git_index_entry **out, git_iterator *i) +{ + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); + + if (! S_ISDIR(iter->tree_entry.mode)) { + if (out) + *out = NULL; + + return 0; + } + + iter->skip_tree = false; + return index_iterator_advance(out, i); +} + +static int index_iterator_advance_over( + const git_index_entry **out, + git_iterator_status_t *status, + git_iterator *i) +{ + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); + const git_index_entry *entry; + int error; + + if ((error = index_iterator_current(&entry, i)) < 0) + return error; + + if (S_ISDIR(entry->mode)) + index_iterator_skip_pseudotree(iter); + + *status = GIT_ITERATOR_STATUS_NORMAL; + return index_iterator_advance(out, i); +} + +static void index_iterator_clear(index_iterator *iter) +{ + iterator_clear(&iter->base); +} + +static int index_iterator_init(index_iterator *iter) +{ + iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; + iter->next_idx = 0; + iter->skip_tree = false; + return 0; +} + +static int index_iterator_reset(git_iterator *i) +{ + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); + + index_iterator_clear(iter); + return index_iterator_init(iter); +} + +static void index_iterator_free(git_iterator *i) +{ + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); + + git_index_snapshot_release(&iter->entries, iter->base.index); + git_str_dispose(&iter->tree_buf); +} + +int git_iterator_for_index( + git_iterator **out, + git_repository *repo, + git_index *index, + git_iterator_options *options) +{ + index_iterator *iter; + int error; + + static git_iterator_callbacks callbacks = { + index_iterator_current, + index_iterator_advance, + index_iterator_advance_into, + index_iterator_advance_over, + index_iterator_reset, + index_iterator_free + }; + + *out = NULL; + + if (index == NULL) + return git_iterator_for_nothing(out, options); + + iter = git__calloc(1, sizeof(index_iterator)); + GIT_ERROR_CHECK_ALLOC(iter); + + iter->base.type = GIT_ITERATOR_INDEX; + iter->base.cb = &callbacks; + + if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0 || + (error = git_index_snapshot_new(&iter->entries, index)) < 0 || + (error = index_iterator_init(iter)) < 0) + goto on_error; + + git_vector_set_cmp(&iter->entries, iterator__ignore_case(&iter->base) ? + git_index_entry_icmp : git_index_entry_cmp); + git_vector_sort(&iter->entries); + + *out = &iter->base; + return 0; + +on_error: + git_iterator_free(&iter->base); + return error; +} + + +/* Iterator API */ + +int git_iterator_reset_range( + git_iterator *i, const char *start, const char *end) +{ + if (iterator_reset_range(i, start, end) < 0) + return -1; + + return i->cb->reset(i); +} + +int git_iterator_set_ignore_case(git_iterator *i, bool ignore_case) +{ + GIT_ASSERT(!iterator__has_been_accessed(i)); + iterator_set_ignore_case(i, ignore_case); + return 0; +} + +void git_iterator_free(git_iterator *iter) +{ + if (iter == NULL) + return; + + iter->cb->free(iter); + + git_vector_free(&iter->pathlist); + git__free(iter->start); + git__free(iter->end); + + memset(iter, 0, sizeof(*iter)); + + git__free(iter); +} + +int git_iterator_foreach( + git_iterator *iterator, + git_iterator_foreach_cb cb, + void *data) +{ + const git_index_entry *iterator_item; + int error = 0; + + if ((error = git_iterator_current(&iterator_item, iterator)) < 0) + goto done; + + if ((error = cb(iterator_item, data)) != 0) + goto done; + + while (true) { + if ((error = git_iterator_advance(&iterator_item, iterator)) < 0) + goto done; + + if ((error = cb(iterator_item, data)) != 0) + goto done; + } + +done: + if (error == GIT_ITEROVER) + error = 0; + + return error; +} + +int git_iterator_walk( + git_iterator **iterators, + size_t cnt, + git_iterator_walk_cb cb, + void *data) +{ + const git_index_entry **iterator_item; /* next in each iterator */ + const git_index_entry **cur_items; /* current path in each iter */ + const git_index_entry *first_match; + size_t i, j; + int error = 0; + + iterator_item = git__calloc(cnt, sizeof(git_index_entry *)); + cur_items = git__calloc(cnt, sizeof(git_index_entry *)); + + GIT_ERROR_CHECK_ALLOC(iterator_item); + GIT_ERROR_CHECK_ALLOC(cur_items); + + /* Set up the iterators */ + for (i = 0; i < cnt; i++) { + error = git_iterator_current(&iterator_item[i], iterators[i]); + + if (error < 0 && error != GIT_ITEROVER) + goto done; + } + + while (true) { + for (i = 0; i < cnt; i++) + cur_items[i] = NULL; + + first_match = NULL; + + /* Find the next path(s) to consume from each iterator */ + for (i = 0; i < cnt; i++) { + if (iterator_item[i] == NULL) + continue; + + if (first_match == NULL) { + first_match = iterator_item[i]; + cur_items[i] = iterator_item[i]; + } else { + int path_diff = git_index_entry_cmp(iterator_item[i], first_match); + + if (path_diff < 0) { + /* Found an index entry that sorts before the one we're + * looking at. Forget that we've seen the other and + * look at the other iterators for this path. + */ + for (j = 0; j < i; j++) + cur_items[j] = NULL; + + first_match = iterator_item[i]; + cur_items[i] = iterator_item[i]; + } else if (path_diff == 0) { + cur_items[i] = iterator_item[i]; + } + } + } + + if (first_match == NULL) + break; + + if ((error = cb(cur_items, data)) != 0) + goto done; + + /* Advance each iterator that participated */ + for (i = 0; i < cnt; i++) { + if (cur_items[i] == NULL) + continue; + + error = git_iterator_advance(&iterator_item[i], iterators[i]); + + if (error < 0 && error != GIT_ITEROVER) + goto done; + } + } + +done: + git__free((git_index_entry **)iterator_item); + git__free((git_index_entry **)cur_items); + + if (error == GIT_ITEROVER) + error = 0; + + return error; +} diff --git a/src/libgit2/iterator.h b/src/libgit2/iterator.h new file mode 100644 index 000000000..6bb8489d0 --- /dev/null +++ b/src/libgit2/iterator.h @@ -0,0 +1,322 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_iterator_h__ +#define INCLUDE_iterator_h__ + +#include "common.h" + +#include "git2/index.h" +#include "vector.h" +#include "str.h" +#include "ignore.h" + +typedef struct git_iterator git_iterator; + +typedef enum { + GIT_ITERATOR_EMPTY = 0, + GIT_ITERATOR_TREE = 1, + GIT_ITERATOR_INDEX = 2, + GIT_ITERATOR_WORKDIR = 3, + GIT_ITERATOR_FS = 4 +} git_iterator_t; + +typedef enum { + /** ignore case for entry sort order */ + GIT_ITERATOR_IGNORE_CASE = (1u << 0), + /** force case sensitivity for entry sort order */ + GIT_ITERATOR_DONT_IGNORE_CASE = (1u << 1), + /** return tree items in addition to blob items */ + GIT_ITERATOR_INCLUDE_TREES = (1u << 2), + /** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */ + GIT_ITERATOR_DONT_AUTOEXPAND = (1u << 3), + /** convert precomposed unicode to decomposed unicode */ + GIT_ITERATOR_PRECOMPOSE_UNICODE = (1u << 4), + /** never convert precomposed unicode to decomposed unicode */ + GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE = (1u << 5), + /** include conflicts */ + GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 6), + /** descend into symlinked directories */ + GIT_ITERATOR_DESCEND_SYMLINKS = (1u << 7), + /** hash files in workdir or filesystem iterators */ + GIT_ITERATOR_INCLUDE_HASH = (1u << 8) +} git_iterator_flag_t; + +typedef enum { + GIT_ITERATOR_STATUS_NORMAL = 0, + GIT_ITERATOR_STATUS_IGNORED = 1, + GIT_ITERATOR_STATUS_EMPTY = 2, + GIT_ITERATOR_STATUS_FILTERED = 3 +} git_iterator_status_t; + +typedef struct { + const char *start; + const char *end; + + /* paths to include in the iterator (literal). if set, any paths not + * listed here will be excluded from iteration. + */ + git_strarray pathlist; + + /* flags, from above */ + unsigned int flags; +} git_iterator_options; + +#define GIT_ITERATOR_OPTIONS_INIT {0} + +typedef struct { + int (*current)(const git_index_entry **, git_iterator *); + int (*advance)(const git_index_entry **, git_iterator *); + int (*advance_into)(const git_index_entry **, git_iterator *); + int (*advance_over)( + const git_index_entry **, git_iterator_status_t *, git_iterator *); + int (*reset)(git_iterator *); + void (*free)(git_iterator *); +} git_iterator_callbacks; + +struct git_iterator { + git_iterator_t type; + git_iterator_callbacks *cb; + + git_repository *repo; + git_index *index; + + char *start; + size_t start_len; + + char *end; + size_t end_len; + + bool started; + bool ended; + git_vector pathlist; + size_t pathlist_walk_idx; + int (*strcomp)(const char *a, const char *b); + int (*strncomp)(const char *a, const char *b, size_t n); + int (*prefixcomp)(const char *str, const char *prefix); + int (*entry_srch)(const void *key, const void *array_member); + size_t stat_calls; + unsigned int flags; +}; + +extern int git_iterator_for_nothing( + git_iterator **out, + git_iterator_options *options); + +/* tree iterators will match the ignore_case value from the index of the + * repository, unless you override with a non-zero flag value + */ +extern int git_iterator_for_tree( + git_iterator **out, + git_tree *tree, + git_iterator_options *options); + +/* index iterators will take the ignore_case value from the index; the + * ignore_case flags are not used + */ +extern int git_iterator_for_index( + git_iterator **out, + git_repository *repo, + git_index *index, + git_iterator_options *options); + +extern int git_iterator_for_workdir_ext( + git_iterator **out, + git_repository *repo, + const char *repo_workdir, + git_index *index, + git_tree *tree, + git_iterator_options *options); + +/* workdir iterators will match the ignore_case value from the index of the + * repository, unless you override with a non-zero flag value + */ +GIT_INLINE(int) git_iterator_for_workdir( + git_iterator **out, + git_repository *repo, + git_index *index, + git_tree *tree, + git_iterator_options *options) +{ + return git_iterator_for_workdir_ext(out, repo, NULL, index, tree, options); +} + +/* for filesystem iterators, you have to explicitly pass in the ignore_case + * behavior that you desire + */ +extern int git_iterator_for_filesystem( + git_iterator **out, + const char *root, + git_iterator_options *options); + +extern void git_iterator_free(git_iterator *iter); + +/* Return a git_index_entry structure for the current value the iterator + * is looking at or NULL if the iterator is at the end. + * + * The entry may noy be fully populated. Tree iterators will only have a + * value mode, OID, and path. Workdir iterators will not have an OID (but + * you can use `git_iterator_current_oid()` to calculate it on demand). + * + * You do not need to free the entry. It is still "owned" by the iterator. + * Once you call `git_iterator_advance()` then the old entry is no longer + * guaranteed to be valid - it may be freed or just overwritten in place. + */ +GIT_INLINE(int) git_iterator_current( + const git_index_entry **entry, git_iterator *iter) +{ + return iter->cb->current(entry, iter); +} + +/** + * Advance to the next item for the iterator. + * + * If GIT_ITERATOR_INCLUDE_TREES is set, this may be a tree item. If + * GIT_ITERATOR_DONT_AUTOEXPAND is set, calling this again when on a tree + * item will skip over all the items under that tree. + */ +GIT_INLINE(int) git_iterator_advance( + const git_index_entry **entry, git_iterator *iter) +{ + return iter->cb->advance(entry, iter); +} + +/** + * Iterate into a tree item (when GIT_ITERATOR_DONT_AUTOEXPAND is set). + * + * git_iterator_advance() steps through all items being iterated over + * (either with or without trees, depending on GIT_ITERATOR_INCLUDE_TREES), + * but if GIT_ITERATOR_DONT_AUTOEXPAND is set, it will skip to the next + * sibling of a tree instead of going to the first child of the tree. In + * that case, use this function to advance to the first child of the tree. + * + * If the current item is not a tree, this is a no-op. + * + * For filesystem and working directory iterators, a tree (i.e. directory) + * can be empty. In that case, this function returns GIT_ENOTFOUND and + * does not advance. That can't happen for tree and index iterators. + */ +GIT_INLINE(int) git_iterator_advance_into( + const git_index_entry **entry, git_iterator *iter) +{ + return iter->cb->advance_into(entry, iter); +} + +/* Advance over a directory and check if it contains no files or just + * ignored files. + * + * In a tree or the index, all directories will contain files, but in the + * working directory it is possible to have an empty directory tree or a + * tree that only contains ignored files. Many Git operations treat these + * cases specially. This advances over a directory (presumably an + * untracked directory) but checks during the scan if there are any files + * and any non-ignored files. + */ +GIT_INLINE(int) git_iterator_advance_over( + const git_index_entry **entry, + git_iterator_status_t *status, + git_iterator *iter) +{ + return iter->cb->advance_over(entry, status, iter); +} + +/** + * Go back to the start of the iteration. + */ +GIT_INLINE(int) git_iterator_reset(git_iterator *iter) +{ + return iter->cb->reset(iter); +} + +/** + * Go back to the start of the iteration after updating the `start` and + * `end` pathname boundaries of the iteration. + */ +extern int git_iterator_reset_range( + git_iterator *iter, const char *start, const char *end); + +GIT_INLINE(git_iterator_t) git_iterator_type(git_iterator *iter) +{ + return iter->type; +} + +GIT_INLINE(git_repository *) git_iterator_owner(git_iterator *iter) +{ + return iter->repo; +} + +GIT_INLINE(git_index *) git_iterator_index(git_iterator *iter) +{ + return iter->index; +} + +GIT_INLINE(git_iterator_flag_t) git_iterator_flags(git_iterator *iter) +{ + return iter->flags; +} + +GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter) +{ + return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0); +} + +extern int git_iterator_set_ignore_case( + git_iterator *iter, bool ignore_case); + +extern int git_iterator_current_tree_entry( + const git_tree_entry **entry_out, git_iterator *iter); + +extern int git_iterator_current_parent_tree( + const git_tree **tree_out, git_iterator *iter, size_t depth); + +extern bool git_iterator_current_is_ignored(git_iterator *iter); + +extern bool git_iterator_current_tree_is_ignored(git_iterator *iter); + +/** + * Get full path of the current item from a workdir iterator. This will + * return NULL for a non-workdir iterator. The git_str is still owned by + * the iterator; this is exposed just for efficiency. + */ +extern int git_iterator_current_workdir_path( + git_str **path, git_iterator *iter); + +/** + * Retrieve the index stored in the iterator. + * + * Only implemented for the workdir and index iterators. + */ +extern git_index *git_iterator_index(git_iterator *iter); + +typedef int (*git_iterator_foreach_cb)( + const git_index_entry *entry, + void *data); + +/** + * Walk the given iterator and invoke the callback for each path + * contained in the iterator. + */ +extern int git_iterator_foreach( + git_iterator *iterator, + git_iterator_foreach_cb cb, + void *data); + +typedef int (*git_iterator_walk_cb)( + const git_index_entry **entries, + void *data); + +/** + * Walk the given iterators in lock-step. The given callback will be + * called for each unique path, with the index entry in each iterator + * (or NULL if the given iterator does not contain that path). + */ +extern int git_iterator_walk( + git_iterator **iterators, + size_t cnt, + git_iterator_walk_cb cb, + void *data); + +#endif diff --git a/src/libgit2/khash.h b/src/libgit2/khash.h new file mode 100644 index 000000000..c9b7f131f --- /dev/null +++ b/src/libgit2/khash.h @@ -0,0 +1,615 @@ +/* The MIT License + + Copyright (c) 2008, 2009, 2011 by Attractive Chaos + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +/* + An example: + +#include "khash.h" +KHASH_MAP_INIT_INT(32, char) +int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; +} +*/ + +/* + 2013-05-02 (0.2.8): + + * Use quadratic probing. When the capacity is power of 2, stepping function + i*(i+1)/2 guarantees to traverse each bucket. It is better than double + hashing on cache performance and is more robust than linear probing. + + In theory, double hashing should be more robust than quadratic probing. + However, my implementation is probably not for large hash tables, because + the second hash function is closely tied to the first hash function, + which reduce the effectiveness of double hashing. + + Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php + + 2011-12-29 (0.2.7): + + * Minor code clean up; no actual effect. + + 2011-09-16 (0.2.6): + + * The capacity is a power of 2. This seems to dramatically improve the + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: + + - http://code.google.com/p/ulib/ + - http://nothings.org/computer/judy/ + + * Allow to optionally use linear probing which usually has better + performance for random input. Double hashing is still the default as it + is more robust to certain non-random input. + + * Added Wang's integer hash function (not used by default). This hash + function is more robust to certain non-random input. + + 2011-02-14 (0.2.5): + + * Allow to declare global functions. + + 2009-09-26 (0.2.4): + + * Improve portability + + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor +*/ + + +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +/*! + @header + + Generic hash table library. + */ + +#define AC_VERSION_KHASH_H "0.2.8" + +#include +#include +#include + +/* compiler specific configuration */ + +typedef uint32_t khint32_t; +typedef uint64_t khint64_t; + +#ifndef kh_inline +#ifdef _MSC_VER +#define kh_inline __inline +#elif defined(__GNUC__) +#define kh_inline __inline__ +#else +#define kh_inline +#endif +#endif /* kh_inline */ + +typedef khint32_t khint_t; +typedef khint_t khiter_t; + +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) + +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) + +#ifndef kroundup32 +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) +#endif + +#ifndef kcalloc +#define kcalloc(N,Z) calloc(N,Z) +#endif +#ifndef kmalloc +#define kmalloc(Z) malloc(Z) +#endif +#ifndef krealloc +#define krealloc(P,Z) realloc(P,Z) +#endif +#ifndef kreallocarray +#define kreallocarray(P,N,Z) ((SIZE_MAX - N < Z) ? NULL : krealloc(P, (N*Z))) +#endif +#ifndef kfree +#define kfree(P) free(P) +#endif + +static const double __ac_HASH_UPPER = 0.77; + +#define __KHASH_TYPE(name, khkey_t, khval_t) \ + typedef struct kh_##name##_s { \ + khint_t n_buckets, size, n_occupied, upper_bound; \ + khint32_t *flags; \ + khkey_t *keys; \ + khval_t *vals; \ + } kh_##name##_t; + +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ + extern kh_##name##_t *kh_init_##name(void); \ + extern void kh_destroy_##name(kh_##name##_t *h); \ + extern void kh_clear_##name(kh_##name##_t *h); \ + extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ + extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ + extern void kh_del_##name(kh_##name##_t *h, khint_t x); + +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + SCOPE kh_##name##_t *kh_init_##name(void) { \ + return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ + } \ + SCOPE void kh_destroy_##name(kh_##name##_t *h) \ + { \ + if (h) { \ + kfree((void *)h->keys); kfree(h->flags); \ + kfree((void *)h->vals); \ + kfree(h); \ + } \ + } \ + SCOPE void kh_clear_##name(kh_##name##_t *h) \ + { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ + { \ + if (h->n_buckets) { \ + khint_t k, i, last, mask, step = 0; \ + mask = h->n_buckets - 1; \ + k = __hash_func(key); i = k & mask; \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + i = (i + (++step)) & mask; \ + if (i == last) return h->n_buckets; \ + } \ + return __ac_iseither(h->flags, i)? h->n_buckets : i; \ + } else return 0; \ + } \ + SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ + { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ + khint32_t *new_flags = 0; \ + khint_t j = 1; \ + { \ + kroundup32(new_n_buckets); \ + if (new_n_buckets < 4) new_n_buckets = 4; \ + if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ + else { /* hash table size to be changed (shrink or expand); rehash */ \ + new_flags = (khint32_t*)kreallocarray(NULL, __ac_fsize(new_n_buckets), sizeof(khint32_t)); \ + if (!new_flags) return -1; \ + memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (h->n_buckets < new_n_buckets) { /* expand */ \ + khkey_t *new_keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ + if (!new_keys) { kfree(new_flags); return -1; } \ + h->keys = new_keys; \ + if (kh_is_map) { \ + khval_t *new_vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ + if (!new_vals) { kfree(new_flags); return -1; } \ + h->vals = new_vals; \ + } \ + } /* otherwise shrink */ \ + } \ + } \ + if (j) { /* rehashing is needed */ \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (__ac_iseither(h->flags, j) == 0) { \ + khkey_t key = h->keys[j]; \ + khval_t val; \ + khint_t new_mask; \ + new_mask = new_n_buckets - 1; \ + if (kh_is_map) val = h->vals[j]; \ + __ac_set_isdel_true(h->flags, j); \ + while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ + khint_t k, i, step = 0; \ + k = __hash_func(key); \ + i = k & new_mask; \ + while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ + __ac_set_isempty_false(new_flags, i); \ + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ + { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ + if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ + __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ + } else { /* write the element and jump out of the loop */ \ + h->keys[i] = key; \ + if (kh_is_map) h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ + h->keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ + if (kh_is_map) h->vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ + } \ + kfree(h->flags); /* free the working space */ \ + h->flags = new_flags; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ + } \ + return 0; \ + } \ + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ + { \ + khint_t x; \ + if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ + if (h->n_buckets > (h->size<<1)) { \ + if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ + *ret = -1; return h->n_buckets; \ + } \ + } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ + *ret = -1; return h->n_buckets; \ + } \ + } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ + { \ + khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ + x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ + if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ + else { \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (__ac_isdel(h->flags, i)) site = i; \ + i = (i + (++step)) & mask; \ + if (i == last) { x = site; break; } \ + } \ + if (x == h->n_buckets) { \ + if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ + else x = i; \ + } \ + } \ + } \ + if (__ac_isempty(h->flags, x)) { /* not present at all */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; ++h->n_occupied; \ + *ret = 1; \ + } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + *ret = 2; \ + } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ + return x; \ + } \ + SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ + { \ + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ + __ac_set_isdel_true(h->flags, x); \ + --h->size; \ + } \ + } + +#define KHASH_DECLARE(name, khkey_t, khval_t) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_PROTOTYPES(name, khkey_t, khval_t) + +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +/*! @function + @abstract Integer hash function + @param key The integer [khint32_t] + @return The hash value [khint_t] + */ +#define kh_int_hash_func(key) (khint32_t)(key) +/*! @function + @abstract Integer comparison function + */ +#define kh_int_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract 64-bit integer hash function + @param key The integer [khint64_t] + @return The hash value [khint_t] + */ +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) +/*! @function + @abstract 64-bit integer comparison function + */ +#define kh_int64_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract const char* hash function + @param s Pointer to a null terminated string + @return The hash value + */ +static kh_inline khint_t __ac_X31_hash_string(const char *s) +{ + khint_t h = (khint_t)*s; + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; + return h; +} +/*! @function + @abstract Another interface to const char* hash function + @param key Pointer to a null terminated string [const char*] + @return The hash value [khint_t] + */ +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +/*! @function + @abstract Const char* comparison function + */ +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) + +static kh_inline khint_t __ac_Wang_hash(khint_t key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} +#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other convenient macros... */ + +/*! + @abstract Type of the hash table. + @param name Name of the hash table [symbol] + */ +#define khash_t(name) kh_##name##_t + +/*! @function + @abstract Initiate a hash table. + @param name Name of the hash table [symbol] + @return Pointer to the hash table [khash_t(name)*] + */ +#define kh_init(name) kh_init_##name() + +/*! @function + @abstract Destroy a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_destroy(name, h) kh_destroy_##name(h) + +/*! @function + @abstract Reset a hash table without deallocating memory. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_clear(name, h) kh_clear_##name(h) + +/*! @function + @abstract Resize a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param s New size [khint_t] + */ +#define kh_resize(name, h, s) kh_resize_##name(h, s) + +/*! @function + @abstract Insert a key to the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @param r Extra return code: -1 if the operation failed; + 0 if the key is present in the hash table; + 1 if the bucket is empty (never used); 2 if the element in + the bucket has been deleted [int*] + @return Iterator to the inserted element [khint_t] + */ +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) + +/*! @function + @abstract Retrieve a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] + */ +#define kh_get(name, h, k) kh_get_##name(h, k) + +/*! @function + @abstract Remove a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Iterator to the element to be deleted [khint_t] + */ +#define kh_del(name, h, k) kh_del_##name(h, k) + +/*! @function + @abstract Test whether a bucket contains data. + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return 1 if containing data; 0 otherwise [int] + */ +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) + +/*! @function + @abstract Get key given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Key [type of keys] + */ +#define kh_key(h, x) ((h)->keys[x]) + +/*! @function + @abstract Get value given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Value [type of values] + @discussion For hash sets, calling this results in segfault. + */ +#define kh_val(h, x) ((h)->vals[x]) + +/*! @function + @abstract Alias of kh_val() + */ +#define kh_value(h, x) ((h)->vals[x]) + +/*! @function + @abstract Get the start iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The start iterator [khint_t] + */ +#define kh_begin(h) (khint_t)(0) + +/*! @function + @abstract Get the end iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The end iterator [khint_t] + */ +#define kh_end(h) ((h)->n_buckets) + +/*! @function + @abstract Get the number of elements in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of elements in the hash table [khint_t] + */ +#define kh_size(h) ((h)->size) + +/*! @function + @abstract Get the number of buckets in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of buckets in the hash table [khint_t] + */ +#define kh_n_buckets(h) ((h)->n_buckets) + +/*! @function + @abstract Iterate over the entries in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param kvar Variable to which key will be assigned + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (kvar) = kh_key(h,__i); \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/*! @function + @abstract Iterate over the values in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach_value(h, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/* More convenient interfaces */ + +/*! @function + @abstract Instantiate a hash set containing integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT(name) \ + KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT(name, khval_t) \ + KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT64(name) \ + KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT64(name, khval_t) \ + KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const char *kh_cstr_t; +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_STR(name) \ + KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_STR(name, khval_t) \ + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#endif /* __AC_KHASH_H */ diff --git a/src/libgit2/libgit2.c b/src/libgit2/libgit2.c new file mode 100644 index 000000000..efad3bf6d --- /dev/null +++ b/src/libgit2/libgit2.c @@ -0,0 +1,417 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "libgit2.h" + +#include +#include "alloc.h" +#include "buf.h" +#include "cache.h" +#include "common.h" +#include "filter.h" +#include "hash.h" +#include "index.h" +#include "merge_driver.h" +#include "pool.h" +#include "mwindow.h" +#include "object.h" +#include "odb.h" +#include "rand.h" +#include "refs.h" +#include "runtime.h" +#include "sysdir.h" +#include "thread.h" +#include "threadstate.h" +#include "git2/global.h" +#include "streams/registry.h" +#include "streams/mbedtls.h" +#include "streams/openssl.h" +#include "transports/smart.h" +#include "transports/http.h" +#include "transports/ssh.h" + +#ifdef GIT_WIN32 +# include "win32/w32_leakcheck.h" +#endif + +/* Declarations for tuneable settings */ +extern size_t git_mwindow__window_size; +extern size_t git_mwindow__mapped_limit; +extern size_t git_mwindow__file_limit; +extern size_t git_indexer__max_objects; +extern bool git_disable_pack_keep_file_checks; +extern int git_odb__packed_priority; +extern int git_odb__loose_priority; + +char *git__user_agent; +char *git__ssl_ciphers; + +static void libgit2_settings_global_shutdown(void) +{ + git__free(git__user_agent); + git__free(git__ssl_ciphers); + git_repository__free_extensions(); +} + +static int git_libgit2_settings_global_init(void) +{ + return git_runtime_shutdown_register(libgit2_settings_global_shutdown); +} + +int git_libgit2_init(void) +{ + static git_runtime_init_fn init_fns[] = { +#ifdef GIT_WIN32 + git_win32_leakcheck_global_init, +#endif + git_allocator_global_init, + git_threadstate_global_init, + git_threads_global_init, + git_rand_global_init, + git_hash_global_init, + git_sysdir_global_init, + git_filter_global_init, + git_merge_driver_global_init, + git_transport_ssh_global_init, + git_stream_registry_global_init, + git_openssl_stream_global_init, + git_mbedtls_stream_global_init, + git_mwindow_global_init, + git_pool_global_init, + git_libgit2_settings_global_init + }; + + return git_runtime_init(init_fns, ARRAY_SIZE(init_fns)); +} + +int git_libgit2_init_count(void) +{ + return git_runtime_init_count(); +} + +int git_libgit2_shutdown(void) +{ + return git_runtime_shutdown(); +} + +int git_libgit2_version(int *major, int *minor, int *rev) +{ + *major = LIBGIT2_VER_MAJOR; + *minor = LIBGIT2_VER_MINOR; + *rev = LIBGIT2_VER_REVISION; + + return 0; +} + +const char *git_libgit2_prerelease(void) +{ + return LIBGIT2_VER_PRERELEASE; +} + +int git_libgit2_features(void) +{ + return 0 +#ifdef GIT_THREADS + | GIT_FEATURE_THREADS +#endif +#ifdef GIT_HTTPS + | GIT_FEATURE_HTTPS +#endif +#if defined(GIT_SSH) + | GIT_FEATURE_SSH +#endif +#if defined(GIT_USE_NSEC) + | GIT_FEATURE_NSEC +#endif + ; +} + +static int config_level_to_sysdir(int *out, int config_level) +{ + switch (config_level) { + case GIT_CONFIG_LEVEL_SYSTEM: + *out = GIT_SYSDIR_SYSTEM; + return 0; + case GIT_CONFIG_LEVEL_XDG: + *out = GIT_SYSDIR_XDG; + return 0; + case GIT_CONFIG_LEVEL_GLOBAL: + *out = GIT_SYSDIR_GLOBAL; + return 0; + case GIT_CONFIG_LEVEL_PROGRAMDATA: + *out = GIT_SYSDIR_PROGRAMDATA; + return 0; + default: + break; + } + + git_error_set( + GIT_ERROR_INVALID, "invalid config path selector %d", config_level); + return -1; +} + +const char *git_libgit2__user_agent(void) +{ + return git__user_agent; +} + +const char *git_libgit2__ssl_ciphers(void) +{ + return git__ssl_ciphers; +} + +int git_libgit2_opts(int key, ...) +{ + int error = 0; + va_list ap; + + va_start(ap, key); + + switch (key) { + case GIT_OPT_SET_MWINDOW_SIZE: + git_mwindow__window_size = va_arg(ap, size_t); + break; + + case GIT_OPT_GET_MWINDOW_SIZE: + *(va_arg(ap, size_t *)) = git_mwindow__window_size; + break; + + case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT: + git_mwindow__mapped_limit = va_arg(ap, size_t); + break; + + case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: + *(va_arg(ap, size_t *)) = git_mwindow__mapped_limit; + break; + + case GIT_OPT_SET_MWINDOW_FILE_LIMIT: + git_mwindow__file_limit = va_arg(ap, size_t); + break; + + case GIT_OPT_GET_MWINDOW_FILE_LIMIT: + *(va_arg(ap, size_t *)) = git_mwindow__file_limit; + break; + + case GIT_OPT_GET_SEARCH_PATH: + { + int sysdir = va_arg(ap, int); + git_buf *out = va_arg(ap, git_buf *); + git_str str = GIT_STR_INIT; + const git_str *tmp; + int level; + + if ((error = git_buf_tostr(&str, out)) < 0 || + (error = config_level_to_sysdir(&level, sysdir)) < 0 || + (error = git_sysdir_get(&tmp, level)) < 0 || + (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_SET_SEARCH_PATH: + { + int level; + + if ((error = config_level_to_sysdir(&level, va_arg(ap, int))) >= 0) + error = git_sysdir_set(level, va_arg(ap, const char *)); + } + break; + + case GIT_OPT_SET_CACHE_OBJECT_LIMIT: + { + git_object_t type = (git_object_t)va_arg(ap, int); + size_t size = va_arg(ap, size_t); + error = git_cache_set_max_object_size(type, size); + break; + } + + case GIT_OPT_SET_CACHE_MAX_SIZE: + git_cache__max_storage = va_arg(ap, ssize_t); + break; + + case GIT_OPT_ENABLE_CACHING: + git_cache__enabled = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_GET_CACHED_MEMORY: + *(va_arg(ap, ssize_t *)) = git_cache__current_storage.val; + *(va_arg(ap, ssize_t *)) = git_cache__max_storage; + break; + + case GIT_OPT_GET_TEMPLATE_PATH: + { + git_buf *out = va_arg(ap, git_buf *); + git_str str = GIT_STR_INIT; + const git_str *tmp; + + if ((error = git_buf_tostr(&str, out)) < 0 || + (error = git_sysdir_get(&tmp, GIT_SYSDIR_TEMPLATE)) < 0 || + (error = git_str_put(&str, tmp->ptr, tmp->size)) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_SET_TEMPLATE_PATH: + error = git_sysdir_set(GIT_SYSDIR_TEMPLATE, va_arg(ap, const char *)); + break; + + case GIT_OPT_SET_SSL_CERT_LOCATIONS: +#ifdef GIT_OPENSSL + { + const char *file = va_arg(ap, const char *); + const char *path = va_arg(ap, const char *); + error = git_openssl__set_cert_location(file, path); + } +#elif defined(GIT_MBEDTLS) + { + const char *file = va_arg(ap, const char *); + const char *path = va_arg(ap, const char *); + error = git_mbedtls__set_cert_location(file, path); + } +#else + git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support certificate locations"); + error = -1; +#endif + break; + case GIT_OPT_SET_USER_AGENT: + git__free(git__user_agent); + git__user_agent = git__strdup(va_arg(ap, const char *)); + if (!git__user_agent) { + git_error_set_oom(); + error = -1; + } + + break; + + case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: + git_object__strict_input_validation = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION: + git_reference__enable_symbolic_ref_target_validation = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_SET_SSL_CIPHERS: +#if (GIT_OPENSSL || GIT_MBEDTLS) + { + git__free(git__ssl_ciphers); + git__ssl_ciphers = git__strdup(va_arg(ap, const char *)); + if (!git__ssl_ciphers) { + git_error_set_oom(); + error = -1; + } + } +#else + git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support custom ciphers"); + error = -1; +#endif + break; + + case GIT_OPT_GET_USER_AGENT: + { + git_buf *out = va_arg(ap, git_buf *); + git_str str = GIT_STR_INIT; + + if ((error = git_buf_tostr(&str, out)) < 0 || + (error = git_str_puts(&str, git__user_agent)) < 0) + break; + + error = git_buf_fromstr(out, &str); + } + break; + + case GIT_OPT_ENABLE_OFS_DELTA: + git_smart__ofs_delta_enabled = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_ENABLE_FSYNC_GITDIR: + git_repository__fsync_gitdir = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_GET_WINDOWS_SHAREMODE: +#ifdef GIT_WIN32 + *(va_arg(ap, unsigned long *)) = git_win32__createfile_sharemode; +#endif + break; + + case GIT_OPT_SET_WINDOWS_SHAREMODE: +#ifdef GIT_WIN32 + git_win32__createfile_sharemode = va_arg(ap, unsigned long); +#endif + break; + + case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: + git_odb__strict_hash_verification = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_SET_ALLOCATOR: + error = git_allocator_setup(va_arg(ap, git_allocator *)); + break; + + case GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY: + git_index__enforce_unsaved_safety = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_SET_PACK_MAX_OBJECTS: + git_indexer__max_objects = va_arg(ap, size_t); + break; + + case GIT_OPT_GET_PACK_MAX_OBJECTS: + *(va_arg(ap, size_t *)) = git_indexer__max_objects; + break; + + case GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS: + git_disable_pack_keep_file_checks = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE: + git_http__expect_continue = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_SET_ODB_PACKED_PRIORITY: + git_odb__packed_priority = va_arg(ap, int); + break; + + case GIT_OPT_SET_ODB_LOOSE_PRIORITY: + git_odb__loose_priority = va_arg(ap, int); + break; + + case GIT_OPT_SET_EXTENSIONS: + { + const char **extensions = va_arg(ap, const char **); + size_t len = va_arg(ap, size_t); + error = git_repository__set_extensions(extensions, len); + } + break; + + case GIT_OPT_GET_EXTENSIONS: + { + git_strarray *out = va_arg(ap, git_strarray *); + char **extensions; + size_t len; + + if ((error = git_repository__extensions(&extensions, &len)) < 0) + break; + + out->strings = extensions; + out->count = len; + } + break; + + default: + git_error_set(GIT_ERROR_INVALID, "invalid option key"); + error = -1; + } + + va_end(ap); + + return error; +} diff --git a/src/libgit2/libgit2.h b/src/libgit2/libgit2.h new file mode 100644 index 000000000..a898367ae --- /dev/null +++ b/src/libgit2/libgit2.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_libgit2_h__ +#define INCLUDE_libgit2_h__ + +extern int git_libgit2_init_count(void); + +extern const char *git_libgit2__user_agent(void); +extern const char *git_libgit2__ssl_ciphers(void); + +#endif diff --git a/src/libgit2/mailmap.c b/src/libgit2/mailmap.c new file mode 100644 index 000000000..4336fe3e5 --- /dev/null +++ b/src/libgit2/mailmap.c @@ -0,0 +1,500 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "mailmap.h" + +#include "common.h" +#include "config.h" +#include "fs_path.h" +#include "repository.h" +#include "signature.h" +#include "git2/config.h" +#include "git2/revparse.h" +#include "blob.h" +#include "parse.h" +#include "path.h" + +#define MM_FILE ".mailmap" +#define MM_FILE_CONFIG "mailmap.file" +#define MM_BLOB_CONFIG "mailmap.blob" +#define MM_BLOB_DEFAULT "HEAD:" MM_FILE + +static void mailmap_entry_free(git_mailmap_entry *entry) +{ + if (!entry) + return; + + git__free(entry->real_name); + git__free(entry->real_email); + git__free(entry->replace_name); + git__free(entry->replace_email); + git__free(entry); +} + +/* + * First we sort by replace_email, then replace_name (if present). + * Entries with names are greater than entries without. + */ +static int mailmap_entry_cmp(const void *a_raw, const void *b_raw) +{ + const git_mailmap_entry *a = (const git_mailmap_entry *)a_raw; + const git_mailmap_entry *b = (const git_mailmap_entry *)b_raw; + int cmp; + + GIT_ASSERT_ARG(a && a->replace_email); + GIT_ASSERT_ARG(b && b->replace_email); + + cmp = git__strcmp(a->replace_email, b->replace_email); + if (cmp) + return cmp; + + /* NULL replace_names are less than not-NULL ones */ + if (a->replace_name == NULL || b->replace_name == NULL) + return (int)(a->replace_name != NULL) - (int)(b->replace_name != NULL); + + return git__strcmp(a->replace_name, b->replace_name); +} + +/* Replace the old entry with the new on duplicate. */ +static int mailmap_entry_replace(void **old_raw, void *new_raw) +{ + mailmap_entry_free((git_mailmap_entry *)*old_raw); + *old_raw = new_raw; + return GIT_EEXISTS; +} + +/* Check if we're at the end of line, w/ comments */ +static bool is_eol(git_parse_ctx *ctx) +{ + char c; + return git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE) < 0 || c == '#'; +} + +static int advance_until( + const char **start, size_t *len, git_parse_ctx *ctx, char needle) +{ + *start = ctx->line; + while (ctx->line_len > 0 && *ctx->line != '#' && *ctx->line != needle) + git_parse_advance_chars(ctx, 1); + + if (ctx->line_len == 0 || *ctx->line == '#') + return -1; /* end of line */ + + *len = ctx->line - *start; + git_parse_advance_chars(ctx, 1); /* advance past needle */ + return 0; +} + +/* + * Parse a single entry from a mailmap file. + * + * The output git_strs will be non-owning, and should be copied before being + * persisted. + */ +static int parse_mailmap_entry( + git_str *real_name, git_str *real_email, + git_str *replace_name, git_str *replace_email, + git_parse_ctx *ctx) +{ + const char *start; + size_t len; + + git_str_clear(real_name); + git_str_clear(real_email); + git_str_clear(replace_name); + git_str_clear(replace_email); + + git_parse_advance_ws(ctx); + if (is_eol(ctx)) + return -1; /* blank line */ + + /* Parse the real name */ + if (advance_until(&start, &len, ctx, '<') < 0) + return -1; + + git_str_attach_notowned(real_name, start, len); + git_str_rtrim(real_name); + + /* + * If this is the last email in the line, this is the email to replace, + * otherwise, it's the real email. + */ + if (advance_until(&start, &len, ctx, '>') < 0) + return -1; + + /* If we aren't at the end of the line, parse a second name and email */ + if (!is_eol(ctx)) { + git_str_attach_notowned(real_email, start, len); + + git_parse_advance_ws(ctx); + if (advance_until(&start, &len, ctx, '<') < 0) + return -1; + git_str_attach_notowned(replace_name, start, len); + git_str_rtrim(replace_name); + + if (advance_until(&start, &len, ctx, '>') < 0) + return -1; + } + + git_str_attach_notowned(replace_email, start, len); + + if (!is_eol(ctx)) + return -1; + + return 0; +} + +int git_mailmap_new(git_mailmap **out) +{ + int error; + git_mailmap *mm = git__calloc(1, sizeof(git_mailmap)); + GIT_ERROR_CHECK_ALLOC(mm); + + error = git_vector_init(&mm->entries, 0, mailmap_entry_cmp); + if (error < 0) { + git__free(mm); + return error; + } + *out = mm; + return 0; +} + +void git_mailmap_free(git_mailmap *mm) +{ + size_t idx; + git_mailmap_entry *entry; + if (!mm) + return; + + git_vector_foreach(&mm->entries, idx, entry) + mailmap_entry_free(entry); + + git_vector_free(&mm->entries); + git__free(mm); +} + +static int mailmap_add_entry_unterminated( + git_mailmap *mm, + const char *real_name, size_t real_name_size, + const char *real_email, size_t real_email_size, + const char *replace_name, size_t replace_name_size, + const char *replace_email, size_t replace_email_size) +{ + int error; + git_mailmap_entry *entry = git__calloc(1, sizeof(git_mailmap_entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + GIT_ASSERT_ARG(mm); + GIT_ASSERT_ARG(replace_email && *replace_email); + + if (real_name_size > 0) { + entry->real_name = git__substrdup(real_name, real_name_size); + GIT_ERROR_CHECK_ALLOC(entry->real_name); + } + if (real_email_size > 0) { + entry->real_email = git__substrdup(real_email, real_email_size); + GIT_ERROR_CHECK_ALLOC(entry->real_email); + } + if (replace_name_size > 0) { + entry->replace_name = git__substrdup(replace_name, replace_name_size); + GIT_ERROR_CHECK_ALLOC(entry->replace_name); + } + entry->replace_email = git__substrdup(replace_email, replace_email_size); + GIT_ERROR_CHECK_ALLOC(entry->replace_email); + + error = git_vector_insert_sorted(&mm->entries, entry, mailmap_entry_replace); + if (error == GIT_EEXISTS) + error = GIT_OK; + else if (error < 0) + mailmap_entry_free(entry); + + return error; +} + +int git_mailmap_add_entry( + git_mailmap *mm, const char *real_name, const char *real_email, + const char *replace_name, const char *replace_email) +{ + return mailmap_add_entry_unterminated( + mm, + real_name, real_name ? strlen(real_name) : 0, + real_email, real_email ? strlen(real_email) : 0, + replace_name, replace_name ? strlen(replace_name) : 0, + replace_email, strlen(replace_email)); +} + +static int mailmap_add_buffer(git_mailmap *mm, const char *buf, size_t len) +{ + int error = 0; + git_parse_ctx ctx; + + /* Scratch buffers containing the real parsed names & emails */ + git_str real_name = GIT_STR_INIT; + git_str real_email = GIT_STR_INIT; + git_str replace_name = GIT_STR_INIT; + git_str replace_email = GIT_STR_INIT; + + /* Buffers may not contain '\0's. */ + if (memchr(buf, '\0', len) != NULL) + return -1; + + git_parse_ctx_init(&ctx, buf, len); + + /* Run the parser */ + while (ctx.remain_len > 0) { + error = parse_mailmap_entry( + &real_name, &real_email, &replace_name, &replace_email, &ctx); + if (error < 0) { + error = 0; /* Skip lines which don't contain a valid entry */ + git_parse_advance_line(&ctx); + continue; /* TODO: warn */ + } + + /* NOTE: Can't use add_entry(...) as our buffers aren't terminated */ + error = mailmap_add_entry_unterminated( + mm, real_name.ptr, real_name.size, real_email.ptr, real_email.size, + replace_name.ptr, replace_name.size, replace_email.ptr, replace_email.size); + if (error < 0) + goto cleanup; + + error = 0; + } + +cleanup: + git_str_dispose(&real_name); + git_str_dispose(&real_email); + git_str_dispose(&replace_name); + git_str_dispose(&replace_email); + return error; +} + +int git_mailmap_from_buffer(git_mailmap **out, const char *data, size_t len) +{ + int error = git_mailmap_new(out); + if (error < 0) + return error; + + error = mailmap_add_buffer(*out, data, len); + if (error < 0) { + git_mailmap_free(*out); + *out = NULL; + } + return error; +} + +static int mailmap_add_blob( + git_mailmap *mm, git_repository *repo, const char *rev) +{ + git_object *object = NULL; + git_blob *blob = NULL; + git_str content = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(mm); + GIT_ASSERT_ARG(repo); + + error = git_revparse_single(&object, repo, rev); + if (error < 0) + goto cleanup; + + error = git_object_peel((git_object **)&blob, object, GIT_OBJECT_BLOB); + if (error < 0) + goto cleanup; + + error = git_blob__getbuf(&content, blob); + if (error < 0) + goto cleanup; + + error = mailmap_add_buffer(mm, content.ptr, content.size); + if (error < 0) + goto cleanup; + +cleanup: + git_str_dispose(&content); + git_blob_free(blob); + git_object_free(object); + return error; +} + +static int mailmap_add_file_ondisk( + git_mailmap *mm, const char *path, git_repository *repo) +{ + const char *base = repo ? git_repository_workdir(repo) : NULL; + git_str fullpath = GIT_STR_INIT; + git_str content = GIT_STR_INIT; + int error; + + error = git_fs_path_join_unrooted(&fullpath, path, base, NULL); + if (error < 0) + goto cleanup; + + error = git_path_validate_str_length(repo, &fullpath); + if (error < 0) + goto cleanup; + + error = git_futils_readbuffer(&content, fullpath.ptr); + if (error < 0) + goto cleanup; + + error = mailmap_add_buffer(mm, content.ptr, content.size); + if (error < 0) + goto cleanup; + +cleanup: + git_str_dispose(&fullpath); + git_str_dispose(&content); + return error; +} + +/* NOTE: Only expose with an error return, currently never errors */ +static void mailmap_add_from_repository(git_mailmap *mm, git_repository *repo) +{ + git_config *config = NULL; + git_str rev_buf = GIT_STR_INIT; + git_str path_buf = GIT_STR_INIT; + const char *rev = NULL; + const char *path = NULL; + + /* If we're in a bare repo, default blob to 'HEAD:.mailmap' */ + if (repo->is_bare) + rev = MM_BLOB_DEFAULT; + + /* Try to load 'mailmap.file' and 'mailmap.blob' cfgs from the repo */ + if (git_repository_config(&config, repo) == 0) { + if (git_config__get_string_buf(&rev_buf, config, MM_BLOB_CONFIG) == 0) + rev = rev_buf.ptr; + if (git_config__get_path(&path_buf, config, MM_FILE_CONFIG) == 0) + path = path_buf.ptr; + } + + /* + * Load mailmap files in order, overriding previous entries with new ones. + * 1. The '.mailmap' file in the repository's workdir root, + * 2. The blob described by the 'mailmap.blob' config (default HEAD:.mailmap), + * 3. The file described by the 'mailmap.file' config. + * + * We ignore errors from these loads, as these files may not exist, or may + * contain invalid information, and we don't want to report that error. + * + * XXX: Warn? + */ + if (!repo->is_bare) + mailmap_add_file_ondisk(mm, MM_FILE, repo); + if (rev != NULL) + mailmap_add_blob(mm, repo, rev); + if (path != NULL) + mailmap_add_file_ondisk(mm, path, repo); + + git_str_dispose(&rev_buf); + git_str_dispose(&path_buf); + git_config_free(config); +} + +int git_mailmap_from_repository(git_mailmap **out, git_repository *repo) +{ + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + if ((error = git_mailmap_new(out)) < 0) + return error; + + mailmap_add_from_repository(*out, repo); + return 0; +} + +const git_mailmap_entry *git_mailmap_entry_lookup( + const git_mailmap *mm, const char *name, const char *email) +{ + int error; + ssize_t fallback = -1; + size_t idx; + git_mailmap_entry *entry; + + /* The lookup needle we want to use only sets the replace_email. */ + git_mailmap_entry needle = { NULL }; + needle.replace_email = (char *)email; + + GIT_ASSERT_ARG_WITH_RETVAL(email, NULL); + + if (!mm) + return NULL; + + /* + * We want to find the place to start looking. so we do a binary search for + * the "fallback" nameless entry. If we find it, we advance past it and record + * the index. + */ + error = git_vector_bsearch(&idx, (git_vector *)&mm->entries, &needle); + if (error >= 0) + fallback = idx++; + else if (error != GIT_ENOTFOUND) + return NULL; + + /* do a linear search for an exact match */ + for (; idx < git_vector_length(&mm->entries); ++idx) { + entry = git_vector_get(&mm->entries, idx); + + if (git__strcmp(entry->replace_email, email)) + break; /* it's a different email, so we're done looking */ + + /* should be specific */ + GIT_ASSERT_WITH_RETVAL(entry->replace_name, NULL); + if (!name || !git__strcmp(entry->replace_name, name)) + return entry; + } + + if (fallback < 0) + return NULL; /* no fallback */ + return git_vector_get(&mm->entries, fallback); +} + +int git_mailmap_resolve( + const char **real_name, const char **real_email, + const git_mailmap *mailmap, + const char *name, const char *email) +{ + const git_mailmap_entry *entry = NULL; + + GIT_ASSERT(name); + GIT_ASSERT(email); + + *real_name = name; + *real_email = email; + + if ((entry = git_mailmap_entry_lookup(mailmap, name, email))) { + if (entry->real_name) + *real_name = entry->real_name; + if (entry->real_email) + *real_email = entry->real_email; + } + return 0; +} + +int git_mailmap_resolve_signature( + git_signature **out, const git_mailmap *mailmap, const git_signature *sig) +{ + const char *name = NULL; + const char *email = NULL; + int error; + + if (!sig) + return 0; + + error = git_mailmap_resolve(&name, &email, mailmap, sig->name, sig->email); + if (error < 0) + return error; + + error = git_signature_new(out, name, email, sig->when.time, sig->when.offset); + if (error < 0) + return error; + + /* Copy over the sign, as git_signature_new doesn't let you pass it. */ + (*out)->when.sign = sig->when.sign; + return 0; +} diff --git a/src/libgit2/mailmap.h b/src/libgit2/mailmap.h new file mode 100644 index 000000000..2c9736a4a --- /dev/null +++ b/src/libgit2/mailmap.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_mailmap_h__ +#define INCLUDE_mailmap_h__ + +#include "git2/mailmap.h" +#include "vector.h" + +/* + * A mailmap is stored as a sorted vector of 'git_mailmap_entry's. These entries + * are sorted first by 'replace_email', and then by 'replace_name'. NULL + * replace_names are ordered first. + * + * Looking up a name and email in the mailmap is done with a binary search. + */ +struct git_mailmap { + git_vector entries; +}; + +/* Single entry parsed from a mailmap */ +typedef struct git_mailmap_entry { + char *real_name; /**< the real name (may be NULL) */ + char *real_email; /**< the real email (may be NULL) */ + char *replace_name; /**< the name to replace (may be NULL) */ + char *replace_email; /**< the email to replace */ +} git_mailmap_entry; + +const git_mailmap_entry *git_mailmap_entry_lookup( + const git_mailmap *mm, const char *name, const char *email); + +#endif diff --git a/src/libgit2/map.h b/src/libgit2/map.h new file mode 100644 index 000000000..01931d199 --- /dev/null +++ b/src/libgit2/map.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_map_h__ +#define INCLUDE_map_h__ + +#include "common.h" + + +/* p_mmap() prot values */ +#define GIT_PROT_NONE 0x0 +#define GIT_PROT_READ 0x1 +#define GIT_PROT_WRITE 0x2 +#define GIT_PROT_EXEC 0x4 + +/* git__mmmap() flags values */ +#define GIT_MAP_FILE 0 +#define GIT_MAP_SHARED 1 +#define GIT_MAP_PRIVATE 2 +#define GIT_MAP_TYPE 0xf +#define GIT_MAP_FIXED 0x10 + +#ifdef __amigaos4__ +#define MAP_FAILED 0 +#endif + +typedef struct { /* memory mapped buffer */ + void *data; /* data bytes */ + size_t len; /* data length */ +#ifdef GIT_WIN32 + HANDLE fmh; /* file mapping handle */ +#endif +} git_map; + +#define GIT_MMAP_VALIDATE(out, len, prot, flags) do { \ + GIT_ASSERT(out != NULL && len > 0); \ + GIT_ASSERT((prot & GIT_PROT_WRITE) || (prot & GIT_PROT_READ)); \ + GIT_ASSERT((flags & GIT_MAP_FIXED) == 0); } while (0) + +extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset); +extern int p_munmap(git_map *map); + +#endif diff --git a/src/libgit2/merge.c b/src/libgit2/merge.c new file mode 100644 index 000000000..641b32632 --- /dev/null +++ b/src/libgit2/merge.c @@ -0,0 +1,3435 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "merge.h" + +#include "posix.h" +#include "str.h" +#include "repository.h" +#include "revwalk.h" +#include "commit_list.h" +#include "fs_path.h" +#include "refs.h" +#include "object.h" +#include "iterator.h" +#include "refs.h" +#include "diff.h" +#include "diff_generate.h" +#include "diff_tform.h" +#include "checkout.h" +#include "tree.h" +#include "blob.h" +#include "oid.h" +#include "index.h" +#include "filebuf.h" +#include "config.h" +#include "oidarray.h" +#include "annotated_commit.h" +#include "commit.h" +#include "oidarray.h" +#include "merge_driver.h" +#include "oidmap.h" +#include "array.h" + +#include "git2/types.h" +#include "git2/repository.h" +#include "git2/object.h" +#include "git2/commit.h" +#include "git2/merge.h" +#include "git2/refs.h" +#include "git2/reset.h" +#include "git2/checkout.h" +#include "git2/signature.h" +#include "git2/config.h" +#include "git2/tree.h" +#include "git2/oidarray.h" +#include "git2/annotated_commit.h" +#include "git2/sys/index.h" +#include "git2/sys/hashsig.h" + +#define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0) +#define GIT_MERGE_INDEX_ENTRY_ISFILE(X) S_ISREG((X).mode) + + +typedef enum { + TREE_IDX_ANCESTOR = 0, + TREE_IDX_OURS = 1, + TREE_IDX_THEIRS = 2 +} merge_tree_index_t; + +/* Tracks D/F conflicts */ +struct merge_diff_df_data { + const char *df_path; + const char *prev_path; + git_merge_diff *prev_conflict; +}; + +/* + * This acts as a negative cache entry marker. In case we've tried to calculate + * similarity metrics for a given blob already but `git_hashsig` determined + * that it's too small in order to have a meaningful hash signature, we will + * insert the address of this marker instead of `NULL`. Like this, we can + * easily check whether we have checked a gien entry already and skip doing the + * calculation again and again. + */ +static int cache_invalid_marker; + +/* Merge base computation */ + +static int merge_bases_many(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, size_t length, const git_oid input_array[]) +{ + git_revwalk *walk = NULL; + git_vector list; + git_commit_list *result = NULL; + git_commit_list_node *commit; + int error = -1; + unsigned int i; + + if (length < 2) { + git_error_set(GIT_ERROR_INVALID, "at least two commits are required to find an ancestor"); + return -1; + } + + if (git_vector_init(&list, length - 1, NULL) < 0) + return -1; + + if (git_revwalk_new(&walk, repo) < 0) + goto on_error; + + for (i = 1; i < length; i++) { + commit = git_revwalk__commit_lookup(walk, &input_array[i]); + if (commit == NULL) + goto on_error; + + git_vector_insert(&list, commit); + } + + commit = git_revwalk__commit_lookup(walk, &input_array[0]); + if (commit == NULL) + goto on_error; + + if (git_merge__bases_many(&result, walk, commit, &list, 0) < 0) + goto on_error; + + if (!result) { + git_error_set(GIT_ERROR_MERGE, "no merge base found"); + error = GIT_ENOTFOUND; + goto on_error; + } + + *out = result; + *walk_out = walk; + + git_vector_free(&list); + return 0; + +on_error: + git_vector_free(&list); + git_revwalk_free(walk); + return error; +} + +int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]) +{ + git_revwalk *walk; + git_commit_list *result = NULL; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(input_array); + + if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 0) + return error; + + git_oid_cpy(out, &result->item->oid); + + git_commit_list_free(&result); + git_revwalk_free(walk); + + return 0; +} + +int git_merge_bases_many(git_oidarray *out, git_repository *repo, size_t length, const git_oid input_array[]) +{ + git_revwalk *walk; + git_commit_list *list, *result = NULL; + int error = 0; + git_array_oid_t array; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(input_array); + + if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 0) + return error; + + git_array_init(array); + + list = result; + while (list) { + git_oid *id = git_array_alloc(array); + if (id == NULL) { + error = -1; + goto cleanup; + } + + git_oid_cpy(id, &list->item->oid); + list = list->next; + } + + git_oidarray__from_array(out, &array); + +cleanup: + git_commit_list_free(&result); + git_revwalk_free(walk); + + return error; +} + +int git_merge_base_octopus(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]) +{ + git_oid result; + unsigned int i; + int error = -1; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(input_array); + + if (length < 2) { + git_error_set(GIT_ERROR_INVALID, "at least two commits are required to find an ancestor"); + return -1; + } + + result = input_array[0]; + for (i = 1; i < length; i++) { + error = git_merge_base(&result, repo, &result, &input_array[i]); + if (error < 0) + return error; + } + + *out = result; + + return 0; +} + +static int merge_bases(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, const git_oid *one, const git_oid *two) +{ + git_revwalk *walk; + git_vector list; + git_commit_list *result = NULL; + git_commit_list_node *commit; + void *contents[1]; + + if (git_revwalk_new(&walk, repo) < 0) + return -1; + + commit = git_revwalk__commit_lookup(walk, two); + if (commit == NULL) + goto on_error; + + /* This is just one value, so we can do it on the stack */ + memset(&list, 0x0, sizeof(git_vector)); + contents[0] = commit; + list.length = 1; + list.contents = contents; + + commit = git_revwalk__commit_lookup(walk, one); + if (commit == NULL) + goto on_error; + + if (git_merge__bases_many(&result, walk, commit, &list, 0) < 0) + goto on_error; + + if (!result) { + git_revwalk_free(walk); + git_error_set(GIT_ERROR_MERGE, "no merge base found"); + return GIT_ENOTFOUND; + } + + *out = result; + *walk_out = walk; + + return 0; + +on_error: + git_revwalk_free(walk); + return -1; + +} + +int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two) +{ + int error; + git_revwalk *walk; + git_commit_list *result; + + if ((error = merge_bases(&result, &walk, repo, one, two)) < 0) + return error; + + git_oid_cpy(out, &result->item->oid); + git_commit_list_free(&result); + git_revwalk_free(walk); + + return 0; +} + +int git_merge_bases(git_oidarray *out, git_repository *repo, const git_oid *one, const git_oid *two) +{ + int error; + git_revwalk *walk; + git_commit_list *result, *list; + git_array_oid_t array; + + git_array_init(array); + + if ((error = merge_bases(&result, &walk, repo, one, two)) < 0) + return error; + + list = result; + while (list) { + git_oid *id = git_array_alloc(array); + if (id == NULL) + goto on_error; + + git_oid_cpy(id, &list->item->oid); + list = list->next; + } + + git_oidarray__from_array(out, &array); + git_commit_list_free(&result); + git_revwalk_free(walk); + + return 0; + +on_error: + git_commit_list_free(&result); + git_revwalk_free(walk); + return -1; +} + +static int interesting(git_pqueue *list) +{ + size_t i; + + for (i = 0; i < git_pqueue_size(list); i++) { + git_commit_list_node *commit = git_pqueue_get(list, i); + if ((commit->flags & STALE) == 0) + return 1; + } + + return 0; +} + +static int clear_commit_marks_1(git_commit_list **plist, + git_commit_list_node *commit, unsigned int mark) +{ + while (commit) { + unsigned int i; + + if (!(mark & commit->flags)) + return 0; + + commit->flags &= ~mark; + + for (i = 1; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + if (git_commit_list_insert(p, plist) == NULL) + return -1; + } + + commit = commit->out_degree ? commit->parents[0] : NULL; + } + + return 0; +} + +static int clear_commit_marks_many(git_vector *commits, unsigned int mark) +{ + git_commit_list *list = NULL; + git_commit_list_node *c; + unsigned int i; + + git_vector_foreach(commits, i, c) { + if (git_commit_list_insert(c, &list) == NULL) + return -1; + } + + while (list) + if (clear_commit_marks_1(&list, git_commit_list_pop(&list), mark) < 0) + return -1; + return 0; +} + +static int clear_commit_marks(git_commit_list_node *commit, unsigned int mark) +{ + git_commit_list *list = NULL; + if (git_commit_list_insert(commit, &list) == NULL) + return -1; + while (list) + if (clear_commit_marks_1(&list, git_commit_list_pop(&list), mark) < 0) + return -1; + return 0; +} + +static int paint_down_to_common( + git_commit_list **out, + git_revwalk *walk, + git_commit_list_node *one, + git_vector *twos, + uint32_t minimum_generation) +{ + git_pqueue list; + git_commit_list *result = NULL; + git_commit_list_node *two; + + int error; + unsigned int i; + + if (git_pqueue_init(&list, 0, twos->length * 2, git_commit_list_generation_cmp) < 0) + return -1; + + one->flags |= PARENT1; + if (git_pqueue_insert(&list, one) < 0) + return -1; + + git_vector_foreach(twos, i, two) { + if (git_commit_list_parse(walk, two) < 0) + return -1; + + two->flags |= PARENT2; + if (git_pqueue_insert(&list, two) < 0) + return -1; + } + + /* as long as there are non-STALE commits */ + while (interesting(&list)) { + git_commit_list_node *commit = git_pqueue_pop(&list); + int flags; + + if (commit == NULL) + break; + + flags = commit->flags & (PARENT1 | PARENT2 | STALE); + if (flags == (PARENT1 | PARENT2)) { + if (!(commit->flags & RESULT)) { + commit->flags |= RESULT; + if (git_commit_list_insert(commit, &result) == NULL) + return -1; + } + /* we mark the parents of a merge stale */ + flags |= STALE; + } + + for (i = 0; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + if ((p->flags & flags) == flags) + continue; + if (p->generation < minimum_generation) + continue; + + if ((error = git_commit_list_parse(walk, p)) < 0) + return error; + + p->flags |= flags; + if (git_pqueue_insert(&list, p) < 0) + return -1; + } + } + + git_pqueue_free(&list); + *out = result; + return 0; +} + +static int remove_redundant(git_revwalk *walk, git_vector *commits, uint32_t minimum_generation) +{ + git_vector work = GIT_VECTOR_INIT; + unsigned char *redundant; + unsigned int *filled_index; + unsigned int i, j; + int error = 0; + + redundant = git__calloc(commits->length, 1); + GIT_ERROR_CHECK_ALLOC(redundant); + filled_index = git__calloc((commits->length - 1), sizeof(unsigned int)); + GIT_ERROR_CHECK_ALLOC(filled_index); + + for (i = 0; i < commits->length; ++i) { + if ((error = git_commit_list_parse(walk, commits->contents[i])) < 0) + goto done; + } + + for (i = 0; i < commits->length; ++i) { + git_commit_list *common = NULL; + git_commit_list_node *commit = commits->contents[i]; + + if (redundant[i]) + continue; + + git_vector_clear(&work); + + for (j = 0; j < commits->length; j++) { + if (i == j || redundant[j]) + continue; + + filled_index[work.length] = j; + if ((error = git_vector_insert(&work, commits->contents[j])) < 0) + goto done; + } + + error = paint_down_to_common(&common, walk, commit, &work, minimum_generation); + if (error < 0) + goto done; + + if (commit->flags & PARENT2) + redundant[i] = 1; + + for (j = 0; j < work.length; j++) { + git_commit_list_node *w = work.contents[j]; + if (w->flags & PARENT1) + redundant[filled_index[j]] = 1; + } + + git_commit_list_free(&common); + + if ((error = clear_commit_marks(commit, ALL_FLAGS)) < 0 || + (error = clear_commit_marks_many(&work, ALL_FLAGS)) < 0) + goto done; + } + + for (i = 0; i < commits->length; ++i) { + if (redundant[i]) + commits->contents[i] = NULL; + } + +done: + git__free(redundant); + git__free(filled_index); + git_vector_free(&work); + return error; +} + +int git_merge__bases_many( + git_commit_list **out, + git_revwalk *walk, + git_commit_list_node *one, + git_vector *twos, + uint32_t minimum_generation) +{ + int error; + unsigned int i; + git_commit_list_node *two; + git_commit_list *result = NULL, *tmp = NULL; + + /* If there's only the one commit, there can be no merge bases */ + if (twos->length == 0) { + *out = NULL; + return 0; + } + + /* if the commit is repeated, we have a our merge base already */ + git_vector_foreach(twos, i, two) { + if (one == two) + return git_commit_list_insert(one, out) ? 0 : -1; + } + + if (git_commit_list_parse(walk, one) < 0) + return -1; + + error = paint_down_to_common(&result, walk, one, twos, minimum_generation); + if (error < 0) + return error; + + /* filter out any stale commits in the results */ + tmp = result; + result = NULL; + + while (tmp) { + git_commit_list_node *c = git_commit_list_pop(&tmp); + if (!(c->flags & STALE)) + if (git_commit_list_insert_by_date(c, &result) == NULL) + return -1; + } + + /* + * more than one merge base -- see if there are redundant merge + * bases and remove them + */ + if (result && result->next) { + git_vector redundant = GIT_VECTOR_INIT; + + while (result) + git_vector_insert(&redundant, git_commit_list_pop(&result)); + + if ((error = clear_commit_marks(one, ALL_FLAGS)) < 0 || + (error = clear_commit_marks_many(twos, ALL_FLAGS)) < 0 || + (error = remove_redundant(walk, &redundant, minimum_generation)) < 0) { + git_vector_free(&redundant); + return error; + } + + git_vector_foreach(&redundant, i, two) { + if (two != NULL) + git_commit_list_insert_by_date(two, &result); + } + + git_vector_free(&redundant); + } + + *out = result; + return 0; +} + +int git_repository_mergehead_foreach( + git_repository *repo, + git_repository_mergehead_foreach_cb cb, + void *payload) +{ + git_str merge_head_path = GIT_STR_INIT, merge_head_file = GIT_STR_INIT; + char *buffer, *line; + size_t line_num = 1; + git_oid oid; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(cb); + + if ((error = git_str_joinpath(&merge_head_path, repo->gitdir, + GIT_MERGE_HEAD_FILE)) < 0) + return error; + + if ((error = git_futils_readbuffer(&merge_head_file, + git_str_cstr(&merge_head_path))) < 0) + goto cleanup; + + buffer = merge_head_file.ptr; + + while ((line = git__strsep(&buffer, "\n")) != NULL) { + if (strlen(line) != GIT_OID_HEXSZ) { + git_error_set(GIT_ERROR_INVALID, "unable to parse OID - invalid length"); + error = -1; + goto cleanup; + } + + if ((error = git_oid_fromstr(&oid, line)) < 0) + goto cleanup; + + if ((error = cb(&oid, payload)) != 0) { + git_error_set_after_callback(error); + goto cleanup; + } + + ++line_num; + } + + if (*buffer) { + git_error_set(GIT_ERROR_MERGE, "no EOL at line %"PRIuZ, line_num); + error = -1; + goto cleanup; + } + +cleanup: + git_str_dispose(&merge_head_path); + git_str_dispose(&merge_head_file); + + return error; +} + +GIT_INLINE(int) index_entry_cmp(const git_index_entry *a, const git_index_entry *b) +{ + int value = 0; + + if (a->path == NULL) + return (b->path == NULL) ? 0 : 1; + + if ((value = a->mode - b->mode) == 0 && + (value = git_oid__cmp(&a->id, &b->id)) == 0) + value = strcmp(a->path, b->path); + + return value; +} + +/* Conflict resolution */ + +static int merge_conflict_resolve_trivial( + int *resolved, + git_merge_diff_list *diff_list, + const git_merge_diff *conflict) +{ + int ours_empty, theirs_empty; + int ours_changed, theirs_changed, ours_theirs_differ; + git_index_entry const *result = NULL; + int error = 0; + + GIT_ASSERT_ARG(resolved); + GIT_ASSERT_ARG(diff_list); + GIT_ASSERT_ARG(conflict); + + *resolved = 0; + + if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE || + conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) + return 0; + + if (conflict->our_status == GIT_DELTA_RENAMED || + conflict->their_status == GIT_DELTA_RENAMED) + return 0; + + ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry); + theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry); + + ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED); + theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED); + ours_theirs_differ = ours_changed && theirs_changed && + index_entry_cmp(&conflict->our_entry, &conflict->their_entry); + + /* + * Note: with only one ancestor, some cases are not distinct: + * + * 16: ancest:anc1/anc2, head:anc1, remote:anc2 = result:no merge + * 3: ancest:(empty)^, head:head, remote:(empty) = result:no merge + * 2: ancest:(empty)^, head:(empty), remote:remote = result:no merge + * + * Note that the two cases that take D/F conflicts into account + * specifically do not need to be explicitly tested, as D/F conflicts + * would fail the *empty* test: + * + * 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head + * 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote + * + * Note that many of these cases need not be explicitly tested, as + * they simply degrade to "all different" cases (eg, 11): + * + * 4: ancest:(empty)^, head:head, remote:remote = result:no merge + * 7: ancest:ancest+, head:(empty), remote:remote = result:no merge + * 9: ancest:ancest+, head:head, remote:(empty) = result:no merge + * 11: ancest:ancest+, head:head, remote:remote = result:no merge + */ + + /* 5ALT: ancest:*, head:head, remote:head = result:head */ + if (ours_changed && !ours_empty && !ours_theirs_differ) + result = &conflict->our_entry; + /* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */ + else if (ours_changed && ours_empty && theirs_empty) + *resolved = 0; + /* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */ + else if (ours_empty && !theirs_changed) + *resolved = 0; + /* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */ + else if (!ours_changed && theirs_empty) + *resolved = 0; + /* 13: ancest:ancest+, head:head, remote:ancest = result:head */ + else if (ours_changed && !theirs_changed) + result = &conflict->our_entry; + /* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */ + else if (!ours_changed && theirs_changed) + result = &conflict->their_entry; + else + *resolved = 0; + + if (result != NULL && + GIT_MERGE_INDEX_ENTRY_EXISTS(*result) && + (error = git_vector_insert(&diff_list->staged, (void *)result)) >= 0) + *resolved = 1; + + /* Note: trivial resolution does not update the REUC. */ + + return error; +} + +static int merge_conflict_resolve_one_removed( + int *resolved, + git_merge_diff_list *diff_list, + const git_merge_diff *conflict) +{ + int ours_empty, theirs_empty; + int ours_changed, theirs_changed; + int error = 0; + + GIT_ASSERT_ARG(resolved); + GIT_ASSERT_ARG(diff_list); + GIT_ASSERT_ARG(conflict); + + *resolved = 0; + + if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE || + conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) + return 0; + + ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry); + theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry); + + ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED); + theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED); + + /* Removed in both */ + if (ours_changed && ours_empty && theirs_empty) + *resolved = 1; + /* Removed in ours */ + else if (ours_empty && !theirs_changed) + *resolved = 1; + /* Removed in theirs */ + else if (!ours_changed && theirs_empty) + *resolved = 1; + + if (*resolved) + git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); + + return error; +} + +static int merge_conflict_resolve_one_renamed( + int *resolved, + git_merge_diff_list *diff_list, + const git_merge_diff *conflict) +{ + int ours_renamed, theirs_renamed; + int ours_changed, theirs_changed; + git_index_entry *merged; + int error = 0; + + GIT_ASSERT_ARG(resolved); + GIT_ASSERT_ARG(diff_list); + GIT_ASSERT_ARG(conflict); + + *resolved = 0; + + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) || + !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) + return 0; + + ours_renamed = (conflict->our_status == GIT_DELTA_RENAMED); + theirs_renamed = (conflict->their_status == GIT_DELTA_RENAMED); + + if (!ours_renamed && !theirs_renamed) + return 0; + + /* Reject one file in a 2->1 conflict */ + if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 || + conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 || + conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) + return 0; + + ours_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->our_entry.id) != 0) || + (conflict->ancestor_entry.mode != conflict->our_entry.mode); + + theirs_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->their_entry.id) != 0) || + (conflict->ancestor_entry.mode != conflict->their_entry.mode); + + /* if both are modified (and not to a common target) require a merge */ + if (ours_changed && theirs_changed && + git_oid__cmp(&conflict->our_entry.id, &conflict->their_entry.id) != 0) + return 0; + + if ((merged = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry))) == NULL) + return -1; + + if (ours_changed) + memcpy(merged, &conflict->our_entry, sizeof(git_index_entry)); + else + memcpy(merged, &conflict->their_entry, sizeof(git_index_entry)); + + if (ours_renamed) + merged->path = conflict->our_entry.path; + else + merged->path = conflict->their_entry.path; + + *resolved = 1; + + git_vector_insert(&diff_list->staged, merged); + git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); + + return error; +} + +static bool merge_conflict_can_resolve_contents( + const git_merge_diff *conflict) +{ + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) || + !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) + return false; + + /* Reject D/F conflicts */ + if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE) + return false; + + /* Reject submodules. */ + if (S_ISGITLINK(conflict->ancestor_entry.mode) || + S_ISGITLINK(conflict->our_entry.mode) || + S_ISGITLINK(conflict->their_entry.mode)) + return false; + + /* Reject link/file conflicts. */ + if ((S_ISLNK(conflict->ancestor_entry.mode) ^ + S_ISLNK(conflict->our_entry.mode)) || + (S_ISLNK(conflict->ancestor_entry.mode) ^ + S_ISLNK(conflict->their_entry.mode))) + return false; + + /* Reject name conflicts */ + if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 || + conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) + return false; + + if ((conflict->our_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && + (conflict->their_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && + strcmp(conflict->ancestor_entry.path, conflict->their_entry.path) != 0) + return false; + + return true; +} + +static int merge_conflict_invoke_driver( + git_index_entry **out, + const char *name, + git_merge_driver *driver, + git_merge_diff_list *diff_list, + git_merge_driver_source *src) +{ + git_index_entry *result; + git_buf buf = {0}; + const char *path; + uint32_t mode; + git_odb *odb = NULL; + git_oid oid; + int error; + + *out = NULL; + + if ((error = driver->apply(driver, &path, &mode, &buf, name, src)) < 0 || + (error = git_repository_odb(&odb, src->repo)) < 0 || + (error = git_odb_write(&oid, odb, buf.ptr, buf.size, GIT_OBJECT_BLOB)) < 0) + goto done; + + result = git_pool_mallocz(&diff_list->pool, sizeof(git_index_entry)); + GIT_ERROR_CHECK_ALLOC(result); + + git_oid_cpy(&result->id, &oid); + result->mode = mode; + result->file_size = (uint32_t)buf.size; + + result->path = git_pool_strdup(&diff_list->pool, path); + GIT_ERROR_CHECK_ALLOC(result->path); + + *out = result; + +done: + git_buf_dispose(&buf); + git_odb_free(odb); + + return error; +} + +static int merge_conflict_resolve_contents( + int *resolved, + git_merge_diff_list *diff_list, + const git_merge_diff *conflict, + const git_merge_options *merge_opts, + const git_merge_file_options *file_opts) +{ + git_merge_driver_source source = {0}; + git_merge_file_result result = {0}; + git_merge_driver *driver; + git_merge_driver__builtin builtin = {{0}}; + git_index_entry *merge_result; + git_odb *odb = NULL; + const char *name; + bool fallback = false; + int error; + + GIT_ASSERT_ARG(resolved); + GIT_ASSERT_ARG(diff_list); + GIT_ASSERT_ARG(conflict); + + *resolved = 0; + + if (!merge_conflict_can_resolve_contents(conflict)) + return 0; + + source.repo = diff_list->repo; + source.default_driver = merge_opts->default_driver; + source.file_opts = file_opts; + source.ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? + &conflict->ancestor_entry : NULL; + source.ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? + &conflict->our_entry : NULL; + source.theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? + &conflict->their_entry : NULL; + + if (file_opts->favor != GIT_MERGE_FILE_FAVOR_NORMAL) { + /* if the user requested a particular type of resolution (via the + * favor flag) then let that override the gitattributes and use + * the builtin driver. + */ + name = "text"; + builtin.base.apply = git_merge_driver__builtin_apply; + builtin.favor = file_opts->favor; + + driver = &builtin.base; + } else { + /* find the merge driver for this file */ + if ((error = git_merge_driver_for_source(&name, &driver, &source)) < 0) + goto done; + + if (driver == NULL) + fallback = true; + } + + if (driver) { + error = merge_conflict_invoke_driver(&merge_result, name, driver, + diff_list, &source); + + if (error == GIT_PASSTHROUGH) + fallback = true; + } + + if (fallback) { + error = merge_conflict_invoke_driver(&merge_result, "text", + &git_merge_driver__text.base, diff_list, &source); + } + + if (error < 0) { + if (error == GIT_EMERGECONFLICT) + error = 0; + + goto done; + } + + git_vector_insert(&diff_list->staged, merge_result); + git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); + + *resolved = 1; + +done: + git_merge_file_result_free(&result); + git_odb_free(odb); + + return error; +} + +static int merge_conflict_resolve( + int *out, + git_merge_diff_list *diff_list, + const git_merge_diff *conflict, + const git_merge_options *merge_opts, + const git_merge_file_options *file_opts) +{ + int resolved = 0; + int error = 0; + + *out = 0; + + if ((error = merge_conflict_resolve_trivial( + &resolved, diff_list, conflict)) < 0) + goto done; + + if (!resolved && (error = merge_conflict_resolve_one_removed( + &resolved, diff_list, conflict)) < 0) + goto done; + + if (!resolved && (error = merge_conflict_resolve_one_renamed( + &resolved, diff_list, conflict)) < 0) + goto done; + + if (!resolved && (error = merge_conflict_resolve_contents( + &resolved, diff_list, conflict, merge_opts, file_opts)) < 0) + goto done; + + *out = resolved; + +done: + return error; +} + +/* Rename detection and coalescing */ + +struct merge_diff_similarity { + unsigned char similarity; + size_t other_idx; +}; + +static int index_entry_similarity_calc( + void **out, + git_repository *repo, + git_index_entry *entry, + const git_merge_options *opts) +{ + git_blob *blob; + git_diff_file diff_file = {{{0}}}; + git_object_size_t blobsize; + int error; + + if (*out || *out == &cache_invalid_marker) + return 0; + + *out = NULL; + + if ((error = git_blob_lookup(&blob, repo, &entry->id)) < 0) + return error; + + git_oid_cpy(&diff_file.id, &entry->id); + diff_file.path = entry->path; + diff_file.size = entry->file_size; + diff_file.mode = entry->mode; + diff_file.flags = 0; + + blobsize = git_blob_rawsize(blob); + + /* file too big for rename processing */ + if (!git__is_sizet(blobsize)) + return 0; + + error = opts->metric->buffer_signature(out, &diff_file, + git_blob_rawcontent(blob), (size_t)blobsize, + opts->metric->payload); + if (error == GIT_EBUFS) + *out = &cache_invalid_marker; + + git_blob_free(blob); + + return error; +} + +static int index_entry_similarity_inexact( + git_repository *repo, + git_index_entry *a, + size_t a_idx, + git_index_entry *b, + size_t b_idx, + void **cache, + const git_merge_options *opts) +{ + int score = 0; + int error = 0; + + if (!GIT_MODE_ISBLOB(a->mode) || !GIT_MODE_ISBLOB(b->mode)) + return 0; + + /* update signature cache if needed */ + if ((error = index_entry_similarity_calc(&cache[a_idx], repo, a, opts)) < 0 || + (error = index_entry_similarity_calc(&cache[b_idx], repo, b, opts)) < 0) + return error; + + /* some metrics may not wish to process this file (too big / too small) */ + if (cache[a_idx] == &cache_invalid_marker || cache[b_idx] == &cache_invalid_marker) + return 0; + + /* compare signatures */ + if (opts->metric->similarity(&score, cache[a_idx], cache[b_idx], opts->metric->payload) < 0) + return -1; + + /* clip score */ + if (score < 0) + score = 0; + else if (score > 100) + score = 100; + + return score; +} + +/* Tracks deletes by oid for merge_diff_mark_similarity_exact(). This is a +* non-shrinking queue where next_pos is the next position to dequeue. +*/ +typedef struct { + git_array_t(size_t) arr; + size_t next_pos; + size_t first_entry; +} deletes_by_oid_queue; + +static void deletes_by_oid_free(git_oidmap *map) { + deletes_by_oid_queue *queue; + + if (!map) + return; + + git_oidmap_foreach_value(map, queue, { + git_array_clear(queue->arr); + }); + git_oidmap_free(map); +} + +static int deletes_by_oid_enqueue(git_oidmap *map, git_pool *pool, const git_oid *id, size_t idx) +{ + deletes_by_oid_queue *queue; + size_t *array_entry; + + if ((queue = git_oidmap_get(map, id)) == NULL) { + queue = git_pool_malloc(pool, sizeof(deletes_by_oid_queue)); + GIT_ERROR_CHECK_ALLOC(queue); + + git_array_init(queue->arr); + queue->next_pos = 0; + queue->first_entry = idx; + + if (git_oidmap_set(map, id, queue) < 0) + return -1; + } else { + array_entry = git_array_alloc(queue->arr); + GIT_ERROR_CHECK_ALLOC(array_entry); + *array_entry = idx; + } + + return 0; +} + +static int deletes_by_oid_dequeue(size_t *idx, git_oidmap *map, const git_oid *id) +{ + deletes_by_oid_queue *queue; + size_t *array_entry; + + if ((queue = git_oidmap_get(map, id)) == NULL) + return GIT_ENOTFOUND; + + if (queue->next_pos == 0) { + *idx = queue->first_entry; + } else { + array_entry = git_array_get(queue->arr, queue->next_pos - 1); + if (array_entry == NULL) + return GIT_ENOTFOUND; + + *idx = *array_entry; + } + + queue->next_pos++; + return 0; +} + +static int merge_diff_mark_similarity_exact( + git_merge_diff_list *diff_list, + struct merge_diff_similarity *similarity_ours, + struct merge_diff_similarity *similarity_theirs) +{ + size_t i, j; + git_merge_diff *conflict_src, *conflict_tgt; + git_oidmap *ours_deletes_by_oid = NULL, *theirs_deletes_by_oid = NULL; + int error = 0; + + if (git_oidmap_new(&ours_deletes_by_oid) < 0 || + git_oidmap_new(&theirs_deletes_by_oid) < 0) { + error = -1; + goto done; + } + + /* Build a map of object ids to conflicts */ + git_vector_foreach(&diff_list->conflicts, i, conflict_src) { + /* Items can be the source of a rename iff they have an item in the + * ancestor slot and lack an item in the ours or theirs slot. */ + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry)) + continue; + + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) { + error = deletes_by_oid_enqueue(ours_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); + if (error < 0) + goto done; + } + + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) { + error = deletes_by_oid_enqueue(theirs_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); + if (error < 0) + goto done; + } + } + + git_vector_foreach(&diff_list->conflicts, j, conflict_tgt) { + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->ancestor_entry)) + continue; + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry)) { + if (deletes_by_oid_dequeue(&i, ours_deletes_by_oid, &conflict_tgt->our_entry.id) == 0) { + similarity_ours[i].similarity = 100; + similarity_ours[i].other_idx = j; + + similarity_ours[j].similarity = 100; + similarity_ours[j].other_idx = i; + } + } + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry)) { + if (deletes_by_oid_dequeue(&i, theirs_deletes_by_oid, &conflict_tgt->their_entry.id) == 0) { + similarity_theirs[i].similarity = 100; + similarity_theirs[i].other_idx = j; + + similarity_theirs[j].similarity = 100; + similarity_theirs[j].other_idx = i; + } + } + } + +done: + deletes_by_oid_free(ours_deletes_by_oid); + deletes_by_oid_free(theirs_deletes_by_oid); + + return error; +} + +static int merge_diff_mark_similarity_inexact( + git_repository *repo, + git_merge_diff_list *diff_list, + struct merge_diff_similarity *similarity_ours, + struct merge_diff_similarity *similarity_theirs, + void **cache, + const git_merge_options *opts) +{ + size_t i, j; + git_merge_diff *conflict_src, *conflict_tgt; + int similarity; + + git_vector_foreach(&diff_list->conflicts, i, conflict_src) { + /* Items can be the source of a rename iff they have an item in the + * ancestor slot and lack an item in the ours or theirs slot. */ + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry) || + (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry) && + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry))) + continue; + + git_vector_foreach(&diff_list->conflicts, j, conflict_tgt) { + size_t our_idx = diff_list->conflicts.length + j; + size_t their_idx = (diff_list->conflicts.length * 2) + j; + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->ancestor_entry)) + continue; + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry) && + !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) { + similarity = index_entry_similarity_inexact(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->our_entry, our_idx, cache, opts); + + if (similarity == GIT_EBUFS) + continue; + else if (similarity < 0) + return similarity; + + if (similarity > similarity_ours[i].similarity && + similarity > similarity_ours[j].similarity) { + /* Clear previous best similarity */ + if (similarity_ours[i].similarity > 0) + similarity_ours[similarity_ours[i].other_idx].similarity = 0; + + if (similarity_ours[j].similarity > 0) + similarity_ours[similarity_ours[j].other_idx].similarity = 0; + + similarity_ours[i].similarity = similarity; + similarity_ours[i].other_idx = j; + + similarity_ours[j].similarity = similarity; + similarity_ours[j].other_idx = i; + } + } + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry) && + !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) { + similarity = index_entry_similarity_inexact(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->their_entry, their_idx, cache, opts); + + if (similarity > similarity_theirs[i].similarity && + similarity > similarity_theirs[j].similarity) { + /* Clear previous best similarity */ + if (similarity_theirs[i].similarity > 0) + similarity_theirs[similarity_theirs[i].other_idx].similarity = 0; + + if (similarity_theirs[j].similarity > 0) + similarity_theirs[similarity_theirs[j].other_idx].similarity = 0; + + similarity_theirs[i].similarity = similarity; + similarity_theirs[i].other_idx = j; + + similarity_theirs[j].similarity = similarity; + similarity_theirs[j].other_idx = i; + } + } + } + } + + return 0; +} + +/* + * Rename conflicts: + * + * Ancestor Ours Theirs + * + * 0a A A A No rename + * b A A* A No rename (ours was rewritten) + * c A A A* No rename (theirs rewritten) + * 1a A A B[A] Rename or rename/edit + * b A B[A] A (automergeable) + * 2 A B[A] B[A] Both renamed (automergeable) + * 3a A B[A] Rename/delete + * b A B[A] (same) + * 4a A B[A] B Rename/add [B~ours B~theirs] + * b A B B[A] (same) + * 5 A B[A] C[A] Both renamed ("1 -> 2") + * 6 A C[A] Both renamed ("2 -> 1") + * B C[B] [C~ours C~theirs] (automergeable) + */ +static void merge_diff_mark_rename_conflict( + git_merge_diff_list *diff_list, + struct merge_diff_similarity *similarity_ours, + bool ours_renamed, + size_t ours_source_idx, + struct merge_diff_similarity *similarity_theirs, + bool theirs_renamed, + size_t theirs_source_idx, + git_merge_diff *target, + const git_merge_options *opts) +{ + git_merge_diff *ours_source = NULL, *theirs_source = NULL; + + if (ours_renamed) + ours_source = diff_list->conflicts.contents[ours_source_idx]; + + if (theirs_renamed) + theirs_source = diff_list->conflicts.contents[theirs_source_idx]; + + /* Detect 2->1 conflicts */ + if (ours_renamed && theirs_renamed) { + /* Both renamed to the same target name. */ + if (ours_source_idx == theirs_source_idx) + ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED; + else { + ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1; + theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1; + } + } else if (ours_renamed) { + /* If our source was also renamed in theirs, this is a 1->2 */ + if (similarity_theirs[ours_source_idx].similarity >= opts->rename_threshold) + ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2; + + else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry)) { + ours_source->type = GIT_MERGE_DIFF_RENAMED_ADDED; + target->type = GIT_MERGE_DIFF_RENAMED_ADDED; + } + + else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(ours_source->their_entry)) + ours_source->type = GIT_MERGE_DIFF_RENAMED_DELETED; + + else if (ours_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED) + ours_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED; + } else if (theirs_renamed) { + /* If their source was also renamed in ours, this is a 1->2 */ + if (similarity_ours[theirs_source_idx].similarity >= opts->rename_threshold) + theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2; + + else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry)) { + theirs_source->type = GIT_MERGE_DIFF_RENAMED_ADDED; + target->type = GIT_MERGE_DIFF_RENAMED_ADDED; + } + + else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(theirs_source->our_entry)) + theirs_source->type = GIT_MERGE_DIFF_RENAMED_DELETED; + + else if (theirs_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED) + theirs_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED; + } +} + +GIT_INLINE(void) merge_diff_coalesce_rename( + git_index_entry *source_entry, + git_delta_t *source_status, + git_index_entry *target_entry, + git_delta_t *target_status) +{ + /* Coalesce the rename target into the rename source. */ + memcpy(source_entry, target_entry, sizeof(git_index_entry)); + *source_status = GIT_DELTA_RENAMED; + + memset(target_entry, 0x0, sizeof(git_index_entry)); + *target_status = GIT_DELTA_UNMODIFIED; +} + +static void merge_diff_list_coalesce_renames( + git_merge_diff_list *diff_list, + struct merge_diff_similarity *similarity_ours, + struct merge_diff_similarity *similarity_theirs, + const git_merge_options *opts) +{ + size_t i; + bool ours_renamed = 0, theirs_renamed = 0; + size_t ours_source_idx = 0, theirs_source_idx = 0; + git_merge_diff *ours_source, *theirs_source, *target; + + for (i = 0; i < diff_list->conflicts.length; i++) { + target = diff_list->conflicts.contents[i]; + + ours_renamed = 0; + theirs_renamed = 0; + + if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry) && + similarity_ours[i].similarity >= opts->rename_threshold) { + ours_source_idx = similarity_ours[i].other_idx; + + ours_source = diff_list->conflicts.contents[ours_source_idx]; + + merge_diff_coalesce_rename( + &ours_source->our_entry, + &ours_source->our_status, + &target->our_entry, + &target->our_status); + + similarity_ours[ours_source_idx].similarity = 0; + similarity_ours[i].similarity = 0; + + ours_renamed = 1; + } + + /* insufficient to determine direction */ + if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry) && + similarity_theirs[i].similarity >= opts->rename_threshold) { + theirs_source_idx = similarity_theirs[i].other_idx; + + theirs_source = diff_list->conflicts.contents[theirs_source_idx]; + + merge_diff_coalesce_rename( + &theirs_source->their_entry, + &theirs_source->their_status, + &target->their_entry, + &target->their_status); + + similarity_theirs[theirs_source_idx].similarity = 0; + similarity_theirs[i].similarity = 0; + + theirs_renamed = 1; + } + + merge_diff_mark_rename_conflict(diff_list, + similarity_ours, ours_renamed, ours_source_idx, + similarity_theirs, theirs_renamed, theirs_source_idx, + target, opts); + } +} + +static int merge_diff_empty(const git_vector *conflicts, size_t idx, void *p) +{ + git_merge_diff *conflict = conflicts->contents[idx]; + + GIT_UNUSED(p); + + return (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) && + !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) && + !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)); +} + +static void merge_diff_list_count_candidates( + git_merge_diff_list *diff_list, + size_t *src_count, + size_t *tgt_count) +{ + git_merge_diff *entry; + size_t i; + + *src_count = 0; + *tgt_count = 0; + + git_vector_foreach(&diff_list->conflicts, i, entry) { + if (GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry) && + (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->our_entry) || + !GIT_MERGE_INDEX_ENTRY_EXISTS(entry->their_entry))) + (*src_count)++; + else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry)) + (*tgt_count)++; + } +} + +int git_merge_diff_list__find_renames( + git_repository *repo, + git_merge_diff_list *diff_list, + const git_merge_options *opts) +{ + struct merge_diff_similarity *similarity_ours, *similarity_theirs; + void **cache = NULL; + size_t cache_size = 0; + size_t src_count, tgt_count, i; + int error = 0; + + GIT_ASSERT_ARG(diff_list); + GIT_ASSERT_ARG(opts); + + if ((opts->flags & GIT_MERGE_FIND_RENAMES) == 0 || + !diff_list->conflicts.length) + return 0; + + similarity_ours = git__calloc(diff_list->conflicts.length, + sizeof(struct merge_diff_similarity)); + GIT_ERROR_CHECK_ALLOC(similarity_ours); + + similarity_theirs = git__calloc(diff_list->conflicts.length, + sizeof(struct merge_diff_similarity)); + GIT_ERROR_CHECK_ALLOC(similarity_theirs); + + /* Calculate similarity between items that were deleted from the ancestor + * and added in the other branch. + */ + if ((error = merge_diff_mark_similarity_exact(diff_list, similarity_ours, similarity_theirs)) < 0) + goto done; + + if (opts->rename_threshold < 100 && diff_list->conflicts.length <= opts->target_limit) { + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&cache_size, diff_list->conflicts.length, 3); + cache = git__calloc(cache_size, sizeof(void *)); + GIT_ERROR_CHECK_ALLOC(cache); + + merge_diff_list_count_candidates(diff_list, &src_count, &tgt_count); + + if (src_count > opts->target_limit || tgt_count > opts->target_limit) { + /* TODO: report! */ + } else { + if ((error = merge_diff_mark_similarity_inexact( + repo, diff_list, similarity_ours, similarity_theirs, cache, opts)) < 0) + goto done; + } + } + + /* For entries that are appropriately similar, merge the new name's entry + * into the old name. + */ + merge_diff_list_coalesce_renames(diff_list, similarity_ours, similarity_theirs, opts); + + /* And remove any entries that were merged and are now empty. */ + git_vector_remove_matching(&diff_list->conflicts, merge_diff_empty, NULL); + +done: + if (cache != NULL) { + for (i = 0; i < cache_size; ++i) { + if (cache[i] != NULL && cache[i] != &cache_invalid_marker) + opts->metric->free_signature(cache[i], opts->metric->payload); + } + + git__free(cache); + } + + git__free(similarity_ours); + git__free(similarity_theirs); + + return error; +} + +/* Directory/file conflict handling */ + +GIT_INLINE(const char *) merge_diff_path( + const git_merge_diff *conflict) +{ + if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry)) + return conflict->ancestor_entry.path; + else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry)) + return conflict->our_entry.path; + else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) + return conflict->their_entry.path; + + return NULL; +} + +GIT_INLINE(bool) merge_diff_any_side_added_or_modified( + const git_merge_diff *conflict) +{ + if (conflict->our_status == GIT_DELTA_ADDED || + conflict->our_status == GIT_DELTA_MODIFIED || + conflict->their_status == GIT_DELTA_ADDED || + conflict->their_status == GIT_DELTA_MODIFIED) + return true; + + return false; +} + +GIT_INLINE(bool) path_is_prefixed(const char *parent, const char *child) +{ + size_t child_len = strlen(child); + size_t parent_len = strlen(parent); + + if (child_len < parent_len || + strncmp(parent, child, parent_len) != 0) + return 0; + + return (child[parent_len] == '/'); +} + +GIT_INLINE(int) merge_diff_detect_df_conflict( + struct merge_diff_df_data *df_data, + git_merge_diff *conflict) +{ + const char *cur_path = merge_diff_path(conflict); + + /* Determine if this is a D/F conflict or the child of one */ + if (df_data->df_path && + path_is_prefixed(df_data->df_path, cur_path)) + conflict->type = GIT_MERGE_DIFF_DF_CHILD; + else if(df_data->df_path) + df_data->df_path = NULL; + else if (df_data->prev_path && + merge_diff_any_side_added_or_modified(df_data->prev_conflict) && + merge_diff_any_side_added_or_modified(conflict) && + path_is_prefixed(df_data->prev_path, cur_path)) { + conflict->type = GIT_MERGE_DIFF_DF_CHILD; + + df_data->prev_conflict->type = GIT_MERGE_DIFF_DIRECTORY_FILE; + df_data->df_path = df_data->prev_path; + } + + df_data->prev_path = cur_path; + df_data->prev_conflict = conflict; + + return 0; +} + +/* Conflict handling */ + +GIT_INLINE(int) merge_diff_detect_type( + git_merge_diff *conflict) +{ + if (conflict->our_status == GIT_DELTA_ADDED && + conflict->their_status == GIT_DELTA_ADDED) + conflict->type = GIT_MERGE_DIFF_BOTH_ADDED; + else if (conflict->our_status == GIT_DELTA_MODIFIED && + conflict->their_status == GIT_DELTA_MODIFIED) + conflict->type = GIT_MERGE_DIFF_BOTH_MODIFIED; + else if (conflict->our_status == GIT_DELTA_DELETED && + conflict->their_status == GIT_DELTA_DELETED) + conflict->type = GIT_MERGE_DIFF_BOTH_DELETED; + else if (conflict->our_status == GIT_DELTA_MODIFIED && + conflict->their_status == GIT_DELTA_DELETED) + conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED; + else if (conflict->our_status == GIT_DELTA_DELETED && + conflict->their_status == GIT_DELTA_MODIFIED) + conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED; + else + conflict->type = GIT_MERGE_DIFF_NONE; + + return 0; +} + +GIT_INLINE(int) index_entry_dup_pool( + git_index_entry *out, + git_pool *pool, + const git_index_entry *src) +{ + if (src != NULL) { + memcpy(out, src, sizeof(git_index_entry)); + if ((out->path = git_pool_strdup(pool, src->path)) == NULL) + return -1; + } + + return 0; +} + +GIT_INLINE(int) merge_delta_type_from_index_entries( + const git_index_entry *ancestor, + const git_index_entry *other) +{ + if (ancestor == NULL && other == NULL) + return GIT_DELTA_UNMODIFIED; + else if (ancestor == NULL && other != NULL) + return GIT_DELTA_ADDED; + else if (ancestor != NULL && other == NULL) + return GIT_DELTA_DELETED; + else if (S_ISDIR(ancestor->mode) ^ S_ISDIR(other->mode)) + return GIT_DELTA_TYPECHANGE; + else if(S_ISLNK(ancestor->mode) ^ S_ISLNK(other->mode)) + return GIT_DELTA_TYPECHANGE; + else if (git_oid__cmp(&ancestor->id, &other->id) || + ancestor->mode != other->mode) + return GIT_DELTA_MODIFIED; + + return GIT_DELTA_UNMODIFIED; +} + +static git_merge_diff *merge_diff_from_index_entries( + git_merge_diff_list *diff_list, + const git_index_entry **entries) +{ + git_merge_diff *conflict; + git_pool *pool = &diff_list->pool; + + if ((conflict = git_pool_mallocz(pool, sizeof(git_merge_diff))) == NULL) + return NULL; + + if (index_entry_dup_pool(&conflict->ancestor_entry, pool, entries[TREE_IDX_ANCESTOR]) < 0 || + index_entry_dup_pool(&conflict->our_entry, pool, entries[TREE_IDX_OURS]) < 0 || + index_entry_dup_pool(&conflict->their_entry, pool, entries[TREE_IDX_THEIRS]) < 0) + return NULL; + + conflict->our_status = merge_delta_type_from_index_entries( + entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_OURS]); + conflict->their_status = merge_delta_type_from_index_entries( + entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_THEIRS]); + + return conflict; +} + +/* Merge trees */ + +static int merge_diff_list_insert_conflict( + git_merge_diff_list *diff_list, + struct merge_diff_df_data *merge_df_data, + const git_index_entry *tree_items[3]) +{ + git_merge_diff *conflict; + + if ((conflict = merge_diff_from_index_entries(diff_list, tree_items)) == NULL || + merge_diff_detect_type(conflict) < 0 || + merge_diff_detect_df_conflict(merge_df_data, conflict) < 0 || + git_vector_insert(&diff_list->conflicts, conflict) < 0) + return -1; + + return 0; +} + +static int merge_diff_list_insert_unmodified( + git_merge_diff_list *diff_list, + const git_index_entry *tree_items[3]) +{ + int error = 0; + git_index_entry *entry; + + entry = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + if ((error = index_entry_dup_pool(entry, &diff_list->pool, tree_items[0])) >= 0) + error = git_vector_insert(&diff_list->staged, entry); + + return error; +} + +struct merge_diff_find_data { + git_merge_diff_list *diff_list; + struct merge_diff_df_data df_data; +}; + +static int queue_difference(const git_index_entry **entries, void *data) +{ + struct merge_diff_find_data *find_data = data; + bool item_modified = false; + size_t i; + + if (!entries[0] || !entries[1] || !entries[2]) { + item_modified = true; + } else { + for (i = 1; i < 3; i++) { + if (index_entry_cmp(entries[0], entries[i]) != 0) { + item_modified = true; + break; + } + } + } + + return item_modified ? + merge_diff_list_insert_conflict( + find_data->diff_list, &find_data->df_data, entries) : + merge_diff_list_insert_unmodified(find_data->diff_list, entries); +} + +int git_merge_diff_list__find_differences( + git_merge_diff_list *diff_list, + git_iterator *ancestor_iter, + git_iterator *our_iter, + git_iterator *their_iter) +{ + git_iterator *iterators[3] = { ancestor_iter, our_iter, their_iter }; + struct merge_diff_find_data find_data = { diff_list }; + + return git_iterator_walk(iterators, 3, queue_difference, &find_data); +} + +git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo) +{ + git_merge_diff_list *diff_list = git__calloc(1, sizeof(git_merge_diff_list)); + + if (diff_list == NULL) + return NULL; + + diff_list->repo = repo; + + + if (git_pool_init(&diff_list->pool, 1) < 0 || + git_vector_init(&diff_list->staged, 0, NULL) < 0 || + git_vector_init(&diff_list->conflicts, 0, NULL) < 0 || + git_vector_init(&diff_list->resolved, 0, NULL) < 0) { + git_merge_diff_list__free(diff_list); + return NULL; + } + + return diff_list; +} + +void git_merge_diff_list__free(git_merge_diff_list *diff_list) +{ + if (!diff_list) + return; + + git_vector_free(&diff_list->staged); + git_vector_free(&diff_list->conflicts); + git_vector_free(&diff_list->resolved); + git_pool_clear(&diff_list->pool); + git__free(diff_list); +} + +static int merge_normalize_opts( + git_repository *repo, + git_merge_options *opts, + const git_merge_options *given) +{ + git_config *cfg = NULL; + git_config_entry *entry = NULL; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(opts); + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + return error; + + if (given != NULL) { + memcpy(opts, given, sizeof(git_merge_options)); + } else { + git_merge_options init = GIT_MERGE_OPTIONS_INIT; + memcpy(opts, &init, sizeof(init)); + } + + if ((opts->flags & GIT_MERGE_FIND_RENAMES) && !opts->rename_threshold) + opts->rename_threshold = GIT_MERGE_DEFAULT_RENAME_THRESHOLD; + + if (given && given->default_driver) { + opts->default_driver = git__strdup(given->default_driver); + GIT_ERROR_CHECK_ALLOC(opts->default_driver); + } else { + error = git_config_get_entry(&entry, cfg, "merge.default"); + + if (error == 0) { + opts->default_driver = git__strdup(entry->value); + GIT_ERROR_CHECK_ALLOC(opts->default_driver); + } else if (error == GIT_ENOTFOUND) { + error = 0; + } else { + goto done; + } + } + + if (!opts->target_limit) { + int limit = git_config__get_int_force(cfg, "merge.renamelimit", 0); + + if (!limit) + limit = git_config__get_int_force(cfg, "diff.renamelimit", 0); + + opts->target_limit = (limit <= 0) ? + GIT_MERGE_DEFAULT_TARGET_LIMIT : (unsigned int)limit; + } + + /* assign the internal metric with whitespace flag as payload */ + if (!opts->metric) { + opts->metric = git__malloc(sizeof(git_diff_similarity_metric)); + GIT_ERROR_CHECK_ALLOC(opts->metric); + + opts->metric->file_signature = git_diff_find_similar__hashsig_for_file; + opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf; + opts->metric->free_signature = git_diff_find_similar__hashsig_free; + opts->metric->similarity = git_diff_find_similar__calc_similarity; + opts->metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE; + } + +done: + git_config_entry_free(entry); + return error; +} + + +static int merge_index_insert_reuc( + git_index *index, + size_t idx, + const git_index_entry *entry) +{ + const git_index_reuc_entry *reuc; + int mode[3] = { 0, 0, 0 }; + git_oid const *oid[3] = { NULL, NULL, NULL }; + size_t i; + + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(*entry)) + return 0; + + if ((reuc = git_index_reuc_get_bypath(index, entry->path)) != NULL) { + for (i = 0; i < 3; i++) { + mode[i] = reuc->mode[i]; + oid[i] = &reuc->oid[i]; + } + } + + mode[idx] = entry->mode; + oid[idx] = &entry->id; + + return git_index_reuc_add(index, entry->path, + mode[0], oid[0], mode[1], oid[1], mode[2], oid[2]); +} + +static int index_update_reuc(git_index *index, git_merge_diff_list *diff_list) +{ + int error; + size_t i; + git_merge_diff *conflict; + + /* Add each entry in the resolved conflict to the REUC independently, since + * the paths may differ due to renames. */ + git_vector_foreach(&diff_list->resolved, i, conflict) { + const git_index_entry *ancestor = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? + &conflict->ancestor_entry : NULL; + + const git_index_entry *ours = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? + &conflict->our_entry : NULL; + + const git_index_entry *theirs = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? + &conflict->their_entry : NULL; + + if (ancestor != NULL && + (error = merge_index_insert_reuc(index, TREE_IDX_ANCESTOR, ancestor)) < 0) + return error; + + if (ours != NULL && + (error = merge_index_insert_reuc(index, TREE_IDX_OURS, ours)) < 0) + return error; + + if (theirs != NULL && + (error = merge_index_insert_reuc(index, TREE_IDX_THEIRS, theirs)) < 0) + return error; + } + + return 0; +} + +static int index_from_diff_list(git_index **out, + git_merge_diff_list *diff_list, bool skip_reuc) +{ + git_index *index; + size_t i; + git_merge_diff *conflict; + int error = 0; + + *out = NULL; + + if ((error = git_index_new(&index)) < 0) + return error; + + if ((error = git_index__fill(index, &diff_list->staged)) < 0) + goto on_error; + + git_vector_foreach(&diff_list->conflicts, i, conflict) { + const git_index_entry *ancestor = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? + &conflict->ancestor_entry : NULL; + + const git_index_entry *ours = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? + &conflict->our_entry : NULL; + + const git_index_entry *theirs = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? + &conflict->their_entry : NULL; + + if ((error = git_index_conflict_add(index, ancestor, ours, theirs)) < 0) + goto on_error; + } + + /* Add each rename entry to the rename portion of the index. */ + git_vector_foreach(&diff_list->conflicts, i, conflict) { + const char *ancestor_path, *our_path, *their_path; + + if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry)) + continue; + + ancestor_path = conflict->ancestor_entry.path; + + our_path = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? + conflict->our_entry.path : NULL; + + their_path = + GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? + conflict->their_entry.path : NULL; + + if ((our_path && strcmp(ancestor_path, our_path) != 0) || + (their_path && strcmp(ancestor_path, their_path) != 0)) { + if ((error = git_index_name_add(index, ancestor_path, our_path, their_path)) < 0) + goto on_error; + } + } + + if (!skip_reuc) { + if ((error = index_update_reuc(index, diff_list)) < 0) + goto on_error; + } + + *out = index; + return 0; + +on_error: + git_index_free(index); + return error; +} + +static git_iterator *iterator_given_or_empty(git_iterator **empty, git_iterator *given) +{ + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + + if (given) + return given; + + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if (git_iterator_for_nothing(empty, &opts) < 0) + return NULL; + + return *empty; +} + +int git_merge__iterators( + git_index **out, + git_repository *repo, + git_iterator *ancestor_iter, + git_iterator *our_iter, + git_iterator *theirs_iter, + const git_merge_options *given_opts) +{ + git_iterator *empty_ancestor = NULL, + *empty_ours = NULL, + *empty_theirs = NULL; + git_merge_diff_list *diff_list; + git_merge_options opts; + git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_diff *conflict; + git_vector changes; + size_t i; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + GIT_ERROR_CHECK_VERSION( + given_opts, GIT_MERGE_OPTIONS_VERSION, "git_merge_options"); + + if ((error = merge_normalize_opts(repo, &opts, given_opts)) < 0) + return error; + + file_opts.favor = opts.file_favor; + file_opts.flags = opts.file_flags; + + /* use the git-inspired labels when virtual base building */ + if (opts.flags & GIT_MERGE_VIRTUAL_BASE) { + file_opts.ancestor_label = "merged common ancestors"; + file_opts.our_label = "Temporary merge branch 1"; + file_opts.their_label = "Temporary merge branch 2"; + file_opts.flags |= GIT_MERGE_FILE_ACCEPT_CONFLICTS; + file_opts.marker_size = GIT_MERGE_CONFLICT_MARKER_SIZE + 2; + } + + diff_list = git_merge_diff_list__alloc(repo); + GIT_ERROR_CHECK_ALLOC(diff_list); + + ancestor_iter = iterator_given_or_empty(&empty_ancestor, ancestor_iter); + our_iter = iterator_given_or_empty(&empty_ours, our_iter); + theirs_iter = iterator_given_or_empty(&empty_theirs, theirs_iter); + + if ((error = git_merge_diff_list__find_differences( + diff_list, ancestor_iter, our_iter, theirs_iter)) < 0 || + (error = git_merge_diff_list__find_renames(repo, diff_list, &opts)) < 0) + goto done; + + memcpy(&changes, &diff_list->conflicts, sizeof(git_vector)); + git_vector_clear(&diff_list->conflicts); + + git_vector_foreach(&changes, i, conflict) { + int resolved = 0; + + if ((error = merge_conflict_resolve( + &resolved, diff_list, conflict, &opts, &file_opts)) < 0) + goto done; + + if (!resolved) { + if ((opts.flags & GIT_MERGE_FAIL_ON_CONFLICT)) { + git_error_set(GIT_ERROR_MERGE, "merge conflicts exist"); + error = GIT_EMERGECONFLICT; + goto done; + } + + git_vector_insert(&diff_list->conflicts, conflict); + } + } + + error = index_from_diff_list(out, diff_list, + (opts.flags & GIT_MERGE_SKIP_REUC)); + +done: + if (!given_opts || !given_opts->metric) + git__free(opts.metric); + + git__free((char *)opts.default_driver); + + git_merge_diff_list__free(diff_list); + git_iterator_free(empty_ancestor); + git_iterator_free(empty_ours); + git_iterator_free(empty_theirs); + + return error; +} + +int git_merge_trees( + git_index **out, + git_repository *repo, + const git_tree *ancestor_tree, + const git_tree *our_tree, + const git_tree *their_tree, + const git_merge_options *merge_opts) +{ + git_iterator *ancestor_iter = NULL, *our_iter = NULL, *their_iter = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + /* if one side is treesame to the ancestor, take the other side */ + if (ancestor_tree && merge_opts && (merge_opts->flags & GIT_MERGE_SKIP_REUC)) { + const git_tree *result = NULL; + const git_oid *ancestor_tree_id = git_tree_id(ancestor_tree); + + if (our_tree && !git_oid_cmp(ancestor_tree_id, git_tree_id(our_tree))) + result = their_tree; + else if (their_tree && !git_oid_cmp(ancestor_tree_id, git_tree_id(their_tree))) + result = our_tree; + + if (result) { + if ((error = git_index_new(out)) == 0) + error = git_index_read_tree(*out, result); + + return error; + } + } + + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_tree( + &ancestor_iter, (git_tree *)ancestor_tree, &iter_opts)) < 0 || + (error = git_iterator_for_tree( + &our_iter, (git_tree *)our_tree, &iter_opts)) < 0 || + (error = git_iterator_for_tree( + &their_iter, (git_tree *)their_tree, &iter_opts)) < 0) + goto done; + + error = git_merge__iterators( + out, repo, ancestor_iter, our_iter, their_iter, merge_opts); + +done: + git_iterator_free(ancestor_iter); + git_iterator_free(our_iter); + git_iterator_free(their_iter); + + return error; +} + +static int merge_annotated_commits( + git_index **index_out, + git_annotated_commit **base_out, + git_repository *repo, + git_annotated_commit *our_commit, + git_annotated_commit *their_commit, + size_t recursion_level, + const git_merge_options *opts); + +GIT_INLINE(int) insert_head_ids( + git_array_oid_t *ids, + const git_annotated_commit *annotated_commit) +{ + git_oid *id; + size_t i; + + if (annotated_commit->type == GIT_ANNOTATED_COMMIT_REAL) { + id = git_array_alloc(*ids); + GIT_ERROR_CHECK_ALLOC(id); + + git_oid_cpy(id, git_commit_id(annotated_commit->commit)); + } else { + for (i = 0; i < annotated_commit->parents.size; i++) { + id = git_array_alloc(*ids); + GIT_ERROR_CHECK_ALLOC(id); + + git_oid_cpy(id, &annotated_commit->parents.ptr[i]); + } + } + + return 0; +} + +static int create_virtual_base( + git_annotated_commit **out, + git_repository *repo, + git_annotated_commit *one, + git_annotated_commit *two, + const git_merge_options *opts, + size_t recursion_level) +{ + git_annotated_commit *result = NULL; + git_index *index = NULL; + git_merge_options virtual_opts = GIT_MERGE_OPTIONS_INIT; + + /* Conflicts in the merge base creation do not propagate to conflicts + * in the result; the conflicted base will act as the common ancestor. + */ + if (opts) + memcpy(&virtual_opts, opts, sizeof(git_merge_options)); + + virtual_opts.flags &= ~GIT_MERGE_FAIL_ON_CONFLICT; + virtual_opts.flags |= GIT_MERGE_VIRTUAL_BASE; + + if ((merge_annotated_commits(&index, NULL, repo, one, two, + recursion_level + 1, &virtual_opts)) < 0) + return -1; + + result = git__calloc(1, sizeof(git_annotated_commit)); + GIT_ERROR_CHECK_ALLOC(result); + result->type = GIT_ANNOTATED_COMMIT_VIRTUAL; + result->index = index; + + if (insert_head_ids(&result->parents, one) < 0 || + insert_head_ids(&result->parents, two) < 0) { + git_annotated_commit_free(result); + return -1; + } + + *out = result; + return 0; +} + +static int compute_base( + git_annotated_commit **out, + git_repository *repo, + const git_annotated_commit *one, + const git_annotated_commit *two, + const git_merge_options *given_opts, + size_t recursion_level) +{ + git_array_oid_t head_ids = GIT_ARRAY_INIT; + git_oidarray bases = {0}; + git_annotated_commit *base = NULL, *other = NULL, *new_base = NULL; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + size_t i, base_count; + int error; + + *out = NULL; + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_merge_options)); + + /* With more than two commits, merge_bases_many finds the base of + * the first commit and a hypothetical merge of the others. Since + * "one" may itself be a virtual commit, which insert_head_ids + * substitutes multiple ancestors for, it needs to be added + * after "two" which is always a single real commit. + */ + if ((error = insert_head_ids(&head_ids, two)) < 0 || + (error = insert_head_ids(&head_ids, one)) < 0 || + (error = git_merge_bases_many(&bases, repo, + head_ids.size, head_ids.ptr)) < 0) + goto done; + + base_count = (opts.flags & GIT_MERGE_NO_RECURSIVE) ? 0 : bases.count; + + if (base_count) + git_oidarray__reverse(&bases); + + if ((error = git_annotated_commit_lookup(&base, repo, &bases.ids[0])) < 0) + goto done; + + for (i = 1; i < base_count; i++) { + recursion_level++; + + if (opts.recursion_limit && recursion_level > opts.recursion_limit) + break; + + if ((error = git_annotated_commit_lookup(&other, repo, + &bases.ids[i])) < 0 || + (error = create_virtual_base(&new_base, repo, base, other, &opts, + recursion_level)) < 0) + goto done; + + git_annotated_commit_free(base); + git_annotated_commit_free(other); + + base = new_base; + new_base = NULL; + other = NULL; + } + +done: + if (error == 0) + *out = base; + else + git_annotated_commit_free(base); + + git_annotated_commit_free(other); + git_annotated_commit_free(new_base); + git_oidarray_dispose(&bases); + git_array_clear(head_ids); + return error; +} + +static int iterator_for_annotated_commit( + git_iterator **out, + git_annotated_commit *commit) +{ + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + int error; + + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if (commit == NULL) { + error = git_iterator_for_nothing(out, &opts); + } else if (commit->type == GIT_ANNOTATED_COMMIT_VIRTUAL) { + error = git_iterator_for_index(out, git_index_owner(commit->index), commit->index, &opts); + } else { + if (!commit->tree && + (error = git_commit_tree(&commit->tree, commit->commit)) < 0) + goto done; + + error = git_iterator_for_tree(out, commit->tree, &opts); + } + +done: + return error; +} + +static int merge_annotated_commits( + git_index **index_out, + git_annotated_commit **base_out, + git_repository *repo, + git_annotated_commit *ours, + git_annotated_commit *theirs, + size_t recursion_level, + const git_merge_options *opts) +{ + git_annotated_commit *base = NULL; + git_iterator *base_iter = NULL, *our_iter = NULL, *their_iter = NULL; + int error; + + if ((error = compute_base(&base, repo, ours, theirs, opts, + recursion_level)) < 0) { + + if (error != GIT_ENOTFOUND) + goto done; + + git_error_clear(); + } + + if ((error = iterator_for_annotated_commit(&base_iter, base)) < 0 || + (error = iterator_for_annotated_commit(&our_iter, ours)) < 0 || + (error = iterator_for_annotated_commit(&their_iter, theirs)) < 0 || + (error = git_merge__iterators(index_out, repo, base_iter, our_iter, + their_iter, opts)) < 0) + goto done; + + if (base_out) { + *base_out = base; + base = NULL; + } + +done: + git_annotated_commit_free(base); + git_iterator_free(base_iter); + git_iterator_free(our_iter); + git_iterator_free(their_iter); + return error; +} + + +int git_merge_commits( + git_index **out, + git_repository *repo, + const git_commit *our_commit, + const git_commit *their_commit, + const git_merge_options *opts) +{ + git_annotated_commit *ours = NULL, *theirs = NULL, *base = NULL; + int error = 0; + + if ((error = git_annotated_commit_from_commit(&ours, (git_commit *)our_commit)) < 0 || + (error = git_annotated_commit_from_commit(&theirs, (git_commit *)their_commit)) < 0) + goto done; + + error = merge_annotated_commits(out, &base, repo, ours, theirs, 0, opts); + +done: + git_annotated_commit_free(ours); + git_annotated_commit_free(theirs); + git_annotated_commit_free(base); + return error; +} + +/* Merge setup / cleanup */ + +static int write_merge_head( + git_repository *repo, + const git_annotated_commit *heads[], + size_t heads_len) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + size_t i; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(heads); + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_HEAD_FILE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) < 0) + goto cleanup; + + for (i = 0; i < heads_len; i++) { + if ((error = git_filebuf_printf(&file, "%s\n", heads[i]->id_str)) < 0) + goto cleanup; + } + + error = git_filebuf_commit(&file); + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int write_merge_mode(git_repository *repo) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + int error = 0; + + GIT_ASSERT_ARG(repo); + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MODE_FILE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) < 0) + goto cleanup; + + if ((error = git_filebuf_write(&file, "no-ff", 5)) < 0) + goto cleanup; + + error = git_filebuf_commit(&file); + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +struct merge_msg_entry { + const git_annotated_commit *merge_head; + bool written; +}; + +static int msg_entry_is_branch( + const struct merge_msg_entry *entry, + git_vector *entries) +{ + GIT_UNUSED(entries); + + return (entry->written == 0 && + entry->merge_head->remote_url == NULL && + entry->merge_head->ref_name != NULL && + git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0); +} + +static int msg_entry_is_tracking( + const struct merge_msg_entry *entry, + git_vector *entries) +{ + GIT_UNUSED(entries); + + return (entry->written == 0 && + entry->merge_head->remote_url == NULL && + entry->merge_head->ref_name != NULL && + git__strncmp(GIT_REFS_REMOTES_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_REMOTES_DIR)) == 0); +} + +static int msg_entry_is_tag( + const struct merge_msg_entry *entry, + git_vector *entries) +{ + GIT_UNUSED(entries); + + return (entry->written == 0 && + entry->merge_head->remote_url == NULL && + entry->merge_head->ref_name != NULL && + git__strncmp(GIT_REFS_TAGS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_TAGS_DIR)) == 0); +} + +static int msg_entry_is_remote( + const struct merge_msg_entry *entry, + git_vector *entries) +{ + if (entry->written == 0 && + entry->merge_head->remote_url != NULL && + entry->merge_head->ref_name != NULL && + git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0) + { + struct merge_msg_entry *existing; + + /* Match only branches from the same remote */ + if (entries->length == 0) + return 1; + + existing = git_vector_get(entries, 0); + + return (git__strcmp(existing->merge_head->remote_url, + entry->merge_head->remote_url) == 0); + } + + return 0; +} + +static int msg_entry_is_oid( + const struct merge_msg_entry *merge_msg_entry) +{ + return (merge_msg_entry->written == 0 && + merge_msg_entry->merge_head->ref_name == NULL && + merge_msg_entry->merge_head->remote_url == NULL); +} + +static int merge_msg_entry_written( + const struct merge_msg_entry *merge_msg_entry) +{ + return (merge_msg_entry->written == 1); +} + +static int merge_msg_entries( + git_vector *v, + const struct merge_msg_entry *entries, + size_t len, + int (*match)(const struct merge_msg_entry *entry, git_vector *entries)) +{ + size_t i; + int matches, total = 0; + + git_vector_clear(v); + + for (i = 0; i < len; i++) { + if ((matches = match(&entries[i], v)) < 0) + return matches; + else if (!matches) + continue; + + git_vector_insert(v, (struct merge_msg_entry *)&entries[i]); + total++; + } + + return total; +} + +static int merge_msg_write_entries( + git_filebuf *file, + git_vector *entries, + const char *item_name, + const char *item_plural_name, + size_t ref_name_skip, + const char *source, + char sep) +{ + struct merge_msg_entry *entry; + size_t i; + int error = 0; + + if (entries->length == 0) + return 0; + + if (sep && (error = git_filebuf_printf(file, "%c ", sep)) < 0) + goto done; + + if ((error = git_filebuf_printf(file, "%s ", + (entries->length == 1) ? item_name : item_plural_name)) < 0) + goto done; + + git_vector_foreach(entries, i, entry) { + if (i > 0 && + (error = git_filebuf_printf(file, "%s", (i == entries->length - 1) ? " and " : ", ")) < 0) + goto done; + + if ((error = git_filebuf_printf(file, "'%s'", entry->merge_head->ref_name + ref_name_skip)) < 0) + goto done; + + entry->written = 1; + } + + if (source) + error = git_filebuf_printf(file, " of %s", source); + +done: + return error; +} + +static int merge_msg_write_branches( + git_filebuf *file, + git_vector *entries, + char sep) +{ + return merge_msg_write_entries(file, entries, + "branch", "branches", strlen(GIT_REFS_HEADS_DIR), NULL, sep); +} + +static int merge_msg_write_tracking( + git_filebuf *file, + git_vector *entries, + char sep) +{ + return merge_msg_write_entries(file, entries, + "remote-tracking branch", "remote-tracking branches", 0, NULL, sep); +} + +static int merge_msg_write_tags( + git_filebuf *file, + git_vector *entries, + char sep) +{ + return merge_msg_write_entries(file, entries, + "tag", "tags", strlen(GIT_REFS_TAGS_DIR), NULL, sep); +} + +static int merge_msg_write_remotes( + git_filebuf *file, + git_vector *entries, + char sep) +{ + const char *source; + + if (entries->length == 0) + return 0; + + source = ((struct merge_msg_entry *)entries->contents[0])->merge_head->remote_url; + + return merge_msg_write_entries(file, entries, + "branch", "branches", strlen(GIT_REFS_HEADS_DIR), source, sep); +} + +static int write_merge_msg( + git_repository *repo, + const git_annotated_commit *heads[], + size_t heads_len) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + struct merge_msg_entry *entries; + git_vector matching = GIT_VECTOR_INIT; + size_t i; + char sep = 0; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(heads); + + entries = git__calloc(heads_len, sizeof(struct merge_msg_entry)); + GIT_ERROR_CHECK_ALLOC(entries); + + if (git_vector_init(&matching, heads_len, NULL) < 0) { + git__free(entries); + return -1; + } + + for (i = 0; i < heads_len; i++) + entries[i].merge_head = heads[i]; + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) < 0 || + (error = git_filebuf_write(&file, "Merge ", 6)) < 0) + goto cleanup; + + /* + * This is to emulate the format of MERGE_MSG by core git. + * + * Core git will write all the commits specified by OID, in the order + * provided, until the first named branch or tag is reached, at which + * point all branches will be written in the order provided, then all + * tags, then all remote tracking branches and finally all commits that + * were specified by OID that were not already written. + * + * Yes. Really. + */ + for (i = 0; i < heads_len; i++) { + if (!msg_entry_is_oid(&entries[i])) + break; + + if ((error = git_filebuf_printf(&file, + "%scommit '%s'", (i > 0) ? "; " : "", + entries[i].merge_head->id_str)) < 0) + goto cleanup; + + entries[i].written = 1; + } + + if (i) + sep = ';'; + + if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_branch)) < 0 || + (error = merge_msg_write_branches(&file, &matching, sep)) < 0) + goto cleanup; + + if (matching.length) + sep =','; + + if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tracking)) < 0 || + (error = merge_msg_write_tracking(&file, &matching, sep)) < 0) + goto cleanup; + + if (matching.length) + sep =','; + + if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tag)) < 0 || + (error = merge_msg_write_tags(&file, &matching, sep)) < 0) + goto cleanup; + + if (matching.length) + sep =','; + + /* We should never be called with multiple remote branches, but handle + * it in case we are... */ + while ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_remote)) > 0) { + if ((error = merge_msg_write_remotes(&file, &matching, sep)) < 0) + goto cleanup; + + if (matching.length) + sep =','; + } + + if (error < 0) + goto cleanup; + + for (i = 0; i < heads_len; i++) { + if (merge_msg_entry_written(&entries[i])) + continue; + + if ((error = git_filebuf_printf(&file, "; commit '%s'", + entries[i].merge_head->id_str)) < 0) + goto cleanup; + } + + if ((error = git_filebuf_printf(&file, "\n")) < 0 || + (error = git_filebuf_commit(&file)) < 0) + goto cleanup; + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + git_vector_free(&matching); + git__free(entries); + + return error; +} + +int git_merge__setup( + git_repository *repo, + const git_annotated_commit *our_head, + const git_annotated_commit *heads[], + size_t heads_len) +{ + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(our_head); + GIT_ASSERT_ARG(heads); + + if ((error = git_repository__set_orig_head(repo, git_annotated_commit_id(our_head))) == 0 && + (error = write_merge_head(repo, heads, heads_len)) == 0 && + (error = write_merge_mode(repo)) == 0) { + error = write_merge_msg(repo, heads, heads_len); + } + + return error; +} + +/* Merge branches */ + +static int merge_ancestor_head( + git_annotated_commit **ancestor_head, + git_repository *repo, + const git_annotated_commit *our_head, + const git_annotated_commit **their_heads, + size_t their_heads_len) +{ + git_oid *oids, ancestor_oid; + size_t i, alloc_len; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(our_head); + GIT_ASSERT_ARG(their_heads); + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, their_heads_len, 1); + oids = git__calloc(alloc_len, sizeof(git_oid)); + GIT_ERROR_CHECK_ALLOC(oids); + + git_oid_cpy(&oids[0], git_commit_id(our_head->commit)); + + for (i = 0; i < their_heads_len; i++) + git_oid_cpy(&oids[i + 1], git_annotated_commit_id(their_heads[i])); + + if ((error = git_merge_base_many(&ancestor_oid, repo, their_heads_len + 1, oids)) < 0) + goto on_error; + + error = git_annotated_commit_lookup(ancestor_head, repo, &ancestor_oid); + +on_error: + git__free(oids); + return error; +} + +static const char *merge_their_label(const char *branchname) +{ + const char *slash; + + if ((slash = strrchr(branchname, '/')) == NULL) + return branchname; + + if (*(slash+1) == '\0') + return "theirs"; + + return slash+1; +} + +static int merge_normalize_checkout_opts( + git_checkout_options *out, + git_repository *repo, + const git_checkout_options *given_checkout_opts, + unsigned int checkout_strategy, + git_annotated_commit *ancestor, + const git_annotated_commit *our_head, + const git_annotated_commit **their_heads, + size_t their_heads_len) +{ + git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + int error = 0; + + GIT_UNUSED(repo); + + if (given_checkout_opts != NULL) + memcpy(out, given_checkout_opts, sizeof(git_checkout_options)); + else + memcpy(out, &default_checkout_opts, sizeof(git_checkout_options)); + + out->checkout_strategy = checkout_strategy; + + if (!out->ancestor_label) { + if (ancestor && ancestor->type == GIT_ANNOTATED_COMMIT_REAL) + out->ancestor_label = git_commit_summary(ancestor->commit); + else if (ancestor) + out->ancestor_label = "merged common ancestors"; + else + out->ancestor_label = "empty base"; + } + + if (!out->our_label) { + if (our_head && our_head->ref_name) + out->our_label = our_head->ref_name; + else + out->our_label = "ours"; + } + + if (!out->their_label) { + if (their_heads_len == 1 && their_heads[0]->ref_name) + out->their_label = merge_their_label(their_heads[0]->ref_name); + else if (their_heads_len == 1) + out->their_label = their_heads[0]->id_str; + else + out->their_label = "theirs"; + } + + return error; +} + +static int merge_check_index(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) +{ + git_tree *head_tree = NULL; + git_index *index_repo = NULL; + git_iterator *iter_repo = NULL, *iter_new = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + git_diff *staged_diff_list = NULL, *index_diff_list = NULL; + git_diff_delta *delta; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_vector staged_paths = GIT_VECTOR_INIT; + size_t i; + int error = 0; + + GIT_UNUSED(merged_paths); + + *conflicts = 0; + + /* No staged changes may exist unless the change staged is identical to + * the result of the merge. This allows one to apply to merge manually, + * then run merge. Any other staged change would be overwritten by + * a reset merge. + */ + if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || + (error = git_repository_index(&index_repo, repo)) < 0 || + (error = git_diff_tree_to_index(&staged_diff_list, repo, head_tree, index_repo, &opts)) < 0) + goto done; + + if (staged_diff_list->deltas.length == 0) + goto done; + + git_vector_foreach(&staged_diff_list->deltas, i, delta) { + if ((error = git_vector_insert(&staged_paths, (char *)delta->new_file.path)) < 0) + goto done; + } + + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + iter_opts.pathlist.strings = (char **)staged_paths.contents; + iter_opts.pathlist.count = staged_paths.length; + + if ((error = git_iterator_for_index(&iter_repo, repo, index_repo, &iter_opts)) < 0 || + (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 || + (error = git_diff__from_iterators(&index_diff_list, repo, iter_repo, iter_new, &opts)) < 0) + goto done; + + *conflicts = index_diff_list->deltas.length; + +done: + git_tree_free(head_tree); + git_index_free(index_repo); + git_iterator_free(iter_repo); + git_iterator_free(iter_new); + git_diff_free(staged_diff_list); + git_diff_free(index_diff_list); + git_vector_free(&staged_paths); + + return error; +} + +static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) +{ + git_diff *wd_diff_list = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + int error = 0; + + GIT_UNUSED(index_new); + + *conflicts = 0; + + /* We need to have merged at least 1 file for the possibility to exist to + * have conflicts with the workdir. Passing 0 as the pathspec count parameter + * will consider all files in the working directory, that is, we may detect + * a conflict if there were untracked files in the workdir prior to starting + * the merge. This typically happens when cherry-picking a commit whose + * changes have already been applied. + */ + if (merged_paths->length == 0) + return 0; + + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + + /* Workdir changes may exist iff they do not conflict with changes that + * will be applied by the merge (including conflicts). Ensure that there + * are no changes in the workdir to these paths. + */ + opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; + opts.pathspec.count = merged_paths->length; + opts.pathspec.strings = (char **)merged_paths->contents; + opts.ignore_submodules = GIT_SUBMODULE_IGNORE_ALL; + + if ((error = git_diff_index_to_workdir(&wd_diff_list, repo, NULL, &opts)) < 0) + goto done; + + *conflicts = wd_diff_list->deltas.length; + +done: + git_diff_free(wd_diff_list); + + return error; +} + +int git_merge__check_result(git_repository *repo, git_index *index_new) +{ + git_tree *head_tree = NULL; + git_iterator *iter_head = NULL, *iter_new = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + git_diff *merged_list = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_delta *delta; + git_vector paths = GIT_VECTOR_INIT; + size_t i, index_conflicts = 0, wd_conflicts = 0, conflicts; + const git_index_entry *e; + int error = 0; + + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || + (error = git_iterator_for_tree(&iter_head, head_tree, &iter_opts)) < 0 || + (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 || + (error = git_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0) + goto done; + + git_vector_foreach(&merged_list->deltas, i, delta) { + if ((error = git_vector_insert(&paths, (char *)delta->new_file.path)) < 0) + goto done; + } + + for (i = 0; i < git_index_entrycount(index_new); i++) { + e = git_index_get_byindex(index_new, i); + + if (git_index_entry_is_conflict(e) && + (git_vector_last(&paths) == NULL || + strcmp(git_vector_last(&paths), e->path) != 0)) { + + if ((error = git_vector_insert(&paths, (char *)e->path)) < 0) + goto done; + } + } + + /* Make sure the index and workdir state do not prevent merging */ + if ((error = merge_check_index(&index_conflicts, repo, index_new, &paths)) < 0 || + (error = merge_check_workdir(&wd_conflicts, repo, index_new, &paths)) < 0) + goto done; + + if ((conflicts = index_conflicts + wd_conflicts) > 0) { + git_error_set(GIT_ERROR_MERGE, "%" PRIuZ " uncommitted change%s would be overwritten by merge", + conflicts, (conflicts != 1) ? "s" : ""); + error = GIT_ECONFLICT; + } + +done: + git_vector_free(&paths); + git_tree_free(head_tree); + git_iterator_free(iter_head); + git_iterator_free(iter_new); + git_diff_free(merged_list); + + return error; +} + +int git_merge__append_conflicts_to_merge_msg( + git_repository *repo, + git_index *index) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + const char *last = NULL; + size_t i; + int error; + + if (!git_index_has_conflicts(index)) + return 0; + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_APPEND, GIT_MERGE_FILE_MODE)) < 0) + goto cleanup; + + git_filebuf_printf(&file, "\n#Conflicts:\n"); + + for (i = 0; i < git_index_entrycount(index); i++) { + const git_index_entry *e = git_index_get_byindex(index, i); + + if (!git_index_entry_is_conflict(e)) + continue; + + if (last == NULL || strcmp(e->path, last) != 0) + git_filebuf_printf(&file, "#\t%s\n", e->path); + + last = e->path; + } + + error = git_filebuf_commit(&file); + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int merge_state_cleanup(git_repository *repo) +{ + const char *state_files[] = { + GIT_MERGE_HEAD_FILE, + GIT_MERGE_MODE_FILE, + GIT_MERGE_MSG_FILE, + }; + + return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); +} + +static int merge_heads( + git_annotated_commit **ancestor_head_out, + git_annotated_commit **our_head_out, + git_repository *repo, + git_reference *our_ref, + const git_annotated_commit **their_heads, + size_t their_heads_len) +{ + git_annotated_commit *ancestor_head = NULL, *our_head = NULL; + int error = 0; + + *ancestor_head_out = NULL; + *our_head_out = NULL; + + if ((error = git_annotated_commit_from_ref(&our_head, repo, our_ref)) < 0) + goto done; + + if ((error = merge_ancestor_head(&ancestor_head, repo, our_head, their_heads, their_heads_len)) < 0) { + if (error != GIT_ENOTFOUND) + goto done; + + git_error_clear(); + error = 0; + } + + *ancestor_head_out = ancestor_head; + *our_head_out = our_head; + +done: + if (error < 0) { + git_annotated_commit_free(ancestor_head); + git_annotated_commit_free(our_head); + } + + return error; +} + +static int merge_preference(git_merge_preference_t *out, git_repository *repo) +{ + git_config *config; + const char *value; + int bool_value, error = 0; + + *out = GIT_MERGE_PREFERENCE_NONE; + + if ((error = git_repository_config_snapshot(&config, repo)) < 0) + goto done; + + if ((error = git_config_get_string(&value, config, "merge.ff")) < 0) { + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + goto done; + } + + if (git_config_parse_bool(&bool_value, value) == 0) { + if (!bool_value) + *out |= GIT_MERGE_PREFERENCE_NO_FASTFORWARD; + } else { + if (strcasecmp(value, "only") == 0) + *out |= GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY; + } + +done: + git_config_free(config); + return error; +} + +int git_merge_analysis_for_ref( + git_merge_analysis_t *analysis_out, + git_merge_preference_t *preference_out, + git_repository *repo, + git_reference *our_ref, + const git_annotated_commit **their_heads, + size_t their_heads_len) +{ + git_annotated_commit *ancestor_head = NULL, *our_head = NULL; + int error = 0; + bool unborn; + + GIT_ASSERT_ARG(analysis_out); + GIT_ASSERT_ARG(preference_out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(their_heads && their_heads_len > 0); + + if (their_heads_len != 1) { + git_error_set(GIT_ERROR_MERGE, "can only merge a single branch"); + error = -1; + goto done; + } + + *analysis_out = GIT_MERGE_ANALYSIS_NONE; + + if ((error = merge_preference(preference_out, repo)) < 0) + goto done; + + if ((error = git_reference__is_unborn_head(&unborn, our_ref, repo)) < 0) + goto done; + + if (unborn) { + *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN; + error = 0; + goto done; + } + + if ((error = merge_heads(&ancestor_head, &our_head, repo, our_ref, their_heads, their_heads_len)) < 0) + goto done; + + /* We're up-to-date if we're trying to merge our own common ancestor. */ + if (ancestor_head && git_oid_equal( + git_annotated_commit_id(ancestor_head), git_annotated_commit_id(their_heads[0]))) + *analysis_out |= GIT_MERGE_ANALYSIS_UP_TO_DATE; + + /* We're fastforwardable if we're our own common ancestor. */ + else if (ancestor_head && git_oid_equal( + git_annotated_commit_id(ancestor_head), git_annotated_commit_id(our_head))) + *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL; + + /* Otherwise, just a normal merge is possible. */ + else + *analysis_out |= GIT_MERGE_ANALYSIS_NORMAL; + +done: + git_annotated_commit_free(ancestor_head); + git_annotated_commit_free(our_head); + return error; +} + +int git_merge_analysis( + git_merge_analysis_t *analysis_out, + git_merge_preference_t *preference_out, + git_repository *repo, + const git_annotated_commit **their_heads, + size_t their_heads_len) +{ + git_reference *head_ref = NULL; + int error = 0; + + if ((error = git_reference_lookup(&head_ref, repo, GIT_HEAD_FILE)) < 0) { + git_error_set(GIT_ERROR_MERGE, "failed to lookup HEAD reference"); + return error; + } + + error = git_merge_analysis_for_ref(analysis_out, preference_out, repo, head_ref, their_heads, their_heads_len); + + git_reference_free(head_ref); + + return error; +} + +int git_merge( + git_repository *repo, + const git_annotated_commit **their_heads, + size_t their_heads_len, + const git_merge_options *merge_opts, + const git_checkout_options *given_checkout_opts) +{ + git_reference *our_ref = NULL; + git_checkout_options checkout_opts; + git_annotated_commit *our_head = NULL, *base = NULL; + git_index *repo_index = NULL, *index = NULL; + git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; + unsigned int checkout_strategy; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(their_heads && their_heads_len > 0); + + if (their_heads_len != 1) { + git_error_set(GIT_ERROR_MERGE, "can only merge a single branch"); + return -1; + } + + if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0) + goto done; + + checkout_strategy = given_checkout_opts ? + given_checkout_opts->checkout_strategy : + GIT_CHECKOUT_SAFE; + + if ((error = git_indexwriter_init_for_operation(&indexwriter, repo, + &checkout_strategy)) < 0) + goto done; + + if ((error = git_repository_index(&repo_index, repo) < 0) || + (error = git_index_read(repo_index, 0) < 0)) + goto done; + + /* Write the merge setup files to the repository. */ + if ((error = git_annotated_commit_from_head(&our_head, repo)) < 0 || + (error = git_merge__setup(repo, our_head, their_heads, + their_heads_len)) < 0) + goto done; + + /* TODO: octopus */ + + if ((error = merge_annotated_commits(&index, &base, repo, our_head, + (git_annotated_commit *)their_heads[0], 0, merge_opts)) < 0 || + (error = git_merge__check_result(repo, index)) < 0 || + (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0) + goto done; + + /* check out the merge results */ + + if ((error = merge_normalize_checkout_opts(&checkout_opts, repo, + given_checkout_opts, checkout_strategy, + base, our_head, their_heads, their_heads_len)) < 0 || + (error = git_checkout_index(repo, index, &checkout_opts)) < 0) + goto done; + + error = git_indexwriter_commit(&indexwriter); + +done: + if (error < 0) + merge_state_cleanup(repo); + + git_indexwriter_cleanup(&indexwriter); + git_index_free(index); + git_annotated_commit_free(our_head); + git_annotated_commit_free(base); + git_reference_free(our_ref); + git_index_free(repo_index); + + return error; +} + +int git_merge_options_init(git_merge_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_merge_options, GIT_MERGE_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_merge_init_options(git_merge_options *opts, unsigned int version) +{ + return git_merge_options_init(opts, version); +} +#endif + +int git_merge_file_input_init(git_merge_file_input *input, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + input, version, git_merge_file_input, GIT_MERGE_FILE_INPUT_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_merge_file_init_input(git_merge_file_input *input, unsigned int version) +{ + return git_merge_file_input_init(input, version); +} +#endif + +int git_merge_file_options_init( + git_merge_file_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_merge_file_options, GIT_MERGE_FILE_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_merge_file_init_options( + git_merge_file_options *opts, unsigned int version) +{ + return git_merge_file_options_init(opts, version); +} +#endif diff --git a/src/libgit2/merge.h b/src/libgit2/merge.h new file mode 100644 index 000000000..23932905e --- /dev/null +++ b/src/libgit2/merge.h @@ -0,0 +1,204 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_merge_h__ +#define INCLUDE_merge_h__ + +#include "common.h" + +#include "vector.h" +#include "commit_list.h" +#include "pool.h" +#include "iterator.h" + +#include "git2/types.h" +#include "git2/merge.h" +#include "git2/sys/merge.h" + +#define GIT_MERGE_MSG_FILE "MERGE_MSG" +#define GIT_MERGE_MODE_FILE "MERGE_MODE" +#define GIT_MERGE_FILE_MODE 0666 + +#define GIT_MERGE_DEFAULT_RENAME_THRESHOLD 50 +#define GIT_MERGE_DEFAULT_TARGET_LIMIT 1000 + +/** Types of changes when files are merged from branch to branch. */ +typedef enum { + /* No conflict - a change only occurs in one branch. */ + GIT_MERGE_DIFF_NONE = 0, + + /* Occurs when a file is modified in both branches. */ + GIT_MERGE_DIFF_BOTH_MODIFIED = (1 << 0), + + /* Occurs when a file is added in both branches. */ + GIT_MERGE_DIFF_BOTH_ADDED = (1 << 1), + + /* Occurs when a file is deleted in both branches. */ + GIT_MERGE_DIFF_BOTH_DELETED = (1 << 2), + + /* Occurs when a file is modified in one branch and deleted in the other. */ + GIT_MERGE_DIFF_MODIFIED_DELETED = (1 << 3), + + /* Occurs when a file is renamed in one branch and modified in the other. */ + GIT_MERGE_DIFF_RENAMED_MODIFIED = (1 << 4), + + /* Occurs when a file is renamed in one branch and deleted in the other. */ + GIT_MERGE_DIFF_RENAMED_DELETED = (1 << 5), + + /* Occurs when a file is renamed in one branch and a file with the same + * name is added in the other. Eg, A->B and new file B. Core git calls + * this a "rename/delete". */ + GIT_MERGE_DIFF_RENAMED_ADDED = (1 << 6), + + /* Occurs when both a file is renamed to the same name in the ours and + * theirs branches. Eg, A->B and A->B in both. Automergeable. */ + GIT_MERGE_DIFF_BOTH_RENAMED = (1 << 7), + + /* Occurs when a file is renamed to different names in the ours and theirs + * branches. Eg, A->B and A->C. */ + GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 = (1 << 8), + + /* Occurs when two files are renamed to the same name in the ours and + * theirs branches. Eg, A->C and B->C. */ + GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 = (1 << 9), + + /* Occurs when an item at a path in one branch is a directory, and an + * item at the same path in a different branch is a file. */ + GIT_MERGE_DIFF_DIRECTORY_FILE = (1 << 10), + + /* The child of a folder that is in a directory/file conflict. */ + GIT_MERGE_DIFF_DF_CHILD = (1 << 11) +} git_merge_diff_t; + +typedef struct { + git_repository *repo; + git_pool pool; + + /* Vector of git_index_entry that represent the merged items that + * have been staged, either because only one side changed, or because + * the two changes were non-conflicting and mergeable. These items + * will be written as staged entries in the main index. + */ + git_vector staged; + + /* Vector of git_merge_diff entries that represent the conflicts that + * have not been automerged. These items will be written to high-stage + * entries in the main index. + */ + git_vector conflicts; + + /* Vector of git_merge_diff that have been automerged. These items + * will be written to the REUC when the index is produced. + */ + git_vector resolved; +} git_merge_diff_list; + +/** + * Description of changes to one file across three trees. + */ +typedef struct { + git_merge_diff_t type; + + git_index_entry ancestor_entry; + + git_index_entry our_entry; + git_delta_t our_status; + + git_index_entry their_entry; + git_delta_t their_status; + +} git_merge_diff; + +int git_merge__bases_many( + git_commit_list **out, + git_revwalk *walk, + git_commit_list_node *one, + git_vector *twos, + uint32_t minimum_generation); + +/* + * Three-way tree differencing + */ + +git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo); + +int git_merge_diff_list__find_differences( + git_merge_diff_list *merge_diff_list, + git_iterator *ancestor_iterator, + git_iterator *ours_iter, + git_iterator *theirs_iter); + +int git_merge_diff_list__find_renames(git_repository *repo, git_merge_diff_list *merge_diff_list, const git_merge_options *opts); + +void git_merge_diff_list__free(git_merge_diff_list *diff_list); + +/* Merge metadata setup */ + +int git_merge__setup( + git_repository *repo, + const git_annotated_commit *our_head, + const git_annotated_commit *heads[], + size_t heads_len); + +int git_merge__iterators( + git_index **out, + git_repository *repo, + git_iterator *ancestor_iter, + git_iterator *our_iter, + git_iterator *their_iter, + const git_merge_options *given_opts); + +int git_merge__check_result(git_repository *repo, git_index *index_new); + +int git_merge__append_conflicts_to_merge_msg(git_repository *repo, git_index *index); + +/* Merge files */ + +GIT_INLINE(const char *) git_merge_file__best_path( + const char *ancestor, + const char *ours, + const char *theirs) +{ + if (!ancestor) { + if (ours && theirs && strcmp(ours, theirs) == 0) + return ours; + + return NULL; + } + + if (ours && strcmp(ancestor, ours) == 0) + return theirs; + else if(theirs && strcmp(ancestor, theirs) == 0) + return ours; + + return NULL; +} + +GIT_INLINE(uint32_t) git_merge_file__best_mode( + uint32_t ancestor, uint32_t ours, uint32_t theirs) +{ + /* + * If ancestor didn't exist and either ours or theirs is executable, + * assume executable. Otherwise, if any mode changed from the ancestor, + * use that one. + */ + if (!ancestor) { + if (ours == GIT_FILEMODE_BLOB_EXECUTABLE || + theirs == GIT_FILEMODE_BLOB_EXECUTABLE) + return GIT_FILEMODE_BLOB_EXECUTABLE; + + return GIT_FILEMODE_BLOB; + } else if (ours && theirs) { + if (ancestor == ours) + return theirs; + + return ours; + } + + return 0; +} + +#endif diff --git a/src/libgit2/merge_driver.c b/src/libgit2/merge_driver.c new file mode 100644 index 000000000..19b35ac0e --- /dev/null +++ b/src/libgit2/merge_driver.c @@ -0,0 +1,432 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "merge_driver.h" + +#include "vector.h" +#include "runtime.h" +#include "merge.h" +#include "git2/merge.h" +#include "git2/sys/merge.h" + +static const char *merge_driver_name__text = "text"; +static const char *merge_driver_name__union = "union"; +static const char *merge_driver_name__binary = "binary"; + +struct merge_driver_registry { + git_rwlock lock; + git_vector drivers; +}; + +typedef struct { + git_merge_driver *driver; + int initialized; + char name[GIT_FLEX_ARRAY]; +} git_merge_driver_entry; + +static struct merge_driver_registry merge_driver_registry; + +static void git_merge_driver_global_shutdown(void); + +git_repository *git_merge_driver_source_repo( + const git_merge_driver_source *src) +{ + GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); + return src->repo; +} + +const git_index_entry *git_merge_driver_source_ancestor( + const git_merge_driver_source *src) +{ + GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); + return src->ancestor; +} + +const git_index_entry *git_merge_driver_source_ours( + const git_merge_driver_source *src) +{ + GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); + return src->ours; +} + +const git_index_entry *git_merge_driver_source_theirs( + const git_merge_driver_source *src) +{ + GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); + return src->theirs; +} + +const git_merge_file_options *git_merge_driver_source_file_options( + const git_merge_driver_source *src) +{ + GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); + return src->file_opts; +} + +int git_merge_driver__builtin_apply( + git_merge_driver *self, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + git_merge_driver__builtin *driver = (git_merge_driver__builtin *)self; + git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + int error; + + GIT_UNUSED(filter_name); + + if (src->file_opts) + memcpy(&file_opts, src->file_opts, sizeof(git_merge_file_options)); + + if (driver->favor) + file_opts.favor = driver->favor; + + if ((error = git_merge_file_from_index(&result, src->repo, + src->ancestor, src->ours, src->theirs, &file_opts)) < 0) + goto done; + + if (!result.automergeable && + !(file_opts.flags & GIT_MERGE_FILE_ACCEPT_CONFLICTS)) { + error = GIT_EMERGECONFLICT; + goto done; + } + + *path_out = git_merge_file__best_path( + src->ancestor ? src->ancestor->path : NULL, + src->ours ? src->ours->path : NULL, + src->theirs ? src->theirs->path : NULL); + + *mode_out = git_merge_file__best_mode( + src->ancestor ? src->ancestor->mode : 0, + src->ours ? src->ours->mode : 0, + src->theirs ? src->theirs->mode : 0); + + merged_out->ptr = (char *)result.ptr; + merged_out->size = result.len; + merged_out->reserved = 0; + result.ptr = NULL; + +done: + git_merge_file_result_free(&result); + return error; +} + +static int merge_driver_binary_apply( + git_merge_driver *self, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + GIT_UNUSED(self); + GIT_UNUSED(path_out); + GIT_UNUSED(mode_out); + GIT_UNUSED(merged_out); + GIT_UNUSED(filter_name); + GIT_UNUSED(src); + + return GIT_EMERGECONFLICT; +} + +static int merge_driver_entry_cmp(const void *a, const void *b) +{ + const git_merge_driver_entry *entry_a = a; + const git_merge_driver_entry *entry_b = b; + + return strcmp(entry_a->name, entry_b->name); +} + +static int merge_driver_entry_search(const void *a, const void *b) +{ + const char *name_a = a; + const git_merge_driver_entry *entry_b = b; + + return strcmp(name_a, entry_b->name); +} + +git_merge_driver__builtin git_merge_driver__text = { + { + GIT_MERGE_DRIVER_VERSION, + NULL, + NULL, + git_merge_driver__builtin_apply, + }, + GIT_MERGE_FILE_FAVOR_NORMAL +}; + +git_merge_driver__builtin git_merge_driver__union = { + { + GIT_MERGE_DRIVER_VERSION, + NULL, + NULL, + git_merge_driver__builtin_apply, + }, + GIT_MERGE_FILE_FAVOR_UNION +}; + +git_merge_driver git_merge_driver__binary = { + GIT_MERGE_DRIVER_VERSION, + NULL, + NULL, + merge_driver_binary_apply +}; + +/* Note: callers must lock the registry before calling this function */ +static int merge_driver_registry_insert( + const char *name, git_merge_driver *driver) +{ + git_merge_driver_entry *entry; + + entry = git__calloc(1, sizeof(git_merge_driver_entry) + strlen(name) + 1); + GIT_ERROR_CHECK_ALLOC(entry); + + strcpy(entry->name, name); + entry->driver = driver; + + return git_vector_insert_sorted( + &merge_driver_registry.drivers, entry, NULL); +} + +int git_merge_driver_global_init(void) +{ + int error; + + if (git_rwlock_init(&merge_driver_registry.lock) < 0) + return -1; + + if ((error = git_vector_init(&merge_driver_registry.drivers, 3, + merge_driver_entry_cmp)) < 0) + goto done; + + if ((error = merge_driver_registry_insert( + merge_driver_name__text, &git_merge_driver__text.base)) < 0 || + (error = merge_driver_registry_insert( + merge_driver_name__union, &git_merge_driver__union.base)) < 0 || + (error = merge_driver_registry_insert( + merge_driver_name__binary, &git_merge_driver__binary)) < 0) + goto done; + + error = git_runtime_shutdown_register(git_merge_driver_global_shutdown); + +done: + if (error < 0) + git_vector_free_deep(&merge_driver_registry.drivers); + + return error; +} + +static void git_merge_driver_global_shutdown(void) +{ + git_merge_driver_entry *entry; + size_t i; + + if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) + return; + + git_vector_foreach(&merge_driver_registry.drivers, i, entry) { + if (entry->driver->shutdown) + entry->driver->shutdown(entry->driver); + + git__free(entry); + } + + git_vector_free(&merge_driver_registry.drivers); + + git_rwlock_wrunlock(&merge_driver_registry.lock); + git_rwlock_free(&merge_driver_registry.lock); +} + +/* Note: callers must lock the registry before calling this function */ +static int merge_driver_registry_find(size_t *pos, const char *name) +{ + return git_vector_search2(pos, &merge_driver_registry.drivers, + merge_driver_entry_search, name); +} + +/* Note: callers must lock the registry before calling this function */ +static git_merge_driver_entry *merge_driver_registry_lookup( + size_t *pos, const char *name) +{ + git_merge_driver_entry *entry = NULL; + + if (!merge_driver_registry_find(pos, name)) + entry = git_vector_get(&merge_driver_registry.drivers, *pos); + + return entry; +} + +int git_merge_driver_register(const char *name, git_merge_driver *driver) +{ + int error; + + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(driver); + + if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); + return -1; + } + + if (!merge_driver_registry_find(NULL, name)) { + git_error_set(GIT_ERROR_MERGE, "attempt to reregister existing driver '%s'", + name); + error = GIT_EEXISTS; + goto done; + } + + error = merge_driver_registry_insert(name, driver); + +done: + git_rwlock_wrunlock(&merge_driver_registry.lock); + return error; +} + +int git_merge_driver_unregister(const char *name) +{ + git_merge_driver_entry *entry; + size_t pos; + int error = 0; + + if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); + return -1; + } + + if ((entry = merge_driver_registry_lookup(&pos, name)) == NULL) { + git_error_set(GIT_ERROR_MERGE, "cannot find merge driver '%s' to unregister", + name); + error = GIT_ENOTFOUND; + goto done; + } + + git_vector_remove(&merge_driver_registry.drivers, pos); + + if (entry->initialized && entry->driver->shutdown) { + entry->driver->shutdown(entry->driver); + entry->initialized = false; + } + + git__free(entry); + +done: + git_rwlock_wrunlock(&merge_driver_registry.lock); + return error; +} + +git_merge_driver *git_merge_driver_lookup(const char *name) +{ + git_merge_driver_entry *entry; + size_t pos; + int error; + + /* If we've decided the merge driver to use internally - and not + * based on user configuration (in merge_driver_name_for_path) + * then we can use a hardcoded name to compare instead of bothering + * to take a lock and look it up in the vector. + */ + if (name == merge_driver_name__text) + return &git_merge_driver__text.base; + else if (name == merge_driver_name__binary) + return &git_merge_driver__binary; + + if (git_rwlock_rdlock(&merge_driver_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); + return NULL; + } + + entry = merge_driver_registry_lookup(&pos, name); + + git_rwlock_rdunlock(&merge_driver_registry.lock); + + if (entry == NULL) { + git_error_set(GIT_ERROR_MERGE, "cannot use an unregistered filter"); + return NULL; + } + + if (!entry->initialized) { + if (entry->driver->initialize && + (error = entry->driver->initialize(entry->driver)) < 0) + return NULL; + + entry->initialized = 1; + } + + return entry->driver; +} + +static int merge_driver_name_for_path( + const char **out, + git_repository *repo, + const char *path, + const char *default_driver) +{ + const char *value; + int error; + + *out = NULL; + + if ((error = git_attr_get(&value, repo, 0, path, "merge")) < 0) + return error; + + /* set: use the built-in 3-way merge driver ("text") */ + if (GIT_ATTR_IS_TRUE(value)) + *out = merge_driver_name__text; + + /* unset: do not merge ("binary") */ + else if (GIT_ATTR_IS_FALSE(value)) + *out = merge_driver_name__binary; + + else if (GIT_ATTR_IS_UNSPECIFIED(value) && default_driver) + *out = default_driver; + + else if (GIT_ATTR_IS_UNSPECIFIED(value)) + *out = merge_driver_name__text; + + else + *out = value; + + return 0; +} + + +GIT_INLINE(git_merge_driver *) merge_driver_lookup_with_wildcard( + const char *name) +{ + git_merge_driver *driver = git_merge_driver_lookup(name); + + if (driver == NULL) + driver = git_merge_driver_lookup("*"); + + return driver; +} + +int git_merge_driver_for_source( + const char **name_out, + git_merge_driver **driver_out, + const git_merge_driver_source *src) +{ + const char *path, *driver_name; + int error = 0; + + path = git_merge_file__best_path( + src->ancestor ? src->ancestor->path : NULL, + src->ours ? src->ours->path : NULL, + src->theirs ? src->theirs->path : NULL); + + if ((error = merge_driver_name_for_path( + &driver_name, src->repo, path, src->default_driver)) < 0) + return error; + + *name_out = driver_name; + *driver_out = merge_driver_lookup_with_wildcard(driver_name); + return error; +} + diff --git a/src/libgit2/merge_driver.h b/src/libgit2/merge_driver.h new file mode 100644 index 000000000..6b7da5287 --- /dev/null +++ b/src/libgit2/merge_driver.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_merge_driver_h__ +#define INCLUDE_merge_driver_h__ + +#include "common.h" + +#include "git2/merge.h" +#include "git2/index.h" +#include "git2/sys/merge.h" + +struct git_merge_driver_source { + git_repository *repo; + const char *default_driver; + const git_merge_file_options *file_opts; + + const git_index_entry *ancestor; + const git_index_entry *ours; + const git_index_entry *theirs; +}; + +typedef struct git_merge_driver__builtin { + git_merge_driver base; + git_merge_file_favor_t favor; +} git_merge_driver__builtin; + +extern int git_merge_driver_global_init(void); + +extern int git_merge_driver_for_path( + char **name_out, + git_merge_driver **driver_out, + git_repository *repo, + const char *path); + +/* Merge driver configuration */ +extern int git_merge_driver_for_source( + const char **name_out, + git_merge_driver **driver_out, + const git_merge_driver_source *src); + +extern int git_merge_driver__builtin_apply( + git_merge_driver *self, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src); + +/* Merge driver for text files, performs a standard three-way merge */ +extern git_merge_driver__builtin git_merge_driver__text; + +/* Merge driver for union-style merging */ +extern git_merge_driver__builtin git_merge_driver__union; + +/* Merge driver for unmergeable (binary) files: always produces conflicts */ +extern git_merge_driver git_merge_driver__binary; + +#endif diff --git a/src/libgit2/merge_file.c b/src/libgit2/merge_file.c new file mode 100644 index 000000000..732a047b1 --- /dev/null +++ b/src/libgit2/merge_file.c @@ -0,0 +1,327 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "repository.h" +#include "posix.h" +#include "futils.h" +#include "index.h" +#include "diff_xdiff.h" +#include "merge.h" + +#include "git2/repository.h" +#include "git2/object.h" +#include "git2/index.h" +#include "git2/merge.h" + +#include "xdiff/xdiff.h" + +/* only examine the first 8000 bytes for binaryness. + * https://github.com/git/git/blob/77bd3ea9f54f1584147b594abc04c26ca516d987/xdiff-interface.c#L197 + */ +#define GIT_MERGE_FILE_BINARY_SIZE 8000 + +#define GIT_MERGE_FILE_SIDE_EXISTS(X) ((X)->mode != 0) + +static int merge_file_input_from_index( + git_merge_file_input *input_out, + git_odb_object **odb_object_out, + git_odb *odb, + const git_index_entry *entry) +{ + int error = 0; + + GIT_ASSERT_ARG(input_out); + GIT_ASSERT_ARG(odb_object_out); + GIT_ASSERT_ARG(odb); + GIT_ASSERT_ARG(entry); + + if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0) + goto done; + + input_out->path = entry->path; + input_out->mode = entry->mode; + input_out->ptr = (char *)git_odb_object_data(*odb_object_out); + input_out->size = git_odb_object_size(*odb_object_out); + +done: + return error; +} + +static void merge_file_normalize_opts( + git_merge_file_options *out, + const git_merge_file_options *given_opts) +{ + if (given_opts) + memcpy(out, given_opts, sizeof(git_merge_file_options)); + else { + git_merge_file_options default_opts = GIT_MERGE_FILE_OPTIONS_INIT; + memcpy(out, &default_opts, sizeof(git_merge_file_options)); + } +} + +static int merge_file__xdiff( + git_merge_file_result *out, + const git_merge_file_input *ancestor, + const git_merge_file_input *ours, + const git_merge_file_input *theirs, + const git_merge_file_options *given_opts) +{ + xmparam_t xmparam; + mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0}; + mmbuffer_t mmbuffer; + git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT; + const char *path; + int xdl_result; + int error = 0; + + memset(out, 0x0, sizeof(git_merge_file_result)); + + merge_file_normalize_opts(&options, given_opts); + + memset(&xmparam, 0x0, sizeof(xmparam_t)); + + if (ours->size > LONG_MAX || + theirs->size > LONG_MAX || + (ancestor && ancestor->size > LONG_MAX)) { + git_error_set(GIT_ERROR_MERGE, "failed to merge files"); + error = -1; + goto done; + } + + if (ancestor) { + xmparam.ancestor = (options.ancestor_label) ? + options.ancestor_label : ancestor->path; + ancestor_mmfile.ptr = (char *)ancestor->ptr; + ancestor_mmfile.size = (long)ancestor->size; + } + + xmparam.file1 = (options.our_label) ? + options.our_label : ours->path; + our_mmfile.ptr = (char *)ours->ptr; + our_mmfile.size = (long)ours->size; + + xmparam.file2 = (options.their_label) ? + options.their_label : theirs->path; + their_mmfile.ptr = (char *)theirs->ptr; + their_mmfile.size = (long)theirs->size; + + if (options.favor == GIT_MERGE_FILE_FAVOR_OURS) + xmparam.favor = XDL_MERGE_FAVOR_OURS; + else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS) + xmparam.favor = XDL_MERGE_FAVOR_THEIRS; + else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION) + xmparam.favor = XDL_MERGE_FAVOR_UNION; + + xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ? + XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS; + + if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3) + xmparam.style = XDL_MERGE_DIFF3; + if (options.flags & GIT_MERGE_FILE_STYLE_ZDIFF3) + xmparam.style = XDL_MERGE_ZEALOUS_DIFF3; + + if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE) + xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE; + if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE) + xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_CHANGE; + if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL) + xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; + + if (options.flags & GIT_MERGE_FILE_DIFF_PATIENCE) + xmparam.xpp.flags |= XDF_PATIENCE_DIFF; + + if (options.flags & GIT_MERGE_FILE_DIFF_MINIMAL) + xmparam.xpp.flags |= XDF_NEED_MINIMAL; + + xmparam.marker_size = options.marker_size; + + if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile, + &their_mmfile, &xmparam, &mmbuffer)) < 0) { + git_error_set(GIT_ERROR_MERGE, "failed to merge files"); + error = -1; + goto done; + } + + path = git_merge_file__best_path( + ancestor ? ancestor->path : NULL, + ours->path, + theirs->path); + + if (path != NULL && (out->path = git__strdup(path)) == NULL) { + error = -1; + goto done; + } + + out->automergeable = (xdl_result == 0); + out->ptr = (const char *)mmbuffer.ptr; + out->len = mmbuffer.size; + out->mode = git_merge_file__best_mode( + ancestor ? ancestor->mode : 0, + ours->mode, + theirs->mode); + +done: + if (error < 0) + git_merge_file_result_free(out); + + return error; +} + +static bool merge_file__is_binary(const git_merge_file_input *file) +{ + size_t len = file ? file->size : 0; + + if (len > GIT_XDIFF_MAX_SIZE) + return true; + if (len > GIT_MERGE_FILE_BINARY_SIZE) + len = GIT_MERGE_FILE_BINARY_SIZE; + + return len ? (memchr(file->ptr, 0, len) != NULL) : false; +} + +static int merge_file__binary( + git_merge_file_result *out, + const git_merge_file_input *ours, + const git_merge_file_input *theirs, + const git_merge_file_options *given_opts) +{ + const git_merge_file_input *favored = NULL; + + memset(out, 0x0, sizeof(git_merge_file_result)); + + if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_OURS) + favored = ours; + else if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_THEIRS) + favored = theirs; + else + goto done; + + if ((out->path = git__strdup(favored->path)) == NULL || + (out->ptr = git__malloc(favored->size)) == NULL) + goto done; + + memcpy((char *)out->ptr, favored->ptr, favored->size); + out->len = favored->size; + out->mode = favored->mode; + out->automergeable = 1; + +done: + return 0; +} + +static int merge_file__from_inputs( + git_merge_file_result *out, + const git_merge_file_input *ancestor, + const git_merge_file_input *ours, + const git_merge_file_input *theirs, + const git_merge_file_options *given_opts) +{ + if (merge_file__is_binary(ancestor) || + merge_file__is_binary(ours) || + merge_file__is_binary(theirs)) + return merge_file__binary(out, ours, theirs, given_opts); + + return merge_file__xdiff(out, ancestor, ours, theirs, given_opts); +} + +static git_merge_file_input *git_merge_file__normalize_inputs( + git_merge_file_input *out, + const git_merge_file_input *given) +{ + memcpy(out, given, sizeof(git_merge_file_input)); + + if (!out->path) + out->path = "file.txt"; + + if (!out->mode) + out->mode = 0100644; + + return out; +} + +int git_merge_file( + git_merge_file_result *out, + const git_merge_file_input *ancestor, + const git_merge_file_input *ours, + const git_merge_file_input *theirs, + const git_merge_file_options *options) +{ + git_merge_file_input inputs[3] = { {0} }; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ours); + GIT_ASSERT_ARG(theirs); + + memset(out, 0x0, sizeof(git_merge_file_result)); + + if (ancestor) + ancestor = git_merge_file__normalize_inputs(&inputs[0], ancestor); + + ours = git_merge_file__normalize_inputs(&inputs[1], ours); + theirs = git_merge_file__normalize_inputs(&inputs[2], theirs); + + return merge_file__from_inputs(out, ancestor, ours, theirs, options); +} + +int git_merge_file_from_index( + git_merge_file_result *out, + git_repository *repo, + const git_index_entry *ancestor, + const git_index_entry *ours, + const git_index_entry *theirs, + const git_merge_file_options *options) +{ + git_merge_file_input *ancestor_ptr = NULL, + ancestor_input = {0}, our_input = {0}, their_input = {0}; + git_odb *odb = NULL; + git_odb_object *odb_object[3] = { 0 }; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(ours); + GIT_ASSERT_ARG(theirs); + + memset(out, 0x0, sizeof(git_merge_file_result)); + + if ((error = git_repository_odb(&odb, repo)) < 0) + goto done; + + if (ancestor) { + if ((error = merge_file_input_from_index( + &ancestor_input, &odb_object[0], odb, ancestor)) < 0) + goto done; + + ancestor_ptr = &ancestor_input; + } + + if ((error = merge_file_input_from_index(&our_input, &odb_object[1], odb, ours)) < 0 || + (error = merge_file_input_from_index(&their_input, &odb_object[2], odb, theirs)) < 0) + goto done; + + error = merge_file__from_inputs(out, + ancestor_ptr, &our_input, &their_input, options); + +done: + git_odb_object_free(odb_object[0]); + git_odb_object_free(odb_object[1]); + git_odb_object_free(odb_object[2]); + git_odb_free(odb); + + return error; +} + +void git_merge_file_result_free(git_merge_file_result *result) +{ + if (result == NULL) + return; + + git__free((char *)result->path); + git__free((char *)result->ptr); +} diff --git a/src/libgit2/message.c b/src/libgit2/message.c new file mode 100644 index 000000000..ec0103a33 --- /dev/null +++ b/src/libgit2/message.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "buf.h" + +#include "git2/message.h" + +static size_t line_length_without_trailing_spaces(const char *line, size_t len) +{ + while (len) { + unsigned char c = line[len - 1]; + if (!git__isspace(c)) + break; + len--; + } + + return len; +} + +/* Greatly inspired from git.git "stripspace" */ +/* see https://github.com/git/git/blob/497215d8811ac7b8955693ceaad0899ecd894ed2/builtin/stripspace.c#L4-67 */ +static int git_message__prettify( + git_str *message_out, + const char *message, + int strip_comments, + char comment_char) +{ + const size_t message_len = strlen(message); + + int consecutive_empty_lines = 0; + size_t i, line_length, rtrimmed_line_length; + char *next_newline; + + for (i = 0; i < strlen(message); i += line_length) { + next_newline = memchr(message + i, '\n', message_len - i); + + if (next_newline != NULL) { + line_length = next_newline - (message + i) + 1; + } else { + line_length = message_len - i; + } + + if (strip_comments && line_length && message[i] == comment_char) + continue; + + rtrimmed_line_length = line_length_without_trailing_spaces(message + i, line_length); + + if (!rtrimmed_line_length) { + consecutive_empty_lines++; + continue; + } + + if (consecutive_empty_lines > 0 && message_out->size > 0) + git_str_putc(message_out, '\n'); + + consecutive_empty_lines = 0; + git_str_put(message_out, message + i, rtrimmed_line_length); + git_str_putc(message_out, '\n'); + } + + return git_str_oom(message_out) ? -1 : 0; +} + +int git_message_prettify( + git_buf *message_out, + const char *message, + int strip_comments, + char comment_char) +{ + GIT_BUF_WRAP_PRIVATE(message_out, git_message__prettify, message, strip_comments, comment_char); +} diff --git a/src/libgit2/midx.c b/src/libgit2/midx.c new file mode 100644 index 000000000..eb99e7373 --- /dev/null +++ b/src/libgit2/midx.c @@ -0,0 +1,896 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "midx.h" + +#include "array.h" +#include "buf.h" +#include "filebuf.h" +#include "futils.h" +#include "hash.h" +#include "odb.h" +#include "pack.h" +#include "fs_path.h" +#include "repository.h" +#include "str.h" + +#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */ +#define MIDX_VERSION 1 +#define MIDX_OBJECT_ID_VERSION 1 +struct git_midx_header { + uint32_t signature; + uint8_t version; + uint8_t object_id_version; + uint8_t chunks; + uint8_t base_midx_files; + uint32_t packfiles; +}; + +#define MIDX_PACKFILE_NAMES_ID 0x504e414d /* "PNAM" */ +#define MIDX_OID_FANOUT_ID 0x4f494446 /* "OIDF" */ +#define MIDX_OID_LOOKUP_ID 0x4f49444c /* "OIDL" */ +#define MIDX_OBJECT_OFFSETS_ID 0x4f4f4646 /* "OOFF" */ +#define MIDX_OBJECT_LARGE_OFFSETS_ID 0x4c4f4646 /* "LOFF" */ + +struct git_midx_chunk { + off64_t offset; + size_t length; +}; + +typedef int (*midx_write_cb)(const char *buf, size_t size, void *cb_data); + +static int midx_error(const char *message) +{ + git_error_set(GIT_ERROR_ODB, "invalid multi-pack-index file - %s", message); + return -1; +} + +static int midx_parse_packfile_names( + git_midx_file *idx, + const unsigned char *data, + uint32_t packfiles, + struct git_midx_chunk *chunk) +{ + int error; + uint32_t i; + char *packfile_name = (char *)(data + chunk->offset); + size_t chunk_size = chunk->length, len; + if (chunk->offset == 0) + return midx_error("missing Packfile Names chunk"); + if (chunk->length == 0) + return midx_error("empty Packfile Names chunk"); + if ((error = git_vector_init(&idx->packfile_names, packfiles, git__strcmp_cb)) < 0) + return error; + for (i = 0; i < packfiles; ++i) { + len = p_strnlen(packfile_name, chunk_size); + if (len == 0) + return midx_error("empty packfile name"); + if (len + 1 > chunk_size) + return midx_error("unterminated packfile name"); + git_vector_insert(&idx->packfile_names, packfile_name); + if (i && strcmp(git_vector_get(&idx->packfile_names, i - 1), packfile_name) >= 0) + return midx_error("packfile names are not sorted"); + if (strlen(packfile_name) <= strlen(".idx") || git__suffixcmp(packfile_name, ".idx") != 0) + return midx_error("non-.idx packfile name"); + if (strchr(packfile_name, '/') != NULL || strchr(packfile_name, '\\') != NULL) + return midx_error("non-local packfile"); + packfile_name += len + 1; + chunk_size -= len + 1; + } + return 0; +} + +static int midx_parse_oid_fanout( + git_midx_file *idx, + const unsigned char *data, + struct git_midx_chunk *chunk_oid_fanout) +{ + uint32_t i, nr; + if (chunk_oid_fanout->offset == 0) + return midx_error("missing OID Fanout chunk"); + if (chunk_oid_fanout->length == 0) + return midx_error("empty OID Fanout chunk"); + if (chunk_oid_fanout->length != 256 * 4) + return midx_error("OID Fanout chunk has wrong length"); + + idx->oid_fanout = (const uint32_t *)(data + chunk_oid_fanout->offset); + nr = 0; + for (i = 0; i < 256; ++i) { + uint32_t n = ntohl(idx->oid_fanout[i]); + if (n < nr) + return midx_error("index is non-monotonic"); + nr = n; + } + idx->num_objects = nr; + return 0; +} + +static int midx_parse_oid_lookup( + git_midx_file *idx, + const unsigned char *data, + struct git_midx_chunk *chunk_oid_lookup) +{ + uint32_t i; + git_oid *oid, *prev_oid, zero_oid = {{0}}; + + if (chunk_oid_lookup->offset == 0) + return midx_error("missing OID Lookup chunk"); + if (chunk_oid_lookup->length == 0) + return midx_error("empty OID Lookup chunk"); + if (chunk_oid_lookup->length != idx->num_objects * GIT_OID_RAWSZ) + return midx_error("OID Lookup chunk has wrong length"); + + idx->oid_lookup = oid = (git_oid *)(data + chunk_oid_lookup->offset); + prev_oid = &zero_oid; + for (i = 0; i < idx->num_objects; ++i, ++oid) { + if (git_oid_cmp(prev_oid, oid) >= 0) + return midx_error("OID Lookup index is non-monotonic"); + prev_oid = oid; + } + + return 0; +} + +static int midx_parse_object_offsets( + git_midx_file *idx, + const unsigned char *data, + struct git_midx_chunk *chunk_object_offsets) +{ + if (chunk_object_offsets->offset == 0) + return midx_error("missing Object Offsets chunk"); + if (chunk_object_offsets->length == 0) + return midx_error("empty Object Offsets chunk"); + if (chunk_object_offsets->length != idx->num_objects * 8) + return midx_error("Object Offsets chunk has wrong length"); + + idx->object_offsets = data + chunk_object_offsets->offset; + + return 0; +} + +static int midx_parse_object_large_offsets( + git_midx_file *idx, + const unsigned char *data, + struct git_midx_chunk *chunk_object_large_offsets) +{ + if (chunk_object_large_offsets->length == 0) + return 0; + if (chunk_object_large_offsets->length % 8 != 0) + return midx_error("malformed Object Large Offsets chunk"); + + idx->object_large_offsets = data + chunk_object_large_offsets->offset; + idx->num_object_large_offsets = chunk_object_large_offsets->length / 8; + + return 0; +} + +int git_midx_parse( + git_midx_file *idx, + const unsigned char *data, + size_t size) +{ + struct git_midx_header *hdr; + const unsigned char *chunk_hdr; + struct git_midx_chunk *last_chunk; + uint32_t i; + off64_t last_chunk_offset, chunk_offset, trailer_offset; + size_t checksum_size; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + int error; + struct git_midx_chunk chunk_packfile_names = {0}, + chunk_oid_fanout = {0}, + chunk_oid_lookup = {0}, + chunk_object_offsets = {0}, + chunk_object_large_offsets = {0}; + + GIT_ASSERT_ARG(idx); + + if (size < sizeof(struct git_midx_header) + GIT_OID_RAWSZ) + return midx_error("multi-pack index is too short"); + + hdr = ((struct git_midx_header *)data); + + if (hdr->signature != htonl(MIDX_SIGNATURE) || + hdr->version != MIDX_VERSION || + hdr->object_id_version != MIDX_OBJECT_ID_VERSION) { + return midx_error("unsupported multi-pack index version"); + } + if (hdr->chunks == 0) + return midx_error("no chunks in multi-pack index"); + + /* + * The very first chunk's offset should be after the header, all the chunk + * headers, and a special zero chunk. + */ + last_chunk_offset = + sizeof(struct git_midx_header) + + (1 + hdr->chunks) * 12; + + checksum_size = GIT_HASH_SHA1_SIZE; + trailer_offset = size - checksum_size; + + if (trailer_offset < last_chunk_offset) + return midx_error("wrong index size"); + memcpy(idx->checksum, data + trailer_offset, checksum_size); + + if (git_hash_buf(checksum, data, (size_t)trailer_offset, GIT_HASH_ALGORITHM_SHA1) < 0) + return midx_error("could not calculate signature"); + if (memcmp(checksum, idx->checksum, checksum_size) != 0) + return midx_error("index signature mismatch"); + + chunk_hdr = data + sizeof(struct git_midx_header); + last_chunk = NULL; + for (i = 0; i < hdr->chunks; ++i, chunk_hdr += 12) { + chunk_offset = ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 4)))) << 32 | + ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 8)))); + if (chunk_offset < last_chunk_offset) + return midx_error("chunks are non-monotonic"); + if (chunk_offset >= trailer_offset) + return midx_error("chunks extend beyond the trailer"); + if (last_chunk != NULL) + last_chunk->length = (size_t)(chunk_offset - last_chunk_offset); + last_chunk_offset = chunk_offset; + + switch (ntohl(*((uint32_t *)(chunk_hdr + 0)))) { + case MIDX_PACKFILE_NAMES_ID: + chunk_packfile_names.offset = last_chunk_offset; + last_chunk = &chunk_packfile_names; + break; + + case MIDX_OID_FANOUT_ID: + chunk_oid_fanout.offset = last_chunk_offset; + last_chunk = &chunk_oid_fanout; + break; + + case MIDX_OID_LOOKUP_ID: + chunk_oid_lookup.offset = last_chunk_offset; + last_chunk = &chunk_oid_lookup; + break; + + case MIDX_OBJECT_OFFSETS_ID: + chunk_object_offsets.offset = last_chunk_offset; + last_chunk = &chunk_object_offsets; + break; + + case MIDX_OBJECT_LARGE_OFFSETS_ID: + chunk_object_large_offsets.offset = last_chunk_offset; + last_chunk = &chunk_object_large_offsets; + break; + + default: + return midx_error("unrecognized chunk ID"); + } + } + last_chunk->length = (size_t)(trailer_offset - last_chunk_offset); + + error = midx_parse_packfile_names( + idx, data, ntohl(hdr->packfiles), &chunk_packfile_names); + if (error < 0) + return error; + error = midx_parse_oid_fanout(idx, data, &chunk_oid_fanout); + if (error < 0) + return error; + error = midx_parse_oid_lookup(idx, data, &chunk_oid_lookup); + if (error < 0) + return error; + error = midx_parse_object_offsets(idx, data, &chunk_object_offsets); + if (error < 0) + return error; + error = midx_parse_object_large_offsets(idx, data, &chunk_object_large_offsets); + if (error < 0) + return error; + + return 0; +} + +int git_midx_open( + git_midx_file **idx_out, + const char *path) +{ + git_midx_file *idx; + git_file fd = -1; + size_t idx_size; + struct stat st; + int error; + + /* TODO: properly open the file without access time using O_NOATIME */ + fd = git_futils_open_ro(path); + if (fd < 0) + return fd; + + if (p_fstat(fd, &st) < 0) { + p_close(fd); + git_error_set(GIT_ERROR_ODB, "multi-pack-index file not found - '%s'", path); + return -1; + } + + if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size)) { + p_close(fd); + git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path); + return -1; + } + idx_size = (size_t)st.st_size; + + idx = git__calloc(1, sizeof(git_midx_file)); + GIT_ERROR_CHECK_ALLOC(idx); + + error = git_str_sets(&idx->filename, path); + if (error < 0) + return error; + + error = git_futils_mmap_ro(&idx->index_map, fd, 0, idx_size); + p_close(fd); + if (error < 0) { + git_midx_free(idx); + return error; + } + + if ((error = git_midx_parse(idx, idx->index_map.data, idx_size)) < 0) { + git_midx_free(idx); + return error; + } + + *idx_out = idx; + return 0; +} + +bool git_midx_needs_refresh( + const git_midx_file *idx, + const char *path) +{ + git_file fd = -1; + struct stat st; + ssize_t bytes_read; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size; + + /* TODO: properly open the file without access time using O_NOATIME */ + fd = git_futils_open_ro(path); + if (fd < 0) + return true; + + if (p_fstat(fd, &st) < 0) { + p_close(fd); + return true; + } + + if (!S_ISREG(st.st_mode) || + !git__is_sizet(st.st_size) || + (size_t)st.st_size != idx->index_map.len) { + p_close(fd); + return true; + } + + checksum_size = GIT_HASH_SHA1_SIZE; + bytes_read = p_pread(fd, checksum, checksum_size, st.st_size - GIT_OID_RAWSZ); + p_close(fd); + + if (bytes_read != (ssize_t)checksum_size) + return true; + + return (memcmp(checksum, idx->checksum, checksum_size) != 0); +} + +int git_midx_entry_find( + git_midx_entry *e, + git_midx_file *idx, + const git_oid *short_oid, + size_t len) +{ + int pos, found = 0; + size_t pack_index; + uint32_t hi, lo; + const git_oid *current = NULL; + const unsigned char *object_offset; + off64_t offset; + + GIT_ASSERT_ARG(idx); + + hi = ntohl(idx->oid_fanout[(int)short_oid->id[0]]); + lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(idx->oid_fanout[(int)short_oid->id[0] - 1])); + + pos = git_pack__lookup_sha1(idx->oid_lookup, GIT_OID_RAWSZ, lo, hi, short_oid->id); + + if (pos >= 0) { + /* An object matching exactly the oid was found */ + found = 1; + current = idx->oid_lookup + pos; + } else { + /* No object was found */ + /* pos refers to the object with the "closest" oid to short_oid */ + pos = -1 - pos; + if (pos < (int)idx->num_objects) { + current = idx->oid_lookup + pos; + + if (!git_oid_ncmp(short_oid, current, len)) + found = 1; + } + } + + if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)idx->num_objects) { + /* Check for ambiguousity */ + const git_oid *next = current + 1; + + if (!git_oid_ncmp(short_oid, next, len)) { + found = 2; + } + } + + if (!found) + return git_odb__error_notfound("failed to find offset for multi-pack index entry", short_oid, len); + if (found > 1) + return git_odb__error_ambiguous("found multiple offsets for multi-pack index entry"); + + object_offset = idx->object_offsets + pos * 8; + offset = ntohl(*((uint32_t *)(object_offset + 4))); + if (offset & 0x80000000) { + uint32_t object_large_offsets_pos = offset & 0x7fffffff; + const unsigned char *object_large_offsets_index = idx->object_large_offsets; + + /* Make sure we're not being sent out of bounds */ + if (object_large_offsets_pos >= idx->num_object_large_offsets) + return git_odb__error_notfound("invalid index into the object large offsets table", short_oid, len); + + object_large_offsets_index += 8 * object_large_offsets_pos; + + offset = (((uint64_t)ntohl(*((uint32_t *)(object_large_offsets_index + 0)))) << 32) | + ntohl(*((uint32_t *)(object_large_offsets_index + 4))); + } + pack_index = ntohl(*((uint32_t *)(object_offset + 0))); + if (pack_index >= git_vector_length(&idx->packfile_names)) + return midx_error("invalid index into the packfile names table"); + e->pack_index = pack_index; + e->offset = offset; + git_oid_cpy(&e->sha1, current); + return 0; +} + +int git_midx_foreach_entry( + git_midx_file *idx, + git_odb_foreach_cb cb, + void *data) +{ + size_t i; + int error; + + GIT_ASSERT_ARG(idx); + + for (i = 0; i < idx->num_objects; ++i) { + if ((error = cb(&idx->oid_lookup[i], data)) != 0) + return git_error_set_after_callback(error); + } + + return error; +} + +int git_midx_close(git_midx_file *idx) +{ + GIT_ASSERT_ARG(idx); + + if (idx->index_map.data) + git_futils_mmap_free(&idx->index_map); + + git_vector_free(&idx->packfile_names); + + return 0; +} + +void git_midx_free(git_midx_file *idx) +{ + if (!idx) + return; + + git_str_dispose(&idx->filename); + git_midx_close(idx); + git__free(idx); +} + +static int packfile__cmp(const void *a_, const void *b_) +{ + const struct git_pack_file *a = a_; + const struct git_pack_file *b = b_; + + return strcmp(a->pack_name, b->pack_name); +} + +int git_midx_writer_new( + git_midx_writer **out, + const char *pack_dir) +{ + git_midx_writer *w = git__calloc(1, sizeof(git_midx_writer)); + GIT_ERROR_CHECK_ALLOC(w); + + if (git_str_sets(&w->pack_dir, pack_dir) < 0) { + git__free(w); + return -1; + } + git_fs_path_squash_slashes(&w->pack_dir); + + if (git_vector_init(&w->packs, 0, packfile__cmp) < 0) { + git_str_dispose(&w->pack_dir); + git__free(w); + return -1; + } + + *out = w; + return 0; +} + +void git_midx_writer_free(git_midx_writer *w) +{ + struct git_pack_file *p; + size_t i; + + if (!w) + return; + + git_vector_foreach (&w->packs, i, p) + git_mwindow_put_pack(p); + git_vector_free(&w->packs); + git_str_dispose(&w->pack_dir); + git__free(w); +} + +int git_midx_writer_add( + git_midx_writer *w, + const char *idx_path) +{ + git_str idx_path_buf = GIT_STR_INIT; + int error; + struct git_pack_file *p; + + error = git_fs_path_prettify(&idx_path_buf, idx_path, git_str_cstr(&w->pack_dir)); + if (error < 0) + return error; + + error = git_mwindow_get_pack(&p, git_str_cstr(&idx_path_buf)); + git_str_dispose(&idx_path_buf); + if (error < 0) + return error; + + error = git_vector_insert(&w->packs, p); + if (error < 0) { + git_mwindow_put_pack(p); + return error; + } + + return 0; +} + +typedef git_array_t(git_midx_entry) object_entry_array_t; + +struct object_entry_cb_state { + uint32_t pack_index; + object_entry_array_t *object_entries_array; +}; + +static int object_entry__cb(const git_oid *oid, off64_t offset, void *data) +{ + struct object_entry_cb_state *state = (struct object_entry_cb_state *)data; + + git_midx_entry *entry = git_array_alloc(*state->object_entries_array); + GIT_ERROR_CHECK_ALLOC(entry); + + git_oid_cpy(&entry->sha1, oid); + entry->offset = offset; + entry->pack_index = state->pack_index; + + return 0; +} + +static int object_entry__cmp(const void *a_, const void *b_) +{ + const git_midx_entry *a = (const git_midx_entry *)a_; + const git_midx_entry *b = (const git_midx_entry *)b_; + + return git_oid_cmp(&a->sha1, &b->sha1); +} + +static int write_offset(off64_t offset, midx_write_cb write_cb, void *cb_data) +{ + int error; + uint32_t word; + + word = htonl((uint32_t)((offset >> 32) & 0xffffffffu)); + error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + word = htonl((uint32_t)((offset >> 0) & 0xffffffffu)); + error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + + return 0; +} + +static int write_chunk_header(int chunk_id, off64_t offset, midx_write_cb write_cb, void *cb_data) +{ + uint32_t word = htonl(chunk_id); + int error = write_cb((const char *)&word, sizeof(word), cb_data); + if (error < 0) + return error; + return write_offset(offset, write_cb, cb_data); + + return 0; +} + +static int midx_write_buf(const char *buf, size_t size, void *data) +{ + git_str *b = (git_str *)data; + return git_str_put(b, buf, size); +} + +struct midx_write_hash_context { + midx_write_cb write_cb; + void *cb_data; + git_hash_ctx *ctx; +}; + +static int midx_write_hash(const char *buf, size_t size, void *data) +{ + struct midx_write_hash_context *ctx = (struct midx_write_hash_context *)data; + int error; + + error = git_hash_update(ctx->ctx, buf, size); + if (error < 0) + return error; + + return ctx->write_cb(buf, size, ctx->cb_data); +} + +static int midx_write( + git_midx_writer *w, + midx_write_cb write_cb, + void *cb_data) +{ + int error = 0; + size_t i; + struct git_pack_file *p; + struct git_midx_header hdr = {0}; + uint32_t oid_fanout_count; + uint32_t object_large_offsets_count; + uint32_t oid_fanout[256]; + off64_t offset; + git_str packfile_names = GIT_STR_INIT, + oid_lookup = GIT_STR_INIT, + object_offsets = GIT_STR_INIT, + object_large_offsets = GIT_STR_INIT; + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + size_t checksum_size; + git_midx_entry *entry; + object_entry_array_t object_entries_array = GIT_ARRAY_INIT; + git_vector object_entries = GIT_VECTOR_INIT; + git_hash_ctx ctx; + struct midx_write_hash_context hash_cb_data = {0}; + + hdr.signature = htonl(MIDX_SIGNATURE); + hdr.version = MIDX_VERSION; + hdr.object_id_version = MIDX_OBJECT_ID_VERSION; + hdr.base_midx_files = 0; + + hash_cb_data.write_cb = write_cb; + hash_cb_data.cb_data = cb_data; + hash_cb_data.ctx = &ctx; + + checksum_size = GIT_HASH_SHA1_SIZE; + error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1); + if (error < 0) + return error; + cb_data = &hash_cb_data; + write_cb = midx_write_hash; + + git_vector_sort(&w->packs); + git_vector_foreach (&w->packs, i, p) { + git_str relative_index = GIT_STR_INIT; + struct object_entry_cb_state state = {0}; + size_t path_len; + + state.pack_index = (uint32_t)i; + state.object_entries_array = &object_entries_array; + + error = git_str_sets(&relative_index, p->pack_name); + if (error < 0) + goto cleanup; + error = git_fs_path_make_relative(&relative_index, git_str_cstr(&w->pack_dir)); + if (error < 0) { + git_str_dispose(&relative_index); + goto cleanup; + } + path_len = git_str_len(&relative_index); + if (path_len <= strlen(".pack") || git__suffixcmp(git_str_cstr(&relative_index), ".pack") != 0) { + git_str_dispose(&relative_index); + git_error_set(GIT_ERROR_INVALID, "invalid packfile name: '%s'", p->pack_name); + error = -1; + goto cleanup; + } + path_len -= strlen(".pack"); + + git_str_put(&packfile_names, git_str_cstr(&relative_index), path_len); + git_str_puts(&packfile_names, ".idx"); + git_str_putc(&packfile_names, '\0'); + git_str_dispose(&relative_index); + + error = git_pack_foreach_entry_offset(p, object_entry__cb, &state); + if (error < 0) + goto cleanup; + } + + /* Sort the object entries. */ + error = git_vector_init(&object_entries, git_array_size(object_entries_array), object_entry__cmp); + if (error < 0) + goto cleanup; + git_array_foreach (object_entries_array, i, entry) { + if ((error = git_vector_set(NULL, &object_entries, i, entry)) < 0) + goto cleanup; + } + git_vector_set_sorted(&object_entries, 0); + git_vector_sort(&object_entries); + git_vector_uniq(&object_entries, NULL); + + /* Pad the packfile names so it is a multiple of four. */ + while (git_str_len(&packfile_names) & 3) + git_str_putc(&packfile_names, '\0'); + + /* Fill the OID Fanout table. */ + oid_fanout_count = 0; + for (i = 0; i < 256; i++) { + while (oid_fanout_count < git_vector_length(&object_entries) && + ((const git_midx_entry *)git_vector_get(&object_entries, oid_fanout_count))->sha1.id[0] <= i) + ++oid_fanout_count; + oid_fanout[i] = htonl(oid_fanout_count); + } + + /* Fill the OID Lookup table. */ + git_vector_foreach (&object_entries, i, entry) { + error = git_str_put(&oid_lookup, (const char *)&entry->sha1, sizeof(entry->sha1)); + if (error < 0) + goto cleanup; + } + + /* Fill the Object Offsets and Object Large Offsets tables. */ + object_large_offsets_count = 0; + git_vector_foreach (&object_entries, i, entry) { + uint32_t word; + + word = htonl((uint32_t)entry->pack_index); + error = git_str_put(&object_offsets, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + if (entry->offset >= 0x80000000l) { + word = htonl(0x80000000u | object_large_offsets_count++); + if ((error = write_offset(entry->offset, midx_write_buf, &object_large_offsets)) < 0) + goto cleanup; + } else { + word = htonl((uint32_t)entry->offset & 0x7fffffffu); + } + + error = git_str_put(&object_offsets, (const char *)&word, sizeof(word)); + if (error < 0) + goto cleanup; + } + + /* Write the header. */ + hdr.packfiles = htonl((uint32_t)git_vector_length(&w->packs)); + hdr.chunks = 4; + if (git_str_len(&object_large_offsets) > 0) + hdr.chunks++; + error = write_cb((const char *)&hdr, sizeof(hdr), cb_data); + if (error < 0) + goto cleanup; + + /* Write the chunk headers. */ + offset = sizeof(hdr) + (hdr.chunks + 1) * 12; + error = write_chunk_header(MIDX_PACKFILE_NAMES_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&packfile_names); + error = write_chunk_header(MIDX_OID_FANOUT_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += sizeof(oid_fanout); + error = write_chunk_header(MIDX_OID_LOOKUP_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&oid_lookup); + error = write_chunk_header(MIDX_OBJECT_OFFSETS_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&object_offsets); + if (git_str_len(&object_large_offsets) > 0) { + error = write_chunk_header(MIDX_OBJECT_LARGE_OFFSETS_ID, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + offset += git_str_len(&object_large_offsets); + } + error = write_chunk_header(0, offset, write_cb, cb_data); + if (error < 0) + goto cleanup; + + /* Write all the chunks. */ + error = write_cb(git_str_cstr(&packfile_names), git_str_len(&packfile_names), cb_data); + if (error < 0) + goto cleanup; + error = write_cb((const char *)oid_fanout, sizeof(oid_fanout), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_str_cstr(&oid_lookup), git_str_len(&oid_lookup), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_str_cstr(&object_offsets), git_str_len(&object_offsets), cb_data); + if (error < 0) + goto cleanup; + error = write_cb(git_str_cstr(&object_large_offsets), git_str_len(&object_large_offsets), cb_data); + if (error < 0) + goto cleanup; + + /* Finalize the checksum and write the trailer. */ + error = git_hash_final(checksum, &ctx); + if (error < 0) + goto cleanup; + error = write_cb((char *)checksum, checksum_size, cb_data); + if (error < 0) + goto cleanup; + +cleanup: + git_array_clear(object_entries_array); + git_vector_free(&object_entries); + git_str_dispose(&packfile_names); + git_str_dispose(&oid_lookup); + git_str_dispose(&object_offsets); + git_str_dispose(&object_large_offsets); + git_hash_ctx_cleanup(&ctx); + return error; +} + +static int midx_write_filebuf(const char *buf, size_t size, void *data) +{ + git_filebuf *f = (git_filebuf *)data; + return git_filebuf_write(f, buf, size); +} + +int git_midx_writer_commit( + git_midx_writer *w) +{ + int error; + int filebuf_flags = GIT_FILEBUF_DO_NOT_BUFFER; + git_str midx_path = GIT_STR_INIT; + git_filebuf output = GIT_FILEBUF_INIT; + + error = git_str_joinpath(&midx_path, git_str_cstr(&w->pack_dir), "multi-pack-index"); + if (error < 0) + return error; + + if (git_repository__fsync_gitdir) + filebuf_flags |= GIT_FILEBUF_FSYNC; + error = git_filebuf_open(&output, git_str_cstr(&midx_path), filebuf_flags, 0644); + git_str_dispose(&midx_path); + if (error < 0) + return error; + + error = midx_write(w, midx_write_filebuf, &output); + if (error < 0) { + git_filebuf_cleanup(&output); + return error; + } + + return git_filebuf_commit(&output); +} + +int git_midx_writer_dump( + git_buf *midx, + git_midx_writer *w) +{ + git_str str = GIT_STR_INIT; + int error; + + if ((error = git_buf_tostr(&str, midx)) < 0 || + (error = midx_write(w, midx_write_buf, &str)) == 0) + error = git_buf_fromstr(midx, &str); + + git_str_dispose(&str); + return error; +} diff --git a/src/libgit2/midx.h b/src/libgit2/midx.h new file mode 100644 index 000000000..7dd851bd3 --- /dev/null +++ b/src/libgit2/midx.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_midx_h__ +#define INCLUDE_midx_h__ + +#include "common.h" + +#include + +#include "git2/sys/midx.h" + +#include "map.h" +#include "mwindow.h" +#include "odb.h" + +/* + * A multi-pack-index file. + * + * This file contains a merged index for multiple independent .pack files. This + * can help speed up locating objects without requiring a garbage collection + * cycle to create a single .pack file. + * + * Support for this feature was added in git 2.21, and requires the + * `core.multiPackIndex` config option to be set. + */ +typedef struct git_midx_file { + git_map index_map; + + /* The table of Packfile Names. */ + git_vector packfile_names; + + /* The OID Fanout table. */ + const uint32_t *oid_fanout; + /* The total number of objects in the index. */ + uint32_t num_objects; + + /* The OID Lookup table. */ + git_oid *oid_lookup; + + /* The Object Offsets table. Each entry has two 4-byte fields with the pack index and the offset. */ + const unsigned char *object_offsets; + + /* The Object Large Offsets table. */ + const unsigned char *object_large_offsets; + /* The number of entries in the Object Large Offsets table. Each entry has an 8-byte with an offset */ + size_t num_object_large_offsets; + + /* The trailer of the file. Contains the SHA1-checksum of the whole file. */ + unsigned char checksum[GIT_HASH_SHA1_SIZE]; + + /* something like ".git/objects/pack/multi-pack-index". */ + git_str filename; +} git_midx_file; + +/* + * An entry in the multi-pack-index file. Similar in purpose to git_pack_entry. + */ +typedef struct git_midx_entry { + /* The index within idx->packfile_names where the packfile name can be found. */ + size_t pack_index; + /* The offset within the .pack file where the requested object is found. */ + off64_t offset; + /* The SHA-1 hash of the requested object. */ + git_oid sha1; +} git_midx_entry; + +/* + * A writer for `multi-pack-index` files. + */ +struct git_midx_writer { + /* + * The path of the directory where the .pack/.idx files are stored. The + * `multi-pack-index` file will be written to the same directory. + */ + git_str pack_dir; + + /* The list of `git_pack_file`s. */ + git_vector packs; +}; + +int git_midx_open( + git_midx_file **idx_out, + const char *path); +bool git_midx_needs_refresh( + const git_midx_file *idx, + const char *path); +int git_midx_entry_find( + git_midx_entry *e, + git_midx_file *idx, + const git_oid *short_oid, + size_t len); +int git_midx_foreach_entry( + git_midx_file *idx, + git_odb_foreach_cb cb, + void *data); +int git_midx_close(git_midx_file *idx); +void git_midx_free(git_midx_file *idx); + +/* This is exposed for use in the fuzzers. */ +int git_midx_parse( + git_midx_file *idx, + const unsigned char *data, + size_t size); + +#endif diff --git a/src/libgit2/mwindow.c b/src/libgit2/mwindow.c new file mode 100644 index 000000000..d06b7a80e --- /dev/null +++ b/src/libgit2/mwindow.c @@ -0,0 +1,541 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "mwindow.h" + +#include "vector.h" +#include "futils.h" +#include "map.h" +#include "runtime.h" +#include "strmap.h" +#include "pack.h" + +#define DEFAULT_WINDOW_SIZE \ + (sizeof(void*) >= 8 \ + ? 1 * 1024 * 1024 * 1024 \ + : 32 * 1024 * 1024) + +#define DEFAULT_MAPPED_LIMIT \ + ((1024 * 1024) * (sizeof(void*) >= 8 ? UINT64_C(8192) : UINT64_C(256))) + +/* default is unlimited */ +#define DEFAULT_FILE_LIMIT 0 + +size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE; +size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT; +size_t git_mwindow__file_limit = DEFAULT_FILE_LIMIT; + +/* Mutex to control access to `git_mwindow__mem_ctl` and `git__pack_cache`. */ +git_mutex git__mwindow_mutex; + +/* Whenever you want to read or modify this, grab `git__mwindow_mutex` */ +git_mwindow_ctl git_mwindow__mem_ctl; + +/* Global list of mwindow files, to open packs once across repos */ +git_strmap *git__pack_cache = NULL; + +static void git_mwindow_global_shutdown(void) +{ + git_strmap *tmp = git__pack_cache; + + git_mutex_free(&git__mwindow_mutex); + + git__pack_cache = NULL; + git_strmap_free(tmp); +} + +int git_mwindow_global_init(void) +{ + int error; + + GIT_ASSERT(!git__pack_cache); + + if ((error = git_mutex_init(&git__mwindow_mutex)) < 0 || + (error = git_strmap_new(&git__pack_cache)) < 0) + return error; + + return git_runtime_shutdown_register(git_mwindow_global_shutdown); +} + +int git_mwindow_get_pack(struct git_pack_file **out, const char *path) +{ + struct git_pack_file *pack; + char *packname; + int error; + + if ((error = git_packfile__name(&packname, path)) < 0) + return error; + + if (git_mutex_lock(&git__mwindow_mutex) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock mwindow mutex"); + return -1; + } + + pack = git_strmap_get(git__pack_cache, packname); + git__free(packname); + + if (pack != NULL) { + git_atomic32_inc(&pack->refcount); + git_mutex_unlock(&git__mwindow_mutex); + *out = pack; + return 0; + } + + /* If we didn't find it, we need to create it */ + if ((error = git_packfile_alloc(&pack, path)) < 0) { + git_mutex_unlock(&git__mwindow_mutex); + return error; + } + + git_atomic32_inc(&pack->refcount); + + error = git_strmap_set(git__pack_cache, pack->pack_name, pack); + git_mutex_unlock(&git__mwindow_mutex); + if (error < 0) { + git_packfile_free(pack, false); + return error; + } + + *out = pack; + return 0; +} + +int git_mwindow_put_pack(struct git_pack_file *pack) +{ + int count, error; + struct git_pack_file *pack_to_delete = NULL; + + if ((error = git_mutex_lock(&git__mwindow_mutex)) < 0) + return error; + + /* put before get would be a corrupted state */ + GIT_ASSERT(git__pack_cache); + + /* if we cannot find it, the state is corrupted */ + GIT_ASSERT(git_strmap_exists(git__pack_cache, pack->pack_name)); + + count = git_atomic32_dec(&pack->refcount); + if (count == 0) { + git_strmap_delete(git__pack_cache, pack->pack_name); + pack_to_delete = pack; + } + git_mutex_unlock(&git__mwindow_mutex); + git_packfile_free(pack_to_delete, false); + + return 0; +} + +/* + * Free all the windows in a sequence, typically because we're done + * with the file. Needs to hold the git__mwindow_mutex. + */ +static int git_mwindow_free_all_locked(git_mwindow_file *mwf) +{ + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + size_t i; + + /* + * Remove these windows from the global list + */ + for (i = 0; i < ctl->windowfiles.length; ++i){ + if (git_vector_get(&ctl->windowfiles, i) == mwf) { + git_vector_remove(&ctl->windowfiles, i); + break; + } + } + + if (ctl->windowfiles.length == 0) { + git_vector_free(&ctl->windowfiles); + ctl->windowfiles.contents = NULL; + } + + while (mwf->windows) { + git_mwindow *w = mwf->windows; + GIT_ASSERT(w->inuse_cnt == 0); + + ctl->mapped -= w->window_map.len; + ctl->open_windows--; + + git_futils_mmap_free(&w->window_map); + + mwf->windows = w->next; + git__free(w); + } + + return 0; +} + +int git_mwindow_free_all(git_mwindow_file *mwf) +{ + int error; + + if (git_mutex_lock(&git__mwindow_mutex)) { + git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); + return -1; + } + + error = git_mwindow_free_all_locked(mwf); + + git_mutex_unlock(&git__mwindow_mutex); + + return error; +} + +/* + * Check if a window 'win' contains the address 'offset' + */ +int git_mwindow_contains(git_mwindow *win, off64_t offset) +{ + off64_t win_off = win->offset; + return win_off <= offset + && offset <= (off64_t)(win_off + win->window_map.len); +} + +#define GIT_MWINDOW__LRU -1 +#define GIT_MWINDOW__MRU 1 + +/* + * Find the least- or most-recently-used window in a file that is not currently + * being used. The 'only_unused' flag controls whether the caller requires the + * file to only have unused windows. If '*out_window' is non-null, it is used as + * a starting point for the comparison. + * + * Returns whether such a window was found in the file. + */ +static bool git_mwindow_scan_recently_used( + git_mwindow_file *mwf, + git_mwindow **out_window, + git_mwindow **out_last, + bool only_unused, + int comparison_sign) +{ + git_mwindow *w, *w_last; + git_mwindow *lru_window = NULL, *lru_last = NULL; + bool found = false; + + GIT_ASSERT_ARG(mwf); + GIT_ASSERT_ARG(out_window); + + lru_window = *out_window; + if (out_last) + lru_last = *out_last; + + for (w_last = NULL, w = mwf->windows; w; w_last = w, w = w->next) { + if (w->inuse_cnt) { + if (only_unused) + return false; + /* This window is currently being used. Skip it. */ + continue; + } + + /* + * If the current one is more (or less) recent than the last one, + * store it in the output parameter. If lru_window is NULL, + * it's the first loop, so store it as well. + */ + if (!lru_window || + (comparison_sign == GIT_MWINDOW__LRU && lru_window->last_used > w->last_used) || + (comparison_sign == GIT_MWINDOW__MRU && lru_window->last_used < w->last_used)) { + lru_window = w; + lru_last = w_last; + found = true; + } + } + + if (!found) + return false; + + *out_window = lru_window; + if (out_last) + *out_last = lru_last; + return true; +} + +/* + * Close the least recently used window (that is currently not being used) out + * of all the files. Called under lock from new_window_locked. + */ +static int git_mwindow_close_lru_window_locked(void) +{ + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + git_mwindow_file *cur; + size_t i; + git_mwindow *lru_window = NULL, *lru_last = NULL, **list = NULL; + + git_vector_foreach(&ctl->windowfiles, i, cur) { + if (git_mwindow_scan_recently_used( + cur, &lru_window, &lru_last, false, GIT_MWINDOW__LRU)) { + list = &cur->windows; + } + } + + if (!lru_window) { + git_error_set(GIT_ERROR_OS, "failed to close memory window; couldn't find LRU"); + return -1; + } + + ctl->mapped -= lru_window->window_map.len; + git_futils_mmap_free(&lru_window->window_map); + + if (lru_last) + lru_last->next = lru_window->next; + else + *list = lru_window->next; + + git__free(lru_window); + ctl->open_windows--; + + return 0; +} + +/* + * Finds the file that does not have any open windows AND whose + * most-recently-used window is the least-recently used one across all + * currently open files. + * + * Called under lock from new_window_locked. + */ +static int git_mwindow_find_lru_file_locked(git_mwindow_file **out) +{ + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + git_mwindow_file *lru_file = NULL, *current_file = NULL; + git_mwindow *lru_window = NULL; + size_t i; + + git_vector_foreach(&ctl->windowfiles, i, current_file) { + git_mwindow *mru_window = NULL; + if (!git_mwindow_scan_recently_used( + current_file, &mru_window, NULL, true, GIT_MWINDOW__MRU)) { + continue; + } + if (!lru_window || lru_window->last_used > mru_window->last_used) { + lru_window = mru_window; + lru_file = current_file; + } + } + + if (!lru_file) { + git_error_set(GIT_ERROR_OS, "failed to close memory window file; couldn't find LRU"); + return -1; + } + + *out = lru_file; + return 0; +} + +/* This gets called under lock from git_mwindow_open */ +static git_mwindow *new_window_locked( + git_file fd, + off64_t size, + off64_t offset) +{ + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + size_t walign = git_mwindow__window_size / 2; + off64_t len; + git_mwindow *w; + + w = git__calloc(1, sizeof(*w)); + + if (w == NULL) + return NULL; + + w->offset = (offset / walign) * walign; + + len = size - w->offset; + if (len > (off64_t)git_mwindow__window_size) + len = (off64_t)git_mwindow__window_size; + + ctl->mapped += (size_t)len; + + while (git_mwindow__mapped_limit < ctl->mapped && + git_mwindow_close_lru_window_locked() == 0) /* nop */; + + /* + * We treat `mapped_limit` as a soft limit. If we can't find a + * window to close and are above the limit, we still mmap the new + * window. + */ + + if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) { + /* + * The first error might be down to memory fragmentation even if + * we're below our soft limits, so free up what we can and try again. + */ + + while (git_mwindow_close_lru_window_locked() == 0) + /* nop */; + + if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) { + git__free(w); + return NULL; + } + } + + ctl->mmap_calls++; + ctl->open_windows++; + + if (ctl->mapped > ctl->peak_mapped) + ctl->peak_mapped = ctl->mapped; + + if (ctl->open_windows > ctl->peak_open_windows) + ctl->peak_open_windows = ctl->open_windows; + + return w; +} + +/* + * Open a new window, closing the least recenty used until we have + * enough space. Don't forget to add it to your list + */ +unsigned char *git_mwindow_open( + git_mwindow_file *mwf, + git_mwindow **cursor, + off64_t offset, + size_t extra, + unsigned int *left) +{ + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + git_mwindow *w = *cursor; + + if (git_mutex_lock(&git__mwindow_mutex)) { + git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); + return NULL; + } + + if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) { + if (w) { + w->inuse_cnt--; + } + + for (w = mwf->windows; w; w = w->next) { + if (git_mwindow_contains(w, offset) && + git_mwindow_contains(w, offset + extra)) + break; + } + + /* + * If there isn't a suitable window, we need to create a new + * one. + */ + if (!w) { + w = new_window_locked(mwf->fd, mwf->size, offset); + if (w == NULL) { + git_mutex_unlock(&git__mwindow_mutex); + return NULL; + } + w->next = mwf->windows; + mwf->windows = w; + } + } + + /* If we changed w, store it in the cursor */ + if (w != *cursor) { + w->last_used = ctl->used_ctr++; + w->inuse_cnt++; + *cursor = w; + } + + offset -= w->offset; + + if (left) + *left = (unsigned int)(w->window_map.len - offset); + + git_mutex_unlock(&git__mwindow_mutex); + return (unsigned char *) w->window_map.data + offset; +} + +int git_mwindow_file_register(git_mwindow_file *mwf) +{ + git_vector closed_files = GIT_VECTOR_INIT; + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + int error; + size_t i; + git_mwindow_file *closed_file = NULL; + + if (git_mutex_lock(&git__mwindow_mutex)) { + git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); + return -1; + } + + if (ctl->windowfiles.length == 0 && + (error = git_vector_init(&ctl->windowfiles, 8, NULL)) < 0) { + git_mutex_unlock(&git__mwindow_mutex); + goto cleanup; + } + + if (git_mwindow__file_limit) { + git_mwindow_file *lru_file; + while (git_mwindow__file_limit <= ctl->windowfiles.length && + git_mwindow_find_lru_file_locked(&lru_file) == 0) { + if ((error = git_vector_insert(&closed_files, lru_file)) < 0) { + /* + * Exceeding the file limit seems preferable to being open to + * data races that can end up corrupting the heap. + */ + break; + } + git_mwindow_free_all_locked(lru_file); + } + } + + error = git_vector_insert(&ctl->windowfiles, mwf); + git_mutex_unlock(&git__mwindow_mutex); + if (error < 0) + goto cleanup; + + /* + * Once we have released the global windowfiles lock, we can close each + * individual file. Before doing so, acquire that file's lock to avoid + * closing a file that is currently being used. + */ + git_vector_foreach(&closed_files, i, closed_file) { + error = git_mutex_lock(&closed_file->lock); + if (error < 0) + continue; + p_close(closed_file->fd); + closed_file->fd = -1; + git_mutex_unlock(&closed_file->lock); + } + +cleanup: + git_vector_free(&closed_files); + return error; +} + +void git_mwindow_file_deregister(git_mwindow_file *mwf) +{ + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + git_mwindow_file *cur; + size_t i; + + if (git_mutex_lock(&git__mwindow_mutex)) + return; + + git_vector_foreach(&ctl->windowfiles, i, cur) { + if (cur == mwf) { + git_vector_remove(&ctl->windowfiles, i); + git_mutex_unlock(&git__mwindow_mutex); + return; + } + } + git_mutex_unlock(&git__mwindow_mutex); +} + +void git_mwindow_close(git_mwindow **window) +{ + git_mwindow *w = *window; + if (w) { + if (git_mutex_lock(&git__mwindow_mutex)) { + git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); + return; + } + + w->inuse_cnt--; + git_mutex_unlock(&git__mwindow_mutex); + *window = NULL; + } +} diff --git a/src/libgit2/mwindow.h b/src/libgit2/mwindow.h new file mode 100644 index 000000000..e3a03f019 --- /dev/null +++ b/src/libgit2/mwindow.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_mwindow__ +#define INCLUDE_mwindow__ + +#include "common.h" + +#include "map.h" +#include "vector.h" + +typedef struct git_mwindow { + struct git_mwindow *next; + git_map window_map; + off64_t offset; + size_t last_used; + size_t inuse_cnt; +} git_mwindow; + +typedef struct git_mwindow_file { + git_mutex lock; /* protects updates to fd */ + git_mwindow *windows; + int fd; + off64_t size; +} git_mwindow_file; + +typedef struct git_mwindow_ctl { + size_t mapped; + unsigned int open_windows; + unsigned int mmap_calls; + unsigned int peak_open_windows; + size_t peak_mapped; + size_t used_ctr; + git_vector windowfiles; +} git_mwindow_ctl; + +int git_mwindow_contains(git_mwindow *win, off64_t offset); +int git_mwindow_free_all(git_mwindow_file *mwf); /* locks */ +unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, off64_t offset, size_t extra, unsigned int *left); +int git_mwindow_file_register(git_mwindow_file *mwf); +void git_mwindow_file_deregister(git_mwindow_file *mwf); +void git_mwindow_close(git_mwindow **w_cursor); + +extern int git_mwindow_global_init(void); + +struct git_pack_file; /* just declaration to avoid cyclical includes */ +int git_mwindow_get_pack(struct git_pack_file **out, const char *path); +int git_mwindow_put_pack(struct git_pack_file *pack); + +#endif diff --git a/src/libgit2/net.c b/src/libgit2/net.c new file mode 100644 index 000000000..a76fd1d7c --- /dev/null +++ b/src/libgit2/net.c @@ -0,0 +1,750 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "net.h" +#include "netops.h" + +#include + +#include "posix.h" +#include "str.h" +#include "http_parser.h" +#include "runtime.h" + +#define DEFAULT_PORT_HTTP "80" +#define DEFAULT_PORT_HTTPS "443" +#define DEFAULT_PORT_GIT "9418" +#define DEFAULT_PORT_SSH "22" + +bool git_net_str_is_url(const char *str) +{ + const char *c; + + for (c = str; *c; c++) { + if (*c == ':' && *(c+1) == '/' && *(c+2) == '/') + return true; + + if ((*c < 'a' || *c > 'z') && + (*c < 'A' || *c > 'Z') && + (*c < '0' || *c > '9') && + (*c != '+' && *c != '-' && *c != '.')) + break; + } + + return false; +} + +static const char *default_port_for_scheme(const char *scheme) +{ + if (strcmp(scheme, "http") == 0) + return DEFAULT_PORT_HTTP; + else if (strcmp(scheme, "https") == 0) + return DEFAULT_PORT_HTTPS; + else if (strcmp(scheme, "git") == 0) + return DEFAULT_PORT_GIT; + else if (strcmp(scheme, "ssh") == 0 || + strcmp(scheme, "ssh+git") == 0 || + strcmp(scheme, "git+ssh") == 0) + return DEFAULT_PORT_SSH; + + return NULL; +} + +int git_net_url_dup(git_net_url *out, git_net_url *in) +{ + if (in->scheme) { + out->scheme = git__strdup(in->scheme); + GIT_ERROR_CHECK_ALLOC(out->scheme); + } + + if (in->host) { + out->host = git__strdup(in->host); + GIT_ERROR_CHECK_ALLOC(out->host); + } + + if (in->port) { + out->port = git__strdup(in->port); + GIT_ERROR_CHECK_ALLOC(out->port); + } + + if (in->path) { + out->path = git__strdup(in->path); + GIT_ERROR_CHECK_ALLOC(out->path); + } + + if (in->query) { + out->query = git__strdup(in->query); + GIT_ERROR_CHECK_ALLOC(out->query); + } + + if (in->username) { + out->username = git__strdup(in->username); + GIT_ERROR_CHECK_ALLOC(out->username); + } + + if (in->password) { + out->password = git__strdup(in->password); + GIT_ERROR_CHECK_ALLOC(out->password); + } + + return 0; +} + +int git_net_url_parse(git_net_url *url, const char *given) +{ + struct http_parser_url u = {0}; + bool has_scheme, has_host, has_port, has_path, has_query, has_userinfo; + git_str scheme = GIT_STR_INIT, + host = GIT_STR_INIT, + port = GIT_STR_INIT, + path = GIT_STR_INIT, + username = GIT_STR_INIT, + password = GIT_STR_INIT, + query = GIT_STR_INIT; + int error = GIT_EINVALIDSPEC; + + if (http_parser_parse_url(given, strlen(given), false, &u)) { + git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given); + goto done; + } + + has_scheme = !!(u.field_set & (1 << UF_SCHEMA)); + has_host = !!(u.field_set & (1 << UF_HOST)); + has_port = !!(u.field_set & (1 << UF_PORT)); + has_path = !!(u.field_set & (1 << UF_PATH)); + has_query = !!(u.field_set & (1 << UF_QUERY)); + has_userinfo = !!(u.field_set & (1 << UF_USERINFO)); + + if (has_scheme) { + const char *url_scheme = given + u.field_data[UF_SCHEMA].off; + size_t url_scheme_len = u.field_data[UF_SCHEMA].len; + git_str_put(&scheme, url_scheme, url_scheme_len); + git__strntolower(scheme.ptr, scheme.size); + } else { + git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given); + goto done; + } + + if (has_host) { + const char *url_host = given + u.field_data[UF_HOST].off; + size_t url_host_len = u.field_data[UF_HOST].len; + git_str_decode_percent(&host, url_host, url_host_len); + } + + if (has_port) { + const char *url_port = given + u.field_data[UF_PORT].off; + size_t url_port_len = u.field_data[UF_PORT].len; + git_str_put(&port, url_port, url_port_len); + } else { + const char *default_port = default_port_for_scheme(scheme.ptr); + + if (default_port == NULL) { + git_error_set(GIT_ERROR_NET, "unknown scheme for URL '%s'", given); + goto done; + } + + git_str_puts(&port, default_port); + } + + if (has_path) { + const char *url_path = given + u.field_data[UF_PATH].off; + size_t url_path_len = u.field_data[UF_PATH].len; + git_str_put(&path, url_path, url_path_len); + } else { + git_str_puts(&path, "/"); + } + + if (has_query) { + const char *url_query = given + u.field_data[UF_QUERY].off; + size_t url_query_len = u.field_data[UF_QUERY].len; + git_str_decode_percent(&query, url_query, url_query_len); + } + + if (has_userinfo) { + const char *url_userinfo = given + u.field_data[UF_USERINFO].off; + size_t url_userinfo_len = u.field_data[UF_USERINFO].len; + const char *colon = memchr(url_userinfo, ':', url_userinfo_len); + + if (colon) { + const char *url_username = url_userinfo; + size_t url_username_len = colon - url_userinfo; + const char *url_password = colon + 1; + size_t url_password_len = url_userinfo_len - (url_username_len + 1); + + git_str_decode_percent(&username, url_username, url_username_len); + git_str_decode_percent(&password, url_password, url_password_len); + } else { + git_str_decode_percent(&username, url_userinfo, url_userinfo_len); + } + } + + if (git_str_oom(&scheme) || + git_str_oom(&host) || + git_str_oom(&port) || + git_str_oom(&path) || + git_str_oom(&query) || + git_str_oom(&username) || + git_str_oom(&password)) + return -1; + + url->scheme = git_str_detach(&scheme); + url->host = git_str_detach(&host); + url->port = git_str_detach(&port); + url->path = git_str_detach(&path); + url->query = git_str_detach(&query); + url->username = git_str_detach(&username); + url->password = git_str_detach(&password); + + error = 0; + +done: + git_str_dispose(&scheme); + git_str_dispose(&host); + git_str_dispose(&port); + git_str_dispose(&path); + git_str_dispose(&query); + git_str_dispose(&username); + git_str_dispose(&password); + return error; +} + +static int scp_invalid(const char *message) +{ + git_error_set(GIT_ERROR_NET, "invalid scp-style path: %s", message); + return GIT_EINVALIDSPEC; +} + +static bool is_ipv6(const char *str) +{ + const char *c; + size_t colons = 0; + + if (*str++ != '[') + return false; + + for (c = str; *c; c++) { + if (*c == ':') + colons++; + + if (*c == ']') + return (colons > 1); + + if (*c != ':' && + (*c < '0' || *c > '9') && + (*c < 'a' || *c > 'f') && + (*c < 'A' || *c > 'F')) + return false; + } + + return false; +} + +static bool has_at(const char *str) +{ + const char *c; + + for (c = str; *c; c++) { + if (*c == '@') + return true; + + if (*c == ':') + break; + } + + return false; +} + +int git_net_url_parse_scp(git_net_url *url, const char *given) +{ + const char *default_port = default_port_for_scheme("ssh"); + const char *c, *user, *host, *port, *path = NULL; + size_t user_len = 0, host_len = 0, port_len = 0; + unsigned short bracket = 0; + + enum { + NONE, + USER, + HOST_START, HOST, HOST_END, + IPV6, IPV6_END, + PORT_START, PORT, PORT_END, + PATH_START + } state = NONE; + + memset(url, 0, sizeof(git_net_url)); + + for (c = given; *c && !path; c++) { + switch (state) { + case NONE: + switch (*c) { + case '@': + return scp_invalid("unexpected '@'"); + case ':': + return scp_invalid("unexpected ':'"); + case '[': + if (is_ipv6(c)) { + state = IPV6; + host = c; + } else if (bracket++ > 1) { + return scp_invalid("unexpected '['"); + } + break; + default: + if (has_at(c)) { + state = USER; + user = c; + } else { + state = HOST; + host = c; + } + break; + } + break; + + case USER: + if (*c == '@') { + user_len = (c - user); + state = HOST_START; + } + break; + + case HOST_START: + state = (*c == '[') ? IPV6 : HOST; + host = c; + break; + + case HOST: + if (*c == ':') { + host_len = (c - host); + state = bracket ? PORT_START : PATH_START; + } else if (*c == ']') { + if (bracket-- == 0) + return scp_invalid("unexpected ']'"); + + host_len = (c - host); + state = HOST_END; + } + break; + + case HOST_END: + if (*c != ':') + return scp_invalid("unexpected character after hostname"); + state = PATH_START; + break; + + case IPV6: + if (*c == ']') + state = IPV6_END; + break; + + case IPV6_END: + if (*c != ':') + return scp_invalid("unexpected character after ipv6 address"); + + host_len = (c - host); + state = bracket ? PORT_START : PATH_START; + break; + + case PORT_START: + port = c; + state = PORT; + break; + + case PORT: + if (*c == ']') { + if (bracket-- == 0) + return scp_invalid("unexpected ']'"); + + port_len = c - port; + state = PORT_END; + } + break; + + case PORT_END: + if (*c != ':') + return scp_invalid("unexpected character after ipv6 address"); + + state = PATH_START; + break; + + case PATH_START: + path = c; + break; + + default: + GIT_ASSERT("unhandled state"); + } + } + + if (!path) + return scp_invalid("path is required"); + + GIT_ERROR_CHECK_ALLOC(url->scheme = git__strdup("ssh")); + + if (user_len) + GIT_ERROR_CHECK_ALLOC(url->username = git__strndup(user, user_len)); + + GIT_ASSERT(host_len); + GIT_ERROR_CHECK_ALLOC(url->host = git__strndup(host, host_len)); + + if (port_len) + GIT_ERROR_CHECK_ALLOC(url->port = git__strndup(port, port_len)); + else + GIT_ERROR_CHECK_ALLOC(url->port = git__strdup(default_port)); + + GIT_ASSERT(path); + GIT_ERROR_CHECK_ALLOC(url->path = git__strdup(path)); + + return 0; +} + +int git_net_url_joinpath( + git_net_url *out, + git_net_url *one, + const char *two) +{ + git_str path = GIT_STR_INIT; + const char *query; + size_t one_len, two_len; + + git_net_url_dispose(out); + + if ((query = strchr(two, '?')) != NULL) { + two_len = query - two; + + if (*(++query) != '\0') { + out->query = git__strdup(query); + GIT_ERROR_CHECK_ALLOC(out->query); + } + } else { + two_len = strlen(two); + } + + /* Strip all trailing `/`s from the first path */ + one_len = one->path ? strlen(one->path) : 0; + while (one_len && one->path[one_len - 1] == '/') + one_len--; + + /* Strip all leading `/`s from the second path */ + while (*two == '/') { + two++; + two_len--; + } + + git_str_put(&path, one->path, one_len); + git_str_putc(&path, '/'); + git_str_put(&path, two, two_len); + + if (git_str_oom(&path)) + return -1; + + out->path = git_str_detach(&path); + + if (one->scheme) { + out->scheme = git__strdup(one->scheme); + GIT_ERROR_CHECK_ALLOC(out->scheme); + } + + if (one->host) { + out->host = git__strdup(one->host); + GIT_ERROR_CHECK_ALLOC(out->host); + } + + if (one->port) { + out->port = git__strdup(one->port); + GIT_ERROR_CHECK_ALLOC(out->port); + } + + if (one->username) { + out->username = git__strdup(one->username); + GIT_ERROR_CHECK_ALLOC(out->username); + } + + if (one->password) { + out->password = git__strdup(one->password); + GIT_ERROR_CHECK_ALLOC(out->password); + } + + return 0; +} + +/* + * Some servers strip the query parameters from the Location header + * when sending a redirect. Others leave it in place. + * Check for both, starting with the stripped case first, + * since it appears to be more common. + */ +static void remove_service_suffix( + git_net_url *url, + const char *service_suffix) +{ + const char *service_query = strchr(service_suffix, '?'); + size_t full_suffix_len = strlen(service_suffix); + size_t suffix_len = service_query ? + (size_t)(service_query - service_suffix) : full_suffix_len; + size_t path_len = strlen(url->path); + ssize_t truncate = -1; + + /* + * Check for a redirect without query parameters, + * like "/newloc/info/refs"' + */ + if (suffix_len && path_len >= suffix_len) { + size_t suffix_offset = path_len - suffix_len; + + if (git__strncmp(url->path + suffix_offset, service_suffix, suffix_len) == 0 && + (!service_query || git__strcmp(url->query, service_query + 1) == 0)) { + truncate = suffix_offset; + } + } + + /* + * If we haven't already found where to truncate to remove the + * suffix, check for a redirect with query parameters, like + * "/newloc/info/refs?service=git-upload-pack" + */ + if (truncate < 0 && git__suffixcmp(url->path, service_suffix) == 0) + truncate = path_len - full_suffix_len; + + /* Ensure we leave a minimum of '/' as the path */ + if (truncate == 0) + truncate++; + + if (truncate > 0) { + url->path[truncate] = '\0'; + + git__free(url->query); + url->query = NULL; + } +} + +int git_net_url_apply_redirect( + git_net_url *url, + const char *redirect_location, + bool allow_offsite, + const char *service_suffix) +{ + git_net_url tmp = GIT_NET_URL_INIT; + int error = 0; + + GIT_ASSERT(url); + GIT_ASSERT(redirect_location); + + if (redirect_location[0] == '/') { + git__free(url->path); + + if ((url->path = git__strdup(redirect_location)) == NULL) { + error = -1; + goto done; + } + } else { + git_net_url *original = url; + + if ((error = git_net_url_parse(&tmp, redirect_location)) < 0) + goto done; + + /* Validate that this is a legal redirection */ + + if (original->scheme && + strcmp(original->scheme, tmp.scheme) != 0 && + strcmp(tmp.scheme, "https") != 0) { + git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", + original->scheme, tmp.scheme); + + error = -1; + goto done; + } + + if (original->host && + !allow_offsite && + git__strcasecmp(original->host, tmp.host) != 0) { + git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", + original->host, tmp.host); + + error = -1; + goto done; + } + + git_net_url_swap(url, &tmp); + } + + /* Remove the service suffix if it was given to us */ + if (service_suffix) + remove_service_suffix(url, service_suffix); + +done: + git_net_url_dispose(&tmp); + return error; +} + +bool git_net_url_valid(git_net_url *url) +{ + return (url->host && url->port && url->path); +} + +bool git_net_url_is_default_port(git_net_url *url) +{ + const char *default_port; + + if ((default_port = default_port_for_scheme(url->scheme)) != NULL) + return (strcmp(url->port, default_port) == 0); + else + return false; +} + +bool git_net_url_is_ipv6(git_net_url *url) +{ + return (strchr(url->host, ':') != NULL); +} + +void git_net_url_swap(git_net_url *a, git_net_url *b) +{ + git_net_url tmp = GIT_NET_URL_INIT; + + memcpy(&tmp, a, sizeof(git_net_url)); + memcpy(a, b, sizeof(git_net_url)); + memcpy(b, &tmp, sizeof(git_net_url)); +} + +int git_net_url_fmt(git_str *buf, git_net_url *url) +{ + GIT_ASSERT_ARG(url); + GIT_ASSERT_ARG(url->scheme); + GIT_ASSERT_ARG(url->host); + + git_str_puts(buf, url->scheme); + git_str_puts(buf, "://"); + + if (url->username) { + git_str_puts(buf, url->username); + + if (url->password) { + git_str_puts(buf, ":"); + git_str_puts(buf, url->password); + } + + git_str_putc(buf, '@'); + } + + git_str_puts(buf, url->host); + + if (url->port && !git_net_url_is_default_port(url)) { + git_str_putc(buf, ':'); + git_str_puts(buf, url->port); + } + + git_str_puts(buf, url->path ? url->path : "/"); + + if (url->query) { + git_str_putc(buf, '?'); + git_str_puts(buf, url->query); + } + + return git_str_oom(buf) ? -1 : 0; +} + +int git_net_url_fmt_path(git_str *buf, git_net_url *url) +{ + git_str_puts(buf, url->path ? url->path : "/"); + + if (url->query) { + git_str_putc(buf, '?'); + git_str_puts(buf, url->query); + } + + return git_str_oom(buf) ? -1 : 0; +} + +static bool matches_pattern( + git_net_url *url, + const char *pattern, + size_t pattern_len) +{ + const char *domain, *port = NULL, *colon; + size_t host_len, domain_len, port_len = 0, wildcard = 0; + + GIT_UNUSED(url); + GIT_UNUSED(pattern); + + if (!pattern_len) + return false; + else if (pattern_len == 1 && pattern[0] == '*') + return true; + else if (pattern_len > 1 && pattern[0] == '*' && pattern[1] == '.') + wildcard = 2; + else if (pattern[0] == '.') + wildcard = 1; + + domain = pattern + wildcard; + domain_len = pattern_len - wildcard; + + if ((colon = memchr(domain, ':', domain_len)) != NULL) { + domain_len = colon - domain; + port = colon + 1; + port_len = pattern_len - wildcard - domain_len - 1; + } + + /* A pattern's port *must* match if it's specified */ + if (port_len && git__strlcmp(url->port, port, port_len) != 0) + return false; + + /* No wildcard? Host must match exactly. */ + if (!wildcard) + return !git__strlcmp(url->host, domain, domain_len); + + /* Wildcard: ensure there's (at least) a suffix match */ + if ((host_len = strlen(url->host)) < domain_len || + memcmp(url->host + (host_len - domain_len), domain, domain_len)) + return false; + + /* The pattern is *.domain and the host is simply domain */ + if (host_len == domain_len) + return true; + + /* The pattern is *.domain and the host is foo.domain */ + return (url->host[host_len - domain_len - 1] == '.'); +} + +bool git_net_url_matches_pattern(git_net_url *url, const char *pattern) +{ + return matches_pattern(url, pattern, strlen(pattern)); +} + +bool git_net_url_matches_pattern_list( + git_net_url *url, + const char *pattern_list) +{ + const char *pattern, *pattern_end, *sep; + + for (pattern = pattern_list; + pattern && *pattern; + pattern = sep ? sep + 1 : NULL) { + sep = strchr(pattern, ','); + pattern_end = sep ? sep : strchr(pattern, '\0'); + + if (matches_pattern(url, pattern, (pattern_end - pattern))) + return true; + } + + return false; +} + +void git_net_url_dispose(git_net_url *url) +{ + if (url->username) + git__memzero(url->username, strlen(url->username)); + + if (url->password) + git__memzero(url->password, strlen(url->password)); + + git__free(url->scheme); url->scheme = NULL; + git__free(url->host); url->host = NULL; + git__free(url->port); url->port = NULL; + git__free(url->path); url->path = NULL; + git__free(url->query); url->query = NULL; + git__free(url->username); url->username = NULL; + git__free(url->password); url->password = NULL; +} diff --git a/src/libgit2/net.h b/src/libgit2/net.h new file mode 100644 index 000000000..499315e6c --- /dev/null +++ b/src/libgit2/net.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_net_h__ +#define INCLUDE_net_h__ + +#include "common.h" + +typedef struct git_net_url { + char *scheme; + char *host; + char *port; + char *path; + char *query; + char *username; + char *password; +} git_net_url; + +#define GIT_NET_URL_INIT { NULL } + +/** Is a given string a url? */ +extern bool git_net_str_is_url(const char *str); + +/** Duplicate a URL */ +extern int git_net_url_dup(git_net_url *out, git_net_url *in); + +/** Parses a string containing a URL into a structure. */ +extern int git_net_url_parse(git_net_url *url, const char *str); + +/** Parses a string containing an SCP style path into a URL structure. */ +extern int git_net_url_parse_scp(git_net_url *url, const char *str); + +/** Appends a path and/or query string to the given URL */ +extern int git_net_url_joinpath( + git_net_url *out, + git_net_url *in, + const char *path); + +/** Ensures that a URL is minimally valid (contains a host, port and path) */ +extern bool git_net_url_valid(git_net_url *url); + +/** Returns true if the URL is on the default port. */ +extern bool git_net_url_is_default_port(git_net_url *url); + +/** Returns true if the host portion of the URL is an ipv6 address. */ +extern bool git_net_url_is_ipv6(git_net_url *url); + +/* Applies a redirect to the URL with a git-aware service suffix. */ +extern int git_net_url_apply_redirect( + git_net_url *url, + const char *redirect_location, + bool allow_offsite, + const char *service_suffix); + +/** Swaps the contents of one URL for another. */ +extern void git_net_url_swap(git_net_url *a, git_net_url *b); + +/** Places the URL into the given buffer. */ +extern int git_net_url_fmt(git_str *out, git_net_url *url); + +/** Place the path and query string into the given buffer. */ +extern int git_net_url_fmt_path(git_str *buf, git_net_url *url); + +/** Determines if the url matches given pattern or pattern list */ +extern bool git_net_url_matches_pattern( + git_net_url *url, + const char *pattern); +extern bool git_net_url_matches_pattern_list( + git_net_url *url, + const char *pattern_list); + +/** Disposes the contents of the structure. */ +extern void git_net_url_dispose(git_net_url *url); + +#endif diff --git a/src/libgit2/netops.c b/src/libgit2/netops.c new file mode 100644 index 000000000..0a27365b8 --- /dev/null +++ b/src/libgit2/netops.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "netops.h" + +#include +#include "git2/errors.h" + +#include "posix.h" +#include "str.h" +#include "http_parser.h" +#include "runtime.h" + +int gitno_recv(gitno_buffer *buf) +{ + return buf->recv(buf); +} + +void gitno_buffer_setup_callback( + gitno_buffer *buf, + char *data, + size_t len, + int (*recv)(gitno_buffer *buf), void *cb_data) +{ + memset(data, 0x0, len); + buf->data = data; + buf->len = len; + buf->offset = 0; + buf->recv = recv; + buf->cb_data = cb_data; +} + +static int recv_stream(gitno_buffer *buf) +{ + git_stream *io = (git_stream *) buf->cb_data; + size_t readlen = buf->len - buf->offset; + ssize_t ret; + + readlen = min(readlen, INT_MAX); + + ret = git_stream_read(io, buf->data + buf->offset, (int)readlen); + if (ret < 0) + return -1; + + buf->offset += ret; + return (int)ret; +} + +void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len) +{ + memset(data, 0x0, len); + buf->data = data; + buf->len = len; + buf->offset = 0; + buf->recv = recv_stream; + buf->cb_data = st; +} + +/* Consume up to ptr and move the rest of the buffer to the beginning */ +int gitno_consume(gitno_buffer *buf, const char *ptr) +{ + size_t consumed; + + GIT_ASSERT(ptr - buf->data >= 0); + GIT_ASSERT(ptr - buf->data <= (int) buf->len); + + consumed = ptr - buf->data; + + memmove(buf->data, ptr, buf->offset - consumed); + memset(buf->data + buf->offset, 0x0, buf->len - buf->offset); + buf->offset -= consumed; + + return 0; +} + +/* Consume const bytes and move the rest of the buffer to the beginning */ +void gitno_consume_n(gitno_buffer *buf, size_t cons) +{ + memmove(buf->data, buf->data + cons, buf->len - buf->offset); + memset(buf->data + cons, 0x0, buf->len - buf->offset); + buf->offset -= cons; +} + +/* Match host names according to RFC 2818 rules */ +int gitno__match_host(const char *pattern, const char *host) +{ + for (;;) { + char c = git__tolower(*pattern++); + + if (c == '\0') + return *host ? -1 : 0; + + if (c == '*') { + c = *pattern; + /* '*' at the end matches everything left */ + if (c == '\0') + return 0; + + /* + * We've found a pattern, so move towards the next matching + * char. The '.' is handled specially because wildcards aren't + * allowed to cross subdomains. + */ + + while(*host) { + char h = git__tolower(*host); + if (c == h) + return gitno__match_host(pattern, host++); + if (h == '.') + return gitno__match_host(pattern, host); + host++; + } + return -1; + } + + if (c != git__tolower(*host++)) + return -1; + } + + return -1; +} diff --git a/src/libgit2/netops.h b/src/libgit2/netops.h new file mode 100644 index 000000000..56f968534 --- /dev/null +++ b/src/libgit2/netops.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_netops_h__ +#define INCLUDE_netops_h__ + +#include "common.h" + +#include "posix.h" +#include "stream.h" +#include "net.h" + +#ifdef GIT_OPENSSL +# include "streams/openssl.h" +#endif + +typedef struct gitno_ssl { +#ifdef GIT_OPENSSL + SSL *ssl; +#else + size_t dummy; +#endif +} gitno_ssl; + +/* Represents a socket that may or may not be using SSL */ +typedef struct gitno_socket { + GIT_SOCKET socket; + gitno_ssl ssl; +} gitno_socket; + +typedef struct gitno_buffer { + char *data; + size_t len; + size_t offset; + int (*recv)(struct gitno_buffer *buffer); + void *cb_data; +} gitno_buffer; + +/* Flags to gitno_connect */ +enum { + /* Attempt to create an SSL connection. */ + GITNO_CONNECT_SSL = 1 +}; + +/** + * Check if the name in a cert matches the wanted hostname + * + * Check if a pattern from a certificate matches the hostname we + * wanted to connect to according to RFC2818 rules (which specifies + * HTTP over TLS). Mainly, an asterisk matches anything, but is + * limited to a single url component. + * + * Note that this does not set an error message. It expects the user + * to provide the message for the user. + */ +int gitno__match_host(const char *pattern, const char *host); + +void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len); +void gitno_buffer_setup_callback(gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data); +int gitno_recv(gitno_buffer *buf); + +int gitno_consume(gitno_buffer *buf, const char *ptr); +void gitno_consume_n(gitno_buffer *buf, size_t cons); + +#endif diff --git a/src/libgit2/notes.c b/src/libgit2/notes.c new file mode 100644 index 000000000..d1a2b0f64 --- /dev/null +++ b/src/libgit2/notes.c @@ -0,0 +1,809 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "notes.h" + +#include "buf.h" +#include "refs.h" +#include "config.h" +#include "iterator.h" +#include "signature.h" +#include "blob.h" + +static int note_error_notfound(void) +{ + git_error_set(GIT_ERROR_INVALID, "note could not be found"); + return GIT_ENOTFOUND; +} + +static int find_subtree_in_current_level( + git_tree **out, + git_repository *repo, + git_tree *parent, + const char *annotated_object_sha, + int fanout) +{ + size_t i; + const git_tree_entry *entry; + + *out = NULL; + + if (parent == NULL) + return note_error_notfound(); + + for (i = 0; i < git_tree_entrycount(parent); i++) { + entry = git_tree_entry_byindex(parent, i); + + if (!git__ishex(git_tree_entry_name(entry))) + continue; + + if (S_ISDIR(git_tree_entry_filemode(entry)) + && strlen(git_tree_entry_name(entry)) == 2 + && !strncmp(git_tree_entry_name(entry), annotated_object_sha + fanout, 2)) + return git_tree_lookup(out, repo, git_tree_entry_id(entry)); + + /* Not a DIR, so do we have an already existing blob? */ + if (!strcmp(git_tree_entry_name(entry), annotated_object_sha + fanout)) + return GIT_EEXISTS; + } + + return note_error_notfound(); +} + +static int find_subtree_r(git_tree **out, git_tree *root, + git_repository *repo, const char *target, int *fanout) +{ + int error; + git_tree *subtree = NULL; + + *out = NULL; + + error = find_subtree_in_current_level(&subtree, repo, root, target, *fanout); + if (error == GIT_EEXISTS) + return git_tree_lookup(out, repo, git_tree_id(root)); + + if (error < 0) + return error; + + *fanout += 2; + error = find_subtree_r(out, subtree, repo, target, fanout); + git_tree_free(subtree); + + return error; +} + +static int find_blob(git_oid *blob, git_tree *tree, const char *target) +{ + size_t i; + const git_tree_entry *entry; + + for (i=0; iid, note_oid); + + if (git_signature_dup(¬e->author, git_commit_author(commit)) < 0 || + git_signature_dup(¬e->committer, git_commit_committer(commit)) < 0) + return -1; + + blobsize = git_blob_rawsize(blob); + GIT_ERROR_CHECK_BLOBSIZE(blobsize); + + note->message = git__strndup(git_blob_rawcontent(blob), (size_t)blobsize); + GIT_ERROR_CHECK_ALLOC(note->message); + + *out = note; + return 0; +} + +static int note_lookup( + git_note **out, + git_repository *repo, + git_commit *commit, + git_tree *tree, + const char *target) +{ + int error, fanout = 0; + git_oid oid; + git_blob *blob = NULL; + git_note *note = NULL; + git_tree *subtree = NULL; + + if ((error = find_subtree_r(&subtree, tree, repo, target, &fanout)) < 0) + goto cleanup; + + if ((error = find_blob(&oid, subtree, target + fanout)) < 0) + goto cleanup; + + if ((error = git_blob_lookup(&blob, repo, &oid)) < 0) + goto cleanup; + + if ((error = note_new(¬e, &oid, commit, blob)) < 0) + goto cleanup; + + *out = note; + +cleanup: + git_tree_free(subtree); + git_blob_free(blob); + return error; +} + +static int note_remove( + git_oid *notes_commit_out, + git_repository *repo, + const git_signature *author, const git_signature *committer, + const char *notes_ref, git_tree *tree, + const char *target, git_commit **parents) +{ + int error; + git_tree *tree_after_removal = NULL; + git_oid oid; + + if ((error = manipulate_note_in_tree_r( + &tree_after_removal, repo, tree, NULL, target, 0, + remove_note_in_tree_eexists_cb, remove_note_in_tree_enotfound_cb)) < 0) + goto cleanup; + + error = git_commit_create(&oid, repo, notes_ref, author, committer, + NULL, GIT_NOTES_DEFAULT_MSG_RM, + tree_after_removal, + *parents == NULL ? 0 : 1, + (const git_commit **) parents); + + if (error < 0) + goto cleanup; + + if (notes_commit_out) + git_oid_cpy(notes_commit_out, &oid); + +cleanup: + git_tree_free(tree_after_removal); + return error; +} + +static int note_get_default_ref(git_str *out, git_repository *repo) +{ + git_config *cfg; + int error; + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + return error; + + error = git_config__get_string_buf(out, cfg, "core.notesref"); + + if (error == GIT_ENOTFOUND) + error = git_str_puts(out, GIT_NOTES_DEFAULT_REF); + + return error; +} + +static int normalize_namespace(git_str *out, git_repository *repo, const char *notes_ref) +{ + if (notes_ref) + return git_str_puts(out, notes_ref); + + return note_get_default_ref(out, repo); +} + +static int retrieve_note_commit( + git_commit **commit_out, + git_str *notes_ref_out, + git_repository *repo, + const char *notes_ref) +{ + int error; + git_oid oid; + + if ((error = normalize_namespace(notes_ref_out, repo, notes_ref)) < 0) + return error; + + if ((error = git_reference_name_to_id(&oid, repo, notes_ref_out->ptr)) < 0) + return error; + + if (git_commit_lookup(commit_out, repo, &oid) < 0) + return error; + + return 0; +} + +int git_note_commit_read( + git_note **out, + git_repository *repo, + git_commit *notes_commit, + const git_oid *oid) +{ + int error; + git_tree *tree = NULL; + char target[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(target, sizeof(target), oid); + + if ((error = git_commit_tree(&tree, notes_commit)) < 0) + goto cleanup; + + error = note_lookup(out, repo, notes_commit, tree, target); + +cleanup: + git_tree_free(tree); + return error; +} + +int git_note_read(git_note **out, git_repository *repo, + const char *notes_ref_in, const git_oid *oid) +{ + int error; + git_str notes_ref = GIT_STR_INIT; + git_commit *commit = NULL; + + error = retrieve_note_commit(&commit, ¬es_ref, repo, notes_ref_in); + + if (error < 0) + goto cleanup; + + error = git_note_commit_read(out, repo, commit, oid); + +cleanup: + git_str_dispose(¬es_ref); + git_commit_free(commit); + return error; +} + +int git_note_commit_create( + git_oid *notes_commit_out, + git_oid *notes_blob_out, + git_repository *repo, + git_commit *parent, + const git_signature *author, + const git_signature *committer, + const git_oid *oid, + const char *note, + int allow_note_overwrite) +{ + int error; + git_tree *tree = NULL; + char target[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(target, sizeof(target), oid); + + if (parent != NULL && (error = git_commit_tree(&tree, parent)) < 0) + goto cleanup; + + error = note_write(notes_commit_out, notes_blob_out, repo, author, + committer, NULL, note, tree, target, &parent, allow_note_overwrite); + + if (error < 0) + goto cleanup; + +cleanup: + git_tree_free(tree); + return error; +} + +int git_note_create( + git_oid *out, + git_repository *repo, + const char *notes_ref_in, + const git_signature *author, + const git_signature *committer, + const git_oid *oid, + const char *note, + int allow_note_overwrite) +{ + int error; + git_str notes_ref = GIT_STR_INIT; + git_commit *existing_notes_commit = NULL; + git_reference *ref = NULL; + git_oid notes_blob_oid, notes_commit_oid; + + error = retrieve_note_commit(&existing_notes_commit, ¬es_ref, + repo, notes_ref_in); + + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + error = git_note_commit_create(¬es_commit_oid, + ¬es_blob_oid, + repo, existing_notes_commit, author, + committer, oid, note, + allow_note_overwrite); + if (error < 0) + goto cleanup; + + error = git_reference_create(&ref, repo, notes_ref.ptr, + ¬es_commit_oid, 1, NULL); + + if (out != NULL) + git_oid_cpy(out, ¬es_blob_oid); + +cleanup: + git_str_dispose(¬es_ref); + git_commit_free(existing_notes_commit); + git_reference_free(ref); + return error; +} + +int git_note_commit_remove( + git_oid *notes_commit_out, + git_repository *repo, + git_commit *notes_commit, + const git_signature *author, + const git_signature *committer, + const git_oid *oid) +{ + int error; + git_tree *tree = NULL; + char target[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(target, sizeof(target), oid); + + if ((error = git_commit_tree(&tree, notes_commit)) < 0) + goto cleanup; + + error = note_remove(notes_commit_out, + repo, author, committer, NULL, tree, target, ¬es_commit); + +cleanup: + git_tree_free(tree); + return error; +} + +int git_note_remove(git_repository *repo, const char *notes_ref_in, + const git_signature *author, const git_signature *committer, + const git_oid *oid) +{ + int error; + git_str notes_ref_target = GIT_STR_INIT; + git_commit *existing_notes_commit = NULL; + git_oid new_notes_commit; + git_reference *notes_ref = NULL; + + error = retrieve_note_commit(&existing_notes_commit, ¬es_ref_target, + repo, notes_ref_in); + + if (error < 0) + goto cleanup; + + error = git_note_commit_remove(&new_notes_commit, repo, + existing_notes_commit, author, committer, oid); + if (error < 0) + goto cleanup; + + error = git_reference_create(¬es_ref, repo, notes_ref_target.ptr, + &new_notes_commit, 1, NULL); + +cleanup: + git_str_dispose(¬es_ref_target); + git_reference_free(notes_ref); + git_commit_free(existing_notes_commit); + return error; +} + +int git_note_default_ref(git_buf *out, git_repository *repo) +{ + GIT_BUF_WRAP_PRIVATE(out, note_get_default_ref, repo); +} + +const git_signature *git_note_committer(const git_note *note) +{ + GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); + return note->committer; +} + +const git_signature *git_note_author(const git_note *note) +{ + GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); + return note->author; +} + +const char *git_note_message(const git_note *note) +{ + GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); + return note->message; +} + +const git_oid *git_note_id(const git_note *note) +{ + GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); + return ¬e->id; +} + +void git_note_free(git_note *note) +{ + if (note == NULL) + return; + + git_signature_free(note->committer); + git_signature_free(note->author); + git__free(note->message); + git__free(note); +} + +static int process_entry_path( + const char *entry_path, + git_oid *annotated_object_id) +{ + int error = 0; + size_t i = 0, j = 0, len; + git_str buf = GIT_STR_INIT; + + if ((error = git_str_puts(&buf, entry_path)) < 0) + goto cleanup; + + len = git_str_len(&buf); + + while (i < len) { + if (buf.ptr[i] == '/') { + i++; + continue; + } + + if (git__fromhex(buf.ptr[i]) < 0) { + /* This is not a note entry */ + goto cleanup; + } + + if (i != j) + buf.ptr[j] = buf.ptr[i]; + + i++; + j++; + } + + buf.ptr[j] = '\0'; + buf.size = j; + + if (j != GIT_OID_HEXSZ) { + /* This is not a note entry */ + goto cleanup; + } + + error = git_oid_fromstr(annotated_object_id, buf.ptr); + +cleanup: + git_str_dispose(&buf); + return error; +} + +int git_note_foreach( + git_repository *repo, + const char *notes_ref, + git_note_foreach_cb note_cb, + void *payload) +{ + int error; + git_note_iterator *iter = NULL; + git_oid note_id, annotated_id; + + if ((error = git_note_iterator_new(&iter, repo, notes_ref)) < 0) + return error; + + while (!(error = git_note_next(¬e_id, &annotated_id, iter))) { + if ((error = note_cb(¬e_id, &annotated_id, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + if (error == GIT_ITEROVER) + error = 0; + + git_note_iterator_free(iter); + return error; +} + +void git_note_iterator_free(git_note_iterator *it) +{ + if (it == NULL) + return; + + git_iterator_free(it); +} + +int git_note_commit_iterator_new( + git_note_iterator **it, + git_commit *notes_commit) +{ + int error; + git_tree *tree; + + if ((error = git_commit_tree(&tree, notes_commit)) < 0) + goto cleanup; + + if ((error = git_iterator_for_tree(it, tree, NULL)) < 0) + git_iterator_free(*it); + +cleanup: + git_tree_free(tree); + + return error; +} + +int git_note_iterator_new( + git_note_iterator **it, + git_repository *repo, + const char *notes_ref_in) +{ + int error; + git_commit *commit = NULL; + git_str notes_ref = GIT_STR_INIT; + + error = retrieve_note_commit(&commit, ¬es_ref, repo, notes_ref_in); + if (error < 0) + goto cleanup; + + error = git_note_commit_iterator_new(it, commit); + +cleanup: + git_str_dispose(¬es_ref); + git_commit_free(commit); + + return error; +} + +int git_note_next( + git_oid *note_id, + git_oid *annotated_id, + git_note_iterator *it) +{ + int error; + const git_index_entry *item; + + if ((error = git_iterator_current(&item, it)) < 0) + return error; + + git_oid_cpy(note_id, &item->id); + + if ((error = process_entry_path(item->path, annotated_id)) < 0) + return error; + + if ((error = git_iterator_advance(NULL, it)) < 0 && error != GIT_ITEROVER) + return error; + + return 0; +} diff --git a/src/libgit2/notes.h b/src/libgit2/notes.h new file mode 100644 index 000000000..2168e4595 --- /dev/null +++ b/src/libgit2/notes.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_note_h__ +#define INCLUDE_note_h__ + +#include "common.h" + +#include "git2/oid.h" +#include "git2/types.h" + +#define GIT_NOTES_DEFAULT_REF "refs/notes/commits" + +#define GIT_NOTES_DEFAULT_MSG_ADD \ + "Notes added by 'git_note_create' from libgit2" + +#define GIT_NOTES_DEFAULT_MSG_RM \ + "Notes removed by 'git_note_remove' from libgit2" + +struct git_note { + git_oid id; + + git_signature *author; + git_signature *committer; + + char *message; +}; + +#endif diff --git a/src/libgit2/object.c b/src/libgit2/object.c new file mode 100644 index 000000000..7bc256fce --- /dev/null +++ b/src/libgit2/object.c @@ -0,0 +1,601 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "object.h" + +#include "git2/object.h" + +#include "repository.h" + +#include "buf.h" +#include "commit.h" +#include "hash.h" +#include "tree.h" +#include "blob.h" +#include "oid.h" +#include "tag.h" + +bool git_object__strict_input_validation = true; + +extern int git_odb_hash(git_oid *out, const void *data, size_t len, git_object_t type); +size_t git_object__size(git_object_t type); + +typedef struct { + const char *str; /* type name string */ + size_t size; /* size in bytes of the object structure */ + + int (*parse)(void *self, git_odb_object *obj); + int (*parse_raw)(void *self, const char *data, size_t size); + void (*free)(void *self); +} git_object_def; + +static git_object_def git_objects_table[] = { + /* 0 = GIT_OBJECT__EXT1 */ + { "", 0, NULL, NULL, NULL }, + + /* 1 = GIT_OBJECT_COMMIT */ + { "commit", sizeof(git_commit), git_commit__parse, git_commit__parse_raw, git_commit__free }, + + /* 2 = GIT_OBJECT_TREE */ + { "tree", sizeof(git_tree), git_tree__parse, git_tree__parse_raw, git_tree__free }, + + /* 3 = GIT_OBJECT_BLOB */ + { "blob", sizeof(git_blob), git_blob__parse, git_blob__parse_raw, git_blob__free }, + + /* 4 = GIT_OBJECT_TAG */ + { "tag", sizeof(git_tag), git_tag__parse, git_tag__parse_raw, git_tag__free }, + + /* 5 = GIT_OBJECT__EXT2 */ + { "", 0, NULL, NULL, NULL }, + /* 6 = GIT_OBJECT_OFS_DELTA */ + { "OFS_DELTA", 0, NULL, NULL, NULL }, + /* 7 = GIT_OBJECT_REF_DELTA */ + { "REF_DELTA", 0, NULL, NULL, NULL }, +}; + +int git_object__from_raw( + git_object **object_out, + const char *data, + size_t size, + git_object_t type) +{ + git_object_def *def; + git_object *object; + size_t object_size; + int error; + + GIT_ASSERT_ARG(object_out); + *object_out = NULL; + + /* Validate type match */ + if (type != GIT_OBJECT_BLOB && type != GIT_OBJECT_TREE && type != GIT_OBJECT_COMMIT && type != GIT_OBJECT_TAG) { + git_error_set(GIT_ERROR_INVALID, "the requested type is invalid"); + return GIT_ENOTFOUND; + } + + if ((object_size = git_object__size(type)) == 0) { + git_error_set(GIT_ERROR_INVALID, "the requested type is invalid"); + return GIT_ENOTFOUND; + } + + /* Allocate and initialize base object */ + object = git__calloc(1, object_size); + GIT_ERROR_CHECK_ALLOC(object); + object->cached.flags = GIT_CACHE_STORE_PARSED; + object->cached.type = type; + if ((error = git_odb_hash(&object->cached.oid, data, size, type)) < 0) + return error; + + /* Parse raw object data */ + def = &git_objects_table[type]; + GIT_ASSERT(def->free && def->parse_raw); + + if ((error = def->parse_raw(object, data, size)) < 0) { + def->free(object); + return error; + } + + git_cached_obj_incref(object); + *object_out = object; + + return 0; +} + +int git_object__from_odb_object( + git_object **object_out, + git_repository *repo, + git_odb_object *odb_obj, + git_object_t type) +{ + int error; + size_t object_size; + git_object_def *def; + git_object *object = NULL; + + GIT_ASSERT_ARG(object_out); + *object_out = NULL; + + /* Validate type match */ + if (type != GIT_OBJECT_ANY && type != odb_obj->cached.type) { + git_error_set(GIT_ERROR_INVALID, + "the requested type does not match the type in the ODB"); + return GIT_ENOTFOUND; + } + + if ((object_size = git_object__size(odb_obj->cached.type)) == 0) { + git_error_set(GIT_ERROR_INVALID, "the requested type is invalid"); + return GIT_ENOTFOUND; + } + + /* Allocate and initialize base object */ + object = git__calloc(1, object_size); + GIT_ERROR_CHECK_ALLOC(object); + + git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid); + object->cached.type = odb_obj->cached.type; + object->cached.size = odb_obj->cached.size; + object->repo = repo; + + /* Parse raw object data */ + def = &git_objects_table[odb_obj->cached.type]; + GIT_ASSERT(def->free && def->parse); + + if ((error = def->parse(object, odb_obj)) < 0) { + /* + * parse returns EINVALID on invalid data; downgrade + * that to a normal -1 error code. + */ + def->free(object); + return -1; + } + + *object_out = git_cache_store_parsed(&repo->objects, object); + return 0; +} + +void git_object__free(void *obj) +{ + git_object_t type = ((git_object *)obj)->cached.type; + + if (type < 0 || ((size_t)type) >= ARRAY_SIZE(git_objects_table) || + !git_objects_table[type].free) + git__free(obj); + else + git_objects_table[type].free(obj); +} + +int git_object_lookup_prefix( + git_object **object_out, + git_repository *repo, + const git_oid *id, + size_t len, + git_object_t type) +{ + git_object *object = NULL; + git_odb *odb = NULL; + git_odb_object *odb_obj = NULL; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(object_out); + GIT_ASSERT_ARG(id); + + if (len < GIT_OID_MINPREFIXLEN) { + git_error_set(GIT_ERROR_OBJECT, "ambiguous lookup - OID prefix is too short"); + return GIT_EAMBIGUOUS; + } + + error = git_repository_odb__weakptr(&odb, repo); + if (error < 0) + return error; + + if (len > GIT_OID_HEXSZ) + len = GIT_OID_HEXSZ; + + if (len == GIT_OID_HEXSZ) { + git_cached_obj *cached = NULL; + + /* We want to match the full id : we can first look up in the cache, + * since there is no need to check for non ambiguousity + */ + cached = git_cache_get_any(&repo->objects, id); + if (cached != NULL) { + if (cached->flags == GIT_CACHE_STORE_PARSED) { + object = (git_object *)cached; + + if (type != GIT_OBJECT_ANY && type != object->cached.type) { + git_object_free(object); + git_error_set(GIT_ERROR_INVALID, + "the requested type does not match the type in the ODB"); + return GIT_ENOTFOUND; + } + + *object_out = object; + return 0; + } else if (cached->flags == GIT_CACHE_STORE_RAW) { + odb_obj = (git_odb_object *)cached; + } else { + GIT_ASSERT(!"Wrong caching type in the global object cache"); + } + } else { + /* Object was not found in the cache, let's explore the backends. + * We could just use git_odb_read_unique_short_oid, + * it is the same cost for packed and loose object backends, + * but it may be much more costly for sqlite and hiredis. + */ + error = git_odb_read(&odb_obj, odb, id); + } + } else { + git_oid short_oid = {{ 0 }}; + + git_oid__cpy_prefix(&short_oid, id, len); + + /* If len < GIT_OID_HEXSZ (a strict short oid was given), we have + * 2 options : + * - We always search in the cache first. If we find that short oid is + * ambiguous, we can stop. But in all the other cases, we must then + * explore all the backends (to find an object if there was match, + * or to check that oid is not ambiguous if we have found 1 match in + * the cache) + * - We never explore the cache, go right to exploring the backends + * We chose the latter : we explore directly the backends. + */ + error = git_odb_read_prefix(&odb_obj, odb, &short_oid, len); + } + + if (error < 0) + return error; + + error = git_object__from_odb_object(object_out, repo, odb_obj, type); + + git_odb_object_free(odb_obj); + + return error; +} + +int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_object_t type) { + return git_object_lookup_prefix(object_out, repo, id, GIT_OID_HEXSZ, type); +} + +void git_object_free(git_object *object) +{ + if (object == NULL) + return; + + git_cached_obj_decref(object); +} + +const git_oid *git_object_id(const git_object *obj) +{ + GIT_ASSERT_ARG_WITH_RETVAL(obj, NULL); + return &obj->cached.oid; +} + +git_object_t git_object_type(const git_object *obj) +{ + GIT_ASSERT_ARG_WITH_RETVAL(obj, GIT_OBJECT_INVALID); + return obj->cached.type; +} + +git_repository *git_object_owner(const git_object *obj) +{ + GIT_ASSERT_ARG_WITH_RETVAL(obj, NULL); + return obj->repo; +} + +const char *git_object_type2string(git_object_t type) +{ + if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table)) + return ""; + + return git_objects_table[type].str; +} + +git_object_t git_object_string2type(const char *str) +{ + if (!str) + return GIT_OBJECT_INVALID; + + return git_object_stringn2type(str, strlen(str)); +} + +git_object_t git_object_stringn2type(const char *str, size_t len) +{ + size_t i; + + if (!str || !len || !*str) + return GIT_OBJECT_INVALID; + + for (i = 0; i < ARRAY_SIZE(git_objects_table); i++) + if (*git_objects_table[i].str && + !git__prefixncmp(str, len, git_objects_table[i].str)) + return (git_object_t)i; + + return GIT_OBJECT_INVALID; +} + +int git_object_typeisloose(git_object_t type) +{ + if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table)) + return 0; + + return (git_objects_table[type].size > 0) ? 1 : 0; +} + +size_t git_object__size(git_object_t type) +{ + if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table)) + return 0; + + return git_objects_table[type].size; +} + +static int dereference_object(git_object **dereferenced, git_object *obj) +{ + git_object_t type = git_object_type(obj); + + switch (type) { + case GIT_OBJECT_COMMIT: + return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj); + + case GIT_OBJECT_TAG: + return git_tag_target(dereferenced, (git_tag*)obj); + + case GIT_OBJECT_BLOB: + case GIT_OBJECT_TREE: + return GIT_EPEEL; + + default: + return GIT_EINVALIDSPEC; + } +} + +static int peel_error(int error, const git_oid *oid, git_object_t type) +{ + const char *type_name; + char hex_oid[GIT_OID_HEXSZ + 1]; + + type_name = git_object_type2string(type); + + git_oid_fmt(hex_oid, oid); + hex_oid[GIT_OID_HEXSZ] = '\0'; + + git_error_set(GIT_ERROR_OBJECT, "the git_object of id '%s' can not be " + "successfully peeled into a %s (git_object_t=%i).", hex_oid, type_name, type); + + return error; +} + +static int check_type_combination(git_object_t type, git_object_t target) +{ + if (type == target) + return 0; + + switch (type) { + case GIT_OBJECT_BLOB: + case GIT_OBJECT_TREE: + /* a blob or tree can never be peeled to anything but themselves */ + return GIT_EINVALIDSPEC; + break; + case GIT_OBJECT_COMMIT: + /* a commit can only be peeled to a tree */ + if (target != GIT_OBJECT_TREE && target != GIT_OBJECT_ANY) + return GIT_EINVALIDSPEC; + break; + case GIT_OBJECT_TAG: + /* a tag may point to anything, so we let anything through */ + break; + default: + return GIT_EINVALIDSPEC; + } + + return 0; +} + +int git_object_peel( + git_object **peeled, + const git_object *object, + git_object_t target_type) +{ + git_object *source, *deref = NULL; + int error; + + GIT_ASSERT_ARG(object); + GIT_ASSERT_ARG(peeled); + + GIT_ASSERT_ARG(target_type == GIT_OBJECT_TAG || + target_type == GIT_OBJECT_COMMIT || + target_type == GIT_OBJECT_TREE || + target_type == GIT_OBJECT_BLOB || + target_type == GIT_OBJECT_ANY); + + if ((error = check_type_combination(git_object_type(object), target_type)) < 0) + return peel_error(error, git_object_id(object), target_type); + + if (git_object_type(object) == target_type) + return git_object_dup(peeled, (git_object *)object); + + source = (git_object *)object; + + while (!(error = dereference_object(&deref, source))) { + + if (source != object) + git_object_free(source); + + if (git_object_type(deref) == target_type) { + *peeled = deref; + return 0; + } + + if (target_type == GIT_OBJECT_ANY && + git_object_type(deref) != git_object_type(object)) + { + *peeled = deref; + return 0; + } + + source = deref; + deref = NULL; + } + + if (source != object) + git_object_free(source); + + git_object_free(deref); + + if (error) + error = peel_error(error, git_object_id(object), target_type); + + return error; +} + +int git_object_dup(git_object **dest, git_object *source) +{ + git_cached_obj_incref(source); + *dest = source; + return 0; +} + +int git_object_lookup_bypath( + git_object **out, + const git_object *treeish, + const char *path, + git_object_t type) +{ + int error = -1; + git_tree *tree = NULL; + git_tree_entry *entry = NULL; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(treeish); + GIT_ASSERT_ARG(path); + + if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJECT_TREE)) < 0 || + (error = git_tree_entry_bypath(&entry, tree, path)) < 0) + { + goto cleanup; + } + + if (type != GIT_OBJECT_ANY && git_tree_entry_type(entry) != type) + { + git_error_set(GIT_ERROR_OBJECT, + "object at path '%s' is not of the asked-for type %d", + path, type); + error = GIT_EINVALIDSPEC; + goto cleanup; + } + + error = git_tree_entry_to_object(out, git_object_owner(treeish), entry); + +cleanup: + git_tree_entry_free(entry); + git_tree_free(tree); + return error; +} + +static int git_object__short_id(git_str *out, const git_object *obj) +{ + git_repository *repo; + int len = GIT_ABBREV_DEFAULT, error; + git_oid id = {{0}}; + git_odb *odb; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(obj); + + repo = git_object_owner(obj); + + if ((error = git_repository__configmap_lookup(&len, repo, GIT_CONFIGMAP_ABBREV)) < 0) + return error; + + if ((error = git_repository_odb(&odb, repo)) < 0) + return error; + + while (len < GIT_OID_HEXSZ) { + /* set up short oid */ + memcpy(&id.id, &obj->cached.oid.id, (len + 1) / 2); + if (len & 1) + id.id[len / 2] &= 0xf0; + + error = git_odb_exists_prefix(NULL, odb, &id, len); + if (error != GIT_EAMBIGUOUS) + break; + + git_error_clear(); + len++; + } + + if (!error && !(error = git_str_grow(out, len + 1))) { + git_oid_tostr(out->ptr, len + 1, &id); + out->size = len; + } + + git_odb_free(odb); + + return error; +} + +int git_object_short_id(git_buf *out, const git_object *obj) +{ + GIT_BUF_WRAP_PRIVATE(out, git_object__short_id, obj); +} + +bool git_object__is_valid( + git_repository *repo, const git_oid *id, git_object_t expected_type) +{ + git_odb *odb; + git_object_t actual_type; + size_t len; + int error; + + if (!git_object__strict_input_validation) + return true; + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || + (error = git_odb_read_header(&len, &actual_type, odb, id)) < 0) + return false; + + if (expected_type != GIT_OBJECT_ANY && expected_type != actual_type) { + git_error_set(GIT_ERROR_INVALID, + "the requested type does not match the type in the ODB"); + return false; + } + + return true; +} + +int git_object_rawcontent_is_valid( + int *valid, + const char *buf, + size_t len, + git_object_t type) +{ + git_object *obj = NULL; + int error; + + GIT_ASSERT_ARG(valid); + GIT_ASSERT_ARG(buf); + + /* Blobs are always valid; don't bother parsing. */ + if (type == GIT_OBJECT_BLOB) { + *valid = 1; + return 0; + } + + error = git_object__from_raw(&obj, buf, len, type); + git_object_free(obj); + + if (error == 0) { + *valid = 1; + return 0; + } else if (error == GIT_EINVALID) { + *valid = 0; + return 0; + } + + return error; +} diff --git a/src/libgit2/object.h b/src/libgit2/object.h new file mode 100644 index 000000000..66be57557 --- /dev/null +++ b/src/libgit2/object.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_object_h__ +#define INCLUDE_object_h__ + +#include "common.h" + +#include "repository.h" + +#define GIT_OBJECT_SIZE_MAX UINT64_MAX + +extern bool git_object__strict_input_validation; + +/** Base git object for inheritance */ +struct git_object { + git_cached_obj cached; + git_repository *repo; +}; + +/* fully free the object; internal method, DO NOT EXPORT */ +void git_object__free(void *object); + +/* + * Parse object from raw data. Note that the resulting object is + * tied to the lifetime of the data, as some objects simply point + * into it. + */ +int git_object__from_raw( + git_object **object_out, + const char *data, + size_t size, + git_object_t type); + +int git_object__from_odb_object( + git_object **object_out, + git_repository *repo, + git_odb_object *odb_obj, + git_object_t type); + +int git_object__resolve_to_type(git_object **obj, git_object_t type); + +git_object_t git_object_stringn2type(const char *str, size_t len); + +int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); + +void git_oid__writebuf(git_str *buf, const char *header, const git_oid *oid); + +bool git_object__is_valid( + git_repository *repo, const git_oid *id, git_object_t expected_type); + +GIT_INLINE(git_object_t) git_object__type_from_filemode(git_filemode_t mode) +{ + switch (mode) { + case GIT_FILEMODE_TREE: + return GIT_OBJECT_TREE; + case GIT_FILEMODE_COMMIT: + return GIT_OBJECT_COMMIT; + case GIT_FILEMODE_BLOB: + case GIT_FILEMODE_BLOB_EXECUTABLE: + case GIT_FILEMODE_LINK: + return GIT_OBJECT_BLOB; + default: + return GIT_OBJECT_INVALID; + } +} + +#endif diff --git a/src/libgit2/object_api.c b/src/libgit2/object_api.c new file mode 100644 index 000000000..d45abd5ce --- /dev/null +++ b/src/libgit2/object_api.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "git2/object.h" + +#include "repository.h" +#include "commit.h" +#include "tree.h" +#include "blob.h" +#include "tag.h" + +/** + * Commit + */ +int git_commit_lookup(git_commit **out, git_repository *repo, const git_oid *id) +{ + return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_COMMIT); +} + +int git_commit_lookup_prefix(git_commit **out, git_repository *repo, const git_oid *id, size_t len) +{ + return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_COMMIT); +} + +void git_commit_free(git_commit *obj) +{ + git_object_free((git_object *)obj); +} + +const git_oid *git_commit_id(const git_commit *obj) +{ + return git_object_id((const git_object *)obj); +} + +git_repository *git_commit_owner(const git_commit *obj) +{ + return git_object_owner((const git_object *)obj); +} + +int git_commit_dup(git_commit **out, git_commit *obj) +{ + return git_object_dup((git_object **)out, (git_object *)obj); +} + +/** + * Tree + */ +int git_tree_lookup(git_tree **out, git_repository *repo, const git_oid *id) +{ + return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_TREE); +} + +int git_tree_lookup_prefix(git_tree **out, git_repository *repo, const git_oid *id, size_t len) +{ + return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_TREE); +} + +void git_tree_free(git_tree *obj) +{ + git_object_free((git_object *)obj); +} + +const git_oid *git_tree_id(const git_tree *obj) +{ + return git_object_id((const git_object *)obj); +} + +git_repository *git_tree_owner(const git_tree *obj) +{ + return git_object_owner((const git_object *)obj); +} + +int git_tree_dup(git_tree **out, git_tree *obj) +{ + return git_object_dup((git_object **)out, (git_object *)obj); +} + +/** + * Tag + */ +int git_tag_lookup(git_tag **out, git_repository *repo, const git_oid *id) +{ + return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_TAG); +} + +int git_tag_lookup_prefix(git_tag **out, git_repository *repo, const git_oid *id, size_t len) +{ + return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_TAG); +} + +void git_tag_free(git_tag *obj) +{ + git_object_free((git_object *)obj); +} + +const git_oid *git_tag_id(const git_tag *obj) +{ + return git_object_id((const git_object *)obj); +} + +git_repository *git_tag_owner(const git_tag *obj) +{ + return git_object_owner((const git_object *)obj); +} + +int git_tag_dup(git_tag **out, git_tag *obj) +{ + return git_object_dup((git_object **)out, (git_object *)obj); +} + +/** + * Blob + */ +int git_blob_lookup(git_blob **out, git_repository *repo, const git_oid *id) +{ + return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_BLOB); +} + +int git_blob_lookup_prefix(git_blob **out, git_repository *repo, const git_oid *id, size_t len) +{ + return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_BLOB); +} + +void git_blob_free(git_blob *obj) +{ + git_object_free((git_object *)obj); +} + +const git_oid *git_blob_id(const git_blob *obj) +{ + return git_object_id((const git_object *)obj); +} + +git_repository *git_blob_owner(const git_blob *obj) +{ + return git_object_owner((const git_object *)obj); +} + +int git_blob_dup(git_blob **out, git_blob *obj) +{ + return git_object_dup((git_object **)out, (git_object *)obj); +} diff --git a/src/libgit2/odb.c b/src/libgit2/odb.c new file mode 100644 index 000000000..6d714ba54 --- /dev/null +++ b/src/libgit2/odb.c @@ -0,0 +1,1831 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "odb.h" + +#include +#include "git2/object.h" +#include "git2/sys/odb_backend.h" +#include "futils.h" +#include "hash.h" +#include "delta.h" +#include "filter.h" +#include "repository.h" +#include "blob.h" +#include "oid.h" + +#include "git2/odb_backend.h" +#include "git2/oid.h" +#include "git2/oidarray.h" + +#define GIT_ALTERNATES_FILE "info/alternates" + +#define GIT_ALTERNATES_MAX_DEPTH 5 + +/* + * We work under the assumption that most objects for long-running + * operations will be packed + */ +int git_odb__loose_priority = GIT_ODB_DEFAULT_LOOSE_PRIORITY; +int git_odb__packed_priority = GIT_ODB_DEFAULT_PACKED_PRIORITY; + +bool git_odb__strict_hash_verification = true; + +typedef struct +{ + git_odb_backend *backend; + int priority; + bool is_alternate; + ino_t disk_inode; +} backend_internal; + +static git_cache *odb_cache(git_odb *odb) +{ + git_repository *owner = GIT_REFCOUNT_OWNER(odb); + if (owner != NULL) { + return &owner->objects; + } + + return &odb->own_cache; +} + +static int odb_otype_fast(git_object_t *type_p, git_odb *db, const git_oid *id); +static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth); +static int error_null_oid(int error, const char *message); + +static git_object_t odb_hardcoded_type(const git_oid *id) +{ + if (!git_oid_cmp(id, &git_oid__empty_tree_sha1)) + return GIT_OBJECT_TREE; + + return GIT_OBJECT_INVALID; +} + +static int odb_read_hardcoded(bool *found, git_rawobj *raw, const git_oid *id) +{ + git_object_t type; + + *found = false; + + if ((type = odb_hardcoded_type(id)) == GIT_OBJECT_INVALID) + return 0; + + raw->type = type; + raw->len = 0; + raw->data = git__calloc(1, sizeof(uint8_t)); + GIT_ERROR_CHECK_ALLOC(raw->data); + + *found = true; + return 0; +} + +int git_odb__format_object_header( + size_t *written, + char *hdr, + size_t hdr_size, + git_object_size_t obj_len, + git_object_t obj_type) +{ + const char *type_str = git_object_type2string(obj_type); + int hdr_max = (hdr_size > INT_MAX-2) ? (INT_MAX-2) : (int)hdr_size; + int len; + + len = p_snprintf(hdr, hdr_max, "%s %"PRId64, type_str, (int64_t)obj_len); + + if (len < 0 || len >= hdr_max) { + git_error_set(GIT_ERROR_OS, "object header creation failed"); + return -1; + } + + *written = (size_t)(len + 1); + return 0; +} + +int git_odb__hashobj(git_oid *id, git_rawobj *obj) +{ + git_str_vec vec[2]; + char header[64]; + size_t hdrlen; + int error; + + GIT_ASSERT_ARG(id); + GIT_ASSERT_ARG(obj); + + if (!git_object_typeisloose(obj->type)) { + git_error_set(GIT_ERROR_INVALID, "invalid object type"); + return -1; + } + + if (!obj->data && obj->len != 0) { + git_error_set(GIT_ERROR_INVALID, "invalid object"); + return -1; + } + + if ((error = git_odb__format_object_header(&hdrlen, + header, sizeof(header), obj->len, obj->type)) < 0) + return error; + + vec[0].data = header; + vec[0].len = hdrlen; + vec[1].data = obj->data; + vec[1].len = obj->len; + + return git_hash_vec(id->id, vec, 2, GIT_HASH_ALGORITHM_SHA1); +} + + +static git_odb_object *odb_object__alloc(const git_oid *oid, git_rawobj *source) +{ + git_odb_object *object = git__calloc(1, sizeof(git_odb_object)); + + if (object != NULL) { + git_oid_cpy(&object->cached.oid, oid); + object->cached.type = source->type; + object->cached.size = source->len; + object->buffer = source->data; + } + + return object; +} + +void git_odb_object__free(void *object) +{ + if (object != NULL) { + git__free(((git_odb_object *)object)->buffer); + git__free(object); + } +} + +const git_oid *git_odb_object_id(git_odb_object *object) +{ + return &object->cached.oid; +} + +const void *git_odb_object_data(git_odb_object *object) +{ + return object->buffer; +} + +size_t git_odb_object_size(git_odb_object *object) +{ + return object->cached.size; +} + +git_object_t git_odb_object_type(git_odb_object *object) +{ + return object->cached.type; +} + +int git_odb_object_dup(git_odb_object **dest, git_odb_object *source) +{ + git_cached_obj_incref(source); + *dest = source; + return 0; +} + +void git_odb_object_free(git_odb_object *object) +{ + if (object == NULL) + return; + + git_cached_obj_decref(object); +} + +int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_object_t type) +{ + size_t hdr_len; + char hdr[64], buffer[FILEIO_BUFSIZE]; + git_hash_ctx ctx; + ssize_t read_len = 0; + int error = 0; + + if (!git_object_typeisloose(type)) { + git_error_set(GIT_ERROR_INVALID, "invalid object type for hash"); + return -1; + } + + if ((error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)) < 0) + return error; + + if ((error = git_odb__format_object_header(&hdr_len, hdr, + sizeof(hdr), size, type)) < 0) + goto done; + + if ((error = git_hash_update(&ctx, hdr, hdr_len)) < 0) + goto done; + + while (size > 0 && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { + if ((error = git_hash_update(&ctx, buffer, read_len)) < 0) + goto done; + + size -= read_len; + } + + /* If p_read returned an error code, the read obviously failed. + * If size is not zero, the file was truncated after we originally + * stat'd it, so we consider this a read failure too */ + if (read_len < 0 || size > 0) { + git_error_set(GIT_ERROR_OS, "error reading file for hashing"); + error = -1; + + goto done; + } + + error = git_hash_final(out->id, &ctx); + +done: + git_hash_ctx_cleanup(&ctx); + return error; +} + +int git_odb__hashfd_filtered( + git_oid *out, git_file fd, size_t size, git_object_t type, git_filter_list *fl) +{ + int error; + git_str raw = GIT_STR_INIT; + + if (!fl) + return git_odb__hashfd(out, fd, size, type); + + /* size of data is used in header, so we have to read the whole file + * into memory to apply filters before beginning to calculate the hash + */ + + if (!(error = git_futils_readbuffer_fd(&raw, fd, size))) { + git_str post = GIT_STR_INIT; + + error = git_filter_list__convert_buf(&post, fl, &raw); + + if (!error) + error = git_odb_hash(out, post.ptr, post.size, type); + + git_str_dispose(&post); + } + + return error; +} + +int git_odb__hashlink(git_oid *out, const char *path) +{ + struct stat st; + int size; + int result; + + if (git_fs_path_lstat(path, &st) < 0) + return -1; + + if (!git__is_int(st.st_size) || (int)st.st_size < 0) { + git_error_set(GIT_ERROR_FILESYSTEM, "file size overflow for 32-bit systems"); + return -1; + } + + size = (int)st.st_size; + + if (S_ISLNK(st.st_mode)) { + char *link_data; + int read_len; + size_t alloc_size; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, size, 1); + link_data = git__malloc(alloc_size); + GIT_ERROR_CHECK_ALLOC(link_data); + + read_len = p_readlink(path, link_data, size); + if (read_len == -1) { + git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", path); + git__free(link_data); + return -1; + } + GIT_ASSERT(read_len <= size); + link_data[read_len] = '\0'; + + result = git_odb_hash(out, link_data, read_len, GIT_OBJECT_BLOB); + git__free(link_data); + } else { + int fd = git_futils_open_ro(path); + if (fd < 0) + return -1; + result = git_odb__hashfd(out, fd, size, GIT_OBJECT_BLOB); + p_close(fd); + } + + return result; +} + +int git_odb_hashfile(git_oid *out, const char *path, git_object_t type) +{ + uint64_t size; + int fd, error = 0; + + if ((fd = git_futils_open_ro(path)) < 0) + return fd; + + if ((error = git_futils_filesize(&size, fd)) < 0) + goto done; + + if (!git__is_sizet(size)) { + git_error_set(GIT_ERROR_OS, "file size overflow for 32-bit systems"); + error = -1; + goto done; + } + + error = git_odb__hashfd(out, fd, (size_t)size, type); + +done: + p_close(fd); + return error; +} + +int git_odb_hash(git_oid *id, const void *data, size_t len, git_object_t type) +{ + git_rawobj raw; + + GIT_ASSERT_ARG(id); + + raw.data = (void *)data; + raw.len = len; + raw.type = type; + + return git_odb__hashobj(id, &raw); +} + +/** + * FAKE WSTREAM + */ + +typedef struct { + git_odb_stream stream; + char *buffer; + size_t size, written; + git_object_t type; +} fake_wstream; + +static int fake_wstream__fwrite(git_odb_stream *_stream, const git_oid *oid) +{ + fake_wstream *stream = (fake_wstream *)_stream; + return _stream->backend->write(_stream->backend, oid, stream->buffer, stream->size, stream->type); +} + +static int fake_wstream__write(git_odb_stream *_stream, const char *data, size_t len) +{ + fake_wstream *stream = (fake_wstream *)_stream; + + GIT_ASSERT(stream->written + len <= stream->size); + + memcpy(stream->buffer + stream->written, data, len); + stream->written += len; + return 0; +} + +static void fake_wstream__free(git_odb_stream *_stream) +{ + fake_wstream *stream = (fake_wstream *)_stream; + + git__free(stream->buffer); + git__free(stream); +} + +static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend, git_object_size_t size, git_object_t type) +{ + fake_wstream *stream; + size_t blobsize; + + GIT_ERROR_CHECK_BLOBSIZE(size); + blobsize = (size_t)size; + + stream = git__calloc(1, sizeof(fake_wstream)); + GIT_ERROR_CHECK_ALLOC(stream); + + stream->size = blobsize; + stream->type = type; + stream->buffer = git__malloc(blobsize); + if (stream->buffer == NULL) { + git__free(stream); + return -1; + } + + stream->stream.backend = backend; + stream->stream.read = NULL; /* read only */ + stream->stream.write = &fake_wstream__write; + stream->stream.finalize_write = &fake_wstream__fwrite; + stream->stream.free = &fake_wstream__free; + stream->stream.mode = GIT_STREAM_WRONLY; + + *stream_p = (git_odb_stream *)stream; + return 0; +} + +/*********************************************************** + * + * OBJECT DATABASE PUBLIC API + * + * Public calls for the ODB functionality + * + ***********************************************************/ + +static int backend_sort_cmp(const void *a, const void *b) +{ + const backend_internal *backend_a = (const backend_internal *)(a); + const backend_internal *backend_b = (const backend_internal *)(b); + + if (backend_b->priority == backend_a->priority) { + if (backend_a->is_alternate) + return -1; + if (backend_b->is_alternate) + return 1; + return 0; + } + return (backend_b->priority - backend_a->priority); +} + +int git_odb_new(git_odb **out) +{ + git_odb *db = git__calloc(1, sizeof(*db)); + GIT_ERROR_CHECK_ALLOC(db); + + if (git_mutex_init(&db->lock) < 0) { + git__free(db); + return -1; + } + if (git_cache_init(&db->own_cache) < 0) { + git_mutex_free(&db->lock); + git__free(db); + return -1; + } + if (git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) { + git_cache_dispose(&db->own_cache); + git_mutex_free(&db->lock); + git__free(db); + return -1; + } + + *out = db; + GIT_REFCOUNT_INC(db); + return 0; +} + +static int add_backend_internal( + git_odb *odb, git_odb_backend *backend, + int priority, bool is_alternate, ino_t disk_inode) +{ + backend_internal *internal; + + GIT_ASSERT_ARG(odb); + GIT_ASSERT_ARG(backend); + + GIT_ERROR_CHECK_VERSION(backend, GIT_ODB_BACKEND_VERSION, "git_odb_backend"); + + /* Check if the backend is already owned by another ODB */ + GIT_ASSERT(!backend->odb || backend->odb == odb); + + internal = git__malloc(sizeof(backend_internal)); + GIT_ERROR_CHECK_ALLOC(internal); + + internal->backend = backend; + internal->priority = priority; + internal->is_alternate = is_alternate; + internal->disk_inode = disk_inode; + + if (git_mutex_lock(&odb->lock) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return -1; + } + if (git_vector_insert(&odb->backends, internal) < 0) { + git_mutex_unlock(&odb->lock); + git__free(internal); + return -1; + } + git_vector_sort(&odb->backends); + internal->backend->odb = odb; + git_mutex_unlock(&odb->lock); + return 0; +} + +int git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority) +{ + return add_backend_internal(odb, backend, priority, false, 0); +} + +int git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority) +{ + return add_backend_internal(odb, backend, priority, true, 0); +} + +size_t git_odb_num_backends(git_odb *odb) +{ + size_t length; + bool locked = true; + + GIT_ASSERT_ARG(odb); + + if (git_mutex_lock(&odb->lock) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + locked = false; + } + length = odb->backends.length; + if (locked) + git_mutex_unlock(&odb->lock); + return length; +} + +static int git_odb__error_unsupported_in_backend(const char *action) +{ + git_error_set(GIT_ERROR_ODB, + "cannot %s - unsupported in the loaded odb backends", action); + return -1; +} + + +int git_odb_get_backend(git_odb_backend **out, git_odb *odb, size_t pos) +{ + backend_internal *internal; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(odb); + + + if ((error = git_mutex_lock(&odb->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + internal = git_vector_get(&odb->backends, pos); + + if (!internal || !internal->backend) { + git_mutex_unlock(&odb->lock); + + git_error_set(GIT_ERROR_ODB, "no ODB backend loaded at index %" PRIuZ, pos); + return GIT_ENOTFOUND; + } + *out = internal->backend; + git_mutex_unlock(&odb->lock); + + return 0; +} + +int git_odb__add_default_backends( + git_odb *db, const char *objects_dir, + bool as_alternates, int alternate_depth) +{ + size_t i = 0; + struct stat st; + ino_t inode; + git_odb_backend *loose, *packed; + + /* TODO: inodes are not really relevant on Win32, so we need to find + * a cross-platform workaround for this */ +#ifdef GIT_WIN32 + GIT_UNUSED(i); + GIT_UNUSED(&st); + + inode = 0; +#else + if (p_stat(objects_dir, &st) < 0) { + if (as_alternates) + /* this should warn */ + return 0; + + git_error_set(GIT_ERROR_ODB, "failed to load object database in '%s'", objects_dir); + return -1; + } + + inode = st.st_ino; + + if (git_mutex_lock(&db->lock) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return -1; + } + for (i = 0; i < db->backends.length; ++i) { + backend_internal *backend = git_vector_get(&db->backends, i); + if (backend->disk_inode == inode) { + git_mutex_unlock(&db->lock); + return 0; + } + } + git_mutex_unlock(&db->lock); +#endif + + /* add the loose object backend */ + if (git_odb_backend_loose(&loose, objects_dir, -1, db->do_fsync, 0, 0) < 0 || + add_backend_internal(db, loose, git_odb__loose_priority, as_alternates, inode) < 0) + return -1; + + /* add the packed file backend */ + if (git_odb_backend_pack(&packed, objects_dir) < 0 || + add_backend_internal(db, packed, git_odb__packed_priority, as_alternates, inode) < 0) + return -1; + + if (git_mutex_lock(&db->lock) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return -1; + } + if (!db->cgraph && git_commit_graph_new(&db->cgraph, objects_dir, false) < 0) { + git_mutex_unlock(&db->lock); + return -1; + } + git_mutex_unlock(&db->lock); + + return load_alternates(db, objects_dir, alternate_depth); +} + +static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth) +{ + git_str alternates_path = GIT_STR_INIT; + git_str alternates_buf = GIT_STR_INIT; + char *buffer; + const char *alternate; + int result = 0; + + /* Git reports an error, we just ignore anything deeper */ + if (alternate_depth > GIT_ALTERNATES_MAX_DEPTH) + return 0; + + if (git_str_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0) + return -1; + + if (git_fs_path_exists(alternates_path.ptr) == false) { + git_str_dispose(&alternates_path); + return 0; + } + + if (git_futils_readbuffer(&alternates_buf, alternates_path.ptr) < 0) { + git_str_dispose(&alternates_path); + return -1; + } + + buffer = (char *)alternates_buf.ptr; + + /* add each alternate as a new backend; one alternate per line */ + while ((alternate = git__strtok(&buffer, "\r\n")) != NULL) { + if (*alternate == '\0' || *alternate == '#') + continue; + + /* + * Relative path: build based on the current `objects` + * folder. However, relative paths are only allowed in + * the current repository. + */ + if (*alternate == '.' && !alternate_depth) { + if ((result = git_str_joinpath(&alternates_path, objects_dir, alternate)) < 0) + break; + alternate = git_str_cstr(&alternates_path); + } + + if ((result = git_odb__add_default_backends(odb, alternate, true, alternate_depth + 1)) < 0) + break; + } + + git_str_dispose(&alternates_path); + git_str_dispose(&alternates_buf); + + return result; +} + +int git_odb_add_disk_alternate(git_odb *odb, const char *path) +{ + return git_odb__add_default_backends(odb, path, true, 0); +} + +int git_odb_set_commit_graph(git_odb *odb, git_commit_graph *cgraph) +{ + int error = 0; + + GIT_ASSERT_ARG(odb); + + if ((error = git_mutex_lock(&odb->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the db lock"); + return error; + } + git_commit_graph_free(odb->cgraph); + odb->cgraph = cgraph; + git_mutex_unlock(&odb->lock); + + return error; +} + +int git_odb_open(git_odb **out, const char *objects_dir) +{ + git_odb *db; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(objects_dir); + + *out = NULL; + + if (git_odb_new(&db) < 0) + return -1; + + if (git_odb__add_default_backends(db, objects_dir, 0, 0) < 0) { + git_odb_free(db); + return -1; + } + + *out = db; + return 0; +} + +int git_odb__set_caps(git_odb *odb, int caps) +{ + if (caps == GIT_ODB_CAP_FROM_OWNER) { + git_repository *repo = GIT_REFCOUNT_OWNER(odb); + int val; + + if (!repo) { + git_error_set(GIT_ERROR_ODB, "cannot access repository to set odb caps"); + return -1; + } + + if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_FSYNCOBJECTFILES)) + odb->do_fsync = !!val; + } + + return 0; +} + +static void odb_free(git_odb *db) +{ + size_t i; + bool locked = true; + + if (git_mutex_lock(&db->lock) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + locked = false; + } + for (i = 0; i < db->backends.length; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *backend = internal->backend; + + backend->free(backend); + + git__free(internal); + } + if (locked) + git_mutex_unlock(&db->lock); + + git_commit_graph_free(db->cgraph); + git_vector_free(&db->backends); + git_cache_dispose(&db->own_cache); + git_mutex_free(&db->lock); + + git__memzero(db, sizeof(*db)); + git__free(db); +} + +void git_odb_free(git_odb *db) +{ + if (db == NULL) + return; + + GIT_REFCOUNT_DEC(db, odb_free); +} + +static int odb_exists_1( + git_odb *db, + const git_oid *id, + bool only_refreshed) +{ + size_t i; + bool found = false; + int error; + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0; i < db->backends.length && !found; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (b->exists != NULL) + found = (bool)b->exists(b, id); + } + git_mutex_unlock(&db->lock); + + return (int)found; +} + +int git_odb__get_commit_graph_file(git_commit_graph_file **out, git_odb *db) +{ + int error = 0; + git_commit_graph_file *result = NULL; + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the db lock"); + return error; + } + if (!db->cgraph) { + error = GIT_ENOTFOUND; + goto done; + } + error = git_commit_graph_get_file(&result, db->cgraph); + if (error) + goto done; + *out = result; + +done: + git_mutex_unlock(&db->lock); + return error; +} + +static int odb_freshen_1( + git_odb *db, + const git_oid *id, + bool only_refreshed) +{ + size_t i; + bool found = false; + int error; + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0; i < db->backends.length && !found; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (b->freshen != NULL) + found = !b->freshen(b, id); + else if (b->exists != NULL) + found = b->exists(b, id); + } + git_mutex_unlock(&db->lock); + + return (int)found; +} + +int git_odb__freshen(git_odb *db, const git_oid *id) +{ + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(id); + + if (odb_freshen_1(db, id, false)) + return 1; + + if (!git_odb_refresh(db)) + return odb_freshen_1(db, id, true); + + /* Failed to refresh, hence not found */ + return 0; +} + +int git_odb_exists(git_odb *db, const git_oid *id) +{ + return git_odb_exists_ext(db, id, 0); +} + +int git_odb_exists_ext(git_odb *db, const git_oid *id, unsigned int flags) +{ + git_odb_object *object; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(id); + + if (git_oid_is_zero(id)) + return 0; + + if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { + git_odb_object_free(object); + return 1; + } + + if (odb_exists_1(db, id, false)) + return 1; + + if (!(flags & GIT_ODB_LOOKUP_NO_REFRESH) && !git_odb_refresh(db)) + return odb_exists_1(db, id, true); + + /* Failed to refresh, hence not found */ + return 0; +} + +static int odb_exists_prefix_1(git_oid *out, git_odb *db, + const git_oid *key, size_t len, bool only_refreshed) +{ + size_t i; + int error = GIT_ENOTFOUND, num_found = 0; + git_oid last_found = {{0}}, found; + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + error = GIT_ENOTFOUND; + for (i = 0; i < db->backends.length; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (!b->exists_prefix) + continue; + + error = b->exists_prefix(&found, b, key, len); + if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) + continue; + if (error) { + git_mutex_unlock(&db->lock); + return error; + } + + /* make sure found item doesn't introduce ambiguity */ + if (num_found) { + if (git_oid__cmp(&last_found, &found)) { + git_mutex_unlock(&db->lock); + return git_odb__error_ambiguous("multiple matches for prefix"); + } + } else { + git_oid_cpy(&last_found, &found); + num_found++; + } + } + git_mutex_unlock(&db->lock); + + if (!num_found) + return GIT_ENOTFOUND; + + if (out) + git_oid_cpy(out, &last_found); + + return 0; +} + +int git_odb_exists_prefix( + git_oid *out, git_odb *db, const git_oid *short_id, size_t len) +{ + int error; + git_oid key = {{0}}; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(short_id); + + if (len < GIT_OID_MINPREFIXLEN) + return git_odb__error_ambiguous("prefix length too short"); + + if (len >= GIT_OID_HEXSZ) { + if (git_odb_exists(db, short_id)) { + if (out) + git_oid_cpy(out, short_id); + return 0; + } else { + return git_odb__error_notfound( + "no match for id prefix", short_id, len); + } + } + + git_oid__cpy_prefix(&key, short_id, len); + + error = odb_exists_prefix_1(out, db, &key, len, false); + + if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) + error = odb_exists_prefix_1(out, db, &key, len, true); + + if (error == GIT_ENOTFOUND) + return git_odb__error_notfound("no match for id prefix", &key, len); + + return error; +} + +int git_odb_expand_ids( + git_odb *db, + git_odb_expand_id *ids, + size_t count) +{ + size_t i; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(ids); + + for (i = 0; i < count; i++) { + git_odb_expand_id *query = &ids[i]; + int error = GIT_EAMBIGUOUS; + + if (!query->type) + query->type = GIT_OBJECT_ANY; + + /* if we have a short OID, expand it first */ + if (query->length >= GIT_OID_MINPREFIXLEN && query->length < GIT_OID_HEXSZ) { + git_oid actual_id; + + error = odb_exists_prefix_1(&actual_id, db, &query->id, query->length, false); + if (!error) { + git_oid_cpy(&query->id, &actual_id); + query->length = GIT_OID_HEXSZ; + } + } + + /* + * now we ought to have a 40-char OID, either because we've expanded it + * or because the user passed a full OID. Ensure its type is right. + */ + if (query->length >= GIT_OID_HEXSZ) { + git_object_t actual_type; + + error = odb_otype_fast(&actual_type, db, &query->id); + if (!error) { + if (query->type != GIT_OBJECT_ANY && query->type != actual_type) + error = GIT_ENOTFOUND; + else + query->type = actual_type; + } + } + + switch (error) { + /* no errors, so we've successfully expanded the OID */ + case 0: + continue; + + /* the object is missing or ambiguous */ + case GIT_ENOTFOUND: + case GIT_EAMBIGUOUS: + memset(&query->id, 0, sizeof(git_oid)); + query->length = 0; + query->type = 0; + break; + + /* something went very wrong with the ODB; bail hard */ + default: + return error; + } + } + + git_error_clear(); + return 0; +} + +int git_odb_read_header(size_t *len_p, git_object_t *type_p, git_odb *db, const git_oid *id) +{ + int error; + git_odb_object *object = NULL; + + error = git_odb__read_header_or_object(&object, len_p, type_p, db, id); + + if (object) + git_odb_object_free(object); + + return error; +} + +static int odb_read_header_1( + size_t *len_p, git_object_t *type_p, git_odb *db, + const git_oid *id, bool only_refreshed) +{ + size_t i; + git_object_t ht; + bool passthrough = false; + int error; + + if (!only_refreshed && (ht = odb_hardcoded_type(id)) != GIT_OBJECT_INVALID) { + *type_p = ht; + *len_p = 0; + return 0; + } + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0; i < db->backends.length; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (!b->read_header) { + passthrough = true; + continue; + } + + error = b->read_header(len_p, type_p, b, id); + + switch (error) { + case GIT_PASSTHROUGH: + passthrough = true; + break; + case GIT_ENOTFOUND: + break; + default: + git_mutex_unlock(&db->lock); + return error; + } + } + git_mutex_unlock(&db->lock); + + return passthrough ? GIT_PASSTHROUGH : GIT_ENOTFOUND; +} + +int git_odb__read_header_or_object( + git_odb_object **out, size_t *len_p, git_object_t *type_p, + git_odb *db, const git_oid *id) +{ + int error = GIT_ENOTFOUND; + git_odb_object *object; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(id); + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(len_p); + GIT_ASSERT_ARG(type_p); + + *out = NULL; + + if (git_oid_is_zero(id)) + return error_null_oid(GIT_ENOTFOUND, "cannot read object"); + + if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { + *len_p = object->cached.size; + *type_p = object->cached.type; + *out = object; + return 0; + } + + error = odb_read_header_1(len_p, type_p, db, id, false); + + if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) + error = odb_read_header_1(len_p, type_p, db, id, true); + + if (error == GIT_ENOTFOUND) + return git_odb__error_notfound("cannot read header for", id, GIT_OID_HEXSZ); + + /* we found the header; return early */ + if (!error) + return 0; + + if (error == GIT_PASSTHROUGH) { + /* + * no backend has header-reading functionality + * so try using `git_odb_read` instead + */ + error = git_odb_read(&object, db, id); + if (!error) { + *len_p = object->cached.size; + *type_p = object->cached.type; + *out = object; + } + } + + return error; +} + +static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id, + bool only_refreshed) +{ + size_t i; + git_rawobj raw; + git_odb_object *object; + git_oid hashed; + bool found = false; + int error = 0; + + if (!only_refreshed) { + if ((error = odb_read_hardcoded(&found, &raw, id)) < 0) + return error; + } + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0; i < db->backends.length && !found; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (b->read != NULL) { + error = b->read(&raw.data, &raw.len, &raw.type, b, id); + if (error == GIT_PASSTHROUGH || error == GIT_ENOTFOUND) + continue; + + if (error < 0) { + git_mutex_unlock(&db->lock); + return error; + } + + found = true; + } + } + git_mutex_unlock(&db->lock); + + if (!found) + return GIT_ENOTFOUND; + + if (git_odb__strict_hash_verification) { + if ((error = git_odb_hash(&hashed, raw.data, raw.len, raw.type)) < 0) + goto out; + + if (!git_oid_equal(id, &hashed)) { + error = git_odb__error_mismatch(id, &hashed); + goto out; + } + } + + git_error_clear(); + if ((object = odb_object__alloc(id, &raw)) == NULL) { + error = -1; + goto out; + } + + *out = git_cache_store_raw(odb_cache(db), object); + +out: + if (error) + git__free(raw.data); + return error; +} + +int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) +{ + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(id); + + if (git_oid_is_zero(id)) + return error_null_oid(GIT_ENOTFOUND, "cannot read object"); + + *out = git_cache_get_raw(odb_cache(db), id); + if (*out != NULL) + return 0; + + error = odb_read_1(out, db, id, false); + + if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) + error = odb_read_1(out, db, id, true); + + if (error == GIT_ENOTFOUND) + return git_odb__error_notfound("no match for id", id, GIT_OID_HEXSZ); + + return error; +} + +static int odb_otype_fast(git_object_t *type_p, git_odb *db, const git_oid *id) +{ + git_odb_object *object; + size_t _unused; + int error; + + if (git_oid_is_zero(id)) + return error_null_oid(GIT_ENOTFOUND, "cannot get object type"); + + if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { + *type_p = object->cached.type; + git_odb_object_free(object); + return 0; + } + + error = odb_read_header_1(&_unused, type_p, db, id, false); + + if (error == GIT_PASSTHROUGH) { + error = odb_read_1(&object, db, id, false); + if (!error) + *type_p = object->cached.type; + git_odb_object_free(object); + } + + return error; +} + +static int read_prefix_1(git_odb_object **out, git_odb *db, + const git_oid *key, size_t len, bool only_refreshed) +{ + size_t i; + int error = 0; + git_oid found_full_oid = {{0}}; + git_rawobj raw = {0}; + void *data = NULL; + bool found = false; + git_odb_object *object; + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0; i < db->backends.length; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (b->read_prefix != NULL) { + git_oid full_oid; + error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, key, len); + + if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) { + error = 0; + continue; + } + + if (error) { + git_mutex_unlock(&db->lock); + goto out; + } + + git__free(data); + data = raw.data; + + if (found && git_oid__cmp(&full_oid, &found_full_oid)) { + git_str buf = GIT_STR_INIT; + + git_str_printf(&buf, "multiple matches for prefix: %s", + git_oid_tostr_s(&full_oid)); + git_str_printf(&buf, " %s", + git_oid_tostr_s(&found_full_oid)); + + error = git_odb__error_ambiguous(buf.ptr); + git_str_dispose(&buf); + git_mutex_unlock(&db->lock); + goto out; + } + + found_full_oid = full_oid; + found = true; + } + } + git_mutex_unlock(&db->lock); + + if (!found) + return GIT_ENOTFOUND; + + if (git_odb__strict_hash_verification) { + git_oid hash; + + if ((error = git_odb_hash(&hash, raw.data, raw.len, raw.type)) < 0) + goto out; + + if (!git_oid_equal(&found_full_oid, &hash)) { + error = git_odb__error_mismatch(&found_full_oid, &hash); + goto out; + } + } + + if ((object = odb_object__alloc(&found_full_oid, &raw)) == NULL) { + error = -1; + goto out; + } + + *out = git_cache_store_raw(odb_cache(db), object); + +out: + if (error) + git__free(raw.data); + + return error; +} + +int git_odb_read_prefix( + git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len) +{ + git_oid key = {{0}}; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(db); + + if (len < GIT_OID_MINPREFIXLEN) + return git_odb__error_ambiguous("prefix length too short"); + + if (len > GIT_OID_HEXSZ) + len = GIT_OID_HEXSZ; + + if (len == GIT_OID_HEXSZ) { + *out = git_cache_get_raw(odb_cache(db), short_id); + if (*out != NULL) + return 0; + } + + git_oid__cpy_prefix(&key, short_id, len); + + error = read_prefix_1(out, db, &key, len, false); + + if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) + error = read_prefix_1(out, db, &key, len, true); + + if (error == GIT_ENOTFOUND) + return git_odb__error_notfound("no match for prefix", &key, len); + + return error; +} + +int git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload) +{ + unsigned int i; + git_vector backends = GIT_VECTOR_INIT; + backend_internal *internal; + int error = 0; + + /* Make a copy of the backends vector to invoke the callback without holding the lock. */ + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + goto cleanup; + } + error = git_vector_dup(&backends, &db->backends, NULL); + git_mutex_unlock(&db->lock); + + if (error < 0) + goto cleanup; + + git_vector_foreach(&backends, i, internal) { + git_odb_backend *b = internal->backend; + error = b->foreach(b, cb, payload); + if (error != 0) + goto cleanup; + } + +cleanup: + git_vector_free(&backends); + + return error; +} + +int git_odb_write( + git_oid *oid, git_odb *db, const void *data, size_t len, git_object_t type) +{ + size_t i; + int error; + git_odb_stream *stream; + + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(db); + + if ((error = git_odb_hash(oid, data, len, type)) < 0) + return error; + + if (git_oid_is_zero(oid)) + return error_null_oid(GIT_EINVALID, "cannot write object"); + + if (git_odb__freshen(db, oid)) + return 0; + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0, error = GIT_ERROR; i < db->backends.length && error < 0; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + /* we don't write in alternates! */ + if (internal->is_alternate) + continue; + + if (b->write != NULL) + error = b->write(b, oid, data, len, type); + } + git_mutex_unlock(&db->lock); + + if (!error || error == GIT_PASSTHROUGH) + return 0; + + /* if no backends were able to write the object directly, we try a + * streaming write to the backends; just write the whole object into the + * stream in one push + */ + if ((error = git_odb_open_wstream(&stream, db, len, type)) != 0) + return error; + + if ((error = stream->write(stream, data, len)) == 0) + error = stream->finalize_write(stream, oid); + + git_odb_stream_free(stream); + return error; +} + +static int hash_header(git_hash_ctx *ctx, git_object_size_t size, git_object_t type) +{ + char header[64]; + size_t hdrlen; + int error; + + if ((error = git_odb__format_object_header(&hdrlen, + header, sizeof(header), size, type)) < 0) + return error; + + return git_hash_update(ctx, header, hdrlen); +} + +int git_odb_open_wstream( + git_odb_stream **stream, git_odb *db, git_object_size_t size, git_object_t type) +{ + size_t i, writes = 0; + int error = GIT_ERROR; + git_hash_ctx *ctx = NULL; + + GIT_ASSERT_ARG(stream); + GIT_ASSERT_ARG(db); + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + error = GIT_ERROR; + for (i = 0; i < db->backends.length && error < 0; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + /* we don't write in alternates! */ + if (internal->is_alternate) + continue; + + if (b->writestream != NULL) { + ++writes; + error = b->writestream(stream, b, size, type); + } else if (b->write != NULL) { + ++writes; + error = init_fake_wstream(stream, b, size, type); + } + } + git_mutex_unlock(&db->lock); + + if (error < 0) { + if (error == GIT_PASSTHROUGH) + error = 0; + else if (!writes) + error = git_odb__error_unsupported_in_backend("write object"); + + goto done; + } + + ctx = git__malloc(sizeof(git_hash_ctx)); + GIT_ERROR_CHECK_ALLOC(ctx); + + if ((error = git_hash_ctx_init(ctx, GIT_HASH_ALGORITHM_SHA1)) < 0 || + (error = hash_header(ctx, size, type)) < 0) + goto done; + + (*stream)->hash_ctx = ctx; + (*stream)->declared_size = size; + (*stream)->received_bytes = 0; + +done: + if (error) + git__free(ctx); + return error; +} + +static int git_odb_stream__invalid_length( + const git_odb_stream *stream, + const char *action) +{ + git_error_set(GIT_ERROR_ODB, + "cannot %s - " + "Invalid length. %"PRId64" was expected. The " + "total size of the received chunks amounts to %"PRId64".", + action, stream->declared_size, stream->received_bytes); + + return -1; +} + +int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len) +{ + git_hash_update(stream->hash_ctx, buffer, len); + + stream->received_bytes += len; + + if (stream->received_bytes > stream->declared_size) + return git_odb_stream__invalid_length(stream, + "stream_write()"); + + return stream->write(stream, buffer, len); +} + +int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream) +{ + if (stream->received_bytes != stream->declared_size) + return git_odb_stream__invalid_length(stream, + "stream_finalize_write()"); + + git_hash_final(out->id, stream->hash_ctx); + + if (git_odb__freshen(stream->backend->odb, out)) + return 0; + + return stream->finalize_write(stream, out); +} + +int git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len) +{ + return stream->read(stream, buffer, len); +} + +void git_odb_stream_free(git_odb_stream *stream) +{ + if (stream == NULL) + return; + + git_hash_ctx_cleanup(stream->hash_ctx); + git__free(stream->hash_ctx); + stream->free(stream); +} + +int git_odb_open_rstream( + git_odb_stream **stream, + size_t *len, + git_object_t *type, + git_odb *db, + const git_oid *oid) +{ + size_t i, reads = 0; + int error = GIT_ERROR; + + GIT_ASSERT_ARG(stream); + GIT_ASSERT_ARG(db); + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + error = GIT_ERROR; + for (i = 0; i < db->backends.length && error < 0; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (b->readstream != NULL) { + ++reads; + error = b->readstream(stream, len, type, b, oid); + } + } + git_mutex_unlock(&db->lock); + + if (error == GIT_PASSTHROUGH) + error = 0; + if (error < 0 && !reads) + error = git_odb__error_unsupported_in_backend("read object streamed"); + + return error; +} + +int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_indexer_progress_cb progress_cb, void *progress_payload) +{ + size_t i, writes = 0; + int error = GIT_ERROR; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(db); + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + error = GIT_ERROR; + for (i = 0; i < db->backends.length && error < 0; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + /* we don't write in alternates! */ + if (internal->is_alternate) + continue; + + if (b->writepack != NULL) { + ++writes; + error = b->writepack(out, b, db, progress_cb, progress_payload); + } + } + git_mutex_unlock(&db->lock); + + if (error == GIT_PASSTHROUGH) + error = 0; + if (error < 0 && !writes) + error = git_odb__error_unsupported_in_backend("write pack"); + + return error; +} + +int git_odb_write_multi_pack_index(git_odb *db) +{ + size_t i, writes = 0; + int error = GIT_ERROR; + + GIT_ASSERT_ARG(db); + + for (i = 0; i < db->backends.length && error < 0; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + /* we don't write in alternates! */ + if (internal->is_alternate) + continue; + + if (b->writemidx != NULL) { + ++writes; + error = b->writemidx(b); + } + } + + if (error == GIT_PASSTHROUGH) + error = 0; + if (error < 0 && !writes) + error = git_odb__error_unsupported_in_backend("write multi-pack-index"); + + return error; +} + +void *git_odb_backend_data_alloc(git_odb_backend *backend, size_t len) +{ + GIT_UNUSED(backend); + return git__malloc(len); +} + +#ifndef GIT_DEPRECATE_HARD +void *git_odb_backend_malloc(git_odb_backend *backend, size_t len) +{ + return git_odb_backend_data_alloc(backend, len); +} +#endif + +void git_odb_backend_data_free(git_odb_backend *backend, void *data) +{ + GIT_UNUSED(backend); + git__free(data); +} + +int git_odb_refresh(struct git_odb *db) +{ + size_t i; + int error; + + GIT_ASSERT_ARG(db); + + if ((error = git_mutex_lock(&db->lock)) < 0) { + git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); + return error; + } + for (i = 0; i < db->backends.length; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (b->refresh != NULL) { + int error = b->refresh(b); + if (error < 0) { + git_mutex_unlock(&db->lock); + return error; + } + } + } + if (db->cgraph) + git_commit_graph_refresh(db->cgraph); + git_mutex_unlock(&db->lock); + + return 0; +} + +int git_odb__error_mismatch(const git_oid *expected, const git_oid *actual) +{ + char expected_oid[GIT_OID_HEXSZ + 1], actual_oid[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(expected_oid, sizeof(expected_oid), expected); + git_oid_tostr(actual_oid, sizeof(actual_oid), actual); + + git_error_set(GIT_ERROR_ODB, "object hash mismatch - expected %s but got %s", + expected_oid, actual_oid); + + return GIT_EMISMATCH; +} + +int git_odb__error_notfound( + const char *message, const git_oid *oid, size_t oid_len) +{ + if (oid != NULL) { + char oid_str[GIT_OID_HEXSZ + 1]; + git_oid_tostr(oid_str, oid_len+1, oid); + git_error_set(GIT_ERROR_ODB, "object not found - %s (%.*s)", + message, (int) oid_len, oid_str); + } else + git_error_set(GIT_ERROR_ODB, "object not found - %s", message); + + return GIT_ENOTFOUND; +} + +static int error_null_oid(int error, const char *message) +{ + git_error_set(GIT_ERROR_ODB, "odb: %s: null OID cannot exist", message); + return error; +} + +int git_odb__error_ambiguous(const char *message) +{ + git_error_set(GIT_ERROR_ODB, "ambiguous SHA1 prefix - %s", message); + return GIT_EAMBIGUOUS; +} + +int git_odb_init_backend(git_odb_backend *backend, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + backend, version, git_odb_backend, GIT_ODB_BACKEND_INIT); + return 0; +} diff --git a/src/libgit2/odb.h b/src/libgit2/odb.h new file mode 100644 index 000000000..5aa4cc84a --- /dev/null +++ b/src/libgit2/odb.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_odb_h__ +#define INCLUDE_odb_h__ + +#include "common.h" + +#include "git2/odb.h" +#include "git2/oid.h" +#include "git2/types.h" +#include "git2/sys/commit_graph.h" + +#include "cache.h" +#include "commit_graph.h" +#include "filter.h" +#include "posix.h" +#include "vector.h" + +#define GIT_OBJECTS_DIR "objects/" +#define GIT_OBJECT_DIR_MODE 0777 +#define GIT_OBJECT_FILE_MODE 0444 + +#define GIT_ODB_DEFAULT_LOOSE_PRIORITY 1 +#define GIT_ODB_DEFAULT_PACKED_PRIORITY 2 + +extern bool git_odb__strict_hash_verification; + +/* DO NOT EXPORT */ +typedef struct { + void *data; /**< Raw, decompressed object data. */ + size_t len; /**< Total number of bytes in data. */ + git_object_t type; /**< Type of this object. */ +} git_rawobj; + +/* EXPORT */ +struct git_odb_object { + git_cached_obj cached; + void *buffer; +}; + +/* EXPORT */ +struct git_odb { + git_refcount rc; + git_mutex lock; /* protects backends */ + git_vector backends; + git_cache own_cache; + git_commit_graph *cgraph; + unsigned int do_fsync :1; +}; + +typedef enum { + GIT_ODB_CAP_FROM_OWNER = -1 +} git_odb_cap_t; + +/* + * Set the capabilities for the object database. + */ +int git_odb__set_caps(git_odb *odb, int caps); + +/* + * Add the default loose and packed backends for a database. + */ +int git_odb__add_default_backends( + git_odb *db, const char *objects_dir, + bool as_alternates, int alternate_depth); + +/* + * Hash a git_rawobj internally. + * The `git_rawobj` is supposed to be previously initialized + */ +int git_odb__hashobj(git_oid *id, git_rawobj *obj); + +/* + * Format the object header such as it would appear in the on-disk object + */ +int git_odb__format_object_header(size_t *out_len, char *hdr, size_t hdr_size, git_object_size_t obj_len, git_object_t obj_type); + +/* + * Hash an open file descriptor. + * This is a performance call when the contents of a fd need to be hashed, + * but the fd is already open and we have the size of the contents. + * + * Saves us some `stat` calls. + * + * The fd is never closed, not even on error. It must be opened and closed + * by the caller + */ +int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_object_t type); + +/* + * Hash an open file descriptor applying an array of filters + * Acts just like git_odb__hashfd with the addition of filters... + */ +int git_odb__hashfd_filtered( + git_oid *out, git_file fd, size_t len, git_object_t type, git_filter_list *fl); + +/* + * Hash a `path`, assuming it could be a POSIX symlink: if the path is a + * symlink, then the raw contents of the symlink will be hashed. Otherwise, + * this will fallback to `git_odb__hashfd`. + * + * The hash type for this call is always `GIT_OBJECT_BLOB` because + * symlinks may only point to blobs. + */ +int git_odb__hashlink(git_oid *out, const char *path); + +/** + * Generate a GIT_EMISMATCH error for the ODB. + */ +int git_odb__error_mismatch( + const git_oid *expected, const git_oid *actual); + +/* + * Generate a GIT_ENOTFOUND error for the ODB. + */ +int git_odb__error_notfound( + const char *message, const git_oid *oid, size_t oid_len); + +/* + * Generate a GIT_EAMBIGUOUS error for the ODB. + */ +int git_odb__error_ambiguous(const char *message); + +/* + * Attempt to read object header or just return whole object if it could + * not be read. + */ +int git_odb__read_header_or_object( + git_odb_object **out, size_t *len_p, git_object_t *type_p, + git_odb *db, const git_oid *id); + +/* + * Attempt to get the ODB's commit-graph file. This object is still owned by + * the ODB. If the repository does not contain a commit-graph, it will return + * GIT_ENOTFOUND. + */ +int git_odb__get_commit_graph_file(git_commit_graph_file **out, git_odb *odb); + +/* freshen an entry in the object database */ +int git_odb__freshen(git_odb *db, const git_oid *id); + +/* fully free the object; internal method, DO NOT EXPORT */ +void git_odb_object__free(void *object); + +#endif diff --git a/src/libgit2/odb_loose.c b/src/libgit2/odb_loose.c new file mode 100644 index 000000000..463e24fa5 --- /dev/null +++ b/src/libgit2/odb_loose.c @@ -0,0 +1,1182 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include +#include "git2/object.h" +#include "git2/sys/odb_backend.h" +#include "futils.h" +#include "hash.h" +#include "odb.h" +#include "delta.h" +#include "filebuf.h" +#include "object.h" +#include "zstream.h" + +#include "git2/odb_backend.h" +#include "git2/types.h" + +/* maximum possible header length */ +#define MAX_HEADER_LEN 64 + +typedef struct { /* object header data */ + git_object_t type; /* object type */ + size_t size; /* object size */ +} obj_hdr; + +typedef struct { + git_odb_stream stream; + git_filebuf fbuf; +} loose_writestream; + +typedef struct { + git_odb_stream stream; + git_map map; + char start[MAX_HEADER_LEN]; + size_t start_len; + size_t start_read; + git_zstream zstream; +} loose_readstream; + +typedef struct loose_backend { + git_odb_backend parent; + + int object_zlib_level; /** loose object zlib compression level. */ + int fsync_object_files; /** loose object file fsync flag. */ + mode_t object_file_mode; + mode_t object_dir_mode; + + size_t objects_dirlen; + char objects_dir[GIT_FLEX_ARRAY]; +} loose_backend; + +/* State structure for exploring directories, + * in order to locate objects matching a short oid. + */ +typedef struct { + size_t dir_len; + unsigned char short_oid[GIT_OID_HEXSZ]; /* hex formatted oid to match */ + size_t short_oid_len; + int found; /* number of matching + * objects already found */ + unsigned char res_oid[GIT_OID_HEXSZ]; /* hex formatted oid of + * the object found */ +} loose_locate_object_state; + + +/*********************************************************** + * + * MISCELLANEOUS HELPER FUNCTIONS + * + ***********************************************************/ + +static int object_file_name( + git_str *name, const loose_backend *be, const git_oid *id) +{ + size_t alloclen; + + /* expand length for object root + 40 hex sha1 chars + 2 * '/' + '\0' */ + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, be->objects_dirlen, GIT_OID_HEXSZ); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 3); + if (git_str_grow(name, alloclen) < 0) + return -1; + + git_str_set(name, be->objects_dir, be->objects_dirlen); + git_fs_path_to_dir(name); + + /* loose object filename: aa/aaa... (41 bytes) */ + git_oid_pathfmt(name->ptr + name->size, id); + name->size += GIT_OID_HEXSZ + 1; + name->ptr[name->size] = '\0'; + + return 0; +} + +static int object_mkdir(const git_str *name, const loose_backend *be) +{ + return git_futils_mkdir_relative( + name->ptr + be->objects_dirlen, be->objects_dir, be->object_dir_mode, + GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR, NULL); +} + +static int parse_header_packlike( + obj_hdr *out, size_t *out_len, const unsigned char *data, size_t len) +{ + unsigned long c; + size_t shift, size, used = 0; + + if (len == 0) + goto on_error; + + c = data[used++]; + out->type = (c >> 4) & 7; + + size = c & 15; + shift = 4; + while (c & 0x80) { + if (len <= used) + goto on_error; + + if (sizeof(size_t) * 8 <= shift) + goto on_error; + + c = data[used++]; + size += (c & 0x7f) << shift; + shift += 7; + } + + out->size = size; + + if (out_len) + *out_len = used; + + return 0; + +on_error: + git_error_set(GIT_ERROR_OBJECT, "failed to parse loose object: invalid header"); + return -1; +} + +static int parse_header( + obj_hdr *out, + size_t *out_len, + const unsigned char *_data, + size_t data_len) +{ + const char *data = (char *)_data; + size_t i, typename_len, size_idx, size_len; + int64_t size; + + *out_len = 0; + + /* find the object type name */ + for (i = 0, typename_len = 0; i < data_len; i++, typename_len++) { + if (data[i] == ' ') + break; + } + + if (typename_len == data_len) + goto on_error; + + out->type = git_object_stringn2type(data, typename_len); + + size_idx = typename_len + 1; + for (i = size_idx, size_len = 0; i < data_len; i++, size_len++) { + if (data[i] == '\0') + break; + } + + if (i == data_len) + goto on_error; + + if (git__strntol64(&size, &data[size_idx], size_len, NULL, 10) < 0 || + size < 0) + goto on_error; + + if ((uint64_t)size > SIZE_MAX) { + git_error_set(GIT_ERROR_OBJECT, "object is larger than available memory"); + return -1; + } + + out->size = (size_t)size; + + if (GIT_ADD_SIZET_OVERFLOW(out_len, i, 1)) + goto on_error; + + return 0; + +on_error: + git_error_set(GIT_ERROR_OBJECT, "failed to parse loose object: invalid header"); + return -1; +} + +static int is_zlib_compressed_data(unsigned char *data, size_t data_len) +{ + unsigned int w; + + if (data_len < 2) + return 0; + + w = ((unsigned int)(data[0]) << 8) + data[1]; + return (data[0] & 0x8F) == 0x08 && !(w % 31); +} + +/*********************************************************** + * + * ODB OBJECT READING & WRITING + * + * Backend for the public API; read headers and full objects + * from the ODB. Write raw data to the ODB. + * + ***********************************************************/ + + +/* + * At one point, there was a loose object format that was intended to + * mimic the format used in pack-files. This was to allow easy copying + * of loose object data into packs. This format is no longer used, but + * we must still read it. + */ +static int read_loose_packlike(git_rawobj *out, git_str *obj) +{ + git_str body = GIT_STR_INIT; + const unsigned char *obj_data; + obj_hdr hdr; + size_t obj_len, head_len, alloc_size; + int error; + + obj_data = (unsigned char *)obj->ptr; + obj_len = obj->size; + + /* + * read the object header, which is an (uncompressed) + * binary encoding of the object type and size. + */ + if ((error = parse_header_packlike(&hdr, &head_len, obj_data, obj_len)) < 0) + goto done; + + if (!git_object_typeisloose(hdr.type) || head_len > obj_len) { + git_error_set(GIT_ERROR_ODB, "failed to inflate loose object"); + error = -1; + goto done; + } + + obj_data += head_len; + obj_len -= head_len; + + /* + * allocate a buffer and inflate the data into it + */ + if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, hdr.size, 1) || + git_str_init(&body, alloc_size) < 0) { + error = -1; + goto done; + } + + if ((error = git_zstream_inflatebuf(&body, obj_data, obj_len)) < 0) + goto done; + + out->len = hdr.size; + out->type = hdr.type; + out->data = git_str_detach(&body); + +done: + git_str_dispose(&body); + return error; +} + +static int read_loose_standard(git_rawobj *out, git_str *obj) +{ + git_zstream zstream = GIT_ZSTREAM_INIT; + unsigned char head[MAX_HEADER_LEN], *body = NULL; + size_t decompressed, head_len, body_len, alloc_size; + obj_hdr hdr; + int error; + + if ((error = git_zstream_init(&zstream, GIT_ZSTREAM_INFLATE)) < 0 || + (error = git_zstream_set_input(&zstream, git_str_cstr(obj), git_str_len(obj))) < 0) + goto done; + + decompressed = sizeof(head); + + /* + * inflate the initial part of the compressed buffer in order to + * parse the header; read the largest header possible, then push the + * remainder into the body buffer. + */ + if ((error = git_zstream_get_output(head, &decompressed, &zstream)) < 0 || + (error = parse_header(&hdr, &head_len, head, decompressed)) < 0) + goto done; + + if (!git_object_typeisloose(hdr.type)) { + git_error_set(GIT_ERROR_ODB, "failed to inflate disk object"); + error = -1; + goto done; + } + + /* + * allocate a buffer and inflate the object data into it + * (including the initial sequence in the head buffer). + */ + if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, hdr.size, 1) || + (body = git__calloc(1, alloc_size)) == NULL) { + error = -1; + goto done; + } + + GIT_ASSERT(decompressed >= head_len); + body_len = decompressed - head_len; + + if (body_len) + memcpy(body, head + head_len, body_len); + + decompressed = hdr.size - body_len; + if ((error = git_zstream_get_output(body + body_len, &decompressed, &zstream)) < 0) + goto done; + + if (!git_zstream_done(&zstream)) { + git_error_set(GIT_ERROR_ZLIB, "failed to finish zlib inflation: stream aborted prematurely"); + error = -1; + goto done; + } + + body[hdr.size] = '\0'; + + out->data = body; + out->len = hdr.size; + out->type = hdr.type; + +done: + if (error < 0) + git__free(body); + + git_zstream_free(&zstream); + return error; +} + +static int read_loose(git_rawobj *out, git_str *loc) +{ + int error; + git_str obj = GIT_STR_INIT; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(loc); + + if (git_str_oom(loc)) + return -1; + + out->data = NULL; + out->len = 0; + out->type = GIT_OBJECT_INVALID; + + if ((error = git_futils_readbuffer(&obj, loc->ptr)) < 0) + goto done; + + if (!is_zlib_compressed_data((unsigned char *)obj.ptr, obj.size)) + error = read_loose_packlike(out, &obj); + else + error = read_loose_standard(out, &obj); + +done: + git_str_dispose(&obj); + return error; +} + +static int read_header_loose_packlike( + git_rawobj *out, const unsigned char *data, size_t len) +{ + obj_hdr hdr; + size_t header_len; + int error; + + if ((error = parse_header_packlike(&hdr, &header_len, data, len)) < 0) + return error; + + out->len = hdr.size; + out->type = hdr.type; + + return error; +} + +static int read_header_loose_standard( + git_rawobj *out, const unsigned char *data, size_t len) +{ + git_zstream zs = GIT_ZSTREAM_INIT; + obj_hdr hdr = {0}; + unsigned char inflated[MAX_HEADER_LEN] = {0}; + size_t header_len, inflated_len = sizeof(inflated); + int error; + + if ((error = git_zstream_init(&zs, GIT_ZSTREAM_INFLATE)) < 0 || + (error = git_zstream_set_input(&zs, data, len)) < 0 || + (error = git_zstream_get_output_chunk(inflated, &inflated_len, &zs)) < 0 || + (error = parse_header(&hdr, &header_len, inflated, inflated_len)) < 0) + goto done; + + out->len = hdr.size; + out->type = hdr.type; + +done: + git_zstream_free(&zs); + return error; +} + +static int read_header_loose(git_rawobj *out, git_str *loc) +{ + unsigned char obj[1024]; + ssize_t obj_len; + int fd, error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(loc); + + if (git_str_oom(loc)) + return -1; + + out->data = NULL; + + if ((error = fd = git_futils_open_ro(loc->ptr)) < 0) + goto done; + + if ((obj_len = p_read(fd, obj, sizeof(obj))) < 0) { + error = (int)obj_len; + goto done; + } + + if (!is_zlib_compressed_data(obj, (size_t)obj_len)) + error = read_header_loose_packlike(out, obj, (size_t)obj_len); + else + error = read_header_loose_standard(out, obj, (size_t)obj_len); + + if (!error && !git_object_typeisloose(out->type)) { + git_error_set(GIT_ERROR_ZLIB, "failed to read loose object header"); + error = -1; + goto done; + } + +done: + if (fd >= 0) + p_close(fd); + return error; +} + +static int locate_object( + git_str *object_location, + loose_backend *backend, + const git_oid *oid) +{ + int error = object_file_name(object_location, backend, oid); + + if (!error && !git_fs_path_exists(object_location->ptr)) + return GIT_ENOTFOUND; + + return error; +} + +/* Explore an entry of a directory and see if it matches a short oid */ +static int fn_locate_object_short_oid(void *state, git_str *pathbuf) { + loose_locate_object_state *sstate = (loose_locate_object_state *)state; + + if (git_str_len(pathbuf) - sstate->dir_len != GIT_OID_HEXSZ - 2) { + /* Entry cannot be an object. Continue to next entry */ + return 0; + } + + if (git_fs_path_isdir(pathbuf->ptr) == false) { + /* We are already in the directory matching the 2 first hex characters, + * compare the first ncmp characters of the oids */ + if (!memcmp(sstate->short_oid + 2, + (unsigned char *)pathbuf->ptr + sstate->dir_len, + sstate->short_oid_len - 2)) { + + if (!sstate->found) { + sstate->res_oid[0] = sstate->short_oid[0]; + sstate->res_oid[1] = sstate->short_oid[1]; + memcpy(sstate->res_oid+2, pathbuf->ptr+sstate->dir_len, GIT_OID_HEXSZ-2); + } + sstate->found++; + } + } + + if (sstate->found > 1) + return GIT_EAMBIGUOUS; + + return 0; +} + +/* Locate an object matching a given short oid */ +static int locate_object_short_oid( + git_str *object_location, + git_oid *res_oid, + loose_backend *backend, + const git_oid *short_oid, + size_t len) +{ + char *objects_dir = backend->objects_dir; + size_t dir_len = strlen(objects_dir), alloc_len; + loose_locate_object_state state; + int error; + + /* prealloc memory for OBJ_DIR/xx/xx..38x..xx */ + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, dir_len, GIT_OID_HEXSZ); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 3); + if (git_str_grow(object_location, alloc_len) < 0) + return -1; + + git_str_set(object_location, objects_dir, dir_len); + git_fs_path_to_dir(object_location); + + /* save adjusted position at end of dir so it can be restored later */ + dir_len = git_str_len(object_location); + + /* Convert raw oid to hex formatted oid */ + git_oid_fmt((char *)state.short_oid, short_oid); + + /* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */ + if (git_str_put(object_location, (char *)state.short_oid, 3) < 0) + return -1; + object_location->ptr[object_location->size - 1] = '/'; + + /* Check that directory exists */ + if (git_fs_path_isdir(object_location->ptr) == false) + return git_odb__error_notfound("no matching loose object for prefix", + short_oid, len); + + state.dir_len = git_str_len(object_location); + state.short_oid_len = len; + state.found = 0; + + /* Explore directory to find a unique object matching short_oid */ + error = git_fs_path_direach( + object_location, 0, fn_locate_object_short_oid, &state); + if (error < 0 && error != GIT_EAMBIGUOUS) + return error; + + if (!state.found) + return git_odb__error_notfound("no matching loose object for prefix", + short_oid, len); + + if (state.found > 1) + return git_odb__error_ambiguous("multiple matches in loose objects"); + + /* Convert obtained hex formatted oid to raw */ + error = git_oid_fromstr(res_oid, (char *)state.res_oid); + if (error) + return error; + + /* Update the location according to the oid obtained */ + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, dir_len, GIT_OID_HEXSZ); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); + + git_str_truncate(object_location, dir_len); + if (git_str_grow(object_location, alloc_len) < 0) + return -1; + + git_oid_pathfmt(object_location->ptr + dir_len, res_oid); + + object_location->size += GIT_OID_HEXSZ + 1; + object_location->ptr[object_location->size] = '\0'; + + return 0; +} + + + + + + + + + +/*********************************************************** + * + * LOOSE BACKEND PUBLIC API + * + * Implement the git_odb_backend API calls + * + ***********************************************************/ + +static int loose_backend__read_header(size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) +{ + git_str object_path = GIT_STR_INIT; + git_rawobj raw; + int error; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(oid); + + raw.len = 0; + raw.type = GIT_OBJECT_INVALID; + + if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) { + error = git_odb__error_notfound("no matching loose object", + oid, GIT_OID_HEXSZ); + } else if ((error = read_header_loose(&raw, &object_path)) == 0) { + *len_p = raw.len; + *type_p = raw.type; + } + + git_str_dispose(&object_path); + + return error; +} + +static int loose_backend__read(void **buffer_p, size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) +{ + git_str object_path = GIT_STR_INIT; + git_rawobj raw; + int error = 0; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(oid); + + if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) { + error = git_odb__error_notfound("no matching loose object", + oid, GIT_OID_HEXSZ); + } else if ((error = read_loose(&raw, &object_path)) == 0) { + *buffer_p = raw.data; + *len_p = raw.len; + *type_p = raw.type; + } + + git_str_dispose(&object_path); + + return error; +} + +static int loose_backend__read_prefix( + git_oid *out_oid, + void **buffer_p, + size_t *len_p, + git_object_t *type_p, + git_odb_backend *backend, + const git_oid *short_oid, + size_t len) +{ + int error = 0; + + GIT_ASSERT_ARG(len >= GIT_OID_MINPREFIXLEN && len <= GIT_OID_HEXSZ); + + if (len == GIT_OID_HEXSZ) { + /* We can fall back to regular read method */ + error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid); + if (!error) + git_oid_cpy(out_oid, short_oid); + } else { + git_str object_path = GIT_STR_INIT; + git_rawobj raw; + + GIT_ASSERT_ARG(backend && short_oid); + + if ((error = locate_object_short_oid(&object_path, out_oid, + (loose_backend *)backend, short_oid, len)) == 0 && + (error = read_loose(&raw, &object_path)) == 0) + { + *buffer_p = raw.data; + *len_p = raw.len; + *type_p = raw.type; + } + + git_str_dispose(&object_path); + } + + return error; +} + +static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid) +{ + git_str object_path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(oid); + + error = locate_object(&object_path, (loose_backend *)backend, oid); + + git_str_dispose(&object_path); + + return !error; +} + +static int loose_backend__exists_prefix( + git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len) +{ + git_str object_path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(short_id); + GIT_ASSERT_ARG(len >= GIT_OID_MINPREFIXLEN); + + error = locate_object_short_oid( + &object_path, out, (loose_backend *)backend, short_id, len); + + git_str_dispose(&object_path); + + return error; +} + +struct foreach_state { + size_t dir_len; + git_odb_foreach_cb cb; + void *data; +}; + +GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr) +{ + int v, i = 0; + if (strlen(ptr) != GIT_OID_HEXSZ+1) + return -1; + + if (ptr[2] != '/') { + return -1; + } + + v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i+1]); + if (v < 0) + return -1; + + oid->id[0] = (unsigned char) v; + + ptr += 3; + for (i = 0; i < 38; i += 2) { + v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i + 1]); + if (v < 0) + return -1; + + oid->id[1 + i/2] = (unsigned char) v; + } + + return 0; +} + +static int foreach_object_dir_cb(void *_state, git_str *path) +{ + git_oid oid; + struct foreach_state *state = (struct foreach_state *) _state; + + if (filename_to_oid(&oid, path->ptr + state->dir_len) < 0) + return 0; + + return git_error_set_after_callback_function( + state->cb(&oid, state->data), "git_odb_foreach"); +} + +static int foreach_cb(void *_state, git_str *path) +{ + struct foreach_state *state = (struct foreach_state *) _state; + + /* non-dir is some stray file, ignore it */ + if (!git_fs_path_isdir(git_str_cstr(path))) + return 0; + + return git_fs_path_direach(path, 0, foreach_object_dir_cb, state); +} + +static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data) +{ + char *objects_dir; + int error; + git_str buf = GIT_STR_INIT; + struct foreach_state state; + loose_backend *backend = (loose_backend *) _backend; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(cb); + + objects_dir = backend->objects_dir; + + git_str_sets(&buf, objects_dir); + git_fs_path_to_dir(&buf); + if (git_str_oom(&buf)) + return -1; + + memset(&state, 0, sizeof(state)); + state.cb = cb; + state.data = data; + state.dir_len = git_str_len(&buf); + + error = git_fs_path_direach(&buf, 0, foreach_cb, &state); + + git_str_dispose(&buf); + + return error; +} + +static int loose_backend__writestream_finalize(git_odb_stream *_stream, const git_oid *oid) +{ + loose_writestream *stream = (loose_writestream *)_stream; + loose_backend *backend = (loose_backend *)_stream->backend; + git_str final_path = GIT_STR_INIT; + int error = 0; + + if (object_file_name(&final_path, backend, oid) < 0 || + object_mkdir(&final_path, backend) < 0) + error = -1; + else + error = git_filebuf_commit_at( + &stream->fbuf, final_path.ptr); + + git_str_dispose(&final_path); + + return error; +} + +static int loose_backend__writestream_write(git_odb_stream *_stream, const char *data, size_t len) +{ + loose_writestream *stream = (loose_writestream *)_stream; + return git_filebuf_write(&stream->fbuf, data, len); +} + +static void loose_backend__writestream_free(git_odb_stream *_stream) +{ + loose_writestream *stream = (loose_writestream *)_stream; + + git_filebuf_cleanup(&stream->fbuf); + git__free(stream); +} + +static int filebuf_flags(loose_backend *backend) +{ + int flags = GIT_FILEBUF_TEMPORARY | + (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT); + + if (backend->fsync_object_files || git_repository__fsync_gitdir) + flags |= GIT_FILEBUF_FSYNC; + + return flags; +} + +static int loose_backend__writestream(git_odb_stream **stream_out, git_odb_backend *_backend, git_object_size_t length, git_object_t type) +{ + loose_backend *backend; + loose_writestream *stream = NULL; + char hdr[MAX_HEADER_LEN]; + git_str tmp_path = GIT_STR_INIT; + size_t hdrlen; + int error; + + GIT_ASSERT_ARG(_backend); + + backend = (loose_backend *)_backend; + *stream_out = NULL; + + if ((error = git_odb__format_object_header(&hdrlen, + hdr, sizeof(hdr), length, type)) < 0) + return error; + + stream = git__calloc(1, sizeof(loose_writestream)); + GIT_ERROR_CHECK_ALLOC(stream); + + stream->stream.backend = _backend; + stream->stream.read = NULL; /* read only */ + stream->stream.write = &loose_backend__writestream_write; + stream->stream.finalize_write = &loose_backend__writestream_finalize; + stream->stream.free = &loose_backend__writestream_free; + stream->stream.mode = GIT_STREAM_WRONLY; + + if (git_str_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 || + git_filebuf_open(&stream->fbuf, tmp_path.ptr, filebuf_flags(backend), + backend->object_file_mode) < 0 || + stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0) + { + git_filebuf_cleanup(&stream->fbuf); + git__free(stream); + stream = NULL; + } + git_str_dispose(&tmp_path); + *stream_out = (git_odb_stream *)stream; + + return !stream ? -1 : 0; +} + +static int loose_backend__readstream_read( + git_odb_stream *_stream, + char *buffer, + size_t buffer_len) +{ + loose_readstream *stream = (loose_readstream *)_stream; + size_t start_remain = stream->start_len - stream->start_read; + int total = 0, error; + + buffer_len = min(buffer_len, INT_MAX); + + /* + * if we read more than just the header in the initial read, play + * that back for the caller. + */ + if (start_remain && buffer_len) { + size_t chunk = min(start_remain, buffer_len); + memcpy(buffer, stream->start + stream->start_read, chunk); + + buffer += chunk; + stream->start_read += chunk; + + total += (int)chunk; + buffer_len -= chunk; + } + + if (buffer_len) { + size_t chunk = buffer_len; + + if ((error = git_zstream_get_output(buffer, &chunk, &stream->zstream)) < 0) + return error; + + total += (int)chunk; + } + + return (int)total; +} + +static void loose_backend__readstream_free(git_odb_stream *_stream) +{ + loose_readstream *stream = (loose_readstream *)_stream; + + git_futils_mmap_free(&stream->map); + git_zstream_free(&stream->zstream); + git__free(stream); +} + +static int loose_backend__readstream_packlike( + obj_hdr *hdr, + loose_readstream *stream) +{ + const unsigned char *data; + size_t data_len, head_len; + int error; + + data = stream->map.data; + data_len = stream->map.len; + + /* + * read the object header, which is an (uncompressed) + * binary encoding of the object type and size. + */ + if ((error = parse_header_packlike(hdr, &head_len, data, data_len)) < 0) + return error; + + if (!git_object_typeisloose(hdr->type)) { + git_error_set(GIT_ERROR_ODB, "failed to inflate loose object"); + return -1; + } + + return git_zstream_set_input(&stream->zstream, + data + head_len, data_len - head_len); +} + +static int loose_backend__readstream_standard( + obj_hdr *hdr, + loose_readstream *stream) +{ + unsigned char head[MAX_HEADER_LEN]; + size_t init, head_len; + int error; + + if ((error = git_zstream_set_input(&stream->zstream, + stream->map.data, stream->map.len)) < 0) + return error; + + init = sizeof(head); + + /* + * inflate the initial part of the compressed buffer in order to + * parse the header; read the largest header possible, then store + * it in the `start` field of the stream object. + */ + if ((error = git_zstream_get_output(head, &init, &stream->zstream)) < 0 || + (error = parse_header(hdr, &head_len, head, init)) < 0) + return error; + + if (!git_object_typeisloose(hdr->type)) { + git_error_set(GIT_ERROR_ODB, "failed to inflate disk object"); + return -1; + } + + if (init > head_len) { + stream->start_len = init - head_len; + memcpy(stream->start, head + head_len, init - head_len); + } + + return 0; +} + +static int loose_backend__readstream( + git_odb_stream **stream_out, + size_t *len_out, + git_object_t *type_out, + git_odb_backend *_backend, + const git_oid *oid) +{ + loose_backend *backend; + loose_readstream *stream = NULL; + git_hash_ctx *hash_ctx = NULL; + git_str object_path = GIT_STR_INIT; + obj_hdr hdr; + int error = 0; + + GIT_ASSERT_ARG(stream_out); + GIT_ASSERT_ARG(len_out); + GIT_ASSERT_ARG(type_out); + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(oid); + + backend = (loose_backend *)_backend; + *stream_out = NULL; + *len_out = 0; + *type_out = GIT_OBJECT_INVALID; + + if (locate_object(&object_path, backend, oid) < 0) { + error = git_odb__error_notfound("no matching loose object", + oid, GIT_OID_HEXSZ); + goto done; + } + + stream = git__calloc(1, sizeof(loose_readstream)); + GIT_ERROR_CHECK_ALLOC(stream); + + hash_ctx = git__malloc(sizeof(git_hash_ctx)); + GIT_ERROR_CHECK_ALLOC(hash_ctx); + + if ((error = git_hash_ctx_init(hash_ctx, GIT_HASH_ALGORITHM_SHA1)) < 0 || + (error = git_futils_mmap_ro_file(&stream->map, object_path.ptr)) < 0 || + (error = git_zstream_init(&stream->zstream, GIT_ZSTREAM_INFLATE)) < 0) + goto done; + + /* check for a packlike loose object */ + if (!is_zlib_compressed_data(stream->map.data, stream->map.len)) + error = loose_backend__readstream_packlike(&hdr, stream); + else + error = loose_backend__readstream_standard(&hdr, stream); + + if (error < 0) + goto done; + + stream->stream.backend = _backend; + stream->stream.hash_ctx = hash_ctx; + stream->stream.read = &loose_backend__readstream_read; + stream->stream.free = &loose_backend__readstream_free; + + *stream_out = (git_odb_stream *)stream; + *len_out = hdr.size; + *type_out = hdr.type; + +done: + if (error < 0) { + if (stream) { + git_futils_mmap_free(&stream->map); + git_zstream_free(&stream->zstream); + git__free(stream); + } + if (hash_ctx) { + git_hash_ctx_cleanup(hash_ctx); + git__free(hash_ctx); + } + } + + git_str_dispose(&object_path); + return error; +} + +static int loose_backend__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_object_t type) +{ + int error = 0; + git_str final_path = GIT_STR_INIT; + char header[MAX_HEADER_LEN]; + size_t header_len; + git_filebuf fbuf = GIT_FILEBUF_INIT; + loose_backend *backend; + + backend = (loose_backend *)_backend; + + /* prepare the header for the file */ + if ((error = git_odb__format_object_header(&header_len, + header, sizeof(header), len, type)) < 0) + goto cleanup; + + if (git_str_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 || + git_filebuf_open(&fbuf, final_path.ptr, filebuf_flags(backend), + backend->object_file_mode) < 0) + { + error = -1; + goto cleanup; + } + + git_filebuf_write(&fbuf, header, header_len); + git_filebuf_write(&fbuf, data, len); + + if (object_file_name(&final_path, backend, oid) < 0 || + object_mkdir(&final_path, backend) < 0 || + git_filebuf_commit_at(&fbuf, final_path.ptr) < 0) + error = -1; + +cleanup: + if (error < 0) + git_filebuf_cleanup(&fbuf); + git_str_dispose(&final_path); + return error; +} + +static int loose_backend__freshen( + git_odb_backend *_backend, + const git_oid *oid) +{ + loose_backend *backend = (loose_backend *)_backend; + git_str path = GIT_STR_INIT; + int error; + + if (object_file_name(&path, backend, oid) < 0) + return -1; + + error = git_futils_touch(path.ptr, NULL); + git_str_dispose(&path); + + return error; +} + +static void loose_backend__free(git_odb_backend *_backend) +{ + git__free(_backend); +} + +int git_odb_backend_loose( + git_odb_backend **backend_out, + const char *objects_dir, + int compression_level, + int do_fsync, + unsigned int dir_mode, + unsigned int file_mode) +{ + loose_backend *backend; + size_t objects_dirlen, alloclen; + + GIT_ASSERT_ARG(backend_out); + GIT_ASSERT_ARG(objects_dir); + + objects_dirlen = strlen(objects_dir); + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(loose_backend), objects_dirlen); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 2); + backend = git__calloc(1, alloclen); + GIT_ERROR_CHECK_ALLOC(backend); + + backend->parent.version = GIT_ODB_BACKEND_VERSION; + backend->objects_dirlen = objects_dirlen; + memcpy(backend->objects_dir, objects_dir, objects_dirlen); + if (backend->objects_dir[backend->objects_dirlen - 1] != '/') + backend->objects_dir[backend->objects_dirlen++] = '/'; + + if (compression_level < 0) + compression_level = Z_BEST_SPEED; + + if (dir_mode == 0) + dir_mode = GIT_OBJECT_DIR_MODE; + + if (file_mode == 0) + file_mode = GIT_OBJECT_FILE_MODE; + + backend->object_zlib_level = compression_level; + backend->fsync_object_files = do_fsync; + backend->object_dir_mode = dir_mode; + backend->object_file_mode = file_mode; + + backend->parent.read = &loose_backend__read; + backend->parent.write = &loose_backend__write; + backend->parent.read_prefix = &loose_backend__read_prefix; + backend->parent.read_header = &loose_backend__read_header; + backend->parent.writestream = &loose_backend__writestream; + backend->parent.readstream = &loose_backend__readstream; + backend->parent.exists = &loose_backend__exists; + backend->parent.exists_prefix = &loose_backend__exists_prefix; + backend->parent.foreach = &loose_backend__foreach; + backend->parent.freshen = &loose_backend__freshen; + backend->parent.free = &loose_backend__free; + + *backend_out = (git_odb_backend *)backend; + return 0; +} diff --git a/src/libgit2/odb_mempack.c b/src/libgit2/odb_mempack.c new file mode 100644 index 000000000..6f27f45f8 --- /dev/null +++ b/src/libgit2/odb_mempack.c @@ -0,0 +1,189 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "buf.h" +#include "futils.h" +#include "hash.h" +#include "odb.h" +#include "array.h" +#include "oidmap.h" +#include "pack-objects.h" + +#include "git2/odb_backend.h" +#include "git2/object.h" +#include "git2/types.h" +#include "git2/pack.h" +#include "git2/sys/odb_backend.h" +#include "git2/sys/mempack.h" + +struct memobject { + git_oid oid; + size_t len; + git_object_t type; + char data[GIT_FLEX_ARRAY]; +}; + +struct memory_packer_db { + git_odb_backend parent; + git_oidmap *objects; + git_array_t(struct memobject *) commits; +}; + +static int impl__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_object_t type) +{ + struct memory_packer_db *db = (struct memory_packer_db *)_backend; + struct memobject *obj = NULL; + size_t alloc_len; + + if (git_oidmap_exists(db->objects, oid)) + return 0; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(struct memobject), len); + obj = git__malloc(alloc_len); + GIT_ERROR_CHECK_ALLOC(obj); + + memcpy(obj->data, data, len); + git_oid_cpy(&obj->oid, oid); + obj->len = len; + obj->type = type; + + if (git_oidmap_set(db->objects, &obj->oid, obj) < 0) + return -1; + + if (type == GIT_OBJECT_COMMIT) { + struct memobject **store = git_array_alloc(db->commits); + GIT_ERROR_CHECK_ALLOC(store); + *store = obj; + } + + return 0; +} + +static int impl__exists(git_odb_backend *backend, const git_oid *oid) +{ + struct memory_packer_db *db = (struct memory_packer_db *)backend; + + return git_oidmap_exists(db->objects, oid); +} + +static int impl__read(void **buffer_p, size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) +{ + struct memory_packer_db *db = (struct memory_packer_db *)backend; + struct memobject *obj; + + if ((obj = git_oidmap_get(db->objects, oid)) == NULL) + return GIT_ENOTFOUND; + + *len_p = obj->len; + *type_p = obj->type; + *buffer_p = git__malloc(obj->len); + GIT_ERROR_CHECK_ALLOC(*buffer_p); + + memcpy(*buffer_p, obj->data, obj->len); + return 0; +} + +static int impl__read_header(size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) +{ + struct memory_packer_db *db = (struct memory_packer_db *)backend; + struct memobject *obj; + + if ((obj = git_oidmap_get(db->objects, oid)) == NULL) + return GIT_ENOTFOUND; + + *len_p = obj->len; + *type_p = obj->type; + return 0; +} + +static int git_mempack__dump( + git_str *pack, + git_repository *repo, + git_odb_backend *_backend) +{ + struct memory_packer_db *db = (struct memory_packer_db *)_backend; + git_packbuilder *packbuilder; + uint32_t i; + int err = -1; + + if (git_packbuilder_new(&packbuilder, repo) < 0) + return -1; + + git_packbuilder_set_threads(packbuilder, 0); + + for (i = 0; i < db->commits.size; ++i) { + struct memobject *commit = db->commits.ptr[i]; + + err = git_packbuilder_insert_commit(packbuilder, &commit->oid); + if (err < 0) + goto cleanup; + } + + err = git_packbuilder__write_buf(pack, packbuilder); + +cleanup: + git_packbuilder_free(packbuilder); + return err; +} + +int git_mempack_dump( + git_buf *pack, + git_repository *repo, + git_odb_backend *_backend) +{ + GIT_BUF_WRAP_PRIVATE(pack, git_mempack__dump, repo, _backend); +} + +int git_mempack_reset(git_odb_backend *_backend) +{ + struct memory_packer_db *db = (struct memory_packer_db *)_backend; + struct memobject *object = NULL; + + git_oidmap_foreach_value(db->objects, object, { + git__free(object); + }); + + git_array_clear(db->commits); + + git_oidmap_clear(db->objects); + + return 0; +} + +static void impl__free(git_odb_backend *_backend) +{ + struct memory_packer_db *db = (struct memory_packer_db *)_backend; + + git_mempack_reset(_backend); + git_oidmap_free(db->objects); + git__free(db); +} + +int git_mempack_new(git_odb_backend **out) +{ + struct memory_packer_db *db; + + GIT_ASSERT_ARG(out); + + db = git__calloc(1, sizeof(struct memory_packer_db)); + GIT_ERROR_CHECK_ALLOC(db); + + if (git_oidmap_new(&db->objects) < 0) + return -1; + + db->parent.version = GIT_ODB_BACKEND_VERSION; + db->parent.read = &impl__read; + db->parent.write = &impl__write; + db->parent.read_header = &impl__read_header; + db->parent.exists = &impl__exists; + db->parent.free = &impl__free; + + *out = (git_odb_backend *)db; + return 0; +} diff --git a/src/libgit2/odb_pack.c b/src/libgit2/odb_pack.c new file mode 100644 index 000000000..818cc6125 --- /dev/null +++ b/src/libgit2/odb_pack.c @@ -0,0 +1,921 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include +#include "git2/repository.h" +#include "git2/indexer.h" +#include "git2/sys/odb_backend.h" +#include "delta.h" +#include "futils.h" +#include "hash.h" +#include "midx.h" +#include "mwindow.h" +#include "odb.h" +#include "pack.h" + +#include "git2/odb_backend.h" + +/* re-freshen pack files no more than every 2 seconds */ +#define FRESHEN_FREQUENCY 2 + +struct pack_backend { + git_odb_backend parent; + git_midx_file *midx; + git_vector midx_packs; + git_vector packs; + struct git_pack_file *last_found; + char *pack_folder; +}; + +struct pack_writepack { + struct git_odb_writepack parent; + git_indexer *indexer; +}; + +/** + * The wonderful tale of a Packed Object lookup query + * =================================================== + * A riveting and epic story of epicness and ASCII + * art, presented by yours truly, + * Sir Vicent of Marti + * + * + * Chapter 1: Once upon a time... + * Initialization of the Pack Backend + * -------------------------------------------------- + * + * # git_odb_backend_pack + * | Creates the pack backend structure, initializes the + * | callback pointers to our default read() and exist() methods, + * | and tries to find the `pack` folder, if it exists. ODBs without a `pack` + * | folder are ignored altogether. If there is a `pack` folder, it tries to + * | preload all the known packfiles in the ODB. + * | + * |-# pack_backend__refresh + * | The `multi-pack-index` is loaded if it exists and is valid. + * | Then we run a `dirent` callback through every file in the pack folder, + * | even those present in `multi-pack-index`. The unindexed packfiles are + * | then sorted according to a sorting callback. + * | + * |-# refresh_multi_pack_index + * | Detect the presence of the `multi-pack-index` file. If it needs to be + * | refreshed, frees the old copy and tries to load the new one, together + * | with all the packfiles it indexes. If the process fails, fall back to + * | the old behavior, as if the `multi-pack-index` file was not there. + * | + * |-# packfile_load__cb + * | | This callback is called from `dirent` with every single file + * | | inside the pack folder. We find the packs by actually locating + * | | their index (ends in ".idx"). From that index, we verify that + * | | the corresponding packfile exists and is valid, and if so, we + * | | add it to the pack list. + * | | + * | # git_mwindow_get_pack + * | Make sure that there's a packfile to back this index, and store + * | some very basic information regarding the packfile itself, + * | such as the full path, the size, and the modification time. + * | We don't actually open the packfile to check for internal consistency. + * | + * |-# packfile_sort__cb + * Sort all the preloaded packs according to some specific criteria: + * we prioritize the "newer" packs because it's more likely they + * contain the objects we are looking for, and we prioritize local + * packs over remote ones. + * + * + * + * Chapter 2: To be, or not to be... + * A standard packed `exist` query for an OID + * -------------------------------------------------- + * + * # pack_backend__exists / pack_backend__exists_prefix + * | Check if the given SHA1 oid (or a SHA1 oid prefix) exists in any of the + * | packs that have been loaded for our ODB. + * | + * |-# pack_entry_find / pack_entry_find_prefix + * | If there is a multi-pack-index present, search the SHA1 oid in that + * | index first. If it is not found there, iterate through all the unindexed + * | packs that have been preloaded (starting by the pack where the latest + * | object was found) to try to find the OID in one of them. + * | + * |-# git_midx_entry_find + * | Search for the SHA1 oid in the multi-pack-index. See + * | + * | for specifics on the multi-pack-index format and how do we find + * | entries in it. + * | + * |-# git_pack_entry_find + * | Check the index of an individual unindexed pack to see if the SHA1 + * | OID can be found. If we can find the offset to that SHA1 inside of the + * | index, that means the object is contained inside of the packfile and + * | we can stop searching. Before returning, we verify that the + * | packfile behind the index we are searching still exists on disk. + * | + * |-# pack_entry_find_offset + * | Mmap the actual index file to disk if it hasn't been opened + * | yet, and run a binary search through it to find the OID. + * | See + * | for specifics on the Packfile Index format and how do we find + * | entries in it. + * | + * |-# pack_index_open + * | Guess the name of the index based on the full path to the + * | packfile, open it and verify its contents. Only if the index + * | has not been opened already. + * | + * |-# pack_index_check + * Mmap the index file and do a quick run through the header + * to guess the index version (right now we support v1 and v2), + * and to verify that the size of the index makes sense. + * + * + * + * Chapter 3: The neverending story... + * A standard packed `lookup` query for an OID + * -------------------------------------------------- + * + * # pack_backend__read / pack_backend__read_prefix + * | Check if the given SHA1 oid (or a SHA1 oid prefix) exists in any of the + * | packs that have been loaded for our ODB. If it does, open the packfile and + * | read from it. + * | + * |-# git_packfile_unpack + * Armed with a packfile and the offset within it, we can finally unpack + * the object pointed at by the SHA1 oid. This involves mmapping part of + * the `.pack` file, and uncompressing the object within it (if it is + * stored in the undelfitied representation), or finding a base object and + * applying some deltas to its uncompressed representation (if it is stored + * in the deltified representation). See + * + * for specifics on the Packfile format and how do we read from it. + * + */ + + +/*********************************************************** + * + * FORWARD DECLARATIONS + * + ***********************************************************/ + +static int packfile_sort__cb(const void *a_, const void *b_); + +static int packfile_load__cb(void *_data, git_str *path); + +static int packfile_byname_search_cmp(const void *path, const void *pack_entry); + +static int pack_entry_find(struct git_pack_entry *e, + struct pack_backend *backend, const git_oid *oid); + +/* Can find the offset of an object given + * a prefix of an identifier. + * Sets GIT_EAMBIGUOUS if short oid is ambiguous. + * This method assumes that len is between + * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. + */ +static int pack_entry_find_prefix( + struct git_pack_entry *e, + struct pack_backend *backend, + const git_oid *short_oid, + size_t len); + + + +/*********************************************************** + * + * PACK WINDOW MANAGEMENT + * + ***********************************************************/ + +static int packfile_byname_search_cmp(const void *path_, const void *p_) +{ + const git_str *path = (const git_str *)path_; + const struct git_pack_file *p = (const struct git_pack_file *)p_; + + return strncmp(p->pack_name, git_str_cstr(path), git_str_len(path)); +} + +static int packfile_sort__cb(const void *a_, const void *b_) +{ + const struct git_pack_file *a = a_; + const struct git_pack_file *b = b_; + int st; + + /* + * Local packs tend to contain objects specific to our + * variant of the project than remote ones. In addition, + * remote ones could be on a network mounted filesystem. + * Favor local ones for these reasons. + */ + st = a->pack_local - b->pack_local; + if (st) + return -st; + + /* + * Younger packs tend to contain more recent objects, + * and more recent objects tend to get accessed more + * often. + */ + if (a->mtime < b->mtime) + return 1; + else if (a->mtime == b->mtime) + return 0; + + return -1; +} + + +static int packfile_load__cb(void *data, git_str *path) +{ + struct pack_backend *backend = data; + struct git_pack_file *pack; + const char *path_str = git_str_cstr(path); + git_str index_prefix = GIT_STR_INIT; + size_t cmp_len = git_str_len(path); + int error; + + if (cmp_len <= strlen(".idx") || git__suffixcmp(path_str, ".idx") != 0) + return 0; /* not an index */ + + cmp_len -= strlen(".idx"); + git_str_attach_notowned(&index_prefix, path_str, cmp_len); + + if (git_vector_search2(NULL, &backend->midx_packs, packfile_byname_search_cmp, &index_prefix) == 0) + return 0; + if (git_vector_search2(NULL, &backend->packs, packfile_byname_search_cmp, &index_prefix) == 0) + return 0; + + error = git_mwindow_get_pack(&pack, path->ptr); + + /* ignore missing .pack file as git does */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + return 0; + } + + if (!error) + error = git_vector_insert(&backend->packs, pack); + + return error; + +} + +static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backend, const git_oid *oid) +{ + struct git_pack_file *last_found = backend->last_found, *p; + git_midx_entry midx_entry; + size_t i; + + if (backend->midx && + git_midx_entry_find(&midx_entry, backend->midx, oid, GIT_OID_HEXSZ) == 0 && + midx_entry.pack_index < git_vector_length(&backend->midx_packs)) { + e->offset = midx_entry.offset; + git_oid_cpy(&e->sha1, &midx_entry.sha1); + e->p = git_vector_get(&backend->midx_packs, midx_entry.pack_index); + return 0; + } + + if (last_found && + git_pack_entry_find(e, last_found, oid, GIT_OID_HEXSZ) == 0) + return 0; + + git_vector_foreach(&backend->packs, i, p) { + if (p == last_found) + continue; + + if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) { + backend->last_found = p; + return 0; + } + } + + return git_odb__error_notfound( + "failed to find pack entry", oid, GIT_OID_HEXSZ); +} + +static int pack_entry_find_prefix( + struct git_pack_entry *e, + struct pack_backend *backend, + const git_oid *short_oid, + size_t len) +{ + int error; + size_t i; + git_oid found_full_oid = {{0}}; + bool found = false; + struct git_pack_file *last_found = backend->last_found, *p; + git_midx_entry midx_entry; + + if (backend->midx) { + error = git_midx_entry_find(&midx_entry, backend->midx, short_oid, len); + if (error == GIT_EAMBIGUOUS) + return error; + if (!error && midx_entry.pack_index < git_vector_length(&backend->midx_packs)) { + e->offset = midx_entry.offset; + git_oid_cpy(&e->sha1, &midx_entry.sha1); + e->p = git_vector_get(&backend->midx_packs, midx_entry.pack_index); + git_oid_cpy(&found_full_oid, &e->sha1); + found = true; + } + } + + if (last_found) { + error = git_pack_entry_find(e, last_found, short_oid, len); + if (error == GIT_EAMBIGUOUS) + return error; + if (!error) { + if (found && git_oid_cmp(&e->sha1, &found_full_oid)) + return git_odb__error_ambiguous("found multiple pack entries"); + git_oid_cpy(&found_full_oid, &e->sha1); + found = true; + } + } + + git_vector_foreach(&backend->packs, i, p) { + if (p == last_found) + continue; + + error = git_pack_entry_find(e, p, short_oid, len); + if (error == GIT_EAMBIGUOUS) + return error; + if (!error) { + if (found && git_oid_cmp(&e->sha1, &found_full_oid)) + return git_odb__error_ambiguous("found multiple pack entries"); + git_oid_cpy(&found_full_oid, &e->sha1); + found = true; + backend->last_found = p; + } + } + + if (!found) + return git_odb__error_notfound("no matching pack entry for prefix", + short_oid, len); + else + return 0; +} + +/*********************************************************** + * + * MULTI-PACK-INDEX SUPPORT + * + * Functions needed to support the multi-pack-index. + * + ***********************************************************/ + +/* + * Remove the multi-pack-index, and move all midx_packs to packs. + */ +static int remove_multi_pack_index(struct pack_backend *backend) +{ + size_t i, j = git_vector_length(&backend->packs); + struct pack_backend *p; + int error = git_vector_size_hint( + &backend->packs, + j + git_vector_length(&backend->midx_packs)); + if (error < 0) + return error; + + git_vector_foreach(&backend->midx_packs, i, p) + git_vector_set(NULL, &backend->packs, j++, p); + git_vector_clear(&backend->midx_packs); + + git_midx_free(backend->midx); + backend->midx = NULL; + + return 0; +} + +/* + * Loads a single .pack file referred to by the multi-pack-index. These must + * match the order in which they are declared in the multi-pack-index file, + * since these files are referred to by their index. + */ +static int process_multi_pack_index_pack( + struct pack_backend *backend, + size_t i, + const char *packfile_name) +{ + int error; + struct git_pack_file *pack; + size_t found_position; + git_str pack_path = GIT_STR_INIT, index_prefix = GIT_STR_INIT; + + error = git_str_joinpath(&pack_path, backend->pack_folder, packfile_name); + if (error < 0) + return error; + + /* This is ensured by midx_parse_packfile_name() */ + if (git_str_len(&pack_path) <= strlen(".idx") || git__suffixcmp(git_str_cstr(&pack_path), ".idx") != 0) + return git_odb__error_notfound("midx file contained a non-index", NULL, 0); + + git_str_attach_notowned(&index_prefix, git_str_cstr(&pack_path), git_str_len(&pack_path) - strlen(".idx")); + + if (git_vector_search2(&found_position, &backend->packs, packfile_byname_search_cmp, &index_prefix) == 0) { + /* Pack was found in the packs list. Moving it to the midx_packs list. */ + git_str_dispose(&pack_path); + git_vector_set(NULL, &backend->midx_packs, i, git_vector_get(&backend->packs, found_position)); + git_vector_remove(&backend->packs, found_position); + return 0; + } + + /* Pack was not found. Allocate a new one. */ + error = git_mwindow_get_pack(&pack, git_str_cstr(&pack_path)); + git_str_dispose(&pack_path); + if (error < 0) + return error; + + git_vector_set(NULL, &backend->midx_packs, i, pack); + return 0; +} + +/* + * Reads the multi-pack-index. If this fails for whatever reason, the + * multi-pack-index object is freed, and all the packfiles that are related to + * it are moved to the unindexed packfiles vector. + */ +static int refresh_multi_pack_index(struct pack_backend *backend) +{ + int error; + git_str midx_path = GIT_STR_INIT; + const char *packfile_name; + size_t i; + + error = git_str_joinpath(&midx_path, backend->pack_folder, "multi-pack-index"); + if (error < 0) + return error; + + /* + * Check whether the multi-pack-index has changed. If it has, close any + * old multi-pack-index and move all the packfiles to the unindexed + * packs. This is done to prevent losing any open packfiles in case + * refreshing the new multi-pack-index fails, or the file is deleted. + */ + if (backend->midx) { + if (!git_midx_needs_refresh(backend->midx, git_str_cstr(&midx_path))) { + git_str_dispose(&midx_path); + return 0; + } + error = remove_multi_pack_index(backend); + if (error < 0) { + git_str_dispose(&midx_path); + return error; + } + } + + error = git_midx_open(&backend->midx, git_str_cstr(&midx_path)); + git_str_dispose(&midx_path); + if (error < 0) + return error; + + git_vector_resize_to(&backend->midx_packs, git_vector_length(&backend->midx->packfile_names)); + + git_vector_foreach(&backend->midx->packfile_names, i, packfile_name) { + error = process_multi_pack_index_pack(backend, i, packfile_name); + if (error < 0) { + /* + * Something failed during reading multi-pack-index. + * Restore the state of backend as if the + * multi-pack-index was never there, and move all + * packfiles that have been processed so far to the + * unindexed packs. + */ + git_vector_resize_to(&backend->midx_packs, i); + remove_multi_pack_index(backend); + return error; + } + } + + return 0; +} + +/*********************************************************** + * + * PACKED BACKEND PUBLIC API + * + * Implement the git_odb_backend API calls + * + ***********************************************************/ +static int pack_backend__refresh(git_odb_backend *backend_) +{ + int error; + struct stat st; + git_str path = GIT_STR_INIT; + struct pack_backend *backend = (struct pack_backend *)backend_; + + if (backend->pack_folder == NULL) + return 0; + + if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode)) + return git_odb__error_notfound("failed to refresh packfiles", NULL, 0); + + if (refresh_multi_pack_index(backend) < 0) { + /* + * It is okay if this fails. We will just not use the + * multi-pack-index in this case. + */ + git_error_clear(); + } + + /* reload all packs */ + git_str_sets(&path, backend->pack_folder); + error = git_fs_path_direach(&path, 0, packfile_load__cb, backend); + + git_str_dispose(&path); + git_vector_sort(&backend->packs); + + return error; +} + +static int pack_backend__read_header( + size_t *len_p, git_object_t *type_p, + struct git_odb_backend *backend, const git_oid *oid) +{ + struct git_pack_entry e; + int error; + + GIT_ASSERT_ARG(len_p); + GIT_ASSERT_ARG(type_p); + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(oid); + + if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0) + return error; + + return git_packfile_resolve_header(len_p, type_p, e.p, e.offset); +} + +static int pack_backend__freshen( + git_odb_backend *backend, const git_oid *oid) +{ + struct git_pack_entry e; + time_t now; + int error; + + if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0) + return error; + + now = time(NULL); + + if (e.p->last_freshen > now - FRESHEN_FREQUENCY) + return 0; + + if ((error = git_futils_touch(e.p->pack_name, &now)) < 0) + return error; + + e.p->last_freshen = now; + return 0; +} + +static int pack_backend__read( + void **buffer_p, size_t *len_p, git_object_t *type_p, + git_odb_backend *backend, const git_oid *oid) +{ + struct git_pack_entry e; + git_rawobj raw = {NULL}; + int error; + + if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0 || + (error = git_packfile_unpack(&raw, e.p, &e.offset)) < 0) + return error; + + *buffer_p = raw.data; + *len_p = raw.len; + *type_p = raw.type; + + return 0; +} + +static int pack_backend__read_prefix( + git_oid *out_oid, + void **buffer_p, + size_t *len_p, + git_object_t *type_p, + git_odb_backend *backend, + const git_oid *short_oid, + size_t len) +{ + int error = 0; + + if (len < GIT_OID_MINPREFIXLEN) + error = git_odb__error_ambiguous("prefix length too short"); + + else if (len >= GIT_OID_HEXSZ) { + /* We can fall back to regular read method */ + error = pack_backend__read(buffer_p, len_p, type_p, backend, short_oid); + if (!error) + git_oid_cpy(out_oid, short_oid); + } else { + struct git_pack_entry e; + git_rawobj raw = {NULL}; + + if ((error = pack_entry_find_prefix( + &e, (struct pack_backend *)backend, short_oid, len)) == 0 && + (error = git_packfile_unpack(&raw, e.p, &e.offset)) == 0) + { + *buffer_p = raw.data; + *len_p = raw.len; + *type_p = raw.type; + git_oid_cpy(out_oid, &e.sha1); + } + } + + return error; +} + +static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid) +{ + struct git_pack_entry e; + return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0; +} + +static int pack_backend__exists_prefix( + git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len) +{ + int error; + struct pack_backend *pb = (struct pack_backend *)backend; + struct git_pack_entry e = {0}; + + error = pack_entry_find_prefix(&e, pb, short_id, len); + git_oid_cpy(out, &e.sha1); + return error; +} + +static int pack_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data) +{ + int error; + struct git_pack_file *p; + struct pack_backend *backend; + unsigned int i; + + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(cb); + + backend = (struct pack_backend *)_backend; + + /* Make sure we know about the packfiles */ + if ((error = pack_backend__refresh(_backend)) != 0) + return error; + + if (backend->midx && (error = git_midx_foreach_entry(backend->midx, cb, data)) != 0) + return error; + git_vector_foreach(&backend->packs, i, p) { + if ((error = git_pack_foreach_entry(p, cb, data)) != 0) + return error; + } + + return 0; +} + +static int pack_backend__writepack_append(struct git_odb_writepack *_writepack, const void *data, size_t size, git_indexer_progress *stats) +{ + struct pack_writepack *writepack = (struct pack_writepack *)_writepack; + + GIT_ASSERT_ARG(writepack); + + return git_indexer_append(writepack->indexer, data, size, stats); +} + +static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, git_indexer_progress *stats) +{ + struct pack_writepack *writepack = (struct pack_writepack *)_writepack; + + GIT_ASSERT_ARG(writepack); + + return git_indexer_commit(writepack->indexer, stats); +} + +static void pack_backend__writepack_free(struct git_odb_writepack *_writepack) +{ + struct pack_writepack *writepack; + + if (!_writepack) + return; + + writepack = (struct pack_writepack *)_writepack; + + git_indexer_free(writepack->indexer); + git__free(writepack); +} + +static int pack_backend__writepack(struct git_odb_writepack **out, + git_odb_backend *_backend, + git_odb *odb, + git_indexer_progress_cb progress_cb, + void *progress_payload) +{ + git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; + struct pack_backend *backend; + struct pack_writepack *writepack; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(_backend); + + *out = NULL; + + opts.progress_cb = progress_cb; + opts.progress_cb_payload = progress_payload; + + backend = (struct pack_backend *)_backend; + + writepack = git__calloc(1, sizeof(struct pack_writepack)); + GIT_ERROR_CHECK_ALLOC(writepack); + + if (git_indexer_new(&writepack->indexer, + backend->pack_folder, 0, odb, &opts) < 0) { + git__free(writepack); + return -1; + } + + writepack->parent.backend = _backend; + writepack->parent.append = pack_backend__writepack_append; + writepack->parent.commit = pack_backend__writepack_commit; + writepack->parent.free = pack_backend__writepack_free; + + *out = (git_odb_writepack *)writepack; + + return 0; +} + +static int get_idx_path( + git_str *idx_path, + struct pack_backend *backend, + struct git_pack_file *p) +{ + size_t path_len; + int error; + + error = git_fs_path_prettify(idx_path, p->pack_name, backend->pack_folder); + if (error < 0) + return error; + path_len = git_str_len(idx_path); + if (path_len <= strlen(".pack") || git__suffixcmp(git_str_cstr(idx_path), ".pack") != 0) + return git_odb__error_notfound("packfile does not end in .pack", NULL, 0); + path_len -= strlen(".pack"); + error = git_str_splice(idx_path, path_len, strlen(".pack"), ".idx", strlen(".idx")); + if (error < 0) + return error; + + return 0; +} + +static int pack_backend__writemidx(git_odb_backend *_backend) +{ + struct pack_backend *backend; + git_midx_writer *w = NULL; + struct git_pack_file *p; + size_t i; + int error = 0; + + GIT_ASSERT_ARG(_backend); + + backend = (struct pack_backend *)_backend; + + error = git_midx_writer_new(&w, backend->pack_folder); + if (error < 0) + return error; + + git_vector_foreach(&backend->midx_packs, i, p) { + git_str idx_path = GIT_STR_INIT; + error = get_idx_path(&idx_path, backend, p); + if (error < 0) + goto cleanup; + error = git_midx_writer_add(w, git_str_cstr(&idx_path)); + git_str_dispose(&idx_path); + if (error < 0) + goto cleanup; + } + git_vector_foreach(&backend->packs, i, p) { + git_str idx_path = GIT_STR_INIT; + error = get_idx_path(&idx_path, backend, p); + if (error < 0) + goto cleanup; + error = git_midx_writer_add(w, git_str_cstr(&idx_path)); + git_str_dispose(&idx_path); + if (error < 0) + goto cleanup; + } + + /* + * Invalidate the previous midx before writing the new one. + */ + error = remove_multi_pack_index(backend); + if (error < 0) + goto cleanup; + error = git_midx_writer_commit(w); + if (error < 0) + goto cleanup; + error = refresh_multi_pack_index(backend); + +cleanup: + git_midx_writer_free(w); + return error; +} + +static void pack_backend__free(git_odb_backend *_backend) +{ + struct pack_backend *backend; + struct git_pack_file *p; + size_t i; + + if (!_backend) + return; + + backend = (struct pack_backend *)_backend; + + git_vector_foreach(&backend->midx_packs, i, p) + git_mwindow_put_pack(p); + git_vector_foreach(&backend->packs, i, p) + git_mwindow_put_pack(p); + + git_midx_free(backend->midx); + git_vector_free(&backend->midx_packs); + git_vector_free(&backend->packs); + git__free(backend->pack_folder); + git__free(backend); +} + +static int pack_backend__alloc(struct pack_backend **out, size_t initial_size) +{ + struct pack_backend *backend = git__calloc(1, sizeof(struct pack_backend)); + GIT_ERROR_CHECK_ALLOC(backend); + + if (git_vector_init(&backend->midx_packs, 0, NULL) < 0) { + git__free(backend); + return -1; + } + if (git_vector_init(&backend->packs, initial_size, packfile_sort__cb) < 0) { + git_vector_free(&backend->midx_packs); + git__free(backend); + return -1; + } + + backend->parent.version = GIT_ODB_BACKEND_VERSION; + + backend->parent.read = &pack_backend__read; + backend->parent.read_prefix = &pack_backend__read_prefix; + backend->parent.read_header = &pack_backend__read_header; + backend->parent.exists = &pack_backend__exists; + backend->parent.exists_prefix = &pack_backend__exists_prefix; + backend->parent.refresh = &pack_backend__refresh; + backend->parent.foreach = &pack_backend__foreach; + backend->parent.writepack = &pack_backend__writepack; + backend->parent.writemidx = &pack_backend__writemidx; + backend->parent.freshen = &pack_backend__freshen; + backend->parent.free = &pack_backend__free; + + *out = backend; + return 0; +} + +int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx) +{ + struct pack_backend *backend = NULL; + struct git_pack_file *packfile = NULL; + + if (pack_backend__alloc(&backend, 1) < 0) + return -1; + + if (git_mwindow_get_pack(&packfile, idx) < 0 || + git_vector_insert(&backend->packs, packfile) < 0) + { + pack_backend__free((git_odb_backend *)backend); + return -1; + } + + *backend_out = (git_odb_backend *)backend; + return 0; +} + +int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) +{ + int error = 0; + struct pack_backend *backend = NULL; + git_str path = GIT_STR_INIT; + + if (pack_backend__alloc(&backend, 8) < 0) + return -1; + + if (!(error = git_str_joinpath(&path, objects_dir, "pack")) && + git_fs_path_isdir(git_str_cstr(&path))) + { + backend->pack_folder = git_str_detach(&path); + error = pack_backend__refresh((git_odb_backend *)backend); + } + + if (error < 0) { + pack_backend__free((git_odb_backend *)backend); + backend = NULL; + } + + *backend_out = (git_odb_backend *)backend; + + git_str_dispose(&path); + + return error; +} diff --git a/src/libgit2/offmap.c b/src/libgit2/offmap.c new file mode 100644 index 000000000..be9eb66d8 --- /dev/null +++ b/src/libgit2/offmap.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "offmap.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kreallocarray git__reallocarray +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(off, off64_t, void *) + +__KHASH_IMPL(off, static kh_inline, off64_t, void *, 1, kh_int64_hash_func, kh_int64_hash_equal) + + +int git_offmap_new(git_offmap **out) +{ + *out = kh_init(off); + GIT_ERROR_CHECK_ALLOC(*out); + + return 0; +} + +void git_offmap_free(git_offmap *map) +{ + kh_destroy(off, map); +} + +void git_offmap_clear(git_offmap *map) +{ + kh_clear(off, map); +} + +size_t git_offmap_size(git_offmap *map) +{ + return kh_size(map); +} + +void *git_offmap_get(git_offmap *map, const off64_t key) +{ + size_t idx = kh_get(off, map, key); + if (idx == kh_end(map) || !kh_exist(map, idx)) + return NULL; + return kh_val(map, idx); +} + +int git_offmap_set(git_offmap *map, const off64_t key, void *value) +{ + size_t idx; + int rval; + + idx = kh_put(off, map, key, &rval); + if (rval < 0) + return -1; + + if (rval == 0) + kh_key(map, idx) = key; + + kh_val(map, idx) = value; + + return 0; +} + +int git_offmap_delete(git_offmap *map, const off64_t key) +{ + khiter_t idx = kh_get(off, map, key); + if (idx == kh_end(map)) + return GIT_ENOTFOUND; + kh_del(off, map, idx); + return 0; +} + +int git_offmap_exists(git_offmap *map, const off64_t key) +{ + return kh_get(off, map, key) != kh_end(map); +} + +int git_offmap_iterate(void **value, git_offmap *map, size_t *iter, off64_t *key) +{ + size_t i = *iter; + + while (i < map->n_buckets && !kh_exist(map, i)) + i++; + + if (i >= map->n_buckets) + return GIT_ITEROVER; + + if (key) + *key = kh_key(map, i); + if (value) + *value = kh_value(map, i); + *iter = ++i; + + return 0; +} diff --git a/src/libgit2/offmap.h b/src/libgit2/offmap.h new file mode 100644 index 000000000..81c459b01 --- /dev/null +++ b/src/libgit2/offmap.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_offmap_h__ +#define INCLUDE_offmap_h__ + +#include "common.h" + +#include "git2/types.h" + +/** A map with `off64_t`s as key. */ +typedef struct kh_off_s git_offmap; + +/** + * Allocate a new `off64_t` map. + * + * @param out Pointer to the map that shall be allocated. + * @return 0 on success, an error code if allocation has failed. + */ +int git_offmap_new(git_offmap **out); + +/** + * Free memory associated with the map. + * + * Note that this function will _not_ free values added to this + * map. + * + * @param map Pointer to the map that is to be free'd. May be + * `NULL`. + */ +void git_offmap_free(git_offmap *map); + +/** + * Clear all entries from the map. + * + * This function will remove all entries from the associated map. + * Memory associated with it will not be released, though. + * + * @param map Pointer to the map that shall be cleared. May be + * `NULL`. + */ +void git_offmap_clear(git_offmap *map); + +/** + * Return the number of elements in the map. + * + * @parameter map map containing the elements + * @return number of elements in the map + */ +size_t git_offmap_size(git_offmap *map); + +/** + * Return value associated with the given key. + * + * @param map map to search key in + * @param key key to search for + * @return value associated with the given key or NULL if the key was not found + */ +void *git_offmap_get(git_offmap *map, const off64_t key); + +/** + * Set the entry for key to value. + * + * If the map has no corresponding entry for the given key, a new + * entry will be created with the given value. If an entry exists + * already, its value will be updated to match the given value. + * + * @param map map to create new entry in + * @param key key to set + * @param value value to associate the key with; may be NULL + * @return zero if the key was successfully set, a negative error + * code otherwise + */ +int git_offmap_set(git_offmap *map, const off64_t key, void *value); + +/** + * Delete an entry from the map. + * + * Delete the given key and its value from the map. If no such + * key exists, this will do nothing. + * + * @param map map to delete key in + * @param key key to delete + * @return `0` if the key has been deleted, GIT_ENOTFOUND if no + * such key was found, a negative code in case of an + * error + */ +int git_offmap_delete(git_offmap *map, const off64_t key); + +/** + * Check whether a key exists in the given map. + * + * @param map map to query for the key + * @param key key to search for + * @return 0 if the key has not been found, 1 otherwise + */ +int git_offmap_exists(git_offmap *map, const off64_t key); + +/** + * Iterate over entries of the map. + * + * This functions allows to iterate over all key-value entries of + * the map. The current position is stored in the `iter` variable + * and should be initialized to `0` before the first call to this + * function. + * + * @param map map to iterate over + * @param value pointer to the variable where to store the current + * value. May be NULL. + * @param iter iterator storing the current position. Initialize + * with zero previous to the first call. + * @param key pointer to the variable where to store the current + * key. May be NULL. + * @return `0` if the next entry was correctly retrieved. + * GIT_ITEROVER if no entries are left. A negative error + * code otherwise. + */ +int git_offmap_iterate(void **value, git_offmap *map, size_t *iter, off64_t *key); + +#define git_offmap_foreach(h, kvar, vvar, code) { size_t __i = 0; \ + while (git_offmap_iterate((void **) &(vvar), h, &__i, &(kvar)) == 0) { \ + code; \ + } } + +#define git_offmap_foreach_value(h, vvar, code) { size_t __i = 0; \ + while (git_offmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ + code; \ + } } + +#endif diff --git a/src/libgit2/oid.c b/src/libgit2/oid.c new file mode 100644 index 000000000..19061e899 --- /dev/null +++ b/src/libgit2/oid.c @@ -0,0 +1,463 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "oid.h" + +#include "git2/oid.h" +#include "repository.h" +#include "threadstate.h" +#include +#include + +const git_oid git_oid__empty_blob_sha1 = + {{ 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, 0xd6, 0x43, 0x4b, 0x8b, + 0x29, 0xae, 0x77, 0x5a, 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91 }}; +const git_oid git_oid__empty_tree_sha1 = + {{ 0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, + 0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04 }}; + +static char to_hex[] = "0123456789abcdef"; + +static int oid_error_invalid(const char *msg) +{ + git_error_set(GIT_ERROR_INVALID, "unable to parse OID - %s", msg); + return -1; +} + +int git_oid_fromstrn(git_oid *out, const char *str, size_t length) +{ + size_t p; + int v; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(str); + + if (!length) + return oid_error_invalid("too short"); + + if (length > GIT_OID_HEXSZ) + return oid_error_invalid("too long"); + + memset(out->id, 0, GIT_OID_RAWSZ); + + for (p = 0; p < length; p++) { + v = git__fromhex(str[p]); + if (v < 0) + return oid_error_invalid("contains invalid characters"); + + out->id[p / 2] |= (unsigned char)(v << (p % 2 ? 0 : 4)); + } + + return 0; +} + +int git_oid_fromstrp(git_oid *out, const char *str) +{ + return git_oid_fromstrn(out, str, strlen(str)); +} + +int git_oid_fromstr(git_oid *out, const char *str) +{ + return git_oid_fromstrn(out, str, GIT_OID_HEXSZ); +} + +GIT_INLINE(char) *fmt_one(char *str, unsigned int val) +{ + *str++ = to_hex[val >> 4]; + *str++ = to_hex[val & 0xf]; + return str; +} + +int git_oid_nfmt(char *str, size_t n, const git_oid *oid) +{ + size_t i, max_i; + + if (!oid) { + memset(str, 0, n); + return 0; + } + if (n > GIT_OID_HEXSZ) { + memset(&str[GIT_OID_HEXSZ], 0, n - GIT_OID_HEXSZ); + n = GIT_OID_HEXSZ; + } + + max_i = n / 2; + + for (i = 0; i < max_i; i++) + str = fmt_one(str, oid->id[i]); + + if (n & 1) + *str++ = to_hex[oid->id[i] >> 4]; + + return 0; +} + +int git_oid_fmt(char *str, const git_oid *oid) +{ + return git_oid_nfmt(str, GIT_OID_HEXSZ, oid); +} + +int git_oid_pathfmt(char *str, const git_oid *oid) +{ + size_t i; + + str = fmt_one(str, oid->id[0]); + *str++ = '/'; + for (i = 1; i < sizeof(oid->id); i++) + str = fmt_one(str, oid->id[i]); + + return 0; +} + +char *git_oid_tostr_s(const git_oid *oid) +{ + char *str = GIT_THREADSTATE->oid_fmt; + git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid); + return str; +} + +char *git_oid_allocfmt(const git_oid *oid) +{ + char *str = git__malloc(GIT_OID_HEXSZ + 1); + if (!str) + return NULL; + git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid); + return str; +} + +char *git_oid_tostr(char *out, size_t n, const git_oid *oid) +{ + if (!out || n == 0) + return ""; + + if (n > GIT_OID_HEXSZ + 1) + n = GIT_OID_HEXSZ + 1; + + git_oid_nfmt(out, n - 1, oid); /* allow room for terminating NUL */ + out[n - 1] = '\0'; + + return out; +} + +int git_oid__parse( + git_oid *oid, const char **buffer_out, + const char *buffer_end, const char *header) +{ + const size_t sha_len = GIT_OID_HEXSZ; + const size_t header_len = strlen(header); + + const char *buffer = *buffer_out; + + if (buffer + (header_len + sha_len + 1) > buffer_end) + return -1; + + if (memcmp(buffer, header, header_len) != 0) + return -1; + + if (buffer[header_len + sha_len] != '\n') + return -1; + + if (git_oid_fromstr(oid, buffer + header_len) < 0) + return -1; + + *buffer_out = buffer + (header_len + sha_len + 1); + + return 0; +} + +void git_oid__writebuf(git_str *buf, const char *header, const git_oid *oid) +{ + char hex_oid[GIT_OID_HEXSZ]; + + git_oid_fmt(hex_oid, oid); + git_str_puts(buf, header); + git_str_put(buf, hex_oid, GIT_OID_HEXSZ); + git_str_putc(buf, '\n'); +} + +int git_oid_fromraw(git_oid *out, const unsigned char *raw) +{ + memcpy(out->id, raw, sizeof(out->id)); + return 0; +} + +int git_oid_cpy(git_oid *out, const git_oid *src) +{ + memcpy(out->id, src->id, sizeof(out->id)); + return 0; +} + +int git_oid_cmp(const git_oid *a, const git_oid *b) +{ + return git_oid__cmp(a, b); +} + +int git_oid_equal(const git_oid *a, const git_oid *b) +{ + return (git_oid__cmp(a, b) == 0); +} + +int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, size_t len) +{ + const unsigned char *a = oid_a->id; + const unsigned char *b = oid_b->id; + + if (len > GIT_OID_HEXSZ) + len = GIT_OID_HEXSZ; + + while (len > 1) { + if (*a != *b) + return 1; + a++; + b++; + len -= 2; + }; + + if (len) + if ((*a ^ *b) & 0xf0) + return 1; + + return 0; +} + +int git_oid_strcmp(const git_oid *oid_a, const char *str) +{ + const unsigned char *a; + unsigned char strval; + int hexval; + + for (a = oid_a->id; *str && (a - oid_a->id) < GIT_OID_RAWSZ; ++a) { + if ((hexval = git__fromhex(*str++)) < 0) + return -1; + strval = (unsigned char)(hexval << 4); + if (*str) { + if ((hexval = git__fromhex(*str++)) < 0) + return -1; + strval |= hexval; + } + if (*a != strval) + return (*a - strval); + } + + return 0; +} + +int git_oid_streq(const git_oid *oid_a, const char *str) +{ + return git_oid_strcmp(oid_a, str) == 0 ? 0 : -1; +} + +int git_oid_is_zero(const git_oid *oid_a) +{ + const unsigned char *a = oid_a->id; + unsigned int i; + for (i = 0; i < GIT_OID_RAWSZ; ++i, ++a) + if (*a != 0) + return 0; + return 1; +} + +#ifndef GIT_DEPRECATE_HARD +int git_oid_iszero(const git_oid *oid_a) +{ + return git_oid_is_zero(oid_a); +} +#endif + +typedef short node_index; + +typedef union { + const char *tail; + node_index children[16]; +} trie_node; + +struct git_oid_shorten { + trie_node *nodes; + size_t node_count, size; + int min_length, full; +}; + +static int resize_trie(git_oid_shorten *self, size_t new_size) +{ + self->nodes = git__reallocarray(self->nodes, new_size, sizeof(trie_node)); + GIT_ERROR_CHECK_ALLOC(self->nodes); + + if (new_size > self->size) { + memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(trie_node)); + } + + self->size = new_size; + return 0; +} + +static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, const char *oid) +{ + trie_node *node, *leaf; + node_index idx_leaf; + + if (os->node_count >= os->size) { + if (resize_trie(os, os->size * 2) < 0) + return NULL; + } + + idx_leaf = (node_index)os->node_count++; + + if (os->node_count == SHRT_MAX) { + os->full = 1; + return NULL; + } + + node = &os->nodes[idx]; + node->children[push_at] = -idx_leaf; + + leaf = &os->nodes[idx_leaf]; + leaf->tail = oid; + + return node; +} + +git_oid_shorten *git_oid_shorten_new(size_t min_length) +{ + git_oid_shorten *os; + + GIT_ASSERT_ARG_WITH_RETVAL((size_t)((int)min_length) == min_length, NULL); + + os = git__calloc(1, sizeof(git_oid_shorten)); + if (os == NULL) + return NULL; + + if (resize_trie(os, 16) < 0) { + git__free(os); + return NULL; + } + + os->node_count = 1; + os->min_length = (int)min_length; + + return os; +} + +void git_oid_shorten_free(git_oid_shorten *os) +{ + if (os == NULL) + return; + + git__free(os->nodes); + git__free(os); +} + + +/* + * What wizardry is this? + * + * This is just a memory-optimized trie: basically a very fancy + * 16-ary tree, which is used to store the prefixes of the OID + * strings. + * + * Read more: http://en.wikipedia.org/wiki/Trie + * + * Magic that happens in this method: + * + * - Each node in the trie is an union, so it can work both as + * a normal node, or as a leaf. + * + * - Each normal node points to 16 children (one for each possible + * character in the oid). This is *not* stored in an array of + * pointers, because in a 64-bit arch this would be sucking + * 16*sizeof(void*) = 128 bytes of memory per node, which is + * insane. What we do is store Node Indexes, and use these indexes + * to look up each node in the om->index array. These indexes are + * signed shorts, so this limits the amount of unique OIDs that + * fit in the structure to about 20000 (assuming a more or less uniform + * distribution). + * + * - All the nodes in om->index array are stored contiguously in + * memory, and each of them is 32 bytes, so we fit 2x nodes per + * cache line. Convenient for speed. + * + * - To differentiate the leafs from the normal nodes, we store all + * the indexes towards a leaf as a negative index (indexes to normal + * nodes are positives). When we find that one of the children for + * a node has a negative value, that means it's going to be a leaf. + * This reduces the amount of indexes we have by two, but also reduces + * the size of each node by 1-4 bytes (the amount we would need to + * add a `is_leaf` field): this is good because it allows the nodes + * to fit cleanly in cache lines. + * + * - Once we reach an empty children, instead of continuing to insert + * new nodes for each remaining character of the OID, we store a pointer + * to the tail in the leaf; if the leaf is reached again, we turn it + * into a normal node and use the tail to create a new leaf. + * + * This is a pretty good balance between performance and memory usage. + */ +int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) +{ + int i; + bool is_leaf; + node_index idx; + + if (os->full) { + git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full"); + return -1; + } + + if (text_oid == NULL) + return os->min_length; + + idx = 0; + is_leaf = false; + + for (i = 0; i < GIT_OID_HEXSZ; ++i) { + int c = git__fromhex(text_oid[i]); + trie_node *node; + + if (c == -1) { + git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - invalid hex value"); + return -1; + } + + node = &os->nodes[idx]; + + if (is_leaf) { + const char *tail; + + tail = node->tail; + node->tail = NULL; + + node = push_leaf(os, idx, git__fromhex(tail[0]), &tail[1]); + if (node == NULL) { + if (os->full) + git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full"); + return -1; + } + } + + if (node->children[c] == 0) { + if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) { + if (os->full) + git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full"); + return -1; + } + break; + } + + idx = node->children[c]; + is_leaf = false; + + if (idx < 0) { + node->children[c] = idx = -idx; + is_leaf = true; + } + } + + if (++i > os->min_length) + os->min_length = i; + + return os->min_length; +} + diff --git a/src/libgit2/oid.h b/src/libgit2/oid.h new file mode 100644 index 000000000..5baec33e5 --- /dev/null +++ b/src/libgit2/oid.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_oid_h__ +#define INCLUDE_oid_h__ + +#include "common.h" + +#include "git2/oid.h" + +extern const git_oid git_oid__empty_blob_sha1; +extern const git_oid git_oid__empty_tree_sha1; + +/** + * Format a git_oid into a newly allocated c-string. + * + * The c-string is owned by the caller and needs to be manually freed. + * + * @param id the oid structure to format + * @return the c-string; NULL if memory is exhausted. Caller must + * deallocate the string with git__free(). + */ +char *git_oid_allocfmt(const git_oid *id); + +GIT_INLINE(int) git_oid__hashcmp(const unsigned char *sha1, const unsigned char *sha2) +{ + return memcmp(sha1, sha2, GIT_OID_RAWSZ); +} + +/* + * Compare two oid structures. + * + * @param a first oid structure. + * @param b second oid structure. + * @return <0, 0, >0 if a < b, a == b, a > b. + */ +GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b) +{ + return git_oid__hashcmp(a->id, b->id); +} + +GIT_INLINE(void) git_oid__cpy_prefix( + git_oid *out, const git_oid *id, size_t len) +{ + memcpy(&out->id, id->id, (len + 1) / 2); + + if (len & 1) + out->id[len / 2] &= 0xF0; +} + +GIT_INLINE(bool) git_oid__is_hexstr(const char *str) +{ + size_t i; + + for (i = 0; str[i] != '\0'; i++) { + if (git__fromhex(str[i]) < 0) + return false; + } + + return (i == GIT_OID_HEXSZ); +} + +#endif diff --git a/src/libgit2/oidarray.c b/src/libgit2/oidarray.c new file mode 100644 index 000000000..583017c4e --- /dev/null +++ b/src/libgit2/oidarray.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "oidarray.h" + +#include "git2/oidarray.h" +#include "array.h" + +void git_oidarray_dispose(git_oidarray *arr) +{ + git__free(arr->ids); +} + +void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array) +{ + arr->count = array->size; + arr->ids = array->ptr; +} + +void git_oidarray__reverse(git_oidarray *arr) +{ + size_t i; + git_oid tmp; + + for (i = 0; i < arr->count / 2; i++) { + git_oid_cpy(&tmp, &arr->ids[i]); + git_oid_cpy(&arr->ids[i], &arr->ids[(arr->count-1)-i]); + git_oid_cpy(&arr->ids[(arr->count-1)-i], &tmp); + } +} + +#ifndef GIT_DEPRECATE_HARD + +void git_oidarray_free(git_oidarray *arr) +{ + git_oidarray_dispose(arr); +} + +#endif diff --git a/src/libgit2/oidarray.h b/src/libgit2/oidarray.h new file mode 100644 index 000000000..eed3a1091 --- /dev/null +++ b/src/libgit2/oidarray.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_oidarray_h__ +#define INCLUDE_oidarray_h__ + +#include "common.h" + +#include "git2/oidarray.h" +#include "array.h" + +typedef git_array_t(git_oid) git_array_oid_t; + +extern void git_oidarray__reverse(git_oidarray *arr); +extern void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array); + +#endif diff --git a/src/libgit2/oidmap.c b/src/libgit2/oidmap.c new file mode 100644 index 000000000..0ae8bf33e --- /dev/null +++ b/src/libgit2/oidmap.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "oidmap.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kreallocarray git__reallocarray +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(oid, const git_oid *, void *) + +GIT_INLINE(khint_t) git_oidmap_hash(const git_oid *oid) +{ + khint_t h; + memcpy(&h, oid, sizeof(khint_t)); + return h; +} + +__KHASH_IMPL(oid, static kh_inline, const git_oid *, void *, 1, git_oidmap_hash, git_oid_equal) + +int git_oidmap_new(git_oidmap **out) +{ + *out = kh_init(oid); + GIT_ERROR_CHECK_ALLOC(*out); + + return 0; +} + +void git_oidmap_free(git_oidmap *map) +{ + kh_destroy(oid, map); +} + +void git_oidmap_clear(git_oidmap *map) +{ + kh_clear(oid, map); +} + +size_t git_oidmap_size(git_oidmap *map) +{ + return kh_size(map); +} + +void *git_oidmap_get(git_oidmap *map, const git_oid *key) +{ + size_t idx = kh_get(oid, map, key); + if (idx == kh_end(map) || !kh_exist(map, idx)) + return NULL; + return kh_val(map, idx); +} + +int git_oidmap_set(git_oidmap *map, const git_oid *key, void *value) +{ + size_t idx; + int rval; + + idx = kh_put(oid, map, key, &rval); + if (rval < 0) + return -1; + + if (rval == 0) + kh_key(map, idx) = key; + + kh_val(map, idx) = value; + + return 0; +} + +int git_oidmap_delete(git_oidmap *map, const git_oid *key) +{ + khiter_t idx = kh_get(oid, map, key); + if (idx == kh_end(map)) + return GIT_ENOTFOUND; + kh_del(oid, map, idx); + return 0; +} + +int git_oidmap_exists(git_oidmap *map, const git_oid *key) +{ + return kh_get(oid, map, key) != kh_end(map); +} + +int git_oidmap_iterate(void **value, git_oidmap *map, size_t *iter, const git_oid **key) +{ + size_t i = *iter; + + while (i < map->n_buckets && !kh_exist(map, i)) + i++; + + if (i >= map->n_buckets) + return GIT_ITEROVER; + + if (key) + *key = kh_key(map, i); + if (value) + *value = kh_value(map, i); + *iter = ++i; + + return 0; +} diff --git a/src/libgit2/oidmap.h b/src/libgit2/oidmap.h new file mode 100644 index 000000000..b748f727c --- /dev/null +++ b/src/libgit2/oidmap.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_oidmap_h__ +#define INCLUDE_oidmap_h__ + +#include "common.h" + +#include "git2/oid.h" + +/** A map with `git_oid`s as key. */ +typedef struct kh_oid_s git_oidmap; + +/** + * Allocate a new OID map. + * + * @param out Pointer to the map that shall be allocated. + * @return 0 on success, an error code if allocation has failed. + */ +int git_oidmap_new(git_oidmap **out); + +/** + * Free memory associated with the map. + * + * Note that this function will _not_ free values added to this + * map. + * + * @param map Pointer to the map that is to be free'd. May be + * `NULL`. + */ +void git_oidmap_free(git_oidmap *map); + +/** + * Clear all entries from the map. + * + * This function will remove all entries from the associated map. + * Memory associated with it will not be released, though. + * + * @param map Pointer to the map that shall be cleared. May be + * `NULL`. + */ +void git_oidmap_clear(git_oidmap *map); + +/** + * Return the number of elements in the map. + * + * @parameter map map containing the elements + * @return number of elements in the map + */ +size_t git_oidmap_size(git_oidmap *map); + +/** + * Return value associated with the given key. + * + * @param map map to search key in + * @param key key to search for + * @return value associated with the given key or NULL if the key was not found + */ +void *git_oidmap_get(git_oidmap *map, const git_oid *key); + +/** + * Set the entry for key to value. + * + * If the map has no corresponding entry for the given key, a new + * entry will be created with the given value. If an entry exists + * already, its value will be updated to match the given value. + * + * @param map map to create new entry in + * @param key key to set + * @param value value to associate the key with; may be NULL + * @return zero if the key was successfully set, a negative error + * code otherwise + */ +int git_oidmap_set(git_oidmap *map, const git_oid *key, void *value); + +/** + * Delete an entry from the map. + * + * Delete the given key and its value from the map. If no such + * key exists, this will do nothing. + * + * @param map map to delete key in + * @param key key to delete + * @return `0` if the key has been deleted, GIT_ENOTFOUND if no + * such key was found, a negative code in case of an + * error + */ +int git_oidmap_delete(git_oidmap *map, const git_oid *key); + +/** + * Check whether a key exists in the given map. + * + * @param map map to query for the key + * @param key key to search for + * @return 0 if the key has not been found, 1 otherwise + */ +int git_oidmap_exists(git_oidmap *map, const git_oid *key); + +/** + * Iterate over entries of the map. + * + * This functions allows to iterate over all key-value entries of + * the map. The current position is stored in the `iter` variable + * and should be initialized to `0` before the first call to this + * function. + * + * @param map map to iterate over + * @param value pointer to the variable where to store the current + * value. May be NULL. + * @param iter iterator storing the current position. Initialize + * with zero previous to the first call. + * @param key pointer to the variable where to store the current + * key. May be NULL. + * @return `0` if the next entry was correctly retrieved. + * GIT_ITEROVER if no entries are left. A negative error + * code otherwise. + */ +int git_oidmap_iterate(void **value, git_oidmap *map, size_t *iter, const git_oid **key); + +#define git_oidmap_foreach_value(h, vvar, code) { size_t __i = 0; \ + while (git_oidmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ + code; \ + } } + +#endif diff --git a/src/libgit2/pack-objects.c b/src/libgit2/pack-objects.c new file mode 100644 index 000000000..1aa6731b3 --- /dev/null +++ b/src/libgit2/pack-objects.c @@ -0,0 +1,1821 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "pack-objects.h" + +#include "buf.h" +#include "zstream.h" +#include "delta.h" +#include "iterator.h" +#include "netops.h" +#include "pack.h" +#include "thread.h" +#include "tree.h" +#include "util.h" +#include "revwalk.h" +#include "commit_list.h" + +#include "git2/pack.h" +#include "git2/commit.h" +#include "git2/tag.h" +#include "git2/indexer.h" +#include "git2/config.h" + +struct unpacked { + git_pobject *object; + void *data; + struct git_delta_index *index; + size_t depth; +}; + +struct tree_walk_context { + git_packbuilder *pb; + git_str buf; +}; + +struct pack_write_context { + git_indexer *indexer; + git_indexer_progress *stats; +}; + +struct walk_object { + git_oid id; + unsigned int uninteresting:1, + seen:1; +}; + +#ifdef GIT_THREADS +# define GIT_PACKBUILDER__MUTEX_OP(pb, mtx, op) git_mutex_##op(&(pb)->mtx) +#else +# define GIT_PACKBUILDER__MUTEX_OP(pb, mtx, op) git__noop() +#endif + +#define git_packbuilder__cache_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, lock) +#define git_packbuilder__cache_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, unlock) +#define git_packbuilder__progress_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, lock) +#define git_packbuilder__progress_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, unlock) + +/* The minimal interval between progress updates (in seconds). */ +#define MIN_PROGRESS_UPDATE_INTERVAL 0.5 + +/* Size of the buffer to feed to zlib */ +#define COMPRESS_BUFLEN (1024 * 1024) + +static unsigned name_hash(const char *name) +{ + unsigned c, hash = 0; + + if (!name) + return 0; + + /* + * This effectively just creates a sortable number from the + * last sixteen non-whitespace characters. Last characters + * count "most", so things that end in ".c" sort together. + */ + while ((c = *name++) != 0) { + if (git__isspace(c)) + continue; + hash = (hash >> 2) + (c << 24); + } + return hash; +} + +static int packbuilder_config(git_packbuilder *pb) +{ + git_config *config; + int ret = 0; + int64_t val; + + if ((ret = git_repository_config_snapshot(&config, pb->repo)) < 0) + return ret; + +#define config_get(KEY,DST,DFLT) do { \ + ret = git_config_get_int64(&val, config, KEY); \ + if (!ret) { \ + if (!git__is_sizet(val)) { \ + git_error_set(GIT_ERROR_CONFIG, \ + "configuration value '%s' is too large", KEY); \ + ret = -1; \ + goto out; \ + } \ + (DST) = (size_t)val; \ + } else if (ret == GIT_ENOTFOUND) { \ + (DST) = (DFLT); \ + ret = 0; \ + } else if (ret < 0) goto out; } while (0) + + config_get("pack.deltaCacheSize", pb->max_delta_cache_size, + GIT_PACK_DELTA_CACHE_SIZE); + config_get("pack.deltaCacheLimit", pb->cache_max_small_delta_size, + GIT_PACK_DELTA_CACHE_LIMIT); + config_get("pack.deltaCacheSize", pb->big_file_threshold, + GIT_PACK_BIG_FILE_THRESHOLD); + config_get("pack.windowMemory", pb->window_memory_limit, 0); + +#undef config_get + +out: + git_config_free(config); + + return ret; +} + +int git_packbuilder_new(git_packbuilder **out, git_repository *repo) +{ + git_packbuilder *pb; + + *out = NULL; + + pb = git__calloc(1, sizeof(*pb)); + GIT_ERROR_CHECK_ALLOC(pb); + + if (git_oidmap_new(&pb->object_ix) < 0 || + git_oidmap_new(&pb->walk_objects) < 0 || + git_pool_init(&pb->object_pool, sizeof(struct walk_object)) < 0) + goto on_error; + + pb->repo = repo; + pb->nr_threads = 1; /* do not spawn any thread by default */ + + if (git_hash_ctx_init(&pb->ctx, GIT_HASH_ALGORITHM_SHA1) < 0 || + git_zstream_init(&pb->zstream, GIT_ZSTREAM_DEFLATE) < 0 || + git_repository_odb(&pb->odb, repo) < 0 || + packbuilder_config(pb) < 0) + goto on_error; + +#ifdef GIT_THREADS + + if (git_mutex_init(&pb->cache_mutex) || + git_mutex_init(&pb->progress_mutex) || + git_cond_init(&pb->progress_cond)) + { + git_error_set(GIT_ERROR_OS, "failed to initialize packbuilder mutex"); + goto on_error; + } + +#endif + + *out = pb; + return 0; + +on_error: + git_packbuilder_free(pb); + return -1; +} + +unsigned int git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n) +{ + GIT_ASSERT_ARG(pb); + +#ifdef GIT_THREADS + pb->nr_threads = n; +#else + GIT_UNUSED(n); + GIT_ASSERT(pb->nr_threads == 1); +#endif + + return pb->nr_threads; +} + +static int rehash(git_packbuilder *pb) +{ + git_pobject *po; + size_t i; + + git_oidmap_clear(pb->object_ix); + + for (i = 0, po = pb->object_list; i < pb->nr_objects; i++, po++) { + if (git_oidmap_set(pb->object_ix, &po->id, po) < 0) + return -1; + } + + return 0; +} + +int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid, + const char *name) +{ + git_pobject *po; + size_t newsize; + int ret; + + GIT_ASSERT_ARG(pb); + GIT_ASSERT_ARG(oid); + + /* If the object already exists in the hash table, then we don't + * have any work to do */ + if (git_oidmap_exists(pb->object_ix, oid)) + return 0; + + if (pb->nr_objects >= pb->nr_alloc) { + GIT_ERROR_CHECK_ALLOC_ADD(&newsize, pb->nr_alloc, 1024); + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newsize, newsize / 2, 3); + + if (!git__is_uint32(newsize)) { + git_error_set(GIT_ERROR_NOMEMORY, "packfile too large to fit in memory."); + return -1; + } + + pb->nr_alloc = newsize; + + pb->object_list = git__reallocarray(pb->object_list, + pb->nr_alloc, sizeof(*po)); + GIT_ERROR_CHECK_ALLOC(pb->object_list); + + if (rehash(pb) < 0) + return -1; + } + + po = pb->object_list + pb->nr_objects; + memset(po, 0x0, sizeof(*po)); + + if ((ret = git_odb_read_header(&po->size, &po->type, pb->odb, oid)) < 0) + return ret; + + pb->nr_objects++; + git_oid_cpy(&po->id, oid); + po->hash = name_hash(name); + + if (git_oidmap_set(pb->object_ix, &po->id, po) < 0) { + git_error_set_oom(); + return -1; + } + + pb->done = false; + + if (pb->progress_cb) { + double current_time = git__timer(); + double elapsed = current_time - pb->last_progress_report_time; + + if (elapsed < 0 || elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) { + pb->last_progress_report_time = current_time; + + ret = pb->progress_cb( + GIT_PACKBUILDER_ADDING_OBJECTS, + pb->nr_objects, 0, pb->progress_cb_payload); + + if (ret) + return git_error_set_after_callback(ret); + } + } + + return 0; +} + +static int get_delta(void **out, git_odb *odb, git_pobject *po) +{ + git_odb_object *src = NULL, *trg = NULL; + size_t delta_size; + void *delta_buf; + int error; + + *out = NULL; + + if (git_odb_read(&src, odb, &po->delta->id) < 0 || + git_odb_read(&trg, odb, &po->id) < 0) + goto on_error; + + error = git_delta(&delta_buf, &delta_size, + git_odb_object_data(src), git_odb_object_size(src), + git_odb_object_data(trg), git_odb_object_size(trg), + 0); + + if (error < 0 && error != GIT_EBUFS) + goto on_error; + + if (error == GIT_EBUFS || delta_size != po->delta_size) { + git_error_set(GIT_ERROR_INVALID, "delta size changed"); + goto on_error; + } + + *out = delta_buf; + + git_odb_object_free(src); + git_odb_object_free(trg); + return 0; + +on_error: + git_odb_object_free(src); + git_odb_object_free(trg); + return -1; +} + +static int write_object( + git_packbuilder *pb, + git_pobject *po, + int (*write_cb)(void *buf, size_t size, void *cb_data), + void *cb_data) +{ + git_odb_object *obj = NULL; + git_object_t type; + unsigned char hdr[10], *zbuf = NULL; + void *data = NULL; + size_t hdr_len, zbuf_len = COMPRESS_BUFLEN, data_len; + int error; + + /* + * If we have a delta base, let's use the delta to save space. + * Otherwise load the whole object. 'data' ends up pointing to + * whatever data we want to put into the packfile. + */ + if (po->delta) { + if (po->delta_data) + data = po->delta_data; + else if ((error = get_delta(&data, pb->odb, po)) < 0) + goto done; + + data_len = po->delta_size; + type = GIT_OBJECT_REF_DELTA; + } else { + if ((error = git_odb_read(&obj, pb->odb, &po->id)) < 0) + goto done; + + data = (void *)git_odb_object_data(obj); + data_len = git_odb_object_size(obj); + type = git_odb_object_type(obj); + } + + /* Write header */ + if ((error = git_packfile__object_header(&hdr_len, hdr, data_len, type)) < 0 || + (error = write_cb(hdr, hdr_len, cb_data)) < 0 || + (error = git_hash_update(&pb->ctx, hdr, hdr_len)) < 0) + goto done; + + if (type == GIT_OBJECT_REF_DELTA) { + if ((error = write_cb(po->delta->id.id, GIT_OID_RAWSZ, cb_data)) < 0 || + (error = git_hash_update(&pb->ctx, po->delta->id.id, GIT_OID_RAWSZ)) < 0) + goto done; + } + + /* Write data */ + if (po->z_delta_size) { + data_len = po->z_delta_size; + + if ((error = write_cb(data, data_len, cb_data)) < 0 || + (error = git_hash_update(&pb->ctx, data, data_len)) < 0) + goto done; + } else { + zbuf = git__malloc(zbuf_len); + GIT_ERROR_CHECK_ALLOC(zbuf); + + git_zstream_reset(&pb->zstream); + + if ((error = git_zstream_set_input(&pb->zstream, data, data_len)) < 0) + goto done; + + while (!git_zstream_done(&pb->zstream)) { + if ((error = git_zstream_get_output(zbuf, &zbuf_len, &pb->zstream)) < 0 || + (error = write_cb(zbuf, zbuf_len, cb_data)) < 0 || + (error = git_hash_update(&pb->ctx, zbuf, zbuf_len)) < 0) + goto done; + + zbuf_len = COMPRESS_BUFLEN; /* reuse buffer */ + } + } + + /* + * If po->delta is true, data is a delta and it is our + * responsibility to free it (otherwise it's a git_object's + * data). We set po->delta_data to NULL in case we got the + * data from there instead of get_delta(). If we didn't, + * there's no harm. + */ + if (po->delta) { + git__free(data); + po->delta_data = NULL; + } + + pb->nr_written++; + +done: + git__free(zbuf); + git_odb_object_free(obj); + return error; +} + +enum write_one_status { + WRITE_ONE_SKIP = -1, /* already written */ + WRITE_ONE_BREAK = 0, /* writing this will bust the limit; not written */ + WRITE_ONE_WRITTEN = 1, /* normal */ + WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */ +}; + +static int write_one( + enum write_one_status *status, + git_packbuilder *pb, + git_pobject *po, + int (*write_cb)(void *buf, size_t size, void *cb_data), + void *cb_data) +{ + int error; + + if (po->recursing) { + *status = WRITE_ONE_RECURSIVE; + return 0; + } else if (po->written) { + *status = WRITE_ONE_SKIP; + return 0; + } + + if (po->delta) { + po->recursing = 1; + + if ((error = write_one(status, pb, po->delta, write_cb, cb_data)) < 0) + return error; + + /* we cannot depend on this one */ + if (*status == WRITE_ONE_RECURSIVE) + po->delta = NULL; + } + + *status = WRITE_ONE_WRITTEN; + po->written = 1; + po->recursing = 0; + + return write_object(pb, po, write_cb, cb_data); +} + +GIT_INLINE(void) add_to_write_order(git_pobject **wo, size_t *endp, + git_pobject *po) +{ + if (po->filled) + return; + wo[(*endp)++] = po; + po->filled = 1; +} + +static void add_descendants_to_write_order(git_pobject **wo, size_t *endp, + git_pobject *po) +{ + int add_to_order = 1; + while (po) { + if (add_to_order) { + git_pobject *s; + /* add this node... */ + add_to_write_order(wo, endp, po); + /* all its siblings... */ + for (s = po->delta_sibling; s; s = s->delta_sibling) { + add_to_write_order(wo, endp, s); + } + } + /* drop down a level to add left subtree nodes if possible */ + if (po->delta_child) { + add_to_order = 1; + po = po->delta_child; + } else { + add_to_order = 0; + /* our sibling might have some children, it is next */ + if (po->delta_sibling) { + po = po->delta_sibling; + continue; + } + /* go back to our parent node */ + po = po->delta; + while (po && !po->delta_sibling) { + /* we're on the right side of a subtree, keep + * going up until we can go right again */ + po = po->delta; + } + if (!po) { + /* done- we hit our original root node */ + return; + } + /* pass it off to sibling at this level */ + po = po->delta_sibling; + } + }; +} + +static void add_family_to_write_order(git_pobject **wo, size_t *endp, + git_pobject *po) +{ + git_pobject *root; + + for (root = po; root->delta; root = root->delta) + ; /* nothing */ + add_descendants_to_write_order(wo, endp, root); +} + +static int cb_tag_foreach(const char *name, git_oid *oid, void *data) +{ + git_packbuilder *pb = data; + git_pobject *po; + + GIT_UNUSED(name); + + if ((po = git_oidmap_get(pb->object_ix, oid)) == NULL) + return 0; + + po->tagged = 1; + + /* TODO: peel objects */ + + return 0; +} + +static int compute_write_order(git_pobject ***out, git_packbuilder *pb) +{ + size_t i, wo_end, last_untagged; + git_pobject **wo; + + *out = NULL; + + if (!pb->nr_objects) + return 0; + + if ((wo = git__mallocarray(pb->nr_objects, sizeof(*wo))) == NULL) + return -1; + + for (i = 0; i < pb->nr_objects; i++) { + git_pobject *po = pb->object_list + i; + po->tagged = 0; + po->filled = 0; + po->delta_child = NULL; + po->delta_sibling = NULL; + } + + /* + * Fully connect delta_child/delta_sibling network. + * Make sure delta_sibling is sorted in the original + * recency order. + */ + for (i = pb->nr_objects; i > 0;) { + git_pobject *po = &pb->object_list[--i]; + if (!po->delta) + continue; + /* Mark me as the first child */ + po->delta_sibling = po->delta->delta_child; + po->delta->delta_child = po; + } + + /* + * Mark objects that are at the tip of tags. + */ + if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0) { + git__free(wo); + return -1; + } + + /* + * Give the objects in the original recency order until + * we see a tagged tip. + */ + for (i = wo_end = 0; i < pb->nr_objects; i++) { + git_pobject *po = pb->object_list + i; + if (po->tagged) + break; + add_to_write_order(wo, &wo_end, po); + } + last_untagged = i; + + /* + * Then fill all the tagged tips. + */ + for (; i < pb->nr_objects; i++) { + git_pobject *po = pb->object_list + i; + if (po->tagged) + add_to_write_order(wo, &wo_end, po); + } + + /* + * And then all remaining commits and tags. + */ + for (i = last_untagged; i < pb->nr_objects; i++) { + git_pobject *po = pb->object_list + i; + if (po->type != GIT_OBJECT_COMMIT && + po->type != GIT_OBJECT_TAG) + continue; + add_to_write_order(wo, &wo_end, po); + } + + /* + * And then all the trees. + */ + for (i = last_untagged; i < pb->nr_objects; i++) { + git_pobject *po = pb->object_list + i; + if (po->type != GIT_OBJECT_TREE) + continue; + add_to_write_order(wo, &wo_end, po); + } + + /* + * Finally all the rest in really tight order + */ + for (i = last_untagged; i < pb->nr_objects; i++) { + git_pobject *po = pb->object_list + i; + if (!po->filled) + add_family_to_write_order(wo, &wo_end, po); + } + + if (wo_end != pb->nr_objects) { + git__free(wo); + git_error_set(GIT_ERROR_INVALID, "invalid write order"); + return -1; + } + + *out = wo; + return 0; +} + +static int write_pack(git_packbuilder *pb, + int (*write_cb)(void *buf, size_t size, void *cb_data), + void *cb_data) +{ + git_pobject **write_order; + git_pobject *po; + enum write_one_status status; + struct git_pack_header ph; + git_oid entry_oid; + size_t i = 0; + int error; + + if ((error = compute_write_order(&write_order, pb)) < 0) + return error; + + if (!git__is_uint32(pb->nr_objects)) { + git_error_set(GIT_ERROR_INVALID, "too many objects"); + error = -1; + goto done; + } + + /* Write pack header */ + ph.hdr_signature = htonl(PACK_SIGNATURE); + ph.hdr_version = htonl(PACK_VERSION); + ph.hdr_entries = htonl(pb->nr_objects); + + if ((error = write_cb(&ph, sizeof(ph), cb_data)) < 0 || + (error = git_hash_update(&pb->ctx, &ph, sizeof(ph))) < 0) + goto done; + + pb->nr_remaining = pb->nr_objects; + do { + pb->nr_written = 0; + for ( ; i < pb->nr_objects; ++i) { + po = write_order[i]; + + if ((error = write_one(&status, pb, po, write_cb, cb_data)) < 0) + goto done; + } + + pb->nr_remaining -= pb->nr_written; + } while (pb->nr_remaining && i < pb->nr_objects); + + if ((error = git_hash_final(entry_oid.id, &pb->ctx)) < 0) + goto done; + + error = write_cb(entry_oid.id, GIT_OID_RAWSZ, cb_data); + +done: + /* if callback cancelled writing, we must still free delta_data */ + for ( ; i < pb->nr_objects; ++i) { + po = write_order[i]; + if (po->delta_data) { + git__free(po->delta_data); + po->delta_data = NULL; + } + } + + git__free(write_order); + return error; +} + +static int write_pack_buf(void *buf, size_t size, void *data) +{ + git_str *b = (git_str *)data; + return git_str_put(b, buf, size); +} + +static int type_size_sort(const void *_a, const void *_b) +{ + const git_pobject *a = (git_pobject *)_a; + const git_pobject *b = (git_pobject *)_b; + + if (a->type > b->type) + return -1; + if (a->type < b->type) + return 1; + if (a->hash > b->hash) + return -1; + if (a->hash < b->hash) + return 1; + /* + * TODO + * + if (a->preferred_base > b->preferred_base) + return -1; + if (a->preferred_base < b->preferred_base) + return 1; + */ + if (a->size > b->size) + return -1; + if (a->size < b->size) + return 1; + return a < b ? -1 : (a > b); /* newest first */ +} + +static int delta_cacheable( + git_packbuilder *pb, + size_t src_size, + size_t trg_size, + size_t delta_size) +{ + size_t new_size; + + if (git__add_sizet_overflow(&new_size, pb->delta_cache_size, delta_size)) + return 0; + + if (pb->max_delta_cache_size && new_size > pb->max_delta_cache_size) + return 0; + + if (delta_size < pb->cache_max_small_delta_size) + return 1; + + /* cache delta, if objects are large enough compared to delta size */ + if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10)) + return 1; + + return 0; +} + +static int try_delta(git_packbuilder *pb, struct unpacked *trg, + struct unpacked *src, size_t max_depth, + size_t *mem_usage, int *ret) +{ + git_pobject *trg_object = trg->object; + git_pobject *src_object = src->object; + git_odb_object *obj; + size_t trg_size, src_size, delta_size, sizediff, max_size, sz; + size_t ref_depth; + void *delta_buf; + + /* Don't bother doing diffs between different types */ + if (trg_object->type != src_object->type) { + *ret = -1; + return 0; + } + + *ret = 0; + + /* TODO: support reuse-delta */ + + /* Let's not bust the allowed depth. */ + if (src->depth >= max_depth) + return 0; + + /* Now some size filtering heuristics. */ + trg_size = trg_object->size; + if (!trg_object->delta) { + max_size = trg_size/2 - 20; + ref_depth = 1; + } else { + max_size = trg_object->delta_size; + ref_depth = trg->depth; + } + + max_size = (uint64_t)max_size * (max_depth - src->depth) / + (max_depth - ref_depth + 1); + if (max_size == 0) + return 0; + + src_size = src_object->size; + sizediff = src_size < trg_size ? trg_size - src_size : 0; + if (sizediff >= max_size) + return 0; + if (trg_size < src_size / 32) + return 0; + + /* Load data if not already done */ + if (!trg->data) { + if (git_odb_read(&obj, pb->odb, &trg_object->id) < 0) + return -1; + + sz = git_odb_object_size(obj); + trg->data = git__malloc(sz); + GIT_ERROR_CHECK_ALLOC(trg->data); + memcpy(trg->data, git_odb_object_data(obj), sz); + + git_odb_object_free(obj); + + if (sz != trg_size) { + git_error_set(GIT_ERROR_INVALID, + "inconsistent target object length"); + return -1; + } + + *mem_usage += sz; + } + if (!src->data) { + size_t obj_sz; + + if (git_odb_read(&obj, pb->odb, &src_object->id) < 0 || + !git__is_ulong(obj_sz = git_odb_object_size(obj))) + return -1; + + sz = obj_sz; + src->data = git__malloc(sz); + GIT_ERROR_CHECK_ALLOC(src->data); + memcpy(src->data, git_odb_object_data(obj), sz); + + git_odb_object_free(obj); + + if (sz != src_size) { + git_error_set(GIT_ERROR_INVALID, + "inconsistent source object length"); + return -1; + } + + *mem_usage += sz; + } + if (!src->index) { + if (git_delta_index_init(&src->index, src->data, src_size) < 0) + return 0; /* suboptimal pack - out of memory */ + + *mem_usage += git_delta_index_size(src->index); + } + + if (git_delta_create_from_index(&delta_buf, &delta_size, src->index, trg->data, trg_size, + max_size) < 0) + return 0; + + if (trg_object->delta) { + /* Prefer only shallower same-sized deltas. */ + if (delta_size == trg_object->delta_size && + src->depth + 1 >= trg->depth) { + git__free(delta_buf); + return 0; + } + } + + GIT_ASSERT(git_packbuilder__cache_lock(pb) == 0); + + if (trg_object->delta_data) { + git__free(trg_object->delta_data); + GIT_ASSERT(pb->delta_cache_size >= trg_object->delta_size); + pb->delta_cache_size -= trg_object->delta_size; + trg_object->delta_data = NULL; + } + if (delta_cacheable(pb, src_size, trg_size, delta_size)) { + bool overflow = git__add_sizet_overflow( + &pb->delta_cache_size, pb->delta_cache_size, delta_size); + + GIT_ASSERT(git_packbuilder__cache_unlock(pb) == 0); + + if (overflow) { + git__free(delta_buf); + return -1; + } + + trg_object->delta_data = git__realloc(delta_buf, delta_size); + GIT_ERROR_CHECK_ALLOC(trg_object->delta_data); + } else { + /* create delta when writing the pack */ + GIT_ASSERT(git_packbuilder__cache_unlock(pb) == 0); + git__free(delta_buf); + } + + trg_object->delta = src_object; + trg_object->delta_size = delta_size; + trg->depth = src->depth + 1; + + *ret = 1; + return 0; +} + +static size_t check_delta_limit(git_pobject *me, size_t n) +{ + git_pobject *child = me->delta_child; + size_t m = n; + + while (child) { + size_t c = check_delta_limit(child, n + 1); + if (m < c) + m = c; + child = child->delta_sibling; + } + return m; +} + +static size_t free_unpacked(struct unpacked *n) +{ + size_t freed_mem = 0; + + if (n->index) { + freed_mem += git_delta_index_size(n->index); + git_delta_index_free(n->index); + } + n->index = NULL; + + if (n->data) { + freed_mem += n->object->size; + git__free(n->data); + n->data = NULL; + } + n->object = NULL; + n->depth = 0; + return freed_mem; +} + +static int report_delta_progress( + git_packbuilder *pb, uint32_t count, bool force) +{ + int ret; + + if (pb->progress_cb) { + double current_time = git__timer(); + double elapsed = current_time - pb->last_progress_report_time; + + if (force || elapsed < 0 || elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) { + pb->last_progress_report_time = current_time; + + ret = pb->progress_cb( + GIT_PACKBUILDER_DELTAFICATION, + count, pb->nr_objects, pb->progress_cb_payload); + + if (ret) + return git_error_set_after_callback(ret); + } + } + + return 0; +} + +static int find_deltas(git_packbuilder *pb, git_pobject **list, + size_t *list_size, size_t window, size_t depth) +{ + git_pobject *po; + git_str zbuf = GIT_STR_INIT; + struct unpacked *array; + size_t idx = 0, count = 0; + size_t mem_usage = 0; + size_t i; + int error = -1; + + array = git__calloc(window, sizeof(struct unpacked)); + GIT_ERROR_CHECK_ALLOC(array); + + for (;;) { + struct unpacked *n = array + idx; + size_t max_depth, j, best_base = SIZE_MAX; + + GIT_ASSERT(git_packbuilder__progress_lock(pb) == 0); + if (!*list_size) { + GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); + break; + } + + pb->nr_deltified += 1; + report_delta_progress(pb, pb->nr_deltified, false); + + po = *list++; + (*list_size)--; + GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); + + mem_usage -= free_unpacked(n); + n->object = po; + + while (pb->window_memory_limit && + mem_usage > pb->window_memory_limit && + count > 1) { + size_t tail = (idx + window - count) % window; + mem_usage -= free_unpacked(array + tail); + count--; + } + + /* + * If the current object is at pack edge, take the depth the + * objects that depend on the current object into account + * otherwise they would become too deep. + */ + max_depth = depth; + if (po->delta_child) { + size_t delta_limit = check_delta_limit(po, 0); + + if (delta_limit > max_depth) + goto next; + + max_depth -= delta_limit; + } + + j = window; + while (--j > 0) { + int ret; + size_t other_idx = idx + j; + struct unpacked *m; + + if (other_idx >= window) + other_idx -= window; + + m = array + other_idx; + if (!m->object) + break; + + if (try_delta(pb, n, m, max_depth, &mem_usage, &ret) < 0) + goto on_error; + if (ret < 0) + break; + else if (ret > 0) + best_base = other_idx; + } + + /* + * If we decided to cache the delta data, then it is best + * to compress it right away. First because we have to do + * it anyway, and doing it here while we're threaded will + * save a lot of time in the non threaded write phase, + * as well as allow for caching more deltas within + * the same cache size limit. + * ... + * But only if not writing to stdout, since in that case + * the network is most likely throttling writes anyway, + * and therefore it is best to go to the write phase ASAP + * instead, as we can afford spending more time compressing + * between writes at that moment. + */ + if (po->delta_data) { + if (git_zstream_deflatebuf(&zbuf, po->delta_data, po->delta_size) < 0) + goto on_error; + + git__free(po->delta_data); + po->delta_data = git__malloc(zbuf.size); + GIT_ERROR_CHECK_ALLOC(po->delta_data); + + memcpy(po->delta_data, zbuf.ptr, zbuf.size); + po->z_delta_size = zbuf.size; + git_str_clear(&zbuf); + + GIT_ASSERT(git_packbuilder__cache_lock(pb) == 0); + pb->delta_cache_size -= po->delta_size; + pb->delta_cache_size += po->z_delta_size; + GIT_ASSERT(git_packbuilder__cache_unlock(pb) == 0); + } + + /* + * If we made n a delta, and if n is already at max + * depth, leaving it in the window is pointless. we + * should evict it first. + */ + if (po->delta && max_depth <= n->depth) + continue; + + /* + * Move the best delta base up in the window, after the + * currently deltified object, to keep it longer. It will + * be the first base object to be attempted next. + */ + if (po->delta) { + struct unpacked swap = array[best_base]; + size_t dist = (window + idx - best_base) % window; + size_t dst = best_base; + while (dist--) { + size_t src = (dst + 1) % window; + array[dst] = array[src]; + dst = src; + } + array[dst] = swap; + } + + next: + idx++; + if (count + 1 < window) + count++; + if (idx >= window) + idx = 0; + } + error = 0; + +on_error: + for (i = 0; i < window; ++i) { + git__free(array[i].index); + git__free(array[i].data); + } + git__free(array); + git_str_dispose(&zbuf); + + return error; +} + +#ifdef GIT_THREADS + +struct thread_params { + git_thread thread; + git_packbuilder *pb; + + git_pobject **list; + + git_cond cond; + git_mutex mutex; + + size_t list_size; + size_t remaining; + + size_t window; + size_t depth; + size_t working; + size_t data_ready; +}; + +static void *threaded_find_deltas(void *arg) +{ + struct thread_params *me = arg; + + while (me->remaining) { + if (find_deltas(me->pb, me->list, &me->remaining, + me->window, me->depth) < 0) { + ; /* TODO */ + } + + GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_lock(me->pb) == 0, NULL); + me->working = 0; + git_cond_signal(&me->pb->progress_cond); + GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_unlock(me->pb) == 0, NULL); + + if (git_mutex_lock(&me->mutex)) { + git_error_set(GIT_ERROR_THREAD, "unable to lock packfile condition mutex"); + return NULL; + } + + while (!me->data_ready) + git_cond_wait(&me->cond, &me->mutex); + + /* + * We must not set ->data_ready before we wait on the + * condition because the main thread may have set it to 1 + * before we get here. In order to be sure that new + * work is available if we see 1 in ->data_ready, it + * was initialized to 0 before this thread was spawned + * and we reset it to 0 right away. + */ + me->data_ready = 0; + git_mutex_unlock(&me->mutex); + } + /* leave ->working 1 so that this doesn't get more work assigned */ + return NULL; +} + +static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, + size_t list_size, size_t window, size_t depth) +{ + struct thread_params *p; + size_t i; + int ret, active_threads = 0; + + if (!pb->nr_threads) + pb->nr_threads = git__online_cpus(); + + if (pb->nr_threads <= 1) { + find_deltas(pb, list, &list_size, window, depth); + return 0; + } + + p = git__mallocarray(pb->nr_threads, sizeof(*p)); + GIT_ERROR_CHECK_ALLOC(p); + + /* Partition the work among the threads */ + for (i = 0; i < pb->nr_threads; ++i) { + size_t sub_size = list_size / (pb->nr_threads - i); + + /* don't use too small segments or no deltas will be found */ + if (sub_size < 2*window && i+1 < pb->nr_threads) + sub_size = 0; + + p[i].pb = pb; + p[i].window = window; + p[i].depth = depth; + p[i].working = 1; + p[i].data_ready = 0; + + /* try to split chunks on "path" boundaries */ + while (sub_size && sub_size < list_size && + list[sub_size]->hash && + list[sub_size]->hash == list[sub_size-1]->hash) + sub_size++; + + p[i].list = list; + p[i].list_size = sub_size; + p[i].remaining = sub_size; + + list += sub_size; + list_size -= sub_size; + } + + /* Start work threads */ + for (i = 0; i < pb->nr_threads; ++i) { + if (!p[i].list_size) + continue; + + git_mutex_init(&p[i].mutex); + git_cond_init(&p[i].cond); + + ret = git_thread_create(&p[i].thread, + threaded_find_deltas, &p[i]); + if (ret) { + git_error_set(GIT_ERROR_THREAD, "unable to create thread"); + return -1; + } + active_threads++; + } + + /* + * Now let's wait for work completion. Each time a thread is done + * with its work, we steal half of the remaining work from the + * thread with the largest number of unprocessed objects and give + * it to that newly idle thread. This ensure good load balancing + * until the remaining object list segments are simply too short + * to be worth splitting anymore. + */ + while (active_threads) { + struct thread_params *target = NULL; + struct thread_params *victim = NULL; + size_t sub_size = 0; + + /* Start by locating a thread that has transitioned its + * 'working' flag from 1 -> 0. This indicates that it is + * ready to receive more work using our work-stealing + * algorithm. */ + GIT_ASSERT(git_packbuilder__progress_lock(pb) == 0); + for (;;) { + for (i = 0; !target && i < pb->nr_threads; i++) + if (!p[i].working) + target = &p[i]; + if (target) + break; + git_cond_wait(&pb->progress_cond, &pb->progress_mutex); + } + + /* At this point we hold the progress lock and have located + * a thread to receive more work. We still need to locate a + * thread from which to steal work (the victim). */ + for (i = 0; i < pb->nr_threads; i++) + if (p[i].remaining > 2*window && + (!victim || victim->remaining < p[i].remaining)) + victim = &p[i]; + + if (victim) { + sub_size = victim->remaining / 2; + list = victim->list + victim->list_size - sub_size; + while (sub_size && list[0]->hash && + list[0]->hash == list[-1]->hash) { + list++; + sub_size--; + } + if (!sub_size) { + /* + * It is possible for some "paths" to have + * so many objects that no hash boundary + * might be found. Let's just steal the + * exact half in that case. + */ + sub_size = victim->remaining / 2; + list -= sub_size; + } + target->list = list; + victim->list_size -= sub_size; + victim->remaining -= sub_size; + } + target->list_size = sub_size; + target->remaining = sub_size; + target->working = 1; + GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); + + if (git_mutex_lock(&target->mutex)) { + git_error_set(GIT_ERROR_THREAD, "unable to lock packfile condition mutex"); + git__free(p); + return -1; + } + + target->data_ready = 1; + git_cond_signal(&target->cond); + git_mutex_unlock(&target->mutex); + + if (!sub_size) { + git_thread_join(&target->thread, NULL); + git_cond_free(&target->cond); + git_mutex_free(&target->mutex); + active_threads--; + } + } + + git__free(p); + return 0; +} + +#else +#define ll_find_deltas(pb, l, ls, w, d) find_deltas(pb, l, &ls, w, d) +#endif + +int git_packbuilder__prepare(git_packbuilder *pb) +{ + git_pobject **delta_list; + size_t i, n = 0; + + if (pb->nr_objects == 0 || pb->done) + return 0; /* nothing to do */ + + /* + * Although we do not report progress during deltafication, we + * at least report that we are in the deltafication stage + */ + if (pb->progress_cb) + pb->progress_cb(GIT_PACKBUILDER_DELTAFICATION, 0, pb->nr_objects, pb->progress_cb_payload); + + delta_list = git__mallocarray(pb->nr_objects, sizeof(*delta_list)); + GIT_ERROR_CHECK_ALLOC(delta_list); + + for (i = 0; i < pb->nr_objects; ++i) { + git_pobject *po = pb->object_list + i; + + /* Make sure the item is within our size limits */ + if (po->size < 50 || po->size > pb->big_file_threshold) + continue; + + delta_list[n++] = po; + } + + if (n > 1) { + git__tsort((void **)delta_list, n, type_size_sort); + if (ll_find_deltas(pb, delta_list, n, + GIT_PACK_WINDOW + 1, + GIT_PACK_DEPTH) < 0) { + git__free(delta_list); + return -1; + } + } + + report_delta_progress(pb, pb->nr_objects, true); + + pb->done = true; + git__free(delta_list); + return 0; +} + +#define PREPARE_PACK if (git_packbuilder__prepare(pb) < 0) { return -1; } + +int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload) +{ + PREPARE_PACK; + return write_pack(pb, cb, payload); +} + +int git_packbuilder__write_buf(git_str *buf, git_packbuilder *pb) +{ + PREPARE_PACK; + + return write_pack(pb, &write_pack_buf, buf); +} + +int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb) +{ + GIT_BUF_WRAP_PRIVATE(buf, git_packbuilder__write_buf, pb); +} + +static int write_cb(void *buf, size_t len, void *payload) +{ + struct pack_write_context *ctx = payload; + return git_indexer_append(ctx->indexer, buf, len, ctx->stats); +} + +int git_packbuilder_write( + git_packbuilder *pb, + const char *path, + unsigned int mode, + git_indexer_progress_cb progress_cb, + void *progress_cb_payload) +{ + int error = -1; + git_str object_path = GIT_STR_INIT; + git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; + git_indexer *indexer = NULL; + git_indexer_progress stats; + struct pack_write_context ctx; + int t; + + PREPARE_PACK; + + if (path == NULL) { + if ((error = git_repository__item_path(&object_path, pb->repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0) + goto cleanup; + if ((error = git_str_joinpath(&object_path, git_str_cstr(&object_path), "pack")) < 0) + goto cleanup; + path = git_str_cstr(&object_path); + } + + opts.progress_cb = progress_cb; + opts.progress_cb_payload = progress_cb_payload; + + if ((error = git_indexer_new(&indexer, path, mode, pb->odb, &opts)) < 0) + goto cleanup; + + if (!git_repository__configmap_lookup(&t, pb->repo, GIT_CONFIGMAP_FSYNCOBJECTFILES) && t) + git_indexer__set_fsync(indexer, 1); + + ctx.indexer = indexer; + ctx.stats = &stats; + + if ((error = git_packbuilder_foreach(pb, write_cb, &ctx)) < 0) + goto cleanup; + + if ((error = git_indexer_commit(indexer, &stats)) < 0) + goto cleanup; + +#ifndef GIT_DEPRECATE_HARD + git_oid_cpy(&pb->pack_oid, git_indexer_hash(indexer)); +#endif + + pb->pack_name = git__strdup(git_indexer_name(indexer)); + GIT_ERROR_CHECK_ALLOC(pb->pack_name); + +cleanup: + git_indexer_free(indexer); + git_str_dispose(&object_path); + return error; +} + +#undef PREPARE_PACK + +#ifndef GIT_DEPRECATE_HARD +const git_oid *git_packbuilder_hash(git_packbuilder *pb) +{ + return &pb->pack_oid; +} +#endif + +const char *git_packbuilder_name(git_packbuilder *pb) +{ + return pb->pack_name; +} + + +static int cb_tree_walk( + const char *root, const git_tree_entry *entry, void *payload) +{ + int error; + struct tree_walk_context *ctx = payload; + + /* A commit inside a tree represents a submodule commit and should be skipped. */ + if (git_tree_entry_type(entry) == GIT_OBJECT_COMMIT) + return 0; + + if (!(error = git_str_sets(&ctx->buf, root)) && + !(error = git_str_puts(&ctx->buf, git_tree_entry_name(entry)))) + error = git_packbuilder_insert( + ctx->pb, git_tree_entry_id(entry), git_str_cstr(&ctx->buf)); + + return error; +} + +int git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid *oid) +{ + git_commit *commit; + + if (git_commit_lookup(&commit, pb->repo, oid) < 0 || + git_packbuilder_insert(pb, oid, NULL) < 0) + return -1; + + if (git_packbuilder_insert_tree(pb, git_commit_tree_id(commit)) < 0) + return -1; + + git_commit_free(commit); + return 0; +} + +int git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *oid) +{ + int error; + git_tree *tree = NULL; + struct tree_walk_context context = { pb, GIT_STR_INIT }; + + if (!(error = git_tree_lookup(&tree, pb->repo, oid)) && + !(error = git_packbuilder_insert(pb, oid, NULL))) + error = git_tree_walk(tree, GIT_TREEWALK_PRE, cb_tree_walk, &context); + + git_tree_free(tree); + git_str_dispose(&context.buf); + return error; +} + +int git_packbuilder_insert_recur(git_packbuilder *pb, const git_oid *id, const char *name) +{ + git_object *obj; + int error; + + GIT_ASSERT_ARG(pb); + GIT_ASSERT_ARG(id); + + if ((error = git_object_lookup(&obj, pb->repo, id, GIT_OBJECT_ANY)) < 0) + return error; + + switch (git_object_type(obj)) { + case GIT_OBJECT_BLOB: + error = git_packbuilder_insert(pb, id, name); + break; + case GIT_OBJECT_TREE: + error = git_packbuilder_insert_tree(pb, id); + break; + case GIT_OBJECT_COMMIT: + error = git_packbuilder_insert_commit(pb, id); + break; + case GIT_OBJECT_TAG: + if ((error = git_packbuilder_insert(pb, id, name)) < 0) + goto cleanup; + error = git_packbuilder_insert_recur(pb, git_tag_target_id((git_tag *) obj), NULL); + break; + + default: + git_error_set(GIT_ERROR_INVALID, "unknown object type"); + error = -1; + } + +cleanup: + git_object_free(obj); + return error; +} + +size_t git_packbuilder_object_count(git_packbuilder *pb) +{ + return pb->nr_objects; +} + +size_t git_packbuilder_written(git_packbuilder *pb) +{ + return pb->nr_written; +} + +static int lookup_walk_object(struct walk_object **out, git_packbuilder *pb, const git_oid *id) +{ + struct walk_object *obj; + + obj = git_pool_mallocz(&pb->object_pool, 1); + if (!obj) { + git_error_set_oom(); + return -1; + } + + git_oid_cpy(&obj->id, id); + + *out = obj; + return 0; +} + +static int retrieve_object(struct walk_object **out, git_packbuilder *pb, const git_oid *id) +{ + struct walk_object *obj; + int error; + + if ((obj = git_oidmap_get(pb->walk_objects, id)) == NULL) { + if ((error = lookup_walk_object(&obj, pb, id)) < 0) + return error; + + if ((error = git_oidmap_set(pb->walk_objects, &obj->id, obj)) < 0) + return error; + } + + *out = obj; + return 0; +} + +static int mark_blob_uninteresting(git_packbuilder *pb, const git_oid *id) +{ + int error; + struct walk_object *obj; + + if ((error = retrieve_object(&obj, pb, id)) < 0) + return error; + + obj->uninteresting = 1; + + return 0; +} + +static int mark_tree_uninteresting(git_packbuilder *pb, const git_oid *id) +{ + struct walk_object *obj; + git_tree *tree; + int error; + size_t i; + + if ((error = retrieve_object(&obj, pb, id)) < 0) + return error; + + if (obj->uninteresting) + return 0; + + obj->uninteresting = 1; + + if ((error = git_tree_lookup(&tree, pb->repo, id)) < 0) + return error; + + for (i = 0; i < git_tree_entrycount(tree); i++) { + const git_tree_entry *entry = git_tree_entry_byindex(tree, i); + const git_oid *entry_id = git_tree_entry_id(entry); + switch (git_tree_entry_type(entry)) { + case GIT_OBJECT_TREE: + if ((error = mark_tree_uninteresting(pb, entry_id)) < 0) + goto cleanup; + break; + case GIT_OBJECT_BLOB: + if ((error = mark_blob_uninteresting(pb, entry_id)) < 0) + goto cleanup; + break; + default: + /* it's a submodule or something unknown, we don't want it */ + ; + } + } + +cleanup: + git_tree_free(tree); + return error; +} + +/* + * Mark the edges of the graph uninteresting. Since we start from a + * git_revwalk, the commits are already uninteresting, but we need to + * mark the trees and blobs. + */ +static int mark_edges_uninteresting(git_packbuilder *pb, git_commit_list *commits) +{ + int error; + git_commit_list *list; + git_commit *commit; + + for (list = commits; list; list = list->next) { + if (!list->item->uninteresting) + continue; + + if ((error = git_commit_lookup(&commit, pb->repo, &list->item->oid)) < 0) + return error; + + error = mark_tree_uninteresting(pb, git_commit_tree_id(commit)); + git_commit_free(commit); + + if (error < 0) + return error; + } + + return 0; +} + +static int pack_objects_insert_tree(git_packbuilder *pb, git_tree *tree) +{ + size_t i; + int error; + git_tree *subtree; + struct walk_object *obj; + const char *name; + + if ((error = retrieve_object(&obj, pb, git_tree_id(tree))) < 0) + return error; + + if (obj->seen || obj->uninteresting) + return 0; + + obj->seen = 1; + + if ((error = git_packbuilder_insert(pb, &obj->id, NULL))) + return error; + + for (i = 0; i < git_tree_entrycount(tree); i++) { + const git_tree_entry *entry = git_tree_entry_byindex(tree, i); + const git_oid *entry_id = git_tree_entry_id(entry); + switch (git_tree_entry_type(entry)) { + case GIT_OBJECT_TREE: + if ((error = git_tree_lookup(&subtree, pb->repo, entry_id)) < 0) + return error; + + error = pack_objects_insert_tree(pb, subtree); + git_tree_free(subtree); + + if (error < 0) + return error; + + break; + case GIT_OBJECT_BLOB: + if ((error = retrieve_object(&obj, pb, entry_id)) < 0) + return error; + if (obj->uninteresting) + continue; + name = git_tree_entry_name(entry); + if ((error = git_packbuilder_insert(pb, entry_id, name)) < 0) + return error; + break; + default: + /* it's a submodule or something unknown, we don't want it */ + ; + } + } + + + return error; +} + +static int pack_objects_insert_commit(git_packbuilder *pb, struct walk_object *obj) +{ + int error; + git_commit *commit = NULL; + git_tree *tree = NULL; + + obj->seen = 1; + + if ((error = git_packbuilder_insert(pb, &obj->id, NULL)) < 0) + return error; + + if ((error = git_commit_lookup(&commit, pb->repo, &obj->id)) < 0) + return error; + + if ((error = git_tree_lookup(&tree, pb->repo, git_commit_tree_id(commit))) < 0) + goto cleanup; + + if ((error = pack_objects_insert_tree(pb, tree)) < 0) + goto cleanup; + +cleanup: + git_commit_free(commit); + git_tree_free(tree); + return error; +} + +int git_packbuilder_insert_walk(git_packbuilder *pb, git_revwalk *walk) +{ + int error; + git_oid id; + struct walk_object *obj; + + GIT_ASSERT_ARG(pb); + GIT_ASSERT_ARG(walk); + + if ((error = mark_edges_uninteresting(pb, walk->user_input)) < 0) + return error; + + /* + * TODO: git marks the parents of the edges + * uninteresting. This may provide a speed advantage, but does + * seem to assume the remote does not have a single-commit + * history on the other end. + */ + + /* walk down each tree up to the blobs and insert them, stopping when uninteresting */ + while ((error = git_revwalk_next(&id, walk)) == 0) { + if ((error = retrieve_object(&obj, pb, &id)) < 0) + return error; + + if (obj->seen || obj->uninteresting) + continue; + + if ((error = pack_objects_insert_commit(pb, obj)) < 0) + return error; + } + + if (error == GIT_ITEROVER) + error = 0; + + return error; +} + +int git_packbuilder_set_callbacks(git_packbuilder *pb, git_packbuilder_progress progress_cb, void *progress_cb_payload) +{ + if (!pb) + return -1; + + pb->progress_cb = progress_cb; + pb->progress_cb_payload = progress_cb_payload; + + return 0; +} + +void git_packbuilder_free(git_packbuilder *pb) +{ + if (pb == NULL) + return; + +#ifdef GIT_THREADS + + git_mutex_free(&pb->cache_mutex); + git_mutex_free(&pb->progress_mutex); + git_cond_free(&pb->progress_cond); + +#endif + + if (pb->odb) + git_odb_free(pb->odb); + + if (pb->object_ix) + git_oidmap_free(pb->object_ix); + + if (pb->object_list) + git__free(pb->object_list); + + git_oidmap_free(pb->walk_objects); + git_pool_clear(&pb->object_pool); + + git_hash_ctx_cleanup(&pb->ctx); + git_zstream_free(&pb->zstream); + + git__free(pb->pack_name); + + git__free(pb); +} diff --git a/src/libgit2/pack-objects.h b/src/libgit2/pack-objects.h new file mode 100644 index 000000000..2faa3ec7f --- /dev/null +++ b/src/libgit2/pack-objects.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_pack_objects_h__ +#define INCLUDE_pack_objects_h__ + +#include "common.h" + +#include "str.h" +#include "hash.h" +#include "oidmap.h" +#include "netops.h" +#include "zstream.h" +#include "pool.h" +#include "indexer.h" + +#include "git2/oid.h" +#include "git2/pack.h" + +#define GIT_PACK_WINDOW 10 /* number of objects to possibly delta against */ +#define GIT_PACK_DEPTH 50 /* max delta depth */ +#define GIT_PACK_DELTA_CACHE_SIZE (256 * 1024 * 1024) +#define GIT_PACK_DELTA_CACHE_LIMIT 1000 +#define GIT_PACK_BIG_FILE_THRESHOLD (512 * 1024 * 1024) + +typedef struct git_pobject { + git_oid id; + git_object_t type; + off64_t offset; + + size_t size; + + unsigned int hash; /* name hint hash */ + + struct git_pobject *delta; /* delta base object */ + struct git_pobject *delta_child; /* deltified objects who bases me */ + struct git_pobject *delta_sibling; /* other deltified objects + * who uses the same base as + * me */ + + void *delta_data; + size_t delta_size; + size_t z_delta_size; + + unsigned int written:1, + recursing:1, + tagged:1, + filled:1; +} git_pobject; + +struct git_packbuilder { + git_repository *repo; /* associated repository */ + git_odb *odb; /* associated object database */ + + git_hash_ctx ctx; + git_zstream zstream; + + uint32_t nr_objects, + nr_deltified, + nr_written, + nr_remaining; + + size_t nr_alloc; + + git_pobject *object_list; + + git_oidmap *object_ix; + + git_oidmap *walk_objects; + git_pool object_pool; + +#ifndef GIT_DEPRECATE_HARD + git_oid pack_oid; /* hash of written pack */ +#endif + char *pack_name; /* name of written pack */ + + /* synchronization objects */ + git_mutex cache_mutex; + git_mutex progress_mutex; + git_cond progress_cond; + + /* configs */ + size_t delta_cache_size; + size_t max_delta_cache_size; + size_t cache_max_small_delta_size; + size_t big_file_threshold; + size_t window_memory_limit; + + unsigned int nr_threads; /* nr of threads to use */ + + git_packbuilder_progress progress_cb; + void *progress_cb_payload; + double last_progress_report_time; /* the time progress was last reported */ + + bool done; +}; + +int git_packbuilder__write_buf(git_str *buf, git_packbuilder *pb); +int git_packbuilder__prepare(git_packbuilder *pb); + + +#endif diff --git a/src/libgit2/pack.c b/src/libgit2/pack.c new file mode 100644 index 000000000..5c0cba7e8 --- /dev/null +++ b/src/libgit2/pack.c @@ -0,0 +1,1629 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "pack.h" + +#include "delta.h" +#include "futils.h" +#include "mwindow.h" +#include "odb.h" +#include "oid.h" +#include "oidarray.h" + +/* Option to bypass checking existence of '.keep' files */ +bool git_disable_pack_keep_file_checks = false; + +static int packfile_open_locked(struct git_pack_file *p); +static off64_t nth_packed_object_offset_locked(struct git_pack_file *p, uint32_t n); +static int packfile_unpack_compressed( + git_rawobj *obj, + struct git_pack_file *p, + git_mwindow **w_curs, + off64_t *curpos, + size_t size, + git_object_t type); + +/* Can find the offset of an object given + * a prefix of an identifier. + * Throws GIT_EAMBIGUOUSOIDPREFIX if short oid + * is ambiguous within the pack. + * This method assumes that len is between + * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. + */ +static int pack_entry_find_offset( + off64_t *offset_out, + git_oid *found_oid, + struct git_pack_file *p, + const git_oid *short_oid, + size_t len); + +static int packfile_error(const char *message) +{ + git_error_set(GIT_ERROR_ODB, "invalid pack file - %s", message); + return -1; +} + +/******************** + * Delta base cache + ********************/ + +static git_pack_cache_entry *new_cache_object(git_rawobj *source) +{ + git_pack_cache_entry *e = git__calloc(1, sizeof(git_pack_cache_entry)); + if (!e) + return NULL; + + git_atomic32_inc(&e->refcount); + memcpy(&e->raw, source, sizeof(git_rawobj)); + + return e; +} + +static void free_cache_object(void *o) +{ + git_pack_cache_entry *e = (git_pack_cache_entry *)o; + + if (e != NULL) { + git__free(e->raw.data); + git__free(e); + } +} + +static void cache_free(git_pack_cache *cache) +{ + git_pack_cache_entry *entry; + + if (cache->entries) { + git_offmap_foreach_value(cache->entries, entry, { + free_cache_object(entry); + }); + + git_offmap_free(cache->entries); + cache->entries = NULL; + } +} + +static int cache_init(git_pack_cache *cache) +{ + if (git_offmap_new(&cache->entries) < 0) + return -1; + + cache->memory_limit = GIT_PACK_CACHE_MEMORY_LIMIT; + + if (git_mutex_init(&cache->lock)) { + git_error_set(GIT_ERROR_OS, "failed to initialize pack cache mutex"); + + git__free(cache->entries); + cache->entries = NULL; + + return -1; + } + + return 0; +} + +static git_pack_cache_entry *cache_get(git_pack_cache *cache, off64_t offset) +{ + git_pack_cache_entry *entry; + + if (git_mutex_lock(&cache->lock) < 0) + return NULL; + + if ((entry = git_offmap_get(cache->entries, offset)) != NULL) { + git_atomic32_inc(&entry->refcount); + entry->last_usage = cache->use_ctr++; + } + git_mutex_unlock(&cache->lock); + + return entry; +} + +/* Run with the cache lock held */ +static void free_lowest_entry(git_pack_cache *cache) +{ + off64_t offset; + git_pack_cache_entry *entry; + + git_offmap_foreach(cache->entries, offset, entry, { + if (entry && git_atomic32_get(&entry->refcount) == 0) { + cache->memory_used -= entry->raw.len; + git_offmap_delete(cache->entries, offset); + free_cache_object(entry); + } + }); +} + +static int cache_add( + git_pack_cache_entry **cached_out, + git_pack_cache *cache, + git_rawobj *base, + off64_t offset) +{ + git_pack_cache_entry *entry; + int exists; + + if (base->len > GIT_PACK_CACHE_SIZE_LIMIT) + return -1; + + entry = new_cache_object(base); + if (entry) { + if (git_mutex_lock(&cache->lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock cache"); + git__free(entry); + return -1; + } + /* Add it to the cache if nobody else has */ + exists = git_offmap_exists(cache->entries, offset); + if (!exists) { + while (cache->memory_used + base->len > cache->memory_limit) + free_lowest_entry(cache); + + git_offmap_set(cache->entries, offset, entry); + cache->memory_used += entry->raw.len; + + *cached_out = entry; + } + git_mutex_unlock(&cache->lock); + /* Somebody beat us to adding it into the cache */ + if (exists) { + git__free(entry); + return -1; + } + } + + return 0; +} + +/*********************************************************** + * + * PACK INDEX METHODS + * + ***********************************************************/ + +static void pack_index_free(struct git_pack_file *p) +{ + if (p->oids) { + git__free(p->oids); + p->oids = NULL; + } + if (p->index_map.data) { + git_futils_mmap_free(&p->index_map); + p->index_map.data = NULL; + } +} + +/* Run with the packfile lock held */ +static int pack_index_check_locked(const char *path, struct git_pack_file *p) +{ + struct git_pack_idx_header *hdr; + uint32_t version, nr, i, *index; + void *idx_map; + size_t idx_size; + struct stat st; + int error; + /* TODO: properly open the file without access time using O_NOATIME */ + git_file fd = git_futils_open_ro(path); + if (fd < 0) + return fd; + + if (p_fstat(fd, &st) < 0) { + p_close(fd); + git_error_set(GIT_ERROR_OS, "unable to stat pack index '%s'", path); + return -1; + } + + if (!S_ISREG(st.st_mode) || + !git__is_sizet(st.st_size) || + (idx_size = (size_t)st.st_size) < 4 * 256 + 20 + 20) + { + p_close(fd); + git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path); + return -1; + } + + error = git_futils_mmap_ro(&p->index_map, fd, 0, idx_size); + + p_close(fd); + + if (error < 0) + return error; + + hdr = idx_map = p->index_map.data; + + if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) { + version = ntohl(hdr->idx_version); + + if (version < 2 || version > 2) { + git_futils_mmap_free(&p->index_map); + return packfile_error("unsupported index version"); + } + + } else + version = 1; + + nr = 0; + index = idx_map; + + if (version > 1) + index += 2; /* skip index header */ + + for (i = 0; i < 256; i++) { + uint32_t n = ntohl(index[i]); + if (n < nr) { + git_futils_mmap_free(&p->index_map); + return packfile_error("index is non-monotonic"); + } + nr = n; + } + + if (version == 1) { + /* + * Total size: + * - 256 index entries 4 bytes each + * - 24-byte entries * nr (20-byte sha1 + 4-byte offset) + * - 20-byte SHA1 of the packfile + * - 20-byte SHA1 file checksum + */ + if (idx_size != 4*256 + nr * 24 + 20 + 20) { + git_futils_mmap_free(&p->index_map); + return packfile_error("index is corrupted"); + } + } else if (version == 2) { + /* + * Minimum size: + * - 8 bytes of header + * - 256 index entries 4 bytes each + * - 20-byte sha1 entry * nr + * - 4-byte crc entry * nr + * - 4-byte offset entry * nr + * - 20-byte SHA1 of the packfile + * - 20-byte SHA1 file checksum + * And after the 4-byte offset table might be a + * variable sized table containing 8-byte entries + * for offsets larger than 2^31. + */ + unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20; + unsigned long max_size = min_size; + + if (nr) + max_size += (nr - 1)*8; + + if (idx_size < min_size || idx_size > max_size) { + git_futils_mmap_free(&p->index_map); + return packfile_error("wrong index size"); + } + } + + p->num_objects = nr; + p->index_version = version; + return 0; +} + +/* Run with the packfile lock held */ +static int pack_index_open_locked(struct git_pack_file *p) +{ + int error = 0; + size_t name_len; + git_str idx_name = GIT_STR_INIT; + + if (p->index_version > -1) + goto cleanup; + + /* checked by git_pack_file alloc */ + name_len = strlen(p->pack_name); + GIT_ASSERT(name_len > strlen(".pack")); + + if ((error = git_str_init(&idx_name, name_len)) < 0) + goto cleanup; + + git_str_put(&idx_name, p->pack_name, name_len - strlen(".pack")); + git_str_puts(&idx_name, ".idx"); + if (git_str_oom(&idx_name)) { + error = -1; + goto cleanup; + } + + if (p->index_version == -1) + error = pack_index_check_locked(idx_name.ptr, p); + +cleanup: + git_str_dispose(&idx_name); + + return error; +} + +static unsigned char *pack_window_open( + struct git_pack_file *p, + git_mwindow **w_cursor, + off64_t offset, + unsigned int *left) +{ + unsigned char *pack_data = NULL; + + if (git_mutex_lock(&p->lock) < 0) { + git_error_set(GIT_ERROR_THREAD, "unable to lock packfile"); + return NULL; + } + if (git_mutex_lock(&p->mwf.lock) < 0) { + git_mutex_unlock(&p->lock); + git_error_set(GIT_ERROR_THREAD, "unable to lock packfile"); + return NULL; + } + + if (p->mwf.fd == -1 && packfile_open_locked(p) < 0) + goto cleanup; + + /* Since packfiles end in a hash of their content and it's + * pointless to ask for an offset into the middle of that + * hash, and the pack_window_contains function above wouldn't match + * don't allow an offset too close to the end of the file. + * + * Don't allow a negative offset, as that means we've wrapped + * around. + */ + if (offset > (p->mwf.size - 20)) + goto cleanup; + if (offset < 0) + goto cleanup; + + pack_data = git_mwindow_open(&p->mwf, w_cursor, offset, 20, left); + +cleanup: + git_mutex_unlock(&p->mwf.lock); + git_mutex_unlock(&p->lock); + return pack_data; + } + +/* + * The per-object header is a pretty dense thing, which is + * - first byte: low four bits are "size", + * then three bits of "type", + * with the high bit being "size continues". + * - each byte afterwards: low seven bits are size continuation, + * with the high bit being "size continues" + */ +int git_packfile__object_header(size_t *out, unsigned char *hdr, size_t size, git_object_t type) +{ + unsigned char *hdr_base; + unsigned char c; + + GIT_ASSERT_ARG(type >= GIT_OBJECT_COMMIT && type <= GIT_OBJECT_REF_DELTA); + + /* TODO: add support for chunked objects; see git.git 6c0d19b1 */ + + c = (unsigned char)((type << 4) | (size & 15)); + size >>= 4; + hdr_base = hdr; + + while (size) { + *hdr++ = c | 0x80; + c = size & 0x7f; + size >>= 7; + } + *hdr++ = c; + + *out = (hdr - hdr_base); + return 0; +} + + +static int packfile_unpack_header1( + unsigned long *usedp, + size_t *sizep, + git_object_t *type, + const unsigned char *buf, + unsigned long len) +{ + unsigned shift; + unsigned long size, c; + unsigned long used = 0; + + c = buf[used++]; + *type = (c >> 4) & 7; + size = c & 15; + shift = 4; + while (c & 0x80) { + if (len <= used) { + git_error_set(GIT_ERROR_ODB, "buffer too small"); + return GIT_EBUFS; + } + + if (bitsizeof(long) <= shift) { + *usedp = 0; + git_error_set(GIT_ERROR_ODB, "packfile corrupted"); + return -1; + } + + c = buf[used++]; + size += (c & 0x7f) << shift; + shift += 7; + } + + *sizep = (size_t)size; + *usedp = used; + return 0; +} + +int git_packfile_unpack_header( + size_t *size_p, + git_object_t *type_p, + struct git_pack_file *p, + git_mwindow **w_curs, + off64_t *curpos) +{ + unsigned char *base; + unsigned int left; + unsigned long used; + int error; + + if ((error = git_mutex_lock(&p->lock)) < 0) + return error; + if ((error = git_mutex_lock(&p->mwf.lock)) < 0) { + git_mutex_unlock(&p->lock); + return error; + } + + if (p->mwf.fd == -1 && (error = packfile_open_locked(p)) < 0) { + git_mutex_unlock(&p->lock); + git_mutex_unlock(&p->mwf.lock); + return error; + } + + /* pack_window_open() assures us we have [base, base + 20) available + * as a range that we can look at at. (Its actually the hash + * size that is assured.) With our object header encoding + * the maximum deflated object size is 2^137, which is just + * insane, so we know won't exceed what we have been given. + */ + base = git_mwindow_open(&p->mwf, w_curs, *curpos, 20, &left); + git_mutex_unlock(&p->lock); + git_mutex_unlock(&p->mwf.lock); + if (base == NULL) + return GIT_EBUFS; + + error = packfile_unpack_header1(&used, size_p, type_p, base, left); + git_mwindow_close(w_curs); + if (error == GIT_EBUFS) + return error; + else if (error < 0) + return packfile_error("header length is zero"); + + *curpos += used; + return 0; +} + +int git_packfile_resolve_header( + size_t *size_p, + git_object_t *type_p, + struct git_pack_file *p, + off64_t offset) +{ + git_mwindow *w_curs = NULL; + off64_t curpos = offset; + size_t size; + git_object_t type; + off64_t base_offset; + int error; + + error = git_mutex_lock(&p->lock); + if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); + return error; + } + error = git_mutex_lock(&p->mwf.lock); + if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); + git_mutex_unlock(&p->lock); + return error; + } + + if (p->mwf.fd == -1 && (error = packfile_open_locked(p)) < 0) { + git_mutex_unlock(&p->mwf.lock); + git_mutex_unlock(&p->lock); + return error; + } + git_mutex_unlock(&p->mwf.lock); + git_mutex_unlock(&p->lock); + + error = git_packfile_unpack_header(&size, &type, p, &w_curs, &curpos); + if (error < 0) + return error; + + if (type == GIT_OBJECT_OFS_DELTA || type == GIT_OBJECT_REF_DELTA) { + size_t base_size; + git_packfile_stream stream; + + error = get_delta_base(&base_offset, p, &w_curs, &curpos, type, offset); + git_mwindow_close(&w_curs); + + if (error < 0) + return error; + + if ((error = git_packfile_stream_open(&stream, p, curpos)) < 0) + return error; + error = git_delta_read_header_fromstream(&base_size, size_p, &stream); + git_packfile_stream_dispose(&stream); + if (error < 0) + return error; + } else { + *size_p = size; + base_offset = 0; + } + + while (type == GIT_OBJECT_OFS_DELTA || type == GIT_OBJECT_REF_DELTA) { + curpos = base_offset; + error = git_packfile_unpack_header(&size, &type, p, &w_curs, &curpos); + if (error < 0) + return error; + if (type != GIT_OBJECT_OFS_DELTA && type != GIT_OBJECT_REF_DELTA) + break; + + error = get_delta_base(&base_offset, p, &w_curs, &curpos, type, base_offset); + git_mwindow_close(&w_curs); + + if (error < 0) + return error; + } + *type_p = type; + + return error; +} + +#define SMALL_STACK_SIZE 64 + +/** + * Generate the chain of dependencies which we need to get to the + * object at `off`. `chain` is used a stack, popping gives the right + * order to apply deltas on. If an object is found in the pack's base + * cache, we stop calculating there. + */ +static int pack_dependency_chain(git_dependency_chain *chain_out, + git_pack_cache_entry **cached_out, off64_t *cached_off, + struct pack_chain_elem *small_stack, size_t *stack_sz, + struct git_pack_file *p, off64_t obj_offset) +{ + git_dependency_chain chain = GIT_ARRAY_INIT; + git_mwindow *w_curs = NULL; + off64_t curpos = obj_offset, base_offset; + int error = 0, use_heap = 0; + size_t size, elem_pos; + git_object_t type; + + elem_pos = 0; + while (true) { + struct pack_chain_elem *elem; + git_pack_cache_entry *cached = NULL; + + /* if we have a base cached, we can stop here instead */ + if ((cached = cache_get(&p->bases, obj_offset)) != NULL) { + *cached_out = cached; + *cached_off = obj_offset; + break; + } + + /* if we run out of space on the small stack, use the array */ + if (elem_pos == SMALL_STACK_SIZE) { + git_array_init_to_size(chain, elem_pos); + GIT_ERROR_CHECK_ARRAY(chain); + memcpy(chain.ptr, small_stack, elem_pos * sizeof(struct pack_chain_elem)); + chain.size = elem_pos; + use_heap = 1; + } + + curpos = obj_offset; + if (!use_heap) { + elem = &small_stack[elem_pos]; + } else { + elem = git_array_alloc(chain); + if (!elem) { + error = -1; + goto on_error; + } + } + + elem->base_key = obj_offset; + + error = git_packfile_unpack_header(&size, &type, p, &w_curs, &curpos); + if (error < 0) + goto on_error; + + elem->offset = curpos; + elem->size = size; + elem->type = type; + elem->base_key = obj_offset; + + if (type != GIT_OBJECT_OFS_DELTA && type != GIT_OBJECT_REF_DELTA) + break; + + error = get_delta_base(&base_offset, p, &w_curs, &curpos, type, obj_offset); + git_mwindow_close(&w_curs); + + if (error < 0) + goto on_error; + + /* we need to pass the pos *after* the delta-base bit */ + elem->offset = curpos; + + /* go through the loop again, but with the new object */ + obj_offset = base_offset; + elem_pos++; + } + + + *stack_sz = elem_pos + 1; + *chain_out = chain; + return error; + +on_error: + git_array_clear(chain); + return error; +} + +int git_packfile_unpack( + git_rawobj *obj, + struct git_pack_file *p, + off64_t *obj_offset) +{ + git_mwindow *w_curs = NULL; + off64_t curpos = *obj_offset; + int error, free_base = 0; + git_dependency_chain chain = GIT_ARRAY_INIT; + struct pack_chain_elem *elem = NULL, *stack; + git_pack_cache_entry *cached = NULL; + struct pack_chain_elem small_stack[SMALL_STACK_SIZE]; + size_t stack_size = 0, elem_pos, alloclen; + git_object_t base_type; + + error = git_mutex_lock(&p->lock); + if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); + return error; + } + error = git_mutex_lock(&p->mwf.lock); + if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); + git_mutex_unlock(&p->lock); + return error; + } + + if (p->mwf.fd == -1) + error = packfile_open_locked(p); + git_mutex_unlock(&p->mwf.lock); + git_mutex_unlock(&p->lock); + if (error < 0) + return error; + + /* + * TODO: optionally check the CRC on the packfile + */ + + error = pack_dependency_chain(&chain, &cached, obj_offset, small_stack, &stack_size, p, *obj_offset); + if (error < 0) + return error; + + obj->data = NULL; + obj->len = 0; + obj->type = GIT_OBJECT_INVALID; + + /* let's point to the right stack */ + stack = chain.ptr ? chain.ptr : small_stack; + + elem_pos = stack_size; + if (cached) { + memcpy(obj, &cached->raw, sizeof(git_rawobj)); + base_type = obj->type; + elem_pos--; /* stack_size includes the base, which isn't actually there */ + } else { + elem = &stack[--elem_pos]; + base_type = elem->type; + } + + switch (base_type) { + case GIT_OBJECT_COMMIT: + case GIT_OBJECT_TREE: + case GIT_OBJECT_BLOB: + case GIT_OBJECT_TAG: + if (!cached) { + curpos = elem->offset; + error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type); + git_mwindow_close(&w_curs); + base_type = elem->type; + } + if (error < 0) + goto cleanup; + break; + case GIT_OBJECT_OFS_DELTA: + case GIT_OBJECT_REF_DELTA: + error = packfile_error("dependency chain ends in a delta"); + goto cleanup; + default: + error = packfile_error("invalid packfile type in header"); + goto cleanup; + } + + /* + * Finding the object we want a cached base element is + * problematic, as we need to make sure we don't accidentally + * give the caller the cached object, which it would then feel + * free to free, so we need to copy the data. + */ + if (cached && stack_size == 1) { + void *data = obj->data; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, obj->len, 1); + obj->data = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(obj->data); + + memcpy(obj->data, data, obj->len + 1); + git_atomic32_dec(&cached->refcount); + goto cleanup; + } + + /* we now apply each consecutive delta until we run out */ + while (elem_pos > 0 && !error) { + git_rawobj base, delta; + + /* + * We can now try to add the base to the cache, as + * long as it's not already the cached one. + */ + if (!cached) + free_base = !!cache_add(&cached, &p->bases, obj, elem->base_key); + + elem = &stack[elem_pos - 1]; + curpos = elem->offset; + error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type); + git_mwindow_close(&w_curs); + + if (error < 0) { + /* We have transferred ownership of the data to the cache. */ + obj->data = NULL; + break; + } + + /* the current object becomes the new base, on which we apply the delta */ + base = *obj; + obj->data = NULL; + obj->len = 0; + obj->type = GIT_OBJECT_INVALID; + + error = git_delta_apply(&obj->data, &obj->len, base.data, base.len, delta.data, delta.len); + obj->type = base_type; + + /* + * We usually don't want to free the base at this + * point, as we put it into the cache in the previous + * iteration. free_base lets us know that we got the + * base object directly from the packfile, so we can free it. + */ + git__free(delta.data); + if (free_base) { + free_base = 0; + git__free(base.data); + } + + if (cached) { + git_atomic32_dec(&cached->refcount); + cached = NULL; + } + + if (error < 0) + break; + + elem_pos--; + } + +cleanup: + if (error < 0) { + git__free(obj->data); + if (cached) + git_atomic32_dec(&cached->refcount); + } + + if (elem) + *obj_offset = curpos; + + git_array_clear(chain); + return error; +} + +int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, off64_t curpos) +{ + memset(obj, 0, sizeof(git_packfile_stream)); + obj->curpos = curpos; + obj->p = p; + + if (git_zstream_init(&obj->zstream, GIT_ZSTREAM_INFLATE) < 0) { + git_error_set(GIT_ERROR_ZLIB, "failed to init packfile stream"); + return -1; + } + + return 0; +} + +ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len) +{ + unsigned int window_len; + unsigned char *in; + int error; + + if (obj->done) + return 0; + + if ((in = pack_window_open(obj->p, &obj->mw, obj->curpos, &window_len)) == NULL) + return GIT_EBUFS; + + if ((error = git_zstream_set_input(&obj->zstream, in, window_len)) < 0 || + (error = git_zstream_get_output_chunk(buffer, &len, &obj->zstream)) < 0) { + git_mwindow_close(&obj->mw); + git_error_set(GIT_ERROR_ZLIB, "error reading from the zlib stream"); + return -1; + } + + git_mwindow_close(&obj->mw); + + obj->curpos += window_len - obj->zstream.in_len; + + if (git_zstream_eos(&obj->zstream)) + obj->done = 1; + + /* If we didn't write anything out but we're not done, we need more data */ + if (!len && !git_zstream_eos(&obj->zstream)) + return GIT_EBUFS; + + return len; + +} + +void git_packfile_stream_dispose(git_packfile_stream *obj) +{ + git_zstream_free(&obj->zstream); +} + +static int packfile_unpack_compressed( + git_rawobj *obj, + struct git_pack_file *p, + git_mwindow **mwindow, + off64_t *position, + size_t size, + git_object_t type) +{ + git_zstream zstream = GIT_ZSTREAM_INIT; + size_t buffer_len, total = 0; + char *data = NULL; + int error; + + GIT_ERROR_CHECK_ALLOC_ADD(&buffer_len, size, 1); + data = git__calloc(1, buffer_len); + GIT_ERROR_CHECK_ALLOC(data); + + if ((error = git_zstream_init(&zstream, GIT_ZSTREAM_INFLATE)) < 0) { + git_error_set(GIT_ERROR_ZLIB, "failed to init zlib stream on unpack"); + goto out; + } + + do { + size_t bytes = buffer_len - total; + unsigned int window_len, consumed; + unsigned char *in; + + if ((in = pack_window_open(p, mwindow, *position, &window_len)) == NULL) { + error = -1; + goto out; + } + + if ((error = git_zstream_set_input(&zstream, in, window_len)) < 0 || + (error = git_zstream_get_output_chunk(data + total, &bytes, &zstream)) < 0) { + git_mwindow_close(mwindow); + goto out; + } + + git_mwindow_close(mwindow); + + consumed = window_len - (unsigned int)zstream.in_len; + + if (!bytes && !consumed) { + git_error_set(GIT_ERROR_ZLIB, "error inflating zlib stream"); + error = -1; + goto out; + } + + *position += consumed; + total += bytes; + } while (!git_zstream_eos(&zstream)); + + if (total != size || !git_zstream_eos(&zstream)) { + git_error_set(GIT_ERROR_ZLIB, "error inflating zlib stream"); + error = -1; + goto out; + } + + obj->type = type; + obj->len = size; + obj->data = data; + +out: + git_zstream_free(&zstream); + if (error) + git__free(data); + + return error; +} + +/* + * curpos is where the data starts, delta_obj_offset is the where the + * header starts + */ +int get_delta_base( + off64_t *delta_base_out, + struct git_pack_file *p, + git_mwindow **w_curs, + off64_t *curpos, + git_object_t type, + off64_t delta_obj_offset) +{ + unsigned int left = 0; + unsigned char *base_info; + off64_t base_offset; + git_oid unused; + + GIT_ASSERT_ARG(delta_base_out); + + base_info = pack_window_open(p, w_curs, *curpos, &left); + /* Assumption: the only reason this would fail is because the file is too small */ + if (base_info == NULL) + return GIT_EBUFS; + /* pack_window_open() assured us we have [base_info, base_info + 20) + * as a range that we can look at without walking off the + * end of the mapped window. Its actually the hash size + * that is assured. An OFS_DELTA longer than the hash size + * is stupid, as then a REF_DELTA would be smaller to store. + */ + if (type == GIT_OBJECT_OFS_DELTA) { + unsigned used = 0; + unsigned char c = base_info[used++]; + size_t unsigned_base_offset = c & 127; + while (c & 128) { + if (left <= used) + return GIT_EBUFS; + unsigned_base_offset += 1; + if (!unsigned_base_offset || MSB(unsigned_base_offset, 7)) + return packfile_error("overflow"); + c = base_info[used++]; + unsigned_base_offset = (unsigned_base_offset << 7) + (c & 127); + } + if (unsigned_base_offset == 0 || (size_t)delta_obj_offset <= unsigned_base_offset) + return packfile_error("out of bounds"); + base_offset = delta_obj_offset - unsigned_base_offset; + *curpos += used; + } else if (type == GIT_OBJECT_REF_DELTA) { + /* If we have the cooperative cache, search in it first */ + if (p->has_cache) { + struct git_pack_entry *entry; + git_oid oid; + + git_oid_fromraw(&oid, base_info); + if ((entry = git_oidmap_get(p->idx_cache, &oid)) != NULL) { + if (entry->offset == 0) + return packfile_error("delta offset is zero"); + + *curpos += 20; + *delta_base_out = entry->offset; + return 0; + } else { + /* If we're building an index, don't try to find the pack + * entry; we just haven't seen it yet. We'll make + * progress again in the next loop. + */ + return GIT_PASSTHROUGH; + } + } + + /* The base entry _must_ be in the same pack */ + if (pack_entry_find_offset(&base_offset, &unused, p, (git_oid *)base_info, GIT_OID_HEXSZ) < 0) + return packfile_error("base entry delta is not in the same pack"); + *curpos += 20; + } else + return packfile_error("unknown object type"); + + if (base_offset == 0) + return packfile_error("delta offset is zero"); + + *delta_base_out = base_offset; + return 0; +} + +/*********************************************************** + * + * PACKFILE METHODS + * + ***********************************************************/ + +void git_packfile_free(struct git_pack_file *p, bool unlink_packfile) +{ + bool locked = true; + + if (!p) + return; + + cache_free(&p->bases); + + if (git_mutex_lock(&p->lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock packfile"); + locked = false; + } + if (p->mwf.fd >= 0) { + git_mwindow_free_all(&p->mwf); + p_close(p->mwf.fd); + p->mwf.fd = -1; + } + if (locked) + git_mutex_unlock(&p->lock); + + if (unlink_packfile) + p_unlink(p->pack_name); + + pack_index_free(p); + + git__free(p->bad_object_sha1); + + git_mutex_free(&p->bases.lock); + git_mutex_free(&p->mwf.lock); + git_mutex_free(&p->lock); + git__free(p); +} + +/* Run with the packfile and mwf locks held */ +static int packfile_open_locked(struct git_pack_file *p) +{ + struct stat st; + struct git_pack_header hdr; + git_oid sha1; + unsigned char *idx_sha1; + + if (pack_index_open_locked(p) < 0) + return git_odb__error_notfound("failed to open packfile", NULL, 0); + + if (p->mwf.fd >= 0) + return 0; + + /* TODO: open with noatime */ + p->mwf.fd = git_futils_open_ro(p->pack_name); + if (p->mwf.fd < 0) + goto cleanup; + + if (p_fstat(p->mwf.fd, &st) < 0) { + git_error_set(GIT_ERROR_OS, "could not stat packfile"); + goto cleanup; + } + + /* If we created the struct before we had the pack we lack size. */ + if (!p->mwf.size) { + if (!S_ISREG(st.st_mode)) + goto cleanup; + p->mwf.size = (off64_t)st.st_size; + } else if (p->mwf.size != st.st_size) + goto cleanup; + +#if 0 + /* We leave these file descriptors open with sliding mmap; + * there is no point keeping them open across exec(), though. + */ + fd_flag = fcntl(p->mwf.fd, F_GETFD, 0); + if (fd_flag < 0) + goto cleanup; + + fd_flag |= FD_CLOEXEC; + if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1) + goto cleanup; +#endif + + /* Verify we recognize this pack file format. */ + if (p_read(p->mwf.fd, &hdr, sizeof(hdr)) < 0 || + hdr.hdr_signature != htonl(PACK_SIGNATURE) || + !pack_version_ok(hdr.hdr_version)) + goto cleanup; + + /* Verify the pack matches its index. */ + if (p->num_objects != ntohl(hdr.hdr_entries) || + p_pread(p->mwf.fd, sha1.id, GIT_OID_RAWSZ, p->mwf.size - GIT_OID_RAWSZ) < 0) + goto cleanup; + + idx_sha1 = ((unsigned char *)p->index_map.data) + p->index_map.len - 40; + + if (git_oid__cmp(&sha1, (git_oid *)idx_sha1) != 0) + goto cleanup; + + if (git_mwindow_file_register(&p->mwf) < 0) + goto cleanup; + + return 0; + +cleanup: + git_error_set(GIT_ERROR_OS, "invalid packfile '%s'", p->pack_name); + + if (p->mwf.fd >= 0) + p_close(p->mwf.fd); + p->mwf.fd = -1; + + return -1; +} + +int git_packfile__name(char **out, const char *path) +{ + size_t path_len; + git_str buf = GIT_STR_INIT; + + path_len = strlen(path); + + if (path_len < strlen(".idx")) + return git_odb__error_notfound("invalid packfile path", NULL, 0); + + if (git_str_printf(&buf, "%.*s.pack", (int)(path_len - strlen(".idx")), path) < 0) + return -1; + + *out = git_str_detach(&buf); + return 0; +} + +int git_packfile_alloc(struct git_pack_file **pack_out, const char *path) +{ + struct stat st; + struct git_pack_file *p; + size_t path_len = path ? strlen(path) : 0, alloc_len; + + *pack_out = NULL; + + if (path_len < strlen(".idx")) + return git_odb__error_notfound("invalid packfile path", NULL, 0); + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*p), path_len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); + + p = git__calloc(1, alloc_len); + GIT_ERROR_CHECK_ALLOC(p); + + memcpy(p->pack_name, path, path_len + 1); + + /* + * Make sure a corresponding .pack file exists and that + * the index looks sane. + */ + if (git__suffixcmp(path, ".idx") == 0) { + size_t root_len = path_len - strlen(".idx"); + + if (!git_disable_pack_keep_file_checks) { + memcpy(p->pack_name + root_len, ".keep", sizeof(".keep")); + if (git_fs_path_exists(p->pack_name) == true) + p->pack_keep = 1; + } + + memcpy(p->pack_name + root_len, ".pack", sizeof(".pack")); + } + + if (p_stat(p->pack_name, &st) < 0 || !S_ISREG(st.st_mode)) { + git__free(p); + return git_odb__error_notfound("packfile not found", NULL, 0); + } + + /* ok, it looks sane as far as we can check without + * actually mapping the pack file. + */ + p->mwf.fd = -1; + p->mwf.size = st.st_size; + p->pack_local = 1; + p->mtime = (git_time_t)st.st_mtime; + p->index_version = -1; + + if (git_mutex_init(&p->lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to initialize packfile mutex"); + git__free(p); + return -1; + } + + if (git_mutex_init(&p->mwf.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to initialize packfile window mutex"); + git_mutex_free(&p->lock); + git__free(p); + return -1; + } + + if (cache_init(&p->bases) < 0) { + git_mutex_free(&p->mwf.lock); + git_mutex_free(&p->lock); + git__free(p); + return -1; + } + + *pack_out = p; + + return 0; +} + +/*********************************************************** + * + * PACKFILE ENTRY SEARCH INTERNALS + * + ***********************************************************/ + +static off64_t nth_packed_object_offset_locked(struct git_pack_file *p, uint32_t n) +{ + const unsigned char *index, *end; + uint32_t off32; + + index = p->index_map.data; + end = index + p->index_map.len; + index += 4 * 256; + if (p->index_version == 1) + return ntohl(*((uint32_t *)(index + 24 * n))); + + index += 8 + p->num_objects * (20 + 4); + off32 = ntohl(*((uint32_t *)(index + 4 * n))); + if (!(off32 & 0x80000000)) + return off32; + index += p->num_objects * 4 + (off32 & 0x7fffffff) * 8; + + /* Make sure we're not being sent out of bounds */ + if (index >= end - 8) + return -1; + + return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) | + ntohl(*((uint32_t *)(index + 4))); +} + +static int git__memcmp4(const void *a, const void *b) { + return memcmp(a, b, 4); +} + +int git_pack_foreach_entry( + struct git_pack_file *p, + git_odb_foreach_cb cb, + void *data) +{ + const unsigned char *index, *current; + uint32_t i; + int error = 0; + git_array_oid_t oids = GIT_ARRAY_INIT; + git_oid *oid; + + if (git_mutex_lock(&p->lock) < 0) + return packfile_error("failed to get lock for git_pack_foreach_entry"); + + if ((error = pack_index_open_locked(p)) < 0) { + git_mutex_unlock(&p->lock); + return error; + } + + if (!p->index_map.data) { + git_error_set(GIT_ERROR_INTERNAL, "internal error: p->index_map.data == NULL"); + git_mutex_unlock(&p->lock); + return -1; + } + + index = p->index_map.data; + + if (p->index_version > 1) + index += 8; + + index += 4 * 256; + + if (p->oids == NULL) { + git_vector offsets, oids; + + if ((error = git_vector_init(&oids, p->num_objects, NULL))) { + git_mutex_unlock(&p->lock); + return error; + } + + if ((error = git_vector_init(&offsets, p->num_objects, git__memcmp4))) { + git_mutex_unlock(&p->lock); + return error; + } + + if (p->index_version > 1) { + const unsigned char *off = index + 24 * p->num_objects; + for (i = 0; i < p->num_objects; i++) + git_vector_insert(&offsets, (void*)&off[4 * i]); + git_vector_sort(&offsets); + git_vector_foreach(&offsets, i, current) + git_vector_insert(&oids, (void*)&index[5 * (current - off)]); + } else { + for (i = 0; i < p->num_objects; i++) + git_vector_insert(&offsets, (void*)&index[24 * i]); + git_vector_sort(&offsets); + git_vector_foreach(&offsets, i, current) + git_vector_insert(&oids, (void*)¤t[4]); + } + + git_vector_free(&offsets); + p->oids = (git_oid **)git_vector_detach(NULL, NULL, &oids); + } + + /* We need to copy the OIDs to another array before we relinquish the lock to avoid races. */ + git_array_init_to_size(oids, p->num_objects); + if (!oids.ptr) { + git_mutex_unlock(&p->lock); + git_array_clear(oids); + GIT_ERROR_CHECK_ARRAY(oids); + } + for (i = 0; i < p->num_objects; i++) { + oid = git_array_alloc(oids); + if (!oid) { + git_mutex_unlock(&p->lock); + git_array_clear(oids); + GIT_ERROR_CHECK_ALLOC(oid); + } + git_oid_cpy(oid, p->oids[i]); + } + + git_mutex_unlock(&p->lock); + + git_array_foreach(oids, i, oid) { + if ((error = cb(oid, data)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + git_array_clear(oids); + return error; +} + +int git_pack_foreach_entry_offset( + struct git_pack_file *p, + git_pack_foreach_entry_offset_cb cb, + void *data) +{ + const unsigned char *index; + off64_t current_offset; + const git_oid *current_oid; + uint32_t i; + int error = 0; + + if (git_mutex_lock(&p->lock) < 0) + return packfile_error("failed to get lock for git_pack_foreach_entry_offset"); + + index = p->index_map.data; + if (index == NULL) { + if ((error = pack_index_open_locked(p)) < 0) + goto cleanup; + + if (!p->index_map.data) { + git_error_set(GIT_ERROR_INTERNAL, "internal error: p->index_map.data == NULL"); + goto cleanup; + } + + index = p->index_map.data; + } + + if (p->index_version > 1) + index += 8; + + index += 4 * 256; + + /* all offsets should have been validated by pack_index_check_locked */ + if (p->index_version > 1) { + const unsigned char *offsets = index + 24 * p->num_objects; + const unsigned char *large_offset_ptr; + const unsigned char *large_offsets = index + 28 * p->num_objects; + const unsigned char *large_offsets_end = ((const unsigned char *)p->index_map.data) + p->index_map.len - 20; + for (i = 0; i < p->num_objects; i++) { + current_offset = ntohl(*(const uint32_t *)(offsets + 4 * i)); + if (current_offset & 0x80000000) { + large_offset_ptr = large_offsets + (current_offset & 0x7fffffff) * 8; + if (large_offset_ptr >= large_offsets_end) { + error = packfile_error("invalid large offset"); + goto cleanup; + } + current_offset = (((off64_t)ntohl(*((uint32_t *)(large_offset_ptr + 0)))) << 32) | + ntohl(*((uint32_t *)(large_offset_ptr + 4))); + } + current_oid = (const git_oid *)(index + 20 * i); + if ((error = cb(current_oid, current_offset, data)) != 0) { + error = git_error_set_after_callback(error); + goto cleanup; + } + } + } else { + for (i = 0; i < p->num_objects; i++) { + current_offset = ntohl(*(const uint32_t *)(index + 24 * i)); + current_oid = (const git_oid *)(index + 24 * i + 4); + if ((error = cb(current_oid, current_offset, data)) != 0) { + error = git_error_set_after_callback(error); + goto cleanup; + } + } + } + +cleanup: + git_mutex_unlock(&p->lock); + return error; +} + +int git_pack__lookup_sha1(const void *oid_lookup_table, size_t stride, unsigned lo, + unsigned hi, const unsigned char *oid_prefix) +{ + const unsigned char *base = oid_lookup_table; + + while (lo < hi) { + unsigned mi = (lo + hi) / 2; + int cmp = git_oid__hashcmp(base + mi * stride, oid_prefix); + + if (!cmp) + return mi; + + if (cmp > 0) + hi = mi; + else + lo = mi+1; + } + + return -((int)lo)-1; +} + +static int pack_entry_find_offset( + off64_t *offset_out, + git_oid *found_oid, + struct git_pack_file *p, + const git_oid *short_oid, + size_t len) +{ + const uint32_t *level1_ofs; + const unsigned char *index; + unsigned hi, lo, stride; + int pos, found = 0; + off64_t offset; + const unsigned char *current = 0; + int error = 0; + + *offset_out = 0; + + if (git_mutex_lock(&p->lock) < 0) + return packfile_error("failed to get lock for pack_entry_find_offset"); + + if ((error = pack_index_open_locked(p)) < 0) + goto cleanup; + + if (!p->index_map.data) { + git_error_set(GIT_ERROR_INTERNAL, "internal error: p->index_map.data == NULL"); + goto cleanup; + } + + index = p->index_map.data; + level1_ofs = p->index_map.data; + + if (p->index_version > 1) { + level1_ofs += 2; + index += 8; + } + + index += 4 * 256; + hi = ntohl(level1_ofs[(int)short_oid->id[0]]); + lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(level1_ofs[(int)short_oid->id[0] - 1])); + + if (p->index_version > 1) { + stride = 20; + } else { + stride = 24; + index += 4; + } + +#ifdef INDEX_DEBUG_LOOKUP + printf("%02x%02x%02x... lo %u hi %u nr %d\n", + short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects); +#endif + + pos = git_pack__lookup_sha1(index, stride, lo, hi, short_oid->id); + + if (pos >= 0) { + /* An object matching exactly the oid was found */ + found = 1; + current = index + pos * stride; + } else { + /* No object was found */ + /* pos refers to the object with the "closest" oid to short_oid */ + pos = - 1 - pos; + if (pos < (int)p->num_objects) { + current = index + pos * stride; + + if (!git_oid_ncmp(short_oid, (const git_oid *)current, len)) + found = 1; + } + } + + if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)p->num_objects) { + /* Check for ambiguousity */ + const unsigned char *next = current + stride; + + if (!git_oid_ncmp(short_oid, (const git_oid *)next, len)) { + found = 2; + } + } + + if (!found) { + error = git_odb__error_notfound("failed to find offset for pack entry", short_oid, len); + goto cleanup; + } + if (found > 1) { + error = git_odb__error_ambiguous("found multiple offsets for pack entry"); + goto cleanup; + } + + if ((offset = nth_packed_object_offset_locked(p, pos)) < 0) { + git_error_set(GIT_ERROR_ODB, "packfile index is corrupt"); + error = -1; + goto cleanup; + } + + *offset_out = offset; + git_oid_fromraw(found_oid, current); + +#ifdef INDEX_DEBUG_LOOKUP + { + unsigned char hex_sha1[GIT_OID_HEXSZ + 1]; + git_oid_fmt(hex_sha1, found_oid); + hex_sha1[GIT_OID_HEXSZ] = '\0'; + printf("found lo=%d %s\n", lo, hex_sha1); + } +#endif + +cleanup: + git_mutex_unlock(&p->lock); + return error; +} + +int git_pack_entry_find( + struct git_pack_entry *e, + struct git_pack_file *p, + const git_oid *short_oid, + size_t len) +{ + off64_t offset; + git_oid found_oid; + int error; + + GIT_ASSERT_ARG(p); + + if (len == GIT_OID_HEXSZ && p->num_bad_objects) { + unsigned i; + for (i = 0; i < p->num_bad_objects; i++) + if (git_oid__cmp(short_oid, &p->bad_object_sha1[i]) == 0) + return packfile_error("bad object found in packfile"); + } + + error = pack_entry_find_offset(&offset, &found_oid, p, short_oid, len); + if (error < 0) + return error; + + error = git_mutex_lock(&p->lock); + if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); + return error; + } + error = git_mutex_lock(&p->mwf.lock); + if (error < 0) { + git_mutex_unlock(&p->lock); + git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); + return error; + } + + /* we found a unique entry in the index; + * make sure the packfile backing the index + * still exists on disk */ + if (p->mwf.fd == -1) + error = packfile_open_locked(p); + git_mutex_unlock(&p->mwf.lock); + git_mutex_unlock(&p->lock); + if (error < 0) + return error; + + e->offset = offset; + e->p = p; + + git_oid_cpy(&e->sha1, &found_oid); + return 0; +} diff --git a/src/libgit2/pack.h b/src/libgit2/pack.h new file mode 100644 index 000000000..bf279c6b6 --- /dev/null +++ b/src/libgit2/pack.h @@ -0,0 +1,199 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_pack_h__ +#define INCLUDE_pack_h__ + +#include "common.h" + +#include "git2/oid.h" + +#include "array.h" +#include "map.h" +#include "mwindow.h" +#include "odb.h" +#include "offmap.h" +#include "oidmap.h" +#include "zstream.h" + +/** + * Function type for callbacks from git_pack_foreach_entry_offset. + */ +typedef int git_pack_foreach_entry_offset_cb( + const git_oid *id, + off64_t offset, + void *payload); + +#define GIT_PACK_FILE_MODE 0444 + +#define PACK_SIGNATURE 0x5041434b /* "PACK" */ +#define PACK_VERSION 2 +#define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3)) +struct git_pack_header { + uint32_t hdr_signature; + uint32_t hdr_version; + uint32_t hdr_entries; +}; + +/* + * The first four bytes of index formats later than version 1 should + * start with this signature, as all older git binaries would find this + * value illegal and abort reading the file. + * + * This is the case because the number of objects in a packfile + * cannot exceed 1,431,660,000 as every object would need at least + * 3 bytes of data and the overall packfile cannot exceed 4 GiB with + * version 1 of the index file due to the offsets limited to 32 bits. + * Clearly the signature exceeds this maximum. + * + * Very old git binaries will also compare the first 4 bytes to the + * next 4 bytes in the index and abort with a "non-monotonic index" + * error if the second 4 byte word is smaller than the first 4 + * byte word. This would be true in the proposed future index + * format as idx_signature would be greater than idx_version. + */ + +#define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */ + +struct git_pack_idx_header { + uint32_t idx_signature; + uint32_t idx_version; +}; + +typedef struct git_pack_cache_entry { + size_t last_usage; /* enough? */ + git_atomic32 refcount; + git_rawobj raw; +} git_pack_cache_entry; + +struct pack_chain_elem { + off64_t base_key; + off64_t offset; + size_t size; + git_object_t type; +}; + +typedef git_array_t(struct pack_chain_elem) git_dependency_chain; + +#define GIT_PACK_CACHE_MEMORY_LIMIT 16 * 1024 * 1024 +#define GIT_PACK_CACHE_SIZE_LIMIT 1024 * 1024 /* don't bother caching anything over 1MB */ + +typedef struct { + size_t memory_used; + size_t memory_limit; + size_t use_ctr; + git_mutex lock; + git_offmap *entries; +} git_pack_cache; + +struct git_pack_file { + git_mwindow_file mwf; + git_map index_map; + git_mutex lock; /* protect updates to index_map */ + git_atomic32 refcount; + + uint32_t num_objects; + uint32_t num_bad_objects; + git_oid *bad_object_sha1; /* array of git_oid */ + + int index_version; + git_time_t mtime; + unsigned pack_local:1, pack_keep:1, has_cache:1; + git_oidmap *idx_cache; + git_oid **oids; + + git_pack_cache bases; /* delta base cache */ + + time_t last_freshen; /* last time the packfile was freshened */ + + /* something like ".git/objects/pack/xxxxx.pack" */ + char pack_name[GIT_FLEX_ARRAY]; /* more */ +}; + +/** + * Return the position where an OID (or a prefix) would be inserted within the + * OID Lookup Table of an .idx file. This performs binary search between the lo + * and hi indices. + * + * The stride parameter is provided because .idx files version 1 store the OIDs + * interleaved with the 4-byte file offsets of the objects within the .pack + * file (stride = 24), whereas files with version 2 store them in a contiguous + * flat array (stride = 20). + */ +int git_pack__lookup_sha1(const void *oid_lookup_table, size_t stride, unsigned lo, + unsigned hi, const unsigned char *oid_prefix); + +struct git_pack_entry { + off64_t offset; + git_oid sha1; + struct git_pack_file *p; +}; + +typedef struct git_packfile_stream { + off64_t curpos; + int done; + git_zstream zstream; + struct git_pack_file *p; + git_mwindow *mw; +} git_packfile_stream; + +int git_packfile__object_header(size_t *out, unsigned char *hdr, size_t size, git_object_t type); + +int git_packfile__name(char **out, const char *path); + +int git_packfile_unpack_header( + size_t *size_p, + git_object_t *type_p, + struct git_pack_file *p, + git_mwindow **w_curs, + off64_t *curpos); + +int git_packfile_resolve_header( + size_t *size_p, + git_object_t *type_p, + struct git_pack_file *p, + off64_t offset); + +int git_packfile_unpack(git_rawobj *obj, struct git_pack_file *p, off64_t *obj_offset); + +int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, off64_t curpos); +ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len); +void git_packfile_stream_dispose(git_packfile_stream *obj); + +int get_delta_base( + off64_t *delta_base_out, + struct git_pack_file *p, + git_mwindow **w_curs, + off64_t *curpos, + git_object_t type, + off64_t delta_obj_offset); + +void git_packfile_free(struct git_pack_file *p, bool unlink_packfile); +int git_packfile_alloc(struct git_pack_file **pack_out, const char *path); + +int git_pack_entry_find( + struct git_pack_entry *e, + struct git_pack_file *p, + const git_oid *short_oid, + size_t len); +int git_pack_foreach_entry( + struct git_pack_file *p, + git_odb_foreach_cb cb, + void *data); +/** + * Similar to git_pack_foreach_entry, but: + * - It also provides the offset of the object within the + * packfile. + * - It does not sort the objects in any order. + * - It retains the lock while invoking the callback. + */ +int git_pack_foreach_entry_offset( + struct git_pack_file *p, + git_pack_foreach_entry_offset_cb cb, + void *data); + +#endif diff --git a/src/libgit2/parse.c b/src/libgit2/parse.c new file mode 100644 index 000000000..0a10758bf --- /dev/null +++ b/src/libgit2/parse.c @@ -0,0 +1,134 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "parse.h" + +int git_parse_ctx_init(git_parse_ctx *ctx, const char *content, size_t content_len) +{ + if (content && content_len) { + ctx->content = content; + ctx->content_len = content_len; + } else { + ctx->content = ""; + ctx->content_len = 0; + } + + ctx->remain = ctx->content; + ctx->remain_len = ctx->content_len; + ctx->line = ctx->remain; + ctx->line_len = git__linenlen(ctx->line, ctx->remain_len); + ctx->line_num = 1; + + return 0; +} + +void git_parse_ctx_clear(git_parse_ctx *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->content = ""; +} + +void git_parse_advance_line(git_parse_ctx *ctx) +{ + ctx->line += ctx->line_len; + ctx->remain_len -= ctx->line_len; + ctx->line_len = git__linenlen(ctx->line, ctx->remain_len); + ctx->line_num++; +} + +void git_parse_advance_chars(git_parse_ctx *ctx, size_t char_cnt) +{ + ctx->line += char_cnt; + ctx->remain_len -= char_cnt; + ctx->line_len -= char_cnt; +} + +int git_parse_advance_expected( + git_parse_ctx *ctx, + const char *expected, + size_t expected_len) +{ + if (ctx->line_len < expected_len) + return -1; + + if (memcmp(ctx->line, expected, expected_len) != 0) + return -1; + + git_parse_advance_chars(ctx, expected_len); + return 0; +} + +int git_parse_advance_ws(git_parse_ctx *ctx) +{ + int ret = -1; + + while (ctx->line_len > 0 && + ctx->line[0] != '\n' && + git__isspace(ctx->line[0])) { + ctx->line++; + ctx->line_len--; + ctx->remain_len--; + ret = 0; + } + + return ret; +} + +int git_parse_advance_nl(git_parse_ctx *ctx) +{ + if (ctx->line_len != 1 || ctx->line[0] != '\n') + return -1; + + git_parse_advance_line(ctx); + return 0; +} + +int git_parse_advance_digit(int64_t *out, git_parse_ctx *ctx, int base) +{ + const char *end; + int ret; + + if (ctx->line_len < 1 || !git__isdigit(ctx->line[0])) + return -1; + + if ((ret = git__strntol64(out, ctx->line, ctx->line_len, &end, base)) < 0) + return -1; + + git_parse_advance_chars(ctx, (end - ctx->line)); + return 0; +} + +int git_parse_advance_oid(git_oid *out, git_parse_ctx *ctx) +{ + if (ctx->line_len < GIT_OID_HEXSZ) + return -1; + if ((git_oid_fromstrn(out, ctx->line, GIT_OID_HEXSZ)) < 0) + return -1; + git_parse_advance_chars(ctx, GIT_OID_HEXSZ); + return 0; +} + +int git_parse_peek(char *out, git_parse_ctx *ctx, int flags) +{ + size_t remain = ctx->line_len; + const char *ptr = ctx->line; + + while (remain) { + char c = *ptr; + + if ((flags & GIT_PARSE_PEEK_SKIP_WHITESPACE) && + git__isspace(c)) { + remain--; + ptr++; + continue; + } + + *out = c; + return 0; + } + + return -1; +} diff --git a/src/libgit2/parse.h b/src/libgit2/parse.h new file mode 100644 index 000000000..0ecb7c103 --- /dev/null +++ b/src/libgit2/parse.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_parse_h__ +#define INCLUDE_parse_h__ + +#include "common.h" + +typedef struct { + /* Original content buffer */ + const char *content; + size_t content_len; + + /* The remaining (unparsed) buffer */ + const char *remain; + size_t remain_len; + + const char *line; + size_t line_len; + size_t line_num; +} git_parse_ctx; + +#define GIT_PARSE_CTX_INIT { 0 } + +int git_parse_ctx_init(git_parse_ctx *ctx, const char *content, size_t content_len); +void git_parse_ctx_clear(git_parse_ctx *ctx); + +#define git_parse_ctx_contains_s(ctx, str) \ + git_parse_ctx_contains(ctx, str, sizeof(str) - 1) + +GIT_INLINE(bool) git_parse_ctx_contains( + git_parse_ctx *ctx, const char *str, size_t len) +{ + return (ctx->line_len >= len && memcmp(ctx->line, str, len) == 0); +} + +void git_parse_advance_line(git_parse_ctx *ctx); +void git_parse_advance_chars(git_parse_ctx *ctx, size_t char_cnt); +int git_parse_advance_expected( + git_parse_ctx *ctx, + const char *expected, + size_t expected_len); + +#define git_parse_advance_expected_str(ctx, str) \ + git_parse_advance_expected(ctx, str, strlen(str)) + +int git_parse_advance_ws(git_parse_ctx *ctx); +int git_parse_advance_nl(git_parse_ctx *ctx); +int git_parse_advance_digit(int64_t *out, git_parse_ctx *ctx, int base); +int git_parse_advance_oid(git_oid *out, git_parse_ctx *ctx); + +enum GIT_PARSE_PEEK_FLAGS { + GIT_PARSE_PEEK_SKIP_WHITESPACE = (1 << 0) +}; + +int git_parse_peek(char *out, git_parse_ctx *ctx, int flags); + +#endif diff --git a/src/libgit2/patch.c b/src/libgit2/patch.c new file mode 100644 index 000000000..a30546f3c --- /dev/null +++ b/src/libgit2/patch.c @@ -0,0 +1,230 @@ +/* +* Copyright (C) the libgit2 contributors. All rights reserved. +* +* This file is part of libgit2, distributed under the GNU GPL v2 with +* a Linking Exception. For full terms see the included COPYING file. +*/ + +#include "patch.h" + +#include "git2/patch.h" +#include "diff.h" + +int git_patch__invoke_callbacks( + git_patch *patch, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb line_cb, + void *payload) +{ + int error = 0; + uint32_t i, j; + + if (file_cb) + error = file_cb(patch->delta, 0, payload); + + if (error) + return error; + + if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { + if (binary_cb) + error = binary_cb(patch->delta, &patch->binary, payload); + + return error; + } + + if (!hunk_cb && !line_cb) + return error; + + for (i = 0; !error && i < git_array_size(patch->hunks); ++i) { + git_patch_hunk *h = git_array_get(patch->hunks, i); + + if (hunk_cb) + error = hunk_cb(patch->delta, &h->hunk, payload); + + if (!line_cb) + continue; + + for (j = 0; !error && j < h->line_count; ++j) { + git_diff_line *l = + git_array_get(patch->lines, h->line_start + j); + + error = line_cb(patch->delta, &h->hunk, l, payload); + } + } + + return error; +} + +size_t git_patch_size( + git_patch *patch, + int include_context, + int include_hunk_headers, + int include_file_headers) +{ + size_t out; + + GIT_ASSERT_ARG(patch); + + out = patch->content_size; + + if (!include_context) + out -= patch->context_size; + + if (include_hunk_headers) + out += patch->header_size; + + if (include_file_headers) { + git_str file_header = GIT_STR_INIT; + + if (git_diff_delta__format_file_header( + &file_header, patch->delta, NULL, NULL, 0, true) < 0) + git_error_clear(); + else + out += git_str_len(&file_header); + + git_str_dispose(&file_header); + } + + return out; +} + +int git_patch_line_stats( + size_t *total_ctxt, + size_t *total_adds, + size_t *total_dels, + const git_patch *patch) +{ + size_t totals[3], idx; + + memset(totals, 0, sizeof(totals)); + + for (idx = 0; idx < git_array_size(patch->lines); ++idx) { + git_diff_line *line = git_array_get(patch->lines, idx); + if (!line) + continue; + + switch (line->origin) { + case GIT_DIFF_LINE_CONTEXT: totals[0]++; break; + case GIT_DIFF_LINE_ADDITION: totals[1]++; break; + case GIT_DIFF_LINE_DELETION: totals[2]++; break; + default: + /* diff --stat and --numstat don't count EOFNL marks because + * they will always be paired with a ADDITION or DELETION line. + */ + break; + } + } + + if (total_ctxt) + *total_ctxt = totals[0]; + if (total_adds) + *total_adds = totals[1]; + if (total_dels) + *total_dels = totals[2]; + + return 0; +} + +const git_diff_delta *git_patch_get_delta(const git_patch *patch) +{ + GIT_ASSERT_ARG_WITH_RETVAL(patch, NULL); + return patch->delta; +} + +size_t git_patch_num_hunks(const git_patch *patch) +{ + GIT_ASSERT_ARG(patch); + return git_array_size(patch->hunks); +} + +static int patch_error_outofrange(const char *thing) +{ + git_error_set(GIT_ERROR_INVALID, "patch %s index out of range", thing); + return GIT_ENOTFOUND; +} + +int git_patch_get_hunk( + const git_diff_hunk **out, + size_t *lines_in_hunk, + git_patch *patch, + size_t hunk_idx) +{ + git_patch_hunk *hunk; + GIT_ASSERT_ARG(patch); + + hunk = git_array_get(patch->hunks, hunk_idx); + + if (!hunk) { + if (out) *out = NULL; + if (lines_in_hunk) *lines_in_hunk = 0; + return patch_error_outofrange("hunk"); + } + + if (out) *out = &hunk->hunk; + if (lines_in_hunk) *lines_in_hunk = hunk->line_count; + return 0; +} + +int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx) +{ + git_patch_hunk *hunk; + GIT_ASSERT_ARG(patch); + + if (!(hunk = git_array_get(patch->hunks, hunk_idx))) + return patch_error_outofrange("hunk"); + return (int)hunk->line_count; +} + +int git_patch_get_line_in_hunk( + const git_diff_line **out, + git_patch *patch, + size_t hunk_idx, + size_t line_of_hunk) +{ + git_patch_hunk *hunk; + git_diff_line *line; + + GIT_ASSERT_ARG(patch); + + if (!(hunk = git_array_get(patch->hunks, hunk_idx))) { + if (out) *out = NULL; + return patch_error_outofrange("hunk"); + } + + if (line_of_hunk >= hunk->line_count || + !(line = git_array_get( + patch->lines, hunk->line_start + line_of_hunk))) { + if (out) *out = NULL; + return patch_error_outofrange("line"); + } + + if (out) *out = line; + return 0; +} + +git_repository *git_patch_owner(const git_patch *patch) +{ + return patch->repo; +} + +int git_patch_from_diff(git_patch **out, git_diff *diff, size_t idx) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diff); + GIT_ASSERT_ARG(diff->patch_fn); + return diff->patch_fn(out, diff, idx); +} + +static void git_patch__free(git_patch *patch) +{ + if (patch->free_fn) + patch->free_fn(patch); +} + +void git_patch_free(git_patch *patch) +{ + if (patch) + GIT_REFCOUNT_DEC(patch, git_patch__free); +} diff --git a/src/libgit2/patch.h b/src/libgit2/patch.h new file mode 100644 index 000000000..1e1471ed6 --- /dev/null +++ b/src/libgit2/patch.h @@ -0,0 +1,69 @@ +/* +* Copyright (C) the libgit2 contributors. All rights reserved. +* +* This file is part of libgit2, distributed under the GNU GPL v2 with +* a Linking Exception. For full terms see the included COPYING file. +*/ +#ifndef INCLUDE_patch_h__ +#define INCLUDE_patch_h__ + +#include "common.h" + +#include "git2/patch.h" +#include "array.h" + +/* cached information about a hunk in a patch */ +typedef struct git_patch_hunk { + git_diff_hunk hunk; + size_t line_start; + size_t line_count; +} git_patch_hunk; + +struct git_patch { + git_refcount rc; + + git_repository *repo; /* may be null */ + + git_diff_options diff_opts; + + git_diff_delta *delta; + git_diff_binary binary; + git_array_t(git_patch_hunk) hunks; + git_array_t(git_diff_line) lines; + + size_t header_size; + size_t content_size; + size_t context_size; + + void (*free_fn)(git_patch *patch); +}; + +extern int git_patch__invoke_callbacks( + git_patch *patch, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb line_cb, + void *payload); + +extern int git_patch_line_stats( + size_t *total_ctxt, + size_t *total_adds, + size_t *total_dels, + const git_patch *patch); + +/** Options for parsing patch files. */ +typedef struct { + /** + * The length of the prefix (in path segments) for the filenames. + * This prefix will be removed when looking for files. The default is 1. + */ + uint32_t prefix_len; +} git_patch_options; + +#define GIT_PATCH_OPTIONS_INIT { 1 } + +extern int git_patch__to_buf(git_str *out, git_patch *patch); +extern void git_patch_free(git_patch *patch); + +#endif diff --git a/src/libgit2/patch_generate.c b/src/libgit2/patch_generate.c new file mode 100644 index 000000000..bc598fea8 --- /dev/null +++ b/src/libgit2/patch_generate.c @@ -0,0 +1,915 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "patch_generate.h" + +#include "git2/blob.h" +#include "diff.h" +#include "diff_generate.h" +#include "diff_file.h" +#include "diff_driver.h" +#include "diff_xdiff.h" +#include "delta.h" +#include "zstream.h" +#include "futils.h" + +static void diff_output_init( + git_patch_generated_output *, const git_diff_options *, git_diff_file_cb, + git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*); + +static void diff_output_to_patch( + git_patch_generated_output *, git_patch_generated *); + +static void patch_generated_free(git_patch *p) +{ + git_patch_generated *patch = (git_patch_generated *)p; + + git_array_clear(patch->base.lines); + git_array_clear(patch->base.hunks); + + git__free((char *)patch->base.binary.old_file.data); + git__free((char *)patch->base.binary.new_file.data); + + git_diff_file_content__clear(&patch->ofile); + git_diff_file_content__clear(&patch->nfile); + + git_diff_free(patch->diff); /* decrements refcount */ + patch->diff = NULL; + + git_pool_clear(&patch->flattened); + + git__free((char *)patch->base.diff_opts.old_prefix); + git__free((char *)patch->base.diff_opts.new_prefix); + + if (patch->flags & GIT_PATCH_GENERATED_ALLOCATED) + git__free(patch); +} + +static void patch_generated_update_binary(git_patch_generated *patch) +{ + if ((patch->base.delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) + return; + + if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 || + (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) + patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; + + else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE || + patch->nfile.file->size > GIT_XDIFF_MAX_SIZE) + patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; + + else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 && + (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0) + patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; +} + +static void patch_generated_init_common(git_patch_generated *patch) +{ + patch->base.free_fn = patch_generated_free; + + patch_generated_update_binary(patch); + + patch->flags |= GIT_PATCH_GENERATED_INITIALIZED; + + if (patch->diff) + git_diff_addref(patch->diff); +} + +static int patch_generated_normalize_options( + git_diff_options *out, + const git_diff_options *opts) +{ + if (opts) { + GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); + memcpy(out, opts, sizeof(git_diff_options)); + } else { + git_diff_options default_opts = GIT_DIFF_OPTIONS_INIT; + memcpy(out, &default_opts, sizeof(git_diff_options)); + } + + out->old_prefix = opts && opts->old_prefix ? + git__strdup(opts->old_prefix) : + git__strdup(DIFF_OLD_PREFIX_DEFAULT); + + out->new_prefix = opts && opts->new_prefix ? + git__strdup(opts->new_prefix) : + git__strdup(DIFF_NEW_PREFIX_DEFAULT); + + GIT_ERROR_CHECK_ALLOC(out->old_prefix); + GIT_ERROR_CHECK_ALLOC(out->new_prefix); + + return 0; +} + +static int patch_generated_init( + git_patch_generated *patch, git_diff *diff, size_t delta_index) +{ + int error = 0; + + memset(patch, 0, sizeof(*patch)); + + patch->diff = diff; + patch->base.repo = diff->repo; + patch->base.delta = git_vector_get(&diff->deltas, delta_index); + patch->delta_index = delta_index; + + if ((error = patch_generated_normalize_options( + &patch->base.diff_opts, &diff->opts)) < 0 || + (error = git_diff_file_content__init_from_diff( + &patch->ofile, diff, patch->base.delta, true)) < 0 || + (error = git_diff_file_content__init_from_diff( + &patch->nfile, diff, patch->base.delta, false)) < 0) + return error; + + patch_generated_init_common(patch); + + return 0; +} + +static int patch_generated_alloc_from_diff( + git_patch_generated **out, git_diff *diff, size_t delta_index) +{ + int error; + git_patch_generated *patch = git__calloc(1, sizeof(git_patch_generated)); + GIT_ERROR_CHECK_ALLOC(patch); + + if (!(error = patch_generated_init(patch, diff, delta_index))) { + patch->flags |= GIT_PATCH_GENERATED_ALLOCATED; + GIT_REFCOUNT_INC(&patch->base); + } else { + git__free(patch); + patch = NULL; + } + + *out = patch; + return error; +} + +GIT_INLINE(bool) should_skip_binary(git_patch_generated *patch, git_diff_file *file) +{ + if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0) + return false; + + return (file->flags & GIT_DIFF_FLAG_BINARY) != 0; +} + +static bool patch_generated_diffable(git_patch_generated *patch) +{ + size_t olen, nlen; + + if (patch->base.delta->status == GIT_DELTA_UNMODIFIED) + return false; + + /* if we've determined this to be binary (and we are not showing binary + * data) then we have skipped loading the map data. instead, query the + * file data itself. + */ + if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 && + (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) { + olen = (size_t)patch->ofile.file->size; + nlen = (size_t)patch->nfile.file->size; + } else { + olen = patch->ofile.map.len; + nlen = patch->nfile.map.len; + } + + /* if both sides are empty, files are identical */ + if (!olen && !nlen) + return false; + + /* otherwise, check the file sizes and the oid */ + return (olen != nlen || + !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id)); +} + +static int patch_generated_load(git_patch_generated *patch, git_patch_generated_output *output) +{ + int error = 0; + bool incomplete_data; + + if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0) + return 0; + + /* if no hunk and data callbacks and user doesn't care if data looks + * binary, then there is no need to actually load the data + */ + if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 && + output && !output->binary_cb && !output->hunk_cb && !output->data_cb) + return 0; + + incomplete_data = + (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || + (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) && + ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || + (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0)); + + if ((error = git_diff_file_content__load( + &patch->ofile, &patch->base.diff_opts)) < 0 || + (error = git_diff_file_content__load( + &patch->nfile, &patch->base.diff_opts)) < 0 || + should_skip_binary(patch, patch->nfile.file)) + goto cleanup; + + /* if previously missing an oid, and now that we have it the two sides + * are the same (and not submodules), update MODIFIED -> UNMODIFIED + */ + if (incomplete_data && + patch->ofile.file->mode == patch->nfile.file->mode && + patch->ofile.file->mode != GIT_FILEMODE_COMMIT && + git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) && + patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ + patch->base.delta->status = GIT_DELTA_UNMODIFIED; + +cleanup: + patch_generated_update_binary(patch); + + if (!error) { + if (patch_generated_diffable(patch)) + patch->flags |= GIT_PATCH_GENERATED_DIFFABLE; + + patch->flags |= GIT_PATCH_GENERATED_LOADED; + } + + return error; +} + +static int patch_generated_invoke_file_callback( + git_patch_generated *patch, git_patch_generated_output *output) +{ + float progress = patch->diff ? + ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f; + + if (!output->file_cb) + return 0; + + return git_error_set_after_callback_function( + output->file_cb(patch->base.delta, progress, output->payload), + "git_patch"); +} + +static int create_binary( + git_diff_binary_t *out_type, + char **out_data, + size_t *out_datalen, + size_t *out_inflatedlen, + const char *a_data, + size_t a_datalen, + const char *b_data, + size_t b_datalen) +{ + git_str deflate = GIT_STR_INIT, delta = GIT_STR_INIT; + size_t delta_data_len = 0; + int error; + + /* The git_delta function accepts unsigned long only */ + if (!git__is_ulong(a_datalen) || !git__is_ulong(b_datalen)) + return GIT_EBUFS; + + if ((error = git_zstream_deflatebuf(&deflate, b_data, b_datalen)) < 0) + goto done; + + /* The git_delta function accepts unsigned long only */ + if (!git__is_ulong(deflate.size)) { + error = GIT_EBUFS; + goto done; + } + + if (a_datalen && b_datalen) { + void *delta_data; + + error = git_delta(&delta_data, &delta_data_len, + a_data, a_datalen, + b_data, b_datalen, + deflate.size); + + if (error == 0) { + error = git_zstream_deflatebuf( + &delta, delta_data, delta_data_len); + + git__free(delta_data); + } else if (error == GIT_EBUFS) { + error = 0; + } + + if (error < 0) + goto done; + } + + if (delta.size && delta.size < deflate.size) { + *out_type = GIT_DIFF_BINARY_DELTA; + *out_datalen = delta.size; + *out_data = git_str_detach(&delta); + *out_inflatedlen = delta_data_len; + } else { + *out_type = GIT_DIFF_BINARY_LITERAL; + *out_datalen = deflate.size; + *out_data = git_str_detach(&deflate); + *out_inflatedlen = b_datalen; + } + +done: + git_str_dispose(&deflate); + git_str_dispose(&delta); + + return error; +} + +static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch) +{ + git_diff_binary binary = {0}; + const char *old_data = patch->ofile.map.data; + const char *new_data = patch->nfile.map.data; + size_t old_len = patch->ofile.map.len, + new_len = patch->nfile.map.len; + int error; + + /* Only load contents if the user actually wants to diff + * binary files. */ + if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) { + binary.contains_data = 1; + + /* Create the old->new delta (as the "new" side of the patch), + * and the new->old delta (as the "old" side) + */ + if ((error = create_binary(&binary.old_file.type, + (char **)&binary.old_file.data, + &binary.old_file.datalen, + &binary.old_file.inflatedlen, + new_data, new_len, old_data, old_len)) < 0 || + (error = create_binary(&binary.new_file.type, + (char **)&binary.new_file.data, + &binary.new_file.datalen, + &binary.new_file.inflatedlen, + old_data, old_len, new_data, new_len)) < 0) + return error; + } + + error = git_error_set_after_callback_function( + output->binary_cb(patch->base.delta, &binary, output->payload), + "git_patch"); + + git__free((char *) binary.old_file.data); + git__free((char *) binary.new_file.data); + + return error; +} + +static int patch_generated_create( + git_patch_generated *patch, + git_patch_generated_output *output) +{ + int error = 0; + + if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0) + return 0; + + /* if we are not looking at the binary or text data, don't do the diff */ + if (!output->binary_cb && !output->hunk_cb && !output->data_cb) + return 0; + + if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 && + (error = patch_generated_load(patch, output)) < 0) + return error; + + if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0) + return 0; + + if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { + if (output->binary_cb) + error = diff_binary(output, patch); + } + else { + if (output->diff_cb) + error = output->diff_cb(output, patch); + } + + patch->flags |= GIT_PATCH_GENERATED_DIFFED; + return error; +} + +static int diff_required(git_diff *diff, const char *action) +{ + if (diff) + return 0; + git_error_set(GIT_ERROR_INVALID, "must provide valid diff to %s", action); + return -1; +} + +typedef struct { + git_patch_generated patch; + git_diff_delta delta; + char paths[GIT_FLEX_ARRAY]; +} patch_generated_with_delta; + +static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo) +{ + int error = 0; + git_patch_generated *patch = &pd->patch; + bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); + bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); + + pd->delta.status = has_new ? + (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : + (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); + + if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id)) + pd->delta.status = GIT_DELTA_UNMODIFIED; + + patch->base.delta = &pd->delta; + + patch_generated_init_common(patch); + + if (pd->delta.status == GIT_DELTA_UNMODIFIED && + !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) { + + /* Even empty patches are flagged as binary, and even though + * there's no difference, we flag this as "containing data" + * (the data is known to be empty, as opposed to wholly unknown). + */ + if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) + patch->base.binary.contains_data = 1; + + return error; + } + + error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo); + + if (!error) + error = patch_generated_create(patch, (git_patch_generated_output *)xo); + + return error; +} + +static int patch_generated_from_sources( + patch_generated_with_delta *pd, + git_xdiff_output *xo, + git_diff_file_content_src *oldsrc, + git_diff_file_content_src *newsrc, + const git_diff_options *opts) +{ + int error = 0; + git_repository *repo = + oldsrc->blob ? git_blob_owner(oldsrc->blob) : + newsrc->blob ? git_blob_owner(newsrc->blob) : NULL; + git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file; + git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile; + + if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0) + return error; + + if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { + void *tmp = lfile; lfile = rfile; rfile = tmp; + tmp = ldata; ldata = rdata; rdata = tmp; + } + + pd->patch.base.delta = &pd->delta; + + if (!oldsrc->as_path) { + if (newsrc->as_path) + oldsrc->as_path = newsrc->as_path; + else + oldsrc->as_path = newsrc->as_path = "file"; + } + else if (!newsrc->as_path) + newsrc->as_path = oldsrc->as_path; + + lfile->path = oldsrc->as_path; + rfile->path = newsrc->as_path; + + if ((error = git_diff_file_content__init_from_src( + ldata, repo, opts, oldsrc, lfile)) < 0 || + (error = git_diff_file_content__init_from_src( + rdata, repo, opts, newsrc, rfile)) < 0) + return error; + + return diff_single_generate(pd, xo); +} + +static int patch_generated_with_delta_alloc( + patch_generated_with_delta **out, + const char **old_path, + const char **new_path) +{ + patch_generated_with_delta *pd; + size_t old_len = *old_path ? strlen(*old_path) : 0; + size_t new_len = *new_path ? strlen(*new_path) : 0; + size_t alloc_len; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); + + *out = pd = git__calloc(1, alloc_len); + GIT_ERROR_CHECK_ALLOC(pd); + + pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED; + + if (*old_path) { + memcpy(&pd->paths[0], *old_path, old_len); + *old_path = &pd->paths[0]; + } else if (*new_path) + *old_path = &pd->paths[old_len + 1]; + + if (*new_path) { + memcpy(&pd->paths[old_len + 1], *new_path, new_len); + *new_path = &pd->paths[old_len + 1]; + } else if (*old_path) + *new_path = &pd->paths[0]; + + return 0; +} + +static int diff_from_sources( + git_diff_file_content_src *oldsrc, + git_diff_file_content_src *newsrc, + const git_diff_options *opts, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb data_cb, + void *payload) +{ + int error = 0; + patch_generated_with_delta pd; + git_xdiff_output xo; + + memset(&xo, 0, sizeof(xo)); + diff_output_init( + &xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); + git_xdiff_init(&xo, opts); + + memset(&pd, 0, sizeof(pd)); + + error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts); + + git_patch_free(&pd.patch.base); + + return error; +} + +static int patch_from_sources( + git_patch **out, + git_diff_file_content_src *oldsrc, + git_diff_file_content_src *newsrc, + const git_diff_options *opts) +{ + int error = 0; + patch_generated_with_delta *pd; + git_xdiff_output xo; + + GIT_ASSERT_ARG(out); + *out = NULL; + + if ((error = patch_generated_with_delta_alloc( + &pd, &oldsrc->as_path, &newsrc->as_path)) < 0) + return error; + + memset(&xo, 0, sizeof(xo)); + diff_output_to_patch(&xo.output, &pd->patch); + git_xdiff_init(&xo, opts); + + if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts))) + *out = (git_patch *)pd; + else + git_patch_free((git_patch *)pd); + + return error; +} + +int git_diff_blobs( + const git_blob *old_blob, + const char *old_path, + const git_blob *new_blob, + const char *new_path, + const git_diff_options *opts, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb data_cb, + void *payload) +{ + git_diff_file_content_src osrc = + GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); + git_diff_file_content_src nsrc = + GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path); + return diff_from_sources( + &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); +} + +int git_patch_from_blobs( + git_patch **out, + const git_blob *old_blob, + const char *old_path, + const git_blob *new_blob, + const char *new_path, + const git_diff_options *opts) +{ + git_diff_file_content_src osrc = + GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); + git_diff_file_content_src nsrc = + GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path); + return patch_from_sources(out, &osrc, &nsrc, opts); +} + +int git_diff_blob_to_buffer( + const git_blob *old_blob, + const char *old_path, + const char *buf, + size_t buflen, + const char *buf_path, + const git_diff_options *opts, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb data_cb, + void *payload) +{ + git_diff_file_content_src osrc = + GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); + git_diff_file_content_src nsrc = + GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path); + return diff_from_sources( + &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); +} + +int git_patch_from_blob_and_buffer( + git_patch **out, + const git_blob *old_blob, + const char *old_path, + const void *buf, + size_t buflen, + const char *buf_path, + const git_diff_options *opts) +{ + git_diff_file_content_src osrc = + GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); + git_diff_file_content_src nsrc = + GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path); + return patch_from_sources(out, &osrc, &nsrc, opts); +} + +int git_diff_buffers( + const void *old_buf, + size_t old_len, + const char *old_path, + const void *new_buf, + size_t new_len, + const char *new_path, + const git_diff_options *opts, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb data_cb, + void *payload) +{ + git_diff_file_content_src osrc = + GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path); + git_diff_file_content_src nsrc = + GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path); + return diff_from_sources( + &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); +} + +int git_patch_from_buffers( + git_patch **out, + const void *old_buf, + size_t old_len, + const char *old_path, + const void *new_buf, + size_t new_len, + const char *new_path, + const git_diff_options *opts) +{ + git_diff_file_content_src osrc = + GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path); + git_diff_file_content_src nsrc = + GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path); + return patch_from_sources(out, &osrc, &nsrc, opts); +} + +int git_patch_generated_from_diff( + git_patch **patch_ptr, git_diff *diff, size_t idx) +{ + int error = 0; + git_xdiff_output xo; + git_diff_delta *delta = NULL; + git_patch_generated *patch = NULL; + + if (patch_ptr) *patch_ptr = NULL; + + if (diff_required(diff, "git_patch_from_diff") < 0) + return -1; + + delta = git_vector_get(&diff->deltas, idx); + if (!delta) { + git_error_set(GIT_ERROR_INVALID, "index out of range for delta in diff"); + return GIT_ENOTFOUND; + } + + if (git_diff_delta__should_skip(&diff->opts, delta)) + return 0; + + /* don't load the patch data unless we need it for binary check */ + if (!patch_ptr && + ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 || + (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)) + return 0; + + if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0) + return error; + + memset(&xo, 0, sizeof(xo)); + diff_output_to_patch(&xo.output, patch); + git_xdiff_init(&xo, &diff->opts); + + error = patch_generated_invoke_file_callback(patch, &xo.output); + + if (!error) + error = patch_generated_create(patch, &xo.output); + + if (!error) { + /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */ + /* TODO: and unload the file content */ + } + + if (error || !patch_ptr) + git_patch_free(&patch->base); + else + *patch_ptr = &patch->base; + + return error; +} + +git_diff_driver *git_patch_generated_driver(git_patch_generated *patch) +{ + /* ofile driver is representative for whole patch */ + return patch->ofile.driver; +} + +int git_patch_generated_old_data( + char **ptr, long *len, git_patch_generated *patch) +{ + if (patch->ofile.map.len > LONG_MAX || + patch->ofile.map.len > GIT_XDIFF_MAX_SIZE) { + git_error_set(GIT_ERROR_INVALID, "files too large for diff"); + return -1; + } + + *ptr = patch->ofile.map.data; + *len = (long)patch->ofile.map.len; + + return 0; +} + +int git_patch_generated_new_data( + char **ptr, long *len, git_patch_generated *patch) +{ + if (patch->ofile.map.len > LONG_MAX || + patch->ofile.map.len > GIT_XDIFF_MAX_SIZE) { + git_error_set(GIT_ERROR_INVALID, "files too large for diff"); + return -1; + } + + *ptr = patch->nfile.map.data; + *len = (long)patch->nfile.map.len; + + return 0; +} + +static int patch_generated_file_cb( + const git_diff_delta *delta, + float progress, + void *payload) +{ + GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload); + return 0; +} + +static int patch_generated_binary_cb( + const git_diff_delta *delta, + const git_diff_binary *binary, + void *payload) +{ + git_patch *patch = payload; + + GIT_UNUSED(delta); + + memcpy(&patch->binary, binary, sizeof(git_diff_binary)); + + if (binary->old_file.data) { + patch->binary.old_file.data = git__malloc(binary->old_file.datalen); + GIT_ERROR_CHECK_ALLOC(patch->binary.old_file.data); + + memcpy((char *)patch->binary.old_file.data, + binary->old_file.data, binary->old_file.datalen); + } + + if (binary->new_file.data) { + patch->binary.new_file.data = git__malloc(binary->new_file.datalen); + GIT_ERROR_CHECK_ALLOC(patch->binary.new_file.data); + + memcpy((char *)patch->binary.new_file.data, + binary->new_file.data, binary->new_file.datalen); + } + + return 0; +} + +static int git_patch_hunk_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk_, + void *payload) +{ + git_patch_generated *patch = payload; + git_patch_hunk *hunk; + + GIT_UNUSED(delta); + + hunk = git_array_alloc(patch->base.hunks); + GIT_ERROR_CHECK_ALLOC(hunk); + + memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk)); + + patch->base.header_size += hunk_->header_len; + + hunk->line_start = git_array_size(patch->base.lines); + hunk->line_count = 0; + + return 0; +} + +static int patch_generated_line_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk_, + const git_diff_line *line_, + void *payload) +{ + git_patch_generated *patch = payload; + git_patch_hunk *hunk; + git_diff_line *line; + + GIT_UNUSED(delta); + GIT_UNUSED(hunk_); + + hunk = git_array_last(patch->base.hunks); + GIT_ASSERT(hunk); /* programmer error if no hunk is available */ + + line = git_array_alloc(patch->base.lines); + GIT_ERROR_CHECK_ALLOC(line); + + memcpy(line, line_, sizeof(*line)); + + /* do some bookkeeping so we can provide old/new line numbers */ + + patch->base.content_size += line->content_len; + + if (line->origin == GIT_DIFF_LINE_ADDITION || + line->origin == GIT_DIFF_LINE_DELETION) + patch->base.content_size += 1; + else if (line->origin == GIT_DIFF_LINE_CONTEXT) { + patch->base.content_size += 1; + patch->base.context_size += line->content_len + 1; + } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) + patch->base.context_size += line->content_len; + + hunk->line_count++; + + return 0; +} + +static void diff_output_init( + git_patch_generated_output *out, + const git_diff_options *opts, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb data_cb, + void *payload) +{ + GIT_UNUSED(opts); + + memset(out, 0, sizeof(*out)); + + out->file_cb = file_cb; + out->binary_cb = binary_cb; + out->hunk_cb = hunk_cb; + out->data_cb = data_cb; + out->payload = payload; +} + +static void diff_output_to_patch( + git_patch_generated_output *out, git_patch_generated *patch) +{ + diff_output_init( + out, + NULL, + patch_generated_file_cb, + patch_generated_binary_cb, + git_patch_hunk_cb, + patch_generated_line_cb, + patch); +} diff --git a/src/libgit2/patch_generate.h b/src/libgit2/patch_generate.h new file mode 100644 index 000000000..56e3e9df4 --- /dev/null +++ b/src/libgit2/patch_generate.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_patch_generate_h__ +#define INCLUDE_patch_generate_h__ + +#include "common.h" + +#include "diff.h" +#include "diff_file.h" +#include "patch.h" + +enum { + GIT_PATCH_GENERATED_ALLOCATED = (1 << 0), + GIT_PATCH_GENERATED_INITIALIZED = (1 << 1), + GIT_PATCH_GENERATED_LOADED = (1 << 2), + /* the two sides are different */ + GIT_PATCH_GENERATED_DIFFABLE = (1 << 3), + /* the difference between the two sides has been computed */ + GIT_PATCH_GENERATED_DIFFED = (1 << 4), + GIT_PATCH_GENERATED_FLATTENED = (1 << 5) +}; + +struct git_patch_generated { + struct git_patch base; + + git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */ + size_t delta_index; + git_diff_file_content ofile; + git_diff_file_content nfile; + uint32_t flags; + git_pool flattened; +}; + +typedef struct git_patch_generated git_patch_generated; + +extern git_diff_driver *git_patch_generated_driver(git_patch_generated *); + +extern int git_patch_generated_old_data( + char **, long *, git_patch_generated *); +extern int git_patch_generated_new_data( + char **, long *, git_patch_generated *); +extern int git_patch_generated_from_diff( + git_patch **, git_diff *, size_t); + +typedef struct git_patch_generated_output git_patch_generated_output; + +struct git_patch_generated_output { + /* these callbacks are issued with the diff data */ + git_diff_file_cb file_cb; + git_diff_binary_cb binary_cb; + git_diff_hunk_cb hunk_cb; + git_diff_line_cb data_cb; + void *payload; + + /* this records the actual error in cases where it may be obscured */ + int error; + + /* this callback is used to do the diff and drive the other callbacks. + * see diff_xdiff.h for how to use this in practice for now. + */ + int (*diff_cb)(git_patch_generated_output *output, + git_patch_generated *patch); +}; + +#endif diff --git a/src/libgit2/patch_parse.c b/src/libgit2/patch_parse.c new file mode 100644 index 000000000..78cd96252 --- /dev/null +++ b/src/libgit2/patch_parse.c @@ -0,0 +1,1231 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "patch_parse.h" + +#include "git2/patch.h" +#include "patch.h" +#include "diff_parse.h" +#include "fs_path.h" + +typedef struct { + git_patch base; + + git_patch_parse_ctx *ctx; + + /* the paths from the `diff --git` header, these will be used if this is not + * a rename (and rename paths are specified) or if no `+++`/`---` line specify + * the paths. + */ + char *header_old_path, *header_new_path; + + /* renamed paths are precise and are not prefixed */ + char *rename_old_path, *rename_new_path; + + /* the paths given in `---` and `+++` lines */ + char *old_path, *new_path; + + /* the prefixes from the old/new paths */ + char *old_prefix, *new_prefix; +} git_patch_parsed; + +static int git_parse_err(const char *fmt, ...) GIT_FORMAT_PRINTF(1, 2); +static int git_parse_err(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + git_error_vset(GIT_ERROR_PATCH, fmt, ap); + va_end(ap); + + return -1; +} + +static size_t header_path_len(git_patch_parse_ctx *ctx) +{ + bool inquote = 0; + bool quoted = git_parse_ctx_contains_s(&ctx->parse_ctx, "\""); + size_t len; + + for (len = quoted; len < ctx->parse_ctx.line_len; len++) { + if (!quoted && git__isspace(ctx->parse_ctx.line[len])) + break; + else if (quoted && !inquote && ctx->parse_ctx.line[len] == '"') { + len++; + break; + } + + inquote = (!inquote && ctx->parse_ctx.line[len] == '\\'); + } + + return len; +} + +static int parse_header_path_buf(git_str *path, git_patch_parse_ctx *ctx, size_t path_len) +{ + int error; + + if ((error = git_str_put(path, ctx->parse_ctx.line, path_len)) < 0) + return error; + + git_parse_advance_chars(&ctx->parse_ctx, path_len); + + git_str_rtrim(path); + + if (path->size > 0 && path->ptr[0] == '"' && + (error = git_str_unquote(path)) < 0) + return error; + + git_fs_path_squash_slashes(path); + + if (!path->size) + return git_parse_err("patch contains empty path at line %"PRIuZ, + ctx->parse_ctx.line_num); + + return 0; +} + +static int parse_header_path(char **out, git_patch_parse_ctx *ctx) +{ + git_str path = GIT_STR_INIT; + int error; + + if ((error = parse_header_path_buf(&path, ctx, header_path_len(ctx))) < 0) + goto out; + *out = git_str_detach(&path); + +out: + git_str_dispose(&path); + return error; +} + +static int parse_header_git_oldpath( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + git_str old_path = GIT_STR_INIT; + int error; + + if (patch->old_path) { + error = git_parse_err("patch contains duplicate old path at line %"PRIuZ, + ctx->parse_ctx.line_num); + goto out; + } + + if ((error = parse_header_path_buf(&old_path, ctx, ctx->parse_ctx.line_len - 1)) < 0) + goto out; + + patch->old_path = git_str_detach(&old_path); + +out: + git_str_dispose(&old_path); + return error; +} + +static int parse_header_git_newpath( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + git_str new_path = GIT_STR_INIT; + int error; + + if (patch->new_path) { + error = git_parse_err("patch contains duplicate new path at line %"PRIuZ, + ctx->parse_ctx.line_num); + goto out; + } + + if ((error = parse_header_path_buf(&new_path, ctx, ctx->parse_ctx.line_len - 1)) < 0) + goto out; + patch->new_path = git_str_detach(&new_path); + +out: + git_str_dispose(&new_path); + return error; +} + +static int parse_header_mode(uint16_t *mode, git_patch_parse_ctx *ctx) +{ + int64_t m; + + if ((git_parse_advance_digit(&m, &ctx->parse_ctx, 8)) < 0) + return git_parse_err("invalid file mode at line %"PRIuZ, ctx->parse_ctx.line_num); + + if (m > UINT16_MAX) + return -1; + + *mode = (uint16_t)m; + + return 0; +} + +static int parse_header_oid( + git_oid *oid, + uint16_t *oid_len, + git_patch_parse_ctx *ctx) +{ + size_t len; + + for (len = 0; len < ctx->parse_ctx.line_len && len < GIT_OID_HEXSZ; len++) { + if (!git__isxdigit(ctx->parse_ctx.line[len])) + break; + } + + if (len < GIT_OID_MINPREFIXLEN || len > GIT_OID_HEXSZ || + git_oid_fromstrn(oid, ctx->parse_ctx.line, len) < 0) + return git_parse_err("invalid hex formatted object id at line %"PRIuZ, + ctx->parse_ctx.line_num); + + git_parse_advance_chars(&ctx->parse_ctx, len); + + *oid_len = (uint16_t)len; + + return 0; +} + +static int parse_header_git_index( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + char c; + + if (parse_header_oid(&patch->base.delta->old_file.id, + &patch->base.delta->old_file.id_abbrev, ctx) < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, "..") < 0 || + parse_header_oid(&patch->base.delta->new_file.id, + &patch->base.delta->new_file.id_abbrev, ctx) < 0) + return -1; + + if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ' ') { + uint16_t mode = 0; + + git_parse_advance_chars(&ctx->parse_ctx, 1); + + if (parse_header_mode(&mode, ctx) < 0) + return -1; + + if (!patch->base.delta->new_file.mode) + patch->base.delta->new_file.mode = mode; + + if (!patch->base.delta->old_file.mode) + patch->base.delta->old_file.mode = mode; + } + + return 0; +} + +static int parse_header_git_oldmode( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + return parse_header_mode(&patch->base.delta->old_file.mode, ctx); +} + +static int parse_header_git_newmode( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + return parse_header_mode(&patch->base.delta->new_file.mode, ctx); +} + +static int parse_header_git_deletedfilemode( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + git__free((char *)patch->base.delta->new_file.path); + + patch->base.delta->new_file.path = NULL; + patch->base.delta->status = GIT_DELTA_DELETED; + patch->base.delta->nfiles = 1; + + return parse_header_mode(&patch->base.delta->old_file.mode, ctx); +} + +static int parse_header_git_newfilemode( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + git__free((char *)patch->base.delta->old_file.path); + + patch->base.delta->old_file.path = NULL; + patch->base.delta->status = GIT_DELTA_ADDED; + patch->base.delta->nfiles = 1; + + return parse_header_mode(&patch->base.delta->new_file.mode, ctx); +} + +static int parse_header_rename( + char **out, + git_patch_parse_ctx *ctx) +{ + git_str path = GIT_STR_INIT; + + if (parse_header_path_buf(&path, ctx, header_path_len(ctx)) < 0) + return -1; + + /* Note: the `rename from` and `rename to` lines include the literal + * filename. They do *not* include the prefix. (Who needs consistency?) + */ + *out = git_str_detach(&path); + return 0; +} + +static int parse_header_renamefrom( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + patch->base.delta->status = GIT_DELTA_RENAMED; + return parse_header_rename(&patch->rename_old_path, ctx); +} + +static int parse_header_renameto( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + patch->base.delta->status = GIT_DELTA_RENAMED; + return parse_header_rename(&patch->rename_new_path, ctx); +} + +static int parse_header_copyfrom( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + patch->base.delta->status = GIT_DELTA_COPIED; + return parse_header_rename(&patch->rename_old_path, ctx); +} + +static int parse_header_copyto( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + patch->base.delta->status = GIT_DELTA_COPIED; + return parse_header_rename(&patch->rename_new_path, ctx); +} + +static int parse_header_percent(uint16_t *out, git_patch_parse_ctx *ctx) +{ + int64_t val; + + if (git_parse_advance_digit(&val, &ctx->parse_ctx, 10) < 0) + return -1; + + if (git_parse_advance_expected_str(&ctx->parse_ctx, "%") < 0) + return -1; + + if (val < 0 || val > 100) + return -1; + + *out = (uint16_t)val; + return 0; +} + +static int parse_header_similarity( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + if (parse_header_percent(&patch->base.delta->similarity, ctx) < 0) + return git_parse_err("invalid similarity percentage at line %"PRIuZ, + ctx->parse_ctx.line_num); + + return 0; +} + +static int parse_header_dissimilarity( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + uint16_t dissimilarity; + + if (parse_header_percent(&dissimilarity, ctx) < 0) + return git_parse_err("invalid similarity percentage at line %"PRIuZ, + ctx->parse_ctx.line_num); + + patch->base.delta->similarity = 100 - dissimilarity; + + return 0; +} + +static int parse_header_start(git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + if (parse_header_path(&patch->header_old_path, ctx) < 0) + return git_parse_err("corrupt old path in git diff header at line %"PRIuZ, + ctx->parse_ctx.line_num); + + if (git_parse_advance_ws(&ctx->parse_ctx) < 0 || + parse_header_path(&patch->header_new_path, ctx) < 0) + return git_parse_err("corrupt new path in git diff header at line %"PRIuZ, + ctx->parse_ctx.line_num); + + /* + * We cannot expect to be able to always parse paths correctly at this + * point. Due to the possibility of unquoted names, whitespaces in + * filenames and custom prefixes we have to allow that, though, and just + * proceed here. We then hope for the "---" and "+++" lines to fix that + * for us. + */ + if (!git_parse_ctx_contains(&ctx->parse_ctx, "\n", 1) && + !git_parse_ctx_contains(&ctx->parse_ctx, "\r\n", 2)) { + git_parse_advance_chars(&ctx->parse_ctx, ctx->parse_ctx.line_len - 1); + + git__free(patch->header_old_path); + patch->header_old_path = NULL; + git__free(patch->header_new_path); + patch->header_new_path = NULL; + } + + return 0; +} + +typedef enum { + STATE_START, + + STATE_DIFF, + STATE_FILEMODE, + STATE_MODE, + STATE_INDEX, + STATE_PATH, + + STATE_SIMILARITY, + STATE_RENAME, + STATE_COPY, + + STATE_END +} parse_header_state; + +typedef struct { + const char *str; + parse_header_state expected_state; + parse_header_state next_state; + int(*fn)(git_patch_parsed *, git_patch_parse_ctx *); +} parse_header_transition; + +static const parse_header_transition transitions[] = { + /* Start */ + { "diff --git " , STATE_START, STATE_DIFF, parse_header_start }, + + { "deleted file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_deletedfilemode }, + { "new file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_newfilemode }, + { "old mode " , STATE_DIFF, STATE_MODE, parse_header_git_oldmode }, + { "new mode " , STATE_MODE, STATE_END, parse_header_git_newmode }, + + { "index " , STATE_FILEMODE, STATE_INDEX, parse_header_git_index }, + { "index " , STATE_DIFF, STATE_INDEX, parse_header_git_index }, + { "index " , STATE_END, STATE_INDEX, parse_header_git_index }, + + { "--- " , STATE_DIFF, STATE_PATH, parse_header_git_oldpath }, + { "--- " , STATE_INDEX, STATE_PATH, parse_header_git_oldpath }, + { "--- " , STATE_FILEMODE, STATE_PATH, parse_header_git_oldpath }, + { "+++ " , STATE_PATH, STATE_END, parse_header_git_newpath }, + { "GIT binary patch" , STATE_INDEX, STATE_END, NULL }, + { "Binary files " , STATE_INDEX, STATE_END, NULL }, + + { "similarity index " , STATE_END, STATE_SIMILARITY, parse_header_similarity }, + { "similarity index " , STATE_DIFF, STATE_SIMILARITY, parse_header_similarity }, + { "dissimilarity index ", STATE_DIFF, STATE_SIMILARITY, parse_header_dissimilarity }, + { "rename from " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom }, + { "rename old " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom }, + { "copy from " , STATE_SIMILARITY, STATE_COPY, parse_header_copyfrom }, + { "rename to " , STATE_RENAME, STATE_END, parse_header_renameto }, + { "rename new " , STATE_RENAME, STATE_END, parse_header_renameto }, + { "copy to " , STATE_COPY, STATE_END, parse_header_copyto }, + + /* Next patch */ + { "diff --git " , STATE_END, 0, NULL }, + { "@@ -" , STATE_END, 0, NULL }, + { "-- " , STATE_INDEX, 0, NULL }, + { "-- " , STATE_END, 0, NULL }, +}; + +static int parse_header_git( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + size_t i; + int error = 0; + parse_header_state state = STATE_START; + + /* Parse remaining header lines */ + for (; ctx->parse_ctx.remain_len > 0; git_parse_advance_line(&ctx->parse_ctx)) { + bool found = false; + + if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') + break; + + for (i = 0; i < ARRAY_SIZE(transitions); i++) { + const parse_header_transition *transition = &transitions[i]; + size_t op_len = strlen(transition->str); + + if (transition->expected_state != state || + git__prefixcmp(ctx->parse_ctx.line, transition->str) != 0) + continue; + + state = transition->next_state; + + /* Do not advance if this is the patch separator */ + if (transition->fn == NULL) + goto done; + + git_parse_advance_chars(&ctx->parse_ctx, op_len); + + if ((error = transition->fn(patch, ctx)) < 0) + goto done; + + git_parse_advance_ws(&ctx->parse_ctx); + + if (git_parse_advance_expected_str(&ctx->parse_ctx, "\n") < 0 || + ctx->parse_ctx.line_len > 0) { + error = git_parse_err("trailing data at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + + found = true; + break; + } + + if (!found) { + error = git_parse_err("invalid patch header at line %"PRIuZ, + ctx->parse_ctx.line_num); + goto done; + } + } + + if (state != STATE_END) { + error = git_parse_err("unexpected header line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + +done: + return error; +} + +static int parse_int(int *out, git_patch_parse_ctx *ctx) +{ + int64_t num; + + if (git_parse_advance_digit(&num, &ctx->parse_ctx, 10) < 0 || !git__is_int(num)) + return -1; + + *out = (int)num; + return 0; +} + +static int parse_hunk_header( + git_patch_hunk *hunk, + git_patch_parse_ctx *ctx) +{ + const char *header_start = ctx->parse_ctx.line; + char c; + + hunk->hunk.old_lines = 1; + hunk->hunk.new_lines = 1; + + if (git_parse_advance_expected_str(&ctx->parse_ctx, "@@ -") < 0 || + parse_int(&hunk->hunk.old_start, ctx) < 0) + goto fail; + + if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ',') { + if (git_parse_advance_expected_str(&ctx->parse_ctx, ",") < 0 || + parse_int(&hunk->hunk.old_lines, ctx) < 0) + goto fail; + } + + if (git_parse_advance_expected_str(&ctx->parse_ctx, " +") < 0 || + parse_int(&hunk->hunk.new_start, ctx) < 0) + goto fail; + + if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ',') { + if (git_parse_advance_expected_str(&ctx->parse_ctx, ",") < 0 || + parse_int(&hunk->hunk.new_lines, ctx) < 0) + goto fail; + } + + if (git_parse_advance_expected_str(&ctx->parse_ctx, " @@") < 0) + goto fail; + + git_parse_advance_line(&ctx->parse_ctx); + + if (!hunk->hunk.old_lines && !hunk->hunk.new_lines) + goto fail; + + hunk->hunk.header_len = ctx->parse_ctx.line - header_start; + if (hunk->hunk.header_len > (GIT_DIFF_HUNK_HEADER_SIZE - 1)) + return git_parse_err("oversized patch hunk header at line %"PRIuZ, + ctx->parse_ctx.line_num); + + memcpy(hunk->hunk.header, header_start, hunk->hunk.header_len); + hunk->hunk.header[hunk->hunk.header_len] = '\0'; + + return 0; + +fail: + git_error_set(GIT_ERROR_PATCH, "invalid patch hunk header at line %"PRIuZ, + ctx->parse_ctx.line_num); + return -1; +} + +static int eof_for_origin(int origin) { + if (origin == GIT_DIFF_LINE_ADDITION) + return GIT_DIFF_LINE_ADD_EOFNL; + if (origin == GIT_DIFF_LINE_DELETION) + return GIT_DIFF_LINE_DEL_EOFNL; + return GIT_DIFF_LINE_CONTEXT_EOFNL; +} + +static int parse_hunk_body( + git_patch_parsed *patch, + git_patch_hunk *hunk, + git_patch_parse_ctx *ctx) +{ + git_diff_line *line; + int error = 0; + + int oldlines = hunk->hunk.old_lines; + int newlines = hunk->hunk.new_lines; + int last_origin = 0; + + for (; + ctx->parse_ctx.remain_len > 1 && + (oldlines || newlines) && + !git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -"); + git_parse_advance_line(&ctx->parse_ctx)) { + + int old_lineno, new_lineno, origin, prefix = 1; + char c; + + if (git__add_int_overflow(&old_lineno, hunk->hunk.old_start, hunk->hunk.old_lines) || + git__sub_int_overflow(&old_lineno, old_lineno, oldlines) || + git__add_int_overflow(&new_lineno, hunk->hunk.new_start, hunk->hunk.new_lines) || + git__sub_int_overflow(&new_lineno, new_lineno, newlines)) { + error = git_parse_err("unrepresentable line count at line %"PRIuZ, + ctx->parse_ctx.line_num); + goto done; + } + + if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') { + error = git_parse_err("invalid patch instruction at line %"PRIuZ, + ctx->parse_ctx.line_num); + goto done; + } + + git_parse_peek(&c, &ctx->parse_ctx, 0); + + switch (c) { + case '\n': + prefix = 0; + /* fall through */ + + case ' ': + origin = GIT_DIFF_LINE_CONTEXT; + oldlines--; + newlines--; + break; + + case '-': + origin = GIT_DIFF_LINE_DELETION; + oldlines--; + new_lineno = -1; + break; + + case '+': + origin = GIT_DIFF_LINE_ADDITION; + newlines--; + old_lineno = -1; + break; + + case '\\': + /* + * If there are no oldlines left, then this is probably + * the "\ No newline at end of file" marker. Do not + * verify its format, as it may be localized. + */ + if (!oldlines) { + prefix = 0; + origin = eof_for_origin(last_origin); + old_lineno = -1; + new_lineno = -1; + break; + } + /* fall through */ + + default: + error = git_parse_err("invalid patch hunk at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + + line = git_array_alloc(patch->base.lines); + GIT_ERROR_CHECK_ALLOC(line); + + memset(line, 0x0, sizeof(git_diff_line)); + + line->content_len = ctx->parse_ctx.line_len - prefix; + line->content = git__strndup(ctx->parse_ctx.line + prefix, line->content_len); + GIT_ERROR_CHECK_ALLOC(line->content); + line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len; + line->origin = origin; + line->num_lines = 1; + line->old_lineno = old_lineno; + line->new_lineno = new_lineno; + + hunk->line_count++; + + last_origin = origin; + } + + if (oldlines || newlines) { + error = git_parse_err( + "invalid patch hunk, expected %d old lines and %d new lines", + hunk->hunk.old_lines, hunk->hunk.new_lines); + goto done; + } + + /* + * Handle "\ No newline at end of file". Only expect the leading + * backslash, though, because the rest of the string could be + * localized. Because `diff` optimizes for the case where you + * want to apply the patch by hand. + */ + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "\\ ") && + git_array_size(patch->base.lines) > 0) { + + line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1); + + if (line->content_len < 1) { + error = git_parse_err("last line has no trailing newline"); + goto done; + } + + line = git_array_alloc(patch->base.lines); + GIT_ERROR_CHECK_ALLOC(line); + + memset(line, 0x0, sizeof(git_diff_line)); + + line->content_len = ctx->parse_ctx.line_len; + line->content = git__strndup(ctx->parse_ctx.line, line->content_len); + GIT_ERROR_CHECK_ALLOC(line->content); + line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len; + line->origin = eof_for_origin(last_origin); + line->num_lines = 1; + line->old_lineno = -1; + line->new_lineno = -1; + + hunk->line_count++; + + git_parse_advance_line(&ctx->parse_ctx); + } + +done: + return error; +} + +static int parse_patch_header( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + int error = 0; + + for (; ctx->parse_ctx.remain_len > 0; git_parse_advance_line(&ctx->parse_ctx)) { + /* This line is too short to be a patch header. */ + if (ctx->parse_ctx.line_len < 6) + continue; + + /* This might be a hunk header without a patch header, provide a + * sensible error message. */ + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { + size_t line_num = ctx->parse_ctx.line_num; + git_patch_hunk hunk; + + /* If this cannot be parsed as a hunk header, it's just leading + * noise, continue. + */ + if (parse_hunk_header(&hunk, ctx) < 0) { + git_error_clear(); + continue; + } + + error = git_parse_err("invalid hunk header outside patch at line %"PRIuZ, + line_num); + goto done; + } + + /* This buffer is too short to contain a patch. */ + if (ctx->parse_ctx.remain_len < ctx->parse_ctx.line_len + 6) + break; + + /* A proper git patch */ + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "diff --git ")) { + error = parse_header_git(patch, ctx); + goto done; + } + + error = 0; + continue; + } + + git_error_set(GIT_ERROR_PATCH, "no patch found"); + error = GIT_ENOTFOUND; + +done: + return error; +} + +static int parse_patch_binary_side( + git_diff_binary_file *binary, + git_patch_parse_ctx *ctx) +{ + git_diff_binary_t type = GIT_DIFF_BINARY_NONE; + git_str base85 = GIT_STR_INIT, decoded = GIT_STR_INIT; + int64_t len; + int error = 0; + + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "literal ")) { + type = GIT_DIFF_BINARY_LITERAL; + git_parse_advance_chars(&ctx->parse_ctx, 8); + } else if (git_parse_ctx_contains_s(&ctx->parse_ctx, "delta ")) { + type = GIT_DIFF_BINARY_DELTA; + git_parse_advance_chars(&ctx->parse_ctx, 6); + } else { + error = git_parse_err( + "unknown binary delta type at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + + if (git_parse_advance_digit(&len, &ctx->parse_ctx, 10) < 0 || + git_parse_advance_nl(&ctx->parse_ctx) < 0 || len < 0) { + error = git_parse_err("invalid binary size at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + + while (ctx->parse_ctx.line_len) { + char c; + size_t encoded_len, decoded_len = 0, decoded_orig = decoded.size; + + git_parse_peek(&c, &ctx->parse_ctx, 0); + + if (c == '\n') + break; + else if (c >= 'A' && c <= 'Z') + decoded_len = c - 'A' + 1; + else if (c >= 'a' && c <= 'z') + decoded_len = c - 'a' + (('z' - 'a') + 1) + 1; + + if (!decoded_len) { + error = git_parse_err("invalid binary length at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + + git_parse_advance_chars(&ctx->parse_ctx, 1); + + encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5; + + if (!encoded_len || !ctx->parse_ctx.line_len || encoded_len > ctx->parse_ctx.line_len - 1) { + error = git_parse_err("truncated binary data at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + + if ((error = git_str_decode_base85( + &decoded, ctx->parse_ctx.line, encoded_len, decoded_len)) < 0) + goto done; + + if (decoded.size - decoded_orig != decoded_len) { + error = git_parse_err("truncated binary data at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + + git_parse_advance_chars(&ctx->parse_ctx, encoded_len); + + if (git_parse_advance_nl(&ctx->parse_ctx) < 0) { + error = git_parse_err("trailing data at line %"PRIuZ, ctx->parse_ctx.line_num); + goto done; + } + } + + binary->type = type; + binary->inflatedlen = (size_t)len; + binary->datalen = decoded.size; + binary->data = git_str_detach(&decoded); + +done: + git_str_dispose(&base85); + git_str_dispose(&decoded); + return error; +} + +static int parse_patch_binary( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + int error; + + if (git_parse_advance_expected_str(&ctx->parse_ctx, "GIT binary patch") < 0 || + git_parse_advance_nl(&ctx->parse_ctx) < 0) + return git_parse_err("corrupt git binary header at line %"PRIuZ, ctx->parse_ctx.line_num); + + /* parse old->new binary diff */ + if ((error = parse_patch_binary_side( + &patch->base.binary.new_file, ctx)) < 0) + return error; + + if (git_parse_advance_nl(&ctx->parse_ctx) < 0) + return git_parse_err("corrupt git binary separator at line %"PRIuZ, + ctx->parse_ctx.line_num); + + /* parse new->old binary diff */ + if ((error = parse_patch_binary_side( + &patch->base.binary.old_file, ctx)) < 0) + return error; + + if (git_parse_advance_nl(&ctx->parse_ctx) < 0) + return git_parse_err("corrupt git binary patch separator at line %"PRIuZ, + ctx->parse_ctx.line_num); + + patch->base.binary.contains_data = 1; + patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; + return 0; +} + +static int parse_patch_binary_nodata( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + const char *old = patch->old_path ? patch->old_path : patch->header_old_path; + const char *new = patch->new_path ? patch->new_path : patch->header_new_path; + + if (!old || !new) + return git_parse_err("corrupt binary data without paths at line %"PRIuZ, ctx->parse_ctx.line_num); + + if (patch->base.delta->status == GIT_DELTA_ADDED) + old = "/dev/null"; + else if (patch->base.delta->status == GIT_DELTA_DELETED) + new = "/dev/null"; + + if (git_parse_advance_expected_str(&ctx->parse_ctx, "Binary files ") < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, old) < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, " and ") < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, new) < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, " differ") < 0 || + git_parse_advance_nl(&ctx->parse_ctx) < 0) + return git_parse_err("corrupt git binary header at line %"PRIuZ, ctx->parse_ctx.line_num); + + patch->base.binary.contains_data = 0; + patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; + return 0; +} + +static int parse_patch_hunks( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + git_patch_hunk *hunk; + int error = 0; + + while (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { + hunk = git_array_alloc(patch->base.hunks); + GIT_ERROR_CHECK_ALLOC(hunk); + + memset(hunk, 0, sizeof(git_patch_hunk)); + + hunk->line_start = git_array_size(patch->base.lines); + hunk->line_count = 0; + + if ((error = parse_hunk_header(hunk, ctx)) < 0 || + (error = parse_hunk_body(patch, hunk, ctx)) < 0) + goto done; + } + + patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; + +done: + return error; +} + +static int parse_patch_body( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + if (git_parse_ctx_contains_s(&ctx->parse_ctx, "GIT binary patch")) + return parse_patch_binary(patch, ctx); + else if (git_parse_ctx_contains_s(&ctx->parse_ctx, "Binary files ")) + return parse_patch_binary_nodata(patch, ctx); + else + return parse_patch_hunks(patch, ctx); +} + +static int check_header_names( + const char *one, + const char *two, + const char *old_or_new, + bool two_null) +{ + if (!one || !two) + return 0; + + if (two_null && strcmp(two, "/dev/null") != 0) + return git_parse_err("expected %s path of '/dev/null'", old_or_new); + + else if (!two_null && strcmp(one, two) != 0) + return git_parse_err("mismatched %s path names", old_or_new); + + return 0; +} + +static int check_prefix( + char **out, + size_t *out_len, + git_patch_parsed *patch, + const char *path_start) +{ + const char *path = path_start; + size_t prefix_len = patch->ctx->opts.prefix_len; + size_t remain_len = prefix_len; + + *out = NULL; + *out_len = 0; + + if (prefix_len == 0) + goto done; + + /* leading slashes do not count as part of the prefix in git apply */ + while (*path == '/') + path++; + + while (*path && remain_len) { + if (*path == '/') + remain_len--; + + path++; + } + + if (remain_len || !*path) + return git_parse_err( + "header filename does not contain %"PRIuZ" path components", + prefix_len); + +done: + *out_len = (path - path_start); + *out = git__strndup(path_start, *out_len); + + return (*out == NULL) ? -1 : 0; +} + +static int check_filenames(git_patch_parsed *patch) +{ + const char *prefixed_new, *prefixed_old; + size_t old_prefixlen = 0, new_prefixlen = 0; + bool added = (patch->base.delta->status == GIT_DELTA_ADDED); + bool deleted = (patch->base.delta->status == GIT_DELTA_DELETED); + + if (patch->old_path && !patch->new_path) + return git_parse_err("missing new path"); + + if (!patch->old_path && patch->new_path) + return git_parse_err("missing old path"); + + /* Ensure (non-renamed) paths match */ + if (check_header_names(patch->header_old_path, patch->old_path, "old", added) < 0 || + check_header_names(patch->header_new_path, patch->new_path, "new", deleted) < 0) + return -1; + + prefixed_old = (!added && patch->old_path) ? patch->old_path : patch->header_old_path; + prefixed_new = (!deleted && patch->new_path) ? patch->new_path : patch->header_new_path; + + if ((prefixed_old && check_prefix(&patch->old_prefix, &old_prefixlen, patch, prefixed_old) < 0) || + (prefixed_new && check_prefix(&patch->new_prefix, &new_prefixlen, patch, prefixed_new) < 0)) + return -1; + + /* Prefer the rename filenames as they are unambiguous and unprefixed */ + if (patch->rename_old_path) + patch->base.delta->old_file.path = patch->rename_old_path; + else if (prefixed_old) + patch->base.delta->old_file.path = prefixed_old + old_prefixlen; + else + patch->base.delta->old_file.path = NULL; + + if (patch->rename_new_path) + patch->base.delta->new_file.path = patch->rename_new_path; + else if (prefixed_new) + patch->base.delta->new_file.path = prefixed_new + new_prefixlen; + else + patch->base.delta->new_file.path = NULL; + + if (!patch->base.delta->old_file.path && + !patch->base.delta->new_file.path) + return git_parse_err("git diff header lacks old / new paths"); + + return 0; +} + +static int check_patch(git_patch_parsed *patch) +{ + git_diff_delta *delta = patch->base.delta; + + if (check_filenames(patch) < 0) + return -1; + + if (delta->old_file.path && + delta->status != GIT_DELTA_DELETED && + !delta->new_file.mode) + delta->new_file.mode = delta->old_file.mode; + + if (delta->status == GIT_DELTA_MODIFIED && + !(delta->flags & GIT_DIFF_FLAG_BINARY) && + delta->new_file.mode == delta->old_file.mode && + git_array_size(patch->base.hunks) == 0) + return git_parse_err("patch with no hunks"); + + if (delta->status == GIT_DELTA_ADDED) { + memset(&delta->old_file.id, 0x0, sizeof(git_oid)); + delta->old_file.id_abbrev = 0; + } + + if (delta->status == GIT_DELTA_DELETED) { + memset(&delta->new_file.id, 0x0, sizeof(git_oid)); + delta->new_file.id_abbrev = 0; + } + + return 0; +} + +git_patch_parse_ctx *git_patch_parse_ctx_init( + const char *content, + size_t content_len, + const git_patch_options *opts) +{ + git_patch_parse_ctx *ctx; + git_patch_options default_opts = GIT_PATCH_OPTIONS_INIT; + + if ((ctx = git__calloc(1, sizeof(git_patch_parse_ctx))) == NULL) + return NULL; + + if ((git_parse_ctx_init(&ctx->parse_ctx, content, content_len)) < 0) { + git__free(ctx); + return NULL; + } + + if (opts) + memcpy(&ctx->opts, opts, sizeof(git_patch_options)); + else + memcpy(&ctx->opts, &default_opts, sizeof(git_patch_options)); + + GIT_REFCOUNT_INC(ctx); + return ctx; +} + +static void patch_parse_ctx_free(git_patch_parse_ctx *ctx) +{ + if (!ctx) + return; + + git_parse_ctx_clear(&ctx->parse_ctx); + git__free(ctx); +} + +void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx) +{ + GIT_REFCOUNT_DEC(ctx, patch_parse_ctx_free); +} + +int git_patch_parsed_from_diff(git_patch **out, git_diff *d, size_t idx) +{ + git_diff_parsed *diff = (git_diff_parsed *)d; + git_patch *p; + + if ((p = git_vector_get(&diff->patches, idx)) == NULL) + return -1; + + GIT_REFCOUNT_INC(p); + *out = p; + + return 0; +} + +static void patch_parsed__free(git_patch *p) +{ + git_patch_parsed *patch = (git_patch_parsed *)p; + git_diff_line *line; + size_t i; + + if (!patch) + return; + + git_patch_parse_ctx_free(patch->ctx); + + git__free((char *)patch->base.binary.old_file.data); + git__free((char *)patch->base.binary.new_file.data); + git_array_clear(patch->base.hunks); + git_array_foreach(patch->base.lines, i, line) + git__free((char *) line->content); + git_array_clear(patch->base.lines); + git__free(patch->base.delta); + + git__free(patch->old_prefix); + git__free(patch->new_prefix); + git__free(patch->header_old_path); + git__free(patch->header_new_path); + git__free(patch->rename_old_path); + git__free(patch->rename_new_path); + git__free(patch->old_path); + git__free(patch->new_path); + git__free(patch); +} + +int git_patch_parse( + git_patch **out, + git_patch_parse_ctx *ctx) +{ + git_patch_parsed *patch; + size_t start, used; + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ctx); + + *out = NULL; + + patch = git__calloc(1, sizeof(git_patch_parsed)); + GIT_ERROR_CHECK_ALLOC(patch); + + patch->ctx = ctx; + GIT_REFCOUNT_INC(patch->ctx); + + patch->base.free_fn = patch_parsed__free; + + patch->base.delta = git__calloc(1, sizeof(git_diff_delta)); + GIT_ERROR_CHECK_ALLOC(patch->base.delta); + + patch->base.delta->status = GIT_DELTA_MODIFIED; + patch->base.delta->nfiles = 2; + + start = ctx->parse_ctx.remain_len; + + if ((error = parse_patch_header(patch, ctx)) < 0 || + (error = parse_patch_body(patch, ctx)) < 0 || + (error = check_patch(patch)) < 0) + goto done; + + used = start - ctx->parse_ctx.remain_len; + ctx->parse_ctx.remain += used; + + patch->base.diff_opts.old_prefix = patch->old_prefix; + patch->base.diff_opts.new_prefix = patch->new_prefix; + patch->base.diff_opts.flags |= GIT_DIFF_SHOW_BINARY; + + GIT_REFCOUNT_INC(&patch->base); + *out = &patch->base; + +done: + if (error < 0) + patch_parsed__free(&patch->base); + + return error; +} + +int git_patch_from_buffer( + git_patch **out, + const char *content, + size_t content_len, + const git_patch_options *opts) +{ + git_patch_parse_ctx *ctx; + int error; + + ctx = git_patch_parse_ctx_init(content, content_len, opts); + GIT_ERROR_CHECK_ALLOC(ctx); + + error = git_patch_parse(out, ctx); + + git_patch_parse_ctx_free(ctx); + return error; +} + diff --git a/src/libgit2/patch_parse.h b/src/libgit2/patch_parse.h new file mode 100644 index 000000000..140629da8 --- /dev/null +++ b/src/libgit2/patch_parse.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_patch_parse_h__ +#define INCLUDE_patch_parse_h__ + +#include "common.h" + +#include "parse.h" +#include "patch.h" + +typedef struct { + git_refcount rc; + + git_patch_options opts; + + git_parse_ctx parse_ctx; +} git_patch_parse_ctx; + +extern git_patch_parse_ctx *git_patch_parse_ctx_init( + const char *content, + size_t content_len, + const git_patch_options *opts); + +extern void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx); + +/** + * Create a patch for a single file from the contents of a patch buffer. + * + * @param out The patch to be created + * @param contents The contents of a patch file + * @param contents_len The length of the patch file + * @param opts The git_patch_options + * @return 0 on success, <0 on failure. + */ +extern int git_patch_from_buffer( + git_patch **out, + const char *contents, + size_t contents_len, + const git_patch_options *opts); + +extern int git_patch_parse( + git_patch **out, + git_patch_parse_ctx *ctx); + +extern int git_patch_parsed_from_diff(git_patch **, git_diff *, size_t); + +#endif diff --git a/src/libgit2/path.c b/src/libgit2/path.c new file mode 100644 index 000000000..05a3dc2cf --- /dev/null +++ b/src/libgit2/path.c @@ -0,0 +1,374 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "path.h" + +#include "repository.h" +#include "fs_path.h" + +typedef struct { + git_repository *repo; + uint16_t file_mode; + unsigned int flags; +} repository_path_validate_data; + +static int32_t next_hfs_char(const char **in, size_t *len) +{ + while (*len) { + uint32_t codepoint; + int cp_len = git_utf8_iterate(&codepoint, *in, *len); + if (cp_len < 0) + return -1; + + (*in) += cp_len; + (*len) -= cp_len; + + /* these code points are ignored completely */ + switch (codepoint) { + case 0x200c: /* ZERO WIDTH NON-JOINER */ + case 0x200d: /* ZERO WIDTH JOINER */ + case 0x200e: /* LEFT-TO-RIGHT MARK */ + case 0x200f: /* RIGHT-TO-LEFT MARK */ + case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */ + case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */ + case 0x202c: /* POP DIRECTIONAL FORMATTING */ + case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */ + case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */ + case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */ + case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */ + case 0x206c: /* INHIBIT ARABIC FORM SHAPING */ + case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */ + case 0x206e: /* NATIONAL DIGIT SHAPES */ + case 0x206f: /* NOMINAL DIGIT SHAPES */ + case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */ + continue; + } + + /* fold into lowercase -- this will only fold characters in + * the ASCII range, which is perfectly fine, because the + * git folder name can only be composed of ascii characters + */ + return git__tolower((int)codepoint); + } + return 0; /* NULL byte -- end of string */ +} + +static bool validate_dotgit_hfs_generic( + const char *path, + size_t len, + const char *needle, + size_t needle_len) +{ + size_t i; + char c; + + if (next_hfs_char(&path, &len) != '.') + return true; + + for (i = 0; i < needle_len; i++) { + c = next_hfs_char(&path, &len); + if (c != needle[i]) + return true; + } + + if (next_hfs_char(&path, &len) != '\0') + return true; + + return false; +} + +static bool validate_dotgit_hfs(const char *path, size_t len) +{ + return validate_dotgit_hfs_generic(path, len, "git", CONST_STRLEN("git")); +} + +GIT_INLINE(bool) validate_dotgit_ntfs( + git_repository *repo, + const char *path, + size_t len) +{ + git_str *reserved = git_repository__reserved_names_win32; + size_t reserved_len = git_repository__reserved_names_win32_len; + size_t start = 0, i; + + if (repo) + git_repository__reserved_names(&reserved, &reserved_len, repo, true); + + for (i = 0; i < reserved_len; i++) { + git_str *r = &reserved[i]; + + if (len >= r->size && + strncasecmp(path, r->ptr, r->size) == 0) { + start = r->size; + break; + } + } + + if (!start) + return true; + + /* + * Reject paths that start with Windows-style directory separators + * (".git\") or NTFS alternate streams (".git:") and could be used + * to write to the ".git" directory on Windows platforms. + */ + if (path[start] == '\\' || path[start] == ':') + return false; + + /* Reject paths like '.git ' or '.git.' */ + for (i = start; i < len; i++) { + if (path[i] != ' ' && path[i] != '.') + return true; + } + + return false; +} + +/* + * Windows paths that end with spaces and/or dots are elided to the + * path without them for backward compatibility. That is to say + * that opening file "foo ", "foo." or even "foo . . ." will all + * map to a filename of "foo". This function identifies spaces and + * dots at the end of a filename, whether the proper end of the + * filename (end of string) or a colon (which would indicate a + * Windows alternate data stream.) + */ +GIT_INLINE(bool) ntfs_end_of_filename(const char *path) +{ + const char *c = path; + + for (;; c++) { + if (*c == '\0' || *c == ':') + return true; + if (*c != ' ' && *c != '.') + return false; + } + + return true; +} + +GIT_INLINE(bool) validate_dotgit_ntfs_generic( + const char *name, + size_t len, + const char *dotgit_name, + size_t dotgit_len, + const char *shortname_pfix) +{ + int i, saw_tilde; + + if (name[0] == '.' && len >= dotgit_len && + !strncasecmp(name + 1, dotgit_name, dotgit_len)) { + return !ntfs_end_of_filename(name + dotgit_len + 1); + } + + /* Detect the basic NTFS shortname with the first six chars */ + if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' && + name[7] >= '1' && name[7] <= '4') + return !ntfs_end_of_filename(name + 8); + + /* Catch fallback names */ + for (i = 0, saw_tilde = 0; i < 8; i++) { + if (name[i] == '\0') { + return true; + } else if (saw_tilde) { + if (name[i] < '0' || name[i] > '9') + return true; + } else if (name[i] == '~') { + if (name[i+1] < '1' || name[i+1] > '9') + return true; + saw_tilde = 1; + } else if (i >= 6) { + return true; + } else if ((unsigned char)name[i] > 127) { + return true; + } else if (git__tolower(name[i]) != shortname_pfix[i]) { + return true; + } + } + + return !ntfs_end_of_filename(name + i); +} + +/* + * Return the length of the common prefix between str and prefix, comparing them + * case-insensitively (must be ASCII to match). + */ +GIT_INLINE(size_t) common_prefix_icase(const char *str, size_t len, const char *prefix) +{ + size_t count = 0; + + while (len > 0 && tolower(*str) == tolower(*prefix)) { + count++; + str++; + prefix++; + len--; + } + + return count; +} + +static bool validate_repo_component( + const char *component, + size_t len, + void *payload) +{ + repository_path_validate_data *data = (repository_path_validate_data *)payload; + + if (data->flags & GIT_PATH_REJECT_DOT_GIT_HFS) { + if (!validate_dotgit_hfs(component, len)) + return false; + + if (S_ISLNK(data->file_mode) && + git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_HFS)) + return false; + } + + if (data->flags & GIT_PATH_REJECT_DOT_GIT_NTFS) { + if (!validate_dotgit_ntfs(data->repo, component, len)) + return false; + + if (S_ISLNK(data->file_mode) && + git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_NTFS)) + return false; + } + + /* don't bother rerunning the `.git` test if we ran the HFS or NTFS + * specific tests, they would have already rejected `.git`. + */ + if ((data->flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 && + (data->flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 && + (data->flags & GIT_PATH_REJECT_DOT_GIT_LITERAL)) { + if (len >= 4 && + component[0] == '.' && + (component[1] == 'g' || component[1] == 'G') && + (component[2] == 'i' || component[2] == 'I') && + (component[3] == 't' || component[3] == 'T')) { + if (len == 4) + return false; + + if (S_ISLNK(data->file_mode) && + common_prefix_icase(component, len, ".gitmodules") == len) + return false; + } + } + + return true; +} + +GIT_INLINE(unsigned int) dotgit_flags( + git_repository *repo, + unsigned int flags) +{ + int protectHFS = 0, protectNTFS = 1; + int error = 0; + + flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL; + +#ifdef __APPLE__ + protectHFS = 1; +#endif + + if (repo && !protectHFS) + error = git_repository__configmap_lookup(&protectHFS, repo, GIT_CONFIGMAP_PROTECTHFS); + if (!error && protectHFS) + flags |= GIT_PATH_REJECT_DOT_GIT_HFS; + + if (repo) + error = git_repository__configmap_lookup(&protectNTFS, repo, GIT_CONFIGMAP_PROTECTNTFS); + if (!error && protectNTFS) + flags |= GIT_PATH_REJECT_DOT_GIT_NTFS; + + return flags; +} + +GIT_INLINE(unsigned int) length_flags( + git_repository *repo, + unsigned int flags) +{ +#ifdef GIT_WIN32 + int allow = 0; + + if (repo && + git_repository__configmap_lookup(&allow, repo, GIT_CONFIGMAP_LONGPATHS) < 0) + allow = 0; + + if (allow) + flags &= ~GIT_FS_PATH_REJECT_LONG_PATHS; + +#else + GIT_UNUSED(repo); + flags &= ~GIT_FS_PATH_REJECT_LONG_PATHS; +#endif + + return flags; +} + +bool git_path_str_is_valid( + git_repository *repo, + const git_str *path, + uint16_t file_mode, + unsigned int flags) +{ + repository_path_validate_data data = {0}; + + /* Upgrade the ".git" checks based on platform */ + if ((flags & GIT_PATH_REJECT_DOT_GIT)) + flags = dotgit_flags(repo, flags); + + /* Update the length checks based on platform */ + if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS)) + flags = length_flags(repo, flags); + + data.repo = repo; + data.file_mode = file_mode; + data.flags = flags; + + return git_fs_path_str_is_valid_ext(path, flags, NULL, validate_repo_component, NULL, &data); +} + +static const struct { + const char *file; + const char *hash; + size_t filelen; +} gitfiles[] = { + { "gitignore", "gi250a", CONST_STRLEN("gitignore") }, + { "gitmodules", "gi7eba", CONST_STRLEN("gitmodules") }, + { "gitattributes", "gi7d29", CONST_STRLEN("gitattributes") } +}; + +extern int git_path_is_gitfile( + const char *path, + size_t pathlen, + git_path_gitfile gitfile, + git_path_fs fs) +{ + const char *file, *hash; + size_t filelen; + + if (!(gitfile >= GIT_PATH_GITFILE_GITIGNORE && gitfile < ARRAY_SIZE(gitfiles))) { + git_error_set(GIT_ERROR_OS, "invalid gitfile for path validation"); + return -1; + } + + file = gitfiles[gitfile].file; + filelen = gitfiles[gitfile].filelen; + hash = gitfiles[gitfile].hash; + + switch (fs) { + case GIT_PATH_FS_GENERIC: + return !validate_dotgit_ntfs_generic(path, pathlen, file, filelen, hash) || + !validate_dotgit_hfs_generic(path, pathlen, file, filelen); + case GIT_PATH_FS_NTFS: + return !validate_dotgit_ntfs_generic(path, pathlen, file, filelen, hash); + case GIT_PATH_FS_HFS: + return !validate_dotgit_hfs_generic(path, pathlen, file, filelen); + default: + git_error_set(GIT_ERROR_OS, "invalid filesystem for path validation"); + return -1; + } +} + diff --git a/src/libgit2/path.h b/src/libgit2/path.h new file mode 100644 index 000000000..c4a2c4250 --- /dev/null +++ b/src/libgit2/path.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_path_h__ +#define INCLUDE_path_h__ + +#include "common.h" + +#include "fs_path.h" +#include + +#define GIT_PATH_REJECT_DOT_GIT (GIT_FS_PATH_REJECT_MAX << 1) +#define GIT_PATH_REJECT_DOT_GIT_LITERAL (GIT_FS_PATH_REJECT_MAX << 2) +#define GIT_PATH_REJECT_DOT_GIT_HFS (GIT_FS_PATH_REJECT_MAX << 3) +#define GIT_PATH_REJECT_DOT_GIT_NTFS (GIT_FS_PATH_REJECT_MAX << 4) + +/* Paths that should never be written into the working directory. */ +#define GIT_PATH_REJECT_WORKDIR_DEFAULTS \ + GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS | GIT_PATH_REJECT_DOT_GIT + +/* Paths that should never be written to the index. */ +#define GIT_PATH_REJECT_INDEX_DEFAULTS \ + GIT_FS_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT + +extern bool git_path_str_is_valid( + git_repository *repo, + const git_str *path, + uint16_t file_mode, + unsigned int flags); + +GIT_INLINE(bool) git_path_is_valid( + git_repository *repo, + const char *path, + uint16_t file_mode, + unsigned int flags) +{ + git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); + return git_path_str_is_valid(repo, &str, file_mode, flags); +} + +GIT_INLINE(int) git_path_validate_str_length( + git_repository *repo, + const git_str *path) +{ + if (!git_path_str_is_valid(repo, path, 0, GIT_FS_PATH_REJECT_LONG_PATHS)) { + if (path->size == SIZE_MAX) + git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%s'", path->ptr); + else + git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%.*s'", (int)path->size, path->ptr); + + return -1; + } + + return 0; +} + +GIT_INLINE(int) git_path_validate_length( + git_repository *repo, + const char *path) +{ + git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); + return git_path_validate_str_length(repo, &str); +} + +#endif diff --git a/src/libgit2/pathspec.c b/src/libgit2/pathspec.c new file mode 100644 index 000000000..3e44643c6 --- /dev/null +++ b/src/libgit2/pathspec.c @@ -0,0 +1,722 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "pathspec.h" + +#include "git2/pathspec.h" +#include "git2/diff.h" +#include "attr_file.h" +#include "iterator.h" +#include "repository.h" +#include "index.h" +#include "bitvec.h" +#include "diff.h" +#include "wildmatch.h" + +/* what is the common non-wildcard prefix for all items in the pathspec */ +char *git_pathspec_prefix(const git_strarray *pathspec) +{ + git_str prefix = GIT_STR_INIT; + const char *scan; + + if (!pathspec || !pathspec->count || + git_str_common_prefix(&prefix, pathspec->strings, pathspec->count) < 0) + return NULL; + + /* diff prefix will only be leading non-wildcards */ + for (scan = prefix.ptr; *scan; ++scan) { + if (git__iswildcard(*scan) && + (scan == prefix.ptr || (*(scan - 1) != '\\'))) + break; + } + git_str_truncate(&prefix, scan - prefix.ptr); + + if (prefix.size <= 0) { + git_str_dispose(&prefix); + return NULL; + } + + git_str_unescape(&prefix); + + return git_str_detach(&prefix); +} + +/* is there anything in the spec that needs to be filtered on */ +bool git_pathspec_is_empty(const git_strarray *pathspec) +{ + size_t i; + + if (pathspec == NULL) + return true; + + for (i = 0; i < pathspec->count; ++i) { + const char *str = pathspec->strings[i]; + + if (str && str[0]) + return false; + } + + return true; +} + +/* build a vector of fnmatch patterns to evaluate efficiently */ +int git_pathspec__vinit( + git_vector *vspec, const git_strarray *strspec, git_pool *strpool) +{ + size_t i; + + memset(vspec, 0, sizeof(*vspec)); + + if (git_pathspec_is_empty(strspec)) + return 0; + + if (git_vector_init(vspec, strspec->count, NULL) < 0) + return -1; + + for (i = 0; i < strspec->count; ++i) { + int ret; + const char *pattern = strspec->strings[i]; + git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch)); + if (!match) + return -1; + + match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; + + ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern); + if (ret == GIT_ENOTFOUND) { + git__free(match); + continue; + } else if (ret < 0) { + git__free(match); + return ret; + } + + if (git_vector_insert(vspec, match) < 0) + return -1; + } + + return 0; +} + +/* free data from the pathspec vector */ +void git_pathspec__vfree(git_vector *vspec) +{ + git_vector_free_deep(vspec); +} + +struct pathspec_match_context { + int wildmatch_flags; + int (*strcomp)(const char *, const char *); + int (*strncomp)(const char *, const char *, size_t); +}; + +static void pathspec_match_context_init( + struct pathspec_match_context *ctxt, + bool disable_fnmatch, + bool casefold) +{ + if (disable_fnmatch) + ctxt->wildmatch_flags = -1; + else if (casefold) + ctxt->wildmatch_flags = WM_CASEFOLD; + else + ctxt->wildmatch_flags = 0; + + if (casefold) { + ctxt->strcomp = git__strcasecmp; + ctxt->strncomp = git__strncasecmp; + } else { + ctxt->strcomp = git__strcmp; + ctxt->strncomp = git__strncmp; + } +} + +static int pathspec_match_one( + const git_attr_fnmatch *match, + struct pathspec_match_context *ctxt, + const char *path) +{ + int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : WM_NOMATCH; + + if (result == WM_NOMATCH) + result = ctxt->strcomp(match->pattern, path) ? WM_NOMATCH : 0; + + if (ctxt->wildmatch_flags >= 0 && result == WM_NOMATCH) + result = wildmatch(match->pattern, path, ctxt->wildmatch_flags); + + /* if we didn't match, look for exact dirname prefix match */ + if (result == WM_NOMATCH && + (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && + ctxt->strncomp(path, match->pattern, match->length) == 0 && + path[match->length] == '/') + result = 0; + + /* if we didn't match and this is a negative match, check for exact + * match of filename with leading '!' + */ + if (result == WM_NOMATCH && + (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 && + *path == '!' && + ctxt->strncomp(path + 1, match->pattern, match->length) == 0 && + (!path[match->length + 1] || path[match->length + 1] == '/')) + return 1; + + if (result == 0) + return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1; + return -1; +} + +static int git_pathspec__match_at( + size_t *matched_at, + const git_vector *vspec, + struct pathspec_match_context *ctxt, + const char *path0, + const char *path1) +{ + int result = GIT_ENOTFOUND; + size_t i = 0; + const git_attr_fnmatch *match; + + git_vector_foreach(vspec, i, match) { + if (path0 && (result = pathspec_match_one(match, ctxt, path0)) >= 0) + break; + if (path1 && (result = pathspec_match_one(match, ctxt, path1)) >= 0) + break; + } + + *matched_at = i; + return result; +} + +/* match a path against the vectorized pathspec */ +bool git_pathspec__match( + const git_vector *vspec, + const char *path, + bool disable_fnmatch, + bool casefold, + const char **matched_pathspec, + size_t *matched_at) +{ + int result; + size_t pos; + struct pathspec_match_context ctxt; + + if (matched_pathspec) + *matched_pathspec = NULL; + if (matched_at) + *matched_at = GIT_PATHSPEC_NOMATCH; + + if (!vspec || !vspec->length) + return true; + + pathspec_match_context_init(&ctxt, disable_fnmatch, casefold); + + result = git_pathspec__match_at(&pos, vspec, &ctxt, path, NULL); + if (result >= 0) { + if (matched_pathspec) { + const git_attr_fnmatch *match = git_vector_get(vspec, pos); + *matched_pathspec = match->pattern; + } + + if (matched_at) + *matched_at = pos; + } + + return (result > 0); +} + + +int git_pathspec__init(git_pathspec *ps, const git_strarray *paths) +{ + int error = 0; + + memset(ps, 0, sizeof(*ps)); + + ps->prefix = git_pathspec_prefix(paths); + + if ((error = git_pool_init(&ps->pool, 1)) < 0 || + (error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0) + git_pathspec__clear(ps); + + return error; +} + +void git_pathspec__clear(git_pathspec *ps) +{ + git__free(ps->prefix); + git_pathspec__vfree(&ps->pathspec); + git_pool_clear(&ps->pool); + memset(ps, 0, sizeof(*ps)); +} + +int git_pathspec_new(git_pathspec **out, const git_strarray *pathspec) +{ + int error = 0; + git_pathspec *ps = git__malloc(sizeof(git_pathspec)); + GIT_ERROR_CHECK_ALLOC(ps); + + if ((error = git_pathspec__init(ps, pathspec)) < 0) { + git__free(ps); + return error; + } + + GIT_REFCOUNT_INC(ps); + *out = ps; + return 0; +} + +static void pathspec_free(git_pathspec *ps) +{ + git_pathspec__clear(ps); + git__free(ps); +} + +void git_pathspec_free(git_pathspec *ps) +{ + if (!ps) + return; + GIT_REFCOUNT_DEC(ps, pathspec_free); +} + +int git_pathspec_matches_path( + const git_pathspec *ps, uint32_t flags, const char *path) +{ + bool no_fnmatch = (flags & GIT_PATHSPEC_NO_GLOB) != 0; + bool casefold = (flags & GIT_PATHSPEC_IGNORE_CASE) != 0; + + GIT_ASSERT_ARG(ps); + GIT_ASSERT_ARG(path); + + return (0 != git_pathspec__match( + &ps->pathspec, path, no_fnmatch, casefold, NULL, NULL)); +} + +static void pathspec_match_free(git_pathspec_match_list *m) +{ + if (!m) + return; + + git_pathspec_free(m->pathspec); + m->pathspec = NULL; + + git_array_clear(m->matches); + git_array_clear(m->failures); + git_pool_clear(&m->pool); + git__free(m); +} + +static git_pathspec_match_list *pathspec_match_alloc( + git_pathspec *ps, int datatype) +{ + git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list)); + if (!m) + return NULL; + + if (git_pool_init(&m->pool, 1) < 0) + return NULL; + + /* need to keep reference to pathspec and increment refcount because + * failures array stores pointers to the pattern strings of the + * pathspec that had no matches + */ + GIT_REFCOUNT_INC(ps); + m->pathspec = ps; + m->datatype = datatype; + + return m; +} + +GIT_INLINE(size_t) pathspec_mark_pattern(git_bitvec *used, size_t pos) +{ + if (!git_bitvec_get(used, pos)) { + git_bitvec_set(used, pos, true); + return 1; + } + + return 0; +} + +static size_t pathspec_mark_remaining( + git_bitvec *used, + git_vector *patterns, + struct pathspec_match_context *ctxt, + size_t start, + const char *path0, + const char *path1) +{ + size_t count = 0; + + if (path1 == path0) + path1 = NULL; + + for (; start < patterns->length; ++start) { + const git_attr_fnmatch *pat = git_vector_get(patterns, start); + + if (git_bitvec_get(used, start)) + continue; + + if (path0 && pathspec_match_one(pat, ctxt, path0) > 0) + count += pathspec_mark_pattern(used, start); + else if (path1 && pathspec_match_one(pat, ctxt, path1) > 0) + count += pathspec_mark_pattern(used, start); + } + + return count; +} + +static int pathspec_build_failure_array( + git_pathspec_string_array_t *failures, + git_vector *patterns, + git_bitvec *used, + git_pool *pool) +{ + size_t pos; + char **failed; + const git_attr_fnmatch *pat; + + for (pos = 0; pos < patterns->length; ++pos) { + if (git_bitvec_get(used, pos)) + continue; + + if ((failed = git_array_alloc(*failures)) == NULL) + return -1; + + pat = git_vector_get(patterns, pos); + + if ((*failed = git_pool_strdup(pool, pat->pattern)) == NULL) + return -1; + } + + return 0; +} + +static int pathspec_match_from_iterator( + git_pathspec_match_list **out, + git_iterator *iter, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_pathspec_match_list *m = NULL; + const git_index_entry *entry = NULL; + struct pathspec_match_context ctxt; + git_vector *patterns = &ps->pathspec; + bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; + bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; + size_t pos, used_ct = 0, found_files = 0; + git_index *index = NULL; + git_bitvec used_patterns; + char **file; + + if (git_bitvec_init(&used_patterns, patterns->length) < 0) + return -1; + + if (out) { + *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_STRINGS); + GIT_ERROR_CHECK_ALLOC(m); + } + + if ((error = git_iterator_reset_range(iter, ps->prefix, ps->prefix)) < 0) + goto done; + + if (git_iterator_type(iter) == GIT_ITERATOR_WORKDIR && + (error = git_repository_index__weakptr( + &index, git_iterator_owner(iter))) < 0) + goto done; + + pathspec_match_context_init( + &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, + git_iterator_ignore_case(iter)); + + while (!(error = git_iterator_advance(&entry, iter))) { + /* search for match with entry->path */ + int result = git_pathspec__match_at( + &pos, patterns, &ctxt, entry->path, NULL); + + /* no matches for this path */ + if (result < 0) + continue; + + /* if result was a negative pattern match, then don't list file */ + if (!result) { + used_ct += pathspec_mark_pattern(&used_patterns, pos); + continue; + } + + /* check if path is ignored and untracked */ + if (index != NULL && + git_iterator_current_is_ignored(iter) && + git_index__find_pos(NULL, index, entry->path, 0, GIT_INDEX_STAGE_ANY) < 0) + continue; + + /* mark the matched pattern as used */ + used_ct += pathspec_mark_pattern(&used_patterns, pos); + ++found_files; + + /* if find_failures is on, check if any later patterns also match */ + if (find_failures && used_ct < patterns->length) + used_ct += pathspec_mark_remaining( + &used_patterns, patterns, &ctxt, pos + 1, entry->path, NULL); + + /* if only looking at failures, exit early or just continue */ + if (failures_only || !out) { + if (used_ct == patterns->length) + break; + continue; + } + + /* insert matched path into matches array */ + if ((file = (char **)git_array_alloc(m->matches)) == NULL || + (*file = git_pool_strdup(&m->pool, entry->path)) == NULL) { + error = -1; + goto done; + } + } + + if (error < 0 && error != GIT_ITEROVER) + goto done; + error = 0; + + /* insert patterns that had no matches into failures array */ + if (find_failures && used_ct < patterns->length && + (error = pathspec_build_failure_array( + &m->failures, patterns, &used_patterns, &m->pool)) < 0) + goto done; + + /* if every pattern failed to match, then we have failed */ + if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) { + git_error_set(GIT_ERROR_INVALID, "no matching files were found"); + error = GIT_ENOTFOUND; + } + +done: + git_bitvec_free(&used_patterns); + + if (error < 0) { + pathspec_match_free(m); + if (out) *out = NULL; + } + + return error; +} + +static git_iterator_flag_t pathspec_match_iter_flags(uint32_t flags) +{ + git_iterator_flag_t f = 0; + + if ((flags & GIT_PATHSPEC_IGNORE_CASE) != 0) + f |= GIT_ITERATOR_IGNORE_CASE; + else if ((flags & GIT_PATHSPEC_USE_CASE) != 0) + f |= GIT_ITERATOR_DONT_IGNORE_CASE; + + return f; +} + +int git_pathspec_match_workdir( + git_pathspec_match_list **out, + git_repository *repo, + uint32_t flags, + git_pathspec *ps) +{ + git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error = 0; + + GIT_ASSERT_ARG(repo); + + iter_opts.flags = pathspec_match_iter_flags(flags); + + if (!(error = git_iterator_for_workdir(&iter, repo, NULL, NULL, &iter_opts))) { + error = pathspec_match_from_iterator(out, iter, flags, ps); + git_iterator_free(iter); + } + + return error; +} + +int git_pathspec_match_index( + git_pathspec_match_list **out, + git_index *index, + uint32_t flags, + git_pathspec *ps) +{ + git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error = 0; + + GIT_ASSERT_ARG(index); + + iter_opts.flags = pathspec_match_iter_flags(flags); + + if (!(error = git_iterator_for_index(&iter, git_index_owner(index), index, &iter_opts))) { + error = pathspec_match_from_iterator(out, iter, flags, ps); + git_iterator_free(iter); + } + + return error; +} + +int git_pathspec_match_tree( + git_pathspec_match_list **out, + git_tree *tree, + uint32_t flags, + git_pathspec *ps) +{ + git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error = 0; + + GIT_ASSERT_ARG(tree); + + iter_opts.flags = pathspec_match_iter_flags(flags); + + if (!(error = git_iterator_for_tree(&iter, tree, &iter_opts))) { + error = pathspec_match_from_iterator(out, iter, flags, ps); + git_iterator_free(iter); + } + + return error; +} + +int git_pathspec_match_diff( + git_pathspec_match_list **out, + git_diff *diff, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_pathspec_match_list *m = NULL; + struct pathspec_match_context ctxt; + git_vector *patterns = &ps->pathspec; + bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; + bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; + size_t i, pos, used_ct = 0, found_deltas = 0; + const git_diff_delta *delta, **match; + git_bitvec used_patterns; + + GIT_ASSERT_ARG(diff); + + if (git_bitvec_init(&used_patterns, patterns->length) < 0) + return -1; + + if (out) { + *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_DIFF); + GIT_ERROR_CHECK_ALLOC(m); + } + + pathspec_match_context_init( + &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, + git_diff_is_sorted_icase(diff)); + + git_vector_foreach(&diff->deltas, i, delta) { + /* search for match with delta */ + int result = git_pathspec__match_at( + &pos, patterns, &ctxt, delta->old_file.path, delta->new_file.path); + + /* no matches for this path */ + if (result < 0) + continue; + + /* mark the matched pattern as used */ + used_ct += pathspec_mark_pattern(&used_patterns, pos); + + /* if result was a negative pattern match, then don't list file */ + if (!result) + continue; + + ++found_deltas; + + /* if find_failures is on, check if any later patterns also match */ + if (find_failures && used_ct < patterns->length) + used_ct += pathspec_mark_remaining( + &used_patterns, patterns, &ctxt, pos + 1, + delta->old_file.path, delta->new_file.path); + + /* if only looking at failures, exit early or just continue */ + if (failures_only || !out) { + if (used_ct == patterns->length) + break; + continue; + } + + /* insert matched delta into matches array */ + if (!(match = (const git_diff_delta **)git_array_alloc(m->matches))) { + error = -1; + goto done; + } else { + *match = delta; + } + } + + /* insert patterns that had no matches into failures array */ + if (find_failures && used_ct < patterns->length && + (error = pathspec_build_failure_array( + &m->failures, patterns, &used_patterns, &m->pool)) < 0) + goto done; + + /* if every pattern failed to match, then we have failed */ + if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_deltas) { + git_error_set(GIT_ERROR_INVALID, "no matching deltas were found"); + error = GIT_ENOTFOUND; + } + +done: + git_bitvec_free(&used_patterns); + + if (error < 0) { + pathspec_match_free(m); + if (out) *out = NULL; + } + + return error; +} + +void git_pathspec_match_list_free(git_pathspec_match_list *m) +{ + if (m) + pathspec_match_free(m); +} + +size_t git_pathspec_match_list_entrycount( + const git_pathspec_match_list *m) +{ + return m ? git_array_size(m->matches) : 0; +} + +const char *git_pathspec_match_list_entry( + const git_pathspec_match_list *m, size_t pos) +{ + if (!m || m->datatype != PATHSPEC_DATATYPE_STRINGS || + !git_array_valid_index(m->matches, pos)) + return NULL; + + return *((const char **)git_array_get(m->matches, pos)); +} + +const git_diff_delta *git_pathspec_match_list_diff_entry( + const git_pathspec_match_list *m, size_t pos) +{ + if (!m || m->datatype != PATHSPEC_DATATYPE_DIFF || + !git_array_valid_index(m->matches, pos)) + return NULL; + + return *((const git_diff_delta **)git_array_get(m->matches, pos)); +} + +size_t git_pathspec_match_list_failed_entrycount( + const git_pathspec_match_list *m) +{ + return m ? git_array_size(m->failures) : 0; +} + +const char * git_pathspec_match_list_failed_entry( + const git_pathspec_match_list *m, size_t pos) +{ + char **entry = m ? git_array_get(m->failures, pos) : NULL; + + return entry ? *entry : NULL; +} diff --git a/src/libgit2/pathspec.h b/src/libgit2/pathspec.h new file mode 100644 index 000000000..0256cb927 --- /dev/null +++ b/src/libgit2/pathspec.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_pathspec_h__ +#define INCLUDE_pathspec_h__ + +#include "common.h" + +#include "git2/pathspec.h" +#include "str.h" +#include "vector.h" +#include "pool.h" +#include "array.h" + +/* public compiled pathspec */ +struct git_pathspec { + git_refcount rc; + char *prefix; + git_vector pathspec; + git_pool pool; +}; + +enum { + PATHSPEC_DATATYPE_STRINGS = 0, + PATHSPEC_DATATYPE_DIFF = 1 +}; + +typedef git_array_t(char *) git_pathspec_string_array_t; + +/* public interface to pathspec matching */ +struct git_pathspec_match_list { + git_pathspec *pathspec; + git_array_t(void *) matches; + git_pathspec_string_array_t failures; + git_pool pool; + int datatype; +}; + +/* what is the common non-wildcard prefix for all items in the pathspec */ +extern char *git_pathspec_prefix(const git_strarray *pathspec); + +/* is there anything in the spec that needs to be filtered on */ +extern bool git_pathspec_is_empty(const git_strarray *pathspec); + +/* build a vector of fnmatch patterns to evaluate efficiently */ +extern int git_pathspec__vinit( + git_vector *vspec, const git_strarray *strspec, git_pool *strpool); + +/* free data from the pathspec vector */ +extern void git_pathspec__vfree(git_vector *vspec); + +#define GIT_PATHSPEC_NOMATCH ((size_t)-1) + +/* + * Match a path against the vectorized pathspec. + * The matched pathspec is passed back into the `matched_pathspec` parameter, + * unless it is passed as NULL by the caller. + */ +extern bool git_pathspec__match( + const git_vector *vspec, + const char *path, + bool disable_fnmatch, + bool casefold, + const char **matched_pathspec, + size_t *matched_at); + +/* easy pathspec setup */ + +extern int git_pathspec__init(git_pathspec *ps, const git_strarray *paths); + +extern void git_pathspec__clear(git_pathspec *ps); + +#endif diff --git a/src/libgit2/pool.c b/src/libgit2/pool.c new file mode 100644 index 000000000..16ffa398d --- /dev/null +++ b/src/libgit2/pool.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "pool.h" + +#include "posix.h" +#ifndef GIT_WIN32 +#include +#endif + +struct git_pool_page { + git_pool_page *next; + size_t size; + size_t avail; + GIT_ALIGN(char data[GIT_FLEX_ARRAY], 8); +}; + +static void *pool_alloc_page(git_pool *pool, size_t size); + +#ifndef GIT_DEBUG_POOL + +static size_t system_page_size = 0; + +int git_pool_global_init(void) +{ + if (git__page_size(&system_page_size) < 0) + system_page_size = 4096; + /* allow space for malloc overhead */ + system_page_size -= (2 * sizeof(void *)) + sizeof(git_pool_page); + return 0; +} + +int git_pool_init(git_pool *pool, size_t item_size) +{ + GIT_ASSERT_ARG(pool); + GIT_ASSERT_ARG(item_size >= 1); + + memset(pool, 0, sizeof(git_pool)); + pool->item_size = item_size; + pool->page_size = system_page_size; + + return 0; +} + +void git_pool_clear(git_pool *pool) +{ + git_pool_page *scan, *next; + + for (scan = pool->pages; scan != NULL; scan = next) { + next = scan->next; + git__free(scan); + } + + pool->pages = NULL; +} + +static void *pool_alloc_page(git_pool *pool, size_t size) +{ + git_pool_page *page; + const size_t new_page_size = (size <= pool->page_size) ? pool->page_size : size; + size_t alloc_size; + + if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, new_page_size, sizeof(git_pool_page)) || + !(page = git__malloc(alloc_size))) + return NULL; + + page->size = new_page_size; + page->avail = new_page_size - size; + page->next = pool->pages; + + pool->pages = page; + + return page->data; +} + +static void *pool_alloc(git_pool *pool, size_t size) +{ + git_pool_page *page = pool->pages; + void *ptr = NULL; + + if (!page || page->avail < size) + return pool_alloc_page(pool, size); + + ptr = &page->data[page->size - page->avail]; + page->avail -= size; + + return ptr; +} + +uint32_t git_pool__open_pages(git_pool *pool) +{ + uint32_t ct = 0; + git_pool_page *scan; + for (scan = pool->pages; scan != NULL; scan = scan->next) ct++; + return ct; +} + +bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) +{ + git_pool_page *scan; + for (scan = pool->pages; scan != NULL; scan = scan->next) + if ((void *)scan->data <= ptr && + (void *)(((char *)scan->data) + scan->size) > ptr) + return true; + return false; +} + +#else + +int git_pool_global_init(void) +{ + return 0; +} + +static int git_pool__ptr_cmp(const void * a, const void * b) +{ + if(a > b) { + return 1; + } + if(a < b) { + return -1; + } + else { + return 0; + } +} + +int git_pool_init(git_pool *pool, size_t item_size) +{ + GIT_ASSERT_ARG(pool); + GIT_ASSERT_ARG(item_size >= 1); + + memset(pool, 0, sizeof(git_pool)); + pool->item_size = item_size; + pool->page_size = git_pool__system_page_size(); + git_vector_init(&pool->allocations, 100, git_pool__ptr_cmp); + + return 0; +} + +void git_pool_clear(git_pool *pool) +{ + git_vector_free_deep(&pool->allocations); +} + +static void *pool_alloc(git_pool *pool, size_t size) { + void *ptr = NULL; + if((ptr = git__malloc(size)) == NULL) { + return NULL; + } + git_vector_insert_sorted(&pool->allocations, ptr, NULL); + return ptr; +} + +bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) +{ + size_t pos; + return git_vector_bsearch(&pos, &pool->allocations, ptr) != GIT_ENOTFOUND; +} +#endif + +void git_pool_swap(git_pool *a, git_pool *b) +{ + git_pool temp; + + if (a == b) + return; + + memcpy(&temp, a, sizeof(temp)); + memcpy(a, b, sizeof(temp)); + memcpy(b, &temp, sizeof(temp)); +} + +static size_t alloc_size(git_pool *pool, size_t count) +{ + const size_t align = sizeof(void *) - 1; + + if (pool->item_size > 1) { + const size_t item_size = (pool->item_size + align) & ~align; + return item_size * count; + } + + return (count + align) & ~align; +} + +void *git_pool_malloc(git_pool *pool, size_t items) +{ + return pool_alloc(pool, alloc_size(pool, items)); +} + +void *git_pool_mallocz(git_pool *pool, size_t items) +{ + const size_t size = alloc_size(pool, items); + void *ptr = pool_alloc(pool, size); + if (ptr) + memset(ptr, 0x0, size); + return ptr; +} + +char *git_pool_strndup(git_pool *pool, const char *str, size_t n) +{ + char *ptr = NULL; + + GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(str, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); + + if (n == SIZE_MAX) + return NULL; + + if ((ptr = git_pool_malloc(pool, (n + 1))) != NULL) { + memcpy(ptr, str, n); + ptr[n] = '\0'; + } + + return ptr; +} + +char *git_pool_strdup(git_pool *pool, const char *str) +{ + GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(str, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); + + return git_pool_strndup(pool, str, strlen(str)); +} + +char *git_pool_strdup_safe(git_pool *pool, const char *str) +{ + return str ? git_pool_strdup(pool, str) : NULL; +} + +char *git_pool_strcat(git_pool *pool, const char *a, const char *b) +{ + void *ptr; + size_t len_a, len_b, total; + + GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); + + len_a = a ? strlen(a) : 0; + len_b = b ? strlen(b) : 0; + + if (GIT_ADD_SIZET_OVERFLOW(&total, len_a, len_b) || + GIT_ADD_SIZET_OVERFLOW(&total, total, 1)) + return NULL; + + if ((ptr = git_pool_malloc(pool, total)) != NULL) { + if (len_a) + memcpy(ptr, a, len_a); + if (len_b) + memcpy(((char *)ptr) + len_a, b, len_b); + *(((char *)ptr) + len_a + len_b) = '\0'; + } + return ptr; +} diff --git a/src/libgit2/pool.h b/src/libgit2/pool.h new file mode 100644 index 000000000..cecb84665 --- /dev/null +++ b/src/libgit2/pool.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_pool_h__ +#define INCLUDE_pool_h__ + +#include "common.h" + +#include "vector.h" + +typedef struct git_pool_page git_pool_page; + +#ifndef GIT_DEBUG_POOL +/** + * Chunked allocator. + * + * A `git_pool` can be used when you want to cheaply allocate + * multiple items of the same type and are willing to free them + * all together with a single call. The two most common cases + * are a set of fixed size items (such as lots of OIDs) or a + * bunch of strings. + * + * Internally, a `git_pool` allocates pages of memory and then + * deals out blocks from the trailing unused portion of each page. + * The pages guarantee that the number of actual allocations done + * will be much smaller than the number of items needed. + * + * For examples of how to set up a `git_pool` see `git_pool_init`. + */ +typedef struct { + git_pool_page *pages; /* allocated pages */ + size_t item_size; /* size of single alloc unit in bytes */ + size_t page_size; /* size of page in bytes */ +} git_pool; + +#define GIT_POOL_INIT { NULL, 0, 0 } + +#else + +/** + * Debug chunked allocator. + * + * Acts just like `git_pool` but instead of actually pooling allocations it + * passes them through to `git__malloc`. This makes it possible to easily debug + * systems that use `git_pool` using valgrind. + * + * In order to track allocations during the lifetime of the pool we use a + * `git_vector`. When the pool is deallocated everything in the vector is + * freed. + * + * `API is exactly the same as the standard `git_pool` with one exception. + * Since we aren't allocating pages to hand out in chunks we can't easily + * implement `git_pool__open_pages`. + */ +typedef struct { + git_vector allocations; + size_t item_size; + size_t page_size; +} git_pool; + +#define GIT_POOL_INIT { GIT_VECTOR_INIT, 0, 0 } + +#endif + +/** + * Initialize a pool. + * + * To allocation strings, use like this: + * + * git_pool_init(&string_pool, 1); + * my_string = git_pool_strdup(&string_pool, your_string); + * + * To allocate items of fixed size, use like this: + * + * git_pool_init(&pool, sizeof(item)); + * my_item = git_pool_malloc(&pool, 1); + * + * Of course, you can use this in other ways, but those are the + * two most common patterns. + */ +extern int git_pool_init(git_pool *pool, size_t item_size); + +/** + * Free all items in pool + */ +extern void git_pool_clear(git_pool *pool); + +/** + * Swap two pools with one another + */ +extern void git_pool_swap(git_pool *a, git_pool *b); + +/** + * Allocate space for one or more items from a pool. + */ +extern void *git_pool_malloc(git_pool *pool, size_t items); +extern void *git_pool_mallocz(git_pool *pool, size_t items); + +/** + * Allocate space and duplicate string data into it. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n); + +/** + * Allocate space and duplicate a string into it. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strdup(git_pool *pool, const char *str); + +/** + * Allocate space and duplicate a string into it, NULL is no error. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strdup_safe(git_pool *pool, const char *str); + +/** + * Allocate space for the concatenation of two strings. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b); + +/* + * Misc utilities + */ +#ifndef GIT_DEBUG_POOL +extern uint32_t git_pool__open_pages(git_pool *pool); +#endif +extern bool git_pool__ptr_in_pool(git_pool *pool, void *ptr); + +/** + * This function is being called by our global setup routines to + * initialize the system pool size. + * + * @return 0 on success, <0 on failure + */ +extern int git_pool_global_init(void); + +#endif diff --git a/src/libgit2/posix.c b/src/libgit2/posix.c new file mode 100644 index 000000000..b1f85dc94 --- /dev/null +++ b/src/libgit2/posix.c @@ -0,0 +1,303 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "posix.h" + +#include "fs_path.h" +#include +#include + +size_t p_fsync__cnt = 0; + +#ifndef GIT_WIN32 + +#ifdef NO_ADDRINFO + +int p_getaddrinfo( + const char *host, + const char *port, + struct addrinfo *hints, + struct addrinfo **info) +{ + struct addrinfo *ainfo, *ai; + int p = 0; + + GIT_UNUSED(hints); + + if ((ainfo = git__malloc(sizeof(struct addrinfo))) == NULL) + return -1; + + if ((ainfo->ai_hostent = gethostbyname(host)) == NULL) { + git__free(ainfo); + return -2; + } + + ainfo->ai_servent = getservbyname(port, 0); + + if (ainfo->ai_servent) + ainfo->ai_port = ainfo->ai_servent->s_port; + else + ainfo->ai_port = htons(atol(port)); + + memcpy(&ainfo->ai_addr_in.sin_addr, + ainfo->ai_hostent->h_addr_list[0], + ainfo->ai_hostent->h_length); + + ainfo->ai_protocol = 0; + ainfo->ai_socktype = hints->ai_socktype; + ainfo->ai_family = ainfo->ai_hostent->h_addrtype; + ainfo->ai_addr_in.sin_family = ainfo->ai_family; + ainfo->ai_addr_in.sin_port = ainfo->ai_port; + ainfo->ai_addr = (struct addrinfo *)&ainfo->ai_addr_in; + ainfo->ai_addrlen = sizeof(struct sockaddr_in); + + *info = ainfo; + + if (ainfo->ai_hostent->h_addr_list[1] == NULL) { + ainfo->ai_next = NULL; + return 0; + } + + ai = ainfo; + + for (p = 1; ainfo->ai_hostent->h_addr_list[p] != NULL; p++) { + if (!(ai->ai_next = git__malloc(sizeof(struct addrinfo)))) { + p_freeaddrinfo(ainfo); + return -1; + } + memcpy(ai->ai_next, ainfo, sizeof(struct addrinfo)); + memcpy(&ai->ai_next->ai_addr_in.sin_addr, + ainfo->ai_hostent->h_addr_list[p], + ainfo->ai_hostent->h_length); + ai->ai_next->ai_addr = (struct addrinfo *)&ai->ai_next->ai_addr_in; + ai = ai->ai_next; + } + + ai->ai_next = NULL; + return 0; +} + +void p_freeaddrinfo(struct addrinfo *info) +{ + struct addrinfo *p, *next; + + p = info; + + while(p != NULL) { + next = p->ai_next; + git__free(p); + p = next; + } +} + +const char *p_gai_strerror(int ret) +{ + switch(ret) { + case -1: return "Out of memory"; break; + case -2: return "Address lookup failed"; break; + default: return "Unknown error"; break; + } +} + +#endif /* NO_ADDRINFO */ + +int p_open(const char *path, volatile int flags, ...) +{ + mode_t mode = 0; + + #ifdef GIT_DEBUG_STRICT_OPEN + if (strstr(path, "//") != NULL) { + errno = EACCES; + return -1; + } + #endif + + if (flags & O_CREAT) { + va_list arg_list; + + va_start(arg_list, flags); + mode = (mode_t)va_arg(arg_list, int); + va_end(arg_list); + } + + return open(path, flags | O_BINARY | O_CLOEXEC, mode); +} + +int p_creat(const char *path, mode_t mode) +{ + return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC, mode); +} + +int p_getcwd(char *buffer_out, size_t size) +{ + char *cwd_buffer; + + GIT_ASSERT_ARG(buffer_out); + GIT_ASSERT_ARG(size > 0); + + cwd_buffer = getcwd(buffer_out, size); + + if (cwd_buffer == NULL) + return -1; + + git_fs_path_mkposix(buffer_out); + git_fs_path_string_to_dir(buffer_out, size); /* append trailing slash */ + + return 0; +} + +int p_rename(const char *from, const char *to) +{ + if (!link(from, to)) { + p_unlink(from); + return 0; + } + + if (!rename(from, to)) + return 0; + + return -1; +} + +#endif /* GIT_WIN32 */ + +ssize_t p_read(git_file fd, void *buf, size_t cnt) +{ + char *b = buf; + + if (!git__is_ssizet(cnt)) { +#ifdef GIT_WIN32 + SetLastError(ERROR_INVALID_PARAMETER); +#endif + errno = EINVAL; + return -1; + } + + while (cnt) { + ssize_t r; +#ifdef GIT_WIN32 + r = read(fd, b, cnt > INT_MAX ? INT_MAX : (unsigned int)cnt); +#else + r = read(fd, b, cnt); +#endif + if (r < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!r) + break; + cnt -= r; + b += r; + } + return (b - (char *)buf); +} + +int p_write(git_file fd, const void *buf, size_t cnt) +{ + const char *b = buf; + + while (cnt) { + ssize_t r; +#ifdef GIT_WIN32 + GIT_ASSERT((size_t)((unsigned int)cnt) == cnt); + r = write(fd, b, (unsigned int)cnt); +#else + r = write(fd, b, cnt); +#endif + if (r < 0) { + if (errno == EINTR || GIT_ISBLOCKED(errno)) + continue; + return -1; + } + if (!r) { + errno = EPIPE; + return -1; + } + cnt -= r; + b += r; + } + return 0; +} + +#ifdef NO_MMAP + +#include "map.h" + +int git__page_size(size_t *page_size) +{ + /* dummy; here we don't need any alignment anyway */ + *page_size = 4096; + return 0; +} + +int git__mmap_alignment(size_t *alignment) +{ + /* dummy; here we don't need any alignment anyway */ + *alignment = 4096; + return 0; +} + + +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) +{ + const char *ptr; + size_t remaining_len; + + GIT_MMAP_VALIDATE(out, len, prot, flags); + + /* writes cannot be emulated without handling pagefaults since write happens by + * writing to mapped memory */ + if (prot & GIT_PROT_WRITE) { + git_error_set(GIT_ERROR_OS, "trying to map %s-writeable", + ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) ? "shared": "private"); + return -1; + } + + if (!git__is_ssizet(len)) { + errno = EINVAL; + return -1; + } + + out->len = 0; + out->data = git__malloc(len); + GIT_ERROR_CHECK_ALLOC(out->data); + + remaining_len = len; + ptr = (const char *)out->data; + while (remaining_len > 0) { + ssize_t nb; + HANDLE_EINTR(nb, p_pread(fd, (void *)ptr, remaining_len, offset)); + if (nb <= 0) { + git_error_set(GIT_ERROR_OS, "mmap emulation failed"); + git__free(out->data); + out->data = NULL; + return -1; + } + + ptr += nb; + offset += nb; + remaining_len -= nb; + } + + out->len = len; + return 0; +} + +int p_munmap(git_map *map) +{ + GIT_ASSERT_ARG(map); + git__free(map->data); + + /* Initializing will help debug use-after-free */ + map->len = 0; + map->data = NULL; + + return 0; +} + +#endif diff --git a/src/libgit2/posix.h b/src/libgit2/posix.h new file mode 100644 index 000000000..e6f603078 --- /dev/null +++ b/src/libgit2/posix.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_posix_h__ +#define INCLUDE_posix_h__ + +#include "common.h" + +#include +#include +#include + +/* stat: file mode type testing macros */ +#ifndef S_IFGITLINK +#define S_IFGITLINK 0160000 +#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) +#endif + +#ifndef S_IFLNK +#define S_IFLNK 0120000 +#undef _S_IFLNK +#define _S_IFLNK S_IFLNK +#endif + +#ifndef S_IWUSR +#define S_IWUSR 00200 +#endif + +#ifndef S_IXUSR +#define S_IXUSR 00100 +#endif + +#ifndef S_ISLNK +#define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) +#endif + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) +#endif + +#ifndef S_ISREG +#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) +#endif + +#ifndef S_ISFIFO +#define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO) +#endif + +/* if S_ISGID is not defined, then don't try to set it */ +#ifndef S_ISGID +#define S_ISGID 0 +#endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif +#ifndef SOCK_CLOEXEC +#define SOCK_CLOEXEC 0 +#endif + +/* access() mode parameter #defines */ +#ifndef F_OK +#define F_OK 0 /* existence check */ +#endif +#ifndef W_OK +#define W_OK 2 /* write mode check */ +#endif +#ifndef R_OK +#define R_OK 4 /* read mode check */ +#endif + +/* Determine whether an errno value indicates that a read or write failed + * because the descriptor is blocked. + */ +#if defined(EWOULDBLOCK) +#define GIT_ISBLOCKED(e) ((e) == EAGAIN || (e) == EWOULDBLOCK) +#else +#define GIT_ISBLOCKED(e) ((e) == EAGAIN) +#endif + +/* define some standard errnos that the runtime may be missing. for example, + * mingw lacks EAFNOSUPPORT. */ +#ifndef EAFNOSUPPORT +#define EAFNOSUPPORT (INT_MAX-1) +#endif + +/* Compiler independent macro to handle signal interrpted system calls */ +#define HANDLE_EINTR(result, x) do { \ + result = (x); \ + } while (result == -1 && errno == EINTR); + + +/* Provide a 64-bit size for offsets. */ + +#if defined(_MSC_VER) +typedef __int64 off64_t; +#elif defined(__HAIKU__) +typedef __haiku_std_int64 off64_t; +#elif defined(__APPLE__) +typedef __int64_t off64_t; +#else +typedef int64_t off64_t; +#endif + +typedef int git_file; + +/** + * Standard POSIX Methods + * + * All the methods starting with the `p_` prefix are + * direct ports of the standard POSIX methods. + * + * Some of the methods are slightly wrapped to provide + * saner defaults. Some of these methods are emulated + * in Windows platforms. + * + * Use your manpages to check the docs on these. + */ + +extern ssize_t p_read(git_file fd, void *buf, size_t cnt); +extern int p_write(git_file fd, const void *buf, size_t cnt); + +extern ssize_t p_pread(int fd, void *data, size_t size, off64_t offset); +extern ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset); + +#define p_close(fd) close(fd) +#define p_umask(m) umask(m) + +extern int p_open(const char *path, int flags, ...); +extern int p_creat(const char *path, mode_t mode); +extern int p_getcwd(char *buffer_out, size_t size); +extern int p_rename(const char *from, const char *to); + +extern int git__page_size(size_t *page_size); +extern int git__mmap_alignment(size_t *page_size); + +/* The number of times `p_fsync` has been called. Note that this is for + * test code only; it it not necessarily thread-safe and should not be + * relied upon in production. + */ +extern size_t p_fsync__cnt; + +/** + * Platform-dependent methods + */ +#ifdef GIT_WIN32 +# include "win32/posix.h" +#else +# include "unix/posix.h" +#endif + +#include "strnlen.h" + +#ifdef NO_READDIR_R +GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) +{ + GIT_UNUSED(entry); + *result = readdir(dirp); + return 0; +} +#else /* NO_READDIR_R */ +# define p_readdir_r(d,e,r) readdir_r(d,e,r) +#endif + +#ifdef NO_ADDRINFO +# include +struct addrinfo { + struct hostent *ai_hostent; + struct servent *ai_servent; + struct sockaddr_in ai_addr_in; + struct sockaddr *ai_addr; + size_t ai_addrlen; + int ai_family; + int ai_socktype; + int ai_protocol; + long ai_port; + struct addrinfo *ai_next; +}; + +extern int p_getaddrinfo(const char *host, const char *port, + struct addrinfo *hints, struct addrinfo **info); +extern void p_freeaddrinfo(struct addrinfo *info); +extern const char *p_gai_strerror(int ret); +#else +# define p_getaddrinfo(a, b, c, d) getaddrinfo(a, b, c, d) +# define p_freeaddrinfo(a) freeaddrinfo(a) +# define p_gai_strerror(c) gai_strerror(c) +#endif /* NO_ADDRINFO */ + +#endif diff --git a/src/libgit2/pqueue.c b/src/libgit2/pqueue.c new file mode 100644 index 000000000..3820e999c --- /dev/null +++ b/src/libgit2/pqueue.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "pqueue.h" + +#include "util.h" + +#define PQUEUE_LCHILD_OF(I) (((I)<<1)+1) +#define PQUEUE_RCHILD_OF(I) (((I)<<1)+2) +#define PQUEUE_PARENT_OF(I) (((I)-1)>>1) + +int git_pqueue_init( + git_pqueue *pq, + uint32_t flags, + size_t init_size, + git_vector_cmp cmp) +{ + int error = git_vector_init(pq, init_size, cmp); + + if (!error) { + /* mix in our flags */ + pq->flags |= flags; + + /* if fixed size heap, pretend vector is exactly init_size elements */ + if ((flags & GIT_PQUEUE_FIXED_SIZE) && init_size > 0) + pq->_alloc_size = init_size; + } + + return error; +} + +static void pqueue_up(git_pqueue *pq, size_t el) +{ + size_t parent_el = PQUEUE_PARENT_OF(el); + void *kid = git_vector_get(pq, el); + + while (el > 0) { + void *parent = pq->contents[parent_el]; + + if (pq->_cmp(parent, kid) <= 0) + break; + + pq->contents[el] = parent; + + el = parent_el; + parent_el = PQUEUE_PARENT_OF(el); + } + + pq->contents[el] = kid; +} + +static void pqueue_down(git_pqueue *pq, size_t el) +{ + void *parent = git_vector_get(pq, el), *kid, *rkid; + + while (1) { + size_t kid_el = PQUEUE_LCHILD_OF(el); + + if ((kid = git_vector_get(pq, kid_el)) == NULL) + break; + + if ((rkid = git_vector_get(pq, kid_el + 1)) != NULL && + pq->_cmp(kid, rkid) > 0) { + kid = rkid; + kid_el += 1; + } + + if (pq->_cmp(parent, kid) <= 0) + break; + + pq->contents[el] = kid; + el = kid_el; + } + + pq->contents[el] = parent; +} + +int git_pqueue_insert(git_pqueue *pq, void *item) +{ + int error = 0; + + /* if heap is full, pop the top element if new one should replace it */ + if ((pq->flags & GIT_PQUEUE_FIXED_SIZE) != 0 && + pq->length >= pq->_alloc_size) + { + /* skip this item if below min item in heap or if + * we do not have a comparison function */ + if (!pq->_cmp || pq->_cmp(item, git_vector_get(pq, 0)) <= 0) + return 0; + /* otherwise remove the min item before inserting new */ + (void)git_pqueue_pop(pq); + } + + if (!(error = git_vector_insert(pq, item)) && pq->_cmp) + pqueue_up(pq, pq->length - 1); + + return error; +} + +void *git_pqueue_pop(git_pqueue *pq) +{ + void *rval; + + if (!pq->_cmp) { + rval = git_vector_last(pq); + } else { + rval = git_pqueue_get(pq, 0); + } + + if (git_pqueue_size(pq) > 1 && pq->_cmp) { + /* move last item to top of heap, shrink, and push item down */ + pq->contents[0] = git_vector_last(pq); + git_vector_pop(pq); + pqueue_down(pq, 0); + } else { + /* all we need to do is shrink the heap in this case */ + git_vector_pop(pq); + } + + return rval; +} diff --git a/src/libgit2/pqueue.h b/src/libgit2/pqueue.h new file mode 100644 index 000000000..4db74ea03 --- /dev/null +++ b/src/libgit2/pqueue.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_pqueue_h__ +#define INCLUDE_pqueue_h__ + +#include "common.h" + +#include "vector.h" + +typedef git_vector git_pqueue; + +enum { + /* flag meaning: don't grow heap, keep highest values only */ + GIT_PQUEUE_FIXED_SIZE = (GIT_VECTOR_FLAG_MAX << 1) +}; + +/** + * Initialize priority queue + * + * @param pq The priority queue struct to initialize + * @param flags Flags (see above) to control queue behavior + * @param init_size The initial queue size + * @param cmp The entry priority comparison function + * @return 0 on success, <0 on error + */ +extern int git_pqueue_init( + git_pqueue *pq, + uint32_t flags, + size_t init_size, + git_vector_cmp cmp); + +#define git_pqueue_free git_vector_free +#define git_pqueue_clear git_vector_clear +#define git_pqueue_size git_vector_length +#define git_pqueue_get git_vector_get +#define git_pqueue_reverse git_vector_reverse + +/** + * Insert a new item into the queue + * + * @param pq The priority queue + * @param item Pointer to the item data + * @return 0 on success, <0 on failure + */ +extern int git_pqueue_insert(git_pqueue *pq, void *item); + +/** + * Remove the top item in the priority queue + * + * @param pq The priority queue + * @return item from heap on success, NULL if queue is empty + */ +extern void *git_pqueue_pop(git_pqueue *pq); + +#endif diff --git a/src/libgit2/proxy.c b/src/libgit2/proxy.c new file mode 100644 index 000000000..ef91ad6ea --- /dev/null +++ b/src/libgit2/proxy.c @@ -0,0 +1,49 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "proxy.h" + +#include "git2/proxy.h" + +int git_proxy_options_init(git_proxy_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_proxy_options, GIT_PROXY_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_proxy_init_options(git_proxy_options *opts, unsigned int version) +{ + return git_proxy_options_init(opts, version); +} +#endif + +int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src) +{ + if (!src) { + git_proxy_options_init(tgt, GIT_PROXY_OPTIONS_VERSION); + return 0; + } + + memcpy(tgt, src, sizeof(git_proxy_options)); + if (src->url) { + tgt->url = git__strdup(src->url); + GIT_ERROR_CHECK_ALLOC(tgt->url); + } + + return 0; +} + +void git_proxy_options_dispose(git_proxy_options *opts) +{ + if (!opts) + return; + + git__free((char *) opts->url); + opts->url = NULL; +} diff --git a/src/libgit2/proxy.h b/src/libgit2/proxy.h new file mode 100644 index 000000000..7c0ab598d --- /dev/null +++ b/src/libgit2/proxy.h @@ -0,0 +1,17 @@ +/* +* Copyright (C) the libgit2 contributors. All rights reserved. +* +* This file is part of libgit2, distributed under the GNU GPL v2 with +* a Linking Exception. For full terms see the included COPYING file. +*/ +#ifndef INCLUDE_proxy_h__ +#define INCLUDE_proxy_h__ + +#include "common.h" + +#include "git2/proxy.h" + +extern int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src); +extern void git_proxy_options_dispose(git_proxy_options *opts); + +#endif diff --git a/src/libgit2/push.c b/src/libgit2/push.c new file mode 100644 index 000000000..da8aebadd --- /dev/null +++ b/src/libgit2/push.c @@ -0,0 +1,558 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "push.h" + +#include "git2.h" + +#include "pack.h" +#include "pack-objects.h" +#include "remote.h" +#include "vector.h" +#include "tree.h" + +static int push_spec_rref_cmp(const void *a, const void *b) +{ + const push_spec *push_spec_a = a, *push_spec_b = b; + + return strcmp(push_spec_a->refspec.dst, push_spec_b->refspec.dst); +} + +static int push_status_ref_cmp(const void *a, const void *b) +{ + const push_status *push_status_a = a, *push_status_b = b; + + return strcmp(push_status_a->ref, push_status_b->ref); +} + +int git_push_new(git_push **out, git_remote *remote, const git_push_options *opts) +{ + git_push *p; + + *out = NULL; + + GIT_ERROR_CHECK_VERSION(opts, GIT_PUSH_OPTIONS_VERSION, "git_push_options"); + + p = git__calloc(1, sizeof(*p)); + GIT_ERROR_CHECK_ALLOC(p); + + p->repo = remote->repo; + p->remote = remote; + p->report_status = 1; + p->pb_parallelism = opts ? opts->pb_parallelism : 1; + + if (opts) { + GIT_ERROR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); + memcpy(&p->callbacks, &opts->callbacks, sizeof(git_remote_callbacks)); + } + + if (git_vector_init(&p->specs, 0, push_spec_rref_cmp) < 0) { + git__free(p); + return -1; + } + + if (git_vector_init(&p->status, 0, push_status_ref_cmp) < 0) { + git_vector_free(&p->specs); + git__free(p); + return -1; + } + + if (git_vector_init(&p->updates, 0, NULL) < 0) { + git_vector_free(&p->status); + git_vector_free(&p->specs); + git__free(p); + return -1; + } + + *out = p; + return 0; +} + +static void free_refspec(push_spec *spec) +{ + if (spec == NULL) + return; + + git_refspec__dispose(&spec->refspec); + git__free(spec); +} + +static int check_rref(char *ref) +{ + if (git__prefixcmp(ref, "refs/")) { + git_error_set(GIT_ERROR_INVALID, "not a valid reference '%s'", ref); + return -1; + } + + return 0; +} + +static int check_lref(git_push *push, char *ref) +{ + /* lref must be resolvable to an existing object */ + git_object *obj; + int error = git_revparse_single(&obj, push->repo, ref); + git_object_free(obj); + + if (!error) + return 0; + + if (error == GIT_ENOTFOUND) + git_error_set(GIT_ERROR_REFERENCE, + "src refspec '%s' does not match any existing object", ref); + else + git_error_set(GIT_ERROR_INVALID, "not a valid reference '%s'", ref); + return -1; +} + +static int parse_refspec(git_push *push, push_spec **spec, const char *str) +{ + push_spec *s; + + *spec = NULL; + + s = git__calloc(1, sizeof(*s)); + GIT_ERROR_CHECK_ALLOC(s); + + if (git_refspec__parse(&s->refspec, str, false) < 0) { + git_error_set(GIT_ERROR_INVALID, "invalid refspec %s", str); + goto on_error; + } + + if (s->refspec.src && s->refspec.src[0] != '\0' && + check_lref(push, s->refspec.src) < 0) { + goto on_error; + } + + if (check_rref(s->refspec.dst) < 0) + goto on_error; + + *spec = s; + return 0; + +on_error: + free_refspec(s); + return -1; +} + +int git_push_add_refspec(git_push *push, const char *refspec) +{ + push_spec *spec; + + if (parse_refspec(push, &spec, refspec) < 0 || + git_vector_insert(&push->specs, spec) < 0) + return -1; + + return 0; +} + +int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks) +{ + git_str remote_ref_name = GIT_STR_INIT; + size_t i, j; + git_refspec *fetch_spec; + push_spec *push_spec = NULL; + git_reference *remote_ref; + push_status *status; + int error = 0; + + git_vector_foreach(&push->status, i, status) { + int fire_callback = 1; + + /* Skip unsuccessful updates which have non-empty messages */ + if (status->msg) + continue; + + /* Find the corresponding remote ref */ + fetch_spec = git_remote__matching_refspec(push->remote, status->ref); + if (!fetch_spec) + continue; + + /* Clear the buffer which can be dirty from previous iteration */ + git_str_clear(&remote_ref_name); + + if ((error = git_refspec__transform(&remote_ref_name, fetch_spec, status->ref)) < 0) + goto on_error; + + /* Find matching push ref spec */ + git_vector_foreach(&push->specs, j, push_spec) { + if (!strcmp(push_spec->refspec.dst, status->ref)) + break; + } + + /* Could not find the corresponding push ref spec for this push update */ + if (j == push->specs.length) + continue; + + /* Update the remote ref */ + if (git_oid_is_zero(&push_spec->loid)) { + error = git_reference_lookup(&remote_ref, push->remote->repo, git_str_cstr(&remote_ref_name)); + + if (error >= 0) { + error = git_reference_delete(remote_ref); + git_reference_free(remote_ref); + } + } else { + error = git_reference_create(NULL, push->remote->repo, + git_str_cstr(&remote_ref_name), &push_spec->loid, 1, + "update by push"); + } + + if (error < 0) { + if (error != GIT_ENOTFOUND) + goto on_error; + + git_error_clear(); + fire_callback = 0; + } + + if (fire_callback && callbacks && callbacks->update_tips) { + error = callbacks->update_tips(git_str_cstr(&remote_ref_name), + &push_spec->roid, &push_spec->loid, callbacks->payload); + + if (error < 0) + goto on_error; + } + } + + error = 0; + +on_error: + git_str_dispose(&remote_ref_name); + return error; +} + +/** + * Insert all tags until we find a non-tag object, which is returned + * in `out`. + */ +static int enqueue_tag(git_object **out, git_push *push, git_oid *id) +{ + git_object *obj = NULL, *target = NULL; + int error; + + if ((error = git_object_lookup(&obj, push->repo, id, GIT_OBJECT_TAG)) < 0) + return error; + + while (git_object_type(obj) == GIT_OBJECT_TAG) { + if ((error = git_packbuilder_insert(push->pb, git_object_id(obj), NULL)) < 0) + break; + + if ((error = git_tag_target(&target, (git_tag *) obj)) < 0) + break; + + git_object_free(obj); + obj = target; + } + + if (error < 0) + git_object_free(obj); + else + *out = obj; + + return error; +} + +static int queue_objects(git_push *push) +{ + git_remote_head *head; + push_spec *spec; + git_revwalk *rw; + unsigned int i; + int error = -1; + + if (git_revwalk_new(&rw, push->repo) < 0) + return -1; + + git_revwalk_sorting(rw, GIT_SORT_TIME); + + git_vector_foreach(&push->specs, i, spec) { + git_object_t type; + size_t size; + + if (git_oid_is_zero(&spec->loid)) + /* + * Delete reference on remote side; + * nothing to do here. + */ + continue; + + if (git_oid_equal(&spec->loid, &spec->roid)) + continue; /* up-to-date */ + + if ((error = git_odb_read_header(&size, &type, push->repo->_odb, &spec->loid)) < 0) + goto on_error; + + if (type == GIT_OBJECT_TAG) { + git_object *target; + + if ((error = enqueue_tag(&target, push, &spec->loid)) < 0) + goto on_error; + + if (git_object_type(target) == GIT_OBJECT_COMMIT) { + if ((error = git_revwalk_push(rw, git_object_id(target))) < 0) { + git_object_free(target); + goto on_error; + } + } else { + if ((error = git_packbuilder_insert( + push->pb, git_object_id(target), NULL)) < 0) { + git_object_free(target); + goto on_error; + } + } + git_object_free(target); + } else if ((error = git_revwalk_push(rw, &spec->loid)) < 0) + goto on_error; + + if (!spec->refspec.force) { + git_oid base; + + if (git_oid_is_zero(&spec->roid)) + continue; + + if (!git_odb_exists(push->repo->_odb, &spec->roid)) { + git_error_set(GIT_ERROR_REFERENCE, + "cannot push because a reference that you are trying to update on the remote contains commits that are not present locally."); + error = GIT_ENONFASTFORWARD; + goto on_error; + } + + error = git_merge_base(&base, push->repo, + &spec->loid, &spec->roid); + + if (error == GIT_ENOTFOUND || + (!error && !git_oid_equal(&base, &spec->roid))) { + git_error_set(GIT_ERROR_REFERENCE, + "cannot push non-fastforwardable reference"); + error = GIT_ENONFASTFORWARD; + goto on_error; + } + + if (error < 0) + goto on_error; + } + } + + git_vector_foreach(&push->remote->refs, i, head) { + if (git_oid_is_zero(&head->oid)) + continue; + + if ((error = git_revwalk_hide(rw, &head->oid)) < 0 && + error != GIT_ENOTFOUND && error != GIT_EINVALIDSPEC && error != GIT_EPEEL) + goto on_error; + } + + error = git_packbuilder_insert_walk(push->pb, rw); + +on_error: + git_revwalk_free(rw); + return error; +} + +static int add_update(git_push *push, push_spec *spec) +{ + git_push_update *u = git__calloc(1, sizeof(git_push_update)); + GIT_ERROR_CHECK_ALLOC(u); + + u->src_refname = git__strdup(spec->refspec.src); + GIT_ERROR_CHECK_ALLOC(u->src_refname); + + u->dst_refname = git__strdup(spec->refspec.dst); + GIT_ERROR_CHECK_ALLOC(u->dst_refname); + + git_oid_cpy(&u->src, &spec->roid); + git_oid_cpy(&u->dst, &spec->loid); + + return git_vector_insert(&push->updates, u); +} + +static int calculate_work(git_push *push) +{ + git_remote_head *head; + push_spec *spec; + unsigned int i, j; + + /* Update local and remote oids*/ + + git_vector_foreach(&push->specs, i, spec) { + if (spec->refspec.src && spec->refspec.src[0]!= '\0') { + /* This is a create or update. Local ref must exist. */ + if (git_reference_name_to_id( + &spec->loid, push->repo, spec->refspec.src) < 0) { + git_error_set(GIT_ERROR_REFERENCE, "no such reference '%s'", spec->refspec.src); + return -1; + } + } + + /* Remote ref may or may not (e.g. during create) already exist. */ + git_vector_foreach(&push->remote->refs, j, head) { + if (!strcmp(spec->refspec.dst, head->name)) { + git_oid_cpy(&spec->roid, &head->oid); + break; + } + } + + if (add_update(push, spec) < 0) + return -1; + } + + return 0; +} + +static int do_push(git_push *push) +{ + int error = 0; + git_transport *transport = push->remote->transport; + git_remote_callbacks *callbacks = &push->callbacks; + + if (!transport->push) { + git_error_set(GIT_ERROR_NET, "remote transport doesn't support push"); + error = -1; + goto on_error; + } + + /* + * A pack-file MUST be sent if either create or update command + * is used, even if the server already has all the necessary + * objects. In this case the client MUST send an empty pack-file. + */ + + if ((error = git_packbuilder_new(&push->pb, push->repo)) < 0) + goto on_error; + + git_packbuilder_set_threads(push->pb, push->pb_parallelism); + + if (callbacks && callbacks->pack_progress) + if ((error = git_packbuilder_set_callbacks(push->pb, callbacks->pack_progress, callbacks->payload)) < 0) + goto on_error; + + if ((error = calculate_work(push)) < 0) + goto on_error; + + if (callbacks && callbacks->push_negotiation && + (error = callbacks->push_negotiation((const git_push_update **) push->updates.contents, + push->updates.length, callbacks->payload)) < 0) + goto on_error; + + if ((error = queue_objects(push)) < 0 || + (error = transport->push(transport, push)) < 0) + goto on_error; + +on_error: + git_packbuilder_free(push->pb); + return error; +} + +static int filter_refs(git_remote *remote) +{ + const git_remote_head **heads; + size_t heads_len, i; + + git_vector_clear(&remote->refs); + + if (git_remote_ls(&heads, &heads_len, remote) < 0) + return -1; + + for (i = 0; i < heads_len; i++) { + if (git_vector_insert(&remote->refs, (void *)heads[i]) < 0) + return -1; + } + + return 0; +} + +int git_push_finish(git_push *push) +{ + int error; + + if (!git_remote_connected(push->remote)) { + git_error_set(GIT_ERROR_NET, "remote is disconnected"); + return -1; + } + + if ((error = filter_refs(push->remote)) < 0 || + (error = do_push(push)) < 0) + return error; + + if (!push->unpack_ok) { + error = -1; + git_error_set(GIT_ERROR_NET, "unpacking the sent packfile failed on the remote"); + } + + return error; +} + +int git_push_status_foreach(git_push *push, + int (*cb)(const char *ref, const char *msg, void *data), + void *data) +{ + push_status *status; + unsigned int i; + + git_vector_foreach(&push->status, i, status) { + int error = cb(status->ref, status->msg, data); + if (error) + return git_error_set_after_callback(error); + } + + return 0; +} + +void git_push_status_free(push_status *status) +{ + if (status == NULL) + return; + + git__free(status->msg); + git__free(status->ref); + git__free(status); +} + +void git_push_free(git_push *push) +{ + push_spec *spec; + push_status *status; + git_push_update *update; + unsigned int i; + + if (push == NULL) + return; + + git_vector_foreach(&push->specs, i, spec) { + free_refspec(spec); + } + git_vector_free(&push->specs); + + git_vector_foreach(&push->status, i, status) { + git_push_status_free(status); + } + git_vector_free(&push->status); + + git_vector_foreach(&push->updates, i, update) { + git__free(update->src_refname); + git__free(update->dst_refname); + git__free(update); + } + git_vector_free(&push->updates); + + git__free(push); +} + +int git_push_options_init(git_push_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_push_options, GIT_PUSH_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_push_init_options(git_push_options *opts, unsigned int version) +{ + return git_push_options_init(opts, version); +} +#endif diff --git a/src/libgit2/push.h b/src/libgit2/push.h new file mode 100644 index 000000000..fc72e845e --- /dev/null +++ b/src/libgit2/push.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_push_h__ +#define INCLUDE_push_h__ + +#include "common.h" + +#include "git2.h" +#include "refspec.h" +#include "remote.h" + +typedef struct push_spec { + struct git_refspec refspec; + + git_oid loid; + git_oid roid; +} push_spec; + +typedef struct push_status { + bool ok; + + char *ref; + char *msg; +} push_status; + +struct git_push { + git_repository *repo; + git_packbuilder *pb; + git_remote *remote; + git_vector specs; + git_vector updates; + bool report_status; + + /* report-status */ + bool unpack_ok; + git_vector status; + + /* options */ + unsigned pb_parallelism; + git_remote_callbacks callbacks; +}; + +/** + * Free the given push status object + * + * @param status The push status object + */ +void git_push_status_free(push_status *status); + +/** + * Create a new push object + * + * @param out New push object + * @param remote Remote instance + * @param opts Push options or NULL + * + * @return 0 or an error code + */ +int git_push_new(git_push **out, git_remote *remote, const git_push_options *opts); + +/** + * Add a refspec to be pushed + * + * @param push The push object + * @param refspec Refspec string + * + * @return 0 or an error code + */ +int git_push_add_refspec(git_push *push, const char *refspec); + +/** + * Update remote tips after a push + * + * @param push The push object + * @param callbacks the callbacks to use for this connection + * + * @return 0 or an error code + */ +int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks); + +/** + * Perform the push + * + * This function will return an error in case of a protocol error or + * the server being unable to unpack the data we sent. + * + * The return value does not reflect whether the server accepted or + * refused any reference updates. Use `git_push_status_foreach()` in + * order to find out which updates were accepted or rejected. + * + * @param push The push object + * + * @return 0 or an error code + */ +int git_push_finish(git_push *push); + +/** + * Invoke callback `cb' on each status entry + * + * For each of the updated references, we receive a status report in the + * form of `ok refs/heads/master` or `ng refs/heads/master `. + * `msg != NULL` means the reference has not been updated for the given + * reason. + * + * Return a non-zero value from the callback to stop the loop. + * + * @param push The push object + * @param cb The callback to call on each object + * @param data The payload passed to the callback + * + * @return 0 on success, non-zero callback return value, or error code + */ +int git_push_status_foreach(git_push *push, + int (*cb)(const char *ref, const char *msg, void *data), + void *data); + +/** + * Free the given push object + * + * @param push The push object + */ +void git_push_free(git_push *push); + +#endif diff --git a/src/libgit2/reader.c b/src/libgit2/reader.c new file mode 100644 index 000000000..ba9775240 --- /dev/null +++ b/src/libgit2/reader.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "reader.h" + +#include "futils.h" +#include "blob.h" + +#include "git2/tree.h" +#include "git2/blob.h" +#include "git2/index.h" +#include "git2/repository.h" + +/* tree reader */ + +typedef struct { + git_reader reader; + git_tree *tree; +} tree_reader; + +static int tree_reader_read( + git_str *out, + git_oid *out_id, + git_filemode_t *out_filemode, + git_reader *_reader, + const char *filename) +{ + tree_reader *reader = (tree_reader *)_reader; + git_tree_entry *tree_entry = NULL; + git_blob *blob = NULL; + git_object_size_t blobsize; + int error; + + if ((error = git_tree_entry_bypath(&tree_entry, reader->tree, filename)) < 0 || + (error = git_blob_lookup(&blob, git_tree_owner(reader->tree), git_tree_entry_id(tree_entry))) < 0) + goto done; + + blobsize = git_blob_rawsize(blob); + GIT_ERROR_CHECK_BLOBSIZE(blobsize); + + if ((error = git_str_set(out, git_blob_rawcontent(blob), (size_t)blobsize)) < 0) + goto done; + + if (out_id) + git_oid_cpy(out_id, git_tree_entry_id(tree_entry)); + + if (out_filemode) + *out_filemode = git_tree_entry_filemode(tree_entry); + +done: + git_blob_free(blob); + git_tree_entry_free(tree_entry); + return error; +} + +int git_reader_for_tree(git_reader **out, git_tree *tree) +{ + tree_reader *reader; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(tree); + + reader = git__calloc(1, sizeof(tree_reader)); + GIT_ERROR_CHECK_ALLOC(reader); + + reader->reader.read = tree_reader_read; + reader->tree = tree; + + *out = (git_reader *)reader; + return 0; +} + +/* workdir reader */ + +typedef struct { + git_reader reader; + git_repository *repo; + git_index *index; +} workdir_reader; + +static int workdir_reader_read( + git_str *out, + git_oid *out_id, + git_filemode_t *out_filemode, + git_reader *_reader, + const char *filename) +{ + workdir_reader *reader = (workdir_reader *)_reader; + git_str path = GIT_STR_INIT; + struct stat st; + git_filemode_t filemode; + git_filter_list *filters = NULL; + const git_index_entry *idx_entry; + git_oid id; + int error; + + if ((error = git_repository_workdir_path(&path, reader->repo, filename)) < 0) + goto done; + + if ((error = p_lstat(path.ptr, &st)) < 0) { + if (error == -1 && errno == ENOENT) + error = GIT_ENOTFOUND; + + git_error_set(GIT_ERROR_OS, "could not stat '%s'", path.ptr); + goto done; + } + + filemode = git_futils_canonical_mode(st.st_mode); + + /* + * Patch application - for example - uses the filtered version of + * the working directory data to match git. So we will run the + * workdir -> ODB filter on the contents in this workdir reader. + */ + if ((error = git_filter_list_load(&filters, reader->repo, NULL, filename, + GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT)) < 0) + goto done; + + if ((error = git_filter_list__apply_to_file(out, + filters, reader->repo, path.ptr)) < 0) + goto done; + + if (out_id || reader->index) { + if ((error = git_odb_hash(&id, out->ptr, out->size, GIT_OBJECT_BLOB)) < 0) + goto done; + } + + if (reader->index) { + if (!(idx_entry = git_index_get_bypath(reader->index, filename, 0)) || + filemode != idx_entry->mode || + !git_oid_equal(&id, &idx_entry->id)) { + error = GIT_READER_MISMATCH; + goto done; + } + } + + if (out_id) + git_oid_cpy(out_id, &id); + + if (out_filemode) + *out_filemode = filemode; + +done: + git_filter_list_free(filters); + git_str_dispose(&path); + return error; +} + +int git_reader_for_workdir( + git_reader **out, + git_repository *repo, + bool validate_index) +{ + workdir_reader *reader; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + reader = git__calloc(1, sizeof(workdir_reader)); + GIT_ERROR_CHECK_ALLOC(reader); + + reader->reader.read = workdir_reader_read; + reader->repo = repo; + + if (validate_index && + (error = git_repository_index__weakptr(&reader->index, repo)) < 0) { + git__free(reader); + return error; + } + + *out = (git_reader *)reader; + return 0; +} + +/* index reader */ + +typedef struct { + git_reader reader; + git_repository *repo; + git_index *index; +} index_reader; + +static int index_reader_read( + git_str *out, + git_oid *out_id, + git_filemode_t *out_filemode, + git_reader *_reader, + const char *filename) +{ + index_reader *reader = (index_reader *)_reader; + const git_index_entry *entry; + git_blob *blob; + int error; + + if ((entry = git_index_get_bypath(reader->index, filename, 0)) == NULL) + return GIT_ENOTFOUND; + + if ((error = git_blob_lookup(&blob, reader->repo, &entry->id)) < 0) + goto done; + + if (out_id) + git_oid_cpy(out_id, &entry->id); + + if (out_filemode) + *out_filemode = entry->mode; + + error = git_blob__getbuf(out, blob); + +done: + git_blob_free(blob); + return error; +} + +int git_reader_for_index( + git_reader **out, + git_repository *repo, + git_index *index) +{ + index_reader *reader; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + reader = git__calloc(1, sizeof(index_reader)); + GIT_ERROR_CHECK_ALLOC(reader); + + reader->reader.read = index_reader_read; + reader->repo = repo; + + if (index) { + reader->index = index; + } else if ((error = git_repository_index__weakptr(&reader->index, repo)) < 0) { + git__free(reader); + return error; + } + + *out = (git_reader *)reader; + return 0; +} + +/* generic */ + +int git_reader_read( + git_str *out, + git_oid *out_id, + git_filemode_t *out_filemode, + git_reader *reader, + const char *filename) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(reader); + GIT_ASSERT_ARG(filename); + + return reader->read(out, out_id, out_filemode, reader, filename); +} + +void git_reader_free(git_reader *reader) +{ + if (!reader) + return; + + git__free(reader); +} diff --git a/src/libgit2/reader.h b/src/libgit2/reader.h new file mode 100644 index 000000000..b58dc93f6 --- /dev/null +++ b/src/libgit2/reader.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_reader_h__ +#define INCLUDE_reader_h__ + +#include "common.h" + +/* Returned when the workdir does not match the index */ +#define GIT_READER_MISMATCH 1 + +typedef struct git_reader git_reader; + +/* + * The `git_reader` structure is a generic interface for reading the + * contents of a file by its name, and implementations are provided + * for reading out of a tree, the index, and the working directory. + * + * Note that the reader implementation is meant to have a short + * lifecycle and does not increase the refcount of the object that + * it's reading. Callers should ensure that they do not use a + * reader after disposing the underlying object that it reads. + */ +struct git_reader { + int (*read)(git_str *out, git_oid *out_oid, git_filemode_t *mode, git_reader *reader, const char *filename); +}; + +/** + * Create a `git_reader` that will allow random access to the given + * tree. Paths requested via `git_reader_read` will be rooted at this + * tree, callers are not expected to recurse through tree lookups. Thus, + * you can request to read `/src/foo.c` and the tree provided to this + * function will be searched to find another tree named `src`, which + * will then be opened to find `foo.c`. + * + * @param out The reader for the given tree + * @param tree The tree object to read + * @return 0 on success, or an error code < 0 + */ +extern int git_reader_for_tree( + git_reader **out, + git_tree *tree); + +/** + * Create a `git_reader` that will allow random access to the given + * index, or the repository's index. + * + * @param out The reader for the given index + * @param repo The repository containing the index + * @param index The index to read, or NULL to use the repository's index + * @return 0 on success, or an error code < 0 + */ +extern int git_reader_for_index( + git_reader **out, + git_repository *repo, + git_index *index); + +/** + * Create a `git_reader` that will allow random access to the given + * repository's working directory. Note that the contents are read + * in repository format, meaning any workdir -> odb filters are + * applied. + * + * If `validate_index` is set to true, reads of files will hash the + * on-disk contents and ensure that the resulting object ID matches + * the repository's index. This ensures that the working directory + * is unmodified from the index contents. + * + * @param out The reader for the given working directory + * @param repo The repository containing the working directory + * @param validate_index If true, the working directory contents will + * be compared to the index contents during read to ensure that + * the working directory is unmodified. + * @return 0 on success, or an error code < 0 + */ +extern int git_reader_for_workdir( + git_reader **out, + git_repository *repo, + bool validate_index); + +/** + * Read the given filename from the reader and populate the given buffer + * with the contents and the given oid with the object ID. + * + * @param out The buffer to populate with the file contents + * @param out_id The oid to populate with the object ID + * @param reader The reader to read + * @param filename The filename to read from the reader + */ +extern int git_reader_read( + git_str *out, + git_oid *out_id, + git_filemode_t *out_filemode, + git_reader *reader, + const char *filename); + +/** + * Free the given reader and any associated objects. + * + * @param reader The reader to free + */ +extern void git_reader_free(git_reader *reader); + +#endif diff --git a/src/libgit2/rebase.c b/src/libgit2/rebase.c new file mode 100644 index 000000000..6f01d3990 --- /dev/null +++ b/src/libgit2/rebase.c @@ -0,0 +1,1470 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "str.h" +#include "repository.h" +#include "posix.h" +#include "filebuf.h" +#include "commit.h" +#include "merge.h" +#include "array.h" +#include "config.h" +#include "annotated_commit.h" +#include "index.h" + +#include +#include +#include +#include +#include +#include +#include + +#define REBASE_APPLY_DIR "rebase-apply" +#define REBASE_MERGE_DIR "rebase-merge" + +#define HEAD_NAME_FILE "head-name" +#define ORIG_HEAD_FILE "orig-head" +#define HEAD_FILE "head" +#define ONTO_FILE "onto" +#define ONTO_NAME_FILE "onto_name" +#define QUIET_FILE "quiet" + +#define MSGNUM_FILE "msgnum" +#define END_FILE "end" +#define CMT_FILE_FMT "cmt.%" PRIuZ +#define CURRENT_FILE "current" +#define REWRITTEN_FILE "rewritten" + +#define ORIG_DETACHED_HEAD "detached HEAD" + +#define NOTES_DEFAULT_REF NULL + +#define REBASE_DIR_MODE 0777 +#define REBASE_FILE_MODE 0666 + +typedef enum { + GIT_REBASE_NONE = 0, + GIT_REBASE_APPLY = 1, + GIT_REBASE_MERGE = 2, + GIT_REBASE_INTERACTIVE = 3 +} git_rebase_t; + +struct git_rebase { + git_repository *repo; + + git_rebase_options options; + + git_rebase_t type; + char *state_path; + + unsigned int head_detached:1, + inmemory:1, + quiet:1, + started:1; + + git_array_t(git_rebase_operation) operations; + size_t current; + + /* Used by in-memory rebase */ + git_index *index; + git_commit *last_commit; + + /* Used by regular (not in-memory) merge-style rebase */ + git_oid orig_head_id; + char *orig_head_name; + + git_oid onto_id; + char *onto_name; +}; + +#define GIT_REBASE_STATE_INIT {0} + +static int rebase_state_type( + git_rebase_t *type_out, + char **path_out, + git_repository *repo) +{ + git_str path = GIT_STR_INIT; + git_rebase_t type = GIT_REBASE_NONE; + + if (git_str_joinpath(&path, repo->gitdir, REBASE_APPLY_DIR) < 0) + return -1; + + if (git_fs_path_isdir(git_str_cstr(&path))) { + type = GIT_REBASE_APPLY; + goto done; + } + + git_str_clear(&path); + if (git_str_joinpath(&path, repo->gitdir, REBASE_MERGE_DIR) < 0) + return -1; + + if (git_fs_path_isdir(git_str_cstr(&path))) { + type = GIT_REBASE_MERGE; + goto done; + } + +done: + *type_out = type; + + if (type != GIT_REBASE_NONE && path_out) + *path_out = git_str_detach(&path); + + git_str_dispose(&path); + + return 0; +} + +GIT_INLINE(int) rebase_readfile( + git_str *out, + git_str *state_path, + const char *filename) +{ + size_t state_path_len = state_path->size; + int error; + + git_str_clear(out); + + if ((error = git_str_joinpath(state_path, state_path->ptr, filename)) < 0 || + (error = git_futils_readbuffer(out, state_path->ptr)) < 0) + goto done; + + git_str_rtrim(out); + +done: + git_str_truncate(state_path, state_path_len); + return error; +} + +GIT_INLINE(int) rebase_readint( + size_t *out, git_str *asc_out, git_str *state_path, const char *filename) +{ + int32_t num; + const char *eol; + int error = 0; + + if ((error = rebase_readfile(asc_out, state_path, filename)) < 0) + return error; + + if (git__strntol32(&num, asc_out->ptr, asc_out->size, &eol, 10) < 0 || num < 0 || *eol) { + git_error_set(GIT_ERROR_REBASE, "the file '%s' contains an invalid numeric value", filename); + return -1; + } + + *out = (size_t) num; + + return 0; +} + +GIT_INLINE(int) rebase_readoid( + git_oid *out, git_str *str_out, git_str *state_path, const char *filename) +{ + int error; + + if ((error = rebase_readfile(str_out, state_path, filename)) < 0) + return error; + + if (str_out->size != GIT_OID_HEXSZ || git_oid_fromstr(out, str_out->ptr) < 0) { + git_error_set(GIT_ERROR_REBASE, "the file '%s' contains an invalid object ID", filename); + return -1; + } + + return 0; +} + +static git_rebase_operation *rebase_operation_alloc( + git_rebase *rebase, + git_rebase_operation_t type, + git_oid *id, + const char *exec) +{ + git_rebase_operation *operation; + + GIT_ASSERT_WITH_RETVAL((type == GIT_REBASE_OPERATION_EXEC) == !id, NULL); + GIT_ASSERT_WITH_RETVAL((type == GIT_REBASE_OPERATION_EXEC) == !!exec, NULL); + + if ((operation = git_array_alloc(rebase->operations)) == NULL) + return NULL; + + operation->type = type; + git_oid_cpy((git_oid *)&operation->id, id); + operation->exec = exec; + + return operation; +} + +static int rebase_open_merge(git_rebase *rebase) +{ + git_str state_path = GIT_STR_INIT, buf = GIT_STR_INIT, cmt = GIT_STR_INIT; + git_oid id; + git_rebase_operation *operation; + size_t i, msgnum = 0, end; + int error; + + if ((error = git_str_puts(&state_path, rebase->state_path)) < 0) + goto done; + + /* Read 'msgnum' if it exists (otherwise, let msgnum = 0) */ + if ((error = rebase_readint(&msgnum, &buf, &state_path, MSGNUM_FILE)) < 0 && + error != GIT_ENOTFOUND) + goto done; + + if (msgnum) { + rebase->started = 1; + rebase->current = msgnum - 1; + } + + /* Read 'end' */ + if ((error = rebase_readint(&end, &buf, &state_path, END_FILE)) < 0) + goto done; + + /* Read 'current' if it exists */ + if ((error = rebase_readoid(&id, &buf, &state_path, CURRENT_FILE)) < 0 && + error != GIT_ENOTFOUND) + goto done; + + /* Read cmt.* */ + git_array_init_to_size(rebase->operations, end); + GIT_ERROR_CHECK_ARRAY(rebase->operations); + + for (i = 0; i < end; i++) { + git_str_clear(&cmt); + + if ((error = git_str_printf(&cmt, "cmt.%" PRIuZ, (i+1))) < 0 || + (error = rebase_readoid(&id, &buf, &state_path, cmt.ptr)) < 0) + goto done; + + operation = rebase_operation_alloc(rebase, GIT_REBASE_OPERATION_PICK, &id, NULL); + GIT_ERROR_CHECK_ALLOC(operation); + } + + /* Read 'onto_name' */ + if ((error = rebase_readfile(&buf, &state_path, ONTO_NAME_FILE)) < 0) + goto done; + + rebase->onto_name = git_str_detach(&buf); + +done: + git_str_dispose(&cmt); + git_str_dispose(&state_path); + git_str_dispose(&buf); + + return error; +} + +static int rebase_alloc(git_rebase **out, const git_rebase_options *rebase_opts) +{ + git_rebase *rebase = git__calloc(1, sizeof(git_rebase)); + GIT_ERROR_CHECK_ALLOC(rebase); + + *out = NULL; + + if (rebase_opts) + memcpy(&rebase->options, rebase_opts, sizeof(git_rebase_options)); + else + git_rebase_options_init(&rebase->options, GIT_REBASE_OPTIONS_VERSION); + + if (rebase_opts && rebase_opts->rewrite_notes_ref) { + rebase->options.rewrite_notes_ref = git__strdup(rebase_opts->rewrite_notes_ref); + GIT_ERROR_CHECK_ALLOC(rebase->options.rewrite_notes_ref); + } + + *out = rebase; + + return 0; +} + +static int rebase_check_versions(const git_rebase_options *given_opts) +{ + GIT_ERROR_CHECK_VERSION(given_opts, GIT_REBASE_OPTIONS_VERSION, "git_rebase_options"); + + if (given_opts) + GIT_ERROR_CHECK_VERSION(&given_opts->checkout_options, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options"); + + return 0; +} + +int git_rebase_open( + git_rebase **out, + git_repository *repo, + const git_rebase_options *given_opts) +{ + git_rebase *rebase; + git_str path = GIT_STR_INIT, orig_head_name = GIT_STR_INIT, + orig_head_id = GIT_STR_INIT, onto_id = GIT_STR_INIT; + size_t state_path_len; + int error; + + GIT_ASSERT_ARG(repo); + + if ((error = rebase_check_versions(given_opts)) < 0) + return error; + + if (rebase_alloc(&rebase, given_opts) < 0) + return -1; + + rebase->repo = repo; + + if ((error = rebase_state_type(&rebase->type, &rebase->state_path, repo)) < 0) + goto done; + + if (rebase->type == GIT_REBASE_NONE) { + git_error_set(GIT_ERROR_REBASE, "there is no rebase in progress"); + error = GIT_ENOTFOUND; + goto done; + } + + if ((error = git_str_puts(&path, rebase->state_path)) < 0) + goto done; + + state_path_len = git_str_len(&path); + + if ((error = git_str_joinpath(&path, path.ptr, HEAD_NAME_FILE)) < 0 || + (error = git_futils_readbuffer(&orig_head_name, path.ptr)) < 0) + goto done; + + git_str_rtrim(&orig_head_name); + + if (strcmp(ORIG_DETACHED_HEAD, orig_head_name.ptr) == 0) + rebase->head_detached = 1; + + git_str_truncate(&path, state_path_len); + + if ((error = git_str_joinpath(&path, path.ptr, ORIG_HEAD_FILE)) < 0) + goto done; + + if (!git_fs_path_isfile(path.ptr)) { + /* Previous versions of git.git used 'head' here; support that. */ + git_str_truncate(&path, state_path_len); + + if ((error = git_str_joinpath(&path, path.ptr, HEAD_FILE)) < 0) + goto done; + } + + if ((error = git_futils_readbuffer(&orig_head_id, path.ptr)) < 0) + goto done; + + git_str_rtrim(&orig_head_id); + + if ((error = git_oid_fromstr(&rebase->orig_head_id, orig_head_id.ptr)) < 0) + goto done; + + git_str_truncate(&path, state_path_len); + + if ((error = git_str_joinpath(&path, path.ptr, ONTO_FILE)) < 0 || + (error = git_futils_readbuffer(&onto_id, path.ptr)) < 0) + goto done; + + git_str_rtrim(&onto_id); + + if ((error = git_oid_fromstr(&rebase->onto_id, onto_id.ptr)) < 0) + goto done; + + if (!rebase->head_detached) + rebase->orig_head_name = git_str_detach(&orig_head_name); + + switch (rebase->type) { + case GIT_REBASE_INTERACTIVE: + git_error_set(GIT_ERROR_REBASE, "interactive rebase is not supported"); + error = -1; + break; + case GIT_REBASE_MERGE: + error = rebase_open_merge(rebase); + break; + case GIT_REBASE_APPLY: + git_error_set(GIT_ERROR_REBASE, "patch application rebase is not supported"); + error = -1; + break; + default: + abort(); + } + +done: + if (error == 0) + *out = rebase; + else + git_rebase_free(rebase); + + git_str_dispose(&path); + git_str_dispose(&orig_head_name); + git_str_dispose(&orig_head_id); + git_str_dispose(&onto_id); + return error; +} + +static int rebase_cleanup(git_rebase *rebase) +{ + if (!rebase || rebase->inmemory) + return 0; + + return git_fs_path_isdir(rebase->state_path) ? + git_futils_rmdir_r(rebase->state_path, NULL, GIT_RMDIR_REMOVE_FILES) : + 0; +} + +static int rebase_setupfile(git_rebase *rebase, const char *filename, int flags, const char *fmt, ...) +{ + git_str path = GIT_STR_INIT, + contents = GIT_STR_INIT; + va_list ap; + int error; + + va_start(ap, fmt); + git_str_vprintf(&contents, fmt, ap); + va_end(ap); + + if ((error = git_str_joinpath(&path, rebase->state_path, filename)) == 0) + error = git_futils_writebuffer(&contents, path.ptr, flags, REBASE_FILE_MODE); + + git_str_dispose(&path); + git_str_dispose(&contents); + + return error; +} + +static const char *rebase_onto_name(const git_annotated_commit *onto) +{ + if (onto->ref_name && git__strncmp(onto->ref_name, "refs/heads/", 11) == 0) + return onto->ref_name + 11; + else if (onto->ref_name) + return onto->ref_name; + else + return onto->id_str; +} + +static int rebase_setupfiles_merge(git_rebase *rebase) +{ + git_str commit_filename = GIT_STR_INIT; + char id_str[GIT_OID_HEXSZ]; + git_rebase_operation *operation; + size_t i; + int error = 0; + + if ((error = rebase_setupfile(rebase, END_FILE, 0, "%" PRIuZ "\n", git_array_size(rebase->operations))) < 0 || + (error = rebase_setupfile(rebase, ONTO_NAME_FILE, 0, "%s\n", rebase->onto_name)) < 0) + goto done; + + for (i = 0; i < git_array_size(rebase->operations); i++) { + operation = git_array_get(rebase->operations, i); + + git_str_clear(&commit_filename); + git_str_printf(&commit_filename, CMT_FILE_FMT, i+1); + + git_oid_fmt(id_str, &operation->id); + + if ((error = rebase_setupfile(rebase, commit_filename.ptr, 0, + "%.*s\n", GIT_OID_HEXSZ, id_str)) < 0) + goto done; + } + +done: + git_str_dispose(&commit_filename); + return error; +} + +static int rebase_setupfiles(git_rebase *rebase) +{ + char onto[GIT_OID_HEXSZ], orig_head[GIT_OID_HEXSZ]; + const char *orig_head_name; + + git_oid_fmt(onto, &rebase->onto_id); + git_oid_fmt(orig_head, &rebase->orig_head_id); + + if (p_mkdir(rebase->state_path, REBASE_DIR_MODE) < 0) { + git_error_set(GIT_ERROR_OS, "failed to create rebase directory '%s'", rebase->state_path); + return -1; + } + + orig_head_name = rebase->head_detached ? ORIG_DETACHED_HEAD : + rebase->orig_head_name; + + if (git_repository__set_orig_head(rebase->repo, &rebase->orig_head_id) < 0 || + rebase_setupfile(rebase, HEAD_NAME_FILE, 0, "%s\n", orig_head_name) < 0 || + rebase_setupfile(rebase, ONTO_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, onto) < 0 || + rebase_setupfile(rebase, ORIG_HEAD_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, orig_head) < 0 || + rebase_setupfile(rebase, QUIET_FILE, 0, rebase->quiet ? "t\n" : "\n") < 0) + return -1; + + return rebase_setupfiles_merge(rebase); +} + +int git_rebase_options_init(git_rebase_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_rebase_options, GIT_REBASE_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_rebase_init_options(git_rebase_options *opts, unsigned int version) +{ + return git_rebase_options_init(opts, version); +} +#endif + +static int rebase_ensure_not_in_progress(git_repository *repo) +{ + int error; + git_rebase_t type; + + if ((error = rebase_state_type(&type, NULL, repo)) < 0) + return error; + + if (type != GIT_REBASE_NONE) { + git_error_set(GIT_ERROR_REBASE, "there is an existing rebase in progress"); + return -1; + } + + return 0; +} + +static int rebase_ensure_not_dirty( + git_repository *repo, + bool check_index, + bool check_workdir, + int fail_with) +{ + git_tree *head = NULL; + git_index *index = NULL; + git_diff *diff = NULL; + int error = 0; + + if (check_index) { + if ((error = git_repository_head_tree(&head, repo)) < 0 || + (error = git_repository_index(&index, repo)) < 0 || + (error = git_diff_tree_to_index(&diff, repo, head, index, NULL)) < 0) + goto done; + + if (git_diff_num_deltas(diff) > 0) { + git_error_set(GIT_ERROR_REBASE, "uncommitted changes exist in index"); + error = fail_with; + goto done; + } + + git_diff_free(diff); + diff = NULL; + } + + if (check_workdir) { + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.ignore_submodules = GIT_SUBMODULE_IGNORE_UNTRACKED; + if ((error = git_diff_index_to_workdir(&diff, repo, index, &diff_opts)) < 0) + goto done; + + if (git_diff_num_deltas(diff) > 0) { + git_error_set(GIT_ERROR_REBASE, "unstaged changes exist in workdir"); + error = fail_with; + goto done; + } + } + +done: + git_diff_free(diff); + git_index_free(index); + git_tree_free(head); + + return error; +} + +static int rebase_init_operations( + git_rebase *rebase, + git_repository *repo, + const git_annotated_commit *branch, + const git_annotated_commit *upstream, + const git_annotated_commit *onto) +{ + git_revwalk *revwalk = NULL; + git_commit *commit; + git_oid id; + bool merge; + git_rebase_operation *operation; + int error; + + if (!upstream) + upstream = onto; + + if ((error = git_revwalk_new(&revwalk, rebase->repo)) < 0 || + (error = git_revwalk_push(revwalk, git_annotated_commit_id(branch))) < 0 || + (error = git_revwalk_hide(revwalk, git_annotated_commit_id(upstream))) < 0) + goto done; + + git_revwalk_sorting(revwalk, GIT_SORT_REVERSE); + + while ((error = git_revwalk_next(&id, revwalk)) == 0) { + if ((error = git_commit_lookup(&commit, repo, &id)) < 0) + goto done; + + merge = (git_commit_parentcount(commit) > 1); + git_commit_free(commit); + + if (merge) + continue; + + operation = rebase_operation_alloc(rebase, GIT_REBASE_OPERATION_PICK, &id, NULL); + GIT_ERROR_CHECK_ALLOC(operation); + } + + error = 0; + +done: + git_revwalk_free(revwalk); + return error; +} + +static int rebase_init_merge( + git_rebase *rebase, + git_repository *repo, + const git_annotated_commit *branch, + const git_annotated_commit *upstream, + const git_annotated_commit *onto) +{ + git_reference *head_ref = NULL; + git_commit *onto_commit = NULL; + git_str reflog = GIT_STR_INIT; + git_str state_path = GIT_STR_INIT; + int error; + + GIT_UNUSED(upstream); + + if ((error = git_str_joinpath(&state_path, repo->gitdir, REBASE_MERGE_DIR)) < 0) + goto done; + + rebase->state_path = git_str_detach(&state_path); + GIT_ERROR_CHECK_ALLOC(rebase->state_path); + + if (branch->ref_name && strcmp(branch->ref_name, "HEAD")) { + rebase->orig_head_name = git__strdup(branch->ref_name); + GIT_ERROR_CHECK_ALLOC(rebase->orig_head_name); + } else { + rebase->head_detached = 1; + } + + rebase->onto_name = git__strdup(rebase_onto_name(onto)); + GIT_ERROR_CHECK_ALLOC(rebase->onto_name); + + rebase->quiet = rebase->options.quiet; + + git_oid_cpy(&rebase->orig_head_id, git_annotated_commit_id(branch)); + git_oid_cpy(&rebase->onto_id, git_annotated_commit_id(onto)); + + if ((error = rebase_setupfiles(rebase)) < 0 || + (error = git_str_printf(&reflog, + "rebase: checkout %s", rebase_onto_name(onto))) < 0 || + (error = git_commit_lookup( + &onto_commit, repo, git_annotated_commit_id(onto))) < 0 || + (error = git_checkout_tree(repo, + (git_object *)onto_commit, &rebase->options.checkout_options)) < 0 || + (error = git_reference_create(&head_ref, repo, GIT_HEAD_FILE, + git_annotated_commit_id(onto), 1, reflog.ptr)) < 0) + goto done; + +done: + git_reference_free(head_ref); + git_commit_free(onto_commit); + git_str_dispose(&reflog); + git_str_dispose(&state_path); + + return error; +} + +static int rebase_init_inmemory( + git_rebase *rebase, + git_repository *repo, + const git_annotated_commit *branch, + const git_annotated_commit *upstream, + const git_annotated_commit *onto) +{ + GIT_UNUSED(branch); + GIT_UNUSED(upstream); + + return git_commit_lookup( + &rebase->last_commit, repo, git_annotated_commit_id(onto)); +} + +int git_rebase_init( + git_rebase **out, + git_repository *repo, + const git_annotated_commit *branch, + const git_annotated_commit *upstream, + const git_annotated_commit *onto, + const git_rebase_options *given_opts) +{ + git_rebase *rebase = NULL; + git_annotated_commit *head_branch = NULL; + git_reference *head_ref = NULL; + bool inmemory = (given_opts && given_opts->inmemory); + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(upstream || onto); + + *out = NULL; + + if (!onto) + onto = upstream; + + if ((error = rebase_check_versions(given_opts)) < 0) + goto done; + + if (!inmemory) { + if ((error = git_repository__ensure_not_bare(repo, "rebase")) < 0 || + (error = rebase_ensure_not_in_progress(repo)) < 0 || + (error = rebase_ensure_not_dirty(repo, true, true, GIT_ERROR)) < 0) + goto done; + } + + if (!branch) { + if ((error = git_repository_head(&head_ref, repo)) < 0 || + (error = git_annotated_commit_from_ref(&head_branch, repo, head_ref)) < 0) + goto done; + + branch = head_branch; + } + + if (rebase_alloc(&rebase, given_opts) < 0) + return -1; + + rebase->repo = repo; + rebase->inmemory = inmemory; + rebase->type = GIT_REBASE_MERGE; + + if ((error = rebase_init_operations(rebase, repo, branch, upstream, onto)) < 0) + goto done; + + if (inmemory) + error = rebase_init_inmemory(rebase, repo, branch, upstream, onto); + else + error = rebase_init_merge(rebase, repo, branch ,upstream, onto); + + if (error == 0) + *out = rebase; + +done: + git_reference_free(head_ref); + git_annotated_commit_free(head_branch); + + if (error < 0) { + rebase_cleanup(rebase); + git_rebase_free(rebase); + } + + return error; +} + +static void normalize_checkout_options_for_apply( + git_checkout_options *checkout_opts, + git_rebase *rebase, + git_commit *current_commit) +{ + memcpy(checkout_opts, &rebase->options.checkout_options, sizeof(git_checkout_options)); + + if (!checkout_opts->ancestor_label) + checkout_opts->ancestor_label = "ancestor"; + + if (rebase->type == GIT_REBASE_MERGE) { + if (!checkout_opts->our_label) + checkout_opts->our_label = rebase->onto_name; + + if (!checkout_opts->their_label) + checkout_opts->their_label = git_commit_summary(current_commit); + } else { + abort(); + } +} + +GIT_INLINE(int) rebase_movenext(git_rebase *rebase) +{ + size_t next = rebase->started ? rebase->current + 1 : 0; + + if (next == git_array_size(rebase->operations)) + return GIT_ITEROVER; + + rebase->started = 1; + rebase->current = next; + + return 0; +} + +static int rebase_next_merge( + git_rebase_operation **out, + git_rebase *rebase) +{ + git_str path = GIT_STR_INIT; + git_commit *current_commit = NULL, *parent_commit = NULL; + git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; + git_index *index = NULL; + git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; + git_rebase_operation *operation; + git_checkout_options checkout_opts; + char current_idstr[GIT_OID_HEXSZ]; + unsigned int parent_count; + int error; + + *out = NULL; + + operation = git_array_get(rebase->operations, rebase->current); + + if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || + (error = git_commit_tree(¤t_tree, current_commit)) < 0 || + (error = git_repository_head_tree(&head_tree, rebase->repo)) < 0) + goto done; + + if ((parent_count = git_commit_parentcount(current_commit)) > 1) { + git_error_set(GIT_ERROR_REBASE, "cannot rebase a merge commit"); + error = -1; + goto done; + } else if (parent_count) { + if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0) + goto done; + } + + git_oid_fmt(current_idstr, &operation->id); + + normalize_checkout_options_for_apply(&checkout_opts, rebase, current_commit); + + if ((error = git_indexwriter_init_for_operation(&indexwriter, rebase->repo, &checkout_opts.checkout_strategy)) < 0 || + (error = rebase_setupfile(rebase, MSGNUM_FILE, 0, "%" PRIuZ "\n", rebase->current+1)) < 0 || + (error = rebase_setupfile(rebase, CURRENT_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, current_idstr)) < 0 || + (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0 || + (error = git_merge__check_result(rebase->repo, index)) < 0 || + (error = git_checkout_index(rebase->repo, index, &checkout_opts)) < 0 || + (error = git_indexwriter_commit(&indexwriter)) < 0) + goto done; + + *out = operation; + +done: + git_indexwriter_cleanup(&indexwriter); + git_index_free(index); + git_tree_free(current_tree); + git_tree_free(head_tree); + git_tree_free(parent_tree); + git_commit_free(parent_commit); + git_commit_free(current_commit); + git_str_dispose(&path); + + return error; +} + +static int rebase_next_inmemory( + git_rebase_operation **out, + git_rebase *rebase) +{ + git_commit *current_commit = NULL, *parent_commit = NULL; + git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; + git_rebase_operation *operation; + git_index *index = NULL; + unsigned int parent_count; + int error; + + *out = NULL; + + operation = git_array_get(rebase->operations, rebase->current); + + if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || + (error = git_commit_tree(¤t_tree, current_commit)) < 0) + goto done; + + if ((parent_count = git_commit_parentcount(current_commit)) > 1) { + git_error_set(GIT_ERROR_REBASE, "cannot rebase a merge commit"); + error = -1; + goto done; + } else if (parent_count) { + if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0) + goto done; + } + + if ((error = git_commit_tree(&head_tree, rebase->last_commit)) < 0 || + (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0) + goto done; + + if (!rebase->index) { + rebase->index = index; + index = NULL; + } else { + if ((error = git_index_read_index(rebase->index, index)) < 0) + goto done; + } + + *out = operation; + +done: + git_commit_free(current_commit); + git_commit_free(parent_commit); + git_tree_free(current_tree); + git_tree_free(head_tree); + git_tree_free(parent_tree); + git_index_free(index); + + return error; +} + +int git_rebase_next( + git_rebase_operation **out, + git_rebase *rebase) +{ + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(rebase); + + if ((error = rebase_movenext(rebase)) < 0) + return error; + + if (rebase->inmemory) + error = rebase_next_inmemory(out, rebase); + else if (rebase->type == GIT_REBASE_MERGE) + error = rebase_next_merge(out, rebase); + else + abort(); + + return error; +} + +int git_rebase_inmemory_index( + git_index **out, + git_rebase *rebase) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(rebase); + GIT_ASSERT_ARG(rebase->index); + + GIT_REFCOUNT_INC(rebase->index); + *out = rebase->index; + + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +static int create_signed( + git_oid *out, + git_rebase *rebase, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + git_tree *tree, + size_t parent_count, + const git_commit **parents) +{ + git_str commit_content = GIT_STR_INIT; + git_buf commit_signature = { NULL, 0, 0 }, + signature_field = { NULL, 0, 0 }; + int error; + + git_error_clear(); + + if ((error = git_commit__create_buffer(&commit_content, + rebase->repo, author, committer, message_encoding, + message, tree, parent_count, parents)) < 0) + goto done; + + error = rebase->options.signing_cb(&commit_signature, + &signature_field, commit_content.ptr, + rebase->options.payload); + + if (error) { + if (error != GIT_PASSTHROUGH) + git_error_set_after_callback_function(error, "signing_cb"); + + goto done; + } + + error = git_commit_create_with_signature(out, rebase->repo, + commit_content.ptr, + commit_signature.size > 0 ? commit_signature.ptr : NULL, + signature_field.size > 0 ? signature_field.ptr : NULL); + +done: + git_buf_dispose(&commit_signature); + git_buf_dispose(&signature_field); + git_str_dispose(&commit_content); + return error; +} +#endif + +static int rebase_commit__create( + git_commit **out, + git_rebase *rebase, + git_index *index, + git_commit *parent_commit, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message) +{ + git_rebase_operation *operation; + git_commit *current_commit = NULL, *commit = NULL; + git_tree *parent_tree = NULL, *tree = NULL; + git_oid tree_id, commit_id; + int error; + + operation = git_array_get(rebase->operations, rebase->current); + + if (git_index_has_conflicts(index)) { + git_error_set(GIT_ERROR_REBASE, "conflicts have not been resolved"); + error = GIT_EUNMERGED; + goto done; + } + + if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0 || + (error = git_index_write_tree_to(&tree_id, index, rebase->repo)) < 0 || + (error = git_tree_lookup(&tree, rebase->repo, &tree_id)) < 0) + goto done; + + if (git_oid_equal(&tree_id, git_tree_id(parent_tree))) { + git_error_set(GIT_ERROR_REBASE, "this patch has already been applied"); + error = GIT_EAPPLIED; + goto done; + } + + if (!author) + author = git_commit_author(current_commit); + + if (!message) { + message_encoding = git_commit_message_encoding(current_commit); + message = git_commit_message(current_commit); + } + + git_error_clear(); + error = GIT_PASSTHROUGH; + + if (rebase->options.commit_create_cb) { + error = rebase->options.commit_create_cb(&commit_id, + author, committer, message_encoding, message, + tree, 1, (const git_commit **)&parent_commit, + rebase->options.payload); + + git_error_set_after_callback_function(error, + "commit_create_cb"); + } +#ifndef GIT_DEPRECATE_HARD + else if (rebase->options.signing_cb) { + error = create_signed(&commit_id, rebase, author, + committer, message_encoding, message, tree, + 1, (const git_commit **)&parent_commit); + } +#endif + + if (error == GIT_PASSTHROUGH) + error = git_commit_create(&commit_id, rebase->repo, NULL, + author, committer, message_encoding, message, + tree, 1, (const git_commit **)&parent_commit); + + if (error) + goto done; + + if ((error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0) + goto done; + + *out = commit; + +done: + if (error < 0) + git_commit_free(commit); + + git_commit_free(current_commit); + git_tree_free(parent_tree); + git_tree_free(tree); + + return error; +} + +static int rebase_commit_merge( + git_oid *commit_id, + git_rebase *rebase, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message) +{ + git_rebase_operation *operation; + git_reference *head = NULL; + git_commit *head_commit = NULL, *commit = NULL; + git_index *index = NULL; + char old_idstr[GIT_OID_HEXSZ], new_idstr[GIT_OID_HEXSZ]; + int error; + + operation = git_array_get(rebase->operations, rebase->current); + GIT_ASSERT(operation); + + if ((error = rebase_ensure_not_dirty(rebase->repo, false, true, GIT_EUNMERGED)) < 0 || + (error = git_repository_head(&head, rebase->repo)) < 0 || + (error = git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)) < 0 || + (error = git_repository_index(&index, rebase->repo)) < 0 || + (error = rebase_commit__create(&commit, rebase, index, head_commit, + author, committer, message_encoding, message)) < 0 || + (error = git_reference__update_for_commit( + rebase->repo, NULL, "HEAD", git_commit_id(commit), "rebase")) < 0) + goto done; + + git_oid_fmt(old_idstr, &operation->id); + git_oid_fmt(new_idstr, git_commit_id(commit)); + + if ((error = rebase_setupfile(rebase, REWRITTEN_FILE, O_CREAT|O_WRONLY|O_APPEND, + "%.*s %.*s\n", GIT_OID_HEXSZ, old_idstr, GIT_OID_HEXSZ, new_idstr)) < 0) + goto done; + + git_oid_cpy(commit_id, git_commit_id(commit)); + +done: + git_index_free(index); + git_reference_free(head); + git_commit_free(head_commit); + git_commit_free(commit); + return error; +} + +static int rebase_commit_inmemory( + git_oid *commit_id, + git_rebase *rebase, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message) +{ + git_commit *commit = NULL; + int error = 0; + + GIT_ASSERT_ARG(rebase->index); + GIT_ASSERT_ARG(rebase->last_commit); + GIT_ASSERT_ARG(rebase->current < rebase->operations.size); + + if ((error = rebase_commit__create(&commit, rebase, rebase->index, + rebase->last_commit, author, committer, message_encoding, message)) < 0) + goto done; + + git_commit_free(rebase->last_commit); + rebase->last_commit = commit; + + git_oid_cpy(commit_id, git_commit_id(commit)); + +done: + if (error < 0) + git_commit_free(commit); + + return error; +} + +int git_rebase_commit( + git_oid *id, + git_rebase *rebase, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message) +{ + int error; + + GIT_ASSERT_ARG(rebase); + GIT_ASSERT_ARG(committer); + + if (rebase->inmemory) + error = rebase_commit_inmemory( + id, rebase, author, committer, message_encoding, message); + else if (rebase->type == GIT_REBASE_MERGE) + error = rebase_commit_merge( + id, rebase, author, committer, message_encoding, message); + else + abort(); + + return error; +} + +int git_rebase_abort(git_rebase *rebase) +{ + git_reference *orig_head_ref = NULL; + git_commit *orig_head_commit = NULL; + int error; + + GIT_ASSERT_ARG(rebase); + + if (rebase->inmemory) + return 0; + + error = rebase->head_detached ? + git_reference_create(&orig_head_ref, rebase->repo, GIT_HEAD_FILE, + &rebase->orig_head_id, 1, "rebase: aborting") : + git_reference_symbolic_create( + &orig_head_ref, rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1, + "rebase: aborting"); + + if (error < 0) + goto done; + + if ((error = git_commit_lookup( + &orig_head_commit, rebase->repo, &rebase->orig_head_id)) < 0 || + (error = git_reset(rebase->repo, (git_object *)orig_head_commit, + GIT_RESET_HARD, &rebase->options.checkout_options)) < 0) + goto done; + + error = rebase_cleanup(rebase); + +done: + git_commit_free(orig_head_commit); + git_reference_free(orig_head_ref); + + return error; +} + +static int notes_ref_lookup(git_str *out, git_rebase *rebase) +{ + git_config *config = NULL; + int do_rewrite, error; + + if (rebase->options.rewrite_notes_ref) { + git_str_attach_notowned(out, + rebase->options.rewrite_notes_ref, + strlen(rebase->options.rewrite_notes_ref)); + return 0; + } + + if ((error = git_repository_config(&config, rebase->repo)) < 0 || + (error = git_config_get_bool(&do_rewrite, config, "notes.rewrite.rebase")) < 0) { + + if (error != GIT_ENOTFOUND) + goto done; + + git_error_clear(); + do_rewrite = 1; + } + + error = do_rewrite ? + git_config__get_string_buf(out, config, "notes.rewriteref") : + GIT_ENOTFOUND; + +done: + git_config_free(config); + return error; +} + +static int rebase_copy_note( + git_rebase *rebase, + const char *notes_ref, + git_oid *from, + git_oid *to, + const git_signature *committer) +{ + git_note *note = NULL; + git_oid note_id; + git_signature *who = NULL; + int error; + + if ((error = git_note_read(¬e, rebase->repo, notes_ref, from)) < 0) { + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + goto done; + } + + if (!committer) { + if((error = git_signature_default(&who, rebase->repo)) < 0) { + if (error != GIT_ENOTFOUND || + (error = git_signature_now(&who, "unknown", "unknown")) < 0) + goto done; + + git_error_clear(); + } + + committer = who; + } + + error = git_note_create(¬e_id, rebase->repo, notes_ref, + git_note_author(note), committer, to, git_note_message(note), 0); + +done: + git_note_free(note); + git_signature_free(who); + + return error; +} + +static int rebase_copy_notes( + git_rebase *rebase, + const git_signature *committer) +{ + git_str path = GIT_STR_INIT, rewritten = GIT_STR_INIT, notes_ref = GIT_STR_INIT; + char *pair_list, *fromstr, *tostr, *end; + git_oid from, to; + unsigned int linenum = 1; + int error = 0; + + if ((error = notes_ref_lookup(¬es_ref, rebase)) < 0) { + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + goto done; + } + + if ((error = git_str_joinpath(&path, rebase->state_path, REWRITTEN_FILE)) < 0 || + (error = git_futils_readbuffer(&rewritten, path.ptr)) < 0) + goto done; + + pair_list = rewritten.ptr; + + while (*pair_list) { + fromstr = pair_list; + + if ((end = strchr(fromstr, '\n')) == NULL) + goto on_error; + + pair_list = end+1; + *end = '\0'; + + if ((end = strchr(fromstr, ' ')) == NULL) + goto on_error; + + tostr = end+1; + *end = '\0'; + + if (strlen(fromstr) != GIT_OID_HEXSZ || + strlen(tostr) != GIT_OID_HEXSZ || + git_oid_fromstr(&from, fromstr) < 0 || + git_oid_fromstr(&to, tostr) < 0) + goto on_error; + + if ((error = rebase_copy_note(rebase, notes_ref.ptr, &from, &to, committer)) < 0) + goto done; + + linenum++; + } + + goto done; + +on_error: + git_error_set(GIT_ERROR_REBASE, "invalid rewritten file at line %d", linenum); + error = -1; + +done: + git_str_dispose(&rewritten); + git_str_dispose(&path); + git_str_dispose(¬es_ref); + + return error; +} + +static int return_to_orig_head(git_rebase *rebase) +{ + git_reference *terminal_ref = NULL, *branch_ref = NULL, *head_ref = NULL; + git_commit *terminal_commit = NULL; + git_str branch_msg = GIT_STR_INIT, head_msg = GIT_STR_INIT; + char onto[GIT_OID_HEXSZ]; + int error = 0; + + git_oid_fmt(onto, &rebase->onto_id); + + if ((error = git_str_printf(&branch_msg, + "rebase finished: %s onto %.*s", + rebase->orig_head_name, GIT_OID_HEXSZ, onto)) == 0 && + (error = git_str_printf(&head_msg, + "rebase finished: returning to %s", + rebase->orig_head_name)) == 0 && + (error = git_repository_head(&terminal_ref, rebase->repo)) == 0 && + (error = git_reference_peel((git_object **)&terminal_commit, + terminal_ref, GIT_OBJECT_COMMIT)) == 0 && + (error = git_reference_create_matching(&branch_ref, + rebase->repo, rebase->orig_head_name, + git_commit_id(terminal_commit), 1, + &rebase->orig_head_id, branch_msg.ptr)) == 0) + error = git_reference_symbolic_create(&head_ref, + rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1, + head_msg.ptr); + + git_str_dispose(&head_msg); + git_str_dispose(&branch_msg); + git_commit_free(terminal_commit); + git_reference_free(head_ref); + git_reference_free(branch_ref); + git_reference_free(terminal_ref); + + return error; +} + +int git_rebase_finish( + git_rebase *rebase, + const git_signature *signature) +{ + int error = 0; + + GIT_ASSERT_ARG(rebase); + + if (rebase->inmemory) + return 0; + + if (!rebase->head_detached) + error = return_to_orig_head(rebase); + + if (error == 0 && (error = rebase_copy_notes(rebase, signature)) == 0) + error = rebase_cleanup(rebase); + + return error; +} + +const char *git_rebase_orig_head_name(git_rebase *rebase) { + GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); + return rebase->orig_head_name; +} + +const git_oid *git_rebase_orig_head_id(git_rebase *rebase) { + GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); + return &rebase->orig_head_id; +} + +const char *git_rebase_onto_name(git_rebase *rebase) { + GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); + return rebase->onto_name; +} + +const git_oid *git_rebase_onto_id(git_rebase *rebase) { + return &rebase->onto_id; +} + +size_t git_rebase_operation_entrycount(git_rebase *rebase) +{ + GIT_ASSERT_ARG_WITH_RETVAL(rebase, 0); + + return git_array_size(rebase->operations); +} + +size_t git_rebase_operation_current(git_rebase *rebase) +{ + GIT_ASSERT_ARG_WITH_RETVAL(rebase, 0); + + return rebase->started ? rebase->current : GIT_REBASE_NO_OPERATION; +} + +git_rebase_operation *git_rebase_operation_byindex(git_rebase *rebase, size_t idx) +{ + GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); + + return git_array_get(rebase->operations, idx); +} + +void git_rebase_free(git_rebase *rebase) +{ + if (rebase == NULL) + return; + + git_index_free(rebase->index); + git_commit_free(rebase->last_commit); + git__free(rebase->onto_name); + git__free(rebase->orig_head_name); + git__free(rebase->state_path); + git_array_clear(rebase->operations); + git__free((char *)rebase->options.rewrite_notes_ref); + git__free(rebase); +} diff --git a/src/libgit2/refdb.c b/src/libgit2/refdb.c new file mode 100644 index 000000000..ed33de92b --- /dev/null +++ b/src/libgit2/refdb.c @@ -0,0 +1,424 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "refdb.h" + +#include "git2/object.h" +#include "git2/refs.h" +#include "git2/refdb.h" +#include "git2/sys/refdb_backend.h" + +#include "hash.h" +#include "refs.h" +#include "reflog.h" +#include "posix.h" + +#define DEFAULT_NESTING_LEVEL 5 +#define MAX_NESTING_LEVEL 10 + +int git_refdb_new(git_refdb **out, git_repository *repo) +{ + git_refdb *db; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + db = git__calloc(1, sizeof(*db)); + GIT_ERROR_CHECK_ALLOC(db); + + db->repo = repo; + + *out = db; + GIT_REFCOUNT_INC(db); + return 0; +} + +int git_refdb_open(git_refdb **out, git_repository *repo) +{ + git_refdb *db; + git_refdb_backend *dir; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + *out = NULL; + + if (git_refdb_new(&db, repo) < 0) + return -1; + + /* Add the default (filesystem) backend */ + if (git_refdb_backend_fs(&dir, repo) < 0) { + git_refdb_free(db); + return -1; + } + + db->repo = repo; + db->backend = dir; + + *out = db; + return 0; +} + +static void refdb_free_backend(git_refdb *db) +{ + if (db->backend) + db->backend->free(db->backend); +} + +int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend) +{ + GIT_ERROR_CHECK_VERSION(backend, GIT_REFDB_BACKEND_VERSION, "git_refdb_backend"); + + if (!backend->exists || !backend->lookup || !backend->iterator || + !backend->write || !backend->rename || !backend->del || + !backend->has_log || !backend->ensure_log || !backend->free || + !backend->reflog_read || !backend->reflog_write || + !backend->reflog_rename || !backend->reflog_delete || + (backend->lock && !backend->unlock)) { + git_error_set(GIT_ERROR_REFERENCE, "incomplete refdb backend implementation"); + return GIT_EINVALID; + } + + refdb_free_backend(db); + db->backend = backend; + + return 0; +} + +int git_refdb_compress(git_refdb *db) +{ + GIT_ASSERT_ARG(db); + + if (db->backend->compress) + return db->backend->compress(db->backend); + + return 0; +} + +void git_refdb__free(git_refdb *db) +{ + refdb_free_backend(db); + git__memzero(db, sizeof(*db)); + git__free(db); +} + +void git_refdb_free(git_refdb *db) +{ + if (db == NULL) + return; + + GIT_REFCOUNT_DEC(db, git_refdb__free); +} + +int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name) +{ + GIT_ASSERT_ARG(exists); + GIT_ASSERT_ARG(refdb); + GIT_ASSERT_ARG(refdb->backend); + + return refdb->backend->exists(exists, refdb->backend, ref_name); +} + +int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name) +{ + git_reference *ref; + int error; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(db->backend); + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ref_name); + + error = db->backend->lookup(&ref, db->backend, ref_name); + if (error < 0) + return error; + + GIT_REFCOUNT_INC(db); + ref->db = db; + + *out = ref; + return 0; +} + +int git_refdb_resolve( + git_reference **out, + git_refdb *db, + const char *ref_name, + int max_nesting) +{ + git_reference *ref = NULL; + int error = 0, nesting; + + *out = NULL; + + if (max_nesting > MAX_NESTING_LEVEL) + max_nesting = MAX_NESTING_LEVEL; + else if (max_nesting < 0) + max_nesting = DEFAULT_NESTING_LEVEL; + + if ((error = git_refdb_lookup(&ref, db, ref_name)) < 0) + goto out; + + for (nesting = 0; nesting < max_nesting; nesting++) { + git_reference *resolved; + + if (ref->type == GIT_REFERENCE_DIRECT) + break; + + if ((error = git_refdb_lookup(&resolved, db, git_reference_symbolic_target(ref))) < 0) { + /* If we found a symbolic reference with a nonexistent target, return it. */ + if (error == GIT_ENOTFOUND) { + error = 0; + *out = ref; + ref = NULL; + } + goto out; + } + + git_reference_free(ref); + ref = resolved; + } + + if (ref->type != GIT_REFERENCE_DIRECT && max_nesting != 0) { + git_error_set(GIT_ERROR_REFERENCE, + "cannot resolve reference (>%u levels deep)", max_nesting); + error = -1; + goto out; + } + + *out = ref; + ref = NULL; +out: + git_reference_free(ref); + return error; +} + +int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob) +{ + int error; + + if (!db->backend || !db->backend->iterator) { + git_error_set(GIT_ERROR_REFERENCE, "this backend doesn't support iterators"); + return -1; + } + + if ((error = db->backend->iterator(out, db->backend, glob)) < 0) + return error; + + GIT_REFCOUNT_INC(db); + (*out)->db = db; + + return 0; +} + +int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter) +{ + int error; + + if ((error = iter->next(out, iter)) < 0) + return error; + + GIT_REFCOUNT_INC(iter->db); + (*out)->db = iter->db; + + return 0; +} + +int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter) +{ + return iter->next_name(out, iter); +} + +void git_refdb_iterator_free(git_reference_iterator *iter) +{ + GIT_REFCOUNT_DEC(iter->db, git_refdb__free); + iter->free(iter); +} + +int git_refdb_write(git_refdb *db, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target) +{ + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(db->backend); + + GIT_REFCOUNT_INC(db); + ref->db = db; + + return db->backend->write(db->backend, ref, force, who, message, old_id, old_target); +} + +int git_refdb_rename( + git_reference **out, + git_refdb *db, + const char *old_name, + const char *new_name, + int force, + const git_signature *who, + const char *message) +{ + int error; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(db->backend); + + error = db->backend->rename(out, db->backend, old_name, new_name, force, who, message); + if (error < 0) + return error; + + if (out) { + GIT_REFCOUNT_INC(db); + (*out)->db = db; + } + + return 0; +} + +int git_refdb_delete(struct git_refdb *db, const char *ref_name, const git_oid *old_id, const char *old_target) +{ + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(db->backend); + + return db->backend->del(db->backend, ref_name, old_id, old_target); +} + +int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name) +{ + int error; + + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(db->backend); + + if ((error = db->backend->reflog_read(out, db->backend, name)) < 0) + return error; + + GIT_REFCOUNT_INC(db); + (*out)->db = db; + + return 0; +} + +int git_refdb_should_write_reflog(int *out, git_refdb *db, const git_reference *ref) +{ + int error, logall; + + error = git_repository__configmap_lookup(&logall, db->repo, GIT_CONFIGMAP_LOGALLREFUPDATES); + if (error < 0) + return error; + + /* Defaults to the opposite of the repo being bare */ + if (logall == GIT_LOGALLREFUPDATES_UNSET) + logall = !git_repository_is_bare(db->repo); + + *out = 0; + switch (logall) { + case GIT_LOGALLREFUPDATES_FALSE: + *out = 0; + break; + + case GIT_LOGALLREFUPDATES_TRUE: + /* Only write if it already has a log, + * or if it's under heads/, remotes/ or notes/ + */ + *out = git_refdb_has_log(db, ref->name) || + !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) || + !git__strcmp(ref->name, GIT_HEAD_FILE) || + !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR) || + !git__prefixcmp(ref->name, GIT_REFS_NOTES_DIR); + break; + + case GIT_LOGALLREFUPDATES_ALWAYS: + *out = 1; + break; + } + + return 0; +} + +int git_refdb_should_write_head_reflog(int *out, git_refdb *db, const git_reference *ref) +{ + git_reference *head = NULL, *resolved = NULL; + const char *name; + int error; + + *out = 0; + + if (ref->type == GIT_REFERENCE_SYMBOLIC) { + error = 0; + goto out; + } + + if ((error = git_refdb_lookup(&head, db, GIT_HEAD_FILE)) < 0) + goto out; + + if (git_reference_type(head) == GIT_REFERENCE_DIRECT) + goto out; + + /* Go down the symref chain until we find the branch */ + if ((error = git_refdb_resolve(&resolved, db, git_reference_symbolic_target(head), -1)) < 0) { + if (error != GIT_ENOTFOUND) + goto out; + error = 0; + name = git_reference_symbolic_target(head); + } else if (git_reference_type(resolved) == GIT_REFERENCE_SYMBOLIC) { + name = git_reference_symbolic_target(resolved); + } else { + name = git_reference_name(resolved); + } + + if (strcmp(name, ref->name)) + goto out; + + *out = 1; + +out: + git_reference_free(resolved); + git_reference_free(head); + return error; +} + +int git_refdb_has_log(git_refdb *db, const char *refname) +{ + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(refname); + + return db->backend->has_log(db->backend, refname); +} + +int git_refdb_ensure_log(git_refdb *db, const char *refname) +{ + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(refname); + + return db->backend->ensure_log(db->backend, refname); +} + +int git_refdb_init_backend(git_refdb_backend *backend, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + backend, version, git_refdb_backend, GIT_REFDB_BACKEND_INIT); + return 0; +} + +int git_refdb_lock(void **payload, git_refdb *db, const char *refname) +{ + GIT_ASSERT_ARG(payload); + GIT_ASSERT_ARG(db); + GIT_ASSERT_ARG(refname); + + if (!db->backend->lock) { + git_error_set(GIT_ERROR_REFERENCE, "backend does not support locking"); + return -1; + } + + return db->backend->lock(payload, db->backend, refname); +} + +int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message) +{ + GIT_ASSERT_ARG(db); + + return db->backend->unlock(db->backend, payload, success, update_reflog, ref, sig, message); +} diff --git a/src/libgit2/refdb.h b/src/libgit2/refdb.h new file mode 100644 index 000000000..84e19b1c3 --- /dev/null +++ b/src/libgit2/refdb.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_refdb_h__ +#define INCLUDE_refdb_h__ + +#include "common.h" + +#include "git2/refdb.h" +#include "repository.h" + +struct git_refdb { + git_refcount rc; + git_repository *repo; + git_refdb_backend *backend; +}; + +void git_refdb__free(git_refdb *db); + +int git_refdb_exists( + int *exists, + git_refdb *refdb, + const char *ref_name); + +int git_refdb_lookup( + git_reference **out, + git_refdb *refdb, + const char *ref_name); + +/** + * Resolve the reference by following symbolic references. + * + * Given a reference name, this function will follow any symbolic references up + * to `max_nesting` deep and return the resolved direct reference. If any of + * the intermediate symbolic references points to a non-existing reference, + * then that symbolic reference is returned instead with an error code of `0`. + * If the given reference is a direct reference already, it is returned + * directly. + * + * If `max_nesting` is `0`, the reference will not be resolved. If it's + * negative, it will be set to the default resolve depth which is `5`. + * + * @param out Pointer to store the result in. + * @param db The refdb to use for resolving the reference. + * @param ref_name The reference name to lookup and resolve. + * @param max_nesting The maximum nesting depth. + * @return `0` on success, a negative error code otherwise. + */ +int git_refdb_resolve( + git_reference **out, + git_refdb *db, + const char *ref_name, + int max_nesting); + +int git_refdb_rename( + git_reference **out, + git_refdb *db, + const char *old_name, + const char *new_name, + int force, + const git_signature *who, + const char *message); + +int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob); +int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter); +int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter); +void git_refdb_iterator_free(git_reference_iterator *iter); + +int git_refdb_write(git_refdb *refdb, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target); +int git_refdb_delete(git_refdb *refdb, const char *ref_name, const git_oid *old_id, const char *old_target); + +int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name); +int git_refdb_reflog_write(git_reflog *reflog); + +/** + * Determine whether a reflog entry should be created for the given reference. + * + * Whether or not writing to a reference should create a reflog entry is + * dependent on a number of things. Most importantly, there's the + * "core.logAllRefUpdates" setting that controls in which situations a + * reference should get a corresponding reflog entry. The following values for + * it are understood: + * + * - "false": Do not log reference updates. + * + * - "true": Log normal reference updates. This will write entries for + * references in "refs/heads", "refs/remotes", "refs/notes" and + * "HEAD" or if the reference already has a log entry. + * + * - "always": Always create a reflog entry. + * + * If unset, the value will default to "true" for non-bare repositories and + * "false" for bare ones. + * + * @param out pointer to which the result will be written, `1` means a reflog + * entry should be written, `0` means none should be written. + * @param db The refdb to decide this for. + * @param ref The reference one wants to check. + * @return `0` on success, a negative error code otherwise. + */ +int git_refdb_should_write_reflog(int *out, git_refdb *db, const git_reference *ref); + +/** + * Determine whether a reflog entry should be created for HEAD if creating one + * for the given reference + * + * In case the given reference is being pointed to by HEAD, then creating a + * reflog entry for this reference also requires us to create a corresponding + * reflog entry for HEAD. This function can be used to determine that scenario. + * + * @param out pointer to which the result will be written, `1` means a reflog + * entry should be written, `0` means none should be written. + * @param db The refdb to decide this for. + * @param ref The reference one wants to check. + * @return `0` on success, a negative error code otherwise. + */ +int git_refdb_should_write_head_reflog(int *out, git_refdb *db, const git_reference *ref); + +int git_refdb_has_log(git_refdb *db, const char *refname); +int git_refdb_ensure_log(git_refdb *refdb, const char *refname); + +int git_refdb_lock(void **payload, git_refdb *db, const char *refname); +int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message); + +#endif diff --git a/src/libgit2/refdb_fs.c b/src/libgit2/refdb_fs.c new file mode 100644 index 000000000..95bda9404 --- /dev/null +++ b/src/libgit2/refdb_fs.c @@ -0,0 +1,2464 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "refs.h" +#include "hash.h" +#include "repository.h" +#include "futils.h" +#include "filebuf.h" +#include "pack.h" +#include "parse.h" +#include "reflog.h" +#include "refdb.h" +#include "iterator.h" +#include "sortedcache.h" +#include "signature.h" +#include "wildmatch.h" +#include "path.h" + +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_NESTING_LEVEL 5 +#define MAX_NESTING_LEVEL 10 + +enum { + PACKREF_HAS_PEEL = 1, + PACKREF_WAS_LOOSE = 2, + PACKREF_CANNOT_PEEL = 4, + PACKREF_SHADOWED = 8 +}; + +enum { + PEELING_NONE = 0, + PEELING_STANDARD, + PEELING_FULL +}; + +struct packref { + git_oid oid; + git_oid peel; + char flags; + char name[GIT_FLEX_ARRAY]; +}; + +typedef struct refdb_fs_backend { + git_refdb_backend parent; + + git_repository *repo; + /* path to git directory */ + char *gitpath; + /* path to common objects' directory */ + char *commonpath; + + git_sortedcache *refcache; + int peeling_mode; + git_iterator_flag_t iterator_flags; + uint32_t direach_flags; + int fsync; + git_map packed_refs_map; + git_mutex prlock; /* protect packed_refs_map */ + git_futils_filestamp packed_refs_stamp; + bool sorted; +} refdb_fs_backend; + +static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name); +static char *packed_set_peeling_mode(char *data, size_t data_sz, refdb_fs_backend *backend); + +GIT_INLINE(int) loose_path( + git_str *out, + const char *base, + const char *refname) +{ + if (git_str_joinpath(out, base, refname) < 0) + return -1; + + return git_fs_path_validate_str_length_with_suffix(out, + CONST_STRLEN(".lock")); +} + +GIT_INLINE(int) reflog_path( + git_str *out, + git_repository *repo, + const char *refname) +{ + const char *base; + int error; + + base = (strcmp(refname, GIT_HEAD_FILE) == 0) ? repo->gitdir : + repo->commondir; + + if ((error = git_str_joinpath(out, base, GIT_REFLOG_DIR)) < 0) + return error; + + return loose_path(out, out->ptr, refname); +} + +static int packref_cmp(const void *a_, const void *b_) +{ + const struct packref *a = a_, *b = b_; + return strcmp(a->name, b->name); +} + +static int packed_reload(refdb_fs_backend *backend) +{ + int error; + git_str packedrefs = GIT_STR_INIT; + char *scan, *eof, *eol; + + if (!backend->gitpath) + return 0; + + error = git_sortedcache_lockandload(backend->refcache, &packedrefs); + + /* + * If we can't find the packed-refs, clear table and return. + * Any other error just gets passed through. + * If no error, and file wasn't changed, just return. + * Anything else means we need to refresh the packed refs. + */ + if (error <= 0) { + if (error == GIT_ENOTFOUND) { + GIT_UNUSED(git_sortedcache_clear(backend->refcache, true)); + git_error_clear(); + error = 0; + } + return error; + } + + /* At this point, refresh the packed refs from the loaded buffer. */ + + GIT_UNUSED(git_sortedcache_clear(backend->refcache, false)); + + scan = packedrefs.ptr; + eof = scan + packedrefs.size; + + scan = packed_set_peeling_mode(scan, packedrefs.size, backend); + if (!scan) + goto parse_failed; + + while (scan < eof && *scan == '#') { + if (!(eol = strchr(scan, '\n'))) + goto parse_failed; + scan = eol + 1; + } + + while (scan < eof) { + struct packref *ref; + git_oid oid; + + /* parse " \n" */ + + if (git_oid_fromstr(&oid, scan) < 0) + goto parse_failed; + scan += GIT_OID_HEXSZ; + + if (*scan++ != ' ') + goto parse_failed; + if (!(eol = strchr(scan, '\n'))) + goto parse_failed; + *eol = '\0'; + if (eol[-1] == '\r') + eol[-1] = '\0'; + + if (git_sortedcache_upsert((void **)&ref, backend->refcache, scan) < 0) + goto parse_failed; + scan = eol + 1; + + git_oid_cpy(&ref->oid, &oid); + + /* look for optional "^\n" */ + + if (*scan == '^') { + if (git_oid_fromstr(&oid, scan + 1) < 0) + goto parse_failed; + scan += GIT_OID_HEXSZ + 1; + + if (scan < eof) { + if (!(eol = strchr(scan, '\n'))) + goto parse_failed; + scan = eol + 1; + } + + git_oid_cpy(&ref->peel, &oid); + ref->flags |= PACKREF_HAS_PEEL; + } + else if (backend->peeling_mode == PEELING_FULL || + (backend->peeling_mode == PEELING_STANDARD && + git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0)) + ref->flags |= PACKREF_CANNOT_PEEL; + } + + git_sortedcache_wunlock(backend->refcache); + git_str_dispose(&packedrefs); + + return 0; + +parse_failed: + git_error_set(GIT_ERROR_REFERENCE, "corrupted packed references file"); + + GIT_UNUSED(git_sortedcache_clear(backend->refcache, false)); + git_sortedcache_wunlock(backend->refcache); + git_str_dispose(&packedrefs); + + return -1; +} + +static int loose_parse_oid( + git_oid *oid, const char *filename, git_str *file_content) +{ + const char *str = git_str_cstr(file_content); + + if (git_str_len(file_content) < GIT_OID_HEXSZ) + goto corrupted; + + /* we need to get 40 OID characters from the file */ + if (git_oid_fromstr(oid, str) < 0) + goto corrupted; + + /* If the file is longer than 40 chars, the 41st must be a space */ + str += GIT_OID_HEXSZ; + if (*str == '\0' || git__isspace(*str)) + return 0; + +corrupted: + git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file: %s", filename); + return -1; +} + +static int loose_readbuffer(git_str *buf, const char *base, const char *path) +{ + int error; + + if ((error = loose_path(buf, base, path)) < 0 || + (error = git_futils_readbuffer(buf, buf->ptr)) < 0) + git_str_dispose(buf); + + return error; +} + +static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name) +{ + int error = 0; + git_str ref_file = GIT_STR_INIT; + struct packref *ref = NULL; + git_oid oid; + + /* if we fail to load the loose reference, assume someone changed + * the filesystem under us and skip it... + */ + if (loose_readbuffer(&ref_file, backend->gitpath, name) < 0) { + git_error_clear(); + goto done; + } + + /* skip symbolic refs */ + if (!git__prefixcmp(git_str_cstr(&ref_file), GIT_SYMREF)) + goto done; + + /* parse OID from file */ + if ((error = loose_parse_oid(&oid, name, &ref_file)) < 0) + goto done; + + if ((error = git_sortedcache_wlock(backend->refcache)) < 0) + goto done; + + if (!(error = git_sortedcache_upsert( + (void **)&ref, backend->refcache, name))) { + + git_oid_cpy(&ref->oid, &oid); + ref->flags = PACKREF_WAS_LOOSE; + } + + git_sortedcache_wunlock(backend->refcache); + +done: + git_str_dispose(&ref_file); + return error; +} + +static int _dirent_loose_load(void *payload, git_str *full_path) +{ + refdb_fs_backend *backend = payload; + const char *file_path; + + if (git__suffixcmp(full_path->ptr, ".lock") == 0) + return 0; + + if (git_fs_path_isdir(full_path->ptr)) { + int error = git_fs_path_direach( + full_path, backend->direach_flags, _dirent_loose_load, backend); + /* Race with the filesystem, ignore it */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + return 0; + } + + return error; + } + + file_path = full_path->ptr + strlen(backend->gitpath); + + return loose_lookup_to_packfile(backend, file_path); +} + +/* + * Load all the loose references from the repository + * into the in-memory Packfile, and build a vector with + * all the references so it can be written back to + * disk. + */ +static int packed_loadloose(refdb_fs_backend *backend) +{ + int error; + git_str refs_path = GIT_STR_INIT; + + if (git_str_joinpath(&refs_path, backend->gitpath, GIT_REFS_DIR) < 0) + return -1; + + /* + * Load all the loose files from disk into the Packfile table. + * This will overwrite any old packed entries with their + * updated loose versions + */ + error = git_fs_path_direach( + &refs_path, backend->direach_flags, _dirent_loose_load, backend); + + git_str_dispose(&refs_path); + + return error; +} + +static int refdb_fs_backend__exists( + int *exists, + git_refdb_backend *_backend, + const char *ref_name) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + git_str ref_path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(backend); + + *exists = 0; + + if ((error = loose_path(&ref_path, backend->gitpath, ref_name)) < 0) + goto out; + + if (git_fs_path_isfile(ref_path.ptr)) { + *exists = 1; + goto out; + } + + if ((error = packed_reload(backend)) < 0) + goto out; + + if (git_sortedcache_lookup(backend->refcache, ref_name) != NULL) { + *exists = 1; + goto out; + } + +out: + git_str_dispose(&ref_path); + return error; +} + +static const char *loose_parse_symbolic(git_str *file_content) +{ + const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); + const char *refname_start; + + refname_start = (const char *)file_content->ptr; + + if (git_str_len(file_content) < header_len + 1) { + git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file"); + return NULL; + } + + /* + * Assume we have already checked for the header + * before calling this function + */ + refname_start += header_len; + + return refname_start; +} + +/* + * Returns whether a reference is stored per worktree or not. + * Per-worktree references are: + * + * - all pseudorefs, e.g. HEAD and MERGE_HEAD + * - all references stored inside of "refs/bisect/" + */ +static bool is_per_worktree_ref(const char *ref_name) +{ + return git__prefixcmp(ref_name, "refs/") != 0 || + git__prefixcmp(ref_name, "refs/bisect/") == 0; +} + +static int loose_lookup( + git_reference **out, + refdb_fs_backend *backend, + const char *ref_name) +{ + git_str ref_file = GIT_STR_INIT; + int error = 0; + const char *ref_dir; + + if (out) + *out = NULL; + + if (is_per_worktree_ref(ref_name)) + ref_dir = backend->gitpath; + else + ref_dir = backend->commonpath; + + if ((error = loose_readbuffer(&ref_file, ref_dir, ref_name)) < 0) + /* cannot read loose ref file - gah */; + else if (git__prefixcmp(git_str_cstr(&ref_file), GIT_SYMREF) == 0) { + const char *target; + + git_str_rtrim(&ref_file); + + if (!(target = loose_parse_symbolic(&ref_file))) + error = -1; + else if (out != NULL) + *out = git_reference__alloc_symbolic(ref_name, target); + } else { + git_oid oid; + + if (!(error = loose_parse_oid(&oid, ref_name, &ref_file)) && + out != NULL) + *out = git_reference__alloc(ref_name, &oid, NULL); + } + + git_str_dispose(&ref_file); + return error; +} + +static int ref_error_notfound(const char *name) +{ + git_error_set(GIT_ERROR_REFERENCE, "reference '%s' not found", name); + return GIT_ENOTFOUND; +} + +static char *packed_set_peeling_mode( + char *data, + size_t data_sz, + refdb_fs_backend *backend) +{ + static const char *traits_header = "# pack-refs with:"; + char *eol; + backend->peeling_mode = PEELING_NONE; + + if (git__prefixncmp(data, data_sz, traits_header) == 0) { + size_t hdr_sz = strlen(traits_header); + const char *sorted = " sorted "; + const char *peeled = " peeled "; + const char *fully_peeled = " fully-peeled "; + data += hdr_sz; + data_sz -= hdr_sz; + + eol = memchr(data, '\n', data_sz); + + if (!eol) + return NULL; + + if (git__memmem(data, eol - data, fully_peeled, strlen(fully_peeled))) + backend->peeling_mode = PEELING_FULL; + else if (git__memmem(data, eol - data, peeled, strlen(peeled))) + backend->peeling_mode = PEELING_STANDARD; + + backend->sorted = NULL != git__memmem(data, eol - data, sorted, strlen(sorted)); + + return eol + 1; + } + return data; +} + +static void packed_map_free(refdb_fs_backend *backend) +{ + if (backend->packed_refs_map.data) { +#ifdef GIT_WIN32 + git__free(backend->packed_refs_map.data); +#else + git_futils_mmap_free(&backend->packed_refs_map); +#endif + backend->packed_refs_map.data = NULL; + backend->packed_refs_map.len = 0; + git_futils_filestamp_set(&backend->packed_refs_stamp, NULL); + } +} + +static int packed_map_check(refdb_fs_backend *backend) +{ + int error = 0; + git_file fd = -1; + struct stat st; + + if ((error = git_mutex_lock(&backend->prlock)) < 0) + return error; + + if (backend->packed_refs_map.data && + !git_futils_filestamp_check( + &backend->packed_refs_stamp, backend->refcache->path)) { + git_mutex_unlock(&backend->prlock); + return error; + } + packed_map_free(backend); + + fd = git_futils_open_ro(backend->refcache->path); + if (fd < 0) { + git_mutex_unlock(&backend->prlock); + if (fd == GIT_ENOTFOUND) { + git_error_clear(); + return 0; + } + return fd; + } + + if (p_fstat(fd, &st) < 0) { + p_close(fd); + git_mutex_unlock(&backend->prlock); + git_error_set(GIT_ERROR_OS, "unable to stat packed-refs '%s'", backend->refcache->path); + return -1; + } + + if (st.st_size == 0) { + p_close(fd); + git_mutex_unlock(&backend->prlock); + return 0; + } + + git_futils_filestamp_set_from_stat(&backend->packed_refs_stamp, &st); + +#ifdef GIT_WIN32 + /* on windows, we copy the entire file into memory rather than using + * mmap() because using mmap() on windows also locks the file and this + * map is long-lived. */ + backend->packed_refs_map.len = (size_t)st.st_size; + backend->packed_refs_map.data = + git__malloc(backend->packed_refs_map.len); + GIT_ERROR_CHECK_ALLOC(backend->packed_refs_map.data); + { + ssize_t bytesread = + p_read(fd, backend->packed_refs_map.data, + backend->packed_refs_map.len); + error = (bytesread == (ssize_t)backend->packed_refs_map.len) ? 0 : -1; + } +#else + error = git_futils_mmap_ro(&backend->packed_refs_map, fd, 0, (size_t)st.st_size); +#endif + p_close(fd); + if (error < 0) { + git_mutex_unlock(&backend->prlock); + return error; + } + + packed_set_peeling_mode( + backend->packed_refs_map.data, backend->packed_refs_map.len, + backend); + + git_mutex_unlock(&backend->prlock); + return error; +} + +/* + * Find beginning of packed-ref record pointed to by p. + * buf - a lower-bound pointer to some memory buffer + * p - an upper-bound pointer to the same memory buffer + */ +static const char *start_of_record(const char *buf, const char *p) +{ + const char *nl = p; + while (true) { + nl = git__memrchr(buf, '\n', nl - buf); + if (!nl) + return buf; + + if (nl[1] == '^' && nl > buf) + --nl; + else + break; + }; + return nl + 1; +} + +/* + * Find end of packed-ref record pointed to by p. + * end - an upper-bound pointer to some memory buffer + * p - a lower-bound pointer to the same memory buffer + */ +static const char *end_of_record(const char *p, const char *end) +{ + while (1) { + size_t sz = end - p; + p = memchr(p, '\n', sz); + if (!p) + return end; + ++p; + if (p < end && p[0] == '^') + ++p; + else + break; + } + return p; +} + +static int +cmp_record_to_refname(const char *rec, size_t data_end, const char *ref_name) +{ + const size_t ref_len = strlen(ref_name); + int cmp_val; + const char *end; + + rec += GIT_OID_HEXSZ + 1; /* + space */ + if (data_end < GIT_OID_HEXSZ + 3) { + /* an incomplete (corrupt) record is treated as less than ref_name */ + return -1; + } + data_end -= GIT_OID_HEXSZ + 1; + + end = memchr(rec, '\n', data_end); + if (end) + data_end = end - rec; + + cmp_val = memcmp(rec, ref_name, min(ref_len, data_end)); + + if (cmp_val == 0 && data_end != ref_len) + return (data_end > ref_len) ? 1 : -1; + return cmp_val; +} + +static int packed_unsorted_lookup( + git_reference **out, + refdb_fs_backend *backend, + const char *ref_name) +{ + int error = 0; + struct packref *entry; + + if ((error = packed_reload(backend)) < 0) + return error; + + if (git_sortedcache_rlock(backend->refcache) < 0) + return -1; + + entry = git_sortedcache_lookup(backend->refcache, ref_name); + if (!entry) { + error = ref_error_notfound(ref_name); + } else { + *out = git_reference__alloc(ref_name, &entry->oid, &entry->peel); + if (!*out) + error = -1; + } + + git_sortedcache_runlock(backend->refcache); + + return error; +} + +static int packed_lookup( + git_reference **out, + refdb_fs_backend *backend, + const char *ref_name) +{ + int error = 0; + const char *left, *right, *data_end; + + if ((error = packed_map_check(backend)) < 0) + return error; + + if (!backend->sorted) + return packed_unsorted_lookup(out, backend, ref_name); + + left = backend->packed_refs_map.data; + right = data_end = (const char *) backend->packed_refs_map.data + + backend->packed_refs_map.len; + + while (left < right && *left == '#') { + if (!(left = memchr(left, '\n', data_end - left))) + goto parse_failed; + left++; + } + + while (left < right) { + const char *mid, *rec; + int compare; + + mid = left + (right - left) / 2; + rec = start_of_record(left, mid); + compare = cmp_record_to_refname(rec, data_end - rec, ref_name); + + if (compare < 0) { + left = end_of_record(mid, right); + } else if (compare > 0) { + right = rec; + } else { + const char *eol; + git_oid oid, peel, *peel_ptr = NULL; + + if (data_end - rec < GIT_OID_HEXSZ || + git_oid_fromstr(&oid, rec) < 0) { + goto parse_failed; + } + rec += GIT_OID_HEXSZ + 1; + if (!(eol = memchr(rec, '\n', data_end - rec))) { + goto parse_failed; + } + + /* look for optional "^\n" */ + + if (eol + 1 < data_end) { + rec = eol + 1; + + if (*rec == '^') { + rec++; + if (data_end - rec < GIT_OID_HEXSZ || + git_oid_fromstr(&peel, rec) < 0) { + goto parse_failed; + } + peel_ptr = &peel; + } + } + + *out = git_reference__alloc(ref_name, &oid, peel_ptr); + if (!*out) { + return -1; + } + + return 0; + } + } + return GIT_ENOTFOUND; + +parse_failed: + git_error_set(GIT_ERROR_REFERENCE, "corrupted packed references file"); + return -1; +} + +static int refdb_fs_backend__lookup( + git_reference **out, + git_refdb_backend *_backend, + const char *ref_name) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + int error; + + GIT_ASSERT_ARG(backend); + + if (!(error = loose_lookup(out, backend, ref_name))) + return 0; + + /* only try to lookup this reference on the packfile if it + * wasn't found on the loose refs; not if there was a critical error */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = packed_lookup(out, backend, ref_name); + } + return error; +} + +typedef struct { + git_reference_iterator parent; + + char *glob; + + git_pool pool; + git_vector loose; + + git_sortedcache *cache; + size_t loose_pos; + size_t packed_pos; +} refdb_fs_iter; + +static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) +{ + refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent); + + git_vector_free(&iter->loose); + git_pool_clear(&iter->pool); + git_sortedcache_free(iter->cache); + git__free(iter); +} + +static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) +{ + int error = 0; + git_str path = GIT_STR_INIT; + git_iterator *fsit = NULL; + git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry = NULL; + const char *ref_prefix = GIT_REFS_DIR; + size_t ref_prefix_len = strlen(ref_prefix); + + if (!backend->commonpath) /* do nothing if no commonpath for loose refs */ + return 0; + + fsit_opts.flags = backend->iterator_flags; + + if (iter->glob) { + const char *last_sep = NULL; + const char *pos; + for (pos = iter->glob; *pos; ++pos) { + switch (*pos) { + case '?': + case '*': + case '[': + case '\\': + break; + case '/': + last_sep = pos; + /* FALLTHROUGH */ + default: + continue; + } + break; + } + if (last_sep) { + ref_prefix = iter->glob; + ref_prefix_len = (last_sep - ref_prefix) + 1; + } + } + + if ((error = git_str_puts(&path, backend->commonpath)) < 0 || + (error = git_str_put(&path, ref_prefix, ref_prefix_len)) < 0) { + git_str_dispose(&path); + return error; + } + + if ((error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) { + git_str_dispose(&path); + return (iter->glob && error == GIT_ENOTFOUND)? 0 : error; + } + + error = git_str_sets(&path, ref_prefix); + + while (!error && !git_iterator_advance(&entry, fsit)) { + const char *ref_name; + char *ref_dup; + + git_str_truncate(&path, ref_prefix_len); + git_str_puts(&path, entry->path); + ref_name = git_str_cstr(&path); + + if (git__suffixcmp(ref_name, ".lock") == 0 || + (iter->glob && wildmatch(iter->glob, ref_name, 0) != 0)) + continue; + + ref_dup = git_pool_strdup(&iter->pool, ref_name); + if (!ref_dup) + error = -1; + else + error = git_vector_insert(&iter->loose, ref_dup); + } + + git_iterator_free(fsit); + git_str_dispose(&path); + + return error; +} + +static int refdb_fs_backend__iterator_next( + git_reference **out, git_reference_iterator *_iter) +{ + int error = GIT_ITEROVER; + refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent); + refdb_fs_backend *backend = GIT_CONTAINER_OF(iter->parent.db->backend, refdb_fs_backend, parent); + struct packref *ref; + + while (iter->loose_pos < iter->loose.length) { + const char *path = git_vector_get(&iter->loose, iter->loose_pos++); + + if (loose_lookup(out, backend, path) == 0) { + ref = git_sortedcache_lookup(iter->cache, path); + if (ref) + ref->flags |= PACKREF_SHADOWED; + + return 0; + } + + git_error_clear(); + } + + error = GIT_ITEROVER; + while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { + ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); + if (!ref) /* stop now if another thread deleted refs and we past end */ + break; + + if (ref->flags & PACKREF_SHADOWED) + continue; + if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0) + continue; + + *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel); + error = (*out != NULL) ? 0 : -1; + break; + } + + return error; +} + +static int refdb_fs_backend__iterator_next_name( + const char **out, git_reference_iterator *_iter) +{ + int error = GIT_ITEROVER; + refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent); + refdb_fs_backend *backend = GIT_CONTAINER_OF(iter->parent.db->backend, refdb_fs_backend, parent); + struct packref *ref; + + while (iter->loose_pos < iter->loose.length) { + const char *path = git_vector_get(&iter->loose, iter->loose_pos++); + struct packref *ref; + + if (loose_lookup(NULL, backend, path) == 0) { + ref = git_sortedcache_lookup(iter->cache, path); + if (ref) + ref->flags |= PACKREF_SHADOWED; + + *out = path; + return 0; + } + + git_error_clear(); + } + + error = GIT_ITEROVER; + while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { + ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); + if (!ref) /* stop now if another thread deleted refs and we past end */ + break; + + if (ref->flags & PACKREF_SHADOWED) + continue; + if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0) + continue; + + *out = ref->name; + error = 0; + break; + } + + return error; +} + +static int refdb_fs_backend__iterator( + git_reference_iterator **out, git_refdb_backend *_backend, const char *glob) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + refdb_fs_iter *iter = NULL; + int error; + + GIT_ASSERT_ARG(backend); + + iter = git__calloc(1, sizeof(refdb_fs_iter)); + GIT_ERROR_CHECK_ALLOC(iter); + + if ((error = git_pool_init(&iter->pool, 1)) < 0) + goto out; + + if ((error = git_vector_init(&iter->loose, 8, NULL)) < 0) + goto out; + + if (glob != NULL && + (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL) { + error = GIT_ERROR_NOMEMORY; + goto out; + } + + if ((error = iter_load_loose_paths(backend, iter)) < 0) + goto out; + + if ((error = packed_reload(backend)) < 0) + goto out; + + if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0) + goto out; + + iter->parent.next = refdb_fs_backend__iterator_next; + iter->parent.next_name = refdb_fs_backend__iterator_next_name; + iter->parent.free = refdb_fs_backend__iterator_free; + + *out = (git_reference_iterator *)iter; +out: + if (error) + refdb_fs_backend__iterator_free((git_reference_iterator *)iter); + return error; +} + +static bool ref_is_available( + const char *old_ref, const char *new_ref, const char *this_ref) +{ + if (old_ref == NULL || strcmp(old_ref, this_ref)) { + size_t reflen = strlen(this_ref); + size_t newlen = strlen(new_ref); + size_t cmplen = reflen < newlen ? reflen : newlen; + const char *lead = reflen < newlen ? new_ref : this_ref; + + if (!strncmp(new_ref, this_ref, cmplen) && lead[cmplen] == '/') { + return false; + } + } + + return true; +} + +static int reference_path_available( + refdb_fs_backend *backend, + const char *new_ref, + const char *old_ref, + int force) +{ + size_t i; + int error; + + if ((error = packed_reload(backend)) < 0) + return error; + + if (!force) { + int exists; + + if ((error = refdb_fs_backend__exists( + &exists, (git_refdb_backend *)backend, new_ref)) < 0) { + return error; + } + + if (exists) { + git_error_set(GIT_ERROR_REFERENCE, + "failed to write reference '%s': a reference with " + "that name already exists.", new_ref); + return GIT_EEXISTS; + } + } + + if ((error = git_sortedcache_rlock(backend->refcache)) < 0) + return error; + + for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { + struct packref *ref = git_sortedcache_entry(backend->refcache, i); + + if (ref && !ref_is_available(old_ref, new_ref, ref->name)) { + git_sortedcache_runlock(backend->refcache); + git_error_set(GIT_ERROR_REFERENCE, + "path to reference '%s' collides with existing one", new_ref); + return -1; + } + } + + git_sortedcache_runlock(backend->refcache); + return 0; +} + +static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name) +{ + int error, filebuf_flags; + git_str ref_path = GIT_STR_INIT; + const char *basedir; + + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(name); + + if (!git_path_is_valid(backend->repo, name, 0, GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS)) { + git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", name); + return GIT_EINVALIDSPEC; + } + + if (is_per_worktree_ref(name)) + basedir = backend->gitpath; + else + basedir = backend->commonpath; + + /* Remove a possibly existing empty directory hierarchy + * which name would collide with the reference name + */ + if ((error = git_futils_rmdir_r(name, basedir, GIT_RMDIR_SKIP_NONEMPTY)) < 0) + return error; + + if ((error = loose_path(&ref_path, basedir, name)) < 0) + return error; + + filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS; + if (backend->fsync) + filebuf_flags |= GIT_FILEBUF_FSYNC; + + error = git_filebuf_open(file, ref_path.ptr, filebuf_flags, GIT_REFS_FILE_MODE); + + if (error == GIT_EDIRECTORY) + git_error_set(GIT_ERROR_REFERENCE, "cannot lock ref '%s', there are refs beneath that folder", name); + + git_str_dispose(&ref_path); + return error; +} + +static int loose_commit(git_filebuf *file, const git_reference *ref) +{ + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(ref); + + if (ref->type == GIT_REFERENCE_DIRECT) { + char oid[GIT_OID_HEXSZ + 1]; + git_oid_nfmt(oid, sizeof(oid), &ref->target.oid); + + git_filebuf_printf(file, "%s\n", oid); + } else if (ref->type == GIT_REFERENCE_SYMBOLIC) { + git_filebuf_printf(file, GIT_SYMREF "%s\n", ref->target.symbolic); + } else { + GIT_ASSERT(0); + } + + return git_filebuf_commit(file); +} + +static int refdb_fs_backend__lock(void **out, git_refdb_backend *_backend, const char *refname) +{ + int error; + git_filebuf *lock; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + + lock = git__calloc(1, sizeof(git_filebuf)); + GIT_ERROR_CHECK_ALLOC(lock); + + if ((error = loose_lock(lock, backend, refname)) < 0) { + git__free(lock); + return error; + } + + *out = lock; + return 0; +} + +static int refdb_fs_backend__write_tail( + git_refdb_backend *_backend, + const git_reference *ref, + git_filebuf *file, + int update_reflog, + const git_oid *old_id, + const char *old_target, + const git_signature *who, + const char *message); + +static int refdb_fs_backend__delete_tail( + git_refdb_backend *_backend, + git_filebuf *file, + const char *ref_name, + const git_oid *old_id, + const char *old_target); + +static int refdb_fs_backend__unlock(git_refdb_backend *backend, void *payload, int success, int update_reflog, + const git_reference *ref, const git_signature *sig, const char *message) +{ + git_filebuf *lock = (git_filebuf *) payload; + int error = 0; + + if (success == 2) + error = refdb_fs_backend__delete_tail(backend, lock, ref->name, NULL, NULL); + else if (success) + error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, NULL, NULL, sig, message); + else + git_filebuf_cleanup(lock); + + git__free(lock); + return error; +} + +/* + * Find out what object this reference resolves to. + * + * For references that point to a 'big' tag (e.g. an + * actual tag object on the repository), we need to + * cache on the packfile the OID of the object to + * which that 'big tag' is pointing to. + */ +static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref) +{ + git_object *object; + + if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL) + return 0; + + /* + * Find the tagged object in the repository + */ + if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJECT_ANY) < 0) + return -1; + + /* + * If the tagged object is a Tag object, we need to resolve it; + * if the ref is actually a 'weak' ref, we don't need to resolve + * anything. + */ + if (git_object_type(object) == GIT_OBJECT_TAG) { + git_tag *tag = (git_tag *)object; + + /* + * Find the object pointed at by this tag + */ + git_oid_cpy(&ref->peel, git_tag_target_id(tag)); + ref->flags |= PACKREF_HAS_PEEL; + + /* + * The reference has now cached the resolved OID, and is + * marked at such. When written to the packfile, it'll be + * accompanied by this resolved oid + */ + } + + git_object_free(object); + return 0; +} + +/* + * Write a single reference into a packfile + */ +static int packed_write_ref(struct packref *ref, git_filebuf *file) +{ + char oid[GIT_OID_HEXSZ + 1]; + git_oid_nfmt(oid, sizeof(oid), &ref->oid); + + /* + * For references that peel to an object in the repo, we must + * write the resulting peel on a separate line, e.g. + * + * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4 + * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100 + * + * This obviously only applies to tags. + * The required peels have already been loaded into `ref->peel_target`. + */ + if (ref->flags & PACKREF_HAS_PEEL) { + char peel[GIT_OID_HEXSZ + 1]; + git_oid_nfmt(peel, sizeof(peel), &ref->peel); + + if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) + return -1; + } else { + if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0) + return -1; + } + + return 0; +} + +/* + * Remove all loose references + * + * Once we have successfully written a packfile, + * all the loose references that were packed must be + * removed from disk. + * + * This is a dangerous method; make sure the packfile + * is well-written, because we are destructing references + * here otherwise. + */ +static int packed_remove_loose(refdb_fs_backend *backend) +{ + size_t i; + git_filebuf lock = GIT_FILEBUF_INIT; + git_str ref_content = GIT_STR_INIT; + int error = 0; + + /* backend->refcache is already locked when this is called */ + + for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { + struct packref *ref = git_sortedcache_entry(backend->refcache, i); + git_oid current_id; + + if (!ref || !(ref->flags & PACKREF_WAS_LOOSE)) + continue; + + git_filebuf_cleanup(&lock); + + /* We need to stop anybody from updating the ref while we try to do a safe delete */ + error = loose_lock(&lock, backend, ref->name); + /* If someone else is updating it, let them do it */ + if (error == GIT_EEXISTS || error == GIT_ENOTFOUND) + continue; + + if (error < 0) { + git_str_dispose(&ref_content); + git_error_set(GIT_ERROR_REFERENCE, "failed to lock loose reference '%s'", ref->name); + return error; + } + + error = git_futils_readbuffer(&ref_content, lock.path_original); + /* Someone else beat us to cleaning up the ref, let's simply continue */ + if (error == GIT_ENOTFOUND) + continue; + + /* This became a symref between us packing and trying to delete it, so ignore it */ + if (!git__prefixcmp(ref_content.ptr, GIT_SYMREF)) + continue; + + /* Figure out the current id; if we find a bad ref file, skip it so we can do the rest */ + if (loose_parse_oid(¤t_id, lock.path_original, &ref_content) < 0) + continue; + + /* If the ref moved since we packed it, we must not delete it */ + if (!git_oid_equal(¤t_id, &ref->oid)) + continue; + + /* + * if we fail to remove a single file, this is *not* good, + * but we should keep going and remove as many as possible. + * If we fail to remove, the ref is still in the old state, so + * we haven't lost information. + */ + p_unlink(lock.path_original); + } + + git_str_dispose(&ref_content); + git_filebuf_cleanup(&lock); + return 0; +} + +/* + * Write all the contents in the in-memory packfile to disk. + */ +static int packed_write(refdb_fs_backend *backend) +{ + git_sortedcache *refcache = backend->refcache; + git_filebuf pack_file = GIT_FILEBUF_INIT; + int error, open_flags = 0; + size_t i; + + /* take lock and close up packed-refs mmap if open */ + if ((error = git_mutex_lock(&backend->prlock)) < 0) { + return error; + } + + packed_map_free(backend); + + git_mutex_unlock(&backend->prlock); + + /* lock the cache to updates while we do this */ + if ((error = git_sortedcache_wlock(refcache)) < 0) + return error; + + if (backend->fsync) + open_flags = GIT_FILEBUF_FSYNC; + + /* Open the file! */ + if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), open_flags, GIT_PACKEDREFS_FILE_MODE)) < 0) + goto fail; + + /* Packfiles have a header... apparently + * This is in fact not required, but we might as well print it + * just for kicks */ + if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < 0) + goto fail; + + for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) { + struct packref *ref = git_sortedcache_entry(refcache, i); + GIT_ASSERT(ref); + + if ((error = packed_find_peel(backend, ref)) < 0) + goto fail; + + if ((error = packed_write_ref(ref, &pack_file)) < 0) + goto fail; + } + + /* if we've written all the references properly, we can commit + * the packfile to make the changes effective */ + if ((error = git_filebuf_commit(&pack_file)) < 0) + goto fail; + + /* when and only when the packfile has been properly written, + * we can go ahead and remove the loose refs */ + if ((error = packed_remove_loose(backend)) < 0) + goto fail; + + git_sortedcache_updated(refcache); + git_sortedcache_wunlock(refcache); + + /* we're good now */ + return 0; + +fail: + git_filebuf_cleanup(&pack_file); + git_sortedcache_wunlock(refcache); + + return error; +} + +static int packed_delete(refdb_fs_backend *backend, const char *ref_name) +{ + size_t pack_pos; + int error, found = 0; + + if ((error = packed_reload(backend)) < 0) + goto cleanup; + + if ((error = git_sortedcache_wlock(backend->refcache)) < 0) + goto cleanup; + + /* If a packed reference exists, remove it from the packfile and repack if necessary */ + error = git_sortedcache_lookup_index(&pack_pos, backend->refcache, ref_name); + if (error == 0) { + error = git_sortedcache_remove(backend->refcache, pack_pos); + found = 1; + } + if (error == GIT_ENOTFOUND) + error = 0; + + git_sortedcache_wunlock(backend->refcache); + + if (found) + error = packed_write(backend); + +cleanup: + return error; +} + +static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *author, const char *message); + +static int cmp_old_ref(int *cmp, git_refdb_backend *backend, const char *name, + const git_oid *old_id, const char *old_target) +{ + int error = 0; + git_reference *old_ref = NULL; + + *cmp = 0; + /* It "matches" if there is no old value to compare against */ + if (!old_id && !old_target) + return 0; + + if ((error = refdb_fs_backend__lookup(&old_ref, backend, name)) < 0) { + if (error == GIT_ENOTFOUND && old_id && git_oid_is_zero(old_id)) + return 0; + goto out; + } + + /* If the types don't match, there's no way the values do */ + if (old_id && old_ref->type != GIT_REFERENCE_DIRECT) { + *cmp = -1; + goto out; + } + if (old_target && old_ref->type != GIT_REFERENCE_SYMBOLIC) { + *cmp = 1; + goto out; + } + + if (old_id && old_ref->type == GIT_REFERENCE_DIRECT) + *cmp = git_oid_cmp(old_id, &old_ref->target.oid); + + if (old_target && old_ref->type == GIT_REFERENCE_SYMBOLIC) + *cmp = git__strcmp(old_target, old_ref->target.symbolic); + +out: + git_reference_free(old_ref); + + return error; +} + +/* + * The git.git comment regarding this, for your viewing pleasure: + * + * Special hack: If a branch is updated directly and HEAD + * points to it (may happen on the remote side of a push + * for example) then logically the HEAD reflog should be + * updated too. + * A generic solution implies reverse symref information, + * but finding all symrefs pointing to the given branch + * would be rather costly for this rare event (the direct + * update of a branch) to be worth it. So let's cheat and + * check with HEAD only which should cover 99% of all usage + * scenarios (even 100% of the default ones). + */ +static int maybe_append_head(refdb_fs_backend *backend, const git_reference *ref, const git_signature *who, const char *message) +{ + git_reference *head = NULL; + git_refdb *refdb = NULL; + int error, write_reflog; + git_oid old_id; + + if ((error = git_repository_refdb(&refdb, backend->repo)) < 0 || + (error = git_refdb_should_write_head_reflog(&write_reflog, refdb, ref)) < 0) + goto out; + if (!write_reflog) + goto out; + + /* if we can't resolve, we use {0}*40 as old id */ + if (git_reference_name_to_id(&old_id, backend->repo, ref->name) < 0) + memset(&old_id, 0, sizeof(old_id)); + + if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0 || + (error = reflog_append(backend, head, &old_id, git_reference_target(ref), who, message)) < 0) + goto out; + +out: + git_reference_free(head); + git_refdb_free(refdb); + return error; +} + +static int refdb_fs_backend__write( + git_refdb_backend *_backend, + const git_reference *ref, + int force, + const git_signature *who, + const char *message, + const git_oid *old_id, + const char *old_target) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + git_filebuf file = GIT_FILEBUF_INIT; + int error = 0; + + GIT_ASSERT_ARG(backend); + + if ((error = reference_path_available(backend, ref->name, NULL, force)) < 0) + return error; + + /* We need to perform the reflog append and old value check under the ref's lock */ + if ((error = loose_lock(&file, backend, ref->name)) < 0) + return error; + + return refdb_fs_backend__write_tail(_backend, ref, &file, true, old_id, old_target, who, message); +} + +static int refdb_fs_backend__write_tail( + git_refdb_backend *_backend, + const git_reference *ref, + git_filebuf *file, + int update_reflog, + const git_oid *old_id, + const char *old_target, + const git_signature *who, + const char *message) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + int error = 0, cmp = 0, should_write; + const char *new_target = NULL; + const git_oid *new_id = NULL; + + if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0) + goto on_error; + + if (cmp) { + git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match"); + error = GIT_EMODIFIED; + goto on_error; + } + + if (ref->type == GIT_REFERENCE_SYMBOLIC) + new_target = ref->target.symbolic; + else + new_id = &ref->target.oid; + + error = cmp_old_ref(&cmp, _backend, ref->name, new_id, new_target); + if (error < 0 && error != GIT_ENOTFOUND) + goto on_error; + + /* Don't update if we have the same value */ + if (!error && !cmp) { + error = 0; + goto on_error; /* not really error */ + } + + if (update_reflog) { + git_refdb *refdb; + + if ((error = git_repository_refdb__weakptr(&refdb, backend->repo)) < 0 || + (error = git_refdb_should_write_reflog(&should_write, refdb, ref)) < 0) + goto on_error; + + if (should_write) { + if ((error = reflog_append(backend, ref, NULL, NULL, who, message)) < 0) + goto on_error; + if ((error = maybe_append_head(backend, ref, who, message)) < 0) + goto on_error; + } + } + + return loose_commit(file, ref); + +on_error: + git_filebuf_cleanup(file); + return error; +} + +static int refdb_fs_backend__prune_refs( + refdb_fs_backend *backend, + const char *ref_name, + const char *prefix) +{ + git_str relative_path = GIT_STR_INIT; + git_str base_path = GIT_STR_INIT; + size_t commonlen; + int error; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(ref_name); + + if ((error = git_str_sets(&relative_path, ref_name)) < 0) + goto cleanup; + + git_fs_path_squash_slashes(&relative_path); + if ((commonlen = git_fs_path_common_dirlen("refs/heads/", git_str_cstr(&relative_path))) == strlen("refs/heads/") || + (commonlen = git_fs_path_common_dirlen("refs/tags/", git_str_cstr(&relative_path))) == strlen("refs/tags/") || + (commonlen = git_fs_path_common_dirlen("refs/remotes/", git_str_cstr(&relative_path))) == strlen("refs/remotes/")) { + + git_str_truncate(&relative_path, commonlen); + + if (prefix) + error = git_str_join3(&base_path, '/', + backend->commonpath, prefix, + git_str_cstr(&relative_path)); + else + error = git_str_joinpath(&base_path, + backend->commonpath, + git_str_cstr(&relative_path)); + + if (!error) + error = git_path_validate_str_length(NULL, &base_path); + + if (error < 0) + goto cleanup; + + error = git_futils_rmdir_r(ref_name + commonlen, + git_str_cstr(&base_path), + GIT_RMDIR_EMPTY_PARENTS | GIT_RMDIR_SKIP_ROOT); + + if (error == GIT_ENOTFOUND) + error = 0; + } + +cleanup: + git_str_dispose(&relative_path); + git_str_dispose(&base_path); + return error; +} + +static int refdb_fs_backend__delete( + git_refdb_backend *_backend, + const char *ref_name, + const git_oid *old_id, const char *old_target) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + git_filebuf file = GIT_FILEBUF_INIT; + int error = 0; + + GIT_ASSERT_ARG(backend); + GIT_ASSERT_ARG(ref_name); + + if ((error = loose_lock(&file, backend, ref_name)) < 0) + return error; + + if ((error = refdb_reflog_fs__delete(_backend, ref_name)) < 0) { + git_filebuf_cleanup(&file); + return error; + } + + return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target); +} + +static int loose_delete(refdb_fs_backend *backend, const char *ref_name) +{ + git_str path = GIT_STR_INIT; + int error = 0; + + if ((error = loose_path(&path, backend->commonpath, ref_name)) < 0) + return error; + + error = p_unlink(path.ptr); + if (error < 0 && errno == ENOENT) + error = GIT_ENOTFOUND; + else if (error != 0) + error = -1; + + git_str_dispose(&path); + + return error; +} + +static int refdb_fs_backend__delete_tail( + git_refdb_backend *_backend, + git_filebuf *file, + const char *ref_name, + const git_oid *old_id, const char *old_target) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + int error = 0, cmp = 0; + bool packed_deleted = 0; + + error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target); + if (error < 0) + goto cleanup; + + if (cmp) { + git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match"); + error = GIT_EMODIFIED; + goto cleanup; + } + + /* + * To ensure that an external observer will see either the current ref value + * (because the loose ref still exists), or a missing ref (after the packed-file is + * unlocked, there will be nothing left), we must ensure things happen in the + * following order: + * + * - the packed-ref file is locked and loaded, as well as a loose one, if it exists + * - we optimistically delete a packed ref, keeping track of whether it existed + * - we delete the loose ref, note that we have its .lock + * - the loose ref is "unlocked", then the packed-ref file is rewritten and unlocked + * - we should prune the path components if a loose ref was deleted + * + * Note that, because our packed backend doesn't expose its filesystem lock, + * we might not be able to guarantee that this is what actually happens (ie. + * as our current code never write packed-refs.lock, nothing stops observers + * from grabbing a "stale" value from there). + */ + if ((error = packed_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if (error == 0) + packed_deleted = 1; + + if ((error = loose_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if (error == GIT_ENOTFOUND) { + error = packed_deleted ? 0 : ref_error_notfound(ref_name); + goto cleanup; + } + +cleanup: + git_filebuf_cleanup(file); + if (error == 0) + error = refdb_fs_backend__prune_refs(backend, ref_name, ""); + return error; +} + +static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name); + +static int refdb_fs_backend__rename( + git_reference **out, + git_refdb_backend *_backend, + const char *old_name, + const char *new_name, + int force, + const git_signature *who, + const char *message) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + git_reference *old, *new = NULL; + git_filebuf file = GIT_FILEBUF_INIT; + int error; + + GIT_ASSERT_ARG(backend); + + if ((error = reference_path_available( + backend, new_name, old_name, force)) < 0 || + (error = refdb_fs_backend__lookup(&old, _backend, old_name)) < 0) + return error; + + if ((error = refdb_fs_backend__delete(_backend, old_name, NULL, NULL)) < 0) { + git_reference_free(old); + return error; + } + + new = git_reference__realloc(&old, new_name); + if (!new) { + git_reference_free(old); + return -1; + } + + if ((error = loose_lock(&file, backend, new->name)) < 0) { + git_reference_free(new); + return error; + } + + /* Try to rename the refog; it's ok if the old doesn't exist */ + error = refdb_reflog_fs__rename(_backend, old_name, new_name); + if (((error == 0) || (error == GIT_ENOTFOUND)) && + ((error = reflog_append(backend, new, git_reference_target(new), NULL, who, message)) < 0)) { + git_reference_free(new); + git_filebuf_cleanup(&file); + return error; + } + + if (error < 0) { + git_reference_free(new); + git_filebuf_cleanup(&file); + return error; + } + + + if ((error = loose_commit(&file, new)) < 0 || out == NULL) { + git_reference_free(new); + return error; + } + + *out = new; + return 0; +} + +static int refdb_fs_backend__compress(git_refdb_backend *_backend) +{ + int error; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + + GIT_ASSERT_ARG(backend); + + if ((error = packed_reload(backend)) < 0 || /* load the existing packfile */ + (error = packed_loadloose(backend)) < 0 || /* add all the loose refs */ + (error = packed_write(backend)) < 0) /* write back to disk */ + return error; + + return 0; +} + +static void refdb_fs_backend__free(git_refdb_backend *_backend) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + + if (!backend) + return; + + git_sortedcache_free(backend->refcache); + + git_mutex_lock(&backend->prlock); + packed_map_free(backend); + git_mutex_unlock(&backend->prlock); + git_mutex_free(&backend->prlock); + + git__free(backend->gitpath); + git__free(backend->commonpath); + git__free(backend); +} + +static char *setup_namespace(git_repository *repo, const char *in) +{ + git_str path = GIT_STR_INIT; + char *parts, *start, *end, *out = NULL; + + if (!in) + goto done; + + git_str_puts(&path, in); + + /* if the repo is not namespaced, nothing else to do */ + if (repo->namespace == NULL) { + out = git_str_detach(&path); + goto done; + } + + parts = end = git__strdup(repo->namespace); + if (parts == NULL) + goto done; + + /* + * From `man gitnamespaces`: + * namespaces which include a / will expand to a hierarchy + * of namespaces; for example, GIT_NAMESPACE=foo/bar will store + * refs under refs/namespaces/foo/refs/namespaces/bar/ + */ + while ((start = git__strsep(&end, "/")) != NULL) + git_str_printf(&path, "refs/namespaces/%s/", start); + + git_str_printf(&path, "refs/namespaces/%s/refs", end); + git__free(parts); + + /* Make sure that the folder with the namespace exists */ + if (git_futils_mkdir_relative(git_str_cstr(&path), in, 0777, + GIT_MKDIR_PATH, NULL) < 0) + goto done; + + /* Return root of the namespaced gitpath, i.e. without the trailing 'refs' */ + git_str_rtruncate_at_char(&path, '/'); + git_str_putc(&path, '/'); + out = git_str_detach(&path); + +done: + git_str_dispose(&path); + return out; +} + +static int reflog_alloc(git_reflog **reflog, const char *name) +{ + git_reflog *log; + + *reflog = NULL; + + log = git__calloc(1, sizeof(git_reflog)); + GIT_ERROR_CHECK_ALLOC(log); + + log->ref_name = git__strdup(name); + GIT_ERROR_CHECK_ALLOC(log->ref_name); + + if (git_vector_init(&log->entries, 0, NULL) < 0) { + git__free(log->ref_name); + git__free(log); + return -1; + } + + *reflog = log; + + return 0; +} + +static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) +{ + git_parse_ctx parser = GIT_PARSE_CTX_INIT; + + if ((git_parse_ctx_init(&parser, buf, buf_size)) < 0) + return -1; + + for (; parser.remain_len; git_parse_advance_line(&parser)) { + git_reflog_entry *entry; + const char *sig; + char c; + + entry = git__calloc(1, sizeof(*entry)); + GIT_ERROR_CHECK_ALLOC(entry); + entry->committer = git__calloc(1, sizeof(*entry->committer)); + GIT_ERROR_CHECK_ALLOC(entry->committer); + + if (git_parse_advance_oid(&entry->oid_old, &parser) < 0 || + git_parse_advance_expected(&parser, " ", 1) < 0 || + git_parse_advance_oid(&entry->oid_cur, &parser) < 0) + goto next; + + sig = parser.line; + while (git_parse_peek(&c, &parser, 0) == 0 && c != '\t' && c != '\n') + git_parse_advance_chars(&parser, 1); + + if (git_signature__parse(entry->committer, &sig, parser.line, NULL, 0) < 0) + goto next; + + if (c == '\t') { + size_t len; + git_parse_advance_chars(&parser, 1); + + len = parser.line_len; + if (parser.line[len - 1] == '\n') + len--; + + entry->msg = git__strndup(parser.line, len); + GIT_ERROR_CHECK_ALLOC(entry->msg); + } + + if ((git_vector_insert(&log->entries, entry)) < 0) { + git_reflog_entry__free(entry); + return -1; + } + + continue; + +next: + git_reflog_entry__free(entry); + } + + return 0; +} + +static int create_new_reflog_file(const char *filepath) +{ + int fd, error; + + if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0) + return error; + + if ((fd = p_open(filepath, + O_WRONLY | O_CREAT, + GIT_REFLOG_FILE_MODE)) < 0) + return -1; + + return p_close(fd); +} + +static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name) +{ + refdb_fs_backend *backend; + git_repository *repo; + git_str path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(_backend && name); + + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + repo = backend->repo; + + if ((error = reflog_path(&path, repo, name)) < 0) + return error; + + error = create_new_reflog_file(git_str_cstr(&path)); + git_str_dispose(&path); + + return error; +} + +static int has_reflog(git_repository *repo, const char *name) +{ + int ret = 0; + git_str path = GIT_STR_INIT; + + if (reflog_path(&path, repo, name) < 0) + goto cleanup; + + ret = git_fs_path_isfile(git_str_cstr(&path)); + +cleanup: + git_str_dispose(&path); + return ret; +} + +static int refdb_reflog_fs__has_log(git_refdb_backend *_backend, const char *name) +{ + refdb_fs_backend *backend; + + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(name); + + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + + return has_reflog(backend->repo, name); +} + +static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend, const char *name) +{ + int error = -1; + git_str log_path = GIT_STR_INIT; + git_str log_file = GIT_STR_INIT; + git_reflog *log = NULL; + git_repository *repo; + refdb_fs_backend *backend; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(name); + + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + repo = backend->repo; + + if (reflog_alloc(&log, name) < 0) + return -1; + + if (reflog_path(&log_path, repo, name) < 0) + goto cleanup; + + error = git_futils_readbuffer(&log_file, git_str_cstr(&log_path)); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if ((error == GIT_ENOTFOUND) && + ((error = create_new_reflog_file(git_str_cstr(&log_path))) < 0)) + goto cleanup; + + if ((error = reflog_parse(log, + git_str_cstr(&log_file), git_str_len(&log_file))) < 0) + goto cleanup; + + *out = log; + goto success; + +cleanup: + git_reflog_free(log); + +success: + git_str_dispose(&log_file); + git_str_dispose(&log_path); + + return error; +} + +static int serialize_reflog_entry( + git_str *buf, + const git_oid *oid_old, + const git_oid *oid_new, + const git_signature *committer, + const char *msg) +{ + char raw_old[GIT_OID_HEXSZ+1]; + char raw_new[GIT_OID_HEXSZ+1]; + + git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old); + git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new); + + git_str_clear(buf); + + git_str_puts(buf, raw_old); + git_str_putc(buf, ' '); + git_str_puts(buf, raw_new); + + git_signature__writebuf(buf, " ", committer); + + /* drop trailing LF */ + git_str_rtrim(buf); + + if (msg) { + size_t i; + + git_str_putc(buf, '\t'); + git_str_puts(buf, msg); + + for (i = 0; i < buf->size - 2; i++) + if (buf->ptr[i] == '\n') + buf->ptr[i] = ' '; + git_str_rtrim(buf); + } + + git_str_putc(buf, '\n'); + + return git_str_oom(buf); +} + +static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char *refname) +{ + git_repository *repo; + git_str log_path = GIT_STR_INIT; + int error; + + repo = backend->repo; + + if (!git_path_is_valid(backend->repo, refname, 0, GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS)) { + git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", refname); + return GIT_EINVALIDSPEC; + } + + if (reflog_path(&log_path, repo, refname) < 0) + return -1; + + if (!git_fs_path_isfile(git_str_cstr(&log_path))) { + git_error_set(GIT_ERROR_INVALID, + "log file for reference '%s' doesn't exist", refname); + error = -1; + goto cleanup; + } + + error = git_filebuf_open(file, git_str_cstr(&log_path), 0, GIT_REFLOG_FILE_MODE); + +cleanup: + git_str_dispose(&log_path); + + return error; +} + +static int refdb_reflog_fs__write(git_refdb_backend *_backend, git_reflog *reflog) +{ + int error = -1; + unsigned int i; + git_reflog_entry *entry; + refdb_fs_backend *backend; + git_str log = GIT_STR_INIT; + git_filebuf fbuf = GIT_FILEBUF_INIT; + + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(reflog); + + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + + if ((error = lock_reflog(&fbuf, backend, reflog->ref_name)) < 0) + return -1; + + git_vector_foreach(&reflog->entries, i, entry) { + if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0) + goto cleanup; + + if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) + goto cleanup; + } + + error = git_filebuf_commit(&fbuf); + goto success; + +cleanup: + git_filebuf_cleanup(&fbuf); + +success: + git_str_dispose(&log); + + return error; +} + +/* Append to the reflog, must be called under reference lock */ +static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *who, const char *message) +{ + int error, is_symbolic, open_flags; + git_oid old_id = {{0}}, new_id = {{0}}; + git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; + git_repository *repo = backend->repo; + + is_symbolic = ref->type == GIT_REFERENCE_SYMBOLIC; + + /* "normal" symbolic updates do not write */ + if (is_symbolic && + strcmp(ref->name, GIT_HEAD_FILE) && + !(old && new)) + return 0; + + /* From here on is_symbolic also means that it's HEAD */ + + if (old) { + git_oid_cpy(&old_id, old); + } else { + error = git_reference_name_to_id(&old_id, repo, ref->name); + if (error < 0 && error != GIT_ENOTFOUND) + return error; + } + + if (new) { + git_oid_cpy(&new_id, new); + } else { + if (!is_symbolic) { + git_oid_cpy(&new_id, git_reference_target(ref)); + } else { + error = git_reference_name_to_id(&new_id, repo, git_reference_symbolic_target(ref)); + if (error < 0 && error != GIT_ENOTFOUND) + return error; + /* detaching HEAD does not create an entry */ + if (error == GIT_ENOTFOUND) + return 0; + + git_error_clear(); + } + } + + if ((error = serialize_reflog_entry(&buf, &old_id, &new_id, who, message)) < 0) + goto cleanup; + + if ((error = reflog_path(&path, repo, ref->name)) < 0) + goto cleanup; + + if (((error = git_futils_mkpath2file(git_str_cstr(&path), 0777)) < 0) && + (error != GIT_EEXISTS)) { + goto cleanup; + } + + /* If the new branch matches part of the namespace of a previously deleted branch, + * there maybe an obsolete/unused directory (or directory hierarchy) in the way. + */ + if (git_fs_path_isdir(git_str_cstr(&path))) { + if ((error = git_futils_rmdir_r(git_str_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY)) < 0) { + if (error == GIT_ENOTFOUND) + error = 0; + } else if (git_fs_path_isdir(git_str_cstr(&path))) { + git_error_set(GIT_ERROR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder", + ref->name); + error = GIT_EDIRECTORY; + } + + if (error != 0) + goto cleanup; + } + + open_flags = O_WRONLY | O_CREAT | O_APPEND; + + if (backend->fsync) + open_flags |= O_FSYNC; + + error = git_futils_writebuffer(&buf, git_str_cstr(&path), open_flags, GIT_REFLOG_FILE_MODE); + +cleanup: + git_str_dispose(&buf); + git_str_dispose(&path); + + return error; +} + +static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name) +{ + int error = 0, fd; + git_str old_path = GIT_STR_INIT; + git_str new_path = GIT_STR_INIT; + git_str temp_path = GIT_STR_INIT; + git_str normalized = GIT_STR_INIT; + git_repository *repo; + refdb_fs_backend *backend; + + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(old_name); + GIT_ASSERT_ARG(new_name); + + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + repo = backend->repo; + + if ((error = git_reference__normalize_name( + &normalized, new_name, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL)) < 0) + return error; + + if (git_str_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0) + return -1; + + if ((error = loose_path(&old_path, git_str_cstr(&temp_path), old_name)) < 0) + return error; + + if ((error = loose_path(&new_path, git_str_cstr(&temp_path), git_str_cstr(&normalized))) < 0) + return error; + + if (!git_fs_path_exists(git_str_cstr(&old_path))) { + error = GIT_ENOTFOUND; + goto cleanup; + } + + /* + * Move the reflog to a temporary place. This two-phase renaming is required + * in order to cope with funny renaming use cases when one tries to move a reference + * to a partially colliding namespace: + * - a/b -> a/b/c + * - a/b/c/d -> a/b/c + */ + if ((error = loose_path(&temp_path, git_str_cstr(&temp_path), "temp_reflog")) < 0) + return error; + + if ((fd = git_futils_mktmp(&temp_path, git_str_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) { + error = -1; + goto cleanup; + } + + p_close(fd); + + if (p_rename(git_str_cstr(&old_path), git_str_cstr(&temp_path)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name); + error = -1; + goto cleanup; + } + + if (git_fs_path_isdir(git_str_cstr(&new_path)) && + (git_futils_rmdir_r(git_str_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) { + error = -1; + goto cleanup; + } + + if (git_futils_mkpath2file(git_str_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) { + error = -1; + goto cleanup; + } + + if (p_rename(git_str_cstr(&temp_path), git_str_cstr(&new_path)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name); + error = -1; + } + +cleanup: + git_str_dispose(&temp_path); + git_str_dispose(&old_path); + git_str_dispose(&new_path); + git_str_dispose(&normalized); + + return error; +} + +static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name) +{ + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + git_str path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(_backend); + GIT_ASSERT_ARG(name); + + if ((error = reflog_path(&path, backend->repo, name)) < 0) + goto out; + + if (!git_fs_path_exists(path.ptr)) + goto out; + + if ((error = p_unlink(path.ptr)) < 0) + goto out; + + error = refdb_fs_backend__prune_refs(backend, name, GIT_REFLOG_DIR); + +out: + git_str_dispose(&path); + + return error; +} + +int git_refdb_backend_fs( + git_refdb_backend **backend_out, + git_repository *repository) +{ + int t = 0; + git_str gitpath = GIT_STR_INIT; + refdb_fs_backend *backend; + + backend = git__calloc(1, sizeof(refdb_fs_backend)); + GIT_ERROR_CHECK_ALLOC(backend); + if (git_mutex_init(&backend->prlock) < 0) { + git__free(backend); + return -1; + } + + + if (git_refdb_init_backend(&backend->parent, GIT_REFDB_BACKEND_VERSION) < 0) + goto fail; + + backend->repo = repository; + + if (repository->gitdir) { + backend->gitpath = setup_namespace(repository, repository->gitdir); + + if (backend->gitpath == NULL) + goto fail; + } + + if (repository->commondir) { + backend->commonpath = setup_namespace(repository, repository->commondir); + + if (backend->commonpath == NULL) + goto fail; + } + + if (git_str_joinpath(&gitpath, backend->commonpath, GIT_PACKEDREFS_FILE) < 0 || + git_sortedcache_new( + &backend->refcache, offsetof(struct packref, name), + NULL, NULL, packref_cmp, git_str_cstr(&gitpath)) < 0) + goto fail; + + git_str_dispose(&gitpath); + + if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_IGNORECASE) && t) { + backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE; + backend->direach_flags |= GIT_FS_PATH_DIR_IGNORE_CASE; + } + if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_PRECOMPOSE) && t) { + backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; + backend->direach_flags |= GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE; + } + if ((!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_FSYNCOBJECTFILES) && t) || + git_repository__fsync_gitdir) + backend->fsync = 1; + backend->iterator_flags |= GIT_ITERATOR_DESCEND_SYMLINKS; + + backend->parent.exists = &refdb_fs_backend__exists; + backend->parent.lookup = &refdb_fs_backend__lookup; + backend->parent.iterator = &refdb_fs_backend__iterator; + backend->parent.write = &refdb_fs_backend__write; + backend->parent.del = &refdb_fs_backend__delete; + backend->parent.rename = &refdb_fs_backend__rename; + backend->parent.compress = &refdb_fs_backend__compress; + backend->parent.lock = &refdb_fs_backend__lock; + backend->parent.unlock = &refdb_fs_backend__unlock; + backend->parent.has_log = &refdb_reflog_fs__has_log; + backend->parent.ensure_log = &refdb_reflog_fs__ensure_log; + backend->parent.free = &refdb_fs_backend__free; + backend->parent.reflog_read = &refdb_reflog_fs__read; + backend->parent.reflog_write = &refdb_reflog_fs__write; + backend->parent.reflog_rename = &refdb_reflog_fs__rename; + backend->parent.reflog_delete = &refdb_reflog_fs__delete; + + *backend_out = (git_refdb_backend *)backend; + return 0; + +fail: + git_mutex_free(&backend->prlock); + git_str_dispose(&gitpath); + git__free(backend->gitpath); + git__free(backend->commonpath); + git__free(backend); + return -1; +} diff --git a/src/libgit2/reflog.c b/src/libgit2/reflog.c new file mode 100644 index 000000000..1e9c0d4f1 --- /dev/null +++ b/src/libgit2/reflog.c @@ -0,0 +1,232 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "reflog.h" + +#include "repository.h" +#include "filebuf.h" +#include "signature.h" +#include "refdb.h" + +#include "git2/sys/refdb_backend.h" +#include "git2/sys/reflog.h" + +void git_reflog_entry__free(git_reflog_entry *entry) +{ + git_signature_free(entry->committer); + + git__free(entry->msg); + git__free(entry); +} + +void git_reflog_free(git_reflog *reflog) +{ + size_t i; + git_reflog_entry *entry; + + if (reflog == NULL) + return; + + if (reflog->db) + GIT_REFCOUNT_DEC(reflog->db, git_refdb__free); + + for (i=0; i < reflog->entries.length; i++) { + entry = git_vector_get(&reflog->entries, i); + + git_reflog_entry__free(entry); + } + + git_vector_free(&reflog->entries); + git__free(reflog->ref_name); + git__free(reflog); +} + +int git_reflog_read(git_reflog **reflog, git_repository *repo, const char *name) +{ + git_refdb *refdb; + int error; + + GIT_ASSERT_ARG(reflog); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return error; + + return git_refdb_reflog_read(reflog, refdb, name); +} + +int git_reflog_write(git_reflog *reflog) +{ + git_refdb *db; + + GIT_ASSERT_ARG(reflog); + GIT_ASSERT_ARG(reflog->db); + + db = reflog->db; + return db->backend->reflog_write(db->backend, reflog); +} + +int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg) +{ + const git_reflog_entry *previous; + git_reflog_entry *entry; + + GIT_ASSERT_ARG(reflog); + GIT_ASSERT_ARG(new_oid); + GIT_ASSERT_ARG(committer); + + entry = git__calloc(1, sizeof(git_reflog_entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + if ((git_signature_dup(&entry->committer, committer)) < 0) + goto cleanup; + + if (msg != NULL) { + size_t i, msglen = strlen(msg); + + if ((entry->msg = git__strndup(msg, msglen)) == NULL) + goto cleanup; + + /* + * Replace all newlines with spaces, except for + * the final trailing newline. + */ + for (i = 0; i < msglen; i++) + if (entry->msg[i] == '\n') + entry->msg[i] = ' '; + } + + previous = git_reflog_entry_byindex(reflog, 0); + + if (previous == NULL) + git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO); + else + git_oid_cpy(&entry->oid_old, &previous->oid_cur); + + git_oid_cpy(&entry->oid_cur, new_oid); + + if (git_vector_insert(&reflog->entries, entry) < 0) + goto cleanup; + + return 0; + +cleanup: + git_reflog_entry__free(entry); + return -1; +} + +int git_reflog_rename(git_repository *repo, const char *old_name, const char *new_name) +{ + git_refdb *refdb; + int error; + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return -1; + + return refdb->backend->reflog_rename(refdb->backend, old_name, new_name); +} + +int git_reflog_delete(git_repository *repo, const char *name) +{ + git_refdb *refdb; + int error; + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return -1; + + return refdb->backend->reflog_delete(refdb->backend, name); +} + +size_t git_reflog_entrycount(git_reflog *reflog) +{ + GIT_ASSERT_ARG_WITH_RETVAL(reflog, 0); + return reflog->entries.length; +} + +const git_reflog_entry *git_reflog_entry_byindex(const git_reflog *reflog, size_t idx) +{ + GIT_ASSERT_ARG_WITH_RETVAL(reflog, NULL); + + if (idx >= reflog->entries.length) + return NULL; + + return git_vector_get( + &reflog->entries, reflog_inverse_index(idx, reflog->entries.length)); +} + +const git_oid *git_reflog_entry_id_old(const git_reflog_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); + return &entry->oid_old; +} + +const git_oid *git_reflog_entry_id_new(const git_reflog_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); + return &entry->oid_cur; +} + +const git_signature *git_reflog_entry_committer(const git_reflog_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); + return entry->committer; +} + +const char *git_reflog_entry_message(const git_reflog_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); + return entry->msg; +} + +int git_reflog_drop(git_reflog *reflog, size_t idx, int rewrite_previous_entry) +{ + size_t entrycount; + git_reflog_entry *entry, *previous; + + entrycount = git_reflog_entrycount(reflog); + + entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); + + if (entry == NULL) { + git_error_set(GIT_ERROR_REFERENCE, "no reflog entry at index %"PRIuZ, idx); + return GIT_ENOTFOUND; + } + + git_reflog_entry__free(entry); + + if (git_vector_remove( + &reflog->entries, reflog_inverse_index(idx, entrycount)) < 0) + return -1; + + if (!rewrite_previous_entry) + return 0; + + /* No need to rewrite anything when removing the most recent entry */ + if (idx == 0) + return 0; + + /* Have the latest entry just been dropped? */ + if (entrycount == 1) + return 0; + + entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1); + + /* If the oldest entry has just been removed... */ + if (idx == entrycount - 1) { + /* ...clear the oid_old member of the "new" oldest entry */ + if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0) + return -1; + + return 0; + } + + previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); + git_oid_cpy(&entry->oid_old, &previous->oid_cur); + + return 0; +} diff --git a/src/libgit2/reflog.h b/src/libgit2/reflog.h new file mode 100644 index 000000000..8c3895952 --- /dev/null +++ b/src/libgit2/reflog.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_reflog_h__ +#define INCLUDE_reflog_h__ + +#include "common.h" + +#include "git2/reflog.h" +#include "vector.h" + +#define GIT_REFLOG_DIR "logs/" +#define GIT_REFLOG_DIR_MODE 0777 +#define GIT_REFLOG_FILE_MODE 0666 + +#define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17) + +struct git_reflog_entry { + git_oid oid_old; + git_oid oid_cur; + + git_signature *committer; + + char *msg; +}; + +struct git_reflog { + git_refdb *db; + char *ref_name; + git_vector entries; +}; + +GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total) +{ + return (total - 1) - idx; +} + +#endif diff --git a/src/libgit2/refs.c b/src/libgit2/refs.c new file mode 100644 index 000000000..5c875b95b --- /dev/null +++ b/src/libgit2/refs.c @@ -0,0 +1,1395 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "refs.h" + +#include "hash.h" +#include "repository.h" +#include "futils.h" +#include "filebuf.h" +#include "pack.h" +#include "reflog.h" +#include "refdb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool git_reference__enable_symbolic_ref_target_validation = true; + +enum { + GIT_PACKREF_HAS_PEEL = 1, + GIT_PACKREF_WAS_LOOSE = 2 +}; + +static git_reference *alloc_ref(const char *name) +{ + git_reference *ref = NULL; + size_t namelen = strlen(name), reflen; + + if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) && + !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) && + (ref = git__calloc(1, reflen)) != NULL) + memcpy(ref->name, name, namelen + 1); + + return ref; +} + +git_reference *git_reference__alloc_symbolic( + const char *name, const char *target) +{ + git_reference *ref; + + GIT_ASSERT_ARG_WITH_RETVAL(name, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(target, NULL); + + ref = alloc_ref(name); + if (!ref) + return NULL; + + ref->type = GIT_REFERENCE_SYMBOLIC; + + if ((ref->target.symbolic = git__strdup(target)) == NULL) { + git__free(ref); + return NULL; + } + + return ref; +} + +git_reference *git_reference__alloc( + const char *name, + const git_oid *oid, + const git_oid *peel) +{ + git_reference *ref; + + GIT_ASSERT_ARG_WITH_RETVAL(name, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(oid, NULL); + + ref = alloc_ref(name); + if (!ref) + return NULL; + + ref->type = GIT_REFERENCE_DIRECT; + git_oid_cpy(&ref->target.oid, oid); + + if (peel != NULL) + git_oid_cpy(&ref->peel, peel); + + return ref; +} + +git_reference *git_reference__realloc( + git_reference **ptr_to_ref, const char *name) +{ + size_t namelen, reflen; + git_reference *rewrite = NULL; + + GIT_ASSERT_ARG_WITH_RETVAL(ptr_to_ref, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(name, NULL); + + namelen = strlen(name); + + if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) && + !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) && + (rewrite = git__realloc(*ptr_to_ref, reflen)) != NULL) + memcpy(rewrite->name, name, namelen + 1); + + *ptr_to_ref = NULL; + + return rewrite; +} + +int git_reference_dup(git_reference **dest, git_reference *source) +{ + if (source->type == GIT_REFERENCE_SYMBOLIC) + *dest = git_reference__alloc_symbolic(source->name, source->target.symbolic); + else + *dest = git_reference__alloc(source->name, &source->target.oid, &source->peel); + + GIT_ERROR_CHECK_ALLOC(*dest); + + (*dest)->db = source->db; + GIT_REFCOUNT_INC((*dest)->db); + + return 0; +} + +void git_reference_free(git_reference *reference) +{ + if (reference == NULL) + return; + + if (reference->type == GIT_REFERENCE_SYMBOLIC) + git__free(reference->target.symbolic); + + if (reference->db) + GIT_REFCOUNT_DEC(reference->db, git_refdb__free); + + git__free(reference); +} + +int git_reference_delete(git_reference *ref) +{ + const git_oid *old_id = NULL; + const char *old_target = NULL; + + if (!strcmp(ref->name, "HEAD")) { + git_error_set(GIT_ERROR_REFERENCE, "cannot delete HEAD"); + return GIT_ERROR; + } + + if (ref->type == GIT_REFERENCE_DIRECT) + old_id = &ref->target.oid; + else + old_target = ref->target.symbolic; + + return git_refdb_delete(ref->db, ref->name, old_id, old_target); +} + +int git_reference_remove(git_repository *repo, const char *name) +{ + git_refdb *db; + int error; + + if ((error = git_repository_refdb__weakptr(&db, repo)) < 0) + return error; + + return git_refdb_delete(db, name, NULL, NULL); +} + +int git_reference_lookup(git_reference **ref_out, + git_repository *repo, const char *name) +{ + return git_reference_lookup_resolved(ref_out, repo, name, 0); +} + +int git_reference_name_to_id( + git_oid *out, git_repository *repo, const char *name) +{ + int error; + git_reference *ref; + + if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0) + return error; + + git_oid_cpy(out, git_reference_target(ref)); + git_reference_free(ref); + return 0; +} + +static int reference_normalize_for_repo( + git_refname_t out, + git_repository *repo, + const char *name, + bool validate) +{ + int precompose; + unsigned int flags = GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL; + + if (!git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) && + precompose) + flags |= GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE; + + if (!validate) + flags |= GIT_REFERENCE_FORMAT__VALIDATION_DISABLE; + + return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags); +} + +int git_reference_lookup_resolved( + git_reference **ref_out, + git_repository *repo, + const char *name, + int max_nesting) +{ + git_refname_t normalized; + git_refdb *refdb; + int error = 0; + + GIT_ASSERT_ARG(ref_out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = reference_normalize_for_repo(normalized, repo, name, true)) < 0 || + (error = git_repository_refdb__weakptr(&refdb, repo)) < 0 || + (error = git_refdb_resolve(ref_out, refdb, normalized, max_nesting)) < 0) + return error; + + /* + * The resolved reference may be a symbolic reference in case its + * target doesn't exist. If the user asked us to resolve (e.g. + * `max_nesting != 0`), then we need to return an error in case we got + * a symbolic reference back. + */ + if (max_nesting && git_reference_type(*ref_out) == GIT_REFERENCE_SYMBOLIC) { + git_reference_free(*ref_out); + *ref_out = NULL; + return GIT_ENOTFOUND; + } + + return 0; +} + +int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname) +{ + int error = 0, i, valid; + bool fallbackmode = true, foundvalid = false; + git_reference *ref; + git_str refnamebuf = GIT_STR_INIT, name = GIT_STR_INIT; + + static const char *formatters[] = { + "%s", + GIT_REFS_DIR "%s", + GIT_REFS_TAGS_DIR "%s", + GIT_REFS_HEADS_DIR "%s", + GIT_REFS_REMOTES_DIR "%s", + GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE, + NULL + }; + + if (*refname) + git_str_puts(&name, refname); + else { + git_str_puts(&name, GIT_HEAD_FILE); + fallbackmode = false; + } + + for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) { + + git_str_clear(&refnamebuf); + + if ((error = git_str_printf(&refnamebuf, formatters[i], git_str_cstr(&name))) < 0 || + (error = git_reference_name_is_valid(&valid, git_str_cstr(&refnamebuf))) < 0) + goto cleanup; + + if (!valid) { + error = GIT_EINVALIDSPEC; + continue; + } + foundvalid = true; + + error = git_reference_lookup_resolved(&ref, repo, git_str_cstr(&refnamebuf), -1); + + if (!error) { + *out = ref; + error = 0; + goto cleanup; + } + + if (error != GIT_ENOTFOUND) + goto cleanup; + } + +cleanup: + if (error && !foundvalid) { + /* never found a valid reference name */ + git_error_set(GIT_ERROR_REFERENCE, + "could not use '%s' as valid reference name", git_str_cstr(&name)); + } + + if (error == GIT_ENOTFOUND) + git_error_set(GIT_ERROR_REFERENCE, "no reference found for shorthand '%s'", refname); + + git_str_dispose(&name); + git_str_dispose(&refnamebuf); + return error; +} + +/** + * Getters + */ +git_reference_t git_reference_type(const git_reference *ref) +{ + GIT_ASSERT_ARG(ref); + return ref->type; +} + +const char *git_reference_name(const git_reference *ref) +{ + GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); + return ref->name; +} + +git_repository *git_reference_owner(const git_reference *ref) +{ + GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); + return ref->db->repo; +} + +const git_oid *git_reference_target(const git_reference *ref) +{ + GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); + + if (ref->type != GIT_REFERENCE_DIRECT) + return NULL; + + return &ref->target.oid; +} + +const git_oid *git_reference_target_peel(const git_reference *ref) +{ + GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); + + if (ref->type != GIT_REFERENCE_DIRECT || git_oid_is_zero(&ref->peel)) + return NULL; + + return &ref->peel; +} + +const char *git_reference_symbolic_target(const git_reference *ref) +{ + GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); + + if (ref->type != GIT_REFERENCE_SYMBOLIC) + return NULL; + + return ref->target.symbolic; +} + +static int reference__create( + git_reference **ref_out, + git_repository *repo, + const char *name, + const git_oid *oid, + const char *symbolic, + int force, + const git_signature *signature, + const char *log_message, + const git_oid *old_id, + const char *old_target) +{ + git_refname_t normalized; + git_refdb *refdb; + git_reference *ref = NULL; + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(symbolic || signature); + + if (ref_out) + *ref_out = NULL; + + error = reference_normalize_for_repo(normalized, repo, name, true); + if (error < 0) + return error; + + error = git_repository_refdb__weakptr(&refdb, repo); + if (error < 0) + return error; + + if (oid != NULL) { + GIT_ASSERT(symbolic == NULL); + + if (!git_object__is_valid(repo, oid, GIT_OBJECT_ANY)) { + git_error_set(GIT_ERROR_REFERENCE, + "target OID for the reference doesn't exist on the repository"); + return -1; + } + + ref = git_reference__alloc(normalized, oid, NULL); + } else { + git_refname_t normalized_target; + + error = reference_normalize_for_repo(normalized_target, repo, + symbolic, git_reference__enable_symbolic_ref_target_validation); + + if (error < 0) + return error; + + ref = git_reference__alloc_symbolic(normalized, normalized_target); + } + + GIT_ERROR_CHECK_ALLOC(ref); + + if ((error = git_refdb_write(refdb, ref, force, signature, log_message, old_id, old_target)) < 0) { + git_reference_free(ref); + return error; + } + + if (ref_out == NULL) + git_reference_free(ref); + else + *ref_out = ref; + + return 0; +} + +static int refs_configured_ident(git_signature **out, const git_repository *repo) +{ + if (repo->ident_name && repo->ident_email) + return git_signature_now(out, repo->ident_name, repo->ident_email); + + /* if not configured let us fall-through to the next method */ + return -1; +} + +int git_reference__log_signature(git_signature **out, git_repository *repo) +{ + int error; + git_signature *who; + + if(((error = refs_configured_ident(&who, repo)) < 0) && + ((error = git_signature_default(&who, repo)) < 0) && + ((error = git_signature_now(&who, "unknown", "unknown")) < 0)) + return error; + + *out = who; + return 0; +} + +int git_reference_create_matching( + git_reference **ref_out, + git_repository *repo, + const char *name, + const git_oid *id, + int force, + const git_oid *old_id, + const char *log_message) + +{ + int error; + git_signature *who = NULL; + + GIT_ASSERT_ARG(id); + + if ((error = git_reference__log_signature(&who, repo)) < 0) + return error; + + error = reference__create( + ref_out, repo, name, id, NULL, force, who, log_message, old_id, NULL); + + git_signature_free(who); + return error; +} + +int git_reference_create( + git_reference **ref_out, + git_repository *repo, + const char *name, + const git_oid *id, + int force, + const char *log_message) +{ + return git_reference_create_matching(ref_out, repo, name, id, force, NULL, log_message); +} + +int git_reference_symbolic_create_matching( + git_reference **ref_out, + git_repository *repo, + const char *name, + const char *target, + int force, + const char *old_target, + const char *log_message) +{ + int error; + git_signature *who = NULL; + + GIT_ASSERT_ARG(target); + + if ((error = git_reference__log_signature(&who, repo)) < 0) + return error; + + error = reference__create( + ref_out, repo, name, NULL, target, force, who, log_message, NULL, old_target); + + git_signature_free(who); + return error; +} + +int git_reference_symbolic_create( + git_reference **ref_out, + git_repository *repo, + const char *name, + const char *target, + int force, + const char *log_message) +{ + return git_reference_symbolic_create_matching(ref_out, repo, name, target, force, NULL, log_message); +} + +static int ensure_is_an_updatable_direct_reference(git_reference *ref) +{ + if (ref->type == GIT_REFERENCE_DIRECT) + return 0; + + git_error_set(GIT_ERROR_REFERENCE, "cannot set OID on symbolic reference"); + return -1; +} + +int git_reference_set_target( + git_reference **out, + git_reference *ref, + const git_oid *id, + const char *log_message) +{ + int error; + git_repository *repo; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ref); + GIT_ASSERT_ARG(id); + + repo = ref->db->repo; + + if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0) + return error; + + return git_reference_create_matching(out, repo, ref->name, id, 1, &ref->target.oid, log_message); +} + +static int ensure_is_an_updatable_symbolic_reference(git_reference *ref) +{ + if (ref->type == GIT_REFERENCE_SYMBOLIC) + return 0; + + git_error_set(GIT_ERROR_REFERENCE, "cannot set symbolic target on a direct reference"); + return -1; +} + +int git_reference_symbolic_set_target( + git_reference **out, + git_reference *ref, + const char *target, + const char *log_message) +{ + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ref); + GIT_ASSERT_ARG(target); + + if ((error = ensure_is_an_updatable_symbolic_reference(ref)) < 0) + return error; + + return git_reference_symbolic_create_matching( + out, ref->db->repo, ref->name, target, 1, ref->target.symbolic, log_message); +} + +typedef struct { + const char *old_name; + git_refname_t new_name; +} refs_update_head_payload; + +static int refs_update_head(git_repository *worktree, void *_payload) +{ + refs_update_head_payload *payload = (refs_update_head_payload *)_payload; + git_reference *head = NULL, *updated = NULL; + int error; + + if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0) + goto out; + + if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC || + git__strcmp(git_reference_symbolic_target(head), payload->old_name) != 0) + goto out; + + /* Update HEAD if it was pointing to the reference being renamed */ + if ((error = git_reference_symbolic_set_target(&updated, head, payload->new_name, NULL)) < 0) { + git_error_set(GIT_ERROR_REFERENCE, "failed to update HEAD after renaming reference"); + goto out; + } + +out: + git_reference_free(updated); + git_reference_free(head); + return error; +} + +int git_reference_rename( + git_reference **out, + git_reference *ref, + const char *new_name, + int force, + const char *log_message) +{ + refs_update_head_payload payload; + git_signature *signature = NULL; + git_repository *repo; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(ref); + + repo = git_reference_owner(ref); + + if ((error = git_reference__log_signature(&signature, repo)) < 0 || + (error = reference_normalize_for_repo(payload.new_name, repo, new_name, true)) < 0 || + (error = git_refdb_rename(out, ref->db, ref->name, payload.new_name, force, signature, log_message)) < 0) + goto out; + + payload.old_name = ref->name; + + /* We may have to update any HEAD that was pointing to the renamed reference. */ + if ((error = git_repository_foreach_worktree(repo, refs_update_head, &payload)) < 0) + goto out; + +out: + git_signature_free(signature); + return error; +} + +int git_reference_resolve(git_reference **ref_out, const git_reference *ref) +{ + switch (git_reference_type(ref)) { + case GIT_REFERENCE_DIRECT: + return git_reference_lookup(ref_out, ref->db->repo, ref->name); + + case GIT_REFERENCE_SYMBOLIC: + return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1); + + default: + git_error_set(GIT_ERROR_REFERENCE, "invalid reference"); + return -1; + } +} + +int git_reference_foreach( + git_repository *repo, + git_reference_foreach_cb callback, + void *payload) +{ + git_reference_iterator *iter; + git_reference *ref; + int error; + + if ((error = git_reference_iterator_new(&iter, repo)) < 0) + return error; + + while (!(error = git_reference_next(&ref, iter))) { + if ((error = callback(ref, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + if (error == GIT_ITEROVER) + error = 0; + + git_reference_iterator_free(iter); + return error; +} + +int git_reference_foreach_name( + git_repository *repo, + git_reference_foreach_name_cb callback, + void *payload) +{ + git_reference_iterator *iter; + const char *refname; + int error; + + if ((error = git_reference_iterator_new(&iter, repo)) < 0) + return error; + + while (!(error = git_reference_next_name(&refname, iter))) { + if ((error = callback(refname, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + if (error == GIT_ITEROVER) + error = 0; + + git_reference_iterator_free(iter); + return error; +} + +int git_reference_foreach_glob( + git_repository *repo, + const char *glob, + git_reference_foreach_name_cb callback, + void *payload) +{ + git_reference_iterator *iter; + const char *refname; + int error; + + if ((error = git_reference_iterator_glob_new(&iter, repo, glob)) < 0) + return error; + + while (!(error = git_reference_next_name(&refname, iter))) { + if ((error = callback(refname, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + if (error == GIT_ITEROVER) + error = 0; + + git_reference_iterator_free(iter); + return error; +} + +int git_reference_iterator_new(git_reference_iterator **out, git_repository *repo) +{ + git_refdb *refdb; + + if (git_repository_refdb__weakptr(&refdb, repo) < 0) + return -1; + + return git_refdb_iterator(out, refdb, NULL); +} + +int git_reference_iterator_glob_new( + git_reference_iterator **out, git_repository *repo, const char *glob) +{ + git_refdb *refdb; + + if (git_repository_refdb__weakptr(&refdb, repo) < 0) + return -1; + + return git_refdb_iterator(out, refdb, glob); +} + +int git_reference_next(git_reference **out, git_reference_iterator *iter) +{ + return git_refdb_iterator_next(out, iter); +} + +int git_reference_next_name(const char **out, git_reference_iterator *iter) +{ + return git_refdb_iterator_next_name(out, iter); +} + +void git_reference_iterator_free(git_reference_iterator *iter) +{ + if (iter == NULL) + return; + + git_refdb_iterator_free(iter); +} + +static int cb__reflist_add(const char *ref, void *data) +{ + char *name = git__strdup(ref); + GIT_ERROR_CHECK_ALLOC(name); + return git_vector_insert((git_vector *)data, name); +} + +int git_reference_list( + git_strarray *array, + git_repository *repo) +{ + git_vector ref_list; + + GIT_ASSERT_ARG(array); + GIT_ASSERT_ARG(repo); + + array->strings = NULL; + array->count = 0; + + if (git_vector_init(&ref_list, 8, NULL) < 0) + return -1; + + if (git_reference_foreach_name( + repo, &cb__reflist_add, (void *)&ref_list) < 0) { + git_vector_free(&ref_list); + return -1; + } + + array->strings = (char **)git_vector_detach(&array->count, NULL, &ref_list); + + return 0; +} + +static int is_valid_ref_char(char ch) +{ + if ((unsigned) ch <= ' ') + return 0; + + switch (ch) { + case '~': + case '^': + case ':': + case '\\': + case '?': + case '[': + return 0; + default: + return 1; + } +} + +static int ensure_segment_validity(const char *name, char may_contain_glob) +{ + const char *current = name; + char prev = '\0'; + const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION); + int segment_len; + + if (*current == '.') + return -1; /* Refname starts with "." */ + + for (current = name; ; current++) { + if (*current == '\0' || *current == '/') + break; + + if (!is_valid_ref_char(*current)) + return -1; /* Illegal character in refname */ + + if (prev == '.' && *current == '.') + return -1; /* Refname contains ".." */ + + if (prev == '@' && *current == '{') + return -1; /* Refname contains "@{" */ + + if (*current == '*') { + if (!may_contain_glob) + return -1; + may_contain_glob = 0; + } + + prev = *current; + } + + segment_len = (int)(current - name); + + /* A refname component can not end with ".lock" */ + if (segment_len >= lock_len && + !memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len)) + return -1; + + return segment_len; +} + +static bool is_all_caps_and_underscore(const char *name, size_t len) +{ + size_t i; + char c; + + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(len > 0); + + for (i = 0; i < len; i++) + { + c = name[i]; + if ((c < 'A' || c > 'Z') && c != '_') + return false; + } + + if (*name == '_' || name[len - 1] == '_') + return false; + + return true; +} + +/* Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 */ +int git_reference__normalize_name( + git_str *buf, + const char *name, + unsigned int flags) +{ + const char *current; + int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC; + unsigned int process_flags; + bool normalize = (buf != NULL); + bool validate = (flags & GIT_REFERENCE_FORMAT__VALIDATION_DISABLE) == 0; + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT; +#endif + + GIT_ASSERT_ARG(name); + + process_flags = flags; + current = (char *)name; + + if (validate && *current == '/') + goto cleanup; + + if (normalize) + git_str_clear(buf); + +#ifdef GIT_USE_ICONV + if ((flags & GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE) != 0) { + size_t namelen = strlen(current); + if ((error = git_fs_path_iconv_init_precompose(&ic)) < 0 || + (error = git_fs_path_iconv(&ic, ¤t, &namelen)) < 0) + goto cleanup; + error = GIT_EINVALIDSPEC; + } +#endif + + if (!validate) { + git_str_sets(buf, current); + + error = git_str_oom(buf) ? -1 : 0; + goto cleanup; + } + + while (true) { + char may_contain_glob = process_flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN; + + segment_len = ensure_segment_validity(current, may_contain_glob); + if (segment_len < 0) + goto cleanup; + + if (segment_len > 0) { + /* + * There may only be one glob in a pattern, thus we reset + * the pattern-flag in case the current segment has one. + */ + if (memchr(current, '*', segment_len)) + process_flags &= ~GIT_REFERENCE_FORMAT_REFSPEC_PATTERN; + + if (normalize) { + size_t cur_len = git_str_len(buf); + + git_str_joinpath(buf, git_str_cstr(buf), current); + git_str_truncate(buf, + cur_len + segment_len + (segments_count ? 1 : 0)); + + if (git_str_oom(buf)) { + error = -1; + goto cleanup; + } + } + + segments_count++; + } + + /* No empty segment is allowed when not normalizing */ + if (segment_len == 0 && !normalize) + goto cleanup; + + if (current[segment_len] == '\0') + break; + + current += segment_len + 1; + } + + /* A refname can not be empty */ + if (segment_len == 0 && segments_count == 0) + goto cleanup; + + /* A refname can not end with "." */ + if (current[segment_len - 1] == '.') + goto cleanup; + + /* A refname can not end with "/" */ + if (current[segment_len - 1] == '/') + goto cleanup; + + if ((segments_count == 1 ) && !(flags & GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL)) + goto cleanup; + + if ((segments_count == 1 ) && + !(flags & GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND) && + !(is_all_caps_and_underscore(name, (size_t)segment_len) || + ((flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name)))) + goto cleanup; + + if ((segments_count > 1) + && (is_all_caps_and_underscore(name, strchr(name, '/') - name))) + goto cleanup; + + error = 0; + +cleanup: + if (error == GIT_EINVALIDSPEC) + git_error_set( + GIT_ERROR_REFERENCE, + "the given reference name '%s' is not valid", name); + + if (error && normalize) + git_str_dispose(buf); + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_clear(&ic); +#endif + + return error; +} + +int git_reference_normalize_name( + char *buffer_out, + size_t buffer_size, + const char *name, + unsigned int flags) +{ + git_str buf = GIT_STR_INIT; + int error; + + if ((error = git_reference__normalize_name(&buf, name, flags)) < 0) + goto cleanup; + + if (git_str_len(&buf) > buffer_size - 1) { + git_error_set( + GIT_ERROR_REFERENCE, + "the provided buffer is too short to hold the normalization of '%s'", name); + error = GIT_EBUFS; + goto cleanup; + } + + if ((error = git_str_copy_cstr(buffer_out, buffer_size, &buf)) < 0) + goto cleanup; + + error = 0; + +cleanup: + git_str_dispose(&buf); + return error; +} + +#define GIT_REFERENCE_TYPEMASK (GIT_REFERENCE_DIRECT | GIT_REFERENCE_SYMBOLIC) + +int git_reference_cmp( + const git_reference *ref1, + const git_reference *ref2) +{ + git_reference_t type1, type2; + + GIT_ASSERT_ARG(ref1); + GIT_ASSERT_ARG(ref2); + + type1 = git_reference_type(ref1); + type2 = git_reference_type(ref2); + + /* let's put symbolic refs before OIDs */ + if (type1 != type2) + return (type1 == GIT_REFERENCE_SYMBOLIC) ? -1 : 1; + + if (type1 == GIT_REFERENCE_SYMBOLIC) + return strcmp(ref1->target.symbolic, ref2->target.symbolic); + + return git_oid__cmp(&ref1->target.oid, &ref2->target.oid); +} + +/* + * Starting with the reference given by `ref_name`, follows symbolic + * references until a direct reference is found and updated the OID + * on that direct reference to `oid`. + */ +int git_reference__update_terminal( + git_repository *repo, + const char *ref_name, + const git_oid *oid, + const git_signature *sig, + const char *log_message) +{ + git_reference *ref = NULL, *ref2 = NULL; + git_signature *who = NULL; + git_refdb *refdb = NULL; + const git_signature *to_use; + int error = 0; + + if (!sig && (error = git_reference__log_signature(&who, repo)) < 0) + goto out; + + to_use = sig ? sig : who; + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + goto out; + + if ((error = git_refdb_resolve(&ref, refdb, ref_name, -1)) < 0) { + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = reference__create(&ref2, repo, ref_name, oid, NULL, 0, to_use, + log_message, NULL, NULL); + } + goto out; + } + + /* In case the resolved reference is symbolic, then it's a dangling symref. */ + if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { + error = reference__create(&ref2, repo, ref->target.symbolic, oid, NULL, 0, to_use, + log_message, NULL, NULL); + } else { + error = reference__create(&ref2, repo, ref->name, oid, NULL, 1, to_use, + log_message, &ref->target.oid, NULL); + } + +out: + git_reference_free(ref2); + git_reference_free(ref); + git_signature_free(who); + return error; +} + +static const char *commit_type(const git_commit *commit) +{ + unsigned int count = git_commit_parentcount(commit); + + if (count >= 2) + return " (merge)"; + else if (count == 0) + return " (initial)"; + else + return ""; +} + +int git_reference__update_for_commit( + git_repository *repo, + git_reference *ref, + const char *ref_name, + const git_oid *id, + const char *operation) +{ + git_reference *ref_new = NULL; + git_commit *commit = NULL; + git_str reflog_msg = GIT_STR_INIT; + const git_signature *who; + int error; + + if ((error = git_commit_lookup(&commit, repo, id)) < 0 || + (error = git_str_printf(&reflog_msg, "%s%s: %s", + operation ? operation : "commit", + commit_type(commit), + git_commit_summary(commit))) < 0) + goto done; + + who = git_commit_committer(commit); + + if (ref) { + if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0) + return error; + + error = reference__create(&ref_new, repo, ref->name, id, NULL, 1, who, + git_str_cstr(&reflog_msg), &ref->target.oid, NULL); + } + else + error = git_reference__update_terminal( + repo, ref_name, id, who, git_str_cstr(&reflog_msg)); + +done: + git_reference_free(ref_new); + git_str_dispose(&reflog_msg); + git_commit_free(commit); + return error; +} + +int git_reference_has_log(git_repository *repo, const char *refname) +{ + int error; + git_refdb *refdb; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(refname); + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return error; + + return git_refdb_has_log(refdb, refname); +} + +int git_reference_ensure_log(git_repository *repo, const char *refname) +{ + int error; + git_refdb *refdb; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(refname); + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return error; + + return git_refdb_ensure_log(refdb, refname); +} + +int git_reference__is_branch(const char *ref_name) +{ + return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0; +} + +int git_reference_is_branch(const git_reference *ref) +{ + GIT_ASSERT_ARG(ref); + return git_reference__is_branch(ref->name); +} + +int git_reference__is_remote(const char *ref_name) +{ + return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0; +} + +int git_reference_is_remote(const git_reference *ref) +{ + GIT_ASSERT_ARG(ref); + return git_reference__is_remote(ref->name); +} + +int git_reference__is_tag(const char *ref_name) +{ + return git__prefixcmp(ref_name, GIT_REFS_TAGS_DIR) == 0; +} + +int git_reference_is_tag(const git_reference *ref) +{ + GIT_ASSERT_ARG(ref); + return git_reference__is_tag(ref->name); +} + +int git_reference__is_note(const char *ref_name) +{ + return git__prefixcmp(ref_name, GIT_REFS_NOTES_DIR) == 0; +} + +int git_reference_is_note(const git_reference *ref) +{ + GIT_ASSERT_ARG(ref); + return git_reference__is_note(ref->name); +} + +static int peel_error(int error, const git_reference *ref, const char *msg) +{ + git_error_set( + GIT_ERROR_INVALID, + "the reference '%s' cannot be peeled - %s", git_reference_name(ref), msg); + return error; +} + +int git_reference_peel( + git_object **peeled, + const git_reference *ref, + git_object_t target_type) +{ + const git_reference *resolved = NULL; + git_reference *allocated = NULL; + git_object *target = NULL; + int error; + + GIT_ASSERT_ARG(ref); + + if (ref->type == GIT_REFERENCE_DIRECT) { + resolved = ref; + } else { + if ((error = git_reference_resolve(&allocated, ref)) < 0) + return peel_error(error, ref, "Cannot resolve reference"); + + resolved = allocated; + } + + /* + * If we try to peel an object to a tag, we cannot use + * the fully peeled object, as that will always resolve + * to a commit. So we only want to use the peeled value + * if it is not zero and the target is not a tag. + */ + if (target_type != GIT_OBJECT_TAG && !git_oid_is_zero(&resolved->peel)) { + error = git_object_lookup(&target, + git_reference_owner(ref), &resolved->peel, GIT_OBJECT_ANY); + } else { + error = git_object_lookup(&target, + git_reference_owner(ref), &resolved->target.oid, GIT_OBJECT_ANY); + } + + if (error < 0) { + peel_error(error, ref, "Cannot retrieve reference target"); + goto cleanup; + } + + if (target_type == GIT_OBJECT_ANY && git_object_type(target) != GIT_OBJECT_TAG) + error = git_object_dup(peeled, target); + else + error = git_object_peel(peeled, target, target_type); + +cleanup: + git_object_free(target); + git_reference_free(allocated); + + return error; +} + +int git_reference__name_is_valid( + int *valid, + const char *refname, + unsigned int flags) +{ + int error; + + GIT_ASSERT(valid && refname); + + *valid = 0; + + error = git_reference__normalize_name(NULL, refname, flags); + + if (!error) + *valid = 1; + else if (error == GIT_EINVALIDSPEC) + error = 0; + + return error; +} + +int git_reference_name_is_valid(int *valid, const char *refname) +{ + return git_reference__name_is_valid(valid, refname, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL); +} + +const char *git_reference__shorthand(const char *name) +{ + if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) + return name + strlen(GIT_REFS_HEADS_DIR); + else if (!git__prefixcmp(name, GIT_REFS_TAGS_DIR)) + return name + strlen(GIT_REFS_TAGS_DIR); + else if (!git__prefixcmp(name, GIT_REFS_REMOTES_DIR)) + return name + strlen(GIT_REFS_REMOTES_DIR); + else if (!git__prefixcmp(name, GIT_REFS_DIR)) + return name + strlen(GIT_REFS_DIR); + + /* No shorthands are available, so just return the name. */ + return name; +} + +const char *git_reference_shorthand(const git_reference *ref) +{ + return git_reference__shorthand(ref->name); +} + +int git_reference__is_unborn_head(bool *unborn, const git_reference *ref, git_repository *repo) +{ + int error; + git_reference *tmp_ref; + + GIT_ASSERT_ARG(unborn); + GIT_ASSERT_ARG(ref); + GIT_ASSERT_ARG(repo); + + if (ref->type == GIT_REFERENCE_DIRECT) { + *unborn = 0; + return 0; + } + + error = git_reference_lookup_resolved(&tmp_ref, repo, ref->name, -1); + git_reference_free(tmp_ref); + + if (error != 0 && error != GIT_ENOTFOUND) + return error; + else if (error == GIT_ENOTFOUND && git__strcmp(ref->name, GIT_HEAD_FILE) == 0) + *unborn = true; + else + *unborn = false; + + return 0; +} + +/* Deprecated functions */ + +#ifndef GIT_DEPRECATE_HARD + +int git_reference_is_valid_name(const char *refname) +{ + int valid = 0; + + git_reference__name_is_valid(&valid, refname, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL); + + return valid; +} + +#endif diff --git a/src/libgit2/refs.h b/src/libgit2/refs.h new file mode 100644 index 000000000..cb888bf8f --- /dev/null +++ b/src/libgit2/refs.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_refs_h__ +#define INCLUDE_refs_h__ + +#include "common.h" + +#include "git2/oid.h" +#include "git2/refs.h" +#include "git2/refdb.h" +#include "strmap.h" +#include "str.h" +#include "oid.h" + +extern bool git_reference__enable_symbolic_ref_target_validation; + +#define GIT_REFS_DIR "refs/" +#define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/" +#define GIT_REFS_TAGS_DIR GIT_REFS_DIR "tags/" +#define GIT_REFS_REMOTES_DIR GIT_REFS_DIR "remotes/" +#define GIT_REFS_NOTES_DIR GIT_REFS_DIR "notes/" +#define GIT_REFS_DIR_MODE 0777 +#define GIT_REFS_FILE_MODE 0666 + +#define GIT_RENAMED_REF_FILE GIT_REFS_DIR "RENAMED-REF" + +#define GIT_SYMREF "ref: " +#define GIT_PACKEDREFS_FILE "packed-refs" +#define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled fully-peeled sorted " +#define GIT_PACKEDREFS_FILE_MODE 0666 + +#define GIT_HEAD_FILE "HEAD" +#define GIT_ORIG_HEAD_FILE "ORIG_HEAD" +#define GIT_FETCH_HEAD_FILE "FETCH_HEAD" +#define GIT_MERGE_HEAD_FILE "MERGE_HEAD" +#define GIT_REVERT_HEAD_FILE "REVERT_HEAD" +#define GIT_CHERRYPICK_HEAD_FILE "CHERRY_PICK_HEAD" +#define GIT_BISECT_LOG_FILE "BISECT_LOG" +#define GIT_REBASE_MERGE_DIR "rebase-merge/" +#define GIT_REBASE_MERGE_INTERACTIVE_FILE GIT_REBASE_MERGE_DIR "interactive" +#define GIT_REBASE_APPLY_DIR "rebase-apply/" +#define GIT_REBASE_APPLY_REBASING_FILE GIT_REBASE_APPLY_DIR "rebasing" +#define GIT_REBASE_APPLY_APPLYING_FILE GIT_REBASE_APPLY_DIR "applying" + +#define GIT_SEQUENCER_DIR "sequencer/" +#define GIT_SEQUENCER_HEAD_FILE GIT_SEQUENCER_DIR "head" +#define GIT_SEQUENCER_OPTIONS_FILE GIT_SEQUENCER_DIR "options" +#define GIT_SEQUENCER_TODO_FILE GIT_SEQUENCER_DIR "todo" + +#define GIT_STASH_FILE "stash" +#define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE + +#define GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE (1u << 16) +#define GIT_REFERENCE_FORMAT__VALIDATION_DISABLE (1u << 15) + +#define GIT_REFNAME_MAX 1024 + +typedef char git_refname_t[GIT_REFNAME_MAX]; + +struct git_reference { + git_refdb *db; + git_reference_t type; + + union { + git_oid oid; + char *symbolic; + } target; + + git_oid peel; + char name[GIT_FLEX_ARRAY]; +}; + +/** + * Reallocate the reference with a new name + * + * Note that this is a dangerous operation, as on success, all existing + * pointers to the old reference will now be dangling. Only call this on objects + * you control, possibly using `git_reference_dup`. + */ +git_reference *git_reference__realloc(git_reference **ptr_to_ref, const char *name); + +int git_reference__normalize_name(git_str *buf, const char *name, unsigned int flags); +int git_reference__update_terminal(git_repository *repo, const char *ref_name, const git_oid *oid, const git_signature *sig, const char *log_message); +int git_reference__name_is_valid(int *valid, const char *name, unsigned int flags); +int git_reference__is_branch(const char *ref_name); +int git_reference__is_remote(const char *ref_name); +int git_reference__is_tag(const char *ref_name); +int git_reference__is_note(const char *ref_name); +const char *git_reference__shorthand(const char *name); + +/** + * Lookup a reference by name and try to resolve to an OID. + * + * You can control how many dereferences this will attempt to resolve the + * reference with the `max_deref` parameter, or pass -1 to use a sane + * default. If you pass 0 for `max_deref`, this will not attempt to resolve + * the reference. For any value of `max_deref` other than 0, not + * successfully resolving the reference will be reported as an error. + + * The generated reference must be freed by the user. + * + * @param reference_out Pointer to the looked-up reference + * @param repo The repository to look up the reference + * @param name The long name for the reference (e.g. HEAD, ref/heads/master, refs/tags/v0.1.0, ...) + * @param max_deref Maximum number of dereferences to make of symbolic refs, 0 means simple lookup, < 0 means use default reasonable value + * @return 0 on success or < 0 on error; not being able to resolve the reference is an error unless 0 was passed for max_deref + */ +int git_reference_lookup_resolved( + git_reference **reference_out, + git_repository *repo, + const char *name, + int max_deref); + +int git_reference__log_signature(git_signature **out, git_repository *repo); + +/** Update a reference after a commit. */ +int git_reference__update_for_commit( + git_repository *repo, + git_reference *ref, + const char *ref_name, + const git_oid *id, + const char *operation); + +int git_reference__is_unborn_head(bool *unborn, const git_reference *ref, git_repository *repo); + +#endif diff --git a/src/libgit2/refspec.c b/src/libgit2/refspec.c new file mode 100644 index 000000000..f0a0c2bfb --- /dev/null +++ b/src/libgit2/refspec.c @@ -0,0 +1,420 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "refspec.h" + +#include "buf.h" +#include "refs.h" +#include "util.h" +#include "vector.h" +#include "wildmatch.h" + +int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch) +{ + /* Ported from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/remote.c#L518-636 */ + + size_t llen; + int is_glob = 0; + const char *lhs, *rhs; + int valid = 0; + unsigned int flags; + + GIT_ASSERT_ARG(refspec); + GIT_ASSERT_ARG(input); + + memset(refspec, 0x0, sizeof(git_refspec)); + refspec->push = !is_fetch; + + lhs = input; + if (*lhs == '+') { + refspec->force = 1; + lhs++; + } + + rhs = strrchr(lhs, ':'); + + /* + * Before going on, special case ":" (or "+:") as a refspec + * for matching refs. + */ + if (!is_fetch && rhs == lhs && rhs[1] == '\0') { + refspec->matching = 1; + refspec->string = git__strdup(input); + GIT_ERROR_CHECK_ALLOC(refspec->string); + refspec->src = git__strdup(""); + GIT_ERROR_CHECK_ALLOC(refspec->src); + refspec->dst = git__strdup(""); + GIT_ERROR_CHECK_ALLOC(refspec->dst); + return 0; + } + + if (rhs) { + size_t rlen = strlen(++rhs); + if (rlen || !is_fetch) { + is_glob = (1 <= rlen && strchr(rhs, '*')); + refspec->dst = git__strndup(rhs, rlen); + } + } + + llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs)); + if (1 <= llen && memchr(lhs, '*', llen)) { + if ((rhs && !is_glob) || (!rhs && is_fetch)) + goto invalid; + is_glob = 1; + } else if (rhs && is_glob) + goto invalid; + + refspec->pattern = is_glob; + refspec->src = git__strndup(lhs, llen); + flags = GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL | + GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND | + (is_glob ? GIT_REFERENCE_FORMAT_REFSPEC_PATTERN : 0); + + if (is_fetch) { + /* + * LHS + * - empty is allowed; it means HEAD. + * - otherwise it must be a valid looking ref. + */ + if (!*refspec->src) + ; /* empty is ok */ + else if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) + goto on_error; + else if (!valid) + goto invalid; + + /* + * RHS + * - missing is ok, and is same as empty. + * - empty is ok; it means not to store. + * - otherwise it must be a valid looking ref. + */ + if (!refspec->dst) + ; /* ok */ + else if (!*refspec->dst) + ; /* ok */ + else if (git_reference__name_is_valid(&valid, refspec->dst, flags) < 0) + goto on_error; + else if (!valid) + goto invalid; + } else { + /* + * LHS + * - empty is allowed; it means delete. + * - when wildcarded, it must be a valid looking ref. + * - otherwise, it must be an extended SHA-1, but + * there is no existing way to validate this. + */ + if (!*refspec->src) + ; /* empty is ok */ + else if (is_glob) { + if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) + goto on_error; + else if (!valid) + goto invalid; + } + else { + ; /* anything goes, for now */ + } + + /* + * RHS + * - missing is allowed, but LHS then must be a + * valid looking ref. + * - empty is not allowed. + * - otherwise it must be a valid looking ref. + */ + if (!refspec->dst) { + if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) + goto on_error; + else if (!valid) + goto invalid; + } else if (!*refspec->dst) { + goto invalid; + } else { + if (git_reference__name_is_valid(&valid, refspec->dst, flags) < 0) + goto on_error; + else if (!valid) + goto invalid; + } + + /* if the RHS is empty, then it's a copy of the LHS */ + if (!refspec->dst) { + refspec->dst = git__strdup(refspec->src); + GIT_ERROR_CHECK_ALLOC(refspec->dst); + } + } + + refspec->string = git__strdup(input); + GIT_ERROR_CHECK_ALLOC(refspec->string); + + return 0; + +invalid: + git_error_set(GIT_ERROR_INVALID, + "'%s' is not a valid refspec.", input); + git_refspec__dispose(refspec); + return GIT_EINVALIDSPEC; + +on_error: + git_refspec__dispose(refspec); + return -1; +} + +void git_refspec__dispose(git_refspec *refspec) +{ + if (refspec == NULL) + return; + + git__free(refspec->src); + git__free(refspec->dst); + git__free(refspec->string); + + memset(refspec, 0x0, sizeof(git_refspec)); +} + +int git_refspec_parse(git_refspec **out_refspec, const char *input, int is_fetch) +{ + git_refspec *refspec; + GIT_ASSERT_ARG(out_refspec); + GIT_ASSERT_ARG(input); + + *out_refspec = NULL; + + refspec = git__malloc(sizeof(git_refspec)); + GIT_ERROR_CHECK_ALLOC(refspec); + + if (git_refspec__parse(refspec, input, !!is_fetch) != 0) { + git__free(refspec); + return -1; + } + + *out_refspec = refspec; + return 0; +} + +void git_refspec_free(git_refspec *refspec) +{ + git_refspec__dispose(refspec); + git__free(refspec); +} + +const char *git_refspec_src(const git_refspec *refspec) +{ + return refspec == NULL ? NULL : refspec->src; +} + +const char *git_refspec_dst(const git_refspec *refspec) +{ + return refspec == NULL ? NULL : refspec->dst; +} + +const char *git_refspec_string(const git_refspec *refspec) +{ + return refspec == NULL ? NULL : refspec->string; +} + +int git_refspec_force(const git_refspec *refspec) +{ + GIT_ASSERT_ARG(refspec); + + return refspec->force; +} + +int git_refspec_src_matches(const git_refspec *refspec, const char *refname) +{ + if (refspec == NULL || refspec->src == NULL) + return false; + + return (wildmatch(refspec->src, refname, 0) == 0); +} + +int git_refspec_dst_matches(const git_refspec *refspec, const char *refname) +{ + if (refspec == NULL || refspec->dst == NULL) + return false; + + return (wildmatch(refspec->dst, refname, 0) == 0); +} + +static int refspec_transform( + git_str *out, const char *from, const char *to, const char *name) +{ + const char *from_star, *to_star; + size_t replacement_len, star_offset; + + git_str_clear(out); + + /* + * There are two parts to each side of a refspec, the bit + * before the star and the bit after it. The star can be in + * the middle of the pattern, so we need to look at each bit + * individually. + */ + from_star = strchr(from, '*'); + to_star = strchr(to, '*'); + + GIT_ASSERT(from_star && to_star); + + /* star offset, both in 'from' and in 'name' */ + star_offset = from_star - from; + + /* the first half is copied over */ + git_str_put(out, to, to_star - to); + + /* + * Copy over the name, but exclude the trailing part in "from" starting + * after the glob + */ + replacement_len = strlen(name + star_offset) - strlen(from_star + 1); + git_str_put(out, name + star_offset, replacement_len); + + return git_str_puts(out, to_star + 1); +} + +int git_refspec_transform(git_buf *out, const git_refspec *spec, const char *name) +{ + GIT_BUF_WRAP_PRIVATE(out, git_refspec__transform, spec, name); +} + +int git_refspec__transform(git_str *out, const git_refspec *spec, const char *name) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(spec); + GIT_ASSERT_ARG(name); + + if (!git_refspec_src_matches(spec, name)) { + git_error_set(GIT_ERROR_INVALID, "ref '%s' doesn't match the source", name); + return -1; + } + + if (!spec->pattern) + return git_str_puts(out, spec->dst ? spec->dst : ""); + + return refspec_transform(out, spec->src, spec->dst, name); +} + +int git_refspec_rtransform(git_buf *out, const git_refspec *spec, const char *name) +{ + GIT_BUF_WRAP_PRIVATE(out, git_refspec__rtransform, spec, name); +} + +int git_refspec__rtransform(git_str *out, const git_refspec *spec, const char *name) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(spec); + GIT_ASSERT_ARG(name); + + if (!git_refspec_dst_matches(spec, name)) { + git_error_set(GIT_ERROR_INVALID, "ref '%s' doesn't match the destination", name); + return -1; + } + + if (!spec->pattern) + return git_str_puts(out, spec->src); + + return refspec_transform(out, spec->dst, spec->src, name); +} + +int git_refspec__serialize(git_str *out, const git_refspec *refspec) +{ + if (refspec->force) + git_str_putc(out, '+'); + + git_str_printf(out, "%s:%s", + refspec->src != NULL ? refspec->src : "", + refspec->dst != NULL ? refspec->dst : ""); + + return git_str_oom(out) == false; +} + +int git_refspec_is_wildcard(const git_refspec *spec) +{ + GIT_ASSERT_ARG(spec); + GIT_ASSERT_ARG(spec->src); + + return (spec->src[strlen(spec->src) - 1] == '*'); +} + +git_direction git_refspec_direction(const git_refspec *spec) +{ + GIT_ASSERT_ARG(spec); + + return spec->push; +} + +int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs) +{ + git_str buf = GIT_STR_INIT; + size_t j, pos; + git_remote_head key; + git_refspec *cur; + + const char *formatters[] = { + GIT_REFS_DIR "%s", + GIT_REFS_TAGS_DIR "%s", + GIT_REFS_HEADS_DIR "%s", + NULL + }; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(spec); + GIT_ASSERT_ARG(refs); + + cur = git__calloc(1, sizeof(git_refspec)); + GIT_ERROR_CHECK_ALLOC(cur); + + cur->force = spec->force; + cur->push = spec->push; + cur->pattern = spec->pattern; + cur->matching = spec->matching; + cur->string = git__strdup(spec->string); + + /* shorthand on the lhs */ + if (git__prefixcmp(spec->src, GIT_REFS_DIR)) { + for (j = 0; formatters[j]; j++) { + git_str_clear(&buf); + git_str_printf(&buf, formatters[j], spec->src); + GIT_ERROR_CHECK_ALLOC_STR(&buf); + + key.name = (char *) git_str_cstr(&buf); + if (!git_vector_search(&pos, refs, &key)) { + /* we found something to match the shorthand, set src to that */ + cur->src = git_str_detach(&buf); + } + } + } + + /* No shorthands found, copy over the name */ + if (cur->src == NULL && spec->src != NULL) { + cur->src = git__strdup(spec->src); + GIT_ERROR_CHECK_ALLOC(cur->src); + } + + if (spec->dst && git__prefixcmp(spec->dst, GIT_REFS_DIR)) { + /* if it starts with "remotes" then we just prepend "refs/" */ + if (!git__prefixcmp(spec->dst, "remotes/")) { + git_str_puts(&buf, GIT_REFS_DIR); + } else { + git_str_puts(&buf, GIT_REFS_HEADS_DIR); + } + + git_str_puts(&buf, spec->dst); + GIT_ERROR_CHECK_ALLOC_STR(&buf); + + cur->dst = git_str_detach(&buf); + } + + git_str_dispose(&buf); + + if (cur->dst == NULL && spec->dst != NULL) { + cur->dst = git__strdup(spec->dst); + GIT_ERROR_CHECK_ALLOC(cur->dst); + } + + return git_vector_insert(out, cur); +} diff --git a/src/libgit2/refspec.h b/src/libgit2/refspec.h new file mode 100644 index 000000000..bf4f7fcfb --- /dev/null +++ b/src/libgit2/refspec.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_refspec_h__ +#define INCLUDE_refspec_h__ + +#include "common.h" + +#include "git2/refspec.h" +#include "str.h" +#include "vector.h" + +struct git_refspec { + char *string; + char *src; + char *dst; + unsigned int force :1, + push : 1, + pattern :1, + matching :1; +}; + +#define GIT_REFSPEC_TAGS "refs/tags/*:refs/tags/*" + +int git_refspec__transform(git_str *out, const git_refspec *spec, const char *name); +int git_refspec__rtransform(git_str *out, const git_refspec *spec, const char *name); + +int git_refspec__parse( + struct git_refspec *refspec, + const char *str, + bool is_fetch); + +void git_refspec__dispose(git_refspec *refspec); + +int git_refspec__serialize(git_str *out, const git_refspec *refspec); + +/** + * Determines if a refspec is a wildcard refspec. + * + * @param spec the refspec + * @return 1 if the refspec is a wildcard, 0 otherwise + */ +int git_refspec_is_wildcard(const git_refspec *spec); + +/** + * DWIM `spec` with `refs` existing on the remote, append the dwim'ed + * result in `out`. + */ +int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs); + +#endif diff --git a/src/libgit2/regexp.c b/src/libgit2/regexp.c new file mode 100644 index 000000000..2569dea0a --- /dev/null +++ b/src/libgit2/regexp.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "regexp.h" + +#if defined(GIT_REGEX_BUILTIN) || defined(GIT_REGEX_PCRE) + +int git_regexp_compile(git_regexp *r, const char *pattern, int flags) +{ + int erroffset, cflags = 0; + const char *error = NULL; + + if (flags & GIT_REGEXP_ICASE) + cflags |= PCRE_CASELESS; + + if ((*r = pcre_compile(pattern, cflags, &error, &erroffset, NULL)) == NULL) { + git_error_set_str(GIT_ERROR_REGEX, error); + return GIT_EINVALIDSPEC; + } + + return 0; +} + +void git_regexp_dispose(git_regexp *r) +{ + pcre_free(*r); + *r = NULL; +} + +int git_regexp_match(const git_regexp *r, const char *string) +{ + int error; + if ((error = pcre_exec(*r, NULL, string, (int) strlen(string), 0, 0, NULL, 0)) < 0) + return (error == PCRE_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) +{ + int static_ovec[9] = {0}, *ovec; + int error; + size_t i; + + /* The ovec array always needs to be a multiple of three */ + if (nmatches <= ARRAY_SIZE(static_ovec) / 3) + ovec = static_ovec; + else + ovec = git__calloc(nmatches * 3, sizeof(*ovec)); + GIT_ERROR_CHECK_ALLOC(ovec); + + if ((error = pcre_exec(*r, NULL, string, (int) strlen(string), 0, 0, ovec, (int) nmatches * 3)) < 0) + goto out; + + if (error == 0) + error = (int) nmatches; + + for (i = 0; i < (unsigned int) error; i++) { + matches[i].start = (ovec[i * 2] < 0) ? -1 : ovec[i * 2]; + matches[i].end = (ovec[i * 2 + 1] < 0) ? -1 : ovec[i * 2 + 1]; + } + for (i = (unsigned int) error; i < nmatches; i++) + matches[i].start = matches[i].end = -1; + +out: + if (nmatches > ARRAY_SIZE(static_ovec) / 3) + git__free(ovec); + if (error < 0) + return (error == PCRE_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +#elif defined(GIT_REGEX_PCRE2) + +int git_regexp_compile(git_regexp *r, const char *pattern, int flags) +{ + unsigned char errmsg[1024]; + PCRE2_SIZE erroff; + int error, cflags = 0; + + if (flags & GIT_REGEXP_ICASE) + cflags |= PCRE2_CASELESS; + + if ((*r = pcre2_compile((const unsigned char *) pattern, PCRE2_ZERO_TERMINATED, + cflags, &error, &erroff, NULL)) == NULL) { + pcre2_get_error_message(error, errmsg, sizeof(errmsg)); + git_error_set_str(GIT_ERROR_REGEX, (char *) errmsg); + return GIT_EINVALIDSPEC; + } + + return 0; +} + +void git_regexp_dispose(git_regexp *r) +{ + pcre2_code_free(*r); + *r = NULL; +} + +int git_regexp_match(const git_regexp *r, const char *string) +{ + pcre2_match_data *data; + int error; + + data = pcre2_match_data_create(1, NULL); + GIT_ERROR_CHECK_ALLOC(data); + + if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string), + 0, 0, data, NULL)) < 0) + return (error == PCRE2_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + + pcre2_match_data_free(data); + return 0; +} + +int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) +{ + pcre2_match_data *data = NULL; + PCRE2_SIZE *ovec; + int error; + size_t i; + + if ((data = pcre2_match_data_create(nmatches, NULL)) == NULL) { + git_error_set_oom(); + goto out; + } + + if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string), + 0, 0, data, NULL)) < 0) + goto out; + + if (error == 0 || (unsigned int) error > nmatches) + error = nmatches; + ovec = pcre2_get_ovector_pointer(data); + + for (i = 0; i < (unsigned int) error; i++) { + matches[i].start = (ovec[i * 2] == PCRE2_UNSET) ? -1 : (ssize_t) ovec[i * 2]; + matches[i].end = (ovec[i * 2 + 1] == PCRE2_UNSET) ? -1 : (ssize_t) ovec[i * 2 + 1]; + } + for (i = (unsigned int) error; i < nmatches; i++) + matches[i].start = matches[i].end = -1; + +out: + pcre2_match_data_free(data); + if (error < 0) + return (error == PCRE2_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +#elif defined(GIT_REGEX_REGCOMP) || defined(GIT_REGEX_REGCOMP_L) + +#if defined(GIT_REGEX_REGCOMP_L) +# include +#endif + +int git_regexp_compile(git_regexp *r, const char *pattern, int flags) +{ + int cflags = REG_EXTENDED, error; + char errmsg[1024]; + + if (flags & GIT_REGEXP_ICASE) + cflags |= REG_ICASE; + +# if defined(GIT_REGEX_REGCOMP) + if ((error = regcomp(r, pattern, cflags)) != 0) +# else + if ((error = regcomp_l(r, pattern, cflags, (locale_t) 0)) != 0) +# endif + { + regerror(error, r, errmsg, sizeof(errmsg)); + git_error_set_str(GIT_ERROR_REGEX, errmsg); + return GIT_EINVALIDSPEC; + } + + return 0; +} + +void git_regexp_dispose(git_regexp *r) +{ + regfree(r); +} + +int git_regexp_match(const git_regexp *r, const char *string) +{ + int error; + if ((error = regexec(r, string, 0, NULL, 0)) != 0) + return (error == REG_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) +{ + regmatch_t static_m[3], *m; + int error; + size_t i; + + if (nmatches <= ARRAY_SIZE(static_m)) + m = static_m; + else + m = git__calloc(nmatches, sizeof(*m)); + + if ((error = regexec(r, string, nmatches, m, 0)) != 0) + goto out; + + for (i = 0; i < nmatches; i++) { + matches[i].start = (m[i].rm_so < 0) ? -1 : m[i].rm_so; + matches[i].end = (m[i].rm_eo < 0) ? -1 : m[i].rm_eo; + } + +out: + if (nmatches > ARRAY_SIZE(static_m)) + git__free(m); + if (error) + return (error == REG_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +#endif diff --git a/src/libgit2/regexp.h b/src/libgit2/regexp.h new file mode 100644 index 000000000..2592ef383 --- /dev/null +++ b/src/libgit2/regexp.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_regexp_h__ +#define INCLUDE_regexp_h__ + +#include "common.h" + +#if defined(GIT_REGEX_BUILTIN) || defined(GIT_REGEX_PCRE) +# include "pcre.h" +typedef pcre *git_regexp; +# define GIT_REGEX_INIT NULL +#elif defined(GIT_REGEX_PCRE2) +# define PCRE2_CODE_UNIT_WIDTH 8 +# include +typedef pcre2_code *git_regexp; +# define GIT_REGEX_INIT NULL +#elif defined(GIT_REGEX_REGCOMP) || defined(GIT_REGEX_REGCOMP_L) +# include +typedef regex_t git_regexp; +# define GIT_REGEX_INIT { 0 } +#else +# error "No regex backend" +#endif + +/** Options supported by @git_regexp_compile. */ +typedef enum { + /** Enable case-insensitive matching */ + GIT_REGEXP_ICASE = (1 << 0) +} git_regexp_flags_t; + +/** Structure containing information about regular expression matching groups */ +typedef struct { + /** Start of the given match. -1 if the group didn't match anything */ + ssize_t start; + /** End of the given match. -1 if the group didn't match anything */ + ssize_t end; +} git_regmatch; + +/** + * Compile a regular expression. The compiled expression needs to + * be cleaned up afterwards with `git_regexp_dispose`. + * + * @param r Pointer to the storage where to initialize the regular expression. + * @param pattern The pattern that shall be compiled. + * @param flags Flags to alter how the pattern shall be handled. + * 0 for defaults, otherwise see @git_regexp_flags_t. + * @return 0 on success, otherwise a negative return value. + */ +int git_regexp_compile(git_regexp *r, const char *pattern, int flags); + +/** + * Free memory associated with the regular expression + * + * @param r The regular expression structure to dispose. + */ +void git_regexp_dispose(git_regexp *r); + +/** + * Test whether a given string matches a compiled regular + * expression. + * + * @param r Compiled regular expression. + * @param string String to match against the regular expression. + * @return 0 if the string matches, a negative error code + * otherwise. GIT_ENOTFOUND if no match was found, + * GIT_EINVALIDSPEC if the regular expression matching + * was invalid. + */ +int git_regexp_match(const git_regexp *r, const char *string); + +/** + * Search for matches inside of a given string. + * + * Given a regular expression with capturing groups, this + * function will populate provided @git_regmatch structures with + * offsets for each of the given matches. Non-matching groups + * will have start and end values of the respective @git_regmatch + * structure set to -1. + * + * @param r Compiled regular expression. + * @param string String to match against the regular expression. + * @param nmatches Number of @git_regmatch structures provided by + * the user. + * @param matches Pointer to an array of @git_regmatch structures. + * @return 0 if the string matches, a negative error code + * otherwise. GIT_ENOTFOUND if no match was found, + * GIT_EINVALIDSPEC if the regular expression matching + * was invalid. + */ +int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches); + +#endif diff --git a/src/libgit2/remote.c b/src/libgit2/remote.c new file mode 100644 index 000000000..f6421b9eb --- /dev/null +++ b/src/libgit2/remote.c @@ -0,0 +1,3084 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "remote.h" + +#include "buf.h" +#include "branch.h" +#include "config.h" +#include "repository.h" +#include "fetch.h" +#include "refs.h" +#include "refspec.h" +#include "fetchhead.h" +#include "push.h" +#include "proxy.h" + +#include "git2/config.h" +#include "git2/types.h" +#include "git2/oid.h" +#include "git2/net.h" + +#define CONFIG_URL_FMT "remote.%s.url" +#define CONFIG_PUSHURL_FMT "remote.%s.pushurl" +#define CONFIG_FETCH_FMT "remote.%s.fetch" +#define CONFIG_PUSH_FMT "remote.%s.push" +#define CONFIG_TAGOPT_FMT "remote.%s.tagopt" + +static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs); +static int lookup_remote_prune_config(git_remote *remote, git_config *config, const char *name); +static int apply_insteadof(char **out, git_config *config, const char *url, int direction, bool use_default_if_empty); + +static int add_refspec_to(git_vector *vector, const char *string, bool is_fetch) +{ + git_refspec *spec; + + spec = git__calloc(1, sizeof(git_refspec)); + GIT_ERROR_CHECK_ALLOC(spec); + + if (git_refspec__parse(spec, string, is_fetch) < 0) { + git__free(spec); + return -1; + } + + spec->push = !is_fetch; + if (git_vector_insert(vector, spec) < 0) { + git_refspec__dispose(spec); + git__free(spec); + return -1; + } + + return 0; +} + +static int add_refspec(git_remote *remote, const char *string, bool is_fetch) +{ + return add_refspec_to(&remote->refspecs, string, is_fetch); +} + +static int download_tags_value(git_remote *remote, git_config *cfg) +{ + git_config_entry *ce; + git_str buf = GIT_STR_INIT; + int error; + + if (git_str_printf(&buf, "remote.%s.tagopt", remote->name) < 0) + return -1; + + error = git_config__lookup_entry(&ce, cfg, git_str_cstr(&buf), false); + git_str_dispose(&buf); + + if (!error && ce && ce->value) { + if (!strcmp(ce->value, "--no-tags")) + remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; + else if (!strcmp(ce->value, "--tags")) + remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; + } + + git_config_entry_free(ce); + return error; +} + +static int ensure_remote_name_is_valid(const char *name) +{ + int valid, error; + + error = git_remote_name_is_valid(&valid, name); + + if (!error && !valid) { + git_error_set( + GIT_ERROR_CONFIG, + "'%s' is not a valid remote name.", name ? name : "(null)"); + error = GIT_EINVALIDSPEC; + } + + return error; +} + +static int write_add_refspec(git_repository *repo, const char *name, const char *refspec, bool fetch) +{ + git_config *cfg; + git_str var = GIT_STR_INIT; + git_refspec spec; + const char *fmt; + int error; + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + return error; + + fmt = fetch ? CONFIG_FETCH_FMT : CONFIG_PUSH_FMT; + + if ((error = ensure_remote_name_is_valid(name)) < 0) + return error; + + if ((error = git_refspec__parse(&spec, refspec, fetch)) < 0) + return error; + + git_refspec__dispose(&spec); + + if ((error = git_str_printf(&var, fmt, name)) < 0) + return error; + + /* + * "$^" is an unmatchable regexp: it will not match anything at all, so + * all values will be considered new and we will not replace any + * present value. + */ + if ((error = git_config_set_multivar(cfg, var.ptr, "$^", refspec)) < 0) { + goto cleanup; + } + +cleanup: + git_str_dispose(&var); + return 0; +} + +static int canonicalize_url(git_str *out, const char *in) +{ + if (in == NULL || strlen(in) == 0) { + git_error_set(GIT_ERROR_INVALID, "cannot set empty URL"); + return GIT_EINVALIDSPEC; + } + +#ifdef GIT_WIN32 + /* Given a UNC path like \\server\path, we need to convert this + * to //server/path for compatibility with core git. + */ + if (in[0] == '\\' && in[1] == '\\' && + (git__isalpha(in[2]) || git__isdigit(in[2]))) { + const char *c; + for (c = in; *c; c++) + git_str_putc(out, *c == '\\' ? '/' : *c); + + return git_str_oom(out) ? -1 : 0; + } +#endif + + return git_str_puts(out, in); +} + +static int default_fetchspec_for_name(git_str *buf, const char *name) +{ + if (git_str_printf(buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0) + return -1; + + return 0; +} + +static int ensure_remote_doesnot_exist(git_repository *repo, const char *name) +{ + int error; + git_remote *remote; + + error = git_remote_lookup(&remote, repo, name); + + if (error == GIT_ENOTFOUND) + return 0; + + if (error < 0) + return error; + + git_remote_free(remote); + + git_error_set(GIT_ERROR_CONFIG, "remote '%s' already exists", name); + + return GIT_EEXISTS; +} + +int git_remote_create_options_init(git_remote_create_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_remote_create_options, GIT_REMOTE_CREATE_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_remote_create_init_options(git_remote_create_options *opts, unsigned int version) +{ + return git_remote_create_options_init(opts, version); +} +#endif + +int git_remote_create_with_opts(git_remote **out, const char *url, const git_remote_create_options *opts) +{ + git_remote *remote = NULL; + git_config *config_ro = NULL, *config_rw; + git_str canonical_url = GIT_STR_INIT; + git_str var = GIT_STR_INIT; + git_str specbuf = GIT_STR_INIT; + const git_remote_create_options dummy_opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + int error = -1; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(url); + + if (!opts) { + opts = &dummy_opts; + } + + GIT_ERROR_CHECK_VERSION(opts, GIT_REMOTE_CREATE_OPTIONS_VERSION, "git_remote_create_options"); + + if (opts->name != NULL) { + if ((error = ensure_remote_name_is_valid(opts->name)) < 0) + return error; + + if (opts->repository && + (error = ensure_remote_doesnot_exist(opts->repository, opts->name)) < 0) + return error; + } + + if (opts->repository) { + if ((error = git_repository_config_snapshot(&config_ro, opts->repository)) < 0) + goto on_error; + } + + remote = git__calloc(1, sizeof(git_remote)); + GIT_ERROR_CHECK_ALLOC(remote); + + remote->repo = opts->repository; + + if ((error = git_vector_init(&remote->refs, 8, NULL)) < 0 || + (error = canonicalize_url(&canonical_url, url)) < 0) + goto on_error; + + if (opts->repository && !(opts->flags & GIT_REMOTE_CREATE_SKIP_INSTEADOF)) { + if ((error = apply_insteadof(&remote->url, config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH, true)) < 0 || + (error = apply_insteadof(&remote->pushurl, config_ro, canonical_url.ptr, GIT_DIRECTION_PUSH, false)) < 0) + goto on_error; + } else { + remote->url = git__strdup(canonical_url.ptr); + GIT_ERROR_CHECK_ALLOC(remote->url); + } + + if (opts->name != NULL) { + remote->name = git__strdup(opts->name); + GIT_ERROR_CHECK_ALLOC(remote->name); + + if (opts->repository && + ((error = git_str_printf(&var, CONFIG_URL_FMT, opts->name)) < 0 || + (error = git_repository_config__weakptr(&config_rw, opts->repository)) < 0 || + (error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0)) + goto on_error; + } + + if (opts->fetchspec != NULL || + (opts->name && !(opts->flags & GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC))) { + const char *fetch = NULL; + if (opts->fetchspec) { + fetch = opts->fetchspec; + } else { + if ((error = default_fetchspec_for_name(&specbuf, opts->name)) < 0) + goto on_error; + + fetch = git_str_cstr(&specbuf); + } + + if ((error = add_refspec(remote, fetch, true)) < 0) + goto on_error; + + /* only write for named remotes with a repository */ + if (opts->repository && opts->name && + ((error = write_add_refspec(opts->repository, opts->name, fetch, true)) < 0 || + (error = lookup_remote_prune_config(remote, config_ro, opts->name)) < 0)) + goto on_error; + + /* Move the data over to where the matching functions can find them */ + if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0) + goto on_error; + } + + /* A remote without a name doesn't download tags */ + if (!opts->name) + remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; + else + remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; + + + git_str_dispose(&var); + + *out = remote; + error = 0; + +on_error: + if (error) + git_remote_free(remote); + + git_config_free(config_ro); + git_str_dispose(&specbuf); + git_str_dispose(&canonical_url); + git_str_dispose(&var); + return error; +} + +int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url) +{ + git_str buf = GIT_STR_INIT; + int error; + git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + + /* Those 2 tests are duplicated here because of backward-compatibility */ + if ((error = ensure_remote_name_is_valid(name)) < 0) + return error; + + if (canonicalize_url(&buf, url) < 0) + return GIT_ERROR; + + git_str_clear(&buf); + + opts.repository = repo; + opts.name = name; + + error = git_remote_create_with_opts(out, url, &opts); + + git_str_dispose(&buf); + + return error; +} + +int git_remote_create_with_fetchspec(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) +{ + int error; + git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + + if ((error = ensure_remote_name_is_valid(name)) < 0) + return error; + + opts.repository = repo; + opts.name = name; + opts.fetchspec = fetch; + opts.flags = GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC; + + return git_remote_create_with_opts(out, url, &opts); +} + +int git_remote_create_anonymous(git_remote **out, git_repository *repo, const char *url) +{ + git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + + opts.repository = repo; + + return git_remote_create_with_opts(out, url, &opts); +} + +int git_remote_create_detached(git_remote **out, const char *url) +{ + return git_remote_create_with_opts(out, url, NULL); +} + +int git_remote_dup(git_remote **dest, git_remote *source) +{ + size_t i; + int error = 0; + git_refspec *spec; + git_remote *remote = git__calloc(1, sizeof(git_remote)); + GIT_ERROR_CHECK_ALLOC(remote); + + if (source->name != NULL) { + remote->name = git__strdup(source->name); + GIT_ERROR_CHECK_ALLOC(remote->name); + } + + if (source->url != NULL) { + remote->url = git__strdup(source->url); + GIT_ERROR_CHECK_ALLOC(remote->url); + } + + if (source->pushurl != NULL) { + remote->pushurl = git__strdup(source->pushurl); + GIT_ERROR_CHECK_ALLOC(remote->pushurl); + } + + remote->repo = source->repo; + remote->download_tags = source->download_tags; + remote->prune_refs = source->prune_refs; + + if (git_vector_init(&remote->refs, 32, NULL) < 0 || + git_vector_init(&remote->refspecs, 2, NULL) < 0 || + git_vector_init(&remote->active_refspecs, 2, NULL) < 0) { + error = -1; + goto cleanup; + } + + git_vector_foreach(&source->refspecs, i, spec) { + if ((error = add_refspec(remote, spec->string, !spec->push)) < 0) + goto cleanup; + } + + *dest = remote; + +cleanup: + + if (error < 0) + git__free(remote); + + return error; +} + +struct refspec_cb_data { + git_remote *remote; + int fetch; +}; + +static int refspec_cb(const git_config_entry *entry, void *payload) +{ + struct refspec_cb_data *data = (struct refspec_cb_data *)payload; + return add_refspec(data->remote, entry->value, data->fetch); +} + +static int get_optional_config( + bool *found, git_config *config, git_str *buf, + git_config_foreach_cb cb, void *payload) +{ + int error = 0; + const char *key = git_str_cstr(buf); + + if (git_str_oom(buf)) + return -1; + + if (cb != NULL) + error = git_config_get_multivar_foreach(config, key, NULL, cb, payload); + else + error = git_config_get_string(payload, config, key); + + if (found) + *found = !error; + + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + return error; +} + +int git_remote_lookup(git_remote **out, git_repository *repo, const char *name) +{ + git_remote *remote = NULL; + git_str buf = GIT_STR_INIT; + const char *val; + int error = 0; + git_config *config; + struct refspec_cb_data data = { NULL }; + bool optional_setting_found = false, found; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = ensure_remote_name_is_valid(name)) < 0) + return error; + + if ((error = git_repository_config_snapshot(&config, repo)) < 0) + return error; + + remote = git__calloc(1, sizeof(git_remote)); + GIT_ERROR_CHECK_ALLOC(remote); + + remote->name = git__strdup(name); + GIT_ERROR_CHECK_ALLOC(remote->name); + + if (git_vector_init(&remote->refs, 32, NULL) < 0 || + git_vector_init(&remote->refspecs, 2, NULL) < 0 || + git_vector_init(&remote->passive_refspecs, 2, NULL) < 0 || + git_vector_init(&remote->active_refspecs, 2, NULL) < 0) { + error = -1; + goto cleanup; + } + + if ((error = git_str_printf(&buf, "remote.%s.url", name)) < 0) + goto cleanup; + + if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) + goto cleanup; + + optional_setting_found |= found; + + remote->repo = repo; + remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; + + if (found && strlen(val) > 0) { + if ((error = apply_insteadof(&remote->url, config, val, GIT_DIRECTION_FETCH, true)) < 0 || + (error = apply_insteadof(&remote->pushurl, config, val, GIT_DIRECTION_PUSH, false)) < 0) + goto cleanup; + } + + val = NULL; + git_str_clear(&buf); + git_str_printf(&buf, "remote.%s.pushurl", name); + + if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) + goto cleanup; + + optional_setting_found |= found; + + if (!optional_setting_found) { + error = GIT_ENOTFOUND; + git_error_set(GIT_ERROR_CONFIG, "remote '%s' does not exist", name); + goto cleanup; + } + + if (found && strlen(val) > 0) { + if (remote->pushurl) + git__free(remote->pushurl); + + if ((error = apply_insteadof(&remote->pushurl, config, val, GIT_DIRECTION_FETCH, true)) < 0) + goto cleanup; + } + + data.remote = remote; + data.fetch = true; + + git_str_clear(&buf); + git_str_printf(&buf, "remote.%s.fetch", name); + + if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) + goto cleanup; + + data.fetch = false; + git_str_clear(&buf); + git_str_printf(&buf, "remote.%s.push", name); + + if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) + goto cleanup; + + if ((error = download_tags_value(remote, config)) < 0) + goto cleanup; + + if ((error = lookup_remote_prune_config(remote, config, name)) < 0) + goto cleanup; + + /* Move the data over to where the matching functions can find them */ + if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0) + goto cleanup; + + *out = remote; + +cleanup: + git_config_free(config); + git_str_dispose(&buf); + + if (error < 0) + git_remote_free(remote); + + return error; +} + +static int lookup_remote_prune_config(git_remote *remote, git_config *config, const char *name) +{ + git_str buf = GIT_STR_INIT; + int error = 0; + + git_str_printf(&buf, "remote.%s.prune", name); + + if ((error = git_config_get_bool(&remote->prune_refs, config, git_str_cstr(&buf))) < 0) { + if (error == GIT_ENOTFOUND) { + git_error_clear(); + + if ((error = git_config_get_bool(&remote->prune_refs, config, "fetch.prune")) < 0) { + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + } + } + } + + git_str_dispose(&buf); + return error; +} + +const char *git_remote_name(const git_remote *remote) +{ + GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); + return remote->name; +} + +git_repository *git_remote_owner(const git_remote *remote) +{ + GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); + return remote->repo; +} + +const char *git_remote_url(const git_remote *remote) +{ + GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); + return remote->url; +} + +int git_remote_set_instance_url(git_remote *remote, const char *url) +{ + char *tmp; + + GIT_ASSERT_ARG(remote); + GIT_ASSERT_ARG(url); + + if ((tmp = git__strdup(url)) == NULL) + return -1; + + git__free(remote->url); + remote->url = tmp; + + return 0; +} + +static int set_url(git_repository *repo, const char *remote, const char *pattern, const char *url) +{ + git_config *cfg; + git_str buf = GIT_STR_INIT, canonical_url = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(remote); + + if ((error = ensure_remote_name_is_valid(remote)) < 0) + return error; + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + return error; + + if ((error = git_str_printf(&buf, pattern, remote)) < 0) + return error; + + if (url) { + if ((error = canonicalize_url(&canonical_url, url)) < 0) + goto cleanup; + + error = git_config_set_string(cfg, buf.ptr, url); + } else { + error = git_config_delete_entry(cfg, buf.ptr); + } + +cleanup: + git_str_dispose(&canonical_url); + git_str_dispose(&buf); + + return error; +} + +int git_remote_set_url(git_repository *repo, const char *remote, const char *url) +{ + return set_url(repo, remote, CONFIG_URL_FMT, url); +} + +const char *git_remote_pushurl(const git_remote *remote) +{ + GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); + return remote->pushurl; +} + +int git_remote_set_instance_pushurl(git_remote *remote, const char *url) +{ + char *tmp; + + GIT_ASSERT_ARG(remote); + GIT_ASSERT_ARG(url); + + if ((tmp = git__strdup(url)) == NULL) + return -1; + + git__free(remote->pushurl); + remote->pushurl = tmp; + + return 0; +} + +int git_remote_set_pushurl(git_repository *repo, const char *remote, const char *url) +{ + return set_url(repo, remote, CONFIG_PUSHURL_FMT, url); +} + +static int resolve_url( + git_str *resolved_url, + const char *url, + int direction, + const git_remote_callbacks *callbacks) +{ +#ifdef GIT_DEPRECATE_HARD + GIT_UNUSED(direction); + GIT_UNUSED(callbacks); +#else + git_buf buf = GIT_BUF_INIT; + int error; + + if (callbacks && callbacks->resolve_url) { + error = callbacks->resolve_url(&buf, url, direction, callbacks->payload); + + if (error != GIT_PASSTHROUGH) { + git_error_set_after_callback_function(error, "git_resolve_url_cb"); + + git_str_set(resolved_url, buf.ptr, buf.size); + git_buf_dispose(&buf); + + return error; + } + } +#endif + + return git_str_sets(resolved_url, url); +} + +int git_remote__urlfordirection( + git_str *url_out, + struct git_remote *remote, + int direction, + const git_remote_callbacks *callbacks) +{ + const char *url = NULL; + + GIT_ASSERT_ARG(remote); + GIT_ASSERT_ARG(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH); + + if (callbacks && callbacks->remote_ready) { + int status = callbacks->remote_ready(remote, direction, callbacks->payload); + + if (status != 0 && status != GIT_PASSTHROUGH) { + git_error_set_after_callback_function(status, "git_remote_ready_cb"); + return status; + } + } + + if (direction == GIT_DIRECTION_FETCH) + url = remote->url; + else if (direction == GIT_DIRECTION_PUSH) + url = remote->pushurl ? remote->pushurl : remote->url; + + if (!url) { + git_error_set(GIT_ERROR_INVALID, + "malformed remote '%s' - missing %s URL", + remote->name ? remote->name : "(anonymous)", + direction == GIT_DIRECTION_FETCH ? "fetch" : "push"); + return GIT_EINVALID; + } + + return resolve_url(url_out, url, direction, callbacks); +} + +int git_remote_connect_options_init( + git_remote_connect_options *opts, + unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_remote_connect_options, GIT_REMOTE_CONNECT_OPTIONS_INIT); + return 0; +} + +int git_remote_connect_options_dup( + git_remote_connect_options *dst, + const git_remote_connect_options *src) +{ + memcpy(dst, src, sizeof(git_remote_connect_options)); + + if (git_proxy_options_dup(&dst->proxy_opts, &src->proxy_opts) < 0 || + git_strarray_copy(&dst->custom_headers, &src->custom_headers) < 0) + return -1; + + return 0; +} + +void git_remote_connect_options_dispose(git_remote_connect_options *opts) +{ + if (!opts) + return; + + git_strarray_dispose(&opts->custom_headers); + git_proxy_options_dispose(&opts->proxy_opts); +} + +static size_t http_header_name_length(const char *http_header) +{ + const char *colon = strchr(http_header, ':'); + if (!colon) + return 0; + return colon - http_header; +} + +static bool is_malformed_http_header(const char *http_header) +{ + const char *c; + size_t name_len; + + /* Disallow \r and \n */ + if ((c = strchr(http_header, '\r')) != NULL) + return true; + if ((c = strchr(http_header, '\n')) != NULL) + return true; + + /* Require a header name followed by : */ + if ((name_len = http_header_name_length(http_header)) < 1) + return true; + + return false; +} + +static char *forbidden_custom_headers[] = { + "User-Agent", + "Host", + "Accept", + "Content-Type", + "Transfer-Encoding", + "Content-Length", +}; + +static bool is_forbidden_custom_header(const char *custom_header) +{ + unsigned long i; + size_t name_len = http_header_name_length(custom_header); + + /* Disallow headers that we set */ + for (i = 0; i < ARRAY_SIZE(forbidden_custom_headers); i++) + if (strncmp(forbidden_custom_headers[i], custom_header, name_len) == 0) + return true; + + return false; +} + +static int validate_custom_headers(const git_strarray *custom_headers) +{ + size_t i; + + if (!custom_headers) + return 0; + + for (i = 0; i < custom_headers->count; i++) { + if (is_malformed_http_header(custom_headers->strings[i])) { + git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is malformed", custom_headers->strings[i]); + return -1; + } + + if (is_forbidden_custom_header(custom_headers->strings[i])) { + git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is already set by libgit2", custom_headers->strings[i]); + return -1; + } + } + + return 0; +} + +static int lookup_redirect_config( + git_remote_redirect_t *out, + git_repository *repo) +{ + git_config *config; + const char *value; + int bool_value, error = 0; + + if (!repo) { + *out = GIT_REMOTE_REDIRECT_INITIAL; + return 0; + } + + if ((error = git_repository_config_snapshot(&config, repo)) < 0) + goto done; + + if ((error = git_config_get_string(&value, config, "http.followRedirects")) < 0) { + if (error == GIT_ENOTFOUND) { + *out = GIT_REMOTE_REDIRECT_INITIAL; + error = 0; + } + + goto done; + } + + if (git_config_parse_bool(&bool_value, value) == 0) { + *out = bool_value ? GIT_REMOTE_REDIRECT_ALL : + GIT_REMOTE_REDIRECT_NONE; + } else if (strcasecmp(value, "initial") == 0) { + *out = GIT_REMOTE_REDIRECT_INITIAL; + } else { + git_error_set(GIT_ERROR_CONFIG, "invalid configuration setting '%s' for 'http.followRedirects'", value); + error = -1; + } + +done: + git_config_free(config); + return error; +} + +int git_remote_connect_options_normalize( + git_remote_connect_options *dst, + git_repository *repo, + const git_remote_connect_options *src) +{ + git_remote_connect_options_dispose(dst); + git_remote_connect_options_init(dst, GIT_REMOTE_CONNECT_OPTIONS_VERSION); + + if (src) { + GIT_ERROR_CHECK_VERSION(src, GIT_REMOTE_CONNECT_OPTIONS_VERSION, "git_remote_connect_options"); + GIT_ERROR_CHECK_VERSION(&src->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); + GIT_ERROR_CHECK_VERSION(&src->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); + + if (validate_custom_headers(&src->custom_headers) < 0 || + git_remote_connect_options_dup(dst, src) < 0) + return -1; + } + + if (dst->follow_redirects == 0) { + if (lookup_redirect_config(&dst->follow_redirects, repo) < 0) + return -1; + } + + return 0; +} + +int git_remote_connect_ext( + git_remote *remote, + git_direction direction, + const git_remote_connect_options *given_opts) +{ + git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + git_str url = GIT_STR_INIT; + git_transport *t; + int error; + + GIT_ASSERT_ARG(remote); + + if (given_opts) + memcpy(&opts, given_opts, sizeof(git_remote_connect_options)); + + GIT_ERROR_CHECK_VERSION(&opts.callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); + GIT_ERROR_CHECK_VERSION(&opts.proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); + + t = remote->transport; + + if ((error = git_remote__urlfordirection(&url, remote, direction, &opts.callbacks)) < 0) + goto on_error; + + /* If we don't have a transport object yet, and the caller specified a + * custom transport factory, use that */ + if (!t && opts.callbacks.transport && + (error = opts.callbacks.transport(&t, remote, opts.callbacks.payload)) < 0) + goto on_error; + + /* If we still don't have a transport, then use the global + * transport registrations which map URI schemes to transport factories */ + if (!t && (error = git_transport_new(&t, remote, url.ptr)) < 0) + goto on_error; + + if ((error = t->connect(t, url.ptr, direction, &opts)) != 0) + goto on_error; + + remote->transport = t; + + git_str_dispose(&url); + + return 0; + +on_error: + if (t) + t->free(t); + + git_str_dispose(&url); + + if (t == remote->transport) + remote->transport = NULL; + + return error; +} + +int git_remote_connect( + git_remote *remote, + git_direction direction, + const git_remote_callbacks *callbacks, + const git_proxy_options *proxy, + const git_strarray *custom_headers) +{ + git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + + if (callbacks) + memcpy(&opts.callbacks, callbacks, sizeof(git_remote_callbacks)); + + if (proxy) + memcpy(&opts.proxy_opts, proxy, sizeof(git_proxy_options)); + + if (custom_headers) + memcpy(&opts.custom_headers, custom_headers, sizeof(git_strarray)); + + return git_remote_connect_ext(remote, direction, &opts); +} + +int git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote) +{ + GIT_ASSERT_ARG(remote); + + if (!remote->transport) { + git_error_set(GIT_ERROR_NET, "this remote has never connected"); + return -1; + } + + return remote->transport->ls(out, size, remote->transport); +} + +int git_remote_capabilities(unsigned int *out, git_remote *remote) +{ + GIT_ASSERT_ARG(remote); + + *out = 0; + + if (!remote->transport) { + git_error_set(GIT_ERROR_NET, "this remote has never connected"); + return -1; + } + + return remote->transport->capabilities(out, remote->transport); +} + +static int lookup_config(char **out, git_config *cfg, const char *name) +{ + git_config_entry *ce = NULL; + int error; + + if ((error = git_config__lookup_entry(&ce, cfg, name, false)) < 0) + return error; + + if (ce && ce->value) { + *out = git__strdup(ce->value); + GIT_ERROR_CHECK_ALLOC(*out); + } else { + error = GIT_ENOTFOUND; + } + + git_config_entry_free(ce); + return error; +} + +static void url_config_trim(git_net_url *url) +{ + size_t len = strlen(url->path); + + if (url->path[len - 1] == '/') { + len--; + } else { + while (len && url->path[len - 1] != '/') + len--; + } + + url->path[len] = '\0'; +} + +static int http_proxy_config(char **out, git_remote *remote, git_net_url *url) +{ + git_config *cfg = NULL; + git_str buf = GIT_STR_INIT; + git_net_url lookup_url = GIT_NET_URL_INIT; + int error; + + if ((error = git_net_url_dup(&lookup_url, url)) < 0) + goto done; + + if (remote->repo) { + if ((error = git_repository_config(&cfg, remote->repo)) < 0) + goto done; + } else { + if ((error = git_config_open_default(&cfg)) < 0) + goto done; + } + + /* remote..proxy config setting */ + if (remote->name && remote->name[0]) { + git_str_clear(&buf); + + if ((error = git_str_printf(&buf, "remote.%s.proxy", remote->name)) < 0 || + (error = lookup_config(out, cfg, buf.ptr)) != GIT_ENOTFOUND) + goto done; + } + + while (true) { + git_str_clear(&buf); + + if ((error = git_str_puts(&buf, "http.")) < 0 || + (error = git_net_url_fmt(&buf, &lookup_url)) < 0 || + (error = git_str_puts(&buf, ".proxy")) < 0 || + (error = lookup_config(out, cfg, buf.ptr)) != GIT_ENOTFOUND) + goto done; + + if (! lookup_url.path[0]) + break; + + url_config_trim(&lookup_url); + } + + git_str_clear(&buf); + + error = lookup_config(out, cfg, "http.proxy"); + +done: + git_config_free(cfg); + git_str_dispose(&buf); + git_net_url_dispose(&lookup_url); + return error; +} + +static int http_proxy_env(char **out, git_remote *remote, git_net_url *url) +{ + git_str proxy_env = GIT_STR_INIT, no_proxy_env = GIT_STR_INIT; + bool use_ssl = (strcmp(url->scheme, "https") == 0); + int error; + + GIT_UNUSED(remote); + + /* http_proxy / https_proxy environment variables */ + error = git__getenv(&proxy_env, use_ssl ? "https_proxy" : "http_proxy"); + + /* try uppercase environment variables */ + if (error == GIT_ENOTFOUND) + error = git__getenv(&proxy_env, use_ssl ? "HTTPS_PROXY" : "HTTP_PROXY"); + + if (error) + goto done; + + /* no_proxy/NO_PROXY environment variables */ + error = git__getenv(&no_proxy_env, "no_proxy"); + + if (error == GIT_ENOTFOUND) + error = git__getenv(&no_proxy_env, "NO_PROXY"); + + if (error && error != GIT_ENOTFOUND) + goto done; + + if (!git_net_url_matches_pattern_list(url, no_proxy_env.ptr)) + *out = git_str_detach(&proxy_env); + else + error = GIT_ENOTFOUND; + +done: + git_str_dispose(&proxy_env); + git_str_dispose(&no_proxy_env); + return error; +} + +int git_remote__http_proxy(char **out, git_remote *remote, git_net_url *url) +{ + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(remote); + + *out = NULL; + + /* + * Go through the possible sources for proxy configuration, + * Examine the various git config options first, then + * consult environment variables. + */ + if ((error = http_proxy_config(out, remote, url)) != GIT_ENOTFOUND || + (error = http_proxy_env(out, remote, url)) != GIT_ENOTFOUND) + return error; + + return 0; +} + +/* DWIM `refspecs` based on `refs` and append the output to `out` */ +static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs) +{ + size_t i; + git_refspec *spec; + + git_vector_foreach(refspecs, i, spec) { + if (git_refspec__dwim_one(out, spec, refs) < 0) + return -1; + } + + return 0; +} + +static void free_refspecs(git_vector *vec) +{ + size_t i; + git_refspec *spec; + + git_vector_foreach(vec, i, spec) { + git_refspec__dispose(spec); + git__free(spec); + } + + git_vector_clear(vec); +} + +static int remote_head_cmp(const void *_a, const void *_b) +{ + const git_remote_head *a = (git_remote_head *) _a; + const git_remote_head *b = (git_remote_head *) _b; + + return git__strcmp_cb(a->name, b->name); +} + +static int ls_to_vector(git_vector *out, git_remote *remote) +{ + git_remote_head **heads; + size_t heads_len, i; + + if (git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote) < 0) + return -1; + + if (git_vector_init(out, heads_len, remote_head_cmp) < 0) + return -1; + + for (i = 0; i < heads_len; i++) { + if (git_vector_insert(out, heads[i]) < 0) + return -1; + } + + return 0; +} + +#define copy_opts(out, in) \ + if (in) { \ + (out)->callbacks = (in)->callbacks; \ + (out)->proxy_opts = (in)->proxy_opts; \ + (out)->custom_headers = (in)->custom_headers; \ + (out)->follow_redirects = (in)->follow_redirects; \ + } + +GIT_INLINE(int) connect_opts_from_fetch_opts( + git_remote_connect_options *out, + git_remote *remote, + const git_fetch_options *fetch_opts) +{ + git_remote_connect_options tmp = GIT_REMOTE_CONNECT_OPTIONS_INIT; + copy_opts(&tmp, fetch_opts); + return git_remote_connect_options_normalize(out, remote->repo, &tmp); +} + +static int connect_or_reset_options( + git_remote *remote, + int direction, + git_remote_connect_options *opts) +{ + if (!git_remote_connected(remote)) { + return git_remote_connect_ext(remote, direction, opts); + } else { + return remote->transport->set_connect_opts(remote->transport, opts); + } +} + +/* Download from an already connected remote. */ +static int git_remote__download( + git_remote *remote, + const git_strarray *refspecs, + const git_fetch_options *opts) +{ + git_vector *to_active, specs = GIT_VECTOR_INIT, refs = GIT_VECTOR_INIT; + size_t i; + int error; + + if (ls_to_vector(&refs, remote) < 0) + return -1; + + if ((error = git_vector_init(&specs, 0, NULL)) < 0) + goto on_error; + + remote->passed_refspecs = 0; + if (!refspecs || !refspecs->count) { + to_active = &remote->refspecs; + } else { + for (i = 0; i < refspecs->count; i++) { + if ((error = add_refspec_to(&specs, refspecs->strings[i], true)) < 0) + goto on_error; + } + + to_active = &specs; + remote->passed_refspecs = 1; + } + + free_refspecs(&remote->passive_refspecs); + if ((error = dwim_refspecs(&remote->passive_refspecs, &remote->refspecs, &refs)) < 0) + goto on_error; + + free_refspecs(&remote->active_refspecs); + error = dwim_refspecs(&remote->active_refspecs, to_active, &refs); + + git_vector_free(&refs); + free_refspecs(&specs); + git_vector_free(&specs); + + if (error < 0) + goto on_error; + + if (remote->push) { + git_push_free(remote->push); + remote->push = NULL; + } + + if ((error = git_fetch_negotiate(remote, opts)) < 0) + goto on_error; + + error = git_fetch_download_pack(remote); + +on_error: + git_vector_free(&refs); + free_refspecs(&specs); + git_vector_free(&specs); + return error; +} + +int git_remote_download( + git_remote *remote, + const git_strarray *refspecs, + const git_fetch_options *opts) +{ + git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + int error; + + GIT_ASSERT_ARG(remote); + + if (!remote->repo) { + git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); + return -1; + } + + if (connect_opts_from_fetch_opts(&connect_opts, remote, opts) < 0) + return -1; + + if ((error = connect_or_reset_options(remote, GIT_DIRECTION_FETCH, &connect_opts)) < 0) + return error; + + return git_remote__download(remote, refspecs, opts); +} + +int git_remote_fetch( + git_remote *remote, + const git_strarray *refspecs, + const git_fetch_options *opts, + const char *reflog_message) +{ + int error, update_fetchhead = 1; + git_remote_autotag_option_t tagopt = remote->download_tags; + bool prune = false; + git_str reflog_msg_buf = GIT_STR_INIT; + git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + + GIT_ASSERT_ARG(remote); + + if (!remote->repo) { + git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); + return -1; + } + + if (connect_opts_from_fetch_opts(&connect_opts, remote, opts) < 0) + return -1; + + if ((error = connect_or_reset_options(remote, GIT_DIRECTION_FETCH, &connect_opts)) < 0) + return error; + + if (opts) { + update_fetchhead = opts->update_fetchhead; + tagopt = opts->download_tags; + } + + /* Connect and download everything */ + error = git_remote__download(remote, refspecs, opts); + + /* We don't need to be connected anymore */ + git_remote_disconnect(remote); + + /* If the download failed, return the error */ + if (error != 0) + goto done; + + /* Default reflog message */ + if (reflog_message) + git_str_sets(&reflog_msg_buf, reflog_message); + else { + git_str_printf(&reflog_msg_buf, "fetch %s", + remote->name ? remote->name : remote->url); + } + + /* Create "remote/foo" branches for all remote branches */ + error = git_remote_update_tips(remote, &connect_opts.callbacks, update_fetchhead, tagopt, git_str_cstr(&reflog_msg_buf)); + git_str_dispose(&reflog_msg_buf); + if (error < 0) + goto done; + + if (opts && opts->prune == GIT_FETCH_PRUNE) + prune = true; + else if (opts && opts->prune == GIT_FETCH_PRUNE_UNSPECIFIED && remote->prune_refs) + prune = true; + else if (opts && opts->prune == GIT_FETCH_NO_PRUNE) + prune = false; + else + prune = remote->prune_refs; + + if (prune) + error = git_remote_prune(remote, &connect_opts.callbacks); + +done: + git_remote_connect_options_dispose(&connect_opts); + return error; +} + +static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src) +{ + unsigned int i; + git_remote_head *remote_ref; + + GIT_ASSERT_ARG(update_heads); + GIT_ASSERT_ARG(fetchspec_src); + + *out = NULL; + + git_vector_foreach(update_heads, i, remote_ref) { + if (strcmp(remote_ref->name, fetchspec_src) == 0) { + *out = remote_ref; + break; + } + } + + return 0; +} + +static int ref_to_update(int *update, git_str *remote_name, git_remote *remote, git_refspec *spec, const char *ref_name) +{ + int error = 0; + git_repository *repo; + git_str upstream_remote = GIT_STR_INIT; + git_str upstream_name = GIT_STR_INIT; + + repo = git_remote_owner(remote); + + if ((!git_reference__is_branch(ref_name)) || + !git_remote_name(remote) || + (error = git_branch__upstream_remote(&upstream_remote, repo, ref_name) < 0) || + git__strcmp(git_remote_name(remote), git_str_cstr(&upstream_remote)) || + (error = git_branch__upstream_name(&upstream_name, repo, ref_name)) < 0 || + !git_refspec_dst_matches(spec, git_str_cstr(&upstream_name)) || + (error = git_refspec__rtransform(remote_name, spec, upstream_name.ptr)) < 0) { + /* Not an error if there is no upstream */ + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + *update = 0; + } else { + *update = 1; + } + + git_str_dispose(&upstream_remote); + git_str_dispose(&upstream_name); + return error; +} + +static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_refspec *spec, git_vector *update_heads, git_reference *ref) +{ + git_reference *resolved_ref = NULL; + git_str remote_name = GIT_STR_INIT; + git_config *config = NULL; + const char *ref_name; + int error = 0, update; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(spec); + GIT_ASSERT_ARG(ref); + + *out = NULL; + + error = git_reference_resolve(&resolved_ref, ref); + + /* If we're in an unborn branch, let's pretend nothing happened */ + if (error == GIT_ENOTFOUND && git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { + ref_name = git_reference_symbolic_target(ref); + error = 0; + } else { + ref_name = git_reference_name(resolved_ref); + } + + /* + * The ref name may be unresolvable - perhaps it's pointing to + * something invalid. In this case, there is no remote head for + * this ref. + */ + if (!ref_name) { + error = 0; + goto cleanup; + } + + if ((error = ref_to_update(&update, &remote_name, remote, spec, ref_name)) < 0) + goto cleanup; + + if (update) + error = remote_head_for_fetchspec_src(out, update_heads, git_str_cstr(&remote_name)); + +cleanup: + git_str_dispose(&remote_name); + git_reference_free(resolved_ref); + git_config_free(config); + return error; +} + +static int git_remote_write_fetchhead(git_remote *remote, git_refspec *spec, git_vector *update_heads) +{ + git_reference *head_ref = NULL; + git_fetchhead_ref *fetchhead_ref; + git_remote_head *remote_ref, *merge_remote_ref; + git_vector fetchhead_refs; + bool include_all_fetchheads; + unsigned int i = 0; + int error = 0; + + GIT_ASSERT_ARG(remote); + + /* no heads, nothing to do */ + if (update_heads->length == 0) + return 0; + + if (git_vector_init(&fetchhead_refs, update_heads->length, git_fetchhead_ref_cmp) < 0) + return -1; + + /* Iff refspec is * (but not subdir slash star), include tags */ + include_all_fetchheads = (strcmp(GIT_REFS_HEADS_DIR "*", git_refspec_src(spec)) == 0); + + /* Determine what to merge: if refspec was a wildcard, just use HEAD */ + if (git_refspec_is_wildcard(spec)) { + if ((error = git_reference_lookup(&head_ref, remote->repo, GIT_HEAD_FILE)) < 0 || + (error = remote_head_for_ref(&merge_remote_ref, remote, spec, update_heads, head_ref)) < 0) + goto cleanup; + } else { + /* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */ + if ((error = remote_head_for_fetchspec_src(&merge_remote_ref, update_heads, git_refspec_src(spec))) < 0) + goto cleanup; + } + + /* Create the FETCH_HEAD file */ + git_vector_foreach(update_heads, i, remote_ref) { + int merge_this_fetchhead = (merge_remote_ref == remote_ref); + + if (!include_all_fetchheads && + !git_refspec_src_matches(spec, remote_ref->name) && + !merge_this_fetchhead) + continue; + + if (git_fetchhead_ref_create(&fetchhead_ref, + &remote_ref->oid, + merge_this_fetchhead, + remote_ref->name, + git_remote_url(remote)) < 0) + goto cleanup; + + if (git_vector_insert(&fetchhead_refs, fetchhead_ref) < 0) + goto cleanup; + } + + git_fetchhead_write(remote->repo, &fetchhead_refs); + +cleanup: + for (i = 0; i < fetchhead_refs.length; ++i) + git_fetchhead_ref_free(fetchhead_refs.contents[i]); + + git_vector_free(&fetchhead_refs); + git_reference_free(head_ref); + + return error; +} + +/** + * Generate a list of candidates for pruning by getting a list of + * references which match the rhs of an active refspec. + */ +static int prune_candidates(git_vector *candidates, git_remote *remote) +{ + git_strarray arr = { 0 }; + size_t i; + int error; + + if ((error = git_reference_list(&arr, remote->repo)) < 0) + return error; + + for (i = 0; i < arr.count; i++) { + const char *refname = arr.strings[i]; + char *refname_dup; + + if (!git_remote__matching_dst_refspec(remote, refname)) + continue; + + refname_dup = git__strdup(refname); + GIT_ERROR_CHECK_ALLOC(refname_dup); + + if ((error = git_vector_insert(candidates, refname_dup)) < 0) + goto out; + } + +out: + git_strarray_dispose(&arr); + return error; +} + +static int find_head(const void *_a, const void *_b) +{ + git_remote_head *a = (git_remote_head *) _a; + git_remote_head *b = (git_remote_head *) _b; + + return strcmp(a->name, b->name); +} + +int git_remote_prune(git_remote *remote, const git_remote_callbacks *callbacks) +{ + size_t i, j; + git_vector remote_refs = GIT_VECTOR_INIT; + git_vector candidates = GIT_VECTOR_INIT; + const git_refspec *spec; + const char *refname; + int error; + git_oid zero_id = {{ 0 }}; + + if (callbacks) + GIT_ERROR_CHECK_VERSION(callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); + + if ((error = ls_to_vector(&remote_refs, remote)) < 0) + goto cleanup; + + git_vector_set_cmp(&remote_refs, find_head); + + if ((error = prune_candidates(&candidates, remote)) < 0) + goto cleanup; + + /* + * Remove those entries from the candidate list for which we + * can find a remote reference in at least one refspec. + */ + git_vector_foreach(&candidates, i, refname) { + git_vector_foreach(&remote->active_refspecs, j, spec) { + git_str buf = GIT_STR_INIT; + size_t pos; + char *src_name; + git_remote_head key = {0}; + + if (!git_refspec_dst_matches(spec, refname)) + continue; + + if ((error = git_refspec__rtransform(&buf, spec, refname)) < 0) + goto cleanup; + + key.name = (char *) git_str_cstr(&buf); + error = git_vector_bsearch(&pos, &remote_refs, &key); + git_str_dispose(&buf); + + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if (error == GIT_ENOTFOUND) + continue; + + /* If we did find a source, remove it from the candidates. */ + if ((error = git_vector_set((void **) &src_name, &candidates, i, NULL)) < 0) + goto cleanup; + + git__free(src_name); + break; + } + } + + /* + * For those candidates still left in the list, we need to + * remove them. We do not remove symrefs, as those are for + * stuff like origin/HEAD which will never match, but we do + * not want to remove them. + */ + git_vector_foreach(&candidates, i, refname) { + git_reference *ref; + git_oid id; + + if (refname == NULL) + continue; + + error = git_reference_lookup(&ref, remote->repo, refname); + /* as we want it gone, let's not consider this an error */ + if (error == GIT_ENOTFOUND) + continue; + + if (error < 0) + goto cleanup; + + if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { + git_reference_free(ref); + continue; + } + + git_oid_cpy(&id, git_reference_target(ref)); + error = git_reference_delete(ref); + git_reference_free(ref); + if (error < 0) + goto cleanup; + + if (callbacks && callbacks->update_tips) + error = callbacks->update_tips(refname, &id, &zero_id, callbacks->payload); + + if (error < 0) + goto cleanup; + } + +cleanup: + git_vector_free(&remote_refs); + git_vector_free_deep(&candidates); + return error; +} + +static int update_ref( + const git_remote *remote, + const char *ref_name, + git_oid *id, + const char *msg, + const git_remote_callbacks *callbacks) +{ + git_reference *ref; + git_oid old_id; + int error; + + error = git_reference_name_to_id(&old_id, remote->repo, ref_name); + + if (error < 0 && error != GIT_ENOTFOUND) + return error; + else if (error == 0 && git_oid_equal(&old_id, id)) + return 0; + + /* If we did find a current reference, make sure we haven't lost a race */ + if (error) + error = git_reference_create(&ref, remote->repo, ref_name, id, true, msg); + else + error = git_reference_create_matching(&ref, remote->repo, ref_name, id, true, &old_id, msg); + + git_reference_free(ref); + + if (error < 0) + return error; + + if (callbacks && callbacks->update_tips && + (error = callbacks->update_tips(ref_name, &old_id, id, callbacks->payload)) < 0) + return error; + + return 0; +} + +static int update_one_tip( + git_vector *update_heads, + git_remote *remote, + git_refspec *spec, + git_remote_head *head, + git_refspec *tagspec, + git_remote_autotag_option_t tagopt, + const char *log_message, + const git_remote_callbacks *callbacks) +{ + git_odb *odb; + git_str refname = GIT_STR_INIT; + git_reference *ref = NULL; + bool autotag = false; + git_oid old; + int valid; + int error; + + if ((error = git_repository_odb__weakptr(&odb, remote->repo)) < 0) + goto done; + + /* Ignore malformed ref names (which also saves us from tag^{} */ + if ((error = git_reference_name_is_valid(&valid, head->name)) < 0) + goto done; + + if (!valid) + goto done; + + /* If we have a tag, see if the auto-follow rules say to update it */ + if (git_refspec_src_matches(tagspec, head->name)) { + if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_AUTO) + autotag = true; + + if (tagopt != GIT_REMOTE_DOWNLOAD_TAGS_NONE) { + if (git_str_puts(&refname, head->name) < 0) + goto done; + } + } + + /* If we didn't want to auto-follow the tag, check if the refspec matches */ + if (!autotag && git_refspec_src_matches(spec, head->name)) { + if (spec->dst) { + if ((error = git_refspec__transform(&refname, spec, head->name)) < 0) + goto done; + } else { + /* + * no rhs means store it in FETCH_HEAD, even if we don't + * update anything else. + */ + error = git_vector_insert(update_heads, head); + goto done; + } + } + + /* If we still don't have a refname, we don't want it */ + if (git_str_len(&refname) == 0) + goto done; + + /* In autotag mode, only create tags for objects already in db */ + if (autotag && !git_odb_exists(odb, &head->oid)) + goto done; + + if (!autotag && (error = git_vector_insert(update_heads, head)) < 0) + goto done; + + error = git_reference_name_to_id(&old, remote->repo, refname.ptr); + + if (error < 0 && error != GIT_ENOTFOUND) + goto done; + + if (!(error || error == GIT_ENOTFOUND) && + !spec->force && + !git_graph_descendant_of(remote->repo, &head->oid, &old)) { + error = 0; + goto done; + } + + if (error == GIT_ENOTFOUND) { + memset(&old, 0, GIT_OID_RAWSZ); + error = 0; + + if (autotag && (error = git_vector_insert(update_heads, head)) < 0) + goto done; + } + + if (!git_oid__cmp(&old, &head->oid)) + goto done; + + /* In autotag mode, don't overwrite any locally-existing tags */ + error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag, + log_message); + + if (error < 0) { + if (error == GIT_EEXISTS) + error = 0; + + goto done; + } + + if (callbacks && callbacks->update_tips != NULL && + (error = callbacks->update_tips(refname.ptr, &old, &head->oid, callbacks->payload)) < 0) + git_error_set_after_callback_function(error, "git_remote_fetch"); + +done: + git_reference_free(ref); + git_str_dispose(&refname); + return error; +} + +static int update_tips_for_spec( + git_remote *remote, + const git_remote_callbacks *callbacks, + int update_fetchhead, + git_remote_autotag_option_t tagopt, + git_refspec *spec, + git_vector *refs, + const char *log_message) +{ + git_refspec tagspec; + git_remote_head *head, oid_head; + git_vector update_heads; + int error = 0; + size_t i; + + GIT_ASSERT_ARG(remote); + + if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) + return -1; + + /* Make a copy of the transport's refs */ + if (git_vector_init(&update_heads, 16, NULL) < 0) + return -1; + + /* Update tips based on the remote heads */ + git_vector_foreach(refs, i, head) { + if (update_one_tip(&update_heads, remote, spec, head, &tagspec, tagopt, log_message, callbacks) < 0) + goto on_error; + } + + /* Handle specified oid sources */ + if (git_oid__is_hexstr(spec->src)) { + git_oid id; + + if ((error = git_oid_fromstr(&id, spec->src)) < 0 || + (error = update_ref(remote, spec->dst, &id, log_message, callbacks)) < 0) + goto on_error; + + git_oid_cpy(&oid_head.oid, &id); + oid_head.name = spec->src; + + if ((error = git_vector_insert(&update_heads, &oid_head)) < 0) + goto on_error; + } + + if (update_fetchhead && + (error = git_remote_write_fetchhead(remote, spec, &update_heads)) < 0) + goto on_error; + + git_refspec__dispose(&tagspec); + git_vector_free(&update_heads); + return 0; + +on_error: + git_refspec__dispose(&tagspec); + git_vector_free(&update_heads); + return -1; + +} + +/** + * Iteration over the three vectors, with a pause whenever we find a match + * + * On each stop, we store the iteration stat in the inout i,j,k + * parameters, and return the currently matching passive refspec as + * well as the head which we matched. + */ +static int next_head(const git_remote *remote, git_vector *refs, + git_refspec **out_spec, git_remote_head **out_head, + size_t *out_i, size_t *out_j, size_t *out_k) +{ + const git_vector *active, *passive; + git_remote_head *head; + git_refspec *spec, *passive_spec; + size_t i, j, k; + int valid; + + active = &remote->active_refspecs; + passive = &remote->passive_refspecs; + + i = *out_i; + j = *out_j; + k = *out_k; + + for (; i < refs->length; i++) { + head = git_vector_get(refs, i); + + if (git_reference_name_is_valid(&valid, head->name) < 0) + return -1; + + if (!valid) + continue; + + for (; j < active->length; j++) { + spec = git_vector_get(active, j); + + if (!git_refspec_src_matches(spec, head->name)) + continue; + + for (; k < passive->length; k++) { + passive_spec = git_vector_get(passive, k); + + if (!git_refspec_src_matches(passive_spec, head->name)) + continue; + + *out_spec = passive_spec; + *out_head = head; + *out_i = i; + *out_j = j; + *out_k = k + 1; + return 0; + + } + k = 0; + } + j = 0; + } + + return GIT_ITEROVER; +} + +static int opportunistic_updates( + const git_remote *remote, + const git_remote_callbacks *callbacks, + git_vector *refs, + const char *msg) +{ + size_t i, j, k; + git_refspec *spec; + git_remote_head *head; + git_str refname = GIT_STR_INIT; + int error = 0; + + i = j = k = 0; + + /* Handle refspecs matching remote heads */ + while ((error = next_head(remote, refs, &spec, &head, &i, &j, &k)) == 0) { + /* + * If we got here, there is a refspec which was used + * for fetching which matches the source of one of the + * passive refspecs, so we should update that + * remote-tracking branch, but not add it to + * FETCH_HEAD + */ + + git_str_clear(&refname); + if ((error = git_refspec__transform(&refname, spec, head->name)) < 0 || + (error = update_ref(remote, refname.ptr, &head->oid, msg, callbacks)) < 0) + goto cleanup; + } + + if (error != GIT_ITEROVER) + goto cleanup; + + error = 0; + +cleanup: + git_str_dispose(&refname); + return error; +} + +static int truncate_fetch_head(const char *gitdir) +{ + git_str path = GIT_STR_INIT; + int error; + + if ((error = git_str_joinpath(&path, gitdir, GIT_FETCH_HEAD_FILE)) < 0) + return error; + + error = git_futils_truncate(path.ptr, GIT_REFS_FILE_MODE); + git_str_dispose(&path); + + return error; +} + +int git_remote_update_tips( + git_remote *remote, + const git_remote_callbacks *callbacks, + int update_fetchhead, + git_remote_autotag_option_t download_tags, + const char *reflog_message) +{ + git_refspec *spec, tagspec; + git_vector refs = GIT_VECTOR_INIT; + git_remote_autotag_option_t tagopt; + int error; + size_t i; + + /* push has its own logic hidden away in the push object */ + if (remote->push) { + return git_push_update_tips(remote->push, callbacks); + } + + if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) + return -1; + + + if ((error = ls_to_vector(&refs, remote)) < 0) + goto out; + + if (download_tags == GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED) + tagopt = remote->download_tags; + else + tagopt = download_tags; + + if ((error = truncate_fetch_head(git_repository_path(remote->repo))) < 0) + goto out; + + if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { + if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, &tagspec, &refs, reflog_message)) < 0) + goto out; + } + + git_vector_foreach(&remote->active_refspecs, i, spec) { + if (spec->push) + continue; + + if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, spec, &refs, reflog_message)) < 0) + goto out; + } + + /* Only try to do opportunistic updates if the refspec lists differ. */ + if (remote->passed_refspecs) + error = opportunistic_updates(remote, callbacks, &refs, reflog_message); + +out: + git_vector_free(&refs); + git_refspec__dispose(&tagspec); + return error; +} + +int git_remote_connected(const git_remote *remote) +{ + GIT_ASSERT_ARG(remote); + + if (!remote->transport || !remote->transport->is_connected) + return 0; + + /* Ask the transport if it's connected. */ + return remote->transport->is_connected(remote->transport); +} + +int git_remote_stop(git_remote *remote) +{ + GIT_ASSERT_ARG(remote); + + if (remote->transport && remote->transport->cancel) + remote->transport->cancel(remote->transport); + + return 0; +} + +int git_remote_disconnect(git_remote *remote) +{ + GIT_ASSERT_ARG(remote); + + if (git_remote_connected(remote)) + remote->transport->close(remote->transport); + + return 0; +} + +static void free_heads(git_vector *heads) +{ + git_remote_head *head; + size_t i; + + git_vector_foreach(heads, i, head) { + git__free(head->name); + git__free(head); + } +} + +void git_remote_free(git_remote *remote) +{ + if (remote == NULL) + return; + + if (remote->transport != NULL) { + git_remote_disconnect(remote); + + remote->transport->free(remote->transport); + remote->transport = NULL; + } + + git_vector_free(&remote->refs); + + free_refspecs(&remote->refspecs); + git_vector_free(&remote->refspecs); + + free_refspecs(&remote->active_refspecs); + git_vector_free(&remote->active_refspecs); + + free_refspecs(&remote->passive_refspecs); + git_vector_free(&remote->passive_refspecs); + + free_heads(&remote->local_heads); + git_vector_free(&remote->local_heads); + + git_push_free(remote->push); + git__free(remote->url); + git__free(remote->pushurl); + git__free(remote->name); + git__free(remote); +} + +static int remote_list_cb(const git_config_entry *entry, void *payload) +{ + git_vector *list = payload; + const char *name = entry->name + strlen("remote."); + size_t namelen = strlen(name); + char *remote_name; + + /* we know name matches "remote..(push)?url" */ + + if (!strcmp(&name[namelen - 4], ".url")) + remote_name = git__strndup(name, namelen - 4); /* strip ".url" */ + else + remote_name = git__strndup(name, namelen - 8); /* strip ".pushurl" */ + GIT_ERROR_CHECK_ALLOC(remote_name); + + return git_vector_insert(list, remote_name); +} + +int git_remote_list(git_strarray *remotes_list, git_repository *repo) +{ + int error; + git_config *cfg; + git_vector list = GIT_VECTOR_INIT; + + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + return error; + + if ((error = git_vector_init(&list, 4, git__strcmp_cb)) < 0) + return error; + + error = git_config_foreach_match( + cfg, "^remote\\..*\\.(push)?url$", remote_list_cb, &list); + + if (error < 0) { + git_vector_free_deep(&list); + return error; + } + + git_vector_uniq(&list, git__free); + + remotes_list->strings = + (char **)git_vector_detach(&remotes_list->count, NULL, &list); + + return 0; +} + +const git_indexer_progress *git_remote_stats(git_remote *remote) +{ + GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); + return &remote->stats; +} + +git_remote_autotag_option_t git_remote_autotag(const git_remote *remote) +{ + return remote->download_tags; +} + +int git_remote_set_autotag(git_repository *repo, const char *remote, git_remote_autotag_option_t value) +{ + git_str var = GIT_STR_INIT; + git_config *config; + int error; + + GIT_ASSERT_ARG(repo && remote); + + if ((error = ensure_remote_name_is_valid(remote)) < 0) + return error; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + return error; + + if ((error = git_str_printf(&var, CONFIG_TAGOPT_FMT, remote))) + return error; + + switch (value) { + case GIT_REMOTE_DOWNLOAD_TAGS_NONE: + error = git_config_set_string(config, var.ptr, "--no-tags"); + break; + case GIT_REMOTE_DOWNLOAD_TAGS_ALL: + error = git_config_set_string(config, var.ptr, "--tags"); + break; + case GIT_REMOTE_DOWNLOAD_TAGS_AUTO: + error = git_config_delete_entry(config, var.ptr); + if (error == GIT_ENOTFOUND) + error = 0; + break; + default: + git_error_set(GIT_ERROR_INVALID, "invalid value for the tagopt setting"); + error = -1; + } + + git_str_dispose(&var); + return error; +} + +int git_remote_prune_refs(const git_remote *remote) +{ + return remote->prune_refs; +} + +static int rename_remote_config_section( + git_repository *repo, + const char *old_name, + const char *new_name) +{ + git_str old_section_name = GIT_STR_INIT, + new_section_name = GIT_STR_INIT; + int error = -1; + + if (git_str_printf(&old_section_name, "remote.%s", old_name) < 0) + goto cleanup; + + if (new_name && + (git_str_printf(&new_section_name, "remote.%s", new_name) < 0)) + goto cleanup; + + error = git_config_rename_section( + repo, + git_str_cstr(&old_section_name), + new_name ? git_str_cstr(&new_section_name) : NULL); + +cleanup: + git_str_dispose(&old_section_name); + git_str_dispose(&new_section_name); + + return error; +} + +struct update_data { + git_config *config; + const char *old_remote_name; + const char *new_remote_name; +}; + +static int update_config_entries_cb( + const git_config_entry *entry, + void *payload) +{ + struct update_data *data = (struct update_data *)payload; + + if (strcmp(entry->value, data->old_remote_name)) + return 0; + + return git_config_set_string( + data->config, entry->name, data->new_remote_name); +} + +static int update_branch_remote_config_entry( + git_repository *repo, + const char *old_name, + const char *new_name) +{ + int error; + struct update_data data = { NULL }; + + if ((error = git_repository_config__weakptr(&data.config, repo)) < 0) + return error; + + data.old_remote_name = old_name; + data.new_remote_name = new_name; + + return git_config_foreach_match( + data.config, "branch\\..+\\.remote", update_config_entries_cb, &data); +} + +static int rename_one_remote_reference( + git_reference *reference_in, + const char *old_remote_name, + const char *new_remote_name) +{ + int error; + git_reference *ref = NULL, *dummy = NULL; + git_str namespace = GIT_STR_INIT, old_namespace = GIT_STR_INIT; + git_str new_name = GIT_STR_INIT; + git_str log_message = GIT_STR_INIT; + size_t pfx_len; + const char *target; + + if ((error = git_str_printf(&namespace, GIT_REFS_REMOTES_DIR "%s/", new_remote_name)) < 0) + return error; + + pfx_len = strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name) + 1; + git_str_puts(&new_name, namespace.ptr); + if ((error = git_str_puts(&new_name, git_reference_name(reference_in) + pfx_len)) < 0) + goto cleanup; + + if ((error = git_str_printf(&log_message, + "renamed remote %s to %s", + old_remote_name, new_remote_name)) < 0) + goto cleanup; + + if ((error = git_reference_rename(&ref, reference_in, git_str_cstr(&new_name), 1, + git_str_cstr(&log_message))) < 0) + goto cleanup; + + if (git_reference_type(ref) != GIT_REFERENCE_SYMBOLIC) + goto cleanup; + + /* Handle refs like origin/HEAD -> origin/master */ + target = git_reference_symbolic_target(ref); + if ((error = git_str_printf(&old_namespace, GIT_REFS_REMOTES_DIR "%s/", old_remote_name)) < 0) + goto cleanup; + + if (git__prefixcmp(target, old_namespace.ptr)) + goto cleanup; + + git_str_clear(&new_name); + git_str_puts(&new_name, namespace.ptr); + if ((error = git_str_puts(&new_name, target + pfx_len)) < 0) + goto cleanup; + + error = git_reference_symbolic_set_target(&dummy, ref, git_str_cstr(&new_name), + git_str_cstr(&log_message)); + + git_reference_free(dummy); + +cleanup: + git_reference_free(reference_in); + git_reference_free(ref); + git_str_dispose(&namespace); + git_str_dispose(&old_namespace); + git_str_dispose(&new_name); + git_str_dispose(&log_message); + return error; +} + +static int rename_remote_references( + git_repository *repo, + const char *old_name, + const char *new_name) +{ + int error; + git_str buf = GIT_STR_INIT; + git_reference *ref; + git_reference_iterator *iter; + + if ((error = git_str_printf(&buf, GIT_REFS_REMOTES_DIR "%s/*", old_name)) < 0) + return error; + + error = git_reference_iterator_glob_new(&iter, repo, git_str_cstr(&buf)); + git_str_dispose(&buf); + + if (error < 0) + return error; + + while ((error = git_reference_next(&ref, iter)) == 0) { + if ((error = rename_one_remote_reference(ref, old_name, new_name)) < 0) + break; + } + + git_reference_iterator_free(iter); + + return (error == GIT_ITEROVER) ? 0 : error; +} + +static int rename_fetch_refspecs(git_vector *problems, git_remote *remote, const char *new_name) +{ + git_config *config; + git_str base = GIT_STR_INIT, var = GIT_STR_INIT, val = GIT_STR_INIT; + const git_refspec *spec; + size_t i; + int error = 0; + + if ((error = git_repository_config__weakptr(&config, remote->repo)) < 0) + return error; + + if ((error = git_vector_init(problems, 1, NULL)) < 0) + return error; + + if ((error = default_fetchspec_for_name(&base, remote->name)) < 0) + return error; + + git_vector_foreach(&remote->refspecs, i, spec) { + if (spec->push) + continue; + + /* Does the dst part of the refspec follow the expected format? */ + if (strcmp(git_str_cstr(&base), spec->string)) { + char *dup; + + dup = git__strdup(spec->string); + GIT_ERROR_CHECK_ALLOC(dup); + + if ((error = git_vector_insert(problems, dup)) < 0) + break; + + continue; + } + + /* If we do want to move it to the new section */ + + git_str_clear(&val); + git_str_clear(&var); + + if (default_fetchspec_for_name(&val, new_name) < 0 || + git_str_printf(&var, "remote.%s.fetch", new_name) < 0) + { + error = -1; + break; + } + + if ((error = git_config_set_string( + config, git_str_cstr(&var), git_str_cstr(&val))) < 0) + break; + } + + git_str_dispose(&base); + git_str_dispose(&var); + git_str_dispose(&val); + + if (error < 0) { + char *str; + git_vector_foreach(problems, i, str) + git__free(str); + + git_vector_free(problems); + } + + return error; +} + +int git_remote_rename(git_strarray *out, git_repository *repo, const char *name, const char *new_name) +{ + int error; + git_vector problem_refspecs = GIT_VECTOR_INIT; + git_remote *remote = NULL; + + GIT_ASSERT_ARG(out && repo && name && new_name); + + if ((error = git_remote_lookup(&remote, repo, name)) < 0) + return error; + + if ((error = ensure_remote_name_is_valid(new_name)) < 0) + goto cleanup; + + if ((error = ensure_remote_doesnot_exist(repo, new_name)) < 0) + goto cleanup; + + if ((error = rename_remote_config_section(repo, name, new_name)) < 0) + goto cleanup; + + if ((error = update_branch_remote_config_entry(repo, name, new_name)) < 0) + goto cleanup; + + if ((error = rename_remote_references(repo, name, new_name)) < 0) + goto cleanup; + + if ((error = rename_fetch_refspecs(&problem_refspecs, remote, new_name)) < 0) + goto cleanup; + + out->count = problem_refspecs.length; + out->strings = (char **) problem_refspecs.contents; + +cleanup: + if (error < 0) + git_vector_free(&problem_refspecs); + + git_remote_free(remote); + return error; +} + +int git_remote_name_is_valid(int *valid, const char *remote_name) +{ + git_str buf = GIT_STR_INIT; + git_refspec refspec = {0}; + int error; + + GIT_ASSERT(valid); + + *valid = 0; + + if (!remote_name || *remote_name == '\0') + return 0; + + if ((error = git_str_printf(&buf, "refs/heads/test:refs/remotes/%s/test", remote_name)) < 0) + goto done; + + error = git_refspec__parse(&refspec, git_str_cstr(&buf), true); + + if (!error) + *valid = 1; + else if (error == GIT_EINVALIDSPEC) + error = 0; + +done: + git_str_dispose(&buf); + git_refspec__dispose(&refspec); + + return error; +} + +git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname) +{ + git_refspec *spec; + size_t i; + + git_vector_foreach(&remote->active_refspecs, i, spec) { + if (spec->push) + continue; + + if (git_refspec_src_matches(spec, refname)) + return spec; + } + + return NULL; +} + +git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname) +{ + git_refspec *spec; + size_t i; + + git_vector_foreach(&remote->active_refspecs, i, spec) { + if (spec->push) + continue; + + if (git_refspec_dst_matches(spec, refname)) + return spec; + } + + return NULL; +} + +int git_remote_add_fetch(git_repository *repo, const char *remote, const char *refspec) +{ + return write_add_refspec(repo, remote, refspec, true); +} + +int git_remote_add_push(git_repository *repo, const char *remote, const char *refspec) +{ + return write_add_refspec(repo, remote, refspec, false); +} + +static int copy_refspecs(git_strarray *array, const git_remote *remote, unsigned int push) +{ + size_t i; + git_vector refspecs; + git_refspec *spec; + char *dup; + + if (git_vector_init(&refspecs, remote->refspecs.length, NULL) < 0) + return -1; + + git_vector_foreach(&remote->refspecs, i, spec) { + if (spec->push != push) + continue; + + if ((dup = git__strdup(spec->string)) == NULL) + goto on_error; + + if (git_vector_insert(&refspecs, dup) < 0) { + git__free(dup); + goto on_error; + } + } + + array->strings = (char **)refspecs.contents; + array->count = refspecs.length; + + return 0; + +on_error: + git_vector_free_deep(&refspecs); + + return -1; +} + +int git_remote_get_fetch_refspecs(git_strarray *array, const git_remote *remote) +{ + return copy_refspecs(array, remote, false); +} + +int git_remote_get_push_refspecs(git_strarray *array, const git_remote *remote) +{ + return copy_refspecs(array, remote, true); +} + +size_t git_remote_refspec_count(const git_remote *remote) +{ + return remote->refspecs.length; +} + +const git_refspec *git_remote_get_refspec(const git_remote *remote, size_t n) +{ + return git_vector_get(&remote->refspecs, n); +} + +int git_remote_init_callbacks(git_remote_callbacks *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_remote_callbacks, GIT_REMOTE_CALLBACKS_INIT); + return 0; +} + +/* asserts a branch..remote format */ +static const char *name_offset(size_t *len_out, const char *name) +{ + size_t prefix_len; + const char *dot; + + prefix_len = strlen("remote."); + dot = strchr(name + prefix_len, '.'); + + GIT_ASSERT_ARG_WITH_RETVAL(dot, NULL); + + *len_out = dot - name - prefix_len; + return name + prefix_len; +} + +static int remove_branch_config_related_entries( + git_repository *repo, + const char *remote_name) +{ + int error; + git_config *config; + git_config_entry *entry; + git_config_iterator *iter; + git_str buf = GIT_STR_INIT; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + return error; + + if ((error = git_config_iterator_glob_new(&iter, config, "branch\\..+\\.remote")) < 0) + return error; + + /* find any branches with us as upstream and remove that config */ + while ((error = git_config_next(&entry, iter)) == 0) { + const char *branch; + size_t branch_len; + + if (strcmp(remote_name, entry->value)) + continue; + + if ((branch = name_offset(&branch_len, entry->name)) == NULL) { + error = -1; + break; + } + + git_str_clear(&buf); + if ((error = git_str_printf(&buf, "branch.%.*s.merge", (int)branch_len, branch)) < 0) + break; + + if ((error = git_config_delete_entry(config, git_str_cstr(&buf))) < 0) { + if (error != GIT_ENOTFOUND) + break; + git_error_clear(); + } + + git_str_clear(&buf); + if ((error = git_str_printf(&buf, "branch.%.*s.remote", (int)branch_len, branch)) < 0) + break; + + if ((error = git_config_delete_entry(config, git_str_cstr(&buf))) < 0) { + if (error != GIT_ENOTFOUND) + break; + git_error_clear(); + } + } + + if (error == GIT_ITEROVER) + error = 0; + + git_str_dispose(&buf); + git_config_iterator_free(iter); + return error; +} + +static int remove_refs(git_repository *repo, const git_refspec *spec) +{ + git_reference_iterator *iter = NULL; + git_vector refs; + const char *name; + char *dup; + int error; + size_t i; + + if ((error = git_vector_init(&refs, 8, NULL)) < 0) + return error; + + if ((error = git_reference_iterator_new(&iter, repo)) < 0) + goto cleanup; + + while ((error = git_reference_next_name(&name, iter)) == 0) { + if (!git_refspec_dst_matches(spec, name)) + continue; + + dup = git__strdup(name); + if (!dup) { + error = -1; + goto cleanup; + } + + if ((error = git_vector_insert(&refs, dup)) < 0) + goto cleanup; + } + if (error == GIT_ITEROVER) + error = 0; + if (error < 0) + goto cleanup; + + git_vector_foreach(&refs, i, name) { + if ((error = git_reference_remove(repo, name)) < 0) + break; + } + +cleanup: + git_reference_iterator_free(iter); + git_vector_foreach(&refs, i, dup) { + git__free(dup); + } + git_vector_free(&refs); + return error; +} + +static int remove_remote_tracking(git_repository *repo, const char *remote_name) +{ + git_remote *remote; + int error; + size_t i, count; + + /* we want to use what's on the config, regardless of changes to the instance in memory */ + if ((error = git_remote_lookup(&remote, repo, remote_name)) < 0) + return error; + + count = git_remote_refspec_count(remote); + for (i = 0; i < count; i++) { + const git_refspec *refspec = git_remote_get_refspec(remote, i); + + /* shouldn't ever actually happen */ + if (refspec == NULL) + continue; + + if ((error = remove_refs(repo, refspec)) < 0) + break; + } + + git_remote_free(remote); + return error; +} + +int git_remote_delete(git_repository *repo, const char *name) +{ + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = remove_branch_config_related_entries(repo, name)) < 0 || + (error = remove_remote_tracking(repo, name)) < 0 || + (error = rename_remote_config_section(repo, name, NULL)) < 0) + return error; + + return 0; +} + +int git_remote_default_branch(git_buf *out, git_remote *remote) +{ + GIT_BUF_WRAP_PRIVATE(out, git_remote__default_branch, remote); +} + +int git_remote__default_branch(git_str *out, git_remote *remote) +{ + const git_remote_head **heads; + const git_remote_head *guess = NULL; + const git_oid *head_id; + size_t heads_len, i; + git_str local_default = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(out); + + if ((error = git_remote_ls(&heads, &heads_len, remote)) < 0) + goto done; + + if (heads_len == 0 || strcmp(heads[0]->name, GIT_HEAD_FILE)) { + error = GIT_ENOTFOUND; + goto done; + } + + /* the first one must be HEAD so if that has the symref info, we're done */ + if (heads[0]->symref_target) { + error = git_str_puts(out, heads[0]->symref_target); + goto done; + } + + /* + * If there's no symref information, we have to look over them + * and guess. We return the first match unless the default + * branch is a candidate. Then we return the default branch. + */ + + if ((error = git_repository_initialbranch(&local_default, remote->repo)) < 0) + goto done; + + head_id = &heads[0]->oid; + + for (i = 1; i < heads_len; i++) { + if (git_oid_cmp(head_id, &heads[i]->oid)) + continue; + + if (git__prefixcmp(heads[i]->name, GIT_REFS_HEADS_DIR)) + continue; + + if (!guess) { + guess = heads[i]; + continue; + } + + if (!git__strcmp(local_default.ptr, heads[i]->name)) { + guess = heads[i]; + break; + } + } + + if (!guess) { + error = GIT_ENOTFOUND; + goto done; + } + + error = git_str_puts(out, guess->name); + +done: + git_str_dispose(&local_default); + return error; +} + +GIT_INLINE(int) connect_opts_from_push_opts( + git_remote_connect_options *out, + git_remote *remote, + const git_push_options *push_opts) +{ + git_remote_connect_options tmp = GIT_REMOTE_CONNECT_OPTIONS_INIT; + copy_opts(&tmp, push_opts); + return git_remote_connect_options_normalize(out, remote->repo, &tmp); +} + +int git_remote_upload( + git_remote *remote, + const git_strarray *refspecs, + const git_push_options *opts) +{ + git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + git_push *push; + git_refspec *spec; + size_t i; + int error; + + GIT_ASSERT_ARG(remote); + + if (!remote->repo) { + git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); + return -1; + } + + if ((error = connect_opts_from_push_opts(&connect_opts, remote, opts)) < 0) + goto cleanup; + + if ((error = connect_or_reset_options(remote, GIT_DIRECTION_PUSH, &connect_opts)) < 0) + goto cleanup; + + free_refspecs(&remote->active_refspecs); + if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0) + goto cleanup; + + if (remote->push) { + git_push_free(remote->push); + remote->push = NULL; + } + + if ((error = git_push_new(&remote->push, remote, opts)) < 0) + goto cleanup; + + push = remote->push; + + if (refspecs && refspecs->count > 0) { + for (i = 0; i < refspecs->count; i++) { + if ((error = git_push_add_refspec(push, refspecs->strings[i])) < 0) + goto cleanup; + } + } else { + git_vector_foreach(&remote->refspecs, i, spec) { + if (!spec->push) + continue; + if ((error = git_push_add_refspec(push, spec->string)) < 0) + goto cleanup; + } + } + + if ((error = git_push_finish(push)) < 0) + goto cleanup; + + if (connect_opts.callbacks.push_update_reference && + (error = git_push_status_foreach(push, connect_opts.callbacks.push_update_reference, connect_opts.callbacks.payload)) < 0) + goto cleanup; + +cleanup: + git_remote_connect_options_dispose(&connect_opts); + return error; +} + +int git_remote_push( + git_remote *remote, + const git_strarray *refspecs, + const git_push_options *opts) +{ + git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; + int error; + + GIT_ASSERT_ARG(remote); + + if (!remote->repo) { + git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); + return -1; + } + + if (connect_opts_from_push_opts(&connect_opts, remote, opts) < 0) + return -1; + + if ((error = git_remote_upload(remote, refspecs, opts)) < 0) + goto done; + + error = git_remote_update_tips(remote, &connect_opts.callbacks, 0, 0, NULL); + +done: + git_remote_disconnect(remote); + git_remote_connect_options_dispose(&connect_opts); + return error; +} + +#define PREFIX "url" +#define SUFFIX_FETCH "insteadof" +#define SUFFIX_PUSH "pushinsteadof" + +static int apply_insteadof(char **out, git_config *config, const char *url, int direction, bool use_default_if_empty) +{ + size_t match_length, prefix_length, suffix_length; + char *replacement = NULL; + const char *regexp; + + git_str result = GIT_STR_INIT; + git_config_entry *entry; + git_config_iterator *iter; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(config); + GIT_ASSERT_ARG(url); + GIT_ASSERT_ARG(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH); + + /* Add 1 to prefix/suffix length due to the additional escaped dot */ + prefix_length = strlen(PREFIX) + 1; + if (direction == GIT_DIRECTION_FETCH) { + regexp = PREFIX "\\..*\\." SUFFIX_FETCH; + suffix_length = strlen(SUFFIX_FETCH) + 1; + } else { + regexp = PREFIX "\\..*\\." SUFFIX_PUSH; + suffix_length = strlen(SUFFIX_PUSH) + 1; + } + + if (git_config_iterator_glob_new(&iter, config, regexp) < 0) + return -1; + + match_length = 0; + while (git_config_next(&entry, iter) == 0) { + size_t n, replacement_length; + + /* Check if entry value is a prefix of URL */ + if (git__prefixcmp(url, entry->value)) + continue; + + /* Check if entry value is longer than previous + * prefixes */ + if ((n = strlen(entry->value)) <= match_length) + continue; + + git__free(replacement); + match_length = n; + + /* Cut off prefix and suffix of the value */ + replacement_length = + strlen(entry->name) - (prefix_length + suffix_length); + replacement = git__strndup(entry->name + prefix_length, + replacement_length); + } + + git_config_iterator_free(iter); + + if (match_length == 0 && use_default_if_empty) { + *out = git__strdup(url); + return *out ? 0 : -1; + } else if (match_length == 0) { + *out = NULL; + return 0; + } + + git_str_printf(&result, "%s%s", replacement, url + match_length); + + git__free(replacement); + + *out = git_str_detach(&result); + return 0; +} + +/* Deprecated functions */ + +#ifndef GIT_DEPRECATE_HARD + +int git_remote_is_valid_name(const char *remote_name) +{ + int valid = 0; + + git_remote_name_is_valid(&valid, remote_name); + return valid; +} + +#endif diff --git a/src/libgit2/remote.h b/src/libgit2/remote.h new file mode 100644 index 000000000..ea9c7d17f --- /dev/null +++ b/src/libgit2/remote.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_remote_h__ +#define INCLUDE_remote_h__ + +#include "common.h" + +#include "git2/remote.h" +#include "git2/transport.h" +#include "git2/sys/transport.h" + +#include "refspec.h" +#include "vector.h" +#include "net.h" + +#define GIT_REMOTE_ORIGIN "origin" + +struct git_remote { + char *name; + char *url; + char *pushurl; + git_vector refs; + git_vector refspecs; + git_vector active_refspecs; + git_vector passive_refspecs; + git_vector local_heads; + git_transport *transport; + git_repository *repo; + git_push *push; + git_indexer_progress stats; + unsigned int need_pack; + git_remote_autotag_option_t download_tags; + int prune_refs; + int passed_refspecs; +}; + +int git_remote__urlfordirection(git_str *url_out, struct git_remote *remote, int direction, const git_remote_callbacks *callbacks); +int git_remote__http_proxy(char **out, git_remote *remote, git_net_url *url); + +git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname); +git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname); + +int git_remote__default_branch(git_str *out, git_remote *remote); + +int git_remote_connect_options_dup( + git_remote_connect_options *dst, + const git_remote_connect_options *src); +int git_remote_connect_options_normalize( + git_remote_connect_options *dst, + git_repository *repo, + const git_remote_connect_options *src); +void git_remote_connect_options_dispose(git_remote_connect_options *opts); + +int git_remote_capabilities(unsigned int *out, git_remote *remote); + +#endif diff --git a/src/libgit2/repo_template.h b/src/libgit2/repo_template.h new file mode 100644 index 000000000..099279aa7 --- /dev/null +++ b/src/libgit2/repo_template.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_repo_template_h__ +#define INCLUDE_repo_template_h__ + +#define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/" +#define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/" + +#define GIT_HOOKS_DIR "hooks/" +#define GIT_HOOKS_DIR_MODE 0777 + +#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample" +#define GIT_HOOKS_README_MODE 0777 +#define GIT_HOOKS_README_CONTENT \ +"#!/bin/sh\n"\ +"#\n"\ +"# Place appropriately named executable hook scripts into this directory\n"\ +"# to intercept various actions that git takes. See `git help hooks` for\n"\ +"# more information.\n" + +#define GIT_INFO_DIR "info/" +#define GIT_INFO_DIR_MODE 0777 + +#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude" +#define GIT_INFO_EXCLUDE_MODE 0666 +#define GIT_INFO_EXCLUDE_CONTENT \ +"# File patterns to ignore; see `git help ignore` for more information.\n"\ +"# Lines that start with '#' are comments.\n" + +#define GIT_DESC_FILE "description" +#define GIT_DESC_MODE 0666 +#define GIT_DESC_CONTENT \ +"Unnamed repository; edit this file 'description' to name the repository.\n" + +typedef struct { + const char *path; + mode_t mode; + const char *content; +} repo_template_item; + +static repo_template_item repo_template[] = { + { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/info/' */ + { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/pack/' */ + { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/heads/' */ + { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/tags/' */ + { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE, NULL }, /* '/hooks/' */ + { GIT_INFO_DIR, GIT_INFO_DIR_MODE, NULL }, /* '/info/' */ + { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT }, + { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT }, + { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT }, + { NULL, 0, NULL } +}; + +#endif diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c new file mode 100644 index 000000000..80b4a98eb --- /dev/null +++ b/src/libgit2/repository.c @@ -0,0 +1,3253 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "repository.h" + +#include + +#include "git2/object.h" +#include "git2/sys/repository.h" + +#include "buf.h" +#include "common.h" +#include "commit.h" +#include "tag.h" +#include "blob.h" +#include "futils.h" +#include "sysdir.h" +#include "filebuf.h" +#include "index.h" +#include "config.h" +#include "refs.h" +#include "filter.h" +#include "odb.h" +#include "refdb.h" +#include "remote.h" +#include "merge.h" +#include "diff_driver.h" +#include "annotated_commit.h" +#include "submodule.h" +#include "worktree.h" +#include "path.h" +#include "strmap.h" + +#ifdef GIT_WIN32 +# include "win32/w32_util.h" +#endif + +bool git_repository__fsync_gitdir = false; + +static const struct { + git_repository_item_t parent; + git_repository_item_t fallback; + const char *name; + bool directory; +} items[] = { + { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, NULL, true }, + { GIT_REPOSITORY_ITEM_WORKDIR, GIT_REPOSITORY_ITEM__LAST, NULL, true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM__LAST, NULL, true }, + { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, "index", false }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "objects", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "refs", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "packed-refs", false }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "remotes", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "config", false }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "info", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "hooks", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "logs", true }, + { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, "modules", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "worktrees", true } +}; + +static int check_repositoryformatversion(int *version, git_config *config); +static int check_extensions(git_config *config, int version); + +#define GIT_COMMONDIR_FILE "commondir" +#define GIT_GITDIR_FILE "gitdir" + +#define GIT_FILE_CONTENT_PREFIX "gitdir:" + +#define GIT_BRANCH_DEFAULT "master" + +#define GIT_REPO_VERSION 0 +#define GIT_REPO_MAX_VERSION 1 + +git_str git_repository__reserved_names_win32[] = { + { DOT_GIT, 0, CONST_STRLEN(DOT_GIT) }, + { GIT_DIR_SHORTNAME, 0, CONST_STRLEN(GIT_DIR_SHORTNAME) } +}; +size_t git_repository__reserved_names_win32_len = 2; + +git_str git_repository__reserved_names_posix[] = { + { DOT_GIT, 0, CONST_STRLEN(DOT_GIT) }, +}; +size_t git_repository__reserved_names_posix_len = 1; + +static void set_odb(git_repository *repo, git_odb *odb) +{ + if (odb) { + GIT_REFCOUNT_OWN(odb, repo); + GIT_REFCOUNT_INC(odb); + } + + if ((odb = git_atomic_swap(repo->_odb, odb)) != NULL) { + GIT_REFCOUNT_OWN(odb, NULL); + git_odb_free(odb); + } +} + +static void set_refdb(git_repository *repo, git_refdb *refdb) +{ + if (refdb) { + GIT_REFCOUNT_OWN(refdb, repo); + GIT_REFCOUNT_INC(refdb); + } + + if ((refdb = git_atomic_swap(repo->_refdb, refdb)) != NULL) { + GIT_REFCOUNT_OWN(refdb, NULL); + git_refdb_free(refdb); + } +} + +static void set_config(git_repository *repo, git_config *config) +{ + if (config) { + GIT_REFCOUNT_OWN(config, repo); + GIT_REFCOUNT_INC(config); + } + + if ((config = git_atomic_swap(repo->_config, config)) != NULL) { + GIT_REFCOUNT_OWN(config, NULL); + git_config_free(config); + } + + git_repository__configmap_lookup_cache_clear(repo); +} + +static void set_index(git_repository *repo, git_index *index) +{ + if (index) { + GIT_REFCOUNT_OWN(index, repo); + GIT_REFCOUNT_INC(index); + } + + if ((index = git_atomic_swap(repo->_index, index)) != NULL) { + GIT_REFCOUNT_OWN(index, NULL); + git_index_free(index); + } +} + +int git_repository__cleanup(git_repository *repo) +{ + GIT_ASSERT_ARG(repo); + + git_repository_submodule_cache_clear(repo); + git_cache_clear(&repo->objects); + git_attr_cache_flush(repo); + + set_config(repo, NULL); + set_index(repo, NULL); + set_odb(repo, NULL); + set_refdb(repo, NULL); + + return 0; +} + +void git_repository_free(git_repository *repo) +{ + size_t i; + + if (repo == NULL) + return; + + git_repository__cleanup(repo); + + git_cache_dispose(&repo->objects); + + git_diff_driver_registry_free(repo->diff_drivers); + repo->diff_drivers = NULL; + + for (i = 0; i < repo->reserved_names.size; i++) + git_str_dispose(git_array_get(repo->reserved_names, i)); + git_array_clear(repo->reserved_names); + + git__free(repo->gitlink); + git__free(repo->gitdir); + git__free(repo->commondir); + git__free(repo->workdir); + git__free(repo->namespace); + git__free(repo->ident_name); + git__free(repo->ident_email); + + git__memzero(repo, sizeof(*repo)); + git__free(repo); +} + +/* Check if we have a separate commondir (e.g. we have a worktree) */ +static int lookup_commondir(bool *separate, git_str *commondir, git_str *repository_path) +{ + git_str common_link = GIT_STR_INIT; + int error; + + /* + * If there's no commondir file, the repository path is the + * common path, but it needs a trailing slash. + */ + if (!git_fs_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) { + if ((error = git_str_set(commondir, repository_path->ptr, repository_path->size)) == 0) + error = git_fs_path_to_dir(commondir); + + *separate = false; + goto done; + } + + *separate = true; + + if ((error = git_str_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE)) < 0 || + (error = git_futils_readbuffer(&common_link, common_link.ptr)) < 0) + goto done; + + git_str_rtrim(&common_link); + if (git_fs_path_is_relative(common_link.ptr)) { + if ((error = git_str_joinpath(commondir, repository_path->ptr, common_link.ptr)) < 0) + goto done; + } else { + git_str_swap(commondir, &common_link); + } + + git_str_dispose(&common_link); + + /* Make sure the commondir path always has a trailing slash */ + error = git_fs_path_prettify_dir(commondir, commondir->ptr, NULL); + +done: + return error; +} + +GIT_INLINE(int) validate_repo_path(git_str *path) +{ + /* + * The longest static path in a repository (or commondir) is the + * packed refs file. (Loose refs may be longer since they + * include the reference name, but will be validated when the + * path is constructed.) + */ + static size_t suffix_len = + CONST_STRLEN("objects/pack/pack-.pack.lock") + + GIT_OID_HEXSZ; + + return git_fs_path_validate_str_length_with_suffix( + path, suffix_len); +} + +/* + * Git repository open methods + * + * Open a repository object from its path + */ +static int is_valid_repository_path(bool *out, git_str *repository_path, git_str *common_path) +{ + bool separate_commondir = false; + int error; + + *out = false; + + if ((error = lookup_commondir(&separate_commondir, common_path, repository_path)) < 0) + return error; + + /* Ensure HEAD file exists */ + if (git_fs_path_contains_file(repository_path, GIT_HEAD_FILE) == false) + return 0; + + /* Check files in common dir */ + if (git_fs_path_contains_dir(common_path, GIT_OBJECTS_DIR) == false) + return 0; + if (git_fs_path_contains_dir(common_path, GIT_REFS_DIR) == false) + return 0; + + /* Ensure the repo (and commondir) are valid paths */ + if ((error = validate_repo_path(common_path)) < 0 || + (separate_commondir && + (error = validate_repo_path(repository_path)) < 0)) + return error; + + *out = true; + return 0; +} + +static git_repository *repository_alloc(void) +{ + git_repository *repo = git__calloc(1, sizeof(git_repository)); + + if (repo == NULL || + git_cache_init(&repo->objects) < 0) + goto on_error; + + git_array_init_to_size(repo->reserved_names, 4); + if (!repo->reserved_names.ptr) + goto on_error; + + /* set all the entries in the configmap cache to `unset` */ + git_repository__configmap_lookup_cache_clear(repo); + + return repo; + +on_error: + if (repo) + git_cache_dispose(&repo->objects); + + git__free(repo); + return NULL; +} + +int git_repository_new(git_repository **out) +{ + git_repository *repo; + + *out = repo = repository_alloc(); + GIT_ERROR_CHECK_ALLOC(repo); + + repo->is_bare = 1; + repo->is_worktree = 0; + + return 0; +} + +static int load_config_data(git_repository *repo, const git_config *config) +{ + int is_bare; + + int err = git_config_get_bool(&is_bare, config, "core.bare"); + if (err < 0 && err != GIT_ENOTFOUND) + return err; + + /* Try to figure out if it's bare, default to non-bare if it's not set */ + if (err != GIT_ENOTFOUND) + repo->is_bare = is_bare && !repo->is_worktree; + else + repo->is_bare = 0; + + return 0; +} + +static int load_workdir(git_repository *repo, git_config *config, git_str *parent_path) +{ + int error; + git_config_entry *ce; + git_str worktree = GIT_STR_INIT; + git_str path = GIT_STR_INIT; + + if (repo->is_bare) + return 0; + + if ((error = git_config__lookup_entry( + &ce, config, "core.worktree", false)) < 0) + return error; + + if (repo->is_worktree) { + char *gitlink = git_worktree__read_link(repo->gitdir, GIT_GITDIR_FILE); + if (!gitlink) { + error = -1; + goto cleanup; + } + + git_str_attach(&worktree, gitlink, 0); + + if ((git_fs_path_dirname_r(&worktree, worktree.ptr)) < 0 || + git_fs_path_to_dir(&worktree) < 0) { + error = -1; + goto cleanup; + } + + repo->workdir = git_str_detach(&worktree); + } + else if (ce && ce->value) { + if ((error = git_fs_path_prettify_dir( + &worktree, ce->value, repo->gitdir)) < 0) + goto cleanup; + + repo->workdir = git_str_detach(&worktree); + } + else if (parent_path && git_fs_path_isdir(parent_path->ptr)) + repo->workdir = git_str_detach(parent_path); + else { + if (git_fs_path_dirname_r(&worktree, repo->gitdir) < 0 || + git_fs_path_to_dir(&worktree) < 0) { + error = -1; + goto cleanup; + } + + repo->workdir = git_str_detach(&worktree); + } + + GIT_ERROR_CHECK_ALLOC(repo->workdir); +cleanup: + git_str_dispose(&path); + git_config_entry_free(ce); + return error; +} + +/* + * This function returns furthest offset into path where a ceiling dir + * is found, so we can stop processing the path at that point. + * + * Note: converting this to use git_strs instead of GIT_PATH_MAX buffers on + * the stack could remove directories name limits, but at the cost of doing + * repeated malloc/frees inside the loop below, so let's not do it now. + */ +static size_t find_ceiling_dir_offset( + const char *path, + const char *ceiling_directories) +{ + char buf[GIT_PATH_MAX + 1]; + char buf2[GIT_PATH_MAX + 1]; + const char *ceil, *sep; + size_t len, max_len = 0, min_len; + + GIT_ASSERT_ARG(path); + + min_len = (size_t)(git_fs_path_root(path) + 1); + + if (ceiling_directories == NULL || min_len == 0) + return min_len; + + for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) { + for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++); + len = sep - ceil; + + if (len == 0 || len >= sizeof(buf) || git_fs_path_root(ceil) == -1) + continue; + + strncpy(buf, ceil, len); + buf[len] = '\0'; + + if (p_realpath(buf, buf2) == NULL) + continue; + + len = strlen(buf2); + if (len > 0 && buf2[len-1] == '/') + buf[--len] = '\0'; + + if (!strncmp(path, buf2, len) && + (path[len] == '/' || !path[len]) && + len > max_len) + { + max_len = len; + } + } + + return (max_len <= min_len ? min_len : max_len); +} + +/* + * Read the contents of `file_path` and set `path_out` to the repo dir that + * it points to. Before calling, set `path_out` to the base directory that + * should be used if the contents of `file_path` are a relative path. + */ +static int read_gitfile(git_str *path_out, const char *file_path) +{ + int error = 0; + git_str file = GIT_STR_INIT; + size_t prefix_len = strlen(GIT_FILE_CONTENT_PREFIX); + + GIT_ASSERT_ARG(path_out); + GIT_ASSERT_ARG(file_path); + + if (git_futils_readbuffer(&file, file_path) < 0) + return -1; + + git_str_rtrim(&file); + /* apparently on Windows, some people use backslashes in paths */ + git_fs_path_mkposix(file.ptr); + + if (git_str_len(&file) <= prefix_len || + memcmp(git_str_cstr(&file), GIT_FILE_CONTENT_PREFIX, prefix_len) != 0) + { + git_error_set(GIT_ERROR_REPOSITORY, + "the `.git` file at '%s' is malformed", file_path); + error = -1; + } + else if ((error = git_fs_path_dirname_r(path_out, file_path)) >= 0) { + const char *gitlink = git_str_cstr(&file) + prefix_len; + while (*gitlink && git__isspace(*gitlink)) gitlink++; + + error = git_fs_path_prettify_dir( + path_out, gitlink, git_str_cstr(path_out)); + } + + git_str_dispose(&file); + return error; +} + +static int find_repo( + git_str *gitdir_path, + git_str *workdir_path, + git_str *gitlink_path, + git_str *commondir_path, + const char *start_path, + uint32_t flags, + const char *ceiling_dirs) +{ + git_str path = GIT_STR_INIT; + git_str repo_link = GIT_STR_INIT; + git_str common_link = GIT_STR_INIT; + struct stat st; + dev_t initial_device = 0; + int min_iterations; + bool in_dot_git, is_valid; + size_t ceiling_offset = 0; + int error; + + git_str_clear(gitdir_path); + + error = git_fs_path_prettify(&path, start_path, NULL); + if (error < 0) + return error; + + /* in_dot_git toggles each loop: + * /a/b/c/.git, /a/b/c, /a/b/.git, /a/b, /a/.git, /a + * With GIT_REPOSITORY_OPEN_BARE or GIT_REPOSITORY_OPEN_NO_DOTGIT, we + * assume we started with /a/b/c.git and don't append .git the first + * time through. + * min_iterations indicates the number of iterations left before going + * further counts as a search. */ + if (flags & (GIT_REPOSITORY_OPEN_BARE | GIT_REPOSITORY_OPEN_NO_DOTGIT)) { + in_dot_git = true; + min_iterations = 1; + } else { + in_dot_git = false; + min_iterations = 2; + } + + for (;;) { + if (!(flags & GIT_REPOSITORY_OPEN_NO_DOTGIT)) { + if (!in_dot_git) { + if ((error = git_str_joinpath(&path, path.ptr, DOT_GIT)) < 0) + goto out; + } + in_dot_git = !in_dot_git; + } + + if (p_stat(path.ptr, &st) == 0) { + /* check that we have not crossed device boundaries */ + if (initial_device == 0) + initial_device = st.st_dev; + else if (st.st_dev != initial_device && + !(flags & GIT_REPOSITORY_OPEN_CROSS_FS)) + break; + + if (S_ISDIR(st.st_mode)) { + if ((error = is_valid_repository_path(&is_valid, &path, &common_link)) < 0) + goto out; + + if (is_valid) { + if ((error = git_fs_path_to_dir(&path)) < 0 || + (error = git_str_set(gitdir_path, path.ptr, path.size)) < 0) + goto out; + + if (gitlink_path) + if ((error = git_str_attach(gitlink_path, git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0)) < 0) + goto out; + if (commondir_path) + git_str_swap(&common_link, commondir_path); + + break; + } + } else if (S_ISREG(st.st_mode) && git__suffixcmp(path.ptr, "/" DOT_GIT) == 0) { + if ((error = read_gitfile(&repo_link, path.ptr)) < 0 || + (error = is_valid_repository_path(&is_valid, &repo_link, &common_link)) < 0) + goto out; + + if (is_valid) { + git_str_swap(gitdir_path, &repo_link); + + if (gitlink_path) + if ((error = git_str_put(gitlink_path, path.ptr, path.size)) < 0) + goto out; + if (commondir_path) + git_str_swap(&common_link, commondir_path); + } + break; + } + } + + /* Move up one directory. If we're in_dot_git, we'll search the + * parent itself next. If we're !in_dot_git, we'll search .git + * in the parent directory next (added at the top of the loop). */ + if ((error = git_fs_path_dirname_r(&path, path.ptr)) < 0) + goto out; + + /* Once we've checked the directory (and .git if applicable), + * find the ceiling for a search. */ + if (min_iterations && (--min_iterations == 0)) + ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs); + + /* Check if we should stop searching here. */ + if (min_iterations == 0 && + (path.ptr[ceiling_offset] == 0 || (flags & GIT_REPOSITORY_OPEN_NO_SEARCH))) + break; + } + + if (workdir_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { + if (!git_str_len(gitdir_path)) + git_str_clear(workdir_path); + else if ((error = git_fs_path_dirname_r(workdir_path, path.ptr)) < 0 || + (error = git_fs_path_to_dir(workdir_path)) < 0) + goto out; + } + + /* If we didn't find the repository, and we don't have any other error + * to report, report that. */ + if (!git_str_len(gitdir_path)) { + git_error_set(GIT_ERROR_REPOSITORY, "could not find repository from '%s'", start_path); + error = GIT_ENOTFOUND; + goto out; + } + +out: + git_str_dispose(&path); + git_str_dispose(&repo_link); + git_str_dispose(&common_link); + return error; +} + +int git_repository_open_bare( + git_repository **repo_ptr, + const char *bare_path) +{ + git_str path = GIT_STR_INIT, common_path = GIT_STR_INIT; + git_repository *repo = NULL; + bool is_valid; + int error; + + if ((error = git_fs_path_prettify_dir(&path, bare_path, NULL)) < 0 || + (error = is_valid_repository_path(&is_valid, &path, &common_path)) < 0) + return error; + + if (!is_valid) { + git_str_dispose(&path); + git_str_dispose(&common_path); + git_error_set(GIT_ERROR_REPOSITORY, "path is not a repository: %s", bare_path); + return GIT_ENOTFOUND; + } + + repo = repository_alloc(); + GIT_ERROR_CHECK_ALLOC(repo); + + repo->gitdir = git_str_detach(&path); + GIT_ERROR_CHECK_ALLOC(repo->gitdir); + repo->commondir = git_str_detach(&common_path); + GIT_ERROR_CHECK_ALLOC(repo->commondir); + + /* of course we're bare! */ + repo->is_bare = 1; + repo->is_worktree = 0; + repo->workdir = NULL; + + *repo_ptr = repo; + return 0; +} + +static int _git_repository_open_ext_from_env( + git_repository **out, + const char *start_path) +{ + git_repository *repo = NULL; + git_index *index = NULL; + git_odb *odb = NULL; + git_str dir_buf = GIT_STR_INIT; + git_str ceiling_dirs_buf = GIT_STR_INIT; + git_str across_fs_buf = GIT_STR_INIT; + git_str index_file_buf = GIT_STR_INIT; + git_str namespace_buf = GIT_STR_INIT; + git_str object_dir_buf = GIT_STR_INIT; + git_str alts_buf = GIT_STR_INIT; + git_str work_tree_buf = GIT_STR_INIT; + git_str common_dir_buf = GIT_STR_INIT; + const char *ceiling_dirs = NULL; + unsigned flags = 0; + int error; + + if (!start_path) { + error = git__getenv(&dir_buf, "GIT_DIR"); + if (error == GIT_ENOTFOUND) { + git_error_clear(); + start_path = "."; + } else if (error < 0) + goto error; + else { + start_path = git_str_cstr(&dir_buf); + flags |= GIT_REPOSITORY_OPEN_NO_SEARCH; + flags |= GIT_REPOSITORY_OPEN_NO_DOTGIT; + } + } + + error = git__getenv(&ceiling_dirs_buf, "GIT_CEILING_DIRECTORIES"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + else + ceiling_dirs = git_str_cstr(&ceiling_dirs_buf); + + error = git__getenv(&across_fs_buf, "GIT_DISCOVERY_ACROSS_FILESYSTEM"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + else { + int across_fs = 0; + error = git_config_parse_bool(&across_fs, git_str_cstr(&across_fs_buf)); + if (error < 0) + goto error; + if (across_fs) + flags |= GIT_REPOSITORY_OPEN_CROSS_FS; + } + + error = git__getenv(&index_file_buf, "GIT_INDEX_FILE"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + else { + error = git_index_open(&index, git_str_cstr(&index_file_buf)); + if (error < 0) + goto error; + } + + error = git__getenv(&namespace_buf, "GIT_NAMESPACE"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + + error = git__getenv(&object_dir_buf, "GIT_OBJECT_DIRECTORY"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + else { + error = git_odb_open(&odb, git_str_cstr(&object_dir_buf)); + if (error < 0) + goto error; + } + + error = git__getenv(&work_tree_buf, "GIT_WORK_TREE"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + else { + git_error_set(GIT_ERROR_INVALID, "GIT_WORK_TREE unimplemented"); + error = GIT_ERROR; + goto error; + } + + error = git__getenv(&work_tree_buf, "GIT_COMMON_DIR"); + if (error == GIT_ENOTFOUND) + git_error_clear(); + else if (error < 0) + goto error; + else { + git_error_set(GIT_ERROR_INVALID, "GIT_COMMON_DIR unimplemented"); + error = GIT_ERROR; + goto error; + } + + error = git_repository_open_ext(&repo, start_path, flags, ceiling_dirs); + if (error < 0) + goto error; + + if (odb) + git_repository_set_odb(repo, odb); + + error = git__getenv(&alts_buf, "GIT_ALTERNATE_OBJECT_DIRECTORIES"); + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } else if (error < 0) + goto error; + else { + const char *end; + char *alt, *sep; + if (!odb) { + error = git_repository_odb(&odb, repo); + if (error < 0) + goto error; + } + + end = git_str_cstr(&alts_buf) + git_str_len(&alts_buf); + for (sep = alt = alts_buf.ptr; sep != end; alt = sep+1) { + for (sep = alt; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++) + ; + if (*sep) + *sep = '\0'; + error = git_odb_add_disk_alternate(odb, alt); + if (error < 0) + goto error; + } + } + + if (git_str_len(&namespace_buf)) { + error = git_repository_set_namespace(repo, git_str_cstr(&namespace_buf)); + if (error < 0) + goto error; + } + + git_repository_set_index(repo, index); + + if (out) { + *out = repo; + goto success; + } +error: + git_repository_free(repo); +success: + git_odb_free(odb); + git_index_free(index); + git_str_dispose(&common_dir_buf); + git_str_dispose(&work_tree_buf); + git_str_dispose(&alts_buf); + git_str_dispose(&object_dir_buf); + git_str_dispose(&namespace_buf); + git_str_dispose(&index_file_buf); + git_str_dispose(&across_fs_buf); + git_str_dispose(&ceiling_dirs_buf); + git_str_dispose(&dir_buf); + return error; +} + +static int repo_is_worktree(unsigned *out, const git_repository *repo) +{ + git_str gitdir_link = GIT_STR_INIT; + int error; + + /* Worktrees cannot have the same commondir and gitdir */ + if (repo->commondir && repo->gitdir + && !strcmp(repo->commondir, repo->gitdir)) { + *out = 0; + return 0; + } + + if ((error = git_str_joinpath(&gitdir_link, repo->gitdir, "gitdir")) < 0) + return -1; + + /* A 'gitdir' file inside a git directory is currently + * only used when the repository is a working tree. */ + *out = !!git_fs_path_exists(gitdir_link.ptr); + + git_str_dispose(&gitdir_link); + return error; +} + +int git_repository_open_ext( + git_repository **repo_ptr, + const char *start_path, + unsigned int flags, + const char *ceiling_dirs) +{ + int error; + unsigned is_worktree; + git_str gitdir = GIT_STR_INIT, workdir = GIT_STR_INIT, + gitlink = GIT_STR_INIT, commondir = GIT_STR_INIT; + git_repository *repo = NULL; + git_config *config = NULL; + int version = 0; + + if (flags & GIT_REPOSITORY_OPEN_FROM_ENV) + return _git_repository_open_ext_from_env(repo_ptr, start_path); + + if (repo_ptr) + *repo_ptr = NULL; + + error = find_repo( + &gitdir, &workdir, &gitlink, &commondir, start_path, flags, ceiling_dirs); + + if (error < 0 || !repo_ptr) + goto cleanup; + + repo = repository_alloc(); + GIT_ERROR_CHECK_ALLOC(repo); + + repo->gitdir = git_str_detach(&gitdir); + GIT_ERROR_CHECK_ALLOC(repo->gitdir); + + if (gitlink.size) { + repo->gitlink = git_str_detach(&gitlink); + GIT_ERROR_CHECK_ALLOC(repo->gitlink); + } + if (commondir.size) { + repo->commondir = git_str_detach(&commondir); + GIT_ERROR_CHECK_ALLOC(repo->commondir); + } + + if ((error = repo_is_worktree(&is_worktree, repo)) < 0) + goto cleanup; + repo->is_worktree = is_worktree; + + /* + * We'd like to have the config, but git doesn't particularly + * care if it's not there, so we need to deal with that. + */ + + error = git_repository_config_snapshot(&config, repo); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if (config && (error = check_repositoryformatversion(&version, config)) < 0) + goto cleanup; + + if ((error = check_extensions(config, version)) < 0) + goto cleanup; + + if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0) + repo->is_bare = 1; + else { + + if (config && + ((error = load_config_data(repo, config)) < 0 || + (error = load_workdir(repo, config, &workdir)) < 0)) + goto cleanup; + } + +cleanup: + git_str_dispose(&gitdir); + git_str_dispose(&workdir); + git_str_dispose(&gitlink); + git_str_dispose(&commondir); + git_config_free(config); + + if (error < 0) + git_repository_free(repo); + else if (repo_ptr) + *repo_ptr = repo; + + return error; +} + +int git_repository_open(git_repository **repo_out, const char *path) +{ + return git_repository_open_ext( + repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL); +} + +int git_repository_open_from_worktree(git_repository **repo_out, git_worktree *wt) +{ + git_str path = GIT_STR_INIT; + git_repository *repo = NULL; + size_t len; + int err; + + GIT_ASSERT_ARG(repo_out); + GIT_ASSERT_ARG(wt); + + *repo_out = NULL; + len = strlen(wt->gitlink_path); + + if (len <= 4 || strcasecmp(wt->gitlink_path + len - 4, ".git")) { + err = -1; + goto out; + } + + if ((err = git_str_set(&path, wt->gitlink_path, len - 4)) < 0) + goto out; + + if ((err = git_repository_open(&repo, path.ptr)) < 0) + goto out; + + *repo_out = repo; + +out: + git_str_dispose(&path); + + return err; +} + +int git_repository_wrap_odb(git_repository **repo_out, git_odb *odb) +{ + git_repository *repo; + + repo = repository_alloc(); + GIT_ERROR_CHECK_ALLOC(repo); + + git_repository_set_odb(repo, odb); + *repo_out = repo; + + return 0; +} + +int git_repository_discover( + git_buf *out, + const char *start_path, + int across_fs, + const char *ceiling_dirs) +{ + uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0; + + GIT_ASSERT_ARG(start_path); + + GIT_BUF_WRAP_PRIVATE(out, find_repo, NULL, NULL, NULL, start_path, flags, ceiling_dirs); +} + +static int load_config( + git_config **out, + git_repository *repo, + const char *global_config_path, + const char *xdg_config_path, + const char *system_config_path, + const char *programdata_path) +{ + int error; + git_str config_path = GIT_STR_INIT; + git_config *cfg = NULL; + + GIT_ASSERT_ARG(out); + + if ((error = git_config_new(&cfg)) < 0) + return error; + + if (repo) { + if ((error = git_repository__item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG)) == 0) + error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, repo, 0); + + if (error && error != GIT_ENOTFOUND) + goto on_error; + + git_str_dispose(&config_path); + } + + if (global_config_path != NULL && + (error = git_config_add_file_ondisk( + cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, repo, 0)) < 0 && + error != GIT_ENOTFOUND) + goto on_error; + + if (xdg_config_path != NULL && + (error = git_config_add_file_ondisk( + cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, repo, 0)) < 0 && + error != GIT_ENOTFOUND) + goto on_error; + + if (system_config_path != NULL && + (error = git_config_add_file_ondisk( + cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, repo, 0)) < 0 && + error != GIT_ENOTFOUND) + goto on_error; + + if (programdata_path != NULL && + (error = git_config_add_file_ondisk( + cfg, programdata_path, GIT_CONFIG_LEVEL_PROGRAMDATA, repo, 0)) < 0 && + error != GIT_ENOTFOUND) + goto on_error; + + git_error_clear(); /* clear any lingering ENOTFOUND errors */ + + *out = cfg; + return 0; + +on_error: + git_str_dispose(&config_path); + git_config_free(cfg); + *out = NULL; + return error; +} + +static const char *path_unless_empty(git_str *buf) +{ + return git_str_len(buf) > 0 ? git_str_cstr(buf) : NULL; +} + +int git_repository_config__weakptr(git_config **out, git_repository *repo) +{ + int error = 0; + + if (repo->_config == NULL) { + git_str global_buf = GIT_STR_INIT; + git_str xdg_buf = GIT_STR_INIT; + git_str system_buf = GIT_STR_INIT; + git_str programdata_buf = GIT_STR_INIT; + git_config *config; + + git_config__find_global(&global_buf); + git_config__find_xdg(&xdg_buf); + git_config__find_system(&system_buf); + git_config__find_programdata(&programdata_buf); + + /* If there is no global file, open a backend for it anyway */ + if (git_str_len(&global_buf) == 0) + git_config__global_location(&global_buf); + + error = load_config( + &config, repo, + path_unless_empty(&global_buf), + path_unless_empty(&xdg_buf), + path_unless_empty(&system_buf), + path_unless_empty(&programdata_buf)); + if (!error) { + GIT_REFCOUNT_OWN(config, repo); + + if (git_atomic_compare_and_swap(&repo->_config, NULL, config) != NULL) { + GIT_REFCOUNT_OWN(config, NULL); + git_config_free(config); + } + } + + git_str_dispose(&global_buf); + git_str_dispose(&xdg_buf); + git_str_dispose(&system_buf); + git_str_dispose(&programdata_buf); + } + + *out = repo->_config; + return error; +} + +int git_repository_config(git_config **out, git_repository *repo) +{ + if (git_repository_config__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; +} + +int git_repository_config_snapshot(git_config **out, git_repository *repo) +{ + int error; + git_config *weak; + + if ((error = git_repository_config__weakptr(&weak, repo)) < 0) + return error; + + return git_config_snapshot(out, weak); +} + +int git_repository_set_config(git_repository *repo, git_config *config) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(config); + + set_config(repo, config); + return 0; +} + +int git_repository_odb__weakptr(git_odb **out, git_repository *repo) +{ + int error = 0; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(out); + + *out = git_atomic_load(repo->_odb); + if (*out == NULL) { + git_str odb_path = GIT_STR_INIT; + git_odb *odb; + + if ((error = git_repository__item_path(&odb_path, repo, + GIT_REPOSITORY_ITEM_OBJECTS)) < 0 || + (error = git_odb_new(&odb)) < 0) + return error; + + GIT_REFCOUNT_OWN(odb, repo); + + if ((error = git_odb__set_caps(odb, GIT_ODB_CAP_FROM_OWNER)) < 0 || + (error = git_odb__add_default_backends(odb, odb_path.ptr, 0, 0)) < 0) { + git_odb_free(odb); + return error; + } + + if (git_atomic_compare_and_swap(&repo->_odb, NULL, odb) != NULL) { + GIT_REFCOUNT_OWN(odb, NULL); + git_odb_free(odb); + } + + git_str_dispose(&odb_path); + *out = git_atomic_load(repo->_odb); + } + + return error; +} + +int git_repository_odb(git_odb **out, git_repository *repo) +{ + if (git_repository_odb__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; +} + +int git_repository_set_odb(git_repository *repo, git_odb *odb) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(odb); + + set_odb(repo, odb); + return 0; +} + +int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo) +{ + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + if (repo->_refdb == NULL) { + git_refdb *refdb; + + error = git_refdb_open(&refdb, repo); + if (!error) { + GIT_REFCOUNT_OWN(refdb, repo); + + if (git_atomic_compare_and_swap(&repo->_refdb, NULL, refdb) != NULL) { + GIT_REFCOUNT_OWN(refdb, NULL); + git_refdb_free(refdb); + } + } + } + + *out = repo->_refdb; + return error; +} + +int git_repository_refdb(git_refdb **out, git_repository *repo) +{ + if (git_repository_refdb__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; +} + +int git_repository_set_refdb(git_repository *repo, git_refdb *refdb) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(refdb); + + set_refdb(repo, refdb); + return 0; +} + +int git_repository_index__weakptr(git_index **out, git_repository *repo) +{ + int error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + if (repo->_index == NULL) { + git_str index_path = GIT_STR_INIT; + git_index *index; + + if ((error = git_str_joinpath(&index_path, repo->gitdir, GIT_INDEX_FILE)) < 0) + return error; + + error = git_index_open(&index, index_path.ptr); + if (!error) { + GIT_REFCOUNT_OWN(index, repo); + + if (git_atomic_compare_and_swap(&repo->_index, NULL, index) != NULL) { + GIT_REFCOUNT_OWN(index, NULL); + git_index_free(index); + } + + error = git_index_set_caps(repo->_index, + GIT_INDEX_CAPABILITY_FROM_OWNER); + } + + git_str_dispose(&index_path); + } + + *out = repo->_index; + return error; +} + +int git_repository_index(git_index **out, git_repository *repo) +{ + if (git_repository_index__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; +} + +int git_repository_set_index(git_repository *repo, git_index *index) +{ + GIT_ASSERT_ARG(repo); + set_index(repo, index); + return 0; +} + +int git_repository_set_namespace(git_repository *repo, const char *namespace) +{ + git__free(repo->namespace); + + if (namespace == NULL) { + repo->namespace = NULL; + return 0; + } + + return (repo->namespace = git__strdup(namespace)) ? 0 : -1; +} + +const char *git_repository_get_namespace(git_repository *repo) +{ + return repo->namespace; +} + +#ifdef GIT_WIN32 +static int reserved_names_add8dot3(git_repository *repo, const char *path) +{ + char *name = git_win32_path_8dot3_name(path); + const char *def = GIT_DIR_SHORTNAME; + const char *def_dot_git = DOT_GIT; + size_t name_len, def_len = CONST_STRLEN(GIT_DIR_SHORTNAME); + size_t def_dot_git_len = CONST_STRLEN(DOT_GIT); + git_str *buf; + + if (!name) + return 0; + + name_len = strlen(name); + + if ((name_len == def_len && memcmp(name, def, def_len) == 0) || + (name_len == def_dot_git_len && memcmp(name, def_dot_git, def_dot_git_len) == 0)) { + git__free(name); + return 0; + } + + if ((buf = git_array_alloc(repo->reserved_names)) == NULL) + return -1; + + git_str_attach(buf, name, name_len); + return true; +} + +bool git_repository__reserved_names( + git_str **out, size_t *outlen, git_repository *repo, bool include_ntfs) +{ + GIT_UNUSED(include_ntfs); + + if (repo->reserved_names.size == 0) { + git_str *buf; + size_t i; + + /* Add the static defaults */ + for (i = 0; i < git_repository__reserved_names_win32_len; i++) { + if ((buf = git_array_alloc(repo->reserved_names)) == NULL) + goto on_error; + + buf->ptr = git_repository__reserved_names_win32[i].ptr; + buf->size = git_repository__reserved_names_win32[i].size; + } + + /* Try to add any repo-specific reserved names - the gitlink file + * within a submodule or the repository (if the repository directory + * is beneath the workdir). These are typically `.git`, but should + * be protected in case they are not. Note, repo and workdir paths + * are always prettified to end in `/`, so a prefixcmp is safe. + */ + if (!repo->is_bare) { + int (*prefixcmp)(const char *, const char *); + int error, ignorecase; + + error = git_repository__configmap_lookup( + &ignorecase, repo, GIT_CONFIGMAP_IGNORECASE); + prefixcmp = (error || ignorecase) ? git__prefixcmp_icase : + git__prefixcmp; + + if (repo->gitlink && + reserved_names_add8dot3(repo, repo->gitlink) < 0) + goto on_error; + + if (repo->gitdir && + prefixcmp(repo->gitdir, repo->workdir) == 0 && + reserved_names_add8dot3(repo, repo->gitdir) < 0) + goto on_error; + } + } + + *out = repo->reserved_names.ptr; + *outlen = repo->reserved_names.size; + + return true; + + /* Always give good defaults, even on OOM */ +on_error: + *out = git_repository__reserved_names_win32; + *outlen = git_repository__reserved_names_win32_len; + + return false; +} +#else +bool git_repository__reserved_names( + git_str **out, size_t *outlen, git_repository *repo, bool include_ntfs) +{ + GIT_UNUSED(repo); + + if (include_ntfs) { + *out = git_repository__reserved_names_win32; + *outlen = git_repository__reserved_names_win32_len; + } else { + *out = git_repository__reserved_names_posix; + *outlen = git_repository__reserved_names_posix_len; + } + + return true; +} +#endif + +static int check_repositoryformatversion(int *version, git_config *config) +{ + int error; + + error = git_config_get_int32(version, config, "core.repositoryformatversion"); + /* git ignores this if the config variable isn't there */ + if (error == GIT_ENOTFOUND) + return 0; + + if (error < 0) + return -1; + + if (GIT_REPO_MAX_VERSION < *version) { + git_error_set(GIT_ERROR_REPOSITORY, + "unsupported repository version %d; only versions up to %d are supported", + *version, GIT_REPO_MAX_VERSION); + return -1; + } + + return 0; +} + +static const char *builtin_extensions[] = { + "noop" +}; + +static git_vector user_extensions = GIT_VECTOR_INIT; + +static int check_valid_extension(const git_config_entry *entry, void *payload) +{ + git_str cfg = GIT_STR_INIT; + bool reject; + const char *extension; + size_t i; + int error = 0; + + GIT_UNUSED(payload); + + git_vector_foreach (&user_extensions, i, extension) { + git_str_clear(&cfg); + + /* + * Users can specify that they don't want to support an + * extension with a '!' prefix. + */ + if ((reject = (extension[0] == '!')) == true) + extension = &extension[1]; + + if ((error = git_str_printf(&cfg, "extensions.%s", extension)) < 0) + goto done; + + if (strcmp(entry->name, cfg.ptr) == 0) { + if (reject) + goto fail; + + goto done; + } + } + + for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) { + extension = builtin_extensions[i]; + + if ((error = git_str_printf(&cfg, "extensions.%s", extension)) < 0) + goto done; + + if (strcmp(entry->name, cfg.ptr) == 0) + goto done; + } + +fail: + git_error_set(GIT_ERROR_REPOSITORY, "unsupported extension name %s", entry->name); + error = -1; + +done: + git_str_dispose(&cfg); + return error; +} + +static int check_extensions(git_config *config, int version) +{ + if (version < 1) + return 0; + + return git_config_foreach_match(config, "^extensions\\.", check_valid_extension, NULL); +} + +int git_repository__extensions(char ***out, size_t *out_len) +{ + git_vector extensions; + const char *builtin, *user; + char *extension; + size_t i, j; + + if (git_vector_init(&extensions, 8, NULL) < 0) + return -1; + + for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) { + bool match = false; + + builtin = builtin_extensions[i]; + + git_vector_foreach (&user_extensions, j, user) { + if (user[0] == '!' && strcmp(builtin, &user[1]) == 0) { + match = true; + break; + } + } + + if (match) + continue; + + if ((extension = git__strdup(builtin)) == NULL || + git_vector_insert(&extensions, extension) < 0) + return -1; + } + + git_vector_foreach (&user_extensions, i, user) { + if (user[0] == '!') + continue; + + if ((extension = git__strdup(user)) == NULL || + git_vector_insert(&extensions, extension) < 0) + return -1; + } + + *out = (char **)git_vector_detach(out_len, NULL, &extensions); + return 0; +} + +int git_repository__set_extensions(const char **extensions, size_t len) +{ + char *extension; + size_t i; + + git_repository__free_extensions(); + + for (i = 0; i < len; i++) { + if ((extension = git__strdup(extensions[i])) == NULL || + git_vector_insert(&user_extensions, extension) < 0) + return -1; + } + + return 0; +} + +void git_repository__free_extensions(void) +{ + git_vector_free_deep(&user_extensions); +} + +int git_repository_create_head(const char *git_dir, const char *ref_name) +{ + git_str ref_path = GIT_STR_INIT; + git_filebuf ref = GIT_FILEBUF_INIT; + const char *fmt; + int error; + + if ((error = git_str_joinpath(&ref_path, git_dir, GIT_HEAD_FILE)) < 0 || + (error = git_filebuf_open(&ref, ref_path.ptr, 0, GIT_REFS_FILE_MODE)) < 0) + goto out; + + if (git__prefixcmp(ref_name, GIT_REFS_DIR) == 0) + fmt = "ref: %s\n"; + else + fmt = "ref: " GIT_REFS_HEADS_DIR "%s\n"; + + if ((error = git_filebuf_printf(&ref, fmt, ref_name)) < 0 || + (error = git_filebuf_commit(&ref)) < 0) + goto out; + +out: + git_str_dispose(&ref_path); + git_filebuf_cleanup(&ref); + return error; +} + +static bool is_chmod_supported(const char *file_path) +{ + struct stat st1, st2; + + if (p_stat(file_path, &st1) < 0) + return false; + + if (p_chmod(file_path, st1.st_mode ^ S_IXUSR) < 0) + return false; + + if (p_stat(file_path, &st2) < 0) + return false; + + return (st1.st_mode != st2.st_mode); +} + +static bool is_filesystem_case_insensitive(const char *gitdir_path) +{ + git_str path = GIT_STR_INIT; + int is_insensitive = -1; + + if (!git_str_joinpath(&path, gitdir_path, "CoNfIg")) + is_insensitive = git_fs_path_exists(git_str_cstr(&path)); + + git_str_dispose(&path); + return is_insensitive; +} + +static bool are_symlinks_supported(const char *wd_path) +{ + git_config *config = NULL; + git_str global_buf = GIT_STR_INIT; + git_str xdg_buf = GIT_STR_INIT; + git_str system_buf = GIT_STR_INIT; + git_str programdata_buf = GIT_STR_INIT; + int symlinks = 0; + + /* + * To emulate Git for Windows, symlinks on Windows must be explicitly + * opted-in. We examine the system configuration for a core.symlinks + * set to true. If found, we then examine the filesystem to see if + * symlinks are _actually_ supported by the current user. If that is + * _not_ set, then we do not test or enable symlink support. + */ +#ifdef GIT_WIN32 + git_config__find_global(&global_buf); + git_config__find_xdg(&xdg_buf); + git_config__find_system(&system_buf); + git_config__find_programdata(&programdata_buf); + + if (load_config(&config, NULL, + path_unless_empty(&global_buf), + path_unless_empty(&xdg_buf), + path_unless_empty(&system_buf), + path_unless_empty(&programdata_buf)) < 0) + goto done; + + if (git_config_get_bool(&symlinks, config, "core.symlinks") < 0 || !symlinks) + goto done; +#endif + + if (!(symlinks = git_fs_path_supports_symlinks(wd_path))) + goto done; + +done: + git_str_dispose(&global_buf); + git_str_dispose(&xdg_buf); + git_str_dispose(&system_buf); + git_str_dispose(&programdata_buf); + git_config_free(config); + return symlinks != 0; +} + +static int create_empty_file(const char *path, mode_t mode) +{ + int fd; + + if ((fd = p_creat(path, mode)) < 0) { + git_error_set(GIT_ERROR_OS, "error while creating '%s'", path); + return -1; + } + + if (p_close(fd) < 0) { + git_error_set(GIT_ERROR_OS, "error while closing '%s'", path); + return -1; + } + + return 0; +} + +static int repo_local_config( + git_config **out, + git_str *config_dir, + git_repository *repo, + const char *repo_dir) +{ + int error = 0; + git_config *parent; + const char *cfg_path; + + if (git_str_joinpath(config_dir, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0) + return -1; + cfg_path = git_str_cstr(config_dir); + + /* make LOCAL config if missing */ + if (!git_fs_path_isfile(cfg_path) && + (error = create_empty_file(cfg_path, GIT_CONFIG_FILE_MODE)) < 0) + return error; + + /* if no repo, just open that file directly */ + if (!repo) + return git_config_open_ondisk(out, cfg_path); + + /* otherwise, open parent config and get that level */ + if ((error = git_repository_config__weakptr(&parent, repo)) < 0) + return error; + + if (git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL) < 0) { + git_error_clear(); + + if (!(error = git_config_add_file_ondisk( + parent, cfg_path, GIT_CONFIG_LEVEL_LOCAL, repo, false))) + error = git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL); + } + + git_config_free(parent); + + return error; +} + +static int repo_init_fs_configs( + git_config *cfg, + const char *cfg_path, + const char *repo_dir, + const char *work_dir, + bool update_ignorecase) +{ + int error = 0; + + if (!work_dir) + work_dir = repo_dir; + + if ((error = git_config_set_bool( + cfg, "core.filemode", is_chmod_supported(cfg_path))) < 0) + return error; + + if (!are_symlinks_supported(work_dir)) { + if ((error = git_config_set_bool(cfg, "core.symlinks", false)) < 0) + return error; + } else if (git_config_delete_entry(cfg, "core.symlinks") < 0) + git_error_clear(); + + if (update_ignorecase) { + if (is_filesystem_case_insensitive(repo_dir)) { + if ((error = git_config_set_bool(cfg, "core.ignorecase", true)) < 0) + return error; + } else if (git_config_delete_entry(cfg, "core.ignorecase") < 0) + git_error_clear(); + } + +#ifdef GIT_USE_ICONV + if ((error = git_config_set_bool( + cfg, "core.precomposeunicode", + git_fs_path_does_decompose_unicode(work_dir))) < 0) + return error; + /* on non-iconv platforms, don't even set core.precomposeunicode */ +#endif + + return 0; +} + +static int repo_init_config( + const char *repo_dir, + const char *work_dir, + uint32_t flags, + uint32_t mode) +{ + int error = 0; + git_str cfg_path = GIT_STR_INIT, worktree_path = GIT_STR_INIT; + git_config *config = NULL; + bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0); + bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0); + int version = 0; + + if ((error = repo_local_config(&config, &cfg_path, NULL, repo_dir)) < 0) + goto cleanup; + + if (is_reinit && (error = check_repositoryformatversion(&version, config)) < 0) + goto cleanup; + + if ((error = check_extensions(config, version)) < 0) + goto cleanup; + +#define SET_REPO_CONFIG(TYPE, NAME, VAL) do { \ + if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \ + goto cleanup; } while (0) + + SET_REPO_CONFIG(bool, "core.bare", is_bare); + SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION); + + if ((error = repo_init_fs_configs( + config, cfg_path.ptr, repo_dir, work_dir, !is_reinit)) < 0) + goto cleanup; + + if (!is_bare) { + SET_REPO_CONFIG(bool, "core.logallrefupdates", true); + + if (!(flags & GIT_REPOSITORY_INIT__NATURAL_WD)) { + if ((error = git_str_sets(&worktree_path, work_dir)) < 0) + goto cleanup; + + if ((flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK)) + if ((error = git_fs_path_make_relative(&worktree_path, repo_dir)) < 0) + goto cleanup; + + SET_REPO_CONFIG(string, "core.worktree", worktree_path.ptr); + } else if (is_reinit) { + if (git_config_delete_entry(config, "core.worktree") < 0) + git_error_clear(); + } + } + + if (mode == GIT_REPOSITORY_INIT_SHARED_GROUP) { + SET_REPO_CONFIG(int32, "core.sharedrepository", 1); + SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); + } + else if (mode == GIT_REPOSITORY_INIT_SHARED_ALL) { + SET_REPO_CONFIG(int32, "core.sharedrepository", 2); + SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); + } + +cleanup: + git_str_dispose(&cfg_path); + git_str_dispose(&worktree_path); + git_config_free(config); + + return error; +} + +static int repo_reinit_submodule_fs(git_submodule *sm, const char *n, void *p) +{ + git_repository *smrepo = NULL; + GIT_UNUSED(n); GIT_UNUSED(p); + + if (git_submodule_open(&smrepo, sm) < 0 || + git_repository_reinit_filesystem(smrepo, true) < 0) + git_error_clear(); + git_repository_free(smrepo); + + return 0; +} + +int git_repository_reinit_filesystem(git_repository *repo, int recurse) +{ + int error = 0; + git_str path = GIT_STR_INIT; + git_config *config = NULL; + const char *repo_dir = git_repository_path(repo); + + if (!(error = repo_local_config(&config, &path, repo, repo_dir))) + error = repo_init_fs_configs( + config, path.ptr, repo_dir, git_repository_workdir(repo), true); + + git_config_free(config); + git_str_dispose(&path); + + git_repository__configmap_lookup_cache_clear(repo); + + if (!repo->is_bare && recurse) + (void)git_submodule_foreach(repo, repo_reinit_submodule_fs, NULL); + + return error; +} + +static int repo_write_template( + const char *git_dir, + bool allow_overwrite, + const char *file, + mode_t mode, + bool hidden, + const char *content) +{ + git_str path = GIT_STR_INIT; + int fd, error = 0, flags; + + if (git_str_joinpath(&path, git_dir, file) < 0) + return -1; + + if (allow_overwrite) + flags = O_WRONLY | O_CREAT | O_TRUNC; + else + flags = O_WRONLY | O_CREAT | O_EXCL; + + fd = p_open(git_str_cstr(&path), flags, mode); + + if (fd >= 0) { + error = p_write(fd, content, strlen(content)); + + p_close(fd); + } + else if (errno != EEXIST) + error = fd; + +#ifdef GIT_WIN32 + if (!error && hidden) { + if (git_win32__set_hidden(path.ptr, true) < 0) + error = -1; + } +#else + GIT_UNUSED(hidden); +#endif + + git_str_dispose(&path); + + if (error) + git_error_set(GIT_ERROR_OS, + "failed to initialize repository with template '%s'", file); + + return error; +} + +static int repo_write_gitlink( + const char *in_dir, const char *to_repo, bool use_relative_path) +{ + int error; + git_str buf = GIT_STR_INIT; + git_str path_to_repo = GIT_STR_INIT; + struct stat st; + + git_fs_path_dirname_r(&buf, to_repo); + git_fs_path_to_dir(&buf); + if (git_str_oom(&buf)) + return -1; + + /* don't write gitlink to natural workdir */ + if (git__suffixcmp(to_repo, "/" DOT_GIT "/") == 0 && + strcmp(in_dir, buf.ptr) == 0) + { + error = GIT_PASSTHROUGH; + goto cleanup; + } + + if ((error = git_str_joinpath(&buf, in_dir, DOT_GIT)) < 0) + goto cleanup; + + if (!p_stat(buf.ptr, &st) && !S_ISREG(st.st_mode)) { + git_error_set(GIT_ERROR_REPOSITORY, + "cannot overwrite gitlink file into path '%s'", in_dir); + error = GIT_EEXISTS; + goto cleanup; + } + + git_str_clear(&buf); + + error = git_str_sets(&path_to_repo, to_repo); + + if (!error && use_relative_path) + error = git_fs_path_make_relative(&path_to_repo, in_dir); + + if (!error) + error = git_str_join(&buf, ' ', GIT_FILE_CONTENT_PREFIX, path_to_repo.ptr); + + if (!error) + error = repo_write_template(in_dir, true, DOT_GIT, 0666, true, buf.ptr); + +cleanup: + git_str_dispose(&buf); + git_str_dispose(&path_to_repo); + return error; +} + +static mode_t pick_dir_mode(git_repository_init_options *opts) +{ + if (opts->mode == GIT_REPOSITORY_INIT_SHARED_UMASK) + return 0777; + if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP) + return (0775 | S_ISGID); + if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL) + return (0777 | S_ISGID); + return opts->mode; +} + +#include "repo_template.h" + +static int repo_init_structure( + const char *repo_dir, + const char *work_dir, + git_repository_init_options *opts) +{ + int error = 0; + repo_template_item *tpl; + bool external_tpl = + ((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0); + mode_t dmode = pick_dir_mode(opts); + bool chmod = opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK; + + /* Hide the ".git" directory */ +#ifdef GIT_WIN32 + if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) { + if (git_win32__set_hidden(repo_dir, true) < 0) { + git_error_set(GIT_ERROR_OS, + "failed to mark Git repository folder as hidden"); + return -1; + } + } +#endif + + /* Create the .git gitlink if appropriate */ + if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0 && + (opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD) == 0) + { + if (repo_write_gitlink(work_dir, repo_dir, opts->flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK) < 0) + return -1; + } + + /* Copy external template if requested */ + if (external_tpl) { + git_config *cfg = NULL; + const char *tdir = NULL; + bool default_template = false; + git_str template_buf = GIT_STR_INIT; + + if (opts->template_path) + tdir = opts->template_path; + else if ((error = git_config_open_default(&cfg)) >= 0) { + if (!git_config__get_path(&template_buf, cfg, "init.templatedir")) + tdir = template_buf.ptr; + git_error_clear(); + } + + if (!tdir) { + if (!(error = git_sysdir_find_template_dir(&template_buf))) + tdir = template_buf.ptr; + default_template = true; + } + + /* + * If tdir was the empty string, treat it like tdir was a path to an + * empty directory (so, don't do any copying). This is the behavior + * that git(1) exhibits, although it doesn't seem to be officially + * documented. + */ + if (tdir && git__strcmp(tdir, "") != 0) { + uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS | + GIT_CPDIR_SIMPLE_TO_MODE | + GIT_CPDIR_COPY_DOTFILES; + if (opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK) + cpflags |= GIT_CPDIR_CHMOD_DIRS; + error = git_futils_cp_r(tdir, repo_dir, cpflags, dmode); + } + + git_str_dispose(&template_buf); + git_config_free(cfg); + + /* If tdir does not exist, then do not error out. This matches the + * behaviour of git(1), which just prints a warning and continues. + * TODO: issue warning when warning API is available. + * `git` prints to stderr: 'warning: templates not found in /path/to/tdir' + */ + if (error < 0) { + if (!default_template && error != GIT_ENOTFOUND) + return error; + + /* if template was default, ignore error and use internal */ + git_error_clear(); + external_tpl = false; + error = 0; + } + } + + /* Copy internal template + * - always ensure existence of dirs + * - only create files if no external template was specified + */ + for (tpl = repo_template; !error && tpl->path; ++tpl) { + if (!tpl->content) { + uint32_t mkdir_flags = GIT_MKDIR_PATH; + if (chmod) + mkdir_flags |= GIT_MKDIR_CHMOD; + + error = git_futils_mkdir_relative( + tpl->path, repo_dir, dmode, mkdir_flags, NULL); + } + else if (!external_tpl) { + const char *content = tpl->content; + + if (opts->description && strcmp(tpl->path, GIT_DESC_FILE) == 0) + content = opts->description; + + error = repo_write_template( + repo_dir, false, tpl->path, tpl->mode, false, content); + } + } + + return error; +} + +static int mkdir_parent(git_str *buf, uint32_t mode, bool skip2) +{ + /* When making parent directories during repository initialization + * don't try to set gid or grant world write access + */ + return git_futils_mkdir( + buf->ptr, mode & ~(S_ISGID | 0002), + GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR | + (skip2 ? GIT_MKDIR_SKIP_LAST2 : GIT_MKDIR_SKIP_LAST)); +} + +static int repo_init_directories( + git_str *repo_path, + git_str *wd_path, + const char *given_repo, + git_repository_init_options *opts) +{ + int error = 0; + bool is_bare, add_dotgit, has_dotgit, natural_wd; + mode_t dirmode; + + /* There are three possible rules for what we are allowed to create: + * - MKPATH means anything we need + * - MKDIR means just the .git directory and its parent and the workdir + * - Neither means only the .git directory can be created + * + * There are 5 "segments" of path that we might need to deal with: + * 1. The .git directory + * 2. The parent of the .git directory + * 3. Everything above the parent of the .git directory + * 4. The working directory (often the same as #2) + * 5. Everything above the working directory (often the same as #3) + * + * For all directories created, we start with the init_mode value for + * permissions and then strip off bits in some cases: + * + * For MKPATH, we create #3 (and #5) paths without S_ISGID or S_IWOTH + * For MKPATH and MKDIR, we create #2 (and #4) without S_ISGID + * For all rules, we create #1 using the untouched init_mode + */ + + /* set up repo path */ + + is_bare = ((opts->flags & GIT_REPOSITORY_INIT_BARE) != 0); + + add_dotgit = + (opts->flags & GIT_REPOSITORY_INIT_NO_DOTGIT_DIR) == 0 && + !is_bare && + git__suffixcmp(given_repo, "/" DOT_GIT) != 0 && + git__suffixcmp(given_repo, "/" GIT_DIR) != 0; + + if (git_str_joinpath(repo_path, given_repo, add_dotgit ? GIT_DIR : "") < 0) + return -1; + + has_dotgit = (git__suffixcmp(repo_path->ptr, "/" GIT_DIR) == 0); + if (has_dotgit) + opts->flags |= GIT_REPOSITORY_INIT__HAS_DOTGIT; + + /* set up workdir path */ + + if (!is_bare) { + if (opts->workdir_path) { + if (git_fs_path_join_unrooted( + wd_path, opts->workdir_path, repo_path->ptr, NULL) < 0) + return -1; + } else if (has_dotgit) { + if (git_fs_path_dirname_r(wd_path, repo_path->ptr) < 0) + return -1; + } else { + git_error_set(GIT_ERROR_REPOSITORY, "cannot pick working directory" + " for non-bare repository that isn't a '.git' directory"); + return -1; + } + + if (git_fs_path_to_dir(wd_path) < 0) + return -1; + } else { + git_str_clear(wd_path); + } + + natural_wd = + has_dotgit && + wd_path->size > 0 && + wd_path->size + strlen(GIT_DIR) == repo_path->size && + memcmp(repo_path->ptr, wd_path->ptr, wd_path->size) == 0; + if (natural_wd) + opts->flags |= GIT_REPOSITORY_INIT__NATURAL_WD; + + /* create directories as needed / requested */ + + dirmode = pick_dir_mode(opts); + + if ((opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) { + /* create path #5 */ + if (wd_path->size > 0 && + (error = mkdir_parent(wd_path, dirmode, false)) < 0) + return error; + + /* create path #3 (if not the same as #5) */ + if (!natural_wd && + (error = mkdir_parent(repo_path, dirmode, has_dotgit)) < 0) + return error; + } + + if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 || + (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) + { + /* create path #4 */ + if (wd_path->size > 0 && + (error = git_futils_mkdir( + wd_path->ptr, dirmode & ~S_ISGID, + GIT_MKDIR_VERIFY_DIR)) < 0) + return error; + + /* create path #2 (if not the same as #4) */ + if (!natural_wd && + (error = git_futils_mkdir( + repo_path->ptr, dirmode & ~S_ISGID, + GIT_MKDIR_VERIFY_DIR | GIT_MKDIR_SKIP_LAST)) < 0) + return error; + } + + if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 || + (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0 || + has_dotgit) + { + /* create path #1 */ + error = git_futils_mkdir(repo_path->ptr, dirmode, + GIT_MKDIR_VERIFY_DIR | ((dirmode & S_ISGID) ? GIT_MKDIR_CHMOD : 0)); + } + + /* prettify both directories now that they are created */ + + if (!error) { + error = git_fs_path_prettify_dir(repo_path, repo_path->ptr, NULL); + + if (!error && wd_path->size > 0) + error = git_fs_path_prettify_dir(wd_path, wd_path->ptr, NULL); + } + + return error; +} + +static int repo_init_head(const char *repo_dir, const char *given) +{ + git_config *cfg = NULL; + git_str head_path = GIT_STR_INIT, cfg_branch = GIT_STR_INIT; + const char *initial_head = NULL; + int error; + + if ((error = git_str_joinpath(&head_path, repo_dir, GIT_HEAD_FILE)) < 0) + goto out; + + /* + * A template may have set a HEAD; use that unless it's been + * overridden by the caller's given initial head setting. + */ + if (git_fs_path_exists(head_path.ptr) && !given) + goto out; + + if (given) { + initial_head = given; + } else if ((error = git_config_open_default(&cfg)) >= 0 && + (error = git_config__get_string_buf(&cfg_branch, cfg, "init.defaultbranch")) >= 0 && + *cfg_branch.ptr) { + initial_head = cfg_branch.ptr; + } + + if (!initial_head) + initial_head = GIT_BRANCH_DEFAULT; + + error = git_repository_create_head(repo_dir, initial_head); + +out: + git_config_free(cfg); + git_str_dispose(&head_path); + git_str_dispose(&cfg_branch); + + return error; +} + +static int repo_init_create_origin(git_repository *repo, const char *url) +{ + int error; + git_remote *remote; + + if (!(error = git_remote_create(&remote, repo, GIT_REMOTE_ORIGIN, url))) { + git_remote_free(remote); + } + + return error; +} + +int git_repository_init( + git_repository **repo_out, const char *path, unsigned is_bare) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH; /* don't love this default */ + if (is_bare) + opts.flags |= GIT_REPOSITORY_INIT_BARE; + + return git_repository_init_ext(repo_out, path, &opts); +} + +int git_repository_init_ext( + git_repository **out, + const char *given_repo, + git_repository_init_options *opts) +{ + git_str repo_path = GIT_STR_INIT, wd_path = GIT_STR_INIT, + common_path = GIT_STR_INIT; + const char *wd; + bool is_valid; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(given_repo); + GIT_ASSERT_ARG(opts); + + GIT_ERROR_CHECK_VERSION(opts, GIT_REPOSITORY_INIT_OPTIONS_VERSION, "git_repository_init_options"); + + if ((error = repo_init_directories(&repo_path, &wd_path, given_repo, opts)) < 0) + goto out; + + wd = (opts->flags & GIT_REPOSITORY_INIT_BARE) ? NULL : git_str_cstr(&wd_path); + + if ((error = is_valid_repository_path(&is_valid, &repo_path, &common_path)) < 0) + goto out; + + if (is_valid) { + if ((opts->flags & GIT_REPOSITORY_INIT_NO_REINIT) != 0) { + git_error_set(GIT_ERROR_REPOSITORY, + "attempt to reinitialize '%s'", given_repo); + error = GIT_EEXISTS; + goto out; + } + + opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT; + + if ((error = repo_init_config(repo_path.ptr, wd, opts->flags, opts->mode)) < 0) + goto out; + + /* TODO: reinitialize the templates */ + } else { + if ((error = repo_init_structure(repo_path.ptr, wd, opts)) < 0 || + (error = repo_init_config(repo_path.ptr, wd, opts->flags, opts->mode)) < 0 || + (error = repo_init_head(repo_path.ptr, opts->initial_head)) < 0) + goto out; + } + + if ((error = git_repository_open(out, repo_path.ptr)) < 0) + goto out; + + if (opts->origin_url && + (error = repo_init_create_origin(*out, opts->origin_url)) < 0) + goto out; + +out: + git_str_dispose(&common_path); + git_str_dispose(&repo_path); + git_str_dispose(&wd_path); + + return error; +} + +int git_repository_head_detached(git_repository *repo) +{ + git_reference *ref; + git_odb *odb = NULL; + int exists; + + if (git_repository_odb__weakptr(&odb, repo) < 0) + return -1; + + if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) + return -1; + + if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { + git_reference_free(ref); + return 0; + } + + exists = git_odb_exists(odb, git_reference_target(ref)); + + git_reference_free(ref); + return exists; +} + +int git_repository_head_detached_for_worktree(git_repository *repo, const char *name) +{ + git_reference *ref = NULL; + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = git_repository_head_for_worktree(&ref, repo, name)) < 0) + goto out; + + error = (git_reference_type(ref) != GIT_REFERENCE_SYMBOLIC); +out: + git_reference_free(ref); + + return error; +} + +int git_repository_head(git_reference **head_out, git_repository *repo) +{ + git_reference *head; + int error; + + GIT_ASSERT_ARG(head_out); + + if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0) + return error; + + if (git_reference_type(head) == GIT_REFERENCE_DIRECT) { + *head_out = head; + return 0; + } + + error = git_reference_lookup_resolved(head_out, repo, git_reference_symbolic_target(head), -1); + git_reference_free(head); + + return error == GIT_ENOTFOUND ? GIT_EUNBORNBRANCH : error; +} + +int git_repository_head_for_worktree(git_reference **out, git_repository *repo, const char *name) +{ + git_repository *worktree_repo = NULL; + git_worktree *worktree = NULL; + git_reference *head = NULL; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + *out = NULL; + + if ((error = git_worktree_lookup(&worktree, repo, name)) < 0 || + (error = git_repository_open_from_worktree(&worktree_repo, worktree)) < 0 || + (error = git_reference_lookup(&head, worktree_repo, GIT_HEAD_FILE)) < 0) + goto out; + + if (git_reference_type(head) != GIT_REFERENCE_DIRECT) { + if ((error = git_reference_lookup_resolved(out, worktree_repo, git_reference_symbolic_target(head), -1)) < 0) + goto out; + } else { + *out = head; + head = NULL; + } + +out: + git_reference_free(head); + git_worktree_free(worktree); + git_repository_free(worktree_repo); + return error; +} + +int git_repository_foreach_worktree(git_repository *repo, + git_repository_foreach_worktree_cb cb, + void *payload) +{ + git_strarray worktrees = {0}; + git_repository *worktree_repo = NULL; + git_worktree *worktree = NULL; + int error; + size_t i; + + /* apply operation to repository supplied when commondir is empty, implying there's + * no linked worktrees to iterate, which can occur when using custom odb/refdb + */ + if (!repo->commondir) + return cb(repo, payload); + + if ((error = git_repository_open(&worktree_repo, repo->commondir)) < 0 || + (error = cb(worktree_repo, payload) != 0)) + goto out; + + git_repository_free(worktree_repo); + worktree_repo = NULL; + + if ((error = git_worktree_list(&worktrees, repo)) < 0) + goto out; + + for (i = 0; i < worktrees.count; i++) { + git_repository_free(worktree_repo); + worktree_repo = NULL; + git_worktree_free(worktree); + worktree = NULL; + + if ((error = git_worktree_lookup(&worktree, repo, worktrees.strings[i]) < 0) || + (error = git_repository_open_from_worktree(&worktree_repo, worktree)) < 0) { + if (error != GIT_ENOTFOUND) + goto out; + error = 0; + continue; + } + + if ((error = cb(worktree_repo, payload)) != 0) + goto out; + } + +out: + git_strarray_dispose(&worktrees); + git_repository_free(worktree_repo); + git_worktree_free(worktree); + return error; +} + +int git_repository_head_unborn(git_repository *repo) +{ + git_reference *ref = NULL; + int error; + + error = git_repository_head(&ref, repo); + git_reference_free(ref); + + if (error == GIT_EUNBORNBRANCH) { + git_error_clear(); + return 1; + } + + if (error < 0) + return -1; + + return 0; +} + +static int repo_contains_no_reference(git_repository *repo) +{ + git_reference_iterator *iter; + const char *refname; + int error; + + if ((error = git_reference_iterator_new(&iter, repo)) < 0) + return error; + + error = git_reference_next_name(&refname, iter); + git_reference_iterator_free(iter); + + if (error == GIT_ITEROVER) + return 1; + + return error; +} + +int git_repository_initialbranch(git_str *out, git_repository *repo) +{ + git_config *config; + git_config_entry *entry = NULL; + const char *branch; + int valid, error; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + return error; + + if ((error = git_config_get_entry(&entry, config, "init.defaultbranch")) == 0 && + *entry->value) { + branch = entry->value; + } + else if (!error || error == GIT_ENOTFOUND) { + branch = GIT_BRANCH_DEFAULT; + } + else { + goto done; + } + + if ((error = git_str_puts(out, GIT_REFS_HEADS_DIR)) < 0 || + (error = git_str_puts(out, branch)) < 0 || + (error = git_reference_name_is_valid(&valid, out->ptr)) < 0) + goto done; + + if (!valid) { + git_error_set(GIT_ERROR_INVALID, "the value of init.defaultBranch is not a valid branch name"); + error = -1; + } + +done: + git_config_entry_free(entry); + return error; +} + +int git_repository_is_empty(git_repository *repo) +{ + git_reference *head = NULL; + git_str initialbranch = GIT_STR_INIT; + int result = 0; + + if ((result = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0 || + (result = git_repository_initialbranch(&initialbranch, repo)) < 0) + goto done; + + result = (git_reference_type(head) == GIT_REFERENCE_SYMBOLIC && + strcmp(git_reference_symbolic_target(head), initialbranch.ptr) == 0 && + repo_contains_no_reference(repo)); + +done: + git_reference_free(head); + git_str_dispose(&initialbranch); + + return result; +} + +static const char *resolved_parent_path(const git_repository *repo, git_repository_item_t item, git_repository_item_t fallback) +{ + const char *parent; + + switch (item) { + case GIT_REPOSITORY_ITEM_GITDIR: + parent = git_repository_path(repo); + break; + case GIT_REPOSITORY_ITEM_WORKDIR: + parent = git_repository_workdir(repo); + break; + case GIT_REPOSITORY_ITEM_COMMONDIR: + parent = git_repository_commondir(repo); + break; + default: + git_error_set(GIT_ERROR_INVALID, "invalid item directory"); + return NULL; + } + if (!parent && fallback != GIT_REPOSITORY_ITEM__LAST) + return resolved_parent_path(repo, fallback, GIT_REPOSITORY_ITEM__LAST); + + return parent; +} + +int git_repository_item_path( + git_buf *out, + const git_repository *repo, + git_repository_item_t item) +{ + GIT_BUF_WRAP_PRIVATE(out, git_repository__item_path, repo, item); +} + +int git_repository__item_path( + git_str *out, + const git_repository *repo, + git_repository_item_t item) +{ + const char *parent = resolved_parent_path(repo, items[item].parent, items[item].fallback); + if (parent == NULL) { + git_error_set(GIT_ERROR_INVALID, "path cannot exist in repository"); + return GIT_ENOTFOUND; + } + + if (git_str_sets(out, parent) < 0) + return -1; + + if (items[item].name) { + if (git_str_joinpath(out, parent, items[item].name) < 0) + return -1; + } + + if (items[item].directory) { + if (git_fs_path_to_dir(out) < 0) + return -1; + } + + return 0; +} + +const char *git_repository_path(const git_repository *repo) +{ + GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); + return repo->gitdir; +} + +const char *git_repository_workdir(const git_repository *repo) +{ + GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); + + if (repo->is_bare) + return NULL; + + return repo->workdir; +} + +int git_repository_workdir_path( + git_str *out, git_repository *repo, const char *path) +{ + int error; + + if (!repo->workdir) { + git_error_set(GIT_ERROR_REPOSITORY, "repository has no working directory"); + return GIT_EBAREREPO; + } + + if (!(error = git_str_joinpath(out, repo->workdir, path))) + error = git_path_validate_str_length(repo, out); + + return error; +} + +const char *git_repository_commondir(const git_repository *repo) +{ + GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); + return repo->commondir; +} + +int git_repository_set_workdir( + git_repository *repo, const char *workdir, int update_gitlink) +{ + int error = 0; + git_str path = GIT_STR_INIT; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(workdir); + + if (git_fs_path_prettify_dir(&path, workdir, NULL) < 0) + return -1; + + if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0) + return 0; + + if (update_gitlink) { + git_config *config; + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + error = repo_write_gitlink(path.ptr, git_repository_path(repo), false); + + /* passthrough error means gitlink is unnecessary */ + if (error == GIT_PASSTHROUGH) + error = git_config_delete_entry(config, "core.worktree"); + else if (!error) + error = git_config_set_string(config, "core.worktree", path.ptr); + + if (!error) + error = git_config_set_bool(config, "core.bare", false); + } + + if (!error) { + char *old_workdir = repo->workdir; + + repo->workdir = git_str_detach(&path); + repo->is_bare = 0; + + git__free(old_workdir); + } + + return error; +} + +int git_repository_is_bare(const git_repository *repo) +{ + GIT_ASSERT_ARG(repo); + return repo->is_bare; +} + +int git_repository_is_worktree(const git_repository *repo) +{ + GIT_ASSERT_ARG(repo); + return repo->is_worktree; +} + +int git_repository_set_bare(git_repository *repo) +{ + int error; + git_config *config; + + GIT_ASSERT_ARG(repo); + + if (repo->is_bare) + return 0; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + return error; + + if ((error = git_config_set_bool(config, "core.bare", true)) < 0) + return error; + + if ((error = git_config__update_entry(config, "core.worktree", NULL, true, true)) < 0) + return error; + + git__free(repo->workdir); + repo->workdir = NULL; + repo->is_bare = 1; + + return 0; +} + +int git_repository_head_tree(git_tree **tree, git_repository *repo) +{ + git_reference *head; + git_object *obj; + int error; + + if ((error = git_repository_head(&head, repo)) < 0) + return error; + + if ((error = git_reference_peel(&obj, head, GIT_OBJECT_TREE)) < 0) + goto cleanup; + + *tree = (git_tree *)obj; + +cleanup: + git_reference_free(head); + return error; +} + +int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + char orig_head_str[GIT_OID_HEXSZ]; + int error = 0; + + git_oid_fmt(orig_head_str, orig_head); + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_ORIG_HEAD_FILE)) == 0 && + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) == 0 && + (error = git_filebuf_printf(&file, "%.*s\n", GIT_OID_HEXSZ, orig_head_str)) == 0) + error = git_filebuf_commit(&file); + + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int git_repository__message(git_str *out, git_repository *repo) +{ + git_str path = GIT_STR_INIT; + struct stat st; + int error; + + if (git_str_joinpath(&path, repo->gitdir, GIT_MERGE_MSG_FILE) < 0) + return -1; + + if ((error = p_stat(git_str_cstr(&path), &st)) < 0) { + if (errno == ENOENT) + error = GIT_ENOTFOUND; + git_error_set(GIT_ERROR_OS, "could not access message file"); + } else { + error = git_futils_readbuffer(out, git_str_cstr(&path)); + } + + git_str_dispose(&path); + + return error; +} + +int git_repository_message(git_buf *out, git_repository *repo) +{ + GIT_BUF_WRAP_PRIVATE(out, git_repository__message, repo); +} + +int git_repository_message_remove(git_repository *repo) +{ + git_str path = GIT_STR_INIT; + int error; + + if (git_str_joinpath(&path, repo->gitdir, GIT_MERGE_MSG_FILE) < 0) + return -1; + + error = p_unlink(git_str_cstr(&path)); + git_str_dispose(&path); + + return error; +} + +int git_repository_hashfile( + git_oid *out, + git_repository *repo, + const char *path, + git_object_t type, + const char *as_path) +{ + int error; + git_filter_list *fl = NULL; + git_file fd = -1; + uint64_t len; + git_str full_path = GIT_STR_INIT; + const char *workdir = git_repository_workdir(repo); + + /* as_path can be NULL */ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(path); + GIT_ASSERT_ARG(repo); + + if ((error = git_fs_path_join_unrooted(&full_path, path, workdir, NULL)) < 0 || + (error = git_path_validate_str_length(repo, &full_path)) < 0) + return error; + + /* + * NULL as_path means that we should derive it from the + * given path. + */ + if (!as_path) { + if (workdir && !git__prefixcmp(full_path.ptr, workdir)) + as_path = full_path.ptr + strlen(workdir); + else + as_path = ""; + } + + /* passing empty string for "as_path" indicated --no-filters */ + if (strlen(as_path) > 0) { + error = git_filter_list_load( + &fl, repo, NULL, as_path, + GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT); + + if (error < 0) + return error; + } + + /* at this point, error is a count of the number of loaded filters */ + + fd = git_futils_open_ro(full_path.ptr); + if (fd < 0) { + error = fd; + goto cleanup; + } + + if ((error = git_futils_filesize(&len, fd)) < 0) + goto cleanup; + + if (!git__is_sizet(len)) { + git_error_set(GIT_ERROR_OS, "file size overflow for 32-bit systems"); + error = -1; + goto cleanup; + } + + error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, fl); + +cleanup: + if (fd >= 0) + p_close(fd); + git_filter_list_free(fl); + git_str_dispose(&full_path); + + return error; +} + +static int checkout_message(git_str *out, git_reference *old, const char *new) +{ + git_str_puts(out, "checkout: moving from "); + + if (git_reference_type(old) == GIT_REFERENCE_SYMBOLIC) + git_str_puts(out, git_reference__shorthand(git_reference_symbolic_target(old))); + else + git_str_puts(out, git_oid_tostr_s(git_reference_target(old))); + + git_str_puts(out, " to "); + + if (git_reference__is_branch(new) || + git_reference__is_tag(new) || + git_reference__is_remote(new)) + git_str_puts(out, git_reference__shorthand(new)); + else + git_str_puts(out, new); + + if (git_str_oom(out)) + return -1; + + return 0; +} + +static int detach(git_repository *repo, const git_oid *id, const char *new) +{ + int error; + git_str log_message = GIT_STR_INIT; + git_object *object = NULL, *peeled = NULL; + git_reference *new_head = NULL, *current = NULL; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(id); + + if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) + return error; + + if ((error = git_object_lookup(&object, repo, id, GIT_OBJECT_ANY)) < 0) + goto cleanup; + + if ((error = git_object_peel(&peeled, object, GIT_OBJECT_COMMIT)) < 0) + goto cleanup; + + if (new == NULL) + new = git_oid_tostr_s(git_object_id(peeled)); + + if ((error = checkout_message(&log_message, current, new)) < 0) + goto cleanup; + + error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), true, git_str_cstr(&log_message)); + +cleanup: + git_str_dispose(&log_message); + git_object_free(object); + git_object_free(peeled); + git_reference_free(current); + git_reference_free(new_head); + return error; +} + +int git_repository_set_head( + git_repository *repo, + const char *refname) +{ + git_reference *ref = NULL, *current = NULL, *new_head = NULL; + git_str log_message = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(refname); + + if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) + return error; + + if ((error = checkout_message(&log_message, current, refname)) < 0) + goto cleanup; + + error = git_reference_lookup(&ref, repo, refname); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + if (ref && current->type == GIT_REFERENCE_SYMBOLIC && git__strcmp(current->target.symbolic, ref->name) && + git_reference_is_branch(ref) && git_branch_is_checked_out(ref)) { + git_error_set(GIT_ERROR_REPOSITORY, "cannot set HEAD to reference '%s' as it is the current HEAD " + "of a linked repository.", git_reference_name(ref)); + error = -1; + goto cleanup; + } + + if (!error) { + if (git_reference_is_branch(ref)) { + error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, + git_reference_name(ref), true, git_str_cstr(&log_message)); + } else { + error = detach(repo, git_reference_target(ref), + git_reference_is_tag(ref) || git_reference_is_remote(ref) ? refname : NULL); + } + } else if (git_reference__is_branch(refname)) { + error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, refname, + true, git_str_cstr(&log_message)); + } + +cleanup: + git_str_dispose(&log_message); + git_reference_free(current); + git_reference_free(ref); + git_reference_free(new_head); + return error; +} + +int git_repository_set_head_detached( + git_repository *repo, + const git_oid *committish) +{ + return detach(repo, committish, NULL); +} + +int git_repository_set_head_detached_from_annotated( + git_repository *repo, + const git_annotated_commit *committish) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(committish); + + return detach(repo, git_annotated_commit_id(committish), committish->description); +} + +int git_repository_detach_head(git_repository *repo) +{ + git_reference *old_head = NULL, *new_head = NULL, *current = NULL; + git_object *object = NULL; + git_str log_message = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(repo); + + if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) + return error; + + if ((error = git_repository_head(&old_head, repo)) < 0) + goto cleanup; + + if ((error = git_object_lookup(&object, repo, git_reference_target(old_head), GIT_OBJECT_COMMIT)) < 0) + goto cleanup; + + if ((error = checkout_message(&log_message, current, git_oid_tostr_s(git_object_id(object)))) < 0) + goto cleanup; + + error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_reference_target(old_head), + 1, git_str_cstr(&log_message)); + +cleanup: + git_str_dispose(&log_message); + git_object_free(object); + git_reference_free(old_head); + git_reference_free(new_head); + git_reference_free(current); + return error; +} + +/** + * Loosely ported from git.git + * https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh#L198-289 + */ +int git_repository_state(git_repository *repo) +{ + git_str repo_path = GIT_STR_INIT; + int state = GIT_REPOSITORY_STATE_NONE; + + GIT_ASSERT_ARG(repo); + + if (git_str_puts(&repo_path, repo->gitdir) < 0) + return -1; + + if (git_fs_path_contains_file(&repo_path, GIT_REBASE_MERGE_INTERACTIVE_FILE)) + state = GIT_REPOSITORY_STATE_REBASE_INTERACTIVE; + else if (git_fs_path_contains_dir(&repo_path, GIT_REBASE_MERGE_DIR)) + state = GIT_REPOSITORY_STATE_REBASE_MERGE; + else if (git_fs_path_contains_file(&repo_path, GIT_REBASE_APPLY_REBASING_FILE)) + state = GIT_REPOSITORY_STATE_REBASE; + else if (git_fs_path_contains_file(&repo_path, GIT_REBASE_APPLY_APPLYING_FILE)) + state = GIT_REPOSITORY_STATE_APPLY_MAILBOX; + else if (git_fs_path_contains_dir(&repo_path, GIT_REBASE_APPLY_DIR)) + state = GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE; + else if (git_fs_path_contains_file(&repo_path, GIT_MERGE_HEAD_FILE)) + state = GIT_REPOSITORY_STATE_MERGE; + else if (git_fs_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE)) { + state = GIT_REPOSITORY_STATE_REVERT; + if (git_fs_path_contains_file(&repo_path, GIT_SEQUENCER_TODO_FILE)) { + state = GIT_REPOSITORY_STATE_REVERT_SEQUENCE; + } + } else if (git_fs_path_contains_file(&repo_path, GIT_CHERRYPICK_HEAD_FILE)) { + state = GIT_REPOSITORY_STATE_CHERRYPICK; + if (git_fs_path_contains_file(&repo_path, GIT_SEQUENCER_TODO_FILE)) { + state = GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE; + } + } else if (git_fs_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE)) + state = GIT_REPOSITORY_STATE_BISECT; + + git_str_dispose(&repo_path); + return state; +} + +int git_repository__cleanup_files( + git_repository *repo, const char *files[], size_t files_len) +{ + git_str buf = GIT_STR_INIT; + size_t i; + int error; + + for (error = 0, i = 0; !error && i < files_len; ++i) { + const char *path; + + if (git_str_joinpath(&buf, repo->gitdir, files[i]) < 0) + return -1; + + path = git_str_cstr(&buf); + + if (git_fs_path_isfile(path)) { + error = p_unlink(path); + } else if (git_fs_path_isdir(path)) { + error = git_futils_rmdir_r(path, NULL, + GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS); + } + + git_str_clear(&buf); + } + + git_str_dispose(&buf); + return error; +} + +static const char *state_files[] = { + GIT_MERGE_HEAD_FILE, + GIT_MERGE_MODE_FILE, + GIT_MERGE_MSG_FILE, + GIT_REVERT_HEAD_FILE, + GIT_CHERRYPICK_HEAD_FILE, + GIT_BISECT_LOG_FILE, + GIT_REBASE_MERGE_DIR, + GIT_REBASE_APPLY_DIR, + GIT_SEQUENCER_DIR, +}; + +int git_repository_state_cleanup(git_repository *repo) +{ + GIT_ASSERT_ARG(repo); + + return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); +} + +int git_repository_is_shallow(git_repository *repo) +{ + git_str path = GIT_STR_INIT; + struct stat st; + int error; + + if ((error = git_str_joinpath(&path, repo->gitdir, "shallow")) < 0) + return error; + + error = git_fs_path_lstat(path.ptr, &st); + git_str_dispose(&path); + + if (error == GIT_ENOTFOUND) { + git_error_clear(); + return 0; + } + + if (error < 0) + return error; + return st.st_size == 0 ? 0 : 1; +} + +int git_repository_init_options_init( + git_repository_init_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_repository_init_options, + GIT_REPOSITORY_INIT_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_repository_init_init_options( + git_repository_init_options *opts, unsigned int version) +{ + return git_repository_init_options_init(opts, version); +} +#endif + +int git_repository_ident(const char **name, const char **email, const git_repository *repo) +{ + *name = repo->ident_name; + *email = repo->ident_email; + + return 0; +} + +int git_repository_set_ident(git_repository *repo, const char *name, const char *email) +{ + char *tmp_name = NULL, *tmp_email = NULL; + + if (name) { + tmp_name = git__strdup(name); + GIT_ERROR_CHECK_ALLOC(tmp_name); + } + + if (email) { + tmp_email = git__strdup(email); + GIT_ERROR_CHECK_ALLOC(tmp_email); + } + + tmp_name = git_atomic_swap(repo->ident_name, tmp_name); + tmp_email = git_atomic_swap(repo->ident_email, tmp_email); + + git__free(tmp_name); + git__free(tmp_email); + + return 0; +} + +int git_repository_submodule_cache_all(git_repository *repo) +{ + GIT_ASSERT_ARG(repo); + return git_submodule_cache_init(&repo->submodule_cache, repo); +} + +int git_repository_submodule_cache_clear(git_repository *repo) +{ + int error = 0; + GIT_ASSERT_ARG(repo); + + error = git_submodule_cache_free(repo->submodule_cache); + repo->submodule_cache = NULL; + return error; +} diff --git a/src/libgit2/repository.h b/src/libgit2/repository.h new file mode 100644 index 000000000..3c3aa1e8e --- /dev/null +++ b/src/libgit2/repository.h @@ -0,0 +1,258 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_repository_h__ +#define INCLUDE_repository_h__ + +#include "common.h" + +#include "git2/common.h" +#include "git2/oid.h" +#include "git2/odb.h" +#include "git2/repository.h" +#include "git2/object.h" +#include "git2/config.h" + +#include "array.h" +#include "cache.h" +#include "refs.h" +#include "str.h" +#include "object.h" +#include "attrcache.h" +#include "submodule.h" +#include "diff_driver.h" + +#define DOT_GIT ".git" +#define GIT_DIR DOT_GIT "/" +#define GIT_DIR_MODE 0755 +#define GIT_BARE_DIR_MODE 0777 + +/* Default DOS-compatible 8.3 "short name" for a git repository, "GIT~1" */ +#define GIT_DIR_SHORTNAME "GIT~1" + +extern bool git_repository__fsync_gitdir; + +/** Cvar cache identifiers */ +typedef enum { + GIT_CONFIGMAP_AUTO_CRLF = 0, /* core.autocrlf */ + GIT_CONFIGMAP_EOL, /* core.eol */ + GIT_CONFIGMAP_SYMLINKS, /* core.symlinks */ + GIT_CONFIGMAP_IGNORECASE, /* core.ignorecase */ + GIT_CONFIGMAP_FILEMODE, /* core.filemode */ + GIT_CONFIGMAP_IGNORESTAT, /* core.ignorestat */ + GIT_CONFIGMAP_TRUSTCTIME, /* core.trustctime */ + GIT_CONFIGMAP_ABBREV, /* core.abbrev */ + GIT_CONFIGMAP_PRECOMPOSE, /* core.precomposeunicode */ + GIT_CONFIGMAP_SAFE_CRLF, /* core.safecrlf */ + GIT_CONFIGMAP_LOGALLREFUPDATES, /* core.logallrefupdates */ + GIT_CONFIGMAP_PROTECTHFS, /* core.protectHFS */ + GIT_CONFIGMAP_PROTECTNTFS, /* core.protectNTFS */ + GIT_CONFIGMAP_FSYNCOBJECTFILES, /* core.fsyncObjectFiles */ + GIT_CONFIGMAP_LONGPATHS, /* core.longpaths */ + GIT_CONFIGMAP_CACHE_MAX +} git_configmap_item; + +/** + * Configuration map value enumerations + * + * These are the values that are actually stored in the configmap cache, + * instead of their string equivalents. These values are internal and + * symbolic; make sure that none of them is set to `-1`, since that is + * the unique identifier for "not cached" + */ +typedef enum { + /* The value hasn't been loaded from the cache yet */ + GIT_CONFIGMAP_NOT_CACHED = -1, + + /* core.safecrlf: false, 'fail', 'warn' */ + GIT_SAFE_CRLF_FALSE = 0, + GIT_SAFE_CRLF_FAIL = 1, + GIT_SAFE_CRLF_WARN = 2, + + /* core.autocrlf: false, true, 'input; */ + GIT_AUTO_CRLF_FALSE = 0, + GIT_AUTO_CRLF_TRUE = 1, + GIT_AUTO_CRLF_INPUT = 2, + GIT_AUTO_CRLF_DEFAULT = GIT_AUTO_CRLF_FALSE, + + /* core.eol: unset, 'crlf', 'lf', 'native' */ + GIT_EOL_UNSET = 0, + GIT_EOL_CRLF = 1, + GIT_EOL_LF = 2, +#ifdef GIT_WIN32 + GIT_EOL_NATIVE = GIT_EOL_CRLF, +#else + GIT_EOL_NATIVE = GIT_EOL_LF, +#endif + GIT_EOL_DEFAULT = GIT_EOL_NATIVE, + + /* core.symlinks: bool */ + GIT_SYMLINKS_DEFAULT = GIT_CONFIGMAP_TRUE, + /* core.ignorecase */ + GIT_IGNORECASE_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.filemode */ + GIT_FILEMODE_DEFAULT = GIT_CONFIGMAP_TRUE, + /* core.ignorestat */ + GIT_IGNORESTAT_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.trustctime */ + GIT_TRUSTCTIME_DEFAULT = GIT_CONFIGMAP_TRUE, + /* core.abbrev */ + GIT_ABBREV_DEFAULT = 7, + /* core.precomposeunicode */ + GIT_PRECOMPOSE_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.safecrlf */ + GIT_SAFE_CRLF_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.logallrefupdates */ + GIT_LOGALLREFUPDATES_FALSE = GIT_CONFIGMAP_FALSE, + GIT_LOGALLREFUPDATES_TRUE = GIT_CONFIGMAP_TRUE, + GIT_LOGALLREFUPDATES_UNSET = 2, + GIT_LOGALLREFUPDATES_ALWAYS = 3, + GIT_LOGALLREFUPDATES_DEFAULT = GIT_LOGALLREFUPDATES_UNSET, + /* core.protectHFS */ + GIT_PROTECTHFS_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.protectNTFS */ + GIT_PROTECTNTFS_DEFAULT = GIT_CONFIGMAP_TRUE, + /* core.fsyncObjectFiles */ + GIT_FSYNCOBJECTFILES_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.longpaths */ + GIT_LONGPATHS_DEFAULT = GIT_CONFIGMAP_FALSE +} git_configmap_value; + +/* internal repository init flags */ +enum { + GIT_REPOSITORY_INIT__HAS_DOTGIT = (1u << 16), + GIT_REPOSITORY_INIT__NATURAL_WD = (1u << 17), + GIT_REPOSITORY_INIT__IS_REINIT = (1u << 18) +}; + +/** Internal structure for repository object */ +struct git_repository { + git_odb *_odb; + git_refdb *_refdb; + git_config *_config; + git_index *_index; + + git_cache objects; + git_attr_cache *attrcache; + git_diff_driver_registry *diff_drivers; + + char *gitlink; + char *gitdir; + char *commondir; + char *workdir; + char *namespace; + + char *ident_name; + char *ident_email; + + git_array_t(git_str) reserved_names; + + unsigned is_bare:1; + unsigned is_worktree:1; + + unsigned int lru_counter; + + git_atomic32 attr_session_key; + + intptr_t configmap_cache[GIT_CONFIGMAP_CACHE_MAX]; + git_strmap *submodule_cache; +}; + +GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) +{ + return repo->attrcache; +} + +int git_repository_head_tree(git_tree **tree, git_repository *repo); +int git_repository_create_head(const char *git_dir, const char *ref_name); + +typedef int (*git_repository_foreach_worktree_cb)(git_repository *, void *); + +int git_repository_foreach_worktree(git_repository *repo, + git_repository_foreach_worktree_cb cb, + void *payload); + +/* + * Weak pointers to repository internals. + * + * The returned pointers do not need to be freed. Do not keep + * permanent references to these (i.e. between API calls), since they may + * become invalidated if the user replaces a repository internal. + */ +int git_repository_config__weakptr(git_config **out, git_repository *repo); +int git_repository_odb__weakptr(git_odb **out, git_repository *repo); +int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo); +int git_repository_index__weakptr(git_index **out, git_repository *repo); + +/* + * Configuration map cache + * + * Efficient access to the most used config variables of a repository. + * The cache is cleared every time the config backend is replaced. + */ +int git_repository__configmap_lookup(int *out, git_repository *repo, git_configmap_item item); +void git_repository__configmap_lookup_cache_clear(git_repository *repo); + +int git_repository__item_path(git_str *out, const git_repository *repo, git_repository_item_t item); + +GIT_INLINE(int) git_repository__ensure_not_bare( + git_repository *repo, + const char *operation_name) +{ + if (!git_repository_is_bare(repo)) + return 0; + + git_error_set( + GIT_ERROR_REPOSITORY, + "cannot %s. This operation is not allowed against bare repositories.", + operation_name); + + return GIT_EBAREREPO; +} + +int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head); + +int git_repository__cleanup_files(git_repository *repo, const char *files[], size_t files_len); + +/* The default "reserved names" for a repository */ +extern git_str git_repository__reserved_names_win32[]; +extern size_t git_repository__reserved_names_win32_len; + +extern git_str git_repository__reserved_names_posix[]; +extern size_t git_repository__reserved_names_posix_len; + +/* + * Gets any "reserved names" in the repository. This will return paths + * that should not be allowed in the repository (like ".git") to avoid + * conflicting with the repository path, or with alternate mechanisms to + * the repository path (eg, "GIT~1"). Every attempt will be made to look + * up all possible reserved names - if there was a conflict for the shortname + * GIT~1, for example, this function will try to look up the alternate + * shortname. If that fails, this function returns false, but out and outlen + * will still be populated with good defaults. + */ +bool git_repository__reserved_names( + git_str **out, size_t *outlen, git_repository *repo, bool include_ntfs); + +/* + * The default branch for the repository; the `init.defaultBranch` + * configuration option, if set, or `master` if it is not. + */ +int git_repository_initialbranch(git_str *out, git_repository *repo); + +/* + * Given a relative `path`, this makes it absolute based on the + * repository's working directory. This will perform validation + * to ensure that the path is not longer than MAX_PATH on Windows + * (unless `core.longpaths` is set in the repo config). + */ +int git_repository_workdir_path(git_str *out, git_repository *repo, const char *path); + +int git_repository__extensions(char ***out, size_t *out_len); +int git_repository__set_extensions(const char **extensions, size_t len); +void git_repository__free_extensions(void); + +#endif diff --git a/src/libgit2/reset.c b/src/libgit2/reset.c new file mode 100644 index 000000000..e0d942e5e --- /dev/null +++ b/src/libgit2/reset.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "commit.h" +#include "tag.h" +#include "merge.h" +#include "diff.h" +#include "annotated_commit.h" +#include "git2/reset.h" +#include "git2/checkout.h" +#include "git2/merge.h" +#include "git2/refs.h" + +#define ERROR_MSG "Cannot perform reset" + +int git_reset_default( + git_repository *repo, + const git_object *target, + const git_strarray *pathspecs) +{ + git_object *commit = NULL; + git_tree *tree = NULL; + git_diff *diff = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + size_t i, max_i; + git_index_entry entry; + int error; + git_index *index = NULL; + + GIT_ASSERT_ARG(pathspecs && pathspecs->count > 0); + + memset(&entry, 0, sizeof(git_index_entry)); + + if ((error = git_repository_index(&index, repo)) < 0) + goto cleanup; + + if (target) { + if (git_object_owner(target) != repo) { + git_error_set(GIT_ERROR_OBJECT, + "%s_default - The given target does not belong to this repository.", ERROR_MSG); + return -1; + } + + if ((error = git_object_peel(&commit, target, GIT_OBJECT_COMMIT)) < 0 || + (error = git_commit_tree(&tree, (git_commit *)commit)) < 0) + goto cleanup; + } + + opts.pathspec = *pathspecs; + opts.flags = GIT_DIFF_REVERSE; + + if ((error = git_diff_tree_to_index( + &diff, repo, tree, index, &opts)) < 0) + goto cleanup; + + for (i = 0, max_i = git_diff_num_deltas(diff); i < max_i; ++i) { + const git_diff_delta *delta = git_diff_get_delta(diff, i); + + GIT_ASSERT(delta->status == GIT_DELTA_ADDED || + delta->status == GIT_DELTA_MODIFIED || + delta->status == GIT_DELTA_CONFLICTED || + delta->status == GIT_DELTA_DELETED); + + error = git_index_conflict_remove(index, delta->old_file.path); + if (error < 0) { + if (delta->status == GIT_DELTA_ADDED && error == GIT_ENOTFOUND) + git_error_clear(); + else + goto cleanup; + } + + if (delta->status == GIT_DELTA_DELETED) { + if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0) + goto cleanup; + } else { + entry.mode = delta->new_file.mode; + git_oid_cpy(&entry.id, &delta->new_file.id); + entry.path = (char *)delta->new_file.path; + + if ((error = git_index_add(index, &entry)) < 0) + goto cleanup; + } + } + + error = git_index_write(index); + +cleanup: + git_object_free(commit); + git_tree_free(tree); + git_index_free(index); + git_diff_free(diff); + + return error; +} + +static int reset( + git_repository *repo, + const git_object *target, + const char *to, + git_reset_t reset_type, + const git_checkout_options *checkout_opts) +{ + git_object *commit = NULL; + git_index *index = NULL; + git_tree *tree = NULL; + int error = 0; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_str log_message = GIT_STR_INIT; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(target); + + if (checkout_opts) + opts = *checkout_opts; + + if (git_object_owner(target) != repo) { + git_error_set(GIT_ERROR_OBJECT, + "%s - The given target does not belong to this repository.", ERROR_MSG); + return -1; + } + + if (reset_type != GIT_RESET_SOFT && + (error = git_repository__ensure_not_bare(repo, + reset_type == GIT_RESET_MIXED ? "reset mixed" : "reset hard")) < 0) + return error; + + if ((error = git_object_peel(&commit, target, GIT_OBJECT_COMMIT)) < 0 || + (error = git_repository_index(&index, repo)) < 0 || + (error = git_commit_tree(&tree, (git_commit *)commit)) < 0) + goto cleanup; + + if (reset_type == GIT_RESET_SOFT && + (git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE || + git_index_has_conflicts(index))) + { + git_error_set(GIT_ERROR_OBJECT, "%s (soft) in the middle of a merge", ERROR_MSG); + error = GIT_EUNMERGED; + goto cleanup; + } + + if ((error = git_str_printf(&log_message, "reset: moving to %s", to)) < 0) + return error; + + if (reset_type == GIT_RESET_HARD) { + /* overwrite working directory with the new tree */ + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0) + goto cleanup; + } + + /* move HEAD to the new target */ + if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE, + git_object_id(commit), NULL, git_str_cstr(&log_message))) < 0) + goto cleanup; + + if (reset_type > GIT_RESET_SOFT) { + /* reset index to the target content */ + + if ((error = git_index_read_tree(index, tree)) < 0 || + (error = git_index_write(index)) < 0) + goto cleanup; + + if ((error = git_repository_state_cleanup(repo)) < 0) { + git_error_set(GIT_ERROR_INDEX, "%s - failed to clean up merge data", ERROR_MSG); + goto cleanup; + } + } + +cleanup: + git_object_free(commit); + git_index_free(index); + git_tree_free(tree); + git_str_dispose(&log_message); + + return error; +} + +int git_reset( + git_repository *repo, + const git_object *target, + git_reset_t reset_type, + const git_checkout_options *checkout_opts) +{ + char to[GIT_OID_HEXSZ + 1]; + + git_oid_tostr(to, GIT_OID_HEXSZ + 1, git_object_id(target)); + return reset(repo, target, to, reset_type, checkout_opts); +} + +int git_reset_from_annotated( + git_repository *repo, + const git_annotated_commit *commit, + git_reset_t reset_type, + const git_checkout_options *checkout_opts) +{ + return reset(repo, (git_object *) commit->commit, commit->description, reset_type, checkout_opts); +} diff --git a/src/libgit2/revert.c b/src/libgit2/revert.c new file mode 100644 index 000000000..d6ab6ae3c --- /dev/null +++ b/src/libgit2/revert.c @@ -0,0 +1,243 @@ +/* +* Copyright (C) the libgit2 contributors. All rights reserved. +* +* This file is part of libgit2, distributed under the GNU GPL v2 with +* a Linking Exception. For full terms see the included COPYING file. +*/ + +#include "common.h" + +#include "repository.h" +#include "filebuf.h" +#include "merge.h" +#include "index.h" + +#include "git2/types.h" +#include "git2/merge.h" +#include "git2/revert.h" +#include "git2/commit.h" +#include "git2/sys/commit.h" + +#define GIT_REVERT_FILE_MODE 0666 + +static int write_revert_head( + git_repository *repo, + const char *commit_oidstr) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + int error = 0; + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_REVERT_HEAD_FILE)) >= 0 && + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_REVERT_FILE_MODE)) >= 0 && + (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0) + error = git_filebuf_commit(&file); + + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int write_merge_msg( + git_repository *repo, + const char *commit_oidstr, + const char *commit_msgline) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str file_path = GIT_STR_INIT; + int error = 0; + + if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_REVERT_FILE_MODE)) < 0 || + (error = git_filebuf_printf(&file, "Revert \"%s\"\n\nThis reverts commit %s.\n", + commit_msgline, commit_oidstr)) < 0) + goto cleanup; + + error = git_filebuf_commit(&file); + +cleanup: + if (error < 0) + git_filebuf_cleanup(&file); + + git_str_dispose(&file_path); + + return error; +} + +static int revert_normalize_opts( + git_repository *repo, + git_revert_options *opts, + const git_revert_options *given, + const char *their_label) +{ + int error = 0; + unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_ALLOW_CONFLICTS; + + GIT_UNUSED(repo); + + if (given != NULL) + memcpy(opts, given, sizeof(git_revert_options)); + else { + git_revert_options default_opts = GIT_REVERT_OPTIONS_INIT; + memcpy(opts, &default_opts, sizeof(git_revert_options)); + } + + if (!opts->checkout_opts.checkout_strategy) + opts->checkout_opts.checkout_strategy = default_checkout_strategy; + + if (!opts->checkout_opts.our_label) + opts->checkout_opts.our_label = "HEAD"; + + if (!opts->checkout_opts.their_label) + opts->checkout_opts.their_label = their_label; + + return error; +} + +static int revert_state_cleanup(git_repository *repo) +{ + const char *state_files[] = { GIT_REVERT_HEAD_FILE, GIT_MERGE_MSG_FILE }; + + return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); +} + +static int revert_seterr(git_commit *commit, const char *fmt) +{ + char commit_oidstr[GIT_OID_HEXSZ + 1]; + + git_oid_fmt(commit_oidstr, git_commit_id(commit)); + commit_oidstr[GIT_OID_HEXSZ] = '\0'; + + git_error_set(GIT_ERROR_REVERT, fmt, commit_oidstr); + + return -1; +} + +int git_revert_commit( + git_index **out, + git_repository *repo, + git_commit *revert_commit, + git_commit *our_commit, + unsigned int mainline, + const git_merge_options *merge_opts) +{ + git_commit *parent_commit = NULL; + git_tree *parent_tree = NULL, *our_tree = NULL, *revert_tree = NULL; + int parent = 0, error = 0; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(revert_commit); + GIT_ASSERT_ARG(our_commit); + + if (git_commit_parentcount(revert_commit) > 1) { + if (!mainline) + return revert_seterr(revert_commit, + "mainline branch is not specified but %s is a merge commit"); + + parent = mainline; + } else { + if (mainline) + return revert_seterr(revert_commit, + "mainline branch specified but %s is not a merge commit"); + + parent = git_commit_parentcount(revert_commit); + } + + if (parent && + ((error = git_commit_parent(&parent_commit, revert_commit, (parent - 1))) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0)) + goto done; + + if ((error = git_commit_tree(&revert_tree, revert_commit)) < 0 || + (error = git_commit_tree(&our_tree, our_commit)) < 0) + goto done; + + error = git_merge_trees(out, repo, revert_tree, our_tree, parent_tree, merge_opts); + +done: + git_tree_free(parent_tree); + git_tree_free(our_tree); + git_tree_free(revert_tree); + git_commit_free(parent_commit); + + return error; +} + +int git_revert( + git_repository *repo, + git_commit *commit, + const git_revert_options *given_opts) +{ + git_revert_options opts; + git_reference *our_ref = NULL; + git_commit *our_commit = NULL; + char commit_oidstr[GIT_OID_HEXSZ + 1]; + const char *commit_msg; + git_str their_label = GIT_STR_INIT; + git_index *index = NULL; + git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(commit); + + GIT_ERROR_CHECK_VERSION(given_opts, GIT_REVERT_OPTIONS_VERSION, "git_revert_options"); + + if ((error = git_repository__ensure_not_bare(repo, "revert")) < 0) + return error; + + git_oid_fmt(commit_oidstr, git_commit_id(commit)); + commit_oidstr[GIT_OID_HEXSZ] = '\0'; + + if ((commit_msg = git_commit_summary(commit)) == NULL) { + error = -1; + goto on_error; + } + + if ((error = git_str_printf(&their_label, "parent of %.7s... %s", commit_oidstr, commit_msg)) < 0 || + (error = revert_normalize_opts(repo, &opts, given_opts, git_str_cstr(&their_label))) < 0 || + (error = git_indexwriter_init_for_operation(&indexwriter, repo, &opts.checkout_opts.checkout_strategy)) < 0 || + (error = write_revert_head(repo, commit_oidstr)) < 0 || + (error = write_merge_msg(repo, commit_oidstr, commit_msg)) < 0 || + (error = git_repository_head(&our_ref, repo)) < 0 || + (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJECT_COMMIT)) < 0 || + (error = git_revert_commit(&index, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 || + (error = git_merge__check_result(repo, index)) < 0 || + (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 || + (error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 || + (error = git_indexwriter_commit(&indexwriter)) < 0) + goto on_error; + + goto done; + +on_error: + revert_state_cleanup(repo); + +done: + git_indexwriter_cleanup(&indexwriter); + git_index_free(index); + git_commit_free(our_commit); + git_reference_free(our_ref); + git_str_dispose(&their_label); + + return error; +} + +int git_revert_options_init(git_revert_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_revert_options, GIT_REVERT_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_revert_init_options(git_revert_options *opts, unsigned int version) +{ + return git_revert_options_init(opts, version); +} +#endif diff --git a/src/libgit2/revparse.c b/src/libgit2/revparse.c new file mode 100644 index 000000000..9bc28e9fc --- /dev/null +++ b/src/libgit2/revparse.c @@ -0,0 +1,949 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "str.h" +#include "tree.h" +#include "refdb.h" +#include "regexp.h" +#include "date.h" + +#include "git2.h" + +static int maybe_sha_or_abbrev(git_object **out, git_repository *repo, const char *spec, size_t speclen) +{ + git_oid oid; + + if (git_oid_fromstrn(&oid, spec, speclen) < 0) + return GIT_ENOTFOUND; + + return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJECT_ANY); +} + +static int maybe_sha(git_object **out, git_repository *repo, const char *spec) +{ + size_t speclen = strlen(spec); + + if (speclen != GIT_OID_HEXSZ) + return GIT_ENOTFOUND; + + return maybe_sha_or_abbrev(out, repo, spec, speclen); +} + +static int maybe_abbrev(git_object **out, git_repository *repo, const char *spec) +{ + size_t speclen = strlen(spec); + + return maybe_sha_or_abbrev(out, repo, spec, speclen); +} + +static int build_regex(git_regexp *regex, const char *pattern) +{ + int error; + + if (*pattern == '\0') { + git_error_set(GIT_ERROR_REGEX, "empty pattern"); + return GIT_EINVALIDSPEC; + } + + error = git_regexp_compile(regex, pattern, 0); + if (!error) + return 0; + + git_regexp_dispose(regex); + + return error; +} + +static int maybe_describe(git_object**out, git_repository *repo, const char *spec) +{ + const char *substr; + int error; + git_regexp regex; + + substr = strstr(spec, "-g"); + + if (substr == NULL) + return GIT_ENOTFOUND; + + if (build_regex(®ex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0) + return -1; + + error = git_regexp_match(®ex, spec); + git_regexp_dispose(®ex); + + if (error) + return GIT_ENOTFOUND; + + return maybe_abbrev(out, repo, substr+2); +} + +static int revparse_lookup_object( + git_object **object_out, + git_reference **reference_out, + git_repository *repo, + const char *spec) +{ + int error; + git_reference *ref; + + if ((error = maybe_sha(object_out, repo, spec)) != GIT_ENOTFOUND) + return error; + + error = git_reference_dwim(&ref, repo, spec); + if (!error) { + + error = git_object_lookup( + object_out, repo, git_reference_target(ref), GIT_OBJECT_ANY); + + if (!error) + *reference_out = ref; + + return error; + } + + if (error != GIT_ENOTFOUND) + return error; + + if ((strlen(spec) < GIT_OID_HEXSZ) && + ((error = maybe_abbrev(object_out, repo, spec)) != GIT_ENOTFOUND)) + return error; + + if ((error = maybe_describe(object_out, repo, spec)) != GIT_ENOTFOUND) + return error; + + git_error_set(GIT_ERROR_REFERENCE, "revspec '%s' not found", spec); + return GIT_ENOTFOUND; +} + +static int try_parse_numeric(int *n, const char *curly_braces_content) +{ + int32_t content; + const char *end_ptr; + + if (git__strntol32(&content, curly_braces_content, strlen(curly_braces_content), + &end_ptr, 10) < 0) + return -1; + + if (*end_ptr != '\0') + return -1; + + *n = (int)content; + return 0; +} + +static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position) +{ + git_reference *ref = NULL; + git_reflog *reflog = NULL; + git_regexp preg; + int error = -1; + size_t i, numentries, cur; + const git_reflog_entry *entry; + const char *msg; + git_str buf = GIT_STR_INIT; + + cur = position; + + if (*identifier != '\0' || *base_ref != NULL) + return GIT_EINVALIDSPEC; + + if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0) + return -1; + + if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) + goto cleanup; + + if (git_reflog_read(&reflog, repo, GIT_HEAD_FILE) < 0) + goto cleanup; + + numentries = git_reflog_entrycount(reflog); + + for (i = 0; i < numentries; i++) { + git_regmatch regexmatches[2]; + + entry = git_reflog_entry_byindex(reflog, i); + msg = git_reflog_entry_message(entry); + if (!msg) + continue; + + if (git_regexp_search(&preg, msg, 2, regexmatches) < 0) + continue; + + cur--; + + if (cur > 0) + continue; + + if ((git_str_put(&buf, msg+regexmatches[1].start, regexmatches[1].end - regexmatches[1].start)) < 0) + goto cleanup; + + if ((error = git_reference_dwim(base_ref, repo, git_str_cstr(&buf))) == 0) + goto cleanup; + + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + error = maybe_abbrev(out, repo, git_str_cstr(&buf)); + + goto cleanup; + } + + error = GIT_ENOTFOUND; + +cleanup: + git_reference_free(ref); + git_str_dispose(&buf); + git_regexp_dispose(&preg); + git_reflog_free(reflog); + return error; +} + +static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t identifier) +{ + git_reflog *reflog; + size_t numentries; + const git_reflog_entry *entry = NULL; + bool search_by_pos = (identifier <= 100000000); + + if (git_reflog_read(&reflog, git_reference_owner(ref), git_reference_name(ref)) < 0) + return -1; + + numentries = git_reflog_entrycount(reflog); + + if (search_by_pos) { + if (numentries < identifier + 1) + goto notfound; + + entry = git_reflog_entry_byindex(reflog, identifier); + git_oid_cpy(oid, git_reflog_entry_id_new(entry)); + } else { + size_t i; + git_time commit_time; + + for (i = 0; i < numentries; i++) { + entry = git_reflog_entry_byindex(reflog, i); + commit_time = git_reflog_entry_committer(entry)->when; + + if (commit_time.time > (git_time_t)identifier) + continue; + + git_oid_cpy(oid, git_reflog_entry_id_new(entry)); + break; + } + + if (i == numentries) { + if (entry == NULL) + goto notfound; + + /* + * TODO: emit a warning (log for 'branch' only goes back to ...) + */ + git_oid_cpy(oid, git_reflog_entry_id_new(entry)); + } + } + + git_reflog_free(reflog); + return 0; + +notfound: + git_error_set( + GIT_ERROR_REFERENCE, + "reflog for '%s' has only %"PRIuZ" entries, asked for %"PRIuZ, + git_reference_name(ref), numentries, identifier); + + git_reflog_free(reflog); + return GIT_ENOTFOUND; +} + +static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position) +{ + git_reference *ref; + git_oid oid; + int error = -1; + + if (*base_ref == NULL) { + if ((error = git_reference_dwim(&ref, repo, identifier)) < 0) + return error; + } else { + ref = *base_ref; + *base_ref = NULL; + } + + if (position == 0) { + error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJECT_ANY); + goto cleanup; + } + + if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0) + goto cleanup; + + error = git_object_lookup(out, repo, &oid, GIT_OBJECT_ANY); + +cleanup: + git_reference_free(ref); + return error; +} + +static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo) +{ + git_reference *tracking, *ref; + int error = -1; + + if (*base_ref == NULL) { + if ((error = git_reference_dwim(&ref, repo, identifier)) < 0) + return error; + } else { + ref = *base_ref; + *base_ref = NULL; + } + + if (!git_reference_is_branch(ref)) { + error = GIT_EINVALIDSPEC; + goto cleanup; + } + + if ((error = git_branch_upstream(&tracking, ref)) < 0) + goto cleanup; + + *base_ref = tracking; + +cleanup: + git_reference_free(ref); + return error; +} + +static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, size_t identifier_len, git_repository *repo, const char *curly_braces_content) +{ + bool is_numeric; + int parsed = 0, error = -1; + git_str identifier = GIT_STR_INIT; + git_time_t timestamp; + + GIT_ASSERT(*out == NULL); + + if (git_str_put(&identifier, spec, identifier_len) < 0) + return -1; + + is_numeric = !try_parse_numeric(&parsed, curly_braces_content); + + if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) { + error = GIT_EINVALIDSPEC; + goto cleanup; + } + + if (is_numeric) { + if (parsed < 0) + error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, git_str_cstr(&identifier), -parsed); + else + error = retrieve_revobject_from_reflog(out, ref, repo, git_str_cstr(&identifier), parsed); + + goto cleanup; + } + + if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) { + error = retrieve_remote_tracking_reference(ref, git_str_cstr(&identifier), repo); + + goto cleanup; + } + + if (git_date_parse(×tamp, curly_braces_content) < 0) { + error = GIT_EINVALIDSPEC; + goto cleanup; + } + + error = retrieve_revobject_from_reflog(out, ref, repo, git_str_cstr(&identifier), (size_t)timestamp); + +cleanup: + git_str_dispose(&identifier); + return error; +} + +static git_object_t parse_obj_type(const char *str) +{ + if (!strcmp(str, "commit")) + return GIT_OBJECT_COMMIT; + + if (!strcmp(str, "tree")) + return GIT_OBJECT_TREE; + + if (!strcmp(str, "blob")) + return GIT_OBJECT_BLOB; + + if (!strcmp(str, "tag")) + return GIT_OBJECT_TAG; + + return GIT_OBJECT_INVALID; +} + +static int dereference_to_non_tag(git_object **out, git_object *obj) +{ + if (git_object_type(obj) == GIT_OBJECT_TAG) + return git_tag_peel(out, (git_tag *)obj); + + return git_object_dup(out, obj); +} + +static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n) +{ + git_object *temp_commit = NULL; + int error; + + if ((error = git_object_peel(&temp_commit, obj, GIT_OBJECT_COMMIT)) < 0) + return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ? + GIT_EINVALIDSPEC : error; + + if (n == 0) { + *out = temp_commit; + return 0; + } + + error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1); + + git_object_free(temp_commit); + return error; +} + +static int handle_linear_syntax(git_object **out, git_object *obj, int n) +{ + git_object *temp_commit = NULL; + int error; + + if ((error = git_object_peel(&temp_commit, obj, GIT_OBJECT_COMMIT)) < 0) + return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ? + GIT_EINVALIDSPEC : error; + + error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n); + + git_object_free(temp_commit); + return error; +} + +static int handle_colon_syntax( + git_object **out, + git_object *obj, + const char *path) +{ + git_object *tree; + int error = -1; + git_tree_entry *entry = NULL; + + if ((error = git_object_peel(&tree, obj, GIT_OBJECT_TREE)) < 0) + return error == GIT_ENOTFOUND ? GIT_EINVALIDSPEC : error; + + if (*path == '\0') { + *out = tree; + return 0; + } + + /* + * TODO: Handle the relative path syntax + * (:./relative/path and :../relative/path) + */ + if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0) + goto cleanup; + + error = git_tree_entry_to_object(out, git_object_owner(tree), entry); + +cleanup: + git_tree_entry_free(entry); + git_object_free(tree); + + return error; +} + +static int walk_and_search(git_object **out, git_revwalk *walk, git_regexp *regex) +{ + int error; + git_oid oid; + git_object *obj; + + while (!(error = git_revwalk_next(&oid, walk))) { + + error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJECT_COMMIT); + if ((error < 0) && (error != GIT_ENOTFOUND)) + return -1; + + if (!git_regexp_match(regex, git_commit_message((git_commit*)obj))) { + *out = obj; + return 0; + } + + git_object_free(obj); + } + + if (error < 0 && error == GIT_ITEROVER) + error = GIT_ENOTFOUND; + + return error; +} + +static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern) +{ + git_regexp preg; + git_revwalk *walk = NULL; + int error; + + if ((error = build_regex(&preg, pattern)) < 0) + return error; + + if ((error = git_revwalk_new(&walk, repo)) < 0) + goto cleanup; + + git_revwalk_sorting(walk, GIT_SORT_TIME); + + if (spec_oid == NULL) { + if ((error = git_revwalk_push_glob(walk, "refs/*")) < 0) + goto cleanup; + } else if ((error = git_revwalk_push(walk, spec_oid)) < 0) + goto cleanup; + + error = walk_and_search(out, walk, &preg); + +cleanup: + git_regexp_dispose(&preg); + git_revwalk_free(walk); + + return error; +} + +static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content) +{ + git_object_t expected_type; + + if (*curly_braces_content == '\0') + return dereference_to_non_tag(out, obj); + + if (*curly_braces_content == '/') + return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1); + + expected_type = parse_obj_type(curly_braces_content); + + if (expected_type == GIT_OBJECT_INVALID) + return GIT_EINVALIDSPEC; + + return git_object_peel(out, obj, expected_type); +} + +static int extract_curly_braces_content(git_str *buf, const char *spec, size_t *pos) +{ + git_str_clear(buf); + + GIT_ASSERT_ARG(spec[*pos] == '^' || spec[*pos] == '@'); + + (*pos)++; + + if (spec[*pos] == '\0' || spec[*pos] != '{') + return GIT_EINVALIDSPEC; + + (*pos)++; + + while (spec[*pos] != '}') { + if (spec[*pos] == '\0') + return GIT_EINVALIDSPEC; + + if (git_str_putc(buf, spec[(*pos)++]) < 0) + return -1; + } + + (*pos)++; + + return 0; +} + +static int extract_path(git_str *buf, const char *spec, size_t *pos) +{ + git_str_clear(buf); + + GIT_ASSERT_ARG(spec[*pos] == ':'); + + (*pos)++; + + if (git_str_puts(buf, spec + *pos) < 0) + return -1; + + *pos += git_str_len(buf); + + return 0; +} + +static int extract_how_many(int *n, const char *spec, size_t *pos) +{ + const char *end_ptr; + int parsed, accumulated; + char kind = spec[*pos]; + + GIT_ASSERT_ARG(spec[*pos] == '^' || spec[*pos] == '~'); + + accumulated = 0; + + do { + do { + (*pos)++; + accumulated++; + } while (spec[(*pos)] == kind && kind == '~'); + + if (git__isdigit(spec[*pos])) { + if (git__strntol32(&parsed, spec + *pos, strlen(spec + *pos), &end_ptr, 10) < 0) + return GIT_EINVALIDSPEC; + + accumulated += (parsed - 1); + *pos = end_ptr - spec; + } + + } while (spec[(*pos)] == kind && kind == '~'); + + *n = accumulated; + + return 0; +} + +static int object_from_reference(git_object **object, git_reference *reference) +{ + git_reference *resolved = NULL; + int error; + + if (git_reference_resolve(&resolved, reference) < 0) + return -1; + + error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJECT_ANY); + git_reference_free(resolved); + + return error; +} + +static int ensure_base_rev_loaded(git_object **object, git_reference **reference, const char *spec, size_t identifier_len, git_repository *repo, bool allow_empty_identifier) +{ + int error; + git_str identifier = GIT_STR_INIT; + + if (*object != NULL) + return 0; + + if (*reference != NULL) + return object_from_reference(object, *reference); + + if (!allow_empty_identifier && identifier_len == 0) + return GIT_EINVALIDSPEC; + + if (git_str_put(&identifier, spec, identifier_len) < 0) + return -1; + + error = revparse_lookup_object(object, reference, repo, git_str_cstr(&identifier)); + git_str_dispose(&identifier); + + return error; +} + +static int ensure_base_rev_is_not_known_yet(git_object *object) +{ + if (object == NULL) + return 0; + + return GIT_EINVALIDSPEC; +} + +static bool any_left_hand_identifier(git_object *object, git_reference *reference, size_t identifier_len) +{ + if (object != NULL) + return true; + + if (reference != NULL) + return true; + + if (identifier_len > 0) + return true; + + return false; +} + +static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference) +{ + if (!ensure_base_rev_is_not_known_yet(object) && reference == NULL) + return 0; + + return GIT_EINVALIDSPEC; +} + +static int revparse( + git_object **object_out, + git_reference **reference_out, + size_t *identifier_len_out, + git_repository *repo, + const char *spec) +{ + size_t pos = 0, identifier_len = 0; + int error = -1, n; + git_str buf = GIT_STR_INIT; + + git_reference *reference = NULL; + git_object *base_rev = NULL; + + bool should_return_reference = true; + + GIT_ASSERT_ARG(object_out); + GIT_ASSERT_ARG(reference_out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(spec); + + *object_out = NULL; + *reference_out = NULL; + + while (spec[pos]) { + switch (spec[pos]) { + case '^': + should_return_reference = false; + + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; + + if (spec[pos+1] == '{') { + git_object *temp_object = NULL; + + if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0) + goto cleanup; + + if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_str_cstr(&buf))) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; + } else { + git_object *temp_object = NULL; + + if ((error = extract_how_many(&n, spec, &pos)) < 0) + goto cleanup; + + if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; + } + break; + + case '~': + { + git_object *temp_object = NULL; + + should_return_reference = false; + + if ((error = extract_how_many(&n, spec, &pos)) < 0) + goto cleanup; + + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; + + if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; + break; + } + + case ':': + { + git_object *temp_object = NULL; + + should_return_reference = false; + + if ((error = extract_path(&buf, spec, &pos)) < 0) + goto cleanup; + + if (any_left_hand_identifier(base_rev, reference, identifier_len)) { + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0) + goto cleanup; + + if ((error = handle_colon_syntax(&temp_object, base_rev, git_str_cstr(&buf))) < 0) + goto cleanup; + } else { + if (*git_str_cstr(&buf) == '/') { + if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_str_cstr(&buf) + 1)) < 0) + goto cleanup; + } else { + + /* + * TODO: support merge-stage path lookup (":2:Makefile") + * and plain index blob lookup (:i-am/a/blob) + */ + git_error_set(GIT_ERROR_INVALID, "unimplemented"); + error = GIT_ERROR; + goto cleanup; + } + } + + git_object_free(base_rev); + base_rev = temp_object; + break; + } + + case '@': + if (spec[pos+1] == '{') { + git_object *temp_object = NULL; + + if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0) + goto cleanup; + + if ((error = ensure_base_rev_is_not_known_yet(base_rev)) < 0) + goto cleanup; + + if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_str_cstr(&buf))) < 0) + goto cleanup; + + if (temp_object != NULL) + base_rev = temp_object; + break; + } else if (spec[pos+1] == '\0') { + spec = "HEAD"; + break; + } + /* fall through */ + + default: + if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference)) < 0) + goto cleanup; + + pos++; + identifier_len++; + } + } + + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; + + if (!should_return_reference) { + git_reference_free(reference); + reference = NULL; + } + + *object_out = base_rev; + *reference_out = reference; + *identifier_len_out = identifier_len; + error = 0; + +cleanup: + if (error) { + if (error == GIT_EINVALIDSPEC) + git_error_set(GIT_ERROR_INVALID, + "failed to parse revision specifier - Invalid pattern '%s'", spec); + + git_object_free(base_rev); + git_reference_free(reference); + } + + git_str_dispose(&buf); + return error; +} + +int git_revparse_ext( + git_object **object_out, + git_reference **reference_out, + git_repository *repo, + const char *spec) +{ + int error; + size_t identifier_len; + git_object *obj = NULL; + git_reference *ref = NULL; + + if ((error = revparse(&obj, &ref, &identifier_len, repo, spec)) < 0) + goto cleanup; + + *object_out = obj; + *reference_out = ref; + GIT_UNUSED(identifier_len); + + return 0; + +cleanup: + git_object_free(obj); + git_reference_free(ref); + return error; +} + +int git_revparse_single(git_object **out, git_repository *repo, const char *spec) +{ + int error; + git_object *obj = NULL; + git_reference *ref = NULL; + + *out = NULL; + + if ((error = git_revparse_ext(&obj, &ref, repo, spec)) < 0) + goto cleanup; + + git_reference_free(ref); + + *out = obj; + + return 0; + +cleanup: + git_object_free(obj); + git_reference_free(ref); + return error; +} + +int git_revparse( + git_revspec *revspec, + git_repository *repo, + const char *spec) +{ + const char *dotdot; + int error = 0; + + GIT_ASSERT_ARG(revspec); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(spec); + + memset(revspec, 0x0, sizeof(*revspec)); + + if ((dotdot = strstr(spec, "..")) != NULL) { + char *lstr; + const char *rstr; + revspec->flags = GIT_REVSPEC_RANGE; + + /* + * Following git.git, don't allow '..' because it makes command line + * arguments which can be either paths or revisions ambiguous when the + * path is almost certainly intended. The empty range '...' is still + * allowed. + */ + if (!git__strcmp(spec, "..")) { + git_error_set(GIT_ERROR_INVALID, "Invalid pattern '..'"); + return GIT_EINVALIDSPEC; + } + + lstr = git__substrdup(spec, dotdot - spec); + rstr = dotdot + 2; + if (dotdot[2] == '.') { + revspec->flags |= GIT_REVSPEC_MERGE_BASE; + rstr++; + } + + error = git_revparse_single( + &revspec->from, + repo, + *lstr == '\0' ? "HEAD" : lstr); + + if (!error) { + error = git_revparse_single( + &revspec->to, + repo, + *rstr == '\0' ? "HEAD" : rstr); + } + + git__free((void*)lstr); + } else { + revspec->flags = GIT_REVSPEC_SINGLE; + error = git_revparse_single(&revspec->from, repo, spec); + } + + return error; +} diff --git a/src/libgit2/revwalk.c b/src/libgit2/revwalk.c new file mode 100644 index 000000000..553e0497a --- /dev/null +++ b/src/libgit2/revwalk.c @@ -0,0 +1,820 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "revwalk.h" + +#include "commit.h" +#include "odb.h" +#include "pool.h" + +#include "git2/revparse.h" +#include "merge.h" +#include "vector.h" + +static int get_revision(git_commit_list_node **out, git_revwalk *walk, git_commit_list **list); + +git_commit_list_node *git_revwalk__commit_lookup( + git_revwalk *walk, const git_oid *oid) +{ + git_commit_list_node *commit; + + /* lookup and reserve space if not already present */ + if ((commit = git_oidmap_get(walk->commits, oid)) != NULL) + return commit; + + commit = git_commit_list_alloc_node(walk); + if (commit == NULL) + return NULL; + + git_oid_cpy(&commit->oid, oid); + + if ((git_oidmap_set(walk->commits, &commit->oid, commit)) < 0) + return NULL; + + return commit; +} + +int git_revwalk__push_commit(git_revwalk *walk, const git_oid *oid, const git_revwalk__push_options *opts) +{ + git_oid commit_id; + int error; + git_object *obj, *oobj; + git_commit_list_node *commit; + git_commit_list *list; + + if ((error = git_object_lookup(&oobj, walk->repo, oid, GIT_OBJECT_ANY)) < 0) + return error; + + error = git_object_peel(&obj, oobj, GIT_OBJECT_COMMIT); + git_object_free(oobj); + + if (error == GIT_ENOTFOUND || error == GIT_EINVALIDSPEC || error == GIT_EPEEL) { + /* If this comes from e.g. push_glob("tags"), ignore this */ + if (opts->from_glob) + return 0; + + git_error_set(GIT_ERROR_INVALID, "object is not a committish"); + return error; + } + if (error < 0) + return error; + + git_oid_cpy(&commit_id, git_object_id(obj)); + git_object_free(obj); + + commit = git_revwalk__commit_lookup(walk, &commit_id); + if (commit == NULL) + return -1; /* error already reported by failed lookup */ + + /* A previous hide already told us we don't want this commit */ + if (commit->uninteresting) + return 0; + + if (opts->uninteresting) { + walk->limited = 1; + walk->did_hide = 1; + } else { + walk->did_push = 1; + } + + commit->uninteresting = opts->uninteresting; + list = walk->user_input; + if ((opts->insert_by_date && + git_commit_list_insert_by_date(commit, &list) == NULL) || + git_commit_list_insert(commit, &list) == NULL) { + git_error_set_oom(); + return -1; + } + + walk->user_input = list; + + return 0; +} + +int git_revwalk_push(git_revwalk *walk, const git_oid *oid) +{ + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(oid); + + return git_revwalk__push_commit(walk, oid, &opts); +} + + +int git_revwalk_hide(git_revwalk *walk, const git_oid *oid) +{ + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(oid); + + opts.uninteresting = 1; + return git_revwalk__push_commit(walk, oid, &opts); +} + +int git_revwalk__push_ref(git_revwalk *walk, const char *refname, const git_revwalk__push_options *opts) +{ + git_oid oid; + + if (git_reference_name_to_id(&oid, walk->repo, refname) < 0) + return -1; + + return git_revwalk__push_commit(walk, &oid, opts); +} + +int git_revwalk__push_glob(git_revwalk *walk, const char *glob, const git_revwalk__push_options *given_opts) +{ + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + int error = 0; + git_str buf = GIT_STR_INIT; + git_reference *ref; + git_reference_iterator *iter; + size_t wildcard; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(glob); + + if (given_opts) + memcpy(&opts, given_opts, sizeof(opts)); + + /* refs/ is implied if not given in the glob */ + if (git__prefixcmp(glob, GIT_REFS_DIR) != 0) + git_str_joinpath(&buf, GIT_REFS_DIR, glob); + else + git_str_puts(&buf, glob); + GIT_ERROR_CHECK_ALLOC_STR(&buf); + + /* If no '?', '*' or '[' exist, we append '/ *' to the glob */ + wildcard = strcspn(glob, "?*["); + if (!glob[wildcard]) + git_str_put(&buf, "/*", 2); + + if ((error = git_reference_iterator_glob_new(&iter, walk->repo, buf.ptr)) < 0) + goto out; + + opts.from_glob = true; + while ((error = git_reference_next(&ref, iter)) == 0) { + error = git_revwalk__push_ref(walk, git_reference_name(ref), &opts); + git_reference_free(ref); + if (error < 0) + break; + } + git_reference_iterator_free(iter); + + if (error == GIT_ITEROVER) + error = 0; +out: + git_str_dispose(&buf); + return error; +} + +int git_revwalk_push_glob(git_revwalk *walk, const char *glob) +{ + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(glob); + + return git_revwalk__push_glob(walk, glob, &opts); +} + +int git_revwalk_hide_glob(git_revwalk *walk, const char *glob) +{ + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(glob); + + opts.uninteresting = 1; + return git_revwalk__push_glob(walk, glob, &opts); +} + +int git_revwalk_push_head(git_revwalk *walk) +{ + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + + GIT_ASSERT_ARG(walk); + + return git_revwalk__push_ref(walk, GIT_HEAD_FILE, &opts); +} + +int git_revwalk_hide_head(git_revwalk *walk) +{ + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + + GIT_ASSERT_ARG(walk); + + opts.uninteresting = 1; + return git_revwalk__push_ref(walk, GIT_HEAD_FILE, &opts); +} + +int git_revwalk_push_ref(git_revwalk *walk, const char *refname) +{ + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(refname); + + return git_revwalk__push_ref(walk, refname, &opts); +} + +int git_revwalk_push_range(git_revwalk *walk, const char *range) +{ + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + git_revspec revspec; + int error = 0; + + if ((error = git_revparse(&revspec, walk->repo, range))) + return error; + + if (!revspec.to) { + git_error_set(GIT_ERROR_INVALID, "invalid revspec: range not provided"); + error = GIT_EINVALIDSPEC; + goto out; + } + + if (revspec.flags & GIT_REVSPEC_MERGE_BASE) { + /* TODO: support "..." */ + git_error_set(GIT_ERROR_INVALID, "symmetric differences not implemented in revwalk"); + error = GIT_EINVALIDSPEC; + goto out; + } + + opts.uninteresting = 1; + if ((error = git_revwalk__push_commit(walk, git_object_id(revspec.from), &opts))) + goto out; + + opts.uninteresting = 0; + error = git_revwalk__push_commit(walk, git_object_id(revspec.to), &opts); + +out: + git_object_free(revspec.from); + git_object_free(revspec.to); + return error; +} + +int git_revwalk_hide_ref(git_revwalk *walk, const char *refname) +{ + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(refname); + + opts.uninteresting = 1; + return git_revwalk__push_ref(walk, refname, &opts); +} + +static int revwalk_enqueue_timesort(git_revwalk *walk, git_commit_list_node *commit) +{ + return git_pqueue_insert(&walk->iterator_time, commit); +} + +static int revwalk_enqueue_unsorted(git_revwalk *walk, git_commit_list_node *commit) +{ + return git_commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1; +} + +static int revwalk_next_timesort(git_commit_list_node **object_out, git_revwalk *walk) +{ + git_commit_list_node *next; + + while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) { + /* Some commits might become uninteresting after being added to the list */ + if (!next->uninteresting) { + *object_out = next; + return 0; + } + } + + git_error_clear(); + return GIT_ITEROVER; +} + +static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk *walk) +{ + int error; + git_commit_list_node *next; + + while (!(error = get_revision(&next, walk, &walk->iterator_rand))) { + /* Some commits might become uninteresting after being added to the list */ + if (!next->uninteresting) { + *object_out = next; + return 0; + } + } + + return error; +} + +static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk *walk) +{ + int error; + git_commit_list_node *next; + + while (!(error = get_revision(&next, walk, &walk->iterator_topo))) { + /* Some commits might become uninteresting after being added to the list */ + if (!next->uninteresting) { + *object_out = next; + return 0; + } + } + + return error; +} + +static int revwalk_next_reverse(git_commit_list_node **object_out, git_revwalk *walk) +{ + *object_out = git_commit_list_pop(&walk->iterator_reverse); + return *object_out ? 0 : GIT_ITEROVER; +} + +static void mark_parents_uninteresting(git_commit_list_node *commit) +{ + unsigned short i; + git_commit_list *parents = NULL; + + for (i = 0; i < commit->out_degree; i++) + git_commit_list_insert(commit->parents[i], &parents); + + + while (parents) { + commit = git_commit_list_pop(&parents); + + while (commit) { + if (commit->uninteresting) + break; + + commit->uninteresting = 1; + /* + * If we've reached this commit some other way + * already, we need to mark its parents uninteresting + * as well. + */ + if (!commit->parents) + break; + + for (i = 0; i < commit->out_degree; i++) + git_commit_list_insert(commit->parents[i], &parents); + commit = commit->parents[0]; + } + } +} + +static int add_parents_to_list(git_revwalk *walk, git_commit_list_node *commit, git_commit_list **list) +{ + unsigned short i; + int error; + + if (commit->added) + return 0; + + commit->added = 1; + + /* + * Go full on in the uninteresting case as we want to include + * as many of these as we can. + * + * Usually we haven't parsed the parent of a parent, but if we + * have it we reached it via other means so we want to mark + * its parents recursively too. + */ + if (commit->uninteresting) { + for (i = 0; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + p->uninteresting = 1; + + /* git does it gently here, but we don't like missing objects */ + if ((error = git_commit_list_parse(walk, p)) < 0) + return error; + + if (p->parents) + mark_parents_uninteresting(p); + + p->seen = 1; + git_commit_list_insert_by_date(p, list); + } + + return 0; + } + + /* + * Now on to what we do if the commit is indeed + * interesting. Here we do want things like first-parent take + * effect as this is what we'll be showing. + */ + for (i = 0; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + + if ((error = git_commit_list_parse(walk, p)) < 0) + return error; + + if (walk->hide_cb && walk->hide_cb(&p->oid, walk->hide_cb_payload)) + continue; + + if (!p->seen) { + p->seen = 1; + git_commit_list_insert_by_date(p, list); + } + + if (walk->first_parent) + break; + } + return 0; +} + +/* How many uninteresting commits we want to look at after we run out of interesting ones */ +#define SLOP 5 + +static int still_interesting(git_commit_list *list, int64_t time, int slop) +{ + /* The empty list is pretty boring */ + if (!list) + return 0; + + /* + * If the destination list has commits with an earlier date than our + * source, we want to reset the slop counter as we're not done. + */ + if (time <= list->item->time) + return SLOP; + + for (; list; list = list->next) { + /* + * If the destination list still contains interesting commits we + * want to continue looking. + */ + if (!list->item->uninteresting || list->item->time > time) + return SLOP; + } + + /* Everything's uninteresting, reduce the count */ + return slop - 1; +} + +static int limit_list(git_commit_list **out, git_revwalk *walk, git_commit_list *commits) +{ + int error, slop = SLOP; + int64_t time = INT64_MAX; + git_commit_list *list = commits; + git_commit_list *newlist = NULL; + git_commit_list **p = &newlist; + + while (list) { + git_commit_list_node *commit = git_commit_list_pop(&list); + + if ((error = add_parents_to_list(walk, commit, &list)) < 0) + return error; + + if (commit->uninteresting) { + mark_parents_uninteresting(commit); + + slop = still_interesting(list, time, slop); + if (slop) + continue; + + break; + } + + if (walk->hide_cb && walk->hide_cb(&commit->oid, walk->hide_cb_payload)) + continue; + + time = commit->time; + p = &git_commit_list_insert(commit, p)->next; + } + + git_commit_list_free(&list); + *out = newlist; + return 0; +} + +static int get_revision(git_commit_list_node **out, git_revwalk *walk, git_commit_list **list) +{ + int error; + git_commit_list_node *commit; + + commit = git_commit_list_pop(list); + if (!commit) { + git_error_clear(); + return GIT_ITEROVER; + } + + /* + * If we did not run limit_list and we must add parents to the + * list ourselves. + */ + if (!walk->limited) { + if ((error = add_parents_to_list(walk, commit, list)) < 0) + return error; + } + + *out = commit; + return 0; +} + +static int sort_in_topological_order(git_commit_list **out, git_revwalk *walk, git_commit_list *list) +{ + git_commit_list *ll = NULL, *newlist, **pptr; + git_commit_list_node *next; + git_pqueue queue; + git_vector_cmp queue_cmp = NULL; + unsigned short i; + int error; + + if (walk->sorting & GIT_SORT_TIME) + queue_cmp = git_commit_list_time_cmp; + + if ((error = git_pqueue_init(&queue, 0, 8, queue_cmp))) + return error; + + /* + * Start by resetting the in-degree to 1 for the commits in + * our list. We want to go through this list again, so we + * store it in the commit list as we extract it from the lower + * machinery. + */ + for (ll = list; ll; ll = ll->next) { + ll->item->in_degree = 1; + } + + /* + * Count up how many children each commit has. We limit + * ourselves to those commits in the original list (in-degree + * of 1) avoiding setting it for any parent that was hidden. + */ + for(ll = list; ll; ll = ll->next) { + for (i = 0; i < ll->item->out_degree; ++i) { + git_commit_list_node *parent = ll->item->parents[i]; + if (parent->in_degree) + parent->in_degree++; + } + } + + /* + * Now we find the tips i.e. those not reachable from any other node + * i.e. those which still have an in-degree of 1. + */ + for(ll = list; ll; ll = ll->next) { + if (ll->item->in_degree == 1) { + if ((error = git_pqueue_insert(&queue, ll->item))) + goto cleanup; + } + } + + /* + * We need to output the tips in the order that they came out of the + * traversal, so if we're not doing time-sorting, we need to reverse the + * pqueue in order to get them to come out as we inserted them. + */ + if ((walk->sorting & GIT_SORT_TIME) == 0) + git_pqueue_reverse(&queue); + + + pptr = &newlist; + newlist = NULL; + while ((next = git_pqueue_pop(&queue)) != NULL) { + for (i = 0; i < next->out_degree; ++i) { + git_commit_list_node *parent = next->parents[i]; + if (parent->in_degree == 0) + continue; + + if (--parent->in_degree == 1) { + if ((error = git_pqueue_insert(&queue, parent))) + goto cleanup; + } + } + + /* All the children of 'item' have been emitted (since we got to it via the priority queue) */ + next->in_degree = 0; + + pptr = &git_commit_list_insert(next, pptr)->next; + } + + *out = newlist; + error = 0; + +cleanup: + git_pqueue_free(&queue); + return error; +} + +static int prepare_walk(git_revwalk *walk) +{ + int error = 0; + git_commit_list *list, *commits = NULL; + git_commit_list_node *next; + + /* If there were no pushes, we know that the walk is already over */ + if (!walk->did_push) { + git_error_clear(); + return GIT_ITEROVER; + } + + for (list = walk->user_input; list; list = list->next) { + git_commit_list_node *commit = list->item; + if ((error = git_commit_list_parse(walk, commit)) < 0) + return error; + + if (commit->uninteresting) + mark_parents_uninteresting(commit); + + if (!commit->seen) { + commit->seen = 1; + git_commit_list_insert(commit, &commits); + } + } + + if (walk->limited && (error = limit_list(&commits, walk, commits)) < 0) + return error; + + if (walk->sorting & GIT_SORT_TOPOLOGICAL) { + error = sort_in_topological_order(&walk->iterator_topo, walk, commits); + git_commit_list_free(&commits); + + if (error < 0) + return error; + + walk->get_next = &revwalk_next_toposort; + } else if (walk->sorting & GIT_SORT_TIME) { + for (list = commits; list && !error; list = list->next) + error = walk->enqueue(walk, list->item); + + git_commit_list_free(&commits); + + if (error < 0) + return error; + } else { + walk->iterator_rand = commits; + walk->get_next = revwalk_next_unsorted; + } + + if (walk->sorting & GIT_SORT_REVERSE) { + + while ((error = walk->get_next(&next, walk)) == 0) + if (git_commit_list_insert(next, &walk->iterator_reverse) == NULL) + return -1; + + if (error != GIT_ITEROVER) + return error; + + walk->get_next = &revwalk_next_reverse; + } + + walk->walking = 1; + return 0; +} + + +int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo) +{ + git_revwalk *walk = git__calloc(1, sizeof(git_revwalk)); + GIT_ERROR_CHECK_ALLOC(walk); + + if (git_oidmap_new(&walk->commits) < 0 || + git_pqueue_init(&walk->iterator_time, 0, 8, git_commit_list_time_cmp) < 0 || + git_pool_init(&walk->commit_pool, COMMIT_ALLOC) < 0) + return -1; + + walk->get_next = &revwalk_next_unsorted; + walk->enqueue = &revwalk_enqueue_unsorted; + + walk->repo = repo; + + if (git_repository_odb(&walk->odb, repo) < 0) { + git_revwalk_free(walk); + return -1; + } + + *revwalk_out = walk; + return 0; +} + +void git_revwalk_free(git_revwalk *walk) +{ + if (walk == NULL) + return; + + git_revwalk_reset(walk); + git_odb_free(walk->odb); + + git_oidmap_free(walk->commits); + git_pool_clear(&walk->commit_pool); + git_pqueue_free(&walk->iterator_time); + git__free(walk); +} + +git_repository *git_revwalk_repository(git_revwalk *walk) +{ + GIT_ASSERT_ARG_WITH_RETVAL(walk, NULL); + + return walk->repo; +} + +int git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode) +{ + GIT_ASSERT_ARG(walk); + + if (walk->walking) + git_revwalk_reset(walk); + + walk->sorting = sort_mode; + + if (walk->sorting & GIT_SORT_TIME) { + walk->get_next = &revwalk_next_timesort; + walk->enqueue = &revwalk_enqueue_timesort; + } else { + walk->get_next = &revwalk_next_unsorted; + walk->enqueue = &revwalk_enqueue_unsorted; + } + + if (walk->sorting != GIT_SORT_NONE) + walk->limited = 1; + + return 0; +} + +int git_revwalk_simplify_first_parent(git_revwalk *walk) +{ + walk->first_parent = 1; + return 0; +} + +int git_revwalk_next(git_oid *oid, git_revwalk *walk) +{ + int error; + git_commit_list_node *next; + + GIT_ASSERT_ARG(walk); + GIT_ASSERT_ARG(oid); + + if (!walk->walking) { + if ((error = prepare_walk(walk)) < 0) + return error; + } + + error = walk->get_next(&next, walk); + + if (error == GIT_ITEROVER) { + git_revwalk_reset(walk); + git_error_clear(); + return GIT_ITEROVER; + } + + if (!error) + git_oid_cpy(oid, &next->oid); + + return error; +} + +int git_revwalk_reset(git_revwalk *walk) +{ + git_commit_list_node *commit; + + GIT_ASSERT_ARG(walk); + + git_oidmap_foreach_value(walk->commits, commit, { + commit->seen = 0; + commit->in_degree = 0; + commit->topo_delay = 0; + commit->uninteresting = 0; + commit->added = 0; + commit->flags = 0; + }); + + git_pqueue_clear(&walk->iterator_time); + git_commit_list_free(&walk->iterator_topo); + git_commit_list_free(&walk->iterator_rand); + git_commit_list_free(&walk->iterator_reverse); + git_commit_list_free(&walk->user_input); + walk->first_parent = 0; + walk->walking = 0; + walk->limited = 0; + walk->did_push = walk->did_hide = 0; + walk->sorting = GIT_SORT_NONE; + + return 0; +} + +int git_revwalk_add_hide_cb( + git_revwalk *walk, + git_revwalk_hide_cb hide_cb, + void *payload) +{ + GIT_ASSERT_ARG(walk); + + if (walk->walking) + git_revwalk_reset(walk); + + walk->hide_cb = hide_cb; + walk->hide_cb_payload = payload; + + if (hide_cb) + walk->limited = 1; + + return 0; +} + diff --git a/src/libgit2/revwalk.h b/src/libgit2/revwalk.h new file mode 100644 index 000000000..94b8a6fb1 --- /dev/null +++ b/src/libgit2/revwalk.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_revwalk_h__ +#define INCLUDE_revwalk_h__ + +#include "common.h" + +#include "git2/revwalk.h" +#include "oidmap.h" +#include "commit_list.h" +#include "pqueue.h" +#include "pool.h" +#include "vector.h" + +#include "oidmap.h" + +struct git_revwalk { + git_repository *repo; + git_odb *odb; + + git_oidmap *commits; + git_pool commit_pool; + + git_commit_list *iterator_topo; + git_commit_list *iterator_rand; + git_commit_list *iterator_reverse; + git_pqueue iterator_time; + + int (*get_next)(git_commit_list_node **, git_revwalk *); + int (*enqueue)(git_revwalk *, git_commit_list_node *); + + unsigned walking:1, + first_parent: 1, + did_hide: 1, + did_push: 1, + limited: 1; + unsigned int sorting; + + /* the pushes and hides */ + git_commit_list *user_input; + + /* hide callback */ + git_revwalk_hide_cb hide_cb; + void *hide_cb_payload; +}; + +git_commit_list_node *git_revwalk__commit_lookup(git_revwalk *walk, const git_oid *oid); + +typedef struct { + int uninteresting; + int from_glob; + int insert_by_date; +} git_revwalk__push_options; + +#define GIT_REVWALK__PUSH_OPTIONS_INIT { 0 } + +int git_revwalk__push_commit(git_revwalk *walk, + const git_oid *oid, + const git_revwalk__push_options *opts); + +int git_revwalk__push_ref(git_revwalk *walk, + const char *refname, + const git_revwalk__push_options *opts); + +int git_revwalk__push_glob(git_revwalk *walk, + const char *glob, + const git_revwalk__push_options *given_opts); + +#endif diff --git a/src/libgit2/runtime.c b/src/libgit2/runtime.c new file mode 100644 index 000000000..c05dee8b9 --- /dev/null +++ b/src/libgit2/runtime.c @@ -0,0 +1,162 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "runtime.h" + +static git_runtime_shutdown_fn shutdown_callback[32]; +static git_atomic32 shutdown_callback_count; + +static git_atomic32 init_count; + +static int init_common(git_runtime_init_fn init_fns[], size_t cnt) +{ + size_t i; + int ret; + + /* Initialize subsystems that have global state */ + for (i = 0; i < cnt; i++) { + if ((ret = init_fns[i]()) != 0) + break; + } + + GIT_MEMORY_BARRIER; + + return ret; +} + +static void shutdown_common(void) +{ + git_runtime_shutdown_fn cb; + int pos; + + for (pos = git_atomic32_get(&shutdown_callback_count); + pos > 0; + pos = git_atomic32_dec(&shutdown_callback_count)) { + cb = git_atomic_swap(shutdown_callback[pos - 1], NULL); + + if (cb != NULL) + cb(); + } +} + +int git_runtime_shutdown_register(git_runtime_shutdown_fn callback) +{ + int count = git_atomic32_inc(&shutdown_callback_count); + + if (count > (int)ARRAY_SIZE(shutdown_callback) || count == 0) { + git_error_set(GIT_ERROR_INVALID, + "too many shutdown callbacks registered"); + git_atomic32_dec(&shutdown_callback_count); + return -1; + } + + shutdown_callback[count - 1] = callback; + + return 0; +} + +#if defined(GIT_THREADS) && defined(GIT_WIN32) + +/* + * On Win32, we use a spinlock to provide locking semantics. This is + * lighter-weight than a proper critical section. + */ +static volatile LONG init_spinlock = 0; + +GIT_INLINE(int) init_lock(void) +{ + while (InterlockedCompareExchange(&init_spinlock, 1, 0)) { Sleep(0); } + return 0; +} + +GIT_INLINE(int) init_unlock(void) +{ + InterlockedExchange(&init_spinlock, 0); + return 0; +} + +#elif defined(GIT_THREADS) && defined(_POSIX_THREADS) + +/* + * On POSIX, we need to use a proper mutex for locking. We might prefer + * a spinlock here, too, but there's no static initializer for a + * pthread_spinlock_t. + */ +static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER; + +GIT_INLINE(int) init_lock(void) +{ + return pthread_mutex_lock(&init_mutex) == 0 ? 0 : -1; +} + +GIT_INLINE(int) init_unlock(void) +{ + return pthread_mutex_unlock(&init_mutex) == 0 ? 0 : -1; +} + +#elif defined(GIT_THREADS) +# error unknown threading model +#else + +# define init_lock() git__noop() +# define init_unlock() git__noop() + +#endif + +int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt) +{ + int ret; + + if (init_lock() < 0) + return -1; + + /* Only do work on a 0 -> 1 transition of the refcount */ + if ((ret = git_atomic32_inc(&init_count)) == 1) { + if (init_common(init_fns, cnt) < 0) + ret = -1; + } + + if (init_unlock() < 0) + return -1; + + return ret; +} + +int git_runtime_init_count(void) +{ + int ret; + + if (init_lock() < 0) + return -1; + + ret = git_atomic32_get(&init_count); + + if (init_unlock() < 0) + return -1; + + return ret; +} + +int git_runtime_shutdown(void) +{ + int ret; + + /* Enter the lock */ + if (init_lock() < 0) + return -1; + + /* Only do work on a 1 -> 0 transition of the refcount */ + if ((ret = git_atomic32_dec(&init_count)) == 0) + shutdown_common(); + + /* Exit the lock */ + if (init_unlock() < 0) + return -1; + + return ret; +} diff --git a/src/libgit2/runtime.h b/src/libgit2/runtime.h new file mode 100644 index 000000000..24ac58ee9 --- /dev/null +++ b/src/libgit2/runtime.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_runtime_h__ +#define INCLUDE_runtime_h__ + +#include "common.h" + +typedef int (*git_runtime_init_fn)(void); +typedef void (*git_runtime_shutdown_fn)(void); + +/** + * Start up a new runtime. If this is the first time that this + * function is called within the context of the current library + * or executable, then the given `init_fns` will be invoked. If + * it is not the first time, they will be ignored. + * + * The given initialization functions _may_ register shutdown + * handlers using `git_runtime_shutdown_register` to be notified + * when the runtime is shutdown. + * + * @param init_fns The list of initialization functions to call + * @param cnt The number of init_fns + * @return The number of initializations performed (including this one) or an error + */ +int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt); + +/* + * Returns the number of initializations active (the number of calls to + * `git_runtime_init` minus the number of calls sto `git_runtime_shutdown`). + * If 0, the runtime is not currently initialized. + * + * @return The number of initializations performed or an error + */ +int git_runtime_init_count(void); + +/** + * Shut down the runtime. If this is the last shutdown call, + * such that there are no remaining `init` calls, then any + * shutdown hooks that have been registered will be invoked. + * + * The number of outstanding initializations will be returned. + * If this number is 0, then the runtime is shutdown. + * + * @return The number of outstanding initializations (after this one) or an error + */ +int git_runtime_shutdown(void); + +/** + * Register a shutdown handler for this runtime. This should be done + * by a function invoked by `git_runtime_init` to ensure that the + * appropriate locks are taken. + * + * @param callback The shutdown handler callback + * @return 0 or an error code + */ +int git_runtime_shutdown_register(git_runtime_shutdown_fn callback); + +#endif diff --git a/src/libgit2/settings.h b/src/libgit2/settings.h new file mode 100644 index 000000000..dc42ce939 --- /dev/null +++ b/src/libgit2/settings.h @@ -0,0 +1,11 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +extern int git_settings_global_init(void); + +extern const char *git_libgit2__user_agent(void); +extern const char *git_libgit2__ssl_ciphers(void); diff --git a/src/libgit2/signature.c b/src/libgit2/signature.c new file mode 100644 index 000000000..5d6ab572c --- /dev/null +++ b/src/libgit2/signature.c @@ -0,0 +1,339 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "signature.h" + +#include "repository.h" +#include "git2/common.h" +#include "posix.h" + +void git_signature_free(git_signature *sig) +{ + if (sig == NULL) + return; + + git__free(sig->name); + sig->name = NULL; + git__free(sig->email); + sig->email = NULL; + git__free(sig); +} + +static int signature_parse_error(const char *msg) +{ + git_error_set(GIT_ERROR_INVALID, "failed to parse signature - %s", msg); + return GIT_EINVALID; +} + +static int signature_error(const char *msg) +{ + git_error_set(GIT_ERROR_INVALID, "failed to parse signature - %s", msg); + return -1; +} + +static bool contains_angle_brackets(const char *input) +{ + return strchr(input, '<') != NULL || strchr(input, '>') != NULL; +} + +static bool is_crud(unsigned char c) +{ + return c <= 32 || + c == '.' || + c == ',' || + c == ':' || + c == ';' || + c == '<' || + c == '>' || + c == '"' || + c == '\\' || + c == '\''; +} + +static char *extract_trimmed(const char *ptr, size_t len) +{ + while (len && is_crud((unsigned char)ptr[0])) { + ptr++; len--; + } + + while (len && is_crud((unsigned char)ptr[len - 1])) { + len--; + } + + return git__substrdup(ptr, len); +} + +int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset) +{ + git_signature *p = NULL; + + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(email); + + *sig_out = NULL; + + if (contains_angle_brackets(name) || + contains_angle_brackets(email)) { + return signature_error( + "Neither `name` nor `email` should contain angle brackets chars."); + } + + p = git__calloc(1, sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(p); + + p->name = extract_trimmed(name, strlen(name)); + GIT_ERROR_CHECK_ALLOC(p->name); + p->email = extract_trimmed(email, strlen(email)); + GIT_ERROR_CHECK_ALLOC(p->email); + + if (p->name[0] == '\0' || p->email[0] == '\0') { + git_signature_free(p); + return signature_error("Signature cannot have an empty name or email"); + } + + p->when.time = time; + p->when.offset = offset; + p->when.sign = (offset < 0) ? '-' : '+'; + + *sig_out = p; + return 0; +} + +int git_signature_dup(git_signature **dest, const git_signature *source) +{ + git_signature *signature; + + if (source == NULL) + return 0; + + signature = git__calloc(1, sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(signature); + + signature->name = git__strdup(source->name); + GIT_ERROR_CHECK_ALLOC(signature->name); + + signature->email = git__strdup(source->email); + GIT_ERROR_CHECK_ALLOC(signature->email); + + signature->when.time = source->when.time; + signature->when.offset = source->when.offset; + signature->when.sign = source->when.sign; + + *dest = signature; + + return 0; +} + +int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool) +{ + git_signature *signature; + + if (source == NULL) + return 0; + + signature = git_pool_mallocz(pool, sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(signature); + + signature->name = git_pool_strdup(pool, source->name); + GIT_ERROR_CHECK_ALLOC(signature->name); + + signature->email = git_pool_strdup(pool, source->email); + GIT_ERROR_CHECK_ALLOC(signature->email); + + signature->when.time = source->when.time; + signature->when.offset = source->when.offset; + signature->when.sign = source->when.sign; + + *dest = signature; + + return 0; +} + +int git_signature_now(git_signature **sig_out, const char *name, const char *email) +{ + time_t now; + time_t offset; + struct tm *utc_tm; + git_signature *sig; + struct tm _utc; + + *sig_out = NULL; + + /* + * Get the current time as seconds since the epoch and + * transform that into a tm struct containing the time at + * UTC. Give that to mktime which considers it a local time + * (tm_isdst = -1 asks it to take DST into account) and gives + * us that time as seconds since the epoch. The difference + * between its return value and 'now' is our offset to UTC. + */ + time(&now); + utc_tm = p_gmtime_r(&now, &_utc); + utc_tm->tm_isdst = -1; + offset = (time_t)difftime(now, mktime(utc_tm)); + offset /= 60; + + if (git_signature_new(&sig, name, email, now, (int)offset) < 0) + return -1; + + *sig_out = sig; + + return 0; +} + +int git_signature_default(git_signature **out, git_repository *repo) +{ + int error; + git_config *cfg; + const char *user_name, *user_email; + + if ((error = git_repository_config_snapshot(&cfg, repo)) < 0) + return error; + + if (!(error = git_config_get_string(&user_name, cfg, "user.name")) && + !(error = git_config_get_string(&user_email, cfg, "user.email"))) + error = git_signature_now(out, user_name, user_email); + + git_config_free(cfg); + return error; +} + +int git_signature__parse(git_signature *sig, const char **buffer_out, + const char *buffer_end, const char *header, char ender) +{ + const char *buffer = *buffer_out; + const char *email_start, *email_end; + + memset(sig, 0, sizeof(git_signature)); + + if (ender && + (buffer_end = memchr(buffer, ender, buffer_end - buffer)) == NULL) + return signature_parse_error("no newline given"); + + if (header) { + const size_t header_len = strlen(header); + + if (buffer + header_len >= buffer_end || memcmp(buffer, header, header_len) != 0) + return signature_parse_error("expected prefix doesn't match actual"); + + buffer += header_len; + } + + email_start = git__memrchr(buffer, '<', buffer_end - buffer); + email_end = git__memrchr(buffer, '>', buffer_end - buffer); + + if (!email_start || !email_end || email_end <= email_start) + return signature_parse_error("malformed e-mail"); + + email_start += 1; + sig->name = extract_trimmed(buffer, email_start - buffer - 1); + sig->email = extract_trimmed(email_start, email_end - email_start); + + /* Do we even have a time at the end of the signature? */ + if (email_end + 2 < buffer_end) { + const char *time_start = email_end + 2; + const char *time_end; + + if (git__strntol64(&sig->when.time, time_start, + buffer_end - time_start, &time_end, 10) < 0) { + git__free(sig->name); + git__free(sig->email); + sig->name = sig->email = NULL; + return signature_parse_error("invalid Unix timestamp"); + } + + /* do we have a timezone? */ + if (time_end + 1 < buffer_end) { + int offset, hours, mins; + const char *tz_start, *tz_end; + + tz_start = time_end + 1; + + if ((tz_start[0] != '-' && tz_start[0] != '+') || + git__strntol32(&offset, tz_start + 1, + buffer_end - tz_start - 1, &tz_end, 10) < 0) { + /* malformed timezone, just assume it's zero */ + offset = 0; + } + + hours = offset / 100; + mins = offset % 100; + + /* + * only store timezone if it's not overflowing; + * see http://www.worldtimezone.com/faq.html + */ + if (hours <= 14 && mins <= 59) { + sig->when.offset = (hours * 60) + mins; + sig->when.sign = tz_start[0]; + if (tz_start[0] == '-') + sig->when.offset = -sig->when.offset; + } + } + } + + *buffer_out = buffer_end + 1; + return 0; +} + +int git_signature_from_buffer(git_signature **out, const char *buf) +{ + git_signature *sig; + const char *buf_end; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(buf); + + *out = NULL; + + sig = git__calloc(1, sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(sig); + + buf_end = buf + strlen(buf); + error = git_signature__parse(sig, &buf, buf_end, NULL, '\0'); + + if (error) + git__free(sig); + else + *out = sig; + + return error; +} + +void git_signature__writebuf(git_str *buf, const char *header, const git_signature *sig) +{ + int offset, hours, mins; + char sign; + + offset = sig->when.offset; + sign = (sig->when.offset < 0 || sig->when.sign == '-') ? '-' : '+'; + + if (offset < 0) + offset = -offset; + + hours = offset / 60; + mins = offset % 60; + + git_str_printf(buf, "%s%s <%s> %u %c%02d%02d\n", + header ? header : "", sig->name, sig->email, + (unsigned)sig->when.time, sign, hours, mins); +} + +bool git_signature__equal(const git_signature *one, const git_signature *two) +{ + GIT_ASSERT_ARG(one); + GIT_ASSERT_ARG(two); + + return + git__strcmp(one->name, two->name) == 0 && + git__strcmp(one->email, two->email) == 0 && + one->when.time == two->when.time && + one->when.offset == two->when.offset && + one->when.sign == two->when.sign; +} + diff --git a/src/libgit2/signature.h b/src/libgit2/signature.h new file mode 100644 index 000000000..5c8270954 --- /dev/null +++ b/src/libgit2/signature.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_signature_h__ +#define INCLUDE_signature_h__ + +#include "common.h" + +#include "git2/common.h" +#include "git2/signature.h" +#include "repository.h" +#include + +int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender); +void git_signature__writebuf(git_str *buf, const char *header, const git_signature *sig); +bool git_signature__equal(const git_signature *one, const git_signature *two); + +int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool); + +#endif diff --git a/src/libgit2/sortedcache.c b/src/libgit2/sortedcache.c new file mode 100644 index 000000000..7ff900efe --- /dev/null +++ b/src/libgit2/sortedcache.c @@ -0,0 +1,380 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "sortedcache.h" + +int git_sortedcache_new( + git_sortedcache **out, + size_t item_path_offset, + git_sortedcache_free_item_fn free_item, + void *free_item_payload, + git_vector_cmp item_cmp, + const char *path) +{ + git_sortedcache *sc; + size_t pathlen, alloclen; + + pathlen = path ? strlen(path) : 0; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_sortedcache), pathlen); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + sc = git__calloc(1, alloclen); + GIT_ERROR_CHECK_ALLOC(sc); + + if (git_pool_init(&sc->pool, 1) < 0 || + git_vector_init(&sc->items, 4, item_cmp) < 0 || + git_strmap_new(&sc->map) < 0) + goto fail; + + if (git_rwlock_init(&sc->lock)) { + git_error_set(GIT_ERROR_OS, "failed to initialize lock"); + goto fail; + } + + sc->item_path_offset = item_path_offset; + sc->free_item = free_item; + sc->free_item_payload = free_item_payload; + GIT_REFCOUNT_INC(sc); + if (pathlen) + memcpy(sc->path, path, pathlen); + + *out = sc; + return 0; + +fail: + git_strmap_free(sc->map); + git_vector_free(&sc->items); + git_pool_clear(&sc->pool); + git__free(sc); + return -1; +} + +void git_sortedcache_incref(git_sortedcache *sc) +{ + GIT_REFCOUNT_INC(sc); +} + +const char *git_sortedcache_path(git_sortedcache *sc) +{ + return sc->path; +} + +static void sortedcache_clear(git_sortedcache *sc) +{ + git_strmap_clear(sc->map); + + if (sc->free_item) { + size_t i; + void *item; + + git_vector_foreach(&sc->items, i, item) { + sc->free_item(sc->free_item_payload, item); + } + } + + git_vector_clear(&sc->items); + + git_pool_clear(&sc->pool); +} + +static void sortedcache_free(git_sortedcache *sc) +{ + /* acquire write lock to make sure everyone else is done */ + if (git_sortedcache_wlock(sc) < 0) + return; + + sortedcache_clear(sc); + git_vector_free(&sc->items); + git_strmap_free(sc->map); + + git_sortedcache_wunlock(sc); + + git_rwlock_free(&sc->lock); + git__free(sc); +} + +void git_sortedcache_free(git_sortedcache *sc) +{ + if (!sc) + return; + GIT_REFCOUNT_DEC(sc, sortedcache_free); +} + +static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item) +{ + git_sortedcache *sc = payload; + /* path will already have been copied by upsert */ + memcpy(tgt_item, src_item, sc->item_path_offset); + return 0; +} + +/* copy a sorted cache */ +int git_sortedcache_copy( + git_sortedcache **out, + git_sortedcache *src, + bool lock, + int (*copy_item)(void *payload, void *tgt_item, void *src_item), + void *payload) +{ + int error = 0; + git_sortedcache *tgt; + size_t i; + void *src_item, *tgt_item; + + /* just use memcpy if no special copy fn is passed in */ + if (!copy_item) { + copy_item = sortedcache_copy_item; + payload = src; + } + + if ((error = git_sortedcache_new( + &tgt, src->item_path_offset, + src->free_item, src->free_item_payload, + src->items._cmp, src->path)) < 0) + return error; + + if (lock && git_sortedcache_rlock(src) < 0) { + git_sortedcache_free(tgt); + return -1; + } + + git_vector_foreach(&src->items, i, src_item) { + char *path = ((char *)src_item) + src->item_path_offset; + + if ((error = git_sortedcache_upsert(&tgt_item, tgt, path)) < 0 || + (error = copy_item(payload, tgt_item, src_item)) < 0) + break; + } + + if (lock) + git_sortedcache_runlock(src); + if (error) + git_sortedcache_free(tgt); + + *out = !error ? tgt : NULL; + + return error; +} + +/* lock sortedcache while making modifications */ +int git_sortedcache_wlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + + if (git_rwlock_wrlock(&sc->lock) < 0) { + git_error_set(GIT_ERROR_OS, "unable to acquire write lock on cache"); + return -1; + } + return 0; +} + +/* unlock sorted cache when done with modifications */ +void git_sortedcache_wunlock(git_sortedcache *sc) +{ + git_vector_sort(&sc->items); + git_rwlock_wrunlock(&sc->lock); +} + +/* lock sortedcache for read */ +int git_sortedcache_rlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + + if (git_rwlock_rdlock(&sc->lock) < 0) { + git_error_set(GIT_ERROR_OS, "unable to acquire read lock on cache"); + return -1; + } + return 0; +} + +/* unlock sorted cache when done reading */ +void git_sortedcache_runlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + git_rwlock_rdunlock(&sc->lock); +} + +/* if the file has changed, lock cache and load file contents into buf; + * returns <0 on error, >0 if file has not changed + */ +int git_sortedcache_lockandload(git_sortedcache *sc, git_str *buf) +{ + int error, fd; + struct stat st; + + if ((error = git_sortedcache_wlock(sc)) < 0) + return error; + + if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0) + goto unlock; + + if ((fd = git_futils_open_ro(sc->path)) < 0) { + error = fd; + goto unlock; + } + + if (p_fstat(fd, &st) < 0) { + git_error_set(GIT_ERROR_OS, "failed to stat file"); + error = -1; + (void)p_close(fd); + goto unlock; + } + + if (!git__is_sizet(st.st_size)) { + git_error_set(GIT_ERROR_INVALID, "unable to load file larger than size_t"); + error = -1; + (void)p_close(fd); + goto unlock; + } + + if (buf) + error = git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size); + + (void)p_close(fd); + + if (error < 0) + goto unlock; + + return 1; /* return 1 -> file needs reload and was successfully loaded */ + +unlock: + git_sortedcache_wunlock(sc); + return error; +} + +void git_sortedcache_updated(git_sortedcache *sc) +{ + /* update filestamp to latest value */ + git_futils_filestamp_check(&sc->stamp, sc->path); +} + +/* release all items in sorted cache */ +int git_sortedcache_clear(git_sortedcache *sc, bool wlock) +{ + if (wlock && git_sortedcache_wlock(sc) < 0) + return -1; + + sortedcache_clear(sc); + + if (wlock) + git_sortedcache_wunlock(sc); + + return 0; +} + +/* find and/or insert item, returning pointer to item data */ +int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) +{ + size_t keylen, itemlen; + int error = 0; + char *item_key; + void *item; + + if ((item = git_strmap_get(sc->map, key)) != NULL) + goto done; + + keylen = strlen(key); + itemlen = sc->item_path_offset + keylen + 1; + itemlen = (itemlen + 7) & ~7; + + if ((item = git_pool_mallocz(&sc->pool, itemlen)) == NULL) { + /* don't use GIT_ERROR_CHECK_ALLOC b/c of lock */ + error = -1; + goto done; + } + + /* one strange thing is that even if the vector or hash table insert + * fail, there is no way to free the pool item so we just abandon it + */ + + item_key = ((char *)item) + sc->item_path_offset; + memcpy(item_key, key, keylen); + + if ((error = git_strmap_set(sc->map, item_key, item)) < 0) + goto done; + + if ((error = git_vector_insert(&sc->items, item)) < 0) + git_strmap_delete(sc->map, item_key); + +done: + if (out) + *out = !error ? item : NULL; + return error; +} + +/* lookup item by key */ +void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key) +{ + return git_strmap_get(sc->map, key); +} + +/* find out how many items are in the cache */ +size_t git_sortedcache_entrycount(const git_sortedcache *sc) +{ + return git_vector_length(&sc->items); +} + +/* lookup item by index */ +void *git_sortedcache_entry(git_sortedcache *sc, size_t pos) +{ + /* make sure the items are sorted so this gets the correct item */ + if (!git_vector_is_sorted(&sc->items)) + git_vector_sort(&sc->items); + + return git_vector_get(&sc->items, pos); +} + +/* helper struct so bsearch callback can know offset + key value for cmp */ +struct sortedcache_magic_key { + size_t offset; + const char *key; +}; + +static int sortedcache_magic_cmp(const void *key, const void *value) +{ + const struct sortedcache_magic_key *magic = key; + const char *value_key = ((const char *)value) + magic->offset; + return strcmp(magic->key, value_key); +} + +/* lookup index of item by key */ +int git_sortedcache_lookup_index( + size_t *out, git_sortedcache *sc, const char *key) +{ + struct sortedcache_magic_key magic; + + magic.offset = sc->item_path_offset; + magic.key = key; + + return git_vector_bsearch2(out, &sc->items, sortedcache_magic_cmp, &magic); +} + +/* remove entry from cache */ +int git_sortedcache_remove(git_sortedcache *sc, size_t pos) +{ + char *item; + + /* + * Because of pool allocation, this can't actually remove the item, + * but we can remove it from the items vector and the hash table. + */ + + if ((item = git_vector_get(&sc->items, pos)) == NULL) { + git_error_set(GIT_ERROR_INVALID, "removing item out of range"); + return GIT_ENOTFOUND; + } + + (void)git_vector_remove(&sc->items, pos); + + git_strmap_delete(sc->map, item + sc->item_path_offset); + + if (sc->free_item) + sc->free_item(sc->free_item_payload, item); + + return 0; +} + diff --git a/src/libgit2/sortedcache.h b/src/libgit2/sortedcache.h new file mode 100644 index 000000000..ef260a093 --- /dev/null +++ b/src/libgit2/sortedcache.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_sorted_cache_h__ +#define INCLUDE_sorted_cache_h__ + +#include "common.h" + +#include "util.h" +#include "futils.h" +#include "vector.h" +#include "thread.h" +#include "pool.h" +#include "strmap.h" + +#include + +/* + * The purpose of this data structure is to cache the parsed contents of a + * file (a.k.a. the backing file) where each item in the file can be + * identified by a key string and you want to both look them up by name + * and traverse them in sorted order. Each item is assumed to itself end + * in a GIT_FLEX_ARRAY. + */ + +typedef void (*git_sortedcache_free_item_fn)(void *payload, void *item); + +typedef struct { + git_refcount rc; + git_rwlock lock; + size_t item_path_offset; + git_sortedcache_free_item_fn free_item; + void *free_item_payload; + git_pool pool; + git_vector items; + git_strmap *map; + git_futils_filestamp stamp; + char path[GIT_FLEX_ARRAY]; +} git_sortedcache; + +/* Create a new sortedcache + * + * Even though every sortedcache stores items with a GIT_FLEX_ARRAY at + * the end containing their key string, you have to provide the item_cmp + * sorting function because the sorting function doesn't get a payload + * and therefore can't know the offset to the item key string. :-( + * + * @param out The allocated git_sortedcache + * @param item_path_offset Offset to the GIT_FLEX_ARRAY item key in the + * struct - use offsetof(struct mine, key-field) to get this + * @param free_item Optional callback to free each item + * @param free_item_payload Optional payload passed to free_item callback + * @param item_cmp Compare the keys of two items + * @param path The path to the backing store file for this cache; this + * may be NULL. The cache makes it easy to load this and check + * if it has been modified since the last load and/or write. + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_new( + git_sortedcache **out, + size_t item_path_offset, /* use offsetof(struct, path-field) macro */ + git_sortedcache_free_item_fn free_item, + void *free_item_payload, + git_vector_cmp item_cmp, + const char *path); + +/* Copy a sorted cache + * + * - `copy_item` can be NULL to just use memcpy + * - if `lock`, grabs read lock on `src` during copy and releases after + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_copy( + git_sortedcache **out, + git_sortedcache *src, + bool lock, + int (*copy_item)(void *payload, void *tgt_item, void *src_item), + void *payload); + +/* Free sorted cache (first calling `free_item` callbacks) + * + * Don't call on a locked collection - it may acquire a write lock + */ +void git_sortedcache_free(git_sortedcache *sc); + +/* Increment reference count - balance with call to free */ +void git_sortedcache_incref(git_sortedcache *sc); + +/* Get the pathname associated with this cache at creation time */ +const char *git_sortedcache_path(git_sortedcache *sc); + +/* + * CACHE WRITE FUNCTIONS + * + * The following functions require you to have a writer lock to make the + * modification. Some of the functions take a `wlock` parameter and + * will optionally lock and unlock for you if that is passed as true. + * + */ + +/* Lock sortedcache for write */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_wlock(git_sortedcache *sc); + +/* Unlock sorted cache when done with write */ +void git_sortedcache_wunlock(git_sortedcache *sc); + +/* Lock cache and load backing file into a buffer. + * + * This grabs a write lock on the cache then looks at the modification + * time and size of the file on disk. + * + * If the file appears to have changed, this loads the file contents into + * the buffer and returns a positive value leaving the cache locked - the + * caller should parse the file content, update the cache as needed, then + * release the lock. NOTE: In this case, the caller MUST unlock the cache. + * + * If the file appears to be unchanged, then this automatically releases + * the lock on the cache, clears the buffer, and returns 0. + * + * @return 0 if up-to-date, 1 if out-of-date, <0 on error + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_lockandload( + git_sortedcache *sc, git_str *buf); + +/* Refresh file timestamp after write completes + * You should already be holding the write lock when you call this. + */ +void git_sortedcache_updated(git_sortedcache *sc); + +/* Release all items in sorted cache + * + * If `wlock` is true, grabs write lock and releases when done, otherwise + * you should already be holding a write lock when you call this. + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_clear( + git_sortedcache *sc, bool wlock); + +/* Find and/or insert item, returning pointer to item data. + * You should already be holding the write lock when you call this. + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_upsert( + void **out, git_sortedcache *sc, const char *key); + +/* Removes entry at pos from cache + * You should already be holding the write lock when you call this. + */ +int git_sortedcache_remove(git_sortedcache *sc, size_t pos); + +/* + * CACHE READ FUNCTIONS + * + * The following functions access items in the cache. To prevent the + * results from being invalidated before they can be used, you should be + * holding either a read lock or a write lock when using these functions. + * + */ + +/* Lock sortedcache for read */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_rlock(git_sortedcache *sc); + +/* Unlock sorted cache when done with read */ +void git_sortedcache_runlock(git_sortedcache *sc); + +/* Lookup item by key - returns NULL if not found */ +void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key); + +/* Get how many items are in the cache + * + * You can call this function without holding a lock, but be aware + * that it may change before you use it. + */ +size_t git_sortedcache_entrycount(const git_sortedcache *sc); + +/* Lookup item by index - returns NULL if out of range */ +void *git_sortedcache_entry(git_sortedcache *sc, size_t pos); + +/* Lookup index of item by key - returns GIT_ENOTFOUND if not found */ +int git_sortedcache_lookup_index( + size_t *out, git_sortedcache *sc, const char *key); + +#endif diff --git a/src/libgit2/stash.c b/src/libgit2/stash.c new file mode 100644 index 000000000..5fc01ac36 --- /dev/null +++ b/src/libgit2/stash.c @@ -0,0 +1,1110 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "repository.h" +#include "commit.h" +#include "tree.h" +#include "reflog.h" +#include "blob.h" +#include "git2/diff.h" +#include "git2/stash.h" +#include "git2/status.h" +#include "git2/checkout.h" +#include "git2/index.h" +#include "git2/transaction.h" +#include "git2/merge.h" +#include "index.h" +#include "signature.h" +#include "iterator.h" +#include "merge.h" +#include "diff.h" +#include "diff_generate.h" + +static int create_error(int error, const char *msg) +{ + git_error_set(GIT_ERROR_STASH, "cannot stash changes - %s", msg); + return error; +} + +static int retrieve_head(git_reference **out, git_repository *repo) +{ + int error = git_repository_head(out, repo); + + if (error == GIT_EUNBORNBRANCH) + return create_error(error, "you do not have the initial commit yet."); + + return error; +} + +static int append_abbreviated_oid(git_str *out, const git_oid *b_commit) +{ + char *formatted_oid; + + formatted_oid = git_oid_allocfmt(b_commit); + GIT_ERROR_CHECK_ALLOC(formatted_oid); + + git_str_put(out, formatted_oid, 7); + git__free(formatted_oid); + + return git_str_oom(out) ? -1 : 0; +} + +static int append_commit_description(git_str *out, git_commit *commit) +{ + const char *summary = git_commit_summary(commit); + GIT_ERROR_CHECK_ALLOC(summary); + + if (append_abbreviated_oid(out, git_commit_id(commit)) < 0) + return -1; + + git_str_putc(out, ' '); + git_str_puts(out, summary); + git_str_putc(out, '\n'); + + return git_str_oom(out) ? -1 : 0; +} + +static int retrieve_base_commit_and_message( + git_commit **b_commit, + git_str *stash_message, + git_repository *repo) +{ + git_reference *head = NULL; + int error; + + if ((error = retrieve_head(&head, repo)) < 0) + return error; + + if (strcmp("HEAD", git_reference_name(head)) == 0) + error = git_str_puts(stash_message, "(no branch): "); + else + error = git_str_printf( + stash_message, + "%s: ", + git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR)); + if (error < 0) + goto cleanup; + + if ((error = git_commit_lookup( + b_commit, repo, git_reference_target(head))) < 0) + goto cleanup; + + if ((error = append_commit_description(stash_message, *b_commit)) < 0) + goto cleanup; + +cleanup: + git_reference_free(head); + return error; +} + +static int build_tree_from_index( + git_tree **out, + git_repository *repo, + git_index *index) +{ + int error; + git_oid i_tree_oid; + + if ((error = git_index_write_tree_to(&i_tree_oid, index, repo)) < 0) + return error; + + return git_tree_lookup(out, repo, &i_tree_oid); +} + +static int commit_index( + git_commit **i_commit, + git_repository *repo, + git_index *index, + const git_signature *stasher, + const char *message, + const git_commit *parent) +{ + git_tree *i_tree = NULL; + git_oid i_commit_oid; + git_str msg = GIT_STR_INIT; + int error; + + if ((error = build_tree_from_index(&i_tree, repo, index)) < 0) + goto cleanup; + + if ((error = git_str_printf(&msg, "index on %s\n", message)) < 0) + goto cleanup; + + if ((error = git_commit_create( + &i_commit_oid, + git_index_owner(index), + NULL, + stasher, + stasher, + NULL, + git_str_cstr(&msg), + i_tree, + 1, + &parent)) < 0) + goto cleanup; + + error = git_commit_lookup(i_commit, git_index_owner(index), &i_commit_oid); + +cleanup: + git_tree_free(i_tree); + git_str_dispose(&msg); + return error; +} + +struct stash_update_rules { + bool include_changed; + bool include_untracked; + bool include_ignored; +}; + +/* + * Similar to git_index_add_bypath but able to operate on any + * index without making assumptions about the repository's index + */ +static int stash_to_index( + git_repository *repo, + git_index *index, + const char *path) +{ + git_index *repo_index = NULL; + git_index_entry entry = {{0}}; + struct stat st; + int error; + + if (!git_repository_is_bare(repo) && + (error = git_repository_index__weakptr(&repo_index, repo)) < 0) + return error; + + if ((error = git_blob__create_from_paths( + &entry.id, &st, repo, NULL, path, 0, true)) < 0) + return error; + + git_index_entry__init_from_stat(&entry, &st, + (repo_index == NULL || !repo_index->distrust_filemode)); + + entry.path = path; + + return git_index_add(index, &entry); +} + +static int stash_update_index_from_diff( + git_repository *repo, + git_index *index, + const git_diff *diff, + struct stash_update_rules *data) +{ + int error = 0; + size_t d, max_d = git_diff_num_deltas(diff); + + for (d = 0; !error && d < max_d; ++d) { + const char *add_path = NULL; + const git_diff_delta *delta = git_diff_get_delta(diff, d); + + switch (delta->status) { + case GIT_DELTA_IGNORED: + if (data->include_ignored) + add_path = delta->new_file.path; + break; + + case GIT_DELTA_UNTRACKED: + if (data->include_untracked && + delta->new_file.mode != GIT_FILEMODE_TREE) + add_path = delta->new_file.path; + break; + + case GIT_DELTA_ADDED: + case GIT_DELTA_MODIFIED: + if (data->include_changed) + add_path = delta->new_file.path; + break; + + case GIT_DELTA_DELETED: + if (data->include_changed && + !git_index_find(NULL, index, delta->old_file.path)) + error = git_index_remove(index, delta->old_file.path, 0); + break; + + default: + /* Unimplemented */ + git_error_set( + GIT_ERROR_INVALID, + "cannot update index. Unimplemented status (%d)", + delta->status); + return -1; + } + + if (add_path != NULL) + error = stash_to_index(repo, index, add_path); + } + + return error; +} + +static int build_untracked_tree( + git_tree **tree_out, + git_repository *repo, + git_commit *i_commit, + uint32_t flags) +{ + git_index *i_index = NULL; + git_tree *i_tree = NULL; + git_diff *diff = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + struct stash_update_rules data = {0}; + int error; + + if ((error = git_index_new(&i_index)) < 0) + goto cleanup; + + if (flags & GIT_STASH_INCLUDE_UNTRACKED) { + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_RECURSE_UNTRACKED_DIRS; + data.include_untracked = true; + } + + if (flags & GIT_STASH_INCLUDE_IGNORED) { + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | + GIT_DIFF_RECURSE_IGNORED_DIRS; + data.include_ignored = true; + } + + if ((error = git_commit_tree(&i_tree, i_commit)) < 0) + goto cleanup; + + if ((error = git_diff_tree_to_workdir(&diff, repo, i_tree, &opts)) < 0) + goto cleanup; + + if ((error = stash_update_index_from_diff(repo, i_index, diff, &data)) < 0) + goto cleanup; + + error = build_tree_from_index(tree_out, repo, i_index); + +cleanup: + git_diff_free(diff); + git_tree_free(i_tree); + git_index_free(i_index); + return error; +} + +static int commit_untracked( + git_commit **u_commit, + git_repository *repo, + const git_signature *stasher, + const char *message, + git_commit *i_commit, + uint32_t flags) +{ + git_tree *u_tree = NULL; + git_oid u_commit_oid; + git_str msg = GIT_STR_INIT; + int error; + + if ((error = build_untracked_tree(&u_tree, repo, i_commit, flags)) < 0) + goto cleanup; + + if ((error = git_str_printf(&msg, "untracked files on %s\n", message)) < 0) + goto cleanup; + + if ((error = git_commit_create( + &u_commit_oid, + repo, + NULL, + stasher, + stasher, + NULL, + git_str_cstr(&msg), + u_tree, + 0, + NULL)) < 0) + goto cleanup; + + error = git_commit_lookup(u_commit, repo, &u_commit_oid); + +cleanup: + git_tree_free(u_tree); + git_str_dispose(&msg); + return error; +} + +static git_diff_delta *stash_delta_merge( + const git_diff_delta *a, + const git_diff_delta *b, + git_pool *pool) +{ + /* Special case for stash: if a file is deleted in the index, but exists + * in the working tree, we need to stash the workdir copy for the workdir. + */ + if (a->status == GIT_DELTA_DELETED && b->status == GIT_DELTA_UNTRACKED) { + git_diff_delta *dup = git_diff__delta_dup(b, pool); + + if (dup) + dup->status = GIT_DELTA_MODIFIED; + return dup; + } + + return git_diff__merge_like_cgit(a, b, pool); +} + +static int build_workdir_tree( + git_tree **tree_out, + git_repository *repo, + git_index *i_index, + git_commit *b_commit) +{ + git_tree *b_tree = NULL; + git_diff *diff = NULL, *idx_to_wd = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + struct stash_update_rules data = {0}; + int error; + + opts.flags = GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_INCLUDE_UNTRACKED; + + if ((error = git_commit_tree(&b_tree, b_commit)) < 0) + goto cleanup; + + if ((error = git_diff_tree_to_index(&diff, repo, b_tree, i_index, &opts)) < 0 || + (error = git_diff_index_to_workdir(&idx_to_wd, repo, i_index, &opts)) < 0 || + (error = git_diff__merge(diff, idx_to_wd, stash_delta_merge)) < 0) + goto cleanup; + + data.include_changed = true; + + if ((error = stash_update_index_from_diff(repo, i_index, diff, &data)) < 0) + goto cleanup; + + error = build_tree_from_index(tree_out, repo, i_index); + +cleanup: + git_diff_free(idx_to_wd); + git_diff_free(diff); + git_tree_free(b_tree); + + return error; +} + +static int commit_worktree( + git_oid *w_commit_oid, + git_repository *repo, + const git_signature *stasher, + const char *message, + git_commit *i_commit, + git_commit *b_commit, + git_commit *u_commit) +{ + const git_commit *parents[] = { NULL, NULL, NULL }; + git_index *i_index = NULL, *r_index = NULL; + git_tree *w_tree = NULL; + int error = 0, ignorecase; + + parents[0] = b_commit; + parents[1] = i_commit; + parents[2] = u_commit; + + if ((error = git_repository_index(&r_index, repo) < 0) || + (error = git_index_new(&i_index)) < 0 || + (error = git_index__fill(i_index, &r_index->entries) < 0) || + (error = git_repository__configmap_lookup(&ignorecase, repo, GIT_CONFIGMAP_IGNORECASE)) < 0) + goto cleanup; + + git_index__set_ignore_case(i_index, ignorecase); + + if ((error = build_workdir_tree(&w_tree, repo, i_index, b_commit)) < 0) + goto cleanup; + + error = git_commit_create( + w_commit_oid, + repo, + NULL, + stasher, + stasher, + NULL, + message, + w_tree, + u_commit ? 3 : 2, + parents); + +cleanup: + git_tree_free(w_tree); + git_index_free(i_index); + git_index_free(r_index); + return error; +} + +static int prepare_worktree_commit_message(git_str *out, const char *user_message) +{ + git_str buf = GIT_STR_INIT; + int error = 0; + + if (!user_message) { + git_str_printf(&buf, "WIP on %s", git_str_cstr(out)); + } else { + const char *colon; + + if ((colon = strchr(git_str_cstr(out), ':')) == NULL) + goto cleanup; + + git_str_puts(&buf, "On "); + git_str_put(&buf, git_str_cstr(out), colon - out->ptr); + git_str_printf(&buf, ": %s\n", user_message); + } + + if (git_str_oom(&buf)) { + error = -1; + goto cleanup; + } + + git_str_swap(out, &buf); + +cleanup: + git_str_dispose(&buf); + return error; +} + +static int update_reflog( + git_oid *w_commit_oid, + git_repository *repo, + const char *message) +{ + git_reference *stash; + int error; + + if ((error = git_reference_ensure_log(repo, GIT_REFS_STASH_FILE)) < 0) + return error; + + error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1, message); + + git_reference_free(stash); + + return error; +} + +static int is_dirty_cb(const char *path, unsigned int status, void *payload) +{ + GIT_UNUSED(path); + GIT_UNUSED(status); + GIT_UNUSED(payload); + + return GIT_PASSTHROUGH; +} + +static int ensure_there_are_changes_to_stash(git_repository *repo, uint32_t flags) +{ + int error; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_EXCLUDE_SUBMODULES; + + if (flags & GIT_STASH_INCLUDE_UNTRACKED) + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + if (flags & GIT_STASH_INCLUDE_IGNORED) + opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + + error = git_status_foreach_ext(repo, &opts, is_dirty_cb, NULL); + + if (error == GIT_PASSTHROUGH) + return 0; + + if (!error) + return create_error(GIT_ENOTFOUND, "there is nothing to stash."); + + return error; +} + +static int reset_index_and_workdir(git_repository *repo, git_commit *commit, uint32_t flags) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + if (flags & GIT_STASH_INCLUDE_UNTRACKED) + opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED; + if (flags & GIT_STASH_INCLUDE_IGNORED) + opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_IGNORED; + + return git_checkout_tree(repo, (git_object *)commit, &opts); +} + +int git_stash_save( + git_oid *out, + git_repository *repo, + const git_signature *stasher, + const char *message, + uint32_t flags) +{ + git_index *index = NULL; + git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL; + git_str msg = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(stasher); + + if ((error = git_repository__ensure_not_bare(repo, "stash save")) < 0) + return error; + + if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0) + goto cleanup; + + if ((error = ensure_there_are_changes_to_stash(repo, flags)) < 0) + goto cleanup; + + if ((error = git_repository_index(&index, repo)) < 0) + goto cleanup; + + if ((error = commit_index(&i_commit, repo, index, stasher, + git_str_cstr(&msg), b_commit)) < 0) + goto cleanup; + + if ((flags & (GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)) && + (error = commit_untracked(&u_commit, repo, stasher, + git_str_cstr(&msg), i_commit, flags)) < 0) + goto cleanup; + + if ((error = prepare_worktree_commit_message(&msg, message)) < 0) + goto cleanup; + + if ((error = commit_worktree(out, repo, stasher, git_str_cstr(&msg), + i_commit, b_commit, u_commit)) < 0) + goto cleanup; + + git_str_rtrim(&msg); + + if ((error = update_reflog(out, repo, git_str_cstr(&msg))) < 0) + goto cleanup; + + if ((error = reset_index_and_workdir(repo, (flags & GIT_STASH_KEEP_INDEX) ? i_commit : b_commit, + flags)) < 0) + goto cleanup; + +cleanup: + + git_str_dispose(&msg); + git_commit_free(i_commit); + git_commit_free(b_commit); + git_commit_free(u_commit); + git_index_free(index); + + return error; +} + +static int retrieve_stash_commit( + git_commit **commit, + git_repository *repo, + size_t index) +{ + git_reference *stash = NULL; + git_reflog *reflog = NULL; + int error; + size_t max; + const git_reflog_entry *entry; + + if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + max = git_reflog_entrycount(reflog); + if (!max || index > max - 1) { + error = GIT_ENOTFOUND; + git_error_set(GIT_ERROR_STASH, "no stashed state at position %" PRIuZ, index); + goto cleanup; + } + + entry = git_reflog_entry_byindex(reflog, index); + if ((error = git_commit_lookup(commit, repo, git_reflog_entry_id_new(entry))) < 0) + goto cleanup; + +cleanup: + git_reference_free(stash); + git_reflog_free(reflog); + return error; +} + +static int retrieve_stash_trees( + git_tree **out_stash_tree, + git_tree **out_base_tree, + git_tree **out_index_tree, + git_tree **out_index_parent_tree, + git_tree **out_untracked_tree, + git_commit *stash_commit) +{ + git_tree *stash_tree = NULL; + git_commit *base_commit = NULL; + git_tree *base_tree = NULL; + git_commit *index_commit = NULL; + git_tree *index_tree = NULL; + git_commit *index_parent_commit = NULL; + git_tree *index_parent_tree = NULL; + git_commit *untracked_commit = NULL; + git_tree *untracked_tree = NULL; + int error; + + if ((error = git_commit_tree(&stash_tree, stash_commit)) < 0) + goto cleanup; + + if ((error = git_commit_parent(&base_commit, stash_commit, 0)) < 0) + goto cleanup; + if ((error = git_commit_tree(&base_tree, base_commit)) < 0) + goto cleanup; + + if ((error = git_commit_parent(&index_commit, stash_commit, 1)) < 0) + goto cleanup; + if ((error = git_commit_tree(&index_tree, index_commit)) < 0) + goto cleanup; + + if ((error = git_commit_parent(&index_parent_commit, index_commit, 0)) < 0) + goto cleanup; + if ((error = git_commit_tree(&index_parent_tree, index_parent_commit)) < 0) + goto cleanup; + + if (git_commit_parentcount(stash_commit) == 3) { + if ((error = git_commit_parent(&untracked_commit, stash_commit, 2)) < 0) + goto cleanup; + if ((error = git_commit_tree(&untracked_tree, untracked_commit)) < 0) + goto cleanup; + } + + *out_stash_tree = stash_tree; + *out_base_tree = base_tree; + *out_index_tree = index_tree; + *out_index_parent_tree = index_parent_tree; + *out_untracked_tree = untracked_tree; + +cleanup: + git_commit_free(untracked_commit); + git_commit_free(index_parent_commit); + git_commit_free(index_commit); + git_commit_free(base_commit); + if (error < 0) { + git_tree_free(stash_tree); + git_tree_free(base_tree); + git_tree_free(index_tree); + git_tree_free(index_parent_tree); + git_tree_free(untracked_tree); + } + return error; +} + +static int merge_indexes( + git_index **out, + git_repository *repo, + git_tree *ancestor_tree, + git_index *ours_index, + git_index *theirs_index) +{ + git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error; + + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || + (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 || + (error = git_iterator_for_index(&theirs, repo, theirs_index, &iter_opts)) < 0) + goto done; + + error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); + +done: + git_iterator_free(ancestor); + git_iterator_free(ours); + git_iterator_free(theirs); + return error; +} + +static int merge_index_and_tree( + git_index **out, + git_repository *repo, + git_tree *ancestor_tree, + git_index *ours_index, + git_tree *theirs_tree) +{ + git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error; + + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || + (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 || + (error = git_iterator_for_tree(&theirs, theirs_tree, &iter_opts)) < 0) + goto done; + + error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); + +done: + git_iterator_free(ancestor); + git_iterator_free(ours); + git_iterator_free(theirs); + return error; +} + +static void normalize_apply_options( + git_stash_apply_options *opts, + const git_stash_apply_options *given_apply_opts) +{ + if (given_apply_opts != NULL) { + memcpy(opts, given_apply_opts, sizeof(git_stash_apply_options)); + } else { + git_stash_apply_options default_apply_opts = GIT_STASH_APPLY_OPTIONS_INIT; + memcpy(opts, &default_apply_opts, sizeof(git_stash_apply_options)); + } + + opts->checkout_options.checkout_strategy |= GIT_CHECKOUT_NO_REFRESH; + + if (!opts->checkout_options.our_label) + opts->checkout_options.our_label = "Updated upstream"; + + if (!opts->checkout_options.their_label) + opts->checkout_options.their_label = "Stashed changes"; +} + +int git_stash_apply_options_init(git_stash_apply_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_stash_apply_options, GIT_STASH_APPLY_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int version) +{ + return git_stash_apply_options_init(opts, version); +} +#endif + +#define NOTIFY_PROGRESS(opts, progress_type) \ + do { \ + if ((opts).progress_cb && \ + (error = (opts).progress_cb((progress_type), (opts).progress_payload))) { \ + error = (error < 0) ? error : -1; \ + goto cleanup; \ + } \ + } while(false); + +static int ensure_clean_index(git_repository *repo, git_index *index) +{ + git_tree *head_tree = NULL; + git_diff *index_diff = NULL; + int error = 0; + + if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || + (error = git_diff_tree_to_index( + &index_diff, repo, head_tree, index, NULL)) < 0) + goto done; + + if (git_diff_num_deltas(index_diff) > 0) { + git_error_set(GIT_ERROR_STASH, "%" PRIuZ " uncommitted changes exist in the index", + git_diff_num_deltas(index_diff)); + error = GIT_EUNCOMMITTED; + } + +done: + git_diff_free(index_diff); + git_tree_free(head_tree); + return error; +} + +static int stage_new_file(const git_index_entry **entries, void *data) +{ + git_index *index = data; + + if(entries[0] == NULL) + return git_index_add(index, entries[1]); + else + return git_index_add(index, entries[0]); +} + +static int stage_new_files( + git_index **out, + git_tree *parent_tree, + git_tree *tree) +{ + git_iterator *iterators[2] = { NULL, NULL }; + git_iterator_options iterator_options = GIT_ITERATOR_OPTIONS_INIT; + git_index *index = NULL; + int error; + + if ((error = git_index_new(&index)) < 0 || + (error = git_iterator_for_tree( + &iterators[0], parent_tree, &iterator_options)) < 0 || + (error = git_iterator_for_tree( + &iterators[1], tree, &iterator_options)) < 0) + goto done; + + error = git_iterator_walk(iterators, 2, stage_new_file, index); + +done: + if (error < 0) + git_index_free(index); + else + *out = index; + + git_iterator_free(iterators[0]); + git_iterator_free(iterators[1]); + + return error; +} + +int git_stash_apply( + git_repository *repo, + size_t index, + const git_stash_apply_options *given_opts) +{ + git_stash_apply_options opts; + unsigned int checkout_strategy; + git_commit *stash_commit = NULL; + git_tree *stash_tree = NULL; + git_tree *stash_parent_tree = NULL; + git_tree *index_tree = NULL; + git_tree *index_parent_tree = NULL; + git_tree *untracked_tree = NULL; + git_index *stash_adds = NULL; + git_index *repo_index = NULL; + git_index *unstashed_index = NULL; + git_index *modified_index = NULL; + git_index *untracked_index = NULL; + int error; + + GIT_ERROR_CHECK_VERSION(given_opts, GIT_STASH_APPLY_OPTIONS_VERSION, "git_stash_apply_options"); + + normalize_apply_options(&opts, given_opts); + checkout_strategy = opts.checkout_options.checkout_strategy; + + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_LOADING_STASH); + + /* Retrieve commit corresponding to the given stash */ + if ((error = retrieve_stash_commit(&stash_commit, repo, index)) < 0) + goto cleanup; + + /* Retrieve all trees in the stash */ + if ((error = retrieve_stash_trees( + &stash_tree, &stash_parent_tree, &index_tree, + &index_parent_tree, &untracked_tree, stash_commit)) < 0) + goto cleanup; + + /* Load repo index */ + if ((error = git_repository_index(&repo_index, repo)) < 0) + goto cleanup; + + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX); + + if ((error = ensure_clean_index(repo, repo_index)) < 0) + goto cleanup; + + /* Restore index if required */ + if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) && + git_oid_cmp(git_tree_id(stash_parent_tree), git_tree_id(index_tree))) { + + if ((error = merge_index_and_tree( + &unstashed_index, repo, index_parent_tree, repo_index, index_tree)) < 0) + goto cleanup; + + if (git_index_has_conflicts(unstashed_index)) { + error = GIT_ECONFLICT; + goto cleanup; + } + + /* Otherwise, stage any new files in the stash tree. (Note: their + * previously unstaged contents are staged, not the previously staged.) + */ + } else if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) == 0) { + if ((error = stage_new_files( + &stash_adds, stash_parent_tree, stash_tree)) < 0 || + (error = merge_indexes( + &unstashed_index, repo, stash_parent_tree, repo_index, stash_adds)) < 0) + goto cleanup; + } + + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED); + + /* Restore modified files in workdir */ + if ((error = merge_index_and_tree( + &modified_index, repo, stash_parent_tree, repo_index, stash_tree)) < 0) + goto cleanup; + + /* If applicable, restore untracked / ignored files in workdir */ + if (untracked_tree) { + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED); + + if ((error = merge_index_and_tree(&untracked_index, repo, NULL, repo_index, untracked_tree)) < 0) + goto cleanup; + } + + if (untracked_index) { + opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; + + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED); + + if ((error = git_checkout_index(repo, untracked_index, &opts.checkout_options)) < 0) + goto cleanup; + + opts.checkout_options.checkout_strategy = checkout_strategy; + } + + + /* If there are conflicts in the modified index, then we need to actually + * check that out as the repo's index. Otherwise, we don't update the + * index. + */ + + if (!git_index_has_conflicts(modified_index)) + opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; + + /* Check out the modified index using the existing repo index as baseline, + * so that existing modifications in the index can be rewritten even when + * checking out safely. + */ + opts.checkout_options.baseline_index = repo_index; + + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED); + + if ((error = git_checkout_index(repo, modified_index, &opts.checkout_options)) < 0) + goto cleanup; + + if (unstashed_index && !git_index_has_conflicts(modified_index)) { + if ((error = git_index_read_index(repo_index, unstashed_index)) < 0) + goto cleanup; + } + + NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_DONE); + + error = git_index_write(repo_index); + +cleanup: + git_index_free(untracked_index); + git_index_free(modified_index); + git_index_free(unstashed_index); + git_index_free(stash_adds); + git_index_free(repo_index); + git_tree_free(untracked_tree); + git_tree_free(index_parent_tree); + git_tree_free(index_tree); + git_tree_free(stash_parent_tree); + git_tree_free(stash_tree); + git_commit_free(stash_commit); + return error; +} + +int git_stash_foreach( + git_repository *repo, + git_stash_cb callback, + void *payload) +{ + git_reference *stash; + git_reflog *reflog = NULL; + int error; + size_t i, max; + const git_reflog_entry *entry; + + error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE); + if (error == GIT_ENOTFOUND) { + git_error_clear(); + return 0; + } + if (error < 0) + goto cleanup; + + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + max = git_reflog_entrycount(reflog); + for (i = 0; i < max; i++) { + entry = git_reflog_entry_byindex(reflog, i); + + error = callback(i, + git_reflog_entry_message(entry), + git_reflog_entry_id_new(entry), + payload); + + if (error) { + git_error_set_after_callback(error); + break; + } + } + +cleanup: + git_reference_free(stash); + git_reflog_free(reflog); + return error; +} + +int git_stash_drop( + git_repository *repo, + size_t index) +{ + git_transaction *tx; + git_reference *stash = NULL; + git_reflog *reflog = NULL; + size_t max; + int error; + + if ((error = git_transaction_new(&tx, repo)) < 0) + return error; + + if ((error = git_transaction_lock_ref(tx, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + + max = git_reflog_entrycount(reflog); + + if (!max || index > max - 1) { + error = GIT_ENOTFOUND; + git_error_set(GIT_ERROR_STASH, "no stashed state at position %" PRIuZ, index); + goto cleanup; + } + + if ((error = git_reflog_drop(reflog, index, true)) < 0) + goto cleanup; + + if ((error = git_transaction_set_reflog(tx, GIT_REFS_STASH_FILE, reflog)) < 0) + goto cleanup; + + if (max == 1) { + if ((error = git_transaction_remove(tx, GIT_REFS_STASH_FILE)) < 0) + goto cleanup; + } else if (index == 0) { + const git_reflog_entry *entry; + + entry = git_reflog_entry_byindex(reflog, 0); + if ((error = git_transaction_set_target(tx, GIT_REFS_STASH_FILE, &entry->oid_cur, NULL, NULL)) < 0) + goto cleanup; + } + + error = git_transaction_commit(tx); + +cleanup: + git_reference_free(stash); + git_transaction_free(tx); + git_reflog_free(reflog); + return error; +} + +int git_stash_pop( + git_repository *repo, + size_t index, + const git_stash_apply_options *options) +{ + int error; + + if ((error = git_stash_apply(repo, index, options)) < 0) + return error; + + return git_stash_drop(repo, index); +} diff --git a/src/libgit2/status.c b/src/libgit2/status.c new file mode 100644 index 000000000..df0f74507 --- /dev/null +++ b/src/libgit2/status.c @@ -0,0 +1,584 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "status.h" + +#include "git2.h" +#include "futils.h" +#include "hash.h" +#include "vector.h" +#include "tree.h" +#include "git2/status.h" +#include "repository.h" +#include "ignore.h" +#include "index.h" +#include "wildmatch.h" + +#include "git2/diff.h" +#include "diff.h" +#include "diff_generate.h" + +static unsigned int index_delta2status(const git_diff_delta *head2idx) +{ + git_status_t st = GIT_STATUS_CURRENT; + + switch (head2idx->status) { + case GIT_DELTA_ADDED: + case GIT_DELTA_COPIED: + st = GIT_STATUS_INDEX_NEW; + break; + case GIT_DELTA_DELETED: + st = GIT_STATUS_INDEX_DELETED; + break; + case GIT_DELTA_MODIFIED: + st = GIT_STATUS_INDEX_MODIFIED; + break; + case GIT_DELTA_RENAMED: + st = GIT_STATUS_INDEX_RENAMED; + + if (!git_oid_equal(&head2idx->old_file.id, &head2idx->new_file.id)) + st |= GIT_STATUS_INDEX_MODIFIED; + break; + case GIT_DELTA_TYPECHANGE: + st = GIT_STATUS_INDEX_TYPECHANGE; + break; + case GIT_DELTA_CONFLICTED: + st = GIT_STATUS_CONFLICTED; + break; + default: + break; + } + + return st; +} + +static unsigned int workdir_delta2status( + git_diff *diff, git_diff_delta *idx2wd) +{ + git_status_t st = GIT_STATUS_CURRENT; + + switch (idx2wd->status) { + case GIT_DELTA_ADDED: + case GIT_DELTA_COPIED: + case GIT_DELTA_UNTRACKED: + st = GIT_STATUS_WT_NEW; + break; + case GIT_DELTA_UNREADABLE: + st = GIT_STATUS_WT_UNREADABLE; + break; + case GIT_DELTA_DELETED: + st = GIT_STATUS_WT_DELETED; + break; + case GIT_DELTA_MODIFIED: + st = GIT_STATUS_WT_MODIFIED; + break; + case GIT_DELTA_IGNORED: + st = GIT_STATUS_IGNORED; + break; + case GIT_DELTA_RENAMED: + st = GIT_STATUS_WT_RENAMED; + + if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id)) { + /* if OIDs don't match, we might need to calculate them now to + * discern between RENAMED vs RENAMED+MODIFIED + */ + if (git_oid_is_zero(&idx2wd->old_file.id) && + diff->old_src == GIT_ITERATOR_WORKDIR && + !git_diff__oid_for_file( + &idx2wd->old_file.id, diff, idx2wd->old_file.path, + idx2wd->old_file.mode, idx2wd->old_file.size)) + idx2wd->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + if (git_oid_is_zero(&idx2wd->new_file.id) && + diff->new_src == GIT_ITERATOR_WORKDIR && + !git_diff__oid_for_file( + &idx2wd->new_file.id, diff, idx2wd->new_file.path, + idx2wd->new_file.mode, idx2wd->new_file.size)) + idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id)) + st |= GIT_STATUS_WT_MODIFIED; + } + break; + case GIT_DELTA_TYPECHANGE: + st = GIT_STATUS_WT_TYPECHANGE; + break; + case GIT_DELTA_CONFLICTED: + st = GIT_STATUS_CONFLICTED; + break; + default: + break; + } + + return st; +} + +static bool status_is_included( + git_status_list *status, + git_diff_delta *head2idx, + git_diff_delta *idx2wd) +{ + if (!(status->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES)) + return 1; + + /* if excluding submodules and this is a submodule everywhere */ + if (head2idx) { + if (head2idx->status != GIT_DELTA_ADDED && + head2idx->old_file.mode != GIT_FILEMODE_COMMIT) + return 1; + if (head2idx->status != GIT_DELTA_DELETED && + head2idx->new_file.mode != GIT_FILEMODE_COMMIT) + return 1; + } + if (idx2wd) { + if (idx2wd->status != GIT_DELTA_ADDED && + idx2wd->old_file.mode != GIT_FILEMODE_COMMIT) + return 1; + if (idx2wd->status != GIT_DELTA_DELETED && + idx2wd->new_file.mode != GIT_FILEMODE_COMMIT) + return 1; + } + + /* only get here if every valid mode is GIT_FILEMODE_COMMIT */ + return 0; +} + +static git_status_t status_compute( + git_status_list *status, + git_diff_delta *head2idx, + git_diff_delta *idx2wd) +{ + git_status_t st = GIT_STATUS_CURRENT; + + if (head2idx) + st |= index_delta2status(head2idx); + + if (idx2wd) + st |= workdir_delta2status(status->idx2wd, idx2wd); + + return st; +} + +static int status_collect( + git_diff_delta *head2idx, + git_diff_delta *idx2wd, + void *payload) +{ + git_status_list *status = payload; + git_status_entry *status_entry; + + if (!status_is_included(status, head2idx, idx2wd)) + return 0; + + status_entry = git__malloc(sizeof(git_status_entry)); + GIT_ERROR_CHECK_ALLOC(status_entry); + + status_entry->status = status_compute(status, head2idx, idx2wd); + status_entry->head_to_index = head2idx; + status_entry->index_to_workdir = idx2wd; + + return git_vector_insert(&status->paired, status_entry); +} + +GIT_INLINE(int) status_entry_cmp_base( + const void *a, + const void *b, + int (*strcomp)(const char *a, const char *b)) +{ + const git_status_entry *entry_a = a; + const git_status_entry *entry_b = b; + const git_diff_delta *delta_a, *delta_b; + + delta_a = entry_a->index_to_workdir ? entry_a->index_to_workdir : + entry_a->head_to_index; + delta_b = entry_b->index_to_workdir ? entry_b->index_to_workdir : + entry_b->head_to_index; + + if (!delta_a && delta_b) + return -1; + if (delta_a && !delta_b) + return 1; + if (!delta_a && !delta_b) + return 0; + + return strcomp(delta_a->new_file.path, delta_b->new_file.path); +} + +static int status_entry_icmp(const void *a, const void *b) +{ + return status_entry_cmp_base(a, b, git__strcasecmp); +} + +static int status_entry_cmp(const void *a, const void *b) +{ + return status_entry_cmp_base(a, b, git__strcmp); +} + +static git_status_list *git_status_list_alloc(git_index *index) +{ + git_status_list *status = NULL; + int (*entrycmp)(const void *a, const void *b); + + if (!(status = git__calloc(1, sizeof(git_status_list)))) + return NULL; + + entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp; + + if (git_vector_init(&status->paired, 0, entrycmp) < 0) { + git__free(status); + return NULL; + } + + return status; +} + +static int status_validate_options(const git_status_options *opts) +{ + if (!opts) + return 0; + + GIT_ERROR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options"); + + if (opts->show > GIT_STATUS_SHOW_WORKDIR_ONLY) { + git_error_set(GIT_ERROR_INVALID, "unknown status 'show' option"); + return -1; + } + + if ((opts->flags & GIT_STATUS_OPT_NO_REFRESH) != 0 && + (opts->flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) { + git_error_set(GIT_ERROR_INVALID, "updating index from status " + "is not allowed when index refresh is disabled"); + return -1; + } + + return 0; +} + +int git_status_list_new( + git_status_list **out, + git_repository *repo, + const git_status_options *opts) +{ + git_index *index = NULL; + git_status_list *status = NULL; + git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopt = GIT_DIFF_FIND_OPTIONS_INIT; + git_tree *head = NULL; + git_status_show_t show = + opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + int error = 0; + unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS; + + *out = NULL; + + if (status_validate_options(opts) < 0) + return -1; + + if ((error = git_repository__ensure_not_bare(repo, "status")) < 0 || + (error = git_repository_index(&index, repo)) < 0) + return error; + + if (opts != NULL && opts->baseline != NULL) { + head = opts->baseline; + } else { + /* if there is no HEAD, that's okay - we'll make an empty iterator */ + if ((error = git_repository_head_tree(&head, repo)) < 0) { + if (error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH) + goto done; + git_error_clear(); + } + } + + /* refresh index from disk unless prevented */ + if ((flags & GIT_STATUS_OPT_NO_REFRESH) == 0 && + git_index_read_safely(index) < 0) + git_error_clear(); + + status = git_status_list_alloc(index); + GIT_ERROR_CHECK_ALLOC(status); + + if (opts) { + memcpy(&status->opts, opts, sizeof(git_status_options)); + memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); + } + + diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE; + findopt.flags = GIT_DIFF_FIND_FOR_UNTRACKED; + + if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED; + if ((flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED; + if ((flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED; + if ((flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + if ((flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH; + if ((flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS; + if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; + if ((flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_UPDATE_INDEX; + if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE; + if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED; + + if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0) + findopt.flags = findopt.flags | + GIT_DIFF_FIND_AND_BREAK_REWRITES | + GIT_DIFF_FIND_RENAMES_FROM_REWRITES | + GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY; + + if (opts != NULL && opts->rename_threshold != 0) + findopt.rename_threshold = opts->rename_threshold; + + if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { + if ((error = git_diff_tree_to_index( + &status->head2idx, repo, head, index, &diffopt)) < 0) + goto done; + + if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && + (error = git_diff_find_similar(status->head2idx, &findopt)) < 0) + goto done; + } + + if (show != GIT_STATUS_SHOW_INDEX_ONLY) { + if ((error = git_diff_index_to_workdir( + &status->idx2wd, repo, index, &diffopt)) < 0) { + goto done; + } + + if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && + (error = git_diff_find_similar(status->idx2wd, &findopt)) < 0) + goto done; + } + + error = git_diff__paired_foreach( + status->head2idx, status->idx2wd, status_collect, status); + if (error < 0) + goto done; + + if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY) + git_vector_set_cmp(&status->paired, status_entry_cmp); + if (flags & GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY) + git_vector_set_cmp(&status->paired, status_entry_icmp); + + if ((flags & + (GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR | + GIT_STATUS_OPT_SORT_CASE_SENSITIVELY | + GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)) != 0) + git_vector_sort(&status->paired); + +done: + if (error < 0) { + git_status_list_free(status); + status = NULL; + } + + *out = status; + + if (opts == NULL || opts->baseline != head) + git_tree_free(head); + git_index_free(index); + + return error; +} + +size_t git_status_list_entrycount(git_status_list *status) +{ + GIT_ASSERT_ARG_WITH_RETVAL(status, 0); + + return status->paired.length; +} + +const git_status_entry *git_status_byindex(git_status_list *status, size_t i) +{ + GIT_ASSERT_ARG_WITH_RETVAL(status, NULL); + + return git_vector_get(&status->paired, i); +} + +void git_status_list_free(git_status_list *status) +{ + if (status == NULL) + return; + + git_diff_free(status->head2idx); + git_diff_free(status->idx2wd); + + git_vector_free_deep(&status->paired); + + git__memzero(status, sizeof(*status)); + git__free(status); +} + +int git_status_foreach_ext( + git_repository *repo, + const git_status_options *opts, + git_status_cb cb, + void *payload) +{ + git_status_list *status; + const git_status_entry *status_entry; + size_t i; + int error = 0; + + if ((error = git_status_list_new(&status, repo, opts)) < 0) { + return error; + } + + git_vector_foreach(&status->paired, i, status_entry) { + const char *path = status_entry->head_to_index ? + status_entry->head_to_index->old_file.path : + status_entry->index_to_workdir->old_file.path; + + if ((error = cb(path, status_entry->status, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + + git_status_list_free(status); + + return error; +} + +int git_status_foreach(git_repository *repo, git_status_cb cb, void *payload) +{ + return git_status_foreach_ext(repo, NULL, cb, payload); +} + +struct status_file_info { + char *expected; + unsigned int count; + unsigned int status; + int wildmatch_flags; + int ambiguous; +}; + +static int get_one_status(const char *path, unsigned int status, void *data) +{ + struct status_file_info *sfi = data; + int (*strcomp)(const char *a, const char *b); + + sfi->count++; + sfi->status = status; + + strcomp = (sfi->wildmatch_flags & WM_CASEFOLD) ? git__strcasecmp : git__strcmp; + + if (sfi->count > 1 || + (strcomp(sfi->expected, path) != 0 && + wildmatch(sfi->expected, path, sfi->wildmatch_flags) != 0)) + { + sfi->ambiguous = true; + return GIT_EAMBIGUOUS; /* git_error_set will be done by caller */ + } + + return 0; +} + +int git_status_file( + unsigned int *status_flags, + git_repository *repo, + const char *path) +{ + int error; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_file_info sfi = {0}; + git_index *index; + + GIT_ASSERT_ARG(status_flags); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(path); + + if ((error = git_repository_index__weakptr(&index, repo)) < 0) + return error; + + if ((sfi.expected = git__strdup(path)) == NULL) + return -1; + if (index->ignore_case) + sfi.wildmatch_flags = WM_CASEFOLD; + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS | + GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; + opts.pathspec.count = 1; + opts.pathspec.strings = &sfi.expected; + + error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi); + + if (error < 0 && sfi.ambiguous) { + git_error_set(GIT_ERROR_INVALID, + "ambiguous path '%s' given to git_status_file", sfi.expected); + error = GIT_EAMBIGUOUS; + } + + if (!error && !sfi.count) { + git_error_set(GIT_ERROR_INVALID, + "attempt to get status of nonexistent file '%s'", path); + error = GIT_ENOTFOUND; + } + + *status_flags = sfi.status; + + git__free(sfi.expected); + + return error; +} + +int git_status_should_ignore( + int *ignored, + git_repository *repo, + const char *path) +{ + return git_ignore_path_is_ignored(ignored, repo, path); +} + +int git_status_options_init(git_status_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_status_options, GIT_STATUS_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_status_init_options(git_status_options *opts, unsigned int version) +{ + return git_status_options_init(opts, version); +} +#endif + +int git_status_list_get_perfdata( + git_diff_perfdata *out, const git_status_list *status) +{ + GIT_ASSERT_ARG(out); + + GIT_ERROR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata"); + + out->stat_calls = 0; + out->oid_calculations = 0; + + if (status->head2idx) { + out->stat_calls += status->head2idx->perf.stat_calls; + out->oid_calculations += status->head2idx->perf.oid_calculations; + } + if (status->idx2wd) { + out->stat_calls += status->idx2wd->perf.stat_calls; + out->oid_calculations += status->idx2wd->perf.oid_calculations; + } + + return 0; +} + diff --git a/src/libgit2/status.h b/src/libgit2/status.h new file mode 100644 index 000000000..907479a22 --- /dev/null +++ b/src/libgit2/status.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_status_h__ +#define INCLUDE_status_h__ + +#include "common.h" + +#include "diff.h" +#include "git2/status.h" +#include "git2/diff.h" + +struct git_status_list { + git_status_options opts; + + git_diff *head2idx; + git_diff *idx2wd; + + git_vector paired; +}; + +#endif diff --git a/src/libgit2/str.c b/src/libgit2/str.c new file mode 100644 index 000000000..0d405bfda --- /dev/null +++ b/src/libgit2/str.c @@ -0,0 +1,1372 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "str.h" +#include "posix.h" +#include + +/* Used as default value for git_str->ptr so that people can always + * assume ptr is non-NULL and zero terminated even for new git_strs. + */ +char git_str__initstr[1]; + +char git_str__oom[1]; + +#define ENSURE_SIZE(b, d) \ + if ((b)->ptr == git_str__oom || \ + ((d) > (b)->asize && git_str_grow((b), (d)) < 0))\ + return -1; + + +int git_str_init(git_str *buf, size_t initial_size) +{ + buf->asize = 0; + buf->size = 0; + buf->ptr = git_str__initstr; + + ENSURE_SIZE(buf, initial_size); + + return 0; +} + +int git_str_try_grow( + git_str *buf, size_t target_size, bool mark_oom) +{ + char *new_ptr; + size_t new_size; + + if (buf->ptr == git_str__oom) + return -1; + + if (buf->asize == 0 && buf->size != 0) { + git_error_set(GIT_ERROR_INVALID, "cannot grow a borrowed buffer"); + return GIT_EINVALID; + } + + if (!target_size) + target_size = buf->size; + + if (target_size <= buf->asize) + return 0; + + if (buf->asize == 0) { + new_size = target_size; + new_ptr = NULL; + } else { + new_size = buf->asize; + /* + * Grow the allocated buffer by 1.5 to allow + * re-use of memory holes resulting from the + * realloc. If this is still too small, then just + * use the target size. + */ + if ((new_size = (new_size << 1) - (new_size >> 1)) < target_size) + new_size = target_size; + new_ptr = buf->ptr; + } + + /* round allocation up to multiple of 8 */ + new_size = (new_size + 7) & ~7; + + if (new_size < buf->size) { + if (mark_oom) { + if (buf->ptr && buf->ptr != git_str__initstr) + git__free(buf->ptr); + buf->ptr = git_str__oom; + } + + git_error_set_oom(); + return -1; + } + + new_ptr = git__realloc(new_ptr, new_size); + + if (!new_ptr) { + if (mark_oom) { + if (buf->ptr && (buf->ptr != git_str__initstr)) + git__free(buf->ptr); + buf->ptr = git_str__oom; + } + return -1; + } + + buf->asize = new_size; + buf->ptr = new_ptr; + + /* truncate the existing buffer size if necessary */ + if (buf->size >= buf->asize) + buf->size = buf->asize - 1; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +int git_str_grow(git_str *buffer, size_t target_size) +{ + return git_str_try_grow(buffer, target_size, true); +} + +int git_str_grow_by(git_str *buffer, size_t additional_size) +{ + size_t newsize; + + if (GIT_ADD_SIZET_OVERFLOW(&newsize, buffer->size, additional_size)) { + buffer->ptr = git_str__oom; + return -1; + } + + return git_str_try_grow(buffer, newsize, true); +} + +void git_str_dispose(git_str *buf) +{ + if (!buf) return; + + if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_str__oom) + git__free(buf->ptr); + + git_str_init(buf, 0); +} + +void git_str_clear(git_str *buf) +{ + buf->size = 0; + + if (!buf->ptr) { + buf->ptr = git_str__initstr; + buf->asize = 0; + } + + if (buf->asize > 0) + buf->ptr[0] = '\0'; +} + +int git_str_set(git_str *buf, const void *data, size_t len) +{ + size_t alloclen; + + if (len == 0 || data == NULL) { + git_str_clear(buf); + } else { + if (data != buf->ptr) { + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); + ENSURE_SIZE(buf, alloclen); + memmove(buf->ptr, data, len); + } + + buf->size = len; + if (buf->asize > buf->size) + buf->ptr[buf->size] = '\0'; + + } + return 0; +} + +int git_str_sets(git_str *buf, const char *string) +{ + return git_str_set(buf, string, string ? strlen(string) : 0); +} + +int git_str_putc(git_str *buf, char c) +{ + size_t new_size; + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, 2); + ENSURE_SIZE(buf, new_size); + buf->ptr[buf->size++] = c; + buf->ptr[buf->size] = '\0'; + return 0; +} + +int git_str_putcn(git_str *buf, char c, size_t len) +{ + size_t new_size; + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + memset(buf->ptr + buf->size, c, len); + buf->size += len; + buf->ptr[buf->size] = '\0'; + return 0; +} + +int git_str_put(git_str *buf, const char *data, size_t len) +{ + if (len) { + size_t new_size; + + GIT_ASSERT_ARG(data); + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + memmove(buf->ptr + buf->size, data, len); + buf->size += len; + buf->ptr[buf->size] = '\0'; + } + return 0; +} + +int git_str_puts(git_str *buf, const char *string) +{ + GIT_ASSERT_ARG(string); + + return git_str_put(buf, string, strlen(string)); +} + +static char hex_encode[] = "0123456789abcdef"; + +int git_str_encode_hexstr(git_str *str, const char *data, size_t len) +{ + size_t new_size, i; + char *s; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&new_size, len, 2); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + + if (git_str_grow_by(str, new_size) < 0) + return -1; + + s = str->ptr + str->size; + + for (i = 0; i < len; i++) { + *s++ = hex_encode[(data[i] & 0xf0) >> 4]; + *s++ = hex_encode[(data[i] & 0x0f)]; + } + + str->size += (len * 2); + str->ptr[str->size] = '\0'; + + return 0; +} + +static const char base64_encode[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +int git_str_encode_base64(git_str *buf, const char *data, size_t len) +{ + size_t extra = len % 3; + uint8_t *write, a, b, c; + const uint8_t *read = (const uint8_t *)data; + size_t blocks = (len / 3) + !!extra, alloclen; + + GIT_ERROR_CHECK_ALLOC_ADD(&blocks, blocks, 1); + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 4); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size); + + ENSURE_SIZE(buf, alloclen); + write = (uint8_t *)&buf->ptr[buf->size]; + + /* convert each run of 3 bytes into 4 output bytes */ + for (len -= extra; len > 0; len -= 3) { + a = *read++; + b = *read++; + c = *read++; + + *write++ = base64_encode[a >> 2]; + *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; + *write++ = base64_encode[(b & 0x0f) << 2 | c >> 6]; + *write++ = base64_encode[c & 0x3f]; + } + + if (extra > 0) { + a = *read++; + b = (extra > 1) ? *read++ : 0; + + *write++ = base64_encode[a >> 2]; + *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; + *write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '='; + *write++ = '='; + } + + buf->size = ((char *)write) - buf->ptr; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +/* The inverse of base64_encode */ +static const int8_t base64_decode[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +int git_str_decode_base64(git_str *buf, const char *base64, size_t len) +{ + size_t i; + int8_t a, b, c, d; + size_t orig_size = buf->size, new_size; + + if (len % 4) { + git_error_set(GIT_ERROR_INVALID, "invalid base64 input"); + return -1; + } + + GIT_ASSERT_ARG(len % 4 == 0); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + + for (i = 0; i < len; i += 4) { + if ((a = base64_decode[(unsigned char)base64[i]]) < 0 || + (b = base64_decode[(unsigned char)base64[i+1]]) < 0 || + (c = base64_decode[(unsigned char)base64[i+2]]) < 0 || + (d = base64_decode[(unsigned char)base64[i+3]]) < 0) { + buf->size = orig_size; + buf->ptr[buf->size] = '\0'; + + git_error_set(GIT_ERROR_INVALID, "invalid base64 input"); + return -1; + } + + buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4); + buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f); + } + + buf->ptr[buf->size] = '\0'; + return 0; +} + +static const char base85_encode[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; + +int git_str_encode_base85(git_str *buf, const char *data, size_t len) +{ + size_t blocks = (len / 4) + !!(len % 4), alloclen; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 5); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + + ENSURE_SIZE(buf, alloclen); + + while (len) { + uint32_t acc = 0; + char b85[5]; + int i; + + for (i = 24; i >= 0; i -= 8) { + uint8_t ch = *data++; + acc |= (uint32_t)ch << i; + + if (--len == 0) + break; + } + + for (i = 4; i >= 0; i--) { + int val = acc % 85; + acc /= 85; + + b85[i] = base85_encode[val]; + } + + for (i = 0; i < 5; i++) + buf->ptr[buf->size++] = b85[i]; + } + + buf->ptr[buf->size] = '\0'; + + return 0; +} + +/* The inverse of base85_encode */ +static const int8_t base85_decode[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 73, 74, 75, 76, 77, + 78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80, + 81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +int git_str_decode_base85( + git_str *buf, + const char *base85, + size_t base85_len, + size_t output_len) +{ + size_t orig_size = buf->size, new_size; + + if (base85_len % 5 || + output_len > base85_len * 4 / 5) { + git_error_set(GIT_ERROR_INVALID, "invalid base85 input"); + return -1; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + + while (output_len) { + unsigned acc = 0; + int de, cnt = 4; + unsigned char ch; + do { + ch = *base85++; + de = base85_decode[ch]; + if (--de < 0) + goto on_error; + + acc = acc * 85 + de; + } while (--cnt); + ch = *base85++; + de = base85_decode[ch]; + if (--de < 0) + goto on_error; + + /* Detect overflow. */ + if (0xffffffff / 85 < acc || + 0xffffffff - de < (acc *= 85)) + goto on_error; + + acc += de; + + cnt = (output_len < 4) ? (int)output_len : 4; + output_len -= cnt; + do { + acc = (acc << 8) | (acc >> 24); + buf->ptr[buf->size++] = acc; + } while (--cnt); + } + + buf->ptr[buf->size] = 0; + + return 0; + +on_error: + buf->size = orig_size; + buf->ptr[buf->size] = '\0'; + + git_error_set(GIT_ERROR_INVALID, "invalid base85 input"); + return -1; +} + +#define HEX_DECODE(c) ((c | 32) % 39 - 9) + +int git_str_decode_percent( + git_str *buf, + const char *str, + size_t str_len) +{ + size_t str_pos, new_size; + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, str_len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + + for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) { + if (str[str_pos] == '%' && + str_len > str_pos + 2 && + isxdigit(str[str_pos + 1]) && + isxdigit(str[str_pos + 2])) { + buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) + + HEX_DECODE(str[str_pos + 2]); + str_pos += 2; + } else { + buf->ptr[buf->size] = str[str_pos]; + } + } + + buf->ptr[buf->size] = '\0'; + return 0; +} + +int git_str_vprintf(git_str *buf, const char *format, va_list ap) +{ + size_t expected_size, new_size; + int len; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&expected_size, strlen(format), 2); + GIT_ERROR_CHECK_ALLOC_ADD(&expected_size, expected_size, buf->size); + ENSURE_SIZE(buf, expected_size); + + while (1) { + va_list args; + va_copy(args, ap); + + len = p_vsnprintf( + buf->ptr + buf->size, + buf->asize - buf->size, + format, args + ); + + va_end(args); + + if (len < 0) { + git__free(buf->ptr); + buf->ptr = git_str__oom; + return -1; + } + + if ((size_t)len + 1 <= buf->asize - buf->size) { + buf->size += len; + break; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + } + + return 0; +} + +int git_str_printf(git_str *buf, const char *format, ...) +{ + int r; + va_list ap; + + va_start(ap, format); + r = git_str_vprintf(buf, format, ap); + va_end(ap); + + return r; +} + +int git_str_copy_cstr(char *data, size_t datasize, const git_str *buf) +{ + size_t copylen; + + GIT_ASSERT_ARG(data); + GIT_ASSERT_ARG(datasize); + GIT_ASSERT_ARG(buf); + + data[0] = '\0'; + + if (buf->size == 0 || buf->asize <= 0) + return 0; + + copylen = buf->size; + if (copylen > datasize - 1) + copylen = datasize - 1; + memmove(data, buf->ptr, copylen); + data[copylen] = '\0'; + + return 0; +} + +void git_str_consume_bytes(git_str *buf, size_t len) +{ + git_str_consume(buf, buf->ptr + len); +} + +void git_str_consume(git_str *buf, const char *end) +{ + if (end > buf->ptr && end <= buf->ptr + buf->size) { + size_t consumed = end - buf->ptr; + memmove(buf->ptr, end, buf->size - consumed); + buf->size -= consumed; + buf->ptr[buf->size] = '\0'; + } +} + +void git_str_truncate(git_str *buf, size_t len) +{ + if (len >= buf->size) + return; + + buf->size = len; + if (buf->size < buf->asize) + buf->ptr[buf->size] = '\0'; +} + +void git_str_shorten(git_str *buf, size_t amount) +{ + if (buf->size > amount) + git_str_truncate(buf, buf->size - amount); + else + git_str_clear(buf); +} + +void git_str_truncate_at_char(git_str *buf, char separator) +{ + ssize_t idx = git_str_find(buf, separator); + if (idx >= 0) + git_str_truncate(buf, (size_t)idx); +} + +void git_str_rtruncate_at_char(git_str *buf, char separator) +{ + ssize_t idx = git_str_rfind_next(buf, separator); + git_str_truncate(buf, idx < 0 ? 0 : (size_t)idx); +} + +void git_str_swap(git_str *str_a, git_str *str_b) +{ + git_str t = *str_a; + *str_a = *str_b; + *str_b = t; +} + +char *git_str_detach(git_str *buf) +{ + char *data = buf->ptr; + + if (buf->asize == 0 || buf->ptr == git_str__oom) + return NULL; + + git_str_init(buf, 0); + + return data; +} + +int git_str_attach(git_str *buf, char *ptr, size_t asize) +{ + git_str_dispose(buf); + + if (ptr) { + buf->ptr = ptr; + buf->size = strlen(ptr); + if (asize) + buf->asize = (asize < buf->size) ? buf->size + 1 : asize; + else /* pass 0 to fall back on strlen + 1 */ + buf->asize = buf->size + 1; + } + + ENSURE_SIZE(buf, asize); + return 0; +} + +void git_str_attach_notowned(git_str *buf, const char *ptr, size_t size) +{ + if (git_str_is_allocated(buf)) + git_str_dispose(buf); + + if (!size) { + git_str_init(buf, 0); + } else { + buf->ptr = (char *)ptr; + buf->asize = 0; + buf->size = size; + } +} + +int git_str_join_n(git_str *buf, char separator, int nbuf, ...) +{ + va_list ap; + int i; + size_t total_size = 0, original_size = buf->size; + char *out, *original = buf->ptr; + + if (buf->size > 0 && buf->ptr[buf->size - 1] != separator) + ++total_size; /* space for initial separator */ + + /* Make two passes to avoid multiple reallocation */ + + va_start(ap, nbuf); + for (i = 0; i < nbuf; ++i) { + const char *segment; + size_t segment_len; + + segment = va_arg(ap, const char *); + if (!segment) + continue; + + segment_len = strlen(segment); + + GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, segment_len); + + if (segment_len == 0 || segment[segment_len - 1] != separator) + GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1); + } + va_end(ap); + + /* expand buffer if needed */ + if (total_size == 0) + return 0; + + GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1); + if (git_str_grow_by(buf, total_size) < 0) + return -1; + + out = buf->ptr + buf->size; + + /* append separator to existing buf if needed */ + if (buf->size > 0 && out[-1] != separator) + *out++ = separator; + + va_start(ap, nbuf); + for (i = 0; i < nbuf; ++i) { + const char *segment; + size_t segment_len; + + segment = va_arg(ap, const char *); + if (!segment) + continue; + + /* deal with join that references buffer's original content */ + if (segment >= original && segment < original + original_size) { + size_t offset = (segment - original); + segment = buf->ptr + offset; + segment_len = original_size - offset; + } else { + segment_len = strlen(segment); + } + + /* skip leading separators */ + if (out > buf->ptr && out[-1] == separator) + while (segment_len > 0 && *segment == separator) { + segment++; + segment_len--; + } + + /* copy over next buffer */ + if (segment_len > 0) { + memmove(out, segment, segment_len); + out += segment_len; + } + + /* append trailing separator (except for last item) */ + if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator) + *out++ = separator; + } + va_end(ap); + + /* set size based on num characters actually written */ + buf->size = out - buf->ptr; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +int git_str_join( + git_str *buf, + char separator, + const char *str_a, + const char *str_b) +{ + size_t strlen_a = str_a ? strlen(str_a) : 0; + size_t strlen_b = strlen(str_b); + size_t alloc_len; + int need_sep = 0; + ssize_t offset_a = -1; + + /* not safe to have str_b point internally to the buffer */ + if (buf->size) + GIT_ASSERT_ARG(str_b < buf->ptr || str_b >= buf->ptr + buf->size); + + /* figure out if we need to insert a separator */ + if (separator && strlen_a) { + while (*str_b == separator) { str_b++; strlen_b--; } + if (str_a[strlen_a - 1] != separator) + need_sep = 1; + } + + /* str_a could be part of the buffer */ + if (buf->size && str_a >= buf->ptr && str_a < buf->ptr + buf->size) + offset_a = str_a - buf->ptr; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); + ENSURE_SIZE(buf, alloc_len); + + /* fix up internal pointers */ + if (offset_a >= 0) + str_a = buf->ptr + offset_a; + + /* do the actual copying */ + if (offset_a != 0 && str_a) + memmove(buf->ptr, str_a, strlen_a); + if (need_sep) + buf->ptr[strlen_a] = separator; + memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b); + + buf->size = strlen_a + strlen_b + need_sep; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +int git_str_join3( + git_str *buf, + char separator, + const char *str_a, + const char *str_b, + const char *str_c) +{ + size_t len_a = strlen(str_a), + len_b = strlen(str_b), + len_c = strlen(str_c), + len_total; + int sep_a = 0, sep_b = 0; + char *tgt; + + /* for this function, disallow pointers into the existing buffer */ + GIT_ASSERT(str_a < buf->ptr || str_a >= buf->ptr + buf->size); + GIT_ASSERT(str_b < buf->ptr || str_b >= buf->ptr + buf->size); + GIT_ASSERT(str_c < buf->ptr || str_c >= buf->ptr + buf->size); + + if (separator) { + if (len_a > 0) { + while (*str_b == separator) { str_b++; len_b--; } + sep_a = (str_a[len_a - 1] != separator); + } + if (len_a > 0 || len_b > 0) + while (*str_c == separator) { str_c++; len_c--; } + if (len_b > 0) + sep_b = (str_b[len_b - 1] != separator); + } + + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_a, sep_a); + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_b); + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b); + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_c); + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, 1); + ENSURE_SIZE(buf, len_total); + + tgt = buf->ptr; + + if (len_a) { + memcpy(tgt, str_a, len_a); + tgt += len_a; + } + if (sep_a) + *tgt++ = separator; + if (len_b) { + memcpy(tgt, str_b, len_b); + tgt += len_b; + } + if (sep_b) + *tgt++ = separator; + if (len_c) + memcpy(tgt, str_c, len_c); + + buf->size = len_a + sep_a + len_b + sep_b + len_c; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +void git_str_rtrim(git_str *buf) +{ + while (buf->size > 0) { + if (!git__isspace(buf->ptr[buf->size - 1])) + break; + + buf->size--; + } + + if (buf->asize > buf->size) + buf->ptr[buf->size] = '\0'; +} + +int git_str_cmp(const git_str *a, const git_str *b) +{ + int result = memcmp(a->ptr, b->ptr, min(a->size, b->size)); + return (result != 0) ? result : + (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0; +} + +int git_str_splice( + git_str *buf, + size_t where, + size_t nb_to_remove, + const char *data, + size_t nb_to_insert) +{ + char *splice_loc; + size_t new_size, alloc_size; + + GIT_ASSERT(buf); + GIT_ASSERT(where <= buf->size); + GIT_ASSERT(nb_to_remove <= buf->size - where); + + splice_loc = buf->ptr + where; + + /* Ported from git.git + * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176 + */ + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (buf->size - nb_to_remove), nb_to_insert); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, new_size, 1); + ENSURE_SIZE(buf, alloc_size); + + memmove(splice_loc + nb_to_insert, + splice_loc + nb_to_remove, + buf->size - where - nb_to_remove); + + memcpy(splice_loc, data, nb_to_insert); + + buf->size = new_size; + buf->ptr[buf->size] = '\0'; + return 0; +} + +/* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */ +int git_str_quote(git_str *buf) +{ + const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' }; + git_str quoted = GIT_STR_INIT; + size_t i = 0; + bool quote = false; + int error = 0; + + /* walk to the first char that needs quoting */ + if (buf->size && buf->ptr[0] == '!') + quote = true; + + for (i = 0; !quote && i < buf->size; i++) { + if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' || + buf->ptr[i] < ' ' || buf->ptr[i] > '~') { + quote = true; + break; + } + } + + if (!quote) + goto done; + + git_str_putc("ed, '"'); + git_str_put("ed, buf->ptr, i); + + for (; i < buf->size; i++) { + /* whitespace - use the map above, which is ordered by ascii value */ + if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') { + git_str_putc("ed, '\\'); + git_str_putc("ed, whitespace[buf->ptr[i] - '\a']); + } + + /* double quote and backslash must be escaped */ + else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') { + git_str_putc("ed, '\\'); + git_str_putc("ed, buf->ptr[i]); + } + + /* escape anything unprintable as octal */ + else if (buf->ptr[i] != ' ' && + (buf->ptr[i] < '!' || buf->ptr[i] > '~')) { + git_str_printf("ed, "\\%03o", (unsigned char)buf->ptr[i]); + } + + /* yay, printable! */ + else { + git_str_putc("ed, buf->ptr[i]); + } + } + + git_str_putc("ed, '"'); + + if (git_str_oom("ed)) { + error = -1; + goto done; + } + + git_str_swap("ed, buf); + +done: + git_str_dispose("ed); + return error; +} + +/* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */ +int git_str_unquote(git_str *buf) +{ + size_t i, j; + char ch; + + git_str_rtrim(buf); + + if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"') + goto invalid; + + for (i = 0, j = 1; j < buf->size-1; i++, j++) { + ch = buf->ptr[j]; + + if (ch == '\\') { + if (j == buf->size-2) + goto invalid; + + ch = buf->ptr[++j]; + + switch (ch) { + /* \" or \\ simply copy the char in */ + case '"': case '\\': + break; + + /* add the appropriate escaped char */ + case 'a': ch = '\a'; break; + case 'b': ch = '\b'; break; + case 'f': ch = '\f'; break; + case 'n': ch = '\n'; break; + case 'r': ch = '\r'; break; + case 't': ch = '\t'; break; + case 'v': ch = '\v'; break; + + /* \xyz digits convert to the char*/ + case '0': case '1': case '2': case '3': + if (j == buf->size-3) { + git_error_set(GIT_ERROR_INVALID, + "truncated quoted character \\%c", ch); + return -1; + } + + if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' || + buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') { + git_error_set(GIT_ERROR_INVALID, + "truncated quoted character \\%c%c%c", + buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]); + return -1; + } + + ch = ((buf->ptr[j] - '0') << 6) | + ((buf->ptr[j+1] - '0') << 3) | + (buf->ptr[j+2] - '0'); + j += 2; + break; + + default: + git_error_set(GIT_ERROR_INVALID, "invalid quoted character \\%c", ch); + return -1; + } + } + + buf->ptr[i] = ch; + } + + buf->ptr[i] = '\0'; + buf->size = i; + + return 0; + +invalid: + git_error_set(GIT_ERROR_INVALID, "invalid quoted line"); + return -1; +} + +int git_str_puts_escaped( + git_str *buf, + const char *string, + const char *esc_chars, + const char *esc_with) +{ + const char *scan; + size_t total = 0, esc_len = strlen(esc_with), count, alloclen; + + if (!string) + return 0; + + for (scan = string; *scan; ) { + /* count run of non-escaped characters */ + count = strcspn(scan, esc_chars); + total += count; + scan += count; + /* count run of escaped characters */ + count = strspn(scan, esc_chars); + total += count * (esc_len + 1); + scan += count; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, total, 1); + if (git_str_grow_by(buf, alloclen) < 0) + return -1; + + for (scan = string; *scan; ) { + count = strcspn(scan, esc_chars); + + memmove(buf->ptr + buf->size, scan, count); + scan += count; + buf->size += count; + + for (count = strspn(scan, esc_chars); count > 0; --count) { + /* copy escape sequence */ + memmove(buf->ptr + buf->size, esc_with, esc_len); + buf->size += esc_len; + /* copy character to be escaped */ + buf->ptr[buf->size] = *scan; + buf->size++; + scan++; + } + } + + buf->ptr[buf->size] = '\0'; + + return 0; +} + +void git_str_unescape(git_str *buf) +{ + buf->size = git__unescape(buf->ptr); +} + +int git_str_crlf_to_lf(git_str *tgt, const git_str *src) +{ + const char *scan = src->ptr; + const char *scan_end = src->ptr + src->size; + const char *next = memchr(scan, '\r', src->size); + size_t new_size; + char *out; + + GIT_ASSERT(tgt != src); + + if (!next) + return git_str_set(tgt, src->ptr, src->size); + + /* reduce reallocs while in the loop */ + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, src->size, 1); + if (git_str_grow(tgt, new_size) < 0) + return -1; + + out = tgt->ptr; + tgt->size = 0; + + /* Find the next \r and copy whole chunk up to there to tgt */ + for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) { + if (next > scan) { + size_t copylen = (size_t)(next - scan); + memcpy(out, scan, copylen); + out += copylen; + } + + /* Do not drop \r unless it is followed by \n */ + if (next + 1 == scan_end || next[1] != '\n') + *out++ = '\r'; + } + + /* Copy remaining input into dest */ + if (scan < scan_end) { + size_t remaining = (size_t)(scan_end - scan); + memcpy(out, scan, remaining); + out += remaining; + } + + tgt->size = (size_t)(out - tgt->ptr); + tgt->ptr[tgt->size] = '\0'; + + return 0; +} + +int git_str_lf_to_crlf(git_str *tgt, const git_str *src) +{ + const char *start = src->ptr; + const char *end = start + src->size; + const char *scan = start; + const char *next = memchr(scan, '\n', src->size); + size_t alloclen; + + GIT_ASSERT(tgt != src); + + if (!next) + return git_str_set(tgt, src->ptr, src->size); + + /* attempt to reduce reallocs while in the loop */ + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, src->size, src->size >> 4); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + if (git_str_grow(tgt, alloclen) < 0) + return -1; + tgt->size = 0; + + for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) { + size_t copylen = next - scan; + + /* if we find mixed line endings, carry on */ + if (copylen && next[-1] == '\r') + copylen--; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, copylen, 3); + if (git_str_grow_by(tgt, alloclen) < 0) + return -1; + + if (copylen) { + memcpy(tgt->ptr + tgt->size, scan, copylen); + tgt->size += copylen; + } + + tgt->ptr[tgt->size++] = '\r'; + tgt->ptr[tgt->size++] = '\n'; + } + + tgt->ptr[tgt->size] = '\0'; + return git_str_put(tgt, scan, end - scan); +} + +int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count) +{ + size_t i; + const char *str, *pfx; + + git_str_clear(buf); + + if (!strings || !count) + return 0; + + /* initialize common prefix to first string */ + if (git_str_sets(buf, strings[0]) < 0) + return -1; + + /* go through the rest of the strings, truncating to shared prefix */ + for (i = 1; i < count; ++i) { + + for (str = strings[i], pfx = buf->ptr; + *str && *str == *pfx; + str++, pfx++) + /* scanning */; + + git_str_truncate(buf, pfx - buf->ptr); + + if (!buf->size) + break; + } + + return 0; +} + +int git_str_is_binary(const git_str *buf) +{ + const char *scan = buf->ptr, *end = buf->ptr + buf->size; + git_str_bom_t bom; + int printable = 0, nonprintable = 0; + + scan += git_str_detect_bom(&bom, buf); + + if (bom > GIT_STR_BOM_UTF8) + return 1; + + while (scan < end) { + unsigned char c = *scan++; + + /* Printable characters are those above SPACE (0x1F) excluding DEL, + * and including BS, ESC and FF. + */ + if ((c > 0x1F && c != 127) || c == '\b' || c == '\033' || c == '\014') + printable++; + else if (c == '\0') + return true; + else if (!git__isspace(c)) + nonprintable++; + } + + return ((printable >> 7) < nonprintable); +} + +int git_str_contains_nul(const git_str *buf) +{ + return (memchr(buf->ptr, '\0', buf->size) != NULL); +} + +int git_str_detect_bom(git_str_bom_t *bom, const git_str *buf) +{ + const char *ptr; + size_t len; + + *bom = GIT_STR_BOM_NONE; + /* need at least 2 bytes to look for any BOM */ + if (buf->size < 2) + return 0; + + ptr = buf->ptr; + len = buf->size; + + switch (*ptr++) { + case 0: + if (len >= 4 && ptr[0] == 0 && ptr[1] == '\xFE' && ptr[2] == '\xFF') { + *bom = GIT_STR_BOM_UTF32_BE; + return 4; + } + break; + case '\xEF': + if (len >= 3 && ptr[0] == '\xBB' && ptr[1] == '\xBF') { + *bom = GIT_STR_BOM_UTF8; + return 3; + } + break; + case '\xFE': + if (*ptr == '\xFF') { + *bom = GIT_STR_BOM_UTF16_BE; + return 2; + } + break; + case '\xFF': + if (*ptr != '\xFE') + break; + if (len >= 4 && ptr[1] == 0 && ptr[2] == 0) { + *bom = GIT_STR_BOM_UTF32_LE; + return 4; + } else { + *bom = GIT_STR_BOM_UTF16_LE; + return 2; + } + break; + default: + break; + } + + return 0; +} + +bool git_str_gather_text_stats( + git_str_text_stats *stats, const git_str *buf, bool skip_bom) +{ + const char *scan = buf->ptr, *end = buf->ptr + buf->size; + int skip; + + memset(stats, 0, sizeof(*stats)); + + /* BOM detection */ + skip = git_str_detect_bom(&stats->bom, buf); + if (skip_bom) + scan += skip; + + /* Ignore EOF character */ + if (buf->size > 0 && end[-1] == '\032') + end--; + + /* Counting loop */ + while (scan < end) { + unsigned char c = *scan++; + + if (c > 0x1F && c != 0x7F) + stats->printable++; + else switch (c) { + case '\0': + stats->nul++; + stats->nonprintable++; + break; + case '\n': + stats->lf++; + break; + case '\r': + stats->cr++; + if (scan < end && *scan == '\n') + stats->crlf++; + break; + case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/ + stats->printable++; + break; + default: + stats->nonprintable++; + break; + } + } + + /* Treat files with a bare CR as binary */ + return (stats->cr != stats->crlf || stats->nul > 0 || + ((stats->printable >> 7) < stats->nonprintable)); +} diff --git a/src/libgit2/str.h b/src/libgit2/str.h new file mode 100644 index 000000000..ef769ce2f --- /dev/null +++ b/src/libgit2/str.h @@ -0,0 +1,357 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_str_h__ +#define INCLUDE_str_h__ + +#include "common.h" + +struct git_str { + char *ptr; + size_t asize; + size_t size; +}; + +typedef enum { + GIT_STR_BOM_NONE = 0, + GIT_STR_BOM_UTF8 = 1, + GIT_STR_BOM_UTF16_LE = 2, + GIT_STR_BOM_UTF16_BE = 3, + GIT_STR_BOM_UTF32_LE = 4, + GIT_STR_BOM_UTF32_BE = 5 +} git_str_bom_t; + +typedef struct { + git_str_bom_t bom; /* BOM found at head of text */ + unsigned int nul, cr, lf, crlf; /* NUL, CR, LF and CRLF counts */ + unsigned int printable, nonprintable; /* These are just approximations! */ +} git_str_text_stats; + +extern char git_str__initstr[]; +extern char git_str__oom[]; + +/* Use to initialize string buffer structure when git_str is on stack */ +#define GIT_STR_INIT { git_str__initstr, 0, 0 } + +/** + * Static initializer for git_str from static string buffer + */ +#define GIT_STR_INIT_CONST(str, len) { (char *)(str), 0, (size_t)(len) } + +GIT_INLINE(bool) git_str_is_allocated(const git_str *str) +{ + return (str->ptr != NULL && str->asize > 0); +} + +/** + * Initialize a git_str structure. + * + * For the cases where GIT_STR_INIT cannot be used to do static + * initialization. + */ +extern int git_str_init(git_str *str, size_t initial_size); + +extern void git_str_dispose(git_str *str); + +/** + * Resize the string buffer allocation to make more space. + * + * This will attempt to grow the string buffer to accommodate the target + * size. The bstring buffer's `ptr` will be replaced with a newly + * allocated block of data. Be careful so that memory allocated by the + * caller is not lost. As a special variant, if you pass `target_size` as + * 0 and the memory is not allocated by libgit2, this will allocate a new + * buffer of size `size` and copy the external data into it. + * + * Currently, this will never shrink a buffer, only expand it. + * + * If the allocation fails, this will return an error and the buffer will be + * marked as invalid for future operations, invaliding the contents. + * + * @param str The buffer to be resized; may or may not be allocated yet + * @param target_size The desired available size + * @return 0 on success, -1 on allocation failure + */ +int git_str_grow(git_str *str, size_t target_size); + +/** + * Resize the buffer allocation to make more space. + * + * This will attempt to grow the string buffer to accommodate the + * additional size. It is similar to `git_str_grow`, but performs the + * new size calculation, checking for overflow. + * + * Like `git_str_grow`, if this is a user-supplied string buffer, + * this will allocate a new string uffer. + */ +extern int git_str_grow_by(git_str *str, size_t additional_size); + +/** + * Attempt to grow the buffer to hold at least `target_size` bytes. + * + * If the allocation fails, this will return an error. If `mark_oom` is + * true, this will mark the string buffer as invalid for future + * operations; if false, existing string buffer content will be preserved, + * but calling code must handle that string buffer was not expanded. If + * `preserve_external` is true, then any existing data pointed to be + * `ptr` even if `asize` is zero will be copied into the newly allocated + * string buffer. + */ +extern int git_str_try_grow( + git_str *str, size_t target_size, bool mark_oom); + +extern void git_str_swap(git_str *str_a, git_str *str_b); +extern char *git_str_detach(git_str *str); +extern int git_str_attach(git_str *str, char *ptr, size_t asize); + +/* Populates a `git_str` where the contents are not "owned" by the string + * buffer, and calls to `git_str_dispose` will not free the given str. + */ +extern void git_str_attach_notowned( + git_str *str, const char *ptr, size_t size); + +/** + * Test if there have been any reallocation failures with this git_str. + * + * Any function that writes to a git_str can fail due to memory allocation + * issues. If one fails, the git_str will be marked with an OOM error and + * further calls to modify the string buffer will fail. Check + * git_str_oom() at the end of your sequence and it will be true if you + * ran out of memory at any point with that string buffer. + * + * @return false if no error, true if allocation error + */ +GIT_INLINE(bool) git_str_oom(const git_str *str) +{ + return (str->ptr == git_str__oom); +} + +/* + * Functions below that return int value error codes will return 0 on + * success or -1 on failure (which generally means an allocation failed). + * Using a git_str where the allocation has failed with result in -1 from + * all further calls using that string buffer. As a result, you can + * ignore the return code of these functions and call them in a series + * then just call git_str_oom at the end. + */ + +int git_str_set(git_str *str, const void *data, size_t datalen); + +int git_str_sets(git_str *str, const char *string); +int git_str_putc(git_str *str, char c); +int git_str_putcn(git_str *str, char c, size_t len); +int git_str_put(git_str *str, const char *data, size_t len); +int git_str_puts(git_str *str, const char *string); +int git_str_printf(git_str *str, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); +int git_str_vprintf(git_str *str, const char *format, va_list ap); +void git_str_clear(git_str *str); +void git_str_consume_bytes(git_str *str, size_t len); +void git_str_consume(git_str *str, const char *end); +void git_str_truncate(git_str *str, size_t len); +void git_str_shorten(git_str *str, size_t amount); +void git_str_truncate_at_char(git_str *path, char separator); +void git_str_rtruncate_at_char(git_str *path, char separator); + +/** General join with separator */ +int git_str_join_n(git_str *str, char separator, int len, ...); +/** Fast join of two strings - first may legally point into `str` data */ +int git_str_join(git_str *str, char separator, const char *str_a, const char *str_b); +/** Fast join of three strings - cannot reference `str` data */ +int git_str_join3(git_str *str, char separator, const char *str_a, const char *str_b, const char *str_c); + +/** + * Join two strings as paths, inserting a slash between as needed. + * @return 0 on success, -1 on failure + */ +GIT_INLINE(int) git_str_joinpath(git_str *str, const char *a, const char *b) +{ + return git_str_join(str, '/', a, b); +} + +GIT_INLINE(const char *) git_str_cstr(const git_str *str) +{ + return str->ptr; +} + +GIT_INLINE(size_t) git_str_len(const git_str *str) +{ + return str->size; +} + +int git_str_copy_cstr(char *data, size_t datasize, const git_str *str); + +#define git_str_PUTS(str, cstr) git_str_put(str, cstr, sizeof(cstr) - 1) + +GIT_INLINE(ssize_t) git_str_rfind_next(const git_str *str, char ch) +{ + ssize_t idx = (ssize_t)str->size - 1; + while (idx >= 0 && str->ptr[idx] == ch) idx--; + while (idx >= 0 && str->ptr[idx] != ch) idx--; + return idx; +} + +GIT_INLINE(ssize_t) git_str_rfind(const git_str *str, char ch) +{ + ssize_t idx = (ssize_t)str->size - 1; + while (idx >= 0 && str->ptr[idx] != ch) idx--; + return idx; +} + +GIT_INLINE(ssize_t) git_str_find(const git_str *str, char ch) +{ + void *found = memchr(str->ptr, ch, str->size); + return found ? (ssize_t)((const char *)found - str->ptr) : -1; +} + +/* Remove whitespace from the end of the string buffer */ +void git_str_rtrim(git_str *str); + +int git_str_cmp(const git_str *a, const git_str *b); + +/* Quote and unquote a string buffer as specified in + * http://marc.info/?l=git&m=112927316408690&w=2 + */ +int git_str_quote(git_str *str); +int git_str_unquote(git_str *str); + +/* Write data as a hex string */ +int git_str_encode_hexstr(git_str *str, const char *data, size_t len); + +/* Write data as base64 encoded in string buffer */ +int git_str_encode_base64(git_str *str, const char *data, size_t len); +/* Decode the given bas64 and write the result to the string buffer */ +int git_str_decode_base64(git_str *str, const char *base64, size_t len); + +/* Write data as "base85" encoded in string buffer */ +int git_str_encode_base85(git_str *str, const char *data, size_t len); +/* Decode the given "base85" and write the result to the string buffer */ +int git_str_decode_base85(git_str *str, const char *base64, size_t len, size_t output_len); + +/* + * Decode the given percent-encoded string and write the result to the + * string buffer. + */ +int git_str_decode_percent(git_str *str, const char *encoded, size_t len); + +/* + * Insert, remove or replace a portion of the string buffer. + * + * @param str The string buffer to work with + * + * @param where The location in the string buffer where the transformation + * should be applied. + * + * @param nb_to_remove The number of chars to be removed. 0 to not + * remove any character in the string buffer. + * + * @param data A pointer to the data which should be inserted. + * + * @param nb_to_insert The number of chars to be inserted. 0 to not + * insert any character from the string buffer. + * + * @return 0 or an error code. + */ +int git_str_splice( + git_str *str, + size_t where, + size_t nb_to_remove, + const char *data, + size_t nb_to_insert); + +/** + * Append string to string buffer, prefixing each character from + * `esc_chars` with `esc_with` string. + * + * @param str String buffer to append data to + * @param string String to escape and append + * @param esc_chars Characters to be escaped + * @param esc_with String to insert in from of each found character + * @return 0 on success, <0 on failure (probably allocation problem) + */ +extern int git_str_puts_escaped( + git_str *str, + const char *string, + const char *esc_chars, + const char *esc_with); + +/** + * Append string escaping characters that are regex special + */ +GIT_INLINE(int) git_str_puts_escape_regex(git_str *str, const char *string) +{ + return git_str_puts_escaped(str, string, "^.[]$()|*+?{}\\", "\\"); +} + +/** + * Unescape all characters in a string buffer in place + * + * I.e. remove backslashes + */ +extern void git_str_unescape(git_str *str); + +/** + * Replace all \r\n with \n. + * + * @return 0 on success, -1 on memory error + */ +extern int git_str_crlf_to_lf(git_str *tgt, const git_str *src); + +/** + * Replace all \n with \r\n. Does not modify existing \r\n. + * + * @return 0 on success, -1 on memory error + */ +extern int git_str_lf_to_crlf(git_str *tgt, const git_str *src); + +/** + * Fill string buffer with the common prefix of a array of strings + * + * String buffer will be set to empty if there is no common prefix + */ +extern int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count); + +/** + * Check if a string buffer begins with a UTF BOM + * + * @param bom Set to the type of BOM detected or GIT_BOM_NONE + * @param str String buffer in which to check the first bytes for a BOM + * @return Number of bytes of BOM data (or 0 if no BOM found) + */ +extern int git_str_detect_bom(git_str_bom_t *bom, const git_str *str); + +/** + * Gather stats for a piece of text + * + * Fill the `stats` structure with counts of unreadable characters, carriage + * returns, etc, so it can be used in heuristics. This automatically skips + * a trailing EOF (\032 character). Also it will look for a BOM at the + * start of the text and can be told to skip that as well. + * + * @param stats Structure to be filled in + * @param str Text to process + * @param skip_bom Exclude leading BOM from stats if true + * @return Does the string buffer heuristically look like binary data + */ +extern bool git_str_gather_text_stats( + git_str_text_stats *stats, const git_str *str, bool skip_bom); + +/** +* Check quickly if string buffer looks like it contains binary data +* +* @param str string buffer to check +* @return 1 if string buffer looks like non-text data +*/ +int git_str_is_binary(const git_str *str); + +/** +* Check quickly if buffer contains a NUL byte +* +* @param str string buffer to check +* @return 1 if string buffer contains a NUL byte +*/ +int git_str_contains_nul(const git_str *str); + +#endif diff --git a/src/libgit2/strarray.c b/src/libgit2/strarray.c new file mode 100644 index 000000000..2f9b77cc2 --- /dev/null +++ b/src/libgit2/strarray.c @@ -0,0 +1,64 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "util.h" + +#include "common.h" + +int git_strarray_copy(git_strarray *tgt, const git_strarray *src) +{ + size_t i; + + GIT_ASSERT_ARG(tgt); + GIT_ASSERT_ARG(src); + + memset(tgt, 0, sizeof(*tgt)); + + if (!src->count) + return 0; + + tgt->strings = git__calloc(src->count, sizeof(char *)); + GIT_ERROR_CHECK_ALLOC(tgt->strings); + + for (i = 0; i < src->count; ++i) { + if (!src->strings[i]) + continue; + + tgt->strings[tgt->count] = git__strdup(src->strings[i]); + if (!tgt->strings[tgt->count]) { + git_strarray_dispose(tgt); + memset(tgt, 0, sizeof(*tgt)); + return -1; + } + + tgt->count++; + } + + return 0; +} + +void git_strarray_dispose(git_strarray *array) +{ + size_t i; + + if (array == NULL) + return; + + for (i = 0; i < array->count; ++i) + git__free(array->strings[i]); + + git__free(array->strings); + + memset(array, 0, sizeof(*array)); +} + +#ifndef GIT_DEPRECATE_HARD +void git_strarray_free(git_strarray *array) +{ + git_strarray_dispose(array); +} +#endif diff --git a/src/libgit2/stream.h b/src/libgit2/stream.h new file mode 100644 index 000000000..f16b026fb --- /dev/null +++ b/src/libgit2/stream.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_stream_h__ +#define INCLUDE_stream_h__ + +#include "common.h" +#include "git2/sys/stream.h" + +GIT_INLINE(int) git_stream_connect(git_stream *st) +{ + return st->connect(st); +} + +GIT_INLINE(int) git_stream_is_encrypted(git_stream *st) +{ + return st->encrypted; +} + +GIT_INLINE(int) git_stream_certificate(git_cert **out, git_stream *st) +{ + if (!st->encrypted) { + git_error_set(GIT_ERROR_INVALID, "an unencrypted stream does not have a certificate"); + return -1; + } + + return st->certificate(out, st); +} + +GIT_INLINE(int) git_stream_supports_proxy(git_stream *st) +{ + return st->proxy_support; +} + +GIT_INLINE(int) git_stream_set_proxy(git_stream *st, const git_proxy_options *proxy_opts) +{ + if (!st->proxy_support) { + git_error_set(GIT_ERROR_INVALID, "proxy not supported on this stream"); + return -1; + } + + return st->set_proxy(st, proxy_opts); +} + +GIT_INLINE(ssize_t) git_stream_read(git_stream *st, void *data, size_t len) +{ + return st->read(st, data, len); +} + +GIT_INLINE(ssize_t) git_stream_write(git_stream *st, const char *data, size_t len, int flags) +{ + return st->write(st, data, len, flags); +} + +GIT_INLINE(int) git_stream__write_full(git_stream *st, const char *data, size_t len, int flags) +{ + size_t total_written = 0; + + while (total_written < len) { + ssize_t written = git_stream_write(st, data + total_written, len - total_written, flags); + if (written <= 0) + return -1; + + total_written += written; + } + + return 0; +} + +GIT_INLINE(int) git_stream_close(git_stream *st) +{ + return st->close(st); +} + +GIT_INLINE(void) git_stream_free(git_stream *st) +{ + if (!st) + return; + + st->free(st); +} + +#endif diff --git a/src/libgit2/streams/mbedtls.c b/src/libgit2/streams/mbedtls.c new file mode 100644 index 000000000..0cf5c8af1 --- /dev/null +++ b/src/libgit2/streams/mbedtls.c @@ -0,0 +1,482 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "streams/mbedtls.h" + +#ifdef GIT_MBEDTLS + +#include + +#include "runtime.h" +#include "stream.h" +#include "streams/socket.h" +#include "netops.h" +#include "git2/transport.h" +#include "util.h" + +#ifndef GIT_DEFAULT_CERT_LOCATION +#define GIT_DEFAULT_CERT_LOCATION NULL +#endif + +/* Work around C90-conformance issues */ +#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) +# if defined(_MSC_VER) +# define inline __inline +# elif defined(__GNUC__) +# define inline __inline__ +# else +# define inline +# endif +#endif + +#include +#include +#include +#include +#include + +#undef inline + +#define GIT_SSL_DEFAULT_CIPHERS "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-DSS-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-DSS-WITH-AES-256-GCM-SHA384:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-128-CBC-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-128-CBC-SHA256:TLS-DHE-DSS-WITH-AES-256-CBC-SHA256:TLS-DHE-DSS-WITH-AES-128-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-128-GCM-SHA256:TLS-RSA-WITH-AES-256-GCM-SHA384:TLS-RSA-WITH-AES-128-CBC-SHA256:TLS-RSA-WITH-AES-256-CBC-SHA256:TLS-RSA-WITH-AES-128-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA" +#define GIT_SSL_DEFAULT_CIPHERS_COUNT 30 + +static mbedtls_ssl_config *git__ssl_conf; +static int ciphers_list[GIT_SSL_DEFAULT_CIPHERS_COUNT]; +static mbedtls_entropy_context *mbedtls_entropy; + +/** + * This function aims to clean-up the SSL context which + * we allocated. + */ +static void shutdown_ssl(void) +{ + if (git__ssl_conf) { + mbedtls_x509_crt_free(git__ssl_conf->ca_chain); + git__free(git__ssl_conf->ca_chain); + mbedtls_ctr_drbg_free(git__ssl_conf->p_rng); + git__free(git__ssl_conf->p_rng); + mbedtls_ssl_config_free(git__ssl_conf); + git__free(git__ssl_conf); + git__ssl_conf = NULL; + } + if (mbedtls_entropy) { + mbedtls_entropy_free(mbedtls_entropy); + git__free(mbedtls_entropy); + mbedtls_entropy = NULL; + } +} + +int git_mbedtls_stream_global_init(void) +{ + int loaded = 0; + char *crtpath = GIT_DEFAULT_CERT_LOCATION; + struct stat statbuf; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + + size_t ciphers_known = 0; + char *cipher_name = NULL; + char *cipher_string = NULL; + char *cipher_string_tmp = NULL; + + git__ssl_conf = git__malloc(sizeof(mbedtls_ssl_config)); + GIT_ERROR_CHECK_ALLOC(git__ssl_conf); + + mbedtls_ssl_config_init(git__ssl_conf); + if (mbedtls_ssl_config_defaults(git__ssl_conf, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT) != 0) { + git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS"); + goto cleanup; + } + + /* configure TLSv1 */ + mbedtls_ssl_conf_min_version(git__ssl_conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0); + + /* verify_server_cert is responsible for making the check. + * OPTIONAL because REQUIRED drops the certificate as soon as the check + * is made, so we can never see the certificate and override it. */ + mbedtls_ssl_conf_authmode(git__ssl_conf, MBEDTLS_SSL_VERIFY_OPTIONAL); + + /* set the list of allowed ciphersuites */ + ciphers_known = 0; + cipher_string = cipher_string_tmp = git__strdup(GIT_SSL_DEFAULT_CIPHERS); + GIT_ERROR_CHECK_ALLOC(cipher_string); + + while ((cipher_name = git__strtok(&cipher_string_tmp, ":")) != NULL) { + int cipherid = mbedtls_ssl_get_ciphersuite_id(cipher_name); + if (cipherid == 0) continue; + + if (ciphers_known >= ARRAY_SIZE(ciphers_list)) { + git_error_set(GIT_ERROR_SSL, "out of cipher list space"); + goto cleanup; + } + + ciphers_list[ciphers_known++] = cipherid; + } + git__free(cipher_string); + + if (!ciphers_known) { + git_error_set(GIT_ERROR_SSL, "no cipher could be enabled"); + goto cleanup; + } + mbedtls_ssl_conf_ciphersuites(git__ssl_conf, ciphers_list); + + /* Seeding the random number generator */ + mbedtls_entropy = git__malloc(sizeof(mbedtls_entropy_context)); + GIT_ERROR_CHECK_ALLOC(mbedtls_entropy); + + mbedtls_entropy_init(mbedtls_entropy); + + ctr_drbg = git__malloc(sizeof(mbedtls_ctr_drbg_context)); + GIT_ERROR_CHECK_ALLOC(ctr_drbg); + + mbedtls_ctr_drbg_init(ctr_drbg); + + if (mbedtls_ctr_drbg_seed(ctr_drbg, + mbedtls_entropy_func, + mbedtls_entropy, NULL, 0) != 0) { + git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS entropy pool"); + goto cleanup; + } + + mbedtls_ssl_conf_rng(git__ssl_conf, mbedtls_ctr_drbg_random, ctr_drbg); + + /* load default certificates */ + if (crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) + loaded = (git_mbedtls__set_cert_location(crtpath, NULL) == 0); + if (!loaded && crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) + loaded = (git_mbedtls__set_cert_location(NULL, crtpath) == 0); + + return git_runtime_shutdown_register(shutdown_ssl); + +cleanup: + mbedtls_ctr_drbg_free(ctr_drbg); + git__free(ctr_drbg); + mbedtls_ssl_config_free(git__ssl_conf); + git__free(git__ssl_conf); + git__ssl_conf = NULL; + + return -1; +} + +static int bio_read(void *b, unsigned char *buf, size_t len) +{ + git_stream *io = (git_stream *) b; + return (int) git_stream_read(io, buf, min(len, INT_MAX)); +} + +static int bio_write(void *b, const unsigned char *buf, size_t len) +{ + git_stream *io = (git_stream *) b; + return (int) git_stream_write(io, (const char *)buf, min(len, INT_MAX), 0); +} + +static int ssl_set_error(mbedtls_ssl_context *ssl, int error) +{ + char errbuf[512]; + int ret = -1; + + GIT_ASSERT(error != MBEDTLS_ERR_SSL_WANT_READ); + GIT_ASSERT(error != MBEDTLS_ERR_SSL_WANT_WRITE); + + if (error != 0) + mbedtls_strerror( error, errbuf, 512 ); + + switch(error) { + case 0: + git_error_set(GIT_ERROR_SSL, "SSL error: unknown error"); + break; + + case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: + git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, ssl->session_negotiate->verify_result, errbuf); + ret = GIT_ECERTIFICATE; + break; + + default: + git_error_set(GIT_ERROR_SSL, "SSL error: %#04x - %s", error, errbuf); + } + + return ret; +} + +static int ssl_teardown(mbedtls_ssl_context *ssl) +{ + int ret = 0; + + ret = mbedtls_ssl_close_notify(ssl); + if (ret < 0) + ret = ssl_set_error(ssl, ret); + + mbedtls_ssl_free(ssl); + return ret; +} + +static int verify_server_cert(mbedtls_ssl_context *ssl) +{ + int ret = -1; + + if ((ret = mbedtls_ssl_get_verify_result(ssl)) != 0) { + char vrfy_buf[512]; + int len = mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), "", ret); + if (len >= 1) vrfy_buf[len - 1] = '\0'; /* Remove trailing \n */ + git_error_set(GIT_ERROR_SSL, "the SSL certificate is invalid: %#04x - %s", ret, vrfy_buf); + return GIT_ECERTIFICATE; + } + + return 0; +} + +typedef struct { + git_stream parent; + git_stream *io; + int owned; + bool connected; + char *host; + mbedtls_ssl_context *ssl; + git_cert_x509 cert_info; +} mbedtls_stream; + + +static int mbedtls_connect(git_stream *stream) +{ + int ret; + mbedtls_stream *st = (mbedtls_stream *) stream; + + if (st->owned && (ret = git_stream_connect(st->io)) < 0) + return ret; + + st->connected = true; + + mbedtls_ssl_set_hostname(st->ssl, st->host); + + mbedtls_ssl_set_bio(st->ssl, st->io, bio_write, bio_read, NULL); + + if ((ret = mbedtls_ssl_handshake(st->ssl)) != 0) + return ssl_set_error(st->ssl, ret); + + return verify_server_cert(st->ssl); +} + +static int mbedtls_certificate(git_cert **out, git_stream *stream) +{ + unsigned char *encoded_cert; + mbedtls_stream *st = (mbedtls_stream *) stream; + + const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(st->ssl); + if (!cert) { + git_error_set(GIT_ERROR_SSL, "the server did not provide a certificate"); + return -1; + } + + /* Retrieve the length of the certificate first */ + if (cert->raw.len == 0) { + git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); + return -1; + } + + encoded_cert = git__malloc(cert->raw.len); + GIT_ERROR_CHECK_ALLOC(encoded_cert); + memcpy(encoded_cert, cert->raw.p, cert->raw.len); + + st->cert_info.parent.cert_type = GIT_CERT_X509; + st->cert_info.data = encoded_cert; + st->cert_info.len = cert->raw.len; + + *out = &st->cert_info.parent; + + return 0; +} + +static int mbedtls_set_proxy(git_stream *stream, const git_proxy_options *proxy_options) +{ + mbedtls_stream *st = (mbedtls_stream *) stream; + + return git_stream_set_proxy(st->io, proxy_options); +} + +static ssize_t mbedtls_stream_write(git_stream *stream, const char *data, size_t len, int flags) +{ + mbedtls_stream *st = (mbedtls_stream *) stream; + int written; + + GIT_UNUSED(flags); + + /* + * `mbedtls_ssl_write` can only represent INT_MAX bytes + * written via its return value. We thus need to clamp + * the maximum number of bytes written. + */ + len = min(len, INT_MAX); + + if ((written = mbedtls_ssl_write(st->ssl, (const unsigned char *)data, len)) <= 0) + return ssl_set_error(st->ssl, written); + + return written; +} + +static ssize_t mbedtls_stream_read(git_stream *stream, void *data, size_t len) +{ + mbedtls_stream *st = (mbedtls_stream *) stream; + int ret; + + if ((ret = mbedtls_ssl_read(st->ssl, (unsigned char *)data, len)) <= 0) + ssl_set_error(st->ssl, ret); + + return ret; +} + +static int mbedtls_stream_close(git_stream *stream) +{ + mbedtls_stream *st = (mbedtls_stream *) stream; + int ret = 0; + + if (st->connected && (ret = ssl_teardown(st->ssl)) != 0) + return -1; + + st->connected = false; + + return st->owned ? git_stream_close(st->io) : 0; +} + +static void mbedtls_stream_free(git_stream *stream) +{ + mbedtls_stream *st = (mbedtls_stream *) stream; + + if (st->owned) + git_stream_free(st->io); + + git__free(st->host); + git__free(st->cert_info.data); + mbedtls_ssl_free(st->ssl); + git__free(st->ssl); + git__free(st); +} + +static int mbedtls_stream_wrap( + git_stream **out, + git_stream *in, + const char *host, + int owned) +{ + mbedtls_stream *st; + int error; + + st = git__calloc(1, sizeof(mbedtls_stream)); + GIT_ERROR_CHECK_ALLOC(st); + + st->io = in; + st->owned = owned; + + st->ssl = git__malloc(sizeof(mbedtls_ssl_context)); + GIT_ERROR_CHECK_ALLOC(st->ssl); + mbedtls_ssl_init(st->ssl); + if (mbedtls_ssl_setup(st->ssl, git__ssl_conf)) { + git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); + error = -1; + goto out_err; + } + + st->host = git__strdup(host); + GIT_ERROR_CHECK_ALLOC(st->host); + + st->parent.version = GIT_STREAM_VERSION; + st->parent.encrypted = 1; + st->parent.proxy_support = git_stream_supports_proxy(st->io); + st->parent.connect = mbedtls_connect; + st->parent.certificate = mbedtls_certificate; + st->parent.set_proxy = mbedtls_set_proxy; + st->parent.read = mbedtls_stream_read; + st->parent.write = mbedtls_stream_write; + st->parent.close = mbedtls_stream_close; + st->parent.free = mbedtls_stream_free; + + *out = (git_stream *) st; + return 0; + +out_err: + mbedtls_ssl_free(st->ssl); + git_stream_close(st->io); + git_stream_free(st->io); + git__free(st); + + return error; +} + +int git_mbedtls_stream_wrap( + git_stream **out, + git_stream *in, + const char *host) +{ + return mbedtls_stream_wrap(out, in, host, 0); +} + +int git_mbedtls_stream_new( + git_stream **out, + const char *host, + const char *port) +{ + git_stream *stream; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(host); + GIT_ASSERT_ARG(port); + + if ((error = git_socket_stream_new(&stream, host, port)) < 0) + return error; + + if ((error = mbedtls_stream_wrap(out, stream, host, 1)) < 0) { + git_stream_close(stream); + git_stream_free(stream); + } + + return error; +} + +int git_mbedtls__set_cert_location(const char *file, const char *path) +{ + int ret = 0; + char errbuf[512]; + mbedtls_x509_crt *cacert; + + GIT_ASSERT_ARG(file || path); + + cacert = git__malloc(sizeof(mbedtls_x509_crt)); + GIT_ERROR_CHECK_ALLOC(cacert); + + mbedtls_x509_crt_init(cacert); + if (file) + ret = mbedtls_x509_crt_parse_file(cacert, file); + if (ret >= 0 && path) + ret = mbedtls_x509_crt_parse_path(cacert, path); + /* mbedtls_x509_crt_parse_path returns the number of invalid certs on success */ + if (ret < 0) { + mbedtls_x509_crt_free(cacert); + git__free(cacert); + mbedtls_strerror( ret, errbuf, 512 ); + git_error_set(GIT_ERROR_SSL, "failed to load CA certificates: %#04x - %s", ret, errbuf); + return -1; + } + + mbedtls_x509_crt_free(git__ssl_conf->ca_chain); + git__free(git__ssl_conf->ca_chain); + mbedtls_ssl_conf_ca_chain(git__ssl_conf, cacert, NULL); + + return 0; +} + +#else + +#include "stream.h" + +int git_mbedtls_stream_global_init(void) +{ + return 0; +} + +#endif diff --git a/src/libgit2/streams/mbedtls.h b/src/libgit2/streams/mbedtls.h new file mode 100644 index 000000000..bcca6dd40 --- /dev/null +++ b/src/libgit2/streams/mbedtls.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_steams_mbedtls_h__ +#define INCLUDE_steams_mbedtls_h__ + +#include "common.h" + +#include "git2/sys/stream.h" + +extern int git_mbedtls_stream_global_init(void); + +#ifdef GIT_MBEDTLS +extern int git_mbedtls__set_cert_location(const char *file, const char *path); + +extern int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port); +extern int git_mbedtls_stream_wrap(git_stream **out, git_stream *in, const char *host); +#endif + +#endif diff --git a/src/libgit2/streams/openssl.c b/src/libgit2/streams/openssl.c new file mode 100644 index 000000000..89c96780c --- /dev/null +++ b/src/libgit2/streams/openssl.c @@ -0,0 +1,747 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "streams/openssl.h" +#include "streams/openssl_legacy.h" +#include "streams/openssl_dynamic.h" + +#ifdef GIT_OPENSSL + +#include + +#include "common.h" +#include "runtime.h" +#include "settings.h" +#include "posix.h" +#include "stream.h" +#include "streams/socket.h" +#include "netops.h" +#include "git2/transport.h" +#include "git2/sys/openssl.h" + +#ifndef GIT_WIN32 +# include +# include +# include +#endif + +#ifndef GIT_OPENSSL_DYNAMIC +# include +# include +# include +# include +#endif + +SSL_CTX *git__ssl_ctx; + +#define GIT_SSL_DEFAULT_CIPHERS "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA" + + +static BIO_METHOD *git_stream_bio_method; +static int init_bio_method(void); + +/** + * This function aims to clean-up the SSL context which + * we allocated. + */ +static void shutdown_ssl(void) +{ + if (git_stream_bio_method) { + BIO_meth_free(git_stream_bio_method); + git_stream_bio_method = NULL; + } + + if (git__ssl_ctx) { + SSL_CTX_free(git__ssl_ctx); + git__ssl_ctx = NULL; + } +} + +#ifdef VALGRIND +# if !defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC) + +static void *git_openssl_malloc(size_t bytes, const char *file, int line) +{ + GIT_UNUSED(file); + GIT_UNUSED(line); + return git__calloc(1, bytes); +} + +static void *git_openssl_realloc(void *mem, size_t size, const char *file, int line) +{ + GIT_UNUSED(file); + GIT_UNUSED(line); + return git__realloc(mem, size); +} + +static void git_openssl_free(void *mem, const char *file, int line) +{ + GIT_UNUSED(file); + GIT_UNUSED(line); + git__free(mem); +} +# else /* !GIT_OPENSSL_LEGACY && !GIT_OPENSSL_DYNAMIC */ +static void *git_openssl_malloc(size_t bytes) +{ + return git__calloc(1, bytes); +} + +static void *git_openssl_realloc(void *mem, size_t size) +{ + return git__realloc(mem, size); +} + +static void git_openssl_free(void *mem) +{ + git__free(mem); +} +# endif /* !GIT_OPENSSL_LEGACY && !GIT_OPENSSL_DYNAMIC */ +#endif /* VALGRIND */ + +static int openssl_init(void) +{ + long ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; + const char *ciphers = git_libgit2__ssl_ciphers(); +#ifdef VALGRIND + static bool allocators_initialized = false; +#endif + + /* Older OpenSSL and MacOS OpenSSL doesn't have this */ +#ifdef SSL_OP_NO_COMPRESSION + ssl_opts |= SSL_OP_NO_COMPRESSION; +#endif + +#ifdef VALGRIND + /* + * Swap in our own allocator functions that initialize + * allocated memory to avoid spurious valgrind warnings. + * Don't error on failure; many builds of OpenSSL do not + * allow you to set these functions. + */ + if (!allocators_initialized) { + CRYPTO_set_mem_functions(git_openssl_malloc, + git_openssl_realloc, + git_openssl_free); + allocators_initialized = true; + } +#endif + + OPENSSL_init_ssl(0, NULL); + + /* + * Load SSLv{2,3} and TLSv1 so that we can talk with servers + * which use the SSL hellos, which are often used for + * compatibility. We then disable SSL so we only allow OpenSSL + * to speak TLSv1 to perform the encryption itself. + */ + if (!(git__ssl_ctx = SSL_CTX_new(SSLv23_method()))) + goto error; + + SSL_CTX_set_options(git__ssl_ctx, ssl_opts); + SSL_CTX_set_mode(git__ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify(git__ssl_ctx, SSL_VERIFY_NONE, NULL); + if (!SSL_CTX_set_default_verify_paths(git__ssl_ctx)) + goto error; + + if (!ciphers) + ciphers = GIT_SSL_DEFAULT_CIPHERS; + + if(!SSL_CTX_set_cipher_list(git__ssl_ctx, ciphers)) + goto error; + + if (init_bio_method() < 0) + goto error; + + return git_runtime_shutdown_register(shutdown_ssl); + +error: + git_error_set(GIT_ERROR_NET, "could not initialize openssl: %s", + ERR_error_string(ERR_get_error(), NULL)); + SSL_CTX_free(git__ssl_ctx); + git__ssl_ctx = NULL; + return -1; +} + +/* + * When we use dynamic loading, we defer OpenSSL initialization until + * it's first used. `openssl_ensure_initialized` will do the work + * under a mutex. + */ +git_mutex openssl_mutex; +bool openssl_initialized; + +int git_openssl_stream_global_init(void) +{ +#ifndef GIT_OPENSSL_DYNAMIC + return openssl_init(); +#else + if (git_mutex_init(&openssl_mutex) != 0) + return -1; + + return 0; +#endif +} + +static int openssl_ensure_initialized(void) +{ +#ifdef GIT_OPENSSL_DYNAMIC + int error = 0; + + if (git_mutex_lock(&openssl_mutex) != 0) + return -1; + + if (!openssl_initialized) { + if ((error = git_openssl_stream_dynamic_init()) == 0) + error = openssl_init(); + + openssl_initialized = true; + } + + error |= git_mutex_unlock(&openssl_mutex); + return error; + +#else + return 0; +#endif +} + +#if !defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC) +int git_openssl_set_locking(void) +{ +# ifdef GIT_THREADS + return 0; +# else + git_error_set(GIT_ERROR_THREAD, "libgit2 was not built with threads"); + return -1; +# endif +} +#endif + + +static int bio_create(BIO *b) +{ + BIO_set_init(b, 1); + BIO_set_data(b, NULL); + + return 1; +} + +static int bio_destroy(BIO *b) +{ + if (!b) + return 0; + + BIO_set_data(b, NULL); + + return 1; +} + +static int bio_read(BIO *b, char *buf, int len) +{ + git_stream *io = (git_stream *) BIO_get_data(b); + + return (int) git_stream_read(io, buf, len); +} + +static int bio_write(BIO *b, const char *buf, int len) +{ + git_stream *io = (git_stream *) BIO_get_data(b); + return (int) git_stream_write(io, buf, len, 0); +} + +static long bio_ctrl(BIO *b, int cmd, long num, void *ptr) +{ + GIT_UNUSED(b); + GIT_UNUSED(num); + GIT_UNUSED(ptr); + + if (cmd == BIO_CTRL_FLUSH) + return 1; + + return 0; +} + +static int bio_gets(BIO *b, char *buf, int len) +{ + GIT_UNUSED(b); + GIT_UNUSED(buf); + GIT_UNUSED(len); + return -1; +} + +static int bio_puts(BIO *b, const char *str) +{ + return bio_write(b, str, strlen(str)); +} + +static int init_bio_method(void) +{ + /* Set up the BIO_METHOD we use for wrapping our own stream implementations */ + git_stream_bio_method = BIO_meth_new(BIO_TYPE_SOURCE_SINK | BIO_get_new_index(), "git_stream"); + GIT_ERROR_CHECK_ALLOC(git_stream_bio_method); + + BIO_meth_set_write(git_stream_bio_method, bio_write); + BIO_meth_set_read(git_stream_bio_method, bio_read); + BIO_meth_set_puts(git_stream_bio_method, bio_puts); + BIO_meth_set_gets(git_stream_bio_method, bio_gets); + BIO_meth_set_ctrl(git_stream_bio_method, bio_ctrl); + BIO_meth_set_create(git_stream_bio_method, bio_create); + BIO_meth_set_destroy(git_stream_bio_method, bio_destroy); + + return 0; +} + +static int ssl_set_error(SSL *ssl, int error) +{ + int err; + unsigned long e; + + err = SSL_get_error(ssl, error); + + GIT_ASSERT(err != SSL_ERROR_WANT_READ); + GIT_ASSERT(err != SSL_ERROR_WANT_WRITE); + + switch (err) { + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + git_error_set(GIT_ERROR_SSL, "SSL error: connection failure"); + break; + case SSL_ERROR_WANT_X509_LOOKUP: + git_error_set(GIT_ERROR_SSL, "SSL error: x509 error"); + break; + case SSL_ERROR_SYSCALL: + e = ERR_get_error(); + if (e > 0) { + char errmsg[256]; + ERR_error_string_n(e, errmsg, sizeof(errmsg)); + git_error_set(GIT_ERROR_NET, "SSL error: %s", errmsg); + break; + } else if (error < 0) { + git_error_set(GIT_ERROR_OS, "SSL error: syscall failure"); + break; + } + git_error_set(GIT_ERROR_SSL, "SSL error: received early EOF"); + return GIT_EEOF; + break; + case SSL_ERROR_SSL: + { + char errmsg[256]; + e = ERR_get_error(); + ERR_error_string_n(e, errmsg, sizeof(errmsg)); + git_error_set(GIT_ERROR_SSL, "SSL error: %s", errmsg); + break; + } + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + default: + git_error_set(GIT_ERROR_SSL, "SSL error: unknown error"); + break; + } + return -1; +} + +static int ssl_teardown(SSL *ssl) +{ + int ret; + + ret = SSL_shutdown(ssl); + if (ret < 0) + ret = ssl_set_error(ssl, ret); + else + ret = 0; + + return ret; +} + +static int check_host_name(const char *name, const char *host) +{ + if (!strcasecmp(name, host)) + return 0; + + if (gitno__match_host(name, host) < 0) + return -1; + + return 0; +} + +static int verify_server_cert(SSL *ssl, const char *host) +{ + X509 *cert = NULL; + X509_NAME *peer_name; + ASN1_STRING *str; + unsigned char *peer_cn = NULL; + int matched = -1, type = GEN_DNS; + GENERAL_NAMES *alts; + struct in6_addr addr6; + struct in_addr addr4; + void *addr = NULL; + int i = -1, j, error = 0; + + if (SSL_get_verify_result(ssl) != X509_V_OK) { + git_error_set(GIT_ERROR_SSL, "the SSL certificate is invalid"); + return GIT_ECERTIFICATE; + } + + /* Try to parse the host as an IP address to see if it is */ + if (p_inet_pton(AF_INET, host, &addr4)) { + type = GEN_IPADD; + addr = &addr4; + } else { + if (p_inet_pton(AF_INET6, host, &addr6)) { + type = GEN_IPADD; + addr = &addr6; + } + } + + + cert = SSL_get_peer_certificate(ssl); + if (!cert) { + error = -1; + git_error_set(GIT_ERROR_SSL, "the server did not provide a certificate"); + goto cleanup; + } + + /* Check the alternative names */ + alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (alts) { + int num; + + num = sk_GENERAL_NAME_num(alts); + for (i = 0; i < num && matched != 1; i++) { + const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i); + const char *name = (char *) ASN1_STRING_get0_data(gn->d.ia5); + size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); + + /* Skip any names of a type we're not looking for */ + if (gn->type != type) + continue; + + if (type == GEN_DNS) { + /* If it contains embedded NULs, don't even try */ + if (memchr(name, '\0', namelen)) + continue; + + if (check_host_name(name, host) < 0) + matched = 0; + else + matched = 1; + } else if (type == GEN_IPADD) { + /* Here name isn't so much a name but a binary representation of the IP */ + matched = addr && !!memcmp(name, addr, namelen); + } + } + } + GENERAL_NAMES_free(alts); + + if (matched == 0) + goto cert_fail_name; + + if (matched == 1) { + goto cleanup; + } + + /* If no alternative names are available, check the common name */ + peer_name = X509_get_subject_name(cert); + if (peer_name == NULL) + goto on_error; + + if (peer_name) { + /* Get the index of the last CN entry */ + while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0) + i = j; + } + + if (i < 0) + goto on_error; + + str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i)); + if (str == NULL) + goto on_error; + + /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */ + if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) { + int size = ASN1_STRING_length(str); + + if (size > 0) { + peer_cn = OPENSSL_malloc(size + 1); + GIT_ERROR_CHECK_ALLOC(peer_cn); + memcpy(peer_cn, ASN1_STRING_get0_data(str), size); + peer_cn[size] = '\0'; + } else { + goto cert_fail_name; + } + } else { + int size = ASN1_STRING_to_UTF8(&peer_cn, str); + GIT_ERROR_CHECK_ALLOC(peer_cn); + if (memchr(peer_cn, '\0', size)) + goto cert_fail_name; + } + + if (check_host_name((char *)peer_cn, host) < 0) + goto cert_fail_name; + + goto cleanup; + +cert_fail_name: + error = GIT_ECERTIFICATE; + git_error_set(GIT_ERROR_SSL, "hostname does not match certificate"); + goto cleanup; + +on_error: + error = ssl_set_error(ssl, 0); + goto cleanup; + +cleanup: + X509_free(cert); + OPENSSL_free(peer_cn); + return error; +} + +typedef struct { + git_stream parent; + git_stream *io; + int owned; + bool connected; + char *host; + SSL *ssl; + git_cert_x509 cert_info; +} openssl_stream; + +static int openssl_connect(git_stream *stream) +{ + int ret; + BIO *bio; + openssl_stream *st = (openssl_stream *) stream; + + if (st->owned && (ret = git_stream_connect(st->io)) < 0) + return ret; + + bio = BIO_new(git_stream_bio_method); + GIT_ERROR_CHECK_ALLOC(bio); + + BIO_set_data(bio, st->io); + SSL_set_bio(st->ssl, bio, bio); + + /* specify the host in case SNI is needed */ +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + SSL_set_tlsext_host_name(st->ssl, st->host); +#endif + + if ((ret = SSL_connect(st->ssl)) <= 0) + return ssl_set_error(st->ssl, ret); + + st->connected = true; + + return verify_server_cert(st->ssl, st->host); +} + +static int openssl_certificate(git_cert **out, git_stream *stream) +{ + openssl_stream *st = (openssl_stream *) stream; + X509 *cert = SSL_get_peer_certificate(st->ssl); + unsigned char *guard, *encoded_cert = NULL; + int error, len; + + /* Retrieve the length of the certificate first */ + len = i2d_X509(cert, NULL); + if (len < 0) { + git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); + error = -1; + goto out; + } + + encoded_cert = git__malloc(len); + GIT_ERROR_CHECK_ALLOC(encoded_cert); + /* i2d_X509 makes 'guard' point to just after the data */ + guard = encoded_cert; + + len = i2d_X509(cert, &guard); + if (len < 0) { + git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); + error = -1; + goto out; + } + + st->cert_info.parent.cert_type = GIT_CERT_X509; + st->cert_info.data = encoded_cert; + st->cert_info.len = len; + encoded_cert = NULL; + + *out = &st->cert_info.parent; + error = 0; + +out: + git__free(encoded_cert); + X509_free(cert); + return error; +} + +static int openssl_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts) +{ + openssl_stream *st = (openssl_stream *) stream; + + return git_stream_set_proxy(st->io, proxy_opts); +} + +static ssize_t openssl_write(git_stream *stream, const char *data, size_t data_len, int flags) +{ + openssl_stream *st = (openssl_stream *) stream; + int ret, len = min(data_len, INT_MAX); + + GIT_UNUSED(flags); + + if ((ret = SSL_write(st->ssl, data, len)) <= 0) + return ssl_set_error(st->ssl, ret); + + return ret; +} + +static ssize_t openssl_read(git_stream *stream, void *data, size_t len) +{ + openssl_stream *st = (openssl_stream *) stream; + int ret; + + if ((ret = SSL_read(st->ssl, data, len)) <= 0) + return ssl_set_error(st->ssl, ret); + + return ret; +} + +static int openssl_close(git_stream *stream) +{ + openssl_stream *st = (openssl_stream *) stream; + int ret; + + if (st->connected && (ret = ssl_teardown(st->ssl)) < 0) + return -1; + + st->connected = false; + + return st->owned ? git_stream_close(st->io) : 0; +} + +static void openssl_free(git_stream *stream) +{ + openssl_stream *st = (openssl_stream *) stream; + + if (st->owned) + git_stream_free(st->io); + + SSL_free(st->ssl); + git__free(st->host); + git__free(st->cert_info.data); + git__free(st); +} + +static int openssl_stream_wrap( + git_stream **out, + git_stream *in, + const char *host, + int owned) +{ + openssl_stream *st; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(in); + GIT_ASSERT_ARG(host); + + st = git__calloc(1, sizeof(openssl_stream)); + GIT_ERROR_CHECK_ALLOC(st); + + st->io = in; + st->owned = owned; + + st->ssl = SSL_new(git__ssl_ctx); + if (st->ssl == NULL) { + git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); + git__free(st); + return -1; + } + + st->host = git__strdup(host); + GIT_ERROR_CHECK_ALLOC(st->host); + + st->parent.version = GIT_STREAM_VERSION; + st->parent.encrypted = 1; + st->parent.proxy_support = git_stream_supports_proxy(st->io); + st->parent.connect = openssl_connect; + st->parent.certificate = openssl_certificate; + st->parent.set_proxy = openssl_set_proxy; + st->parent.read = openssl_read; + st->parent.write = openssl_write; + st->parent.close = openssl_close; + st->parent.free = openssl_free; + + *out = (git_stream *) st; + return 0; +} + +int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host) +{ + if (openssl_ensure_initialized() < 0) + return -1; + + return openssl_stream_wrap(out, in, host, 0); +} + +int git_openssl_stream_new(git_stream **out, const char *host, const char *port) +{ + git_stream *stream = NULL; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(host); + GIT_ASSERT_ARG(port); + + if (openssl_ensure_initialized() < 0) + return -1; + + if ((error = git_socket_stream_new(&stream, host, port)) < 0) + return error; + + if ((error = openssl_stream_wrap(out, stream, host, 1)) < 0) { + git_stream_close(stream); + git_stream_free(stream); + } + + return error; +} + +int git_openssl__set_cert_location(const char *file, const char *path) +{ + if (openssl_ensure_initialized() < 0) + return -1; + + if (SSL_CTX_load_verify_locations(git__ssl_ctx, file, path) == 0) { + char errmsg[256]; + + ERR_error_string_n(ERR_get_error(), errmsg, sizeof(errmsg)); + git_error_set(GIT_ERROR_SSL, "OpenSSL error: failed to load certificates: %s", + errmsg); + + return -1; + } + return 0; +} + +#else + +#include "stream.h" +#include "git2/sys/openssl.h" + +int git_openssl_stream_global_init(void) +{ + return 0; +} + +int git_openssl_set_locking(void) +{ + git_error_set(GIT_ERROR_SSL, "libgit2 was not built with OpenSSL support"); + return -1; +} + +#endif diff --git a/src/libgit2/streams/openssl.h b/src/libgit2/streams/openssl.h new file mode 100644 index 000000000..89fb60a82 --- /dev/null +++ b/src/libgit2/streams/openssl.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_streams_openssl_h__ +#define INCLUDE_streams_openssl_h__ + +#include "common.h" +#include "streams/openssl_legacy.h" +#include "streams/openssl_dynamic.h" + +#include "git2/sys/stream.h" + +extern int git_openssl_stream_global_init(void); + +#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC) +# include +# include +# include +# include +# endif + +#ifdef GIT_OPENSSL +extern int git_openssl__set_cert_location(const char *file, const char *path); +extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port); +extern int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host); +#endif + +#endif diff --git a/src/libgit2/streams/openssl_dynamic.c b/src/libgit2/streams/openssl_dynamic.c new file mode 100644 index 000000000..da16b6ed7 --- /dev/null +++ b/src/libgit2/streams/openssl_dynamic.c @@ -0,0 +1,309 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "streams/openssl.h" +#include "streams/openssl_dynamic.h" + +#if defined(GIT_OPENSSL) && defined(GIT_OPENSSL_DYNAMIC) + +#include "runtime.h" + +#include + +unsigned char *(*ASN1_STRING_data)(ASN1_STRING *x); +const unsigned char *(*ASN1_STRING_get0_data)(const ASN1_STRING *x); +int (*ASN1_STRING_length)(const ASN1_STRING *x); +int (*ASN1_STRING_to_UTF8)(unsigned char **out, const ASN1_STRING *in); +int (*ASN1_STRING_type)(const ASN1_STRING *x); + +void *(*BIO_get_data)(BIO *a); +int (*BIO_get_new_index)(void); +int (*OPENSSL_init_ssl)(uint64_t opts, const void *settings); +void (*BIO_meth_free)(BIO_METHOD *biom); +int (*BIO_meth_set_create)(BIO_METHOD *biom, int (*create) (BIO *)); +int (*BIO_meth_set_ctrl)(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)); +int (*BIO_meth_set_destroy)(BIO_METHOD *biom, int (*destroy) (BIO *)); +int (*BIO_meth_set_gets)(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)); +int (*BIO_meth_set_puts)(BIO_METHOD *biom, int (*puts) (BIO *, const char *)); +int (*BIO_meth_set_read)(BIO_METHOD *biom, int (*read) (BIO *, char *, int)); +int (*BIO_meth_set_write)(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)); +BIO_METHOD *(*BIO_meth_new)(int type, const char *name); +BIO *(*BIO_new)(const BIO_METHOD *type); +void (*BIO_set_data)(BIO *a, void *ptr); +void (*BIO_set_init)(BIO *a, int init); + +void (*CRYPTO_free)(void *ptr, const char *file, int line); +void *(*CRYPTO_malloc)(size_t num, const char *file, int line); +int (*CRYPTO_num_locks)(void); +void (*CRYPTO_set_locking_callback)(void (*func)(int mode, int type, const char *file, int line)); +int (*CRYPTO_set_mem_functions)(void *(*m)(size_t bytes), void *(*r)(void *mem, size_t size), void (*f)(void *mem)); +int (*CRYPTO_THREADID_set_callback)(void (*func)(CRYPTO_THREADID *id)); +void (*CRYPTO_THREADID_set_numeric)(CRYPTO_THREADID *id, unsigned long val); + +char *(*ERR_error_string)(unsigned long e, char *buf); +void (*ERR_error_string_n)(unsigned long e, char *buf, size_t len); +unsigned long (*ERR_get_error)(void); + +int (*SSL_connect)(SSL *ssl); +long (*SSL_ctrl)(SSL *ssl, int cmd, long arg, void *parg); +void (*SSL_free)(SSL *ssl); +int (*SSL_get_error)(SSL *ssl, int ret); +X509 *(*SSL_get_peer_certificate)(const SSL *ssl); +long (*SSL_get_verify_result)(const SSL *ssl); +int (*SSL_library_init)(void); +void (*SSL_load_error_strings)(void); +SSL *(*SSL_new)(SSL_CTX *ctx); +int (*SSL_read)(SSL *ssl, const void *buf, int num); +void (*SSL_set_bio)(SSL *ssl, BIO *rbio, BIO *wbio); +int (*SSL_shutdown)(SSL *ssl); +int (*SSL_write)(SSL *ssl, const void *buf, int num); + +long (*SSL_CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg); +void (*SSL_CTX_free)(SSL_CTX *ctx); +SSL_CTX *(*SSL_CTX_new)(const SSL_METHOD *method); +int (*SSL_CTX_set_cipher_list)(SSL_CTX *ctx, const char *str); +int (*SSL_CTX_set_default_verify_paths)(SSL_CTX *ctx); +long (*SSL_CTX_set_options)(SSL_CTX *ctx, long options); +void (*SSL_CTX_set_verify)(SSL_CTX *ctx, int mode, int (*verify_callback)(int, X509_STORE_CTX *)); +int (*SSL_CTX_load_verify_locations)(SSL_CTX *ctx, const char *CAfile, const char *CApath); + +const SSL_METHOD *(*SSLv23_method)(void); +const SSL_METHOD *(*TLS_method)(void); + +ASN1_STRING *(*X509_NAME_ENTRY_get_data)(const X509_NAME_ENTRY *ne); +X509_NAME_ENTRY *(*X509_NAME_get_entry)(X509_NAME *name, int loc); +int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos); +void (*X509_free)(X509 *a); +void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx); +X509_NAME *(*X509_get_subject_name)(const X509 *x); + +int (*i2d_X509)(X509 *a, unsigned char **ppout); + +int (*OPENSSL_sk_num)(const void *sk); +void *(*OPENSSL_sk_value)(const void *sk, int i); +void (*OPENSSL_sk_free)(void *sk); + +int (*sk_num)(const void *sk); +void *(*sk_value)(const void *sk, int i); +void (*sk_free)(void *sk); + +void *openssl_handle; + +GIT_INLINE(void *) openssl_sym(int *err, const char *name, bool required) +{ + void *symbol; + + /* if we've seen an err, noop to retain it */ + if (*err) + return NULL; + + + if ((symbol = dlsym(openssl_handle, name)) == NULL && required) { + const char *msg = dlerror(); + git_error_set(GIT_ERROR_SSL, "could not load ssl function '%s': %s", name, msg ? msg : "unknown error"); + *err = -1; + } + + return symbol; +} + +static void dynamic_shutdown(void) +{ + dlclose(openssl_handle); + openssl_handle = NULL; +} + +int git_openssl_stream_dynamic_init(void) +{ + int err = 0; + + if ((openssl_handle = dlopen("libssl.so.1.1", RTLD_NOW)) == NULL && + (openssl_handle = dlopen("libssl.1.1.dylib", RTLD_NOW)) == NULL && + (openssl_handle = dlopen("libssl.so.1.0.0", RTLD_NOW)) == NULL && + (openssl_handle = dlopen("libssl.1.0.0.dylib", RTLD_NOW)) == NULL && + (openssl_handle = dlopen("libssl.so.10", RTLD_NOW)) == NULL) { + git_error_set(GIT_ERROR_SSL, "could not load ssl libraries"); + return -1; + } + + ASN1_STRING_data = (unsigned char *(*)(ASN1_STRING *x))openssl_sym(&err, "ASN1_STRING_data", false); + ASN1_STRING_get0_data = (const unsigned char *(*)(const ASN1_STRING *x))openssl_sym(&err, "ASN1_STRING_get0_data", false); + ASN1_STRING_length = (int (*)(const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_length", true); + ASN1_STRING_to_UTF8 = (int (*)(unsigned char **, const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_to_UTF8", true); + ASN1_STRING_type = (int (*)(const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_type", true); + + BIO_get_data = (void *(*)(BIO *))openssl_sym(&err, "BIO_get_data", false); + BIO_get_new_index = (int (*)(void))openssl_sym(&err, "BIO_get_new_index", false); + BIO_meth_free = (void (*)(BIO_METHOD *))openssl_sym(&err, "BIO_meth_free", false); + BIO_meth_new = (BIO_METHOD *(*)(int, const char *))openssl_sym(&err, "BIO_meth_new", false); + BIO_meth_set_create = (int (*)(BIO_METHOD *, int (*)(BIO *)))openssl_sym(&err, "BIO_meth_set_create", false); + BIO_meth_set_ctrl = (int (*)(BIO_METHOD *, long (*)(BIO *, int, long, void *)))openssl_sym(&err, "BIO_meth_set_ctrl", false); + BIO_meth_set_destroy = (int (*)(BIO_METHOD *, int (*)(BIO *)))openssl_sym(&err, "BIO_meth_set_destroy", false); + BIO_meth_set_gets = (int (*)(BIO_METHOD *, int (*)(BIO *, char *, int)))openssl_sym(&err, "BIO_meth_set_gets", false); + BIO_meth_set_puts = (int (*)(BIO_METHOD *, int (*)(BIO *, const char *)))openssl_sym(&err, "BIO_meth_set_puts", false); + BIO_meth_set_read = (int (*)(BIO_METHOD *, int (*)(BIO *, char *, int)))openssl_sym(&err, "BIO_meth_set_read", false); + BIO_meth_set_write = (int (*)(BIO_METHOD *, int (*)(BIO *, const char *, int)))openssl_sym(&err, "BIO_meth_set_write", false); + BIO_new = (BIO *(*)(const BIO_METHOD *))openssl_sym(&err, "BIO_new", true); + BIO_set_data = (void (*)(BIO *a, void *))openssl_sym(&err, "BIO_set_data", false); + BIO_set_init = (void (*)(BIO *a, int))openssl_sym(&err, "BIO_set_init", false); + + CRYPTO_free = (void (*)(void *, const char *, int))openssl_sym(&err, "CRYPTO_free", true); + CRYPTO_malloc = (void *(*)(size_t, const char *, int))openssl_sym(&err, "CRYPTO_malloc", true); + CRYPTO_num_locks = (int (*)(void))openssl_sym(&err, "CRYPTO_num_locks", false); + CRYPTO_set_locking_callback = (void (*)(void (*)(int, int, const char *, int)))openssl_sym(&err, "CRYPTO_set_locking_callback", false); + CRYPTO_set_mem_functions = (int (*)(void *(*)(size_t), void *(*)(void *, size_t), void (*f)(void *)))openssl_sym(&err, "CRYPTO_set_mem_functions", true); + + CRYPTO_THREADID_set_callback = (int (*)(void (*)(CRYPTO_THREADID *)))openssl_sym(&err, "CRYPTO_THREADID_set_callback", false); + CRYPTO_THREADID_set_numeric = (void (*)(CRYPTO_THREADID *, unsigned long))openssl_sym(&err, "CRYPTO_THREADID_set_numeric", false); + + ERR_error_string = (char *(*)(unsigned long, char *))openssl_sym(&err, "ERR_error_string", true); + ERR_error_string_n = (void (*)(unsigned long, char *, size_t))openssl_sym(&err, "ERR_error_string_n", true); + ERR_get_error = (unsigned long (*)(void))openssl_sym(&err, "ERR_get_error", true); + + OPENSSL_init_ssl = (int (*)(uint64_t opts, const void *settings))openssl_sym(&err, "OPENSSL_init_ssl", false); + OPENSSL_sk_num = (int (*)(const void *))openssl_sym(&err, "OPENSSL_sk_num", false); + OPENSSL_sk_value = (void *(*)(const void *sk, int i))openssl_sym(&err, "OPENSSL_sk_value", false); + OPENSSL_sk_free = (void (*)(void *))openssl_sym(&err, "OPENSSL_sk_free", false); + + sk_num = (int (*)(const void *))openssl_sym(&err, "sk_num", false); + sk_value = (void *(*)(const void *sk, int i))openssl_sym(&err, "sk_value", false); + sk_free = (void (*)(void *))openssl_sym(&err, "sk_free", false); + + SSL_connect = (int (*)(SSL *))openssl_sym(&err, "SSL_connect", true); + SSL_ctrl = (long (*)(SSL *, int, long, void *))openssl_sym(&err, "SSL_ctrl", true); + SSL_get_peer_certificate = (X509 *(*)(const SSL *))openssl_sym(&err, "SSL_get_peer_certificate", true); + SSL_library_init = (int (*)(void))openssl_sym(&err, "SSL_library_init", false); + SSL_free = (void (*)(SSL *))openssl_sym(&err, "SSL_free", true); + SSL_get_error = (int (*)(SSL *, int))openssl_sym(&err, "SSL_get_error", true); + SSL_get_verify_result = (long (*)(const SSL *ssl))openssl_sym(&err, "SSL_get_verify_result", true); + SSL_load_error_strings = (void (*)(void))openssl_sym(&err, "SSL_load_error_strings", false); + SSL_new = (SSL *(*)(SSL_CTX *))openssl_sym(&err, "SSL_new", true); + SSL_read = (int (*)(SSL *, const void *, int))openssl_sym(&err, "SSL_read", true); + SSL_set_bio = (void (*)(SSL *, BIO *, BIO *))openssl_sym(&err, "SSL_set_bio", true); + SSL_shutdown = (int (*)(SSL *ssl))openssl_sym(&err, "SSL_shutdown", true); + SSL_write = (int (*)(SSL *, const void *, int))openssl_sym(&err, "SSL_write", true); + + SSL_CTX_ctrl = (long (*)(SSL_CTX *, int, long, void *))openssl_sym(&err, "SSL_CTX_ctrl", true); + SSL_CTX_free = (void (*)(SSL_CTX *))openssl_sym(&err, "SSL_CTX_free", true); + SSL_CTX_new = (SSL_CTX *(*)(const SSL_METHOD *))openssl_sym(&err, "SSL_CTX_new", true); + SSL_CTX_set_cipher_list = (int (*)(SSL_CTX *, const char *))openssl_sym(&err, "SSL_CTX_set_cipher_list", true); + SSL_CTX_set_default_verify_paths = (int (*)(SSL_CTX *ctx))openssl_sym(&err, "SSL_CTX_set_default_verify_paths", true); + SSL_CTX_set_options = (long (*)(SSL_CTX *, long))openssl_sym(&err, "SSL_CTX_set_options", false); + SSL_CTX_set_verify = (void (*)(SSL_CTX *, int, int (*)(int, X509_STORE_CTX *)))openssl_sym(&err, "SSL_CTX_set_verify", true); + SSL_CTX_load_verify_locations = (int (*)(SSL_CTX *, const char *, const char *))openssl_sym(&err, "SSL_CTX_load_verify_locations", true); + + SSLv23_method = (const SSL_METHOD *(*)(void))openssl_sym(&err, "SSLv23_method", false); + TLS_method = (const SSL_METHOD *(*)(void))openssl_sym(&err, "TLS_method", false); + + X509_NAME_ENTRY_get_data = (ASN1_STRING *(*)(const X509_NAME_ENTRY *))openssl_sym(&err, "X509_NAME_ENTRY_get_data", true); + X509_NAME_get_entry = (X509_NAME_ENTRY *(*)(X509_NAME *, int))openssl_sym(&err, "X509_NAME_get_entry", true); + X509_NAME_get_index_by_NID = (int (*)(X509_NAME *, int, int))openssl_sym(&err, "X509_NAME_get_index_by_NID", true); + X509_free = (void (*)(X509 *))openssl_sym(&err, "X509_free", true); + X509_get_ext_d2i = (void *(*)(const X509 *x, int nid, int *crit, int *idx))openssl_sym(&err, "X509_get_ext_d2i", true); + X509_get_subject_name = (X509_NAME *(*)(const X509 *))openssl_sym(&err, "X509_get_subject_name", true); + + i2d_X509 = (int (*)(X509 *a, unsigned char **ppout))openssl_sym(&err, "i2d_X509", true); + + if (err) + goto on_error; + + /* Add legacy functionality */ + if (!OPENSSL_init_ssl) { + OPENSSL_init_ssl = OPENSSL_init_ssl__legacy; + + if (!SSL_library_init || + !SSL_load_error_strings || + !CRYPTO_num_locks || + !CRYPTO_set_locking_callback || + !CRYPTO_THREADID_set_callback || + !CRYPTO_THREADID_set_numeric) { + git_error_set(GIT_ERROR_SSL, "could not load legacy openssl initialization functions"); + goto on_error; + } + } + + if (!SSL_CTX_set_options) + SSL_CTX_set_options = SSL_CTX_set_options__legacy; + + if (TLS_method) + SSLv23_method = TLS_method; + + if (!BIO_meth_new) { + BIO_meth_new = BIO_meth_new__legacy; + BIO_meth_new = BIO_meth_new__legacy; + BIO_meth_free = BIO_meth_free__legacy; + BIO_meth_set_write = BIO_meth_set_write__legacy; + BIO_meth_set_read = BIO_meth_set_read__legacy; + BIO_meth_set_puts = BIO_meth_set_puts__legacy; + BIO_meth_set_gets = BIO_meth_set_gets__legacy; + BIO_meth_set_ctrl = BIO_meth_set_ctrl__legacy; + BIO_meth_set_create = BIO_meth_set_create__legacy; + BIO_meth_set_destroy = BIO_meth_set_destroy__legacy; + BIO_get_new_index = BIO_get_new_index__legacy; + BIO_set_data = BIO_set_data__legacy; + BIO_set_init = BIO_set_init__legacy; + BIO_get_data = BIO_get_data__legacy; + } + + if (!ASN1_STRING_get0_data) { + if (!ASN1_STRING_data) { + git_error_set(GIT_ERROR_SSL, "could not load legacy openssl string function"); + goto on_error; + } + + ASN1_STRING_get0_data = ASN1_STRING_get0_data__legacy; + } + + if ((!OPENSSL_sk_num && !sk_num) || + (!OPENSSL_sk_value && !sk_value) || + (!OPENSSL_sk_free && !sk_free)) { + git_error_set(GIT_ERROR_SSL, "could not load legacy openssl stack functions"); + goto on_error; + } + + if (git_runtime_shutdown_register(dynamic_shutdown) != 0) + goto on_error; + + return 0; + +on_error: + dlclose(openssl_handle); + return -1; +} + + +int sk_GENERAL_NAME_num(const GENERAL_NAME *sk) +{ + if (OPENSSL_sk_num) + return OPENSSL_sk_num(sk); + else if (sk_num) + return sk_num(sk); + + GIT_ASSERT_WITH_RETVAL(false, 0); + return 0; +} + +GENERAL_NAME *sk_GENERAL_NAME_value(const GENERAL_NAME *sk, int i) +{ + if (OPENSSL_sk_value) + return OPENSSL_sk_value(sk, i); + else if (sk_value) + return sk_value(sk, i); + + GIT_ASSERT_WITH_RETVAL(false, NULL); + return NULL; +} + +void GENERAL_NAMES_free(GENERAL_NAME *sk) +{ + if (OPENSSL_sk_free) + OPENSSL_sk_free(sk); + else if (sk_free) + sk_free(sk); +} + +#endif /* GIT_OPENSSL && GIT_OPENSSL_DYNAMIC */ diff --git a/src/libgit2/streams/openssl_dynamic.h b/src/libgit2/streams/openssl_dynamic.h new file mode 100644 index 000000000..a99691910 --- /dev/null +++ b/src/libgit2/streams/openssl_dynamic.h @@ -0,0 +1,348 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are adhered to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the routines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publicly available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ +/* ==================================================================== + * Copyright (c) 1998-2007 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ +/* ==================================================================== + * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. + * ECC cipher suite support in OpenSSL originally developed by + * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. + */ +/* ==================================================================== + * Copyright 2005 Nokia. All rights reserved. + * + * The portions of the attached software ("Contribution") is developed by + * Nokia Corporation and is licensed pursuant to the OpenSSL open source + * license. + * + * The Contribution, originally written by Mika Kousa and Pasi Eronen of + * Nokia Corporation, consists of the "PSK" (Pre-Shared Key) ciphersuites + * support (see RFC 4279) to OpenSSL. + * + * No patent licenses or other rights except those expressly stated in + * the OpenSSL open source license shall be deemed granted or received + * expressly, by implication, estoppel, or otherwise. + * + * No assurances are provided by Nokia that the Contribution does not + * infringe the patent or other intellectual property rights of any third + * party or that the license provides you with all the necessary rights + * to make use of the Contribution. + * + * THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. IN + * ADDITION TO THE DISCLAIMERS INCLUDED IN THE LICENSE, NOKIA + * SPECIFICALLY DISCLAIMS ANY LIABILITY FOR CLAIMS BROUGHT BY YOU OR ANY + * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR + * OTHERWISE. + */ + +#ifndef INCLUDE_streams_openssl_dynamic_h__ +#define INCLUDE_streams_openssl_dynamic_h__ + +#ifdef GIT_OPENSSL_DYNAMIC + +# define BIO_CTRL_FLUSH 11 + +# define BIO_TYPE_SOURCE_SINK 0x0400 + +# define CRYPTO_LOCK 1 + +# define GEN_DNS 2 +# define GEN_IPADD 7 + +# define NID_commonName 13 +# define NID_subject_alt_name 85 + +# define SSL_VERIFY_NONE 0x00 + +# define SSL_CTRL_OPTIONS 32 +# define SSL_CTRL_MODE 33 +# define SSL_CTRL_SET_TLSEXT_HOSTNAME 55 + +# define SSL_ERROR_NONE 0 +# define SSL_ERROR_SSL 1 +# define SSL_ERROR_WANT_READ 2 +# define SSL_ERROR_WANT_WRITE 3 +# define SSL_ERROR_WANT_X509_LOOKUP 4 +# define SSL_ERROR_SYSCALL 5 +# define SSL_ERROR_ZERO_RETURN 6 +# define SSL_ERROR_WANT_CONNECT 7 +# define SSL_ERROR_WANT_ACCEPT 8 + +# define SSL_OP_NO_COMPRESSION 0x00020000L +# define SSL_OP_NO_SSLv2 0x01000000L +# define SSL_OP_NO_SSLv3 0x02000000L + +# define SSL_MODE_AUTO_RETRY 0x00000004L + +# define TLSEXT_NAMETYPE_host_name 0 + +# define V_ASN1_UTF8STRING 12 + +# define X509_V_OK 0 + +/* Most of the OpenSSL types are mercifully opaque, so we can treat them like `void *` */ +typedef struct bio_st BIO; +typedef struct bio_method_st BIO_METHOD; +typedef void bio_info_cb; +typedef void * CRYPTO_EX_DATA; +typedef void CRYPTO_THREADID; +typedef void GENERAL_NAMES; +typedef void SSL; +typedef void SSL_CTX; +typedef void SSL_METHOD; +typedef void X509; +typedef void X509_NAME; +typedef void X509_NAME_ENTRY; +typedef void X509_STORE_CTX; + +typedef struct { + int length; + int type; + unsigned char *data; + long flags; +} ASN1_STRING; + +typedef struct { + int type; + union { + char *ptr; + ASN1_STRING *ia5; + } d; +} GENERAL_NAME; + +struct bio_st { + BIO_METHOD *method; + /* bio, mode, argp, argi, argl, ret */ + long (*callback) (struct bio_st *, int, const char *, int, long, long); + char *cb_arg; /* first argument for the callback */ + int init; + int shutdown; + int flags; /* extra storage */ + int retry_reason; + int num; + void *ptr; + struct bio_st *next_bio; /* used by filter BIOs */ + struct bio_st *prev_bio; /* used by filter BIOs */ + int references; + unsigned long num_read; + unsigned long num_write; + CRYPTO_EX_DATA ex_data; +}; + +struct bio_method_st { + int type; + const char *name; + int (*bwrite) (BIO *, const char *, int); + int (*bread) (BIO *, char *, int); + int (*bputs) (BIO *, const char *); + int (*bgets) (BIO *, char *, int); + long (*ctrl) (BIO *, int, long, void *); + int (*create) (BIO *); + int (*destroy) (BIO *); + long (*callback_ctrl) (BIO *, int, bio_info_cb *); +}; + +extern unsigned char *(*ASN1_STRING_data)(ASN1_STRING *x); +extern const unsigned char *(*ASN1_STRING_get0_data)(const ASN1_STRING *x); +extern int (*ASN1_STRING_length)(const ASN1_STRING *x); +extern int (*ASN1_STRING_to_UTF8)(unsigned char **out, const ASN1_STRING *in); +extern int (*ASN1_STRING_type)(const ASN1_STRING *x); + +extern void *(*BIO_get_data)(BIO *a); +extern int (*BIO_get_new_index)(void); +extern int (*OPENSSL_init_ssl)(uint64_t opts, const void *settings); +extern void (*BIO_meth_free)(BIO_METHOD *biom); +extern int (*BIO_meth_set_create)(BIO_METHOD *biom, int (*create) (BIO *)); +extern int (*BIO_meth_set_ctrl)(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)); +extern int (*BIO_meth_set_destroy)(BIO_METHOD *biom, int (*destroy) (BIO *)); +extern int (*BIO_meth_set_gets)(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)); +extern int (*BIO_meth_set_puts)(BIO_METHOD *biom, int (*puts) (BIO *, const char *)); +extern int (*BIO_meth_set_read)(BIO_METHOD *biom, int (*read) (BIO *, char *, int)); +extern int (*BIO_meth_set_write)(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)); +extern BIO_METHOD *(*BIO_meth_new)(int type, const char *name); +extern BIO *(*BIO_new)(const BIO_METHOD *type); +extern void (*BIO_set_data)(BIO *a, void *ptr); +extern void (*BIO_set_init)(BIO *a, int init); + +extern void (*CRYPTO_free)(void *ptr, const char *file, int line); +extern void *(*CRYPTO_malloc)(size_t num, const char *file, int line); +extern int (*CRYPTO_num_locks)(void); +extern void (*CRYPTO_set_locking_callback)(void (*func)(int mode, int type, const char *file, int line)); +extern int (*CRYPTO_set_mem_functions)(void *(*m)(size_t bytes), void *(*r)(void *mem, size_t size), void (*f)(void *mem)); +extern int (*CRYPTO_THREADID_set_callback)(void (*func)(CRYPTO_THREADID *id)); +extern void (*CRYPTO_THREADID_set_numeric)(CRYPTO_THREADID *id, unsigned long val); + +extern char *(*ERR_error_string)(unsigned long e, char *buf); +extern void (*ERR_error_string_n)(unsigned long e, char *buf, size_t len); +extern unsigned long (*ERR_get_error)(void); + +# define OPENSSL_malloc(num) CRYPTO_malloc(num, __FILE__, __LINE__) +# define OPENSSL_free(addr) CRYPTO_free(addr, __FILE__, __LINE__) + +extern int (*SSL_connect)(SSL *ssl); +extern long (*SSL_ctrl)(SSL *ssl, int cmd, long arg, void *parg); +extern void (*SSL_free)(SSL *ssl); +extern int (*SSL_get_error)(SSL *ssl, int ret); +extern X509 *(*SSL_get_peer_certificate)(const SSL *ssl); +extern long (*SSL_get_verify_result)(const SSL *ssl); +extern int (*SSL_library_init)(void); +extern void (*SSL_load_error_strings)(void); +extern SSL *(*SSL_new)(SSL_CTX *ctx); +extern int (*SSL_read)(SSL *ssl, const void *buf, int num); +extern void (*SSL_set_bio)(SSL *ssl, BIO *rbio, BIO *wbio); +extern int (*SSL_shutdown)(SSL *ssl); +extern int (*SSL_write)(SSL *ssl, const void *buf, int num); + +# define SSL_set_tlsext_host_name(s, name) SSL_ctrl((s), SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (char *)(name)); + +extern long (*SSL_CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg); +extern void (*SSL_CTX_free)(SSL_CTX *ctx); +extern SSL_CTX *(*SSL_CTX_new)(const SSL_METHOD *method); +extern int (*SSL_CTX_set_cipher_list)(SSL_CTX *ctx, const char *str); +extern int (*SSL_CTX_set_default_verify_paths)(SSL_CTX *ctx); +extern long (*SSL_CTX_set_options)(SSL_CTX *ctx, long options); +extern void (*SSL_CTX_set_verify)(SSL_CTX *ctx, int mode, int (*verify_callback)(int, X509_STORE_CTX *)); +extern int (*SSL_CTX_load_verify_locations)(SSL_CTX *ctx, const char *CAfile, const char *CApath); + +# define SSL_CTX_set_mode(ctx, mode) SSL_CTX_ctrl((ctx), SSL_CTRL_MODE, (mode), NULL); + +extern const SSL_METHOD *(*SSLv23_method)(void); +extern const SSL_METHOD *(*TLS_method)(void); + +extern ASN1_STRING *(*X509_NAME_ENTRY_get_data)(const X509_NAME_ENTRY *ne); +extern X509_NAME_ENTRY *(*X509_NAME_get_entry)(X509_NAME *name, int loc); +extern int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos); +extern void (*X509_free)(X509 *a); +extern void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx); +extern X509_NAME *(*X509_get_subject_name)(const X509 *x); + +extern int (*i2d_X509)(X509 *a, unsigned char **ppout); + +extern int (*OPENSSL_sk_num)(const void *sk); +extern void *(*OPENSSL_sk_value)(const void *sk, int i); +extern void (*OPENSSL_sk_free)(void *sk); + +extern int (*sk_num)(const void *sk); +extern void *(*sk_value)(const void *sk, int i); +extern void (*sk_free)(void *sk); + +extern int sk_GENERAL_NAME_num(const GENERAL_NAME *sk); +extern GENERAL_NAME *sk_GENERAL_NAME_value(const GENERAL_NAME *sk, int i); +extern void GENERAL_NAMES_free(GENERAL_NAME *sk); + +extern int git_openssl_stream_dynamic_init(void); + +#endif /* GIT_OPENSSL_DYNAMIC */ + +#endif diff --git a/src/libgit2/streams/openssl_legacy.c b/src/libgit2/streams/openssl_legacy.c new file mode 100644 index 000000000..e61e6efbb --- /dev/null +++ b/src/libgit2/streams/openssl_legacy.c @@ -0,0 +1,203 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "streams/openssl.h" +#include "streams/openssl_legacy.h" + +#include "runtime.h" +#include "git2/sys/openssl.h" + +#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC) +# include +# include +# include +# include +#endif + +#if defined(GIT_OPENSSL_LEGACY) || defined(GIT_OPENSSL_DYNAMIC) + +/* + * OpenSSL 1.1 made BIO opaque so we have to use functions to interact with it + * which do not exist in previous versions. We define these inline functions so + * we can program against the interface instead of littering the implementation + * with ifdefs. We do the same for OPENSSL_init_ssl. + */ + +int OPENSSL_init_ssl__legacy(uint64_t opts, const void *settings) +{ + GIT_UNUSED(opts); + GIT_UNUSED(settings); + SSL_load_error_strings(); + SSL_library_init(); + return 0; +} + +BIO_METHOD *BIO_meth_new__legacy(int type, const char *name) +{ + BIO_METHOD *meth = git__calloc(1, sizeof(BIO_METHOD)); + if (!meth) { + return NULL; + } + + meth->type = type; + meth->name = name; + + return meth; +} + +void BIO_meth_free__legacy(BIO_METHOD *biom) +{ + git__free(biom); +} + +int BIO_meth_set_write__legacy(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)) +{ + biom->bwrite = write; + return 1; +} + +int BIO_meth_set_read__legacy(BIO_METHOD *biom, int (*read) (BIO *, char *, int)) +{ + biom->bread = read; + return 1; +} + +int BIO_meth_set_puts__legacy(BIO_METHOD *biom, int (*puts) (BIO *, const char *)) +{ + biom->bputs = puts; + return 1; +} + +int BIO_meth_set_gets__legacy(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)) + +{ + biom->bgets = gets; + return 1; +} + +int BIO_meth_set_ctrl__legacy(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)) +{ + biom->ctrl = ctrl; + return 1; +} + +int BIO_meth_set_create__legacy(BIO_METHOD *biom, int (*create) (BIO *)) +{ + biom->create = create; + return 1; +} + +int BIO_meth_set_destroy__legacy(BIO_METHOD *biom, int (*destroy) (BIO *)) +{ + biom->destroy = destroy; + return 1; +} + +int BIO_get_new_index__legacy(void) +{ + /* This exists as of 1.1 so before we'd just have 0 */ + return 0; +} + +void BIO_set_init__legacy(BIO *b, int init) +{ + b->init = init; +} + +void BIO_set_data__legacy(BIO *a, void *ptr) +{ + a->ptr = ptr; +} + +void *BIO_get_data__legacy(BIO *a) +{ + return a->ptr; +} + +const unsigned char *ASN1_STRING_get0_data__legacy(const ASN1_STRING *x) +{ + return ASN1_STRING_data((ASN1_STRING *)x); +} + +long SSL_CTX_set_options__legacy(SSL_CTX *ctx, long op) +{ + return SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, op, NULL); +} + +# if defined(GIT_THREADS) +static git_mutex *openssl_locks; + +static void openssl_locking_function(int mode, int n, const char *file, int line) +{ + int lock; + + GIT_UNUSED(file); + GIT_UNUSED(line); + + lock = mode & CRYPTO_LOCK; + + if (lock) + (void)git_mutex_lock(&openssl_locks[n]); + else + git_mutex_unlock(&openssl_locks[n]); +} + +static void shutdown_ssl_locking(void) +{ + int num_locks, i; + + num_locks = CRYPTO_num_locks(); + CRYPTO_set_locking_callback(NULL); + + for (i = 0; i < num_locks; ++i) + git_mutex_free(&openssl_locks[i]); + git__free(openssl_locks); +} + +static void threadid_cb(CRYPTO_THREADID *threadid) +{ + GIT_UNUSED(threadid); + CRYPTO_THREADID_set_numeric(threadid, git_thread_currentid()); +} + +int git_openssl_set_locking(void) +{ + int num_locks, i; + +#ifndef GIT_THREADS + git_error_set(GIT_ERROR_THREAD, "libgit2 was not built with threads"); + return -1; +#endif + +#ifdef GIT_OPENSSL_DYNAMIC + /* + * This function is required on legacy versions of OpenSSL; when building + * with dynamically-loaded OpenSSL, we detect whether we loaded it or not. + */ + if (!CRYPTO_set_locking_callback) + return 0; +#endif + + CRYPTO_THREADID_set_callback(threadid_cb); + + num_locks = CRYPTO_num_locks(); + openssl_locks = git__calloc(num_locks, sizeof(git_mutex)); + GIT_ERROR_CHECK_ALLOC(openssl_locks); + + for (i = 0; i < num_locks; i++) { + if (git_mutex_init(&openssl_locks[i]) != 0) { + git_error_set(GIT_ERROR_SSL, "failed to initialize openssl locks"); + return -1; + } + } + + CRYPTO_set_locking_callback(openssl_locking_function); + return git_runtime_shutdown_register(shutdown_ssl_locking); +} +#endif /* GIT_THREADS */ + +#endif /* GIT_OPENSSL_LEGACY || GIT_OPENSSL_DYNAMIC */ diff --git a/src/libgit2/streams/openssl_legacy.h b/src/libgit2/streams/openssl_legacy.h new file mode 100644 index 000000000..e6dae9572 --- /dev/null +++ b/src/libgit2/streams/openssl_legacy.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_streams_openssl_legacy_h__ +#define INCLUDE_streams_openssl_legacy_h__ + +#include "streams/openssl_dynamic.h" + +#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC) +# include +# include +# include +# include + +# if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) +# define GIT_OPENSSL_LEGACY +# endif +#endif + +#if defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC) +# define OPENSSL_init_ssl OPENSSL_init_ssl__legacy +# define BIO_meth_new BIO_meth_new__legacy +# define BIO_meth_free BIO_meth_free__legacy +# define BIO_meth_set_write BIO_meth_set_write__legacy +# define BIO_meth_set_read BIO_meth_set_read__legacy +# define BIO_meth_set_puts BIO_meth_set_puts__legacy +# define BIO_meth_set_gets BIO_meth_set_gets__legacy +# define BIO_meth_set_ctrl BIO_meth_set_ctrl__legacy +# define BIO_meth_set_create BIO_meth_set_create__legacy +# define BIO_meth_set_destroy BIO_meth_set_destroy__legacy +# define BIO_get_new_index BIO_get_new_index__legacy +# define BIO_set_data BIO_set_data__legacy +# define BIO_set_init BIO_set_init__legacy +# define BIO_get_data BIO_get_data__legacy +# define ASN1_STRING_get0_data ASN1_STRING_get0_data__legacy +#endif + +#if defined(GIT_OPENSSL_LEGACY) || defined(GIT_OPENSSL_DYNAMIC) + +extern int OPENSSL_init_ssl__legacy(uint64_t opts, const void *settings); +extern BIO_METHOD *BIO_meth_new__legacy(int type, const char *name); +extern void BIO_meth_free__legacy(BIO_METHOD *biom); +extern int BIO_meth_set_write__legacy(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)); +extern int BIO_meth_set_read__legacy(BIO_METHOD *biom, int (*read) (BIO *, char *, int)); +extern int BIO_meth_set_puts__legacy(BIO_METHOD *biom, int (*puts) (BIO *, const char *)); +extern int BIO_meth_set_gets__legacy(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)); +extern int BIO_meth_set_ctrl__legacy(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)); +extern int BIO_meth_set_create__legacy(BIO_METHOD *biom, int (*create) (BIO *)); +extern int BIO_meth_set_destroy__legacy(BIO_METHOD *biom, int (*destroy) (BIO *)); +extern int BIO_get_new_index__legacy(void); +extern void BIO_set_data__legacy(BIO *a, void *ptr); +extern void BIO_set_init__legacy(BIO *b, int init); +extern void *BIO_get_data__legacy(BIO *a); +extern const unsigned char *ASN1_STRING_get0_data__legacy(const ASN1_STRING *x); +extern long SSL_CTX_set_options__legacy(SSL_CTX *ctx, long op); + +#endif + +#endif diff --git a/src/libgit2/streams/registry.c b/src/libgit2/streams/registry.c new file mode 100644 index 000000000..e60e1cd63 --- /dev/null +++ b/src/libgit2/streams/registry.c @@ -0,0 +1,119 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "streams/registry.h" + +#include "runtime.h" +#include "streams/tls.h" +#include "streams/mbedtls.h" +#include "streams/openssl.h" +#include "streams/stransport.h" + +struct stream_registry { + git_rwlock lock; + git_stream_registration callbacks; + git_stream_registration tls_callbacks; +}; + +static struct stream_registry stream_registry; + +static void shutdown_stream_registry(void) +{ + git_rwlock_free(&stream_registry.lock); +} + +int git_stream_registry_global_init(void) +{ + if (git_rwlock_init(&stream_registry.lock) < 0) + return -1; + + return git_runtime_shutdown_register(shutdown_stream_registry); +} + +GIT_INLINE(void) stream_registration_cpy( + git_stream_registration *target, + git_stream_registration *src) +{ + if (src) + memcpy(target, src, sizeof(git_stream_registration)); + else + memset(target, 0, sizeof(git_stream_registration)); +} + +int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type) +{ + git_stream_registration *target; + int error = GIT_ENOTFOUND; + + GIT_ASSERT_ARG(out); + + switch(type) { + case GIT_STREAM_STANDARD: + target = &stream_registry.callbacks; + break; + case GIT_STREAM_TLS: + target = &stream_registry.tls_callbacks; + break; + default: + git_error_set(GIT_ERROR_INVALID, "invalid stream type"); + return -1; + } + + if (git_rwlock_rdlock(&stream_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock stream registry"); + return -1; + } + + if (target->init) { + stream_registration_cpy(out, target); + error = 0; + } + + git_rwlock_rdunlock(&stream_registry.lock); + return error; +} + +int git_stream_register(git_stream_t type, git_stream_registration *registration) +{ + GIT_ASSERT(!registration || registration->init); + + GIT_ERROR_CHECK_VERSION(registration, GIT_STREAM_VERSION, "stream_registration"); + + if (git_rwlock_wrlock(&stream_registry.lock) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock stream registry"); + return -1; + } + + if ((type & GIT_STREAM_STANDARD) == GIT_STREAM_STANDARD) + stream_registration_cpy(&stream_registry.callbacks, registration); + + if ((type & GIT_STREAM_TLS) == GIT_STREAM_TLS) + stream_registration_cpy(&stream_registry.tls_callbacks, registration); + + git_rwlock_wrunlock(&stream_registry.lock); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_stream_register_tls( + int GIT_CALLBACK(ctor)(git_stream **out, const char *host, const char *port)) +{ + git_stream_registration registration = {0}; + + if (ctor) { + registration.version = GIT_STREAM_VERSION; + registration.init = ctor; + registration.wrap = NULL; + + return git_stream_register(GIT_STREAM_TLS, ®istration); + } else { + return git_stream_register(GIT_STREAM_TLS, NULL); + } +} +#endif diff --git a/src/libgit2/streams/registry.h b/src/libgit2/streams/registry.h new file mode 100644 index 000000000..adc2b8bdf --- /dev/null +++ b/src/libgit2/streams/registry.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_streams_registry_h__ +#define INCLUDE_streams_registry_h__ + +#include "common.h" +#include "git2/sys/stream.h" + +/** Configure stream registry. */ +int git_stream_registry_global_init(void); + +/** Lookup a stream registration. */ +extern int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type); + +#endif diff --git a/src/libgit2/streams/socket.c b/src/libgit2/streams/socket.c new file mode 100644 index 000000000..9415fe892 --- /dev/null +++ b/src/libgit2/streams/socket.c @@ -0,0 +1,239 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "streams/socket.h" + +#include "posix.h" +#include "netops.h" +#include "registry.h" +#include "stream.h" + +#ifndef _WIN32 +# include +# include +# include +# include +# include +# include +# include +#else +# include +# include +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32") +# endif +#endif + +#ifdef GIT_WIN32 +static void net_set_error(const char *str) +{ + int error = WSAGetLastError(); + char * win32_error = git_win32_get_error_message(error); + + if (win32_error) { + git_error_set(GIT_ERROR_NET, "%s: %s", str, win32_error); + git__free(win32_error); + } else { + git_error_set(GIT_ERROR_NET, "%s", str); + } +} +#else +static void net_set_error(const char *str) +{ + git_error_set(GIT_ERROR_NET, "%s: %s", str, strerror(errno)); +} +#endif + +static int close_socket(GIT_SOCKET s) +{ + if (s == INVALID_SOCKET) + return 0; + +#ifdef GIT_WIN32 + if (SOCKET_ERROR == closesocket(s)) + return -1; + + if (0 != WSACleanup()) { + git_error_set(GIT_ERROR_OS, "winsock cleanup failed"); + return -1; + } + + return 0; +#else + return close(s); +#endif + +} + +static int socket_connect(git_stream *stream) +{ + struct addrinfo *info = NULL, *p; + struct addrinfo hints; + git_socket_stream *st = (git_socket_stream *) stream; + GIT_SOCKET s = INVALID_SOCKET; + int ret; + +#ifdef GIT_WIN32 + /* on win32, the WSA context needs to be initialized + * before any socket calls can be performed */ + WSADATA wsd; + + if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { + git_error_set(GIT_ERROR_OS, "winsock init failed"); + return -1; + } + + if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { + WSACleanup(); + git_error_set(GIT_ERROR_OS, "winsock init failed"); + return -1; + } +#endif + + memset(&hints, 0x0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + + if ((ret = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) { + git_error_set(GIT_ERROR_NET, + "failed to resolve address for %s: %s", st->host, p_gai_strerror(ret)); + return -1; + } + + for (p = info; p != NULL; p = p->ai_next) { + s = socket(p->ai_family, p->ai_socktype | SOCK_CLOEXEC, p->ai_protocol); + + if (s == INVALID_SOCKET) + continue; + + if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) + break; + + /* If we can't connect, try the next one */ + close_socket(s); + s = INVALID_SOCKET; + } + + /* Oops, we couldn't connect to any address */ + if (s == INVALID_SOCKET && p == NULL) { + git_error_set(GIT_ERROR_OS, "failed to connect to %s", st->host); + p_freeaddrinfo(info); + return -1; + } + + st->s = s; + p_freeaddrinfo(info); + return 0; +} + +static ssize_t socket_write(git_stream *stream, const char *data, size_t len, int flags) +{ + git_socket_stream *st = (git_socket_stream *) stream; + ssize_t written; + + errno = 0; + + if ((written = p_send(st->s, data, len, flags)) < 0) { + net_set_error("error sending data"); + return -1; + } + + return written; +} + +static ssize_t socket_read(git_stream *stream, void *data, size_t len) +{ + ssize_t ret; + git_socket_stream *st = (git_socket_stream *) stream; + + if ((ret = p_recv(st->s, data, len, 0)) < 0) + net_set_error("error receiving socket data"); + + return ret; +} + +static int socket_close(git_stream *stream) +{ + git_socket_stream *st = (git_socket_stream *) stream; + int error; + + error = close_socket(st->s); + st->s = INVALID_SOCKET; + + return error; +} + +static void socket_free(git_stream *stream) +{ + git_socket_stream *st = (git_socket_stream *) stream; + + git__free(st->host); + git__free(st->port); + git__free(st); +} + +static int default_socket_stream_new( + git_stream **out, + const char *host, + const char *port) +{ + git_socket_stream *st; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(host); + GIT_ASSERT_ARG(port); + + st = git__calloc(1, sizeof(git_socket_stream)); + GIT_ERROR_CHECK_ALLOC(st); + + st->host = git__strdup(host); + GIT_ERROR_CHECK_ALLOC(st->host); + + if (port) { + st->port = git__strdup(port); + GIT_ERROR_CHECK_ALLOC(st->port); + } + + st->parent.version = GIT_STREAM_VERSION; + st->parent.connect = socket_connect; + st->parent.write = socket_write; + st->parent.read = socket_read; + st->parent.close = socket_close; + st->parent.free = socket_free; + st->s = INVALID_SOCKET; + + *out = (git_stream *) st; + return 0; +} + +int git_socket_stream_new( + git_stream **out, + const char *host, + const char *port) +{ + int (*init)(git_stream **, const char *, const char *) = NULL; + git_stream_registration custom = {0}; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(host); + GIT_ASSERT_ARG(port); + + if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_STANDARD)) == 0) + init = custom.init; + else if (error == GIT_ENOTFOUND) + init = default_socket_stream_new; + else + return error; + + if (!init) { + git_error_set(GIT_ERROR_NET, "there is no socket stream available"); + return -1; + } + + return init(out, host, port); +} diff --git a/src/libgit2/streams/socket.h b/src/libgit2/streams/socket.h new file mode 100644 index 000000000..3235f3167 --- /dev/null +++ b/src/libgit2/streams/socket.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_streams_socket_h__ +#define INCLUDE_streams_socket_h__ + +#include "common.h" + +#include "netops.h" + +typedef struct { + git_stream parent; + char *host; + char *port; + GIT_SOCKET s; +} git_socket_stream; + +extern int git_socket_stream_new(git_stream **out, const char *host, const char *port); + +#endif diff --git a/src/libgit2/streams/stransport.c b/src/libgit2/streams/stransport.c new file mode 100644 index 000000000..3f31d2541 --- /dev/null +++ b/src/libgit2/streams/stransport.c @@ -0,0 +1,326 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "streams/stransport.h" + +#ifdef GIT_SECURE_TRANSPORT + +#include +#include +#include + +#include "git2/transport.h" + +#include "streams/socket.h" + +static int stransport_error(OSStatus ret) +{ + CFStringRef message; + + if (ret == noErr || ret == errSSLClosedGraceful) { + git_error_clear(); + return 0; + } + +#if !TARGET_OS_IPHONE + message = SecCopyErrorMessageString(ret, NULL); + GIT_ERROR_CHECK_ALLOC(message); + + git_error_set(GIT_ERROR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8)); + CFRelease(message); +#else + git_error_set(GIT_ERROR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret); + GIT_UNUSED(message); +#endif + + return -1; +} + +typedef struct { + git_stream parent; + git_stream *io; + int owned; + SSLContextRef ctx; + CFDataRef der_data; + git_cert_x509 cert_info; +} stransport_stream; + +static int stransport_connect(git_stream *stream) +{ + stransport_stream *st = (stransport_stream *) stream; + int error; + SecTrustRef trust = NULL; + SecTrustResultType sec_res; + OSStatus ret; + + if (st->owned && (error = git_stream_connect(st->io)) < 0) + return error; + + ret = SSLHandshake(st->ctx); + if (ret != errSSLServerAuthCompleted) { + git_error_set(GIT_ERROR_SSL, "unexpected return value from ssl handshake %d", (int)ret); + return -1; + } + + if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr) + goto on_error; + + if (!trust) + return GIT_ECERTIFICATE; + + if ((ret = SecTrustEvaluate(trust, &sec_res)) != noErr) + goto on_error; + + CFRelease(trust); + + if (sec_res == kSecTrustResultInvalid || sec_res == kSecTrustResultOtherError) { + git_error_set(GIT_ERROR_SSL, "internal security trust error"); + return -1; + } + + if (sec_res == kSecTrustResultDeny || sec_res == kSecTrustResultRecoverableTrustFailure || + sec_res == kSecTrustResultFatalTrustFailure) { + git_error_set(GIT_ERROR_SSL, "untrusted connection error"); + return GIT_ECERTIFICATE; + } + + return 0; + +on_error: + if (trust) + CFRelease(trust); + + return stransport_error(ret); +} + +static int stransport_certificate(git_cert **out, git_stream *stream) +{ + stransport_stream *st = (stransport_stream *) stream; + SecTrustRef trust = NULL; + SecCertificateRef sec_cert; + OSStatus ret; + + if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr) + return stransport_error(ret); + + sec_cert = SecTrustGetCertificateAtIndex(trust, 0); + st->der_data = SecCertificateCopyData(sec_cert); + CFRelease(trust); + + if (st->der_data == NULL) { + git_error_set(GIT_ERROR_SSL, "retrieved invalid certificate data"); + return -1; + } + + st->cert_info.parent.cert_type = GIT_CERT_X509; + st->cert_info.data = (void *) CFDataGetBytePtr(st->der_data); + st->cert_info.len = CFDataGetLength(st->der_data); + + *out = (git_cert *)&st->cert_info; + return 0; +} + +static int stransport_set_proxy( + git_stream *stream, + const git_proxy_options *proxy_opts) +{ + stransport_stream *st = (stransport_stream *) stream; + + return git_stream_set_proxy(st->io, proxy_opts); +} + +/* + * Contrary to typical network IO callbacks, Secure Transport write callback is + * expected to write *all* passed data, not just as much as it can, and any + * other case would be considered a failure. + * + * This behavior is actually not specified in the Apple documentation, but is + * required for things to work correctly (and incidentally, that's also how + * Apple implements it in its projects at opensource.apple.com). + * + * Libgit2 streams happen to already have this very behavior so this is just + * passthrough. + */ +static OSStatus write_cb(SSLConnectionRef conn, const void *data, size_t *len) +{ + git_stream *io = (git_stream *) conn; + + if (git_stream__write_full(io, data, *len, 0) < 0) + return -36; /* "ioErr" from MacErrors.h which is not available on iOS */ + + return noErr; +} + +static ssize_t stransport_write(git_stream *stream, const char *data, size_t len, int flags) +{ + stransport_stream *st = (stransport_stream *) stream; + size_t data_len, processed; + OSStatus ret; + + GIT_UNUSED(flags); + + data_len = min(len, SSIZE_MAX); + if ((ret = SSLWrite(st->ctx, data, data_len, &processed)) != noErr) + return stransport_error(ret); + + GIT_ASSERT(processed < SSIZE_MAX); + return (ssize_t)processed; +} + +/* + * Contrary to typical network IO callbacks, Secure Transport read callback is + * expected to read *exactly* the requested number of bytes, not just as much + * as it can, and any other case would be considered a failure. + * + * This behavior is actually not specified in the Apple documentation, but is + * required for things to work correctly (and incidentally, that's also how + * Apple implements it in its projects at opensource.apple.com). + */ +static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len) +{ + git_stream *io = (git_stream *) conn; + OSStatus error = noErr; + size_t off = 0; + ssize_t ret; + + do { + ret = git_stream_read(io, data + off, *len - off); + if (ret < 0) { + error = -36; /* "ioErr" from MacErrors.h which is not available on iOS */ + break; + } + if (ret == 0) { + error = errSSLClosedGraceful; + break; + } + + off += ret; + } while (off < *len); + + *len = off; + return error; +} + +static ssize_t stransport_read(git_stream *stream, void *data, size_t len) +{ + stransport_stream *st = (stransport_stream *) stream; + size_t processed; + OSStatus ret; + + if ((ret = SSLRead(st->ctx, data, len, &processed)) != noErr) + return stransport_error(ret); + + return processed; +} + +static int stransport_close(git_stream *stream) +{ + stransport_stream *st = (stransport_stream *) stream; + OSStatus ret; + + ret = SSLClose(st->ctx); + if (ret != noErr && ret != errSSLClosedGraceful) + return stransport_error(ret); + + return st->owned ? git_stream_close(st->io) : 0; +} + +static void stransport_free(git_stream *stream) +{ + stransport_stream *st = (stransport_stream *) stream; + + if (st->owned) + git_stream_free(st->io); + + CFRelease(st->ctx); + if (st->der_data) + CFRelease(st->der_data); + git__free(st); +} + +static int stransport_wrap( + git_stream **out, + git_stream *in, + const char *host, + int owned) +{ + stransport_stream *st; + OSStatus ret; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(in); + GIT_ASSERT_ARG(host); + + st = git__calloc(1, sizeof(stransport_stream)); + GIT_ERROR_CHECK_ALLOC(st); + + st->io = in; + st->owned = owned; + + st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType); + if (!st->ctx) { + git_error_set(GIT_ERROR_NET, "failed to create SSL context"); + git__free(st); + return -1; + } + + if ((ret = SSLSetIOFuncs(st->ctx, read_cb, write_cb)) != noErr || + (ret = SSLSetConnection(st->ctx, st->io)) != noErr || + (ret = SSLSetSessionOption(st->ctx, kSSLSessionOptionBreakOnServerAuth, true)) != noErr || + (ret = SSLSetProtocolVersionMin(st->ctx, kTLSProtocol1)) != noErr || + (ret = SSLSetProtocolVersionMax(st->ctx, kTLSProtocol12)) != noErr || + (ret = SSLSetPeerDomainName(st->ctx, host, strlen(host))) != noErr) { + CFRelease(st->ctx); + git__free(st); + return stransport_error(ret); + } + + st->parent.version = GIT_STREAM_VERSION; + st->parent.encrypted = 1; + st->parent.proxy_support = git_stream_supports_proxy(st->io); + st->parent.connect = stransport_connect; + st->parent.certificate = stransport_certificate; + st->parent.set_proxy = stransport_set_proxy; + st->parent.read = stransport_read; + st->parent.write = stransport_write; + st->parent.close = stransport_close; + st->parent.free = stransport_free; + + *out = (git_stream *) st; + return 0; +} + +int git_stransport_stream_wrap( + git_stream **out, + git_stream *in, + const char *host) +{ + return stransport_wrap(out, in, host, 0); +} + +int git_stransport_stream_new(git_stream **out, const char *host, const char *port) +{ + git_stream *stream = NULL; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(host); + + error = git_socket_stream_new(&stream, host, port); + + if (!error) + error = stransport_wrap(out, stream, host, 1); + + if (error < 0 && stream) { + git_stream_close(stream); + git_stream_free(stream); + } + + return error; +} + +#endif diff --git a/src/libgit2/streams/stransport.h b/src/libgit2/streams/stransport.h new file mode 100644 index 000000000..1026e204b --- /dev/null +++ b/src/libgit2/streams/stransport.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_streams_stransport_h__ +#define INCLUDE_streams_stransport_h__ + +#include "common.h" + +#include "git2/sys/stream.h" + +#ifdef GIT_SECURE_TRANSPORT + +extern int git_stransport_stream_new(git_stream **out, const char *host, const char *port); +extern int git_stransport_stream_wrap(git_stream **out, git_stream *in, const char *host); + +#endif + +#endif diff --git a/src/libgit2/streams/tls.c b/src/libgit2/streams/tls.c new file mode 100644 index 000000000..e063a33f9 --- /dev/null +++ b/src/libgit2/streams/tls.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2/errors.h" + +#include "common.h" +#include "streams/registry.h" +#include "streams/tls.h" +#include "streams/mbedtls.h" +#include "streams/openssl.h" +#include "streams/stransport.h" + +int git_tls_stream_new(git_stream **out, const char *host, const char *port) +{ + int (*init)(git_stream **, const char *, const char *) = NULL; + git_stream_registration custom = {0}; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(host); + GIT_ASSERT_ARG(port); + + if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_TLS)) == 0) { + init = custom.init; + } else if (error == GIT_ENOTFOUND) { +#ifdef GIT_SECURE_TRANSPORT + init = git_stransport_stream_new; +#elif defined(GIT_OPENSSL) + init = git_openssl_stream_new; +#elif defined(GIT_MBEDTLS) + init = git_mbedtls_stream_new; +#endif + } else { + return error; + } + + if (!init) { + git_error_set(GIT_ERROR_SSL, "there is no TLS stream available"); + return -1; + } + + return init(out, host, port); +} + +int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host) +{ + int (*wrap)(git_stream **, git_stream *, const char *) = NULL; + git_stream_registration custom = {0}; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(in); + + if (git_stream_registry_lookup(&custom, GIT_STREAM_TLS) == 0) { + wrap = custom.wrap; + } else { +#ifdef GIT_SECURE_TRANSPORT + wrap = git_stransport_stream_wrap; +#elif defined(GIT_OPENSSL) + wrap = git_openssl_stream_wrap; +#elif defined(GIT_MBEDTLS) + wrap = git_mbedtls_stream_wrap; +#endif + } + + if (!wrap) { + git_error_set(GIT_ERROR_SSL, "there is no TLS stream available"); + return -1; + } + + return wrap(out, in, host); +} diff --git a/src/libgit2/streams/tls.h b/src/libgit2/streams/tls.h new file mode 100644 index 000000000..465a6ea89 --- /dev/null +++ b/src/libgit2/streams/tls.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_streams_tls_h__ +#define INCLUDE_streams_tls_h__ + +#include "common.h" + +#include "git2/sys/stream.h" + +/** + * Create a TLS stream with the most appropriate backend available for + * the current platform, whether that's SecureTransport on macOS, + * OpenSSL or mbedTLS on other Unixes, or something else entirely. + */ +extern int git_tls_stream_new(git_stream **out, const char *host, const char *port); + +/** + * Create a TLS stream on top of an existing insecure stream, using + * the most appropriate backend available for the current platform. + * + * This allows us to create a CONNECT stream on top of a proxy; + * using SecureTransport on macOS, OpenSSL or mbedTLS on other + * Unixes, or something else entirely. + */ +extern int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host); + +#endif diff --git a/src/libgit2/strmap.c b/src/libgit2/strmap.c new file mode 100644 index 000000000..c6e5b6dc7 --- /dev/null +++ b/src/libgit2/strmap.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "strmap.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kreallocarray git__reallocarray +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(str, const char *, void *) + +__KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) + +int git_strmap_new(git_strmap **out) +{ + *out = kh_init(str); + GIT_ERROR_CHECK_ALLOC(*out); + + return 0; +} + +void git_strmap_free(git_strmap *map) +{ + kh_destroy(str, map); +} + +void git_strmap_clear(git_strmap *map) +{ + kh_clear(str, map); +} + +size_t git_strmap_size(git_strmap *map) +{ + return kh_size(map); +} + +void *git_strmap_get(git_strmap *map, const char *key) +{ + size_t idx = kh_get(str, map, key); + if (idx == kh_end(map) || !kh_exist(map, idx)) + return NULL; + return kh_val(map, idx); +} + +int git_strmap_set(git_strmap *map, const char *key, void *value) +{ + size_t idx; + int rval; + + idx = kh_put(str, map, key, &rval); + if (rval < 0) + return -1; + + if (rval == 0) + kh_key(map, idx) = key; + + kh_val(map, idx) = value; + + return 0; +} + +int git_strmap_delete(git_strmap *map, const char *key) +{ + khiter_t idx = kh_get(str, map, key); + if (idx == kh_end(map)) + return GIT_ENOTFOUND; + kh_del(str, map, idx); + return 0; +} + +int git_strmap_exists(git_strmap *map, const char *key) +{ + return kh_get(str, map, key) != kh_end(map); +} + +int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key) +{ + size_t i = *iter; + + while (i < map->n_buckets && !kh_exist(map, i)) + i++; + + if (i >= map->n_buckets) + return GIT_ITEROVER; + + if (key) + *key = kh_key(map, i); + if (value) + *value = kh_val(map, i); + *iter = ++i; + + return 0; +} diff --git a/src/libgit2/strmap.h b/src/libgit2/strmap.h new file mode 100644 index 000000000..9f5e4cc8b --- /dev/null +++ b/src/libgit2/strmap.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_strmap_h__ +#define INCLUDE_strmap_h__ + +#include "common.h" + +/** A map with C strings as key. */ +typedef struct kh_str_s git_strmap; + +/** + * Allocate a new string map. + * + * @param out Pointer to the map that shall be allocated. + * @return 0 on success, an error code if allocation has failed. + */ +int git_strmap_new(git_strmap **out); + +/** + * Free memory associated with the map. + * + * Note that this function will _not_ free keys or values added + * to this map. + * + * @param map Pointer to the map that is to be free'd. May be + * `NULL`. + */ +void git_strmap_free(git_strmap *map); + +/** + * Clear all entries from the map. + * + * This function will remove all entries from the associated map. + * Memory associated with it will not be released, though. + * + * @param map Pointer to the map that shall be cleared. May be + * `NULL`. + */ +void git_strmap_clear(git_strmap *map); + +/** + * Return the number of elements in the map. + * + * @parameter map map containing the elements + * @return number of elements in the map + */ +size_t git_strmap_size(git_strmap *map); + +/** + * Return value associated with the given key. + * + * @param map map to search key in + * @param key key to search for + * @return value associated with the given key or NULL if the key was not found + */ +void *git_strmap_get(git_strmap *map, const char *key); + +/** + * Set the entry for key to value. + * + * If the map has no corresponding entry for the given key, a new + * entry will be created with the given value. If an entry exists + * already, its value will be updated to match the given value. + * + * @param map map to create new entry in + * @param key key to set + * @param value value to associate the key with; may be NULL + * @return zero if the key was successfully set, a negative error + * code otherwise + */ +int git_strmap_set(git_strmap *map, const char *key, void *value); + +/** + * Delete an entry from the map. + * + * Delete the given key and its value from the map. If no such + * key exists, this will do nothing. + * + * @param map map to delete key in + * @param key key to delete + * @return `0` if the key has been deleted, GIT_ENOTFOUND if no + * such key was found, a negative code in case of an + * error + */ +int git_strmap_delete(git_strmap *map, const char *key); + +/** + * Check whether a key exists in the given map. + * + * @param map map to query for the key + * @param key key to search for + * @return 0 if the key has not been found, 1 otherwise + */ +int git_strmap_exists(git_strmap *map, const char *key); + +/** + * Iterate over entries of the map. + * + * This functions allows to iterate over all key-value entries of + * the map. The current position is stored in the `iter` variable + * and should be initialized to `0` before the first call to this + * function. + * + * @param map map to iterate over + * @param value pointer to the variable where to store the current + * value. May be NULL. + * @param iter iterator storing the current position. Initialize + * with zero previous to the first call. + * @param key pointer to the variable where to store the current + * key. May be NULL. + * @return `0` if the next entry was correctly retrieved. + * GIT_ITEROVER if no entries are left. A negative error + * code otherwise. + */ +int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key); + +#define git_strmap_foreach(h, kvar, vvar, code) { size_t __i = 0; \ + while (git_strmap_iterate((void **) &(vvar), h, &__i, &(kvar)) == 0) { \ + code; \ + } } + +#define git_strmap_foreach_value(h, vvar, code) { size_t __i = 0; \ + while (git_strmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ + code; \ + } } + +#endif diff --git a/src/libgit2/strnlen.h b/src/libgit2/strnlen.h new file mode 100644 index 000000000..eecfe3c02 --- /dev/null +++ b/src/libgit2/strnlen.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_strlen_h__ +#define INCLUDE_strlen_h__ + +#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) || defined(__MidnightBSD__) ||\ + (defined(_MSC_VER) && _MSC_VER < 1500) +# define NO_STRNLEN +#endif + +#ifdef NO_STRNLEN +GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) { + const char *end = memchr(s, 0, maxlen); + return end ? (size_t)(end - s) : maxlen; +} +#else +# define p_strnlen strnlen +#endif + +#endif diff --git a/src/libgit2/submodule.c b/src/libgit2/submodule.c new file mode 100644 index 000000000..0f4f0726c --- /dev/null +++ b/src/libgit2/submodule.c @@ -0,0 +1,2380 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "submodule.h" + +#include "buf.h" +#include "branch.h" +#include "vector.h" +#include "posix.h" +#include "config_backend.h" +#include "config.h" +#include "repository.h" +#include "tree.h" +#include "iterator.h" +#include "fs_path.h" +#include "str.h" +#include "index.h" +#include "worktree.h" +#include "clone.h" +#include "path.h" + +#include "git2/config.h" +#include "git2/sys/config.h" +#include "git2/types.h" +#include "git2/index.h" + +#define GIT_MODULES_FILE ".gitmodules" + +static git_configmap _sm_update_map[] = { + {GIT_CONFIGMAP_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT}, + {GIT_CONFIGMAP_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE}, + {GIT_CONFIGMAP_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}, + {GIT_CONFIGMAP_STRING, "none", GIT_SUBMODULE_UPDATE_NONE}, + {GIT_CONFIGMAP_FALSE, NULL, GIT_SUBMODULE_UPDATE_NONE}, + {GIT_CONFIGMAP_TRUE, NULL, GIT_SUBMODULE_UPDATE_CHECKOUT}, +}; + +static git_configmap _sm_ignore_map[] = { + {GIT_CONFIGMAP_STRING, "none", GIT_SUBMODULE_IGNORE_NONE}, + {GIT_CONFIGMAP_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED}, + {GIT_CONFIGMAP_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, + {GIT_CONFIGMAP_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, + {GIT_CONFIGMAP_FALSE, NULL, GIT_SUBMODULE_IGNORE_NONE}, + {GIT_CONFIGMAP_TRUE, NULL, GIT_SUBMODULE_IGNORE_ALL}, +}; + +static git_configmap _sm_recurse_map[] = { + {GIT_CONFIGMAP_STRING, "on-demand", GIT_SUBMODULE_RECURSE_ONDEMAND}, + {GIT_CONFIGMAP_FALSE, NULL, GIT_SUBMODULE_RECURSE_NO}, + {GIT_CONFIGMAP_TRUE, NULL, GIT_SUBMODULE_RECURSE_YES}, +}; + +enum { + CACHE_OK = 0, + CACHE_REFRESH = 1, + CACHE_FLUSH = 2 +}; +enum { + GITMODULES_EXISTING = 0, + GITMODULES_CREATE = 1 +}; + +static int submodule_alloc(git_submodule **out, git_repository *repo, const char *name); +static git_config_backend *open_gitmodules(git_repository *repo, int gitmod); +static int gitmodules_snapshot(git_config **snap, git_repository *repo); +static int get_url_base(git_str *url, git_repository *repo); +static int lookup_head_remote_key(git_str *remote_key, git_repository *repo); +static int lookup_default_remote(git_remote **remote, git_repository *repo); +static int submodule_load_each(const git_config_entry *entry, void *payload); +static int submodule_read_config(git_submodule *sm, git_config *cfg); +static int submodule_load_from_wd_lite(git_submodule *); +static void submodule_get_index_status(unsigned int *, git_submodule *); +static void submodule_get_wd_status(unsigned int *, git_submodule *, git_repository *, git_submodule_ignore_t); +static void submodule_update_from_index_entry(git_submodule *sm, const git_index_entry *ie); +static void submodule_update_from_head_data(git_submodule *sm, mode_t mode, const git_oid *id); + +static int submodule_cmp(const void *a, const void *b) +{ + return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name); +} + +static int submodule_config_key_trunc_puts(git_str *key, const char *suffix) +{ + ssize_t idx = git_str_rfind(key, '.'); + git_str_truncate(key, (size_t)(idx + 1)); + return git_str_puts(key, suffix); +} + +/* + * PUBLIC APIS + */ + +static void submodule_set_lookup_error(int error, const char *name) +{ + if (!error) + return; + + git_error_set(GIT_ERROR_SUBMODULE, (error == GIT_ENOTFOUND) ? + "no submodule named '%s'" : + "submodule '%s' has not been added yet", name); +} + +typedef struct { + const char *path; + char *name; +} fbp_data; + +static int find_by_path(const git_config_entry *entry, void *payload) +{ + fbp_data *data = payload; + + if (!strcmp(entry->value, data->path)) { + const char *fdot, *ldot; + fdot = strchr(entry->name, '.'); + ldot = strrchr(entry->name, '.'); + data->name = git__strndup(fdot + 1, ldot - fdot - 1); + GIT_ERROR_CHECK_ALLOC(data->name); + } + + return 0; +} + +/* + * Checks to see if the submodule shares its name with a file or directory that + * already exists on the index. If so, the submodule cannot be added. + */ +static int is_path_occupied(bool *occupied, git_repository *repo, const char *path) +{ + int error = 0; + git_index *index; + git_str dir = GIT_STR_INIT; + *occupied = false; + + if ((error = git_repository_index__weakptr(&index, repo)) < 0) + goto out; + + if ((error = git_index_find(NULL, index, path)) != GIT_ENOTFOUND) { + if (!error) { + git_error_set(GIT_ERROR_SUBMODULE, + "File '%s' already exists in the index", path); + *occupied = true; + } + goto out; + } + + if ((error = git_str_sets(&dir, path)) < 0) + goto out; + + if ((error = git_fs_path_to_dir(&dir)) < 0) + goto out; + + if ((error = git_index_find_prefix(NULL, index, dir.ptr)) != GIT_ENOTFOUND) { + if (!error) { + git_error_set(GIT_ERROR_SUBMODULE, + "Directory '%s' already exists in the index", path); + *occupied = true; + } + goto out; + } + + error = 0; + +out: + git_str_dispose(&dir); + return error; +} + +/** + * Release the name map returned by 'load_submodule_names'. + */ +static void free_submodule_names(git_strmap *names) +{ + const char *key; + char *value; + + if (names == NULL) + return; + + git_strmap_foreach(names, key, value, { + git__free((char *) key); + git__free(value); + }); + git_strmap_free(names); + + return; +} + +/** + * Map submodule paths to names. + * TODO: for some use-cases, this might need case-folding on a + * case-insensitive filesystem + */ +static int load_submodule_names(git_strmap **out, git_repository *repo, git_config *cfg) +{ + const char *key = "submodule\\..*\\.path"; + git_config_iterator *iter = NULL; + git_config_entry *entry; + git_str buf = GIT_STR_INIT; + git_strmap *names; + int isvalid, error; + + *out = NULL; + + if ((error = git_strmap_new(&names)) < 0) + goto out; + + if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0) + goto out; + + while ((error = git_config_next(&entry, iter)) == 0) { + const char *fdot, *ldot; + fdot = strchr(entry->name, '.'); + ldot = strrchr(entry->name, '.'); + + if (git_strmap_exists(names, entry->value)) { + git_error_set(GIT_ERROR_SUBMODULE, + "duplicated submodule path '%s'", entry->value); + error = -1; + goto out; + } + + git_str_clear(&buf); + git_str_put(&buf, fdot + 1, ldot - fdot - 1); + isvalid = git_submodule_name_is_valid(repo, buf.ptr, 0); + if (isvalid < 0) { + error = isvalid; + goto out; + } + if (!isvalid) + continue; + + if ((error = git_strmap_set(names, git__strdup(entry->value), git_str_detach(&buf))) < 0) { + git_error_set(GIT_ERROR_NOMEMORY, "error inserting submodule into hash table"); + error = -1; + goto out; + } + } + if (error == GIT_ITEROVER) + error = 0; + + *out = names; + names = NULL; + +out: + free_submodule_names(names); + git_str_dispose(&buf); + git_config_iterator_free(iter); + return error; +} + +int git_submodule_cache_init(git_strmap **out, git_repository *repo) +{ + int error = 0; + git_strmap *cache = NULL; + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + if ((error = git_strmap_new(&cache)) < 0) + return error; + if ((error = git_submodule__map(repo, cache)) < 0) { + git_submodule_cache_free(cache); + return error; + } + *out = cache; + return error; +} + +int git_submodule_cache_free(git_strmap *cache) +{ + git_submodule *sm = NULL; + if (cache == NULL) + return 0; + git_strmap_foreach_value(cache, sm, { + git_submodule_free(sm); + }); + git_strmap_free(cache); + return 0; +} + +int git_submodule_lookup( + git_submodule **out, /* NULL if user only wants to test existence */ + git_repository *repo, + const char *name) /* trailing slash is allowed */ +{ + return git_submodule__lookup_with_cache(out, repo, name, repo->submodule_cache); +} + +int git_submodule__lookup_with_cache( + git_submodule **out, /* NULL if user only wants to test existence */ + git_repository *repo, + const char *name, /* trailing slash is allowed */ + git_strmap *cache) +{ + int error; + unsigned int location; + git_submodule *sm; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if (repo->is_bare) { + git_error_set(GIT_ERROR_SUBMODULE, "cannot get submodules without a working tree"); + return -1; + } + + if (cache != NULL) { + if ((sm = git_strmap_get(cache, name)) != NULL) { + if (out) { + *out = sm; + GIT_REFCOUNT_INC(*out); + } + return 0; + } + } + + if ((error = submodule_alloc(&sm, repo, name)) < 0) + return error; + + if ((error = git_submodule_reload(sm, false)) < 0) { + git_submodule_free(sm); + return error; + } + + if ((error = git_submodule_location(&location, sm)) < 0) { + git_submodule_free(sm); + return error; + } + + /* If it's not configured or we're looking by path */ + if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) { + git_config_backend *mods; + const char *pattern = "submodule\\..*\\.path"; + git_str path = GIT_STR_INIT; + fbp_data data = { NULL, NULL }; + + git_str_puts(&path, name); + while (path.ptr[path.size-1] == '/') { + path.ptr[--path.size] = '\0'; + } + data.path = path.ptr; + + mods = open_gitmodules(repo, GITMODULES_EXISTING); + + if (mods) + error = git_config_backend_foreach_match(mods, pattern, find_by_path, &data); + + git_config_backend_free(mods); + + if (error < 0) { + git_submodule_free(sm); + git_str_dispose(&path); + return error; + } + + if (data.name) { + git__free(sm->name); + sm->name = data.name; + sm->path = git_str_detach(&path); + + /* Try to load again with the right name */ + if ((error = git_submodule_reload(sm, false)) < 0) { + git_submodule_free(sm); + return error; + } + } + + git_str_dispose(&path); + } + + if ((error = git_submodule_location(&location, sm)) < 0) { + git_submodule_free(sm); + return error; + } + + /* If we still haven't found it, do the WD check */ + if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) { + git_submodule_free(sm); + error = GIT_ENOTFOUND; + + /* If it's not configured, we still check if there's a repo at the path */ + if (git_repository_workdir(repo)) { + git_str path = GIT_STR_INIT; + if (git_str_join3(&path, '/', + git_repository_workdir(repo), + name, DOT_GIT) < 0 || + git_path_validate_str_length(NULL, &path) < 0) + return -1; + + if (git_fs_path_exists(path.ptr)) + error = GIT_EEXISTS; + + git_str_dispose(&path); + } + + submodule_set_lookup_error(error, name); + return error; + } + + if (out) + *out = sm; + else + git_submodule_free(sm); + + return 0; +} + +int git_submodule_name_is_valid(git_repository *repo, const char *name, int flags) +{ + git_str buf = GIT_STR_INIT; + int error, isvalid; + + if (flags == 0) + flags = GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS; + + /* Avoid allocating a new string if we can avoid it */ + if (strchr(name, '\\') != NULL) { + if ((error = git_fs_path_normalize_slashes(&buf, name)) < 0) + return error; + } else { + git_str_attach_notowned(&buf, name, strlen(name)); + } + + isvalid = git_path_is_valid(repo, buf.ptr, 0, flags); + git_str_dispose(&buf); + + return isvalid; +} + +static void submodule_free_dup(void *sm) +{ + git_submodule_free(sm); +} + +static int submodule_get_or_create(git_submodule **out, git_repository *repo, git_strmap *map, const char *name) +{ + git_submodule *sm = NULL; + int error; + + if ((sm = git_strmap_get(map, name)) != NULL) + goto done; + + /* if the submodule doesn't exist yet in the map, create it */ + if ((error = submodule_alloc(&sm, repo, name)) < 0) + return error; + + if ((error = git_strmap_set(map, sm->name, sm)) < 0) { + git_submodule_free(sm); + return error; + } + +done: + GIT_REFCOUNT_INC(sm); + *out = sm; + return 0; +} + +static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cfg) +{ + int error; + git_iterator *i = NULL; + const git_index_entry *entry; + git_strmap *names; + + if ((error = load_submodule_names(&names, git_index_owner(idx), cfg))) + goto done; + + if ((error = git_iterator_for_index(&i, git_index_owner(idx), idx, NULL)) < 0) + goto done; + + while (!(error = git_iterator_advance(&entry, i))) { + git_submodule *sm; + + if ((sm = git_strmap_get(map, entry->path)) != NULL) { + if (S_ISGITLINK(entry->mode)) + submodule_update_from_index_entry(sm, entry); + else + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; + } else if (S_ISGITLINK(entry->mode)) { + const char *name; + + if ((name = git_strmap_get(names, entry->path)) == NULL) + name = entry->path; + + if (!submodule_get_or_create(&sm, git_index_owner(idx), map, name)) { + submodule_update_from_index_entry(sm, entry); + git_submodule_free(sm); + } + } + } + + if (error == GIT_ITEROVER) + error = 0; + +done: + git_iterator_free(i); + free_submodule_names(names); + + return error; +} + +static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg) +{ + int error; + git_iterator *i = NULL; + const git_index_entry *entry; + git_strmap *names; + + if ((error = load_submodule_names(&names, git_tree_owner(head), cfg))) + goto done; + + if ((error = git_iterator_for_tree(&i, head, NULL)) < 0) + goto done; + + while (!(error = git_iterator_advance(&entry, i))) { + git_submodule *sm; + + if ((sm = git_strmap_get(map, entry->path)) != NULL) { + if (S_ISGITLINK(entry->mode)) + submodule_update_from_head_data(sm, entry->mode, &entry->id); + else + sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; + } else if (S_ISGITLINK(entry->mode)) { + const char *name; + + if ((name = git_strmap_get(names, entry->path)) == NULL) + name = entry->path; + + if (!submodule_get_or_create(&sm, git_tree_owner(head), map, name)) { + submodule_update_from_head_data( + sm, entry->mode, &entry->id); + git_submodule_free(sm); + } + } + } + + if (error == GIT_ITEROVER) + error = 0; + +done: + git_iterator_free(i); + free_submodule_names(names); + + return error; +} + +/* If have_sm is true, sm is populated, otherwise map an repo are. */ +typedef struct { + git_config *mods; + git_strmap *map; + git_repository *repo; +} lfc_data; + +int git_submodule__map(git_repository *repo, git_strmap *map) +{ + int error = 0; + git_index *idx = NULL; + git_tree *head = NULL; + git_str path = GIT_STR_INIT; + git_submodule *sm; + git_config *mods = NULL; + bool has_workdir; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(map); + + /* get sources that we will need to check */ + if (git_repository_index(&idx, repo) < 0) + git_error_clear(); + if (git_repository_head_tree(&head, repo) < 0) + git_error_clear(); + + has_workdir = git_repository_workdir(repo) != NULL; + + if (has_workdir && + (error = git_repository_workdir_path(&path, repo, GIT_MODULES_FILE)) < 0) + goto cleanup; + + /* add submodule information from .gitmodules */ + if (has_workdir) { + lfc_data data = { 0 }; + data.map = map; + data.repo = repo; + + if ((error = gitmodules_snapshot(&mods, repo)) < 0) { + if (error == GIT_ENOTFOUND) + error = 0; + goto cleanup; + } + + data.mods = mods; + if ((error = git_config_foreach( + mods, submodule_load_each, &data)) < 0) + goto cleanup; + } + /* add back submodule information from index */ + if (mods && idx) { + if ((error = submodules_from_index(map, idx, mods)) < 0) + goto cleanup; + } + /* add submodule information from HEAD */ + if (mods && head) { + if ((error = submodules_from_head(map, head, mods)) < 0) + goto cleanup; + } + /* shallow scan submodules in work tree as needed */ + if (has_workdir) { + git_strmap_foreach_value(map, sm, { + submodule_load_from_wd_lite(sm); + }); + } + +cleanup: + git_config_free(mods); + /* TODO: if we got an error, mark submodule config as invalid? */ + git_index_free(idx); + git_tree_free(head); + git_str_dispose(&path); + return error; +} + +int git_submodule_foreach( + git_repository *repo, + git_submodule_cb callback, + void *payload) +{ + git_vector snapshot = GIT_VECTOR_INIT; + git_strmap *submodules; + git_submodule *sm; + int error; + size_t i; + + if (repo->is_bare) { + git_error_set(GIT_ERROR_SUBMODULE, "cannot get submodules without a working tree"); + return -1; + } + + if ((error = git_strmap_new(&submodules)) < 0) + return error; + + if ((error = git_submodule__map(repo, submodules)) < 0) + goto done; + + if (!(error = git_vector_init( + &snapshot, git_strmap_size(submodules), submodule_cmp))) { + + git_strmap_foreach_value(submodules, sm, { + if ((error = git_vector_insert(&snapshot, sm)) < 0) + break; + GIT_REFCOUNT_INC(sm); + }); + } + + if (error < 0) + goto done; + + git_vector_uniq(&snapshot, submodule_free_dup); + + git_vector_foreach(&snapshot, i, sm) { + if ((error = callback(sm, sm->name, payload)) != 0) { + git_error_set_after_callback(error); + break; + } + } + +done: + git_vector_foreach(&snapshot, i, sm) + git_submodule_free(sm); + git_vector_free(&snapshot); + + git_strmap_foreach_value(submodules, sm, { + git_submodule_free(sm); + }); + git_strmap_free(submodules); + + return error; +} + +static int submodule_repo_init( + git_repository **out, + git_repository *parent_repo, + const char *path, + const char *url, + bool use_gitlink) +{ + int error = 0; + git_str workdir = GIT_STR_INIT, repodir = GIT_STR_INIT; + git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_repository *subrepo = NULL; + + error = git_repository_workdir_path(&workdir, parent_repo, path); + if (error < 0) + goto cleanup; + + initopt.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_NO_REINIT; + initopt.origin_url = url; + + /* init submodule repository and add origin remote as needed */ + + /* New style: sub-repo goes in /modules// with a + * gitlink in the sub-repo workdir directory to that repository + * + * Old style: sub-repo goes directly into repo//.git/ + */ + if (use_gitlink) { + error = git_repository__item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES); + if (error < 0) + goto cleanup; + error = git_str_joinpath(&repodir, repodir.ptr, path); + if (error < 0) + goto cleanup; + + initopt.workdir_path = workdir.ptr; + initopt.flags |= + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR | + GIT_REPOSITORY_INIT_RELATIVE_GITLINK; + + error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); + } else + error = git_repository_init_ext(&subrepo, workdir.ptr, &initopt); + +cleanup: + git_str_dispose(&workdir); + git_str_dispose(&repodir); + + *out = subrepo; + + return error; +} + +static int git_submodule__resolve_url( + git_str *out, + git_repository *repo, + const char *url) +{ + int error = 0; + git_str normalized = GIT_STR_INIT; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(url); + + /* We do this in all platforms in case someone on Windows created the .gitmodules */ + if (strchr(url, '\\')) { + if ((error = git_fs_path_normalize_slashes(&normalized, url)) < 0) + return error; + + url = normalized.ptr; + } + + + if (git_fs_path_is_relative(url)) { + if (!(error = get_url_base(out, repo))) + error = git_fs_path_apply_relative(out, url); + } else if (strchr(url, ':') != NULL || url[0] == '/') { + error = git_str_sets(out, url); + } else { + git_error_set(GIT_ERROR_SUBMODULE, "invalid format for submodule URL"); + error = -1; + } + + git_str_dispose(&normalized); + return error; +} + +int git_submodule_resolve_url( + git_buf *out, + git_repository *repo, + const char *url) +{ + GIT_BUF_WRAP_PRIVATE(out, git_submodule__resolve_url, repo, url); +} + +int git_submodule_add_setup( + git_submodule **out, + git_repository *repo, + const char *url, + const char *path, + int use_gitlink) +{ + int error = 0; + git_config_backend *mods = NULL; + git_submodule *sm = NULL; + git_str name = GIT_STR_INIT, real_url = GIT_STR_INIT; + git_repository *subrepo = NULL; + bool path_occupied; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(url); + GIT_ASSERT_ARG(path); + + /* see if there is already an entry for this submodule */ + + if (git_submodule_lookup(NULL, repo, path) < 0) + git_error_clear(); + else { + git_error_set(GIT_ERROR_SUBMODULE, + "attempt to add submodule '%s' that already exists", path); + return GIT_EEXISTS; + } + + /* validate and normalize path */ + + if (git__prefixcmp(path, git_repository_workdir(repo)) == 0) + path += strlen(git_repository_workdir(repo)); + + if (git_fs_path_root(path) >= 0) { + git_error_set(GIT_ERROR_SUBMODULE, "submodule path must be a relative path"); + error = -1; + goto cleanup; + } + + if ((error = is_path_occupied(&path_occupied, repo, path)) < 0) + goto cleanup; + + if (path_occupied) { + error = GIT_EEXISTS; + goto cleanup; + } + + /* update .gitmodules */ + + if (!(mods = open_gitmodules(repo, GITMODULES_CREATE))) { + git_error_set(GIT_ERROR_SUBMODULE, + "adding submodules to a bare repository is not supported"); + return -1; + } + + if ((error = git_str_printf(&name, "submodule.%s.path", path)) < 0 || + (error = git_config_backend_set_string(mods, name.ptr, path)) < 0) + goto cleanup; + + if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 || + (error = git_config_backend_set_string(mods, name.ptr, url)) < 0) + goto cleanup; + + git_str_clear(&name); + + /* init submodule repository and add origin remote as needed */ + + error = git_repository_workdir_path(&name, repo, path); + if (error < 0) + goto cleanup; + + /* if the repo does not already exist, then init a new repo and add it. + * Otherwise, just add the existing repo. + */ + if (!(git_fs_path_exists(name.ptr) && + git_fs_path_contains(&name, DOT_GIT))) { + + /* resolve the actual URL to use */ + if ((error = git_submodule__resolve_url(&real_url, repo, url)) < 0) + goto cleanup; + + if ((error = submodule_repo_init(&subrepo, repo, path, real_url.ptr, use_gitlink)) < 0) + goto cleanup; + } + + if ((error = git_submodule_lookup(&sm, repo, path)) < 0) + goto cleanup; + + error = git_submodule_init(sm, false); + +cleanup: + if (error && sm) { + git_submodule_free(sm); + sm = NULL; + } + if (out != NULL) + *out = sm; + + git_config_backend_free(mods); + git_repository_free(subrepo); + git_str_dispose(&real_url); + git_str_dispose(&name); + + return error; +} + +int git_submodule_repo_init( + git_repository **out, + const git_submodule *sm, + int use_gitlink) +{ + int error; + git_repository *sub_repo = NULL; + const char *configured_url; + git_config *cfg = NULL; + git_str buf = GIT_STR_INIT; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(sm); + + /* get the configured remote url of the submodule */ + if ((error = git_str_printf(&buf, "submodule.%s.url", sm->name)) < 0 || + (error = git_repository_config_snapshot(&cfg, sm->repo)) < 0 || + (error = git_config_get_string(&configured_url, cfg, buf.ptr)) < 0 || + (error = submodule_repo_init(&sub_repo, sm->repo, sm->path, configured_url, use_gitlink)) < 0) + goto done; + + *out = sub_repo; + +done: + git_config_free(cfg); + git_str_dispose(&buf); + return error; +} + +static int clone_return_origin(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload) +{ + GIT_UNUSED(url); + GIT_UNUSED(payload); + return git_remote_lookup(out, repo, name); +} + +static int clone_return_repo(git_repository **out, const char *path, int bare, void *payload) +{ + git_submodule *sm = payload; + + GIT_UNUSED(path); + GIT_UNUSED(bare); + return git_submodule_open(out, sm); +} + +int git_submodule_clone(git_repository **out, git_submodule *submodule, const git_submodule_update_options *given_opts) +{ + int error; + git_repository *clone; + git_str rel_path = GIT_STR_INIT; + git_submodule_update_options sub_opts = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + + GIT_ASSERT_ARG(submodule); + + if (given_opts) + memcpy(&sub_opts, given_opts, sizeof(sub_opts)); + + GIT_ERROR_CHECK_VERSION(&sub_opts, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, "git_submodule_update_options"); + + memcpy(&opts.checkout_opts, &sub_opts.checkout_opts, sizeof(sub_opts.checkout_opts)); + memcpy(&opts.fetch_opts, &sub_opts.fetch_opts, sizeof(sub_opts.fetch_opts)); + opts.repository_cb = clone_return_repo; + opts.repository_cb_payload = submodule; + opts.remote_cb = clone_return_origin; + opts.remote_cb_payload = submodule; + + error = git_repository_workdir_path(&rel_path, git_submodule_owner(submodule), git_submodule_path(submodule)); + if (error < 0) + goto cleanup; + + error = git_clone__submodule(&clone, git_submodule_url(submodule), git_str_cstr(&rel_path), &opts); + if (error < 0) + goto cleanup; + + if (!out) + git_repository_free(clone); + else + *out = clone; + +cleanup: + git_str_dispose(&rel_path); + + return error; +} + +int git_submodule_add_finalize(git_submodule *sm) +{ + int error; + git_index *index; + + GIT_ASSERT_ARG(sm); + + if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 || + (error = git_index_add_bypath(index, GIT_MODULES_FILE)) < 0) + return error; + + return git_submodule_add_to_index(sm, true); +} + +int git_submodule_add_to_index(git_submodule *sm, int write_index) +{ + int error; + git_repository *sm_repo = NULL; + git_index *index; + git_str path = GIT_STR_INIT; + git_commit *head; + git_index_entry entry; + struct stat st; + + GIT_ASSERT_ARG(sm); + + /* force reload of wd OID by git_submodule_open */ + sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID; + + if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 || + (error = git_repository_workdir_path(&path, sm->repo, sm->path)) < 0 || + (error = git_submodule_open(&sm_repo, sm)) < 0) + goto cleanup; + + /* read stat information for submodule working directory */ + if (p_stat(path.ptr, &st) < 0) { + git_error_set(GIT_ERROR_SUBMODULE, + "cannot add submodule without working directory"); + error = -1; + goto cleanup; + } + + memset(&entry, 0, sizeof(entry)); + entry.path = sm->path; + git_index_entry__init_from_stat( + &entry, &st, !(git_index_caps(index) & GIT_INDEX_CAPABILITY_NO_FILEMODE)); + + /* calling git_submodule_open will have set sm->wd_oid if possible */ + if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) { + git_error_set(GIT_ERROR_SUBMODULE, + "cannot add submodule without HEAD to index"); + error = -1; + goto cleanup; + } + git_oid_cpy(&entry.id, &sm->wd_oid); + + if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0) + goto cleanup; + + entry.ctime.seconds = (int32_t)git_commit_time(head); + entry.ctime.nanoseconds = 0; + entry.mtime.seconds = (int32_t)git_commit_time(head); + entry.mtime.nanoseconds = 0; + + git_commit_free(head); + + /* add it */ + error = git_index_add(index, &entry); + + /* write it, if requested */ + if (!error && write_index) { + error = git_index_write(index); + + if (!error) + git_oid_cpy(&sm->index_oid, &sm->wd_oid); + } + +cleanup: + git_repository_free(sm_repo); + git_str_dispose(&path); + return error; +} + +static const char *submodule_update_to_str(git_submodule_update_t update) +{ + int i; + for (i = 0; i < (int)ARRAY_SIZE(_sm_update_map); ++i) + if (_sm_update_map[i].map_value == (int)update) + return _sm_update_map[i].str_match; + return NULL; +} + +git_repository *git_submodule_owner(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + return submodule->repo; +} + +const char *git_submodule_name(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + return submodule->name; +} + +const char *git_submodule_path(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + return submodule->path; +} + +const char *git_submodule_url(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + return submodule->url; +} + +static int write_var(git_repository *repo, const char *name, const char *var, const char *val) +{ + git_str key = GIT_STR_INIT; + git_config_backend *mods; + int error; + + mods = open_gitmodules(repo, GITMODULES_CREATE); + if (!mods) + return -1; + + if ((error = git_str_printf(&key, "submodule.%s.%s", name, var)) < 0) + goto cleanup; + + if (val) + error = git_config_backend_set_string(mods, key.ptr, val); + else + error = git_config_backend_delete(mods, key.ptr); + + git_str_dispose(&key); + +cleanup: + git_config_backend_free(mods); + return error; +} + +static int write_mapped_var(git_repository *repo, const char *name, git_configmap *maps, size_t nmaps, const char *var, int ival) +{ + git_configmap_t type; + const char *val; + + if (git_config_lookup_map_enum(&type, &val, maps, nmaps, ival) < 0) { + git_error_set(GIT_ERROR_SUBMODULE, "invalid value for %s", var); + return -1; + } + + if (type == GIT_CONFIGMAP_TRUE) + val = "true"; + + return write_var(repo, name, var, val); +} + +const char *git_submodule_branch(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + return submodule->branch; +} + +int git_submodule_set_branch(git_repository *repo, const char *name, const char *branch) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + return write_var(repo, name, "branch", branch); +} + +int git_submodule_set_url(git_repository *repo, const char *name, const char *url) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(url); + + return write_var(repo, name, "url", url); +} + +const git_oid *git_submodule_index_id(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + + if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) + return &submodule->index_oid; + else + return NULL; +} + +const git_oid *git_submodule_head_id(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + + if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) + return &submodule->head_oid; + else + return NULL; +} + +const git_oid *git_submodule_wd_id(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); + + /* load unless we think we have a valid oid */ + if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) { + git_repository *subrepo; + + /* calling submodule open grabs the HEAD OID if possible */ + if (!git_submodule_open_bare(&subrepo, submodule)) + git_repository_free(subrepo); + else + git_error_clear(); + } + + if (submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) + return &submodule->wd_oid; + else + return NULL; +} + +git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_IGNORE_UNSPECIFIED); + + return (submodule->ignore < GIT_SUBMODULE_IGNORE_NONE) ? + GIT_SUBMODULE_IGNORE_NONE : submodule->ignore; +} + +int git_submodule_set_ignore(git_repository *repo, const char *name, git_submodule_ignore_t ignore) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + return write_mapped_var(repo, name, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), "ignore", ignore); +} + +git_submodule_update_t git_submodule_update_strategy(git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_UPDATE_NONE); + + return (submodule->update < GIT_SUBMODULE_UPDATE_CHECKOUT) ? + GIT_SUBMODULE_UPDATE_CHECKOUT : submodule->update; +} + +int git_submodule_set_update(git_repository *repo, const char *name, git_submodule_update_t update) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + return write_mapped_var(repo, name, _sm_update_map, ARRAY_SIZE(_sm_update_map), "update", update); +} + +git_submodule_recurse_t git_submodule_fetch_recurse_submodules( + git_submodule *submodule) +{ + GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_RECURSE_NO); + return submodule->fetch_recurse; +} + +int git_submodule_set_fetch_recurse_submodules(git_repository *repo, const char *name, git_submodule_recurse_t recurse) +{ + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + return write_mapped_var(repo, name, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), "fetchRecurseSubmodules", recurse); +} + +static int submodule_repo_create( + git_repository **out, + git_repository *parent_repo, + const char *path) +{ + int error = 0; + git_str workdir = GIT_STR_INIT, repodir = GIT_STR_INIT; + git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_repository *subrepo = NULL; + + initopt.flags = + GIT_REPOSITORY_INIT_MKPATH | + GIT_REPOSITORY_INIT_NO_REINIT | + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR | + GIT_REPOSITORY_INIT_RELATIVE_GITLINK; + + /* Workdir: path to sub-repo working directory */ + error = git_repository_workdir_path(&workdir, parent_repo, path); + if (error < 0) + goto cleanup; + + initopt.workdir_path = workdir.ptr; + + /** + * Repodir: path to the sub-repo. sub-repo goes in: + * /modules// with a gitlink in the + * sub-repo workdir directory to that repository. + */ + error = git_repository__item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES); + if (error < 0) + goto cleanup; + error = git_str_joinpath(&repodir, repodir.ptr, path); + if (error < 0) + goto cleanup; + + error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); + +cleanup: + git_str_dispose(&workdir); + git_str_dispose(&repodir); + + *out = subrepo; + + return error; +} + +/** + * Callback to override sub-repository creation when + * cloning a sub-repository. + */ +static int git_submodule_update_repo_init_cb( + git_repository **out, + const char *path, + int bare, + void *payload) +{ + git_submodule *sm; + + GIT_UNUSED(bare); + + sm = payload; + + return submodule_repo_create(out, sm->repo, path); +} + +int git_submodule_update_options_init(git_submodule_update_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_submodule_update_options, GIT_SUBMODULE_UPDATE_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_submodule_update_init_options(git_submodule_update_options *opts, unsigned int version) +{ + return git_submodule_update_options_init(opts, version); +} +#endif + +int git_submodule_update(git_submodule *sm, int init, git_submodule_update_options *_update_options) +{ + int error; + unsigned int submodule_status; + git_config *config = NULL; + const char *submodule_url; + git_repository *sub_repo = NULL; + git_remote *remote = NULL; + git_object *target_commit = NULL; + git_str buf = GIT_STR_INIT; + git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + git_clone_options clone_options = GIT_CLONE_OPTIONS_INIT; + + GIT_ASSERT_ARG(sm); + + if (_update_options) + memcpy(&update_options, _update_options, sizeof(git_submodule_update_options)); + + GIT_ERROR_CHECK_VERSION(&update_options, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, "git_submodule_update_options"); + + /* Copy over the remote callbacks */ + memcpy(&clone_options.fetch_opts, &update_options.fetch_opts, sizeof(git_fetch_options)); + + /* Get the status of the submodule to determine if it is already initialized */ + if ((error = git_submodule_status(&submodule_status, sm->repo, sm->name, GIT_SUBMODULE_IGNORE_UNSPECIFIED)) < 0) + goto done; + + /* + * If submodule work dir is not already initialized, check to see + * what we need to do (initialize, clone, return error...) + */ + if (submodule_status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) { + /* + * Work dir is not initialized, check to see if the submodule + * info has been copied into .git/config + */ + if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 || + (error = git_str_printf(&buf, "submodule.%s.url", git_submodule_name(sm))) < 0) + goto done; + + if ((error = git_config_get_string(&submodule_url, config, git_str_cstr(&buf))) < 0) { + /* + * If the error is not "not found" or if it is "not found" and we are not + * initializing the submodule, then return error. + */ + if (error != GIT_ENOTFOUND) + goto done; + + if (!init) { + git_error_set(GIT_ERROR_SUBMODULE, "submodule is not initialized"); + error = GIT_ERROR; + goto done; + } + + /* The submodule has not been initialized yet - initialize it now.*/ + if ((error = git_submodule_init(sm, 0)) < 0) + goto done; + + git_config_free(config); + config = NULL; + + if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 || + (error = git_config_get_string(&submodule_url, config, git_str_cstr(&buf))) < 0) + goto done; + } + + /** submodule is initialized - now clone it **/ + /* override repo creation */ + clone_options.repository_cb = git_submodule_update_repo_init_cb; + clone_options.repository_cb_payload = sm; + + /* + * Do not perform checkout as part of clone, instead we + * will checkout the specific commit manually. + */ + clone_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE; + + if ((error = git_clone(&sub_repo, submodule_url, sm->path, &clone_options)) < 0 || + (error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0 || + (error = git_checkout_head(sub_repo, &update_options.checkout_opts)) != 0) + goto done; + } else { + const git_oid *oid; + + /** + * Work dir is initialized - look up the commit in the parent repository's index, + * update the workdir contents of the subrepository, and set the subrepository's + * head to the new commit. + */ + if ((error = git_submodule_open(&sub_repo, sm)) < 0) + goto done; + + if ((oid = git_submodule_index_id(sm)) == NULL) { + git_error_set(GIT_ERROR_SUBMODULE, "could not get ID of submodule in index"); + error = -1; + goto done; + } + + /* Look up the target commit in the submodule. */ + if ((error = git_object_lookup(&target_commit, sub_repo, oid, GIT_OBJECT_COMMIT)) < 0) { + /* If it isn't found then fetch and try again. */ + if (error != GIT_ENOTFOUND || !update_options.allow_fetch || + (error = lookup_default_remote(&remote, sub_repo)) < 0 || + (error = git_remote_fetch(remote, NULL, &update_options.fetch_opts, NULL)) < 0 || + (error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJECT_COMMIT)) < 0) + goto done; + } + + if ((error = git_checkout_tree(sub_repo, target_commit, &update_options.checkout_opts)) != 0 || + (error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0) + goto done; + + /* Invalidate the wd flags as the workdir has been updated. */ + sm->flags = sm->flags & + ~(GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_OID_VALID | + GIT_SUBMODULE_STATUS__WD_SCANNED); + } + +done: + git_str_dispose(&buf); + git_config_free(config); + git_object_free(target_commit); + git_remote_free(remote); + git_repository_free(sub_repo); + + return error; +} + +int git_submodule_init(git_submodule *sm, int overwrite) +{ + int error; + const char *val; + git_str key = GIT_STR_INIT, effective_submodule_url = GIT_STR_INIT; + git_config *cfg = NULL; + + if (!sm->url) { + git_error_set(GIT_ERROR_SUBMODULE, + "no URL configured for submodule '%s'", sm->name); + return -1; + } + + if ((error = git_repository_config(&cfg, sm->repo)) < 0) + return error; + + /* write "submodule.NAME.url" */ + + if ((error = git_submodule__resolve_url(&effective_submodule_url, sm->repo, sm->url)) < 0 || + (error = git_str_printf(&key, "submodule.%s.url", sm->name)) < 0 || + (error = git_config__update_entry( + cfg, key.ptr, effective_submodule_url.ptr, overwrite != 0, false)) < 0) + goto cleanup; + + /* write "submodule.NAME.update" if not default */ + + val = (sm->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ? + NULL : submodule_update_to_str(sm->update); + + if ((error = git_str_printf(&key, "submodule.%s.update", sm->name)) < 0 || + (error = git_config__update_entry( + cfg, key.ptr, val, overwrite != 0, false)) < 0) + goto cleanup; + + /* success */ + +cleanup: + git_config_free(cfg); + git_str_dispose(&key); + git_str_dispose(&effective_submodule_url); + + return error; +} + +int git_submodule_sync(git_submodule *sm) +{ + git_str key = GIT_STR_INIT, url = GIT_STR_INIT, remote_name = GIT_STR_INIT; + git_repository *smrepo = NULL; + git_config *cfg = NULL; + int error = 0; + + if (!sm->url) { + git_error_set(GIT_ERROR_SUBMODULE, "no URL configured for submodule '%s'", sm->name); + return -1; + } + + /* copy URL over to config only if it already exists */ + if ((error = git_repository_config__weakptr(&cfg, sm->repo)) < 0 || + (error = git_str_printf(&key, "submodule.%s.url", sm->name)) < 0 || + (error = git_submodule__resolve_url(&url, sm->repo, sm->url)) < 0 || + (error = git_config__update_entry(cfg, key.ptr, url.ptr, true, true)) < 0) + goto out; + + if (!(sm->flags & GIT_SUBMODULE_STATUS_IN_WD)) + goto out; + + /* if submodule exists in the working directory, update remote url */ + if ((error = git_submodule_open(&smrepo, sm)) < 0 || + (error = git_repository_config__weakptr(&cfg, smrepo)) < 0) + goto out; + + if (lookup_head_remote_key(&remote_name, smrepo) == 0) { + if ((error = git_str_join3(&key, '.', "remote", remote_name.ptr, "url")) < 0) + goto out; + } else if ((error = git_str_sets(&key, "remote.origin.url")) < 0) { + goto out; + } + + if ((error = git_config__update_entry(cfg, key.ptr, url.ptr, true, false)) < 0) + goto out; + +out: + git_repository_free(smrepo); + git_str_dispose(&remote_name); + git_str_dispose(&key); + git_str_dispose(&url); + return error; +} + +static int git_submodule__open( + git_repository **subrepo, git_submodule *sm, bool bare) +{ + int error; + git_str path = GIT_STR_INIT; + unsigned int flags = GIT_REPOSITORY_OPEN_NO_SEARCH; + const char *wd; + + GIT_ASSERT_ARG(sm); + GIT_ASSERT_ARG(subrepo); + + if (git_repository__ensure_not_bare( + sm->repo, "open submodule repository") < 0) + return GIT_EBAREREPO; + + wd = git_repository_workdir(sm->repo); + + if (git_str_join3(&path, '/', wd, sm->path, DOT_GIT) < 0) + return -1; + + sm->flags = sm->flags & + ~(GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_OID_VALID | + GIT_SUBMODULE_STATUS__WD_SCANNED); + + if (bare) + flags |= GIT_REPOSITORY_OPEN_BARE; + + error = git_repository_open_ext(subrepo, path.ptr, flags, wd); + + /* if we opened the submodule successfully, grab HEAD OID, etc. */ + if (!error) { + sm->flags |= GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_SCANNED; + + if (!git_reference_name_to_id(&sm->wd_oid, *subrepo, GIT_HEAD_FILE)) + sm->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; + else + git_error_clear(); + } else if (git_fs_path_exists(path.ptr)) { + sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED | + GIT_SUBMODULE_STATUS_IN_WD; + } else { + git_str_rtruncate_at_char(&path, '/'); /* remove "/.git" */ + + if (git_fs_path_isdir(path.ptr)) + sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; + } + + git_str_dispose(&path); + + return error; +} + +int git_submodule_open_bare(git_repository **subrepo, git_submodule *sm) +{ + return git_submodule__open(subrepo, sm, true); +} + +int git_submodule_open(git_repository **subrepo, git_submodule *sm) +{ + return git_submodule__open(subrepo, sm, false); +} + +static void submodule_update_from_index_entry( + git_submodule *sm, const git_index_entry *ie) +{ + bool already_found = (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) != 0; + + if (!S_ISGITLINK(ie->mode)) { + if (!already_found) + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; + } else { + if (already_found) + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES; + else + git_oid_cpy(&sm->index_oid, &ie->id); + + sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS__INDEX_OID_VALID; + } +} + +static int submodule_update_index(git_submodule *sm) +{ + git_index *index; + const git_index_entry *ie; + + if (git_repository_index__weakptr(&index, sm->repo) < 0) + return -1; + + sm->flags = sm->flags & + ~(GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS__INDEX_OID_VALID); + + if (!(ie = git_index_get_bypath(index, sm->path, 0))) + return 0; + + submodule_update_from_index_entry(sm, ie); + + return 0; +} + +static void submodule_update_from_head_data( + git_submodule *sm, mode_t mode, const git_oid *id) +{ + if (!S_ISGITLINK(mode)) + sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; + else { + git_oid_cpy(&sm->head_oid, id); + + sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS__HEAD_OID_VALID; + } +} + +static int submodule_update_head(git_submodule *submodule) +{ + git_tree *head = NULL; + git_tree_entry *te = NULL; + + submodule->flags = submodule->flags & + ~(GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS__HEAD_OID_VALID); + + /* if we can't look up file in current head, then done */ + if (git_repository_head_tree(&head, submodule->repo) < 0 || + git_tree_entry_bypath(&te, head, submodule->path) < 0) + git_error_clear(); + else + submodule_update_from_head_data(submodule, te->attr, git_tree_entry_id(te)); + + git_tree_entry_free(te); + git_tree_free(head); + return 0; +} + +int git_submodule_reload(git_submodule *sm, int force) +{ + git_config *mods = NULL; + int error; + + GIT_UNUSED(force); + + GIT_ASSERT_ARG(sm); + + if ((error = git_submodule_name_is_valid(sm->repo, sm->name, 0)) <= 0) + /* This should come with a warning, but we've no API for that */ + goto out; + + if (git_repository_is_bare(sm->repo)) + goto out; + + /* refresh config data */ + if ((error = gitmodules_snapshot(&mods, sm->repo)) < 0 && error != GIT_ENOTFOUND) + goto out; + + if (mods != NULL && (error = submodule_read_config(sm, mods)) < 0) + goto out; + + /* refresh wd data */ + sm->flags &= + ~(GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_OID_VALID | + GIT_SUBMODULE_STATUS__WD_FLAGS); + + if ((error = submodule_load_from_wd_lite(sm)) < 0 || + (error = submodule_update_index(sm)) < 0 || + (error = submodule_update_head(sm)) < 0) + goto out; + +out: + git_config_free(mods); + return error; +} + +static void submodule_copy_oid_maybe( + git_oid *tgt, const git_oid *src, bool valid) +{ + if (tgt) { + if (valid) + memcpy(tgt, src, sizeof(*tgt)); + else + memset(tgt, 0, sizeof(*tgt)); + } +} + +int git_submodule__status( + unsigned int *out_status, + git_oid *out_head_id, + git_oid *out_index_id, + git_oid *out_wd_id, + git_submodule *sm, + git_submodule_ignore_t ign) +{ + unsigned int status; + git_repository *smrepo = NULL; + + if (ign == GIT_SUBMODULE_IGNORE_UNSPECIFIED) + ign = sm->ignore; + + /* only return location info if ignore == all */ + if (ign == GIT_SUBMODULE_IGNORE_ALL) { + *out_status = (sm->flags & GIT_SUBMODULE_STATUS__IN_FLAGS); + return 0; + } + + /* If the user has requested caching submodule state, performing these + * expensive operations (especially `submodule_update_head`, which is + * bottlenecked on `git_repository_head_tree`) eliminates much of the + * advantage. We will, therefore, interpret the request for caching to + * apply here to and skip them. + */ + + if (sm->repo->submodule_cache == NULL) { + /* refresh the index OID */ + if (submodule_update_index(sm) < 0) + return -1; + + /* refresh the HEAD OID */ + if (submodule_update_head(sm) < 0) + return -1; + } + + /* for ignore == dirty, don't scan the working directory */ + if (ign == GIT_SUBMODULE_IGNORE_DIRTY) { + /* git_submodule_open_bare will load WD OID data */ + if (git_submodule_open_bare(&smrepo, sm) < 0) + git_error_clear(); + else + git_repository_free(smrepo); + smrepo = NULL; + } else if (git_submodule_open(&smrepo, sm) < 0) { + git_error_clear(); + smrepo = NULL; + } + + status = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(sm->flags); + + submodule_get_index_status(&status, sm); + submodule_get_wd_status(&status, sm, smrepo, ign); + + git_repository_free(smrepo); + + *out_status = status; + + submodule_copy_oid_maybe(out_head_id, &sm->head_oid, + (sm->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) != 0); + submodule_copy_oid_maybe(out_index_id, &sm->index_oid, + (sm->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) != 0); + submodule_copy_oid_maybe(out_wd_id, &sm->wd_oid, + (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) != 0); + + return 0; +} + +int git_submodule_status(unsigned int *status, git_repository *repo, const char *name, git_submodule_ignore_t ignore) +{ + git_submodule *sm; + int error; + + GIT_ASSERT_ARG(status); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + if ((error = git_submodule_lookup(&sm, repo, name)) < 0) + return error; + + error = git_submodule__status(status, NULL, NULL, NULL, sm, ignore); + git_submodule_free(sm); + + return error; +} + +int git_submodule_location(unsigned int *location, git_submodule *sm) +{ + GIT_ASSERT_ARG(location); + GIT_ASSERT_ARG(sm); + + return git_submodule__status( + location, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_ALL); +} + +/* + * INTERNAL FUNCTIONS + */ + +static int submodule_alloc( + git_submodule **out, git_repository *repo, const char *name) +{ + size_t namelen; + git_submodule *sm; + + if (!name || !(namelen = strlen(name))) { + git_error_set(GIT_ERROR_SUBMODULE, "invalid submodule name"); + return -1; + } + + sm = git__calloc(1, sizeof(git_submodule)); + GIT_ERROR_CHECK_ALLOC(sm); + + sm->name = sm->path = git__strdup(name); + if (!sm->name) { + git__free(sm); + return -1; + } + + GIT_REFCOUNT_INC(sm); + sm->ignore = sm->ignore_default = GIT_SUBMODULE_IGNORE_NONE; + sm->update = sm->update_default = GIT_SUBMODULE_UPDATE_CHECKOUT; + sm->fetch_recurse = sm->fetch_recurse_default = GIT_SUBMODULE_RECURSE_NO; + sm->repo = repo; + sm->branch = NULL; + + *out = sm; + return 0; +} + +static void submodule_release(git_submodule *sm) +{ + if (!sm) + return; + + if (sm->repo) { + sm->repo = NULL; + } + + if (sm->path != sm->name) + git__free(sm->path); + git__free(sm->name); + git__free(sm->url); + git__free(sm->branch); + git__memzero(sm, sizeof(*sm)); + git__free(sm); +} + +int git_submodule_dup(git_submodule **out, git_submodule *source) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(source); + + GIT_REFCOUNT_INC(source); + + *out = source; + return 0; +} + +void git_submodule_free(git_submodule *sm) +{ + if (!sm) + return; + GIT_REFCOUNT_DEC(sm, submodule_release); +} + +static int submodule_config_error(const char *property, const char *value) +{ + git_error_set(GIT_ERROR_INVALID, + "invalid value for submodule '%s' property: '%s'", property, value); + return -1; +} + +int git_submodule_parse_ignore(git_submodule_ignore_t *out, const char *value) +{ + int val; + + if (git_config_lookup_map_value( + &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) { + *out = GIT_SUBMODULE_IGNORE_NONE; + return submodule_config_error("ignore", value); + } + + *out = (git_submodule_ignore_t)val; + return 0; +} + +int git_submodule_parse_update(git_submodule_update_t *out, const char *value) +{ + int val; + + if (git_config_lookup_map_value( + &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) { + *out = GIT_SUBMODULE_UPDATE_CHECKOUT; + return submodule_config_error("update", value); + } + + *out = (git_submodule_update_t)val; + return 0; +} + +static int submodule_parse_recurse(git_submodule_recurse_t *out, const char *value) +{ + int val; + + if (git_config_lookup_map_value( + &val, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), value) < 0) { + *out = GIT_SUBMODULE_RECURSE_YES; + return submodule_config_error("recurse", value); + } + + *out = (git_submodule_recurse_t)val; + return 0; +} + +static int get_value(const char **out, git_config *cfg, git_str *buf, const char *name, const char *field) +{ + int error; + + git_str_clear(buf); + + if ((error = git_str_printf(buf, "submodule.%s.%s", name, field)) < 0 || + (error = git_config_get_string(out, cfg, buf->ptr)) < 0) + return error; + + return error; +} + +static bool looks_like_command_line_option(const char *s) +{ + if (s && s[0] == '-') + return true; + + return false; +} + +static int submodule_read_config(git_submodule *sm, git_config *cfg) +{ + git_str key = GIT_STR_INIT; + const char *value; + int error, in_config = 0; + + /* + * TODO: Look up path in index and if it is present but not a GITLINK + * then this should be deleted (at least to match git's behavior) + */ + + if ((error = get_value(&value, cfg, &key, sm->name, "path")) == 0) { + in_config = 1; + /* We would warn here if we had that API */ + if (!looks_like_command_line_option(value)) { + /* + * TODO: if case insensitive filesystem, then the following strcmp + * should be strcasecmp + */ + if (strcmp(sm->name, value) != 0) { + if (sm->path != sm->name) + git__free(sm->path); + sm->path = git__strdup(value); + GIT_ERROR_CHECK_ALLOC(sm->path); + } + + } + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "url")) == 0) { + /* We would warn here if we had that API */ + if (!looks_like_command_line_option(value)) { + in_config = 1; + sm->url = git__strdup(value); + GIT_ERROR_CHECK_ALLOC(sm->url); + } + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "branch")) == 0) { + in_config = 1; + sm->branch = git__strdup(value); + GIT_ERROR_CHECK_ALLOC(sm->branch); + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "update")) == 0) { + in_config = 1; + if ((error = git_submodule_parse_update(&sm->update, value)) < 0) + goto cleanup; + sm->update_default = sm->update; + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "fetchRecurseSubmodules")) == 0) { + in_config = 1; + if ((error = submodule_parse_recurse(&sm->fetch_recurse, value)) < 0) + goto cleanup; + sm->fetch_recurse_default = sm->fetch_recurse; + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if ((error = get_value(&value, cfg, &key, sm->name, "ignore")) == 0) { + in_config = 1; + if ((error = git_submodule_parse_ignore(&sm->ignore, value)) < 0) + goto cleanup; + sm->ignore_default = sm->ignore; + } else if (error != GIT_ENOTFOUND) { + goto cleanup; + } + + if (in_config) + sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; + + error = 0; + +cleanup: + git_str_dispose(&key); + return error; +} + +static int submodule_load_each(const git_config_entry *entry, void *payload) +{ + lfc_data *data = payload; + const char *namestart, *property; + git_strmap *map = data->map; + git_str name = GIT_STR_INIT; + git_submodule *sm; + int error, isvalid; + + if (git__prefixcmp(entry->name, "submodule.") != 0) + return 0; + + namestart = entry->name + strlen("submodule."); + property = strrchr(namestart, '.'); + + if (!property || (property == namestart)) + return 0; + + property++; + + if ((error = git_str_set(&name, namestart, property - namestart -1)) < 0) + return error; + + isvalid = git_submodule_name_is_valid(data->repo, name.ptr, 0); + if (isvalid <= 0) { + error = isvalid; + goto done; + } + + /* + * Now that we have the submodule's name, we can use that to + * figure out whether it's in the map. If it's not, we create + * a new submodule, load the config and insert it. If it's + * already inserted, we've already loaded it, so we skip. + */ + if (git_strmap_exists(map, name.ptr)) { + error = 0; + goto done; + } + + if ((error = submodule_alloc(&sm, data->repo, name.ptr)) < 0) + goto done; + + if ((error = submodule_read_config(sm, data->mods)) < 0) { + git_submodule_free(sm); + goto done; + } + + if ((error = git_strmap_set(map, sm->name, sm)) < 0) + goto done; + + error = 0; + +done: + git_str_dispose(&name); + return error; +} + +static int submodule_load_from_wd_lite(git_submodule *sm) +{ + git_str path = GIT_STR_INIT; + + if (git_repository_workdir_path(&path, sm->repo, sm->path) < 0) + return -1; + + if (git_fs_path_isdir(path.ptr)) + sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; + + if (git_fs_path_contains(&path, DOT_GIT)) + sm->flags |= GIT_SUBMODULE_STATUS_IN_WD; + + git_str_dispose(&path); + return 0; +} + +/** + * Requests a snapshot of $WORK_TREE/.gitmodules. + * + * Returns GIT_ENOTFOUND in case no .gitmodules file exist + */ +static int gitmodules_snapshot(git_config **snap, git_repository *repo) +{ + git_config *mods = NULL; + git_str path = GIT_STR_INIT; + int error; + + if (git_repository_workdir(repo) == NULL) + return GIT_ENOTFOUND; + + if ((error = git_repository_workdir_path(&path, repo, GIT_MODULES_FILE)) < 0) + return error; + + if ((error = git_config_open_ondisk(&mods, path.ptr)) < 0) + goto cleanup; + git_str_dispose(&path); + + if ((error = git_config_snapshot(snap, mods)) < 0) + goto cleanup; + + error = 0; + +cleanup: + if (mods) + git_config_free(mods); + git_str_dispose(&path); + + return error; +} + +static git_config_backend *open_gitmodules( + git_repository *repo, + int okay_to_create) +{ + git_str path = GIT_STR_INIT; + git_config_backend *mods = NULL; + + if (git_repository_workdir(repo) != NULL) { + if (git_repository_workdir_path(&path, repo, GIT_MODULES_FILE) != 0) + return NULL; + + if (okay_to_create || git_fs_path_isfile(path.ptr)) { + /* git_config_backend_from_file should only fail if OOM */ + if (git_config_backend_from_file(&mods, path.ptr) < 0) + mods = NULL; + /* open should only fail here if the file is malformed */ + else if (git_config_backend_open(mods, GIT_CONFIG_LEVEL_LOCAL, repo) < 0) { + git_config_backend_free(mods); + mods = NULL; + } + } + } + + git_str_dispose(&path); + + return mods; +} + +/* Lookup name of remote of the local tracking branch HEAD points to */ +static int lookup_head_remote_key(git_str *remote_name, git_repository *repo) +{ + int error; + git_reference *head = NULL; + git_str upstream_name = GIT_STR_INIT; + + /* lookup and dereference HEAD */ + if ((error = git_repository_head(&head, repo)) < 0) + return error; + + /** + * If head does not refer to a branch, then return + * GIT_ENOTFOUND to indicate that we could not find + * a remote key for the local tracking branch HEAD points to. + **/ + if (!git_reference_is_branch(head)) { + git_error_set(GIT_ERROR_INVALID, + "HEAD does not refer to a branch."); + error = GIT_ENOTFOUND; + goto done; + } + + /* lookup remote tracking branch of HEAD */ + if ((error = git_branch__upstream_name( + &upstream_name, + repo, + git_reference_name(head))) < 0) + goto done; + + /* lookup remote of remote tracking branch */ + if ((error = git_branch__remote_name(remote_name, repo, upstream_name.ptr)) < 0) + goto done; + +done: + git_str_dispose(&upstream_name); + git_reference_free(head); + + return error; +} + +/* Lookup the remote of the local tracking branch HEAD points to */ +static int lookup_head_remote(git_remote **remote, git_repository *repo) +{ + int error; + git_str remote_name = GIT_STR_INIT; + + /* lookup remote of remote tracking branch name */ + if (!(error = lookup_head_remote_key(&remote_name, repo))) + error = git_remote_lookup(remote, repo, remote_name.ptr); + + git_str_dispose(&remote_name); + + return error; +} + +/* Lookup remote, either from HEAD or fall back on origin */ +static int lookup_default_remote(git_remote **remote, git_repository *repo) +{ + int error = lookup_head_remote(remote, repo); + + /* if that failed, use 'origin' instead */ + if (error == GIT_ENOTFOUND || error == GIT_EUNBORNBRANCH) + error = git_remote_lookup(remote, repo, "origin"); + + if (error == GIT_ENOTFOUND) + git_error_set( + GIT_ERROR_SUBMODULE, + "cannot get default remote for submodule - no local tracking " + "branch for HEAD and origin does not exist"); + + return error; +} + +static int get_url_base(git_str *url, git_repository *repo) +{ + int error; + git_worktree *wt = NULL; + git_remote *remote = NULL; + + if ((error = lookup_default_remote(&remote, repo)) == 0) { + error = git_str_sets(url, git_remote_url(remote)); + goto out; + } else if (error != GIT_ENOTFOUND) + goto out; + else + git_error_clear(); + + /* if repository does not have a default remote, use workdir instead */ + if (git_repository_is_worktree(repo)) { + if ((error = git_worktree_open_from_repository(&wt, repo)) < 0) + goto out; + error = git_str_sets(url, wt->parent_path); + } else { + error = git_str_sets(url, git_repository_workdir(repo)); + } + +out: + git_remote_free(remote); + git_worktree_free(wt); + + return error; +} + +static void submodule_get_index_status(unsigned int *status, git_submodule *sm) +{ + const git_oid *head_oid = git_submodule_head_id(sm); + const git_oid *index_oid = git_submodule_index_id(sm); + + *status = *status & ~GIT_SUBMODULE_STATUS__INDEX_FLAGS; + + if (!head_oid) { + if (index_oid) + *status |= GIT_SUBMODULE_STATUS_INDEX_ADDED; + } + else if (!index_oid) + *status |= GIT_SUBMODULE_STATUS_INDEX_DELETED; + else if (!git_oid_equal(head_oid, index_oid)) + *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED; +} + + +static void submodule_get_wd_status( + unsigned int *status, + git_submodule *sm, + git_repository *sm_repo, + git_submodule_ignore_t ign) +{ + const git_oid *index_oid = git_submodule_index_id(sm); + const git_oid *wd_oid = + (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) ? &sm->wd_oid : NULL; + git_tree *sm_head = NULL; + git_index *index = NULL; + git_diff_options opt = GIT_DIFF_OPTIONS_INIT; + git_diff *diff; + + *status = *status & ~GIT_SUBMODULE_STATUS__WD_FLAGS; + + if (!index_oid) { + if (wd_oid) + *status |= GIT_SUBMODULE_STATUS_WD_ADDED; + } + else if (!wd_oid) { + if ((sm->flags & GIT_SUBMODULE_STATUS__WD_SCANNED) != 0 && + (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) + *status |= GIT_SUBMODULE_STATUS_WD_UNINITIALIZED; + else + *status |= GIT_SUBMODULE_STATUS_WD_DELETED; + } + else if (!git_oid_equal(index_oid, wd_oid)) + *status |= GIT_SUBMODULE_STATUS_WD_MODIFIED; + + /* if we have no repo, then we're done */ + if (!sm_repo) + return; + + /* the diffs below could be optimized with an early termination + * option to the git_diff functions, but for now this is sufficient + * (and certainly no worse that what core git does). + */ + + if (ign == GIT_SUBMODULE_IGNORE_NONE) + opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + + (void)git_repository_index__weakptr(&index, sm_repo); + + /* if we don't have an unborn head, check diff with index */ + if (git_repository_head_tree(&sm_head, sm_repo) < 0) + git_error_clear(); + else { + /* perform head to index diff on submodule */ + if (git_diff_tree_to_index(&diff, sm_repo, sm_head, index, &opt) < 0) + git_error_clear(); + else { + if (git_diff_num_deltas(diff) > 0) + *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED; + git_diff_free(diff); + diff = NULL; + } + + git_tree_free(sm_head); + } + + /* perform index-to-workdir diff on submodule */ + if (git_diff_index_to_workdir(&diff, sm_repo, index, &opt) < 0) + git_error_clear(); + else { + size_t untracked = + git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED); + + if (untracked > 0) + *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; + + if (git_diff_num_deltas(diff) != untracked) + *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; + + git_diff_free(diff); + diff = NULL; + } +} diff --git a/src/libgit2/submodule.h b/src/libgit2/submodule.h new file mode 100644 index 000000000..7fa982486 --- /dev/null +++ b/src/libgit2/submodule.h @@ -0,0 +1,164 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_submodule_h__ +#define INCLUDE_submodule_h__ + +#include "common.h" + +#include "git2/submodule.h" +#include "git2/repository.h" +#include "futils.h" + +/* Notes: + * + * Submodule information can be in four places: the index, the config files + * (both .git/config and .gitmodules), the HEAD tree, and the working + * directory. + * + * In the index: + * - submodule is found by path + * - may be missing, present, or of the wrong type + * - will have an oid if present + * + * In the HEAD tree: + * - submodule is found by path + * - may be missing, present, or of the wrong type + * - will have an oid if present + * + * In the config files: + * - submodule is found by submodule "name" which is usually the path + * - may be missing or present + * - will have a name, path, url, and other properties + * + * In the working directory: + * - submodule is found by path + * - may be missing, an empty directory, a checked out directory, + * or of the wrong type + * - if checked out, will have a HEAD oid + * - if checked out, will have git history that can be used to compare oids + * - if checked out, may have modified files and/or untracked files + */ + +/** + * Description of submodule + * + * This record describes a submodule found in a repository. There should be + * an entry for every submodule found in the HEAD and index, and for every + * submodule described in .gitmodules. The fields are as follows: + * + * - `rc` tracks the refcount of how many hash table entries in the + * git_submodule_cache there are for this submodule. It only comes into + * play if the name and path of the submodule differ. + * + * - `name` is the name of the submodule from .gitmodules. + * - `path` is the path to the submodule from the repo root. It is almost + * always the same as `name`. + * - `url` is the url for the submodule. + * - `update` is a git_submodule_update_t value - see gitmodules(5) update. + * - `update_default` is the update value from the config + * - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore. + * - `ignore_default` is the ignore value from the config + * - `fetch_recurse` is a git_submodule_recurse_t value - see gitmodules(5) + * fetchRecurseSubmodules. + * - `fetch_recurse_default` is the recurse value from the config + * + * - `repo` is the parent repository that contains this submodule. + * - `flags` after for internal use, tracking where this submodule has been + * found (head, index, config, workdir) and known status info, etc. + * - `head_oid` is the SHA1 for the submodule path in the repo HEAD. + * - `index_oid` is the SHA1 for the submodule recorded in the index. + * - `wd_oid` is the SHA1 for the HEAD of the checked out submodule. + * + * If the submodule has been added to .gitmodules but not yet git added, + * then the `index_oid` will be zero but still marked valid. If the + * submodule has been deleted, but the delete has not been committed yet, + * then the `index_oid` will be set, but the `url` will be NULL. + */ +struct git_submodule { + git_refcount rc; + + /* information from config */ + char *name; + char *path; /* important: may just point to "name" string */ + char *url; + char *branch; + git_submodule_update_t update; + git_submodule_update_t update_default; + git_submodule_ignore_t ignore; + git_submodule_ignore_t ignore_default; + git_submodule_recurse_t fetch_recurse; + git_submodule_recurse_t fetch_recurse_default; + + /* internal information */ + git_repository *repo; + uint32_t flags; + git_oid head_oid; + git_oid index_oid; + git_oid wd_oid; +}; + +/* Additional flags on top of public GIT_SUBMODULE_STATUS values */ +enum { + GIT_SUBMODULE_STATUS__WD_SCANNED = (1u << 20), + GIT_SUBMODULE_STATUS__HEAD_OID_VALID = (1u << 21), + GIT_SUBMODULE_STATUS__INDEX_OID_VALID = (1u << 22), + GIT_SUBMODULE_STATUS__WD_OID_VALID = (1u << 23), + GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE = (1u << 24), + GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE = (1u << 25), + GIT_SUBMODULE_STATUS__WD_NOT_SUBMODULE = (1u << 26), + GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES = (1u << 27) +}; + +#define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \ + ((S) & ~(0xFFFFFFFFu << 20)) + +/* Initialize an external submodule cache for the provided repo. */ +extern int git_submodule_cache_init(git_strmap **out, git_repository *repo); + +/* Release the resources of the submodule cache. */ +extern int git_submodule_cache_free(git_strmap *cache); + +/* Submodule lookup with an explicit cache */ +extern int git_submodule__lookup_with_cache( + git_submodule **out, git_repository *repo, const char *path, git_strmap *cache); + +/* Internal status fn returns status and optionally the various OIDs */ +extern int git_submodule__status( + unsigned int *out_status, + git_oid *out_head_id, + git_oid *out_index_id, + git_oid *out_wd_id, + git_submodule *sm, + git_submodule_ignore_t ign); + +/* Open submodule repository as bare repo for quick HEAD check, etc. */ +extern int git_submodule_open_bare( + git_repository **repo, + git_submodule *submodule); + +extern int git_submodule_parse_ignore( + git_submodule_ignore_t *out, const char *value); +extern int git_submodule_parse_update( + git_submodule_update_t *out, const char *value); + +extern int git_submodule__map( + git_repository *repo, + git_strmap *map); + +/** + * Check whether a submodule's name is valid. + * + * Check the path against the path validity rules, either the filesystem + * defaults (like checkout does) or whichever you want to compare against. + * + * @param repo the repository which contains the submodule + * @param name the name to check + * @param flags the `GIT_PATH` flags to use for the check (0 to use filesystem defaults) + */ +extern int git_submodule_name_is_valid(git_repository *repo, const char *name, int flags); + +#endif diff --git a/src/libgit2/sysdir.c b/src/libgit2/sysdir.c new file mode 100644 index 000000000..450cb509b --- /dev/null +++ b/src/libgit2/sysdir.c @@ -0,0 +1,363 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "sysdir.h" + +#include "runtime.h" +#include "str.h" +#include "fs_path.h" +#include +#if GIT_WIN32 +#include "win32/findfile.h" +#else +#include +#include +#endif + +static int git_sysdir_guess_programdata_dirs(git_str *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_programdata_dirs(out); +#else + git_str_clear(out); + return 0; +#endif +} + +static int git_sysdir_guess_system_dirs(git_str *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_system_dirs(out, "etc"); +#else + return git_str_sets(out, "/etc"); +#endif +} + +#ifndef GIT_WIN32 +static int get_passwd_home(git_str *out, uid_t uid) +{ + struct passwd pwd, *pwdptr; + char *buf = NULL; + long buflen; + int error; + + GIT_ASSERT_ARG(out); + + if ((buflen = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) + buflen = 1024; + + do { + buf = git__realloc(buf, buflen); + error = getpwuid_r(uid, &pwd, buf, buflen, &pwdptr); + buflen *= 2; + } while (error == ERANGE && buflen <= 8192); + + if (error) { + git_error_set(GIT_ERROR_OS, "failed to get passwd entry"); + goto out; + } + + if (!pwdptr) { + git_error_set(GIT_ERROR_OS, "no passwd entry found for user"); + goto out; + } + + if ((error = git_str_puts(out, pwdptr->pw_dir)) < 0) + goto out; + +out: + git__free(buf); + return error; +} +#endif + +static int git_sysdir_guess_global_dirs(git_str *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_global_dirs(out); +#else + int error; + uid_t uid, euid; + const char *sandbox_id; + + uid = getuid(); + euid = geteuid(); + + /** + * If APP_SANDBOX_CONTAINER_ID is set, we are running in a + * sandboxed environment on macOS. + */ + sandbox_id = getenv("APP_SANDBOX_CONTAINER_ID"); + + /* + * In case we are running setuid, use the configuration + * of the effective user. + * + * If we are running in a sandboxed environment on macOS, + * we have to get the HOME dir from the password entry file. + */ + if (!sandbox_id && uid == euid) + error = git__getenv(out, "HOME"); + else + error = get_passwd_home(out, euid); + + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + return error; +#endif +} + +static int git_sysdir_guess_xdg_dirs(git_str *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_xdg_dirs(out); +#else + git_str env = GIT_STR_INIT; + int error; + uid_t uid, euid; + + uid = getuid(); + euid = geteuid(); + + /* + * In case we are running setuid, only look up passwd + * directory of the effective user. + */ + if (uid == euid) { + if ((error = git__getenv(&env, "XDG_CONFIG_HOME")) == 0) + error = git_str_joinpath(out, env.ptr, "git"); + + if (error == GIT_ENOTFOUND && (error = git__getenv(&env, "HOME")) == 0) + error = git_str_joinpath(out, env.ptr, ".config/git"); + } else { + if ((error = get_passwd_home(&env, euid)) == 0) + error = git_str_joinpath(out, env.ptr, ".config/git"); + } + + if (error == GIT_ENOTFOUND) { + git_error_clear(); + error = 0; + } + + git_str_dispose(&env); + return error; +#endif +} + +static int git_sysdir_guess_template_dirs(git_str *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_system_dirs(out, "share/git-core/templates"); +#else + return git_str_sets(out, "/usr/share/git-core/templates"); +#endif +} + +struct git_sysdir__dir { + git_str buf; + int (*guess)(git_str *out); +}; + +static struct git_sysdir__dir git_sysdir__dirs[] = { + { GIT_STR_INIT, git_sysdir_guess_system_dirs }, + { GIT_STR_INIT, git_sysdir_guess_global_dirs }, + { GIT_STR_INIT, git_sysdir_guess_xdg_dirs }, + { GIT_STR_INIT, git_sysdir_guess_programdata_dirs }, + { GIT_STR_INIT, git_sysdir_guess_template_dirs }, +}; + +static void git_sysdir_global_shutdown(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(git_sysdir__dirs); ++i) + git_str_dispose(&git_sysdir__dirs[i].buf); +} + +int git_sysdir_global_init(void) +{ + size_t i; + int error = 0; + + for (i = 0; !error && i < ARRAY_SIZE(git_sysdir__dirs); i++) + error = git_sysdir__dirs[i].guess(&git_sysdir__dirs[i].buf); + + if (error) + return error; + + return git_runtime_shutdown_register(git_sysdir_global_shutdown); +} + +int git_sysdir_reset(void) +{ + size_t i; + int error = 0; + + for (i = 0; !error && i < ARRAY_SIZE(git_sysdir__dirs); ++i) { + git_str_dispose(&git_sysdir__dirs[i].buf); + error = git_sysdir__dirs[i].guess(&git_sysdir__dirs[i].buf); + } + + return error; +} + +static int git_sysdir_check_selector(git_sysdir_t which) +{ + if (which < ARRAY_SIZE(git_sysdir__dirs)) + return 0; + + git_error_set(GIT_ERROR_INVALID, "config directory selector out of range"); + return -1; +} + + +int git_sysdir_get(const git_str **out, git_sysdir_t which) +{ + GIT_ASSERT_ARG(out); + + *out = NULL; + + GIT_ERROR_CHECK_ERROR(git_sysdir_check_selector(which)); + + *out = &git_sysdir__dirs[which].buf; + return 0; +} + +#define PATH_MAGIC "$PATH" + +int git_sysdir_set(git_sysdir_t which, const char *search_path) +{ + const char *expand_path = NULL; + git_str merge = GIT_STR_INIT; + + GIT_ERROR_CHECK_ERROR(git_sysdir_check_selector(which)); + + if (search_path != NULL) + expand_path = strstr(search_path, PATH_MAGIC); + + /* reset the default if this path has been cleared */ + if (!search_path) + git_sysdir__dirs[which].guess(&git_sysdir__dirs[which].buf); + + /* if $PATH is not referenced, then just set the path */ + if (!expand_path) { + if (search_path) + git_str_sets(&git_sysdir__dirs[which].buf, search_path); + + goto done; + } + + /* otherwise set to join(before $PATH, old value, after $PATH) */ + if (expand_path > search_path) + git_str_set(&merge, search_path, expand_path - search_path); + + if (git_str_len(&git_sysdir__dirs[which].buf)) + git_str_join(&merge, GIT_PATH_LIST_SEPARATOR, + merge.ptr, git_sysdir__dirs[which].buf.ptr); + + expand_path += strlen(PATH_MAGIC); + if (*expand_path) + git_str_join(&merge, GIT_PATH_LIST_SEPARATOR, merge.ptr, expand_path); + + git_str_swap(&git_sysdir__dirs[which].buf, &merge); + git_str_dispose(&merge); + +done: + if (git_str_oom(&git_sysdir__dirs[which].buf)) + return -1; + + return 0; +} + +static int git_sysdir_find_in_dirlist( + git_str *path, + const char *name, + git_sysdir_t which, + const char *label) +{ + size_t len; + const char *scan, *next = NULL; + const git_str *syspath; + + GIT_ERROR_CHECK_ERROR(git_sysdir_get(&syspath, which)); + if (!syspath || !git_str_len(syspath)) + goto done; + + for (scan = git_str_cstr(syspath); scan; scan = next) { + /* find unescaped separator or end of string */ + for (next = scan; *next; ++next) { + if (*next == GIT_PATH_LIST_SEPARATOR && + (next <= scan || next[-1] != '\\')) + break; + } + + len = (size_t)(next - scan); + next = (*next ? next + 1 : NULL); + if (!len) + continue; + + GIT_ERROR_CHECK_ERROR(git_str_set(path, scan, len)); + if (name) + GIT_ERROR_CHECK_ERROR(git_str_joinpath(path, path->ptr, name)); + + if (git_fs_path_exists(path->ptr)) + return 0; + } + +done: + if (name) + git_error_set(GIT_ERROR_OS, "the %s file '%s' doesn't exist", label, name); + else + git_error_set(GIT_ERROR_OS, "the %s directory doesn't exist", label); + git_str_dispose(path); + return GIT_ENOTFOUND; +} + +int git_sysdir_find_system_file(git_str *path, const char *filename) +{ + return git_sysdir_find_in_dirlist( + path, filename, GIT_SYSDIR_SYSTEM, "system"); +} + +int git_sysdir_find_global_file(git_str *path, const char *filename) +{ + return git_sysdir_find_in_dirlist( + path, filename, GIT_SYSDIR_GLOBAL, "global"); +} + +int git_sysdir_find_xdg_file(git_str *path, const char *filename) +{ + return git_sysdir_find_in_dirlist( + path, filename, GIT_SYSDIR_XDG, "global/xdg"); +} + +int git_sysdir_find_programdata_file(git_str *path, const char *filename) +{ + return git_sysdir_find_in_dirlist( + path, filename, GIT_SYSDIR_PROGRAMDATA, "ProgramData"); +} + +int git_sysdir_find_template_dir(git_str *path) +{ + return git_sysdir_find_in_dirlist( + path, NULL, GIT_SYSDIR_TEMPLATE, "template"); +} + +int git_sysdir_expand_global_file(git_str *path, const char *filename) +{ + int error; + + if ((error = git_sysdir_find_global_file(path, NULL)) == 0) { + if (filename) + error = git_str_joinpath(path, path->ptr, filename); + } + + return error; +} diff --git a/src/libgit2/sysdir.h b/src/libgit2/sysdir.h new file mode 100644 index 000000000..568f27940 --- /dev/null +++ b/src/libgit2/sysdir.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_sysdir_h__ +#define INCLUDE_sysdir_h__ + +#include "common.h" + +#include "posix.h" +#include "str.h" + +/** + * Find a "global" file (i.e. one in a user's home directory). + * + * @param path buffer to write the full path into + * @param filename name of file to find in the home directory + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error + */ +extern int git_sysdir_find_global_file(git_str *path, const char *filename); + +/** + * Find an "XDG" file (i.e. one in user's XDG config path). + * + * @param path buffer to write the full path into + * @param filename name of file to find in the home directory + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error + */ +extern int git_sysdir_find_xdg_file(git_str *path, const char *filename); + +/** + * Find a "system" file (i.e. one shared for all users of the system). + * + * @param path buffer to write the full path into + * @param filename name of file to find in the home directory + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error + */ +extern int git_sysdir_find_system_file(git_str *path, const char *filename); + +/** + * Find a "ProgramData" file (i.e. one in %PROGRAMDATA%) + * + * @param path buffer to write the full path into + * @param filename name of file to find in the ProgramData directory + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error + */ +extern int git_sysdir_find_programdata_file(git_str *path, const char *filename); + +/** + * Find template directory. + * + * @param path buffer to write the full path into + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error + */ +extern int git_sysdir_find_template_dir(git_str *path); + +/** + * Expand the name of a "global" file (i.e. one in a user's home + * directory). Unlike `find_global_file` (above), this makes no + * attempt to check for the existence of the file, and is useful if + * you want the full path regardless of existence. + * + * @param path buffer to write the full path into + * @param filename name of file in the home directory + * @return 0 on success or -1 on error + */ +extern int git_sysdir_expand_global_file(git_str *path, const char *filename); + +typedef enum { + GIT_SYSDIR_SYSTEM = 0, + GIT_SYSDIR_GLOBAL = 1, + GIT_SYSDIR_XDG = 2, + GIT_SYSDIR_PROGRAMDATA = 3, + GIT_SYSDIR_TEMPLATE = 4, + GIT_SYSDIR__MAX = 5 +} git_sysdir_t; + +/** + * Configures global data for configuration file search paths. + * + * @return 0 on success, <0 on failure + */ +extern int git_sysdir_global_init(void); + +/** + * Get the search path for global/system/xdg files + * + * @param out pointer to git_str containing search path + * @param which which list of paths to return + * @return 0 on success, <0 on failure + */ +extern int git_sysdir_get(const git_str **out, git_sysdir_t which); + +/** + * Set search paths for global/system/xdg files + * + * The first occurrence of the magic string "$PATH" in the new value will + * be replaced with the old value of the search path. + * + * @param which Which search path to modify + * @param paths New search path (separated by GIT_PATH_LIST_SEPARATOR) + * @return 0 on success, <0 on failure (allocation error) + */ +extern int git_sysdir_set(git_sysdir_t which, const char *paths); + +/** + * Reset search paths for global/system/xdg files. + */ +extern int git_sysdir_reset(void); + +#endif diff --git a/src/libgit2/tag.c b/src/libgit2/tag.c new file mode 100644 index 000000000..5734106fa --- /dev/null +++ b/src/libgit2/tag.c @@ -0,0 +1,570 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "tag.h" + +#include "commit.h" +#include "signature.h" +#include "wildmatch.h" +#include "git2/object.h" +#include "git2/repository.h" +#include "git2/signature.h" +#include "git2/odb_backend.h" + +void git_tag__free(void *_tag) +{ + git_tag *tag = _tag; + git_signature_free(tag->tagger); + git__free(tag->message); + git__free(tag->tag_name); + git__free(tag); +} + +int git_tag_target(git_object **target, const git_tag *t) +{ + GIT_ASSERT_ARG(t); + return git_object_lookup(target, t->object.repo, &t->target, t->type); +} + +const git_oid *git_tag_target_id(const git_tag *t) +{ + GIT_ASSERT_ARG_WITH_RETVAL(t, NULL); + return &t->target; +} + +git_object_t git_tag_target_type(const git_tag *t) +{ + GIT_ASSERT_ARG_WITH_RETVAL(t, GIT_OBJECT_INVALID); + return t->type; +} + +const char *git_tag_name(const git_tag *t) +{ + GIT_ASSERT_ARG_WITH_RETVAL(t, NULL); + return t->tag_name; +} + +const git_signature *git_tag_tagger(const git_tag *t) +{ + return t->tagger; +} + +const char *git_tag_message(const git_tag *t) +{ + GIT_ASSERT_ARG_WITH_RETVAL(t, NULL); + return t->message; +} + +static int tag_error(const char *str) +{ + git_error_set(GIT_ERROR_TAG, "failed to parse tag: %s", str); + return GIT_EINVALID; +} + +static int tag_parse(git_tag *tag, const char *buffer, const char *buffer_end) +{ + static const char *tag_types[] = { + NULL, "commit\n", "tree\n", "blob\n", "tag\n" + }; + size_t text_len, alloc_len; + const char *search; + unsigned int i; + int error; + + if (git_oid__parse(&tag->target, &buffer, buffer_end, "object ") < 0) + return tag_error("object field invalid"); + + if (buffer + 5 >= buffer_end) + return tag_error("object too short"); + + if (memcmp(buffer, "type ", 5) != 0) + return tag_error("type field not found"); + buffer += 5; + + tag->type = GIT_OBJECT_INVALID; + + for (i = 1; i < ARRAY_SIZE(tag_types); ++i) { + size_t type_length = strlen(tag_types[i]); + + if (buffer + type_length >= buffer_end) + return tag_error("object too short"); + + if (memcmp(buffer, tag_types[i], type_length) == 0) { + tag->type = i; + buffer += type_length; + break; + } + } + + if (tag->type == GIT_OBJECT_INVALID) + return tag_error("invalid object type"); + + if (buffer + 4 >= buffer_end) + return tag_error("object too short"); + + if (memcmp(buffer, "tag ", 4) != 0) + return tag_error("tag field not found"); + + buffer += 4; + + search = memchr(buffer, '\n', buffer_end - buffer); + if (search == NULL) + return tag_error("object too short"); + + text_len = search - buffer; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, text_len, 1); + tag->tag_name = git__malloc(alloc_len); + GIT_ERROR_CHECK_ALLOC(tag->tag_name); + + memcpy(tag->tag_name, buffer, text_len); + tag->tag_name[text_len] = '\0'; + + buffer = search + 1; + + tag->tagger = NULL; + if (buffer < buffer_end && *buffer != '\n') { + tag->tagger = git__malloc(sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(tag->tagger); + + if ((error = git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ", '\n')) < 0) + return error; + } + + tag->message = NULL; + if (buffer < buffer_end) { + /* If we're not at the end of the header, search for it */ + if(*buffer != '\n') { + search = git__memmem(buffer, buffer_end - buffer, + "\n\n", 2); + if (search) + buffer = search + 1; + else + return tag_error("tag contains no message"); + } + + text_len = buffer_end - ++buffer; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, text_len, 1); + tag->message = git__malloc(alloc_len); + GIT_ERROR_CHECK_ALLOC(tag->message); + + memcpy(tag->message, buffer, text_len); + tag->message[text_len] = '\0'; + } + + return 0; +} + +int git_tag__parse_raw(void *_tag, const char *data, size_t size) +{ + return tag_parse(_tag, data, data + size); +} + +int git_tag__parse(void *_tag, git_odb_object *odb_obj) +{ + git_tag *tag = _tag; + const char *buffer = git_odb_object_data(odb_obj); + const char *buffer_end = buffer + git_odb_object_size(odb_obj); + + return tag_parse(tag, buffer, buffer_end); +} + +static int retrieve_tag_reference( + git_reference **tag_reference_out, + git_str *ref_name_out, + git_repository *repo, + const char *tag_name) +{ + git_reference *tag_ref; + int error; + + *tag_reference_out = NULL; + + if (git_str_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) + return -1; + + error = git_reference_lookup(&tag_ref, repo, ref_name_out->ptr); + if (error < 0) + return error; /* Be it not foundo or corrupted */ + + *tag_reference_out = tag_ref; + + return 0; +} + +static int retrieve_tag_reference_oid( + git_oid *oid, + git_str *ref_name_out, + git_repository *repo, + const char *tag_name) +{ + if (git_str_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) + return -1; + + return git_reference_name_to_id(oid, repo, ref_name_out->ptr); +} + +static int write_tag_annotation( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + const git_signature *tagger, + const char *message) +{ + git_str tag = GIT_STR_INIT; + git_odb *odb; + + git_oid__writebuf(&tag, "object ", git_object_id(target)); + git_str_printf(&tag, "type %s\n", git_object_type2string(git_object_type(target))); + git_str_printf(&tag, "tag %s\n", tag_name); + git_signature__writebuf(&tag, "tagger ", tagger); + git_str_putc(&tag, '\n'); + + if (git_str_puts(&tag, message) < 0) + goto on_error; + + if (git_repository_odb__weakptr(&odb, repo) < 0) + goto on_error; + + if (git_odb_write(oid, odb, tag.ptr, tag.size, GIT_OBJECT_TAG) < 0) + goto on_error; + + git_str_dispose(&tag); + return 0; + +on_error: + git_str_dispose(&tag); + git_error_set(GIT_ERROR_OBJECT, "failed to create tag annotation"); + return -1; +} + +static int git_tag_create__internal( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + const git_signature *tagger, + const char *message, + int allow_ref_overwrite, + int create_tag_annotation) +{ + git_reference *new_ref = NULL; + git_str ref_name = GIT_STR_INIT; + + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(tag_name); + GIT_ASSERT_ARG(target); + GIT_ASSERT_ARG(!create_tag_annotation || (tagger && message)); + + if (git_object_owner(target) != repo) { + git_error_set(GIT_ERROR_INVALID, "the given target does not belong to this repository"); + return -1; + } + + error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag_name); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; + + /** Ensure the tag name doesn't conflict with an already existing + * reference unless overwriting has explicitly been requested **/ + if (error == 0 && !allow_ref_overwrite) { + git_str_dispose(&ref_name); + git_error_set(GIT_ERROR_TAG, "tag already exists"); + return GIT_EEXISTS; + } + + if (create_tag_annotation) { + if (write_tag_annotation(oid, repo, tag_name, target, tagger, message) < 0) + return -1; + } else + git_oid_cpy(oid, git_object_id(target)); + + error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL); + +cleanup: + git_reference_free(new_ref); + git_str_dispose(&ref_name); + return error; +} + +int git_tag_create( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + const git_signature *tagger, + const char *message, + int allow_ref_overwrite) +{ + return git_tag_create__internal(oid, repo, tag_name, target, tagger, message, allow_ref_overwrite, 1); +} + +int git_tag_annotation_create( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + const git_signature *tagger, + const char *message) +{ + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(tag_name); + GIT_ASSERT_ARG(target); + GIT_ASSERT_ARG(tagger); + GIT_ASSERT_ARG(message); + + return write_tag_annotation(oid, repo, tag_name, target, tagger, message); +} + +int git_tag_create_lightweight( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + int allow_ref_overwrite) +{ + return git_tag_create__internal(oid, repo, tag_name, target, NULL, NULL, allow_ref_overwrite, 0); +} + +int git_tag_create_from_buffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite) +{ + git_tag tag; + int error; + git_odb *odb; + git_odb_stream *stream; + git_odb_object *target_obj; + + git_reference *new_ref = NULL; + git_str ref_name = GIT_STR_INIT; + + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(buffer); + + memset(&tag, 0, sizeof(tag)); + + if (git_repository_odb__weakptr(&odb, repo) < 0) + return -1; + + /* validate the buffer */ + if (tag_parse(&tag, buffer, buffer + strlen(buffer)) < 0) + return -1; + + /* validate the target */ + if (git_odb_read(&target_obj, odb, &tag.target) < 0) + goto on_error; + + if (tag.type != target_obj->cached.type) { + git_error_set(GIT_ERROR_TAG, "the type for the given target is invalid"); + goto on_error; + } + + error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag.tag_name); + if (error < 0 && error != GIT_ENOTFOUND) + goto on_error; + + /* We don't need these objects after this */ + git_signature_free(tag.tagger); + git__free(tag.tag_name); + git__free(tag.message); + git_odb_object_free(target_obj); + + /** Ensure the tag name doesn't conflict with an already existing + * reference unless overwriting has explicitly been requested **/ + if (error == 0 && !allow_ref_overwrite) { + git_error_set(GIT_ERROR_TAG, "tag already exists"); + return GIT_EEXISTS; + } + + /* write the buffer */ + if ((error = git_odb_open_wstream( + &stream, odb, strlen(buffer), GIT_OBJECT_TAG)) < 0) + return error; + + if (!(error = git_odb_stream_write(stream, buffer, strlen(buffer)))) + error = git_odb_stream_finalize_write(oid, stream); + + git_odb_stream_free(stream); + + if (error < 0) { + git_str_dispose(&ref_name); + return error; + } + + error = git_reference_create( + &new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL); + + git_reference_free(new_ref); + git_str_dispose(&ref_name); + + return error; + +on_error: + git_signature_free(tag.tagger); + git__free(tag.tag_name); + git__free(tag.message); + git_odb_object_free(target_obj); + return -1; +} + +int git_tag_delete(git_repository *repo, const char *tag_name) +{ + git_reference *tag_ref; + git_str ref_name = GIT_STR_INIT; + int error; + + error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name); + + git_str_dispose(&ref_name); + + if (error < 0) + return error; + + error = git_reference_delete(tag_ref); + + git_reference_free(tag_ref); + + return error; +} + +typedef struct { + git_repository *repo; + git_tag_foreach_cb cb; + void *cb_data; +} tag_cb_data; + +static int tags_cb(const char *ref, void *data) +{ + int error; + git_oid oid; + tag_cb_data *d = (tag_cb_data *)data; + + if (git__prefixcmp(ref, GIT_REFS_TAGS_DIR) != 0) + return 0; /* no tag */ + + if (!(error = git_reference_name_to_id(&oid, d->repo, ref))) { + if ((error = d->cb(ref, &oid, d->cb_data)) != 0) + git_error_set_after_callback_function(error, "git_tag_foreach"); + } + + return error; +} + +int git_tag_foreach(git_repository *repo, git_tag_foreach_cb cb, void *cb_data) +{ + tag_cb_data data; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(cb); + + data.cb = cb; + data.cb_data = cb_data; + data.repo = repo; + + return git_reference_foreach_name(repo, &tags_cb, &data); +} + +typedef struct { + git_vector *taglist; + const char *pattern; +} tag_filter_data; + +#define GIT_REFS_TAGS_DIR_LEN strlen(GIT_REFS_TAGS_DIR) + +static int tag_list_cb(const char *tag_name, git_oid *oid, void *data) +{ + tag_filter_data *filter = (tag_filter_data *)data; + GIT_UNUSED(oid); + + if (!*filter->pattern || + wildmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0) + { + char *matched = git__strdup(tag_name + GIT_REFS_TAGS_DIR_LEN); + GIT_ERROR_CHECK_ALLOC(matched); + + return git_vector_insert(filter->taglist, matched); + } + + return 0; +} + +int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_repository *repo) +{ + int error; + tag_filter_data filter; + git_vector taglist; + + GIT_ASSERT_ARG(tag_names); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(pattern); + + if ((error = git_vector_init(&taglist, 8, NULL)) < 0) + return error; + + filter.taglist = &taglist; + filter.pattern = pattern; + + error = git_tag_foreach(repo, &tag_list_cb, (void *)&filter); + + if (error < 0) + git_vector_free(&taglist); + + tag_names->strings = + (char **)git_vector_detach(&tag_names->count, NULL, &taglist); + + return 0; +} + +int git_tag_list(git_strarray *tag_names, git_repository *repo) +{ + return git_tag_list_match(tag_names, "", repo); +} + +int git_tag_peel(git_object **tag_target, const git_tag *tag) +{ + return git_object_peel(tag_target, (const git_object *)tag, GIT_OBJECT_ANY); +} + +int git_tag_name_is_valid(int *valid, const char *name) +{ + git_str ref_name = GIT_STR_INIT; + int error = 0; + + GIT_ASSERT(valid); + + *valid = 0; + + /* + * Discourage tag name starting with dash, + * https://github.com/git/git/commit/4f0accd638b8d2 + */ + if (!name || name[0] == '-') + goto done; + + if ((error = git_str_puts(&ref_name, GIT_REFS_TAGS_DIR)) < 0 || + (error = git_str_puts(&ref_name, name)) < 0) + goto done; + + error = git_reference_name_is_valid(valid, ref_name.ptr); + +done: + git_str_dispose(&ref_name); + return error; +} + +/* Deprecated Functions */ + +#ifndef GIT_DEPRECATE_HARD +int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite) +{ + return git_tag_create_from_buffer(oid, repo, buffer, allow_ref_overwrite); +} +#endif diff --git a/src/libgit2/tag.h b/src/libgit2/tag.h new file mode 100644 index 000000000..76ae1508e --- /dev/null +++ b/src/libgit2/tag.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_tag_h__ +#define INCLUDE_tag_h__ + +#include "common.h" + +#include "git2/tag.h" +#include "repository.h" +#include "odb.h" + +struct git_tag { + git_object object; + + git_oid target; + git_object_t type; + + char *tag_name; + git_signature *tagger; + char *message; +}; + +void git_tag__free(void *tag); +int git_tag__parse(void *tag, git_odb_object *obj); +int git_tag__parse_raw(void *tag, const char *data, size_t size); + +#endif diff --git a/src/libgit2/thread.c b/src/libgit2/thread.c new file mode 100644 index 000000000..3171771d7 --- /dev/null +++ b/src/libgit2/thread.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#if !defined(GIT_THREADS) + +#define TLSDATA_MAX 16 + +typedef struct { + void *value; + void (GIT_SYSTEM_CALL *destroy_fn)(void *); +} tlsdata_value; + +static tlsdata_value tlsdata_values[TLSDATA_MAX]; +static int tlsdata_cnt = 0; + +int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) +{ + if (tlsdata_cnt >= TLSDATA_MAX) + return -1; + + tlsdata_values[tlsdata_cnt].value = NULL; + tlsdata_values[tlsdata_cnt].destroy_fn = destroy_fn; + + *key = tlsdata_cnt; + tlsdata_cnt++; + + return 0; +} + +int git_tlsdata_set(git_tlsdata_key key, void *value) +{ + if (key < 0 || key > tlsdata_cnt) + return -1; + + tlsdata_values[key].value = value; + return 0; +} + +void *git_tlsdata_get(git_tlsdata_key key) +{ + if (key < 0 || key > tlsdata_cnt) + return NULL; + + return tlsdata_values[key].value; +} + +int git_tlsdata_dispose(git_tlsdata_key key) +{ + void *value; + void (*destroy_fn)(void *) = NULL; + + if (key < 0 || key > tlsdata_cnt) + return -1; + + value = tlsdata_values[key].value; + destroy_fn = tlsdata_values[key].destroy_fn; + + tlsdata_values[key].value = NULL; + tlsdata_values[key].destroy_fn = NULL; + + if (value && destroy_fn) + destroy_fn(value); + + return 0; +} + +#elif defined(GIT_WIN32) + +int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) +{ + DWORD fls_index = FlsAlloc(destroy_fn); + + if (fls_index == FLS_OUT_OF_INDEXES) + return -1; + + *key = fls_index; + return 0; +} + +int git_tlsdata_set(git_tlsdata_key key, void *value) +{ + if (!FlsSetValue(key, value)) + return -1; + + return 0; +} + +void *git_tlsdata_get(git_tlsdata_key key) +{ + return FlsGetValue(key); +} + +int git_tlsdata_dispose(git_tlsdata_key key) +{ + if (!FlsFree(key)) + return -1; + + return 0; +} + +#elif defined(_POSIX_THREADS) + +int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) +{ + if (pthread_key_create(key, destroy_fn) != 0) + return -1; + + return 0; +} + +int git_tlsdata_set(git_tlsdata_key key, void *value) +{ + if (pthread_setspecific(key, value) != 0) + return -1; + + return 0; +} + +void *git_tlsdata_get(git_tlsdata_key key) +{ + return pthread_getspecific(key); +} + +int git_tlsdata_dispose(git_tlsdata_key key) +{ + if (pthread_key_delete(key) != 0) + return -1; + + return 0; +} + +#else +# error unknown threading model +#endif diff --git a/src/libgit2/thread.h b/src/libgit2/thread.h new file mode 100644 index 000000000..4bbac9fd8 --- /dev/null +++ b/src/libgit2/thread.h @@ -0,0 +1,479 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_thread_h__ +#define INCLUDE_thread_h__ + +#if defined(GIT_THREADS) + +#if defined(__clang__) + +# if (__clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ < 1)) +# error Atomic primitives do not exist on this version of clang; configure libgit2 with -DUSE_THREADS=OFF +# else +# define GIT_BUILTIN_ATOMIC +# endif + +#elif defined(__GNUC__) + +# if (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 1)) +# error Atomic primitives do not exist on this version of gcc; configure libgit2 with -DUSE_THREADS=OFF +# elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) +# define GIT_BUILTIN_ATOMIC +# else +# define GIT_BUILTIN_SYNC +# endif + +#endif + +#endif /* GIT_THREADS */ + +/* Common operations even if threading has been disabled */ +typedef struct { +#if defined(GIT_WIN32) + volatile long val; +#else + volatile int val; +#endif +} git_atomic32; + +#ifdef GIT_ARCH_64 + +typedef struct { +#if defined(GIT_WIN32) + volatile __int64 val; +#else + volatile int64_t val; +#endif +} git_atomic64; + +typedef git_atomic64 git_atomic_ssize; + +#define git_atomic_ssize_set git_atomic64_set +#define git_atomic_ssize_add git_atomic64_add +#define git_atomic_ssize_get git_atomic64_get + +#else + +typedef git_atomic32 git_atomic_ssize; + +#define git_atomic_ssize_set git_atomic32_set +#define git_atomic_ssize_add git_atomic32_add +#define git_atomic_ssize_get git_atomic32_get + +#endif + +#ifdef GIT_THREADS + +#ifdef GIT_WIN32 +# include "win32/thread.h" +#else +# include "unix/pthread.h" +#endif + +/* + * Atomically sets the contents of *a to be val. + */ +GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) +{ +#if defined(GIT_WIN32) + InterlockedExchange(&a->val, (LONG)val); +#elif defined(GIT_BUILTIN_ATOMIC) + __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + __sync_lock_test_and_set(&a->val, val); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically increments the contents of *a by 1, and stores the result back into *a. + * @return the result of the operation. + */ +GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) +{ +#if defined(GIT_WIN32) + return InterlockedIncrement(&a->val); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_add_fetch(&a->val, 1, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_add_and_fetch(&a->val, 1); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically adds the contents of *a and addend, and stores the result back into *a. + * @return the result of the operation. + */ +GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) +{ +#if defined(GIT_WIN32) + return InterlockedAdd(&a->val, addend); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_add_and_fetch(&a->val, addend); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically decrements the contents of *a by 1, and stores the result back into *a. + * @return the result of the operation. + */ +GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) +{ +#if defined(GIT_WIN32) + return InterlockedDecrement(&a->val); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_sub_fetch(&a->val, 1, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_sub_and_fetch(&a->val, 1); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically gets the contents of *a. + * @return the contents of *a. + */ +GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) +{ +#if defined(GIT_WIN32) + return (int)InterlockedCompareExchange(&a->val, 0, 0); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_val_compare_and_swap(&a->val, 0, 0); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +GIT_INLINE(void *) git_atomic__compare_and_swap( + void * volatile *ptr, void *oldval, void *newval) +{ +#if defined(GIT_WIN32) + return InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); +#elif defined(GIT_BUILTIN_ATOMIC) + void *foundval = oldval; + __atomic_compare_exchange(ptr, &foundval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + return foundval; +#elif defined(GIT_BUILTIN_SYNC) + return __sync_val_compare_and_swap(ptr, oldval, newval); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +GIT_INLINE(volatile void *) git_atomic__swap( + void * volatile *ptr, void *newval) +{ +#if defined(GIT_WIN32) + return InterlockedExchangePointer(ptr, newval); +#elif defined(GIT_BUILTIN_ATOMIC) + void * foundval = NULL; + __atomic_exchange(ptr, &newval, &foundval, __ATOMIC_SEQ_CST); + return foundval; +#elif defined(GIT_BUILTIN_SYNC) + return (volatile void *)__sync_lock_test_and_set(ptr, newval); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) +{ +#if defined(GIT_WIN32) + void *newval = NULL, *oldval = NULL; + return (volatile void *)InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); +#elif defined(GIT_BUILTIN_ATOMIC) + return (volatile void *)__atomic_load_n(ptr, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return (volatile void *)__sync_val_compare_and_swap(ptr, 0, 0); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +#ifdef GIT_ARCH_64 + +/* + * Atomically adds the contents of *a and addend, and stores the result back into *a. + * @return the result of the operation. + */ +GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) +{ +#if defined(GIT_WIN32) + return InterlockedAdd64(&a->val, addend); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_add_and_fetch(&a->val, addend); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically sets the contents of *a to be val. + */ +GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) +{ +#if defined(GIT_WIN32) + InterlockedExchange64(&a->val, val); +#elif defined(GIT_BUILTIN_ATOMIC) + __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + __sync_lock_test_and_set(&a->val, val); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically gets the contents of *a. + * @return the contents of *a. + */ +GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) +{ +#if defined(GIT_WIN32) + return (int64_t)InterlockedCompareExchange64(&a->val, 0, 0); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_val_compare_and_swap(&a->val, 0, 0); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +#endif + +#else + +#define git_threads_global_init git__noop + +#define git_thread unsigned int +#define git_thread_create(thread, start_routine, arg) git__noop() +#define git_thread_join(id, status) git__noop() + +/* Pthreads Mutex */ +#define git_mutex unsigned int +#define git_mutex_init(a) git__noop() +#define git_mutex_init(a) git__noop() +#define git_mutex_lock(a) git__noop() +#define git_mutex_unlock(a) git__noop() +#define git_mutex_free(a) git__noop() + +/* Pthreads condition vars */ +#define git_cond unsigned int +#define git_cond_init(c) git__noop() +#define git_cond_free(c) git__noop() +#define git_cond_wait(c, l) git__noop() +#define git_cond_signal(c) git__noop() +#define git_cond_broadcast(c) git__noop() + +/* Pthreads rwlock */ +#define git_rwlock unsigned int +#define git_rwlock_init(a) git__noop() +#define git_rwlock_rdlock(a) git__noop() +#define git_rwlock_rdunlock(a) git__noop() +#define git_rwlock_wrlock(a) git__noop() +#define git_rwlock_wrunlock(a) git__noop() +#define git_rwlock_free(a) git__noop() +#define GIT_RWLOCK_STATIC_INIT 0 + + +GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) +{ + a->val = val; +} + +GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) +{ + return ++a->val; +} + +GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) +{ + a->val += addend; + return a->val; +} + +GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) +{ + return --a->val; +} + +GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) +{ + return (int)a->val; +} + +GIT_INLINE(void *) git_atomic__compare_and_swap( + void * volatile *ptr, void *oldval, void *newval) +{ + void *foundval = *ptr; + if (foundval == oldval) + *ptr = newval; + return foundval; +} + +GIT_INLINE(volatile void *) git_atomic__swap( + void * volatile *ptr, void *newval) +{ + volatile void *old = *ptr; + *ptr = newval; + return old; +} + +GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) +{ + return *ptr; +} + +#ifdef GIT_ARCH_64 + +GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) +{ + a->val += addend; + return a->val; +} + +GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) +{ + a->val = val; +} + +GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) +{ + return (int64_t)a->val; +} + +#endif + +#endif + +/* + * Atomically replace the contents of *ptr (if they are equal to oldval) with + * newval. ptr must point to a pointer or a value that is the same size as a + * pointer. This is semantically compatible with: + * + * #define git_atomic_compare_and_swap(ptr, oldval, newval) \ + * ({ \ + * void *foundval = *ptr; \ + * if (foundval == oldval) \ + * *ptr = newval; \ + * foundval; \ + * }) + * + * @return the original contents of *ptr. + */ +#define git_atomic_compare_and_swap(ptr, oldval, newval) \ + git_atomic__compare_and_swap((void * volatile *)ptr, oldval, newval) + +/* + * Atomically replace the contents of v with newval. v must be the same size as + * a pointer. This is semantically compatible with: + * + * #define git_atomic_swap(v, newval) \ + * ({ \ + * volatile void *old = v; \ + * v = newval; \ + * old; \ + * }) + * + * @return the original contents of v. + */ +#define git_atomic_swap(v, newval) \ + (void *)git_atomic__swap((void * volatile *)&(v), newval) + +/* + * Atomically reads the contents of v. v must be the same size as a pointer. + * This is semantically compatible with: + * + * #define git_atomic_load(v) v + * + * @return the contents of v. + */ +#define git_atomic_load(v) \ + (void *)git_atomic__load((void * volatile *)&(v)) + +#if defined(GIT_THREADS) + +# if defined(GIT_WIN32) +# define GIT_MEMORY_BARRIER MemoryBarrier() +# elif defined(GIT_BUILTIN_ATOMIC) +# define GIT_MEMORY_BARRIER __atomic_thread_fence(__ATOMIC_SEQ_CST) +# elif defined(GIT_BUILTIN_SYNC) +# define GIT_MEMORY_BARRIER __sync_synchronize() +# endif + +#else + +# define GIT_MEMORY_BARRIER /* noop */ + +#endif + +/* Thread-local data */ + +#if !defined(GIT_THREADS) +# define git_tlsdata_key int +#elif defined(GIT_WIN32) +# define git_tlsdata_key DWORD +#elif defined(_POSIX_THREADS) +# define git_tlsdata_key pthread_key_t +#else +# error unknown threading model +#endif + +/** + * Create a thread-local data key. The destroy function will be + * called upon thread exit. On some platforms, it may be called + * when all threads have deleted their keys. + * + * Note that the tlsdata functions do not set an error message on + * failure; this is because the error handling in libgit2 is itself + * handled by thread-local data storage. + * + * @param key the tlsdata key + * @param destroy_fn function pointer called upon thread exit + * @return 0 on success, non-zero on failure + */ +int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)); + +/** + * Set a the thread-local value for the given key. + * + * @param key the tlsdata key to store data on + * @param value the pointer to store + * @return 0 on success, non-zero on failure + */ +int git_tlsdata_set(git_tlsdata_key key, void *value); + +/** + * Get the thread-local value for the given key. + * + * @param key the tlsdata key to retrieve the value of + * @return the pointer stored with git_tlsdata_set + */ +void *git_tlsdata_get(git_tlsdata_key key); + +/** + * Delete the given thread-local key. + * + * @param key the tlsdata key to dispose + * @return 0 on success, non-zero on failure + */ +int git_tlsdata_dispose(git_tlsdata_key key); + +#endif diff --git a/src/libgit2/threadstate.c b/src/libgit2/threadstate.c new file mode 100644 index 000000000..9e3ef5818 --- /dev/null +++ b/src/libgit2/threadstate.c @@ -0,0 +1,84 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "threadstate.h" +#include "runtime.h" + +/** + * Handle the thread-local state + * + * `git_threadstate_global_init` will be called as part + * of `git_libgit2_init` (which itself must be called + * before calling any other function in the library). + * + * This function allocates a TLS index to store the per- + * thread state. + * + * Any internal method that requires thread-local state + * will then call `git_threadstate_get()` which returns a + * pointer to the thread-local state structure; this + * structure is lazily allocated on each thread. + * + * This mechanism will register a shutdown handler + * (`git_threadstate_global_shutdown`) which will free the + * TLS index. This shutdown handler will be called by + * `git_libgit2_shutdown`. + */ + +static git_tlsdata_key tls_key; + +static void threadstate_dispose(git_threadstate *threadstate) +{ + if (!threadstate) + return; + + if (threadstate->error_t.message != git_str__initstr) + git__free(threadstate->error_t.message); + threadstate->error_t.message = NULL; +} + +static void GIT_SYSTEM_CALL threadstate_free(void *threadstate) +{ + threadstate_dispose(threadstate); + git__free(threadstate); +} + +static void git_threadstate_global_shutdown(void) +{ + git_threadstate *threadstate; + + threadstate = git_tlsdata_get(tls_key); + git_tlsdata_set(tls_key, NULL); + + threadstate_dispose(threadstate); + git__free(threadstate); + + git_tlsdata_dispose(tls_key); +} + +int git_threadstate_global_init(void) +{ + if (git_tlsdata_init(&tls_key, &threadstate_free) != 0) + return -1; + + return git_runtime_shutdown_register(git_threadstate_global_shutdown); +} + +git_threadstate *git_threadstate_get(void) +{ + git_threadstate *threadstate; + + if ((threadstate = git_tlsdata_get(tls_key)) != NULL) + return threadstate; + + if ((threadstate = git__calloc(1, sizeof(git_threadstate))) == NULL || + git_str_init(&threadstate->error_buf, 0) < 0) + return NULL; + + git_tlsdata_set(tls_key, threadstate); + return threadstate; +} diff --git a/src/libgit2/threadstate.h b/src/libgit2/threadstate.h new file mode 100644 index 000000000..c10f26b59 --- /dev/null +++ b/src/libgit2/threadstate.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_threadstate_h__ +#define INCLUDE_threadstate_h__ + +#include "common.h" + +typedef struct { + git_error *last_error; + git_error error_t; + git_str error_buf; + char oid_fmt[GIT_OID_HEXSZ+1]; +} git_threadstate; + +extern int git_threadstate_global_init(void); +extern git_threadstate *git_threadstate_get(void); + +#define GIT_THREADSTATE (git_threadstate_get()) + +#endif diff --git a/src/libgit2/trace.c b/src/libgit2/trace.c new file mode 100644 index 000000000..b0c56c4dc --- /dev/null +++ b/src/libgit2/trace.c @@ -0,0 +1,25 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "trace.h" + +#include "str.h" +#include "runtime.h" +#include "git2/trace.h" + +struct git_trace_data git_trace__data = {0}; + +int git_trace_set(git_trace_level_t level, git_trace_cb callback) +{ + GIT_ASSERT_ARG(level == 0 || callback != NULL); + + git_trace__data.level = level; + git_trace__data.callback = callback; + GIT_MEMORY_BARRIER; + + return 0; +} diff --git a/src/libgit2/trace.h b/src/libgit2/trace.h new file mode 100644 index 000000000..239928dcb --- /dev/null +++ b/src/libgit2/trace.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_trace_h__ +#define INCLUDE_trace_h__ + +#include "common.h" + +#include +#include "str.h" + +struct git_trace_data { + git_trace_level_t level; + git_trace_cb callback; +}; + +extern struct git_trace_data git_trace__data; + +GIT_INLINE(void) git_trace__write_fmt( + git_trace_level_t level, + const char *fmt, + va_list ap) +{ + git_trace_cb callback = git_trace__data.callback; + git_str message = GIT_STR_INIT; + + git_str_vprintf(&message, fmt, ap); + + callback(level, git_str_cstr(&message)); + + git_str_dispose(&message); +} + +#define git_trace_level() (git_trace__data.level) + +GIT_INLINE(void) git_trace(git_trace_level_t level, const char *fmt, ...) +{ + if (git_trace__data.level >= level && + git_trace__data.callback != NULL) { + va_list ap; + + va_start(ap, fmt); + git_trace__write_fmt(level, fmt, ap); + va_end(ap); + } +} + +#endif diff --git a/src/libgit2/trailer.c b/src/libgit2/trailer.c new file mode 100644 index 000000000..4761c9922 --- /dev/null +++ b/src/libgit2/trailer.c @@ -0,0 +1,430 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "array.h" +#include "common.h" +#include "git2/message.h" + +#include +#include +#include + +#define COMMENT_LINE_CHAR '#' +#define TRAILER_SEPARATORS ":" + +static const char *const git_generated_prefixes[] = { + "Signed-off-by: ", + "(cherry picked from commit ", + NULL +}; + +static int is_blank_line(const char *str) +{ + const char *s = str; + while (*s && *s != '\n' && isspace(*s)) + s++; + return !*s || *s == '\n'; +} + +static const char *next_line(const char *str) +{ + const char *nl = strchr(str, '\n'); + + if (nl) { + return nl + 1; + } else { + /* return pointer to the NUL terminator: */ + return str + strlen(str); + } +} + +/* + * Return the position of the start of the last line. If len is 0, return 0. + */ +static bool last_line(size_t *out, const char *buf, size_t len) +{ + size_t i; + + *out = 0; + + if (len == 0) + return false; + if (len == 1) + return true; + + /* + * Skip the last character (in addition to the null terminator), + * because if the last character is a newline, it is considered as part + * of the last line anyway. + */ + i = len - 2; + + for (; i > 0; i--) { + if (buf[i] == '\n') { + *out = i + 1; + return true; + } + } + return true; +} + +/* + * If the given line is of the form + * "..." or "...", sets out + * to the location of the separator and returns true. Otherwise, returns + * false. The optional whitespace is allowed there primarily to allow things + * like "Bug #43" where is "Bug" and is "#". + * + * The separator-starts-line case (in which this function returns true and + * sets out to 0) is distinguished from the non-well-formed-line case (in + * which this function returns false) because some callers of this function + * need such a distinction. + */ +static bool find_separator(size_t *out, const char *line, const char *separators) +{ + int whitespace_found = 0; + const char *c; + for (c = line; *c; c++) { + if (strchr(separators, *c)) { + *out = c - line; + return true; + } + + if (!whitespace_found && (isalnum(*c) || *c == '-')) + continue; + if (c != line && (*c == ' ' || *c == '\t')) { + whitespace_found = 1; + continue; + } + break; + } + return false; +} + +/* + * Inspect the given string and determine the true "end" of the log message, in + * order to find where to put a new Signed-off-by: line. Ignored are + * trailing comment lines and blank lines. To support "git commit -s + * --amend" on an existing commit, we also ignore "Conflicts:". To + * support "git commit -v", we truncate at cut lines. + * + * Returns the number of bytes from the tail to ignore, to be fed as + * the second parameter to append_signoff(). + */ +static size_t ignore_non_trailer(const char *buf, size_t len) +{ + size_t boc = 0, bol = 0; + int in_old_conflicts_block = 0; + size_t cutoff = len; + + while (bol < cutoff) { + const char *next_line = memchr(buf + bol, '\n', len - bol); + + if (!next_line) + next_line = buf + len; + else + next_line++; + + if (buf[bol] == COMMENT_LINE_CHAR || buf[bol] == '\n') { + /* is this the first of the run of comments? */ + if (!boc) + boc = bol; + /* otherwise, it is just continuing */ + } else if (git__prefixcmp(buf + bol, "Conflicts:\n") == 0) { + in_old_conflicts_block = 1; + if (!boc) + boc = bol; + } else if (in_old_conflicts_block && buf[bol] == '\t') { + ; /* a pathname in the conflicts block */ + } else if (boc) { + /* the previous was not trailing comment */ + boc = 0; + in_old_conflicts_block = 0; + } + bol = next_line - buf; + } + return boc ? len - boc : len - cutoff; +} + +/* + * Return the position of the start of the patch or the length of str if there + * is no patch in the message. + */ +static size_t find_patch_start(const char *str) +{ + const char *s; + + for (s = str; *s; s = next_line(s)) { + if (git__prefixcmp(s, "---") == 0) + return s - str; + } + + return s - str; +} + +/* + * Return the position of the first trailer line or len if there are no + * trailers. + */ +static size_t find_trailer_start(const char *buf, size_t len) +{ + const char *s; + size_t end_of_title, l; + int only_spaces = 1; + int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0; + /* + * Number of possible continuation lines encountered. This will be + * reset to 0 if we encounter a trailer (since those lines are to be + * considered continuations of that trailer), and added to + * non_trailer_lines if we encounter a non-trailer (since those lines + * are to be considered non-trailers). + */ + int possible_continuation_lines = 0; + + /* The first paragraph is the title and cannot be trailers */ + for (s = buf; s < buf + len; s = next_line(s)) { + if (s[0] == COMMENT_LINE_CHAR) + continue; + if (is_blank_line(s)) + break; + } + end_of_title = s - buf; + + /* + * Get the start of the trailers by looking starting from the end for a + * blank line before a set of non-blank lines that (i) are all + * trailers, or (ii) contains at least one Git-generated trailer and + * consists of at least 25% trailers. + */ + l = len; + while (last_line(&l, buf, l) && l >= end_of_title) { + const char *bol = buf + l; + const char *const *p; + size_t separator_pos = 0; + + if (bol[0] == COMMENT_LINE_CHAR) { + non_trailer_lines += possible_continuation_lines; + possible_continuation_lines = 0; + continue; + } + if (is_blank_line(bol)) { + if (only_spaces) + continue; + non_trailer_lines += possible_continuation_lines; + if (recognized_prefix && + trailer_lines * 3 >= non_trailer_lines) + return next_line(bol) - buf; + else if (trailer_lines && !non_trailer_lines) + return next_line(bol) - buf; + return len; + } + only_spaces = 0; + + for (p = git_generated_prefixes; *p; p++) { + if (git__prefixcmp(bol, *p) == 0) { + trailer_lines++; + possible_continuation_lines = 0; + recognized_prefix = 1; + goto continue_outer_loop; + } + } + + find_separator(&separator_pos, bol, TRAILER_SEPARATORS); + if (separator_pos >= 1 && !isspace(bol[0])) { + trailer_lines++; + possible_continuation_lines = 0; + if (recognized_prefix) + continue; + } else if (isspace(bol[0])) + possible_continuation_lines++; + else { + non_trailer_lines++; + non_trailer_lines += possible_continuation_lines; + possible_continuation_lines = 0; + } +continue_outer_loop: + ; + } + + return len; +} + +/* Return the position of the end of the trailers. */ +static size_t find_trailer_end(const char *buf, size_t len) +{ + return len - ignore_non_trailer(buf, len); +} + +static char *extract_trailer_block(const char *message, size_t *len) +{ + size_t patch_start = find_patch_start(message); + size_t trailer_end = find_trailer_end(message, patch_start); + size_t trailer_start = find_trailer_start(message, trailer_end); + + size_t trailer_len = trailer_end - trailer_start; + + char *buffer = git__malloc(trailer_len + 1); + if (buffer == NULL) + return NULL; + + memcpy(buffer, message + trailer_start, trailer_len); + buffer[trailer_len] = 0; + + *len = trailer_len; + + return buffer; +} + +enum trailer_state { + S_START = 0, + S_KEY = 1, + S_KEY_WS = 2, + S_SEP_WS = 3, + S_VALUE = 4, + S_VALUE_NL = 5, + S_VALUE_END = 6, + S_IGNORE = 7 +}; + +#define NEXT(st) { state = (st); ptr++; continue; } +#define GOTO(st) { state = (st); continue; } + +typedef git_array_t(git_message_trailer) git_array_trailer_t; + +int git_message_trailers(git_message_trailer_array *trailer_arr, const char *message) +{ + enum trailer_state state = S_START; + int rc = 0; + char *ptr; + char *key = NULL; + char *value = NULL; + git_array_trailer_t arr = GIT_ARRAY_INIT; + + size_t trailer_len; + char *trailer = extract_trailer_block(message, &trailer_len); + if (trailer == NULL) + return -1; + + for (ptr = trailer;;) { + switch (state) { + case S_START: { + if (*ptr == 0) { + goto ret; + } + + key = ptr; + GOTO(S_KEY); + } + case S_KEY: { + if (*ptr == 0) { + goto ret; + } + + if (isalnum(*ptr) || *ptr == '-') { + /* legal key character */ + NEXT(S_KEY); + } + + if (*ptr == ' ' || *ptr == '\t') { + /* optional whitespace before separator */ + *ptr = 0; + NEXT(S_KEY_WS); + } + + if (strchr(TRAILER_SEPARATORS, *ptr)) { + *ptr = 0; + NEXT(S_SEP_WS); + } + + /* illegal character */ + GOTO(S_IGNORE); + } + case S_KEY_WS: { + if (*ptr == 0) { + goto ret; + } + + if (*ptr == ' ' || *ptr == '\t') { + NEXT(S_KEY_WS); + } + + if (strchr(TRAILER_SEPARATORS, *ptr)) { + NEXT(S_SEP_WS); + } + + /* illegal character */ + GOTO(S_IGNORE); + } + case S_SEP_WS: { + if (*ptr == 0) { + goto ret; + } + + if (*ptr == ' ' || *ptr == '\t') { + NEXT(S_SEP_WS); + } + + value = ptr; + NEXT(S_VALUE); + } + case S_VALUE: { + if (*ptr == 0) { + GOTO(S_VALUE_END); + } + + if (*ptr == '\n') { + NEXT(S_VALUE_NL); + } + + NEXT(S_VALUE); + } + case S_VALUE_NL: { + if (*ptr == ' ') { + /* continuation; */ + NEXT(S_VALUE); + } + + ptr[-1] = 0; + GOTO(S_VALUE_END); + } + case S_VALUE_END: { + git_message_trailer *t = git_array_alloc(arr); + + t->key = key; + t->value = value; + + key = NULL; + value = NULL; + + GOTO(S_START); + } + case S_IGNORE: { + if (*ptr == 0) { + goto ret; + } + + if (*ptr == '\n') { + NEXT(S_START); + } + + NEXT(S_IGNORE); + } + } + } + +ret: + trailer_arr->_trailer_block = trailer; + trailer_arr->trailers = arr.ptr; + trailer_arr->count = arr.size; + + return rc; +} + +void git_message_trailer_array_free(git_message_trailer_array *arr) +{ + git__free(arr->_trailer_block); + git__free(arr->trailers); +} diff --git a/src/libgit2/transaction.c b/src/libgit2/transaction.c new file mode 100644 index 000000000..ccffa9984 --- /dev/null +++ b/src/libgit2/transaction.c @@ -0,0 +1,395 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "transaction.h" + +#include "repository.h" +#include "strmap.h" +#include "refdb.h" +#include "pool.h" +#include "reflog.h" +#include "signature.h" +#include "config.h" + +#include "git2/transaction.h" +#include "git2/signature.h" +#include "git2/sys/refs.h" +#include "git2/sys/refdb_backend.h" + +typedef enum { + TRANSACTION_NONE, + TRANSACTION_REFS, + TRANSACTION_CONFIG +} transaction_t; + +typedef struct { + const char *name; + void *payload; + + git_reference_t ref_type; + union { + git_oid id; + char *symbolic; + } target; + git_reflog *reflog; + + const char *message; + git_signature *sig; + + unsigned int committed :1, + remove :1; +} transaction_node; + +struct git_transaction { + transaction_t type; + git_repository *repo; + git_refdb *db; + git_config *cfg; + + git_strmap *locks; + git_pool pool; +}; + +int git_transaction_config_new(git_transaction **out, git_config *cfg) +{ + git_transaction *tx; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(cfg); + + tx = git__calloc(1, sizeof(git_transaction)); + GIT_ERROR_CHECK_ALLOC(tx); + + tx->type = TRANSACTION_CONFIG; + tx->cfg = cfg; + *out = tx; + return 0; +} + +int git_transaction_new(git_transaction **out, git_repository *repo) +{ + int error; + git_pool pool; + git_transaction *tx = NULL; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + + if ((error = git_pool_init(&pool, 1)) < 0) + goto on_error; + + tx = git_pool_mallocz(&pool, sizeof(git_transaction)); + if (!tx) { + error = -1; + goto on_error; + } + + if ((error = git_strmap_new(&tx->locks)) < 0) { + error = -1; + goto on_error; + } + + if ((error = git_repository_refdb(&tx->db, repo)) < 0) + goto on_error; + + tx->type = TRANSACTION_REFS; + memcpy(&tx->pool, &pool, sizeof(git_pool)); + tx->repo = repo; + *out = tx; + return 0; + +on_error: + git_pool_clear(&pool); + return error; +} + +int git_transaction_lock_ref(git_transaction *tx, const char *refname) +{ + int error; + transaction_node *node; + + GIT_ASSERT_ARG(tx); + GIT_ASSERT_ARG(refname); + + node = git_pool_mallocz(&tx->pool, sizeof(transaction_node)); + GIT_ERROR_CHECK_ALLOC(node); + + node->name = git_pool_strdup(&tx->pool, refname); + GIT_ERROR_CHECK_ALLOC(node->name); + + if ((error = git_refdb_lock(&node->payload, tx->db, refname)) < 0) + return error; + + if ((error = git_strmap_set(tx->locks, node->name, node)) < 0) + goto cleanup; + + return 0; + +cleanup: + git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL); + + return error; +} + +static int find_locked(transaction_node **out, git_transaction *tx, const char *refname) +{ + transaction_node *node; + + if ((node = git_strmap_get(tx->locks, refname)) == NULL) { + git_error_set(GIT_ERROR_REFERENCE, "the specified reference is not locked"); + return GIT_ENOTFOUND; + } + + *out = node; + return 0; +} + +static int copy_common(transaction_node *node, git_transaction *tx, const git_signature *sig, const char *msg) +{ + if (sig && git_signature__pdup(&node->sig, sig, &tx->pool) < 0) + return -1; + + if (!node->sig) { + git_signature *tmp; + int error; + + if (git_reference__log_signature(&tmp, tx->repo) < 0) + return -1; + + /* make sure the sig we use is in our pool */ + error = git_signature__pdup(&node->sig, tmp, &tx->pool); + git_signature_free(tmp); + if (error < 0) + return error; + } + + if (msg) { + node->message = git_pool_strdup(&tx->pool, msg); + GIT_ERROR_CHECK_ALLOC(node->message); + } + + return 0; +} + +int git_transaction_set_target(git_transaction *tx, const char *refname, const git_oid *target, const git_signature *sig, const char *msg) +{ + int error; + transaction_node *node; + + GIT_ASSERT_ARG(tx); + GIT_ASSERT_ARG(refname); + GIT_ASSERT_ARG(target); + + if ((error = find_locked(&node, tx, refname)) < 0) + return error; + + if ((error = copy_common(node, tx, sig, msg)) < 0) + return error; + + git_oid_cpy(&node->target.id, target); + node->ref_type = GIT_REFERENCE_DIRECT; + + return 0; +} + +int git_transaction_set_symbolic_target(git_transaction *tx, const char *refname, const char *target, const git_signature *sig, const char *msg) +{ + int error; + transaction_node *node; + + GIT_ASSERT_ARG(tx); + GIT_ASSERT_ARG(refname); + GIT_ASSERT_ARG(target); + + if ((error = find_locked(&node, tx, refname)) < 0) + return error; + + if ((error = copy_common(node, tx, sig, msg)) < 0) + return error; + + node->target.symbolic = git_pool_strdup(&tx->pool, target); + GIT_ERROR_CHECK_ALLOC(node->target.symbolic); + node->ref_type = GIT_REFERENCE_SYMBOLIC; + + return 0; +} + +int git_transaction_remove(git_transaction *tx, const char *refname) +{ + int error; + transaction_node *node; + + if ((error = find_locked(&node, tx, refname)) < 0) + return error; + + node->remove = true; + node->ref_type = GIT_REFERENCE_DIRECT; /* the id will be ignored */ + + return 0; +} + +static int dup_reflog(git_reflog **out, const git_reflog *in, git_pool *pool) +{ + git_reflog *reflog; + git_reflog_entry *entries; + size_t len, i; + + reflog = git_pool_mallocz(pool, sizeof(git_reflog)); + GIT_ERROR_CHECK_ALLOC(reflog); + + reflog->ref_name = git_pool_strdup(pool, in->ref_name); + GIT_ERROR_CHECK_ALLOC(reflog->ref_name); + + len = in->entries.length; + reflog->entries.length = len; + reflog->entries.contents = git_pool_mallocz(pool, len * sizeof(void *)); + GIT_ERROR_CHECK_ALLOC(reflog->entries.contents); + + entries = git_pool_mallocz(pool, len * sizeof(git_reflog_entry)); + GIT_ERROR_CHECK_ALLOC(entries); + + for (i = 0; i < len; i++) { + const git_reflog_entry *src; + git_reflog_entry *tgt; + + tgt = &entries[i]; + reflog->entries.contents[i] = tgt; + + src = git_vector_get(&in->entries, i); + git_oid_cpy(&tgt->oid_old, &src->oid_old); + git_oid_cpy(&tgt->oid_cur, &src->oid_cur); + + tgt->msg = git_pool_strdup(pool, src->msg); + GIT_ERROR_CHECK_ALLOC(tgt->msg); + + if (git_signature__pdup(&tgt->committer, src->committer, pool) < 0) + return -1; + } + + + *out = reflog; + return 0; +} + +int git_transaction_set_reflog(git_transaction *tx, const char *refname, const git_reflog *reflog) +{ + int error; + transaction_node *node; + + GIT_ASSERT_ARG(tx); + GIT_ASSERT_ARG(refname); + GIT_ASSERT_ARG(reflog); + + if ((error = find_locked(&node, tx, refname)) < 0) + return error; + + if ((error = dup_reflog(&node->reflog, reflog, &tx->pool)) < 0) + return error; + + return 0; +} + +static int update_target(git_refdb *db, transaction_node *node) +{ + git_reference *ref; + int error, update_reflog; + + if (node->ref_type == GIT_REFERENCE_DIRECT) { + ref = git_reference__alloc(node->name, &node->target.id, NULL); + } else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) { + ref = git_reference__alloc_symbolic(node->name, node->target.symbolic); + } else { + abort(); + } + + GIT_ERROR_CHECK_ALLOC(ref); + update_reflog = node->reflog == NULL; + + if (node->remove) { + error = git_refdb_unlock(db, node->payload, 2, false, ref, NULL, NULL); + } else if (node->ref_type == GIT_REFERENCE_DIRECT) { + error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message); + } else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) { + error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message); + } else { + abort(); + } + + git_reference_free(ref); + node->committed = true; + + return error; +} + +int git_transaction_commit(git_transaction *tx) +{ + transaction_node *node; + int error = 0; + + GIT_ASSERT_ARG(tx); + + if (tx->type == TRANSACTION_CONFIG) { + error = git_config_unlock(tx->cfg, true); + tx->cfg = NULL; + + return error; + } + + git_strmap_foreach_value(tx->locks, node, { + if (node->reflog) { + if ((error = tx->db->backend->reflog_write(tx->db->backend, node->reflog)) < 0) + return error; + } + + if (node->ref_type == GIT_REFERENCE_INVALID) { + /* ref was locked but not modified */ + if ((error = git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL)) < 0) { + return error; + } + node->committed = true; + } else { + if ((error = update_target(tx->db, node)) < 0) + return error; + } + }); + + return 0; +} + +void git_transaction_free(git_transaction *tx) +{ + transaction_node *node; + git_pool pool; + + if (!tx) + return; + + if (tx->type == TRANSACTION_CONFIG) { + if (tx->cfg) { + git_config_unlock(tx->cfg, false); + git_config_free(tx->cfg); + } + + git__free(tx); + return; + } + + /* start by unlocking the ones we've left hanging, if any */ + git_strmap_foreach_value(tx->locks, node, { + if (node->committed) + continue; + + git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL); + }); + + git_refdb_free(tx->db); + git_strmap_free(tx->locks); + + /* tx is inside the pool, so we need to extract the data */ + memcpy(&pool, &tx->pool, sizeof(git_pool)); + git_pool_clear(&pool); +} diff --git a/src/libgit2/transaction.h b/src/libgit2/transaction.h new file mode 100644 index 000000000..780c06830 --- /dev/null +++ b/src/libgit2/transaction.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_transaction_h__ +#define INCLUDE_transaction_h__ + +#include "common.h" + +int git_transaction_config_new(git_transaction **out, git_config *cfg); + +#endif diff --git a/src/libgit2/transport.c b/src/libgit2/transport.c new file mode 100644 index 000000000..640ccacae --- /dev/null +++ b/src/libgit2/transport.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "git2/types.h" +#include "git2/remote.h" +#include "git2/net.h" +#include "git2/transport.h" +#include "git2/sys/transport.h" +#include "fs_path.h" + +typedef struct transport_definition { + char *prefix; + git_transport_cb fn; + void *param; +} transport_definition; + +static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1, NULL }; +static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0, NULL }; +#ifdef GIT_SSH +static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0, NULL }; +#endif + +static transport_definition local_transport_definition = { "file://", git_transport_local, NULL }; + +static transport_definition transports[] = { + { "git://", git_transport_smart, &git_subtransport_definition }, + { "http://", git_transport_smart, &http_subtransport_definition }, + { "https://", git_transport_smart, &http_subtransport_definition }, + { "file://", git_transport_local, NULL }, +#ifdef GIT_SSH + { "ssh://", git_transport_smart, &ssh_subtransport_definition }, + { "ssh+git://", git_transport_smart, &ssh_subtransport_definition }, + { "git+ssh://", git_transport_smart, &ssh_subtransport_definition }, +#endif + { NULL, 0, 0 } +}; + +static git_vector custom_transports = GIT_VECTOR_INIT; + +#define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1 + +static transport_definition * transport_find_by_url(const char *url) +{ + size_t i = 0; + transport_definition *d; + + /* Find a user transport who wants to deal with this URI */ + git_vector_foreach(&custom_transports, i, d) { + if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) { + return d; + } + } + + /* Find a system transport for this URI */ + for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) { + d = &transports[i]; + + if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) { + return d; + } + } + + return NULL; +} + +static int transport_find_fn( + git_transport_cb *out, + const char *url, + void **param) +{ + transport_definition *definition = transport_find_by_url(url); + +#ifdef GIT_WIN32 + /* On Windows, it might not be possible to discern between absolute local + * and ssh paths - first check if this is a valid local path that points + * to a directory and if so assume local path, else assume SSH */ + + /* Check to see if the path points to a file on the local file system */ + if (!definition && git_fs_path_exists(url) && git_fs_path_isdir(url)) + definition = &local_transport_definition; +#endif + + /* For other systems, perform the SSH check first, to avoid going to the + * filesystem if it is not necessary */ + + /* It could be a SSH remote path. Check to see if there's a : */ + if (!definition && strrchr(url, ':')) { + /* re-search transports again with ssh:// as url + * so that we can find a third party ssh transport */ + definition = transport_find_by_url("ssh://"); + } + +#ifndef GIT_WIN32 + /* Check to see if the path points to a file on the local file system */ + if (!definition && git_fs_path_exists(url) && git_fs_path_isdir(url)) + definition = &local_transport_definition; +#endif + + if (!definition) + return GIT_ENOTFOUND; + + *out = definition->fn; + *param = definition->param; + + return 0; +} + +/************** + * Public API * + **************/ + +int git_transport_new(git_transport **out, git_remote *owner, const char *url) +{ + git_transport_cb fn; + git_transport *transport; + void *param; + int error; + + if ((error = transport_find_fn(&fn, url, ¶m)) == GIT_ENOTFOUND) { + git_error_set(GIT_ERROR_NET, "unsupported URL protocol"); + return -1; + } else if (error < 0) + return error; + + if ((error = fn(&transport, owner, param)) < 0) + return error; + + GIT_ERROR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport"); + + *out = transport; + + return 0; +} + +int git_transport_register( + const char *scheme, + git_transport_cb cb, + void *param) +{ + git_str prefix = GIT_STR_INIT; + transport_definition *d, *definition = NULL; + size_t i; + int error = 0; + + GIT_ASSERT_ARG(scheme); + GIT_ASSERT_ARG(cb); + + if ((error = git_str_printf(&prefix, "%s://", scheme)) < 0) + goto on_error; + + git_vector_foreach(&custom_transports, i, d) { + if (strcasecmp(d->prefix, prefix.ptr) == 0) { + error = GIT_EEXISTS; + goto on_error; + } + } + + definition = git__calloc(1, sizeof(transport_definition)); + GIT_ERROR_CHECK_ALLOC(definition); + + definition->prefix = git_str_detach(&prefix); + definition->fn = cb; + definition->param = param; + + if (git_vector_insert(&custom_transports, definition) < 0) + goto on_error; + + return 0; + +on_error: + git_str_dispose(&prefix); + git__free(definition); + return error; +} + +int git_transport_unregister(const char *scheme) +{ + git_str prefix = GIT_STR_INIT; + transport_definition *d; + size_t i; + int error = 0; + + GIT_ASSERT_ARG(scheme); + + if ((error = git_str_printf(&prefix, "%s://", scheme)) < 0) + goto done; + + git_vector_foreach(&custom_transports, i, d) { + if (strcasecmp(d->prefix, prefix.ptr) == 0) { + if ((error = git_vector_remove(&custom_transports, i)) < 0) + goto done; + + git__free(d->prefix); + git__free(d); + + if (!custom_transports.length) + git_vector_free(&custom_transports); + + error = 0; + goto done; + } + } + + error = GIT_ENOTFOUND; + +done: + git_str_dispose(&prefix); + return error; +} + +int git_transport_init(git_transport *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_transport, GIT_TRANSPORT_INIT); + return 0; +} diff --git a/src/libgit2/transports/auth.c b/src/libgit2/transports/auth.c new file mode 100644 index 000000000..90b6b124f --- /dev/null +++ b/src/libgit2/transports/auth.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "auth.h" + +#include "git2/sys/credential.h" + +static int basic_next_token( + git_str *out, + git_http_auth_context *ctx, + git_credential *c) +{ + git_credential_userpass_plaintext *cred; + git_str raw = GIT_STR_INIT; + int error = GIT_EAUTH; + + GIT_UNUSED(ctx); + + if (c->credtype != GIT_CREDENTIAL_USERPASS_PLAINTEXT) { + git_error_set(GIT_ERROR_INVALID, "invalid credential type for basic auth"); + goto on_error; + } + + cred = (git_credential_userpass_plaintext *)c; + + git_str_printf(&raw, "%s:%s", cred->username, cred->password); + + if (git_str_oom(&raw) || + git_str_puts(out, "Basic ") < 0 || + git_str_encode_base64(out, git_str_cstr(&raw), raw.size) < 0) + goto on_error; + + error = 0; + +on_error: + if (raw.size) + git__memzero(raw.ptr, raw.size); + + git_str_dispose(&raw); + return error; +} + +static git_http_auth_context basic_context = { + GIT_HTTP_AUTH_BASIC, + GIT_CREDENTIAL_USERPASS_PLAINTEXT, + 0, + NULL, + basic_next_token, + NULL, + NULL +}; + +int git_http_auth_basic( + git_http_auth_context **out, const git_net_url *url) +{ + GIT_UNUSED(url); + + *out = &basic_context; + return 0; +} + +int git_http_auth_dummy( + git_http_auth_context **out, const git_net_url *url) +{ + GIT_UNUSED(url); + + *out = NULL; + return GIT_PASSTHROUGH; +} + diff --git a/src/libgit2/transports/auth.h b/src/libgit2/transports/auth.h new file mode 100644 index 000000000..64680cc53 --- /dev/null +++ b/src/libgit2/transports/auth.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_transports_auth_h__ +#define INCLUDE_transports_auth_h__ + +#include "common.h" + +#include "netops.h" + +typedef enum { + GIT_HTTP_AUTH_BASIC = 1, + GIT_HTTP_AUTH_NEGOTIATE = 2, + GIT_HTTP_AUTH_NTLM = 4 +} git_http_auth_t; + +typedef struct git_http_auth_context git_http_auth_context; + +struct git_http_auth_context { + /** Type of scheme */ + git_http_auth_t type; + + /** Supported credentials */ + git_credential_t credtypes; + + /** Connection affinity or request affinity */ + unsigned connection_affinity : 1; + + /** Sets the challenge on the authentication context */ + int (*set_challenge)(git_http_auth_context *ctx, const char *challenge); + + /** Gets the next authentication token from the context */ + int (*next_token)(git_str *out, git_http_auth_context *ctx, git_credential *cred); + + /** Examines if all tokens have been presented. */ + int (*is_complete)(git_http_auth_context *ctx); + + /** Frees the authentication context */ + void (*free)(git_http_auth_context *ctx); +}; + +typedef struct { + /** Type of scheme */ + git_http_auth_t type; + + /** Name of the scheme (as used in the Authorization header) */ + const char *name; + + /** Credential types this scheme supports */ + git_credential_t credtypes; + + /** Function to initialize an authentication context */ + int (*init_context)( + git_http_auth_context **out, + const git_net_url *url); +} git_http_auth_scheme; + +int git_http_auth_dummy( + git_http_auth_context **out, + const git_net_url *url); + +int git_http_auth_basic( + git_http_auth_context **out, + const git_net_url *url); + +#endif diff --git a/src/libgit2/transports/auth_negotiate.c b/src/libgit2/transports/auth_negotiate.c new file mode 100644 index 000000000..6380504be --- /dev/null +++ b/src/libgit2/transports/auth_negotiate.c @@ -0,0 +1,314 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "auth_negotiate.h" + +#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) + +#include "git2.h" +#include "auth.h" +#include "git2/sys/credential.h" + +#ifdef GIT_GSSFRAMEWORK +#import +#elif defined(GIT_GSSAPI) +#include +#include +#endif + +static gss_OID_desc negotiate_oid_spnego = + { 6, (void *) "\x2b\x06\x01\x05\x05\x02" }; +static gss_OID_desc negotiate_oid_krb5 = + { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; + +static gss_OID negotiate_oids[] = + { &negotiate_oid_spnego, &negotiate_oid_krb5, NULL }; + +typedef struct { + git_http_auth_context parent; + unsigned configured : 1, + complete : 1; + git_str target; + char *challenge; + gss_ctx_id_t gss_context; + gss_OID oid; +} http_auth_negotiate_context; + +static void negotiate_err_set( + OM_uint32 status_major, + OM_uint32 status_minor, + const char *message) +{ + gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER; + OM_uint32 status_display, context = 0; + + if (gss_display_status(&status_display, status_major, GSS_C_GSS_CODE, + GSS_C_NO_OID, &context, &buffer) == GSS_S_COMPLETE) { + git_error_set(GIT_ERROR_NET, "%s: %.*s (%d.%d)", + message, (int)buffer.length, (const char *)buffer.value, + status_major, status_minor); + gss_release_buffer(&status_minor, &buffer); + } else { + git_error_set(GIT_ERROR_NET, "%s: unknown negotiate error (%d.%d)", + message, status_major, status_minor); + } +} + +static int negotiate_set_challenge( + git_http_auth_context *c, + const char *challenge) +{ + http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + + GIT_ASSERT_ARG(ctx); + GIT_ASSERT_ARG(challenge); + GIT_ASSERT(ctx->configured); + + git__free(ctx->challenge); + + ctx->challenge = git__strdup(challenge); + GIT_ERROR_CHECK_ALLOC(ctx->challenge); + + return 0; +} + +static void negotiate_context_dispose(http_auth_negotiate_context *ctx) +{ + OM_uint32 status_minor; + + if (ctx->gss_context != GSS_C_NO_CONTEXT) { + gss_delete_sec_context( + &status_minor, &ctx->gss_context, GSS_C_NO_BUFFER); + ctx->gss_context = GSS_C_NO_CONTEXT; + } + + git_str_dispose(&ctx->target); + + git__free(ctx->challenge); + ctx->challenge = NULL; +} + +static int negotiate_next_token( + git_str *buf, + git_http_auth_context *c, + git_credential *cred) +{ + http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + OM_uint32 status_major, status_minor; + gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER, + input_token = GSS_C_EMPTY_BUFFER, + output_token = GSS_C_EMPTY_BUFFER; + gss_buffer_t input_token_ptr = GSS_C_NO_BUFFER; + git_str input_buf = GIT_STR_INIT; + gss_name_t server = NULL; + gss_OID mech; + size_t challenge_len; + int error = 0; + + GIT_ASSERT_ARG(buf); + GIT_ASSERT_ARG(ctx); + GIT_ASSERT_ARG(cred); + + GIT_ASSERT(ctx->configured); + GIT_ASSERT(cred->credtype == GIT_CREDENTIAL_DEFAULT); + + if (ctx->complete) + return 0; + + target_buffer.value = (void *)ctx->target.ptr; + target_buffer.length = ctx->target.size; + + status_major = gss_import_name(&status_minor, &target_buffer, + GSS_C_NT_HOSTBASED_SERVICE, &server); + + if (GSS_ERROR(status_major)) { + negotiate_err_set(status_major, status_minor, + "could not parse principal"); + error = -1; + goto done; + } + + challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0; + + if (challenge_len < 9 || memcmp(ctx->challenge, "Negotiate", 9) != 0) { + git_error_set(GIT_ERROR_NET, "server did not request negotiate"); + error = -1; + goto done; + } + + if (challenge_len > 9) { + if (git_str_decode_base64(&input_buf, + ctx->challenge + 10, challenge_len - 10) < 0) { + git_error_set(GIT_ERROR_NET, "invalid negotiate challenge from server"); + error = -1; + goto done; + } + + input_token.value = input_buf.ptr; + input_token.length = input_buf.size; + input_token_ptr = &input_token; + } else if (ctx->gss_context != GSS_C_NO_CONTEXT) { + negotiate_context_dispose(ctx); + } + + mech = &negotiate_oid_spnego; + + status_major = gss_init_sec_context( + &status_minor, + GSS_C_NO_CREDENTIAL, + &ctx->gss_context, + server, + mech, + GSS_C_DELEG_FLAG | GSS_C_MUTUAL_FLAG, + GSS_C_INDEFINITE, + GSS_C_NO_CHANNEL_BINDINGS, + input_token_ptr, + NULL, + &output_token, + NULL, + NULL); + + if (GSS_ERROR(status_major)) { + negotiate_err_set(status_major, status_minor, "negotiate failure"); + error = -1; + goto done; + } + + /* This message merely told us auth was complete; we do not respond. */ + if (status_major == GSS_S_COMPLETE) { + negotiate_context_dispose(ctx); + ctx->complete = 1; + goto done; + } + + if (output_token.length == 0) { + git_error_set(GIT_ERROR_NET, "GSSAPI did not return token"); + error = -1; + goto done; + } + + git_str_puts(buf, "Negotiate "); + git_str_encode_base64(buf, output_token.value, output_token.length); + + if (git_str_oom(buf)) + error = -1; + +done: + gss_release_name(&status_minor, &server); + gss_release_buffer(&status_minor, (gss_buffer_t) &output_token); + git_str_dispose(&input_buf); + return error; +} + +static int negotiate_is_complete(git_http_auth_context *c) +{ + http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + + GIT_ASSERT_ARG(ctx); + + return (ctx->complete == 1); +} + +static void negotiate_context_free(git_http_auth_context *c) +{ + http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + + negotiate_context_dispose(ctx); + + ctx->configured = 0; + ctx->complete = 0; + ctx->oid = NULL; + + git__free(ctx); +} + +static int negotiate_init_context( + http_auth_negotiate_context *ctx, + const git_net_url *url) +{ + OM_uint32 status_major, status_minor; + gss_OID item, *oid; + gss_OID_set mechanism_list; + size_t i; + + /* Query supported mechanisms looking for SPNEGO) */ + status_major = gss_indicate_mechs(&status_minor, &mechanism_list); + + if (GSS_ERROR(status_major)) { + negotiate_err_set(status_major, status_minor, + "could not query mechanisms"); + return -1; + } + + if (mechanism_list) { + for (oid = negotiate_oids; *oid; oid++) { + for (i = 0; i < mechanism_list->count; i++) { + item = &mechanism_list->elements[i]; + + if (item->length == (*oid)->length && + memcmp(item->elements, (*oid)->elements, item->length) == 0) { + ctx->oid = *oid; + break; + } + + } + + if (ctx->oid) + break; + } + } + + gss_release_oid_set(&status_minor, &mechanism_list); + + if (!ctx->oid) { + git_error_set(GIT_ERROR_NET, "negotiate authentication is not supported"); + return GIT_EAUTH; + } + + git_str_puts(&ctx->target, "HTTP@"); + git_str_puts(&ctx->target, url->host); + + if (git_str_oom(&ctx->target)) + return -1; + + ctx->gss_context = GSS_C_NO_CONTEXT; + ctx->configured = 1; + + return 0; +} + +int git_http_auth_negotiate( + git_http_auth_context **out, + const git_net_url *url) +{ + http_auth_negotiate_context *ctx; + + *out = NULL; + + ctx = git__calloc(1, sizeof(http_auth_negotiate_context)); + GIT_ERROR_CHECK_ALLOC(ctx); + + if (negotiate_init_context(ctx, url) < 0) { + git__free(ctx); + return -1; + } + + ctx->parent.type = GIT_HTTP_AUTH_NEGOTIATE; + ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT; + ctx->parent.connection_affinity = 1; + ctx->parent.set_challenge = negotiate_set_challenge; + ctx->parent.next_token = negotiate_next_token; + ctx->parent.is_complete = negotiate_is_complete; + ctx->parent.free = negotiate_context_free; + + *out = (git_http_auth_context *)ctx; + + return 0; +} + +#endif /* GIT_GSSAPI */ + diff --git a/src/libgit2/transports/auth_negotiate.h b/src/libgit2/transports/auth_negotiate.h new file mode 100644 index 000000000..34aff295b --- /dev/null +++ b/src/libgit2/transports/auth_negotiate.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_transports_auth_negotiate_h__ +#define INCLUDE_transports_auth_negotiate_h__ + +#include "common.h" +#include "git2.h" +#include "auth.h" + +#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) + +extern int git_http_auth_negotiate( + git_http_auth_context **out, + const git_net_url *url); + +#else + +#define git_http_auth_negotiate git_http_auth_dummy + +#endif /* GIT_GSSAPI */ + +#endif diff --git a/src/libgit2/transports/auth_ntlm.c b/src/libgit2/transports/auth_ntlm.c new file mode 100644 index 000000000..f49ce101a --- /dev/null +++ b/src/libgit2/transports/auth_ntlm.c @@ -0,0 +1,227 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "auth_ntlm.h" + +#include "common.h" +#include "str.h" +#include "auth.h" +#include "git2/sys/credential.h" + +#ifdef GIT_NTLM + +#include "ntlmclient.h" + +typedef struct { + git_http_auth_context parent; + ntlm_client *ntlm; + char *challenge; + bool complete; +} http_auth_ntlm_context; + +static int ntlm_set_challenge( + git_http_auth_context *c, + const char *challenge) +{ + http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; + + GIT_ASSERT_ARG(ctx); + GIT_ASSERT_ARG(challenge); + + git__free(ctx->challenge); + + ctx->challenge = git__strdup(challenge); + GIT_ERROR_CHECK_ALLOC(ctx->challenge); + + return 0; +} + +static int ntlm_set_credentials(http_auth_ntlm_context *ctx, git_credential *_cred) +{ + git_credential_userpass_plaintext *cred; + const char *sep, *username; + char *domain = NULL, *domainuser = NULL; + int error = 0; + + GIT_ASSERT(_cred->credtype == GIT_CREDENTIAL_USERPASS_PLAINTEXT); + cred = (git_credential_userpass_plaintext *)_cred; + + if ((sep = strchr(cred->username, '\\')) != NULL) { + domain = git__strndup(cred->username, (sep - cred->username)); + GIT_ERROR_CHECK_ALLOC(domain); + + domainuser = git__strdup(sep + 1); + GIT_ERROR_CHECK_ALLOC(domainuser); + + username = domainuser; + } else { + username = cred->username; + } + + if (ntlm_client_set_credentials(ctx->ntlm, + username, domain, cred->password) < 0) { + git_error_set(GIT_ERROR_NET, "could not set credentials: %s", + ntlm_client_errmsg(ctx->ntlm)); + error = -1; + goto done; + } + +done: + git__free(domain); + git__free(domainuser); + return error; +} + +static int ntlm_next_token( + git_str *buf, + git_http_auth_context *c, + git_credential *cred) +{ + http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; + git_str input_buf = GIT_STR_INIT; + const unsigned char *msg; + size_t challenge_len, msg_len; + int error = GIT_EAUTH; + + GIT_ASSERT_ARG(buf); + GIT_ASSERT_ARG(ctx); + + GIT_ASSERT(ctx->ntlm); + + challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0; + + if (ctx->complete) + ntlm_client_reset(ctx->ntlm); + + /* + * Set us complete now since it's the default case; the one + * incomplete case (successfully created a client request) + * will explicitly set that it requires a second step. + */ + ctx->complete = true; + + if (cred && ntlm_set_credentials(ctx, cred) != 0) + goto done; + + if (challenge_len < 4) { + git_error_set(GIT_ERROR_NET, "no ntlm challenge sent from server"); + goto done; + } else if (challenge_len == 4) { + if (memcmp(ctx->challenge, "NTLM", 4) != 0) { + git_error_set(GIT_ERROR_NET, "server did not request NTLM"); + goto done; + } + + if (ntlm_client_negotiate(&msg, &msg_len, ctx->ntlm) != 0) { + git_error_set(GIT_ERROR_NET, "ntlm authentication failed: %s", + ntlm_client_errmsg(ctx->ntlm)); + goto done; + } + + ctx->complete = false; + } else { + if (memcmp(ctx->challenge, "NTLM ", 5) != 0) { + git_error_set(GIT_ERROR_NET, "challenge from server was not NTLM"); + goto done; + } + + if (git_str_decode_base64(&input_buf, + ctx->challenge + 5, challenge_len - 5) < 0) { + git_error_set(GIT_ERROR_NET, "invalid NTLM challenge from server"); + goto done; + } + + if (ntlm_client_set_challenge(ctx->ntlm, + (const unsigned char *)input_buf.ptr, input_buf.size) != 0) { + git_error_set(GIT_ERROR_NET, "ntlm challenge failed: %s", + ntlm_client_errmsg(ctx->ntlm)); + goto done; + } + + if (ntlm_client_response(&msg, &msg_len, ctx->ntlm) != 0) { + git_error_set(GIT_ERROR_NET, "ntlm authentication failed: %s", + ntlm_client_errmsg(ctx->ntlm)); + goto done; + } + } + + git_str_puts(buf, "NTLM "); + git_str_encode_base64(buf, (const char *)msg, msg_len); + + if (git_str_oom(buf)) + goto done; + + error = 0; + +done: + git_str_dispose(&input_buf); + return error; +} + +static int ntlm_is_complete(git_http_auth_context *c) +{ + http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; + + GIT_ASSERT_ARG(ctx); + return (ctx->complete == true); +} + +static void ntlm_context_free(git_http_auth_context *c) +{ + http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; + + ntlm_client_free(ctx->ntlm); + git__free(ctx->challenge); + git__free(ctx); +} + +static int ntlm_init_context( + http_auth_ntlm_context *ctx, + const git_net_url *url) +{ + GIT_UNUSED(url); + + if ((ctx->ntlm = ntlm_client_init(NTLM_CLIENT_DEFAULTS)) == NULL) { + git_error_set_oom(); + return -1; + } + + return 0; +} + +int git_http_auth_ntlm( + git_http_auth_context **out, + const git_net_url *url) +{ + http_auth_ntlm_context *ctx; + + GIT_UNUSED(url); + + *out = NULL; + + ctx = git__calloc(1, sizeof(http_auth_ntlm_context)); + GIT_ERROR_CHECK_ALLOC(ctx); + + if (ntlm_init_context(ctx, url) < 0) { + git__free(ctx); + return -1; + } + + ctx->parent.type = GIT_HTTP_AUTH_NTLM; + ctx->parent.credtypes = GIT_CREDENTIAL_USERPASS_PLAINTEXT; + ctx->parent.connection_affinity = 1; + ctx->parent.set_challenge = ntlm_set_challenge; + ctx->parent.next_token = ntlm_next_token; + ctx->parent.is_complete = ntlm_is_complete; + ctx->parent.free = ntlm_context_free; + + *out = (git_http_auth_context *)ctx; + + return 0; +} + +#endif /* GIT_NTLM */ diff --git a/src/libgit2/transports/auth_ntlm.h b/src/libgit2/transports/auth_ntlm.h new file mode 100644 index 000000000..40689498c --- /dev/null +++ b/src/libgit2/transports/auth_ntlm.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_transports_auth_ntlm_h__ +#define INCLUDE_transports_auth_ntlm_h__ + +#include "auth.h" + +/* NTLM requires a full request/challenge/response */ +#define GIT_AUTH_STEPS_NTLM 2 + +#ifdef GIT_NTLM + +#if defined(GIT_OPENSSL) +# define CRYPT_OPENSSL +#elif defined(GIT_MBEDTLS) +# define CRYPT_MBEDTLS +#elif defined(GIT_SECURE_TRANSPORT) +# define CRYPT_COMMONCRYPTO +#endif + +extern int git_http_auth_ntlm( + git_http_auth_context **out, + const git_net_url *url); + +#else + +#define git_http_auth_ntlm git_http_auth_dummy + +#endif /* GIT_NTLM */ + +#endif + diff --git a/src/libgit2/transports/credential.c b/src/libgit2/transports/credential.c new file mode 100644 index 000000000..6e00b0282 --- /dev/null +++ b/src/libgit2/transports/credential.c @@ -0,0 +1,486 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "git2/credential.h" +#include "git2/sys/credential.h" +#include "git2/credential_helpers.h" + +static int git_credential_ssh_key_type_new( + git_credential **cred, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase, + git_credential_t credtype); + +int git_credential_has_username(git_credential *cred) +{ + if (cred->credtype == GIT_CREDENTIAL_DEFAULT) + return 0; + + return 1; +} + +const char *git_credential_get_username(git_credential *cred) +{ + switch (cred->credtype) { + case GIT_CREDENTIAL_USERNAME: + { + git_credential_username *c = (git_credential_username *) cred; + return c->username; + } + case GIT_CREDENTIAL_USERPASS_PLAINTEXT: + { + git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *) cred; + return c->username; + } + case GIT_CREDENTIAL_SSH_KEY: + case GIT_CREDENTIAL_SSH_MEMORY: + { + git_credential_ssh_key *c = (git_credential_ssh_key *) cred; + return c->username; + } + case GIT_CREDENTIAL_SSH_CUSTOM: + { + git_credential_ssh_custom *c = (git_credential_ssh_custom *) cred; + return c->username; + } + case GIT_CREDENTIAL_SSH_INTERACTIVE: + { + git_credential_ssh_interactive *c = (git_credential_ssh_interactive *) cred; + return c->username; + } + + default: + return NULL; + } +} + +static void plaintext_free(struct git_credential *cred) +{ + git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred; + + git__free(c->username); + + /* Zero the memory which previously held the password */ + if (c->password) { + size_t pass_len = strlen(c->password); + git__memzero(c->password, pass_len); + git__free(c->password); + } + + git__free(c); +} + +int git_credential_userpass_plaintext_new( + git_credential **cred, + const char *username, + const char *password) +{ + git_credential_userpass_plaintext *c; + + GIT_ASSERT_ARG(cred); + GIT_ASSERT_ARG(username); + GIT_ASSERT_ARG(password); + + c = git__malloc(sizeof(git_credential_userpass_plaintext)); + GIT_ERROR_CHECK_ALLOC(c); + + c->parent.credtype = GIT_CREDENTIAL_USERPASS_PLAINTEXT; + c->parent.free = plaintext_free; + c->username = git__strdup(username); + + if (!c->username) { + git__free(c); + return -1; + } + + c->password = git__strdup(password); + + if (!c->password) { + git__free(c->username); + git__free(c); + return -1; + } + + *cred = &c->parent; + return 0; +} + +static void ssh_key_free(struct git_credential *cred) +{ + git_credential_ssh_key *c = + (git_credential_ssh_key *)cred; + + git__free(c->username); + + if (c->privatekey) { + /* Zero the memory which previously held the private key */ + size_t key_len = strlen(c->privatekey); + git__memzero(c->privatekey, key_len); + git__free(c->privatekey); + } + + if (c->passphrase) { + /* Zero the memory which previously held the passphrase */ + size_t pass_len = strlen(c->passphrase); + git__memzero(c->passphrase, pass_len); + git__free(c->passphrase); + } + + if (c->publickey) { + /* Zero the memory which previously held the public key */ + size_t key_len = strlen(c->publickey); + git__memzero(c->publickey, key_len); + git__free(c->publickey); + } + + git__free(c); +} + +static void ssh_interactive_free(struct git_credential *cred) +{ + git_credential_ssh_interactive *c = (git_credential_ssh_interactive *)cred; + + git__free(c->username); + + git__free(c); +} + +static void ssh_custom_free(struct git_credential *cred) +{ + git_credential_ssh_custom *c = (git_credential_ssh_custom *)cred; + + git__free(c->username); + + if (c->publickey) { + /* Zero the memory which previously held the publickey */ + size_t key_len = strlen(c->publickey); + git__memzero(c->publickey, key_len); + git__free(c->publickey); + } + + git__free(c); +} + +static void default_free(struct git_credential *cred) +{ + git_credential_default *c = (git_credential_default *)cred; + + git__free(c); +} + +static void username_free(struct git_credential *cred) +{ + git__free(cred); +} + +int git_credential_ssh_key_new( + git_credential **cred, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ + return git_credential_ssh_key_type_new( + cred, + username, + publickey, + privatekey, + passphrase, + GIT_CREDENTIAL_SSH_KEY); +} + +int git_credential_ssh_key_memory_new( + git_credential **cred, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ +#ifdef GIT_SSH_MEMORY_CREDENTIALS + return git_credential_ssh_key_type_new( + cred, + username, + publickey, + privatekey, + passphrase, + GIT_CREDENTIAL_SSH_MEMORY); +#else + GIT_UNUSED(cred); + GIT_UNUSED(username); + GIT_UNUSED(publickey); + GIT_UNUSED(privatekey); + GIT_UNUSED(passphrase); + + git_error_set(GIT_ERROR_INVALID, + "this version of libgit2 was not built with ssh memory credentials."); + return -1; +#endif +} + +static int git_credential_ssh_key_type_new( + git_credential **cred, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase, + git_credential_t credtype) +{ + git_credential_ssh_key *c; + + GIT_ASSERT_ARG(username); + GIT_ASSERT_ARG(cred); + GIT_ASSERT_ARG(privatekey); + + c = git__calloc(1, sizeof(git_credential_ssh_key)); + GIT_ERROR_CHECK_ALLOC(c); + + c->parent.credtype = credtype; + c->parent.free = ssh_key_free; + + c->username = git__strdup(username); + GIT_ERROR_CHECK_ALLOC(c->username); + + c->privatekey = git__strdup(privatekey); + GIT_ERROR_CHECK_ALLOC(c->privatekey); + + if (publickey) { + c->publickey = git__strdup(publickey); + GIT_ERROR_CHECK_ALLOC(c->publickey); + } + + if (passphrase) { + c->passphrase = git__strdup(passphrase); + GIT_ERROR_CHECK_ALLOC(c->passphrase); + } + + *cred = &c->parent; + return 0; +} + +int git_credential_ssh_interactive_new( + git_credential **out, + const char *username, + git_credential_ssh_interactive_cb prompt_callback, + void *payload) +{ + git_credential_ssh_interactive *c; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(username); + GIT_ASSERT_ARG(prompt_callback); + + c = git__calloc(1, sizeof(git_credential_ssh_interactive)); + GIT_ERROR_CHECK_ALLOC(c); + + c->parent.credtype = GIT_CREDENTIAL_SSH_INTERACTIVE; + c->parent.free = ssh_interactive_free; + + c->username = git__strdup(username); + GIT_ERROR_CHECK_ALLOC(c->username); + + c->prompt_callback = prompt_callback; + c->payload = payload; + + *out = &c->parent; + return 0; +} + +int git_credential_ssh_key_from_agent(git_credential **cred, const char *username) { + git_credential_ssh_key *c; + + GIT_ASSERT_ARG(username); + GIT_ASSERT_ARG(cred); + + c = git__calloc(1, sizeof(git_credential_ssh_key)); + GIT_ERROR_CHECK_ALLOC(c); + + c->parent.credtype = GIT_CREDENTIAL_SSH_KEY; + c->parent.free = ssh_key_free; + + c->username = git__strdup(username); + GIT_ERROR_CHECK_ALLOC(c->username); + + c->privatekey = NULL; + + *cred = &c->parent; + return 0; +} + +int git_credential_ssh_custom_new( + git_credential **cred, + const char *username, + const char *publickey, + size_t publickey_len, + git_credential_sign_cb sign_callback, + void *payload) +{ + git_credential_ssh_custom *c; + + GIT_ASSERT_ARG(username); + GIT_ASSERT_ARG(cred); + + c = git__calloc(1, sizeof(git_credential_ssh_custom)); + GIT_ERROR_CHECK_ALLOC(c); + + c->parent.credtype = GIT_CREDENTIAL_SSH_CUSTOM; + c->parent.free = ssh_custom_free; + + c->username = git__strdup(username); + GIT_ERROR_CHECK_ALLOC(c->username); + + if (publickey_len > 0) { + c->publickey = git__malloc(publickey_len); + GIT_ERROR_CHECK_ALLOC(c->publickey); + + memcpy(c->publickey, publickey, publickey_len); + } + + c->publickey_len = publickey_len; + c->sign_callback = sign_callback; + c->payload = payload; + + *cred = &c->parent; + return 0; +} + +int git_credential_default_new(git_credential **cred) +{ + git_credential_default *c; + + GIT_ASSERT_ARG(cred); + + c = git__calloc(1, sizeof(git_credential_default)); + GIT_ERROR_CHECK_ALLOC(c); + + c->credtype = GIT_CREDENTIAL_DEFAULT; + c->free = default_free; + + *cred = c; + return 0; +} + +int git_credential_username_new(git_credential **cred, const char *username) +{ + git_credential_username *c; + size_t len, allocsize; + + GIT_ASSERT_ARG(cred); + + len = strlen(username); + + GIT_ERROR_CHECK_ALLOC_ADD(&allocsize, sizeof(git_credential_username), len); + GIT_ERROR_CHECK_ALLOC_ADD(&allocsize, allocsize, 1); + c = git__malloc(allocsize); + GIT_ERROR_CHECK_ALLOC(c); + + c->parent.credtype = GIT_CREDENTIAL_USERNAME; + c->parent.free = username_free; + memcpy(c->username, username, len + 1); + + *cred = (git_credential *) c; + return 0; +} + +void git_credential_free(git_credential *cred) +{ + if (!cred) + return; + + cred->free(cred); +} + +/* Deprecated credential functions */ + +#ifndef GIT_DEPRECATE_HARD +int git_cred_has_username(git_credential *cred) +{ + return git_credential_has_username(cred); +} + +const char *git_cred_get_username(git_credential *cred) +{ + return git_credential_get_username(cred); +} + +int git_cred_userpass_plaintext_new( + git_credential **out, + const char *username, + const char *password) +{ + return git_credential_userpass_plaintext_new(out,username, password); +} + +int git_cred_default_new(git_credential **out) +{ + return git_credential_default_new(out); +} + +int git_cred_username_new(git_credential **out, const char *username) +{ + return git_credential_username_new(out, username); +} + +int git_cred_ssh_key_new( + git_credential **out, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ + return git_credential_ssh_key_new(out, username, + publickey, privatekey, passphrase); +} + +int git_cred_ssh_key_memory_new( + git_credential **out, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ + return git_credential_ssh_key_memory_new(out, username, + publickey, privatekey, passphrase); +} + +int git_cred_ssh_interactive_new( + git_credential **out, + const char *username, + git_credential_ssh_interactive_cb prompt_callback, + void *payload) +{ + return git_credential_ssh_interactive_new(out, username, + prompt_callback, payload); +} + +int git_cred_ssh_key_from_agent( + git_credential **out, + const char *username) +{ + return git_credential_ssh_key_from_agent(out, username); +} + +int git_cred_ssh_custom_new( + git_credential **out, + const char *username, + const char *publickey, + size_t publickey_len, + git_credential_sign_cb sign_callback, + void *payload) +{ + return git_credential_ssh_custom_new(out, username, + publickey, publickey_len, sign_callback, payload); +} + +void git_cred_free(git_credential *cred) +{ + git_credential_free(cred); +} +#endif diff --git a/src/libgit2/transports/credential_helpers.c b/src/libgit2/transports/credential_helpers.c new file mode 100644 index 000000000..6d34a4e97 --- /dev/null +++ b/src/libgit2/transports/credential_helpers.c @@ -0,0 +1,68 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "git2/credential_helpers.h" + +int git_credential_userpass( + git_credential **cred, + const char *url, + const char *user_from_url, + unsigned int allowed_types, + void *payload) +{ + git_credential_userpass_payload *userpass = (git_credential_userpass_payload*)payload; + const char *effective_username = NULL; + + GIT_UNUSED(url); + + if (!userpass || !userpass->password) return -1; + + /* Username resolution: a username can be passed with the URL, the + * credentials payload, or both. Here's what we do. Note that if we get + * this far, we know that any password the url may contain has already + * failed at least once, so we ignore it. + * + * | Payload | URL | Used | + * +-------------+----------+-----------+ + * | yes | no | payload | + * | yes | yes | payload | + * | no | yes | url | + * | no | no | FAIL | + */ + if (userpass->username) + effective_username = userpass->username; + else if (user_from_url) + effective_username = user_from_url; + else + return -1; + + if (GIT_CREDENTIAL_USERNAME & allowed_types) + return git_credential_username_new(cred, effective_username); + + if ((GIT_CREDENTIAL_USERPASS_PLAINTEXT & allowed_types) == 0 || + git_credential_userpass_plaintext_new(cred, effective_username, userpass->password) < 0) + return -1; + + return 0; +} + +/* Deprecated credential functions */ + +#ifndef GIT_DEPRECATE_HARD +int git_cred_userpass( + git_credential **out, + const char *url, + const char *user_from_url, + unsigned int allowed_types, + void *payload) +{ + return git_credential_userpass(out, url, user_from_url, + allowed_types, payload); +} +#endif diff --git a/src/libgit2/transports/git.c b/src/libgit2/transports/git.c new file mode 100644 index 000000000..591e2ab03 --- /dev/null +++ b/src/libgit2/transports/git.c @@ -0,0 +1,361 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "netops.h" +#include "stream.h" +#include "streams/socket.h" +#include "git2/sys/transport.h" + +#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport) + +static const char prefix_git[] = "git://"; +static const char cmd_uploadpack[] = "git-upload-pack"; +static const char cmd_receivepack[] = "git-receive-pack"; + +typedef struct { + git_smart_subtransport_stream parent; + git_stream *io; + const char *cmd; + char *url; + unsigned sent_command : 1; +} git_proto_stream; + +typedef struct { + git_smart_subtransport parent; + git_transport *owner; + git_proto_stream *current_stream; +} git_subtransport; + +/* + * Create a git protocol request. + * + * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0 + */ +static int gen_proto(git_str *request, const char *cmd, const char *url) +{ + char *delim, *repo; + char host[] = "host="; + size_t len; + + delim = strchr(url, '/'); + if (delim == NULL) { + git_error_set(GIT_ERROR_NET, "malformed URL"); + return -1; + } + + repo = delim; + if (repo[1] == '~') + ++repo; + + delim = strchr(url, ':'); + if (delim == NULL) + delim = strchr(url, '/'); + + len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1; + + git_str_grow(request, len); + git_str_printf(request, "%04x%s %s%c%s", + (unsigned int)(len & 0x0FFFF), cmd, repo, 0, host); + git_str_put(request, url, delim - url); + git_str_putc(request, '\0'); + + if (git_str_oom(request)) + return -1; + + return 0; +} + +static int send_command(git_proto_stream *s) +{ + git_str request = GIT_STR_INIT; + int error; + + if ((error = gen_proto(&request, s->cmd, s->url)) < 0) + goto cleanup; + + if ((error = git_stream__write_full(s->io, request.ptr, request.size, 0)) < 0) + goto cleanup; + + s->sent_command = 1; + +cleanup: + git_str_dispose(&request); + return error; +} + +static int git_proto_stream_read( + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read) +{ + int error; + git_proto_stream *s = (git_proto_stream *)stream; + gitno_buffer buf; + + *bytes_read = 0; + + if (!s->sent_command && (error = send_command(s)) < 0) + return error; + + gitno_buffer_setup_fromstream(s->io, &buf, buffer, buf_size); + + if ((error = gitno_recv(&buf)) < 0) + return error; + + *bytes_read = buf.offset; + + return 0; +} + +static int git_proto_stream_write( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + git_proto_stream *s = (git_proto_stream *)stream; + int error; + + if (!s->sent_command && (error = send_command(s)) < 0) + return error; + + return git_stream__write_full(s->io, buffer, len, 0); +} + +static void git_proto_stream_free(git_smart_subtransport_stream *stream) +{ + git_proto_stream *s; + git_subtransport *t; + + if (!stream) + return; + + s = (git_proto_stream *)stream; + t = OWNING_SUBTRANSPORT(s); + + t->current_stream = NULL; + + git_stream_close(s->io); + git_stream_free(s->io); + git__free(s->url); + git__free(s); +} + +static int git_proto_stream_alloc( + git_subtransport *t, + const char *url, + const char *cmd, + const char *host, + const char *port, + git_smart_subtransport_stream **stream) +{ + git_proto_stream *s; + + if (!stream) + return -1; + + s = git__calloc(1, sizeof(git_proto_stream)); + GIT_ERROR_CHECK_ALLOC(s); + + s->parent.subtransport = &t->parent; + s->parent.read = git_proto_stream_read; + s->parent.write = git_proto_stream_write; + s->parent.free = git_proto_stream_free; + + s->cmd = cmd; + s->url = git__strdup(url); + + if (!s->url) { + git__free(s); + return -1; + } + + if ((git_socket_stream_new(&s->io, host, port)) < 0) + return -1; + + GIT_ERROR_CHECK_VERSION(s->io, GIT_STREAM_VERSION, "git_stream"); + + *stream = &s->parent; + return 0; +} + +static int _git_uploadpack_ls( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + git_net_url urldata = GIT_NET_URL_INIT; + const char *stream_url = url; + const char *host, *port; + git_proto_stream *s; + int error; + + *stream = NULL; + + if (!git__prefixcmp(url, prefix_git)) + stream_url += strlen(prefix_git); + + if ((error = git_net_url_parse(&urldata, url)) < 0) + return error; + + host = urldata.host; + port = urldata.port ? urldata.port : GIT_DEFAULT_PORT; + + error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream); + + git_net_url_dispose(&urldata); + + if (error < 0) { + git_proto_stream_free(*stream); + return error; + } + + s = (git_proto_stream *) *stream; + if ((error = git_stream_connect(s->io)) < 0) { + git_proto_stream_free(*stream); + return error; + } + + t->current_stream = s; + + return 0; +} + +static int _git_uploadpack( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + GIT_UNUSED(url); + + if (t->current_stream) { + *stream = &t->current_stream->parent; + return 0; + } + + git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK"); + return -1; +} + +static int _git_receivepack_ls( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + git_net_url urldata = GIT_NET_URL_INIT; + const char *stream_url = url; + git_proto_stream *s; + int error; + + *stream = NULL; + if (!git__prefixcmp(url, prefix_git)) + stream_url += strlen(prefix_git); + + if ((error = git_net_url_parse(&urldata, url)) < 0) + return error; + + error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, urldata.host, urldata.port, stream); + + git_net_url_dispose(&urldata); + + if (error < 0) { + git_proto_stream_free(*stream); + return error; + } + + s = (git_proto_stream *) *stream; + + if ((error = git_stream_connect(s->io)) < 0) + return error; + + t->current_stream = s; + + return 0; +} + +static int _git_receivepack( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + GIT_UNUSED(url); + + if (t->current_stream) { + *stream = &t->current_stream->parent; + return 0; + } + + git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK"); + return -1; +} + +static int _git_action( + git_smart_subtransport_stream **stream, + git_smart_subtransport *subtransport, + const char *url, + git_smart_service_t action) +{ + git_subtransport *t = (git_subtransport *) subtransport; + + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + return _git_uploadpack_ls(t, url, stream); + + case GIT_SERVICE_UPLOADPACK: + return _git_uploadpack(t, url, stream); + + case GIT_SERVICE_RECEIVEPACK_LS: + return _git_receivepack_ls(t, url, stream); + + case GIT_SERVICE_RECEIVEPACK: + return _git_receivepack(t, url, stream); + } + + *stream = NULL; + return -1; +} + +static int _git_close(git_smart_subtransport *subtransport) +{ + git_subtransport *t = (git_subtransport *) subtransport; + + GIT_ASSERT(!t->current_stream); + + GIT_UNUSED(t); + + return 0; +} + +static void _git_free(git_smart_subtransport *subtransport) +{ + git_subtransport *t = (git_subtransport *) subtransport; + + git__free(t); +} + +int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner, void *param) +{ + git_subtransport *t; + + GIT_UNUSED(param); + + if (!out) + return -1; + + t = git__calloc(1, sizeof(git_subtransport)); + GIT_ERROR_CHECK_ALLOC(t); + + t->owner = owner; + t->parent.action = _git_action; + t->parent.close = _git_close; + t->parent.free = _git_free; + + *out = (git_smart_subtransport *) t; + return 0; +} diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c new file mode 100644 index 000000000..7db5582ca --- /dev/null +++ b/src/libgit2/transports/http.c @@ -0,0 +1,760 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#ifndef GIT_WINHTTP + +#include "http_parser.h" +#include "net.h" +#include "netops.h" +#include "remote.h" +#include "smart.h" +#include "auth.h" +#include "http.h" +#include "auth_negotiate.h" +#include "auth_ntlm.h" +#include "trace.h" +#include "streams/tls.h" +#include "streams/socket.h" +#include "httpclient.h" +#include "git2/sys/credential.h" + +bool git_http__expect_continue = false; + +typedef enum { + HTTP_STATE_NONE = 0, + HTTP_STATE_SENDING_REQUEST, + HTTP_STATE_RECEIVING_RESPONSE, + HTTP_STATE_DONE +} http_state; + +typedef struct { + git_http_method method; + const char *url; + const char *request_type; + const char *response_type; + unsigned int initial : 1, + chunked : 1; +} http_service; + +typedef struct { + git_smart_subtransport_stream parent; + const http_service *service; + http_state state; + unsigned replay_count; +} http_stream; + +typedef struct { + git_net_url url; + + git_credential *cred; + unsigned auth_schemetypes; + unsigned url_cred_presented : 1; +} http_server; + +typedef struct { + git_smart_subtransport parent; + transport_smart *owner; + + http_server server; + http_server proxy; + + git_http_client *http_client; +} http_subtransport; + +static const http_service upload_pack_ls_service = { + GIT_HTTP_METHOD_GET, "/info/refs?service=git-upload-pack", + NULL, + "application/x-git-upload-pack-advertisement", + 1, + 0 +}; +static const http_service upload_pack_service = { + GIT_HTTP_METHOD_POST, "/git-upload-pack", + "application/x-git-upload-pack-request", + "application/x-git-upload-pack-result", + 0, + 0 +}; +static const http_service receive_pack_ls_service = { + GIT_HTTP_METHOD_GET, "/info/refs?service=git-receive-pack", + NULL, + "application/x-git-receive-pack-advertisement", + 1, + 0 +}; +static const http_service receive_pack_service = { + GIT_HTTP_METHOD_POST, "/git-receive-pack", + "application/x-git-receive-pack-request", + "application/x-git-receive-pack-result", + 0, + 1 +}; + +#define SERVER_TYPE_REMOTE "remote" +#define SERVER_TYPE_PROXY "proxy" + +#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport) + +static int apply_url_credentials( + git_credential **cred, + unsigned int allowed_types, + const char *username, + const char *password) +{ + GIT_ASSERT_ARG(username); + + if (!password) + password = ""; + + if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) + return git_credential_userpass_plaintext_new(cred, username, password); + + if ((allowed_types & GIT_CREDENTIAL_DEFAULT) && *username == '\0' && *password == '\0') + return git_credential_default_new(cred); + + return GIT_PASSTHROUGH; +} + +GIT_INLINE(void) free_cred(git_credential **cred) +{ + if (*cred) { + git_credential_free(*cred); + (*cred) = NULL; + } +} + +static int handle_auth( + http_server *server, + const char *server_type, + const char *url, + unsigned int allowed_schemetypes, + unsigned int allowed_credtypes, + git_credential_acquire_cb callback, + void *callback_payload) +{ + int error = 1; + + if (server->cred) + free_cred(&server->cred); + + /* Start with URL-specified credentials, if there were any. */ + if ((allowed_credtypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT) && + !server->url_cred_presented && + server->url.username) { + error = apply_url_credentials(&server->cred, allowed_credtypes, server->url.username, server->url.password); + server->url_cred_presented = 1; + + /* treat GIT_PASSTHROUGH as if callback isn't set */ + if (error == GIT_PASSTHROUGH) + error = 1; + } + + if (error > 0 && callback) { + error = callback(&server->cred, url, server->url.username, allowed_credtypes, callback_payload); + + /* treat GIT_PASSTHROUGH as if callback isn't set */ + if (error == GIT_PASSTHROUGH) + error = 1; + } + + if (error > 0) { + git_error_set(GIT_ERROR_HTTP, "%s authentication required but no callback set", server_type); + error = GIT_EAUTH; + } + + if (!error) + server->auth_schemetypes = allowed_schemetypes; + + return error; +} + +GIT_INLINE(int) handle_remote_auth( + http_stream *stream, + git_http_response *response) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_remote_connect_options *connect_opts = &transport->owner->connect_opts; + + if (response->server_auth_credtypes == 0) { + git_error_set(GIT_ERROR_HTTP, "server requires authentication that we do not support"); + return GIT_EAUTH; + } + + /* Otherwise, prompt for credentials. */ + return handle_auth( + &transport->server, + SERVER_TYPE_REMOTE, + transport->owner->url, + response->server_auth_schemetypes, + response->server_auth_credtypes, + connect_opts->callbacks.credentials, + connect_opts->callbacks.payload); +} + +GIT_INLINE(int) handle_proxy_auth( + http_stream *stream, + git_http_response *response) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_remote_connect_options *connect_opts = &transport->owner->connect_opts; + + if (response->proxy_auth_credtypes == 0) { + git_error_set(GIT_ERROR_HTTP, "proxy requires authentication that we do not support"); + return GIT_EAUTH; + } + + /* Otherwise, prompt for credentials. */ + return handle_auth( + &transport->proxy, + SERVER_TYPE_PROXY, + connect_opts->proxy_opts.url, + response->server_auth_schemetypes, + response->proxy_auth_credtypes, + connect_opts->proxy_opts.credentials, + connect_opts->proxy_opts.payload); +} + +static bool allow_redirect(http_stream *stream) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + + switch (transport->owner->connect_opts.follow_redirects) { + case GIT_REMOTE_REDIRECT_INITIAL: + return (stream->service->initial == 1); + case GIT_REMOTE_REDIRECT_ALL: + return true; + default: + return false; + } +} + +static int handle_response( + bool *complete, + http_stream *stream, + git_http_response *response, + bool allow_replay) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + int error; + + *complete = false; + + if (allow_replay && git_http_response_is_redirect(response)) { + if (!response->location) { + git_error_set(GIT_ERROR_HTTP, "redirect without location"); + return -1; + } + + if (git_net_url_apply_redirect(&transport->server.url, response->location, allow_redirect(stream), stream->service->url) < 0) { + return -1; + } + + return 0; + } else if (git_http_response_is_redirect(response)) { + git_error_set(GIT_ERROR_HTTP, "unexpected redirect"); + return -1; + } + + /* If we're in the middle of challenge/response auth, continue. */ + if (allow_replay && response->resend_credentials) { + return 0; + } else if (allow_replay && response->status == GIT_HTTP_STATUS_UNAUTHORIZED) { + if ((error = handle_remote_auth(stream, response)) < 0) + return error; + + return git_http_client_skip_body(transport->http_client); + } else if (allow_replay && response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { + if ((error = handle_proxy_auth(stream, response)) < 0) + return error; + + return git_http_client_skip_body(transport->http_client); + } else if (response->status == GIT_HTTP_STATUS_UNAUTHORIZED || + response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { + git_error_set(GIT_ERROR_HTTP, "unexpected authentication failure"); + return GIT_EAUTH; + } + + if (response->status != GIT_HTTP_STATUS_OK) { + git_error_set(GIT_ERROR_HTTP, "unexpected http status code: %d", response->status); + return -1; + } + + /* The response must contain a Content-Type header. */ + if (!response->content_type) { + git_error_set(GIT_ERROR_HTTP, "no content-type header in response"); + return -1; + } + + /* The Content-Type header must match our expectation. */ + if (strcmp(response->content_type, stream->service->response_type) != 0) { + git_error_set(GIT_ERROR_HTTP, "invalid content-type: '%s'", response->content_type); + return -1; + } + + *complete = true; + stream->state = HTTP_STATE_RECEIVING_RESPONSE; + return 0; +} + +static int lookup_proxy( + bool *out_use, + http_subtransport *transport) +{ + git_remote_connect_options *connect_opts = &transport->owner->connect_opts; + const char *proxy; + git_remote *remote; + char *config = NULL; + int error = 0; + + *out_use = false; + git_net_url_dispose(&transport->proxy.url); + + switch (connect_opts->proxy_opts.type) { + case GIT_PROXY_SPECIFIED: + proxy = connect_opts->proxy_opts.url; + break; + + case GIT_PROXY_AUTO: + remote = transport->owner->owner; + + error = git_remote__http_proxy(&config, remote, &transport->server.url); + + if (error || !config) + goto done; + + proxy = config; + break; + + default: + return 0; + } + + if (!proxy || + (error = git_net_url_parse(&transport->proxy.url, proxy)) < 0) + goto done; + + *out_use = true; + +done: + git__free(config); + return error; +} + +static int generate_request( + git_net_url *url, + git_http_request *request, + http_stream *stream, + size_t len) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + bool use_proxy = false; + int error; + + if ((error = git_net_url_joinpath(url, + &transport->server.url, stream->service->url)) < 0 || + (error = lookup_proxy(&use_proxy, transport)) < 0) + return error; + + request->method = stream->service->method; + request->url = url; + request->credentials = transport->server.cred; + request->proxy = use_proxy ? &transport->proxy.url : NULL; + request->proxy_credentials = transport->proxy.cred; + request->custom_headers = &transport->owner->connect_opts.custom_headers; + + if (stream->service->method == GIT_HTTP_METHOD_POST) { + request->chunked = stream->service->chunked; + request->content_length = stream->service->chunked ? 0 : len; + request->content_type = stream->service->request_type; + request->accept = stream->service->response_type; + request->expect_continue = git_http__expect_continue; + } + + return 0; +} + +/* + * Read from an HTTP transport - for the first invocation of this function + * (ie, when stream->state == HTTP_STATE_NONE), we'll send a GET request + * to the remote host. We will stream that data back on all subsequent + * calls. + */ +static int http_stream_read( + git_smart_subtransport_stream *s, + char *buffer, + size_t buffer_size, + size_t *out_len) +{ + http_stream *stream = (http_stream *)s; + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_net_url url = GIT_NET_URL_INIT; + git_net_url proxy_url = GIT_NET_URL_INIT; + git_http_request request = {0}; + git_http_response response = {0}; + bool complete; + int error; + + *out_len = 0; + + if (stream->state == HTTP_STATE_NONE) { + stream->state = HTTP_STATE_SENDING_REQUEST; + stream->replay_count = 0; + } + + /* + * Formulate the URL, send the request and read the response + * headers. Some of the request body may also be read. + */ + while (stream->state == HTTP_STATE_SENDING_REQUEST && + stream->replay_count < GIT_HTTP_REPLAY_MAX) { + git_net_url_dispose(&url); + git_net_url_dispose(&proxy_url); + git_http_response_dispose(&response); + + if ((error = generate_request(&url, &request, stream, 0)) < 0 || + (error = git_http_client_send_request( + transport->http_client, &request)) < 0 || + (error = git_http_client_read_response( + &response, transport->http_client)) < 0 || + (error = handle_response(&complete, stream, &response, true)) < 0) + goto done; + + if (complete) + break; + + stream->replay_count++; + } + + if (stream->state == HTTP_STATE_SENDING_REQUEST) { + git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); + error = GIT_ERROR; /* not GIT_EAUTH, because the exact cause is unclear */ + goto done; + } + + GIT_ASSERT(stream->state == HTTP_STATE_RECEIVING_RESPONSE); + + error = git_http_client_read_body(transport->http_client, buffer, buffer_size); + + if (error > 0) { + *out_len = error; + error = 0; + } + +done: + git_net_url_dispose(&url); + git_net_url_dispose(&proxy_url); + git_http_response_dispose(&response); + + return error; +} + +static bool needs_probe(http_stream *stream) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + + return (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM || + transport->server.auth_schemetypes == GIT_HTTP_AUTH_NEGOTIATE); +} + +static int send_probe(http_stream *stream) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_http_client *client = transport->http_client; + const char *probe = "0000"; + size_t len = 4; + git_net_url url = GIT_NET_URL_INIT; + git_http_request request = {0}; + git_http_response response = {0}; + bool complete = false; + size_t step, steps = 1; + int error; + + /* NTLM requires a full challenge/response */ + if (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM) + steps = GIT_AUTH_STEPS_NTLM; + + /* + * Send at most two requests: one without any authentication to see + * if we get prompted to authenticate. If we do, send a second one + * with the first authentication message. The final authentication + * message with the response will occur with the *actual* POST data. + */ + for (step = 0; step < steps && !complete; step++) { + git_net_url_dispose(&url); + git_http_response_dispose(&response); + + if ((error = generate_request(&url, &request, stream, len)) < 0 || + (error = git_http_client_send_request(client, &request)) < 0 || + (error = git_http_client_send_body(client, probe, len)) < 0 || + (error = git_http_client_read_response(&response, client)) < 0 || + (error = git_http_client_skip_body(client)) < 0 || + (error = handle_response(&complete, stream, &response, true)) < 0) + goto done; + } + +done: + git_http_response_dispose(&response); + git_net_url_dispose(&url); + return error; +} + +/* +* Write to an HTTP transport - for the first invocation of this function +* (ie, when stream->state == HTTP_STATE_NONE), we'll send a POST request +* to the remote host. If we're sending chunked data, then subsequent calls +* will write the additional data given in the buffer. If we're not chunking, +* then the caller should have given us all the data in the original call. +* The caller should call http_stream_read_response to get the result. +*/ +static int http_stream_write( + git_smart_subtransport_stream *s, + const char *buffer, + size_t len) +{ + http_stream *stream = GIT_CONTAINER_OF(s, http_stream, parent); + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_net_url url = GIT_NET_URL_INIT; + git_http_request request = {0}; + git_http_response response = {0}; + int error; + + while (stream->state == HTTP_STATE_NONE && + stream->replay_count < GIT_HTTP_REPLAY_MAX) { + + git_net_url_dispose(&url); + git_http_response_dispose(&response); + + /* + * If we're authenticating with a connection-based mechanism + * (NTLM, Kerberos), send a "probe" packet. Servers SHOULD + * authenticate an entire keep-alive connection, so ideally + * we should not need to authenticate but some servers do + * not support this. By sending a probe packet, we'll be + * able to follow up with a second POST using the actual + * data (and, in the degenerate case, the authentication + * header as well). + */ + if (needs_probe(stream) && (error = send_probe(stream)) < 0) + goto done; + + /* Send the regular POST request. */ + if ((error = generate_request(&url, &request, stream, len)) < 0 || + (error = git_http_client_send_request( + transport->http_client, &request)) < 0) + goto done; + + if (request.expect_continue && + git_http_client_has_response(transport->http_client)) { + bool complete; + + /* + * If we got a response to an expect/continue, then + * it's something other than a 100 and we should + * deal with the response somehow. + */ + if ((error = git_http_client_read_response(&response, transport->http_client)) < 0 || + (error = handle_response(&complete, stream, &response, true)) < 0) + goto done; + } else { + stream->state = HTTP_STATE_SENDING_REQUEST; + } + + stream->replay_count++; + } + + if (stream->state == HTTP_STATE_NONE) { + git_error_set(GIT_ERROR_HTTP, + "too many redirects or authentication replays"); + error = GIT_ERROR; /* not GIT_EAUTH because the exact cause is unclear */ + goto done; + } + + GIT_ASSERT(stream->state == HTTP_STATE_SENDING_REQUEST); + + error = git_http_client_send_body(transport->http_client, buffer, len); + +done: + git_http_response_dispose(&response); + git_net_url_dispose(&url); + return error; +} + +/* +* Read from an HTTP transport after it has been written to. This is the +* response from a POST request made by http_stream_write. +*/ +static int http_stream_read_response( + git_smart_subtransport_stream *s, + char *buffer, + size_t buffer_size, + size_t *out_len) +{ + http_stream *stream = (http_stream *)s; + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_http_client *client = transport->http_client; + git_http_response response = {0}; + bool complete; + int error; + + *out_len = 0; + + if (stream->state == HTTP_STATE_SENDING_REQUEST) { + if ((error = git_http_client_read_response(&response, client)) < 0 || + (error = handle_response(&complete, stream, &response, false)) < 0) + goto done; + + GIT_ASSERT(complete); + stream->state = HTTP_STATE_RECEIVING_RESPONSE; + } + + error = git_http_client_read_body(client, buffer, buffer_size); + + if (error > 0) { + *out_len = error; + error = 0; + } + +done: + git_http_response_dispose(&response); + return error; +} + +static void http_stream_free(git_smart_subtransport_stream *stream) +{ + http_stream *s = GIT_CONTAINER_OF(stream, http_stream, parent); + git__free(s); +} + +static const http_service *select_service(git_smart_service_t action) +{ + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + return &upload_pack_ls_service; + case GIT_SERVICE_UPLOADPACK: + return &upload_pack_service; + case GIT_SERVICE_RECEIVEPACK_LS: + return &receive_pack_ls_service; + case GIT_SERVICE_RECEIVEPACK: + return &receive_pack_service; + } + + return NULL; +} + +static int http_action( + git_smart_subtransport_stream **out, + git_smart_subtransport *t, + const char *url, + git_smart_service_t action) +{ + http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); + git_remote_connect_options *connect_opts = &transport->owner->connect_opts; + http_stream *stream; + const http_service *service; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(t); + + *out = NULL; + + /* + * If we've seen a redirect then preserve the location that we've + * been given. This is important to continue authorization against + * the redirect target, not the user-given source; the endpoint may + * have redirected us from HTTP->HTTPS and is using an auth mechanism + * that would be insecure in plaintext (eg, HTTP Basic). + */ + if (!git_net_url_valid(&transport->server.url) && + (error = git_net_url_parse(&transport->server.url, url)) < 0) + return error; + + if ((service = select_service(action)) == NULL) { + git_error_set(GIT_ERROR_HTTP, "invalid action"); + return -1; + } + + stream = git__calloc(sizeof(http_stream), 1); + GIT_ERROR_CHECK_ALLOC(stream); + + if (!transport->http_client) { + git_http_client_options opts = {0}; + + opts.server_certificate_check_cb = connect_opts->callbacks.certificate_check; + opts.server_certificate_check_payload = connect_opts->callbacks.payload; + opts.proxy_certificate_check_cb = connect_opts->proxy_opts.certificate_check; + opts.proxy_certificate_check_payload = connect_opts->proxy_opts.payload; + + if (git_http_client_new(&transport->http_client, &opts) < 0) + return -1; + } + + stream->service = service; + stream->parent.subtransport = &transport->parent; + + if (service->method == GIT_HTTP_METHOD_GET) { + stream->parent.read = http_stream_read; + } else { + stream->parent.write = http_stream_write; + stream->parent.read = http_stream_read_response; + } + + stream->parent.free = http_stream_free; + + *out = (git_smart_subtransport_stream *)stream; + return 0; +} + +static int http_close(git_smart_subtransport *t) +{ + http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); + + free_cred(&transport->server.cred); + free_cred(&transport->proxy.cred); + + transport->server.url_cred_presented = false; + transport->proxy.url_cred_presented = false; + + git_net_url_dispose(&transport->server.url); + git_net_url_dispose(&transport->proxy.url); + + return 0; +} + +static void http_free(git_smart_subtransport *t) +{ + http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); + + git_http_client_free(transport->http_client); + + http_close(t); + git__free(transport); +} + +int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param) +{ + http_subtransport *transport; + + GIT_UNUSED(param); + + GIT_ASSERT_ARG(out); + + transport = git__calloc(sizeof(http_subtransport), 1); + GIT_ERROR_CHECK_ALLOC(transport); + + transport->owner = (transport_smart *)owner; + transport->parent.action = http_action; + transport->parent.close = http_close; + transport->parent.free = http_free; + + *out = (git_smart_subtransport *) transport; + return 0; +} + +#endif /* !GIT_WINHTTP */ diff --git a/src/libgit2/transports/http.h b/src/libgit2/transports/http.h new file mode 100644 index 000000000..8e8e7226e --- /dev/null +++ b/src/libgit2/transports/http.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_transports_http_h__ +#define INCLUDE_transports_http_h__ + +#include "settings.h" +#include "httpclient.h" + +#define GIT_HTTP_REPLAY_MAX 15 + +extern bool git_http__expect_continue; + +GIT_INLINE(int) git_http__user_agent(git_str *buf) +{ + const char *ua = git_libgit2__user_agent(); + + if (!ua) + ua = "libgit2 " LIBGIT2_VERSION; + + return git_str_printf(buf, "git/2.0 (%s)", ua); +} + +#endif diff --git a/src/libgit2/transports/httpclient.c b/src/libgit2/transports/httpclient.c new file mode 100644 index 000000000..75782da82 --- /dev/null +++ b/src/libgit2/transports/httpclient.c @@ -0,0 +1,1579 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "git2.h" +#include "http_parser.h" +#include "vector.h" +#include "trace.h" +#include "httpclient.h" +#include "http.h" +#include "auth.h" +#include "auth_negotiate.h" +#include "auth_ntlm.h" +#include "git2/sys/credential.h" +#include "net.h" +#include "stream.h" +#include "streams/socket.h" +#include "streams/tls.h" +#include "auth.h" + +static git_http_auth_scheme auth_schemes[] = { + { GIT_HTTP_AUTH_NEGOTIATE, "Negotiate", GIT_CREDENTIAL_DEFAULT, git_http_auth_negotiate }, + { GIT_HTTP_AUTH_NTLM, "NTLM", GIT_CREDENTIAL_USERPASS_PLAINTEXT, git_http_auth_ntlm }, + { GIT_HTTP_AUTH_BASIC, "Basic", GIT_CREDENTIAL_USERPASS_PLAINTEXT, git_http_auth_basic }, +}; + +/* + * Use a 16kb read buffer to match the maximum size of a TLS packet. This + * is critical for compatibility with SecureTransport, which will always do + * a network read on every call, even if it has data buffered to return to + * you. That buffered data may be the _end_ of a keep-alive response, so + * if SecureTransport performs another network read, it will wait until the + * server ultimately times out before it returns that buffered data to you. + * Since SecureTransport only reads a single TLS packet at a time, by + * calling it with a read buffer that is the maximum size of a TLS packet, + * we ensure that it will never buffer. + */ +#define GIT_READ_BUFFER_SIZE (16 * 1024) + +typedef struct { + git_net_url url; + git_stream *stream; + + git_vector auth_challenges; + git_http_auth_context *auth_context; +} git_http_server; + +typedef enum { + PROXY = 1, + SERVER +} git_http_server_t; + +typedef enum { + NONE = 0, + SENDING_REQUEST, + SENDING_BODY, + SENT_REQUEST, + HAS_EARLY_RESPONSE, + READING_RESPONSE, + READING_BODY, + DONE +} http_client_state; + +/* Parser state */ +typedef enum { + PARSE_HEADER_NONE = 0, + PARSE_HEADER_NAME, + PARSE_HEADER_VALUE, + PARSE_HEADER_COMPLETE +} parse_header_state; + +typedef enum { + PARSE_STATUS_OK, + PARSE_STATUS_NO_OUTPUT, + PARSE_STATUS_ERROR +} parse_status; + +typedef struct { + git_http_client *client; + git_http_response *response; + + /* Temporary buffers to avoid extra mallocs */ + git_str parse_header_name; + git_str parse_header_value; + + /* Parser state */ + int error; + parse_status parse_status; + + /* Headers parsing */ + parse_header_state parse_header_state; + + /* Body parsing */ + char *output_buf; /* Caller's output buffer */ + size_t output_size; /* Size of caller's output buffer */ + size_t output_written; /* Bytes we've written to output buffer */ +} http_parser_context; + +/* HTTP client connection */ +struct git_http_client { + git_http_client_options opts; + + /* Are we writing to the proxy or server, and state of the client. */ + git_http_server_t current_server; + http_client_state state; + + http_parser parser; + + git_http_server server; + git_http_server proxy; + + unsigned request_count; + unsigned connected : 1, + proxy_connected : 1, + keepalive : 1, + request_chunked : 1; + + /* Temporary buffers to avoid extra mallocs */ + git_str request_msg; + git_str read_buf; + + /* A subset of information from the request */ + size_t request_body_len, + request_body_remain; + + /* + * When state == HAS_EARLY_RESPONSE, the response of our proxy + * that we have buffered and will deliver during read_response. + */ + git_http_response early_response; +}; + +bool git_http_response_is_redirect(git_http_response *response) +{ + return (response->status == GIT_HTTP_MOVED_PERMANENTLY || + response->status == GIT_HTTP_FOUND || + response->status == GIT_HTTP_SEE_OTHER || + response->status == GIT_HTTP_TEMPORARY_REDIRECT || + response->status == GIT_HTTP_PERMANENT_REDIRECT); +} + +void git_http_response_dispose(git_http_response *response) +{ + if (!response) + return; + + git__free(response->content_type); + git__free(response->location); + + memset(response, 0, sizeof(git_http_response)); +} + +static int on_header_complete(http_parser *parser) +{ + http_parser_context *ctx = (http_parser_context *) parser->data; + git_http_client *client = ctx->client; + git_http_response *response = ctx->response; + + git_str *name = &ctx->parse_header_name; + git_str *value = &ctx->parse_header_value; + + if (!strcasecmp("Content-Type", name->ptr)) { + if (response->content_type) { + git_error_set(GIT_ERROR_HTTP, + "multiple content-type headers"); + return -1; + } + + response->content_type = + git__strndup(value->ptr, value->size); + GIT_ERROR_CHECK_ALLOC(ctx->response->content_type); + } else if (!strcasecmp("Content-Length", name->ptr)) { + int64_t len; + + if (response->content_length) { + git_error_set(GIT_ERROR_HTTP, + "multiple content-length headers"); + return -1; + } + + if (git__strntol64(&len, value->ptr, value->size, + NULL, 10) < 0 || len < 0) { + git_error_set(GIT_ERROR_HTTP, + "invalid content-length"); + return -1; + } + + response->content_length = (size_t)len; + } else if (!strcasecmp("Transfer-Encoding", name->ptr) && + !strcasecmp("chunked", value->ptr)) { + ctx->response->chunked = 1; + } else if (!strcasecmp("Proxy-Authenticate", git_str_cstr(name))) { + char *dup = git__strndup(value->ptr, value->size); + GIT_ERROR_CHECK_ALLOC(dup); + + if (git_vector_insert(&client->proxy.auth_challenges, dup) < 0) + return -1; + } else if (!strcasecmp("WWW-Authenticate", name->ptr)) { + char *dup = git__strndup(value->ptr, value->size); + GIT_ERROR_CHECK_ALLOC(dup); + + if (git_vector_insert(&client->server.auth_challenges, dup) < 0) + return -1; + } else if (!strcasecmp("Location", name->ptr)) { + if (response->location) { + git_error_set(GIT_ERROR_HTTP, + "multiple location headers"); + return -1; + } + + response->location = git__strndup(value->ptr, value->size); + GIT_ERROR_CHECK_ALLOC(response->location); + } + + return 0; +} + +static int on_header_field(http_parser *parser, const char *str, size_t len) +{ + http_parser_context *ctx = (http_parser_context *) parser->data; + + switch (ctx->parse_header_state) { + /* + * We last saw a header value, process the name/value pair and + * get ready to handle this new name. + */ + case PARSE_HEADER_VALUE: + if (on_header_complete(parser) < 0) + return ctx->parse_status = PARSE_STATUS_ERROR; + + git_str_clear(&ctx->parse_header_name); + git_str_clear(&ctx->parse_header_value); + /* Fall through */ + + case PARSE_HEADER_NONE: + case PARSE_HEADER_NAME: + ctx->parse_header_state = PARSE_HEADER_NAME; + + if (git_str_put(&ctx->parse_header_name, str, len) < 0) + return ctx->parse_status = PARSE_STATUS_ERROR; + + break; + + default: + git_error_set(GIT_ERROR_HTTP, + "header name seen at unexpected time"); + return ctx->parse_status = PARSE_STATUS_ERROR; + } + + return 0; +} + +static int on_header_value(http_parser *parser, const char *str, size_t len) +{ + http_parser_context *ctx = (http_parser_context *) parser->data; + + switch (ctx->parse_header_state) { + case PARSE_HEADER_NAME: + case PARSE_HEADER_VALUE: + ctx->parse_header_state = PARSE_HEADER_VALUE; + + if (git_str_put(&ctx->parse_header_value, str, len) < 0) + return ctx->parse_status = PARSE_STATUS_ERROR; + + break; + + default: + git_error_set(GIT_ERROR_HTTP, + "header value seen at unexpected time"); + return ctx->parse_status = PARSE_STATUS_ERROR; + } + + return 0; +} + +GIT_INLINE(bool) challenge_matches_scheme( + const char *challenge, + git_http_auth_scheme *scheme) +{ + const char *scheme_name = scheme->name; + size_t scheme_len = strlen(scheme_name); + + if (!strncasecmp(challenge, scheme_name, scheme_len) && + (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' ')) + return true; + + return false; +} + +static git_http_auth_scheme *scheme_for_challenge(const char *challenge) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) { + if (challenge_matches_scheme(challenge, &auth_schemes[i])) + return &auth_schemes[i]; + } + + return NULL; +} + +GIT_INLINE(void) collect_authinfo( + unsigned int *schemetypes, + unsigned int *credtypes, + git_vector *challenges) +{ + git_http_auth_scheme *scheme; + const char *challenge; + size_t i; + + *schemetypes = 0; + *credtypes = 0; + + git_vector_foreach(challenges, i, challenge) { + if ((scheme = scheme_for_challenge(challenge)) != NULL) { + *schemetypes |= scheme->type; + *credtypes |= scheme->credtypes; + } + } +} + +static int resend_needed(git_http_client *client, git_http_response *response) +{ + git_http_auth_context *auth_context; + + if (response->status == GIT_HTTP_STATUS_UNAUTHORIZED && + (auth_context = client->server.auth_context) && + auth_context->is_complete && + !auth_context->is_complete(auth_context)) + return 1; + + if (response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED && + (auth_context = client->proxy.auth_context) && + auth_context->is_complete && + !auth_context->is_complete(auth_context)) + return 1; + + return 0; +} + +static int on_headers_complete(http_parser *parser) +{ + http_parser_context *ctx = (http_parser_context *) parser->data; + + /* Finalize the last seen header */ + switch (ctx->parse_header_state) { + case PARSE_HEADER_VALUE: + if (on_header_complete(parser) < 0) + return ctx->parse_status = PARSE_STATUS_ERROR; + + /* Fall through */ + + case PARSE_HEADER_NONE: + ctx->parse_header_state = PARSE_HEADER_COMPLETE; + break; + + default: + git_error_set(GIT_ERROR_HTTP, + "header completion at unexpected time"); + return ctx->parse_status = PARSE_STATUS_ERROR; + } + + ctx->response->status = parser->status_code; + ctx->client->keepalive = http_should_keep_alive(parser); + + /* Prepare for authentication */ + collect_authinfo(&ctx->response->server_auth_schemetypes, + &ctx->response->server_auth_credtypes, + &ctx->client->server.auth_challenges); + collect_authinfo(&ctx->response->proxy_auth_schemetypes, + &ctx->response->proxy_auth_credtypes, + &ctx->client->proxy.auth_challenges); + + ctx->response->resend_credentials = resend_needed(ctx->client, + ctx->response); + + /* Stop parsing. */ + http_parser_pause(parser, 1); + + if (ctx->response->content_type || ctx->response->chunked) + ctx->client->state = READING_BODY; + else + ctx->client->state = DONE; + + return 0; +} + +static int on_body(http_parser *parser, const char *buf, size_t len) +{ + http_parser_context *ctx = (http_parser_context *) parser->data; + size_t max_len; + + /* Saw data when we expected not to (eg, in consume_response_body) */ + if (ctx->output_buf == NULL && ctx->output_size == 0) { + ctx->parse_status = PARSE_STATUS_NO_OUTPUT; + return 0; + } + + GIT_ASSERT(ctx->output_size >= ctx->output_written); + + max_len = min(ctx->output_size - ctx->output_written, len); + max_len = min(max_len, INT_MAX); + + memcpy(ctx->output_buf + ctx->output_written, buf, max_len); + ctx->output_written += max_len; + + return 0; +} + +static int on_message_complete(http_parser *parser) +{ + http_parser_context *ctx = (http_parser_context *) parser->data; + + ctx->client->state = DONE; + return 0; +} + +GIT_INLINE(int) stream_write( + git_http_server *server, + const char *data, + size_t len) +{ + git_trace(GIT_TRACE_TRACE, + "Sending request:\n%.*s", (int)len, data); + + return git_stream__write_full(server->stream, data, len, 0); +} + +GIT_INLINE(int) client_write_request(git_http_client *client) +{ + git_stream *stream = client->current_server == PROXY ? + client->proxy.stream : client->server.stream; + + git_trace(GIT_TRACE_TRACE, + "Sending request:\n%.*s", + (int)client->request_msg.size, client->request_msg.ptr); + + return git_stream__write_full(stream, + client->request_msg.ptr, + client->request_msg.size, + 0); +} + +static const char *name_for_method(git_http_method method) +{ + switch (method) { + case GIT_HTTP_METHOD_GET: + return "GET"; + case GIT_HTTP_METHOD_POST: + return "POST"; + case GIT_HTTP_METHOD_CONNECT: + return "CONNECT"; + } + + return NULL; +} + +/* + * Find the scheme that is suitable for the given credentials, based on the + * server's auth challenges. + */ +static bool best_scheme_and_challenge( + git_http_auth_scheme **scheme_out, + const char **challenge_out, + git_vector *challenges, + git_credential *credentials) +{ + const char *challenge; + size_t i, j; + + for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) { + git_vector_foreach(challenges, j, challenge) { + git_http_auth_scheme *scheme = &auth_schemes[i]; + + if (challenge_matches_scheme(challenge, scheme) && + (scheme->credtypes & credentials->credtype)) { + *scheme_out = scheme; + *challenge_out = challenge; + return true; + } + } + } + + return false; +} + +/* + * Find the challenge from the server for our current auth context. + */ +static const char *challenge_for_context( + git_vector *challenges, + git_http_auth_context *auth_ctx) +{ + const char *challenge; + size_t i, j; + + for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) { + if (auth_schemes[i].type == auth_ctx->type) { + git_http_auth_scheme *scheme = &auth_schemes[i]; + + git_vector_foreach(challenges, j, challenge) { + if (challenge_matches_scheme(challenge, scheme)) + return challenge; + } + } + } + + return NULL; +} + +static const char *init_auth_context( + git_http_server *server, + git_vector *challenges, + git_credential *credentials) +{ + git_http_auth_scheme *scheme; + const char *challenge; + int error; + + if (!best_scheme_and_challenge(&scheme, &challenge, challenges, credentials)) { + git_error_set(GIT_ERROR_HTTP, "could not find appropriate mechanism for credentials"); + return NULL; + } + + error = scheme->init_context(&server->auth_context, &server->url); + + if (error == GIT_PASSTHROUGH) { + git_error_set(GIT_ERROR_HTTP, "'%s' authentication is not supported", scheme->name); + return NULL; + } + + return challenge; +} + +static void free_auth_context(git_http_server *server) +{ + if (!server->auth_context) + return; + + if (server->auth_context->free) + server->auth_context->free(server->auth_context); + + server->auth_context = NULL; +} + +static int apply_credentials( + git_str *buf, + git_http_server *server, + const char *header_name, + git_credential *credentials) +{ + git_http_auth_context *auth = server->auth_context; + git_vector *challenges = &server->auth_challenges; + const char *challenge; + git_str token = GIT_STR_INIT; + int error = 0; + + /* We've started a new request without creds; free the context. */ + if (auth && !credentials) { + free_auth_context(server); + return 0; + } + + /* We haven't authenticated, nor were we asked to. Nothing to do. */ + if (!auth && !git_vector_length(challenges)) + return 0; + + if (!auth) { + challenge = init_auth_context(server, challenges, credentials); + auth = server->auth_context; + + if (!challenge || !auth) { + error = -1; + goto done; + } + } else if (auth->set_challenge) { + challenge = challenge_for_context(challenges, auth); + } + + if (auth->set_challenge && challenge && + (error = auth->set_challenge(auth, challenge)) < 0) + goto done; + + if ((error = auth->next_token(&token, auth, credentials)) < 0) + goto done; + + if (auth->is_complete && auth->is_complete(auth)) { + /* + * If we're done with an auth mechanism with connection affinity, + * we don't need to send any more headers and can dispose the context. + */ + if (auth->connection_affinity) + free_auth_context(server); + } else if (!token.size) { + git_error_set(GIT_ERROR_HTTP, "failed to respond to authentication challenge"); + error = GIT_EAUTH; + goto done; + } + + if (token.size > 0) + error = git_str_printf(buf, "%s: %s\r\n", header_name, token.ptr); + +done: + git_str_dispose(&token); + return error; +} + +GIT_INLINE(int) apply_server_credentials( + git_str *buf, + git_http_client *client, + git_http_request *request) +{ + return apply_credentials(buf, + &client->server, + "Authorization", + request->credentials); +} + +GIT_INLINE(int) apply_proxy_credentials( + git_str *buf, + git_http_client *client, + git_http_request *request) +{ + return apply_credentials(buf, + &client->proxy, + "Proxy-Authorization", + request->proxy_credentials); +} + +static int puts_host_and_port(git_str *buf, git_net_url *url, bool force_port) +{ + bool ipv6 = git_net_url_is_ipv6(url); + + if (ipv6) + git_str_putc(buf, '['); + + git_str_puts(buf, url->host); + + if (ipv6) + git_str_putc(buf, ']'); + + if (force_port || !git_net_url_is_default_port(url)) { + git_str_putc(buf, ':'); + git_str_puts(buf, url->port); + } + + return git_str_oom(buf) ? -1 : 0; +} + +static int generate_connect_request( + git_http_client *client, + git_http_request *request) +{ + git_str *buf; + int error; + + git_str_clear(&client->request_msg); + buf = &client->request_msg; + + git_str_puts(buf, "CONNECT "); + puts_host_and_port(buf, &client->server.url, true); + git_str_puts(buf, " HTTP/1.1\r\n"); + + git_str_puts(buf, "User-Agent: "); + git_http__user_agent(buf); + git_str_puts(buf, "\r\n"); + + git_str_puts(buf, "Host: "); + puts_host_and_port(buf, &client->server.url, true); + git_str_puts(buf, "\r\n"); + + if ((error = apply_proxy_credentials(buf, client, request) < 0)) + return -1; + + git_str_puts(buf, "\r\n"); + + return git_str_oom(buf) ? -1 : 0; +} + +static bool use_connect_proxy(git_http_client *client) +{ + return client->proxy.url.host && !strcmp(client->server.url.scheme, "https"); +} + +static int generate_request( + git_http_client *client, + git_http_request *request) +{ + git_str *buf; + size_t i; + int error; + + GIT_ASSERT_ARG(client); + GIT_ASSERT_ARG(request); + + git_str_clear(&client->request_msg); + buf = &client->request_msg; + + /* GET|POST path HTTP/1.1 */ + git_str_puts(buf, name_for_method(request->method)); + git_str_putc(buf, ' '); + + if (request->proxy && strcmp(request->url->scheme, "https")) + git_net_url_fmt(buf, request->url); + else + git_net_url_fmt_path(buf, request->url); + + git_str_puts(buf, " HTTP/1.1\r\n"); + + git_str_puts(buf, "User-Agent: "); + git_http__user_agent(buf); + git_str_puts(buf, "\r\n"); + + git_str_puts(buf, "Host: "); + puts_host_and_port(buf, request->url, false); + git_str_puts(buf, "\r\n"); + + if (request->accept) + git_str_printf(buf, "Accept: %s\r\n", request->accept); + else + git_str_puts(buf, "Accept: */*\r\n"); + + if (request->content_type) + git_str_printf(buf, "Content-Type: %s\r\n", + request->content_type); + + if (request->chunked) + git_str_puts(buf, "Transfer-Encoding: chunked\r\n"); + + if (request->content_length > 0) + git_str_printf(buf, "Content-Length: %"PRIuZ "\r\n", + request->content_length); + + if (request->expect_continue) + git_str_printf(buf, "Expect: 100-continue\r\n"); + + if ((error = apply_server_credentials(buf, client, request)) < 0 || + (!use_connect_proxy(client) && + (error = apply_proxy_credentials(buf, client, request)) < 0)) + return error; + + if (request->custom_headers) { + for (i = 0; i < request->custom_headers->count; i++) { + const char *hdr = request->custom_headers->strings[i]; + + if (hdr) + git_str_printf(buf, "%s\r\n", hdr); + } + } + + git_str_puts(buf, "\r\n"); + + if (git_str_oom(buf)) + return -1; + + return 0; +} + +static int check_certificate( + git_stream *stream, + git_net_url *url, + int is_valid, + git_transport_certificate_check_cb cert_cb, + void *cert_cb_payload) +{ + git_cert *cert; + git_error_state last_error = {0}; + int error; + + if ((error = git_stream_certificate(&cert, stream)) < 0) + return error; + + git_error_state_capture(&last_error, GIT_ECERTIFICATE); + + error = cert_cb(cert, is_valid, url->host, cert_cb_payload); + + if (error == GIT_PASSTHROUGH && !is_valid) + return git_error_state_restore(&last_error); + else if (error == GIT_PASSTHROUGH) + error = 0; + else if (error && !git_error_last()) + git_error_set(GIT_ERROR_HTTP, + "user rejected certificate for %s", url->host); + + git_error_state_free(&last_error); + return error; +} + +static int server_connect_stream( + git_http_server *server, + git_transport_certificate_check_cb cert_cb, + void *cb_payload) +{ + int error; + + GIT_ERROR_CHECK_VERSION(server->stream, GIT_STREAM_VERSION, "git_stream"); + + error = git_stream_connect(server->stream); + + if (error && error != GIT_ECERTIFICATE) + return error; + + if (git_stream_is_encrypted(server->stream) && cert_cb != NULL) + error = check_certificate(server->stream, &server->url, !error, + cert_cb, cb_payload); + + return error; +} + +static void reset_auth_connection(git_http_server *server) +{ + /* + * If we've authenticated and we're doing "normal" + * authentication with a request affinity (Basic, Digest) + * then we want to _keep_ our context, since authentication + * survives even through non-keep-alive connections. If + * we've authenticated and we're doing connection-based + * authentication (NTLM, Negotiate) - indicated by the presence + * of an `is_complete` callback - then we need to restart + * authentication on a new connection. + */ + + if (server->auth_context && + server->auth_context->connection_affinity) + free_auth_context(server); +} + +/* + * Updates the server data structure with the new URL; returns 1 if the server + * has changed and we need to reconnect, returns 0 otherwise. + */ +GIT_INLINE(int) server_setup_from_url( + git_http_server *server, + git_net_url *url) +{ + if (!server->url.scheme || strcmp(server->url.scheme, url->scheme) || + !server->url.host || strcmp(server->url.host, url->host) || + !server->url.port || strcmp(server->url.port, url->port)) { + git__free(server->url.scheme); + git__free(server->url.host); + git__free(server->url.port); + + server->url.scheme = git__strdup(url->scheme); + GIT_ERROR_CHECK_ALLOC(server->url.scheme); + + server->url.host = git__strdup(url->host); + GIT_ERROR_CHECK_ALLOC(server->url.host); + + server->url.port = git__strdup(url->port); + GIT_ERROR_CHECK_ALLOC(server->url.port); + + return 1; + } + + return 0; +} + +static void reset_parser(git_http_client *client) +{ + http_parser_init(&client->parser, HTTP_RESPONSE); +} + +static int setup_hosts( + git_http_client *client, + git_http_request *request) +{ + int ret, diff = 0; + + GIT_ASSERT_ARG(client); + GIT_ASSERT_ARG(request); + + GIT_ASSERT(request->url); + + if ((ret = server_setup_from_url(&client->server, request->url)) < 0) + return ret; + + diff |= ret; + + if (request->proxy && + (ret = server_setup_from_url(&client->proxy, request->proxy)) < 0) + return ret; + + diff |= ret; + + if (diff) { + free_auth_context(&client->server); + free_auth_context(&client->proxy); + + client->connected = 0; + } + + return 0; +} + +GIT_INLINE(int) server_create_stream(git_http_server *server) +{ + git_net_url *url = &server->url; + + if (strcasecmp(url->scheme, "https") == 0) + return git_tls_stream_new(&server->stream, url->host, url->port); + else if (strcasecmp(url->scheme, "http") == 0) + return git_socket_stream_new(&server->stream, url->host, url->port); + + git_error_set(GIT_ERROR_HTTP, "unknown http scheme '%s'", url->scheme); + return -1; +} + +GIT_INLINE(void) save_early_response( + git_http_client *client, + git_http_response *response) +{ + /* Buffer the response so we can return it in read_response */ + client->state = HAS_EARLY_RESPONSE; + + memcpy(&client->early_response, response, sizeof(git_http_response)); + memset(response, 0, sizeof(git_http_response)); +} + +static int proxy_connect( + git_http_client *client, + git_http_request *request) +{ + git_http_response response = {0}; + int error; + + if (!client->proxy_connected || !client->keepalive) { + git_trace(GIT_TRACE_DEBUG, "Connecting to proxy %s port %s", + client->proxy.url.host, client->proxy.url.port); + + if ((error = server_create_stream(&client->proxy)) < 0 || + (error = server_connect_stream(&client->proxy, + client->opts.proxy_certificate_check_cb, + client->opts.proxy_certificate_check_payload)) < 0) + goto done; + + client->proxy_connected = 1; + } + + client->current_server = PROXY; + client->state = SENDING_REQUEST; + + if ((error = generate_connect_request(client, request)) < 0 || + (error = client_write_request(client)) < 0) + goto done; + + client->state = SENT_REQUEST; + + if ((error = git_http_client_read_response(&response, client)) < 0 || + (error = git_http_client_skip_body(client)) < 0) + goto done; + + GIT_ASSERT(client->state == DONE); + + if (response.status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { + save_early_response(client, &response); + + error = GIT_RETRY; + goto done; + } else if (response.status != GIT_HTTP_STATUS_OK) { + git_error_set(GIT_ERROR_HTTP, "proxy returned unexpected status: %d", response.status); + error = -1; + goto done; + } + + reset_parser(client); + client->state = NONE; + +done: + git_http_response_dispose(&response); + return error; +} + +static int server_connect(git_http_client *client) +{ + git_net_url *url = &client->server.url; + git_transport_certificate_check_cb cert_cb; + void *cert_payload; + int error; + + client->current_server = SERVER; + + if (client->proxy.stream) + error = git_tls_stream_wrap(&client->server.stream, client->proxy.stream, url->host); + else + error = server_create_stream(&client->server); + + if (error < 0) + goto done; + + cert_cb = client->opts.server_certificate_check_cb; + cert_payload = client->opts.server_certificate_check_payload; + + error = server_connect_stream(&client->server, cert_cb, cert_payload); + +done: + return error; +} + +GIT_INLINE(void) close_stream(git_http_server *server) +{ + if (server->stream) { + git_stream_close(server->stream); + git_stream_free(server->stream); + server->stream = NULL; + } +} + +static int http_client_connect( + git_http_client *client, + git_http_request *request) +{ + bool use_proxy = false; + int error; + + if ((error = setup_hosts(client, request)) < 0) + goto on_error; + + /* We're connected to our destination server; no need to reconnect */ + if (client->connected && client->keepalive && + (client->state == NONE || client->state == DONE)) + return 0; + + client->connected = 0; + client->request_count = 0; + + close_stream(&client->server); + reset_auth_connection(&client->server); + + reset_parser(client); + + /* Reconnect to the proxy if necessary. */ + use_proxy = use_connect_proxy(client); + + if (use_proxy) { + if (!client->proxy_connected || !client->keepalive || + (client->state != NONE && client->state != DONE)) { + close_stream(&client->proxy); + reset_auth_connection(&client->proxy); + + client->proxy_connected = 0; + } + + if ((error = proxy_connect(client, request)) < 0) + goto on_error; + } + + git_trace(GIT_TRACE_DEBUG, "Connecting to remote %s port %s", + client->server.url.host, client->server.url.port); + + if ((error = server_connect(client)) < 0) + goto on_error; + + client->connected = 1; + return error; + +on_error: + if (error != GIT_RETRY) + close_stream(&client->proxy); + + close_stream(&client->server); + return error; +} + +GIT_INLINE(int) client_read(git_http_client *client) +{ + http_parser_context *parser_context = client->parser.data; + git_stream *stream; + char *buf = client->read_buf.ptr + client->read_buf.size; + size_t max_len; + ssize_t read_len; + + stream = client->current_server == PROXY ? + client->proxy.stream : client->server.stream; + + /* + * We use a git_str for convenience, but statically allocate it and + * don't resize. Limit our consumption to INT_MAX since calling + * functions use an int return type to return number of bytes read. + */ + max_len = client->read_buf.asize - client->read_buf.size; + max_len = min(max_len, INT_MAX); + + if (parser_context->output_size) + max_len = min(max_len, parser_context->output_size); + + if (max_len == 0) { + git_error_set(GIT_ERROR_HTTP, "no room in output buffer"); + return -1; + } + + read_len = git_stream_read(stream, buf, max_len); + + if (read_len >= 0) { + client->read_buf.size += read_len; + + git_trace(GIT_TRACE_TRACE, "Received:\n%.*s", + (int)read_len, buf); + } + + return (int)read_len; +} + +static bool parser_settings_initialized; +static http_parser_settings parser_settings; + +GIT_INLINE(http_parser_settings *) http_client_parser_settings(void) +{ + if (!parser_settings_initialized) { + parser_settings.on_header_field = on_header_field; + parser_settings.on_header_value = on_header_value; + parser_settings.on_headers_complete = on_headers_complete; + parser_settings.on_body = on_body; + parser_settings.on_message_complete = on_message_complete; + + parser_settings_initialized = true; + } + + return &parser_settings; +} + +GIT_INLINE(int) client_read_and_parse(git_http_client *client) +{ + http_parser *parser = &client->parser; + http_parser_context *ctx = (http_parser_context *) parser->data; + unsigned char http_errno; + int read_len; + size_t parsed_len; + + /* + * If we have data in our read buffer, that means we stopped early + * when parsing headers. Use the data in the read buffer instead of + * reading more from the socket. + */ + if (!client->read_buf.size && (read_len = client_read(client)) < 0) + return read_len; + + parsed_len = http_parser_execute(parser, + http_client_parser_settings(), + client->read_buf.ptr, + client->read_buf.size); + http_errno = client->parser.http_errno; + + if (parsed_len > INT_MAX) { + git_error_set(GIT_ERROR_HTTP, "unexpectedly large parse"); + return -1; + } + + if (ctx->parse_status == PARSE_STATUS_ERROR) { + client->connected = 0; + return ctx->error ? ctx->error : -1; + } + + /* + * If we finished reading the headers or body, we paused parsing. + * Otherwise the parser will start filling the body, or even parse + * a new response if the server pipelined us multiple responses. + * (This can happen in response to an expect/continue request, + * where the server gives you a 100 and 200 simultaneously.) + */ + if (http_errno == HPE_PAUSED) { + /* + * http-parser has a "feature" where it will not deliver the + * final byte when paused in a callback. Consume that byte. + * https://github.com/nodejs/http-parser/issues/97 + */ + GIT_ASSERT(client->read_buf.size > parsed_len); + + http_parser_pause(parser, 0); + + parsed_len += http_parser_execute(parser, + http_client_parser_settings(), + client->read_buf.ptr + parsed_len, + 1); + } + + /* Most failures will be reported in http_errno */ + else if (parser->http_errno != HPE_OK) { + git_error_set(GIT_ERROR_HTTP, "http parser error: %s", + http_errno_description(http_errno)); + return -1; + } + + /* Otherwise we should have consumed the entire buffer. */ + else if (parsed_len != client->read_buf.size) { + git_error_set(GIT_ERROR_HTTP, + "http parser did not consume entire buffer: %s", + http_errno_description(http_errno)); + return -1; + } + + /* recv returned 0, the server hung up on us */ + else if (!parsed_len) { + git_error_set(GIT_ERROR_HTTP, "unexpected EOF"); + return -1; + } + + git_str_consume_bytes(&client->read_buf, parsed_len); + + return (int)parsed_len; +} + +/* + * See if we've consumed the entire response body. If the client was + * reading the body but did not consume it entirely, it's possible that + * they knew that the stream had finished (in a git response, seeing a + * final flush) and stopped reading. But if the response was chunked, + * we may have not consumed the final chunk marker. Consume it to + * ensure that we don't have it waiting in our socket. If there's + * more than just a chunk marker, close the connection. + */ +static void complete_response_body(git_http_client *client) +{ + http_parser_context parser_context = {0}; + + /* If we're not keeping alive, don't bother. */ + if (!client->keepalive) { + client->connected = 0; + goto done; + } + + parser_context.client = client; + client->parser.data = &parser_context; + + /* If there was an error, just close the connection. */ + if (client_read_and_parse(client) < 0 || + parser_context.error != HPE_OK || + (parser_context.parse_status != PARSE_STATUS_OK && + parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) { + git_error_clear(); + client->connected = 0; + } + +done: + git_str_clear(&client->read_buf); +} + +int git_http_client_send_request( + git_http_client *client, + git_http_request *request) +{ + git_http_response response = {0}; + int error = -1; + + GIT_ASSERT_ARG(client); + GIT_ASSERT_ARG(request); + + /* If the client did not finish reading, clean up the stream. */ + if (client->state == READING_BODY) + complete_response_body(client); + + /* If we're waiting for proxy auth, don't sending more requests. */ + if (client->state == HAS_EARLY_RESPONSE) + return 0; + + if (git_trace_level() >= GIT_TRACE_DEBUG) { + git_str url = GIT_STR_INIT; + git_net_url_fmt(&url, request->url); + git_trace(GIT_TRACE_DEBUG, "Sending %s request to %s", + name_for_method(request->method), + url.ptr ? url.ptr : ""); + git_str_dispose(&url); + } + + if ((error = http_client_connect(client, request)) < 0 || + (error = generate_request(client, request)) < 0 || + (error = client_write_request(client)) < 0) + goto done; + + client->state = SENT_REQUEST; + + if (request->expect_continue) { + if ((error = git_http_client_read_response(&response, client)) < 0 || + (error = git_http_client_skip_body(client)) < 0) + goto done; + + error = 0; + + if (response.status != GIT_HTTP_STATUS_CONTINUE) { + save_early_response(client, &response); + goto done; + } + } + + if (request->content_length || request->chunked) { + client->state = SENDING_BODY; + client->request_body_len = request->content_length; + client->request_body_remain = request->content_length; + client->request_chunked = request->chunked; + } + + reset_parser(client); + +done: + if (error == GIT_RETRY) + error = 0; + + git_http_response_dispose(&response); + return error; +} + +bool git_http_client_has_response(git_http_client *client) +{ + return (client->state == HAS_EARLY_RESPONSE || + client->state > SENT_REQUEST); +} + +int git_http_client_send_body( + git_http_client *client, + const char *buffer, + size_t buffer_len) +{ + git_http_server *server; + git_str hdr = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(client); + + /* If we're waiting for proxy auth, don't sending more requests. */ + if (client->state == HAS_EARLY_RESPONSE) + return 0; + + if (client->state != SENDING_BODY) { + git_error_set(GIT_ERROR_HTTP, "client is in invalid state"); + return -1; + } + + if (!buffer_len) + return 0; + + server = &client->server; + + if (client->request_body_len) { + GIT_ASSERT(buffer_len <= client->request_body_remain); + + if ((error = stream_write(server, buffer, buffer_len)) < 0) + goto done; + + client->request_body_remain -= buffer_len; + } else { + if ((error = git_str_printf(&hdr, "%" PRIxZ "\r\n", buffer_len)) < 0 || + (error = stream_write(server, hdr.ptr, hdr.size)) < 0 || + (error = stream_write(server, buffer, buffer_len)) < 0 || + (error = stream_write(server, "\r\n", 2)) < 0) + goto done; + } + +done: + git_str_dispose(&hdr); + return error; +} + +static int complete_request(git_http_client *client) +{ + int error = 0; + + GIT_ASSERT_ARG(client); + GIT_ASSERT(client->state == SENDING_BODY); + + if (client->request_body_len && client->request_body_remain) { + git_error_set(GIT_ERROR_HTTP, "truncated write"); + error = -1; + } else if (client->request_chunked) { + error = stream_write(&client->server, "0\r\n\r\n", 5); + } + + client->state = SENT_REQUEST; + return error; +} + +int git_http_client_read_response( + git_http_response *response, + git_http_client *client) +{ + http_parser_context parser_context = {0}; + int error; + + GIT_ASSERT_ARG(response); + GIT_ASSERT_ARG(client); + + if (client->state == SENDING_BODY) { + if ((error = complete_request(client)) < 0) + goto done; + } + + if (client->state == HAS_EARLY_RESPONSE) { + memcpy(response, &client->early_response, sizeof(git_http_response)); + memset(&client->early_response, 0, sizeof(git_http_response)); + client->state = DONE; + return 0; + } + + if (client->state != SENT_REQUEST) { + git_error_set(GIT_ERROR_HTTP, "client is in invalid state"); + error = -1; + goto done; + } + + git_http_response_dispose(response); + + if (client->current_server == PROXY) { + git_vector_free_deep(&client->proxy.auth_challenges); + } else if(client->current_server == SERVER) { + git_vector_free_deep(&client->server.auth_challenges); + } + + client->state = READING_RESPONSE; + client->keepalive = 0; + client->parser.data = &parser_context; + + parser_context.client = client; + parser_context.response = response; + + while (client->state == READING_RESPONSE) { + if ((error = client_read_and_parse(client)) < 0) + goto done; + } + + GIT_ASSERT(client->state == READING_BODY || client->state == DONE); + +done: + git_str_dispose(&parser_context.parse_header_name); + git_str_dispose(&parser_context.parse_header_value); + + return error; +} + +int git_http_client_read_body( + git_http_client *client, + char *buffer, + size_t buffer_size) +{ + http_parser_context parser_context = {0}; + int error = 0; + + if (client->state == DONE) + return 0; + + if (client->state != READING_BODY) { + git_error_set(GIT_ERROR_HTTP, "client is in invalid state"); + return -1; + } + + /* + * Now we'll read from the socket and http_parser will pipeline the + * data directly to the client. + */ + + parser_context.client = client; + parser_context.output_buf = buffer; + parser_context.output_size = buffer_size; + + client->parser.data = &parser_context; + + /* + * Clients expect to get a non-zero amount of data from us, + * so we either block until we have data to return, until we + * hit EOF or there's an error. Do this in a loop, since we + * may end up reading only some stream metadata (like chunk + * information). + */ + while (!parser_context.output_written) { + error = client_read_and_parse(client); + + if (error <= 0) + goto done; + + if (client->state == DONE) + break; + } + + GIT_ASSERT(parser_context.output_written <= INT_MAX); + error = (int)parser_context.output_written; + +done: + if (error < 0) + client->connected = 0; + + return error; +} + +int git_http_client_skip_body(git_http_client *client) +{ + http_parser_context parser_context = {0}; + int error; + + if (client->state == DONE) + return 0; + + if (client->state != READING_BODY) { + git_error_set(GIT_ERROR_HTTP, "client is in invalid state"); + return -1; + } + + parser_context.client = client; + client->parser.data = &parser_context; + + do { + error = client_read_and_parse(client); + + if (parser_context.error != HPE_OK || + (parser_context.parse_status != PARSE_STATUS_OK && + parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) { + git_error_set(GIT_ERROR_HTTP, + "unexpected data handled in callback"); + error = -1; + } + } while (error >= 0 && client->state != DONE); + + if (error < 0) + client->connected = 0; + + return error; +} + +/* + * Create an http_client capable of communicating with the given remote + * host. + */ +int git_http_client_new( + git_http_client **out, + git_http_client_options *opts) +{ + git_http_client *client; + + GIT_ASSERT_ARG(out); + + client = git__calloc(1, sizeof(git_http_client)); + GIT_ERROR_CHECK_ALLOC(client); + + git_str_init(&client->read_buf, GIT_READ_BUFFER_SIZE); + GIT_ERROR_CHECK_ALLOC(client->read_buf.ptr); + + if (opts) + memcpy(&client->opts, opts, sizeof(git_http_client_options)); + + *out = client; + return 0; +} + +GIT_INLINE(void) http_server_close(git_http_server *server) +{ + if (server->stream) { + git_stream_close(server->stream); + git_stream_free(server->stream); + server->stream = NULL; + } + + git_net_url_dispose(&server->url); + + git_vector_free_deep(&server->auth_challenges); + free_auth_context(server); +} + +static void http_client_close(git_http_client *client) +{ + http_server_close(&client->server); + http_server_close(&client->proxy); + + git_str_dispose(&client->request_msg); + + client->state = 0; + client->request_count = 0; + client->connected = 0; + client->keepalive = 0; +} + +void git_http_client_free(git_http_client *client) +{ + if (!client) + return; + + http_client_close(client); + git_str_dispose(&client->read_buf); + git__free(client); +} diff --git a/src/libgit2/transports/httpclient.h b/src/libgit2/transports/httpclient.h new file mode 100644 index 000000000..6d0ef9edb --- /dev/null +++ b/src/libgit2/transports/httpclient.h @@ -0,0 +1,190 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_transports_httpclient_h__ +#define INCLUDE_transports_httpclient_h__ + +#include "common.h" +#include "net.h" + +#define GIT_HTTP_STATUS_CONTINUE 100 +#define GIT_HTTP_STATUS_OK 200 +#define GIT_HTTP_MOVED_PERMANENTLY 301 +#define GIT_HTTP_FOUND 302 +#define GIT_HTTP_SEE_OTHER 303 +#define GIT_HTTP_TEMPORARY_REDIRECT 307 +#define GIT_HTTP_PERMANENT_REDIRECT 308 +#define GIT_HTTP_STATUS_UNAUTHORIZED 401 +#define GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED 407 + +typedef struct git_http_client git_http_client; + +/** Method for the HTTP request */ +typedef enum { + GIT_HTTP_METHOD_GET, + GIT_HTTP_METHOD_POST, + GIT_HTTP_METHOD_CONNECT +} git_http_method; + +/** An HTTP request */ +typedef struct { + git_http_method method; /**< Method for the request */ + git_net_url *url; /**< Full request URL */ + git_net_url *proxy; /**< Proxy to use */ + + /* Headers */ + const char *accept; /**< Contents of the Accept header */ + const char *content_type; /**< Content-Type header (for POST) */ + git_credential *credentials; /**< Credentials to authenticate with */ + git_credential *proxy_credentials; /**< Credentials for proxy */ + git_strarray *custom_headers; /**< Additional headers to deliver */ + + /* To POST a payload, either set content_length OR set chunked. */ + size_t content_length; /**< Length of the POST body */ + unsigned chunked : 1, /**< Post with chunking */ + expect_continue : 1; /**< Use expect/continue negotiation */ +} git_http_request; + +typedef struct { + int status; + + /* Headers */ + char *content_type; + size_t content_length; + char *location; + + /* Authentication headers */ + unsigned server_auth_schemetypes; /**< Schemes requested by remote */ + unsigned server_auth_credtypes; /**< Supported cred types for remote */ + + unsigned proxy_auth_schemetypes; /**< Schemes requested by proxy */ + unsigned proxy_auth_credtypes; /**< Supported cred types for proxy */ + + unsigned chunked : 1, /**< Response body is chunked */ + resend_credentials : 1; /**< Resend with authentication */ +} git_http_response; + +typedef struct { + /** Certificate check callback for the remote */ + git_transport_certificate_check_cb server_certificate_check_cb; + void *server_certificate_check_payload; + + /** Certificate check callback for the proxy */ + git_transport_certificate_check_cb proxy_certificate_check_cb; + void *proxy_certificate_check_payload; +} git_http_client_options; + +/** + * Create a new httpclient instance with the given options. + * + * @param out pointer to receive the new instance + * @param opts options to create the client with or NULL for defaults + */ +extern int git_http_client_new( + git_http_client **out, + git_http_client_options *opts); + +/* + * Sends a request to the host specified by the request URL. If the + * method is POST, either the content_length or the chunked flag must + * be specified. The body should be provided in subsequent calls to + * git_http_client_send_body. + * + * @param client the client to write the request to + * @param request the request to send + */ +extern int git_http_client_send_request( + git_http_client *client, + git_http_request *request); + +/* + * After sending a request, there may already be a response to read -- + * either because there was a non-continue response to an expect: continue + * request, or because the server pipelined a response to us before we even + * sent the request. Examine the state. + * + * @param client the client to examine + * @return true if there's already a response to read, false otherwise + */ +extern bool git_http_client_has_response(git_http_client *client); + +/** + * Sends the given buffer to the remote as part of the request body. The + * request must have specified either a content_length or the chunked flag. + * + * @param client the client to write the request body to + * @param buffer the request body + * @param buffer_len number of bytes of the buffer to send + */ +extern int git_http_client_send_body( + git_http_client *client, + const char *buffer, + size_t buffer_len); + +/** + * Reads the headers of a response to a request. This will consume the + * entirety of the headers of a response from the server. The body (if any) + * can be read by calling git_http_client_read_body. Callers must free + * the response with git_http_response_dispose. + * + * @param response pointer to the response object to fill + * @param client the client to read the response from + */ +extern int git_http_client_read_response( + git_http_response *response, + git_http_client *client); + +/** + * Reads some or all of the body of a response. At most buffer_size (or + * INT_MAX) bytes will be read and placed into the buffer provided. The + * number of bytes read will be returned, or 0 to indicate that the end of + * the body has been read. + * + * @param client the client to read the response from + * @param buffer pointer to the buffer to fill + * @param buffer_size the maximum number of bytes to read + * @return the number of bytes read, 0 on end of body, or error code + */ +extern int git_http_client_read_body( + git_http_client *client, + char *buffer, + size_t buffer_size); + +/** + * Reads all of the (remainder of the) body of the response and ignores it. + * None of the data from the body will be returned to the caller. + * + * @param client the client to read the response from + * @return 0 or an error code + */ +extern int git_http_client_skip_body(git_http_client *client); + +/** + * Examines the status code of the response to determine if it is a + * redirect of any type (eg, 301, 302, etc). + * + * @param response the response to inspect + * @return true if the response is a redirect, false otherwise + */ +extern bool git_http_response_is_redirect(git_http_response *response); + +/** + * Frees any memory associated with the response. + * + * @param response the response to free + */ +extern void git_http_response_dispose(git_http_response *response); + +/** + * Frees any memory associated with the client. If any sockets are open, + * they will be closed. + * + * @param client the client to free + */ +extern void git_http_client_free(git_http_client *client); + +#endif diff --git a/src/libgit2/transports/local.c b/src/libgit2/transports/local.c new file mode 100644 index 000000000..6c754a034 --- /dev/null +++ b/src/libgit2/transports/local.c @@ -0,0 +1,754 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "pack-objects.h" +#include "refs.h" +#include "posix.h" +#include "fs_path.h" +#include "repository.h" +#include "odb.h" +#include "push.h" +#include "remote.h" +#include "proxy.h" + +#include "git2/types.h" +#include "git2/net.h" +#include "git2/repository.h" +#include "git2/object.h" +#include "git2/tag.h" +#include "git2/transport.h" +#include "git2/revwalk.h" +#include "git2/odb_backend.h" +#include "git2/pack.h" +#include "git2/commit.h" +#include "git2/revparse.h" +#include "git2/sys/remote.h" + +typedef struct { + git_transport parent; + git_remote *owner; + char *url; + int direction; + git_atomic32 cancelled; + git_repository *repo; + git_remote_connect_options connect_opts; + git_vector refs; + unsigned connected : 1, + have_refs : 1; +} transport_local; + +static void free_head(git_remote_head *head) +{ + git__free(head->name); + git__free(head->symref_target); + git__free(head); +} + +static void free_heads(git_vector *heads) +{ + git_remote_head *head; + size_t i; + + git_vector_foreach(heads, i, head) + free_head(head); + + git_vector_free(heads); +} + +static int add_ref(transport_local *t, const char *name) +{ + const char peeled[] = "^{}"; + git_reference *ref, *resolved; + git_remote_head *head; + git_oid obj_id; + git_object *obj = NULL, *target = NULL; + git_str buf = GIT_STR_INIT; + int error; + + if ((error = git_reference_lookup(&ref, t->repo, name)) < 0) + return error; + + error = git_reference_resolve(&resolved, ref); + if (error < 0) { + git_reference_free(ref); + if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) { + /* This is actually okay. Empty repos often have a HEAD that + * points to a nonexistent "refs/heads/master". */ + git_error_clear(); + return 0; + } + return error; + } + + git_oid_cpy(&obj_id, git_reference_target(resolved)); + git_reference_free(resolved); + + head = git__calloc(1, sizeof(git_remote_head)); + GIT_ERROR_CHECK_ALLOC(head); + + head->name = git__strdup(name); + GIT_ERROR_CHECK_ALLOC(head->name); + + git_oid_cpy(&head->oid, &obj_id); + + if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { + head->symref_target = git__strdup(git_reference_symbolic_target(ref)); + GIT_ERROR_CHECK_ALLOC(head->symref_target); + } + git_reference_free(ref); + + if ((error = git_vector_insert(&t->refs, head)) < 0) { + free_head(head); + return error; + } + + /* If it's not a tag, we don't need to try to peel it */ + if (git__prefixcmp(name, GIT_REFS_TAGS_DIR)) + return 0; + + if ((error = git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJECT_ANY)) < 0) + return error; + + head = NULL; + + /* If it's not an annotated tag, or if we're mocking + * git-receive-pack, just get out */ + if (git_object_type(obj) != GIT_OBJECT_TAG || + t->direction != GIT_DIRECTION_FETCH) { + git_object_free(obj); + return 0; + } + + /* And if it's a tag, peel it, and add it to the list */ + head = git__calloc(1, sizeof(git_remote_head)); + GIT_ERROR_CHECK_ALLOC(head); + + if (git_str_join(&buf, 0, name, peeled) < 0) { + free_head(head); + return -1; + } + head->name = git_str_detach(&buf); + + if (!(error = git_tag_peel(&target, (git_tag *)obj))) { + git_oid_cpy(&head->oid, git_object_id(target)); + + if ((error = git_vector_insert(&t->refs, head)) < 0) { + free_head(head); + } + } + + git_object_free(obj); + git_object_free(target); + + return error; +} + +static int store_refs(transport_local *t) +{ + size_t i; + git_remote_head *head; + git_strarray ref_names = {0}; + + GIT_ASSERT_ARG(t); + + if (git_reference_list(&ref_names, t->repo) < 0) + goto on_error; + + /* Clear all heads we might have fetched in a previous connect */ + git_vector_foreach(&t->refs, i, head) { + git__free(head->name); + git__free(head); + } + + /* Clear the vector so we can reuse it */ + git_vector_clear(&t->refs); + + /* Sort the references first */ + git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb); + + /* Add HEAD iff direction is fetch */ + if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0) + goto on_error; + + for (i = 0; i < ref_names.count; ++i) { + if (add_ref(t, ref_names.strings[i]) < 0) + goto on_error; + } + + t->have_refs = 1; + git_strarray_dispose(&ref_names); + return 0; + +on_error: + git_vector_free(&t->refs); + git_strarray_dispose(&ref_names); + return -1; +} + +/* + * Try to open the url as a git directory. The direction doesn't + * matter in this case because we're calculating the heads ourselves. + */ +static int local_connect( + git_transport *transport, + const char *url, + int direction, + const git_remote_connect_options *connect_opts) +{ + git_repository *repo; + int error; + transport_local *t = (transport_local *)transport; + const char *path; + git_str buf = GIT_STR_INIT; + + if (t->connected) + return 0; + + if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0) + return -1; + + free_heads(&t->refs); + + t->url = git__strdup(url); + GIT_ERROR_CHECK_ALLOC(t->url); + t->direction = direction; + + /* 'url' may be a url or path; convert to a path */ + if ((error = git_fs_path_from_url_or_path(&buf, url)) < 0) { + git_str_dispose(&buf); + return error; + } + path = git_str_cstr(&buf); + + error = git_repository_open(&repo, path); + + git_str_dispose(&buf); + + if (error < 0) + return -1; + + t->repo = repo; + + if (store_refs(t) < 0) + return -1; + + t->connected = 1; + + return 0; +} + +static int local_set_connect_opts( + git_transport *transport, + const git_remote_connect_options *connect_opts) +{ + transport_local *t = (transport_local *)transport; + + if (!t->connected) { + git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected"); + return -1; + } + + return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts); +} + +static int local_capabilities(unsigned int *capabilities, git_transport *transport) +{ + GIT_UNUSED(transport); + + *capabilities = GIT_REMOTE_CAPABILITY_TIP_OID | + GIT_REMOTE_CAPABILITY_REACHABLE_OID; + return 0; +} + +static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + if (!t->have_refs) { + git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs"); + return -1; + } + + *out = (const git_remote_head **)t->refs.contents; + *size = t->refs.length; + + return 0; +} + +static int local_negotiate_fetch( + git_transport *transport, + git_repository *repo, + const git_remote_head * const *refs, + size_t count) +{ + transport_local *t = (transport_local*)transport; + git_remote_head *rhead; + unsigned int i; + + GIT_UNUSED(refs); + GIT_UNUSED(count); + + /* Fill in the loids */ + git_vector_foreach(&t->refs, i, rhead) { + git_object *obj; + + int error = git_revparse_single(&obj, repo, rhead->name); + if (!error) + git_oid_cpy(&rhead->loid, git_object_id(obj)); + else if (error != GIT_ENOTFOUND) + return error; + else + git_error_clear(); + git_object_free(obj); + } + + return 0; +} + +static int local_push_update_remote_ref( + git_repository *remote_repo, + const char *lref, + const char *rref, + git_oid *loid, + git_oid *roid) +{ + int error; + git_reference *remote_ref = NULL; + + /* check for lhs, if it's empty it means to delete */ + if (lref[0] != '\0') { + /* Create or update a ref */ + error = git_reference_create(NULL, remote_repo, rref, loid, + !git_oid_is_zero(roid), NULL); + } else { + /* Delete a ref */ + if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) { + if (error == GIT_ENOTFOUND) + error = 0; + return error; + } + + error = git_reference_delete(remote_ref); + git_reference_free(remote_ref); + } + + return error; +} + +static int transfer_to_push_transfer(const git_indexer_progress *stats, void *payload) +{ + const git_remote_callbacks *cbs = payload; + + if (!cbs || !cbs->push_transfer_progress) + return 0; + + return cbs->push_transfer_progress(stats->received_objects, stats->total_objects, stats->received_bytes, + cbs->payload); +} + +static int local_push( + git_transport *transport, + git_push *push) +{ + transport_local *t = (transport_local *)transport; + git_remote_callbacks *cbs = &t->connect_opts.callbacks; + git_repository *remote_repo = NULL; + push_spec *spec; + char *url = NULL; + const char *path; + git_str buf = GIT_STR_INIT, odb_path = GIT_STR_INIT; + int error; + size_t j; + + /* 'push->remote->url' may be a url or path; convert to a path */ + if ((error = git_fs_path_from_url_or_path(&buf, push->remote->url)) < 0) { + git_str_dispose(&buf); + return error; + } + path = git_str_cstr(&buf); + + error = git_repository_open(&remote_repo, path); + + git_str_dispose(&buf); + + if (error < 0) + return error; + + /* We don't currently support pushing locally to non-bare repos. Proper + non-bare repo push support would require checking configs to see if + we should override the default 'don't let this happen' behavior. + + Note that this is only an issue when pushing to the current branch, + but we forbid all pushes just in case */ + if (!remote_repo->is_bare) { + error = GIT_EBAREREPO; + git_error_set(GIT_ERROR_INVALID, "local push doesn't (yet) support pushing to non-bare repos."); + goto on_error; + } + + if ((error = git_repository__item_path(&odb_path, remote_repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0 + || (error = git_str_joinpath(&odb_path, odb_path.ptr, "pack")) < 0) + goto on_error; + + error = git_packbuilder_write(push->pb, odb_path.ptr, 0, transfer_to_push_transfer, (void *) cbs); + git_str_dispose(&odb_path); + + if (error < 0) + goto on_error; + + push->unpack_ok = 1; + + git_vector_foreach(&push->specs, j, spec) { + push_status *status; + const git_error *last; + char *ref = spec->refspec.dst; + + status = git__calloc(1, sizeof(push_status)); + if (!status) + goto on_error; + + status->ref = git__strdup(ref); + if (!status->ref) { + git_push_status_free(status); + goto on_error; + } + + error = local_push_update_remote_ref(remote_repo, spec->refspec.src, spec->refspec.dst, + &spec->loid, &spec->roid); + + switch (error) { + case GIT_OK: + break; + case GIT_EINVALIDSPEC: + status->msg = git__strdup("funny refname"); + break; + case GIT_ENOTFOUND: + status->msg = git__strdup("Remote branch not found to delete"); + break; + default: + last = git_error_last(); + + if (last && last->message) + status->msg = git__strdup(last->message); + else + status->msg = git__strdup("Unspecified error encountered"); + break; + } + + /* failed to allocate memory for a status message */ + if (error < 0 && !status->msg) { + git_push_status_free(status); + goto on_error; + } + + /* failed to insert the ref update status */ + if ((error = git_vector_insert(&push->status, status)) < 0) { + git_push_status_free(status); + goto on_error; + } + } + + if (push->specs.length) { + url = git__strdup(t->url); + + if (!url || t->parent.close(&t->parent) < 0 || + t->parent.connect(&t->parent, url, + GIT_DIRECTION_PUSH, NULL)) + goto on_error; + } + + error = 0; + +on_error: + git_repository_free(remote_repo); + git__free(url); + + return error; +} + +typedef struct foreach_data { + git_indexer_progress *stats; + git_indexer_progress_cb progress_cb; + void *progress_payload; + git_odb_writepack *writepack; +} foreach_data; + +static int foreach_cb(void *buf, size_t len, void *payload) +{ + foreach_data *data = (foreach_data*)payload; + + data->stats->received_bytes += len; + return data->writepack->append(data->writepack, buf, len, data->stats); +} + +static const char *counting_objects_fmt = "Counting objects %d\r"; +static const char *compressing_objects_fmt = "Compressing objects: %.0f%% (%d/%d)"; + +static int local_counting(int stage, unsigned int current, unsigned int total, void *payload) +{ + git_str progress_info = GIT_STR_INIT; + transport_local *t = payload; + int error; + + if (!t->connect_opts.callbacks.sideband_progress) + return 0; + + if (stage == GIT_PACKBUILDER_ADDING_OBJECTS) { + git_str_printf(&progress_info, counting_objects_fmt, current); + } else if (stage == GIT_PACKBUILDER_DELTAFICATION) { + float perc = (((float) current) / total) * 100; + git_str_printf(&progress_info, compressing_objects_fmt, perc, current, total); + if (current == total) + git_str_printf(&progress_info, ", done\n"); + else + git_str_putc(&progress_info, '\r'); + + } + + if (git_str_oom(&progress_info)) + return -1; + + if (progress_info.size > INT_MAX) { + git_error_set(GIT_ERROR_NET, "remote sent overly large progress data"); + git_str_dispose(&progress_info); + return -1; + } + + + error = t->connect_opts.callbacks.sideband_progress( + progress_info.ptr, + (int)progress_info.size, + t->connect_opts.callbacks.payload); + + git_str_dispose(&progress_info); + return error; +} + +static int foreach_reference_cb(git_reference *reference, void *payload) +{ + git_revwalk *walk = (git_revwalk *)payload; + int error; + + if (git_reference_type(reference) != GIT_REFERENCE_DIRECT) { + git_reference_free(reference); + return 0; + } + + error = git_revwalk_hide(walk, git_reference_target(reference)); + /* The reference is in the local repository, so the target may not + * exist on the remote. It also may not be a commit. */ + if (error == GIT_ENOTFOUND || error == GIT_ERROR_INVALID) { + git_error_clear(); + error = 0; + } + + git_reference_free(reference); + + return error; +} + +static int local_download_pack( + git_transport *transport, + git_repository *repo, + git_indexer_progress *stats) +{ + transport_local *t = (transport_local*)transport; + git_revwalk *walk = NULL; + git_remote_head *rhead; + unsigned int i; + int error = -1; + git_packbuilder *pack = NULL; + git_odb_writepack *writepack = NULL; + git_odb *odb = NULL; + git_str progress_info = GIT_STR_INIT; + foreach_data data = {0}; + + if ((error = git_revwalk_new(&walk, t->repo)) < 0) + goto cleanup; + + git_revwalk_sorting(walk, GIT_SORT_TIME); + + if ((error = git_packbuilder_new(&pack, t->repo)) < 0) + goto cleanup; + + git_packbuilder_set_callbacks(pack, local_counting, t); + + stats->total_objects = 0; + stats->indexed_objects = 0; + stats->received_objects = 0; + stats->received_bytes = 0; + + git_vector_foreach(&t->refs, i, rhead) { + git_object *obj; + if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJECT_ANY)) < 0) + goto cleanup; + + if (git_object_type(obj) == GIT_OBJECT_COMMIT) { + /* Revwalker includes only wanted commits */ + error = git_revwalk_push(walk, &rhead->oid); + } else { + /* Tag or some other wanted object. Add it on its own */ + error = git_packbuilder_insert_recur(pack, &rhead->oid, rhead->name); + } + git_object_free(obj); + if (error < 0) + goto cleanup; + } + + if ((error = git_reference_foreach(repo, foreach_reference_cb, walk))) + goto cleanup; + + if ((error = git_packbuilder_insert_walk(pack, walk))) + goto cleanup; + + if (t->connect_opts.callbacks.sideband_progress) { + if ((error = git_str_printf( + &progress_info, + counting_objects_fmt, + git_packbuilder_object_count(pack))) < 0 || + (error = t->connect_opts.callbacks.sideband_progress( + progress_info.ptr, + (int)progress_info.size, + t->connect_opts.callbacks.payload)) < 0) + goto cleanup; + } + + /* Walk the objects, building a packfile */ + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) + goto cleanup; + + /* One last one with the newline */ + if (t->connect_opts.callbacks.sideband_progress) { + git_str_clear(&progress_info); + + if ((error = git_str_printf( + &progress_info, + counting_objects_fmt, + git_packbuilder_object_count(pack))) < 0 || + (error = git_str_putc(&progress_info, '\n')) < 0 || + (error = t->connect_opts.callbacks.sideband_progress( + progress_info.ptr, + (int)progress_info.size, + t->connect_opts.callbacks.payload)) < 0) + goto cleanup; + } + + if ((error = git_odb_write_pack( + &writepack, + odb, + t->connect_opts.callbacks.transfer_progress, + t->connect_opts.callbacks.payload)) < 0) + goto cleanup; + + /* Write the data to the ODB */ + data.stats = stats; + data.progress_cb = t->connect_opts.callbacks.transfer_progress; + data.progress_payload = t->connect_opts.callbacks.payload; + data.writepack = writepack; + + /* autodetect */ + git_packbuilder_set_threads(pack, 0); + + if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) != 0) + goto cleanup; + + error = writepack->commit(writepack, stats); + +cleanup: + if (writepack) writepack->free(writepack); + git_str_dispose(&progress_info); + git_packbuilder_free(pack); + git_revwalk_free(walk); + return error; +} + +static int local_is_connected(git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + return t->connected; +} + +static void local_cancel(git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + git_atomic32_set(&t->cancelled, 1); +} + +static int local_close(git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + t->connected = 0; + + if (t->repo) { + git_repository_free(t->repo); + t->repo = NULL; + } + + if (t->url) { + git__free(t->url); + t->url = NULL; + } + + return 0; +} + +static void local_free(git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + free_heads(&t->refs); + + /* Close the transport, if it's still open. */ + local_close(transport); + + /* Free the transport */ + git__free(t); +} + +/************** + * Public API * + **************/ + +int git_transport_local(git_transport **out, git_remote *owner, void *param) +{ + int error; + transport_local *t; + + GIT_UNUSED(param); + + t = git__calloc(1, sizeof(transport_local)); + GIT_ERROR_CHECK_ALLOC(t); + + t->parent.version = GIT_TRANSPORT_VERSION; + t->parent.connect = local_connect; + t->parent.set_connect_opts = local_set_connect_opts; + t->parent.capabilities = local_capabilities; + t->parent.negotiate_fetch = local_negotiate_fetch; + t->parent.download_pack = local_download_pack; + t->parent.push = local_push; + t->parent.close = local_close; + t->parent.free = local_free; + t->parent.ls = local_ls; + t->parent.is_connected = local_is_connected; + t->parent.cancel = local_cancel; + + if ((error = git_vector_init(&t->refs, 0, NULL)) < 0) { + git__free(t); + return error; + } + + t->owner = owner; + + *out = (git_transport *) t; + + return 0; +} diff --git a/src/libgit2/transports/smart.c b/src/libgit2/transports/smart.c new file mode 100644 index 000000000..801fcbe53 --- /dev/null +++ b/src/libgit2/transports/smart.c @@ -0,0 +1,472 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "smart.h" + +#include "git2.h" +#include "git2/sys/remote.h" +#include "refs.h" +#include "refspec.h" +#include "proxy.h" + +static int git_smart__recv_cb(gitno_buffer *buf) +{ + transport_smart *t = (transport_smart *) buf->cb_data; + size_t old_len, bytes_read; + int error; + + GIT_ASSERT(t->current_stream); + + old_len = buf->offset; + + if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0) + return error; + + buf->offset += bytes_read; + + if (t->packetsize_cb && !t->cancelled.val) { + error = t->packetsize_cb(bytes_read, t->packetsize_payload); + if (error) { + git_atomic32_set(&t->cancelled, 1); + return GIT_EUSER; + } + } + + return (int)(buf->offset - old_len); +} + +GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport) +{ + if (t->current_stream) { + t->current_stream->free(t->current_stream); + t->current_stream = NULL; + } + + if (close_subtransport) { + git__free(t->url); + t->url = NULL; + + if (t->wrapped->close(t->wrapped) < 0) + return -1; + } + + return 0; +} + +int git_smart__update_heads(transport_smart *t, git_vector *symrefs) +{ + size_t i; + git_pkt *pkt; + + git_vector_clear(&t->heads); + git_vector_foreach(&t->refs, i, pkt) { + git_pkt_ref *ref = (git_pkt_ref *) pkt; + if (pkt->type != GIT_PKT_REF) + continue; + + if (symrefs) { + git_refspec *spec; + git_str buf = GIT_STR_INIT; + size_t j; + int error = 0; + + git_vector_foreach(symrefs, j, spec) { + git_str_clear(&buf); + if (git_refspec_src_matches(spec, ref->head.name) && + !(error = git_refspec__transform(&buf, spec, ref->head.name))) { + git__free(ref->head.symref_target); + ref->head.symref_target = git_str_detach(&buf); + } + } + + git_str_dispose(&buf); + + if (error < 0) + return error; + } + + if (git_vector_insert(&t->heads, &ref->head) < 0) + return -1; + } + + return 0; +} + +static void free_symrefs(git_vector *symrefs) +{ + git_refspec *spec; + size_t i; + + git_vector_foreach(symrefs, i, spec) { + git_refspec__dispose(spec); + git__free(spec); + } + + git_vector_free(symrefs); +} + +static int git_smart__connect( + git_transport *transport, + const char *url, + int direction, + const git_remote_connect_options *connect_opts) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_smart_subtransport_stream *stream; + int error; + git_pkt *pkt; + git_pkt_ref *first; + git_vector symrefs; + git_smart_service_t service; + + if (git_smart__reset_stream(t, true) < 0) + return -1; + + if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0) + return -1; + + t->url = git__strdup(url); + GIT_ERROR_CHECK_ALLOC(t->url); + + t->direction = direction; + + if (GIT_DIRECTION_FETCH == t->direction) { + service = GIT_SERVICE_UPLOADPACK_LS; + } else if (GIT_DIRECTION_PUSH == t->direction) { + service = GIT_SERVICE_RECEIVEPACK_LS; + } else { + git_error_set(GIT_ERROR_NET, "invalid direction"); + return -1; + } + + if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0) + return error; + + /* Save off the current stream (i.e. socket) that we are working with */ + t->current_stream = stream; + + gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + + /* 2 flushes for RPC; 1 for stateful */ + if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0) + return error; + + /* Strip the comment packet for RPC */ + if (t->rpc) { + pkt = (git_pkt *)git_vector_get(&t->refs, 0); + + if (!pkt || GIT_PKT_COMMENT != pkt->type) { + git_error_set(GIT_ERROR_NET, "invalid response"); + return -1; + } else { + /* Remove the comment pkt from the list */ + git_vector_remove(&t->refs, 0); + git__free(pkt); + } + } + + /* We now have loaded the refs. */ + t->have_refs = 1; + + pkt = (git_pkt *)git_vector_get(&t->refs, 0); + if (pkt && GIT_PKT_REF != pkt->type) { + git_error_set(GIT_ERROR_NET, "invalid response"); + return -1; + } + first = (git_pkt_ref *)pkt; + + if ((error = git_vector_init(&symrefs, 1, NULL)) < 0) + return error; + + /* Detect capabilities */ + if ((error = git_smart__detect_caps(first, &t->caps, &symrefs)) == 0) { + /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */ + if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") && + git_oid_is_zero(&first->head.oid)) { + git_vector_clear(&t->refs); + git_pkt_free((git_pkt *)first); + } + + /* Keep a list of heads for _ls */ + git_smart__update_heads(t, &symrefs); + } else if (error == GIT_ENOTFOUND) { + /* There was no ref packet received, or the cap list was empty */ + error = 0; + } else { + git_error_set(GIT_ERROR_NET, "invalid response"); + goto cleanup; + } + + if (t->rpc && (error = git_smart__reset_stream(t, false)) < 0) + goto cleanup; + + /* We're now logically connected. */ + t->connected = 1; + +cleanup: + free_symrefs(&symrefs); + + return error; +} + +static int git_smart__set_connect_opts( + git_transport *transport, + const git_remote_connect_options *opts) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + + if (!t->connected) { + git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected"); + return -1; + } + + return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, opts); +} + +static int git_smart__capabilities(unsigned int *capabilities, git_transport *transport) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + + *capabilities = 0; + + if (t->caps.want_tip_sha1) + *capabilities |= GIT_REMOTE_CAPABILITY_TIP_OID; + + if (t->caps.want_reachable_sha1) + *capabilities |= GIT_REMOTE_CAPABILITY_REACHABLE_OID; + + return 0; +} + +static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + + if (!t->have_refs) { + git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs"); + return -1; + } + + *out = (const git_remote_head **) t->heads.contents; + *size = t->heads.length; + + return 0; +} + +int git_smart__negotiation_step(git_transport *transport, void *data, size_t len) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_smart_subtransport_stream *stream; + int error; + + if (t->rpc && git_smart__reset_stream(t, false) < 0) + return -1; + + if (GIT_DIRECTION_FETCH != t->direction) { + git_error_set(GIT_ERROR_NET, "this operation is only valid for fetch"); + return -1; + } + + if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0) + return error; + + /* If this is a stateful implementation, the stream we get back should be the same */ + GIT_ASSERT(t->rpc || t->current_stream == stream); + + /* Save off the current stream (i.e. socket) that we are working with */ + t->current_stream = stream; + + if ((error = stream->write(stream, (const char *)data, len)) < 0) + return error; + + gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + + return 0; +} + +int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream) +{ + int error; + + if (t->rpc && git_smart__reset_stream(t, false) < 0) + return -1; + + if (GIT_DIRECTION_PUSH != t->direction) { + git_error_set(GIT_ERROR_NET, "this operation is only valid for push"); + return -1; + } + + if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0) + return error; + + /* If this is a stateful implementation, the stream we get back should be the same */ + GIT_ASSERT(t->rpc || t->current_stream == *stream); + + /* Save off the current stream (i.e. socket) that we are working with */ + t->current_stream = *stream; + + gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + + return 0; +} + +static void git_smart__cancel(git_transport *transport) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + + git_atomic32_set(&t->cancelled, 1); +} + +static int git_smart__is_connected(git_transport *transport) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + + return t->connected; +} + +static int git_smart__close(git_transport *transport) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_vector *common = &t->common; + unsigned int i; + git_pkt *p; + int ret; + git_smart_subtransport_stream *stream; + const char flush[] = "0000"; + + /* + * If we're still connected at this point and not using RPC, + * we should say goodbye by sending a flush, or git-daemon + * will complain that we disconnected unexpectedly. + */ + if (t->connected && !t->rpc && + !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) { + t->current_stream->write(t->current_stream, flush, 4); + } + + ret = git_smart__reset_stream(t, true); + + git_vector_foreach(common, i, p) + git_pkt_free(p); + + git_vector_free(common); + + if (t->url) { + git__free(t->url); + t->url = NULL; + } + + t->connected = 0; + + return ret; +} + +static void git_smart__free(git_transport *transport) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_vector *refs = &t->refs; + unsigned int i; + git_pkt *p; + + /* Make sure that the current stream is closed, if we have one. */ + git_smart__close(transport); + + /* Free the subtransport */ + t->wrapped->free(t->wrapped); + + git_vector_free(&t->heads); + git_vector_foreach(refs, i, p) + git_pkt_free(p); + + git_vector_free(refs); + + git_remote_connect_options_dispose(&t->connect_opts); + + git__free(t); +} + +static int ref_name_cmp(const void *a, const void *b) +{ + const git_pkt_ref *ref_a = a, *ref_b = b; + + return strcmp(ref_a->head.name, ref_b->head.name); +} + +int git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_remote_connect_options *connect_opts = &t->connect_opts; + + GIT_ASSERT_ARG(transport); + GIT_ASSERT_ARG(cert); + GIT_ASSERT_ARG(hostname); + + if (!connect_opts->callbacks.certificate_check) + return GIT_PASSTHROUGH; + + return connect_opts->callbacks.certificate_check(cert, valid, hostname, connect_opts->callbacks.payload); +} + +int git_transport_smart_credentials(git_credential **out, git_transport *transport, const char *user, int methods) +{ + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); + git_remote_connect_options *connect_opts = &t->connect_opts; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(transport); + + if (!connect_opts->callbacks.credentials) + return GIT_PASSTHROUGH; + + return connect_opts->callbacks.credentials(out, t->url, user, methods, connect_opts->callbacks.payload); +} + +int git_transport_smart(git_transport **out, git_remote *owner, void *param) +{ + transport_smart *t; + git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param; + + if (!param) + return -1; + + t = git__calloc(1, sizeof(transport_smart)); + GIT_ERROR_CHECK_ALLOC(t); + + t->parent.version = GIT_TRANSPORT_VERSION; + t->parent.connect = git_smart__connect; + t->parent.set_connect_opts = git_smart__set_connect_opts; + t->parent.capabilities = git_smart__capabilities; + t->parent.close = git_smart__close; + t->parent.free = git_smart__free; + t->parent.negotiate_fetch = git_smart__negotiate_fetch; + t->parent.download_pack = git_smart__download_pack; + t->parent.push = git_smart__push; + t->parent.ls = git_smart__ls; + t->parent.is_connected = git_smart__is_connected; + t->parent.cancel = git_smart__cancel; + + t->owner = owner; + t->rpc = definition->rpc; + + if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) { + git__free(t); + return -1; + } + + if (git_vector_init(&t->heads, 16, ref_name_cmp) < 0) { + git__free(t); + return -1; + } + + if (definition->callback(&t->wrapped, &t->parent, definition->param) < 0) { + git__free(t); + return -1; + } + + *out = (git_transport *) t; + return 0; +} diff --git a/src/libgit2/transports/smart.h b/src/libgit2/transports/smart.h new file mode 100644 index 000000000..9323d6c44 --- /dev/null +++ b/src/libgit2/transports/smart.h @@ -0,0 +1,193 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_transports_smart_h__ +#define INCLUDE_transports_smart_h__ + +#include "common.h" + +#include "git2.h" +#include "vector.h" +#include "netops.h" +#include "push.h" +#include "str.h" +#include "git2/sys/transport.h" + +#define GIT_SIDE_BAND_DATA 1 +#define GIT_SIDE_BAND_PROGRESS 2 +#define GIT_SIDE_BAND_ERROR 3 + +#define GIT_CAP_OFS_DELTA "ofs-delta" +#define GIT_CAP_MULTI_ACK "multi_ack" +#define GIT_CAP_MULTI_ACK_DETAILED "multi_ack_detailed" +#define GIT_CAP_SIDE_BAND "side-band" +#define GIT_CAP_SIDE_BAND_64K "side-band-64k" +#define GIT_CAP_INCLUDE_TAG "include-tag" +#define GIT_CAP_DELETE_REFS "delete-refs" +#define GIT_CAP_REPORT_STATUS "report-status" +#define GIT_CAP_THIN_PACK "thin-pack" +#define GIT_CAP_SYMREF "symref" +#define GIT_CAP_WANT_TIP_SHA1 "allow-tip-sha1-in-want" +#define GIT_CAP_WANT_REACHABLE_SHA1 "allow-reachable-sha1-in-want" + +extern bool git_smart__ofs_delta_enabled; + +typedef enum { + GIT_PKT_CMD, + GIT_PKT_FLUSH, + GIT_PKT_REF, + GIT_PKT_HAVE, + GIT_PKT_ACK, + GIT_PKT_NAK, + GIT_PKT_COMMENT, + GIT_PKT_ERR, + GIT_PKT_DATA, + GIT_PKT_PROGRESS, + GIT_PKT_OK, + GIT_PKT_NG, + GIT_PKT_UNPACK +} git_pkt_type; + +/* Used for multi_ack and multi_ack_detailed */ +enum git_ack_status { + GIT_ACK_NONE, + GIT_ACK_CONTINUE, + GIT_ACK_COMMON, + GIT_ACK_READY +}; + +/* This would be a flush pkt */ +typedef struct { + git_pkt_type type; +} git_pkt; + +struct git_pkt_cmd { + git_pkt_type type; + char *cmd; + char *path; + char *host; +}; + +/* This is a pkt-line with some info in it */ +typedef struct { + git_pkt_type type; + git_remote_head head; + char *capabilities; +} git_pkt_ref; + +/* Useful later */ +typedef struct { + git_pkt_type type; + git_oid oid; + enum git_ack_status status; +} git_pkt_ack; + +typedef struct { + git_pkt_type type; + char comment[GIT_FLEX_ARRAY]; +} git_pkt_comment; + +typedef struct { + git_pkt_type type; + size_t len; + char data[GIT_FLEX_ARRAY]; +} git_pkt_data; + +typedef git_pkt_data git_pkt_progress; + +typedef struct { + git_pkt_type type; + size_t len; + char error[GIT_FLEX_ARRAY]; +} git_pkt_err; + +typedef struct { + git_pkt_type type; + char *ref; +} git_pkt_ok; + +typedef struct { + git_pkt_type type; + char *ref; + char *msg; +} git_pkt_ng; + +typedef struct { + git_pkt_type type; + int unpack_ok; +} git_pkt_unpack; + +typedef struct transport_smart_caps { + unsigned int common:1, + ofs_delta:1, + multi_ack:1, + multi_ack_detailed:1, + side_band:1, + side_band_64k:1, + include_tag:1, + delete_refs:1, + report_status:1, + thin_pack:1, + want_tip_sha1:1, + want_reachable_sha1:1; +} transport_smart_caps; + +typedef int (*packetsize_cb)(size_t received, void *payload); + +typedef struct { + git_transport parent; + git_remote *owner; + char *url; + git_remote_connect_options connect_opts; + int direction; + git_smart_subtransport *wrapped; + git_smart_subtransport_stream *current_stream; + transport_smart_caps caps; + git_vector refs; + git_vector heads; + git_vector common; + git_atomic32 cancelled; + packetsize_cb packetsize_cb; + void *packetsize_payload; + unsigned rpc : 1, + have_refs : 1, + connected : 1; + gitno_buffer buffer; + char buffer_data[65536]; +} transport_smart; + +/* smart_protocol.c */ +int git_smart__store_refs(transport_smart *t, int flushes); +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs); +int git_smart__push(git_transport *transport, git_push *push); + +int git_smart__negotiate_fetch( + git_transport *transport, + git_repository *repo, + const git_remote_head * const *refs, + size_t count); + +int git_smart__download_pack( + git_transport *transport, + git_repository *repo, + git_indexer_progress *stats); + +/* smart.c */ +int git_smart__negotiation_step(git_transport *transport, void *data, size_t len); +int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out); + +int git_smart__update_heads(transport_smart *t, git_vector *symrefs); + +/* smart_pkt.c */ +int git_pkt_parse_line(git_pkt **head, const char **endptr, const char *line, size_t linelen); +int git_pkt_buffer_flush(git_str *buf); +int git_pkt_send_flush(GIT_SOCKET s); +int git_pkt_buffer_done(git_str *buf); +int git_pkt_buffer_wants(const git_remote_head * const *refs, size_t count, transport_smart_caps *caps, git_str *buf); +int git_pkt_buffer_have(git_oid *oid, git_str *buf); +void git_pkt_free(git_pkt *pkt); + +#endif diff --git a/src/libgit2/transports/smart_pkt.c b/src/libgit2/transports/smart_pkt.c new file mode 100644 index 000000000..b42edd0d6 --- /dev/null +++ b/src/libgit2/transports/smart_pkt.c @@ -0,0 +1,637 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "smart.h" +#include "util.h" +#include "netops.h" +#include "posix.h" +#include "str.h" + +#include "git2/types.h" +#include "git2/errors.h" +#include "git2/refs.h" +#include "git2/revwalk.h" + +#include + +#define PKT_LEN_SIZE 4 +static const char pkt_done_str[] = "0009done\n"; +static const char pkt_flush_str[] = "0000"; +static const char pkt_have_prefix[] = "0032have "; +static const char pkt_want_prefix[] = "0032want "; + +static int flush_pkt(git_pkt **out) +{ + git_pkt *pkt; + + pkt = git__malloc(sizeof(git_pkt)); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_FLUSH; + *out = pkt; + + return 0; +} + +/* the rest of the line will be useful for multi_ack and multi_ack_detailed */ +static int ack_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_ack *pkt; + + pkt = git__calloc(1, sizeof(git_pkt_ack)); + GIT_ERROR_CHECK_ALLOC(pkt); + pkt->type = GIT_PKT_ACK; + + if (git__prefixncmp(line, len, "ACK ")) + goto out_err; + line += 4; + len -= 4; + + if (len < GIT_OID_HEXSZ || git_oid_fromstr(&pkt->oid, line) < 0) + goto out_err; + line += GIT_OID_HEXSZ; + len -= GIT_OID_HEXSZ; + + if (len && line[0] == ' ') { + line++; + len--; + + if (!git__prefixncmp(line, len, "continue")) + pkt->status = GIT_ACK_CONTINUE; + else if (!git__prefixncmp(line, len, "common")) + pkt->status = GIT_ACK_COMMON; + else if (!git__prefixncmp(line, len, "ready")) + pkt->status = GIT_ACK_READY; + else + goto out_err; + } + + *out = (git_pkt *) pkt; + + return 0; + +out_err: + git_error_set(GIT_ERROR_NET, "error parsing ACK pkt-line"); + git__free(pkt); + return -1; +} + +static int nak_pkt(git_pkt **out) +{ + git_pkt *pkt; + + pkt = git__malloc(sizeof(git_pkt)); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_NAK; + *out = pkt; + + return 0; +} + +static int comment_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_comment *pkt; + size_t alloclen; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_comment), len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + pkt = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_COMMENT; + memcpy(pkt->comment, line, len); + pkt->comment[len] = '\0'; + + *out = (git_pkt *) pkt; + + return 0; +} + +static int err_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_err *pkt = NULL; + size_t alloclen; + + /* Remove "ERR " from the line */ + if (git__prefixncmp(line, len, "ERR ")) + goto out_err; + line += 4; + len -= 4; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + pkt = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt); + pkt->type = GIT_PKT_ERR; + pkt->len = len; + + memcpy(pkt->error, line, len); + pkt->error[len] = '\0'; + + *out = (git_pkt *) pkt; + + return 0; + +out_err: + git_error_set(GIT_ERROR_NET, "error parsing ERR pkt-line"); + git__free(pkt); + return -1; +} + +static int data_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_data *pkt; + size_t alloclen; + + line++; + len--; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len); + pkt = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_DATA; + pkt->len = len; + memcpy(pkt->data, line, len); + + *out = (git_pkt *) pkt; + + return 0; +} + +static int sideband_progress_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_progress *pkt; + size_t alloclen; + + line++; + len--; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len); + pkt = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_PROGRESS; + pkt->len = len; + memcpy(pkt->data, line, len); + + *out = (git_pkt *) pkt; + + return 0; +} + +static int sideband_error_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_err *pkt; + size_t alloc_len; + + line++; + len--; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(git_pkt_err), len); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); + pkt = git__malloc(alloc_len); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_ERR; + pkt->len = (int)len; + memcpy(pkt->error, line, len); + pkt->error[len] = '\0'; + + *out = (git_pkt *)pkt; + + return 0; +} + +/* + * Parse an other-ref line. + */ +static int ref_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_ref *pkt; + size_t alloclen; + + pkt = git__calloc(1, sizeof(git_pkt_ref)); + GIT_ERROR_CHECK_ALLOC(pkt); + pkt->type = GIT_PKT_REF; + + if (len < GIT_OID_HEXSZ || git_oid_fromstr(&pkt->head.oid, line) < 0) + goto out_err; + line += GIT_OID_HEXSZ; + len -= GIT_OID_HEXSZ; + + if (git__prefixncmp(line, len, " ")) + goto out_err; + line++; + len--; + + if (!len) + goto out_err; + + if (line[len - 1] == '\n') + --len; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); + pkt->head.name = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt->head.name); + + memcpy(pkt->head.name, line, len); + pkt->head.name[len] = '\0'; + + if (strlen(pkt->head.name) < len) + pkt->capabilities = strchr(pkt->head.name, '\0') + 1; + + *out = (git_pkt *)pkt; + return 0; + +out_err: + git_error_set(GIT_ERROR_NET, "error parsing REF pkt-line"); + if (pkt) + git__free(pkt->head.name); + git__free(pkt); + return -1; +} + +static int ok_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_ok *pkt; + size_t alloc_len; + + pkt = git__malloc(sizeof(*pkt)); + GIT_ERROR_CHECK_ALLOC(pkt); + pkt->type = GIT_PKT_OK; + + if (git__prefixncmp(line, len, "ok ")) + goto out_err; + line += 3; + len -= 3; + + if (len && line[len - 1] == '\n') + --len; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1); + pkt->ref = git__malloc(alloc_len); + GIT_ERROR_CHECK_ALLOC(pkt->ref); + + memcpy(pkt->ref, line, len); + pkt->ref[len] = '\0'; + + *out = (git_pkt *)pkt; + return 0; + +out_err: + git_error_set(GIT_ERROR_NET, "error parsing OK pkt-line"); + git__free(pkt); + return -1; +} + +static int ng_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_ng *pkt; + const char *ptr, *eol; + size_t alloclen; + + pkt = git__malloc(sizeof(*pkt)); + GIT_ERROR_CHECK_ALLOC(pkt); + + pkt->ref = NULL; + pkt->type = GIT_PKT_NG; + + eol = line + len; + + if (git__prefixncmp(line, len, "ng ")) + goto out_err; + line += 3; + + if (!(ptr = memchr(line, ' ', eol - line))) + goto out_err; + len = ptr - line; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); + pkt->ref = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt->ref); + + memcpy(pkt->ref, line, len); + pkt->ref[len] = '\0'; + + line = ptr + 1; + if (line >= eol) + goto out_err; + + if (!(ptr = memchr(line, '\n', eol - line))) + goto out_err; + len = ptr - line; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); + pkt->msg = git__malloc(alloclen); + GIT_ERROR_CHECK_ALLOC(pkt->msg); + + memcpy(pkt->msg, line, len); + pkt->msg[len] = '\0'; + + *out = (git_pkt *)pkt; + return 0; + +out_err: + git_error_set(GIT_ERROR_NET, "invalid packet line"); + git__free(pkt->ref); + git__free(pkt); + return -1; +} + +static int unpack_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_unpack *pkt; + + pkt = git__malloc(sizeof(*pkt)); + GIT_ERROR_CHECK_ALLOC(pkt); + pkt->type = GIT_PKT_UNPACK; + + if (!git__prefixncmp(line, len, "unpack ok")) + pkt->unpack_ok = 1; + else + pkt->unpack_ok = 0; + + *out = (git_pkt *)pkt; + return 0; +} + +static int parse_len(size_t *out, const char *line, size_t linelen) +{ + char num[PKT_LEN_SIZE + 1]; + int i, k, error; + int32_t len; + const char *num_end; + + /* Not even enough for the length */ + if (linelen < PKT_LEN_SIZE) + return GIT_EBUFS; + + memcpy(num, line, PKT_LEN_SIZE); + num[PKT_LEN_SIZE] = '\0'; + + for (i = 0; i < PKT_LEN_SIZE; ++i) { + if (!isxdigit(num[i])) { + /* Make sure there are no special characters before passing to error message */ + for (k = 0; k < PKT_LEN_SIZE; ++k) { + if(!isprint(num[k])) { + num[k] = '.'; + } + } + + git_error_set(GIT_ERROR_NET, "invalid hex digit in length: '%s'", num); + return -1; + } + } + + if ((error = git__strntol32(&len, num, PKT_LEN_SIZE, &num_end, 16)) < 0) + return error; + + if (len < 0) + return -1; + + *out = (size_t) len; + return 0; +} + +/* + * As per the documentation, the syntax is: + * + * pkt-line = data-pkt / flush-pkt + * data-pkt = pkt-len pkt-payload + * pkt-len = 4*(HEXDIG) + * pkt-payload = (pkt-len -4)*(OCTET) + * flush-pkt = "0000" + * + * Which means that the first four bytes are the length of the line, + * in ASCII hexadecimal (including itself) + */ + +int git_pkt_parse_line( + git_pkt **pkt, const char **endptr, const char *line, size_t linelen) +{ + int error; + size_t len; + + if ((error = parse_len(&len, line, linelen)) < 0) { + /* + * If we fail to parse the length, it might be + * because the server is trying to send us the + * packfile already or because we do not yet have + * enough data. + */ + if (error == GIT_EBUFS) + ; + else if (!git__prefixncmp(line, linelen, "PACK")) + git_error_set(GIT_ERROR_NET, "unexpected pack file"); + else + git_error_set(GIT_ERROR_NET, "bad packet length"); + return error; + } + + /* + * Make sure there is enough in the buffer to satisfy + * this line. + */ + if (linelen < len) + return GIT_EBUFS; + + /* + * The length has to be exactly 0 in case of a flush + * packet or greater than PKT_LEN_SIZE, as the decoded + * length includes its own encoded length of four bytes. + */ + if (len != 0 && len < PKT_LEN_SIZE) + return GIT_ERROR; + + line += PKT_LEN_SIZE; + /* + * The Git protocol does not specify empty lines as part + * of the protocol. Not knowing what to do with an empty + * line, we should return an error upon hitting one. + */ + if (len == PKT_LEN_SIZE) { + git_error_set_str(GIT_ERROR_NET, "Invalid empty packet"); + return GIT_ERROR; + } + + if (len == 0) { /* Flush pkt */ + *endptr = line; + return flush_pkt(pkt); + } + + len -= PKT_LEN_SIZE; /* the encoded length includes its own size */ + + if (*line == GIT_SIDE_BAND_DATA) + error = data_pkt(pkt, line, len); + else if (*line == GIT_SIDE_BAND_PROGRESS) + error = sideband_progress_pkt(pkt, line, len); + else if (*line == GIT_SIDE_BAND_ERROR) + error = sideband_error_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "ACK")) + error = ack_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "NAK")) + error = nak_pkt(pkt); + else if (!git__prefixncmp(line, len, "ERR")) + error = err_pkt(pkt, line, len); + else if (*line == '#') + error = comment_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "ok")) + error = ok_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "ng")) + error = ng_pkt(pkt, line, len); + else if (!git__prefixncmp(line, len, "unpack")) + error = unpack_pkt(pkt, line, len); + else + error = ref_pkt(pkt, line, len); + + *endptr = line + len; + + return error; +} + +void git_pkt_free(git_pkt *pkt) +{ + if (pkt == NULL) { + return; + } + if (pkt->type == GIT_PKT_REF) { + git_pkt_ref *p = (git_pkt_ref *) pkt; + git__free(p->head.name); + git__free(p->head.symref_target); + } + + if (pkt->type == GIT_PKT_OK) { + git_pkt_ok *p = (git_pkt_ok *) pkt; + git__free(p->ref); + } + + if (pkt->type == GIT_PKT_NG) { + git_pkt_ng *p = (git_pkt_ng *) pkt; + git__free(p->ref); + git__free(p->msg); + } + + git__free(pkt); +} + +int git_pkt_buffer_flush(git_str *buf) +{ + return git_str_put(buf, pkt_flush_str, strlen(pkt_flush_str)); +} + +static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_str *buf) +{ + git_str str = GIT_STR_INIT; + char oid[GIT_OID_HEXSZ +1] = {0}; + size_t len; + + /* Prefer multi_ack_detailed */ + if (caps->multi_ack_detailed) + git_str_puts(&str, GIT_CAP_MULTI_ACK_DETAILED " "); + else if (caps->multi_ack) + git_str_puts(&str, GIT_CAP_MULTI_ACK " "); + + /* Prefer side-band-64k if the server supports both */ + if (caps->side_band_64k) + git_str_printf(&str, "%s ", GIT_CAP_SIDE_BAND_64K); + else if (caps->side_band) + git_str_printf(&str, "%s ", GIT_CAP_SIDE_BAND); + + if (caps->include_tag) + git_str_puts(&str, GIT_CAP_INCLUDE_TAG " "); + + if (caps->thin_pack) + git_str_puts(&str, GIT_CAP_THIN_PACK " "); + + if (caps->ofs_delta) + git_str_puts(&str, GIT_CAP_OFS_DELTA " "); + + if (git_str_oom(&str)) + return -1; + + len = strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ + + git_str_len(&str) + 1 /* LF */; + + if (len > 0xffff) { + git_error_set(GIT_ERROR_NET, + "tried to produce packet with invalid length %" PRIuZ, len); + return -1; + } + + git_str_grow_by(buf, len); + git_oid_fmt(oid, &head->oid); + git_str_printf(buf, + "%04xwant %s %s\n", (unsigned int)len, oid, git_str_cstr(&str)); + git_str_dispose(&str); + + GIT_ERROR_CHECK_ALLOC_STR(buf); + + return 0; +} + +/* + * All "want" packets have the same length and format, so what we do + * is overwrite the OID each time. + */ + +int git_pkt_buffer_wants( + const git_remote_head * const *refs, + size_t count, + transport_smart_caps *caps, + git_str *buf) +{ + size_t i = 0; + const git_remote_head *head; + + if (caps->common) { + for (; i < count; ++i) { + head = refs[i]; + if (!head->local) + break; + } + + if (buffer_want_with_caps(refs[i], caps, buf) < 0) + return -1; + + i++; + } + + for (; i < count; ++i) { + char oid[GIT_OID_HEXSZ]; + + head = refs[i]; + if (head->local) + continue; + + git_oid_fmt(oid, &head->oid); + git_str_put(buf, pkt_want_prefix, strlen(pkt_want_prefix)); + git_str_put(buf, oid, GIT_OID_HEXSZ); + git_str_putc(buf, '\n'); + if (git_str_oom(buf)) + return -1; + } + + return git_pkt_buffer_flush(buf); +} + +int git_pkt_buffer_have(git_oid *oid, git_str *buf) +{ + char oidhex[GIT_OID_HEXSZ + 1]; + + memset(oidhex, 0x0, sizeof(oidhex)); + git_oid_fmt(oidhex, oid); + return git_str_printf(buf, "%s%s\n", pkt_have_prefix, oidhex); +} + +int git_pkt_buffer_done(git_str *buf) +{ + return git_str_puts(buf, pkt_done_str); +} diff --git a/src/libgit2/transports/smart_protocol.c b/src/libgit2/transports/smart_protocol.c new file mode 100644 index 000000000..8cf027133 --- /dev/null +++ b/src/libgit2/transports/smart_protocol.c @@ -0,0 +1,1094 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "git2.h" +#include "git2/odb_backend.h" + +#include "smart.h" +#include "refs.h" +#include "repository.h" +#include "push.h" +#include "pack-objects.h" +#include "remote.h" +#include "util.h" +#include "revwalk.h" + +#define NETWORK_XFER_THRESHOLD (100*1024) +/* The minimal interval between progress updates (in seconds). */ +#define MIN_PROGRESS_UPDATE_INTERVAL 0.5 + +bool git_smart__ofs_delta_enabled = true; + +int git_smart__store_refs(transport_smart *t, int flushes) +{ + gitno_buffer *buf = &t->buffer; + git_vector *refs = &t->refs; + int error, flush = 0, recvd; + const char *line_end = NULL; + git_pkt *pkt = NULL; + size_t i; + + /* Clear existing refs in case git_remote_connect() is called again + * after git_remote_disconnect(). + */ + git_vector_foreach(refs, i, pkt) { + git_pkt_free(pkt); + } + git_vector_clear(refs); + pkt = NULL; + + do { + if (buf->offset > 0) + error = git_pkt_parse_line(&pkt, &line_end, buf->data, buf->offset); + else + error = GIT_EBUFS; + + if (error < 0 && error != GIT_EBUFS) + return error; + + if (error == GIT_EBUFS) { + if ((recvd = gitno_recv(buf)) < 0) + return recvd; + + if (recvd == 0) { + git_error_set(GIT_ERROR_NET, "early EOF"); + return GIT_EEOF; + } + + continue; + } + + if (gitno_consume(buf, line_end) < 0) + return -1; + + if (pkt->type == GIT_PKT_ERR) { + git_error_set(GIT_ERROR_NET, "remote error: %s", ((git_pkt_err *)pkt)->error); + git__free(pkt); + return -1; + } + + if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0) + return -1; + + if (pkt->type == GIT_PKT_FLUSH) { + flush++; + git_pkt_free(pkt); + } + } while (flush < flushes); + + return flush; +} + +static int append_symref(const char **out, git_vector *symrefs, const char *ptr) +{ + int error; + const char *end; + git_str buf = GIT_STR_INIT; + git_refspec *mapping = NULL; + + ptr += strlen(GIT_CAP_SYMREF); + if (*ptr != '=') + goto on_invalid; + + ptr++; + if (!(end = strchr(ptr, ' ')) && + !(end = strchr(ptr, '\0'))) + goto on_invalid; + + if ((error = git_str_put(&buf, ptr, end - ptr)) < 0) + return error; + + /* symref mapping has refspec format */ + mapping = git__calloc(1, sizeof(git_refspec)); + GIT_ERROR_CHECK_ALLOC(mapping); + + error = git_refspec__parse(mapping, git_str_cstr(&buf), true); + git_str_dispose(&buf); + + /* if the error isn't OOM, then it's a parse error; let's use a nicer message */ + if (error < 0) { + if (git_error_last()->klass != GIT_ERROR_NOMEMORY) + goto on_invalid; + + git__free(mapping); + return error; + } + + if ((error = git_vector_insert(symrefs, mapping)) < 0) + return error; + + *out = end; + return 0; + +on_invalid: + git_error_set(GIT_ERROR_NET, "remote sent invalid symref"); + git_refspec__dispose(mapping); + git__free(mapping); + return -1; +} + +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs) +{ + const char *ptr; + + /* No refs or capabilities, odd but not a problem */ + if (pkt == NULL || pkt->capabilities == NULL) + return GIT_ENOTFOUND; + + ptr = pkt->capabilities; + while (ptr != NULL && *ptr != '\0') { + if (*ptr == ' ') + ptr++; + + if (git_smart__ofs_delta_enabled && !git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { + caps->common = caps->ofs_delta = 1; + ptr += strlen(GIT_CAP_OFS_DELTA); + continue; + } + + /* Keep multi_ack_detailed before multi_ack */ + if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK_DETAILED)) { + caps->common = caps->multi_ack_detailed = 1; + ptr += strlen(GIT_CAP_MULTI_ACK_DETAILED); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) { + caps->common = caps->multi_ack = 1; + ptr += strlen(GIT_CAP_MULTI_ACK); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) { + caps->common = caps->include_tag = 1; + ptr += strlen(GIT_CAP_INCLUDE_TAG); + continue; + } + + /* Keep side-band check after side-band-64k */ + if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) { + caps->common = caps->side_band_64k = 1; + ptr += strlen(GIT_CAP_SIDE_BAND_64K); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) { + caps->common = caps->side_band = 1; + ptr += strlen(GIT_CAP_SIDE_BAND); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_DELETE_REFS)) { + caps->common = caps->delete_refs = 1; + ptr += strlen(GIT_CAP_DELETE_REFS); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_THIN_PACK)) { + caps->common = caps->thin_pack = 1; + ptr += strlen(GIT_CAP_THIN_PACK); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_SYMREF)) { + int error; + + if ((error = append_symref(&ptr, symrefs, ptr)) < 0) + return error; + + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_WANT_TIP_SHA1)) { + caps->common = caps->want_tip_sha1 = 1; + ptr += strlen(GIT_CAP_DELETE_REFS); + continue; + } + + if (!git__prefixcmp(ptr, GIT_CAP_WANT_REACHABLE_SHA1)) { + caps->common = caps->want_reachable_sha1 = 1; + ptr += strlen(GIT_CAP_DELETE_REFS); + continue; + } + + /* We don't know this capability, so skip it */ + ptr = strchr(ptr, ' '); + } + + return 0; +} + +static int recv_pkt(git_pkt **out_pkt, git_pkt_type *out_type, gitno_buffer *buf) +{ + const char *ptr = buf->data, *line_end = ptr; + git_pkt *pkt = NULL; + int error = 0, ret; + + do { + if (buf->offset > 0) + error = git_pkt_parse_line(&pkt, &line_end, ptr, buf->offset); + else + error = GIT_EBUFS; + + if (error == 0) + break; /* return the pkt */ + + if (error < 0 && error != GIT_EBUFS) + return error; + + if ((ret = gitno_recv(buf)) < 0) { + return ret; + } else if (ret == 0) { + git_error_set(GIT_ERROR_NET, "early EOF"); + return GIT_EEOF; + } + } while (error); + + if (gitno_consume(buf, line_end) < 0) + return -1; + + if (out_type != NULL) + *out_type = pkt->type; + if (out_pkt != NULL) + *out_pkt = pkt; + else + git__free(pkt); + + return error; +} + +static int store_common(transport_smart *t) +{ + git_pkt *pkt = NULL; + gitno_buffer *buf = &t->buffer; + int error; + + do { + if ((error = recv_pkt(&pkt, NULL, buf)) < 0) + return error; + + if (pkt->type != GIT_PKT_ACK) { + git__free(pkt); + return 0; + } + + if (git_vector_insert(&t->common, pkt) < 0) { + git__free(pkt); + return -1; + } + } while (1); + + return 0; +} + +static int wait_while_ack(gitno_buffer *buf) +{ + int error; + git_pkt *pkt = NULL; + git_pkt_ack *ack = NULL; + + while (1) { + git_pkt_free(pkt); + + if ((error = recv_pkt(&pkt, NULL, buf)) < 0) + return error; + + if (pkt->type == GIT_PKT_NAK) + break; + if (pkt->type != GIT_PKT_ACK) + continue; + + ack = (git_pkt_ack*)pkt; + + if (ack->status != GIT_ACK_CONTINUE && + ack->status != GIT_ACK_COMMON && + ack->status != GIT_ACK_READY) { + break; + } + } + + git_pkt_free(pkt); + return 0; +} + +int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *wants, size_t count) +{ + transport_smart *t = (transport_smart *)transport; + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + gitno_buffer *buf = &t->buffer; + git_str data = GIT_STR_INIT; + git_revwalk *walk = NULL; + int error = -1; + git_pkt_type pkt_type; + unsigned int i; + git_oid oid; + + if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) + return error; + + if ((error = git_revwalk_new(&walk, repo)) < 0) + goto on_error; + + opts.insert_by_date = 1; + if ((error = git_revwalk__push_glob(walk, "refs/*", &opts)) < 0) + goto on_error; + + /* + * Our support for ACK extensions is simply to parse them. On + * the first ACK we will accept that as enough common + * objects. We give up if we haven't found an answer in the + * first 256 we send. + */ + i = 0; + while (i < 256) { + error = git_revwalk_next(&oid, walk); + + if (error < 0) { + if (GIT_ITEROVER == error) + break; + + goto on_error; + } + + git_pkt_buffer_have(&oid, &data); + i++; + if (i % 20 == 0) { + if (t->cancelled.val) { + git_error_set(GIT_ERROR_NET, "The fetch was cancelled by the user"); + error = GIT_EUSER; + goto on_error; + } + + git_pkt_buffer_flush(&data); + if (git_str_oom(&data)) { + error = -1; + goto on_error; + } + + if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0) + goto on_error; + + git_str_clear(&data); + if (t->caps.multi_ack || t->caps.multi_ack_detailed) { + if ((error = store_common(t)) < 0) + goto on_error; + } else { + if ((error = recv_pkt(NULL, &pkt_type, buf)) < 0) + goto on_error; + + if (pkt_type == GIT_PKT_ACK) { + break; + } else if (pkt_type == GIT_PKT_NAK) { + continue; + } else { + git_error_set(GIT_ERROR_NET, "unexpected pkt type"); + error = -1; + goto on_error; + } + } + } + + if (t->common.length > 0) + break; + + if (i % 20 == 0 && t->rpc) { + git_pkt_ack *pkt; + unsigned int j; + + if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) + goto on_error; + + git_vector_foreach(&t->common, j, pkt) { + if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0) + goto on_error; + } + + if (git_str_oom(&data)) { + error = -1; + goto on_error; + } + } + } + + /* Tell the other end that we're done negotiating */ + if (t->rpc && t->common.length > 0) { + git_pkt_ack *pkt; + unsigned int j; + + if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) + goto on_error; + + git_vector_foreach(&t->common, j, pkt) { + if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0) + goto on_error; + } + + if (git_str_oom(&data)) { + error = -1; + goto on_error; + } + } + + if ((error = git_pkt_buffer_done(&data)) < 0) + goto on_error; + + if (t->cancelled.val) { + git_error_set(GIT_ERROR_NET, "The fetch was cancelled by the user"); + error = GIT_EUSER; + goto on_error; + } + if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0) + goto on_error; + + git_str_dispose(&data); + git_revwalk_free(walk); + + /* Now let's eat up whatever the server gives us */ + if (!t->caps.multi_ack && !t->caps.multi_ack_detailed) { + if ((error = recv_pkt(NULL, &pkt_type, buf)) < 0) + return error; + + if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) { + git_error_set(GIT_ERROR_NET, "unexpected pkt type"); + return -1; + } + } else { + error = wait_while_ack(buf); + } + + return error; + +on_error: + git_revwalk_free(walk); + git_str_dispose(&data); + return error; +} + +static int no_sideband(transport_smart *t, struct git_odb_writepack *writepack, gitno_buffer *buf, git_indexer_progress *stats) +{ + int recvd; + + do { + if (t->cancelled.val) { + git_error_set(GIT_ERROR_NET, "the fetch was cancelled by the user"); + return GIT_EUSER; + } + + if (writepack->append(writepack, buf->data, buf->offset, stats) < 0) + return -1; + + gitno_consume_n(buf, buf->offset); + + if ((recvd = gitno_recv(buf)) < 0) + return recvd; + } while(recvd > 0); + + if (writepack->commit(writepack, stats) < 0) + return -1; + + return 0; +} + +struct network_packetsize_payload +{ + git_indexer_progress_cb callback; + void *payload; + git_indexer_progress *stats; + size_t last_fired_bytes; +}; + +static int network_packetsize(size_t received, void *payload) +{ + struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload; + + /* Accumulate bytes */ + npp->stats->received_bytes += received; + + /* Fire notification if the threshold is reached */ + if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) { + npp->last_fired_bytes = npp->stats->received_bytes; + + if (npp->callback(npp->stats, npp->payload)) + return GIT_EUSER; + } + + return 0; +} + +int git_smart__download_pack( + git_transport *transport, + git_repository *repo, + git_indexer_progress *stats) +{ + transport_smart *t = (transport_smart *)transport; + gitno_buffer *buf = &t->buffer; + git_odb *odb; + struct git_odb_writepack *writepack = NULL; + int error = 0; + struct network_packetsize_payload npp = {0}; + + git_indexer_progress_cb progress_cb = t->connect_opts.callbacks.transfer_progress; + void *progress_payload = t->connect_opts.callbacks.payload; + + memset(stats, 0, sizeof(git_indexer_progress)); + + if (progress_cb) { + npp.callback = progress_cb; + npp.payload = progress_payload; + npp.stats = stats; + t->packetsize_cb = &network_packetsize; + t->packetsize_payload = &npp; + + /* We might have something in the buffer already from negotiate_fetch */ + if (t->buffer.offset > 0 && !t->cancelled.val) + if (t->packetsize_cb(t->buffer.offset, t->packetsize_payload)) + git_atomic32_set(&t->cancelled, 1); + } + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || + ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) != 0)) + goto done; + + /* + * If the remote doesn't support the side-band, we can feed + * the data directly to the pack writer. Otherwise, we need to + * check which one belongs there. + */ + if (!t->caps.side_band && !t->caps.side_band_64k) { + error = no_sideband(t, writepack, buf, stats); + goto done; + } + + do { + git_pkt *pkt = NULL; + + /* Check cancellation before network call */ + if (t->cancelled.val) { + git_error_clear(); + error = GIT_EUSER; + goto done; + } + + if ((error = recv_pkt(&pkt, NULL, buf)) >= 0) { + /* Check cancellation after network call */ + if (t->cancelled.val) { + git_error_clear(); + error = GIT_EUSER; + } else if (pkt->type == GIT_PKT_PROGRESS) { + if (t->connect_opts.callbacks.sideband_progress) { + git_pkt_progress *p = (git_pkt_progress *) pkt; + + if (p->len > INT_MAX) { + git_error_set(GIT_ERROR_NET, "oversized progress message"); + error = GIT_ERROR; + goto done; + } + + error = t->connect_opts.callbacks.sideband_progress(p->data, (int)p->len, t->connect_opts.callbacks.payload); + } + } else if (pkt->type == GIT_PKT_DATA) { + git_pkt_data *p = (git_pkt_data *) pkt; + + if (p->len) + error = writepack->append(writepack, p->data, p->len, stats); + } else if (pkt->type == GIT_PKT_FLUSH) { + /* A flush indicates the end of the packfile */ + git__free(pkt); + break; + } + } + + git_pkt_free(pkt); + + if (error < 0) + goto done; + + } while (1); + + /* + * Trailing execution of progress_cb, if necessary... + * Only the callback through the npp datastructure currently + * updates the last_fired_bytes value. It is possible that + * progress has already been reported with the correct + * "received_bytes" value, but until (if?) this is unified + * then we will report progress again to be sure that the + * correct last received_bytes value is reported. + */ + if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes) { + error = npp.callback(npp.stats, npp.payload); + if (error != 0) + goto done; + } + + error = writepack->commit(writepack, stats); + +done: + if (writepack) + writepack->free(writepack); + if (progress_cb) { + t->packetsize_cb = NULL; + t->packetsize_payload = NULL; + } + + return error; +} + +static int gen_pktline(git_str *buf, git_push *push) +{ + push_spec *spec; + size_t i, len; + char old_id[GIT_OID_HEXSZ+1], new_id[GIT_OID_HEXSZ+1]; + + old_id[GIT_OID_HEXSZ] = '\0'; new_id[GIT_OID_HEXSZ] = '\0'; + + git_vector_foreach(&push->specs, i, spec) { + len = 2*GIT_OID_HEXSZ + 7 + strlen(spec->refspec.dst); + + if (i == 0) { + ++len; /* '\0' */ + if (push->report_status) + len += strlen(GIT_CAP_REPORT_STATUS) + 1; + len += strlen(GIT_CAP_SIDE_BAND_64K) + 1; + } + + git_oid_fmt(old_id, &spec->roid); + git_oid_fmt(new_id, &spec->loid); + + git_str_printf(buf, "%04"PRIxZ"%s %s %s", len, old_id, new_id, spec->refspec.dst); + + if (i == 0) { + git_str_putc(buf, '\0'); + /* Core git always starts their capabilities string with a space */ + if (push->report_status) { + git_str_putc(buf, ' '); + git_str_printf(buf, GIT_CAP_REPORT_STATUS); + } + git_str_putc(buf, ' '); + git_str_printf(buf, GIT_CAP_SIDE_BAND_64K); + } + + git_str_putc(buf, '\n'); + } + + git_str_puts(buf, "0000"); + return git_str_oom(buf) ? -1 : 0; +} + +static int add_push_report_pkt(git_push *push, git_pkt *pkt) +{ + push_status *status; + + switch (pkt->type) { + case GIT_PKT_OK: + status = git__calloc(1, sizeof(push_status)); + GIT_ERROR_CHECK_ALLOC(status); + status->msg = NULL; + status->ref = git__strdup(((git_pkt_ok *)pkt)->ref); + if (!status->ref || + git_vector_insert(&push->status, status) < 0) { + git_push_status_free(status); + return -1; + } + break; + case GIT_PKT_NG: + status = git__calloc(1, sizeof(push_status)); + GIT_ERROR_CHECK_ALLOC(status); + status->ref = git__strdup(((git_pkt_ng *)pkt)->ref); + status->msg = git__strdup(((git_pkt_ng *)pkt)->msg); + if (!status->ref || !status->msg || + git_vector_insert(&push->status, status) < 0) { + git_push_status_free(status); + return -1; + } + break; + case GIT_PKT_UNPACK: + push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok; + break; + case GIT_PKT_FLUSH: + return GIT_ITEROVER; + default: + git_error_set(GIT_ERROR_NET, "report-status: protocol error"); + return -1; + } + + return 0; +} + +static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt, git_str *data_pkt_buf) +{ + git_pkt *pkt; + const char *line, *line_end = NULL; + size_t line_len; + int error; + int reading_from_buf = data_pkt_buf->size > 0; + + if (reading_from_buf) { + /* We had an existing partial packet, so add the new + * packet to the buffer and parse the whole thing */ + git_str_put(data_pkt_buf, data_pkt->data, data_pkt->len); + line = data_pkt_buf->ptr; + line_len = data_pkt_buf->size; + } + else { + line = data_pkt->data; + line_len = data_pkt->len; + } + + while (line_len > 0) { + error = git_pkt_parse_line(&pkt, &line_end, line, line_len); + + if (error == GIT_EBUFS) { + /* Buffer the data when the inner packet is split + * across multiple sideband packets */ + if (!reading_from_buf) + git_str_put(data_pkt_buf, line, line_len); + error = 0; + goto done; + } + else if (error < 0) + goto done; + + /* Advance in the buffer */ + line_len -= (line_end - line); + line = line_end; + + error = add_push_report_pkt(push, pkt); + + git_pkt_free(pkt); + + if (error < 0 && error != GIT_ITEROVER) + goto done; + } + + error = 0; + +done: + if (reading_from_buf) + git_str_consume(data_pkt_buf, line_end); + return error; +} + +static int parse_report(transport_smart *transport, git_push *push) +{ + git_pkt *pkt = NULL; + const char *line_end = NULL; + gitno_buffer *buf = &transport->buffer; + int error, recvd; + git_str data_pkt_buf = GIT_STR_INIT; + + for (;;) { + if (buf->offset > 0) + error = git_pkt_parse_line(&pkt, &line_end, + buf->data, buf->offset); + else + error = GIT_EBUFS; + + if (error < 0 && error != GIT_EBUFS) { + error = -1; + goto done; + } + + if (error == GIT_EBUFS) { + if ((recvd = gitno_recv(buf)) < 0) { + error = recvd; + goto done; + } + + if (recvd == 0) { + git_error_set(GIT_ERROR_NET, "early EOF"); + error = GIT_EEOF; + goto done; + } + continue; + } + + if (gitno_consume(buf, line_end) < 0) + return -1; + + error = 0; + + switch (pkt->type) { + case GIT_PKT_DATA: + /* This is a sideband packet which contains other packets */ + error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt, &data_pkt_buf); + break; + case GIT_PKT_ERR: + git_error_set(GIT_ERROR_NET, "report-status: Error reported: %s", + ((git_pkt_err *)pkt)->error); + error = -1; + break; + case GIT_PKT_PROGRESS: + if (transport->connect_opts.callbacks.sideband_progress) { + git_pkt_progress *p = (git_pkt_progress *) pkt; + + if (p->len > INT_MAX) { + git_error_set(GIT_ERROR_NET, "oversized progress message"); + error = GIT_ERROR; + goto done; + } + + error = transport->connect_opts.callbacks.sideband_progress(p->data, (int)p->len, transport->connect_opts.callbacks.payload); + } + break; + default: + error = add_push_report_pkt(push, pkt); + break; + } + + git_pkt_free(pkt); + + /* add_push_report_pkt returns GIT_ITEROVER when it receives a flush */ + if (error == GIT_ITEROVER) { + error = 0; + if (data_pkt_buf.size > 0) { + /* If there was data remaining in the pack data buffer, + * then the server sent a partial pkt-line */ + git_error_set(GIT_ERROR_NET, "incomplete pack data pkt-line"); + error = GIT_ERROR; + } + goto done; + } + + if (error < 0) { + goto done; + } + } +done: + git_str_dispose(&data_pkt_buf); + return error; +} + +static int add_ref_from_push_spec(git_vector *refs, push_spec *push_spec) +{ + git_pkt_ref *added = git__calloc(1, sizeof(git_pkt_ref)); + GIT_ERROR_CHECK_ALLOC(added); + + added->type = GIT_PKT_REF; + git_oid_cpy(&added->head.oid, &push_spec->loid); + added->head.name = git__strdup(push_spec->refspec.dst); + + if (!added->head.name || + git_vector_insert(refs, added) < 0) { + git_pkt_free((git_pkt *)added); + return -1; + } + + return 0; +} + +static int update_refs_from_report( + git_vector *refs, + git_vector *push_specs, + git_vector *push_report) +{ + git_pkt_ref *ref; + push_spec *push_spec; + push_status *push_status; + size_t i, j, refs_len; + int cmp; + + /* For each push spec we sent to the server, we should have + * gotten back a status packet in the push report */ + if (push_specs->length != push_report->length) { + git_error_set(GIT_ERROR_NET, "report-status: protocol error"); + return -1; + } + + /* We require that push_specs be sorted with push_spec_rref_cmp, + * and that push_report be sorted with push_status_ref_cmp */ + git_vector_sort(push_specs); + git_vector_sort(push_report); + + git_vector_foreach(push_specs, i, push_spec) { + push_status = git_vector_get(push_report, i); + + /* For each push spec we sent to the server, we should have + * gotten back a status packet in the push report which matches */ + if (strcmp(push_spec->refspec.dst, push_status->ref)) { + git_error_set(GIT_ERROR_NET, "report-status: protocol error"); + return -1; + } + } + + /* We require that refs be sorted with ref_name_cmp */ + git_vector_sort(refs); + i = j = 0; + refs_len = refs->length; + + /* Merge join push_specs with refs */ + while (i < push_specs->length && j < refs_len) { + push_spec = git_vector_get(push_specs, i); + push_status = git_vector_get(push_report, i); + ref = git_vector_get(refs, j); + + cmp = strcmp(push_spec->refspec.dst, ref->head.name); + + /* Iterate appropriately */ + if (cmp <= 0) i++; + if (cmp >= 0) j++; + + /* Add case */ + if (cmp < 0 && + !push_status->msg && + add_ref_from_push_spec(refs, push_spec) < 0) + return -1; + + /* Update case, delete case */ + if (cmp == 0 && + !push_status->msg) + git_oid_cpy(&ref->head.oid, &push_spec->loid); + } + + for (; i < push_specs->length; i++) { + push_spec = git_vector_get(push_specs, i); + push_status = git_vector_get(push_report, i); + + /* Add case */ + if (!push_status->msg && + add_ref_from_push_spec(refs, push_spec) < 0) + return -1; + } + + /* Remove any refs which we updated to have a zero OID. */ + git_vector_rforeach(refs, i, ref) { + if (git_oid_is_zero(&ref->head.oid)) { + git_vector_remove(refs, i); + git_pkt_free((git_pkt *)ref); + } + } + + git_vector_sort(refs); + + return 0; +} + +struct push_packbuilder_payload +{ + git_smart_subtransport_stream *stream; + git_packbuilder *pb; + git_push_transfer_progress_cb cb; + void *cb_payload; + size_t last_bytes; + double last_progress_report_time; +}; + +static int stream_thunk(void *buf, size_t size, void *data) +{ + int error = 0; + struct push_packbuilder_payload *payload = data; + + if ((error = payload->stream->write(payload->stream, (const char *)buf, size)) < 0) + return error; + + if (payload->cb) { + double current_time = git__timer(); + double elapsed = current_time - payload->last_progress_report_time; + payload->last_bytes += size; + + if (elapsed < 0 || elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) { + payload->last_progress_report_time = current_time; + error = payload->cb(payload->pb->nr_written, payload->pb->nr_objects, payload->last_bytes, payload->cb_payload); + } + } + + return error; +} + +int git_smart__push(git_transport *transport, git_push *push) +{ + transport_smart *t = (transport_smart *)transport; + git_remote_callbacks *cbs = &t->connect_opts.callbacks; + struct push_packbuilder_payload packbuilder_payload = {0}; + git_str pktline = GIT_STR_INIT; + int error = 0, need_pack = 0; + push_spec *spec; + unsigned int i; + + packbuilder_payload.pb = push->pb; + + if (cbs && cbs->push_transfer_progress) { + packbuilder_payload.cb = cbs->push_transfer_progress; + packbuilder_payload.cb_payload = cbs->payload; + } + +#ifdef PUSH_DEBUG +{ + git_remote_head *head; + char hex[GIT_OID_HEXSZ+1]; hex[GIT_OID_HEXSZ] = '\0'; + + git_vector_foreach(&push->remote->refs, i, head) { + git_oid_fmt(hex, &head->oid); + fprintf(stderr, "%s (%s)\n", hex, head->name); + } + + git_vector_foreach(&push->specs, i, spec) { + git_oid_fmt(hex, &spec->roid); + fprintf(stderr, "%s (%s) -> ", hex, spec->lref); + git_oid_fmt(hex, &spec->loid); + fprintf(stderr, "%s (%s)\n", hex, spec->rref ? + spec->rref : spec->lref); + } +} +#endif + + /* + * Figure out if we need to send a packfile; which is in all + * cases except when we only send delete commands + */ + git_vector_foreach(&push->specs, i, spec) { + if (spec->refspec.src && spec->refspec.src[0] != '\0') { + need_pack = 1; + break; + } + } + + /* prepare pack before sending pack header to avoid timeouts */ + if (need_pack && ((error = git_packbuilder__prepare(push->pb))) < 0) + goto done; + + if ((error = git_smart__get_push_stream(t, &packbuilder_payload.stream)) < 0 || + (error = gen_pktline(&pktline, push)) < 0 || + (error = packbuilder_payload.stream->write(packbuilder_payload.stream, git_str_cstr(&pktline), git_str_len(&pktline))) < 0) + goto done; + + if (need_pack && + (error = git_packbuilder_foreach(push->pb, &stream_thunk, &packbuilder_payload)) < 0) + goto done; + + /* If we sent nothing or the server doesn't support report-status, then + * we consider the pack to have been unpacked successfully */ + if (!push->specs.length || !push->report_status) + push->unpack_ok = 1; + else if ((error = parse_report(t, push)) < 0) + goto done; + + /* If progress is being reported write the final report */ + if (cbs && cbs->push_transfer_progress) { + error = cbs->push_transfer_progress( + push->pb->nr_written, + push->pb->nr_objects, + packbuilder_payload.last_bytes, + cbs->payload); + + if (error < 0) + goto done; + } + + if (push->status.length) { + error = update_refs_from_report(&t->refs, &push->specs, &push->status); + if (error < 0) + goto done; + + error = git_smart__update_heads(t, NULL); + } + +done: + git_str_dispose(&pktline); + return error; +} diff --git a/src/libgit2/transports/ssh.c b/src/libgit2/transports/ssh.c new file mode 100644 index 000000000..89f085230 --- /dev/null +++ b/src/libgit2/transports/ssh.c @@ -0,0 +1,915 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "ssh.h" + +#ifdef GIT_SSH +#include +#endif + +#include "runtime.h" +#include "net.h" +#include "netops.h" +#include "smart.h" +#include "streams/socket.h" + +#include "git2/credential.h" +#include "git2/sys/credential.h" + +#ifdef GIT_SSH + +#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport) + +static const char cmd_uploadpack[] = "git-upload-pack"; +static const char cmd_receivepack[] = "git-receive-pack"; + +typedef struct { + git_smart_subtransport_stream parent; + git_stream *io; + LIBSSH2_SESSION *session; + LIBSSH2_CHANNEL *channel; + const char *cmd; + git_net_url url; + unsigned sent_command : 1; +} ssh_stream; + +typedef struct { + git_smart_subtransport parent; + transport_smart *owner; + ssh_stream *current_stream; + git_credential *cred; + char *cmd_uploadpack; + char *cmd_receivepack; +} ssh_subtransport; + +static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username); + +static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) +{ + char *ssherr; + libssh2_session_last_error(session, &ssherr, NULL, 0); + + git_error_set(GIT_ERROR_SSH, "%s: %s", errmsg, ssherr); +} + +/* + * Create a git protocol request. + * + * For example: git-upload-pack '/libgit2/libgit2' + */ +static int gen_proto(git_str *request, const char *cmd, git_net_url *url) +{ + const char *repo; + + repo = url->path; + + if (repo && repo[0] == '/' && repo[1] == '~') + repo++; + + if (!repo || !repo[0]) { + git_error_set(GIT_ERROR_NET, "malformed git protocol URL"); + return -1; + } + + git_str_puts(request, cmd); + git_str_puts(request, " '"); + git_str_puts(request, repo); + git_str_puts(request, "'"); + + if (git_str_oom(request)) + return -1; + + return 0; +} + +static int send_command(ssh_stream *s) +{ + int error; + git_str request = GIT_STR_INIT; + + error = gen_proto(&request, s->cmd, &s->url); + if (error < 0) + goto cleanup; + + error = libssh2_channel_exec(s->channel, request.ptr); + if (error < LIBSSH2_ERROR_NONE) { + ssh_error(s->session, "SSH could not execute request"); + goto cleanup; + } + + s->sent_command = 1; + +cleanup: + git_str_dispose(&request); + return error; +} + +static int ssh_stream_read( + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read) +{ + int rc; + ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); + + *bytes_read = 0; + + if (!s->sent_command && send_command(s) < 0) + return -1; + + if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) { + ssh_error(s->session, "SSH could not read data"); + return -1; + } + + /* + * If we can't get anything out of stdout, it's typically a + * not-found error, so read from stderr and signal EOF on + * stderr. + */ + if (rc == 0) { + if ((rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) { + git_error_set(GIT_ERROR_SSH, "%*s", rc, buffer); + return GIT_EEOF; + } else if (rc < LIBSSH2_ERROR_NONE) { + ssh_error(s->session, "SSH could not read stderr"); + return -1; + } + } + + + *bytes_read = rc; + + return 0; +} + +static int ssh_stream_write( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); + size_t off = 0; + ssize_t ret = 0; + + if (!s->sent_command && send_command(s) < 0) + return -1; + + do { + ret = libssh2_channel_write(s->channel, buffer + off, len - off); + if (ret < 0) + break; + + off += ret; + + } while (off < len); + + if (ret < 0) { + ssh_error(s->session, "SSH could not write data"); + return -1; + } + + return 0; +} + +static void ssh_stream_free(git_smart_subtransport_stream *stream) +{ + ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); + ssh_subtransport *t; + + if (!stream) + return; + + t = OWNING_SUBTRANSPORT(s); + t->current_stream = NULL; + + if (s->channel) { + libssh2_channel_close(s->channel); + libssh2_channel_free(s->channel); + s->channel = NULL; + } + + if (s->session) { + libssh2_session_disconnect(s->session, "closing transport"); + libssh2_session_free(s->session); + s->session = NULL; + } + + if (s->io) { + git_stream_close(s->io); + git_stream_free(s->io); + s->io = NULL; + } + + git_net_url_dispose(&s->url); + git__free(s); +} + +static int ssh_stream_alloc( + ssh_subtransport *t, + const char *cmd, + git_smart_subtransport_stream **stream) +{ + ssh_stream *s; + + GIT_ASSERT_ARG(stream); + + s = git__calloc(sizeof(ssh_stream), 1); + GIT_ERROR_CHECK_ALLOC(s); + + s->parent.subtransport = &t->parent; + s->parent.read = ssh_stream_read; + s->parent.write = ssh_stream_write; + s->parent.free = ssh_stream_free; + + s->cmd = cmd; + + *stream = &s->parent; + return 0; +} + +static int ssh_agent_auth(LIBSSH2_SESSION *session, git_credential_ssh_key *c) { + int rc = LIBSSH2_ERROR_NONE; + + struct libssh2_agent_publickey *curr, *prev = NULL; + + LIBSSH2_AGENT *agent = libssh2_agent_init(session); + + if (agent == NULL) + return -1; + + rc = libssh2_agent_connect(agent); + + if (rc != LIBSSH2_ERROR_NONE) + goto shutdown; + + rc = libssh2_agent_list_identities(agent); + + if (rc != LIBSSH2_ERROR_NONE) + goto shutdown; + + while (1) { + rc = libssh2_agent_get_identity(agent, &curr, prev); + + if (rc < 0) + goto shutdown; + + /* rc is set to 1 whenever the ssh agent ran out of keys to check. + * Set the error code to authentication failure rather than erroring + * out with an untranslatable error code. + */ + if (rc == 1) { + rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; + goto shutdown; + } + + rc = libssh2_agent_userauth(agent, c->username, curr); + + if (rc == 0) + break; + + prev = curr; + } + +shutdown: + + if (rc != LIBSSH2_ERROR_NONE) + ssh_error(session, "error authenticating"); + + libssh2_agent_disconnect(agent); + libssh2_agent_free(agent); + + return rc; +} + +static int _git_ssh_authenticate_session( + LIBSSH2_SESSION *session, + git_credential *cred) +{ + int rc; + + do { + git_error_clear(); + switch (cred->credtype) { + case GIT_CREDENTIAL_USERPASS_PLAINTEXT: { + git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred; + rc = libssh2_userauth_password(session, c->username, c->password); + break; + } + case GIT_CREDENTIAL_SSH_KEY: { + git_credential_ssh_key *c = (git_credential_ssh_key *)cred; + + if (c->privatekey) + rc = libssh2_userauth_publickey_fromfile( + session, c->username, c->publickey, + c->privatekey, c->passphrase); + else + rc = ssh_agent_auth(session, c); + + break; + } + case GIT_CREDENTIAL_SSH_CUSTOM: { + git_credential_ssh_custom *c = (git_credential_ssh_custom *)cred; + + rc = libssh2_userauth_publickey( + session, c->username, (const unsigned char *)c->publickey, + c->publickey_len, c->sign_callback, &c->payload); + break; + } + case GIT_CREDENTIAL_SSH_INTERACTIVE: { + void **abstract = libssh2_session_abstract(session); + git_credential_ssh_interactive *c = (git_credential_ssh_interactive *)cred; + + /* ideally, we should be able to set this by calling + * libssh2_session_init_ex() instead of libssh2_session_init(). + * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey() + * allows you to pass the `abstract` as part of the call, whereas + * libssh2_userauth_keyboard_interactive() does not! + * + * The only way to set the `abstract` pointer is by calling + * libssh2_session_abstract(), which will replace the existing + * pointer as is done below. This is safe for now (at time of writing), + * but may not be valid in future. + */ + *abstract = c->payload; + + rc = libssh2_userauth_keyboard_interactive( + session, c->username, c->prompt_callback); + break; + } +#ifdef GIT_SSH_MEMORY_CREDENTIALS + case GIT_CREDENTIAL_SSH_MEMORY: { + git_credential_ssh_key *c = (git_credential_ssh_key *)cred; + + GIT_ASSERT(c->username); + GIT_ASSERT(c->privatekey); + + rc = libssh2_userauth_publickey_frommemory( + session, + c->username, + strlen(c->username), + c->publickey, + c->publickey ? strlen(c->publickey) : 0, + c->privatekey, + strlen(c->privatekey), + c->passphrase); + break; + } +#endif + default: + rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; + } + } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + + if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || + rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED || + rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) + return GIT_EAUTH; + + if (rc != LIBSSH2_ERROR_NONE) { + if (!git_error_last()) + ssh_error(session, "Failed to authenticate SSH session"); + return -1; + } + + return 0; +} + +static int request_creds(git_credential **out, ssh_subtransport *t, const char *user, int auth_methods) +{ + int error, no_callback = 0; + git_credential *cred = NULL; + + if (!t->owner->connect_opts.callbacks.credentials) { + no_callback = 1; + } else { + error = t->owner->connect_opts.callbacks.credentials( + &cred, + t->owner->url, + user, + auth_methods, + t->owner->connect_opts.callbacks.payload); + + if (error == GIT_PASSTHROUGH) { + no_callback = 1; + } else if (error < 0) { + return error; + } else if (!cred) { + git_error_set(GIT_ERROR_SSH, "callback failed to initialize SSH credentials"); + return -1; + } + } + + if (no_callback) { + git_error_set(GIT_ERROR_SSH, "authentication required but no callback set"); + return GIT_EAUTH; + } + + if (!(cred->credtype & auth_methods)) { + cred->free(cred); + git_error_set(GIT_ERROR_SSH, "authentication callback returned unsupported credentials type"); + return GIT_EAUTH; + } + + *out = cred; + + return 0; +} + +static int _git_ssh_session_create( + LIBSSH2_SESSION **session, + git_stream *io) +{ + int rc = 0; + LIBSSH2_SESSION *s; + git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent); + + GIT_ASSERT_ARG(session); + + s = libssh2_session_init(); + if (!s) { + git_error_set(GIT_ERROR_NET, "failed to initialize SSH session"); + return -1; + } + + do { + rc = libssh2_session_handshake(s, socket->s); + } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + + if (rc != LIBSSH2_ERROR_NONE) { + ssh_error(s, "failed to start SSH session"); + libssh2_session_free(s); + return -1; + } + + libssh2_session_set_blocking(s, 1); + + *session = s; + + return 0; +} + +#define SSH_DEFAULT_PORT "22" + +static int _git_ssh_setup_conn( + ssh_subtransport *t, + const char *url, + const char *cmd, + git_smart_subtransport_stream **stream) +{ + int auth_methods, error = 0; + ssh_stream *s; + git_credential *cred = NULL; + LIBSSH2_SESSION *session=NULL; + LIBSSH2_CHANNEL *channel=NULL; + + t->current_stream = NULL; + + *stream = NULL; + if (ssh_stream_alloc(t, cmd, stream) < 0) + return -1; + + s = (ssh_stream *)*stream; + s->session = NULL; + s->channel = NULL; + + if (git_net_str_is_url(url)) + error = git_net_url_parse(&s->url, url); + else + error = git_net_url_parse_scp(&s->url, url); + + if (error < 0) + goto done; + + if ((error = git_socket_stream_new(&s->io, s->url.host, s->url.port)) < 0 || + (error = git_stream_connect(s->io)) < 0) + goto done; + + if ((error = _git_ssh_session_create(&session, s->io)) < 0) + goto done; + + if (t->owner->connect_opts.callbacks.certificate_check != NULL) { + git_cert_hostkey cert = {{ 0 }}, *cert_ptr; + const char *key; + size_t cert_len; + int cert_type; + + cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2; + + key = libssh2_session_hostkey(session, &cert_len, &cert_type); + if (key != NULL) { + cert.type |= GIT_CERT_SSH_RAW; + cert.hostkey = key; + cert.hostkey_len = cert_len; + switch (cert_type) { + case LIBSSH2_HOSTKEY_TYPE_RSA: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_RSA; + break; + case LIBSSH2_HOSTKEY_TYPE_DSS: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_DSS; + break; + +#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256 + case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256; + break; + case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384; + break; + case LIBSSH2_KNOWNHOST_KEY_ECDSA_521: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521; + break; +#endif + +#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519 + case LIBSSH2_HOSTKEY_TYPE_ED25519: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ED25519; + break; +#endif + default: + cert.raw_type = GIT_CERT_SSH_RAW_TYPE_UNKNOWN; + } + } + +#ifdef LIBSSH2_HOSTKEY_HASH_SHA256 + key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256); + if (key != NULL) { + cert.type |= GIT_CERT_SSH_SHA256; + memcpy(&cert.hash_sha256, key, 32); + } +#endif + + key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); + if (key != NULL) { + cert.type |= GIT_CERT_SSH_SHA1; + memcpy(&cert.hash_sha1, key, 20); + } + + key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); + if (key != NULL) { + cert.type |= GIT_CERT_SSH_MD5; + memcpy(&cert.hash_md5, key, 16); + } + + if (cert.type == 0) { + git_error_set(GIT_ERROR_SSH, "unable to get the host key"); + error = -1; + goto done; + } + + /* We don't currently trust any hostkeys */ + git_error_clear(); + + cert_ptr = &cert; + + error = t->owner->connect_opts.callbacks.certificate_check( + (git_cert *)cert_ptr, + 0, + s->url.host, + t->owner->connect_opts.callbacks.payload); + + if (error < 0 && error != GIT_PASSTHROUGH) { + if (!git_error_last()) + git_error_set(GIT_ERROR_NET, "user cancelled hostkey check"); + + goto done; + } + } + + /* we need the username to ask for auth methods */ + if (!s->url.username) { + if ((error = request_creds(&cred, t, NULL, GIT_CREDENTIAL_USERNAME)) < 0) + goto done; + + s->url.username = git__strdup(((git_credential_username *) cred)->username); + cred->free(cred); + cred = NULL; + if (!s->url.username) + goto done; + } else if (s->url.username && s->url.password) { + if ((error = git_credential_userpass_plaintext_new(&cred, s->url.username, s->url.password)) < 0) + goto done; + } + + if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0) + goto done; + + error = GIT_EAUTH; + /* if we already have something to try */ + if (cred && auth_methods & cred->credtype) + error = _git_ssh_authenticate_session(session, cred); + + while (error == GIT_EAUTH) { + if (cred) { + cred->free(cred); + cred = NULL; + } + + if ((error = request_creds(&cred, t, s->url.username, auth_methods)) < 0) + goto done; + + if (strcmp(s->url.username, git_credential_get_username(cred))) { + git_error_set(GIT_ERROR_SSH, "username does not match previous request"); + error = -1; + goto done; + } + + error = _git_ssh_authenticate_session(session, cred); + + if (error == GIT_EAUTH) { + /* refresh auth methods */ + if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0) + goto done; + else + error = GIT_EAUTH; + } + } + + if (error < 0) + goto done; + + channel = libssh2_channel_open_session(session); + if (!channel) { + error = -1; + ssh_error(session, "Failed to open SSH channel"); + goto done; + } + + libssh2_channel_set_blocking(channel, 1); + + s->session = session; + s->channel = channel; + + t->current_stream = s; + +done: + if (error < 0) { + ssh_stream_free(*stream); + + if (session) + libssh2_session_free(session); + } + + if (cred) + cred->free(cred); + + return error; +} + +static int ssh_uploadpack_ls( + ssh_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack; + + return _git_ssh_setup_conn(t, url, cmd, stream); +} + +static int ssh_uploadpack( + ssh_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + GIT_UNUSED(url); + + if (t->current_stream) { + *stream = &t->current_stream->parent; + return 0; + } + + git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK"); + return -1; +} + +static int ssh_receivepack_ls( + ssh_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack; + + + return _git_ssh_setup_conn(t, url, cmd, stream); +} + +static int ssh_receivepack( + ssh_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + GIT_UNUSED(url); + + if (t->current_stream) { + *stream = &t->current_stream->parent; + return 0; + } + + git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK"); + return -1; +} + +static int _ssh_action( + git_smart_subtransport_stream **stream, + git_smart_subtransport *subtransport, + const char *url, + git_smart_service_t action) +{ + ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); + + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + return ssh_uploadpack_ls(t, url, stream); + + case GIT_SERVICE_UPLOADPACK: + return ssh_uploadpack(t, url, stream); + + case GIT_SERVICE_RECEIVEPACK_LS: + return ssh_receivepack_ls(t, url, stream); + + case GIT_SERVICE_RECEIVEPACK: + return ssh_receivepack(t, url, stream); + } + + *stream = NULL; + return -1; +} + +static int _ssh_close(git_smart_subtransport *subtransport) +{ + ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); + + GIT_ASSERT(!t->current_stream); + + GIT_UNUSED(t); + + return 0; +} + +static void _ssh_free(git_smart_subtransport *subtransport) +{ + ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); + + git__free(t->cmd_uploadpack); + git__free(t->cmd_receivepack); + git__free(t); +} + +#define SSH_AUTH_PUBLICKEY "publickey" +#define SSH_AUTH_PASSWORD "password" +#define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive" + +static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username) +{ + const char *list, *ptr; + + *out = 0; + + list = libssh2_userauth_list(session, username, strlen(username)); + + /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ + if (list == NULL && !libssh2_userauth_authenticated(session)) { + ssh_error(session, "Failed to retrieve list of SSH authentication methods"); + return GIT_EAUTH; + } + + ptr = list; + while (ptr) { + if (*ptr == ',') + ptr++; + + if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) { + *out |= GIT_CREDENTIAL_SSH_KEY; + *out |= GIT_CREDENTIAL_SSH_CUSTOM; +#ifdef GIT_SSH_MEMORY_CREDENTIALS + *out |= GIT_CREDENTIAL_SSH_MEMORY; +#endif + ptr += strlen(SSH_AUTH_PUBLICKEY); + continue; + } + + if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) { + *out |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; + ptr += strlen(SSH_AUTH_PASSWORD); + continue; + } + + if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) { + *out |= GIT_CREDENTIAL_SSH_INTERACTIVE; + ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE); + continue; + } + + /* Skip it if we don't know it */ + ptr = strchr(ptr, ','); + } + + return 0; +} +#endif + +int git_smart_subtransport_ssh( + git_smart_subtransport **out, git_transport *owner, void *param) +{ +#ifdef GIT_SSH + ssh_subtransport *t; + + GIT_ASSERT_ARG(out); + + GIT_UNUSED(param); + + t = git__calloc(sizeof(ssh_subtransport), 1); + GIT_ERROR_CHECK_ALLOC(t); + + t->owner = (transport_smart *)owner; + t->parent.action = _ssh_action; + t->parent.close = _ssh_close; + t->parent.free = _ssh_free; + + *out = (git_smart_subtransport *) t; + return 0; +#else + GIT_UNUSED(owner); + GIT_UNUSED(param); + + GIT_ASSERT_ARG(out); + *out = NULL; + + git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support"); + return -1; +#endif +} + +int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload) +{ +#ifdef GIT_SSH + git_strarray *paths = (git_strarray *) payload; + git_transport *transport; + transport_smart *smart; + ssh_subtransport *t; + int error; + git_smart_subtransport_definition ssh_definition = { + git_smart_subtransport_ssh, + 0, /* no RPC */ + NULL, + }; + + if (paths->count != 2) { + git_error_set(GIT_ERROR_SSH, "invalid ssh paths, must be two strings"); + return GIT_EINVALIDSPEC; + } + + if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0) + return error; + + smart = (transport_smart *) transport; + t = (ssh_subtransport *) smart->wrapped; + + t->cmd_uploadpack = git__strdup(paths->strings[0]); + GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack); + t->cmd_receivepack = git__strdup(paths->strings[1]); + GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack); + + *out = transport; + return 0; +#else + GIT_UNUSED(owner); + GIT_UNUSED(payload); + + GIT_ASSERT_ARG(out); + *out = NULL; + + git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support"); + return -1; +#endif +} + +#ifdef GIT_SSH +static void shutdown_ssh(void) +{ + libssh2_exit(); +} +#endif + +int git_transport_ssh_global_init(void) +{ +#ifdef GIT_SSH + if (libssh2_init(0) < 0) { + git_error_set(GIT_ERROR_SSH, "unable to initialize libssh2"); + return -1; + } + + return git_runtime_shutdown_register(shutdown_ssh); + +#else + + /* Nothing to initialize */ + return 0; + +#endif +} diff --git a/src/libgit2/transports/ssh.h b/src/libgit2/transports/ssh.h new file mode 100644 index 000000000..d3e741f1d --- /dev/null +++ b/src/libgit2/transports/ssh.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_transports_ssh_h__ +#define INCLUDE_transports_ssh_h__ + +#include "common.h" + +int git_transport_ssh_global_init(void); + +#endif diff --git a/src/libgit2/transports/winhttp.c b/src/libgit2/transports/winhttp.c new file mode 100644 index 000000000..8ec5b37c5 --- /dev/null +++ b/src/libgit2/transports/winhttp.c @@ -0,0 +1,1686 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#ifdef GIT_WINHTTP + +#include "git2.h" +#include "git2/transport.h" +#include "posix.h" +#include "str.h" +#include "netops.h" +#include "smart.h" +#include "remote.h" +#include "repository.h" +#include "http.h" +#include "git2/sys/credential.h" + +#include +#include + +/* For IInternetSecurityManager zone check */ +#include +#include + +#define WIDEN2(s) L ## s +#define WIDEN(s) WIDEN2(s) + +#define MAX_CONTENT_TYPE_LEN 100 +#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109 +#define CACHED_POST_BODY_BUF_SIZE 4096 +#define UUID_LENGTH_CCH 32 +#define TIMEOUT_INFINITE -1 +#define DEFAULT_CONNECT_TIMEOUT 60000 +#ifndef WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH +#define WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH 0 +#endif + +#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 +# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 0x00000200 +#endif + +#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 +# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 0x00000800 +#endif + +#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 +# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 0x00002000 +#endif + +#ifndef WINHTTP_NO_CLIENT_CERT_CONTEXT +# define WINHTTP_NO_CLIENT_CERT_CONTEXT NULL +#endif + +#ifndef HTTP_STATUS_PERMANENT_REDIRECT +# define HTTP_STATUS_PERMANENT_REDIRECT 308 +#endif + +#ifndef DWORD_MAX +# define DWORD_MAX 0xffffffff +#endif + +bool git_http__expect_continue = false; + +static const char *prefix_https = "https://"; +static const char *upload_pack_service = "upload-pack"; +static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; +static const char *upload_pack_service_url = "/git-upload-pack"; +static const char *receive_pack_service = "receive-pack"; +static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack"; +static const char *receive_pack_service_url = "/git-receive-pack"; +static const wchar_t *get_verb = L"GET"; +static const wchar_t *post_verb = L"POST"; +static const wchar_t *pragma_nocache = L"Pragma: no-cache"; +static const wchar_t *transfer_encoding = L"Transfer-Encoding: chunked"; +static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | + SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | + SECURITY_FLAG_IGNORE_UNKNOWN_CA; + +#if defined(__MINGW32__) +static const CLSID CLSID_InternetSecurityManager_mingw = + { 0x7B8A2D94, 0x0AC9, 0x11D1, + { 0x89, 0x6C, 0x00, 0xC0, 0x4F, 0xB6, 0xBF, 0xC4 } }; +static const IID IID_IInternetSecurityManager_mingw = + { 0x79EAC9EE, 0xBAF9, 0x11CE, + { 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B } }; + +# define CLSID_InternetSecurityManager CLSID_InternetSecurityManager_mingw +# define IID_IInternetSecurityManager IID_IInternetSecurityManager_mingw +#endif + +#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport) + +typedef enum { + GIT_WINHTTP_AUTH_BASIC = 1, + GIT_WINHTTP_AUTH_NTLM = 2, + GIT_WINHTTP_AUTH_NEGOTIATE = 4, + GIT_WINHTTP_AUTH_DIGEST = 8 +} winhttp_authmechanism_t; + +typedef struct { + git_smart_subtransport_stream parent; + const char *service; + const char *service_url; + const wchar_t *verb; + HINTERNET request; + wchar_t *request_uri; + char *chunk_buffer; + unsigned chunk_buffer_len; + HANDLE post_body; + DWORD post_body_len; + unsigned sent_request : 1, + received_response : 1, + chunked : 1, + status_sending_request_reached: 1; +} winhttp_stream; + +typedef struct { + git_net_url url; + git_credential *cred; + int auth_mechanisms; + bool url_cred_presented; +} winhttp_server; + +typedef struct { + git_smart_subtransport parent; + transport_smart *owner; + + winhttp_server server; + winhttp_server proxy; + + HINTERNET session; + HINTERNET connection; +} winhttp_subtransport; + +static int apply_userpass_credentials(HINTERNET request, DWORD target, int mechanisms, git_credential *cred) +{ + git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred; + wchar_t *user = NULL, *pass = NULL; + int user_len = 0, pass_len = 0, error = 0; + DWORD native_scheme; + + if (mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) { + native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE; + } else if (mechanisms & GIT_WINHTTP_AUTH_NTLM) { + native_scheme = WINHTTP_AUTH_SCHEME_NTLM; + } else if (mechanisms & GIT_WINHTTP_AUTH_DIGEST) { + native_scheme = WINHTTP_AUTH_SCHEME_DIGEST; + } else if (mechanisms & GIT_WINHTTP_AUTH_BASIC) { + native_scheme = WINHTTP_AUTH_SCHEME_BASIC; + } else { + git_error_set(GIT_ERROR_HTTP, "invalid authentication scheme"); + error = GIT_EAUTH; + goto done; + } + + if ((error = user_len = git__utf8_to_16_alloc(&user, c->username)) < 0) + goto done; + + if ((error = pass_len = git__utf8_to_16_alloc(&pass, c->password)) < 0) + goto done; + + if (!WinHttpSetCredentials(request, target, native_scheme, user, pass, NULL)) { + git_error_set(GIT_ERROR_OS, "failed to set credentials"); + error = -1; + } + +done: + if (user_len > 0) + git__memzero(user, user_len * sizeof(wchar_t)); + + if (pass_len > 0) + git__memzero(pass, pass_len * sizeof(wchar_t)); + + git__free(user); + git__free(pass); + + return error; +} + +static int apply_default_credentials(HINTERNET request, DWORD target, int mechanisms) +{ + DWORD autologon_level = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW; + DWORD native_scheme = 0; + + if ((mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) != 0) { + native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE; + } else if ((mechanisms & GIT_WINHTTP_AUTH_NTLM) != 0) { + native_scheme = WINHTTP_AUTH_SCHEME_NTLM; + } else { + git_error_set(GIT_ERROR_HTTP, "invalid authentication scheme"); + return GIT_EAUTH; + } + + /* + * Autologon policy must be "low" to use default creds. + * This is safe as the user has explicitly requested it. + */ + if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &autologon_level, sizeof(DWORD))) { + git_error_set(GIT_ERROR_OS, "could not configure logon policy"); + return -1; + } + + if (!WinHttpSetCredentials(request, target, native_scheme, NULL, NULL, NULL)) { + git_error_set(GIT_ERROR_OS, "could not configure credentials"); + return -1; + } + + return 0; +} + +static int acquire_url_cred( + git_credential **cred, + unsigned int allowed_types, + const char *username, + const char *password) +{ + if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) + return git_credential_userpass_plaintext_new(cred, username, password); + + if ((allowed_types & GIT_CREDENTIAL_DEFAULT) && *username == '\0' && *password == '\0') + return git_credential_default_new(cred); + + return 1; +} + +static int acquire_fallback_cred( + git_credential **cred, + const char *url, + unsigned int allowed_types) +{ + int error = 1; + + /* If the target URI supports integrated Windows authentication + * as an authentication mechanism */ + if (GIT_CREDENTIAL_DEFAULT & allowed_types) { + wchar_t *wide_url; + HRESULT hCoInitResult; + + /* Convert URL to wide characters */ + if (git__utf8_to_16_alloc(&wide_url, url) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert string to wide form"); + return -1; + } + + hCoInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED); + + if (SUCCEEDED(hCoInitResult) || hCoInitResult == RPC_E_CHANGED_MODE) { + IInternetSecurityManager *pISM; + + /* And if the target URI is in the My Computer, Intranet, or Trusted zones */ + if (SUCCEEDED(CoCreateInstance(&CLSID_InternetSecurityManager, NULL, + CLSCTX_ALL, &IID_IInternetSecurityManager, (void **)&pISM))) { + DWORD dwZone; + + if (SUCCEEDED(pISM->lpVtbl->MapUrlToZone(pISM, wide_url, &dwZone, 0)) && + (URLZONE_LOCAL_MACHINE == dwZone || + URLZONE_INTRANET == dwZone || + URLZONE_TRUSTED == dwZone)) { + git_credential *existing = *cred; + + if (existing) + existing->free(existing); + + /* Then use default Windows credentials to authenticate this request */ + error = git_credential_default_new(cred); + } + + pISM->lpVtbl->Release(pISM); + } + + /* Only uninitialize if the call to CoInitializeEx was successful. */ + if (SUCCEEDED(hCoInitResult)) + CoUninitialize(); + } + + git__free(wide_url); + } + + return error; +} + +static int certificate_check(winhttp_stream *s, int valid) +{ + int error; + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + PCERT_CONTEXT cert_ctx; + DWORD cert_ctx_size = sizeof(cert_ctx); + git_cert_x509 cert; + + /* If there is no override, we should fail if WinHTTP doesn't think it's fine */ + if (t->owner->connect_opts.callbacks.certificate_check == NULL && !valid) { + if (!git_error_last()) + git_error_set(GIT_ERROR_HTTP, "unknown certificate check failure"); + + return GIT_ECERTIFICATE; + } + + if (t->owner->connect_opts.callbacks.certificate_check == NULL || git__strcmp(t->server.url.scheme, "https") != 0) + return 0; + + if (!WinHttpQueryOption(s->request, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert_ctx, &cert_ctx_size)) { + git_error_set(GIT_ERROR_OS, "failed to get server certificate"); + return -1; + } + + git_error_clear(); + cert.parent.cert_type = GIT_CERT_X509; + cert.data = cert_ctx->pbCertEncoded; + cert.len = cert_ctx->cbCertEncoded; + error = t->owner->connect_opts.callbacks.certificate_check((git_cert *) &cert, valid, t->server.url.host, t->owner->connect_opts.callbacks.payload); + CertFreeCertificateContext(cert_ctx); + + if (error == GIT_PASSTHROUGH) + error = valid ? 0 : GIT_ECERTIFICATE; + + if (error < 0 && !git_error_last()) + git_error_set(GIT_ERROR_HTTP, "user cancelled certificate check"); + + return error; +} + +static void winhttp_stream_close(winhttp_stream *s) +{ + if (s->chunk_buffer) { + git__free(s->chunk_buffer); + s->chunk_buffer = NULL; + } + + if (s->post_body) { + CloseHandle(s->post_body); + s->post_body = NULL; + } + + if (s->request_uri) { + git__free(s->request_uri); + s->request_uri = NULL; + } + + if (s->request) { + WinHttpCloseHandle(s->request); + s->request = NULL; + } + + s->sent_request = 0; +} + +static int apply_credentials( + HINTERNET request, + git_net_url *url, + int target, + git_credential *creds, + int mechanisms) +{ + int error = 0; + + GIT_UNUSED(url); + + /* If we have creds, just apply them */ + if (creds && creds->credtype == GIT_CREDENTIAL_USERPASS_PLAINTEXT) + error = apply_userpass_credentials(request, target, mechanisms, creds); + else if (creds && creds->credtype == GIT_CREDENTIAL_DEFAULT) + error = apply_default_credentials(request, target, mechanisms); + + return error; +} + +static int winhttp_stream_connect(winhttp_stream *s) +{ + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + git_str buf = GIT_STR_INIT; + char *proxy_url = NULL; + wchar_t ct[MAX_CONTENT_TYPE_LEN]; + LPCWSTR types[] = { L"*/*", NULL }; + BOOL peerdist = FALSE; + int error = -1; + unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS; + int default_timeout = TIMEOUT_INFINITE; + int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; + DWORD autologon_policy = WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH; + + const char *service_url = s->service_url; + size_t i; + const git_proxy_options *proxy_opts; + + /* If path already ends in /, remove the leading slash from service_url */ + if ((git__suffixcmp(t->server.url.path, "/") == 0) && (git__prefixcmp(service_url, "/") == 0)) + service_url++; + /* Prepare URL */ + git_str_printf(&buf, "%s%s", t->server.url.path, service_url); + + if (git_str_oom(&buf)) + return -1; + + /* Convert URL to wide characters */ + if (git__utf8_to_16_alloc(&s->request_uri, git_str_cstr(&buf)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert string to wide form"); + goto on_error; + } + + /* Establish request */ + s->request = WinHttpOpenRequest( + t->connection, + s->verb, + s->request_uri, + NULL, + WINHTTP_NO_REFERER, + types, + git__strcmp(t->server.url.scheme, "https") == 0 ? WINHTTP_FLAG_SECURE : 0); + + if (!s->request) { + git_error_set(GIT_ERROR_OS, "failed to open request"); + goto on_error; + } + + /* Never attempt default credentials; we'll provide them explicitly. */ + if (!WinHttpSetOption(s->request, WINHTTP_OPTION_AUTOLOGON_POLICY, &autologon_policy, sizeof(DWORD))) + return -1; + + if (!WinHttpSetTimeouts(s->request, default_timeout, default_connect_timeout, default_timeout, default_timeout)) { + git_error_set(GIT_ERROR_OS, "failed to set timeouts for WinHTTP"); + goto on_error; + } + + proxy_opts = &t->owner->connect_opts.proxy_opts; + if (proxy_opts->type == GIT_PROXY_AUTO) { + /* Set proxy if necessary */ + if (git_remote__http_proxy(&proxy_url, t->owner->owner, &t->server.url) < 0) + goto on_error; + } + else if (proxy_opts->type == GIT_PROXY_SPECIFIED) { + proxy_url = git__strdup(proxy_opts->url); + GIT_ERROR_CHECK_ALLOC(proxy_url); + } + + if (proxy_url) { + git_str processed_url = GIT_STR_INIT; + WINHTTP_PROXY_INFO proxy_info; + wchar_t *proxy_wide; + + git_net_url_dispose(&t->proxy.url); + + if ((error = git_net_url_parse(&t->proxy.url, proxy_url)) < 0) + goto on_error; + + if (strcmp(t->proxy.url.scheme, "http") != 0 && strcmp(t->proxy.url.scheme, "https") != 0) { + git_error_set(GIT_ERROR_HTTP, "invalid URL: '%s'", proxy_url); + error = -1; + goto on_error; + } + + git_str_puts(&processed_url, t->proxy.url.scheme); + git_str_PUTS(&processed_url, "://"); + + if (git_net_url_is_ipv6(&t->proxy.url)) + git_str_putc(&processed_url, '['); + + git_str_puts(&processed_url, t->proxy.url.host); + + if (git_net_url_is_ipv6(&t->proxy.url)) + git_str_putc(&processed_url, ']'); + + if (!git_net_url_is_default_port(&t->proxy.url)) + git_str_printf(&processed_url, ":%s", t->proxy.url.port); + + if (git_str_oom(&processed_url)) { + error = -1; + goto on_error; + } + + /* Convert URL to wide characters */ + error = git__utf8_to_16_alloc(&proxy_wide, processed_url.ptr); + git_str_dispose(&processed_url); + if (error < 0) + goto on_error; + + proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; + proxy_info.lpszProxy = proxy_wide; + proxy_info.lpszProxyBypass = NULL; + + if (!WinHttpSetOption(s->request, + WINHTTP_OPTION_PROXY, + &proxy_info, + sizeof(WINHTTP_PROXY_INFO))) { + git_error_set(GIT_ERROR_OS, "failed to set proxy"); + git__free(proxy_wide); + goto on_error; + } + + git__free(proxy_wide); + + if ((error = apply_credentials(s->request, &t->proxy.url, WINHTTP_AUTH_TARGET_PROXY, t->proxy.cred, t->proxy.auth_mechanisms)) < 0) + goto on_error; + } + + /* Disable WinHTTP redirects so we can handle them manually. Why, you ask? + * http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/b2ff8879-ab9f-4218-8f09-16d25dff87ae + */ + if (!WinHttpSetOption(s->request, + WINHTTP_OPTION_DISABLE_FEATURE, + &disable_redirects, + sizeof(disable_redirects))) { + git_error_set(GIT_ERROR_OS, "failed to disable redirects"); + error = -1; + goto on_error; + } + + /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP + * adds itself. This option may not be supported by the underlying + * platform, so we do not error-check it */ + WinHttpSetOption(s->request, + WINHTTP_OPTION_PEERDIST_EXTENSION_STATE, + &peerdist, + sizeof(peerdist)); + + /* Send Pragma: no-cache header */ + if (!WinHttpAddRequestHeaders(s->request, pragma_nocache, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { + git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); + goto on_error; + } + + if (post_verb == s->verb) { + /* Send Content-Type and Accept headers -- only necessary on a POST */ + git_str_clear(&buf); + if (git_str_printf(&buf, + "Content-Type: application/x-git-%s-request", + s->service) < 0) + goto on_error; + + if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert content-type to wide characters"); + goto on_error; + } + + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); + goto on_error; + } + + git_str_clear(&buf); + if (git_str_printf(&buf, + "Accept: application/x-git-%s-result", + s->service) < 0) + goto on_error; + + if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert accept header to wide characters"); + goto on_error; + } + + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); + goto on_error; + } + } + + for (i = 0; i < t->owner->connect_opts.custom_headers.count; i++) { + if (t->owner->connect_opts.custom_headers.strings[i]) { + git_str_clear(&buf); + git_str_puts(&buf, t->owner->connect_opts.custom_headers.strings[i]); + if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert custom header to wide characters"); + goto on_error; + } + + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); + goto on_error; + } + } + } + + if ((error = apply_credentials(s->request, &t->server.url, WINHTTP_AUTH_TARGET_SERVER, t->server.cred, t->server.auth_mechanisms)) < 0) + goto on_error; + + /* We've done everything up to calling WinHttpSendRequest. */ + + error = 0; + +on_error: + if (error < 0) + winhttp_stream_close(s); + + git__free(proxy_url); + git_str_dispose(&buf); + return error; +} + +static int parse_unauthorized_response( + int *allowed_types, + int *allowed_mechanisms, + HINTERNET request) +{ + DWORD supported, first, target; + + *allowed_types = 0; + *allowed_mechanisms = 0; + + /* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes(). + * We can assume this was already done, since we know we are unauthorized. + */ + if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) { + git_error_set(GIT_ERROR_OS, "failed to parse supported auth schemes"); + return GIT_EAUTH; + } + + if (WINHTTP_AUTH_SCHEME_NTLM & supported) { + *allowed_types |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; + *allowed_types |= GIT_CREDENTIAL_DEFAULT; + *allowed_mechanisms |= GIT_WINHTTP_AUTH_NTLM; + } + + if (WINHTTP_AUTH_SCHEME_NEGOTIATE & supported) { + *allowed_types |= GIT_CREDENTIAL_DEFAULT; + *allowed_mechanisms |= GIT_WINHTTP_AUTH_NEGOTIATE; + } + + if (WINHTTP_AUTH_SCHEME_BASIC & supported) { + *allowed_types |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; + *allowed_mechanisms |= GIT_WINHTTP_AUTH_BASIC; + } + + if (WINHTTP_AUTH_SCHEME_DIGEST & supported) { + *allowed_types |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; + *allowed_mechanisms |= GIT_WINHTTP_AUTH_DIGEST; + } + + return 0; +} + +static int write_chunk(HINTERNET request, const char *buffer, size_t len) +{ + DWORD bytes_written; + git_str buf = GIT_STR_INIT; + + /* Chunk header */ + git_str_printf(&buf, "%"PRIXZ"\r\n", len); + + if (git_str_oom(&buf)) + return -1; + + if (!WinHttpWriteData(request, + git_str_cstr(&buf), (DWORD)git_str_len(&buf), + &bytes_written)) { + git_str_dispose(&buf); + git_error_set(GIT_ERROR_OS, "failed to write chunk header"); + return -1; + } + + git_str_dispose(&buf); + + /* Chunk body */ + if (!WinHttpWriteData(request, + buffer, (DWORD)len, + &bytes_written)) { + git_error_set(GIT_ERROR_OS, "failed to write chunk"); + return -1; + } + + /* Chunk footer */ + if (!WinHttpWriteData(request, + "\r\n", 2, + &bytes_written)) { + git_error_set(GIT_ERROR_OS, "failed to write chunk footer"); + return -1; + } + + return 0; +} + +static int winhttp_close_connection(winhttp_subtransport *t) +{ + int ret = 0; + + if (t->connection) { + if (!WinHttpCloseHandle(t->connection)) { + git_error_set(GIT_ERROR_OS, "unable to close connection"); + ret = -1; + } + + t->connection = NULL; + } + + if (t->session) { + if (!WinHttpCloseHandle(t->session)) { + git_error_set(GIT_ERROR_OS, "unable to close session"); + ret = -1; + } + + t->session = NULL; + } + + return ret; +} + +static void CALLBACK winhttp_status( + HINTERNET connection, + DWORD_PTR ctx, + DWORD code, + LPVOID info, + DWORD info_len) +{ + DWORD status; + + GIT_UNUSED(connection); + GIT_UNUSED(info_len); + + switch (code) { + case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE: + status = *((DWORD *)info); + + if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID)) + git_error_set(GIT_ERROR_HTTP, "SSL certificate issued for different common name"); + else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID)) + git_error_set(GIT_ERROR_HTTP, "SSL certificate has expired"); + else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA)) + git_error_set(GIT_ERROR_HTTP, "SSL certificate signed by unknown CA"); + else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT)) + git_error_set(GIT_ERROR_HTTP, "SSL certificate is invalid"); + else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED)) + git_error_set(GIT_ERROR_HTTP, "certificate revocation check failed"); + else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED)) + git_error_set(GIT_ERROR_HTTP, "SSL certificate was revoked"); + else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR)) + git_error_set(GIT_ERROR_HTTP, "security libraries could not be loaded"); + else + git_error_set(GIT_ERROR_HTTP, "unknown security error %lu", status); + + break; + + case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: + ((winhttp_stream *) ctx)->status_sending_request_reached = 1; + + break; + } +} + +static int winhttp_connect( + winhttp_subtransport *t) +{ + wchar_t *wide_host = NULL; + int32_t port; + wchar_t *wide_ua = NULL; + git_str ipv6 = GIT_STR_INIT, ua = GIT_STR_INIT; + const char *host; + int error = -1; + int default_timeout = TIMEOUT_INFINITE; + int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; + DWORD protocols = + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3; + + t->session = NULL; + t->connection = NULL; + + /* Prepare port */ + if (git__strntol32(&port, t->server.url.port, + strlen(t->server.url.port), NULL, 10) < 0) + goto on_error; + + /* IPv6? Add braces around the host. */ + if (git_net_url_is_ipv6(&t->server.url)) { + if (git_str_printf(&ipv6, "[%s]", t->server.url.host) < 0) + goto on_error; + + host = ipv6.ptr; + } else { + host = t->server.url.host; + } + + /* Prepare host */ + if (git__utf8_to_16_alloc(&wide_host, host) < 0) { + git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters"); + goto on_error; + } + + + if (git_http__user_agent(&ua) < 0) + goto on_error; + + if (git__utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) { + git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters"); + goto on_error; + } + + /* Establish session */ + t->session = WinHttpOpen( + wide_ua, + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + + if (!t->session) { + git_error_set(GIT_ERROR_OS, "failed to init WinHTTP"); + goto on_error; + } + + /* + * Do a best-effort attempt to enable TLS 1.3 and 1.2 but allow this to + * fail; if TLS 1.2 or 1.3 support is not available for some reason, + * ignore the failure (it will keep the default protocols). + */ + if (WinHttpSetOption(t->session, + WINHTTP_OPTION_SECURE_PROTOCOLS, + &protocols, + sizeof(protocols)) == FALSE) { + protocols &= ~WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3; + WinHttpSetOption(t->session, + WINHTTP_OPTION_SECURE_PROTOCOLS, + &protocols, + sizeof(protocols)); + } + + if (!WinHttpSetTimeouts(t->session, default_timeout, default_connect_timeout, default_timeout, default_timeout)) { + git_error_set(GIT_ERROR_OS, "failed to set timeouts for WinHTTP"); + goto on_error; + } + + + /* Establish connection */ + t->connection = WinHttpConnect( + t->session, + wide_host, + (INTERNET_PORT) port, + 0); + + if (!t->connection) { + git_error_set(GIT_ERROR_OS, "failed to connect to host"); + goto on_error; + } + + if (WinHttpSetStatusCallback( + t->connection, + winhttp_status, + WINHTTP_CALLBACK_FLAG_SECURE_FAILURE | WINHTTP_CALLBACK_FLAG_SEND_REQUEST, + 0 + ) == WINHTTP_INVALID_STATUS_CALLBACK) { + git_error_set(GIT_ERROR_OS, "failed to set status callback"); + goto on_error; + } + + error = 0; + +on_error: + if (error < 0) + winhttp_close_connection(t); + + git_str_dispose(&ua); + git_str_dispose(&ipv6); + git__free(wide_host); + git__free(wide_ua); + + return error; +} + +static int do_send_request(winhttp_stream *s, size_t len, bool chunked) +{ + int attempts; + bool success; + + if (len > DWORD_MAX) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return -1; + } + + for (attempts = 0; attempts < 5; attempts++) { + if (chunked) { + success = WinHttpSendRequest(s->request, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, (DWORD_PTR)s); + } else { + success = WinHttpSendRequest(s->request, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + (DWORD)len, (DWORD_PTR)s); + } + + if (success || GetLastError() != (DWORD)SEC_E_BUFFER_TOO_SMALL) + break; + } + + return success ? 0 : -1; +} + +static int send_request(winhttp_stream *s, size_t len, bool chunked) +{ + int request_failed = 1, error, attempts = 0; + DWORD ignore_flags, send_request_error; + + git_error_clear(); + + while (request_failed && attempts++ < 3) { + int cert_valid = 1; + int client_cert_requested = 0; + request_failed = 0; + if ((error = do_send_request(s, len, chunked)) < 0) { + send_request_error = GetLastError(); + request_failed = 1; + switch (send_request_error) { + case ERROR_WINHTTP_SECURE_FAILURE: + cert_valid = 0; + break; + case ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED: + client_cert_requested = 1; + break; + default: + git_error_set(GIT_ERROR_OS, "failed to send request"); + return -1; + } + } + + /* + * Only check the certificate if we were able to reach the sending request phase, or + * received a secure failure error. Otherwise, the server certificate won't be available + * since the request wasn't able to complete (e.g. proxy auth required) + */ + if (!cert_valid || + (!request_failed && s->status_sending_request_reached)) { + git_error_clear(); + if ((error = certificate_check(s, cert_valid)) < 0) { + if (!git_error_last()) + git_error_set(GIT_ERROR_OS, "user cancelled certificate check"); + + return error; + } + } + + /* if neither the request nor the certificate check returned errors, we're done */ + if (!request_failed) + return 0; + + if (!cert_valid) { + ignore_flags = no_check_cert_flags; + if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, &ignore_flags, sizeof(ignore_flags))) { + git_error_set(GIT_ERROR_OS, "failed to set security options"); + return -1; + } + } + + if (client_cert_requested) { + /* + * Client certificates are not supported, explicitly tell the server that + * (it's possible a client certificate was requested but is not required) + */ + if (!WinHttpSetOption(s->request, WINHTTP_OPTION_CLIENT_CERT_CONTEXT, WINHTTP_NO_CLIENT_CERT_CONTEXT, 0)) { + git_error_set(GIT_ERROR_OS, "failed to set client cert context"); + return -1; + } + } + } + + return error; +} + +static int acquire_credentials( + HINTERNET request, + winhttp_server *server, + const char *url_str, + git_credential_acquire_cb cred_cb, + void *cred_cb_payload) +{ + int allowed_types; + int error = 1; + + if (parse_unauthorized_response(&allowed_types, &server->auth_mechanisms, request) < 0) + return -1; + + if (allowed_types) { + git_credential_free(server->cred); + server->cred = NULL; + + /* Start with URL-specified credentials, if there were any. */ + if (!server->url_cred_presented && server->url.username && server->url.password) { + error = acquire_url_cred(&server->cred, allowed_types, server->url.username, server->url.password); + server->url_cred_presented = 1; + + if (error < 0) + return error; + } + + /* Next use the user-defined callback, if there is one. */ + if (error > 0 && cred_cb) { + error = cred_cb(&server->cred, url_str, server->url.username, allowed_types, cred_cb_payload); + + /* Treat GIT_PASSTHROUGH as though git_credential_acquire_cb isn't set */ + if (error == GIT_PASSTHROUGH) + error = 1; + else if (error < 0) + return error; + } + + /* Finally, invoke the fallback default credential lookup. */ + if (error > 0) { + error = acquire_fallback_cred(&server->cred, url_str, allowed_types); + + if (error < 0) + return error; + } + } + + /* + * No error occurred but we could not find appropriate credentials. + * This behaves like a pass-through. + */ + return error; +} + +static int winhttp_stream_read( + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read) +{ + winhttp_stream *s = (winhttp_stream *)stream; + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + DWORD dw_bytes_read; + char replay_count = 0; + int error; + +replay: + /* Enforce a reasonable cap on the number of replays */ + if (replay_count++ >= GIT_HTTP_REPLAY_MAX) { + git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); + return GIT_ERROR; /* not GIT_EAUTH because the exact cause is not clear */ + } + + /* Connect if necessary */ + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + if (!s->received_response) { + DWORD status_code, status_code_length, content_type_length, bytes_written; + char expected_content_type_8[MAX_CONTENT_TYPE_LEN]; + wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN]; + + if (!s->sent_request) { + + if ((error = send_request(s, s->post_body_len, false)) < 0) + return error; + + s->sent_request = 1; + } + + if (s->chunked) { + GIT_ASSERT(s->verb == post_verb); + + /* Flush, if necessary */ + if (s->chunk_buffer_len > 0 && + write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) + return -1; + + s->chunk_buffer_len = 0; + + /* Write the final chunk. */ + if (!WinHttpWriteData(s->request, + "0\r\n\r\n", 5, + &bytes_written)) { + git_error_set(GIT_ERROR_OS, "failed to write final chunk"); + return -1; + } + } + else if (s->post_body) { + char *buffer; + DWORD len = s->post_body_len, bytes_read; + + if (INVALID_SET_FILE_POINTER == SetFilePointer(s->post_body, + 0, 0, FILE_BEGIN) && + NO_ERROR != GetLastError()) { + git_error_set(GIT_ERROR_OS, "failed to reset file pointer"); + return -1; + } + + buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE); + GIT_ERROR_CHECK_ALLOC(buffer); + + while (len > 0) { + DWORD bytes_written; + + if (!ReadFile(s->post_body, buffer, + min(CACHED_POST_BODY_BUF_SIZE, len), + &bytes_read, NULL) || + !bytes_read) { + git__free(buffer); + git_error_set(GIT_ERROR_OS, "failed to read from temp file"); + return -1; + } + + if (!WinHttpWriteData(s->request, buffer, + bytes_read, &bytes_written)) { + git__free(buffer); + git_error_set(GIT_ERROR_OS, "failed to write data"); + return -1; + } + + len -= bytes_read; + GIT_ASSERT(bytes_read == bytes_written); + } + + git__free(buffer); + + /* Eagerly close the temp file */ + CloseHandle(s->post_body); + s->post_body = NULL; + } + + if (!WinHttpReceiveResponse(s->request, 0)) { + git_error_set(GIT_ERROR_OS, "failed to receive response"); + return -1; + } + + /* Verify that we got a 200 back */ + status_code_length = sizeof(status_code); + + if (!WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &status_code, &status_code_length, + WINHTTP_NO_HEADER_INDEX)) { + git_error_set(GIT_ERROR_OS, "failed to retrieve status code"); + return -1; + } + + /* The implementation of WinHTTP prior to Windows 7 will not + * redirect to an identical URI. Some Git hosters use self-redirects + * as part of their DoS mitigation strategy. Check first to see if we + * have a redirect status code, and that we haven't already streamed + * a post body. (We can't replay a streamed POST.) */ + if (!s->chunked && + (HTTP_STATUS_MOVED == status_code || + HTTP_STATUS_REDIRECT == status_code || + (HTTP_STATUS_REDIRECT_METHOD == status_code && + get_verb == s->verb) || + HTTP_STATUS_REDIRECT_KEEP_VERB == status_code || + HTTP_STATUS_PERMANENT_REDIRECT == status_code)) { + + /* Check for Windows 7. This workaround is only necessary on + * Windows Vista and earlier. Windows 7 is version 6.1. */ + wchar_t *location; + DWORD location_length; + char *location8; + + /* OK, fetch the Location header from the redirect. */ + if (WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_LOCATION, + WINHTTP_HEADER_NAME_BY_INDEX, + WINHTTP_NO_OUTPUT_BUFFER, + &location_length, + WINHTTP_NO_HEADER_INDEX) || + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + git_error_set(GIT_ERROR_OS, "failed to read Location header"); + return -1; + } + + location = git__malloc(location_length); + GIT_ERROR_CHECK_ALLOC(location); + + if (!WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_LOCATION, + WINHTTP_HEADER_NAME_BY_INDEX, + location, + &location_length, + WINHTTP_NO_HEADER_INDEX)) { + git_error_set(GIT_ERROR_OS, "failed to read Location header"); + git__free(location); + return -1; + } + + /* Convert the Location header to UTF-8 */ + if (git__utf16_to_8_alloc(&location8, location) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert Location header to UTF-8"); + git__free(location); + return -1; + } + + git__free(location); + + /* Replay the request */ + winhttp_stream_close(s); + + if (!git__prefixcmp_icase(location8, prefix_https)) { + bool follow = (t->owner->connect_opts.follow_redirects != GIT_REMOTE_REDIRECT_NONE); + + /* Upgrade to secure connection; disconnect and start over */ + if (git_net_url_apply_redirect(&t->server.url, location8, follow, s->service_url) < 0) { + git__free(location8); + return -1; + } + + winhttp_close_connection(t); + + if (winhttp_connect(t) < 0) + return -1; + } + + git__free(location8); + goto replay; + } + + /* Handle authentication failures */ + if (status_code == HTTP_STATUS_DENIED) { + int error = acquire_credentials(s->request, + &t->server, + t->owner->url, + t->owner->connect_opts.callbacks.credentials, + t->owner->connect_opts.callbacks.payload); + + if (error < 0) { + return error; + } else if (!error) { + GIT_ASSERT(t->server.cred); + winhttp_stream_close(s); + goto replay; + } + } else if (status_code == HTTP_STATUS_PROXY_AUTH_REQ) { + int error = acquire_credentials(s->request, + &t->proxy, + t->owner->connect_opts.proxy_opts.url, + t->owner->connect_opts.proxy_opts.credentials, + t->owner->connect_opts.proxy_opts.payload); + + if (error < 0) { + return error; + } else if (!error) { + GIT_ASSERT(t->proxy.cred); + winhttp_stream_close(s); + goto replay; + } + } + + if (HTTP_STATUS_OK != status_code) { + git_error_set(GIT_ERROR_HTTP, "request failed with status code: %lu", status_code); + return -1; + } + + /* Verify that we got the correct content-type back */ + if (post_verb == s->verb) + p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service); + else + p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service); + + if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) { + git_error_set(GIT_ERROR_OS, "failed to convert expected content-type to wide characters"); + return -1; + } + + content_type_length = sizeof(content_type); + + if (!WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_CONTENT_TYPE, + WINHTTP_HEADER_NAME_BY_INDEX, + &content_type, &content_type_length, + WINHTTP_NO_HEADER_INDEX)) { + git_error_set(GIT_ERROR_OS, "failed to retrieve response content-type"); + return -1; + } + + if (wcscmp(expected_content_type, content_type)) { + git_error_set(GIT_ERROR_HTTP, "received unexpected content-type"); + return -1; + } + + s->received_response = 1; + } + + if (!WinHttpReadData(s->request, + (LPVOID)buffer, + (DWORD)buf_size, + &dw_bytes_read)) + { + git_error_set(GIT_ERROR_OS, "failed to read data"); + return -1; + } + + *bytes_read = dw_bytes_read; + + return 0; +} + +static int winhttp_stream_write_single( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + winhttp_stream *s = (winhttp_stream *)stream; + DWORD bytes_written; + int error; + + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + /* This implementation of write permits only a single call. */ + if (s->sent_request) { + git_error_set(GIT_ERROR_HTTP, "subtransport configured for only one write"); + return -1; + } + + if ((error = send_request(s, len, false)) < 0) + return error; + + s->sent_request = 1; + + if (!WinHttpWriteData(s->request, + (LPCVOID)buffer, + (DWORD)len, + &bytes_written)) { + git_error_set(GIT_ERROR_OS, "failed to write data"); + return -1; + } + + GIT_ASSERT((DWORD)len == bytes_written); + + return 0; +} + +static int put_uuid_string(LPWSTR buffer, size_t buffer_len_cch) +{ + UUID uuid; + RPC_STATUS status = UuidCreate(&uuid); + int result; + + if (RPC_S_OK != status && + RPC_S_UUID_LOCAL_ONLY != status && + RPC_S_UUID_NO_ADDRESS != status) { + git_error_set(GIT_ERROR_HTTP, "unable to generate name for temp file"); + return -1; + } + + if (buffer_len_cch < UUID_LENGTH_CCH + 1) { + git_error_set(GIT_ERROR_HTTP, "buffer too small for name of temp file"); + return -1; + } + +#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API) + result = swprintf_s(buffer, buffer_len_cch, +#else + result = wsprintfW(buffer, +#endif + L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x", + uuid.Data1, uuid.Data2, uuid.Data3, + uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3], + uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]); + + if (result < UUID_LENGTH_CCH) { + git_error_set(GIT_ERROR_OS, "unable to generate name for temp file"); + return -1; + } + + return 0; +} + +static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch) +{ + size_t len; + + if (!GetTempPathW(buffer_len_cch, buffer)) { + git_error_set(GIT_ERROR_OS, "failed to get temp path"); + return -1; + } + + len = wcslen(buffer); + + if (buffer[len - 1] != '\\' && len < buffer_len_cch) + buffer[len++] = '\\'; + + if (put_uuid_string(&buffer[len], (size_t)buffer_len_cch - len) < 0) + return -1; + + return 0; +} + +static int winhttp_stream_write_buffered( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + winhttp_stream *s = (winhttp_stream *)stream; + DWORD bytes_written; + + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + /* Buffer the payload, using a temporary file so we delegate + * memory management of the data to the operating system. */ + if (!s->post_body) { + wchar_t temp_path[MAX_PATH + 1]; + + if (get_temp_file(temp_path, MAX_PATH + 1) < 0) + return -1; + + s->post_body = CreateFileW(temp_path, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_DELETE, NULL, + CREATE_NEW, + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + + if (INVALID_HANDLE_VALUE == s->post_body) { + s->post_body = NULL; + git_error_set(GIT_ERROR_OS, "failed to create temporary file"); + return -1; + } + } + + if (!WriteFile(s->post_body, buffer, (DWORD)len, &bytes_written, NULL)) { + git_error_set(GIT_ERROR_OS, "failed to write to temporary file"); + return -1; + } + + GIT_ASSERT((DWORD)len == bytes_written); + + s->post_body_len += bytes_written; + + return 0; +} + +static int winhttp_stream_write_chunked( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + winhttp_stream *s = (winhttp_stream *)stream; + int error; + + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + if (!s->sent_request) { + /* Send Transfer-Encoding: chunked header */ + if (!WinHttpAddRequestHeaders(s->request, + transfer_encoding, (ULONG) -1L, + WINHTTP_ADDREQ_FLAG_ADD)) { + git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); + return -1; + } + + if ((error = send_request(s, 0, true)) < 0) + return error; + + s->sent_request = 1; + } + + if (len > CACHED_POST_BODY_BUF_SIZE) { + /* Flush, if necessary */ + if (s->chunk_buffer_len > 0) { + if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) + return -1; + + s->chunk_buffer_len = 0; + } + + /* Write chunk directly */ + if (write_chunk(s->request, buffer, len) < 0) + return -1; + } + else { + /* Append as much to the buffer as we can */ + int count = (int)min(CACHED_POST_BODY_BUF_SIZE - s->chunk_buffer_len, len); + + if (!s->chunk_buffer) { + s->chunk_buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE); + GIT_ERROR_CHECK_ALLOC(s->chunk_buffer); + } + + memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count); + s->chunk_buffer_len += count; + buffer += count; + len -= count; + + /* Is the buffer full? If so, then flush */ + if (CACHED_POST_BODY_BUF_SIZE == s->chunk_buffer_len) { + if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) + return -1; + + s->chunk_buffer_len = 0; + + /* Is there any remaining data from the source? */ + if (len > 0) { + memcpy(s->chunk_buffer, buffer, len); + s->chunk_buffer_len = (unsigned int)len; + } + } + } + + return 0; +} + +static void winhttp_stream_free(git_smart_subtransport_stream *stream) +{ + winhttp_stream *s = (winhttp_stream *)stream; + + winhttp_stream_close(s); + git__free(s); +} + +static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream) +{ + winhttp_stream *s; + + if (!stream) + return -1; + + s = git__calloc(1, sizeof(winhttp_stream)); + GIT_ERROR_CHECK_ALLOC(s); + + s->parent.subtransport = &t->parent; + s->parent.read = winhttp_stream_read; + s->parent.write = winhttp_stream_write_single; + s->parent.free = winhttp_stream_free; + + *stream = s; + + return 0; +} + +static int winhttp_uploadpack_ls( + winhttp_subtransport *t, + winhttp_stream *s) +{ + GIT_UNUSED(t); + + s->service = upload_pack_service; + s->service_url = upload_pack_ls_service_url; + s->verb = get_verb; + + return 0; +} + +static int winhttp_uploadpack( + winhttp_subtransport *t, + winhttp_stream *s) +{ + GIT_UNUSED(t); + + s->service = upload_pack_service; + s->service_url = upload_pack_service_url; + s->verb = post_verb; + + return 0; +} + +static int winhttp_receivepack_ls( + winhttp_subtransport *t, + winhttp_stream *s) +{ + GIT_UNUSED(t); + + s->service = receive_pack_service; + s->service_url = receive_pack_ls_service_url; + s->verb = get_verb; + + return 0; +} + +static int winhttp_receivepack( + winhttp_subtransport *t, + winhttp_stream *s) +{ + GIT_UNUSED(t); + + /* WinHTTP only supports Transfer-Encoding: chunked + * on Windows Vista (NT 6.0) and higher. */ + s->chunked = git_has_win32_version(6, 0, 0); + + if (s->chunked) + s->parent.write = winhttp_stream_write_chunked; + else + s->parent.write = winhttp_stream_write_buffered; + + s->service = receive_pack_service; + s->service_url = receive_pack_service_url; + s->verb = post_verb; + + return 0; +} + +static int winhttp_action( + git_smart_subtransport_stream **stream, + git_smart_subtransport *subtransport, + const char *url, + git_smart_service_t action) +{ + winhttp_subtransport *t = (winhttp_subtransport *)subtransport; + winhttp_stream *s; + int ret = -1; + + if (!t->connection) + if ((ret = git_net_url_parse(&t->server.url, url)) < 0 || + (ret = winhttp_connect(t)) < 0) + return ret; + + if (winhttp_stream_alloc(t, &s) < 0) + return -1; + + if (!stream) + return -1; + + switch (action) + { + case GIT_SERVICE_UPLOADPACK_LS: + ret = winhttp_uploadpack_ls(t, s); + break; + + case GIT_SERVICE_UPLOADPACK: + ret = winhttp_uploadpack(t, s); + break; + + case GIT_SERVICE_RECEIVEPACK_LS: + ret = winhttp_receivepack_ls(t, s); + break; + + case GIT_SERVICE_RECEIVEPACK: + ret = winhttp_receivepack(t, s); + break; + + default: + GIT_ASSERT(0); + } + + if (!ret) + *stream = &s->parent; + + return ret; +} + +static int winhttp_close(git_smart_subtransport *subtransport) +{ + winhttp_subtransport *t = (winhttp_subtransport *)subtransport; + + git_net_url_dispose(&t->server.url); + git_net_url_dispose(&t->proxy.url); + + if (t->server.cred) { + t->server.cred->free(t->server.cred); + t->server.cred = NULL; + } + + if (t->proxy.cred) { + t->proxy.cred->free(t->proxy.cred); + t->proxy.cred = NULL; + } + + return winhttp_close_connection(t); +} + +static void winhttp_free(git_smart_subtransport *subtransport) +{ + winhttp_subtransport *t = (winhttp_subtransport *)subtransport; + + winhttp_close(subtransport); + + git__free(t); +} + +int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param) +{ + winhttp_subtransport *t; + + GIT_UNUSED(param); + + if (!out) + return -1; + + t = git__calloc(1, sizeof(winhttp_subtransport)); + GIT_ERROR_CHECK_ALLOC(t); + + t->owner = (transport_smart *)owner; + t->parent.action = winhttp_action; + t->parent.close = winhttp_close; + t->parent.free = winhttp_free; + + *out = (git_smart_subtransport *) t; + return 0; +} + +#endif /* GIT_WINHTTP */ diff --git a/src/libgit2/tree-cache.c b/src/libgit2/tree-cache.c new file mode 100644 index 000000000..0977c92f3 --- /dev/null +++ b/src/libgit2/tree-cache.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "tree-cache.h" + +#include "pool.h" +#include "tree.h" + +static git_tree_cache *find_child( + const git_tree_cache *tree, const char *path, const char *end) +{ + size_t i, dirlen = end ? (size_t)(end - path) : strlen(path); + + for (i = 0; i < tree->children_count; ++i) { + git_tree_cache *child = tree->children[i]; + + if (child->namelen == dirlen && !memcmp(path, child->name, dirlen)) + return child; + } + + return NULL; +} + +void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path) +{ + const char *ptr = path, *end; + + if (tree == NULL) + return; + + tree->entry_count = -1; + + while (ptr != NULL) { + end = strchr(ptr, '/'); + + if (end == NULL) /* End of path */ + break; + + tree = find_child(tree, ptr, end); + if (tree == NULL) /* We don't have that tree */ + return; + + tree->entry_count = -1; + ptr = end + 1; + } +} + +const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path) +{ + const char *ptr = path, *end; + + if (tree == NULL) { + return NULL; + } + + while (1) { + end = strchr(ptr, '/'); + + tree = find_child(tree, ptr, end); + if (tree == NULL) /* Can't find it */ + return NULL; + + if (end == NULL || *end + 1 == '\0') + return tree; + + ptr = end + 1; + } +} + +static int read_tree_internal(git_tree_cache **out, + const char **buffer_in, const char *buffer_end, + git_pool *pool) +{ + git_tree_cache *tree = NULL; + const char *name_start, *buffer; + int count; + + buffer = name_start = *buffer_in; + + if ((buffer = memchr(buffer, '\0', buffer_end - buffer)) == NULL) + goto corrupted; + + if (++buffer >= buffer_end) + goto corrupted; + + if (git_tree_cache_new(&tree, name_start, pool) < 0) + return -1; + + /* Blank-terminated ASCII decimal number of entries in this tree */ + if (git__strntol32(&count, buffer, buffer_end - buffer, &buffer, 10) < 0) + goto corrupted; + + tree->entry_count = count; + + if (*buffer != ' ' || ++buffer >= buffer_end) + goto corrupted; + + /* Number of children of the tree, newline-terminated */ + if (git__strntol32(&count, buffer, buffer_end - buffer, &buffer, 10) < 0 || count < 0) + goto corrupted; + + tree->children_count = count; + + if (*buffer != '\n' || ++buffer > buffer_end) + goto corrupted; + + /* The SHA1 is only there if it's not invalidated */ + if (tree->entry_count >= 0) { + /* 160-bit SHA-1 for this tree and it's children */ + if (buffer + GIT_OID_RAWSZ > buffer_end) + goto corrupted; + + git_oid_fromraw(&tree->oid, (const unsigned char *)buffer); + buffer += GIT_OID_RAWSZ; + } + + /* Parse children: */ + if (tree->children_count > 0) { + size_t i, bufsize; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&bufsize, tree->children_count, sizeof(git_tree_cache*)); + + tree->children = git_pool_malloc(pool, bufsize); + GIT_ERROR_CHECK_ALLOC(tree->children); + + memset(tree->children, 0x0, bufsize); + + for (i = 0; i < tree->children_count; ++i) { + if (read_tree_internal(&tree->children[i], &buffer, buffer_end, pool) < 0) + goto corrupted; + } + } + + *buffer_in = buffer; + *out = tree; + return 0; + + corrupted: + git_error_set(GIT_ERROR_INDEX, "corrupted TREE extension in index"); + return -1; +} + +int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size, git_pool *pool) +{ + const char *buffer_end = buffer + buffer_size; + + if (read_tree_internal(tree, &buffer, buffer_end, pool) < 0) + return -1; + + if (buffer < buffer_end) { + git_error_set(GIT_ERROR_INDEX, "corrupted TREE extension in index (unexpected trailing data)"); + return -1; + } + + return 0; +} + +static int read_tree_recursive(git_tree_cache *cache, const git_tree *tree, git_pool *pool) +{ + git_repository *repo; + size_t i, j, nentries, ntrees, alloc_size; + int error; + + repo = git_tree_owner(tree); + + git_oid_cpy(&cache->oid, git_tree_id(tree)); + nentries = git_tree_entrycount(tree); + + /* + * We make sure we know how many trees we need to allocate for + * so we don't have to realloc and change the pointers for the + * parents. + */ + ntrees = 0; + for (i = 0; i < nentries; i++) { + const git_tree_entry *entry; + + entry = git_tree_entry_byindex(tree, i); + if (git_tree_entry_filemode(entry) == GIT_FILEMODE_TREE) + ntrees++; + } + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_size, ntrees, sizeof(git_tree_cache *)); + + cache->children_count = ntrees; + cache->children = git_pool_mallocz(pool, alloc_size); + GIT_ERROR_CHECK_ALLOC(cache->children); + + j = 0; + for (i = 0; i < nentries; i++) { + const git_tree_entry *entry; + git_tree *subtree; + + entry = git_tree_entry_byindex(tree, i); + if (git_tree_entry_filemode(entry) != GIT_FILEMODE_TREE) { + cache->entry_count++; + continue; + } + + if ((error = git_tree_cache_new(&cache->children[j], git_tree_entry_name(entry), pool)) < 0) + return error; + + if ((error = git_tree_lookup(&subtree, repo, git_tree_entry_id(entry))) < 0) + return error; + + error = read_tree_recursive(cache->children[j], subtree, pool); + git_tree_free(subtree); + cache->entry_count += cache->children[j]->entry_count; + j++; + + if (error < 0) + return error; + } + + return 0; +} + +int git_tree_cache_read_tree(git_tree_cache **out, const git_tree *tree, git_pool *pool) +{ + int error; + git_tree_cache *cache; + + if ((error = git_tree_cache_new(&cache, "", pool)) < 0) + return error; + + if ((error = read_tree_recursive(cache, tree, pool)) < 0) + return error; + + *out = cache; + return 0; +} + +int git_tree_cache_new(git_tree_cache **out, const char *name, git_pool *pool) +{ + size_t name_len, alloc_size; + git_tree_cache *tree; + + name_len = strlen(name); + + GIT_ERROR_CHECK_ALLOC_ADD3(&alloc_size, sizeof(git_tree_cache), name_len, 1); + + tree = git_pool_malloc(pool, alloc_size); + GIT_ERROR_CHECK_ALLOC(tree); + + memset(tree, 0x0, sizeof(git_tree_cache)); + /* NUL-terminated tree name */ + tree->namelen = name_len; + memcpy(tree->name, name, name_len); + tree->name[name_len] = '\0'; + + *out = tree; + return 0; +} + +static void write_tree(git_str *out, git_tree_cache *tree) +{ + size_t i; + + git_str_printf(out, "%s%c%"PRIdZ" %"PRIuZ"\n", tree->name, 0, tree->entry_count, tree->children_count); + + if (tree->entry_count != -1) + git_str_put(out, (const char *) &tree->oid, GIT_OID_RAWSZ); + + for (i = 0; i < tree->children_count; i++) + write_tree(out, tree->children[i]); +} + +int git_tree_cache_write(git_str *out, git_tree_cache *tree) +{ + write_tree(out, tree); + + return git_str_oom(out) ? -1 : 0; +} diff --git a/src/libgit2/tree-cache.h b/src/libgit2/tree-cache.h new file mode 100644 index 000000000..a27e30466 --- /dev/null +++ b/src/libgit2/tree-cache.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_tree_cache_h__ +#define INCLUDE_tree_cache_h__ + +#include "common.h" + +#include "pool.h" +#include "str.h" +#include "git2/oid.h" + +typedef struct git_tree_cache { + struct git_tree_cache **children; + size_t children_count; + + ssize_t entry_count; + git_oid oid; + size_t namelen; + char name[GIT_FLEX_ARRAY]; +} git_tree_cache; + +int git_tree_cache_write(git_str *out, git_tree_cache *tree); +int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size, git_pool *pool); +void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path); +const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path); +int git_tree_cache_new(git_tree_cache **out, const char *name, git_pool *pool); +/** + * Read a tree as the root of the tree cache (like for `git read-tree`) + */ +int git_tree_cache_read_tree(git_tree_cache **out, const git_tree *tree, git_pool *pool); +void git_tree_cache_free(git_tree_cache *tree); + +#endif diff --git a/src/libgit2/tree.c b/src/libgit2/tree.c new file mode 100644 index 000000000..a1545dc2d --- /dev/null +++ b/src/libgit2/tree.c @@ -0,0 +1,1331 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "tree.h" + +#include "commit.h" +#include "git2/repository.h" +#include "git2/object.h" +#include "futils.h" +#include "tree-cache.h" +#include "index.h" +#include "path.h" + +#define DEFAULT_TREE_SIZE 16 +#define MAX_FILEMODE_BYTES 6 + +#define TREE_ENTRY_CHECK_NAMELEN(n) \ + if (n > UINT16_MAX) { git_error_set(GIT_ERROR_INVALID, "tree entry path too long"); } + +static bool valid_filemode(const int filemode) +{ + return (filemode == GIT_FILEMODE_TREE + || filemode == GIT_FILEMODE_BLOB + || filemode == GIT_FILEMODE_BLOB_EXECUTABLE + || filemode == GIT_FILEMODE_LINK + || filemode == GIT_FILEMODE_COMMIT); +} + +GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode) +{ + /* Tree bits set, but it's not a commit */ + if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_TREE) + return GIT_FILEMODE_TREE; + + /* If any of the x bits are set */ + if (GIT_PERMS_IS_EXEC(filemode)) + return GIT_FILEMODE_BLOB_EXECUTABLE; + + /* 16XXXX means commit */ + if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_COMMIT) + return GIT_FILEMODE_COMMIT; + + /* 12XXXX means symlink */ + if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_LINK) + return GIT_FILEMODE_LINK; + + /* Otherwise, return a blob */ + return GIT_FILEMODE_BLOB; +} + +static int valid_entry_name(git_repository *repo, const char *filename) +{ + return *filename != '\0' && + git_path_is_valid(repo, filename, 0, + GIT_FS_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT | GIT_FS_PATH_REJECT_SLASH); +} + +static int entry_sort_cmp(const void *a, const void *b) +{ + const git_tree_entry *e1 = (const git_tree_entry *)a; + const git_tree_entry *e2 = (const git_tree_entry *)b; + + return git_fs_path_cmp( + e1->filename, e1->filename_len, git_tree_entry__is_tree(e1), + e2->filename, e2->filename_len, git_tree_entry__is_tree(e2), + git__strncmp); +} + +int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2) +{ + return entry_sort_cmp(e1, e2); +} + +/** + * Allocate a new self-contained entry, with enough space after it to + * store the filename and the id. + */ +static git_tree_entry *alloc_entry(const char *filename, size_t filename_len, const git_oid *id) +{ + git_tree_entry *entry = NULL; + size_t tree_len; + + TREE_ENTRY_CHECK_NAMELEN(filename_len); + + if (GIT_ADD_SIZET_OVERFLOW(&tree_len, sizeof(git_tree_entry), filename_len) || + GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, 1) || + GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, GIT_OID_RAWSZ)) + return NULL; + + entry = git__calloc(1, tree_len); + if (!entry) + return NULL; + + { + char *filename_ptr; + void *id_ptr; + + filename_ptr = ((char *) entry) + sizeof(git_tree_entry); + memcpy(filename_ptr, filename, filename_len); + entry->filename = filename_ptr; + + id_ptr = filename_ptr + filename_len + 1; + git_oid_cpy(id_ptr, id); + entry->oid = id_ptr; + } + + entry->filename_len = (uint16_t)filename_len; + + return entry; +} + +struct tree_key_search { + const char *filename; + uint16_t filename_len; +}; + +static int homing_search_cmp(const void *key, const void *array_member) +{ + const struct tree_key_search *ksearch = key; + const git_tree_entry *entry = array_member; + + const uint16_t len1 = ksearch->filename_len; + const uint16_t len2 = entry->filename_len; + + return memcmp( + ksearch->filename, + entry->filename, + len1 < len2 ? len1 : len2 + ); +} + +/* + * Search for an entry in a given tree. + * + * Note that this search is performed in two steps because + * of the way tree entries are sorted internally in git: + * + * Entries in a tree are not sorted alphabetically; two entries + * with the same root prefix will have different positions + * depending on whether they are folders (subtrees) or normal files. + * + * Consequently, it is not possible to find an entry on the tree + * with a binary search if you don't know whether the filename + * you're looking for is a folder or a normal file. + * + * To work around this, we first perform a homing binary search + * on the tree, using the minimal length root prefix of our filename. + * Once the comparisons for this homing search start becoming + * ambiguous because of folder vs file sorting, we look linearly + * around the area for our target file. + */ +static int tree_key_search( + size_t *at_pos, + const git_tree *tree, + const char *filename, + size_t filename_len) +{ + struct tree_key_search ksearch; + const git_tree_entry *entry; + size_t homing, i; + + TREE_ENTRY_CHECK_NAMELEN(filename_len); + + ksearch.filename = filename; + ksearch.filename_len = (uint16_t)filename_len; + + /* Initial homing search; find an entry on the tree with + * the same prefix as the filename we're looking for */ + + if (git_array_search(&homing, + tree->entries, &homing_search_cmp, &ksearch) < 0) + return GIT_ENOTFOUND; /* just a signal error; not passed back to user */ + + /* We found a common prefix. Look forward as long as + * there are entries that share the common prefix */ + for (i = homing; i < tree->entries.size; ++i) { + entry = git_array_get(tree->entries, i); + + if (homing_search_cmp(&ksearch, entry) < 0) + break; + + if (entry->filename_len == filename_len && + memcmp(filename, entry->filename, filename_len) == 0) { + if (at_pos) + *at_pos = i; + + return 0; + } + } + + /* If we haven't found our filename yet, look backwards + * too as long as we have entries with the same prefix */ + if (homing > 0) { + i = homing - 1; + + do { + entry = git_array_get(tree->entries, i); + + if (homing_search_cmp(&ksearch, entry) > 0) + break; + + if (entry->filename_len == filename_len && + memcmp(filename, entry->filename, filename_len) == 0) { + if (at_pos) + *at_pos = i; + + return 0; + } + } while (i-- > 0); + } + + /* The filename doesn't exist at all */ + return GIT_ENOTFOUND; +} + +void git_tree_entry_free(git_tree_entry *entry) +{ + if (entry == NULL) + return; + + git__free(entry); +} + +int git_tree_entry_dup(git_tree_entry **dest, const git_tree_entry *source) +{ + git_tree_entry *cpy; + + GIT_ASSERT_ARG(source); + + cpy = alloc_entry(source->filename, source->filename_len, source->oid); + if (cpy == NULL) + return -1; + + cpy->attr = source->attr; + + *dest = cpy; + return 0; +} + +void git_tree__free(void *_tree) +{ + git_tree *tree = _tree; + + git_odb_object_free(tree->odb_obj); + git_array_clear(tree->entries); + git__free(tree); +} + +git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry) +{ + return normalize_filemode(entry->attr); +} + +git_filemode_t git_tree_entry_filemode_raw(const git_tree_entry *entry) +{ + return entry->attr; +} + +const char *git_tree_entry_name(const git_tree_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); + return entry->filename; +} + +const git_oid *git_tree_entry_id(const git_tree_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); + return entry->oid; +} + +git_object_t git_tree_entry_type(const git_tree_entry *entry) +{ + GIT_ASSERT_ARG_WITH_RETVAL(entry, GIT_OBJECT_INVALID); + + if (S_ISGITLINK(entry->attr)) + return GIT_OBJECT_COMMIT; + else if (S_ISDIR(entry->attr)) + return GIT_OBJECT_TREE; + else + return GIT_OBJECT_BLOB; +} + +int git_tree_entry_to_object( + git_object **object_out, + git_repository *repo, + const git_tree_entry *entry) +{ + GIT_ASSERT_ARG(entry); + GIT_ASSERT_ARG(object_out); + + return git_object_lookup(object_out, repo, entry->oid, GIT_OBJECT_ANY); +} + +static const git_tree_entry *entry_fromname( + const git_tree *tree, const char *name, size_t name_len) +{ + size_t idx; + + if (tree_key_search(&idx, tree, name, name_len) < 0) + return NULL; + + return git_array_get(tree->entries, idx); +} + +const git_tree_entry *git_tree_entry_byname( + const git_tree *tree, const char *filename) +{ + GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(filename, NULL); + + return entry_fromname(tree, filename, strlen(filename)); +} + +const git_tree_entry *git_tree_entry_byindex( + const git_tree *tree, size_t idx) +{ + GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL); + return git_array_get(tree->entries, idx); +} + +const git_tree_entry *git_tree_entry_byid( + const git_tree *tree, const git_oid *id) +{ + size_t i; + const git_tree_entry *e; + + GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL); + + git_array_foreach(tree->entries, i, e) { + if (memcmp(&e->oid->id, &id->id, sizeof(id->id)) == 0) + return e; + } + + return NULL; +} + +size_t git_tree_entrycount(const git_tree *tree) +{ + GIT_ASSERT_ARG_WITH_RETVAL(tree, 0); + return tree->entries.size; +} + +size_t git_treebuilder_entrycount(git_treebuilder *bld) +{ + GIT_ASSERT_ARG_WITH_RETVAL(bld, 0); + + return git_strmap_size(bld->map); +} + +GIT_INLINE(void) set_error(const char *str, const char *path) +{ + if (path) + git_error_set(GIT_ERROR_TREE, "%s - %s", str, path); + else + git_error_set(GIT_ERROR_TREE, "%s", str); +} + +static int tree_error(const char *str, const char *path) +{ + set_error(str, path); + return -1; +} + +static int tree_parse_error(const char *str, const char *path) +{ + set_error(str, path); + return GIT_EINVALID; +} + +static int parse_mode(uint16_t *mode_out, const char *buffer, size_t buffer_len, const char **buffer_out) +{ + int32_t mode; + int error; + + if (!buffer_len || git__isspace(*buffer)) + return -1; + + if ((error = git__strntol32(&mode, buffer, buffer_len, buffer_out, 8)) < 0) + return error; + + if (mode < 0 || mode > UINT16_MAX) + return -1; + + *mode_out = mode; + + return 0; +} + +int git_tree__parse_raw(void *_tree, const char *data, size_t size) +{ + git_tree *tree = _tree; + const char *buffer; + const char *buffer_end; + + buffer = data; + buffer_end = buffer + size; + + tree->odb_obj = NULL; + git_array_init_to_size(tree->entries, DEFAULT_TREE_SIZE); + GIT_ERROR_CHECK_ARRAY(tree->entries); + + while (buffer < buffer_end) { + git_tree_entry *entry; + size_t filename_len; + const char *nul; + uint16_t attr; + + if (parse_mode(&attr, buffer, buffer_end - buffer, &buffer) < 0 || !buffer) + return tree_parse_error("failed to parse tree: can't parse filemode", NULL); + + if (buffer >= buffer_end || (*buffer++) != ' ') + return tree_parse_error("failed to parse tree: missing space after filemode", NULL); + + if ((nul = memchr(buffer, 0, buffer_end - buffer)) == NULL) + return tree_parse_error("failed to parse tree: object is corrupted", NULL); + + if ((filename_len = nul - buffer) == 0 || filename_len > UINT16_MAX) + return tree_parse_error("failed to parse tree: can't parse filename", NULL); + + if ((buffer_end - (nul + 1)) < GIT_OID_RAWSZ) + return tree_parse_error("failed to parse tree: can't parse OID", NULL); + + /* Allocate the entry */ + { + entry = git_array_alloc(tree->entries); + GIT_ERROR_CHECK_ALLOC(entry); + + entry->attr = attr; + entry->filename_len = (uint16_t)filename_len; + entry->filename = buffer; + entry->oid = (git_oid *) ((char *) buffer + filename_len + 1); + } + + buffer += filename_len + 1; + buffer += GIT_OID_RAWSZ; + } + + return 0; +} + +int git_tree__parse(void *_tree, git_odb_object *odb_obj) +{ + git_tree *tree = _tree; + const char *data = git_odb_object_data(odb_obj); + size_t size = git_odb_object_size(odb_obj); + int error; + + if ((error = git_tree__parse_raw(tree, data, size)) < 0 || + (error = git_odb_object_dup(&tree->odb_obj, odb_obj)) < 0) + return error; + + return error; +} + +static size_t find_next_dir(const char *dirname, git_index *index, size_t start) +{ + size_t dirlen, i, entries = git_index_entrycount(index); + + dirlen = strlen(dirname); + for (i = start; i < entries; ++i) { + const git_index_entry *entry = git_index_get_byindex(index, i); + if (strlen(entry->path) < dirlen || + memcmp(entry->path, dirname, dirlen) || + (dirlen > 0 && entry->path[dirlen] != '/')) { + break; + } + } + + return i; +} + +static git_object_t otype_from_mode(git_filemode_t filemode) +{ + switch (filemode) { + case GIT_FILEMODE_TREE: + return GIT_OBJECT_TREE; + case GIT_FILEMODE_COMMIT: + return GIT_OBJECT_COMMIT; + default: + return GIT_OBJECT_BLOB; + } +} + +static int check_entry(git_repository *repo, const char *filename, const git_oid *id, git_filemode_t filemode) +{ + if (!valid_filemode(filemode)) + return tree_error("failed to insert entry: invalid filemode for file", filename); + + if (!valid_entry_name(repo, filename)) + return tree_error("failed to insert entry: invalid name for a tree entry", filename); + + if (git_oid_is_zero(id)) + return tree_error("failed to insert entry: invalid null OID", filename); + + if (filemode != GIT_FILEMODE_COMMIT && + !git_object__is_valid(repo, id, otype_from_mode(filemode))) + return tree_error("failed to insert entry: invalid object specified", filename); + + return 0; +} + +static int git_treebuilder__write_with_buffer( + git_oid *oid, + git_treebuilder *bld, + git_str *buf) +{ + int error = 0; + size_t i, entrycount; + git_odb *odb; + git_tree_entry *entry; + git_vector entries = GIT_VECTOR_INIT; + + git_str_clear(buf); + + entrycount = git_strmap_size(bld->map); + if ((error = git_vector_init(&entries, entrycount, entry_sort_cmp)) < 0) + goto out; + + if (buf->asize == 0 && + (error = git_str_grow(buf, entrycount * 72)) < 0) + goto out; + + git_strmap_foreach_value(bld->map, entry, { + if ((error = git_vector_insert(&entries, entry)) < 0) + goto out; + }); + + git_vector_sort(&entries); + + for (i = 0; i < entries.length && !error; ++i) { + entry = git_vector_get(&entries, i); + + git_str_printf(buf, "%o ", entry->attr); + git_str_put(buf, entry->filename, entry->filename_len + 1); + git_str_put(buf, (char *)entry->oid->id, GIT_OID_RAWSZ); + + if (git_str_oom(buf)) { + error = -1; + goto out; + } + } + + if ((error = git_repository_odb__weakptr(&odb, bld->repo)) == 0) + error = git_odb_write(oid, odb, buf->ptr, buf->size, GIT_OBJECT_TREE); + +out: + git_vector_free(&entries); + + return error; +} + +static int append_entry( + git_treebuilder *bld, + const char *filename, + const git_oid *id, + git_filemode_t filemode, + bool validate) +{ + git_tree_entry *entry; + int error = 0; + + if (validate && ((error = check_entry(bld->repo, filename, id, filemode)) < 0)) + return error; + + entry = alloc_entry(filename, strlen(filename), id); + GIT_ERROR_CHECK_ALLOC(entry); + + entry->attr = (uint16_t)filemode; + + if ((error = git_strmap_set(bld->map, entry->filename, entry)) < 0) { + git_tree_entry_free(entry); + git_error_set(GIT_ERROR_TREE, "failed to append entry %s to the tree builder", filename); + return -1; + } + + return 0; +} + +static int write_tree( + git_oid *oid, + git_repository *repo, + git_index *index, + const char *dirname, + size_t start, + git_str *shared_buf) +{ + git_treebuilder *bld = NULL; + size_t i, entries = git_index_entrycount(index); + int error; + size_t dirname_len = strlen(dirname); + const git_tree_cache *cache; + + cache = git_tree_cache_get(index->tree, dirname); + if (cache != NULL && cache->entry_count >= 0){ + git_oid_cpy(oid, &cache->oid); + return (int)find_next_dir(dirname, index, start); + } + + if ((error = git_treebuilder_new(&bld, repo, NULL)) < 0 || bld == NULL) + return -1; + + /* + * This loop is unfortunate, but necessary. The index doesn't have + * any directories, so we need to handle that manually, and we + * need to keep track of the current position. + */ + for (i = start; i < entries; ++i) { + const git_index_entry *entry = git_index_get_byindex(index, i); + const char *filename, *next_slash; + + /* + * If we've left our (sub)tree, exit the loop and return. The + * first check is an early out (and security for the + * third). The second check is a simple prefix comparison. The + * third check catches situations where there is a directory + * win32/sys and a file win32mmap.c. Without it, the following + * code believes there is a file win32/mmap.c + */ + if (strlen(entry->path) < dirname_len || + memcmp(entry->path, dirname, dirname_len) || + (dirname_len > 0 && entry->path[dirname_len] != '/')) { + break; + } + + filename = entry->path + dirname_len; + if (*filename == '/') + filename++; + next_slash = strchr(filename, '/'); + if (next_slash) { + git_oid sub_oid; + int written; + char *subdir, *last_comp; + + subdir = git__strndup(entry->path, next_slash - entry->path); + GIT_ERROR_CHECK_ALLOC(subdir); + + /* Write out the subtree */ + written = write_tree(&sub_oid, repo, index, subdir, i, shared_buf); + if (written < 0) { + git__free(subdir); + goto on_error; + } else { + i = written - 1; /* -1 because of the loop increment */ + } + + /* + * We need to figure out what we want toinsert + * into this tree. If we're traversing + * deps/zlib/, then we only want to write + * 'zlib' into the tree. + */ + last_comp = strrchr(subdir, '/'); + if (last_comp) { + last_comp++; /* Get rid of the '/' */ + } else { + last_comp = subdir; + } + + error = append_entry(bld, last_comp, &sub_oid, S_IFDIR, true); + git__free(subdir); + if (error < 0) + goto on_error; + } else { + error = append_entry(bld, filename, &entry->id, entry->mode, true); + if (error < 0) + goto on_error; + } + } + + if (git_treebuilder__write_with_buffer(oid, bld, shared_buf) < 0) + goto on_error; + + git_treebuilder_free(bld); + return (int)i; + +on_error: + git_treebuilder_free(bld); + return -1; +} + +int git_tree__write_index( + git_oid *oid, git_index *index, git_repository *repo) +{ + int ret; + git_tree *tree; + git_str shared_buf = GIT_STR_INIT; + bool old_ignore_case = false; + + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(index); + GIT_ASSERT_ARG(repo); + + if (git_index_has_conflicts(index)) { + git_error_set(GIT_ERROR_INDEX, + "cannot create a tree from a not fully merged index."); + return GIT_EUNMERGED; + } + + if (index->tree != NULL && index->tree->entry_count >= 0) { + git_oid_cpy(oid, &index->tree->oid); + return 0; + } + + /* The tree cache didn't help us; we'll have to write + * out a tree. If the index is ignore_case, we must + * make it case-sensitive for the duration of the tree-write + * operation. */ + + if (index->ignore_case) { + old_ignore_case = true; + git_index__set_ignore_case(index, false); + } + + ret = write_tree(oid, repo, index, "", 0, &shared_buf); + git_str_dispose(&shared_buf); + + if (old_ignore_case) + git_index__set_ignore_case(index, true); + + index->tree = NULL; + + if (ret < 0) + return ret; + + git_pool_clear(&index->tree_pool); + + if ((ret = git_tree_lookup(&tree, repo, oid)) < 0) + return ret; + + /* Read the tree cache into the index */ + ret = git_tree_cache_read_tree(&index->tree, tree, &index->tree_pool); + git_tree_free(tree); + + return ret; +} + +int git_treebuilder_new( + git_treebuilder **builder_p, + git_repository *repo, + const git_tree *source) +{ + git_treebuilder *bld; + size_t i; + + GIT_ASSERT_ARG(builder_p); + GIT_ASSERT_ARG(repo); + + bld = git__calloc(1, sizeof(git_treebuilder)); + GIT_ERROR_CHECK_ALLOC(bld); + + bld->repo = repo; + + if (git_strmap_new(&bld->map) < 0) { + git__free(bld); + return -1; + } + + if (source != NULL) { + git_tree_entry *entry_src; + + git_array_foreach(source->entries, i, entry_src) { + if (append_entry( + bld, entry_src->filename, + entry_src->oid, + entry_src->attr, + false) < 0) + goto on_error; + } + } + + *builder_p = bld; + return 0; + +on_error: + git_treebuilder_free(bld); + return -1; +} + +int git_treebuilder_insert( + const git_tree_entry **entry_out, + git_treebuilder *bld, + const char *filename, + const git_oid *id, + git_filemode_t filemode) +{ + git_tree_entry *entry; + int error; + + GIT_ASSERT_ARG(bld); + GIT_ASSERT_ARG(id); + GIT_ASSERT_ARG(filename); + + if ((error = check_entry(bld->repo, filename, id, filemode)) < 0) + return error; + + if ((entry = git_strmap_get(bld->map, filename)) != NULL) { + git_oid_cpy((git_oid *) entry->oid, id); + } else { + entry = alloc_entry(filename, strlen(filename), id); + GIT_ERROR_CHECK_ALLOC(entry); + + if ((error = git_strmap_set(bld->map, entry->filename, entry)) < 0) { + git_tree_entry_free(entry); + git_error_set(GIT_ERROR_TREE, "failed to insert %s", filename); + return -1; + } + } + + entry->attr = filemode; + + if (entry_out) + *entry_out = entry; + + return 0; +} + +static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename) +{ + GIT_ASSERT_ARG_WITH_RETVAL(bld, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(filename, NULL); + + return git_strmap_get(bld->map, filename); +} + +const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename) +{ + return treebuilder_get(bld, filename); +} + +int git_treebuilder_remove(git_treebuilder *bld, const char *filename) +{ + git_tree_entry *entry = treebuilder_get(bld, filename); + + if (entry == NULL) + return tree_error("failed to remove entry: file isn't in the tree", filename); + + git_strmap_delete(bld->map, filename); + git_tree_entry_free(entry); + + return 0; +} + +int git_treebuilder_write(git_oid *oid, git_treebuilder *bld) +{ + GIT_ASSERT_ARG(oid); + GIT_ASSERT_ARG(bld); + + return git_treebuilder__write_with_buffer(oid, bld, &bld->write_cache); +} + +int git_treebuilder_filter( + git_treebuilder *bld, + git_treebuilder_filter_cb filter, + void *payload) +{ + const char *filename; + git_tree_entry *entry; + + GIT_ASSERT_ARG(bld); + GIT_ASSERT_ARG(filter); + + git_strmap_foreach(bld->map, filename, entry, { + if (filter(entry, payload)) { + git_strmap_delete(bld->map, filename); + git_tree_entry_free(entry); + } + }); + + return 0; +} + +int git_treebuilder_clear(git_treebuilder *bld) +{ + git_tree_entry *e; + + GIT_ASSERT_ARG(bld); + + git_strmap_foreach_value(bld->map, e, git_tree_entry_free(e)); + git_strmap_clear(bld->map); + + return 0; +} + +void git_treebuilder_free(git_treebuilder *bld) +{ + if (bld == NULL) + return; + + git_str_dispose(&bld->write_cache); + git_treebuilder_clear(bld); + git_strmap_free(bld->map); + git__free(bld); +} + +static size_t subpath_len(const char *path) +{ + const char *slash_pos = strchr(path, '/'); + if (slash_pos == NULL) + return strlen(path); + + return slash_pos - path; +} + +int git_tree_entry_bypath( + git_tree_entry **entry_out, + const git_tree *root, + const char *path) +{ + int error = 0; + git_tree *subtree; + const git_tree_entry *entry; + size_t filename_len; + + /* Find how long is the current path component (i.e. + * the filename between two slashes */ + filename_len = subpath_len(path); + + if (filename_len == 0) { + git_error_set(GIT_ERROR_TREE, "invalid tree path given"); + return GIT_ENOTFOUND; + } + + entry = entry_fromname(root, path, filename_len); + + if (entry == NULL) { + git_error_set(GIT_ERROR_TREE, + "the path '%.*s' does not exist in the given tree", (int) filename_len, path); + return GIT_ENOTFOUND; + } + + switch (path[filename_len]) { + case '/': + /* If there are more components in the path... + * then this entry *must* be a tree */ + if (!git_tree_entry__is_tree(entry)) { + git_error_set(GIT_ERROR_TREE, + "the path '%.*s' exists but is not a tree", (int) filename_len, path); + return GIT_ENOTFOUND; + } + + /* If there's only a slash left in the path, we + * return the current entry; otherwise, we keep + * walking down the path */ + if (path[filename_len + 1] != '\0') + break; + /* fall through */ + case '\0': + /* If there are no more components in the path, return + * this entry */ + return git_tree_entry_dup(entry_out, entry); + } + + if (git_tree_lookup(&subtree, root->object.repo, entry->oid) < 0) + return -1; + + error = git_tree_entry_bypath( + entry_out, + subtree, + path + filename_len + 1 + ); + + git_tree_free(subtree); + return error; +} + +static int tree_walk( + const git_tree *tree, + git_treewalk_cb callback, + git_str *path, + void *payload, + bool preorder) +{ + int error = 0; + size_t i; + const git_tree_entry *entry; + + git_array_foreach(tree->entries, i, entry) { + if (preorder) { + error = callback(path->ptr, entry, payload); + if (error < 0) { /* negative value stops iteration */ + git_error_set_after_callback_function(error, "git_tree_walk"); + break; + } + if (error > 0) { /* positive value skips this entry */ + error = 0; + continue; + } + } + + if (git_tree_entry__is_tree(entry)) { + git_tree *subtree; + size_t path_len = git_str_len(path); + + error = git_tree_lookup(&subtree, tree->object.repo, entry->oid); + if (error < 0) + break; + + /* append the next entry to the path */ + git_str_puts(path, entry->filename); + git_str_putc(path, '/'); + + if (git_str_oom(path)) + error = -1; + else + error = tree_walk(subtree, callback, path, payload, preorder); + + git_tree_free(subtree); + if (error != 0) + break; + + git_str_truncate(path, path_len); + } + + if (!preorder) { + error = callback(path->ptr, entry, payload); + if (error < 0) { /* negative value stops iteration */ + git_error_set_after_callback_function(error, "git_tree_walk"); + break; + } + error = 0; + } + } + + return error; +} + +int git_tree_walk( + const git_tree *tree, + git_treewalk_mode mode, + git_treewalk_cb callback, + void *payload) +{ + int error = 0; + git_str root_path = GIT_STR_INIT; + + if (mode != GIT_TREEWALK_POST && mode != GIT_TREEWALK_PRE) { + git_error_set(GIT_ERROR_INVALID, "invalid walking mode for tree walk"); + return -1; + } + + error = tree_walk( + tree, callback, &root_path, payload, (mode == GIT_TREEWALK_PRE)); + + git_str_dispose(&root_path); + + return error; +} + +static int compare_entries(const void *_a, const void *_b) +{ + const git_tree_update *a = (git_tree_update *) _a; + const git_tree_update *b = (git_tree_update *) _b; + + return strcmp(a->path, b->path); +} + +static int on_dup_entry(void **old, void *new) +{ + GIT_UNUSED(old); GIT_UNUSED(new); + + git_error_set(GIT_ERROR_TREE, "duplicate entries given for update"); + return -1; +} + +/* + * We keep the previous tree and the new one at each level of the + * stack. When we leave a level we're done with that tree and we can + * write it out to the odb. + */ +typedef struct { + git_treebuilder *bld; + git_tree *tree; + char *name; +} tree_stack_entry; + +/** Count how many slashes (i.e. path components) there are in this string */ +GIT_INLINE(size_t) count_slashes(const char *path) +{ + size_t count = 0; + const char *slash; + + while ((slash = strchr(path, '/')) != NULL) { + count++; + path = slash + 1; + } + + return count; +} + +static bool next_component(git_str *out, const char *in) +{ + const char *slash = strchr(in, '/'); + + git_str_clear(out); + + if (slash) + git_str_put(out, in, slash - in); + + return !!slash; +} + +static int create_popped_tree(tree_stack_entry *current, tree_stack_entry *popped, git_str *component) +{ + int error; + git_oid new_tree; + + git_tree_free(popped->tree); + + /* If the tree would be empty, remove it from the one higher up */ + if (git_treebuilder_entrycount(popped->bld) == 0) { + git_treebuilder_free(popped->bld); + error = git_treebuilder_remove(current->bld, popped->name); + git__free(popped->name); + return error; + } + + error = git_treebuilder_write(&new_tree, popped->bld); + git_treebuilder_free(popped->bld); + + if (error < 0) { + git__free(popped->name); + return error; + } + + /* We've written out the tree, now we have to put the new value into its parent */ + git_str_clear(component); + git_str_puts(component, popped->name); + git__free(popped->name); + + GIT_ERROR_CHECK_ALLOC(component->ptr); + + /* Error out if this would create a D/F conflict in this update */ + if (current->tree) { + const git_tree_entry *to_replace; + to_replace = git_tree_entry_byname(current->tree, component->ptr); + if (to_replace && git_tree_entry_type(to_replace) != GIT_OBJECT_TREE) { + git_error_set(GIT_ERROR_TREE, "D/F conflict when updating tree"); + return -1; + } + } + + return git_treebuilder_insert(NULL, current->bld, component->ptr, &new_tree, GIT_FILEMODE_TREE); +} + +int git_tree_create_updated(git_oid *out, git_repository *repo, git_tree *baseline, size_t nupdates, const git_tree_update *updates) +{ + git_array_t(tree_stack_entry) stack = GIT_ARRAY_INIT; + tree_stack_entry *root_elem; + git_vector entries; + int error; + size_t i; + git_str component = GIT_STR_INIT; + + if ((error = git_vector_init(&entries, nupdates, compare_entries)) < 0) + return error; + + /* Sort the entries for treversal */ + for (i = 0 ; i < nupdates; i++) { + if ((error = git_vector_insert_sorted(&entries, (void *) &updates[i], on_dup_entry)) < 0) + goto cleanup; + } + + root_elem = git_array_alloc(stack); + GIT_ERROR_CHECK_ALLOC(root_elem); + memset(root_elem, 0, sizeof(*root_elem)); + + if (baseline && (error = git_tree_dup(&root_elem->tree, baseline)) < 0) + goto cleanup; + + if ((error = git_treebuilder_new(&root_elem->bld, repo, root_elem->tree)) < 0) + goto cleanup; + + for (i = 0; i < nupdates; i++) { + const git_tree_update *last_update = i == 0 ? NULL : git_vector_get(&entries, i-1); + const git_tree_update *update = git_vector_get(&entries, i); + size_t common_prefix = 0, steps_up, j; + const char *path; + + /* Figure out how much we need to change from the previous tree */ + if (last_update) + common_prefix = git_fs_path_common_dirlen(last_update->path, update->path); + + /* + * The entries are sorted, so when we find we're no + * longer in the same directory, we need to abandon + * the old tree (steps up) and dive down to the next + * one. + */ + steps_up = last_update == NULL ? 0 : count_slashes(&last_update->path[common_prefix]); + + for (j = 0; j < steps_up; j++) { + tree_stack_entry *current, *popped = git_array_pop(stack); + GIT_ASSERT(popped); + + current = git_array_last(stack); + GIT_ASSERT(current); + + if ((error = create_popped_tree(current, popped, &component)) < 0) + goto cleanup; + } + + /* Now that we've created the trees we popped from the stack, let's go back down */ + path = &update->path[common_prefix]; + while (next_component(&component, path)) { + tree_stack_entry *last, *new_entry; + const git_tree_entry *entry; + + last = git_array_last(stack); + entry = last->tree ? git_tree_entry_byname(last->tree, component.ptr) : NULL; + if (!entry) + entry = treebuilder_get(last->bld, component.ptr); + + if (entry && git_tree_entry_type(entry) != GIT_OBJECT_TREE) { + git_error_set(GIT_ERROR_TREE, "D/F conflict when updating tree"); + error = -1; + goto cleanup; + } + + new_entry = git_array_alloc(stack); + GIT_ERROR_CHECK_ALLOC(new_entry); + memset(new_entry, 0, sizeof(*new_entry)); + + new_entry->tree = NULL; + if (entry && (error = git_tree_lookup(&new_entry->tree, repo, git_tree_entry_id(entry))) < 0) + goto cleanup; + + if ((error = git_treebuilder_new(&new_entry->bld, repo, new_entry->tree)) < 0) + goto cleanup; + + new_entry->name = git__strdup(component.ptr); + GIT_ERROR_CHECK_ALLOC(new_entry->name); + + /* Get to the start of the next component */ + path += component.size + 1; + } + + /* After all that, we're finally at the place where we want to perform the update */ + switch (update->action) { + case GIT_TREE_UPDATE_UPSERT: + { + /* Make sure we're replacing something of the same type */ + tree_stack_entry *last = git_array_last(stack); + char *basename = git_fs_path_basename(update->path); + const git_tree_entry *e = git_treebuilder_get(last->bld, basename); + if (e && git_tree_entry_type(e) != git_object__type_from_filemode(update->filemode)) { + git__free(basename); + git_error_set(GIT_ERROR_TREE, "cannot replace '%s' with '%s' at '%s'", + git_object_type2string(git_tree_entry_type(e)), + git_object_type2string(git_object__type_from_filemode(update->filemode)), + update->path); + error = -1; + goto cleanup; + } + + error = git_treebuilder_insert(NULL, last->bld, basename, &update->id, update->filemode); + git__free(basename); + break; + } + case GIT_TREE_UPDATE_REMOVE: + { + tree_stack_entry *last = git_array_last(stack); + char *basename = git_fs_path_basename(update->path); + error = git_treebuilder_remove(last->bld, basename); + git__free(basename); + break; + } + default: + git_error_set(GIT_ERROR_TREE, "unknown action for update"); + error = -1; + goto cleanup; + } + + if (error < 0) + goto cleanup; + } + + /* We're done, go up the stack again and write out the tree */ + { + tree_stack_entry *current = NULL, *popped = NULL; + while ((popped = git_array_pop(stack)) != NULL) { + current = git_array_last(stack); + /* We've reached the top, current is the root tree */ + if (!current) + break; + + if ((error = create_popped_tree(current, popped, &component)) < 0) + goto cleanup; + } + + /* Write out the root tree */ + git__free(popped->name); + git_tree_free(popped->tree); + + error = git_treebuilder_write(out, popped->bld); + git_treebuilder_free(popped->bld); + if (error < 0) + goto cleanup; + } + +cleanup: + { + tree_stack_entry *e; + while ((e = git_array_pop(stack)) != NULL) { + git_treebuilder_free(e->bld); + git_tree_free(e->tree); + git__free(e->name); + } + } + + git_str_dispose(&component); + git_array_clear(stack); + git_vector_free(&entries); + return error; +} + +/* Deprecated Functions */ + +#ifndef GIT_DEPRECATE_HARD + +int git_treebuilder_write_with_buffer(git_oid *oid, git_treebuilder *bld, git_buf *buf) +{ + GIT_UNUSED(buf); + + return git_treebuilder_write(oid, bld); +} + +#endif diff --git a/src/libgit2/tree.h b/src/libgit2/tree.h new file mode 100644 index 000000000..6bd9ed652 --- /dev/null +++ b/src/libgit2/tree.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_tree_h__ +#define INCLUDE_tree_h__ + +#include "common.h" + +#include "git2/tree.h" +#include "repository.h" +#include "odb.h" +#include "vector.h" +#include "strmap.h" +#include "pool.h" + +struct git_tree_entry { + uint16_t attr; + uint16_t filename_len; + const git_oid *oid; + const char *filename; +}; + +struct git_tree { + git_object object; + git_odb_object *odb_obj; + git_array_t(git_tree_entry) entries; +}; + +struct git_treebuilder { + git_repository *repo; + git_strmap *map; + git_str write_cache; +}; + +GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e) +{ + return (S_ISDIR(e->attr) && !S_ISGITLINK(e->attr)); +} + +void git_tree__free(void *tree); +int git_tree__parse(void *tree, git_odb_object *obj); +int git_tree__parse_raw(void *_tree, const char *data, size_t size); + +/** + * Write a tree to the given repository + */ +int git_tree__write_index( + git_oid *oid, git_index *index, git_repository *repo); + +/** + * Obsolete mode kept for compatibility reasons + */ +#define GIT_FILEMODE_BLOB_GROUP_WRITABLE 0100664 + +#endif diff --git a/src/libgit2/tsort.c b/src/libgit2/tsort.c new file mode 100644 index 000000000..045efad23 --- /dev/null +++ b/src/libgit2/tsort.c @@ -0,0 +1,382 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +/** + * An array-of-pointers implementation of Python's Timsort + * Based on code by Christopher Swenson under the MIT license + * + * Copyright (c) 2010 Christopher Swenson + * Copyright (c) 2011 Vicent Marti + */ + +#ifndef MAX +# define MAX(x,y) (((x) > (y) ? (x) : (y))) +#endif + +#ifndef MIN +# define MIN(x,y) (((x) < (y) ? (x) : (y))) +#endif + +static int binsearch( + void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload) +{ + int l, c, r; + void *lx, *cx; + + l = 0; + r = (int)size - 1; + c = r >> 1; + lx = dst[l]; + + /* check for beginning conditions */ + if (cmp(x, lx, payload) < 0) + return 0; + + else if (cmp(x, lx, payload) == 0) { + int i = 1; + while (cmp(x, dst[i], payload) == 0) + i++; + return i; + } + + /* guaranteed not to be >= rx */ + cx = dst[c]; + while (1) { + const int val = cmp(x, cx, payload); + if (val < 0) { + if (c - l <= 1) return c; + r = c; + } else if (val > 0) { + if (r - c <= 1) return c + 1; + l = c; + lx = cx; + } else { + do { + cx = dst[++c]; + } while (cmp(x, cx, payload) == 0); + return c; + } + c = l + ((r - l) >> 1); + cx = dst[c]; + } +} + +/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */ +static void bisort( + void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload) +{ + size_t i; + void *x; + int location; + + for (i = start; i < size; i++) { + int j; + /* If this entry is already correct, just move along */ + if (cmp(dst[i - 1], dst[i], payload) <= 0) + continue; + + /* Else we need to find the right place, shift everything over, and squeeze in */ + x = dst[i]; + location = binsearch(dst, x, i, cmp, payload); + for (j = (int)i - 1; j >= location; j--) { + dst[j + 1] = dst[j]; + } + dst[location] = x; + } +} + + +/* timsort implementation, based on timsort.txt */ +struct tsort_run { + ssize_t start; + ssize_t length; +}; + +struct tsort_store { + size_t alloc; + git__sort_r_cmp cmp; + void *payload; + void **storage; +}; + +static void reverse_elements(void **dst, ssize_t start, ssize_t end) +{ + while (start < end) { + void *tmp = dst[start]; + dst[start] = dst[end]; + dst[end] = tmp; + + start++; + end--; + } +} + +static ssize_t count_run( + void **dst, ssize_t start, ssize_t size, struct tsort_store *store) +{ + ssize_t curr = start + 2; + + if (size - start == 1) + return 1; + + if (start >= size - 2) { + if (store->cmp(dst[size - 2], dst[size - 1], store->payload) > 0) { + void *tmp = dst[size - 1]; + dst[size - 1] = dst[size - 2]; + dst[size - 2] = tmp; + } + + return 2; + } + + if (store->cmp(dst[start], dst[start + 1], store->payload) <= 0) { + while (curr < size - 1 && + store->cmp(dst[curr - 1], dst[curr], store->payload) <= 0) + curr++; + + return curr - start; + } else { + while (curr < size - 1 && + store->cmp(dst[curr - 1], dst[curr], store->payload) > 0) + curr++; + + /* reverse in-place */ + reverse_elements(dst, start, curr - 1); + return curr - start; + } +} + +static size_t compute_minrun(size_t n) +{ + int r = 0; + while (n >= 64) { + r |= n & 1; + n >>= 1; + } + return n + r; +} + +static int check_invariant(struct tsort_run *stack, ssize_t stack_curr) +{ + if (stack_curr < 2) + return 1; + + else if (stack_curr == 2) { + const ssize_t A = stack[stack_curr - 2].length; + const ssize_t B = stack[stack_curr - 1].length; + return (A > B); + } else { + const ssize_t A = stack[stack_curr - 3].length; + const ssize_t B = stack[stack_curr - 2].length; + const ssize_t C = stack[stack_curr - 1].length; + return !((A <= B + C) || (B <= C)); + } +} + +static int resize(struct tsort_store *store, size_t new_size) +{ + if (store->alloc < new_size) { + void **tempstore; + + tempstore = git__reallocarray(store->storage, new_size, sizeof(void *)); + + /** + * Do not propagate on OOM; this will abort the sort and + * leave the array unsorted, but no error code will be + * raised + */ + if (tempstore == NULL) + return -1; + + store->storage = tempstore; + store->alloc = new_size; + } + + return 0; +} + +static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store) +{ + const ssize_t A = stack[stack_curr - 2].length; + const ssize_t B = stack[stack_curr - 1].length; + const ssize_t curr = stack[stack_curr - 2].start; + + void **storage; + ssize_t i, j, k; + + if (resize(store, MIN(A, B)) < 0) + return; + + storage = store->storage; + + /* left merge */ + if (A < B) { + memcpy(storage, &dst[curr], A * sizeof(void *)); + i = 0; + j = curr + A; + + for (k = curr; k < curr + A + B; k++) { + if ((i < A) && (j < curr + A + B)) { + if (store->cmp(storage[i], dst[j], store->payload) <= 0) + dst[k] = storage[i++]; + else + dst[k] = dst[j++]; + } else if (i < A) { + dst[k] = storage[i++]; + } else + dst[k] = dst[j++]; + } + } else { + memcpy(storage, &dst[curr + A], B * sizeof(void *)); + i = B - 1; + j = curr + A - 1; + + for (k = curr + A + B - 1; k >= curr; k--) { + if ((i >= 0) && (j >= curr)) { + if (store->cmp(dst[j], storage[i], store->payload) > 0) + dst[k] = dst[j--]; + else + dst[k] = storage[i--]; + } else if (i >= 0) + dst[k] = storage[i--]; + else + dst[k] = dst[j--]; + } + } +} + +static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store, ssize_t size) +{ + ssize_t A, B, C; + + while (1) { + /* if the stack only has one thing on it, we are done with the collapse */ + if (stack_curr <= 1) + break; + + /* if this is the last merge, just do it */ + if ((stack_curr == 2) && (stack[0].length + stack[1].length == size)) { + merge(dst, stack, stack_curr, store); + stack[0].length += stack[1].length; + stack_curr--; + break; + } + + /* check if the invariant is off for a stack of 2 elements */ + else if ((stack_curr == 2) && (stack[0].length <= stack[1].length)) { + merge(dst, stack, stack_curr, store); + stack[0].length += stack[1].length; + stack_curr--; + break; + } + else if (stack_curr == 2) + break; + + A = stack[stack_curr - 3].length; + B = stack[stack_curr - 2].length; + C = stack[stack_curr - 1].length; + + /* check first invariant */ + if (A <= B + C) { + if (A < C) { + merge(dst, stack, stack_curr - 1, store); + stack[stack_curr - 3].length += stack[stack_curr - 2].length; + stack[stack_curr - 2] = stack[stack_curr - 1]; + stack_curr--; + } else { + merge(dst, stack, stack_curr, store); + stack[stack_curr - 2].length += stack[stack_curr - 1].length; + stack_curr--; + } + } else if (B <= C) { + merge(dst, stack, stack_curr, store); + stack[stack_curr - 2].length += stack[stack_curr - 1].length; + stack_curr--; + } else + break; + } + + return stack_curr; +} + +#define PUSH_NEXT() do {\ + len = count_run(dst, curr, size, store);\ + run = minrun;\ + if (run > (ssize_t)size - curr) run = size - curr;\ + if (run > len) {\ + bisort(&dst[curr], len, run, cmp, payload);\ + len = run;\ + }\ + run_stack[stack_curr].start = curr;\ + run_stack[stack_curr++].length = len;\ + curr += len;\ + if (curr == (ssize_t)size) {\ + /* finish up */ \ + while (stack_curr > 1) { \ + merge(dst, run_stack, stack_curr, store); \ + run_stack[stack_curr - 2].length += run_stack[stack_curr - 1].length; \ + stack_curr--; \ + } \ + if (store->storage != NULL) {\ + git__free(store->storage);\ + store->storage = NULL;\ + }\ + return;\ + }\ +}\ +while (0) + +void git__tsort_r( + void **dst, size_t size, git__sort_r_cmp cmp, void *payload) +{ + struct tsort_store _store, *store = &_store; + struct tsort_run run_stack[128]; + + ssize_t stack_curr = 0; + ssize_t len, run; + ssize_t curr = 0; + ssize_t minrun; + + if (size < 64) { + bisort(dst, 1, size, cmp, payload); + return; + } + + /* compute the minimum run length */ + minrun = (ssize_t)compute_minrun(size); + + /* temporary storage for merges */ + store->alloc = 0; + store->storage = NULL; + store->cmp = cmp; + store->payload = payload; + + PUSH_NEXT(); + PUSH_NEXT(); + PUSH_NEXT(); + + while (1) { + if (!check_invariant(run_stack, stack_curr)) { + stack_curr = collapse(dst, run_stack, stack_curr, store, size); + continue; + } + + PUSH_NEXT(); + } +} + +static int tsort_r_cmp(const void *a, const void *b, void *payload) +{ + return ((git__tsort_cmp)payload)(a, b); +} + +void git__tsort(void **dst, size_t size, git__tsort_cmp cmp) +{ + git__tsort_r(dst, size, tsort_r_cmp, cmp); +} diff --git a/src/libgit2/unix/map.c b/src/libgit2/unix/map.c new file mode 100644 index 000000000..23fcb786e --- /dev/null +++ b/src/libgit2/unix/map.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#if !defined(GIT_WIN32) && !defined(NO_MMAP) + +#include "map.h" +#include +#include +#include + +int git__page_size(size_t *page_size) +{ + long sc_page_size = sysconf(_SC_PAGE_SIZE); + if (sc_page_size < 0) { + git_error_set(GIT_ERROR_OS, "can't determine system page size"); + return -1; + } + *page_size = (size_t) sc_page_size; + return 0; +} + +int git__mmap_alignment(size_t *alignment) +{ + return git__page_size(alignment); +} + +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) +{ + int mprot = PROT_READ; + int mflag = 0; + + GIT_MMAP_VALIDATE(out, len, prot, flags); + + out->data = NULL; + out->len = 0; + + if (prot & GIT_PROT_WRITE) + mprot |= PROT_WRITE; + + if ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) + mflag = MAP_SHARED; + else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE) + mflag = MAP_PRIVATE; + else + mflag = MAP_SHARED; + + out->data = mmap(NULL, len, mprot, mflag, fd, offset); + + if (!out->data || out->data == MAP_FAILED) { + git_error_set(GIT_ERROR_OS, "failed to mmap. Could not write data"); + return -1; + } + + out->len = len; + + return 0; +} + +int p_munmap(git_map *map) +{ + GIT_ASSERT_ARG(map); + munmap(map->data, map->len); + map->data = NULL; + map->len = 0; + + return 0; +} + +#endif + diff --git a/src/libgit2/unix/posix.h b/src/libgit2/unix/posix.h new file mode 100644 index 000000000..49065e533 --- /dev/null +++ b/src/libgit2/unix/posix.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_unix_posix_h__ +#define INCLUDE_unix_posix_h__ + +#include "common.h" + +#include +#include +#include +#include +#include + +typedef int GIT_SOCKET; +#define INVALID_SOCKET -1 + +#define p_lseek(f,n,w) lseek(f, n, w) +#define p_fstat(f,b) fstat(f, b) +#define p_lstat(p,b) lstat(p,b) +#define p_stat(p,b) stat(p, b) + +#if defined(GIT_USE_STAT_MTIMESPEC) +# define st_atime_nsec st_atimespec.tv_nsec +# define st_mtime_nsec st_mtimespec.tv_nsec +# define st_ctime_nsec st_ctimespec.tv_nsec +#elif defined(GIT_USE_STAT_MTIM) +# define st_atime_nsec st_atim.tv_nsec +# define st_mtime_nsec st_mtim.tv_nsec +# define st_ctime_nsec st_ctim.tv_nsec +#elif !defined(GIT_USE_STAT_MTIME_NSEC) && defined(GIT_USE_NSEC) +# error GIT_USE_NSEC defined but unknown struct stat nanosecond type +#endif + +#define p_utimes(f, t) utimes(f, t) + +#define p_readlink(a, b, c) readlink(a, b, c) +#define p_symlink(o,n) symlink(o, n) +#define p_link(o,n) link(o, n) +#define p_unlink(p) unlink(p) +#define p_mkdir(p,m) mkdir(p, m) +extern char *p_realpath(const char *, char *); + +GIT_INLINE(int) p_fsync(int fd) +{ + p_fsync__cnt++; + return fsync(fd); +} + +#define p_recv(s,b,l,f) recv(s,b,l,f) +#define p_send(s,b,l,f) send(s,b,l,f) +#define p_inet_pton(a, b, c) inet_pton(a, b, c) + +#define p_strcasecmp(s1, s2) strcasecmp(s1, s2) +#define p_strncasecmp(s1, s2, c) strncasecmp(s1, s2, c) +#define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a) +#define p_snprintf snprintf +#define p_chdir(p) chdir(p) +#define p_rmdir(p) rmdir(p) +#define p_access(p,m) access(p,m) +#define p_ftruncate(fd, sz) ftruncate(fd, sz) + +/* + * Pre-Android 5 did not implement a virtual filesystem atop FAT + * partitions for Unix permissions, which causes chmod to fail. However, + * Unix permissions have no effect on Android anyway as file permissions + * are not actually managed this way, so treating it as a no-op across + * all Android is safe. + */ +#ifdef __ANDROID__ +# define p_chmod(p,m) 0 +#else +# define p_chmod(p,m) chmod(p, m) +#endif + +/* see win32/posix.h for explanation about why this exists */ +#define p_lstat_posixly(p,b) lstat(p,b) + +#define p_localtime_r(c, r) localtime_r(c, r) +#define p_gmtime_r(c, r) gmtime_r(c, r) + +#define p_timeval timeval + +#ifdef GIT_USE_FUTIMENS +GIT_INLINE(int) p_futimes(int f, const struct p_timeval t[2]) +{ + struct timespec s[2]; + s[0].tv_sec = t[0].tv_sec; + s[0].tv_nsec = t[0].tv_usec * 1000; + s[1].tv_sec = t[1].tv_sec; + s[1].tv_nsec = t[1].tv_usec * 1000; + return futimens(f, s); +} +#else +# define p_futimes futimes +#endif + +#define p_pread(f, d, s, o) pread(f, d, s, o) +#define p_pwrite(f, d, s, o) pwrite(f, d, s, o) + +#endif diff --git a/src/libgit2/unix/pthread.h b/src/libgit2/unix/pthread.h new file mode 100644 index 000000000..55f4ae227 --- /dev/null +++ b/src/libgit2/unix/pthread.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_unix_pthread_h__ +#define INCLUDE_unix_pthread_h__ + +typedef struct { + pthread_t thread; +} git_thread; + +GIT_INLINE(int) git_threads_global_init(void) { return 0; } + +#define git_thread_create(git_thread_ptr, start_routine, arg) \ + pthread_create(&(git_thread_ptr)->thread, NULL, start_routine, arg) +#define git_thread_join(git_thread_ptr, status) \ + pthread_join((git_thread_ptr)->thread, status) +#define git_thread_currentid() ((size_t)(pthread_self())) +#define git_thread_exit(retval) pthread_exit(retval) + +/* Git Mutex */ +#define git_mutex pthread_mutex_t +#define git_mutex_init(a) pthread_mutex_init(a, NULL) +#define git_mutex_lock(a) pthread_mutex_lock(a) +#define git_mutex_unlock(a) pthread_mutex_unlock(a) +#define git_mutex_free(a) pthread_mutex_destroy(a) + +/* Git condition vars */ +#define git_cond pthread_cond_t +#define git_cond_init(c) pthread_cond_init(c, NULL) +#define git_cond_free(c) pthread_cond_destroy(c) +#define git_cond_wait(c, l) pthread_cond_wait(c, l) +#define git_cond_signal(c) pthread_cond_signal(c) +#define git_cond_broadcast(c) pthread_cond_broadcast(c) + +/* Pthread (-ish) rwlock + * + * This differs from normal pthreads rwlocks in two ways: + * 1. Separate APIs for releasing read locks and write locks (as + * opposed to the pure POSIX API which only has one unlock fn) + * 2. You should not use recursive read locks (i.e. grabbing a read + * lock in a thread that already holds a read lock) because the + * Windows implementation doesn't support it + */ +#define git_rwlock pthread_rwlock_t +#define git_rwlock_init(a) pthread_rwlock_init(a, NULL) +#define git_rwlock_rdlock(a) pthread_rwlock_rdlock(a) +#define git_rwlock_rdunlock(a) pthread_rwlock_unlock(a) +#define git_rwlock_wrlock(a) pthread_rwlock_wrlock(a) +#define git_rwlock_wrunlock(a) pthread_rwlock_unlock(a) +#define git_rwlock_free(a) pthread_rwlock_destroy(a) +#define GIT_RWLOCK_STATIC_INIT PTHREAD_RWLOCK_INITIALIZER + +#endif diff --git a/src/libgit2/unix/realpath.c b/src/libgit2/unix/realpath.c new file mode 100644 index 000000000..f1ca669f7 --- /dev/null +++ b/src/libgit2/unix/realpath.c @@ -0,0 +1,32 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#ifndef GIT_WIN32 + +#include +#include +#include +#include + +char *p_realpath(const char *pathname, char *resolved) +{ + char *ret; + if ((ret = realpath(pathname, resolved)) == NULL) + return NULL; + +#ifdef __OpenBSD__ + /* The OpenBSD realpath function behaves differently, + * figure out if the file exists */ + if (access(ret, F_OK) < 0) + ret = NULL; +#endif + return ret; +} + +#endif diff --git a/src/libgit2/userdiff.h b/src/libgit2/userdiff.h new file mode 100644 index 000000000..c9a80d712 --- /dev/null +++ b/src/libgit2/userdiff.h @@ -0,0 +1,210 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_userdiff_h__ +#define INCLUDE_userdiff_h__ + +#include "regexp.h" + +/* + * This file isolates the built in diff driver function name patterns. + * Most of these patterns are taken from Git (with permission from the + * original authors for relicensing to libgit2). + */ + +typedef struct { + const char *name; + const char *fns; + const char *words; + int flags; +} git_diff_driver_definition; + +#define WORD_DEFAULT "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" + +/* + * These builtin driver definition macros have same signature as in core + * git userdiff.c so that the data can be extracted verbatim + */ +#define PATTERNS(NAME, FN_PATS, WORD_PAT) \ + { NAME, FN_PATS, WORD_PAT WORD_DEFAULT, 0 } +#define IPATTERN(NAME, FN_PATS, WORD_PAT) \ + { NAME, FN_PATS, WORD_PAT WORD_DEFAULT, GIT_REGEXP_ICASE } + +/* + * The table of diff driver patterns + * + * Function name patterns are a list of newline separated patterns that + * match a function declaration (i.e. the line you want in the hunk header), + * or a negative pattern prefixed with a '!' to reject a pattern (such as + * rejecting goto labels in C code). + * + * Word boundary patterns are just a simple pattern that will be OR'ed with + * the default value above (i.e. whitespace or non-ASCII characters). + */ +static git_diff_driver_definition builtin_defs[] = { + +IPATTERN("ada", + "!^(.*[ \t])?(is[ \t]+new|renames|is[ \t]+separate)([ \t].*)?$\n" + "!^[ \t]*with[ \t].*$\n" + "^[ \t]*((procedure|function)[ \t]+.*)$\n" + "^[ \t]*((package|protected|task)[ \t]+.*)$", + /* -- */ + "[a-zA-Z][a-zA-Z0-9_]*" + "|[-+]?[0-9][0-9#_.aAbBcCdDeEfF]*([eE][+-]?[0-9_]+)?" + "|=>|\\.\\.|\\*\\*|:=|/=|>=|<=|<<|>>|<>"), + +IPATTERN("fortran", + "!^([C*]|[ \t]*!)\n" + "!^[ \t]*MODULE[ \t]+PROCEDURE[ \t]\n" + "^[ \t]*((END[ \t]+)?(PROGRAM|MODULE|BLOCK[ \t]+DATA" + "|([^'\" \t]+[ \t]+)*(SUBROUTINE|FUNCTION))[ \t]+[A-Z].*)$", + /* -- */ + "[a-zA-Z][a-zA-Z0-9_]*" + "|\\.([Ee][Qq]|[Nn][Ee]|[Gg][TtEe]|[Ll][TtEe]|[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|[Aa][Nn][Dd]|[Oo][Rr]|[Nn]?[Ee][Qq][Vv]|[Nn][Oo][Tt])\\." + /* numbers and format statements like 2E14.4, or ES12.6, 9X. + * Don't worry about format statements without leading digits since + * they would have been matched above as a variable anyway. */ + "|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?" + "|//|\\*\\*|::|[/<>=]="), + +PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", + "[^<>= \t]+"), + +PATTERNS("java", + "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n" + "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=" + "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"), + +PATTERNS("matlab", + "^[[:space:]]*((classdef|function)[[:space:]].*)$|^%%[[:space:]].*$", + "[a-zA-Z_][a-zA-Z0-9_]*|[-+0-9.e]+|[=~<>]=|\\.[*/\\^']|\\|\\||&&"), + +PATTERNS("objc", + /* Negate C statements that can look like functions */ + "!^[ \t]*(do|for|if|else|return|switch|while)\n" + /* Objective-C methods */ + "^[ \t]*([-+][ \t]*\\([ \t]*[A-Za-z_][A-Za-z_0-9* \t]*\\)[ \t]*[A-Za-z_].*)$\n" + /* C functions */ + "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$\n" + /* Objective-C class/protocol definitions */ + "^(@(implementation|interface|protocol)[ \t].*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), + +PATTERNS("pascal", + "^(((class[ \t]+)?(procedure|function)|constructor|destructor|interface|" + "implementation|initialization|finalization)[ \t]*.*)$" + "\n" + "^(.*=[ \t]*(class|record).*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" + "|<>|<=|>=|:=|\\.\\."), + +PATTERNS("perl", + "^package .*\n" + "^sub [[:alnum:]_':]+[ \t]*" + "(\\([^)]*\\)[ \t]*)?" /* prototype */ + /* + * Attributes. A regex can't count nested parentheses, + * so just slurp up whatever we see, taking care not + * to accept lines like "sub foo; # defined elsewhere". + * + * An attribute could contain a semicolon, but at that + * point it seems reasonable enough to give up. + */ + "(:[^;#]*)?" + "(\\{[ \t]*)?" /* brace can come here or on the next line */ + "(#.*)?$\n" /* comment */ + "^(BEGIN|END|INIT|CHECK|UNITCHECK|AUTOLOAD|DESTROY)[ \t]*" + "(\\{[ \t]*)?" /* brace can come here or on the next line */ + "(#.*)?$\n" + "^=head[0-9] .*", /* POD */ + /* -- */ + "[[:alpha:]_'][[:alnum:]_']*" + "|0[xb]?[0-9a-fA-F_]*" + /* taking care not to interpret 3..5 as (3.)(.5) */ + "|[0-9a-fA-F_]+(\\.[0-9a-fA-F_]+)?([eE][-+]?[0-9_]+)?" + "|=>|-[rwxoRWXOezsfdlpSugkbctTBMAC>]|~~|::" + "|&&=|\\|\\|=|//=|\\*\\*=" + "|&&|\\|\\||//|\\+\\+|--|\\*\\*|\\.\\.\\.?" + "|[-+*/%.^&<>=!|]=" + "|=~|!~" + "|<<|<>|<=>|>>"), + +PATTERNS("python", "^[ \t]*((class|def)[ \t].*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"), + +PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$", + /* -- */ + "(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?." + "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~"), + +PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$", + "[={}\"]|[^={}\" \t]+"), + +PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$", + "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"), + +PATTERNS("cpp", + /* Jump targets or access declarations */ + "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:[[:space:]]*($|/[/*])\n" + /* functions/methods, variables, and compounds at top level */ + "^((::[[:space:]]*)?[A-Za-z_].*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lLuU]*" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*"), + +PATTERNS("csharp", + /* Keywords */ + "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n" + /* Methods and constructors */ + "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n" + /* Properties */ + "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n" + /* Type definitions */ + "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct)[ \t]+.*)$\n" + /* Namespace */ + "^[ \t]*(namespace[ \t]+.*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), + +PATTERNS("php", + "^[ \t]*(((public|private|protected|static|final)[ \t]+)*((class|function)[ \t].*))$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xX]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), + +PATTERNS("javascript", + "([a-zA-Z_$][a-zA-Z0-9_$]*(\\.[a-zA-Z0-9_$]+)*[ \t]*=[ \t]*function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)\n" + "([a-zA-Z_$][a-zA-Z0-9_$]*[ \t]*:[ \t]*function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)\n" + "[^a-zA-Z0-9_\\$](function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xX]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), +}; + +#undef IPATTERN +#undef PATTERNS +#undef WORD_DEFAULT + +#endif + diff --git a/src/libgit2/utf8.c b/src/libgit2/utf8.c new file mode 100644 index 000000000..77065cb71 --- /dev/null +++ b/src/libgit2/utf8.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "utf8.h" + +#include "common.h" + +/* + * git_utf8_iterate is taken from the utf8proc project, + * http://www.public-software-group.org/utf8proc + * + * Copyright (c) 2009 Public Software Group e. V., Berlin, Germany + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the ""Software""), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +static const uint8_t utf8proc_utf8class[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static int utf8_charlen(const uint8_t *str, size_t str_len) +{ + uint8_t length; + size_t i; + + length = utf8proc_utf8class[str[0]]; + if (!length) + return -1; + + if (str_len > 0 && length > str_len) + return -1; + + for (i = 1; i < length; i++) { + if ((str[i] & 0xC0) != 0x80) + return -1; + } + + return (int)length; +} + +int git_utf8_iterate(uint32_t *out, const char *_str, size_t str_len) +{ + const uint8_t *str = (const uint8_t *)_str; + uint32_t uc = 0; + int length; + + *out = 0; + + if ((length = utf8_charlen(str, str_len)) < 0) + return -1; + + switch (length) { + case 1: + uc = str[0]; + break; + case 2: + uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F); + if (uc < 0x80) uc = -1; + break; + case 3: + uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6) + + (str[2] & 0x3F); + if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000) || + (uc >= 0xFDD0 && uc < 0xFDF0)) uc = -1; + break; + case 4: + uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12) + + ((str[2] & 0x3F) << 6) + (str[3] & 0x3F); + if (uc < 0x10000 || uc >= 0x110000) uc = -1; + break; + default: + return -1; + } + + if ((uc & 0xFFFF) >= 0xFFFE) + return -1; + + *out = uc; + return length; +} + +size_t git_utf8_char_length(const char *_str, size_t str_len) +{ + const uint8_t *str = (const uint8_t *)_str; + size_t offset = 0, count = 0; + + while (offset < str_len) { + int length = utf8_charlen(str + offset, str_len - offset); + + if (length < 0) + length = 1; + + offset += length; + count++; + } + + return count; +} + +size_t git_utf8_valid_buf_length(const char *_str, size_t str_len) +{ + const uint8_t *str = (const uint8_t *)_str; + size_t offset = 0; + + while (offset < str_len) { + int length = utf8_charlen(str + offset, str_len - offset); + + if (length < 0) + break; + + offset += length; + } + + return offset; +} diff --git a/src/libgit2/utf8.h b/src/libgit2/utf8.h new file mode 100644 index 000000000..dff91b294 --- /dev/null +++ b/src/libgit2/utf8.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_utf8_h__ +#define INCLUDE_utf8_h__ + +#include "common.h" + +/* + * Iterate through an UTF-8 string, yielding one codepoint at a time. + * + * @param out pointer where to store the current codepoint + * @param str current position in the string + * @param str_len size left in the string + * @return length in bytes of the read codepoint; -1 if the codepoint was invalid + */ +extern int git_utf8_iterate(uint32_t *out, const char *str, size_t str_len); + +/** + * Returns the number of characters in the given string. + * + * This function will count invalid codepoints; if any given byte is + * not part of a valid UTF-8 codepoint, then it will be counted toward + * the length in characters. + * + * In other words: + * 0x24 (U+0024 "$") has length 1 + * 0xc2 0xa2 (U+00A2 "¢") has length 1 + * 0x24 0xc2 0xa2 (U+0024 U+00A2 "$¢") has length 2 + * 0xf0 0x90 0x8d 0x88 (U+10348 "𐍈") has length 1 + * 0x24 0xc0 0xc1 0x34 (U+0024 "4) has length 4 + * + * @param str string to scan + * @param str_len size of the string + * @return length in characters of the string + */ +extern size_t git_utf8_char_length(const char *str, size_t str_len); + +/** + * Iterate through an UTF-8 string and stops after finding any invalid UTF-8 + * codepoints. + * + * @param str string to scan + * @param str_len size of the string + * @return length in bytes of the string that contains valid data + */ +extern size_t git_utf8_valid_buf_length(const char *str, size_t str_len); + +#endif diff --git a/src/libgit2/util.c b/src/libgit2/util.c new file mode 100644 index 000000000..e06d4ca09 --- /dev/null +++ b/src/libgit2/util.c @@ -0,0 +1,819 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "util.h" + +#include "common.h" + +#ifdef GIT_WIN32 +# include "win32/utf-conv.h" +# include "win32/w32_buffer.h" + +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include + +# ifdef GIT_QSORT_S +# include +# endif +#endif + +#ifdef _MSC_VER +# include +#endif + +#if defined(hpux) || defined(__hpux) || defined(_hpux) +# include +#endif + +int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) +{ + const char *p; + int64_t n, nn, v; + int c, ovfl, neg, ndig; + + p = nptr; + neg = 0; + n = 0; + ndig = 0; + ovfl = 0; + + /* + * White space + */ + while (nptr_len && git__isspace(*p)) + p++, nptr_len--; + + if (!nptr_len) + goto Return; + + /* + * Sign + */ + if (*p == '-' || *p == '+') { + if (*p == '-') + neg = 1; + p++; + nptr_len--; + } + + if (!nptr_len) + goto Return; + + /* + * Automatically detect the base if none was given to us. + * Right now, we assume that a number starting with '0x' + * is hexadecimal and a number starting with '0' is + * octal. + */ + if (base == 0) { + if (*p != '0') + base = 10; + else if (nptr_len > 2 && (p[1] == 'x' || p[1] == 'X')) + base = 16; + else + base = 8; + } + + if (base < 0 || 36 < base) + goto Return; + + /* + * Skip prefix of '0x'-prefixed hexadecimal numbers. There is no + * need to do the same for '0'-prefixed octal numbers as a + * leading '0' does not have any impact. Also, if we skip a + * leading '0' in such a string, then we may end up with no + * digits left and produce an error later on which isn't one. + */ + if (base == 16 && nptr_len > 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + p += 2; + nptr_len -= 2; + } + + /* + * Non-empty sequence of digits + */ + for (; nptr_len > 0; p++,ndig++,nptr_len--) { + c = *p; + v = base; + if ('0'<=c && c<='9') + v = c - '0'; + else if ('a'<=c && c<='z') + v = c - 'a' + 10; + else if ('A'<=c && c<='Z') + v = c - 'A' + 10; + if (v >= base) + break; + v = neg ? -v : v; + if (git__multiply_int64_overflow(&nn, n, base) || git__add_int64_overflow(&n, nn, v)) { + ovfl = 1; + /* Keep on iterating until the end of this number */ + continue; + } + } + +Return: + if (ndig == 0) { + git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: not a number"); + return -1; + } + + if (endptr) + *endptr = p; + + if (ovfl) { + git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: overflow error"); + return -1; + } + + *result = n; + return 0; +} + +int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) +{ + const char *tmp_endptr; + int32_t tmp_int; + int64_t tmp_long; + int error; + + if ((error = git__strntol64(&tmp_long, nptr, nptr_len, &tmp_endptr, base)) < 0) + return error; + + tmp_int = tmp_long & 0xFFFFFFFF; + if (tmp_int != tmp_long) { + int len = (int)(tmp_endptr - nptr); + git_error_set(GIT_ERROR_INVALID, "failed to convert: '%.*s' is too large", len, nptr); + return -1; + } + + *result = tmp_int; + if (endptr) + *endptr = tmp_endptr; + + return error; +} + +int git__strcasecmp(const char *a, const char *b) +{ + while (*a && *b && git__tolower(*a) == git__tolower(*b)) + ++a, ++b; + return ((unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b)); +} + +int git__strcasesort_cmp(const char *a, const char *b) +{ + int cmp = 0; + + while (*a && *b) { + if (*a != *b) { + if (git__tolower(*a) != git__tolower(*b)) + break; + /* use case in sort order even if not in equivalence */ + if (!cmp) + cmp = (int)(*(const uint8_t *)a) - (int)(*(const uint8_t *)b); + } + + ++a, ++b; + } + + if (*a || *b) + return (unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b); + + return cmp; +} + +int git__strncasecmp(const char *a, const char *b, size_t sz) +{ + int al, bl; + + do { + al = (unsigned char)git__tolower(*a); + bl = (unsigned char)git__tolower(*b); + ++a, ++b; + } while (--sz && al && al == bl); + + return al - bl; +} + +void git__strntolower(char *str, size_t len) +{ + size_t i; + + for (i = 0; i < len; ++i) { + str[i] = (char)git__tolower(str[i]); + } +} + +void git__strtolower(char *str) +{ + git__strntolower(str, strlen(str)); +} + +GIT_INLINE(int) prefixcmp(const char *str, size_t str_n, const char *prefix, bool icase) +{ + int s, p; + + while (str_n--) { + s = (unsigned char)*str++; + p = (unsigned char)*prefix++; + + if (icase) { + s = git__tolower(s); + p = git__tolower(p); + } + + if (!p) + return 0; + + if (s != p) + return s - p; + } + + return (0 - *prefix); +} + +int git__prefixcmp(const char *str, const char *prefix) +{ + unsigned char s, p; + + while (1) { + p = *prefix++; + s = *str++; + + if (!p) + return 0; + + if (s != p) + return s - p; + } +} + +int git__prefixncmp(const char *str, size_t str_n, const char *prefix) +{ + return prefixcmp(str, str_n, prefix, false); +} + +int git__prefixcmp_icase(const char *str, const char *prefix) +{ + return prefixcmp(str, SIZE_MAX, prefix, true); +} + +int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix) +{ + return prefixcmp(str, str_n, prefix, true); +} + +int git__suffixcmp(const char *str, const char *suffix) +{ + size_t a = strlen(str); + size_t b = strlen(suffix); + if (a < b) + return -1; + return strcmp(str + (a - b), suffix); +} + +char *git__strtok(char **end, const char *sep) +{ + char *ptr = *end; + + while (*ptr && strchr(sep, *ptr)) + ++ptr; + + if (*ptr) { + char *start = ptr; + *end = start + 1; + + while (**end && !strchr(sep, **end)) + ++*end; + + if (**end) { + **end = '\0'; + ++*end; + } + + return start; + } + + return NULL; +} + +/* Similar to strtok, but does not collapse repeated tokens. */ +char *git__strsep(char **end, const char *sep) +{ + char *start = *end, *ptr = *end; + + while (*ptr && !strchr(sep, *ptr)) + ++ptr; + + if (*ptr) { + *end = ptr + 1; + *ptr = '\0'; + + return start; + } + + return NULL; +} + +size_t git__linenlen(const char *buffer, size_t buffer_len) +{ + char *nl = memchr(buffer, '\n', buffer_len); + return nl ? (size_t)(nl - buffer) + 1 : buffer_len; +} + +/* + * Adapted Not So Naive algorithm from http://www-igm.univ-mlv.fr/~lecroq/string/ + */ +const void * git__memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen) +{ + const char *h, *n; + size_t j, k, l; + + if (needlelen > haystacklen || !haystacklen || !needlelen) + return NULL; + + h = (const char *) haystack, + n = (const char *) needle; + + if (needlelen == 1) + return memchr(haystack, *n, haystacklen); + + if (n[0] == n[1]) { + k = 2; + l = 1; + } else { + k = 1; + l = 2; + } + + j = 0; + while (j <= haystacklen - needlelen) { + if (n[1] != h[j + 1]) { + j += k; + } else { + if (memcmp(n + 2, h + j + 2, needlelen - 2) == 0 && + n[0] == h[j]) + return h + j; + j += l; + } + } + + return NULL; +} + +void git__hexdump(const char *buffer, size_t len) +{ + static const size_t LINE_WIDTH = 16; + + size_t line_count, last_line, i, j; + const char *line; + + line_count = (len / LINE_WIDTH); + last_line = (len % LINE_WIDTH); + + for (i = 0; i < line_count; ++i) { + printf("%08" PRIxZ " ", (i * LINE_WIDTH)); + + line = buffer + (i * LINE_WIDTH); + for (j = 0; j < LINE_WIDTH; ++j, ++line) { + printf("%02x ", (unsigned char)*line & 0xFF); + + if (j == (LINE_WIDTH / 2)) + printf(" "); + } + + printf(" |"); + + line = buffer + (i * LINE_WIDTH); + for (j = 0; j < LINE_WIDTH; ++j, ++line) + printf("%c", (*line >= 32 && *line <= 126) ? *line : '.'); + + printf("|\n"); + } + + if (last_line > 0) { + printf("%08" PRIxZ " ", (line_count * LINE_WIDTH)); + + line = buffer + (line_count * LINE_WIDTH); + for (j = 0; j < last_line; ++j, ++line) { + printf("%02x ", (unsigned char)*line & 0xFF); + + if (j == (LINE_WIDTH / 2)) + printf(" "); + } + + if (j < (LINE_WIDTH / 2)) + printf(" "); + for (j = 0; j < (LINE_WIDTH - last_line); ++j) + printf(" "); + + printf(" |"); + + line = buffer + (line_count * LINE_WIDTH); + for (j = 0; j < last_line; ++j, ++line) + printf("%c", (*line >= 32 && *line <= 126) ? *line : '.'); + + printf("|\n"); + } + + printf("\n"); +} + +#ifdef GIT_LEGACY_HASH +uint32_t git__hash(const void *key, int len, unsigned int seed) +{ + const uint32_t m = 0x5bd1e995; + const int r = 24; + uint32_t h = seed ^ len; + + const unsigned char *data = (const unsigned char *)key; + + while(len >= 4) { + uint32_t k = *(uint32_t *)data; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + data += 4; + len -= 4; + } + + switch(len) { + case 3: h ^= data[2] << 16; + case 2: h ^= data[1] << 8; + case 1: h ^= data[0]; + h *= m; + }; + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} +#else +/* + Cross-platform version of Murmurhash3 + http://code.google.com/p/smhasher/wiki/MurmurHash3 + by Austin Appleby (aappleby@gmail.com) + + This code is on the public domain. +*/ +uint32_t git__hash(const void *key, int len, uint32_t seed) +{ + +#define MURMUR_BLOCK() {\ + k1 *= c1; \ + k1 = git__rotl(k1,11);\ + k1 *= c2;\ + h1 ^= k1;\ + h1 = h1*3 + 0x52dce729;\ + c1 = c1*5 + 0x7b7d159c;\ + c2 = c2*5 + 0x6bce6396;\ +} + + const uint8_t *data = (const uint8_t*)key; + const int nblocks = len / 4; + + const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4); + const uint8_t *tail = (const uint8_t *)(data + nblocks * 4); + + uint32_t h1 = 0x971e137b ^ seed; + uint32_t k1; + + uint32_t c1 = 0x95543787; + uint32_t c2 = 0x2ad7eb25; + + int i; + + for (i = -nblocks; i; i++) { + k1 = blocks[i]; + MURMUR_BLOCK(); + } + + k1 = 0; + + switch(len & 3) { + case 3: k1 ^= tail[2] << 16; + /* fall through */ + case 2: k1 ^= tail[1] << 8; + /* fall through */ + case 1: k1 ^= tail[0]; + MURMUR_BLOCK(); + } + + h1 ^= len; + h1 ^= h1 >> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >> 16; + + return h1; +} +#endif + +/** + * A modified `bsearch` from the BSD glibc. + * + * Copyright (c) 1990 Regents of the University of California. + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. [rescinded 22 July 1999] + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +int git__bsearch( + void **array, + size_t array_len, + const void *key, + int (*compare)(const void *, const void *), + size_t *position) +{ + size_t lim; + int cmp = -1; + void **part, **base = array; + + for (lim = array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1); + cmp = (*compare)(key, *part); + if (cmp == 0) { + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ + base = part + 1; + lim--; + } /* else take left partition */ + } + + if (position) + *position = (base - array); + + return (cmp == 0) ? 0 : GIT_ENOTFOUND; +} + +int git__bsearch_r( + void **array, + size_t array_len, + const void *key, + int (*compare_r)(const void *, const void *, void *), + void *payload, + size_t *position) +{ + size_t lim; + int cmp = -1; + void **part, **base = array; + + for (lim = array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1); + cmp = (*compare_r)(key, *part, payload); + if (cmp == 0) { + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ + base = part + 1; + lim--; + } /* else take left partition */ + } + + if (position) + *position = (base - array); + + return (cmp == 0) ? 0 : GIT_ENOTFOUND; +} + +/** + * A strcmp wrapper + * + * We don't want direct pointers to the CRT on Windows, we may + * get stdcall conflicts. + */ +int git__strcmp_cb(const void *a, const void *b) +{ + return strcmp((const char *)a, (const char *)b); +} + +int git__strcasecmp_cb(const void *a, const void *b) +{ + return strcasecmp((const char *)a, (const char *)b); +} + +int git__parse_bool(int *out, const char *value) +{ + /* A missing value means true */ + if (value == NULL || + !strcasecmp(value, "true") || + !strcasecmp(value, "yes") || + !strcasecmp(value, "on")) { + *out = 1; + return 0; + } + if (!strcasecmp(value, "false") || + !strcasecmp(value, "no") || + !strcasecmp(value, "off") || + value[0] == '\0') { + *out = 0; + return 0; + } + + return -1; +} + +size_t git__unescape(char *str) +{ + char *scan, *pos = str; + + if (!str) + return 0; + + for (scan = str; *scan; pos++, scan++) { + if (*scan == '\\' && *(scan + 1) != '\0') + scan++; /* skip '\' but include next char */ + if (pos != scan) + *pos = *scan; + } + + if (pos != scan) { + *pos = '\0'; + } + + return (pos - str); +} + +#if defined(GIT_QSORT_S) || defined(GIT_QSORT_R_BSD) +typedef struct { + git__sort_r_cmp cmp; + void *payload; +} git__qsort_r_glue; + +static int GIT_LIBGIT2_CALL git__qsort_r_glue_cmp( + void *payload, const void *a, const void *b) +{ + git__qsort_r_glue *glue = payload; + return glue->cmp(a, b, glue->payload); +} +#endif + + +#if !defined(GIT_QSORT_R_BSD) && \ + !defined(GIT_QSORT_R_GNU) && \ + !defined(GIT_QSORT_S) +static void swap(uint8_t *a, uint8_t *b, size_t elsize) +{ + char tmp[256]; + + while (elsize) { + size_t n = elsize < sizeof(tmp) ? elsize : sizeof(tmp); + memcpy(tmp, a + elsize - n, n); + memcpy(a + elsize - n, b + elsize - n, n); + memcpy(b + elsize - n, tmp, n); + elsize -= n; + } +} + +static void insertsort( + void *els, size_t nel, size_t elsize, + git__sort_r_cmp cmp, void *payload) +{ + uint8_t *base = els; + uint8_t *end = base + nel * elsize; + uint8_t *i, *j; + + for (i = base + elsize; i < end; i += elsize) + for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize) + swap(j, j - elsize, elsize); +} +#endif + +void git__qsort_r( + void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload) +{ +#if defined(GIT_QSORT_R_BSD) + git__qsort_r_glue glue = { cmp, payload }; + qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp); +#elif defined(GIT_QSORT_R_GNU) + qsort_r(els, nel, elsize, cmp, payload); +#elif defined(GIT_QSORT_S) + git__qsort_r_glue glue = { cmp, payload }; + qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue); +#else + insertsort(els, nel, elsize, cmp, payload); +#endif +} + +#ifdef GIT_WIN32 +int git__getenv(git_str *out, const char *name) +{ + wchar_t *wide_name = NULL, *wide_value = NULL; + DWORD value_len; + int error = -1; + + git_str_clear(out); + + if (git__utf8_to_16_alloc(&wide_name, name) < 0) + return -1; + + if ((value_len = GetEnvironmentVariableW(wide_name, NULL, 0)) > 0) { + wide_value = git__malloc(value_len * sizeof(wchar_t)); + GIT_ERROR_CHECK_ALLOC(wide_value); + + value_len = GetEnvironmentVariableW(wide_name, wide_value, value_len); + } + + if (value_len) + error = git_str_put_w(out, wide_value, value_len); + else if (GetLastError() == ERROR_SUCCESS || GetLastError() == ERROR_ENVVAR_NOT_FOUND) + error = GIT_ENOTFOUND; + else + git_error_set(GIT_ERROR_OS, "could not read environment variable '%s'", name); + + git__free(wide_name); + git__free(wide_value); + return error; +} +#else +int git__getenv(git_str *out, const char *name) +{ + const char *val = getenv(name); + + git_str_clear(out); + + if (!val) + return GIT_ENOTFOUND; + + return git_str_puts(out, val); +} +#endif + +/* + * By doing this in two steps we can at least get + * the function to be somewhat coherent, even + * with this disgusting nest of #ifdefs. + */ +#ifndef _SC_NPROCESSORS_ONLN +# ifdef _SC_NPROC_ONLN +# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN +# elif defined _SC_CRAY_NCPU +# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU +# endif +#endif + +int git__online_cpus(void) +{ +#ifdef _SC_NPROCESSORS_ONLN + long ncpus; +#endif + +#ifdef _WIN32 + SYSTEM_INFO info; + GetSystemInfo(&info); + + if ((int)info.dwNumberOfProcessors > 0) + return (int)info.dwNumberOfProcessors; +#elif defined(hpux) || defined(__hpux) || defined(_hpux) + struct pst_dynamic psd; + + if (!pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0)) + return (int)psd.psd_proc_cnt; +#endif + +#ifdef _SC_NPROCESSORS_ONLN + if ((ncpus = (long)sysconf(_SC_NPROCESSORS_ONLN)) > 0) + return (int)ncpus; +#endif + + return 1; +} diff --git a/src/libgit2/util.h b/src/libgit2/util.h new file mode 100644 index 000000000..141779ade --- /dev/null +++ b/src/libgit2/util.h @@ -0,0 +1,387 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_util_h__ +#define INCLUDE_util_h__ + +#ifndef GIT_WIN32 +# include +#endif + +#include "str.h" +#include "common.h" +#include "strnlen.h" +#include "thread.h" + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) +#define bitsizeof(x) (CHAR_BIT * sizeof(x)) +#define MSB(x, bits) ((x) & (~UINT64_C(0) << (bitsizeof(x) - (bits)))) +#ifndef min +# define min(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef max +# define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#if defined(__GNUC__) +# define GIT_CONTAINER_OF(ptr, type, member) \ + __builtin_choose_expr( \ + __builtin_offsetof(type, member) == 0 && \ + __builtin_types_compatible_p(__typeof__(&((type *) 0)->member), __typeof__(ptr)), \ + ((type *) (ptr)), \ + (void)0) +#else +# define GIT_CONTAINER_OF(ptr, type, member) (type *)(ptr) +#endif + +/** + * Return the length of a constant string. + * We are aware that `strlen` performs the same task and is usually + * optimized away by the compiler, whilst being safer because it returns + * valid values when passed a pointer instead of a constant string; however + * this macro will transparently work with wide-char and single-char strings. + */ +#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1) + +#define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \ + ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2))) + +#define CASESELECT(IGNORE_CASE, ICASE, CASE) \ + ((IGNORE_CASE) ? (ICASE) : (CASE)) + +extern int git__prefixcmp(const char *str, const char *prefix); +extern int git__prefixcmp_icase(const char *str, const char *prefix); +extern int git__prefixncmp(const char *str, size_t str_n, const char *prefix); +extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix); +extern int git__suffixcmp(const char *str, const char *suffix); + +GIT_INLINE(int) git__signum(int val) +{ + return ((val > 0) - (val < 0)); +} + +extern int git__strntol32(int32_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); +extern int git__strntol64(int64_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); + + +extern void git__hexdump(const char *buffer, size_t n); +extern uint32_t git__hash(const void *key, int len, uint32_t seed); + +/* 32-bit cross-platform rotl */ +#ifdef _MSC_VER /* use built-in method in MSVC */ +# define git__rotl(v, s) (uint32_t)_rotl(v, s) +#else /* use bitops in GCC; with o2 this gets optimized to a rotl instruction */ +# define git__rotl(v, s) (uint32_t)(((uint32_t)(v) << (s)) | ((uint32_t)(v) >> (32 - (s)))) +#endif + +extern char *git__strtok(char **end, const char *sep); +extern char *git__strsep(char **end, const char *sep); + +extern void git__strntolower(char *str, size_t len); +extern void git__strtolower(char *str); + +#ifdef GIT_WIN32 +GIT_INLINE(int) git__tolower(int c) +{ + return (c >= 'A' && c <= 'Z') ? (c + 32) : c; +} +#else +# define git__tolower(a) tolower(a) +#endif + +extern size_t git__linenlen(const char *buffer, size_t buffer_len); + +GIT_INLINE(const char *) git__next_line(const char *s) +{ + while (*s && *s != '\n') s++; + while (*s == '\n' || *s == '\r') s++; + return s; +} + +GIT_INLINE(const void *) git__memrchr(const void *s, int c, size_t n) +{ + const unsigned char *cp; + + if (n != 0) { + cp = (unsigned char *)s + n; + do { + if (*(--cp) == (unsigned char)c) + return cp; + } while (--n != 0); + } + + return NULL; +} + +extern const void * git__memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); + +typedef int (*git__tsort_cmp)(const void *a, const void *b); + +extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp); + +typedef int (*git__sort_r_cmp)(const void *a, const void *b, void *payload); + +extern void git__tsort_r( + void **dst, size_t size, git__sort_r_cmp cmp, void *payload); + +extern void git__qsort_r( + void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload); + +/** + * @param position If non-NULL, this will be set to the position where the + * element is or would be inserted if not found. + * @return 0 if found; GIT_ENOTFOUND if not found + */ +extern int git__bsearch( + void **array, + size_t array_len, + const void *key, + int (*compare)(const void *key, const void *element), + size_t *position); + +extern int git__bsearch_r( + void **array, + size_t array_len, + const void *key, + int (*compare_r)(const void *key, const void *element, void *payload), + void *payload, + size_t *position); + +#define git__strcmp strcmp +#define git__strncmp strncmp + +extern int git__strcmp_cb(const void *a, const void *b); +extern int git__strcasecmp_cb(const void *a, const void *b); + +extern int git__strcasecmp(const char *a, const char *b); +extern int git__strncasecmp(const char *a, const char *b, size_t sz); + +extern int git__strcasesort_cmp(const char *a, const char *b); + +/* + * Compare some NUL-terminated `a` to a possibly non-NUL terminated + * `b` of length `b_len`; like `strncmp` but ensuring that + * `strlen(a) == b_len` as well. + */ +GIT_INLINE(int) git__strlcmp(const char *a, const char *b, size_t b_len) +{ + int cmp = strncmp(a, b, b_len); + return cmp ? cmp : (int)a[b_len]; +} + +typedef struct { + git_atomic32 refcount; + void *owner; +} git_refcount; + +typedef void (*git_refcount_freeptr)(void *r); + +#define GIT_REFCOUNT_INC(r) { \ + git_atomic32_inc(&(r)->rc.refcount); \ +} + +#define GIT_REFCOUNT_DEC(_r, do_free) { \ + git_refcount *r = &(_r)->rc; \ + int val = git_atomic32_dec(&r->refcount); \ + if (val <= 0 && r->owner == NULL) { do_free(_r); } \ +} + +#define GIT_REFCOUNT_OWN(r, o) { \ + (void)git_atomic_swap((r)->rc.owner, o); \ +} + +#define GIT_REFCOUNT_OWNER(r) git_atomic_load((r)->rc.owner) + +#define GIT_REFCOUNT_VAL(r) git_atomic32_get((r)->rc.refcount) + + +static signed char from_hex[] = { +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */ +-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */ +-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */ +}; + +GIT_INLINE(int) git__fromhex(char h) +{ + return from_hex[(unsigned char) h]; +} + +GIT_INLINE(int) git__ishex(const char *str) +{ + unsigned i; + for (i=0; str[i] != '\0'; i++) + if (git__fromhex(str[i]) < 0) + return 0; + return 1; +} + +GIT_INLINE(size_t) git__size_t_bitmask(size_t v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + + return v; +} + +GIT_INLINE(size_t) git__size_t_powerof2(size_t v) +{ + return git__size_t_bitmask(v) + 1; +} + +GIT_INLINE(bool) git__isupper(int c) +{ + return (c >= 'A' && c <= 'Z'); +} + +GIT_INLINE(bool) git__isalpha(int c) +{ + return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); +} + +GIT_INLINE(bool) git__isdigit(int c) +{ + return (c >= '0' && c <= '9'); +} + +GIT_INLINE(bool) git__isspace(int c) +{ + return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); +} + +GIT_INLINE(bool) git__isspace_nonlf(int c) +{ + return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v'); +} + +GIT_INLINE(bool) git__iswildcard(int c) +{ + return (c == '*' || c == '?' || c == '['); +} + +GIT_INLINE(bool) git__isxdigit(int c) +{ + return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); +} + +/* + * Parse a string value as a boolean, just like Core Git does. + * + * Valid values for true are: 'true', 'yes', 'on' + * Valid values for false are: 'false', 'no', 'off' + */ +extern int git__parse_bool(int *out, const char *value); + +/* + * Unescapes a string in-place. + * + * Edge cases behavior: + * - "jackie\" -> "jacky\" + * - "chan\\" -> "chan\" + */ +extern size_t git__unescape(char *str); + +/* + * Safely zero-out memory, making sure that the compiler + * doesn't optimize away the operation. + */ +GIT_INLINE(void) git__memzero(void *data, size_t size) +{ +#ifdef _MSC_VER + SecureZeroMemory((PVOID)data, size); +#else + volatile uint8_t *scan = (volatile uint8_t *)data; + + while (size--) + *scan++ = 0x0; +#endif +} + +#ifdef GIT_WIN32 + +GIT_INLINE(double) git__timer(void) +{ + /* GetTickCount64 returns the number of milliseconds that have + * elapsed since the system was started. */ + return (double) GetTickCount64() / (double) 1000; +} + +#elif __APPLE__ + +#include + +GIT_INLINE(double) git__timer(void) +{ + uint64_t time = mach_absolute_time(); + static double scaling_factor = 0; + + if (scaling_factor == 0) { + mach_timebase_info_data_t info; + (void)mach_timebase_info(&info); + scaling_factor = (double)info.numer / (double)info.denom; + } + + return (double)time * scaling_factor / 1.0E9; +} + +#elif defined(__amigaos4__) + +#include + +GIT_INLINE(double) git__timer(void) +{ + struct TimeVal tv; + ITimer->GetUpTime(&tv); + return (double)tv.Seconds + (double)tv.Microseconds / 1.0E6; +} + +#else + +#include + +GIT_INLINE(double) git__timer(void) +{ + struct timeval tv; + +#ifdef CLOCK_MONOTONIC + struct timespec tp; + if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) + return (double) tp.tv_sec + (double) tp.tv_nsec / 1.0E9; +#endif + + /* Fall back to using gettimeofday */ + gettimeofday(&tv, NULL); + return (double)tv.tv_sec + (double)tv.tv_usec / 1.0E6; +} + +#endif + +extern int git__getenv(git_str *out, const char *name); + +extern int git__online_cpus(void); + +GIT_INLINE(int) git__noop(void) { return 0; } + +#include "alloc.h" + +#endif diff --git a/src/libgit2/util/platform.h.in b/src/libgit2/util/platform.h.in new file mode 100644 index 000000000..e511fe331 --- /dev/null +++ b/src/libgit2/util/platform.h.in @@ -0,0 +1,34 @@ +#ifndef INCLUDE_platform_h__ +#define INCLUDE_platform_h__ + +#cmakedefine GIT_DEBUG_POOL 1 +#cmakedefine GIT_DEBUG_STRICT_ALLOC 1 +#cmakedefine GIT_DEBUG_STRICT_OPEN 1 + +#cmakedefine GIT_WIN32_LEAKCHECK 1 + +#cmakedefine GIT_ARCH_64 1 +#cmakedefine GIT_ARCH_32 1 + +#cmakedefine GIT_USE_STAT_MTIM 1 +#cmakedefine GIT_USE_STAT_MTIMESPEC 1 +#cmakedefine GIT_USE_STAT_MTIME_NSEC 1 +#cmakedefine GIT_USE_FUTIMENS 1 + +#cmakedefine GIT_USE_QSORT_R_BSD 1 +#cmakedefine GIT_USE_QSORT_R_GNU 1 +#cmakedefine GIT_USE_QSORT_S 1 + +#cmakedefine GIT_REGEX_REGCOMP_L 1 +#cmakedefine GIT_REGEX_REGCOMP 1 +#cmakedefine GIT_REGEX_PCRE 1 +#cmakedefine GIT_REGEX_PCRE2 1 +#cmakedefine GIT_REGEX_BUILTIN 1 + +#cmakedefine GIT_SHA1_COLLISIONDETECT 1 +#cmakedefine GIT_SHA1_WIN32 1 +#cmakedefine GIT_SHA1_COMMON_CRYPTO 1 +#cmakedefine GIT_SHA1_OPENSSL 1 +#cmakedefine GIT_SHA1_MBEDTLS 1 + +#endif diff --git a/src/libgit2/varint.c b/src/libgit2/varint.c new file mode 100644 index 000000000..9ffc1d744 --- /dev/null +++ b/src/libgit2/varint.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "varint.h" + +uintmax_t git_decode_varint(const unsigned char *bufp, size_t *varint_len) +{ + const unsigned char *buf = bufp; + unsigned char c = *buf++; + uintmax_t val = c & 127; + while (c & 128) { + val += 1; + if (!val || MSB(val, 7)) { + /* This is not a valid varint_len, so it signals + the error */ + *varint_len = 0; + return 0; /* overflow */ + } + c = *buf++; + val = (val << 7) + (c & 127); + } + *varint_len = buf - bufp; + return val; +} + +int git_encode_varint(unsigned char *buf, size_t bufsize, uintmax_t value) +{ + unsigned char varint[16]; + unsigned pos = sizeof(varint) - 1; + varint[pos] = value & 127; + while (value >>= 7) + varint[--pos] = 128 | (--value & 127); + if (buf) { + if (bufsize < (sizeof(varint) - pos)) + return -1; + memcpy(buf, varint + pos, sizeof(varint) - pos); + } + return sizeof(varint) - pos; +} diff --git a/src/libgit2/varint.h b/src/libgit2/varint.h new file mode 100644 index 000000000..652e22486 --- /dev/null +++ b/src/libgit2/varint.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_varint_h__ +#define INCLUDE_varint_h__ + +#include "common.h" + +#include + +extern int git_encode_varint(unsigned char *, size_t, uintmax_t); +extern uintmax_t git_decode_varint(const unsigned char *, size_t *); + +#endif diff --git a/src/libgit2/vector.c b/src/libgit2/vector.c new file mode 100644 index 000000000..4a4bc8c0e --- /dev/null +++ b/src/libgit2/vector.c @@ -0,0 +1,431 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "vector.h" + +#include "integer.h" + +/* In elements, not bytes */ +#define MIN_ALLOCSIZE 8 + +GIT_INLINE(size_t) compute_new_size(git_vector *v) +{ + size_t new_size = v->_alloc_size; + + /* Use a resize factor of 1.5, which is quick to compute using integer + * instructions and less than the golden ratio (1.618...) */ + if (new_size < MIN_ALLOCSIZE) + new_size = MIN_ALLOCSIZE; + else if (new_size <= (SIZE_MAX / 3) * 2) + new_size += new_size / 2; + else + new_size = SIZE_MAX; + + return new_size; +} + +GIT_INLINE(int) resize_vector(git_vector *v, size_t new_size) +{ + void *new_contents; + + if (new_size == 0) + return 0; + + new_contents = git__reallocarray(v->contents, new_size, sizeof(void *)); + GIT_ERROR_CHECK_ALLOC(new_contents); + + v->_alloc_size = new_size; + v->contents = new_contents; + + return 0; +} + +int git_vector_size_hint(git_vector *v, size_t size_hint) +{ + if (v->_alloc_size >= size_hint) + return 0; + return resize_vector(v, size_hint); +} + +int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp) +{ + GIT_ASSERT_ARG(v); + GIT_ASSERT_ARG(src); + + v->_alloc_size = 0; + v->contents = NULL; + v->_cmp = cmp ? cmp : src->_cmp; + v->length = src->length; + v->flags = src->flags; + if (cmp != src->_cmp) + git_vector_set_sorted(v, 0); + + if (src->length) { + size_t bytes; + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&bytes, src->length, sizeof(void *)); + v->contents = git__malloc(bytes); + GIT_ERROR_CHECK_ALLOC(v->contents); + v->_alloc_size = src->length; + memcpy(v->contents, src->contents, bytes); + } + + return 0; +} + +void git_vector_free(git_vector *v) +{ + if (!v) + return; + + git__free(v->contents); + v->contents = NULL; + + v->length = 0; + v->_alloc_size = 0; +} + +void git_vector_free_deep(git_vector *v) +{ + size_t i; + + if (!v) + return; + + for (i = 0; i < v->length; ++i) { + git__free(v->contents[i]); + v->contents[i] = NULL; + } + + git_vector_free(v); +} + +int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp) +{ + GIT_ASSERT_ARG(v); + + v->_alloc_size = 0; + v->_cmp = cmp; + v->length = 0; + v->flags = GIT_VECTOR_SORTED; + v->contents = NULL; + + return resize_vector(v, max(initial_size, MIN_ALLOCSIZE)); +} + +void **git_vector_detach(size_t *size, size_t *asize, git_vector *v) +{ + void **data = v->contents; + + if (size) + *size = v->length; + if (asize) + *asize = v->_alloc_size; + + v->_alloc_size = 0; + v->length = 0; + v->contents = NULL; + + return data; +} + +int git_vector_insert(git_vector *v, void *element) +{ + GIT_ASSERT_ARG(v); + + if (v->length >= v->_alloc_size && + resize_vector(v, compute_new_size(v)) < 0) + return -1; + + v->contents[v->length++] = element; + + git_vector_set_sorted(v, v->length <= 1); + + return 0; +} + +int git_vector_insert_sorted( + git_vector *v, void *element, int (*on_dup)(void **old, void *new)) +{ + int result; + size_t pos; + + GIT_ASSERT_ARG(v); + GIT_ASSERT(v->_cmp); + + if (!git_vector_is_sorted(v)) + git_vector_sort(v); + + if (v->length >= v->_alloc_size && + resize_vector(v, compute_new_size(v)) < 0) + return -1; + + /* If we find the element and have a duplicate handler callback, + * invoke it. If it returns non-zero, then cancel insert, otherwise + * proceed with normal insert. + */ + if (!git__bsearch(v->contents, v->length, element, v->_cmp, &pos) && + on_dup && (result = on_dup(&v->contents[pos], element)) < 0) + return result; + + /* shift elements to the right */ + if (pos < v->length) + memmove(v->contents + pos + 1, v->contents + pos, + (v->length - pos) * sizeof(void *)); + + v->contents[pos] = element; + v->length++; + + return 0; +} + +void git_vector_sort(git_vector *v) +{ + if (git_vector_is_sorted(v) || !v->_cmp) + return; + + if (v->length > 1) + git__tsort(v->contents, v->length, v->_cmp); + git_vector_set_sorted(v, 1); +} + +int git_vector_bsearch2( + size_t *at_pos, + git_vector *v, + git_vector_cmp key_lookup, + const void *key) +{ + GIT_ASSERT_ARG(v); + GIT_ASSERT_ARG(key); + GIT_ASSERT(key_lookup); + + /* need comparison function to sort the vector */ + if (!v->_cmp) + return -1; + + git_vector_sort(v); + + return git__bsearch(v->contents, v->length, key, key_lookup, at_pos); +} + +int git_vector_search2( + size_t *at_pos, const git_vector *v, git_vector_cmp key_lookup, const void *key) +{ + size_t i; + + GIT_ASSERT_ARG(v); + GIT_ASSERT_ARG(key); + GIT_ASSERT(key_lookup); + + for (i = 0; i < v->length; ++i) { + if (key_lookup(key, v->contents[i]) == 0) { + if (at_pos) + *at_pos = i; + + return 0; + } + } + + return GIT_ENOTFOUND; +} + +static int strict_comparison(const void *a, const void *b) +{ + return (a == b) ? 0 : -1; +} + +int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry) +{ + return git_vector_search2(at_pos, v, v->_cmp ? v->_cmp : strict_comparison, entry); +} + +int git_vector_remove(git_vector *v, size_t idx) +{ + size_t shift_count; + + GIT_ASSERT_ARG(v); + + if (idx >= v->length) + return GIT_ENOTFOUND; + + shift_count = v->length - idx - 1; + + if (shift_count) + memmove(&v->contents[idx], &v->contents[idx + 1], + shift_count * sizeof(void *)); + + v->length--; + return 0; +} + +void git_vector_pop(git_vector *v) +{ + if (v->length > 0) + v->length--; +} + +void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)) +{ + git_vector_cmp cmp; + size_t i, j; + + if (v->length <= 1) + return; + + git_vector_sort(v); + cmp = v->_cmp ? v->_cmp : strict_comparison; + + for (i = 0, j = 1 ; j < v->length; ++j) + if (!cmp(v->contents[i], v->contents[j])) { + if (git_free_cb) + git_free_cb(v->contents[i]); + + v->contents[i] = v->contents[j]; + } else + v->contents[++i] = v->contents[j]; + + v->length -= j - i - 1; +} + +void git_vector_remove_matching( + git_vector *v, + int (*match)(const git_vector *v, size_t idx, void *payload), + void *payload) +{ + size_t i, j; + + for (i = 0, j = 0; j < v->length; ++j) { + v->contents[i] = v->contents[j]; + + if (!match(v, i, payload)) + i++; + } + + v->length = i; +} + +void git_vector_clear(git_vector *v) +{ + v->length = 0; + git_vector_set_sorted(v, 1); +} + +void git_vector_swap(git_vector *a, git_vector *b) +{ + git_vector t; + + if (a != b) { + memcpy(&t, a, sizeof(t)); + memcpy(a, b, sizeof(t)); + memcpy(b, &t, sizeof(t)); + } +} + +int git_vector_resize_to(git_vector *v, size_t new_length) +{ + if (new_length > v->_alloc_size && + resize_vector(v, new_length) < 0) + return -1; + + if (new_length > v->length) + memset(&v->contents[v->length], 0, + sizeof(void *) * (new_length - v->length)); + + v->length = new_length; + + return 0; +} + +int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len) +{ + size_t new_length; + + GIT_ASSERT_ARG(insert_len > 0); + GIT_ASSERT_ARG(idx <= v->length); + + GIT_ERROR_CHECK_ALLOC_ADD(&new_length, v->length, insert_len); + + if (new_length > v->_alloc_size && resize_vector(v, new_length) < 0) + return -1; + + memmove(&v->contents[idx + insert_len], &v->contents[idx], + sizeof(void *) * (v->length - idx)); + memset(&v->contents[idx], 0, sizeof(void *) * insert_len); + + v->length = new_length; + return 0; +} + +int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len) +{ + size_t new_length = v->length - remove_len; + size_t end_idx = 0; + + GIT_ASSERT_ARG(remove_len > 0); + + if (git__add_sizet_overflow(&end_idx, idx, remove_len)) + GIT_ASSERT(0); + + GIT_ASSERT(end_idx <= v->length); + + if (end_idx < v->length) + memmove(&v->contents[idx], &v->contents[end_idx], + sizeof(void *) * (v->length - end_idx)); + + memset(&v->contents[new_length], 0, sizeof(void *) * remove_len); + + v->length = new_length; + return 0; +} + +int git_vector_set(void **old, git_vector *v, size_t position, void *value) +{ + if (position + 1 > v->length) { + if (git_vector_resize_to(v, position + 1) < 0) + return -1; + } + + if (old != NULL) + *old = v->contents[position]; + + v->contents[position] = value; + + return 0; +} + +int git_vector_verify_sorted(const git_vector *v) +{ + size_t i; + + if (!git_vector_is_sorted(v)) + return -1; + + for (i = 1; i < v->length; ++i) { + if (v->_cmp(v->contents[i - 1], v->contents[i]) > 0) + return -1; + } + + return 0; +} + +void git_vector_reverse(git_vector *v) +{ + size_t a, b; + + if (v->length == 0) + return; + + a = 0; + b = v->length - 1; + + while (a < b) { + void *tmp = v->contents[a]; + v->contents[a] = v->contents[b]; + v->contents[b] = tmp; + a++; + b--; + } +} diff --git a/src/libgit2/vector.h b/src/libgit2/vector.h new file mode 100644 index 000000000..ae3c79a4c --- /dev/null +++ b/src/libgit2/vector.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_vector_h__ +#define INCLUDE_vector_h__ + +#include "common.h" + +typedef int (*git_vector_cmp)(const void *, const void *); + +enum { + GIT_VECTOR_SORTED = (1u << 0), + GIT_VECTOR_FLAG_MAX = (1u << 1) +}; + +typedef struct git_vector { + size_t _alloc_size; + git_vector_cmp _cmp; + void **contents; + size_t length; + uint32_t flags; +} git_vector; + +#define GIT_VECTOR_INIT {0} + +GIT_WARN_UNUSED_RESULT int git_vector_init( + git_vector *v, size_t initial_size, git_vector_cmp cmp); +void git_vector_free(git_vector *v); +void git_vector_free_deep(git_vector *v); /* free each entry and self */ +void git_vector_clear(git_vector *v); +GIT_WARN_UNUSED_RESULT int git_vector_dup( + git_vector *v, const git_vector *src, git_vector_cmp cmp); +void git_vector_swap(git_vector *a, git_vector *b); +int git_vector_size_hint(git_vector *v, size_t size_hint); + +void **git_vector_detach(size_t *size, size_t *asize, git_vector *v); + +void git_vector_sort(git_vector *v); + +/** Linear search for matching entry using internal comparison function */ +int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry); + +/** Linear search for matching entry using explicit comparison function */ +int git_vector_search2(size_t *at_pos, const git_vector *v, git_vector_cmp cmp, const void *key); + +/** + * Binary search for matching entry using explicit comparison function that + * returns position where item would go if not found. + */ +int git_vector_bsearch2( + size_t *at_pos, git_vector *v, git_vector_cmp cmp, const void *key); + +/** Binary search for matching entry using internal comparison function */ +GIT_INLINE(int) git_vector_bsearch(size_t *at_pos, git_vector *v, const void *key) +{ + return git_vector_bsearch2(at_pos, v, v->_cmp, key); +} + +GIT_INLINE(void *) git_vector_get(const git_vector *v, size_t position) +{ + return (position < v->length) ? v->contents[position] : NULL; +} + +#define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL) + +GIT_INLINE(size_t) git_vector_length(const git_vector *v) +{ + return v->length; +} + +GIT_INLINE(void *) git_vector_last(const git_vector *v) +{ + return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL; +} + +#define git_vector_foreach(v, iter, elem) \ + for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ ) + +#define git_vector_rforeach(v, iter, elem) \ + for ((iter) = (v)->length - 1; (iter) < SIZE_MAX && ((elem) = (v)->contents[(iter)], 1); (iter)-- ) + +int git_vector_insert(git_vector *v, void *element); +int git_vector_insert_sorted(git_vector *v, void *element, + int (*on_dup)(void **old, void *new)); +int git_vector_remove(git_vector *v, size_t idx); +void git_vector_pop(git_vector *v); +void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)); + +void git_vector_remove_matching( + git_vector *v, + int (*match)(const git_vector *v, size_t idx, void *payload), + void *payload); + +int git_vector_resize_to(git_vector *v, size_t new_length); +int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len); +int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len); + +int git_vector_set(void **old, git_vector *v, size_t position, void *value); + +/** Check if vector is sorted */ +#define git_vector_is_sorted(V) (((V)->flags & GIT_VECTOR_SORTED) != 0) + +/** Directly set sorted state of vector */ +#define git_vector_set_sorted(V,S) do { \ + (V)->flags = (S) ? ((V)->flags | GIT_VECTOR_SORTED) : \ + ((V)->flags & ~GIT_VECTOR_SORTED); } while (0) + +/** Set the comparison function used for sorting the vector */ +GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp) +{ + if (cmp != v->_cmp) { + v->_cmp = cmp; + git_vector_set_sorted(v, 0); + } +} + +/* Just use this in tests, not for realz. returns -1 if not sorted */ +int git_vector_verify_sorted(const git_vector *v); + +/** + * Reverse the vector in-place. + */ +void git_vector_reverse(git_vector *v); + +#endif diff --git a/src/libgit2/wildmatch.c b/src/libgit2/wildmatch.c new file mode 100644 index 000000000..a894e4841 --- /dev/null +++ b/src/libgit2/wildmatch.c @@ -0,0 +1,320 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + * + * Do shell-style pattern matching for ?, \, [], and * characters. + * It is 8bit clean. + * + * Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. + * Rich $alz is now . + * + * Modified by Wayne Davison to special-case '/' matching, to make '**' + * work differently than '*', and to fix the character-class code. + * + * Imported from git.git. + */ + +#include "wildmatch.h" + +#define GIT_SPACE 0x01 +#define GIT_DIGIT 0x02 +#define GIT_ALPHA 0x04 +#define GIT_GLOB_SPECIAL 0x08 +#define GIT_REGEX_SPECIAL 0x10 +#define GIT_PATHSPEC_MAGIC 0x20 +#define GIT_CNTRL 0x40 +#define GIT_PUNCT 0x80 + +enum { + S = GIT_SPACE, + A = GIT_ALPHA, + D = GIT_DIGIT, + G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ + R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */ + P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */ + X = GIT_CNTRL, + U = GIT_PUNCT, + Z = GIT_CNTRL | GIT_SPACE +}; + +static const unsigned char sane_ctype[256] = { + X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X, /* 0.. 15 */ + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 16.. 31 */ + S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */ + D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */ + P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */ + A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P, /* 80.. 95 */ + P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */ + A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X, /* 112..127 */ + /* Nothing in the 128.. range */ +}; + +#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0) +#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL) + +typedef unsigned char uchar; + +/* What character marks an inverted character class? */ +#define NEGATE_CLASS '!' +#define NEGATE_CLASS2 '^' + +#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \ + && *(class) == *(litmatch) \ + && strncmp((char*)class, litmatch, len) == 0) + +#if defined STDC_HEADERS || !defined isascii +# define ISASCII(c) 1 +#else +# define ISASCII(c) isascii(c) +#endif + +#ifdef isblank +# define ISBLANK(c) (ISASCII(c) && isblank(c)) +#else +# define ISBLANK(c) ((c) == ' ' || (c) == '\t') +#endif + +#ifdef isgraph +# define ISGRAPH(c) (ISASCII(c) && isgraph(c)) +#else +# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c)) +#endif + +#define ISPRINT(c) (ISASCII(c) && isprint(c)) +#define ISDIGIT(c) (ISASCII(c) && isdigit(c)) +#define ISALNUM(c) (ISASCII(c) && isalnum(c)) +#define ISALPHA(c) (ISASCII(c) && isalpha(c)) +#define ISCNTRL(c) (ISASCII(c) && iscntrl(c)) +#define ISLOWER(c) (ISASCII(c) && islower(c)) +#define ISPUNCT(c) (ISASCII(c) && ispunct(c)) +#define ISSPACE(c) (ISASCII(c) && isspace(c)) +#define ISUPPER(c) (ISASCII(c) && isupper(c)) +#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c)) + +/* Match pattern "p" against "text" */ +static int dowild(const uchar *p, const uchar *text, unsigned int flags) +{ + uchar p_ch; + const uchar *pattern = p; + + for ( ; (p_ch = *p) != '\0'; text++, p++) { + int matched, match_slash, negated; + uchar t_ch, prev_ch; + if ((t_ch = *text) == '\0' && p_ch != '*') + return WM_ABORT_ALL; + if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) + t_ch = tolower(t_ch); + if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) + p_ch = tolower(p_ch); + switch (p_ch) { + case '\\': + /* Literal match with following character. Note that the test + * in "default" handles the p[1] == '\0' failure case. */ + p_ch = *++p; + /* FALLTHROUGH */ + default: + if (t_ch != p_ch) + return WM_NOMATCH; + continue; + case '?': + /* Match anything but '/'. */ + if ((flags & WM_PATHNAME) && t_ch == '/') + return WM_NOMATCH; + continue; + case '*': + if (*++p == '*') { + const uchar *prev_p = p - 2; + while (*++p == '*') {} + if (!(flags & WM_PATHNAME)) + /* without WM_PATHNAME, '*' == '**' */ + match_slash = 1; + else if ((prev_p < pattern || *prev_p == '/') && + (*p == '\0' || *p == '/' || + (p[0] == '\\' && p[1] == '/'))) { + /* + * Assuming we already match 'foo/' and are at + * , just assume it matches + * nothing and go ahead match the rest of the + * pattern with the remaining string. This + * helps make foo/<*><*>/bar (<> because + * otherwise it breaks C comment syntax) match + * both foo/bar and foo/a/bar. + */ + if (p[0] == '/' && + dowild(p + 1, text, flags) == WM_MATCH) + return WM_MATCH; + match_slash = 1; + } else /* WM_PATHNAME is set */ + match_slash = 0; + } else + /* without WM_PATHNAME, '*' == '**' */ + match_slash = flags & WM_PATHNAME ? 0 : 1; + if (*p == '\0') { + /* Trailing "**" matches everything. Trailing "*" matches + * only if there are no more slash characters. */ + if (!match_slash) { + if (strchr((char*)text, '/') != NULL) + return WM_NOMATCH; + } + return WM_MATCH; + } else if (!match_slash && *p == '/') { + /* + * _one_ asterisk followed by a slash + * with WM_PATHNAME matches the next + * directory + */ + const char *slash = strchr((char*)text, '/'); + if (!slash) + return WM_NOMATCH; + text = (const uchar*)slash; + /* the slash is consumed by the top-level for loop */ + break; + } + while (1) { + if (t_ch == '\0') + break; + /* + * Try to advance faster when an asterisk is + * followed by a literal. We know in this case + * that the string before the literal + * must belong to "*". + * If match_slash is false, do not look past + * the first slash as it cannot belong to '*'. + */ + if (!is_glob_special(*p)) { + p_ch = *p; + if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) + p_ch = tolower(p_ch); + while ((t_ch = *text) != '\0' && + (match_slash || t_ch != '/')) { + if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) + t_ch = tolower(t_ch); + if (t_ch == p_ch) + break; + text++; + } + if (t_ch != p_ch) + return WM_NOMATCH; + } + if ((matched = dowild(p, text, flags)) != WM_NOMATCH) { + if (!match_slash || matched != WM_ABORT_TO_STARSTAR) + return matched; + } else if (!match_slash && t_ch == '/') + return WM_ABORT_TO_STARSTAR; + t_ch = *++text; + } + return WM_ABORT_ALL; + case '[': + p_ch = *++p; +#ifdef NEGATE_CLASS2 + if (p_ch == NEGATE_CLASS2) + p_ch = NEGATE_CLASS; +#endif + /* Assign literal 1/0 because of "matched" comparison. */ + negated = p_ch == NEGATE_CLASS ? 1 : 0; + if (negated) { + /* Inverted character class. */ + p_ch = *++p; + } + prev_ch = 0; + matched = 0; + do { + if (!p_ch) + return WM_ABORT_ALL; + if (p_ch == '\\') { + p_ch = *++p; + if (!p_ch) + return WM_ABORT_ALL; + if (t_ch == p_ch) + matched = 1; + } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') { + p_ch = *++p; + if (p_ch == '\\') { + p_ch = *++p; + if (!p_ch) + return WM_ABORT_ALL; + } + if (t_ch <= p_ch && t_ch >= prev_ch) + matched = 1; + else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) { + uchar t_ch_upper = toupper(t_ch); + if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch) + matched = 1; + } + p_ch = 0; /* This makes "prev_ch" get set to 0. */ + } else if (p_ch == '[' && p[1] == ':') { + const uchar *s; + int i; + for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/ + if (!p_ch) + return WM_ABORT_ALL; + i = (int)(p - s - 1); + if (i < 0 || p[-1] != ':') { + /* Didn't find ":]", so treat like a normal set. */ + p = s - 2; + p_ch = '['; + if (t_ch == p_ch) + matched = 1; + continue; + } + if (CC_EQ(s,i, "alnum")) { + if (ISALNUM(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "alpha")) { + if (ISALPHA(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "blank")) { + if (ISBLANK(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "cntrl")) { + if (ISCNTRL(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "digit")) { + if (ISDIGIT(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "graph")) { + if (ISGRAPH(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "lower")) { + if (ISLOWER(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "print")) { + if (ISPRINT(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "punct")) { + if (ISPUNCT(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "space")) { + if (ISSPACE(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "upper")) { + if (ISUPPER(t_ch)) + matched = 1; + else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "xdigit")) { + if (ISXDIGIT(t_ch)) + matched = 1; + } else /* malformed [:class:] string */ + return WM_ABORT_ALL; + p_ch = 0; /* This makes "prev_ch" get set to 0. */ + } else if (t_ch == p_ch) + matched = 1; + } while (prev_ch = p_ch, (p_ch = *++p) != ']'); + if (matched == negated || + ((flags & WM_PATHNAME) && t_ch == '/')) + return WM_NOMATCH; + continue; + } + } + + return *text ? WM_NOMATCH : WM_MATCH; +} + +/* Match the "pattern" against the "text" string. */ +int wildmatch(const char *pattern, const char *text, unsigned int flags) +{ + return dowild((const uchar*)pattern, (const uchar*)text, flags); +} diff --git a/src/libgit2/wildmatch.h b/src/libgit2/wildmatch.h new file mode 100644 index 000000000..44bb575a6 --- /dev/null +++ b/src/libgit2/wildmatch.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_wildmatch_h__ +#define INCLUDE_wildmatch_h__ + +#include "common.h" + +#define WM_CASEFOLD 1 +#define WM_PATHNAME 2 + +#define WM_NOMATCH 1 +#define WM_MATCH 0 +#define WM_ABORT_ALL -1 +#define WM_ABORT_TO_STARSTAR -2 + +int wildmatch(const char *pattern, const char *text, unsigned int flags); + +#endif diff --git a/src/libgit2/win32/dir.c b/src/libgit2/win32/dir.c new file mode 100644 index 000000000..44052caf0 --- /dev/null +++ b/src/libgit2/win32/dir.c @@ -0,0 +1,122 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "dir.h" + +#define GIT__WIN32_NO_WRAP_DIR +#include "posix.h" + +git__DIR *git__opendir(const char *dir) +{ + git_win32_path filter_w; + git__DIR *new = NULL; + size_t dirlen, alloclen; + + if (!dir || !git_win32__findfirstfile_filter(filter_w, dir)) + return NULL; + + dirlen = strlen(dir); + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, sizeof(*new), dirlen) || + GIT_ADD_SIZET_OVERFLOW(&alloclen, alloclen, 1) || + !(new = git__calloc(1, alloclen))) + return NULL; + + memcpy(new->dir, dir, dirlen); + + new->h = FindFirstFileW(filter_w, &new->f); + + if (new->h == INVALID_HANDLE_VALUE) { + git_error_set(GIT_ERROR_OS, "could not open directory '%s'", dir); + git__free(new); + return NULL; + } + + new->first = 1; + return new; +} + +int git__readdir_ext( + git__DIR *d, + struct git__dirent *entry, + struct git__dirent **result, + int *is_dir) +{ + if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE) + return -1; + + *result = NULL; + + if (d->first) + d->first = 0; + else if (!FindNextFileW(d->h, &d->f)) { + if (GetLastError() == ERROR_NO_MORE_FILES) + return 0; + git_error_set(GIT_ERROR_OS, "could not read from directory '%s'", d->dir); + return -1; + } + + /* Convert the path to UTF-8 */ + if (git_win32_path_to_utf8(entry->d_name, d->f.cFileName) < 0) + return -1; + + entry->d_ino = 0; + + *result = entry; + + if (is_dir != NULL) + *is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); + + return 0; +} + +struct git__dirent *git__readdir(git__DIR *d) +{ + struct git__dirent *result; + if (git__readdir_ext(d, &d->entry, &result, NULL) < 0) + return NULL; + return result; +} + +void git__rewinddir(git__DIR *d) +{ + git_win32_path filter_w; + + if (!d) + return; + + if (d->h != INVALID_HANDLE_VALUE) { + FindClose(d->h); + d->h = INVALID_HANDLE_VALUE; + d->first = 0; + } + + if (!git_win32__findfirstfile_filter(filter_w, d->dir)) + return; + + d->h = FindFirstFileW(filter_w, &d->f); + + if (d->h == INVALID_HANDLE_VALUE) + git_error_set(GIT_ERROR_OS, "could not open directory '%s'", d->dir); + else + d->first = 1; +} + +int git__closedir(git__DIR *d) +{ + if (!d) + return 0; + + if (d->h != INVALID_HANDLE_VALUE) { + FindClose(d->h); + d->h = INVALID_HANDLE_VALUE; + } + + git__free(d); + return 0; +} + diff --git a/src/libgit2/win32/dir.h b/src/libgit2/win32/dir.h new file mode 100644 index 000000000..acd64729e --- /dev/null +++ b/src/libgit2/win32/dir.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_dir_h__ +#define INCLUDE_win32_dir_h__ + +#include "common.h" + +#include "w32_util.h" + +struct git__dirent { + int d_ino; + git_win32_utf8_path d_name; +}; + +typedef struct { + HANDLE h; + WIN32_FIND_DATAW f; + struct git__dirent entry; + int first; + char dir[GIT_FLEX_ARRAY]; +} git__DIR; + +extern git__DIR *git__opendir(const char *); +extern struct git__dirent *git__readdir(git__DIR *); +extern int git__readdir_ext( + git__DIR *, struct git__dirent *, struct git__dirent **, int *); +extern void git__rewinddir(git__DIR *); +extern int git__closedir(git__DIR *); + +# ifndef GIT__WIN32_NO_WRAP_DIR +# define dirent git__dirent +# define DIR git__DIR +# define opendir git__opendir +# define readdir git__readdir +# define readdir_r(d,e,r) git__readdir_ext((d),(e),(r),NULL) +# define rewinddir git__rewinddir +# define closedir git__closedir +# endif + +#endif diff --git a/src/libgit2/win32/error.c b/src/libgit2/win32/error.c new file mode 100644 index 000000000..3a52fb5a9 --- /dev/null +++ b/src/libgit2/win32/error.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "error.h" + +#include "utf-conv.h" + +#ifdef GIT_WINHTTP +# include +#endif + +char *git_win32_get_error_message(DWORD error_code) +{ + LPWSTR lpMsgBuf = NULL; + HMODULE hModule = NULL; + char *utf8_msg = NULL; + DWORD dwFlags = + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS; + + if (!error_code) + return NULL; + +#ifdef GIT_WINHTTP + /* Errors raised by WinHTTP are not in the system resource table */ + if (error_code >= WINHTTP_ERROR_BASE && + error_code <= WINHTTP_ERROR_LAST) + hModule = GetModuleHandleW(L"winhttp"); +#endif + + GIT_UNUSED(hModule); + + if (hModule) + dwFlags |= FORMAT_MESSAGE_FROM_HMODULE; + else + dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM; + + if (FormatMessageW(dwFlags, hModule, error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&lpMsgBuf, 0, NULL)) { + /* Convert the message to UTF-8. If this fails, we will + * return NULL, which is a condition expected by the caller */ + if (git__utf16_to_8_alloc(&utf8_msg, lpMsgBuf) < 0) + utf8_msg = NULL; + + LocalFree(lpMsgBuf); + } + + return utf8_msg; +} diff --git a/src/libgit2/win32/error.h b/src/libgit2/win32/error.h new file mode 100644 index 000000000..9e81141ce --- /dev/null +++ b/src/libgit2/win32/error.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_win32_error_h__ +#define INCLUDE_win32_error_h__ + +#include "common.h" + +extern char *git_win32_get_error_message(DWORD error_code); + +#endif diff --git a/src/libgit2/win32/findfile.c b/src/libgit2/win32/findfile.c new file mode 100644 index 000000000..725a90167 --- /dev/null +++ b/src/libgit2/win32/findfile.c @@ -0,0 +1,286 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "findfile.h" + +#include "path_w32.h" +#include "utf-conv.h" +#include "fs_path.h" + +#define REG_GITFORWINDOWS_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" +#define REG_GITFORWINDOWS_KEY_WOW64 L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" + +static int git_win32__expand_path(git_win32_path dest, const wchar_t *src) +{ + DWORD len = ExpandEnvironmentStringsW(src, dest, GIT_WIN_PATH_UTF16); + + if (!len || len > GIT_WIN_PATH_UTF16) + return -1; + + return 0; +} + +static int win32_path_to_8(git_str *dest, const wchar_t *src) +{ + git_win32_utf8_path utf8_path; + + if (git_win32_path_to_utf8(utf8_path, src) < 0) { + git_error_set(GIT_ERROR_OS, "unable to convert path to UTF-8"); + return -1; + } + + /* Convert backslashes to forward slashes */ + git_fs_path_mkposix(utf8_path); + + return git_str_sets(dest, utf8_path); +} + +static git_win32_path mock_registry; +static bool mock_registry_set; + +extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir) +{ + if (!mock_sysdir) { + mock_registry[0] = L'\0'; + mock_registry_set = false; + } else { + size_t len = wcslen(mock_sysdir); + + if (len > GIT_WIN_PATH_MAX) { + git_error_set(GIT_ERROR_INVALID, "mock path too long"); + return -1; + } + + wcscpy(mock_registry, mock_sysdir); + mock_registry_set = true; + } + + return 0; +} + +static int lookup_registry_key( + git_win32_path out, + const HKEY hive, + const wchar_t* key, + const wchar_t *value) +{ + HKEY hkey; + DWORD type, size; + int error = GIT_ENOTFOUND; + + /* + * Registry data may not be NUL terminated, provide room to do + * it ourselves. + */ + size = (DWORD)((sizeof(git_win32_path) - 1) * sizeof(wchar_t)); + + if (RegOpenKeyExW(hive, key, 0, KEY_READ, &hkey) != 0) + return GIT_ENOTFOUND; + + if (RegQueryValueExW(hkey, value, NULL, &type, (LPBYTE)out, &size) == 0 && + type == REG_SZ && + size > 0 && + size < sizeof(git_win32_path)) { + size_t wsize = size / sizeof(wchar_t); + size_t len = wsize - 1; + + if (out[wsize - 1] != L'\0') { + len = wsize; + out[wsize] = L'\0'; + } + + if (out[len - 1] == L'\\') + out[len - 1] = L'\0'; + + if (_waccess(out, F_OK) == 0) + error = 0; + } + + RegCloseKey(hkey); + return error; +} + +static int find_sysdir_in_registry(git_win32_path out) +{ + if (mock_registry_set) { + if (mock_registry[0] == L'\0') + return GIT_ENOTFOUND; + + wcscpy(out, mock_registry); + return 0; + } + + if (lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || + lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0 || + lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || + lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0) + return 0; + + return GIT_ENOTFOUND; +} + +static int find_sysdir_in_path(git_win32_path out) +{ + size_t out_len; + + if (git_win32_path_find_executable(out, L"git.exe") < 0 && + git_win32_path_find_executable(out, L"git.cmd") < 0) + return GIT_ENOTFOUND; + + out_len = wcslen(out); + + /* Trim the file name */ + if (out_len <= CONST_STRLEN(L"git.exe")) + return GIT_ENOTFOUND; + + out_len -= CONST_STRLEN(L"git.exe"); + + if (out_len && out[out_len - 1] == L'\\') + out_len--; + + /* + * Git for Windows usually places the command in a 'bin' or + * 'cmd' directory, trim that. + */ + if (out_len >= CONST_STRLEN(L"\\bin") && + wcsncmp(&out[out_len - CONST_STRLEN(L"\\bin")], L"\\bin", CONST_STRLEN(L"\\bin")) == 0) + out_len -= CONST_STRLEN(L"\\bin"); + else if (out_len >= CONST_STRLEN(L"\\cmd") && + wcsncmp(&out[out_len - CONST_STRLEN(L"\\cmd")], L"\\cmd", CONST_STRLEN(L"\\cmd")) == 0) + out_len -= CONST_STRLEN(L"\\cmd"); + + if (!out_len) + return GIT_ENOTFOUND; + + out[out_len] = L'\0'; + return 0; +} + +static int win32_find_existing_dirs( + git_str* out, + const wchar_t* tmpl[]) +{ + git_win32_path path16; + git_str buf = GIT_STR_INIT; + + git_str_clear(out); + + for (; *tmpl != NULL; tmpl++) { + if (!git_win32__expand_path(path16, *tmpl) && + path16[0] != L'%' && + !_waccess(path16, F_OK)) { + win32_path_to_8(&buf, path16); + + if (buf.size) + git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); + } + } + + git_str_dispose(&buf); + + return (git_str_oom(out) ? -1 : 0); +} + +static int append_subdir(git_str *out, git_str *path, const char *subdir) +{ + static const char* architecture_roots[] = { + "", + "mingw64", + "mingw32", + NULL + }; + const char **root; + size_t orig_path_len = path->size; + + for (root = architecture_roots; *root; root++) { + if ((*root[0] && git_str_joinpath(path, path->ptr, *root) < 0) || + git_str_joinpath(path, path->ptr, subdir) < 0) + return -1; + + if (git_fs_path_exists(path->ptr) && + git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, path->ptr) < 0) + return -1; + + git_str_truncate(path, orig_path_len); + } + + return 0; +} + +int git_win32__find_system_dirs(git_str *out, const char *subdir) +{ + git_win32_path pathdir, regdir; + git_str path8 = GIT_STR_INIT; + bool has_pathdir, has_regdir; + int error; + + has_pathdir = (find_sysdir_in_path(pathdir) == 0); + has_regdir = (find_sysdir_in_registry(regdir) == 0); + + if (!has_pathdir && !has_regdir) + return 0; + + /* + * Usually the git in the path is the same git in the registry, + * in this case there's no need to duplicate the paths. + */ + if (has_pathdir && has_regdir && wcscmp(pathdir, regdir) == 0) + has_regdir = false; + + if (has_pathdir) { + if ((error = win32_path_to_8(&path8, pathdir)) < 0 || + (error = append_subdir(out, &path8, subdir)) < 0) + goto done; + } + + if (has_regdir) { + if ((error = win32_path_to_8(&path8, regdir)) < 0 || + (error = append_subdir(out, &path8, subdir)) < 0) + goto done; + } + +done: + git_str_dispose(&path8); + return error; +} + +int git_win32__find_global_dirs(git_str *out) +{ + static const wchar_t *global_tmpls[4] = { + L"%HOME%\\", + L"%HOMEDRIVE%%HOMEPATH%\\", + L"%USERPROFILE%\\", + NULL, + }; + + return win32_find_existing_dirs(out, global_tmpls); +} + +int git_win32__find_xdg_dirs(git_str *out) +{ + static const wchar_t *global_tmpls[7] = { + L"%XDG_CONFIG_HOME%\\git", + L"%APPDATA%\\git", + L"%LOCALAPPDATA%\\git", + L"%HOME%\\.config\\git", + L"%HOMEDRIVE%%HOMEPATH%\\.config\\git", + L"%USERPROFILE%\\.config\\git", + NULL, + }; + + return win32_find_existing_dirs(out, global_tmpls); +} + +int git_win32__find_programdata_dirs(git_str *out) +{ + static const wchar_t *programdata_tmpls[2] = { + L"%PROGRAMDATA%\\Git", + NULL, + }; + + return win32_find_existing_dirs(out, programdata_tmpls); +} diff --git a/src/libgit2/win32/findfile.h b/src/libgit2/win32/findfile.h new file mode 100644 index 000000000..61fb7dbad --- /dev/null +++ b/src/libgit2/win32/findfile.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_win32_findfile_h__ +#define INCLUDE_win32_findfile_h__ + +#include "common.h" + +/** Sets the mock registry root for Git for Windows for testing. */ +extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir); + +extern int git_win32__find_system_dirs(git_str *out, const char *subpath); +extern int git_win32__find_global_dirs(git_str *out); +extern int git_win32__find_xdg_dirs(git_str *out); +extern int git_win32__find_programdata_dirs(git_str *out); + +#endif + diff --git a/src/libgit2/win32/git2.rc b/src/libgit2/win32/git2.rc new file mode 100644 index 000000000..3f97239da --- /dev/null +++ b/src/libgit2/win32/git2.rc @@ -0,0 +1,59 @@ +#include +#include "../../../include/git2/version.h" + +#ifndef LIBGIT2_FILENAME +# ifdef __GNUC__ +# define LIBGIT2_FILENAME git2 +# else +# define LIBGIT2_FILENAME "git2" +# endif +#endif + +#ifndef LIBGIT2_COMMENTS +# define LIBGIT2_COMMENTS "For more information visit http://libgit2.github.com/" +#endif + +#ifdef __GNUC__ +# define _STR(x) #x +# define STR(x) _STR(x) +#else +# define STR(x) x +#endif + +#ifdef __GNUC__ +VS_VERSION_INFO VERSIONINFO +#else +VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE +#endif + FILEVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH + PRODUCTVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0 +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + BEGIN + VALUE "FileDescription", "libgit2 - the Git linkable library\0" + VALUE "FileVersion", LIBGIT2_VERSION "\0" + VALUE "InternalName", STR(LIBGIT2_FILENAME) ".dll\0" + VALUE "LegalCopyright", "Copyright (C) the libgit2 contributors. All rights reserved.\0" + VALUE "OriginalFilename", STR(LIBGIT2_FILENAME) ".dll\0" + VALUE "ProductName", "libgit2\0" + VALUE "ProductVersion", LIBGIT2_VERSION "\0" + VALUE "Comments", LIBGIT2_COMMENTS "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END diff --git a/src/libgit2/win32/map.c b/src/libgit2/win32/map.c new file mode 100644 index 000000000..2aabc9b15 --- /dev/null +++ b/src/libgit2/win32/map.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "map.h" +#include + +#ifndef NO_MMAP + +static DWORD get_page_size(void) +{ + static DWORD page_size; + SYSTEM_INFO sys; + + if (!page_size) { + GetSystemInfo(&sys); + page_size = sys.dwPageSize; + } + + return page_size; +} + +static DWORD get_allocation_granularity(void) +{ + static DWORD granularity; + SYSTEM_INFO sys; + + if (!granularity) { + GetSystemInfo(&sys); + granularity = sys.dwAllocationGranularity; + } + + return granularity; +} + +int git__page_size(size_t *page_size) +{ + *page_size = get_page_size(); + return 0; +} + +int git__mmap_alignment(size_t *page_size) +{ + *page_size = get_allocation_granularity(); + return 0; +} + +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) +{ + HANDLE fh = (HANDLE)_get_osfhandle(fd); + DWORD alignment = get_allocation_granularity(); + DWORD fmap_prot = 0; + DWORD view_prot = 0; + DWORD off_low = 0; + DWORD off_hi = 0; + off64_t page_start; + off64_t page_offset; + + GIT_MMAP_VALIDATE(out, len, prot, flags); + + out->data = NULL; + out->len = 0; + out->fmh = NULL; + + if (fh == INVALID_HANDLE_VALUE) { + errno = EBADF; + git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value"); + return -1; + } + + if (prot & GIT_PROT_WRITE) + fmap_prot |= PAGE_READWRITE; + else if (prot & GIT_PROT_READ) + fmap_prot |= PAGE_READONLY; + + if (prot & GIT_PROT_WRITE) + view_prot |= FILE_MAP_WRITE; + if (prot & GIT_PROT_READ) + view_prot |= FILE_MAP_READ; + + page_start = (offset / alignment) * alignment; + page_offset = offset - page_start; + + if (page_offset != 0) { /* offset must be multiple of the allocation granularity */ + errno = EINVAL; + git_error_set(GIT_ERROR_OS, "failed to mmap. Offset must be multiple of allocation granularity"); + return -1; + } + + out->fmh = CreateFileMapping(fh, NULL, fmap_prot, 0, 0, NULL); + if (!out->fmh || out->fmh == INVALID_HANDLE_VALUE) { + git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value"); + out->fmh = NULL; + return -1; + } + + off_low = (DWORD)(page_start); + off_hi = (DWORD)(page_start >> 32); + out->data = MapViewOfFile(out->fmh, view_prot, off_hi, off_low, len); + if (!out->data) { + git_error_set(GIT_ERROR_OS, "failed to mmap. No data written"); + CloseHandle(out->fmh); + out->fmh = NULL; + return -1; + } + out->len = len; + + return 0; +} + +int p_munmap(git_map *map) +{ + int error = 0; + + GIT_ASSERT_ARG(map); + + if (map->data) { + if (!UnmapViewOfFile(map->data)) { + git_error_set(GIT_ERROR_OS, "failed to munmap. Could not unmap view of file"); + error = -1; + } + map->data = NULL; + } + + if (map->fmh) { + if (!CloseHandle(map->fmh)) { + git_error_set(GIT_ERROR_OS, "failed to munmap. Could not close handle"); + error = -1; + } + map->fmh = NULL; + } + + return error; +} + +#endif diff --git a/src/libgit2/win32/mingw-compat.h b/src/libgit2/win32/mingw-compat.h new file mode 100644 index 000000000..aa2bef98d --- /dev/null +++ b/src/libgit2/win32/mingw-compat.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_mingw_compat_h__ +#define INCLUDE_win32_mingw_compat_h__ + +#if defined(__MINGW32__) + +#undef stat + +#if _WIN32_WINNT < 0x0600 && !defined(__MINGW64_VERSION_MAJOR) +#undef MemoryBarrier +void __mingworg_MemoryBarrier(void); +#define MemoryBarrier __mingworg_MemoryBarrier +#define VOLUME_NAME_DOS 0x0 +#endif + +#endif + +#endif diff --git a/src/libgit2/win32/msvc-compat.h b/src/libgit2/win32/msvc-compat.h new file mode 100644 index 000000000..03f9f36dc --- /dev/null +++ b/src/libgit2/win32/msvc-compat.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_msvc_compat_h__ +#define INCLUDE_win32_msvc_compat_h__ + +#if defined(_MSC_VER) + +typedef unsigned short mode_t; +typedef SSIZE_T ssize_t; + +#ifdef _WIN64 +# define SSIZE_MAX _I64_MAX +#else +# define SSIZE_MAX LONG_MAX +#endif + +#define strcasecmp(s1, s2) _stricmp(s1, s2) +#define strncasecmp(s1, s2, c) _strnicmp(s1, s2, c) + +#endif + +/* + * Offer GIT_LIBGIT2_CALL for our calling conventions (__cdecl, always). + * This is useful for providing callbacks to userspace code. + * + * Offer GIT_SYSTEM_CALL for the system calling conventions (__stdcall on + * Win32). Useful for providing callbacks to system libraries. + */ +#define GIT_LIBGIT2_CALL __cdecl +#define GIT_SYSTEM_CALL NTAPI + +#endif diff --git a/src/libgit2/win32/path_w32.c b/src/libgit2/win32/path_w32.c new file mode 100644 index 000000000..d9fc8292b --- /dev/null +++ b/src/libgit2/win32/path_w32.c @@ -0,0 +1,642 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "path_w32.h" + +#include "fs_path.h" +#include "utf-conv.h" +#include "posix.h" +#include "reparse.h" +#include "dir.h" + +#define PATH__NT_NAMESPACE L"\\\\?\\" +#define PATH__NT_NAMESPACE_LEN 4 + +#define PATH__ABSOLUTE_LEN 3 + +#define path__is_nt_namespace(p) \ + (((p)[0] == '\\' && (p)[1] == '\\' && (p)[2] == '?' && (p)[3] == '\\') || \ + ((p)[0] == '/' && (p)[1] == '/' && (p)[2] == '?' && (p)[3] == '/')) + +#define path__is_unc(p) \ + (((p)[0] == '\\' && (p)[1] == '\\') || ((p)[0] == '/' && (p)[1] == '/')) + +#define path__startswith_slash(p) \ + ((p)[0] == '\\' || (p)[0] == '/') + +GIT_INLINE(int) path__cwd(wchar_t *path, int size) +{ + int len; + + if ((len = GetCurrentDirectoryW(size, path)) == 0) { + errno = GetLastError() == ERROR_ACCESS_DENIED ? EACCES : ENOENT; + return -1; + } else if (len > size) { + errno = ENAMETOOLONG; + return -1; + } + + /* The Win32 APIs may return "\\?\" once you've used it first. + * But it may not. What a gloriously predictable API! + */ + if (wcsncmp(path, PATH__NT_NAMESPACE, PATH__NT_NAMESPACE_LEN)) + return len; + + len -= PATH__NT_NAMESPACE_LEN; + + memmove(path, path + PATH__NT_NAMESPACE_LEN, sizeof(wchar_t) * len); + return len; +} + +static wchar_t *path__skip_server(wchar_t *path) +{ + wchar_t *c; + + for (c = path; *c; c++) { + if (git_fs_path_is_dirsep(*c)) + return c + 1; + } + + return c; +} + +static wchar_t *path__skip_prefix(wchar_t *path) +{ + if (path__is_nt_namespace(path)) { + path += PATH__NT_NAMESPACE_LEN; + + if (wcsncmp(path, L"UNC\\", 4) == 0) + path = path__skip_server(path + 4); + else if (git_fs_path_is_absolute(path)) + path += PATH__ABSOLUTE_LEN; + } else if (git_fs_path_is_absolute(path)) { + path += PATH__ABSOLUTE_LEN; + } else if (path__is_unc(path)) { + path = path__skip_server(path + 2); + } + + return path; +} + +int git_win32_path_canonicalize(git_win32_path path) +{ + wchar_t *base, *from, *to, *next; + size_t len; + + base = to = path__skip_prefix(path); + + /* Unposixify if the prefix */ + for (from = path; from < to; from++) { + if (*from == L'/') + *from = L'\\'; + } + + while (*from) { + for (next = from; *next; ++next) { + if (*next == L'/') { + *next = L'\\'; + break; + } + + if (*next == L'\\') + break; + } + + len = next - from; + + if (len == 1 && from[0] == L'.') + /* do nothing with singleton dot */; + + else if (len == 2 && from[0] == L'.' && from[1] == L'.') { + if (to == base) { + /* no more path segments to strip, eat the "../" */ + if (*next == L'\\') + len++; + + base = to; + } else { + /* back up a path segment */ + while (to > base && to[-1] == L'\\') to--; + while (to > base && to[-1] != L'\\') to--; + } + } else { + if (*next == L'\\' && *from != L'\\') + len++; + + if (to != from) + memmove(to, from, sizeof(wchar_t) * len); + + to += len; + } + + from += len; + + while (*from == L'\\') from++; + } + + /* Strip trailing backslashes */ + while (to > base && to[-1] == L'\\') to--; + + *to = L'\0'; + + if ((to - path) > INT_MAX) { + SetLastError(ERROR_FILENAME_EXCED_RANGE); + return -1; + } + + return (int)(to - path); +} + +static int git_win32_path_join( + git_win32_path dest, + const wchar_t *one, + size_t one_len, + const wchar_t *two, + size_t two_len) +{ + size_t backslash = 0; + + if (one_len && two_len && one[one_len - 1] != L'\\') + backslash = 1; + + if (one_len + two_len + backslash > MAX_PATH) { + git_error_set(GIT_ERROR_INVALID, "path too long"); + return -1; + } + + memmove(dest, one, one_len * sizeof(wchar_t)); + + if (backslash) + dest[one_len] = L'\\'; + + memcpy(dest + one_len + backslash, two, two_len * sizeof(wchar_t)); + dest[one_len + backslash + two_len] = L'\0'; + + return 0; +} + +struct win32_path_iter { + wchar_t *env; + const wchar_t *current_dir; +}; + +static int win32_path_iter_init(struct win32_path_iter *iter) +{ + DWORD len = GetEnvironmentVariableW(L"PATH", NULL, 0); + + if (!len && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { + iter->env = NULL; + iter->current_dir = NULL; + return 0; + } else if (!len) { + git_error_set(GIT_ERROR_OS, "could not load PATH"); + return -1; + } + + iter->env = git__malloc(len * sizeof(wchar_t)); + GIT_ERROR_CHECK_ALLOC(iter->env); + + len = GetEnvironmentVariableW(L"PATH", iter->env, len); + + if (len == 0) { + git_error_set(GIT_ERROR_OS, "could not load PATH"); + return -1; + } + + iter->current_dir = iter->env; + return 0; +} + +static int win32_path_iter_next( + const wchar_t **out, + size_t *out_len, + struct win32_path_iter *iter) +{ + const wchar_t *start; + wchar_t term; + size_t len = 0; + + if (!iter->current_dir || !*iter->current_dir) + return GIT_ITEROVER; + + term = (*iter->current_dir == L'"') ? *iter->current_dir++ : L';'; + start = iter->current_dir; + + while (*iter->current_dir && *iter->current_dir != term) { + iter->current_dir++; + len++; + } + + *out = start; + *out_len = len; + + if (term == L'"' && *iter->current_dir) + iter->current_dir++; + + while (*iter->current_dir == L';') + iter->current_dir++; + + return 0; +} + +static void win32_path_iter_dispose(struct win32_path_iter *iter) +{ + if (!iter) + return; + + git__free(iter->env); + iter->env = NULL; + iter->current_dir = NULL; +} + +int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe) +{ + struct win32_path_iter path_iter; + const wchar_t *dir; + size_t dir_len, exe_len = wcslen(exe); + bool found = false; + + if (win32_path_iter_init(&path_iter) < 0) + return -1; + + while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER) { + if (git_win32_path_join(fullpath, dir, dir_len, exe, exe_len) < 0) + continue; + + if (_waccess(fullpath, 0) == 0) { + found = true; + break; + } + } + + win32_path_iter_dispose(&path_iter); + + if (found) + return 0; + + fullpath[0] = L'\0'; + return GIT_ENOTFOUND; +} + +static int win32_path_cwd(wchar_t *out, size_t len) +{ + int cwd_len; + + if (len > INT_MAX) { + errno = ENAMETOOLONG; + return -1; + } + + if ((cwd_len = path__cwd(out, (int)len)) < 0) + return -1; + + /* UNC paths */ + if (wcsncmp(L"\\\\", out, 2) == 0) { + /* Our buffer must be at least 5 characters larger than the + * current working directory: we swallow one of the leading + * '\'s, but we we add a 'UNC' specifier to the path, plus + * a trailing directory separator, plus a NUL. + */ + if (cwd_len > GIT_WIN_PATH_MAX - 4) { + errno = ENAMETOOLONG; + return -1; + } + + memmove(out+2, out, sizeof(wchar_t) * cwd_len); + out[0] = L'U'; + out[1] = L'N'; + out[2] = L'C'; + + cwd_len += 2; + } + + /* Our buffer must be at least 2 characters larger than the current + * working directory. (One character for the directory separator, + * one for the null. + */ + else if (cwd_len > GIT_WIN_PATH_MAX - 2) { + errno = ENAMETOOLONG; + return -1; + } + + return cwd_len; +} + +int git_win32_path_from_utf8(git_win32_path out, const char *src) +{ + wchar_t *dest = out; + + /* All win32 paths are in NT-prefixed format, beginning with "\\?\". */ + memcpy(dest, PATH__NT_NAMESPACE, sizeof(wchar_t) * PATH__NT_NAMESPACE_LEN); + dest += PATH__NT_NAMESPACE_LEN; + + /* See if this is an absolute path (beginning with a drive letter) */ + if (git_fs_path_is_absolute(src)) { + if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0) + goto on_error; + } + /* File-prefixed NT-style paths beginning with \\?\ */ + else if (path__is_nt_namespace(src)) { + /* Skip the NT prefix, the destination already contains it */ + if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0) + goto on_error; + } + /* UNC paths */ + else if (path__is_unc(src)) { + memcpy(dest, L"UNC\\", sizeof(wchar_t) * 4); + dest += 4; + + /* Skip the leading "\\" */ + if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0) + goto on_error; + } + /* Absolute paths omitting the drive letter */ + else if (path__startswith_slash(src)) { + if (path__cwd(dest, GIT_WIN_PATH_MAX) < 0) + goto on_error; + + if (!git_fs_path_is_absolute(dest)) { + errno = ENOENT; + goto on_error; + } + + /* Skip the drive letter specification ("C:") */ + if (git__utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0) + goto on_error; + } + /* Relative paths */ + else { + int cwd_len; + + if ((cwd_len = win32_path_cwd(dest, GIT_WIN_PATH_MAX)) < 0) + goto on_error; + + dest[cwd_len++] = L'\\'; + + if (git__utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0) + goto on_error; + } + + return git_win32_path_canonicalize(out); + +on_error: + /* set windows error code so we can use its error message */ + if (errno == ENAMETOOLONG) + SetLastError(ERROR_FILENAME_EXCED_RANGE); + + return -1; +} + +int git_win32_path_relative_from_utf8(git_win32_path out, const char *src) +{ + wchar_t *dest = out, *p; + int len; + + /* Handle absolute paths */ + if (git_fs_path_is_absolute(src) || + path__is_nt_namespace(src) || + path__is_unc(src) || + path__startswith_slash(src)) { + return git_win32_path_from_utf8(out, src); + } + + if ((len = git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src)) < 0) + return -1; + + for (p = dest; p < (dest + len); p++) { + if (*p == L'/') + *p = L'\\'; + } + + return len; +} + +int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src) +{ + char *out = dest; + int len; + + /* Strip NT namespacing "\\?\" */ + if (path__is_nt_namespace(src)) { + src += 4; + + /* "\\?\UNC\server\share" -> "\\server\share" */ + if (wcsncmp(src, L"UNC\\", 4) == 0) { + src += 4; + + memcpy(dest, "\\\\", 2); + out = dest + 2; + } + } + + if ((len = git__utf16_to_8(out, GIT_WIN_PATH_UTF8, src)) < 0) + return len; + + git_fs_path_mkposix(dest); + + return len; +} + +char *git_win32_path_8dot3_name(const char *path) +{ + git_win32_path longpath, shortpath; + wchar_t *start; + char *shortname; + int len, namelen = 1; + + if (git_win32_path_from_utf8(longpath, path) < 0) + return NULL; + + len = GetShortPathNameW(longpath, shortpath, GIT_WIN_PATH_UTF16); + + while (len && shortpath[len-1] == L'\\') + shortpath[--len] = L'\0'; + + if (len == 0 || len >= GIT_WIN_PATH_UTF16) + return NULL; + + for (start = shortpath + (len - 1); + start > shortpath && *(start-1) != '/' && *(start-1) != '\\'; + start--) + namelen++; + + /* We may not have actually been given a short name. But if we have, + * it will be in the ASCII byte range, so we don't need to worry about + * multi-byte sequences and can allocate naively. + */ + if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL) + return NULL; + + if ((len = git__utf16_to_8(shortname, namelen + 1, start)) < 0) + return NULL; + + return shortname; +} + +static bool path_is_volume(wchar_t *target, size_t target_len) +{ + return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0); +} + +/* On success, returns the length, in characters, of the path stored in dest. + * On failure, returns a negative value. */ +int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path) +{ + BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf; + HANDLE handle = NULL; + DWORD ioctl_ret; + wchar_t *target; + size_t target_len; + + int error = -1; + + handle = CreateFileW(path, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (handle == INVALID_HANDLE_VALUE) { + errno = ENOENT; + return -1; + } + + if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, + reparse_buf, sizeof(buf), &ioctl_ret, NULL)) { + errno = EINVAL; + goto on_error; + } + + switch (reparse_buf->ReparseTag) { + case IO_REPARSE_TAG_SYMLINK: + target = reparse_buf->ReparseBuffer.SymbolicLink.PathBuffer + + (reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameOffset / sizeof(WCHAR)); + target_len = reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameLength / sizeof(WCHAR); + break; + case IO_REPARSE_TAG_MOUNT_POINT: + target = reparse_buf->ReparseBuffer.MountPoint.PathBuffer + + (reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset / sizeof(WCHAR)); + target_len = reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength / sizeof(WCHAR); + break; + default: + errno = EINVAL; + goto on_error; + } + + if (path_is_volume(target, target_len)) { + /* This path is a reparse point that represents another volume mounted + * at this location, it is not a symbolic link our input was canonical. + */ + errno = EINVAL; + error = -1; + } else if (target_len) { + /* The path may need to have a namespace prefix removed. */ + target_len = git_win32_path_remove_namespace(target, target_len); + + /* Need one additional character in the target buffer + * for the terminating NULL. */ + if (GIT_WIN_PATH_UTF16 > target_len) { + wcscpy(dest, target); + error = (int)target_len; + } + } + +on_error: + CloseHandle(handle); + return error; +} + +/** + * Removes any trailing backslashes from a path, except in the case of a drive + * letter path (C:\, D:\, etc.). This function cannot fail. + * + * @param path The path which should be trimmed. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_trim_end(wchar_t *str, size_t len) +{ + while (1) { + if (!len || str[len - 1] != L'\\') + break; + + /* + * Don't trim backslashes from drive letter paths, which + * are 3 characters long and of the form C:\, D:\, etc. + */ + if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':') + break; + + len--; + } + + str[len] = L'\0'; + + return len; +} + +/** + * Removes any of the following namespace prefixes from a path, + * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. + * + * @param path The path which should be converted. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_remove_namespace(wchar_t *str, size_t len) +{ + static const wchar_t dosdevices_namespace[] = L"\\\?\?\\"; + static const wchar_t nt_namespace[] = L"\\\\?\\"; + static const wchar_t unc_namespace_remainder[] = L"UNC\\"; + static const wchar_t unc_prefix[] = L"\\\\"; + + const wchar_t *prefix = NULL, *remainder = NULL; + size_t prefix_len = 0, remainder_len = 0; + + /* "\??\" -- DOS Devices prefix */ + if (len >= CONST_STRLEN(dosdevices_namespace) && + !wcsncmp(str, dosdevices_namespace, CONST_STRLEN(dosdevices_namespace))) { + remainder = str + CONST_STRLEN(dosdevices_namespace); + remainder_len = len - CONST_STRLEN(dosdevices_namespace); + } + /* "\\?\" -- NT namespace prefix */ + else if (len >= CONST_STRLEN(nt_namespace) && + !wcsncmp(str, nt_namespace, CONST_STRLEN(nt_namespace))) { + remainder = str + CONST_STRLEN(nt_namespace); + remainder_len = len - CONST_STRLEN(nt_namespace); + } + + /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */ + if (remainder_len >= CONST_STRLEN(unc_namespace_remainder) && + !wcsncmp(remainder, unc_namespace_remainder, CONST_STRLEN(unc_namespace_remainder))) { + + /* + * The proper Win32 path for a UNC share has "\\" at beginning of it + * and looks like "\\server\share\". So remove the + * UNC namespace and add a prefix of "\\" in its place. + */ + remainder += CONST_STRLEN(unc_namespace_remainder); + remainder_len -= CONST_STRLEN(unc_namespace_remainder); + + prefix = unc_prefix; + prefix_len = CONST_STRLEN(unc_prefix); + } + + /* + * Sanity check that the new string isn't longer than the old one. + * (This could only happen due to programmer error introducing a + * prefix longer than the namespace it replaces.) + */ + if (remainder && len >= remainder_len + prefix_len) { + if (prefix) + memmove(str, prefix, prefix_len * sizeof(wchar_t)); + + memmove(str + prefix_len, remainder, remainder_len * sizeof(wchar_t)); + + len = remainder_len + prefix_len; + str[len] = L'\0'; + } + + return git_win32_path_trim_end(str, len); +} diff --git a/src/libgit2/win32/path_w32.h b/src/libgit2/win32/path_w32.h new file mode 100644 index 000000000..837b11ebd --- /dev/null +++ b/src/libgit2/win32/path_w32.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_path_w32_h__ +#define INCLUDE_win32_path_w32_h__ + +#include "common.h" + +/** + * Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given + * path is relative, then it will be turned into an absolute path by having + * the current working directory prepended. + * + * @param dest The buffer to receive the wide string. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +extern int git_win32_path_from_utf8(git_win32_path dest, const char *src); + +/** + * Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given + * path is relative, then it will not be turned into an absolute path. + * + * @param dest The buffer to receive the wide string. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +extern int git_win32_path_relative_from_utf8(git_win32_path dest, const char *src); + +/** + * Canonicalize a Win32 UCS-2 path so that it is suitable for delivery to the + * Win32 APIs: remove multiple directory separators, squashing to a single one, + * strip trailing directory separators, ensure directory separators are all + * canonical (always backslashes, never forward slashes) and process any + * directory entries of '.' or '..'. + * + * Note that this is intended to be used on absolute Windows paths, those + * that start with `C:\`, `\\server\share`, `\\?\`, etc. + * + * This processes the buffer in place. + * + * @param path The buffer to process + * @return The new length of the buffer, in wchar_t's (not counting the NULL terminator) + */ +extern int git_win32_path_canonicalize(git_win32_path path); + +/** + * Create an internal format (posix-style) UTF-8 path from a Win32 UCS-2 path. + * + * @param dest The buffer to receive the UTF-8 string. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +extern int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src); + +/** + * Get the short name for the terminal path component in the given path. + * For example, given "C:\Foo\Bar\Asdf.txt", this will return the short name + * for the file "Asdf.txt". + * + * @param path The given path in UTF-8 + * @return The name of the shortname for the given path + */ +extern char *git_win32_path_8dot3_name(const char *path); + +extern int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path); + +/** + * Removes any trailing backslashes from a path, except in the case of a drive + * letter path (C:\, D:\, etc.). This function cannot fail. + * + * @param path The path which should be trimmed. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_trim_end(wchar_t *str, size_t len); + +/** + * Removes any of the following namespace prefixes from a path, + * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. + * + * @param path The path which should be converted. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_remove_namespace(wchar_t *str, size_t len); + +int git_win32_path_find_executable(git_win32_path fullpath, wchar_t* exe); + +#endif diff --git a/src/libgit2/win32/posix.h b/src/libgit2/win32/posix.h new file mode 100644 index 000000000..578347f15 --- /dev/null +++ b/src/libgit2/win32/posix.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_posix_h__ +#define INCLUDE_win32_posix_h__ + +#include "common.h" +#include "../posix.h" +#include "win32-compat.h" +#include "path_w32.h" +#include "utf-conv.h" +#include "dir.h" + +extern unsigned long git_win32__createfile_sharemode; +extern int git_win32__retries; + +typedef SOCKET GIT_SOCKET; + +#define p_lseek(f,n,w) _lseeki64(f, n, w) + +extern int p_fstat(int fd, struct stat *buf); +extern int p_lstat(const char *file_name, struct stat *buf); +extern int p_stat(const char *path, struct stat *buf); + +extern int p_utimes(const char *filename, const struct p_timeval times[2]); +extern int p_futimes(int fd, const struct p_timeval times[2]); + +extern int p_readlink(const char *path, char *buf, size_t bufsiz); +extern int p_symlink(const char *old, const char *new); +extern int p_link(const char *old, const char *new); +extern int p_unlink(const char *path); +extern int p_mkdir(const char *path, mode_t mode); +extern int p_fsync(int fd); +extern char *p_realpath(const char *orig_path, char *buffer); + +extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags); +extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags); +extern int p_inet_pton(int af, const char *src, void* dst); + +extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr); +extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4); +extern int p_chdir(const char *path); +extern int p_chmod(const char *path, mode_t mode); +extern int p_rmdir(const char *path); +extern int p_access(const char *path, mode_t mode); +extern int p_ftruncate(int fd, off64_t size); + +/* p_lstat is almost but not quite POSIX correct. Specifically, the use of + * ENOTDIR is wrong, in that it does not mean precisely that a non-directory + * entry was encountered. Making it correct is potentially expensive, + * however, so this is a separate version of p_lstat to use when correct + * POSIX ENOTDIR semantics is required. + */ +extern int p_lstat_posixly(const char *filename, struct stat *buf); + +extern struct tm * p_localtime_r(const time_t *timer, struct tm *result); +extern struct tm * p_gmtime_r(const time_t *timer, struct tm *result); + +#endif diff --git a/src/libgit2/win32/posix_w32.c b/src/libgit2/win32/posix_w32.c new file mode 100644 index 000000000..5f7cd0c26 --- /dev/null +++ b/src/libgit2/win32/posix_w32.c @@ -0,0 +1,1047 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "../posix.h" +#include "../futils.h" +#include "fs_path.h" +#include "path_w32.h" +#include "utf-conv.h" +#include "reparse.h" +#include +#include +#include +#include + +#ifndef FILE_NAME_NORMALIZED +# define FILE_NAME_NORMALIZED 0 +#endif + +#ifndef IO_REPARSE_TAG_SYMLINK +#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) +#endif + +#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE +# define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x02 +#endif + +#ifndef SYMBOLIC_LINK_FLAG_DIRECTORY +# define SYMBOLIC_LINK_FLAG_DIRECTORY 0x01 +#endif + +/* Allowable mode bits on Win32. Using mode bits that are not supported on + * Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it + * so we simply remove them. + */ +#define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE) + +unsigned long git_win32__createfile_sharemode = + FILE_SHARE_READ | FILE_SHARE_WRITE; +int git_win32__retries = 10; + +GIT_INLINE(void) set_errno(void) +{ + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + case ERROR_NO_MORE_FILES: + case ERROR_BAD_NETPATH: + case ERROR_BAD_NET_NAME: + case ERROR_BAD_PATHNAME: + case ERROR_FILENAME_EXCED_RANGE: + errno = ENOENT; + break; + case ERROR_BAD_ENVIRONMENT: + errno = E2BIG; + break; + case ERROR_BAD_FORMAT: + case ERROR_INVALID_STARTING_CODESEG: + case ERROR_INVALID_STACKSEG: + case ERROR_INVALID_MODULETYPE: + case ERROR_INVALID_EXE_SIGNATURE: + case ERROR_EXE_MARKED_INVALID: + case ERROR_BAD_EXE_FORMAT: + case ERROR_ITERATED_DATA_EXCEEDS_64k: + case ERROR_INVALID_MINALLOCSIZE: + case ERROR_DYNLINK_FROM_INVALID_RING: + case ERROR_IOPL_NOT_ENABLED: + case ERROR_INVALID_SEGDPL: + case ERROR_AUTODATASEG_EXCEEDS_64k: + case ERROR_RING2SEG_MUST_BE_MOVABLE: + case ERROR_RELOC_CHAIN_XEEDS_SEGLIM: + case ERROR_INFLOOP_IN_RELOC_CHAIN: + errno = ENOEXEC; + break; + case ERROR_INVALID_HANDLE: + case ERROR_INVALID_TARGET_HANDLE: + case ERROR_DIRECT_ACCESS_HANDLE: + errno = EBADF; + break; + case ERROR_WAIT_NO_CHILDREN: + case ERROR_CHILD_NOT_COMPLETE: + errno = ECHILD; + break; + case ERROR_NO_PROC_SLOTS: + case ERROR_MAX_THRDS_REACHED: + case ERROR_NESTING_NOT_ALLOWED: + errno = EAGAIN; + break; + case ERROR_ARENA_TRASHED: + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_INVALID_BLOCK: + case ERROR_NOT_ENOUGH_QUOTA: + errno = ENOMEM; + break; + case ERROR_ACCESS_DENIED: + case ERROR_CURRENT_DIRECTORY: + case ERROR_WRITE_PROTECT: + case ERROR_BAD_UNIT: + case ERROR_NOT_READY: + case ERROR_BAD_COMMAND: + case ERROR_CRC: + case ERROR_BAD_LENGTH: + case ERROR_SEEK: + case ERROR_NOT_DOS_DISK: + case ERROR_SECTOR_NOT_FOUND: + case ERROR_OUT_OF_PAPER: + case ERROR_WRITE_FAULT: + case ERROR_READ_FAULT: + case ERROR_GEN_FAILURE: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + case ERROR_WRONG_DISK: + case ERROR_SHARING_BUFFER_EXCEEDED: + case ERROR_NETWORK_ACCESS_DENIED: + case ERROR_CANNOT_MAKE: + case ERROR_FAIL_I24: + case ERROR_DRIVE_LOCKED: + case ERROR_SEEK_ON_DEVICE: + case ERROR_NOT_LOCKED: + case ERROR_LOCK_FAILED: + errno = EACCES; + break; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + errno = EEXIST; + break; + case ERROR_NOT_SAME_DEVICE: + errno = EXDEV; + break; + case ERROR_INVALID_FUNCTION: + case ERROR_INVALID_ACCESS: + case ERROR_INVALID_DATA: + case ERROR_INVALID_PARAMETER: + case ERROR_NEGATIVE_SEEK: + errno = EINVAL; + break; + case ERROR_TOO_MANY_OPEN_FILES: + errno = EMFILE; + break; + case ERROR_DISK_FULL: + errno = ENOSPC; + break; + case ERROR_BROKEN_PIPE: + errno = EPIPE; + break; + case ERROR_DIR_NOT_EMPTY: + errno = ENOTEMPTY; + break; + default: + errno = EINVAL; + } +} + +GIT_INLINE(bool) last_error_retryable(void) +{ + int os_error = GetLastError(); + + return (os_error == ERROR_SHARING_VIOLATION || + os_error == ERROR_ACCESS_DENIED); +} + +#define do_with_retries(fn, remediation) \ + do { \ + int __retry, __ret; \ + for (__retry = git_win32__retries; __retry; __retry--) { \ + if ((__ret = (fn)) != GIT_RETRY) \ + return __ret; \ + if (__retry > 1 && (__ret = (remediation)) != 0) { \ + if (__ret == GIT_RETRY) \ + continue; \ + return __ret; \ + } \ + Sleep(5); \ + } \ + return -1; \ + } while (0) \ + +static int ensure_writable(wchar_t *path) +{ + DWORD attrs; + + if ((attrs = GetFileAttributesW(path)) == INVALID_FILE_ATTRIBUTES) + goto on_error; + + if ((attrs & FILE_ATTRIBUTE_READONLY) == 0) + return 0; + + if (!SetFileAttributesW(path, (attrs & ~FILE_ATTRIBUTE_READONLY))) + goto on_error; + + return GIT_RETRY; + +on_error: + set_errno(); + return -1; +} + +/** + * Truncate or extend file. + * + * We now take a "git_off_t" rather than "long" because + * files may be longer than 2Gb. + */ +int p_ftruncate(int fd, off64_t size) +{ + if (size < 0) { + errno = EINVAL; + return -1; + } + +#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API) + return ((_chsize_s(fd, size) == 0) ? 0 : -1); +#else + /* TODO MINGW32 Find a replacement for _chsize() that handles big files. */ + if (size > INT32_MAX) { + errno = EFBIG; + return -1; + } + return _chsize(fd, (long)size); +#endif +} + +int p_mkdir(const char *path, mode_t mode) +{ + git_win32_path buf; + + GIT_UNUSED(mode); + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + return _wmkdir(buf); +} + +int p_link(const char *old, const char *new) +{ + GIT_UNUSED(old); + GIT_UNUSED(new); + errno = ENOSYS; + return -1; +} + +GIT_INLINE(int) unlink_once(const wchar_t *path) +{ + DWORD error; + + if (DeleteFileW(path)) + return 0; + + if ((error = GetLastError()) == ERROR_ACCESS_DENIED) { + WIN32_FILE_ATTRIBUTE_DATA fdata; + if (!GetFileAttributesExW(path, GetFileExInfoStandard, &fdata) || + !(fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) || + !(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + goto out; + + if (RemoveDirectoryW(path)) + return 0; + } + +out: + SetLastError(error); + + if (last_error_retryable()) + return GIT_RETRY; + + set_errno(); + return -1; +} + +int p_unlink(const char *path) +{ + git_win32_path wpath; + + if (git_win32_path_from_utf8(wpath, path) < 0) + return -1; + + do_with_retries(unlink_once(wpath), ensure_writable(wpath)); +} + +int p_fsync(int fd) +{ + HANDLE fh = (HANDLE)_get_osfhandle(fd); + + p_fsync__cnt++; + + if (fh == INVALID_HANDLE_VALUE) { + errno = EBADF; + return -1; + } + + if (!FlushFileBuffers(fh)) { + DWORD code = GetLastError(); + + if (code == ERROR_INVALID_HANDLE) + errno = EINVAL; + else + errno = EIO; + + return -1; + } + + return 0; +} + +#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\') + +static int lstat_w( + wchar_t *path, + struct stat *buf, + bool posix_enotdir) +{ + WIN32_FILE_ATTRIBUTE_DATA fdata; + + if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) { + if (!buf) + return 0; + + return git_win32__file_attribute_to_stat(buf, &fdata, path); + } + + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + errno = EACCES; + break; + default: + errno = ENOENT; + break; + } + + /* To match POSIX behavior, set ENOTDIR when any of the folders in the + * file path is a regular file, otherwise set ENOENT. + */ + if (errno == ENOENT && posix_enotdir) { + size_t path_len = wcslen(path); + + /* scan up path until we find an existing item */ + while (1) { + DWORD attrs; + + /* remove last directory component */ + for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--); + + if (path_len <= 0) + break; + + path[path_len] = L'\0'; + attrs = GetFileAttributesW(path); + + if (attrs != INVALID_FILE_ATTRIBUTES) { + if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) + errno = ENOTDIR; + break; + } + } + } + + return -1; +} + +static int do_lstat(const char *path, struct stat *buf, bool posixly_correct) +{ + git_win32_path path_w; + int len; + + if ((len = git_win32_path_from_utf8(path_w, path)) < 0) + return -1; + + git_win32_path_trim_end(path_w, len); + + return lstat_w(path_w, buf, posixly_correct); +} + +int p_lstat(const char *filename, struct stat *buf) +{ + return do_lstat(filename, buf, false); +} + +int p_lstat_posixly(const char *filename, struct stat *buf) +{ + return do_lstat(filename, buf, true); +} + +int p_readlink(const char *path, char *buf, size_t bufsiz) +{ + git_win32_path path_w, target_w; + git_win32_utf8_path target; + int len; + + /* readlink(2) does not NULL-terminate the string written + * to the target buffer. Furthermore, the target buffer need + * not be large enough to hold the entire result. A truncated + * result should be written in this case. Since this truncation + * could occur in the middle of the encoding of a code point, + * we need to buffer the result on the stack. */ + + if (git_win32_path_from_utf8(path_w, path) < 0 || + git_win32_path_readlink_w(target_w, path_w) < 0 || + (len = git_win32_path_to_utf8(target, target_w)) < 0) + return -1; + + bufsiz = min((size_t)len, bufsiz); + memcpy(buf, target, bufsiz); + + return (int)bufsiz; +} + +static bool target_is_dir(const char *target, const char *path) +{ + git_str resolved = GIT_STR_INIT; + git_win32_path resolved_w; + bool isdir = true; + + if (git_fs_path_is_absolute(target)) + git_win32_path_from_utf8(resolved_w, target); + else if (git_fs_path_dirname_r(&resolved, path) < 0 || + git_fs_path_apply_relative(&resolved, target) < 0 || + git_win32_path_from_utf8(resolved_w, resolved.ptr) < 0) + goto out; + + isdir = GetFileAttributesW(resolved_w) & FILE_ATTRIBUTE_DIRECTORY; + +out: + git_str_dispose(&resolved); + return isdir; +} + +int p_symlink(const char *target, const char *path) +{ + git_win32_path target_w, path_w; + DWORD dwFlags; + + /* + * Convert both target and path to Windows-style paths. Note that we do + * not want to use `git_win32_path_from_utf8` for converting the target, + * as that function will automatically pre-pend the current working + * directory in case the path is not absolute. As Git will instead use + * relative symlinks, this is not something we want. + */ + if (git_win32_path_from_utf8(path_w, path) < 0 || + git_win32_path_relative_from_utf8(target_w, target) < 0) + return -1; + + dwFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + if (target_is_dir(target, path)) + dwFlags |= SYMBOLIC_LINK_FLAG_DIRECTORY; + + if (!CreateSymbolicLinkW(path_w, target_w, dwFlags)) + return -1; + + return 0; +} + +struct open_opts { + DWORD access; + DWORD sharing; + SECURITY_ATTRIBUTES security; + DWORD creation_disposition; + DWORD attributes; + int osf_flags; +}; + +GIT_INLINE(void) open_opts_from_posix(struct open_opts *opts, int flags, mode_t mode) +{ + memset(opts, 0, sizeof(struct open_opts)); + + switch (flags & (O_WRONLY | O_RDWR)) { + case O_WRONLY: + opts->access = GENERIC_WRITE; + break; + case O_RDWR: + opts->access = GENERIC_READ | GENERIC_WRITE; + break; + default: + opts->access = GENERIC_READ; + break; + } + + opts->sharing = (DWORD)git_win32__createfile_sharemode; + + switch (flags & (O_CREAT | O_TRUNC | O_EXCL)) { + case O_CREAT | O_EXCL: + case O_CREAT | O_TRUNC | O_EXCL: + opts->creation_disposition = CREATE_NEW; + break; + case O_CREAT | O_TRUNC: + opts->creation_disposition = CREATE_ALWAYS; + break; + case O_TRUNC: + opts->creation_disposition = TRUNCATE_EXISTING; + break; + case O_CREAT: + opts->creation_disposition = OPEN_ALWAYS; + break; + default: + opts->creation_disposition = OPEN_EXISTING; + break; + } + + opts->attributes = ((flags & O_CREAT) && !(mode & S_IWRITE)) ? + FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL; + opts->osf_flags = flags & (O_RDONLY | O_APPEND); + + opts->security.nLength = sizeof(SECURITY_ATTRIBUTES); + opts->security.lpSecurityDescriptor = NULL; + opts->security.bInheritHandle = 0; +} + +GIT_INLINE(int) open_once( + const wchar_t *path, + struct open_opts *opts) +{ + int fd; + + HANDLE handle = CreateFileW(path, opts->access, opts->sharing, + &opts->security, opts->creation_disposition, opts->attributes, 0); + + if (handle == INVALID_HANDLE_VALUE) { + if (last_error_retryable()) + return GIT_RETRY; + + set_errno(); + return -1; + } + + if ((fd = _open_osfhandle((intptr_t)handle, opts->osf_flags)) < 0) + CloseHandle(handle); + + return fd; +} + +int p_open(const char *path, int flags, ...) +{ + git_win32_path wpath; + mode_t mode = 0; + struct open_opts opts = {0}; + + #ifdef GIT_DEBUG_STRICT_OPEN + if (strstr(path, "//") != NULL) { + errno = EACCES; + return -1; + } + #endif + + if (git_win32_path_from_utf8(wpath, path) < 0) + return -1; + + if (flags & O_CREAT) { + va_list arg_list; + + va_start(arg_list, flags); + mode = (mode_t)va_arg(arg_list, int); + va_end(arg_list); + } + + open_opts_from_posix(&opts, flags, mode); + + do_with_retries( + open_once(wpath, &opts), + 0); +} + +int p_creat(const char *path, mode_t mode) +{ + return p_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); +} + +int p_utimes(const char *path, const struct p_timeval times[2]) +{ + git_win32_path wpath; + int fd, error; + DWORD attrs_orig, attrs_new = 0; + struct open_opts opts = { 0 }; + + if (git_win32_path_from_utf8(wpath, path) < 0) + return -1; + + attrs_orig = GetFileAttributesW(wpath); + + if (attrs_orig & FILE_ATTRIBUTE_READONLY) { + attrs_new = attrs_orig & ~FILE_ATTRIBUTE_READONLY; + + if (!SetFileAttributesW(wpath, attrs_new)) { + git_error_set(GIT_ERROR_OS, "failed to set attributes"); + return -1; + } + } + + open_opts_from_posix(&opts, O_RDWR, 0); + + if ((fd = open_once(wpath, &opts)) < 0) { + error = -1; + goto done; + } + + error = p_futimes(fd, times); + close(fd); + +done: + if (attrs_orig != attrs_new) { + DWORD os_error = GetLastError(); + SetFileAttributesW(wpath, attrs_orig); + SetLastError(os_error); + } + + return error; +} + +int p_futimes(int fd, const struct p_timeval times[2]) +{ + HANDLE handle; + FILETIME atime = { 0 }, mtime = { 0 }; + + if (times == NULL) { + SYSTEMTIME st; + + GetSystemTime(&st); + SystemTimeToFileTime(&st, &atime); + SystemTimeToFileTime(&st, &mtime); + } + else { + git_win32__timeval_to_filetime(&atime, times[0]); + git_win32__timeval_to_filetime(&mtime, times[1]); + } + + if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE) + return -1; + + if (SetFileTime(handle, NULL, &atime, &mtime) == 0) + return -1; + + return 0; +} + +int p_getcwd(char *buffer_out, size_t size) +{ + git_win32_path buf; + wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16); + + if (!cwd) + return -1; + + git_win32_path_remove_namespace(cwd, wcslen(cwd)); + + /* Convert the working directory back to UTF-8 */ + if (git__utf16_to_8(buffer_out, size, cwd) < 0) { + DWORD code = GetLastError(); + + if (code == ERROR_INSUFFICIENT_BUFFER) + errno = ERANGE; + else + errno = EINVAL; + + return -1; + } + + git_fs_path_mkposix(buffer_out); + return 0; +} + +static int getfinalpath_w( + git_win32_path dest, + const wchar_t *path) +{ + HANDLE hFile; + DWORD dwChars; + + /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not + * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the + * target of the link. */ + hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (INVALID_HANDLE_VALUE == hFile) + return -1; + + /* Call GetFinalPathNameByHandle */ + dwChars = GetFinalPathNameByHandleW(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED); + CloseHandle(hFile); + + if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16) + return -1; + + /* The path may be delivered to us with a namespace prefix; remove */ + return (int)git_win32_path_remove_namespace(dest, dwChars); +} + +static int follow_and_lstat_link(git_win32_path path, struct stat *buf) +{ + git_win32_path target_w; + + if (getfinalpath_w(target_w, path) < 0) + return -1; + + return lstat_w(target_w, buf, false); +} + +int p_fstat(int fd, struct stat *buf) +{ + BY_HANDLE_FILE_INFORMATION fhInfo; + + HANDLE fh = (HANDLE)_get_osfhandle(fd); + + if (fh == INVALID_HANDLE_VALUE || + !GetFileInformationByHandle(fh, &fhInfo)) { + errno = EBADF; + return -1; + } + + git_win32__file_information_to_stat(buf, &fhInfo); + return 0; +} + +int p_stat(const char *path, struct stat *buf) +{ + git_win32_path path_w; + int len; + + if ((len = git_win32_path_from_utf8(path_w, path)) < 0 || + lstat_w(path_w, buf, false) < 0) + return -1; + + /* The item is a symbolic link or mount point. No need to iterate + * to follow multiple links; use GetFinalPathNameFromHandle. */ + if (S_ISLNK(buf->st_mode)) + return follow_and_lstat_link(path_w, buf); + + return 0; +} + +int p_chdir(const char *path) +{ + git_win32_path buf; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + return _wchdir(buf); +} + +int p_chmod(const char *path, mode_t mode) +{ + git_win32_path buf; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + return _wchmod(buf, mode); +} + +int p_rmdir(const char *path) +{ + git_win32_path buf; + int error; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + error = _wrmdir(buf); + + if (error == -1) { + switch (GetLastError()) { + /* _wrmdir() is documented to return EACCES if "A program has an open + * handle to the directory." This sounds like what everybody else calls + * EBUSY. Let's convert appropriate error codes. + */ + case ERROR_SHARING_VIOLATION: + errno = EBUSY; + break; + + /* This error can be returned when trying to rmdir an extant file. */ + case ERROR_DIRECTORY: + errno = ENOTDIR; + break; + } + } + + return error; +} + +char *p_realpath(const char *orig_path, char *buffer) +{ + git_win32_path orig_path_w, buffer_w; + + if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0) + return NULL; + + /* Note that if the path provided is a relative path, then the current directory + * is used to resolve the path -- which is a concurrency issue because the current + * directory is a process-wide variable. */ + if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + errno = ENAMETOOLONG; + else + errno = EINVAL; + + return NULL; + } + + /* The path must exist. */ + if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { + errno = ENOENT; + return NULL; + } + + if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) { + errno = ENOMEM; + return NULL; + } + + /* Convert the path to UTF-8. If the caller provided a buffer, then it + * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't, + * then we may overflow. */ + if (git_win32_path_to_utf8(buffer, buffer_w) < 0) + return NULL; + + git_fs_path_mkposix(buffer); + + return buffer; +} + +int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) +{ +#if defined(_MSC_VER) + int len; + + if (count == 0) + return _vscprintf(format, argptr); + + #if _MSC_VER >= 1500 + len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr); + #else + len = _vsnprintf(buffer, count, format, argptr); + #endif + + if (len < 0) + return _vscprintf(format, argptr); + + return len; +#else /* MinGW */ + return vsnprintf(buffer, count, format, argptr); +#endif +} + +int p_snprintf(char *buffer, size_t count, const char *format, ...) +{ + va_list va; + int r; + + va_start(va, format); + r = p_vsnprintf(buffer, count, format, va); + va_end(va); + + return r; +} + +int p_access(const char *path, mode_t mode) +{ + git_win32_path buf; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + return _waccess(buf, mode & WIN32_MODE_MASK); +} + +GIT_INLINE(int) rename_once(const wchar_t *from, const wchar_t *to) +{ + if (MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) + return 0; + + if (last_error_retryable()) + return GIT_RETRY; + + set_errno(); + return -1; +} + +int p_rename(const char *from, const char *to) +{ + git_win32_path wfrom, wto; + + if (git_win32_path_from_utf8(wfrom, from) < 0 || + git_win32_path_from_utf8(wto, to) < 0) + return -1; + + do_with_retries(rename_once(wfrom, wto), ensure_writable(wto)); +} + +int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags) +{ + if ((size_t)((int)length) != length) + return -1; /* git_error_set will be done by caller */ + + return recv(socket, buffer, (int)length, flags); +} + +int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags) +{ + if ((size_t)((int)length) != length) + return -1; /* git_error_set will be done by caller */ + + return send(socket, buffer, (int)length, flags); +} + +/** + * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html + * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that + */ +struct tm * +p_localtime_r (const time_t *timer, struct tm *result) +{ + struct tm *local_result; + local_result = localtime (timer); + + if (local_result == NULL || result == NULL) + return NULL; + + memcpy (result, local_result, sizeof (struct tm)); + return result; +} +struct tm * +p_gmtime_r (const time_t *timer, struct tm *result) +{ + struct tm *local_result; + local_result = gmtime (timer); + + if (local_result == NULL || result == NULL) + return NULL; + + memcpy (result, local_result, sizeof (struct tm)); + return result; +} + +int p_inet_pton(int af, const char *src, void *dst) +{ + struct sockaddr_storage sin; + void *addr; + int sin_len = sizeof(struct sockaddr_storage), addr_len; + int error = 0; + + if (af == AF_INET) { + addr = &((struct sockaddr_in *)&sin)->sin_addr; + addr_len = sizeof(struct in_addr); + } else if (af == AF_INET6) { + addr = &((struct sockaddr_in6 *)&sin)->sin6_addr; + addr_len = sizeof(struct in6_addr); + } else { + errno = EAFNOSUPPORT; + return -1; + } + + if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) { + memcpy(dst, addr, addr_len); + return 1; + } + + switch(WSAGetLastError()) { + case WSAEINVAL: + return 0; + case WSAEFAULT: + errno = ENOSPC; + return -1; + case WSA_NOT_ENOUGH_MEMORY: + errno = ENOMEM; + return -1; + } + + errno = EINVAL; + return -1; +} + +ssize_t p_pread(int fd, void *data, size_t size, off64_t offset) +{ + HANDLE fh; + DWORD rsize = 0; + OVERLAPPED ov = {0}; + LARGE_INTEGER pos = {0}; + off64_t final_offset = 0; + + /* Fail if the final offset would have overflowed to match POSIX semantics. */ + if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) { + errno = EINVAL; + return -1; + } + + /* + * Truncate large writes to the maximum allowable size: the caller + * needs to always call this in a loop anyways. + */ + if (size > INT32_MAX) { + size = INT32_MAX; + } + + pos.QuadPart = offset; + ov.Offset = pos.LowPart; + ov.OffsetHigh = pos.HighPart; + fh = (HANDLE)_get_osfhandle(fd); + + if (ReadFile(fh, data, (DWORD)size, &rsize, &ov)) { + return (ssize_t)rsize; + } + + set_errno(); + return -1; +} + +ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset) +{ + HANDLE fh; + DWORD wsize = 0; + OVERLAPPED ov = {0}; + LARGE_INTEGER pos = {0}; + off64_t final_offset = 0; + + /* Fail if the final offset would have overflowed to match POSIX semantics. */ + if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) { + errno = EINVAL; + return -1; + } + + /* + * Truncate large writes to the maximum allowable size: the caller + * needs to always call this in a loop anyways. + */ + if (size > INT32_MAX) { + size = INT32_MAX; + } + + pos.QuadPart = offset; + ov.Offset = pos.LowPart; + ov.OffsetHigh = pos.HighPart; + fh = (HANDLE)_get_osfhandle(fd); + + if (WriteFile(fh, data, (DWORD)size, &wsize, &ov)) { + return (ssize_t)wsize; + } + + set_errno(); + return -1; +} diff --git a/src/libgit2/win32/precompiled.c b/src/libgit2/win32/precompiled.c new file mode 100644 index 000000000..5f656a45d --- /dev/null +++ b/src/libgit2/win32/precompiled.c @@ -0,0 +1 @@ +#include "precompiled.h" diff --git a/src/libgit2/win32/precompiled.h b/src/libgit2/win32/precompiled.h new file mode 100644 index 000000000..806b1698a --- /dev/null +++ b/src/libgit2/win32/precompiled.h @@ -0,0 +1,21 @@ +#include "common.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#ifdef GIT_THREADS + #include "win32/thread.h" +#endif + +#include "git2.h" diff --git a/src/libgit2/win32/reparse.h b/src/libgit2/win32/reparse.h new file mode 100644 index 000000000..23312319f --- /dev/null +++ b/src/libgit2/win32/reparse.h @@ -0,0 +1,57 @@ +/* +* Copyright (C) the libgit2 contributors. All rights reserved. +* +* This file is part of libgit2, distributed under the GNU GPL v2 with +* a Linking Exception. For full terms see the included COPYING file. +*/ + +#ifndef INCLUDE_win32_reparse_h__ +#define INCLUDE_win32_reparse_h__ + +/* This structure is defined on MSDN at +* http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx +* +* It was formerly included in the Windows 2000 SDK and remains defined in +* MinGW, so we must define it with a silly name to avoid conflicting. +*/ +typedef struct _GIT_REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLink; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPoint; + struct { + UCHAR DataBuffer[1]; + } Generic; + } ReparseBuffer; +} GIT_REPARSE_DATA_BUFFER; + +#define REPARSE_DATA_HEADER_SIZE 8 +#define REPARSE_DATA_MOUNTPOINT_HEADER_SIZE 8 +#define REPARSE_DATA_UNION_SIZE 12 + +/* Missing in MinGW */ +#ifndef FSCTL_GET_REPARSE_POINT +# define FSCTL_GET_REPARSE_POINT 0x000900a8 +#endif + +/* Missing in MinGW */ +#ifndef FSCTL_SET_REPARSE_POINT +# define FSCTL_SET_REPARSE_POINT 0x000900a4 +#endif + +#endif diff --git a/src/libgit2/win32/thread.c b/src/libgit2/win32/thread.c new file mode 100644 index 000000000..f5cacd320 --- /dev/null +++ b/src/libgit2/win32/thread.c @@ -0,0 +1,262 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "thread.h" +#include "runtime.h" + +#define CLEAN_THREAD_EXIT 0x6F012842 + +typedef void (WINAPI *win32_srwlock_fn)(GIT_SRWLOCK *); + +static win32_srwlock_fn win32_srwlock_initialize; +static win32_srwlock_fn win32_srwlock_acquire_shared; +static win32_srwlock_fn win32_srwlock_release_shared; +static win32_srwlock_fn win32_srwlock_acquire_exclusive; +static win32_srwlock_fn win32_srwlock_release_exclusive; + +static DWORD fls_index; + +/* The thread procedure stub used to invoke the caller's procedure + * and capture the return value for later collection. Windows will + * only hold a DWORD, but we need to be able to store an entire + * void pointer. This requires the indirection. */ +static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter) +{ + git_thread *thread = lpParameter; + + /* Set the current thread for `git_thread_exit` */ + FlsSetValue(fls_index, thread); + + thread->result = thread->proc(thread->param); + + return CLEAN_THREAD_EXIT; +} + +static void git_threads_global_shutdown(void) +{ + FlsFree(fls_index); +} + +int git_threads_global_init(void) +{ + HMODULE hModule = GetModuleHandleW(L"kernel32"); + + if (hModule) { + win32_srwlock_initialize = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "InitializeSRWLock"); + win32_srwlock_acquire_shared = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "AcquireSRWLockShared"); + win32_srwlock_release_shared = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "ReleaseSRWLockShared"); + win32_srwlock_acquire_exclusive = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "AcquireSRWLockExclusive"); + win32_srwlock_release_exclusive = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "ReleaseSRWLockExclusive"); + } + + if ((fls_index = FlsAlloc(NULL)) == FLS_OUT_OF_INDEXES) + return -1; + + return git_runtime_shutdown_register(git_threads_global_shutdown); +} + +int git_thread_create( + git_thread *GIT_RESTRICT thread, + void *(*start_routine)(void*), + void *GIT_RESTRICT arg) +{ + thread->result = NULL; + thread->param = arg; + thread->proc = start_routine; + thread->thread = CreateThread( + NULL, 0, git_win32__threadproc, thread, 0, NULL); + + return thread->thread ? 0 : -1; +} + +int git_thread_join( + git_thread *thread, + void **value_ptr) +{ + DWORD exit; + + if (WaitForSingleObject(thread->thread, INFINITE) != WAIT_OBJECT_0) + return -1; + + if (!GetExitCodeThread(thread->thread, &exit)) { + CloseHandle(thread->thread); + return -1; + } + + /* Check for the thread having exited uncleanly. If exit was unclean, + * then we don't have a return value to give back to the caller. */ + GIT_ASSERT(exit == CLEAN_THREAD_EXIT); + + if (value_ptr) + *value_ptr = thread->result; + + CloseHandle(thread->thread); + return 0; +} + +void git_thread_exit(void *value) +{ + git_thread *thread = FlsGetValue(fls_index); + + if (thread) + thread->result = value; + + ExitThread(CLEAN_THREAD_EXIT); +} + +size_t git_thread_currentid(void) +{ + return GetCurrentThreadId(); +} + +int git_mutex_init(git_mutex *GIT_RESTRICT mutex) +{ + InitializeCriticalSection(mutex); + return 0; +} + +int git_mutex_free(git_mutex *mutex) +{ + DeleteCriticalSection(mutex); + return 0; +} + +int git_mutex_lock(git_mutex *mutex) +{ + EnterCriticalSection(mutex); + return 0; +} + +int git_mutex_unlock(git_mutex *mutex) +{ + LeaveCriticalSection(mutex); + return 0; +} + +int git_cond_init(git_cond *cond) +{ + /* This is an auto-reset event. */ + *cond = CreateEventW(NULL, FALSE, FALSE, NULL); + GIT_ASSERT(*cond); + + /* If we can't create the event, claim that the reason was out-of-memory. + * The actual reason can be fetched with GetLastError(). */ + return *cond ? 0 : ENOMEM; +} + +int git_cond_free(git_cond *cond) +{ + BOOL closed; + + if (!cond) + return EINVAL; + + closed = CloseHandle(*cond); + GIT_ASSERT(closed); + GIT_UNUSED(closed); + + *cond = NULL; + return 0; +} + +int git_cond_wait(git_cond *cond, git_mutex *mutex) +{ + int error; + DWORD wait_result; + + if (!cond || !mutex) + return EINVAL; + + /* The caller must be holding the mutex. */ + error = git_mutex_unlock(mutex); + + if (error) + return error; + + wait_result = WaitForSingleObject(*cond, INFINITE); + GIT_ASSERT(WAIT_OBJECT_0 == wait_result); + GIT_UNUSED(wait_result); + + return git_mutex_lock(mutex); +} + +int git_cond_signal(git_cond *cond) +{ + BOOL signaled; + + if (!cond) + return EINVAL; + + signaled = SetEvent(*cond); + GIT_ASSERT(signaled); + GIT_UNUSED(signaled); + + return 0; +} + +int git_rwlock_init(git_rwlock *GIT_RESTRICT lock) +{ + if (win32_srwlock_initialize) + win32_srwlock_initialize(&lock->native.srwl); + else + InitializeCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_rdlock(git_rwlock *lock) +{ + if (win32_srwlock_acquire_shared) + win32_srwlock_acquire_shared(&lock->native.srwl); + else + EnterCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_rdunlock(git_rwlock *lock) +{ + if (win32_srwlock_release_shared) + win32_srwlock_release_shared(&lock->native.srwl); + else + LeaveCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_wrlock(git_rwlock *lock) +{ + if (win32_srwlock_acquire_exclusive) + win32_srwlock_acquire_exclusive(&lock->native.srwl); + else + EnterCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_wrunlock(git_rwlock *lock) +{ + if (win32_srwlock_release_exclusive) + win32_srwlock_release_exclusive(&lock->native.srwl); + else + LeaveCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_free(git_rwlock *lock) +{ + if (!win32_srwlock_initialize) + DeleteCriticalSection(&lock->native.csec); + git__memzero(lock, sizeof(*lock)); + return 0; +} diff --git a/src/libgit2/win32/thread.h b/src/libgit2/win32/thread.h new file mode 100644 index 000000000..8305036b4 --- /dev/null +++ b/src/libgit2/win32/thread.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_win32_thread_h__ +#define INCLUDE_win32_thread_h__ + +#include "common.h" + +#if defined (_MSC_VER) +# define GIT_RESTRICT __restrict +#else +# define GIT_RESTRICT __restrict__ +#endif + +typedef struct { + HANDLE thread; + void *(*proc)(void *); + void *param; + void *result; +} git_thread; + +typedef CRITICAL_SECTION git_mutex; +typedef HANDLE git_cond; + +typedef struct { void *Ptr; } GIT_SRWLOCK; + +typedef struct { + union { + GIT_SRWLOCK srwl; + CRITICAL_SECTION csec; + } native; +} git_rwlock; + +int git_threads_global_init(void); + +int git_thread_create(git_thread *GIT_RESTRICT, + void *(*) (void *), + void *GIT_RESTRICT); +int git_thread_join(git_thread *, void **); +size_t git_thread_currentid(void); +void git_thread_exit(void *); + +int git_mutex_init(git_mutex *GIT_RESTRICT mutex); +int git_mutex_free(git_mutex *); +int git_mutex_lock(git_mutex *); +int git_mutex_unlock(git_mutex *); + +int git_cond_init(git_cond *); +int git_cond_free(git_cond *); +int git_cond_wait(git_cond *, git_mutex *); +int git_cond_signal(git_cond *); + +int git_rwlock_init(git_rwlock *GIT_RESTRICT lock); +int git_rwlock_rdlock(git_rwlock *); +int git_rwlock_rdunlock(git_rwlock *); +int git_rwlock_wrlock(git_rwlock *); +int git_rwlock_wrunlock(git_rwlock *); +int git_rwlock_free(git_rwlock *); + +#endif diff --git a/src/libgit2/win32/utf-conv.c b/src/libgit2/win32/utf-conv.c new file mode 100644 index 000000000..4bde3023a --- /dev/null +++ b/src/libgit2/win32/utf-conv.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "utf-conv.h" + +GIT_INLINE(void) git__set_errno(void) +{ + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + errno = ENAMETOOLONG; + else + errno = EINVAL; +} + +/** + * Converts a UTF-8 string to wide characters. + * + * @param dest The buffer to receive the wide string. + * @param dest_size The size of the buffer, in characters. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src) +{ + int len; + + /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to + * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's + * length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */ + if ((len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1) < 0) + git__set_errno(); + + return len; +} + +/** + * Converts a wide string to UTF-8. + * + * @param dest The buffer to receive the UTF-8 string. + * @param dest_size The size of the buffer, in bytes. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src) +{ + int len; + + /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to + * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's + * length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */ + if ((len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0) + git__set_errno(); + + return len; +} + +/** + * Converts a UTF-8 string to wide characters. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to the wide string. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +int git__utf8_to_16_alloc(wchar_t **dest, const char *src) +{ + int utf16_size; + + *dest = NULL; + + /* Length of -1 indicates NULL termination of the input string */ + utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0); + + if (!utf16_size) { + git__set_errno(); + return -1; + } + + if (!(*dest = git__mallocarray(utf16_size, sizeof(wchar_t)))) { + errno = ENOMEM; + return -1; + } + + utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size); + + if (!utf16_size) { + git__set_errno(); + + git__free(*dest); + *dest = NULL; + } + + /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL + * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, + * so underflow is not possible */ + return utf16_size - 1; +} + +/** + * Converts a wide string to UTF-8. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to the UTF-8 string. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +int git__utf16_to_8_alloc(char **dest, const wchar_t *src) +{ + int utf8_size; + + *dest = NULL; + + /* Length of -1 indicates NULL termination of the input string */ + utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL); + + if (!utf8_size) { + git__set_errno(); + return -1; + } + + *dest = git__malloc(utf8_size); + + if (!*dest) { + errno = ENOMEM; + return -1; + } + + utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, *dest, utf8_size, NULL, NULL); + + if (!utf8_size) { + git__set_errno(); + + git__free(*dest); + *dest = NULL; + } + + /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL + * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, + * so underflow is not possible */ + return utf8_size - 1; +} diff --git a/src/libgit2/win32/utf-conv.h b/src/libgit2/win32/utf-conv.h new file mode 100644 index 000000000..6090a4b35 --- /dev/null +++ b/src/libgit2/win32/utf-conv.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_utf_conv_h__ +#define INCLUDE_win32_utf_conv_h__ + +#include "common.h" + +#include + +#ifndef WC_ERR_INVALID_CHARS +# define WC_ERR_INVALID_CHARS 0x80 +#endif + +/** + * Converts a UTF-8 string to wide characters. + * + * @param dest The buffer to receive the wide string. + * @param dest_size The size of the buffer, in characters. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src); + +/** + * Converts a wide string to UTF-8. + * + * @param dest The buffer to receive the UTF-8 string. + * @param dest_size The size of the buffer, in bytes. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src); + +/** + * Converts a UTF-8 string to wide characters. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to the wide string. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +int git__utf8_to_16_alloc(wchar_t **dest, const char *src); + +/** + * Converts a wide string to UTF-8. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to the UTF-8 string. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +int git__utf16_to_8_alloc(char **dest, const wchar_t *src); + +#endif diff --git a/src/libgit2/win32/version.h b/src/libgit2/win32/version.h new file mode 100644 index 000000000..79667697f --- /dev/null +++ b/src/libgit2/win32/version.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_version_h__ +#define INCLUDE_win32_version_h__ + +#include + +GIT_INLINE(int) git_has_win32_version(int major, int minor, int service_pack) +{ + OSVERSIONINFOEX version_test = {0}; + DWORD version_test_mask; + DWORDLONG version_condition_mask = 0; + + version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + version_test.dwMajorVersion = major; + version_test.dwMinorVersion = minor; + version_test.wServicePackMajor = (WORD)service_pack; + version_test.wServicePackMinor = 0; + + version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR); + + VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); + + if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask)) + return 0; + + return 1; +} + +#endif diff --git a/src/libgit2/win32/w32_buffer.c b/src/libgit2/win32/w32_buffer.c new file mode 100644 index 000000000..6fee8203c --- /dev/null +++ b/src/libgit2/win32/w32_buffer.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "w32_buffer.h" + +#include "utf-conv.h" + +GIT_INLINE(int) handle_wc_error(void) +{ + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + errno = ENAMETOOLONG; + else + errno = EINVAL; + + return -1; +} + +int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w) +{ + int utf8_len, utf8_write_len; + size_t new_size; + + if (!len_w) { + return 0; + } else if (len_w > INT_MAX) { + git_error_set_oom(); + return -1; + } + + GIT_ASSERT(string_w); + + /* Measure the string necessary for conversion */ + if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, NULL, 0, NULL, NULL)) == 0) + return 0; + + GIT_ASSERT(utf8_len > 0); + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, (size_t)utf8_len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + + if (git_str_grow(buf, new_size) < 0) + return -1; + + if ((utf8_write_len = WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, &buf->ptr[buf->size], utf8_len, NULL, NULL)) == 0) + return handle_wc_error(); + + GIT_ASSERT(utf8_write_len == utf8_len); + + buf->size += utf8_write_len; + buf->ptr[buf->size] = '\0'; + return 0; +} diff --git a/src/libgit2/win32/w32_buffer.h b/src/libgit2/win32/w32_buffer.h new file mode 100644 index 000000000..4227296d8 --- /dev/null +++ b/src/libgit2/win32/w32_buffer.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_w32_buffer_h__ +#define INCLUDE_win32_w32_buffer_h__ + +#include "common.h" +#include "str.h" + +/** + * Convert a wide character string to UTF-8 and append the results to the + * buffer. + */ +int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w); + +#endif diff --git a/src/libgit2/win32/w32_common.h b/src/libgit2/win32/w32_common.h new file mode 100644 index 000000000..c20b3e85e --- /dev/null +++ b/src/libgit2/win32/w32_common.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_win32_w32_common_h__ +#define INCLUDE_win32_w32_common_h__ + +#include + +/* + * 4096 is the max allowed Git path. `MAX_PATH` (260) is the typical max allowed + * Windows path length, however win32 Unicode APIs generally allow up to 32,767 + * if prefixed with "\\?\" (i.e. converted to an NT-style name). + */ +#define GIT_WIN_PATH_MAX GIT_PATH_MAX + +/* + * Provides a large enough buffer to support Windows Git paths: + * GIT_WIN_PATH_MAX is 4096, corresponding to a maximum path length of 4095 + * characters plus a NULL terminator. Prefixing with "\\?\" adds 4 characters, + * but if the original was a UNC path, then we turn "\\server\share" into + * "\\?\UNC\server\share". So we replace the first two characters with + * 8 characters, a net gain of 6, so the maximum length is GIT_WIN_PATH_MAX+6. + */ +#define GIT_WIN_PATH_UTF16 GIT_WIN_PATH_MAX+6 + +/* Maximum size of a UTF-8 Win32 Git path. We remove the "\\?\" or "\\?\UNC\" + * prefixes for presentation, bringing us back to 4095 (non-NULL) + * characters. UTF-8 does have 4-byte sequences, but they are encoded in + * UTF-16 using surrogate pairs, which takes up the space of two characters. + * Two characters in the range U+0800 -> U+FFFF take up more space in UTF-8 + * (6 bytes) than one surrogate pair (4 bytes). + */ +#define GIT_WIN_PATH_UTF8 ((GIT_WIN_PATH_MAX - 1) * 3 + 1) + +/* + * The length of a Windows "shortname", for 8.3 compatibility. + */ +#define GIT_WIN_PATH_SHORTNAME 13 + +/* Win32 path types */ +typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16]; +typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8]; + +#endif diff --git a/src/libgit2/win32/w32_leakcheck.c b/src/libgit2/win32/w32_leakcheck.c new file mode 100644 index 000000000..0f095de12 --- /dev/null +++ b/src/libgit2/win32/w32_leakcheck.c @@ -0,0 +1,581 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "w32_leakcheck.h" + +#if defined(GIT_WIN32_LEAKCHECK) + +#include "Windows.h" +#include "Dbghelp.h" +#include "win32/posix.h" +#include "hash.h" +#include "runtime.h" + +/* Stack frames (for stack tracing, below) */ + +static bool g_win32_stack_initialized = false; +static HANDLE g_win32_stack_process = INVALID_HANDLE_VALUE; +static git_win32_leakcheck_stack_aux_cb_alloc g_aux_cb_alloc = NULL; +static git_win32_leakcheck_stack_aux_cb_lookup g_aux_cb_lookup = NULL; + +int git_win32_leakcheck_stack_set_aux_cb( + git_win32_leakcheck_stack_aux_cb_alloc cb_alloc, + git_win32_leakcheck_stack_aux_cb_lookup cb_lookup) +{ + g_aux_cb_alloc = cb_alloc; + g_aux_cb_lookup = cb_lookup; + + return 0; +} + +/** + * Load symbol table data. This should be done in the primary + * thread at startup (under a lock if there are other threads + * active). + */ +void git_win32_leakcheck_stack_init(void) +{ + if (!g_win32_stack_initialized) { + g_win32_stack_process = GetCurrentProcess(); + SymSetOptions(SYMOPT_LOAD_LINES); + SymInitialize(g_win32_stack_process, NULL, TRUE); + g_win32_stack_initialized = true; + } +} + +/** + * Cleanup symbol table data. This should be done in the + * primary thead at shutdown (under a lock if there are other + * threads active). + */ +void git_win32_leakcheck_stack_cleanup(void) +{ + if (g_win32_stack_initialized) { + SymCleanup(g_win32_stack_process); + g_win32_stack_process = INVALID_HANDLE_VALUE; + g_win32_stack_initialized = false; + } +} + +int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip) +{ + if (!g_win32_stack_initialized) { + git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); + return GIT_ERROR; + } + + memset(pdata, 0, sizeof(*pdata)); + pdata->nr_frames = RtlCaptureStackBackTrace( + skip+1, GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES, pdata->frames, NULL); + + /* If an "aux" data provider was registered, ask it to capture + * whatever data it needs and give us an "aux_id" to it so that + * we can refer to it later when reporting. + */ + if (g_aux_cb_alloc) + (g_aux_cb_alloc)(&pdata->aux_id); + + return 0; +} + +int git_win32_leakcheck_stack_compare( + git_win32_leakcheck_stack_raw_data *d1, + git_win32_leakcheck_stack_raw_data *d2) +{ + return memcmp(d1, d2, sizeof(*d1)); +} + +int git_win32_leakcheck_stack_format( + char *pbuf, size_t buf_len, + const git_win32_leakcheck_stack_raw_data *pdata, + const char *prefix, const char *suffix) +{ +#define MY_MAX_FILENAME 255 + + /* SYMBOL_INFO has char FileName[1] at the end. The docs say to + * to malloc it with extra space for your desired max filename. + */ + struct { + SYMBOL_INFO symbol; + char extra[MY_MAX_FILENAME + 1]; + } s; + + IMAGEHLP_LINE64 line; + size_t buf_used = 0; + unsigned int k; + char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */ + size_t detail_len; + + if (!g_win32_stack_initialized) { + git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); + return GIT_ERROR; + } + + if (!prefix) + prefix = "\t"; + if (!suffix) + suffix = "\n"; + + memset(pbuf, 0, buf_len); + + memset(&s, 0, sizeof(s)); + s.symbol.MaxNameLen = MY_MAX_FILENAME; + s.symbol.SizeOfStruct = sizeof(SYMBOL_INFO); + + memset(&line, 0, sizeof(line)); + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + for (k=0; k < pdata->nr_frames; k++) { + DWORD64 frame_k = (DWORD64)pdata->frames[k]; + DWORD dwUnused; + + if (SymFromAddr(g_win32_stack_process, frame_k, 0, &s.symbol) && + SymGetLineFromAddr64(g_win32_stack_process, frame_k, &dwUnused, &line)) { + const char *pslash; + const char *pfile; + + pslash = strrchr(line.FileName, '\\'); + pfile = ((pslash) ? (pslash+1) : line.FileName); + p_snprintf(detail, sizeof(detail), "%s%s:%d> %s%s", + prefix, pfile, line.LineNumber, s.symbol.Name, suffix); + } else { + /* This happens when we cross into another module. + * For example, in CLAR tests, this is typically + * the CRT startup code. Just print an unknown + * frame and continue. + */ + p_snprintf(detail, sizeof(detail), "%s??%s", prefix, suffix); + } + detail_len = strlen(detail); + + if (buf_len < (buf_used + detail_len + 1)) { + /* we don't have room for this frame in the buffer, so just stop. */ + break; + } + + memcpy(&pbuf[buf_used], detail, detail_len); + buf_used += detail_len; + } + + /* "aux_id" 0 is reserved to mean no aux data. This is needed to handle + * allocs that occur before the aux callbacks were registered. + */ + if (pdata->aux_id > 0) { + p_snprintf(detail, sizeof(detail), "%saux_id: %d%s", + prefix, pdata->aux_id, suffix); + detail_len = strlen(detail); + if ((buf_used + detail_len + 1) < buf_len) { + memcpy(&pbuf[buf_used], detail, detail_len); + buf_used += detail_len; + } + + /* If an "aux" data provider is still registered, ask it to append its detailed + * data to the end of ours using the "aux_id" it gave us when this de-duped + * item was created. + */ + if (g_aux_cb_lookup) + (g_aux_cb_lookup)(pdata->aux_id, &pbuf[buf_used], (buf_len - buf_used - 1)); + } + + return GIT_OK; +} + +int git_win32_leakcheck_stack( + char * pbuf, size_t buf_len, + int skip, + const char *prefix, const char *suffix) +{ + git_win32_leakcheck_stack_raw_data data; + int error; + + if ((error = git_win32_leakcheck_stack_capture(&data, skip)) < 0) + return error; + if ((error = git_win32_leakcheck_stack_format(pbuf, buf_len, &data, prefix, suffix)) < 0) + return error; + return 0; +} + +/* Stack tracing */ + +#define STACKTRACE_UID_LEN (15) + +/** + * The stacktrace of an allocation can be distilled + * to a unique id based upon the stackframe pointers + * and ignoring any size arguments. We will use these + * UIDs as the (char const*) __FILE__ argument we + * give to the CRT malloc routines. + */ +typedef struct { + char uid[STACKTRACE_UID_LEN + 1]; +} git_win32_leakcheck_stacktrace_uid; + +/** + * All mallocs with the same stacktrace will be de-duped + * and aggregated into this row. + */ +typedef struct { + git_win32_leakcheck_stacktrace_uid uid; /* must be first */ + git_win32_leakcheck_stack_raw_data raw_data; + unsigned int count_allocs; /* times this alloc signature seen since init */ + unsigned int count_allocs_at_last_checkpoint; /* times since last mark */ + unsigned int transient_count_leaks; /* sum of leaks */ +} git_win32_leakcheck_stacktrace_row; + +static CRITICAL_SECTION g_crtdbg_stacktrace_cs; + +/** + * CRTDBG memory leak tracking takes a "char const * const file_name" + * and stores the pointer in the heap data (instead of allocing a copy + * for itself). Normally, this is not a problem, since we usually pass + * in __FILE__. But I'm going to lie to it and pass in the address of + * the UID in place of the file_name. Also, I do not want to alloc the + * stacktrace data (because we are called from inside our alloc routines). + * Therefore, I'm creating a very large static pool array to store row + * data. This also eliminates the temptation to realloc it (and move the + * UID pointers). + * + * And to efficiently look for duplicates we need an index on the rows + * so we can bsearch it. Again, without mallocing. + * + * If we observe more than MY_ROW_LIMIT unique malloc signatures, we + * fall through and use the traditional __FILE__ processing and don't + * try to de-dup them. If your testing hits this limit, just increase + * it and try again. + */ + +#define MY_ROW_LIMIT (2 * 1024 * 1024) +static git_win32_leakcheck_stacktrace_row g_cs_rows[MY_ROW_LIMIT]; +static git_win32_leakcheck_stacktrace_row *g_cs_index[MY_ROW_LIMIT]; + +static unsigned int g_cs_end = MY_ROW_LIMIT; +static unsigned int g_cs_ins = 0; /* insertion point == unique allocs seen */ +static unsigned int g_count_total_allocs = 0; /* number of allocs seen */ +static unsigned int g_transient_count_total_leaks = 0; /* number of total leaks */ +static unsigned int g_transient_count_dedup_leaks = 0; /* number of unique leaks */ +static bool g_limit_reached = false; /* had allocs after we filled row table */ + +static unsigned int g_checkpoint_id = 0; /* to better label leak checkpoints */ +static bool g_transient_leaks_since_mark = false; /* payload for hook */ + +/** + * Compare function for bsearch on g_cs_index table. + */ +static int row_cmp(const void *v1, const void *v2) +{ + git_win32_leakcheck_stack_raw_data *d1 = (git_win32_leakcheck_stack_raw_data*)v1; + git_win32_leakcheck_stacktrace_row *r2 = (git_win32_leakcheck_stacktrace_row *)v2; + + return (git_win32_leakcheck_stack_compare(d1, &r2->raw_data)); +} + +/** + * Unique insert the new data into the row and index tables. + * We have to sort by the stackframe data itself, not the uid. + */ +static git_win32_leakcheck_stacktrace_row * insert_unique( + const git_win32_leakcheck_stack_raw_data *pdata) +{ + size_t pos; + if (git__bsearch(g_cs_index, g_cs_ins, pdata, row_cmp, &pos) < 0) { + /* Append new unique item to row table. */ + memcpy(&g_cs_rows[g_cs_ins].raw_data, pdata, sizeof(*pdata)); + sprintf(g_cs_rows[g_cs_ins].uid.uid, "##%08lx", g_cs_ins); + + /* Insert pointer to it into the proper place in the index table. */ + if (pos < g_cs_ins) + memmove(&g_cs_index[pos+1], &g_cs_index[pos], (g_cs_ins - pos)*sizeof(g_cs_index[0])); + g_cs_index[pos] = &g_cs_rows[g_cs_ins]; + + g_cs_ins++; + } + + g_cs_index[pos]->count_allocs++; + + return g_cs_index[pos]; +} + +/** + * Hook function to receive leak data from the CRT. (This includes + * both ":()" data, but also each of the + * various headers and fields. + * + * Scan this for the special "##" UID forms that we substituted + * for the "". Map back to the row data and + * increment its leak count. + * + * See https://msdn.microsoft.com/en-us/library/74kabxyx.aspx + * + * We suppress the actual crtdbg output. + */ +static int __cdecl report_hook(int nRptType, char *szMsg, int *retVal) +{ + static int hook_result = TRUE; /* FALSE to get stock dump; TRUE to suppress. */ + unsigned int pos; + + *retVal = 0; /* do not invoke debugger */ + + if ((szMsg[0] != '#') || (szMsg[1] != '#')) + return hook_result; + + if (sscanf(&szMsg[2], "%08lx", &pos) < 1) + return hook_result; + if (pos >= g_cs_ins) + return hook_result; + + if (g_transient_leaks_since_mark) { + if (g_cs_rows[pos].count_allocs == g_cs_rows[pos].count_allocs_at_last_checkpoint) + return hook_result; + } + + g_cs_rows[pos].transient_count_leaks++; + + if (g_cs_rows[pos].transient_count_leaks == 1) + g_transient_count_dedup_leaks++; + + g_transient_count_total_leaks++; + + return hook_result; +} + +/** + * Write leak data to all of the various places we need. + * We force the caller to sprintf() the message first + * because we want to avoid fprintf() because it allocs. + */ +static void my_output(const char *buf) +{ + fwrite(buf, strlen(buf), 1, stderr); + OutputDebugString(buf); +} + +/** + * For each row with leaks, dump a stacktrace for it. + */ +static void dump_summary(const char *label) +{ + unsigned int k; + char buf[10 * 1024]; + + if (g_transient_count_total_leaks == 0) + return; + + fflush(stdout); + fflush(stderr); + my_output("\n"); + + if (g_limit_reached) { + sprintf(buf, + "LEAK SUMMARY: de-dup row table[%d] filled. Increase MY_ROW_LIMIT.\n", + MY_ROW_LIMIT); + my_output(buf); + } + + if (!label) + label = ""; + + if (g_transient_leaks_since_mark) { + sprintf(buf, "LEAK CHECKPOINT %d: leaks %d unique %d: %s\n", + g_checkpoint_id, g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); + my_output(buf); + } else { + sprintf(buf, "LEAK SUMMARY: TOTAL leaks %d de-duped %d: %s\n", + g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); + my_output(buf); + } + my_output("\n"); + + for (k = 0; k < g_cs_ins; k++) { + if (g_cs_rows[k].transient_count_leaks > 0) { + sprintf(buf, "LEAK: %s leaked %d of %d times:\n", + g_cs_rows[k].uid.uid, + g_cs_rows[k].transient_count_leaks, + g_cs_rows[k].count_allocs); + my_output(buf); + + if (git_win32_leakcheck_stack_format( + buf, sizeof(buf), &g_cs_rows[k].raw_data, + NULL, NULL) >= 0) { + my_output(buf); + } + + my_output("\n"); + } + } + + fflush(stderr); +} + +/** + * Initialize our memory leak tracking and de-dup data structures. + * This should ONLY be called by git_libgit2_init(). + */ +void git_win32_leakcheck_stacktrace_init(void) +{ + InitializeCriticalSection(&g_crtdbg_stacktrace_cs); + + EnterCriticalSection(&g_crtdbg_stacktrace_cs); + + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + + LeaveCriticalSection(&g_crtdbg_stacktrace_cs); +} + +int git_win32_leakcheck_stacktrace_dump( + git_win32_leakcheck_stacktrace_options opt, + const char *label) +{ + _CRT_REPORT_HOOK old; + unsigned int k; + int r = 0; + +#define IS_BIT_SET(o,b) (((o) & (b)) != 0) + + bool b_set_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK); + bool b_leaks_since_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK); + bool b_leaks_total = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL); + bool b_quiet = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET); + + if (b_leaks_since_mark && b_leaks_total) { + git_error_set(GIT_ERROR_INVALID, "cannot combine LEAKS_SINCE_MARK and LEAKS_TOTAL."); + return GIT_ERROR; + } + if (!b_set_mark && !b_leaks_since_mark && !b_leaks_total) { + git_error_set(GIT_ERROR_INVALID, "nothing to do."); + return GIT_ERROR; + } + + EnterCriticalSection(&g_crtdbg_stacktrace_cs); + + if (b_leaks_since_mark || b_leaks_total) { + /* All variables with "transient" in the name are per-dump counters + * and reset before each dump. This lets us handle checkpoints. + */ + g_transient_count_total_leaks = 0; + g_transient_count_dedup_leaks = 0; + for (k = 0; k < g_cs_ins; k++) { + g_cs_rows[k].transient_count_leaks = 0; + } + } + + g_transient_leaks_since_mark = b_leaks_since_mark; + + old = _CrtSetReportHook(report_hook); + _CrtDumpMemoryLeaks(); + _CrtSetReportHook(old); + + if (b_leaks_since_mark || b_leaks_total) { + r = g_transient_count_dedup_leaks; + + if (!b_quiet) + dump_summary(label); + } + + if (b_set_mark) { + for (k = 0; k < g_cs_ins; k++) { + g_cs_rows[k].count_allocs_at_last_checkpoint = g_cs_rows[k].count_allocs; + } + + g_checkpoint_id++; + } + + LeaveCriticalSection(&g_crtdbg_stacktrace_cs); + + return r; +} + +/** + * Shutdown our memory leak tracking and dump summary data. + * This should ONLY be called by git_libgit2_shutdown(). + * + * We explicitly call _CrtDumpMemoryLeaks() during here so + * that we can compute summary data for the leaks. We print + * the stacktrace of each unique leak. + * + * This cleanup does not happen if the app calls exit() + * without calling the libgit2 shutdown code. + * + * This info we print here is independent of any automatic + * reporting during exit() caused by _CRTDBG_LEAK_CHECK_DF. + * Set it in your app if you also want traditional reporting. + */ +void git_win32_leakcheck_stacktrace_cleanup(void) +{ + /* At shutdown/cleanup, dump cumulative leak info + * with everything since startup. This might generate + * extra noise if the caller has been doing checkpoint + * dumps, but it might also eliminate some false + * positives for resources previously reported during + * checkpoints. + */ + git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL, + "CLEANUP"); + + DeleteCriticalSection(&g_crtdbg_stacktrace_cs); +} + +const char *git_win32_leakcheck_stacktrace(int skip, const char *file) +{ + git_win32_leakcheck_stack_raw_data new_data; + git_win32_leakcheck_stacktrace_row *row; + const char * result = file; + + if (git_win32_leakcheck_stack_capture(&new_data, skip+1) < 0) + return result; + + EnterCriticalSection(&g_crtdbg_stacktrace_cs); + + if (g_cs_ins < g_cs_end) { + row = insert_unique(&new_data); + result = row->uid.uid; + } else { + g_limit_reached = true; + } + + g_count_total_allocs++; + + LeaveCriticalSection(&g_crtdbg_stacktrace_cs); + + return result; +} + +static void git_win32_leakcheck_global_shutdown(void) +{ + git_win32_leakcheck_stacktrace_cleanup(); + git_win32_leakcheck_stack_cleanup(); +} + +bool git_win32_leakcheck_has_leaks(void) +{ + return (g_transient_count_total_leaks > 0); +} + +int git_win32_leakcheck_global_init(void) +{ + git_win32_leakcheck_stacktrace_init(); + git_win32_leakcheck_stack_init(); + + return git_runtime_shutdown_register(git_win32_leakcheck_global_shutdown); +} + +#else + +int git_win32_leakcheck_global_init(void) +{ + return 0; +} + +#endif diff --git a/src/libgit2/win32/w32_leakcheck.h b/src/libgit2/win32/w32_leakcheck.h new file mode 100644 index 000000000..cb45e3675 --- /dev/null +++ b/src/libgit2/win32/w32_leakcheck.h @@ -0,0 +1,222 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_win32_leakcheck_h__ +#define INCLUDE_win32_leakcheck_h__ + +#include "common.h" + +/* Initialize the win32 leak checking system. */ +int git_win32_leakcheck_global_init(void); + +#if defined(GIT_WIN32_LEAKCHECK) + +#include +#include + +#include "git2/errors.h" +#include "strnlen.h" + +bool git_win32_leakcheck_has_leaks(void); + +/* Stack frames (for stack tracing, below) */ + +/** + * This type defines a callback to be used to augment a C stacktrace + * with "aux" data. This can be used, for example, to allow LibGit2Sharp + * (or other interpreted consumer libraries) to give us C# stacktrace + * data for the PInvoke. + * + * This callback will be called during crtdbg-instrumented allocs. + * + * @param aux_id [out] A returned "aux_id" representing a unique + * (de-duped at the C# layer) stacktrace. "aux_id" 0 is reserved + * to mean no aux stacktrace data. + */ +typedef void (*git_win32_leakcheck_stack_aux_cb_alloc)(unsigned int *aux_id); + +/** + * This type defines a callback to be used to augment the output of + * a stacktrace. This will be used to request the C# layer format + * the C# stacktrace associated with "aux_id" into the provided + * buffer. + * + * This callback will be called during leak reporting. + * + * @param aux_id The "aux_id" key associated with a stacktrace. + * @param aux_msg A buffer where a formatted message should be written. + * @param aux_msg_len The size of the buffer. + */ +typedef void (*git_win32_leakcheck_stack_aux_cb_lookup)(unsigned int aux_id, char *aux_msg, size_t aux_msg_len); + +/** + * Register an "aux" data provider to augment our C stacktrace data. + * + * This can be used, for example, to allow LibGit2Sharp (or other + * interpreted consumer libraries) to give us the C# stacktrace of + * the PInvoke. + * + * If you choose to use this feature, it should be registered during + * initialization and not changed for the duration of the process. + */ +int git_win32_leakcheck_stack_set_aux_cb( + git_win32_leakcheck_stack_aux_cb_alloc cb_alloc, + git_win32_leakcheck_stack_aux_cb_lookup cb_lookup); + +/** + * Maximum number of stackframes to record for a + * single stacktrace. + */ +#define GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES 30 + +/** + * Wrapper containing the raw unprocessed stackframe + * data for a single stacktrace and any "aux_id". + * + * I put the aux_id first so leaks will be sorted by it. + * So, for example, if a specific callstack in C# leaks + * a repo handle, all of the pointers within the associated + * repo pointer will be grouped together. + */ +typedef struct { + unsigned int aux_id; + unsigned int nr_frames; + void *frames[GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES]; +} git_win32_leakcheck_stack_raw_data; + +/** + * Capture raw stack trace data for the current process/thread. + * + * @param skip Number of initial frames to skip. Pass 0 to + * begin with the caller of this routine. Pass 1 to begin + * with its caller. And so on. + */ +int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip); + +/** + * Compare 2 raw stacktraces with the usual -1,0,+1 result. + * This includes any "aux_id" values in the comparison, so that + * our de-dup is also "aux" context relative. + */ +int git_win32_leakcheck_stack_compare( + git_win32_leakcheck_stack_raw_data *d1, + git_win32_leakcheck_stack_raw_data *d2); + +/** + * Format raw stacktrace data into buffer WITHOUT using any mallocs. + * + * @param prefix String written before each frame; defaults to "\t". + * @param suffix String written after each frame; defaults to "\n". + */ +int git_win32_leakcheck_stack_format( + char *pbuf, size_t buf_len, + const git_win32_leakcheck_stack_raw_data *pdata, + const char *prefix, const char *suffix); + +/** + * Convenience routine to capture and format stacktrace into + * a buffer WITHOUT using any mallocs. This is primarily a + * wrapper for testing. + * + * @param skip Number of initial frames to skip. Pass 0 to + * begin with the caller of this routine. Pass 1 to begin + * with its caller. And so on. + * @param prefix String written before each frame; defaults to "\t". + * @param suffix String written after each frame; defaults to "\n". + */ +int git_win32_leakcheck_stack( + char * pbuf, size_t buf_len, + int skip, + const char *prefix, const char *suffix); + +/* Stack tracing */ + +/* MSVC CRTDBG memory leak reporting. + * + * We DO NOT use the "_CRTDBG_MAP_ALLOC" macro described in the MSVC + * documentation because all allocs/frees in libgit2 already go through + * the "git__" routines defined in this file. Simply using the normal + * reporting mechanism causes all leaks to be attributed to a routine + * here in util.h (ie, the actual call to calloc()) rather than the + * caller of git__calloc(). + * + * Therefore, we declare a set of "git__crtdbg__" routines to replace + * the corresponding "git__" routines and re-define the "git__" symbols + * as macros. This allows us to get and report the file:line info of + * the real caller. + * + * We DO NOT replace the "git__free" routine because it needs to remain + * a function pointer because it is used as a function argument when + * setting up various structure "destructors". + * + * We also DO NOT use the "_CRTDBG_MAP_ALLOC" macro because it causes + * "free" to be remapped to "_free_dbg" and this causes problems for + * structures which define a field named "free". + * + * Finally, CRTDBG must be explicitly enabled and configured at program + * startup. See tests/main.c for an example. + */ + +/** + * Checkpoint options. + */ +typedef enum git_win32_leakcheck_stacktrace_options { + /** + * Set checkpoint marker. + */ + GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK = (1 << 0), + + /** + * Dump leaks since last checkpoint marker. + * May not be combined with _LEAKS_TOTAL. + * + * Note that this may generate false positives for global TLS + * error state and other global caches that aren't cleaned up + * until the thread/process terminates. So when using this + * around a region of interest, also check the final (at exit) + * dump before digging into leaks reported here. + */ + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK = (1 << 1), + + /** + * Dump leaks since init. May not be combined + * with _LEAKS_SINCE_MARK. + */ + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL = (1 << 2), + + /** + * Suppress printing during dumps. + * Just return leak count. + */ + GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET = (1 << 3), + +} git_win32_leakcheck_stacktrace_options; + +/** + * Checkpoint memory state and/or dump unique stack traces of + * current memory leaks. + * + * @return number of unique leaks (relative to requested starting + * point) or error. + */ +int git_win32_leakcheck_stacktrace_dump( + git_win32_leakcheck_stacktrace_options opt, + const char *label); + +/** + * Construct stacktrace and append it to the global buffer. + * Return pointer to start of this string. On any error or + * lack of buffer space, just return the given file buffer + * so it will behave as usual. + * + * This should ONLY be called by our internal memory allocations + * routines. + */ +const char *git_win32_leakcheck_stacktrace(int skip, const char *file); + +#endif +#endif diff --git a/src/libgit2/win32/w32_util.c b/src/libgit2/win32/w32_util.c new file mode 100644 index 000000000..fe4b75bae --- /dev/null +++ b/src/libgit2/win32/w32_util.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "w32_util.h" + +/** + * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. + * The filter string enumerates all items in the directory. + * + * @param dest The buffer to receive the filter string. + * @param src The UTF-8 path of the directory to enumerate. + * @return True if the filter string was created successfully; false otherwise + */ +bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src) +{ + static const wchar_t suffix[] = L"\\*"; + int len = git_win32_path_from_utf8(dest, src); + + /* Ensure the path was converted */ + if (len < 0) + return false; + + /* Ensure that the path does not end with a trailing slash, + * because we're about to add one. Don't rely our trim_end + * helper, because we want to remove the backslash even for + * drive letter paths, in this case. */ + if (len > 0 && + (dest[len - 1] == L'/' || dest[len - 1] == L'\\')) { + dest[len - 1] = L'\0'; + len--; + } + + /* Ensure we have enough room to add the suffix */ + if ((size_t)len >= GIT_WIN_PATH_UTF16 - CONST_STRLEN(suffix)) + return false; + + wcscat(dest, suffix); + return true; +} + +/** + * Ensures the given path (file or folder) has the +H (hidden) attribute set. + * + * @param path The path which should receive the +H bit. + * @return 0 on success; -1 on failure + */ +int git_win32__set_hidden(const char *path, bool hidden) +{ + git_win32_path buf; + DWORD attrs, newattrs; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + attrs = GetFileAttributesW(buf); + + /* Ensure the path exists */ + if (attrs == INVALID_FILE_ATTRIBUTES) + return -1; + + if (hidden) + newattrs = attrs | FILE_ATTRIBUTE_HIDDEN; + else + newattrs = attrs & ~FILE_ATTRIBUTE_HIDDEN; + + if (attrs != newattrs && !SetFileAttributesW(buf, newattrs)) { + git_error_set(GIT_ERROR_OS, "failed to %s hidden bit for '%s'", + hidden ? "set" : "unset", path); + return -1; + } + + return 0; +} + +int git_win32__hidden(bool *out, const char *path) +{ + git_win32_path buf; + DWORD attrs; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + attrs = GetFileAttributesW(buf); + + /* Ensure the path exists */ + if (attrs == INVALID_FILE_ATTRIBUTES) + return -1; + + *out = (attrs & FILE_ATTRIBUTE_HIDDEN) ? true : false; + return 0; +} + +int git_win32__file_attribute_to_stat( + struct stat *st, + const WIN32_FILE_ATTRIBUTE_DATA *attrdata, + const wchar_t *path) +{ + git_win32__stat_init(st, + attrdata->dwFileAttributes, + attrdata->nFileSizeHigh, + attrdata->nFileSizeLow, + attrdata->ftCreationTime, + attrdata->ftLastAccessTime, + attrdata->ftLastWriteTime); + + if (attrdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && path) { + git_win32_path target; + + if (git_win32_path_readlink_w(target, path) >= 0) { + st->st_mode = (st->st_mode & ~S_IFMT) | S_IFLNK; + + /* st_size gets the UTF-8 length of the target name, in bytes, + * not counting the NULL terminator */ + if ((st->st_size = git__utf16_to_8(NULL, 0, target)) < 0) { + git_error_set(GIT_ERROR_OS, "could not convert reparse point name for '%ls'", path); + return -1; + } + } + } + + return 0; +} diff --git a/src/libgit2/win32/w32_util.h b/src/libgit2/win32/w32_util.h new file mode 100644 index 000000000..1321d30e6 --- /dev/null +++ b/src/libgit2/win32/w32_util.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_win32_w32_util_h__ +#define INCLUDE_win32_w32_util_h__ + +#include "common.h" + +#include "utf-conv.h" +#include "posix.h" +#include "path_w32.h" + +/* + +#include "common.h" +#include "path.h" +#include "path_w32.h" +#include "utf-conv.h" +#include "posix.h" +#include "reparse.h" +#include "dir.h" +*/ + + +GIT_INLINE(bool) git_win32__isalpha(wchar_t c) +{ + return ((c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z')); +} + +/** + * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. + * The filter string enumerates all items in the directory. + * + * @param dest The buffer to receive the filter string. + * @param src The UTF-8 path of the directory to enumerate. + * @return True if the filter string was created successfully; false otherwise + */ +bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src); + +/** + * Ensures the given path (file or folder) has the +H (hidden) attribute set + * or unset. + * + * @param path The path that should receive the +H bit. + * @param hidden true to set +H, false to unset it + * @return 0 on success; -1 on failure + */ +extern int git_win32__set_hidden(const char *path, bool hidden); + +/** + * Determines if the given file or folder has the hidden attribute set. + * @param hidden pointer to store hidden value + * @param path The path that should be queried for hiddenness. + * @return 0 on success or an error code. + */ +extern int git_win32__hidden(bool *hidden, const char *path); + +extern int git_win32__file_attribute_to_stat( + struct stat *st, + const WIN32_FILE_ATTRIBUTE_DATA *attrdata, + const wchar_t *path); + +/** + * Converts a FILETIME structure to a struct timespec. + * + * @param FILETIME A pointer to a FILETIME + * @param ts A pointer to the timespec structure to fill in + */ +GIT_INLINE(void) git_win32__filetime_to_timespec( + const FILETIME *ft, + struct timespec *ts) +{ + int64_t winTime = ((int64_t)ft->dwHighDateTime << 32) + ft->dwLowDateTime; + winTime -= INT64_C(116444736000000000); /* Windows to Unix Epoch conversion */ + ts->tv_sec = (time_t)(winTime / 10000000); +#ifdef GIT_USE_NSEC + ts->tv_nsec = (winTime % 10000000) * 100; +#else + ts->tv_nsec = 0; +#endif +} + +GIT_INLINE(void) git_win32__timeval_to_filetime( + FILETIME *ft, const struct p_timeval tv) +{ + int64_t ticks = (tv.tv_sec * INT64_C(10000000)) + + (tv.tv_usec * INT64_C(10)) + INT64_C(116444736000000000); + + ft->dwHighDateTime = ((ticks >> 32) & INT64_C(0xffffffff)); + ft->dwLowDateTime = (ticks & INT64_C(0xffffffff)); +} + +GIT_INLINE(void) git_win32__stat_init( + struct stat *st, + DWORD dwFileAttributes, + DWORD nFileSizeHigh, + DWORD nFileSizeLow, + FILETIME ftCreationTime, + FILETIME ftLastAccessTime, + FILETIME ftLastWriteTime) +{ + mode_t mode = S_IREAD; + + memset(st, 0, sizeof(struct stat)); + + if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + mode |= S_IFDIR; + else + mode |= S_IFREG; + + if ((dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) + mode |= S_IWRITE; + + st->st_ino = 0; + st->st_gid = 0; + st->st_uid = 0; + st->st_nlink = 1; + st->st_mode = mode; + st->st_size = ((int64_t)nFileSizeHigh << 32) + nFileSizeLow; + st->st_dev = _getdrive() - 1; + st->st_rdev = st->st_dev; + git_win32__filetime_to_timespec(&ftLastAccessTime, &(st->st_atim)); + git_win32__filetime_to_timespec(&ftLastWriteTime, &(st->st_mtim)); + git_win32__filetime_to_timespec(&ftCreationTime, &(st->st_ctim)); +} + +GIT_INLINE(void) git_win32__file_information_to_stat( + struct stat *st, + const BY_HANDLE_FILE_INFORMATION *fileinfo) +{ + git_win32__stat_init(st, + fileinfo->dwFileAttributes, + fileinfo->nFileSizeHigh, + fileinfo->nFileSizeLow, + fileinfo->ftCreationTime, + fileinfo->ftLastAccessTime, + fileinfo->ftLastWriteTime); +} + +#endif diff --git a/src/libgit2/win32/win32-compat.h b/src/libgit2/win32/win32-compat.h new file mode 100644 index 000000000..dee40a438 --- /dev/null +++ b/src/libgit2/win32/win32-compat.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_win32_compat_h__ +#define INCLUDE_win32_win32_compat_h__ + +#include +#include +#include +#include +#include + +typedef long suseconds_t; + +struct p_timeval { + time_t tv_sec; + suseconds_t tv_usec; +}; + +struct p_timespec { + time_t tv_sec; + long tv_nsec; +}; + +#define timespec p_timespec + +struct p_stat { + _dev_t st_dev; + _ino_t st_ino; + mode_t st_mode; + short st_nlink; + short st_uid; + short st_gid; + _dev_t st_rdev; + __int64 st_size; + struct timespec st_atim; + struct timespec st_mtim; + struct timespec st_ctim; +#define st_atime st_atim.tv_sec +#define st_mtime st_mtim.tv_sec +#define st_ctime st_ctim.tv_sec +#define st_atime_nsec st_atim.tv_nsec +#define st_mtime_nsec st_mtim.tv_nsec +#define st_ctime_nsec st_ctim.tv_nsec +}; + +#define stat p_stat + +#endif diff --git a/src/libgit2/worktree.c b/src/libgit2/worktree.c new file mode 100644 index 000000000..2ac2274f1 --- /dev/null +++ b/src/libgit2/worktree.c @@ -0,0 +1,652 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "worktree.h" + +#include "buf.h" +#include "repository.h" +#include "path.h" + +#include "git2/branch.h" +#include "git2/commit.h" +#include "git2/worktree.h" + +static bool is_worktree_dir(const char *dir) +{ + git_str buf = GIT_STR_INIT; + int error; + + if (git_str_sets(&buf, dir) < 0) + return -1; + + error = git_fs_path_contains_file(&buf, "commondir") + && git_fs_path_contains_file(&buf, "gitdir") + && git_fs_path_contains_file(&buf, "HEAD"); + + git_str_dispose(&buf); + return error; +} + +int git_worktree_list(git_strarray *wts, git_repository *repo) +{ + git_vector worktrees = GIT_VECTOR_INIT; + git_str path = GIT_STR_INIT; + char *worktree; + size_t i, len; + int error; + + GIT_ASSERT_ARG(wts); + GIT_ASSERT_ARG(repo); + + wts->count = 0; + wts->strings = NULL; + + if ((error = git_str_joinpath(&path, repo->commondir, "worktrees/")) < 0) + goto exit; + if (!git_fs_path_exists(path.ptr) || git_fs_path_is_empty_dir(path.ptr)) + goto exit; + if ((error = git_fs_path_dirload(&worktrees, path.ptr, path.size, 0x0)) < 0) + goto exit; + + len = path.size; + + git_vector_foreach(&worktrees, i, worktree) { + git_str_truncate(&path, len); + git_str_puts(&path, worktree); + + if (!is_worktree_dir(path.ptr)) { + git_vector_remove(&worktrees, i); + git__free(worktree); + } + } + + wts->strings = (char **)git_vector_detach(&wts->count, NULL, &worktrees); + +exit: + git_str_dispose(&path); + + return error; +} + +char *git_worktree__read_link(const char *base, const char *file) +{ + git_str path = GIT_STR_INIT, buf = GIT_STR_INIT; + + GIT_ASSERT_ARG_WITH_RETVAL(base, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(file, NULL); + + if (git_str_joinpath(&path, base, file) < 0) + goto err; + if (git_futils_readbuffer(&buf, path.ptr) < 0) + goto err; + git_str_dispose(&path); + + git_str_rtrim(&buf); + + if (!git_fs_path_is_relative(buf.ptr)) + return git_str_detach(&buf); + + if (git_str_sets(&path, base) < 0) + goto err; + if (git_fs_path_apply_relative(&path, buf.ptr) < 0) + goto err; + git_str_dispose(&buf); + + return git_str_detach(&path); + +err: + git_str_dispose(&buf); + git_str_dispose(&path); + + return NULL; +} + +static int write_wtfile(const char *base, const char *file, const git_str *buf) +{ + git_str path = GIT_STR_INIT; + int err; + + GIT_ASSERT_ARG(base); + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(buf); + + if ((err = git_str_joinpath(&path, base, file)) < 0) + goto out; + + if ((err = git_futils_writebuffer(buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) + goto out; + +out: + git_str_dispose(&path); + + return err; +} + +static int open_worktree_dir(git_worktree **out, const char *parent, const char *dir, const char *name) +{ + git_str gitdir = GIT_STR_INIT; + git_worktree *wt = NULL; + int error = 0; + + if (!is_worktree_dir(dir)) { + error = -1; + goto out; + } + + if ((error = git_path_validate_length(NULL, dir)) < 0) + goto out; + + if ((wt = git__calloc(1, sizeof(*wt))) == NULL) { + error = -1; + goto out; + } + + if ((wt->name = git__strdup(name)) == NULL || + (wt->commondir_path = git_worktree__read_link(dir, "commondir")) == NULL || + (wt->gitlink_path = git_worktree__read_link(dir, "gitdir")) == NULL || + (parent && (wt->parent_path = git__strdup(parent)) == NULL) || + (wt->worktree_path = git_fs_path_dirname(wt->gitlink_path)) == NULL) { + error = -1; + goto out; + } + + if ((error = git_fs_path_prettify_dir(&gitdir, dir, NULL)) < 0) + goto out; + wt->gitdir_path = git_str_detach(&gitdir); + + if ((error = git_worktree_is_locked(NULL, wt)) < 0) + goto out; + wt->locked = !!error; + error = 0; + + *out = wt; + +out: + if (error) + git_worktree_free(wt); + git_str_dispose(&gitdir); + + return error; +} + +int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name) +{ + git_str path = GIT_STR_INIT; + git_worktree *wt = NULL; + int error; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + + *out = NULL; + + if ((error = git_str_join3(&path, '/', repo->commondir, "worktrees", name)) < 0) + goto out; + + if ((error = (open_worktree_dir(out, git_repository_workdir(repo), path.ptr, name))) < 0) + goto out; + +out: + git_str_dispose(&path); + + if (error) + git_worktree_free(wt); + + return error; +} + +int git_worktree_open_from_repository(git_worktree **out, git_repository *repo) +{ + git_str parent = GIT_STR_INIT; + const char *gitdir, *commondir; + char *name = NULL; + int error = 0; + + if (!git_repository_is_worktree(repo)) { + git_error_set(GIT_ERROR_WORKTREE, "cannot open worktree of a non-worktree repo"); + error = -1; + goto out; + } + + gitdir = git_repository_path(repo); + commondir = git_repository_commondir(repo); + + if ((error = git_fs_path_prettify_dir(&parent, "..", commondir)) < 0) + goto out; + + /* The name is defined by the last component in '.git/worktree/%s' */ + name = git_fs_path_basename(gitdir); + + if ((error = open_worktree_dir(out, parent.ptr, gitdir, name)) < 0) + goto out; + +out: + git__free(name); + git_str_dispose(&parent); + + return error; +} + +void git_worktree_free(git_worktree *wt) +{ + if (!wt) + return; + + git__free(wt->commondir_path); + git__free(wt->worktree_path); + git__free(wt->gitlink_path); + git__free(wt->gitdir_path); + git__free(wt->parent_path); + git__free(wt->name); + git__free(wt); +} + +int git_worktree_validate(const git_worktree *wt) +{ + GIT_ASSERT_ARG(wt); + + if (!is_worktree_dir(wt->gitdir_path)) { + git_error_set(GIT_ERROR_WORKTREE, + "worktree gitdir ('%s') is not valid", + wt->gitlink_path); + return GIT_ERROR; + } + + if (wt->parent_path && !git_fs_path_exists(wt->parent_path)) { + git_error_set(GIT_ERROR_WORKTREE, + "worktree parent directory ('%s') does not exist ", + wt->parent_path); + return GIT_ERROR; + } + + if (!git_fs_path_exists(wt->commondir_path)) { + git_error_set(GIT_ERROR_WORKTREE, + "worktree common directory ('%s') does not exist ", + wt->commondir_path); + return GIT_ERROR; + } + + if (!git_fs_path_exists(wt->worktree_path)) { + git_error_set(GIT_ERROR_WORKTREE, + "worktree directory '%s' does not exist", + wt->worktree_path); + return GIT_ERROR; + } + + return 0; +} + +int git_worktree_add_options_init(git_worktree_add_options *opts, + unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, + git_worktree_add_options, GIT_WORKTREE_ADD_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_worktree_add_init_options(git_worktree_add_options *opts, + unsigned int version) +{ + return git_worktree_add_options_init(opts, version); +} +#endif + +int git_worktree_add(git_worktree **out, git_repository *repo, + const char *name, const char *worktree, + const git_worktree_add_options *opts) +{ + git_str gitdir = GIT_STR_INIT, wddir = GIT_STR_INIT, buf = GIT_STR_INIT; + git_reference *ref = NULL, *head = NULL; + git_commit *commit = NULL; + git_repository *wt = NULL; + git_checkout_options coopts; + git_worktree_add_options wtopts = GIT_WORKTREE_ADD_OPTIONS_INIT; + int err; + + GIT_ERROR_CHECK_VERSION( + opts, GIT_WORKTREE_ADD_OPTIONS_VERSION, "git_worktree_add_options"); + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(name); + GIT_ASSERT_ARG(worktree); + + *out = NULL; + + if (opts) + memcpy(&wtopts, opts, sizeof(wtopts)); + + memcpy(&coopts, &wtopts.checkout_options, sizeof(coopts)); + + if (wtopts.ref) { + if (!git_reference_is_branch(wtopts.ref)) { + git_error_set(GIT_ERROR_WORKTREE, "reference is not a branch"); + err = -1; + goto out; + } + + if (git_branch_is_checked_out(wtopts.ref)) { + git_error_set(GIT_ERROR_WORKTREE, "reference is already checked out"); + err = -1; + goto out; + } + } + + /* Create gitdir directory ".git/worktrees/" */ + if ((err = git_str_joinpath(&gitdir, repo->commondir, "worktrees")) < 0) + goto out; + if (!git_fs_path_exists(gitdir.ptr)) + if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0) + goto out; + if ((err = git_str_joinpath(&gitdir, gitdir.ptr, name)) < 0) + goto out; + if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0) + goto out; + if ((err = git_fs_path_prettify_dir(&gitdir, gitdir.ptr, NULL)) < 0) + goto out; + + /* Create worktree work dir */ + if ((err = git_futils_mkdir(worktree, 0755, GIT_MKDIR_EXCL)) < 0) + goto out; + if ((err = git_fs_path_prettify_dir(&wddir, worktree, NULL)) < 0) + goto out; + + if (wtopts.lock) { + int fd; + + if ((err = git_str_joinpath(&buf, gitdir.ptr, "locked")) < 0) + goto out; + + if ((fd = p_creat(buf.ptr, 0644)) < 0) { + err = fd; + goto out; + } + + p_close(fd); + git_str_clear(&buf); + } + + /* Create worktree .git file */ + if ((err = git_str_printf(&buf, "gitdir: %s\n", gitdir.ptr)) < 0) + goto out; + if ((err = write_wtfile(wddir.ptr, ".git", &buf)) < 0) + goto out; + + /* Create gitdir files */ + if ((err = git_fs_path_prettify_dir(&buf, repo->commondir, NULL) < 0) + || (err = git_str_putc(&buf, '\n')) < 0 + || (err = write_wtfile(gitdir.ptr, "commondir", &buf)) < 0) + goto out; + if ((err = git_str_joinpath(&buf, wddir.ptr, ".git")) < 0 + || (err = git_str_putc(&buf, '\n')) < 0 + || (err = write_wtfile(gitdir.ptr, "gitdir", &buf)) < 0) + goto out; + + /* Set up worktree reference */ + if (wtopts.ref) { + if ((err = git_reference_dup(&ref, wtopts.ref)) < 0) + goto out; + } else { + if ((err = git_repository_head(&head, repo)) < 0) + goto out; + if ((err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0) + goto out; + if ((err = git_branch_create(&ref, repo, name, commit, false)) < 0) + goto out; + } + + /* Set worktree's HEAD */ + if ((err = git_repository_create_head(gitdir.ptr, git_reference_name(ref))) < 0) + goto out; + if ((err = git_repository_open(&wt, wddir.ptr)) < 0) + goto out; + + /* Checkout worktree's HEAD */ + if ((err = git_checkout_head(wt, &coopts)) < 0) + goto out; + + /* Load result */ + if ((err = git_worktree_lookup(out, repo, name)) < 0) + goto out; + +out: + git_str_dispose(&gitdir); + git_str_dispose(&wddir); + git_str_dispose(&buf); + git_reference_free(ref); + git_reference_free(head); + git_commit_free(commit); + git_repository_free(wt); + + return err; +} + +int git_worktree_lock(git_worktree *wt, const char *reason) +{ + git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(wt); + + if ((error = git_worktree_is_locked(NULL, wt)) < 0) + goto out; + if (error) { + error = GIT_ELOCKED; + goto out; + } + + if ((error = git_str_joinpath(&path, wt->gitdir_path, "locked")) < 0) + goto out; + + if (reason) + git_str_attach_notowned(&buf, reason, strlen(reason)); + + if ((error = git_futils_writebuffer(&buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) + goto out; + + wt->locked = 1; + +out: + git_str_dispose(&path); + + return error; +} + +int git_worktree_unlock(git_worktree *wt) +{ + git_str path = GIT_STR_INIT; + int error; + + GIT_ASSERT_ARG(wt); + + if ((error = git_worktree_is_locked(NULL, wt)) < 0) + return error; + if (!error) + return 1; + + if (git_str_joinpath(&path, wt->gitdir_path, "locked") < 0) + return -1; + + if (p_unlink(path.ptr) != 0) { + git_str_dispose(&path); + return -1; + } + + wt->locked = 0; + + git_str_dispose(&path); + + return 0; +} + +static int git_worktree__is_locked(git_str *reason, const git_worktree *wt) +{ + git_str path = GIT_STR_INIT; + int error, locked; + + GIT_ASSERT_ARG(wt); + + if (reason) + git_str_clear(reason); + + if ((error = git_str_joinpath(&path, wt->gitdir_path, "locked")) < 0) + goto out; + locked = git_fs_path_exists(path.ptr); + if (locked && reason && + (error = git_futils_readbuffer(reason, path.ptr)) < 0) + goto out; + + error = locked; +out: + git_str_dispose(&path); + + return error; +} + +int git_worktree_is_locked(git_buf *reason, const git_worktree *wt) +{ + git_str str = GIT_STR_INIT; + int error = 0; + + if (reason && (error = git_buf_tostr(&str, reason)) < 0) + return error; + + error = git_worktree__is_locked(reason ? &str : NULL, wt); + + if (error >= 0 && reason) { + if (git_buf_fromstr(reason, &str) < 0) + error = -1; + } + + git_str_dispose(&str); + return error; +} + +const char *git_worktree_name(const git_worktree *wt) +{ + GIT_ASSERT_ARG_WITH_RETVAL(wt, NULL); + return wt->name; +} + +const char *git_worktree_path(const git_worktree *wt) +{ + GIT_ASSERT_ARG_WITH_RETVAL(wt, NULL); + return wt->worktree_path; +} + +int git_worktree_prune_options_init( + git_worktree_prune_options *opts, + unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, + git_worktree_prune_options, GIT_WORKTREE_PRUNE_OPTIONS_INIT); + return 0; +} + +#ifndef GIT_DEPRECATE_HARD +int git_worktree_prune_init_options(git_worktree_prune_options *opts, + unsigned int version) +{ + return git_worktree_prune_options_init(opts, version); +} +#endif + +int git_worktree_is_prunable(git_worktree *wt, + git_worktree_prune_options *opts) +{ + git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + + GIT_ERROR_CHECK_VERSION( + opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION, + "git_worktree_prune_options"); + + if (opts) + memcpy(&popts, opts, sizeof(popts)); + + if ((popts.flags & GIT_WORKTREE_PRUNE_LOCKED) == 0) { + git_str reason = GIT_STR_INIT; + int error; + + if ((error = git_worktree__is_locked(&reason, wt)) < 0) + return error; + + if (error) { + if (!reason.size) + git_str_attach_notowned(&reason, "no reason given", 15); + git_error_set(GIT_ERROR_WORKTREE, "not pruning locked working tree: '%s'", reason.ptr); + git_str_dispose(&reason); + return 0; + } + } + + if ((popts.flags & GIT_WORKTREE_PRUNE_VALID) == 0 && + git_worktree_validate(wt) == 0) { + git_error_set(GIT_ERROR_WORKTREE, "not pruning valid working tree"); + return 0; + } + + return 1; +} + +int git_worktree_prune(git_worktree *wt, + git_worktree_prune_options *opts) +{ + git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + git_str path = GIT_STR_INIT; + char *wtpath; + int err; + + GIT_ERROR_CHECK_VERSION( + opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION, + "git_worktree_prune_options"); + + if (opts) + memcpy(&popts, opts, sizeof(popts)); + + if (!git_worktree_is_prunable(wt, &popts)) { + err = -1; + goto out; + } + + /* Delete gitdir in parent repository */ + if ((err = git_str_join3(&path, '/', wt->commondir_path, "worktrees", wt->name)) < 0) + goto out; + if (!git_fs_path_exists(path.ptr)) + { + git_error_set(GIT_ERROR_WORKTREE, "worktree gitdir '%s' does not exist", path.ptr); + err = -1; + goto out; + } + if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0) + goto out; + + /* Skip deletion of the actual working tree if it does + * not exist or deletion was not requested */ + if ((popts.flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 || + !git_fs_path_exists(wt->gitlink_path)) + { + goto out; + } + + if ((wtpath = git_fs_path_dirname(wt->gitlink_path)) == NULL) + goto out; + git_str_attach(&path, wtpath, 0); + if (!git_fs_path_exists(path.ptr)) + { + git_error_set(GIT_ERROR_WORKTREE, "working tree '%s' does not exist", path.ptr); + err = -1; + goto out; + } + if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0) + goto out; + +out: + git_str_dispose(&path); + + return err; +} diff --git a/src/libgit2/worktree.h b/src/libgit2/worktree.h new file mode 100644 index 000000000..587189f81 --- /dev/null +++ b/src/libgit2/worktree.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_worktree_h__ +#define INCLUDE_worktree_h__ + +#include "common.h" + +#include "git2/common.h" +#include "git2/worktree.h" + +struct git_worktree { + /* Name of the working tree. This is the name of the + * containing directory in the `$PARENT/.git/worktrees/` + * directory. */ + char *name; + + /* Path to the where the worktree lives in the filesystem */ + char *worktree_path; + /* Path to the .git file in the working tree's repository */ + char *gitlink_path; + /* Path to the .git directory inside the parent's + * worktrees directory */ + char *gitdir_path; + /* Path to the common directory contained in the parent + * repository */ + char *commondir_path; + /* Path to the parent's working directory */ + char *parent_path; + + unsigned int locked:1; +}; + +char *git_worktree__read_link(const char *base, const char *file); + +#endif diff --git a/src/libgit2/xdiff/git-xdiff.h b/src/libgit2/xdiff/git-xdiff.h new file mode 100644 index 000000000..b75dba819 --- /dev/null +++ b/src/libgit2/xdiff/git-xdiff.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +/* + * This file provides the necessary indirection between xdiff and + * libgit2. libgit2-specific functionality should live here, so + * that git and libgit2 can share a common xdiff implementation. + */ + +#ifndef INCLUDE_git_xdiff_h__ +#define INCLUDE_git_xdiff_h__ + +#include "regexp.h" + +/* Work around C90-conformance issues */ +#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) +# if defined(_MSC_VER) +# define inline __inline +# elif defined(__GNUC__) +# define inline __inline__ +# else +# define inline +# endif +#endif + +#define xdl_malloc(x) git__malloc(x) +#define xdl_free(ptr) git__free(ptr) +#define xdl_realloc(ptr, x) git__realloc(ptr, x) + +#define XDL_BUG(msg) GIT_ASSERT(msg) + +#define xdl_regex_t git_regexp +#define xdl_regmatch_t git_regmatch + +GIT_INLINE(int) xdl_regexec_buf( + const xdl_regex_t *preg, const char *buf, size_t size, + size_t nmatch, xdl_regmatch_t pmatch[], int eflags) +{ + GIT_UNUSED(preg); + GIT_UNUSED(buf); + GIT_UNUSED(size); + GIT_UNUSED(nmatch); + GIT_UNUSED(pmatch); + GIT_UNUSED(eflags); + GIT_ASSERT("not implemented"); + return -1; +} + +#endif diff --git a/src/libgit2/xdiff/xdiff.h b/src/libgit2/xdiff/xdiff.h new file mode 100644 index 000000000..fb47f63fb --- /dev/null +++ b/src/libgit2/xdiff/xdiff.h @@ -0,0 +1,150 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XDIFF_H) +#define XDIFF_H + +#ifdef __cplusplus +extern "C" { +#endif /* #ifdef __cplusplus */ + +#include "git-xdiff.h" + +/* xpparm_t.flags */ +#define XDF_NEED_MINIMAL (1 << 0) + +#define XDF_IGNORE_WHITESPACE (1 << 1) +#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 2) +#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 3) +#define XDF_IGNORE_CR_AT_EOL (1 << 4) +#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | \ + XDF_IGNORE_WHITESPACE_CHANGE | \ + XDF_IGNORE_WHITESPACE_AT_EOL | \ + XDF_IGNORE_CR_AT_EOL) + +#define XDF_IGNORE_BLANK_LINES (1 << 7) + +#define XDF_PATIENCE_DIFF (1 << 14) +#define XDF_HISTOGRAM_DIFF (1 << 15) +#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF) +#define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK) + +#define XDF_INDENT_HEURISTIC (1 << 23) + +/* xdemitconf_t.flags */ +#define XDL_EMIT_FUNCNAMES (1 << 0) +#define XDL_EMIT_NO_HUNK_HDR (1 << 1) +#define XDL_EMIT_FUNCCONTEXT (1 << 2) + +/* merge simplification levels */ +#define XDL_MERGE_MINIMAL 0 +#define XDL_MERGE_EAGER 1 +#define XDL_MERGE_ZEALOUS 2 +#define XDL_MERGE_ZEALOUS_ALNUM 3 + +/* merge favor modes */ +#define XDL_MERGE_FAVOR_OURS 1 +#define XDL_MERGE_FAVOR_THEIRS 2 +#define XDL_MERGE_FAVOR_UNION 3 + +/* merge output styles */ +#define XDL_MERGE_DIFF3 1 +#define XDL_MERGE_ZEALOUS_DIFF3 2 + +typedef struct s_mmfile { + char *ptr; + long size; +} mmfile_t; + +typedef struct s_mmbuffer { + char *ptr; + long size; +} mmbuffer_t; + +typedef struct s_xpparam { + unsigned long flags; + + /* -I */ + xdl_regex_t **ignore_regex; + size_t ignore_regex_nr; + + /* See Documentation/diff-options.txt. */ + char **anchors; + size_t anchors_nr; +} xpparam_t; + +typedef struct s_xdemitcb { + void *priv; + int (*out_hunk)(void *, + long old_begin, long old_nr, + long new_begin, long new_nr, + const char *func, long funclen); + int (*out_line)(void *, mmbuffer_t *, int); +} xdemitcb_t; + +typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv); + +typedef int (*xdl_emit_hunk_consume_func_t)(long start_a, long count_a, + long start_b, long count_b, + void *cb_data); + +typedef struct s_xdemitconf { + long ctxlen; + long interhunkctxlen; + unsigned long flags; + find_func_t find_func; + void *find_func_priv; + xdl_emit_hunk_consume_func_t hunk_func; +} xdemitconf_t; + +typedef struct s_bdiffparam { + long bsize; +} bdiffparam_t; + + +void *xdl_mmfile_first(mmfile_t *mmf, long *size); +long xdl_mmfile_size(mmfile_t *mmf); + +int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdemitconf_t const *xecfg, xdemitcb_t *ecb); + +typedef struct s_xmparam { + xpparam_t xpp; + int marker_size; + int level; + int favor; + int style; + const char *ancestor; /* label for orig */ + const char *file1; /* label for mf1 */ + const char *file2; /* label for mf2 */ +} xmparam_t; + +#define DEFAULT_CONFLICT_MARKER_SIZE 7 + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, + xmparam_t const *xmp, mmbuffer_t *result); + +#ifdef __cplusplus +} +#endif /* #ifdef __cplusplus */ + +#endif /* #if !defined(XDIFF_H) */ diff --git a/src/libgit2/xdiff/xdiffi.c b/src/libgit2/xdiff/xdiffi.c new file mode 100644 index 000000000..af31b7f4b --- /dev/null +++ b/src/libgit2/xdiff/xdiffi.c @@ -0,0 +1,1088 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + +#define XDL_MAX_COST_MIN 256 +#define XDL_HEUR_MIN_COST 256 +#define XDL_LINE_MAX (long)((1UL << (CHAR_BIT * sizeof(long) - 1)) - 1) +#define XDL_SNAKE_CNT 20 +#define XDL_K_HEUR 4 + +typedef struct s_xdpsplit { + long i1, i2; + int min_lo, min_hi; +} xdpsplit_t; + +/* + * See "An O(ND) Difference Algorithm and its Variations", by Eugene Myers. + * Basically considers a "box" (off1, off2, lim1, lim2) and scan from both + * the forward diagonal starting from (off1, off2) and the backward diagonal + * starting from (lim1, lim2). If the K values on the same diagonal crosses + * returns the furthest point of reach. We might encounter expensive edge cases + * using this algorithm, so a little bit of heuristic is needed to cut the + * search and to return a suboptimal point. + */ +static long xdl_split(unsigned long const *ha1, long off1, long lim1, + unsigned long const *ha2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, + xdalgoenv_t *xenv) { + long dmin = off1 - lim2, dmax = lim1 - off2; + long fmid = off1 - off2, bmid = lim1 - lim2; + long odd = (fmid - bmid) & 1; + long fmin = fmid, fmax = fmid; + long bmin = bmid, bmax = bmid; + long ec, d, i1, i2, prev1, best, dd, v, k; + + /* + * Set initial diagonal values for both forward and backward path. + */ + kvdf[fmid] = off1; + kvdb[bmid] = lim1; + + for (ec = 1;; ec++) { + int got_snake = 0; + + /* + * We need to extend the diagonal "domain" by one. If the next + * values exits the box boundaries we need to change it in the + * opposite direction because (max - min) must be a power of + * two. + * + * Also we initialize the external K value to -1 so that we can + * avoid extra conditions in the check inside the core loop. + */ + if (fmin > dmin) + kvdf[--fmin - 1] = -1; + else + ++fmin; + if (fmax < dmax) + kvdf[++fmax + 1] = -1; + else + --fmax; + + for (d = fmax; d >= fmin; d -= 2) { + if (kvdf[d - 1] >= kvdf[d + 1]) + i1 = kvdf[d - 1] + 1; + else + i1 = kvdf[d + 1]; + prev1 = i1; + i2 = i1 - d; + for (; i1 < lim1 && i2 < lim2 && ha1[i1] == ha2[i2]; i1++, i2++); + if (i1 - prev1 > xenv->snake_cnt) + got_snake = 1; + kvdf[d] = i1; + if (odd && bmin <= d && d <= bmax && kvdb[d] <= i1) { + spl->i1 = i1; + spl->i2 = i2; + spl->min_lo = spl->min_hi = 1; + return ec; + } + } + + /* + * We need to extend the diagonal "domain" by one. If the next + * values exits the box boundaries we need to change it in the + * opposite direction because (max - min) must be a power of + * two. + * + * Also we initialize the external K value to -1 so that we can + * avoid extra conditions in the check inside the core loop. + */ + if (bmin > dmin) + kvdb[--bmin - 1] = XDL_LINE_MAX; + else + ++bmin; + if (bmax < dmax) + kvdb[++bmax + 1] = XDL_LINE_MAX; + else + --bmax; + + for (d = bmax; d >= bmin; d -= 2) { + if (kvdb[d - 1] < kvdb[d + 1]) + i1 = kvdb[d - 1]; + else + i1 = kvdb[d + 1] - 1; + prev1 = i1; + i2 = i1 - d; + for (; i1 > off1 && i2 > off2 && ha1[i1 - 1] == ha2[i2 - 1]; i1--, i2--); + if (prev1 - i1 > xenv->snake_cnt) + got_snake = 1; + kvdb[d] = i1; + if (!odd && fmin <= d && d <= fmax && i1 <= kvdf[d]) { + spl->i1 = i1; + spl->i2 = i2; + spl->min_lo = spl->min_hi = 1; + return ec; + } + } + + if (need_min) + continue; + + /* + * If the edit cost is above the heuristic trigger and if + * we got a good snake, we sample current diagonals to see + * if some of them have reached an "interesting" path. Our + * measure is a function of the distance from the diagonal + * corner (i1 + i2) penalized with the distance from the + * mid diagonal itself. If this value is above the current + * edit cost times a magic factor (XDL_K_HEUR) we consider + * it interesting. + */ + if (got_snake && ec > xenv->heur_min) { + for (best = 0, d = fmax; d >= fmin; d -= 2) { + dd = d > fmid ? d - fmid: fmid - d; + i1 = kvdf[d]; + i2 = i1 - d; + v = (i1 - off1) + (i2 - off2) - dd; + + if (v > XDL_K_HEUR * ec && v > best && + off1 + xenv->snake_cnt <= i1 && i1 < lim1 && + off2 + xenv->snake_cnt <= i2 && i2 < lim2) { + for (k = 1; ha1[i1 - k] == ha2[i2 - k]; k++) + if (k == xenv->snake_cnt) { + best = v; + spl->i1 = i1; + spl->i2 = i2; + break; + } + } + } + if (best > 0) { + spl->min_lo = 1; + spl->min_hi = 0; + return ec; + } + + for (best = 0, d = bmax; d >= bmin; d -= 2) { + dd = d > bmid ? d - bmid: bmid - d; + i1 = kvdb[d]; + i2 = i1 - d; + v = (lim1 - i1) + (lim2 - i2) - dd; + + if (v > XDL_K_HEUR * ec && v > best && + off1 < i1 && i1 <= lim1 - xenv->snake_cnt && + off2 < i2 && i2 <= lim2 - xenv->snake_cnt) { + for (k = 0; ha1[i1 + k] == ha2[i2 + k]; k++) + if (k == xenv->snake_cnt - 1) { + best = v; + spl->i1 = i1; + spl->i2 = i2; + break; + } + } + } + if (best > 0) { + spl->min_lo = 0; + spl->min_hi = 1; + return ec; + } + } + + /* + * Enough is enough. We spent too much time here and now we + * collect the furthest reaching path using the (i1 + i2) + * measure. + */ + if (ec >= xenv->mxcost) { + long fbest, fbest1, bbest, bbest1; + + fbest = fbest1 = -1; + for (d = fmax; d >= fmin; d -= 2) { + i1 = XDL_MIN(kvdf[d], lim1); + i2 = i1 - d; + if (lim2 < i2) + i1 = lim2 + d, i2 = lim2; + if (fbest < i1 + i2) { + fbest = i1 + i2; + fbest1 = i1; + } + } + + bbest = bbest1 = XDL_LINE_MAX; + for (d = bmax; d >= bmin; d -= 2) { + i1 = XDL_MAX(off1, kvdb[d]); + i2 = i1 - d; + if (i2 < off2) + i1 = off2 + d, i2 = off2; + if (i1 + i2 < bbest) { + bbest = i1 + i2; + bbest1 = i1; + } + } + + if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) { + spl->i1 = fbest1; + spl->i2 = fbest - fbest1; + spl->min_lo = 1; + spl->min_hi = 0; + } else { + spl->i1 = bbest1; + spl->i2 = bbest - bbest1; + spl->min_lo = 0; + spl->min_hi = 1; + } + return ec; + } + } +} + + +/* + * Rule: "Divide et Impera" (divide & conquer). Recursively split the box in + * sub-boxes by calling the box splitting function. Note that the real job + * (marking changed lines) is done in the two boundary reaching checks. + */ +int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, + diffdata_t *dd2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) { + unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha; + + /* + * Shrink the box by walking through each diagonal snake (SW and NE). + */ + for (; off1 < lim1 && off2 < lim2 && ha1[off1] == ha2[off2]; off1++, off2++); + for (; off1 < lim1 && off2 < lim2 && ha1[lim1 - 1] == ha2[lim2 - 1]; lim1--, lim2--); + + /* + * If one dimension is empty, then all records on the other one must + * be obviously changed. + */ + if (off1 == lim1) { + char *rchg2 = dd2->rchg; + long *rindex2 = dd2->rindex; + + for (; off2 < lim2; off2++) + rchg2[rindex2[off2]] = 1; + } else if (off2 == lim2) { + char *rchg1 = dd1->rchg; + long *rindex1 = dd1->rindex; + + for (; off1 < lim1; off1++) + rchg1[rindex1[off1]] = 1; + } else { + xdpsplit_t spl; + spl.i1 = spl.i2 = 0; + + /* + * Divide ... + */ + if (xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb, + need_min, &spl, xenv) < 0) { + + return -1; + } + + /* + * ... et Impera. + */ + if (xdl_recs_cmp(dd1, off1, spl.i1, dd2, off2, spl.i2, + kvdf, kvdb, spl.min_lo, xenv) < 0 || + xdl_recs_cmp(dd1, spl.i1, lim1, dd2, spl.i2, lim2, + kvdf, kvdb, spl.min_hi, xenv) < 0) { + + return -1; + } + } + + return 0; +} + + +int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe) { + long ndiags; + long *kvd, *kvdf, *kvdb; + xdalgoenv_t xenv; + diffdata_t dd1, dd2; + + if (XDF_DIFF_ALG(xpp->flags) == XDF_PATIENCE_DIFF) + return xdl_do_patience_diff(mf1, mf2, xpp, xe); + + if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF) + return xdl_do_histogram_diff(mf1, mf2, xpp, xe); + + if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) { + + return -1; + } + + /* + * Allocate and setup K vectors to be used by the differential + * algorithm. + * + * One is to store the forward path and one to store the backward path. + */ + ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3; + if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) { + + xdl_free_env(xe); + return -1; + } + kvdf = kvd; + kvdb = kvdf + ndiags; + kvdf += xe->xdf2.nreff + 1; + kvdb += xe->xdf2.nreff + 1; + + xenv.mxcost = xdl_bogosqrt(ndiags); + if (xenv.mxcost < XDL_MAX_COST_MIN) + xenv.mxcost = XDL_MAX_COST_MIN; + xenv.snake_cnt = XDL_SNAKE_CNT; + xenv.heur_min = XDL_HEUR_MIN_COST; + + dd1.nrec = xe->xdf1.nreff; + dd1.ha = xe->xdf1.ha; + dd1.rchg = xe->xdf1.rchg; + dd1.rindex = xe->xdf1.rindex; + dd2.nrec = xe->xdf2.nreff; + dd2.ha = xe->xdf2.ha; + dd2.rchg = xe->xdf2.rchg; + dd2.rindex = xe->xdf2.rindex; + + if (xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec, + kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0, &xenv) < 0) { + + xdl_free(kvd); + xdl_free_env(xe); + return -1; + } + + xdl_free(kvd); + + return 0; +} + + +static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2) { + xdchange_t *xch; + + if (!(xch = (xdchange_t *) xdl_malloc(sizeof(xdchange_t)))) + return NULL; + + xch->next = xscr; + xch->i1 = i1; + xch->i2 = i2; + xch->chg1 = chg1; + xch->chg2 = chg2; + xch->ignore = 0; + + return xch; +} + + +static int recs_match(xrecord_t *rec1, xrecord_t *rec2) +{ + return (rec1->ha == rec2->ha); +} + +/* + * If a line is indented more than this, get_indent() just returns this value. + * This avoids having to do absurd amounts of work for data that are not + * human-readable text, and also ensures that the output of get_indent fits + * within an int. + */ +#define MAX_INDENT 200 + +/* + * Return the amount of indentation of the specified line, treating TAB as 8 + * columns. Return -1 if line is empty or contains only whitespace. Clamp the + * output value at MAX_INDENT. + */ +static int get_indent(xrecord_t *rec) +{ + long i; + int ret = 0; + + for (i = 0; i < rec->size; i++) { + char c = rec->ptr[i]; + + if (!XDL_ISSPACE(c)) + return ret; + else if (c == ' ') + ret += 1; + else if (c == '\t') + ret += 8 - ret % 8; + /* ignore other whitespace characters */ + + if (ret >= MAX_INDENT) + return MAX_INDENT; + } + + /* The line contains only whitespace. */ + return -1; +} + +/* + * If more than this number of consecutive blank rows are found, just return + * this value. This avoids requiring O(N^2) work for pathological cases, and + * also ensures that the output of score_split fits in an int. + */ +#define MAX_BLANKS 20 + +/* Characteristics measured about a hypothetical split position. */ +struct split_measurement { + /* + * Is the split at the end of the file (aside from any blank lines)? + */ + int end_of_file; + + /* + * How much is the line immediately following the split indented (or -1 + * if the line is blank): + */ + int indent; + + /* + * How many consecutive lines above the split are blank? + */ + int pre_blank; + + /* + * How much is the nearest non-blank line above the split indented (or + * -1 if there is no such line)? + */ + int pre_indent; + + /* + * How many lines after the line following the split are blank? + */ + int post_blank; + + /* + * How much is the nearest non-blank line after the line following the + * split indented (or -1 if there is no such line)? + */ + int post_indent; +}; + +struct split_score { + /* The effective indent of this split (smaller is preferred). */ + int effective_indent; + + /* Penalty for this split (smaller is preferred). */ + int penalty; +}; + +/* + * Fill m with information about a hypothetical split of xdf above line split. + */ +static void measure_split(const xdfile_t *xdf, long split, + struct split_measurement *m) +{ + long i; + + if (split >= xdf->nrec) { + m->end_of_file = 1; + m->indent = -1; + } else { + m->end_of_file = 0; + m->indent = get_indent(xdf->recs[split]); + } + + m->pre_blank = 0; + m->pre_indent = -1; + for (i = split - 1; i >= 0; i--) { + m->pre_indent = get_indent(xdf->recs[i]); + if (m->pre_indent != -1) + break; + m->pre_blank += 1; + if (m->pre_blank == MAX_BLANKS) { + m->pre_indent = 0; + break; + } + } + + m->post_blank = 0; + m->post_indent = -1; + for (i = split + 1; i < xdf->nrec; i++) { + m->post_indent = get_indent(xdf->recs[i]); + if (m->post_indent != -1) + break; + m->post_blank += 1; + if (m->post_blank == MAX_BLANKS) { + m->post_indent = 0; + break; + } + } +} + +/* + * The empirically-determined weight factors used by score_split() below. + * Larger values means that the position is a less favorable place to split. + * + * Note that scores are only ever compared against each other, so multiplying + * all of these weight/penalty values by the same factor wouldn't change the + * heuristic's behavior. Still, we need to set that arbitrary scale *somehow*. + * In practice, these numbers are chosen to be large enough that they can be + * adjusted relative to each other with sufficient precision despite using + * integer math. + */ + +/* Penalty if there are no non-blank lines before the split */ +#define START_OF_FILE_PENALTY 1 + +/* Penalty if there are no non-blank lines after the split */ +#define END_OF_FILE_PENALTY 21 + +/* Multiplier for the number of blank lines around the split */ +#define TOTAL_BLANK_WEIGHT (-30) + +/* Multiplier for the number of blank lines after the split */ +#define POST_BLANK_WEIGHT 6 + +/* + * Penalties applied if the line is indented more than its predecessor + */ +#define RELATIVE_INDENT_PENALTY (-4) +#define RELATIVE_INDENT_WITH_BLANK_PENALTY 10 + +/* + * Penalties applied if the line is indented less than both its predecessor and + * its successor + */ +#define RELATIVE_OUTDENT_PENALTY 24 +#define RELATIVE_OUTDENT_WITH_BLANK_PENALTY 17 + +/* + * Penalties applied if the line is indented less than its predecessor but not + * less than its successor + */ +#define RELATIVE_DEDENT_PENALTY 23 +#define RELATIVE_DEDENT_WITH_BLANK_PENALTY 17 + +/* + * We only consider whether the sum of the effective indents for splits are + * less than (-1), equal to (0), or greater than (+1) each other. The resulting + * value is multiplied by the following weight and combined with the penalty to + * determine the better of two scores. + */ +#define INDENT_WEIGHT 60 + +/* + * How far do we slide a hunk at most? + */ +#define INDENT_HEURISTIC_MAX_SLIDING 100 + +/* + * Compute a badness score for the hypothetical split whose measurements are + * stored in m. The weight factors were determined empirically using the tools + * and corpus described in + * + * https://github.com/mhagger/diff-slider-tools + * + * Also see that project if you want to improve the weights based on, for + * example, a larger or more diverse corpus. + */ +static void score_add_split(const struct split_measurement *m, struct split_score *s) +{ + /* + * A place to accumulate penalty factors (positive makes this index more + * favored): + */ + int post_blank, total_blank, indent, any_blanks; + + if (m->pre_indent == -1 && m->pre_blank == 0) + s->penalty += START_OF_FILE_PENALTY; + + if (m->end_of_file) + s->penalty += END_OF_FILE_PENALTY; + + /* + * Set post_blank to the number of blank lines following the split, + * including the line immediately after the split: + */ + post_blank = (m->indent == -1) ? 1 + m->post_blank : 0; + total_blank = m->pre_blank + post_blank; + + /* Penalties based on nearby blank lines: */ + s->penalty += TOTAL_BLANK_WEIGHT * total_blank; + s->penalty += POST_BLANK_WEIGHT * post_blank; + + if (m->indent != -1) + indent = m->indent; + else + indent = m->post_indent; + + any_blanks = (total_blank != 0); + + /* Note that the effective indent is -1 at the end of the file: */ + s->effective_indent += indent; + + if (indent == -1) { + /* No additional adjustments needed. */ + } else if (m->pre_indent == -1) { + /* No additional adjustments needed. */ + } else if (indent > m->pre_indent) { + /* + * The line is indented more than its predecessor. + */ + s->penalty += any_blanks ? + RELATIVE_INDENT_WITH_BLANK_PENALTY : + RELATIVE_INDENT_PENALTY; + } else if (indent == m->pre_indent) { + /* + * The line has the same indentation level as its predecessor. + * No additional adjustments needed. + */ + } else { + /* + * The line is indented less than its predecessor. It could be + * the block terminator of the previous block, but it could + * also be the start of a new block (e.g., an "else" block, or + * maybe the previous block didn't have a block terminator). + * Try to distinguish those cases based on what comes next: + */ + if (m->post_indent != -1 && m->post_indent > indent) { + /* + * The following line is indented more. So it is likely + * that this line is the start of a block. + */ + s->penalty += any_blanks ? + RELATIVE_OUTDENT_WITH_BLANK_PENALTY : + RELATIVE_OUTDENT_PENALTY; + } else { + /* + * That was probably the end of a block. + */ + s->penalty += any_blanks ? + RELATIVE_DEDENT_WITH_BLANK_PENALTY : + RELATIVE_DEDENT_PENALTY; + } + } +} + +static int score_cmp(struct split_score *s1, struct split_score *s2) +{ + /* -1 if s1.effective_indent < s2->effective_indent, etc. */ + int cmp_indents = ((s1->effective_indent > s2->effective_indent) - + (s1->effective_indent < s2->effective_indent)); + + return INDENT_WEIGHT * cmp_indents + (s1->penalty - s2->penalty); +} + +/* + * Represent a group of changed lines in an xdfile_t (i.e., a contiguous group + * of lines that was inserted or deleted from the corresponding version of the + * file). We consider there to be such a group at the beginning of the file, at + * the end of the file, and between any two unchanged lines, though most such + * groups will usually be empty. + * + * If the first line in a group is equal to the line following the group, then + * the group can be slid down. Similarly, if the last line in a group is equal + * to the line preceding the group, then the group can be slid up. See + * group_slide_down() and group_slide_up(). + * + * Note that loops that are testing for changed lines in xdf->rchg do not need + * index bounding since the array is prepared with a zero at position -1 and N. + */ +struct xdlgroup { + /* + * The index of the first changed line in the group, or the index of + * the unchanged line above which the (empty) group is located. + */ + long start; + + /* + * The index of the first unchanged line after the group. For an empty + * group, end is equal to start. + */ + long end; +}; + +/* + * Initialize g to point at the first group in xdf. + */ +static void group_init(xdfile_t *xdf, struct xdlgroup *g) +{ + g->start = g->end = 0; + while (xdf->rchg[g->end]) + g->end++; +} + +/* + * Move g to describe the next (possibly empty) group in xdf and return 0. If g + * is already at the end of the file, do nothing and return -1. + */ +static inline int group_next(xdfile_t *xdf, struct xdlgroup *g) +{ + if (g->end == xdf->nrec) + return -1; + + g->start = g->end + 1; + for (g->end = g->start; xdf->rchg[g->end]; g->end++) + ; + + return 0; +} + +/* + * Move g to describe the previous (possibly empty) group in xdf and return 0. + * If g is already at the beginning of the file, do nothing and return -1. + */ +static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g) +{ + if (g->start == 0) + return -1; + + g->end = g->start - 1; + for (g->start = g->end; xdf->rchg[g->start - 1]; g->start--) + ; + + return 0; +} + +/* + * If g can be slid toward the end of the file, do so, and if it bumps into a + * following group, expand this group to include it. Return 0 on success or -1 + * if g cannot be slid down. + */ +static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g) +{ + if (g->end < xdf->nrec && + recs_match(xdf->recs[g->start], xdf->recs[g->end])) { + xdf->rchg[g->start++] = 0; + xdf->rchg[g->end++] = 1; + + while (xdf->rchg[g->end]) + g->end++; + + return 0; + } else { + return -1; + } +} + +/* + * If g can be slid toward the beginning of the file, do so, and if it bumps + * into a previous group, expand this group to include it. Return 0 on success + * or -1 if g cannot be slid up. + */ +static int group_slide_up(xdfile_t *xdf, struct xdlgroup *g) +{ + if (g->start > 0 && + recs_match(xdf->recs[g->start - 1], xdf->recs[g->end - 1])) { + xdf->rchg[--g->start] = 1; + xdf->rchg[--g->end] = 0; + + while (xdf->rchg[g->start - 1]) + g->start--; + + return 0; + } else { + return -1; + } +} + +/* + * Move back and forward change groups for a consistent and pretty diff output. + * This also helps in finding joinable change groups and reducing the diff + * size. + */ +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { + struct xdlgroup g, go; + long earliest_end, end_matching_other; + long groupsize; + + group_init(xdf, &g); + group_init(xdfo, &go); + + while (1) { + /* + * If the group is empty in the to-be-compacted file, skip it: + */ + if (g.end == g.start) + goto next; + + /* + * Now shift the change up and then down as far as possible in + * each direction. If it bumps into any other changes, merge + * them. + */ + do { + groupsize = g.end - g.start; + + /* + * Keep track of the last "end" index that causes this + * group to align with a group of changed lines in the + * other file. -1 indicates that we haven't found such + * a match yet: + */ + end_matching_other = -1; + + /* Shift the group backward as much as possible: */ + while (!group_slide_up(xdf, &g)) + if (group_previous(xdfo, &go)) + XDL_BUG("group sync broken sliding up"); + + /* + * This is this highest that this group can be shifted. + * Record its end index: + */ + earliest_end = g.end; + + if (go.end > go.start) + end_matching_other = g.end; + + /* Now shift the group forward as far as possible: */ + while (1) { + if (group_slide_down(xdf, &g)) + break; + if (group_next(xdfo, &go)) + XDL_BUG("group sync broken sliding down"); + + if (go.end > go.start) + end_matching_other = g.end; + } + } while (groupsize != g.end - g.start); + + /* + * If the group can be shifted, then we can possibly use this + * freedom to produce a more intuitive diff. + * + * The group is currently shifted as far down as possible, so + * the heuristics below only have to handle upwards shifts. + */ + + if (g.end == earliest_end) { + /* no shifting was possible */ + } else if (end_matching_other != -1) { + /* + * Move the possibly merged group of changes back to + * line up with the last group of changes from the + * other file that it can align with. + */ + while (go.end == go.start) { + if (group_slide_up(xdf, &g)) + XDL_BUG("match disappeared"); + if (group_previous(xdfo, &go)) + XDL_BUG("group sync broken sliding to match"); + } + } else if (flags & XDF_INDENT_HEURISTIC) { + /* + * Indent heuristic: a group of pure add/delete lines + * implies two splits, one between the end of the + * "before" context and the start of the group, and + * another between the end of the group and the + * beginning of the "after" context. Some splits are + * aesthetically better and some are worse. We compute + * a badness "score" for each split, and add the scores + * for the two splits to define a "score" for each + * position that the group can be shifted to. Then we + * pick the shift with the lowest score. + */ + long shift, best_shift = -1; + struct split_score best_score; + + shift = earliest_end; + if (g.end - groupsize - 1 > shift) + shift = g.end - groupsize - 1; + if (g.end - INDENT_HEURISTIC_MAX_SLIDING > shift) + shift = g.end - INDENT_HEURISTIC_MAX_SLIDING; + for (; shift <= g.end; shift++) { + struct split_measurement m; + struct split_score score = {0, 0}; + + measure_split(xdf, shift, &m); + score_add_split(&m, &score); + measure_split(xdf, shift - groupsize, &m); + score_add_split(&m, &score); + if (best_shift == -1 || + score_cmp(&score, &best_score) <= 0) { + best_score.effective_indent = score.effective_indent; + best_score.penalty = score.penalty; + best_shift = shift; + } + } + + while (g.end > best_shift) { + if (group_slide_up(xdf, &g)) + XDL_BUG("best shift unreached"); + if (group_previous(xdfo, &go)) + XDL_BUG("group sync broken sliding to blank line"); + } + } + + next: + /* Move past the just-processed group: */ + if (group_next(xdf, &g)) + break; + if (group_next(xdfo, &go)) + XDL_BUG("group sync broken moving to next group"); + } + + if (!group_next(xdfo, &go)) + XDL_BUG("group sync broken at end of file"); + + return 0; +} + + +int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) { + xdchange_t *cscr = NULL, *xch; + char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg; + long i1, i2, l1, l2; + + /* + * Trivial. Collects "groups" of changes and creates an edit script. + */ + for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--) + if (rchg1[i1 - 1] || rchg2[i2 - 1]) { + for (l1 = i1; rchg1[i1 - 1]; i1--); + for (l2 = i2; rchg2[i2 - 1]; i2--); + + if (!(xch = xdl_add_change(cscr, i1, i2, l1 - i1, l2 - i2))) { + xdl_free_script(cscr); + return -1; + } + cscr = xch; + } + + *xscr = cscr; + + return 0; +} + + +void xdl_free_script(xdchange_t *xscr) { + xdchange_t *xch; + + while ((xch = xscr) != NULL) { + xscr = xscr->next; + xdl_free(xch); + } +} + +static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg) +{ + xdchange_t *xch, *xche; + + for (xch = xscr; xch; xch = xche->next) { + xche = xdl_get_hunk(&xch, xecfg); + if (!xch) + break; + if (xecfg->hunk_func(xch->i1, xche->i1 + xche->chg1 - xch->i1, + xch->i2, xche->i2 + xche->chg2 - xch->i2, + ecb->priv) < 0) + return -1; + } + return 0; +} + +static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags) +{ + xdchange_t *xch; + + for (xch = xscr; xch; xch = xch->next) { + int ignore = 1; + xrecord_t **rec; + long i; + + rec = &xe->xdf1.recs[xch->i1]; + for (i = 0; i < xch->chg1 && ignore; i++) + ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags); + + rec = &xe->xdf2.recs[xch->i2]; + for (i = 0; i < xch->chg2 && ignore; i++) + ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags); + + xch->ignore = ignore; + } +} + +static int record_matches_regex(xrecord_t *rec, xpparam_t const *xpp) { + xdl_regmatch_t regmatch; + int i; + + for (i = 0; i < xpp->ignore_regex_nr; i++) + if (!xdl_regexec_buf(xpp->ignore_regex[i], rec->ptr, rec->size, 1, + ®match, 0)) + return 1; + + return 0; +} + +static void xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe, + xpparam_t const *xpp) +{ + xdchange_t *xch; + + for (xch = xscr; xch; xch = xch->next) { + xrecord_t **rec; + int ignore = 1; + long i; + + /* + * Do not override --ignore-blank-lines. + */ + if (xch->ignore) + continue; + + rec = &xe->xdf1.recs[xch->i1]; + for (i = 0; i < xch->chg1 && ignore; i++) + ignore = record_matches_regex(rec[i], xpp); + + rec = &xe->xdf2.recs[xch->i2]; + for (i = 0; i < xch->chg2 && ignore; i++) + ignore = record_matches_regex(rec[i], xpp); + + xch->ignore = ignore; + } +} + +int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdemitconf_t const *xecfg, xdemitcb_t *ecb) { + xdchange_t *xscr; + xdfenv_t xe; + emit_func_t ef = xecfg->hunk_func ? xdl_call_hunk_func : xdl_emit_diff; + + if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) { + + return -1; + } + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + + xdl_free_env(&xe); + return -1; + } + if (xscr) { + if (xpp->flags & XDF_IGNORE_BLANK_LINES) + xdl_mark_ignorable_lines(xscr, &xe, xpp->flags); + + if (xpp->ignore_regex) + xdl_mark_ignorable_regex(xscr, &xe, xpp); + + if (ef(&xe, xscr, ecb, xecfg) < 0) { + + xdl_free_script(xscr); + xdl_free_env(&xe); + return -1; + } + xdl_free_script(xscr); + } + xdl_free_env(&xe); + + return 0; +} diff --git a/src/libgit2/xdiff/xdiffi.h b/src/libgit2/xdiff/xdiffi.h new file mode 100644 index 000000000..8f1c7c8b0 --- /dev/null +++ b/src/libgit2/xdiff/xdiffi.h @@ -0,0 +1,64 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XDIFFI_H) +#define XDIFFI_H + + +typedef struct s_diffdata { + long nrec; + unsigned long const *ha; + long *rindex; + char *rchg; +} diffdata_t; + +typedef struct s_xdalgoenv { + long mxcost; + long snake_cnt; + long heur_min; +} xdalgoenv_t; + +typedef struct s_xdchange { + struct s_xdchange *next; + long i1, i2; + long chg1, chg2; + int ignore; +} xdchange_t; + + + +int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, + diffdata_t *dd2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv); +int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe); +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); +int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr); +void xdl_free_script(xdchange_t *xscr); +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); +int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *env); +int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *env); + +#endif /* #if !defined(XDIFFI_H) */ diff --git a/src/libgit2/xdiff/xemit.c b/src/libgit2/xdiff/xemit.c new file mode 100644 index 000000000..1cbf2b982 --- /dev/null +++ b/src/libgit2/xdiff/xemit.c @@ -0,0 +1,330 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + +static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) { + + *rec = xdf->recs[ri]->ptr; + + return xdf->recs[ri]->size; +} + + +static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb) { + long size, psize = strlen(pre); + char const *rec; + + size = xdl_get_rec(xdf, ri, &rec); + if (xdl_emit_diffrec(rec, size, pre, psize, ecb) < 0) { + + return -1; + } + + return 0; +} + + +/* + * Starting at the passed change atom, find the latest change atom to be included + * inside the differential hunk according to the specified configuration. + * Also advance xscr if the first changes must be discarded. + */ +xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg) +{ + xdchange_t *xch, *xchp, *lxch; + long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen; + long max_ignorable = xecfg->ctxlen; + unsigned long ignored = 0; /* number of ignored blank lines */ + + /* remove ignorable changes that are too far before other changes */ + for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) { + xch = xchp->next; + + if (xch == NULL || + xch->i1 - (xchp->i1 + xchp->chg1) >= max_ignorable) + *xscr = xch; + } + + if (*xscr == NULL) + return NULL; + + lxch = *xscr; + + for (xchp = *xscr, xch = xchp->next; xch; xchp = xch, xch = xch->next) { + long distance = xch->i1 - (xchp->i1 + xchp->chg1); + if (distance > max_common) + break; + + if (distance < max_ignorable && (!xch->ignore || lxch == xchp)) { + lxch = xch; + ignored = 0; + } else if (distance < max_ignorable && xch->ignore) { + ignored += xch->chg2; + } else if (lxch != xchp && + xch->i1 + ignored - (lxch->i1 + lxch->chg1) > max_common) { + break; + } else if (!xch->ignore) { + lxch = xch; + ignored = 0; + } else { + ignored += xch->chg2; + } + } + + return lxch; +} + + +static long def_ff(const char *rec, long len, char *buf, long sz, void *priv) +{ + if (len > 0 && + (isalpha((unsigned char)*rec) || /* identifier? */ + *rec == '_' || /* also identifier? */ + *rec == '$')) { /* identifiers from VMS and other esoterico */ + if (len > sz) + len = sz; + while (0 < len && isspace((unsigned char)rec[len - 1])) + len--; + memcpy(buf, rec, len); + return len; + } + return -1; +} + +static long match_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri, + char *buf, long sz) +{ + const char *rec; + long len = xdl_get_rec(xdf, ri, &rec); + if (!xecfg->find_func) + return def_ff(rec, len, buf, sz, xecfg->find_func_priv); + return xecfg->find_func(rec, len, buf, sz, xecfg->find_func_priv); +} + +static int is_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri) +{ + char dummy[1]; + return match_func_rec(xdf, xecfg, ri, dummy, sizeof(dummy)) >= 0; +} + +struct func_line { + long len; + char buf[80]; +}; + +static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg, + struct func_line *func_line, long start, long limit) +{ + long l, size, step = (start > limit) ? -1 : 1; + char *buf, dummy[1]; + + buf = func_line ? func_line->buf : dummy; + size = func_line ? sizeof(func_line->buf) : sizeof(dummy); + + for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) { + long len = match_func_rec(&xe->xdf1, xecfg, l, buf, size); + if (len >= 0) { + if (func_line) + func_line->len = len; + return l; + } + } + return -1; +} + +static int is_empty_rec(xdfile_t *xdf, long ri) +{ + const char *rec; + long len = xdl_get_rec(xdf, ri, &rec); + + while (len > 0 && XDL_ISSPACE(*rec)) { + rec++; + len--; + } + return !len; +} + +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg) { + long s1, s2, e1, e2, lctx; + xdchange_t *xch, *xche; + long funclineprev = -1; + struct func_line func_line = { 0 }; + + for (xch = xscr; xch; xch = xche->next) { + xdchange_t *xchp = xch; + xche = xdl_get_hunk(&xch, xecfg); + if (!xch) + break; + +pre_context_calculation: + s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0); + s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0); + + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { + long fs1, i1 = xch->i1; + + /* Appended chunk? */ + if (i1 >= xe->xdf1.nrec) { + long i2 = xch->i2; + + /* + * We don't need additional context if + * a whole function was added. + */ + while (i2 < xe->xdf2.nrec) { + if (is_func_rec(&xe->xdf2, xecfg, i2)) + goto post_context_calculation; + i2++; + } + + /* + * Otherwise get more context from the + * pre-image. + */ + i1 = xe->xdf1.nrec - 1; + } + + fs1 = get_func_line(xe, xecfg, NULL, i1, -1); + while (fs1 > 0 && !is_empty_rec(&xe->xdf1, fs1 - 1) && + !is_func_rec(&xe->xdf1, xecfg, fs1 - 1)) + fs1--; + if (fs1 < 0) + fs1 = 0; + if (fs1 < s1) { + s2 = XDL_MAX(s2 - (s1 - fs1), 0); + s1 = fs1; + + /* + * Did we extend context upwards into an + * ignored change? + */ + while (xchp != xch && + xchp->i1 + xchp->chg1 <= s1 && + xchp->i2 + xchp->chg2 <= s2) + xchp = xchp->next; + + /* If so, show it after all. */ + if (xchp != xch) { + xch = xchp; + goto pre_context_calculation; + } + } + } + + post_context_calculation: + lctx = xecfg->ctxlen; + lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1)); + lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2)); + + e1 = xche->i1 + xche->chg1 + lctx; + e2 = xche->i2 + xche->chg2 + lctx; + + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { + long fe1 = get_func_line(xe, xecfg, NULL, + xche->i1 + xche->chg1, + xe->xdf1.nrec); + while (fe1 > 0 && is_empty_rec(&xe->xdf1, fe1 - 1)) + fe1--; + if (fe1 < 0) + fe1 = xe->xdf1.nrec; + if (fe1 > e1) { + e2 = XDL_MIN(e2 + (fe1 - e1), xe->xdf2.nrec); + e1 = fe1; + } + + /* + * Overlap with next change? Then include it + * in the current hunk and start over to find + * its new end. + */ + if (xche->next) { + long l = XDL_MIN(xche->next->i1, + xe->xdf1.nrec - 1); + if (l - xecfg->ctxlen <= e1 || + get_func_line(xe, xecfg, NULL, l, e1) < 0) { + xche = xche->next; + goto post_context_calculation; + } + } + } + + /* + * Emit current hunk header. + */ + + if (xecfg->flags & XDL_EMIT_FUNCNAMES) { + get_func_line(xe, xecfg, &func_line, + s1 - 1, funclineprev); + funclineprev = s1 - 1; + } + if (!(xecfg->flags & XDL_EMIT_NO_HUNK_HDR) && + xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2, + func_line.buf, func_line.len, ecb) < 0) + return -1; + + /* + * Emit pre-context. + */ + for (; s2 < xch->i2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + + for (s1 = xch->i1, s2 = xch->i2;; xch = xch->next) { + /* + * Merge previous with current change atom. + */ + for (; s1 < xch->i1 && s2 < xch->i2; s1++, s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + + /* + * Removes lines from the first file. + */ + for (s1 = xch->i1; s1 < xch->i1 + xch->chg1; s1++) + if (xdl_emit_record(&xe->xdf1, s1, "-", ecb) < 0) + return -1; + + /* + * Adds lines from the second file. + */ + for (s2 = xch->i2; s2 < xch->i2 + xch->chg2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, "+", ecb) < 0) + return -1; + + if (xch == xche) + break; + s1 = xch->i1 + xch->chg1; + s2 = xch->i2 + xch->chg2; + } + + /* + * Emit post-context. + */ + for (s2 = xche->i2 + xche->chg2; s2 < e2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + } + + return 0; +} diff --git a/src/libgit2/xdiff/xemit.h b/src/libgit2/xdiff/xemit.h new file mode 100644 index 000000000..1b9887e67 --- /dev/null +++ b/src/libgit2/xdiff/xemit.h @@ -0,0 +1,36 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XEMIT_H) +#define XEMIT_H + + +typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); + +xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg); +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); + + + +#endif /* #if !defined(XEMIT_H) */ diff --git a/src/libgit2/xdiff/xhistogram.c b/src/libgit2/xdiff/xhistogram.c new file mode 100644 index 000000000..80794748b --- /dev/null +++ b/src/libgit2/xdiff/xhistogram.c @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in JGit's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "xinclude.h" + +#define MAX_PTR UINT_MAX +#define MAX_CNT UINT_MAX + +#define LINE_END(n) (line##n + count##n - 1) +#define LINE_END_PTR(n) (*line##n + *count##n - 1) + +struct histindex { + struct record { + unsigned int ptr, cnt; + struct record *next; + } **records, /* an occurrence */ + **line_map; /* map of line to record chain */ + chastore_t rcha; + unsigned int *next_ptrs; + unsigned int table_bits, + records_size, + line_map_size; + + unsigned int max_chain_length, + key_shift, + ptr_shift; + + unsigned int cnt, + has_common; + + xdfenv_t *env; + xpparam_t const *xpp; +}; + +struct region { + unsigned int begin1, end1; + unsigned int begin2, end2; +}; + +#define LINE_MAP(i, a) (i->line_map[(a) - i->ptr_shift]) + +#define NEXT_PTR(index, ptr) \ + (index->next_ptrs[(ptr) - index->ptr_shift]) + +#define CNT(index, ptr) \ + ((LINE_MAP(index, ptr))->cnt) + +#define REC(env, s, l) \ + (env->xdf##s.recs[l - 1]) + +static int cmp_recs(xrecord_t *r1, xrecord_t *r2) +{ + return r1->ha == r2->ha; + +} + +#define CMP(i, s1, l1, s2, l2) \ + (cmp_recs(REC(i->env, s1, l1), REC(i->env, s2, l2))) + +#define TABLE_HASH(index, side, line) \ + XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits) + +static int scanA(struct histindex *index, int line1, int count1) +{ + unsigned int ptr, tbl_idx; + unsigned int chain_len; + struct record **rec_chain, *rec; + + for (ptr = LINE_END(1); line1 <= ptr; ptr--) { + tbl_idx = TABLE_HASH(index, 1, ptr); + rec_chain = index->records + tbl_idx; + rec = *rec_chain; + + chain_len = 0; + while (rec) { + if (CMP(index, 1, rec->ptr, 1, ptr)) { + /* + * ptr is identical to another element. Insert + * it onto the front of the existing element + * chain. + */ + NEXT_PTR(index, ptr) = rec->ptr; + rec->ptr = ptr; + /* cap rec->cnt at MAX_CNT */ + rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1); + LINE_MAP(index, ptr) = rec; + goto continue_scan; + } + + rec = rec->next; + chain_len++; + } + + if (chain_len == index->max_chain_length) + return -1; + + /* + * This is the first time we have ever seen this particular + * element in the sequence. Construct a new chain for it. + */ + if (!(rec = xdl_cha_alloc(&index->rcha))) + return -1; + rec->ptr = ptr; + rec->cnt = 1; + rec->next = *rec_chain; + *rec_chain = rec; + LINE_MAP(index, ptr) = rec; + +continue_scan: + ; /* no op */ + } + + return 0; +} + +static int try_lcs(struct histindex *index, struct region *lcs, int b_ptr, + int line1, int count1, int line2, int count2) +{ + unsigned int b_next = b_ptr + 1; + struct record *rec = index->records[TABLE_HASH(index, 2, b_ptr)]; + unsigned int as, ae, bs, be, np, rc; + int should_break; + + for (; rec; rec = rec->next) { + if (rec->cnt > index->cnt) { + if (!index->has_common) + index->has_common = CMP(index, 1, rec->ptr, 2, b_ptr); + continue; + } + + as = rec->ptr; + if (!CMP(index, 1, as, 2, b_ptr)) + continue; + + index->has_common = 1; + for (;;) { + should_break = 0; + np = NEXT_PTR(index, as); + bs = b_ptr; + ae = as; + be = bs; + rc = rec->cnt; + + while (line1 < as && line2 < bs + && CMP(index, 1, as - 1, 2, bs - 1)) { + as--; + bs--; + if (1 < rc) + rc = XDL_MIN(rc, CNT(index, as)); + } + while (ae < LINE_END(1) && be < LINE_END(2) + && CMP(index, 1, ae + 1, 2, be + 1)) { + ae++; + be++; + if (1 < rc) + rc = XDL_MIN(rc, CNT(index, ae)); + } + + if (b_next <= be) + b_next = be + 1; + if (lcs->end1 - lcs->begin1 < ae - as || rc < index->cnt) { + lcs->begin1 = as; + lcs->begin2 = bs; + lcs->end1 = ae; + lcs->end2 = be; + index->cnt = rc; + } + + if (np == 0) + break; + + while (np <= ae) { + np = NEXT_PTR(index, np); + if (np == 0) { + should_break = 1; + break; + } + } + + if (should_break) + break; + + as = np; + } + } + return b_next; +} + +static int fall_back_to_classic_diff(xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2) +{ + xpparam_t xpparam; + + memset(&xpparam, 0, sizeof(xpparam)); + xpparam.flags = xpp->flags & ~XDF_DIFF_ALGORITHM_MASK; + + return xdl_fall_back_diff(env, &xpparam, + line1, count1, line2, count2); +} + +static inline void free_index(struct histindex *index) +{ + xdl_free(index->records); + xdl_free(index->line_map); + xdl_free(index->next_ptrs); + xdl_cha_free(&index->rcha); +} + +static int find_lcs(xpparam_t const *xpp, xdfenv_t *env, + struct region *lcs, + int line1, int count1, int line2, int count2) +{ + int b_ptr; + int sz, ret = -1; + struct histindex index; + + memset(&index, 0, sizeof(index)); + + index.env = env; + index.xpp = xpp; + + index.records = NULL; + index.line_map = NULL; + /* in case of early xdl_cha_free() */ + index.rcha.head = NULL; + + index.table_bits = xdl_hashbits(count1); + sz = index.records_size = 1 << index.table_bits; + sz *= sizeof(struct record *); + if (!(index.records = (struct record **) xdl_malloc(sz))) + goto cleanup; + memset(index.records, 0, sz); + + sz = index.line_map_size = count1; + sz *= sizeof(struct record *); + if (!(index.line_map = (struct record **) xdl_malloc(sz))) + goto cleanup; + memset(index.line_map, 0, sz); + + sz = index.line_map_size; + sz *= sizeof(unsigned int); + if (!(index.next_ptrs = (unsigned int *) xdl_malloc(sz))) + goto cleanup; + memset(index.next_ptrs, 0, sz); + + /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */ + if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0) + goto cleanup; + + index.ptr_shift = line1; + index.max_chain_length = 64; + + if (scanA(&index, line1, count1)) + goto cleanup; + + index.cnt = index.max_chain_length + 1; + + for (b_ptr = line2; b_ptr <= LINE_END(2); ) + b_ptr = try_lcs(&index, lcs, b_ptr, line1, count1, line2, count2); + + if (index.has_common && index.max_chain_length < index.cnt) + ret = 1; + else + ret = 0; + +cleanup: + free_index(&index); + return ret; +} + +static int histogram_diff(xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2) +{ + struct region lcs; + int lcs_found; + int result; +redo: + result = -1; + + if (count1 <= 0 && count2 <= 0) + return 0; + + if (LINE_END(1) >= MAX_PTR) + return -1; + + if (!count1) { + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + return 0; + } else if (!count2) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + return 0; + } + + memset(&lcs, 0, sizeof(lcs)); + lcs_found = find_lcs(xpp, env, &lcs, line1, count1, line2, count2); + if (lcs_found < 0) + goto out; + else if (lcs_found) + result = fall_back_to_classic_diff(xpp, env, line1, count1, line2, count2); + else { + if (lcs.begin1 == 0 && lcs.begin2 == 0) { + while (count1--) + env->xdf1.rchg[line1++ - 1] = 1; + while (count2--) + env->xdf2.rchg[line2++ - 1] = 1; + result = 0; + } else { + result = histogram_diff(xpp, env, + line1, lcs.begin1 - line1, + line2, lcs.begin2 - line2); + if (result) + goto out; + /* + * result = histogram_diff(xpp, env, + * lcs.end1 + 1, LINE_END(1) - lcs.end1, + * lcs.end2 + 1, LINE_END(2) - lcs.end2); + * but let's optimize tail recursion ourself: + */ + count1 = LINE_END(1) - lcs.end1; + line1 = lcs.end1 + 1; + count2 = LINE_END(2) - lcs.end2; + line2 = lcs.end2 + 1; + goto redo; + } + } +out: + return result; +} + +int xdl_do_histogram_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env) +{ + if (xdl_prepare_env(file1, file2, xpp, env) < 0) + return -1; + + return histogram_diff(xpp, env, + env->xdf1.dstart + 1, env->xdf1.dend - env->xdf1.dstart + 1, + env->xdf2.dstart + 1, env->xdf2.dend - env->xdf2.dstart + 1); +} diff --git a/src/libgit2/xdiff/xinclude.h b/src/libgit2/xdiff/xinclude.h new file mode 100644 index 000000000..75db1d8f3 --- /dev/null +++ b/src/libgit2/xdiff/xinclude.h @@ -0,0 +1,36 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XINCLUDE_H) +#define XINCLUDE_H + +#include "git-xdiff.h" +#include "xmacros.h" +#include "xdiff.h" +#include "xtypes.h" +#include "xutils.h" +#include "xprepare.h" +#include "xdiffi.h" +#include "xemit.h" + + +#endif /* #if !defined(XINCLUDE_H) */ diff --git a/src/libgit2/xdiff/xmacros.h b/src/libgit2/xdiff/xmacros.h new file mode 100644 index 000000000..2809a28ca --- /dev/null +++ b/src/libgit2/xdiff/xmacros.h @@ -0,0 +1,54 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XMACROS_H) +#define XMACROS_H + + + + +#define XDL_MIN(a, b) ((a) < (b) ? (a): (b)) +#define XDL_MAX(a, b) ((a) > (b) ? (a): (b)) +#define XDL_ABS(v) ((v) >= 0 ? (v): -(v)) +#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9') +#define XDL_ISSPACE(c) (isspace((unsigned char)(c))) +#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b))) +#define XDL_MASKBITS(b) ((1UL << (b)) - 1) +#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b)) +#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0) +#define XDL_LE32_PUT(p, v) \ +do { \ + unsigned char *__p = (unsigned char *) (p); \ + *__p++ = (unsigned char) (v); \ + *__p++ = (unsigned char) ((v) >> 8); \ + *__p++ = (unsigned char) ((v) >> 16); \ + *__p = (unsigned char) ((v) >> 24); \ +} while (0) +#define XDL_LE32_GET(p, v) \ +do { \ + unsigned char const *__p = (unsigned char const *) (p); \ + (v) = (unsigned long) __p[0] | ((unsigned long) __p[1]) << 8 | \ + ((unsigned long) __p[2]) << 16 | ((unsigned long) __p[3]) << 24; \ +} while (0) + + +#endif /* #if !defined(XMACROS_H) */ diff --git a/src/libgit2/xdiff/xmerge.c b/src/libgit2/xdiff/xmerge.c new file mode 100644 index 000000000..433e2d741 --- /dev/null +++ b/src/libgit2/xdiff/xmerge.c @@ -0,0 +1,737 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + +typedef struct s_xdmerge { + struct s_xdmerge *next; + /* + * 0 = conflict, + * 1 = no conflict, take first, + * 2 = no conflict, take second. + * 3 = no conflict, take both. + */ + int mode; + /* + * These point at the respective postimages. E.g. is + * how side #1 wants to change the common ancestor; if there is no + * overlap, lines before i1 in the postimage of side #1 appear + * in the merge result as a region touched by neither side. + */ + long i1, i2; + long chg1, chg2; + /* + * These point at the preimage; of course there is just one + * preimage, that is from the shared common ancestor. + */ + long i0; + long chg0; +} xdmerge_t; + +static int xdl_append_merge(xdmerge_t **merge, int mode, + long i0, long chg0, + long i1, long chg1, + long i2, long chg2) +{ + xdmerge_t *m = *merge; + if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { + if (mode != m->mode) + m->mode = 0; + m->chg0 = i0 + chg0 - m->i0; + m->chg1 = i1 + chg1 - m->i1; + m->chg2 = i2 + chg2 - m->i2; + } else { + m = xdl_malloc(sizeof(xdmerge_t)); + if (!m) + return -1; + m->next = NULL; + m->mode = mode; + m->i0 = i0; + m->chg0 = chg0; + m->i1 = i1; + m->chg1 = chg1; + m->i2 = i2; + m->chg2 = chg2; + if (*merge) + (*merge)->next = m; + *merge = m; + } + return 0; +} + +static int xdl_cleanup_merge(xdmerge_t *c) +{ + int count = 0; + xdmerge_t *next_c; + + /* were there conflicts? */ + for (; c; c = next_c) { + if (c->mode == 0) + count++; + next_c = c->next; + xdl_free(c); + } + return count; +} + +static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, + int line_count, long flags) +{ + int i; + xrecord_t **rec1 = xe1->xdf2.recs + i1; + xrecord_t **rec2 = xe2->xdf2.recs + i2; + + for (i = 0; i < line_count; i++) { + int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size, + rec2[i]->ptr, rec2[i]->size, flags); + if (!result) + return -1; + } + return 0; +} + +static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) +{ + xrecord_t **recs; + int size = 0; + + recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i; + + if (count < 1) + return 0; + + for (i = 0; i < count; size += recs[i++]->size) + if (dest) + memcpy(dest + size, recs[i]->ptr, recs[i]->size); + if (add_nl) { + i = recs[count - 1]->size; + if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { + if (needs_cr) { + if (dest) + dest[size] = '\r'; + size++; + } + + if (dest) + dest[size] = '\n'; + size++; + } + } + return size; +} + +static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) +{ + return xdl_recs_copy_0(0, xe, i, count, needs_cr, add_nl, dest); +} + +static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) +{ + return xdl_recs_copy_0(1, xe, i, count, needs_cr, add_nl, dest); +} + +/* + * Returns 1 if the i'th line ends in CR/LF (if it is the last line and + * has no eol, the preceding line, if any), 0 if it ends in LF-only, and + * -1 if the line ending cannot be determined. + */ +static int is_eol_crlf(xdfile_t *file, int i) +{ + long size; + + if (i < file->nrec - 1) + /* All lines before the last *must* end in LF */ + return (size = file->recs[i]->size) > 1 && + file->recs[i]->ptr[size - 2] == '\r'; + if (!file->nrec) + /* Cannot determine eol style from empty file */ + return -1; + if ((size = file->recs[i]->size) && + file->recs[i]->ptr[size - 1] == '\n') + /* Last line; ends in LF; Is it CR/LF? */ + return size > 1 && + file->recs[i]->ptr[size - 2] == '\r'; + if (!i) + /* The only line has no eol */ + return -1; + /* Determine eol from second-to-last line */ + return (size = file->recs[i - 1]->size) > 1 && + file->recs[i - 1]->ptr[size - 2] == '\r'; +} + +static int is_cr_needed(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m) +{ + int needs_cr; + + /* Match post-images' preceding, or first, lines' end-of-line style */ + needs_cr = is_eol_crlf(&xe1->xdf2, m->i1 ? m->i1 - 1 : 0); + if (needs_cr) + needs_cr = is_eol_crlf(&xe2->xdf2, m->i2 ? m->i2 - 1 : 0); + /* Look at pre-image's first line, unless we already settled on LF */ + if (needs_cr) + needs_cr = is_eol_crlf(&xe1->xdf1, 0); + /* If still undecided, use LF-only */ + return needs_cr < 0 ? 0 : needs_cr; +} + +static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, + const char *name3, + int size, int i, int style, + xdmerge_t *m, char *dest, int marker_size) +{ + int marker1_size = (name1 ? strlen(name1) + 1 : 0); + int marker2_size = (name2 ? strlen(name2) + 1 : 0); + int marker3_size = (name3 ? strlen(name3) + 1 : 0); + int needs_cr = is_cr_needed(xe1, xe2, m); + + if (marker_size <= 0) + marker_size = DEFAULT_CONFLICT_MARKER_SIZE; + + /* Before conflicting part */ + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, 0, + dest ? dest + size : NULL); + + if (!dest) { + size += marker_size + 1 + needs_cr + marker1_size; + } else { + memset(dest + size, '<', marker_size); + size += marker_size; + if (marker1_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name1, marker1_size - 1); + size += marker1_size; + } + if (needs_cr) + dest[size++] = '\r'; + dest[size++] = '\n'; + } + + /* Postimage from side #1 */ + size += xdl_recs_copy(xe1, m->i1, m->chg1, needs_cr, 1, + dest ? dest + size : NULL); + + if (style == XDL_MERGE_DIFF3 || style == XDL_MERGE_ZEALOUS_DIFF3) { + /* Shared preimage */ + if (!dest) { + size += marker_size + 1 + needs_cr + marker3_size; + } else { + memset(dest + size, '|', marker_size); + size += marker_size; + if (marker3_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name3, marker3_size - 1); + size += marker3_size; + } + if (needs_cr) + dest[size++] = '\r'; + dest[size++] = '\n'; + } + size += xdl_orig_copy(xe1, m->i0, m->chg0, needs_cr, 1, + dest ? dest + size : NULL); + } + + if (!dest) { + size += marker_size + 1 + needs_cr; + } else { + memset(dest + size, '=', marker_size); + size += marker_size; + if (needs_cr) + dest[size++] = '\r'; + dest[size++] = '\n'; + } + + /* Postimage from side #2 */ + size += xdl_recs_copy(xe2, m->i2, m->chg2, needs_cr, 1, + dest ? dest + size : NULL); + if (!dest) { + size += marker_size + 1 + needs_cr + marker2_size; + } else { + memset(dest + size, '>', marker_size); + size += marker_size; + if (marker2_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name2, marker2_size - 1); + size += marker2_size; + } + if (needs_cr) + dest[size++] = '\r'; + dest[size++] = '\n'; + } + return size; +} + +static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, + const char *ancestor_name, + int favor, + xdmerge_t *m, char *dest, int style, + int marker_size) +{ + int size, i; + + for (size = i = 0; m; m = m->next) { + if (favor && !m->mode) + m->mode = favor; + + if (m->mode == 0) + size = fill_conflict_hunk(xe1, name1, xe2, name2, + ancestor_name, + size, i, style, m, dest, + marker_size); + else if (m->mode & 3) { + /* Before conflicting part */ + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, 0, + dest ? dest + size : NULL); + /* Postimage from side #1 */ + if (m->mode & 1) { + int needs_cr = is_cr_needed(xe1, xe2, m); + + size += xdl_recs_copy(xe1, m->i1, m->chg1, needs_cr, (m->mode & 2), + dest ? dest + size : NULL); + } + /* Postimage from side #2 */ + if (m->mode & 2) + size += xdl_recs_copy(xe2, m->i2, m->chg2, 0, 0, + dest ? dest + size : NULL); + } else + continue; + i = m->i1 + m->chg1; + } + size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, 0, + dest ? dest + size : NULL); + return size; +} + +static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags) +{ + return xdl_recmatch(rec1->ptr, rec1->size, + rec2->ptr, rec2->size, flags); +} + +/* + * Remove any common lines from the beginning and end of the conflicted region. + */ +static void xdl_refine_zdiff3_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, + xpparam_t const *xpp) +{ + xrecord_t **rec1 = xe1->xdf2.recs, **rec2 = xe2->xdf2.recs; + for (; m; m = m->next) { + /* let's handle just the conflicts */ + if (m->mode) + continue; + + while(m->chg1 && m->chg2 && + recmatch(rec1[m->i1], rec2[m->i2], xpp->flags)) { + m->chg1--; + m->chg2--; + m->i1++; + m->i2++; + } + while (m->chg1 && m->chg2 && + recmatch(rec1[m->i1 + m->chg1 - 1], + rec2[m->i2 + m->chg2 - 1], xpp->flags)) { + m->chg1--; + m->chg2--; + } + } +} + +/* + * Sometimes, changes are not quite identical, but differ in only a few + * lines. Try hard to show only these few lines as conflicting. + */ +static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, + xpparam_t const *xpp) +{ + for (; m; m = m->next) { + mmfile_t t1, t2; + xdfenv_t xe; + xdchange_t *xscr, *x; + int i1 = m->i1, i2 = m->i2; + + /* let's handle just the conflicts */ + if (m->mode) + continue; + + /* no sense refining a conflict when one side is empty */ + if (m->chg1 == 0 || m->chg2 == 0) + continue; + + /* + * This probably does not work outside git, since + * we have a very simple mmfile structure. + */ + t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr; + t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr + + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr; + t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr; + t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr + + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr; + if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) + return -1; + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + xdl_free_env(&xe); + return -1; + } + if (!xscr) { + /* If this happens, the changes are identical. */ + xdl_free_env(&xe); + m->mode = 4; + continue; + } + x = xscr; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + while (xscr->next) { + xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t)); + if (!m2) { + xdl_free_env(&xe); + xdl_free_script(x); + return -1; + } + xscr = xscr->next; + m2->next = m->next; + m->next = m2; + m = m2; + m->mode = 0; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + } + xdl_free_env(&xe); + xdl_free_script(x); + } + return 0; +} + +static int line_contains_alnum(const char *ptr, long size) +{ + while (size--) + if (isalnum((unsigned char)*(ptr++))) + return 1; + return 0; +} + +static int lines_contain_alnum(xdfenv_t *xe, int i, int chg) +{ + for (; chg; chg--, i++) + if (line_contains_alnum(xe->xdf2.recs[i]->ptr, + xe->xdf2.recs[i]->size)) + return 1; + return 0; +} + +/* + * This function merges m and m->next, marking everything between those hunks + * as conflicting, too. + */ +static void xdl_merge_two_conflicts(xdmerge_t *m) +{ + xdmerge_t *next_m = m->next; + m->chg1 = next_m->i1 + next_m->chg1 - m->i1; + m->chg2 = next_m->i2 + next_m->chg2 - m->i2; + m->next = next_m->next; + xdl_free(next_m); +} + +/* + * If there are less than 3 non-conflicting lines between conflicts, + * it appears simpler -- because it takes up less (or as many) lines -- + * if the lines are moved into the conflicts. + */ +static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m, + int simplify_if_no_alnum) +{ + int result = 0; + + if (!m) + return result; + for (;;) { + xdmerge_t *next_m = m->next; + int begin, end; + + if (!next_m) + return result; + + begin = m->i1 + m->chg1; + end = next_m->i1; + + if (m->mode != 0 || next_m->mode != 0 || + (end - begin > 3 && + (!simplify_if_no_alnum || + lines_contain_alnum(xe1, begin, end - begin)))) { + m = next_m; + } else { + result++; + xdl_merge_two_conflicts(m); + } + } +} + +/* + * level == 0: mark all overlapping changes as conflict + * level == 1: mark overlapping changes as conflict only if not identical + * level == 2: analyze non-identical changes for minimal conflict set + * level == 3: analyze non-identical changes for minimal conflict set, but + * treat hunks not containing any letter or number as conflicting + * + * returns < 0 on error, == 0 for no conflicts, else number of conflicts + */ +static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, + xdfenv_t *xe2, xdchange_t *xscr2, + xmparam_t const *xmp, mmbuffer_t *result) +{ + xdmerge_t *changes, *c; + xpparam_t const *xpp = &xmp->xpp; + const char *const ancestor_name = xmp->ancestor; + const char *const name1 = xmp->file1; + const char *const name2 = xmp->file2; + int i0, i1, i2, chg0, chg1, chg2; + int level = xmp->level; + int style = xmp->style; + int favor = xmp->favor; + + /* + * XDL_MERGE_DIFF3 does not attempt to refine conflicts by looking + * at common areas of sides 1 & 2, because the base (side 0) does + * not match and is being shown. Similarly, simplification of + * non-conflicts is also skipped due to the skipping of conflict + * refinement. + * + * XDL_MERGE_ZEALOUS_DIFF3, on the other hand, will attempt to + * refine conflicts looking for common areas of sides 1 & 2. + * However, since the base is being shown and does not match, + * it will only look for common areas at the beginning or end + * of the conflict block. Since XDL_MERGE_ZEALOUS_DIFF3's + * conflict refinement is much more limited in this fashion, the + * conflict simplification will be skipped. + */ + if (style == XDL_MERGE_DIFF3 || style == XDL_MERGE_ZEALOUS_DIFF3) { + /* + * "diff3 -m" output does not make sense for anything + * more aggressive than XDL_MERGE_EAGER. + */ + if (XDL_MERGE_EAGER < level) + level = XDL_MERGE_EAGER; + } + + c = changes = NULL; + + while (xscr1 && xscr2) { + if (!changes) + changes = c; + if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; + chg0 = xscr1->chg1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + continue; + } + if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { + i0 = xscr2->i1; + i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; + i2 = xscr2->i2; + chg0 = xscr2->chg1; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + continue; + } + if (level == XDL_MERGE_MINIMAL || xscr1->i1 != xscr2->i1 || + xscr1->chg1 != xscr2->chg1 || + xscr1->chg2 != xscr2->chg2 || + xdl_merge_cmp_lines(xe1, xscr1->i2, + xe2, xscr2->i2, + xscr1->chg2, xpp->flags)) { + /* conflict */ + int off = xscr1->i1 - xscr2->i1; + int ffo = off + xscr1->chg1 - xscr2->chg1; + + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr2->i2; + if (off > 0) { + i0 -= off; + i1 -= off; + } + else + i2 += off; + chg0 = xscr1->i1 + xscr1->chg1 - i0; + chg1 = xscr1->i2 + xscr1->chg2 - i1; + chg2 = xscr2->i2 + xscr2->chg2 - i2; + if (ffo < 0) { + chg0 -= ffo; + chg1 -= ffo; + } else + chg2 += ffo; + if (xdl_append_merge(&c, 0, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + } + + i1 = xscr1->i1 + xscr1->chg1; + i2 = xscr2->i1 + xscr2->chg1; + + if (i1 >= i2) + xscr2 = xscr2->next; + if (i2 >= i1) + xscr1 = xscr1->next; + } + while (xscr1) { + if (!changes) + changes = c; + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; + chg0 = xscr1->chg1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + } + while (xscr2) { + if (!changes) + changes = c; + i0 = xscr2->i1; + i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; + i2 = xscr2->i2; + chg0 = xscr2->chg1; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + } + if (!changes) + changes = c; + /* refine conflicts */ + if (style == XDL_MERGE_ZEALOUS_DIFF3) { + xdl_refine_zdiff3_conflicts(xe1, xe2, changes, xpp); + } else if (XDL_MERGE_ZEALOUS <= level && + (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 || + xdl_simplify_non_conflicts(xe1, changes, + XDL_MERGE_ZEALOUS < level) < 0)) { + xdl_cleanup_merge(changes); + return -1; + } + /* output */ + if (result) { + int marker_size = xmp->marker_size; + int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, + favor, changes, NULL, style, + marker_size); + result->ptr = xdl_malloc(size); + if (!result->ptr) { + xdl_cleanup_merge(changes); + return -1; + } + result->size = size; + xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, favor, changes, + result->ptr, style, marker_size); + } + return xdl_cleanup_merge(changes); +} + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, + xmparam_t const *xmp, mmbuffer_t *result) +{ + xdchange_t *xscr1, *xscr2; + xdfenv_t xe1, xe2; + int status; + xpparam_t const *xpp = &xmp->xpp; + + result->ptr = NULL; + result->size = 0; + + if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0) { + return -1; + } + if (xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { + xdl_free_env(&xe1); + return -1; + } + if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe1, &xscr1) < 0) { + xdl_free_env(&xe1); + return -1; + } + if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe2, &xscr2) < 0) { + xdl_free_script(xscr1); + xdl_free_env(&xe1); + xdl_free_env(&xe2); + return -1; + } + status = 0; + if (!xscr1) { + result->ptr = xdl_malloc(mf2->size); + memcpy(result->ptr, mf2->ptr, mf2->size); + result->size = mf2->size; + } else if (!xscr2) { + result->ptr = xdl_malloc(mf1->size); + memcpy(result->ptr, mf1->ptr, mf1->size); + result->size = mf1->size; + } else { + status = xdl_do_merge(&xe1, xscr1, + &xe2, xscr2, + xmp, result); + } + xdl_free_script(xscr1); + xdl_free_script(xscr2); + + xdl_free_env(&xe1); + xdl_free_env(&xe2); + + return status; +} diff --git a/src/libgit2/xdiff/xpatience.c b/src/libgit2/xdiff/xpatience.c new file mode 100644 index 000000000..c5d48e80a --- /dev/null +++ b/src/libgit2/xdiff/xpatience.c @@ -0,0 +1,382 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2016 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ +#include "xinclude.h" + +/* + * The basic idea of patience diff is to find lines that are unique in + * both files. These are intuitively the ones that we want to see as + * common lines. + * + * The maximal ordered sequence of such line pairs (where ordered means + * that the order in the sequence agrees with the order of the lines in + * both files) naturally defines an initial set of common lines. + * + * Now, the algorithm tries to extend the set of common lines by growing + * the line ranges where the files have identical lines. + * + * Between those common lines, the patience diff algorithm is applied + * recursively, until no unique line pairs can be found; these line ranges + * are handled by the well-known Myers algorithm. + */ + +#define NON_UNIQUE ULONG_MAX + +/* + * This is a hash mapping from line hash to line numbers in the first and + * second file. + */ +struct hashmap { + int nr, alloc; + struct entry { + unsigned long hash; + /* + * 0 = unused entry, 1 = first line, 2 = second, etc. + * line2 is NON_UNIQUE if the line is not unique + * in either the first or the second file. + */ + unsigned long line1, line2; + /* + * "next" & "previous" are used for the longest common + * sequence; + * initially, "next" reflects only the order in file1. + */ + struct entry *next, *previous; + + /* + * If 1, this entry can serve as an anchor. See + * Documentation/diff-options.txt for more information. + */ + unsigned anchor : 1; + } *entries, *first, *last; + /* were common records found? */ + unsigned long has_matches; + mmfile_t *file1, *file2; + xdfenv_t *env; + xpparam_t const *xpp; +}; + +static int is_anchor(xpparam_t const *xpp, const char *line) +{ + int i; + for (i = 0; i < xpp->anchors_nr; i++) { + if (!strncmp(line, xpp->anchors[i], strlen(xpp->anchors[i]))) + return 1; + } + return 0; +} + +/* The argument "pass" is 1 for the first file, 2 for the second. */ +static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map, + int pass) +{ + xrecord_t **records = pass == 1 ? + map->env->xdf1.recs : map->env->xdf2.recs; + xrecord_t *record = records[line - 1]; + /* + * After xdl_prepare_env() (or more precisely, due to + * xdl_classify_record()), the "ha" member of the records (AKA lines) + * is _not_ the hash anymore, but a linearized version of it. In + * other words, the "ha" member is guaranteed to start with 0 and + * the second record's ha can only be 0 or 1, etc. + * + * So we multiply ha by 2 in the hope that the hashing was + * "unique enough". + */ + int index = (int)((record->ha << 1) % map->alloc); + + while (map->entries[index].line1) { + if (map->entries[index].hash != record->ha) { + if (++index >= map->alloc) + index = 0; + continue; + } + if (pass == 2) + map->has_matches = 1; + if (pass == 1 || map->entries[index].line2) + map->entries[index].line2 = NON_UNIQUE; + else + map->entries[index].line2 = line; + return; + } + if (pass == 2) + return; + map->entries[index].line1 = line; + map->entries[index].hash = record->ha; + map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr); + if (!map->first) + map->first = map->entries + index; + if (map->last) { + map->last->next = map->entries + index; + map->entries[index].previous = map->last; + } + map->last = map->entries + index; + map->nr++; +} + +/* + * This function has to be called for each recursion into the inter-hunk + * parts, as previously non-unique lines can become unique when being + * restricted to a smaller part of the files. + * + * It is assumed that env has been prepared using xdl_prepare(). + */ +static int fill_hashmap(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + struct hashmap *result, + int line1, int count1, int line2, int count2) +{ + result->file1 = file1; + result->file2 = file2; + result->xpp = xpp; + result->env = env; + + /* We know exactly how large we want the hash map */ + result->alloc = count1 * 2; + result->entries = (struct entry *) + xdl_malloc(result->alloc * sizeof(struct entry)); + if (!result->entries) + return -1; + memset(result->entries, 0, result->alloc * sizeof(struct entry)); + + /* First, fill with entries from the first file */ + while (count1--) + insert_record(xpp, line1++, result, 1); + + /* Then search for matches in the second file */ + while (count2--) + insert_record(xpp, line2++, result, 2); + + return 0; +} + +/* + * Find the longest sequence with a smaller last element (meaning a smaller + * line2, as we construct the sequence with entries ordered by line1). + */ +static int binary_search(struct entry **sequence, int longest, + struct entry *entry) +{ + int left = -1, right = longest; + + while (left + 1 < right) { + int middle = left + (right - left) / 2; + /* by construction, no two entries can be equal */ + if (sequence[middle]->line2 > entry->line2) + right = middle; + else + left = middle; + } + /* return the index in "sequence", _not_ the sequence length */ + return left; +} + +/* + * The idea is to start with the list of common unique lines sorted by + * the order in file1. For each of these pairs, the longest (partial) + * sequence whose last element's line2 is smaller is determined. + * + * For efficiency, the sequences are kept in a list containing exactly one + * item per sequence length: the sequence with the smallest last + * element (in terms of line2). + */ +static struct entry *find_longest_common_sequence(struct hashmap *map) +{ + struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *)); + int longest = 0, i; + struct entry *entry; + + /* + * If not -1, this entry in sequence must never be overridden. + * Therefore, overriding entries before this has no effect, so + * do not do that either. + */ + int anchor_i = -1; + + for (entry = map->first; entry; entry = entry->next) { + if (!entry->line2 || entry->line2 == NON_UNIQUE) + continue; + i = binary_search(sequence, longest, entry); + entry->previous = i < 0 ? NULL : sequence[i]; + ++i; + if (i <= anchor_i) + continue; + sequence[i] = entry; + if (entry->anchor) { + anchor_i = i; + longest = anchor_i + 1; + } else if (i == longest) { + longest++; + } + } + + /* No common unique lines were found */ + if (!longest) { + xdl_free(sequence); + return NULL; + } + + /* Iterate starting at the last element, adjusting the "next" members */ + entry = sequence[longest - 1]; + entry->next = NULL; + while (entry->previous) { + entry->previous->next = entry; + entry = entry->previous; + } + xdl_free(sequence); + return entry; +} + +static int match(struct hashmap *map, int line1, int line2) +{ + xrecord_t *record1 = map->env->xdf1.recs[line1 - 1]; + xrecord_t *record2 = map->env->xdf2.recs[line2 - 1]; + return record1->ha == record2->ha; +} + +static int patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2); + +static int walk_common_sequence(struct hashmap *map, struct entry *first, + int line1, int count1, int line2, int count2) +{ + int end1 = line1 + count1, end2 = line2 + count2; + int next1, next2; + + for (;;) { + /* Try to grow the line ranges of common lines */ + if (first) { + next1 = first->line1; + next2 = first->line2; + while (next1 > line1 && next2 > line2 && + match(map, next1 - 1, next2 - 1)) { + next1--; + next2--; + } + } else { + next1 = end1; + next2 = end2; + } + while (line1 < next1 && line2 < next2 && + match(map, line1, line2)) { + line1++; + line2++; + } + + /* Recurse */ + if (next1 > line1 || next2 > line2) { + if (patience_diff(map->file1, map->file2, + map->xpp, map->env, + line1, next1 - line1, + line2, next2 - line2)) + return -1; + } + + if (!first) + return 0; + + while (first->next && + first->next->line1 == first->line1 + 1 && + first->next->line2 == first->line2 + 1) + first = first->next; + + line1 = first->line1 + 1; + line2 = first->line2 + 1; + + first = first->next; + } +} + +static int fall_back_to_classic_diff(struct hashmap *map, + int line1, int count1, int line2, int count2) +{ + xpparam_t xpp; + + memset(&xpp, 0, sizeof(xpp)); + xpp.flags = map->xpp->flags & ~XDF_DIFF_ALGORITHM_MASK; + + return xdl_fall_back_diff(map->env, &xpp, + line1, count1, line2, count2); +} + +/* + * Recursively find the longest common sequence of unique lines, + * and if none was found, ask xdl_do_diff() to do the job. + * + * This function assumes that env was prepared with xdl_prepare_env(). + */ +static int patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2) +{ + struct hashmap map; + struct entry *first; + int result = 0; + + /* trivial case: one side is empty */ + if (!count1) { + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + return 0; + } else if (!count2) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + return 0; + } + + memset(&map, 0, sizeof(map)); + if (fill_hashmap(file1, file2, xpp, env, &map, + line1, count1, line2, count2)) + return -1; + + /* are there any matching lines at all? */ + if (!map.has_matches) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + xdl_free(map.entries); + return 0; + } + + first = find_longest_common_sequence(&map); + if (first) + result = walk_common_sequence(&map, first, + line1, count1, line2, count2); + else + result = fall_back_to_classic_diff(&map, + line1, count1, line2, count2); + + xdl_free(map.entries); + return result; +} + +int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env) +{ + if (xdl_prepare_env(file1, file2, xpp, env) < 0) + return -1; + + /* environment is cleaned up in xdl_diff() */ + return patience_diff(file1, file2, xpp, env, + 1, env->xdf1.nrec, 1, env->xdf2.nrec); +} diff --git a/src/libgit2/xdiff/xprepare.c b/src/libgit2/xdiff/xprepare.c new file mode 100644 index 000000000..4527a4a07 --- /dev/null +++ b/src/libgit2/xdiff/xprepare.c @@ -0,0 +1,478 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + + +#define XDL_KPDIS_RUN 4 +#define XDL_MAX_EQLIMIT 1024 +#define XDL_SIMSCAN_WINDOW 100 +#define XDL_GUESS_NLINES1 256 +#define XDL_GUESS_NLINES2 20 + + +typedef struct s_xdlclass { + struct s_xdlclass *next; + unsigned long ha; + char const *line; + long size; + long idx; + long len1, len2; +} xdlclass_t; + +typedef struct s_xdlclassifier { + unsigned int hbits; + long hsize; + xdlclass_t **rchash; + chastore_t ncha; + xdlclass_t **rcrecs; + long alloc; + long count; + long flags; +} xdlclassifier_t; + + + + +static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags); +static void xdl_free_classifier(xdlclassifier_t *cf); +static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, + unsigned int hbits, xrecord_t *rec); +static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, + xdlclassifier_t *cf, xdfile_t *xdf); +static void xdl_free_ctx(xdfile_t *xdf); +static int xdl_clean_mmatch(char const *dis, long i, long s, long e); +static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); +static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2); +static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); + + + + +static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) { + cf->flags = flags; + + cf->hbits = xdl_hashbits((unsigned int) size); + cf->hsize = 1 << cf->hbits; + + if (xdl_cha_init(&cf->ncha, sizeof(xdlclass_t), size / 4 + 1) < 0) { + + return -1; + } + if (!(cf->rchash = (xdlclass_t **) xdl_malloc(cf->hsize * sizeof(xdlclass_t *)))) { + + xdl_cha_free(&cf->ncha); + return -1; + } + memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *)); + + cf->alloc = size; + if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) { + + xdl_free(cf->rchash); + xdl_cha_free(&cf->ncha); + return -1; + } + + cf->count = 0; + + return 0; +} + + +static void xdl_free_classifier(xdlclassifier_t *cf) { + + xdl_free(cf->rcrecs); + xdl_free(cf->rchash); + xdl_cha_free(&cf->ncha); +} + + +static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, + unsigned int hbits, xrecord_t *rec) { + long hi; + char const *line; + xdlclass_t *rcrec; + xdlclass_t **rcrecs; + + line = rec->ptr; + hi = (long) XDL_HASHLONG(rec->ha, cf->hbits); + for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next) + if (rcrec->ha == rec->ha && + xdl_recmatch(rcrec->line, rcrec->size, + rec->ptr, rec->size, cf->flags)) + break; + + if (!rcrec) { + if (!(rcrec = xdl_cha_alloc(&cf->ncha))) { + + return -1; + } + rcrec->idx = cf->count++; + if (cf->count > cf->alloc) { + cf->alloc *= 2; + if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) { + + return -1; + } + cf->rcrecs = rcrecs; + } + cf->rcrecs[rcrec->idx] = rcrec; + rcrec->line = line; + rcrec->size = rec->size; + rcrec->ha = rec->ha; + rcrec->len1 = rcrec->len2 = 0; + rcrec->next = cf->rchash[hi]; + cf->rchash[hi] = rcrec; + } + + (pass == 1) ? rcrec->len1++ : rcrec->len2++; + + rec->ha = (unsigned long) rcrec->idx; + + hi = (long) XDL_HASHLONG(rec->ha, hbits); + rec->next = rhash[hi]; + rhash[hi] = rec; + + return 0; +} + + +static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, + xdlclassifier_t *cf, xdfile_t *xdf) { + unsigned int hbits; + long nrec, hsize, bsize; + unsigned long hav; + char const *blk, *cur, *top, *prev; + xrecord_t *crec; + xrecord_t **recs, **rrecs; + xrecord_t **rhash; + unsigned long *ha; + char *rchg; + long *rindex; + + ha = NULL; + rindex = NULL; + rchg = NULL; + rhash = NULL; + recs = NULL; + + if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0) + goto abort; + if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *)))) + goto abort; + + hbits = xdl_hashbits((unsigned int) narec); + hsize = 1 << hbits; + if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *)))) + goto abort; + memset(rhash, 0, hsize * sizeof(xrecord_t *)); + + nrec = 0; + if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) { + for (top = blk + bsize; cur < top; ) { + prev = cur; + hav = xdl_hash_record(&cur, top, xpp->flags); + if (nrec >= narec) { + narec *= 2; + if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *)))) + goto abort; + recs = rrecs; + } + if (!(crec = xdl_cha_alloc(&xdf->rcha))) + goto abort; + crec->ptr = prev; + crec->size = (long) (cur - prev); + crec->ha = hav; + recs[nrec++] = crec; + if (xdl_classify_record(pass, cf, rhash, hbits, crec) < 0) + goto abort; + } + } + + if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char)))) + goto abort; + memset(rchg, 0, (nrec + 2) * sizeof(char)); + + if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) && + (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) { + if (!(rindex = xdl_malloc((nrec + 1) * sizeof(*rindex)))) + goto abort; + if (!(ha = xdl_malloc((nrec + 1) * sizeof(*ha)))) + goto abort; + } + + xdf->nrec = nrec; + xdf->recs = recs; + xdf->hbits = hbits; + xdf->rhash = rhash; + xdf->rchg = rchg + 1; + xdf->rindex = rindex; + xdf->nreff = 0; + xdf->ha = ha; + xdf->dstart = 0; + xdf->dend = nrec - 1; + + return 0; + +abort: + xdl_free(ha); + xdl_free(rindex); + xdl_free(rchg); + xdl_free(rhash); + xdl_free(recs); + xdl_cha_free(&xdf->rcha); + return -1; +} + + +static void xdl_free_ctx(xdfile_t *xdf) { + + xdl_free(xdf->rhash); + xdl_free(xdf->rindex); + xdl_free(xdf->rchg - 1); + xdl_free(xdf->ha); + xdl_free(xdf->recs); + xdl_cha_free(&xdf->rcha); +} + + +int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe) { + long enl1, enl2, sample; + xdlclassifier_t cf; + + memset(&cf, 0, sizeof(cf)); + + /* + * For histogram diff, we can afford a smaller sample size and + * thus a poorer estimate of the number of lines, as the hash + * table (rhash) won't be filled up/grown. The number of lines + * (nrecs) will be updated correctly anyway by + * xdl_prepare_ctx(). + */ + sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF + ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1); + + enl1 = xdl_guess_lines(mf1, sample) + 1; + enl2 = xdl_guess_lines(mf2, sample) + 1; + + if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0) + return -1; + + if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) { + + xdl_free_classifier(&cf); + return -1; + } + if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) { + + xdl_free_ctx(&xe->xdf1); + xdl_free_classifier(&cf); + return -1; + } + + if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) && + (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) && + xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) { + + xdl_free_ctx(&xe->xdf2); + xdl_free_ctx(&xe->xdf1); + xdl_free_classifier(&cf); + return -1; + } + + xdl_free_classifier(&cf); + + return 0; +} + + +void xdl_free_env(xdfenv_t *xe) { + + xdl_free_ctx(&xe->xdf2); + xdl_free_ctx(&xe->xdf1); +} + + +static int xdl_clean_mmatch(char const *dis, long i, long s, long e) { + long r, rdis0, rpdis0, rdis1, rpdis1; + + /* + * Limits the window the is examined during the similar-lines + * scan. The loops below stops when dis[i - r] == 1 (line that + * has no match), but there are corner cases where the loop + * proceed all the way to the extremities by causing huge + * performance penalties in case of big files. + */ + if (i - s > XDL_SIMSCAN_WINDOW) + s = i - XDL_SIMSCAN_WINDOW; + if (e - i > XDL_SIMSCAN_WINDOW) + e = i + XDL_SIMSCAN_WINDOW; + + /* + * Scans the lines before 'i' to find a run of lines that either + * have no match (dis[j] == 0) or have multiple matches (dis[j] > 1). + * Note that we always call this function with dis[i] > 1, so the + * current line (i) is already a multimatch line. + */ + for (r = 1, rdis0 = 0, rpdis0 = 1; (i - r) >= s; r++) { + if (!dis[i - r]) + rdis0++; + else if (dis[i - r] == 2) + rpdis0++; + else + break; + } + /* + * If the run before the line 'i' found only multimatch lines, we + * return 0 and hence we don't make the current line (i) discarded. + * We want to discard multimatch lines only when they appear in the + * middle of runs with nomatch lines (dis[j] == 0). + */ + if (rdis0 == 0) + return 0; + for (r = 1, rdis1 = 0, rpdis1 = 1; (i + r) <= e; r++) { + if (!dis[i + r]) + rdis1++; + else if (dis[i + r] == 2) + rpdis1++; + else + break; + } + /* + * If the run after the line 'i' found only multimatch lines, we + * return 0 and hence we don't make the current line (i) discarded. + */ + if (rdis1 == 0) + return 0; + rdis1 += rdis0; + rpdis1 += rpdis0; + + return rpdis1 * XDL_KPDIS_RUN < (rpdis1 + rdis1); +} + + +/* + * Try to reduce the problem complexity, discard records that have no + * matches on the other file. Also, lines that have multiple matches + * might be potentially discarded if they happear in a run of discardable. + */ +static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { + long i, nm, nreff, mlim; + xrecord_t **recs; + xdlclass_t *rcrec; + char *dis, *dis1, *dis2; + + if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) { + + return -1; + } + memset(dis, 0, xdf1->nrec + xdf2->nrec + 2); + dis1 = dis; + dis2 = dis1 + xdf1->nrec + 1; + + if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT) + mlim = XDL_MAX_EQLIMIT; + for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) { + rcrec = cf->rcrecs[(*recs)->ha]; + nm = rcrec ? rcrec->len2 : 0; + dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; + } + + if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT) + mlim = XDL_MAX_EQLIMIT; + for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) { + rcrec = cf->rcrecs[(*recs)->ha]; + nm = rcrec ? rcrec->len1 : 0; + dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; + } + + for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; + i <= xdf1->dend; i++, recs++) { + if (dis1[i] == 1 || + (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) { + xdf1->rindex[nreff] = i; + xdf1->ha[nreff] = (*recs)->ha; + nreff++; + } else + xdf1->rchg[i] = 1; + } + xdf1->nreff = nreff; + + for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; + i <= xdf2->dend; i++, recs++) { + if (dis2[i] == 1 || + (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) { + xdf2->rindex[nreff] = i; + xdf2->ha[nreff] = (*recs)->ha; + nreff++; + } else + xdf2->rchg[i] = 1; + } + xdf2->nreff = nreff; + + xdl_free(dis); + + return 0; +} + + +/* + * Early trim initial and terminal matching records. + */ +static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) { + long i, lim; + xrecord_t **recs1, **recs2; + + recs1 = xdf1->recs; + recs2 = xdf2->recs; + for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim; + i++, recs1++, recs2++) + if ((*recs1)->ha != (*recs2)->ha) + break; + + xdf1->dstart = xdf2->dstart = i; + + recs1 = xdf1->recs + xdf1->nrec - 1; + recs2 = xdf2->recs + xdf2->nrec - 1; + for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--) + if ((*recs1)->ha != (*recs2)->ha) + break; + + xdf1->dend = xdf1->nrec - i - 1; + xdf2->dend = xdf2->nrec - i - 1; + + return 0; +} + + +static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { + + if (xdl_trim_ends(xdf1, xdf2) < 0 || + xdl_cleanup_records(cf, xdf1, xdf2) < 0) { + + return -1; + } + + return 0; +} diff --git a/src/libgit2/xdiff/xprepare.h b/src/libgit2/xdiff/xprepare.h new file mode 100644 index 000000000..947d9fc1b --- /dev/null +++ b/src/libgit2/xdiff/xprepare.h @@ -0,0 +1,34 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XPREPARE_H) +#define XPREPARE_H + + + +int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe); +void xdl_free_env(xdfenv_t *xe); + + + +#endif /* #if !defined(XPREPARE_H) */ diff --git a/src/libgit2/xdiff/xtypes.h b/src/libgit2/xdiff/xtypes.h new file mode 100644 index 000000000..8442bd436 --- /dev/null +++ b/src/libgit2/xdiff/xtypes.h @@ -0,0 +1,67 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XTYPES_H) +#define XTYPES_H + + + +typedef struct s_chanode { + struct s_chanode *next; + long icurr; +} chanode_t; + +typedef struct s_chastore { + chanode_t *head, *tail; + long isize, nsize; + chanode_t *ancur; + chanode_t *sncur; + long scurr; +} chastore_t; + +typedef struct s_xrecord { + struct s_xrecord *next; + char const *ptr; + long size; + unsigned long ha; +} xrecord_t; + +typedef struct s_xdfile { + chastore_t rcha; + long nrec; + unsigned int hbits; + xrecord_t **rhash; + long dstart, dend; + xrecord_t **recs; + char *rchg; + long *rindex; + long nreff; + unsigned long *ha; +} xdfile_t; + +typedef struct s_xdfenv { + xdfile_t xdf1, xdf2; +} xdfenv_t; + + + +#endif /* #if !defined(XTYPES_H) */ diff --git a/src/libgit2/xdiff/xutils.c b/src/libgit2/xdiff/xutils.c new file mode 100644 index 000000000..cfa6e2220 --- /dev/null +++ b/src/libgit2/xdiff/xutils.c @@ -0,0 +1,434 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + + +long xdl_bogosqrt(long n) { + long i; + + /* + * Classical integer square root approximation using shifts. + */ + for (i = 1; n > 0; n >>= 2) + i <<= 1; + + return i; +} + + +int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, + xdemitcb_t *ecb) { + int i = 2; + mmbuffer_t mb[3]; + + mb[0].ptr = (char *) pre; + mb[0].size = psize; + mb[1].ptr = (char *) rec; + mb[1].size = size; + if (size > 0 && rec[size - 1] != '\n') { + mb[2].ptr = (char *) "\n\\ No newline at end of file\n"; + mb[2].size = strlen(mb[2].ptr); + i++; + } + if (ecb->out_line(ecb->priv, mb, i) < 0) { + + return -1; + } + + return 0; +} + +void *xdl_mmfile_first(mmfile_t *mmf, long *size) +{ + *size = mmf->size; + return mmf->ptr; +} + + +long xdl_mmfile_size(mmfile_t *mmf) +{ + return mmf->size; +} + + +int xdl_cha_init(chastore_t *cha, long isize, long icount) { + + cha->head = cha->tail = NULL; + cha->isize = isize; + cha->nsize = icount * isize; + cha->ancur = cha->sncur = NULL; + cha->scurr = 0; + + return 0; +} + + +void xdl_cha_free(chastore_t *cha) { + chanode_t *cur, *tmp; + + for (cur = cha->head; (tmp = cur) != NULL;) { + cur = cur->next; + xdl_free(tmp); + } +} + + +void *xdl_cha_alloc(chastore_t *cha) { + chanode_t *ancur; + void *data; + + if (!(ancur = cha->ancur) || ancur->icurr == cha->nsize) { + if (!(ancur = (chanode_t *) xdl_malloc(sizeof(chanode_t) + cha->nsize))) { + + return NULL; + } + ancur->icurr = 0; + ancur->next = NULL; + if (cha->tail) + cha->tail->next = ancur; + if (!cha->head) + cha->head = ancur; + cha->tail = ancur; + cha->ancur = ancur; + } + + data = (char *) ancur + sizeof(chanode_t) + ancur->icurr; + ancur->icurr += cha->isize; + + return data; +} + +long xdl_guess_lines(mmfile_t *mf, long sample) { + long nl = 0, size, tsize = 0; + char const *data, *cur, *top; + + if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) { + for (top = data + size; nl < sample && cur < top; ) { + nl++; + if (!(cur = memchr(cur, '\n', top - cur))) + cur = top; + else + cur++; + } + tsize += (long) (cur - data); + } + + if (nl && tsize) + nl = xdl_mmfile_size(mf) / (tsize / nl); + + return nl + 1; +} + +int xdl_blankline(const char *line, long size, long flags) +{ + long i; + + if (!(flags & XDF_WHITESPACE_FLAGS)) + return (size <= 1); + + for (i = 0; i < size && XDL_ISSPACE(line[i]); i++) + ; + + return (i == size); +} + +/* + * Have we eaten everything on the line, except for an optional + * CR at the very end? + */ +static int ends_with_optional_cr(const char *l, long s, long i) +{ + int complete = s && l[s-1] == '\n'; + + if (complete) + s--; + if (s == i) + return 1; + /* do not ignore CR at the end of an incomplete line */ + if (complete && s == i + 1 && l[i] == '\r') + return 1; + return 0; +} + +int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) +{ + int i1, i2; + + if (s1 == s2 && !memcmp(l1, l2, s1)) + return 1; + if (!(flags & XDF_WHITESPACE_FLAGS)) + return 0; + + i1 = 0; + i2 = 0; + + /* + * -w matches everything that matches with -b, and -b in turn + * matches everything that matches with --ignore-space-at-eol, + * which in turn matches everything that matches with --ignore-cr-at-eol. + * + * Each flavor of ignoring needs different logic to skip whitespaces + * while we have both sides to compare. + */ + if (flags & XDF_IGNORE_WHITESPACE) { + goto skip_ws; + while (i1 < s1 && i2 < s2) { + if (l1[i1++] != l2[i2++]) + return 0; + skip_ws: + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + } + } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { + while (i1 < s1 && i2 < s2) { + if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) { + /* Skip matching spaces and try again */ + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + continue; + } + if (l1[i1++] != l2[i2++]) + return 0; + } + } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) { + while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) { + i1++; + i2++; + } + } else if (flags & XDF_IGNORE_CR_AT_EOL) { + /* Find the first difference and see how the line ends */ + while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) { + i1++; + i2++; + } + return (ends_with_optional_cr(l1, s1, i1) && + ends_with_optional_cr(l2, s2, i2)); + } + + /* + * After running out of one side, the remaining side must have + * nothing but whitespace for the lines to match. Note that + * ignore-whitespace-at-eol case may break out of the loop + * while there still are characters remaining on both lines. + */ + if (i1 < s1) { + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + if (s1 != i1) + return 0; + } + if (i2 < s2) { + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + return (s2 == i2); + } + return 1; +} + +static unsigned long xdl_hash_record_with_whitespace(char const **data, + char const *top, long flags) { + unsigned long ha = 5381; + char const *ptr = *data; + int cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL; + + for (; ptr < top && *ptr != '\n'; ptr++) { + if (cr_at_eol_only) { + /* do not ignore CR at the end of an incomplete line */ + if (*ptr == '\r' && + (ptr + 1 < top && ptr[1] == '\n')) + continue; + } + else if (XDL_ISSPACE(*ptr)) { + const char *ptr2 = ptr; + int at_eol; + while (ptr + 1 < top && XDL_ISSPACE(ptr[1]) + && ptr[1] != '\n') + ptr++; + at_eol = (top <= ptr + 1 || ptr[1] == '\n'); + if (flags & XDF_IGNORE_WHITESPACE) + ; /* already handled */ + else if (flags & XDF_IGNORE_WHITESPACE_CHANGE + && !at_eol) { + ha += (ha << 5); + ha ^= (unsigned long) ' '; + } + else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL + && !at_eol) { + while (ptr2 != ptr + 1) { + ha += (ha << 5); + ha ^= (unsigned long) *ptr2; + ptr2++; + } + } + continue; + } + ha += (ha << 5); + ha ^= (unsigned long) *ptr; + } + *data = ptr < top ? ptr + 1: ptr; + + return ha; +} + +unsigned long xdl_hash_record(char const **data, char const *top, long flags) { + unsigned long ha = 5381; + char const *ptr = *data; + + if (flags & XDF_WHITESPACE_FLAGS) + return xdl_hash_record_with_whitespace(data, top, flags); + + for (; ptr < top && *ptr != '\n'; ptr++) { + ha += (ha << 5); + ha ^= (unsigned long) *ptr; + } + *data = ptr < top ? ptr + 1: ptr; + + return ha; +} + +unsigned int xdl_hashbits(unsigned int size) { + unsigned int val = 1, bits = 0; + + for (; val < size && bits < CHAR_BIT * sizeof(unsigned int); val <<= 1, bits++); + return bits ? bits: 1; +} + + +int xdl_num_out(char *out, long val) { + char *ptr, *str = out; + char buf[32]; + + ptr = buf + sizeof(buf) - 1; + *ptr = '\0'; + if (val < 0) { + *--ptr = '-'; + val = -val; + } + for (; val && ptr > buf; val /= 10) + *--ptr = "0123456789"[val % 10]; + if (*ptr) + for (; *ptr; ptr++, str++) + *str = *ptr; + else + *str++ = '0'; + *str = '\0'; + + return str - out; +} + +static int xdl_format_hunk_hdr(long s1, long c1, long s2, long c2, + const char *func, long funclen, + xdemitcb_t *ecb) { + int nb = 0; + mmbuffer_t mb; + char buf[128]; + + memcpy(buf, "@@ -", 4); + nb += 4; + + nb += xdl_num_out(buf + nb, c1 ? s1: s1 - 1); + + if (c1 != 1) { + memcpy(buf + nb, ",", 1); + nb += 1; + + nb += xdl_num_out(buf + nb, c1); + } + + memcpy(buf + nb, " +", 2); + nb += 2; + + nb += xdl_num_out(buf + nb, c2 ? s2: s2 - 1); + + if (c2 != 1) { + memcpy(buf + nb, ",", 1); + nb += 1; + + nb += xdl_num_out(buf + nb, c2); + } + + memcpy(buf + nb, " @@", 3); + nb += 3; + if (func && funclen) { + buf[nb++] = ' '; + if (funclen > sizeof(buf) - nb - 1) + funclen = sizeof(buf) - nb - 1; + memcpy(buf + nb, func, funclen); + nb += funclen; + } + buf[nb++] = '\n'; + + mb.ptr = buf; + mb.size = nb; + if (ecb->out_line(ecb->priv, &mb, 1) < 0) + return -1; + return 0; +} + +int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, + const char *func, long funclen, + xdemitcb_t *ecb) { + if (!ecb->out_hunk) + return xdl_format_hunk_hdr(s1, c1, s2, c2, func, funclen, ecb); + if (ecb->out_hunk(ecb->priv, + c1 ? s1 : s1 - 1, c1, + c2 ? s2 : s2 - 1, c2, + func, funclen) < 0) + return -1; + return 0; +} + +int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, + int line1, int count1, int line2, int count2) +{ + /* + * This probably does not work outside Git, since + * we have a very simple mmfile structure. + * + * Note: ideally, we would reuse the prepared environment, but + * the libxdiff interface does not (yet) allow for diffing only + * ranges of lines instead of the whole files. + */ + mmfile_t subfile1, subfile2; + xdfenv_t env; + + subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr; + subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr + + diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr; + subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr; + subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr + + diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr; + if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0) + return -1; + + memcpy(diff_env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1); + memcpy(diff_env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2); + + xdl_free_env(&env); + + return 0; +} diff --git a/src/libgit2/xdiff/xutils.h b/src/libgit2/xdiff/xutils.h new file mode 100644 index 000000000..fba7bae03 --- /dev/null +++ b/src/libgit2/xdiff/xutils.h @@ -0,0 +1,47 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see + * . + * + * Davide Libenzi + * + */ + +#if !defined(XUTILS_H) +#define XUTILS_H + + + +long xdl_bogosqrt(long n); +int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, + xdemitcb_t *ecb); +int xdl_cha_init(chastore_t *cha, long isize, long icount); +void xdl_cha_free(chastore_t *cha); +void *xdl_cha_alloc(chastore_t *cha); +long xdl_guess_lines(mmfile_t *mf, long sample); +int xdl_blankline(const char *line, long size, long flags); +int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags); +unsigned long xdl_hash_record(char const **data, char const *top, long flags); +unsigned int xdl_hashbits(unsigned int size); +int xdl_num_out(char *out, long val); +int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, + const char *func, long funclen, xdemitcb_t *ecb); +int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, + int line1, int count1, int line2, int count2); + + + +#endif /* #if !defined(XUTILS_H) */ diff --git a/src/libgit2/zstream.c b/src/libgit2/zstream.c new file mode 100644 index 000000000..cb8b125ed --- /dev/null +++ b/src/libgit2/zstream.c @@ -0,0 +1,210 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "zstream.h" + +#include + +#include "str.h" + +#define ZSTREAM_BUFFER_SIZE (1024 * 1024) +#define ZSTREAM_BUFFER_MIN_EXTRA 8 + +GIT_INLINE(int) zstream_seterr(git_zstream *zs) +{ + switch (zs->zerr) { + case Z_OK: + case Z_STREAM_END: + case Z_BUF_ERROR: /* not fatal; we retry with a larger buffer */ + return 0; + case Z_MEM_ERROR: + git_error_set_oom(); + break; + default: + if (zs->z.msg) + git_error_set_str(GIT_ERROR_ZLIB, zs->z.msg); + else + git_error_set(GIT_ERROR_ZLIB, "unknown compression error"); + } + + return -1; +} + +int git_zstream_init(git_zstream *zstream, git_zstream_t type) +{ + zstream->type = type; + + if (zstream->type == GIT_ZSTREAM_INFLATE) + zstream->zerr = inflateInit(&zstream->z); + else + zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION); + return zstream_seterr(zstream); +} + +void git_zstream_free(git_zstream *zstream) +{ + if (zstream->type == GIT_ZSTREAM_INFLATE) + inflateEnd(&zstream->z); + else + deflateEnd(&zstream->z); +} + +void git_zstream_reset(git_zstream *zstream) +{ + if (zstream->type == GIT_ZSTREAM_INFLATE) + inflateReset(&zstream->z); + else + deflateReset(&zstream->z); + zstream->in = NULL; + zstream->in_len = 0; + zstream->zerr = Z_STREAM_END; +} + +int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len) +{ + zstream->in = in; + zstream->in_len = in_len; + zstream->zerr = Z_OK; + return 0; +} + +bool git_zstream_done(git_zstream *zstream) +{ + return (!zstream->in_len && zstream->zerr == Z_STREAM_END); +} + +bool git_zstream_eos(git_zstream *zstream) +{ + return zstream->zerr == Z_STREAM_END; +} + +size_t git_zstream_suggest_output_len(git_zstream *zstream) +{ + if (zstream->in_len > ZSTREAM_BUFFER_SIZE) + return ZSTREAM_BUFFER_SIZE; + else if (zstream->in_len > ZSTREAM_BUFFER_MIN_EXTRA) + return zstream->in_len; + else + return ZSTREAM_BUFFER_MIN_EXTRA; +} + +int git_zstream_get_output_chunk( + void *out, size_t *out_len, git_zstream *zstream) +{ + size_t in_queued, in_used, out_queued; + + /* set up input data */ + zstream->z.next_in = (Bytef *)zstream->in; + + /* feed as much data to zlib as it can consume, at most UINT_MAX */ + if (zstream->in_len > UINT_MAX) { + zstream->z.avail_in = UINT_MAX; + zstream->flush = Z_NO_FLUSH; + } else { + zstream->z.avail_in = (uInt)zstream->in_len; + zstream->flush = Z_FINISH; + } + in_queued = (size_t)zstream->z.avail_in; + + /* set up output data */ + zstream->z.next_out = out; + zstream->z.avail_out = (uInt)*out_len; + + if ((size_t)zstream->z.avail_out != *out_len) + zstream->z.avail_out = UINT_MAX; + out_queued = (size_t)zstream->z.avail_out; + + /* compress next chunk */ + if (zstream->type == GIT_ZSTREAM_INFLATE) + zstream->zerr = inflate(&zstream->z, zstream->flush); + else + zstream->zerr = deflate(&zstream->z, zstream->flush); + + if (zstream_seterr(zstream)) + return -1; + + in_used = (in_queued - zstream->z.avail_in); + zstream->in_len -= in_used; + zstream->in += in_used; + + *out_len = (out_queued - zstream->z.avail_out); + + return 0; +} + +int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) +{ + size_t out_remain = *out_len; + + if (zstream->in_len && zstream->zerr == Z_STREAM_END) { + git_error_set(GIT_ERROR_ZLIB, "zlib input had trailing garbage"); + return -1; + } + + while (out_remain > 0 && zstream->zerr != Z_STREAM_END) { + size_t out_written = out_remain; + + if (git_zstream_get_output_chunk(out, &out_written, zstream) < 0) + return -1; + + out_remain -= out_written; + out = ((char *)out) + out_written; + } + + /* either we finished the input or we did not flush the data */ + GIT_ASSERT(zstream->in_len > 0 || zstream->flush == Z_FINISH); + + /* set out_size to number of bytes actually written to output */ + *out_len = *out_len - out_remain; + + return 0; +} + +static int zstream_buf(git_str *out, const void *in, size_t in_len, git_zstream_t type) +{ + git_zstream zs = GIT_ZSTREAM_INIT; + int error = 0; + + if ((error = git_zstream_init(&zs, type)) < 0) + return error; + + if ((error = git_zstream_set_input(&zs, in, in_len)) < 0) + goto done; + + while (!git_zstream_done(&zs)) { + size_t step = git_zstream_suggest_output_len(&zs), written; + + if ((error = git_str_grow_by(out, step)) < 0) + goto done; + + written = out->asize - out->size; + + if ((error = git_zstream_get_output( + out->ptr + out->size, &written, &zs)) < 0) + goto done; + + out->size += written; + } + + /* NULL terminate for consistency if possible */ + if (out->size < out->asize) + out->ptr[out->size] = '\0'; + +done: + git_zstream_free(&zs); + return error; +} + +int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len) +{ + return zstream_buf(out, in, in_len, GIT_ZSTREAM_DEFLATE); +} + +int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len) +{ + return zstream_buf(out, in, in_len, GIT_ZSTREAM_INFLATE); +} diff --git a/src/libgit2/zstream.h b/src/libgit2/zstream.h new file mode 100644 index 000000000..3f8b1c72f --- /dev/null +++ b/src/libgit2/zstream.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_zstream_h__ +#define INCLUDE_zstream_h__ + +#include "common.h" + +#include + +#include "str.h" + +typedef enum { + GIT_ZSTREAM_INFLATE, + GIT_ZSTREAM_DEFLATE +} git_zstream_t; + +typedef struct { + z_stream z; + git_zstream_t type; + const char *in; + size_t in_len; + int flush; + int zerr; +} git_zstream; + +#define GIT_ZSTREAM_INIT {{0}} + +int git_zstream_init(git_zstream *zstream, git_zstream_t type); +void git_zstream_free(git_zstream *zstream); + +int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len); + +size_t git_zstream_suggest_output_len(git_zstream *zstream); + +/* get as much output as is available in the input buffer */ +int git_zstream_get_output_chunk( + void *out, size_t *out_len, git_zstream *zstream); + +/* get all the output from the entire input buffer */ +int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream); + +bool git_zstream_done(git_zstream *zstream); +bool git_zstream_eos(git_zstream *zstream); + +void git_zstream_reset(git_zstream *zstream); + +int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len); +int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len); + +#endif diff --git a/src/mailmap.c b/src/mailmap.c deleted file mode 100644 index 4336fe3e5..000000000 --- a/src/mailmap.c +++ /dev/null @@ -1,500 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "mailmap.h" - -#include "common.h" -#include "config.h" -#include "fs_path.h" -#include "repository.h" -#include "signature.h" -#include "git2/config.h" -#include "git2/revparse.h" -#include "blob.h" -#include "parse.h" -#include "path.h" - -#define MM_FILE ".mailmap" -#define MM_FILE_CONFIG "mailmap.file" -#define MM_BLOB_CONFIG "mailmap.blob" -#define MM_BLOB_DEFAULT "HEAD:" MM_FILE - -static void mailmap_entry_free(git_mailmap_entry *entry) -{ - if (!entry) - return; - - git__free(entry->real_name); - git__free(entry->real_email); - git__free(entry->replace_name); - git__free(entry->replace_email); - git__free(entry); -} - -/* - * First we sort by replace_email, then replace_name (if present). - * Entries with names are greater than entries without. - */ -static int mailmap_entry_cmp(const void *a_raw, const void *b_raw) -{ - const git_mailmap_entry *a = (const git_mailmap_entry *)a_raw; - const git_mailmap_entry *b = (const git_mailmap_entry *)b_raw; - int cmp; - - GIT_ASSERT_ARG(a && a->replace_email); - GIT_ASSERT_ARG(b && b->replace_email); - - cmp = git__strcmp(a->replace_email, b->replace_email); - if (cmp) - return cmp; - - /* NULL replace_names are less than not-NULL ones */ - if (a->replace_name == NULL || b->replace_name == NULL) - return (int)(a->replace_name != NULL) - (int)(b->replace_name != NULL); - - return git__strcmp(a->replace_name, b->replace_name); -} - -/* Replace the old entry with the new on duplicate. */ -static int mailmap_entry_replace(void **old_raw, void *new_raw) -{ - mailmap_entry_free((git_mailmap_entry *)*old_raw); - *old_raw = new_raw; - return GIT_EEXISTS; -} - -/* Check if we're at the end of line, w/ comments */ -static bool is_eol(git_parse_ctx *ctx) -{ - char c; - return git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE) < 0 || c == '#'; -} - -static int advance_until( - const char **start, size_t *len, git_parse_ctx *ctx, char needle) -{ - *start = ctx->line; - while (ctx->line_len > 0 && *ctx->line != '#' && *ctx->line != needle) - git_parse_advance_chars(ctx, 1); - - if (ctx->line_len == 0 || *ctx->line == '#') - return -1; /* end of line */ - - *len = ctx->line - *start; - git_parse_advance_chars(ctx, 1); /* advance past needle */ - return 0; -} - -/* - * Parse a single entry from a mailmap file. - * - * The output git_strs will be non-owning, and should be copied before being - * persisted. - */ -static int parse_mailmap_entry( - git_str *real_name, git_str *real_email, - git_str *replace_name, git_str *replace_email, - git_parse_ctx *ctx) -{ - const char *start; - size_t len; - - git_str_clear(real_name); - git_str_clear(real_email); - git_str_clear(replace_name); - git_str_clear(replace_email); - - git_parse_advance_ws(ctx); - if (is_eol(ctx)) - return -1; /* blank line */ - - /* Parse the real name */ - if (advance_until(&start, &len, ctx, '<') < 0) - return -1; - - git_str_attach_notowned(real_name, start, len); - git_str_rtrim(real_name); - - /* - * If this is the last email in the line, this is the email to replace, - * otherwise, it's the real email. - */ - if (advance_until(&start, &len, ctx, '>') < 0) - return -1; - - /* If we aren't at the end of the line, parse a second name and email */ - if (!is_eol(ctx)) { - git_str_attach_notowned(real_email, start, len); - - git_parse_advance_ws(ctx); - if (advance_until(&start, &len, ctx, '<') < 0) - return -1; - git_str_attach_notowned(replace_name, start, len); - git_str_rtrim(replace_name); - - if (advance_until(&start, &len, ctx, '>') < 0) - return -1; - } - - git_str_attach_notowned(replace_email, start, len); - - if (!is_eol(ctx)) - return -1; - - return 0; -} - -int git_mailmap_new(git_mailmap **out) -{ - int error; - git_mailmap *mm = git__calloc(1, sizeof(git_mailmap)); - GIT_ERROR_CHECK_ALLOC(mm); - - error = git_vector_init(&mm->entries, 0, mailmap_entry_cmp); - if (error < 0) { - git__free(mm); - return error; - } - *out = mm; - return 0; -} - -void git_mailmap_free(git_mailmap *mm) -{ - size_t idx; - git_mailmap_entry *entry; - if (!mm) - return; - - git_vector_foreach(&mm->entries, idx, entry) - mailmap_entry_free(entry); - - git_vector_free(&mm->entries); - git__free(mm); -} - -static int mailmap_add_entry_unterminated( - git_mailmap *mm, - const char *real_name, size_t real_name_size, - const char *real_email, size_t real_email_size, - const char *replace_name, size_t replace_name_size, - const char *replace_email, size_t replace_email_size) -{ - int error; - git_mailmap_entry *entry = git__calloc(1, sizeof(git_mailmap_entry)); - GIT_ERROR_CHECK_ALLOC(entry); - - GIT_ASSERT_ARG(mm); - GIT_ASSERT_ARG(replace_email && *replace_email); - - if (real_name_size > 0) { - entry->real_name = git__substrdup(real_name, real_name_size); - GIT_ERROR_CHECK_ALLOC(entry->real_name); - } - if (real_email_size > 0) { - entry->real_email = git__substrdup(real_email, real_email_size); - GIT_ERROR_CHECK_ALLOC(entry->real_email); - } - if (replace_name_size > 0) { - entry->replace_name = git__substrdup(replace_name, replace_name_size); - GIT_ERROR_CHECK_ALLOC(entry->replace_name); - } - entry->replace_email = git__substrdup(replace_email, replace_email_size); - GIT_ERROR_CHECK_ALLOC(entry->replace_email); - - error = git_vector_insert_sorted(&mm->entries, entry, mailmap_entry_replace); - if (error == GIT_EEXISTS) - error = GIT_OK; - else if (error < 0) - mailmap_entry_free(entry); - - return error; -} - -int git_mailmap_add_entry( - git_mailmap *mm, const char *real_name, const char *real_email, - const char *replace_name, const char *replace_email) -{ - return mailmap_add_entry_unterminated( - mm, - real_name, real_name ? strlen(real_name) : 0, - real_email, real_email ? strlen(real_email) : 0, - replace_name, replace_name ? strlen(replace_name) : 0, - replace_email, strlen(replace_email)); -} - -static int mailmap_add_buffer(git_mailmap *mm, const char *buf, size_t len) -{ - int error = 0; - git_parse_ctx ctx; - - /* Scratch buffers containing the real parsed names & emails */ - git_str real_name = GIT_STR_INIT; - git_str real_email = GIT_STR_INIT; - git_str replace_name = GIT_STR_INIT; - git_str replace_email = GIT_STR_INIT; - - /* Buffers may not contain '\0's. */ - if (memchr(buf, '\0', len) != NULL) - return -1; - - git_parse_ctx_init(&ctx, buf, len); - - /* Run the parser */ - while (ctx.remain_len > 0) { - error = parse_mailmap_entry( - &real_name, &real_email, &replace_name, &replace_email, &ctx); - if (error < 0) { - error = 0; /* Skip lines which don't contain a valid entry */ - git_parse_advance_line(&ctx); - continue; /* TODO: warn */ - } - - /* NOTE: Can't use add_entry(...) as our buffers aren't terminated */ - error = mailmap_add_entry_unterminated( - mm, real_name.ptr, real_name.size, real_email.ptr, real_email.size, - replace_name.ptr, replace_name.size, replace_email.ptr, replace_email.size); - if (error < 0) - goto cleanup; - - error = 0; - } - -cleanup: - git_str_dispose(&real_name); - git_str_dispose(&real_email); - git_str_dispose(&replace_name); - git_str_dispose(&replace_email); - return error; -} - -int git_mailmap_from_buffer(git_mailmap **out, const char *data, size_t len) -{ - int error = git_mailmap_new(out); - if (error < 0) - return error; - - error = mailmap_add_buffer(*out, data, len); - if (error < 0) { - git_mailmap_free(*out); - *out = NULL; - } - return error; -} - -static int mailmap_add_blob( - git_mailmap *mm, git_repository *repo, const char *rev) -{ - git_object *object = NULL; - git_blob *blob = NULL; - git_str content = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(mm); - GIT_ASSERT_ARG(repo); - - error = git_revparse_single(&object, repo, rev); - if (error < 0) - goto cleanup; - - error = git_object_peel((git_object **)&blob, object, GIT_OBJECT_BLOB); - if (error < 0) - goto cleanup; - - error = git_blob__getbuf(&content, blob); - if (error < 0) - goto cleanup; - - error = mailmap_add_buffer(mm, content.ptr, content.size); - if (error < 0) - goto cleanup; - -cleanup: - git_str_dispose(&content); - git_blob_free(blob); - git_object_free(object); - return error; -} - -static int mailmap_add_file_ondisk( - git_mailmap *mm, const char *path, git_repository *repo) -{ - const char *base = repo ? git_repository_workdir(repo) : NULL; - git_str fullpath = GIT_STR_INIT; - git_str content = GIT_STR_INIT; - int error; - - error = git_fs_path_join_unrooted(&fullpath, path, base, NULL); - if (error < 0) - goto cleanup; - - error = git_path_validate_str_length(repo, &fullpath); - if (error < 0) - goto cleanup; - - error = git_futils_readbuffer(&content, fullpath.ptr); - if (error < 0) - goto cleanup; - - error = mailmap_add_buffer(mm, content.ptr, content.size); - if (error < 0) - goto cleanup; - -cleanup: - git_str_dispose(&fullpath); - git_str_dispose(&content); - return error; -} - -/* NOTE: Only expose with an error return, currently never errors */ -static void mailmap_add_from_repository(git_mailmap *mm, git_repository *repo) -{ - git_config *config = NULL; - git_str rev_buf = GIT_STR_INIT; - git_str path_buf = GIT_STR_INIT; - const char *rev = NULL; - const char *path = NULL; - - /* If we're in a bare repo, default blob to 'HEAD:.mailmap' */ - if (repo->is_bare) - rev = MM_BLOB_DEFAULT; - - /* Try to load 'mailmap.file' and 'mailmap.blob' cfgs from the repo */ - if (git_repository_config(&config, repo) == 0) { - if (git_config__get_string_buf(&rev_buf, config, MM_BLOB_CONFIG) == 0) - rev = rev_buf.ptr; - if (git_config__get_path(&path_buf, config, MM_FILE_CONFIG) == 0) - path = path_buf.ptr; - } - - /* - * Load mailmap files in order, overriding previous entries with new ones. - * 1. The '.mailmap' file in the repository's workdir root, - * 2. The blob described by the 'mailmap.blob' config (default HEAD:.mailmap), - * 3. The file described by the 'mailmap.file' config. - * - * We ignore errors from these loads, as these files may not exist, or may - * contain invalid information, and we don't want to report that error. - * - * XXX: Warn? - */ - if (!repo->is_bare) - mailmap_add_file_ondisk(mm, MM_FILE, repo); - if (rev != NULL) - mailmap_add_blob(mm, repo, rev); - if (path != NULL) - mailmap_add_file_ondisk(mm, path, repo); - - git_str_dispose(&rev_buf); - git_str_dispose(&path_buf); - git_config_free(config); -} - -int git_mailmap_from_repository(git_mailmap **out, git_repository *repo) -{ - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - if ((error = git_mailmap_new(out)) < 0) - return error; - - mailmap_add_from_repository(*out, repo); - return 0; -} - -const git_mailmap_entry *git_mailmap_entry_lookup( - const git_mailmap *mm, const char *name, const char *email) -{ - int error; - ssize_t fallback = -1; - size_t idx; - git_mailmap_entry *entry; - - /* The lookup needle we want to use only sets the replace_email. */ - git_mailmap_entry needle = { NULL }; - needle.replace_email = (char *)email; - - GIT_ASSERT_ARG_WITH_RETVAL(email, NULL); - - if (!mm) - return NULL; - - /* - * We want to find the place to start looking. so we do a binary search for - * the "fallback" nameless entry. If we find it, we advance past it and record - * the index. - */ - error = git_vector_bsearch(&idx, (git_vector *)&mm->entries, &needle); - if (error >= 0) - fallback = idx++; - else if (error != GIT_ENOTFOUND) - return NULL; - - /* do a linear search for an exact match */ - for (; idx < git_vector_length(&mm->entries); ++idx) { - entry = git_vector_get(&mm->entries, idx); - - if (git__strcmp(entry->replace_email, email)) - break; /* it's a different email, so we're done looking */ - - /* should be specific */ - GIT_ASSERT_WITH_RETVAL(entry->replace_name, NULL); - if (!name || !git__strcmp(entry->replace_name, name)) - return entry; - } - - if (fallback < 0) - return NULL; /* no fallback */ - return git_vector_get(&mm->entries, fallback); -} - -int git_mailmap_resolve( - const char **real_name, const char **real_email, - const git_mailmap *mailmap, - const char *name, const char *email) -{ - const git_mailmap_entry *entry = NULL; - - GIT_ASSERT(name); - GIT_ASSERT(email); - - *real_name = name; - *real_email = email; - - if ((entry = git_mailmap_entry_lookup(mailmap, name, email))) { - if (entry->real_name) - *real_name = entry->real_name; - if (entry->real_email) - *real_email = entry->real_email; - } - return 0; -} - -int git_mailmap_resolve_signature( - git_signature **out, const git_mailmap *mailmap, const git_signature *sig) -{ - const char *name = NULL; - const char *email = NULL; - int error; - - if (!sig) - return 0; - - error = git_mailmap_resolve(&name, &email, mailmap, sig->name, sig->email); - if (error < 0) - return error; - - error = git_signature_new(out, name, email, sig->when.time, sig->when.offset); - if (error < 0) - return error; - - /* Copy over the sign, as git_signature_new doesn't let you pass it. */ - (*out)->when.sign = sig->when.sign; - return 0; -} diff --git a/src/mailmap.h b/src/mailmap.h deleted file mode 100644 index 2c9736a4a..000000000 --- a/src/mailmap.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_mailmap_h__ -#define INCLUDE_mailmap_h__ - -#include "git2/mailmap.h" -#include "vector.h" - -/* - * A mailmap is stored as a sorted vector of 'git_mailmap_entry's. These entries - * are sorted first by 'replace_email', and then by 'replace_name'. NULL - * replace_names are ordered first. - * - * Looking up a name and email in the mailmap is done with a binary search. - */ -struct git_mailmap { - git_vector entries; -}; - -/* Single entry parsed from a mailmap */ -typedef struct git_mailmap_entry { - char *real_name; /**< the real name (may be NULL) */ - char *real_email; /**< the real email (may be NULL) */ - char *replace_name; /**< the name to replace (may be NULL) */ - char *replace_email; /**< the email to replace */ -} git_mailmap_entry; - -const git_mailmap_entry *git_mailmap_entry_lookup( - const git_mailmap *mm, const char *name, const char *email); - -#endif diff --git a/src/map.h b/src/map.h deleted file mode 100644 index 01931d199..000000000 --- a/src/map.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_map_h__ -#define INCLUDE_map_h__ - -#include "common.h" - - -/* p_mmap() prot values */ -#define GIT_PROT_NONE 0x0 -#define GIT_PROT_READ 0x1 -#define GIT_PROT_WRITE 0x2 -#define GIT_PROT_EXEC 0x4 - -/* git__mmmap() flags values */ -#define GIT_MAP_FILE 0 -#define GIT_MAP_SHARED 1 -#define GIT_MAP_PRIVATE 2 -#define GIT_MAP_TYPE 0xf -#define GIT_MAP_FIXED 0x10 - -#ifdef __amigaos4__ -#define MAP_FAILED 0 -#endif - -typedef struct { /* memory mapped buffer */ - void *data; /* data bytes */ - size_t len; /* data length */ -#ifdef GIT_WIN32 - HANDLE fmh; /* file mapping handle */ -#endif -} git_map; - -#define GIT_MMAP_VALIDATE(out, len, prot, flags) do { \ - GIT_ASSERT(out != NULL && len > 0); \ - GIT_ASSERT((prot & GIT_PROT_WRITE) || (prot & GIT_PROT_READ)); \ - GIT_ASSERT((flags & GIT_MAP_FIXED) == 0); } while (0) - -extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset); -extern int p_munmap(git_map *map); - -#endif diff --git a/src/merge.c b/src/merge.c deleted file mode 100644 index 641b32632..000000000 --- a/src/merge.c +++ /dev/null @@ -1,3435 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "merge.h" - -#include "posix.h" -#include "str.h" -#include "repository.h" -#include "revwalk.h" -#include "commit_list.h" -#include "fs_path.h" -#include "refs.h" -#include "object.h" -#include "iterator.h" -#include "refs.h" -#include "diff.h" -#include "diff_generate.h" -#include "diff_tform.h" -#include "checkout.h" -#include "tree.h" -#include "blob.h" -#include "oid.h" -#include "index.h" -#include "filebuf.h" -#include "config.h" -#include "oidarray.h" -#include "annotated_commit.h" -#include "commit.h" -#include "oidarray.h" -#include "merge_driver.h" -#include "oidmap.h" -#include "array.h" - -#include "git2/types.h" -#include "git2/repository.h" -#include "git2/object.h" -#include "git2/commit.h" -#include "git2/merge.h" -#include "git2/refs.h" -#include "git2/reset.h" -#include "git2/checkout.h" -#include "git2/signature.h" -#include "git2/config.h" -#include "git2/tree.h" -#include "git2/oidarray.h" -#include "git2/annotated_commit.h" -#include "git2/sys/index.h" -#include "git2/sys/hashsig.h" - -#define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0) -#define GIT_MERGE_INDEX_ENTRY_ISFILE(X) S_ISREG((X).mode) - - -typedef enum { - TREE_IDX_ANCESTOR = 0, - TREE_IDX_OURS = 1, - TREE_IDX_THEIRS = 2 -} merge_tree_index_t; - -/* Tracks D/F conflicts */ -struct merge_diff_df_data { - const char *df_path; - const char *prev_path; - git_merge_diff *prev_conflict; -}; - -/* - * This acts as a negative cache entry marker. In case we've tried to calculate - * similarity metrics for a given blob already but `git_hashsig` determined - * that it's too small in order to have a meaningful hash signature, we will - * insert the address of this marker instead of `NULL`. Like this, we can - * easily check whether we have checked a gien entry already and skip doing the - * calculation again and again. - */ -static int cache_invalid_marker; - -/* Merge base computation */ - -static int merge_bases_many(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, size_t length, const git_oid input_array[]) -{ - git_revwalk *walk = NULL; - git_vector list; - git_commit_list *result = NULL; - git_commit_list_node *commit; - int error = -1; - unsigned int i; - - if (length < 2) { - git_error_set(GIT_ERROR_INVALID, "at least two commits are required to find an ancestor"); - return -1; - } - - if (git_vector_init(&list, length - 1, NULL) < 0) - return -1; - - if (git_revwalk_new(&walk, repo) < 0) - goto on_error; - - for (i = 1; i < length; i++) { - commit = git_revwalk__commit_lookup(walk, &input_array[i]); - if (commit == NULL) - goto on_error; - - git_vector_insert(&list, commit); - } - - commit = git_revwalk__commit_lookup(walk, &input_array[0]); - if (commit == NULL) - goto on_error; - - if (git_merge__bases_many(&result, walk, commit, &list, 0) < 0) - goto on_error; - - if (!result) { - git_error_set(GIT_ERROR_MERGE, "no merge base found"); - error = GIT_ENOTFOUND; - goto on_error; - } - - *out = result; - *walk_out = walk; - - git_vector_free(&list); - return 0; - -on_error: - git_vector_free(&list); - git_revwalk_free(walk); - return error; -} - -int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]) -{ - git_revwalk *walk; - git_commit_list *result = NULL; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(input_array); - - if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 0) - return error; - - git_oid_cpy(out, &result->item->oid); - - git_commit_list_free(&result); - git_revwalk_free(walk); - - return 0; -} - -int git_merge_bases_many(git_oidarray *out, git_repository *repo, size_t length, const git_oid input_array[]) -{ - git_revwalk *walk; - git_commit_list *list, *result = NULL; - int error = 0; - git_array_oid_t array; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(input_array); - - if ((error = merge_bases_many(&result, &walk, repo, length, input_array)) < 0) - return error; - - git_array_init(array); - - list = result; - while (list) { - git_oid *id = git_array_alloc(array); - if (id == NULL) { - error = -1; - goto cleanup; - } - - git_oid_cpy(id, &list->item->oid); - list = list->next; - } - - git_oidarray__from_array(out, &array); - -cleanup: - git_commit_list_free(&result); - git_revwalk_free(walk); - - return error; -} - -int git_merge_base_octopus(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]) -{ - git_oid result; - unsigned int i; - int error = -1; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(input_array); - - if (length < 2) { - git_error_set(GIT_ERROR_INVALID, "at least two commits are required to find an ancestor"); - return -1; - } - - result = input_array[0]; - for (i = 1; i < length; i++) { - error = git_merge_base(&result, repo, &result, &input_array[i]); - if (error < 0) - return error; - } - - *out = result; - - return 0; -} - -static int merge_bases(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, const git_oid *one, const git_oid *two) -{ - git_revwalk *walk; - git_vector list; - git_commit_list *result = NULL; - git_commit_list_node *commit; - void *contents[1]; - - if (git_revwalk_new(&walk, repo) < 0) - return -1; - - commit = git_revwalk__commit_lookup(walk, two); - if (commit == NULL) - goto on_error; - - /* This is just one value, so we can do it on the stack */ - memset(&list, 0x0, sizeof(git_vector)); - contents[0] = commit; - list.length = 1; - list.contents = contents; - - commit = git_revwalk__commit_lookup(walk, one); - if (commit == NULL) - goto on_error; - - if (git_merge__bases_many(&result, walk, commit, &list, 0) < 0) - goto on_error; - - if (!result) { - git_revwalk_free(walk); - git_error_set(GIT_ERROR_MERGE, "no merge base found"); - return GIT_ENOTFOUND; - } - - *out = result; - *walk_out = walk; - - return 0; - -on_error: - git_revwalk_free(walk); - return -1; - -} - -int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two) -{ - int error; - git_revwalk *walk; - git_commit_list *result; - - if ((error = merge_bases(&result, &walk, repo, one, two)) < 0) - return error; - - git_oid_cpy(out, &result->item->oid); - git_commit_list_free(&result); - git_revwalk_free(walk); - - return 0; -} - -int git_merge_bases(git_oidarray *out, git_repository *repo, const git_oid *one, const git_oid *two) -{ - int error; - git_revwalk *walk; - git_commit_list *result, *list; - git_array_oid_t array; - - git_array_init(array); - - if ((error = merge_bases(&result, &walk, repo, one, two)) < 0) - return error; - - list = result; - while (list) { - git_oid *id = git_array_alloc(array); - if (id == NULL) - goto on_error; - - git_oid_cpy(id, &list->item->oid); - list = list->next; - } - - git_oidarray__from_array(out, &array); - git_commit_list_free(&result); - git_revwalk_free(walk); - - return 0; - -on_error: - git_commit_list_free(&result); - git_revwalk_free(walk); - return -1; -} - -static int interesting(git_pqueue *list) -{ - size_t i; - - for (i = 0; i < git_pqueue_size(list); i++) { - git_commit_list_node *commit = git_pqueue_get(list, i); - if ((commit->flags & STALE) == 0) - return 1; - } - - return 0; -} - -static int clear_commit_marks_1(git_commit_list **plist, - git_commit_list_node *commit, unsigned int mark) -{ - while (commit) { - unsigned int i; - - if (!(mark & commit->flags)) - return 0; - - commit->flags &= ~mark; - - for (i = 1; i < commit->out_degree; i++) { - git_commit_list_node *p = commit->parents[i]; - if (git_commit_list_insert(p, plist) == NULL) - return -1; - } - - commit = commit->out_degree ? commit->parents[0] : NULL; - } - - return 0; -} - -static int clear_commit_marks_many(git_vector *commits, unsigned int mark) -{ - git_commit_list *list = NULL; - git_commit_list_node *c; - unsigned int i; - - git_vector_foreach(commits, i, c) { - if (git_commit_list_insert(c, &list) == NULL) - return -1; - } - - while (list) - if (clear_commit_marks_1(&list, git_commit_list_pop(&list), mark) < 0) - return -1; - return 0; -} - -static int clear_commit_marks(git_commit_list_node *commit, unsigned int mark) -{ - git_commit_list *list = NULL; - if (git_commit_list_insert(commit, &list) == NULL) - return -1; - while (list) - if (clear_commit_marks_1(&list, git_commit_list_pop(&list), mark) < 0) - return -1; - return 0; -} - -static int paint_down_to_common( - git_commit_list **out, - git_revwalk *walk, - git_commit_list_node *one, - git_vector *twos, - uint32_t minimum_generation) -{ - git_pqueue list; - git_commit_list *result = NULL; - git_commit_list_node *two; - - int error; - unsigned int i; - - if (git_pqueue_init(&list, 0, twos->length * 2, git_commit_list_generation_cmp) < 0) - return -1; - - one->flags |= PARENT1; - if (git_pqueue_insert(&list, one) < 0) - return -1; - - git_vector_foreach(twos, i, two) { - if (git_commit_list_parse(walk, two) < 0) - return -1; - - two->flags |= PARENT2; - if (git_pqueue_insert(&list, two) < 0) - return -1; - } - - /* as long as there are non-STALE commits */ - while (interesting(&list)) { - git_commit_list_node *commit = git_pqueue_pop(&list); - int flags; - - if (commit == NULL) - break; - - flags = commit->flags & (PARENT1 | PARENT2 | STALE); - if (flags == (PARENT1 | PARENT2)) { - if (!(commit->flags & RESULT)) { - commit->flags |= RESULT; - if (git_commit_list_insert(commit, &result) == NULL) - return -1; - } - /* we mark the parents of a merge stale */ - flags |= STALE; - } - - for (i = 0; i < commit->out_degree; i++) { - git_commit_list_node *p = commit->parents[i]; - if ((p->flags & flags) == flags) - continue; - if (p->generation < minimum_generation) - continue; - - if ((error = git_commit_list_parse(walk, p)) < 0) - return error; - - p->flags |= flags; - if (git_pqueue_insert(&list, p) < 0) - return -1; - } - } - - git_pqueue_free(&list); - *out = result; - return 0; -} - -static int remove_redundant(git_revwalk *walk, git_vector *commits, uint32_t minimum_generation) -{ - git_vector work = GIT_VECTOR_INIT; - unsigned char *redundant; - unsigned int *filled_index; - unsigned int i, j; - int error = 0; - - redundant = git__calloc(commits->length, 1); - GIT_ERROR_CHECK_ALLOC(redundant); - filled_index = git__calloc((commits->length - 1), sizeof(unsigned int)); - GIT_ERROR_CHECK_ALLOC(filled_index); - - for (i = 0; i < commits->length; ++i) { - if ((error = git_commit_list_parse(walk, commits->contents[i])) < 0) - goto done; - } - - for (i = 0; i < commits->length; ++i) { - git_commit_list *common = NULL; - git_commit_list_node *commit = commits->contents[i]; - - if (redundant[i]) - continue; - - git_vector_clear(&work); - - for (j = 0; j < commits->length; j++) { - if (i == j || redundant[j]) - continue; - - filled_index[work.length] = j; - if ((error = git_vector_insert(&work, commits->contents[j])) < 0) - goto done; - } - - error = paint_down_to_common(&common, walk, commit, &work, minimum_generation); - if (error < 0) - goto done; - - if (commit->flags & PARENT2) - redundant[i] = 1; - - for (j = 0; j < work.length; j++) { - git_commit_list_node *w = work.contents[j]; - if (w->flags & PARENT1) - redundant[filled_index[j]] = 1; - } - - git_commit_list_free(&common); - - if ((error = clear_commit_marks(commit, ALL_FLAGS)) < 0 || - (error = clear_commit_marks_many(&work, ALL_FLAGS)) < 0) - goto done; - } - - for (i = 0; i < commits->length; ++i) { - if (redundant[i]) - commits->contents[i] = NULL; - } - -done: - git__free(redundant); - git__free(filled_index); - git_vector_free(&work); - return error; -} - -int git_merge__bases_many( - git_commit_list **out, - git_revwalk *walk, - git_commit_list_node *one, - git_vector *twos, - uint32_t minimum_generation) -{ - int error; - unsigned int i; - git_commit_list_node *two; - git_commit_list *result = NULL, *tmp = NULL; - - /* If there's only the one commit, there can be no merge bases */ - if (twos->length == 0) { - *out = NULL; - return 0; - } - - /* if the commit is repeated, we have a our merge base already */ - git_vector_foreach(twos, i, two) { - if (one == two) - return git_commit_list_insert(one, out) ? 0 : -1; - } - - if (git_commit_list_parse(walk, one) < 0) - return -1; - - error = paint_down_to_common(&result, walk, one, twos, minimum_generation); - if (error < 0) - return error; - - /* filter out any stale commits in the results */ - tmp = result; - result = NULL; - - while (tmp) { - git_commit_list_node *c = git_commit_list_pop(&tmp); - if (!(c->flags & STALE)) - if (git_commit_list_insert_by_date(c, &result) == NULL) - return -1; - } - - /* - * more than one merge base -- see if there are redundant merge - * bases and remove them - */ - if (result && result->next) { - git_vector redundant = GIT_VECTOR_INIT; - - while (result) - git_vector_insert(&redundant, git_commit_list_pop(&result)); - - if ((error = clear_commit_marks(one, ALL_FLAGS)) < 0 || - (error = clear_commit_marks_many(twos, ALL_FLAGS)) < 0 || - (error = remove_redundant(walk, &redundant, minimum_generation)) < 0) { - git_vector_free(&redundant); - return error; - } - - git_vector_foreach(&redundant, i, two) { - if (two != NULL) - git_commit_list_insert_by_date(two, &result); - } - - git_vector_free(&redundant); - } - - *out = result; - return 0; -} - -int git_repository_mergehead_foreach( - git_repository *repo, - git_repository_mergehead_foreach_cb cb, - void *payload) -{ - git_str merge_head_path = GIT_STR_INIT, merge_head_file = GIT_STR_INIT; - char *buffer, *line; - size_t line_num = 1; - git_oid oid; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(cb); - - if ((error = git_str_joinpath(&merge_head_path, repo->gitdir, - GIT_MERGE_HEAD_FILE)) < 0) - return error; - - if ((error = git_futils_readbuffer(&merge_head_file, - git_str_cstr(&merge_head_path))) < 0) - goto cleanup; - - buffer = merge_head_file.ptr; - - while ((line = git__strsep(&buffer, "\n")) != NULL) { - if (strlen(line) != GIT_OID_HEXSZ) { - git_error_set(GIT_ERROR_INVALID, "unable to parse OID - invalid length"); - error = -1; - goto cleanup; - } - - if ((error = git_oid_fromstr(&oid, line)) < 0) - goto cleanup; - - if ((error = cb(&oid, payload)) != 0) { - git_error_set_after_callback(error); - goto cleanup; - } - - ++line_num; - } - - if (*buffer) { - git_error_set(GIT_ERROR_MERGE, "no EOL at line %"PRIuZ, line_num); - error = -1; - goto cleanup; - } - -cleanup: - git_str_dispose(&merge_head_path); - git_str_dispose(&merge_head_file); - - return error; -} - -GIT_INLINE(int) index_entry_cmp(const git_index_entry *a, const git_index_entry *b) -{ - int value = 0; - - if (a->path == NULL) - return (b->path == NULL) ? 0 : 1; - - if ((value = a->mode - b->mode) == 0 && - (value = git_oid__cmp(&a->id, &b->id)) == 0) - value = strcmp(a->path, b->path); - - return value; -} - -/* Conflict resolution */ - -static int merge_conflict_resolve_trivial( - int *resolved, - git_merge_diff_list *diff_list, - const git_merge_diff *conflict) -{ - int ours_empty, theirs_empty; - int ours_changed, theirs_changed, ours_theirs_differ; - git_index_entry const *result = NULL; - int error = 0; - - GIT_ASSERT_ARG(resolved); - GIT_ASSERT_ARG(diff_list); - GIT_ASSERT_ARG(conflict); - - *resolved = 0; - - if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE || - conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) - return 0; - - if (conflict->our_status == GIT_DELTA_RENAMED || - conflict->their_status == GIT_DELTA_RENAMED) - return 0; - - ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry); - theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry); - - ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED); - theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED); - ours_theirs_differ = ours_changed && theirs_changed && - index_entry_cmp(&conflict->our_entry, &conflict->their_entry); - - /* - * Note: with only one ancestor, some cases are not distinct: - * - * 16: ancest:anc1/anc2, head:anc1, remote:anc2 = result:no merge - * 3: ancest:(empty)^, head:head, remote:(empty) = result:no merge - * 2: ancest:(empty)^, head:(empty), remote:remote = result:no merge - * - * Note that the two cases that take D/F conflicts into account - * specifically do not need to be explicitly tested, as D/F conflicts - * would fail the *empty* test: - * - * 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head - * 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote - * - * Note that many of these cases need not be explicitly tested, as - * they simply degrade to "all different" cases (eg, 11): - * - * 4: ancest:(empty)^, head:head, remote:remote = result:no merge - * 7: ancest:ancest+, head:(empty), remote:remote = result:no merge - * 9: ancest:ancest+, head:head, remote:(empty) = result:no merge - * 11: ancest:ancest+, head:head, remote:remote = result:no merge - */ - - /* 5ALT: ancest:*, head:head, remote:head = result:head */ - if (ours_changed && !ours_empty && !ours_theirs_differ) - result = &conflict->our_entry; - /* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */ - else if (ours_changed && ours_empty && theirs_empty) - *resolved = 0; - /* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */ - else if (ours_empty && !theirs_changed) - *resolved = 0; - /* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */ - else if (!ours_changed && theirs_empty) - *resolved = 0; - /* 13: ancest:ancest+, head:head, remote:ancest = result:head */ - else if (ours_changed && !theirs_changed) - result = &conflict->our_entry; - /* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */ - else if (!ours_changed && theirs_changed) - result = &conflict->their_entry; - else - *resolved = 0; - - if (result != NULL && - GIT_MERGE_INDEX_ENTRY_EXISTS(*result) && - (error = git_vector_insert(&diff_list->staged, (void *)result)) >= 0) - *resolved = 1; - - /* Note: trivial resolution does not update the REUC. */ - - return error; -} - -static int merge_conflict_resolve_one_removed( - int *resolved, - git_merge_diff_list *diff_list, - const git_merge_diff *conflict) -{ - int ours_empty, theirs_empty; - int ours_changed, theirs_changed; - int error = 0; - - GIT_ASSERT_ARG(resolved); - GIT_ASSERT_ARG(diff_list); - GIT_ASSERT_ARG(conflict); - - *resolved = 0; - - if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE || - conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) - return 0; - - ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry); - theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry); - - ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED); - theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED); - - /* Removed in both */ - if (ours_changed && ours_empty && theirs_empty) - *resolved = 1; - /* Removed in ours */ - else if (ours_empty && !theirs_changed) - *resolved = 1; - /* Removed in theirs */ - else if (!ours_changed && theirs_empty) - *resolved = 1; - - if (*resolved) - git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); - - return error; -} - -static int merge_conflict_resolve_one_renamed( - int *resolved, - git_merge_diff_list *diff_list, - const git_merge_diff *conflict) -{ - int ours_renamed, theirs_renamed; - int ours_changed, theirs_changed; - git_index_entry *merged; - int error = 0; - - GIT_ASSERT_ARG(resolved); - GIT_ASSERT_ARG(diff_list); - GIT_ASSERT_ARG(conflict); - - *resolved = 0; - - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) || - !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) - return 0; - - ours_renamed = (conflict->our_status == GIT_DELTA_RENAMED); - theirs_renamed = (conflict->their_status == GIT_DELTA_RENAMED); - - if (!ours_renamed && !theirs_renamed) - return 0; - - /* Reject one file in a 2->1 conflict */ - if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 || - conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 || - conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) - return 0; - - ours_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->our_entry.id) != 0) || - (conflict->ancestor_entry.mode != conflict->our_entry.mode); - - theirs_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->their_entry.id) != 0) || - (conflict->ancestor_entry.mode != conflict->their_entry.mode); - - /* if both are modified (and not to a common target) require a merge */ - if (ours_changed && theirs_changed && - git_oid__cmp(&conflict->our_entry.id, &conflict->their_entry.id) != 0) - return 0; - - if ((merged = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry))) == NULL) - return -1; - - if (ours_changed) - memcpy(merged, &conflict->our_entry, sizeof(git_index_entry)); - else - memcpy(merged, &conflict->their_entry, sizeof(git_index_entry)); - - if (ours_renamed) - merged->path = conflict->our_entry.path; - else - merged->path = conflict->their_entry.path; - - *resolved = 1; - - git_vector_insert(&diff_list->staged, merged); - git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); - - return error; -} - -static bool merge_conflict_can_resolve_contents( - const git_merge_diff *conflict) -{ - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) || - !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) - return false; - - /* Reject D/F conflicts */ - if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE) - return false; - - /* Reject submodules. */ - if (S_ISGITLINK(conflict->ancestor_entry.mode) || - S_ISGITLINK(conflict->our_entry.mode) || - S_ISGITLINK(conflict->their_entry.mode)) - return false; - - /* Reject link/file conflicts. */ - if ((S_ISLNK(conflict->ancestor_entry.mode) ^ - S_ISLNK(conflict->our_entry.mode)) || - (S_ISLNK(conflict->ancestor_entry.mode) ^ - S_ISLNK(conflict->their_entry.mode))) - return false; - - /* Reject name conflicts */ - if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 || - conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) - return false; - - if ((conflict->our_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && - (conflict->their_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && - strcmp(conflict->ancestor_entry.path, conflict->their_entry.path) != 0) - return false; - - return true; -} - -static int merge_conflict_invoke_driver( - git_index_entry **out, - const char *name, - git_merge_driver *driver, - git_merge_diff_list *diff_list, - git_merge_driver_source *src) -{ - git_index_entry *result; - git_buf buf = {0}; - const char *path; - uint32_t mode; - git_odb *odb = NULL; - git_oid oid; - int error; - - *out = NULL; - - if ((error = driver->apply(driver, &path, &mode, &buf, name, src)) < 0 || - (error = git_repository_odb(&odb, src->repo)) < 0 || - (error = git_odb_write(&oid, odb, buf.ptr, buf.size, GIT_OBJECT_BLOB)) < 0) - goto done; - - result = git_pool_mallocz(&diff_list->pool, sizeof(git_index_entry)); - GIT_ERROR_CHECK_ALLOC(result); - - git_oid_cpy(&result->id, &oid); - result->mode = mode; - result->file_size = (uint32_t)buf.size; - - result->path = git_pool_strdup(&diff_list->pool, path); - GIT_ERROR_CHECK_ALLOC(result->path); - - *out = result; - -done: - git_buf_dispose(&buf); - git_odb_free(odb); - - return error; -} - -static int merge_conflict_resolve_contents( - int *resolved, - git_merge_diff_list *diff_list, - const git_merge_diff *conflict, - const git_merge_options *merge_opts, - const git_merge_file_options *file_opts) -{ - git_merge_driver_source source = {0}; - git_merge_file_result result = {0}; - git_merge_driver *driver; - git_merge_driver__builtin builtin = {{0}}; - git_index_entry *merge_result; - git_odb *odb = NULL; - const char *name; - bool fallback = false; - int error; - - GIT_ASSERT_ARG(resolved); - GIT_ASSERT_ARG(diff_list); - GIT_ASSERT_ARG(conflict); - - *resolved = 0; - - if (!merge_conflict_can_resolve_contents(conflict)) - return 0; - - source.repo = diff_list->repo; - source.default_driver = merge_opts->default_driver; - source.file_opts = file_opts; - source.ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? - &conflict->ancestor_entry : NULL; - source.ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? - &conflict->our_entry : NULL; - source.theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? - &conflict->their_entry : NULL; - - if (file_opts->favor != GIT_MERGE_FILE_FAVOR_NORMAL) { - /* if the user requested a particular type of resolution (via the - * favor flag) then let that override the gitattributes and use - * the builtin driver. - */ - name = "text"; - builtin.base.apply = git_merge_driver__builtin_apply; - builtin.favor = file_opts->favor; - - driver = &builtin.base; - } else { - /* find the merge driver for this file */ - if ((error = git_merge_driver_for_source(&name, &driver, &source)) < 0) - goto done; - - if (driver == NULL) - fallback = true; - } - - if (driver) { - error = merge_conflict_invoke_driver(&merge_result, name, driver, - diff_list, &source); - - if (error == GIT_PASSTHROUGH) - fallback = true; - } - - if (fallback) { - error = merge_conflict_invoke_driver(&merge_result, "text", - &git_merge_driver__text.base, diff_list, &source); - } - - if (error < 0) { - if (error == GIT_EMERGECONFLICT) - error = 0; - - goto done; - } - - git_vector_insert(&diff_list->staged, merge_result); - git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); - - *resolved = 1; - -done: - git_merge_file_result_free(&result); - git_odb_free(odb); - - return error; -} - -static int merge_conflict_resolve( - int *out, - git_merge_diff_list *diff_list, - const git_merge_diff *conflict, - const git_merge_options *merge_opts, - const git_merge_file_options *file_opts) -{ - int resolved = 0; - int error = 0; - - *out = 0; - - if ((error = merge_conflict_resolve_trivial( - &resolved, diff_list, conflict)) < 0) - goto done; - - if (!resolved && (error = merge_conflict_resolve_one_removed( - &resolved, diff_list, conflict)) < 0) - goto done; - - if (!resolved && (error = merge_conflict_resolve_one_renamed( - &resolved, diff_list, conflict)) < 0) - goto done; - - if (!resolved && (error = merge_conflict_resolve_contents( - &resolved, diff_list, conflict, merge_opts, file_opts)) < 0) - goto done; - - *out = resolved; - -done: - return error; -} - -/* Rename detection and coalescing */ - -struct merge_diff_similarity { - unsigned char similarity; - size_t other_idx; -}; - -static int index_entry_similarity_calc( - void **out, - git_repository *repo, - git_index_entry *entry, - const git_merge_options *opts) -{ - git_blob *blob; - git_diff_file diff_file = {{{0}}}; - git_object_size_t blobsize; - int error; - - if (*out || *out == &cache_invalid_marker) - return 0; - - *out = NULL; - - if ((error = git_blob_lookup(&blob, repo, &entry->id)) < 0) - return error; - - git_oid_cpy(&diff_file.id, &entry->id); - diff_file.path = entry->path; - diff_file.size = entry->file_size; - diff_file.mode = entry->mode; - diff_file.flags = 0; - - blobsize = git_blob_rawsize(blob); - - /* file too big for rename processing */ - if (!git__is_sizet(blobsize)) - return 0; - - error = opts->metric->buffer_signature(out, &diff_file, - git_blob_rawcontent(blob), (size_t)blobsize, - opts->metric->payload); - if (error == GIT_EBUFS) - *out = &cache_invalid_marker; - - git_blob_free(blob); - - return error; -} - -static int index_entry_similarity_inexact( - git_repository *repo, - git_index_entry *a, - size_t a_idx, - git_index_entry *b, - size_t b_idx, - void **cache, - const git_merge_options *opts) -{ - int score = 0; - int error = 0; - - if (!GIT_MODE_ISBLOB(a->mode) || !GIT_MODE_ISBLOB(b->mode)) - return 0; - - /* update signature cache if needed */ - if ((error = index_entry_similarity_calc(&cache[a_idx], repo, a, opts)) < 0 || - (error = index_entry_similarity_calc(&cache[b_idx], repo, b, opts)) < 0) - return error; - - /* some metrics may not wish to process this file (too big / too small) */ - if (cache[a_idx] == &cache_invalid_marker || cache[b_idx] == &cache_invalid_marker) - return 0; - - /* compare signatures */ - if (opts->metric->similarity(&score, cache[a_idx], cache[b_idx], opts->metric->payload) < 0) - return -1; - - /* clip score */ - if (score < 0) - score = 0; - else if (score > 100) - score = 100; - - return score; -} - -/* Tracks deletes by oid for merge_diff_mark_similarity_exact(). This is a -* non-shrinking queue where next_pos is the next position to dequeue. -*/ -typedef struct { - git_array_t(size_t) arr; - size_t next_pos; - size_t first_entry; -} deletes_by_oid_queue; - -static void deletes_by_oid_free(git_oidmap *map) { - deletes_by_oid_queue *queue; - - if (!map) - return; - - git_oidmap_foreach_value(map, queue, { - git_array_clear(queue->arr); - }); - git_oidmap_free(map); -} - -static int deletes_by_oid_enqueue(git_oidmap *map, git_pool *pool, const git_oid *id, size_t idx) -{ - deletes_by_oid_queue *queue; - size_t *array_entry; - - if ((queue = git_oidmap_get(map, id)) == NULL) { - queue = git_pool_malloc(pool, sizeof(deletes_by_oid_queue)); - GIT_ERROR_CHECK_ALLOC(queue); - - git_array_init(queue->arr); - queue->next_pos = 0; - queue->first_entry = idx; - - if (git_oidmap_set(map, id, queue) < 0) - return -1; - } else { - array_entry = git_array_alloc(queue->arr); - GIT_ERROR_CHECK_ALLOC(array_entry); - *array_entry = idx; - } - - return 0; -} - -static int deletes_by_oid_dequeue(size_t *idx, git_oidmap *map, const git_oid *id) -{ - deletes_by_oid_queue *queue; - size_t *array_entry; - - if ((queue = git_oidmap_get(map, id)) == NULL) - return GIT_ENOTFOUND; - - if (queue->next_pos == 0) { - *idx = queue->first_entry; - } else { - array_entry = git_array_get(queue->arr, queue->next_pos - 1); - if (array_entry == NULL) - return GIT_ENOTFOUND; - - *idx = *array_entry; - } - - queue->next_pos++; - return 0; -} - -static int merge_diff_mark_similarity_exact( - git_merge_diff_list *diff_list, - struct merge_diff_similarity *similarity_ours, - struct merge_diff_similarity *similarity_theirs) -{ - size_t i, j; - git_merge_diff *conflict_src, *conflict_tgt; - git_oidmap *ours_deletes_by_oid = NULL, *theirs_deletes_by_oid = NULL; - int error = 0; - - if (git_oidmap_new(&ours_deletes_by_oid) < 0 || - git_oidmap_new(&theirs_deletes_by_oid) < 0) { - error = -1; - goto done; - } - - /* Build a map of object ids to conflicts */ - git_vector_foreach(&diff_list->conflicts, i, conflict_src) { - /* Items can be the source of a rename iff they have an item in the - * ancestor slot and lack an item in the ours or theirs slot. */ - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry)) - continue; - - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) { - error = deletes_by_oid_enqueue(ours_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); - if (error < 0) - goto done; - } - - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) { - error = deletes_by_oid_enqueue(theirs_deletes_by_oid, &diff_list->pool, &conflict_src->ancestor_entry.id, i); - if (error < 0) - goto done; - } - } - - git_vector_foreach(&diff_list->conflicts, j, conflict_tgt) { - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->ancestor_entry)) - continue; - - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry)) { - if (deletes_by_oid_dequeue(&i, ours_deletes_by_oid, &conflict_tgt->our_entry.id) == 0) { - similarity_ours[i].similarity = 100; - similarity_ours[i].other_idx = j; - - similarity_ours[j].similarity = 100; - similarity_ours[j].other_idx = i; - } - } - - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry)) { - if (deletes_by_oid_dequeue(&i, theirs_deletes_by_oid, &conflict_tgt->their_entry.id) == 0) { - similarity_theirs[i].similarity = 100; - similarity_theirs[i].other_idx = j; - - similarity_theirs[j].similarity = 100; - similarity_theirs[j].other_idx = i; - } - } - } - -done: - deletes_by_oid_free(ours_deletes_by_oid); - deletes_by_oid_free(theirs_deletes_by_oid); - - return error; -} - -static int merge_diff_mark_similarity_inexact( - git_repository *repo, - git_merge_diff_list *diff_list, - struct merge_diff_similarity *similarity_ours, - struct merge_diff_similarity *similarity_theirs, - void **cache, - const git_merge_options *opts) -{ - size_t i, j; - git_merge_diff *conflict_src, *conflict_tgt; - int similarity; - - git_vector_foreach(&diff_list->conflicts, i, conflict_src) { - /* Items can be the source of a rename iff they have an item in the - * ancestor slot and lack an item in the ours or theirs slot. */ - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry) || - (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry) && - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry))) - continue; - - git_vector_foreach(&diff_list->conflicts, j, conflict_tgt) { - size_t our_idx = diff_list->conflicts.length + j; - size_t their_idx = (diff_list->conflicts.length * 2) + j; - - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->ancestor_entry)) - continue; - - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry) && - !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) { - similarity = index_entry_similarity_inexact(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->our_entry, our_idx, cache, opts); - - if (similarity == GIT_EBUFS) - continue; - else if (similarity < 0) - return similarity; - - if (similarity > similarity_ours[i].similarity && - similarity > similarity_ours[j].similarity) { - /* Clear previous best similarity */ - if (similarity_ours[i].similarity > 0) - similarity_ours[similarity_ours[i].other_idx].similarity = 0; - - if (similarity_ours[j].similarity > 0) - similarity_ours[similarity_ours[j].other_idx].similarity = 0; - - similarity_ours[i].similarity = similarity; - similarity_ours[i].other_idx = j; - - similarity_ours[j].similarity = similarity; - similarity_ours[j].other_idx = i; - } - } - - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry) && - !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) { - similarity = index_entry_similarity_inexact(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->their_entry, their_idx, cache, opts); - - if (similarity > similarity_theirs[i].similarity && - similarity > similarity_theirs[j].similarity) { - /* Clear previous best similarity */ - if (similarity_theirs[i].similarity > 0) - similarity_theirs[similarity_theirs[i].other_idx].similarity = 0; - - if (similarity_theirs[j].similarity > 0) - similarity_theirs[similarity_theirs[j].other_idx].similarity = 0; - - similarity_theirs[i].similarity = similarity; - similarity_theirs[i].other_idx = j; - - similarity_theirs[j].similarity = similarity; - similarity_theirs[j].other_idx = i; - } - } - } - } - - return 0; -} - -/* - * Rename conflicts: - * - * Ancestor Ours Theirs - * - * 0a A A A No rename - * b A A* A No rename (ours was rewritten) - * c A A A* No rename (theirs rewritten) - * 1a A A B[A] Rename or rename/edit - * b A B[A] A (automergeable) - * 2 A B[A] B[A] Both renamed (automergeable) - * 3a A B[A] Rename/delete - * b A B[A] (same) - * 4a A B[A] B Rename/add [B~ours B~theirs] - * b A B B[A] (same) - * 5 A B[A] C[A] Both renamed ("1 -> 2") - * 6 A C[A] Both renamed ("2 -> 1") - * B C[B] [C~ours C~theirs] (automergeable) - */ -static void merge_diff_mark_rename_conflict( - git_merge_diff_list *diff_list, - struct merge_diff_similarity *similarity_ours, - bool ours_renamed, - size_t ours_source_idx, - struct merge_diff_similarity *similarity_theirs, - bool theirs_renamed, - size_t theirs_source_idx, - git_merge_diff *target, - const git_merge_options *opts) -{ - git_merge_diff *ours_source = NULL, *theirs_source = NULL; - - if (ours_renamed) - ours_source = diff_list->conflicts.contents[ours_source_idx]; - - if (theirs_renamed) - theirs_source = diff_list->conflicts.contents[theirs_source_idx]; - - /* Detect 2->1 conflicts */ - if (ours_renamed && theirs_renamed) { - /* Both renamed to the same target name. */ - if (ours_source_idx == theirs_source_idx) - ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED; - else { - ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1; - theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1; - } - } else if (ours_renamed) { - /* If our source was also renamed in theirs, this is a 1->2 */ - if (similarity_theirs[ours_source_idx].similarity >= opts->rename_threshold) - ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2; - - else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry)) { - ours_source->type = GIT_MERGE_DIFF_RENAMED_ADDED; - target->type = GIT_MERGE_DIFF_RENAMED_ADDED; - } - - else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(ours_source->their_entry)) - ours_source->type = GIT_MERGE_DIFF_RENAMED_DELETED; - - else if (ours_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED) - ours_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED; - } else if (theirs_renamed) { - /* If their source was also renamed in ours, this is a 1->2 */ - if (similarity_ours[theirs_source_idx].similarity >= opts->rename_threshold) - theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2; - - else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry)) { - theirs_source->type = GIT_MERGE_DIFF_RENAMED_ADDED; - target->type = GIT_MERGE_DIFF_RENAMED_ADDED; - } - - else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(theirs_source->our_entry)) - theirs_source->type = GIT_MERGE_DIFF_RENAMED_DELETED; - - else if (theirs_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED) - theirs_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED; - } -} - -GIT_INLINE(void) merge_diff_coalesce_rename( - git_index_entry *source_entry, - git_delta_t *source_status, - git_index_entry *target_entry, - git_delta_t *target_status) -{ - /* Coalesce the rename target into the rename source. */ - memcpy(source_entry, target_entry, sizeof(git_index_entry)); - *source_status = GIT_DELTA_RENAMED; - - memset(target_entry, 0x0, sizeof(git_index_entry)); - *target_status = GIT_DELTA_UNMODIFIED; -} - -static void merge_diff_list_coalesce_renames( - git_merge_diff_list *diff_list, - struct merge_diff_similarity *similarity_ours, - struct merge_diff_similarity *similarity_theirs, - const git_merge_options *opts) -{ - size_t i; - bool ours_renamed = 0, theirs_renamed = 0; - size_t ours_source_idx = 0, theirs_source_idx = 0; - git_merge_diff *ours_source, *theirs_source, *target; - - for (i = 0; i < diff_list->conflicts.length; i++) { - target = diff_list->conflicts.contents[i]; - - ours_renamed = 0; - theirs_renamed = 0; - - if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry) && - similarity_ours[i].similarity >= opts->rename_threshold) { - ours_source_idx = similarity_ours[i].other_idx; - - ours_source = diff_list->conflicts.contents[ours_source_idx]; - - merge_diff_coalesce_rename( - &ours_source->our_entry, - &ours_source->our_status, - &target->our_entry, - &target->our_status); - - similarity_ours[ours_source_idx].similarity = 0; - similarity_ours[i].similarity = 0; - - ours_renamed = 1; - } - - /* insufficient to determine direction */ - if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry) && - similarity_theirs[i].similarity >= opts->rename_threshold) { - theirs_source_idx = similarity_theirs[i].other_idx; - - theirs_source = diff_list->conflicts.contents[theirs_source_idx]; - - merge_diff_coalesce_rename( - &theirs_source->their_entry, - &theirs_source->their_status, - &target->their_entry, - &target->their_status); - - similarity_theirs[theirs_source_idx].similarity = 0; - similarity_theirs[i].similarity = 0; - - theirs_renamed = 1; - } - - merge_diff_mark_rename_conflict(diff_list, - similarity_ours, ours_renamed, ours_source_idx, - similarity_theirs, theirs_renamed, theirs_source_idx, - target, opts); - } -} - -static int merge_diff_empty(const git_vector *conflicts, size_t idx, void *p) -{ - git_merge_diff *conflict = conflicts->contents[idx]; - - GIT_UNUSED(p); - - return (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) && - !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) && - !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)); -} - -static void merge_diff_list_count_candidates( - git_merge_diff_list *diff_list, - size_t *src_count, - size_t *tgt_count) -{ - git_merge_diff *entry; - size_t i; - - *src_count = 0; - *tgt_count = 0; - - git_vector_foreach(&diff_list->conflicts, i, entry) { - if (GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry) && - (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->our_entry) || - !GIT_MERGE_INDEX_ENTRY_EXISTS(entry->their_entry))) - (*src_count)++; - else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry)) - (*tgt_count)++; - } -} - -int git_merge_diff_list__find_renames( - git_repository *repo, - git_merge_diff_list *diff_list, - const git_merge_options *opts) -{ - struct merge_diff_similarity *similarity_ours, *similarity_theirs; - void **cache = NULL; - size_t cache_size = 0; - size_t src_count, tgt_count, i; - int error = 0; - - GIT_ASSERT_ARG(diff_list); - GIT_ASSERT_ARG(opts); - - if ((opts->flags & GIT_MERGE_FIND_RENAMES) == 0 || - !diff_list->conflicts.length) - return 0; - - similarity_ours = git__calloc(diff_list->conflicts.length, - sizeof(struct merge_diff_similarity)); - GIT_ERROR_CHECK_ALLOC(similarity_ours); - - similarity_theirs = git__calloc(diff_list->conflicts.length, - sizeof(struct merge_diff_similarity)); - GIT_ERROR_CHECK_ALLOC(similarity_theirs); - - /* Calculate similarity between items that were deleted from the ancestor - * and added in the other branch. - */ - if ((error = merge_diff_mark_similarity_exact(diff_list, similarity_ours, similarity_theirs)) < 0) - goto done; - - if (opts->rename_threshold < 100 && diff_list->conflicts.length <= opts->target_limit) { - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&cache_size, diff_list->conflicts.length, 3); - cache = git__calloc(cache_size, sizeof(void *)); - GIT_ERROR_CHECK_ALLOC(cache); - - merge_diff_list_count_candidates(diff_list, &src_count, &tgt_count); - - if (src_count > opts->target_limit || tgt_count > opts->target_limit) { - /* TODO: report! */ - } else { - if ((error = merge_diff_mark_similarity_inexact( - repo, diff_list, similarity_ours, similarity_theirs, cache, opts)) < 0) - goto done; - } - } - - /* For entries that are appropriately similar, merge the new name's entry - * into the old name. - */ - merge_diff_list_coalesce_renames(diff_list, similarity_ours, similarity_theirs, opts); - - /* And remove any entries that were merged and are now empty. */ - git_vector_remove_matching(&diff_list->conflicts, merge_diff_empty, NULL); - -done: - if (cache != NULL) { - for (i = 0; i < cache_size; ++i) { - if (cache[i] != NULL && cache[i] != &cache_invalid_marker) - opts->metric->free_signature(cache[i], opts->metric->payload); - } - - git__free(cache); - } - - git__free(similarity_ours); - git__free(similarity_theirs); - - return error; -} - -/* Directory/file conflict handling */ - -GIT_INLINE(const char *) merge_diff_path( - const git_merge_diff *conflict) -{ - if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry)) - return conflict->ancestor_entry.path; - else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry)) - return conflict->our_entry.path; - else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) - return conflict->their_entry.path; - - return NULL; -} - -GIT_INLINE(bool) merge_diff_any_side_added_or_modified( - const git_merge_diff *conflict) -{ - if (conflict->our_status == GIT_DELTA_ADDED || - conflict->our_status == GIT_DELTA_MODIFIED || - conflict->their_status == GIT_DELTA_ADDED || - conflict->their_status == GIT_DELTA_MODIFIED) - return true; - - return false; -} - -GIT_INLINE(bool) path_is_prefixed(const char *parent, const char *child) -{ - size_t child_len = strlen(child); - size_t parent_len = strlen(parent); - - if (child_len < parent_len || - strncmp(parent, child, parent_len) != 0) - return 0; - - return (child[parent_len] == '/'); -} - -GIT_INLINE(int) merge_diff_detect_df_conflict( - struct merge_diff_df_data *df_data, - git_merge_diff *conflict) -{ - const char *cur_path = merge_diff_path(conflict); - - /* Determine if this is a D/F conflict or the child of one */ - if (df_data->df_path && - path_is_prefixed(df_data->df_path, cur_path)) - conflict->type = GIT_MERGE_DIFF_DF_CHILD; - else if(df_data->df_path) - df_data->df_path = NULL; - else if (df_data->prev_path && - merge_diff_any_side_added_or_modified(df_data->prev_conflict) && - merge_diff_any_side_added_or_modified(conflict) && - path_is_prefixed(df_data->prev_path, cur_path)) { - conflict->type = GIT_MERGE_DIFF_DF_CHILD; - - df_data->prev_conflict->type = GIT_MERGE_DIFF_DIRECTORY_FILE; - df_data->df_path = df_data->prev_path; - } - - df_data->prev_path = cur_path; - df_data->prev_conflict = conflict; - - return 0; -} - -/* Conflict handling */ - -GIT_INLINE(int) merge_diff_detect_type( - git_merge_diff *conflict) -{ - if (conflict->our_status == GIT_DELTA_ADDED && - conflict->their_status == GIT_DELTA_ADDED) - conflict->type = GIT_MERGE_DIFF_BOTH_ADDED; - else if (conflict->our_status == GIT_DELTA_MODIFIED && - conflict->their_status == GIT_DELTA_MODIFIED) - conflict->type = GIT_MERGE_DIFF_BOTH_MODIFIED; - else if (conflict->our_status == GIT_DELTA_DELETED && - conflict->their_status == GIT_DELTA_DELETED) - conflict->type = GIT_MERGE_DIFF_BOTH_DELETED; - else if (conflict->our_status == GIT_DELTA_MODIFIED && - conflict->their_status == GIT_DELTA_DELETED) - conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED; - else if (conflict->our_status == GIT_DELTA_DELETED && - conflict->their_status == GIT_DELTA_MODIFIED) - conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED; - else - conflict->type = GIT_MERGE_DIFF_NONE; - - return 0; -} - -GIT_INLINE(int) index_entry_dup_pool( - git_index_entry *out, - git_pool *pool, - const git_index_entry *src) -{ - if (src != NULL) { - memcpy(out, src, sizeof(git_index_entry)); - if ((out->path = git_pool_strdup(pool, src->path)) == NULL) - return -1; - } - - return 0; -} - -GIT_INLINE(int) merge_delta_type_from_index_entries( - const git_index_entry *ancestor, - const git_index_entry *other) -{ - if (ancestor == NULL && other == NULL) - return GIT_DELTA_UNMODIFIED; - else if (ancestor == NULL && other != NULL) - return GIT_DELTA_ADDED; - else if (ancestor != NULL && other == NULL) - return GIT_DELTA_DELETED; - else if (S_ISDIR(ancestor->mode) ^ S_ISDIR(other->mode)) - return GIT_DELTA_TYPECHANGE; - else if(S_ISLNK(ancestor->mode) ^ S_ISLNK(other->mode)) - return GIT_DELTA_TYPECHANGE; - else if (git_oid__cmp(&ancestor->id, &other->id) || - ancestor->mode != other->mode) - return GIT_DELTA_MODIFIED; - - return GIT_DELTA_UNMODIFIED; -} - -static git_merge_diff *merge_diff_from_index_entries( - git_merge_diff_list *diff_list, - const git_index_entry **entries) -{ - git_merge_diff *conflict; - git_pool *pool = &diff_list->pool; - - if ((conflict = git_pool_mallocz(pool, sizeof(git_merge_diff))) == NULL) - return NULL; - - if (index_entry_dup_pool(&conflict->ancestor_entry, pool, entries[TREE_IDX_ANCESTOR]) < 0 || - index_entry_dup_pool(&conflict->our_entry, pool, entries[TREE_IDX_OURS]) < 0 || - index_entry_dup_pool(&conflict->their_entry, pool, entries[TREE_IDX_THEIRS]) < 0) - return NULL; - - conflict->our_status = merge_delta_type_from_index_entries( - entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_OURS]); - conflict->their_status = merge_delta_type_from_index_entries( - entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_THEIRS]); - - return conflict; -} - -/* Merge trees */ - -static int merge_diff_list_insert_conflict( - git_merge_diff_list *diff_list, - struct merge_diff_df_data *merge_df_data, - const git_index_entry *tree_items[3]) -{ - git_merge_diff *conflict; - - if ((conflict = merge_diff_from_index_entries(diff_list, tree_items)) == NULL || - merge_diff_detect_type(conflict) < 0 || - merge_diff_detect_df_conflict(merge_df_data, conflict) < 0 || - git_vector_insert(&diff_list->conflicts, conflict) < 0) - return -1; - - return 0; -} - -static int merge_diff_list_insert_unmodified( - git_merge_diff_list *diff_list, - const git_index_entry *tree_items[3]) -{ - int error = 0; - git_index_entry *entry; - - entry = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry)); - GIT_ERROR_CHECK_ALLOC(entry); - - if ((error = index_entry_dup_pool(entry, &diff_list->pool, tree_items[0])) >= 0) - error = git_vector_insert(&diff_list->staged, entry); - - return error; -} - -struct merge_diff_find_data { - git_merge_diff_list *diff_list; - struct merge_diff_df_data df_data; -}; - -static int queue_difference(const git_index_entry **entries, void *data) -{ - struct merge_diff_find_data *find_data = data; - bool item_modified = false; - size_t i; - - if (!entries[0] || !entries[1] || !entries[2]) { - item_modified = true; - } else { - for (i = 1; i < 3; i++) { - if (index_entry_cmp(entries[0], entries[i]) != 0) { - item_modified = true; - break; - } - } - } - - return item_modified ? - merge_diff_list_insert_conflict( - find_data->diff_list, &find_data->df_data, entries) : - merge_diff_list_insert_unmodified(find_data->diff_list, entries); -} - -int git_merge_diff_list__find_differences( - git_merge_diff_list *diff_list, - git_iterator *ancestor_iter, - git_iterator *our_iter, - git_iterator *their_iter) -{ - git_iterator *iterators[3] = { ancestor_iter, our_iter, their_iter }; - struct merge_diff_find_data find_data = { diff_list }; - - return git_iterator_walk(iterators, 3, queue_difference, &find_data); -} - -git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo) -{ - git_merge_diff_list *diff_list = git__calloc(1, sizeof(git_merge_diff_list)); - - if (diff_list == NULL) - return NULL; - - diff_list->repo = repo; - - - if (git_pool_init(&diff_list->pool, 1) < 0 || - git_vector_init(&diff_list->staged, 0, NULL) < 0 || - git_vector_init(&diff_list->conflicts, 0, NULL) < 0 || - git_vector_init(&diff_list->resolved, 0, NULL) < 0) { - git_merge_diff_list__free(diff_list); - return NULL; - } - - return diff_list; -} - -void git_merge_diff_list__free(git_merge_diff_list *diff_list) -{ - if (!diff_list) - return; - - git_vector_free(&diff_list->staged); - git_vector_free(&diff_list->conflicts); - git_vector_free(&diff_list->resolved); - git_pool_clear(&diff_list->pool); - git__free(diff_list); -} - -static int merge_normalize_opts( - git_repository *repo, - git_merge_options *opts, - const git_merge_options *given) -{ - git_config *cfg = NULL; - git_config_entry *entry = NULL; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(opts); - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - return error; - - if (given != NULL) { - memcpy(opts, given, sizeof(git_merge_options)); - } else { - git_merge_options init = GIT_MERGE_OPTIONS_INIT; - memcpy(opts, &init, sizeof(init)); - } - - if ((opts->flags & GIT_MERGE_FIND_RENAMES) && !opts->rename_threshold) - opts->rename_threshold = GIT_MERGE_DEFAULT_RENAME_THRESHOLD; - - if (given && given->default_driver) { - opts->default_driver = git__strdup(given->default_driver); - GIT_ERROR_CHECK_ALLOC(opts->default_driver); - } else { - error = git_config_get_entry(&entry, cfg, "merge.default"); - - if (error == 0) { - opts->default_driver = git__strdup(entry->value); - GIT_ERROR_CHECK_ALLOC(opts->default_driver); - } else if (error == GIT_ENOTFOUND) { - error = 0; - } else { - goto done; - } - } - - if (!opts->target_limit) { - int limit = git_config__get_int_force(cfg, "merge.renamelimit", 0); - - if (!limit) - limit = git_config__get_int_force(cfg, "diff.renamelimit", 0); - - opts->target_limit = (limit <= 0) ? - GIT_MERGE_DEFAULT_TARGET_LIMIT : (unsigned int)limit; - } - - /* assign the internal metric with whitespace flag as payload */ - if (!opts->metric) { - opts->metric = git__malloc(sizeof(git_diff_similarity_metric)); - GIT_ERROR_CHECK_ALLOC(opts->metric); - - opts->metric->file_signature = git_diff_find_similar__hashsig_for_file; - opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf; - opts->metric->free_signature = git_diff_find_similar__hashsig_free; - opts->metric->similarity = git_diff_find_similar__calc_similarity; - opts->metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE; - } - -done: - git_config_entry_free(entry); - return error; -} - - -static int merge_index_insert_reuc( - git_index *index, - size_t idx, - const git_index_entry *entry) -{ - const git_index_reuc_entry *reuc; - int mode[3] = { 0, 0, 0 }; - git_oid const *oid[3] = { NULL, NULL, NULL }; - size_t i; - - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(*entry)) - return 0; - - if ((reuc = git_index_reuc_get_bypath(index, entry->path)) != NULL) { - for (i = 0; i < 3; i++) { - mode[i] = reuc->mode[i]; - oid[i] = &reuc->oid[i]; - } - } - - mode[idx] = entry->mode; - oid[idx] = &entry->id; - - return git_index_reuc_add(index, entry->path, - mode[0], oid[0], mode[1], oid[1], mode[2], oid[2]); -} - -static int index_update_reuc(git_index *index, git_merge_diff_list *diff_list) -{ - int error; - size_t i; - git_merge_diff *conflict; - - /* Add each entry in the resolved conflict to the REUC independently, since - * the paths may differ due to renames. */ - git_vector_foreach(&diff_list->resolved, i, conflict) { - const git_index_entry *ancestor = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? - &conflict->ancestor_entry : NULL; - - const git_index_entry *ours = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? - &conflict->our_entry : NULL; - - const git_index_entry *theirs = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? - &conflict->their_entry : NULL; - - if (ancestor != NULL && - (error = merge_index_insert_reuc(index, TREE_IDX_ANCESTOR, ancestor)) < 0) - return error; - - if (ours != NULL && - (error = merge_index_insert_reuc(index, TREE_IDX_OURS, ours)) < 0) - return error; - - if (theirs != NULL && - (error = merge_index_insert_reuc(index, TREE_IDX_THEIRS, theirs)) < 0) - return error; - } - - return 0; -} - -static int index_from_diff_list(git_index **out, - git_merge_diff_list *diff_list, bool skip_reuc) -{ - git_index *index; - size_t i; - git_merge_diff *conflict; - int error = 0; - - *out = NULL; - - if ((error = git_index_new(&index)) < 0) - return error; - - if ((error = git_index__fill(index, &diff_list->staged)) < 0) - goto on_error; - - git_vector_foreach(&diff_list->conflicts, i, conflict) { - const git_index_entry *ancestor = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? - &conflict->ancestor_entry : NULL; - - const git_index_entry *ours = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? - &conflict->our_entry : NULL; - - const git_index_entry *theirs = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? - &conflict->their_entry : NULL; - - if ((error = git_index_conflict_add(index, ancestor, ours, theirs)) < 0) - goto on_error; - } - - /* Add each rename entry to the rename portion of the index. */ - git_vector_foreach(&diff_list->conflicts, i, conflict) { - const char *ancestor_path, *our_path, *their_path; - - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry)) - continue; - - ancestor_path = conflict->ancestor_entry.path; - - our_path = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? - conflict->our_entry.path : NULL; - - their_path = - GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? - conflict->their_entry.path : NULL; - - if ((our_path && strcmp(ancestor_path, our_path) != 0) || - (their_path && strcmp(ancestor_path, their_path) != 0)) { - if ((error = git_index_name_add(index, ancestor_path, our_path, their_path)) < 0) - goto on_error; - } - } - - if (!skip_reuc) { - if ((error = index_update_reuc(index, diff_list)) < 0) - goto on_error; - } - - *out = index; - return 0; - -on_error: - git_index_free(index); - return error; -} - -static git_iterator *iterator_given_or_empty(git_iterator **empty, git_iterator *given) -{ - git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; - - if (given) - return given; - - opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - if (git_iterator_for_nothing(empty, &opts) < 0) - return NULL; - - return *empty; -} - -int git_merge__iterators( - git_index **out, - git_repository *repo, - git_iterator *ancestor_iter, - git_iterator *our_iter, - git_iterator *theirs_iter, - const git_merge_options *given_opts) -{ - git_iterator *empty_ancestor = NULL, - *empty_ours = NULL, - *empty_theirs = NULL; - git_merge_diff_list *diff_list; - git_merge_options opts; - git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; - git_merge_diff *conflict; - git_vector changes; - size_t i; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - GIT_ERROR_CHECK_VERSION( - given_opts, GIT_MERGE_OPTIONS_VERSION, "git_merge_options"); - - if ((error = merge_normalize_opts(repo, &opts, given_opts)) < 0) - return error; - - file_opts.favor = opts.file_favor; - file_opts.flags = opts.file_flags; - - /* use the git-inspired labels when virtual base building */ - if (opts.flags & GIT_MERGE_VIRTUAL_BASE) { - file_opts.ancestor_label = "merged common ancestors"; - file_opts.our_label = "Temporary merge branch 1"; - file_opts.their_label = "Temporary merge branch 2"; - file_opts.flags |= GIT_MERGE_FILE_ACCEPT_CONFLICTS; - file_opts.marker_size = GIT_MERGE_CONFLICT_MARKER_SIZE + 2; - } - - diff_list = git_merge_diff_list__alloc(repo); - GIT_ERROR_CHECK_ALLOC(diff_list); - - ancestor_iter = iterator_given_or_empty(&empty_ancestor, ancestor_iter); - our_iter = iterator_given_or_empty(&empty_ours, our_iter); - theirs_iter = iterator_given_or_empty(&empty_theirs, theirs_iter); - - if ((error = git_merge_diff_list__find_differences( - diff_list, ancestor_iter, our_iter, theirs_iter)) < 0 || - (error = git_merge_diff_list__find_renames(repo, diff_list, &opts)) < 0) - goto done; - - memcpy(&changes, &diff_list->conflicts, sizeof(git_vector)); - git_vector_clear(&diff_list->conflicts); - - git_vector_foreach(&changes, i, conflict) { - int resolved = 0; - - if ((error = merge_conflict_resolve( - &resolved, diff_list, conflict, &opts, &file_opts)) < 0) - goto done; - - if (!resolved) { - if ((opts.flags & GIT_MERGE_FAIL_ON_CONFLICT)) { - git_error_set(GIT_ERROR_MERGE, "merge conflicts exist"); - error = GIT_EMERGECONFLICT; - goto done; - } - - git_vector_insert(&diff_list->conflicts, conflict); - } - } - - error = index_from_diff_list(out, diff_list, - (opts.flags & GIT_MERGE_SKIP_REUC)); - -done: - if (!given_opts || !given_opts->metric) - git__free(opts.metric); - - git__free((char *)opts.default_driver); - - git_merge_diff_list__free(diff_list); - git_iterator_free(empty_ancestor); - git_iterator_free(empty_ours); - git_iterator_free(empty_theirs); - - return error; -} - -int git_merge_trees( - git_index **out, - git_repository *repo, - const git_tree *ancestor_tree, - const git_tree *our_tree, - const git_tree *their_tree, - const git_merge_options *merge_opts) -{ - git_iterator *ancestor_iter = NULL, *our_iter = NULL, *their_iter = NULL; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - /* if one side is treesame to the ancestor, take the other side */ - if (ancestor_tree && merge_opts && (merge_opts->flags & GIT_MERGE_SKIP_REUC)) { - const git_tree *result = NULL; - const git_oid *ancestor_tree_id = git_tree_id(ancestor_tree); - - if (our_tree && !git_oid_cmp(ancestor_tree_id, git_tree_id(our_tree))) - result = their_tree; - else if (their_tree && !git_oid_cmp(ancestor_tree_id, git_tree_id(their_tree))) - result = our_tree; - - if (result) { - if ((error = git_index_new(out)) == 0) - error = git_index_read_tree(*out, result); - - return error; - } - } - - iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - if ((error = git_iterator_for_tree( - &ancestor_iter, (git_tree *)ancestor_tree, &iter_opts)) < 0 || - (error = git_iterator_for_tree( - &our_iter, (git_tree *)our_tree, &iter_opts)) < 0 || - (error = git_iterator_for_tree( - &their_iter, (git_tree *)their_tree, &iter_opts)) < 0) - goto done; - - error = git_merge__iterators( - out, repo, ancestor_iter, our_iter, their_iter, merge_opts); - -done: - git_iterator_free(ancestor_iter); - git_iterator_free(our_iter); - git_iterator_free(their_iter); - - return error; -} - -static int merge_annotated_commits( - git_index **index_out, - git_annotated_commit **base_out, - git_repository *repo, - git_annotated_commit *our_commit, - git_annotated_commit *their_commit, - size_t recursion_level, - const git_merge_options *opts); - -GIT_INLINE(int) insert_head_ids( - git_array_oid_t *ids, - const git_annotated_commit *annotated_commit) -{ - git_oid *id; - size_t i; - - if (annotated_commit->type == GIT_ANNOTATED_COMMIT_REAL) { - id = git_array_alloc(*ids); - GIT_ERROR_CHECK_ALLOC(id); - - git_oid_cpy(id, git_commit_id(annotated_commit->commit)); - } else { - for (i = 0; i < annotated_commit->parents.size; i++) { - id = git_array_alloc(*ids); - GIT_ERROR_CHECK_ALLOC(id); - - git_oid_cpy(id, &annotated_commit->parents.ptr[i]); - } - } - - return 0; -} - -static int create_virtual_base( - git_annotated_commit **out, - git_repository *repo, - git_annotated_commit *one, - git_annotated_commit *two, - const git_merge_options *opts, - size_t recursion_level) -{ - git_annotated_commit *result = NULL; - git_index *index = NULL; - git_merge_options virtual_opts = GIT_MERGE_OPTIONS_INIT; - - /* Conflicts in the merge base creation do not propagate to conflicts - * in the result; the conflicted base will act as the common ancestor. - */ - if (opts) - memcpy(&virtual_opts, opts, sizeof(git_merge_options)); - - virtual_opts.flags &= ~GIT_MERGE_FAIL_ON_CONFLICT; - virtual_opts.flags |= GIT_MERGE_VIRTUAL_BASE; - - if ((merge_annotated_commits(&index, NULL, repo, one, two, - recursion_level + 1, &virtual_opts)) < 0) - return -1; - - result = git__calloc(1, sizeof(git_annotated_commit)); - GIT_ERROR_CHECK_ALLOC(result); - result->type = GIT_ANNOTATED_COMMIT_VIRTUAL; - result->index = index; - - if (insert_head_ids(&result->parents, one) < 0 || - insert_head_ids(&result->parents, two) < 0) { - git_annotated_commit_free(result); - return -1; - } - - *out = result; - return 0; -} - -static int compute_base( - git_annotated_commit **out, - git_repository *repo, - const git_annotated_commit *one, - const git_annotated_commit *two, - const git_merge_options *given_opts, - size_t recursion_level) -{ - git_array_oid_t head_ids = GIT_ARRAY_INIT; - git_oidarray bases = {0}; - git_annotated_commit *base = NULL, *other = NULL, *new_base = NULL; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - size_t i, base_count; - int error; - - *out = NULL; - - if (given_opts) - memcpy(&opts, given_opts, sizeof(git_merge_options)); - - /* With more than two commits, merge_bases_many finds the base of - * the first commit and a hypothetical merge of the others. Since - * "one" may itself be a virtual commit, which insert_head_ids - * substitutes multiple ancestors for, it needs to be added - * after "two" which is always a single real commit. - */ - if ((error = insert_head_ids(&head_ids, two)) < 0 || - (error = insert_head_ids(&head_ids, one)) < 0 || - (error = git_merge_bases_many(&bases, repo, - head_ids.size, head_ids.ptr)) < 0) - goto done; - - base_count = (opts.flags & GIT_MERGE_NO_RECURSIVE) ? 0 : bases.count; - - if (base_count) - git_oidarray__reverse(&bases); - - if ((error = git_annotated_commit_lookup(&base, repo, &bases.ids[0])) < 0) - goto done; - - for (i = 1; i < base_count; i++) { - recursion_level++; - - if (opts.recursion_limit && recursion_level > opts.recursion_limit) - break; - - if ((error = git_annotated_commit_lookup(&other, repo, - &bases.ids[i])) < 0 || - (error = create_virtual_base(&new_base, repo, base, other, &opts, - recursion_level)) < 0) - goto done; - - git_annotated_commit_free(base); - git_annotated_commit_free(other); - - base = new_base; - new_base = NULL; - other = NULL; - } - -done: - if (error == 0) - *out = base; - else - git_annotated_commit_free(base); - - git_annotated_commit_free(other); - git_annotated_commit_free(new_base); - git_oidarray_dispose(&bases); - git_array_clear(head_ids); - return error; -} - -static int iterator_for_annotated_commit( - git_iterator **out, - git_annotated_commit *commit) -{ - git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; - int error; - - opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - if (commit == NULL) { - error = git_iterator_for_nothing(out, &opts); - } else if (commit->type == GIT_ANNOTATED_COMMIT_VIRTUAL) { - error = git_iterator_for_index(out, git_index_owner(commit->index), commit->index, &opts); - } else { - if (!commit->tree && - (error = git_commit_tree(&commit->tree, commit->commit)) < 0) - goto done; - - error = git_iterator_for_tree(out, commit->tree, &opts); - } - -done: - return error; -} - -static int merge_annotated_commits( - git_index **index_out, - git_annotated_commit **base_out, - git_repository *repo, - git_annotated_commit *ours, - git_annotated_commit *theirs, - size_t recursion_level, - const git_merge_options *opts) -{ - git_annotated_commit *base = NULL; - git_iterator *base_iter = NULL, *our_iter = NULL, *their_iter = NULL; - int error; - - if ((error = compute_base(&base, repo, ours, theirs, opts, - recursion_level)) < 0) { - - if (error != GIT_ENOTFOUND) - goto done; - - git_error_clear(); - } - - if ((error = iterator_for_annotated_commit(&base_iter, base)) < 0 || - (error = iterator_for_annotated_commit(&our_iter, ours)) < 0 || - (error = iterator_for_annotated_commit(&their_iter, theirs)) < 0 || - (error = git_merge__iterators(index_out, repo, base_iter, our_iter, - their_iter, opts)) < 0) - goto done; - - if (base_out) { - *base_out = base; - base = NULL; - } - -done: - git_annotated_commit_free(base); - git_iterator_free(base_iter); - git_iterator_free(our_iter); - git_iterator_free(their_iter); - return error; -} - - -int git_merge_commits( - git_index **out, - git_repository *repo, - const git_commit *our_commit, - const git_commit *their_commit, - const git_merge_options *opts) -{ - git_annotated_commit *ours = NULL, *theirs = NULL, *base = NULL; - int error = 0; - - if ((error = git_annotated_commit_from_commit(&ours, (git_commit *)our_commit)) < 0 || - (error = git_annotated_commit_from_commit(&theirs, (git_commit *)their_commit)) < 0) - goto done; - - error = merge_annotated_commits(out, &base, repo, ours, theirs, 0, opts); - -done: - git_annotated_commit_free(ours); - git_annotated_commit_free(theirs); - git_annotated_commit_free(base); - return error; -} - -/* Merge setup / cleanup */ - -static int write_merge_head( - git_repository *repo, - const git_annotated_commit *heads[], - size_t heads_len) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - size_t i; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(heads); - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_HEAD_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) < 0) - goto cleanup; - - for (i = 0; i < heads_len; i++) { - if ((error = git_filebuf_printf(&file, "%s\n", heads[i]->id_str)) < 0) - goto cleanup; - } - - error = git_filebuf_commit(&file); - -cleanup: - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int write_merge_mode(git_repository *repo) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - int error = 0; - - GIT_ASSERT_ARG(repo); - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MODE_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) < 0) - goto cleanup; - - if ((error = git_filebuf_write(&file, "no-ff", 5)) < 0) - goto cleanup; - - error = git_filebuf_commit(&file); - -cleanup: - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -struct merge_msg_entry { - const git_annotated_commit *merge_head; - bool written; -}; - -static int msg_entry_is_branch( - const struct merge_msg_entry *entry, - git_vector *entries) -{ - GIT_UNUSED(entries); - - return (entry->written == 0 && - entry->merge_head->remote_url == NULL && - entry->merge_head->ref_name != NULL && - git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0); -} - -static int msg_entry_is_tracking( - const struct merge_msg_entry *entry, - git_vector *entries) -{ - GIT_UNUSED(entries); - - return (entry->written == 0 && - entry->merge_head->remote_url == NULL && - entry->merge_head->ref_name != NULL && - git__strncmp(GIT_REFS_REMOTES_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_REMOTES_DIR)) == 0); -} - -static int msg_entry_is_tag( - const struct merge_msg_entry *entry, - git_vector *entries) -{ - GIT_UNUSED(entries); - - return (entry->written == 0 && - entry->merge_head->remote_url == NULL && - entry->merge_head->ref_name != NULL && - git__strncmp(GIT_REFS_TAGS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_TAGS_DIR)) == 0); -} - -static int msg_entry_is_remote( - const struct merge_msg_entry *entry, - git_vector *entries) -{ - if (entry->written == 0 && - entry->merge_head->remote_url != NULL && - entry->merge_head->ref_name != NULL && - git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0) - { - struct merge_msg_entry *existing; - - /* Match only branches from the same remote */ - if (entries->length == 0) - return 1; - - existing = git_vector_get(entries, 0); - - return (git__strcmp(existing->merge_head->remote_url, - entry->merge_head->remote_url) == 0); - } - - return 0; -} - -static int msg_entry_is_oid( - const struct merge_msg_entry *merge_msg_entry) -{ - return (merge_msg_entry->written == 0 && - merge_msg_entry->merge_head->ref_name == NULL && - merge_msg_entry->merge_head->remote_url == NULL); -} - -static int merge_msg_entry_written( - const struct merge_msg_entry *merge_msg_entry) -{ - return (merge_msg_entry->written == 1); -} - -static int merge_msg_entries( - git_vector *v, - const struct merge_msg_entry *entries, - size_t len, - int (*match)(const struct merge_msg_entry *entry, git_vector *entries)) -{ - size_t i; - int matches, total = 0; - - git_vector_clear(v); - - for (i = 0; i < len; i++) { - if ((matches = match(&entries[i], v)) < 0) - return matches; - else if (!matches) - continue; - - git_vector_insert(v, (struct merge_msg_entry *)&entries[i]); - total++; - } - - return total; -} - -static int merge_msg_write_entries( - git_filebuf *file, - git_vector *entries, - const char *item_name, - const char *item_plural_name, - size_t ref_name_skip, - const char *source, - char sep) -{ - struct merge_msg_entry *entry; - size_t i; - int error = 0; - - if (entries->length == 0) - return 0; - - if (sep && (error = git_filebuf_printf(file, "%c ", sep)) < 0) - goto done; - - if ((error = git_filebuf_printf(file, "%s ", - (entries->length == 1) ? item_name : item_plural_name)) < 0) - goto done; - - git_vector_foreach(entries, i, entry) { - if (i > 0 && - (error = git_filebuf_printf(file, "%s", (i == entries->length - 1) ? " and " : ", ")) < 0) - goto done; - - if ((error = git_filebuf_printf(file, "'%s'", entry->merge_head->ref_name + ref_name_skip)) < 0) - goto done; - - entry->written = 1; - } - - if (source) - error = git_filebuf_printf(file, " of %s", source); - -done: - return error; -} - -static int merge_msg_write_branches( - git_filebuf *file, - git_vector *entries, - char sep) -{ - return merge_msg_write_entries(file, entries, - "branch", "branches", strlen(GIT_REFS_HEADS_DIR), NULL, sep); -} - -static int merge_msg_write_tracking( - git_filebuf *file, - git_vector *entries, - char sep) -{ - return merge_msg_write_entries(file, entries, - "remote-tracking branch", "remote-tracking branches", 0, NULL, sep); -} - -static int merge_msg_write_tags( - git_filebuf *file, - git_vector *entries, - char sep) -{ - return merge_msg_write_entries(file, entries, - "tag", "tags", strlen(GIT_REFS_TAGS_DIR), NULL, sep); -} - -static int merge_msg_write_remotes( - git_filebuf *file, - git_vector *entries, - char sep) -{ - const char *source; - - if (entries->length == 0) - return 0; - - source = ((struct merge_msg_entry *)entries->contents[0])->merge_head->remote_url; - - return merge_msg_write_entries(file, entries, - "branch", "branches", strlen(GIT_REFS_HEADS_DIR), source, sep); -} - -static int write_merge_msg( - git_repository *repo, - const git_annotated_commit *heads[], - size_t heads_len) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - struct merge_msg_entry *entries; - git_vector matching = GIT_VECTOR_INIT; - size_t i; - char sep = 0; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(heads); - - entries = git__calloc(heads_len, sizeof(struct merge_msg_entry)); - GIT_ERROR_CHECK_ALLOC(entries); - - if (git_vector_init(&matching, heads_len, NULL) < 0) { - git__free(entries); - return -1; - } - - for (i = 0; i < heads_len; i++) - entries[i].merge_head = heads[i]; - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) < 0 || - (error = git_filebuf_write(&file, "Merge ", 6)) < 0) - goto cleanup; - - /* - * This is to emulate the format of MERGE_MSG by core git. - * - * Core git will write all the commits specified by OID, in the order - * provided, until the first named branch or tag is reached, at which - * point all branches will be written in the order provided, then all - * tags, then all remote tracking branches and finally all commits that - * were specified by OID that were not already written. - * - * Yes. Really. - */ - for (i = 0; i < heads_len; i++) { - if (!msg_entry_is_oid(&entries[i])) - break; - - if ((error = git_filebuf_printf(&file, - "%scommit '%s'", (i > 0) ? "; " : "", - entries[i].merge_head->id_str)) < 0) - goto cleanup; - - entries[i].written = 1; - } - - if (i) - sep = ';'; - - if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_branch)) < 0 || - (error = merge_msg_write_branches(&file, &matching, sep)) < 0) - goto cleanup; - - if (matching.length) - sep =','; - - if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tracking)) < 0 || - (error = merge_msg_write_tracking(&file, &matching, sep)) < 0) - goto cleanup; - - if (matching.length) - sep =','; - - if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tag)) < 0 || - (error = merge_msg_write_tags(&file, &matching, sep)) < 0) - goto cleanup; - - if (matching.length) - sep =','; - - /* We should never be called with multiple remote branches, but handle - * it in case we are... */ - while ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_remote)) > 0) { - if ((error = merge_msg_write_remotes(&file, &matching, sep)) < 0) - goto cleanup; - - if (matching.length) - sep =','; - } - - if (error < 0) - goto cleanup; - - for (i = 0; i < heads_len; i++) { - if (merge_msg_entry_written(&entries[i])) - continue; - - if ((error = git_filebuf_printf(&file, "; commit '%s'", - entries[i].merge_head->id_str)) < 0) - goto cleanup; - } - - if ((error = git_filebuf_printf(&file, "\n")) < 0 || - (error = git_filebuf_commit(&file)) < 0) - goto cleanup; - -cleanup: - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - git_vector_free(&matching); - git__free(entries); - - return error; -} - -int git_merge__setup( - git_repository *repo, - const git_annotated_commit *our_head, - const git_annotated_commit *heads[], - size_t heads_len) -{ - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(our_head); - GIT_ASSERT_ARG(heads); - - if ((error = git_repository__set_orig_head(repo, git_annotated_commit_id(our_head))) == 0 && - (error = write_merge_head(repo, heads, heads_len)) == 0 && - (error = write_merge_mode(repo)) == 0) { - error = write_merge_msg(repo, heads, heads_len); - } - - return error; -} - -/* Merge branches */ - -static int merge_ancestor_head( - git_annotated_commit **ancestor_head, - git_repository *repo, - const git_annotated_commit *our_head, - const git_annotated_commit **their_heads, - size_t their_heads_len) -{ - git_oid *oids, ancestor_oid; - size_t i, alloc_len; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(our_head); - GIT_ASSERT_ARG(their_heads); - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, their_heads_len, 1); - oids = git__calloc(alloc_len, sizeof(git_oid)); - GIT_ERROR_CHECK_ALLOC(oids); - - git_oid_cpy(&oids[0], git_commit_id(our_head->commit)); - - for (i = 0; i < their_heads_len; i++) - git_oid_cpy(&oids[i + 1], git_annotated_commit_id(their_heads[i])); - - if ((error = git_merge_base_many(&ancestor_oid, repo, their_heads_len + 1, oids)) < 0) - goto on_error; - - error = git_annotated_commit_lookup(ancestor_head, repo, &ancestor_oid); - -on_error: - git__free(oids); - return error; -} - -static const char *merge_their_label(const char *branchname) -{ - const char *slash; - - if ((slash = strrchr(branchname, '/')) == NULL) - return branchname; - - if (*(slash+1) == '\0') - return "theirs"; - - return slash+1; -} - -static int merge_normalize_checkout_opts( - git_checkout_options *out, - git_repository *repo, - const git_checkout_options *given_checkout_opts, - unsigned int checkout_strategy, - git_annotated_commit *ancestor, - const git_annotated_commit *our_head, - const git_annotated_commit **their_heads, - size_t their_heads_len) -{ - git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - int error = 0; - - GIT_UNUSED(repo); - - if (given_checkout_opts != NULL) - memcpy(out, given_checkout_opts, sizeof(git_checkout_options)); - else - memcpy(out, &default_checkout_opts, sizeof(git_checkout_options)); - - out->checkout_strategy = checkout_strategy; - - if (!out->ancestor_label) { - if (ancestor && ancestor->type == GIT_ANNOTATED_COMMIT_REAL) - out->ancestor_label = git_commit_summary(ancestor->commit); - else if (ancestor) - out->ancestor_label = "merged common ancestors"; - else - out->ancestor_label = "empty base"; - } - - if (!out->our_label) { - if (our_head && our_head->ref_name) - out->our_label = our_head->ref_name; - else - out->our_label = "ours"; - } - - if (!out->their_label) { - if (their_heads_len == 1 && their_heads[0]->ref_name) - out->their_label = merge_their_label(their_heads[0]->ref_name); - else if (their_heads_len == 1) - out->their_label = their_heads[0]->id_str; - else - out->their_label = "theirs"; - } - - return error; -} - -static int merge_check_index(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) -{ - git_tree *head_tree = NULL; - git_index *index_repo = NULL; - git_iterator *iter_repo = NULL, *iter_new = NULL; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - git_diff *staged_diff_list = NULL, *index_diff_list = NULL; - git_diff_delta *delta; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_vector staged_paths = GIT_VECTOR_INIT; - size_t i; - int error = 0; - - GIT_UNUSED(merged_paths); - - *conflicts = 0; - - /* No staged changes may exist unless the change staged is identical to - * the result of the merge. This allows one to apply to merge manually, - * then run merge. Any other staged change would be overwritten by - * a reset merge. - */ - if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || - (error = git_repository_index(&index_repo, repo)) < 0 || - (error = git_diff_tree_to_index(&staged_diff_list, repo, head_tree, index_repo, &opts)) < 0) - goto done; - - if (staged_diff_list->deltas.length == 0) - goto done; - - git_vector_foreach(&staged_diff_list->deltas, i, delta) { - if ((error = git_vector_insert(&staged_paths, (char *)delta->new_file.path)) < 0) - goto done; - } - - iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - iter_opts.pathlist.strings = (char **)staged_paths.contents; - iter_opts.pathlist.count = staged_paths.length; - - if ((error = git_iterator_for_index(&iter_repo, repo, index_repo, &iter_opts)) < 0 || - (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 || - (error = git_diff__from_iterators(&index_diff_list, repo, iter_repo, iter_new, &opts)) < 0) - goto done; - - *conflicts = index_diff_list->deltas.length; - -done: - git_tree_free(head_tree); - git_index_free(index_repo); - git_iterator_free(iter_repo); - git_iterator_free(iter_new); - git_diff_free(staged_diff_list); - git_diff_free(index_diff_list); - git_vector_free(&staged_paths); - - return error; -} - -static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) -{ - git_diff *wd_diff_list = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - int error = 0; - - GIT_UNUSED(index_new); - - *conflicts = 0; - - /* We need to have merged at least 1 file for the possibility to exist to - * have conflicts with the workdir. Passing 0 as the pathspec count parameter - * will consider all files in the working directory, that is, we may detect - * a conflict if there were untracked files in the workdir prior to starting - * the merge. This typically happens when cherry-picking a commit whose - * changes have already been applied. - */ - if (merged_paths->length == 0) - return 0; - - opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - - /* Workdir changes may exist iff they do not conflict with changes that - * will be applied by the merge (including conflicts). Ensure that there - * are no changes in the workdir to these paths. - */ - opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; - opts.pathspec.count = merged_paths->length; - opts.pathspec.strings = (char **)merged_paths->contents; - opts.ignore_submodules = GIT_SUBMODULE_IGNORE_ALL; - - if ((error = git_diff_index_to_workdir(&wd_diff_list, repo, NULL, &opts)) < 0) - goto done; - - *conflicts = wd_diff_list->deltas.length; - -done: - git_diff_free(wd_diff_list); - - return error; -} - -int git_merge__check_result(git_repository *repo, git_index *index_new) -{ - git_tree *head_tree = NULL; - git_iterator *iter_head = NULL, *iter_new = NULL; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - git_diff *merged_list = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_delta *delta; - git_vector paths = GIT_VECTOR_INIT; - size_t i, index_conflicts = 0, wd_conflicts = 0, conflicts; - const git_index_entry *e; - int error = 0; - - iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || - (error = git_iterator_for_tree(&iter_head, head_tree, &iter_opts)) < 0 || - (error = git_iterator_for_index(&iter_new, repo, index_new, &iter_opts)) < 0 || - (error = git_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0) - goto done; - - git_vector_foreach(&merged_list->deltas, i, delta) { - if ((error = git_vector_insert(&paths, (char *)delta->new_file.path)) < 0) - goto done; - } - - for (i = 0; i < git_index_entrycount(index_new); i++) { - e = git_index_get_byindex(index_new, i); - - if (git_index_entry_is_conflict(e) && - (git_vector_last(&paths) == NULL || - strcmp(git_vector_last(&paths), e->path) != 0)) { - - if ((error = git_vector_insert(&paths, (char *)e->path)) < 0) - goto done; - } - } - - /* Make sure the index and workdir state do not prevent merging */ - if ((error = merge_check_index(&index_conflicts, repo, index_new, &paths)) < 0 || - (error = merge_check_workdir(&wd_conflicts, repo, index_new, &paths)) < 0) - goto done; - - if ((conflicts = index_conflicts + wd_conflicts) > 0) { - git_error_set(GIT_ERROR_MERGE, "%" PRIuZ " uncommitted change%s would be overwritten by merge", - conflicts, (conflicts != 1) ? "s" : ""); - error = GIT_ECONFLICT; - } - -done: - git_vector_free(&paths); - git_tree_free(head_tree); - git_iterator_free(iter_head); - git_iterator_free(iter_new); - git_diff_free(merged_list); - - return error; -} - -int git_merge__append_conflicts_to_merge_msg( - git_repository *repo, - git_index *index) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - const char *last = NULL; - size_t i; - int error; - - if (!git_index_has_conflicts(index)) - return 0; - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_APPEND, GIT_MERGE_FILE_MODE)) < 0) - goto cleanup; - - git_filebuf_printf(&file, "\n#Conflicts:\n"); - - for (i = 0; i < git_index_entrycount(index); i++) { - const git_index_entry *e = git_index_get_byindex(index, i); - - if (!git_index_entry_is_conflict(e)) - continue; - - if (last == NULL || strcmp(e->path, last) != 0) - git_filebuf_printf(&file, "#\t%s\n", e->path); - - last = e->path; - } - - error = git_filebuf_commit(&file); - -cleanup: - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int merge_state_cleanup(git_repository *repo) -{ - const char *state_files[] = { - GIT_MERGE_HEAD_FILE, - GIT_MERGE_MODE_FILE, - GIT_MERGE_MSG_FILE, - }; - - return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); -} - -static int merge_heads( - git_annotated_commit **ancestor_head_out, - git_annotated_commit **our_head_out, - git_repository *repo, - git_reference *our_ref, - const git_annotated_commit **their_heads, - size_t their_heads_len) -{ - git_annotated_commit *ancestor_head = NULL, *our_head = NULL; - int error = 0; - - *ancestor_head_out = NULL; - *our_head_out = NULL; - - if ((error = git_annotated_commit_from_ref(&our_head, repo, our_ref)) < 0) - goto done; - - if ((error = merge_ancestor_head(&ancestor_head, repo, our_head, their_heads, their_heads_len)) < 0) { - if (error != GIT_ENOTFOUND) - goto done; - - git_error_clear(); - error = 0; - } - - *ancestor_head_out = ancestor_head; - *our_head_out = our_head; - -done: - if (error < 0) { - git_annotated_commit_free(ancestor_head); - git_annotated_commit_free(our_head); - } - - return error; -} - -static int merge_preference(git_merge_preference_t *out, git_repository *repo) -{ - git_config *config; - const char *value; - int bool_value, error = 0; - - *out = GIT_MERGE_PREFERENCE_NONE; - - if ((error = git_repository_config_snapshot(&config, repo)) < 0) - goto done; - - if ((error = git_config_get_string(&value, config, "merge.ff")) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - goto done; - } - - if (git_config_parse_bool(&bool_value, value) == 0) { - if (!bool_value) - *out |= GIT_MERGE_PREFERENCE_NO_FASTFORWARD; - } else { - if (strcasecmp(value, "only") == 0) - *out |= GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY; - } - -done: - git_config_free(config); - return error; -} - -int git_merge_analysis_for_ref( - git_merge_analysis_t *analysis_out, - git_merge_preference_t *preference_out, - git_repository *repo, - git_reference *our_ref, - const git_annotated_commit **their_heads, - size_t their_heads_len) -{ - git_annotated_commit *ancestor_head = NULL, *our_head = NULL; - int error = 0; - bool unborn; - - GIT_ASSERT_ARG(analysis_out); - GIT_ASSERT_ARG(preference_out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(their_heads && their_heads_len > 0); - - if (their_heads_len != 1) { - git_error_set(GIT_ERROR_MERGE, "can only merge a single branch"); - error = -1; - goto done; - } - - *analysis_out = GIT_MERGE_ANALYSIS_NONE; - - if ((error = merge_preference(preference_out, repo)) < 0) - goto done; - - if ((error = git_reference__is_unborn_head(&unborn, our_ref, repo)) < 0) - goto done; - - if (unborn) { - *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN; - error = 0; - goto done; - } - - if ((error = merge_heads(&ancestor_head, &our_head, repo, our_ref, their_heads, their_heads_len)) < 0) - goto done; - - /* We're up-to-date if we're trying to merge our own common ancestor. */ - if (ancestor_head && git_oid_equal( - git_annotated_commit_id(ancestor_head), git_annotated_commit_id(their_heads[0]))) - *analysis_out |= GIT_MERGE_ANALYSIS_UP_TO_DATE; - - /* We're fastforwardable if we're our own common ancestor. */ - else if (ancestor_head && git_oid_equal( - git_annotated_commit_id(ancestor_head), git_annotated_commit_id(our_head))) - *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL; - - /* Otherwise, just a normal merge is possible. */ - else - *analysis_out |= GIT_MERGE_ANALYSIS_NORMAL; - -done: - git_annotated_commit_free(ancestor_head); - git_annotated_commit_free(our_head); - return error; -} - -int git_merge_analysis( - git_merge_analysis_t *analysis_out, - git_merge_preference_t *preference_out, - git_repository *repo, - const git_annotated_commit **their_heads, - size_t their_heads_len) -{ - git_reference *head_ref = NULL; - int error = 0; - - if ((error = git_reference_lookup(&head_ref, repo, GIT_HEAD_FILE)) < 0) { - git_error_set(GIT_ERROR_MERGE, "failed to lookup HEAD reference"); - return error; - } - - error = git_merge_analysis_for_ref(analysis_out, preference_out, repo, head_ref, their_heads, their_heads_len); - - git_reference_free(head_ref); - - return error; -} - -int git_merge( - git_repository *repo, - const git_annotated_commit **their_heads, - size_t their_heads_len, - const git_merge_options *merge_opts, - const git_checkout_options *given_checkout_opts) -{ - git_reference *our_ref = NULL; - git_checkout_options checkout_opts; - git_annotated_commit *our_head = NULL, *base = NULL; - git_index *repo_index = NULL, *index = NULL; - git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; - unsigned int checkout_strategy; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(their_heads && their_heads_len > 0); - - if (their_heads_len != 1) { - git_error_set(GIT_ERROR_MERGE, "can only merge a single branch"); - return -1; - } - - if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0) - goto done; - - checkout_strategy = given_checkout_opts ? - given_checkout_opts->checkout_strategy : - GIT_CHECKOUT_SAFE; - - if ((error = git_indexwriter_init_for_operation(&indexwriter, repo, - &checkout_strategy)) < 0) - goto done; - - if ((error = git_repository_index(&repo_index, repo) < 0) || - (error = git_index_read(repo_index, 0) < 0)) - goto done; - - /* Write the merge setup files to the repository. */ - if ((error = git_annotated_commit_from_head(&our_head, repo)) < 0 || - (error = git_merge__setup(repo, our_head, their_heads, - their_heads_len)) < 0) - goto done; - - /* TODO: octopus */ - - if ((error = merge_annotated_commits(&index, &base, repo, our_head, - (git_annotated_commit *)their_heads[0], 0, merge_opts)) < 0 || - (error = git_merge__check_result(repo, index)) < 0 || - (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0) - goto done; - - /* check out the merge results */ - - if ((error = merge_normalize_checkout_opts(&checkout_opts, repo, - given_checkout_opts, checkout_strategy, - base, our_head, their_heads, their_heads_len)) < 0 || - (error = git_checkout_index(repo, index, &checkout_opts)) < 0) - goto done; - - error = git_indexwriter_commit(&indexwriter); - -done: - if (error < 0) - merge_state_cleanup(repo); - - git_indexwriter_cleanup(&indexwriter); - git_index_free(index); - git_annotated_commit_free(our_head); - git_annotated_commit_free(base); - git_reference_free(our_ref); - git_index_free(repo_index); - - return error; -} - -int git_merge_options_init(git_merge_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_merge_options, GIT_MERGE_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_merge_init_options(git_merge_options *opts, unsigned int version) -{ - return git_merge_options_init(opts, version); -} -#endif - -int git_merge_file_input_init(git_merge_file_input *input, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - input, version, git_merge_file_input, GIT_MERGE_FILE_INPUT_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_merge_file_init_input(git_merge_file_input *input, unsigned int version) -{ - return git_merge_file_input_init(input, version); -} -#endif - -int git_merge_file_options_init( - git_merge_file_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_merge_file_options, GIT_MERGE_FILE_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_merge_file_init_options( - git_merge_file_options *opts, unsigned int version) -{ - return git_merge_file_options_init(opts, version); -} -#endif diff --git a/src/merge.h b/src/merge.h deleted file mode 100644 index 23932905e..000000000 --- a/src/merge.h +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_merge_h__ -#define INCLUDE_merge_h__ - -#include "common.h" - -#include "vector.h" -#include "commit_list.h" -#include "pool.h" -#include "iterator.h" - -#include "git2/types.h" -#include "git2/merge.h" -#include "git2/sys/merge.h" - -#define GIT_MERGE_MSG_FILE "MERGE_MSG" -#define GIT_MERGE_MODE_FILE "MERGE_MODE" -#define GIT_MERGE_FILE_MODE 0666 - -#define GIT_MERGE_DEFAULT_RENAME_THRESHOLD 50 -#define GIT_MERGE_DEFAULT_TARGET_LIMIT 1000 - -/** Types of changes when files are merged from branch to branch. */ -typedef enum { - /* No conflict - a change only occurs in one branch. */ - GIT_MERGE_DIFF_NONE = 0, - - /* Occurs when a file is modified in both branches. */ - GIT_MERGE_DIFF_BOTH_MODIFIED = (1 << 0), - - /* Occurs when a file is added in both branches. */ - GIT_MERGE_DIFF_BOTH_ADDED = (1 << 1), - - /* Occurs when a file is deleted in both branches. */ - GIT_MERGE_DIFF_BOTH_DELETED = (1 << 2), - - /* Occurs when a file is modified in one branch and deleted in the other. */ - GIT_MERGE_DIFF_MODIFIED_DELETED = (1 << 3), - - /* Occurs when a file is renamed in one branch and modified in the other. */ - GIT_MERGE_DIFF_RENAMED_MODIFIED = (1 << 4), - - /* Occurs when a file is renamed in one branch and deleted in the other. */ - GIT_MERGE_DIFF_RENAMED_DELETED = (1 << 5), - - /* Occurs when a file is renamed in one branch and a file with the same - * name is added in the other. Eg, A->B and new file B. Core git calls - * this a "rename/delete". */ - GIT_MERGE_DIFF_RENAMED_ADDED = (1 << 6), - - /* Occurs when both a file is renamed to the same name in the ours and - * theirs branches. Eg, A->B and A->B in both. Automergeable. */ - GIT_MERGE_DIFF_BOTH_RENAMED = (1 << 7), - - /* Occurs when a file is renamed to different names in the ours and theirs - * branches. Eg, A->B and A->C. */ - GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 = (1 << 8), - - /* Occurs when two files are renamed to the same name in the ours and - * theirs branches. Eg, A->C and B->C. */ - GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 = (1 << 9), - - /* Occurs when an item at a path in one branch is a directory, and an - * item at the same path in a different branch is a file. */ - GIT_MERGE_DIFF_DIRECTORY_FILE = (1 << 10), - - /* The child of a folder that is in a directory/file conflict. */ - GIT_MERGE_DIFF_DF_CHILD = (1 << 11) -} git_merge_diff_t; - -typedef struct { - git_repository *repo; - git_pool pool; - - /* Vector of git_index_entry that represent the merged items that - * have been staged, either because only one side changed, or because - * the two changes were non-conflicting and mergeable. These items - * will be written as staged entries in the main index. - */ - git_vector staged; - - /* Vector of git_merge_diff entries that represent the conflicts that - * have not been automerged. These items will be written to high-stage - * entries in the main index. - */ - git_vector conflicts; - - /* Vector of git_merge_diff that have been automerged. These items - * will be written to the REUC when the index is produced. - */ - git_vector resolved; -} git_merge_diff_list; - -/** - * Description of changes to one file across three trees. - */ -typedef struct { - git_merge_diff_t type; - - git_index_entry ancestor_entry; - - git_index_entry our_entry; - git_delta_t our_status; - - git_index_entry their_entry; - git_delta_t their_status; - -} git_merge_diff; - -int git_merge__bases_many( - git_commit_list **out, - git_revwalk *walk, - git_commit_list_node *one, - git_vector *twos, - uint32_t minimum_generation); - -/* - * Three-way tree differencing - */ - -git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo); - -int git_merge_diff_list__find_differences( - git_merge_diff_list *merge_diff_list, - git_iterator *ancestor_iterator, - git_iterator *ours_iter, - git_iterator *theirs_iter); - -int git_merge_diff_list__find_renames(git_repository *repo, git_merge_diff_list *merge_diff_list, const git_merge_options *opts); - -void git_merge_diff_list__free(git_merge_diff_list *diff_list); - -/* Merge metadata setup */ - -int git_merge__setup( - git_repository *repo, - const git_annotated_commit *our_head, - const git_annotated_commit *heads[], - size_t heads_len); - -int git_merge__iterators( - git_index **out, - git_repository *repo, - git_iterator *ancestor_iter, - git_iterator *our_iter, - git_iterator *their_iter, - const git_merge_options *given_opts); - -int git_merge__check_result(git_repository *repo, git_index *index_new); - -int git_merge__append_conflicts_to_merge_msg(git_repository *repo, git_index *index); - -/* Merge files */ - -GIT_INLINE(const char *) git_merge_file__best_path( - const char *ancestor, - const char *ours, - const char *theirs) -{ - if (!ancestor) { - if (ours && theirs && strcmp(ours, theirs) == 0) - return ours; - - return NULL; - } - - if (ours && strcmp(ancestor, ours) == 0) - return theirs; - else if(theirs && strcmp(ancestor, theirs) == 0) - return ours; - - return NULL; -} - -GIT_INLINE(uint32_t) git_merge_file__best_mode( - uint32_t ancestor, uint32_t ours, uint32_t theirs) -{ - /* - * If ancestor didn't exist and either ours or theirs is executable, - * assume executable. Otherwise, if any mode changed from the ancestor, - * use that one. - */ - if (!ancestor) { - if (ours == GIT_FILEMODE_BLOB_EXECUTABLE || - theirs == GIT_FILEMODE_BLOB_EXECUTABLE) - return GIT_FILEMODE_BLOB_EXECUTABLE; - - return GIT_FILEMODE_BLOB; - } else if (ours && theirs) { - if (ancestor == ours) - return theirs; - - return ours; - } - - return 0; -} - -#endif diff --git a/src/merge_driver.c b/src/merge_driver.c deleted file mode 100644 index 19b35ac0e..000000000 --- a/src/merge_driver.c +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "merge_driver.h" - -#include "vector.h" -#include "runtime.h" -#include "merge.h" -#include "git2/merge.h" -#include "git2/sys/merge.h" - -static const char *merge_driver_name__text = "text"; -static const char *merge_driver_name__union = "union"; -static const char *merge_driver_name__binary = "binary"; - -struct merge_driver_registry { - git_rwlock lock; - git_vector drivers; -}; - -typedef struct { - git_merge_driver *driver; - int initialized; - char name[GIT_FLEX_ARRAY]; -} git_merge_driver_entry; - -static struct merge_driver_registry merge_driver_registry; - -static void git_merge_driver_global_shutdown(void); - -git_repository *git_merge_driver_source_repo( - const git_merge_driver_source *src) -{ - GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); - return src->repo; -} - -const git_index_entry *git_merge_driver_source_ancestor( - const git_merge_driver_source *src) -{ - GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); - return src->ancestor; -} - -const git_index_entry *git_merge_driver_source_ours( - const git_merge_driver_source *src) -{ - GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); - return src->ours; -} - -const git_index_entry *git_merge_driver_source_theirs( - const git_merge_driver_source *src) -{ - GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); - return src->theirs; -} - -const git_merge_file_options *git_merge_driver_source_file_options( - const git_merge_driver_source *src) -{ - GIT_ASSERT_ARG_WITH_RETVAL(src, NULL); - return src->file_opts; -} - -int git_merge_driver__builtin_apply( - git_merge_driver *self, - const char **path_out, - uint32_t *mode_out, - git_buf *merged_out, - const char *filter_name, - const git_merge_driver_source *src) -{ - git_merge_driver__builtin *driver = (git_merge_driver__builtin *)self; - git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; - git_merge_file_result result = {0}; - int error; - - GIT_UNUSED(filter_name); - - if (src->file_opts) - memcpy(&file_opts, src->file_opts, sizeof(git_merge_file_options)); - - if (driver->favor) - file_opts.favor = driver->favor; - - if ((error = git_merge_file_from_index(&result, src->repo, - src->ancestor, src->ours, src->theirs, &file_opts)) < 0) - goto done; - - if (!result.automergeable && - !(file_opts.flags & GIT_MERGE_FILE_ACCEPT_CONFLICTS)) { - error = GIT_EMERGECONFLICT; - goto done; - } - - *path_out = git_merge_file__best_path( - src->ancestor ? src->ancestor->path : NULL, - src->ours ? src->ours->path : NULL, - src->theirs ? src->theirs->path : NULL); - - *mode_out = git_merge_file__best_mode( - src->ancestor ? src->ancestor->mode : 0, - src->ours ? src->ours->mode : 0, - src->theirs ? src->theirs->mode : 0); - - merged_out->ptr = (char *)result.ptr; - merged_out->size = result.len; - merged_out->reserved = 0; - result.ptr = NULL; - -done: - git_merge_file_result_free(&result); - return error; -} - -static int merge_driver_binary_apply( - git_merge_driver *self, - const char **path_out, - uint32_t *mode_out, - git_buf *merged_out, - const char *filter_name, - const git_merge_driver_source *src) -{ - GIT_UNUSED(self); - GIT_UNUSED(path_out); - GIT_UNUSED(mode_out); - GIT_UNUSED(merged_out); - GIT_UNUSED(filter_name); - GIT_UNUSED(src); - - return GIT_EMERGECONFLICT; -} - -static int merge_driver_entry_cmp(const void *a, const void *b) -{ - const git_merge_driver_entry *entry_a = a; - const git_merge_driver_entry *entry_b = b; - - return strcmp(entry_a->name, entry_b->name); -} - -static int merge_driver_entry_search(const void *a, const void *b) -{ - const char *name_a = a; - const git_merge_driver_entry *entry_b = b; - - return strcmp(name_a, entry_b->name); -} - -git_merge_driver__builtin git_merge_driver__text = { - { - GIT_MERGE_DRIVER_VERSION, - NULL, - NULL, - git_merge_driver__builtin_apply, - }, - GIT_MERGE_FILE_FAVOR_NORMAL -}; - -git_merge_driver__builtin git_merge_driver__union = { - { - GIT_MERGE_DRIVER_VERSION, - NULL, - NULL, - git_merge_driver__builtin_apply, - }, - GIT_MERGE_FILE_FAVOR_UNION -}; - -git_merge_driver git_merge_driver__binary = { - GIT_MERGE_DRIVER_VERSION, - NULL, - NULL, - merge_driver_binary_apply -}; - -/* Note: callers must lock the registry before calling this function */ -static int merge_driver_registry_insert( - const char *name, git_merge_driver *driver) -{ - git_merge_driver_entry *entry; - - entry = git__calloc(1, sizeof(git_merge_driver_entry) + strlen(name) + 1); - GIT_ERROR_CHECK_ALLOC(entry); - - strcpy(entry->name, name); - entry->driver = driver; - - return git_vector_insert_sorted( - &merge_driver_registry.drivers, entry, NULL); -} - -int git_merge_driver_global_init(void) -{ - int error; - - if (git_rwlock_init(&merge_driver_registry.lock) < 0) - return -1; - - if ((error = git_vector_init(&merge_driver_registry.drivers, 3, - merge_driver_entry_cmp)) < 0) - goto done; - - if ((error = merge_driver_registry_insert( - merge_driver_name__text, &git_merge_driver__text.base)) < 0 || - (error = merge_driver_registry_insert( - merge_driver_name__union, &git_merge_driver__union.base)) < 0 || - (error = merge_driver_registry_insert( - merge_driver_name__binary, &git_merge_driver__binary)) < 0) - goto done; - - error = git_runtime_shutdown_register(git_merge_driver_global_shutdown); - -done: - if (error < 0) - git_vector_free_deep(&merge_driver_registry.drivers); - - return error; -} - -static void git_merge_driver_global_shutdown(void) -{ - git_merge_driver_entry *entry; - size_t i; - - if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) - return; - - git_vector_foreach(&merge_driver_registry.drivers, i, entry) { - if (entry->driver->shutdown) - entry->driver->shutdown(entry->driver); - - git__free(entry); - } - - git_vector_free(&merge_driver_registry.drivers); - - git_rwlock_wrunlock(&merge_driver_registry.lock); - git_rwlock_free(&merge_driver_registry.lock); -} - -/* Note: callers must lock the registry before calling this function */ -static int merge_driver_registry_find(size_t *pos, const char *name) -{ - return git_vector_search2(pos, &merge_driver_registry.drivers, - merge_driver_entry_search, name); -} - -/* Note: callers must lock the registry before calling this function */ -static git_merge_driver_entry *merge_driver_registry_lookup( - size_t *pos, const char *name) -{ - git_merge_driver_entry *entry = NULL; - - if (!merge_driver_registry_find(pos, name)) - entry = git_vector_get(&merge_driver_registry.drivers, *pos); - - return entry; -} - -int git_merge_driver_register(const char *name, git_merge_driver *driver) -{ - int error; - - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(driver); - - if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); - return -1; - } - - if (!merge_driver_registry_find(NULL, name)) { - git_error_set(GIT_ERROR_MERGE, "attempt to reregister existing driver '%s'", - name); - error = GIT_EEXISTS; - goto done; - } - - error = merge_driver_registry_insert(name, driver); - -done: - git_rwlock_wrunlock(&merge_driver_registry.lock); - return error; -} - -int git_merge_driver_unregister(const char *name) -{ - git_merge_driver_entry *entry; - size_t pos; - int error = 0; - - if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); - return -1; - } - - if ((entry = merge_driver_registry_lookup(&pos, name)) == NULL) { - git_error_set(GIT_ERROR_MERGE, "cannot find merge driver '%s' to unregister", - name); - error = GIT_ENOTFOUND; - goto done; - } - - git_vector_remove(&merge_driver_registry.drivers, pos); - - if (entry->initialized && entry->driver->shutdown) { - entry->driver->shutdown(entry->driver); - entry->initialized = false; - } - - git__free(entry); - -done: - git_rwlock_wrunlock(&merge_driver_registry.lock); - return error; -} - -git_merge_driver *git_merge_driver_lookup(const char *name) -{ - git_merge_driver_entry *entry; - size_t pos; - int error; - - /* If we've decided the merge driver to use internally - and not - * based on user configuration (in merge_driver_name_for_path) - * then we can use a hardcoded name to compare instead of bothering - * to take a lock and look it up in the vector. - */ - if (name == merge_driver_name__text) - return &git_merge_driver__text.base; - else if (name == merge_driver_name__binary) - return &git_merge_driver__binary; - - if (git_rwlock_rdlock(&merge_driver_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); - return NULL; - } - - entry = merge_driver_registry_lookup(&pos, name); - - git_rwlock_rdunlock(&merge_driver_registry.lock); - - if (entry == NULL) { - git_error_set(GIT_ERROR_MERGE, "cannot use an unregistered filter"); - return NULL; - } - - if (!entry->initialized) { - if (entry->driver->initialize && - (error = entry->driver->initialize(entry->driver)) < 0) - return NULL; - - entry->initialized = 1; - } - - return entry->driver; -} - -static int merge_driver_name_for_path( - const char **out, - git_repository *repo, - const char *path, - const char *default_driver) -{ - const char *value; - int error; - - *out = NULL; - - if ((error = git_attr_get(&value, repo, 0, path, "merge")) < 0) - return error; - - /* set: use the built-in 3-way merge driver ("text") */ - if (GIT_ATTR_IS_TRUE(value)) - *out = merge_driver_name__text; - - /* unset: do not merge ("binary") */ - else if (GIT_ATTR_IS_FALSE(value)) - *out = merge_driver_name__binary; - - else if (GIT_ATTR_IS_UNSPECIFIED(value) && default_driver) - *out = default_driver; - - else if (GIT_ATTR_IS_UNSPECIFIED(value)) - *out = merge_driver_name__text; - - else - *out = value; - - return 0; -} - - -GIT_INLINE(git_merge_driver *) merge_driver_lookup_with_wildcard( - const char *name) -{ - git_merge_driver *driver = git_merge_driver_lookup(name); - - if (driver == NULL) - driver = git_merge_driver_lookup("*"); - - return driver; -} - -int git_merge_driver_for_source( - const char **name_out, - git_merge_driver **driver_out, - const git_merge_driver_source *src) -{ - const char *path, *driver_name; - int error = 0; - - path = git_merge_file__best_path( - src->ancestor ? src->ancestor->path : NULL, - src->ours ? src->ours->path : NULL, - src->theirs ? src->theirs->path : NULL); - - if ((error = merge_driver_name_for_path( - &driver_name, src->repo, path, src->default_driver)) < 0) - return error; - - *name_out = driver_name; - *driver_out = merge_driver_lookup_with_wildcard(driver_name); - return error; -} - diff --git a/src/merge_driver.h b/src/merge_driver.h deleted file mode 100644 index 6b7da5287..000000000 --- a/src/merge_driver.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_merge_driver_h__ -#define INCLUDE_merge_driver_h__ - -#include "common.h" - -#include "git2/merge.h" -#include "git2/index.h" -#include "git2/sys/merge.h" - -struct git_merge_driver_source { - git_repository *repo; - const char *default_driver; - const git_merge_file_options *file_opts; - - const git_index_entry *ancestor; - const git_index_entry *ours; - const git_index_entry *theirs; -}; - -typedef struct git_merge_driver__builtin { - git_merge_driver base; - git_merge_file_favor_t favor; -} git_merge_driver__builtin; - -extern int git_merge_driver_global_init(void); - -extern int git_merge_driver_for_path( - char **name_out, - git_merge_driver **driver_out, - git_repository *repo, - const char *path); - -/* Merge driver configuration */ -extern int git_merge_driver_for_source( - const char **name_out, - git_merge_driver **driver_out, - const git_merge_driver_source *src); - -extern int git_merge_driver__builtin_apply( - git_merge_driver *self, - const char **path_out, - uint32_t *mode_out, - git_buf *merged_out, - const char *filter_name, - const git_merge_driver_source *src); - -/* Merge driver for text files, performs a standard three-way merge */ -extern git_merge_driver__builtin git_merge_driver__text; - -/* Merge driver for union-style merging */ -extern git_merge_driver__builtin git_merge_driver__union; - -/* Merge driver for unmergeable (binary) files: always produces conflicts */ -extern git_merge_driver git_merge_driver__binary; - -#endif diff --git a/src/merge_file.c b/src/merge_file.c deleted file mode 100644 index 732a047b1..000000000 --- a/src/merge_file.c +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "repository.h" -#include "posix.h" -#include "futils.h" -#include "index.h" -#include "diff_xdiff.h" -#include "merge.h" - -#include "git2/repository.h" -#include "git2/object.h" -#include "git2/index.h" -#include "git2/merge.h" - -#include "xdiff/xdiff.h" - -/* only examine the first 8000 bytes for binaryness. - * https://github.com/git/git/blob/77bd3ea9f54f1584147b594abc04c26ca516d987/xdiff-interface.c#L197 - */ -#define GIT_MERGE_FILE_BINARY_SIZE 8000 - -#define GIT_MERGE_FILE_SIDE_EXISTS(X) ((X)->mode != 0) - -static int merge_file_input_from_index( - git_merge_file_input *input_out, - git_odb_object **odb_object_out, - git_odb *odb, - const git_index_entry *entry) -{ - int error = 0; - - GIT_ASSERT_ARG(input_out); - GIT_ASSERT_ARG(odb_object_out); - GIT_ASSERT_ARG(odb); - GIT_ASSERT_ARG(entry); - - if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0) - goto done; - - input_out->path = entry->path; - input_out->mode = entry->mode; - input_out->ptr = (char *)git_odb_object_data(*odb_object_out); - input_out->size = git_odb_object_size(*odb_object_out); - -done: - return error; -} - -static void merge_file_normalize_opts( - git_merge_file_options *out, - const git_merge_file_options *given_opts) -{ - if (given_opts) - memcpy(out, given_opts, sizeof(git_merge_file_options)); - else { - git_merge_file_options default_opts = GIT_MERGE_FILE_OPTIONS_INIT; - memcpy(out, &default_opts, sizeof(git_merge_file_options)); - } -} - -static int merge_file__xdiff( - git_merge_file_result *out, - const git_merge_file_input *ancestor, - const git_merge_file_input *ours, - const git_merge_file_input *theirs, - const git_merge_file_options *given_opts) -{ - xmparam_t xmparam; - mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0}; - mmbuffer_t mmbuffer; - git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT; - const char *path; - int xdl_result; - int error = 0; - - memset(out, 0x0, sizeof(git_merge_file_result)); - - merge_file_normalize_opts(&options, given_opts); - - memset(&xmparam, 0x0, sizeof(xmparam_t)); - - if (ours->size > LONG_MAX || - theirs->size > LONG_MAX || - (ancestor && ancestor->size > LONG_MAX)) { - git_error_set(GIT_ERROR_MERGE, "failed to merge files"); - error = -1; - goto done; - } - - if (ancestor) { - xmparam.ancestor = (options.ancestor_label) ? - options.ancestor_label : ancestor->path; - ancestor_mmfile.ptr = (char *)ancestor->ptr; - ancestor_mmfile.size = (long)ancestor->size; - } - - xmparam.file1 = (options.our_label) ? - options.our_label : ours->path; - our_mmfile.ptr = (char *)ours->ptr; - our_mmfile.size = (long)ours->size; - - xmparam.file2 = (options.their_label) ? - options.their_label : theirs->path; - their_mmfile.ptr = (char *)theirs->ptr; - their_mmfile.size = (long)theirs->size; - - if (options.favor == GIT_MERGE_FILE_FAVOR_OURS) - xmparam.favor = XDL_MERGE_FAVOR_OURS; - else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS) - xmparam.favor = XDL_MERGE_FAVOR_THEIRS; - else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION) - xmparam.favor = XDL_MERGE_FAVOR_UNION; - - xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ? - XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS; - - if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3) - xmparam.style = XDL_MERGE_DIFF3; - if (options.flags & GIT_MERGE_FILE_STYLE_ZDIFF3) - xmparam.style = XDL_MERGE_ZEALOUS_DIFF3; - - if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE) - xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE; - if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE) - xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_CHANGE; - if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL) - xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; - - if (options.flags & GIT_MERGE_FILE_DIFF_PATIENCE) - xmparam.xpp.flags |= XDF_PATIENCE_DIFF; - - if (options.flags & GIT_MERGE_FILE_DIFF_MINIMAL) - xmparam.xpp.flags |= XDF_NEED_MINIMAL; - - xmparam.marker_size = options.marker_size; - - if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile, - &their_mmfile, &xmparam, &mmbuffer)) < 0) { - git_error_set(GIT_ERROR_MERGE, "failed to merge files"); - error = -1; - goto done; - } - - path = git_merge_file__best_path( - ancestor ? ancestor->path : NULL, - ours->path, - theirs->path); - - if (path != NULL && (out->path = git__strdup(path)) == NULL) { - error = -1; - goto done; - } - - out->automergeable = (xdl_result == 0); - out->ptr = (const char *)mmbuffer.ptr; - out->len = mmbuffer.size; - out->mode = git_merge_file__best_mode( - ancestor ? ancestor->mode : 0, - ours->mode, - theirs->mode); - -done: - if (error < 0) - git_merge_file_result_free(out); - - return error; -} - -static bool merge_file__is_binary(const git_merge_file_input *file) -{ - size_t len = file ? file->size : 0; - - if (len > GIT_XDIFF_MAX_SIZE) - return true; - if (len > GIT_MERGE_FILE_BINARY_SIZE) - len = GIT_MERGE_FILE_BINARY_SIZE; - - return len ? (memchr(file->ptr, 0, len) != NULL) : false; -} - -static int merge_file__binary( - git_merge_file_result *out, - const git_merge_file_input *ours, - const git_merge_file_input *theirs, - const git_merge_file_options *given_opts) -{ - const git_merge_file_input *favored = NULL; - - memset(out, 0x0, sizeof(git_merge_file_result)); - - if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_OURS) - favored = ours; - else if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_THEIRS) - favored = theirs; - else - goto done; - - if ((out->path = git__strdup(favored->path)) == NULL || - (out->ptr = git__malloc(favored->size)) == NULL) - goto done; - - memcpy((char *)out->ptr, favored->ptr, favored->size); - out->len = favored->size; - out->mode = favored->mode; - out->automergeable = 1; - -done: - return 0; -} - -static int merge_file__from_inputs( - git_merge_file_result *out, - const git_merge_file_input *ancestor, - const git_merge_file_input *ours, - const git_merge_file_input *theirs, - const git_merge_file_options *given_opts) -{ - if (merge_file__is_binary(ancestor) || - merge_file__is_binary(ours) || - merge_file__is_binary(theirs)) - return merge_file__binary(out, ours, theirs, given_opts); - - return merge_file__xdiff(out, ancestor, ours, theirs, given_opts); -} - -static git_merge_file_input *git_merge_file__normalize_inputs( - git_merge_file_input *out, - const git_merge_file_input *given) -{ - memcpy(out, given, sizeof(git_merge_file_input)); - - if (!out->path) - out->path = "file.txt"; - - if (!out->mode) - out->mode = 0100644; - - return out; -} - -int git_merge_file( - git_merge_file_result *out, - const git_merge_file_input *ancestor, - const git_merge_file_input *ours, - const git_merge_file_input *theirs, - const git_merge_file_options *options) -{ - git_merge_file_input inputs[3] = { {0} }; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ours); - GIT_ASSERT_ARG(theirs); - - memset(out, 0x0, sizeof(git_merge_file_result)); - - if (ancestor) - ancestor = git_merge_file__normalize_inputs(&inputs[0], ancestor); - - ours = git_merge_file__normalize_inputs(&inputs[1], ours); - theirs = git_merge_file__normalize_inputs(&inputs[2], theirs); - - return merge_file__from_inputs(out, ancestor, ours, theirs, options); -} - -int git_merge_file_from_index( - git_merge_file_result *out, - git_repository *repo, - const git_index_entry *ancestor, - const git_index_entry *ours, - const git_index_entry *theirs, - const git_merge_file_options *options) -{ - git_merge_file_input *ancestor_ptr = NULL, - ancestor_input = {0}, our_input = {0}, their_input = {0}; - git_odb *odb = NULL; - git_odb_object *odb_object[3] = { 0 }; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(ours); - GIT_ASSERT_ARG(theirs); - - memset(out, 0x0, sizeof(git_merge_file_result)); - - if ((error = git_repository_odb(&odb, repo)) < 0) - goto done; - - if (ancestor) { - if ((error = merge_file_input_from_index( - &ancestor_input, &odb_object[0], odb, ancestor)) < 0) - goto done; - - ancestor_ptr = &ancestor_input; - } - - if ((error = merge_file_input_from_index(&our_input, &odb_object[1], odb, ours)) < 0 || - (error = merge_file_input_from_index(&their_input, &odb_object[2], odb, theirs)) < 0) - goto done; - - error = merge_file__from_inputs(out, - ancestor_ptr, &our_input, &their_input, options); - -done: - git_odb_object_free(odb_object[0]); - git_odb_object_free(odb_object[1]); - git_odb_object_free(odb_object[2]); - git_odb_free(odb); - - return error; -} - -void git_merge_file_result_free(git_merge_file_result *result) -{ - if (result == NULL) - return; - - git__free((char *)result->path); - git__free((char *)result->ptr); -} diff --git a/src/message.c b/src/message.c deleted file mode 100644 index ec0103a33..000000000 --- a/src/message.c +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "buf.h" - -#include "git2/message.h" - -static size_t line_length_without_trailing_spaces(const char *line, size_t len) -{ - while (len) { - unsigned char c = line[len - 1]; - if (!git__isspace(c)) - break; - len--; - } - - return len; -} - -/* Greatly inspired from git.git "stripspace" */ -/* see https://github.com/git/git/blob/497215d8811ac7b8955693ceaad0899ecd894ed2/builtin/stripspace.c#L4-67 */ -static int git_message__prettify( - git_str *message_out, - const char *message, - int strip_comments, - char comment_char) -{ - const size_t message_len = strlen(message); - - int consecutive_empty_lines = 0; - size_t i, line_length, rtrimmed_line_length; - char *next_newline; - - for (i = 0; i < strlen(message); i += line_length) { - next_newline = memchr(message + i, '\n', message_len - i); - - if (next_newline != NULL) { - line_length = next_newline - (message + i) + 1; - } else { - line_length = message_len - i; - } - - if (strip_comments && line_length && message[i] == comment_char) - continue; - - rtrimmed_line_length = line_length_without_trailing_spaces(message + i, line_length); - - if (!rtrimmed_line_length) { - consecutive_empty_lines++; - continue; - } - - if (consecutive_empty_lines > 0 && message_out->size > 0) - git_str_putc(message_out, '\n'); - - consecutive_empty_lines = 0; - git_str_put(message_out, message + i, rtrimmed_line_length); - git_str_putc(message_out, '\n'); - } - - return git_str_oom(message_out) ? -1 : 0; -} - -int git_message_prettify( - git_buf *message_out, - const char *message, - int strip_comments, - char comment_char) -{ - GIT_BUF_WRAP_PRIVATE(message_out, git_message__prettify, message, strip_comments, comment_char); -} diff --git a/src/midx.c b/src/midx.c deleted file mode 100644 index eb99e7373..000000000 --- a/src/midx.c +++ /dev/null @@ -1,896 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "midx.h" - -#include "array.h" -#include "buf.h" -#include "filebuf.h" -#include "futils.h" -#include "hash.h" -#include "odb.h" -#include "pack.h" -#include "fs_path.h" -#include "repository.h" -#include "str.h" - -#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */ -#define MIDX_VERSION 1 -#define MIDX_OBJECT_ID_VERSION 1 -struct git_midx_header { - uint32_t signature; - uint8_t version; - uint8_t object_id_version; - uint8_t chunks; - uint8_t base_midx_files; - uint32_t packfiles; -}; - -#define MIDX_PACKFILE_NAMES_ID 0x504e414d /* "PNAM" */ -#define MIDX_OID_FANOUT_ID 0x4f494446 /* "OIDF" */ -#define MIDX_OID_LOOKUP_ID 0x4f49444c /* "OIDL" */ -#define MIDX_OBJECT_OFFSETS_ID 0x4f4f4646 /* "OOFF" */ -#define MIDX_OBJECT_LARGE_OFFSETS_ID 0x4c4f4646 /* "LOFF" */ - -struct git_midx_chunk { - off64_t offset; - size_t length; -}; - -typedef int (*midx_write_cb)(const char *buf, size_t size, void *cb_data); - -static int midx_error(const char *message) -{ - git_error_set(GIT_ERROR_ODB, "invalid multi-pack-index file - %s", message); - return -1; -} - -static int midx_parse_packfile_names( - git_midx_file *idx, - const unsigned char *data, - uint32_t packfiles, - struct git_midx_chunk *chunk) -{ - int error; - uint32_t i; - char *packfile_name = (char *)(data + chunk->offset); - size_t chunk_size = chunk->length, len; - if (chunk->offset == 0) - return midx_error("missing Packfile Names chunk"); - if (chunk->length == 0) - return midx_error("empty Packfile Names chunk"); - if ((error = git_vector_init(&idx->packfile_names, packfiles, git__strcmp_cb)) < 0) - return error; - for (i = 0; i < packfiles; ++i) { - len = p_strnlen(packfile_name, chunk_size); - if (len == 0) - return midx_error("empty packfile name"); - if (len + 1 > chunk_size) - return midx_error("unterminated packfile name"); - git_vector_insert(&idx->packfile_names, packfile_name); - if (i && strcmp(git_vector_get(&idx->packfile_names, i - 1), packfile_name) >= 0) - return midx_error("packfile names are not sorted"); - if (strlen(packfile_name) <= strlen(".idx") || git__suffixcmp(packfile_name, ".idx") != 0) - return midx_error("non-.idx packfile name"); - if (strchr(packfile_name, '/') != NULL || strchr(packfile_name, '\\') != NULL) - return midx_error("non-local packfile"); - packfile_name += len + 1; - chunk_size -= len + 1; - } - return 0; -} - -static int midx_parse_oid_fanout( - git_midx_file *idx, - const unsigned char *data, - struct git_midx_chunk *chunk_oid_fanout) -{ - uint32_t i, nr; - if (chunk_oid_fanout->offset == 0) - return midx_error("missing OID Fanout chunk"); - if (chunk_oid_fanout->length == 0) - return midx_error("empty OID Fanout chunk"); - if (chunk_oid_fanout->length != 256 * 4) - return midx_error("OID Fanout chunk has wrong length"); - - idx->oid_fanout = (const uint32_t *)(data + chunk_oid_fanout->offset); - nr = 0; - for (i = 0; i < 256; ++i) { - uint32_t n = ntohl(idx->oid_fanout[i]); - if (n < nr) - return midx_error("index is non-monotonic"); - nr = n; - } - idx->num_objects = nr; - return 0; -} - -static int midx_parse_oid_lookup( - git_midx_file *idx, - const unsigned char *data, - struct git_midx_chunk *chunk_oid_lookup) -{ - uint32_t i; - git_oid *oid, *prev_oid, zero_oid = {{0}}; - - if (chunk_oid_lookup->offset == 0) - return midx_error("missing OID Lookup chunk"); - if (chunk_oid_lookup->length == 0) - return midx_error("empty OID Lookup chunk"); - if (chunk_oid_lookup->length != idx->num_objects * GIT_OID_RAWSZ) - return midx_error("OID Lookup chunk has wrong length"); - - idx->oid_lookup = oid = (git_oid *)(data + chunk_oid_lookup->offset); - prev_oid = &zero_oid; - for (i = 0; i < idx->num_objects; ++i, ++oid) { - if (git_oid_cmp(prev_oid, oid) >= 0) - return midx_error("OID Lookup index is non-monotonic"); - prev_oid = oid; - } - - return 0; -} - -static int midx_parse_object_offsets( - git_midx_file *idx, - const unsigned char *data, - struct git_midx_chunk *chunk_object_offsets) -{ - if (chunk_object_offsets->offset == 0) - return midx_error("missing Object Offsets chunk"); - if (chunk_object_offsets->length == 0) - return midx_error("empty Object Offsets chunk"); - if (chunk_object_offsets->length != idx->num_objects * 8) - return midx_error("Object Offsets chunk has wrong length"); - - idx->object_offsets = data + chunk_object_offsets->offset; - - return 0; -} - -static int midx_parse_object_large_offsets( - git_midx_file *idx, - const unsigned char *data, - struct git_midx_chunk *chunk_object_large_offsets) -{ - if (chunk_object_large_offsets->length == 0) - return 0; - if (chunk_object_large_offsets->length % 8 != 0) - return midx_error("malformed Object Large Offsets chunk"); - - idx->object_large_offsets = data + chunk_object_large_offsets->offset; - idx->num_object_large_offsets = chunk_object_large_offsets->length / 8; - - return 0; -} - -int git_midx_parse( - git_midx_file *idx, - const unsigned char *data, - size_t size) -{ - struct git_midx_header *hdr; - const unsigned char *chunk_hdr; - struct git_midx_chunk *last_chunk; - uint32_t i; - off64_t last_chunk_offset, chunk_offset, trailer_offset; - size_t checksum_size; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - int error; - struct git_midx_chunk chunk_packfile_names = {0}, - chunk_oid_fanout = {0}, - chunk_oid_lookup = {0}, - chunk_object_offsets = {0}, - chunk_object_large_offsets = {0}; - - GIT_ASSERT_ARG(idx); - - if (size < sizeof(struct git_midx_header) + GIT_OID_RAWSZ) - return midx_error("multi-pack index is too short"); - - hdr = ((struct git_midx_header *)data); - - if (hdr->signature != htonl(MIDX_SIGNATURE) || - hdr->version != MIDX_VERSION || - hdr->object_id_version != MIDX_OBJECT_ID_VERSION) { - return midx_error("unsupported multi-pack index version"); - } - if (hdr->chunks == 0) - return midx_error("no chunks in multi-pack index"); - - /* - * The very first chunk's offset should be after the header, all the chunk - * headers, and a special zero chunk. - */ - last_chunk_offset = - sizeof(struct git_midx_header) + - (1 + hdr->chunks) * 12; - - checksum_size = GIT_HASH_SHA1_SIZE; - trailer_offset = size - checksum_size; - - if (trailer_offset < last_chunk_offset) - return midx_error("wrong index size"); - memcpy(idx->checksum, data + trailer_offset, checksum_size); - - if (git_hash_buf(checksum, data, (size_t)trailer_offset, GIT_HASH_ALGORITHM_SHA1) < 0) - return midx_error("could not calculate signature"); - if (memcmp(checksum, idx->checksum, checksum_size) != 0) - return midx_error("index signature mismatch"); - - chunk_hdr = data + sizeof(struct git_midx_header); - last_chunk = NULL; - for (i = 0; i < hdr->chunks; ++i, chunk_hdr += 12) { - chunk_offset = ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 4)))) << 32 | - ((off64_t)ntohl(*((uint32_t *)(chunk_hdr + 8)))); - if (chunk_offset < last_chunk_offset) - return midx_error("chunks are non-monotonic"); - if (chunk_offset >= trailer_offset) - return midx_error("chunks extend beyond the trailer"); - if (last_chunk != NULL) - last_chunk->length = (size_t)(chunk_offset - last_chunk_offset); - last_chunk_offset = chunk_offset; - - switch (ntohl(*((uint32_t *)(chunk_hdr + 0)))) { - case MIDX_PACKFILE_NAMES_ID: - chunk_packfile_names.offset = last_chunk_offset; - last_chunk = &chunk_packfile_names; - break; - - case MIDX_OID_FANOUT_ID: - chunk_oid_fanout.offset = last_chunk_offset; - last_chunk = &chunk_oid_fanout; - break; - - case MIDX_OID_LOOKUP_ID: - chunk_oid_lookup.offset = last_chunk_offset; - last_chunk = &chunk_oid_lookup; - break; - - case MIDX_OBJECT_OFFSETS_ID: - chunk_object_offsets.offset = last_chunk_offset; - last_chunk = &chunk_object_offsets; - break; - - case MIDX_OBJECT_LARGE_OFFSETS_ID: - chunk_object_large_offsets.offset = last_chunk_offset; - last_chunk = &chunk_object_large_offsets; - break; - - default: - return midx_error("unrecognized chunk ID"); - } - } - last_chunk->length = (size_t)(trailer_offset - last_chunk_offset); - - error = midx_parse_packfile_names( - idx, data, ntohl(hdr->packfiles), &chunk_packfile_names); - if (error < 0) - return error; - error = midx_parse_oid_fanout(idx, data, &chunk_oid_fanout); - if (error < 0) - return error; - error = midx_parse_oid_lookup(idx, data, &chunk_oid_lookup); - if (error < 0) - return error; - error = midx_parse_object_offsets(idx, data, &chunk_object_offsets); - if (error < 0) - return error; - error = midx_parse_object_large_offsets(idx, data, &chunk_object_large_offsets); - if (error < 0) - return error; - - return 0; -} - -int git_midx_open( - git_midx_file **idx_out, - const char *path) -{ - git_midx_file *idx; - git_file fd = -1; - size_t idx_size; - struct stat st; - int error; - - /* TODO: properly open the file without access time using O_NOATIME */ - fd = git_futils_open_ro(path); - if (fd < 0) - return fd; - - if (p_fstat(fd, &st) < 0) { - p_close(fd); - git_error_set(GIT_ERROR_ODB, "multi-pack-index file not found - '%s'", path); - return -1; - } - - if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size)) { - p_close(fd); - git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path); - return -1; - } - idx_size = (size_t)st.st_size; - - idx = git__calloc(1, sizeof(git_midx_file)); - GIT_ERROR_CHECK_ALLOC(idx); - - error = git_str_sets(&idx->filename, path); - if (error < 0) - return error; - - error = git_futils_mmap_ro(&idx->index_map, fd, 0, idx_size); - p_close(fd); - if (error < 0) { - git_midx_free(idx); - return error; - } - - if ((error = git_midx_parse(idx, idx->index_map.data, idx_size)) < 0) { - git_midx_free(idx); - return error; - } - - *idx_out = idx; - return 0; -} - -bool git_midx_needs_refresh( - const git_midx_file *idx, - const char *path) -{ - git_file fd = -1; - struct stat st; - ssize_t bytes_read; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size; - - /* TODO: properly open the file without access time using O_NOATIME */ - fd = git_futils_open_ro(path); - if (fd < 0) - return true; - - if (p_fstat(fd, &st) < 0) { - p_close(fd); - return true; - } - - if (!S_ISREG(st.st_mode) || - !git__is_sizet(st.st_size) || - (size_t)st.st_size != idx->index_map.len) { - p_close(fd); - return true; - } - - checksum_size = GIT_HASH_SHA1_SIZE; - bytes_read = p_pread(fd, checksum, checksum_size, st.st_size - GIT_OID_RAWSZ); - p_close(fd); - - if (bytes_read != (ssize_t)checksum_size) - return true; - - return (memcmp(checksum, idx->checksum, checksum_size) != 0); -} - -int git_midx_entry_find( - git_midx_entry *e, - git_midx_file *idx, - const git_oid *short_oid, - size_t len) -{ - int pos, found = 0; - size_t pack_index; - uint32_t hi, lo; - const git_oid *current = NULL; - const unsigned char *object_offset; - off64_t offset; - - GIT_ASSERT_ARG(idx); - - hi = ntohl(idx->oid_fanout[(int)short_oid->id[0]]); - lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(idx->oid_fanout[(int)short_oid->id[0] - 1])); - - pos = git_pack__lookup_sha1(idx->oid_lookup, GIT_OID_RAWSZ, lo, hi, short_oid->id); - - if (pos >= 0) { - /* An object matching exactly the oid was found */ - found = 1; - current = idx->oid_lookup + pos; - } else { - /* No object was found */ - /* pos refers to the object with the "closest" oid to short_oid */ - pos = -1 - pos; - if (pos < (int)idx->num_objects) { - current = idx->oid_lookup + pos; - - if (!git_oid_ncmp(short_oid, current, len)) - found = 1; - } - } - - if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)idx->num_objects) { - /* Check for ambiguousity */ - const git_oid *next = current + 1; - - if (!git_oid_ncmp(short_oid, next, len)) { - found = 2; - } - } - - if (!found) - return git_odb__error_notfound("failed to find offset for multi-pack index entry", short_oid, len); - if (found > 1) - return git_odb__error_ambiguous("found multiple offsets for multi-pack index entry"); - - object_offset = idx->object_offsets + pos * 8; - offset = ntohl(*((uint32_t *)(object_offset + 4))); - if (offset & 0x80000000) { - uint32_t object_large_offsets_pos = offset & 0x7fffffff; - const unsigned char *object_large_offsets_index = idx->object_large_offsets; - - /* Make sure we're not being sent out of bounds */ - if (object_large_offsets_pos >= idx->num_object_large_offsets) - return git_odb__error_notfound("invalid index into the object large offsets table", short_oid, len); - - object_large_offsets_index += 8 * object_large_offsets_pos; - - offset = (((uint64_t)ntohl(*((uint32_t *)(object_large_offsets_index + 0)))) << 32) | - ntohl(*((uint32_t *)(object_large_offsets_index + 4))); - } - pack_index = ntohl(*((uint32_t *)(object_offset + 0))); - if (pack_index >= git_vector_length(&idx->packfile_names)) - return midx_error("invalid index into the packfile names table"); - e->pack_index = pack_index; - e->offset = offset; - git_oid_cpy(&e->sha1, current); - return 0; -} - -int git_midx_foreach_entry( - git_midx_file *idx, - git_odb_foreach_cb cb, - void *data) -{ - size_t i; - int error; - - GIT_ASSERT_ARG(idx); - - for (i = 0; i < idx->num_objects; ++i) { - if ((error = cb(&idx->oid_lookup[i], data)) != 0) - return git_error_set_after_callback(error); - } - - return error; -} - -int git_midx_close(git_midx_file *idx) -{ - GIT_ASSERT_ARG(idx); - - if (idx->index_map.data) - git_futils_mmap_free(&idx->index_map); - - git_vector_free(&idx->packfile_names); - - return 0; -} - -void git_midx_free(git_midx_file *idx) -{ - if (!idx) - return; - - git_str_dispose(&idx->filename); - git_midx_close(idx); - git__free(idx); -} - -static int packfile__cmp(const void *a_, const void *b_) -{ - const struct git_pack_file *a = a_; - const struct git_pack_file *b = b_; - - return strcmp(a->pack_name, b->pack_name); -} - -int git_midx_writer_new( - git_midx_writer **out, - const char *pack_dir) -{ - git_midx_writer *w = git__calloc(1, sizeof(git_midx_writer)); - GIT_ERROR_CHECK_ALLOC(w); - - if (git_str_sets(&w->pack_dir, pack_dir) < 0) { - git__free(w); - return -1; - } - git_fs_path_squash_slashes(&w->pack_dir); - - if (git_vector_init(&w->packs, 0, packfile__cmp) < 0) { - git_str_dispose(&w->pack_dir); - git__free(w); - return -1; - } - - *out = w; - return 0; -} - -void git_midx_writer_free(git_midx_writer *w) -{ - struct git_pack_file *p; - size_t i; - - if (!w) - return; - - git_vector_foreach (&w->packs, i, p) - git_mwindow_put_pack(p); - git_vector_free(&w->packs); - git_str_dispose(&w->pack_dir); - git__free(w); -} - -int git_midx_writer_add( - git_midx_writer *w, - const char *idx_path) -{ - git_str idx_path_buf = GIT_STR_INIT; - int error; - struct git_pack_file *p; - - error = git_fs_path_prettify(&idx_path_buf, idx_path, git_str_cstr(&w->pack_dir)); - if (error < 0) - return error; - - error = git_mwindow_get_pack(&p, git_str_cstr(&idx_path_buf)); - git_str_dispose(&idx_path_buf); - if (error < 0) - return error; - - error = git_vector_insert(&w->packs, p); - if (error < 0) { - git_mwindow_put_pack(p); - return error; - } - - return 0; -} - -typedef git_array_t(git_midx_entry) object_entry_array_t; - -struct object_entry_cb_state { - uint32_t pack_index; - object_entry_array_t *object_entries_array; -}; - -static int object_entry__cb(const git_oid *oid, off64_t offset, void *data) -{ - struct object_entry_cb_state *state = (struct object_entry_cb_state *)data; - - git_midx_entry *entry = git_array_alloc(*state->object_entries_array); - GIT_ERROR_CHECK_ALLOC(entry); - - git_oid_cpy(&entry->sha1, oid); - entry->offset = offset; - entry->pack_index = state->pack_index; - - return 0; -} - -static int object_entry__cmp(const void *a_, const void *b_) -{ - const git_midx_entry *a = (const git_midx_entry *)a_; - const git_midx_entry *b = (const git_midx_entry *)b_; - - return git_oid_cmp(&a->sha1, &b->sha1); -} - -static int write_offset(off64_t offset, midx_write_cb write_cb, void *cb_data) -{ - int error; - uint32_t word; - - word = htonl((uint32_t)((offset >> 32) & 0xffffffffu)); - error = write_cb((const char *)&word, sizeof(word), cb_data); - if (error < 0) - return error; - word = htonl((uint32_t)((offset >> 0) & 0xffffffffu)); - error = write_cb((const char *)&word, sizeof(word), cb_data); - if (error < 0) - return error; - - return 0; -} - -static int write_chunk_header(int chunk_id, off64_t offset, midx_write_cb write_cb, void *cb_data) -{ - uint32_t word = htonl(chunk_id); - int error = write_cb((const char *)&word, sizeof(word), cb_data); - if (error < 0) - return error; - return write_offset(offset, write_cb, cb_data); - - return 0; -} - -static int midx_write_buf(const char *buf, size_t size, void *data) -{ - git_str *b = (git_str *)data; - return git_str_put(b, buf, size); -} - -struct midx_write_hash_context { - midx_write_cb write_cb; - void *cb_data; - git_hash_ctx *ctx; -}; - -static int midx_write_hash(const char *buf, size_t size, void *data) -{ - struct midx_write_hash_context *ctx = (struct midx_write_hash_context *)data; - int error; - - error = git_hash_update(ctx->ctx, buf, size); - if (error < 0) - return error; - - return ctx->write_cb(buf, size, ctx->cb_data); -} - -static int midx_write( - git_midx_writer *w, - midx_write_cb write_cb, - void *cb_data) -{ - int error = 0; - size_t i; - struct git_pack_file *p; - struct git_midx_header hdr = {0}; - uint32_t oid_fanout_count; - uint32_t object_large_offsets_count; - uint32_t oid_fanout[256]; - off64_t offset; - git_str packfile_names = GIT_STR_INIT, - oid_lookup = GIT_STR_INIT, - object_offsets = GIT_STR_INIT, - object_large_offsets = GIT_STR_INIT; - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - size_t checksum_size; - git_midx_entry *entry; - object_entry_array_t object_entries_array = GIT_ARRAY_INIT; - git_vector object_entries = GIT_VECTOR_INIT; - git_hash_ctx ctx; - struct midx_write_hash_context hash_cb_data = {0}; - - hdr.signature = htonl(MIDX_SIGNATURE); - hdr.version = MIDX_VERSION; - hdr.object_id_version = MIDX_OBJECT_ID_VERSION; - hdr.base_midx_files = 0; - - hash_cb_data.write_cb = write_cb; - hash_cb_data.cb_data = cb_data; - hash_cb_data.ctx = &ctx; - - checksum_size = GIT_HASH_SHA1_SIZE; - error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1); - if (error < 0) - return error; - cb_data = &hash_cb_data; - write_cb = midx_write_hash; - - git_vector_sort(&w->packs); - git_vector_foreach (&w->packs, i, p) { - git_str relative_index = GIT_STR_INIT; - struct object_entry_cb_state state = {0}; - size_t path_len; - - state.pack_index = (uint32_t)i; - state.object_entries_array = &object_entries_array; - - error = git_str_sets(&relative_index, p->pack_name); - if (error < 0) - goto cleanup; - error = git_fs_path_make_relative(&relative_index, git_str_cstr(&w->pack_dir)); - if (error < 0) { - git_str_dispose(&relative_index); - goto cleanup; - } - path_len = git_str_len(&relative_index); - if (path_len <= strlen(".pack") || git__suffixcmp(git_str_cstr(&relative_index), ".pack") != 0) { - git_str_dispose(&relative_index); - git_error_set(GIT_ERROR_INVALID, "invalid packfile name: '%s'", p->pack_name); - error = -1; - goto cleanup; - } - path_len -= strlen(".pack"); - - git_str_put(&packfile_names, git_str_cstr(&relative_index), path_len); - git_str_puts(&packfile_names, ".idx"); - git_str_putc(&packfile_names, '\0'); - git_str_dispose(&relative_index); - - error = git_pack_foreach_entry_offset(p, object_entry__cb, &state); - if (error < 0) - goto cleanup; - } - - /* Sort the object entries. */ - error = git_vector_init(&object_entries, git_array_size(object_entries_array), object_entry__cmp); - if (error < 0) - goto cleanup; - git_array_foreach (object_entries_array, i, entry) { - if ((error = git_vector_set(NULL, &object_entries, i, entry)) < 0) - goto cleanup; - } - git_vector_set_sorted(&object_entries, 0); - git_vector_sort(&object_entries); - git_vector_uniq(&object_entries, NULL); - - /* Pad the packfile names so it is a multiple of four. */ - while (git_str_len(&packfile_names) & 3) - git_str_putc(&packfile_names, '\0'); - - /* Fill the OID Fanout table. */ - oid_fanout_count = 0; - for (i = 0; i < 256; i++) { - while (oid_fanout_count < git_vector_length(&object_entries) && - ((const git_midx_entry *)git_vector_get(&object_entries, oid_fanout_count))->sha1.id[0] <= i) - ++oid_fanout_count; - oid_fanout[i] = htonl(oid_fanout_count); - } - - /* Fill the OID Lookup table. */ - git_vector_foreach (&object_entries, i, entry) { - error = git_str_put(&oid_lookup, (const char *)&entry->sha1, sizeof(entry->sha1)); - if (error < 0) - goto cleanup; - } - - /* Fill the Object Offsets and Object Large Offsets tables. */ - object_large_offsets_count = 0; - git_vector_foreach (&object_entries, i, entry) { - uint32_t word; - - word = htonl((uint32_t)entry->pack_index); - error = git_str_put(&object_offsets, (const char *)&word, sizeof(word)); - if (error < 0) - goto cleanup; - if (entry->offset >= 0x80000000l) { - word = htonl(0x80000000u | object_large_offsets_count++); - if ((error = write_offset(entry->offset, midx_write_buf, &object_large_offsets)) < 0) - goto cleanup; - } else { - word = htonl((uint32_t)entry->offset & 0x7fffffffu); - } - - error = git_str_put(&object_offsets, (const char *)&word, sizeof(word)); - if (error < 0) - goto cleanup; - } - - /* Write the header. */ - hdr.packfiles = htonl((uint32_t)git_vector_length(&w->packs)); - hdr.chunks = 4; - if (git_str_len(&object_large_offsets) > 0) - hdr.chunks++; - error = write_cb((const char *)&hdr, sizeof(hdr), cb_data); - if (error < 0) - goto cleanup; - - /* Write the chunk headers. */ - offset = sizeof(hdr) + (hdr.chunks + 1) * 12; - error = write_chunk_header(MIDX_PACKFILE_NAMES_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&packfile_names); - error = write_chunk_header(MIDX_OID_FANOUT_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += sizeof(oid_fanout); - error = write_chunk_header(MIDX_OID_LOOKUP_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&oid_lookup); - error = write_chunk_header(MIDX_OBJECT_OFFSETS_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&object_offsets); - if (git_str_len(&object_large_offsets) > 0) { - error = write_chunk_header(MIDX_OBJECT_LARGE_OFFSETS_ID, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - offset += git_str_len(&object_large_offsets); - } - error = write_chunk_header(0, offset, write_cb, cb_data); - if (error < 0) - goto cleanup; - - /* Write all the chunks. */ - error = write_cb(git_str_cstr(&packfile_names), git_str_len(&packfile_names), cb_data); - if (error < 0) - goto cleanup; - error = write_cb((const char *)oid_fanout, sizeof(oid_fanout), cb_data); - if (error < 0) - goto cleanup; - error = write_cb(git_str_cstr(&oid_lookup), git_str_len(&oid_lookup), cb_data); - if (error < 0) - goto cleanup; - error = write_cb(git_str_cstr(&object_offsets), git_str_len(&object_offsets), cb_data); - if (error < 0) - goto cleanup; - error = write_cb(git_str_cstr(&object_large_offsets), git_str_len(&object_large_offsets), cb_data); - if (error < 0) - goto cleanup; - - /* Finalize the checksum and write the trailer. */ - error = git_hash_final(checksum, &ctx); - if (error < 0) - goto cleanup; - error = write_cb((char *)checksum, checksum_size, cb_data); - if (error < 0) - goto cleanup; - -cleanup: - git_array_clear(object_entries_array); - git_vector_free(&object_entries); - git_str_dispose(&packfile_names); - git_str_dispose(&oid_lookup); - git_str_dispose(&object_offsets); - git_str_dispose(&object_large_offsets); - git_hash_ctx_cleanup(&ctx); - return error; -} - -static int midx_write_filebuf(const char *buf, size_t size, void *data) -{ - git_filebuf *f = (git_filebuf *)data; - return git_filebuf_write(f, buf, size); -} - -int git_midx_writer_commit( - git_midx_writer *w) -{ - int error; - int filebuf_flags = GIT_FILEBUF_DO_NOT_BUFFER; - git_str midx_path = GIT_STR_INIT; - git_filebuf output = GIT_FILEBUF_INIT; - - error = git_str_joinpath(&midx_path, git_str_cstr(&w->pack_dir), "multi-pack-index"); - if (error < 0) - return error; - - if (git_repository__fsync_gitdir) - filebuf_flags |= GIT_FILEBUF_FSYNC; - error = git_filebuf_open(&output, git_str_cstr(&midx_path), filebuf_flags, 0644); - git_str_dispose(&midx_path); - if (error < 0) - return error; - - error = midx_write(w, midx_write_filebuf, &output); - if (error < 0) { - git_filebuf_cleanup(&output); - return error; - } - - return git_filebuf_commit(&output); -} - -int git_midx_writer_dump( - git_buf *midx, - git_midx_writer *w) -{ - git_str str = GIT_STR_INIT; - int error; - - if ((error = git_buf_tostr(&str, midx)) < 0 || - (error = midx_write(w, midx_write_buf, &str)) == 0) - error = git_buf_fromstr(midx, &str); - - git_str_dispose(&str); - return error; -} diff --git a/src/midx.h b/src/midx.h deleted file mode 100644 index 7dd851bd3..000000000 --- a/src/midx.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_midx_h__ -#define INCLUDE_midx_h__ - -#include "common.h" - -#include - -#include "git2/sys/midx.h" - -#include "map.h" -#include "mwindow.h" -#include "odb.h" - -/* - * A multi-pack-index file. - * - * This file contains a merged index for multiple independent .pack files. This - * can help speed up locating objects without requiring a garbage collection - * cycle to create a single .pack file. - * - * Support for this feature was added in git 2.21, and requires the - * `core.multiPackIndex` config option to be set. - */ -typedef struct git_midx_file { - git_map index_map; - - /* The table of Packfile Names. */ - git_vector packfile_names; - - /* The OID Fanout table. */ - const uint32_t *oid_fanout; - /* The total number of objects in the index. */ - uint32_t num_objects; - - /* The OID Lookup table. */ - git_oid *oid_lookup; - - /* The Object Offsets table. Each entry has two 4-byte fields with the pack index and the offset. */ - const unsigned char *object_offsets; - - /* The Object Large Offsets table. */ - const unsigned char *object_large_offsets; - /* The number of entries in the Object Large Offsets table. Each entry has an 8-byte with an offset */ - size_t num_object_large_offsets; - - /* The trailer of the file. Contains the SHA1-checksum of the whole file. */ - unsigned char checksum[GIT_HASH_SHA1_SIZE]; - - /* something like ".git/objects/pack/multi-pack-index". */ - git_str filename; -} git_midx_file; - -/* - * An entry in the multi-pack-index file. Similar in purpose to git_pack_entry. - */ -typedef struct git_midx_entry { - /* The index within idx->packfile_names where the packfile name can be found. */ - size_t pack_index; - /* The offset within the .pack file where the requested object is found. */ - off64_t offset; - /* The SHA-1 hash of the requested object. */ - git_oid sha1; -} git_midx_entry; - -/* - * A writer for `multi-pack-index` files. - */ -struct git_midx_writer { - /* - * The path of the directory where the .pack/.idx files are stored. The - * `multi-pack-index` file will be written to the same directory. - */ - git_str pack_dir; - - /* The list of `git_pack_file`s. */ - git_vector packs; -}; - -int git_midx_open( - git_midx_file **idx_out, - const char *path); -bool git_midx_needs_refresh( - const git_midx_file *idx, - const char *path); -int git_midx_entry_find( - git_midx_entry *e, - git_midx_file *idx, - const git_oid *short_oid, - size_t len); -int git_midx_foreach_entry( - git_midx_file *idx, - git_odb_foreach_cb cb, - void *data); -int git_midx_close(git_midx_file *idx); -void git_midx_free(git_midx_file *idx); - -/* This is exposed for use in the fuzzers. */ -int git_midx_parse( - git_midx_file *idx, - const unsigned char *data, - size_t size); - -#endif diff --git a/src/mwindow.c b/src/mwindow.c deleted file mode 100644 index d06b7a80e..000000000 --- a/src/mwindow.c +++ /dev/null @@ -1,541 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "mwindow.h" - -#include "vector.h" -#include "futils.h" -#include "map.h" -#include "runtime.h" -#include "strmap.h" -#include "pack.h" - -#define DEFAULT_WINDOW_SIZE \ - (sizeof(void*) >= 8 \ - ? 1 * 1024 * 1024 * 1024 \ - : 32 * 1024 * 1024) - -#define DEFAULT_MAPPED_LIMIT \ - ((1024 * 1024) * (sizeof(void*) >= 8 ? UINT64_C(8192) : UINT64_C(256))) - -/* default is unlimited */ -#define DEFAULT_FILE_LIMIT 0 - -size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE; -size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT; -size_t git_mwindow__file_limit = DEFAULT_FILE_LIMIT; - -/* Mutex to control access to `git_mwindow__mem_ctl` and `git__pack_cache`. */ -git_mutex git__mwindow_mutex; - -/* Whenever you want to read or modify this, grab `git__mwindow_mutex` */ -git_mwindow_ctl git_mwindow__mem_ctl; - -/* Global list of mwindow files, to open packs once across repos */ -git_strmap *git__pack_cache = NULL; - -static void git_mwindow_global_shutdown(void) -{ - git_strmap *tmp = git__pack_cache; - - git_mutex_free(&git__mwindow_mutex); - - git__pack_cache = NULL; - git_strmap_free(tmp); -} - -int git_mwindow_global_init(void) -{ - int error; - - GIT_ASSERT(!git__pack_cache); - - if ((error = git_mutex_init(&git__mwindow_mutex)) < 0 || - (error = git_strmap_new(&git__pack_cache)) < 0) - return error; - - return git_runtime_shutdown_register(git_mwindow_global_shutdown); -} - -int git_mwindow_get_pack(struct git_pack_file **out, const char *path) -{ - struct git_pack_file *pack; - char *packname; - int error; - - if ((error = git_packfile__name(&packname, path)) < 0) - return error; - - if (git_mutex_lock(&git__mwindow_mutex) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock mwindow mutex"); - return -1; - } - - pack = git_strmap_get(git__pack_cache, packname); - git__free(packname); - - if (pack != NULL) { - git_atomic32_inc(&pack->refcount); - git_mutex_unlock(&git__mwindow_mutex); - *out = pack; - return 0; - } - - /* If we didn't find it, we need to create it */ - if ((error = git_packfile_alloc(&pack, path)) < 0) { - git_mutex_unlock(&git__mwindow_mutex); - return error; - } - - git_atomic32_inc(&pack->refcount); - - error = git_strmap_set(git__pack_cache, pack->pack_name, pack); - git_mutex_unlock(&git__mwindow_mutex); - if (error < 0) { - git_packfile_free(pack, false); - return error; - } - - *out = pack; - return 0; -} - -int git_mwindow_put_pack(struct git_pack_file *pack) -{ - int count, error; - struct git_pack_file *pack_to_delete = NULL; - - if ((error = git_mutex_lock(&git__mwindow_mutex)) < 0) - return error; - - /* put before get would be a corrupted state */ - GIT_ASSERT(git__pack_cache); - - /* if we cannot find it, the state is corrupted */ - GIT_ASSERT(git_strmap_exists(git__pack_cache, pack->pack_name)); - - count = git_atomic32_dec(&pack->refcount); - if (count == 0) { - git_strmap_delete(git__pack_cache, pack->pack_name); - pack_to_delete = pack; - } - git_mutex_unlock(&git__mwindow_mutex); - git_packfile_free(pack_to_delete, false); - - return 0; -} - -/* - * Free all the windows in a sequence, typically because we're done - * with the file. Needs to hold the git__mwindow_mutex. - */ -static int git_mwindow_free_all_locked(git_mwindow_file *mwf) -{ - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - size_t i; - - /* - * Remove these windows from the global list - */ - for (i = 0; i < ctl->windowfiles.length; ++i){ - if (git_vector_get(&ctl->windowfiles, i) == mwf) { - git_vector_remove(&ctl->windowfiles, i); - break; - } - } - - if (ctl->windowfiles.length == 0) { - git_vector_free(&ctl->windowfiles); - ctl->windowfiles.contents = NULL; - } - - while (mwf->windows) { - git_mwindow *w = mwf->windows; - GIT_ASSERT(w->inuse_cnt == 0); - - ctl->mapped -= w->window_map.len; - ctl->open_windows--; - - git_futils_mmap_free(&w->window_map); - - mwf->windows = w->next; - git__free(w); - } - - return 0; -} - -int git_mwindow_free_all(git_mwindow_file *mwf) -{ - int error; - - if (git_mutex_lock(&git__mwindow_mutex)) { - git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); - return -1; - } - - error = git_mwindow_free_all_locked(mwf); - - git_mutex_unlock(&git__mwindow_mutex); - - return error; -} - -/* - * Check if a window 'win' contains the address 'offset' - */ -int git_mwindow_contains(git_mwindow *win, off64_t offset) -{ - off64_t win_off = win->offset; - return win_off <= offset - && offset <= (off64_t)(win_off + win->window_map.len); -} - -#define GIT_MWINDOW__LRU -1 -#define GIT_MWINDOW__MRU 1 - -/* - * Find the least- or most-recently-used window in a file that is not currently - * being used. The 'only_unused' flag controls whether the caller requires the - * file to only have unused windows. If '*out_window' is non-null, it is used as - * a starting point for the comparison. - * - * Returns whether such a window was found in the file. - */ -static bool git_mwindow_scan_recently_used( - git_mwindow_file *mwf, - git_mwindow **out_window, - git_mwindow **out_last, - bool only_unused, - int comparison_sign) -{ - git_mwindow *w, *w_last; - git_mwindow *lru_window = NULL, *lru_last = NULL; - bool found = false; - - GIT_ASSERT_ARG(mwf); - GIT_ASSERT_ARG(out_window); - - lru_window = *out_window; - if (out_last) - lru_last = *out_last; - - for (w_last = NULL, w = mwf->windows; w; w_last = w, w = w->next) { - if (w->inuse_cnt) { - if (only_unused) - return false; - /* This window is currently being used. Skip it. */ - continue; - } - - /* - * If the current one is more (or less) recent than the last one, - * store it in the output parameter. If lru_window is NULL, - * it's the first loop, so store it as well. - */ - if (!lru_window || - (comparison_sign == GIT_MWINDOW__LRU && lru_window->last_used > w->last_used) || - (comparison_sign == GIT_MWINDOW__MRU && lru_window->last_used < w->last_used)) { - lru_window = w; - lru_last = w_last; - found = true; - } - } - - if (!found) - return false; - - *out_window = lru_window; - if (out_last) - *out_last = lru_last; - return true; -} - -/* - * Close the least recently used window (that is currently not being used) out - * of all the files. Called under lock from new_window_locked. - */ -static int git_mwindow_close_lru_window_locked(void) -{ - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - git_mwindow_file *cur; - size_t i; - git_mwindow *lru_window = NULL, *lru_last = NULL, **list = NULL; - - git_vector_foreach(&ctl->windowfiles, i, cur) { - if (git_mwindow_scan_recently_used( - cur, &lru_window, &lru_last, false, GIT_MWINDOW__LRU)) { - list = &cur->windows; - } - } - - if (!lru_window) { - git_error_set(GIT_ERROR_OS, "failed to close memory window; couldn't find LRU"); - return -1; - } - - ctl->mapped -= lru_window->window_map.len; - git_futils_mmap_free(&lru_window->window_map); - - if (lru_last) - lru_last->next = lru_window->next; - else - *list = lru_window->next; - - git__free(lru_window); - ctl->open_windows--; - - return 0; -} - -/* - * Finds the file that does not have any open windows AND whose - * most-recently-used window is the least-recently used one across all - * currently open files. - * - * Called under lock from new_window_locked. - */ -static int git_mwindow_find_lru_file_locked(git_mwindow_file **out) -{ - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - git_mwindow_file *lru_file = NULL, *current_file = NULL; - git_mwindow *lru_window = NULL; - size_t i; - - git_vector_foreach(&ctl->windowfiles, i, current_file) { - git_mwindow *mru_window = NULL; - if (!git_mwindow_scan_recently_used( - current_file, &mru_window, NULL, true, GIT_MWINDOW__MRU)) { - continue; - } - if (!lru_window || lru_window->last_used > mru_window->last_used) { - lru_window = mru_window; - lru_file = current_file; - } - } - - if (!lru_file) { - git_error_set(GIT_ERROR_OS, "failed to close memory window file; couldn't find LRU"); - return -1; - } - - *out = lru_file; - return 0; -} - -/* This gets called under lock from git_mwindow_open */ -static git_mwindow *new_window_locked( - git_file fd, - off64_t size, - off64_t offset) -{ - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - size_t walign = git_mwindow__window_size / 2; - off64_t len; - git_mwindow *w; - - w = git__calloc(1, sizeof(*w)); - - if (w == NULL) - return NULL; - - w->offset = (offset / walign) * walign; - - len = size - w->offset; - if (len > (off64_t)git_mwindow__window_size) - len = (off64_t)git_mwindow__window_size; - - ctl->mapped += (size_t)len; - - while (git_mwindow__mapped_limit < ctl->mapped && - git_mwindow_close_lru_window_locked() == 0) /* nop */; - - /* - * We treat `mapped_limit` as a soft limit. If we can't find a - * window to close and are above the limit, we still mmap the new - * window. - */ - - if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) { - /* - * The first error might be down to memory fragmentation even if - * we're below our soft limits, so free up what we can and try again. - */ - - while (git_mwindow_close_lru_window_locked() == 0) - /* nop */; - - if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) { - git__free(w); - return NULL; - } - } - - ctl->mmap_calls++; - ctl->open_windows++; - - if (ctl->mapped > ctl->peak_mapped) - ctl->peak_mapped = ctl->mapped; - - if (ctl->open_windows > ctl->peak_open_windows) - ctl->peak_open_windows = ctl->open_windows; - - return w; -} - -/* - * Open a new window, closing the least recenty used until we have - * enough space. Don't forget to add it to your list - */ -unsigned char *git_mwindow_open( - git_mwindow_file *mwf, - git_mwindow **cursor, - off64_t offset, - size_t extra, - unsigned int *left) -{ - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - git_mwindow *w = *cursor; - - if (git_mutex_lock(&git__mwindow_mutex)) { - git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); - return NULL; - } - - if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) { - if (w) { - w->inuse_cnt--; - } - - for (w = mwf->windows; w; w = w->next) { - if (git_mwindow_contains(w, offset) && - git_mwindow_contains(w, offset + extra)) - break; - } - - /* - * If there isn't a suitable window, we need to create a new - * one. - */ - if (!w) { - w = new_window_locked(mwf->fd, mwf->size, offset); - if (w == NULL) { - git_mutex_unlock(&git__mwindow_mutex); - return NULL; - } - w->next = mwf->windows; - mwf->windows = w; - } - } - - /* If we changed w, store it in the cursor */ - if (w != *cursor) { - w->last_used = ctl->used_ctr++; - w->inuse_cnt++; - *cursor = w; - } - - offset -= w->offset; - - if (left) - *left = (unsigned int)(w->window_map.len - offset); - - git_mutex_unlock(&git__mwindow_mutex); - return (unsigned char *) w->window_map.data + offset; -} - -int git_mwindow_file_register(git_mwindow_file *mwf) -{ - git_vector closed_files = GIT_VECTOR_INIT; - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - int error; - size_t i; - git_mwindow_file *closed_file = NULL; - - if (git_mutex_lock(&git__mwindow_mutex)) { - git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); - return -1; - } - - if (ctl->windowfiles.length == 0 && - (error = git_vector_init(&ctl->windowfiles, 8, NULL)) < 0) { - git_mutex_unlock(&git__mwindow_mutex); - goto cleanup; - } - - if (git_mwindow__file_limit) { - git_mwindow_file *lru_file; - while (git_mwindow__file_limit <= ctl->windowfiles.length && - git_mwindow_find_lru_file_locked(&lru_file) == 0) { - if ((error = git_vector_insert(&closed_files, lru_file)) < 0) { - /* - * Exceeding the file limit seems preferable to being open to - * data races that can end up corrupting the heap. - */ - break; - } - git_mwindow_free_all_locked(lru_file); - } - } - - error = git_vector_insert(&ctl->windowfiles, mwf); - git_mutex_unlock(&git__mwindow_mutex); - if (error < 0) - goto cleanup; - - /* - * Once we have released the global windowfiles lock, we can close each - * individual file. Before doing so, acquire that file's lock to avoid - * closing a file that is currently being used. - */ - git_vector_foreach(&closed_files, i, closed_file) { - error = git_mutex_lock(&closed_file->lock); - if (error < 0) - continue; - p_close(closed_file->fd); - closed_file->fd = -1; - git_mutex_unlock(&closed_file->lock); - } - -cleanup: - git_vector_free(&closed_files); - return error; -} - -void git_mwindow_file_deregister(git_mwindow_file *mwf) -{ - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - git_mwindow_file *cur; - size_t i; - - if (git_mutex_lock(&git__mwindow_mutex)) - return; - - git_vector_foreach(&ctl->windowfiles, i, cur) { - if (cur == mwf) { - git_vector_remove(&ctl->windowfiles, i); - git_mutex_unlock(&git__mwindow_mutex); - return; - } - } - git_mutex_unlock(&git__mwindow_mutex); -} - -void git_mwindow_close(git_mwindow **window) -{ - git_mwindow *w = *window; - if (w) { - if (git_mutex_lock(&git__mwindow_mutex)) { - git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); - return; - } - - w->inuse_cnt--; - git_mutex_unlock(&git__mwindow_mutex); - *window = NULL; - } -} diff --git a/src/mwindow.h b/src/mwindow.h deleted file mode 100644 index e3a03f019..000000000 --- a/src/mwindow.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_mwindow__ -#define INCLUDE_mwindow__ - -#include "common.h" - -#include "map.h" -#include "vector.h" - -typedef struct git_mwindow { - struct git_mwindow *next; - git_map window_map; - off64_t offset; - size_t last_used; - size_t inuse_cnt; -} git_mwindow; - -typedef struct git_mwindow_file { - git_mutex lock; /* protects updates to fd */ - git_mwindow *windows; - int fd; - off64_t size; -} git_mwindow_file; - -typedef struct git_mwindow_ctl { - size_t mapped; - unsigned int open_windows; - unsigned int mmap_calls; - unsigned int peak_open_windows; - size_t peak_mapped; - size_t used_ctr; - git_vector windowfiles; -} git_mwindow_ctl; - -int git_mwindow_contains(git_mwindow *win, off64_t offset); -int git_mwindow_free_all(git_mwindow_file *mwf); /* locks */ -unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, off64_t offset, size_t extra, unsigned int *left); -int git_mwindow_file_register(git_mwindow_file *mwf); -void git_mwindow_file_deregister(git_mwindow_file *mwf); -void git_mwindow_close(git_mwindow **w_cursor); - -extern int git_mwindow_global_init(void); - -struct git_pack_file; /* just declaration to avoid cyclical includes */ -int git_mwindow_get_pack(struct git_pack_file **out, const char *path); -int git_mwindow_put_pack(struct git_pack_file *pack); - -#endif diff --git a/src/net.c b/src/net.c deleted file mode 100644 index a76fd1d7c..000000000 --- a/src/net.c +++ /dev/null @@ -1,750 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "net.h" -#include "netops.h" - -#include - -#include "posix.h" -#include "str.h" -#include "http_parser.h" -#include "runtime.h" - -#define DEFAULT_PORT_HTTP "80" -#define DEFAULT_PORT_HTTPS "443" -#define DEFAULT_PORT_GIT "9418" -#define DEFAULT_PORT_SSH "22" - -bool git_net_str_is_url(const char *str) -{ - const char *c; - - for (c = str; *c; c++) { - if (*c == ':' && *(c+1) == '/' && *(c+2) == '/') - return true; - - if ((*c < 'a' || *c > 'z') && - (*c < 'A' || *c > 'Z') && - (*c < '0' || *c > '9') && - (*c != '+' && *c != '-' && *c != '.')) - break; - } - - return false; -} - -static const char *default_port_for_scheme(const char *scheme) -{ - if (strcmp(scheme, "http") == 0) - return DEFAULT_PORT_HTTP; - else if (strcmp(scheme, "https") == 0) - return DEFAULT_PORT_HTTPS; - else if (strcmp(scheme, "git") == 0) - return DEFAULT_PORT_GIT; - else if (strcmp(scheme, "ssh") == 0 || - strcmp(scheme, "ssh+git") == 0 || - strcmp(scheme, "git+ssh") == 0) - return DEFAULT_PORT_SSH; - - return NULL; -} - -int git_net_url_dup(git_net_url *out, git_net_url *in) -{ - if (in->scheme) { - out->scheme = git__strdup(in->scheme); - GIT_ERROR_CHECK_ALLOC(out->scheme); - } - - if (in->host) { - out->host = git__strdup(in->host); - GIT_ERROR_CHECK_ALLOC(out->host); - } - - if (in->port) { - out->port = git__strdup(in->port); - GIT_ERROR_CHECK_ALLOC(out->port); - } - - if (in->path) { - out->path = git__strdup(in->path); - GIT_ERROR_CHECK_ALLOC(out->path); - } - - if (in->query) { - out->query = git__strdup(in->query); - GIT_ERROR_CHECK_ALLOC(out->query); - } - - if (in->username) { - out->username = git__strdup(in->username); - GIT_ERROR_CHECK_ALLOC(out->username); - } - - if (in->password) { - out->password = git__strdup(in->password); - GIT_ERROR_CHECK_ALLOC(out->password); - } - - return 0; -} - -int git_net_url_parse(git_net_url *url, const char *given) -{ - struct http_parser_url u = {0}; - bool has_scheme, has_host, has_port, has_path, has_query, has_userinfo; - git_str scheme = GIT_STR_INIT, - host = GIT_STR_INIT, - port = GIT_STR_INIT, - path = GIT_STR_INIT, - username = GIT_STR_INIT, - password = GIT_STR_INIT, - query = GIT_STR_INIT; - int error = GIT_EINVALIDSPEC; - - if (http_parser_parse_url(given, strlen(given), false, &u)) { - git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given); - goto done; - } - - has_scheme = !!(u.field_set & (1 << UF_SCHEMA)); - has_host = !!(u.field_set & (1 << UF_HOST)); - has_port = !!(u.field_set & (1 << UF_PORT)); - has_path = !!(u.field_set & (1 << UF_PATH)); - has_query = !!(u.field_set & (1 << UF_QUERY)); - has_userinfo = !!(u.field_set & (1 << UF_USERINFO)); - - if (has_scheme) { - const char *url_scheme = given + u.field_data[UF_SCHEMA].off; - size_t url_scheme_len = u.field_data[UF_SCHEMA].len; - git_str_put(&scheme, url_scheme, url_scheme_len); - git__strntolower(scheme.ptr, scheme.size); - } else { - git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given); - goto done; - } - - if (has_host) { - const char *url_host = given + u.field_data[UF_HOST].off; - size_t url_host_len = u.field_data[UF_HOST].len; - git_str_decode_percent(&host, url_host, url_host_len); - } - - if (has_port) { - const char *url_port = given + u.field_data[UF_PORT].off; - size_t url_port_len = u.field_data[UF_PORT].len; - git_str_put(&port, url_port, url_port_len); - } else { - const char *default_port = default_port_for_scheme(scheme.ptr); - - if (default_port == NULL) { - git_error_set(GIT_ERROR_NET, "unknown scheme for URL '%s'", given); - goto done; - } - - git_str_puts(&port, default_port); - } - - if (has_path) { - const char *url_path = given + u.field_data[UF_PATH].off; - size_t url_path_len = u.field_data[UF_PATH].len; - git_str_put(&path, url_path, url_path_len); - } else { - git_str_puts(&path, "/"); - } - - if (has_query) { - const char *url_query = given + u.field_data[UF_QUERY].off; - size_t url_query_len = u.field_data[UF_QUERY].len; - git_str_decode_percent(&query, url_query, url_query_len); - } - - if (has_userinfo) { - const char *url_userinfo = given + u.field_data[UF_USERINFO].off; - size_t url_userinfo_len = u.field_data[UF_USERINFO].len; - const char *colon = memchr(url_userinfo, ':', url_userinfo_len); - - if (colon) { - const char *url_username = url_userinfo; - size_t url_username_len = colon - url_userinfo; - const char *url_password = colon + 1; - size_t url_password_len = url_userinfo_len - (url_username_len + 1); - - git_str_decode_percent(&username, url_username, url_username_len); - git_str_decode_percent(&password, url_password, url_password_len); - } else { - git_str_decode_percent(&username, url_userinfo, url_userinfo_len); - } - } - - if (git_str_oom(&scheme) || - git_str_oom(&host) || - git_str_oom(&port) || - git_str_oom(&path) || - git_str_oom(&query) || - git_str_oom(&username) || - git_str_oom(&password)) - return -1; - - url->scheme = git_str_detach(&scheme); - url->host = git_str_detach(&host); - url->port = git_str_detach(&port); - url->path = git_str_detach(&path); - url->query = git_str_detach(&query); - url->username = git_str_detach(&username); - url->password = git_str_detach(&password); - - error = 0; - -done: - git_str_dispose(&scheme); - git_str_dispose(&host); - git_str_dispose(&port); - git_str_dispose(&path); - git_str_dispose(&query); - git_str_dispose(&username); - git_str_dispose(&password); - return error; -} - -static int scp_invalid(const char *message) -{ - git_error_set(GIT_ERROR_NET, "invalid scp-style path: %s", message); - return GIT_EINVALIDSPEC; -} - -static bool is_ipv6(const char *str) -{ - const char *c; - size_t colons = 0; - - if (*str++ != '[') - return false; - - for (c = str; *c; c++) { - if (*c == ':') - colons++; - - if (*c == ']') - return (colons > 1); - - if (*c != ':' && - (*c < '0' || *c > '9') && - (*c < 'a' || *c > 'f') && - (*c < 'A' || *c > 'F')) - return false; - } - - return false; -} - -static bool has_at(const char *str) -{ - const char *c; - - for (c = str; *c; c++) { - if (*c == '@') - return true; - - if (*c == ':') - break; - } - - return false; -} - -int git_net_url_parse_scp(git_net_url *url, const char *given) -{ - const char *default_port = default_port_for_scheme("ssh"); - const char *c, *user, *host, *port, *path = NULL; - size_t user_len = 0, host_len = 0, port_len = 0; - unsigned short bracket = 0; - - enum { - NONE, - USER, - HOST_START, HOST, HOST_END, - IPV6, IPV6_END, - PORT_START, PORT, PORT_END, - PATH_START - } state = NONE; - - memset(url, 0, sizeof(git_net_url)); - - for (c = given; *c && !path; c++) { - switch (state) { - case NONE: - switch (*c) { - case '@': - return scp_invalid("unexpected '@'"); - case ':': - return scp_invalid("unexpected ':'"); - case '[': - if (is_ipv6(c)) { - state = IPV6; - host = c; - } else if (bracket++ > 1) { - return scp_invalid("unexpected '['"); - } - break; - default: - if (has_at(c)) { - state = USER; - user = c; - } else { - state = HOST; - host = c; - } - break; - } - break; - - case USER: - if (*c == '@') { - user_len = (c - user); - state = HOST_START; - } - break; - - case HOST_START: - state = (*c == '[') ? IPV6 : HOST; - host = c; - break; - - case HOST: - if (*c == ':') { - host_len = (c - host); - state = bracket ? PORT_START : PATH_START; - } else if (*c == ']') { - if (bracket-- == 0) - return scp_invalid("unexpected ']'"); - - host_len = (c - host); - state = HOST_END; - } - break; - - case HOST_END: - if (*c != ':') - return scp_invalid("unexpected character after hostname"); - state = PATH_START; - break; - - case IPV6: - if (*c == ']') - state = IPV6_END; - break; - - case IPV6_END: - if (*c != ':') - return scp_invalid("unexpected character after ipv6 address"); - - host_len = (c - host); - state = bracket ? PORT_START : PATH_START; - break; - - case PORT_START: - port = c; - state = PORT; - break; - - case PORT: - if (*c == ']') { - if (bracket-- == 0) - return scp_invalid("unexpected ']'"); - - port_len = c - port; - state = PORT_END; - } - break; - - case PORT_END: - if (*c != ':') - return scp_invalid("unexpected character after ipv6 address"); - - state = PATH_START; - break; - - case PATH_START: - path = c; - break; - - default: - GIT_ASSERT("unhandled state"); - } - } - - if (!path) - return scp_invalid("path is required"); - - GIT_ERROR_CHECK_ALLOC(url->scheme = git__strdup("ssh")); - - if (user_len) - GIT_ERROR_CHECK_ALLOC(url->username = git__strndup(user, user_len)); - - GIT_ASSERT(host_len); - GIT_ERROR_CHECK_ALLOC(url->host = git__strndup(host, host_len)); - - if (port_len) - GIT_ERROR_CHECK_ALLOC(url->port = git__strndup(port, port_len)); - else - GIT_ERROR_CHECK_ALLOC(url->port = git__strdup(default_port)); - - GIT_ASSERT(path); - GIT_ERROR_CHECK_ALLOC(url->path = git__strdup(path)); - - return 0; -} - -int git_net_url_joinpath( - git_net_url *out, - git_net_url *one, - const char *two) -{ - git_str path = GIT_STR_INIT; - const char *query; - size_t one_len, two_len; - - git_net_url_dispose(out); - - if ((query = strchr(two, '?')) != NULL) { - two_len = query - two; - - if (*(++query) != '\0') { - out->query = git__strdup(query); - GIT_ERROR_CHECK_ALLOC(out->query); - } - } else { - two_len = strlen(two); - } - - /* Strip all trailing `/`s from the first path */ - one_len = one->path ? strlen(one->path) : 0; - while (one_len && one->path[one_len - 1] == '/') - one_len--; - - /* Strip all leading `/`s from the second path */ - while (*two == '/') { - two++; - two_len--; - } - - git_str_put(&path, one->path, one_len); - git_str_putc(&path, '/'); - git_str_put(&path, two, two_len); - - if (git_str_oom(&path)) - return -1; - - out->path = git_str_detach(&path); - - if (one->scheme) { - out->scheme = git__strdup(one->scheme); - GIT_ERROR_CHECK_ALLOC(out->scheme); - } - - if (one->host) { - out->host = git__strdup(one->host); - GIT_ERROR_CHECK_ALLOC(out->host); - } - - if (one->port) { - out->port = git__strdup(one->port); - GIT_ERROR_CHECK_ALLOC(out->port); - } - - if (one->username) { - out->username = git__strdup(one->username); - GIT_ERROR_CHECK_ALLOC(out->username); - } - - if (one->password) { - out->password = git__strdup(one->password); - GIT_ERROR_CHECK_ALLOC(out->password); - } - - return 0; -} - -/* - * Some servers strip the query parameters from the Location header - * when sending a redirect. Others leave it in place. - * Check for both, starting with the stripped case first, - * since it appears to be more common. - */ -static void remove_service_suffix( - git_net_url *url, - const char *service_suffix) -{ - const char *service_query = strchr(service_suffix, '?'); - size_t full_suffix_len = strlen(service_suffix); - size_t suffix_len = service_query ? - (size_t)(service_query - service_suffix) : full_suffix_len; - size_t path_len = strlen(url->path); - ssize_t truncate = -1; - - /* - * Check for a redirect without query parameters, - * like "/newloc/info/refs"' - */ - if (suffix_len && path_len >= suffix_len) { - size_t suffix_offset = path_len - suffix_len; - - if (git__strncmp(url->path + suffix_offset, service_suffix, suffix_len) == 0 && - (!service_query || git__strcmp(url->query, service_query + 1) == 0)) { - truncate = suffix_offset; - } - } - - /* - * If we haven't already found where to truncate to remove the - * suffix, check for a redirect with query parameters, like - * "/newloc/info/refs?service=git-upload-pack" - */ - if (truncate < 0 && git__suffixcmp(url->path, service_suffix) == 0) - truncate = path_len - full_suffix_len; - - /* Ensure we leave a minimum of '/' as the path */ - if (truncate == 0) - truncate++; - - if (truncate > 0) { - url->path[truncate] = '\0'; - - git__free(url->query); - url->query = NULL; - } -} - -int git_net_url_apply_redirect( - git_net_url *url, - const char *redirect_location, - bool allow_offsite, - const char *service_suffix) -{ - git_net_url tmp = GIT_NET_URL_INIT; - int error = 0; - - GIT_ASSERT(url); - GIT_ASSERT(redirect_location); - - if (redirect_location[0] == '/') { - git__free(url->path); - - if ((url->path = git__strdup(redirect_location)) == NULL) { - error = -1; - goto done; - } - } else { - git_net_url *original = url; - - if ((error = git_net_url_parse(&tmp, redirect_location)) < 0) - goto done; - - /* Validate that this is a legal redirection */ - - if (original->scheme && - strcmp(original->scheme, tmp.scheme) != 0 && - strcmp(tmp.scheme, "https") != 0) { - git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", - original->scheme, tmp.scheme); - - error = -1; - goto done; - } - - if (original->host && - !allow_offsite && - git__strcasecmp(original->host, tmp.host) != 0) { - git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", - original->host, tmp.host); - - error = -1; - goto done; - } - - git_net_url_swap(url, &tmp); - } - - /* Remove the service suffix if it was given to us */ - if (service_suffix) - remove_service_suffix(url, service_suffix); - -done: - git_net_url_dispose(&tmp); - return error; -} - -bool git_net_url_valid(git_net_url *url) -{ - return (url->host && url->port && url->path); -} - -bool git_net_url_is_default_port(git_net_url *url) -{ - const char *default_port; - - if ((default_port = default_port_for_scheme(url->scheme)) != NULL) - return (strcmp(url->port, default_port) == 0); - else - return false; -} - -bool git_net_url_is_ipv6(git_net_url *url) -{ - return (strchr(url->host, ':') != NULL); -} - -void git_net_url_swap(git_net_url *a, git_net_url *b) -{ - git_net_url tmp = GIT_NET_URL_INIT; - - memcpy(&tmp, a, sizeof(git_net_url)); - memcpy(a, b, sizeof(git_net_url)); - memcpy(b, &tmp, sizeof(git_net_url)); -} - -int git_net_url_fmt(git_str *buf, git_net_url *url) -{ - GIT_ASSERT_ARG(url); - GIT_ASSERT_ARG(url->scheme); - GIT_ASSERT_ARG(url->host); - - git_str_puts(buf, url->scheme); - git_str_puts(buf, "://"); - - if (url->username) { - git_str_puts(buf, url->username); - - if (url->password) { - git_str_puts(buf, ":"); - git_str_puts(buf, url->password); - } - - git_str_putc(buf, '@'); - } - - git_str_puts(buf, url->host); - - if (url->port && !git_net_url_is_default_port(url)) { - git_str_putc(buf, ':'); - git_str_puts(buf, url->port); - } - - git_str_puts(buf, url->path ? url->path : "/"); - - if (url->query) { - git_str_putc(buf, '?'); - git_str_puts(buf, url->query); - } - - return git_str_oom(buf) ? -1 : 0; -} - -int git_net_url_fmt_path(git_str *buf, git_net_url *url) -{ - git_str_puts(buf, url->path ? url->path : "/"); - - if (url->query) { - git_str_putc(buf, '?'); - git_str_puts(buf, url->query); - } - - return git_str_oom(buf) ? -1 : 0; -} - -static bool matches_pattern( - git_net_url *url, - const char *pattern, - size_t pattern_len) -{ - const char *domain, *port = NULL, *colon; - size_t host_len, domain_len, port_len = 0, wildcard = 0; - - GIT_UNUSED(url); - GIT_UNUSED(pattern); - - if (!pattern_len) - return false; - else if (pattern_len == 1 && pattern[0] == '*') - return true; - else if (pattern_len > 1 && pattern[0] == '*' && pattern[1] == '.') - wildcard = 2; - else if (pattern[0] == '.') - wildcard = 1; - - domain = pattern + wildcard; - domain_len = pattern_len - wildcard; - - if ((colon = memchr(domain, ':', domain_len)) != NULL) { - domain_len = colon - domain; - port = colon + 1; - port_len = pattern_len - wildcard - domain_len - 1; - } - - /* A pattern's port *must* match if it's specified */ - if (port_len && git__strlcmp(url->port, port, port_len) != 0) - return false; - - /* No wildcard? Host must match exactly. */ - if (!wildcard) - return !git__strlcmp(url->host, domain, domain_len); - - /* Wildcard: ensure there's (at least) a suffix match */ - if ((host_len = strlen(url->host)) < domain_len || - memcmp(url->host + (host_len - domain_len), domain, domain_len)) - return false; - - /* The pattern is *.domain and the host is simply domain */ - if (host_len == domain_len) - return true; - - /* The pattern is *.domain and the host is foo.domain */ - return (url->host[host_len - domain_len - 1] == '.'); -} - -bool git_net_url_matches_pattern(git_net_url *url, const char *pattern) -{ - return matches_pattern(url, pattern, strlen(pattern)); -} - -bool git_net_url_matches_pattern_list( - git_net_url *url, - const char *pattern_list) -{ - const char *pattern, *pattern_end, *sep; - - for (pattern = pattern_list; - pattern && *pattern; - pattern = sep ? sep + 1 : NULL) { - sep = strchr(pattern, ','); - pattern_end = sep ? sep : strchr(pattern, '\0'); - - if (matches_pattern(url, pattern, (pattern_end - pattern))) - return true; - } - - return false; -} - -void git_net_url_dispose(git_net_url *url) -{ - if (url->username) - git__memzero(url->username, strlen(url->username)); - - if (url->password) - git__memzero(url->password, strlen(url->password)); - - git__free(url->scheme); url->scheme = NULL; - git__free(url->host); url->host = NULL; - git__free(url->port); url->port = NULL; - git__free(url->path); url->path = NULL; - git__free(url->query); url->query = NULL; - git__free(url->username); url->username = NULL; - git__free(url->password); url->password = NULL; -} diff --git a/src/net.h b/src/net.h deleted file mode 100644 index 499315e6c..000000000 --- a/src/net.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_net_h__ -#define INCLUDE_net_h__ - -#include "common.h" - -typedef struct git_net_url { - char *scheme; - char *host; - char *port; - char *path; - char *query; - char *username; - char *password; -} git_net_url; - -#define GIT_NET_URL_INIT { NULL } - -/** Is a given string a url? */ -extern bool git_net_str_is_url(const char *str); - -/** Duplicate a URL */ -extern int git_net_url_dup(git_net_url *out, git_net_url *in); - -/** Parses a string containing a URL into a structure. */ -extern int git_net_url_parse(git_net_url *url, const char *str); - -/** Parses a string containing an SCP style path into a URL structure. */ -extern int git_net_url_parse_scp(git_net_url *url, const char *str); - -/** Appends a path and/or query string to the given URL */ -extern int git_net_url_joinpath( - git_net_url *out, - git_net_url *in, - const char *path); - -/** Ensures that a URL is minimally valid (contains a host, port and path) */ -extern bool git_net_url_valid(git_net_url *url); - -/** Returns true if the URL is on the default port. */ -extern bool git_net_url_is_default_port(git_net_url *url); - -/** Returns true if the host portion of the URL is an ipv6 address. */ -extern bool git_net_url_is_ipv6(git_net_url *url); - -/* Applies a redirect to the URL with a git-aware service suffix. */ -extern int git_net_url_apply_redirect( - git_net_url *url, - const char *redirect_location, - bool allow_offsite, - const char *service_suffix); - -/** Swaps the contents of one URL for another. */ -extern void git_net_url_swap(git_net_url *a, git_net_url *b); - -/** Places the URL into the given buffer. */ -extern int git_net_url_fmt(git_str *out, git_net_url *url); - -/** Place the path and query string into the given buffer. */ -extern int git_net_url_fmt_path(git_str *buf, git_net_url *url); - -/** Determines if the url matches given pattern or pattern list */ -extern bool git_net_url_matches_pattern( - git_net_url *url, - const char *pattern); -extern bool git_net_url_matches_pattern_list( - git_net_url *url, - const char *pattern_list); - -/** Disposes the contents of the structure. */ -extern void git_net_url_dispose(git_net_url *url); - -#endif diff --git a/src/netops.c b/src/netops.c deleted file mode 100644 index 0a27365b8..000000000 --- a/src/netops.c +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "netops.h" - -#include -#include "git2/errors.h" - -#include "posix.h" -#include "str.h" -#include "http_parser.h" -#include "runtime.h" - -int gitno_recv(gitno_buffer *buf) -{ - return buf->recv(buf); -} - -void gitno_buffer_setup_callback( - gitno_buffer *buf, - char *data, - size_t len, - int (*recv)(gitno_buffer *buf), void *cb_data) -{ - memset(data, 0x0, len); - buf->data = data; - buf->len = len; - buf->offset = 0; - buf->recv = recv; - buf->cb_data = cb_data; -} - -static int recv_stream(gitno_buffer *buf) -{ - git_stream *io = (git_stream *) buf->cb_data; - size_t readlen = buf->len - buf->offset; - ssize_t ret; - - readlen = min(readlen, INT_MAX); - - ret = git_stream_read(io, buf->data + buf->offset, (int)readlen); - if (ret < 0) - return -1; - - buf->offset += ret; - return (int)ret; -} - -void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len) -{ - memset(data, 0x0, len); - buf->data = data; - buf->len = len; - buf->offset = 0; - buf->recv = recv_stream; - buf->cb_data = st; -} - -/* Consume up to ptr and move the rest of the buffer to the beginning */ -int gitno_consume(gitno_buffer *buf, const char *ptr) -{ - size_t consumed; - - GIT_ASSERT(ptr - buf->data >= 0); - GIT_ASSERT(ptr - buf->data <= (int) buf->len); - - consumed = ptr - buf->data; - - memmove(buf->data, ptr, buf->offset - consumed); - memset(buf->data + buf->offset, 0x0, buf->len - buf->offset); - buf->offset -= consumed; - - return 0; -} - -/* Consume const bytes and move the rest of the buffer to the beginning */ -void gitno_consume_n(gitno_buffer *buf, size_t cons) -{ - memmove(buf->data, buf->data + cons, buf->len - buf->offset); - memset(buf->data + cons, 0x0, buf->len - buf->offset); - buf->offset -= cons; -} - -/* Match host names according to RFC 2818 rules */ -int gitno__match_host(const char *pattern, const char *host) -{ - for (;;) { - char c = git__tolower(*pattern++); - - if (c == '\0') - return *host ? -1 : 0; - - if (c == '*') { - c = *pattern; - /* '*' at the end matches everything left */ - if (c == '\0') - return 0; - - /* - * We've found a pattern, so move towards the next matching - * char. The '.' is handled specially because wildcards aren't - * allowed to cross subdomains. - */ - - while(*host) { - char h = git__tolower(*host); - if (c == h) - return gitno__match_host(pattern, host++); - if (h == '.') - return gitno__match_host(pattern, host); - host++; - } - return -1; - } - - if (c != git__tolower(*host++)) - return -1; - } - - return -1; -} diff --git a/src/netops.h b/src/netops.h deleted file mode 100644 index 56f968534..000000000 --- a/src/netops.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_netops_h__ -#define INCLUDE_netops_h__ - -#include "common.h" - -#include "posix.h" -#include "stream.h" -#include "net.h" - -#ifdef GIT_OPENSSL -# include "streams/openssl.h" -#endif - -typedef struct gitno_ssl { -#ifdef GIT_OPENSSL - SSL *ssl; -#else - size_t dummy; -#endif -} gitno_ssl; - -/* Represents a socket that may or may not be using SSL */ -typedef struct gitno_socket { - GIT_SOCKET socket; - gitno_ssl ssl; -} gitno_socket; - -typedef struct gitno_buffer { - char *data; - size_t len; - size_t offset; - int (*recv)(struct gitno_buffer *buffer); - void *cb_data; -} gitno_buffer; - -/* Flags to gitno_connect */ -enum { - /* Attempt to create an SSL connection. */ - GITNO_CONNECT_SSL = 1 -}; - -/** - * Check if the name in a cert matches the wanted hostname - * - * Check if a pattern from a certificate matches the hostname we - * wanted to connect to according to RFC2818 rules (which specifies - * HTTP over TLS). Mainly, an asterisk matches anything, but is - * limited to a single url component. - * - * Note that this does not set an error message. It expects the user - * to provide the message for the user. - */ -int gitno__match_host(const char *pattern, const char *host); - -void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len); -void gitno_buffer_setup_callback(gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data); -int gitno_recv(gitno_buffer *buf); - -int gitno_consume(gitno_buffer *buf, const char *ptr); -void gitno_consume_n(gitno_buffer *buf, size_t cons); - -#endif diff --git a/src/notes.c b/src/notes.c deleted file mode 100644 index d1a2b0f64..000000000 --- a/src/notes.c +++ /dev/null @@ -1,809 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "notes.h" - -#include "buf.h" -#include "refs.h" -#include "config.h" -#include "iterator.h" -#include "signature.h" -#include "blob.h" - -static int note_error_notfound(void) -{ - git_error_set(GIT_ERROR_INVALID, "note could not be found"); - return GIT_ENOTFOUND; -} - -static int find_subtree_in_current_level( - git_tree **out, - git_repository *repo, - git_tree *parent, - const char *annotated_object_sha, - int fanout) -{ - size_t i; - const git_tree_entry *entry; - - *out = NULL; - - if (parent == NULL) - return note_error_notfound(); - - for (i = 0; i < git_tree_entrycount(parent); i++) { - entry = git_tree_entry_byindex(parent, i); - - if (!git__ishex(git_tree_entry_name(entry))) - continue; - - if (S_ISDIR(git_tree_entry_filemode(entry)) - && strlen(git_tree_entry_name(entry)) == 2 - && !strncmp(git_tree_entry_name(entry), annotated_object_sha + fanout, 2)) - return git_tree_lookup(out, repo, git_tree_entry_id(entry)); - - /* Not a DIR, so do we have an already existing blob? */ - if (!strcmp(git_tree_entry_name(entry), annotated_object_sha + fanout)) - return GIT_EEXISTS; - } - - return note_error_notfound(); -} - -static int find_subtree_r(git_tree **out, git_tree *root, - git_repository *repo, const char *target, int *fanout) -{ - int error; - git_tree *subtree = NULL; - - *out = NULL; - - error = find_subtree_in_current_level(&subtree, repo, root, target, *fanout); - if (error == GIT_EEXISTS) - return git_tree_lookup(out, repo, git_tree_id(root)); - - if (error < 0) - return error; - - *fanout += 2; - error = find_subtree_r(out, subtree, repo, target, fanout); - git_tree_free(subtree); - - return error; -} - -static int find_blob(git_oid *blob, git_tree *tree, const char *target) -{ - size_t i; - const git_tree_entry *entry; - - for (i=0; iid, note_oid); - - if (git_signature_dup(¬e->author, git_commit_author(commit)) < 0 || - git_signature_dup(¬e->committer, git_commit_committer(commit)) < 0) - return -1; - - blobsize = git_blob_rawsize(blob); - GIT_ERROR_CHECK_BLOBSIZE(blobsize); - - note->message = git__strndup(git_blob_rawcontent(blob), (size_t)blobsize); - GIT_ERROR_CHECK_ALLOC(note->message); - - *out = note; - return 0; -} - -static int note_lookup( - git_note **out, - git_repository *repo, - git_commit *commit, - git_tree *tree, - const char *target) -{ - int error, fanout = 0; - git_oid oid; - git_blob *blob = NULL; - git_note *note = NULL; - git_tree *subtree = NULL; - - if ((error = find_subtree_r(&subtree, tree, repo, target, &fanout)) < 0) - goto cleanup; - - if ((error = find_blob(&oid, subtree, target + fanout)) < 0) - goto cleanup; - - if ((error = git_blob_lookup(&blob, repo, &oid)) < 0) - goto cleanup; - - if ((error = note_new(¬e, &oid, commit, blob)) < 0) - goto cleanup; - - *out = note; - -cleanup: - git_tree_free(subtree); - git_blob_free(blob); - return error; -} - -static int note_remove( - git_oid *notes_commit_out, - git_repository *repo, - const git_signature *author, const git_signature *committer, - const char *notes_ref, git_tree *tree, - const char *target, git_commit **parents) -{ - int error; - git_tree *tree_after_removal = NULL; - git_oid oid; - - if ((error = manipulate_note_in_tree_r( - &tree_after_removal, repo, tree, NULL, target, 0, - remove_note_in_tree_eexists_cb, remove_note_in_tree_enotfound_cb)) < 0) - goto cleanup; - - error = git_commit_create(&oid, repo, notes_ref, author, committer, - NULL, GIT_NOTES_DEFAULT_MSG_RM, - tree_after_removal, - *parents == NULL ? 0 : 1, - (const git_commit **) parents); - - if (error < 0) - goto cleanup; - - if (notes_commit_out) - git_oid_cpy(notes_commit_out, &oid); - -cleanup: - git_tree_free(tree_after_removal); - return error; -} - -static int note_get_default_ref(git_str *out, git_repository *repo) -{ - git_config *cfg; - int error; - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - return error; - - error = git_config__get_string_buf(out, cfg, "core.notesref"); - - if (error == GIT_ENOTFOUND) - error = git_str_puts(out, GIT_NOTES_DEFAULT_REF); - - return error; -} - -static int normalize_namespace(git_str *out, git_repository *repo, const char *notes_ref) -{ - if (notes_ref) - return git_str_puts(out, notes_ref); - - return note_get_default_ref(out, repo); -} - -static int retrieve_note_commit( - git_commit **commit_out, - git_str *notes_ref_out, - git_repository *repo, - const char *notes_ref) -{ - int error; - git_oid oid; - - if ((error = normalize_namespace(notes_ref_out, repo, notes_ref)) < 0) - return error; - - if ((error = git_reference_name_to_id(&oid, repo, notes_ref_out->ptr)) < 0) - return error; - - if (git_commit_lookup(commit_out, repo, &oid) < 0) - return error; - - return 0; -} - -int git_note_commit_read( - git_note **out, - git_repository *repo, - git_commit *notes_commit, - const git_oid *oid) -{ - int error; - git_tree *tree = NULL; - char target[GIT_OID_HEXSZ + 1]; - - git_oid_tostr(target, sizeof(target), oid); - - if ((error = git_commit_tree(&tree, notes_commit)) < 0) - goto cleanup; - - error = note_lookup(out, repo, notes_commit, tree, target); - -cleanup: - git_tree_free(tree); - return error; -} - -int git_note_read(git_note **out, git_repository *repo, - const char *notes_ref_in, const git_oid *oid) -{ - int error; - git_str notes_ref = GIT_STR_INIT; - git_commit *commit = NULL; - - error = retrieve_note_commit(&commit, ¬es_ref, repo, notes_ref_in); - - if (error < 0) - goto cleanup; - - error = git_note_commit_read(out, repo, commit, oid); - -cleanup: - git_str_dispose(¬es_ref); - git_commit_free(commit); - return error; -} - -int git_note_commit_create( - git_oid *notes_commit_out, - git_oid *notes_blob_out, - git_repository *repo, - git_commit *parent, - const git_signature *author, - const git_signature *committer, - const git_oid *oid, - const char *note, - int allow_note_overwrite) -{ - int error; - git_tree *tree = NULL; - char target[GIT_OID_HEXSZ + 1]; - - git_oid_tostr(target, sizeof(target), oid); - - if (parent != NULL && (error = git_commit_tree(&tree, parent)) < 0) - goto cleanup; - - error = note_write(notes_commit_out, notes_blob_out, repo, author, - committer, NULL, note, tree, target, &parent, allow_note_overwrite); - - if (error < 0) - goto cleanup; - -cleanup: - git_tree_free(tree); - return error; -} - -int git_note_create( - git_oid *out, - git_repository *repo, - const char *notes_ref_in, - const git_signature *author, - const git_signature *committer, - const git_oid *oid, - const char *note, - int allow_note_overwrite) -{ - int error; - git_str notes_ref = GIT_STR_INIT; - git_commit *existing_notes_commit = NULL; - git_reference *ref = NULL; - git_oid notes_blob_oid, notes_commit_oid; - - error = retrieve_note_commit(&existing_notes_commit, ¬es_ref, - repo, notes_ref_in); - - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - error = git_note_commit_create(¬es_commit_oid, - ¬es_blob_oid, - repo, existing_notes_commit, author, - committer, oid, note, - allow_note_overwrite); - if (error < 0) - goto cleanup; - - error = git_reference_create(&ref, repo, notes_ref.ptr, - ¬es_commit_oid, 1, NULL); - - if (out != NULL) - git_oid_cpy(out, ¬es_blob_oid); - -cleanup: - git_str_dispose(¬es_ref); - git_commit_free(existing_notes_commit); - git_reference_free(ref); - return error; -} - -int git_note_commit_remove( - git_oid *notes_commit_out, - git_repository *repo, - git_commit *notes_commit, - const git_signature *author, - const git_signature *committer, - const git_oid *oid) -{ - int error; - git_tree *tree = NULL; - char target[GIT_OID_HEXSZ + 1]; - - git_oid_tostr(target, sizeof(target), oid); - - if ((error = git_commit_tree(&tree, notes_commit)) < 0) - goto cleanup; - - error = note_remove(notes_commit_out, - repo, author, committer, NULL, tree, target, ¬es_commit); - -cleanup: - git_tree_free(tree); - return error; -} - -int git_note_remove(git_repository *repo, const char *notes_ref_in, - const git_signature *author, const git_signature *committer, - const git_oid *oid) -{ - int error; - git_str notes_ref_target = GIT_STR_INIT; - git_commit *existing_notes_commit = NULL; - git_oid new_notes_commit; - git_reference *notes_ref = NULL; - - error = retrieve_note_commit(&existing_notes_commit, ¬es_ref_target, - repo, notes_ref_in); - - if (error < 0) - goto cleanup; - - error = git_note_commit_remove(&new_notes_commit, repo, - existing_notes_commit, author, committer, oid); - if (error < 0) - goto cleanup; - - error = git_reference_create(¬es_ref, repo, notes_ref_target.ptr, - &new_notes_commit, 1, NULL); - -cleanup: - git_str_dispose(¬es_ref_target); - git_reference_free(notes_ref); - git_commit_free(existing_notes_commit); - return error; -} - -int git_note_default_ref(git_buf *out, git_repository *repo) -{ - GIT_BUF_WRAP_PRIVATE(out, note_get_default_ref, repo); -} - -const git_signature *git_note_committer(const git_note *note) -{ - GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); - return note->committer; -} - -const git_signature *git_note_author(const git_note *note) -{ - GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); - return note->author; -} - -const char *git_note_message(const git_note *note) -{ - GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); - return note->message; -} - -const git_oid *git_note_id(const git_note *note) -{ - GIT_ASSERT_ARG_WITH_RETVAL(note, NULL); - return ¬e->id; -} - -void git_note_free(git_note *note) -{ - if (note == NULL) - return; - - git_signature_free(note->committer); - git_signature_free(note->author); - git__free(note->message); - git__free(note); -} - -static int process_entry_path( - const char *entry_path, - git_oid *annotated_object_id) -{ - int error = 0; - size_t i = 0, j = 0, len; - git_str buf = GIT_STR_INIT; - - if ((error = git_str_puts(&buf, entry_path)) < 0) - goto cleanup; - - len = git_str_len(&buf); - - while (i < len) { - if (buf.ptr[i] == '/') { - i++; - continue; - } - - if (git__fromhex(buf.ptr[i]) < 0) { - /* This is not a note entry */ - goto cleanup; - } - - if (i != j) - buf.ptr[j] = buf.ptr[i]; - - i++; - j++; - } - - buf.ptr[j] = '\0'; - buf.size = j; - - if (j != GIT_OID_HEXSZ) { - /* This is not a note entry */ - goto cleanup; - } - - error = git_oid_fromstr(annotated_object_id, buf.ptr); - -cleanup: - git_str_dispose(&buf); - return error; -} - -int git_note_foreach( - git_repository *repo, - const char *notes_ref, - git_note_foreach_cb note_cb, - void *payload) -{ - int error; - git_note_iterator *iter = NULL; - git_oid note_id, annotated_id; - - if ((error = git_note_iterator_new(&iter, repo, notes_ref)) < 0) - return error; - - while (!(error = git_note_next(¬e_id, &annotated_id, iter))) { - if ((error = note_cb(¬e_id, &annotated_id, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - if (error == GIT_ITEROVER) - error = 0; - - git_note_iterator_free(iter); - return error; -} - -void git_note_iterator_free(git_note_iterator *it) -{ - if (it == NULL) - return; - - git_iterator_free(it); -} - -int git_note_commit_iterator_new( - git_note_iterator **it, - git_commit *notes_commit) -{ - int error; - git_tree *tree; - - if ((error = git_commit_tree(&tree, notes_commit)) < 0) - goto cleanup; - - if ((error = git_iterator_for_tree(it, tree, NULL)) < 0) - git_iterator_free(*it); - -cleanup: - git_tree_free(tree); - - return error; -} - -int git_note_iterator_new( - git_note_iterator **it, - git_repository *repo, - const char *notes_ref_in) -{ - int error; - git_commit *commit = NULL; - git_str notes_ref = GIT_STR_INIT; - - error = retrieve_note_commit(&commit, ¬es_ref, repo, notes_ref_in); - if (error < 0) - goto cleanup; - - error = git_note_commit_iterator_new(it, commit); - -cleanup: - git_str_dispose(¬es_ref); - git_commit_free(commit); - - return error; -} - -int git_note_next( - git_oid *note_id, - git_oid *annotated_id, - git_note_iterator *it) -{ - int error; - const git_index_entry *item; - - if ((error = git_iterator_current(&item, it)) < 0) - return error; - - git_oid_cpy(note_id, &item->id); - - if ((error = process_entry_path(item->path, annotated_id)) < 0) - return error; - - if ((error = git_iterator_advance(NULL, it)) < 0 && error != GIT_ITEROVER) - return error; - - return 0; -} diff --git a/src/notes.h b/src/notes.h deleted file mode 100644 index 2168e4595..000000000 --- a/src/notes.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_note_h__ -#define INCLUDE_note_h__ - -#include "common.h" - -#include "git2/oid.h" -#include "git2/types.h" - -#define GIT_NOTES_DEFAULT_REF "refs/notes/commits" - -#define GIT_NOTES_DEFAULT_MSG_ADD \ - "Notes added by 'git_note_create' from libgit2" - -#define GIT_NOTES_DEFAULT_MSG_RM \ - "Notes removed by 'git_note_remove' from libgit2" - -struct git_note { - git_oid id; - - git_signature *author; - git_signature *committer; - - char *message; -}; - -#endif diff --git a/src/object.c b/src/object.c deleted file mode 100644 index 7bc256fce..000000000 --- a/src/object.c +++ /dev/null @@ -1,601 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "object.h" - -#include "git2/object.h" - -#include "repository.h" - -#include "buf.h" -#include "commit.h" -#include "hash.h" -#include "tree.h" -#include "blob.h" -#include "oid.h" -#include "tag.h" - -bool git_object__strict_input_validation = true; - -extern int git_odb_hash(git_oid *out, const void *data, size_t len, git_object_t type); -size_t git_object__size(git_object_t type); - -typedef struct { - const char *str; /* type name string */ - size_t size; /* size in bytes of the object structure */ - - int (*parse)(void *self, git_odb_object *obj); - int (*parse_raw)(void *self, const char *data, size_t size); - void (*free)(void *self); -} git_object_def; - -static git_object_def git_objects_table[] = { - /* 0 = GIT_OBJECT__EXT1 */ - { "", 0, NULL, NULL, NULL }, - - /* 1 = GIT_OBJECT_COMMIT */ - { "commit", sizeof(git_commit), git_commit__parse, git_commit__parse_raw, git_commit__free }, - - /* 2 = GIT_OBJECT_TREE */ - { "tree", sizeof(git_tree), git_tree__parse, git_tree__parse_raw, git_tree__free }, - - /* 3 = GIT_OBJECT_BLOB */ - { "blob", sizeof(git_blob), git_blob__parse, git_blob__parse_raw, git_blob__free }, - - /* 4 = GIT_OBJECT_TAG */ - { "tag", sizeof(git_tag), git_tag__parse, git_tag__parse_raw, git_tag__free }, - - /* 5 = GIT_OBJECT__EXT2 */ - { "", 0, NULL, NULL, NULL }, - /* 6 = GIT_OBJECT_OFS_DELTA */ - { "OFS_DELTA", 0, NULL, NULL, NULL }, - /* 7 = GIT_OBJECT_REF_DELTA */ - { "REF_DELTA", 0, NULL, NULL, NULL }, -}; - -int git_object__from_raw( - git_object **object_out, - const char *data, - size_t size, - git_object_t type) -{ - git_object_def *def; - git_object *object; - size_t object_size; - int error; - - GIT_ASSERT_ARG(object_out); - *object_out = NULL; - - /* Validate type match */ - if (type != GIT_OBJECT_BLOB && type != GIT_OBJECT_TREE && type != GIT_OBJECT_COMMIT && type != GIT_OBJECT_TAG) { - git_error_set(GIT_ERROR_INVALID, "the requested type is invalid"); - return GIT_ENOTFOUND; - } - - if ((object_size = git_object__size(type)) == 0) { - git_error_set(GIT_ERROR_INVALID, "the requested type is invalid"); - return GIT_ENOTFOUND; - } - - /* Allocate and initialize base object */ - object = git__calloc(1, object_size); - GIT_ERROR_CHECK_ALLOC(object); - object->cached.flags = GIT_CACHE_STORE_PARSED; - object->cached.type = type; - if ((error = git_odb_hash(&object->cached.oid, data, size, type)) < 0) - return error; - - /* Parse raw object data */ - def = &git_objects_table[type]; - GIT_ASSERT(def->free && def->parse_raw); - - if ((error = def->parse_raw(object, data, size)) < 0) { - def->free(object); - return error; - } - - git_cached_obj_incref(object); - *object_out = object; - - return 0; -} - -int git_object__from_odb_object( - git_object **object_out, - git_repository *repo, - git_odb_object *odb_obj, - git_object_t type) -{ - int error; - size_t object_size; - git_object_def *def; - git_object *object = NULL; - - GIT_ASSERT_ARG(object_out); - *object_out = NULL; - - /* Validate type match */ - if (type != GIT_OBJECT_ANY && type != odb_obj->cached.type) { - git_error_set(GIT_ERROR_INVALID, - "the requested type does not match the type in the ODB"); - return GIT_ENOTFOUND; - } - - if ((object_size = git_object__size(odb_obj->cached.type)) == 0) { - git_error_set(GIT_ERROR_INVALID, "the requested type is invalid"); - return GIT_ENOTFOUND; - } - - /* Allocate and initialize base object */ - object = git__calloc(1, object_size); - GIT_ERROR_CHECK_ALLOC(object); - - git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid); - object->cached.type = odb_obj->cached.type; - object->cached.size = odb_obj->cached.size; - object->repo = repo; - - /* Parse raw object data */ - def = &git_objects_table[odb_obj->cached.type]; - GIT_ASSERT(def->free && def->parse); - - if ((error = def->parse(object, odb_obj)) < 0) { - /* - * parse returns EINVALID on invalid data; downgrade - * that to a normal -1 error code. - */ - def->free(object); - return -1; - } - - *object_out = git_cache_store_parsed(&repo->objects, object); - return 0; -} - -void git_object__free(void *obj) -{ - git_object_t type = ((git_object *)obj)->cached.type; - - if (type < 0 || ((size_t)type) >= ARRAY_SIZE(git_objects_table) || - !git_objects_table[type].free) - git__free(obj); - else - git_objects_table[type].free(obj); -} - -int git_object_lookup_prefix( - git_object **object_out, - git_repository *repo, - const git_oid *id, - size_t len, - git_object_t type) -{ - git_object *object = NULL; - git_odb *odb = NULL; - git_odb_object *odb_obj = NULL; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(object_out); - GIT_ASSERT_ARG(id); - - if (len < GIT_OID_MINPREFIXLEN) { - git_error_set(GIT_ERROR_OBJECT, "ambiguous lookup - OID prefix is too short"); - return GIT_EAMBIGUOUS; - } - - error = git_repository_odb__weakptr(&odb, repo); - if (error < 0) - return error; - - if (len > GIT_OID_HEXSZ) - len = GIT_OID_HEXSZ; - - if (len == GIT_OID_HEXSZ) { - git_cached_obj *cached = NULL; - - /* We want to match the full id : we can first look up in the cache, - * since there is no need to check for non ambiguousity - */ - cached = git_cache_get_any(&repo->objects, id); - if (cached != NULL) { - if (cached->flags == GIT_CACHE_STORE_PARSED) { - object = (git_object *)cached; - - if (type != GIT_OBJECT_ANY && type != object->cached.type) { - git_object_free(object); - git_error_set(GIT_ERROR_INVALID, - "the requested type does not match the type in the ODB"); - return GIT_ENOTFOUND; - } - - *object_out = object; - return 0; - } else if (cached->flags == GIT_CACHE_STORE_RAW) { - odb_obj = (git_odb_object *)cached; - } else { - GIT_ASSERT(!"Wrong caching type in the global object cache"); - } - } else { - /* Object was not found in the cache, let's explore the backends. - * We could just use git_odb_read_unique_short_oid, - * it is the same cost for packed and loose object backends, - * but it may be much more costly for sqlite and hiredis. - */ - error = git_odb_read(&odb_obj, odb, id); - } - } else { - git_oid short_oid = {{ 0 }}; - - git_oid__cpy_prefix(&short_oid, id, len); - - /* If len < GIT_OID_HEXSZ (a strict short oid was given), we have - * 2 options : - * - We always search in the cache first. If we find that short oid is - * ambiguous, we can stop. But in all the other cases, we must then - * explore all the backends (to find an object if there was match, - * or to check that oid is not ambiguous if we have found 1 match in - * the cache) - * - We never explore the cache, go right to exploring the backends - * We chose the latter : we explore directly the backends. - */ - error = git_odb_read_prefix(&odb_obj, odb, &short_oid, len); - } - - if (error < 0) - return error; - - error = git_object__from_odb_object(object_out, repo, odb_obj, type); - - git_odb_object_free(odb_obj); - - return error; -} - -int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_object_t type) { - return git_object_lookup_prefix(object_out, repo, id, GIT_OID_HEXSZ, type); -} - -void git_object_free(git_object *object) -{ - if (object == NULL) - return; - - git_cached_obj_decref(object); -} - -const git_oid *git_object_id(const git_object *obj) -{ - GIT_ASSERT_ARG_WITH_RETVAL(obj, NULL); - return &obj->cached.oid; -} - -git_object_t git_object_type(const git_object *obj) -{ - GIT_ASSERT_ARG_WITH_RETVAL(obj, GIT_OBJECT_INVALID); - return obj->cached.type; -} - -git_repository *git_object_owner(const git_object *obj) -{ - GIT_ASSERT_ARG_WITH_RETVAL(obj, NULL); - return obj->repo; -} - -const char *git_object_type2string(git_object_t type) -{ - if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table)) - return ""; - - return git_objects_table[type].str; -} - -git_object_t git_object_string2type(const char *str) -{ - if (!str) - return GIT_OBJECT_INVALID; - - return git_object_stringn2type(str, strlen(str)); -} - -git_object_t git_object_stringn2type(const char *str, size_t len) -{ - size_t i; - - if (!str || !len || !*str) - return GIT_OBJECT_INVALID; - - for (i = 0; i < ARRAY_SIZE(git_objects_table); i++) - if (*git_objects_table[i].str && - !git__prefixncmp(str, len, git_objects_table[i].str)) - return (git_object_t)i; - - return GIT_OBJECT_INVALID; -} - -int git_object_typeisloose(git_object_t type) -{ - if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table)) - return 0; - - return (git_objects_table[type].size > 0) ? 1 : 0; -} - -size_t git_object__size(git_object_t type) -{ - if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table)) - return 0; - - return git_objects_table[type].size; -} - -static int dereference_object(git_object **dereferenced, git_object *obj) -{ - git_object_t type = git_object_type(obj); - - switch (type) { - case GIT_OBJECT_COMMIT: - return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj); - - case GIT_OBJECT_TAG: - return git_tag_target(dereferenced, (git_tag*)obj); - - case GIT_OBJECT_BLOB: - case GIT_OBJECT_TREE: - return GIT_EPEEL; - - default: - return GIT_EINVALIDSPEC; - } -} - -static int peel_error(int error, const git_oid *oid, git_object_t type) -{ - const char *type_name; - char hex_oid[GIT_OID_HEXSZ + 1]; - - type_name = git_object_type2string(type); - - git_oid_fmt(hex_oid, oid); - hex_oid[GIT_OID_HEXSZ] = '\0'; - - git_error_set(GIT_ERROR_OBJECT, "the git_object of id '%s' can not be " - "successfully peeled into a %s (git_object_t=%i).", hex_oid, type_name, type); - - return error; -} - -static int check_type_combination(git_object_t type, git_object_t target) -{ - if (type == target) - return 0; - - switch (type) { - case GIT_OBJECT_BLOB: - case GIT_OBJECT_TREE: - /* a blob or tree can never be peeled to anything but themselves */ - return GIT_EINVALIDSPEC; - break; - case GIT_OBJECT_COMMIT: - /* a commit can only be peeled to a tree */ - if (target != GIT_OBJECT_TREE && target != GIT_OBJECT_ANY) - return GIT_EINVALIDSPEC; - break; - case GIT_OBJECT_TAG: - /* a tag may point to anything, so we let anything through */ - break; - default: - return GIT_EINVALIDSPEC; - } - - return 0; -} - -int git_object_peel( - git_object **peeled, - const git_object *object, - git_object_t target_type) -{ - git_object *source, *deref = NULL; - int error; - - GIT_ASSERT_ARG(object); - GIT_ASSERT_ARG(peeled); - - GIT_ASSERT_ARG(target_type == GIT_OBJECT_TAG || - target_type == GIT_OBJECT_COMMIT || - target_type == GIT_OBJECT_TREE || - target_type == GIT_OBJECT_BLOB || - target_type == GIT_OBJECT_ANY); - - if ((error = check_type_combination(git_object_type(object), target_type)) < 0) - return peel_error(error, git_object_id(object), target_type); - - if (git_object_type(object) == target_type) - return git_object_dup(peeled, (git_object *)object); - - source = (git_object *)object; - - while (!(error = dereference_object(&deref, source))) { - - if (source != object) - git_object_free(source); - - if (git_object_type(deref) == target_type) { - *peeled = deref; - return 0; - } - - if (target_type == GIT_OBJECT_ANY && - git_object_type(deref) != git_object_type(object)) - { - *peeled = deref; - return 0; - } - - source = deref; - deref = NULL; - } - - if (source != object) - git_object_free(source); - - git_object_free(deref); - - if (error) - error = peel_error(error, git_object_id(object), target_type); - - return error; -} - -int git_object_dup(git_object **dest, git_object *source) -{ - git_cached_obj_incref(source); - *dest = source; - return 0; -} - -int git_object_lookup_bypath( - git_object **out, - const git_object *treeish, - const char *path, - git_object_t type) -{ - int error = -1; - git_tree *tree = NULL; - git_tree_entry *entry = NULL; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(treeish); - GIT_ASSERT_ARG(path); - - if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJECT_TREE)) < 0 || - (error = git_tree_entry_bypath(&entry, tree, path)) < 0) - { - goto cleanup; - } - - if (type != GIT_OBJECT_ANY && git_tree_entry_type(entry) != type) - { - git_error_set(GIT_ERROR_OBJECT, - "object at path '%s' is not of the asked-for type %d", - path, type); - error = GIT_EINVALIDSPEC; - goto cleanup; - } - - error = git_tree_entry_to_object(out, git_object_owner(treeish), entry); - -cleanup: - git_tree_entry_free(entry); - git_tree_free(tree); - return error; -} - -static int git_object__short_id(git_str *out, const git_object *obj) -{ - git_repository *repo; - int len = GIT_ABBREV_DEFAULT, error; - git_oid id = {{0}}; - git_odb *odb; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(obj); - - repo = git_object_owner(obj); - - if ((error = git_repository__configmap_lookup(&len, repo, GIT_CONFIGMAP_ABBREV)) < 0) - return error; - - if ((error = git_repository_odb(&odb, repo)) < 0) - return error; - - while (len < GIT_OID_HEXSZ) { - /* set up short oid */ - memcpy(&id.id, &obj->cached.oid.id, (len + 1) / 2); - if (len & 1) - id.id[len / 2] &= 0xf0; - - error = git_odb_exists_prefix(NULL, odb, &id, len); - if (error != GIT_EAMBIGUOUS) - break; - - git_error_clear(); - len++; - } - - if (!error && !(error = git_str_grow(out, len + 1))) { - git_oid_tostr(out->ptr, len + 1, &id); - out->size = len; - } - - git_odb_free(odb); - - return error; -} - -int git_object_short_id(git_buf *out, const git_object *obj) -{ - GIT_BUF_WRAP_PRIVATE(out, git_object__short_id, obj); -} - -bool git_object__is_valid( - git_repository *repo, const git_oid *id, git_object_t expected_type) -{ - git_odb *odb; - git_object_t actual_type; - size_t len; - int error; - - if (!git_object__strict_input_validation) - return true; - - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || - (error = git_odb_read_header(&len, &actual_type, odb, id)) < 0) - return false; - - if (expected_type != GIT_OBJECT_ANY && expected_type != actual_type) { - git_error_set(GIT_ERROR_INVALID, - "the requested type does not match the type in the ODB"); - return false; - } - - return true; -} - -int git_object_rawcontent_is_valid( - int *valid, - const char *buf, - size_t len, - git_object_t type) -{ - git_object *obj = NULL; - int error; - - GIT_ASSERT_ARG(valid); - GIT_ASSERT_ARG(buf); - - /* Blobs are always valid; don't bother parsing. */ - if (type == GIT_OBJECT_BLOB) { - *valid = 1; - return 0; - } - - error = git_object__from_raw(&obj, buf, len, type); - git_object_free(obj); - - if (error == 0) { - *valid = 1; - return 0; - } else if (error == GIT_EINVALID) { - *valid = 0; - return 0; - } - - return error; -} diff --git a/src/object.h b/src/object.h deleted file mode 100644 index 66be57557..000000000 --- a/src/object.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_object_h__ -#define INCLUDE_object_h__ - -#include "common.h" - -#include "repository.h" - -#define GIT_OBJECT_SIZE_MAX UINT64_MAX - -extern bool git_object__strict_input_validation; - -/** Base git object for inheritance */ -struct git_object { - git_cached_obj cached; - git_repository *repo; -}; - -/* fully free the object; internal method, DO NOT EXPORT */ -void git_object__free(void *object); - -/* - * Parse object from raw data. Note that the resulting object is - * tied to the lifetime of the data, as some objects simply point - * into it. - */ -int git_object__from_raw( - git_object **object_out, - const char *data, - size_t size, - git_object_t type); - -int git_object__from_odb_object( - git_object **object_out, - git_repository *repo, - git_odb_object *odb_obj, - git_object_t type); - -int git_object__resolve_to_type(git_object **obj, git_object_t type); - -git_object_t git_object_stringn2type(const char *str, size_t len); - -int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); - -void git_oid__writebuf(git_str *buf, const char *header, const git_oid *oid); - -bool git_object__is_valid( - git_repository *repo, const git_oid *id, git_object_t expected_type); - -GIT_INLINE(git_object_t) git_object__type_from_filemode(git_filemode_t mode) -{ - switch (mode) { - case GIT_FILEMODE_TREE: - return GIT_OBJECT_TREE; - case GIT_FILEMODE_COMMIT: - return GIT_OBJECT_COMMIT; - case GIT_FILEMODE_BLOB: - case GIT_FILEMODE_BLOB_EXECUTABLE: - case GIT_FILEMODE_LINK: - return GIT_OBJECT_BLOB; - default: - return GIT_OBJECT_INVALID; - } -} - -#endif diff --git a/src/object_api.c b/src/object_api.c deleted file mode 100644 index d45abd5ce..000000000 --- a/src/object_api.c +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "git2/object.h" - -#include "repository.h" -#include "commit.h" -#include "tree.h" -#include "blob.h" -#include "tag.h" - -/** - * Commit - */ -int git_commit_lookup(git_commit **out, git_repository *repo, const git_oid *id) -{ - return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_COMMIT); -} - -int git_commit_lookup_prefix(git_commit **out, git_repository *repo, const git_oid *id, size_t len) -{ - return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_COMMIT); -} - -void git_commit_free(git_commit *obj) -{ - git_object_free((git_object *)obj); -} - -const git_oid *git_commit_id(const git_commit *obj) -{ - return git_object_id((const git_object *)obj); -} - -git_repository *git_commit_owner(const git_commit *obj) -{ - return git_object_owner((const git_object *)obj); -} - -int git_commit_dup(git_commit **out, git_commit *obj) -{ - return git_object_dup((git_object **)out, (git_object *)obj); -} - -/** - * Tree - */ -int git_tree_lookup(git_tree **out, git_repository *repo, const git_oid *id) -{ - return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_TREE); -} - -int git_tree_lookup_prefix(git_tree **out, git_repository *repo, const git_oid *id, size_t len) -{ - return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_TREE); -} - -void git_tree_free(git_tree *obj) -{ - git_object_free((git_object *)obj); -} - -const git_oid *git_tree_id(const git_tree *obj) -{ - return git_object_id((const git_object *)obj); -} - -git_repository *git_tree_owner(const git_tree *obj) -{ - return git_object_owner((const git_object *)obj); -} - -int git_tree_dup(git_tree **out, git_tree *obj) -{ - return git_object_dup((git_object **)out, (git_object *)obj); -} - -/** - * Tag - */ -int git_tag_lookup(git_tag **out, git_repository *repo, const git_oid *id) -{ - return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_TAG); -} - -int git_tag_lookup_prefix(git_tag **out, git_repository *repo, const git_oid *id, size_t len) -{ - return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_TAG); -} - -void git_tag_free(git_tag *obj) -{ - git_object_free((git_object *)obj); -} - -const git_oid *git_tag_id(const git_tag *obj) -{ - return git_object_id((const git_object *)obj); -} - -git_repository *git_tag_owner(const git_tag *obj) -{ - return git_object_owner((const git_object *)obj); -} - -int git_tag_dup(git_tag **out, git_tag *obj) -{ - return git_object_dup((git_object **)out, (git_object *)obj); -} - -/** - * Blob - */ -int git_blob_lookup(git_blob **out, git_repository *repo, const git_oid *id) -{ - return git_object_lookup((git_object **)out, repo, id, GIT_OBJECT_BLOB); -} - -int git_blob_lookup_prefix(git_blob **out, git_repository *repo, const git_oid *id, size_t len) -{ - return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJECT_BLOB); -} - -void git_blob_free(git_blob *obj) -{ - git_object_free((git_object *)obj); -} - -const git_oid *git_blob_id(const git_blob *obj) -{ - return git_object_id((const git_object *)obj); -} - -git_repository *git_blob_owner(const git_blob *obj) -{ - return git_object_owner((const git_object *)obj); -} - -int git_blob_dup(git_blob **out, git_blob *obj) -{ - return git_object_dup((git_object **)out, (git_object *)obj); -} diff --git a/src/odb.c b/src/odb.c deleted file mode 100644 index 6d714ba54..000000000 --- a/src/odb.c +++ /dev/null @@ -1,1831 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "odb.h" - -#include -#include "git2/object.h" -#include "git2/sys/odb_backend.h" -#include "futils.h" -#include "hash.h" -#include "delta.h" -#include "filter.h" -#include "repository.h" -#include "blob.h" -#include "oid.h" - -#include "git2/odb_backend.h" -#include "git2/oid.h" -#include "git2/oidarray.h" - -#define GIT_ALTERNATES_FILE "info/alternates" - -#define GIT_ALTERNATES_MAX_DEPTH 5 - -/* - * We work under the assumption that most objects for long-running - * operations will be packed - */ -int git_odb__loose_priority = GIT_ODB_DEFAULT_LOOSE_PRIORITY; -int git_odb__packed_priority = GIT_ODB_DEFAULT_PACKED_PRIORITY; - -bool git_odb__strict_hash_verification = true; - -typedef struct -{ - git_odb_backend *backend; - int priority; - bool is_alternate; - ino_t disk_inode; -} backend_internal; - -static git_cache *odb_cache(git_odb *odb) -{ - git_repository *owner = GIT_REFCOUNT_OWNER(odb); - if (owner != NULL) { - return &owner->objects; - } - - return &odb->own_cache; -} - -static int odb_otype_fast(git_object_t *type_p, git_odb *db, const git_oid *id); -static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth); -static int error_null_oid(int error, const char *message); - -static git_object_t odb_hardcoded_type(const git_oid *id) -{ - if (!git_oid_cmp(id, &git_oid__empty_tree_sha1)) - return GIT_OBJECT_TREE; - - return GIT_OBJECT_INVALID; -} - -static int odb_read_hardcoded(bool *found, git_rawobj *raw, const git_oid *id) -{ - git_object_t type; - - *found = false; - - if ((type = odb_hardcoded_type(id)) == GIT_OBJECT_INVALID) - return 0; - - raw->type = type; - raw->len = 0; - raw->data = git__calloc(1, sizeof(uint8_t)); - GIT_ERROR_CHECK_ALLOC(raw->data); - - *found = true; - return 0; -} - -int git_odb__format_object_header( - size_t *written, - char *hdr, - size_t hdr_size, - git_object_size_t obj_len, - git_object_t obj_type) -{ - const char *type_str = git_object_type2string(obj_type); - int hdr_max = (hdr_size > INT_MAX-2) ? (INT_MAX-2) : (int)hdr_size; - int len; - - len = p_snprintf(hdr, hdr_max, "%s %"PRId64, type_str, (int64_t)obj_len); - - if (len < 0 || len >= hdr_max) { - git_error_set(GIT_ERROR_OS, "object header creation failed"); - return -1; - } - - *written = (size_t)(len + 1); - return 0; -} - -int git_odb__hashobj(git_oid *id, git_rawobj *obj) -{ - git_str_vec vec[2]; - char header[64]; - size_t hdrlen; - int error; - - GIT_ASSERT_ARG(id); - GIT_ASSERT_ARG(obj); - - if (!git_object_typeisloose(obj->type)) { - git_error_set(GIT_ERROR_INVALID, "invalid object type"); - return -1; - } - - if (!obj->data && obj->len != 0) { - git_error_set(GIT_ERROR_INVALID, "invalid object"); - return -1; - } - - if ((error = git_odb__format_object_header(&hdrlen, - header, sizeof(header), obj->len, obj->type)) < 0) - return error; - - vec[0].data = header; - vec[0].len = hdrlen; - vec[1].data = obj->data; - vec[1].len = obj->len; - - return git_hash_vec(id->id, vec, 2, GIT_HASH_ALGORITHM_SHA1); -} - - -static git_odb_object *odb_object__alloc(const git_oid *oid, git_rawobj *source) -{ - git_odb_object *object = git__calloc(1, sizeof(git_odb_object)); - - if (object != NULL) { - git_oid_cpy(&object->cached.oid, oid); - object->cached.type = source->type; - object->cached.size = source->len; - object->buffer = source->data; - } - - return object; -} - -void git_odb_object__free(void *object) -{ - if (object != NULL) { - git__free(((git_odb_object *)object)->buffer); - git__free(object); - } -} - -const git_oid *git_odb_object_id(git_odb_object *object) -{ - return &object->cached.oid; -} - -const void *git_odb_object_data(git_odb_object *object) -{ - return object->buffer; -} - -size_t git_odb_object_size(git_odb_object *object) -{ - return object->cached.size; -} - -git_object_t git_odb_object_type(git_odb_object *object) -{ - return object->cached.type; -} - -int git_odb_object_dup(git_odb_object **dest, git_odb_object *source) -{ - git_cached_obj_incref(source); - *dest = source; - return 0; -} - -void git_odb_object_free(git_odb_object *object) -{ - if (object == NULL) - return; - - git_cached_obj_decref(object); -} - -int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_object_t type) -{ - size_t hdr_len; - char hdr[64], buffer[FILEIO_BUFSIZE]; - git_hash_ctx ctx; - ssize_t read_len = 0; - int error = 0; - - if (!git_object_typeisloose(type)) { - git_error_set(GIT_ERROR_INVALID, "invalid object type for hash"); - return -1; - } - - if ((error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)) < 0) - return error; - - if ((error = git_odb__format_object_header(&hdr_len, hdr, - sizeof(hdr), size, type)) < 0) - goto done; - - if ((error = git_hash_update(&ctx, hdr, hdr_len)) < 0) - goto done; - - while (size > 0 && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { - if ((error = git_hash_update(&ctx, buffer, read_len)) < 0) - goto done; - - size -= read_len; - } - - /* If p_read returned an error code, the read obviously failed. - * If size is not zero, the file was truncated after we originally - * stat'd it, so we consider this a read failure too */ - if (read_len < 0 || size > 0) { - git_error_set(GIT_ERROR_OS, "error reading file for hashing"); - error = -1; - - goto done; - } - - error = git_hash_final(out->id, &ctx); - -done: - git_hash_ctx_cleanup(&ctx); - return error; -} - -int git_odb__hashfd_filtered( - git_oid *out, git_file fd, size_t size, git_object_t type, git_filter_list *fl) -{ - int error; - git_str raw = GIT_STR_INIT; - - if (!fl) - return git_odb__hashfd(out, fd, size, type); - - /* size of data is used in header, so we have to read the whole file - * into memory to apply filters before beginning to calculate the hash - */ - - if (!(error = git_futils_readbuffer_fd(&raw, fd, size))) { - git_str post = GIT_STR_INIT; - - error = git_filter_list__convert_buf(&post, fl, &raw); - - if (!error) - error = git_odb_hash(out, post.ptr, post.size, type); - - git_str_dispose(&post); - } - - return error; -} - -int git_odb__hashlink(git_oid *out, const char *path) -{ - struct stat st; - int size; - int result; - - if (git_fs_path_lstat(path, &st) < 0) - return -1; - - if (!git__is_int(st.st_size) || (int)st.st_size < 0) { - git_error_set(GIT_ERROR_FILESYSTEM, "file size overflow for 32-bit systems"); - return -1; - } - - size = (int)st.st_size; - - if (S_ISLNK(st.st_mode)) { - char *link_data; - int read_len; - size_t alloc_size; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, size, 1); - link_data = git__malloc(alloc_size); - GIT_ERROR_CHECK_ALLOC(link_data); - - read_len = p_readlink(path, link_data, size); - if (read_len == -1) { - git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", path); - git__free(link_data); - return -1; - } - GIT_ASSERT(read_len <= size); - link_data[read_len] = '\0'; - - result = git_odb_hash(out, link_data, read_len, GIT_OBJECT_BLOB); - git__free(link_data); - } else { - int fd = git_futils_open_ro(path); - if (fd < 0) - return -1; - result = git_odb__hashfd(out, fd, size, GIT_OBJECT_BLOB); - p_close(fd); - } - - return result; -} - -int git_odb_hashfile(git_oid *out, const char *path, git_object_t type) -{ - uint64_t size; - int fd, error = 0; - - if ((fd = git_futils_open_ro(path)) < 0) - return fd; - - if ((error = git_futils_filesize(&size, fd)) < 0) - goto done; - - if (!git__is_sizet(size)) { - git_error_set(GIT_ERROR_OS, "file size overflow for 32-bit systems"); - error = -1; - goto done; - } - - error = git_odb__hashfd(out, fd, (size_t)size, type); - -done: - p_close(fd); - return error; -} - -int git_odb_hash(git_oid *id, const void *data, size_t len, git_object_t type) -{ - git_rawobj raw; - - GIT_ASSERT_ARG(id); - - raw.data = (void *)data; - raw.len = len; - raw.type = type; - - return git_odb__hashobj(id, &raw); -} - -/** - * FAKE WSTREAM - */ - -typedef struct { - git_odb_stream stream; - char *buffer; - size_t size, written; - git_object_t type; -} fake_wstream; - -static int fake_wstream__fwrite(git_odb_stream *_stream, const git_oid *oid) -{ - fake_wstream *stream = (fake_wstream *)_stream; - return _stream->backend->write(_stream->backend, oid, stream->buffer, stream->size, stream->type); -} - -static int fake_wstream__write(git_odb_stream *_stream, const char *data, size_t len) -{ - fake_wstream *stream = (fake_wstream *)_stream; - - GIT_ASSERT(stream->written + len <= stream->size); - - memcpy(stream->buffer + stream->written, data, len); - stream->written += len; - return 0; -} - -static void fake_wstream__free(git_odb_stream *_stream) -{ - fake_wstream *stream = (fake_wstream *)_stream; - - git__free(stream->buffer); - git__free(stream); -} - -static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend, git_object_size_t size, git_object_t type) -{ - fake_wstream *stream; - size_t blobsize; - - GIT_ERROR_CHECK_BLOBSIZE(size); - blobsize = (size_t)size; - - stream = git__calloc(1, sizeof(fake_wstream)); - GIT_ERROR_CHECK_ALLOC(stream); - - stream->size = blobsize; - stream->type = type; - stream->buffer = git__malloc(blobsize); - if (stream->buffer == NULL) { - git__free(stream); - return -1; - } - - stream->stream.backend = backend; - stream->stream.read = NULL; /* read only */ - stream->stream.write = &fake_wstream__write; - stream->stream.finalize_write = &fake_wstream__fwrite; - stream->stream.free = &fake_wstream__free; - stream->stream.mode = GIT_STREAM_WRONLY; - - *stream_p = (git_odb_stream *)stream; - return 0; -} - -/*********************************************************** - * - * OBJECT DATABASE PUBLIC API - * - * Public calls for the ODB functionality - * - ***********************************************************/ - -static int backend_sort_cmp(const void *a, const void *b) -{ - const backend_internal *backend_a = (const backend_internal *)(a); - const backend_internal *backend_b = (const backend_internal *)(b); - - if (backend_b->priority == backend_a->priority) { - if (backend_a->is_alternate) - return -1; - if (backend_b->is_alternate) - return 1; - return 0; - } - return (backend_b->priority - backend_a->priority); -} - -int git_odb_new(git_odb **out) -{ - git_odb *db = git__calloc(1, sizeof(*db)); - GIT_ERROR_CHECK_ALLOC(db); - - if (git_mutex_init(&db->lock) < 0) { - git__free(db); - return -1; - } - if (git_cache_init(&db->own_cache) < 0) { - git_mutex_free(&db->lock); - git__free(db); - return -1; - } - if (git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) { - git_cache_dispose(&db->own_cache); - git_mutex_free(&db->lock); - git__free(db); - return -1; - } - - *out = db; - GIT_REFCOUNT_INC(db); - return 0; -} - -static int add_backend_internal( - git_odb *odb, git_odb_backend *backend, - int priority, bool is_alternate, ino_t disk_inode) -{ - backend_internal *internal; - - GIT_ASSERT_ARG(odb); - GIT_ASSERT_ARG(backend); - - GIT_ERROR_CHECK_VERSION(backend, GIT_ODB_BACKEND_VERSION, "git_odb_backend"); - - /* Check if the backend is already owned by another ODB */ - GIT_ASSERT(!backend->odb || backend->odb == odb); - - internal = git__malloc(sizeof(backend_internal)); - GIT_ERROR_CHECK_ALLOC(internal); - - internal->backend = backend; - internal->priority = priority; - internal->is_alternate = is_alternate; - internal->disk_inode = disk_inode; - - if (git_mutex_lock(&odb->lock) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return -1; - } - if (git_vector_insert(&odb->backends, internal) < 0) { - git_mutex_unlock(&odb->lock); - git__free(internal); - return -1; - } - git_vector_sort(&odb->backends); - internal->backend->odb = odb; - git_mutex_unlock(&odb->lock); - return 0; -} - -int git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority) -{ - return add_backend_internal(odb, backend, priority, false, 0); -} - -int git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority) -{ - return add_backend_internal(odb, backend, priority, true, 0); -} - -size_t git_odb_num_backends(git_odb *odb) -{ - size_t length; - bool locked = true; - - GIT_ASSERT_ARG(odb); - - if (git_mutex_lock(&odb->lock) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - locked = false; - } - length = odb->backends.length; - if (locked) - git_mutex_unlock(&odb->lock); - return length; -} - -static int git_odb__error_unsupported_in_backend(const char *action) -{ - git_error_set(GIT_ERROR_ODB, - "cannot %s - unsupported in the loaded odb backends", action); - return -1; -} - - -int git_odb_get_backend(git_odb_backend **out, git_odb *odb, size_t pos) -{ - backend_internal *internal; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(odb); - - - if ((error = git_mutex_lock(&odb->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - internal = git_vector_get(&odb->backends, pos); - - if (!internal || !internal->backend) { - git_mutex_unlock(&odb->lock); - - git_error_set(GIT_ERROR_ODB, "no ODB backend loaded at index %" PRIuZ, pos); - return GIT_ENOTFOUND; - } - *out = internal->backend; - git_mutex_unlock(&odb->lock); - - return 0; -} - -int git_odb__add_default_backends( - git_odb *db, const char *objects_dir, - bool as_alternates, int alternate_depth) -{ - size_t i = 0; - struct stat st; - ino_t inode; - git_odb_backend *loose, *packed; - - /* TODO: inodes are not really relevant on Win32, so we need to find - * a cross-platform workaround for this */ -#ifdef GIT_WIN32 - GIT_UNUSED(i); - GIT_UNUSED(&st); - - inode = 0; -#else - if (p_stat(objects_dir, &st) < 0) { - if (as_alternates) - /* this should warn */ - return 0; - - git_error_set(GIT_ERROR_ODB, "failed to load object database in '%s'", objects_dir); - return -1; - } - - inode = st.st_ino; - - if (git_mutex_lock(&db->lock) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return -1; - } - for (i = 0; i < db->backends.length; ++i) { - backend_internal *backend = git_vector_get(&db->backends, i); - if (backend->disk_inode == inode) { - git_mutex_unlock(&db->lock); - return 0; - } - } - git_mutex_unlock(&db->lock); -#endif - - /* add the loose object backend */ - if (git_odb_backend_loose(&loose, objects_dir, -1, db->do_fsync, 0, 0) < 0 || - add_backend_internal(db, loose, git_odb__loose_priority, as_alternates, inode) < 0) - return -1; - - /* add the packed file backend */ - if (git_odb_backend_pack(&packed, objects_dir) < 0 || - add_backend_internal(db, packed, git_odb__packed_priority, as_alternates, inode) < 0) - return -1; - - if (git_mutex_lock(&db->lock) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return -1; - } - if (!db->cgraph && git_commit_graph_new(&db->cgraph, objects_dir, false) < 0) { - git_mutex_unlock(&db->lock); - return -1; - } - git_mutex_unlock(&db->lock); - - return load_alternates(db, objects_dir, alternate_depth); -} - -static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth) -{ - git_str alternates_path = GIT_STR_INIT; - git_str alternates_buf = GIT_STR_INIT; - char *buffer; - const char *alternate; - int result = 0; - - /* Git reports an error, we just ignore anything deeper */ - if (alternate_depth > GIT_ALTERNATES_MAX_DEPTH) - return 0; - - if (git_str_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0) - return -1; - - if (git_fs_path_exists(alternates_path.ptr) == false) { - git_str_dispose(&alternates_path); - return 0; - } - - if (git_futils_readbuffer(&alternates_buf, alternates_path.ptr) < 0) { - git_str_dispose(&alternates_path); - return -1; - } - - buffer = (char *)alternates_buf.ptr; - - /* add each alternate as a new backend; one alternate per line */ - while ((alternate = git__strtok(&buffer, "\r\n")) != NULL) { - if (*alternate == '\0' || *alternate == '#') - continue; - - /* - * Relative path: build based on the current `objects` - * folder. However, relative paths are only allowed in - * the current repository. - */ - if (*alternate == '.' && !alternate_depth) { - if ((result = git_str_joinpath(&alternates_path, objects_dir, alternate)) < 0) - break; - alternate = git_str_cstr(&alternates_path); - } - - if ((result = git_odb__add_default_backends(odb, alternate, true, alternate_depth + 1)) < 0) - break; - } - - git_str_dispose(&alternates_path); - git_str_dispose(&alternates_buf); - - return result; -} - -int git_odb_add_disk_alternate(git_odb *odb, const char *path) -{ - return git_odb__add_default_backends(odb, path, true, 0); -} - -int git_odb_set_commit_graph(git_odb *odb, git_commit_graph *cgraph) -{ - int error = 0; - - GIT_ASSERT_ARG(odb); - - if ((error = git_mutex_lock(&odb->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the db lock"); - return error; - } - git_commit_graph_free(odb->cgraph); - odb->cgraph = cgraph; - git_mutex_unlock(&odb->lock); - - return error; -} - -int git_odb_open(git_odb **out, const char *objects_dir) -{ - git_odb *db; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(objects_dir); - - *out = NULL; - - if (git_odb_new(&db) < 0) - return -1; - - if (git_odb__add_default_backends(db, objects_dir, 0, 0) < 0) { - git_odb_free(db); - return -1; - } - - *out = db; - return 0; -} - -int git_odb__set_caps(git_odb *odb, int caps) -{ - if (caps == GIT_ODB_CAP_FROM_OWNER) { - git_repository *repo = GIT_REFCOUNT_OWNER(odb); - int val; - - if (!repo) { - git_error_set(GIT_ERROR_ODB, "cannot access repository to set odb caps"); - return -1; - } - - if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_FSYNCOBJECTFILES)) - odb->do_fsync = !!val; - } - - return 0; -} - -static void odb_free(git_odb *db) -{ - size_t i; - bool locked = true; - - if (git_mutex_lock(&db->lock) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - locked = false; - } - for (i = 0; i < db->backends.length; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *backend = internal->backend; - - backend->free(backend); - - git__free(internal); - } - if (locked) - git_mutex_unlock(&db->lock); - - git_commit_graph_free(db->cgraph); - git_vector_free(&db->backends); - git_cache_dispose(&db->own_cache); - git_mutex_free(&db->lock); - - git__memzero(db, sizeof(*db)); - git__free(db); -} - -void git_odb_free(git_odb *db) -{ - if (db == NULL) - return; - - GIT_REFCOUNT_DEC(db, odb_free); -} - -static int odb_exists_1( - git_odb *db, - const git_oid *id, - bool only_refreshed) -{ - size_t i; - bool found = false; - int error; - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0; i < db->backends.length && !found; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (only_refreshed && !b->refresh) - continue; - - if (b->exists != NULL) - found = (bool)b->exists(b, id); - } - git_mutex_unlock(&db->lock); - - return (int)found; -} - -int git_odb__get_commit_graph_file(git_commit_graph_file **out, git_odb *db) -{ - int error = 0; - git_commit_graph_file *result = NULL; - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the db lock"); - return error; - } - if (!db->cgraph) { - error = GIT_ENOTFOUND; - goto done; - } - error = git_commit_graph_get_file(&result, db->cgraph); - if (error) - goto done; - *out = result; - -done: - git_mutex_unlock(&db->lock); - return error; -} - -static int odb_freshen_1( - git_odb *db, - const git_oid *id, - bool only_refreshed) -{ - size_t i; - bool found = false; - int error; - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0; i < db->backends.length && !found; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (only_refreshed && !b->refresh) - continue; - - if (b->freshen != NULL) - found = !b->freshen(b, id); - else if (b->exists != NULL) - found = b->exists(b, id); - } - git_mutex_unlock(&db->lock); - - return (int)found; -} - -int git_odb__freshen(git_odb *db, const git_oid *id) -{ - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(id); - - if (odb_freshen_1(db, id, false)) - return 1; - - if (!git_odb_refresh(db)) - return odb_freshen_1(db, id, true); - - /* Failed to refresh, hence not found */ - return 0; -} - -int git_odb_exists(git_odb *db, const git_oid *id) -{ - return git_odb_exists_ext(db, id, 0); -} - -int git_odb_exists_ext(git_odb *db, const git_oid *id, unsigned int flags) -{ - git_odb_object *object; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(id); - - if (git_oid_is_zero(id)) - return 0; - - if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { - git_odb_object_free(object); - return 1; - } - - if (odb_exists_1(db, id, false)) - return 1; - - if (!(flags & GIT_ODB_LOOKUP_NO_REFRESH) && !git_odb_refresh(db)) - return odb_exists_1(db, id, true); - - /* Failed to refresh, hence not found */ - return 0; -} - -static int odb_exists_prefix_1(git_oid *out, git_odb *db, - const git_oid *key, size_t len, bool only_refreshed) -{ - size_t i; - int error = GIT_ENOTFOUND, num_found = 0; - git_oid last_found = {{0}}, found; - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - error = GIT_ENOTFOUND; - for (i = 0; i < db->backends.length; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (only_refreshed && !b->refresh) - continue; - - if (!b->exists_prefix) - continue; - - error = b->exists_prefix(&found, b, key, len); - if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) - continue; - if (error) { - git_mutex_unlock(&db->lock); - return error; - } - - /* make sure found item doesn't introduce ambiguity */ - if (num_found) { - if (git_oid__cmp(&last_found, &found)) { - git_mutex_unlock(&db->lock); - return git_odb__error_ambiguous("multiple matches for prefix"); - } - } else { - git_oid_cpy(&last_found, &found); - num_found++; - } - } - git_mutex_unlock(&db->lock); - - if (!num_found) - return GIT_ENOTFOUND; - - if (out) - git_oid_cpy(out, &last_found); - - return 0; -} - -int git_odb_exists_prefix( - git_oid *out, git_odb *db, const git_oid *short_id, size_t len) -{ - int error; - git_oid key = {{0}}; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(short_id); - - if (len < GIT_OID_MINPREFIXLEN) - return git_odb__error_ambiguous("prefix length too short"); - - if (len >= GIT_OID_HEXSZ) { - if (git_odb_exists(db, short_id)) { - if (out) - git_oid_cpy(out, short_id); - return 0; - } else { - return git_odb__error_notfound( - "no match for id prefix", short_id, len); - } - } - - git_oid__cpy_prefix(&key, short_id, len); - - error = odb_exists_prefix_1(out, db, &key, len, false); - - if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) - error = odb_exists_prefix_1(out, db, &key, len, true); - - if (error == GIT_ENOTFOUND) - return git_odb__error_notfound("no match for id prefix", &key, len); - - return error; -} - -int git_odb_expand_ids( - git_odb *db, - git_odb_expand_id *ids, - size_t count) -{ - size_t i; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(ids); - - for (i = 0; i < count; i++) { - git_odb_expand_id *query = &ids[i]; - int error = GIT_EAMBIGUOUS; - - if (!query->type) - query->type = GIT_OBJECT_ANY; - - /* if we have a short OID, expand it first */ - if (query->length >= GIT_OID_MINPREFIXLEN && query->length < GIT_OID_HEXSZ) { - git_oid actual_id; - - error = odb_exists_prefix_1(&actual_id, db, &query->id, query->length, false); - if (!error) { - git_oid_cpy(&query->id, &actual_id); - query->length = GIT_OID_HEXSZ; - } - } - - /* - * now we ought to have a 40-char OID, either because we've expanded it - * or because the user passed a full OID. Ensure its type is right. - */ - if (query->length >= GIT_OID_HEXSZ) { - git_object_t actual_type; - - error = odb_otype_fast(&actual_type, db, &query->id); - if (!error) { - if (query->type != GIT_OBJECT_ANY && query->type != actual_type) - error = GIT_ENOTFOUND; - else - query->type = actual_type; - } - } - - switch (error) { - /* no errors, so we've successfully expanded the OID */ - case 0: - continue; - - /* the object is missing or ambiguous */ - case GIT_ENOTFOUND: - case GIT_EAMBIGUOUS: - memset(&query->id, 0, sizeof(git_oid)); - query->length = 0; - query->type = 0; - break; - - /* something went very wrong with the ODB; bail hard */ - default: - return error; - } - } - - git_error_clear(); - return 0; -} - -int git_odb_read_header(size_t *len_p, git_object_t *type_p, git_odb *db, const git_oid *id) -{ - int error; - git_odb_object *object = NULL; - - error = git_odb__read_header_or_object(&object, len_p, type_p, db, id); - - if (object) - git_odb_object_free(object); - - return error; -} - -static int odb_read_header_1( - size_t *len_p, git_object_t *type_p, git_odb *db, - const git_oid *id, bool only_refreshed) -{ - size_t i; - git_object_t ht; - bool passthrough = false; - int error; - - if (!only_refreshed && (ht = odb_hardcoded_type(id)) != GIT_OBJECT_INVALID) { - *type_p = ht; - *len_p = 0; - return 0; - } - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0; i < db->backends.length; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (only_refreshed && !b->refresh) - continue; - - if (!b->read_header) { - passthrough = true; - continue; - } - - error = b->read_header(len_p, type_p, b, id); - - switch (error) { - case GIT_PASSTHROUGH: - passthrough = true; - break; - case GIT_ENOTFOUND: - break; - default: - git_mutex_unlock(&db->lock); - return error; - } - } - git_mutex_unlock(&db->lock); - - return passthrough ? GIT_PASSTHROUGH : GIT_ENOTFOUND; -} - -int git_odb__read_header_or_object( - git_odb_object **out, size_t *len_p, git_object_t *type_p, - git_odb *db, const git_oid *id) -{ - int error = GIT_ENOTFOUND; - git_odb_object *object; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(id); - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(len_p); - GIT_ASSERT_ARG(type_p); - - *out = NULL; - - if (git_oid_is_zero(id)) - return error_null_oid(GIT_ENOTFOUND, "cannot read object"); - - if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { - *len_p = object->cached.size; - *type_p = object->cached.type; - *out = object; - return 0; - } - - error = odb_read_header_1(len_p, type_p, db, id, false); - - if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) - error = odb_read_header_1(len_p, type_p, db, id, true); - - if (error == GIT_ENOTFOUND) - return git_odb__error_notfound("cannot read header for", id, GIT_OID_HEXSZ); - - /* we found the header; return early */ - if (!error) - return 0; - - if (error == GIT_PASSTHROUGH) { - /* - * no backend has header-reading functionality - * so try using `git_odb_read` instead - */ - error = git_odb_read(&object, db, id); - if (!error) { - *len_p = object->cached.size; - *type_p = object->cached.type; - *out = object; - } - } - - return error; -} - -static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id, - bool only_refreshed) -{ - size_t i; - git_rawobj raw; - git_odb_object *object; - git_oid hashed; - bool found = false; - int error = 0; - - if (!only_refreshed) { - if ((error = odb_read_hardcoded(&found, &raw, id)) < 0) - return error; - } - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0; i < db->backends.length && !found; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (only_refreshed && !b->refresh) - continue; - - if (b->read != NULL) { - error = b->read(&raw.data, &raw.len, &raw.type, b, id); - if (error == GIT_PASSTHROUGH || error == GIT_ENOTFOUND) - continue; - - if (error < 0) { - git_mutex_unlock(&db->lock); - return error; - } - - found = true; - } - } - git_mutex_unlock(&db->lock); - - if (!found) - return GIT_ENOTFOUND; - - if (git_odb__strict_hash_verification) { - if ((error = git_odb_hash(&hashed, raw.data, raw.len, raw.type)) < 0) - goto out; - - if (!git_oid_equal(id, &hashed)) { - error = git_odb__error_mismatch(id, &hashed); - goto out; - } - } - - git_error_clear(); - if ((object = odb_object__alloc(id, &raw)) == NULL) { - error = -1; - goto out; - } - - *out = git_cache_store_raw(odb_cache(db), object); - -out: - if (error) - git__free(raw.data); - return error; -} - -int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) -{ - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(id); - - if (git_oid_is_zero(id)) - return error_null_oid(GIT_ENOTFOUND, "cannot read object"); - - *out = git_cache_get_raw(odb_cache(db), id); - if (*out != NULL) - return 0; - - error = odb_read_1(out, db, id, false); - - if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) - error = odb_read_1(out, db, id, true); - - if (error == GIT_ENOTFOUND) - return git_odb__error_notfound("no match for id", id, GIT_OID_HEXSZ); - - return error; -} - -static int odb_otype_fast(git_object_t *type_p, git_odb *db, const git_oid *id) -{ - git_odb_object *object; - size_t _unused; - int error; - - if (git_oid_is_zero(id)) - return error_null_oid(GIT_ENOTFOUND, "cannot get object type"); - - if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { - *type_p = object->cached.type; - git_odb_object_free(object); - return 0; - } - - error = odb_read_header_1(&_unused, type_p, db, id, false); - - if (error == GIT_PASSTHROUGH) { - error = odb_read_1(&object, db, id, false); - if (!error) - *type_p = object->cached.type; - git_odb_object_free(object); - } - - return error; -} - -static int read_prefix_1(git_odb_object **out, git_odb *db, - const git_oid *key, size_t len, bool only_refreshed) -{ - size_t i; - int error = 0; - git_oid found_full_oid = {{0}}; - git_rawobj raw = {0}; - void *data = NULL; - bool found = false; - git_odb_object *object; - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0; i < db->backends.length; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (only_refreshed && !b->refresh) - continue; - - if (b->read_prefix != NULL) { - git_oid full_oid; - error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, key, len); - - if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) { - error = 0; - continue; - } - - if (error) { - git_mutex_unlock(&db->lock); - goto out; - } - - git__free(data); - data = raw.data; - - if (found && git_oid__cmp(&full_oid, &found_full_oid)) { - git_str buf = GIT_STR_INIT; - - git_str_printf(&buf, "multiple matches for prefix: %s", - git_oid_tostr_s(&full_oid)); - git_str_printf(&buf, " %s", - git_oid_tostr_s(&found_full_oid)); - - error = git_odb__error_ambiguous(buf.ptr); - git_str_dispose(&buf); - git_mutex_unlock(&db->lock); - goto out; - } - - found_full_oid = full_oid; - found = true; - } - } - git_mutex_unlock(&db->lock); - - if (!found) - return GIT_ENOTFOUND; - - if (git_odb__strict_hash_verification) { - git_oid hash; - - if ((error = git_odb_hash(&hash, raw.data, raw.len, raw.type)) < 0) - goto out; - - if (!git_oid_equal(&found_full_oid, &hash)) { - error = git_odb__error_mismatch(&found_full_oid, &hash); - goto out; - } - } - - if ((object = odb_object__alloc(&found_full_oid, &raw)) == NULL) { - error = -1; - goto out; - } - - *out = git_cache_store_raw(odb_cache(db), object); - -out: - if (error) - git__free(raw.data); - - return error; -} - -int git_odb_read_prefix( - git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len) -{ - git_oid key = {{0}}; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(db); - - if (len < GIT_OID_MINPREFIXLEN) - return git_odb__error_ambiguous("prefix length too short"); - - if (len > GIT_OID_HEXSZ) - len = GIT_OID_HEXSZ; - - if (len == GIT_OID_HEXSZ) { - *out = git_cache_get_raw(odb_cache(db), short_id); - if (*out != NULL) - return 0; - } - - git_oid__cpy_prefix(&key, short_id, len); - - error = read_prefix_1(out, db, &key, len, false); - - if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) - error = read_prefix_1(out, db, &key, len, true); - - if (error == GIT_ENOTFOUND) - return git_odb__error_notfound("no match for prefix", &key, len); - - return error; -} - -int git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload) -{ - unsigned int i; - git_vector backends = GIT_VECTOR_INIT; - backend_internal *internal; - int error = 0; - - /* Make a copy of the backends vector to invoke the callback without holding the lock. */ - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - goto cleanup; - } - error = git_vector_dup(&backends, &db->backends, NULL); - git_mutex_unlock(&db->lock); - - if (error < 0) - goto cleanup; - - git_vector_foreach(&backends, i, internal) { - git_odb_backend *b = internal->backend; - error = b->foreach(b, cb, payload); - if (error != 0) - goto cleanup; - } - -cleanup: - git_vector_free(&backends); - - return error; -} - -int git_odb_write( - git_oid *oid, git_odb *db, const void *data, size_t len, git_object_t type) -{ - size_t i; - int error; - git_odb_stream *stream; - - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(db); - - if ((error = git_odb_hash(oid, data, len, type)) < 0) - return error; - - if (git_oid_is_zero(oid)) - return error_null_oid(GIT_EINVALID, "cannot write object"); - - if (git_odb__freshen(db, oid)) - return 0; - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0, error = GIT_ERROR; i < db->backends.length && error < 0; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - /* we don't write in alternates! */ - if (internal->is_alternate) - continue; - - if (b->write != NULL) - error = b->write(b, oid, data, len, type); - } - git_mutex_unlock(&db->lock); - - if (!error || error == GIT_PASSTHROUGH) - return 0; - - /* if no backends were able to write the object directly, we try a - * streaming write to the backends; just write the whole object into the - * stream in one push - */ - if ((error = git_odb_open_wstream(&stream, db, len, type)) != 0) - return error; - - if ((error = stream->write(stream, data, len)) == 0) - error = stream->finalize_write(stream, oid); - - git_odb_stream_free(stream); - return error; -} - -static int hash_header(git_hash_ctx *ctx, git_object_size_t size, git_object_t type) -{ - char header[64]; - size_t hdrlen; - int error; - - if ((error = git_odb__format_object_header(&hdrlen, - header, sizeof(header), size, type)) < 0) - return error; - - return git_hash_update(ctx, header, hdrlen); -} - -int git_odb_open_wstream( - git_odb_stream **stream, git_odb *db, git_object_size_t size, git_object_t type) -{ - size_t i, writes = 0; - int error = GIT_ERROR; - git_hash_ctx *ctx = NULL; - - GIT_ASSERT_ARG(stream); - GIT_ASSERT_ARG(db); - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - error = GIT_ERROR; - for (i = 0; i < db->backends.length && error < 0; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - /* we don't write in alternates! */ - if (internal->is_alternate) - continue; - - if (b->writestream != NULL) { - ++writes; - error = b->writestream(stream, b, size, type); - } else if (b->write != NULL) { - ++writes; - error = init_fake_wstream(stream, b, size, type); - } - } - git_mutex_unlock(&db->lock); - - if (error < 0) { - if (error == GIT_PASSTHROUGH) - error = 0; - else if (!writes) - error = git_odb__error_unsupported_in_backend("write object"); - - goto done; - } - - ctx = git__malloc(sizeof(git_hash_ctx)); - GIT_ERROR_CHECK_ALLOC(ctx); - - if ((error = git_hash_ctx_init(ctx, GIT_HASH_ALGORITHM_SHA1)) < 0 || - (error = hash_header(ctx, size, type)) < 0) - goto done; - - (*stream)->hash_ctx = ctx; - (*stream)->declared_size = size; - (*stream)->received_bytes = 0; - -done: - if (error) - git__free(ctx); - return error; -} - -static int git_odb_stream__invalid_length( - const git_odb_stream *stream, - const char *action) -{ - git_error_set(GIT_ERROR_ODB, - "cannot %s - " - "Invalid length. %"PRId64" was expected. The " - "total size of the received chunks amounts to %"PRId64".", - action, stream->declared_size, stream->received_bytes); - - return -1; -} - -int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len) -{ - git_hash_update(stream->hash_ctx, buffer, len); - - stream->received_bytes += len; - - if (stream->received_bytes > stream->declared_size) - return git_odb_stream__invalid_length(stream, - "stream_write()"); - - return stream->write(stream, buffer, len); -} - -int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream) -{ - if (stream->received_bytes != stream->declared_size) - return git_odb_stream__invalid_length(stream, - "stream_finalize_write()"); - - git_hash_final(out->id, stream->hash_ctx); - - if (git_odb__freshen(stream->backend->odb, out)) - return 0; - - return stream->finalize_write(stream, out); -} - -int git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len) -{ - return stream->read(stream, buffer, len); -} - -void git_odb_stream_free(git_odb_stream *stream) -{ - if (stream == NULL) - return; - - git_hash_ctx_cleanup(stream->hash_ctx); - git__free(stream->hash_ctx); - stream->free(stream); -} - -int git_odb_open_rstream( - git_odb_stream **stream, - size_t *len, - git_object_t *type, - git_odb *db, - const git_oid *oid) -{ - size_t i, reads = 0; - int error = GIT_ERROR; - - GIT_ASSERT_ARG(stream); - GIT_ASSERT_ARG(db); - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - error = GIT_ERROR; - for (i = 0; i < db->backends.length && error < 0; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (b->readstream != NULL) { - ++reads; - error = b->readstream(stream, len, type, b, oid); - } - } - git_mutex_unlock(&db->lock); - - if (error == GIT_PASSTHROUGH) - error = 0; - if (error < 0 && !reads) - error = git_odb__error_unsupported_in_backend("read object streamed"); - - return error; -} - -int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_indexer_progress_cb progress_cb, void *progress_payload) -{ - size_t i, writes = 0; - int error = GIT_ERROR; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(db); - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - error = GIT_ERROR; - for (i = 0; i < db->backends.length && error < 0; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - /* we don't write in alternates! */ - if (internal->is_alternate) - continue; - - if (b->writepack != NULL) { - ++writes; - error = b->writepack(out, b, db, progress_cb, progress_payload); - } - } - git_mutex_unlock(&db->lock); - - if (error == GIT_PASSTHROUGH) - error = 0; - if (error < 0 && !writes) - error = git_odb__error_unsupported_in_backend("write pack"); - - return error; -} - -int git_odb_write_multi_pack_index(git_odb *db) -{ - size_t i, writes = 0; - int error = GIT_ERROR; - - GIT_ASSERT_ARG(db); - - for (i = 0; i < db->backends.length && error < 0; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - /* we don't write in alternates! */ - if (internal->is_alternate) - continue; - - if (b->writemidx != NULL) { - ++writes; - error = b->writemidx(b); - } - } - - if (error == GIT_PASSTHROUGH) - error = 0; - if (error < 0 && !writes) - error = git_odb__error_unsupported_in_backend("write multi-pack-index"); - - return error; -} - -void *git_odb_backend_data_alloc(git_odb_backend *backend, size_t len) -{ - GIT_UNUSED(backend); - return git__malloc(len); -} - -#ifndef GIT_DEPRECATE_HARD -void *git_odb_backend_malloc(git_odb_backend *backend, size_t len) -{ - return git_odb_backend_data_alloc(backend, len); -} -#endif - -void git_odb_backend_data_free(git_odb_backend *backend, void *data) -{ - GIT_UNUSED(backend); - git__free(data); -} - -int git_odb_refresh(struct git_odb *db) -{ - size_t i; - int error; - - GIT_ASSERT_ARG(db); - - if ((error = git_mutex_lock(&db->lock)) < 0) { - git_error_set(GIT_ERROR_ODB, "failed to acquire the odb lock"); - return error; - } - for (i = 0; i < db->backends.length; ++i) { - backend_internal *internal = git_vector_get(&db->backends, i); - git_odb_backend *b = internal->backend; - - if (b->refresh != NULL) { - int error = b->refresh(b); - if (error < 0) { - git_mutex_unlock(&db->lock); - return error; - } - } - } - if (db->cgraph) - git_commit_graph_refresh(db->cgraph); - git_mutex_unlock(&db->lock); - - return 0; -} - -int git_odb__error_mismatch(const git_oid *expected, const git_oid *actual) -{ - char expected_oid[GIT_OID_HEXSZ + 1], actual_oid[GIT_OID_HEXSZ + 1]; - - git_oid_tostr(expected_oid, sizeof(expected_oid), expected); - git_oid_tostr(actual_oid, sizeof(actual_oid), actual); - - git_error_set(GIT_ERROR_ODB, "object hash mismatch - expected %s but got %s", - expected_oid, actual_oid); - - return GIT_EMISMATCH; -} - -int git_odb__error_notfound( - const char *message, const git_oid *oid, size_t oid_len) -{ - if (oid != NULL) { - char oid_str[GIT_OID_HEXSZ + 1]; - git_oid_tostr(oid_str, oid_len+1, oid); - git_error_set(GIT_ERROR_ODB, "object not found - %s (%.*s)", - message, (int) oid_len, oid_str); - } else - git_error_set(GIT_ERROR_ODB, "object not found - %s", message); - - return GIT_ENOTFOUND; -} - -static int error_null_oid(int error, const char *message) -{ - git_error_set(GIT_ERROR_ODB, "odb: %s: null OID cannot exist", message); - return error; -} - -int git_odb__error_ambiguous(const char *message) -{ - git_error_set(GIT_ERROR_ODB, "ambiguous SHA1 prefix - %s", message); - return GIT_EAMBIGUOUS; -} - -int git_odb_init_backend(git_odb_backend *backend, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - backend, version, git_odb_backend, GIT_ODB_BACKEND_INIT); - return 0; -} diff --git a/src/odb.h b/src/odb.h deleted file mode 100644 index 5aa4cc84a..000000000 --- a/src/odb.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_odb_h__ -#define INCLUDE_odb_h__ - -#include "common.h" - -#include "git2/odb.h" -#include "git2/oid.h" -#include "git2/types.h" -#include "git2/sys/commit_graph.h" - -#include "cache.h" -#include "commit_graph.h" -#include "filter.h" -#include "posix.h" -#include "vector.h" - -#define GIT_OBJECTS_DIR "objects/" -#define GIT_OBJECT_DIR_MODE 0777 -#define GIT_OBJECT_FILE_MODE 0444 - -#define GIT_ODB_DEFAULT_LOOSE_PRIORITY 1 -#define GIT_ODB_DEFAULT_PACKED_PRIORITY 2 - -extern bool git_odb__strict_hash_verification; - -/* DO NOT EXPORT */ -typedef struct { - void *data; /**< Raw, decompressed object data. */ - size_t len; /**< Total number of bytes in data. */ - git_object_t type; /**< Type of this object. */ -} git_rawobj; - -/* EXPORT */ -struct git_odb_object { - git_cached_obj cached; - void *buffer; -}; - -/* EXPORT */ -struct git_odb { - git_refcount rc; - git_mutex lock; /* protects backends */ - git_vector backends; - git_cache own_cache; - git_commit_graph *cgraph; - unsigned int do_fsync :1; -}; - -typedef enum { - GIT_ODB_CAP_FROM_OWNER = -1 -} git_odb_cap_t; - -/* - * Set the capabilities for the object database. - */ -int git_odb__set_caps(git_odb *odb, int caps); - -/* - * Add the default loose and packed backends for a database. - */ -int git_odb__add_default_backends( - git_odb *db, const char *objects_dir, - bool as_alternates, int alternate_depth); - -/* - * Hash a git_rawobj internally. - * The `git_rawobj` is supposed to be previously initialized - */ -int git_odb__hashobj(git_oid *id, git_rawobj *obj); - -/* - * Format the object header such as it would appear in the on-disk object - */ -int git_odb__format_object_header(size_t *out_len, char *hdr, size_t hdr_size, git_object_size_t obj_len, git_object_t obj_type); - -/* - * Hash an open file descriptor. - * This is a performance call when the contents of a fd need to be hashed, - * but the fd is already open and we have the size of the contents. - * - * Saves us some `stat` calls. - * - * The fd is never closed, not even on error. It must be opened and closed - * by the caller - */ -int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_object_t type); - -/* - * Hash an open file descriptor applying an array of filters - * Acts just like git_odb__hashfd with the addition of filters... - */ -int git_odb__hashfd_filtered( - git_oid *out, git_file fd, size_t len, git_object_t type, git_filter_list *fl); - -/* - * Hash a `path`, assuming it could be a POSIX symlink: if the path is a - * symlink, then the raw contents of the symlink will be hashed. Otherwise, - * this will fallback to `git_odb__hashfd`. - * - * The hash type for this call is always `GIT_OBJECT_BLOB` because - * symlinks may only point to blobs. - */ -int git_odb__hashlink(git_oid *out, const char *path); - -/** - * Generate a GIT_EMISMATCH error for the ODB. - */ -int git_odb__error_mismatch( - const git_oid *expected, const git_oid *actual); - -/* - * Generate a GIT_ENOTFOUND error for the ODB. - */ -int git_odb__error_notfound( - const char *message, const git_oid *oid, size_t oid_len); - -/* - * Generate a GIT_EAMBIGUOUS error for the ODB. - */ -int git_odb__error_ambiguous(const char *message); - -/* - * Attempt to read object header or just return whole object if it could - * not be read. - */ -int git_odb__read_header_or_object( - git_odb_object **out, size_t *len_p, git_object_t *type_p, - git_odb *db, const git_oid *id); - -/* - * Attempt to get the ODB's commit-graph file. This object is still owned by - * the ODB. If the repository does not contain a commit-graph, it will return - * GIT_ENOTFOUND. - */ -int git_odb__get_commit_graph_file(git_commit_graph_file **out, git_odb *odb); - -/* freshen an entry in the object database */ -int git_odb__freshen(git_odb *db, const git_oid *id); - -/* fully free the object; internal method, DO NOT EXPORT */ -void git_odb_object__free(void *object); - -#endif diff --git a/src/odb_loose.c b/src/odb_loose.c deleted file mode 100644 index 463e24fa5..000000000 --- a/src/odb_loose.c +++ /dev/null @@ -1,1182 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include -#include "git2/object.h" -#include "git2/sys/odb_backend.h" -#include "futils.h" -#include "hash.h" -#include "odb.h" -#include "delta.h" -#include "filebuf.h" -#include "object.h" -#include "zstream.h" - -#include "git2/odb_backend.h" -#include "git2/types.h" - -/* maximum possible header length */ -#define MAX_HEADER_LEN 64 - -typedef struct { /* object header data */ - git_object_t type; /* object type */ - size_t size; /* object size */ -} obj_hdr; - -typedef struct { - git_odb_stream stream; - git_filebuf fbuf; -} loose_writestream; - -typedef struct { - git_odb_stream stream; - git_map map; - char start[MAX_HEADER_LEN]; - size_t start_len; - size_t start_read; - git_zstream zstream; -} loose_readstream; - -typedef struct loose_backend { - git_odb_backend parent; - - int object_zlib_level; /** loose object zlib compression level. */ - int fsync_object_files; /** loose object file fsync flag. */ - mode_t object_file_mode; - mode_t object_dir_mode; - - size_t objects_dirlen; - char objects_dir[GIT_FLEX_ARRAY]; -} loose_backend; - -/* State structure for exploring directories, - * in order to locate objects matching a short oid. - */ -typedef struct { - size_t dir_len; - unsigned char short_oid[GIT_OID_HEXSZ]; /* hex formatted oid to match */ - size_t short_oid_len; - int found; /* number of matching - * objects already found */ - unsigned char res_oid[GIT_OID_HEXSZ]; /* hex formatted oid of - * the object found */ -} loose_locate_object_state; - - -/*********************************************************** - * - * MISCELLANEOUS HELPER FUNCTIONS - * - ***********************************************************/ - -static int object_file_name( - git_str *name, const loose_backend *be, const git_oid *id) -{ - size_t alloclen; - - /* expand length for object root + 40 hex sha1 chars + 2 * '/' + '\0' */ - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, be->objects_dirlen, GIT_OID_HEXSZ); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 3); - if (git_str_grow(name, alloclen) < 0) - return -1; - - git_str_set(name, be->objects_dir, be->objects_dirlen); - git_fs_path_to_dir(name); - - /* loose object filename: aa/aaa... (41 bytes) */ - git_oid_pathfmt(name->ptr + name->size, id); - name->size += GIT_OID_HEXSZ + 1; - name->ptr[name->size] = '\0'; - - return 0; -} - -static int object_mkdir(const git_str *name, const loose_backend *be) -{ - return git_futils_mkdir_relative( - name->ptr + be->objects_dirlen, be->objects_dir, be->object_dir_mode, - GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR, NULL); -} - -static int parse_header_packlike( - obj_hdr *out, size_t *out_len, const unsigned char *data, size_t len) -{ - unsigned long c; - size_t shift, size, used = 0; - - if (len == 0) - goto on_error; - - c = data[used++]; - out->type = (c >> 4) & 7; - - size = c & 15; - shift = 4; - while (c & 0x80) { - if (len <= used) - goto on_error; - - if (sizeof(size_t) * 8 <= shift) - goto on_error; - - c = data[used++]; - size += (c & 0x7f) << shift; - shift += 7; - } - - out->size = size; - - if (out_len) - *out_len = used; - - return 0; - -on_error: - git_error_set(GIT_ERROR_OBJECT, "failed to parse loose object: invalid header"); - return -1; -} - -static int parse_header( - obj_hdr *out, - size_t *out_len, - const unsigned char *_data, - size_t data_len) -{ - const char *data = (char *)_data; - size_t i, typename_len, size_idx, size_len; - int64_t size; - - *out_len = 0; - - /* find the object type name */ - for (i = 0, typename_len = 0; i < data_len; i++, typename_len++) { - if (data[i] == ' ') - break; - } - - if (typename_len == data_len) - goto on_error; - - out->type = git_object_stringn2type(data, typename_len); - - size_idx = typename_len + 1; - for (i = size_idx, size_len = 0; i < data_len; i++, size_len++) { - if (data[i] == '\0') - break; - } - - if (i == data_len) - goto on_error; - - if (git__strntol64(&size, &data[size_idx], size_len, NULL, 10) < 0 || - size < 0) - goto on_error; - - if ((uint64_t)size > SIZE_MAX) { - git_error_set(GIT_ERROR_OBJECT, "object is larger than available memory"); - return -1; - } - - out->size = (size_t)size; - - if (GIT_ADD_SIZET_OVERFLOW(out_len, i, 1)) - goto on_error; - - return 0; - -on_error: - git_error_set(GIT_ERROR_OBJECT, "failed to parse loose object: invalid header"); - return -1; -} - -static int is_zlib_compressed_data(unsigned char *data, size_t data_len) -{ - unsigned int w; - - if (data_len < 2) - return 0; - - w = ((unsigned int)(data[0]) << 8) + data[1]; - return (data[0] & 0x8F) == 0x08 && !(w % 31); -} - -/*********************************************************** - * - * ODB OBJECT READING & WRITING - * - * Backend for the public API; read headers and full objects - * from the ODB. Write raw data to the ODB. - * - ***********************************************************/ - - -/* - * At one point, there was a loose object format that was intended to - * mimic the format used in pack-files. This was to allow easy copying - * of loose object data into packs. This format is no longer used, but - * we must still read it. - */ -static int read_loose_packlike(git_rawobj *out, git_str *obj) -{ - git_str body = GIT_STR_INIT; - const unsigned char *obj_data; - obj_hdr hdr; - size_t obj_len, head_len, alloc_size; - int error; - - obj_data = (unsigned char *)obj->ptr; - obj_len = obj->size; - - /* - * read the object header, which is an (uncompressed) - * binary encoding of the object type and size. - */ - if ((error = parse_header_packlike(&hdr, &head_len, obj_data, obj_len)) < 0) - goto done; - - if (!git_object_typeisloose(hdr.type) || head_len > obj_len) { - git_error_set(GIT_ERROR_ODB, "failed to inflate loose object"); - error = -1; - goto done; - } - - obj_data += head_len; - obj_len -= head_len; - - /* - * allocate a buffer and inflate the data into it - */ - if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, hdr.size, 1) || - git_str_init(&body, alloc_size) < 0) { - error = -1; - goto done; - } - - if ((error = git_zstream_inflatebuf(&body, obj_data, obj_len)) < 0) - goto done; - - out->len = hdr.size; - out->type = hdr.type; - out->data = git_str_detach(&body); - -done: - git_str_dispose(&body); - return error; -} - -static int read_loose_standard(git_rawobj *out, git_str *obj) -{ - git_zstream zstream = GIT_ZSTREAM_INIT; - unsigned char head[MAX_HEADER_LEN], *body = NULL; - size_t decompressed, head_len, body_len, alloc_size; - obj_hdr hdr; - int error; - - if ((error = git_zstream_init(&zstream, GIT_ZSTREAM_INFLATE)) < 0 || - (error = git_zstream_set_input(&zstream, git_str_cstr(obj), git_str_len(obj))) < 0) - goto done; - - decompressed = sizeof(head); - - /* - * inflate the initial part of the compressed buffer in order to - * parse the header; read the largest header possible, then push the - * remainder into the body buffer. - */ - if ((error = git_zstream_get_output(head, &decompressed, &zstream)) < 0 || - (error = parse_header(&hdr, &head_len, head, decompressed)) < 0) - goto done; - - if (!git_object_typeisloose(hdr.type)) { - git_error_set(GIT_ERROR_ODB, "failed to inflate disk object"); - error = -1; - goto done; - } - - /* - * allocate a buffer and inflate the object data into it - * (including the initial sequence in the head buffer). - */ - if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, hdr.size, 1) || - (body = git__calloc(1, alloc_size)) == NULL) { - error = -1; - goto done; - } - - GIT_ASSERT(decompressed >= head_len); - body_len = decompressed - head_len; - - if (body_len) - memcpy(body, head + head_len, body_len); - - decompressed = hdr.size - body_len; - if ((error = git_zstream_get_output(body + body_len, &decompressed, &zstream)) < 0) - goto done; - - if (!git_zstream_done(&zstream)) { - git_error_set(GIT_ERROR_ZLIB, "failed to finish zlib inflation: stream aborted prematurely"); - error = -1; - goto done; - } - - body[hdr.size] = '\0'; - - out->data = body; - out->len = hdr.size; - out->type = hdr.type; - -done: - if (error < 0) - git__free(body); - - git_zstream_free(&zstream); - return error; -} - -static int read_loose(git_rawobj *out, git_str *loc) -{ - int error; - git_str obj = GIT_STR_INIT; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(loc); - - if (git_str_oom(loc)) - return -1; - - out->data = NULL; - out->len = 0; - out->type = GIT_OBJECT_INVALID; - - if ((error = git_futils_readbuffer(&obj, loc->ptr)) < 0) - goto done; - - if (!is_zlib_compressed_data((unsigned char *)obj.ptr, obj.size)) - error = read_loose_packlike(out, &obj); - else - error = read_loose_standard(out, &obj); - -done: - git_str_dispose(&obj); - return error; -} - -static int read_header_loose_packlike( - git_rawobj *out, const unsigned char *data, size_t len) -{ - obj_hdr hdr; - size_t header_len; - int error; - - if ((error = parse_header_packlike(&hdr, &header_len, data, len)) < 0) - return error; - - out->len = hdr.size; - out->type = hdr.type; - - return error; -} - -static int read_header_loose_standard( - git_rawobj *out, const unsigned char *data, size_t len) -{ - git_zstream zs = GIT_ZSTREAM_INIT; - obj_hdr hdr = {0}; - unsigned char inflated[MAX_HEADER_LEN] = {0}; - size_t header_len, inflated_len = sizeof(inflated); - int error; - - if ((error = git_zstream_init(&zs, GIT_ZSTREAM_INFLATE)) < 0 || - (error = git_zstream_set_input(&zs, data, len)) < 0 || - (error = git_zstream_get_output_chunk(inflated, &inflated_len, &zs)) < 0 || - (error = parse_header(&hdr, &header_len, inflated, inflated_len)) < 0) - goto done; - - out->len = hdr.size; - out->type = hdr.type; - -done: - git_zstream_free(&zs); - return error; -} - -static int read_header_loose(git_rawobj *out, git_str *loc) -{ - unsigned char obj[1024]; - ssize_t obj_len; - int fd, error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(loc); - - if (git_str_oom(loc)) - return -1; - - out->data = NULL; - - if ((error = fd = git_futils_open_ro(loc->ptr)) < 0) - goto done; - - if ((obj_len = p_read(fd, obj, sizeof(obj))) < 0) { - error = (int)obj_len; - goto done; - } - - if (!is_zlib_compressed_data(obj, (size_t)obj_len)) - error = read_header_loose_packlike(out, obj, (size_t)obj_len); - else - error = read_header_loose_standard(out, obj, (size_t)obj_len); - - if (!error && !git_object_typeisloose(out->type)) { - git_error_set(GIT_ERROR_ZLIB, "failed to read loose object header"); - error = -1; - goto done; - } - -done: - if (fd >= 0) - p_close(fd); - return error; -} - -static int locate_object( - git_str *object_location, - loose_backend *backend, - const git_oid *oid) -{ - int error = object_file_name(object_location, backend, oid); - - if (!error && !git_fs_path_exists(object_location->ptr)) - return GIT_ENOTFOUND; - - return error; -} - -/* Explore an entry of a directory and see if it matches a short oid */ -static int fn_locate_object_short_oid(void *state, git_str *pathbuf) { - loose_locate_object_state *sstate = (loose_locate_object_state *)state; - - if (git_str_len(pathbuf) - sstate->dir_len != GIT_OID_HEXSZ - 2) { - /* Entry cannot be an object. Continue to next entry */ - return 0; - } - - if (git_fs_path_isdir(pathbuf->ptr) == false) { - /* We are already in the directory matching the 2 first hex characters, - * compare the first ncmp characters of the oids */ - if (!memcmp(sstate->short_oid + 2, - (unsigned char *)pathbuf->ptr + sstate->dir_len, - sstate->short_oid_len - 2)) { - - if (!sstate->found) { - sstate->res_oid[0] = sstate->short_oid[0]; - sstate->res_oid[1] = sstate->short_oid[1]; - memcpy(sstate->res_oid+2, pathbuf->ptr+sstate->dir_len, GIT_OID_HEXSZ-2); - } - sstate->found++; - } - } - - if (sstate->found > 1) - return GIT_EAMBIGUOUS; - - return 0; -} - -/* Locate an object matching a given short oid */ -static int locate_object_short_oid( - git_str *object_location, - git_oid *res_oid, - loose_backend *backend, - const git_oid *short_oid, - size_t len) -{ - char *objects_dir = backend->objects_dir; - size_t dir_len = strlen(objects_dir), alloc_len; - loose_locate_object_state state; - int error; - - /* prealloc memory for OBJ_DIR/xx/xx..38x..xx */ - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, dir_len, GIT_OID_HEXSZ); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 3); - if (git_str_grow(object_location, alloc_len) < 0) - return -1; - - git_str_set(object_location, objects_dir, dir_len); - git_fs_path_to_dir(object_location); - - /* save adjusted position at end of dir so it can be restored later */ - dir_len = git_str_len(object_location); - - /* Convert raw oid to hex formatted oid */ - git_oid_fmt((char *)state.short_oid, short_oid); - - /* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */ - if (git_str_put(object_location, (char *)state.short_oid, 3) < 0) - return -1; - object_location->ptr[object_location->size - 1] = '/'; - - /* Check that directory exists */ - if (git_fs_path_isdir(object_location->ptr) == false) - return git_odb__error_notfound("no matching loose object for prefix", - short_oid, len); - - state.dir_len = git_str_len(object_location); - state.short_oid_len = len; - state.found = 0; - - /* Explore directory to find a unique object matching short_oid */ - error = git_fs_path_direach( - object_location, 0, fn_locate_object_short_oid, &state); - if (error < 0 && error != GIT_EAMBIGUOUS) - return error; - - if (!state.found) - return git_odb__error_notfound("no matching loose object for prefix", - short_oid, len); - - if (state.found > 1) - return git_odb__error_ambiguous("multiple matches in loose objects"); - - /* Convert obtained hex formatted oid to raw */ - error = git_oid_fromstr(res_oid, (char *)state.res_oid); - if (error) - return error; - - /* Update the location according to the oid obtained */ - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, dir_len, GIT_OID_HEXSZ); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); - - git_str_truncate(object_location, dir_len); - if (git_str_grow(object_location, alloc_len) < 0) - return -1; - - git_oid_pathfmt(object_location->ptr + dir_len, res_oid); - - object_location->size += GIT_OID_HEXSZ + 1; - object_location->ptr[object_location->size] = '\0'; - - return 0; -} - - - - - - - - - -/*********************************************************** - * - * LOOSE BACKEND PUBLIC API - * - * Implement the git_odb_backend API calls - * - ***********************************************************/ - -static int loose_backend__read_header(size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) -{ - git_str object_path = GIT_STR_INIT; - git_rawobj raw; - int error; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(oid); - - raw.len = 0; - raw.type = GIT_OBJECT_INVALID; - - if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) { - error = git_odb__error_notfound("no matching loose object", - oid, GIT_OID_HEXSZ); - } else if ((error = read_header_loose(&raw, &object_path)) == 0) { - *len_p = raw.len; - *type_p = raw.type; - } - - git_str_dispose(&object_path); - - return error; -} - -static int loose_backend__read(void **buffer_p, size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) -{ - git_str object_path = GIT_STR_INIT; - git_rawobj raw; - int error = 0; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(oid); - - if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) { - error = git_odb__error_notfound("no matching loose object", - oid, GIT_OID_HEXSZ); - } else if ((error = read_loose(&raw, &object_path)) == 0) { - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; - } - - git_str_dispose(&object_path); - - return error; -} - -static int loose_backend__read_prefix( - git_oid *out_oid, - void **buffer_p, - size_t *len_p, - git_object_t *type_p, - git_odb_backend *backend, - const git_oid *short_oid, - size_t len) -{ - int error = 0; - - GIT_ASSERT_ARG(len >= GIT_OID_MINPREFIXLEN && len <= GIT_OID_HEXSZ); - - if (len == GIT_OID_HEXSZ) { - /* We can fall back to regular read method */ - error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid); - if (!error) - git_oid_cpy(out_oid, short_oid); - } else { - git_str object_path = GIT_STR_INIT; - git_rawobj raw; - - GIT_ASSERT_ARG(backend && short_oid); - - if ((error = locate_object_short_oid(&object_path, out_oid, - (loose_backend *)backend, short_oid, len)) == 0 && - (error = read_loose(&raw, &object_path)) == 0) - { - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; - } - - git_str_dispose(&object_path); - } - - return error; -} - -static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid) -{ - git_str object_path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(oid); - - error = locate_object(&object_path, (loose_backend *)backend, oid); - - git_str_dispose(&object_path); - - return !error; -} - -static int loose_backend__exists_prefix( - git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len) -{ - git_str object_path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(short_id); - GIT_ASSERT_ARG(len >= GIT_OID_MINPREFIXLEN); - - error = locate_object_short_oid( - &object_path, out, (loose_backend *)backend, short_id, len); - - git_str_dispose(&object_path); - - return error; -} - -struct foreach_state { - size_t dir_len; - git_odb_foreach_cb cb; - void *data; -}; - -GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr) -{ - int v, i = 0; - if (strlen(ptr) != GIT_OID_HEXSZ+1) - return -1; - - if (ptr[2] != '/') { - return -1; - } - - v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i+1]); - if (v < 0) - return -1; - - oid->id[0] = (unsigned char) v; - - ptr += 3; - for (i = 0; i < 38; i += 2) { - v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i + 1]); - if (v < 0) - return -1; - - oid->id[1 + i/2] = (unsigned char) v; - } - - return 0; -} - -static int foreach_object_dir_cb(void *_state, git_str *path) -{ - git_oid oid; - struct foreach_state *state = (struct foreach_state *) _state; - - if (filename_to_oid(&oid, path->ptr + state->dir_len) < 0) - return 0; - - return git_error_set_after_callback_function( - state->cb(&oid, state->data), "git_odb_foreach"); -} - -static int foreach_cb(void *_state, git_str *path) -{ - struct foreach_state *state = (struct foreach_state *) _state; - - /* non-dir is some stray file, ignore it */ - if (!git_fs_path_isdir(git_str_cstr(path))) - return 0; - - return git_fs_path_direach(path, 0, foreach_object_dir_cb, state); -} - -static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data) -{ - char *objects_dir; - int error; - git_str buf = GIT_STR_INIT; - struct foreach_state state; - loose_backend *backend = (loose_backend *) _backend; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(cb); - - objects_dir = backend->objects_dir; - - git_str_sets(&buf, objects_dir); - git_fs_path_to_dir(&buf); - if (git_str_oom(&buf)) - return -1; - - memset(&state, 0, sizeof(state)); - state.cb = cb; - state.data = data; - state.dir_len = git_str_len(&buf); - - error = git_fs_path_direach(&buf, 0, foreach_cb, &state); - - git_str_dispose(&buf); - - return error; -} - -static int loose_backend__writestream_finalize(git_odb_stream *_stream, const git_oid *oid) -{ - loose_writestream *stream = (loose_writestream *)_stream; - loose_backend *backend = (loose_backend *)_stream->backend; - git_str final_path = GIT_STR_INIT; - int error = 0; - - if (object_file_name(&final_path, backend, oid) < 0 || - object_mkdir(&final_path, backend) < 0) - error = -1; - else - error = git_filebuf_commit_at( - &stream->fbuf, final_path.ptr); - - git_str_dispose(&final_path); - - return error; -} - -static int loose_backend__writestream_write(git_odb_stream *_stream, const char *data, size_t len) -{ - loose_writestream *stream = (loose_writestream *)_stream; - return git_filebuf_write(&stream->fbuf, data, len); -} - -static void loose_backend__writestream_free(git_odb_stream *_stream) -{ - loose_writestream *stream = (loose_writestream *)_stream; - - git_filebuf_cleanup(&stream->fbuf); - git__free(stream); -} - -static int filebuf_flags(loose_backend *backend) -{ - int flags = GIT_FILEBUF_TEMPORARY | - (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT); - - if (backend->fsync_object_files || git_repository__fsync_gitdir) - flags |= GIT_FILEBUF_FSYNC; - - return flags; -} - -static int loose_backend__writestream(git_odb_stream **stream_out, git_odb_backend *_backend, git_object_size_t length, git_object_t type) -{ - loose_backend *backend; - loose_writestream *stream = NULL; - char hdr[MAX_HEADER_LEN]; - git_str tmp_path = GIT_STR_INIT; - size_t hdrlen; - int error; - - GIT_ASSERT_ARG(_backend); - - backend = (loose_backend *)_backend; - *stream_out = NULL; - - if ((error = git_odb__format_object_header(&hdrlen, - hdr, sizeof(hdr), length, type)) < 0) - return error; - - stream = git__calloc(1, sizeof(loose_writestream)); - GIT_ERROR_CHECK_ALLOC(stream); - - stream->stream.backend = _backend; - stream->stream.read = NULL; /* read only */ - stream->stream.write = &loose_backend__writestream_write; - stream->stream.finalize_write = &loose_backend__writestream_finalize; - stream->stream.free = &loose_backend__writestream_free; - stream->stream.mode = GIT_STREAM_WRONLY; - - if (git_str_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 || - git_filebuf_open(&stream->fbuf, tmp_path.ptr, filebuf_flags(backend), - backend->object_file_mode) < 0 || - stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0) - { - git_filebuf_cleanup(&stream->fbuf); - git__free(stream); - stream = NULL; - } - git_str_dispose(&tmp_path); - *stream_out = (git_odb_stream *)stream; - - return !stream ? -1 : 0; -} - -static int loose_backend__readstream_read( - git_odb_stream *_stream, - char *buffer, - size_t buffer_len) -{ - loose_readstream *stream = (loose_readstream *)_stream; - size_t start_remain = stream->start_len - stream->start_read; - int total = 0, error; - - buffer_len = min(buffer_len, INT_MAX); - - /* - * if we read more than just the header in the initial read, play - * that back for the caller. - */ - if (start_remain && buffer_len) { - size_t chunk = min(start_remain, buffer_len); - memcpy(buffer, stream->start + stream->start_read, chunk); - - buffer += chunk; - stream->start_read += chunk; - - total += (int)chunk; - buffer_len -= chunk; - } - - if (buffer_len) { - size_t chunk = buffer_len; - - if ((error = git_zstream_get_output(buffer, &chunk, &stream->zstream)) < 0) - return error; - - total += (int)chunk; - } - - return (int)total; -} - -static void loose_backend__readstream_free(git_odb_stream *_stream) -{ - loose_readstream *stream = (loose_readstream *)_stream; - - git_futils_mmap_free(&stream->map); - git_zstream_free(&stream->zstream); - git__free(stream); -} - -static int loose_backend__readstream_packlike( - obj_hdr *hdr, - loose_readstream *stream) -{ - const unsigned char *data; - size_t data_len, head_len; - int error; - - data = stream->map.data; - data_len = stream->map.len; - - /* - * read the object header, which is an (uncompressed) - * binary encoding of the object type and size. - */ - if ((error = parse_header_packlike(hdr, &head_len, data, data_len)) < 0) - return error; - - if (!git_object_typeisloose(hdr->type)) { - git_error_set(GIT_ERROR_ODB, "failed to inflate loose object"); - return -1; - } - - return git_zstream_set_input(&stream->zstream, - data + head_len, data_len - head_len); -} - -static int loose_backend__readstream_standard( - obj_hdr *hdr, - loose_readstream *stream) -{ - unsigned char head[MAX_HEADER_LEN]; - size_t init, head_len; - int error; - - if ((error = git_zstream_set_input(&stream->zstream, - stream->map.data, stream->map.len)) < 0) - return error; - - init = sizeof(head); - - /* - * inflate the initial part of the compressed buffer in order to - * parse the header; read the largest header possible, then store - * it in the `start` field of the stream object. - */ - if ((error = git_zstream_get_output(head, &init, &stream->zstream)) < 0 || - (error = parse_header(hdr, &head_len, head, init)) < 0) - return error; - - if (!git_object_typeisloose(hdr->type)) { - git_error_set(GIT_ERROR_ODB, "failed to inflate disk object"); - return -1; - } - - if (init > head_len) { - stream->start_len = init - head_len; - memcpy(stream->start, head + head_len, init - head_len); - } - - return 0; -} - -static int loose_backend__readstream( - git_odb_stream **stream_out, - size_t *len_out, - git_object_t *type_out, - git_odb_backend *_backend, - const git_oid *oid) -{ - loose_backend *backend; - loose_readstream *stream = NULL; - git_hash_ctx *hash_ctx = NULL; - git_str object_path = GIT_STR_INIT; - obj_hdr hdr; - int error = 0; - - GIT_ASSERT_ARG(stream_out); - GIT_ASSERT_ARG(len_out); - GIT_ASSERT_ARG(type_out); - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(oid); - - backend = (loose_backend *)_backend; - *stream_out = NULL; - *len_out = 0; - *type_out = GIT_OBJECT_INVALID; - - if (locate_object(&object_path, backend, oid) < 0) { - error = git_odb__error_notfound("no matching loose object", - oid, GIT_OID_HEXSZ); - goto done; - } - - stream = git__calloc(1, sizeof(loose_readstream)); - GIT_ERROR_CHECK_ALLOC(stream); - - hash_ctx = git__malloc(sizeof(git_hash_ctx)); - GIT_ERROR_CHECK_ALLOC(hash_ctx); - - if ((error = git_hash_ctx_init(hash_ctx, GIT_HASH_ALGORITHM_SHA1)) < 0 || - (error = git_futils_mmap_ro_file(&stream->map, object_path.ptr)) < 0 || - (error = git_zstream_init(&stream->zstream, GIT_ZSTREAM_INFLATE)) < 0) - goto done; - - /* check for a packlike loose object */ - if (!is_zlib_compressed_data(stream->map.data, stream->map.len)) - error = loose_backend__readstream_packlike(&hdr, stream); - else - error = loose_backend__readstream_standard(&hdr, stream); - - if (error < 0) - goto done; - - stream->stream.backend = _backend; - stream->stream.hash_ctx = hash_ctx; - stream->stream.read = &loose_backend__readstream_read; - stream->stream.free = &loose_backend__readstream_free; - - *stream_out = (git_odb_stream *)stream; - *len_out = hdr.size; - *type_out = hdr.type; - -done: - if (error < 0) { - if (stream) { - git_futils_mmap_free(&stream->map); - git_zstream_free(&stream->zstream); - git__free(stream); - } - if (hash_ctx) { - git_hash_ctx_cleanup(hash_ctx); - git__free(hash_ctx); - } - } - - git_str_dispose(&object_path); - return error; -} - -static int loose_backend__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_object_t type) -{ - int error = 0; - git_str final_path = GIT_STR_INIT; - char header[MAX_HEADER_LEN]; - size_t header_len; - git_filebuf fbuf = GIT_FILEBUF_INIT; - loose_backend *backend; - - backend = (loose_backend *)_backend; - - /* prepare the header for the file */ - if ((error = git_odb__format_object_header(&header_len, - header, sizeof(header), len, type)) < 0) - goto cleanup; - - if (git_str_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 || - git_filebuf_open(&fbuf, final_path.ptr, filebuf_flags(backend), - backend->object_file_mode) < 0) - { - error = -1; - goto cleanup; - } - - git_filebuf_write(&fbuf, header, header_len); - git_filebuf_write(&fbuf, data, len); - - if (object_file_name(&final_path, backend, oid) < 0 || - object_mkdir(&final_path, backend) < 0 || - git_filebuf_commit_at(&fbuf, final_path.ptr) < 0) - error = -1; - -cleanup: - if (error < 0) - git_filebuf_cleanup(&fbuf); - git_str_dispose(&final_path); - return error; -} - -static int loose_backend__freshen( - git_odb_backend *_backend, - const git_oid *oid) -{ - loose_backend *backend = (loose_backend *)_backend; - git_str path = GIT_STR_INIT; - int error; - - if (object_file_name(&path, backend, oid) < 0) - return -1; - - error = git_futils_touch(path.ptr, NULL); - git_str_dispose(&path); - - return error; -} - -static void loose_backend__free(git_odb_backend *_backend) -{ - git__free(_backend); -} - -int git_odb_backend_loose( - git_odb_backend **backend_out, - const char *objects_dir, - int compression_level, - int do_fsync, - unsigned int dir_mode, - unsigned int file_mode) -{ - loose_backend *backend; - size_t objects_dirlen, alloclen; - - GIT_ASSERT_ARG(backend_out); - GIT_ASSERT_ARG(objects_dir); - - objects_dirlen = strlen(objects_dir); - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(loose_backend), objects_dirlen); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 2); - backend = git__calloc(1, alloclen); - GIT_ERROR_CHECK_ALLOC(backend); - - backend->parent.version = GIT_ODB_BACKEND_VERSION; - backend->objects_dirlen = objects_dirlen; - memcpy(backend->objects_dir, objects_dir, objects_dirlen); - if (backend->objects_dir[backend->objects_dirlen - 1] != '/') - backend->objects_dir[backend->objects_dirlen++] = '/'; - - if (compression_level < 0) - compression_level = Z_BEST_SPEED; - - if (dir_mode == 0) - dir_mode = GIT_OBJECT_DIR_MODE; - - if (file_mode == 0) - file_mode = GIT_OBJECT_FILE_MODE; - - backend->object_zlib_level = compression_level; - backend->fsync_object_files = do_fsync; - backend->object_dir_mode = dir_mode; - backend->object_file_mode = file_mode; - - backend->parent.read = &loose_backend__read; - backend->parent.write = &loose_backend__write; - backend->parent.read_prefix = &loose_backend__read_prefix; - backend->parent.read_header = &loose_backend__read_header; - backend->parent.writestream = &loose_backend__writestream; - backend->parent.readstream = &loose_backend__readstream; - backend->parent.exists = &loose_backend__exists; - backend->parent.exists_prefix = &loose_backend__exists_prefix; - backend->parent.foreach = &loose_backend__foreach; - backend->parent.freshen = &loose_backend__freshen; - backend->parent.free = &loose_backend__free; - - *backend_out = (git_odb_backend *)backend; - return 0; -} diff --git a/src/odb_mempack.c b/src/odb_mempack.c deleted file mode 100644 index 6f27f45f8..000000000 --- a/src/odb_mempack.c +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "buf.h" -#include "futils.h" -#include "hash.h" -#include "odb.h" -#include "array.h" -#include "oidmap.h" -#include "pack-objects.h" - -#include "git2/odb_backend.h" -#include "git2/object.h" -#include "git2/types.h" -#include "git2/pack.h" -#include "git2/sys/odb_backend.h" -#include "git2/sys/mempack.h" - -struct memobject { - git_oid oid; - size_t len; - git_object_t type; - char data[GIT_FLEX_ARRAY]; -}; - -struct memory_packer_db { - git_odb_backend parent; - git_oidmap *objects; - git_array_t(struct memobject *) commits; -}; - -static int impl__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_object_t type) -{ - struct memory_packer_db *db = (struct memory_packer_db *)_backend; - struct memobject *obj = NULL; - size_t alloc_len; - - if (git_oidmap_exists(db->objects, oid)) - return 0; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(struct memobject), len); - obj = git__malloc(alloc_len); - GIT_ERROR_CHECK_ALLOC(obj); - - memcpy(obj->data, data, len); - git_oid_cpy(&obj->oid, oid); - obj->len = len; - obj->type = type; - - if (git_oidmap_set(db->objects, &obj->oid, obj) < 0) - return -1; - - if (type == GIT_OBJECT_COMMIT) { - struct memobject **store = git_array_alloc(db->commits); - GIT_ERROR_CHECK_ALLOC(store); - *store = obj; - } - - return 0; -} - -static int impl__exists(git_odb_backend *backend, const git_oid *oid) -{ - struct memory_packer_db *db = (struct memory_packer_db *)backend; - - return git_oidmap_exists(db->objects, oid); -} - -static int impl__read(void **buffer_p, size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) -{ - struct memory_packer_db *db = (struct memory_packer_db *)backend; - struct memobject *obj; - - if ((obj = git_oidmap_get(db->objects, oid)) == NULL) - return GIT_ENOTFOUND; - - *len_p = obj->len; - *type_p = obj->type; - *buffer_p = git__malloc(obj->len); - GIT_ERROR_CHECK_ALLOC(*buffer_p); - - memcpy(*buffer_p, obj->data, obj->len); - return 0; -} - -static int impl__read_header(size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) -{ - struct memory_packer_db *db = (struct memory_packer_db *)backend; - struct memobject *obj; - - if ((obj = git_oidmap_get(db->objects, oid)) == NULL) - return GIT_ENOTFOUND; - - *len_p = obj->len; - *type_p = obj->type; - return 0; -} - -static int git_mempack__dump( - git_str *pack, - git_repository *repo, - git_odb_backend *_backend) -{ - struct memory_packer_db *db = (struct memory_packer_db *)_backend; - git_packbuilder *packbuilder; - uint32_t i; - int err = -1; - - if (git_packbuilder_new(&packbuilder, repo) < 0) - return -1; - - git_packbuilder_set_threads(packbuilder, 0); - - for (i = 0; i < db->commits.size; ++i) { - struct memobject *commit = db->commits.ptr[i]; - - err = git_packbuilder_insert_commit(packbuilder, &commit->oid); - if (err < 0) - goto cleanup; - } - - err = git_packbuilder__write_buf(pack, packbuilder); - -cleanup: - git_packbuilder_free(packbuilder); - return err; -} - -int git_mempack_dump( - git_buf *pack, - git_repository *repo, - git_odb_backend *_backend) -{ - GIT_BUF_WRAP_PRIVATE(pack, git_mempack__dump, repo, _backend); -} - -int git_mempack_reset(git_odb_backend *_backend) -{ - struct memory_packer_db *db = (struct memory_packer_db *)_backend; - struct memobject *object = NULL; - - git_oidmap_foreach_value(db->objects, object, { - git__free(object); - }); - - git_array_clear(db->commits); - - git_oidmap_clear(db->objects); - - return 0; -} - -static void impl__free(git_odb_backend *_backend) -{ - struct memory_packer_db *db = (struct memory_packer_db *)_backend; - - git_mempack_reset(_backend); - git_oidmap_free(db->objects); - git__free(db); -} - -int git_mempack_new(git_odb_backend **out) -{ - struct memory_packer_db *db; - - GIT_ASSERT_ARG(out); - - db = git__calloc(1, sizeof(struct memory_packer_db)); - GIT_ERROR_CHECK_ALLOC(db); - - if (git_oidmap_new(&db->objects) < 0) - return -1; - - db->parent.version = GIT_ODB_BACKEND_VERSION; - db->parent.read = &impl__read; - db->parent.write = &impl__write; - db->parent.read_header = &impl__read_header; - db->parent.exists = &impl__exists; - db->parent.free = &impl__free; - - *out = (git_odb_backend *)db; - return 0; -} diff --git a/src/odb_pack.c b/src/odb_pack.c deleted file mode 100644 index 818cc6125..000000000 --- a/src/odb_pack.c +++ /dev/null @@ -1,921 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include -#include "git2/repository.h" -#include "git2/indexer.h" -#include "git2/sys/odb_backend.h" -#include "delta.h" -#include "futils.h" -#include "hash.h" -#include "midx.h" -#include "mwindow.h" -#include "odb.h" -#include "pack.h" - -#include "git2/odb_backend.h" - -/* re-freshen pack files no more than every 2 seconds */ -#define FRESHEN_FREQUENCY 2 - -struct pack_backend { - git_odb_backend parent; - git_midx_file *midx; - git_vector midx_packs; - git_vector packs; - struct git_pack_file *last_found; - char *pack_folder; -}; - -struct pack_writepack { - struct git_odb_writepack parent; - git_indexer *indexer; -}; - -/** - * The wonderful tale of a Packed Object lookup query - * =================================================== - * A riveting and epic story of epicness and ASCII - * art, presented by yours truly, - * Sir Vicent of Marti - * - * - * Chapter 1: Once upon a time... - * Initialization of the Pack Backend - * -------------------------------------------------- - * - * # git_odb_backend_pack - * | Creates the pack backend structure, initializes the - * | callback pointers to our default read() and exist() methods, - * | and tries to find the `pack` folder, if it exists. ODBs without a `pack` - * | folder are ignored altogether. If there is a `pack` folder, it tries to - * | preload all the known packfiles in the ODB. - * | - * |-# pack_backend__refresh - * | The `multi-pack-index` is loaded if it exists and is valid. - * | Then we run a `dirent` callback through every file in the pack folder, - * | even those present in `multi-pack-index`. The unindexed packfiles are - * | then sorted according to a sorting callback. - * | - * |-# refresh_multi_pack_index - * | Detect the presence of the `multi-pack-index` file. If it needs to be - * | refreshed, frees the old copy and tries to load the new one, together - * | with all the packfiles it indexes. If the process fails, fall back to - * | the old behavior, as if the `multi-pack-index` file was not there. - * | - * |-# packfile_load__cb - * | | This callback is called from `dirent` with every single file - * | | inside the pack folder. We find the packs by actually locating - * | | their index (ends in ".idx"). From that index, we verify that - * | | the corresponding packfile exists and is valid, and if so, we - * | | add it to the pack list. - * | | - * | # git_mwindow_get_pack - * | Make sure that there's a packfile to back this index, and store - * | some very basic information regarding the packfile itself, - * | such as the full path, the size, and the modification time. - * | We don't actually open the packfile to check for internal consistency. - * | - * |-# packfile_sort__cb - * Sort all the preloaded packs according to some specific criteria: - * we prioritize the "newer" packs because it's more likely they - * contain the objects we are looking for, and we prioritize local - * packs over remote ones. - * - * - * - * Chapter 2: To be, or not to be... - * A standard packed `exist` query for an OID - * -------------------------------------------------- - * - * # pack_backend__exists / pack_backend__exists_prefix - * | Check if the given SHA1 oid (or a SHA1 oid prefix) exists in any of the - * | packs that have been loaded for our ODB. - * | - * |-# pack_entry_find / pack_entry_find_prefix - * | If there is a multi-pack-index present, search the SHA1 oid in that - * | index first. If it is not found there, iterate through all the unindexed - * | packs that have been preloaded (starting by the pack where the latest - * | object was found) to try to find the OID in one of them. - * | - * |-# git_midx_entry_find - * | Search for the SHA1 oid in the multi-pack-index. See - * | - * | for specifics on the multi-pack-index format and how do we find - * | entries in it. - * | - * |-# git_pack_entry_find - * | Check the index of an individual unindexed pack to see if the SHA1 - * | OID can be found. If we can find the offset to that SHA1 inside of the - * | index, that means the object is contained inside of the packfile and - * | we can stop searching. Before returning, we verify that the - * | packfile behind the index we are searching still exists on disk. - * | - * |-# pack_entry_find_offset - * | Mmap the actual index file to disk if it hasn't been opened - * | yet, and run a binary search through it to find the OID. - * | See - * | for specifics on the Packfile Index format and how do we find - * | entries in it. - * | - * |-# pack_index_open - * | Guess the name of the index based on the full path to the - * | packfile, open it and verify its contents. Only if the index - * | has not been opened already. - * | - * |-# pack_index_check - * Mmap the index file and do a quick run through the header - * to guess the index version (right now we support v1 and v2), - * and to verify that the size of the index makes sense. - * - * - * - * Chapter 3: The neverending story... - * A standard packed `lookup` query for an OID - * -------------------------------------------------- - * - * # pack_backend__read / pack_backend__read_prefix - * | Check if the given SHA1 oid (or a SHA1 oid prefix) exists in any of the - * | packs that have been loaded for our ODB. If it does, open the packfile and - * | read from it. - * | - * |-# git_packfile_unpack - * Armed with a packfile and the offset within it, we can finally unpack - * the object pointed at by the SHA1 oid. This involves mmapping part of - * the `.pack` file, and uncompressing the object within it (if it is - * stored in the undelfitied representation), or finding a base object and - * applying some deltas to its uncompressed representation (if it is stored - * in the deltified representation). See - * - * for specifics on the Packfile format and how do we read from it. - * - */ - - -/*********************************************************** - * - * FORWARD DECLARATIONS - * - ***********************************************************/ - -static int packfile_sort__cb(const void *a_, const void *b_); - -static int packfile_load__cb(void *_data, git_str *path); - -static int packfile_byname_search_cmp(const void *path, const void *pack_entry); - -static int pack_entry_find(struct git_pack_entry *e, - struct pack_backend *backend, const git_oid *oid); - -/* Can find the offset of an object given - * a prefix of an identifier. - * Sets GIT_EAMBIGUOUS if short oid is ambiguous. - * This method assumes that len is between - * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. - */ -static int pack_entry_find_prefix( - struct git_pack_entry *e, - struct pack_backend *backend, - const git_oid *short_oid, - size_t len); - - - -/*********************************************************** - * - * PACK WINDOW MANAGEMENT - * - ***********************************************************/ - -static int packfile_byname_search_cmp(const void *path_, const void *p_) -{ - const git_str *path = (const git_str *)path_; - const struct git_pack_file *p = (const struct git_pack_file *)p_; - - return strncmp(p->pack_name, git_str_cstr(path), git_str_len(path)); -} - -static int packfile_sort__cb(const void *a_, const void *b_) -{ - const struct git_pack_file *a = a_; - const struct git_pack_file *b = b_; - int st; - - /* - * Local packs tend to contain objects specific to our - * variant of the project than remote ones. In addition, - * remote ones could be on a network mounted filesystem. - * Favor local ones for these reasons. - */ - st = a->pack_local - b->pack_local; - if (st) - return -st; - - /* - * Younger packs tend to contain more recent objects, - * and more recent objects tend to get accessed more - * often. - */ - if (a->mtime < b->mtime) - return 1; - else if (a->mtime == b->mtime) - return 0; - - return -1; -} - - -static int packfile_load__cb(void *data, git_str *path) -{ - struct pack_backend *backend = data; - struct git_pack_file *pack; - const char *path_str = git_str_cstr(path); - git_str index_prefix = GIT_STR_INIT; - size_t cmp_len = git_str_len(path); - int error; - - if (cmp_len <= strlen(".idx") || git__suffixcmp(path_str, ".idx") != 0) - return 0; /* not an index */ - - cmp_len -= strlen(".idx"); - git_str_attach_notowned(&index_prefix, path_str, cmp_len); - - if (git_vector_search2(NULL, &backend->midx_packs, packfile_byname_search_cmp, &index_prefix) == 0) - return 0; - if (git_vector_search2(NULL, &backend->packs, packfile_byname_search_cmp, &index_prefix) == 0) - return 0; - - error = git_mwindow_get_pack(&pack, path->ptr); - - /* ignore missing .pack file as git does */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - return 0; - } - - if (!error) - error = git_vector_insert(&backend->packs, pack); - - return error; - -} - -static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backend, const git_oid *oid) -{ - struct git_pack_file *last_found = backend->last_found, *p; - git_midx_entry midx_entry; - size_t i; - - if (backend->midx && - git_midx_entry_find(&midx_entry, backend->midx, oid, GIT_OID_HEXSZ) == 0 && - midx_entry.pack_index < git_vector_length(&backend->midx_packs)) { - e->offset = midx_entry.offset; - git_oid_cpy(&e->sha1, &midx_entry.sha1); - e->p = git_vector_get(&backend->midx_packs, midx_entry.pack_index); - return 0; - } - - if (last_found && - git_pack_entry_find(e, last_found, oid, GIT_OID_HEXSZ) == 0) - return 0; - - git_vector_foreach(&backend->packs, i, p) { - if (p == last_found) - continue; - - if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) { - backend->last_found = p; - return 0; - } - } - - return git_odb__error_notfound( - "failed to find pack entry", oid, GIT_OID_HEXSZ); -} - -static int pack_entry_find_prefix( - struct git_pack_entry *e, - struct pack_backend *backend, - const git_oid *short_oid, - size_t len) -{ - int error; - size_t i; - git_oid found_full_oid = {{0}}; - bool found = false; - struct git_pack_file *last_found = backend->last_found, *p; - git_midx_entry midx_entry; - - if (backend->midx) { - error = git_midx_entry_find(&midx_entry, backend->midx, short_oid, len); - if (error == GIT_EAMBIGUOUS) - return error; - if (!error && midx_entry.pack_index < git_vector_length(&backend->midx_packs)) { - e->offset = midx_entry.offset; - git_oid_cpy(&e->sha1, &midx_entry.sha1); - e->p = git_vector_get(&backend->midx_packs, midx_entry.pack_index); - git_oid_cpy(&found_full_oid, &e->sha1); - found = true; - } - } - - if (last_found) { - error = git_pack_entry_find(e, last_found, short_oid, len); - if (error == GIT_EAMBIGUOUS) - return error; - if (!error) { - if (found && git_oid_cmp(&e->sha1, &found_full_oid)) - return git_odb__error_ambiguous("found multiple pack entries"); - git_oid_cpy(&found_full_oid, &e->sha1); - found = true; - } - } - - git_vector_foreach(&backend->packs, i, p) { - if (p == last_found) - continue; - - error = git_pack_entry_find(e, p, short_oid, len); - if (error == GIT_EAMBIGUOUS) - return error; - if (!error) { - if (found && git_oid_cmp(&e->sha1, &found_full_oid)) - return git_odb__error_ambiguous("found multiple pack entries"); - git_oid_cpy(&found_full_oid, &e->sha1); - found = true; - backend->last_found = p; - } - } - - if (!found) - return git_odb__error_notfound("no matching pack entry for prefix", - short_oid, len); - else - return 0; -} - -/*********************************************************** - * - * MULTI-PACK-INDEX SUPPORT - * - * Functions needed to support the multi-pack-index. - * - ***********************************************************/ - -/* - * Remove the multi-pack-index, and move all midx_packs to packs. - */ -static int remove_multi_pack_index(struct pack_backend *backend) -{ - size_t i, j = git_vector_length(&backend->packs); - struct pack_backend *p; - int error = git_vector_size_hint( - &backend->packs, - j + git_vector_length(&backend->midx_packs)); - if (error < 0) - return error; - - git_vector_foreach(&backend->midx_packs, i, p) - git_vector_set(NULL, &backend->packs, j++, p); - git_vector_clear(&backend->midx_packs); - - git_midx_free(backend->midx); - backend->midx = NULL; - - return 0; -} - -/* - * Loads a single .pack file referred to by the multi-pack-index. These must - * match the order in which they are declared in the multi-pack-index file, - * since these files are referred to by their index. - */ -static int process_multi_pack_index_pack( - struct pack_backend *backend, - size_t i, - const char *packfile_name) -{ - int error; - struct git_pack_file *pack; - size_t found_position; - git_str pack_path = GIT_STR_INIT, index_prefix = GIT_STR_INIT; - - error = git_str_joinpath(&pack_path, backend->pack_folder, packfile_name); - if (error < 0) - return error; - - /* This is ensured by midx_parse_packfile_name() */ - if (git_str_len(&pack_path) <= strlen(".idx") || git__suffixcmp(git_str_cstr(&pack_path), ".idx") != 0) - return git_odb__error_notfound("midx file contained a non-index", NULL, 0); - - git_str_attach_notowned(&index_prefix, git_str_cstr(&pack_path), git_str_len(&pack_path) - strlen(".idx")); - - if (git_vector_search2(&found_position, &backend->packs, packfile_byname_search_cmp, &index_prefix) == 0) { - /* Pack was found in the packs list. Moving it to the midx_packs list. */ - git_str_dispose(&pack_path); - git_vector_set(NULL, &backend->midx_packs, i, git_vector_get(&backend->packs, found_position)); - git_vector_remove(&backend->packs, found_position); - return 0; - } - - /* Pack was not found. Allocate a new one. */ - error = git_mwindow_get_pack(&pack, git_str_cstr(&pack_path)); - git_str_dispose(&pack_path); - if (error < 0) - return error; - - git_vector_set(NULL, &backend->midx_packs, i, pack); - return 0; -} - -/* - * Reads the multi-pack-index. If this fails for whatever reason, the - * multi-pack-index object is freed, and all the packfiles that are related to - * it are moved to the unindexed packfiles vector. - */ -static int refresh_multi_pack_index(struct pack_backend *backend) -{ - int error; - git_str midx_path = GIT_STR_INIT; - const char *packfile_name; - size_t i; - - error = git_str_joinpath(&midx_path, backend->pack_folder, "multi-pack-index"); - if (error < 0) - return error; - - /* - * Check whether the multi-pack-index has changed. If it has, close any - * old multi-pack-index and move all the packfiles to the unindexed - * packs. This is done to prevent losing any open packfiles in case - * refreshing the new multi-pack-index fails, or the file is deleted. - */ - if (backend->midx) { - if (!git_midx_needs_refresh(backend->midx, git_str_cstr(&midx_path))) { - git_str_dispose(&midx_path); - return 0; - } - error = remove_multi_pack_index(backend); - if (error < 0) { - git_str_dispose(&midx_path); - return error; - } - } - - error = git_midx_open(&backend->midx, git_str_cstr(&midx_path)); - git_str_dispose(&midx_path); - if (error < 0) - return error; - - git_vector_resize_to(&backend->midx_packs, git_vector_length(&backend->midx->packfile_names)); - - git_vector_foreach(&backend->midx->packfile_names, i, packfile_name) { - error = process_multi_pack_index_pack(backend, i, packfile_name); - if (error < 0) { - /* - * Something failed during reading multi-pack-index. - * Restore the state of backend as if the - * multi-pack-index was never there, and move all - * packfiles that have been processed so far to the - * unindexed packs. - */ - git_vector_resize_to(&backend->midx_packs, i); - remove_multi_pack_index(backend); - return error; - } - } - - return 0; -} - -/*********************************************************** - * - * PACKED BACKEND PUBLIC API - * - * Implement the git_odb_backend API calls - * - ***********************************************************/ -static int pack_backend__refresh(git_odb_backend *backend_) -{ - int error; - struct stat st; - git_str path = GIT_STR_INIT; - struct pack_backend *backend = (struct pack_backend *)backend_; - - if (backend->pack_folder == NULL) - return 0; - - if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode)) - return git_odb__error_notfound("failed to refresh packfiles", NULL, 0); - - if (refresh_multi_pack_index(backend) < 0) { - /* - * It is okay if this fails. We will just not use the - * multi-pack-index in this case. - */ - git_error_clear(); - } - - /* reload all packs */ - git_str_sets(&path, backend->pack_folder); - error = git_fs_path_direach(&path, 0, packfile_load__cb, backend); - - git_str_dispose(&path); - git_vector_sort(&backend->packs); - - return error; -} - -static int pack_backend__read_header( - size_t *len_p, git_object_t *type_p, - struct git_odb_backend *backend, const git_oid *oid) -{ - struct git_pack_entry e; - int error; - - GIT_ASSERT_ARG(len_p); - GIT_ASSERT_ARG(type_p); - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(oid); - - if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0) - return error; - - return git_packfile_resolve_header(len_p, type_p, e.p, e.offset); -} - -static int pack_backend__freshen( - git_odb_backend *backend, const git_oid *oid) -{ - struct git_pack_entry e; - time_t now; - int error; - - if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0) - return error; - - now = time(NULL); - - if (e.p->last_freshen > now - FRESHEN_FREQUENCY) - return 0; - - if ((error = git_futils_touch(e.p->pack_name, &now)) < 0) - return error; - - e.p->last_freshen = now; - return 0; -} - -static int pack_backend__read( - void **buffer_p, size_t *len_p, git_object_t *type_p, - git_odb_backend *backend, const git_oid *oid) -{ - struct git_pack_entry e; - git_rawobj raw = {NULL}; - int error; - - if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0 || - (error = git_packfile_unpack(&raw, e.p, &e.offset)) < 0) - return error; - - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; - - return 0; -} - -static int pack_backend__read_prefix( - git_oid *out_oid, - void **buffer_p, - size_t *len_p, - git_object_t *type_p, - git_odb_backend *backend, - const git_oid *short_oid, - size_t len) -{ - int error = 0; - - if (len < GIT_OID_MINPREFIXLEN) - error = git_odb__error_ambiguous("prefix length too short"); - - else if (len >= GIT_OID_HEXSZ) { - /* We can fall back to regular read method */ - error = pack_backend__read(buffer_p, len_p, type_p, backend, short_oid); - if (!error) - git_oid_cpy(out_oid, short_oid); - } else { - struct git_pack_entry e; - git_rawobj raw = {NULL}; - - if ((error = pack_entry_find_prefix( - &e, (struct pack_backend *)backend, short_oid, len)) == 0 && - (error = git_packfile_unpack(&raw, e.p, &e.offset)) == 0) - { - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; - git_oid_cpy(out_oid, &e.sha1); - } - } - - return error; -} - -static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid) -{ - struct git_pack_entry e; - return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0; -} - -static int pack_backend__exists_prefix( - git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len) -{ - int error; - struct pack_backend *pb = (struct pack_backend *)backend; - struct git_pack_entry e = {0}; - - error = pack_entry_find_prefix(&e, pb, short_id, len); - git_oid_cpy(out, &e.sha1); - return error; -} - -static int pack_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data) -{ - int error; - struct git_pack_file *p; - struct pack_backend *backend; - unsigned int i; - - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(cb); - - backend = (struct pack_backend *)_backend; - - /* Make sure we know about the packfiles */ - if ((error = pack_backend__refresh(_backend)) != 0) - return error; - - if (backend->midx && (error = git_midx_foreach_entry(backend->midx, cb, data)) != 0) - return error; - git_vector_foreach(&backend->packs, i, p) { - if ((error = git_pack_foreach_entry(p, cb, data)) != 0) - return error; - } - - return 0; -} - -static int pack_backend__writepack_append(struct git_odb_writepack *_writepack, const void *data, size_t size, git_indexer_progress *stats) -{ - struct pack_writepack *writepack = (struct pack_writepack *)_writepack; - - GIT_ASSERT_ARG(writepack); - - return git_indexer_append(writepack->indexer, data, size, stats); -} - -static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, git_indexer_progress *stats) -{ - struct pack_writepack *writepack = (struct pack_writepack *)_writepack; - - GIT_ASSERT_ARG(writepack); - - return git_indexer_commit(writepack->indexer, stats); -} - -static void pack_backend__writepack_free(struct git_odb_writepack *_writepack) -{ - struct pack_writepack *writepack; - - if (!_writepack) - return; - - writepack = (struct pack_writepack *)_writepack; - - git_indexer_free(writepack->indexer); - git__free(writepack); -} - -static int pack_backend__writepack(struct git_odb_writepack **out, - git_odb_backend *_backend, - git_odb *odb, - git_indexer_progress_cb progress_cb, - void *progress_payload) -{ - git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; - struct pack_backend *backend; - struct pack_writepack *writepack; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(_backend); - - *out = NULL; - - opts.progress_cb = progress_cb; - opts.progress_cb_payload = progress_payload; - - backend = (struct pack_backend *)_backend; - - writepack = git__calloc(1, sizeof(struct pack_writepack)); - GIT_ERROR_CHECK_ALLOC(writepack); - - if (git_indexer_new(&writepack->indexer, - backend->pack_folder, 0, odb, &opts) < 0) { - git__free(writepack); - return -1; - } - - writepack->parent.backend = _backend; - writepack->parent.append = pack_backend__writepack_append; - writepack->parent.commit = pack_backend__writepack_commit; - writepack->parent.free = pack_backend__writepack_free; - - *out = (git_odb_writepack *)writepack; - - return 0; -} - -static int get_idx_path( - git_str *idx_path, - struct pack_backend *backend, - struct git_pack_file *p) -{ - size_t path_len; - int error; - - error = git_fs_path_prettify(idx_path, p->pack_name, backend->pack_folder); - if (error < 0) - return error; - path_len = git_str_len(idx_path); - if (path_len <= strlen(".pack") || git__suffixcmp(git_str_cstr(idx_path), ".pack") != 0) - return git_odb__error_notfound("packfile does not end in .pack", NULL, 0); - path_len -= strlen(".pack"); - error = git_str_splice(idx_path, path_len, strlen(".pack"), ".idx", strlen(".idx")); - if (error < 0) - return error; - - return 0; -} - -static int pack_backend__writemidx(git_odb_backend *_backend) -{ - struct pack_backend *backend; - git_midx_writer *w = NULL; - struct git_pack_file *p; - size_t i; - int error = 0; - - GIT_ASSERT_ARG(_backend); - - backend = (struct pack_backend *)_backend; - - error = git_midx_writer_new(&w, backend->pack_folder); - if (error < 0) - return error; - - git_vector_foreach(&backend->midx_packs, i, p) { - git_str idx_path = GIT_STR_INIT; - error = get_idx_path(&idx_path, backend, p); - if (error < 0) - goto cleanup; - error = git_midx_writer_add(w, git_str_cstr(&idx_path)); - git_str_dispose(&idx_path); - if (error < 0) - goto cleanup; - } - git_vector_foreach(&backend->packs, i, p) { - git_str idx_path = GIT_STR_INIT; - error = get_idx_path(&idx_path, backend, p); - if (error < 0) - goto cleanup; - error = git_midx_writer_add(w, git_str_cstr(&idx_path)); - git_str_dispose(&idx_path); - if (error < 0) - goto cleanup; - } - - /* - * Invalidate the previous midx before writing the new one. - */ - error = remove_multi_pack_index(backend); - if (error < 0) - goto cleanup; - error = git_midx_writer_commit(w); - if (error < 0) - goto cleanup; - error = refresh_multi_pack_index(backend); - -cleanup: - git_midx_writer_free(w); - return error; -} - -static void pack_backend__free(git_odb_backend *_backend) -{ - struct pack_backend *backend; - struct git_pack_file *p; - size_t i; - - if (!_backend) - return; - - backend = (struct pack_backend *)_backend; - - git_vector_foreach(&backend->midx_packs, i, p) - git_mwindow_put_pack(p); - git_vector_foreach(&backend->packs, i, p) - git_mwindow_put_pack(p); - - git_midx_free(backend->midx); - git_vector_free(&backend->midx_packs); - git_vector_free(&backend->packs); - git__free(backend->pack_folder); - git__free(backend); -} - -static int pack_backend__alloc(struct pack_backend **out, size_t initial_size) -{ - struct pack_backend *backend = git__calloc(1, sizeof(struct pack_backend)); - GIT_ERROR_CHECK_ALLOC(backend); - - if (git_vector_init(&backend->midx_packs, 0, NULL) < 0) { - git__free(backend); - return -1; - } - if (git_vector_init(&backend->packs, initial_size, packfile_sort__cb) < 0) { - git_vector_free(&backend->midx_packs); - git__free(backend); - return -1; - } - - backend->parent.version = GIT_ODB_BACKEND_VERSION; - - backend->parent.read = &pack_backend__read; - backend->parent.read_prefix = &pack_backend__read_prefix; - backend->parent.read_header = &pack_backend__read_header; - backend->parent.exists = &pack_backend__exists; - backend->parent.exists_prefix = &pack_backend__exists_prefix; - backend->parent.refresh = &pack_backend__refresh; - backend->parent.foreach = &pack_backend__foreach; - backend->parent.writepack = &pack_backend__writepack; - backend->parent.writemidx = &pack_backend__writemidx; - backend->parent.freshen = &pack_backend__freshen; - backend->parent.free = &pack_backend__free; - - *out = backend; - return 0; -} - -int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx) -{ - struct pack_backend *backend = NULL; - struct git_pack_file *packfile = NULL; - - if (pack_backend__alloc(&backend, 1) < 0) - return -1; - - if (git_mwindow_get_pack(&packfile, idx) < 0 || - git_vector_insert(&backend->packs, packfile) < 0) - { - pack_backend__free((git_odb_backend *)backend); - return -1; - } - - *backend_out = (git_odb_backend *)backend; - return 0; -} - -int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) -{ - int error = 0; - struct pack_backend *backend = NULL; - git_str path = GIT_STR_INIT; - - if (pack_backend__alloc(&backend, 8) < 0) - return -1; - - if (!(error = git_str_joinpath(&path, objects_dir, "pack")) && - git_fs_path_isdir(git_str_cstr(&path))) - { - backend->pack_folder = git_str_detach(&path); - error = pack_backend__refresh((git_odb_backend *)backend); - } - - if (error < 0) { - pack_backend__free((git_odb_backend *)backend); - backend = NULL; - } - - *backend_out = (git_odb_backend *)backend; - - git_str_dispose(&path); - - return error; -} diff --git a/src/offmap.c b/src/offmap.c deleted file mode 100644 index be9eb66d8..000000000 --- a/src/offmap.c +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "offmap.h" - -#define kmalloc git__malloc -#define kcalloc git__calloc -#define krealloc git__realloc -#define kreallocarray git__reallocarray -#define kfree git__free -#include "khash.h" - -__KHASH_TYPE(off, off64_t, void *) - -__KHASH_IMPL(off, static kh_inline, off64_t, void *, 1, kh_int64_hash_func, kh_int64_hash_equal) - - -int git_offmap_new(git_offmap **out) -{ - *out = kh_init(off); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -void git_offmap_free(git_offmap *map) -{ - kh_destroy(off, map); -} - -void git_offmap_clear(git_offmap *map) -{ - kh_clear(off, map); -} - -size_t git_offmap_size(git_offmap *map) -{ - return kh_size(map); -} - -void *git_offmap_get(git_offmap *map, const off64_t key) -{ - size_t idx = kh_get(off, map, key); - if (idx == kh_end(map) || !kh_exist(map, idx)) - return NULL; - return kh_val(map, idx); -} - -int git_offmap_set(git_offmap *map, const off64_t key, void *value) -{ - size_t idx; - int rval; - - idx = kh_put(off, map, key, &rval); - if (rval < 0) - return -1; - - if (rval == 0) - kh_key(map, idx) = key; - - kh_val(map, idx) = value; - - return 0; -} - -int git_offmap_delete(git_offmap *map, const off64_t key) -{ - khiter_t idx = kh_get(off, map, key); - if (idx == kh_end(map)) - return GIT_ENOTFOUND; - kh_del(off, map, idx); - return 0; -} - -int git_offmap_exists(git_offmap *map, const off64_t key) -{ - return kh_get(off, map, key) != kh_end(map); -} - -int git_offmap_iterate(void **value, git_offmap *map, size_t *iter, off64_t *key) -{ - size_t i = *iter; - - while (i < map->n_buckets && !kh_exist(map, i)) - i++; - - if (i >= map->n_buckets) - return GIT_ITEROVER; - - if (key) - *key = kh_key(map, i); - if (value) - *value = kh_value(map, i); - *iter = ++i; - - return 0; -} diff --git a/src/offmap.h b/src/offmap.h deleted file mode 100644 index 81c459b01..000000000 --- a/src/offmap.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_offmap_h__ -#define INCLUDE_offmap_h__ - -#include "common.h" - -#include "git2/types.h" - -/** A map with `off64_t`s as key. */ -typedef struct kh_off_s git_offmap; - -/** - * Allocate a new `off64_t` map. - * - * @param out Pointer to the map that shall be allocated. - * @return 0 on success, an error code if allocation has failed. - */ -int git_offmap_new(git_offmap **out); - -/** - * Free memory associated with the map. - * - * Note that this function will _not_ free values added to this - * map. - * - * @param map Pointer to the map that is to be free'd. May be - * `NULL`. - */ -void git_offmap_free(git_offmap *map); - -/** - * Clear all entries from the map. - * - * This function will remove all entries from the associated map. - * Memory associated with it will not be released, though. - * - * @param map Pointer to the map that shall be cleared. May be - * `NULL`. - */ -void git_offmap_clear(git_offmap *map); - -/** - * Return the number of elements in the map. - * - * @parameter map map containing the elements - * @return number of elements in the map - */ -size_t git_offmap_size(git_offmap *map); - -/** - * Return value associated with the given key. - * - * @param map map to search key in - * @param key key to search for - * @return value associated with the given key or NULL if the key was not found - */ -void *git_offmap_get(git_offmap *map, const off64_t key); - -/** - * Set the entry for key to value. - * - * If the map has no corresponding entry for the given key, a new - * entry will be created with the given value. If an entry exists - * already, its value will be updated to match the given value. - * - * @param map map to create new entry in - * @param key key to set - * @param value value to associate the key with; may be NULL - * @return zero if the key was successfully set, a negative error - * code otherwise - */ -int git_offmap_set(git_offmap *map, const off64_t key, void *value); - -/** - * Delete an entry from the map. - * - * Delete the given key and its value from the map. If no such - * key exists, this will do nothing. - * - * @param map map to delete key in - * @param key key to delete - * @return `0` if the key has been deleted, GIT_ENOTFOUND if no - * such key was found, a negative code in case of an - * error - */ -int git_offmap_delete(git_offmap *map, const off64_t key); - -/** - * Check whether a key exists in the given map. - * - * @param map map to query for the key - * @param key key to search for - * @return 0 if the key has not been found, 1 otherwise - */ -int git_offmap_exists(git_offmap *map, const off64_t key); - -/** - * Iterate over entries of the map. - * - * This functions allows to iterate over all key-value entries of - * the map. The current position is stored in the `iter` variable - * and should be initialized to `0` before the first call to this - * function. - * - * @param map map to iterate over - * @param value pointer to the variable where to store the current - * value. May be NULL. - * @param iter iterator storing the current position. Initialize - * with zero previous to the first call. - * @param key pointer to the variable where to store the current - * key. May be NULL. - * @return `0` if the next entry was correctly retrieved. - * GIT_ITEROVER if no entries are left. A negative error - * code otherwise. - */ -int git_offmap_iterate(void **value, git_offmap *map, size_t *iter, off64_t *key); - -#define git_offmap_foreach(h, kvar, vvar, code) { size_t __i = 0; \ - while (git_offmap_iterate((void **) &(vvar), h, &__i, &(kvar)) == 0) { \ - code; \ - } } - -#define git_offmap_foreach_value(h, vvar, code) { size_t __i = 0; \ - while (git_offmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ - code; \ - } } - -#endif diff --git a/src/oid.c b/src/oid.c deleted file mode 100644 index 19061e899..000000000 --- a/src/oid.c +++ /dev/null @@ -1,463 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "oid.h" - -#include "git2/oid.h" -#include "repository.h" -#include "threadstate.h" -#include -#include - -const git_oid git_oid__empty_blob_sha1 = - {{ 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, 0xd6, 0x43, 0x4b, 0x8b, - 0x29, 0xae, 0x77, 0x5a, 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91 }}; -const git_oid git_oid__empty_tree_sha1 = - {{ 0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, - 0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04 }}; - -static char to_hex[] = "0123456789abcdef"; - -static int oid_error_invalid(const char *msg) -{ - git_error_set(GIT_ERROR_INVALID, "unable to parse OID - %s", msg); - return -1; -} - -int git_oid_fromstrn(git_oid *out, const char *str, size_t length) -{ - size_t p; - int v; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(str); - - if (!length) - return oid_error_invalid("too short"); - - if (length > GIT_OID_HEXSZ) - return oid_error_invalid("too long"); - - memset(out->id, 0, GIT_OID_RAWSZ); - - for (p = 0; p < length; p++) { - v = git__fromhex(str[p]); - if (v < 0) - return oid_error_invalid("contains invalid characters"); - - out->id[p / 2] |= (unsigned char)(v << (p % 2 ? 0 : 4)); - } - - return 0; -} - -int git_oid_fromstrp(git_oid *out, const char *str) -{ - return git_oid_fromstrn(out, str, strlen(str)); -} - -int git_oid_fromstr(git_oid *out, const char *str) -{ - return git_oid_fromstrn(out, str, GIT_OID_HEXSZ); -} - -GIT_INLINE(char) *fmt_one(char *str, unsigned int val) -{ - *str++ = to_hex[val >> 4]; - *str++ = to_hex[val & 0xf]; - return str; -} - -int git_oid_nfmt(char *str, size_t n, const git_oid *oid) -{ - size_t i, max_i; - - if (!oid) { - memset(str, 0, n); - return 0; - } - if (n > GIT_OID_HEXSZ) { - memset(&str[GIT_OID_HEXSZ], 0, n - GIT_OID_HEXSZ); - n = GIT_OID_HEXSZ; - } - - max_i = n / 2; - - for (i = 0; i < max_i; i++) - str = fmt_one(str, oid->id[i]); - - if (n & 1) - *str++ = to_hex[oid->id[i] >> 4]; - - return 0; -} - -int git_oid_fmt(char *str, const git_oid *oid) -{ - return git_oid_nfmt(str, GIT_OID_HEXSZ, oid); -} - -int git_oid_pathfmt(char *str, const git_oid *oid) -{ - size_t i; - - str = fmt_one(str, oid->id[0]); - *str++ = '/'; - for (i = 1; i < sizeof(oid->id); i++) - str = fmt_one(str, oid->id[i]); - - return 0; -} - -char *git_oid_tostr_s(const git_oid *oid) -{ - char *str = GIT_THREADSTATE->oid_fmt; - git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid); - return str; -} - -char *git_oid_allocfmt(const git_oid *oid) -{ - char *str = git__malloc(GIT_OID_HEXSZ + 1); - if (!str) - return NULL; - git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid); - return str; -} - -char *git_oid_tostr(char *out, size_t n, const git_oid *oid) -{ - if (!out || n == 0) - return ""; - - if (n > GIT_OID_HEXSZ + 1) - n = GIT_OID_HEXSZ + 1; - - git_oid_nfmt(out, n - 1, oid); /* allow room for terminating NUL */ - out[n - 1] = '\0'; - - return out; -} - -int git_oid__parse( - git_oid *oid, const char **buffer_out, - const char *buffer_end, const char *header) -{ - const size_t sha_len = GIT_OID_HEXSZ; - const size_t header_len = strlen(header); - - const char *buffer = *buffer_out; - - if (buffer + (header_len + sha_len + 1) > buffer_end) - return -1; - - if (memcmp(buffer, header, header_len) != 0) - return -1; - - if (buffer[header_len + sha_len] != '\n') - return -1; - - if (git_oid_fromstr(oid, buffer + header_len) < 0) - return -1; - - *buffer_out = buffer + (header_len + sha_len + 1); - - return 0; -} - -void git_oid__writebuf(git_str *buf, const char *header, const git_oid *oid) -{ - char hex_oid[GIT_OID_HEXSZ]; - - git_oid_fmt(hex_oid, oid); - git_str_puts(buf, header); - git_str_put(buf, hex_oid, GIT_OID_HEXSZ); - git_str_putc(buf, '\n'); -} - -int git_oid_fromraw(git_oid *out, const unsigned char *raw) -{ - memcpy(out->id, raw, sizeof(out->id)); - return 0; -} - -int git_oid_cpy(git_oid *out, const git_oid *src) -{ - memcpy(out->id, src->id, sizeof(out->id)); - return 0; -} - -int git_oid_cmp(const git_oid *a, const git_oid *b) -{ - return git_oid__cmp(a, b); -} - -int git_oid_equal(const git_oid *a, const git_oid *b) -{ - return (git_oid__cmp(a, b) == 0); -} - -int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, size_t len) -{ - const unsigned char *a = oid_a->id; - const unsigned char *b = oid_b->id; - - if (len > GIT_OID_HEXSZ) - len = GIT_OID_HEXSZ; - - while (len > 1) { - if (*a != *b) - return 1; - a++; - b++; - len -= 2; - }; - - if (len) - if ((*a ^ *b) & 0xf0) - return 1; - - return 0; -} - -int git_oid_strcmp(const git_oid *oid_a, const char *str) -{ - const unsigned char *a; - unsigned char strval; - int hexval; - - for (a = oid_a->id; *str && (a - oid_a->id) < GIT_OID_RAWSZ; ++a) { - if ((hexval = git__fromhex(*str++)) < 0) - return -1; - strval = (unsigned char)(hexval << 4); - if (*str) { - if ((hexval = git__fromhex(*str++)) < 0) - return -1; - strval |= hexval; - } - if (*a != strval) - return (*a - strval); - } - - return 0; -} - -int git_oid_streq(const git_oid *oid_a, const char *str) -{ - return git_oid_strcmp(oid_a, str) == 0 ? 0 : -1; -} - -int git_oid_is_zero(const git_oid *oid_a) -{ - const unsigned char *a = oid_a->id; - unsigned int i; - for (i = 0; i < GIT_OID_RAWSZ; ++i, ++a) - if (*a != 0) - return 0; - return 1; -} - -#ifndef GIT_DEPRECATE_HARD -int git_oid_iszero(const git_oid *oid_a) -{ - return git_oid_is_zero(oid_a); -} -#endif - -typedef short node_index; - -typedef union { - const char *tail; - node_index children[16]; -} trie_node; - -struct git_oid_shorten { - trie_node *nodes; - size_t node_count, size; - int min_length, full; -}; - -static int resize_trie(git_oid_shorten *self, size_t new_size) -{ - self->nodes = git__reallocarray(self->nodes, new_size, sizeof(trie_node)); - GIT_ERROR_CHECK_ALLOC(self->nodes); - - if (new_size > self->size) { - memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(trie_node)); - } - - self->size = new_size; - return 0; -} - -static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, const char *oid) -{ - trie_node *node, *leaf; - node_index idx_leaf; - - if (os->node_count >= os->size) { - if (resize_trie(os, os->size * 2) < 0) - return NULL; - } - - idx_leaf = (node_index)os->node_count++; - - if (os->node_count == SHRT_MAX) { - os->full = 1; - return NULL; - } - - node = &os->nodes[idx]; - node->children[push_at] = -idx_leaf; - - leaf = &os->nodes[idx_leaf]; - leaf->tail = oid; - - return node; -} - -git_oid_shorten *git_oid_shorten_new(size_t min_length) -{ - git_oid_shorten *os; - - GIT_ASSERT_ARG_WITH_RETVAL((size_t)((int)min_length) == min_length, NULL); - - os = git__calloc(1, sizeof(git_oid_shorten)); - if (os == NULL) - return NULL; - - if (resize_trie(os, 16) < 0) { - git__free(os); - return NULL; - } - - os->node_count = 1; - os->min_length = (int)min_length; - - return os; -} - -void git_oid_shorten_free(git_oid_shorten *os) -{ - if (os == NULL) - return; - - git__free(os->nodes); - git__free(os); -} - - -/* - * What wizardry is this? - * - * This is just a memory-optimized trie: basically a very fancy - * 16-ary tree, which is used to store the prefixes of the OID - * strings. - * - * Read more: http://en.wikipedia.org/wiki/Trie - * - * Magic that happens in this method: - * - * - Each node in the trie is an union, so it can work both as - * a normal node, or as a leaf. - * - * - Each normal node points to 16 children (one for each possible - * character in the oid). This is *not* stored in an array of - * pointers, because in a 64-bit arch this would be sucking - * 16*sizeof(void*) = 128 bytes of memory per node, which is - * insane. What we do is store Node Indexes, and use these indexes - * to look up each node in the om->index array. These indexes are - * signed shorts, so this limits the amount of unique OIDs that - * fit in the structure to about 20000 (assuming a more or less uniform - * distribution). - * - * - All the nodes in om->index array are stored contiguously in - * memory, and each of them is 32 bytes, so we fit 2x nodes per - * cache line. Convenient for speed. - * - * - To differentiate the leafs from the normal nodes, we store all - * the indexes towards a leaf as a negative index (indexes to normal - * nodes are positives). When we find that one of the children for - * a node has a negative value, that means it's going to be a leaf. - * This reduces the amount of indexes we have by two, but also reduces - * the size of each node by 1-4 bytes (the amount we would need to - * add a `is_leaf` field): this is good because it allows the nodes - * to fit cleanly in cache lines. - * - * - Once we reach an empty children, instead of continuing to insert - * new nodes for each remaining character of the OID, we store a pointer - * to the tail in the leaf; if the leaf is reached again, we turn it - * into a normal node and use the tail to create a new leaf. - * - * This is a pretty good balance between performance and memory usage. - */ -int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) -{ - int i; - bool is_leaf; - node_index idx; - - if (os->full) { - git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full"); - return -1; - } - - if (text_oid == NULL) - return os->min_length; - - idx = 0; - is_leaf = false; - - for (i = 0; i < GIT_OID_HEXSZ; ++i) { - int c = git__fromhex(text_oid[i]); - trie_node *node; - - if (c == -1) { - git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - invalid hex value"); - return -1; - } - - node = &os->nodes[idx]; - - if (is_leaf) { - const char *tail; - - tail = node->tail; - node->tail = NULL; - - node = push_leaf(os, idx, git__fromhex(tail[0]), &tail[1]); - if (node == NULL) { - if (os->full) - git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full"); - return -1; - } - } - - if (node->children[c] == 0) { - if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) { - if (os->full) - git_error_set(GIT_ERROR_INVALID, "unable to shorten OID - OID set full"); - return -1; - } - break; - } - - idx = node->children[c]; - is_leaf = false; - - if (idx < 0) { - node->children[c] = idx = -idx; - is_leaf = true; - } - } - - if (++i > os->min_length) - os->min_length = i; - - return os->min_length; -} - diff --git a/src/oid.h b/src/oid.h deleted file mode 100644 index 5baec33e5..000000000 --- a/src/oid.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_oid_h__ -#define INCLUDE_oid_h__ - -#include "common.h" - -#include "git2/oid.h" - -extern const git_oid git_oid__empty_blob_sha1; -extern const git_oid git_oid__empty_tree_sha1; - -/** - * Format a git_oid into a newly allocated c-string. - * - * The c-string is owned by the caller and needs to be manually freed. - * - * @param id the oid structure to format - * @return the c-string; NULL if memory is exhausted. Caller must - * deallocate the string with git__free(). - */ -char *git_oid_allocfmt(const git_oid *id); - -GIT_INLINE(int) git_oid__hashcmp(const unsigned char *sha1, const unsigned char *sha2) -{ - return memcmp(sha1, sha2, GIT_OID_RAWSZ); -} - -/* - * Compare two oid structures. - * - * @param a first oid structure. - * @param b second oid structure. - * @return <0, 0, >0 if a < b, a == b, a > b. - */ -GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b) -{ - return git_oid__hashcmp(a->id, b->id); -} - -GIT_INLINE(void) git_oid__cpy_prefix( - git_oid *out, const git_oid *id, size_t len) -{ - memcpy(&out->id, id->id, (len + 1) / 2); - - if (len & 1) - out->id[len / 2] &= 0xF0; -} - -GIT_INLINE(bool) git_oid__is_hexstr(const char *str) -{ - size_t i; - - for (i = 0; str[i] != '\0'; i++) { - if (git__fromhex(str[i]) < 0) - return false; - } - - return (i == GIT_OID_HEXSZ); -} - -#endif diff --git a/src/oidarray.c b/src/oidarray.c deleted file mode 100644 index 583017c4e..000000000 --- a/src/oidarray.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "oidarray.h" - -#include "git2/oidarray.h" -#include "array.h" - -void git_oidarray_dispose(git_oidarray *arr) -{ - git__free(arr->ids); -} - -void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array) -{ - arr->count = array->size; - arr->ids = array->ptr; -} - -void git_oidarray__reverse(git_oidarray *arr) -{ - size_t i; - git_oid tmp; - - for (i = 0; i < arr->count / 2; i++) { - git_oid_cpy(&tmp, &arr->ids[i]); - git_oid_cpy(&arr->ids[i], &arr->ids[(arr->count-1)-i]); - git_oid_cpy(&arr->ids[(arr->count-1)-i], &tmp); - } -} - -#ifndef GIT_DEPRECATE_HARD - -void git_oidarray_free(git_oidarray *arr) -{ - git_oidarray_dispose(arr); -} - -#endif diff --git a/src/oidarray.h b/src/oidarray.h deleted file mode 100644 index eed3a1091..000000000 --- a/src/oidarray.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_oidarray_h__ -#define INCLUDE_oidarray_h__ - -#include "common.h" - -#include "git2/oidarray.h" -#include "array.h" - -typedef git_array_t(git_oid) git_array_oid_t; - -extern void git_oidarray__reverse(git_oidarray *arr); -extern void git_oidarray__from_array(git_oidarray *arr, git_array_oid_t *array); - -#endif diff --git a/src/oidmap.c b/src/oidmap.c deleted file mode 100644 index 0ae8bf33e..000000000 --- a/src/oidmap.c +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "oidmap.h" - -#define kmalloc git__malloc -#define kcalloc git__calloc -#define krealloc git__realloc -#define kreallocarray git__reallocarray -#define kfree git__free -#include "khash.h" - -__KHASH_TYPE(oid, const git_oid *, void *) - -GIT_INLINE(khint_t) git_oidmap_hash(const git_oid *oid) -{ - khint_t h; - memcpy(&h, oid, sizeof(khint_t)); - return h; -} - -__KHASH_IMPL(oid, static kh_inline, const git_oid *, void *, 1, git_oidmap_hash, git_oid_equal) - -int git_oidmap_new(git_oidmap **out) -{ - *out = kh_init(oid); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -void git_oidmap_free(git_oidmap *map) -{ - kh_destroy(oid, map); -} - -void git_oidmap_clear(git_oidmap *map) -{ - kh_clear(oid, map); -} - -size_t git_oidmap_size(git_oidmap *map) -{ - return kh_size(map); -} - -void *git_oidmap_get(git_oidmap *map, const git_oid *key) -{ - size_t idx = kh_get(oid, map, key); - if (idx == kh_end(map) || !kh_exist(map, idx)) - return NULL; - return kh_val(map, idx); -} - -int git_oidmap_set(git_oidmap *map, const git_oid *key, void *value) -{ - size_t idx; - int rval; - - idx = kh_put(oid, map, key, &rval); - if (rval < 0) - return -1; - - if (rval == 0) - kh_key(map, idx) = key; - - kh_val(map, idx) = value; - - return 0; -} - -int git_oidmap_delete(git_oidmap *map, const git_oid *key) -{ - khiter_t idx = kh_get(oid, map, key); - if (idx == kh_end(map)) - return GIT_ENOTFOUND; - kh_del(oid, map, idx); - return 0; -} - -int git_oidmap_exists(git_oidmap *map, const git_oid *key) -{ - return kh_get(oid, map, key) != kh_end(map); -} - -int git_oidmap_iterate(void **value, git_oidmap *map, size_t *iter, const git_oid **key) -{ - size_t i = *iter; - - while (i < map->n_buckets && !kh_exist(map, i)) - i++; - - if (i >= map->n_buckets) - return GIT_ITEROVER; - - if (key) - *key = kh_key(map, i); - if (value) - *value = kh_value(map, i); - *iter = ++i; - - return 0; -} diff --git a/src/oidmap.h b/src/oidmap.h deleted file mode 100644 index b748f727c..000000000 --- a/src/oidmap.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_oidmap_h__ -#define INCLUDE_oidmap_h__ - -#include "common.h" - -#include "git2/oid.h" - -/** A map with `git_oid`s as key. */ -typedef struct kh_oid_s git_oidmap; - -/** - * Allocate a new OID map. - * - * @param out Pointer to the map that shall be allocated. - * @return 0 on success, an error code if allocation has failed. - */ -int git_oidmap_new(git_oidmap **out); - -/** - * Free memory associated with the map. - * - * Note that this function will _not_ free values added to this - * map. - * - * @param map Pointer to the map that is to be free'd. May be - * `NULL`. - */ -void git_oidmap_free(git_oidmap *map); - -/** - * Clear all entries from the map. - * - * This function will remove all entries from the associated map. - * Memory associated with it will not be released, though. - * - * @param map Pointer to the map that shall be cleared. May be - * `NULL`. - */ -void git_oidmap_clear(git_oidmap *map); - -/** - * Return the number of elements in the map. - * - * @parameter map map containing the elements - * @return number of elements in the map - */ -size_t git_oidmap_size(git_oidmap *map); - -/** - * Return value associated with the given key. - * - * @param map map to search key in - * @param key key to search for - * @return value associated with the given key or NULL if the key was not found - */ -void *git_oidmap_get(git_oidmap *map, const git_oid *key); - -/** - * Set the entry for key to value. - * - * If the map has no corresponding entry for the given key, a new - * entry will be created with the given value. If an entry exists - * already, its value will be updated to match the given value. - * - * @param map map to create new entry in - * @param key key to set - * @param value value to associate the key with; may be NULL - * @return zero if the key was successfully set, a negative error - * code otherwise - */ -int git_oidmap_set(git_oidmap *map, const git_oid *key, void *value); - -/** - * Delete an entry from the map. - * - * Delete the given key and its value from the map. If no such - * key exists, this will do nothing. - * - * @param map map to delete key in - * @param key key to delete - * @return `0` if the key has been deleted, GIT_ENOTFOUND if no - * such key was found, a negative code in case of an - * error - */ -int git_oidmap_delete(git_oidmap *map, const git_oid *key); - -/** - * Check whether a key exists in the given map. - * - * @param map map to query for the key - * @param key key to search for - * @return 0 if the key has not been found, 1 otherwise - */ -int git_oidmap_exists(git_oidmap *map, const git_oid *key); - -/** - * Iterate over entries of the map. - * - * This functions allows to iterate over all key-value entries of - * the map. The current position is stored in the `iter` variable - * and should be initialized to `0` before the first call to this - * function. - * - * @param map map to iterate over - * @param value pointer to the variable where to store the current - * value. May be NULL. - * @param iter iterator storing the current position. Initialize - * with zero previous to the first call. - * @param key pointer to the variable where to store the current - * key. May be NULL. - * @return `0` if the next entry was correctly retrieved. - * GIT_ITEROVER if no entries are left. A negative error - * code otherwise. - */ -int git_oidmap_iterate(void **value, git_oidmap *map, size_t *iter, const git_oid **key); - -#define git_oidmap_foreach_value(h, vvar, code) { size_t __i = 0; \ - while (git_oidmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ - code; \ - } } - -#endif diff --git a/src/pack-objects.c b/src/pack-objects.c deleted file mode 100644 index 1aa6731b3..000000000 --- a/src/pack-objects.c +++ /dev/null @@ -1,1821 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "pack-objects.h" - -#include "buf.h" -#include "zstream.h" -#include "delta.h" -#include "iterator.h" -#include "netops.h" -#include "pack.h" -#include "thread.h" -#include "tree.h" -#include "util.h" -#include "revwalk.h" -#include "commit_list.h" - -#include "git2/pack.h" -#include "git2/commit.h" -#include "git2/tag.h" -#include "git2/indexer.h" -#include "git2/config.h" - -struct unpacked { - git_pobject *object; - void *data; - struct git_delta_index *index; - size_t depth; -}; - -struct tree_walk_context { - git_packbuilder *pb; - git_str buf; -}; - -struct pack_write_context { - git_indexer *indexer; - git_indexer_progress *stats; -}; - -struct walk_object { - git_oid id; - unsigned int uninteresting:1, - seen:1; -}; - -#ifdef GIT_THREADS -# define GIT_PACKBUILDER__MUTEX_OP(pb, mtx, op) git_mutex_##op(&(pb)->mtx) -#else -# define GIT_PACKBUILDER__MUTEX_OP(pb, mtx, op) git__noop() -#endif - -#define git_packbuilder__cache_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, lock) -#define git_packbuilder__cache_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, unlock) -#define git_packbuilder__progress_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, lock) -#define git_packbuilder__progress_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, unlock) - -/* The minimal interval between progress updates (in seconds). */ -#define MIN_PROGRESS_UPDATE_INTERVAL 0.5 - -/* Size of the buffer to feed to zlib */ -#define COMPRESS_BUFLEN (1024 * 1024) - -static unsigned name_hash(const char *name) -{ - unsigned c, hash = 0; - - if (!name) - return 0; - - /* - * This effectively just creates a sortable number from the - * last sixteen non-whitespace characters. Last characters - * count "most", so things that end in ".c" sort together. - */ - while ((c = *name++) != 0) { - if (git__isspace(c)) - continue; - hash = (hash >> 2) + (c << 24); - } - return hash; -} - -static int packbuilder_config(git_packbuilder *pb) -{ - git_config *config; - int ret = 0; - int64_t val; - - if ((ret = git_repository_config_snapshot(&config, pb->repo)) < 0) - return ret; - -#define config_get(KEY,DST,DFLT) do { \ - ret = git_config_get_int64(&val, config, KEY); \ - if (!ret) { \ - if (!git__is_sizet(val)) { \ - git_error_set(GIT_ERROR_CONFIG, \ - "configuration value '%s' is too large", KEY); \ - ret = -1; \ - goto out; \ - } \ - (DST) = (size_t)val; \ - } else if (ret == GIT_ENOTFOUND) { \ - (DST) = (DFLT); \ - ret = 0; \ - } else if (ret < 0) goto out; } while (0) - - config_get("pack.deltaCacheSize", pb->max_delta_cache_size, - GIT_PACK_DELTA_CACHE_SIZE); - config_get("pack.deltaCacheLimit", pb->cache_max_small_delta_size, - GIT_PACK_DELTA_CACHE_LIMIT); - config_get("pack.deltaCacheSize", pb->big_file_threshold, - GIT_PACK_BIG_FILE_THRESHOLD); - config_get("pack.windowMemory", pb->window_memory_limit, 0); - -#undef config_get - -out: - git_config_free(config); - - return ret; -} - -int git_packbuilder_new(git_packbuilder **out, git_repository *repo) -{ - git_packbuilder *pb; - - *out = NULL; - - pb = git__calloc(1, sizeof(*pb)); - GIT_ERROR_CHECK_ALLOC(pb); - - if (git_oidmap_new(&pb->object_ix) < 0 || - git_oidmap_new(&pb->walk_objects) < 0 || - git_pool_init(&pb->object_pool, sizeof(struct walk_object)) < 0) - goto on_error; - - pb->repo = repo; - pb->nr_threads = 1; /* do not spawn any thread by default */ - - if (git_hash_ctx_init(&pb->ctx, GIT_HASH_ALGORITHM_SHA1) < 0 || - git_zstream_init(&pb->zstream, GIT_ZSTREAM_DEFLATE) < 0 || - git_repository_odb(&pb->odb, repo) < 0 || - packbuilder_config(pb) < 0) - goto on_error; - -#ifdef GIT_THREADS - - if (git_mutex_init(&pb->cache_mutex) || - git_mutex_init(&pb->progress_mutex) || - git_cond_init(&pb->progress_cond)) - { - git_error_set(GIT_ERROR_OS, "failed to initialize packbuilder mutex"); - goto on_error; - } - -#endif - - *out = pb; - return 0; - -on_error: - git_packbuilder_free(pb); - return -1; -} - -unsigned int git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n) -{ - GIT_ASSERT_ARG(pb); - -#ifdef GIT_THREADS - pb->nr_threads = n; -#else - GIT_UNUSED(n); - GIT_ASSERT(pb->nr_threads == 1); -#endif - - return pb->nr_threads; -} - -static int rehash(git_packbuilder *pb) -{ - git_pobject *po; - size_t i; - - git_oidmap_clear(pb->object_ix); - - for (i = 0, po = pb->object_list; i < pb->nr_objects; i++, po++) { - if (git_oidmap_set(pb->object_ix, &po->id, po) < 0) - return -1; - } - - return 0; -} - -int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid, - const char *name) -{ - git_pobject *po; - size_t newsize; - int ret; - - GIT_ASSERT_ARG(pb); - GIT_ASSERT_ARG(oid); - - /* If the object already exists in the hash table, then we don't - * have any work to do */ - if (git_oidmap_exists(pb->object_ix, oid)) - return 0; - - if (pb->nr_objects >= pb->nr_alloc) { - GIT_ERROR_CHECK_ALLOC_ADD(&newsize, pb->nr_alloc, 1024); - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newsize, newsize / 2, 3); - - if (!git__is_uint32(newsize)) { - git_error_set(GIT_ERROR_NOMEMORY, "packfile too large to fit in memory."); - return -1; - } - - pb->nr_alloc = newsize; - - pb->object_list = git__reallocarray(pb->object_list, - pb->nr_alloc, sizeof(*po)); - GIT_ERROR_CHECK_ALLOC(pb->object_list); - - if (rehash(pb) < 0) - return -1; - } - - po = pb->object_list + pb->nr_objects; - memset(po, 0x0, sizeof(*po)); - - if ((ret = git_odb_read_header(&po->size, &po->type, pb->odb, oid)) < 0) - return ret; - - pb->nr_objects++; - git_oid_cpy(&po->id, oid); - po->hash = name_hash(name); - - if (git_oidmap_set(pb->object_ix, &po->id, po) < 0) { - git_error_set_oom(); - return -1; - } - - pb->done = false; - - if (pb->progress_cb) { - double current_time = git__timer(); - double elapsed = current_time - pb->last_progress_report_time; - - if (elapsed < 0 || elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) { - pb->last_progress_report_time = current_time; - - ret = pb->progress_cb( - GIT_PACKBUILDER_ADDING_OBJECTS, - pb->nr_objects, 0, pb->progress_cb_payload); - - if (ret) - return git_error_set_after_callback(ret); - } - } - - return 0; -} - -static int get_delta(void **out, git_odb *odb, git_pobject *po) -{ - git_odb_object *src = NULL, *trg = NULL; - size_t delta_size; - void *delta_buf; - int error; - - *out = NULL; - - if (git_odb_read(&src, odb, &po->delta->id) < 0 || - git_odb_read(&trg, odb, &po->id) < 0) - goto on_error; - - error = git_delta(&delta_buf, &delta_size, - git_odb_object_data(src), git_odb_object_size(src), - git_odb_object_data(trg), git_odb_object_size(trg), - 0); - - if (error < 0 && error != GIT_EBUFS) - goto on_error; - - if (error == GIT_EBUFS || delta_size != po->delta_size) { - git_error_set(GIT_ERROR_INVALID, "delta size changed"); - goto on_error; - } - - *out = delta_buf; - - git_odb_object_free(src); - git_odb_object_free(trg); - return 0; - -on_error: - git_odb_object_free(src); - git_odb_object_free(trg); - return -1; -} - -static int write_object( - git_packbuilder *pb, - git_pobject *po, - int (*write_cb)(void *buf, size_t size, void *cb_data), - void *cb_data) -{ - git_odb_object *obj = NULL; - git_object_t type; - unsigned char hdr[10], *zbuf = NULL; - void *data = NULL; - size_t hdr_len, zbuf_len = COMPRESS_BUFLEN, data_len; - int error; - - /* - * If we have a delta base, let's use the delta to save space. - * Otherwise load the whole object. 'data' ends up pointing to - * whatever data we want to put into the packfile. - */ - if (po->delta) { - if (po->delta_data) - data = po->delta_data; - else if ((error = get_delta(&data, pb->odb, po)) < 0) - goto done; - - data_len = po->delta_size; - type = GIT_OBJECT_REF_DELTA; - } else { - if ((error = git_odb_read(&obj, pb->odb, &po->id)) < 0) - goto done; - - data = (void *)git_odb_object_data(obj); - data_len = git_odb_object_size(obj); - type = git_odb_object_type(obj); - } - - /* Write header */ - if ((error = git_packfile__object_header(&hdr_len, hdr, data_len, type)) < 0 || - (error = write_cb(hdr, hdr_len, cb_data)) < 0 || - (error = git_hash_update(&pb->ctx, hdr, hdr_len)) < 0) - goto done; - - if (type == GIT_OBJECT_REF_DELTA) { - if ((error = write_cb(po->delta->id.id, GIT_OID_RAWSZ, cb_data)) < 0 || - (error = git_hash_update(&pb->ctx, po->delta->id.id, GIT_OID_RAWSZ)) < 0) - goto done; - } - - /* Write data */ - if (po->z_delta_size) { - data_len = po->z_delta_size; - - if ((error = write_cb(data, data_len, cb_data)) < 0 || - (error = git_hash_update(&pb->ctx, data, data_len)) < 0) - goto done; - } else { - zbuf = git__malloc(zbuf_len); - GIT_ERROR_CHECK_ALLOC(zbuf); - - git_zstream_reset(&pb->zstream); - - if ((error = git_zstream_set_input(&pb->zstream, data, data_len)) < 0) - goto done; - - while (!git_zstream_done(&pb->zstream)) { - if ((error = git_zstream_get_output(zbuf, &zbuf_len, &pb->zstream)) < 0 || - (error = write_cb(zbuf, zbuf_len, cb_data)) < 0 || - (error = git_hash_update(&pb->ctx, zbuf, zbuf_len)) < 0) - goto done; - - zbuf_len = COMPRESS_BUFLEN; /* reuse buffer */ - } - } - - /* - * If po->delta is true, data is a delta and it is our - * responsibility to free it (otherwise it's a git_object's - * data). We set po->delta_data to NULL in case we got the - * data from there instead of get_delta(). If we didn't, - * there's no harm. - */ - if (po->delta) { - git__free(data); - po->delta_data = NULL; - } - - pb->nr_written++; - -done: - git__free(zbuf); - git_odb_object_free(obj); - return error; -} - -enum write_one_status { - WRITE_ONE_SKIP = -1, /* already written */ - WRITE_ONE_BREAK = 0, /* writing this will bust the limit; not written */ - WRITE_ONE_WRITTEN = 1, /* normal */ - WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */ -}; - -static int write_one( - enum write_one_status *status, - git_packbuilder *pb, - git_pobject *po, - int (*write_cb)(void *buf, size_t size, void *cb_data), - void *cb_data) -{ - int error; - - if (po->recursing) { - *status = WRITE_ONE_RECURSIVE; - return 0; - } else if (po->written) { - *status = WRITE_ONE_SKIP; - return 0; - } - - if (po->delta) { - po->recursing = 1; - - if ((error = write_one(status, pb, po->delta, write_cb, cb_data)) < 0) - return error; - - /* we cannot depend on this one */ - if (*status == WRITE_ONE_RECURSIVE) - po->delta = NULL; - } - - *status = WRITE_ONE_WRITTEN; - po->written = 1; - po->recursing = 0; - - return write_object(pb, po, write_cb, cb_data); -} - -GIT_INLINE(void) add_to_write_order(git_pobject **wo, size_t *endp, - git_pobject *po) -{ - if (po->filled) - return; - wo[(*endp)++] = po; - po->filled = 1; -} - -static void add_descendants_to_write_order(git_pobject **wo, size_t *endp, - git_pobject *po) -{ - int add_to_order = 1; - while (po) { - if (add_to_order) { - git_pobject *s; - /* add this node... */ - add_to_write_order(wo, endp, po); - /* all its siblings... */ - for (s = po->delta_sibling; s; s = s->delta_sibling) { - add_to_write_order(wo, endp, s); - } - } - /* drop down a level to add left subtree nodes if possible */ - if (po->delta_child) { - add_to_order = 1; - po = po->delta_child; - } else { - add_to_order = 0; - /* our sibling might have some children, it is next */ - if (po->delta_sibling) { - po = po->delta_sibling; - continue; - } - /* go back to our parent node */ - po = po->delta; - while (po && !po->delta_sibling) { - /* we're on the right side of a subtree, keep - * going up until we can go right again */ - po = po->delta; - } - if (!po) { - /* done- we hit our original root node */ - return; - } - /* pass it off to sibling at this level */ - po = po->delta_sibling; - } - }; -} - -static void add_family_to_write_order(git_pobject **wo, size_t *endp, - git_pobject *po) -{ - git_pobject *root; - - for (root = po; root->delta; root = root->delta) - ; /* nothing */ - add_descendants_to_write_order(wo, endp, root); -} - -static int cb_tag_foreach(const char *name, git_oid *oid, void *data) -{ - git_packbuilder *pb = data; - git_pobject *po; - - GIT_UNUSED(name); - - if ((po = git_oidmap_get(pb->object_ix, oid)) == NULL) - return 0; - - po->tagged = 1; - - /* TODO: peel objects */ - - return 0; -} - -static int compute_write_order(git_pobject ***out, git_packbuilder *pb) -{ - size_t i, wo_end, last_untagged; - git_pobject **wo; - - *out = NULL; - - if (!pb->nr_objects) - return 0; - - if ((wo = git__mallocarray(pb->nr_objects, sizeof(*wo))) == NULL) - return -1; - - for (i = 0; i < pb->nr_objects; i++) { - git_pobject *po = pb->object_list + i; - po->tagged = 0; - po->filled = 0; - po->delta_child = NULL; - po->delta_sibling = NULL; - } - - /* - * Fully connect delta_child/delta_sibling network. - * Make sure delta_sibling is sorted in the original - * recency order. - */ - for (i = pb->nr_objects; i > 0;) { - git_pobject *po = &pb->object_list[--i]; - if (!po->delta) - continue; - /* Mark me as the first child */ - po->delta_sibling = po->delta->delta_child; - po->delta->delta_child = po; - } - - /* - * Mark objects that are at the tip of tags. - */ - if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0) { - git__free(wo); - return -1; - } - - /* - * Give the objects in the original recency order until - * we see a tagged tip. - */ - for (i = wo_end = 0; i < pb->nr_objects; i++) { - git_pobject *po = pb->object_list + i; - if (po->tagged) - break; - add_to_write_order(wo, &wo_end, po); - } - last_untagged = i; - - /* - * Then fill all the tagged tips. - */ - for (; i < pb->nr_objects; i++) { - git_pobject *po = pb->object_list + i; - if (po->tagged) - add_to_write_order(wo, &wo_end, po); - } - - /* - * And then all remaining commits and tags. - */ - for (i = last_untagged; i < pb->nr_objects; i++) { - git_pobject *po = pb->object_list + i; - if (po->type != GIT_OBJECT_COMMIT && - po->type != GIT_OBJECT_TAG) - continue; - add_to_write_order(wo, &wo_end, po); - } - - /* - * And then all the trees. - */ - for (i = last_untagged; i < pb->nr_objects; i++) { - git_pobject *po = pb->object_list + i; - if (po->type != GIT_OBJECT_TREE) - continue; - add_to_write_order(wo, &wo_end, po); - } - - /* - * Finally all the rest in really tight order - */ - for (i = last_untagged; i < pb->nr_objects; i++) { - git_pobject *po = pb->object_list + i; - if (!po->filled) - add_family_to_write_order(wo, &wo_end, po); - } - - if (wo_end != pb->nr_objects) { - git__free(wo); - git_error_set(GIT_ERROR_INVALID, "invalid write order"); - return -1; - } - - *out = wo; - return 0; -} - -static int write_pack(git_packbuilder *pb, - int (*write_cb)(void *buf, size_t size, void *cb_data), - void *cb_data) -{ - git_pobject **write_order; - git_pobject *po; - enum write_one_status status; - struct git_pack_header ph; - git_oid entry_oid; - size_t i = 0; - int error; - - if ((error = compute_write_order(&write_order, pb)) < 0) - return error; - - if (!git__is_uint32(pb->nr_objects)) { - git_error_set(GIT_ERROR_INVALID, "too many objects"); - error = -1; - goto done; - } - - /* Write pack header */ - ph.hdr_signature = htonl(PACK_SIGNATURE); - ph.hdr_version = htonl(PACK_VERSION); - ph.hdr_entries = htonl(pb->nr_objects); - - if ((error = write_cb(&ph, sizeof(ph), cb_data)) < 0 || - (error = git_hash_update(&pb->ctx, &ph, sizeof(ph))) < 0) - goto done; - - pb->nr_remaining = pb->nr_objects; - do { - pb->nr_written = 0; - for ( ; i < pb->nr_objects; ++i) { - po = write_order[i]; - - if ((error = write_one(&status, pb, po, write_cb, cb_data)) < 0) - goto done; - } - - pb->nr_remaining -= pb->nr_written; - } while (pb->nr_remaining && i < pb->nr_objects); - - if ((error = git_hash_final(entry_oid.id, &pb->ctx)) < 0) - goto done; - - error = write_cb(entry_oid.id, GIT_OID_RAWSZ, cb_data); - -done: - /* if callback cancelled writing, we must still free delta_data */ - for ( ; i < pb->nr_objects; ++i) { - po = write_order[i]; - if (po->delta_data) { - git__free(po->delta_data); - po->delta_data = NULL; - } - } - - git__free(write_order); - return error; -} - -static int write_pack_buf(void *buf, size_t size, void *data) -{ - git_str *b = (git_str *)data; - return git_str_put(b, buf, size); -} - -static int type_size_sort(const void *_a, const void *_b) -{ - const git_pobject *a = (git_pobject *)_a; - const git_pobject *b = (git_pobject *)_b; - - if (a->type > b->type) - return -1; - if (a->type < b->type) - return 1; - if (a->hash > b->hash) - return -1; - if (a->hash < b->hash) - return 1; - /* - * TODO - * - if (a->preferred_base > b->preferred_base) - return -1; - if (a->preferred_base < b->preferred_base) - return 1; - */ - if (a->size > b->size) - return -1; - if (a->size < b->size) - return 1; - return a < b ? -1 : (a > b); /* newest first */ -} - -static int delta_cacheable( - git_packbuilder *pb, - size_t src_size, - size_t trg_size, - size_t delta_size) -{ - size_t new_size; - - if (git__add_sizet_overflow(&new_size, pb->delta_cache_size, delta_size)) - return 0; - - if (pb->max_delta_cache_size && new_size > pb->max_delta_cache_size) - return 0; - - if (delta_size < pb->cache_max_small_delta_size) - return 1; - - /* cache delta, if objects are large enough compared to delta size */ - if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10)) - return 1; - - return 0; -} - -static int try_delta(git_packbuilder *pb, struct unpacked *trg, - struct unpacked *src, size_t max_depth, - size_t *mem_usage, int *ret) -{ - git_pobject *trg_object = trg->object; - git_pobject *src_object = src->object; - git_odb_object *obj; - size_t trg_size, src_size, delta_size, sizediff, max_size, sz; - size_t ref_depth; - void *delta_buf; - - /* Don't bother doing diffs between different types */ - if (trg_object->type != src_object->type) { - *ret = -1; - return 0; - } - - *ret = 0; - - /* TODO: support reuse-delta */ - - /* Let's not bust the allowed depth. */ - if (src->depth >= max_depth) - return 0; - - /* Now some size filtering heuristics. */ - trg_size = trg_object->size; - if (!trg_object->delta) { - max_size = trg_size/2 - 20; - ref_depth = 1; - } else { - max_size = trg_object->delta_size; - ref_depth = trg->depth; - } - - max_size = (uint64_t)max_size * (max_depth - src->depth) / - (max_depth - ref_depth + 1); - if (max_size == 0) - return 0; - - src_size = src_object->size; - sizediff = src_size < trg_size ? trg_size - src_size : 0; - if (sizediff >= max_size) - return 0; - if (trg_size < src_size / 32) - return 0; - - /* Load data if not already done */ - if (!trg->data) { - if (git_odb_read(&obj, pb->odb, &trg_object->id) < 0) - return -1; - - sz = git_odb_object_size(obj); - trg->data = git__malloc(sz); - GIT_ERROR_CHECK_ALLOC(trg->data); - memcpy(trg->data, git_odb_object_data(obj), sz); - - git_odb_object_free(obj); - - if (sz != trg_size) { - git_error_set(GIT_ERROR_INVALID, - "inconsistent target object length"); - return -1; - } - - *mem_usage += sz; - } - if (!src->data) { - size_t obj_sz; - - if (git_odb_read(&obj, pb->odb, &src_object->id) < 0 || - !git__is_ulong(obj_sz = git_odb_object_size(obj))) - return -1; - - sz = obj_sz; - src->data = git__malloc(sz); - GIT_ERROR_CHECK_ALLOC(src->data); - memcpy(src->data, git_odb_object_data(obj), sz); - - git_odb_object_free(obj); - - if (sz != src_size) { - git_error_set(GIT_ERROR_INVALID, - "inconsistent source object length"); - return -1; - } - - *mem_usage += sz; - } - if (!src->index) { - if (git_delta_index_init(&src->index, src->data, src_size) < 0) - return 0; /* suboptimal pack - out of memory */ - - *mem_usage += git_delta_index_size(src->index); - } - - if (git_delta_create_from_index(&delta_buf, &delta_size, src->index, trg->data, trg_size, - max_size) < 0) - return 0; - - if (trg_object->delta) { - /* Prefer only shallower same-sized deltas. */ - if (delta_size == trg_object->delta_size && - src->depth + 1 >= trg->depth) { - git__free(delta_buf); - return 0; - } - } - - GIT_ASSERT(git_packbuilder__cache_lock(pb) == 0); - - if (trg_object->delta_data) { - git__free(trg_object->delta_data); - GIT_ASSERT(pb->delta_cache_size >= trg_object->delta_size); - pb->delta_cache_size -= trg_object->delta_size; - trg_object->delta_data = NULL; - } - if (delta_cacheable(pb, src_size, trg_size, delta_size)) { - bool overflow = git__add_sizet_overflow( - &pb->delta_cache_size, pb->delta_cache_size, delta_size); - - GIT_ASSERT(git_packbuilder__cache_unlock(pb) == 0); - - if (overflow) { - git__free(delta_buf); - return -1; - } - - trg_object->delta_data = git__realloc(delta_buf, delta_size); - GIT_ERROR_CHECK_ALLOC(trg_object->delta_data); - } else { - /* create delta when writing the pack */ - GIT_ASSERT(git_packbuilder__cache_unlock(pb) == 0); - git__free(delta_buf); - } - - trg_object->delta = src_object; - trg_object->delta_size = delta_size; - trg->depth = src->depth + 1; - - *ret = 1; - return 0; -} - -static size_t check_delta_limit(git_pobject *me, size_t n) -{ - git_pobject *child = me->delta_child; - size_t m = n; - - while (child) { - size_t c = check_delta_limit(child, n + 1); - if (m < c) - m = c; - child = child->delta_sibling; - } - return m; -} - -static size_t free_unpacked(struct unpacked *n) -{ - size_t freed_mem = 0; - - if (n->index) { - freed_mem += git_delta_index_size(n->index); - git_delta_index_free(n->index); - } - n->index = NULL; - - if (n->data) { - freed_mem += n->object->size; - git__free(n->data); - n->data = NULL; - } - n->object = NULL; - n->depth = 0; - return freed_mem; -} - -static int report_delta_progress( - git_packbuilder *pb, uint32_t count, bool force) -{ - int ret; - - if (pb->progress_cb) { - double current_time = git__timer(); - double elapsed = current_time - pb->last_progress_report_time; - - if (force || elapsed < 0 || elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) { - pb->last_progress_report_time = current_time; - - ret = pb->progress_cb( - GIT_PACKBUILDER_DELTAFICATION, - count, pb->nr_objects, pb->progress_cb_payload); - - if (ret) - return git_error_set_after_callback(ret); - } - } - - return 0; -} - -static int find_deltas(git_packbuilder *pb, git_pobject **list, - size_t *list_size, size_t window, size_t depth) -{ - git_pobject *po; - git_str zbuf = GIT_STR_INIT; - struct unpacked *array; - size_t idx = 0, count = 0; - size_t mem_usage = 0; - size_t i; - int error = -1; - - array = git__calloc(window, sizeof(struct unpacked)); - GIT_ERROR_CHECK_ALLOC(array); - - for (;;) { - struct unpacked *n = array + idx; - size_t max_depth, j, best_base = SIZE_MAX; - - GIT_ASSERT(git_packbuilder__progress_lock(pb) == 0); - if (!*list_size) { - GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); - break; - } - - pb->nr_deltified += 1; - report_delta_progress(pb, pb->nr_deltified, false); - - po = *list++; - (*list_size)--; - GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); - - mem_usage -= free_unpacked(n); - n->object = po; - - while (pb->window_memory_limit && - mem_usage > pb->window_memory_limit && - count > 1) { - size_t tail = (idx + window - count) % window; - mem_usage -= free_unpacked(array + tail); - count--; - } - - /* - * If the current object is at pack edge, take the depth the - * objects that depend on the current object into account - * otherwise they would become too deep. - */ - max_depth = depth; - if (po->delta_child) { - size_t delta_limit = check_delta_limit(po, 0); - - if (delta_limit > max_depth) - goto next; - - max_depth -= delta_limit; - } - - j = window; - while (--j > 0) { - int ret; - size_t other_idx = idx + j; - struct unpacked *m; - - if (other_idx >= window) - other_idx -= window; - - m = array + other_idx; - if (!m->object) - break; - - if (try_delta(pb, n, m, max_depth, &mem_usage, &ret) < 0) - goto on_error; - if (ret < 0) - break; - else if (ret > 0) - best_base = other_idx; - } - - /* - * If we decided to cache the delta data, then it is best - * to compress it right away. First because we have to do - * it anyway, and doing it here while we're threaded will - * save a lot of time in the non threaded write phase, - * as well as allow for caching more deltas within - * the same cache size limit. - * ... - * But only if not writing to stdout, since in that case - * the network is most likely throttling writes anyway, - * and therefore it is best to go to the write phase ASAP - * instead, as we can afford spending more time compressing - * between writes at that moment. - */ - if (po->delta_data) { - if (git_zstream_deflatebuf(&zbuf, po->delta_data, po->delta_size) < 0) - goto on_error; - - git__free(po->delta_data); - po->delta_data = git__malloc(zbuf.size); - GIT_ERROR_CHECK_ALLOC(po->delta_data); - - memcpy(po->delta_data, zbuf.ptr, zbuf.size); - po->z_delta_size = zbuf.size; - git_str_clear(&zbuf); - - GIT_ASSERT(git_packbuilder__cache_lock(pb) == 0); - pb->delta_cache_size -= po->delta_size; - pb->delta_cache_size += po->z_delta_size; - GIT_ASSERT(git_packbuilder__cache_unlock(pb) == 0); - } - - /* - * If we made n a delta, and if n is already at max - * depth, leaving it in the window is pointless. we - * should evict it first. - */ - if (po->delta && max_depth <= n->depth) - continue; - - /* - * Move the best delta base up in the window, after the - * currently deltified object, to keep it longer. It will - * be the first base object to be attempted next. - */ - if (po->delta) { - struct unpacked swap = array[best_base]; - size_t dist = (window + idx - best_base) % window; - size_t dst = best_base; - while (dist--) { - size_t src = (dst + 1) % window; - array[dst] = array[src]; - dst = src; - } - array[dst] = swap; - } - - next: - idx++; - if (count + 1 < window) - count++; - if (idx >= window) - idx = 0; - } - error = 0; - -on_error: - for (i = 0; i < window; ++i) { - git__free(array[i].index); - git__free(array[i].data); - } - git__free(array); - git_str_dispose(&zbuf); - - return error; -} - -#ifdef GIT_THREADS - -struct thread_params { - git_thread thread; - git_packbuilder *pb; - - git_pobject **list; - - git_cond cond; - git_mutex mutex; - - size_t list_size; - size_t remaining; - - size_t window; - size_t depth; - size_t working; - size_t data_ready; -}; - -static void *threaded_find_deltas(void *arg) -{ - struct thread_params *me = arg; - - while (me->remaining) { - if (find_deltas(me->pb, me->list, &me->remaining, - me->window, me->depth) < 0) { - ; /* TODO */ - } - - GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_lock(me->pb) == 0, NULL); - me->working = 0; - git_cond_signal(&me->pb->progress_cond); - GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_unlock(me->pb) == 0, NULL); - - if (git_mutex_lock(&me->mutex)) { - git_error_set(GIT_ERROR_THREAD, "unable to lock packfile condition mutex"); - return NULL; - } - - while (!me->data_ready) - git_cond_wait(&me->cond, &me->mutex); - - /* - * We must not set ->data_ready before we wait on the - * condition because the main thread may have set it to 1 - * before we get here. In order to be sure that new - * work is available if we see 1 in ->data_ready, it - * was initialized to 0 before this thread was spawned - * and we reset it to 0 right away. - */ - me->data_ready = 0; - git_mutex_unlock(&me->mutex); - } - /* leave ->working 1 so that this doesn't get more work assigned */ - return NULL; -} - -static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, - size_t list_size, size_t window, size_t depth) -{ - struct thread_params *p; - size_t i; - int ret, active_threads = 0; - - if (!pb->nr_threads) - pb->nr_threads = git__online_cpus(); - - if (pb->nr_threads <= 1) { - find_deltas(pb, list, &list_size, window, depth); - return 0; - } - - p = git__mallocarray(pb->nr_threads, sizeof(*p)); - GIT_ERROR_CHECK_ALLOC(p); - - /* Partition the work among the threads */ - for (i = 0; i < pb->nr_threads; ++i) { - size_t sub_size = list_size / (pb->nr_threads - i); - - /* don't use too small segments or no deltas will be found */ - if (sub_size < 2*window && i+1 < pb->nr_threads) - sub_size = 0; - - p[i].pb = pb; - p[i].window = window; - p[i].depth = depth; - p[i].working = 1; - p[i].data_ready = 0; - - /* try to split chunks on "path" boundaries */ - while (sub_size && sub_size < list_size && - list[sub_size]->hash && - list[sub_size]->hash == list[sub_size-1]->hash) - sub_size++; - - p[i].list = list; - p[i].list_size = sub_size; - p[i].remaining = sub_size; - - list += sub_size; - list_size -= sub_size; - } - - /* Start work threads */ - for (i = 0; i < pb->nr_threads; ++i) { - if (!p[i].list_size) - continue; - - git_mutex_init(&p[i].mutex); - git_cond_init(&p[i].cond); - - ret = git_thread_create(&p[i].thread, - threaded_find_deltas, &p[i]); - if (ret) { - git_error_set(GIT_ERROR_THREAD, "unable to create thread"); - return -1; - } - active_threads++; - } - - /* - * Now let's wait for work completion. Each time a thread is done - * with its work, we steal half of the remaining work from the - * thread with the largest number of unprocessed objects and give - * it to that newly idle thread. This ensure good load balancing - * until the remaining object list segments are simply too short - * to be worth splitting anymore. - */ - while (active_threads) { - struct thread_params *target = NULL; - struct thread_params *victim = NULL; - size_t sub_size = 0; - - /* Start by locating a thread that has transitioned its - * 'working' flag from 1 -> 0. This indicates that it is - * ready to receive more work using our work-stealing - * algorithm. */ - GIT_ASSERT(git_packbuilder__progress_lock(pb) == 0); - for (;;) { - for (i = 0; !target && i < pb->nr_threads; i++) - if (!p[i].working) - target = &p[i]; - if (target) - break; - git_cond_wait(&pb->progress_cond, &pb->progress_mutex); - } - - /* At this point we hold the progress lock and have located - * a thread to receive more work. We still need to locate a - * thread from which to steal work (the victim). */ - for (i = 0; i < pb->nr_threads; i++) - if (p[i].remaining > 2*window && - (!victim || victim->remaining < p[i].remaining)) - victim = &p[i]; - - if (victim) { - sub_size = victim->remaining / 2; - list = victim->list + victim->list_size - sub_size; - while (sub_size && list[0]->hash && - list[0]->hash == list[-1]->hash) { - list++; - sub_size--; - } - if (!sub_size) { - /* - * It is possible for some "paths" to have - * so many objects that no hash boundary - * might be found. Let's just steal the - * exact half in that case. - */ - sub_size = victim->remaining / 2; - list -= sub_size; - } - target->list = list; - victim->list_size -= sub_size; - victim->remaining -= sub_size; - } - target->list_size = sub_size; - target->remaining = sub_size; - target->working = 1; - GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); - - if (git_mutex_lock(&target->mutex)) { - git_error_set(GIT_ERROR_THREAD, "unable to lock packfile condition mutex"); - git__free(p); - return -1; - } - - target->data_ready = 1; - git_cond_signal(&target->cond); - git_mutex_unlock(&target->mutex); - - if (!sub_size) { - git_thread_join(&target->thread, NULL); - git_cond_free(&target->cond); - git_mutex_free(&target->mutex); - active_threads--; - } - } - - git__free(p); - return 0; -} - -#else -#define ll_find_deltas(pb, l, ls, w, d) find_deltas(pb, l, &ls, w, d) -#endif - -int git_packbuilder__prepare(git_packbuilder *pb) -{ - git_pobject **delta_list; - size_t i, n = 0; - - if (pb->nr_objects == 0 || pb->done) - return 0; /* nothing to do */ - - /* - * Although we do not report progress during deltafication, we - * at least report that we are in the deltafication stage - */ - if (pb->progress_cb) - pb->progress_cb(GIT_PACKBUILDER_DELTAFICATION, 0, pb->nr_objects, pb->progress_cb_payload); - - delta_list = git__mallocarray(pb->nr_objects, sizeof(*delta_list)); - GIT_ERROR_CHECK_ALLOC(delta_list); - - for (i = 0; i < pb->nr_objects; ++i) { - git_pobject *po = pb->object_list + i; - - /* Make sure the item is within our size limits */ - if (po->size < 50 || po->size > pb->big_file_threshold) - continue; - - delta_list[n++] = po; - } - - if (n > 1) { - git__tsort((void **)delta_list, n, type_size_sort); - if (ll_find_deltas(pb, delta_list, n, - GIT_PACK_WINDOW + 1, - GIT_PACK_DEPTH) < 0) { - git__free(delta_list); - return -1; - } - } - - report_delta_progress(pb, pb->nr_objects, true); - - pb->done = true; - git__free(delta_list); - return 0; -} - -#define PREPARE_PACK if (git_packbuilder__prepare(pb) < 0) { return -1; } - -int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload) -{ - PREPARE_PACK; - return write_pack(pb, cb, payload); -} - -int git_packbuilder__write_buf(git_str *buf, git_packbuilder *pb) -{ - PREPARE_PACK; - - return write_pack(pb, &write_pack_buf, buf); -} - -int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb) -{ - GIT_BUF_WRAP_PRIVATE(buf, git_packbuilder__write_buf, pb); -} - -static int write_cb(void *buf, size_t len, void *payload) -{ - struct pack_write_context *ctx = payload; - return git_indexer_append(ctx->indexer, buf, len, ctx->stats); -} - -int git_packbuilder_write( - git_packbuilder *pb, - const char *path, - unsigned int mode, - git_indexer_progress_cb progress_cb, - void *progress_cb_payload) -{ - int error = -1; - git_str object_path = GIT_STR_INIT; - git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; - git_indexer *indexer = NULL; - git_indexer_progress stats; - struct pack_write_context ctx; - int t; - - PREPARE_PACK; - - if (path == NULL) { - if ((error = git_repository__item_path(&object_path, pb->repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0) - goto cleanup; - if ((error = git_str_joinpath(&object_path, git_str_cstr(&object_path), "pack")) < 0) - goto cleanup; - path = git_str_cstr(&object_path); - } - - opts.progress_cb = progress_cb; - opts.progress_cb_payload = progress_cb_payload; - - if ((error = git_indexer_new(&indexer, path, mode, pb->odb, &opts)) < 0) - goto cleanup; - - if (!git_repository__configmap_lookup(&t, pb->repo, GIT_CONFIGMAP_FSYNCOBJECTFILES) && t) - git_indexer__set_fsync(indexer, 1); - - ctx.indexer = indexer; - ctx.stats = &stats; - - if ((error = git_packbuilder_foreach(pb, write_cb, &ctx)) < 0) - goto cleanup; - - if ((error = git_indexer_commit(indexer, &stats)) < 0) - goto cleanup; - -#ifndef GIT_DEPRECATE_HARD - git_oid_cpy(&pb->pack_oid, git_indexer_hash(indexer)); -#endif - - pb->pack_name = git__strdup(git_indexer_name(indexer)); - GIT_ERROR_CHECK_ALLOC(pb->pack_name); - -cleanup: - git_indexer_free(indexer); - git_str_dispose(&object_path); - return error; -} - -#undef PREPARE_PACK - -#ifndef GIT_DEPRECATE_HARD -const git_oid *git_packbuilder_hash(git_packbuilder *pb) -{ - return &pb->pack_oid; -} -#endif - -const char *git_packbuilder_name(git_packbuilder *pb) -{ - return pb->pack_name; -} - - -static int cb_tree_walk( - const char *root, const git_tree_entry *entry, void *payload) -{ - int error; - struct tree_walk_context *ctx = payload; - - /* A commit inside a tree represents a submodule commit and should be skipped. */ - if (git_tree_entry_type(entry) == GIT_OBJECT_COMMIT) - return 0; - - if (!(error = git_str_sets(&ctx->buf, root)) && - !(error = git_str_puts(&ctx->buf, git_tree_entry_name(entry)))) - error = git_packbuilder_insert( - ctx->pb, git_tree_entry_id(entry), git_str_cstr(&ctx->buf)); - - return error; -} - -int git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid *oid) -{ - git_commit *commit; - - if (git_commit_lookup(&commit, pb->repo, oid) < 0 || - git_packbuilder_insert(pb, oid, NULL) < 0) - return -1; - - if (git_packbuilder_insert_tree(pb, git_commit_tree_id(commit)) < 0) - return -1; - - git_commit_free(commit); - return 0; -} - -int git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *oid) -{ - int error; - git_tree *tree = NULL; - struct tree_walk_context context = { pb, GIT_STR_INIT }; - - if (!(error = git_tree_lookup(&tree, pb->repo, oid)) && - !(error = git_packbuilder_insert(pb, oid, NULL))) - error = git_tree_walk(tree, GIT_TREEWALK_PRE, cb_tree_walk, &context); - - git_tree_free(tree); - git_str_dispose(&context.buf); - return error; -} - -int git_packbuilder_insert_recur(git_packbuilder *pb, const git_oid *id, const char *name) -{ - git_object *obj; - int error; - - GIT_ASSERT_ARG(pb); - GIT_ASSERT_ARG(id); - - if ((error = git_object_lookup(&obj, pb->repo, id, GIT_OBJECT_ANY)) < 0) - return error; - - switch (git_object_type(obj)) { - case GIT_OBJECT_BLOB: - error = git_packbuilder_insert(pb, id, name); - break; - case GIT_OBJECT_TREE: - error = git_packbuilder_insert_tree(pb, id); - break; - case GIT_OBJECT_COMMIT: - error = git_packbuilder_insert_commit(pb, id); - break; - case GIT_OBJECT_TAG: - if ((error = git_packbuilder_insert(pb, id, name)) < 0) - goto cleanup; - error = git_packbuilder_insert_recur(pb, git_tag_target_id((git_tag *) obj), NULL); - break; - - default: - git_error_set(GIT_ERROR_INVALID, "unknown object type"); - error = -1; - } - -cleanup: - git_object_free(obj); - return error; -} - -size_t git_packbuilder_object_count(git_packbuilder *pb) -{ - return pb->nr_objects; -} - -size_t git_packbuilder_written(git_packbuilder *pb) -{ - return pb->nr_written; -} - -static int lookup_walk_object(struct walk_object **out, git_packbuilder *pb, const git_oid *id) -{ - struct walk_object *obj; - - obj = git_pool_mallocz(&pb->object_pool, 1); - if (!obj) { - git_error_set_oom(); - return -1; - } - - git_oid_cpy(&obj->id, id); - - *out = obj; - return 0; -} - -static int retrieve_object(struct walk_object **out, git_packbuilder *pb, const git_oid *id) -{ - struct walk_object *obj; - int error; - - if ((obj = git_oidmap_get(pb->walk_objects, id)) == NULL) { - if ((error = lookup_walk_object(&obj, pb, id)) < 0) - return error; - - if ((error = git_oidmap_set(pb->walk_objects, &obj->id, obj)) < 0) - return error; - } - - *out = obj; - return 0; -} - -static int mark_blob_uninteresting(git_packbuilder *pb, const git_oid *id) -{ - int error; - struct walk_object *obj; - - if ((error = retrieve_object(&obj, pb, id)) < 0) - return error; - - obj->uninteresting = 1; - - return 0; -} - -static int mark_tree_uninteresting(git_packbuilder *pb, const git_oid *id) -{ - struct walk_object *obj; - git_tree *tree; - int error; - size_t i; - - if ((error = retrieve_object(&obj, pb, id)) < 0) - return error; - - if (obj->uninteresting) - return 0; - - obj->uninteresting = 1; - - if ((error = git_tree_lookup(&tree, pb->repo, id)) < 0) - return error; - - for (i = 0; i < git_tree_entrycount(tree); i++) { - const git_tree_entry *entry = git_tree_entry_byindex(tree, i); - const git_oid *entry_id = git_tree_entry_id(entry); - switch (git_tree_entry_type(entry)) { - case GIT_OBJECT_TREE: - if ((error = mark_tree_uninteresting(pb, entry_id)) < 0) - goto cleanup; - break; - case GIT_OBJECT_BLOB: - if ((error = mark_blob_uninteresting(pb, entry_id)) < 0) - goto cleanup; - break; - default: - /* it's a submodule or something unknown, we don't want it */ - ; - } - } - -cleanup: - git_tree_free(tree); - return error; -} - -/* - * Mark the edges of the graph uninteresting. Since we start from a - * git_revwalk, the commits are already uninteresting, but we need to - * mark the trees and blobs. - */ -static int mark_edges_uninteresting(git_packbuilder *pb, git_commit_list *commits) -{ - int error; - git_commit_list *list; - git_commit *commit; - - for (list = commits; list; list = list->next) { - if (!list->item->uninteresting) - continue; - - if ((error = git_commit_lookup(&commit, pb->repo, &list->item->oid)) < 0) - return error; - - error = mark_tree_uninteresting(pb, git_commit_tree_id(commit)); - git_commit_free(commit); - - if (error < 0) - return error; - } - - return 0; -} - -static int pack_objects_insert_tree(git_packbuilder *pb, git_tree *tree) -{ - size_t i; - int error; - git_tree *subtree; - struct walk_object *obj; - const char *name; - - if ((error = retrieve_object(&obj, pb, git_tree_id(tree))) < 0) - return error; - - if (obj->seen || obj->uninteresting) - return 0; - - obj->seen = 1; - - if ((error = git_packbuilder_insert(pb, &obj->id, NULL))) - return error; - - for (i = 0; i < git_tree_entrycount(tree); i++) { - const git_tree_entry *entry = git_tree_entry_byindex(tree, i); - const git_oid *entry_id = git_tree_entry_id(entry); - switch (git_tree_entry_type(entry)) { - case GIT_OBJECT_TREE: - if ((error = git_tree_lookup(&subtree, pb->repo, entry_id)) < 0) - return error; - - error = pack_objects_insert_tree(pb, subtree); - git_tree_free(subtree); - - if (error < 0) - return error; - - break; - case GIT_OBJECT_BLOB: - if ((error = retrieve_object(&obj, pb, entry_id)) < 0) - return error; - if (obj->uninteresting) - continue; - name = git_tree_entry_name(entry); - if ((error = git_packbuilder_insert(pb, entry_id, name)) < 0) - return error; - break; - default: - /* it's a submodule or something unknown, we don't want it */ - ; - } - } - - - return error; -} - -static int pack_objects_insert_commit(git_packbuilder *pb, struct walk_object *obj) -{ - int error; - git_commit *commit = NULL; - git_tree *tree = NULL; - - obj->seen = 1; - - if ((error = git_packbuilder_insert(pb, &obj->id, NULL)) < 0) - return error; - - if ((error = git_commit_lookup(&commit, pb->repo, &obj->id)) < 0) - return error; - - if ((error = git_tree_lookup(&tree, pb->repo, git_commit_tree_id(commit))) < 0) - goto cleanup; - - if ((error = pack_objects_insert_tree(pb, tree)) < 0) - goto cleanup; - -cleanup: - git_commit_free(commit); - git_tree_free(tree); - return error; -} - -int git_packbuilder_insert_walk(git_packbuilder *pb, git_revwalk *walk) -{ - int error; - git_oid id; - struct walk_object *obj; - - GIT_ASSERT_ARG(pb); - GIT_ASSERT_ARG(walk); - - if ((error = mark_edges_uninteresting(pb, walk->user_input)) < 0) - return error; - - /* - * TODO: git marks the parents of the edges - * uninteresting. This may provide a speed advantage, but does - * seem to assume the remote does not have a single-commit - * history on the other end. - */ - - /* walk down each tree up to the blobs and insert them, stopping when uninteresting */ - while ((error = git_revwalk_next(&id, walk)) == 0) { - if ((error = retrieve_object(&obj, pb, &id)) < 0) - return error; - - if (obj->seen || obj->uninteresting) - continue; - - if ((error = pack_objects_insert_commit(pb, obj)) < 0) - return error; - } - - if (error == GIT_ITEROVER) - error = 0; - - return error; -} - -int git_packbuilder_set_callbacks(git_packbuilder *pb, git_packbuilder_progress progress_cb, void *progress_cb_payload) -{ - if (!pb) - return -1; - - pb->progress_cb = progress_cb; - pb->progress_cb_payload = progress_cb_payload; - - return 0; -} - -void git_packbuilder_free(git_packbuilder *pb) -{ - if (pb == NULL) - return; - -#ifdef GIT_THREADS - - git_mutex_free(&pb->cache_mutex); - git_mutex_free(&pb->progress_mutex); - git_cond_free(&pb->progress_cond); - -#endif - - if (pb->odb) - git_odb_free(pb->odb); - - if (pb->object_ix) - git_oidmap_free(pb->object_ix); - - if (pb->object_list) - git__free(pb->object_list); - - git_oidmap_free(pb->walk_objects); - git_pool_clear(&pb->object_pool); - - git_hash_ctx_cleanup(&pb->ctx); - git_zstream_free(&pb->zstream); - - git__free(pb->pack_name); - - git__free(pb); -} diff --git a/src/pack-objects.h b/src/pack-objects.h deleted file mode 100644 index 2faa3ec7f..000000000 --- a/src/pack-objects.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_pack_objects_h__ -#define INCLUDE_pack_objects_h__ - -#include "common.h" - -#include "str.h" -#include "hash.h" -#include "oidmap.h" -#include "netops.h" -#include "zstream.h" -#include "pool.h" -#include "indexer.h" - -#include "git2/oid.h" -#include "git2/pack.h" - -#define GIT_PACK_WINDOW 10 /* number of objects to possibly delta against */ -#define GIT_PACK_DEPTH 50 /* max delta depth */ -#define GIT_PACK_DELTA_CACHE_SIZE (256 * 1024 * 1024) -#define GIT_PACK_DELTA_CACHE_LIMIT 1000 -#define GIT_PACK_BIG_FILE_THRESHOLD (512 * 1024 * 1024) - -typedef struct git_pobject { - git_oid id; - git_object_t type; - off64_t offset; - - size_t size; - - unsigned int hash; /* name hint hash */ - - struct git_pobject *delta; /* delta base object */ - struct git_pobject *delta_child; /* deltified objects who bases me */ - struct git_pobject *delta_sibling; /* other deltified objects - * who uses the same base as - * me */ - - void *delta_data; - size_t delta_size; - size_t z_delta_size; - - unsigned int written:1, - recursing:1, - tagged:1, - filled:1; -} git_pobject; - -struct git_packbuilder { - git_repository *repo; /* associated repository */ - git_odb *odb; /* associated object database */ - - git_hash_ctx ctx; - git_zstream zstream; - - uint32_t nr_objects, - nr_deltified, - nr_written, - nr_remaining; - - size_t nr_alloc; - - git_pobject *object_list; - - git_oidmap *object_ix; - - git_oidmap *walk_objects; - git_pool object_pool; - -#ifndef GIT_DEPRECATE_HARD - git_oid pack_oid; /* hash of written pack */ -#endif - char *pack_name; /* name of written pack */ - - /* synchronization objects */ - git_mutex cache_mutex; - git_mutex progress_mutex; - git_cond progress_cond; - - /* configs */ - size_t delta_cache_size; - size_t max_delta_cache_size; - size_t cache_max_small_delta_size; - size_t big_file_threshold; - size_t window_memory_limit; - - unsigned int nr_threads; /* nr of threads to use */ - - git_packbuilder_progress progress_cb; - void *progress_cb_payload; - double last_progress_report_time; /* the time progress was last reported */ - - bool done; -}; - -int git_packbuilder__write_buf(git_str *buf, git_packbuilder *pb); -int git_packbuilder__prepare(git_packbuilder *pb); - - -#endif diff --git a/src/pack.c b/src/pack.c deleted file mode 100644 index 5c0cba7e8..000000000 --- a/src/pack.c +++ /dev/null @@ -1,1629 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "pack.h" - -#include "delta.h" -#include "futils.h" -#include "mwindow.h" -#include "odb.h" -#include "oid.h" -#include "oidarray.h" - -/* Option to bypass checking existence of '.keep' files */ -bool git_disable_pack_keep_file_checks = false; - -static int packfile_open_locked(struct git_pack_file *p); -static off64_t nth_packed_object_offset_locked(struct git_pack_file *p, uint32_t n); -static int packfile_unpack_compressed( - git_rawobj *obj, - struct git_pack_file *p, - git_mwindow **w_curs, - off64_t *curpos, - size_t size, - git_object_t type); - -/* Can find the offset of an object given - * a prefix of an identifier. - * Throws GIT_EAMBIGUOUSOIDPREFIX if short oid - * is ambiguous within the pack. - * This method assumes that len is between - * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. - */ -static int pack_entry_find_offset( - off64_t *offset_out, - git_oid *found_oid, - struct git_pack_file *p, - const git_oid *short_oid, - size_t len); - -static int packfile_error(const char *message) -{ - git_error_set(GIT_ERROR_ODB, "invalid pack file - %s", message); - return -1; -} - -/******************** - * Delta base cache - ********************/ - -static git_pack_cache_entry *new_cache_object(git_rawobj *source) -{ - git_pack_cache_entry *e = git__calloc(1, sizeof(git_pack_cache_entry)); - if (!e) - return NULL; - - git_atomic32_inc(&e->refcount); - memcpy(&e->raw, source, sizeof(git_rawobj)); - - return e; -} - -static void free_cache_object(void *o) -{ - git_pack_cache_entry *e = (git_pack_cache_entry *)o; - - if (e != NULL) { - git__free(e->raw.data); - git__free(e); - } -} - -static void cache_free(git_pack_cache *cache) -{ - git_pack_cache_entry *entry; - - if (cache->entries) { - git_offmap_foreach_value(cache->entries, entry, { - free_cache_object(entry); - }); - - git_offmap_free(cache->entries); - cache->entries = NULL; - } -} - -static int cache_init(git_pack_cache *cache) -{ - if (git_offmap_new(&cache->entries) < 0) - return -1; - - cache->memory_limit = GIT_PACK_CACHE_MEMORY_LIMIT; - - if (git_mutex_init(&cache->lock)) { - git_error_set(GIT_ERROR_OS, "failed to initialize pack cache mutex"); - - git__free(cache->entries); - cache->entries = NULL; - - return -1; - } - - return 0; -} - -static git_pack_cache_entry *cache_get(git_pack_cache *cache, off64_t offset) -{ - git_pack_cache_entry *entry; - - if (git_mutex_lock(&cache->lock) < 0) - return NULL; - - if ((entry = git_offmap_get(cache->entries, offset)) != NULL) { - git_atomic32_inc(&entry->refcount); - entry->last_usage = cache->use_ctr++; - } - git_mutex_unlock(&cache->lock); - - return entry; -} - -/* Run with the cache lock held */ -static void free_lowest_entry(git_pack_cache *cache) -{ - off64_t offset; - git_pack_cache_entry *entry; - - git_offmap_foreach(cache->entries, offset, entry, { - if (entry && git_atomic32_get(&entry->refcount) == 0) { - cache->memory_used -= entry->raw.len; - git_offmap_delete(cache->entries, offset); - free_cache_object(entry); - } - }); -} - -static int cache_add( - git_pack_cache_entry **cached_out, - git_pack_cache *cache, - git_rawobj *base, - off64_t offset) -{ - git_pack_cache_entry *entry; - int exists; - - if (base->len > GIT_PACK_CACHE_SIZE_LIMIT) - return -1; - - entry = new_cache_object(base); - if (entry) { - if (git_mutex_lock(&cache->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock cache"); - git__free(entry); - return -1; - } - /* Add it to the cache if nobody else has */ - exists = git_offmap_exists(cache->entries, offset); - if (!exists) { - while (cache->memory_used + base->len > cache->memory_limit) - free_lowest_entry(cache); - - git_offmap_set(cache->entries, offset, entry); - cache->memory_used += entry->raw.len; - - *cached_out = entry; - } - git_mutex_unlock(&cache->lock); - /* Somebody beat us to adding it into the cache */ - if (exists) { - git__free(entry); - return -1; - } - } - - return 0; -} - -/*********************************************************** - * - * PACK INDEX METHODS - * - ***********************************************************/ - -static void pack_index_free(struct git_pack_file *p) -{ - if (p->oids) { - git__free(p->oids); - p->oids = NULL; - } - if (p->index_map.data) { - git_futils_mmap_free(&p->index_map); - p->index_map.data = NULL; - } -} - -/* Run with the packfile lock held */ -static int pack_index_check_locked(const char *path, struct git_pack_file *p) -{ - struct git_pack_idx_header *hdr; - uint32_t version, nr, i, *index; - void *idx_map; - size_t idx_size; - struct stat st; - int error; - /* TODO: properly open the file without access time using O_NOATIME */ - git_file fd = git_futils_open_ro(path); - if (fd < 0) - return fd; - - if (p_fstat(fd, &st) < 0) { - p_close(fd); - git_error_set(GIT_ERROR_OS, "unable to stat pack index '%s'", path); - return -1; - } - - if (!S_ISREG(st.st_mode) || - !git__is_sizet(st.st_size) || - (idx_size = (size_t)st.st_size) < 4 * 256 + 20 + 20) - { - p_close(fd); - git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path); - return -1; - } - - error = git_futils_mmap_ro(&p->index_map, fd, 0, idx_size); - - p_close(fd); - - if (error < 0) - return error; - - hdr = idx_map = p->index_map.data; - - if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) { - version = ntohl(hdr->idx_version); - - if (version < 2 || version > 2) { - git_futils_mmap_free(&p->index_map); - return packfile_error("unsupported index version"); - } - - } else - version = 1; - - nr = 0; - index = idx_map; - - if (version > 1) - index += 2; /* skip index header */ - - for (i = 0; i < 256; i++) { - uint32_t n = ntohl(index[i]); - if (n < nr) { - git_futils_mmap_free(&p->index_map); - return packfile_error("index is non-monotonic"); - } - nr = n; - } - - if (version == 1) { - /* - * Total size: - * - 256 index entries 4 bytes each - * - 24-byte entries * nr (20-byte sha1 + 4-byte offset) - * - 20-byte SHA1 of the packfile - * - 20-byte SHA1 file checksum - */ - if (idx_size != 4*256 + nr * 24 + 20 + 20) { - git_futils_mmap_free(&p->index_map); - return packfile_error("index is corrupted"); - } - } else if (version == 2) { - /* - * Minimum size: - * - 8 bytes of header - * - 256 index entries 4 bytes each - * - 20-byte sha1 entry * nr - * - 4-byte crc entry * nr - * - 4-byte offset entry * nr - * - 20-byte SHA1 of the packfile - * - 20-byte SHA1 file checksum - * And after the 4-byte offset table might be a - * variable sized table containing 8-byte entries - * for offsets larger than 2^31. - */ - unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20; - unsigned long max_size = min_size; - - if (nr) - max_size += (nr - 1)*8; - - if (idx_size < min_size || idx_size > max_size) { - git_futils_mmap_free(&p->index_map); - return packfile_error("wrong index size"); - } - } - - p->num_objects = nr; - p->index_version = version; - return 0; -} - -/* Run with the packfile lock held */ -static int pack_index_open_locked(struct git_pack_file *p) -{ - int error = 0; - size_t name_len; - git_str idx_name = GIT_STR_INIT; - - if (p->index_version > -1) - goto cleanup; - - /* checked by git_pack_file alloc */ - name_len = strlen(p->pack_name); - GIT_ASSERT(name_len > strlen(".pack")); - - if ((error = git_str_init(&idx_name, name_len)) < 0) - goto cleanup; - - git_str_put(&idx_name, p->pack_name, name_len - strlen(".pack")); - git_str_puts(&idx_name, ".idx"); - if (git_str_oom(&idx_name)) { - error = -1; - goto cleanup; - } - - if (p->index_version == -1) - error = pack_index_check_locked(idx_name.ptr, p); - -cleanup: - git_str_dispose(&idx_name); - - return error; -} - -static unsigned char *pack_window_open( - struct git_pack_file *p, - git_mwindow **w_cursor, - off64_t offset, - unsigned int *left) -{ - unsigned char *pack_data = NULL; - - if (git_mutex_lock(&p->lock) < 0) { - git_error_set(GIT_ERROR_THREAD, "unable to lock packfile"); - return NULL; - } - if (git_mutex_lock(&p->mwf.lock) < 0) { - git_mutex_unlock(&p->lock); - git_error_set(GIT_ERROR_THREAD, "unable to lock packfile"); - return NULL; - } - - if (p->mwf.fd == -1 && packfile_open_locked(p) < 0) - goto cleanup; - - /* Since packfiles end in a hash of their content and it's - * pointless to ask for an offset into the middle of that - * hash, and the pack_window_contains function above wouldn't match - * don't allow an offset too close to the end of the file. - * - * Don't allow a negative offset, as that means we've wrapped - * around. - */ - if (offset > (p->mwf.size - 20)) - goto cleanup; - if (offset < 0) - goto cleanup; - - pack_data = git_mwindow_open(&p->mwf, w_cursor, offset, 20, left); - -cleanup: - git_mutex_unlock(&p->mwf.lock); - git_mutex_unlock(&p->lock); - return pack_data; - } - -/* - * The per-object header is a pretty dense thing, which is - * - first byte: low four bits are "size", - * then three bits of "type", - * with the high bit being "size continues". - * - each byte afterwards: low seven bits are size continuation, - * with the high bit being "size continues" - */ -int git_packfile__object_header(size_t *out, unsigned char *hdr, size_t size, git_object_t type) -{ - unsigned char *hdr_base; - unsigned char c; - - GIT_ASSERT_ARG(type >= GIT_OBJECT_COMMIT && type <= GIT_OBJECT_REF_DELTA); - - /* TODO: add support for chunked objects; see git.git 6c0d19b1 */ - - c = (unsigned char)((type << 4) | (size & 15)); - size >>= 4; - hdr_base = hdr; - - while (size) { - *hdr++ = c | 0x80; - c = size & 0x7f; - size >>= 7; - } - *hdr++ = c; - - *out = (hdr - hdr_base); - return 0; -} - - -static int packfile_unpack_header1( - unsigned long *usedp, - size_t *sizep, - git_object_t *type, - const unsigned char *buf, - unsigned long len) -{ - unsigned shift; - unsigned long size, c; - unsigned long used = 0; - - c = buf[used++]; - *type = (c >> 4) & 7; - size = c & 15; - shift = 4; - while (c & 0x80) { - if (len <= used) { - git_error_set(GIT_ERROR_ODB, "buffer too small"); - return GIT_EBUFS; - } - - if (bitsizeof(long) <= shift) { - *usedp = 0; - git_error_set(GIT_ERROR_ODB, "packfile corrupted"); - return -1; - } - - c = buf[used++]; - size += (c & 0x7f) << shift; - shift += 7; - } - - *sizep = (size_t)size; - *usedp = used; - return 0; -} - -int git_packfile_unpack_header( - size_t *size_p, - git_object_t *type_p, - struct git_pack_file *p, - git_mwindow **w_curs, - off64_t *curpos) -{ - unsigned char *base; - unsigned int left; - unsigned long used; - int error; - - if ((error = git_mutex_lock(&p->lock)) < 0) - return error; - if ((error = git_mutex_lock(&p->mwf.lock)) < 0) { - git_mutex_unlock(&p->lock); - return error; - } - - if (p->mwf.fd == -1 && (error = packfile_open_locked(p)) < 0) { - git_mutex_unlock(&p->lock); - git_mutex_unlock(&p->mwf.lock); - return error; - } - - /* pack_window_open() assures us we have [base, base + 20) available - * as a range that we can look at at. (Its actually the hash - * size that is assured.) With our object header encoding - * the maximum deflated object size is 2^137, which is just - * insane, so we know won't exceed what we have been given. - */ - base = git_mwindow_open(&p->mwf, w_curs, *curpos, 20, &left); - git_mutex_unlock(&p->lock); - git_mutex_unlock(&p->mwf.lock); - if (base == NULL) - return GIT_EBUFS; - - error = packfile_unpack_header1(&used, size_p, type_p, base, left); - git_mwindow_close(w_curs); - if (error == GIT_EBUFS) - return error; - else if (error < 0) - return packfile_error("header length is zero"); - - *curpos += used; - return 0; -} - -int git_packfile_resolve_header( - size_t *size_p, - git_object_t *type_p, - struct git_pack_file *p, - off64_t offset) -{ - git_mwindow *w_curs = NULL; - off64_t curpos = offset; - size_t size; - git_object_t type; - off64_t base_offset; - int error; - - error = git_mutex_lock(&p->lock); - if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); - return error; - } - error = git_mutex_lock(&p->mwf.lock); - if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); - git_mutex_unlock(&p->lock); - return error; - } - - if (p->mwf.fd == -1 && (error = packfile_open_locked(p)) < 0) { - git_mutex_unlock(&p->mwf.lock); - git_mutex_unlock(&p->lock); - return error; - } - git_mutex_unlock(&p->mwf.lock); - git_mutex_unlock(&p->lock); - - error = git_packfile_unpack_header(&size, &type, p, &w_curs, &curpos); - if (error < 0) - return error; - - if (type == GIT_OBJECT_OFS_DELTA || type == GIT_OBJECT_REF_DELTA) { - size_t base_size; - git_packfile_stream stream; - - error = get_delta_base(&base_offset, p, &w_curs, &curpos, type, offset); - git_mwindow_close(&w_curs); - - if (error < 0) - return error; - - if ((error = git_packfile_stream_open(&stream, p, curpos)) < 0) - return error; - error = git_delta_read_header_fromstream(&base_size, size_p, &stream); - git_packfile_stream_dispose(&stream); - if (error < 0) - return error; - } else { - *size_p = size; - base_offset = 0; - } - - while (type == GIT_OBJECT_OFS_DELTA || type == GIT_OBJECT_REF_DELTA) { - curpos = base_offset; - error = git_packfile_unpack_header(&size, &type, p, &w_curs, &curpos); - if (error < 0) - return error; - if (type != GIT_OBJECT_OFS_DELTA && type != GIT_OBJECT_REF_DELTA) - break; - - error = get_delta_base(&base_offset, p, &w_curs, &curpos, type, base_offset); - git_mwindow_close(&w_curs); - - if (error < 0) - return error; - } - *type_p = type; - - return error; -} - -#define SMALL_STACK_SIZE 64 - -/** - * Generate the chain of dependencies which we need to get to the - * object at `off`. `chain` is used a stack, popping gives the right - * order to apply deltas on. If an object is found in the pack's base - * cache, we stop calculating there. - */ -static int pack_dependency_chain(git_dependency_chain *chain_out, - git_pack_cache_entry **cached_out, off64_t *cached_off, - struct pack_chain_elem *small_stack, size_t *stack_sz, - struct git_pack_file *p, off64_t obj_offset) -{ - git_dependency_chain chain = GIT_ARRAY_INIT; - git_mwindow *w_curs = NULL; - off64_t curpos = obj_offset, base_offset; - int error = 0, use_heap = 0; - size_t size, elem_pos; - git_object_t type; - - elem_pos = 0; - while (true) { - struct pack_chain_elem *elem; - git_pack_cache_entry *cached = NULL; - - /* if we have a base cached, we can stop here instead */ - if ((cached = cache_get(&p->bases, obj_offset)) != NULL) { - *cached_out = cached; - *cached_off = obj_offset; - break; - } - - /* if we run out of space on the small stack, use the array */ - if (elem_pos == SMALL_STACK_SIZE) { - git_array_init_to_size(chain, elem_pos); - GIT_ERROR_CHECK_ARRAY(chain); - memcpy(chain.ptr, small_stack, elem_pos * sizeof(struct pack_chain_elem)); - chain.size = elem_pos; - use_heap = 1; - } - - curpos = obj_offset; - if (!use_heap) { - elem = &small_stack[elem_pos]; - } else { - elem = git_array_alloc(chain); - if (!elem) { - error = -1; - goto on_error; - } - } - - elem->base_key = obj_offset; - - error = git_packfile_unpack_header(&size, &type, p, &w_curs, &curpos); - if (error < 0) - goto on_error; - - elem->offset = curpos; - elem->size = size; - elem->type = type; - elem->base_key = obj_offset; - - if (type != GIT_OBJECT_OFS_DELTA && type != GIT_OBJECT_REF_DELTA) - break; - - error = get_delta_base(&base_offset, p, &w_curs, &curpos, type, obj_offset); - git_mwindow_close(&w_curs); - - if (error < 0) - goto on_error; - - /* we need to pass the pos *after* the delta-base bit */ - elem->offset = curpos; - - /* go through the loop again, but with the new object */ - obj_offset = base_offset; - elem_pos++; - } - - - *stack_sz = elem_pos + 1; - *chain_out = chain; - return error; - -on_error: - git_array_clear(chain); - return error; -} - -int git_packfile_unpack( - git_rawobj *obj, - struct git_pack_file *p, - off64_t *obj_offset) -{ - git_mwindow *w_curs = NULL; - off64_t curpos = *obj_offset; - int error, free_base = 0; - git_dependency_chain chain = GIT_ARRAY_INIT; - struct pack_chain_elem *elem = NULL, *stack; - git_pack_cache_entry *cached = NULL; - struct pack_chain_elem small_stack[SMALL_STACK_SIZE]; - size_t stack_size = 0, elem_pos, alloclen; - git_object_t base_type; - - error = git_mutex_lock(&p->lock); - if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); - return error; - } - error = git_mutex_lock(&p->mwf.lock); - if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); - git_mutex_unlock(&p->lock); - return error; - } - - if (p->mwf.fd == -1) - error = packfile_open_locked(p); - git_mutex_unlock(&p->mwf.lock); - git_mutex_unlock(&p->lock); - if (error < 0) - return error; - - /* - * TODO: optionally check the CRC on the packfile - */ - - error = pack_dependency_chain(&chain, &cached, obj_offset, small_stack, &stack_size, p, *obj_offset); - if (error < 0) - return error; - - obj->data = NULL; - obj->len = 0; - obj->type = GIT_OBJECT_INVALID; - - /* let's point to the right stack */ - stack = chain.ptr ? chain.ptr : small_stack; - - elem_pos = stack_size; - if (cached) { - memcpy(obj, &cached->raw, sizeof(git_rawobj)); - base_type = obj->type; - elem_pos--; /* stack_size includes the base, which isn't actually there */ - } else { - elem = &stack[--elem_pos]; - base_type = elem->type; - } - - switch (base_type) { - case GIT_OBJECT_COMMIT: - case GIT_OBJECT_TREE: - case GIT_OBJECT_BLOB: - case GIT_OBJECT_TAG: - if (!cached) { - curpos = elem->offset; - error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type); - git_mwindow_close(&w_curs); - base_type = elem->type; - } - if (error < 0) - goto cleanup; - break; - case GIT_OBJECT_OFS_DELTA: - case GIT_OBJECT_REF_DELTA: - error = packfile_error("dependency chain ends in a delta"); - goto cleanup; - default: - error = packfile_error("invalid packfile type in header"); - goto cleanup; - } - - /* - * Finding the object we want a cached base element is - * problematic, as we need to make sure we don't accidentally - * give the caller the cached object, which it would then feel - * free to free, so we need to copy the data. - */ - if (cached && stack_size == 1) { - void *data = obj->data; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, obj->len, 1); - obj->data = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(obj->data); - - memcpy(obj->data, data, obj->len + 1); - git_atomic32_dec(&cached->refcount); - goto cleanup; - } - - /* we now apply each consecutive delta until we run out */ - while (elem_pos > 0 && !error) { - git_rawobj base, delta; - - /* - * We can now try to add the base to the cache, as - * long as it's not already the cached one. - */ - if (!cached) - free_base = !!cache_add(&cached, &p->bases, obj, elem->base_key); - - elem = &stack[elem_pos - 1]; - curpos = elem->offset; - error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type); - git_mwindow_close(&w_curs); - - if (error < 0) { - /* We have transferred ownership of the data to the cache. */ - obj->data = NULL; - break; - } - - /* the current object becomes the new base, on which we apply the delta */ - base = *obj; - obj->data = NULL; - obj->len = 0; - obj->type = GIT_OBJECT_INVALID; - - error = git_delta_apply(&obj->data, &obj->len, base.data, base.len, delta.data, delta.len); - obj->type = base_type; - - /* - * We usually don't want to free the base at this - * point, as we put it into the cache in the previous - * iteration. free_base lets us know that we got the - * base object directly from the packfile, so we can free it. - */ - git__free(delta.data); - if (free_base) { - free_base = 0; - git__free(base.data); - } - - if (cached) { - git_atomic32_dec(&cached->refcount); - cached = NULL; - } - - if (error < 0) - break; - - elem_pos--; - } - -cleanup: - if (error < 0) { - git__free(obj->data); - if (cached) - git_atomic32_dec(&cached->refcount); - } - - if (elem) - *obj_offset = curpos; - - git_array_clear(chain); - return error; -} - -int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, off64_t curpos) -{ - memset(obj, 0, sizeof(git_packfile_stream)); - obj->curpos = curpos; - obj->p = p; - - if (git_zstream_init(&obj->zstream, GIT_ZSTREAM_INFLATE) < 0) { - git_error_set(GIT_ERROR_ZLIB, "failed to init packfile stream"); - return -1; - } - - return 0; -} - -ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len) -{ - unsigned int window_len; - unsigned char *in; - int error; - - if (obj->done) - return 0; - - if ((in = pack_window_open(obj->p, &obj->mw, obj->curpos, &window_len)) == NULL) - return GIT_EBUFS; - - if ((error = git_zstream_set_input(&obj->zstream, in, window_len)) < 0 || - (error = git_zstream_get_output_chunk(buffer, &len, &obj->zstream)) < 0) { - git_mwindow_close(&obj->mw); - git_error_set(GIT_ERROR_ZLIB, "error reading from the zlib stream"); - return -1; - } - - git_mwindow_close(&obj->mw); - - obj->curpos += window_len - obj->zstream.in_len; - - if (git_zstream_eos(&obj->zstream)) - obj->done = 1; - - /* If we didn't write anything out but we're not done, we need more data */ - if (!len && !git_zstream_eos(&obj->zstream)) - return GIT_EBUFS; - - return len; - -} - -void git_packfile_stream_dispose(git_packfile_stream *obj) -{ - git_zstream_free(&obj->zstream); -} - -static int packfile_unpack_compressed( - git_rawobj *obj, - struct git_pack_file *p, - git_mwindow **mwindow, - off64_t *position, - size_t size, - git_object_t type) -{ - git_zstream zstream = GIT_ZSTREAM_INIT; - size_t buffer_len, total = 0; - char *data = NULL; - int error; - - GIT_ERROR_CHECK_ALLOC_ADD(&buffer_len, size, 1); - data = git__calloc(1, buffer_len); - GIT_ERROR_CHECK_ALLOC(data); - - if ((error = git_zstream_init(&zstream, GIT_ZSTREAM_INFLATE)) < 0) { - git_error_set(GIT_ERROR_ZLIB, "failed to init zlib stream on unpack"); - goto out; - } - - do { - size_t bytes = buffer_len - total; - unsigned int window_len, consumed; - unsigned char *in; - - if ((in = pack_window_open(p, mwindow, *position, &window_len)) == NULL) { - error = -1; - goto out; - } - - if ((error = git_zstream_set_input(&zstream, in, window_len)) < 0 || - (error = git_zstream_get_output_chunk(data + total, &bytes, &zstream)) < 0) { - git_mwindow_close(mwindow); - goto out; - } - - git_mwindow_close(mwindow); - - consumed = window_len - (unsigned int)zstream.in_len; - - if (!bytes && !consumed) { - git_error_set(GIT_ERROR_ZLIB, "error inflating zlib stream"); - error = -1; - goto out; - } - - *position += consumed; - total += bytes; - } while (!git_zstream_eos(&zstream)); - - if (total != size || !git_zstream_eos(&zstream)) { - git_error_set(GIT_ERROR_ZLIB, "error inflating zlib stream"); - error = -1; - goto out; - } - - obj->type = type; - obj->len = size; - obj->data = data; - -out: - git_zstream_free(&zstream); - if (error) - git__free(data); - - return error; -} - -/* - * curpos is where the data starts, delta_obj_offset is the where the - * header starts - */ -int get_delta_base( - off64_t *delta_base_out, - struct git_pack_file *p, - git_mwindow **w_curs, - off64_t *curpos, - git_object_t type, - off64_t delta_obj_offset) -{ - unsigned int left = 0; - unsigned char *base_info; - off64_t base_offset; - git_oid unused; - - GIT_ASSERT_ARG(delta_base_out); - - base_info = pack_window_open(p, w_curs, *curpos, &left); - /* Assumption: the only reason this would fail is because the file is too small */ - if (base_info == NULL) - return GIT_EBUFS; - /* pack_window_open() assured us we have [base_info, base_info + 20) - * as a range that we can look at without walking off the - * end of the mapped window. Its actually the hash size - * that is assured. An OFS_DELTA longer than the hash size - * is stupid, as then a REF_DELTA would be smaller to store. - */ - if (type == GIT_OBJECT_OFS_DELTA) { - unsigned used = 0; - unsigned char c = base_info[used++]; - size_t unsigned_base_offset = c & 127; - while (c & 128) { - if (left <= used) - return GIT_EBUFS; - unsigned_base_offset += 1; - if (!unsigned_base_offset || MSB(unsigned_base_offset, 7)) - return packfile_error("overflow"); - c = base_info[used++]; - unsigned_base_offset = (unsigned_base_offset << 7) + (c & 127); - } - if (unsigned_base_offset == 0 || (size_t)delta_obj_offset <= unsigned_base_offset) - return packfile_error("out of bounds"); - base_offset = delta_obj_offset - unsigned_base_offset; - *curpos += used; - } else if (type == GIT_OBJECT_REF_DELTA) { - /* If we have the cooperative cache, search in it first */ - if (p->has_cache) { - struct git_pack_entry *entry; - git_oid oid; - - git_oid_fromraw(&oid, base_info); - if ((entry = git_oidmap_get(p->idx_cache, &oid)) != NULL) { - if (entry->offset == 0) - return packfile_error("delta offset is zero"); - - *curpos += 20; - *delta_base_out = entry->offset; - return 0; - } else { - /* If we're building an index, don't try to find the pack - * entry; we just haven't seen it yet. We'll make - * progress again in the next loop. - */ - return GIT_PASSTHROUGH; - } - } - - /* The base entry _must_ be in the same pack */ - if (pack_entry_find_offset(&base_offset, &unused, p, (git_oid *)base_info, GIT_OID_HEXSZ) < 0) - return packfile_error("base entry delta is not in the same pack"); - *curpos += 20; - } else - return packfile_error("unknown object type"); - - if (base_offset == 0) - return packfile_error("delta offset is zero"); - - *delta_base_out = base_offset; - return 0; -} - -/*********************************************************** - * - * PACKFILE METHODS - * - ***********************************************************/ - -void git_packfile_free(struct git_pack_file *p, bool unlink_packfile) -{ - bool locked = true; - - if (!p) - return; - - cache_free(&p->bases); - - if (git_mutex_lock(&p->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock packfile"); - locked = false; - } - if (p->mwf.fd >= 0) { - git_mwindow_free_all(&p->mwf); - p_close(p->mwf.fd); - p->mwf.fd = -1; - } - if (locked) - git_mutex_unlock(&p->lock); - - if (unlink_packfile) - p_unlink(p->pack_name); - - pack_index_free(p); - - git__free(p->bad_object_sha1); - - git_mutex_free(&p->bases.lock); - git_mutex_free(&p->mwf.lock); - git_mutex_free(&p->lock); - git__free(p); -} - -/* Run with the packfile and mwf locks held */ -static int packfile_open_locked(struct git_pack_file *p) -{ - struct stat st; - struct git_pack_header hdr; - git_oid sha1; - unsigned char *idx_sha1; - - if (pack_index_open_locked(p) < 0) - return git_odb__error_notfound("failed to open packfile", NULL, 0); - - if (p->mwf.fd >= 0) - return 0; - - /* TODO: open with noatime */ - p->mwf.fd = git_futils_open_ro(p->pack_name); - if (p->mwf.fd < 0) - goto cleanup; - - if (p_fstat(p->mwf.fd, &st) < 0) { - git_error_set(GIT_ERROR_OS, "could not stat packfile"); - goto cleanup; - } - - /* If we created the struct before we had the pack we lack size. */ - if (!p->mwf.size) { - if (!S_ISREG(st.st_mode)) - goto cleanup; - p->mwf.size = (off64_t)st.st_size; - } else if (p->mwf.size != st.st_size) - goto cleanup; - -#if 0 - /* We leave these file descriptors open with sliding mmap; - * there is no point keeping them open across exec(), though. - */ - fd_flag = fcntl(p->mwf.fd, F_GETFD, 0); - if (fd_flag < 0) - goto cleanup; - - fd_flag |= FD_CLOEXEC; - if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1) - goto cleanup; -#endif - - /* Verify we recognize this pack file format. */ - if (p_read(p->mwf.fd, &hdr, sizeof(hdr)) < 0 || - hdr.hdr_signature != htonl(PACK_SIGNATURE) || - !pack_version_ok(hdr.hdr_version)) - goto cleanup; - - /* Verify the pack matches its index. */ - if (p->num_objects != ntohl(hdr.hdr_entries) || - p_pread(p->mwf.fd, sha1.id, GIT_OID_RAWSZ, p->mwf.size - GIT_OID_RAWSZ) < 0) - goto cleanup; - - idx_sha1 = ((unsigned char *)p->index_map.data) + p->index_map.len - 40; - - if (git_oid__cmp(&sha1, (git_oid *)idx_sha1) != 0) - goto cleanup; - - if (git_mwindow_file_register(&p->mwf) < 0) - goto cleanup; - - return 0; - -cleanup: - git_error_set(GIT_ERROR_OS, "invalid packfile '%s'", p->pack_name); - - if (p->mwf.fd >= 0) - p_close(p->mwf.fd); - p->mwf.fd = -1; - - return -1; -} - -int git_packfile__name(char **out, const char *path) -{ - size_t path_len; - git_str buf = GIT_STR_INIT; - - path_len = strlen(path); - - if (path_len < strlen(".idx")) - return git_odb__error_notfound("invalid packfile path", NULL, 0); - - if (git_str_printf(&buf, "%.*s.pack", (int)(path_len - strlen(".idx")), path) < 0) - return -1; - - *out = git_str_detach(&buf); - return 0; -} - -int git_packfile_alloc(struct git_pack_file **pack_out, const char *path) -{ - struct stat st; - struct git_pack_file *p; - size_t path_len = path ? strlen(path) : 0, alloc_len; - - *pack_out = NULL; - - if (path_len < strlen(".idx")) - return git_odb__error_notfound("invalid packfile path", NULL, 0); - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*p), path_len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); - - p = git__calloc(1, alloc_len); - GIT_ERROR_CHECK_ALLOC(p); - - memcpy(p->pack_name, path, path_len + 1); - - /* - * Make sure a corresponding .pack file exists and that - * the index looks sane. - */ - if (git__suffixcmp(path, ".idx") == 0) { - size_t root_len = path_len - strlen(".idx"); - - if (!git_disable_pack_keep_file_checks) { - memcpy(p->pack_name + root_len, ".keep", sizeof(".keep")); - if (git_fs_path_exists(p->pack_name) == true) - p->pack_keep = 1; - } - - memcpy(p->pack_name + root_len, ".pack", sizeof(".pack")); - } - - if (p_stat(p->pack_name, &st) < 0 || !S_ISREG(st.st_mode)) { - git__free(p); - return git_odb__error_notfound("packfile not found", NULL, 0); - } - - /* ok, it looks sane as far as we can check without - * actually mapping the pack file. - */ - p->mwf.fd = -1; - p->mwf.size = st.st_size; - p->pack_local = 1; - p->mtime = (git_time_t)st.st_mtime; - p->index_version = -1; - - if (git_mutex_init(&p->lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to initialize packfile mutex"); - git__free(p); - return -1; - } - - if (git_mutex_init(&p->mwf.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to initialize packfile window mutex"); - git_mutex_free(&p->lock); - git__free(p); - return -1; - } - - if (cache_init(&p->bases) < 0) { - git_mutex_free(&p->mwf.lock); - git_mutex_free(&p->lock); - git__free(p); - return -1; - } - - *pack_out = p; - - return 0; -} - -/*********************************************************** - * - * PACKFILE ENTRY SEARCH INTERNALS - * - ***********************************************************/ - -static off64_t nth_packed_object_offset_locked(struct git_pack_file *p, uint32_t n) -{ - const unsigned char *index, *end; - uint32_t off32; - - index = p->index_map.data; - end = index + p->index_map.len; - index += 4 * 256; - if (p->index_version == 1) - return ntohl(*((uint32_t *)(index + 24 * n))); - - index += 8 + p->num_objects * (20 + 4); - off32 = ntohl(*((uint32_t *)(index + 4 * n))); - if (!(off32 & 0x80000000)) - return off32; - index += p->num_objects * 4 + (off32 & 0x7fffffff) * 8; - - /* Make sure we're not being sent out of bounds */ - if (index >= end - 8) - return -1; - - return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) | - ntohl(*((uint32_t *)(index + 4))); -} - -static int git__memcmp4(const void *a, const void *b) { - return memcmp(a, b, 4); -} - -int git_pack_foreach_entry( - struct git_pack_file *p, - git_odb_foreach_cb cb, - void *data) -{ - const unsigned char *index, *current; - uint32_t i; - int error = 0; - git_array_oid_t oids = GIT_ARRAY_INIT; - git_oid *oid; - - if (git_mutex_lock(&p->lock) < 0) - return packfile_error("failed to get lock for git_pack_foreach_entry"); - - if ((error = pack_index_open_locked(p)) < 0) { - git_mutex_unlock(&p->lock); - return error; - } - - if (!p->index_map.data) { - git_error_set(GIT_ERROR_INTERNAL, "internal error: p->index_map.data == NULL"); - git_mutex_unlock(&p->lock); - return -1; - } - - index = p->index_map.data; - - if (p->index_version > 1) - index += 8; - - index += 4 * 256; - - if (p->oids == NULL) { - git_vector offsets, oids; - - if ((error = git_vector_init(&oids, p->num_objects, NULL))) { - git_mutex_unlock(&p->lock); - return error; - } - - if ((error = git_vector_init(&offsets, p->num_objects, git__memcmp4))) { - git_mutex_unlock(&p->lock); - return error; - } - - if (p->index_version > 1) { - const unsigned char *off = index + 24 * p->num_objects; - for (i = 0; i < p->num_objects; i++) - git_vector_insert(&offsets, (void*)&off[4 * i]); - git_vector_sort(&offsets); - git_vector_foreach(&offsets, i, current) - git_vector_insert(&oids, (void*)&index[5 * (current - off)]); - } else { - for (i = 0; i < p->num_objects; i++) - git_vector_insert(&offsets, (void*)&index[24 * i]); - git_vector_sort(&offsets); - git_vector_foreach(&offsets, i, current) - git_vector_insert(&oids, (void*)¤t[4]); - } - - git_vector_free(&offsets); - p->oids = (git_oid **)git_vector_detach(NULL, NULL, &oids); - } - - /* We need to copy the OIDs to another array before we relinquish the lock to avoid races. */ - git_array_init_to_size(oids, p->num_objects); - if (!oids.ptr) { - git_mutex_unlock(&p->lock); - git_array_clear(oids); - GIT_ERROR_CHECK_ARRAY(oids); - } - for (i = 0; i < p->num_objects; i++) { - oid = git_array_alloc(oids); - if (!oid) { - git_mutex_unlock(&p->lock); - git_array_clear(oids); - GIT_ERROR_CHECK_ALLOC(oid); - } - git_oid_cpy(oid, p->oids[i]); - } - - git_mutex_unlock(&p->lock); - - git_array_foreach(oids, i, oid) { - if ((error = cb(oid, data)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - git_array_clear(oids); - return error; -} - -int git_pack_foreach_entry_offset( - struct git_pack_file *p, - git_pack_foreach_entry_offset_cb cb, - void *data) -{ - const unsigned char *index; - off64_t current_offset; - const git_oid *current_oid; - uint32_t i; - int error = 0; - - if (git_mutex_lock(&p->lock) < 0) - return packfile_error("failed to get lock for git_pack_foreach_entry_offset"); - - index = p->index_map.data; - if (index == NULL) { - if ((error = pack_index_open_locked(p)) < 0) - goto cleanup; - - if (!p->index_map.data) { - git_error_set(GIT_ERROR_INTERNAL, "internal error: p->index_map.data == NULL"); - goto cleanup; - } - - index = p->index_map.data; - } - - if (p->index_version > 1) - index += 8; - - index += 4 * 256; - - /* all offsets should have been validated by pack_index_check_locked */ - if (p->index_version > 1) { - const unsigned char *offsets = index + 24 * p->num_objects; - const unsigned char *large_offset_ptr; - const unsigned char *large_offsets = index + 28 * p->num_objects; - const unsigned char *large_offsets_end = ((const unsigned char *)p->index_map.data) + p->index_map.len - 20; - for (i = 0; i < p->num_objects; i++) { - current_offset = ntohl(*(const uint32_t *)(offsets + 4 * i)); - if (current_offset & 0x80000000) { - large_offset_ptr = large_offsets + (current_offset & 0x7fffffff) * 8; - if (large_offset_ptr >= large_offsets_end) { - error = packfile_error("invalid large offset"); - goto cleanup; - } - current_offset = (((off64_t)ntohl(*((uint32_t *)(large_offset_ptr + 0)))) << 32) | - ntohl(*((uint32_t *)(large_offset_ptr + 4))); - } - current_oid = (const git_oid *)(index + 20 * i); - if ((error = cb(current_oid, current_offset, data)) != 0) { - error = git_error_set_after_callback(error); - goto cleanup; - } - } - } else { - for (i = 0; i < p->num_objects; i++) { - current_offset = ntohl(*(const uint32_t *)(index + 24 * i)); - current_oid = (const git_oid *)(index + 24 * i + 4); - if ((error = cb(current_oid, current_offset, data)) != 0) { - error = git_error_set_after_callback(error); - goto cleanup; - } - } - } - -cleanup: - git_mutex_unlock(&p->lock); - return error; -} - -int git_pack__lookup_sha1(const void *oid_lookup_table, size_t stride, unsigned lo, - unsigned hi, const unsigned char *oid_prefix) -{ - const unsigned char *base = oid_lookup_table; - - while (lo < hi) { - unsigned mi = (lo + hi) / 2; - int cmp = git_oid__hashcmp(base + mi * stride, oid_prefix); - - if (!cmp) - return mi; - - if (cmp > 0) - hi = mi; - else - lo = mi+1; - } - - return -((int)lo)-1; -} - -static int pack_entry_find_offset( - off64_t *offset_out, - git_oid *found_oid, - struct git_pack_file *p, - const git_oid *short_oid, - size_t len) -{ - const uint32_t *level1_ofs; - const unsigned char *index; - unsigned hi, lo, stride; - int pos, found = 0; - off64_t offset; - const unsigned char *current = 0; - int error = 0; - - *offset_out = 0; - - if (git_mutex_lock(&p->lock) < 0) - return packfile_error("failed to get lock for pack_entry_find_offset"); - - if ((error = pack_index_open_locked(p)) < 0) - goto cleanup; - - if (!p->index_map.data) { - git_error_set(GIT_ERROR_INTERNAL, "internal error: p->index_map.data == NULL"); - goto cleanup; - } - - index = p->index_map.data; - level1_ofs = p->index_map.data; - - if (p->index_version > 1) { - level1_ofs += 2; - index += 8; - } - - index += 4 * 256; - hi = ntohl(level1_ofs[(int)short_oid->id[0]]); - lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(level1_ofs[(int)short_oid->id[0] - 1])); - - if (p->index_version > 1) { - stride = 20; - } else { - stride = 24; - index += 4; - } - -#ifdef INDEX_DEBUG_LOOKUP - printf("%02x%02x%02x... lo %u hi %u nr %d\n", - short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects); -#endif - - pos = git_pack__lookup_sha1(index, stride, lo, hi, short_oid->id); - - if (pos >= 0) { - /* An object matching exactly the oid was found */ - found = 1; - current = index + pos * stride; - } else { - /* No object was found */ - /* pos refers to the object with the "closest" oid to short_oid */ - pos = - 1 - pos; - if (pos < (int)p->num_objects) { - current = index + pos * stride; - - if (!git_oid_ncmp(short_oid, (const git_oid *)current, len)) - found = 1; - } - } - - if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)p->num_objects) { - /* Check for ambiguousity */ - const unsigned char *next = current + stride; - - if (!git_oid_ncmp(short_oid, (const git_oid *)next, len)) { - found = 2; - } - } - - if (!found) { - error = git_odb__error_notfound("failed to find offset for pack entry", short_oid, len); - goto cleanup; - } - if (found > 1) { - error = git_odb__error_ambiguous("found multiple offsets for pack entry"); - goto cleanup; - } - - if ((offset = nth_packed_object_offset_locked(p, pos)) < 0) { - git_error_set(GIT_ERROR_ODB, "packfile index is corrupt"); - error = -1; - goto cleanup; - } - - *offset_out = offset; - git_oid_fromraw(found_oid, current); - -#ifdef INDEX_DEBUG_LOOKUP - { - unsigned char hex_sha1[GIT_OID_HEXSZ + 1]; - git_oid_fmt(hex_sha1, found_oid); - hex_sha1[GIT_OID_HEXSZ] = '\0'; - printf("found lo=%d %s\n", lo, hex_sha1); - } -#endif - -cleanup: - git_mutex_unlock(&p->lock); - return error; -} - -int git_pack_entry_find( - struct git_pack_entry *e, - struct git_pack_file *p, - const git_oid *short_oid, - size_t len) -{ - off64_t offset; - git_oid found_oid; - int error; - - GIT_ASSERT_ARG(p); - - if (len == GIT_OID_HEXSZ && p->num_bad_objects) { - unsigned i; - for (i = 0; i < p->num_bad_objects; i++) - if (git_oid__cmp(short_oid, &p->bad_object_sha1[i]) == 0) - return packfile_error("bad object found in packfile"); - } - - error = pack_entry_find_offset(&offset, &found_oid, p, short_oid, len); - if (error < 0) - return error; - - error = git_mutex_lock(&p->lock); - if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); - return error; - } - error = git_mutex_lock(&p->mwf.lock); - if (error < 0) { - git_mutex_unlock(&p->lock); - git_error_set(GIT_ERROR_OS, "failed to lock packfile reader"); - return error; - } - - /* we found a unique entry in the index; - * make sure the packfile backing the index - * still exists on disk */ - if (p->mwf.fd == -1) - error = packfile_open_locked(p); - git_mutex_unlock(&p->mwf.lock); - git_mutex_unlock(&p->lock); - if (error < 0) - return error; - - e->offset = offset; - e->p = p; - - git_oid_cpy(&e->sha1, &found_oid); - return 0; -} diff --git a/src/pack.h b/src/pack.h deleted file mode 100644 index bf279c6b6..000000000 --- a/src/pack.h +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_pack_h__ -#define INCLUDE_pack_h__ - -#include "common.h" - -#include "git2/oid.h" - -#include "array.h" -#include "map.h" -#include "mwindow.h" -#include "odb.h" -#include "offmap.h" -#include "oidmap.h" -#include "zstream.h" - -/** - * Function type for callbacks from git_pack_foreach_entry_offset. - */ -typedef int git_pack_foreach_entry_offset_cb( - const git_oid *id, - off64_t offset, - void *payload); - -#define GIT_PACK_FILE_MODE 0444 - -#define PACK_SIGNATURE 0x5041434b /* "PACK" */ -#define PACK_VERSION 2 -#define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3)) -struct git_pack_header { - uint32_t hdr_signature; - uint32_t hdr_version; - uint32_t hdr_entries; -}; - -/* - * The first four bytes of index formats later than version 1 should - * start with this signature, as all older git binaries would find this - * value illegal and abort reading the file. - * - * This is the case because the number of objects in a packfile - * cannot exceed 1,431,660,000 as every object would need at least - * 3 bytes of data and the overall packfile cannot exceed 4 GiB with - * version 1 of the index file due to the offsets limited to 32 bits. - * Clearly the signature exceeds this maximum. - * - * Very old git binaries will also compare the first 4 bytes to the - * next 4 bytes in the index and abort with a "non-monotonic index" - * error if the second 4 byte word is smaller than the first 4 - * byte word. This would be true in the proposed future index - * format as idx_signature would be greater than idx_version. - */ - -#define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */ - -struct git_pack_idx_header { - uint32_t idx_signature; - uint32_t idx_version; -}; - -typedef struct git_pack_cache_entry { - size_t last_usage; /* enough? */ - git_atomic32 refcount; - git_rawobj raw; -} git_pack_cache_entry; - -struct pack_chain_elem { - off64_t base_key; - off64_t offset; - size_t size; - git_object_t type; -}; - -typedef git_array_t(struct pack_chain_elem) git_dependency_chain; - -#define GIT_PACK_CACHE_MEMORY_LIMIT 16 * 1024 * 1024 -#define GIT_PACK_CACHE_SIZE_LIMIT 1024 * 1024 /* don't bother caching anything over 1MB */ - -typedef struct { - size_t memory_used; - size_t memory_limit; - size_t use_ctr; - git_mutex lock; - git_offmap *entries; -} git_pack_cache; - -struct git_pack_file { - git_mwindow_file mwf; - git_map index_map; - git_mutex lock; /* protect updates to index_map */ - git_atomic32 refcount; - - uint32_t num_objects; - uint32_t num_bad_objects; - git_oid *bad_object_sha1; /* array of git_oid */ - - int index_version; - git_time_t mtime; - unsigned pack_local:1, pack_keep:1, has_cache:1; - git_oidmap *idx_cache; - git_oid **oids; - - git_pack_cache bases; /* delta base cache */ - - time_t last_freshen; /* last time the packfile was freshened */ - - /* something like ".git/objects/pack/xxxxx.pack" */ - char pack_name[GIT_FLEX_ARRAY]; /* more */ -}; - -/** - * Return the position where an OID (or a prefix) would be inserted within the - * OID Lookup Table of an .idx file. This performs binary search between the lo - * and hi indices. - * - * The stride parameter is provided because .idx files version 1 store the OIDs - * interleaved with the 4-byte file offsets of the objects within the .pack - * file (stride = 24), whereas files with version 2 store them in a contiguous - * flat array (stride = 20). - */ -int git_pack__lookup_sha1(const void *oid_lookup_table, size_t stride, unsigned lo, - unsigned hi, const unsigned char *oid_prefix); - -struct git_pack_entry { - off64_t offset; - git_oid sha1; - struct git_pack_file *p; -}; - -typedef struct git_packfile_stream { - off64_t curpos; - int done; - git_zstream zstream; - struct git_pack_file *p; - git_mwindow *mw; -} git_packfile_stream; - -int git_packfile__object_header(size_t *out, unsigned char *hdr, size_t size, git_object_t type); - -int git_packfile__name(char **out, const char *path); - -int git_packfile_unpack_header( - size_t *size_p, - git_object_t *type_p, - struct git_pack_file *p, - git_mwindow **w_curs, - off64_t *curpos); - -int git_packfile_resolve_header( - size_t *size_p, - git_object_t *type_p, - struct git_pack_file *p, - off64_t offset); - -int git_packfile_unpack(git_rawobj *obj, struct git_pack_file *p, off64_t *obj_offset); - -int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, off64_t curpos); -ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len); -void git_packfile_stream_dispose(git_packfile_stream *obj); - -int get_delta_base( - off64_t *delta_base_out, - struct git_pack_file *p, - git_mwindow **w_curs, - off64_t *curpos, - git_object_t type, - off64_t delta_obj_offset); - -void git_packfile_free(struct git_pack_file *p, bool unlink_packfile); -int git_packfile_alloc(struct git_pack_file **pack_out, const char *path); - -int git_pack_entry_find( - struct git_pack_entry *e, - struct git_pack_file *p, - const git_oid *short_oid, - size_t len); -int git_pack_foreach_entry( - struct git_pack_file *p, - git_odb_foreach_cb cb, - void *data); -/** - * Similar to git_pack_foreach_entry, but: - * - It also provides the offset of the object within the - * packfile. - * - It does not sort the objects in any order. - * - It retains the lock while invoking the callback. - */ -int git_pack_foreach_entry_offset( - struct git_pack_file *p, - git_pack_foreach_entry_offset_cb cb, - void *data); - -#endif diff --git a/src/parse.c b/src/parse.c deleted file mode 100644 index 0a10758bf..000000000 --- a/src/parse.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#include "parse.h" - -int git_parse_ctx_init(git_parse_ctx *ctx, const char *content, size_t content_len) -{ - if (content && content_len) { - ctx->content = content; - ctx->content_len = content_len; - } else { - ctx->content = ""; - ctx->content_len = 0; - } - - ctx->remain = ctx->content; - ctx->remain_len = ctx->content_len; - ctx->line = ctx->remain; - ctx->line_len = git__linenlen(ctx->line, ctx->remain_len); - ctx->line_num = 1; - - return 0; -} - -void git_parse_ctx_clear(git_parse_ctx *ctx) -{ - memset(ctx, 0, sizeof(*ctx)); - ctx->content = ""; -} - -void git_parse_advance_line(git_parse_ctx *ctx) -{ - ctx->line += ctx->line_len; - ctx->remain_len -= ctx->line_len; - ctx->line_len = git__linenlen(ctx->line, ctx->remain_len); - ctx->line_num++; -} - -void git_parse_advance_chars(git_parse_ctx *ctx, size_t char_cnt) -{ - ctx->line += char_cnt; - ctx->remain_len -= char_cnt; - ctx->line_len -= char_cnt; -} - -int git_parse_advance_expected( - git_parse_ctx *ctx, - const char *expected, - size_t expected_len) -{ - if (ctx->line_len < expected_len) - return -1; - - if (memcmp(ctx->line, expected, expected_len) != 0) - return -1; - - git_parse_advance_chars(ctx, expected_len); - return 0; -} - -int git_parse_advance_ws(git_parse_ctx *ctx) -{ - int ret = -1; - - while (ctx->line_len > 0 && - ctx->line[0] != '\n' && - git__isspace(ctx->line[0])) { - ctx->line++; - ctx->line_len--; - ctx->remain_len--; - ret = 0; - } - - return ret; -} - -int git_parse_advance_nl(git_parse_ctx *ctx) -{ - if (ctx->line_len != 1 || ctx->line[0] != '\n') - return -1; - - git_parse_advance_line(ctx); - return 0; -} - -int git_parse_advance_digit(int64_t *out, git_parse_ctx *ctx, int base) -{ - const char *end; - int ret; - - if (ctx->line_len < 1 || !git__isdigit(ctx->line[0])) - return -1; - - if ((ret = git__strntol64(out, ctx->line, ctx->line_len, &end, base)) < 0) - return -1; - - git_parse_advance_chars(ctx, (end - ctx->line)); - return 0; -} - -int git_parse_advance_oid(git_oid *out, git_parse_ctx *ctx) -{ - if (ctx->line_len < GIT_OID_HEXSZ) - return -1; - if ((git_oid_fromstrn(out, ctx->line, GIT_OID_HEXSZ)) < 0) - return -1; - git_parse_advance_chars(ctx, GIT_OID_HEXSZ); - return 0; -} - -int git_parse_peek(char *out, git_parse_ctx *ctx, int flags) -{ - size_t remain = ctx->line_len; - const char *ptr = ctx->line; - - while (remain) { - char c = *ptr; - - if ((flags & GIT_PARSE_PEEK_SKIP_WHITESPACE) && - git__isspace(c)) { - remain--; - ptr++; - continue; - } - - *out = c; - return 0; - } - - return -1; -} diff --git a/src/parse.h b/src/parse.h deleted file mode 100644 index 0ecb7c103..000000000 --- a/src/parse.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_parse_h__ -#define INCLUDE_parse_h__ - -#include "common.h" - -typedef struct { - /* Original content buffer */ - const char *content; - size_t content_len; - - /* The remaining (unparsed) buffer */ - const char *remain; - size_t remain_len; - - const char *line; - size_t line_len; - size_t line_num; -} git_parse_ctx; - -#define GIT_PARSE_CTX_INIT { 0 } - -int git_parse_ctx_init(git_parse_ctx *ctx, const char *content, size_t content_len); -void git_parse_ctx_clear(git_parse_ctx *ctx); - -#define git_parse_ctx_contains_s(ctx, str) \ - git_parse_ctx_contains(ctx, str, sizeof(str) - 1) - -GIT_INLINE(bool) git_parse_ctx_contains( - git_parse_ctx *ctx, const char *str, size_t len) -{ - return (ctx->line_len >= len && memcmp(ctx->line, str, len) == 0); -} - -void git_parse_advance_line(git_parse_ctx *ctx); -void git_parse_advance_chars(git_parse_ctx *ctx, size_t char_cnt); -int git_parse_advance_expected( - git_parse_ctx *ctx, - const char *expected, - size_t expected_len); - -#define git_parse_advance_expected_str(ctx, str) \ - git_parse_advance_expected(ctx, str, strlen(str)) - -int git_parse_advance_ws(git_parse_ctx *ctx); -int git_parse_advance_nl(git_parse_ctx *ctx); -int git_parse_advance_digit(int64_t *out, git_parse_ctx *ctx, int base); -int git_parse_advance_oid(git_oid *out, git_parse_ctx *ctx); - -enum GIT_PARSE_PEEK_FLAGS { - GIT_PARSE_PEEK_SKIP_WHITESPACE = (1 << 0) -}; - -int git_parse_peek(char *out, git_parse_ctx *ctx, int flags); - -#endif diff --git a/src/patch.c b/src/patch.c deleted file mode 100644 index a30546f3c..000000000 --- a/src/patch.c +++ /dev/null @@ -1,230 +0,0 @@ -/* -* Copyright (C) the libgit2 contributors. All rights reserved. -* -* This file is part of libgit2, distributed under the GNU GPL v2 with -* a Linking Exception. For full terms see the included COPYING file. -*/ - -#include "patch.h" - -#include "git2/patch.h" -#include "diff.h" - -int git_patch__invoke_callbacks( - git_patch *patch, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb line_cb, - void *payload) -{ - int error = 0; - uint32_t i, j; - - if (file_cb) - error = file_cb(patch->delta, 0, payload); - - if (error) - return error; - - if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { - if (binary_cb) - error = binary_cb(patch->delta, &patch->binary, payload); - - return error; - } - - if (!hunk_cb && !line_cb) - return error; - - for (i = 0; !error && i < git_array_size(patch->hunks); ++i) { - git_patch_hunk *h = git_array_get(patch->hunks, i); - - if (hunk_cb) - error = hunk_cb(patch->delta, &h->hunk, payload); - - if (!line_cb) - continue; - - for (j = 0; !error && j < h->line_count; ++j) { - git_diff_line *l = - git_array_get(patch->lines, h->line_start + j); - - error = line_cb(patch->delta, &h->hunk, l, payload); - } - } - - return error; -} - -size_t git_patch_size( - git_patch *patch, - int include_context, - int include_hunk_headers, - int include_file_headers) -{ - size_t out; - - GIT_ASSERT_ARG(patch); - - out = patch->content_size; - - if (!include_context) - out -= patch->context_size; - - if (include_hunk_headers) - out += patch->header_size; - - if (include_file_headers) { - git_str file_header = GIT_STR_INIT; - - if (git_diff_delta__format_file_header( - &file_header, patch->delta, NULL, NULL, 0, true) < 0) - git_error_clear(); - else - out += git_str_len(&file_header); - - git_str_dispose(&file_header); - } - - return out; -} - -int git_patch_line_stats( - size_t *total_ctxt, - size_t *total_adds, - size_t *total_dels, - const git_patch *patch) -{ - size_t totals[3], idx; - - memset(totals, 0, sizeof(totals)); - - for (idx = 0; idx < git_array_size(patch->lines); ++idx) { - git_diff_line *line = git_array_get(patch->lines, idx); - if (!line) - continue; - - switch (line->origin) { - case GIT_DIFF_LINE_CONTEXT: totals[0]++; break; - case GIT_DIFF_LINE_ADDITION: totals[1]++; break; - case GIT_DIFF_LINE_DELETION: totals[2]++; break; - default: - /* diff --stat and --numstat don't count EOFNL marks because - * they will always be paired with a ADDITION or DELETION line. - */ - break; - } - } - - if (total_ctxt) - *total_ctxt = totals[0]; - if (total_adds) - *total_adds = totals[1]; - if (total_dels) - *total_dels = totals[2]; - - return 0; -} - -const git_diff_delta *git_patch_get_delta(const git_patch *patch) -{ - GIT_ASSERT_ARG_WITH_RETVAL(patch, NULL); - return patch->delta; -} - -size_t git_patch_num_hunks(const git_patch *patch) -{ - GIT_ASSERT_ARG(patch); - return git_array_size(patch->hunks); -} - -static int patch_error_outofrange(const char *thing) -{ - git_error_set(GIT_ERROR_INVALID, "patch %s index out of range", thing); - return GIT_ENOTFOUND; -} - -int git_patch_get_hunk( - const git_diff_hunk **out, - size_t *lines_in_hunk, - git_patch *patch, - size_t hunk_idx) -{ - git_patch_hunk *hunk; - GIT_ASSERT_ARG(patch); - - hunk = git_array_get(patch->hunks, hunk_idx); - - if (!hunk) { - if (out) *out = NULL; - if (lines_in_hunk) *lines_in_hunk = 0; - return patch_error_outofrange("hunk"); - } - - if (out) *out = &hunk->hunk; - if (lines_in_hunk) *lines_in_hunk = hunk->line_count; - return 0; -} - -int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx) -{ - git_patch_hunk *hunk; - GIT_ASSERT_ARG(patch); - - if (!(hunk = git_array_get(patch->hunks, hunk_idx))) - return patch_error_outofrange("hunk"); - return (int)hunk->line_count; -} - -int git_patch_get_line_in_hunk( - const git_diff_line **out, - git_patch *patch, - size_t hunk_idx, - size_t line_of_hunk) -{ - git_patch_hunk *hunk; - git_diff_line *line; - - GIT_ASSERT_ARG(patch); - - if (!(hunk = git_array_get(patch->hunks, hunk_idx))) { - if (out) *out = NULL; - return patch_error_outofrange("hunk"); - } - - if (line_of_hunk >= hunk->line_count || - !(line = git_array_get( - patch->lines, hunk->line_start + line_of_hunk))) { - if (out) *out = NULL; - return patch_error_outofrange("line"); - } - - if (out) *out = line; - return 0; -} - -git_repository *git_patch_owner(const git_patch *patch) -{ - return patch->repo; -} - -int git_patch_from_diff(git_patch **out, git_diff *diff, size_t idx) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diff); - GIT_ASSERT_ARG(diff->patch_fn); - return diff->patch_fn(out, diff, idx); -} - -static void git_patch__free(git_patch *patch) -{ - if (patch->free_fn) - patch->free_fn(patch); -} - -void git_patch_free(git_patch *patch) -{ - if (patch) - GIT_REFCOUNT_DEC(patch, git_patch__free); -} diff --git a/src/patch.h b/src/patch.h deleted file mode 100644 index 1e1471ed6..000000000 --- a/src/patch.h +++ /dev/null @@ -1,69 +0,0 @@ -/* -* Copyright (C) the libgit2 contributors. All rights reserved. -* -* This file is part of libgit2, distributed under the GNU GPL v2 with -* a Linking Exception. For full terms see the included COPYING file. -*/ -#ifndef INCLUDE_patch_h__ -#define INCLUDE_patch_h__ - -#include "common.h" - -#include "git2/patch.h" -#include "array.h" - -/* cached information about a hunk in a patch */ -typedef struct git_patch_hunk { - git_diff_hunk hunk; - size_t line_start; - size_t line_count; -} git_patch_hunk; - -struct git_patch { - git_refcount rc; - - git_repository *repo; /* may be null */ - - git_diff_options diff_opts; - - git_diff_delta *delta; - git_diff_binary binary; - git_array_t(git_patch_hunk) hunks; - git_array_t(git_diff_line) lines; - - size_t header_size; - size_t content_size; - size_t context_size; - - void (*free_fn)(git_patch *patch); -}; - -extern int git_patch__invoke_callbacks( - git_patch *patch, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb line_cb, - void *payload); - -extern int git_patch_line_stats( - size_t *total_ctxt, - size_t *total_adds, - size_t *total_dels, - const git_patch *patch); - -/** Options for parsing patch files. */ -typedef struct { - /** - * The length of the prefix (in path segments) for the filenames. - * This prefix will be removed when looking for files. The default is 1. - */ - uint32_t prefix_len; -} git_patch_options; - -#define GIT_PATCH_OPTIONS_INIT { 1 } - -extern int git_patch__to_buf(git_str *out, git_patch *patch); -extern void git_patch_free(git_patch *patch); - -#endif diff --git a/src/patch_generate.c b/src/patch_generate.c deleted file mode 100644 index bc598fea8..000000000 --- a/src/patch_generate.c +++ /dev/null @@ -1,915 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "patch_generate.h" - -#include "git2/blob.h" -#include "diff.h" -#include "diff_generate.h" -#include "diff_file.h" -#include "diff_driver.h" -#include "diff_xdiff.h" -#include "delta.h" -#include "zstream.h" -#include "futils.h" - -static void diff_output_init( - git_patch_generated_output *, const git_diff_options *, git_diff_file_cb, - git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*); - -static void diff_output_to_patch( - git_patch_generated_output *, git_patch_generated *); - -static void patch_generated_free(git_patch *p) -{ - git_patch_generated *patch = (git_patch_generated *)p; - - git_array_clear(patch->base.lines); - git_array_clear(patch->base.hunks); - - git__free((char *)patch->base.binary.old_file.data); - git__free((char *)patch->base.binary.new_file.data); - - git_diff_file_content__clear(&patch->ofile); - git_diff_file_content__clear(&patch->nfile); - - git_diff_free(patch->diff); /* decrements refcount */ - patch->diff = NULL; - - git_pool_clear(&patch->flattened); - - git__free((char *)patch->base.diff_opts.old_prefix); - git__free((char *)patch->base.diff_opts.new_prefix); - - if (patch->flags & GIT_PATCH_GENERATED_ALLOCATED) - git__free(patch); -} - -static void patch_generated_update_binary(git_patch_generated *patch) -{ - if ((patch->base.delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) - return; - - if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 || - (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) - patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; - - else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE || - patch->nfile.file->size > GIT_XDIFF_MAX_SIZE) - patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; - - else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 && - (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0) - patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; -} - -static void patch_generated_init_common(git_patch_generated *patch) -{ - patch->base.free_fn = patch_generated_free; - - patch_generated_update_binary(patch); - - patch->flags |= GIT_PATCH_GENERATED_INITIALIZED; - - if (patch->diff) - git_diff_addref(patch->diff); -} - -static int patch_generated_normalize_options( - git_diff_options *out, - const git_diff_options *opts) -{ - if (opts) { - GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); - memcpy(out, opts, sizeof(git_diff_options)); - } else { - git_diff_options default_opts = GIT_DIFF_OPTIONS_INIT; - memcpy(out, &default_opts, sizeof(git_diff_options)); - } - - out->old_prefix = opts && opts->old_prefix ? - git__strdup(opts->old_prefix) : - git__strdup(DIFF_OLD_PREFIX_DEFAULT); - - out->new_prefix = opts && opts->new_prefix ? - git__strdup(opts->new_prefix) : - git__strdup(DIFF_NEW_PREFIX_DEFAULT); - - GIT_ERROR_CHECK_ALLOC(out->old_prefix); - GIT_ERROR_CHECK_ALLOC(out->new_prefix); - - return 0; -} - -static int patch_generated_init( - git_patch_generated *patch, git_diff *diff, size_t delta_index) -{ - int error = 0; - - memset(patch, 0, sizeof(*patch)); - - patch->diff = diff; - patch->base.repo = diff->repo; - patch->base.delta = git_vector_get(&diff->deltas, delta_index); - patch->delta_index = delta_index; - - if ((error = patch_generated_normalize_options( - &patch->base.diff_opts, &diff->opts)) < 0 || - (error = git_diff_file_content__init_from_diff( - &patch->ofile, diff, patch->base.delta, true)) < 0 || - (error = git_diff_file_content__init_from_diff( - &patch->nfile, diff, patch->base.delta, false)) < 0) - return error; - - patch_generated_init_common(patch); - - return 0; -} - -static int patch_generated_alloc_from_diff( - git_patch_generated **out, git_diff *diff, size_t delta_index) -{ - int error; - git_patch_generated *patch = git__calloc(1, sizeof(git_patch_generated)); - GIT_ERROR_CHECK_ALLOC(patch); - - if (!(error = patch_generated_init(patch, diff, delta_index))) { - patch->flags |= GIT_PATCH_GENERATED_ALLOCATED; - GIT_REFCOUNT_INC(&patch->base); - } else { - git__free(patch); - patch = NULL; - } - - *out = patch; - return error; -} - -GIT_INLINE(bool) should_skip_binary(git_patch_generated *patch, git_diff_file *file) -{ - if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0) - return false; - - return (file->flags & GIT_DIFF_FLAG_BINARY) != 0; -} - -static bool patch_generated_diffable(git_patch_generated *patch) -{ - size_t olen, nlen; - - if (patch->base.delta->status == GIT_DELTA_UNMODIFIED) - return false; - - /* if we've determined this to be binary (and we are not showing binary - * data) then we have skipped loading the map data. instead, query the - * file data itself. - */ - if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 && - (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) { - olen = (size_t)patch->ofile.file->size; - nlen = (size_t)patch->nfile.file->size; - } else { - olen = patch->ofile.map.len; - nlen = patch->nfile.map.len; - } - - /* if both sides are empty, files are identical */ - if (!olen && !nlen) - return false; - - /* otherwise, check the file sizes and the oid */ - return (olen != nlen || - !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id)); -} - -static int patch_generated_load(git_patch_generated *patch, git_patch_generated_output *output) -{ - int error = 0; - bool incomplete_data; - - if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0) - return 0; - - /* if no hunk and data callbacks and user doesn't care if data looks - * binary, then there is no need to actually load the data - */ - if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 && - output && !output->binary_cb && !output->hunk_cb && !output->data_cb) - return 0; - - incomplete_data = - (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || - (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) && - ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || - (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0)); - - if ((error = git_diff_file_content__load( - &patch->ofile, &patch->base.diff_opts)) < 0 || - (error = git_diff_file_content__load( - &patch->nfile, &patch->base.diff_opts)) < 0 || - should_skip_binary(patch, patch->nfile.file)) - goto cleanup; - - /* if previously missing an oid, and now that we have it the two sides - * are the same (and not submodules), update MODIFIED -> UNMODIFIED - */ - if (incomplete_data && - patch->ofile.file->mode == patch->nfile.file->mode && - patch->ofile.file->mode != GIT_FILEMODE_COMMIT && - git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) && - patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ - patch->base.delta->status = GIT_DELTA_UNMODIFIED; - -cleanup: - patch_generated_update_binary(patch); - - if (!error) { - if (patch_generated_diffable(patch)) - patch->flags |= GIT_PATCH_GENERATED_DIFFABLE; - - patch->flags |= GIT_PATCH_GENERATED_LOADED; - } - - return error; -} - -static int patch_generated_invoke_file_callback( - git_patch_generated *patch, git_patch_generated_output *output) -{ - float progress = patch->diff ? - ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f; - - if (!output->file_cb) - return 0; - - return git_error_set_after_callback_function( - output->file_cb(patch->base.delta, progress, output->payload), - "git_patch"); -} - -static int create_binary( - git_diff_binary_t *out_type, - char **out_data, - size_t *out_datalen, - size_t *out_inflatedlen, - const char *a_data, - size_t a_datalen, - const char *b_data, - size_t b_datalen) -{ - git_str deflate = GIT_STR_INIT, delta = GIT_STR_INIT; - size_t delta_data_len = 0; - int error; - - /* The git_delta function accepts unsigned long only */ - if (!git__is_ulong(a_datalen) || !git__is_ulong(b_datalen)) - return GIT_EBUFS; - - if ((error = git_zstream_deflatebuf(&deflate, b_data, b_datalen)) < 0) - goto done; - - /* The git_delta function accepts unsigned long only */ - if (!git__is_ulong(deflate.size)) { - error = GIT_EBUFS; - goto done; - } - - if (a_datalen && b_datalen) { - void *delta_data; - - error = git_delta(&delta_data, &delta_data_len, - a_data, a_datalen, - b_data, b_datalen, - deflate.size); - - if (error == 0) { - error = git_zstream_deflatebuf( - &delta, delta_data, delta_data_len); - - git__free(delta_data); - } else if (error == GIT_EBUFS) { - error = 0; - } - - if (error < 0) - goto done; - } - - if (delta.size && delta.size < deflate.size) { - *out_type = GIT_DIFF_BINARY_DELTA; - *out_datalen = delta.size; - *out_data = git_str_detach(&delta); - *out_inflatedlen = delta_data_len; - } else { - *out_type = GIT_DIFF_BINARY_LITERAL; - *out_datalen = deflate.size; - *out_data = git_str_detach(&deflate); - *out_inflatedlen = b_datalen; - } - -done: - git_str_dispose(&deflate); - git_str_dispose(&delta); - - return error; -} - -static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch) -{ - git_diff_binary binary = {0}; - const char *old_data = patch->ofile.map.data; - const char *new_data = patch->nfile.map.data; - size_t old_len = patch->ofile.map.len, - new_len = patch->nfile.map.len; - int error; - - /* Only load contents if the user actually wants to diff - * binary files. */ - if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) { - binary.contains_data = 1; - - /* Create the old->new delta (as the "new" side of the patch), - * and the new->old delta (as the "old" side) - */ - if ((error = create_binary(&binary.old_file.type, - (char **)&binary.old_file.data, - &binary.old_file.datalen, - &binary.old_file.inflatedlen, - new_data, new_len, old_data, old_len)) < 0 || - (error = create_binary(&binary.new_file.type, - (char **)&binary.new_file.data, - &binary.new_file.datalen, - &binary.new_file.inflatedlen, - old_data, old_len, new_data, new_len)) < 0) - return error; - } - - error = git_error_set_after_callback_function( - output->binary_cb(patch->base.delta, &binary, output->payload), - "git_patch"); - - git__free((char *) binary.old_file.data); - git__free((char *) binary.new_file.data); - - return error; -} - -static int patch_generated_create( - git_patch_generated *patch, - git_patch_generated_output *output) -{ - int error = 0; - - if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0) - return 0; - - /* if we are not looking at the binary or text data, don't do the diff */ - if (!output->binary_cb && !output->hunk_cb && !output->data_cb) - return 0; - - if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 && - (error = patch_generated_load(patch, output)) < 0) - return error; - - if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0) - return 0; - - if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { - if (output->binary_cb) - error = diff_binary(output, patch); - } - else { - if (output->diff_cb) - error = output->diff_cb(output, patch); - } - - patch->flags |= GIT_PATCH_GENERATED_DIFFED; - return error; -} - -static int diff_required(git_diff *diff, const char *action) -{ - if (diff) - return 0; - git_error_set(GIT_ERROR_INVALID, "must provide valid diff to %s", action); - return -1; -} - -typedef struct { - git_patch_generated patch; - git_diff_delta delta; - char paths[GIT_FLEX_ARRAY]; -} patch_generated_with_delta; - -static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo) -{ - int error = 0; - git_patch_generated *patch = &pd->patch; - bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); - bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); - - pd->delta.status = has_new ? - (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : - (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); - - if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id)) - pd->delta.status = GIT_DELTA_UNMODIFIED; - - patch->base.delta = &pd->delta; - - patch_generated_init_common(patch); - - if (pd->delta.status == GIT_DELTA_UNMODIFIED && - !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) { - - /* Even empty patches are flagged as binary, and even though - * there's no difference, we flag this as "containing data" - * (the data is known to be empty, as opposed to wholly unknown). - */ - if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) - patch->base.binary.contains_data = 1; - - return error; - } - - error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo); - - if (!error) - error = patch_generated_create(patch, (git_patch_generated_output *)xo); - - return error; -} - -static int patch_generated_from_sources( - patch_generated_with_delta *pd, - git_xdiff_output *xo, - git_diff_file_content_src *oldsrc, - git_diff_file_content_src *newsrc, - const git_diff_options *opts) -{ - int error = 0; - git_repository *repo = - oldsrc->blob ? git_blob_owner(oldsrc->blob) : - newsrc->blob ? git_blob_owner(newsrc->blob) : NULL; - git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file; - git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile; - - if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0) - return error; - - if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { - void *tmp = lfile; lfile = rfile; rfile = tmp; - tmp = ldata; ldata = rdata; rdata = tmp; - } - - pd->patch.base.delta = &pd->delta; - - if (!oldsrc->as_path) { - if (newsrc->as_path) - oldsrc->as_path = newsrc->as_path; - else - oldsrc->as_path = newsrc->as_path = "file"; - } - else if (!newsrc->as_path) - newsrc->as_path = oldsrc->as_path; - - lfile->path = oldsrc->as_path; - rfile->path = newsrc->as_path; - - if ((error = git_diff_file_content__init_from_src( - ldata, repo, opts, oldsrc, lfile)) < 0 || - (error = git_diff_file_content__init_from_src( - rdata, repo, opts, newsrc, rfile)) < 0) - return error; - - return diff_single_generate(pd, xo); -} - -static int patch_generated_with_delta_alloc( - patch_generated_with_delta **out, - const char **old_path, - const char **new_path) -{ - patch_generated_with_delta *pd; - size_t old_len = *old_path ? strlen(*old_path) : 0; - size_t new_len = *new_path ? strlen(*new_path) : 0; - size_t alloc_len; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); - - *out = pd = git__calloc(1, alloc_len); - GIT_ERROR_CHECK_ALLOC(pd); - - pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED; - - if (*old_path) { - memcpy(&pd->paths[0], *old_path, old_len); - *old_path = &pd->paths[0]; - } else if (*new_path) - *old_path = &pd->paths[old_len + 1]; - - if (*new_path) { - memcpy(&pd->paths[old_len + 1], *new_path, new_len); - *new_path = &pd->paths[old_len + 1]; - } else if (*old_path) - *new_path = &pd->paths[0]; - - return 0; -} - -static int diff_from_sources( - git_diff_file_content_src *oldsrc, - git_diff_file_content_src *newsrc, - const git_diff_options *opts, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb data_cb, - void *payload) -{ - int error = 0; - patch_generated_with_delta pd; - git_xdiff_output xo; - - memset(&xo, 0, sizeof(xo)); - diff_output_init( - &xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); - git_xdiff_init(&xo, opts); - - memset(&pd, 0, sizeof(pd)); - - error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts); - - git_patch_free(&pd.patch.base); - - return error; -} - -static int patch_from_sources( - git_patch **out, - git_diff_file_content_src *oldsrc, - git_diff_file_content_src *newsrc, - const git_diff_options *opts) -{ - int error = 0; - patch_generated_with_delta *pd; - git_xdiff_output xo; - - GIT_ASSERT_ARG(out); - *out = NULL; - - if ((error = patch_generated_with_delta_alloc( - &pd, &oldsrc->as_path, &newsrc->as_path)) < 0) - return error; - - memset(&xo, 0, sizeof(xo)); - diff_output_to_patch(&xo.output, &pd->patch); - git_xdiff_init(&xo, opts); - - if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts))) - *out = (git_patch *)pd; - else - git_patch_free((git_patch *)pd); - - return error; -} - -int git_diff_blobs( - const git_blob *old_blob, - const char *old_path, - const git_blob *new_blob, - const char *new_path, - const git_diff_options *opts, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb data_cb, - void *payload) -{ - git_diff_file_content_src osrc = - GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); - git_diff_file_content_src nsrc = - GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path); - return diff_from_sources( - &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); -} - -int git_patch_from_blobs( - git_patch **out, - const git_blob *old_blob, - const char *old_path, - const git_blob *new_blob, - const char *new_path, - const git_diff_options *opts) -{ - git_diff_file_content_src osrc = - GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); - git_diff_file_content_src nsrc = - GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path); - return patch_from_sources(out, &osrc, &nsrc, opts); -} - -int git_diff_blob_to_buffer( - const git_blob *old_blob, - const char *old_path, - const char *buf, - size_t buflen, - const char *buf_path, - const git_diff_options *opts, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb data_cb, - void *payload) -{ - git_diff_file_content_src osrc = - GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); - git_diff_file_content_src nsrc = - GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path); - return diff_from_sources( - &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); -} - -int git_patch_from_blob_and_buffer( - git_patch **out, - const git_blob *old_blob, - const char *old_path, - const void *buf, - size_t buflen, - const char *buf_path, - const git_diff_options *opts) -{ - git_diff_file_content_src osrc = - GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); - git_diff_file_content_src nsrc = - GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path); - return patch_from_sources(out, &osrc, &nsrc, opts); -} - -int git_diff_buffers( - const void *old_buf, - size_t old_len, - const char *old_path, - const void *new_buf, - size_t new_len, - const char *new_path, - const git_diff_options *opts, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb data_cb, - void *payload) -{ - git_diff_file_content_src osrc = - GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path); - git_diff_file_content_src nsrc = - GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path); - return diff_from_sources( - &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); -} - -int git_patch_from_buffers( - git_patch **out, - const void *old_buf, - size_t old_len, - const char *old_path, - const void *new_buf, - size_t new_len, - const char *new_path, - const git_diff_options *opts) -{ - git_diff_file_content_src osrc = - GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path); - git_diff_file_content_src nsrc = - GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path); - return patch_from_sources(out, &osrc, &nsrc, opts); -} - -int git_patch_generated_from_diff( - git_patch **patch_ptr, git_diff *diff, size_t idx) -{ - int error = 0; - git_xdiff_output xo; - git_diff_delta *delta = NULL; - git_patch_generated *patch = NULL; - - if (patch_ptr) *patch_ptr = NULL; - - if (diff_required(diff, "git_patch_from_diff") < 0) - return -1; - - delta = git_vector_get(&diff->deltas, idx); - if (!delta) { - git_error_set(GIT_ERROR_INVALID, "index out of range for delta in diff"); - return GIT_ENOTFOUND; - } - - if (git_diff_delta__should_skip(&diff->opts, delta)) - return 0; - - /* don't load the patch data unless we need it for binary check */ - if (!patch_ptr && - ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 || - (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)) - return 0; - - if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0) - return error; - - memset(&xo, 0, sizeof(xo)); - diff_output_to_patch(&xo.output, patch); - git_xdiff_init(&xo, &diff->opts); - - error = patch_generated_invoke_file_callback(patch, &xo.output); - - if (!error) - error = patch_generated_create(patch, &xo.output); - - if (!error) { - /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */ - /* TODO: and unload the file content */ - } - - if (error || !patch_ptr) - git_patch_free(&patch->base); - else - *patch_ptr = &patch->base; - - return error; -} - -git_diff_driver *git_patch_generated_driver(git_patch_generated *patch) -{ - /* ofile driver is representative for whole patch */ - return patch->ofile.driver; -} - -int git_patch_generated_old_data( - char **ptr, long *len, git_patch_generated *patch) -{ - if (patch->ofile.map.len > LONG_MAX || - patch->ofile.map.len > GIT_XDIFF_MAX_SIZE) { - git_error_set(GIT_ERROR_INVALID, "files too large for diff"); - return -1; - } - - *ptr = patch->ofile.map.data; - *len = (long)patch->ofile.map.len; - - return 0; -} - -int git_patch_generated_new_data( - char **ptr, long *len, git_patch_generated *patch) -{ - if (patch->ofile.map.len > LONG_MAX || - patch->ofile.map.len > GIT_XDIFF_MAX_SIZE) { - git_error_set(GIT_ERROR_INVALID, "files too large for diff"); - return -1; - } - - *ptr = patch->nfile.map.data; - *len = (long)patch->nfile.map.len; - - return 0; -} - -static int patch_generated_file_cb( - const git_diff_delta *delta, - float progress, - void *payload) -{ - GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload); - return 0; -} - -static int patch_generated_binary_cb( - const git_diff_delta *delta, - const git_diff_binary *binary, - void *payload) -{ - git_patch *patch = payload; - - GIT_UNUSED(delta); - - memcpy(&patch->binary, binary, sizeof(git_diff_binary)); - - if (binary->old_file.data) { - patch->binary.old_file.data = git__malloc(binary->old_file.datalen); - GIT_ERROR_CHECK_ALLOC(patch->binary.old_file.data); - - memcpy((char *)patch->binary.old_file.data, - binary->old_file.data, binary->old_file.datalen); - } - - if (binary->new_file.data) { - patch->binary.new_file.data = git__malloc(binary->new_file.datalen); - GIT_ERROR_CHECK_ALLOC(patch->binary.new_file.data); - - memcpy((char *)patch->binary.new_file.data, - binary->new_file.data, binary->new_file.datalen); - } - - return 0; -} - -static int git_patch_hunk_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk_, - void *payload) -{ - git_patch_generated *patch = payload; - git_patch_hunk *hunk; - - GIT_UNUSED(delta); - - hunk = git_array_alloc(patch->base.hunks); - GIT_ERROR_CHECK_ALLOC(hunk); - - memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk)); - - patch->base.header_size += hunk_->header_len; - - hunk->line_start = git_array_size(patch->base.lines); - hunk->line_count = 0; - - return 0; -} - -static int patch_generated_line_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk_, - const git_diff_line *line_, - void *payload) -{ - git_patch_generated *patch = payload; - git_patch_hunk *hunk; - git_diff_line *line; - - GIT_UNUSED(delta); - GIT_UNUSED(hunk_); - - hunk = git_array_last(patch->base.hunks); - GIT_ASSERT(hunk); /* programmer error if no hunk is available */ - - line = git_array_alloc(patch->base.lines); - GIT_ERROR_CHECK_ALLOC(line); - - memcpy(line, line_, sizeof(*line)); - - /* do some bookkeeping so we can provide old/new line numbers */ - - patch->base.content_size += line->content_len; - - if (line->origin == GIT_DIFF_LINE_ADDITION || - line->origin == GIT_DIFF_LINE_DELETION) - patch->base.content_size += 1; - else if (line->origin == GIT_DIFF_LINE_CONTEXT) { - patch->base.content_size += 1; - patch->base.context_size += line->content_len + 1; - } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) - patch->base.context_size += line->content_len; - - hunk->line_count++; - - return 0; -} - -static void diff_output_init( - git_patch_generated_output *out, - const git_diff_options *opts, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb data_cb, - void *payload) -{ - GIT_UNUSED(opts); - - memset(out, 0, sizeof(*out)); - - out->file_cb = file_cb; - out->binary_cb = binary_cb; - out->hunk_cb = hunk_cb; - out->data_cb = data_cb; - out->payload = payload; -} - -static void diff_output_to_patch( - git_patch_generated_output *out, git_patch_generated *patch) -{ - diff_output_init( - out, - NULL, - patch_generated_file_cb, - patch_generated_binary_cb, - git_patch_hunk_cb, - patch_generated_line_cb, - patch); -} diff --git a/src/patch_generate.h b/src/patch_generate.h deleted file mode 100644 index 56e3e9df4..000000000 --- a/src/patch_generate.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_patch_generate_h__ -#define INCLUDE_patch_generate_h__ - -#include "common.h" - -#include "diff.h" -#include "diff_file.h" -#include "patch.h" - -enum { - GIT_PATCH_GENERATED_ALLOCATED = (1 << 0), - GIT_PATCH_GENERATED_INITIALIZED = (1 << 1), - GIT_PATCH_GENERATED_LOADED = (1 << 2), - /* the two sides are different */ - GIT_PATCH_GENERATED_DIFFABLE = (1 << 3), - /* the difference between the two sides has been computed */ - GIT_PATCH_GENERATED_DIFFED = (1 << 4), - GIT_PATCH_GENERATED_FLATTENED = (1 << 5) -}; - -struct git_patch_generated { - struct git_patch base; - - git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */ - size_t delta_index; - git_diff_file_content ofile; - git_diff_file_content nfile; - uint32_t flags; - git_pool flattened; -}; - -typedef struct git_patch_generated git_patch_generated; - -extern git_diff_driver *git_patch_generated_driver(git_patch_generated *); - -extern int git_patch_generated_old_data( - char **, long *, git_patch_generated *); -extern int git_patch_generated_new_data( - char **, long *, git_patch_generated *); -extern int git_patch_generated_from_diff( - git_patch **, git_diff *, size_t); - -typedef struct git_patch_generated_output git_patch_generated_output; - -struct git_patch_generated_output { - /* these callbacks are issued with the diff data */ - git_diff_file_cb file_cb; - git_diff_binary_cb binary_cb; - git_diff_hunk_cb hunk_cb; - git_diff_line_cb data_cb; - void *payload; - - /* this records the actual error in cases where it may be obscured */ - int error; - - /* this callback is used to do the diff and drive the other callbacks. - * see diff_xdiff.h for how to use this in practice for now. - */ - int (*diff_cb)(git_patch_generated_output *output, - git_patch_generated *patch); -}; - -#endif diff --git a/src/patch_parse.c b/src/patch_parse.c deleted file mode 100644 index 78cd96252..000000000 --- a/src/patch_parse.c +++ /dev/null @@ -1,1231 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "patch_parse.h" - -#include "git2/patch.h" -#include "patch.h" -#include "diff_parse.h" -#include "fs_path.h" - -typedef struct { - git_patch base; - - git_patch_parse_ctx *ctx; - - /* the paths from the `diff --git` header, these will be used if this is not - * a rename (and rename paths are specified) or if no `+++`/`---` line specify - * the paths. - */ - char *header_old_path, *header_new_path; - - /* renamed paths are precise and are not prefixed */ - char *rename_old_path, *rename_new_path; - - /* the paths given in `---` and `+++` lines */ - char *old_path, *new_path; - - /* the prefixes from the old/new paths */ - char *old_prefix, *new_prefix; -} git_patch_parsed; - -static int git_parse_err(const char *fmt, ...) GIT_FORMAT_PRINTF(1, 2); -static int git_parse_err(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - git_error_vset(GIT_ERROR_PATCH, fmt, ap); - va_end(ap); - - return -1; -} - -static size_t header_path_len(git_patch_parse_ctx *ctx) -{ - bool inquote = 0; - bool quoted = git_parse_ctx_contains_s(&ctx->parse_ctx, "\""); - size_t len; - - for (len = quoted; len < ctx->parse_ctx.line_len; len++) { - if (!quoted && git__isspace(ctx->parse_ctx.line[len])) - break; - else if (quoted && !inquote && ctx->parse_ctx.line[len] == '"') { - len++; - break; - } - - inquote = (!inquote && ctx->parse_ctx.line[len] == '\\'); - } - - return len; -} - -static int parse_header_path_buf(git_str *path, git_patch_parse_ctx *ctx, size_t path_len) -{ - int error; - - if ((error = git_str_put(path, ctx->parse_ctx.line, path_len)) < 0) - return error; - - git_parse_advance_chars(&ctx->parse_ctx, path_len); - - git_str_rtrim(path); - - if (path->size > 0 && path->ptr[0] == '"' && - (error = git_str_unquote(path)) < 0) - return error; - - git_fs_path_squash_slashes(path); - - if (!path->size) - return git_parse_err("patch contains empty path at line %"PRIuZ, - ctx->parse_ctx.line_num); - - return 0; -} - -static int parse_header_path(char **out, git_patch_parse_ctx *ctx) -{ - git_str path = GIT_STR_INIT; - int error; - - if ((error = parse_header_path_buf(&path, ctx, header_path_len(ctx))) < 0) - goto out; - *out = git_str_detach(&path); - -out: - git_str_dispose(&path); - return error; -} - -static int parse_header_git_oldpath( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - git_str old_path = GIT_STR_INIT; - int error; - - if (patch->old_path) { - error = git_parse_err("patch contains duplicate old path at line %"PRIuZ, - ctx->parse_ctx.line_num); - goto out; - } - - if ((error = parse_header_path_buf(&old_path, ctx, ctx->parse_ctx.line_len - 1)) < 0) - goto out; - - patch->old_path = git_str_detach(&old_path); - -out: - git_str_dispose(&old_path); - return error; -} - -static int parse_header_git_newpath( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - git_str new_path = GIT_STR_INIT; - int error; - - if (patch->new_path) { - error = git_parse_err("patch contains duplicate new path at line %"PRIuZ, - ctx->parse_ctx.line_num); - goto out; - } - - if ((error = parse_header_path_buf(&new_path, ctx, ctx->parse_ctx.line_len - 1)) < 0) - goto out; - patch->new_path = git_str_detach(&new_path); - -out: - git_str_dispose(&new_path); - return error; -} - -static int parse_header_mode(uint16_t *mode, git_patch_parse_ctx *ctx) -{ - int64_t m; - - if ((git_parse_advance_digit(&m, &ctx->parse_ctx, 8)) < 0) - return git_parse_err("invalid file mode at line %"PRIuZ, ctx->parse_ctx.line_num); - - if (m > UINT16_MAX) - return -1; - - *mode = (uint16_t)m; - - return 0; -} - -static int parse_header_oid( - git_oid *oid, - uint16_t *oid_len, - git_patch_parse_ctx *ctx) -{ - size_t len; - - for (len = 0; len < ctx->parse_ctx.line_len && len < GIT_OID_HEXSZ; len++) { - if (!git__isxdigit(ctx->parse_ctx.line[len])) - break; - } - - if (len < GIT_OID_MINPREFIXLEN || len > GIT_OID_HEXSZ || - git_oid_fromstrn(oid, ctx->parse_ctx.line, len) < 0) - return git_parse_err("invalid hex formatted object id at line %"PRIuZ, - ctx->parse_ctx.line_num); - - git_parse_advance_chars(&ctx->parse_ctx, len); - - *oid_len = (uint16_t)len; - - return 0; -} - -static int parse_header_git_index( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - char c; - - if (parse_header_oid(&patch->base.delta->old_file.id, - &patch->base.delta->old_file.id_abbrev, ctx) < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, "..") < 0 || - parse_header_oid(&patch->base.delta->new_file.id, - &patch->base.delta->new_file.id_abbrev, ctx) < 0) - return -1; - - if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ' ') { - uint16_t mode = 0; - - git_parse_advance_chars(&ctx->parse_ctx, 1); - - if (parse_header_mode(&mode, ctx) < 0) - return -1; - - if (!patch->base.delta->new_file.mode) - patch->base.delta->new_file.mode = mode; - - if (!patch->base.delta->old_file.mode) - patch->base.delta->old_file.mode = mode; - } - - return 0; -} - -static int parse_header_git_oldmode( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - return parse_header_mode(&patch->base.delta->old_file.mode, ctx); -} - -static int parse_header_git_newmode( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - return parse_header_mode(&patch->base.delta->new_file.mode, ctx); -} - -static int parse_header_git_deletedfilemode( - git_patch_parsed *patch, - git_patch_parse_ctx *ctx) -{ - git__free((char *)patch->base.delta->new_file.path); - - patch->base.delta->new_file.path = NULL; - patch->base.delta->status = GIT_DELTA_DELETED; - patch->base.delta->nfiles = 1; - - return parse_header_mode(&patch->base.delta->old_file.mode, ctx); -} - -static int parse_header_git_newfilemode( - git_patch_parsed *patch, - git_patch_parse_ctx *ctx) -{ - git__free((char *)patch->base.delta->old_file.path); - - patch->base.delta->old_file.path = NULL; - patch->base.delta->status = GIT_DELTA_ADDED; - patch->base.delta->nfiles = 1; - - return parse_header_mode(&patch->base.delta->new_file.mode, ctx); -} - -static int parse_header_rename( - char **out, - git_patch_parse_ctx *ctx) -{ - git_str path = GIT_STR_INIT; - - if (parse_header_path_buf(&path, ctx, header_path_len(ctx)) < 0) - return -1; - - /* Note: the `rename from` and `rename to` lines include the literal - * filename. They do *not* include the prefix. (Who needs consistency?) - */ - *out = git_str_detach(&path); - return 0; -} - -static int parse_header_renamefrom( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - patch->base.delta->status = GIT_DELTA_RENAMED; - return parse_header_rename(&patch->rename_old_path, ctx); -} - -static int parse_header_renameto( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - patch->base.delta->status = GIT_DELTA_RENAMED; - return parse_header_rename(&patch->rename_new_path, ctx); -} - -static int parse_header_copyfrom( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - patch->base.delta->status = GIT_DELTA_COPIED; - return parse_header_rename(&patch->rename_old_path, ctx); -} - -static int parse_header_copyto( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - patch->base.delta->status = GIT_DELTA_COPIED; - return parse_header_rename(&patch->rename_new_path, ctx); -} - -static int parse_header_percent(uint16_t *out, git_patch_parse_ctx *ctx) -{ - int64_t val; - - if (git_parse_advance_digit(&val, &ctx->parse_ctx, 10) < 0) - return -1; - - if (git_parse_advance_expected_str(&ctx->parse_ctx, "%") < 0) - return -1; - - if (val < 0 || val > 100) - return -1; - - *out = (uint16_t)val; - return 0; -} - -static int parse_header_similarity( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - if (parse_header_percent(&patch->base.delta->similarity, ctx) < 0) - return git_parse_err("invalid similarity percentage at line %"PRIuZ, - ctx->parse_ctx.line_num); - - return 0; -} - -static int parse_header_dissimilarity( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - uint16_t dissimilarity; - - if (parse_header_percent(&dissimilarity, ctx) < 0) - return git_parse_err("invalid similarity percentage at line %"PRIuZ, - ctx->parse_ctx.line_num); - - patch->base.delta->similarity = 100 - dissimilarity; - - return 0; -} - -static int parse_header_start(git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - if (parse_header_path(&patch->header_old_path, ctx) < 0) - return git_parse_err("corrupt old path in git diff header at line %"PRIuZ, - ctx->parse_ctx.line_num); - - if (git_parse_advance_ws(&ctx->parse_ctx) < 0 || - parse_header_path(&patch->header_new_path, ctx) < 0) - return git_parse_err("corrupt new path in git diff header at line %"PRIuZ, - ctx->parse_ctx.line_num); - - /* - * We cannot expect to be able to always parse paths correctly at this - * point. Due to the possibility of unquoted names, whitespaces in - * filenames and custom prefixes we have to allow that, though, and just - * proceed here. We then hope for the "---" and "+++" lines to fix that - * for us. - */ - if (!git_parse_ctx_contains(&ctx->parse_ctx, "\n", 1) && - !git_parse_ctx_contains(&ctx->parse_ctx, "\r\n", 2)) { - git_parse_advance_chars(&ctx->parse_ctx, ctx->parse_ctx.line_len - 1); - - git__free(patch->header_old_path); - patch->header_old_path = NULL; - git__free(patch->header_new_path); - patch->header_new_path = NULL; - } - - return 0; -} - -typedef enum { - STATE_START, - - STATE_DIFF, - STATE_FILEMODE, - STATE_MODE, - STATE_INDEX, - STATE_PATH, - - STATE_SIMILARITY, - STATE_RENAME, - STATE_COPY, - - STATE_END -} parse_header_state; - -typedef struct { - const char *str; - parse_header_state expected_state; - parse_header_state next_state; - int(*fn)(git_patch_parsed *, git_patch_parse_ctx *); -} parse_header_transition; - -static const parse_header_transition transitions[] = { - /* Start */ - { "diff --git " , STATE_START, STATE_DIFF, parse_header_start }, - - { "deleted file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_deletedfilemode }, - { "new file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_newfilemode }, - { "old mode " , STATE_DIFF, STATE_MODE, parse_header_git_oldmode }, - { "new mode " , STATE_MODE, STATE_END, parse_header_git_newmode }, - - { "index " , STATE_FILEMODE, STATE_INDEX, parse_header_git_index }, - { "index " , STATE_DIFF, STATE_INDEX, parse_header_git_index }, - { "index " , STATE_END, STATE_INDEX, parse_header_git_index }, - - { "--- " , STATE_DIFF, STATE_PATH, parse_header_git_oldpath }, - { "--- " , STATE_INDEX, STATE_PATH, parse_header_git_oldpath }, - { "--- " , STATE_FILEMODE, STATE_PATH, parse_header_git_oldpath }, - { "+++ " , STATE_PATH, STATE_END, parse_header_git_newpath }, - { "GIT binary patch" , STATE_INDEX, STATE_END, NULL }, - { "Binary files " , STATE_INDEX, STATE_END, NULL }, - - { "similarity index " , STATE_END, STATE_SIMILARITY, parse_header_similarity }, - { "similarity index " , STATE_DIFF, STATE_SIMILARITY, parse_header_similarity }, - { "dissimilarity index ", STATE_DIFF, STATE_SIMILARITY, parse_header_dissimilarity }, - { "rename from " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom }, - { "rename old " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom }, - { "copy from " , STATE_SIMILARITY, STATE_COPY, parse_header_copyfrom }, - { "rename to " , STATE_RENAME, STATE_END, parse_header_renameto }, - { "rename new " , STATE_RENAME, STATE_END, parse_header_renameto }, - { "copy to " , STATE_COPY, STATE_END, parse_header_copyto }, - - /* Next patch */ - { "diff --git " , STATE_END, 0, NULL }, - { "@@ -" , STATE_END, 0, NULL }, - { "-- " , STATE_INDEX, 0, NULL }, - { "-- " , STATE_END, 0, NULL }, -}; - -static int parse_header_git( - git_patch_parsed *patch, - git_patch_parse_ctx *ctx) -{ - size_t i; - int error = 0; - parse_header_state state = STATE_START; - - /* Parse remaining header lines */ - for (; ctx->parse_ctx.remain_len > 0; git_parse_advance_line(&ctx->parse_ctx)) { - bool found = false; - - if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') - break; - - for (i = 0; i < ARRAY_SIZE(transitions); i++) { - const parse_header_transition *transition = &transitions[i]; - size_t op_len = strlen(transition->str); - - if (transition->expected_state != state || - git__prefixcmp(ctx->parse_ctx.line, transition->str) != 0) - continue; - - state = transition->next_state; - - /* Do not advance if this is the patch separator */ - if (transition->fn == NULL) - goto done; - - git_parse_advance_chars(&ctx->parse_ctx, op_len); - - if ((error = transition->fn(patch, ctx)) < 0) - goto done; - - git_parse_advance_ws(&ctx->parse_ctx); - - if (git_parse_advance_expected_str(&ctx->parse_ctx, "\n") < 0 || - ctx->parse_ctx.line_len > 0) { - error = git_parse_err("trailing data at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - - found = true; - break; - } - - if (!found) { - error = git_parse_err("invalid patch header at line %"PRIuZ, - ctx->parse_ctx.line_num); - goto done; - } - } - - if (state != STATE_END) { - error = git_parse_err("unexpected header line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - -done: - return error; -} - -static int parse_int(int *out, git_patch_parse_ctx *ctx) -{ - int64_t num; - - if (git_parse_advance_digit(&num, &ctx->parse_ctx, 10) < 0 || !git__is_int(num)) - return -1; - - *out = (int)num; - return 0; -} - -static int parse_hunk_header( - git_patch_hunk *hunk, - git_patch_parse_ctx *ctx) -{ - const char *header_start = ctx->parse_ctx.line; - char c; - - hunk->hunk.old_lines = 1; - hunk->hunk.new_lines = 1; - - if (git_parse_advance_expected_str(&ctx->parse_ctx, "@@ -") < 0 || - parse_int(&hunk->hunk.old_start, ctx) < 0) - goto fail; - - if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ',') { - if (git_parse_advance_expected_str(&ctx->parse_ctx, ",") < 0 || - parse_int(&hunk->hunk.old_lines, ctx) < 0) - goto fail; - } - - if (git_parse_advance_expected_str(&ctx->parse_ctx, " +") < 0 || - parse_int(&hunk->hunk.new_start, ctx) < 0) - goto fail; - - if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ',') { - if (git_parse_advance_expected_str(&ctx->parse_ctx, ",") < 0 || - parse_int(&hunk->hunk.new_lines, ctx) < 0) - goto fail; - } - - if (git_parse_advance_expected_str(&ctx->parse_ctx, " @@") < 0) - goto fail; - - git_parse_advance_line(&ctx->parse_ctx); - - if (!hunk->hunk.old_lines && !hunk->hunk.new_lines) - goto fail; - - hunk->hunk.header_len = ctx->parse_ctx.line - header_start; - if (hunk->hunk.header_len > (GIT_DIFF_HUNK_HEADER_SIZE - 1)) - return git_parse_err("oversized patch hunk header at line %"PRIuZ, - ctx->parse_ctx.line_num); - - memcpy(hunk->hunk.header, header_start, hunk->hunk.header_len); - hunk->hunk.header[hunk->hunk.header_len] = '\0'; - - return 0; - -fail: - git_error_set(GIT_ERROR_PATCH, "invalid patch hunk header at line %"PRIuZ, - ctx->parse_ctx.line_num); - return -1; -} - -static int eof_for_origin(int origin) { - if (origin == GIT_DIFF_LINE_ADDITION) - return GIT_DIFF_LINE_ADD_EOFNL; - if (origin == GIT_DIFF_LINE_DELETION) - return GIT_DIFF_LINE_DEL_EOFNL; - return GIT_DIFF_LINE_CONTEXT_EOFNL; -} - -static int parse_hunk_body( - git_patch_parsed *patch, - git_patch_hunk *hunk, - git_patch_parse_ctx *ctx) -{ - git_diff_line *line; - int error = 0; - - int oldlines = hunk->hunk.old_lines; - int newlines = hunk->hunk.new_lines; - int last_origin = 0; - - for (; - ctx->parse_ctx.remain_len > 1 && - (oldlines || newlines) && - !git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -"); - git_parse_advance_line(&ctx->parse_ctx)) { - - int old_lineno, new_lineno, origin, prefix = 1; - char c; - - if (git__add_int_overflow(&old_lineno, hunk->hunk.old_start, hunk->hunk.old_lines) || - git__sub_int_overflow(&old_lineno, old_lineno, oldlines) || - git__add_int_overflow(&new_lineno, hunk->hunk.new_start, hunk->hunk.new_lines) || - git__sub_int_overflow(&new_lineno, new_lineno, newlines)) { - error = git_parse_err("unrepresentable line count at line %"PRIuZ, - ctx->parse_ctx.line_num); - goto done; - } - - if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') { - error = git_parse_err("invalid patch instruction at line %"PRIuZ, - ctx->parse_ctx.line_num); - goto done; - } - - git_parse_peek(&c, &ctx->parse_ctx, 0); - - switch (c) { - case '\n': - prefix = 0; - /* fall through */ - - case ' ': - origin = GIT_DIFF_LINE_CONTEXT; - oldlines--; - newlines--; - break; - - case '-': - origin = GIT_DIFF_LINE_DELETION; - oldlines--; - new_lineno = -1; - break; - - case '+': - origin = GIT_DIFF_LINE_ADDITION; - newlines--; - old_lineno = -1; - break; - - case '\\': - /* - * If there are no oldlines left, then this is probably - * the "\ No newline at end of file" marker. Do not - * verify its format, as it may be localized. - */ - if (!oldlines) { - prefix = 0; - origin = eof_for_origin(last_origin); - old_lineno = -1; - new_lineno = -1; - break; - } - /* fall through */ - - default: - error = git_parse_err("invalid patch hunk at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - - line = git_array_alloc(patch->base.lines); - GIT_ERROR_CHECK_ALLOC(line); - - memset(line, 0x0, sizeof(git_diff_line)); - - line->content_len = ctx->parse_ctx.line_len - prefix; - line->content = git__strndup(ctx->parse_ctx.line + prefix, line->content_len); - GIT_ERROR_CHECK_ALLOC(line->content); - line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len; - line->origin = origin; - line->num_lines = 1; - line->old_lineno = old_lineno; - line->new_lineno = new_lineno; - - hunk->line_count++; - - last_origin = origin; - } - - if (oldlines || newlines) { - error = git_parse_err( - "invalid patch hunk, expected %d old lines and %d new lines", - hunk->hunk.old_lines, hunk->hunk.new_lines); - goto done; - } - - /* - * Handle "\ No newline at end of file". Only expect the leading - * backslash, though, because the rest of the string could be - * localized. Because `diff` optimizes for the case where you - * want to apply the patch by hand. - */ - if (git_parse_ctx_contains_s(&ctx->parse_ctx, "\\ ") && - git_array_size(patch->base.lines) > 0) { - - line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1); - - if (line->content_len < 1) { - error = git_parse_err("last line has no trailing newline"); - goto done; - } - - line = git_array_alloc(patch->base.lines); - GIT_ERROR_CHECK_ALLOC(line); - - memset(line, 0x0, sizeof(git_diff_line)); - - line->content_len = ctx->parse_ctx.line_len; - line->content = git__strndup(ctx->parse_ctx.line, line->content_len); - GIT_ERROR_CHECK_ALLOC(line->content); - line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len; - line->origin = eof_for_origin(last_origin); - line->num_lines = 1; - line->old_lineno = -1; - line->new_lineno = -1; - - hunk->line_count++; - - git_parse_advance_line(&ctx->parse_ctx); - } - -done: - return error; -} - -static int parse_patch_header( - git_patch_parsed *patch, - git_patch_parse_ctx *ctx) -{ - int error = 0; - - for (; ctx->parse_ctx.remain_len > 0; git_parse_advance_line(&ctx->parse_ctx)) { - /* This line is too short to be a patch header. */ - if (ctx->parse_ctx.line_len < 6) - continue; - - /* This might be a hunk header without a patch header, provide a - * sensible error message. */ - if (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { - size_t line_num = ctx->parse_ctx.line_num; - git_patch_hunk hunk; - - /* If this cannot be parsed as a hunk header, it's just leading - * noise, continue. - */ - if (parse_hunk_header(&hunk, ctx) < 0) { - git_error_clear(); - continue; - } - - error = git_parse_err("invalid hunk header outside patch at line %"PRIuZ, - line_num); - goto done; - } - - /* This buffer is too short to contain a patch. */ - if (ctx->parse_ctx.remain_len < ctx->parse_ctx.line_len + 6) - break; - - /* A proper git patch */ - if (git_parse_ctx_contains_s(&ctx->parse_ctx, "diff --git ")) { - error = parse_header_git(patch, ctx); - goto done; - } - - error = 0; - continue; - } - - git_error_set(GIT_ERROR_PATCH, "no patch found"); - error = GIT_ENOTFOUND; - -done: - return error; -} - -static int parse_patch_binary_side( - git_diff_binary_file *binary, - git_patch_parse_ctx *ctx) -{ - git_diff_binary_t type = GIT_DIFF_BINARY_NONE; - git_str base85 = GIT_STR_INIT, decoded = GIT_STR_INIT; - int64_t len; - int error = 0; - - if (git_parse_ctx_contains_s(&ctx->parse_ctx, "literal ")) { - type = GIT_DIFF_BINARY_LITERAL; - git_parse_advance_chars(&ctx->parse_ctx, 8); - } else if (git_parse_ctx_contains_s(&ctx->parse_ctx, "delta ")) { - type = GIT_DIFF_BINARY_DELTA; - git_parse_advance_chars(&ctx->parse_ctx, 6); - } else { - error = git_parse_err( - "unknown binary delta type at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - - if (git_parse_advance_digit(&len, &ctx->parse_ctx, 10) < 0 || - git_parse_advance_nl(&ctx->parse_ctx) < 0 || len < 0) { - error = git_parse_err("invalid binary size at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - - while (ctx->parse_ctx.line_len) { - char c; - size_t encoded_len, decoded_len = 0, decoded_orig = decoded.size; - - git_parse_peek(&c, &ctx->parse_ctx, 0); - - if (c == '\n') - break; - else if (c >= 'A' && c <= 'Z') - decoded_len = c - 'A' + 1; - else if (c >= 'a' && c <= 'z') - decoded_len = c - 'a' + (('z' - 'a') + 1) + 1; - - if (!decoded_len) { - error = git_parse_err("invalid binary length at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - - git_parse_advance_chars(&ctx->parse_ctx, 1); - - encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5; - - if (!encoded_len || !ctx->parse_ctx.line_len || encoded_len > ctx->parse_ctx.line_len - 1) { - error = git_parse_err("truncated binary data at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - - if ((error = git_str_decode_base85( - &decoded, ctx->parse_ctx.line, encoded_len, decoded_len)) < 0) - goto done; - - if (decoded.size - decoded_orig != decoded_len) { - error = git_parse_err("truncated binary data at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - - git_parse_advance_chars(&ctx->parse_ctx, encoded_len); - - if (git_parse_advance_nl(&ctx->parse_ctx) < 0) { - error = git_parse_err("trailing data at line %"PRIuZ, ctx->parse_ctx.line_num); - goto done; - } - } - - binary->type = type; - binary->inflatedlen = (size_t)len; - binary->datalen = decoded.size; - binary->data = git_str_detach(&decoded); - -done: - git_str_dispose(&base85); - git_str_dispose(&decoded); - return error; -} - -static int parse_patch_binary( - git_patch_parsed *patch, - git_patch_parse_ctx *ctx) -{ - int error; - - if (git_parse_advance_expected_str(&ctx->parse_ctx, "GIT binary patch") < 0 || - git_parse_advance_nl(&ctx->parse_ctx) < 0) - return git_parse_err("corrupt git binary header at line %"PRIuZ, ctx->parse_ctx.line_num); - - /* parse old->new binary diff */ - if ((error = parse_patch_binary_side( - &patch->base.binary.new_file, ctx)) < 0) - return error; - - if (git_parse_advance_nl(&ctx->parse_ctx) < 0) - return git_parse_err("corrupt git binary separator at line %"PRIuZ, - ctx->parse_ctx.line_num); - - /* parse new->old binary diff */ - if ((error = parse_patch_binary_side( - &patch->base.binary.old_file, ctx)) < 0) - return error; - - if (git_parse_advance_nl(&ctx->parse_ctx) < 0) - return git_parse_err("corrupt git binary patch separator at line %"PRIuZ, - ctx->parse_ctx.line_num); - - patch->base.binary.contains_data = 1; - patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; - return 0; -} - -static int parse_patch_binary_nodata( - git_patch_parsed *patch, - git_patch_parse_ctx *ctx) -{ - const char *old = patch->old_path ? patch->old_path : patch->header_old_path; - const char *new = patch->new_path ? patch->new_path : patch->header_new_path; - - if (!old || !new) - return git_parse_err("corrupt binary data without paths at line %"PRIuZ, ctx->parse_ctx.line_num); - - if (patch->base.delta->status == GIT_DELTA_ADDED) - old = "/dev/null"; - else if (patch->base.delta->status == GIT_DELTA_DELETED) - new = "/dev/null"; - - if (git_parse_advance_expected_str(&ctx->parse_ctx, "Binary files ") < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, old) < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, " and ") < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, new) < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, " differ") < 0 || - git_parse_advance_nl(&ctx->parse_ctx) < 0) - return git_parse_err("corrupt git binary header at line %"PRIuZ, ctx->parse_ctx.line_num); - - patch->base.binary.contains_data = 0; - patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; - return 0; -} - -static int parse_patch_hunks( - git_patch_parsed *patch, - git_patch_parse_ctx *ctx) -{ - git_patch_hunk *hunk; - int error = 0; - - while (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { - hunk = git_array_alloc(patch->base.hunks); - GIT_ERROR_CHECK_ALLOC(hunk); - - memset(hunk, 0, sizeof(git_patch_hunk)); - - hunk->line_start = git_array_size(patch->base.lines); - hunk->line_count = 0; - - if ((error = parse_hunk_header(hunk, ctx)) < 0 || - (error = parse_hunk_body(patch, hunk, ctx)) < 0) - goto done; - } - - patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; - -done: - return error; -} - -static int parse_patch_body( - git_patch_parsed *patch, git_patch_parse_ctx *ctx) -{ - if (git_parse_ctx_contains_s(&ctx->parse_ctx, "GIT binary patch")) - return parse_patch_binary(patch, ctx); - else if (git_parse_ctx_contains_s(&ctx->parse_ctx, "Binary files ")) - return parse_patch_binary_nodata(patch, ctx); - else - return parse_patch_hunks(patch, ctx); -} - -static int check_header_names( - const char *one, - const char *two, - const char *old_or_new, - bool two_null) -{ - if (!one || !two) - return 0; - - if (two_null && strcmp(two, "/dev/null") != 0) - return git_parse_err("expected %s path of '/dev/null'", old_or_new); - - else if (!two_null && strcmp(one, two) != 0) - return git_parse_err("mismatched %s path names", old_or_new); - - return 0; -} - -static int check_prefix( - char **out, - size_t *out_len, - git_patch_parsed *patch, - const char *path_start) -{ - const char *path = path_start; - size_t prefix_len = patch->ctx->opts.prefix_len; - size_t remain_len = prefix_len; - - *out = NULL; - *out_len = 0; - - if (prefix_len == 0) - goto done; - - /* leading slashes do not count as part of the prefix in git apply */ - while (*path == '/') - path++; - - while (*path && remain_len) { - if (*path == '/') - remain_len--; - - path++; - } - - if (remain_len || !*path) - return git_parse_err( - "header filename does not contain %"PRIuZ" path components", - prefix_len); - -done: - *out_len = (path - path_start); - *out = git__strndup(path_start, *out_len); - - return (*out == NULL) ? -1 : 0; -} - -static int check_filenames(git_patch_parsed *patch) -{ - const char *prefixed_new, *prefixed_old; - size_t old_prefixlen = 0, new_prefixlen = 0; - bool added = (patch->base.delta->status == GIT_DELTA_ADDED); - bool deleted = (patch->base.delta->status == GIT_DELTA_DELETED); - - if (patch->old_path && !patch->new_path) - return git_parse_err("missing new path"); - - if (!patch->old_path && patch->new_path) - return git_parse_err("missing old path"); - - /* Ensure (non-renamed) paths match */ - if (check_header_names(patch->header_old_path, patch->old_path, "old", added) < 0 || - check_header_names(patch->header_new_path, patch->new_path, "new", deleted) < 0) - return -1; - - prefixed_old = (!added && patch->old_path) ? patch->old_path : patch->header_old_path; - prefixed_new = (!deleted && patch->new_path) ? patch->new_path : patch->header_new_path; - - if ((prefixed_old && check_prefix(&patch->old_prefix, &old_prefixlen, patch, prefixed_old) < 0) || - (prefixed_new && check_prefix(&patch->new_prefix, &new_prefixlen, patch, prefixed_new) < 0)) - return -1; - - /* Prefer the rename filenames as they are unambiguous and unprefixed */ - if (patch->rename_old_path) - patch->base.delta->old_file.path = patch->rename_old_path; - else if (prefixed_old) - patch->base.delta->old_file.path = prefixed_old + old_prefixlen; - else - patch->base.delta->old_file.path = NULL; - - if (patch->rename_new_path) - patch->base.delta->new_file.path = patch->rename_new_path; - else if (prefixed_new) - patch->base.delta->new_file.path = prefixed_new + new_prefixlen; - else - patch->base.delta->new_file.path = NULL; - - if (!patch->base.delta->old_file.path && - !patch->base.delta->new_file.path) - return git_parse_err("git diff header lacks old / new paths"); - - return 0; -} - -static int check_patch(git_patch_parsed *patch) -{ - git_diff_delta *delta = patch->base.delta; - - if (check_filenames(patch) < 0) - return -1; - - if (delta->old_file.path && - delta->status != GIT_DELTA_DELETED && - !delta->new_file.mode) - delta->new_file.mode = delta->old_file.mode; - - if (delta->status == GIT_DELTA_MODIFIED && - !(delta->flags & GIT_DIFF_FLAG_BINARY) && - delta->new_file.mode == delta->old_file.mode && - git_array_size(patch->base.hunks) == 0) - return git_parse_err("patch with no hunks"); - - if (delta->status == GIT_DELTA_ADDED) { - memset(&delta->old_file.id, 0x0, sizeof(git_oid)); - delta->old_file.id_abbrev = 0; - } - - if (delta->status == GIT_DELTA_DELETED) { - memset(&delta->new_file.id, 0x0, sizeof(git_oid)); - delta->new_file.id_abbrev = 0; - } - - return 0; -} - -git_patch_parse_ctx *git_patch_parse_ctx_init( - const char *content, - size_t content_len, - const git_patch_options *opts) -{ - git_patch_parse_ctx *ctx; - git_patch_options default_opts = GIT_PATCH_OPTIONS_INIT; - - if ((ctx = git__calloc(1, sizeof(git_patch_parse_ctx))) == NULL) - return NULL; - - if ((git_parse_ctx_init(&ctx->parse_ctx, content, content_len)) < 0) { - git__free(ctx); - return NULL; - } - - if (opts) - memcpy(&ctx->opts, opts, sizeof(git_patch_options)); - else - memcpy(&ctx->opts, &default_opts, sizeof(git_patch_options)); - - GIT_REFCOUNT_INC(ctx); - return ctx; -} - -static void patch_parse_ctx_free(git_patch_parse_ctx *ctx) -{ - if (!ctx) - return; - - git_parse_ctx_clear(&ctx->parse_ctx); - git__free(ctx); -} - -void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx) -{ - GIT_REFCOUNT_DEC(ctx, patch_parse_ctx_free); -} - -int git_patch_parsed_from_diff(git_patch **out, git_diff *d, size_t idx) -{ - git_diff_parsed *diff = (git_diff_parsed *)d; - git_patch *p; - - if ((p = git_vector_get(&diff->patches, idx)) == NULL) - return -1; - - GIT_REFCOUNT_INC(p); - *out = p; - - return 0; -} - -static void patch_parsed__free(git_patch *p) -{ - git_patch_parsed *patch = (git_patch_parsed *)p; - git_diff_line *line; - size_t i; - - if (!patch) - return; - - git_patch_parse_ctx_free(patch->ctx); - - git__free((char *)patch->base.binary.old_file.data); - git__free((char *)patch->base.binary.new_file.data); - git_array_clear(patch->base.hunks); - git_array_foreach(patch->base.lines, i, line) - git__free((char *) line->content); - git_array_clear(patch->base.lines); - git__free(patch->base.delta); - - git__free(patch->old_prefix); - git__free(patch->new_prefix); - git__free(patch->header_old_path); - git__free(patch->header_new_path); - git__free(patch->rename_old_path); - git__free(patch->rename_new_path); - git__free(patch->old_path); - git__free(patch->new_path); - git__free(patch); -} - -int git_patch_parse( - git_patch **out, - git_patch_parse_ctx *ctx) -{ - git_patch_parsed *patch; - size_t start, used; - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ctx); - - *out = NULL; - - patch = git__calloc(1, sizeof(git_patch_parsed)); - GIT_ERROR_CHECK_ALLOC(patch); - - patch->ctx = ctx; - GIT_REFCOUNT_INC(patch->ctx); - - patch->base.free_fn = patch_parsed__free; - - patch->base.delta = git__calloc(1, sizeof(git_diff_delta)); - GIT_ERROR_CHECK_ALLOC(patch->base.delta); - - patch->base.delta->status = GIT_DELTA_MODIFIED; - patch->base.delta->nfiles = 2; - - start = ctx->parse_ctx.remain_len; - - if ((error = parse_patch_header(patch, ctx)) < 0 || - (error = parse_patch_body(patch, ctx)) < 0 || - (error = check_patch(patch)) < 0) - goto done; - - used = start - ctx->parse_ctx.remain_len; - ctx->parse_ctx.remain += used; - - patch->base.diff_opts.old_prefix = patch->old_prefix; - patch->base.diff_opts.new_prefix = patch->new_prefix; - patch->base.diff_opts.flags |= GIT_DIFF_SHOW_BINARY; - - GIT_REFCOUNT_INC(&patch->base); - *out = &patch->base; - -done: - if (error < 0) - patch_parsed__free(&patch->base); - - return error; -} - -int git_patch_from_buffer( - git_patch **out, - const char *content, - size_t content_len, - const git_patch_options *opts) -{ - git_patch_parse_ctx *ctx; - int error; - - ctx = git_patch_parse_ctx_init(content, content_len, opts); - GIT_ERROR_CHECK_ALLOC(ctx); - - error = git_patch_parse(out, ctx); - - git_patch_parse_ctx_free(ctx); - return error; -} - diff --git a/src/patch_parse.h b/src/patch_parse.h deleted file mode 100644 index 140629da8..000000000 --- a/src/patch_parse.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_patch_parse_h__ -#define INCLUDE_patch_parse_h__ - -#include "common.h" - -#include "parse.h" -#include "patch.h" - -typedef struct { - git_refcount rc; - - git_patch_options opts; - - git_parse_ctx parse_ctx; -} git_patch_parse_ctx; - -extern git_patch_parse_ctx *git_patch_parse_ctx_init( - const char *content, - size_t content_len, - const git_patch_options *opts); - -extern void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx); - -/** - * Create a patch for a single file from the contents of a patch buffer. - * - * @param out The patch to be created - * @param contents The contents of a patch file - * @param contents_len The length of the patch file - * @param opts The git_patch_options - * @return 0 on success, <0 on failure. - */ -extern int git_patch_from_buffer( - git_patch **out, - const char *contents, - size_t contents_len, - const git_patch_options *opts); - -extern int git_patch_parse( - git_patch **out, - git_patch_parse_ctx *ctx); - -extern int git_patch_parsed_from_diff(git_patch **, git_diff *, size_t); - -#endif diff --git a/src/path.c b/src/path.c deleted file mode 100644 index 05a3dc2cf..000000000 --- a/src/path.c +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "path.h" - -#include "repository.h" -#include "fs_path.h" - -typedef struct { - git_repository *repo; - uint16_t file_mode; - unsigned int flags; -} repository_path_validate_data; - -static int32_t next_hfs_char(const char **in, size_t *len) -{ - while (*len) { - uint32_t codepoint; - int cp_len = git_utf8_iterate(&codepoint, *in, *len); - if (cp_len < 0) - return -1; - - (*in) += cp_len; - (*len) -= cp_len; - - /* these code points are ignored completely */ - switch (codepoint) { - case 0x200c: /* ZERO WIDTH NON-JOINER */ - case 0x200d: /* ZERO WIDTH JOINER */ - case 0x200e: /* LEFT-TO-RIGHT MARK */ - case 0x200f: /* RIGHT-TO-LEFT MARK */ - case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */ - case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */ - case 0x202c: /* POP DIRECTIONAL FORMATTING */ - case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */ - case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */ - case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */ - case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */ - case 0x206c: /* INHIBIT ARABIC FORM SHAPING */ - case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */ - case 0x206e: /* NATIONAL DIGIT SHAPES */ - case 0x206f: /* NOMINAL DIGIT SHAPES */ - case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */ - continue; - } - - /* fold into lowercase -- this will only fold characters in - * the ASCII range, which is perfectly fine, because the - * git folder name can only be composed of ascii characters - */ - return git__tolower((int)codepoint); - } - return 0; /* NULL byte -- end of string */ -} - -static bool validate_dotgit_hfs_generic( - const char *path, - size_t len, - const char *needle, - size_t needle_len) -{ - size_t i; - char c; - - if (next_hfs_char(&path, &len) != '.') - return true; - - for (i = 0; i < needle_len; i++) { - c = next_hfs_char(&path, &len); - if (c != needle[i]) - return true; - } - - if (next_hfs_char(&path, &len) != '\0') - return true; - - return false; -} - -static bool validate_dotgit_hfs(const char *path, size_t len) -{ - return validate_dotgit_hfs_generic(path, len, "git", CONST_STRLEN("git")); -} - -GIT_INLINE(bool) validate_dotgit_ntfs( - git_repository *repo, - const char *path, - size_t len) -{ - git_str *reserved = git_repository__reserved_names_win32; - size_t reserved_len = git_repository__reserved_names_win32_len; - size_t start = 0, i; - - if (repo) - git_repository__reserved_names(&reserved, &reserved_len, repo, true); - - for (i = 0; i < reserved_len; i++) { - git_str *r = &reserved[i]; - - if (len >= r->size && - strncasecmp(path, r->ptr, r->size) == 0) { - start = r->size; - break; - } - } - - if (!start) - return true; - - /* - * Reject paths that start with Windows-style directory separators - * (".git\") or NTFS alternate streams (".git:") and could be used - * to write to the ".git" directory on Windows platforms. - */ - if (path[start] == '\\' || path[start] == ':') - return false; - - /* Reject paths like '.git ' or '.git.' */ - for (i = start; i < len; i++) { - if (path[i] != ' ' && path[i] != '.') - return true; - } - - return false; -} - -/* - * Windows paths that end with spaces and/or dots are elided to the - * path without them for backward compatibility. That is to say - * that opening file "foo ", "foo." or even "foo . . ." will all - * map to a filename of "foo". This function identifies spaces and - * dots at the end of a filename, whether the proper end of the - * filename (end of string) or a colon (which would indicate a - * Windows alternate data stream.) - */ -GIT_INLINE(bool) ntfs_end_of_filename(const char *path) -{ - const char *c = path; - - for (;; c++) { - if (*c == '\0' || *c == ':') - return true; - if (*c != ' ' && *c != '.') - return false; - } - - return true; -} - -GIT_INLINE(bool) validate_dotgit_ntfs_generic( - const char *name, - size_t len, - const char *dotgit_name, - size_t dotgit_len, - const char *shortname_pfix) -{ - int i, saw_tilde; - - if (name[0] == '.' && len >= dotgit_len && - !strncasecmp(name + 1, dotgit_name, dotgit_len)) { - return !ntfs_end_of_filename(name + dotgit_len + 1); - } - - /* Detect the basic NTFS shortname with the first six chars */ - if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' && - name[7] >= '1' && name[7] <= '4') - return !ntfs_end_of_filename(name + 8); - - /* Catch fallback names */ - for (i = 0, saw_tilde = 0; i < 8; i++) { - if (name[i] == '\0') { - return true; - } else if (saw_tilde) { - if (name[i] < '0' || name[i] > '9') - return true; - } else if (name[i] == '~') { - if (name[i+1] < '1' || name[i+1] > '9') - return true; - saw_tilde = 1; - } else if (i >= 6) { - return true; - } else if ((unsigned char)name[i] > 127) { - return true; - } else if (git__tolower(name[i]) != shortname_pfix[i]) { - return true; - } - } - - return !ntfs_end_of_filename(name + i); -} - -/* - * Return the length of the common prefix between str and prefix, comparing them - * case-insensitively (must be ASCII to match). - */ -GIT_INLINE(size_t) common_prefix_icase(const char *str, size_t len, const char *prefix) -{ - size_t count = 0; - - while (len > 0 && tolower(*str) == tolower(*prefix)) { - count++; - str++; - prefix++; - len--; - } - - return count; -} - -static bool validate_repo_component( - const char *component, - size_t len, - void *payload) -{ - repository_path_validate_data *data = (repository_path_validate_data *)payload; - - if (data->flags & GIT_PATH_REJECT_DOT_GIT_HFS) { - if (!validate_dotgit_hfs(component, len)) - return false; - - if (S_ISLNK(data->file_mode) && - git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_HFS)) - return false; - } - - if (data->flags & GIT_PATH_REJECT_DOT_GIT_NTFS) { - if (!validate_dotgit_ntfs(data->repo, component, len)) - return false; - - if (S_ISLNK(data->file_mode) && - git_path_is_gitfile(component, len, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_NTFS)) - return false; - } - - /* don't bother rerunning the `.git` test if we ran the HFS or NTFS - * specific tests, they would have already rejected `.git`. - */ - if ((data->flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 && - (data->flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 && - (data->flags & GIT_PATH_REJECT_DOT_GIT_LITERAL)) { - if (len >= 4 && - component[0] == '.' && - (component[1] == 'g' || component[1] == 'G') && - (component[2] == 'i' || component[2] == 'I') && - (component[3] == 't' || component[3] == 'T')) { - if (len == 4) - return false; - - if (S_ISLNK(data->file_mode) && - common_prefix_icase(component, len, ".gitmodules") == len) - return false; - } - } - - return true; -} - -GIT_INLINE(unsigned int) dotgit_flags( - git_repository *repo, - unsigned int flags) -{ - int protectHFS = 0, protectNTFS = 1; - int error = 0; - - flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL; - -#ifdef __APPLE__ - protectHFS = 1; -#endif - - if (repo && !protectHFS) - error = git_repository__configmap_lookup(&protectHFS, repo, GIT_CONFIGMAP_PROTECTHFS); - if (!error && protectHFS) - flags |= GIT_PATH_REJECT_DOT_GIT_HFS; - - if (repo) - error = git_repository__configmap_lookup(&protectNTFS, repo, GIT_CONFIGMAP_PROTECTNTFS); - if (!error && protectNTFS) - flags |= GIT_PATH_REJECT_DOT_GIT_NTFS; - - return flags; -} - -GIT_INLINE(unsigned int) length_flags( - git_repository *repo, - unsigned int flags) -{ -#ifdef GIT_WIN32 - int allow = 0; - - if (repo && - git_repository__configmap_lookup(&allow, repo, GIT_CONFIGMAP_LONGPATHS) < 0) - allow = 0; - - if (allow) - flags &= ~GIT_FS_PATH_REJECT_LONG_PATHS; - -#else - GIT_UNUSED(repo); - flags &= ~GIT_FS_PATH_REJECT_LONG_PATHS; -#endif - - return flags; -} - -bool git_path_str_is_valid( - git_repository *repo, - const git_str *path, - uint16_t file_mode, - unsigned int flags) -{ - repository_path_validate_data data = {0}; - - /* Upgrade the ".git" checks based on platform */ - if ((flags & GIT_PATH_REJECT_DOT_GIT)) - flags = dotgit_flags(repo, flags); - - /* Update the length checks based on platform */ - if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS)) - flags = length_flags(repo, flags); - - data.repo = repo; - data.file_mode = file_mode; - data.flags = flags; - - return git_fs_path_str_is_valid_ext(path, flags, NULL, validate_repo_component, NULL, &data); -} - -static const struct { - const char *file; - const char *hash; - size_t filelen; -} gitfiles[] = { - { "gitignore", "gi250a", CONST_STRLEN("gitignore") }, - { "gitmodules", "gi7eba", CONST_STRLEN("gitmodules") }, - { "gitattributes", "gi7d29", CONST_STRLEN("gitattributes") } -}; - -extern int git_path_is_gitfile( - const char *path, - size_t pathlen, - git_path_gitfile gitfile, - git_path_fs fs) -{ - const char *file, *hash; - size_t filelen; - - if (!(gitfile >= GIT_PATH_GITFILE_GITIGNORE && gitfile < ARRAY_SIZE(gitfiles))) { - git_error_set(GIT_ERROR_OS, "invalid gitfile for path validation"); - return -1; - } - - file = gitfiles[gitfile].file; - filelen = gitfiles[gitfile].filelen; - hash = gitfiles[gitfile].hash; - - switch (fs) { - case GIT_PATH_FS_GENERIC: - return !validate_dotgit_ntfs_generic(path, pathlen, file, filelen, hash) || - !validate_dotgit_hfs_generic(path, pathlen, file, filelen); - case GIT_PATH_FS_NTFS: - return !validate_dotgit_ntfs_generic(path, pathlen, file, filelen, hash); - case GIT_PATH_FS_HFS: - return !validate_dotgit_hfs_generic(path, pathlen, file, filelen); - default: - git_error_set(GIT_ERROR_OS, "invalid filesystem for path validation"); - return -1; - } -} - diff --git a/src/path.h b/src/path.h deleted file mode 100644 index c4a2c4250..000000000 --- a/src/path.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_path_h__ -#define INCLUDE_path_h__ - -#include "common.h" - -#include "fs_path.h" -#include - -#define GIT_PATH_REJECT_DOT_GIT (GIT_FS_PATH_REJECT_MAX << 1) -#define GIT_PATH_REJECT_DOT_GIT_LITERAL (GIT_FS_PATH_REJECT_MAX << 2) -#define GIT_PATH_REJECT_DOT_GIT_HFS (GIT_FS_PATH_REJECT_MAX << 3) -#define GIT_PATH_REJECT_DOT_GIT_NTFS (GIT_FS_PATH_REJECT_MAX << 4) - -/* Paths that should never be written into the working directory. */ -#define GIT_PATH_REJECT_WORKDIR_DEFAULTS \ - GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS | GIT_PATH_REJECT_DOT_GIT - -/* Paths that should never be written to the index. */ -#define GIT_PATH_REJECT_INDEX_DEFAULTS \ - GIT_FS_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT - -extern bool git_path_str_is_valid( - git_repository *repo, - const git_str *path, - uint16_t file_mode, - unsigned int flags); - -GIT_INLINE(bool) git_path_is_valid( - git_repository *repo, - const char *path, - uint16_t file_mode, - unsigned int flags) -{ - git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); - return git_path_str_is_valid(repo, &str, file_mode, flags); -} - -GIT_INLINE(int) git_path_validate_str_length( - git_repository *repo, - const git_str *path) -{ - if (!git_path_str_is_valid(repo, path, 0, GIT_FS_PATH_REJECT_LONG_PATHS)) { - if (path->size == SIZE_MAX) - git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%s'", path->ptr); - else - git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%.*s'", (int)path->size, path->ptr); - - return -1; - } - - return 0; -} - -GIT_INLINE(int) git_path_validate_length( - git_repository *repo, - const char *path) -{ - git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); - return git_path_validate_str_length(repo, &str); -} - -#endif diff --git a/src/pathspec.c b/src/pathspec.c deleted file mode 100644 index 3e44643c6..000000000 --- a/src/pathspec.c +++ /dev/null @@ -1,722 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "pathspec.h" - -#include "git2/pathspec.h" -#include "git2/diff.h" -#include "attr_file.h" -#include "iterator.h" -#include "repository.h" -#include "index.h" -#include "bitvec.h" -#include "diff.h" -#include "wildmatch.h" - -/* what is the common non-wildcard prefix for all items in the pathspec */ -char *git_pathspec_prefix(const git_strarray *pathspec) -{ - git_str prefix = GIT_STR_INIT; - const char *scan; - - if (!pathspec || !pathspec->count || - git_str_common_prefix(&prefix, pathspec->strings, pathspec->count) < 0) - return NULL; - - /* diff prefix will only be leading non-wildcards */ - for (scan = prefix.ptr; *scan; ++scan) { - if (git__iswildcard(*scan) && - (scan == prefix.ptr || (*(scan - 1) != '\\'))) - break; - } - git_str_truncate(&prefix, scan - prefix.ptr); - - if (prefix.size <= 0) { - git_str_dispose(&prefix); - return NULL; - } - - git_str_unescape(&prefix); - - return git_str_detach(&prefix); -} - -/* is there anything in the spec that needs to be filtered on */ -bool git_pathspec_is_empty(const git_strarray *pathspec) -{ - size_t i; - - if (pathspec == NULL) - return true; - - for (i = 0; i < pathspec->count; ++i) { - const char *str = pathspec->strings[i]; - - if (str && str[0]) - return false; - } - - return true; -} - -/* build a vector of fnmatch patterns to evaluate efficiently */ -int git_pathspec__vinit( - git_vector *vspec, const git_strarray *strspec, git_pool *strpool) -{ - size_t i; - - memset(vspec, 0, sizeof(*vspec)); - - if (git_pathspec_is_empty(strspec)) - return 0; - - if (git_vector_init(vspec, strspec->count, NULL) < 0) - return -1; - - for (i = 0; i < strspec->count; ++i) { - int ret; - const char *pattern = strspec->strings[i]; - git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch)); - if (!match) - return -1; - - match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; - - ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern); - if (ret == GIT_ENOTFOUND) { - git__free(match); - continue; - } else if (ret < 0) { - git__free(match); - return ret; - } - - if (git_vector_insert(vspec, match) < 0) - return -1; - } - - return 0; -} - -/* free data from the pathspec vector */ -void git_pathspec__vfree(git_vector *vspec) -{ - git_vector_free_deep(vspec); -} - -struct pathspec_match_context { - int wildmatch_flags; - int (*strcomp)(const char *, const char *); - int (*strncomp)(const char *, const char *, size_t); -}; - -static void pathspec_match_context_init( - struct pathspec_match_context *ctxt, - bool disable_fnmatch, - bool casefold) -{ - if (disable_fnmatch) - ctxt->wildmatch_flags = -1; - else if (casefold) - ctxt->wildmatch_flags = WM_CASEFOLD; - else - ctxt->wildmatch_flags = 0; - - if (casefold) { - ctxt->strcomp = git__strcasecmp; - ctxt->strncomp = git__strncasecmp; - } else { - ctxt->strcomp = git__strcmp; - ctxt->strncomp = git__strncmp; - } -} - -static int pathspec_match_one( - const git_attr_fnmatch *match, - struct pathspec_match_context *ctxt, - const char *path) -{ - int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : WM_NOMATCH; - - if (result == WM_NOMATCH) - result = ctxt->strcomp(match->pattern, path) ? WM_NOMATCH : 0; - - if (ctxt->wildmatch_flags >= 0 && result == WM_NOMATCH) - result = wildmatch(match->pattern, path, ctxt->wildmatch_flags); - - /* if we didn't match, look for exact dirname prefix match */ - if (result == WM_NOMATCH && - (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && - ctxt->strncomp(path, match->pattern, match->length) == 0 && - path[match->length] == '/') - result = 0; - - /* if we didn't match and this is a negative match, check for exact - * match of filename with leading '!' - */ - if (result == WM_NOMATCH && - (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 && - *path == '!' && - ctxt->strncomp(path + 1, match->pattern, match->length) == 0 && - (!path[match->length + 1] || path[match->length + 1] == '/')) - return 1; - - if (result == 0) - return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1; - return -1; -} - -static int git_pathspec__match_at( - size_t *matched_at, - const git_vector *vspec, - struct pathspec_match_context *ctxt, - const char *path0, - const char *path1) -{ - int result = GIT_ENOTFOUND; - size_t i = 0; - const git_attr_fnmatch *match; - - git_vector_foreach(vspec, i, match) { - if (path0 && (result = pathspec_match_one(match, ctxt, path0)) >= 0) - break; - if (path1 && (result = pathspec_match_one(match, ctxt, path1)) >= 0) - break; - } - - *matched_at = i; - return result; -} - -/* match a path against the vectorized pathspec */ -bool git_pathspec__match( - const git_vector *vspec, - const char *path, - bool disable_fnmatch, - bool casefold, - const char **matched_pathspec, - size_t *matched_at) -{ - int result; - size_t pos; - struct pathspec_match_context ctxt; - - if (matched_pathspec) - *matched_pathspec = NULL; - if (matched_at) - *matched_at = GIT_PATHSPEC_NOMATCH; - - if (!vspec || !vspec->length) - return true; - - pathspec_match_context_init(&ctxt, disable_fnmatch, casefold); - - result = git_pathspec__match_at(&pos, vspec, &ctxt, path, NULL); - if (result >= 0) { - if (matched_pathspec) { - const git_attr_fnmatch *match = git_vector_get(vspec, pos); - *matched_pathspec = match->pattern; - } - - if (matched_at) - *matched_at = pos; - } - - return (result > 0); -} - - -int git_pathspec__init(git_pathspec *ps, const git_strarray *paths) -{ - int error = 0; - - memset(ps, 0, sizeof(*ps)); - - ps->prefix = git_pathspec_prefix(paths); - - if ((error = git_pool_init(&ps->pool, 1)) < 0 || - (error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0) - git_pathspec__clear(ps); - - return error; -} - -void git_pathspec__clear(git_pathspec *ps) -{ - git__free(ps->prefix); - git_pathspec__vfree(&ps->pathspec); - git_pool_clear(&ps->pool); - memset(ps, 0, sizeof(*ps)); -} - -int git_pathspec_new(git_pathspec **out, const git_strarray *pathspec) -{ - int error = 0; - git_pathspec *ps = git__malloc(sizeof(git_pathspec)); - GIT_ERROR_CHECK_ALLOC(ps); - - if ((error = git_pathspec__init(ps, pathspec)) < 0) { - git__free(ps); - return error; - } - - GIT_REFCOUNT_INC(ps); - *out = ps; - return 0; -} - -static void pathspec_free(git_pathspec *ps) -{ - git_pathspec__clear(ps); - git__free(ps); -} - -void git_pathspec_free(git_pathspec *ps) -{ - if (!ps) - return; - GIT_REFCOUNT_DEC(ps, pathspec_free); -} - -int git_pathspec_matches_path( - const git_pathspec *ps, uint32_t flags, const char *path) -{ - bool no_fnmatch = (flags & GIT_PATHSPEC_NO_GLOB) != 0; - bool casefold = (flags & GIT_PATHSPEC_IGNORE_CASE) != 0; - - GIT_ASSERT_ARG(ps); - GIT_ASSERT_ARG(path); - - return (0 != git_pathspec__match( - &ps->pathspec, path, no_fnmatch, casefold, NULL, NULL)); -} - -static void pathspec_match_free(git_pathspec_match_list *m) -{ - if (!m) - return; - - git_pathspec_free(m->pathspec); - m->pathspec = NULL; - - git_array_clear(m->matches); - git_array_clear(m->failures); - git_pool_clear(&m->pool); - git__free(m); -} - -static git_pathspec_match_list *pathspec_match_alloc( - git_pathspec *ps, int datatype) -{ - git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list)); - if (!m) - return NULL; - - if (git_pool_init(&m->pool, 1) < 0) - return NULL; - - /* need to keep reference to pathspec and increment refcount because - * failures array stores pointers to the pattern strings of the - * pathspec that had no matches - */ - GIT_REFCOUNT_INC(ps); - m->pathspec = ps; - m->datatype = datatype; - - return m; -} - -GIT_INLINE(size_t) pathspec_mark_pattern(git_bitvec *used, size_t pos) -{ - if (!git_bitvec_get(used, pos)) { - git_bitvec_set(used, pos, true); - return 1; - } - - return 0; -} - -static size_t pathspec_mark_remaining( - git_bitvec *used, - git_vector *patterns, - struct pathspec_match_context *ctxt, - size_t start, - const char *path0, - const char *path1) -{ - size_t count = 0; - - if (path1 == path0) - path1 = NULL; - - for (; start < patterns->length; ++start) { - const git_attr_fnmatch *pat = git_vector_get(patterns, start); - - if (git_bitvec_get(used, start)) - continue; - - if (path0 && pathspec_match_one(pat, ctxt, path0) > 0) - count += pathspec_mark_pattern(used, start); - else if (path1 && pathspec_match_one(pat, ctxt, path1) > 0) - count += pathspec_mark_pattern(used, start); - } - - return count; -} - -static int pathspec_build_failure_array( - git_pathspec_string_array_t *failures, - git_vector *patterns, - git_bitvec *used, - git_pool *pool) -{ - size_t pos; - char **failed; - const git_attr_fnmatch *pat; - - for (pos = 0; pos < patterns->length; ++pos) { - if (git_bitvec_get(used, pos)) - continue; - - if ((failed = git_array_alloc(*failures)) == NULL) - return -1; - - pat = git_vector_get(patterns, pos); - - if ((*failed = git_pool_strdup(pool, pat->pattern)) == NULL) - return -1; - } - - return 0; -} - -static int pathspec_match_from_iterator( - git_pathspec_match_list **out, - git_iterator *iter, - uint32_t flags, - git_pathspec *ps) -{ - int error = 0; - git_pathspec_match_list *m = NULL; - const git_index_entry *entry = NULL; - struct pathspec_match_context ctxt; - git_vector *patterns = &ps->pathspec; - bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; - bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; - size_t pos, used_ct = 0, found_files = 0; - git_index *index = NULL; - git_bitvec used_patterns; - char **file; - - if (git_bitvec_init(&used_patterns, patterns->length) < 0) - return -1; - - if (out) { - *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_STRINGS); - GIT_ERROR_CHECK_ALLOC(m); - } - - if ((error = git_iterator_reset_range(iter, ps->prefix, ps->prefix)) < 0) - goto done; - - if (git_iterator_type(iter) == GIT_ITERATOR_WORKDIR && - (error = git_repository_index__weakptr( - &index, git_iterator_owner(iter))) < 0) - goto done; - - pathspec_match_context_init( - &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, - git_iterator_ignore_case(iter)); - - while (!(error = git_iterator_advance(&entry, iter))) { - /* search for match with entry->path */ - int result = git_pathspec__match_at( - &pos, patterns, &ctxt, entry->path, NULL); - - /* no matches for this path */ - if (result < 0) - continue; - - /* if result was a negative pattern match, then don't list file */ - if (!result) { - used_ct += pathspec_mark_pattern(&used_patterns, pos); - continue; - } - - /* check if path is ignored and untracked */ - if (index != NULL && - git_iterator_current_is_ignored(iter) && - git_index__find_pos(NULL, index, entry->path, 0, GIT_INDEX_STAGE_ANY) < 0) - continue; - - /* mark the matched pattern as used */ - used_ct += pathspec_mark_pattern(&used_patterns, pos); - ++found_files; - - /* if find_failures is on, check if any later patterns also match */ - if (find_failures && used_ct < patterns->length) - used_ct += pathspec_mark_remaining( - &used_patterns, patterns, &ctxt, pos + 1, entry->path, NULL); - - /* if only looking at failures, exit early or just continue */ - if (failures_only || !out) { - if (used_ct == patterns->length) - break; - continue; - } - - /* insert matched path into matches array */ - if ((file = (char **)git_array_alloc(m->matches)) == NULL || - (*file = git_pool_strdup(&m->pool, entry->path)) == NULL) { - error = -1; - goto done; - } - } - - if (error < 0 && error != GIT_ITEROVER) - goto done; - error = 0; - - /* insert patterns that had no matches into failures array */ - if (find_failures && used_ct < patterns->length && - (error = pathspec_build_failure_array( - &m->failures, patterns, &used_patterns, &m->pool)) < 0) - goto done; - - /* if every pattern failed to match, then we have failed */ - if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) { - git_error_set(GIT_ERROR_INVALID, "no matching files were found"); - error = GIT_ENOTFOUND; - } - -done: - git_bitvec_free(&used_patterns); - - if (error < 0) { - pathspec_match_free(m); - if (out) *out = NULL; - } - - return error; -} - -static git_iterator_flag_t pathspec_match_iter_flags(uint32_t flags) -{ - git_iterator_flag_t f = 0; - - if ((flags & GIT_PATHSPEC_IGNORE_CASE) != 0) - f |= GIT_ITERATOR_IGNORE_CASE; - else if ((flags & GIT_PATHSPEC_USE_CASE) != 0) - f |= GIT_ITERATOR_DONT_IGNORE_CASE; - - return f; -} - -int git_pathspec_match_workdir( - git_pathspec_match_list **out, - git_repository *repo, - uint32_t flags, - git_pathspec *ps) -{ - git_iterator *iter; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error = 0; - - GIT_ASSERT_ARG(repo); - - iter_opts.flags = pathspec_match_iter_flags(flags); - - if (!(error = git_iterator_for_workdir(&iter, repo, NULL, NULL, &iter_opts))) { - error = pathspec_match_from_iterator(out, iter, flags, ps); - git_iterator_free(iter); - } - - return error; -} - -int git_pathspec_match_index( - git_pathspec_match_list **out, - git_index *index, - uint32_t flags, - git_pathspec *ps) -{ - git_iterator *iter; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error = 0; - - GIT_ASSERT_ARG(index); - - iter_opts.flags = pathspec_match_iter_flags(flags); - - if (!(error = git_iterator_for_index(&iter, git_index_owner(index), index, &iter_opts))) { - error = pathspec_match_from_iterator(out, iter, flags, ps); - git_iterator_free(iter); - } - - return error; -} - -int git_pathspec_match_tree( - git_pathspec_match_list **out, - git_tree *tree, - uint32_t flags, - git_pathspec *ps) -{ - git_iterator *iter; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error = 0; - - GIT_ASSERT_ARG(tree); - - iter_opts.flags = pathspec_match_iter_flags(flags); - - if (!(error = git_iterator_for_tree(&iter, tree, &iter_opts))) { - error = pathspec_match_from_iterator(out, iter, flags, ps); - git_iterator_free(iter); - } - - return error; -} - -int git_pathspec_match_diff( - git_pathspec_match_list **out, - git_diff *diff, - uint32_t flags, - git_pathspec *ps) -{ - int error = 0; - git_pathspec_match_list *m = NULL; - struct pathspec_match_context ctxt; - git_vector *patterns = &ps->pathspec; - bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; - bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; - size_t i, pos, used_ct = 0, found_deltas = 0; - const git_diff_delta *delta, **match; - git_bitvec used_patterns; - - GIT_ASSERT_ARG(diff); - - if (git_bitvec_init(&used_patterns, patterns->length) < 0) - return -1; - - if (out) { - *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_DIFF); - GIT_ERROR_CHECK_ALLOC(m); - } - - pathspec_match_context_init( - &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, - git_diff_is_sorted_icase(diff)); - - git_vector_foreach(&diff->deltas, i, delta) { - /* search for match with delta */ - int result = git_pathspec__match_at( - &pos, patterns, &ctxt, delta->old_file.path, delta->new_file.path); - - /* no matches for this path */ - if (result < 0) - continue; - - /* mark the matched pattern as used */ - used_ct += pathspec_mark_pattern(&used_patterns, pos); - - /* if result was a negative pattern match, then don't list file */ - if (!result) - continue; - - ++found_deltas; - - /* if find_failures is on, check if any later patterns also match */ - if (find_failures && used_ct < patterns->length) - used_ct += pathspec_mark_remaining( - &used_patterns, patterns, &ctxt, pos + 1, - delta->old_file.path, delta->new_file.path); - - /* if only looking at failures, exit early or just continue */ - if (failures_only || !out) { - if (used_ct == patterns->length) - break; - continue; - } - - /* insert matched delta into matches array */ - if (!(match = (const git_diff_delta **)git_array_alloc(m->matches))) { - error = -1; - goto done; - } else { - *match = delta; - } - } - - /* insert patterns that had no matches into failures array */ - if (find_failures && used_ct < patterns->length && - (error = pathspec_build_failure_array( - &m->failures, patterns, &used_patterns, &m->pool)) < 0) - goto done; - - /* if every pattern failed to match, then we have failed */ - if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_deltas) { - git_error_set(GIT_ERROR_INVALID, "no matching deltas were found"); - error = GIT_ENOTFOUND; - } - -done: - git_bitvec_free(&used_patterns); - - if (error < 0) { - pathspec_match_free(m); - if (out) *out = NULL; - } - - return error; -} - -void git_pathspec_match_list_free(git_pathspec_match_list *m) -{ - if (m) - pathspec_match_free(m); -} - -size_t git_pathspec_match_list_entrycount( - const git_pathspec_match_list *m) -{ - return m ? git_array_size(m->matches) : 0; -} - -const char *git_pathspec_match_list_entry( - const git_pathspec_match_list *m, size_t pos) -{ - if (!m || m->datatype != PATHSPEC_DATATYPE_STRINGS || - !git_array_valid_index(m->matches, pos)) - return NULL; - - return *((const char **)git_array_get(m->matches, pos)); -} - -const git_diff_delta *git_pathspec_match_list_diff_entry( - const git_pathspec_match_list *m, size_t pos) -{ - if (!m || m->datatype != PATHSPEC_DATATYPE_DIFF || - !git_array_valid_index(m->matches, pos)) - return NULL; - - return *((const git_diff_delta **)git_array_get(m->matches, pos)); -} - -size_t git_pathspec_match_list_failed_entrycount( - const git_pathspec_match_list *m) -{ - return m ? git_array_size(m->failures) : 0; -} - -const char * git_pathspec_match_list_failed_entry( - const git_pathspec_match_list *m, size_t pos) -{ - char **entry = m ? git_array_get(m->failures, pos) : NULL; - - return entry ? *entry : NULL; -} diff --git a/src/pathspec.h b/src/pathspec.h deleted file mode 100644 index 0256cb927..000000000 --- a/src/pathspec.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_pathspec_h__ -#define INCLUDE_pathspec_h__ - -#include "common.h" - -#include "git2/pathspec.h" -#include "str.h" -#include "vector.h" -#include "pool.h" -#include "array.h" - -/* public compiled pathspec */ -struct git_pathspec { - git_refcount rc; - char *prefix; - git_vector pathspec; - git_pool pool; -}; - -enum { - PATHSPEC_DATATYPE_STRINGS = 0, - PATHSPEC_DATATYPE_DIFF = 1 -}; - -typedef git_array_t(char *) git_pathspec_string_array_t; - -/* public interface to pathspec matching */ -struct git_pathspec_match_list { - git_pathspec *pathspec; - git_array_t(void *) matches; - git_pathspec_string_array_t failures; - git_pool pool; - int datatype; -}; - -/* what is the common non-wildcard prefix for all items in the pathspec */ -extern char *git_pathspec_prefix(const git_strarray *pathspec); - -/* is there anything in the spec that needs to be filtered on */ -extern bool git_pathspec_is_empty(const git_strarray *pathspec); - -/* build a vector of fnmatch patterns to evaluate efficiently */ -extern int git_pathspec__vinit( - git_vector *vspec, const git_strarray *strspec, git_pool *strpool); - -/* free data from the pathspec vector */ -extern void git_pathspec__vfree(git_vector *vspec); - -#define GIT_PATHSPEC_NOMATCH ((size_t)-1) - -/* - * Match a path against the vectorized pathspec. - * The matched pathspec is passed back into the `matched_pathspec` parameter, - * unless it is passed as NULL by the caller. - */ -extern bool git_pathspec__match( - const git_vector *vspec, - const char *path, - bool disable_fnmatch, - bool casefold, - const char **matched_pathspec, - size_t *matched_at); - -/* easy pathspec setup */ - -extern int git_pathspec__init(git_pathspec *ps, const git_strarray *paths); - -extern void git_pathspec__clear(git_pathspec *ps); - -#endif diff --git a/src/pool.c b/src/pool.c deleted file mode 100644 index 16ffa398d..000000000 --- a/src/pool.c +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "pool.h" - -#include "posix.h" -#ifndef GIT_WIN32 -#include -#endif - -struct git_pool_page { - git_pool_page *next; - size_t size; - size_t avail; - GIT_ALIGN(char data[GIT_FLEX_ARRAY], 8); -}; - -static void *pool_alloc_page(git_pool *pool, size_t size); - -#ifndef GIT_DEBUG_POOL - -static size_t system_page_size = 0; - -int git_pool_global_init(void) -{ - if (git__page_size(&system_page_size) < 0) - system_page_size = 4096; - /* allow space for malloc overhead */ - system_page_size -= (2 * sizeof(void *)) + sizeof(git_pool_page); - return 0; -} - -int git_pool_init(git_pool *pool, size_t item_size) -{ - GIT_ASSERT_ARG(pool); - GIT_ASSERT_ARG(item_size >= 1); - - memset(pool, 0, sizeof(git_pool)); - pool->item_size = item_size; - pool->page_size = system_page_size; - - return 0; -} - -void git_pool_clear(git_pool *pool) -{ - git_pool_page *scan, *next; - - for (scan = pool->pages; scan != NULL; scan = next) { - next = scan->next; - git__free(scan); - } - - pool->pages = NULL; -} - -static void *pool_alloc_page(git_pool *pool, size_t size) -{ - git_pool_page *page; - const size_t new_page_size = (size <= pool->page_size) ? pool->page_size : size; - size_t alloc_size; - - if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, new_page_size, sizeof(git_pool_page)) || - !(page = git__malloc(alloc_size))) - return NULL; - - page->size = new_page_size; - page->avail = new_page_size - size; - page->next = pool->pages; - - pool->pages = page; - - return page->data; -} - -static void *pool_alloc(git_pool *pool, size_t size) -{ - git_pool_page *page = pool->pages; - void *ptr = NULL; - - if (!page || page->avail < size) - return pool_alloc_page(pool, size); - - ptr = &page->data[page->size - page->avail]; - page->avail -= size; - - return ptr; -} - -uint32_t git_pool__open_pages(git_pool *pool) -{ - uint32_t ct = 0; - git_pool_page *scan; - for (scan = pool->pages; scan != NULL; scan = scan->next) ct++; - return ct; -} - -bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) -{ - git_pool_page *scan; - for (scan = pool->pages; scan != NULL; scan = scan->next) - if ((void *)scan->data <= ptr && - (void *)(((char *)scan->data) + scan->size) > ptr) - return true; - return false; -} - -#else - -int git_pool_global_init(void) -{ - return 0; -} - -static int git_pool__ptr_cmp(const void * a, const void * b) -{ - if(a > b) { - return 1; - } - if(a < b) { - return -1; - } - else { - return 0; - } -} - -int git_pool_init(git_pool *pool, size_t item_size) -{ - GIT_ASSERT_ARG(pool); - GIT_ASSERT_ARG(item_size >= 1); - - memset(pool, 0, sizeof(git_pool)); - pool->item_size = item_size; - pool->page_size = git_pool__system_page_size(); - git_vector_init(&pool->allocations, 100, git_pool__ptr_cmp); - - return 0; -} - -void git_pool_clear(git_pool *pool) -{ - git_vector_free_deep(&pool->allocations); -} - -static void *pool_alloc(git_pool *pool, size_t size) { - void *ptr = NULL; - if((ptr = git__malloc(size)) == NULL) { - return NULL; - } - git_vector_insert_sorted(&pool->allocations, ptr, NULL); - return ptr; -} - -bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) -{ - size_t pos; - return git_vector_bsearch(&pos, &pool->allocations, ptr) != GIT_ENOTFOUND; -} -#endif - -void git_pool_swap(git_pool *a, git_pool *b) -{ - git_pool temp; - - if (a == b) - return; - - memcpy(&temp, a, sizeof(temp)); - memcpy(a, b, sizeof(temp)); - memcpy(b, &temp, sizeof(temp)); -} - -static size_t alloc_size(git_pool *pool, size_t count) -{ - const size_t align = sizeof(void *) - 1; - - if (pool->item_size > 1) { - const size_t item_size = (pool->item_size + align) & ~align; - return item_size * count; - } - - return (count + align) & ~align; -} - -void *git_pool_malloc(git_pool *pool, size_t items) -{ - return pool_alloc(pool, alloc_size(pool, items)); -} - -void *git_pool_mallocz(git_pool *pool, size_t items) -{ - const size_t size = alloc_size(pool, items); - void *ptr = pool_alloc(pool, size); - if (ptr) - memset(ptr, 0x0, size); - return ptr; -} - -char *git_pool_strndup(git_pool *pool, const char *str, size_t n) -{ - char *ptr = NULL; - - GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(str, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); - - if (n == SIZE_MAX) - return NULL; - - if ((ptr = git_pool_malloc(pool, (n + 1))) != NULL) { - memcpy(ptr, str, n); - ptr[n] = '\0'; - } - - return ptr; -} - -char *git_pool_strdup(git_pool *pool, const char *str) -{ - GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(str, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); - - return git_pool_strndup(pool, str, strlen(str)); -} - -char *git_pool_strdup_safe(git_pool *pool, const char *str) -{ - return str ? git_pool_strdup(pool, str) : NULL; -} - -char *git_pool_strcat(git_pool *pool, const char *a, const char *b) -{ - void *ptr; - size_t len_a, len_b, total; - - GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); - - len_a = a ? strlen(a) : 0; - len_b = b ? strlen(b) : 0; - - if (GIT_ADD_SIZET_OVERFLOW(&total, len_a, len_b) || - GIT_ADD_SIZET_OVERFLOW(&total, total, 1)) - return NULL; - - if ((ptr = git_pool_malloc(pool, total)) != NULL) { - if (len_a) - memcpy(ptr, a, len_a); - if (len_b) - memcpy(((char *)ptr) + len_a, b, len_b); - *(((char *)ptr) + len_a + len_b) = '\0'; - } - return ptr; -} diff --git a/src/pool.h b/src/pool.h deleted file mode 100644 index cecb84665..000000000 --- a/src/pool.h +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_pool_h__ -#define INCLUDE_pool_h__ - -#include "common.h" - -#include "vector.h" - -typedef struct git_pool_page git_pool_page; - -#ifndef GIT_DEBUG_POOL -/** - * Chunked allocator. - * - * A `git_pool` can be used when you want to cheaply allocate - * multiple items of the same type and are willing to free them - * all together with a single call. The two most common cases - * are a set of fixed size items (such as lots of OIDs) or a - * bunch of strings. - * - * Internally, a `git_pool` allocates pages of memory and then - * deals out blocks from the trailing unused portion of each page. - * The pages guarantee that the number of actual allocations done - * will be much smaller than the number of items needed. - * - * For examples of how to set up a `git_pool` see `git_pool_init`. - */ -typedef struct { - git_pool_page *pages; /* allocated pages */ - size_t item_size; /* size of single alloc unit in bytes */ - size_t page_size; /* size of page in bytes */ -} git_pool; - -#define GIT_POOL_INIT { NULL, 0, 0 } - -#else - -/** - * Debug chunked allocator. - * - * Acts just like `git_pool` but instead of actually pooling allocations it - * passes them through to `git__malloc`. This makes it possible to easily debug - * systems that use `git_pool` using valgrind. - * - * In order to track allocations during the lifetime of the pool we use a - * `git_vector`. When the pool is deallocated everything in the vector is - * freed. - * - * `API is exactly the same as the standard `git_pool` with one exception. - * Since we aren't allocating pages to hand out in chunks we can't easily - * implement `git_pool__open_pages`. - */ -typedef struct { - git_vector allocations; - size_t item_size; - size_t page_size; -} git_pool; - -#define GIT_POOL_INIT { GIT_VECTOR_INIT, 0, 0 } - -#endif - -/** - * Initialize a pool. - * - * To allocation strings, use like this: - * - * git_pool_init(&string_pool, 1); - * my_string = git_pool_strdup(&string_pool, your_string); - * - * To allocate items of fixed size, use like this: - * - * git_pool_init(&pool, sizeof(item)); - * my_item = git_pool_malloc(&pool, 1); - * - * Of course, you can use this in other ways, but those are the - * two most common patterns. - */ -extern int git_pool_init(git_pool *pool, size_t item_size); - -/** - * Free all items in pool - */ -extern void git_pool_clear(git_pool *pool); - -/** - * Swap two pools with one another - */ -extern void git_pool_swap(git_pool *a, git_pool *b); - -/** - * Allocate space for one or more items from a pool. - */ -extern void *git_pool_malloc(git_pool *pool, size_t items); -extern void *git_pool_mallocz(git_pool *pool, size_t items); - -/** - * Allocate space and duplicate string data into it. - * - * This is allowed only for pools with item_size == sizeof(char) - */ -extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n); - -/** - * Allocate space and duplicate a string into it. - * - * This is allowed only for pools with item_size == sizeof(char) - */ -extern char *git_pool_strdup(git_pool *pool, const char *str); - -/** - * Allocate space and duplicate a string into it, NULL is no error. - * - * This is allowed only for pools with item_size == sizeof(char) - */ -extern char *git_pool_strdup_safe(git_pool *pool, const char *str); - -/** - * Allocate space for the concatenation of two strings. - * - * This is allowed only for pools with item_size == sizeof(char) - */ -extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b); - -/* - * Misc utilities - */ -#ifndef GIT_DEBUG_POOL -extern uint32_t git_pool__open_pages(git_pool *pool); -#endif -extern bool git_pool__ptr_in_pool(git_pool *pool, void *ptr); - -/** - * This function is being called by our global setup routines to - * initialize the system pool size. - * - * @return 0 on success, <0 on failure - */ -extern int git_pool_global_init(void); - -#endif diff --git a/src/posix.c b/src/posix.c deleted file mode 100644 index b1f85dc94..000000000 --- a/src/posix.c +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "posix.h" - -#include "fs_path.h" -#include -#include - -size_t p_fsync__cnt = 0; - -#ifndef GIT_WIN32 - -#ifdef NO_ADDRINFO - -int p_getaddrinfo( - const char *host, - const char *port, - struct addrinfo *hints, - struct addrinfo **info) -{ - struct addrinfo *ainfo, *ai; - int p = 0; - - GIT_UNUSED(hints); - - if ((ainfo = git__malloc(sizeof(struct addrinfo))) == NULL) - return -1; - - if ((ainfo->ai_hostent = gethostbyname(host)) == NULL) { - git__free(ainfo); - return -2; - } - - ainfo->ai_servent = getservbyname(port, 0); - - if (ainfo->ai_servent) - ainfo->ai_port = ainfo->ai_servent->s_port; - else - ainfo->ai_port = htons(atol(port)); - - memcpy(&ainfo->ai_addr_in.sin_addr, - ainfo->ai_hostent->h_addr_list[0], - ainfo->ai_hostent->h_length); - - ainfo->ai_protocol = 0; - ainfo->ai_socktype = hints->ai_socktype; - ainfo->ai_family = ainfo->ai_hostent->h_addrtype; - ainfo->ai_addr_in.sin_family = ainfo->ai_family; - ainfo->ai_addr_in.sin_port = ainfo->ai_port; - ainfo->ai_addr = (struct addrinfo *)&ainfo->ai_addr_in; - ainfo->ai_addrlen = sizeof(struct sockaddr_in); - - *info = ainfo; - - if (ainfo->ai_hostent->h_addr_list[1] == NULL) { - ainfo->ai_next = NULL; - return 0; - } - - ai = ainfo; - - for (p = 1; ainfo->ai_hostent->h_addr_list[p] != NULL; p++) { - if (!(ai->ai_next = git__malloc(sizeof(struct addrinfo)))) { - p_freeaddrinfo(ainfo); - return -1; - } - memcpy(ai->ai_next, ainfo, sizeof(struct addrinfo)); - memcpy(&ai->ai_next->ai_addr_in.sin_addr, - ainfo->ai_hostent->h_addr_list[p], - ainfo->ai_hostent->h_length); - ai->ai_next->ai_addr = (struct addrinfo *)&ai->ai_next->ai_addr_in; - ai = ai->ai_next; - } - - ai->ai_next = NULL; - return 0; -} - -void p_freeaddrinfo(struct addrinfo *info) -{ - struct addrinfo *p, *next; - - p = info; - - while(p != NULL) { - next = p->ai_next; - git__free(p); - p = next; - } -} - -const char *p_gai_strerror(int ret) -{ - switch(ret) { - case -1: return "Out of memory"; break; - case -2: return "Address lookup failed"; break; - default: return "Unknown error"; break; - } -} - -#endif /* NO_ADDRINFO */ - -int p_open(const char *path, volatile int flags, ...) -{ - mode_t mode = 0; - - #ifdef GIT_DEBUG_STRICT_OPEN - if (strstr(path, "//") != NULL) { - errno = EACCES; - return -1; - } - #endif - - if (flags & O_CREAT) { - va_list arg_list; - - va_start(arg_list, flags); - mode = (mode_t)va_arg(arg_list, int); - va_end(arg_list); - } - - return open(path, flags | O_BINARY | O_CLOEXEC, mode); -} - -int p_creat(const char *path, mode_t mode) -{ - return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC, mode); -} - -int p_getcwd(char *buffer_out, size_t size) -{ - char *cwd_buffer; - - GIT_ASSERT_ARG(buffer_out); - GIT_ASSERT_ARG(size > 0); - - cwd_buffer = getcwd(buffer_out, size); - - if (cwd_buffer == NULL) - return -1; - - git_fs_path_mkposix(buffer_out); - git_fs_path_string_to_dir(buffer_out, size); /* append trailing slash */ - - return 0; -} - -int p_rename(const char *from, const char *to) -{ - if (!link(from, to)) { - p_unlink(from); - return 0; - } - - if (!rename(from, to)) - return 0; - - return -1; -} - -#endif /* GIT_WIN32 */ - -ssize_t p_read(git_file fd, void *buf, size_t cnt) -{ - char *b = buf; - - if (!git__is_ssizet(cnt)) { -#ifdef GIT_WIN32 - SetLastError(ERROR_INVALID_PARAMETER); -#endif - errno = EINVAL; - return -1; - } - - while (cnt) { - ssize_t r; -#ifdef GIT_WIN32 - r = read(fd, b, cnt > INT_MAX ? INT_MAX : (unsigned int)cnt); -#else - r = read(fd, b, cnt); -#endif - if (r < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - return -1; - } - if (!r) - break; - cnt -= r; - b += r; - } - return (b - (char *)buf); -} - -int p_write(git_file fd, const void *buf, size_t cnt) -{ - const char *b = buf; - - while (cnt) { - ssize_t r; -#ifdef GIT_WIN32 - GIT_ASSERT((size_t)((unsigned int)cnt) == cnt); - r = write(fd, b, (unsigned int)cnt); -#else - r = write(fd, b, cnt); -#endif - if (r < 0) { - if (errno == EINTR || GIT_ISBLOCKED(errno)) - continue; - return -1; - } - if (!r) { - errno = EPIPE; - return -1; - } - cnt -= r; - b += r; - } - return 0; -} - -#ifdef NO_MMAP - -#include "map.h" - -int git__page_size(size_t *page_size) -{ - /* dummy; here we don't need any alignment anyway */ - *page_size = 4096; - return 0; -} - -int git__mmap_alignment(size_t *alignment) -{ - /* dummy; here we don't need any alignment anyway */ - *alignment = 4096; - return 0; -} - - -int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) -{ - const char *ptr; - size_t remaining_len; - - GIT_MMAP_VALIDATE(out, len, prot, flags); - - /* writes cannot be emulated without handling pagefaults since write happens by - * writing to mapped memory */ - if (prot & GIT_PROT_WRITE) { - git_error_set(GIT_ERROR_OS, "trying to map %s-writeable", - ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) ? "shared": "private"); - return -1; - } - - if (!git__is_ssizet(len)) { - errno = EINVAL; - return -1; - } - - out->len = 0; - out->data = git__malloc(len); - GIT_ERROR_CHECK_ALLOC(out->data); - - remaining_len = len; - ptr = (const char *)out->data; - while (remaining_len > 0) { - ssize_t nb; - HANDLE_EINTR(nb, p_pread(fd, (void *)ptr, remaining_len, offset)); - if (nb <= 0) { - git_error_set(GIT_ERROR_OS, "mmap emulation failed"); - git__free(out->data); - out->data = NULL; - return -1; - } - - ptr += nb; - offset += nb; - remaining_len -= nb; - } - - out->len = len; - return 0; -} - -int p_munmap(git_map *map) -{ - GIT_ASSERT_ARG(map); - git__free(map->data); - - /* Initializing will help debug use-after-free */ - map->len = 0; - map->data = NULL; - - return 0; -} - -#endif diff --git a/src/posix.h b/src/posix.h deleted file mode 100644 index e6f603078..000000000 --- a/src/posix.h +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_posix_h__ -#define INCLUDE_posix_h__ - -#include "common.h" - -#include -#include -#include - -/* stat: file mode type testing macros */ -#ifndef S_IFGITLINK -#define S_IFGITLINK 0160000 -#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) -#endif - -#ifndef S_IFLNK -#define S_IFLNK 0120000 -#undef _S_IFLNK -#define _S_IFLNK S_IFLNK -#endif - -#ifndef S_IWUSR -#define S_IWUSR 00200 -#endif - -#ifndef S_IXUSR -#define S_IXUSR 00100 -#endif - -#ifndef S_ISLNK -#define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) -#endif - -#ifndef S_ISDIR -#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) -#endif - -#ifndef S_ISREG -#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) -#endif - -#ifndef S_ISFIFO -#define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO) -#endif - -/* if S_ISGID is not defined, then don't try to set it */ -#ifndef S_ISGID -#define S_ISGID 0 -#endif - -#ifndef O_BINARY -#define O_BINARY 0 -#endif -#ifndef O_CLOEXEC -#define O_CLOEXEC 0 -#endif -#ifndef SOCK_CLOEXEC -#define SOCK_CLOEXEC 0 -#endif - -/* access() mode parameter #defines */ -#ifndef F_OK -#define F_OK 0 /* existence check */ -#endif -#ifndef W_OK -#define W_OK 2 /* write mode check */ -#endif -#ifndef R_OK -#define R_OK 4 /* read mode check */ -#endif - -/* Determine whether an errno value indicates that a read or write failed - * because the descriptor is blocked. - */ -#if defined(EWOULDBLOCK) -#define GIT_ISBLOCKED(e) ((e) == EAGAIN || (e) == EWOULDBLOCK) -#else -#define GIT_ISBLOCKED(e) ((e) == EAGAIN) -#endif - -/* define some standard errnos that the runtime may be missing. for example, - * mingw lacks EAFNOSUPPORT. */ -#ifndef EAFNOSUPPORT -#define EAFNOSUPPORT (INT_MAX-1) -#endif - -/* Compiler independent macro to handle signal interrpted system calls */ -#define HANDLE_EINTR(result, x) do { \ - result = (x); \ - } while (result == -1 && errno == EINTR); - - -/* Provide a 64-bit size for offsets. */ - -#if defined(_MSC_VER) -typedef __int64 off64_t; -#elif defined(__HAIKU__) -typedef __haiku_std_int64 off64_t; -#elif defined(__APPLE__) -typedef __int64_t off64_t; -#else -typedef int64_t off64_t; -#endif - -typedef int git_file; - -/** - * Standard POSIX Methods - * - * All the methods starting with the `p_` prefix are - * direct ports of the standard POSIX methods. - * - * Some of the methods are slightly wrapped to provide - * saner defaults. Some of these methods are emulated - * in Windows platforms. - * - * Use your manpages to check the docs on these. - */ - -extern ssize_t p_read(git_file fd, void *buf, size_t cnt); -extern int p_write(git_file fd, const void *buf, size_t cnt); - -extern ssize_t p_pread(int fd, void *data, size_t size, off64_t offset); -extern ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset); - -#define p_close(fd) close(fd) -#define p_umask(m) umask(m) - -extern int p_open(const char *path, int flags, ...); -extern int p_creat(const char *path, mode_t mode); -extern int p_getcwd(char *buffer_out, size_t size); -extern int p_rename(const char *from, const char *to); - -extern int git__page_size(size_t *page_size); -extern int git__mmap_alignment(size_t *page_size); - -/* The number of times `p_fsync` has been called. Note that this is for - * test code only; it it not necessarily thread-safe and should not be - * relied upon in production. - */ -extern size_t p_fsync__cnt; - -/** - * Platform-dependent methods - */ -#ifdef GIT_WIN32 -# include "win32/posix.h" -#else -# include "unix/posix.h" -#endif - -#include "strnlen.h" - -#ifdef NO_READDIR_R -GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) -{ - GIT_UNUSED(entry); - *result = readdir(dirp); - return 0; -} -#else /* NO_READDIR_R */ -# define p_readdir_r(d,e,r) readdir_r(d,e,r) -#endif - -#ifdef NO_ADDRINFO -# include -struct addrinfo { - struct hostent *ai_hostent; - struct servent *ai_servent; - struct sockaddr_in ai_addr_in; - struct sockaddr *ai_addr; - size_t ai_addrlen; - int ai_family; - int ai_socktype; - int ai_protocol; - long ai_port; - struct addrinfo *ai_next; -}; - -extern int p_getaddrinfo(const char *host, const char *port, - struct addrinfo *hints, struct addrinfo **info); -extern void p_freeaddrinfo(struct addrinfo *info); -extern const char *p_gai_strerror(int ret); -#else -# define p_getaddrinfo(a, b, c, d) getaddrinfo(a, b, c, d) -# define p_freeaddrinfo(a) freeaddrinfo(a) -# define p_gai_strerror(c) gai_strerror(c) -#endif /* NO_ADDRINFO */ - -#endif diff --git a/src/pqueue.c b/src/pqueue.c deleted file mode 100644 index 3820e999c..000000000 --- a/src/pqueue.c +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "pqueue.h" - -#include "util.h" - -#define PQUEUE_LCHILD_OF(I) (((I)<<1)+1) -#define PQUEUE_RCHILD_OF(I) (((I)<<1)+2) -#define PQUEUE_PARENT_OF(I) (((I)-1)>>1) - -int git_pqueue_init( - git_pqueue *pq, - uint32_t flags, - size_t init_size, - git_vector_cmp cmp) -{ - int error = git_vector_init(pq, init_size, cmp); - - if (!error) { - /* mix in our flags */ - pq->flags |= flags; - - /* if fixed size heap, pretend vector is exactly init_size elements */ - if ((flags & GIT_PQUEUE_FIXED_SIZE) && init_size > 0) - pq->_alloc_size = init_size; - } - - return error; -} - -static void pqueue_up(git_pqueue *pq, size_t el) -{ - size_t parent_el = PQUEUE_PARENT_OF(el); - void *kid = git_vector_get(pq, el); - - while (el > 0) { - void *parent = pq->contents[parent_el]; - - if (pq->_cmp(parent, kid) <= 0) - break; - - pq->contents[el] = parent; - - el = parent_el; - parent_el = PQUEUE_PARENT_OF(el); - } - - pq->contents[el] = kid; -} - -static void pqueue_down(git_pqueue *pq, size_t el) -{ - void *parent = git_vector_get(pq, el), *kid, *rkid; - - while (1) { - size_t kid_el = PQUEUE_LCHILD_OF(el); - - if ((kid = git_vector_get(pq, kid_el)) == NULL) - break; - - if ((rkid = git_vector_get(pq, kid_el + 1)) != NULL && - pq->_cmp(kid, rkid) > 0) { - kid = rkid; - kid_el += 1; - } - - if (pq->_cmp(parent, kid) <= 0) - break; - - pq->contents[el] = kid; - el = kid_el; - } - - pq->contents[el] = parent; -} - -int git_pqueue_insert(git_pqueue *pq, void *item) -{ - int error = 0; - - /* if heap is full, pop the top element if new one should replace it */ - if ((pq->flags & GIT_PQUEUE_FIXED_SIZE) != 0 && - pq->length >= pq->_alloc_size) - { - /* skip this item if below min item in heap or if - * we do not have a comparison function */ - if (!pq->_cmp || pq->_cmp(item, git_vector_get(pq, 0)) <= 0) - return 0; - /* otherwise remove the min item before inserting new */ - (void)git_pqueue_pop(pq); - } - - if (!(error = git_vector_insert(pq, item)) && pq->_cmp) - pqueue_up(pq, pq->length - 1); - - return error; -} - -void *git_pqueue_pop(git_pqueue *pq) -{ - void *rval; - - if (!pq->_cmp) { - rval = git_vector_last(pq); - } else { - rval = git_pqueue_get(pq, 0); - } - - if (git_pqueue_size(pq) > 1 && pq->_cmp) { - /* move last item to top of heap, shrink, and push item down */ - pq->contents[0] = git_vector_last(pq); - git_vector_pop(pq); - pqueue_down(pq, 0); - } else { - /* all we need to do is shrink the heap in this case */ - git_vector_pop(pq); - } - - return rval; -} diff --git a/src/pqueue.h b/src/pqueue.h deleted file mode 100644 index 4db74ea03..000000000 --- a/src/pqueue.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_pqueue_h__ -#define INCLUDE_pqueue_h__ - -#include "common.h" - -#include "vector.h" - -typedef git_vector git_pqueue; - -enum { - /* flag meaning: don't grow heap, keep highest values only */ - GIT_PQUEUE_FIXED_SIZE = (GIT_VECTOR_FLAG_MAX << 1) -}; - -/** - * Initialize priority queue - * - * @param pq The priority queue struct to initialize - * @param flags Flags (see above) to control queue behavior - * @param init_size The initial queue size - * @param cmp The entry priority comparison function - * @return 0 on success, <0 on error - */ -extern int git_pqueue_init( - git_pqueue *pq, - uint32_t flags, - size_t init_size, - git_vector_cmp cmp); - -#define git_pqueue_free git_vector_free -#define git_pqueue_clear git_vector_clear -#define git_pqueue_size git_vector_length -#define git_pqueue_get git_vector_get -#define git_pqueue_reverse git_vector_reverse - -/** - * Insert a new item into the queue - * - * @param pq The priority queue - * @param item Pointer to the item data - * @return 0 on success, <0 on failure - */ -extern int git_pqueue_insert(git_pqueue *pq, void *item); - -/** - * Remove the top item in the priority queue - * - * @param pq The priority queue - * @return item from heap on success, NULL if queue is empty - */ -extern void *git_pqueue_pop(git_pqueue *pq); - -#endif diff --git a/src/proxy.c b/src/proxy.c deleted file mode 100644 index ef91ad6ea..000000000 --- a/src/proxy.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "proxy.h" - -#include "git2/proxy.h" - -int git_proxy_options_init(git_proxy_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_proxy_options, GIT_PROXY_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_proxy_init_options(git_proxy_options *opts, unsigned int version) -{ - return git_proxy_options_init(opts, version); -} -#endif - -int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src) -{ - if (!src) { - git_proxy_options_init(tgt, GIT_PROXY_OPTIONS_VERSION); - return 0; - } - - memcpy(tgt, src, sizeof(git_proxy_options)); - if (src->url) { - tgt->url = git__strdup(src->url); - GIT_ERROR_CHECK_ALLOC(tgt->url); - } - - return 0; -} - -void git_proxy_options_dispose(git_proxy_options *opts) -{ - if (!opts) - return; - - git__free((char *) opts->url); - opts->url = NULL; -} diff --git a/src/proxy.h b/src/proxy.h deleted file mode 100644 index 7c0ab598d..000000000 --- a/src/proxy.h +++ /dev/null @@ -1,17 +0,0 @@ -/* -* Copyright (C) the libgit2 contributors. All rights reserved. -* -* This file is part of libgit2, distributed under the GNU GPL v2 with -* a Linking Exception. For full terms see the included COPYING file. -*/ -#ifndef INCLUDE_proxy_h__ -#define INCLUDE_proxy_h__ - -#include "common.h" - -#include "git2/proxy.h" - -extern int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src); -extern void git_proxy_options_dispose(git_proxy_options *opts); - -#endif diff --git a/src/push.c b/src/push.c deleted file mode 100644 index da8aebadd..000000000 --- a/src/push.c +++ /dev/null @@ -1,558 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "push.h" - -#include "git2.h" - -#include "pack.h" -#include "pack-objects.h" -#include "remote.h" -#include "vector.h" -#include "tree.h" - -static int push_spec_rref_cmp(const void *a, const void *b) -{ - const push_spec *push_spec_a = a, *push_spec_b = b; - - return strcmp(push_spec_a->refspec.dst, push_spec_b->refspec.dst); -} - -static int push_status_ref_cmp(const void *a, const void *b) -{ - const push_status *push_status_a = a, *push_status_b = b; - - return strcmp(push_status_a->ref, push_status_b->ref); -} - -int git_push_new(git_push **out, git_remote *remote, const git_push_options *opts) -{ - git_push *p; - - *out = NULL; - - GIT_ERROR_CHECK_VERSION(opts, GIT_PUSH_OPTIONS_VERSION, "git_push_options"); - - p = git__calloc(1, sizeof(*p)); - GIT_ERROR_CHECK_ALLOC(p); - - p->repo = remote->repo; - p->remote = remote; - p->report_status = 1; - p->pb_parallelism = opts ? opts->pb_parallelism : 1; - - if (opts) { - GIT_ERROR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); - memcpy(&p->callbacks, &opts->callbacks, sizeof(git_remote_callbacks)); - } - - if (git_vector_init(&p->specs, 0, push_spec_rref_cmp) < 0) { - git__free(p); - return -1; - } - - if (git_vector_init(&p->status, 0, push_status_ref_cmp) < 0) { - git_vector_free(&p->specs); - git__free(p); - return -1; - } - - if (git_vector_init(&p->updates, 0, NULL) < 0) { - git_vector_free(&p->status); - git_vector_free(&p->specs); - git__free(p); - return -1; - } - - *out = p; - return 0; -} - -static void free_refspec(push_spec *spec) -{ - if (spec == NULL) - return; - - git_refspec__dispose(&spec->refspec); - git__free(spec); -} - -static int check_rref(char *ref) -{ - if (git__prefixcmp(ref, "refs/")) { - git_error_set(GIT_ERROR_INVALID, "not a valid reference '%s'", ref); - return -1; - } - - return 0; -} - -static int check_lref(git_push *push, char *ref) -{ - /* lref must be resolvable to an existing object */ - git_object *obj; - int error = git_revparse_single(&obj, push->repo, ref); - git_object_free(obj); - - if (!error) - return 0; - - if (error == GIT_ENOTFOUND) - git_error_set(GIT_ERROR_REFERENCE, - "src refspec '%s' does not match any existing object", ref); - else - git_error_set(GIT_ERROR_INVALID, "not a valid reference '%s'", ref); - return -1; -} - -static int parse_refspec(git_push *push, push_spec **spec, const char *str) -{ - push_spec *s; - - *spec = NULL; - - s = git__calloc(1, sizeof(*s)); - GIT_ERROR_CHECK_ALLOC(s); - - if (git_refspec__parse(&s->refspec, str, false) < 0) { - git_error_set(GIT_ERROR_INVALID, "invalid refspec %s", str); - goto on_error; - } - - if (s->refspec.src && s->refspec.src[0] != '\0' && - check_lref(push, s->refspec.src) < 0) { - goto on_error; - } - - if (check_rref(s->refspec.dst) < 0) - goto on_error; - - *spec = s; - return 0; - -on_error: - free_refspec(s); - return -1; -} - -int git_push_add_refspec(git_push *push, const char *refspec) -{ - push_spec *spec; - - if (parse_refspec(push, &spec, refspec) < 0 || - git_vector_insert(&push->specs, spec) < 0) - return -1; - - return 0; -} - -int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks) -{ - git_str remote_ref_name = GIT_STR_INIT; - size_t i, j; - git_refspec *fetch_spec; - push_spec *push_spec = NULL; - git_reference *remote_ref; - push_status *status; - int error = 0; - - git_vector_foreach(&push->status, i, status) { - int fire_callback = 1; - - /* Skip unsuccessful updates which have non-empty messages */ - if (status->msg) - continue; - - /* Find the corresponding remote ref */ - fetch_spec = git_remote__matching_refspec(push->remote, status->ref); - if (!fetch_spec) - continue; - - /* Clear the buffer which can be dirty from previous iteration */ - git_str_clear(&remote_ref_name); - - if ((error = git_refspec__transform(&remote_ref_name, fetch_spec, status->ref)) < 0) - goto on_error; - - /* Find matching push ref spec */ - git_vector_foreach(&push->specs, j, push_spec) { - if (!strcmp(push_spec->refspec.dst, status->ref)) - break; - } - - /* Could not find the corresponding push ref spec for this push update */ - if (j == push->specs.length) - continue; - - /* Update the remote ref */ - if (git_oid_is_zero(&push_spec->loid)) { - error = git_reference_lookup(&remote_ref, push->remote->repo, git_str_cstr(&remote_ref_name)); - - if (error >= 0) { - error = git_reference_delete(remote_ref); - git_reference_free(remote_ref); - } - } else { - error = git_reference_create(NULL, push->remote->repo, - git_str_cstr(&remote_ref_name), &push_spec->loid, 1, - "update by push"); - } - - if (error < 0) { - if (error != GIT_ENOTFOUND) - goto on_error; - - git_error_clear(); - fire_callback = 0; - } - - if (fire_callback && callbacks && callbacks->update_tips) { - error = callbacks->update_tips(git_str_cstr(&remote_ref_name), - &push_spec->roid, &push_spec->loid, callbacks->payload); - - if (error < 0) - goto on_error; - } - } - - error = 0; - -on_error: - git_str_dispose(&remote_ref_name); - return error; -} - -/** - * Insert all tags until we find a non-tag object, which is returned - * in `out`. - */ -static int enqueue_tag(git_object **out, git_push *push, git_oid *id) -{ - git_object *obj = NULL, *target = NULL; - int error; - - if ((error = git_object_lookup(&obj, push->repo, id, GIT_OBJECT_TAG)) < 0) - return error; - - while (git_object_type(obj) == GIT_OBJECT_TAG) { - if ((error = git_packbuilder_insert(push->pb, git_object_id(obj), NULL)) < 0) - break; - - if ((error = git_tag_target(&target, (git_tag *) obj)) < 0) - break; - - git_object_free(obj); - obj = target; - } - - if (error < 0) - git_object_free(obj); - else - *out = obj; - - return error; -} - -static int queue_objects(git_push *push) -{ - git_remote_head *head; - push_spec *spec; - git_revwalk *rw; - unsigned int i; - int error = -1; - - if (git_revwalk_new(&rw, push->repo) < 0) - return -1; - - git_revwalk_sorting(rw, GIT_SORT_TIME); - - git_vector_foreach(&push->specs, i, spec) { - git_object_t type; - size_t size; - - if (git_oid_is_zero(&spec->loid)) - /* - * Delete reference on remote side; - * nothing to do here. - */ - continue; - - if (git_oid_equal(&spec->loid, &spec->roid)) - continue; /* up-to-date */ - - if ((error = git_odb_read_header(&size, &type, push->repo->_odb, &spec->loid)) < 0) - goto on_error; - - if (type == GIT_OBJECT_TAG) { - git_object *target; - - if ((error = enqueue_tag(&target, push, &spec->loid)) < 0) - goto on_error; - - if (git_object_type(target) == GIT_OBJECT_COMMIT) { - if ((error = git_revwalk_push(rw, git_object_id(target))) < 0) { - git_object_free(target); - goto on_error; - } - } else { - if ((error = git_packbuilder_insert( - push->pb, git_object_id(target), NULL)) < 0) { - git_object_free(target); - goto on_error; - } - } - git_object_free(target); - } else if ((error = git_revwalk_push(rw, &spec->loid)) < 0) - goto on_error; - - if (!spec->refspec.force) { - git_oid base; - - if (git_oid_is_zero(&spec->roid)) - continue; - - if (!git_odb_exists(push->repo->_odb, &spec->roid)) { - git_error_set(GIT_ERROR_REFERENCE, - "cannot push because a reference that you are trying to update on the remote contains commits that are not present locally."); - error = GIT_ENONFASTFORWARD; - goto on_error; - } - - error = git_merge_base(&base, push->repo, - &spec->loid, &spec->roid); - - if (error == GIT_ENOTFOUND || - (!error && !git_oid_equal(&base, &spec->roid))) { - git_error_set(GIT_ERROR_REFERENCE, - "cannot push non-fastforwardable reference"); - error = GIT_ENONFASTFORWARD; - goto on_error; - } - - if (error < 0) - goto on_error; - } - } - - git_vector_foreach(&push->remote->refs, i, head) { - if (git_oid_is_zero(&head->oid)) - continue; - - if ((error = git_revwalk_hide(rw, &head->oid)) < 0 && - error != GIT_ENOTFOUND && error != GIT_EINVALIDSPEC && error != GIT_EPEEL) - goto on_error; - } - - error = git_packbuilder_insert_walk(push->pb, rw); - -on_error: - git_revwalk_free(rw); - return error; -} - -static int add_update(git_push *push, push_spec *spec) -{ - git_push_update *u = git__calloc(1, sizeof(git_push_update)); - GIT_ERROR_CHECK_ALLOC(u); - - u->src_refname = git__strdup(spec->refspec.src); - GIT_ERROR_CHECK_ALLOC(u->src_refname); - - u->dst_refname = git__strdup(spec->refspec.dst); - GIT_ERROR_CHECK_ALLOC(u->dst_refname); - - git_oid_cpy(&u->src, &spec->roid); - git_oid_cpy(&u->dst, &spec->loid); - - return git_vector_insert(&push->updates, u); -} - -static int calculate_work(git_push *push) -{ - git_remote_head *head; - push_spec *spec; - unsigned int i, j; - - /* Update local and remote oids*/ - - git_vector_foreach(&push->specs, i, spec) { - if (spec->refspec.src && spec->refspec.src[0]!= '\0') { - /* This is a create or update. Local ref must exist. */ - if (git_reference_name_to_id( - &spec->loid, push->repo, spec->refspec.src) < 0) { - git_error_set(GIT_ERROR_REFERENCE, "no such reference '%s'", spec->refspec.src); - return -1; - } - } - - /* Remote ref may or may not (e.g. during create) already exist. */ - git_vector_foreach(&push->remote->refs, j, head) { - if (!strcmp(spec->refspec.dst, head->name)) { - git_oid_cpy(&spec->roid, &head->oid); - break; - } - } - - if (add_update(push, spec) < 0) - return -1; - } - - return 0; -} - -static int do_push(git_push *push) -{ - int error = 0; - git_transport *transport = push->remote->transport; - git_remote_callbacks *callbacks = &push->callbacks; - - if (!transport->push) { - git_error_set(GIT_ERROR_NET, "remote transport doesn't support push"); - error = -1; - goto on_error; - } - - /* - * A pack-file MUST be sent if either create or update command - * is used, even if the server already has all the necessary - * objects. In this case the client MUST send an empty pack-file. - */ - - if ((error = git_packbuilder_new(&push->pb, push->repo)) < 0) - goto on_error; - - git_packbuilder_set_threads(push->pb, push->pb_parallelism); - - if (callbacks && callbacks->pack_progress) - if ((error = git_packbuilder_set_callbacks(push->pb, callbacks->pack_progress, callbacks->payload)) < 0) - goto on_error; - - if ((error = calculate_work(push)) < 0) - goto on_error; - - if (callbacks && callbacks->push_negotiation && - (error = callbacks->push_negotiation((const git_push_update **) push->updates.contents, - push->updates.length, callbacks->payload)) < 0) - goto on_error; - - if ((error = queue_objects(push)) < 0 || - (error = transport->push(transport, push)) < 0) - goto on_error; - -on_error: - git_packbuilder_free(push->pb); - return error; -} - -static int filter_refs(git_remote *remote) -{ - const git_remote_head **heads; - size_t heads_len, i; - - git_vector_clear(&remote->refs); - - if (git_remote_ls(&heads, &heads_len, remote) < 0) - return -1; - - for (i = 0; i < heads_len; i++) { - if (git_vector_insert(&remote->refs, (void *)heads[i]) < 0) - return -1; - } - - return 0; -} - -int git_push_finish(git_push *push) -{ - int error; - - if (!git_remote_connected(push->remote)) { - git_error_set(GIT_ERROR_NET, "remote is disconnected"); - return -1; - } - - if ((error = filter_refs(push->remote)) < 0 || - (error = do_push(push)) < 0) - return error; - - if (!push->unpack_ok) { - error = -1; - git_error_set(GIT_ERROR_NET, "unpacking the sent packfile failed on the remote"); - } - - return error; -} - -int git_push_status_foreach(git_push *push, - int (*cb)(const char *ref, const char *msg, void *data), - void *data) -{ - push_status *status; - unsigned int i; - - git_vector_foreach(&push->status, i, status) { - int error = cb(status->ref, status->msg, data); - if (error) - return git_error_set_after_callback(error); - } - - return 0; -} - -void git_push_status_free(push_status *status) -{ - if (status == NULL) - return; - - git__free(status->msg); - git__free(status->ref); - git__free(status); -} - -void git_push_free(git_push *push) -{ - push_spec *spec; - push_status *status; - git_push_update *update; - unsigned int i; - - if (push == NULL) - return; - - git_vector_foreach(&push->specs, i, spec) { - free_refspec(spec); - } - git_vector_free(&push->specs); - - git_vector_foreach(&push->status, i, status) { - git_push_status_free(status); - } - git_vector_free(&push->status); - - git_vector_foreach(&push->updates, i, update) { - git__free(update->src_refname); - git__free(update->dst_refname); - git__free(update); - } - git_vector_free(&push->updates); - - git__free(push); -} - -int git_push_options_init(git_push_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_push_options, GIT_PUSH_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_push_init_options(git_push_options *opts, unsigned int version) -{ - return git_push_options_init(opts, version); -} -#endif diff --git a/src/push.h b/src/push.h deleted file mode 100644 index fc72e845e..000000000 --- a/src/push.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_push_h__ -#define INCLUDE_push_h__ - -#include "common.h" - -#include "git2.h" -#include "refspec.h" -#include "remote.h" - -typedef struct push_spec { - struct git_refspec refspec; - - git_oid loid; - git_oid roid; -} push_spec; - -typedef struct push_status { - bool ok; - - char *ref; - char *msg; -} push_status; - -struct git_push { - git_repository *repo; - git_packbuilder *pb; - git_remote *remote; - git_vector specs; - git_vector updates; - bool report_status; - - /* report-status */ - bool unpack_ok; - git_vector status; - - /* options */ - unsigned pb_parallelism; - git_remote_callbacks callbacks; -}; - -/** - * Free the given push status object - * - * @param status The push status object - */ -void git_push_status_free(push_status *status); - -/** - * Create a new push object - * - * @param out New push object - * @param remote Remote instance - * @param opts Push options or NULL - * - * @return 0 or an error code - */ -int git_push_new(git_push **out, git_remote *remote, const git_push_options *opts); - -/** - * Add a refspec to be pushed - * - * @param push The push object - * @param refspec Refspec string - * - * @return 0 or an error code - */ -int git_push_add_refspec(git_push *push, const char *refspec); - -/** - * Update remote tips after a push - * - * @param push The push object - * @param callbacks the callbacks to use for this connection - * - * @return 0 or an error code - */ -int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks); - -/** - * Perform the push - * - * This function will return an error in case of a protocol error or - * the server being unable to unpack the data we sent. - * - * The return value does not reflect whether the server accepted or - * refused any reference updates. Use `git_push_status_foreach()` in - * order to find out which updates were accepted or rejected. - * - * @param push The push object - * - * @return 0 or an error code - */ -int git_push_finish(git_push *push); - -/** - * Invoke callback `cb' on each status entry - * - * For each of the updated references, we receive a status report in the - * form of `ok refs/heads/master` or `ng refs/heads/master `. - * `msg != NULL` means the reference has not been updated for the given - * reason. - * - * Return a non-zero value from the callback to stop the loop. - * - * @param push The push object - * @param cb The callback to call on each object - * @param data The payload passed to the callback - * - * @return 0 on success, non-zero callback return value, or error code - */ -int git_push_status_foreach(git_push *push, - int (*cb)(const char *ref, const char *msg, void *data), - void *data); - -/** - * Free the given push object - * - * @param push The push object - */ -void git_push_free(git_push *push); - -#endif diff --git a/src/reader.c b/src/reader.c deleted file mode 100644 index ba9775240..000000000 --- a/src/reader.c +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "reader.h" - -#include "futils.h" -#include "blob.h" - -#include "git2/tree.h" -#include "git2/blob.h" -#include "git2/index.h" -#include "git2/repository.h" - -/* tree reader */ - -typedef struct { - git_reader reader; - git_tree *tree; -} tree_reader; - -static int tree_reader_read( - git_str *out, - git_oid *out_id, - git_filemode_t *out_filemode, - git_reader *_reader, - const char *filename) -{ - tree_reader *reader = (tree_reader *)_reader; - git_tree_entry *tree_entry = NULL; - git_blob *blob = NULL; - git_object_size_t blobsize; - int error; - - if ((error = git_tree_entry_bypath(&tree_entry, reader->tree, filename)) < 0 || - (error = git_blob_lookup(&blob, git_tree_owner(reader->tree), git_tree_entry_id(tree_entry))) < 0) - goto done; - - blobsize = git_blob_rawsize(blob); - GIT_ERROR_CHECK_BLOBSIZE(blobsize); - - if ((error = git_str_set(out, git_blob_rawcontent(blob), (size_t)blobsize)) < 0) - goto done; - - if (out_id) - git_oid_cpy(out_id, git_tree_entry_id(tree_entry)); - - if (out_filemode) - *out_filemode = git_tree_entry_filemode(tree_entry); - -done: - git_blob_free(blob); - git_tree_entry_free(tree_entry); - return error; -} - -int git_reader_for_tree(git_reader **out, git_tree *tree) -{ - tree_reader *reader; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(tree); - - reader = git__calloc(1, sizeof(tree_reader)); - GIT_ERROR_CHECK_ALLOC(reader); - - reader->reader.read = tree_reader_read; - reader->tree = tree; - - *out = (git_reader *)reader; - return 0; -} - -/* workdir reader */ - -typedef struct { - git_reader reader; - git_repository *repo; - git_index *index; -} workdir_reader; - -static int workdir_reader_read( - git_str *out, - git_oid *out_id, - git_filemode_t *out_filemode, - git_reader *_reader, - const char *filename) -{ - workdir_reader *reader = (workdir_reader *)_reader; - git_str path = GIT_STR_INIT; - struct stat st; - git_filemode_t filemode; - git_filter_list *filters = NULL; - const git_index_entry *idx_entry; - git_oid id; - int error; - - if ((error = git_repository_workdir_path(&path, reader->repo, filename)) < 0) - goto done; - - if ((error = p_lstat(path.ptr, &st)) < 0) { - if (error == -1 && errno == ENOENT) - error = GIT_ENOTFOUND; - - git_error_set(GIT_ERROR_OS, "could not stat '%s'", path.ptr); - goto done; - } - - filemode = git_futils_canonical_mode(st.st_mode); - - /* - * Patch application - for example - uses the filtered version of - * the working directory data to match git. So we will run the - * workdir -> ODB filter on the contents in this workdir reader. - */ - if ((error = git_filter_list_load(&filters, reader->repo, NULL, filename, - GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT)) < 0) - goto done; - - if ((error = git_filter_list__apply_to_file(out, - filters, reader->repo, path.ptr)) < 0) - goto done; - - if (out_id || reader->index) { - if ((error = git_odb_hash(&id, out->ptr, out->size, GIT_OBJECT_BLOB)) < 0) - goto done; - } - - if (reader->index) { - if (!(idx_entry = git_index_get_bypath(reader->index, filename, 0)) || - filemode != idx_entry->mode || - !git_oid_equal(&id, &idx_entry->id)) { - error = GIT_READER_MISMATCH; - goto done; - } - } - - if (out_id) - git_oid_cpy(out_id, &id); - - if (out_filemode) - *out_filemode = filemode; - -done: - git_filter_list_free(filters); - git_str_dispose(&path); - return error; -} - -int git_reader_for_workdir( - git_reader **out, - git_repository *repo, - bool validate_index) -{ - workdir_reader *reader; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - reader = git__calloc(1, sizeof(workdir_reader)); - GIT_ERROR_CHECK_ALLOC(reader); - - reader->reader.read = workdir_reader_read; - reader->repo = repo; - - if (validate_index && - (error = git_repository_index__weakptr(&reader->index, repo)) < 0) { - git__free(reader); - return error; - } - - *out = (git_reader *)reader; - return 0; -} - -/* index reader */ - -typedef struct { - git_reader reader; - git_repository *repo; - git_index *index; -} index_reader; - -static int index_reader_read( - git_str *out, - git_oid *out_id, - git_filemode_t *out_filemode, - git_reader *_reader, - const char *filename) -{ - index_reader *reader = (index_reader *)_reader; - const git_index_entry *entry; - git_blob *blob; - int error; - - if ((entry = git_index_get_bypath(reader->index, filename, 0)) == NULL) - return GIT_ENOTFOUND; - - if ((error = git_blob_lookup(&blob, reader->repo, &entry->id)) < 0) - goto done; - - if (out_id) - git_oid_cpy(out_id, &entry->id); - - if (out_filemode) - *out_filemode = entry->mode; - - error = git_blob__getbuf(out, blob); - -done: - git_blob_free(blob); - return error; -} - -int git_reader_for_index( - git_reader **out, - git_repository *repo, - git_index *index) -{ - index_reader *reader; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - reader = git__calloc(1, sizeof(index_reader)); - GIT_ERROR_CHECK_ALLOC(reader); - - reader->reader.read = index_reader_read; - reader->repo = repo; - - if (index) { - reader->index = index; - } else if ((error = git_repository_index__weakptr(&reader->index, repo)) < 0) { - git__free(reader); - return error; - } - - *out = (git_reader *)reader; - return 0; -} - -/* generic */ - -int git_reader_read( - git_str *out, - git_oid *out_id, - git_filemode_t *out_filemode, - git_reader *reader, - const char *filename) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(reader); - GIT_ASSERT_ARG(filename); - - return reader->read(out, out_id, out_filemode, reader, filename); -} - -void git_reader_free(git_reader *reader) -{ - if (!reader) - return; - - git__free(reader); -} diff --git a/src/reader.h b/src/reader.h deleted file mode 100644 index b58dc93f6..000000000 --- a/src/reader.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_reader_h__ -#define INCLUDE_reader_h__ - -#include "common.h" - -/* Returned when the workdir does not match the index */ -#define GIT_READER_MISMATCH 1 - -typedef struct git_reader git_reader; - -/* - * The `git_reader` structure is a generic interface for reading the - * contents of a file by its name, and implementations are provided - * for reading out of a tree, the index, and the working directory. - * - * Note that the reader implementation is meant to have a short - * lifecycle and does not increase the refcount of the object that - * it's reading. Callers should ensure that they do not use a - * reader after disposing the underlying object that it reads. - */ -struct git_reader { - int (*read)(git_str *out, git_oid *out_oid, git_filemode_t *mode, git_reader *reader, const char *filename); -}; - -/** - * Create a `git_reader` that will allow random access to the given - * tree. Paths requested via `git_reader_read` will be rooted at this - * tree, callers are not expected to recurse through tree lookups. Thus, - * you can request to read `/src/foo.c` and the tree provided to this - * function will be searched to find another tree named `src`, which - * will then be opened to find `foo.c`. - * - * @param out The reader for the given tree - * @param tree The tree object to read - * @return 0 on success, or an error code < 0 - */ -extern int git_reader_for_tree( - git_reader **out, - git_tree *tree); - -/** - * Create a `git_reader` that will allow random access to the given - * index, or the repository's index. - * - * @param out The reader for the given index - * @param repo The repository containing the index - * @param index The index to read, or NULL to use the repository's index - * @return 0 on success, or an error code < 0 - */ -extern int git_reader_for_index( - git_reader **out, - git_repository *repo, - git_index *index); - -/** - * Create a `git_reader` that will allow random access to the given - * repository's working directory. Note that the contents are read - * in repository format, meaning any workdir -> odb filters are - * applied. - * - * If `validate_index` is set to true, reads of files will hash the - * on-disk contents and ensure that the resulting object ID matches - * the repository's index. This ensures that the working directory - * is unmodified from the index contents. - * - * @param out The reader for the given working directory - * @param repo The repository containing the working directory - * @param validate_index If true, the working directory contents will - * be compared to the index contents during read to ensure that - * the working directory is unmodified. - * @return 0 on success, or an error code < 0 - */ -extern int git_reader_for_workdir( - git_reader **out, - git_repository *repo, - bool validate_index); - -/** - * Read the given filename from the reader and populate the given buffer - * with the contents and the given oid with the object ID. - * - * @param out The buffer to populate with the file contents - * @param out_id The oid to populate with the object ID - * @param reader The reader to read - * @param filename The filename to read from the reader - */ -extern int git_reader_read( - git_str *out, - git_oid *out_id, - git_filemode_t *out_filemode, - git_reader *reader, - const char *filename); - -/** - * Free the given reader and any associated objects. - * - * @param reader The reader to free - */ -extern void git_reader_free(git_reader *reader); - -#endif diff --git a/src/rebase.c b/src/rebase.c deleted file mode 100644 index 6f01d3990..000000000 --- a/src/rebase.c +++ /dev/null @@ -1,1470 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "str.h" -#include "repository.h" -#include "posix.h" -#include "filebuf.h" -#include "commit.h" -#include "merge.h" -#include "array.h" -#include "config.h" -#include "annotated_commit.h" -#include "index.h" - -#include -#include -#include -#include -#include -#include -#include - -#define REBASE_APPLY_DIR "rebase-apply" -#define REBASE_MERGE_DIR "rebase-merge" - -#define HEAD_NAME_FILE "head-name" -#define ORIG_HEAD_FILE "orig-head" -#define HEAD_FILE "head" -#define ONTO_FILE "onto" -#define ONTO_NAME_FILE "onto_name" -#define QUIET_FILE "quiet" - -#define MSGNUM_FILE "msgnum" -#define END_FILE "end" -#define CMT_FILE_FMT "cmt.%" PRIuZ -#define CURRENT_FILE "current" -#define REWRITTEN_FILE "rewritten" - -#define ORIG_DETACHED_HEAD "detached HEAD" - -#define NOTES_DEFAULT_REF NULL - -#define REBASE_DIR_MODE 0777 -#define REBASE_FILE_MODE 0666 - -typedef enum { - GIT_REBASE_NONE = 0, - GIT_REBASE_APPLY = 1, - GIT_REBASE_MERGE = 2, - GIT_REBASE_INTERACTIVE = 3 -} git_rebase_t; - -struct git_rebase { - git_repository *repo; - - git_rebase_options options; - - git_rebase_t type; - char *state_path; - - unsigned int head_detached:1, - inmemory:1, - quiet:1, - started:1; - - git_array_t(git_rebase_operation) operations; - size_t current; - - /* Used by in-memory rebase */ - git_index *index; - git_commit *last_commit; - - /* Used by regular (not in-memory) merge-style rebase */ - git_oid orig_head_id; - char *orig_head_name; - - git_oid onto_id; - char *onto_name; -}; - -#define GIT_REBASE_STATE_INIT {0} - -static int rebase_state_type( - git_rebase_t *type_out, - char **path_out, - git_repository *repo) -{ - git_str path = GIT_STR_INIT; - git_rebase_t type = GIT_REBASE_NONE; - - if (git_str_joinpath(&path, repo->gitdir, REBASE_APPLY_DIR) < 0) - return -1; - - if (git_fs_path_isdir(git_str_cstr(&path))) { - type = GIT_REBASE_APPLY; - goto done; - } - - git_str_clear(&path); - if (git_str_joinpath(&path, repo->gitdir, REBASE_MERGE_DIR) < 0) - return -1; - - if (git_fs_path_isdir(git_str_cstr(&path))) { - type = GIT_REBASE_MERGE; - goto done; - } - -done: - *type_out = type; - - if (type != GIT_REBASE_NONE && path_out) - *path_out = git_str_detach(&path); - - git_str_dispose(&path); - - return 0; -} - -GIT_INLINE(int) rebase_readfile( - git_str *out, - git_str *state_path, - const char *filename) -{ - size_t state_path_len = state_path->size; - int error; - - git_str_clear(out); - - if ((error = git_str_joinpath(state_path, state_path->ptr, filename)) < 0 || - (error = git_futils_readbuffer(out, state_path->ptr)) < 0) - goto done; - - git_str_rtrim(out); - -done: - git_str_truncate(state_path, state_path_len); - return error; -} - -GIT_INLINE(int) rebase_readint( - size_t *out, git_str *asc_out, git_str *state_path, const char *filename) -{ - int32_t num; - const char *eol; - int error = 0; - - if ((error = rebase_readfile(asc_out, state_path, filename)) < 0) - return error; - - if (git__strntol32(&num, asc_out->ptr, asc_out->size, &eol, 10) < 0 || num < 0 || *eol) { - git_error_set(GIT_ERROR_REBASE, "the file '%s' contains an invalid numeric value", filename); - return -1; - } - - *out = (size_t) num; - - return 0; -} - -GIT_INLINE(int) rebase_readoid( - git_oid *out, git_str *str_out, git_str *state_path, const char *filename) -{ - int error; - - if ((error = rebase_readfile(str_out, state_path, filename)) < 0) - return error; - - if (str_out->size != GIT_OID_HEXSZ || git_oid_fromstr(out, str_out->ptr) < 0) { - git_error_set(GIT_ERROR_REBASE, "the file '%s' contains an invalid object ID", filename); - return -1; - } - - return 0; -} - -static git_rebase_operation *rebase_operation_alloc( - git_rebase *rebase, - git_rebase_operation_t type, - git_oid *id, - const char *exec) -{ - git_rebase_operation *operation; - - GIT_ASSERT_WITH_RETVAL((type == GIT_REBASE_OPERATION_EXEC) == !id, NULL); - GIT_ASSERT_WITH_RETVAL((type == GIT_REBASE_OPERATION_EXEC) == !!exec, NULL); - - if ((operation = git_array_alloc(rebase->operations)) == NULL) - return NULL; - - operation->type = type; - git_oid_cpy((git_oid *)&operation->id, id); - operation->exec = exec; - - return operation; -} - -static int rebase_open_merge(git_rebase *rebase) -{ - git_str state_path = GIT_STR_INIT, buf = GIT_STR_INIT, cmt = GIT_STR_INIT; - git_oid id; - git_rebase_operation *operation; - size_t i, msgnum = 0, end; - int error; - - if ((error = git_str_puts(&state_path, rebase->state_path)) < 0) - goto done; - - /* Read 'msgnum' if it exists (otherwise, let msgnum = 0) */ - if ((error = rebase_readint(&msgnum, &buf, &state_path, MSGNUM_FILE)) < 0 && - error != GIT_ENOTFOUND) - goto done; - - if (msgnum) { - rebase->started = 1; - rebase->current = msgnum - 1; - } - - /* Read 'end' */ - if ((error = rebase_readint(&end, &buf, &state_path, END_FILE)) < 0) - goto done; - - /* Read 'current' if it exists */ - if ((error = rebase_readoid(&id, &buf, &state_path, CURRENT_FILE)) < 0 && - error != GIT_ENOTFOUND) - goto done; - - /* Read cmt.* */ - git_array_init_to_size(rebase->operations, end); - GIT_ERROR_CHECK_ARRAY(rebase->operations); - - for (i = 0; i < end; i++) { - git_str_clear(&cmt); - - if ((error = git_str_printf(&cmt, "cmt.%" PRIuZ, (i+1))) < 0 || - (error = rebase_readoid(&id, &buf, &state_path, cmt.ptr)) < 0) - goto done; - - operation = rebase_operation_alloc(rebase, GIT_REBASE_OPERATION_PICK, &id, NULL); - GIT_ERROR_CHECK_ALLOC(operation); - } - - /* Read 'onto_name' */ - if ((error = rebase_readfile(&buf, &state_path, ONTO_NAME_FILE)) < 0) - goto done; - - rebase->onto_name = git_str_detach(&buf); - -done: - git_str_dispose(&cmt); - git_str_dispose(&state_path); - git_str_dispose(&buf); - - return error; -} - -static int rebase_alloc(git_rebase **out, const git_rebase_options *rebase_opts) -{ - git_rebase *rebase = git__calloc(1, sizeof(git_rebase)); - GIT_ERROR_CHECK_ALLOC(rebase); - - *out = NULL; - - if (rebase_opts) - memcpy(&rebase->options, rebase_opts, sizeof(git_rebase_options)); - else - git_rebase_options_init(&rebase->options, GIT_REBASE_OPTIONS_VERSION); - - if (rebase_opts && rebase_opts->rewrite_notes_ref) { - rebase->options.rewrite_notes_ref = git__strdup(rebase_opts->rewrite_notes_ref); - GIT_ERROR_CHECK_ALLOC(rebase->options.rewrite_notes_ref); - } - - *out = rebase; - - return 0; -} - -static int rebase_check_versions(const git_rebase_options *given_opts) -{ - GIT_ERROR_CHECK_VERSION(given_opts, GIT_REBASE_OPTIONS_VERSION, "git_rebase_options"); - - if (given_opts) - GIT_ERROR_CHECK_VERSION(&given_opts->checkout_options, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options"); - - return 0; -} - -int git_rebase_open( - git_rebase **out, - git_repository *repo, - const git_rebase_options *given_opts) -{ - git_rebase *rebase; - git_str path = GIT_STR_INIT, orig_head_name = GIT_STR_INIT, - orig_head_id = GIT_STR_INIT, onto_id = GIT_STR_INIT; - size_t state_path_len; - int error; - - GIT_ASSERT_ARG(repo); - - if ((error = rebase_check_versions(given_opts)) < 0) - return error; - - if (rebase_alloc(&rebase, given_opts) < 0) - return -1; - - rebase->repo = repo; - - if ((error = rebase_state_type(&rebase->type, &rebase->state_path, repo)) < 0) - goto done; - - if (rebase->type == GIT_REBASE_NONE) { - git_error_set(GIT_ERROR_REBASE, "there is no rebase in progress"); - error = GIT_ENOTFOUND; - goto done; - } - - if ((error = git_str_puts(&path, rebase->state_path)) < 0) - goto done; - - state_path_len = git_str_len(&path); - - if ((error = git_str_joinpath(&path, path.ptr, HEAD_NAME_FILE)) < 0 || - (error = git_futils_readbuffer(&orig_head_name, path.ptr)) < 0) - goto done; - - git_str_rtrim(&orig_head_name); - - if (strcmp(ORIG_DETACHED_HEAD, orig_head_name.ptr) == 0) - rebase->head_detached = 1; - - git_str_truncate(&path, state_path_len); - - if ((error = git_str_joinpath(&path, path.ptr, ORIG_HEAD_FILE)) < 0) - goto done; - - if (!git_fs_path_isfile(path.ptr)) { - /* Previous versions of git.git used 'head' here; support that. */ - git_str_truncate(&path, state_path_len); - - if ((error = git_str_joinpath(&path, path.ptr, HEAD_FILE)) < 0) - goto done; - } - - if ((error = git_futils_readbuffer(&orig_head_id, path.ptr)) < 0) - goto done; - - git_str_rtrim(&orig_head_id); - - if ((error = git_oid_fromstr(&rebase->orig_head_id, orig_head_id.ptr)) < 0) - goto done; - - git_str_truncate(&path, state_path_len); - - if ((error = git_str_joinpath(&path, path.ptr, ONTO_FILE)) < 0 || - (error = git_futils_readbuffer(&onto_id, path.ptr)) < 0) - goto done; - - git_str_rtrim(&onto_id); - - if ((error = git_oid_fromstr(&rebase->onto_id, onto_id.ptr)) < 0) - goto done; - - if (!rebase->head_detached) - rebase->orig_head_name = git_str_detach(&orig_head_name); - - switch (rebase->type) { - case GIT_REBASE_INTERACTIVE: - git_error_set(GIT_ERROR_REBASE, "interactive rebase is not supported"); - error = -1; - break; - case GIT_REBASE_MERGE: - error = rebase_open_merge(rebase); - break; - case GIT_REBASE_APPLY: - git_error_set(GIT_ERROR_REBASE, "patch application rebase is not supported"); - error = -1; - break; - default: - abort(); - } - -done: - if (error == 0) - *out = rebase; - else - git_rebase_free(rebase); - - git_str_dispose(&path); - git_str_dispose(&orig_head_name); - git_str_dispose(&orig_head_id); - git_str_dispose(&onto_id); - return error; -} - -static int rebase_cleanup(git_rebase *rebase) -{ - if (!rebase || rebase->inmemory) - return 0; - - return git_fs_path_isdir(rebase->state_path) ? - git_futils_rmdir_r(rebase->state_path, NULL, GIT_RMDIR_REMOVE_FILES) : - 0; -} - -static int rebase_setupfile(git_rebase *rebase, const char *filename, int flags, const char *fmt, ...) -{ - git_str path = GIT_STR_INIT, - contents = GIT_STR_INIT; - va_list ap; - int error; - - va_start(ap, fmt); - git_str_vprintf(&contents, fmt, ap); - va_end(ap); - - if ((error = git_str_joinpath(&path, rebase->state_path, filename)) == 0) - error = git_futils_writebuffer(&contents, path.ptr, flags, REBASE_FILE_MODE); - - git_str_dispose(&path); - git_str_dispose(&contents); - - return error; -} - -static const char *rebase_onto_name(const git_annotated_commit *onto) -{ - if (onto->ref_name && git__strncmp(onto->ref_name, "refs/heads/", 11) == 0) - return onto->ref_name + 11; - else if (onto->ref_name) - return onto->ref_name; - else - return onto->id_str; -} - -static int rebase_setupfiles_merge(git_rebase *rebase) -{ - git_str commit_filename = GIT_STR_INIT; - char id_str[GIT_OID_HEXSZ]; - git_rebase_operation *operation; - size_t i; - int error = 0; - - if ((error = rebase_setupfile(rebase, END_FILE, 0, "%" PRIuZ "\n", git_array_size(rebase->operations))) < 0 || - (error = rebase_setupfile(rebase, ONTO_NAME_FILE, 0, "%s\n", rebase->onto_name)) < 0) - goto done; - - for (i = 0; i < git_array_size(rebase->operations); i++) { - operation = git_array_get(rebase->operations, i); - - git_str_clear(&commit_filename); - git_str_printf(&commit_filename, CMT_FILE_FMT, i+1); - - git_oid_fmt(id_str, &operation->id); - - if ((error = rebase_setupfile(rebase, commit_filename.ptr, 0, - "%.*s\n", GIT_OID_HEXSZ, id_str)) < 0) - goto done; - } - -done: - git_str_dispose(&commit_filename); - return error; -} - -static int rebase_setupfiles(git_rebase *rebase) -{ - char onto[GIT_OID_HEXSZ], orig_head[GIT_OID_HEXSZ]; - const char *orig_head_name; - - git_oid_fmt(onto, &rebase->onto_id); - git_oid_fmt(orig_head, &rebase->orig_head_id); - - if (p_mkdir(rebase->state_path, REBASE_DIR_MODE) < 0) { - git_error_set(GIT_ERROR_OS, "failed to create rebase directory '%s'", rebase->state_path); - return -1; - } - - orig_head_name = rebase->head_detached ? ORIG_DETACHED_HEAD : - rebase->orig_head_name; - - if (git_repository__set_orig_head(rebase->repo, &rebase->orig_head_id) < 0 || - rebase_setupfile(rebase, HEAD_NAME_FILE, 0, "%s\n", orig_head_name) < 0 || - rebase_setupfile(rebase, ONTO_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, onto) < 0 || - rebase_setupfile(rebase, ORIG_HEAD_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, orig_head) < 0 || - rebase_setupfile(rebase, QUIET_FILE, 0, rebase->quiet ? "t\n" : "\n") < 0) - return -1; - - return rebase_setupfiles_merge(rebase); -} - -int git_rebase_options_init(git_rebase_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_rebase_options, GIT_REBASE_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_rebase_init_options(git_rebase_options *opts, unsigned int version) -{ - return git_rebase_options_init(opts, version); -} -#endif - -static int rebase_ensure_not_in_progress(git_repository *repo) -{ - int error; - git_rebase_t type; - - if ((error = rebase_state_type(&type, NULL, repo)) < 0) - return error; - - if (type != GIT_REBASE_NONE) { - git_error_set(GIT_ERROR_REBASE, "there is an existing rebase in progress"); - return -1; - } - - return 0; -} - -static int rebase_ensure_not_dirty( - git_repository *repo, - bool check_index, - bool check_workdir, - int fail_with) -{ - git_tree *head = NULL; - git_index *index = NULL; - git_diff *diff = NULL; - int error = 0; - - if (check_index) { - if ((error = git_repository_head_tree(&head, repo)) < 0 || - (error = git_repository_index(&index, repo)) < 0 || - (error = git_diff_tree_to_index(&diff, repo, head, index, NULL)) < 0) - goto done; - - if (git_diff_num_deltas(diff) > 0) { - git_error_set(GIT_ERROR_REBASE, "uncommitted changes exist in index"); - error = fail_with; - goto done; - } - - git_diff_free(diff); - diff = NULL; - } - - if (check_workdir) { - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.ignore_submodules = GIT_SUBMODULE_IGNORE_UNTRACKED; - if ((error = git_diff_index_to_workdir(&diff, repo, index, &diff_opts)) < 0) - goto done; - - if (git_diff_num_deltas(diff) > 0) { - git_error_set(GIT_ERROR_REBASE, "unstaged changes exist in workdir"); - error = fail_with; - goto done; - } - } - -done: - git_diff_free(diff); - git_index_free(index); - git_tree_free(head); - - return error; -} - -static int rebase_init_operations( - git_rebase *rebase, - git_repository *repo, - const git_annotated_commit *branch, - const git_annotated_commit *upstream, - const git_annotated_commit *onto) -{ - git_revwalk *revwalk = NULL; - git_commit *commit; - git_oid id; - bool merge; - git_rebase_operation *operation; - int error; - - if (!upstream) - upstream = onto; - - if ((error = git_revwalk_new(&revwalk, rebase->repo)) < 0 || - (error = git_revwalk_push(revwalk, git_annotated_commit_id(branch))) < 0 || - (error = git_revwalk_hide(revwalk, git_annotated_commit_id(upstream))) < 0) - goto done; - - git_revwalk_sorting(revwalk, GIT_SORT_REVERSE); - - while ((error = git_revwalk_next(&id, revwalk)) == 0) { - if ((error = git_commit_lookup(&commit, repo, &id)) < 0) - goto done; - - merge = (git_commit_parentcount(commit) > 1); - git_commit_free(commit); - - if (merge) - continue; - - operation = rebase_operation_alloc(rebase, GIT_REBASE_OPERATION_PICK, &id, NULL); - GIT_ERROR_CHECK_ALLOC(operation); - } - - error = 0; - -done: - git_revwalk_free(revwalk); - return error; -} - -static int rebase_init_merge( - git_rebase *rebase, - git_repository *repo, - const git_annotated_commit *branch, - const git_annotated_commit *upstream, - const git_annotated_commit *onto) -{ - git_reference *head_ref = NULL; - git_commit *onto_commit = NULL; - git_str reflog = GIT_STR_INIT; - git_str state_path = GIT_STR_INIT; - int error; - - GIT_UNUSED(upstream); - - if ((error = git_str_joinpath(&state_path, repo->gitdir, REBASE_MERGE_DIR)) < 0) - goto done; - - rebase->state_path = git_str_detach(&state_path); - GIT_ERROR_CHECK_ALLOC(rebase->state_path); - - if (branch->ref_name && strcmp(branch->ref_name, "HEAD")) { - rebase->orig_head_name = git__strdup(branch->ref_name); - GIT_ERROR_CHECK_ALLOC(rebase->orig_head_name); - } else { - rebase->head_detached = 1; - } - - rebase->onto_name = git__strdup(rebase_onto_name(onto)); - GIT_ERROR_CHECK_ALLOC(rebase->onto_name); - - rebase->quiet = rebase->options.quiet; - - git_oid_cpy(&rebase->orig_head_id, git_annotated_commit_id(branch)); - git_oid_cpy(&rebase->onto_id, git_annotated_commit_id(onto)); - - if ((error = rebase_setupfiles(rebase)) < 0 || - (error = git_str_printf(&reflog, - "rebase: checkout %s", rebase_onto_name(onto))) < 0 || - (error = git_commit_lookup( - &onto_commit, repo, git_annotated_commit_id(onto))) < 0 || - (error = git_checkout_tree(repo, - (git_object *)onto_commit, &rebase->options.checkout_options)) < 0 || - (error = git_reference_create(&head_ref, repo, GIT_HEAD_FILE, - git_annotated_commit_id(onto), 1, reflog.ptr)) < 0) - goto done; - -done: - git_reference_free(head_ref); - git_commit_free(onto_commit); - git_str_dispose(&reflog); - git_str_dispose(&state_path); - - return error; -} - -static int rebase_init_inmemory( - git_rebase *rebase, - git_repository *repo, - const git_annotated_commit *branch, - const git_annotated_commit *upstream, - const git_annotated_commit *onto) -{ - GIT_UNUSED(branch); - GIT_UNUSED(upstream); - - return git_commit_lookup( - &rebase->last_commit, repo, git_annotated_commit_id(onto)); -} - -int git_rebase_init( - git_rebase **out, - git_repository *repo, - const git_annotated_commit *branch, - const git_annotated_commit *upstream, - const git_annotated_commit *onto, - const git_rebase_options *given_opts) -{ - git_rebase *rebase = NULL; - git_annotated_commit *head_branch = NULL; - git_reference *head_ref = NULL; - bool inmemory = (given_opts && given_opts->inmemory); - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(upstream || onto); - - *out = NULL; - - if (!onto) - onto = upstream; - - if ((error = rebase_check_versions(given_opts)) < 0) - goto done; - - if (!inmemory) { - if ((error = git_repository__ensure_not_bare(repo, "rebase")) < 0 || - (error = rebase_ensure_not_in_progress(repo)) < 0 || - (error = rebase_ensure_not_dirty(repo, true, true, GIT_ERROR)) < 0) - goto done; - } - - if (!branch) { - if ((error = git_repository_head(&head_ref, repo)) < 0 || - (error = git_annotated_commit_from_ref(&head_branch, repo, head_ref)) < 0) - goto done; - - branch = head_branch; - } - - if (rebase_alloc(&rebase, given_opts) < 0) - return -1; - - rebase->repo = repo; - rebase->inmemory = inmemory; - rebase->type = GIT_REBASE_MERGE; - - if ((error = rebase_init_operations(rebase, repo, branch, upstream, onto)) < 0) - goto done; - - if (inmemory) - error = rebase_init_inmemory(rebase, repo, branch, upstream, onto); - else - error = rebase_init_merge(rebase, repo, branch ,upstream, onto); - - if (error == 0) - *out = rebase; - -done: - git_reference_free(head_ref); - git_annotated_commit_free(head_branch); - - if (error < 0) { - rebase_cleanup(rebase); - git_rebase_free(rebase); - } - - return error; -} - -static void normalize_checkout_options_for_apply( - git_checkout_options *checkout_opts, - git_rebase *rebase, - git_commit *current_commit) -{ - memcpy(checkout_opts, &rebase->options.checkout_options, sizeof(git_checkout_options)); - - if (!checkout_opts->ancestor_label) - checkout_opts->ancestor_label = "ancestor"; - - if (rebase->type == GIT_REBASE_MERGE) { - if (!checkout_opts->our_label) - checkout_opts->our_label = rebase->onto_name; - - if (!checkout_opts->their_label) - checkout_opts->their_label = git_commit_summary(current_commit); - } else { - abort(); - } -} - -GIT_INLINE(int) rebase_movenext(git_rebase *rebase) -{ - size_t next = rebase->started ? rebase->current + 1 : 0; - - if (next == git_array_size(rebase->operations)) - return GIT_ITEROVER; - - rebase->started = 1; - rebase->current = next; - - return 0; -} - -static int rebase_next_merge( - git_rebase_operation **out, - git_rebase *rebase) -{ - git_str path = GIT_STR_INIT; - git_commit *current_commit = NULL, *parent_commit = NULL; - git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; - git_index *index = NULL; - git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; - git_rebase_operation *operation; - git_checkout_options checkout_opts; - char current_idstr[GIT_OID_HEXSZ]; - unsigned int parent_count; - int error; - - *out = NULL; - - operation = git_array_get(rebase->operations, rebase->current); - - if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || - (error = git_commit_tree(¤t_tree, current_commit)) < 0 || - (error = git_repository_head_tree(&head_tree, rebase->repo)) < 0) - goto done; - - if ((parent_count = git_commit_parentcount(current_commit)) > 1) { - git_error_set(GIT_ERROR_REBASE, "cannot rebase a merge commit"); - error = -1; - goto done; - } else if (parent_count) { - if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0) - goto done; - } - - git_oid_fmt(current_idstr, &operation->id); - - normalize_checkout_options_for_apply(&checkout_opts, rebase, current_commit); - - if ((error = git_indexwriter_init_for_operation(&indexwriter, rebase->repo, &checkout_opts.checkout_strategy)) < 0 || - (error = rebase_setupfile(rebase, MSGNUM_FILE, 0, "%" PRIuZ "\n", rebase->current+1)) < 0 || - (error = rebase_setupfile(rebase, CURRENT_FILE, 0, "%.*s\n", GIT_OID_HEXSZ, current_idstr)) < 0 || - (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0 || - (error = git_merge__check_result(rebase->repo, index)) < 0 || - (error = git_checkout_index(rebase->repo, index, &checkout_opts)) < 0 || - (error = git_indexwriter_commit(&indexwriter)) < 0) - goto done; - - *out = operation; - -done: - git_indexwriter_cleanup(&indexwriter); - git_index_free(index); - git_tree_free(current_tree); - git_tree_free(head_tree); - git_tree_free(parent_tree); - git_commit_free(parent_commit); - git_commit_free(current_commit); - git_str_dispose(&path); - - return error; -} - -static int rebase_next_inmemory( - git_rebase_operation **out, - git_rebase *rebase) -{ - git_commit *current_commit = NULL, *parent_commit = NULL; - git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; - git_rebase_operation *operation; - git_index *index = NULL; - unsigned int parent_count; - int error; - - *out = NULL; - - operation = git_array_get(rebase->operations, rebase->current); - - if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || - (error = git_commit_tree(¤t_tree, current_commit)) < 0) - goto done; - - if ((parent_count = git_commit_parentcount(current_commit)) > 1) { - git_error_set(GIT_ERROR_REBASE, "cannot rebase a merge commit"); - error = -1; - goto done; - } else if (parent_count) { - if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0) - goto done; - } - - if ((error = git_commit_tree(&head_tree, rebase->last_commit)) < 0 || - (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0) - goto done; - - if (!rebase->index) { - rebase->index = index; - index = NULL; - } else { - if ((error = git_index_read_index(rebase->index, index)) < 0) - goto done; - } - - *out = operation; - -done: - git_commit_free(current_commit); - git_commit_free(parent_commit); - git_tree_free(current_tree); - git_tree_free(head_tree); - git_tree_free(parent_tree); - git_index_free(index); - - return error; -} - -int git_rebase_next( - git_rebase_operation **out, - git_rebase *rebase) -{ - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(rebase); - - if ((error = rebase_movenext(rebase)) < 0) - return error; - - if (rebase->inmemory) - error = rebase_next_inmemory(out, rebase); - else if (rebase->type == GIT_REBASE_MERGE) - error = rebase_next_merge(out, rebase); - else - abort(); - - return error; -} - -int git_rebase_inmemory_index( - git_index **out, - git_rebase *rebase) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(rebase); - GIT_ASSERT_ARG(rebase->index); - - GIT_REFCOUNT_INC(rebase->index); - *out = rebase->index; - - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -static int create_signed( - git_oid *out, - git_rebase *rebase, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - git_tree *tree, - size_t parent_count, - const git_commit **parents) -{ - git_str commit_content = GIT_STR_INIT; - git_buf commit_signature = { NULL, 0, 0 }, - signature_field = { NULL, 0, 0 }; - int error; - - git_error_clear(); - - if ((error = git_commit__create_buffer(&commit_content, - rebase->repo, author, committer, message_encoding, - message, tree, parent_count, parents)) < 0) - goto done; - - error = rebase->options.signing_cb(&commit_signature, - &signature_field, commit_content.ptr, - rebase->options.payload); - - if (error) { - if (error != GIT_PASSTHROUGH) - git_error_set_after_callback_function(error, "signing_cb"); - - goto done; - } - - error = git_commit_create_with_signature(out, rebase->repo, - commit_content.ptr, - commit_signature.size > 0 ? commit_signature.ptr : NULL, - signature_field.size > 0 ? signature_field.ptr : NULL); - -done: - git_buf_dispose(&commit_signature); - git_buf_dispose(&signature_field); - git_str_dispose(&commit_content); - return error; -} -#endif - -static int rebase_commit__create( - git_commit **out, - git_rebase *rebase, - git_index *index, - git_commit *parent_commit, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message) -{ - git_rebase_operation *operation; - git_commit *current_commit = NULL, *commit = NULL; - git_tree *parent_tree = NULL, *tree = NULL; - git_oid tree_id, commit_id; - int error; - - operation = git_array_get(rebase->operations, rebase->current); - - if (git_index_has_conflicts(index)) { - git_error_set(GIT_ERROR_REBASE, "conflicts have not been resolved"); - error = GIT_EUNMERGED; - goto done; - } - - if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0 || - (error = git_index_write_tree_to(&tree_id, index, rebase->repo)) < 0 || - (error = git_tree_lookup(&tree, rebase->repo, &tree_id)) < 0) - goto done; - - if (git_oid_equal(&tree_id, git_tree_id(parent_tree))) { - git_error_set(GIT_ERROR_REBASE, "this patch has already been applied"); - error = GIT_EAPPLIED; - goto done; - } - - if (!author) - author = git_commit_author(current_commit); - - if (!message) { - message_encoding = git_commit_message_encoding(current_commit); - message = git_commit_message(current_commit); - } - - git_error_clear(); - error = GIT_PASSTHROUGH; - - if (rebase->options.commit_create_cb) { - error = rebase->options.commit_create_cb(&commit_id, - author, committer, message_encoding, message, - tree, 1, (const git_commit **)&parent_commit, - rebase->options.payload); - - git_error_set_after_callback_function(error, - "commit_create_cb"); - } -#ifndef GIT_DEPRECATE_HARD - else if (rebase->options.signing_cb) { - error = create_signed(&commit_id, rebase, author, - committer, message_encoding, message, tree, - 1, (const git_commit **)&parent_commit); - } -#endif - - if (error == GIT_PASSTHROUGH) - error = git_commit_create(&commit_id, rebase->repo, NULL, - author, committer, message_encoding, message, - tree, 1, (const git_commit **)&parent_commit); - - if (error) - goto done; - - if ((error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0) - goto done; - - *out = commit; - -done: - if (error < 0) - git_commit_free(commit); - - git_commit_free(current_commit); - git_tree_free(parent_tree); - git_tree_free(tree); - - return error; -} - -static int rebase_commit_merge( - git_oid *commit_id, - git_rebase *rebase, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message) -{ - git_rebase_operation *operation; - git_reference *head = NULL; - git_commit *head_commit = NULL, *commit = NULL; - git_index *index = NULL; - char old_idstr[GIT_OID_HEXSZ], new_idstr[GIT_OID_HEXSZ]; - int error; - - operation = git_array_get(rebase->operations, rebase->current); - GIT_ASSERT(operation); - - if ((error = rebase_ensure_not_dirty(rebase->repo, false, true, GIT_EUNMERGED)) < 0 || - (error = git_repository_head(&head, rebase->repo)) < 0 || - (error = git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)) < 0 || - (error = git_repository_index(&index, rebase->repo)) < 0 || - (error = rebase_commit__create(&commit, rebase, index, head_commit, - author, committer, message_encoding, message)) < 0 || - (error = git_reference__update_for_commit( - rebase->repo, NULL, "HEAD", git_commit_id(commit), "rebase")) < 0) - goto done; - - git_oid_fmt(old_idstr, &operation->id); - git_oid_fmt(new_idstr, git_commit_id(commit)); - - if ((error = rebase_setupfile(rebase, REWRITTEN_FILE, O_CREAT|O_WRONLY|O_APPEND, - "%.*s %.*s\n", GIT_OID_HEXSZ, old_idstr, GIT_OID_HEXSZ, new_idstr)) < 0) - goto done; - - git_oid_cpy(commit_id, git_commit_id(commit)); - -done: - git_index_free(index); - git_reference_free(head); - git_commit_free(head_commit); - git_commit_free(commit); - return error; -} - -static int rebase_commit_inmemory( - git_oid *commit_id, - git_rebase *rebase, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message) -{ - git_commit *commit = NULL; - int error = 0; - - GIT_ASSERT_ARG(rebase->index); - GIT_ASSERT_ARG(rebase->last_commit); - GIT_ASSERT_ARG(rebase->current < rebase->operations.size); - - if ((error = rebase_commit__create(&commit, rebase, rebase->index, - rebase->last_commit, author, committer, message_encoding, message)) < 0) - goto done; - - git_commit_free(rebase->last_commit); - rebase->last_commit = commit; - - git_oid_cpy(commit_id, git_commit_id(commit)); - -done: - if (error < 0) - git_commit_free(commit); - - return error; -} - -int git_rebase_commit( - git_oid *id, - git_rebase *rebase, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message) -{ - int error; - - GIT_ASSERT_ARG(rebase); - GIT_ASSERT_ARG(committer); - - if (rebase->inmemory) - error = rebase_commit_inmemory( - id, rebase, author, committer, message_encoding, message); - else if (rebase->type == GIT_REBASE_MERGE) - error = rebase_commit_merge( - id, rebase, author, committer, message_encoding, message); - else - abort(); - - return error; -} - -int git_rebase_abort(git_rebase *rebase) -{ - git_reference *orig_head_ref = NULL; - git_commit *orig_head_commit = NULL; - int error; - - GIT_ASSERT_ARG(rebase); - - if (rebase->inmemory) - return 0; - - error = rebase->head_detached ? - git_reference_create(&orig_head_ref, rebase->repo, GIT_HEAD_FILE, - &rebase->orig_head_id, 1, "rebase: aborting") : - git_reference_symbolic_create( - &orig_head_ref, rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1, - "rebase: aborting"); - - if (error < 0) - goto done; - - if ((error = git_commit_lookup( - &orig_head_commit, rebase->repo, &rebase->orig_head_id)) < 0 || - (error = git_reset(rebase->repo, (git_object *)orig_head_commit, - GIT_RESET_HARD, &rebase->options.checkout_options)) < 0) - goto done; - - error = rebase_cleanup(rebase); - -done: - git_commit_free(orig_head_commit); - git_reference_free(orig_head_ref); - - return error; -} - -static int notes_ref_lookup(git_str *out, git_rebase *rebase) -{ - git_config *config = NULL; - int do_rewrite, error; - - if (rebase->options.rewrite_notes_ref) { - git_str_attach_notowned(out, - rebase->options.rewrite_notes_ref, - strlen(rebase->options.rewrite_notes_ref)); - return 0; - } - - if ((error = git_repository_config(&config, rebase->repo)) < 0 || - (error = git_config_get_bool(&do_rewrite, config, "notes.rewrite.rebase")) < 0) { - - if (error != GIT_ENOTFOUND) - goto done; - - git_error_clear(); - do_rewrite = 1; - } - - error = do_rewrite ? - git_config__get_string_buf(out, config, "notes.rewriteref") : - GIT_ENOTFOUND; - -done: - git_config_free(config); - return error; -} - -static int rebase_copy_note( - git_rebase *rebase, - const char *notes_ref, - git_oid *from, - git_oid *to, - const git_signature *committer) -{ - git_note *note = NULL; - git_oid note_id; - git_signature *who = NULL; - int error; - - if ((error = git_note_read(¬e, rebase->repo, notes_ref, from)) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - goto done; - } - - if (!committer) { - if((error = git_signature_default(&who, rebase->repo)) < 0) { - if (error != GIT_ENOTFOUND || - (error = git_signature_now(&who, "unknown", "unknown")) < 0) - goto done; - - git_error_clear(); - } - - committer = who; - } - - error = git_note_create(¬e_id, rebase->repo, notes_ref, - git_note_author(note), committer, to, git_note_message(note), 0); - -done: - git_note_free(note); - git_signature_free(who); - - return error; -} - -static int rebase_copy_notes( - git_rebase *rebase, - const git_signature *committer) -{ - git_str path = GIT_STR_INIT, rewritten = GIT_STR_INIT, notes_ref = GIT_STR_INIT; - char *pair_list, *fromstr, *tostr, *end; - git_oid from, to; - unsigned int linenum = 1; - int error = 0; - - if ((error = notes_ref_lookup(¬es_ref, rebase)) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - goto done; - } - - if ((error = git_str_joinpath(&path, rebase->state_path, REWRITTEN_FILE)) < 0 || - (error = git_futils_readbuffer(&rewritten, path.ptr)) < 0) - goto done; - - pair_list = rewritten.ptr; - - while (*pair_list) { - fromstr = pair_list; - - if ((end = strchr(fromstr, '\n')) == NULL) - goto on_error; - - pair_list = end+1; - *end = '\0'; - - if ((end = strchr(fromstr, ' ')) == NULL) - goto on_error; - - tostr = end+1; - *end = '\0'; - - if (strlen(fromstr) != GIT_OID_HEXSZ || - strlen(tostr) != GIT_OID_HEXSZ || - git_oid_fromstr(&from, fromstr) < 0 || - git_oid_fromstr(&to, tostr) < 0) - goto on_error; - - if ((error = rebase_copy_note(rebase, notes_ref.ptr, &from, &to, committer)) < 0) - goto done; - - linenum++; - } - - goto done; - -on_error: - git_error_set(GIT_ERROR_REBASE, "invalid rewritten file at line %d", linenum); - error = -1; - -done: - git_str_dispose(&rewritten); - git_str_dispose(&path); - git_str_dispose(¬es_ref); - - return error; -} - -static int return_to_orig_head(git_rebase *rebase) -{ - git_reference *terminal_ref = NULL, *branch_ref = NULL, *head_ref = NULL; - git_commit *terminal_commit = NULL; - git_str branch_msg = GIT_STR_INIT, head_msg = GIT_STR_INIT; - char onto[GIT_OID_HEXSZ]; - int error = 0; - - git_oid_fmt(onto, &rebase->onto_id); - - if ((error = git_str_printf(&branch_msg, - "rebase finished: %s onto %.*s", - rebase->orig_head_name, GIT_OID_HEXSZ, onto)) == 0 && - (error = git_str_printf(&head_msg, - "rebase finished: returning to %s", - rebase->orig_head_name)) == 0 && - (error = git_repository_head(&terminal_ref, rebase->repo)) == 0 && - (error = git_reference_peel((git_object **)&terminal_commit, - terminal_ref, GIT_OBJECT_COMMIT)) == 0 && - (error = git_reference_create_matching(&branch_ref, - rebase->repo, rebase->orig_head_name, - git_commit_id(terminal_commit), 1, - &rebase->orig_head_id, branch_msg.ptr)) == 0) - error = git_reference_symbolic_create(&head_ref, - rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1, - head_msg.ptr); - - git_str_dispose(&head_msg); - git_str_dispose(&branch_msg); - git_commit_free(terminal_commit); - git_reference_free(head_ref); - git_reference_free(branch_ref); - git_reference_free(terminal_ref); - - return error; -} - -int git_rebase_finish( - git_rebase *rebase, - const git_signature *signature) -{ - int error = 0; - - GIT_ASSERT_ARG(rebase); - - if (rebase->inmemory) - return 0; - - if (!rebase->head_detached) - error = return_to_orig_head(rebase); - - if (error == 0 && (error = rebase_copy_notes(rebase, signature)) == 0) - error = rebase_cleanup(rebase); - - return error; -} - -const char *git_rebase_orig_head_name(git_rebase *rebase) { - GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); - return rebase->orig_head_name; -} - -const git_oid *git_rebase_orig_head_id(git_rebase *rebase) { - GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); - return &rebase->orig_head_id; -} - -const char *git_rebase_onto_name(git_rebase *rebase) { - GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); - return rebase->onto_name; -} - -const git_oid *git_rebase_onto_id(git_rebase *rebase) { - return &rebase->onto_id; -} - -size_t git_rebase_operation_entrycount(git_rebase *rebase) -{ - GIT_ASSERT_ARG_WITH_RETVAL(rebase, 0); - - return git_array_size(rebase->operations); -} - -size_t git_rebase_operation_current(git_rebase *rebase) -{ - GIT_ASSERT_ARG_WITH_RETVAL(rebase, 0); - - return rebase->started ? rebase->current : GIT_REBASE_NO_OPERATION; -} - -git_rebase_operation *git_rebase_operation_byindex(git_rebase *rebase, size_t idx) -{ - GIT_ASSERT_ARG_WITH_RETVAL(rebase, NULL); - - return git_array_get(rebase->operations, idx); -} - -void git_rebase_free(git_rebase *rebase) -{ - if (rebase == NULL) - return; - - git_index_free(rebase->index); - git_commit_free(rebase->last_commit); - git__free(rebase->onto_name); - git__free(rebase->orig_head_name); - git__free(rebase->state_path); - git_array_clear(rebase->operations); - git__free((char *)rebase->options.rewrite_notes_ref); - git__free(rebase); -} diff --git a/src/refdb.c b/src/refdb.c deleted file mode 100644 index ed33de92b..000000000 --- a/src/refdb.c +++ /dev/null @@ -1,424 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "refdb.h" - -#include "git2/object.h" -#include "git2/refs.h" -#include "git2/refdb.h" -#include "git2/sys/refdb_backend.h" - -#include "hash.h" -#include "refs.h" -#include "reflog.h" -#include "posix.h" - -#define DEFAULT_NESTING_LEVEL 5 -#define MAX_NESTING_LEVEL 10 - -int git_refdb_new(git_refdb **out, git_repository *repo) -{ - git_refdb *db; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - db = git__calloc(1, sizeof(*db)); - GIT_ERROR_CHECK_ALLOC(db); - - db->repo = repo; - - *out = db; - GIT_REFCOUNT_INC(db); - return 0; -} - -int git_refdb_open(git_refdb **out, git_repository *repo) -{ - git_refdb *db; - git_refdb_backend *dir; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - *out = NULL; - - if (git_refdb_new(&db, repo) < 0) - return -1; - - /* Add the default (filesystem) backend */ - if (git_refdb_backend_fs(&dir, repo) < 0) { - git_refdb_free(db); - return -1; - } - - db->repo = repo; - db->backend = dir; - - *out = db; - return 0; -} - -static void refdb_free_backend(git_refdb *db) -{ - if (db->backend) - db->backend->free(db->backend); -} - -int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend) -{ - GIT_ERROR_CHECK_VERSION(backend, GIT_REFDB_BACKEND_VERSION, "git_refdb_backend"); - - if (!backend->exists || !backend->lookup || !backend->iterator || - !backend->write || !backend->rename || !backend->del || - !backend->has_log || !backend->ensure_log || !backend->free || - !backend->reflog_read || !backend->reflog_write || - !backend->reflog_rename || !backend->reflog_delete || - (backend->lock && !backend->unlock)) { - git_error_set(GIT_ERROR_REFERENCE, "incomplete refdb backend implementation"); - return GIT_EINVALID; - } - - refdb_free_backend(db); - db->backend = backend; - - return 0; -} - -int git_refdb_compress(git_refdb *db) -{ - GIT_ASSERT_ARG(db); - - if (db->backend->compress) - return db->backend->compress(db->backend); - - return 0; -} - -void git_refdb__free(git_refdb *db) -{ - refdb_free_backend(db); - git__memzero(db, sizeof(*db)); - git__free(db); -} - -void git_refdb_free(git_refdb *db) -{ - if (db == NULL) - return; - - GIT_REFCOUNT_DEC(db, git_refdb__free); -} - -int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name) -{ - GIT_ASSERT_ARG(exists); - GIT_ASSERT_ARG(refdb); - GIT_ASSERT_ARG(refdb->backend); - - return refdb->backend->exists(exists, refdb->backend, ref_name); -} - -int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name) -{ - git_reference *ref; - int error; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(db->backend); - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ref_name); - - error = db->backend->lookup(&ref, db->backend, ref_name); - if (error < 0) - return error; - - GIT_REFCOUNT_INC(db); - ref->db = db; - - *out = ref; - return 0; -} - -int git_refdb_resolve( - git_reference **out, - git_refdb *db, - const char *ref_name, - int max_nesting) -{ - git_reference *ref = NULL; - int error = 0, nesting; - - *out = NULL; - - if (max_nesting > MAX_NESTING_LEVEL) - max_nesting = MAX_NESTING_LEVEL; - else if (max_nesting < 0) - max_nesting = DEFAULT_NESTING_LEVEL; - - if ((error = git_refdb_lookup(&ref, db, ref_name)) < 0) - goto out; - - for (nesting = 0; nesting < max_nesting; nesting++) { - git_reference *resolved; - - if (ref->type == GIT_REFERENCE_DIRECT) - break; - - if ((error = git_refdb_lookup(&resolved, db, git_reference_symbolic_target(ref))) < 0) { - /* If we found a symbolic reference with a nonexistent target, return it. */ - if (error == GIT_ENOTFOUND) { - error = 0; - *out = ref; - ref = NULL; - } - goto out; - } - - git_reference_free(ref); - ref = resolved; - } - - if (ref->type != GIT_REFERENCE_DIRECT && max_nesting != 0) { - git_error_set(GIT_ERROR_REFERENCE, - "cannot resolve reference (>%u levels deep)", max_nesting); - error = -1; - goto out; - } - - *out = ref; - ref = NULL; -out: - git_reference_free(ref); - return error; -} - -int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob) -{ - int error; - - if (!db->backend || !db->backend->iterator) { - git_error_set(GIT_ERROR_REFERENCE, "this backend doesn't support iterators"); - return -1; - } - - if ((error = db->backend->iterator(out, db->backend, glob)) < 0) - return error; - - GIT_REFCOUNT_INC(db); - (*out)->db = db; - - return 0; -} - -int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter) -{ - int error; - - if ((error = iter->next(out, iter)) < 0) - return error; - - GIT_REFCOUNT_INC(iter->db); - (*out)->db = iter->db; - - return 0; -} - -int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter) -{ - return iter->next_name(out, iter); -} - -void git_refdb_iterator_free(git_reference_iterator *iter) -{ - GIT_REFCOUNT_DEC(iter->db, git_refdb__free); - iter->free(iter); -} - -int git_refdb_write(git_refdb *db, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target) -{ - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(db->backend); - - GIT_REFCOUNT_INC(db); - ref->db = db; - - return db->backend->write(db->backend, ref, force, who, message, old_id, old_target); -} - -int git_refdb_rename( - git_reference **out, - git_refdb *db, - const char *old_name, - const char *new_name, - int force, - const git_signature *who, - const char *message) -{ - int error; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(db->backend); - - error = db->backend->rename(out, db->backend, old_name, new_name, force, who, message); - if (error < 0) - return error; - - if (out) { - GIT_REFCOUNT_INC(db); - (*out)->db = db; - } - - return 0; -} - -int git_refdb_delete(struct git_refdb *db, const char *ref_name, const git_oid *old_id, const char *old_target) -{ - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(db->backend); - - return db->backend->del(db->backend, ref_name, old_id, old_target); -} - -int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name) -{ - int error; - - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(db->backend); - - if ((error = db->backend->reflog_read(out, db->backend, name)) < 0) - return error; - - GIT_REFCOUNT_INC(db); - (*out)->db = db; - - return 0; -} - -int git_refdb_should_write_reflog(int *out, git_refdb *db, const git_reference *ref) -{ - int error, logall; - - error = git_repository__configmap_lookup(&logall, db->repo, GIT_CONFIGMAP_LOGALLREFUPDATES); - if (error < 0) - return error; - - /* Defaults to the opposite of the repo being bare */ - if (logall == GIT_LOGALLREFUPDATES_UNSET) - logall = !git_repository_is_bare(db->repo); - - *out = 0; - switch (logall) { - case GIT_LOGALLREFUPDATES_FALSE: - *out = 0; - break; - - case GIT_LOGALLREFUPDATES_TRUE: - /* Only write if it already has a log, - * or if it's under heads/, remotes/ or notes/ - */ - *out = git_refdb_has_log(db, ref->name) || - !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) || - !git__strcmp(ref->name, GIT_HEAD_FILE) || - !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR) || - !git__prefixcmp(ref->name, GIT_REFS_NOTES_DIR); - break; - - case GIT_LOGALLREFUPDATES_ALWAYS: - *out = 1; - break; - } - - return 0; -} - -int git_refdb_should_write_head_reflog(int *out, git_refdb *db, const git_reference *ref) -{ - git_reference *head = NULL, *resolved = NULL; - const char *name; - int error; - - *out = 0; - - if (ref->type == GIT_REFERENCE_SYMBOLIC) { - error = 0; - goto out; - } - - if ((error = git_refdb_lookup(&head, db, GIT_HEAD_FILE)) < 0) - goto out; - - if (git_reference_type(head) == GIT_REFERENCE_DIRECT) - goto out; - - /* Go down the symref chain until we find the branch */ - if ((error = git_refdb_resolve(&resolved, db, git_reference_symbolic_target(head), -1)) < 0) { - if (error != GIT_ENOTFOUND) - goto out; - error = 0; - name = git_reference_symbolic_target(head); - } else if (git_reference_type(resolved) == GIT_REFERENCE_SYMBOLIC) { - name = git_reference_symbolic_target(resolved); - } else { - name = git_reference_name(resolved); - } - - if (strcmp(name, ref->name)) - goto out; - - *out = 1; - -out: - git_reference_free(resolved); - git_reference_free(head); - return error; -} - -int git_refdb_has_log(git_refdb *db, const char *refname) -{ - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(refname); - - return db->backend->has_log(db->backend, refname); -} - -int git_refdb_ensure_log(git_refdb *db, const char *refname) -{ - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(refname); - - return db->backend->ensure_log(db->backend, refname); -} - -int git_refdb_init_backend(git_refdb_backend *backend, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - backend, version, git_refdb_backend, GIT_REFDB_BACKEND_INIT); - return 0; -} - -int git_refdb_lock(void **payload, git_refdb *db, const char *refname) -{ - GIT_ASSERT_ARG(payload); - GIT_ASSERT_ARG(db); - GIT_ASSERT_ARG(refname); - - if (!db->backend->lock) { - git_error_set(GIT_ERROR_REFERENCE, "backend does not support locking"); - return -1; - } - - return db->backend->lock(payload, db->backend, refname); -} - -int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message) -{ - GIT_ASSERT_ARG(db); - - return db->backend->unlock(db->backend, payload, success, update_reflog, ref, sig, message); -} diff --git a/src/refdb.h b/src/refdb.h deleted file mode 100644 index 84e19b1c3..000000000 --- a/src/refdb.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_refdb_h__ -#define INCLUDE_refdb_h__ - -#include "common.h" - -#include "git2/refdb.h" -#include "repository.h" - -struct git_refdb { - git_refcount rc; - git_repository *repo; - git_refdb_backend *backend; -}; - -void git_refdb__free(git_refdb *db); - -int git_refdb_exists( - int *exists, - git_refdb *refdb, - const char *ref_name); - -int git_refdb_lookup( - git_reference **out, - git_refdb *refdb, - const char *ref_name); - -/** - * Resolve the reference by following symbolic references. - * - * Given a reference name, this function will follow any symbolic references up - * to `max_nesting` deep and return the resolved direct reference. If any of - * the intermediate symbolic references points to a non-existing reference, - * then that symbolic reference is returned instead with an error code of `0`. - * If the given reference is a direct reference already, it is returned - * directly. - * - * If `max_nesting` is `0`, the reference will not be resolved. If it's - * negative, it will be set to the default resolve depth which is `5`. - * - * @param out Pointer to store the result in. - * @param db The refdb to use for resolving the reference. - * @param ref_name The reference name to lookup and resolve. - * @param max_nesting The maximum nesting depth. - * @return `0` on success, a negative error code otherwise. - */ -int git_refdb_resolve( - git_reference **out, - git_refdb *db, - const char *ref_name, - int max_nesting); - -int git_refdb_rename( - git_reference **out, - git_refdb *db, - const char *old_name, - const char *new_name, - int force, - const git_signature *who, - const char *message); - -int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob); -int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter); -int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter); -void git_refdb_iterator_free(git_reference_iterator *iter); - -int git_refdb_write(git_refdb *refdb, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target); -int git_refdb_delete(git_refdb *refdb, const char *ref_name, const git_oid *old_id, const char *old_target); - -int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name); -int git_refdb_reflog_write(git_reflog *reflog); - -/** - * Determine whether a reflog entry should be created for the given reference. - * - * Whether or not writing to a reference should create a reflog entry is - * dependent on a number of things. Most importantly, there's the - * "core.logAllRefUpdates" setting that controls in which situations a - * reference should get a corresponding reflog entry. The following values for - * it are understood: - * - * - "false": Do not log reference updates. - * - * - "true": Log normal reference updates. This will write entries for - * references in "refs/heads", "refs/remotes", "refs/notes" and - * "HEAD" or if the reference already has a log entry. - * - * - "always": Always create a reflog entry. - * - * If unset, the value will default to "true" for non-bare repositories and - * "false" for bare ones. - * - * @param out pointer to which the result will be written, `1` means a reflog - * entry should be written, `0` means none should be written. - * @param db The refdb to decide this for. - * @param ref The reference one wants to check. - * @return `0` on success, a negative error code otherwise. - */ -int git_refdb_should_write_reflog(int *out, git_refdb *db, const git_reference *ref); - -/** - * Determine whether a reflog entry should be created for HEAD if creating one - * for the given reference - * - * In case the given reference is being pointed to by HEAD, then creating a - * reflog entry for this reference also requires us to create a corresponding - * reflog entry for HEAD. This function can be used to determine that scenario. - * - * @param out pointer to which the result will be written, `1` means a reflog - * entry should be written, `0` means none should be written. - * @param db The refdb to decide this for. - * @param ref The reference one wants to check. - * @return `0` on success, a negative error code otherwise. - */ -int git_refdb_should_write_head_reflog(int *out, git_refdb *db, const git_reference *ref); - -int git_refdb_has_log(git_refdb *db, const char *refname); -int git_refdb_ensure_log(git_refdb *refdb, const char *refname); - -int git_refdb_lock(void **payload, git_refdb *db, const char *refname); -int git_refdb_unlock(git_refdb *db, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message); - -#endif diff --git a/src/refdb_fs.c b/src/refdb_fs.c deleted file mode 100644 index 95bda9404..000000000 --- a/src/refdb_fs.c +++ /dev/null @@ -1,2464 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "refs.h" -#include "hash.h" -#include "repository.h" -#include "futils.h" -#include "filebuf.h" -#include "pack.h" -#include "parse.h" -#include "reflog.h" -#include "refdb.h" -#include "iterator.h" -#include "sortedcache.h" -#include "signature.h" -#include "wildmatch.h" -#include "path.h" - -#include -#include -#include -#include -#include -#include -#include - -#define DEFAULT_NESTING_LEVEL 5 -#define MAX_NESTING_LEVEL 10 - -enum { - PACKREF_HAS_PEEL = 1, - PACKREF_WAS_LOOSE = 2, - PACKREF_CANNOT_PEEL = 4, - PACKREF_SHADOWED = 8 -}; - -enum { - PEELING_NONE = 0, - PEELING_STANDARD, - PEELING_FULL -}; - -struct packref { - git_oid oid; - git_oid peel; - char flags; - char name[GIT_FLEX_ARRAY]; -}; - -typedef struct refdb_fs_backend { - git_refdb_backend parent; - - git_repository *repo; - /* path to git directory */ - char *gitpath; - /* path to common objects' directory */ - char *commonpath; - - git_sortedcache *refcache; - int peeling_mode; - git_iterator_flag_t iterator_flags; - uint32_t direach_flags; - int fsync; - git_map packed_refs_map; - git_mutex prlock; /* protect packed_refs_map */ - git_futils_filestamp packed_refs_stamp; - bool sorted; -} refdb_fs_backend; - -static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name); -static char *packed_set_peeling_mode(char *data, size_t data_sz, refdb_fs_backend *backend); - -GIT_INLINE(int) loose_path( - git_str *out, - const char *base, - const char *refname) -{ - if (git_str_joinpath(out, base, refname) < 0) - return -1; - - return git_fs_path_validate_str_length_with_suffix(out, - CONST_STRLEN(".lock")); -} - -GIT_INLINE(int) reflog_path( - git_str *out, - git_repository *repo, - const char *refname) -{ - const char *base; - int error; - - base = (strcmp(refname, GIT_HEAD_FILE) == 0) ? repo->gitdir : - repo->commondir; - - if ((error = git_str_joinpath(out, base, GIT_REFLOG_DIR)) < 0) - return error; - - return loose_path(out, out->ptr, refname); -} - -static int packref_cmp(const void *a_, const void *b_) -{ - const struct packref *a = a_, *b = b_; - return strcmp(a->name, b->name); -} - -static int packed_reload(refdb_fs_backend *backend) -{ - int error; - git_str packedrefs = GIT_STR_INIT; - char *scan, *eof, *eol; - - if (!backend->gitpath) - return 0; - - error = git_sortedcache_lockandload(backend->refcache, &packedrefs); - - /* - * If we can't find the packed-refs, clear table and return. - * Any other error just gets passed through. - * If no error, and file wasn't changed, just return. - * Anything else means we need to refresh the packed refs. - */ - if (error <= 0) { - if (error == GIT_ENOTFOUND) { - GIT_UNUSED(git_sortedcache_clear(backend->refcache, true)); - git_error_clear(); - error = 0; - } - return error; - } - - /* At this point, refresh the packed refs from the loaded buffer. */ - - GIT_UNUSED(git_sortedcache_clear(backend->refcache, false)); - - scan = packedrefs.ptr; - eof = scan + packedrefs.size; - - scan = packed_set_peeling_mode(scan, packedrefs.size, backend); - if (!scan) - goto parse_failed; - - while (scan < eof && *scan == '#') { - if (!(eol = strchr(scan, '\n'))) - goto parse_failed; - scan = eol + 1; - } - - while (scan < eof) { - struct packref *ref; - git_oid oid; - - /* parse " \n" */ - - if (git_oid_fromstr(&oid, scan) < 0) - goto parse_failed; - scan += GIT_OID_HEXSZ; - - if (*scan++ != ' ') - goto parse_failed; - if (!(eol = strchr(scan, '\n'))) - goto parse_failed; - *eol = '\0'; - if (eol[-1] == '\r') - eol[-1] = '\0'; - - if (git_sortedcache_upsert((void **)&ref, backend->refcache, scan) < 0) - goto parse_failed; - scan = eol + 1; - - git_oid_cpy(&ref->oid, &oid); - - /* look for optional "^\n" */ - - if (*scan == '^') { - if (git_oid_fromstr(&oid, scan + 1) < 0) - goto parse_failed; - scan += GIT_OID_HEXSZ + 1; - - if (scan < eof) { - if (!(eol = strchr(scan, '\n'))) - goto parse_failed; - scan = eol + 1; - } - - git_oid_cpy(&ref->peel, &oid); - ref->flags |= PACKREF_HAS_PEEL; - } - else if (backend->peeling_mode == PEELING_FULL || - (backend->peeling_mode == PEELING_STANDARD && - git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0)) - ref->flags |= PACKREF_CANNOT_PEEL; - } - - git_sortedcache_wunlock(backend->refcache); - git_str_dispose(&packedrefs); - - return 0; - -parse_failed: - git_error_set(GIT_ERROR_REFERENCE, "corrupted packed references file"); - - GIT_UNUSED(git_sortedcache_clear(backend->refcache, false)); - git_sortedcache_wunlock(backend->refcache); - git_str_dispose(&packedrefs); - - return -1; -} - -static int loose_parse_oid( - git_oid *oid, const char *filename, git_str *file_content) -{ - const char *str = git_str_cstr(file_content); - - if (git_str_len(file_content) < GIT_OID_HEXSZ) - goto corrupted; - - /* we need to get 40 OID characters from the file */ - if (git_oid_fromstr(oid, str) < 0) - goto corrupted; - - /* If the file is longer than 40 chars, the 41st must be a space */ - str += GIT_OID_HEXSZ; - if (*str == '\0' || git__isspace(*str)) - return 0; - -corrupted: - git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file: %s", filename); - return -1; -} - -static int loose_readbuffer(git_str *buf, const char *base, const char *path) -{ - int error; - - if ((error = loose_path(buf, base, path)) < 0 || - (error = git_futils_readbuffer(buf, buf->ptr)) < 0) - git_str_dispose(buf); - - return error; -} - -static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name) -{ - int error = 0; - git_str ref_file = GIT_STR_INIT; - struct packref *ref = NULL; - git_oid oid; - - /* if we fail to load the loose reference, assume someone changed - * the filesystem under us and skip it... - */ - if (loose_readbuffer(&ref_file, backend->gitpath, name) < 0) { - git_error_clear(); - goto done; - } - - /* skip symbolic refs */ - if (!git__prefixcmp(git_str_cstr(&ref_file), GIT_SYMREF)) - goto done; - - /* parse OID from file */ - if ((error = loose_parse_oid(&oid, name, &ref_file)) < 0) - goto done; - - if ((error = git_sortedcache_wlock(backend->refcache)) < 0) - goto done; - - if (!(error = git_sortedcache_upsert( - (void **)&ref, backend->refcache, name))) { - - git_oid_cpy(&ref->oid, &oid); - ref->flags = PACKREF_WAS_LOOSE; - } - - git_sortedcache_wunlock(backend->refcache); - -done: - git_str_dispose(&ref_file); - return error; -} - -static int _dirent_loose_load(void *payload, git_str *full_path) -{ - refdb_fs_backend *backend = payload; - const char *file_path; - - if (git__suffixcmp(full_path->ptr, ".lock") == 0) - return 0; - - if (git_fs_path_isdir(full_path->ptr)) { - int error = git_fs_path_direach( - full_path, backend->direach_flags, _dirent_loose_load, backend); - /* Race with the filesystem, ignore it */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - return 0; - } - - return error; - } - - file_path = full_path->ptr + strlen(backend->gitpath); - - return loose_lookup_to_packfile(backend, file_path); -} - -/* - * Load all the loose references from the repository - * into the in-memory Packfile, and build a vector with - * all the references so it can be written back to - * disk. - */ -static int packed_loadloose(refdb_fs_backend *backend) -{ - int error; - git_str refs_path = GIT_STR_INIT; - - if (git_str_joinpath(&refs_path, backend->gitpath, GIT_REFS_DIR) < 0) - return -1; - - /* - * Load all the loose files from disk into the Packfile table. - * This will overwrite any old packed entries with their - * updated loose versions - */ - error = git_fs_path_direach( - &refs_path, backend->direach_flags, _dirent_loose_load, backend); - - git_str_dispose(&refs_path); - - return error; -} - -static int refdb_fs_backend__exists( - int *exists, - git_refdb_backend *_backend, - const char *ref_name) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - git_str ref_path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(backend); - - *exists = 0; - - if ((error = loose_path(&ref_path, backend->gitpath, ref_name)) < 0) - goto out; - - if (git_fs_path_isfile(ref_path.ptr)) { - *exists = 1; - goto out; - } - - if ((error = packed_reload(backend)) < 0) - goto out; - - if (git_sortedcache_lookup(backend->refcache, ref_name) != NULL) { - *exists = 1; - goto out; - } - -out: - git_str_dispose(&ref_path); - return error; -} - -static const char *loose_parse_symbolic(git_str *file_content) -{ - const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); - const char *refname_start; - - refname_start = (const char *)file_content->ptr; - - if (git_str_len(file_content) < header_len + 1) { - git_error_set(GIT_ERROR_REFERENCE, "corrupted loose reference file"); - return NULL; - } - - /* - * Assume we have already checked for the header - * before calling this function - */ - refname_start += header_len; - - return refname_start; -} - -/* - * Returns whether a reference is stored per worktree or not. - * Per-worktree references are: - * - * - all pseudorefs, e.g. HEAD and MERGE_HEAD - * - all references stored inside of "refs/bisect/" - */ -static bool is_per_worktree_ref(const char *ref_name) -{ - return git__prefixcmp(ref_name, "refs/") != 0 || - git__prefixcmp(ref_name, "refs/bisect/") == 0; -} - -static int loose_lookup( - git_reference **out, - refdb_fs_backend *backend, - const char *ref_name) -{ - git_str ref_file = GIT_STR_INIT; - int error = 0; - const char *ref_dir; - - if (out) - *out = NULL; - - if (is_per_worktree_ref(ref_name)) - ref_dir = backend->gitpath; - else - ref_dir = backend->commonpath; - - if ((error = loose_readbuffer(&ref_file, ref_dir, ref_name)) < 0) - /* cannot read loose ref file - gah */; - else if (git__prefixcmp(git_str_cstr(&ref_file), GIT_SYMREF) == 0) { - const char *target; - - git_str_rtrim(&ref_file); - - if (!(target = loose_parse_symbolic(&ref_file))) - error = -1; - else if (out != NULL) - *out = git_reference__alloc_symbolic(ref_name, target); - } else { - git_oid oid; - - if (!(error = loose_parse_oid(&oid, ref_name, &ref_file)) && - out != NULL) - *out = git_reference__alloc(ref_name, &oid, NULL); - } - - git_str_dispose(&ref_file); - return error; -} - -static int ref_error_notfound(const char *name) -{ - git_error_set(GIT_ERROR_REFERENCE, "reference '%s' not found", name); - return GIT_ENOTFOUND; -} - -static char *packed_set_peeling_mode( - char *data, - size_t data_sz, - refdb_fs_backend *backend) -{ - static const char *traits_header = "# pack-refs with:"; - char *eol; - backend->peeling_mode = PEELING_NONE; - - if (git__prefixncmp(data, data_sz, traits_header) == 0) { - size_t hdr_sz = strlen(traits_header); - const char *sorted = " sorted "; - const char *peeled = " peeled "; - const char *fully_peeled = " fully-peeled "; - data += hdr_sz; - data_sz -= hdr_sz; - - eol = memchr(data, '\n', data_sz); - - if (!eol) - return NULL; - - if (git__memmem(data, eol - data, fully_peeled, strlen(fully_peeled))) - backend->peeling_mode = PEELING_FULL; - else if (git__memmem(data, eol - data, peeled, strlen(peeled))) - backend->peeling_mode = PEELING_STANDARD; - - backend->sorted = NULL != git__memmem(data, eol - data, sorted, strlen(sorted)); - - return eol + 1; - } - return data; -} - -static void packed_map_free(refdb_fs_backend *backend) -{ - if (backend->packed_refs_map.data) { -#ifdef GIT_WIN32 - git__free(backend->packed_refs_map.data); -#else - git_futils_mmap_free(&backend->packed_refs_map); -#endif - backend->packed_refs_map.data = NULL; - backend->packed_refs_map.len = 0; - git_futils_filestamp_set(&backend->packed_refs_stamp, NULL); - } -} - -static int packed_map_check(refdb_fs_backend *backend) -{ - int error = 0; - git_file fd = -1; - struct stat st; - - if ((error = git_mutex_lock(&backend->prlock)) < 0) - return error; - - if (backend->packed_refs_map.data && - !git_futils_filestamp_check( - &backend->packed_refs_stamp, backend->refcache->path)) { - git_mutex_unlock(&backend->prlock); - return error; - } - packed_map_free(backend); - - fd = git_futils_open_ro(backend->refcache->path); - if (fd < 0) { - git_mutex_unlock(&backend->prlock); - if (fd == GIT_ENOTFOUND) { - git_error_clear(); - return 0; - } - return fd; - } - - if (p_fstat(fd, &st) < 0) { - p_close(fd); - git_mutex_unlock(&backend->prlock); - git_error_set(GIT_ERROR_OS, "unable to stat packed-refs '%s'", backend->refcache->path); - return -1; - } - - if (st.st_size == 0) { - p_close(fd); - git_mutex_unlock(&backend->prlock); - return 0; - } - - git_futils_filestamp_set_from_stat(&backend->packed_refs_stamp, &st); - -#ifdef GIT_WIN32 - /* on windows, we copy the entire file into memory rather than using - * mmap() because using mmap() on windows also locks the file and this - * map is long-lived. */ - backend->packed_refs_map.len = (size_t)st.st_size; - backend->packed_refs_map.data = - git__malloc(backend->packed_refs_map.len); - GIT_ERROR_CHECK_ALLOC(backend->packed_refs_map.data); - { - ssize_t bytesread = - p_read(fd, backend->packed_refs_map.data, - backend->packed_refs_map.len); - error = (bytesread == (ssize_t)backend->packed_refs_map.len) ? 0 : -1; - } -#else - error = git_futils_mmap_ro(&backend->packed_refs_map, fd, 0, (size_t)st.st_size); -#endif - p_close(fd); - if (error < 0) { - git_mutex_unlock(&backend->prlock); - return error; - } - - packed_set_peeling_mode( - backend->packed_refs_map.data, backend->packed_refs_map.len, - backend); - - git_mutex_unlock(&backend->prlock); - return error; -} - -/* - * Find beginning of packed-ref record pointed to by p. - * buf - a lower-bound pointer to some memory buffer - * p - an upper-bound pointer to the same memory buffer - */ -static const char *start_of_record(const char *buf, const char *p) -{ - const char *nl = p; - while (true) { - nl = git__memrchr(buf, '\n', nl - buf); - if (!nl) - return buf; - - if (nl[1] == '^' && nl > buf) - --nl; - else - break; - }; - return nl + 1; -} - -/* - * Find end of packed-ref record pointed to by p. - * end - an upper-bound pointer to some memory buffer - * p - a lower-bound pointer to the same memory buffer - */ -static const char *end_of_record(const char *p, const char *end) -{ - while (1) { - size_t sz = end - p; - p = memchr(p, '\n', sz); - if (!p) - return end; - ++p; - if (p < end && p[0] == '^') - ++p; - else - break; - } - return p; -} - -static int -cmp_record_to_refname(const char *rec, size_t data_end, const char *ref_name) -{ - const size_t ref_len = strlen(ref_name); - int cmp_val; - const char *end; - - rec += GIT_OID_HEXSZ + 1; /* + space */ - if (data_end < GIT_OID_HEXSZ + 3) { - /* an incomplete (corrupt) record is treated as less than ref_name */ - return -1; - } - data_end -= GIT_OID_HEXSZ + 1; - - end = memchr(rec, '\n', data_end); - if (end) - data_end = end - rec; - - cmp_val = memcmp(rec, ref_name, min(ref_len, data_end)); - - if (cmp_val == 0 && data_end != ref_len) - return (data_end > ref_len) ? 1 : -1; - return cmp_val; -} - -static int packed_unsorted_lookup( - git_reference **out, - refdb_fs_backend *backend, - const char *ref_name) -{ - int error = 0; - struct packref *entry; - - if ((error = packed_reload(backend)) < 0) - return error; - - if (git_sortedcache_rlock(backend->refcache) < 0) - return -1; - - entry = git_sortedcache_lookup(backend->refcache, ref_name); - if (!entry) { - error = ref_error_notfound(ref_name); - } else { - *out = git_reference__alloc(ref_name, &entry->oid, &entry->peel); - if (!*out) - error = -1; - } - - git_sortedcache_runlock(backend->refcache); - - return error; -} - -static int packed_lookup( - git_reference **out, - refdb_fs_backend *backend, - const char *ref_name) -{ - int error = 0; - const char *left, *right, *data_end; - - if ((error = packed_map_check(backend)) < 0) - return error; - - if (!backend->sorted) - return packed_unsorted_lookup(out, backend, ref_name); - - left = backend->packed_refs_map.data; - right = data_end = (const char *) backend->packed_refs_map.data + - backend->packed_refs_map.len; - - while (left < right && *left == '#') { - if (!(left = memchr(left, '\n', data_end - left))) - goto parse_failed; - left++; - } - - while (left < right) { - const char *mid, *rec; - int compare; - - mid = left + (right - left) / 2; - rec = start_of_record(left, mid); - compare = cmp_record_to_refname(rec, data_end - rec, ref_name); - - if (compare < 0) { - left = end_of_record(mid, right); - } else if (compare > 0) { - right = rec; - } else { - const char *eol; - git_oid oid, peel, *peel_ptr = NULL; - - if (data_end - rec < GIT_OID_HEXSZ || - git_oid_fromstr(&oid, rec) < 0) { - goto parse_failed; - } - rec += GIT_OID_HEXSZ + 1; - if (!(eol = memchr(rec, '\n', data_end - rec))) { - goto parse_failed; - } - - /* look for optional "^\n" */ - - if (eol + 1 < data_end) { - rec = eol + 1; - - if (*rec == '^') { - rec++; - if (data_end - rec < GIT_OID_HEXSZ || - git_oid_fromstr(&peel, rec) < 0) { - goto parse_failed; - } - peel_ptr = &peel; - } - } - - *out = git_reference__alloc(ref_name, &oid, peel_ptr); - if (!*out) { - return -1; - } - - return 0; - } - } - return GIT_ENOTFOUND; - -parse_failed: - git_error_set(GIT_ERROR_REFERENCE, "corrupted packed references file"); - return -1; -} - -static int refdb_fs_backend__lookup( - git_reference **out, - git_refdb_backend *_backend, - const char *ref_name) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - int error; - - GIT_ASSERT_ARG(backend); - - if (!(error = loose_lookup(out, backend, ref_name))) - return 0; - - /* only try to lookup this reference on the packfile if it - * wasn't found on the loose refs; not if there was a critical error */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = packed_lookup(out, backend, ref_name); - } - return error; -} - -typedef struct { - git_reference_iterator parent; - - char *glob; - - git_pool pool; - git_vector loose; - - git_sortedcache *cache; - size_t loose_pos; - size_t packed_pos; -} refdb_fs_iter; - -static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) -{ - refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent); - - git_vector_free(&iter->loose); - git_pool_clear(&iter->pool); - git_sortedcache_free(iter->cache); - git__free(iter); -} - -static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) -{ - int error = 0; - git_str path = GIT_STR_INIT; - git_iterator *fsit = NULL; - git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry = NULL; - const char *ref_prefix = GIT_REFS_DIR; - size_t ref_prefix_len = strlen(ref_prefix); - - if (!backend->commonpath) /* do nothing if no commonpath for loose refs */ - return 0; - - fsit_opts.flags = backend->iterator_flags; - - if (iter->glob) { - const char *last_sep = NULL; - const char *pos; - for (pos = iter->glob; *pos; ++pos) { - switch (*pos) { - case '?': - case '*': - case '[': - case '\\': - break; - case '/': - last_sep = pos; - /* FALLTHROUGH */ - default: - continue; - } - break; - } - if (last_sep) { - ref_prefix = iter->glob; - ref_prefix_len = (last_sep - ref_prefix) + 1; - } - } - - if ((error = git_str_puts(&path, backend->commonpath)) < 0 || - (error = git_str_put(&path, ref_prefix, ref_prefix_len)) < 0) { - git_str_dispose(&path); - return error; - } - - if ((error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) { - git_str_dispose(&path); - return (iter->glob && error == GIT_ENOTFOUND)? 0 : error; - } - - error = git_str_sets(&path, ref_prefix); - - while (!error && !git_iterator_advance(&entry, fsit)) { - const char *ref_name; - char *ref_dup; - - git_str_truncate(&path, ref_prefix_len); - git_str_puts(&path, entry->path); - ref_name = git_str_cstr(&path); - - if (git__suffixcmp(ref_name, ".lock") == 0 || - (iter->glob && wildmatch(iter->glob, ref_name, 0) != 0)) - continue; - - ref_dup = git_pool_strdup(&iter->pool, ref_name); - if (!ref_dup) - error = -1; - else - error = git_vector_insert(&iter->loose, ref_dup); - } - - git_iterator_free(fsit); - git_str_dispose(&path); - - return error; -} - -static int refdb_fs_backend__iterator_next( - git_reference **out, git_reference_iterator *_iter) -{ - int error = GIT_ITEROVER; - refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent); - refdb_fs_backend *backend = GIT_CONTAINER_OF(iter->parent.db->backend, refdb_fs_backend, parent); - struct packref *ref; - - while (iter->loose_pos < iter->loose.length) { - const char *path = git_vector_get(&iter->loose, iter->loose_pos++); - - if (loose_lookup(out, backend, path) == 0) { - ref = git_sortedcache_lookup(iter->cache, path); - if (ref) - ref->flags |= PACKREF_SHADOWED; - - return 0; - } - - git_error_clear(); - } - - error = GIT_ITEROVER; - while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { - ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); - if (!ref) /* stop now if another thread deleted refs and we past end */ - break; - - if (ref->flags & PACKREF_SHADOWED) - continue; - if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0) - continue; - - *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel); - error = (*out != NULL) ? 0 : -1; - break; - } - - return error; -} - -static int refdb_fs_backend__iterator_next_name( - const char **out, git_reference_iterator *_iter) -{ - int error = GIT_ITEROVER; - refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent); - refdb_fs_backend *backend = GIT_CONTAINER_OF(iter->parent.db->backend, refdb_fs_backend, parent); - struct packref *ref; - - while (iter->loose_pos < iter->loose.length) { - const char *path = git_vector_get(&iter->loose, iter->loose_pos++); - struct packref *ref; - - if (loose_lookup(NULL, backend, path) == 0) { - ref = git_sortedcache_lookup(iter->cache, path); - if (ref) - ref->flags |= PACKREF_SHADOWED; - - *out = path; - return 0; - } - - git_error_clear(); - } - - error = GIT_ITEROVER; - while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { - ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); - if (!ref) /* stop now if another thread deleted refs and we past end */ - break; - - if (ref->flags & PACKREF_SHADOWED) - continue; - if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0) - continue; - - *out = ref->name; - error = 0; - break; - } - - return error; -} - -static int refdb_fs_backend__iterator( - git_reference_iterator **out, git_refdb_backend *_backend, const char *glob) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - refdb_fs_iter *iter = NULL; - int error; - - GIT_ASSERT_ARG(backend); - - iter = git__calloc(1, sizeof(refdb_fs_iter)); - GIT_ERROR_CHECK_ALLOC(iter); - - if ((error = git_pool_init(&iter->pool, 1)) < 0) - goto out; - - if ((error = git_vector_init(&iter->loose, 8, NULL)) < 0) - goto out; - - if (glob != NULL && - (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL) { - error = GIT_ERROR_NOMEMORY; - goto out; - } - - if ((error = iter_load_loose_paths(backend, iter)) < 0) - goto out; - - if ((error = packed_reload(backend)) < 0) - goto out; - - if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0) - goto out; - - iter->parent.next = refdb_fs_backend__iterator_next; - iter->parent.next_name = refdb_fs_backend__iterator_next_name; - iter->parent.free = refdb_fs_backend__iterator_free; - - *out = (git_reference_iterator *)iter; -out: - if (error) - refdb_fs_backend__iterator_free((git_reference_iterator *)iter); - return error; -} - -static bool ref_is_available( - const char *old_ref, const char *new_ref, const char *this_ref) -{ - if (old_ref == NULL || strcmp(old_ref, this_ref)) { - size_t reflen = strlen(this_ref); - size_t newlen = strlen(new_ref); - size_t cmplen = reflen < newlen ? reflen : newlen; - const char *lead = reflen < newlen ? new_ref : this_ref; - - if (!strncmp(new_ref, this_ref, cmplen) && lead[cmplen] == '/') { - return false; - } - } - - return true; -} - -static int reference_path_available( - refdb_fs_backend *backend, - const char *new_ref, - const char *old_ref, - int force) -{ - size_t i; - int error; - - if ((error = packed_reload(backend)) < 0) - return error; - - if (!force) { - int exists; - - if ((error = refdb_fs_backend__exists( - &exists, (git_refdb_backend *)backend, new_ref)) < 0) { - return error; - } - - if (exists) { - git_error_set(GIT_ERROR_REFERENCE, - "failed to write reference '%s': a reference with " - "that name already exists.", new_ref); - return GIT_EEXISTS; - } - } - - if ((error = git_sortedcache_rlock(backend->refcache)) < 0) - return error; - - for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { - struct packref *ref = git_sortedcache_entry(backend->refcache, i); - - if (ref && !ref_is_available(old_ref, new_ref, ref->name)) { - git_sortedcache_runlock(backend->refcache); - git_error_set(GIT_ERROR_REFERENCE, - "path to reference '%s' collides with existing one", new_ref); - return -1; - } - } - - git_sortedcache_runlock(backend->refcache); - return 0; -} - -static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name) -{ - int error, filebuf_flags; - git_str ref_path = GIT_STR_INIT; - const char *basedir; - - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(name); - - if (!git_path_is_valid(backend->repo, name, 0, GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS)) { - git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", name); - return GIT_EINVALIDSPEC; - } - - if (is_per_worktree_ref(name)) - basedir = backend->gitpath; - else - basedir = backend->commonpath; - - /* Remove a possibly existing empty directory hierarchy - * which name would collide with the reference name - */ - if ((error = git_futils_rmdir_r(name, basedir, GIT_RMDIR_SKIP_NONEMPTY)) < 0) - return error; - - if ((error = loose_path(&ref_path, basedir, name)) < 0) - return error; - - filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS; - if (backend->fsync) - filebuf_flags |= GIT_FILEBUF_FSYNC; - - error = git_filebuf_open(file, ref_path.ptr, filebuf_flags, GIT_REFS_FILE_MODE); - - if (error == GIT_EDIRECTORY) - git_error_set(GIT_ERROR_REFERENCE, "cannot lock ref '%s', there are refs beneath that folder", name); - - git_str_dispose(&ref_path); - return error; -} - -static int loose_commit(git_filebuf *file, const git_reference *ref) -{ - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(ref); - - if (ref->type == GIT_REFERENCE_DIRECT) { - char oid[GIT_OID_HEXSZ + 1]; - git_oid_nfmt(oid, sizeof(oid), &ref->target.oid); - - git_filebuf_printf(file, "%s\n", oid); - } else if (ref->type == GIT_REFERENCE_SYMBOLIC) { - git_filebuf_printf(file, GIT_SYMREF "%s\n", ref->target.symbolic); - } else { - GIT_ASSERT(0); - } - - return git_filebuf_commit(file); -} - -static int refdb_fs_backend__lock(void **out, git_refdb_backend *_backend, const char *refname) -{ - int error; - git_filebuf *lock; - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - - lock = git__calloc(1, sizeof(git_filebuf)); - GIT_ERROR_CHECK_ALLOC(lock); - - if ((error = loose_lock(lock, backend, refname)) < 0) { - git__free(lock); - return error; - } - - *out = lock; - return 0; -} - -static int refdb_fs_backend__write_tail( - git_refdb_backend *_backend, - const git_reference *ref, - git_filebuf *file, - int update_reflog, - const git_oid *old_id, - const char *old_target, - const git_signature *who, - const char *message); - -static int refdb_fs_backend__delete_tail( - git_refdb_backend *_backend, - git_filebuf *file, - const char *ref_name, - const git_oid *old_id, - const char *old_target); - -static int refdb_fs_backend__unlock(git_refdb_backend *backend, void *payload, int success, int update_reflog, - const git_reference *ref, const git_signature *sig, const char *message) -{ - git_filebuf *lock = (git_filebuf *) payload; - int error = 0; - - if (success == 2) - error = refdb_fs_backend__delete_tail(backend, lock, ref->name, NULL, NULL); - else if (success) - error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, NULL, NULL, sig, message); - else - git_filebuf_cleanup(lock); - - git__free(lock); - return error; -} - -/* - * Find out what object this reference resolves to. - * - * For references that point to a 'big' tag (e.g. an - * actual tag object on the repository), we need to - * cache on the packfile the OID of the object to - * which that 'big tag' is pointing to. - */ -static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref) -{ - git_object *object; - - if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL) - return 0; - - /* - * Find the tagged object in the repository - */ - if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJECT_ANY) < 0) - return -1; - - /* - * If the tagged object is a Tag object, we need to resolve it; - * if the ref is actually a 'weak' ref, we don't need to resolve - * anything. - */ - if (git_object_type(object) == GIT_OBJECT_TAG) { - git_tag *tag = (git_tag *)object; - - /* - * Find the object pointed at by this tag - */ - git_oid_cpy(&ref->peel, git_tag_target_id(tag)); - ref->flags |= PACKREF_HAS_PEEL; - - /* - * The reference has now cached the resolved OID, and is - * marked at such. When written to the packfile, it'll be - * accompanied by this resolved oid - */ - } - - git_object_free(object); - return 0; -} - -/* - * Write a single reference into a packfile - */ -static int packed_write_ref(struct packref *ref, git_filebuf *file) -{ - char oid[GIT_OID_HEXSZ + 1]; - git_oid_nfmt(oid, sizeof(oid), &ref->oid); - - /* - * For references that peel to an object in the repo, we must - * write the resulting peel on a separate line, e.g. - * - * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4 - * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100 - * - * This obviously only applies to tags. - * The required peels have already been loaded into `ref->peel_target`. - */ - if (ref->flags & PACKREF_HAS_PEEL) { - char peel[GIT_OID_HEXSZ + 1]; - git_oid_nfmt(peel, sizeof(peel), &ref->peel); - - if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) - return -1; - } else { - if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0) - return -1; - } - - return 0; -} - -/* - * Remove all loose references - * - * Once we have successfully written a packfile, - * all the loose references that were packed must be - * removed from disk. - * - * This is a dangerous method; make sure the packfile - * is well-written, because we are destructing references - * here otherwise. - */ -static int packed_remove_loose(refdb_fs_backend *backend) -{ - size_t i; - git_filebuf lock = GIT_FILEBUF_INIT; - git_str ref_content = GIT_STR_INIT; - int error = 0; - - /* backend->refcache is already locked when this is called */ - - for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { - struct packref *ref = git_sortedcache_entry(backend->refcache, i); - git_oid current_id; - - if (!ref || !(ref->flags & PACKREF_WAS_LOOSE)) - continue; - - git_filebuf_cleanup(&lock); - - /* We need to stop anybody from updating the ref while we try to do a safe delete */ - error = loose_lock(&lock, backend, ref->name); - /* If someone else is updating it, let them do it */ - if (error == GIT_EEXISTS || error == GIT_ENOTFOUND) - continue; - - if (error < 0) { - git_str_dispose(&ref_content); - git_error_set(GIT_ERROR_REFERENCE, "failed to lock loose reference '%s'", ref->name); - return error; - } - - error = git_futils_readbuffer(&ref_content, lock.path_original); - /* Someone else beat us to cleaning up the ref, let's simply continue */ - if (error == GIT_ENOTFOUND) - continue; - - /* This became a symref between us packing and trying to delete it, so ignore it */ - if (!git__prefixcmp(ref_content.ptr, GIT_SYMREF)) - continue; - - /* Figure out the current id; if we find a bad ref file, skip it so we can do the rest */ - if (loose_parse_oid(¤t_id, lock.path_original, &ref_content) < 0) - continue; - - /* If the ref moved since we packed it, we must not delete it */ - if (!git_oid_equal(¤t_id, &ref->oid)) - continue; - - /* - * if we fail to remove a single file, this is *not* good, - * but we should keep going and remove as many as possible. - * If we fail to remove, the ref is still in the old state, so - * we haven't lost information. - */ - p_unlink(lock.path_original); - } - - git_str_dispose(&ref_content); - git_filebuf_cleanup(&lock); - return 0; -} - -/* - * Write all the contents in the in-memory packfile to disk. - */ -static int packed_write(refdb_fs_backend *backend) -{ - git_sortedcache *refcache = backend->refcache; - git_filebuf pack_file = GIT_FILEBUF_INIT; - int error, open_flags = 0; - size_t i; - - /* take lock and close up packed-refs mmap if open */ - if ((error = git_mutex_lock(&backend->prlock)) < 0) { - return error; - } - - packed_map_free(backend); - - git_mutex_unlock(&backend->prlock); - - /* lock the cache to updates while we do this */ - if ((error = git_sortedcache_wlock(refcache)) < 0) - return error; - - if (backend->fsync) - open_flags = GIT_FILEBUF_FSYNC; - - /* Open the file! */ - if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), open_flags, GIT_PACKEDREFS_FILE_MODE)) < 0) - goto fail; - - /* Packfiles have a header... apparently - * This is in fact not required, but we might as well print it - * just for kicks */ - if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < 0) - goto fail; - - for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) { - struct packref *ref = git_sortedcache_entry(refcache, i); - GIT_ASSERT(ref); - - if ((error = packed_find_peel(backend, ref)) < 0) - goto fail; - - if ((error = packed_write_ref(ref, &pack_file)) < 0) - goto fail; - } - - /* if we've written all the references properly, we can commit - * the packfile to make the changes effective */ - if ((error = git_filebuf_commit(&pack_file)) < 0) - goto fail; - - /* when and only when the packfile has been properly written, - * we can go ahead and remove the loose refs */ - if ((error = packed_remove_loose(backend)) < 0) - goto fail; - - git_sortedcache_updated(refcache); - git_sortedcache_wunlock(refcache); - - /* we're good now */ - return 0; - -fail: - git_filebuf_cleanup(&pack_file); - git_sortedcache_wunlock(refcache); - - return error; -} - -static int packed_delete(refdb_fs_backend *backend, const char *ref_name) -{ - size_t pack_pos; - int error, found = 0; - - if ((error = packed_reload(backend)) < 0) - goto cleanup; - - if ((error = git_sortedcache_wlock(backend->refcache)) < 0) - goto cleanup; - - /* If a packed reference exists, remove it from the packfile and repack if necessary */ - error = git_sortedcache_lookup_index(&pack_pos, backend->refcache, ref_name); - if (error == 0) { - error = git_sortedcache_remove(backend->refcache, pack_pos); - found = 1; - } - if (error == GIT_ENOTFOUND) - error = 0; - - git_sortedcache_wunlock(backend->refcache); - - if (found) - error = packed_write(backend); - -cleanup: - return error; -} - -static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *author, const char *message); - -static int cmp_old_ref(int *cmp, git_refdb_backend *backend, const char *name, - const git_oid *old_id, const char *old_target) -{ - int error = 0; - git_reference *old_ref = NULL; - - *cmp = 0; - /* It "matches" if there is no old value to compare against */ - if (!old_id && !old_target) - return 0; - - if ((error = refdb_fs_backend__lookup(&old_ref, backend, name)) < 0) { - if (error == GIT_ENOTFOUND && old_id && git_oid_is_zero(old_id)) - return 0; - goto out; - } - - /* If the types don't match, there's no way the values do */ - if (old_id && old_ref->type != GIT_REFERENCE_DIRECT) { - *cmp = -1; - goto out; - } - if (old_target && old_ref->type != GIT_REFERENCE_SYMBOLIC) { - *cmp = 1; - goto out; - } - - if (old_id && old_ref->type == GIT_REFERENCE_DIRECT) - *cmp = git_oid_cmp(old_id, &old_ref->target.oid); - - if (old_target && old_ref->type == GIT_REFERENCE_SYMBOLIC) - *cmp = git__strcmp(old_target, old_ref->target.symbolic); - -out: - git_reference_free(old_ref); - - return error; -} - -/* - * The git.git comment regarding this, for your viewing pleasure: - * - * Special hack: If a branch is updated directly and HEAD - * points to it (may happen on the remote side of a push - * for example) then logically the HEAD reflog should be - * updated too. - * A generic solution implies reverse symref information, - * but finding all symrefs pointing to the given branch - * would be rather costly for this rare event (the direct - * update of a branch) to be worth it. So let's cheat and - * check with HEAD only which should cover 99% of all usage - * scenarios (even 100% of the default ones). - */ -static int maybe_append_head(refdb_fs_backend *backend, const git_reference *ref, const git_signature *who, const char *message) -{ - git_reference *head = NULL; - git_refdb *refdb = NULL; - int error, write_reflog; - git_oid old_id; - - if ((error = git_repository_refdb(&refdb, backend->repo)) < 0 || - (error = git_refdb_should_write_head_reflog(&write_reflog, refdb, ref)) < 0) - goto out; - if (!write_reflog) - goto out; - - /* if we can't resolve, we use {0}*40 as old id */ - if (git_reference_name_to_id(&old_id, backend->repo, ref->name) < 0) - memset(&old_id, 0, sizeof(old_id)); - - if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0 || - (error = reflog_append(backend, head, &old_id, git_reference_target(ref), who, message)) < 0) - goto out; - -out: - git_reference_free(head); - git_refdb_free(refdb); - return error; -} - -static int refdb_fs_backend__write( - git_refdb_backend *_backend, - const git_reference *ref, - int force, - const git_signature *who, - const char *message, - const git_oid *old_id, - const char *old_target) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - git_filebuf file = GIT_FILEBUF_INIT; - int error = 0; - - GIT_ASSERT_ARG(backend); - - if ((error = reference_path_available(backend, ref->name, NULL, force)) < 0) - return error; - - /* We need to perform the reflog append and old value check under the ref's lock */ - if ((error = loose_lock(&file, backend, ref->name)) < 0) - return error; - - return refdb_fs_backend__write_tail(_backend, ref, &file, true, old_id, old_target, who, message); -} - -static int refdb_fs_backend__write_tail( - git_refdb_backend *_backend, - const git_reference *ref, - git_filebuf *file, - int update_reflog, - const git_oid *old_id, - const char *old_target, - const git_signature *who, - const char *message) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - int error = 0, cmp = 0, should_write; - const char *new_target = NULL; - const git_oid *new_id = NULL; - - if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0) - goto on_error; - - if (cmp) { - git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match"); - error = GIT_EMODIFIED; - goto on_error; - } - - if (ref->type == GIT_REFERENCE_SYMBOLIC) - new_target = ref->target.symbolic; - else - new_id = &ref->target.oid; - - error = cmp_old_ref(&cmp, _backend, ref->name, new_id, new_target); - if (error < 0 && error != GIT_ENOTFOUND) - goto on_error; - - /* Don't update if we have the same value */ - if (!error && !cmp) { - error = 0; - goto on_error; /* not really error */ - } - - if (update_reflog) { - git_refdb *refdb; - - if ((error = git_repository_refdb__weakptr(&refdb, backend->repo)) < 0 || - (error = git_refdb_should_write_reflog(&should_write, refdb, ref)) < 0) - goto on_error; - - if (should_write) { - if ((error = reflog_append(backend, ref, NULL, NULL, who, message)) < 0) - goto on_error; - if ((error = maybe_append_head(backend, ref, who, message)) < 0) - goto on_error; - } - } - - return loose_commit(file, ref); - -on_error: - git_filebuf_cleanup(file); - return error; -} - -static int refdb_fs_backend__prune_refs( - refdb_fs_backend *backend, - const char *ref_name, - const char *prefix) -{ - git_str relative_path = GIT_STR_INIT; - git_str base_path = GIT_STR_INIT; - size_t commonlen; - int error; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(ref_name); - - if ((error = git_str_sets(&relative_path, ref_name)) < 0) - goto cleanup; - - git_fs_path_squash_slashes(&relative_path); - if ((commonlen = git_fs_path_common_dirlen("refs/heads/", git_str_cstr(&relative_path))) == strlen("refs/heads/") || - (commonlen = git_fs_path_common_dirlen("refs/tags/", git_str_cstr(&relative_path))) == strlen("refs/tags/") || - (commonlen = git_fs_path_common_dirlen("refs/remotes/", git_str_cstr(&relative_path))) == strlen("refs/remotes/")) { - - git_str_truncate(&relative_path, commonlen); - - if (prefix) - error = git_str_join3(&base_path, '/', - backend->commonpath, prefix, - git_str_cstr(&relative_path)); - else - error = git_str_joinpath(&base_path, - backend->commonpath, - git_str_cstr(&relative_path)); - - if (!error) - error = git_path_validate_str_length(NULL, &base_path); - - if (error < 0) - goto cleanup; - - error = git_futils_rmdir_r(ref_name + commonlen, - git_str_cstr(&base_path), - GIT_RMDIR_EMPTY_PARENTS | GIT_RMDIR_SKIP_ROOT); - - if (error == GIT_ENOTFOUND) - error = 0; - } - -cleanup: - git_str_dispose(&relative_path); - git_str_dispose(&base_path); - return error; -} - -static int refdb_fs_backend__delete( - git_refdb_backend *_backend, - const char *ref_name, - const git_oid *old_id, const char *old_target) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - git_filebuf file = GIT_FILEBUF_INIT; - int error = 0; - - GIT_ASSERT_ARG(backend); - GIT_ASSERT_ARG(ref_name); - - if ((error = loose_lock(&file, backend, ref_name)) < 0) - return error; - - if ((error = refdb_reflog_fs__delete(_backend, ref_name)) < 0) { - git_filebuf_cleanup(&file); - return error; - } - - return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target); -} - -static int loose_delete(refdb_fs_backend *backend, const char *ref_name) -{ - git_str path = GIT_STR_INIT; - int error = 0; - - if ((error = loose_path(&path, backend->commonpath, ref_name)) < 0) - return error; - - error = p_unlink(path.ptr); - if (error < 0 && errno == ENOENT) - error = GIT_ENOTFOUND; - else if (error != 0) - error = -1; - - git_str_dispose(&path); - - return error; -} - -static int refdb_fs_backend__delete_tail( - git_refdb_backend *_backend, - git_filebuf *file, - const char *ref_name, - const git_oid *old_id, const char *old_target) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - int error = 0, cmp = 0; - bool packed_deleted = 0; - - error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target); - if (error < 0) - goto cleanup; - - if (cmp) { - git_error_set(GIT_ERROR_REFERENCE, "old reference value does not match"); - error = GIT_EMODIFIED; - goto cleanup; - } - - /* - * To ensure that an external observer will see either the current ref value - * (because the loose ref still exists), or a missing ref (after the packed-file is - * unlocked, there will be nothing left), we must ensure things happen in the - * following order: - * - * - the packed-ref file is locked and loaded, as well as a loose one, if it exists - * - we optimistically delete a packed ref, keeping track of whether it existed - * - we delete the loose ref, note that we have its .lock - * - the loose ref is "unlocked", then the packed-ref file is rewritten and unlocked - * - we should prune the path components if a loose ref was deleted - * - * Note that, because our packed backend doesn't expose its filesystem lock, - * we might not be able to guarantee that this is what actually happens (ie. - * as our current code never write packed-refs.lock, nothing stops observers - * from grabbing a "stale" value from there). - */ - if ((error = packed_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - if (error == 0) - packed_deleted = 1; - - if ((error = loose_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - if (error == GIT_ENOTFOUND) { - error = packed_deleted ? 0 : ref_error_notfound(ref_name); - goto cleanup; - } - -cleanup: - git_filebuf_cleanup(file); - if (error == 0) - error = refdb_fs_backend__prune_refs(backend, ref_name, ""); - return error; -} - -static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name); - -static int refdb_fs_backend__rename( - git_reference **out, - git_refdb_backend *_backend, - const char *old_name, - const char *new_name, - int force, - const git_signature *who, - const char *message) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - git_reference *old, *new = NULL; - git_filebuf file = GIT_FILEBUF_INIT; - int error; - - GIT_ASSERT_ARG(backend); - - if ((error = reference_path_available( - backend, new_name, old_name, force)) < 0 || - (error = refdb_fs_backend__lookup(&old, _backend, old_name)) < 0) - return error; - - if ((error = refdb_fs_backend__delete(_backend, old_name, NULL, NULL)) < 0) { - git_reference_free(old); - return error; - } - - new = git_reference__realloc(&old, new_name); - if (!new) { - git_reference_free(old); - return -1; - } - - if ((error = loose_lock(&file, backend, new->name)) < 0) { - git_reference_free(new); - return error; - } - - /* Try to rename the refog; it's ok if the old doesn't exist */ - error = refdb_reflog_fs__rename(_backend, old_name, new_name); - if (((error == 0) || (error == GIT_ENOTFOUND)) && - ((error = reflog_append(backend, new, git_reference_target(new), NULL, who, message)) < 0)) { - git_reference_free(new); - git_filebuf_cleanup(&file); - return error; - } - - if (error < 0) { - git_reference_free(new); - git_filebuf_cleanup(&file); - return error; - } - - - if ((error = loose_commit(&file, new)) < 0 || out == NULL) { - git_reference_free(new); - return error; - } - - *out = new; - return 0; -} - -static int refdb_fs_backend__compress(git_refdb_backend *_backend) -{ - int error; - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - - GIT_ASSERT_ARG(backend); - - if ((error = packed_reload(backend)) < 0 || /* load the existing packfile */ - (error = packed_loadloose(backend)) < 0 || /* add all the loose refs */ - (error = packed_write(backend)) < 0) /* write back to disk */ - return error; - - return 0; -} - -static void refdb_fs_backend__free(git_refdb_backend *_backend) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - - if (!backend) - return; - - git_sortedcache_free(backend->refcache); - - git_mutex_lock(&backend->prlock); - packed_map_free(backend); - git_mutex_unlock(&backend->prlock); - git_mutex_free(&backend->prlock); - - git__free(backend->gitpath); - git__free(backend->commonpath); - git__free(backend); -} - -static char *setup_namespace(git_repository *repo, const char *in) -{ - git_str path = GIT_STR_INIT; - char *parts, *start, *end, *out = NULL; - - if (!in) - goto done; - - git_str_puts(&path, in); - - /* if the repo is not namespaced, nothing else to do */ - if (repo->namespace == NULL) { - out = git_str_detach(&path); - goto done; - } - - parts = end = git__strdup(repo->namespace); - if (parts == NULL) - goto done; - - /* - * From `man gitnamespaces`: - * namespaces which include a / will expand to a hierarchy - * of namespaces; for example, GIT_NAMESPACE=foo/bar will store - * refs under refs/namespaces/foo/refs/namespaces/bar/ - */ - while ((start = git__strsep(&end, "/")) != NULL) - git_str_printf(&path, "refs/namespaces/%s/", start); - - git_str_printf(&path, "refs/namespaces/%s/refs", end); - git__free(parts); - - /* Make sure that the folder with the namespace exists */ - if (git_futils_mkdir_relative(git_str_cstr(&path), in, 0777, - GIT_MKDIR_PATH, NULL) < 0) - goto done; - - /* Return root of the namespaced gitpath, i.e. without the trailing 'refs' */ - git_str_rtruncate_at_char(&path, '/'); - git_str_putc(&path, '/'); - out = git_str_detach(&path); - -done: - git_str_dispose(&path); - return out; -} - -static int reflog_alloc(git_reflog **reflog, const char *name) -{ - git_reflog *log; - - *reflog = NULL; - - log = git__calloc(1, sizeof(git_reflog)); - GIT_ERROR_CHECK_ALLOC(log); - - log->ref_name = git__strdup(name); - GIT_ERROR_CHECK_ALLOC(log->ref_name); - - if (git_vector_init(&log->entries, 0, NULL) < 0) { - git__free(log->ref_name); - git__free(log); - return -1; - } - - *reflog = log; - - return 0; -} - -static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) -{ - git_parse_ctx parser = GIT_PARSE_CTX_INIT; - - if ((git_parse_ctx_init(&parser, buf, buf_size)) < 0) - return -1; - - for (; parser.remain_len; git_parse_advance_line(&parser)) { - git_reflog_entry *entry; - const char *sig; - char c; - - entry = git__calloc(1, sizeof(*entry)); - GIT_ERROR_CHECK_ALLOC(entry); - entry->committer = git__calloc(1, sizeof(*entry->committer)); - GIT_ERROR_CHECK_ALLOC(entry->committer); - - if (git_parse_advance_oid(&entry->oid_old, &parser) < 0 || - git_parse_advance_expected(&parser, " ", 1) < 0 || - git_parse_advance_oid(&entry->oid_cur, &parser) < 0) - goto next; - - sig = parser.line; - while (git_parse_peek(&c, &parser, 0) == 0 && c != '\t' && c != '\n') - git_parse_advance_chars(&parser, 1); - - if (git_signature__parse(entry->committer, &sig, parser.line, NULL, 0) < 0) - goto next; - - if (c == '\t') { - size_t len; - git_parse_advance_chars(&parser, 1); - - len = parser.line_len; - if (parser.line[len - 1] == '\n') - len--; - - entry->msg = git__strndup(parser.line, len); - GIT_ERROR_CHECK_ALLOC(entry->msg); - } - - if ((git_vector_insert(&log->entries, entry)) < 0) { - git_reflog_entry__free(entry); - return -1; - } - - continue; - -next: - git_reflog_entry__free(entry); - } - - return 0; -} - -static int create_new_reflog_file(const char *filepath) -{ - int fd, error; - - if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0) - return error; - - if ((fd = p_open(filepath, - O_WRONLY | O_CREAT, - GIT_REFLOG_FILE_MODE)) < 0) - return -1; - - return p_close(fd); -} - -static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name) -{ - refdb_fs_backend *backend; - git_repository *repo; - git_str path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(_backend && name); - - backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - repo = backend->repo; - - if ((error = reflog_path(&path, repo, name)) < 0) - return error; - - error = create_new_reflog_file(git_str_cstr(&path)); - git_str_dispose(&path); - - return error; -} - -static int has_reflog(git_repository *repo, const char *name) -{ - int ret = 0; - git_str path = GIT_STR_INIT; - - if (reflog_path(&path, repo, name) < 0) - goto cleanup; - - ret = git_fs_path_isfile(git_str_cstr(&path)); - -cleanup: - git_str_dispose(&path); - return ret; -} - -static int refdb_reflog_fs__has_log(git_refdb_backend *_backend, const char *name) -{ - refdb_fs_backend *backend; - - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(name); - - backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - - return has_reflog(backend->repo, name); -} - -static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend, const char *name) -{ - int error = -1; - git_str log_path = GIT_STR_INIT; - git_str log_file = GIT_STR_INIT; - git_reflog *log = NULL; - git_repository *repo; - refdb_fs_backend *backend; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(name); - - backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - repo = backend->repo; - - if (reflog_alloc(&log, name) < 0) - return -1; - - if (reflog_path(&log_path, repo, name) < 0) - goto cleanup; - - error = git_futils_readbuffer(&log_file, git_str_cstr(&log_path)); - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - if ((error == GIT_ENOTFOUND) && - ((error = create_new_reflog_file(git_str_cstr(&log_path))) < 0)) - goto cleanup; - - if ((error = reflog_parse(log, - git_str_cstr(&log_file), git_str_len(&log_file))) < 0) - goto cleanup; - - *out = log; - goto success; - -cleanup: - git_reflog_free(log); - -success: - git_str_dispose(&log_file); - git_str_dispose(&log_path); - - return error; -} - -static int serialize_reflog_entry( - git_str *buf, - const git_oid *oid_old, - const git_oid *oid_new, - const git_signature *committer, - const char *msg) -{ - char raw_old[GIT_OID_HEXSZ+1]; - char raw_new[GIT_OID_HEXSZ+1]; - - git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old); - git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new); - - git_str_clear(buf); - - git_str_puts(buf, raw_old); - git_str_putc(buf, ' '); - git_str_puts(buf, raw_new); - - git_signature__writebuf(buf, " ", committer); - - /* drop trailing LF */ - git_str_rtrim(buf); - - if (msg) { - size_t i; - - git_str_putc(buf, '\t'); - git_str_puts(buf, msg); - - for (i = 0; i < buf->size - 2; i++) - if (buf->ptr[i] == '\n') - buf->ptr[i] = ' '; - git_str_rtrim(buf); - } - - git_str_putc(buf, '\n'); - - return git_str_oom(buf); -} - -static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char *refname) -{ - git_repository *repo; - git_str log_path = GIT_STR_INIT; - int error; - - repo = backend->repo; - - if (!git_path_is_valid(backend->repo, refname, 0, GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS)) { - git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", refname); - return GIT_EINVALIDSPEC; - } - - if (reflog_path(&log_path, repo, refname) < 0) - return -1; - - if (!git_fs_path_isfile(git_str_cstr(&log_path))) { - git_error_set(GIT_ERROR_INVALID, - "log file for reference '%s' doesn't exist", refname); - error = -1; - goto cleanup; - } - - error = git_filebuf_open(file, git_str_cstr(&log_path), 0, GIT_REFLOG_FILE_MODE); - -cleanup: - git_str_dispose(&log_path); - - return error; -} - -static int refdb_reflog_fs__write(git_refdb_backend *_backend, git_reflog *reflog) -{ - int error = -1; - unsigned int i; - git_reflog_entry *entry; - refdb_fs_backend *backend; - git_str log = GIT_STR_INIT; - git_filebuf fbuf = GIT_FILEBUF_INIT; - - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(reflog); - - backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - - if ((error = lock_reflog(&fbuf, backend, reflog->ref_name)) < 0) - return -1; - - git_vector_foreach(&reflog->entries, i, entry) { - if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0) - goto cleanup; - - if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) - goto cleanup; - } - - error = git_filebuf_commit(&fbuf); - goto success; - -cleanup: - git_filebuf_cleanup(&fbuf); - -success: - git_str_dispose(&log); - - return error; -} - -/* Append to the reflog, must be called under reference lock */ -static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *who, const char *message) -{ - int error, is_symbolic, open_flags; - git_oid old_id = {{0}}, new_id = {{0}}; - git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; - git_repository *repo = backend->repo; - - is_symbolic = ref->type == GIT_REFERENCE_SYMBOLIC; - - /* "normal" symbolic updates do not write */ - if (is_symbolic && - strcmp(ref->name, GIT_HEAD_FILE) && - !(old && new)) - return 0; - - /* From here on is_symbolic also means that it's HEAD */ - - if (old) { - git_oid_cpy(&old_id, old); - } else { - error = git_reference_name_to_id(&old_id, repo, ref->name); - if (error < 0 && error != GIT_ENOTFOUND) - return error; - } - - if (new) { - git_oid_cpy(&new_id, new); - } else { - if (!is_symbolic) { - git_oid_cpy(&new_id, git_reference_target(ref)); - } else { - error = git_reference_name_to_id(&new_id, repo, git_reference_symbolic_target(ref)); - if (error < 0 && error != GIT_ENOTFOUND) - return error; - /* detaching HEAD does not create an entry */ - if (error == GIT_ENOTFOUND) - return 0; - - git_error_clear(); - } - } - - if ((error = serialize_reflog_entry(&buf, &old_id, &new_id, who, message)) < 0) - goto cleanup; - - if ((error = reflog_path(&path, repo, ref->name)) < 0) - goto cleanup; - - if (((error = git_futils_mkpath2file(git_str_cstr(&path), 0777)) < 0) && - (error != GIT_EEXISTS)) { - goto cleanup; - } - - /* If the new branch matches part of the namespace of a previously deleted branch, - * there maybe an obsolete/unused directory (or directory hierarchy) in the way. - */ - if (git_fs_path_isdir(git_str_cstr(&path))) { - if ((error = git_futils_rmdir_r(git_str_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY)) < 0) { - if (error == GIT_ENOTFOUND) - error = 0; - } else if (git_fs_path_isdir(git_str_cstr(&path))) { - git_error_set(GIT_ERROR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder", - ref->name); - error = GIT_EDIRECTORY; - } - - if (error != 0) - goto cleanup; - } - - open_flags = O_WRONLY | O_CREAT | O_APPEND; - - if (backend->fsync) - open_flags |= O_FSYNC; - - error = git_futils_writebuffer(&buf, git_str_cstr(&path), open_flags, GIT_REFLOG_FILE_MODE); - -cleanup: - git_str_dispose(&buf); - git_str_dispose(&path); - - return error; -} - -static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name) -{ - int error = 0, fd; - git_str old_path = GIT_STR_INIT; - git_str new_path = GIT_STR_INIT; - git_str temp_path = GIT_STR_INIT; - git_str normalized = GIT_STR_INIT; - git_repository *repo; - refdb_fs_backend *backend; - - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(old_name); - GIT_ASSERT_ARG(new_name); - - backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - repo = backend->repo; - - if ((error = git_reference__normalize_name( - &normalized, new_name, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL)) < 0) - return error; - - if (git_str_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0) - return -1; - - if ((error = loose_path(&old_path, git_str_cstr(&temp_path), old_name)) < 0) - return error; - - if ((error = loose_path(&new_path, git_str_cstr(&temp_path), git_str_cstr(&normalized))) < 0) - return error; - - if (!git_fs_path_exists(git_str_cstr(&old_path))) { - error = GIT_ENOTFOUND; - goto cleanup; - } - - /* - * Move the reflog to a temporary place. This two-phase renaming is required - * in order to cope with funny renaming use cases when one tries to move a reference - * to a partially colliding namespace: - * - a/b -> a/b/c - * - a/b/c/d -> a/b/c - */ - if ((error = loose_path(&temp_path, git_str_cstr(&temp_path), "temp_reflog")) < 0) - return error; - - if ((fd = git_futils_mktmp(&temp_path, git_str_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) { - error = -1; - goto cleanup; - } - - p_close(fd); - - if (p_rename(git_str_cstr(&old_path), git_str_cstr(&temp_path)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name); - error = -1; - goto cleanup; - } - - if (git_fs_path_isdir(git_str_cstr(&new_path)) && - (git_futils_rmdir_r(git_str_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) { - error = -1; - goto cleanup; - } - - if (git_futils_mkpath2file(git_str_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) { - error = -1; - goto cleanup; - } - - if (p_rename(git_str_cstr(&temp_path), git_str_cstr(&new_path)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to rename reflog for %s", new_name); - error = -1; - } - -cleanup: - git_str_dispose(&temp_path); - git_str_dispose(&old_path); - git_str_dispose(&new_path); - git_str_dispose(&normalized); - - return error; -} - -static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name) -{ - refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); - git_str path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(_backend); - GIT_ASSERT_ARG(name); - - if ((error = reflog_path(&path, backend->repo, name)) < 0) - goto out; - - if (!git_fs_path_exists(path.ptr)) - goto out; - - if ((error = p_unlink(path.ptr)) < 0) - goto out; - - error = refdb_fs_backend__prune_refs(backend, name, GIT_REFLOG_DIR); - -out: - git_str_dispose(&path); - - return error; -} - -int git_refdb_backend_fs( - git_refdb_backend **backend_out, - git_repository *repository) -{ - int t = 0; - git_str gitpath = GIT_STR_INIT; - refdb_fs_backend *backend; - - backend = git__calloc(1, sizeof(refdb_fs_backend)); - GIT_ERROR_CHECK_ALLOC(backend); - if (git_mutex_init(&backend->prlock) < 0) { - git__free(backend); - return -1; - } - - - if (git_refdb_init_backend(&backend->parent, GIT_REFDB_BACKEND_VERSION) < 0) - goto fail; - - backend->repo = repository; - - if (repository->gitdir) { - backend->gitpath = setup_namespace(repository, repository->gitdir); - - if (backend->gitpath == NULL) - goto fail; - } - - if (repository->commondir) { - backend->commonpath = setup_namespace(repository, repository->commondir); - - if (backend->commonpath == NULL) - goto fail; - } - - if (git_str_joinpath(&gitpath, backend->commonpath, GIT_PACKEDREFS_FILE) < 0 || - git_sortedcache_new( - &backend->refcache, offsetof(struct packref, name), - NULL, NULL, packref_cmp, git_str_cstr(&gitpath)) < 0) - goto fail; - - git_str_dispose(&gitpath); - - if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_IGNORECASE) && t) { - backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE; - backend->direach_flags |= GIT_FS_PATH_DIR_IGNORE_CASE; - } - if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_PRECOMPOSE) && t) { - backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; - backend->direach_flags |= GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE; - } - if ((!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_FSYNCOBJECTFILES) && t) || - git_repository__fsync_gitdir) - backend->fsync = 1; - backend->iterator_flags |= GIT_ITERATOR_DESCEND_SYMLINKS; - - backend->parent.exists = &refdb_fs_backend__exists; - backend->parent.lookup = &refdb_fs_backend__lookup; - backend->parent.iterator = &refdb_fs_backend__iterator; - backend->parent.write = &refdb_fs_backend__write; - backend->parent.del = &refdb_fs_backend__delete; - backend->parent.rename = &refdb_fs_backend__rename; - backend->parent.compress = &refdb_fs_backend__compress; - backend->parent.lock = &refdb_fs_backend__lock; - backend->parent.unlock = &refdb_fs_backend__unlock; - backend->parent.has_log = &refdb_reflog_fs__has_log; - backend->parent.ensure_log = &refdb_reflog_fs__ensure_log; - backend->parent.free = &refdb_fs_backend__free; - backend->parent.reflog_read = &refdb_reflog_fs__read; - backend->parent.reflog_write = &refdb_reflog_fs__write; - backend->parent.reflog_rename = &refdb_reflog_fs__rename; - backend->parent.reflog_delete = &refdb_reflog_fs__delete; - - *backend_out = (git_refdb_backend *)backend; - return 0; - -fail: - git_mutex_free(&backend->prlock); - git_str_dispose(&gitpath); - git__free(backend->gitpath); - git__free(backend->commonpath); - git__free(backend); - return -1; -} diff --git a/src/reflog.c b/src/reflog.c deleted file mode 100644 index 1e9c0d4f1..000000000 --- a/src/reflog.c +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "reflog.h" - -#include "repository.h" -#include "filebuf.h" -#include "signature.h" -#include "refdb.h" - -#include "git2/sys/refdb_backend.h" -#include "git2/sys/reflog.h" - -void git_reflog_entry__free(git_reflog_entry *entry) -{ - git_signature_free(entry->committer); - - git__free(entry->msg); - git__free(entry); -} - -void git_reflog_free(git_reflog *reflog) -{ - size_t i; - git_reflog_entry *entry; - - if (reflog == NULL) - return; - - if (reflog->db) - GIT_REFCOUNT_DEC(reflog->db, git_refdb__free); - - for (i=0; i < reflog->entries.length; i++) { - entry = git_vector_get(&reflog->entries, i); - - git_reflog_entry__free(entry); - } - - git_vector_free(&reflog->entries); - git__free(reflog->ref_name); - git__free(reflog); -} - -int git_reflog_read(git_reflog **reflog, git_repository *repo, const char *name) -{ - git_refdb *refdb; - int error; - - GIT_ASSERT_ARG(reflog); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - return error; - - return git_refdb_reflog_read(reflog, refdb, name); -} - -int git_reflog_write(git_reflog *reflog) -{ - git_refdb *db; - - GIT_ASSERT_ARG(reflog); - GIT_ASSERT_ARG(reflog->db); - - db = reflog->db; - return db->backend->reflog_write(db->backend, reflog); -} - -int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg) -{ - const git_reflog_entry *previous; - git_reflog_entry *entry; - - GIT_ASSERT_ARG(reflog); - GIT_ASSERT_ARG(new_oid); - GIT_ASSERT_ARG(committer); - - entry = git__calloc(1, sizeof(git_reflog_entry)); - GIT_ERROR_CHECK_ALLOC(entry); - - if ((git_signature_dup(&entry->committer, committer)) < 0) - goto cleanup; - - if (msg != NULL) { - size_t i, msglen = strlen(msg); - - if ((entry->msg = git__strndup(msg, msglen)) == NULL) - goto cleanup; - - /* - * Replace all newlines with spaces, except for - * the final trailing newline. - */ - for (i = 0; i < msglen; i++) - if (entry->msg[i] == '\n') - entry->msg[i] = ' '; - } - - previous = git_reflog_entry_byindex(reflog, 0); - - if (previous == NULL) - git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO); - else - git_oid_cpy(&entry->oid_old, &previous->oid_cur); - - git_oid_cpy(&entry->oid_cur, new_oid); - - if (git_vector_insert(&reflog->entries, entry) < 0) - goto cleanup; - - return 0; - -cleanup: - git_reflog_entry__free(entry); - return -1; -} - -int git_reflog_rename(git_repository *repo, const char *old_name, const char *new_name) -{ - git_refdb *refdb; - int error; - - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - return -1; - - return refdb->backend->reflog_rename(refdb->backend, old_name, new_name); -} - -int git_reflog_delete(git_repository *repo, const char *name) -{ - git_refdb *refdb; - int error; - - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - return -1; - - return refdb->backend->reflog_delete(refdb->backend, name); -} - -size_t git_reflog_entrycount(git_reflog *reflog) -{ - GIT_ASSERT_ARG_WITH_RETVAL(reflog, 0); - return reflog->entries.length; -} - -const git_reflog_entry *git_reflog_entry_byindex(const git_reflog *reflog, size_t idx) -{ - GIT_ASSERT_ARG_WITH_RETVAL(reflog, NULL); - - if (idx >= reflog->entries.length) - return NULL; - - return git_vector_get( - &reflog->entries, reflog_inverse_index(idx, reflog->entries.length)); -} - -const git_oid *git_reflog_entry_id_old(const git_reflog_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); - return &entry->oid_old; -} - -const git_oid *git_reflog_entry_id_new(const git_reflog_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); - return &entry->oid_cur; -} - -const git_signature *git_reflog_entry_committer(const git_reflog_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); - return entry->committer; -} - -const char *git_reflog_entry_message(const git_reflog_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); - return entry->msg; -} - -int git_reflog_drop(git_reflog *reflog, size_t idx, int rewrite_previous_entry) -{ - size_t entrycount; - git_reflog_entry *entry, *previous; - - entrycount = git_reflog_entrycount(reflog); - - entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); - - if (entry == NULL) { - git_error_set(GIT_ERROR_REFERENCE, "no reflog entry at index %"PRIuZ, idx); - return GIT_ENOTFOUND; - } - - git_reflog_entry__free(entry); - - if (git_vector_remove( - &reflog->entries, reflog_inverse_index(idx, entrycount)) < 0) - return -1; - - if (!rewrite_previous_entry) - return 0; - - /* No need to rewrite anything when removing the most recent entry */ - if (idx == 0) - return 0; - - /* Have the latest entry just been dropped? */ - if (entrycount == 1) - return 0; - - entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1); - - /* If the oldest entry has just been removed... */ - if (idx == entrycount - 1) { - /* ...clear the oid_old member of the "new" oldest entry */ - if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0) - return -1; - - return 0; - } - - previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); - git_oid_cpy(&entry->oid_old, &previous->oid_cur); - - return 0; -} diff --git a/src/reflog.h b/src/reflog.h deleted file mode 100644 index 8c3895952..000000000 --- a/src/reflog.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_reflog_h__ -#define INCLUDE_reflog_h__ - -#include "common.h" - -#include "git2/reflog.h" -#include "vector.h" - -#define GIT_REFLOG_DIR "logs/" -#define GIT_REFLOG_DIR_MODE 0777 -#define GIT_REFLOG_FILE_MODE 0666 - -#define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17) - -struct git_reflog_entry { - git_oid oid_old; - git_oid oid_cur; - - git_signature *committer; - - char *msg; -}; - -struct git_reflog { - git_refdb *db; - char *ref_name; - git_vector entries; -}; - -GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total) -{ - return (total - 1) - idx; -} - -#endif diff --git a/src/refs.c b/src/refs.c deleted file mode 100644 index 5c875b95b..000000000 --- a/src/refs.c +++ /dev/null @@ -1,1395 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "refs.h" - -#include "hash.h" -#include "repository.h" -#include "futils.h" -#include "filebuf.h" -#include "pack.h" -#include "reflog.h" -#include "refdb.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -bool git_reference__enable_symbolic_ref_target_validation = true; - -enum { - GIT_PACKREF_HAS_PEEL = 1, - GIT_PACKREF_WAS_LOOSE = 2 -}; - -static git_reference *alloc_ref(const char *name) -{ - git_reference *ref = NULL; - size_t namelen = strlen(name), reflen; - - if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) && - !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) && - (ref = git__calloc(1, reflen)) != NULL) - memcpy(ref->name, name, namelen + 1); - - return ref; -} - -git_reference *git_reference__alloc_symbolic( - const char *name, const char *target) -{ - git_reference *ref; - - GIT_ASSERT_ARG_WITH_RETVAL(name, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(target, NULL); - - ref = alloc_ref(name); - if (!ref) - return NULL; - - ref->type = GIT_REFERENCE_SYMBOLIC; - - if ((ref->target.symbolic = git__strdup(target)) == NULL) { - git__free(ref); - return NULL; - } - - return ref; -} - -git_reference *git_reference__alloc( - const char *name, - const git_oid *oid, - const git_oid *peel) -{ - git_reference *ref; - - GIT_ASSERT_ARG_WITH_RETVAL(name, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(oid, NULL); - - ref = alloc_ref(name); - if (!ref) - return NULL; - - ref->type = GIT_REFERENCE_DIRECT; - git_oid_cpy(&ref->target.oid, oid); - - if (peel != NULL) - git_oid_cpy(&ref->peel, peel); - - return ref; -} - -git_reference *git_reference__realloc( - git_reference **ptr_to_ref, const char *name) -{ - size_t namelen, reflen; - git_reference *rewrite = NULL; - - GIT_ASSERT_ARG_WITH_RETVAL(ptr_to_ref, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(name, NULL); - - namelen = strlen(name); - - if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) && - !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) && - (rewrite = git__realloc(*ptr_to_ref, reflen)) != NULL) - memcpy(rewrite->name, name, namelen + 1); - - *ptr_to_ref = NULL; - - return rewrite; -} - -int git_reference_dup(git_reference **dest, git_reference *source) -{ - if (source->type == GIT_REFERENCE_SYMBOLIC) - *dest = git_reference__alloc_symbolic(source->name, source->target.symbolic); - else - *dest = git_reference__alloc(source->name, &source->target.oid, &source->peel); - - GIT_ERROR_CHECK_ALLOC(*dest); - - (*dest)->db = source->db; - GIT_REFCOUNT_INC((*dest)->db); - - return 0; -} - -void git_reference_free(git_reference *reference) -{ - if (reference == NULL) - return; - - if (reference->type == GIT_REFERENCE_SYMBOLIC) - git__free(reference->target.symbolic); - - if (reference->db) - GIT_REFCOUNT_DEC(reference->db, git_refdb__free); - - git__free(reference); -} - -int git_reference_delete(git_reference *ref) -{ - const git_oid *old_id = NULL; - const char *old_target = NULL; - - if (!strcmp(ref->name, "HEAD")) { - git_error_set(GIT_ERROR_REFERENCE, "cannot delete HEAD"); - return GIT_ERROR; - } - - if (ref->type == GIT_REFERENCE_DIRECT) - old_id = &ref->target.oid; - else - old_target = ref->target.symbolic; - - return git_refdb_delete(ref->db, ref->name, old_id, old_target); -} - -int git_reference_remove(git_repository *repo, const char *name) -{ - git_refdb *db; - int error; - - if ((error = git_repository_refdb__weakptr(&db, repo)) < 0) - return error; - - return git_refdb_delete(db, name, NULL, NULL); -} - -int git_reference_lookup(git_reference **ref_out, - git_repository *repo, const char *name) -{ - return git_reference_lookup_resolved(ref_out, repo, name, 0); -} - -int git_reference_name_to_id( - git_oid *out, git_repository *repo, const char *name) -{ - int error; - git_reference *ref; - - if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0) - return error; - - git_oid_cpy(out, git_reference_target(ref)); - git_reference_free(ref); - return 0; -} - -static int reference_normalize_for_repo( - git_refname_t out, - git_repository *repo, - const char *name, - bool validate) -{ - int precompose; - unsigned int flags = GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL; - - if (!git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) && - precompose) - flags |= GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE; - - if (!validate) - flags |= GIT_REFERENCE_FORMAT__VALIDATION_DISABLE; - - return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags); -} - -int git_reference_lookup_resolved( - git_reference **ref_out, - git_repository *repo, - const char *name, - int max_nesting) -{ - git_refname_t normalized; - git_refdb *refdb; - int error = 0; - - GIT_ASSERT_ARG(ref_out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = reference_normalize_for_repo(normalized, repo, name, true)) < 0 || - (error = git_repository_refdb__weakptr(&refdb, repo)) < 0 || - (error = git_refdb_resolve(ref_out, refdb, normalized, max_nesting)) < 0) - return error; - - /* - * The resolved reference may be a symbolic reference in case its - * target doesn't exist. If the user asked us to resolve (e.g. - * `max_nesting != 0`), then we need to return an error in case we got - * a symbolic reference back. - */ - if (max_nesting && git_reference_type(*ref_out) == GIT_REFERENCE_SYMBOLIC) { - git_reference_free(*ref_out); - *ref_out = NULL; - return GIT_ENOTFOUND; - } - - return 0; -} - -int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname) -{ - int error = 0, i, valid; - bool fallbackmode = true, foundvalid = false; - git_reference *ref; - git_str refnamebuf = GIT_STR_INIT, name = GIT_STR_INIT; - - static const char *formatters[] = { - "%s", - GIT_REFS_DIR "%s", - GIT_REFS_TAGS_DIR "%s", - GIT_REFS_HEADS_DIR "%s", - GIT_REFS_REMOTES_DIR "%s", - GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE, - NULL - }; - - if (*refname) - git_str_puts(&name, refname); - else { - git_str_puts(&name, GIT_HEAD_FILE); - fallbackmode = false; - } - - for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) { - - git_str_clear(&refnamebuf); - - if ((error = git_str_printf(&refnamebuf, formatters[i], git_str_cstr(&name))) < 0 || - (error = git_reference_name_is_valid(&valid, git_str_cstr(&refnamebuf))) < 0) - goto cleanup; - - if (!valid) { - error = GIT_EINVALIDSPEC; - continue; - } - foundvalid = true; - - error = git_reference_lookup_resolved(&ref, repo, git_str_cstr(&refnamebuf), -1); - - if (!error) { - *out = ref; - error = 0; - goto cleanup; - } - - if (error != GIT_ENOTFOUND) - goto cleanup; - } - -cleanup: - if (error && !foundvalid) { - /* never found a valid reference name */ - git_error_set(GIT_ERROR_REFERENCE, - "could not use '%s' as valid reference name", git_str_cstr(&name)); - } - - if (error == GIT_ENOTFOUND) - git_error_set(GIT_ERROR_REFERENCE, "no reference found for shorthand '%s'", refname); - - git_str_dispose(&name); - git_str_dispose(&refnamebuf); - return error; -} - -/** - * Getters - */ -git_reference_t git_reference_type(const git_reference *ref) -{ - GIT_ASSERT_ARG(ref); - return ref->type; -} - -const char *git_reference_name(const git_reference *ref) -{ - GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); - return ref->name; -} - -git_repository *git_reference_owner(const git_reference *ref) -{ - GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); - return ref->db->repo; -} - -const git_oid *git_reference_target(const git_reference *ref) -{ - GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); - - if (ref->type != GIT_REFERENCE_DIRECT) - return NULL; - - return &ref->target.oid; -} - -const git_oid *git_reference_target_peel(const git_reference *ref) -{ - GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); - - if (ref->type != GIT_REFERENCE_DIRECT || git_oid_is_zero(&ref->peel)) - return NULL; - - return &ref->peel; -} - -const char *git_reference_symbolic_target(const git_reference *ref) -{ - GIT_ASSERT_ARG_WITH_RETVAL(ref, NULL); - - if (ref->type != GIT_REFERENCE_SYMBOLIC) - return NULL; - - return ref->target.symbolic; -} - -static int reference__create( - git_reference **ref_out, - git_repository *repo, - const char *name, - const git_oid *oid, - const char *symbolic, - int force, - const git_signature *signature, - const char *log_message, - const git_oid *old_id, - const char *old_target) -{ - git_refname_t normalized; - git_refdb *refdb; - git_reference *ref = NULL; - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(symbolic || signature); - - if (ref_out) - *ref_out = NULL; - - error = reference_normalize_for_repo(normalized, repo, name, true); - if (error < 0) - return error; - - error = git_repository_refdb__weakptr(&refdb, repo); - if (error < 0) - return error; - - if (oid != NULL) { - GIT_ASSERT(symbolic == NULL); - - if (!git_object__is_valid(repo, oid, GIT_OBJECT_ANY)) { - git_error_set(GIT_ERROR_REFERENCE, - "target OID for the reference doesn't exist on the repository"); - return -1; - } - - ref = git_reference__alloc(normalized, oid, NULL); - } else { - git_refname_t normalized_target; - - error = reference_normalize_for_repo(normalized_target, repo, - symbolic, git_reference__enable_symbolic_ref_target_validation); - - if (error < 0) - return error; - - ref = git_reference__alloc_symbolic(normalized, normalized_target); - } - - GIT_ERROR_CHECK_ALLOC(ref); - - if ((error = git_refdb_write(refdb, ref, force, signature, log_message, old_id, old_target)) < 0) { - git_reference_free(ref); - return error; - } - - if (ref_out == NULL) - git_reference_free(ref); - else - *ref_out = ref; - - return 0; -} - -static int refs_configured_ident(git_signature **out, const git_repository *repo) -{ - if (repo->ident_name && repo->ident_email) - return git_signature_now(out, repo->ident_name, repo->ident_email); - - /* if not configured let us fall-through to the next method */ - return -1; -} - -int git_reference__log_signature(git_signature **out, git_repository *repo) -{ - int error; - git_signature *who; - - if(((error = refs_configured_ident(&who, repo)) < 0) && - ((error = git_signature_default(&who, repo)) < 0) && - ((error = git_signature_now(&who, "unknown", "unknown")) < 0)) - return error; - - *out = who; - return 0; -} - -int git_reference_create_matching( - git_reference **ref_out, - git_repository *repo, - const char *name, - const git_oid *id, - int force, - const git_oid *old_id, - const char *log_message) - -{ - int error; - git_signature *who = NULL; - - GIT_ASSERT_ARG(id); - - if ((error = git_reference__log_signature(&who, repo)) < 0) - return error; - - error = reference__create( - ref_out, repo, name, id, NULL, force, who, log_message, old_id, NULL); - - git_signature_free(who); - return error; -} - -int git_reference_create( - git_reference **ref_out, - git_repository *repo, - const char *name, - const git_oid *id, - int force, - const char *log_message) -{ - return git_reference_create_matching(ref_out, repo, name, id, force, NULL, log_message); -} - -int git_reference_symbolic_create_matching( - git_reference **ref_out, - git_repository *repo, - const char *name, - const char *target, - int force, - const char *old_target, - const char *log_message) -{ - int error; - git_signature *who = NULL; - - GIT_ASSERT_ARG(target); - - if ((error = git_reference__log_signature(&who, repo)) < 0) - return error; - - error = reference__create( - ref_out, repo, name, NULL, target, force, who, log_message, NULL, old_target); - - git_signature_free(who); - return error; -} - -int git_reference_symbolic_create( - git_reference **ref_out, - git_repository *repo, - const char *name, - const char *target, - int force, - const char *log_message) -{ - return git_reference_symbolic_create_matching(ref_out, repo, name, target, force, NULL, log_message); -} - -static int ensure_is_an_updatable_direct_reference(git_reference *ref) -{ - if (ref->type == GIT_REFERENCE_DIRECT) - return 0; - - git_error_set(GIT_ERROR_REFERENCE, "cannot set OID on symbolic reference"); - return -1; -} - -int git_reference_set_target( - git_reference **out, - git_reference *ref, - const git_oid *id, - const char *log_message) -{ - int error; - git_repository *repo; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ref); - GIT_ASSERT_ARG(id); - - repo = ref->db->repo; - - if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0) - return error; - - return git_reference_create_matching(out, repo, ref->name, id, 1, &ref->target.oid, log_message); -} - -static int ensure_is_an_updatable_symbolic_reference(git_reference *ref) -{ - if (ref->type == GIT_REFERENCE_SYMBOLIC) - return 0; - - git_error_set(GIT_ERROR_REFERENCE, "cannot set symbolic target on a direct reference"); - return -1; -} - -int git_reference_symbolic_set_target( - git_reference **out, - git_reference *ref, - const char *target, - const char *log_message) -{ - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ref); - GIT_ASSERT_ARG(target); - - if ((error = ensure_is_an_updatable_symbolic_reference(ref)) < 0) - return error; - - return git_reference_symbolic_create_matching( - out, ref->db->repo, ref->name, target, 1, ref->target.symbolic, log_message); -} - -typedef struct { - const char *old_name; - git_refname_t new_name; -} refs_update_head_payload; - -static int refs_update_head(git_repository *worktree, void *_payload) -{ - refs_update_head_payload *payload = (refs_update_head_payload *)_payload; - git_reference *head = NULL, *updated = NULL; - int error; - - if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0) - goto out; - - if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC || - git__strcmp(git_reference_symbolic_target(head), payload->old_name) != 0) - goto out; - - /* Update HEAD if it was pointing to the reference being renamed */ - if ((error = git_reference_symbolic_set_target(&updated, head, payload->new_name, NULL)) < 0) { - git_error_set(GIT_ERROR_REFERENCE, "failed to update HEAD after renaming reference"); - goto out; - } - -out: - git_reference_free(updated); - git_reference_free(head); - return error; -} - -int git_reference_rename( - git_reference **out, - git_reference *ref, - const char *new_name, - int force, - const char *log_message) -{ - refs_update_head_payload payload; - git_signature *signature = NULL; - git_repository *repo; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(ref); - - repo = git_reference_owner(ref); - - if ((error = git_reference__log_signature(&signature, repo)) < 0 || - (error = reference_normalize_for_repo(payload.new_name, repo, new_name, true)) < 0 || - (error = git_refdb_rename(out, ref->db, ref->name, payload.new_name, force, signature, log_message)) < 0) - goto out; - - payload.old_name = ref->name; - - /* We may have to update any HEAD that was pointing to the renamed reference. */ - if ((error = git_repository_foreach_worktree(repo, refs_update_head, &payload)) < 0) - goto out; - -out: - git_signature_free(signature); - return error; -} - -int git_reference_resolve(git_reference **ref_out, const git_reference *ref) -{ - switch (git_reference_type(ref)) { - case GIT_REFERENCE_DIRECT: - return git_reference_lookup(ref_out, ref->db->repo, ref->name); - - case GIT_REFERENCE_SYMBOLIC: - return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1); - - default: - git_error_set(GIT_ERROR_REFERENCE, "invalid reference"); - return -1; - } -} - -int git_reference_foreach( - git_repository *repo, - git_reference_foreach_cb callback, - void *payload) -{ - git_reference_iterator *iter; - git_reference *ref; - int error; - - if ((error = git_reference_iterator_new(&iter, repo)) < 0) - return error; - - while (!(error = git_reference_next(&ref, iter))) { - if ((error = callback(ref, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - if (error == GIT_ITEROVER) - error = 0; - - git_reference_iterator_free(iter); - return error; -} - -int git_reference_foreach_name( - git_repository *repo, - git_reference_foreach_name_cb callback, - void *payload) -{ - git_reference_iterator *iter; - const char *refname; - int error; - - if ((error = git_reference_iterator_new(&iter, repo)) < 0) - return error; - - while (!(error = git_reference_next_name(&refname, iter))) { - if ((error = callback(refname, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - if (error == GIT_ITEROVER) - error = 0; - - git_reference_iterator_free(iter); - return error; -} - -int git_reference_foreach_glob( - git_repository *repo, - const char *glob, - git_reference_foreach_name_cb callback, - void *payload) -{ - git_reference_iterator *iter; - const char *refname; - int error; - - if ((error = git_reference_iterator_glob_new(&iter, repo, glob)) < 0) - return error; - - while (!(error = git_reference_next_name(&refname, iter))) { - if ((error = callback(refname, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - if (error == GIT_ITEROVER) - error = 0; - - git_reference_iterator_free(iter); - return error; -} - -int git_reference_iterator_new(git_reference_iterator **out, git_repository *repo) -{ - git_refdb *refdb; - - if (git_repository_refdb__weakptr(&refdb, repo) < 0) - return -1; - - return git_refdb_iterator(out, refdb, NULL); -} - -int git_reference_iterator_glob_new( - git_reference_iterator **out, git_repository *repo, const char *glob) -{ - git_refdb *refdb; - - if (git_repository_refdb__weakptr(&refdb, repo) < 0) - return -1; - - return git_refdb_iterator(out, refdb, glob); -} - -int git_reference_next(git_reference **out, git_reference_iterator *iter) -{ - return git_refdb_iterator_next(out, iter); -} - -int git_reference_next_name(const char **out, git_reference_iterator *iter) -{ - return git_refdb_iterator_next_name(out, iter); -} - -void git_reference_iterator_free(git_reference_iterator *iter) -{ - if (iter == NULL) - return; - - git_refdb_iterator_free(iter); -} - -static int cb__reflist_add(const char *ref, void *data) -{ - char *name = git__strdup(ref); - GIT_ERROR_CHECK_ALLOC(name); - return git_vector_insert((git_vector *)data, name); -} - -int git_reference_list( - git_strarray *array, - git_repository *repo) -{ - git_vector ref_list; - - GIT_ASSERT_ARG(array); - GIT_ASSERT_ARG(repo); - - array->strings = NULL; - array->count = 0; - - if (git_vector_init(&ref_list, 8, NULL) < 0) - return -1; - - if (git_reference_foreach_name( - repo, &cb__reflist_add, (void *)&ref_list) < 0) { - git_vector_free(&ref_list); - return -1; - } - - array->strings = (char **)git_vector_detach(&array->count, NULL, &ref_list); - - return 0; -} - -static int is_valid_ref_char(char ch) -{ - if ((unsigned) ch <= ' ') - return 0; - - switch (ch) { - case '~': - case '^': - case ':': - case '\\': - case '?': - case '[': - return 0; - default: - return 1; - } -} - -static int ensure_segment_validity(const char *name, char may_contain_glob) -{ - const char *current = name; - char prev = '\0'; - const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION); - int segment_len; - - if (*current == '.') - return -1; /* Refname starts with "." */ - - for (current = name; ; current++) { - if (*current == '\0' || *current == '/') - break; - - if (!is_valid_ref_char(*current)) - return -1; /* Illegal character in refname */ - - if (prev == '.' && *current == '.') - return -1; /* Refname contains ".." */ - - if (prev == '@' && *current == '{') - return -1; /* Refname contains "@{" */ - - if (*current == '*') { - if (!may_contain_glob) - return -1; - may_contain_glob = 0; - } - - prev = *current; - } - - segment_len = (int)(current - name); - - /* A refname component can not end with ".lock" */ - if (segment_len >= lock_len && - !memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len)) - return -1; - - return segment_len; -} - -static bool is_all_caps_and_underscore(const char *name, size_t len) -{ - size_t i; - char c; - - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(len > 0); - - for (i = 0; i < len; i++) - { - c = name[i]; - if ((c < 'A' || c > 'Z') && c != '_') - return false; - } - - if (*name == '_' || name[len - 1] == '_') - return false; - - return true; -} - -/* Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 */ -int git_reference__normalize_name( - git_str *buf, - const char *name, - unsigned int flags) -{ - const char *current; - int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC; - unsigned int process_flags; - bool normalize = (buf != NULL); - bool validate = (flags & GIT_REFERENCE_FORMAT__VALIDATION_DISABLE) == 0; - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT; -#endif - - GIT_ASSERT_ARG(name); - - process_flags = flags; - current = (char *)name; - - if (validate && *current == '/') - goto cleanup; - - if (normalize) - git_str_clear(buf); - -#ifdef GIT_USE_ICONV - if ((flags & GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE) != 0) { - size_t namelen = strlen(current); - if ((error = git_fs_path_iconv_init_precompose(&ic)) < 0 || - (error = git_fs_path_iconv(&ic, ¤t, &namelen)) < 0) - goto cleanup; - error = GIT_EINVALIDSPEC; - } -#endif - - if (!validate) { - git_str_sets(buf, current); - - error = git_str_oom(buf) ? -1 : 0; - goto cleanup; - } - - while (true) { - char may_contain_glob = process_flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN; - - segment_len = ensure_segment_validity(current, may_contain_glob); - if (segment_len < 0) - goto cleanup; - - if (segment_len > 0) { - /* - * There may only be one glob in a pattern, thus we reset - * the pattern-flag in case the current segment has one. - */ - if (memchr(current, '*', segment_len)) - process_flags &= ~GIT_REFERENCE_FORMAT_REFSPEC_PATTERN; - - if (normalize) { - size_t cur_len = git_str_len(buf); - - git_str_joinpath(buf, git_str_cstr(buf), current); - git_str_truncate(buf, - cur_len + segment_len + (segments_count ? 1 : 0)); - - if (git_str_oom(buf)) { - error = -1; - goto cleanup; - } - } - - segments_count++; - } - - /* No empty segment is allowed when not normalizing */ - if (segment_len == 0 && !normalize) - goto cleanup; - - if (current[segment_len] == '\0') - break; - - current += segment_len + 1; - } - - /* A refname can not be empty */ - if (segment_len == 0 && segments_count == 0) - goto cleanup; - - /* A refname can not end with "." */ - if (current[segment_len - 1] == '.') - goto cleanup; - - /* A refname can not end with "/" */ - if (current[segment_len - 1] == '/') - goto cleanup; - - if ((segments_count == 1 ) && !(flags & GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL)) - goto cleanup; - - if ((segments_count == 1 ) && - !(flags & GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND) && - !(is_all_caps_and_underscore(name, (size_t)segment_len) || - ((flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name)))) - goto cleanup; - - if ((segments_count > 1) - && (is_all_caps_and_underscore(name, strchr(name, '/') - name))) - goto cleanup; - - error = 0; - -cleanup: - if (error == GIT_EINVALIDSPEC) - git_error_set( - GIT_ERROR_REFERENCE, - "the given reference name '%s' is not valid", name); - - if (error && normalize) - git_str_dispose(buf); - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_clear(&ic); -#endif - - return error; -} - -int git_reference_normalize_name( - char *buffer_out, - size_t buffer_size, - const char *name, - unsigned int flags) -{ - git_str buf = GIT_STR_INIT; - int error; - - if ((error = git_reference__normalize_name(&buf, name, flags)) < 0) - goto cleanup; - - if (git_str_len(&buf) > buffer_size - 1) { - git_error_set( - GIT_ERROR_REFERENCE, - "the provided buffer is too short to hold the normalization of '%s'", name); - error = GIT_EBUFS; - goto cleanup; - } - - if ((error = git_str_copy_cstr(buffer_out, buffer_size, &buf)) < 0) - goto cleanup; - - error = 0; - -cleanup: - git_str_dispose(&buf); - return error; -} - -#define GIT_REFERENCE_TYPEMASK (GIT_REFERENCE_DIRECT | GIT_REFERENCE_SYMBOLIC) - -int git_reference_cmp( - const git_reference *ref1, - const git_reference *ref2) -{ - git_reference_t type1, type2; - - GIT_ASSERT_ARG(ref1); - GIT_ASSERT_ARG(ref2); - - type1 = git_reference_type(ref1); - type2 = git_reference_type(ref2); - - /* let's put symbolic refs before OIDs */ - if (type1 != type2) - return (type1 == GIT_REFERENCE_SYMBOLIC) ? -1 : 1; - - if (type1 == GIT_REFERENCE_SYMBOLIC) - return strcmp(ref1->target.symbolic, ref2->target.symbolic); - - return git_oid__cmp(&ref1->target.oid, &ref2->target.oid); -} - -/* - * Starting with the reference given by `ref_name`, follows symbolic - * references until a direct reference is found and updated the OID - * on that direct reference to `oid`. - */ -int git_reference__update_terminal( - git_repository *repo, - const char *ref_name, - const git_oid *oid, - const git_signature *sig, - const char *log_message) -{ - git_reference *ref = NULL, *ref2 = NULL; - git_signature *who = NULL; - git_refdb *refdb = NULL; - const git_signature *to_use; - int error = 0; - - if (!sig && (error = git_reference__log_signature(&who, repo)) < 0) - goto out; - - to_use = sig ? sig : who; - - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - goto out; - - if ((error = git_refdb_resolve(&ref, refdb, ref_name, -1)) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = reference__create(&ref2, repo, ref_name, oid, NULL, 0, to_use, - log_message, NULL, NULL); - } - goto out; - } - - /* In case the resolved reference is symbolic, then it's a dangling symref. */ - if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { - error = reference__create(&ref2, repo, ref->target.symbolic, oid, NULL, 0, to_use, - log_message, NULL, NULL); - } else { - error = reference__create(&ref2, repo, ref->name, oid, NULL, 1, to_use, - log_message, &ref->target.oid, NULL); - } - -out: - git_reference_free(ref2); - git_reference_free(ref); - git_signature_free(who); - return error; -} - -static const char *commit_type(const git_commit *commit) -{ - unsigned int count = git_commit_parentcount(commit); - - if (count >= 2) - return " (merge)"; - else if (count == 0) - return " (initial)"; - else - return ""; -} - -int git_reference__update_for_commit( - git_repository *repo, - git_reference *ref, - const char *ref_name, - const git_oid *id, - const char *operation) -{ - git_reference *ref_new = NULL; - git_commit *commit = NULL; - git_str reflog_msg = GIT_STR_INIT; - const git_signature *who; - int error; - - if ((error = git_commit_lookup(&commit, repo, id)) < 0 || - (error = git_str_printf(&reflog_msg, "%s%s: %s", - operation ? operation : "commit", - commit_type(commit), - git_commit_summary(commit))) < 0) - goto done; - - who = git_commit_committer(commit); - - if (ref) { - if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0) - return error; - - error = reference__create(&ref_new, repo, ref->name, id, NULL, 1, who, - git_str_cstr(&reflog_msg), &ref->target.oid, NULL); - } - else - error = git_reference__update_terminal( - repo, ref_name, id, who, git_str_cstr(&reflog_msg)); - -done: - git_reference_free(ref_new); - git_str_dispose(&reflog_msg); - git_commit_free(commit); - return error; -} - -int git_reference_has_log(git_repository *repo, const char *refname) -{ - int error; - git_refdb *refdb; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(refname); - - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - return error; - - return git_refdb_has_log(refdb, refname); -} - -int git_reference_ensure_log(git_repository *repo, const char *refname) -{ - int error; - git_refdb *refdb; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(refname); - - if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) - return error; - - return git_refdb_ensure_log(refdb, refname); -} - -int git_reference__is_branch(const char *ref_name) -{ - return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0; -} - -int git_reference_is_branch(const git_reference *ref) -{ - GIT_ASSERT_ARG(ref); - return git_reference__is_branch(ref->name); -} - -int git_reference__is_remote(const char *ref_name) -{ - return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0; -} - -int git_reference_is_remote(const git_reference *ref) -{ - GIT_ASSERT_ARG(ref); - return git_reference__is_remote(ref->name); -} - -int git_reference__is_tag(const char *ref_name) -{ - return git__prefixcmp(ref_name, GIT_REFS_TAGS_DIR) == 0; -} - -int git_reference_is_tag(const git_reference *ref) -{ - GIT_ASSERT_ARG(ref); - return git_reference__is_tag(ref->name); -} - -int git_reference__is_note(const char *ref_name) -{ - return git__prefixcmp(ref_name, GIT_REFS_NOTES_DIR) == 0; -} - -int git_reference_is_note(const git_reference *ref) -{ - GIT_ASSERT_ARG(ref); - return git_reference__is_note(ref->name); -} - -static int peel_error(int error, const git_reference *ref, const char *msg) -{ - git_error_set( - GIT_ERROR_INVALID, - "the reference '%s' cannot be peeled - %s", git_reference_name(ref), msg); - return error; -} - -int git_reference_peel( - git_object **peeled, - const git_reference *ref, - git_object_t target_type) -{ - const git_reference *resolved = NULL; - git_reference *allocated = NULL; - git_object *target = NULL; - int error; - - GIT_ASSERT_ARG(ref); - - if (ref->type == GIT_REFERENCE_DIRECT) { - resolved = ref; - } else { - if ((error = git_reference_resolve(&allocated, ref)) < 0) - return peel_error(error, ref, "Cannot resolve reference"); - - resolved = allocated; - } - - /* - * If we try to peel an object to a tag, we cannot use - * the fully peeled object, as that will always resolve - * to a commit. So we only want to use the peeled value - * if it is not zero and the target is not a tag. - */ - if (target_type != GIT_OBJECT_TAG && !git_oid_is_zero(&resolved->peel)) { - error = git_object_lookup(&target, - git_reference_owner(ref), &resolved->peel, GIT_OBJECT_ANY); - } else { - error = git_object_lookup(&target, - git_reference_owner(ref), &resolved->target.oid, GIT_OBJECT_ANY); - } - - if (error < 0) { - peel_error(error, ref, "Cannot retrieve reference target"); - goto cleanup; - } - - if (target_type == GIT_OBJECT_ANY && git_object_type(target) != GIT_OBJECT_TAG) - error = git_object_dup(peeled, target); - else - error = git_object_peel(peeled, target, target_type); - -cleanup: - git_object_free(target); - git_reference_free(allocated); - - return error; -} - -int git_reference__name_is_valid( - int *valid, - const char *refname, - unsigned int flags) -{ - int error; - - GIT_ASSERT(valid && refname); - - *valid = 0; - - error = git_reference__normalize_name(NULL, refname, flags); - - if (!error) - *valid = 1; - else if (error == GIT_EINVALIDSPEC) - error = 0; - - return error; -} - -int git_reference_name_is_valid(int *valid, const char *refname) -{ - return git_reference__name_is_valid(valid, refname, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL); -} - -const char *git_reference__shorthand(const char *name) -{ - if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) - return name + strlen(GIT_REFS_HEADS_DIR); - else if (!git__prefixcmp(name, GIT_REFS_TAGS_DIR)) - return name + strlen(GIT_REFS_TAGS_DIR); - else if (!git__prefixcmp(name, GIT_REFS_REMOTES_DIR)) - return name + strlen(GIT_REFS_REMOTES_DIR); - else if (!git__prefixcmp(name, GIT_REFS_DIR)) - return name + strlen(GIT_REFS_DIR); - - /* No shorthands are available, so just return the name. */ - return name; -} - -const char *git_reference_shorthand(const git_reference *ref) -{ - return git_reference__shorthand(ref->name); -} - -int git_reference__is_unborn_head(bool *unborn, const git_reference *ref, git_repository *repo) -{ - int error; - git_reference *tmp_ref; - - GIT_ASSERT_ARG(unborn); - GIT_ASSERT_ARG(ref); - GIT_ASSERT_ARG(repo); - - if (ref->type == GIT_REFERENCE_DIRECT) { - *unborn = 0; - return 0; - } - - error = git_reference_lookup_resolved(&tmp_ref, repo, ref->name, -1); - git_reference_free(tmp_ref); - - if (error != 0 && error != GIT_ENOTFOUND) - return error; - else if (error == GIT_ENOTFOUND && git__strcmp(ref->name, GIT_HEAD_FILE) == 0) - *unborn = true; - else - *unborn = false; - - return 0; -} - -/* Deprecated functions */ - -#ifndef GIT_DEPRECATE_HARD - -int git_reference_is_valid_name(const char *refname) -{ - int valid = 0; - - git_reference__name_is_valid(&valid, refname, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL); - - return valid; -} - -#endif diff --git a/src/refs.h b/src/refs.h deleted file mode 100644 index cb888bf8f..000000000 --- a/src/refs.h +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_refs_h__ -#define INCLUDE_refs_h__ - -#include "common.h" - -#include "git2/oid.h" -#include "git2/refs.h" -#include "git2/refdb.h" -#include "strmap.h" -#include "str.h" -#include "oid.h" - -extern bool git_reference__enable_symbolic_ref_target_validation; - -#define GIT_REFS_DIR "refs/" -#define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/" -#define GIT_REFS_TAGS_DIR GIT_REFS_DIR "tags/" -#define GIT_REFS_REMOTES_DIR GIT_REFS_DIR "remotes/" -#define GIT_REFS_NOTES_DIR GIT_REFS_DIR "notes/" -#define GIT_REFS_DIR_MODE 0777 -#define GIT_REFS_FILE_MODE 0666 - -#define GIT_RENAMED_REF_FILE GIT_REFS_DIR "RENAMED-REF" - -#define GIT_SYMREF "ref: " -#define GIT_PACKEDREFS_FILE "packed-refs" -#define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled fully-peeled sorted " -#define GIT_PACKEDREFS_FILE_MODE 0666 - -#define GIT_HEAD_FILE "HEAD" -#define GIT_ORIG_HEAD_FILE "ORIG_HEAD" -#define GIT_FETCH_HEAD_FILE "FETCH_HEAD" -#define GIT_MERGE_HEAD_FILE "MERGE_HEAD" -#define GIT_REVERT_HEAD_FILE "REVERT_HEAD" -#define GIT_CHERRYPICK_HEAD_FILE "CHERRY_PICK_HEAD" -#define GIT_BISECT_LOG_FILE "BISECT_LOG" -#define GIT_REBASE_MERGE_DIR "rebase-merge/" -#define GIT_REBASE_MERGE_INTERACTIVE_FILE GIT_REBASE_MERGE_DIR "interactive" -#define GIT_REBASE_APPLY_DIR "rebase-apply/" -#define GIT_REBASE_APPLY_REBASING_FILE GIT_REBASE_APPLY_DIR "rebasing" -#define GIT_REBASE_APPLY_APPLYING_FILE GIT_REBASE_APPLY_DIR "applying" - -#define GIT_SEQUENCER_DIR "sequencer/" -#define GIT_SEQUENCER_HEAD_FILE GIT_SEQUENCER_DIR "head" -#define GIT_SEQUENCER_OPTIONS_FILE GIT_SEQUENCER_DIR "options" -#define GIT_SEQUENCER_TODO_FILE GIT_SEQUENCER_DIR "todo" - -#define GIT_STASH_FILE "stash" -#define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE - -#define GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE (1u << 16) -#define GIT_REFERENCE_FORMAT__VALIDATION_DISABLE (1u << 15) - -#define GIT_REFNAME_MAX 1024 - -typedef char git_refname_t[GIT_REFNAME_MAX]; - -struct git_reference { - git_refdb *db; - git_reference_t type; - - union { - git_oid oid; - char *symbolic; - } target; - - git_oid peel; - char name[GIT_FLEX_ARRAY]; -}; - -/** - * Reallocate the reference with a new name - * - * Note that this is a dangerous operation, as on success, all existing - * pointers to the old reference will now be dangling. Only call this on objects - * you control, possibly using `git_reference_dup`. - */ -git_reference *git_reference__realloc(git_reference **ptr_to_ref, const char *name); - -int git_reference__normalize_name(git_str *buf, const char *name, unsigned int flags); -int git_reference__update_terminal(git_repository *repo, const char *ref_name, const git_oid *oid, const git_signature *sig, const char *log_message); -int git_reference__name_is_valid(int *valid, const char *name, unsigned int flags); -int git_reference__is_branch(const char *ref_name); -int git_reference__is_remote(const char *ref_name); -int git_reference__is_tag(const char *ref_name); -int git_reference__is_note(const char *ref_name); -const char *git_reference__shorthand(const char *name); - -/** - * Lookup a reference by name and try to resolve to an OID. - * - * You can control how many dereferences this will attempt to resolve the - * reference with the `max_deref` parameter, or pass -1 to use a sane - * default. If you pass 0 for `max_deref`, this will not attempt to resolve - * the reference. For any value of `max_deref` other than 0, not - * successfully resolving the reference will be reported as an error. - - * The generated reference must be freed by the user. - * - * @param reference_out Pointer to the looked-up reference - * @param repo The repository to look up the reference - * @param name The long name for the reference (e.g. HEAD, ref/heads/master, refs/tags/v0.1.0, ...) - * @param max_deref Maximum number of dereferences to make of symbolic refs, 0 means simple lookup, < 0 means use default reasonable value - * @return 0 on success or < 0 on error; not being able to resolve the reference is an error unless 0 was passed for max_deref - */ -int git_reference_lookup_resolved( - git_reference **reference_out, - git_repository *repo, - const char *name, - int max_deref); - -int git_reference__log_signature(git_signature **out, git_repository *repo); - -/** Update a reference after a commit. */ -int git_reference__update_for_commit( - git_repository *repo, - git_reference *ref, - const char *ref_name, - const git_oid *id, - const char *operation); - -int git_reference__is_unborn_head(bool *unborn, const git_reference *ref, git_repository *repo); - -#endif diff --git a/src/refspec.c b/src/refspec.c deleted file mode 100644 index f0a0c2bfb..000000000 --- a/src/refspec.c +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "refspec.h" - -#include "buf.h" -#include "refs.h" -#include "util.h" -#include "vector.h" -#include "wildmatch.h" - -int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch) -{ - /* Ported from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/remote.c#L518-636 */ - - size_t llen; - int is_glob = 0; - const char *lhs, *rhs; - int valid = 0; - unsigned int flags; - - GIT_ASSERT_ARG(refspec); - GIT_ASSERT_ARG(input); - - memset(refspec, 0x0, sizeof(git_refspec)); - refspec->push = !is_fetch; - - lhs = input; - if (*lhs == '+') { - refspec->force = 1; - lhs++; - } - - rhs = strrchr(lhs, ':'); - - /* - * Before going on, special case ":" (or "+:") as a refspec - * for matching refs. - */ - if (!is_fetch && rhs == lhs && rhs[1] == '\0') { - refspec->matching = 1; - refspec->string = git__strdup(input); - GIT_ERROR_CHECK_ALLOC(refspec->string); - refspec->src = git__strdup(""); - GIT_ERROR_CHECK_ALLOC(refspec->src); - refspec->dst = git__strdup(""); - GIT_ERROR_CHECK_ALLOC(refspec->dst); - return 0; - } - - if (rhs) { - size_t rlen = strlen(++rhs); - if (rlen || !is_fetch) { - is_glob = (1 <= rlen && strchr(rhs, '*')); - refspec->dst = git__strndup(rhs, rlen); - } - } - - llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs)); - if (1 <= llen && memchr(lhs, '*', llen)) { - if ((rhs && !is_glob) || (!rhs && is_fetch)) - goto invalid; - is_glob = 1; - } else if (rhs && is_glob) - goto invalid; - - refspec->pattern = is_glob; - refspec->src = git__strndup(lhs, llen); - flags = GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL | - GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND | - (is_glob ? GIT_REFERENCE_FORMAT_REFSPEC_PATTERN : 0); - - if (is_fetch) { - /* - * LHS - * - empty is allowed; it means HEAD. - * - otherwise it must be a valid looking ref. - */ - if (!*refspec->src) - ; /* empty is ok */ - else if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) - goto on_error; - else if (!valid) - goto invalid; - - /* - * RHS - * - missing is ok, and is same as empty. - * - empty is ok; it means not to store. - * - otherwise it must be a valid looking ref. - */ - if (!refspec->dst) - ; /* ok */ - else if (!*refspec->dst) - ; /* ok */ - else if (git_reference__name_is_valid(&valid, refspec->dst, flags) < 0) - goto on_error; - else if (!valid) - goto invalid; - } else { - /* - * LHS - * - empty is allowed; it means delete. - * - when wildcarded, it must be a valid looking ref. - * - otherwise, it must be an extended SHA-1, but - * there is no existing way to validate this. - */ - if (!*refspec->src) - ; /* empty is ok */ - else if (is_glob) { - if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) - goto on_error; - else if (!valid) - goto invalid; - } - else { - ; /* anything goes, for now */ - } - - /* - * RHS - * - missing is allowed, but LHS then must be a - * valid looking ref. - * - empty is not allowed. - * - otherwise it must be a valid looking ref. - */ - if (!refspec->dst) { - if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) - goto on_error; - else if (!valid) - goto invalid; - } else if (!*refspec->dst) { - goto invalid; - } else { - if (git_reference__name_is_valid(&valid, refspec->dst, flags) < 0) - goto on_error; - else if (!valid) - goto invalid; - } - - /* if the RHS is empty, then it's a copy of the LHS */ - if (!refspec->dst) { - refspec->dst = git__strdup(refspec->src); - GIT_ERROR_CHECK_ALLOC(refspec->dst); - } - } - - refspec->string = git__strdup(input); - GIT_ERROR_CHECK_ALLOC(refspec->string); - - return 0; - -invalid: - git_error_set(GIT_ERROR_INVALID, - "'%s' is not a valid refspec.", input); - git_refspec__dispose(refspec); - return GIT_EINVALIDSPEC; - -on_error: - git_refspec__dispose(refspec); - return -1; -} - -void git_refspec__dispose(git_refspec *refspec) -{ - if (refspec == NULL) - return; - - git__free(refspec->src); - git__free(refspec->dst); - git__free(refspec->string); - - memset(refspec, 0x0, sizeof(git_refspec)); -} - -int git_refspec_parse(git_refspec **out_refspec, const char *input, int is_fetch) -{ - git_refspec *refspec; - GIT_ASSERT_ARG(out_refspec); - GIT_ASSERT_ARG(input); - - *out_refspec = NULL; - - refspec = git__malloc(sizeof(git_refspec)); - GIT_ERROR_CHECK_ALLOC(refspec); - - if (git_refspec__parse(refspec, input, !!is_fetch) != 0) { - git__free(refspec); - return -1; - } - - *out_refspec = refspec; - return 0; -} - -void git_refspec_free(git_refspec *refspec) -{ - git_refspec__dispose(refspec); - git__free(refspec); -} - -const char *git_refspec_src(const git_refspec *refspec) -{ - return refspec == NULL ? NULL : refspec->src; -} - -const char *git_refspec_dst(const git_refspec *refspec) -{ - return refspec == NULL ? NULL : refspec->dst; -} - -const char *git_refspec_string(const git_refspec *refspec) -{ - return refspec == NULL ? NULL : refspec->string; -} - -int git_refspec_force(const git_refspec *refspec) -{ - GIT_ASSERT_ARG(refspec); - - return refspec->force; -} - -int git_refspec_src_matches(const git_refspec *refspec, const char *refname) -{ - if (refspec == NULL || refspec->src == NULL) - return false; - - return (wildmatch(refspec->src, refname, 0) == 0); -} - -int git_refspec_dst_matches(const git_refspec *refspec, const char *refname) -{ - if (refspec == NULL || refspec->dst == NULL) - return false; - - return (wildmatch(refspec->dst, refname, 0) == 0); -} - -static int refspec_transform( - git_str *out, const char *from, const char *to, const char *name) -{ - const char *from_star, *to_star; - size_t replacement_len, star_offset; - - git_str_clear(out); - - /* - * There are two parts to each side of a refspec, the bit - * before the star and the bit after it. The star can be in - * the middle of the pattern, so we need to look at each bit - * individually. - */ - from_star = strchr(from, '*'); - to_star = strchr(to, '*'); - - GIT_ASSERT(from_star && to_star); - - /* star offset, both in 'from' and in 'name' */ - star_offset = from_star - from; - - /* the first half is copied over */ - git_str_put(out, to, to_star - to); - - /* - * Copy over the name, but exclude the trailing part in "from" starting - * after the glob - */ - replacement_len = strlen(name + star_offset) - strlen(from_star + 1); - git_str_put(out, name + star_offset, replacement_len); - - return git_str_puts(out, to_star + 1); -} - -int git_refspec_transform(git_buf *out, const git_refspec *spec, const char *name) -{ - GIT_BUF_WRAP_PRIVATE(out, git_refspec__transform, spec, name); -} - -int git_refspec__transform(git_str *out, const git_refspec *spec, const char *name) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(spec); - GIT_ASSERT_ARG(name); - - if (!git_refspec_src_matches(spec, name)) { - git_error_set(GIT_ERROR_INVALID, "ref '%s' doesn't match the source", name); - return -1; - } - - if (!spec->pattern) - return git_str_puts(out, spec->dst ? spec->dst : ""); - - return refspec_transform(out, spec->src, spec->dst, name); -} - -int git_refspec_rtransform(git_buf *out, const git_refspec *spec, const char *name) -{ - GIT_BUF_WRAP_PRIVATE(out, git_refspec__rtransform, spec, name); -} - -int git_refspec__rtransform(git_str *out, const git_refspec *spec, const char *name) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(spec); - GIT_ASSERT_ARG(name); - - if (!git_refspec_dst_matches(spec, name)) { - git_error_set(GIT_ERROR_INVALID, "ref '%s' doesn't match the destination", name); - return -1; - } - - if (!spec->pattern) - return git_str_puts(out, spec->src); - - return refspec_transform(out, spec->dst, spec->src, name); -} - -int git_refspec__serialize(git_str *out, const git_refspec *refspec) -{ - if (refspec->force) - git_str_putc(out, '+'); - - git_str_printf(out, "%s:%s", - refspec->src != NULL ? refspec->src : "", - refspec->dst != NULL ? refspec->dst : ""); - - return git_str_oom(out) == false; -} - -int git_refspec_is_wildcard(const git_refspec *spec) -{ - GIT_ASSERT_ARG(spec); - GIT_ASSERT_ARG(spec->src); - - return (spec->src[strlen(spec->src) - 1] == '*'); -} - -git_direction git_refspec_direction(const git_refspec *spec) -{ - GIT_ASSERT_ARG(spec); - - return spec->push; -} - -int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs) -{ - git_str buf = GIT_STR_INIT; - size_t j, pos; - git_remote_head key; - git_refspec *cur; - - const char *formatters[] = { - GIT_REFS_DIR "%s", - GIT_REFS_TAGS_DIR "%s", - GIT_REFS_HEADS_DIR "%s", - NULL - }; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(spec); - GIT_ASSERT_ARG(refs); - - cur = git__calloc(1, sizeof(git_refspec)); - GIT_ERROR_CHECK_ALLOC(cur); - - cur->force = spec->force; - cur->push = spec->push; - cur->pattern = spec->pattern; - cur->matching = spec->matching; - cur->string = git__strdup(spec->string); - - /* shorthand on the lhs */ - if (git__prefixcmp(spec->src, GIT_REFS_DIR)) { - for (j = 0; formatters[j]; j++) { - git_str_clear(&buf); - git_str_printf(&buf, formatters[j], spec->src); - GIT_ERROR_CHECK_ALLOC_STR(&buf); - - key.name = (char *) git_str_cstr(&buf); - if (!git_vector_search(&pos, refs, &key)) { - /* we found something to match the shorthand, set src to that */ - cur->src = git_str_detach(&buf); - } - } - } - - /* No shorthands found, copy over the name */ - if (cur->src == NULL && spec->src != NULL) { - cur->src = git__strdup(spec->src); - GIT_ERROR_CHECK_ALLOC(cur->src); - } - - if (spec->dst && git__prefixcmp(spec->dst, GIT_REFS_DIR)) { - /* if it starts with "remotes" then we just prepend "refs/" */ - if (!git__prefixcmp(spec->dst, "remotes/")) { - git_str_puts(&buf, GIT_REFS_DIR); - } else { - git_str_puts(&buf, GIT_REFS_HEADS_DIR); - } - - git_str_puts(&buf, spec->dst); - GIT_ERROR_CHECK_ALLOC_STR(&buf); - - cur->dst = git_str_detach(&buf); - } - - git_str_dispose(&buf); - - if (cur->dst == NULL && spec->dst != NULL) { - cur->dst = git__strdup(spec->dst); - GIT_ERROR_CHECK_ALLOC(cur->dst); - } - - return git_vector_insert(out, cur); -} diff --git a/src/refspec.h b/src/refspec.h deleted file mode 100644 index bf4f7fcfb..000000000 --- a/src/refspec.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_refspec_h__ -#define INCLUDE_refspec_h__ - -#include "common.h" - -#include "git2/refspec.h" -#include "str.h" -#include "vector.h" - -struct git_refspec { - char *string; - char *src; - char *dst; - unsigned int force :1, - push : 1, - pattern :1, - matching :1; -}; - -#define GIT_REFSPEC_TAGS "refs/tags/*:refs/tags/*" - -int git_refspec__transform(git_str *out, const git_refspec *spec, const char *name); -int git_refspec__rtransform(git_str *out, const git_refspec *spec, const char *name); - -int git_refspec__parse( - struct git_refspec *refspec, - const char *str, - bool is_fetch); - -void git_refspec__dispose(git_refspec *refspec); - -int git_refspec__serialize(git_str *out, const git_refspec *refspec); - -/** - * Determines if a refspec is a wildcard refspec. - * - * @param spec the refspec - * @return 1 if the refspec is a wildcard, 0 otherwise - */ -int git_refspec_is_wildcard(const git_refspec *spec); - -/** - * DWIM `spec` with `refs` existing on the remote, append the dwim'ed - * result in `out`. - */ -int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs); - -#endif diff --git a/src/regexp.c b/src/regexp.c deleted file mode 100644 index 2569dea0a..000000000 --- a/src/regexp.c +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "regexp.h" - -#if defined(GIT_REGEX_BUILTIN) || defined(GIT_REGEX_PCRE) - -int git_regexp_compile(git_regexp *r, const char *pattern, int flags) -{ - int erroffset, cflags = 0; - const char *error = NULL; - - if (flags & GIT_REGEXP_ICASE) - cflags |= PCRE_CASELESS; - - if ((*r = pcre_compile(pattern, cflags, &error, &erroffset, NULL)) == NULL) { - git_error_set_str(GIT_ERROR_REGEX, error); - return GIT_EINVALIDSPEC; - } - - return 0; -} - -void git_regexp_dispose(git_regexp *r) -{ - pcre_free(*r); - *r = NULL; -} - -int git_regexp_match(const git_regexp *r, const char *string) -{ - int error; - if ((error = pcre_exec(*r, NULL, string, (int) strlen(string), 0, 0, NULL, 0)) < 0) - return (error == PCRE_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; - return 0; -} - -int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) -{ - int static_ovec[9] = {0}, *ovec; - int error; - size_t i; - - /* The ovec array always needs to be a multiple of three */ - if (nmatches <= ARRAY_SIZE(static_ovec) / 3) - ovec = static_ovec; - else - ovec = git__calloc(nmatches * 3, sizeof(*ovec)); - GIT_ERROR_CHECK_ALLOC(ovec); - - if ((error = pcre_exec(*r, NULL, string, (int) strlen(string), 0, 0, ovec, (int) nmatches * 3)) < 0) - goto out; - - if (error == 0) - error = (int) nmatches; - - for (i = 0; i < (unsigned int) error; i++) { - matches[i].start = (ovec[i * 2] < 0) ? -1 : ovec[i * 2]; - matches[i].end = (ovec[i * 2 + 1] < 0) ? -1 : ovec[i * 2 + 1]; - } - for (i = (unsigned int) error; i < nmatches; i++) - matches[i].start = matches[i].end = -1; - -out: - if (nmatches > ARRAY_SIZE(static_ovec) / 3) - git__free(ovec); - if (error < 0) - return (error == PCRE_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; - return 0; -} - -#elif defined(GIT_REGEX_PCRE2) - -int git_regexp_compile(git_regexp *r, const char *pattern, int flags) -{ - unsigned char errmsg[1024]; - PCRE2_SIZE erroff; - int error, cflags = 0; - - if (flags & GIT_REGEXP_ICASE) - cflags |= PCRE2_CASELESS; - - if ((*r = pcre2_compile((const unsigned char *) pattern, PCRE2_ZERO_TERMINATED, - cflags, &error, &erroff, NULL)) == NULL) { - pcre2_get_error_message(error, errmsg, sizeof(errmsg)); - git_error_set_str(GIT_ERROR_REGEX, (char *) errmsg); - return GIT_EINVALIDSPEC; - } - - return 0; -} - -void git_regexp_dispose(git_regexp *r) -{ - pcre2_code_free(*r); - *r = NULL; -} - -int git_regexp_match(const git_regexp *r, const char *string) -{ - pcre2_match_data *data; - int error; - - data = pcre2_match_data_create(1, NULL); - GIT_ERROR_CHECK_ALLOC(data); - - if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string), - 0, 0, data, NULL)) < 0) - return (error == PCRE2_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; - - pcre2_match_data_free(data); - return 0; -} - -int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) -{ - pcre2_match_data *data = NULL; - PCRE2_SIZE *ovec; - int error; - size_t i; - - if ((data = pcre2_match_data_create(nmatches, NULL)) == NULL) { - git_error_set_oom(); - goto out; - } - - if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string), - 0, 0, data, NULL)) < 0) - goto out; - - if (error == 0 || (unsigned int) error > nmatches) - error = nmatches; - ovec = pcre2_get_ovector_pointer(data); - - for (i = 0; i < (unsigned int) error; i++) { - matches[i].start = (ovec[i * 2] == PCRE2_UNSET) ? -1 : (ssize_t) ovec[i * 2]; - matches[i].end = (ovec[i * 2 + 1] == PCRE2_UNSET) ? -1 : (ssize_t) ovec[i * 2 + 1]; - } - for (i = (unsigned int) error; i < nmatches; i++) - matches[i].start = matches[i].end = -1; - -out: - pcre2_match_data_free(data); - if (error < 0) - return (error == PCRE2_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; - return 0; -} - -#elif defined(GIT_REGEX_REGCOMP) || defined(GIT_REGEX_REGCOMP_L) - -#if defined(GIT_REGEX_REGCOMP_L) -# include -#endif - -int git_regexp_compile(git_regexp *r, const char *pattern, int flags) -{ - int cflags = REG_EXTENDED, error; - char errmsg[1024]; - - if (flags & GIT_REGEXP_ICASE) - cflags |= REG_ICASE; - -# if defined(GIT_REGEX_REGCOMP) - if ((error = regcomp(r, pattern, cflags)) != 0) -# else - if ((error = regcomp_l(r, pattern, cflags, (locale_t) 0)) != 0) -# endif - { - regerror(error, r, errmsg, sizeof(errmsg)); - git_error_set_str(GIT_ERROR_REGEX, errmsg); - return GIT_EINVALIDSPEC; - } - - return 0; -} - -void git_regexp_dispose(git_regexp *r) -{ - regfree(r); -} - -int git_regexp_match(const git_regexp *r, const char *string) -{ - int error; - if ((error = regexec(r, string, 0, NULL, 0)) != 0) - return (error == REG_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; - return 0; -} - -int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) -{ - regmatch_t static_m[3], *m; - int error; - size_t i; - - if (nmatches <= ARRAY_SIZE(static_m)) - m = static_m; - else - m = git__calloc(nmatches, sizeof(*m)); - - if ((error = regexec(r, string, nmatches, m, 0)) != 0) - goto out; - - for (i = 0; i < nmatches; i++) { - matches[i].start = (m[i].rm_so < 0) ? -1 : m[i].rm_so; - matches[i].end = (m[i].rm_eo < 0) ? -1 : m[i].rm_eo; - } - -out: - if (nmatches > ARRAY_SIZE(static_m)) - git__free(m); - if (error) - return (error == REG_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; - return 0; -} - -#endif diff --git a/src/regexp.h b/src/regexp.h deleted file mode 100644 index 2592ef383..000000000 --- a/src/regexp.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_regexp_h__ -#define INCLUDE_regexp_h__ - -#include "common.h" - -#if defined(GIT_REGEX_BUILTIN) || defined(GIT_REGEX_PCRE) -# include "pcre.h" -typedef pcre *git_regexp; -# define GIT_REGEX_INIT NULL -#elif defined(GIT_REGEX_PCRE2) -# define PCRE2_CODE_UNIT_WIDTH 8 -# include -typedef pcre2_code *git_regexp; -# define GIT_REGEX_INIT NULL -#elif defined(GIT_REGEX_REGCOMP) || defined(GIT_REGEX_REGCOMP_L) -# include -typedef regex_t git_regexp; -# define GIT_REGEX_INIT { 0 } -#else -# error "No regex backend" -#endif - -/** Options supported by @git_regexp_compile. */ -typedef enum { - /** Enable case-insensitive matching */ - GIT_REGEXP_ICASE = (1 << 0) -} git_regexp_flags_t; - -/** Structure containing information about regular expression matching groups */ -typedef struct { - /** Start of the given match. -1 if the group didn't match anything */ - ssize_t start; - /** End of the given match. -1 if the group didn't match anything */ - ssize_t end; -} git_regmatch; - -/** - * Compile a regular expression. The compiled expression needs to - * be cleaned up afterwards with `git_regexp_dispose`. - * - * @param r Pointer to the storage where to initialize the regular expression. - * @param pattern The pattern that shall be compiled. - * @param flags Flags to alter how the pattern shall be handled. - * 0 for defaults, otherwise see @git_regexp_flags_t. - * @return 0 on success, otherwise a negative return value. - */ -int git_regexp_compile(git_regexp *r, const char *pattern, int flags); - -/** - * Free memory associated with the regular expression - * - * @param r The regular expression structure to dispose. - */ -void git_regexp_dispose(git_regexp *r); - -/** - * Test whether a given string matches a compiled regular - * expression. - * - * @param r Compiled regular expression. - * @param string String to match against the regular expression. - * @return 0 if the string matches, a negative error code - * otherwise. GIT_ENOTFOUND if no match was found, - * GIT_EINVALIDSPEC if the regular expression matching - * was invalid. - */ -int git_regexp_match(const git_regexp *r, const char *string); - -/** - * Search for matches inside of a given string. - * - * Given a regular expression with capturing groups, this - * function will populate provided @git_regmatch structures with - * offsets for each of the given matches. Non-matching groups - * will have start and end values of the respective @git_regmatch - * structure set to -1. - * - * @param r Compiled regular expression. - * @param string String to match against the regular expression. - * @param nmatches Number of @git_regmatch structures provided by - * the user. - * @param matches Pointer to an array of @git_regmatch structures. - * @return 0 if the string matches, a negative error code - * otherwise. GIT_ENOTFOUND if no match was found, - * GIT_EINVALIDSPEC if the regular expression matching - * was invalid. - */ -int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches); - -#endif diff --git a/src/remote.c b/src/remote.c deleted file mode 100644 index f6421b9eb..000000000 --- a/src/remote.c +++ /dev/null @@ -1,3084 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "remote.h" - -#include "buf.h" -#include "branch.h" -#include "config.h" -#include "repository.h" -#include "fetch.h" -#include "refs.h" -#include "refspec.h" -#include "fetchhead.h" -#include "push.h" -#include "proxy.h" - -#include "git2/config.h" -#include "git2/types.h" -#include "git2/oid.h" -#include "git2/net.h" - -#define CONFIG_URL_FMT "remote.%s.url" -#define CONFIG_PUSHURL_FMT "remote.%s.pushurl" -#define CONFIG_FETCH_FMT "remote.%s.fetch" -#define CONFIG_PUSH_FMT "remote.%s.push" -#define CONFIG_TAGOPT_FMT "remote.%s.tagopt" - -static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs); -static int lookup_remote_prune_config(git_remote *remote, git_config *config, const char *name); -static int apply_insteadof(char **out, git_config *config, const char *url, int direction, bool use_default_if_empty); - -static int add_refspec_to(git_vector *vector, const char *string, bool is_fetch) -{ - git_refspec *spec; - - spec = git__calloc(1, sizeof(git_refspec)); - GIT_ERROR_CHECK_ALLOC(spec); - - if (git_refspec__parse(spec, string, is_fetch) < 0) { - git__free(spec); - return -1; - } - - spec->push = !is_fetch; - if (git_vector_insert(vector, spec) < 0) { - git_refspec__dispose(spec); - git__free(spec); - return -1; - } - - return 0; -} - -static int add_refspec(git_remote *remote, const char *string, bool is_fetch) -{ - return add_refspec_to(&remote->refspecs, string, is_fetch); -} - -static int download_tags_value(git_remote *remote, git_config *cfg) -{ - git_config_entry *ce; - git_str buf = GIT_STR_INIT; - int error; - - if (git_str_printf(&buf, "remote.%s.tagopt", remote->name) < 0) - return -1; - - error = git_config__lookup_entry(&ce, cfg, git_str_cstr(&buf), false); - git_str_dispose(&buf); - - if (!error && ce && ce->value) { - if (!strcmp(ce->value, "--no-tags")) - remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; - else if (!strcmp(ce->value, "--tags")) - remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; - } - - git_config_entry_free(ce); - return error; -} - -static int ensure_remote_name_is_valid(const char *name) -{ - int valid, error; - - error = git_remote_name_is_valid(&valid, name); - - if (!error && !valid) { - git_error_set( - GIT_ERROR_CONFIG, - "'%s' is not a valid remote name.", name ? name : "(null)"); - error = GIT_EINVALIDSPEC; - } - - return error; -} - -static int write_add_refspec(git_repository *repo, const char *name, const char *refspec, bool fetch) -{ - git_config *cfg; - git_str var = GIT_STR_INIT; - git_refspec spec; - const char *fmt; - int error; - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - return error; - - fmt = fetch ? CONFIG_FETCH_FMT : CONFIG_PUSH_FMT; - - if ((error = ensure_remote_name_is_valid(name)) < 0) - return error; - - if ((error = git_refspec__parse(&spec, refspec, fetch)) < 0) - return error; - - git_refspec__dispose(&spec); - - if ((error = git_str_printf(&var, fmt, name)) < 0) - return error; - - /* - * "$^" is an unmatchable regexp: it will not match anything at all, so - * all values will be considered new and we will not replace any - * present value. - */ - if ((error = git_config_set_multivar(cfg, var.ptr, "$^", refspec)) < 0) { - goto cleanup; - } - -cleanup: - git_str_dispose(&var); - return 0; -} - -static int canonicalize_url(git_str *out, const char *in) -{ - if (in == NULL || strlen(in) == 0) { - git_error_set(GIT_ERROR_INVALID, "cannot set empty URL"); - return GIT_EINVALIDSPEC; - } - -#ifdef GIT_WIN32 - /* Given a UNC path like \\server\path, we need to convert this - * to //server/path for compatibility with core git. - */ - if (in[0] == '\\' && in[1] == '\\' && - (git__isalpha(in[2]) || git__isdigit(in[2]))) { - const char *c; - for (c = in; *c; c++) - git_str_putc(out, *c == '\\' ? '/' : *c); - - return git_str_oom(out) ? -1 : 0; - } -#endif - - return git_str_puts(out, in); -} - -static int default_fetchspec_for_name(git_str *buf, const char *name) -{ - if (git_str_printf(buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0) - return -1; - - return 0; -} - -static int ensure_remote_doesnot_exist(git_repository *repo, const char *name) -{ - int error; - git_remote *remote; - - error = git_remote_lookup(&remote, repo, name); - - if (error == GIT_ENOTFOUND) - return 0; - - if (error < 0) - return error; - - git_remote_free(remote); - - git_error_set(GIT_ERROR_CONFIG, "remote '%s' already exists", name); - - return GIT_EEXISTS; -} - -int git_remote_create_options_init(git_remote_create_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_remote_create_options, GIT_REMOTE_CREATE_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_remote_create_init_options(git_remote_create_options *opts, unsigned int version) -{ - return git_remote_create_options_init(opts, version); -} -#endif - -int git_remote_create_with_opts(git_remote **out, const char *url, const git_remote_create_options *opts) -{ - git_remote *remote = NULL; - git_config *config_ro = NULL, *config_rw; - git_str canonical_url = GIT_STR_INIT; - git_str var = GIT_STR_INIT; - git_str specbuf = GIT_STR_INIT; - const git_remote_create_options dummy_opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - int error = -1; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(url); - - if (!opts) { - opts = &dummy_opts; - } - - GIT_ERROR_CHECK_VERSION(opts, GIT_REMOTE_CREATE_OPTIONS_VERSION, "git_remote_create_options"); - - if (opts->name != NULL) { - if ((error = ensure_remote_name_is_valid(opts->name)) < 0) - return error; - - if (opts->repository && - (error = ensure_remote_doesnot_exist(opts->repository, opts->name)) < 0) - return error; - } - - if (opts->repository) { - if ((error = git_repository_config_snapshot(&config_ro, opts->repository)) < 0) - goto on_error; - } - - remote = git__calloc(1, sizeof(git_remote)); - GIT_ERROR_CHECK_ALLOC(remote); - - remote->repo = opts->repository; - - if ((error = git_vector_init(&remote->refs, 8, NULL)) < 0 || - (error = canonicalize_url(&canonical_url, url)) < 0) - goto on_error; - - if (opts->repository && !(opts->flags & GIT_REMOTE_CREATE_SKIP_INSTEADOF)) { - if ((error = apply_insteadof(&remote->url, config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH, true)) < 0 || - (error = apply_insteadof(&remote->pushurl, config_ro, canonical_url.ptr, GIT_DIRECTION_PUSH, false)) < 0) - goto on_error; - } else { - remote->url = git__strdup(canonical_url.ptr); - GIT_ERROR_CHECK_ALLOC(remote->url); - } - - if (opts->name != NULL) { - remote->name = git__strdup(opts->name); - GIT_ERROR_CHECK_ALLOC(remote->name); - - if (opts->repository && - ((error = git_str_printf(&var, CONFIG_URL_FMT, opts->name)) < 0 || - (error = git_repository_config__weakptr(&config_rw, opts->repository)) < 0 || - (error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0)) - goto on_error; - } - - if (opts->fetchspec != NULL || - (opts->name && !(opts->flags & GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC))) { - const char *fetch = NULL; - if (opts->fetchspec) { - fetch = opts->fetchspec; - } else { - if ((error = default_fetchspec_for_name(&specbuf, opts->name)) < 0) - goto on_error; - - fetch = git_str_cstr(&specbuf); - } - - if ((error = add_refspec(remote, fetch, true)) < 0) - goto on_error; - - /* only write for named remotes with a repository */ - if (opts->repository && opts->name && - ((error = write_add_refspec(opts->repository, opts->name, fetch, true)) < 0 || - (error = lookup_remote_prune_config(remote, config_ro, opts->name)) < 0)) - goto on_error; - - /* Move the data over to where the matching functions can find them */ - if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0) - goto on_error; - } - - /* A remote without a name doesn't download tags */ - if (!opts->name) - remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; - else - remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; - - - git_str_dispose(&var); - - *out = remote; - error = 0; - -on_error: - if (error) - git_remote_free(remote); - - git_config_free(config_ro); - git_str_dispose(&specbuf); - git_str_dispose(&canonical_url); - git_str_dispose(&var); - return error; -} - -int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url) -{ - git_str buf = GIT_STR_INIT; - int error; - git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - - /* Those 2 tests are duplicated here because of backward-compatibility */ - if ((error = ensure_remote_name_is_valid(name)) < 0) - return error; - - if (canonicalize_url(&buf, url) < 0) - return GIT_ERROR; - - git_str_clear(&buf); - - opts.repository = repo; - opts.name = name; - - error = git_remote_create_with_opts(out, url, &opts); - - git_str_dispose(&buf); - - return error; -} - -int git_remote_create_with_fetchspec(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) -{ - int error; - git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - - if ((error = ensure_remote_name_is_valid(name)) < 0) - return error; - - opts.repository = repo; - opts.name = name; - opts.fetchspec = fetch; - opts.flags = GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC; - - return git_remote_create_with_opts(out, url, &opts); -} - -int git_remote_create_anonymous(git_remote **out, git_repository *repo, const char *url) -{ - git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - - opts.repository = repo; - - return git_remote_create_with_opts(out, url, &opts); -} - -int git_remote_create_detached(git_remote **out, const char *url) -{ - return git_remote_create_with_opts(out, url, NULL); -} - -int git_remote_dup(git_remote **dest, git_remote *source) -{ - size_t i; - int error = 0; - git_refspec *spec; - git_remote *remote = git__calloc(1, sizeof(git_remote)); - GIT_ERROR_CHECK_ALLOC(remote); - - if (source->name != NULL) { - remote->name = git__strdup(source->name); - GIT_ERROR_CHECK_ALLOC(remote->name); - } - - if (source->url != NULL) { - remote->url = git__strdup(source->url); - GIT_ERROR_CHECK_ALLOC(remote->url); - } - - if (source->pushurl != NULL) { - remote->pushurl = git__strdup(source->pushurl); - GIT_ERROR_CHECK_ALLOC(remote->pushurl); - } - - remote->repo = source->repo; - remote->download_tags = source->download_tags; - remote->prune_refs = source->prune_refs; - - if (git_vector_init(&remote->refs, 32, NULL) < 0 || - git_vector_init(&remote->refspecs, 2, NULL) < 0 || - git_vector_init(&remote->active_refspecs, 2, NULL) < 0) { - error = -1; - goto cleanup; - } - - git_vector_foreach(&source->refspecs, i, spec) { - if ((error = add_refspec(remote, spec->string, !spec->push)) < 0) - goto cleanup; - } - - *dest = remote; - -cleanup: - - if (error < 0) - git__free(remote); - - return error; -} - -struct refspec_cb_data { - git_remote *remote; - int fetch; -}; - -static int refspec_cb(const git_config_entry *entry, void *payload) -{ - struct refspec_cb_data *data = (struct refspec_cb_data *)payload; - return add_refspec(data->remote, entry->value, data->fetch); -} - -static int get_optional_config( - bool *found, git_config *config, git_str *buf, - git_config_foreach_cb cb, void *payload) -{ - int error = 0; - const char *key = git_str_cstr(buf); - - if (git_str_oom(buf)) - return -1; - - if (cb != NULL) - error = git_config_get_multivar_foreach(config, key, NULL, cb, payload); - else - error = git_config_get_string(payload, config, key); - - if (found) - *found = !error; - - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - return error; -} - -int git_remote_lookup(git_remote **out, git_repository *repo, const char *name) -{ - git_remote *remote = NULL; - git_str buf = GIT_STR_INIT; - const char *val; - int error = 0; - git_config *config; - struct refspec_cb_data data = { NULL }; - bool optional_setting_found = false, found; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = ensure_remote_name_is_valid(name)) < 0) - return error; - - if ((error = git_repository_config_snapshot(&config, repo)) < 0) - return error; - - remote = git__calloc(1, sizeof(git_remote)); - GIT_ERROR_CHECK_ALLOC(remote); - - remote->name = git__strdup(name); - GIT_ERROR_CHECK_ALLOC(remote->name); - - if (git_vector_init(&remote->refs, 32, NULL) < 0 || - git_vector_init(&remote->refspecs, 2, NULL) < 0 || - git_vector_init(&remote->passive_refspecs, 2, NULL) < 0 || - git_vector_init(&remote->active_refspecs, 2, NULL) < 0) { - error = -1; - goto cleanup; - } - - if ((error = git_str_printf(&buf, "remote.%s.url", name)) < 0) - goto cleanup; - - if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) - goto cleanup; - - optional_setting_found |= found; - - remote->repo = repo; - remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; - - if (found && strlen(val) > 0) { - if ((error = apply_insteadof(&remote->url, config, val, GIT_DIRECTION_FETCH, true)) < 0 || - (error = apply_insteadof(&remote->pushurl, config, val, GIT_DIRECTION_PUSH, false)) < 0) - goto cleanup; - } - - val = NULL; - git_str_clear(&buf); - git_str_printf(&buf, "remote.%s.pushurl", name); - - if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) - goto cleanup; - - optional_setting_found |= found; - - if (!optional_setting_found) { - error = GIT_ENOTFOUND; - git_error_set(GIT_ERROR_CONFIG, "remote '%s' does not exist", name); - goto cleanup; - } - - if (found && strlen(val) > 0) { - if (remote->pushurl) - git__free(remote->pushurl); - - if ((error = apply_insteadof(&remote->pushurl, config, val, GIT_DIRECTION_FETCH, true)) < 0) - goto cleanup; - } - - data.remote = remote; - data.fetch = true; - - git_str_clear(&buf); - git_str_printf(&buf, "remote.%s.fetch", name); - - if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) - goto cleanup; - - data.fetch = false; - git_str_clear(&buf); - git_str_printf(&buf, "remote.%s.push", name); - - if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) - goto cleanup; - - if ((error = download_tags_value(remote, config)) < 0) - goto cleanup; - - if ((error = lookup_remote_prune_config(remote, config, name)) < 0) - goto cleanup; - - /* Move the data over to where the matching functions can find them */ - if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0) - goto cleanup; - - *out = remote; - -cleanup: - git_config_free(config); - git_str_dispose(&buf); - - if (error < 0) - git_remote_free(remote); - - return error; -} - -static int lookup_remote_prune_config(git_remote *remote, git_config *config, const char *name) -{ - git_str buf = GIT_STR_INIT; - int error = 0; - - git_str_printf(&buf, "remote.%s.prune", name); - - if ((error = git_config_get_bool(&remote->prune_refs, config, git_str_cstr(&buf))) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - - if ((error = git_config_get_bool(&remote->prune_refs, config, "fetch.prune")) < 0) { - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - } - } - } - - git_str_dispose(&buf); - return error; -} - -const char *git_remote_name(const git_remote *remote) -{ - GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); - return remote->name; -} - -git_repository *git_remote_owner(const git_remote *remote) -{ - GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); - return remote->repo; -} - -const char *git_remote_url(const git_remote *remote) -{ - GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); - return remote->url; -} - -int git_remote_set_instance_url(git_remote *remote, const char *url) -{ - char *tmp; - - GIT_ASSERT_ARG(remote); - GIT_ASSERT_ARG(url); - - if ((tmp = git__strdup(url)) == NULL) - return -1; - - git__free(remote->url); - remote->url = tmp; - - return 0; -} - -static int set_url(git_repository *repo, const char *remote, const char *pattern, const char *url) -{ - git_config *cfg; - git_str buf = GIT_STR_INIT, canonical_url = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(remote); - - if ((error = ensure_remote_name_is_valid(remote)) < 0) - return error; - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - return error; - - if ((error = git_str_printf(&buf, pattern, remote)) < 0) - return error; - - if (url) { - if ((error = canonicalize_url(&canonical_url, url)) < 0) - goto cleanup; - - error = git_config_set_string(cfg, buf.ptr, url); - } else { - error = git_config_delete_entry(cfg, buf.ptr); - } - -cleanup: - git_str_dispose(&canonical_url); - git_str_dispose(&buf); - - return error; -} - -int git_remote_set_url(git_repository *repo, const char *remote, const char *url) -{ - return set_url(repo, remote, CONFIG_URL_FMT, url); -} - -const char *git_remote_pushurl(const git_remote *remote) -{ - GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); - return remote->pushurl; -} - -int git_remote_set_instance_pushurl(git_remote *remote, const char *url) -{ - char *tmp; - - GIT_ASSERT_ARG(remote); - GIT_ASSERT_ARG(url); - - if ((tmp = git__strdup(url)) == NULL) - return -1; - - git__free(remote->pushurl); - remote->pushurl = tmp; - - return 0; -} - -int git_remote_set_pushurl(git_repository *repo, const char *remote, const char *url) -{ - return set_url(repo, remote, CONFIG_PUSHURL_FMT, url); -} - -static int resolve_url( - git_str *resolved_url, - const char *url, - int direction, - const git_remote_callbacks *callbacks) -{ -#ifdef GIT_DEPRECATE_HARD - GIT_UNUSED(direction); - GIT_UNUSED(callbacks); -#else - git_buf buf = GIT_BUF_INIT; - int error; - - if (callbacks && callbacks->resolve_url) { - error = callbacks->resolve_url(&buf, url, direction, callbacks->payload); - - if (error != GIT_PASSTHROUGH) { - git_error_set_after_callback_function(error, "git_resolve_url_cb"); - - git_str_set(resolved_url, buf.ptr, buf.size); - git_buf_dispose(&buf); - - return error; - } - } -#endif - - return git_str_sets(resolved_url, url); -} - -int git_remote__urlfordirection( - git_str *url_out, - struct git_remote *remote, - int direction, - const git_remote_callbacks *callbacks) -{ - const char *url = NULL; - - GIT_ASSERT_ARG(remote); - GIT_ASSERT_ARG(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH); - - if (callbacks && callbacks->remote_ready) { - int status = callbacks->remote_ready(remote, direction, callbacks->payload); - - if (status != 0 && status != GIT_PASSTHROUGH) { - git_error_set_after_callback_function(status, "git_remote_ready_cb"); - return status; - } - } - - if (direction == GIT_DIRECTION_FETCH) - url = remote->url; - else if (direction == GIT_DIRECTION_PUSH) - url = remote->pushurl ? remote->pushurl : remote->url; - - if (!url) { - git_error_set(GIT_ERROR_INVALID, - "malformed remote '%s' - missing %s URL", - remote->name ? remote->name : "(anonymous)", - direction == GIT_DIRECTION_FETCH ? "fetch" : "push"); - return GIT_EINVALID; - } - - return resolve_url(url_out, url, direction, callbacks); -} - -int git_remote_connect_options_init( - git_remote_connect_options *opts, - unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_remote_connect_options, GIT_REMOTE_CONNECT_OPTIONS_INIT); - return 0; -} - -int git_remote_connect_options_dup( - git_remote_connect_options *dst, - const git_remote_connect_options *src) -{ - memcpy(dst, src, sizeof(git_remote_connect_options)); - - if (git_proxy_options_dup(&dst->proxy_opts, &src->proxy_opts) < 0 || - git_strarray_copy(&dst->custom_headers, &src->custom_headers) < 0) - return -1; - - return 0; -} - -void git_remote_connect_options_dispose(git_remote_connect_options *opts) -{ - if (!opts) - return; - - git_strarray_dispose(&opts->custom_headers); - git_proxy_options_dispose(&opts->proxy_opts); -} - -static size_t http_header_name_length(const char *http_header) -{ - const char *colon = strchr(http_header, ':'); - if (!colon) - return 0; - return colon - http_header; -} - -static bool is_malformed_http_header(const char *http_header) -{ - const char *c; - size_t name_len; - - /* Disallow \r and \n */ - if ((c = strchr(http_header, '\r')) != NULL) - return true; - if ((c = strchr(http_header, '\n')) != NULL) - return true; - - /* Require a header name followed by : */ - if ((name_len = http_header_name_length(http_header)) < 1) - return true; - - return false; -} - -static char *forbidden_custom_headers[] = { - "User-Agent", - "Host", - "Accept", - "Content-Type", - "Transfer-Encoding", - "Content-Length", -}; - -static bool is_forbidden_custom_header(const char *custom_header) -{ - unsigned long i; - size_t name_len = http_header_name_length(custom_header); - - /* Disallow headers that we set */ - for (i = 0; i < ARRAY_SIZE(forbidden_custom_headers); i++) - if (strncmp(forbidden_custom_headers[i], custom_header, name_len) == 0) - return true; - - return false; -} - -static int validate_custom_headers(const git_strarray *custom_headers) -{ - size_t i; - - if (!custom_headers) - return 0; - - for (i = 0; i < custom_headers->count; i++) { - if (is_malformed_http_header(custom_headers->strings[i])) { - git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is malformed", custom_headers->strings[i]); - return -1; - } - - if (is_forbidden_custom_header(custom_headers->strings[i])) { - git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is already set by libgit2", custom_headers->strings[i]); - return -1; - } - } - - return 0; -} - -static int lookup_redirect_config( - git_remote_redirect_t *out, - git_repository *repo) -{ - git_config *config; - const char *value; - int bool_value, error = 0; - - if (!repo) { - *out = GIT_REMOTE_REDIRECT_INITIAL; - return 0; - } - - if ((error = git_repository_config_snapshot(&config, repo)) < 0) - goto done; - - if ((error = git_config_get_string(&value, config, "http.followRedirects")) < 0) { - if (error == GIT_ENOTFOUND) { - *out = GIT_REMOTE_REDIRECT_INITIAL; - error = 0; - } - - goto done; - } - - if (git_config_parse_bool(&bool_value, value) == 0) { - *out = bool_value ? GIT_REMOTE_REDIRECT_ALL : - GIT_REMOTE_REDIRECT_NONE; - } else if (strcasecmp(value, "initial") == 0) { - *out = GIT_REMOTE_REDIRECT_INITIAL; - } else { - git_error_set(GIT_ERROR_CONFIG, "invalid configuration setting '%s' for 'http.followRedirects'", value); - error = -1; - } - -done: - git_config_free(config); - return error; -} - -int git_remote_connect_options_normalize( - git_remote_connect_options *dst, - git_repository *repo, - const git_remote_connect_options *src) -{ - git_remote_connect_options_dispose(dst); - git_remote_connect_options_init(dst, GIT_REMOTE_CONNECT_OPTIONS_VERSION); - - if (src) { - GIT_ERROR_CHECK_VERSION(src, GIT_REMOTE_CONNECT_OPTIONS_VERSION, "git_remote_connect_options"); - GIT_ERROR_CHECK_VERSION(&src->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); - GIT_ERROR_CHECK_VERSION(&src->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); - - if (validate_custom_headers(&src->custom_headers) < 0 || - git_remote_connect_options_dup(dst, src) < 0) - return -1; - } - - if (dst->follow_redirects == 0) { - if (lookup_redirect_config(&dst->follow_redirects, repo) < 0) - return -1; - } - - return 0; -} - -int git_remote_connect_ext( - git_remote *remote, - git_direction direction, - const git_remote_connect_options *given_opts) -{ - git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; - git_str url = GIT_STR_INIT; - git_transport *t; - int error; - - GIT_ASSERT_ARG(remote); - - if (given_opts) - memcpy(&opts, given_opts, sizeof(git_remote_connect_options)); - - GIT_ERROR_CHECK_VERSION(&opts.callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); - GIT_ERROR_CHECK_VERSION(&opts.proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); - - t = remote->transport; - - if ((error = git_remote__urlfordirection(&url, remote, direction, &opts.callbacks)) < 0) - goto on_error; - - /* If we don't have a transport object yet, and the caller specified a - * custom transport factory, use that */ - if (!t && opts.callbacks.transport && - (error = opts.callbacks.transport(&t, remote, opts.callbacks.payload)) < 0) - goto on_error; - - /* If we still don't have a transport, then use the global - * transport registrations which map URI schemes to transport factories */ - if (!t && (error = git_transport_new(&t, remote, url.ptr)) < 0) - goto on_error; - - if ((error = t->connect(t, url.ptr, direction, &opts)) != 0) - goto on_error; - - remote->transport = t; - - git_str_dispose(&url); - - return 0; - -on_error: - if (t) - t->free(t); - - git_str_dispose(&url); - - if (t == remote->transport) - remote->transport = NULL; - - return error; -} - -int git_remote_connect( - git_remote *remote, - git_direction direction, - const git_remote_callbacks *callbacks, - const git_proxy_options *proxy, - const git_strarray *custom_headers) -{ - git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; - - if (callbacks) - memcpy(&opts.callbacks, callbacks, sizeof(git_remote_callbacks)); - - if (proxy) - memcpy(&opts.proxy_opts, proxy, sizeof(git_proxy_options)); - - if (custom_headers) - memcpy(&opts.custom_headers, custom_headers, sizeof(git_strarray)); - - return git_remote_connect_ext(remote, direction, &opts); -} - -int git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote) -{ - GIT_ASSERT_ARG(remote); - - if (!remote->transport) { - git_error_set(GIT_ERROR_NET, "this remote has never connected"); - return -1; - } - - return remote->transport->ls(out, size, remote->transport); -} - -int git_remote_capabilities(unsigned int *out, git_remote *remote) -{ - GIT_ASSERT_ARG(remote); - - *out = 0; - - if (!remote->transport) { - git_error_set(GIT_ERROR_NET, "this remote has never connected"); - return -1; - } - - return remote->transport->capabilities(out, remote->transport); -} - -static int lookup_config(char **out, git_config *cfg, const char *name) -{ - git_config_entry *ce = NULL; - int error; - - if ((error = git_config__lookup_entry(&ce, cfg, name, false)) < 0) - return error; - - if (ce && ce->value) { - *out = git__strdup(ce->value); - GIT_ERROR_CHECK_ALLOC(*out); - } else { - error = GIT_ENOTFOUND; - } - - git_config_entry_free(ce); - return error; -} - -static void url_config_trim(git_net_url *url) -{ - size_t len = strlen(url->path); - - if (url->path[len - 1] == '/') { - len--; - } else { - while (len && url->path[len - 1] != '/') - len--; - } - - url->path[len] = '\0'; -} - -static int http_proxy_config(char **out, git_remote *remote, git_net_url *url) -{ - git_config *cfg = NULL; - git_str buf = GIT_STR_INIT; - git_net_url lookup_url = GIT_NET_URL_INIT; - int error; - - if ((error = git_net_url_dup(&lookup_url, url)) < 0) - goto done; - - if (remote->repo) { - if ((error = git_repository_config(&cfg, remote->repo)) < 0) - goto done; - } else { - if ((error = git_config_open_default(&cfg)) < 0) - goto done; - } - - /* remote..proxy config setting */ - if (remote->name && remote->name[0]) { - git_str_clear(&buf); - - if ((error = git_str_printf(&buf, "remote.%s.proxy", remote->name)) < 0 || - (error = lookup_config(out, cfg, buf.ptr)) != GIT_ENOTFOUND) - goto done; - } - - while (true) { - git_str_clear(&buf); - - if ((error = git_str_puts(&buf, "http.")) < 0 || - (error = git_net_url_fmt(&buf, &lookup_url)) < 0 || - (error = git_str_puts(&buf, ".proxy")) < 0 || - (error = lookup_config(out, cfg, buf.ptr)) != GIT_ENOTFOUND) - goto done; - - if (! lookup_url.path[0]) - break; - - url_config_trim(&lookup_url); - } - - git_str_clear(&buf); - - error = lookup_config(out, cfg, "http.proxy"); - -done: - git_config_free(cfg); - git_str_dispose(&buf); - git_net_url_dispose(&lookup_url); - return error; -} - -static int http_proxy_env(char **out, git_remote *remote, git_net_url *url) -{ - git_str proxy_env = GIT_STR_INIT, no_proxy_env = GIT_STR_INIT; - bool use_ssl = (strcmp(url->scheme, "https") == 0); - int error; - - GIT_UNUSED(remote); - - /* http_proxy / https_proxy environment variables */ - error = git__getenv(&proxy_env, use_ssl ? "https_proxy" : "http_proxy"); - - /* try uppercase environment variables */ - if (error == GIT_ENOTFOUND) - error = git__getenv(&proxy_env, use_ssl ? "HTTPS_PROXY" : "HTTP_PROXY"); - - if (error) - goto done; - - /* no_proxy/NO_PROXY environment variables */ - error = git__getenv(&no_proxy_env, "no_proxy"); - - if (error == GIT_ENOTFOUND) - error = git__getenv(&no_proxy_env, "NO_PROXY"); - - if (error && error != GIT_ENOTFOUND) - goto done; - - if (!git_net_url_matches_pattern_list(url, no_proxy_env.ptr)) - *out = git_str_detach(&proxy_env); - else - error = GIT_ENOTFOUND; - -done: - git_str_dispose(&proxy_env); - git_str_dispose(&no_proxy_env); - return error; -} - -int git_remote__http_proxy(char **out, git_remote *remote, git_net_url *url) -{ - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(remote); - - *out = NULL; - - /* - * Go through the possible sources for proxy configuration, - * Examine the various git config options first, then - * consult environment variables. - */ - if ((error = http_proxy_config(out, remote, url)) != GIT_ENOTFOUND || - (error = http_proxy_env(out, remote, url)) != GIT_ENOTFOUND) - return error; - - return 0; -} - -/* DWIM `refspecs` based on `refs` and append the output to `out` */ -static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs) -{ - size_t i; - git_refspec *spec; - - git_vector_foreach(refspecs, i, spec) { - if (git_refspec__dwim_one(out, spec, refs) < 0) - return -1; - } - - return 0; -} - -static void free_refspecs(git_vector *vec) -{ - size_t i; - git_refspec *spec; - - git_vector_foreach(vec, i, spec) { - git_refspec__dispose(spec); - git__free(spec); - } - - git_vector_clear(vec); -} - -static int remote_head_cmp(const void *_a, const void *_b) -{ - const git_remote_head *a = (git_remote_head *) _a; - const git_remote_head *b = (git_remote_head *) _b; - - return git__strcmp_cb(a->name, b->name); -} - -static int ls_to_vector(git_vector *out, git_remote *remote) -{ - git_remote_head **heads; - size_t heads_len, i; - - if (git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote) < 0) - return -1; - - if (git_vector_init(out, heads_len, remote_head_cmp) < 0) - return -1; - - for (i = 0; i < heads_len; i++) { - if (git_vector_insert(out, heads[i]) < 0) - return -1; - } - - return 0; -} - -#define copy_opts(out, in) \ - if (in) { \ - (out)->callbacks = (in)->callbacks; \ - (out)->proxy_opts = (in)->proxy_opts; \ - (out)->custom_headers = (in)->custom_headers; \ - (out)->follow_redirects = (in)->follow_redirects; \ - } - -GIT_INLINE(int) connect_opts_from_fetch_opts( - git_remote_connect_options *out, - git_remote *remote, - const git_fetch_options *fetch_opts) -{ - git_remote_connect_options tmp = GIT_REMOTE_CONNECT_OPTIONS_INIT; - copy_opts(&tmp, fetch_opts); - return git_remote_connect_options_normalize(out, remote->repo, &tmp); -} - -static int connect_or_reset_options( - git_remote *remote, - int direction, - git_remote_connect_options *opts) -{ - if (!git_remote_connected(remote)) { - return git_remote_connect_ext(remote, direction, opts); - } else { - return remote->transport->set_connect_opts(remote->transport, opts); - } -} - -/* Download from an already connected remote. */ -static int git_remote__download( - git_remote *remote, - const git_strarray *refspecs, - const git_fetch_options *opts) -{ - git_vector *to_active, specs = GIT_VECTOR_INIT, refs = GIT_VECTOR_INIT; - size_t i; - int error; - - if (ls_to_vector(&refs, remote) < 0) - return -1; - - if ((error = git_vector_init(&specs, 0, NULL)) < 0) - goto on_error; - - remote->passed_refspecs = 0; - if (!refspecs || !refspecs->count) { - to_active = &remote->refspecs; - } else { - for (i = 0; i < refspecs->count; i++) { - if ((error = add_refspec_to(&specs, refspecs->strings[i], true)) < 0) - goto on_error; - } - - to_active = &specs; - remote->passed_refspecs = 1; - } - - free_refspecs(&remote->passive_refspecs); - if ((error = dwim_refspecs(&remote->passive_refspecs, &remote->refspecs, &refs)) < 0) - goto on_error; - - free_refspecs(&remote->active_refspecs); - error = dwim_refspecs(&remote->active_refspecs, to_active, &refs); - - git_vector_free(&refs); - free_refspecs(&specs); - git_vector_free(&specs); - - if (error < 0) - goto on_error; - - if (remote->push) { - git_push_free(remote->push); - remote->push = NULL; - } - - if ((error = git_fetch_negotiate(remote, opts)) < 0) - goto on_error; - - error = git_fetch_download_pack(remote); - -on_error: - git_vector_free(&refs); - free_refspecs(&specs); - git_vector_free(&specs); - return error; -} - -int git_remote_download( - git_remote *remote, - const git_strarray *refspecs, - const git_fetch_options *opts) -{ - git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; - int error; - - GIT_ASSERT_ARG(remote); - - if (!remote->repo) { - git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); - return -1; - } - - if (connect_opts_from_fetch_opts(&connect_opts, remote, opts) < 0) - return -1; - - if ((error = connect_or_reset_options(remote, GIT_DIRECTION_FETCH, &connect_opts)) < 0) - return error; - - return git_remote__download(remote, refspecs, opts); -} - -int git_remote_fetch( - git_remote *remote, - const git_strarray *refspecs, - const git_fetch_options *opts, - const char *reflog_message) -{ - int error, update_fetchhead = 1; - git_remote_autotag_option_t tagopt = remote->download_tags; - bool prune = false; - git_str reflog_msg_buf = GIT_STR_INIT; - git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; - - GIT_ASSERT_ARG(remote); - - if (!remote->repo) { - git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); - return -1; - } - - if (connect_opts_from_fetch_opts(&connect_opts, remote, opts) < 0) - return -1; - - if ((error = connect_or_reset_options(remote, GIT_DIRECTION_FETCH, &connect_opts)) < 0) - return error; - - if (opts) { - update_fetchhead = opts->update_fetchhead; - tagopt = opts->download_tags; - } - - /* Connect and download everything */ - error = git_remote__download(remote, refspecs, opts); - - /* We don't need to be connected anymore */ - git_remote_disconnect(remote); - - /* If the download failed, return the error */ - if (error != 0) - goto done; - - /* Default reflog message */ - if (reflog_message) - git_str_sets(&reflog_msg_buf, reflog_message); - else { - git_str_printf(&reflog_msg_buf, "fetch %s", - remote->name ? remote->name : remote->url); - } - - /* Create "remote/foo" branches for all remote branches */ - error = git_remote_update_tips(remote, &connect_opts.callbacks, update_fetchhead, tagopt, git_str_cstr(&reflog_msg_buf)); - git_str_dispose(&reflog_msg_buf); - if (error < 0) - goto done; - - if (opts && opts->prune == GIT_FETCH_PRUNE) - prune = true; - else if (opts && opts->prune == GIT_FETCH_PRUNE_UNSPECIFIED && remote->prune_refs) - prune = true; - else if (opts && opts->prune == GIT_FETCH_NO_PRUNE) - prune = false; - else - prune = remote->prune_refs; - - if (prune) - error = git_remote_prune(remote, &connect_opts.callbacks); - -done: - git_remote_connect_options_dispose(&connect_opts); - return error; -} - -static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src) -{ - unsigned int i; - git_remote_head *remote_ref; - - GIT_ASSERT_ARG(update_heads); - GIT_ASSERT_ARG(fetchspec_src); - - *out = NULL; - - git_vector_foreach(update_heads, i, remote_ref) { - if (strcmp(remote_ref->name, fetchspec_src) == 0) { - *out = remote_ref; - break; - } - } - - return 0; -} - -static int ref_to_update(int *update, git_str *remote_name, git_remote *remote, git_refspec *spec, const char *ref_name) -{ - int error = 0; - git_repository *repo; - git_str upstream_remote = GIT_STR_INIT; - git_str upstream_name = GIT_STR_INIT; - - repo = git_remote_owner(remote); - - if ((!git_reference__is_branch(ref_name)) || - !git_remote_name(remote) || - (error = git_branch__upstream_remote(&upstream_remote, repo, ref_name) < 0) || - git__strcmp(git_remote_name(remote), git_str_cstr(&upstream_remote)) || - (error = git_branch__upstream_name(&upstream_name, repo, ref_name)) < 0 || - !git_refspec_dst_matches(spec, git_str_cstr(&upstream_name)) || - (error = git_refspec__rtransform(remote_name, spec, upstream_name.ptr)) < 0) { - /* Not an error if there is no upstream */ - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - *update = 0; - } else { - *update = 1; - } - - git_str_dispose(&upstream_remote); - git_str_dispose(&upstream_name); - return error; -} - -static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_refspec *spec, git_vector *update_heads, git_reference *ref) -{ - git_reference *resolved_ref = NULL; - git_str remote_name = GIT_STR_INIT; - git_config *config = NULL; - const char *ref_name; - int error = 0, update; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(spec); - GIT_ASSERT_ARG(ref); - - *out = NULL; - - error = git_reference_resolve(&resolved_ref, ref); - - /* If we're in an unborn branch, let's pretend nothing happened */ - if (error == GIT_ENOTFOUND && git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { - ref_name = git_reference_symbolic_target(ref); - error = 0; - } else { - ref_name = git_reference_name(resolved_ref); - } - - /* - * The ref name may be unresolvable - perhaps it's pointing to - * something invalid. In this case, there is no remote head for - * this ref. - */ - if (!ref_name) { - error = 0; - goto cleanup; - } - - if ((error = ref_to_update(&update, &remote_name, remote, spec, ref_name)) < 0) - goto cleanup; - - if (update) - error = remote_head_for_fetchspec_src(out, update_heads, git_str_cstr(&remote_name)); - -cleanup: - git_str_dispose(&remote_name); - git_reference_free(resolved_ref); - git_config_free(config); - return error; -} - -static int git_remote_write_fetchhead(git_remote *remote, git_refspec *spec, git_vector *update_heads) -{ - git_reference *head_ref = NULL; - git_fetchhead_ref *fetchhead_ref; - git_remote_head *remote_ref, *merge_remote_ref; - git_vector fetchhead_refs; - bool include_all_fetchheads; - unsigned int i = 0; - int error = 0; - - GIT_ASSERT_ARG(remote); - - /* no heads, nothing to do */ - if (update_heads->length == 0) - return 0; - - if (git_vector_init(&fetchhead_refs, update_heads->length, git_fetchhead_ref_cmp) < 0) - return -1; - - /* Iff refspec is * (but not subdir slash star), include tags */ - include_all_fetchheads = (strcmp(GIT_REFS_HEADS_DIR "*", git_refspec_src(spec)) == 0); - - /* Determine what to merge: if refspec was a wildcard, just use HEAD */ - if (git_refspec_is_wildcard(spec)) { - if ((error = git_reference_lookup(&head_ref, remote->repo, GIT_HEAD_FILE)) < 0 || - (error = remote_head_for_ref(&merge_remote_ref, remote, spec, update_heads, head_ref)) < 0) - goto cleanup; - } else { - /* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */ - if ((error = remote_head_for_fetchspec_src(&merge_remote_ref, update_heads, git_refspec_src(spec))) < 0) - goto cleanup; - } - - /* Create the FETCH_HEAD file */ - git_vector_foreach(update_heads, i, remote_ref) { - int merge_this_fetchhead = (merge_remote_ref == remote_ref); - - if (!include_all_fetchheads && - !git_refspec_src_matches(spec, remote_ref->name) && - !merge_this_fetchhead) - continue; - - if (git_fetchhead_ref_create(&fetchhead_ref, - &remote_ref->oid, - merge_this_fetchhead, - remote_ref->name, - git_remote_url(remote)) < 0) - goto cleanup; - - if (git_vector_insert(&fetchhead_refs, fetchhead_ref) < 0) - goto cleanup; - } - - git_fetchhead_write(remote->repo, &fetchhead_refs); - -cleanup: - for (i = 0; i < fetchhead_refs.length; ++i) - git_fetchhead_ref_free(fetchhead_refs.contents[i]); - - git_vector_free(&fetchhead_refs); - git_reference_free(head_ref); - - return error; -} - -/** - * Generate a list of candidates for pruning by getting a list of - * references which match the rhs of an active refspec. - */ -static int prune_candidates(git_vector *candidates, git_remote *remote) -{ - git_strarray arr = { 0 }; - size_t i; - int error; - - if ((error = git_reference_list(&arr, remote->repo)) < 0) - return error; - - for (i = 0; i < arr.count; i++) { - const char *refname = arr.strings[i]; - char *refname_dup; - - if (!git_remote__matching_dst_refspec(remote, refname)) - continue; - - refname_dup = git__strdup(refname); - GIT_ERROR_CHECK_ALLOC(refname_dup); - - if ((error = git_vector_insert(candidates, refname_dup)) < 0) - goto out; - } - -out: - git_strarray_dispose(&arr); - return error; -} - -static int find_head(const void *_a, const void *_b) -{ - git_remote_head *a = (git_remote_head *) _a; - git_remote_head *b = (git_remote_head *) _b; - - return strcmp(a->name, b->name); -} - -int git_remote_prune(git_remote *remote, const git_remote_callbacks *callbacks) -{ - size_t i, j; - git_vector remote_refs = GIT_VECTOR_INIT; - git_vector candidates = GIT_VECTOR_INIT; - const git_refspec *spec; - const char *refname; - int error; - git_oid zero_id = {{ 0 }}; - - if (callbacks) - GIT_ERROR_CHECK_VERSION(callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); - - if ((error = ls_to_vector(&remote_refs, remote)) < 0) - goto cleanup; - - git_vector_set_cmp(&remote_refs, find_head); - - if ((error = prune_candidates(&candidates, remote)) < 0) - goto cleanup; - - /* - * Remove those entries from the candidate list for which we - * can find a remote reference in at least one refspec. - */ - git_vector_foreach(&candidates, i, refname) { - git_vector_foreach(&remote->active_refspecs, j, spec) { - git_str buf = GIT_STR_INIT; - size_t pos; - char *src_name; - git_remote_head key = {0}; - - if (!git_refspec_dst_matches(spec, refname)) - continue; - - if ((error = git_refspec__rtransform(&buf, spec, refname)) < 0) - goto cleanup; - - key.name = (char *) git_str_cstr(&buf); - error = git_vector_bsearch(&pos, &remote_refs, &key); - git_str_dispose(&buf); - - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - if (error == GIT_ENOTFOUND) - continue; - - /* If we did find a source, remove it from the candidates. */ - if ((error = git_vector_set((void **) &src_name, &candidates, i, NULL)) < 0) - goto cleanup; - - git__free(src_name); - break; - } - } - - /* - * For those candidates still left in the list, we need to - * remove them. We do not remove symrefs, as those are for - * stuff like origin/HEAD which will never match, but we do - * not want to remove them. - */ - git_vector_foreach(&candidates, i, refname) { - git_reference *ref; - git_oid id; - - if (refname == NULL) - continue; - - error = git_reference_lookup(&ref, remote->repo, refname); - /* as we want it gone, let's not consider this an error */ - if (error == GIT_ENOTFOUND) - continue; - - if (error < 0) - goto cleanup; - - if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { - git_reference_free(ref); - continue; - } - - git_oid_cpy(&id, git_reference_target(ref)); - error = git_reference_delete(ref); - git_reference_free(ref); - if (error < 0) - goto cleanup; - - if (callbacks && callbacks->update_tips) - error = callbacks->update_tips(refname, &id, &zero_id, callbacks->payload); - - if (error < 0) - goto cleanup; - } - -cleanup: - git_vector_free(&remote_refs); - git_vector_free_deep(&candidates); - return error; -} - -static int update_ref( - const git_remote *remote, - const char *ref_name, - git_oid *id, - const char *msg, - const git_remote_callbacks *callbacks) -{ - git_reference *ref; - git_oid old_id; - int error; - - error = git_reference_name_to_id(&old_id, remote->repo, ref_name); - - if (error < 0 && error != GIT_ENOTFOUND) - return error; - else if (error == 0 && git_oid_equal(&old_id, id)) - return 0; - - /* If we did find a current reference, make sure we haven't lost a race */ - if (error) - error = git_reference_create(&ref, remote->repo, ref_name, id, true, msg); - else - error = git_reference_create_matching(&ref, remote->repo, ref_name, id, true, &old_id, msg); - - git_reference_free(ref); - - if (error < 0) - return error; - - if (callbacks && callbacks->update_tips && - (error = callbacks->update_tips(ref_name, &old_id, id, callbacks->payload)) < 0) - return error; - - return 0; -} - -static int update_one_tip( - git_vector *update_heads, - git_remote *remote, - git_refspec *spec, - git_remote_head *head, - git_refspec *tagspec, - git_remote_autotag_option_t tagopt, - const char *log_message, - const git_remote_callbacks *callbacks) -{ - git_odb *odb; - git_str refname = GIT_STR_INIT; - git_reference *ref = NULL; - bool autotag = false; - git_oid old; - int valid; - int error; - - if ((error = git_repository_odb__weakptr(&odb, remote->repo)) < 0) - goto done; - - /* Ignore malformed ref names (which also saves us from tag^{} */ - if ((error = git_reference_name_is_valid(&valid, head->name)) < 0) - goto done; - - if (!valid) - goto done; - - /* If we have a tag, see if the auto-follow rules say to update it */ - if (git_refspec_src_matches(tagspec, head->name)) { - if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_AUTO) - autotag = true; - - if (tagopt != GIT_REMOTE_DOWNLOAD_TAGS_NONE) { - if (git_str_puts(&refname, head->name) < 0) - goto done; - } - } - - /* If we didn't want to auto-follow the tag, check if the refspec matches */ - if (!autotag && git_refspec_src_matches(spec, head->name)) { - if (spec->dst) { - if ((error = git_refspec__transform(&refname, spec, head->name)) < 0) - goto done; - } else { - /* - * no rhs means store it in FETCH_HEAD, even if we don't - * update anything else. - */ - error = git_vector_insert(update_heads, head); - goto done; - } - } - - /* If we still don't have a refname, we don't want it */ - if (git_str_len(&refname) == 0) - goto done; - - /* In autotag mode, only create tags for objects already in db */ - if (autotag && !git_odb_exists(odb, &head->oid)) - goto done; - - if (!autotag && (error = git_vector_insert(update_heads, head)) < 0) - goto done; - - error = git_reference_name_to_id(&old, remote->repo, refname.ptr); - - if (error < 0 && error != GIT_ENOTFOUND) - goto done; - - if (!(error || error == GIT_ENOTFOUND) && - !spec->force && - !git_graph_descendant_of(remote->repo, &head->oid, &old)) { - error = 0; - goto done; - } - - if (error == GIT_ENOTFOUND) { - memset(&old, 0, GIT_OID_RAWSZ); - error = 0; - - if (autotag && (error = git_vector_insert(update_heads, head)) < 0) - goto done; - } - - if (!git_oid__cmp(&old, &head->oid)) - goto done; - - /* In autotag mode, don't overwrite any locally-existing tags */ - error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag, - log_message); - - if (error < 0) { - if (error == GIT_EEXISTS) - error = 0; - - goto done; - } - - if (callbacks && callbacks->update_tips != NULL && - (error = callbacks->update_tips(refname.ptr, &old, &head->oid, callbacks->payload)) < 0) - git_error_set_after_callback_function(error, "git_remote_fetch"); - -done: - git_reference_free(ref); - git_str_dispose(&refname); - return error; -} - -static int update_tips_for_spec( - git_remote *remote, - const git_remote_callbacks *callbacks, - int update_fetchhead, - git_remote_autotag_option_t tagopt, - git_refspec *spec, - git_vector *refs, - const char *log_message) -{ - git_refspec tagspec; - git_remote_head *head, oid_head; - git_vector update_heads; - int error = 0; - size_t i; - - GIT_ASSERT_ARG(remote); - - if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) - return -1; - - /* Make a copy of the transport's refs */ - if (git_vector_init(&update_heads, 16, NULL) < 0) - return -1; - - /* Update tips based on the remote heads */ - git_vector_foreach(refs, i, head) { - if (update_one_tip(&update_heads, remote, spec, head, &tagspec, tagopt, log_message, callbacks) < 0) - goto on_error; - } - - /* Handle specified oid sources */ - if (git_oid__is_hexstr(spec->src)) { - git_oid id; - - if ((error = git_oid_fromstr(&id, spec->src)) < 0 || - (error = update_ref(remote, spec->dst, &id, log_message, callbacks)) < 0) - goto on_error; - - git_oid_cpy(&oid_head.oid, &id); - oid_head.name = spec->src; - - if ((error = git_vector_insert(&update_heads, &oid_head)) < 0) - goto on_error; - } - - if (update_fetchhead && - (error = git_remote_write_fetchhead(remote, spec, &update_heads)) < 0) - goto on_error; - - git_refspec__dispose(&tagspec); - git_vector_free(&update_heads); - return 0; - -on_error: - git_refspec__dispose(&tagspec); - git_vector_free(&update_heads); - return -1; - -} - -/** - * Iteration over the three vectors, with a pause whenever we find a match - * - * On each stop, we store the iteration stat in the inout i,j,k - * parameters, and return the currently matching passive refspec as - * well as the head which we matched. - */ -static int next_head(const git_remote *remote, git_vector *refs, - git_refspec **out_spec, git_remote_head **out_head, - size_t *out_i, size_t *out_j, size_t *out_k) -{ - const git_vector *active, *passive; - git_remote_head *head; - git_refspec *spec, *passive_spec; - size_t i, j, k; - int valid; - - active = &remote->active_refspecs; - passive = &remote->passive_refspecs; - - i = *out_i; - j = *out_j; - k = *out_k; - - for (; i < refs->length; i++) { - head = git_vector_get(refs, i); - - if (git_reference_name_is_valid(&valid, head->name) < 0) - return -1; - - if (!valid) - continue; - - for (; j < active->length; j++) { - spec = git_vector_get(active, j); - - if (!git_refspec_src_matches(spec, head->name)) - continue; - - for (; k < passive->length; k++) { - passive_spec = git_vector_get(passive, k); - - if (!git_refspec_src_matches(passive_spec, head->name)) - continue; - - *out_spec = passive_spec; - *out_head = head; - *out_i = i; - *out_j = j; - *out_k = k + 1; - return 0; - - } - k = 0; - } - j = 0; - } - - return GIT_ITEROVER; -} - -static int opportunistic_updates( - const git_remote *remote, - const git_remote_callbacks *callbacks, - git_vector *refs, - const char *msg) -{ - size_t i, j, k; - git_refspec *spec; - git_remote_head *head; - git_str refname = GIT_STR_INIT; - int error = 0; - - i = j = k = 0; - - /* Handle refspecs matching remote heads */ - while ((error = next_head(remote, refs, &spec, &head, &i, &j, &k)) == 0) { - /* - * If we got here, there is a refspec which was used - * for fetching which matches the source of one of the - * passive refspecs, so we should update that - * remote-tracking branch, but not add it to - * FETCH_HEAD - */ - - git_str_clear(&refname); - if ((error = git_refspec__transform(&refname, spec, head->name)) < 0 || - (error = update_ref(remote, refname.ptr, &head->oid, msg, callbacks)) < 0) - goto cleanup; - } - - if (error != GIT_ITEROVER) - goto cleanup; - - error = 0; - -cleanup: - git_str_dispose(&refname); - return error; -} - -static int truncate_fetch_head(const char *gitdir) -{ - git_str path = GIT_STR_INIT; - int error; - - if ((error = git_str_joinpath(&path, gitdir, GIT_FETCH_HEAD_FILE)) < 0) - return error; - - error = git_futils_truncate(path.ptr, GIT_REFS_FILE_MODE); - git_str_dispose(&path); - - return error; -} - -int git_remote_update_tips( - git_remote *remote, - const git_remote_callbacks *callbacks, - int update_fetchhead, - git_remote_autotag_option_t download_tags, - const char *reflog_message) -{ - git_refspec *spec, tagspec; - git_vector refs = GIT_VECTOR_INIT; - git_remote_autotag_option_t tagopt; - int error; - size_t i; - - /* push has its own logic hidden away in the push object */ - if (remote->push) { - return git_push_update_tips(remote->push, callbacks); - } - - if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) - return -1; - - - if ((error = ls_to_vector(&refs, remote)) < 0) - goto out; - - if (download_tags == GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED) - tagopt = remote->download_tags; - else - tagopt = download_tags; - - if ((error = truncate_fetch_head(git_repository_path(remote->repo))) < 0) - goto out; - - if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { - if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, &tagspec, &refs, reflog_message)) < 0) - goto out; - } - - git_vector_foreach(&remote->active_refspecs, i, spec) { - if (spec->push) - continue; - - if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, spec, &refs, reflog_message)) < 0) - goto out; - } - - /* Only try to do opportunistic updates if the refspec lists differ. */ - if (remote->passed_refspecs) - error = opportunistic_updates(remote, callbacks, &refs, reflog_message); - -out: - git_vector_free(&refs); - git_refspec__dispose(&tagspec); - return error; -} - -int git_remote_connected(const git_remote *remote) -{ - GIT_ASSERT_ARG(remote); - - if (!remote->transport || !remote->transport->is_connected) - return 0; - - /* Ask the transport if it's connected. */ - return remote->transport->is_connected(remote->transport); -} - -int git_remote_stop(git_remote *remote) -{ - GIT_ASSERT_ARG(remote); - - if (remote->transport && remote->transport->cancel) - remote->transport->cancel(remote->transport); - - return 0; -} - -int git_remote_disconnect(git_remote *remote) -{ - GIT_ASSERT_ARG(remote); - - if (git_remote_connected(remote)) - remote->transport->close(remote->transport); - - return 0; -} - -static void free_heads(git_vector *heads) -{ - git_remote_head *head; - size_t i; - - git_vector_foreach(heads, i, head) { - git__free(head->name); - git__free(head); - } -} - -void git_remote_free(git_remote *remote) -{ - if (remote == NULL) - return; - - if (remote->transport != NULL) { - git_remote_disconnect(remote); - - remote->transport->free(remote->transport); - remote->transport = NULL; - } - - git_vector_free(&remote->refs); - - free_refspecs(&remote->refspecs); - git_vector_free(&remote->refspecs); - - free_refspecs(&remote->active_refspecs); - git_vector_free(&remote->active_refspecs); - - free_refspecs(&remote->passive_refspecs); - git_vector_free(&remote->passive_refspecs); - - free_heads(&remote->local_heads); - git_vector_free(&remote->local_heads); - - git_push_free(remote->push); - git__free(remote->url); - git__free(remote->pushurl); - git__free(remote->name); - git__free(remote); -} - -static int remote_list_cb(const git_config_entry *entry, void *payload) -{ - git_vector *list = payload; - const char *name = entry->name + strlen("remote."); - size_t namelen = strlen(name); - char *remote_name; - - /* we know name matches "remote..(push)?url" */ - - if (!strcmp(&name[namelen - 4], ".url")) - remote_name = git__strndup(name, namelen - 4); /* strip ".url" */ - else - remote_name = git__strndup(name, namelen - 8); /* strip ".pushurl" */ - GIT_ERROR_CHECK_ALLOC(remote_name); - - return git_vector_insert(list, remote_name); -} - -int git_remote_list(git_strarray *remotes_list, git_repository *repo) -{ - int error; - git_config *cfg; - git_vector list = GIT_VECTOR_INIT; - - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - return error; - - if ((error = git_vector_init(&list, 4, git__strcmp_cb)) < 0) - return error; - - error = git_config_foreach_match( - cfg, "^remote\\..*\\.(push)?url$", remote_list_cb, &list); - - if (error < 0) { - git_vector_free_deep(&list); - return error; - } - - git_vector_uniq(&list, git__free); - - remotes_list->strings = - (char **)git_vector_detach(&remotes_list->count, NULL, &list); - - return 0; -} - -const git_indexer_progress *git_remote_stats(git_remote *remote) -{ - GIT_ASSERT_ARG_WITH_RETVAL(remote, NULL); - return &remote->stats; -} - -git_remote_autotag_option_t git_remote_autotag(const git_remote *remote) -{ - return remote->download_tags; -} - -int git_remote_set_autotag(git_repository *repo, const char *remote, git_remote_autotag_option_t value) -{ - git_str var = GIT_STR_INIT; - git_config *config; - int error; - - GIT_ASSERT_ARG(repo && remote); - - if ((error = ensure_remote_name_is_valid(remote)) < 0) - return error; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - return error; - - if ((error = git_str_printf(&var, CONFIG_TAGOPT_FMT, remote))) - return error; - - switch (value) { - case GIT_REMOTE_DOWNLOAD_TAGS_NONE: - error = git_config_set_string(config, var.ptr, "--no-tags"); - break; - case GIT_REMOTE_DOWNLOAD_TAGS_ALL: - error = git_config_set_string(config, var.ptr, "--tags"); - break; - case GIT_REMOTE_DOWNLOAD_TAGS_AUTO: - error = git_config_delete_entry(config, var.ptr); - if (error == GIT_ENOTFOUND) - error = 0; - break; - default: - git_error_set(GIT_ERROR_INVALID, "invalid value for the tagopt setting"); - error = -1; - } - - git_str_dispose(&var); - return error; -} - -int git_remote_prune_refs(const git_remote *remote) -{ - return remote->prune_refs; -} - -static int rename_remote_config_section( - git_repository *repo, - const char *old_name, - const char *new_name) -{ - git_str old_section_name = GIT_STR_INIT, - new_section_name = GIT_STR_INIT; - int error = -1; - - if (git_str_printf(&old_section_name, "remote.%s", old_name) < 0) - goto cleanup; - - if (new_name && - (git_str_printf(&new_section_name, "remote.%s", new_name) < 0)) - goto cleanup; - - error = git_config_rename_section( - repo, - git_str_cstr(&old_section_name), - new_name ? git_str_cstr(&new_section_name) : NULL); - -cleanup: - git_str_dispose(&old_section_name); - git_str_dispose(&new_section_name); - - return error; -} - -struct update_data { - git_config *config; - const char *old_remote_name; - const char *new_remote_name; -}; - -static int update_config_entries_cb( - const git_config_entry *entry, - void *payload) -{ - struct update_data *data = (struct update_data *)payload; - - if (strcmp(entry->value, data->old_remote_name)) - return 0; - - return git_config_set_string( - data->config, entry->name, data->new_remote_name); -} - -static int update_branch_remote_config_entry( - git_repository *repo, - const char *old_name, - const char *new_name) -{ - int error; - struct update_data data = { NULL }; - - if ((error = git_repository_config__weakptr(&data.config, repo)) < 0) - return error; - - data.old_remote_name = old_name; - data.new_remote_name = new_name; - - return git_config_foreach_match( - data.config, "branch\\..+\\.remote", update_config_entries_cb, &data); -} - -static int rename_one_remote_reference( - git_reference *reference_in, - const char *old_remote_name, - const char *new_remote_name) -{ - int error; - git_reference *ref = NULL, *dummy = NULL; - git_str namespace = GIT_STR_INIT, old_namespace = GIT_STR_INIT; - git_str new_name = GIT_STR_INIT; - git_str log_message = GIT_STR_INIT; - size_t pfx_len; - const char *target; - - if ((error = git_str_printf(&namespace, GIT_REFS_REMOTES_DIR "%s/", new_remote_name)) < 0) - return error; - - pfx_len = strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name) + 1; - git_str_puts(&new_name, namespace.ptr); - if ((error = git_str_puts(&new_name, git_reference_name(reference_in) + pfx_len)) < 0) - goto cleanup; - - if ((error = git_str_printf(&log_message, - "renamed remote %s to %s", - old_remote_name, new_remote_name)) < 0) - goto cleanup; - - if ((error = git_reference_rename(&ref, reference_in, git_str_cstr(&new_name), 1, - git_str_cstr(&log_message))) < 0) - goto cleanup; - - if (git_reference_type(ref) != GIT_REFERENCE_SYMBOLIC) - goto cleanup; - - /* Handle refs like origin/HEAD -> origin/master */ - target = git_reference_symbolic_target(ref); - if ((error = git_str_printf(&old_namespace, GIT_REFS_REMOTES_DIR "%s/", old_remote_name)) < 0) - goto cleanup; - - if (git__prefixcmp(target, old_namespace.ptr)) - goto cleanup; - - git_str_clear(&new_name); - git_str_puts(&new_name, namespace.ptr); - if ((error = git_str_puts(&new_name, target + pfx_len)) < 0) - goto cleanup; - - error = git_reference_symbolic_set_target(&dummy, ref, git_str_cstr(&new_name), - git_str_cstr(&log_message)); - - git_reference_free(dummy); - -cleanup: - git_reference_free(reference_in); - git_reference_free(ref); - git_str_dispose(&namespace); - git_str_dispose(&old_namespace); - git_str_dispose(&new_name); - git_str_dispose(&log_message); - return error; -} - -static int rename_remote_references( - git_repository *repo, - const char *old_name, - const char *new_name) -{ - int error; - git_str buf = GIT_STR_INIT; - git_reference *ref; - git_reference_iterator *iter; - - if ((error = git_str_printf(&buf, GIT_REFS_REMOTES_DIR "%s/*", old_name)) < 0) - return error; - - error = git_reference_iterator_glob_new(&iter, repo, git_str_cstr(&buf)); - git_str_dispose(&buf); - - if (error < 0) - return error; - - while ((error = git_reference_next(&ref, iter)) == 0) { - if ((error = rename_one_remote_reference(ref, old_name, new_name)) < 0) - break; - } - - git_reference_iterator_free(iter); - - return (error == GIT_ITEROVER) ? 0 : error; -} - -static int rename_fetch_refspecs(git_vector *problems, git_remote *remote, const char *new_name) -{ - git_config *config; - git_str base = GIT_STR_INIT, var = GIT_STR_INIT, val = GIT_STR_INIT; - const git_refspec *spec; - size_t i; - int error = 0; - - if ((error = git_repository_config__weakptr(&config, remote->repo)) < 0) - return error; - - if ((error = git_vector_init(problems, 1, NULL)) < 0) - return error; - - if ((error = default_fetchspec_for_name(&base, remote->name)) < 0) - return error; - - git_vector_foreach(&remote->refspecs, i, spec) { - if (spec->push) - continue; - - /* Does the dst part of the refspec follow the expected format? */ - if (strcmp(git_str_cstr(&base), spec->string)) { - char *dup; - - dup = git__strdup(spec->string); - GIT_ERROR_CHECK_ALLOC(dup); - - if ((error = git_vector_insert(problems, dup)) < 0) - break; - - continue; - } - - /* If we do want to move it to the new section */ - - git_str_clear(&val); - git_str_clear(&var); - - if (default_fetchspec_for_name(&val, new_name) < 0 || - git_str_printf(&var, "remote.%s.fetch", new_name) < 0) - { - error = -1; - break; - } - - if ((error = git_config_set_string( - config, git_str_cstr(&var), git_str_cstr(&val))) < 0) - break; - } - - git_str_dispose(&base); - git_str_dispose(&var); - git_str_dispose(&val); - - if (error < 0) { - char *str; - git_vector_foreach(problems, i, str) - git__free(str); - - git_vector_free(problems); - } - - return error; -} - -int git_remote_rename(git_strarray *out, git_repository *repo, const char *name, const char *new_name) -{ - int error; - git_vector problem_refspecs = GIT_VECTOR_INIT; - git_remote *remote = NULL; - - GIT_ASSERT_ARG(out && repo && name && new_name); - - if ((error = git_remote_lookup(&remote, repo, name)) < 0) - return error; - - if ((error = ensure_remote_name_is_valid(new_name)) < 0) - goto cleanup; - - if ((error = ensure_remote_doesnot_exist(repo, new_name)) < 0) - goto cleanup; - - if ((error = rename_remote_config_section(repo, name, new_name)) < 0) - goto cleanup; - - if ((error = update_branch_remote_config_entry(repo, name, new_name)) < 0) - goto cleanup; - - if ((error = rename_remote_references(repo, name, new_name)) < 0) - goto cleanup; - - if ((error = rename_fetch_refspecs(&problem_refspecs, remote, new_name)) < 0) - goto cleanup; - - out->count = problem_refspecs.length; - out->strings = (char **) problem_refspecs.contents; - -cleanup: - if (error < 0) - git_vector_free(&problem_refspecs); - - git_remote_free(remote); - return error; -} - -int git_remote_name_is_valid(int *valid, const char *remote_name) -{ - git_str buf = GIT_STR_INIT; - git_refspec refspec = {0}; - int error; - - GIT_ASSERT(valid); - - *valid = 0; - - if (!remote_name || *remote_name == '\0') - return 0; - - if ((error = git_str_printf(&buf, "refs/heads/test:refs/remotes/%s/test", remote_name)) < 0) - goto done; - - error = git_refspec__parse(&refspec, git_str_cstr(&buf), true); - - if (!error) - *valid = 1; - else if (error == GIT_EINVALIDSPEC) - error = 0; - -done: - git_str_dispose(&buf); - git_refspec__dispose(&refspec); - - return error; -} - -git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname) -{ - git_refspec *spec; - size_t i; - - git_vector_foreach(&remote->active_refspecs, i, spec) { - if (spec->push) - continue; - - if (git_refspec_src_matches(spec, refname)) - return spec; - } - - return NULL; -} - -git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname) -{ - git_refspec *spec; - size_t i; - - git_vector_foreach(&remote->active_refspecs, i, spec) { - if (spec->push) - continue; - - if (git_refspec_dst_matches(spec, refname)) - return spec; - } - - return NULL; -} - -int git_remote_add_fetch(git_repository *repo, const char *remote, const char *refspec) -{ - return write_add_refspec(repo, remote, refspec, true); -} - -int git_remote_add_push(git_repository *repo, const char *remote, const char *refspec) -{ - return write_add_refspec(repo, remote, refspec, false); -} - -static int copy_refspecs(git_strarray *array, const git_remote *remote, unsigned int push) -{ - size_t i; - git_vector refspecs; - git_refspec *spec; - char *dup; - - if (git_vector_init(&refspecs, remote->refspecs.length, NULL) < 0) - return -1; - - git_vector_foreach(&remote->refspecs, i, spec) { - if (spec->push != push) - continue; - - if ((dup = git__strdup(spec->string)) == NULL) - goto on_error; - - if (git_vector_insert(&refspecs, dup) < 0) { - git__free(dup); - goto on_error; - } - } - - array->strings = (char **)refspecs.contents; - array->count = refspecs.length; - - return 0; - -on_error: - git_vector_free_deep(&refspecs); - - return -1; -} - -int git_remote_get_fetch_refspecs(git_strarray *array, const git_remote *remote) -{ - return copy_refspecs(array, remote, false); -} - -int git_remote_get_push_refspecs(git_strarray *array, const git_remote *remote) -{ - return copy_refspecs(array, remote, true); -} - -size_t git_remote_refspec_count(const git_remote *remote) -{ - return remote->refspecs.length; -} - -const git_refspec *git_remote_get_refspec(const git_remote *remote, size_t n) -{ - return git_vector_get(&remote->refspecs, n); -} - -int git_remote_init_callbacks(git_remote_callbacks *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_remote_callbacks, GIT_REMOTE_CALLBACKS_INIT); - return 0; -} - -/* asserts a branch..remote format */ -static const char *name_offset(size_t *len_out, const char *name) -{ - size_t prefix_len; - const char *dot; - - prefix_len = strlen("remote."); - dot = strchr(name + prefix_len, '.'); - - GIT_ASSERT_ARG_WITH_RETVAL(dot, NULL); - - *len_out = dot - name - prefix_len; - return name + prefix_len; -} - -static int remove_branch_config_related_entries( - git_repository *repo, - const char *remote_name) -{ - int error; - git_config *config; - git_config_entry *entry; - git_config_iterator *iter; - git_str buf = GIT_STR_INIT; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - return error; - - if ((error = git_config_iterator_glob_new(&iter, config, "branch\\..+\\.remote")) < 0) - return error; - - /* find any branches with us as upstream and remove that config */ - while ((error = git_config_next(&entry, iter)) == 0) { - const char *branch; - size_t branch_len; - - if (strcmp(remote_name, entry->value)) - continue; - - if ((branch = name_offset(&branch_len, entry->name)) == NULL) { - error = -1; - break; - } - - git_str_clear(&buf); - if ((error = git_str_printf(&buf, "branch.%.*s.merge", (int)branch_len, branch)) < 0) - break; - - if ((error = git_config_delete_entry(config, git_str_cstr(&buf))) < 0) { - if (error != GIT_ENOTFOUND) - break; - git_error_clear(); - } - - git_str_clear(&buf); - if ((error = git_str_printf(&buf, "branch.%.*s.remote", (int)branch_len, branch)) < 0) - break; - - if ((error = git_config_delete_entry(config, git_str_cstr(&buf))) < 0) { - if (error != GIT_ENOTFOUND) - break; - git_error_clear(); - } - } - - if (error == GIT_ITEROVER) - error = 0; - - git_str_dispose(&buf); - git_config_iterator_free(iter); - return error; -} - -static int remove_refs(git_repository *repo, const git_refspec *spec) -{ - git_reference_iterator *iter = NULL; - git_vector refs; - const char *name; - char *dup; - int error; - size_t i; - - if ((error = git_vector_init(&refs, 8, NULL)) < 0) - return error; - - if ((error = git_reference_iterator_new(&iter, repo)) < 0) - goto cleanup; - - while ((error = git_reference_next_name(&name, iter)) == 0) { - if (!git_refspec_dst_matches(spec, name)) - continue; - - dup = git__strdup(name); - if (!dup) { - error = -1; - goto cleanup; - } - - if ((error = git_vector_insert(&refs, dup)) < 0) - goto cleanup; - } - if (error == GIT_ITEROVER) - error = 0; - if (error < 0) - goto cleanup; - - git_vector_foreach(&refs, i, name) { - if ((error = git_reference_remove(repo, name)) < 0) - break; - } - -cleanup: - git_reference_iterator_free(iter); - git_vector_foreach(&refs, i, dup) { - git__free(dup); - } - git_vector_free(&refs); - return error; -} - -static int remove_remote_tracking(git_repository *repo, const char *remote_name) -{ - git_remote *remote; - int error; - size_t i, count; - - /* we want to use what's on the config, regardless of changes to the instance in memory */ - if ((error = git_remote_lookup(&remote, repo, remote_name)) < 0) - return error; - - count = git_remote_refspec_count(remote); - for (i = 0; i < count; i++) { - const git_refspec *refspec = git_remote_get_refspec(remote, i); - - /* shouldn't ever actually happen */ - if (refspec == NULL) - continue; - - if ((error = remove_refs(repo, refspec)) < 0) - break; - } - - git_remote_free(remote); - return error; -} - -int git_remote_delete(git_repository *repo, const char *name) -{ - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = remove_branch_config_related_entries(repo, name)) < 0 || - (error = remove_remote_tracking(repo, name)) < 0 || - (error = rename_remote_config_section(repo, name, NULL)) < 0) - return error; - - return 0; -} - -int git_remote_default_branch(git_buf *out, git_remote *remote) -{ - GIT_BUF_WRAP_PRIVATE(out, git_remote__default_branch, remote); -} - -int git_remote__default_branch(git_str *out, git_remote *remote) -{ - const git_remote_head **heads; - const git_remote_head *guess = NULL; - const git_oid *head_id; - size_t heads_len, i; - git_str local_default = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(out); - - if ((error = git_remote_ls(&heads, &heads_len, remote)) < 0) - goto done; - - if (heads_len == 0 || strcmp(heads[0]->name, GIT_HEAD_FILE)) { - error = GIT_ENOTFOUND; - goto done; - } - - /* the first one must be HEAD so if that has the symref info, we're done */ - if (heads[0]->symref_target) { - error = git_str_puts(out, heads[0]->symref_target); - goto done; - } - - /* - * If there's no symref information, we have to look over them - * and guess. We return the first match unless the default - * branch is a candidate. Then we return the default branch. - */ - - if ((error = git_repository_initialbranch(&local_default, remote->repo)) < 0) - goto done; - - head_id = &heads[0]->oid; - - for (i = 1; i < heads_len; i++) { - if (git_oid_cmp(head_id, &heads[i]->oid)) - continue; - - if (git__prefixcmp(heads[i]->name, GIT_REFS_HEADS_DIR)) - continue; - - if (!guess) { - guess = heads[i]; - continue; - } - - if (!git__strcmp(local_default.ptr, heads[i]->name)) { - guess = heads[i]; - break; - } - } - - if (!guess) { - error = GIT_ENOTFOUND; - goto done; - } - - error = git_str_puts(out, guess->name); - -done: - git_str_dispose(&local_default); - return error; -} - -GIT_INLINE(int) connect_opts_from_push_opts( - git_remote_connect_options *out, - git_remote *remote, - const git_push_options *push_opts) -{ - git_remote_connect_options tmp = GIT_REMOTE_CONNECT_OPTIONS_INIT; - copy_opts(&tmp, push_opts); - return git_remote_connect_options_normalize(out, remote->repo, &tmp); -} - -int git_remote_upload( - git_remote *remote, - const git_strarray *refspecs, - const git_push_options *opts) -{ - git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; - git_push *push; - git_refspec *spec; - size_t i; - int error; - - GIT_ASSERT_ARG(remote); - - if (!remote->repo) { - git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); - return -1; - } - - if ((error = connect_opts_from_push_opts(&connect_opts, remote, opts)) < 0) - goto cleanup; - - if ((error = connect_or_reset_options(remote, GIT_DIRECTION_PUSH, &connect_opts)) < 0) - goto cleanup; - - free_refspecs(&remote->active_refspecs); - if ((error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs)) < 0) - goto cleanup; - - if (remote->push) { - git_push_free(remote->push); - remote->push = NULL; - } - - if ((error = git_push_new(&remote->push, remote, opts)) < 0) - goto cleanup; - - push = remote->push; - - if (refspecs && refspecs->count > 0) { - for (i = 0; i < refspecs->count; i++) { - if ((error = git_push_add_refspec(push, refspecs->strings[i])) < 0) - goto cleanup; - } - } else { - git_vector_foreach(&remote->refspecs, i, spec) { - if (!spec->push) - continue; - if ((error = git_push_add_refspec(push, spec->string)) < 0) - goto cleanup; - } - } - - if ((error = git_push_finish(push)) < 0) - goto cleanup; - - if (connect_opts.callbacks.push_update_reference && - (error = git_push_status_foreach(push, connect_opts.callbacks.push_update_reference, connect_opts.callbacks.payload)) < 0) - goto cleanup; - -cleanup: - git_remote_connect_options_dispose(&connect_opts); - return error; -} - -int git_remote_push( - git_remote *remote, - const git_strarray *refspecs, - const git_push_options *opts) -{ - git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; - int error; - - GIT_ASSERT_ARG(remote); - - if (!remote->repo) { - git_error_set(GIT_ERROR_INVALID, "cannot download detached remote"); - return -1; - } - - if (connect_opts_from_push_opts(&connect_opts, remote, opts) < 0) - return -1; - - if ((error = git_remote_upload(remote, refspecs, opts)) < 0) - goto done; - - error = git_remote_update_tips(remote, &connect_opts.callbacks, 0, 0, NULL); - -done: - git_remote_disconnect(remote); - git_remote_connect_options_dispose(&connect_opts); - return error; -} - -#define PREFIX "url" -#define SUFFIX_FETCH "insteadof" -#define SUFFIX_PUSH "pushinsteadof" - -static int apply_insteadof(char **out, git_config *config, const char *url, int direction, bool use_default_if_empty) -{ - size_t match_length, prefix_length, suffix_length; - char *replacement = NULL; - const char *regexp; - - git_str result = GIT_STR_INIT; - git_config_entry *entry; - git_config_iterator *iter; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(config); - GIT_ASSERT_ARG(url); - GIT_ASSERT_ARG(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH); - - /* Add 1 to prefix/suffix length due to the additional escaped dot */ - prefix_length = strlen(PREFIX) + 1; - if (direction == GIT_DIRECTION_FETCH) { - regexp = PREFIX "\\..*\\." SUFFIX_FETCH; - suffix_length = strlen(SUFFIX_FETCH) + 1; - } else { - regexp = PREFIX "\\..*\\." SUFFIX_PUSH; - suffix_length = strlen(SUFFIX_PUSH) + 1; - } - - if (git_config_iterator_glob_new(&iter, config, regexp) < 0) - return -1; - - match_length = 0; - while (git_config_next(&entry, iter) == 0) { - size_t n, replacement_length; - - /* Check if entry value is a prefix of URL */ - if (git__prefixcmp(url, entry->value)) - continue; - - /* Check if entry value is longer than previous - * prefixes */ - if ((n = strlen(entry->value)) <= match_length) - continue; - - git__free(replacement); - match_length = n; - - /* Cut off prefix and suffix of the value */ - replacement_length = - strlen(entry->name) - (prefix_length + suffix_length); - replacement = git__strndup(entry->name + prefix_length, - replacement_length); - } - - git_config_iterator_free(iter); - - if (match_length == 0 && use_default_if_empty) { - *out = git__strdup(url); - return *out ? 0 : -1; - } else if (match_length == 0) { - *out = NULL; - return 0; - } - - git_str_printf(&result, "%s%s", replacement, url + match_length); - - git__free(replacement); - - *out = git_str_detach(&result); - return 0; -} - -/* Deprecated functions */ - -#ifndef GIT_DEPRECATE_HARD - -int git_remote_is_valid_name(const char *remote_name) -{ - int valid = 0; - - git_remote_name_is_valid(&valid, remote_name); - return valid; -} - -#endif diff --git a/src/remote.h b/src/remote.h deleted file mode 100644 index ea9c7d17f..000000000 --- a/src/remote.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_remote_h__ -#define INCLUDE_remote_h__ - -#include "common.h" - -#include "git2/remote.h" -#include "git2/transport.h" -#include "git2/sys/transport.h" - -#include "refspec.h" -#include "vector.h" -#include "net.h" - -#define GIT_REMOTE_ORIGIN "origin" - -struct git_remote { - char *name; - char *url; - char *pushurl; - git_vector refs; - git_vector refspecs; - git_vector active_refspecs; - git_vector passive_refspecs; - git_vector local_heads; - git_transport *transport; - git_repository *repo; - git_push *push; - git_indexer_progress stats; - unsigned int need_pack; - git_remote_autotag_option_t download_tags; - int prune_refs; - int passed_refspecs; -}; - -int git_remote__urlfordirection(git_str *url_out, struct git_remote *remote, int direction, const git_remote_callbacks *callbacks); -int git_remote__http_proxy(char **out, git_remote *remote, git_net_url *url); - -git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname); -git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname); - -int git_remote__default_branch(git_str *out, git_remote *remote); - -int git_remote_connect_options_dup( - git_remote_connect_options *dst, - const git_remote_connect_options *src); -int git_remote_connect_options_normalize( - git_remote_connect_options *dst, - git_repository *repo, - const git_remote_connect_options *src); -void git_remote_connect_options_dispose(git_remote_connect_options *opts); - -int git_remote_capabilities(unsigned int *out, git_remote *remote); - -#endif diff --git a/src/repo_template.h b/src/repo_template.h deleted file mode 100644 index 099279aa7..000000000 --- a/src/repo_template.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_repo_template_h__ -#define INCLUDE_repo_template_h__ - -#define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/" -#define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/" - -#define GIT_HOOKS_DIR "hooks/" -#define GIT_HOOKS_DIR_MODE 0777 - -#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample" -#define GIT_HOOKS_README_MODE 0777 -#define GIT_HOOKS_README_CONTENT \ -"#!/bin/sh\n"\ -"#\n"\ -"# Place appropriately named executable hook scripts into this directory\n"\ -"# to intercept various actions that git takes. See `git help hooks` for\n"\ -"# more information.\n" - -#define GIT_INFO_DIR "info/" -#define GIT_INFO_DIR_MODE 0777 - -#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude" -#define GIT_INFO_EXCLUDE_MODE 0666 -#define GIT_INFO_EXCLUDE_CONTENT \ -"# File patterns to ignore; see `git help ignore` for more information.\n"\ -"# Lines that start with '#' are comments.\n" - -#define GIT_DESC_FILE "description" -#define GIT_DESC_MODE 0666 -#define GIT_DESC_CONTENT \ -"Unnamed repository; edit this file 'description' to name the repository.\n" - -typedef struct { - const char *path; - mode_t mode; - const char *content; -} repo_template_item; - -static repo_template_item repo_template[] = { - { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/info/' */ - { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/pack/' */ - { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/heads/' */ - { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/tags/' */ - { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE, NULL }, /* '/hooks/' */ - { GIT_INFO_DIR, GIT_INFO_DIR_MODE, NULL }, /* '/info/' */ - { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT }, - { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT }, - { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT }, - { NULL, 0, NULL } -}; - -#endif diff --git a/src/repository.c b/src/repository.c deleted file mode 100644 index 80b4a98eb..000000000 --- a/src/repository.c +++ /dev/null @@ -1,3253 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "repository.h" - -#include - -#include "git2/object.h" -#include "git2/sys/repository.h" - -#include "buf.h" -#include "common.h" -#include "commit.h" -#include "tag.h" -#include "blob.h" -#include "futils.h" -#include "sysdir.h" -#include "filebuf.h" -#include "index.h" -#include "config.h" -#include "refs.h" -#include "filter.h" -#include "odb.h" -#include "refdb.h" -#include "remote.h" -#include "merge.h" -#include "diff_driver.h" -#include "annotated_commit.h" -#include "submodule.h" -#include "worktree.h" -#include "path.h" -#include "strmap.h" - -#ifdef GIT_WIN32 -# include "win32/w32_util.h" -#endif - -bool git_repository__fsync_gitdir = false; - -static const struct { - git_repository_item_t parent; - git_repository_item_t fallback; - const char *name; - bool directory; -} items[] = { - { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, NULL, true }, - { GIT_REPOSITORY_ITEM_WORKDIR, GIT_REPOSITORY_ITEM__LAST, NULL, true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM__LAST, NULL, true }, - { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, "index", false }, - { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "objects", true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "refs", true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "packed-refs", false }, - { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "remotes", true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "config", false }, - { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "info", true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "hooks", true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "logs", true }, - { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, "modules", true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "worktrees", true } -}; - -static int check_repositoryformatversion(int *version, git_config *config); -static int check_extensions(git_config *config, int version); - -#define GIT_COMMONDIR_FILE "commondir" -#define GIT_GITDIR_FILE "gitdir" - -#define GIT_FILE_CONTENT_PREFIX "gitdir:" - -#define GIT_BRANCH_DEFAULT "master" - -#define GIT_REPO_VERSION 0 -#define GIT_REPO_MAX_VERSION 1 - -git_str git_repository__reserved_names_win32[] = { - { DOT_GIT, 0, CONST_STRLEN(DOT_GIT) }, - { GIT_DIR_SHORTNAME, 0, CONST_STRLEN(GIT_DIR_SHORTNAME) } -}; -size_t git_repository__reserved_names_win32_len = 2; - -git_str git_repository__reserved_names_posix[] = { - { DOT_GIT, 0, CONST_STRLEN(DOT_GIT) }, -}; -size_t git_repository__reserved_names_posix_len = 1; - -static void set_odb(git_repository *repo, git_odb *odb) -{ - if (odb) { - GIT_REFCOUNT_OWN(odb, repo); - GIT_REFCOUNT_INC(odb); - } - - if ((odb = git_atomic_swap(repo->_odb, odb)) != NULL) { - GIT_REFCOUNT_OWN(odb, NULL); - git_odb_free(odb); - } -} - -static void set_refdb(git_repository *repo, git_refdb *refdb) -{ - if (refdb) { - GIT_REFCOUNT_OWN(refdb, repo); - GIT_REFCOUNT_INC(refdb); - } - - if ((refdb = git_atomic_swap(repo->_refdb, refdb)) != NULL) { - GIT_REFCOUNT_OWN(refdb, NULL); - git_refdb_free(refdb); - } -} - -static void set_config(git_repository *repo, git_config *config) -{ - if (config) { - GIT_REFCOUNT_OWN(config, repo); - GIT_REFCOUNT_INC(config); - } - - if ((config = git_atomic_swap(repo->_config, config)) != NULL) { - GIT_REFCOUNT_OWN(config, NULL); - git_config_free(config); - } - - git_repository__configmap_lookup_cache_clear(repo); -} - -static void set_index(git_repository *repo, git_index *index) -{ - if (index) { - GIT_REFCOUNT_OWN(index, repo); - GIT_REFCOUNT_INC(index); - } - - if ((index = git_atomic_swap(repo->_index, index)) != NULL) { - GIT_REFCOUNT_OWN(index, NULL); - git_index_free(index); - } -} - -int git_repository__cleanup(git_repository *repo) -{ - GIT_ASSERT_ARG(repo); - - git_repository_submodule_cache_clear(repo); - git_cache_clear(&repo->objects); - git_attr_cache_flush(repo); - - set_config(repo, NULL); - set_index(repo, NULL); - set_odb(repo, NULL); - set_refdb(repo, NULL); - - return 0; -} - -void git_repository_free(git_repository *repo) -{ - size_t i; - - if (repo == NULL) - return; - - git_repository__cleanup(repo); - - git_cache_dispose(&repo->objects); - - git_diff_driver_registry_free(repo->diff_drivers); - repo->diff_drivers = NULL; - - for (i = 0; i < repo->reserved_names.size; i++) - git_str_dispose(git_array_get(repo->reserved_names, i)); - git_array_clear(repo->reserved_names); - - git__free(repo->gitlink); - git__free(repo->gitdir); - git__free(repo->commondir); - git__free(repo->workdir); - git__free(repo->namespace); - git__free(repo->ident_name); - git__free(repo->ident_email); - - git__memzero(repo, sizeof(*repo)); - git__free(repo); -} - -/* Check if we have a separate commondir (e.g. we have a worktree) */ -static int lookup_commondir(bool *separate, git_str *commondir, git_str *repository_path) -{ - git_str common_link = GIT_STR_INIT; - int error; - - /* - * If there's no commondir file, the repository path is the - * common path, but it needs a trailing slash. - */ - if (!git_fs_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) { - if ((error = git_str_set(commondir, repository_path->ptr, repository_path->size)) == 0) - error = git_fs_path_to_dir(commondir); - - *separate = false; - goto done; - } - - *separate = true; - - if ((error = git_str_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE)) < 0 || - (error = git_futils_readbuffer(&common_link, common_link.ptr)) < 0) - goto done; - - git_str_rtrim(&common_link); - if (git_fs_path_is_relative(common_link.ptr)) { - if ((error = git_str_joinpath(commondir, repository_path->ptr, common_link.ptr)) < 0) - goto done; - } else { - git_str_swap(commondir, &common_link); - } - - git_str_dispose(&common_link); - - /* Make sure the commondir path always has a trailing slash */ - error = git_fs_path_prettify_dir(commondir, commondir->ptr, NULL); - -done: - return error; -} - -GIT_INLINE(int) validate_repo_path(git_str *path) -{ - /* - * The longest static path in a repository (or commondir) is the - * packed refs file. (Loose refs may be longer since they - * include the reference name, but will be validated when the - * path is constructed.) - */ - static size_t suffix_len = - CONST_STRLEN("objects/pack/pack-.pack.lock") + - GIT_OID_HEXSZ; - - return git_fs_path_validate_str_length_with_suffix( - path, suffix_len); -} - -/* - * Git repository open methods - * - * Open a repository object from its path - */ -static int is_valid_repository_path(bool *out, git_str *repository_path, git_str *common_path) -{ - bool separate_commondir = false; - int error; - - *out = false; - - if ((error = lookup_commondir(&separate_commondir, common_path, repository_path)) < 0) - return error; - - /* Ensure HEAD file exists */ - if (git_fs_path_contains_file(repository_path, GIT_HEAD_FILE) == false) - return 0; - - /* Check files in common dir */ - if (git_fs_path_contains_dir(common_path, GIT_OBJECTS_DIR) == false) - return 0; - if (git_fs_path_contains_dir(common_path, GIT_REFS_DIR) == false) - return 0; - - /* Ensure the repo (and commondir) are valid paths */ - if ((error = validate_repo_path(common_path)) < 0 || - (separate_commondir && - (error = validate_repo_path(repository_path)) < 0)) - return error; - - *out = true; - return 0; -} - -static git_repository *repository_alloc(void) -{ - git_repository *repo = git__calloc(1, sizeof(git_repository)); - - if (repo == NULL || - git_cache_init(&repo->objects) < 0) - goto on_error; - - git_array_init_to_size(repo->reserved_names, 4); - if (!repo->reserved_names.ptr) - goto on_error; - - /* set all the entries in the configmap cache to `unset` */ - git_repository__configmap_lookup_cache_clear(repo); - - return repo; - -on_error: - if (repo) - git_cache_dispose(&repo->objects); - - git__free(repo); - return NULL; -} - -int git_repository_new(git_repository **out) -{ - git_repository *repo; - - *out = repo = repository_alloc(); - GIT_ERROR_CHECK_ALLOC(repo); - - repo->is_bare = 1; - repo->is_worktree = 0; - - return 0; -} - -static int load_config_data(git_repository *repo, const git_config *config) -{ - int is_bare; - - int err = git_config_get_bool(&is_bare, config, "core.bare"); - if (err < 0 && err != GIT_ENOTFOUND) - return err; - - /* Try to figure out if it's bare, default to non-bare if it's not set */ - if (err != GIT_ENOTFOUND) - repo->is_bare = is_bare && !repo->is_worktree; - else - repo->is_bare = 0; - - return 0; -} - -static int load_workdir(git_repository *repo, git_config *config, git_str *parent_path) -{ - int error; - git_config_entry *ce; - git_str worktree = GIT_STR_INIT; - git_str path = GIT_STR_INIT; - - if (repo->is_bare) - return 0; - - if ((error = git_config__lookup_entry( - &ce, config, "core.worktree", false)) < 0) - return error; - - if (repo->is_worktree) { - char *gitlink = git_worktree__read_link(repo->gitdir, GIT_GITDIR_FILE); - if (!gitlink) { - error = -1; - goto cleanup; - } - - git_str_attach(&worktree, gitlink, 0); - - if ((git_fs_path_dirname_r(&worktree, worktree.ptr)) < 0 || - git_fs_path_to_dir(&worktree) < 0) { - error = -1; - goto cleanup; - } - - repo->workdir = git_str_detach(&worktree); - } - else if (ce && ce->value) { - if ((error = git_fs_path_prettify_dir( - &worktree, ce->value, repo->gitdir)) < 0) - goto cleanup; - - repo->workdir = git_str_detach(&worktree); - } - else if (parent_path && git_fs_path_isdir(parent_path->ptr)) - repo->workdir = git_str_detach(parent_path); - else { - if (git_fs_path_dirname_r(&worktree, repo->gitdir) < 0 || - git_fs_path_to_dir(&worktree) < 0) { - error = -1; - goto cleanup; - } - - repo->workdir = git_str_detach(&worktree); - } - - GIT_ERROR_CHECK_ALLOC(repo->workdir); -cleanup: - git_str_dispose(&path); - git_config_entry_free(ce); - return error; -} - -/* - * This function returns furthest offset into path where a ceiling dir - * is found, so we can stop processing the path at that point. - * - * Note: converting this to use git_strs instead of GIT_PATH_MAX buffers on - * the stack could remove directories name limits, but at the cost of doing - * repeated malloc/frees inside the loop below, so let's not do it now. - */ -static size_t find_ceiling_dir_offset( - const char *path, - const char *ceiling_directories) -{ - char buf[GIT_PATH_MAX + 1]; - char buf2[GIT_PATH_MAX + 1]; - const char *ceil, *sep; - size_t len, max_len = 0, min_len; - - GIT_ASSERT_ARG(path); - - min_len = (size_t)(git_fs_path_root(path) + 1); - - if (ceiling_directories == NULL || min_len == 0) - return min_len; - - for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) { - for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++); - len = sep - ceil; - - if (len == 0 || len >= sizeof(buf) || git_fs_path_root(ceil) == -1) - continue; - - strncpy(buf, ceil, len); - buf[len] = '\0'; - - if (p_realpath(buf, buf2) == NULL) - continue; - - len = strlen(buf2); - if (len > 0 && buf2[len-1] == '/') - buf[--len] = '\0'; - - if (!strncmp(path, buf2, len) && - (path[len] == '/' || !path[len]) && - len > max_len) - { - max_len = len; - } - } - - return (max_len <= min_len ? min_len : max_len); -} - -/* - * Read the contents of `file_path` and set `path_out` to the repo dir that - * it points to. Before calling, set `path_out` to the base directory that - * should be used if the contents of `file_path` are a relative path. - */ -static int read_gitfile(git_str *path_out, const char *file_path) -{ - int error = 0; - git_str file = GIT_STR_INIT; - size_t prefix_len = strlen(GIT_FILE_CONTENT_PREFIX); - - GIT_ASSERT_ARG(path_out); - GIT_ASSERT_ARG(file_path); - - if (git_futils_readbuffer(&file, file_path) < 0) - return -1; - - git_str_rtrim(&file); - /* apparently on Windows, some people use backslashes in paths */ - git_fs_path_mkposix(file.ptr); - - if (git_str_len(&file) <= prefix_len || - memcmp(git_str_cstr(&file), GIT_FILE_CONTENT_PREFIX, prefix_len) != 0) - { - git_error_set(GIT_ERROR_REPOSITORY, - "the `.git` file at '%s' is malformed", file_path); - error = -1; - } - else if ((error = git_fs_path_dirname_r(path_out, file_path)) >= 0) { - const char *gitlink = git_str_cstr(&file) + prefix_len; - while (*gitlink && git__isspace(*gitlink)) gitlink++; - - error = git_fs_path_prettify_dir( - path_out, gitlink, git_str_cstr(path_out)); - } - - git_str_dispose(&file); - return error; -} - -static int find_repo( - git_str *gitdir_path, - git_str *workdir_path, - git_str *gitlink_path, - git_str *commondir_path, - const char *start_path, - uint32_t flags, - const char *ceiling_dirs) -{ - git_str path = GIT_STR_INIT; - git_str repo_link = GIT_STR_INIT; - git_str common_link = GIT_STR_INIT; - struct stat st; - dev_t initial_device = 0; - int min_iterations; - bool in_dot_git, is_valid; - size_t ceiling_offset = 0; - int error; - - git_str_clear(gitdir_path); - - error = git_fs_path_prettify(&path, start_path, NULL); - if (error < 0) - return error; - - /* in_dot_git toggles each loop: - * /a/b/c/.git, /a/b/c, /a/b/.git, /a/b, /a/.git, /a - * With GIT_REPOSITORY_OPEN_BARE or GIT_REPOSITORY_OPEN_NO_DOTGIT, we - * assume we started with /a/b/c.git and don't append .git the first - * time through. - * min_iterations indicates the number of iterations left before going - * further counts as a search. */ - if (flags & (GIT_REPOSITORY_OPEN_BARE | GIT_REPOSITORY_OPEN_NO_DOTGIT)) { - in_dot_git = true; - min_iterations = 1; - } else { - in_dot_git = false; - min_iterations = 2; - } - - for (;;) { - if (!(flags & GIT_REPOSITORY_OPEN_NO_DOTGIT)) { - if (!in_dot_git) { - if ((error = git_str_joinpath(&path, path.ptr, DOT_GIT)) < 0) - goto out; - } - in_dot_git = !in_dot_git; - } - - if (p_stat(path.ptr, &st) == 0) { - /* check that we have not crossed device boundaries */ - if (initial_device == 0) - initial_device = st.st_dev; - else if (st.st_dev != initial_device && - !(flags & GIT_REPOSITORY_OPEN_CROSS_FS)) - break; - - if (S_ISDIR(st.st_mode)) { - if ((error = is_valid_repository_path(&is_valid, &path, &common_link)) < 0) - goto out; - - if (is_valid) { - if ((error = git_fs_path_to_dir(&path)) < 0 || - (error = git_str_set(gitdir_path, path.ptr, path.size)) < 0) - goto out; - - if (gitlink_path) - if ((error = git_str_attach(gitlink_path, git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0)) < 0) - goto out; - if (commondir_path) - git_str_swap(&common_link, commondir_path); - - break; - } - } else if (S_ISREG(st.st_mode) && git__suffixcmp(path.ptr, "/" DOT_GIT) == 0) { - if ((error = read_gitfile(&repo_link, path.ptr)) < 0 || - (error = is_valid_repository_path(&is_valid, &repo_link, &common_link)) < 0) - goto out; - - if (is_valid) { - git_str_swap(gitdir_path, &repo_link); - - if (gitlink_path) - if ((error = git_str_put(gitlink_path, path.ptr, path.size)) < 0) - goto out; - if (commondir_path) - git_str_swap(&common_link, commondir_path); - } - break; - } - } - - /* Move up one directory. If we're in_dot_git, we'll search the - * parent itself next. If we're !in_dot_git, we'll search .git - * in the parent directory next (added at the top of the loop). */ - if ((error = git_fs_path_dirname_r(&path, path.ptr)) < 0) - goto out; - - /* Once we've checked the directory (and .git if applicable), - * find the ceiling for a search. */ - if (min_iterations && (--min_iterations == 0)) - ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs); - - /* Check if we should stop searching here. */ - if (min_iterations == 0 && - (path.ptr[ceiling_offset] == 0 || (flags & GIT_REPOSITORY_OPEN_NO_SEARCH))) - break; - } - - if (workdir_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { - if (!git_str_len(gitdir_path)) - git_str_clear(workdir_path); - else if ((error = git_fs_path_dirname_r(workdir_path, path.ptr)) < 0 || - (error = git_fs_path_to_dir(workdir_path)) < 0) - goto out; - } - - /* If we didn't find the repository, and we don't have any other error - * to report, report that. */ - if (!git_str_len(gitdir_path)) { - git_error_set(GIT_ERROR_REPOSITORY, "could not find repository from '%s'", start_path); - error = GIT_ENOTFOUND; - goto out; - } - -out: - git_str_dispose(&path); - git_str_dispose(&repo_link); - git_str_dispose(&common_link); - return error; -} - -int git_repository_open_bare( - git_repository **repo_ptr, - const char *bare_path) -{ - git_str path = GIT_STR_INIT, common_path = GIT_STR_INIT; - git_repository *repo = NULL; - bool is_valid; - int error; - - if ((error = git_fs_path_prettify_dir(&path, bare_path, NULL)) < 0 || - (error = is_valid_repository_path(&is_valid, &path, &common_path)) < 0) - return error; - - if (!is_valid) { - git_str_dispose(&path); - git_str_dispose(&common_path); - git_error_set(GIT_ERROR_REPOSITORY, "path is not a repository: %s", bare_path); - return GIT_ENOTFOUND; - } - - repo = repository_alloc(); - GIT_ERROR_CHECK_ALLOC(repo); - - repo->gitdir = git_str_detach(&path); - GIT_ERROR_CHECK_ALLOC(repo->gitdir); - repo->commondir = git_str_detach(&common_path); - GIT_ERROR_CHECK_ALLOC(repo->commondir); - - /* of course we're bare! */ - repo->is_bare = 1; - repo->is_worktree = 0; - repo->workdir = NULL; - - *repo_ptr = repo; - return 0; -} - -static int _git_repository_open_ext_from_env( - git_repository **out, - const char *start_path) -{ - git_repository *repo = NULL; - git_index *index = NULL; - git_odb *odb = NULL; - git_str dir_buf = GIT_STR_INIT; - git_str ceiling_dirs_buf = GIT_STR_INIT; - git_str across_fs_buf = GIT_STR_INIT; - git_str index_file_buf = GIT_STR_INIT; - git_str namespace_buf = GIT_STR_INIT; - git_str object_dir_buf = GIT_STR_INIT; - git_str alts_buf = GIT_STR_INIT; - git_str work_tree_buf = GIT_STR_INIT; - git_str common_dir_buf = GIT_STR_INIT; - const char *ceiling_dirs = NULL; - unsigned flags = 0; - int error; - - if (!start_path) { - error = git__getenv(&dir_buf, "GIT_DIR"); - if (error == GIT_ENOTFOUND) { - git_error_clear(); - start_path = "."; - } else if (error < 0) - goto error; - else { - start_path = git_str_cstr(&dir_buf); - flags |= GIT_REPOSITORY_OPEN_NO_SEARCH; - flags |= GIT_REPOSITORY_OPEN_NO_DOTGIT; - } - } - - error = git__getenv(&ceiling_dirs_buf, "GIT_CEILING_DIRECTORIES"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else - ceiling_dirs = git_str_cstr(&ceiling_dirs_buf); - - error = git__getenv(&across_fs_buf, "GIT_DISCOVERY_ACROSS_FILESYSTEM"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - int across_fs = 0; - error = git_config_parse_bool(&across_fs, git_str_cstr(&across_fs_buf)); - if (error < 0) - goto error; - if (across_fs) - flags |= GIT_REPOSITORY_OPEN_CROSS_FS; - } - - error = git__getenv(&index_file_buf, "GIT_INDEX_FILE"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - error = git_index_open(&index, git_str_cstr(&index_file_buf)); - if (error < 0) - goto error; - } - - error = git__getenv(&namespace_buf, "GIT_NAMESPACE"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - - error = git__getenv(&object_dir_buf, "GIT_OBJECT_DIRECTORY"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - error = git_odb_open(&odb, git_str_cstr(&object_dir_buf)); - if (error < 0) - goto error; - } - - error = git__getenv(&work_tree_buf, "GIT_WORK_TREE"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - git_error_set(GIT_ERROR_INVALID, "GIT_WORK_TREE unimplemented"); - error = GIT_ERROR; - goto error; - } - - error = git__getenv(&work_tree_buf, "GIT_COMMON_DIR"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - git_error_set(GIT_ERROR_INVALID, "GIT_COMMON_DIR unimplemented"); - error = GIT_ERROR; - goto error; - } - - error = git_repository_open_ext(&repo, start_path, flags, ceiling_dirs); - if (error < 0) - goto error; - - if (odb) - git_repository_set_odb(repo, odb); - - error = git__getenv(&alts_buf, "GIT_ALTERNATE_OBJECT_DIRECTORIES"); - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } else if (error < 0) - goto error; - else { - const char *end; - char *alt, *sep; - if (!odb) { - error = git_repository_odb(&odb, repo); - if (error < 0) - goto error; - } - - end = git_str_cstr(&alts_buf) + git_str_len(&alts_buf); - for (sep = alt = alts_buf.ptr; sep != end; alt = sep+1) { - for (sep = alt; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++) - ; - if (*sep) - *sep = '\0'; - error = git_odb_add_disk_alternate(odb, alt); - if (error < 0) - goto error; - } - } - - if (git_str_len(&namespace_buf)) { - error = git_repository_set_namespace(repo, git_str_cstr(&namespace_buf)); - if (error < 0) - goto error; - } - - git_repository_set_index(repo, index); - - if (out) { - *out = repo; - goto success; - } -error: - git_repository_free(repo); -success: - git_odb_free(odb); - git_index_free(index); - git_str_dispose(&common_dir_buf); - git_str_dispose(&work_tree_buf); - git_str_dispose(&alts_buf); - git_str_dispose(&object_dir_buf); - git_str_dispose(&namespace_buf); - git_str_dispose(&index_file_buf); - git_str_dispose(&across_fs_buf); - git_str_dispose(&ceiling_dirs_buf); - git_str_dispose(&dir_buf); - return error; -} - -static int repo_is_worktree(unsigned *out, const git_repository *repo) -{ - git_str gitdir_link = GIT_STR_INIT; - int error; - - /* Worktrees cannot have the same commondir and gitdir */ - if (repo->commondir && repo->gitdir - && !strcmp(repo->commondir, repo->gitdir)) { - *out = 0; - return 0; - } - - if ((error = git_str_joinpath(&gitdir_link, repo->gitdir, "gitdir")) < 0) - return -1; - - /* A 'gitdir' file inside a git directory is currently - * only used when the repository is a working tree. */ - *out = !!git_fs_path_exists(gitdir_link.ptr); - - git_str_dispose(&gitdir_link); - return error; -} - -int git_repository_open_ext( - git_repository **repo_ptr, - const char *start_path, - unsigned int flags, - const char *ceiling_dirs) -{ - int error; - unsigned is_worktree; - git_str gitdir = GIT_STR_INIT, workdir = GIT_STR_INIT, - gitlink = GIT_STR_INIT, commondir = GIT_STR_INIT; - git_repository *repo = NULL; - git_config *config = NULL; - int version = 0; - - if (flags & GIT_REPOSITORY_OPEN_FROM_ENV) - return _git_repository_open_ext_from_env(repo_ptr, start_path); - - if (repo_ptr) - *repo_ptr = NULL; - - error = find_repo( - &gitdir, &workdir, &gitlink, &commondir, start_path, flags, ceiling_dirs); - - if (error < 0 || !repo_ptr) - goto cleanup; - - repo = repository_alloc(); - GIT_ERROR_CHECK_ALLOC(repo); - - repo->gitdir = git_str_detach(&gitdir); - GIT_ERROR_CHECK_ALLOC(repo->gitdir); - - if (gitlink.size) { - repo->gitlink = git_str_detach(&gitlink); - GIT_ERROR_CHECK_ALLOC(repo->gitlink); - } - if (commondir.size) { - repo->commondir = git_str_detach(&commondir); - GIT_ERROR_CHECK_ALLOC(repo->commondir); - } - - if ((error = repo_is_worktree(&is_worktree, repo)) < 0) - goto cleanup; - repo->is_worktree = is_worktree; - - /* - * We'd like to have the config, but git doesn't particularly - * care if it's not there, so we need to deal with that. - */ - - error = git_repository_config_snapshot(&config, repo); - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - if (config && (error = check_repositoryformatversion(&version, config)) < 0) - goto cleanup; - - if ((error = check_extensions(config, version)) < 0) - goto cleanup; - - if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0) - repo->is_bare = 1; - else { - - if (config && - ((error = load_config_data(repo, config)) < 0 || - (error = load_workdir(repo, config, &workdir)) < 0)) - goto cleanup; - } - -cleanup: - git_str_dispose(&gitdir); - git_str_dispose(&workdir); - git_str_dispose(&gitlink); - git_str_dispose(&commondir); - git_config_free(config); - - if (error < 0) - git_repository_free(repo); - else if (repo_ptr) - *repo_ptr = repo; - - return error; -} - -int git_repository_open(git_repository **repo_out, const char *path) -{ - return git_repository_open_ext( - repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL); -} - -int git_repository_open_from_worktree(git_repository **repo_out, git_worktree *wt) -{ - git_str path = GIT_STR_INIT; - git_repository *repo = NULL; - size_t len; - int err; - - GIT_ASSERT_ARG(repo_out); - GIT_ASSERT_ARG(wt); - - *repo_out = NULL; - len = strlen(wt->gitlink_path); - - if (len <= 4 || strcasecmp(wt->gitlink_path + len - 4, ".git")) { - err = -1; - goto out; - } - - if ((err = git_str_set(&path, wt->gitlink_path, len - 4)) < 0) - goto out; - - if ((err = git_repository_open(&repo, path.ptr)) < 0) - goto out; - - *repo_out = repo; - -out: - git_str_dispose(&path); - - return err; -} - -int git_repository_wrap_odb(git_repository **repo_out, git_odb *odb) -{ - git_repository *repo; - - repo = repository_alloc(); - GIT_ERROR_CHECK_ALLOC(repo); - - git_repository_set_odb(repo, odb); - *repo_out = repo; - - return 0; -} - -int git_repository_discover( - git_buf *out, - const char *start_path, - int across_fs, - const char *ceiling_dirs) -{ - uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0; - - GIT_ASSERT_ARG(start_path); - - GIT_BUF_WRAP_PRIVATE(out, find_repo, NULL, NULL, NULL, start_path, flags, ceiling_dirs); -} - -static int load_config( - git_config **out, - git_repository *repo, - const char *global_config_path, - const char *xdg_config_path, - const char *system_config_path, - const char *programdata_path) -{ - int error; - git_str config_path = GIT_STR_INIT; - git_config *cfg = NULL; - - GIT_ASSERT_ARG(out); - - if ((error = git_config_new(&cfg)) < 0) - return error; - - if (repo) { - if ((error = git_repository__item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG)) == 0) - error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, repo, 0); - - if (error && error != GIT_ENOTFOUND) - goto on_error; - - git_str_dispose(&config_path); - } - - if (global_config_path != NULL && - (error = git_config_add_file_ondisk( - cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, repo, 0)) < 0 && - error != GIT_ENOTFOUND) - goto on_error; - - if (xdg_config_path != NULL && - (error = git_config_add_file_ondisk( - cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, repo, 0)) < 0 && - error != GIT_ENOTFOUND) - goto on_error; - - if (system_config_path != NULL && - (error = git_config_add_file_ondisk( - cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, repo, 0)) < 0 && - error != GIT_ENOTFOUND) - goto on_error; - - if (programdata_path != NULL && - (error = git_config_add_file_ondisk( - cfg, programdata_path, GIT_CONFIG_LEVEL_PROGRAMDATA, repo, 0)) < 0 && - error != GIT_ENOTFOUND) - goto on_error; - - git_error_clear(); /* clear any lingering ENOTFOUND errors */ - - *out = cfg; - return 0; - -on_error: - git_str_dispose(&config_path); - git_config_free(cfg); - *out = NULL; - return error; -} - -static const char *path_unless_empty(git_str *buf) -{ - return git_str_len(buf) > 0 ? git_str_cstr(buf) : NULL; -} - -int git_repository_config__weakptr(git_config **out, git_repository *repo) -{ - int error = 0; - - if (repo->_config == NULL) { - git_str global_buf = GIT_STR_INIT; - git_str xdg_buf = GIT_STR_INIT; - git_str system_buf = GIT_STR_INIT; - git_str programdata_buf = GIT_STR_INIT; - git_config *config; - - git_config__find_global(&global_buf); - git_config__find_xdg(&xdg_buf); - git_config__find_system(&system_buf); - git_config__find_programdata(&programdata_buf); - - /* If there is no global file, open a backend for it anyway */ - if (git_str_len(&global_buf) == 0) - git_config__global_location(&global_buf); - - error = load_config( - &config, repo, - path_unless_empty(&global_buf), - path_unless_empty(&xdg_buf), - path_unless_empty(&system_buf), - path_unless_empty(&programdata_buf)); - if (!error) { - GIT_REFCOUNT_OWN(config, repo); - - if (git_atomic_compare_and_swap(&repo->_config, NULL, config) != NULL) { - GIT_REFCOUNT_OWN(config, NULL); - git_config_free(config); - } - } - - git_str_dispose(&global_buf); - git_str_dispose(&xdg_buf); - git_str_dispose(&system_buf); - git_str_dispose(&programdata_buf); - } - - *out = repo->_config; - return error; -} - -int git_repository_config(git_config **out, git_repository *repo) -{ - if (git_repository_config__weakptr(out, repo) < 0) - return -1; - - GIT_REFCOUNT_INC(*out); - return 0; -} - -int git_repository_config_snapshot(git_config **out, git_repository *repo) -{ - int error; - git_config *weak; - - if ((error = git_repository_config__weakptr(&weak, repo)) < 0) - return error; - - return git_config_snapshot(out, weak); -} - -int git_repository_set_config(git_repository *repo, git_config *config) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(config); - - set_config(repo, config); - return 0; -} - -int git_repository_odb__weakptr(git_odb **out, git_repository *repo) -{ - int error = 0; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(out); - - *out = git_atomic_load(repo->_odb); - if (*out == NULL) { - git_str odb_path = GIT_STR_INIT; - git_odb *odb; - - if ((error = git_repository__item_path(&odb_path, repo, - GIT_REPOSITORY_ITEM_OBJECTS)) < 0 || - (error = git_odb_new(&odb)) < 0) - return error; - - GIT_REFCOUNT_OWN(odb, repo); - - if ((error = git_odb__set_caps(odb, GIT_ODB_CAP_FROM_OWNER)) < 0 || - (error = git_odb__add_default_backends(odb, odb_path.ptr, 0, 0)) < 0) { - git_odb_free(odb); - return error; - } - - if (git_atomic_compare_and_swap(&repo->_odb, NULL, odb) != NULL) { - GIT_REFCOUNT_OWN(odb, NULL); - git_odb_free(odb); - } - - git_str_dispose(&odb_path); - *out = git_atomic_load(repo->_odb); - } - - return error; -} - -int git_repository_odb(git_odb **out, git_repository *repo) -{ - if (git_repository_odb__weakptr(out, repo) < 0) - return -1; - - GIT_REFCOUNT_INC(*out); - return 0; -} - -int git_repository_set_odb(git_repository *repo, git_odb *odb) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(odb); - - set_odb(repo, odb); - return 0; -} - -int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo) -{ - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - if (repo->_refdb == NULL) { - git_refdb *refdb; - - error = git_refdb_open(&refdb, repo); - if (!error) { - GIT_REFCOUNT_OWN(refdb, repo); - - if (git_atomic_compare_and_swap(&repo->_refdb, NULL, refdb) != NULL) { - GIT_REFCOUNT_OWN(refdb, NULL); - git_refdb_free(refdb); - } - } - } - - *out = repo->_refdb; - return error; -} - -int git_repository_refdb(git_refdb **out, git_repository *repo) -{ - if (git_repository_refdb__weakptr(out, repo) < 0) - return -1; - - GIT_REFCOUNT_INC(*out); - return 0; -} - -int git_repository_set_refdb(git_repository *repo, git_refdb *refdb) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(refdb); - - set_refdb(repo, refdb); - return 0; -} - -int git_repository_index__weakptr(git_index **out, git_repository *repo) -{ - int error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - if (repo->_index == NULL) { - git_str index_path = GIT_STR_INIT; - git_index *index; - - if ((error = git_str_joinpath(&index_path, repo->gitdir, GIT_INDEX_FILE)) < 0) - return error; - - error = git_index_open(&index, index_path.ptr); - if (!error) { - GIT_REFCOUNT_OWN(index, repo); - - if (git_atomic_compare_and_swap(&repo->_index, NULL, index) != NULL) { - GIT_REFCOUNT_OWN(index, NULL); - git_index_free(index); - } - - error = git_index_set_caps(repo->_index, - GIT_INDEX_CAPABILITY_FROM_OWNER); - } - - git_str_dispose(&index_path); - } - - *out = repo->_index; - return error; -} - -int git_repository_index(git_index **out, git_repository *repo) -{ - if (git_repository_index__weakptr(out, repo) < 0) - return -1; - - GIT_REFCOUNT_INC(*out); - return 0; -} - -int git_repository_set_index(git_repository *repo, git_index *index) -{ - GIT_ASSERT_ARG(repo); - set_index(repo, index); - return 0; -} - -int git_repository_set_namespace(git_repository *repo, const char *namespace) -{ - git__free(repo->namespace); - - if (namespace == NULL) { - repo->namespace = NULL; - return 0; - } - - return (repo->namespace = git__strdup(namespace)) ? 0 : -1; -} - -const char *git_repository_get_namespace(git_repository *repo) -{ - return repo->namespace; -} - -#ifdef GIT_WIN32 -static int reserved_names_add8dot3(git_repository *repo, const char *path) -{ - char *name = git_win32_path_8dot3_name(path); - const char *def = GIT_DIR_SHORTNAME; - const char *def_dot_git = DOT_GIT; - size_t name_len, def_len = CONST_STRLEN(GIT_DIR_SHORTNAME); - size_t def_dot_git_len = CONST_STRLEN(DOT_GIT); - git_str *buf; - - if (!name) - return 0; - - name_len = strlen(name); - - if ((name_len == def_len && memcmp(name, def, def_len) == 0) || - (name_len == def_dot_git_len && memcmp(name, def_dot_git, def_dot_git_len) == 0)) { - git__free(name); - return 0; - } - - if ((buf = git_array_alloc(repo->reserved_names)) == NULL) - return -1; - - git_str_attach(buf, name, name_len); - return true; -} - -bool git_repository__reserved_names( - git_str **out, size_t *outlen, git_repository *repo, bool include_ntfs) -{ - GIT_UNUSED(include_ntfs); - - if (repo->reserved_names.size == 0) { - git_str *buf; - size_t i; - - /* Add the static defaults */ - for (i = 0; i < git_repository__reserved_names_win32_len; i++) { - if ((buf = git_array_alloc(repo->reserved_names)) == NULL) - goto on_error; - - buf->ptr = git_repository__reserved_names_win32[i].ptr; - buf->size = git_repository__reserved_names_win32[i].size; - } - - /* Try to add any repo-specific reserved names - the gitlink file - * within a submodule or the repository (if the repository directory - * is beneath the workdir). These are typically `.git`, but should - * be protected in case they are not. Note, repo and workdir paths - * are always prettified to end in `/`, so a prefixcmp is safe. - */ - if (!repo->is_bare) { - int (*prefixcmp)(const char *, const char *); - int error, ignorecase; - - error = git_repository__configmap_lookup( - &ignorecase, repo, GIT_CONFIGMAP_IGNORECASE); - prefixcmp = (error || ignorecase) ? git__prefixcmp_icase : - git__prefixcmp; - - if (repo->gitlink && - reserved_names_add8dot3(repo, repo->gitlink) < 0) - goto on_error; - - if (repo->gitdir && - prefixcmp(repo->gitdir, repo->workdir) == 0 && - reserved_names_add8dot3(repo, repo->gitdir) < 0) - goto on_error; - } - } - - *out = repo->reserved_names.ptr; - *outlen = repo->reserved_names.size; - - return true; - - /* Always give good defaults, even on OOM */ -on_error: - *out = git_repository__reserved_names_win32; - *outlen = git_repository__reserved_names_win32_len; - - return false; -} -#else -bool git_repository__reserved_names( - git_str **out, size_t *outlen, git_repository *repo, bool include_ntfs) -{ - GIT_UNUSED(repo); - - if (include_ntfs) { - *out = git_repository__reserved_names_win32; - *outlen = git_repository__reserved_names_win32_len; - } else { - *out = git_repository__reserved_names_posix; - *outlen = git_repository__reserved_names_posix_len; - } - - return true; -} -#endif - -static int check_repositoryformatversion(int *version, git_config *config) -{ - int error; - - error = git_config_get_int32(version, config, "core.repositoryformatversion"); - /* git ignores this if the config variable isn't there */ - if (error == GIT_ENOTFOUND) - return 0; - - if (error < 0) - return -1; - - if (GIT_REPO_MAX_VERSION < *version) { - git_error_set(GIT_ERROR_REPOSITORY, - "unsupported repository version %d; only versions up to %d are supported", - *version, GIT_REPO_MAX_VERSION); - return -1; - } - - return 0; -} - -static const char *builtin_extensions[] = { - "noop" -}; - -static git_vector user_extensions = GIT_VECTOR_INIT; - -static int check_valid_extension(const git_config_entry *entry, void *payload) -{ - git_str cfg = GIT_STR_INIT; - bool reject; - const char *extension; - size_t i; - int error = 0; - - GIT_UNUSED(payload); - - git_vector_foreach (&user_extensions, i, extension) { - git_str_clear(&cfg); - - /* - * Users can specify that they don't want to support an - * extension with a '!' prefix. - */ - if ((reject = (extension[0] == '!')) == true) - extension = &extension[1]; - - if ((error = git_str_printf(&cfg, "extensions.%s", extension)) < 0) - goto done; - - if (strcmp(entry->name, cfg.ptr) == 0) { - if (reject) - goto fail; - - goto done; - } - } - - for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) { - extension = builtin_extensions[i]; - - if ((error = git_str_printf(&cfg, "extensions.%s", extension)) < 0) - goto done; - - if (strcmp(entry->name, cfg.ptr) == 0) - goto done; - } - -fail: - git_error_set(GIT_ERROR_REPOSITORY, "unsupported extension name %s", entry->name); - error = -1; - -done: - git_str_dispose(&cfg); - return error; -} - -static int check_extensions(git_config *config, int version) -{ - if (version < 1) - return 0; - - return git_config_foreach_match(config, "^extensions\\.", check_valid_extension, NULL); -} - -int git_repository__extensions(char ***out, size_t *out_len) -{ - git_vector extensions; - const char *builtin, *user; - char *extension; - size_t i, j; - - if (git_vector_init(&extensions, 8, NULL) < 0) - return -1; - - for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) { - bool match = false; - - builtin = builtin_extensions[i]; - - git_vector_foreach (&user_extensions, j, user) { - if (user[0] == '!' && strcmp(builtin, &user[1]) == 0) { - match = true; - break; - } - } - - if (match) - continue; - - if ((extension = git__strdup(builtin)) == NULL || - git_vector_insert(&extensions, extension) < 0) - return -1; - } - - git_vector_foreach (&user_extensions, i, user) { - if (user[0] == '!') - continue; - - if ((extension = git__strdup(user)) == NULL || - git_vector_insert(&extensions, extension) < 0) - return -1; - } - - *out = (char **)git_vector_detach(out_len, NULL, &extensions); - return 0; -} - -int git_repository__set_extensions(const char **extensions, size_t len) -{ - char *extension; - size_t i; - - git_repository__free_extensions(); - - for (i = 0; i < len; i++) { - if ((extension = git__strdup(extensions[i])) == NULL || - git_vector_insert(&user_extensions, extension) < 0) - return -1; - } - - return 0; -} - -void git_repository__free_extensions(void) -{ - git_vector_free_deep(&user_extensions); -} - -int git_repository_create_head(const char *git_dir, const char *ref_name) -{ - git_str ref_path = GIT_STR_INIT; - git_filebuf ref = GIT_FILEBUF_INIT; - const char *fmt; - int error; - - if ((error = git_str_joinpath(&ref_path, git_dir, GIT_HEAD_FILE)) < 0 || - (error = git_filebuf_open(&ref, ref_path.ptr, 0, GIT_REFS_FILE_MODE)) < 0) - goto out; - - if (git__prefixcmp(ref_name, GIT_REFS_DIR) == 0) - fmt = "ref: %s\n"; - else - fmt = "ref: " GIT_REFS_HEADS_DIR "%s\n"; - - if ((error = git_filebuf_printf(&ref, fmt, ref_name)) < 0 || - (error = git_filebuf_commit(&ref)) < 0) - goto out; - -out: - git_str_dispose(&ref_path); - git_filebuf_cleanup(&ref); - return error; -} - -static bool is_chmod_supported(const char *file_path) -{ - struct stat st1, st2; - - if (p_stat(file_path, &st1) < 0) - return false; - - if (p_chmod(file_path, st1.st_mode ^ S_IXUSR) < 0) - return false; - - if (p_stat(file_path, &st2) < 0) - return false; - - return (st1.st_mode != st2.st_mode); -} - -static bool is_filesystem_case_insensitive(const char *gitdir_path) -{ - git_str path = GIT_STR_INIT; - int is_insensitive = -1; - - if (!git_str_joinpath(&path, gitdir_path, "CoNfIg")) - is_insensitive = git_fs_path_exists(git_str_cstr(&path)); - - git_str_dispose(&path); - return is_insensitive; -} - -static bool are_symlinks_supported(const char *wd_path) -{ - git_config *config = NULL; - git_str global_buf = GIT_STR_INIT; - git_str xdg_buf = GIT_STR_INIT; - git_str system_buf = GIT_STR_INIT; - git_str programdata_buf = GIT_STR_INIT; - int symlinks = 0; - - /* - * To emulate Git for Windows, symlinks on Windows must be explicitly - * opted-in. We examine the system configuration for a core.symlinks - * set to true. If found, we then examine the filesystem to see if - * symlinks are _actually_ supported by the current user. If that is - * _not_ set, then we do not test or enable symlink support. - */ -#ifdef GIT_WIN32 - git_config__find_global(&global_buf); - git_config__find_xdg(&xdg_buf); - git_config__find_system(&system_buf); - git_config__find_programdata(&programdata_buf); - - if (load_config(&config, NULL, - path_unless_empty(&global_buf), - path_unless_empty(&xdg_buf), - path_unless_empty(&system_buf), - path_unless_empty(&programdata_buf)) < 0) - goto done; - - if (git_config_get_bool(&symlinks, config, "core.symlinks") < 0 || !symlinks) - goto done; -#endif - - if (!(symlinks = git_fs_path_supports_symlinks(wd_path))) - goto done; - -done: - git_str_dispose(&global_buf); - git_str_dispose(&xdg_buf); - git_str_dispose(&system_buf); - git_str_dispose(&programdata_buf); - git_config_free(config); - return symlinks != 0; -} - -static int create_empty_file(const char *path, mode_t mode) -{ - int fd; - - if ((fd = p_creat(path, mode)) < 0) { - git_error_set(GIT_ERROR_OS, "error while creating '%s'", path); - return -1; - } - - if (p_close(fd) < 0) { - git_error_set(GIT_ERROR_OS, "error while closing '%s'", path); - return -1; - } - - return 0; -} - -static int repo_local_config( - git_config **out, - git_str *config_dir, - git_repository *repo, - const char *repo_dir) -{ - int error = 0; - git_config *parent; - const char *cfg_path; - - if (git_str_joinpath(config_dir, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0) - return -1; - cfg_path = git_str_cstr(config_dir); - - /* make LOCAL config if missing */ - if (!git_fs_path_isfile(cfg_path) && - (error = create_empty_file(cfg_path, GIT_CONFIG_FILE_MODE)) < 0) - return error; - - /* if no repo, just open that file directly */ - if (!repo) - return git_config_open_ondisk(out, cfg_path); - - /* otherwise, open parent config and get that level */ - if ((error = git_repository_config__weakptr(&parent, repo)) < 0) - return error; - - if (git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL) < 0) { - git_error_clear(); - - if (!(error = git_config_add_file_ondisk( - parent, cfg_path, GIT_CONFIG_LEVEL_LOCAL, repo, false))) - error = git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL); - } - - git_config_free(parent); - - return error; -} - -static int repo_init_fs_configs( - git_config *cfg, - const char *cfg_path, - const char *repo_dir, - const char *work_dir, - bool update_ignorecase) -{ - int error = 0; - - if (!work_dir) - work_dir = repo_dir; - - if ((error = git_config_set_bool( - cfg, "core.filemode", is_chmod_supported(cfg_path))) < 0) - return error; - - if (!are_symlinks_supported(work_dir)) { - if ((error = git_config_set_bool(cfg, "core.symlinks", false)) < 0) - return error; - } else if (git_config_delete_entry(cfg, "core.symlinks") < 0) - git_error_clear(); - - if (update_ignorecase) { - if (is_filesystem_case_insensitive(repo_dir)) { - if ((error = git_config_set_bool(cfg, "core.ignorecase", true)) < 0) - return error; - } else if (git_config_delete_entry(cfg, "core.ignorecase") < 0) - git_error_clear(); - } - -#ifdef GIT_USE_ICONV - if ((error = git_config_set_bool( - cfg, "core.precomposeunicode", - git_fs_path_does_decompose_unicode(work_dir))) < 0) - return error; - /* on non-iconv platforms, don't even set core.precomposeunicode */ -#endif - - return 0; -} - -static int repo_init_config( - const char *repo_dir, - const char *work_dir, - uint32_t flags, - uint32_t mode) -{ - int error = 0; - git_str cfg_path = GIT_STR_INIT, worktree_path = GIT_STR_INIT; - git_config *config = NULL; - bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0); - bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0); - int version = 0; - - if ((error = repo_local_config(&config, &cfg_path, NULL, repo_dir)) < 0) - goto cleanup; - - if (is_reinit && (error = check_repositoryformatversion(&version, config)) < 0) - goto cleanup; - - if ((error = check_extensions(config, version)) < 0) - goto cleanup; - -#define SET_REPO_CONFIG(TYPE, NAME, VAL) do { \ - if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \ - goto cleanup; } while (0) - - SET_REPO_CONFIG(bool, "core.bare", is_bare); - SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION); - - if ((error = repo_init_fs_configs( - config, cfg_path.ptr, repo_dir, work_dir, !is_reinit)) < 0) - goto cleanup; - - if (!is_bare) { - SET_REPO_CONFIG(bool, "core.logallrefupdates", true); - - if (!(flags & GIT_REPOSITORY_INIT__NATURAL_WD)) { - if ((error = git_str_sets(&worktree_path, work_dir)) < 0) - goto cleanup; - - if ((flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK)) - if ((error = git_fs_path_make_relative(&worktree_path, repo_dir)) < 0) - goto cleanup; - - SET_REPO_CONFIG(string, "core.worktree", worktree_path.ptr); - } else if (is_reinit) { - if (git_config_delete_entry(config, "core.worktree") < 0) - git_error_clear(); - } - } - - if (mode == GIT_REPOSITORY_INIT_SHARED_GROUP) { - SET_REPO_CONFIG(int32, "core.sharedrepository", 1); - SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); - } - else if (mode == GIT_REPOSITORY_INIT_SHARED_ALL) { - SET_REPO_CONFIG(int32, "core.sharedrepository", 2); - SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); - } - -cleanup: - git_str_dispose(&cfg_path); - git_str_dispose(&worktree_path); - git_config_free(config); - - return error; -} - -static int repo_reinit_submodule_fs(git_submodule *sm, const char *n, void *p) -{ - git_repository *smrepo = NULL; - GIT_UNUSED(n); GIT_UNUSED(p); - - if (git_submodule_open(&smrepo, sm) < 0 || - git_repository_reinit_filesystem(smrepo, true) < 0) - git_error_clear(); - git_repository_free(smrepo); - - return 0; -} - -int git_repository_reinit_filesystem(git_repository *repo, int recurse) -{ - int error = 0; - git_str path = GIT_STR_INIT; - git_config *config = NULL; - const char *repo_dir = git_repository_path(repo); - - if (!(error = repo_local_config(&config, &path, repo, repo_dir))) - error = repo_init_fs_configs( - config, path.ptr, repo_dir, git_repository_workdir(repo), true); - - git_config_free(config); - git_str_dispose(&path); - - git_repository__configmap_lookup_cache_clear(repo); - - if (!repo->is_bare && recurse) - (void)git_submodule_foreach(repo, repo_reinit_submodule_fs, NULL); - - return error; -} - -static int repo_write_template( - const char *git_dir, - bool allow_overwrite, - const char *file, - mode_t mode, - bool hidden, - const char *content) -{ - git_str path = GIT_STR_INIT; - int fd, error = 0, flags; - - if (git_str_joinpath(&path, git_dir, file) < 0) - return -1; - - if (allow_overwrite) - flags = O_WRONLY | O_CREAT | O_TRUNC; - else - flags = O_WRONLY | O_CREAT | O_EXCL; - - fd = p_open(git_str_cstr(&path), flags, mode); - - if (fd >= 0) { - error = p_write(fd, content, strlen(content)); - - p_close(fd); - } - else if (errno != EEXIST) - error = fd; - -#ifdef GIT_WIN32 - if (!error && hidden) { - if (git_win32__set_hidden(path.ptr, true) < 0) - error = -1; - } -#else - GIT_UNUSED(hidden); -#endif - - git_str_dispose(&path); - - if (error) - git_error_set(GIT_ERROR_OS, - "failed to initialize repository with template '%s'", file); - - return error; -} - -static int repo_write_gitlink( - const char *in_dir, const char *to_repo, bool use_relative_path) -{ - int error; - git_str buf = GIT_STR_INIT; - git_str path_to_repo = GIT_STR_INIT; - struct stat st; - - git_fs_path_dirname_r(&buf, to_repo); - git_fs_path_to_dir(&buf); - if (git_str_oom(&buf)) - return -1; - - /* don't write gitlink to natural workdir */ - if (git__suffixcmp(to_repo, "/" DOT_GIT "/") == 0 && - strcmp(in_dir, buf.ptr) == 0) - { - error = GIT_PASSTHROUGH; - goto cleanup; - } - - if ((error = git_str_joinpath(&buf, in_dir, DOT_GIT)) < 0) - goto cleanup; - - if (!p_stat(buf.ptr, &st) && !S_ISREG(st.st_mode)) { - git_error_set(GIT_ERROR_REPOSITORY, - "cannot overwrite gitlink file into path '%s'", in_dir); - error = GIT_EEXISTS; - goto cleanup; - } - - git_str_clear(&buf); - - error = git_str_sets(&path_to_repo, to_repo); - - if (!error && use_relative_path) - error = git_fs_path_make_relative(&path_to_repo, in_dir); - - if (!error) - error = git_str_join(&buf, ' ', GIT_FILE_CONTENT_PREFIX, path_to_repo.ptr); - - if (!error) - error = repo_write_template(in_dir, true, DOT_GIT, 0666, true, buf.ptr); - -cleanup: - git_str_dispose(&buf); - git_str_dispose(&path_to_repo); - return error; -} - -static mode_t pick_dir_mode(git_repository_init_options *opts) -{ - if (opts->mode == GIT_REPOSITORY_INIT_SHARED_UMASK) - return 0777; - if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP) - return (0775 | S_ISGID); - if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL) - return (0777 | S_ISGID); - return opts->mode; -} - -#include "repo_template.h" - -static int repo_init_structure( - const char *repo_dir, - const char *work_dir, - git_repository_init_options *opts) -{ - int error = 0; - repo_template_item *tpl; - bool external_tpl = - ((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0); - mode_t dmode = pick_dir_mode(opts); - bool chmod = opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK; - - /* Hide the ".git" directory */ -#ifdef GIT_WIN32 - if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) { - if (git_win32__set_hidden(repo_dir, true) < 0) { - git_error_set(GIT_ERROR_OS, - "failed to mark Git repository folder as hidden"); - return -1; - } - } -#endif - - /* Create the .git gitlink if appropriate */ - if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0 && - (opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD) == 0) - { - if (repo_write_gitlink(work_dir, repo_dir, opts->flags & GIT_REPOSITORY_INIT_RELATIVE_GITLINK) < 0) - return -1; - } - - /* Copy external template if requested */ - if (external_tpl) { - git_config *cfg = NULL; - const char *tdir = NULL; - bool default_template = false; - git_str template_buf = GIT_STR_INIT; - - if (opts->template_path) - tdir = opts->template_path; - else if ((error = git_config_open_default(&cfg)) >= 0) { - if (!git_config__get_path(&template_buf, cfg, "init.templatedir")) - tdir = template_buf.ptr; - git_error_clear(); - } - - if (!tdir) { - if (!(error = git_sysdir_find_template_dir(&template_buf))) - tdir = template_buf.ptr; - default_template = true; - } - - /* - * If tdir was the empty string, treat it like tdir was a path to an - * empty directory (so, don't do any copying). This is the behavior - * that git(1) exhibits, although it doesn't seem to be officially - * documented. - */ - if (tdir && git__strcmp(tdir, "") != 0) { - uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS | - GIT_CPDIR_SIMPLE_TO_MODE | - GIT_CPDIR_COPY_DOTFILES; - if (opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK) - cpflags |= GIT_CPDIR_CHMOD_DIRS; - error = git_futils_cp_r(tdir, repo_dir, cpflags, dmode); - } - - git_str_dispose(&template_buf); - git_config_free(cfg); - - /* If tdir does not exist, then do not error out. This matches the - * behaviour of git(1), which just prints a warning and continues. - * TODO: issue warning when warning API is available. - * `git` prints to stderr: 'warning: templates not found in /path/to/tdir' - */ - if (error < 0) { - if (!default_template && error != GIT_ENOTFOUND) - return error; - - /* if template was default, ignore error and use internal */ - git_error_clear(); - external_tpl = false; - error = 0; - } - } - - /* Copy internal template - * - always ensure existence of dirs - * - only create files if no external template was specified - */ - for (tpl = repo_template; !error && tpl->path; ++tpl) { - if (!tpl->content) { - uint32_t mkdir_flags = GIT_MKDIR_PATH; - if (chmod) - mkdir_flags |= GIT_MKDIR_CHMOD; - - error = git_futils_mkdir_relative( - tpl->path, repo_dir, dmode, mkdir_flags, NULL); - } - else if (!external_tpl) { - const char *content = tpl->content; - - if (opts->description && strcmp(tpl->path, GIT_DESC_FILE) == 0) - content = opts->description; - - error = repo_write_template( - repo_dir, false, tpl->path, tpl->mode, false, content); - } - } - - return error; -} - -static int mkdir_parent(git_str *buf, uint32_t mode, bool skip2) -{ - /* When making parent directories during repository initialization - * don't try to set gid or grant world write access - */ - return git_futils_mkdir( - buf->ptr, mode & ~(S_ISGID | 0002), - GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR | - (skip2 ? GIT_MKDIR_SKIP_LAST2 : GIT_MKDIR_SKIP_LAST)); -} - -static int repo_init_directories( - git_str *repo_path, - git_str *wd_path, - const char *given_repo, - git_repository_init_options *opts) -{ - int error = 0; - bool is_bare, add_dotgit, has_dotgit, natural_wd; - mode_t dirmode; - - /* There are three possible rules for what we are allowed to create: - * - MKPATH means anything we need - * - MKDIR means just the .git directory and its parent and the workdir - * - Neither means only the .git directory can be created - * - * There are 5 "segments" of path that we might need to deal with: - * 1. The .git directory - * 2. The parent of the .git directory - * 3. Everything above the parent of the .git directory - * 4. The working directory (often the same as #2) - * 5. Everything above the working directory (often the same as #3) - * - * For all directories created, we start with the init_mode value for - * permissions and then strip off bits in some cases: - * - * For MKPATH, we create #3 (and #5) paths without S_ISGID or S_IWOTH - * For MKPATH and MKDIR, we create #2 (and #4) without S_ISGID - * For all rules, we create #1 using the untouched init_mode - */ - - /* set up repo path */ - - is_bare = ((opts->flags & GIT_REPOSITORY_INIT_BARE) != 0); - - add_dotgit = - (opts->flags & GIT_REPOSITORY_INIT_NO_DOTGIT_DIR) == 0 && - !is_bare && - git__suffixcmp(given_repo, "/" DOT_GIT) != 0 && - git__suffixcmp(given_repo, "/" GIT_DIR) != 0; - - if (git_str_joinpath(repo_path, given_repo, add_dotgit ? GIT_DIR : "") < 0) - return -1; - - has_dotgit = (git__suffixcmp(repo_path->ptr, "/" GIT_DIR) == 0); - if (has_dotgit) - opts->flags |= GIT_REPOSITORY_INIT__HAS_DOTGIT; - - /* set up workdir path */ - - if (!is_bare) { - if (opts->workdir_path) { - if (git_fs_path_join_unrooted( - wd_path, opts->workdir_path, repo_path->ptr, NULL) < 0) - return -1; - } else if (has_dotgit) { - if (git_fs_path_dirname_r(wd_path, repo_path->ptr) < 0) - return -1; - } else { - git_error_set(GIT_ERROR_REPOSITORY, "cannot pick working directory" - " for non-bare repository that isn't a '.git' directory"); - return -1; - } - - if (git_fs_path_to_dir(wd_path) < 0) - return -1; - } else { - git_str_clear(wd_path); - } - - natural_wd = - has_dotgit && - wd_path->size > 0 && - wd_path->size + strlen(GIT_DIR) == repo_path->size && - memcmp(repo_path->ptr, wd_path->ptr, wd_path->size) == 0; - if (natural_wd) - opts->flags |= GIT_REPOSITORY_INIT__NATURAL_WD; - - /* create directories as needed / requested */ - - dirmode = pick_dir_mode(opts); - - if ((opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) { - /* create path #5 */ - if (wd_path->size > 0 && - (error = mkdir_parent(wd_path, dirmode, false)) < 0) - return error; - - /* create path #3 (if not the same as #5) */ - if (!natural_wd && - (error = mkdir_parent(repo_path, dirmode, has_dotgit)) < 0) - return error; - } - - if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 || - (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) - { - /* create path #4 */ - if (wd_path->size > 0 && - (error = git_futils_mkdir( - wd_path->ptr, dirmode & ~S_ISGID, - GIT_MKDIR_VERIFY_DIR)) < 0) - return error; - - /* create path #2 (if not the same as #4) */ - if (!natural_wd && - (error = git_futils_mkdir( - repo_path->ptr, dirmode & ~S_ISGID, - GIT_MKDIR_VERIFY_DIR | GIT_MKDIR_SKIP_LAST)) < 0) - return error; - } - - if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 || - (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0 || - has_dotgit) - { - /* create path #1 */ - error = git_futils_mkdir(repo_path->ptr, dirmode, - GIT_MKDIR_VERIFY_DIR | ((dirmode & S_ISGID) ? GIT_MKDIR_CHMOD : 0)); - } - - /* prettify both directories now that they are created */ - - if (!error) { - error = git_fs_path_prettify_dir(repo_path, repo_path->ptr, NULL); - - if (!error && wd_path->size > 0) - error = git_fs_path_prettify_dir(wd_path, wd_path->ptr, NULL); - } - - return error; -} - -static int repo_init_head(const char *repo_dir, const char *given) -{ - git_config *cfg = NULL; - git_str head_path = GIT_STR_INIT, cfg_branch = GIT_STR_INIT; - const char *initial_head = NULL; - int error; - - if ((error = git_str_joinpath(&head_path, repo_dir, GIT_HEAD_FILE)) < 0) - goto out; - - /* - * A template may have set a HEAD; use that unless it's been - * overridden by the caller's given initial head setting. - */ - if (git_fs_path_exists(head_path.ptr) && !given) - goto out; - - if (given) { - initial_head = given; - } else if ((error = git_config_open_default(&cfg)) >= 0 && - (error = git_config__get_string_buf(&cfg_branch, cfg, "init.defaultbranch")) >= 0 && - *cfg_branch.ptr) { - initial_head = cfg_branch.ptr; - } - - if (!initial_head) - initial_head = GIT_BRANCH_DEFAULT; - - error = git_repository_create_head(repo_dir, initial_head); - -out: - git_config_free(cfg); - git_str_dispose(&head_path); - git_str_dispose(&cfg_branch); - - return error; -} - -static int repo_init_create_origin(git_repository *repo, const char *url) -{ - int error; - git_remote *remote; - - if (!(error = git_remote_create(&remote, repo, GIT_REMOTE_ORIGIN, url))) { - git_remote_free(remote); - } - - return error; -} - -int git_repository_init( - git_repository **repo_out, const char *path, unsigned is_bare) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - - opts.flags = GIT_REPOSITORY_INIT_MKPATH; /* don't love this default */ - if (is_bare) - opts.flags |= GIT_REPOSITORY_INIT_BARE; - - return git_repository_init_ext(repo_out, path, &opts); -} - -int git_repository_init_ext( - git_repository **out, - const char *given_repo, - git_repository_init_options *opts) -{ - git_str repo_path = GIT_STR_INIT, wd_path = GIT_STR_INIT, - common_path = GIT_STR_INIT; - const char *wd; - bool is_valid; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(given_repo); - GIT_ASSERT_ARG(opts); - - GIT_ERROR_CHECK_VERSION(opts, GIT_REPOSITORY_INIT_OPTIONS_VERSION, "git_repository_init_options"); - - if ((error = repo_init_directories(&repo_path, &wd_path, given_repo, opts)) < 0) - goto out; - - wd = (opts->flags & GIT_REPOSITORY_INIT_BARE) ? NULL : git_str_cstr(&wd_path); - - if ((error = is_valid_repository_path(&is_valid, &repo_path, &common_path)) < 0) - goto out; - - if (is_valid) { - if ((opts->flags & GIT_REPOSITORY_INIT_NO_REINIT) != 0) { - git_error_set(GIT_ERROR_REPOSITORY, - "attempt to reinitialize '%s'", given_repo); - error = GIT_EEXISTS; - goto out; - } - - opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT; - - if ((error = repo_init_config(repo_path.ptr, wd, opts->flags, opts->mode)) < 0) - goto out; - - /* TODO: reinitialize the templates */ - } else { - if ((error = repo_init_structure(repo_path.ptr, wd, opts)) < 0 || - (error = repo_init_config(repo_path.ptr, wd, opts->flags, opts->mode)) < 0 || - (error = repo_init_head(repo_path.ptr, opts->initial_head)) < 0) - goto out; - } - - if ((error = git_repository_open(out, repo_path.ptr)) < 0) - goto out; - - if (opts->origin_url && - (error = repo_init_create_origin(*out, opts->origin_url)) < 0) - goto out; - -out: - git_str_dispose(&common_path); - git_str_dispose(&repo_path); - git_str_dispose(&wd_path); - - return error; -} - -int git_repository_head_detached(git_repository *repo) -{ - git_reference *ref; - git_odb *odb = NULL; - int exists; - - if (git_repository_odb__weakptr(&odb, repo) < 0) - return -1; - - if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) - return -1; - - if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { - git_reference_free(ref); - return 0; - } - - exists = git_odb_exists(odb, git_reference_target(ref)); - - git_reference_free(ref); - return exists; -} - -int git_repository_head_detached_for_worktree(git_repository *repo, const char *name) -{ - git_reference *ref = NULL; - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = git_repository_head_for_worktree(&ref, repo, name)) < 0) - goto out; - - error = (git_reference_type(ref) != GIT_REFERENCE_SYMBOLIC); -out: - git_reference_free(ref); - - return error; -} - -int git_repository_head(git_reference **head_out, git_repository *repo) -{ - git_reference *head; - int error; - - GIT_ASSERT_ARG(head_out); - - if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0) - return error; - - if (git_reference_type(head) == GIT_REFERENCE_DIRECT) { - *head_out = head; - return 0; - } - - error = git_reference_lookup_resolved(head_out, repo, git_reference_symbolic_target(head), -1); - git_reference_free(head); - - return error == GIT_ENOTFOUND ? GIT_EUNBORNBRANCH : error; -} - -int git_repository_head_for_worktree(git_reference **out, git_repository *repo, const char *name) -{ - git_repository *worktree_repo = NULL; - git_worktree *worktree = NULL; - git_reference *head = NULL; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - *out = NULL; - - if ((error = git_worktree_lookup(&worktree, repo, name)) < 0 || - (error = git_repository_open_from_worktree(&worktree_repo, worktree)) < 0 || - (error = git_reference_lookup(&head, worktree_repo, GIT_HEAD_FILE)) < 0) - goto out; - - if (git_reference_type(head) != GIT_REFERENCE_DIRECT) { - if ((error = git_reference_lookup_resolved(out, worktree_repo, git_reference_symbolic_target(head), -1)) < 0) - goto out; - } else { - *out = head; - head = NULL; - } - -out: - git_reference_free(head); - git_worktree_free(worktree); - git_repository_free(worktree_repo); - return error; -} - -int git_repository_foreach_worktree(git_repository *repo, - git_repository_foreach_worktree_cb cb, - void *payload) -{ - git_strarray worktrees = {0}; - git_repository *worktree_repo = NULL; - git_worktree *worktree = NULL; - int error; - size_t i; - - /* apply operation to repository supplied when commondir is empty, implying there's - * no linked worktrees to iterate, which can occur when using custom odb/refdb - */ - if (!repo->commondir) - return cb(repo, payload); - - if ((error = git_repository_open(&worktree_repo, repo->commondir)) < 0 || - (error = cb(worktree_repo, payload) != 0)) - goto out; - - git_repository_free(worktree_repo); - worktree_repo = NULL; - - if ((error = git_worktree_list(&worktrees, repo)) < 0) - goto out; - - for (i = 0; i < worktrees.count; i++) { - git_repository_free(worktree_repo); - worktree_repo = NULL; - git_worktree_free(worktree); - worktree = NULL; - - if ((error = git_worktree_lookup(&worktree, repo, worktrees.strings[i]) < 0) || - (error = git_repository_open_from_worktree(&worktree_repo, worktree)) < 0) { - if (error != GIT_ENOTFOUND) - goto out; - error = 0; - continue; - } - - if ((error = cb(worktree_repo, payload)) != 0) - goto out; - } - -out: - git_strarray_dispose(&worktrees); - git_repository_free(worktree_repo); - git_worktree_free(worktree); - return error; -} - -int git_repository_head_unborn(git_repository *repo) -{ - git_reference *ref = NULL; - int error; - - error = git_repository_head(&ref, repo); - git_reference_free(ref); - - if (error == GIT_EUNBORNBRANCH) { - git_error_clear(); - return 1; - } - - if (error < 0) - return -1; - - return 0; -} - -static int repo_contains_no_reference(git_repository *repo) -{ - git_reference_iterator *iter; - const char *refname; - int error; - - if ((error = git_reference_iterator_new(&iter, repo)) < 0) - return error; - - error = git_reference_next_name(&refname, iter); - git_reference_iterator_free(iter); - - if (error == GIT_ITEROVER) - return 1; - - return error; -} - -int git_repository_initialbranch(git_str *out, git_repository *repo) -{ - git_config *config; - git_config_entry *entry = NULL; - const char *branch; - int valid, error; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - return error; - - if ((error = git_config_get_entry(&entry, config, "init.defaultbranch")) == 0 && - *entry->value) { - branch = entry->value; - } - else if (!error || error == GIT_ENOTFOUND) { - branch = GIT_BRANCH_DEFAULT; - } - else { - goto done; - } - - if ((error = git_str_puts(out, GIT_REFS_HEADS_DIR)) < 0 || - (error = git_str_puts(out, branch)) < 0 || - (error = git_reference_name_is_valid(&valid, out->ptr)) < 0) - goto done; - - if (!valid) { - git_error_set(GIT_ERROR_INVALID, "the value of init.defaultBranch is not a valid branch name"); - error = -1; - } - -done: - git_config_entry_free(entry); - return error; -} - -int git_repository_is_empty(git_repository *repo) -{ - git_reference *head = NULL; - git_str initialbranch = GIT_STR_INIT; - int result = 0; - - if ((result = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0 || - (result = git_repository_initialbranch(&initialbranch, repo)) < 0) - goto done; - - result = (git_reference_type(head) == GIT_REFERENCE_SYMBOLIC && - strcmp(git_reference_symbolic_target(head), initialbranch.ptr) == 0 && - repo_contains_no_reference(repo)); - -done: - git_reference_free(head); - git_str_dispose(&initialbranch); - - return result; -} - -static const char *resolved_parent_path(const git_repository *repo, git_repository_item_t item, git_repository_item_t fallback) -{ - const char *parent; - - switch (item) { - case GIT_REPOSITORY_ITEM_GITDIR: - parent = git_repository_path(repo); - break; - case GIT_REPOSITORY_ITEM_WORKDIR: - parent = git_repository_workdir(repo); - break; - case GIT_REPOSITORY_ITEM_COMMONDIR: - parent = git_repository_commondir(repo); - break; - default: - git_error_set(GIT_ERROR_INVALID, "invalid item directory"); - return NULL; - } - if (!parent && fallback != GIT_REPOSITORY_ITEM__LAST) - return resolved_parent_path(repo, fallback, GIT_REPOSITORY_ITEM__LAST); - - return parent; -} - -int git_repository_item_path( - git_buf *out, - const git_repository *repo, - git_repository_item_t item) -{ - GIT_BUF_WRAP_PRIVATE(out, git_repository__item_path, repo, item); -} - -int git_repository__item_path( - git_str *out, - const git_repository *repo, - git_repository_item_t item) -{ - const char *parent = resolved_parent_path(repo, items[item].parent, items[item].fallback); - if (parent == NULL) { - git_error_set(GIT_ERROR_INVALID, "path cannot exist in repository"); - return GIT_ENOTFOUND; - } - - if (git_str_sets(out, parent) < 0) - return -1; - - if (items[item].name) { - if (git_str_joinpath(out, parent, items[item].name) < 0) - return -1; - } - - if (items[item].directory) { - if (git_fs_path_to_dir(out) < 0) - return -1; - } - - return 0; -} - -const char *git_repository_path(const git_repository *repo) -{ - GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); - return repo->gitdir; -} - -const char *git_repository_workdir(const git_repository *repo) -{ - GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); - - if (repo->is_bare) - return NULL; - - return repo->workdir; -} - -int git_repository_workdir_path( - git_str *out, git_repository *repo, const char *path) -{ - int error; - - if (!repo->workdir) { - git_error_set(GIT_ERROR_REPOSITORY, "repository has no working directory"); - return GIT_EBAREREPO; - } - - if (!(error = git_str_joinpath(out, repo->workdir, path))) - error = git_path_validate_str_length(repo, out); - - return error; -} - -const char *git_repository_commondir(const git_repository *repo) -{ - GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); - return repo->commondir; -} - -int git_repository_set_workdir( - git_repository *repo, const char *workdir, int update_gitlink) -{ - int error = 0; - git_str path = GIT_STR_INIT; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(workdir); - - if (git_fs_path_prettify_dir(&path, workdir, NULL) < 0) - return -1; - - if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0) - return 0; - - if (update_gitlink) { - git_config *config; - - if (git_repository_config__weakptr(&config, repo) < 0) - return -1; - - error = repo_write_gitlink(path.ptr, git_repository_path(repo), false); - - /* passthrough error means gitlink is unnecessary */ - if (error == GIT_PASSTHROUGH) - error = git_config_delete_entry(config, "core.worktree"); - else if (!error) - error = git_config_set_string(config, "core.worktree", path.ptr); - - if (!error) - error = git_config_set_bool(config, "core.bare", false); - } - - if (!error) { - char *old_workdir = repo->workdir; - - repo->workdir = git_str_detach(&path); - repo->is_bare = 0; - - git__free(old_workdir); - } - - return error; -} - -int git_repository_is_bare(const git_repository *repo) -{ - GIT_ASSERT_ARG(repo); - return repo->is_bare; -} - -int git_repository_is_worktree(const git_repository *repo) -{ - GIT_ASSERT_ARG(repo); - return repo->is_worktree; -} - -int git_repository_set_bare(git_repository *repo) -{ - int error; - git_config *config; - - GIT_ASSERT_ARG(repo); - - if (repo->is_bare) - return 0; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - return error; - - if ((error = git_config_set_bool(config, "core.bare", true)) < 0) - return error; - - if ((error = git_config__update_entry(config, "core.worktree", NULL, true, true)) < 0) - return error; - - git__free(repo->workdir); - repo->workdir = NULL; - repo->is_bare = 1; - - return 0; -} - -int git_repository_head_tree(git_tree **tree, git_repository *repo) -{ - git_reference *head; - git_object *obj; - int error; - - if ((error = git_repository_head(&head, repo)) < 0) - return error; - - if ((error = git_reference_peel(&obj, head, GIT_OBJECT_TREE)) < 0) - goto cleanup; - - *tree = (git_tree *)obj; - -cleanup: - git_reference_free(head); - return error; -} - -int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - char orig_head_str[GIT_OID_HEXSZ]; - int error = 0; - - git_oid_fmt(orig_head_str, orig_head); - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_ORIG_HEAD_FILE)) == 0 && - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) == 0 && - (error = git_filebuf_printf(&file, "%.*s\n", GIT_OID_HEXSZ, orig_head_str)) == 0) - error = git_filebuf_commit(&file); - - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int git_repository__message(git_str *out, git_repository *repo) -{ - git_str path = GIT_STR_INIT; - struct stat st; - int error; - - if (git_str_joinpath(&path, repo->gitdir, GIT_MERGE_MSG_FILE) < 0) - return -1; - - if ((error = p_stat(git_str_cstr(&path), &st)) < 0) { - if (errno == ENOENT) - error = GIT_ENOTFOUND; - git_error_set(GIT_ERROR_OS, "could not access message file"); - } else { - error = git_futils_readbuffer(out, git_str_cstr(&path)); - } - - git_str_dispose(&path); - - return error; -} - -int git_repository_message(git_buf *out, git_repository *repo) -{ - GIT_BUF_WRAP_PRIVATE(out, git_repository__message, repo); -} - -int git_repository_message_remove(git_repository *repo) -{ - git_str path = GIT_STR_INIT; - int error; - - if (git_str_joinpath(&path, repo->gitdir, GIT_MERGE_MSG_FILE) < 0) - return -1; - - error = p_unlink(git_str_cstr(&path)); - git_str_dispose(&path); - - return error; -} - -int git_repository_hashfile( - git_oid *out, - git_repository *repo, - const char *path, - git_object_t type, - const char *as_path) -{ - int error; - git_filter_list *fl = NULL; - git_file fd = -1; - uint64_t len; - git_str full_path = GIT_STR_INIT; - const char *workdir = git_repository_workdir(repo); - - /* as_path can be NULL */ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(path); - GIT_ASSERT_ARG(repo); - - if ((error = git_fs_path_join_unrooted(&full_path, path, workdir, NULL)) < 0 || - (error = git_path_validate_str_length(repo, &full_path)) < 0) - return error; - - /* - * NULL as_path means that we should derive it from the - * given path. - */ - if (!as_path) { - if (workdir && !git__prefixcmp(full_path.ptr, workdir)) - as_path = full_path.ptr + strlen(workdir); - else - as_path = ""; - } - - /* passing empty string for "as_path" indicated --no-filters */ - if (strlen(as_path) > 0) { - error = git_filter_list_load( - &fl, repo, NULL, as_path, - GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT); - - if (error < 0) - return error; - } - - /* at this point, error is a count of the number of loaded filters */ - - fd = git_futils_open_ro(full_path.ptr); - if (fd < 0) { - error = fd; - goto cleanup; - } - - if ((error = git_futils_filesize(&len, fd)) < 0) - goto cleanup; - - if (!git__is_sizet(len)) { - git_error_set(GIT_ERROR_OS, "file size overflow for 32-bit systems"); - error = -1; - goto cleanup; - } - - error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, fl); - -cleanup: - if (fd >= 0) - p_close(fd); - git_filter_list_free(fl); - git_str_dispose(&full_path); - - return error; -} - -static int checkout_message(git_str *out, git_reference *old, const char *new) -{ - git_str_puts(out, "checkout: moving from "); - - if (git_reference_type(old) == GIT_REFERENCE_SYMBOLIC) - git_str_puts(out, git_reference__shorthand(git_reference_symbolic_target(old))); - else - git_str_puts(out, git_oid_tostr_s(git_reference_target(old))); - - git_str_puts(out, " to "); - - if (git_reference__is_branch(new) || - git_reference__is_tag(new) || - git_reference__is_remote(new)) - git_str_puts(out, git_reference__shorthand(new)); - else - git_str_puts(out, new); - - if (git_str_oom(out)) - return -1; - - return 0; -} - -static int detach(git_repository *repo, const git_oid *id, const char *new) -{ - int error; - git_str log_message = GIT_STR_INIT; - git_object *object = NULL, *peeled = NULL; - git_reference *new_head = NULL, *current = NULL; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(id); - - if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) - return error; - - if ((error = git_object_lookup(&object, repo, id, GIT_OBJECT_ANY)) < 0) - goto cleanup; - - if ((error = git_object_peel(&peeled, object, GIT_OBJECT_COMMIT)) < 0) - goto cleanup; - - if (new == NULL) - new = git_oid_tostr_s(git_object_id(peeled)); - - if ((error = checkout_message(&log_message, current, new)) < 0) - goto cleanup; - - error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), true, git_str_cstr(&log_message)); - -cleanup: - git_str_dispose(&log_message); - git_object_free(object); - git_object_free(peeled); - git_reference_free(current); - git_reference_free(new_head); - return error; -} - -int git_repository_set_head( - git_repository *repo, - const char *refname) -{ - git_reference *ref = NULL, *current = NULL, *new_head = NULL; - git_str log_message = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(refname); - - if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) - return error; - - if ((error = checkout_message(&log_message, current, refname)) < 0) - goto cleanup; - - error = git_reference_lookup(&ref, repo, refname); - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - if (ref && current->type == GIT_REFERENCE_SYMBOLIC && git__strcmp(current->target.symbolic, ref->name) && - git_reference_is_branch(ref) && git_branch_is_checked_out(ref)) { - git_error_set(GIT_ERROR_REPOSITORY, "cannot set HEAD to reference '%s' as it is the current HEAD " - "of a linked repository.", git_reference_name(ref)); - error = -1; - goto cleanup; - } - - if (!error) { - if (git_reference_is_branch(ref)) { - error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, - git_reference_name(ref), true, git_str_cstr(&log_message)); - } else { - error = detach(repo, git_reference_target(ref), - git_reference_is_tag(ref) || git_reference_is_remote(ref) ? refname : NULL); - } - } else if (git_reference__is_branch(refname)) { - error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, refname, - true, git_str_cstr(&log_message)); - } - -cleanup: - git_str_dispose(&log_message); - git_reference_free(current); - git_reference_free(ref); - git_reference_free(new_head); - return error; -} - -int git_repository_set_head_detached( - git_repository *repo, - const git_oid *committish) -{ - return detach(repo, committish, NULL); -} - -int git_repository_set_head_detached_from_annotated( - git_repository *repo, - const git_annotated_commit *committish) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(committish); - - return detach(repo, git_annotated_commit_id(committish), committish->description); -} - -int git_repository_detach_head(git_repository *repo) -{ - git_reference *old_head = NULL, *new_head = NULL, *current = NULL; - git_object *object = NULL; - git_str log_message = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(repo); - - if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) - return error; - - if ((error = git_repository_head(&old_head, repo)) < 0) - goto cleanup; - - if ((error = git_object_lookup(&object, repo, git_reference_target(old_head), GIT_OBJECT_COMMIT)) < 0) - goto cleanup; - - if ((error = checkout_message(&log_message, current, git_oid_tostr_s(git_object_id(object)))) < 0) - goto cleanup; - - error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_reference_target(old_head), - 1, git_str_cstr(&log_message)); - -cleanup: - git_str_dispose(&log_message); - git_object_free(object); - git_reference_free(old_head); - git_reference_free(new_head); - git_reference_free(current); - return error; -} - -/** - * Loosely ported from git.git - * https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh#L198-289 - */ -int git_repository_state(git_repository *repo) -{ - git_str repo_path = GIT_STR_INIT; - int state = GIT_REPOSITORY_STATE_NONE; - - GIT_ASSERT_ARG(repo); - - if (git_str_puts(&repo_path, repo->gitdir) < 0) - return -1; - - if (git_fs_path_contains_file(&repo_path, GIT_REBASE_MERGE_INTERACTIVE_FILE)) - state = GIT_REPOSITORY_STATE_REBASE_INTERACTIVE; - else if (git_fs_path_contains_dir(&repo_path, GIT_REBASE_MERGE_DIR)) - state = GIT_REPOSITORY_STATE_REBASE_MERGE; - else if (git_fs_path_contains_file(&repo_path, GIT_REBASE_APPLY_REBASING_FILE)) - state = GIT_REPOSITORY_STATE_REBASE; - else if (git_fs_path_contains_file(&repo_path, GIT_REBASE_APPLY_APPLYING_FILE)) - state = GIT_REPOSITORY_STATE_APPLY_MAILBOX; - else if (git_fs_path_contains_dir(&repo_path, GIT_REBASE_APPLY_DIR)) - state = GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE; - else if (git_fs_path_contains_file(&repo_path, GIT_MERGE_HEAD_FILE)) - state = GIT_REPOSITORY_STATE_MERGE; - else if (git_fs_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE)) { - state = GIT_REPOSITORY_STATE_REVERT; - if (git_fs_path_contains_file(&repo_path, GIT_SEQUENCER_TODO_FILE)) { - state = GIT_REPOSITORY_STATE_REVERT_SEQUENCE; - } - } else if (git_fs_path_contains_file(&repo_path, GIT_CHERRYPICK_HEAD_FILE)) { - state = GIT_REPOSITORY_STATE_CHERRYPICK; - if (git_fs_path_contains_file(&repo_path, GIT_SEQUENCER_TODO_FILE)) { - state = GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE; - } - } else if (git_fs_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE)) - state = GIT_REPOSITORY_STATE_BISECT; - - git_str_dispose(&repo_path); - return state; -} - -int git_repository__cleanup_files( - git_repository *repo, const char *files[], size_t files_len) -{ - git_str buf = GIT_STR_INIT; - size_t i; - int error; - - for (error = 0, i = 0; !error && i < files_len; ++i) { - const char *path; - - if (git_str_joinpath(&buf, repo->gitdir, files[i]) < 0) - return -1; - - path = git_str_cstr(&buf); - - if (git_fs_path_isfile(path)) { - error = p_unlink(path); - } else if (git_fs_path_isdir(path)) { - error = git_futils_rmdir_r(path, NULL, - GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS); - } - - git_str_clear(&buf); - } - - git_str_dispose(&buf); - return error; -} - -static const char *state_files[] = { - GIT_MERGE_HEAD_FILE, - GIT_MERGE_MODE_FILE, - GIT_MERGE_MSG_FILE, - GIT_REVERT_HEAD_FILE, - GIT_CHERRYPICK_HEAD_FILE, - GIT_BISECT_LOG_FILE, - GIT_REBASE_MERGE_DIR, - GIT_REBASE_APPLY_DIR, - GIT_SEQUENCER_DIR, -}; - -int git_repository_state_cleanup(git_repository *repo) -{ - GIT_ASSERT_ARG(repo); - - return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); -} - -int git_repository_is_shallow(git_repository *repo) -{ - git_str path = GIT_STR_INIT; - struct stat st; - int error; - - if ((error = git_str_joinpath(&path, repo->gitdir, "shallow")) < 0) - return error; - - error = git_fs_path_lstat(path.ptr, &st); - git_str_dispose(&path); - - if (error == GIT_ENOTFOUND) { - git_error_clear(); - return 0; - } - - if (error < 0) - return error; - return st.st_size == 0 ? 0 : 1; -} - -int git_repository_init_options_init( - git_repository_init_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_repository_init_options, - GIT_REPOSITORY_INIT_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_repository_init_init_options( - git_repository_init_options *opts, unsigned int version) -{ - return git_repository_init_options_init(opts, version); -} -#endif - -int git_repository_ident(const char **name, const char **email, const git_repository *repo) -{ - *name = repo->ident_name; - *email = repo->ident_email; - - return 0; -} - -int git_repository_set_ident(git_repository *repo, const char *name, const char *email) -{ - char *tmp_name = NULL, *tmp_email = NULL; - - if (name) { - tmp_name = git__strdup(name); - GIT_ERROR_CHECK_ALLOC(tmp_name); - } - - if (email) { - tmp_email = git__strdup(email); - GIT_ERROR_CHECK_ALLOC(tmp_email); - } - - tmp_name = git_atomic_swap(repo->ident_name, tmp_name); - tmp_email = git_atomic_swap(repo->ident_email, tmp_email); - - git__free(tmp_name); - git__free(tmp_email); - - return 0; -} - -int git_repository_submodule_cache_all(git_repository *repo) -{ - GIT_ASSERT_ARG(repo); - return git_submodule_cache_init(&repo->submodule_cache, repo); -} - -int git_repository_submodule_cache_clear(git_repository *repo) -{ - int error = 0; - GIT_ASSERT_ARG(repo); - - error = git_submodule_cache_free(repo->submodule_cache); - repo->submodule_cache = NULL; - return error; -} diff --git a/src/repository.h b/src/repository.h deleted file mode 100644 index 3c3aa1e8e..000000000 --- a/src/repository.h +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_repository_h__ -#define INCLUDE_repository_h__ - -#include "common.h" - -#include "git2/common.h" -#include "git2/oid.h" -#include "git2/odb.h" -#include "git2/repository.h" -#include "git2/object.h" -#include "git2/config.h" - -#include "array.h" -#include "cache.h" -#include "refs.h" -#include "str.h" -#include "object.h" -#include "attrcache.h" -#include "submodule.h" -#include "diff_driver.h" - -#define DOT_GIT ".git" -#define GIT_DIR DOT_GIT "/" -#define GIT_DIR_MODE 0755 -#define GIT_BARE_DIR_MODE 0777 - -/* Default DOS-compatible 8.3 "short name" for a git repository, "GIT~1" */ -#define GIT_DIR_SHORTNAME "GIT~1" - -extern bool git_repository__fsync_gitdir; - -/** Cvar cache identifiers */ -typedef enum { - GIT_CONFIGMAP_AUTO_CRLF = 0, /* core.autocrlf */ - GIT_CONFIGMAP_EOL, /* core.eol */ - GIT_CONFIGMAP_SYMLINKS, /* core.symlinks */ - GIT_CONFIGMAP_IGNORECASE, /* core.ignorecase */ - GIT_CONFIGMAP_FILEMODE, /* core.filemode */ - GIT_CONFIGMAP_IGNORESTAT, /* core.ignorestat */ - GIT_CONFIGMAP_TRUSTCTIME, /* core.trustctime */ - GIT_CONFIGMAP_ABBREV, /* core.abbrev */ - GIT_CONFIGMAP_PRECOMPOSE, /* core.precomposeunicode */ - GIT_CONFIGMAP_SAFE_CRLF, /* core.safecrlf */ - GIT_CONFIGMAP_LOGALLREFUPDATES, /* core.logallrefupdates */ - GIT_CONFIGMAP_PROTECTHFS, /* core.protectHFS */ - GIT_CONFIGMAP_PROTECTNTFS, /* core.protectNTFS */ - GIT_CONFIGMAP_FSYNCOBJECTFILES, /* core.fsyncObjectFiles */ - GIT_CONFIGMAP_LONGPATHS, /* core.longpaths */ - GIT_CONFIGMAP_CACHE_MAX -} git_configmap_item; - -/** - * Configuration map value enumerations - * - * These are the values that are actually stored in the configmap cache, - * instead of their string equivalents. These values are internal and - * symbolic; make sure that none of them is set to `-1`, since that is - * the unique identifier for "not cached" - */ -typedef enum { - /* The value hasn't been loaded from the cache yet */ - GIT_CONFIGMAP_NOT_CACHED = -1, - - /* core.safecrlf: false, 'fail', 'warn' */ - GIT_SAFE_CRLF_FALSE = 0, - GIT_SAFE_CRLF_FAIL = 1, - GIT_SAFE_CRLF_WARN = 2, - - /* core.autocrlf: false, true, 'input; */ - GIT_AUTO_CRLF_FALSE = 0, - GIT_AUTO_CRLF_TRUE = 1, - GIT_AUTO_CRLF_INPUT = 2, - GIT_AUTO_CRLF_DEFAULT = GIT_AUTO_CRLF_FALSE, - - /* core.eol: unset, 'crlf', 'lf', 'native' */ - GIT_EOL_UNSET = 0, - GIT_EOL_CRLF = 1, - GIT_EOL_LF = 2, -#ifdef GIT_WIN32 - GIT_EOL_NATIVE = GIT_EOL_CRLF, -#else - GIT_EOL_NATIVE = GIT_EOL_LF, -#endif - GIT_EOL_DEFAULT = GIT_EOL_NATIVE, - - /* core.symlinks: bool */ - GIT_SYMLINKS_DEFAULT = GIT_CONFIGMAP_TRUE, - /* core.ignorecase */ - GIT_IGNORECASE_DEFAULT = GIT_CONFIGMAP_FALSE, - /* core.filemode */ - GIT_FILEMODE_DEFAULT = GIT_CONFIGMAP_TRUE, - /* core.ignorestat */ - GIT_IGNORESTAT_DEFAULT = GIT_CONFIGMAP_FALSE, - /* core.trustctime */ - GIT_TRUSTCTIME_DEFAULT = GIT_CONFIGMAP_TRUE, - /* core.abbrev */ - GIT_ABBREV_DEFAULT = 7, - /* core.precomposeunicode */ - GIT_PRECOMPOSE_DEFAULT = GIT_CONFIGMAP_FALSE, - /* core.safecrlf */ - GIT_SAFE_CRLF_DEFAULT = GIT_CONFIGMAP_FALSE, - /* core.logallrefupdates */ - GIT_LOGALLREFUPDATES_FALSE = GIT_CONFIGMAP_FALSE, - GIT_LOGALLREFUPDATES_TRUE = GIT_CONFIGMAP_TRUE, - GIT_LOGALLREFUPDATES_UNSET = 2, - GIT_LOGALLREFUPDATES_ALWAYS = 3, - GIT_LOGALLREFUPDATES_DEFAULT = GIT_LOGALLREFUPDATES_UNSET, - /* core.protectHFS */ - GIT_PROTECTHFS_DEFAULT = GIT_CONFIGMAP_FALSE, - /* core.protectNTFS */ - GIT_PROTECTNTFS_DEFAULT = GIT_CONFIGMAP_TRUE, - /* core.fsyncObjectFiles */ - GIT_FSYNCOBJECTFILES_DEFAULT = GIT_CONFIGMAP_FALSE, - /* core.longpaths */ - GIT_LONGPATHS_DEFAULT = GIT_CONFIGMAP_FALSE -} git_configmap_value; - -/* internal repository init flags */ -enum { - GIT_REPOSITORY_INIT__HAS_DOTGIT = (1u << 16), - GIT_REPOSITORY_INIT__NATURAL_WD = (1u << 17), - GIT_REPOSITORY_INIT__IS_REINIT = (1u << 18) -}; - -/** Internal structure for repository object */ -struct git_repository { - git_odb *_odb; - git_refdb *_refdb; - git_config *_config; - git_index *_index; - - git_cache objects; - git_attr_cache *attrcache; - git_diff_driver_registry *diff_drivers; - - char *gitlink; - char *gitdir; - char *commondir; - char *workdir; - char *namespace; - - char *ident_name; - char *ident_email; - - git_array_t(git_str) reserved_names; - - unsigned is_bare:1; - unsigned is_worktree:1; - - unsigned int lru_counter; - - git_atomic32 attr_session_key; - - intptr_t configmap_cache[GIT_CONFIGMAP_CACHE_MAX]; - git_strmap *submodule_cache; -}; - -GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) -{ - return repo->attrcache; -} - -int git_repository_head_tree(git_tree **tree, git_repository *repo); -int git_repository_create_head(const char *git_dir, const char *ref_name); - -typedef int (*git_repository_foreach_worktree_cb)(git_repository *, void *); - -int git_repository_foreach_worktree(git_repository *repo, - git_repository_foreach_worktree_cb cb, - void *payload); - -/* - * Weak pointers to repository internals. - * - * The returned pointers do not need to be freed. Do not keep - * permanent references to these (i.e. between API calls), since they may - * become invalidated if the user replaces a repository internal. - */ -int git_repository_config__weakptr(git_config **out, git_repository *repo); -int git_repository_odb__weakptr(git_odb **out, git_repository *repo); -int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo); -int git_repository_index__weakptr(git_index **out, git_repository *repo); - -/* - * Configuration map cache - * - * Efficient access to the most used config variables of a repository. - * The cache is cleared every time the config backend is replaced. - */ -int git_repository__configmap_lookup(int *out, git_repository *repo, git_configmap_item item); -void git_repository__configmap_lookup_cache_clear(git_repository *repo); - -int git_repository__item_path(git_str *out, const git_repository *repo, git_repository_item_t item); - -GIT_INLINE(int) git_repository__ensure_not_bare( - git_repository *repo, - const char *operation_name) -{ - if (!git_repository_is_bare(repo)) - return 0; - - git_error_set( - GIT_ERROR_REPOSITORY, - "cannot %s. This operation is not allowed against bare repositories.", - operation_name); - - return GIT_EBAREREPO; -} - -int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head); - -int git_repository__cleanup_files(git_repository *repo, const char *files[], size_t files_len); - -/* The default "reserved names" for a repository */ -extern git_str git_repository__reserved_names_win32[]; -extern size_t git_repository__reserved_names_win32_len; - -extern git_str git_repository__reserved_names_posix[]; -extern size_t git_repository__reserved_names_posix_len; - -/* - * Gets any "reserved names" in the repository. This will return paths - * that should not be allowed in the repository (like ".git") to avoid - * conflicting with the repository path, or with alternate mechanisms to - * the repository path (eg, "GIT~1"). Every attempt will be made to look - * up all possible reserved names - if there was a conflict for the shortname - * GIT~1, for example, this function will try to look up the alternate - * shortname. If that fails, this function returns false, but out and outlen - * will still be populated with good defaults. - */ -bool git_repository__reserved_names( - git_str **out, size_t *outlen, git_repository *repo, bool include_ntfs); - -/* - * The default branch for the repository; the `init.defaultBranch` - * configuration option, if set, or `master` if it is not. - */ -int git_repository_initialbranch(git_str *out, git_repository *repo); - -/* - * Given a relative `path`, this makes it absolute based on the - * repository's working directory. This will perform validation - * to ensure that the path is not longer than MAX_PATH on Windows - * (unless `core.longpaths` is set in the repo config). - */ -int git_repository_workdir_path(git_str *out, git_repository *repo, const char *path); - -int git_repository__extensions(char ***out, size_t *out_len); -int git_repository__set_extensions(const char **extensions, size_t len); -void git_repository__free_extensions(void); - -#endif diff --git a/src/reset.c b/src/reset.c deleted file mode 100644 index e0d942e5e..000000000 --- a/src/reset.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "commit.h" -#include "tag.h" -#include "merge.h" -#include "diff.h" -#include "annotated_commit.h" -#include "git2/reset.h" -#include "git2/checkout.h" -#include "git2/merge.h" -#include "git2/refs.h" - -#define ERROR_MSG "Cannot perform reset" - -int git_reset_default( - git_repository *repo, - const git_object *target, - const git_strarray *pathspecs) -{ - git_object *commit = NULL; - git_tree *tree = NULL; - git_diff *diff = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - size_t i, max_i; - git_index_entry entry; - int error; - git_index *index = NULL; - - GIT_ASSERT_ARG(pathspecs && pathspecs->count > 0); - - memset(&entry, 0, sizeof(git_index_entry)); - - if ((error = git_repository_index(&index, repo)) < 0) - goto cleanup; - - if (target) { - if (git_object_owner(target) != repo) { - git_error_set(GIT_ERROR_OBJECT, - "%s_default - The given target does not belong to this repository.", ERROR_MSG); - return -1; - } - - if ((error = git_object_peel(&commit, target, GIT_OBJECT_COMMIT)) < 0 || - (error = git_commit_tree(&tree, (git_commit *)commit)) < 0) - goto cleanup; - } - - opts.pathspec = *pathspecs; - opts.flags = GIT_DIFF_REVERSE; - - if ((error = git_diff_tree_to_index( - &diff, repo, tree, index, &opts)) < 0) - goto cleanup; - - for (i = 0, max_i = git_diff_num_deltas(diff); i < max_i; ++i) { - const git_diff_delta *delta = git_diff_get_delta(diff, i); - - GIT_ASSERT(delta->status == GIT_DELTA_ADDED || - delta->status == GIT_DELTA_MODIFIED || - delta->status == GIT_DELTA_CONFLICTED || - delta->status == GIT_DELTA_DELETED); - - error = git_index_conflict_remove(index, delta->old_file.path); - if (error < 0) { - if (delta->status == GIT_DELTA_ADDED && error == GIT_ENOTFOUND) - git_error_clear(); - else - goto cleanup; - } - - if (delta->status == GIT_DELTA_DELETED) { - if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0) - goto cleanup; - } else { - entry.mode = delta->new_file.mode; - git_oid_cpy(&entry.id, &delta->new_file.id); - entry.path = (char *)delta->new_file.path; - - if ((error = git_index_add(index, &entry)) < 0) - goto cleanup; - } - } - - error = git_index_write(index); - -cleanup: - git_object_free(commit); - git_tree_free(tree); - git_index_free(index); - git_diff_free(diff); - - return error; -} - -static int reset( - git_repository *repo, - const git_object *target, - const char *to, - git_reset_t reset_type, - const git_checkout_options *checkout_opts) -{ - git_object *commit = NULL; - git_index *index = NULL; - git_tree *tree = NULL; - int error = 0; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_str log_message = GIT_STR_INIT; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(target); - - if (checkout_opts) - opts = *checkout_opts; - - if (git_object_owner(target) != repo) { - git_error_set(GIT_ERROR_OBJECT, - "%s - The given target does not belong to this repository.", ERROR_MSG); - return -1; - } - - if (reset_type != GIT_RESET_SOFT && - (error = git_repository__ensure_not_bare(repo, - reset_type == GIT_RESET_MIXED ? "reset mixed" : "reset hard")) < 0) - return error; - - if ((error = git_object_peel(&commit, target, GIT_OBJECT_COMMIT)) < 0 || - (error = git_repository_index(&index, repo)) < 0 || - (error = git_commit_tree(&tree, (git_commit *)commit)) < 0) - goto cleanup; - - if (reset_type == GIT_RESET_SOFT && - (git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE || - git_index_has_conflicts(index))) - { - git_error_set(GIT_ERROR_OBJECT, "%s (soft) in the middle of a merge", ERROR_MSG); - error = GIT_EUNMERGED; - goto cleanup; - } - - if ((error = git_str_printf(&log_message, "reset: moving to %s", to)) < 0) - return error; - - if (reset_type == GIT_RESET_HARD) { - /* overwrite working directory with the new tree */ - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0) - goto cleanup; - } - - /* move HEAD to the new target */ - if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE, - git_object_id(commit), NULL, git_str_cstr(&log_message))) < 0) - goto cleanup; - - if (reset_type > GIT_RESET_SOFT) { - /* reset index to the target content */ - - if ((error = git_index_read_tree(index, tree)) < 0 || - (error = git_index_write(index)) < 0) - goto cleanup; - - if ((error = git_repository_state_cleanup(repo)) < 0) { - git_error_set(GIT_ERROR_INDEX, "%s - failed to clean up merge data", ERROR_MSG); - goto cleanup; - } - } - -cleanup: - git_object_free(commit); - git_index_free(index); - git_tree_free(tree); - git_str_dispose(&log_message); - - return error; -} - -int git_reset( - git_repository *repo, - const git_object *target, - git_reset_t reset_type, - const git_checkout_options *checkout_opts) -{ - char to[GIT_OID_HEXSZ + 1]; - - git_oid_tostr(to, GIT_OID_HEXSZ + 1, git_object_id(target)); - return reset(repo, target, to, reset_type, checkout_opts); -} - -int git_reset_from_annotated( - git_repository *repo, - const git_annotated_commit *commit, - git_reset_t reset_type, - const git_checkout_options *checkout_opts) -{ - return reset(repo, (git_object *) commit->commit, commit->description, reset_type, checkout_opts); -} diff --git a/src/revert.c b/src/revert.c deleted file mode 100644 index d6ab6ae3c..000000000 --- a/src/revert.c +++ /dev/null @@ -1,243 +0,0 @@ -/* -* Copyright (C) the libgit2 contributors. All rights reserved. -* -* This file is part of libgit2, distributed under the GNU GPL v2 with -* a Linking Exception. For full terms see the included COPYING file. -*/ - -#include "common.h" - -#include "repository.h" -#include "filebuf.h" -#include "merge.h" -#include "index.h" - -#include "git2/types.h" -#include "git2/merge.h" -#include "git2/revert.h" -#include "git2/commit.h" -#include "git2/sys/commit.h" - -#define GIT_REVERT_FILE_MODE 0666 - -static int write_revert_head( - git_repository *repo, - const char *commit_oidstr) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - int error = 0; - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_REVERT_HEAD_FILE)) >= 0 && - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_REVERT_FILE_MODE)) >= 0 && - (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0) - error = git_filebuf_commit(&file); - - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int write_merge_msg( - git_repository *repo, - const char *commit_oidstr, - const char *commit_msgline) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str file_path = GIT_STR_INIT; - int error = 0; - - if ((error = git_str_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_REVERT_FILE_MODE)) < 0 || - (error = git_filebuf_printf(&file, "Revert \"%s\"\n\nThis reverts commit %s.\n", - commit_msgline, commit_oidstr)) < 0) - goto cleanup; - - error = git_filebuf_commit(&file); - -cleanup: - if (error < 0) - git_filebuf_cleanup(&file); - - git_str_dispose(&file_path); - - return error; -} - -static int revert_normalize_opts( - git_repository *repo, - git_revert_options *opts, - const git_revert_options *given, - const char *their_label) -{ - int error = 0; - unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_ALLOW_CONFLICTS; - - GIT_UNUSED(repo); - - if (given != NULL) - memcpy(opts, given, sizeof(git_revert_options)); - else { - git_revert_options default_opts = GIT_REVERT_OPTIONS_INIT; - memcpy(opts, &default_opts, sizeof(git_revert_options)); - } - - if (!opts->checkout_opts.checkout_strategy) - opts->checkout_opts.checkout_strategy = default_checkout_strategy; - - if (!opts->checkout_opts.our_label) - opts->checkout_opts.our_label = "HEAD"; - - if (!opts->checkout_opts.their_label) - opts->checkout_opts.their_label = their_label; - - return error; -} - -static int revert_state_cleanup(git_repository *repo) -{ - const char *state_files[] = { GIT_REVERT_HEAD_FILE, GIT_MERGE_MSG_FILE }; - - return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files)); -} - -static int revert_seterr(git_commit *commit, const char *fmt) -{ - char commit_oidstr[GIT_OID_HEXSZ + 1]; - - git_oid_fmt(commit_oidstr, git_commit_id(commit)); - commit_oidstr[GIT_OID_HEXSZ] = '\0'; - - git_error_set(GIT_ERROR_REVERT, fmt, commit_oidstr); - - return -1; -} - -int git_revert_commit( - git_index **out, - git_repository *repo, - git_commit *revert_commit, - git_commit *our_commit, - unsigned int mainline, - const git_merge_options *merge_opts) -{ - git_commit *parent_commit = NULL; - git_tree *parent_tree = NULL, *our_tree = NULL, *revert_tree = NULL; - int parent = 0, error = 0; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(revert_commit); - GIT_ASSERT_ARG(our_commit); - - if (git_commit_parentcount(revert_commit) > 1) { - if (!mainline) - return revert_seterr(revert_commit, - "mainline branch is not specified but %s is a merge commit"); - - parent = mainline; - } else { - if (mainline) - return revert_seterr(revert_commit, - "mainline branch specified but %s is not a merge commit"); - - parent = git_commit_parentcount(revert_commit); - } - - if (parent && - ((error = git_commit_parent(&parent_commit, revert_commit, (parent - 1))) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0)) - goto done; - - if ((error = git_commit_tree(&revert_tree, revert_commit)) < 0 || - (error = git_commit_tree(&our_tree, our_commit)) < 0) - goto done; - - error = git_merge_trees(out, repo, revert_tree, our_tree, parent_tree, merge_opts); - -done: - git_tree_free(parent_tree); - git_tree_free(our_tree); - git_tree_free(revert_tree); - git_commit_free(parent_commit); - - return error; -} - -int git_revert( - git_repository *repo, - git_commit *commit, - const git_revert_options *given_opts) -{ - git_revert_options opts; - git_reference *our_ref = NULL; - git_commit *our_commit = NULL; - char commit_oidstr[GIT_OID_HEXSZ + 1]; - const char *commit_msg; - git_str their_label = GIT_STR_INIT; - git_index *index = NULL; - git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(commit); - - GIT_ERROR_CHECK_VERSION(given_opts, GIT_REVERT_OPTIONS_VERSION, "git_revert_options"); - - if ((error = git_repository__ensure_not_bare(repo, "revert")) < 0) - return error; - - git_oid_fmt(commit_oidstr, git_commit_id(commit)); - commit_oidstr[GIT_OID_HEXSZ] = '\0'; - - if ((commit_msg = git_commit_summary(commit)) == NULL) { - error = -1; - goto on_error; - } - - if ((error = git_str_printf(&their_label, "parent of %.7s... %s", commit_oidstr, commit_msg)) < 0 || - (error = revert_normalize_opts(repo, &opts, given_opts, git_str_cstr(&their_label))) < 0 || - (error = git_indexwriter_init_for_operation(&indexwriter, repo, &opts.checkout_opts.checkout_strategy)) < 0 || - (error = write_revert_head(repo, commit_oidstr)) < 0 || - (error = write_merge_msg(repo, commit_oidstr, commit_msg)) < 0 || - (error = git_repository_head(&our_ref, repo)) < 0 || - (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJECT_COMMIT)) < 0 || - (error = git_revert_commit(&index, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 || - (error = git_merge__check_result(repo, index)) < 0 || - (error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 || - (error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 || - (error = git_indexwriter_commit(&indexwriter)) < 0) - goto on_error; - - goto done; - -on_error: - revert_state_cleanup(repo); - -done: - git_indexwriter_cleanup(&indexwriter); - git_index_free(index); - git_commit_free(our_commit); - git_reference_free(our_ref); - git_str_dispose(&their_label); - - return error; -} - -int git_revert_options_init(git_revert_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_revert_options, GIT_REVERT_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_revert_init_options(git_revert_options *opts, unsigned int version) -{ - return git_revert_options_init(opts, version); -} -#endif diff --git a/src/revparse.c b/src/revparse.c deleted file mode 100644 index 9bc28e9fc..000000000 --- a/src/revparse.c +++ /dev/null @@ -1,949 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "str.h" -#include "tree.h" -#include "refdb.h" -#include "regexp.h" -#include "date.h" - -#include "git2.h" - -static int maybe_sha_or_abbrev(git_object **out, git_repository *repo, const char *spec, size_t speclen) -{ - git_oid oid; - - if (git_oid_fromstrn(&oid, spec, speclen) < 0) - return GIT_ENOTFOUND; - - return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJECT_ANY); -} - -static int maybe_sha(git_object **out, git_repository *repo, const char *spec) -{ - size_t speclen = strlen(spec); - - if (speclen != GIT_OID_HEXSZ) - return GIT_ENOTFOUND; - - return maybe_sha_or_abbrev(out, repo, spec, speclen); -} - -static int maybe_abbrev(git_object **out, git_repository *repo, const char *spec) -{ - size_t speclen = strlen(spec); - - return maybe_sha_or_abbrev(out, repo, spec, speclen); -} - -static int build_regex(git_regexp *regex, const char *pattern) -{ - int error; - - if (*pattern == '\0') { - git_error_set(GIT_ERROR_REGEX, "empty pattern"); - return GIT_EINVALIDSPEC; - } - - error = git_regexp_compile(regex, pattern, 0); - if (!error) - return 0; - - git_regexp_dispose(regex); - - return error; -} - -static int maybe_describe(git_object**out, git_repository *repo, const char *spec) -{ - const char *substr; - int error; - git_regexp regex; - - substr = strstr(spec, "-g"); - - if (substr == NULL) - return GIT_ENOTFOUND; - - if (build_regex(®ex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0) - return -1; - - error = git_regexp_match(®ex, spec); - git_regexp_dispose(®ex); - - if (error) - return GIT_ENOTFOUND; - - return maybe_abbrev(out, repo, substr+2); -} - -static int revparse_lookup_object( - git_object **object_out, - git_reference **reference_out, - git_repository *repo, - const char *spec) -{ - int error; - git_reference *ref; - - if ((error = maybe_sha(object_out, repo, spec)) != GIT_ENOTFOUND) - return error; - - error = git_reference_dwim(&ref, repo, spec); - if (!error) { - - error = git_object_lookup( - object_out, repo, git_reference_target(ref), GIT_OBJECT_ANY); - - if (!error) - *reference_out = ref; - - return error; - } - - if (error != GIT_ENOTFOUND) - return error; - - if ((strlen(spec) < GIT_OID_HEXSZ) && - ((error = maybe_abbrev(object_out, repo, spec)) != GIT_ENOTFOUND)) - return error; - - if ((error = maybe_describe(object_out, repo, spec)) != GIT_ENOTFOUND) - return error; - - git_error_set(GIT_ERROR_REFERENCE, "revspec '%s' not found", spec); - return GIT_ENOTFOUND; -} - -static int try_parse_numeric(int *n, const char *curly_braces_content) -{ - int32_t content; - const char *end_ptr; - - if (git__strntol32(&content, curly_braces_content, strlen(curly_braces_content), - &end_ptr, 10) < 0) - return -1; - - if (*end_ptr != '\0') - return -1; - - *n = (int)content; - return 0; -} - -static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position) -{ - git_reference *ref = NULL; - git_reflog *reflog = NULL; - git_regexp preg; - int error = -1; - size_t i, numentries, cur; - const git_reflog_entry *entry; - const char *msg; - git_str buf = GIT_STR_INIT; - - cur = position; - - if (*identifier != '\0' || *base_ref != NULL) - return GIT_EINVALIDSPEC; - - if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0) - return -1; - - if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) - goto cleanup; - - if (git_reflog_read(&reflog, repo, GIT_HEAD_FILE) < 0) - goto cleanup; - - numentries = git_reflog_entrycount(reflog); - - for (i = 0; i < numentries; i++) { - git_regmatch regexmatches[2]; - - entry = git_reflog_entry_byindex(reflog, i); - msg = git_reflog_entry_message(entry); - if (!msg) - continue; - - if (git_regexp_search(&preg, msg, 2, regexmatches) < 0) - continue; - - cur--; - - if (cur > 0) - continue; - - if ((git_str_put(&buf, msg+regexmatches[1].start, regexmatches[1].end - regexmatches[1].start)) < 0) - goto cleanup; - - if ((error = git_reference_dwim(base_ref, repo, git_str_cstr(&buf))) == 0) - goto cleanup; - - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - error = maybe_abbrev(out, repo, git_str_cstr(&buf)); - - goto cleanup; - } - - error = GIT_ENOTFOUND; - -cleanup: - git_reference_free(ref); - git_str_dispose(&buf); - git_regexp_dispose(&preg); - git_reflog_free(reflog); - return error; -} - -static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t identifier) -{ - git_reflog *reflog; - size_t numentries; - const git_reflog_entry *entry = NULL; - bool search_by_pos = (identifier <= 100000000); - - if (git_reflog_read(&reflog, git_reference_owner(ref), git_reference_name(ref)) < 0) - return -1; - - numentries = git_reflog_entrycount(reflog); - - if (search_by_pos) { - if (numentries < identifier + 1) - goto notfound; - - entry = git_reflog_entry_byindex(reflog, identifier); - git_oid_cpy(oid, git_reflog_entry_id_new(entry)); - } else { - size_t i; - git_time commit_time; - - for (i = 0; i < numentries; i++) { - entry = git_reflog_entry_byindex(reflog, i); - commit_time = git_reflog_entry_committer(entry)->when; - - if (commit_time.time > (git_time_t)identifier) - continue; - - git_oid_cpy(oid, git_reflog_entry_id_new(entry)); - break; - } - - if (i == numentries) { - if (entry == NULL) - goto notfound; - - /* - * TODO: emit a warning (log for 'branch' only goes back to ...) - */ - git_oid_cpy(oid, git_reflog_entry_id_new(entry)); - } - } - - git_reflog_free(reflog); - return 0; - -notfound: - git_error_set( - GIT_ERROR_REFERENCE, - "reflog for '%s' has only %"PRIuZ" entries, asked for %"PRIuZ, - git_reference_name(ref), numentries, identifier); - - git_reflog_free(reflog); - return GIT_ENOTFOUND; -} - -static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position) -{ - git_reference *ref; - git_oid oid; - int error = -1; - - if (*base_ref == NULL) { - if ((error = git_reference_dwim(&ref, repo, identifier)) < 0) - return error; - } else { - ref = *base_ref; - *base_ref = NULL; - } - - if (position == 0) { - error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJECT_ANY); - goto cleanup; - } - - if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0) - goto cleanup; - - error = git_object_lookup(out, repo, &oid, GIT_OBJECT_ANY); - -cleanup: - git_reference_free(ref); - return error; -} - -static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo) -{ - git_reference *tracking, *ref; - int error = -1; - - if (*base_ref == NULL) { - if ((error = git_reference_dwim(&ref, repo, identifier)) < 0) - return error; - } else { - ref = *base_ref; - *base_ref = NULL; - } - - if (!git_reference_is_branch(ref)) { - error = GIT_EINVALIDSPEC; - goto cleanup; - } - - if ((error = git_branch_upstream(&tracking, ref)) < 0) - goto cleanup; - - *base_ref = tracking; - -cleanup: - git_reference_free(ref); - return error; -} - -static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, size_t identifier_len, git_repository *repo, const char *curly_braces_content) -{ - bool is_numeric; - int parsed = 0, error = -1; - git_str identifier = GIT_STR_INIT; - git_time_t timestamp; - - GIT_ASSERT(*out == NULL); - - if (git_str_put(&identifier, spec, identifier_len) < 0) - return -1; - - is_numeric = !try_parse_numeric(&parsed, curly_braces_content); - - if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) { - error = GIT_EINVALIDSPEC; - goto cleanup; - } - - if (is_numeric) { - if (parsed < 0) - error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, git_str_cstr(&identifier), -parsed); - else - error = retrieve_revobject_from_reflog(out, ref, repo, git_str_cstr(&identifier), parsed); - - goto cleanup; - } - - if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) { - error = retrieve_remote_tracking_reference(ref, git_str_cstr(&identifier), repo); - - goto cleanup; - } - - if (git_date_parse(×tamp, curly_braces_content) < 0) { - error = GIT_EINVALIDSPEC; - goto cleanup; - } - - error = retrieve_revobject_from_reflog(out, ref, repo, git_str_cstr(&identifier), (size_t)timestamp); - -cleanup: - git_str_dispose(&identifier); - return error; -} - -static git_object_t parse_obj_type(const char *str) -{ - if (!strcmp(str, "commit")) - return GIT_OBJECT_COMMIT; - - if (!strcmp(str, "tree")) - return GIT_OBJECT_TREE; - - if (!strcmp(str, "blob")) - return GIT_OBJECT_BLOB; - - if (!strcmp(str, "tag")) - return GIT_OBJECT_TAG; - - return GIT_OBJECT_INVALID; -} - -static int dereference_to_non_tag(git_object **out, git_object *obj) -{ - if (git_object_type(obj) == GIT_OBJECT_TAG) - return git_tag_peel(out, (git_tag *)obj); - - return git_object_dup(out, obj); -} - -static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n) -{ - git_object *temp_commit = NULL; - int error; - - if ((error = git_object_peel(&temp_commit, obj, GIT_OBJECT_COMMIT)) < 0) - return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ? - GIT_EINVALIDSPEC : error; - - if (n == 0) { - *out = temp_commit; - return 0; - } - - error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1); - - git_object_free(temp_commit); - return error; -} - -static int handle_linear_syntax(git_object **out, git_object *obj, int n) -{ - git_object *temp_commit = NULL; - int error; - - if ((error = git_object_peel(&temp_commit, obj, GIT_OBJECT_COMMIT)) < 0) - return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ? - GIT_EINVALIDSPEC : error; - - error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n); - - git_object_free(temp_commit); - return error; -} - -static int handle_colon_syntax( - git_object **out, - git_object *obj, - const char *path) -{ - git_object *tree; - int error = -1; - git_tree_entry *entry = NULL; - - if ((error = git_object_peel(&tree, obj, GIT_OBJECT_TREE)) < 0) - return error == GIT_ENOTFOUND ? GIT_EINVALIDSPEC : error; - - if (*path == '\0') { - *out = tree; - return 0; - } - - /* - * TODO: Handle the relative path syntax - * (:./relative/path and :../relative/path) - */ - if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0) - goto cleanup; - - error = git_tree_entry_to_object(out, git_object_owner(tree), entry); - -cleanup: - git_tree_entry_free(entry); - git_object_free(tree); - - return error; -} - -static int walk_and_search(git_object **out, git_revwalk *walk, git_regexp *regex) -{ - int error; - git_oid oid; - git_object *obj; - - while (!(error = git_revwalk_next(&oid, walk))) { - - error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJECT_COMMIT); - if ((error < 0) && (error != GIT_ENOTFOUND)) - return -1; - - if (!git_regexp_match(regex, git_commit_message((git_commit*)obj))) { - *out = obj; - return 0; - } - - git_object_free(obj); - } - - if (error < 0 && error == GIT_ITEROVER) - error = GIT_ENOTFOUND; - - return error; -} - -static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern) -{ - git_regexp preg; - git_revwalk *walk = NULL; - int error; - - if ((error = build_regex(&preg, pattern)) < 0) - return error; - - if ((error = git_revwalk_new(&walk, repo)) < 0) - goto cleanup; - - git_revwalk_sorting(walk, GIT_SORT_TIME); - - if (spec_oid == NULL) { - if ((error = git_revwalk_push_glob(walk, "refs/*")) < 0) - goto cleanup; - } else if ((error = git_revwalk_push(walk, spec_oid)) < 0) - goto cleanup; - - error = walk_and_search(out, walk, &preg); - -cleanup: - git_regexp_dispose(&preg); - git_revwalk_free(walk); - - return error; -} - -static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content) -{ - git_object_t expected_type; - - if (*curly_braces_content == '\0') - return dereference_to_non_tag(out, obj); - - if (*curly_braces_content == '/') - return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1); - - expected_type = parse_obj_type(curly_braces_content); - - if (expected_type == GIT_OBJECT_INVALID) - return GIT_EINVALIDSPEC; - - return git_object_peel(out, obj, expected_type); -} - -static int extract_curly_braces_content(git_str *buf, const char *spec, size_t *pos) -{ - git_str_clear(buf); - - GIT_ASSERT_ARG(spec[*pos] == '^' || spec[*pos] == '@'); - - (*pos)++; - - if (spec[*pos] == '\0' || spec[*pos] != '{') - return GIT_EINVALIDSPEC; - - (*pos)++; - - while (spec[*pos] != '}') { - if (spec[*pos] == '\0') - return GIT_EINVALIDSPEC; - - if (git_str_putc(buf, spec[(*pos)++]) < 0) - return -1; - } - - (*pos)++; - - return 0; -} - -static int extract_path(git_str *buf, const char *spec, size_t *pos) -{ - git_str_clear(buf); - - GIT_ASSERT_ARG(spec[*pos] == ':'); - - (*pos)++; - - if (git_str_puts(buf, spec + *pos) < 0) - return -1; - - *pos += git_str_len(buf); - - return 0; -} - -static int extract_how_many(int *n, const char *spec, size_t *pos) -{ - const char *end_ptr; - int parsed, accumulated; - char kind = spec[*pos]; - - GIT_ASSERT_ARG(spec[*pos] == '^' || spec[*pos] == '~'); - - accumulated = 0; - - do { - do { - (*pos)++; - accumulated++; - } while (spec[(*pos)] == kind && kind == '~'); - - if (git__isdigit(spec[*pos])) { - if (git__strntol32(&parsed, spec + *pos, strlen(spec + *pos), &end_ptr, 10) < 0) - return GIT_EINVALIDSPEC; - - accumulated += (parsed - 1); - *pos = end_ptr - spec; - } - - } while (spec[(*pos)] == kind && kind == '~'); - - *n = accumulated; - - return 0; -} - -static int object_from_reference(git_object **object, git_reference *reference) -{ - git_reference *resolved = NULL; - int error; - - if (git_reference_resolve(&resolved, reference) < 0) - return -1; - - error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJECT_ANY); - git_reference_free(resolved); - - return error; -} - -static int ensure_base_rev_loaded(git_object **object, git_reference **reference, const char *spec, size_t identifier_len, git_repository *repo, bool allow_empty_identifier) -{ - int error; - git_str identifier = GIT_STR_INIT; - - if (*object != NULL) - return 0; - - if (*reference != NULL) - return object_from_reference(object, *reference); - - if (!allow_empty_identifier && identifier_len == 0) - return GIT_EINVALIDSPEC; - - if (git_str_put(&identifier, spec, identifier_len) < 0) - return -1; - - error = revparse_lookup_object(object, reference, repo, git_str_cstr(&identifier)); - git_str_dispose(&identifier); - - return error; -} - -static int ensure_base_rev_is_not_known_yet(git_object *object) -{ - if (object == NULL) - return 0; - - return GIT_EINVALIDSPEC; -} - -static bool any_left_hand_identifier(git_object *object, git_reference *reference, size_t identifier_len) -{ - if (object != NULL) - return true; - - if (reference != NULL) - return true; - - if (identifier_len > 0) - return true; - - return false; -} - -static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference) -{ - if (!ensure_base_rev_is_not_known_yet(object) && reference == NULL) - return 0; - - return GIT_EINVALIDSPEC; -} - -static int revparse( - git_object **object_out, - git_reference **reference_out, - size_t *identifier_len_out, - git_repository *repo, - const char *spec) -{ - size_t pos = 0, identifier_len = 0; - int error = -1, n; - git_str buf = GIT_STR_INIT; - - git_reference *reference = NULL; - git_object *base_rev = NULL; - - bool should_return_reference = true; - - GIT_ASSERT_ARG(object_out); - GIT_ASSERT_ARG(reference_out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(spec); - - *object_out = NULL; - *reference_out = NULL; - - while (spec[pos]) { - switch (spec[pos]) { - case '^': - should_return_reference = false; - - if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) - goto cleanup; - - if (spec[pos+1] == '{') { - git_object *temp_object = NULL; - - if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0) - goto cleanup; - - if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_str_cstr(&buf))) < 0) - goto cleanup; - - git_object_free(base_rev); - base_rev = temp_object; - } else { - git_object *temp_object = NULL; - - if ((error = extract_how_many(&n, spec, &pos)) < 0) - goto cleanup; - - if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0) - goto cleanup; - - git_object_free(base_rev); - base_rev = temp_object; - } - break; - - case '~': - { - git_object *temp_object = NULL; - - should_return_reference = false; - - if ((error = extract_how_many(&n, spec, &pos)) < 0) - goto cleanup; - - if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) - goto cleanup; - - if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0) - goto cleanup; - - git_object_free(base_rev); - base_rev = temp_object; - break; - } - - case ':': - { - git_object *temp_object = NULL; - - should_return_reference = false; - - if ((error = extract_path(&buf, spec, &pos)) < 0) - goto cleanup; - - if (any_left_hand_identifier(base_rev, reference, identifier_len)) { - if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0) - goto cleanup; - - if ((error = handle_colon_syntax(&temp_object, base_rev, git_str_cstr(&buf))) < 0) - goto cleanup; - } else { - if (*git_str_cstr(&buf) == '/') { - if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_str_cstr(&buf) + 1)) < 0) - goto cleanup; - } else { - - /* - * TODO: support merge-stage path lookup (":2:Makefile") - * and plain index blob lookup (:i-am/a/blob) - */ - git_error_set(GIT_ERROR_INVALID, "unimplemented"); - error = GIT_ERROR; - goto cleanup; - } - } - - git_object_free(base_rev); - base_rev = temp_object; - break; - } - - case '@': - if (spec[pos+1] == '{') { - git_object *temp_object = NULL; - - if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0) - goto cleanup; - - if ((error = ensure_base_rev_is_not_known_yet(base_rev)) < 0) - goto cleanup; - - if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_str_cstr(&buf))) < 0) - goto cleanup; - - if (temp_object != NULL) - base_rev = temp_object; - break; - } else if (spec[pos+1] == '\0') { - spec = "HEAD"; - break; - } - /* fall through */ - - default: - if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference)) < 0) - goto cleanup; - - pos++; - identifier_len++; - } - } - - if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) - goto cleanup; - - if (!should_return_reference) { - git_reference_free(reference); - reference = NULL; - } - - *object_out = base_rev; - *reference_out = reference; - *identifier_len_out = identifier_len; - error = 0; - -cleanup: - if (error) { - if (error == GIT_EINVALIDSPEC) - git_error_set(GIT_ERROR_INVALID, - "failed to parse revision specifier - Invalid pattern '%s'", spec); - - git_object_free(base_rev); - git_reference_free(reference); - } - - git_str_dispose(&buf); - return error; -} - -int git_revparse_ext( - git_object **object_out, - git_reference **reference_out, - git_repository *repo, - const char *spec) -{ - int error; - size_t identifier_len; - git_object *obj = NULL; - git_reference *ref = NULL; - - if ((error = revparse(&obj, &ref, &identifier_len, repo, spec)) < 0) - goto cleanup; - - *object_out = obj; - *reference_out = ref; - GIT_UNUSED(identifier_len); - - return 0; - -cleanup: - git_object_free(obj); - git_reference_free(ref); - return error; -} - -int git_revparse_single(git_object **out, git_repository *repo, const char *spec) -{ - int error; - git_object *obj = NULL; - git_reference *ref = NULL; - - *out = NULL; - - if ((error = git_revparse_ext(&obj, &ref, repo, spec)) < 0) - goto cleanup; - - git_reference_free(ref); - - *out = obj; - - return 0; - -cleanup: - git_object_free(obj); - git_reference_free(ref); - return error; -} - -int git_revparse( - git_revspec *revspec, - git_repository *repo, - const char *spec) -{ - const char *dotdot; - int error = 0; - - GIT_ASSERT_ARG(revspec); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(spec); - - memset(revspec, 0x0, sizeof(*revspec)); - - if ((dotdot = strstr(spec, "..")) != NULL) { - char *lstr; - const char *rstr; - revspec->flags = GIT_REVSPEC_RANGE; - - /* - * Following git.git, don't allow '..' because it makes command line - * arguments which can be either paths or revisions ambiguous when the - * path is almost certainly intended. The empty range '...' is still - * allowed. - */ - if (!git__strcmp(spec, "..")) { - git_error_set(GIT_ERROR_INVALID, "Invalid pattern '..'"); - return GIT_EINVALIDSPEC; - } - - lstr = git__substrdup(spec, dotdot - spec); - rstr = dotdot + 2; - if (dotdot[2] == '.') { - revspec->flags |= GIT_REVSPEC_MERGE_BASE; - rstr++; - } - - error = git_revparse_single( - &revspec->from, - repo, - *lstr == '\0' ? "HEAD" : lstr); - - if (!error) { - error = git_revparse_single( - &revspec->to, - repo, - *rstr == '\0' ? "HEAD" : rstr); - } - - git__free((void*)lstr); - } else { - revspec->flags = GIT_REVSPEC_SINGLE; - error = git_revparse_single(&revspec->from, repo, spec); - } - - return error; -} diff --git a/src/revwalk.c b/src/revwalk.c deleted file mode 100644 index 553e0497a..000000000 --- a/src/revwalk.c +++ /dev/null @@ -1,820 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "revwalk.h" - -#include "commit.h" -#include "odb.h" -#include "pool.h" - -#include "git2/revparse.h" -#include "merge.h" -#include "vector.h" - -static int get_revision(git_commit_list_node **out, git_revwalk *walk, git_commit_list **list); - -git_commit_list_node *git_revwalk__commit_lookup( - git_revwalk *walk, const git_oid *oid) -{ - git_commit_list_node *commit; - - /* lookup and reserve space if not already present */ - if ((commit = git_oidmap_get(walk->commits, oid)) != NULL) - return commit; - - commit = git_commit_list_alloc_node(walk); - if (commit == NULL) - return NULL; - - git_oid_cpy(&commit->oid, oid); - - if ((git_oidmap_set(walk->commits, &commit->oid, commit)) < 0) - return NULL; - - return commit; -} - -int git_revwalk__push_commit(git_revwalk *walk, const git_oid *oid, const git_revwalk__push_options *opts) -{ - git_oid commit_id; - int error; - git_object *obj, *oobj; - git_commit_list_node *commit; - git_commit_list *list; - - if ((error = git_object_lookup(&oobj, walk->repo, oid, GIT_OBJECT_ANY)) < 0) - return error; - - error = git_object_peel(&obj, oobj, GIT_OBJECT_COMMIT); - git_object_free(oobj); - - if (error == GIT_ENOTFOUND || error == GIT_EINVALIDSPEC || error == GIT_EPEEL) { - /* If this comes from e.g. push_glob("tags"), ignore this */ - if (opts->from_glob) - return 0; - - git_error_set(GIT_ERROR_INVALID, "object is not a committish"); - return error; - } - if (error < 0) - return error; - - git_oid_cpy(&commit_id, git_object_id(obj)); - git_object_free(obj); - - commit = git_revwalk__commit_lookup(walk, &commit_id); - if (commit == NULL) - return -1; /* error already reported by failed lookup */ - - /* A previous hide already told us we don't want this commit */ - if (commit->uninteresting) - return 0; - - if (opts->uninteresting) { - walk->limited = 1; - walk->did_hide = 1; - } else { - walk->did_push = 1; - } - - commit->uninteresting = opts->uninteresting; - list = walk->user_input; - if ((opts->insert_by_date && - git_commit_list_insert_by_date(commit, &list) == NULL) || - git_commit_list_insert(commit, &list) == NULL) { - git_error_set_oom(); - return -1; - } - - walk->user_input = list; - - return 0; -} - -int git_revwalk_push(git_revwalk *walk, const git_oid *oid) -{ - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(oid); - - return git_revwalk__push_commit(walk, oid, &opts); -} - - -int git_revwalk_hide(git_revwalk *walk, const git_oid *oid) -{ - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(oid); - - opts.uninteresting = 1; - return git_revwalk__push_commit(walk, oid, &opts); -} - -int git_revwalk__push_ref(git_revwalk *walk, const char *refname, const git_revwalk__push_options *opts) -{ - git_oid oid; - - if (git_reference_name_to_id(&oid, walk->repo, refname) < 0) - return -1; - - return git_revwalk__push_commit(walk, &oid, opts); -} - -int git_revwalk__push_glob(git_revwalk *walk, const char *glob, const git_revwalk__push_options *given_opts) -{ - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - int error = 0; - git_str buf = GIT_STR_INIT; - git_reference *ref; - git_reference_iterator *iter; - size_t wildcard; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(glob); - - if (given_opts) - memcpy(&opts, given_opts, sizeof(opts)); - - /* refs/ is implied if not given in the glob */ - if (git__prefixcmp(glob, GIT_REFS_DIR) != 0) - git_str_joinpath(&buf, GIT_REFS_DIR, glob); - else - git_str_puts(&buf, glob); - GIT_ERROR_CHECK_ALLOC_STR(&buf); - - /* If no '?', '*' or '[' exist, we append '/ *' to the glob */ - wildcard = strcspn(glob, "?*["); - if (!glob[wildcard]) - git_str_put(&buf, "/*", 2); - - if ((error = git_reference_iterator_glob_new(&iter, walk->repo, buf.ptr)) < 0) - goto out; - - opts.from_glob = true; - while ((error = git_reference_next(&ref, iter)) == 0) { - error = git_revwalk__push_ref(walk, git_reference_name(ref), &opts); - git_reference_free(ref); - if (error < 0) - break; - } - git_reference_iterator_free(iter); - - if (error == GIT_ITEROVER) - error = 0; -out: - git_str_dispose(&buf); - return error; -} - -int git_revwalk_push_glob(git_revwalk *walk, const char *glob) -{ - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(glob); - - return git_revwalk__push_glob(walk, glob, &opts); -} - -int git_revwalk_hide_glob(git_revwalk *walk, const char *glob) -{ - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(glob); - - opts.uninteresting = 1; - return git_revwalk__push_glob(walk, glob, &opts); -} - -int git_revwalk_push_head(git_revwalk *walk) -{ - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - - GIT_ASSERT_ARG(walk); - - return git_revwalk__push_ref(walk, GIT_HEAD_FILE, &opts); -} - -int git_revwalk_hide_head(git_revwalk *walk) -{ - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - - GIT_ASSERT_ARG(walk); - - opts.uninteresting = 1; - return git_revwalk__push_ref(walk, GIT_HEAD_FILE, &opts); -} - -int git_revwalk_push_ref(git_revwalk *walk, const char *refname) -{ - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(refname); - - return git_revwalk__push_ref(walk, refname, &opts); -} - -int git_revwalk_push_range(git_revwalk *walk, const char *range) -{ - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - git_revspec revspec; - int error = 0; - - if ((error = git_revparse(&revspec, walk->repo, range))) - return error; - - if (!revspec.to) { - git_error_set(GIT_ERROR_INVALID, "invalid revspec: range not provided"); - error = GIT_EINVALIDSPEC; - goto out; - } - - if (revspec.flags & GIT_REVSPEC_MERGE_BASE) { - /* TODO: support "..." */ - git_error_set(GIT_ERROR_INVALID, "symmetric differences not implemented in revwalk"); - error = GIT_EINVALIDSPEC; - goto out; - } - - opts.uninteresting = 1; - if ((error = git_revwalk__push_commit(walk, git_object_id(revspec.from), &opts))) - goto out; - - opts.uninteresting = 0; - error = git_revwalk__push_commit(walk, git_object_id(revspec.to), &opts); - -out: - git_object_free(revspec.from); - git_object_free(revspec.to); - return error; -} - -int git_revwalk_hide_ref(git_revwalk *walk, const char *refname) -{ - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(refname); - - opts.uninteresting = 1; - return git_revwalk__push_ref(walk, refname, &opts); -} - -static int revwalk_enqueue_timesort(git_revwalk *walk, git_commit_list_node *commit) -{ - return git_pqueue_insert(&walk->iterator_time, commit); -} - -static int revwalk_enqueue_unsorted(git_revwalk *walk, git_commit_list_node *commit) -{ - return git_commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1; -} - -static int revwalk_next_timesort(git_commit_list_node **object_out, git_revwalk *walk) -{ - git_commit_list_node *next; - - while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) { - /* Some commits might become uninteresting after being added to the list */ - if (!next->uninteresting) { - *object_out = next; - return 0; - } - } - - git_error_clear(); - return GIT_ITEROVER; -} - -static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk *walk) -{ - int error; - git_commit_list_node *next; - - while (!(error = get_revision(&next, walk, &walk->iterator_rand))) { - /* Some commits might become uninteresting after being added to the list */ - if (!next->uninteresting) { - *object_out = next; - return 0; - } - } - - return error; -} - -static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk *walk) -{ - int error; - git_commit_list_node *next; - - while (!(error = get_revision(&next, walk, &walk->iterator_topo))) { - /* Some commits might become uninteresting after being added to the list */ - if (!next->uninteresting) { - *object_out = next; - return 0; - } - } - - return error; -} - -static int revwalk_next_reverse(git_commit_list_node **object_out, git_revwalk *walk) -{ - *object_out = git_commit_list_pop(&walk->iterator_reverse); - return *object_out ? 0 : GIT_ITEROVER; -} - -static void mark_parents_uninteresting(git_commit_list_node *commit) -{ - unsigned short i; - git_commit_list *parents = NULL; - - for (i = 0; i < commit->out_degree; i++) - git_commit_list_insert(commit->parents[i], &parents); - - - while (parents) { - commit = git_commit_list_pop(&parents); - - while (commit) { - if (commit->uninteresting) - break; - - commit->uninteresting = 1; - /* - * If we've reached this commit some other way - * already, we need to mark its parents uninteresting - * as well. - */ - if (!commit->parents) - break; - - for (i = 0; i < commit->out_degree; i++) - git_commit_list_insert(commit->parents[i], &parents); - commit = commit->parents[0]; - } - } -} - -static int add_parents_to_list(git_revwalk *walk, git_commit_list_node *commit, git_commit_list **list) -{ - unsigned short i; - int error; - - if (commit->added) - return 0; - - commit->added = 1; - - /* - * Go full on in the uninteresting case as we want to include - * as many of these as we can. - * - * Usually we haven't parsed the parent of a parent, but if we - * have it we reached it via other means so we want to mark - * its parents recursively too. - */ - if (commit->uninteresting) { - for (i = 0; i < commit->out_degree; i++) { - git_commit_list_node *p = commit->parents[i]; - p->uninteresting = 1; - - /* git does it gently here, but we don't like missing objects */ - if ((error = git_commit_list_parse(walk, p)) < 0) - return error; - - if (p->parents) - mark_parents_uninteresting(p); - - p->seen = 1; - git_commit_list_insert_by_date(p, list); - } - - return 0; - } - - /* - * Now on to what we do if the commit is indeed - * interesting. Here we do want things like first-parent take - * effect as this is what we'll be showing. - */ - for (i = 0; i < commit->out_degree; i++) { - git_commit_list_node *p = commit->parents[i]; - - if ((error = git_commit_list_parse(walk, p)) < 0) - return error; - - if (walk->hide_cb && walk->hide_cb(&p->oid, walk->hide_cb_payload)) - continue; - - if (!p->seen) { - p->seen = 1; - git_commit_list_insert_by_date(p, list); - } - - if (walk->first_parent) - break; - } - return 0; -} - -/* How many uninteresting commits we want to look at after we run out of interesting ones */ -#define SLOP 5 - -static int still_interesting(git_commit_list *list, int64_t time, int slop) -{ - /* The empty list is pretty boring */ - if (!list) - return 0; - - /* - * If the destination list has commits with an earlier date than our - * source, we want to reset the slop counter as we're not done. - */ - if (time <= list->item->time) - return SLOP; - - for (; list; list = list->next) { - /* - * If the destination list still contains interesting commits we - * want to continue looking. - */ - if (!list->item->uninteresting || list->item->time > time) - return SLOP; - } - - /* Everything's uninteresting, reduce the count */ - return slop - 1; -} - -static int limit_list(git_commit_list **out, git_revwalk *walk, git_commit_list *commits) -{ - int error, slop = SLOP; - int64_t time = INT64_MAX; - git_commit_list *list = commits; - git_commit_list *newlist = NULL; - git_commit_list **p = &newlist; - - while (list) { - git_commit_list_node *commit = git_commit_list_pop(&list); - - if ((error = add_parents_to_list(walk, commit, &list)) < 0) - return error; - - if (commit->uninteresting) { - mark_parents_uninteresting(commit); - - slop = still_interesting(list, time, slop); - if (slop) - continue; - - break; - } - - if (walk->hide_cb && walk->hide_cb(&commit->oid, walk->hide_cb_payload)) - continue; - - time = commit->time; - p = &git_commit_list_insert(commit, p)->next; - } - - git_commit_list_free(&list); - *out = newlist; - return 0; -} - -static int get_revision(git_commit_list_node **out, git_revwalk *walk, git_commit_list **list) -{ - int error; - git_commit_list_node *commit; - - commit = git_commit_list_pop(list); - if (!commit) { - git_error_clear(); - return GIT_ITEROVER; - } - - /* - * If we did not run limit_list and we must add parents to the - * list ourselves. - */ - if (!walk->limited) { - if ((error = add_parents_to_list(walk, commit, list)) < 0) - return error; - } - - *out = commit; - return 0; -} - -static int sort_in_topological_order(git_commit_list **out, git_revwalk *walk, git_commit_list *list) -{ - git_commit_list *ll = NULL, *newlist, **pptr; - git_commit_list_node *next; - git_pqueue queue; - git_vector_cmp queue_cmp = NULL; - unsigned short i; - int error; - - if (walk->sorting & GIT_SORT_TIME) - queue_cmp = git_commit_list_time_cmp; - - if ((error = git_pqueue_init(&queue, 0, 8, queue_cmp))) - return error; - - /* - * Start by resetting the in-degree to 1 for the commits in - * our list. We want to go through this list again, so we - * store it in the commit list as we extract it from the lower - * machinery. - */ - for (ll = list; ll; ll = ll->next) { - ll->item->in_degree = 1; - } - - /* - * Count up how many children each commit has. We limit - * ourselves to those commits in the original list (in-degree - * of 1) avoiding setting it for any parent that was hidden. - */ - for(ll = list; ll; ll = ll->next) { - for (i = 0; i < ll->item->out_degree; ++i) { - git_commit_list_node *parent = ll->item->parents[i]; - if (parent->in_degree) - parent->in_degree++; - } - } - - /* - * Now we find the tips i.e. those not reachable from any other node - * i.e. those which still have an in-degree of 1. - */ - for(ll = list; ll; ll = ll->next) { - if (ll->item->in_degree == 1) { - if ((error = git_pqueue_insert(&queue, ll->item))) - goto cleanup; - } - } - - /* - * We need to output the tips in the order that they came out of the - * traversal, so if we're not doing time-sorting, we need to reverse the - * pqueue in order to get them to come out as we inserted them. - */ - if ((walk->sorting & GIT_SORT_TIME) == 0) - git_pqueue_reverse(&queue); - - - pptr = &newlist; - newlist = NULL; - while ((next = git_pqueue_pop(&queue)) != NULL) { - for (i = 0; i < next->out_degree; ++i) { - git_commit_list_node *parent = next->parents[i]; - if (parent->in_degree == 0) - continue; - - if (--parent->in_degree == 1) { - if ((error = git_pqueue_insert(&queue, parent))) - goto cleanup; - } - } - - /* All the children of 'item' have been emitted (since we got to it via the priority queue) */ - next->in_degree = 0; - - pptr = &git_commit_list_insert(next, pptr)->next; - } - - *out = newlist; - error = 0; - -cleanup: - git_pqueue_free(&queue); - return error; -} - -static int prepare_walk(git_revwalk *walk) -{ - int error = 0; - git_commit_list *list, *commits = NULL; - git_commit_list_node *next; - - /* If there were no pushes, we know that the walk is already over */ - if (!walk->did_push) { - git_error_clear(); - return GIT_ITEROVER; - } - - for (list = walk->user_input; list; list = list->next) { - git_commit_list_node *commit = list->item; - if ((error = git_commit_list_parse(walk, commit)) < 0) - return error; - - if (commit->uninteresting) - mark_parents_uninteresting(commit); - - if (!commit->seen) { - commit->seen = 1; - git_commit_list_insert(commit, &commits); - } - } - - if (walk->limited && (error = limit_list(&commits, walk, commits)) < 0) - return error; - - if (walk->sorting & GIT_SORT_TOPOLOGICAL) { - error = sort_in_topological_order(&walk->iterator_topo, walk, commits); - git_commit_list_free(&commits); - - if (error < 0) - return error; - - walk->get_next = &revwalk_next_toposort; - } else if (walk->sorting & GIT_SORT_TIME) { - for (list = commits; list && !error; list = list->next) - error = walk->enqueue(walk, list->item); - - git_commit_list_free(&commits); - - if (error < 0) - return error; - } else { - walk->iterator_rand = commits; - walk->get_next = revwalk_next_unsorted; - } - - if (walk->sorting & GIT_SORT_REVERSE) { - - while ((error = walk->get_next(&next, walk)) == 0) - if (git_commit_list_insert(next, &walk->iterator_reverse) == NULL) - return -1; - - if (error != GIT_ITEROVER) - return error; - - walk->get_next = &revwalk_next_reverse; - } - - walk->walking = 1; - return 0; -} - - -int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo) -{ - git_revwalk *walk = git__calloc(1, sizeof(git_revwalk)); - GIT_ERROR_CHECK_ALLOC(walk); - - if (git_oidmap_new(&walk->commits) < 0 || - git_pqueue_init(&walk->iterator_time, 0, 8, git_commit_list_time_cmp) < 0 || - git_pool_init(&walk->commit_pool, COMMIT_ALLOC) < 0) - return -1; - - walk->get_next = &revwalk_next_unsorted; - walk->enqueue = &revwalk_enqueue_unsorted; - - walk->repo = repo; - - if (git_repository_odb(&walk->odb, repo) < 0) { - git_revwalk_free(walk); - return -1; - } - - *revwalk_out = walk; - return 0; -} - -void git_revwalk_free(git_revwalk *walk) -{ - if (walk == NULL) - return; - - git_revwalk_reset(walk); - git_odb_free(walk->odb); - - git_oidmap_free(walk->commits); - git_pool_clear(&walk->commit_pool); - git_pqueue_free(&walk->iterator_time); - git__free(walk); -} - -git_repository *git_revwalk_repository(git_revwalk *walk) -{ - GIT_ASSERT_ARG_WITH_RETVAL(walk, NULL); - - return walk->repo; -} - -int git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode) -{ - GIT_ASSERT_ARG(walk); - - if (walk->walking) - git_revwalk_reset(walk); - - walk->sorting = sort_mode; - - if (walk->sorting & GIT_SORT_TIME) { - walk->get_next = &revwalk_next_timesort; - walk->enqueue = &revwalk_enqueue_timesort; - } else { - walk->get_next = &revwalk_next_unsorted; - walk->enqueue = &revwalk_enqueue_unsorted; - } - - if (walk->sorting != GIT_SORT_NONE) - walk->limited = 1; - - return 0; -} - -int git_revwalk_simplify_first_parent(git_revwalk *walk) -{ - walk->first_parent = 1; - return 0; -} - -int git_revwalk_next(git_oid *oid, git_revwalk *walk) -{ - int error; - git_commit_list_node *next; - - GIT_ASSERT_ARG(walk); - GIT_ASSERT_ARG(oid); - - if (!walk->walking) { - if ((error = prepare_walk(walk)) < 0) - return error; - } - - error = walk->get_next(&next, walk); - - if (error == GIT_ITEROVER) { - git_revwalk_reset(walk); - git_error_clear(); - return GIT_ITEROVER; - } - - if (!error) - git_oid_cpy(oid, &next->oid); - - return error; -} - -int git_revwalk_reset(git_revwalk *walk) -{ - git_commit_list_node *commit; - - GIT_ASSERT_ARG(walk); - - git_oidmap_foreach_value(walk->commits, commit, { - commit->seen = 0; - commit->in_degree = 0; - commit->topo_delay = 0; - commit->uninteresting = 0; - commit->added = 0; - commit->flags = 0; - }); - - git_pqueue_clear(&walk->iterator_time); - git_commit_list_free(&walk->iterator_topo); - git_commit_list_free(&walk->iterator_rand); - git_commit_list_free(&walk->iterator_reverse); - git_commit_list_free(&walk->user_input); - walk->first_parent = 0; - walk->walking = 0; - walk->limited = 0; - walk->did_push = walk->did_hide = 0; - walk->sorting = GIT_SORT_NONE; - - return 0; -} - -int git_revwalk_add_hide_cb( - git_revwalk *walk, - git_revwalk_hide_cb hide_cb, - void *payload) -{ - GIT_ASSERT_ARG(walk); - - if (walk->walking) - git_revwalk_reset(walk); - - walk->hide_cb = hide_cb; - walk->hide_cb_payload = payload; - - if (hide_cb) - walk->limited = 1; - - return 0; -} - diff --git a/src/revwalk.h b/src/revwalk.h deleted file mode 100644 index 94b8a6fb1..000000000 --- a/src/revwalk.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_revwalk_h__ -#define INCLUDE_revwalk_h__ - -#include "common.h" - -#include "git2/revwalk.h" -#include "oidmap.h" -#include "commit_list.h" -#include "pqueue.h" -#include "pool.h" -#include "vector.h" - -#include "oidmap.h" - -struct git_revwalk { - git_repository *repo; - git_odb *odb; - - git_oidmap *commits; - git_pool commit_pool; - - git_commit_list *iterator_topo; - git_commit_list *iterator_rand; - git_commit_list *iterator_reverse; - git_pqueue iterator_time; - - int (*get_next)(git_commit_list_node **, git_revwalk *); - int (*enqueue)(git_revwalk *, git_commit_list_node *); - - unsigned walking:1, - first_parent: 1, - did_hide: 1, - did_push: 1, - limited: 1; - unsigned int sorting; - - /* the pushes and hides */ - git_commit_list *user_input; - - /* hide callback */ - git_revwalk_hide_cb hide_cb; - void *hide_cb_payload; -}; - -git_commit_list_node *git_revwalk__commit_lookup(git_revwalk *walk, const git_oid *oid); - -typedef struct { - int uninteresting; - int from_glob; - int insert_by_date; -} git_revwalk__push_options; - -#define GIT_REVWALK__PUSH_OPTIONS_INIT { 0 } - -int git_revwalk__push_commit(git_revwalk *walk, - const git_oid *oid, - const git_revwalk__push_options *opts); - -int git_revwalk__push_ref(git_revwalk *walk, - const char *refname, - const git_revwalk__push_options *opts); - -int git_revwalk__push_glob(git_revwalk *walk, - const char *glob, - const git_revwalk__push_options *given_opts); - -#endif diff --git a/src/runtime.c b/src/runtime.c deleted file mode 100644 index c05dee8b9..000000000 --- a/src/runtime.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" -#include "runtime.h" - -static git_runtime_shutdown_fn shutdown_callback[32]; -static git_atomic32 shutdown_callback_count; - -static git_atomic32 init_count; - -static int init_common(git_runtime_init_fn init_fns[], size_t cnt) -{ - size_t i; - int ret; - - /* Initialize subsystems that have global state */ - for (i = 0; i < cnt; i++) { - if ((ret = init_fns[i]()) != 0) - break; - } - - GIT_MEMORY_BARRIER; - - return ret; -} - -static void shutdown_common(void) -{ - git_runtime_shutdown_fn cb; - int pos; - - for (pos = git_atomic32_get(&shutdown_callback_count); - pos > 0; - pos = git_atomic32_dec(&shutdown_callback_count)) { - cb = git_atomic_swap(shutdown_callback[pos - 1], NULL); - - if (cb != NULL) - cb(); - } -} - -int git_runtime_shutdown_register(git_runtime_shutdown_fn callback) -{ - int count = git_atomic32_inc(&shutdown_callback_count); - - if (count > (int)ARRAY_SIZE(shutdown_callback) || count == 0) { - git_error_set(GIT_ERROR_INVALID, - "too many shutdown callbacks registered"); - git_atomic32_dec(&shutdown_callback_count); - return -1; - } - - shutdown_callback[count - 1] = callback; - - return 0; -} - -#if defined(GIT_THREADS) && defined(GIT_WIN32) - -/* - * On Win32, we use a spinlock to provide locking semantics. This is - * lighter-weight than a proper critical section. - */ -static volatile LONG init_spinlock = 0; - -GIT_INLINE(int) init_lock(void) -{ - while (InterlockedCompareExchange(&init_spinlock, 1, 0)) { Sleep(0); } - return 0; -} - -GIT_INLINE(int) init_unlock(void) -{ - InterlockedExchange(&init_spinlock, 0); - return 0; -} - -#elif defined(GIT_THREADS) && defined(_POSIX_THREADS) - -/* - * On POSIX, we need to use a proper mutex for locking. We might prefer - * a spinlock here, too, but there's no static initializer for a - * pthread_spinlock_t. - */ -static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER; - -GIT_INLINE(int) init_lock(void) -{ - return pthread_mutex_lock(&init_mutex) == 0 ? 0 : -1; -} - -GIT_INLINE(int) init_unlock(void) -{ - return pthread_mutex_unlock(&init_mutex) == 0 ? 0 : -1; -} - -#elif defined(GIT_THREADS) -# error unknown threading model -#else - -# define init_lock() git__noop() -# define init_unlock() git__noop() - -#endif - -int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt) -{ - int ret; - - if (init_lock() < 0) - return -1; - - /* Only do work on a 0 -> 1 transition of the refcount */ - if ((ret = git_atomic32_inc(&init_count)) == 1) { - if (init_common(init_fns, cnt) < 0) - ret = -1; - } - - if (init_unlock() < 0) - return -1; - - return ret; -} - -int git_runtime_init_count(void) -{ - int ret; - - if (init_lock() < 0) - return -1; - - ret = git_atomic32_get(&init_count); - - if (init_unlock() < 0) - return -1; - - return ret; -} - -int git_runtime_shutdown(void) -{ - int ret; - - /* Enter the lock */ - if (init_lock() < 0) - return -1; - - /* Only do work on a 1 -> 0 transition of the refcount */ - if ((ret = git_atomic32_dec(&init_count)) == 0) - shutdown_common(); - - /* Exit the lock */ - if (init_unlock() < 0) - return -1; - - return ret; -} diff --git a/src/runtime.h b/src/runtime.h deleted file mode 100644 index 24ac58ee9..000000000 --- a/src/runtime.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_runtime_h__ -#define INCLUDE_runtime_h__ - -#include "common.h" - -typedef int (*git_runtime_init_fn)(void); -typedef void (*git_runtime_shutdown_fn)(void); - -/** - * Start up a new runtime. If this is the first time that this - * function is called within the context of the current library - * or executable, then the given `init_fns` will be invoked. If - * it is not the first time, they will be ignored. - * - * The given initialization functions _may_ register shutdown - * handlers using `git_runtime_shutdown_register` to be notified - * when the runtime is shutdown. - * - * @param init_fns The list of initialization functions to call - * @param cnt The number of init_fns - * @return The number of initializations performed (including this one) or an error - */ -int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt); - -/* - * Returns the number of initializations active (the number of calls to - * `git_runtime_init` minus the number of calls sto `git_runtime_shutdown`). - * If 0, the runtime is not currently initialized. - * - * @return The number of initializations performed or an error - */ -int git_runtime_init_count(void); - -/** - * Shut down the runtime. If this is the last shutdown call, - * such that there are no remaining `init` calls, then any - * shutdown hooks that have been registered will be invoked. - * - * The number of outstanding initializations will be returned. - * If this number is 0, then the runtime is shutdown. - * - * @return The number of outstanding initializations (after this one) or an error - */ -int git_runtime_shutdown(void); - -/** - * Register a shutdown handler for this runtime. This should be done - * by a function invoked by `git_runtime_init` to ensure that the - * appropriate locks are taken. - * - * @param callback The shutdown handler callback - * @return 0 or an error code - */ -int git_runtime_shutdown_register(git_runtime_shutdown_fn callback); - -#endif diff --git a/src/settings.h b/src/settings.h deleted file mode 100644 index dc42ce939..000000000 --- a/src/settings.h +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -extern int git_settings_global_init(void); - -extern const char *git_libgit2__user_agent(void); -extern const char *git_libgit2__ssl_ciphers(void); diff --git a/src/signature.c b/src/signature.c deleted file mode 100644 index 5d6ab572c..000000000 --- a/src/signature.c +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "signature.h" - -#include "repository.h" -#include "git2/common.h" -#include "posix.h" - -void git_signature_free(git_signature *sig) -{ - if (sig == NULL) - return; - - git__free(sig->name); - sig->name = NULL; - git__free(sig->email); - sig->email = NULL; - git__free(sig); -} - -static int signature_parse_error(const char *msg) -{ - git_error_set(GIT_ERROR_INVALID, "failed to parse signature - %s", msg); - return GIT_EINVALID; -} - -static int signature_error(const char *msg) -{ - git_error_set(GIT_ERROR_INVALID, "failed to parse signature - %s", msg); - return -1; -} - -static bool contains_angle_brackets(const char *input) -{ - return strchr(input, '<') != NULL || strchr(input, '>') != NULL; -} - -static bool is_crud(unsigned char c) -{ - return c <= 32 || - c == '.' || - c == ',' || - c == ':' || - c == ';' || - c == '<' || - c == '>' || - c == '"' || - c == '\\' || - c == '\''; -} - -static char *extract_trimmed(const char *ptr, size_t len) -{ - while (len && is_crud((unsigned char)ptr[0])) { - ptr++; len--; - } - - while (len && is_crud((unsigned char)ptr[len - 1])) { - len--; - } - - return git__substrdup(ptr, len); -} - -int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset) -{ - git_signature *p = NULL; - - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(email); - - *sig_out = NULL; - - if (contains_angle_brackets(name) || - contains_angle_brackets(email)) { - return signature_error( - "Neither `name` nor `email` should contain angle brackets chars."); - } - - p = git__calloc(1, sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(p); - - p->name = extract_trimmed(name, strlen(name)); - GIT_ERROR_CHECK_ALLOC(p->name); - p->email = extract_trimmed(email, strlen(email)); - GIT_ERROR_CHECK_ALLOC(p->email); - - if (p->name[0] == '\0' || p->email[0] == '\0') { - git_signature_free(p); - return signature_error("Signature cannot have an empty name or email"); - } - - p->when.time = time; - p->when.offset = offset; - p->when.sign = (offset < 0) ? '-' : '+'; - - *sig_out = p; - return 0; -} - -int git_signature_dup(git_signature **dest, const git_signature *source) -{ - git_signature *signature; - - if (source == NULL) - return 0; - - signature = git__calloc(1, sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(signature); - - signature->name = git__strdup(source->name); - GIT_ERROR_CHECK_ALLOC(signature->name); - - signature->email = git__strdup(source->email); - GIT_ERROR_CHECK_ALLOC(signature->email); - - signature->when.time = source->when.time; - signature->when.offset = source->when.offset; - signature->when.sign = source->when.sign; - - *dest = signature; - - return 0; -} - -int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool) -{ - git_signature *signature; - - if (source == NULL) - return 0; - - signature = git_pool_mallocz(pool, sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(signature); - - signature->name = git_pool_strdup(pool, source->name); - GIT_ERROR_CHECK_ALLOC(signature->name); - - signature->email = git_pool_strdup(pool, source->email); - GIT_ERROR_CHECK_ALLOC(signature->email); - - signature->when.time = source->when.time; - signature->when.offset = source->when.offset; - signature->when.sign = source->when.sign; - - *dest = signature; - - return 0; -} - -int git_signature_now(git_signature **sig_out, const char *name, const char *email) -{ - time_t now; - time_t offset; - struct tm *utc_tm; - git_signature *sig; - struct tm _utc; - - *sig_out = NULL; - - /* - * Get the current time as seconds since the epoch and - * transform that into a tm struct containing the time at - * UTC. Give that to mktime which considers it a local time - * (tm_isdst = -1 asks it to take DST into account) and gives - * us that time as seconds since the epoch. The difference - * between its return value and 'now' is our offset to UTC. - */ - time(&now); - utc_tm = p_gmtime_r(&now, &_utc); - utc_tm->tm_isdst = -1; - offset = (time_t)difftime(now, mktime(utc_tm)); - offset /= 60; - - if (git_signature_new(&sig, name, email, now, (int)offset) < 0) - return -1; - - *sig_out = sig; - - return 0; -} - -int git_signature_default(git_signature **out, git_repository *repo) -{ - int error; - git_config *cfg; - const char *user_name, *user_email; - - if ((error = git_repository_config_snapshot(&cfg, repo)) < 0) - return error; - - if (!(error = git_config_get_string(&user_name, cfg, "user.name")) && - !(error = git_config_get_string(&user_email, cfg, "user.email"))) - error = git_signature_now(out, user_name, user_email); - - git_config_free(cfg); - return error; -} - -int git_signature__parse(git_signature *sig, const char **buffer_out, - const char *buffer_end, const char *header, char ender) -{ - const char *buffer = *buffer_out; - const char *email_start, *email_end; - - memset(sig, 0, sizeof(git_signature)); - - if (ender && - (buffer_end = memchr(buffer, ender, buffer_end - buffer)) == NULL) - return signature_parse_error("no newline given"); - - if (header) { - const size_t header_len = strlen(header); - - if (buffer + header_len >= buffer_end || memcmp(buffer, header, header_len) != 0) - return signature_parse_error("expected prefix doesn't match actual"); - - buffer += header_len; - } - - email_start = git__memrchr(buffer, '<', buffer_end - buffer); - email_end = git__memrchr(buffer, '>', buffer_end - buffer); - - if (!email_start || !email_end || email_end <= email_start) - return signature_parse_error("malformed e-mail"); - - email_start += 1; - sig->name = extract_trimmed(buffer, email_start - buffer - 1); - sig->email = extract_trimmed(email_start, email_end - email_start); - - /* Do we even have a time at the end of the signature? */ - if (email_end + 2 < buffer_end) { - const char *time_start = email_end + 2; - const char *time_end; - - if (git__strntol64(&sig->when.time, time_start, - buffer_end - time_start, &time_end, 10) < 0) { - git__free(sig->name); - git__free(sig->email); - sig->name = sig->email = NULL; - return signature_parse_error("invalid Unix timestamp"); - } - - /* do we have a timezone? */ - if (time_end + 1 < buffer_end) { - int offset, hours, mins; - const char *tz_start, *tz_end; - - tz_start = time_end + 1; - - if ((tz_start[0] != '-' && tz_start[0] != '+') || - git__strntol32(&offset, tz_start + 1, - buffer_end - tz_start - 1, &tz_end, 10) < 0) { - /* malformed timezone, just assume it's zero */ - offset = 0; - } - - hours = offset / 100; - mins = offset % 100; - - /* - * only store timezone if it's not overflowing; - * see http://www.worldtimezone.com/faq.html - */ - if (hours <= 14 && mins <= 59) { - sig->when.offset = (hours * 60) + mins; - sig->when.sign = tz_start[0]; - if (tz_start[0] == '-') - sig->when.offset = -sig->when.offset; - } - } - } - - *buffer_out = buffer_end + 1; - return 0; -} - -int git_signature_from_buffer(git_signature **out, const char *buf) -{ - git_signature *sig; - const char *buf_end; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(buf); - - *out = NULL; - - sig = git__calloc(1, sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(sig); - - buf_end = buf + strlen(buf); - error = git_signature__parse(sig, &buf, buf_end, NULL, '\0'); - - if (error) - git__free(sig); - else - *out = sig; - - return error; -} - -void git_signature__writebuf(git_str *buf, const char *header, const git_signature *sig) -{ - int offset, hours, mins; - char sign; - - offset = sig->when.offset; - sign = (sig->when.offset < 0 || sig->when.sign == '-') ? '-' : '+'; - - if (offset < 0) - offset = -offset; - - hours = offset / 60; - mins = offset % 60; - - git_str_printf(buf, "%s%s <%s> %u %c%02d%02d\n", - header ? header : "", sig->name, sig->email, - (unsigned)sig->when.time, sign, hours, mins); -} - -bool git_signature__equal(const git_signature *one, const git_signature *two) -{ - GIT_ASSERT_ARG(one); - GIT_ASSERT_ARG(two); - - return - git__strcmp(one->name, two->name) == 0 && - git__strcmp(one->email, two->email) == 0 && - one->when.time == two->when.time && - one->when.offset == two->when.offset && - one->when.sign == two->when.sign; -} - diff --git a/src/signature.h b/src/signature.h deleted file mode 100644 index 5c8270954..000000000 --- a/src/signature.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_signature_h__ -#define INCLUDE_signature_h__ - -#include "common.h" - -#include "git2/common.h" -#include "git2/signature.h" -#include "repository.h" -#include - -int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender); -void git_signature__writebuf(git_str *buf, const char *header, const git_signature *sig); -bool git_signature__equal(const git_signature *one, const git_signature *two); - -int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool); - -#endif diff --git a/src/sortedcache.c b/src/sortedcache.c deleted file mode 100644 index 7ff900efe..000000000 --- a/src/sortedcache.c +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "sortedcache.h" - -int git_sortedcache_new( - git_sortedcache **out, - size_t item_path_offset, - git_sortedcache_free_item_fn free_item, - void *free_item_payload, - git_vector_cmp item_cmp, - const char *path) -{ - git_sortedcache *sc; - size_t pathlen, alloclen; - - pathlen = path ? strlen(path) : 0; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_sortedcache), pathlen); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - sc = git__calloc(1, alloclen); - GIT_ERROR_CHECK_ALLOC(sc); - - if (git_pool_init(&sc->pool, 1) < 0 || - git_vector_init(&sc->items, 4, item_cmp) < 0 || - git_strmap_new(&sc->map) < 0) - goto fail; - - if (git_rwlock_init(&sc->lock)) { - git_error_set(GIT_ERROR_OS, "failed to initialize lock"); - goto fail; - } - - sc->item_path_offset = item_path_offset; - sc->free_item = free_item; - sc->free_item_payload = free_item_payload; - GIT_REFCOUNT_INC(sc); - if (pathlen) - memcpy(sc->path, path, pathlen); - - *out = sc; - return 0; - -fail: - git_strmap_free(sc->map); - git_vector_free(&sc->items); - git_pool_clear(&sc->pool); - git__free(sc); - return -1; -} - -void git_sortedcache_incref(git_sortedcache *sc) -{ - GIT_REFCOUNT_INC(sc); -} - -const char *git_sortedcache_path(git_sortedcache *sc) -{ - return sc->path; -} - -static void sortedcache_clear(git_sortedcache *sc) -{ - git_strmap_clear(sc->map); - - if (sc->free_item) { - size_t i; - void *item; - - git_vector_foreach(&sc->items, i, item) { - sc->free_item(sc->free_item_payload, item); - } - } - - git_vector_clear(&sc->items); - - git_pool_clear(&sc->pool); -} - -static void sortedcache_free(git_sortedcache *sc) -{ - /* acquire write lock to make sure everyone else is done */ - if (git_sortedcache_wlock(sc) < 0) - return; - - sortedcache_clear(sc); - git_vector_free(&sc->items); - git_strmap_free(sc->map); - - git_sortedcache_wunlock(sc); - - git_rwlock_free(&sc->lock); - git__free(sc); -} - -void git_sortedcache_free(git_sortedcache *sc) -{ - if (!sc) - return; - GIT_REFCOUNT_DEC(sc, sortedcache_free); -} - -static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item) -{ - git_sortedcache *sc = payload; - /* path will already have been copied by upsert */ - memcpy(tgt_item, src_item, sc->item_path_offset); - return 0; -} - -/* copy a sorted cache */ -int git_sortedcache_copy( - git_sortedcache **out, - git_sortedcache *src, - bool lock, - int (*copy_item)(void *payload, void *tgt_item, void *src_item), - void *payload) -{ - int error = 0; - git_sortedcache *tgt; - size_t i; - void *src_item, *tgt_item; - - /* just use memcpy if no special copy fn is passed in */ - if (!copy_item) { - copy_item = sortedcache_copy_item; - payload = src; - } - - if ((error = git_sortedcache_new( - &tgt, src->item_path_offset, - src->free_item, src->free_item_payload, - src->items._cmp, src->path)) < 0) - return error; - - if (lock && git_sortedcache_rlock(src) < 0) { - git_sortedcache_free(tgt); - return -1; - } - - git_vector_foreach(&src->items, i, src_item) { - char *path = ((char *)src_item) + src->item_path_offset; - - if ((error = git_sortedcache_upsert(&tgt_item, tgt, path)) < 0 || - (error = copy_item(payload, tgt_item, src_item)) < 0) - break; - } - - if (lock) - git_sortedcache_runlock(src); - if (error) - git_sortedcache_free(tgt); - - *out = !error ? tgt : NULL; - - return error; -} - -/* lock sortedcache while making modifications */ -int git_sortedcache_wlock(git_sortedcache *sc) -{ - GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ - - if (git_rwlock_wrlock(&sc->lock) < 0) { - git_error_set(GIT_ERROR_OS, "unable to acquire write lock on cache"); - return -1; - } - return 0; -} - -/* unlock sorted cache when done with modifications */ -void git_sortedcache_wunlock(git_sortedcache *sc) -{ - git_vector_sort(&sc->items); - git_rwlock_wrunlock(&sc->lock); -} - -/* lock sortedcache for read */ -int git_sortedcache_rlock(git_sortedcache *sc) -{ - GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ - - if (git_rwlock_rdlock(&sc->lock) < 0) { - git_error_set(GIT_ERROR_OS, "unable to acquire read lock on cache"); - return -1; - } - return 0; -} - -/* unlock sorted cache when done reading */ -void git_sortedcache_runlock(git_sortedcache *sc) -{ - GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ - git_rwlock_rdunlock(&sc->lock); -} - -/* if the file has changed, lock cache and load file contents into buf; - * returns <0 on error, >0 if file has not changed - */ -int git_sortedcache_lockandload(git_sortedcache *sc, git_str *buf) -{ - int error, fd; - struct stat st; - - if ((error = git_sortedcache_wlock(sc)) < 0) - return error; - - if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0) - goto unlock; - - if ((fd = git_futils_open_ro(sc->path)) < 0) { - error = fd; - goto unlock; - } - - if (p_fstat(fd, &st) < 0) { - git_error_set(GIT_ERROR_OS, "failed to stat file"); - error = -1; - (void)p_close(fd); - goto unlock; - } - - if (!git__is_sizet(st.st_size)) { - git_error_set(GIT_ERROR_INVALID, "unable to load file larger than size_t"); - error = -1; - (void)p_close(fd); - goto unlock; - } - - if (buf) - error = git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size); - - (void)p_close(fd); - - if (error < 0) - goto unlock; - - return 1; /* return 1 -> file needs reload and was successfully loaded */ - -unlock: - git_sortedcache_wunlock(sc); - return error; -} - -void git_sortedcache_updated(git_sortedcache *sc) -{ - /* update filestamp to latest value */ - git_futils_filestamp_check(&sc->stamp, sc->path); -} - -/* release all items in sorted cache */ -int git_sortedcache_clear(git_sortedcache *sc, bool wlock) -{ - if (wlock && git_sortedcache_wlock(sc) < 0) - return -1; - - sortedcache_clear(sc); - - if (wlock) - git_sortedcache_wunlock(sc); - - return 0; -} - -/* find and/or insert item, returning pointer to item data */ -int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) -{ - size_t keylen, itemlen; - int error = 0; - char *item_key; - void *item; - - if ((item = git_strmap_get(sc->map, key)) != NULL) - goto done; - - keylen = strlen(key); - itemlen = sc->item_path_offset + keylen + 1; - itemlen = (itemlen + 7) & ~7; - - if ((item = git_pool_mallocz(&sc->pool, itemlen)) == NULL) { - /* don't use GIT_ERROR_CHECK_ALLOC b/c of lock */ - error = -1; - goto done; - } - - /* one strange thing is that even if the vector or hash table insert - * fail, there is no way to free the pool item so we just abandon it - */ - - item_key = ((char *)item) + sc->item_path_offset; - memcpy(item_key, key, keylen); - - if ((error = git_strmap_set(sc->map, item_key, item)) < 0) - goto done; - - if ((error = git_vector_insert(&sc->items, item)) < 0) - git_strmap_delete(sc->map, item_key); - -done: - if (out) - *out = !error ? item : NULL; - return error; -} - -/* lookup item by key */ -void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key) -{ - return git_strmap_get(sc->map, key); -} - -/* find out how many items are in the cache */ -size_t git_sortedcache_entrycount(const git_sortedcache *sc) -{ - return git_vector_length(&sc->items); -} - -/* lookup item by index */ -void *git_sortedcache_entry(git_sortedcache *sc, size_t pos) -{ - /* make sure the items are sorted so this gets the correct item */ - if (!git_vector_is_sorted(&sc->items)) - git_vector_sort(&sc->items); - - return git_vector_get(&sc->items, pos); -} - -/* helper struct so bsearch callback can know offset + key value for cmp */ -struct sortedcache_magic_key { - size_t offset; - const char *key; -}; - -static int sortedcache_magic_cmp(const void *key, const void *value) -{ - const struct sortedcache_magic_key *magic = key; - const char *value_key = ((const char *)value) + magic->offset; - return strcmp(magic->key, value_key); -} - -/* lookup index of item by key */ -int git_sortedcache_lookup_index( - size_t *out, git_sortedcache *sc, const char *key) -{ - struct sortedcache_magic_key magic; - - magic.offset = sc->item_path_offset; - magic.key = key; - - return git_vector_bsearch2(out, &sc->items, sortedcache_magic_cmp, &magic); -} - -/* remove entry from cache */ -int git_sortedcache_remove(git_sortedcache *sc, size_t pos) -{ - char *item; - - /* - * Because of pool allocation, this can't actually remove the item, - * but we can remove it from the items vector and the hash table. - */ - - if ((item = git_vector_get(&sc->items, pos)) == NULL) { - git_error_set(GIT_ERROR_INVALID, "removing item out of range"); - return GIT_ENOTFOUND; - } - - (void)git_vector_remove(&sc->items, pos); - - git_strmap_delete(sc->map, item + sc->item_path_offset); - - if (sc->free_item) - sc->free_item(sc->free_item_payload, item); - - return 0; -} - diff --git a/src/sortedcache.h b/src/sortedcache.h deleted file mode 100644 index ef260a093..000000000 --- a/src/sortedcache.h +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_sorted_cache_h__ -#define INCLUDE_sorted_cache_h__ - -#include "common.h" - -#include "util.h" -#include "futils.h" -#include "vector.h" -#include "thread.h" -#include "pool.h" -#include "strmap.h" - -#include - -/* - * The purpose of this data structure is to cache the parsed contents of a - * file (a.k.a. the backing file) where each item in the file can be - * identified by a key string and you want to both look them up by name - * and traverse them in sorted order. Each item is assumed to itself end - * in a GIT_FLEX_ARRAY. - */ - -typedef void (*git_sortedcache_free_item_fn)(void *payload, void *item); - -typedef struct { - git_refcount rc; - git_rwlock lock; - size_t item_path_offset; - git_sortedcache_free_item_fn free_item; - void *free_item_payload; - git_pool pool; - git_vector items; - git_strmap *map; - git_futils_filestamp stamp; - char path[GIT_FLEX_ARRAY]; -} git_sortedcache; - -/* Create a new sortedcache - * - * Even though every sortedcache stores items with a GIT_FLEX_ARRAY at - * the end containing their key string, you have to provide the item_cmp - * sorting function because the sorting function doesn't get a payload - * and therefore can't know the offset to the item key string. :-( - * - * @param out The allocated git_sortedcache - * @param item_path_offset Offset to the GIT_FLEX_ARRAY item key in the - * struct - use offsetof(struct mine, key-field) to get this - * @param free_item Optional callback to free each item - * @param free_item_payload Optional payload passed to free_item callback - * @param item_cmp Compare the keys of two items - * @param path The path to the backing store file for this cache; this - * may be NULL. The cache makes it easy to load this and check - * if it has been modified since the last load and/or write. - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_new( - git_sortedcache **out, - size_t item_path_offset, /* use offsetof(struct, path-field) macro */ - git_sortedcache_free_item_fn free_item, - void *free_item_payload, - git_vector_cmp item_cmp, - const char *path); - -/* Copy a sorted cache - * - * - `copy_item` can be NULL to just use memcpy - * - if `lock`, grabs read lock on `src` during copy and releases after - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_copy( - git_sortedcache **out, - git_sortedcache *src, - bool lock, - int (*copy_item)(void *payload, void *tgt_item, void *src_item), - void *payload); - -/* Free sorted cache (first calling `free_item` callbacks) - * - * Don't call on a locked collection - it may acquire a write lock - */ -void git_sortedcache_free(git_sortedcache *sc); - -/* Increment reference count - balance with call to free */ -void git_sortedcache_incref(git_sortedcache *sc); - -/* Get the pathname associated with this cache at creation time */ -const char *git_sortedcache_path(git_sortedcache *sc); - -/* - * CACHE WRITE FUNCTIONS - * - * The following functions require you to have a writer lock to make the - * modification. Some of the functions take a `wlock` parameter and - * will optionally lock and unlock for you if that is passed as true. - * - */ - -/* Lock sortedcache for write */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_wlock(git_sortedcache *sc); - -/* Unlock sorted cache when done with write */ -void git_sortedcache_wunlock(git_sortedcache *sc); - -/* Lock cache and load backing file into a buffer. - * - * This grabs a write lock on the cache then looks at the modification - * time and size of the file on disk. - * - * If the file appears to have changed, this loads the file contents into - * the buffer and returns a positive value leaving the cache locked - the - * caller should parse the file content, update the cache as needed, then - * release the lock. NOTE: In this case, the caller MUST unlock the cache. - * - * If the file appears to be unchanged, then this automatically releases - * the lock on the cache, clears the buffer, and returns 0. - * - * @return 0 if up-to-date, 1 if out-of-date, <0 on error - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_lockandload( - git_sortedcache *sc, git_str *buf); - -/* Refresh file timestamp after write completes - * You should already be holding the write lock when you call this. - */ -void git_sortedcache_updated(git_sortedcache *sc); - -/* Release all items in sorted cache - * - * If `wlock` is true, grabs write lock and releases when done, otherwise - * you should already be holding a write lock when you call this. - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_clear( - git_sortedcache *sc, bool wlock); - -/* Find and/or insert item, returning pointer to item data. - * You should already be holding the write lock when you call this. - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_upsert( - void **out, git_sortedcache *sc, const char *key); - -/* Removes entry at pos from cache - * You should already be holding the write lock when you call this. - */ -int git_sortedcache_remove(git_sortedcache *sc, size_t pos); - -/* - * CACHE READ FUNCTIONS - * - * The following functions access items in the cache. To prevent the - * results from being invalidated before they can be used, you should be - * holding either a read lock or a write lock when using these functions. - * - */ - -/* Lock sortedcache for read */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_rlock(git_sortedcache *sc); - -/* Unlock sorted cache when done with read */ -void git_sortedcache_runlock(git_sortedcache *sc); - -/* Lookup item by key - returns NULL if not found */ -void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key); - -/* Get how many items are in the cache - * - * You can call this function without holding a lock, but be aware - * that it may change before you use it. - */ -size_t git_sortedcache_entrycount(const git_sortedcache *sc); - -/* Lookup item by index - returns NULL if out of range */ -void *git_sortedcache_entry(git_sortedcache *sc, size_t pos); - -/* Lookup index of item by key - returns GIT_ENOTFOUND if not found */ -int git_sortedcache_lookup_index( - size_t *out, git_sortedcache *sc, const char *key); - -#endif diff --git a/src/stash.c b/src/stash.c deleted file mode 100644 index 5fc01ac36..000000000 --- a/src/stash.c +++ /dev/null @@ -1,1110 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "repository.h" -#include "commit.h" -#include "tree.h" -#include "reflog.h" -#include "blob.h" -#include "git2/diff.h" -#include "git2/stash.h" -#include "git2/status.h" -#include "git2/checkout.h" -#include "git2/index.h" -#include "git2/transaction.h" -#include "git2/merge.h" -#include "index.h" -#include "signature.h" -#include "iterator.h" -#include "merge.h" -#include "diff.h" -#include "diff_generate.h" - -static int create_error(int error, const char *msg) -{ - git_error_set(GIT_ERROR_STASH, "cannot stash changes - %s", msg); - return error; -} - -static int retrieve_head(git_reference **out, git_repository *repo) -{ - int error = git_repository_head(out, repo); - - if (error == GIT_EUNBORNBRANCH) - return create_error(error, "you do not have the initial commit yet."); - - return error; -} - -static int append_abbreviated_oid(git_str *out, const git_oid *b_commit) -{ - char *formatted_oid; - - formatted_oid = git_oid_allocfmt(b_commit); - GIT_ERROR_CHECK_ALLOC(formatted_oid); - - git_str_put(out, formatted_oid, 7); - git__free(formatted_oid); - - return git_str_oom(out) ? -1 : 0; -} - -static int append_commit_description(git_str *out, git_commit *commit) -{ - const char *summary = git_commit_summary(commit); - GIT_ERROR_CHECK_ALLOC(summary); - - if (append_abbreviated_oid(out, git_commit_id(commit)) < 0) - return -1; - - git_str_putc(out, ' '); - git_str_puts(out, summary); - git_str_putc(out, '\n'); - - return git_str_oom(out) ? -1 : 0; -} - -static int retrieve_base_commit_and_message( - git_commit **b_commit, - git_str *stash_message, - git_repository *repo) -{ - git_reference *head = NULL; - int error; - - if ((error = retrieve_head(&head, repo)) < 0) - return error; - - if (strcmp("HEAD", git_reference_name(head)) == 0) - error = git_str_puts(stash_message, "(no branch): "); - else - error = git_str_printf( - stash_message, - "%s: ", - git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR)); - if (error < 0) - goto cleanup; - - if ((error = git_commit_lookup( - b_commit, repo, git_reference_target(head))) < 0) - goto cleanup; - - if ((error = append_commit_description(stash_message, *b_commit)) < 0) - goto cleanup; - -cleanup: - git_reference_free(head); - return error; -} - -static int build_tree_from_index( - git_tree **out, - git_repository *repo, - git_index *index) -{ - int error; - git_oid i_tree_oid; - - if ((error = git_index_write_tree_to(&i_tree_oid, index, repo)) < 0) - return error; - - return git_tree_lookup(out, repo, &i_tree_oid); -} - -static int commit_index( - git_commit **i_commit, - git_repository *repo, - git_index *index, - const git_signature *stasher, - const char *message, - const git_commit *parent) -{ - git_tree *i_tree = NULL; - git_oid i_commit_oid; - git_str msg = GIT_STR_INIT; - int error; - - if ((error = build_tree_from_index(&i_tree, repo, index)) < 0) - goto cleanup; - - if ((error = git_str_printf(&msg, "index on %s\n", message)) < 0) - goto cleanup; - - if ((error = git_commit_create( - &i_commit_oid, - git_index_owner(index), - NULL, - stasher, - stasher, - NULL, - git_str_cstr(&msg), - i_tree, - 1, - &parent)) < 0) - goto cleanup; - - error = git_commit_lookup(i_commit, git_index_owner(index), &i_commit_oid); - -cleanup: - git_tree_free(i_tree); - git_str_dispose(&msg); - return error; -} - -struct stash_update_rules { - bool include_changed; - bool include_untracked; - bool include_ignored; -}; - -/* - * Similar to git_index_add_bypath but able to operate on any - * index without making assumptions about the repository's index - */ -static int stash_to_index( - git_repository *repo, - git_index *index, - const char *path) -{ - git_index *repo_index = NULL; - git_index_entry entry = {{0}}; - struct stat st; - int error; - - if (!git_repository_is_bare(repo) && - (error = git_repository_index__weakptr(&repo_index, repo)) < 0) - return error; - - if ((error = git_blob__create_from_paths( - &entry.id, &st, repo, NULL, path, 0, true)) < 0) - return error; - - git_index_entry__init_from_stat(&entry, &st, - (repo_index == NULL || !repo_index->distrust_filemode)); - - entry.path = path; - - return git_index_add(index, &entry); -} - -static int stash_update_index_from_diff( - git_repository *repo, - git_index *index, - const git_diff *diff, - struct stash_update_rules *data) -{ - int error = 0; - size_t d, max_d = git_diff_num_deltas(diff); - - for (d = 0; !error && d < max_d; ++d) { - const char *add_path = NULL; - const git_diff_delta *delta = git_diff_get_delta(diff, d); - - switch (delta->status) { - case GIT_DELTA_IGNORED: - if (data->include_ignored) - add_path = delta->new_file.path; - break; - - case GIT_DELTA_UNTRACKED: - if (data->include_untracked && - delta->new_file.mode != GIT_FILEMODE_TREE) - add_path = delta->new_file.path; - break; - - case GIT_DELTA_ADDED: - case GIT_DELTA_MODIFIED: - if (data->include_changed) - add_path = delta->new_file.path; - break; - - case GIT_DELTA_DELETED: - if (data->include_changed && - !git_index_find(NULL, index, delta->old_file.path)) - error = git_index_remove(index, delta->old_file.path, 0); - break; - - default: - /* Unimplemented */ - git_error_set( - GIT_ERROR_INVALID, - "cannot update index. Unimplemented status (%d)", - delta->status); - return -1; - } - - if (add_path != NULL) - error = stash_to_index(repo, index, add_path); - } - - return error; -} - -static int build_untracked_tree( - git_tree **tree_out, - git_repository *repo, - git_commit *i_commit, - uint32_t flags) -{ - git_index *i_index = NULL; - git_tree *i_tree = NULL; - git_diff *diff = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - struct stash_update_rules data = {0}; - int error; - - if ((error = git_index_new(&i_index)) < 0) - goto cleanup; - - if (flags & GIT_STASH_INCLUDE_UNTRACKED) { - opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_RECURSE_UNTRACKED_DIRS; - data.include_untracked = true; - } - - if (flags & GIT_STASH_INCLUDE_IGNORED) { - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | - GIT_DIFF_RECURSE_IGNORED_DIRS; - data.include_ignored = true; - } - - if ((error = git_commit_tree(&i_tree, i_commit)) < 0) - goto cleanup; - - if ((error = git_diff_tree_to_workdir(&diff, repo, i_tree, &opts)) < 0) - goto cleanup; - - if ((error = stash_update_index_from_diff(repo, i_index, diff, &data)) < 0) - goto cleanup; - - error = build_tree_from_index(tree_out, repo, i_index); - -cleanup: - git_diff_free(diff); - git_tree_free(i_tree); - git_index_free(i_index); - return error; -} - -static int commit_untracked( - git_commit **u_commit, - git_repository *repo, - const git_signature *stasher, - const char *message, - git_commit *i_commit, - uint32_t flags) -{ - git_tree *u_tree = NULL; - git_oid u_commit_oid; - git_str msg = GIT_STR_INIT; - int error; - - if ((error = build_untracked_tree(&u_tree, repo, i_commit, flags)) < 0) - goto cleanup; - - if ((error = git_str_printf(&msg, "untracked files on %s\n", message)) < 0) - goto cleanup; - - if ((error = git_commit_create( - &u_commit_oid, - repo, - NULL, - stasher, - stasher, - NULL, - git_str_cstr(&msg), - u_tree, - 0, - NULL)) < 0) - goto cleanup; - - error = git_commit_lookup(u_commit, repo, &u_commit_oid); - -cleanup: - git_tree_free(u_tree); - git_str_dispose(&msg); - return error; -} - -static git_diff_delta *stash_delta_merge( - const git_diff_delta *a, - const git_diff_delta *b, - git_pool *pool) -{ - /* Special case for stash: if a file is deleted in the index, but exists - * in the working tree, we need to stash the workdir copy for the workdir. - */ - if (a->status == GIT_DELTA_DELETED && b->status == GIT_DELTA_UNTRACKED) { - git_diff_delta *dup = git_diff__delta_dup(b, pool); - - if (dup) - dup->status = GIT_DELTA_MODIFIED; - return dup; - } - - return git_diff__merge_like_cgit(a, b, pool); -} - -static int build_workdir_tree( - git_tree **tree_out, - git_repository *repo, - git_index *i_index, - git_commit *b_commit) -{ - git_tree *b_tree = NULL; - git_diff *diff = NULL, *idx_to_wd = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - struct stash_update_rules data = {0}; - int error; - - opts.flags = GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_INCLUDE_UNTRACKED; - - if ((error = git_commit_tree(&b_tree, b_commit)) < 0) - goto cleanup; - - if ((error = git_diff_tree_to_index(&diff, repo, b_tree, i_index, &opts)) < 0 || - (error = git_diff_index_to_workdir(&idx_to_wd, repo, i_index, &opts)) < 0 || - (error = git_diff__merge(diff, idx_to_wd, stash_delta_merge)) < 0) - goto cleanup; - - data.include_changed = true; - - if ((error = stash_update_index_from_diff(repo, i_index, diff, &data)) < 0) - goto cleanup; - - error = build_tree_from_index(tree_out, repo, i_index); - -cleanup: - git_diff_free(idx_to_wd); - git_diff_free(diff); - git_tree_free(b_tree); - - return error; -} - -static int commit_worktree( - git_oid *w_commit_oid, - git_repository *repo, - const git_signature *stasher, - const char *message, - git_commit *i_commit, - git_commit *b_commit, - git_commit *u_commit) -{ - const git_commit *parents[] = { NULL, NULL, NULL }; - git_index *i_index = NULL, *r_index = NULL; - git_tree *w_tree = NULL; - int error = 0, ignorecase; - - parents[0] = b_commit; - parents[1] = i_commit; - parents[2] = u_commit; - - if ((error = git_repository_index(&r_index, repo) < 0) || - (error = git_index_new(&i_index)) < 0 || - (error = git_index__fill(i_index, &r_index->entries) < 0) || - (error = git_repository__configmap_lookup(&ignorecase, repo, GIT_CONFIGMAP_IGNORECASE)) < 0) - goto cleanup; - - git_index__set_ignore_case(i_index, ignorecase); - - if ((error = build_workdir_tree(&w_tree, repo, i_index, b_commit)) < 0) - goto cleanup; - - error = git_commit_create( - w_commit_oid, - repo, - NULL, - stasher, - stasher, - NULL, - message, - w_tree, - u_commit ? 3 : 2, - parents); - -cleanup: - git_tree_free(w_tree); - git_index_free(i_index); - git_index_free(r_index); - return error; -} - -static int prepare_worktree_commit_message(git_str *out, const char *user_message) -{ - git_str buf = GIT_STR_INIT; - int error = 0; - - if (!user_message) { - git_str_printf(&buf, "WIP on %s", git_str_cstr(out)); - } else { - const char *colon; - - if ((colon = strchr(git_str_cstr(out), ':')) == NULL) - goto cleanup; - - git_str_puts(&buf, "On "); - git_str_put(&buf, git_str_cstr(out), colon - out->ptr); - git_str_printf(&buf, ": %s\n", user_message); - } - - if (git_str_oom(&buf)) { - error = -1; - goto cleanup; - } - - git_str_swap(out, &buf); - -cleanup: - git_str_dispose(&buf); - return error; -} - -static int update_reflog( - git_oid *w_commit_oid, - git_repository *repo, - const char *message) -{ - git_reference *stash; - int error; - - if ((error = git_reference_ensure_log(repo, GIT_REFS_STASH_FILE)) < 0) - return error; - - error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1, message); - - git_reference_free(stash); - - return error; -} - -static int is_dirty_cb(const char *path, unsigned int status, void *payload) -{ - GIT_UNUSED(path); - GIT_UNUSED(status); - GIT_UNUSED(payload); - - return GIT_PASSTHROUGH; -} - -static int ensure_there_are_changes_to_stash(git_repository *repo, uint32_t flags) -{ - int error; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opts.flags = GIT_STATUS_OPT_EXCLUDE_SUBMODULES; - - if (flags & GIT_STASH_INCLUDE_UNTRACKED) - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - - if (flags & GIT_STASH_INCLUDE_IGNORED) - opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED | - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; - - error = git_status_foreach_ext(repo, &opts, is_dirty_cb, NULL); - - if (error == GIT_PASSTHROUGH) - return 0; - - if (!error) - return create_error(GIT_ENOTFOUND, "there is nothing to stash."); - - return error; -} - -static int reset_index_and_workdir(git_repository *repo, git_commit *commit, uint32_t flags) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - if (flags & GIT_STASH_INCLUDE_UNTRACKED) - opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED; - if (flags & GIT_STASH_INCLUDE_IGNORED) - opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_IGNORED; - - return git_checkout_tree(repo, (git_object *)commit, &opts); -} - -int git_stash_save( - git_oid *out, - git_repository *repo, - const git_signature *stasher, - const char *message, - uint32_t flags) -{ - git_index *index = NULL; - git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL; - git_str msg = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(stasher); - - if ((error = git_repository__ensure_not_bare(repo, "stash save")) < 0) - return error; - - if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0) - goto cleanup; - - if ((error = ensure_there_are_changes_to_stash(repo, flags)) < 0) - goto cleanup; - - if ((error = git_repository_index(&index, repo)) < 0) - goto cleanup; - - if ((error = commit_index(&i_commit, repo, index, stasher, - git_str_cstr(&msg), b_commit)) < 0) - goto cleanup; - - if ((flags & (GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)) && - (error = commit_untracked(&u_commit, repo, stasher, - git_str_cstr(&msg), i_commit, flags)) < 0) - goto cleanup; - - if ((error = prepare_worktree_commit_message(&msg, message)) < 0) - goto cleanup; - - if ((error = commit_worktree(out, repo, stasher, git_str_cstr(&msg), - i_commit, b_commit, u_commit)) < 0) - goto cleanup; - - git_str_rtrim(&msg); - - if ((error = update_reflog(out, repo, git_str_cstr(&msg))) < 0) - goto cleanup; - - if ((error = reset_index_and_workdir(repo, (flags & GIT_STASH_KEEP_INDEX) ? i_commit : b_commit, - flags)) < 0) - goto cleanup; - -cleanup: - - git_str_dispose(&msg); - git_commit_free(i_commit); - git_commit_free(b_commit); - git_commit_free(u_commit); - git_index_free(index); - - return error; -} - -static int retrieve_stash_commit( - git_commit **commit, - git_repository *repo, - size_t index) -{ - git_reference *stash = NULL; - git_reflog *reflog = NULL; - int error; - size_t max; - const git_reflog_entry *entry; - - if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - - if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - - max = git_reflog_entrycount(reflog); - if (!max || index > max - 1) { - error = GIT_ENOTFOUND; - git_error_set(GIT_ERROR_STASH, "no stashed state at position %" PRIuZ, index); - goto cleanup; - } - - entry = git_reflog_entry_byindex(reflog, index); - if ((error = git_commit_lookup(commit, repo, git_reflog_entry_id_new(entry))) < 0) - goto cleanup; - -cleanup: - git_reference_free(stash); - git_reflog_free(reflog); - return error; -} - -static int retrieve_stash_trees( - git_tree **out_stash_tree, - git_tree **out_base_tree, - git_tree **out_index_tree, - git_tree **out_index_parent_tree, - git_tree **out_untracked_tree, - git_commit *stash_commit) -{ - git_tree *stash_tree = NULL; - git_commit *base_commit = NULL; - git_tree *base_tree = NULL; - git_commit *index_commit = NULL; - git_tree *index_tree = NULL; - git_commit *index_parent_commit = NULL; - git_tree *index_parent_tree = NULL; - git_commit *untracked_commit = NULL; - git_tree *untracked_tree = NULL; - int error; - - if ((error = git_commit_tree(&stash_tree, stash_commit)) < 0) - goto cleanup; - - if ((error = git_commit_parent(&base_commit, stash_commit, 0)) < 0) - goto cleanup; - if ((error = git_commit_tree(&base_tree, base_commit)) < 0) - goto cleanup; - - if ((error = git_commit_parent(&index_commit, stash_commit, 1)) < 0) - goto cleanup; - if ((error = git_commit_tree(&index_tree, index_commit)) < 0) - goto cleanup; - - if ((error = git_commit_parent(&index_parent_commit, index_commit, 0)) < 0) - goto cleanup; - if ((error = git_commit_tree(&index_parent_tree, index_parent_commit)) < 0) - goto cleanup; - - if (git_commit_parentcount(stash_commit) == 3) { - if ((error = git_commit_parent(&untracked_commit, stash_commit, 2)) < 0) - goto cleanup; - if ((error = git_commit_tree(&untracked_tree, untracked_commit)) < 0) - goto cleanup; - } - - *out_stash_tree = stash_tree; - *out_base_tree = base_tree; - *out_index_tree = index_tree; - *out_index_parent_tree = index_parent_tree; - *out_untracked_tree = untracked_tree; - -cleanup: - git_commit_free(untracked_commit); - git_commit_free(index_parent_commit); - git_commit_free(index_commit); - git_commit_free(base_commit); - if (error < 0) { - git_tree_free(stash_tree); - git_tree_free(base_tree); - git_tree_free(index_tree); - git_tree_free(index_parent_tree); - git_tree_free(untracked_tree); - } - return error; -} - -static int merge_indexes( - git_index **out, - git_repository *repo, - git_tree *ancestor_tree, - git_index *ours_index, - git_index *theirs_index) -{ - git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error; - - iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || - (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 || - (error = git_iterator_for_index(&theirs, repo, theirs_index, &iter_opts)) < 0) - goto done; - - error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); - -done: - git_iterator_free(ancestor); - git_iterator_free(ours); - git_iterator_free(theirs); - return error; -} - -static int merge_index_and_tree( - git_index **out, - git_repository *repo, - git_tree *ancestor_tree, - git_index *ours_index, - git_tree *theirs_tree) -{ - git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - int error; - - iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || - (error = git_iterator_for_index(&ours, repo, ours_index, &iter_opts)) < 0 || - (error = git_iterator_for_tree(&theirs, theirs_tree, &iter_opts)) < 0) - goto done; - - error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); - -done: - git_iterator_free(ancestor); - git_iterator_free(ours); - git_iterator_free(theirs); - return error; -} - -static void normalize_apply_options( - git_stash_apply_options *opts, - const git_stash_apply_options *given_apply_opts) -{ - if (given_apply_opts != NULL) { - memcpy(opts, given_apply_opts, sizeof(git_stash_apply_options)); - } else { - git_stash_apply_options default_apply_opts = GIT_STASH_APPLY_OPTIONS_INIT; - memcpy(opts, &default_apply_opts, sizeof(git_stash_apply_options)); - } - - opts->checkout_options.checkout_strategy |= GIT_CHECKOUT_NO_REFRESH; - - if (!opts->checkout_options.our_label) - opts->checkout_options.our_label = "Updated upstream"; - - if (!opts->checkout_options.their_label) - opts->checkout_options.their_label = "Stashed changes"; -} - -int git_stash_apply_options_init(git_stash_apply_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_stash_apply_options, GIT_STASH_APPLY_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int version) -{ - return git_stash_apply_options_init(opts, version); -} -#endif - -#define NOTIFY_PROGRESS(opts, progress_type) \ - do { \ - if ((opts).progress_cb && \ - (error = (opts).progress_cb((progress_type), (opts).progress_payload))) { \ - error = (error < 0) ? error : -1; \ - goto cleanup; \ - } \ - } while(false); - -static int ensure_clean_index(git_repository *repo, git_index *index) -{ - git_tree *head_tree = NULL; - git_diff *index_diff = NULL; - int error = 0; - - if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || - (error = git_diff_tree_to_index( - &index_diff, repo, head_tree, index, NULL)) < 0) - goto done; - - if (git_diff_num_deltas(index_diff) > 0) { - git_error_set(GIT_ERROR_STASH, "%" PRIuZ " uncommitted changes exist in the index", - git_diff_num_deltas(index_diff)); - error = GIT_EUNCOMMITTED; - } - -done: - git_diff_free(index_diff); - git_tree_free(head_tree); - return error; -} - -static int stage_new_file(const git_index_entry **entries, void *data) -{ - git_index *index = data; - - if(entries[0] == NULL) - return git_index_add(index, entries[1]); - else - return git_index_add(index, entries[0]); -} - -static int stage_new_files( - git_index **out, - git_tree *parent_tree, - git_tree *tree) -{ - git_iterator *iterators[2] = { NULL, NULL }; - git_iterator_options iterator_options = GIT_ITERATOR_OPTIONS_INIT; - git_index *index = NULL; - int error; - - if ((error = git_index_new(&index)) < 0 || - (error = git_iterator_for_tree( - &iterators[0], parent_tree, &iterator_options)) < 0 || - (error = git_iterator_for_tree( - &iterators[1], tree, &iterator_options)) < 0) - goto done; - - error = git_iterator_walk(iterators, 2, stage_new_file, index); - -done: - if (error < 0) - git_index_free(index); - else - *out = index; - - git_iterator_free(iterators[0]); - git_iterator_free(iterators[1]); - - return error; -} - -int git_stash_apply( - git_repository *repo, - size_t index, - const git_stash_apply_options *given_opts) -{ - git_stash_apply_options opts; - unsigned int checkout_strategy; - git_commit *stash_commit = NULL; - git_tree *stash_tree = NULL; - git_tree *stash_parent_tree = NULL; - git_tree *index_tree = NULL; - git_tree *index_parent_tree = NULL; - git_tree *untracked_tree = NULL; - git_index *stash_adds = NULL; - git_index *repo_index = NULL; - git_index *unstashed_index = NULL; - git_index *modified_index = NULL; - git_index *untracked_index = NULL; - int error; - - GIT_ERROR_CHECK_VERSION(given_opts, GIT_STASH_APPLY_OPTIONS_VERSION, "git_stash_apply_options"); - - normalize_apply_options(&opts, given_opts); - checkout_strategy = opts.checkout_options.checkout_strategy; - - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_LOADING_STASH); - - /* Retrieve commit corresponding to the given stash */ - if ((error = retrieve_stash_commit(&stash_commit, repo, index)) < 0) - goto cleanup; - - /* Retrieve all trees in the stash */ - if ((error = retrieve_stash_trees( - &stash_tree, &stash_parent_tree, &index_tree, - &index_parent_tree, &untracked_tree, stash_commit)) < 0) - goto cleanup; - - /* Load repo index */ - if ((error = git_repository_index(&repo_index, repo)) < 0) - goto cleanup; - - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX); - - if ((error = ensure_clean_index(repo, repo_index)) < 0) - goto cleanup; - - /* Restore index if required */ - if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) && - git_oid_cmp(git_tree_id(stash_parent_tree), git_tree_id(index_tree))) { - - if ((error = merge_index_and_tree( - &unstashed_index, repo, index_parent_tree, repo_index, index_tree)) < 0) - goto cleanup; - - if (git_index_has_conflicts(unstashed_index)) { - error = GIT_ECONFLICT; - goto cleanup; - } - - /* Otherwise, stage any new files in the stash tree. (Note: their - * previously unstaged contents are staged, not the previously staged.) - */ - } else if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) == 0) { - if ((error = stage_new_files( - &stash_adds, stash_parent_tree, stash_tree)) < 0 || - (error = merge_indexes( - &unstashed_index, repo, stash_parent_tree, repo_index, stash_adds)) < 0) - goto cleanup; - } - - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED); - - /* Restore modified files in workdir */ - if ((error = merge_index_and_tree( - &modified_index, repo, stash_parent_tree, repo_index, stash_tree)) < 0) - goto cleanup; - - /* If applicable, restore untracked / ignored files in workdir */ - if (untracked_tree) { - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED); - - if ((error = merge_index_and_tree(&untracked_index, repo, NULL, repo_index, untracked_tree)) < 0) - goto cleanup; - } - - if (untracked_index) { - opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; - - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED); - - if ((error = git_checkout_index(repo, untracked_index, &opts.checkout_options)) < 0) - goto cleanup; - - opts.checkout_options.checkout_strategy = checkout_strategy; - } - - - /* If there are conflicts in the modified index, then we need to actually - * check that out as the repo's index. Otherwise, we don't update the - * index. - */ - - if (!git_index_has_conflicts(modified_index)) - opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; - - /* Check out the modified index using the existing repo index as baseline, - * so that existing modifications in the index can be rewritten even when - * checking out safely. - */ - opts.checkout_options.baseline_index = repo_index; - - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED); - - if ((error = git_checkout_index(repo, modified_index, &opts.checkout_options)) < 0) - goto cleanup; - - if (unstashed_index && !git_index_has_conflicts(modified_index)) { - if ((error = git_index_read_index(repo_index, unstashed_index)) < 0) - goto cleanup; - } - - NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_DONE); - - error = git_index_write(repo_index); - -cleanup: - git_index_free(untracked_index); - git_index_free(modified_index); - git_index_free(unstashed_index); - git_index_free(stash_adds); - git_index_free(repo_index); - git_tree_free(untracked_tree); - git_tree_free(index_parent_tree); - git_tree_free(index_tree); - git_tree_free(stash_parent_tree); - git_tree_free(stash_tree); - git_commit_free(stash_commit); - return error; -} - -int git_stash_foreach( - git_repository *repo, - git_stash_cb callback, - void *payload) -{ - git_reference *stash; - git_reflog *reflog = NULL; - int error; - size_t i, max; - const git_reflog_entry *entry; - - error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE); - if (error == GIT_ENOTFOUND) { - git_error_clear(); - return 0; - } - if (error < 0) - goto cleanup; - - if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - - max = git_reflog_entrycount(reflog); - for (i = 0; i < max; i++) { - entry = git_reflog_entry_byindex(reflog, i); - - error = callback(i, - git_reflog_entry_message(entry), - git_reflog_entry_id_new(entry), - payload); - - if (error) { - git_error_set_after_callback(error); - break; - } - } - -cleanup: - git_reference_free(stash); - git_reflog_free(reflog); - return error; -} - -int git_stash_drop( - git_repository *repo, - size_t index) -{ - git_transaction *tx; - git_reference *stash = NULL; - git_reflog *reflog = NULL; - size_t max; - int error; - - if ((error = git_transaction_new(&tx, repo)) < 0) - return error; - - if ((error = git_transaction_lock_ref(tx, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - - if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - - if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - - max = git_reflog_entrycount(reflog); - - if (!max || index > max - 1) { - error = GIT_ENOTFOUND; - git_error_set(GIT_ERROR_STASH, "no stashed state at position %" PRIuZ, index); - goto cleanup; - } - - if ((error = git_reflog_drop(reflog, index, true)) < 0) - goto cleanup; - - if ((error = git_transaction_set_reflog(tx, GIT_REFS_STASH_FILE, reflog)) < 0) - goto cleanup; - - if (max == 1) { - if ((error = git_transaction_remove(tx, GIT_REFS_STASH_FILE)) < 0) - goto cleanup; - } else if (index == 0) { - const git_reflog_entry *entry; - - entry = git_reflog_entry_byindex(reflog, 0); - if ((error = git_transaction_set_target(tx, GIT_REFS_STASH_FILE, &entry->oid_cur, NULL, NULL)) < 0) - goto cleanup; - } - - error = git_transaction_commit(tx); - -cleanup: - git_reference_free(stash); - git_transaction_free(tx); - git_reflog_free(reflog); - return error; -} - -int git_stash_pop( - git_repository *repo, - size_t index, - const git_stash_apply_options *options) -{ - int error; - - if ((error = git_stash_apply(repo, index, options)) < 0) - return error; - - return git_stash_drop(repo, index); -} diff --git a/src/status.c b/src/status.c deleted file mode 100644 index df0f74507..000000000 --- a/src/status.c +++ /dev/null @@ -1,584 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "status.h" - -#include "git2.h" -#include "futils.h" -#include "hash.h" -#include "vector.h" -#include "tree.h" -#include "git2/status.h" -#include "repository.h" -#include "ignore.h" -#include "index.h" -#include "wildmatch.h" - -#include "git2/diff.h" -#include "diff.h" -#include "diff_generate.h" - -static unsigned int index_delta2status(const git_diff_delta *head2idx) -{ - git_status_t st = GIT_STATUS_CURRENT; - - switch (head2idx->status) { - case GIT_DELTA_ADDED: - case GIT_DELTA_COPIED: - st = GIT_STATUS_INDEX_NEW; - break; - case GIT_DELTA_DELETED: - st = GIT_STATUS_INDEX_DELETED; - break; - case GIT_DELTA_MODIFIED: - st = GIT_STATUS_INDEX_MODIFIED; - break; - case GIT_DELTA_RENAMED: - st = GIT_STATUS_INDEX_RENAMED; - - if (!git_oid_equal(&head2idx->old_file.id, &head2idx->new_file.id)) - st |= GIT_STATUS_INDEX_MODIFIED; - break; - case GIT_DELTA_TYPECHANGE: - st = GIT_STATUS_INDEX_TYPECHANGE; - break; - case GIT_DELTA_CONFLICTED: - st = GIT_STATUS_CONFLICTED; - break; - default: - break; - } - - return st; -} - -static unsigned int workdir_delta2status( - git_diff *diff, git_diff_delta *idx2wd) -{ - git_status_t st = GIT_STATUS_CURRENT; - - switch (idx2wd->status) { - case GIT_DELTA_ADDED: - case GIT_DELTA_COPIED: - case GIT_DELTA_UNTRACKED: - st = GIT_STATUS_WT_NEW; - break; - case GIT_DELTA_UNREADABLE: - st = GIT_STATUS_WT_UNREADABLE; - break; - case GIT_DELTA_DELETED: - st = GIT_STATUS_WT_DELETED; - break; - case GIT_DELTA_MODIFIED: - st = GIT_STATUS_WT_MODIFIED; - break; - case GIT_DELTA_IGNORED: - st = GIT_STATUS_IGNORED; - break; - case GIT_DELTA_RENAMED: - st = GIT_STATUS_WT_RENAMED; - - if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id)) { - /* if OIDs don't match, we might need to calculate them now to - * discern between RENAMED vs RENAMED+MODIFIED - */ - if (git_oid_is_zero(&idx2wd->old_file.id) && - diff->old_src == GIT_ITERATOR_WORKDIR && - !git_diff__oid_for_file( - &idx2wd->old_file.id, diff, idx2wd->old_file.path, - idx2wd->old_file.mode, idx2wd->old_file.size)) - idx2wd->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - if (git_oid_is_zero(&idx2wd->new_file.id) && - diff->new_src == GIT_ITERATOR_WORKDIR && - !git_diff__oid_for_file( - &idx2wd->new_file.id, diff, idx2wd->new_file.path, - idx2wd->new_file.mode, idx2wd->new_file.size)) - idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id)) - st |= GIT_STATUS_WT_MODIFIED; - } - break; - case GIT_DELTA_TYPECHANGE: - st = GIT_STATUS_WT_TYPECHANGE; - break; - case GIT_DELTA_CONFLICTED: - st = GIT_STATUS_CONFLICTED; - break; - default: - break; - } - - return st; -} - -static bool status_is_included( - git_status_list *status, - git_diff_delta *head2idx, - git_diff_delta *idx2wd) -{ - if (!(status->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES)) - return 1; - - /* if excluding submodules and this is a submodule everywhere */ - if (head2idx) { - if (head2idx->status != GIT_DELTA_ADDED && - head2idx->old_file.mode != GIT_FILEMODE_COMMIT) - return 1; - if (head2idx->status != GIT_DELTA_DELETED && - head2idx->new_file.mode != GIT_FILEMODE_COMMIT) - return 1; - } - if (idx2wd) { - if (idx2wd->status != GIT_DELTA_ADDED && - idx2wd->old_file.mode != GIT_FILEMODE_COMMIT) - return 1; - if (idx2wd->status != GIT_DELTA_DELETED && - idx2wd->new_file.mode != GIT_FILEMODE_COMMIT) - return 1; - } - - /* only get here if every valid mode is GIT_FILEMODE_COMMIT */ - return 0; -} - -static git_status_t status_compute( - git_status_list *status, - git_diff_delta *head2idx, - git_diff_delta *idx2wd) -{ - git_status_t st = GIT_STATUS_CURRENT; - - if (head2idx) - st |= index_delta2status(head2idx); - - if (idx2wd) - st |= workdir_delta2status(status->idx2wd, idx2wd); - - return st; -} - -static int status_collect( - git_diff_delta *head2idx, - git_diff_delta *idx2wd, - void *payload) -{ - git_status_list *status = payload; - git_status_entry *status_entry; - - if (!status_is_included(status, head2idx, idx2wd)) - return 0; - - status_entry = git__malloc(sizeof(git_status_entry)); - GIT_ERROR_CHECK_ALLOC(status_entry); - - status_entry->status = status_compute(status, head2idx, idx2wd); - status_entry->head_to_index = head2idx; - status_entry->index_to_workdir = idx2wd; - - return git_vector_insert(&status->paired, status_entry); -} - -GIT_INLINE(int) status_entry_cmp_base( - const void *a, - const void *b, - int (*strcomp)(const char *a, const char *b)) -{ - const git_status_entry *entry_a = a; - const git_status_entry *entry_b = b; - const git_diff_delta *delta_a, *delta_b; - - delta_a = entry_a->index_to_workdir ? entry_a->index_to_workdir : - entry_a->head_to_index; - delta_b = entry_b->index_to_workdir ? entry_b->index_to_workdir : - entry_b->head_to_index; - - if (!delta_a && delta_b) - return -1; - if (delta_a && !delta_b) - return 1; - if (!delta_a && !delta_b) - return 0; - - return strcomp(delta_a->new_file.path, delta_b->new_file.path); -} - -static int status_entry_icmp(const void *a, const void *b) -{ - return status_entry_cmp_base(a, b, git__strcasecmp); -} - -static int status_entry_cmp(const void *a, const void *b) -{ - return status_entry_cmp_base(a, b, git__strcmp); -} - -static git_status_list *git_status_list_alloc(git_index *index) -{ - git_status_list *status = NULL; - int (*entrycmp)(const void *a, const void *b); - - if (!(status = git__calloc(1, sizeof(git_status_list)))) - return NULL; - - entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp; - - if (git_vector_init(&status->paired, 0, entrycmp) < 0) { - git__free(status); - return NULL; - } - - return status; -} - -static int status_validate_options(const git_status_options *opts) -{ - if (!opts) - return 0; - - GIT_ERROR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options"); - - if (opts->show > GIT_STATUS_SHOW_WORKDIR_ONLY) { - git_error_set(GIT_ERROR_INVALID, "unknown status 'show' option"); - return -1; - } - - if ((opts->flags & GIT_STATUS_OPT_NO_REFRESH) != 0 && - (opts->flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) { - git_error_set(GIT_ERROR_INVALID, "updating index from status " - "is not allowed when index refresh is disabled"); - return -1; - } - - return 0; -} - -int git_status_list_new( - git_status_list **out, - git_repository *repo, - const git_status_options *opts) -{ - git_index *index = NULL; - git_status_list *status = NULL; - git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopt = GIT_DIFF_FIND_OPTIONS_INIT; - git_tree *head = NULL; - git_status_show_t show = - opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - int error = 0; - unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS; - - *out = NULL; - - if (status_validate_options(opts) < 0) - return -1; - - if ((error = git_repository__ensure_not_bare(repo, "status")) < 0 || - (error = git_repository_index(&index, repo)) < 0) - return error; - - if (opts != NULL && opts->baseline != NULL) { - head = opts->baseline; - } else { - /* if there is no HEAD, that's okay - we'll make an empty iterator */ - if ((error = git_repository_head_tree(&head, repo)) < 0) { - if (error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH) - goto done; - git_error_clear(); - } - } - - /* refresh index from disk unless prevented */ - if ((flags & GIT_STATUS_OPT_NO_REFRESH) == 0 && - git_index_read_safely(index) < 0) - git_error_clear(); - - status = git_status_list_alloc(index); - GIT_ERROR_CHECK_ALLOC(status); - - if (opts) { - memcpy(&status->opts, opts, sizeof(git_status_options)); - memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); - } - - diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE; - findopt.flags = GIT_DIFF_FIND_FOR_UNTRACKED; - - if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED; - if ((flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED; - if ((flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED; - if ((flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; - if ((flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH; - if ((flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS; - if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; - if ((flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_UPDATE_INDEX; - if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE; - if ((flags & GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED) != 0) - diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED; - - if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0) - findopt.flags = findopt.flags | - GIT_DIFF_FIND_AND_BREAK_REWRITES | - GIT_DIFF_FIND_RENAMES_FROM_REWRITES | - GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY; - - if (opts != NULL && opts->rename_threshold != 0) - findopt.rename_threshold = opts->rename_threshold; - - if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { - if ((error = git_diff_tree_to_index( - &status->head2idx, repo, head, index, &diffopt)) < 0) - goto done; - - if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && - (error = git_diff_find_similar(status->head2idx, &findopt)) < 0) - goto done; - } - - if (show != GIT_STATUS_SHOW_INDEX_ONLY) { - if ((error = git_diff_index_to_workdir( - &status->idx2wd, repo, index, &diffopt)) < 0) { - goto done; - } - - if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && - (error = git_diff_find_similar(status->idx2wd, &findopt)) < 0) - goto done; - } - - error = git_diff__paired_foreach( - status->head2idx, status->idx2wd, status_collect, status); - if (error < 0) - goto done; - - if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY) - git_vector_set_cmp(&status->paired, status_entry_cmp); - if (flags & GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY) - git_vector_set_cmp(&status->paired, status_entry_icmp); - - if ((flags & - (GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR | - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY | - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)) != 0) - git_vector_sort(&status->paired); - -done: - if (error < 0) { - git_status_list_free(status); - status = NULL; - } - - *out = status; - - if (opts == NULL || opts->baseline != head) - git_tree_free(head); - git_index_free(index); - - return error; -} - -size_t git_status_list_entrycount(git_status_list *status) -{ - GIT_ASSERT_ARG_WITH_RETVAL(status, 0); - - return status->paired.length; -} - -const git_status_entry *git_status_byindex(git_status_list *status, size_t i) -{ - GIT_ASSERT_ARG_WITH_RETVAL(status, NULL); - - return git_vector_get(&status->paired, i); -} - -void git_status_list_free(git_status_list *status) -{ - if (status == NULL) - return; - - git_diff_free(status->head2idx); - git_diff_free(status->idx2wd); - - git_vector_free_deep(&status->paired); - - git__memzero(status, sizeof(*status)); - git__free(status); -} - -int git_status_foreach_ext( - git_repository *repo, - const git_status_options *opts, - git_status_cb cb, - void *payload) -{ - git_status_list *status; - const git_status_entry *status_entry; - size_t i; - int error = 0; - - if ((error = git_status_list_new(&status, repo, opts)) < 0) { - return error; - } - - git_vector_foreach(&status->paired, i, status_entry) { - const char *path = status_entry->head_to_index ? - status_entry->head_to_index->old_file.path : - status_entry->index_to_workdir->old_file.path; - - if ((error = cb(path, status_entry->status, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - - git_status_list_free(status); - - return error; -} - -int git_status_foreach(git_repository *repo, git_status_cb cb, void *payload) -{ - return git_status_foreach_ext(repo, NULL, cb, payload); -} - -struct status_file_info { - char *expected; - unsigned int count; - unsigned int status; - int wildmatch_flags; - int ambiguous; -}; - -static int get_one_status(const char *path, unsigned int status, void *data) -{ - struct status_file_info *sfi = data; - int (*strcomp)(const char *a, const char *b); - - sfi->count++; - sfi->status = status; - - strcomp = (sfi->wildmatch_flags & WM_CASEFOLD) ? git__strcasecmp : git__strcmp; - - if (sfi->count > 1 || - (strcomp(sfi->expected, path) != 0 && - wildmatch(sfi->expected, path, sfi->wildmatch_flags) != 0)) - { - sfi->ambiguous = true; - return GIT_EAMBIGUOUS; /* git_error_set will be done by caller */ - } - - return 0; -} - -int git_status_file( - unsigned int *status_flags, - git_repository *repo, - const char *path) -{ - int error; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_file_info sfi = {0}; - git_index *index; - - GIT_ASSERT_ARG(status_flags); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(path); - - if ((error = git_repository_index__weakptr(&index, repo)) < 0) - return error; - - if ((sfi.expected = git__strdup(path)) == NULL) - return -1; - if (index->ignore_case) - sfi.wildmatch_flags = WM_CASEFOLD; - - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS | - GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | - GIT_STATUS_OPT_INCLUDE_UNMODIFIED | - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; - opts.pathspec.count = 1; - opts.pathspec.strings = &sfi.expected; - - error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi); - - if (error < 0 && sfi.ambiguous) { - git_error_set(GIT_ERROR_INVALID, - "ambiguous path '%s' given to git_status_file", sfi.expected); - error = GIT_EAMBIGUOUS; - } - - if (!error && !sfi.count) { - git_error_set(GIT_ERROR_INVALID, - "attempt to get status of nonexistent file '%s'", path); - error = GIT_ENOTFOUND; - } - - *status_flags = sfi.status; - - git__free(sfi.expected); - - return error; -} - -int git_status_should_ignore( - int *ignored, - git_repository *repo, - const char *path) -{ - return git_ignore_path_is_ignored(ignored, repo, path); -} - -int git_status_options_init(git_status_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_status_options, GIT_STATUS_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_status_init_options(git_status_options *opts, unsigned int version) -{ - return git_status_options_init(opts, version); -} -#endif - -int git_status_list_get_perfdata( - git_diff_perfdata *out, const git_status_list *status) -{ - GIT_ASSERT_ARG(out); - - GIT_ERROR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata"); - - out->stat_calls = 0; - out->oid_calculations = 0; - - if (status->head2idx) { - out->stat_calls += status->head2idx->perf.stat_calls; - out->oid_calculations += status->head2idx->perf.oid_calculations; - } - if (status->idx2wd) { - out->stat_calls += status->idx2wd->perf.stat_calls; - out->oid_calculations += status->idx2wd->perf.oid_calculations; - } - - return 0; -} - diff --git a/src/status.h b/src/status.h deleted file mode 100644 index 907479a22..000000000 --- a/src/status.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_status_h__ -#define INCLUDE_status_h__ - -#include "common.h" - -#include "diff.h" -#include "git2/status.h" -#include "git2/diff.h" - -struct git_status_list { - git_status_options opts; - - git_diff *head2idx; - git_diff *idx2wd; - - git_vector paired; -}; - -#endif diff --git a/src/str.c b/src/str.c deleted file mode 100644 index 0d405bfda..000000000 --- a/src/str.c +++ /dev/null @@ -1,1372 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "str.h" -#include "posix.h" -#include - -/* Used as default value for git_str->ptr so that people can always - * assume ptr is non-NULL and zero terminated even for new git_strs. - */ -char git_str__initstr[1]; - -char git_str__oom[1]; - -#define ENSURE_SIZE(b, d) \ - if ((b)->ptr == git_str__oom || \ - ((d) > (b)->asize && git_str_grow((b), (d)) < 0))\ - return -1; - - -int git_str_init(git_str *buf, size_t initial_size) -{ - buf->asize = 0; - buf->size = 0; - buf->ptr = git_str__initstr; - - ENSURE_SIZE(buf, initial_size); - - return 0; -} - -int git_str_try_grow( - git_str *buf, size_t target_size, bool mark_oom) -{ - char *new_ptr; - size_t new_size; - - if (buf->ptr == git_str__oom) - return -1; - - if (buf->asize == 0 && buf->size != 0) { - git_error_set(GIT_ERROR_INVALID, "cannot grow a borrowed buffer"); - return GIT_EINVALID; - } - - if (!target_size) - target_size = buf->size; - - if (target_size <= buf->asize) - return 0; - - if (buf->asize == 0) { - new_size = target_size; - new_ptr = NULL; - } else { - new_size = buf->asize; - /* - * Grow the allocated buffer by 1.5 to allow - * re-use of memory holes resulting from the - * realloc. If this is still too small, then just - * use the target size. - */ - if ((new_size = (new_size << 1) - (new_size >> 1)) < target_size) - new_size = target_size; - new_ptr = buf->ptr; - } - - /* round allocation up to multiple of 8 */ - new_size = (new_size + 7) & ~7; - - if (new_size < buf->size) { - if (mark_oom) { - if (buf->ptr && buf->ptr != git_str__initstr) - git__free(buf->ptr); - buf->ptr = git_str__oom; - } - - git_error_set_oom(); - return -1; - } - - new_ptr = git__realloc(new_ptr, new_size); - - if (!new_ptr) { - if (mark_oom) { - if (buf->ptr && (buf->ptr != git_str__initstr)) - git__free(buf->ptr); - buf->ptr = git_str__oom; - } - return -1; - } - - buf->asize = new_size; - buf->ptr = new_ptr; - - /* truncate the existing buffer size if necessary */ - if (buf->size >= buf->asize) - buf->size = buf->asize - 1; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -int git_str_grow(git_str *buffer, size_t target_size) -{ - return git_str_try_grow(buffer, target_size, true); -} - -int git_str_grow_by(git_str *buffer, size_t additional_size) -{ - size_t newsize; - - if (GIT_ADD_SIZET_OVERFLOW(&newsize, buffer->size, additional_size)) { - buffer->ptr = git_str__oom; - return -1; - } - - return git_str_try_grow(buffer, newsize, true); -} - -void git_str_dispose(git_str *buf) -{ - if (!buf) return; - - if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_str__oom) - git__free(buf->ptr); - - git_str_init(buf, 0); -} - -void git_str_clear(git_str *buf) -{ - buf->size = 0; - - if (!buf->ptr) { - buf->ptr = git_str__initstr; - buf->asize = 0; - } - - if (buf->asize > 0) - buf->ptr[0] = '\0'; -} - -int git_str_set(git_str *buf, const void *data, size_t len) -{ - size_t alloclen; - - if (len == 0 || data == NULL) { - git_str_clear(buf); - } else { - if (data != buf->ptr) { - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); - ENSURE_SIZE(buf, alloclen); - memmove(buf->ptr, data, len); - } - - buf->size = len; - if (buf->asize > buf->size) - buf->ptr[buf->size] = '\0'; - - } - return 0; -} - -int git_str_sets(git_str *buf, const char *string) -{ - return git_str_set(buf, string, string ? strlen(string) : 0); -} - -int git_str_putc(git_str *buf, char c) -{ - size_t new_size; - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, 2); - ENSURE_SIZE(buf, new_size); - buf->ptr[buf->size++] = c; - buf->ptr[buf->size] = '\0'; - return 0; -} - -int git_str_putcn(git_str *buf, char c, size_t len) -{ - size_t new_size; - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - memset(buf->ptr + buf->size, c, len); - buf->size += len; - buf->ptr[buf->size] = '\0'; - return 0; -} - -int git_str_put(git_str *buf, const char *data, size_t len) -{ - if (len) { - size_t new_size; - - GIT_ASSERT_ARG(data); - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - memmove(buf->ptr + buf->size, data, len); - buf->size += len; - buf->ptr[buf->size] = '\0'; - } - return 0; -} - -int git_str_puts(git_str *buf, const char *string) -{ - GIT_ASSERT_ARG(string); - - return git_str_put(buf, string, strlen(string)); -} - -static char hex_encode[] = "0123456789abcdef"; - -int git_str_encode_hexstr(git_str *str, const char *data, size_t len) -{ - size_t new_size, i; - char *s; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&new_size, len, 2); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - - if (git_str_grow_by(str, new_size) < 0) - return -1; - - s = str->ptr + str->size; - - for (i = 0; i < len; i++) { - *s++ = hex_encode[(data[i] & 0xf0) >> 4]; - *s++ = hex_encode[(data[i] & 0x0f)]; - } - - str->size += (len * 2); - str->ptr[str->size] = '\0'; - - return 0; -} - -static const char base64_encode[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -int git_str_encode_base64(git_str *buf, const char *data, size_t len) -{ - size_t extra = len % 3; - uint8_t *write, a, b, c; - const uint8_t *read = (const uint8_t *)data; - size_t blocks = (len / 3) + !!extra, alloclen; - - GIT_ERROR_CHECK_ALLOC_ADD(&blocks, blocks, 1); - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 4); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size); - - ENSURE_SIZE(buf, alloclen); - write = (uint8_t *)&buf->ptr[buf->size]; - - /* convert each run of 3 bytes into 4 output bytes */ - for (len -= extra; len > 0; len -= 3) { - a = *read++; - b = *read++; - c = *read++; - - *write++ = base64_encode[a >> 2]; - *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; - *write++ = base64_encode[(b & 0x0f) << 2 | c >> 6]; - *write++ = base64_encode[c & 0x3f]; - } - - if (extra > 0) { - a = *read++; - b = (extra > 1) ? *read++ : 0; - - *write++ = base64_encode[a >> 2]; - *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; - *write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '='; - *write++ = '='; - } - - buf->size = ((char *)write) - buf->ptr; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -/* The inverse of base64_encode */ -static const int8_t base64_decode[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 -}; - -int git_str_decode_base64(git_str *buf, const char *base64, size_t len) -{ - size_t i; - int8_t a, b, c, d; - size_t orig_size = buf->size, new_size; - - if (len % 4) { - git_error_set(GIT_ERROR_INVALID, "invalid base64 input"); - return -1; - } - - GIT_ASSERT_ARG(len % 4 == 0); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - - for (i = 0; i < len; i += 4) { - if ((a = base64_decode[(unsigned char)base64[i]]) < 0 || - (b = base64_decode[(unsigned char)base64[i+1]]) < 0 || - (c = base64_decode[(unsigned char)base64[i+2]]) < 0 || - (d = base64_decode[(unsigned char)base64[i+3]]) < 0) { - buf->size = orig_size; - buf->ptr[buf->size] = '\0'; - - git_error_set(GIT_ERROR_INVALID, "invalid base64 input"); - return -1; - } - - buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4); - buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); - buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f); - } - - buf->ptr[buf->size] = '\0'; - return 0; -} - -static const char base85_encode[] = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; - -int git_str_encode_base85(git_str *buf, const char *data, size_t len) -{ - size_t blocks = (len / 4) + !!(len % 4), alloclen; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 5); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - - ENSURE_SIZE(buf, alloclen); - - while (len) { - uint32_t acc = 0; - char b85[5]; - int i; - - for (i = 24; i >= 0; i -= 8) { - uint8_t ch = *data++; - acc |= (uint32_t)ch << i; - - if (--len == 0) - break; - } - - for (i = 4; i >= 0; i--) { - int val = acc % 85; - acc /= 85; - - b85[i] = base85_encode[val]; - } - - for (i = 0; i < 5; i++) - buf->ptr[buf->size++] = b85[i]; - } - - buf->ptr[buf->size] = '\0'; - - return 0; -} - -/* The inverse of base85_encode */ -static const int8_t base85_decode[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 73, 74, 75, 76, 77, - 78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80, - 81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 -}; - -int git_str_decode_base85( - git_str *buf, - const char *base85, - size_t base85_len, - size_t output_len) -{ - size_t orig_size = buf->size, new_size; - - if (base85_len % 5 || - output_len > base85_len * 4 / 5) { - git_error_set(GIT_ERROR_INVALID, "invalid base85 input"); - return -1; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - - while (output_len) { - unsigned acc = 0; - int de, cnt = 4; - unsigned char ch; - do { - ch = *base85++; - de = base85_decode[ch]; - if (--de < 0) - goto on_error; - - acc = acc * 85 + de; - } while (--cnt); - ch = *base85++; - de = base85_decode[ch]; - if (--de < 0) - goto on_error; - - /* Detect overflow. */ - if (0xffffffff / 85 < acc || - 0xffffffff - de < (acc *= 85)) - goto on_error; - - acc += de; - - cnt = (output_len < 4) ? (int)output_len : 4; - output_len -= cnt; - do { - acc = (acc << 8) | (acc >> 24); - buf->ptr[buf->size++] = acc; - } while (--cnt); - } - - buf->ptr[buf->size] = 0; - - return 0; - -on_error: - buf->size = orig_size; - buf->ptr[buf->size] = '\0'; - - git_error_set(GIT_ERROR_INVALID, "invalid base85 input"); - return -1; -} - -#define HEX_DECODE(c) ((c | 32) % 39 - 9) - -int git_str_decode_percent( - git_str *buf, - const char *str, - size_t str_len) -{ - size_t str_pos, new_size; - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, str_len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - - for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) { - if (str[str_pos] == '%' && - str_len > str_pos + 2 && - isxdigit(str[str_pos + 1]) && - isxdigit(str[str_pos + 2])) { - buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) + - HEX_DECODE(str[str_pos + 2]); - str_pos += 2; - } else { - buf->ptr[buf->size] = str[str_pos]; - } - } - - buf->ptr[buf->size] = '\0'; - return 0; -} - -int git_str_vprintf(git_str *buf, const char *format, va_list ap) -{ - size_t expected_size, new_size; - int len; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&expected_size, strlen(format), 2); - GIT_ERROR_CHECK_ALLOC_ADD(&expected_size, expected_size, buf->size); - ENSURE_SIZE(buf, expected_size); - - while (1) { - va_list args; - va_copy(args, ap); - - len = p_vsnprintf( - buf->ptr + buf->size, - buf->asize - buf->size, - format, args - ); - - va_end(args); - - if (len < 0) { - git__free(buf->ptr); - buf->ptr = git_str__oom; - return -1; - } - - if ((size_t)len + 1 <= buf->asize - buf->size) { - buf->size += len; - break; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - } - - return 0; -} - -int git_str_printf(git_str *buf, const char *format, ...) -{ - int r; - va_list ap; - - va_start(ap, format); - r = git_str_vprintf(buf, format, ap); - va_end(ap); - - return r; -} - -int git_str_copy_cstr(char *data, size_t datasize, const git_str *buf) -{ - size_t copylen; - - GIT_ASSERT_ARG(data); - GIT_ASSERT_ARG(datasize); - GIT_ASSERT_ARG(buf); - - data[0] = '\0'; - - if (buf->size == 0 || buf->asize <= 0) - return 0; - - copylen = buf->size; - if (copylen > datasize - 1) - copylen = datasize - 1; - memmove(data, buf->ptr, copylen); - data[copylen] = '\0'; - - return 0; -} - -void git_str_consume_bytes(git_str *buf, size_t len) -{ - git_str_consume(buf, buf->ptr + len); -} - -void git_str_consume(git_str *buf, const char *end) -{ - if (end > buf->ptr && end <= buf->ptr + buf->size) { - size_t consumed = end - buf->ptr; - memmove(buf->ptr, end, buf->size - consumed); - buf->size -= consumed; - buf->ptr[buf->size] = '\0'; - } -} - -void git_str_truncate(git_str *buf, size_t len) -{ - if (len >= buf->size) - return; - - buf->size = len; - if (buf->size < buf->asize) - buf->ptr[buf->size] = '\0'; -} - -void git_str_shorten(git_str *buf, size_t amount) -{ - if (buf->size > amount) - git_str_truncate(buf, buf->size - amount); - else - git_str_clear(buf); -} - -void git_str_truncate_at_char(git_str *buf, char separator) -{ - ssize_t idx = git_str_find(buf, separator); - if (idx >= 0) - git_str_truncate(buf, (size_t)idx); -} - -void git_str_rtruncate_at_char(git_str *buf, char separator) -{ - ssize_t idx = git_str_rfind_next(buf, separator); - git_str_truncate(buf, idx < 0 ? 0 : (size_t)idx); -} - -void git_str_swap(git_str *str_a, git_str *str_b) -{ - git_str t = *str_a; - *str_a = *str_b; - *str_b = t; -} - -char *git_str_detach(git_str *buf) -{ - char *data = buf->ptr; - - if (buf->asize == 0 || buf->ptr == git_str__oom) - return NULL; - - git_str_init(buf, 0); - - return data; -} - -int git_str_attach(git_str *buf, char *ptr, size_t asize) -{ - git_str_dispose(buf); - - if (ptr) { - buf->ptr = ptr; - buf->size = strlen(ptr); - if (asize) - buf->asize = (asize < buf->size) ? buf->size + 1 : asize; - else /* pass 0 to fall back on strlen + 1 */ - buf->asize = buf->size + 1; - } - - ENSURE_SIZE(buf, asize); - return 0; -} - -void git_str_attach_notowned(git_str *buf, const char *ptr, size_t size) -{ - if (git_str_is_allocated(buf)) - git_str_dispose(buf); - - if (!size) { - git_str_init(buf, 0); - } else { - buf->ptr = (char *)ptr; - buf->asize = 0; - buf->size = size; - } -} - -int git_str_join_n(git_str *buf, char separator, int nbuf, ...) -{ - va_list ap; - int i; - size_t total_size = 0, original_size = buf->size; - char *out, *original = buf->ptr; - - if (buf->size > 0 && buf->ptr[buf->size - 1] != separator) - ++total_size; /* space for initial separator */ - - /* Make two passes to avoid multiple reallocation */ - - va_start(ap, nbuf); - for (i = 0; i < nbuf; ++i) { - const char *segment; - size_t segment_len; - - segment = va_arg(ap, const char *); - if (!segment) - continue; - - segment_len = strlen(segment); - - GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, segment_len); - - if (segment_len == 0 || segment[segment_len - 1] != separator) - GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1); - } - va_end(ap); - - /* expand buffer if needed */ - if (total_size == 0) - return 0; - - GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1); - if (git_str_grow_by(buf, total_size) < 0) - return -1; - - out = buf->ptr + buf->size; - - /* append separator to existing buf if needed */ - if (buf->size > 0 && out[-1] != separator) - *out++ = separator; - - va_start(ap, nbuf); - for (i = 0; i < nbuf; ++i) { - const char *segment; - size_t segment_len; - - segment = va_arg(ap, const char *); - if (!segment) - continue; - - /* deal with join that references buffer's original content */ - if (segment >= original && segment < original + original_size) { - size_t offset = (segment - original); - segment = buf->ptr + offset; - segment_len = original_size - offset; - } else { - segment_len = strlen(segment); - } - - /* skip leading separators */ - if (out > buf->ptr && out[-1] == separator) - while (segment_len > 0 && *segment == separator) { - segment++; - segment_len--; - } - - /* copy over next buffer */ - if (segment_len > 0) { - memmove(out, segment, segment_len); - out += segment_len; - } - - /* append trailing separator (except for last item) */ - if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator) - *out++ = separator; - } - va_end(ap); - - /* set size based on num characters actually written */ - buf->size = out - buf->ptr; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -int git_str_join( - git_str *buf, - char separator, - const char *str_a, - const char *str_b) -{ - size_t strlen_a = str_a ? strlen(str_a) : 0; - size_t strlen_b = strlen(str_b); - size_t alloc_len; - int need_sep = 0; - ssize_t offset_a = -1; - - /* not safe to have str_b point internally to the buffer */ - if (buf->size) - GIT_ASSERT_ARG(str_b < buf->ptr || str_b >= buf->ptr + buf->size); - - /* figure out if we need to insert a separator */ - if (separator && strlen_a) { - while (*str_b == separator) { str_b++; strlen_b--; } - if (str_a[strlen_a - 1] != separator) - need_sep = 1; - } - - /* str_a could be part of the buffer */ - if (buf->size && str_a >= buf->ptr && str_a < buf->ptr + buf->size) - offset_a = str_a - buf->ptr; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); - ENSURE_SIZE(buf, alloc_len); - - /* fix up internal pointers */ - if (offset_a >= 0) - str_a = buf->ptr + offset_a; - - /* do the actual copying */ - if (offset_a != 0 && str_a) - memmove(buf->ptr, str_a, strlen_a); - if (need_sep) - buf->ptr[strlen_a] = separator; - memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b); - - buf->size = strlen_a + strlen_b + need_sep; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -int git_str_join3( - git_str *buf, - char separator, - const char *str_a, - const char *str_b, - const char *str_c) -{ - size_t len_a = strlen(str_a), - len_b = strlen(str_b), - len_c = strlen(str_c), - len_total; - int sep_a = 0, sep_b = 0; - char *tgt; - - /* for this function, disallow pointers into the existing buffer */ - GIT_ASSERT(str_a < buf->ptr || str_a >= buf->ptr + buf->size); - GIT_ASSERT(str_b < buf->ptr || str_b >= buf->ptr + buf->size); - GIT_ASSERT(str_c < buf->ptr || str_c >= buf->ptr + buf->size); - - if (separator) { - if (len_a > 0) { - while (*str_b == separator) { str_b++; len_b--; } - sep_a = (str_a[len_a - 1] != separator); - } - if (len_a > 0 || len_b > 0) - while (*str_c == separator) { str_c++; len_c--; } - if (len_b > 0) - sep_b = (str_b[len_b - 1] != separator); - } - - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_a, sep_a); - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_b); - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b); - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_c); - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, 1); - ENSURE_SIZE(buf, len_total); - - tgt = buf->ptr; - - if (len_a) { - memcpy(tgt, str_a, len_a); - tgt += len_a; - } - if (sep_a) - *tgt++ = separator; - if (len_b) { - memcpy(tgt, str_b, len_b); - tgt += len_b; - } - if (sep_b) - *tgt++ = separator; - if (len_c) - memcpy(tgt, str_c, len_c); - - buf->size = len_a + sep_a + len_b + sep_b + len_c; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -void git_str_rtrim(git_str *buf) -{ - while (buf->size > 0) { - if (!git__isspace(buf->ptr[buf->size - 1])) - break; - - buf->size--; - } - - if (buf->asize > buf->size) - buf->ptr[buf->size] = '\0'; -} - -int git_str_cmp(const git_str *a, const git_str *b) -{ - int result = memcmp(a->ptr, b->ptr, min(a->size, b->size)); - return (result != 0) ? result : - (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0; -} - -int git_str_splice( - git_str *buf, - size_t where, - size_t nb_to_remove, - const char *data, - size_t nb_to_insert) -{ - char *splice_loc; - size_t new_size, alloc_size; - - GIT_ASSERT(buf); - GIT_ASSERT(where <= buf->size); - GIT_ASSERT(nb_to_remove <= buf->size - where); - - splice_loc = buf->ptr + where; - - /* Ported from git.git - * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176 - */ - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (buf->size - nb_to_remove), nb_to_insert); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, new_size, 1); - ENSURE_SIZE(buf, alloc_size); - - memmove(splice_loc + nb_to_insert, - splice_loc + nb_to_remove, - buf->size - where - nb_to_remove); - - memcpy(splice_loc, data, nb_to_insert); - - buf->size = new_size; - buf->ptr[buf->size] = '\0'; - return 0; -} - -/* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */ -int git_str_quote(git_str *buf) -{ - const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' }; - git_str quoted = GIT_STR_INIT; - size_t i = 0; - bool quote = false; - int error = 0; - - /* walk to the first char that needs quoting */ - if (buf->size && buf->ptr[0] == '!') - quote = true; - - for (i = 0; !quote && i < buf->size; i++) { - if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' || - buf->ptr[i] < ' ' || buf->ptr[i] > '~') { - quote = true; - break; - } - } - - if (!quote) - goto done; - - git_str_putc("ed, '"'); - git_str_put("ed, buf->ptr, i); - - for (; i < buf->size; i++) { - /* whitespace - use the map above, which is ordered by ascii value */ - if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') { - git_str_putc("ed, '\\'); - git_str_putc("ed, whitespace[buf->ptr[i] - '\a']); - } - - /* double quote and backslash must be escaped */ - else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') { - git_str_putc("ed, '\\'); - git_str_putc("ed, buf->ptr[i]); - } - - /* escape anything unprintable as octal */ - else if (buf->ptr[i] != ' ' && - (buf->ptr[i] < '!' || buf->ptr[i] > '~')) { - git_str_printf("ed, "\\%03o", (unsigned char)buf->ptr[i]); - } - - /* yay, printable! */ - else { - git_str_putc("ed, buf->ptr[i]); - } - } - - git_str_putc("ed, '"'); - - if (git_str_oom("ed)) { - error = -1; - goto done; - } - - git_str_swap("ed, buf); - -done: - git_str_dispose("ed); - return error; -} - -/* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */ -int git_str_unquote(git_str *buf) -{ - size_t i, j; - char ch; - - git_str_rtrim(buf); - - if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"') - goto invalid; - - for (i = 0, j = 1; j < buf->size-1; i++, j++) { - ch = buf->ptr[j]; - - if (ch == '\\') { - if (j == buf->size-2) - goto invalid; - - ch = buf->ptr[++j]; - - switch (ch) { - /* \" or \\ simply copy the char in */ - case '"': case '\\': - break; - - /* add the appropriate escaped char */ - case 'a': ch = '\a'; break; - case 'b': ch = '\b'; break; - case 'f': ch = '\f'; break; - case 'n': ch = '\n'; break; - case 'r': ch = '\r'; break; - case 't': ch = '\t'; break; - case 'v': ch = '\v'; break; - - /* \xyz digits convert to the char*/ - case '0': case '1': case '2': case '3': - if (j == buf->size-3) { - git_error_set(GIT_ERROR_INVALID, - "truncated quoted character \\%c", ch); - return -1; - } - - if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' || - buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') { - git_error_set(GIT_ERROR_INVALID, - "truncated quoted character \\%c%c%c", - buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]); - return -1; - } - - ch = ((buf->ptr[j] - '0') << 6) | - ((buf->ptr[j+1] - '0') << 3) | - (buf->ptr[j+2] - '0'); - j += 2; - break; - - default: - git_error_set(GIT_ERROR_INVALID, "invalid quoted character \\%c", ch); - return -1; - } - } - - buf->ptr[i] = ch; - } - - buf->ptr[i] = '\0'; - buf->size = i; - - return 0; - -invalid: - git_error_set(GIT_ERROR_INVALID, "invalid quoted line"); - return -1; -} - -int git_str_puts_escaped( - git_str *buf, - const char *string, - const char *esc_chars, - const char *esc_with) -{ - const char *scan; - size_t total = 0, esc_len = strlen(esc_with), count, alloclen; - - if (!string) - return 0; - - for (scan = string; *scan; ) { - /* count run of non-escaped characters */ - count = strcspn(scan, esc_chars); - total += count; - scan += count; - /* count run of escaped characters */ - count = strspn(scan, esc_chars); - total += count * (esc_len + 1); - scan += count; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, total, 1); - if (git_str_grow_by(buf, alloclen) < 0) - return -1; - - for (scan = string; *scan; ) { - count = strcspn(scan, esc_chars); - - memmove(buf->ptr + buf->size, scan, count); - scan += count; - buf->size += count; - - for (count = strspn(scan, esc_chars); count > 0; --count) { - /* copy escape sequence */ - memmove(buf->ptr + buf->size, esc_with, esc_len); - buf->size += esc_len; - /* copy character to be escaped */ - buf->ptr[buf->size] = *scan; - buf->size++; - scan++; - } - } - - buf->ptr[buf->size] = '\0'; - - return 0; -} - -void git_str_unescape(git_str *buf) -{ - buf->size = git__unescape(buf->ptr); -} - -int git_str_crlf_to_lf(git_str *tgt, const git_str *src) -{ - const char *scan = src->ptr; - const char *scan_end = src->ptr + src->size; - const char *next = memchr(scan, '\r', src->size); - size_t new_size; - char *out; - - GIT_ASSERT(tgt != src); - - if (!next) - return git_str_set(tgt, src->ptr, src->size); - - /* reduce reallocs while in the loop */ - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, src->size, 1); - if (git_str_grow(tgt, new_size) < 0) - return -1; - - out = tgt->ptr; - tgt->size = 0; - - /* Find the next \r and copy whole chunk up to there to tgt */ - for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) { - if (next > scan) { - size_t copylen = (size_t)(next - scan); - memcpy(out, scan, copylen); - out += copylen; - } - - /* Do not drop \r unless it is followed by \n */ - if (next + 1 == scan_end || next[1] != '\n') - *out++ = '\r'; - } - - /* Copy remaining input into dest */ - if (scan < scan_end) { - size_t remaining = (size_t)(scan_end - scan); - memcpy(out, scan, remaining); - out += remaining; - } - - tgt->size = (size_t)(out - tgt->ptr); - tgt->ptr[tgt->size] = '\0'; - - return 0; -} - -int git_str_lf_to_crlf(git_str *tgt, const git_str *src) -{ - const char *start = src->ptr; - const char *end = start + src->size; - const char *scan = start; - const char *next = memchr(scan, '\n', src->size); - size_t alloclen; - - GIT_ASSERT(tgt != src); - - if (!next) - return git_str_set(tgt, src->ptr, src->size); - - /* attempt to reduce reallocs while in the loop */ - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, src->size, src->size >> 4); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - if (git_str_grow(tgt, alloclen) < 0) - return -1; - tgt->size = 0; - - for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) { - size_t copylen = next - scan; - - /* if we find mixed line endings, carry on */ - if (copylen && next[-1] == '\r') - copylen--; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, copylen, 3); - if (git_str_grow_by(tgt, alloclen) < 0) - return -1; - - if (copylen) { - memcpy(tgt->ptr + tgt->size, scan, copylen); - tgt->size += copylen; - } - - tgt->ptr[tgt->size++] = '\r'; - tgt->ptr[tgt->size++] = '\n'; - } - - tgt->ptr[tgt->size] = '\0'; - return git_str_put(tgt, scan, end - scan); -} - -int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count) -{ - size_t i; - const char *str, *pfx; - - git_str_clear(buf); - - if (!strings || !count) - return 0; - - /* initialize common prefix to first string */ - if (git_str_sets(buf, strings[0]) < 0) - return -1; - - /* go through the rest of the strings, truncating to shared prefix */ - for (i = 1; i < count; ++i) { - - for (str = strings[i], pfx = buf->ptr; - *str && *str == *pfx; - str++, pfx++) - /* scanning */; - - git_str_truncate(buf, pfx - buf->ptr); - - if (!buf->size) - break; - } - - return 0; -} - -int git_str_is_binary(const git_str *buf) -{ - const char *scan = buf->ptr, *end = buf->ptr + buf->size; - git_str_bom_t bom; - int printable = 0, nonprintable = 0; - - scan += git_str_detect_bom(&bom, buf); - - if (bom > GIT_STR_BOM_UTF8) - return 1; - - while (scan < end) { - unsigned char c = *scan++; - - /* Printable characters are those above SPACE (0x1F) excluding DEL, - * and including BS, ESC and FF. - */ - if ((c > 0x1F && c != 127) || c == '\b' || c == '\033' || c == '\014') - printable++; - else if (c == '\0') - return true; - else if (!git__isspace(c)) - nonprintable++; - } - - return ((printable >> 7) < nonprintable); -} - -int git_str_contains_nul(const git_str *buf) -{ - return (memchr(buf->ptr, '\0', buf->size) != NULL); -} - -int git_str_detect_bom(git_str_bom_t *bom, const git_str *buf) -{ - const char *ptr; - size_t len; - - *bom = GIT_STR_BOM_NONE; - /* need at least 2 bytes to look for any BOM */ - if (buf->size < 2) - return 0; - - ptr = buf->ptr; - len = buf->size; - - switch (*ptr++) { - case 0: - if (len >= 4 && ptr[0] == 0 && ptr[1] == '\xFE' && ptr[2] == '\xFF') { - *bom = GIT_STR_BOM_UTF32_BE; - return 4; - } - break; - case '\xEF': - if (len >= 3 && ptr[0] == '\xBB' && ptr[1] == '\xBF') { - *bom = GIT_STR_BOM_UTF8; - return 3; - } - break; - case '\xFE': - if (*ptr == '\xFF') { - *bom = GIT_STR_BOM_UTF16_BE; - return 2; - } - break; - case '\xFF': - if (*ptr != '\xFE') - break; - if (len >= 4 && ptr[1] == 0 && ptr[2] == 0) { - *bom = GIT_STR_BOM_UTF32_LE; - return 4; - } else { - *bom = GIT_STR_BOM_UTF16_LE; - return 2; - } - break; - default: - break; - } - - return 0; -} - -bool git_str_gather_text_stats( - git_str_text_stats *stats, const git_str *buf, bool skip_bom) -{ - const char *scan = buf->ptr, *end = buf->ptr + buf->size; - int skip; - - memset(stats, 0, sizeof(*stats)); - - /* BOM detection */ - skip = git_str_detect_bom(&stats->bom, buf); - if (skip_bom) - scan += skip; - - /* Ignore EOF character */ - if (buf->size > 0 && end[-1] == '\032') - end--; - - /* Counting loop */ - while (scan < end) { - unsigned char c = *scan++; - - if (c > 0x1F && c != 0x7F) - stats->printable++; - else switch (c) { - case '\0': - stats->nul++; - stats->nonprintable++; - break; - case '\n': - stats->lf++; - break; - case '\r': - stats->cr++; - if (scan < end && *scan == '\n') - stats->crlf++; - break; - case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/ - stats->printable++; - break; - default: - stats->nonprintable++; - break; - } - } - - /* Treat files with a bare CR as binary */ - return (stats->cr != stats->crlf || stats->nul > 0 || - ((stats->printable >> 7) < stats->nonprintable)); -} diff --git a/src/str.h b/src/str.h deleted file mode 100644 index ef769ce2f..000000000 --- a/src/str.h +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_str_h__ -#define INCLUDE_str_h__ - -#include "common.h" - -struct git_str { - char *ptr; - size_t asize; - size_t size; -}; - -typedef enum { - GIT_STR_BOM_NONE = 0, - GIT_STR_BOM_UTF8 = 1, - GIT_STR_BOM_UTF16_LE = 2, - GIT_STR_BOM_UTF16_BE = 3, - GIT_STR_BOM_UTF32_LE = 4, - GIT_STR_BOM_UTF32_BE = 5 -} git_str_bom_t; - -typedef struct { - git_str_bom_t bom; /* BOM found at head of text */ - unsigned int nul, cr, lf, crlf; /* NUL, CR, LF and CRLF counts */ - unsigned int printable, nonprintable; /* These are just approximations! */ -} git_str_text_stats; - -extern char git_str__initstr[]; -extern char git_str__oom[]; - -/* Use to initialize string buffer structure when git_str is on stack */ -#define GIT_STR_INIT { git_str__initstr, 0, 0 } - -/** - * Static initializer for git_str from static string buffer - */ -#define GIT_STR_INIT_CONST(str, len) { (char *)(str), 0, (size_t)(len) } - -GIT_INLINE(bool) git_str_is_allocated(const git_str *str) -{ - return (str->ptr != NULL && str->asize > 0); -} - -/** - * Initialize a git_str structure. - * - * For the cases where GIT_STR_INIT cannot be used to do static - * initialization. - */ -extern int git_str_init(git_str *str, size_t initial_size); - -extern void git_str_dispose(git_str *str); - -/** - * Resize the string buffer allocation to make more space. - * - * This will attempt to grow the string buffer to accommodate the target - * size. The bstring buffer's `ptr` will be replaced with a newly - * allocated block of data. Be careful so that memory allocated by the - * caller is not lost. As a special variant, if you pass `target_size` as - * 0 and the memory is not allocated by libgit2, this will allocate a new - * buffer of size `size` and copy the external data into it. - * - * Currently, this will never shrink a buffer, only expand it. - * - * If the allocation fails, this will return an error and the buffer will be - * marked as invalid for future operations, invaliding the contents. - * - * @param str The buffer to be resized; may or may not be allocated yet - * @param target_size The desired available size - * @return 0 on success, -1 on allocation failure - */ -int git_str_grow(git_str *str, size_t target_size); - -/** - * Resize the buffer allocation to make more space. - * - * This will attempt to grow the string buffer to accommodate the - * additional size. It is similar to `git_str_grow`, but performs the - * new size calculation, checking for overflow. - * - * Like `git_str_grow`, if this is a user-supplied string buffer, - * this will allocate a new string uffer. - */ -extern int git_str_grow_by(git_str *str, size_t additional_size); - -/** - * Attempt to grow the buffer to hold at least `target_size` bytes. - * - * If the allocation fails, this will return an error. If `mark_oom` is - * true, this will mark the string buffer as invalid for future - * operations; if false, existing string buffer content will be preserved, - * but calling code must handle that string buffer was not expanded. If - * `preserve_external` is true, then any existing data pointed to be - * `ptr` even if `asize` is zero will be copied into the newly allocated - * string buffer. - */ -extern int git_str_try_grow( - git_str *str, size_t target_size, bool mark_oom); - -extern void git_str_swap(git_str *str_a, git_str *str_b); -extern char *git_str_detach(git_str *str); -extern int git_str_attach(git_str *str, char *ptr, size_t asize); - -/* Populates a `git_str` where the contents are not "owned" by the string - * buffer, and calls to `git_str_dispose` will not free the given str. - */ -extern void git_str_attach_notowned( - git_str *str, const char *ptr, size_t size); - -/** - * Test if there have been any reallocation failures with this git_str. - * - * Any function that writes to a git_str can fail due to memory allocation - * issues. If one fails, the git_str will be marked with an OOM error and - * further calls to modify the string buffer will fail. Check - * git_str_oom() at the end of your sequence and it will be true if you - * ran out of memory at any point with that string buffer. - * - * @return false if no error, true if allocation error - */ -GIT_INLINE(bool) git_str_oom(const git_str *str) -{ - return (str->ptr == git_str__oom); -} - -/* - * Functions below that return int value error codes will return 0 on - * success or -1 on failure (which generally means an allocation failed). - * Using a git_str where the allocation has failed with result in -1 from - * all further calls using that string buffer. As a result, you can - * ignore the return code of these functions and call them in a series - * then just call git_str_oom at the end. - */ - -int git_str_set(git_str *str, const void *data, size_t datalen); - -int git_str_sets(git_str *str, const char *string); -int git_str_putc(git_str *str, char c); -int git_str_putcn(git_str *str, char c, size_t len); -int git_str_put(git_str *str, const char *data, size_t len); -int git_str_puts(git_str *str, const char *string); -int git_str_printf(git_str *str, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); -int git_str_vprintf(git_str *str, const char *format, va_list ap); -void git_str_clear(git_str *str); -void git_str_consume_bytes(git_str *str, size_t len); -void git_str_consume(git_str *str, const char *end); -void git_str_truncate(git_str *str, size_t len); -void git_str_shorten(git_str *str, size_t amount); -void git_str_truncate_at_char(git_str *path, char separator); -void git_str_rtruncate_at_char(git_str *path, char separator); - -/** General join with separator */ -int git_str_join_n(git_str *str, char separator, int len, ...); -/** Fast join of two strings - first may legally point into `str` data */ -int git_str_join(git_str *str, char separator, const char *str_a, const char *str_b); -/** Fast join of three strings - cannot reference `str` data */ -int git_str_join3(git_str *str, char separator, const char *str_a, const char *str_b, const char *str_c); - -/** - * Join two strings as paths, inserting a slash between as needed. - * @return 0 on success, -1 on failure - */ -GIT_INLINE(int) git_str_joinpath(git_str *str, const char *a, const char *b) -{ - return git_str_join(str, '/', a, b); -} - -GIT_INLINE(const char *) git_str_cstr(const git_str *str) -{ - return str->ptr; -} - -GIT_INLINE(size_t) git_str_len(const git_str *str) -{ - return str->size; -} - -int git_str_copy_cstr(char *data, size_t datasize, const git_str *str); - -#define git_str_PUTS(str, cstr) git_str_put(str, cstr, sizeof(cstr) - 1) - -GIT_INLINE(ssize_t) git_str_rfind_next(const git_str *str, char ch) -{ - ssize_t idx = (ssize_t)str->size - 1; - while (idx >= 0 && str->ptr[idx] == ch) idx--; - while (idx >= 0 && str->ptr[idx] != ch) idx--; - return idx; -} - -GIT_INLINE(ssize_t) git_str_rfind(const git_str *str, char ch) -{ - ssize_t idx = (ssize_t)str->size - 1; - while (idx >= 0 && str->ptr[idx] != ch) idx--; - return idx; -} - -GIT_INLINE(ssize_t) git_str_find(const git_str *str, char ch) -{ - void *found = memchr(str->ptr, ch, str->size); - return found ? (ssize_t)((const char *)found - str->ptr) : -1; -} - -/* Remove whitespace from the end of the string buffer */ -void git_str_rtrim(git_str *str); - -int git_str_cmp(const git_str *a, const git_str *b); - -/* Quote and unquote a string buffer as specified in - * http://marc.info/?l=git&m=112927316408690&w=2 - */ -int git_str_quote(git_str *str); -int git_str_unquote(git_str *str); - -/* Write data as a hex string */ -int git_str_encode_hexstr(git_str *str, const char *data, size_t len); - -/* Write data as base64 encoded in string buffer */ -int git_str_encode_base64(git_str *str, const char *data, size_t len); -/* Decode the given bas64 and write the result to the string buffer */ -int git_str_decode_base64(git_str *str, const char *base64, size_t len); - -/* Write data as "base85" encoded in string buffer */ -int git_str_encode_base85(git_str *str, const char *data, size_t len); -/* Decode the given "base85" and write the result to the string buffer */ -int git_str_decode_base85(git_str *str, const char *base64, size_t len, size_t output_len); - -/* - * Decode the given percent-encoded string and write the result to the - * string buffer. - */ -int git_str_decode_percent(git_str *str, const char *encoded, size_t len); - -/* - * Insert, remove or replace a portion of the string buffer. - * - * @param str The string buffer to work with - * - * @param where The location in the string buffer where the transformation - * should be applied. - * - * @param nb_to_remove The number of chars to be removed. 0 to not - * remove any character in the string buffer. - * - * @param data A pointer to the data which should be inserted. - * - * @param nb_to_insert The number of chars to be inserted. 0 to not - * insert any character from the string buffer. - * - * @return 0 or an error code. - */ -int git_str_splice( - git_str *str, - size_t where, - size_t nb_to_remove, - const char *data, - size_t nb_to_insert); - -/** - * Append string to string buffer, prefixing each character from - * `esc_chars` with `esc_with` string. - * - * @param str String buffer to append data to - * @param string String to escape and append - * @param esc_chars Characters to be escaped - * @param esc_with String to insert in from of each found character - * @return 0 on success, <0 on failure (probably allocation problem) - */ -extern int git_str_puts_escaped( - git_str *str, - const char *string, - const char *esc_chars, - const char *esc_with); - -/** - * Append string escaping characters that are regex special - */ -GIT_INLINE(int) git_str_puts_escape_regex(git_str *str, const char *string) -{ - return git_str_puts_escaped(str, string, "^.[]$()|*+?{}\\", "\\"); -} - -/** - * Unescape all characters in a string buffer in place - * - * I.e. remove backslashes - */ -extern void git_str_unescape(git_str *str); - -/** - * Replace all \r\n with \n. - * - * @return 0 on success, -1 on memory error - */ -extern int git_str_crlf_to_lf(git_str *tgt, const git_str *src); - -/** - * Replace all \n with \r\n. Does not modify existing \r\n. - * - * @return 0 on success, -1 on memory error - */ -extern int git_str_lf_to_crlf(git_str *tgt, const git_str *src); - -/** - * Fill string buffer with the common prefix of a array of strings - * - * String buffer will be set to empty if there is no common prefix - */ -extern int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count); - -/** - * Check if a string buffer begins with a UTF BOM - * - * @param bom Set to the type of BOM detected or GIT_BOM_NONE - * @param str String buffer in which to check the first bytes for a BOM - * @return Number of bytes of BOM data (or 0 if no BOM found) - */ -extern int git_str_detect_bom(git_str_bom_t *bom, const git_str *str); - -/** - * Gather stats for a piece of text - * - * Fill the `stats` structure with counts of unreadable characters, carriage - * returns, etc, so it can be used in heuristics. This automatically skips - * a trailing EOF (\032 character). Also it will look for a BOM at the - * start of the text and can be told to skip that as well. - * - * @param stats Structure to be filled in - * @param str Text to process - * @param skip_bom Exclude leading BOM from stats if true - * @return Does the string buffer heuristically look like binary data - */ -extern bool git_str_gather_text_stats( - git_str_text_stats *stats, const git_str *str, bool skip_bom); - -/** -* Check quickly if string buffer looks like it contains binary data -* -* @param str string buffer to check -* @return 1 if string buffer looks like non-text data -*/ -int git_str_is_binary(const git_str *str); - -/** -* Check quickly if buffer contains a NUL byte -* -* @param str string buffer to check -* @return 1 if string buffer contains a NUL byte -*/ -int git_str_contains_nul(const git_str *str); - -#endif diff --git a/src/strarray.c b/src/strarray.c deleted file mode 100644 index 2f9b77cc2..000000000 --- a/src/strarray.c +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "util.h" - -#include "common.h" - -int git_strarray_copy(git_strarray *tgt, const git_strarray *src) -{ - size_t i; - - GIT_ASSERT_ARG(tgt); - GIT_ASSERT_ARG(src); - - memset(tgt, 0, sizeof(*tgt)); - - if (!src->count) - return 0; - - tgt->strings = git__calloc(src->count, sizeof(char *)); - GIT_ERROR_CHECK_ALLOC(tgt->strings); - - for (i = 0; i < src->count; ++i) { - if (!src->strings[i]) - continue; - - tgt->strings[tgt->count] = git__strdup(src->strings[i]); - if (!tgt->strings[tgt->count]) { - git_strarray_dispose(tgt); - memset(tgt, 0, sizeof(*tgt)); - return -1; - } - - tgt->count++; - } - - return 0; -} - -void git_strarray_dispose(git_strarray *array) -{ - size_t i; - - if (array == NULL) - return; - - for (i = 0; i < array->count; ++i) - git__free(array->strings[i]); - - git__free(array->strings); - - memset(array, 0, sizeof(*array)); -} - -#ifndef GIT_DEPRECATE_HARD -void git_strarray_free(git_strarray *array) -{ - git_strarray_dispose(array); -} -#endif diff --git a/src/stream.h b/src/stream.h deleted file mode 100644 index f16b026fb..000000000 --- a/src/stream.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_stream_h__ -#define INCLUDE_stream_h__ - -#include "common.h" -#include "git2/sys/stream.h" - -GIT_INLINE(int) git_stream_connect(git_stream *st) -{ - return st->connect(st); -} - -GIT_INLINE(int) git_stream_is_encrypted(git_stream *st) -{ - return st->encrypted; -} - -GIT_INLINE(int) git_stream_certificate(git_cert **out, git_stream *st) -{ - if (!st->encrypted) { - git_error_set(GIT_ERROR_INVALID, "an unencrypted stream does not have a certificate"); - return -1; - } - - return st->certificate(out, st); -} - -GIT_INLINE(int) git_stream_supports_proxy(git_stream *st) -{ - return st->proxy_support; -} - -GIT_INLINE(int) git_stream_set_proxy(git_stream *st, const git_proxy_options *proxy_opts) -{ - if (!st->proxy_support) { - git_error_set(GIT_ERROR_INVALID, "proxy not supported on this stream"); - return -1; - } - - return st->set_proxy(st, proxy_opts); -} - -GIT_INLINE(ssize_t) git_stream_read(git_stream *st, void *data, size_t len) -{ - return st->read(st, data, len); -} - -GIT_INLINE(ssize_t) git_stream_write(git_stream *st, const char *data, size_t len, int flags) -{ - return st->write(st, data, len, flags); -} - -GIT_INLINE(int) git_stream__write_full(git_stream *st, const char *data, size_t len, int flags) -{ - size_t total_written = 0; - - while (total_written < len) { - ssize_t written = git_stream_write(st, data + total_written, len - total_written, flags); - if (written <= 0) - return -1; - - total_written += written; - } - - return 0; -} - -GIT_INLINE(int) git_stream_close(git_stream *st) -{ - return st->close(st); -} - -GIT_INLINE(void) git_stream_free(git_stream *st) -{ - if (!st) - return; - - st->free(st); -} - -#endif diff --git a/src/streams/mbedtls.c b/src/streams/mbedtls.c deleted file mode 100644 index 0cf5c8af1..000000000 --- a/src/streams/mbedtls.c +++ /dev/null @@ -1,482 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "streams/mbedtls.h" - -#ifdef GIT_MBEDTLS - -#include - -#include "runtime.h" -#include "stream.h" -#include "streams/socket.h" -#include "netops.h" -#include "git2/transport.h" -#include "util.h" - -#ifndef GIT_DEFAULT_CERT_LOCATION -#define GIT_DEFAULT_CERT_LOCATION NULL -#endif - -/* Work around C90-conformance issues */ -#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) -# if defined(_MSC_VER) -# define inline __inline -# elif defined(__GNUC__) -# define inline __inline__ -# else -# define inline -# endif -#endif - -#include -#include -#include -#include -#include - -#undef inline - -#define GIT_SSL_DEFAULT_CIPHERS "TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-DSS-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-DSS-WITH-AES-256-GCM-SHA384:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256:TLS-ECDHE-ECDSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-128-CBC-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-128-CBC-SHA256:TLS-DHE-DSS-WITH-AES-256-CBC-SHA256:TLS-DHE-DSS-WITH-AES-128-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-128-GCM-SHA256:TLS-RSA-WITH-AES-256-GCM-SHA384:TLS-RSA-WITH-AES-128-CBC-SHA256:TLS-RSA-WITH-AES-256-CBC-SHA256:TLS-RSA-WITH-AES-128-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA" -#define GIT_SSL_DEFAULT_CIPHERS_COUNT 30 - -static mbedtls_ssl_config *git__ssl_conf; -static int ciphers_list[GIT_SSL_DEFAULT_CIPHERS_COUNT]; -static mbedtls_entropy_context *mbedtls_entropy; - -/** - * This function aims to clean-up the SSL context which - * we allocated. - */ -static void shutdown_ssl(void) -{ - if (git__ssl_conf) { - mbedtls_x509_crt_free(git__ssl_conf->ca_chain); - git__free(git__ssl_conf->ca_chain); - mbedtls_ctr_drbg_free(git__ssl_conf->p_rng); - git__free(git__ssl_conf->p_rng); - mbedtls_ssl_config_free(git__ssl_conf); - git__free(git__ssl_conf); - git__ssl_conf = NULL; - } - if (mbedtls_entropy) { - mbedtls_entropy_free(mbedtls_entropy); - git__free(mbedtls_entropy); - mbedtls_entropy = NULL; - } -} - -int git_mbedtls_stream_global_init(void) -{ - int loaded = 0; - char *crtpath = GIT_DEFAULT_CERT_LOCATION; - struct stat statbuf; - mbedtls_ctr_drbg_context *ctr_drbg = NULL; - - size_t ciphers_known = 0; - char *cipher_name = NULL; - char *cipher_string = NULL; - char *cipher_string_tmp = NULL; - - git__ssl_conf = git__malloc(sizeof(mbedtls_ssl_config)); - GIT_ERROR_CHECK_ALLOC(git__ssl_conf); - - mbedtls_ssl_config_init(git__ssl_conf); - if (mbedtls_ssl_config_defaults(git__ssl_conf, - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT) != 0) { - git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS"); - goto cleanup; - } - - /* configure TLSv1 */ - mbedtls_ssl_conf_min_version(git__ssl_conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0); - - /* verify_server_cert is responsible for making the check. - * OPTIONAL because REQUIRED drops the certificate as soon as the check - * is made, so we can never see the certificate and override it. */ - mbedtls_ssl_conf_authmode(git__ssl_conf, MBEDTLS_SSL_VERIFY_OPTIONAL); - - /* set the list of allowed ciphersuites */ - ciphers_known = 0; - cipher_string = cipher_string_tmp = git__strdup(GIT_SSL_DEFAULT_CIPHERS); - GIT_ERROR_CHECK_ALLOC(cipher_string); - - while ((cipher_name = git__strtok(&cipher_string_tmp, ":")) != NULL) { - int cipherid = mbedtls_ssl_get_ciphersuite_id(cipher_name); - if (cipherid == 0) continue; - - if (ciphers_known >= ARRAY_SIZE(ciphers_list)) { - git_error_set(GIT_ERROR_SSL, "out of cipher list space"); - goto cleanup; - } - - ciphers_list[ciphers_known++] = cipherid; - } - git__free(cipher_string); - - if (!ciphers_known) { - git_error_set(GIT_ERROR_SSL, "no cipher could be enabled"); - goto cleanup; - } - mbedtls_ssl_conf_ciphersuites(git__ssl_conf, ciphers_list); - - /* Seeding the random number generator */ - mbedtls_entropy = git__malloc(sizeof(mbedtls_entropy_context)); - GIT_ERROR_CHECK_ALLOC(mbedtls_entropy); - - mbedtls_entropy_init(mbedtls_entropy); - - ctr_drbg = git__malloc(sizeof(mbedtls_ctr_drbg_context)); - GIT_ERROR_CHECK_ALLOC(ctr_drbg); - - mbedtls_ctr_drbg_init(ctr_drbg); - - if (mbedtls_ctr_drbg_seed(ctr_drbg, - mbedtls_entropy_func, - mbedtls_entropy, NULL, 0) != 0) { - git_error_set(GIT_ERROR_SSL, "failed to initialize mbedTLS entropy pool"); - goto cleanup; - } - - mbedtls_ssl_conf_rng(git__ssl_conf, mbedtls_ctr_drbg_random, ctr_drbg); - - /* load default certificates */ - if (crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) - loaded = (git_mbedtls__set_cert_location(crtpath, NULL) == 0); - if (!loaded && crtpath != NULL && stat(crtpath, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) - loaded = (git_mbedtls__set_cert_location(NULL, crtpath) == 0); - - return git_runtime_shutdown_register(shutdown_ssl); - -cleanup: - mbedtls_ctr_drbg_free(ctr_drbg); - git__free(ctr_drbg); - mbedtls_ssl_config_free(git__ssl_conf); - git__free(git__ssl_conf); - git__ssl_conf = NULL; - - return -1; -} - -static int bio_read(void *b, unsigned char *buf, size_t len) -{ - git_stream *io = (git_stream *) b; - return (int) git_stream_read(io, buf, min(len, INT_MAX)); -} - -static int bio_write(void *b, const unsigned char *buf, size_t len) -{ - git_stream *io = (git_stream *) b; - return (int) git_stream_write(io, (const char *)buf, min(len, INT_MAX), 0); -} - -static int ssl_set_error(mbedtls_ssl_context *ssl, int error) -{ - char errbuf[512]; - int ret = -1; - - GIT_ASSERT(error != MBEDTLS_ERR_SSL_WANT_READ); - GIT_ASSERT(error != MBEDTLS_ERR_SSL_WANT_WRITE); - - if (error != 0) - mbedtls_strerror( error, errbuf, 512 ); - - switch(error) { - case 0: - git_error_set(GIT_ERROR_SSL, "SSL error: unknown error"); - break; - - case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: - git_error_set(GIT_ERROR_SSL, "SSL error: %#04x [%x] - %s", error, ssl->session_negotiate->verify_result, errbuf); - ret = GIT_ECERTIFICATE; - break; - - default: - git_error_set(GIT_ERROR_SSL, "SSL error: %#04x - %s", error, errbuf); - } - - return ret; -} - -static int ssl_teardown(mbedtls_ssl_context *ssl) -{ - int ret = 0; - - ret = mbedtls_ssl_close_notify(ssl); - if (ret < 0) - ret = ssl_set_error(ssl, ret); - - mbedtls_ssl_free(ssl); - return ret; -} - -static int verify_server_cert(mbedtls_ssl_context *ssl) -{ - int ret = -1; - - if ((ret = mbedtls_ssl_get_verify_result(ssl)) != 0) { - char vrfy_buf[512]; - int len = mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), "", ret); - if (len >= 1) vrfy_buf[len - 1] = '\0'; /* Remove trailing \n */ - git_error_set(GIT_ERROR_SSL, "the SSL certificate is invalid: %#04x - %s", ret, vrfy_buf); - return GIT_ECERTIFICATE; - } - - return 0; -} - -typedef struct { - git_stream parent; - git_stream *io; - int owned; - bool connected; - char *host; - mbedtls_ssl_context *ssl; - git_cert_x509 cert_info; -} mbedtls_stream; - - -static int mbedtls_connect(git_stream *stream) -{ - int ret; - mbedtls_stream *st = (mbedtls_stream *) stream; - - if (st->owned && (ret = git_stream_connect(st->io)) < 0) - return ret; - - st->connected = true; - - mbedtls_ssl_set_hostname(st->ssl, st->host); - - mbedtls_ssl_set_bio(st->ssl, st->io, bio_write, bio_read, NULL); - - if ((ret = mbedtls_ssl_handshake(st->ssl)) != 0) - return ssl_set_error(st->ssl, ret); - - return verify_server_cert(st->ssl); -} - -static int mbedtls_certificate(git_cert **out, git_stream *stream) -{ - unsigned char *encoded_cert; - mbedtls_stream *st = (mbedtls_stream *) stream; - - const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(st->ssl); - if (!cert) { - git_error_set(GIT_ERROR_SSL, "the server did not provide a certificate"); - return -1; - } - - /* Retrieve the length of the certificate first */ - if (cert->raw.len == 0) { - git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); - return -1; - } - - encoded_cert = git__malloc(cert->raw.len); - GIT_ERROR_CHECK_ALLOC(encoded_cert); - memcpy(encoded_cert, cert->raw.p, cert->raw.len); - - st->cert_info.parent.cert_type = GIT_CERT_X509; - st->cert_info.data = encoded_cert; - st->cert_info.len = cert->raw.len; - - *out = &st->cert_info.parent; - - return 0; -} - -static int mbedtls_set_proxy(git_stream *stream, const git_proxy_options *proxy_options) -{ - mbedtls_stream *st = (mbedtls_stream *) stream; - - return git_stream_set_proxy(st->io, proxy_options); -} - -static ssize_t mbedtls_stream_write(git_stream *stream, const char *data, size_t len, int flags) -{ - mbedtls_stream *st = (mbedtls_stream *) stream; - int written; - - GIT_UNUSED(flags); - - /* - * `mbedtls_ssl_write` can only represent INT_MAX bytes - * written via its return value. We thus need to clamp - * the maximum number of bytes written. - */ - len = min(len, INT_MAX); - - if ((written = mbedtls_ssl_write(st->ssl, (const unsigned char *)data, len)) <= 0) - return ssl_set_error(st->ssl, written); - - return written; -} - -static ssize_t mbedtls_stream_read(git_stream *stream, void *data, size_t len) -{ - mbedtls_stream *st = (mbedtls_stream *) stream; - int ret; - - if ((ret = mbedtls_ssl_read(st->ssl, (unsigned char *)data, len)) <= 0) - ssl_set_error(st->ssl, ret); - - return ret; -} - -static int mbedtls_stream_close(git_stream *stream) -{ - mbedtls_stream *st = (mbedtls_stream *) stream; - int ret = 0; - - if (st->connected && (ret = ssl_teardown(st->ssl)) != 0) - return -1; - - st->connected = false; - - return st->owned ? git_stream_close(st->io) : 0; -} - -static void mbedtls_stream_free(git_stream *stream) -{ - mbedtls_stream *st = (mbedtls_stream *) stream; - - if (st->owned) - git_stream_free(st->io); - - git__free(st->host); - git__free(st->cert_info.data); - mbedtls_ssl_free(st->ssl); - git__free(st->ssl); - git__free(st); -} - -static int mbedtls_stream_wrap( - git_stream **out, - git_stream *in, - const char *host, - int owned) -{ - mbedtls_stream *st; - int error; - - st = git__calloc(1, sizeof(mbedtls_stream)); - GIT_ERROR_CHECK_ALLOC(st); - - st->io = in; - st->owned = owned; - - st->ssl = git__malloc(sizeof(mbedtls_ssl_context)); - GIT_ERROR_CHECK_ALLOC(st->ssl); - mbedtls_ssl_init(st->ssl); - if (mbedtls_ssl_setup(st->ssl, git__ssl_conf)) { - git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); - error = -1; - goto out_err; - } - - st->host = git__strdup(host); - GIT_ERROR_CHECK_ALLOC(st->host); - - st->parent.version = GIT_STREAM_VERSION; - st->parent.encrypted = 1; - st->parent.proxy_support = git_stream_supports_proxy(st->io); - st->parent.connect = mbedtls_connect; - st->parent.certificate = mbedtls_certificate; - st->parent.set_proxy = mbedtls_set_proxy; - st->parent.read = mbedtls_stream_read; - st->parent.write = mbedtls_stream_write; - st->parent.close = mbedtls_stream_close; - st->parent.free = mbedtls_stream_free; - - *out = (git_stream *) st; - return 0; - -out_err: - mbedtls_ssl_free(st->ssl); - git_stream_close(st->io); - git_stream_free(st->io); - git__free(st); - - return error; -} - -int git_mbedtls_stream_wrap( - git_stream **out, - git_stream *in, - const char *host) -{ - return mbedtls_stream_wrap(out, in, host, 0); -} - -int git_mbedtls_stream_new( - git_stream **out, - const char *host, - const char *port) -{ - git_stream *stream; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(host); - GIT_ASSERT_ARG(port); - - if ((error = git_socket_stream_new(&stream, host, port)) < 0) - return error; - - if ((error = mbedtls_stream_wrap(out, stream, host, 1)) < 0) { - git_stream_close(stream); - git_stream_free(stream); - } - - return error; -} - -int git_mbedtls__set_cert_location(const char *file, const char *path) -{ - int ret = 0; - char errbuf[512]; - mbedtls_x509_crt *cacert; - - GIT_ASSERT_ARG(file || path); - - cacert = git__malloc(sizeof(mbedtls_x509_crt)); - GIT_ERROR_CHECK_ALLOC(cacert); - - mbedtls_x509_crt_init(cacert); - if (file) - ret = mbedtls_x509_crt_parse_file(cacert, file); - if (ret >= 0 && path) - ret = mbedtls_x509_crt_parse_path(cacert, path); - /* mbedtls_x509_crt_parse_path returns the number of invalid certs on success */ - if (ret < 0) { - mbedtls_x509_crt_free(cacert); - git__free(cacert); - mbedtls_strerror( ret, errbuf, 512 ); - git_error_set(GIT_ERROR_SSL, "failed to load CA certificates: %#04x - %s", ret, errbuf); - return -1; - } - - mbedtls_x509_crt_free(git__ssl_conf->ca_chain); - git__free(git__ssl_conf->ca_chain); - mbedtls_ssl_conf_ca_chain(git__ssl_conf, cacert, NULL); - - return 0; -} - -#else - -#include "stream.h" - -int git_mbedtls_stream_global_init(void) -{ - return 0; -} - -#endif diff --git a/src/streams/mbedtls.h b/src/streams/mbedtls.h deleted file mode 100644 index bcca6dd40..000000000 --- a/src/streams/mbedtls.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_steams_mbedtls_h__ -#define INCLUDE_steams_mbedtls_h__ - -#include "common.h" - -#include "git2/sys/stream.h" - -extern int git_mbedtls_stream_global_init(void); - -#ifdef GIT_MBEDTLS -extern int git_mbedtls__set_cert_location(const char *file, const char *path); - -extern int git_mbedtls_stream_new(git_stream **out, const char *host, const char *port); -extern int git_mbedtls_stream_wrap(git_stream **out, git_stream *in, const char *host); -#endif - -#endif diff --git a/src/streams/openssl.c b/src/streams/openssl.c deleted file mode 100644 index 89c96780c..000000000 --- a/src/streams/openssl.c +++ /dev/null @@ -1,747 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "streams/openssl.h" -#include "streams/openssl_legacy.h" -#include "streams/openssl_dynamic.h" - -#ifdef GIT_OPENSSL - -#include - -#include "common.h" -#include "runtime.h" -#include "settings.h" -#include "posix.h" -#include "stream.h" -#include "streams/socket.h" -#include "netops.h" -#include "git2/transport.h" -#include "git2/sys/openssl.h" - -#ifndef GIT_WIN32 -# include -# include -# include -#endif - -#ifndef GIT_OPENSSL_DYNAMIC -# include -# include -# include -# include -#endif - -SSL_CTX *git__ssl_ctx; - -#define GIT_SSL_DEFAULT_CIPHERS "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES128-SHA256:DHE-DSS-AES256-SHA256:DHE-DSS-AES128-SHA:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA" - - -static BIO_METHOD *git_stream_bio_method; -static int init_bio_method(void); - -/** - * This function aims to clean-up the SSL context which - * we allocated. - */ -static void shutdown_ssl(void) -{ - if (git_stream_bio_method) { - BIO_meth_free(git_stream_bio_method); - git_stream_bio_method = NULL; - } - - if (git__ssl_ctx) { - SSL_CTX_free(git__ssl_ctx); - git__ssl_ctx = NULL; - } -} - -#ifdef VALGRIND -# if !defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC) - -static void *git_openssl_malloc(size_t bytes, const char *file, int line) -{ - GIT_UNUSED(file); - GIT_UNUSED(line); - return git__calloc(1, bytes); -} - -static void *git_openssl_realloc(void *mem, size_t size, const char *file, int line) -{ - GIT_UNUSED(file); - GIT_UNUSED(line); - return git__realloc(mem, size); -} - -static void git_openssl_free(void *mem, const char *file, int line) -{ - GIT_UNUSED(file); - GIT_UNUSED(line); - git__free(mem); -} -# else /* !GIT_OPENSSL_LEGACY && !GIT_OPENSSL_DYNAMIC */ -static void *git_openssl_malloc(size_t bytes) -{ - return git__calloc(1, bytes); -} - -static void *git_openssl_realloc(void *mem, size_t size) -{ - return git__realloc(mem, size); -} - -static void git_openssl_free(void *mem) -{ - git__free(mem); -} -# endif /* !GIT_OPENSSL_LEGACY && !GIT_OPENSSL_DYNAMIC */ -#endif /* VALGRIND */ - -static int openssl_init(void) -{ - long ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; - const char *ciphers = git_libgit2__ssl_ciphers(); -#ifdef VALGRIND - static bool allocators_initialized = false; -#endif - - /* Older OpenSSL and MacOS OpenSSL doesn't have this */ -#ifdef SSL_OP_NO_COMPRESSION - ssl_opts |= SSL_OP_NO_COMPRESSION; -#endif - -#ifdef VALGRIND - /* - * Swap in our own allocator functions that initialize - * allocated memory to avoid spurious valgrind warnings. - * Don't error on failure; many builds of OpenSSL do not - * allow you to set these functions. - */ - if (!allocators_initialized) { - CRYPTO_set_mem_functions(git_openssl_malloc, - git_openssl_realloc, - git_openssl_free); - allocators_initialized = true; - } -#endif - - OPENSSL_init_ssl(0, NULL); - - /* - * Load SSLv{2,3} and TLSv1 so that we can talk with servers - * which use the SSL hellos, which are often used for - * compatibility. We then disable SSL so we only allow OpenSSL - * to speak TLSv1 to perform the encryption itself. - */ - if (!(git__ssl_ctx = SSL_CTX_new(SSLv23_method()))) - goto error; - - SSL_CTX_set_options(git__ssl_ctx, ssl_opts); - SSL_CTX_set_mode(git__ssl_ctx, SSL_MODE_AUTO_RETRY); - SSL_CTX_set_verify(git__ssl_ctx, SSL_VERIFY_NONE, NULL); - if (!SSL_CTX_set_default_verify_paths(git__ssl_ctx)) - goto error; - - if (!ciphers) - ciphers = GIT_SSL_DEFAULT_CIPHERS; - - if(!SSL_CTX_set_cipher_list(git__ssl_ctx, ciphers)) - goto error; - - if (init_bio_method() < 0) - goto error; - - return git_runtime_shutdown_register(shutdown_ssl); - -error: - git_error_set(GIT_ERROR_NET, "could not initialize openssl: %s", - ERR_error_string(ERR_get_error(), NULL)); - SSL_CTX_free(git__ssl_ctx); - git__ssl_ctx = NULL; - return -1; -} - -/* - * When we use dynamic loading, we defer OpenSSL initialization until - * it's first used. `openssl_ensure_initialized` will do the work - * under a mutex. - */ -git_mutex openssl_mutex; -bool openssl_initialized; - -int git_openssl_stream_global_init(void) -{ -#ifndef GIT_OPENSSL_DYNAMIC - return openssl_init(); -#else - if (git_mutex_init(&openssl_mutex) != 0) - return -1; - - return 0; -#endif -} - -static int openssl_ensure_initialized(void) -{ -#ifdef GIT_OPENSSL_DYNAMIC - int error = 0; - - if (git_mutex_lock(&openssl_mutex) != 0) - return -1; - - if (!openssl_initialized) { - if ((error = git_openssl_stream_dynamic_init()) == 0) - error = openssl_init(); - - openssl_initialized = true; - } - - error |= git_mutex_unlock(&openssl_mutex); - return error; - -#else - return 0; -#endif -} - -#if !defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC) -int git_openssl_set_locking(void) -{ -# ifdef GIT_THREADS - return 0; -# else - git_error_set(GIT_ERROR_THREAD, "libgit2 was not built with threads"); - return -1; -# endif -} -#endif - - -static int bio_create(BIO *b) -{ - BIO_set_init(b, 1); - BIO_set_data(b, NULL); - - return 1; -} - -static int bio_destroy(BIO *b) -{ - if (!b) - return 0; - - BIO_set_data(b, NULL); - - return 1; -} - -static int bio_read(BIO *b, char *buf, int len) -{ - git_stream *io = (git_stream *) BIO_get_data(b); - - return (int) git_stream_read(io, buf, len); -} - -static int bio_write(BIO *b, const char *buf, int len) -{ - git_stream *io = (git_stream *) BIO_get_data(b); - return (int) git_stream_write(io, buf, len, 0); -} - -static long bio_ctrl(BIO *b, int cmd, long num, void *ptr) -{ - GIT_UNUSED(b); - GIT_UNUSED(num); - GIT_UNUSED(ptr); - - if (cmd == BIO_CTRL_FLUSH) - return 1; - - return 0; -} - -static int bio_gets(BIO *b, char *buf, int len) -{ - GIT_UNUSED(b); - GIT_UNUSED(buf); - GIT_UNUSED(len); - return -1; -} - -static int bio_puts(BIO *b, const char *str) -{ - return bio_write(b, str, strlen(str)); -} - -static int init_bio_method(void) -{ - /* Set up the BIO_METHOD we use for wrapping our own stream implementations */ - git_stream_bio_method = BIO_meth_new(BIO_TYPE_SOURCE_SINK | BIO_get_new_index(), "git_stream"); - GIT_ERROR_CHECK_ALLOC(git_stream_bio_method); - - BIO_meth_set_write(git_stream_bio_method, bio_write); - BIO_meth_set_read(git_stream_bio_method, bio_read); - BIO_meth_set_puts(git_stream_bio_method, bio_puts); - BIO_meth_set_gets(git_stream_bio_method, bio_gets); - BIO_meth_set_ctrl(git_stream_bio_method, bio_ctrl); - BIO_meth_set_create(git_stream_bio_method, bio_create); - BIO_meth_set_destroy(git_stream_bio_method, bio_destroy); - - return 0; -} - -static int ssl_set_error(SSL *ssl, int error) -{ - int err; - unsigned long e; - - err = SSL_get_error(ssl, error); - - GIT_ASSERT(err != SSL_ERROR_WANT_READ); - GIT_ASSERT(err != SSL_ERROR_WANT_WRITE); - - switch (err) { - case SSL_ERROR_WANT_CONNECT: - case SSL_ERROR_WANT_ACCEPT: - git_error_set(GIT_ERROR_SSL, "SSL error: connection failure"); - break; - case SSL_ERROR_WANT_X509_LOOKUP: - git_error_set(GIT_ERROR_SSL, "SSL error: x509 error"); - break; - case SSL_ERROR_SYSCALL: - e = ERR_get_error(); - if (e > 0) { - char errmsg[256]; - ERR_error_string_n(e, errmsg, sizeof(errmsg)); - git_error_set(GIT_ERROR_NET, "SSL error: %s", errmsg); - break; - } else if (error < 0) { - git_error_set(GIT_ERROR_OS, "SSL error: syscall failure"); - break; - } - git_error_set(GIT_ERROR_SSL, "SSL error: received early EOF"); - return GIT_EEOF; - break; - case SSL_ERROR_SSL: - { - char errmsg[256]; - e = ERR_get_error(); - ERR_error_string_n(e, errmsg, sizeof(errmsg)); - git_error_set(GIT_ERROR_SSL, "SSL error: %s", errmsg); - break; - } - case SSL_ERROR_NONE: - case SSL_ERROR_ZERO_RETURN: - default: - git_error_set(GIT_ERROR_SSL, "SSL error: unknown error"); - break; - } - return -1; -} - -static int ssl_teardown(SSL *ssl) -{ - int ret; - - ret = SSL_shutdown(ssl); - if (ret < 0) - ret = ssl_set_error(ssl, ret); - else - ret = 0; - - return ret; -} - -static int check_host_name(const char *name, const char *host) -{ - if (!strcasecmp(name, host)) - return 0; - - if (gitno__match_host(name, host) < 0) - return -1; - - return 0; -} - -static int verify_server_cert(SSL *ssl, const char *host) -{ - X509 *cert = NULL; - X509_NAME *peer_name; - ASN1_STRING *str; - unsigned char *peer_cn = NULL; - int matched = -1, type = GEN_DNS; - GENERAL_NAMES *alts; - struct in6_addr addr6; - struct in_addr addr4; - void *addr = NULL; - int i = -1, j, error = 0; - - if (SSL_get_verify_result(ssl) != X509_V_OK) { - git_error_set(GIT_ERROR_SSL, "the SSL certificate is invalid"); - return GIT_ECERTIFICATE; - } - - /* Try to parse the host as an IP address to see if it is */ - if (p_inet_pton(AF_INET, host, &addr4)) { - type = GEN_IPADD; - addr = &addr4; - } else { - if (p_inet_pton(AF_INET6, host, &addr6)) { - type = GEN_IPADD; - addr = &addr6; - } - } - - - cert = SSL_get_peer_certificate(ssl); - if (!cert) { - error = -1; - git_error_set(GIT_ERROR_SSL, "the server did not provide a certificate"); - goto cleanup; - } - - /* Check the alternative names */ - alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); - if (alts) { - int num; - - num = sk_GENERAL_NAME_num(alts); - for (i = 0; i < num && matched != 1; i++) { - const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i); - const char *name = (char *) ASN1_STRING_get0_data(gn->d.ia5); - size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); - - /* Skip any names of a type we're not looking for */ - if (gn->type != type) - continue; - - if (type == GEN_DNS) { - /* If it contains embedded NULs, don't even try */ - if (memchr(name, '\0', namelen)) - continue; - - if (check_host_name(name, host) < 0) - matched = 0; - else - matched = 1; - } else if (type == GEN_IPADD) { - /* Here name isn't so much a name but a binary representation of the IP */ - matched = addr && !!memcmp(name, addr, namelen); - } - } - } - GENERAL_NAMES_free(alts); - - if (matched == 0) - goto cert_fail_name; - - if (matched == 1) { - goto cleanup; - } - - /* If no alternative names are available, check the common name */ - peer_name = X509_get_subject_name(cert); - if (peer_name == NULL) - goto on_error; - - if (peer_name) { - /* Get the index of the last CN entry */ - while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0) - i = j; - } - - if (i < 0) - goto on_error; - - str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i)); - if (str == NULL) - goto on_error; - - /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */ - if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) { - int size = ASN1_STRING_length(str); - - if (size > 0) { - peer_cn = OPENSSL_malloc(size + 1); - GIT_ERROR_CHECK_ALLOC(peer_cn); - memcpy(peer_cn, ASN1_STRING_get0_data(str), size); - peer_cn[size] = '\0'; - } else { - goto cert_fail_name; - } - } else { - int size = ASN1_STRING_to_UTF8(&peer_cn, str); - GIT_ERROR_CHECK_ALLOC(peer_cn); - if (memchr(peer_cn, '\0', size)) - goto cert_fail_name; - } - - if (check_host_name((char *)peer_cn, host) < 0) - goto cert_fail_name; - - goto cleanup; - -cert_fail_name: - error = GIT_ECERTIFICATE; - git_error_set(GIT_ERROR_SSL, "hostname does not match certificate"); - goto cleanup; - -on_error: - error = ssl_set_error(ssl, 0); - goto cleanup; - -cleanup: - X509_free(cert); - OPENSSL_free(peer_cn); - return error; -} - -typedef struct { - git_stream parent; - git_stream *io; - int owned; - bool connected; - char *host; - SSL *ssl; - git_cert_x509 cert_info; -} openssl_stream; - -static int openssl_connect(git_stream *stream) -{ - int ret; - BIO *bio; - openssl_stream *st = (openssl_stream *) stream; - - if (st->owned && (ret = git_stream_connect(st->io)) < 0) - return ret; - - bio = BIO_new(git_stream_bio_method); - GIT_ERROR_CHECK_ALLOC(bio); - - BIO_set_data(bio, st->io); - SSL_set_bio(st->ssl, bio, bio); - - /* specify the host in case SNI is needed */ -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - SSL_set_tlsext_host_name(st->ssl, st->host); -#endif - - if ((ret = SSL_connect(st->ssl)) <= 0) - return ssl_set_error(st->ssl, ret); - - st->connected = true; - - return verify_server_cert(st->ssl, st->host); -} - -static int openssl_certificate(git_cert **out, git_stream *stream) -{ - openssl_stream *st = (openssl_stream *) stream; - X509 *cert = SSL_get_peer_certificate(st->ssl); - unsigned char *guard, *encoded_cert = NULL; - int error, len; - - /* Retrieve the length of the certificate first */ - len = i2d_X509(cert, NULL); - if (len < 0) { - git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); - error = -1; - goto out; - } - - encoded_cert = git__malloc(len); - GIT_ERROR_CHECK_ALLOC(encoded_cert); - /* i2d_X509 makes 'guard' point to just after the data */ - guard = encoded_cert; - - len = i2d_X509(cert, &guard); - if (len < 0) { - git_error_set(GIT_ERROR_NET, "failed to retrieve certificate information"); - error = -1; - goto out; - } - - st->cert_info.parent.cert_type = GIT_CERT_X509; - st->cert_info.data = encoded_cert; - st->cert_info.len = len; - encoded_cert = NULL; - - *out = &st->cert_info.parent; - error = 0; - -out: - git__free(encoded_cert); - X509_free(cert); - return error; -} - -static int openssl_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts) -{ - openssl_stream *st = (openssl_stream *) stream; - - return git_stream_set_proxy(st->io, proxy_opts); -} - -static ssize_t openssl_write(git_stream *stream, const char *data, size_t data_len, int flags) -{ - openssl_stream *st = (openssl_stream *) stream; - int ret, len = min(data_len, INT_MAX); - - GIT_UNUSED(flags); - - if ((ret = SSL_write(st->ssl, data, len)) <= 0) - return ssl_set_error(st->ssl, ret); - - return ret; -} - -static ssize_t openssl_read(git_stream *stream, void *data, size_t len) -{ - openssl_stream *st = (openssl_stream *) stream; - int ret; - - if ((ret = SSL_read(st->ssl, data, len)) <= 0) - return ssl_set_error(st->ssl, ret); - - return ret; -} - -static int openssl_close(git_stream *stream) -{ - openssl_stream *st = (openssl_stream *) stream; - int ret; - - if (st->connected && (ret = ssl_teardown(st->ssl)) < 0) - return -1; - - st->connected = false; - - return st->owned ? git_stream_close(st->io) : 0; -} - -static void openssl_free(git_stream *stream) -{ - openssl_stream *st = (openssl_stream *) stream; - - if (st->owned) - git_stream_free(st->io); - - SSL_free(st->ssl); - git__free(st->host); - git__free(st->cert_info.data); - git__free(st); -} - -static int openssl_stream_wrap( - git_stream **out, - git_stream *in, - const char *host, - int owned) -{ - openssl_stream *st; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(in); - GIT_ASSERT_ARG(host); - - st = git__calloc(1, sizeof(openssl_stream)); - GIT_ERROR_CHECK_ALLOC(st); - - st->io = in; - st->owned = owned; - - st->ssl = SSL_new(git__ssl_ctx); - if (st->ssl == NULL) { - git_error_set(GIT_ERROR_SSL, "failed to create ssl object"); - git__free(st); - return -1; - } - - st->host = git__strdup(host); - GIT_ERROR_CHECK_ALLOC(st->host); - - st->parent.version = GIT_STREAM_VERSION; - st->parent.encrypted = 1; - st->parent.proxy_support = git_stream_supports_proxy(st->io); - st->parent.connect = openssl_connect; - st->parent.certificate = openssl_certificate; - st->parent.set_proxy = openssl_set_proxy; - st->parent.read = openssl_read; - st->parent.write = openssl_write; - st->parent.close = openssl_close; - st->parent.free = openssl_free; - - *out = (git_stream *) st; - return 0; -} - -int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host) -{ - if (openssl_ensure_initialized() < 0) - return -1; - - return openssl_stream_wrap(out, in, host, 0); -} - -int git_openssl_stream_new(git_stream **out, const char *host, const char *port) -{ - git_stream *stream = NULL; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(host); - GIT_ASSERT_ARG(port); - - if (openssl_ensure_initialized() < 0) - return -1; - - if ((error = git_socket_stream_new(&stream, host, port)) < 0) - return error; - - if ((error = openssl_stream_wrap(out, stream, host, 1)) < 0) { - git_stream_close(stream); - git_stream_free(stream); - } - - return error; -} - -int git_openssl__set_cert_location(const char *file, const char *path) -{ - if (openssl_ensure_initialized() < 0) - return -1; - - if (SSL_CTX_load_verify_locations(git__ssl_ctx, file, path) == 0) { - char errmsg[256]; - - ERR_error_string_n(ERR_get_error(), errmsg, sizeof(errmsg)); - git_error_set(GIT_ERROR_SSL, "OpenSSL error: failed to load certificates: %s", - errmsg); - - return -1; - } - return 0; -} - -#else - -#include "stream.h" -#include "git2/sys/openssl.h" - -int git_openssl_stream_global_init(void) -{ - return 0; -} - -int git_openssl_set_locking(void) -{ - git_error_set(GIT_ERROR_SSL, "libgit2 was not built with OpenSSL support"); - return -1; -} - -#endif diff --git a/src/streams/openssl.h b/src/streams/openssl.h deleted file mode 100644 index 89fb60a82..000000000 --- a/src/streams/openssl.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_streams_openssl_h__ -#define INCLUDE_streams_openssl_h__ - -#include "common.h" -#include "streams/openssl_legacy.h" -#include "streams/openssl_dynamic.h" - -#include "git2/sys/stream.h" - -extern int git_openssl_stream_global_init(void); - -#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC) -# include -# include -# include -# include -# endif - -#ifdef GIT_OPENSSL -extern int git_openssl__set_cert_location(const char *file, const char *path); -extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port); -extern int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host); -#endif - -#endif diff --git a/src/streams/openssl_dynamic.c b/src/streams/openssl_dynamic.c deleted file mode 100644 index da16b6ed7..000000000 --- a/src/streams/openssl_dynamic.c +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "streams/openssl.h" -#include "streams/openssl_dynamic.h" - -#if defined(GIT_OPENSSL) && defined(GIT_OPENSSL_DYNAMIC) - -#include "runtime.h" - -#include - -unsigned char *(*ASN1_STRING_data)(ASN1_STRING *x); -const unsigned char *(*ASN1_STRING_get0_data)(const ASN1_STRING *x); -int (*ASN1_STRING_length)(const ASN1_STRING *x); -int (*ASN1_STRING_to_UTF8)(unsigned char **out, const ASN1_STRING *in); -int (*ASN1_STRING_type)(const ASN1_STRING *x); - -void *(*BIO_get_data)(BIO *a); -int (*BIO_get_new_index)(void); -int (*OPENSSL_init_ssl)(uint64_t opts, const void *settings); -void (*BIO_meth_free)(BIO_METHOD *biom); -int (*BIO_meth_set_create)(BIO_METHOD *biom, int (*create) (BIO *)); -int (*BIO_meth_set_ctrl)(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)); -int (*BIO_meth_set_destroy)(BIO_METHOD *biom, int (*destroy) (BIO *)); -int (*BIO_meth_set_gets)(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)); -int (*BIO_meth_set_puts)(BIO_METHOD *biom, int (*puts) (BIO *, const char *)); -int (*BIO_meth_set_read)(BIO_METHOD *biom, int (*read) (BIO *, char *, int)); -int (*BIO_meth_set_write)(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)); -BIO_METHOD *(*BIO_meth_new)(int type, const char *name); -BIO *(*BIO_new)(const BIO_METHOD *type); -void (*BIO_set_data)(BIO *a, void *ptr); -void (*BIO_set_init)(BIO *a, int init); - -void (*CRYPTO_free)(void *ptr, const char *file, int line); -void *(*CRYPTO_malloc)(size_t num, const char *file, int line); -int (*CRYPTO_num_locks)(void); -void (*CRYPTO_set_locking_callback)(void (*func)(int mode, int type, const char *file, int line)); -int (*CRYPTO_set_mem_functions)(void *(*m)(size_t bytes), void *(*r)(void *mem, size_t size), void (*f)(void *mem)); -int (*CRYPTO_THREADID_set_callback)(void (*func)(CRYPTO_THREADID *id)); -void (*CRYPTO_THREADID_set_numeric)(CRYPTO_THREADID *id, unsigned long val); - -char *(*ERR_error_string)(unsigned long e, char *buf); -void (*ERR_error_string_n)(unsigned long e, char *buf, size_t len); -unsigned long (*ERR_get_error)(void); - -int (*SSL_connect)(SSL *ssl); -long (*SSL_ctrl)(SSL *ssl, int cmd, long arg, void *parg); -void (*SSL_free)(SSL *ssl); -int (*SSL_get_error)(SSL *ssl, int ret); -X509 *(*SSL_get_peer_certificate)(const SSL *ssl); -long (*SSL_get_verify_result)(const SSL *ssl); -int (*SSL_library_init)(void); -void (*SSL_load_error_strings)(void); -SSL *(*SSL_new)(SSL_CTX *ctx); -int (*SSL_read)(SSL *ssl, const void *buf, int num); -void (*SSL_set_bio)(SSL *ssl, BIO *rbio, BIO *wbio); -int (*SSL_shutdown)(SSL *ssl); -int (*SSL_write)(SSL *ssl, const void *buf, int num); - -long (*SSL_CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg); -void (*SSL_CTX_free)(SSL_CTX *ctx); -SSL_CTX *(*SSL_CTX_new)(const SSL_METHOD *method); -int (*SSL_CTX_set_cipher_list)(SSL_CTX *ctx, const char *str); -int (*SSL_CTX_set_default_verify_paths)(SSL_CTX *ctx); -long (*SSL_CTX_set_options)(SSL_CTX *ctx, long options); -void (*SSL_CTX_set_verify)(SSL_CTX *ctx, int mode, int (*verify_callback)(int, X509_STORE_CTX *)); -int (*SSL_CTX_load_verify_locations)(SSL_CTX *ctx, const char *CAfile, const char *CApath); - -const SSL_METHOD *(*SSLv23_method)(void); -const SSL_METHOD *(*TLS_method)(void); - -ASN1_STRING *(*X509_NAME_ENTRY_get_data)(const X509_NAME_ENTRY *ne); -X509_NAME_ENTRY *(*X509_NAME_get_entry)(X509_NAME *name, int loc); -int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos); -void (*X509_free)(X509 *a); -void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx); -X509_NAME *(*X509_get_subject_name)(const X509 *x); - -int (*i2d_X509)(X509 *a, unsigned char **ppout); - -int (*OPENSSL_sk_num)(const void *sk); -void *(*OPENSSL_sk_value)(const void *sk, int i); -void (*OPENSSL_sk_free)(void *sk); - -int (*sk_num)(const void *sk); -void *(*sk_value)(const void *sk, int i); -void (*sk_free)(void *sk); - -void *openssl_handle; - -GIT_INLINE(void *) openssl_sym(int *err, const char *name, bool required) -{ - void *symbol; - - /* if we've seen an err, noop to retain it */ - if (*err) - return NULL; - - - if ((symbol = dlsym(openssl_handle, name)) == NULL && required) { - const char *msg = dlerror(); - git_error_set(GIT_ERROR_SSL, "could not load ssl function '%s': %s", name, msg ? msg : "unknown error"); - *err = -1; - } - - return symbol; -} - -static void dynamic_shutdown(void) -{ - dlclose(openssl_handle); - openssl_handle = NULL; -} - -int git_openssl_stream_dynamic_init(void) -{ - int err = 0; - - if ((openssl_handle = dlopen("libssl.so.1.1", RTLD_NOW)) == NULL && - (openssl_handle = dlopen("libssl.1.1.dylib", RTLD_NOW)) == NULL && - (openssl_handle = dlopen("libssl.so.1.0.0", RTLD_NOW)) == NULL && - (openssl_handle = dlopen("libssl.1.0.0.dylib", RTLD_NOW)) == NULL && - (openssl_handle = dlopen("libssl.so.10", RTLD_NOW)) == NULL) { - git_error_set(GIT_ERROR_SSL, "could not load ssl libraries"); - return -1; - } - - ASN1_STRING_data = (unsigned char *(*)(ASN1_STRING *x))openssl_sym(&err, "ASN1_STRING_data", false); - ASN1_STRING_get0_data = (const unsigned char *(*)(const ASN1_STRING *x))openssl_sym(&err, "ASN1_STRING_get0_data", false); - ASN1_STRING_length = (int (*)(const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_length", true); - ASN1_STRING_to_UTF8 = (int (*)(unsigned char **, const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_to_UTF8", true); - ASN1_STRING_type = (int (*)(const ASN1_STRING *))openssl_sym(&err, "ASN1_STRING_type", true); - - BIO_get_data = (void *(*)(BIO *))openssl_sym(&err, "BIO_get_data", false); - BIO_get_new_index = (int (*)(void))openssl_sym(&err, "BIO_get_new_index", false); - BIO_meth_free = (void (*)(BIO_METHOD *))openssl_sym(&err, "BIO_meth_free", false); - BIO_meth_new = (BIO_METHOD *(*)(int, const char *))openssl_sym(&err, "BIO_meth_new", false); - BIO_meth_set_create = (int (*)(BIO_METHOD *, int (*)(BIO *)))openssl_sym(&err, "BIO_meth_set_create", false); - BIO_meth_set_ctrl = (int (*)(BIO_METHOD *, long (*)(BIO *, int, long, void *)))openssl_sym(&err, "BIO_meth_set_ctrl", false); - BIO_meth_set_destroy = (int (*)(BIO_METHOD *, int (*)(BIO *)))openssl_sym(&err, "BIO_meth_set_destroy", false); - BIO_meth_set_gets = (int (*)(BIO_METHOD *, int (*)(BIO *, char *, int)))openssl_sym(&err, "BIO_meth_set_gets", false); - BIO_meth_set_puts = (int (*)(BIO_METHOD *, int (*)(BIO *, const char *)))openssl_sym(&err, "BIO_meth_set_puts", false); - BIO_meth_set_read = (int (*)(BIO_METHOD *, int (*)(BIO *, char *, int)))openssl_sym(&err, "BIO_meth_set_read", false); - BIO_meth_set_write = (int (*)(BIO_METHOD *, int (*)(BIO *, const char *, int)))openssl_sym(&err, "BIO_meth_set_write", false); - BIO_new = (BIO *(*)(const BIO_METHOD *))openssl_sym(&err, "BIO_new", true); - BIO_set_data = (void (*)(BIO *a, void *))openssl_sym(&err, "BIO_set_data", false); - BIO_set_init = (void (*)(BIO *a, int))openssl_sym(&err, "BIO_set_init", false); - - CRYPTO_free = (void (*)(void *, const char *, int))openssl_sym(&err, "CRYPTO_free", true); - CRYPTO_malloc = (void *(*)(size_t, const char *, int))openssl_sym(&err, "CRYPTO_malloc", true); - CRYPTO_num_locks = (int (*)(void))openssl_sym(&err, "CRYPTO_num_locks", false); - CRYPTO_set_locking_callback = (void (*)(void (*)(int, int, const char *, int)))openssl_sym(&err, "CRYPTO_set_locking_callback", false); - CRYPTO_set_mem_functions = (int (*)(void *(*)(size_t), void *(*)(void *, size_t), void (*f)(void *)))openssl_sym(&err, "CRYPTO_set_mem_functions", true); - - CRYPTO_THREADID_set_callback = (int (*)(void (*)(CRYPTO_THREADID *)))openssl_sym(&err, "CRYPTO_THREADID_set_callback", false); - CRYPTO_THREADID_set_numeric = (void (*)(CRYPTO_THREADID *, unsigned long))openssl_sym(&err, "CRYPTO_THREADID_set_numeric", false); - - ERR_error_string = (char *(*)(unsigned long, char *))openssl_sym(&err, "ERR_error_string", true); - ERR_error_string_n = (void (*)(unsigned long, char *, size_t))openssl_sym(&err, "ERR_error_string_n", true); - ERR_get_error = (unsigned long (*)(void))openssl_sym(&err, "ERR_get_error", true); - - OPENSSL_init_ssl = (int (*)(uint64_t opts, const void *settings))openssl_sym(&err, "OPENSSL_init_ssl", false); - OPENSSL_sk_num = (int (*)(const void *))openssl_sym(&err, "OPENSSL_sk_num", false); - OPENSSL_sk_value = (void *(*)(const void *sk, int i))openssl_sym(&err, "OPENSSL_sk_value", false); - OPENSSL_sk_free = (void (*)(void *))openssl_sym(&err, "OPENSSL_sk_free", false); - - sk_num = (int (*)(const void *))openssl_sym(&err, "sk_num", false); - sk_value = (void *(*)(const void *sk, int i))openssl_sym(&err, "sk_value", false); - sk_free = (void (*)(void *))openssl_sym(&err, "sk_free", false); - - SSL_connect = (int (*)(SSL *))openssl_sym(&err, "SSL_connect", true); - SSL_ctrl = (long (*)(SSL *, int, long, void *))openssl_sym(&err, "SSL_ctrl", true); - SSL_get_peer_certificate = (X509 *(*)(const SSL *))openssl_sym(&err, "SSL_get_peer_certificate", true); - SSL_library_init = (int (*)(void))openssl_sym(&err, "SSL_library_init", false); - SSL_free = (void (*)(SSL *))openssl_sym(&err, "SSL_free", true); - SSL_get_error = (int (*)(SSL *, int))openssl_sym(&err, "SSL_get_error", true); - SSL_get_verify_result = (long (*)(const SSL *ssl))openssl_sym(&err, "SSL_get_verify_result", true); - SSL_load_error_strings = (void (*)(void))openssl_sym(&err, "SSL_load_error_strings", false); - SSL_new = (SSL *(*)(SSL_CTX *))openssl_sym(&err, "SSL_new", true); - SSL_read = (int (*)(SSL *, const void *, int))openssl_sym(&err, "SSL_read", true); - SSL_set_bio = (void (*)(SSL *, BIO *, BIO *))openssl_sym(&err, "SSL_set_bio", true); - SSL_shutdown = (int (*)(SSL *ssl))openssl_sym(&err, "SSL_shutdown", true); - SSL_write = (int (*)(SSL *, const void *, int))openssl_sym(&err, "SSL_write", true); - - SSL_CTX_ctrl = (long (*)(SSL_CTX *, int, long, void *))openssl_sym(&err, "SSL_CTX_ctrl", true); - SSL_CTX_free = (void (*)(SSL_CTX *))openssl_sym(&err, "SSL_CTX_free", true); - SSL_CTX_new = (SSL_CTX *(*)(const SSL_METHOD *))openssl_sym(&err, "SSL_CTX_new", true); - SSL_CTX_set_cipher_list = (int (*)(SSL_CTX *, const char *))openssl_sym(&err, "SSL_CTX_set_cipher_list", true); - SSL_CTX_set_default_verify_paths = (int (*)(SSL_CTX *ctx))openssl_sym(&err, "SSL_CTX_set_default_verify_paths", true); - SSL_CTX_set_options = (long (*)(SSL_CTX *, long))openssl_sym(&err, "SSL_CTX_set_options", false); - SSL_CTX_set_verify = (void (*)(SSL_CTX *, int, int (*)(int, X509_STORE_CTX *)))openssl_sym(&err, "SSL_CTX_set_verify", true); - SSL_CTX_load_verify_locations = (int (*)(SSL_CTX *, const char *, const char *))openssl_sym(&err, "SSL_CTX_load_verify_locations", true); - - SSLv23_method = (const SSL_METHOD *(*)(void))openssl_sym(&err, "SSLv23_method", false); - TLS_method = (const SSL_METHOD *(*)(void))openssl_sym(&err, "TLS_method", false); - - X509_NAME_ENTRY_get_data = (ASN1_STRING *(*)(const X509_NAME_ENTRY *))openssl_sym(&err, "X509_NAME_ENTRY_get_data", true); - X509_NAME_get_entry = (X509_NAME_ENTRY *(*)(X509_NAME *, int))openssl_sym(&err, "X509_NAME_get_entry", true); - X509_NAME_get_index_by_NID = (int (*)(X509_NAME *, int, int))openssl_sym(&err, "X509_NAME_get_index_by_NID", true); - X509_free = (void (*)(X509 *))openssl_sym(&err, "X509_free", true); - X509_get_ext_d2i = (void *(*)(const X509 *x, int nid, int *crit, int *idx))openssl_sym(&err, "X509_get_ext_d2i", true); - X509_get_subject_name = (X509_NAME *(*)(const X509 *))openssl_sym(&err, "X509_get_subject_name", true); - - i2d_X509 = (int (*)(X509 *a, unsigned char **ppout))openssl_sym(&err, "i2d_X509", true); - - if (err) - goto on_error; - - /* Add legacy functionality */ - if (!OPENSSL_init_ssl) { - OPENSSL_init_ssl = OPENSSL_init_ssl__legacy; - - if (!SSL_library_init || - !SSL_load_error_strings || - !CRYPTO_num_locks || - !CRYPTO_set_locking_callback || - !CRYPTO_THREADID_set_callback || - !CRYPTO_THREADID_set_numeric) { - git_error_set(GIT_ERROR_SSL, "could not load legacy openssl initialization functions"); - goto on_error; - } - } - - if (!SSL_CTX_set_options) - SSL_CTX_set_options = SSL_CTX_set_options__legacy; - - if (TLS_method) - SSLv23_method = TLS_method; - - if (!BIO_meth_new) { - BIO_meth_new = BIO_meth_new__legacy; - BIO_meth_new = BIO_meth_new__legacy; - BIO_meth_free = BIO_meth_free__legacy; - BIO_meth_set_write = BIO_meth_set_write__legacy; - BIO_meth_set_read = BIO_meth_set_read__legacy; - BIO_meth_set_puts = BIO_meth_set_puts__legacy; - BIO_meth_set_gets = BIO_meth_set_gets__legacy; - BIO_meth_set_ctrl = BIO_meth_set_ctrl__legacy; - BIO_meth_set_create = BIO_meth_set_create__legacy; - BIO_meth_set_destroy = BIO_meth_set_destroy__legacy; - BIO_get_new_index = BIO_get_new_index__legacy; - BIO_set_data = BIO_set_data__legacy; - BIO_set_init = BIO_set_init__legacy; - BIO_get_data = BIO_get_data__legacy; - } - - if (!ASN1_STRING_get0_data) { - if (!ASN1_STRING_data) { - git_error_set(GIT_ERROR_SSL, "could not load legacy openssl string function"); - goto on_error; - } - - ASN1_STRING_get0_data = ASN1_STRING_get0_data__legacy; - } - - if ((!OPENSSL_sk_num && !sk_num) || - (!OPENSSL_sk_value && !sk_value) || - (!OPENSSL_sk_free && !sk_free)) { - git_error_set(GIT_ERROR_SSL, "could not load legacy openssl stack functions"); - goto on_error; - } - - if (git_runtime_shutdown_register(dynamic_shutdown) != 0) - goto on_error; - - return 0; - -on_error: - dlclose(openssl_handle); - return -1; -} - - -int sk_GENERAL_NAME_num(const GENERAL_NAME *sk) -{ - if (OPENSSL_sk_num) - return OPENSSL_sk_num(sk); - else if (sk_num) - return sk_num(sk); - - GIT_ASSERT_WITH_RETVAL(false, 0); - return 0; -} - -GENERAL_NAME *sk_GENERAL_NAME_value(const GENERAL_NAME *sk, int i) -{ - if (OPENSSL_sk_value) - return OPENSSL_sk_value(sk, i); - else if (sk_value) - return sk_value(sk, i); - - GIT_ASSERT_WITH_RETVAL(false, NULL); - return NULL; -} - -void GENERAL_NAMES_free(GENERAL_NAME *sk) -{ - if (OPENSSL_sk_free) - OPENSSL_sk_free(sk); - else if (sk_free) - sk_free(sk); -} - -#endif /* GIT_OPENSSL && GIT_OPENSSL_DYNAMIC */ diff --git a/src/streams/openssl_dynamic.h b/src/streams/openssl_dynamic.h deleted file mode 100644 index a99691910..000000000 --- a/src/streams/openssl_dynamic.h +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are adhered to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the routines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * The licence and distribution terms for any publicly available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ -/* ==================================================================== - * Copyright (c) 1998-2007 The OpenSSL Project. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ -/* ==================================================================== - * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. - * ECC cipher suite support in OpenSSL originally developed by - * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. - */ -/* ==================================================================== - * Copyright 2005 Nokia. All rights reserved. - * - * The portions of the attached software ("Contribution") is developed by - * Nokia Corporation and is licensed pursuant to the OpenSSL open source - * license. - * - * The Contribution, originally written by Mika Kousa and Pasi Eronen of - * Nokia Corporation, consists of the "PSK" (Pre-Shared Key) ciphersuites - * support (see RFC 4279) to OpenSSL. - * - * No patent licenses or other rights except those expressly stated in - * the OpenSSL open source license shall be deemed granted or received - * expressly, by implication, estoppel, or otherwise. - * - * No assurances are provided by Nokia that the Contribution does not - * infringe the patent or other intellectual property rights of any third - * party or that the license provides you with all the necessary rights - * to make use of the Contribution. - * - * THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. IN - * ADDITION TO THE DISCLAIMERS INCLUDED IN THE LICENSE, NOKIA - * SPECIFICALLY DISCLAIMS ANY LIABILITY FOR CLAIMS BROUGHT BY YOU OR ANY - * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR - * OTHERWISE. - */ - -#ifndef INCLUDE_streams_openssl_dynamic_h__ -#define INCLUDE_streams_openssl_dynamic_h__ - -#ifdef GIT_OPENSSL_DYNAMIC - -# define BIO_CTRL_FLUSH 11 - -# define BIO_TYPE_SOURCE_SINK 0x0400 - -# define CRYPTO_LOCK 1 - -# define GEN_DNS 2 -# define GEN_IPADD 7 - -# define NID_commonName 13 -# define NID_subject_alt_name 85 - -# define SSL_VERIFY_NONE 0x00 - -# define SSL_CTRL_OPTIONS 32 -# define SSL_CTRL_MODE 33 -# define SSL_CTRL_SET_TLSEXT_HOSTNAME 55 - -# define SSL_ERROR_NONE 0 -# define SSL_ERROR_SSL 1 -# define SSL_ERROR_WANT_READ 2 -# define SSL_ERROR_WANT_WRITE 3 -# define SSL_ERROR_WANT_X509_LOOKUP 4 -# define SSL_ERROR_SYSCALL 5 -# define SSL_ERROR_ZERO_RETURN 6 -# define SSL_ERROR_WANT_CONNECT 7 -# define SSL_ERROR_WANT_ACCEPT 8 - -# define SSL_OP_NO_COMPRESSION 0x00020000L -# define SSL_OP_NO_SSLv2 0x01000000L -# define SSL_OP_NO_SSLv3 0x02000000L - -# define SSL_MODE_AUTO_RETRY 0x00000004L - -# define TLSEXT_NAMETYPE_host_name 0 - -# define V_ASN1_UTF8STRING 12 - -# define X509_V_OK 0 - -/* Most of the OpenSSL types are mercifully opaque, so we can treat them like `void *` */ -typedef struct bio_st BIO; -typedef struct bio_method_st BIO_METHOD; -typedef void bio_info_cb; -typedef void * CRYPTO_EX_DATA; -typedef void CRYPTO_THREADID; -typedef void GENERAL_NAMES; -typedef void SSL; -typedef void SSL_CTX; -typedef void SSL_METHOD; -typedef void X509; -typedef void X509_NAME; -typedef void X509_NAME_ENTRY; -typedef void X509_STORE_CTX; - -typedef struct { - int length; - int type; - unsigned char *data; - long flags; -} ASN1_STRING; - -typedef struct { - int type; - union { - char *ptr; - ASN1_STRING *ia5; - } d; -} GENERAL_NAME; - -struct bio_st { - BIO_METHOD *method; - /* bio, mode, argp, argi, argl, ret */ - long (*callback) (struct bio_st *, int, const char *, int, long, long); - char *cb_arg; /* first argument for the callback */ - int init; - int shutdown; - int flags; /* extra storage */ - int retry_reason; - int num; - void *ptr; - struct bio_st *next_bio; /* used by filter BIOs */ - struct bio_st *prev_bio; /* used by filter BIOs */ - int references; - unsigned long num_read; - unsigned long num_write; - CRYPTO_EX_DATA ex_data; -}; - -struct bio_method_st { - int type; - const char *name; - int (*bwrite) (BIO *, const char *, int); - int (*bread) (BIO *, char *, int); - int (*bputs) (BIO *, const char *); - int (*bgets) (BIO *, char *, int); - long (*ctrl) (BIO *, int, long, void *); - int (*create) (BIO *); - int (*destroy) (BIO *); - long (*callback_ctrl) (BIO *, int, bio_info_cb *); -}; - -extern unsigned char *(*ASN1_STRING_data)(ASN1_STRING *x); -extern const unsigned char *(*ASN1_STRING_get0_data)(const ASN1_STRING *x); -extern int (*ASN1_STRING_length)(const ASN1_STRING *x); -extern int (*ASN1_STRING_to_UTF8)(unsigned char **out, const ASN1_STRING *in); -extern int (*ASN1_STRING_type)(const ASN1_STRING *x); - -extern void *(*BIO_get_data)(BIO *a); -extern int (*BIO_get_new_index)(void); -extern int (*OPENSSL_init_ssl)(uint64_t opts, const void *settings); -extern void (*BIO_meth_free)(BIO_METHOD *biom); -extern int (*BIO_meth_set_create)(BIO_METHOD *biom, int (*create) (BIO *)); -extern int (*BIO_meth_set_ctrl)(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)); -extern int (*BIO_meth_set_destroy)(BIO_METHOD *biom, int (*destroy) (BIO *)); -extern int (*BIO_meth_set_gets)(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)); -extern int (*BIO_meth_set_puts)(BIO_METHOD *biom, int (*puts) (BIO *, const char *)); -extern int (*BIO_meth_set_read)(BIO_METHOD *biom, int (*read) (BIO *, char *, int)); -extern int (*BIO_meth_set_write)(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)); -extern BIO_METHOD *(*BIO_meth_new)(int type, const char *name); -extern BIO *(*BIO_new)(const BIO_METHOD *type); -extern void (*BIO_set_data)(BIO *a, void *ptr); -extern void (*BIO_set_init)(BIO *a, int init); - -extern void (*CRYPTO_free)(void *ptr, const char *file, int line); -extern void *(*CRYPTO_malloc)(size_t num, const char *file, int line); -extern int (*CRYPTO_num_locks)(void); -extern void (*CRYPTO_set_locking_callback)(void (*func)(int mode, int type, const char *file, int line)); -extern int (*CRYPTO_set_mem_functions)(void *(*m)(size_t bytes), void *(*r)(void *mem, size_t size), void (*f)(void *mem)); -extern int (*CRYPTO_THREADID_set_callback)(void (*func)(CRYPTO_THREADID *id)); -extern void (*CRYPTO_THREADID_set_numeric)(CRYPTO_THREADID *id, unsigned long val); - -extern char *(*ERR_error_string)(unsigned long e, char *buf); -extern void (*ERR_error_string_n)(unsigned long e, char *buf, size_t len); -extern unsigned long (*ERR_get_error)(void); - -# define OPENSSL_malloc(num) CRYPTO_malloc(num, __FILE__, __LINE__) -# define OPENSSL_free(addr) CRYPTO_free(addr, __FILE__, __LINE__) - -extern int (*SSL_connect)(SSL *ssl); -extern long (*SSL_ctrl)(SSL *ssl, int cmd, long arg, void *parg); -extern void (*SSL_free)(SSL *ssl); -extern int (*SSL_get_error)(SSL *ssl, int ret); -extern X509 *(*SSL_get_peer_certificate)(const SSL *ssl); -extern long (*SSL_get_verify_result)(const SSL *ssl); -extern int (*SSL_library_init)(void); -extern void (*SSL_load_error_strings)(void); -extern SSL *(*SSL_new)(SSL_CTX *ctx); -extern int (*SSL_read)(SSL *ssl, const void *buf, int num); -extern void (*SSL_set_bio)(SSL *ssl, BIO *rbio, BIO *wbio); -extern int (*SSL_shutdown)(SSL *ssl); -extern int (*SSL_write)(SSL *ssl, const void *buf, int num); - -# define SSL_set_tlsext_host_name(s, name) SSL_ctrl((s), SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (char *)(name)); - -extern long (*SSL_CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg); -extern void (*SSL_CTX_free)(SSL_CTX *ctx); -extern SSL_CTX *(*SSL_CTX_new)(const SSL_METHOD *method); -extern int (*SSL_CTX_set_cipher_list)(SSL_CTX *ctx, const char *str); -extern int (*SSL_CTX_set_default_verify_paths)(SSL_CTX *ctx); -extern long (*SSL_CTX_set_options)(SSL_CTX *ctx, long options); -extern void (*SSL_CTX_set_verify)(SSL_CTX *ctx, int mode, int (*verify_callback)(int, X509_STORE_CTX *)); -extern int (*SSL_CTX_load_verify_locations)(SSL_CTX *ctx, const char *CAfile, const char *CApath); - -# define SSL_CTX_set_mode(ctx, mode) SSL_CTX_ctrl((ctx), SSL_CTRL_MODE, (mode), NULL); - -extern const SSL_METHOD *(*SSLv23_method)(void); -extern const SSL_METHOD *(*TLS_method)(void); - -extern ASN1_STRING *(*X509_NAME_ENTRY_get_data)(const X509_NAME_ENTRY *ne); -extern X509_NAME_ENTRY *(*X509_NAME_get_entry)(X509_NAME *name, int loc); -extern int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos); -extern void (*X509_free)(X509 *a); -extern void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx); -extern X509_NAME *(*X509_get_subject_name)(const X509 *x); - -extern int (*i2d_X509)(X509 *a, unsigned char **ppout); - -extern int (*OPENSSL_sk_num)(const void *sk); -extern void *(*OPENSSL_sk_value)(const void *sk, int i); -extern void (*OPENSSL_sk_free)(void *sk); - -extern int (*sk_num)(const void *sk); -extern void *(*sk_value)(const void *sk, int i); -extern void (*sk_free)(void *sk); - -extern int sk_GENERAL_NAME_num(const GENERAL_NAME *sk); -extern GENERAL_NAME *sk_GENERAL_NAME_value(const GENERAL_NAME *sk, int i); -extern void GENERAL_NAMES_free(GENERAL_NAME *sk); - -extern int git_openssl_stream_dynamic_init(void); - -#endif /* GIT_OPENSSL_DYNAMIC */ - -#endif diff --git a/src/streams/openssl_legacy.c b/src/streams/openssl_legacy.c deleted file mode 100644 index e61e6efbb..000000000 --- a/src/streams/openssl_legacy.c +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "streams/openssl.h" -#include "streams/openssl_legacy.h" - -#include "runtime.h" -#include "git2/sys/openssl.h" - -#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC) -# include -# include -# include -# include -#endif - -#if defined(GIT_OPENSSL_LEGACY) || defined(GIT_OPENSSL_DYNAMIC) - -/* - * OpenSSL 1.1 made BIO opaque so we have to use functions to interact with it - * which do not exist in previous versions. We define these inline functions so - * we can program against the interface instead of littering the implementation - * with ifdefs. We do the same for OPENSSL_init_ssl. - */ - -int OPENSSL_init_ssl__legacy(uint64_t opts, const void *settings) -{ - GIT_UNUSED(opts); - GIT_UNUSED(settings); - SSL_load_error_strings(); - SSL_library_init(); - return 0; -} - -BIO_METHOD *BIO_meth_new__legacy(int type, const char *name) -{ - BIO_METHOD *meth = git__calloc(1, sizeof(BIO_METHOD)); - if (!meth) { - return NULL; - } - - meth->type = type; - meth->name = name; - - return meth; -} - -void BIO_meth_free__legacy(BIO_METHOD *biom) -{ - git__free(biom); -} - -int BIO_meth_set_write__legacy(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)) -{ - biom->bwrite = write; - return 1; -} - -int BIO_meth_set_read__legacy(BIO_METHOD *biom, int (*read) (BIO *, char *, int)) -{ - biom->bread = read; - return 1; -} - -int BIO_meth_set_puts__legacy(BIO_METHOD *biom, int (*puts) (BIO *, const char *)) -{ - biom->bputs = puts; - return 1; -} - -int BIO_meth_set_gets__legacy(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)) - -{ - biom->bgets = gets; - return 1; -} - -int BIO_meth_set_ctrl__legacy(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)) -{ - biom->ctrl = ctrl; - return 1; -} - -int BIO_meth_set_create__legacy(BIO_METHOD *biom, int (*create) (BIO *)) -{ - biom->create = create; - return 1; -} - -int BIO_meth_set_destroy__legacy(BIO_METHOD *biom, int (*destroy) (BIO *)) -{ - biom->destroy = destroy; - return 1; -} - -int BIO_get_new_index__legacy(void) -{ - /* This exists as of 1.1 so before we'd just have 0 */ - return 0; -} - -void BIO_set_init__legacy(BIO *b, int init) -{ - b->init = init; -} - -void BIO_set_data__legacy(BIO *a, void *ptr) -{ - a->ptr = ptr; -} - -void *BIO_get_data__legacy(BIO *a) -{ - return a->ptr; -} - -const unsigned char *ASN1_STRING_get0_data__legacy(const ASN1_STRING *x) -{ - return ASN1_STRING_data((ASN1_STRING *)x); -} - -long SSL_CTX_set_options__legacy(SSL_CTX *ctx, long op) -{ - return SSL_CTX_ctrl(ctx, SSL_CTRL_OPTIONS, op, NULL); -} - -# if defined(GIT_THREADS) -static git_mutex *openssl_locks; - -static void openssl_locking_function(int mode, int n, const char *file, int line) -{ - int lock; - - GIT_UNUSED(file); - GIT_UNUSED(line); - - lock = mode & CRYPTO_LOCK; - - if (lock) - (void)git_mutex_lock(&openssl_locks[n]); - else - git_mutex_unlock(&openssl_locks[n]); -} - -static void shutdown_ssl_locking(void) -{ - int num_locks, i; - - num_locks = CRYPTO_num_locks(); - CRYPTO_set_locking_callback(NULL); - - for (i = 0; i < num_locks; ++i) - git_mutex_free(&openssl_locks[i]); - git__free(openssl_locks); -} - -static void threadid_cb(CRYPTO_THREADID *threadid) -{ - GIT_UNUSED(threadid); - CRYPTO_THREADID_set_numeric(threadid, git_thread_currentid()); -} - -int git_openssl_set_locking(void) -{ - int num_locks, i; - -#ifndef GIT_THREADS - git_error_set(GIT_ERROR_THREAD, "libgit2 was not built with threads"); - return -1; -#endif - -#ifdef GIT_OPENSSL_DYNAMIC - /* - * This function is required on legacy versions of OpenSSL; when building - * with dynamically-loaded OpenSSL, we detect whether we loaded it or not. - */ - if (!CRYPTO_set_locking_callback) - return 0; -#endif - - CRYPTO_THREADID_set_callback(threadid_cb); - - num_locks = CRYPTO_num_locks(); - openssl_locks = git__calloc(num_locks, sizeof(git_mutex)); - GIT_ERROR_CHECK_ALLOC(openssl_locks); - - for (i = 0; i < num_locks; i++) { - if (git_mutex_init(&openssl_locks[i]) != 0) { - git_error_set(GIT_ERROR_SSL, "failed to initialize openssl locks"); - return -1; - } - } - - CRYPTO_set_locking_callback(openssl_locking_function); - return git_runtime_shutdown_register(shutdown_ssl_locking); -} -#endif /* GIT_THREADS */ - -#endif /* GIT_OPENSSL_LEGACY || GIT_OPENSSL_DYNAMIC */ diff --git a/src/streams/openssl_legacy.h b/src/streams/openssl_legacy.h deleted file mode 100644 index e6dae9572..000000000 --- a/src/streams/openssl_legacy.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_streams_openssl_legacy_h__ -#define INCLUDE_streams_openssl_legacy_h__ - -#include "streams/openssl_dynamic.h" - -#if defined(GIT_OPENSSL) && !defined(GIT_OPENSSL_DYNAMIC) -# include -# include -# include -# include - -# if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) || \ - (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) -# define GIT_OPENSSL_LEGACY -# endif -#endif - -#if defined(GIT_OPENSSL_LEGACY) && !defined(GIT_OPENSSL_DYNAMIC) -# define OPENSSL_init_ssl OPENSSL_init_ssl__legacy -# define BIO_meth_new BIO_meth_new__legacy -# define BIO_meth_free BIO_meth_free__legacy -# define BIO_meth_set_write BIO_meth_set_write__legacy -# define BIO_meth_set_read BIO_meth_set_read__legacy -# define BIO_meth_set_puts BIO_meth_set_puts__legacy -# define BIO_meth_set_gets BIO_meth_set_gets__legacy -# define BIO_meth_set_ctrl BIO_meth_set_ctrl__legacy -# define BIO_meth_set_create BIO_meth_set_create__legacy -# define BIO_meth_set_destroy BIO_meth_set_destroy__legacy -# define BIO_get_new_index BIO_get_new_index__legacy -# define BIO_set_data BIO_set_data__legacy -# define BIO_set_init BIO_set_init__legacy -# define BIO_get_data BIO_get_data__legacy -# define ASN1_STRING_get0_data ASN1_STRING_get0_data__legacy -#endif - -#if defined(GIT_OPENSSL_LEGACY) || defined(GIT_OPENSSL_DYNAMIC) - -extern int OPENSSL_init_ssl__legacy(uint64_t opts, const void *settings); -extern BIO_METHOD *BIO_meth_new__legacy(int type, const char *name); -extern void BIO_meth_free__legacy(BIO_METHOD *biom); -extern int BIO_meth_set_write__legacy(BIO_METHOD *biom, int (*write) (BIO *, const char *, int)); -extern int BIO_meth_set_read__legacy(BIO_METHOD *biom, int (*read) (BIO *, char *, int)); -extern int BIO_meth_set_puts__legacy(BIO_METHOD *biom, int (*puts) (BIO *, const char *)); -extern int BIO_meth_set_gets__legacy(BIO_METHOD *biom, int (*gets) (BIO *, char *, int)); -extern int BIO_meth_set_ctrl__legacy(BIO_METHOD *biom, long (*ctrl) (BIO *, int, long, void *)); -extern int BIO_meth_set_create__legacy(BIO_METHOD *biom, int (*create) (BIO *)); -extern int BIO_meth_set_destroy__legacy(BIO_METHOD *biom, int (*destroy) (BIO *)); -extern int BIO_get_new_index__legacy(void); -extern void BIO_set_data__legacy(BIO *a, void *ptr); -extern void BIO_set_init__legacy(BIO *b, int init); -extern void *BIO_get_data__legacy(BIO *a); -extern const unsigned char *ASN1_STRING_get0_data__legacy(const ASN1_STRING *x); -extern long SSL_CTX_set_options__legacy(SSL_CTX *ctx, long op); - -#endif - -#endif diff --git a/src/streams/registry.c b/src/streams/registry.c deleted file mode 100644 index e60e1cd63..000000000 --- a/src/streams/registry.c +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "streams/registry.h" - -#include "runtime.h" -#include "streams/tls.h" -#include "streams/mbedtls.h" -#include "streams/openssl.h" -#include "streams/stransport.h" - -struct stream_registry { - git_rwlock lock; - git_stream_registration callbacks; - git_stream_registration tls_callbacks; -}; - -static struct stream_registry stream_registry; - -static void shutdown_stream_registry(void) -{ - git_rwlock_free(&stream_registry.lock); -} - -int git_stream_registry_global_init(void) -{ - if (git_rwlock_init(&stream_registry.lock) < 0) - return -1; - - return git_runtime_shutdown_register(shutdown_stream_registry); -} - -GIT_INLINE(void) stream_registration_cpy( - git_stream_registration *target, - git_stream_registration *src) -{ - if (src) - memcpy(target, src, sizeof(git_stream_registration)); - else - memset(target, 0, sizeof(git_stream_registration)); -} - -int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type) -{ - git_stream_registration *target; - int error = GIT_ENOTFOUND; - - GIT_ASSERT_ARG(out); - - switch(type) { - case GIT_STREAM_STANDARD: - target = &stream_registry.callbacks; - break; - case GIT_STREAM_TLS: - target = &stream_registry.tls_callbacks; - break; - default: - git_error_set(GIT_ERROR_INVALID, "invalid stream type"); - return -1; - } - - if (git_rwlock_rdlock(&stream_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock stream registry"); - return -1; - } - - if (target->init) { - stream_registration_cpy(out, target); - error = 0; - } - - git_rwlock_rdunlock(&stream_registry.lock); - return error; -} - -int git_stream_register(git_stream_t type, git_stream_registration *registration) -{ - GIT_ASSERT(!registration || registration->init); - - GIT_ERROR_CHECK_VERSION(registration, GIT_STREAM_VERSION, "stream_registration"); - - if (git_rwlock_wrlock(&stream_registry.lock) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock stream registry"); - return -1; - } - - if ((type & GIT_STREAM_STANDARD) == GIT_STREAM_STANDARD) - stream_registration_cpy(&stream_registry.callbacks, registration); - - if ((type & GIT_STREAM_TLS) == GIT_STREAM_TLS) - stream_registration_cpy(&stream_registry.tls_callbacks, registration); - - git_rwlock_wrunlock(&stream_registry.lock); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_stream_register_tls( - int GIT_CALLBACK(ctor)(git_stream **out, const char *host, const char *port)) -{ - git_stream_registration registration = {0}; - - if (ctor) { - registration.version = GIT_STREAM_VERSION; - registration.init = ctor; - registration.wrap = NULL; - - return git_stream_register(GIT_STREAM_TLS, ®istration); - } else { - return git_stream_register(GIT_STREAM_TLS, NULL); - } -} -#endif diff --git a/src/streams/registry.h b/src/streams/registry.h deleted file mode 100644 index adc2b8bdf..000000000 --- a/src/streams/registry.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_streams_registry_h__ -#define INCLUDE_streams_registry_h__ - -#include "common.h" -#include "git2/sys/stream.h" - -/** Configure stream registry. */ -int git_stream_registry_global_init(void); - -/** Lookup a stream registration. */ -extern int git_stream_registry_lookup(git_stream_registration *out, git_stream_t type); - -#endif diff --git a/src/streams/socket.c b/src/streams/socket.c deleted file mode 100644 index 9415fe892..000000000 --- a/src/streams/socket.c +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "streams/socket.h" - -#include "posix.h" -#include "netops.h" -#include "registry.h" -#include "stream.h" - -#ifndef _WIN32 -# include -# include -# include -# include -# include -# include -# include -#else -# include -# include -# ifdef _MSC_VER -# pragma comment(lib, "ws2_32") -# endif -#endif - -#ifdef GIT_WIN32 -static void net_set_error(const char *str) -{ - int error = WSAGetLastError(); - char * win32_error = git_win32_get_error_message(error); - - if (win32_error) { - git_error_set(GIT_ERROR_NET, "%s: %s", str, win32_error); - git__free(win32_error); - } else { - git_error_set(GIT_ERROR_NET, "%s", str); - } -} -#else -static void net_set_error(const char *str) -{ - git_error_set(GIT_ERROR_NET, "%s: %s", str, strerror(errno)); -} -#endif - -static int close_socket(GIT_SOCKET s) -{ - if (s == INVALID_SOCKET) - return 0; - -#ifdef GIT_WIN32 - if (SOCKET_ERROR == closesocket(s)) - return -1; - - if (0 != WSACleanup()) { - git_error_set(GIT_ERROR_OS, "winsock cleanup failed"); - return -1; - } - - return 0; -#else - return close(s); -#endif - -} - -static int socket_connect(git_stream *stream) -{ - struct addrinfo *info = NULL, *p; - struct addrinfo hints; - git_socket_stream *st = (git_socket_stream *) stream; - GIT_SOCKET s = INVALID_SOCKET; - int ret; - -#ifdef GIT_WIN32 - /* on win32, the WSA context needs to be initialized - * before any socket calls can be performed */ - WSADATA wsd; - - if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { - git_error_set(GIT_ERROR_OS, "winsock init failed"); - return -1; - } - - if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { - WSACleanup(); - git_error_set(GIT_ERROR_OS, "winsock init failed"); - return -1; - } -#endif - - memset(&hints, 0x0, sizeof(struct addrinfo)); - hints.ai_socktype = SOCK_STREAM; - hints.ai_family = AF_UNSPEC; - - if ((ret = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) { - git_error_set(GIT_ERROR_NET, - "failed to resolve address for %s: %s", st->host, p_gai_strerror(ret)); - return -1; - } - - for (p = info; p != NULL; p = p->ai_next) { - s = socket(p->ai_family, p->ai_socktype | SOCK_CLOEXEC, p->ai_protocol); - - if (s == INVALID_SOCKET) - continue; - - if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) - break; - - /* If we can't connect, try the next one */ - close_socket(s); - s = INVALID_SOCKET; - } - - /* Oops, we couldn't connect to any address */ - if (s == INVALID_SOCKET && p == NULL) { - git_error_set(GIT_ERROR_OS, "failed to connect to %s", st->host); - p_freeaddrinfo(info); - return -1; - } - - st->s = s; - p_freeaddrinfo(info); - return 0; -} - -static ssize_t socket_write(git_stream *stream, const char *data, size_t len, int flags) -{ - git_socket_stream *st = (git_socket_stream *) stream; - ssize_t written; - - errno = 0; - - if ((written = p_send(st->s, data, len, flags)) < 0) { - net_set_error("error sending data"); - return -1; - } - - return written; -} - -static ssize_t socket_read(git_stream *stream, void *data, size_t len) -{ - ssize_t ret; - git_socket_stream *st = (git_socket_stream *) stream; - - if ((ret = p_recv(st->s, data, len, 0)) < 0) - net_set_error("error receiving socket data"); - - return ret; -} - -static int socket_close(git_stream *stream) -{ - git_socket_stream *st = (git_socket_stream *) stream; - int error; - - error = close_socket(st->s); - st->s = INVALID_SOCKET; - - return error; -} - -static void socket_free(git_stream *stream) -{ - git_socket_stream *st = (git_socket_stream *) stream; - - git__free(st->host); - git__free(st->port); - git__free(st); -} - -static int default_socket_stream_new( - git_stream **out, - const char *host, - const char *port) -{ - git_socket_stream *st; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(host); - GIT_ASSERT_ARG(port); - - st = git__calloc(1, sizeof(git_socket_stream)); - GIT_ERROR_CHECK_ALLOC(st); - - st->host = git__strdup(host); - GIT_ERROR_CHECK_ALLOC(st->host); - - if (port) { - st->port = git__strdup(port); - GIT_ERROR_CHECK_ALLOC(st->port); - } - - st->parent.version = GIT_STREAM_VERSION; - st->parent.connect = socket_connect; - st->parent.write = socket_write; - st->parent.read = socket_read; - st->parent.close = socket_close; - st->parent.free = socket_free; - st->s = INVALID_SOCKET; - - *out = (git_stream *) st; - return 0; -} - -int git_socket_stream_new( - git_stream **out, - const char *host, - const char *port) -{ - int (*init)(git_stream **, const char *, const char *) = NULL; - git_stream_registration custom = {0}; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(host); - GIT_ASSERT_ARG(port); - - if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_STANDARD)) == 0) - init = custom.init; - else if (error == GIT_ENOTFOUND) - init = default_socket_stream_new; - else - return error; - - if (!init) { - git_error_set(GIT_ERROR_NET, "there is no socket stream available"); - return -1; - } - - return init(out, host, port); -} diff --git a/src/streams/socket.h b/src/streams/socket.h deleted file mode 100644 index 3235f3167..000000000 --- a/src/streams/socket.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_streams_socket_h__ -#define INCLUDE_streams_socket_h__ - -#include "common.h" - -#include "netops.h" - -typedef struct { - git_stream parent; - char *host; - char *port; - GIT_SOCKET s; -} git_socket_stream; - -extern int git_socket_stream_new(git_stream **out, const char *host, const char *port); - -#endif diff --git a/src/streams/stransport.c b/src/streams/stransport.c deleted file mode 100644 index 3f31d2541..000000000 --- a/src/streams/stransport.c +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "streams/stransport.h" - -#ifdef GIT_SECURE_TRANSPORT - -#include -#include -#include - -#include "git2/transport.h" - -#include "streams/socket.h" - -static int stransport_error(OSStatus ret) -{ - CFStringRef message; - - if (ret == noErr || ret == errSSLClosedGraceful) { - git_error_clear(); - return 0; - } - -#if !TARGET_OS_IPHONE - message = SecCopyErrorMessageString(ret, NULL); - GIT_ERROR_CHECK_ALLOC(message); - - git_error_set(GIT_ERROR_NET, "SecureTransport error: %s", CFStringGetCStringPtr(message, kCFStringEncodingUTF8)); - CFRelease(message); -#else - git_error_set(GIT_ERROR_NET, "SecureTransport error: OSStatus %d", (unsigned int)ret); - GIT_UNUSED(message); -#endif - - return -1; -} - -typedef struct { - git_stream parent; - git_stream *io; - int owned; - SSLContextRef ctx; - CFDataRef der_data; - git_cert_x509 cert_info; -} stransport_stream; - -static int stransport_connect(git_stream *stream) -{ - stransport_stream *st = (stransport_stream *) stream; - int error; - SecTrustRef trust = NULL; - SecTrustResultType sec_res; - OSStatus ret; - - if (st->owned && (error = git_stream_connect(st->io)) < 0) - return error; - - ret = SSLHandshake(st->ctx); - if (ret != errSSLServerAuthCompleted) { - git_error_set(GIT_ERROR_SSL, "unexpected return value from ssl handshake %d", (int)ret); - return -1; - } - - if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr) - goto on_error; - - if (!trust) - return GIT_ECERTIFICATE; - - if ((ret = SecTrustEvaluate(trust, &sec_res)) != noErr) - goto on_error; - - CFRelease(trust); - - if (sec_res == kSecTrustResultInvalid || sec_res == kSecTrustResultOtherError) { - git_error_set(GIT_ERROR_SSL, "internal security trust error"); - return -1; - } - - if (sec_res == kSecTrustResultDeny || sec_res == kSecTrustResultRecoverableTrustFailure || - sec_res == kSecTrustResultFatalTrustFailure) { - git_error_set(GIT_ERROR_SSL, "untrusted connection error"); - return GIT_ECERTIFICATE; - } - - return 0; - -on_error: - if (trust) - CFRelease(trust); - - return stransport_error(ret); -} - -static int stransport_certificate(git_cert **out, git_stream *stream) -{ - stransport_stream *st = (stransport_stream *) stream; - SecTrustRef trust = NULL; - SecCertificateRef sec_cert; - OSStatus ret; - - if ((ret = SSLCopyPeerTrust(st->ctx, &trust)) != noErr) - return stransport_error(ret); - - sec_cert = SecTrustGetCertificateAtIndex(trust, 0); - st->der_data = SecCertificateCopyData(sec_cert); - CFRelease(trust); - - if (st->der_data == NULL) { - git_error_set(GIT_ERROR_SSL, "retrieved invalid certificate data"); - return -1; - } - - st->cert_info.parent.cert_type = GIT_CERT_X509; - st->cert_info.data = (void *) CFDataGetBytePtr(st->der_data); - st->cert_info.len = CFDataGetLength(st->der_data); - - *out = (git_cert *)&st->cert_info; - return 0; -} - -static int stransport_set_proxy( - git_stream *stream, - const git_proxy_options *proxy_opts) -{ - stransport_stream *st = (stransport_stream *) stream; - - return git_stream_set_proxy(st->io, proxy_opts); -} - -/* - * Contrary to typical network IO callbacks, Secure Transport write callback is - * expected to write *all* passed data, not just as much as it can, and any - * other case would be considered a failure. - * - * This behavior is actually not specified in the Apple documentation, but is - * required for things to work correctly (and incidentally, that's also how - * Apple implements it in its projects at opensource.apple.com). - * - * Libgit2 streams happen to already have this very behavior so this is just - * passthrough. - */ -static OSStatus write_cb(SSLConnectionRef conn, const void *data, size_t *len) -{ - git_stream *io = (git_stream *) conn; - - if (git_stream__write_full(io, data, *len, 0) < 0) - return -36; /* "ioErr" from MacErrors.h which is not available on iOS */ - - return noErr; -} - -static ssize_t stransport_write(git_stream *stream, const char *data, size_t len, int flags) -{ - stransport_stream *st = (stransport_stream *) stream; - size_t data_len, processed; - OSStatus ret; - - GIT_UNUSED(flags); - - data_len = min(len, SSIZE_MAX); - if ((ret = SSLWrite(st->ctx, data, data_len, &processed)) != noErr) - return stransport_error(ret); - - GIT_ASSERT(processed < SSIZE_MAX); - return (ssize_t)processed; -} - -/* - * Contrary to typical network IO callbacks, Secure Transport read callback is - * expected to read *exactly* the requested number of bytes, not just as much - * as it can, and any other case would be considered a failure. - * - * This behavior is actually not specified in the Apple documentation, but is - * required for things to work correctly (and incidentally, that's also how - * Apple implements it in its projects at opensource.apple.com). - */ -static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len) -{ - git_stream *io = (git_stream *) conn; - OSStatus error = noErr; - size_t off = 0; - ssize_t ret; - - do { - ret = git_stream_read(io, data + off, *len - off); - if (ret < 0) { - error = -36; /* "ioErr" from MacErrors.h which is not available on iOS */ - break; - } - if (ret == 0) { - error = errSSLClosedGraceful; - break; - } - - off += ret; - } while (off < *len); - - *len = off; - return error; -} - -static ssize_t stransport_read(git_stream *stream, void *data, size_t len) -{ - stransport_stream *st = (stransport_stream *) stream; - size_t processed; - OSStatus ret; - - if ((ret = SSLRead(st->ctx, data, len, &processed)) != noErr) - return stransport_error(ret); - - return processed; -} - -static int stransport_close(git_stream *stream) -{ - stransport_stream *st = (stransport_stream *) stream; - OSStatus ret; - - ret = SSLClose(st->ctx); - if (ret != noErr && ret != errSSLClosedGraceful) - return stransport_error(ret); - - return st->owned ? git_stream_close(st->io) : 0; -} - -static void stransport_free(git_stream *stream) -{ - stransport_stream *st = (stransport_stream *) stream; - - if (st->owned) - git_stream_free(st->io); - - CFRelease(st->ctx); - if (st->der_data) - CFRelease(st->der_data); - git__free(st); -} - -static int stransport_wrap( - git_stream **out, - git_stream *in, - const char *host, - int owned) -{ - stransport_stream *st; - OSStatus ret; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(in); - GIT_ASSERT_ARG(host); - - st = git__calloc(1, sizeof(stransport_stream)); - GIT_ERROR_CHECK_ALLOC(st); - - st->io = in; - st->owned = owned; - - st->ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType); - if (!st->ctx) { - git_error_set(GIT_ERROR_NET, "failed to create SSL context"); - git__free(st); - return -1; - } - - if ((ret = SSLSetIOFuncs(st->ctx, read_cb, write_cb)) != noErr || - (ret = SSLSetConnection(st->ctx, st->io)) != noErr || - (ret = SSLSetSessionOption(st->ctx, kSSLSessionOptionBreakOnServerAuth, true)) != noErr || - (ret = SSLSetProtocolVersionMin(st->ctx, kTLSProtocol1)) != noErr || - (ret = SSLSetProtocolVersionMax(st->ctx, kTLSProtocol12)) != noErr || - (ret = SSLSetPeerDomainName(st->ctx, host, strlen(host))) != noErr) { - CFRelease(st->ctx); - git__free(st); - return stransport_error(ret); - } - - st->parent.version = GIT_STREAM_VERSION; - st->parent.encrypted = 1; - st->parent.proxy_support = git_stream_supports_proxy(st->io); - st->parent.connect = stransport_connect; - st->parent.certificate = stransport_certificate; - st->parent.set_proxy = stransport_set_proxy; - st->parent.read = stransport_read; - st->parent.write = stransport_write; - st->parent.close = stransport_close; - st->parent.free = stransport_free; - - *out = (git_stream *) st; - return 0; -} - -int git_stransport_stream_wrap( - git_stream **out, - git_stream *in, - const char *host) -{ - return stransport_wrap(out, in, host, 0); -} - -int git_stransport_stream_new(git_stream **out, const char *host, const char *port) -{ - git_stream *stream = NULL; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(host); - - error = git_socket_stream_new(&stream, host, port); - - if (!error) - error = stransport_wrap(out, stream, host, 1); - - if (error < 0 && stream) { - git_stream_close(stream); - git_stream_free(stream); - } - - return error; -} - -#endif diff --git a/src/streams/stransport.h b/src/streams/stransport.h deleted file mode 100644 index 1026e204b..000000000 --- a/src/streams/stransport.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_streams_stransport_h__ -#define INCLUDE_streams_stransport_h__ - -#include "common.h" - -#include "git2/sys/stream.h" - -#ifdef GIT_SECURE_TRANSPORT - -extern int git_stransport_stream_new(git_stream **out, const char *host, const char *port); -extern int git_stransport_stream_wrap(git_stream **out, git_stream *in, const char *host); - -#endif - -#endif diff --git a/src/streams/tls.c b/src/streams/tls.c deleted file mode 100644 index e063a33f9..000000000 --- a/src/streams/tls.c +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "git2/errors.h" - -#include "common.h" -#include "streams/registry.h" -#include "streams/tls.h" -#include "streams/mbedtls.h" -#include "streams/openssl.h" -#include "streams/stransport.h" - -int git_tls_stream_new(git_stream **out, const char *host, const char *port) -{ - int (*init)(git_stream **, const char *, const char *) = NULL; - git_stream_registration custom = {0}; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(host); - GIT_ASSERT_ARG(port); - - if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_TLS)) == 0) { - init = custom.init; - } else if (error == GIT_ENOTFOUND) { -#ifdef GIT_SECURE_TRANSPORT - init = git_stransport_stream_new; -#elif defined(GIT_OPENSSL) - init = git_openssl_stream_new; -#elif defined(GIT_MBEDTLS) - init = git_mbedtls_stream_new; -#endif - } else { - return error; - } - - if (!init) { - git_error_set(GIT_ERROR_SSL, "there is no TLS stream available"); - return -1; - } - - return init(out, host, port); -} - -int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host) -{ - int (*wrap)(git_stream **, git_stream *, const char *) = NULL; - git_stream_registration custom = {0}; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(in); - - if (git_stream_registry_lookup(&custom, GIT_STREAM_TLS) == 0) { - wrap = custom.wrap; - } else { -#ifdef GIT_SECURE_TRANSPORT - wrap = git_stransport_stream_wrap; -#elif defined(GIT_OPENSSL) - wrap = git_openssl_stream_wrap; -#elif defined(GIT_MBEDTLS) - wrap = git_mbedtls_stream_wrap; -#endif - } - - if (!wrap) { - git_error_set(GIT_ERROR_SSL, "there is no TLS stream available"); - return -1; - } - - return wrap(out, in, host); -} diff --git a/src/streams/tls.h b/src/streams/tls.h deleted file mode 100644 index 465a6ea89..000000000 --- a/src/streams/tls.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_streams_tls_h__ -#define INCLUDE_streams_tls_h__ - -#include "common.h" - -#include "git2/sys/stream.h" - -/** - * Create a TLS stream with the most appropriate backend available for - * the current platform, whether that's SecureTransport on macOS, - * OpenSSL or mbedTLS on other Unixes, or something else entirely. - */ -extern int git_tls_stream_new(git_stream **out, const char *host, const char *port); - -/** - * Create a TLS stream on top of an existing insecure stream, using - * the most appropriate backend available for the current platform. - * - * This allows us to create a CONNECT stream on top of a proxy; - * using SecureTransport on macOS, OpenSSL or mbedTLS on other - * Unixes, or something else entirely. - */ -extern int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host); - -#endif diff --git a/src/strmap.c b/src/strmap.c deleted file mode 100644 index c6e5b6dc7..000000000 --- a/src/strmap.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "strmap.h" - -#define kmalloc git__malloc -#define kcalloc git__calloc -#define krealloc git__realloc -#define kreallocarray git__reallocarray -#define kfree git__free -#include "khash.h" - -__KHASH_TYPE(str, const char *, void *) - -__KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) - -int git_strmap_new(git_strmap **out) -{ - *out = kh_init(str); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -void git_strmap_free(git_strmap *map) -{ - kh_destroy(str, map); -} - -void git_strmap_clear(git_strmap *map) -{ - kh_clear(str, map); -} - -size_t git_strmap_size(git_strmap *map) -{ - return kh_size(map); -} - -void *git_strmap_get(git_strmap *map, const char *key) -{ - size_t idx = kh_get(str, map, key); - if (idx == kh_end(map) || !kh_exist(map, idx)) - return NULL; - return kh_val(map, idx); -} - -int git_strmap_set(git_strmap *map, const char *key, void *value) -{ - size_t idx; - int rval; - - idx = kh_put(str, map, key, &rval); - if (rval < 0) - return -1; - - if (rval == 0) - kh_key(map, idx) = key; - - kh_val(map, idx) = value; - - return 0; -} - -int git_strmap_delete(git_strmap *map, const char *key) -{ - khiter_t idx = kh_get(str, map, key); - if (idx == kh_end(map)) - return GIT_ENOTFOUND; - kh_del(str, map, idx); - return 0; -} - -int git_strmap_exists(git_strmap *map, const char *key) -{ - return kh_get(str, map, key) != kh_end(map); -} - -int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key) -{ - size_t i = *iter; - - while (i < map->n_buckets && !kh_exist(map, i)) - i++; - - if (i >= map->n_buckets) - return GIT_ITEROVER; - - if (key) - *key = kh_key(map, i); - if (value) - *value = kh_val(map, i); - *iter = ++i; - - return 0; -} diff --git a/src/strmap.h b/src/strmap.h deleted file mode 100644 index 9f5e4cc8b..000000000 --- a/src/strmap.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_strmap_h__ -#define INCLUDE_strmap_h__ - -#include "common.h" - -/** A map with C strings as key. */ -typedef struct kh_str_s git_strmap; - -/** - * Allocate a new string map. - * - * @param out Pointer to the map that shall be allocated. - * @return 0 on success, an error code if allocation has failed. - */ -int git_strmap_new(git_strmap **out); - -/** - * Free memory associated with the map. - * - * Note that this function will _not_ free keys or values added - * to this map. - * - * @param map Pointer to the map that is to be free'd. May be - * `NULL`. - */ -void git_strmap_free(git_strmap *map); - -/** - * Clear all entries from the map. - * - * This function will remove all entries from the associated map. - * Memory associated with it will not be released, though. - * - * @param map Pointer to the map that shall be cleared. May be - * `NULL`. - */ -void git_strmap_clear(git_strmap *map); - -/** - * Return the number of elements in the map. - * - * @parameter map map containing the elements - * @return number of elements in the map - */ -size_t git_strmap_size(git_strmap *map); - -/** - * Return value associated with the given key. - * - * @param map map to search key in - * @param key key to search for - * @return value associated with the given key or NULL if the key was not found - */ -void *git_strmap_get(git_strmap *map, const char *key); - -/** - * Set the entry for key to value. - * - * If the map has no corresponding entry for the given key, a new - * entry will be created with the given value. If an entry exists - * already, its value will be updated to match the given value. - * - * @param map map to create new entry in - * @param key key to set - * @param value value to associate the key with; may be NULL - * @return zero if the key was successfully set, a negative error - * code otherwise - */ -int git_strmap_set(git_strmap *map, const char *key, void *value); - -/** - * Delete an entry from the map. - * - * Delete the given key and its value from the map. If no such - * key exists, this will do nothing. - * - * @param map map to delete key in - * @param key key to delete - * @return `0` if the key has been deleted, GIT_ENOTFOUND if no - * such key was found, a negative code in case of an - * error - */ -int git_strmap_delete(git_strmap *map, const char *key); - -/** - * Check whether a key exists in the given map. - * - * @param map map to query for the key - * @param key key to search for - * @return 0 if the key has not been found, 1 otherwise - */ -int git_strmap_exists(git_strmap *map, const char *key); - -/** - * Iterate over entries of the map. - * - * This functions allows to iterate over all key-value entries of - * the map. The current position is stored in the `iter` variable - * and should be initialized to `0` before the first call to this - * function. - * - * @param map map to iterate over - * @param value pointer to the variable where to store the current - * value. May be NULL. - * @param iter iterator storing the current position. Initialize - * with zero previous to the first call. - * @param key pointer to the variable where to store the current - * key. May be NULL. - * @return `0` if the next entry was correctly retrieved. - * GIT_ITEROVER if no entries are left. A negative error - * code otherwise. - */ -int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key); - -#define git_strmap_foreach(h, kvar, vvar, code) { size_t __i = 0; \ - while (git_strmap_iterate((void **) &(vvar), h, &__i, &(kvar)) == 0) { \ - code; \ - } } - -#define git_strmap_foreach_value(h, vvar, code) { size_t __i = 0; \ - while (git_strmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ - code; \ - } } - -#endif diff --git a/src/strnlen.h b/src/strnlen.h deleted file mode 100644 index eecfe3c02..000000000 --- a/src/strnlen.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_strlen_h__ -#define INCLUDE_strlen_h__ - -#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) || defined(__MidnightBSD__) ||\ - (defined(_MSC_VER) && _MSC_VER < 1500) -# define NO_STRNLEN -#endif - -#ifdef NO_STRNLEN -GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) { - const char *end = memchr(s, 0, maxlen); - return end ? (size_t)(end - s) : maxlen; -} -#else -# define p_strnlen strnlen -#endif - -#endif diff --git a/src/submodule.c b/src/submodule.c deleted file mode 100644 index 0f4f0726c..000000000 --- a/src/submodule.c +++ /dev/null @@ -1,2380 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "submodule.h" - -#include "buf.h" -#include "branch.h" -#include "vector.h" -#include "posix.h" -#include "config_backend.h" -#include "config.h" -#include "repository.h" -#include "tree.h" -#include "iterator.h" -#include "fs_path.h" -#include "str.h" -#include "index.h" -#include "worktree.h" -#include "clone.h" -#include "path.h" - -#include "git2/config.h" -#include "git2/sys/config.h" -#include "git2/types.h" -#include "git2/index.h" - -#define GIT_MODULES_FILE ".gitmodules" - -static git_configmap _sm_update_map[] = { - {GIT_CONFIGMAP_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT}, - {GIT_CONFIGMAP_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE}, - {GIT_CONFIGMAP_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}, - {GIT_CONFIGMAP_STRING, "none", GIT_SUBMODULE_UPDATE_NONE}, - {GIT_CONFIGMAP_FALSE, NULL, GIT_SUBMODULE_UPDATE_NONE}, - {GIT_CONFIGMAP_TRUE, NULL, GIT_SUBMODULE_UPDATE_CHECKOUT}, -}; - -static git_configmap _sm_ignore_map[] = { - {GIT_CONFIGMAP_STRING, "none", GIT_SUBMODULE_IGNORE_NONE}, - {GIT_CONFIGMAP_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED}, - {GIT_CONFIGMAP_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, - {GIT_CONFIGMAP_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, - {GIT_CONFIGMAP_FALSE, NULL, GIT_SUBMODULE_IGNORE_NONE}, - {GIT_CONFIGMAP_TRUE, NULL, GIT_SUBMODULE_IGNORE_ALL}, -}; - -static git_configmap _sm_recurse_map[] = { - {GIT_CONFIGMAP_STRING, "on-demand", GIT_SUBMODULE_RECURSE_ONDEMAND}, - {GIT_CONFIGMAP_FALSE, NULL, GIT_SUBMODULE_RECURSE_NO}, - {GIT_CONFIGMAP_TRUE, NULL, GIT_SUBMODULE_RECURSE_YES}, -}; - -enum { - CACHE_OK = 0, - CACHE_REFRESH = 1, - CACHE_FLUSH = 2 -}; -enum { - GITMODULES_EXISTING = 0, - GITMODULES_CREATE = 1 -}; - -static int submodule_alloc(git_submodule **out, git_repository *repo, const char *name); -static git_config_backend *open_gitmodules(git_repository *repo, int gitmod); -static int gitmodules_snapshot(git_config **snap, git_repository *repo); -static int get_url_base(git_str *url, git_repository *repo); -static int lookup_head_remote_key(git_str *remote_key, git_repository *repo); -static int lookup_default_remote(git_remote **remote, git_repository *repo); -static int submodule_load_each(const git_config_entry *entry, void *payload); -static int submodule_read_config(git_submodule *sm, git_config *cfg); -static int submodule_load_from_wd_lite(git_submodule *); -static void submodule_get_index_status(unsigned int *, git_submodule *); -static void submodule_get_wd_status(unsigned int *, git_submodule *, git_repository *, git_submodule_ignore_t); -static void submodule_update_from_index_entry(git_submodule *sm, const git_index_entry *ie); -static void submodule_update_from_head_data(git_submodule *sm, mode_t mode, const git_oid *id); - -static int submodule_cmp(const void *a, const void *b) -{ - return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name); -} - -static int submodule_config_key_trunc_puts(git_str *key, const char *suffix) -{ - ssize_t idx = git_str_rfind(key, '.'); - git_str_truncate(key, (size_t)(idx + 1)); - return git_str_puts(key, suffix); -} - -/* - * PUBLIC APIS - */ - -static void submodule_set_lookup_error(int error, const char *name) -{ - if (!error) - return; - - git_error_set(GIT_ERROR_SUBMODULE, (error == GIT_ENOTFOUND) ? - "no submodule named '%s'" : - "submodule '%s' has not been added yet", name); -} - -typedef struct { - const char *path; - char *name; -} fbp_data; - -static int find_by_path(const git_config_entry *entry, void *payload) -{ - fbp_data *data = payload; - - if (!strcmp(entry->value, data->path)) { - const char *fdot, *ldot; - fdot = strchr(entry->name, '.'); - ldot = strrchr(entry->name, '.'); - data->name = git__strndup(fdot + 1, ldot - fdot - 1); - GIT_ERROR_CHECK_ALLOC(data->name); - } - - return 0; -} - -/* - * Checks to see if the submodule shares its name with a file or directory that - * already exists on the index. If so, the submodule cannot be added. - */ -static int is_path_occupied(bool *occupied, git_repository *repo, const char *path) -{ - int error = 0; - git_index *index; - git_str dir = GIT_STR_INIT; - *occupied = false; - - if ((error = git_repository_index__weakptr(&index, repo)) < 0) - goto out; - - if ((error = git_index_find(NULL, index, path)) != GIT_ENOTFOUND) { - if (!error) { - git_error_set(GIT_ERROR_SUBMODULE, - "File '%s' already exists in the index", path); - *occupied = true; - } - goto out; - } - - if ((error = git_str_sets(&dir, path)) < 0) - goto out; - - if ((error = git_fs_path_to_dir(&dir)) < 0) - goto out; - - if ((error = git_index_find_prefix(NULL, index, dir.ptr)) != GIT_ENOTFOUND) { - if (!error) { - git_error_set(GIT_ERROR_SUBMODULE, - "Directory '%s' already exists in the index", path); - *occupied = true; - } - goto out; - } - - error = 0; - -out: - git_str_dispose(&dir); - return error; -} - -/** - * Release the name map returned by 'load_submodule_names'. - */ -static void free_submodule_names(git_strmap *names) -{ - const char *key; - char *value; - - if (names == NULL) - return; - - git_strmap_foreach(names, key, value, { - git__free((char *) key); - git__free(value); - }); - git_strmap_free(names); - - return; -} - -/** - * Map submodule paths to names. - * TODO: for some use-cases, this might need case-folding on a - * case-insensitive filesystem - */ -static int load_submodule_names(git_strmap **out, git_repository *repo, git_config *cfg) -{ - const char *key = "submodule\\..*\\.path"; - git_config_iterator *iter = NULL; - git_config_entry *entry; - git_str buf = GIT_STR_INIT; - git_strmap *names; - int isvalid, error; - - *out = NULL; - - if ((error = git_strmap_new(&names)) < 0) - goto out; - - if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0) - goto out; - - while ((error = git_config_next(&entry, iter)) == 0) { - const char *fdot, *ldot; - fdot = strchr(entry->name, '.'); - ldot = strrchr(entry->name, '.'); - - if (git_strmap_exists(names, entry->value)) { - git_error_set(GIT_ERROR_SUBMODULE, - "duplicated submodule path '%s'", entry->value); - error = -1; - goto out; - } - - git_str_clear(&buf); - git_str_put(&buf, fdot + 1, ldot - fdot - 1); - isvalid = git_submodule_name_is_valid(repo, buf.ptr, 0); - if (isvalid < 0) { - error = isvalid; - goto out; - } - if (!isvalid) - continue; - - if ((error = git_strmap_set(names, git__strdup(entry->value), git_str_detach(&buf))) < 0) { - git_error_set(GIT_ERROR_NOMEMORY, "error inserting submodule into hash table"); - error = -1; - goto out; - } - } - if (error == GIT_ITEROVER) - error = 0; - - *out = names; - names = NULL; - -out: - free_submodule_names(names); - git_str_dispose(&buf); - git_config_iterator_free(iter); - return error; -} - -int git_submodule_cache_init(git_strmap **out, git_repository *repo) -{ - int error = 0; - git_strmap *cache = NULL; - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - if ((error = git_strmap_new(&cache)) < 0) - return error; - if ((error = git_submodule__map(repo, cache)) < 0) { - git_submodule_cache_free(cache); - return error; - } - *out = cache; - return error; -} - -int git_submodule_cache_free(git_strmap *cache) -{ - git_submodule *sm = NULL; - if (cache == NULL) - return 0; - git_strmap_foreach_value(cache, sm, { - git_submodule_free(sm); - }); - git_strmap_free(cache); - return 0; -} - -int git_submodule_lookup( - git_submodule **out, /* NULL if user only wants to test existence */ - git_repository *repo, - const char *name) /* trailing slash is allowed */ -{ - return git_submodule__lookup_with_cache(out, repo, name, repo->submodule_cache); -} - -int git_submodule__lookup_with_cache( - git_submodule **out, /* NULL if user only wants to test existence */ - git_repository *repo, - const char *name, /* trailing slash is allowed */ - git_strmap *cache) -{ - int error; - unsigned int location; - git_submodule *sm; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if (repo->is_bare) { - git_error_set(GIT_ERROR_SUBMODULE, "cannot get submodules without a working tree"); - return -1; - } - - if (cache != NULL) { - if ((sm = git_strmap_get(cache, name)) != NULL) { - if (out) { - *out = sm; - GIT_REFCOUNT_INC(*out); - } - return 0; - } - } - - if ((error = submodule_alloc(&sm, repo, name)) < 0) - return error; - - if ((error = git_submodule_reload(sm, false)) < 0) { - git_submodule_free(sm); - return error; - } - - if ((error = git_submodule_location(&location, sm)) < 0) { - git_submodule_free(sm); - return error; - } - - /* If it's not configured or we're looking by path */ - if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) { - git_config_backend *mods; - const char *pattern = "submodule\\..*\\.path"; - git_str path = GIT_STR_INIT; - fbp_data data = { NULL, NULL }; - - git_str_puts(&path, name); - while (path.ptr[path.size-1] == '/') { - path.ptr[--path.size] = '\0'; - } - data.path = path.ptr; - - mods = open_gitmodules(repo, GITMODULES_EXISTING); - - if (mods) - error = git_config_backend_foreach_match(mods, pattern, find_by_path, &data); - - git_config_backend_free(mods); - - if (error < 0) { - git_submodule_free(sm); - git_str_dispose(&path); - return error; - } - - if (data.name) { - git__free(sm->name); - sm->name = data.name; - sm->path = git_str_detach(&path); - - /* Try to load again with the right name */ - if ((error = git_submodule_reload(sm, false)) < 0) { - git_submodule_free(sm); - return error; - } - } - - git_str_dispose(&path); - } - - if ((error = git_submodule_location(&location, sm)) < 0) { - git_submodule_free(sm); - return error; - } - - /* If we still haven't found it, do the WD check */ - if (location == 0 || location == GIT_SUBMODULE_STATUS_IN_WD) { - git_submodule_free(sm); - error = GIT_ENOTFOUND; - - /* If it's not configured, we still check if there's a repo at the path */ - if (git_repository_workdir(repo)) { - git_str path = GIT_STR_INIT; - if (git_str_join3(&path, '/', - git_repository_workdir(repo), - name, DOT_GIT) < 0 || - git_path_validate_str_length(NULL, &path) < 0) - return -1; - - if (git_fs_path_exists(path.ptr)) - error = GIT_EEXISTS; - - git_str_dispose(&path); - } - - submodule_set_lookup_error(error, name); - return error; - } - - if (out) - *out = sm; - else - git_submodule_free(sm); - - return 0; -} - -int git_submodule_name_is_valid(git_repository *repo, const char *name, int flags) -{ - git_str buf = GIT_STR_INIT; - int error, isvalid; - - if (flags == 0) - flags = GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS; - - /* Avoid allocating a new string if we can avoid it */ - if (strchr(name, '\\') != NULL) { - if ((error = git_fs_path_normalize_slashes(&buf, name)) < 0) - return error; - } else { - git_str_attach_notowned(&buf, name, strlen(name)); - } - - isvalid = git_path_is_valid(repo, buf.ptr, 0, flags); - git_str_dispose(&buf); - - return isvalid; -} - -static void submodule_free_dup(void *sm) -{ - git_submodule_free(sm); -} - -static int submodule_get_or_create(git_submodule **out, git_repository *repo, git_strmap *map, const char *name) -{ - git_submodule *sm = NULL; - int error; - - if ((sm = git_strmap_get(map, name)) != NULL) - goto done; - - /* if the submodule doesn't exist yet in the map, create it */ - if ((error = submodule_alloc(&sm, repo, name)) < 0) - return error; - - if ((error = git_strmap_set(map, sm->name, sm)) < 0) { - git_submodule_free(sm); - return error; - } - -done: - GIT_REFCOUNT_INC(sm); - *out = sm; - return 0; -} - -static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cfg) -{ - int error; - git_iterator *i = NULL; - const git_index_entry *entry; - git_strmap *names; - - if ((error = load_submodule_names(&names, git_index_owner(idx), cfg))) - goto done; - - if ((error = git_iterator_for_index(&i, git_index_owner(idx), idx, NULL)) < 0) - goto done; - - while (!(error = git_iterator_advance(&entry, i))) { - git_submodule *sm; - - if ((sm = git_strmap_get(map, entry->path)) != NULL) { - if (S_ISGITLINK(entry->mode)) - submodule_update_from_index_entry(sm, entry); - else - sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; - } else if (S_ISGITLINK(entry->mode)) { - const char *name; - - if ((name = git_strmap_get(names, entry->path)) == NULL) - name = entry->path; - - if (!submodule_get_or_create(&sm, git_index_owner(idx), map, name)) { - submodule_update_from_index_entry(sm, entry); - git_submodule_free(sm); - } - } - } - - if (error == GIT_ITEROVER) - error = 0; - -done: - git_iterator_free(i); - free_submodule_names(names); - - return error; -} - -static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg) -{ - int error; - git_iterator *i = NULL; - const git_index_entry *entry; - git_strmap *names; - - if ((error = load_submodule_names(&names, git_tree_owner(head), cfg))) - goto done; - - if ((error = git_iterator_for_tree(&i, head, NULL)) < 0) - goto done; - - while (!(error = git_iterator_advance(&entry, i))) { - git_submodule *sm; - - if ((sm = git_strmap_get(map, entry->path)) != NULL) { - if (S_ISGITLINK(entry->mode)) - submodule_update_from_head_data(sm, entry->mode, &entry->id); - else - sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; - } else if (S_ISGITLINK(entry->mode)) { - const char *name; - - if ((name = git_strmap_get(names, entry->path)) == NULL) - name = entry->path; - - if (!submodule_get_or_create(&sm, git_tree_owner(head), map, name)) { - submodule_update_from_head_data( - sm, entry->mode, &entry->id); - git_submodule_free(sm); - } - } - } - - if (error == GIT_ITEROVER) - error = 0; - -done: - git_iterator_free(i); - free_submodule_names(names); - - return error; -} - -/* If have_sm is true, sm is populated, otherwise map an repo are. */ -typedef struct { - git_config *mods; - git_strmap *map; - git_repository *repo; -} lfc_data; - -int git_submodule__map(git_repository *repo, git_strmap *map) -{ - int error = 0; - git_index *idx = NULL; - git_tree *head = NULL; - git_str path = GIT_STR_INIT; - git_submodule *sm; - git_config *mods = NULL; - bool has_workdir; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(map); - - /* get sources that we will need to check */ - if (git_repository_index(&idx, repo) < 0) - git_error_clear(); - if (git_repository_head_tree(&head, repo) < 0) - git_error_clear(); - - has_workdir = git_repository_workdir(repo) != NULL; - - if (has_workdir && - (error = git_repository_workdir_path(&path, repo, GIT_MODULES_FILE)) < 0) - goto cleanup; - - /* add submodule information from .gitmodules */ - if (has_workdir) { - lfc_data data = { 0 }; - data.map = map; - data.repo = repo; - - if ((error = gitmodules_snapshot(&mods, repo)) < 0) { - if (error == GIT_ENOTFOUND) - error = 0; - goto cleanup; - } - - data.mods = mods; - if ((error = git_config_foreach( - mods, submodule_load_each, &data)) < 0) - goto cleanup; - } - /* add back submodule information from index */ - if (mods && idx) { - if ((error = submodules_from_index(map, idx, mods)) < 0) - goto cleanup; - } - /* add submodule information from HEAD */ - if (mods && head) { - if ((error = submodules_from_head(map, head, mods)) < 0) - goto cleanup; - } - /* shallow scan submodules in work tree as needed */ - if (has_workdir) { - git_strmap_foreach_value(map, sm, { - submodule_load_from_wd_lite(sm); - }); - } - -cleanup: - git_config_free(mods); - /* TODO: if we got an error, mark submodule config as invalid? */ - git_index_free(idx); - git_tree_free(head); - git_str_dispose(&path); - return error; -} - -int git_submodule_foreach( - git_repository *repo, - git_submodule_cb callback, - void *payload) -{ - git_vector snapshot = GIT_VECTOR_INIT; - git_strmap *submodules; - git_submodule *sm; - int error; - size_t i; - - if (repo->is_bare) { - git_error_set(GIT_ERROR_SUBMODULE, "cannot get submodules without a working tree"); - return -1; - } - - if ((error = git_strmap_new(&submodules)) < 0) - return error; - - if ((error = git_submodule__map(repo, submodules)) < 0) - goto done; - - if (!(error = git_vector_init( - &snapshot, git_strmap_size(submodules), submodule_cmp))) { - - git_strmap_foreach_value(submodules, sm, { - if ((error = git_vector_insert(&snapshot, sm)) < 0) - break; - GIT_REFCOUNT_INC(sm); - }); - } - - if (error < 0) - goto done; - - git_vector_uniq(&snapshot, submodule_free_dup); - - git_vector_foreach(&snapshot, i, sm) { - if ((error = callback(sm, sm->name, payload)) != 0) { - git_error_set_after_callback(error); - break; - } - } - -done: - git_vector_foreach(&snapshot, i, sm) - git_submodule_free(sm); - git_vector_free(&snapshot); - - git_strmap_foreach_value(submodules, sm, { - git_submodule_free(sm); - }); - git_strmap_free(submodules); - - return error; -} - -static int submodule_repo_init( - git_repository **out, - git_repository *parent_repo, - const char *path, - const char *url, - bool use_gitlink) -{ - int error = 0; - git_str workdir = GIT_STR_INIT, repodir = GIT_STR_INIT; - git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT; - git_repository *subrepo = NULL; - - error = git_repository_workdir_path(&workdir, parent_repo, path); - if (error < 0) - goto cleanup; - - initopt.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_NO_REINIT; - initopt.origin_url = url; - - /* init submodule repository and add origin remote as needed */ - - /* New style: sub-repo goes in /modules// with a - * gitlink in the sub-repo workdir directory to that repository - * - * Old style: sub-repo goes directly into repo//.git/ - */ - if (use_gitlink) { - error = git_repository__item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES); - if (error < 0) - goto cleanup; - error = git_str_joinpath(&repodir, repodir.ptr, path); - if (error < 0) - goto cleanup; - - initopt.workdir_path = workdir.ptr; - initopt.flags |= - GIT_REPOSITORY_INIT_NO_DOTGIT_DIR | - GIT_REPOSITORY_INIT_RELATIVE_GITLINK; - - error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); - } else - error = git_repository_init_ext(&subrepo, workdir.ptr, &initopt); - -cleanup: - git_str_dispose(&workdir); - git_str_dispose(&repodir); - - *out = subrepo; - - return error; -} - -static int git_submodule__resolve_url( - git_str *out, - git_repository *repo, - const char *url) -{ - int error = 0; - git_str normalized = GIT_STR_INIT; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(url); - - /* We do this in all platforms in case someone on Windows created the .gitmodules */ - if (strchr(url, '\\')) { - if ((error = git_fs_path_normalize_slashes(&normalized, url)) < 0) - return error; - - url = normalized.ptr; - } - - - if (git_fs_path_is_relative(url)) { - if (!(error = get_url_base(out, repo))) - error = git_fs_path_apply_relative(out, url); - } else if (strchr(url, ':') != NULL || url[0] == '/') { - error = git_str_sets(out, url); - } else { - git_error_set(GIT_ERROR_SUBMODULE, "invalid format for submodule URL"); - error = -1; - } - - git_str_dispose(&normalized); - return error; -} - -int git_submodule_resolve_url( - git_buf *out, - git_repository *repo, - const char *url) -{ - GIT_BUF_WRAP_PRIVATE(out, git_submodule__resolve_url, repo, url); -} - -int git_submodule_add_setup( - git_submodule **out, - git_repository *repo, - const char *url, - const char *path, - int use_gitlink) -{ - int error = 0; - git_config_backend *mods = NULL; - git_submodule *sm = NULL; - git_str name = GIT_STR_INIT, real_url = GIT_STR_INIT; - git_repository *subrepo = NULL; - bool path_occupied; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(url); - GIT_ASSERT_ARG(path); - - /* see if there is already an entry for this submodule */ - - if (git_submodule_lookup(NULL, repo, path) < 0) - git_error_clear(); - else { - git_error_set(GIT_ERROR_SUBMODULE, - "attempt to add submodule '%s' that already exists", path); - return GIT_EEXISTS; - } - - /* validate and normalize path */ - - if (git__prefixcmp(path, git_repository_workdir(repo)) == 0) - path += strlen(git_repository_workdir(repo)); - - if (git_fs_path_root(path) >= 0) { - git_error_set(GIT_ERROR_SUBMODULE, "submodule path must be a relative path"); - error = -1; - goto cleanup; - } - - if ((error = is_path_occupied(&path_occupied, repo, path)) < 0) - goto cleanup; - - if (path_occupied) { - error = GIT_EEXISTS; - goto cleanup; - } - - /* update .gitmodules */ - - if (!(mods = open_gitmodules(repo, GITMODULES_CREATE))) { - git_error_set(GIT_ERROR_SUBMODULE, - "adding submodules to a bare repository is not supported"); - return -1; - } - - if ((error = git_str_printf(&name, "submodule.%s.path", path)) < 0 || - (error = git_config_backend_set_string(mods, name.ptr, path)) < 0) - goto cleanup; - - if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 || - (error = git_config_backend_set_string(mods, name.ptr, url)) < 0) - goto cleanup; - - git_str_clear(&name); - - /* init submodule repository and add origin remote as needed */ - - error = git_repository_workdir_path(&name, repo, path); - if (error < 0) - goto cleanup; - - /* if the repo does not already exist, then init a new repo and add it. - * Otherwise, just add the existing repo. - */ - if (!(git_fs_path_exists(name.ptr) && - git_fs_path_contains(&name, DOT_GIT))) { - - /* resolve the actual URL to use */ - if ((error = git_submodule__resolve_url(&real_url, repo, url)) < 0) - goto cleanup; - - if ((error = submodule_repo_init(&subrepo, repo, path, real_url.ptr, use_gitlink)) < 0) - goto cleanup; - } - - if ((error = git_submodule_lookup(&sm, repo, path)) < 0) - goto cleanup; - - error = git_submodule_init(sm, false); - -cleanup: - if (error && sm) { - git_submodule_free(sm); - sm = NULL; - } - if (out != NULL) - *out = sm; - - git_config_backend_free(mods); - git_repository_free(subrepo); - git_str_dispose(&real_url); - git_str_dispose(&name); - - return error; -} - -int git_submodule_repo_init( - git_repository **out, - const git_submodule *sm, - int use_gitlink) -{ - int error; - git_repository *sub_repo = NULL; - const char *configured_url; - git_config *cfg = NULL; - git_str buf = GIT_STR_INIT; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(sm); - - /* get the configured remote url of the submodule */ - if ((error = git_str_printf(&buf, "submodule.%s.url", sm->name)) < 0 || - (error = git_repository_config_snapshot(&cfg, sm->repo)) < 0 || - (error = git_config_get_string(&configured_url, cfg, buf.ptr)) < 0 || - (error = submodule_repo_init(&sub_repo, sm->repo, sm->path, configured_url, use_gitlink)) < 0) - goto done; - - *out = sub_repo; - -done: - git_config_free(cfg); - git_str_dispose(&buf); - return error; -} - -static int clone_return_origin(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload) -{ - GIT_UNUSED(url); - GIT_UNUSED(payload); - return git_remote_lookup(out, repo, name); -} - -static int clone_return_repo(git_repository **out, const char *path, int bare, void *payload) -{ - git_submodule *sm = payload; - - GIT_UNUSED(path); - GIT_UNUSED(bare); - return git_submodule_open(out, sm); -} - -int git_submodule_clone(git_repository **out, git_submodule *submodule, const git_submodule_update_options *given_opts) -{ - int error; - git_repository *clone; - git_str rel_path = GIT_STR_INIT; - git_submodule_update_options sub_opts = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - - GIT_ASSERT_ARG(submodule); - - if (given_opts) - memcpy(&sub_opts, given_opts, sizeof(sub_opts)); - - GIT_ERROR_CHECK_VERSION(&sub_opts, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, "git_submodule_update_options"); - - memcpy(&opts.checkout_opts, &sub_opts.checkout_opts, sizeof(sub_opts.checkout_opts)); - memcpy(&opts.fetch_opts, &sub_opts.fetch_opts, sizeof(sub_opts.fetch_opts)); - opts.repository_cb = clone_return_repo; - opts.repository_cb_payload = submodule; - opts.remote_cb = clone_return_origin; - opts.remote_cb_payload = submodule; - - error = git_repository_workdir_path(&rel_path, git_submodule_owner(submodule), git_submodule_path(submodule)); - if (error < 0) - goto cleanup; - - error = git_clone__submodule(&clone, git_submodule_url(submodule), git_str_cstr(&rel_path), &opts); - if (error < 0) - goto cleanup; - - if (!out) - git_repository_free(clone); - else - *out = clone; - -cleanup: - git_str_dispose(&rel_path); - - return error; -} - -int git_submodule_add_finalize(git_submodule *sm) -{ - int error; - git_index *index; - - GIT_ASSERT_ARG(sm); - - if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 || - (error = git_index_add_bypath(index, GIT_MODULES_FILE)) < 0) - return error; - - return git_submodule_add_to_index(sm, true); -} - -int git_submodule_add_to_index(git_submodule *sm, int write_index) -{ - int error; - git_repository *sm_repo = NULL; - git_index *index; - git_str path = GIT_STR_INIT; - git_commit *head; - git_index_entry entry; - struct stat st; - - GIT_ASSERT_ARG(sm); - - /* force reload of wd OID by git_submodule_open */ - sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID; - - if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 || - (error = git_repository_workdir_path(&path, sm->repo, sm->path)) < 0 || - (error = git_submodule_open(&sm_repo, sm)) < 0) - goto cleanup; - - /* read stat information for submodule working directory */ - if (p_stat(path.ptr, &st) < 0) { - git_error_set(GIT_ERROR_SUBMODULE, - "cannot add submodule without working directory"); - error = -1; - goto cleanup; - } - - memset(&entry, 0, sizeof(entry)); - entry.path = sm->path; - git_index_entry__init_from_stat( - &entry, &st, !(git_index_caps(index) & GIT_INDEX_CAPABILITY_NO_FILEMODE)); - - /* calling git_submodule_open will have set sm->wd_oid if possible */ - if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) { - git_error_set(GIT_ERROR_SUBMODULE, - "cannot add submodule without HEAD to index"); - error = -1; - goto cleanup; - } - git_oid_cpy(&entry.id, &sm->wd_oid); - - if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0) - goto cleanup; - - entry.ctime.seconds = (int32_t)git_commit_time(head); - entry.ctime.nanoseconds = 0; - entry.mtime.seconds = (int32_t)git_commit_time(head); - entry.mtime.nanoseconds = 0; - - git_commit_free(head); - - /* add it */ - error = git_index_add(index, &entry); - - /* write it, if requested */ - if (!error && write_index) { - error = git_index_write(index); - - if (!error) - git_oid_cpy(&sm->index_oid, &sm->wd_oid); - } - -cleanup: - git_repository_free(sm_repo); - git_str_dispose(&path); - return error; -} - -static const char *submodule_update_to_str(git_submodule_update_t update) -{ - int i; - for (i = 0; i < (int)ARRAY_SIZE(_sm_update_map); ++i) - if (_sm_update_map[i].map_value == (int)update) - return _sm_update_map[i].str_match; - return NULL; -} - -git_repository *git_submodule_owner(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - return submodule->repo; -} - -const char *git_submodule_name(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - return submodule->name; -} - -const char *git_submodule_path(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - return submodule->path; -} - -const char *git_submodule_url(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - return submodule->url; -} - -static int write_var(git_repository *repo, const char *name, const char *var, const char *val) -{ - git_str key = GIT_STR_INIT; - git_config_backend *mods; - int error; - - mods = open_gitmodules(repo, GITMODULES_CREATE); - if (!mods) - return -1; - - if ((error = git_str_printf(&key, "submodule.%s.%s", name, var)) < 0) - goto cleanup; - - if (val) - error = git_config_backend_set_string(mods, key.ptr, val); - else - error = git_config_backend_delete(mods, key.ptr); - - git_str_dispose(&key); - -cleanup: - git_config_backend_free(mods); - return error; -} - -static int write_mapped_var(git_repository *repo, const char *name, git_configmap *maps, size_t nmaps, const char *var, int ival) -{ - git_configmap_t type; - const char *val; - - if (git_config_lookup_map_enum(&type, &val, maps, nmaps, ival) < 0) { - git_error_set(GIT_ERROR_SUBMODULE, "invalid value for %s", var); - return -1; - } - - if (type == GIT_CONFIGMAP_TRUE) - val = "true"; - - return write_var(repo, name, var, val); -} - -const char *git_submodule_branch(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - return submodule->branch; -} - -int git_submodule_set_branch(git_repository *repo, const char *name, const char *branch) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - return write_var(repo, name, "branch", branch); -} - -int git_submodule_set_url(git_repository *repo, const char *name, const char *url) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(url); - - return write_var(repo, name, "url", url); -} - -const git_oid *git_submodule_index_id(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - - if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) - return &submodule->index_oid; - else - return NULL; -} - -const git_oid *git_submodule_head_id(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - - if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) - return &submodule->head_oid; - else - return NULL; -} - -const git_oid *git_submodule_wd_id(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, NULL); - - /* load unless we think we have a valid oid */ - if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) { - git_repository *subrepo; - - /* calling submodule open grabs the HEAD OID if possible */ - if (!git_submodule_open_bare(&subrepo, submodule)) - git_repository_free(subrepo); - else - git_error_clear(); - } - - if (submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) - return &submodule->wd_oid; - else - return NULL; -} - -git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_IGNORE_UNSPECIFIED); - - return (submodule->ignore < GIT_SUBMODULE_IGNORE_NONE) ? - GIT_SUBMODULE_IGNORE_NONE : submodule->ignore; -} - -int git_submodule_set_ignore(git_repository *repo, const char *name, git_submodule_ignore_t ignore) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - return write_mapped_var(repo, name, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), "ignore", ignore); -} - -git_submodule_update_t git_submodule_update_strategy(git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_UPDATE_NONE); - - return (submodule->update < GIT_SUBMODULE_UPDATE_CHECKOUT) ? - GIT_SUBMODULE_UPDATE_CHECKOUT : submodule->update; -} - -int git_submodule_set_update(git_repository *repo, const char *name, git_submodule_update_t update) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - return write_mapped_var(repo, name, _sm_update_map, ARRAY_SIZE(_sm_update_map), "update", update); -} - -git_submodule_recurse_t git_submodule_fetch_recurse_submodules( - git_submodule *submodule) -{ - GIT_ASSERT_ARG_WITH_RETVAL(submodule, GIT_SUBMODULE_RECURSE_NO); - return submodule->fetch_recurse; -} - -int git_submodule_set_fetch_recurse_submodules(git_repository *repo, const char *name, git_submodule_recurse_t recurse) -{ - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - return write_mapped_var(repo, name, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), "fetchRecurseSubmodules", recurse); -} - -static int submodule_repo_create( - git_repository **out, - git_repository *parent_repo, - const char *path) -{ - int error = 0; - git_str workdir = GIT_STR_INIT, repodir = GIT_STR_INIT; - git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT; - git_repository *subrepo = NULL; - - initopt.flags = - GIT_REPOSITORY_INIT_MKPATH | - GIT_REPOSITORY_INIT_NO_REINIT | - GIT_REPOSITORY_INIT_NO_DOTGIT_DIR | - GIT_REPOSITORY_INIT_RELATIVE_GITLINK; - - /* Workdir: path to sub-repo working directory */ - error = git_repository_workdir_path(&workdir, parent_repo, path); - if (error < 0) - goto cleanup; - - initopt.workdir_path = workdir.ptr; - - /** - * Repodir: path to the sub-repo. sub-repo goes in: - * /modules// with a gitlink in the - * sub-repo workdir directory to that repository. - */ - error = git_repository__item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES); - if (error < 0) - goto cleanup; - error = git_str_joinpath(&repodir, repodir.ptr, path); - if (error < 0) - goto cleanup; - - error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); - -cleanup: - git_str_dispose(&workdir); - git_str_dispose(&repodir); - - *out = subrepo; - - return error; -} - -/** - * Callback to override sub-repository creation when - * cloning a sub-repository. - */ -static int git_submodule_update_repo_init_cb( - git_repository **out, - const char *path, - int bare, - void *payload) -{ - git_submodule *sm; - - GIT_UNUSED(bare); - - sm = payload; - - return submodule_repo_create(out, sm->repo, path); -} - -int git_submodule_update_options_init(git_submodule_update_options *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_submodule_update_options, GIT_SUBMODULE_UPDATE_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_submodule_update_init_options(git_submodule_update_options *opts, unsigned int version) -{ - return git_submodule_update_options_init(opts, version); -} -#endif - -int git_submodule_update(git_submodule *sm, int init, git_submodule_update_options *_update_options) -{ - int error; - unsigned int submodule_status; - git_config *config = NULL; - const char *submodule_url; - git_repository *sub_repo = NULL; - git_remote *remote = NULL; - git_object *target_commit = NULL; - git_str buf = GIT_STR_INIT; - git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; - git_clone_options clone_options = GIT_CLONE_OPTIONS_INIT; - - GIT_ASSERT_ARG(sm); - - if (_update_options) - memcpy(&update_options, _update_options, sizeof(git_submodule_update_options)); - - GIT_ERROR_CHECK_VERSION(&update_options, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, "git_submodule_update_options"); - - /* Copy over the remote callbacks */ - memcpy(&clone_options.fetch_opts, &update_options.fetch_opts, sizeof(git_fetch_options)); - - /* Get the status of the submodule to determine if it is already initialized */ - if ((error = git_submodule_status(&submodule_status, sm->repo, sm->name, GIT_SUBMODULE_IGNORE_UNSPECIFIED)) < 0) - goto done; - - /* - * If submodule work dir is not already initialized, check to see - * what we need to do (initialize, clone, return error...) - */ - if (submodule_status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) { - /* - * Work dir is not initialized, check to see if the submodule - * info has been copied into .git/config - */ - if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 || - (error = git_str_printf(&buf, "submodule.%s.url", git_submodule_name(sm))) < 0) - goto done; - - if ((error = git_config_get_string(&submodule_url, config, git_str_cstr(&buf))) < 0) { - /* - * If the error is not "not found" or if it is "not found" and we are not - * initializing the submodule, then return error. - */ - if (error != GIT_ENOTFOUND) - goto done; - - if (!init) { - git_error_set(GIT_ERROR_SUBMODULE, "submodule is not initialized"); - error = GIT_ERROR; - goto done; - } - - /* The submodule has not been initialized yet - initialize it now.*/ - if ((error = git_submodule_init(sm, 0)) < 0) - goto done; - - git_config_free(config); - config = NULL; - - if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 || - (error = git_config_get_string(&submodule_url, config, git_str_cstr(&buf))) < 0) - goto done; - } - - /** submodule is initialized - now clone it **/ - /* override repo creation */ - clone_options.repository_cb = git_submodule_update_repo_init_cb; - clone_options.repository_cb_payload = sm; - - /* - * Do not perform checkout as part of clone, instead we - * will checkout the specific commit manually. - */ - clone_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE; - - if ((error = git_clone(&sub_repo, submodule_url, sm->path, &clone_options)) < 0 || - (error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0 || - (error = git_checkout_head(sub_repo, &update_options.checkout_opts)) != 0) - goto done; - } else { - const git_oid *oid; - - /** - * Work dir is initialized - look up the commit in the parent repository's index, - * update the workdir contents of the subrepository, and set the subrepository's - * head to the new commit. - */ - if ((error = git_submodule_open(&sub_repo, sm)) < 0) - goto done; - - if ((oid = git_submodule_index_id(sm)) == NULL) { - git_error_set(GIT_ERROR_SUBMODULE, "could not get ID of submodule in index"); - error = -1; - goto done; - } - - /* Look up the target commit in the submodule. */ - if ((error = git_object_lookup(&target_commit, sub_repo, oid, GIT_OBJECT_COMMIT)) < 0) { - /* If it isn't found then fetch and try again. */ - if (error != GIT_ENOTFOUND || !update_options.allow_fetch || - (error = lookup_default_remote(&remote, sub_repo)) < 0 || - (error = git_remote_fetch(remote, NULL, &update_options.fetch_opts, NULL)) < 0 || - (error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJECT_COMMIT)) < 0) - goto done; - } - - if ((error = git_checkout_tree(sub_repo, target_commit, &update_options.checkout_opts)) != 0 || - (error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0) - goto done; - - /* Invalidate the wd flags as the workdir has been updated. */ - sm->flags = sm->flags & - ~(GIT_SUBMODULE_STATUS_IN_WD | - GIT_SUBMODULE_STATUS__WD_OID_VALID | - GIT_SUBMODULE_STATUS__WD_SCANNED); - } - -done: - git_str_dispose(&buf); - git_config_free(config); - git_object_free(target_commit); - git_remote_free(remote); - git_repository_free(sub_repo); - - return error; -} - -int git_submodule_init(git_submodule *sm, int overwrite) -{ - int error; - const char *val; - git_str key = GIT_STR_INIT, effective_submodule_url = GIT_STR_INIT; - git_config *cfg = NULL; - - if (!sm->url) { - git_error_set(GIT_ERROR_SUBMODULE, - "no URL configured for submodule '%s'", sm->name); - return -1; - } - - if ((error = git_repository_config(&cfg, sm->repo)) < 0) - return error; - - /* write "submodule.NAME.url" */ - - if ((error = git_submodule__resolve_url(&effective_submodule_url, sm->repo, sm->url)) < 0 || - (error = git_str_printf(&key, "submodule.%s.url", sm->name)) < 0 || - (error = git_config__update_entry( - cfg, key.ptr, effective_submodule_url.ptr, overwrite != 0, false)) < 0) - goto cleanup; - - /* write "submodule.NAME.update" if not default */ - - val = (sm->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ? - NULL : submodule_update_to_str(sm->update); - - if ((error = git_str_printf(&key, "submodule.%s.update", sm->name)) < 0 || - (error = git_config__update_entry( - cfg, key.ptr, val, overwrite != 0, false)) < 0) - goto cleanup; - - /* success */ - -cleanup: - git_config_free(cfg); - git_str_dispose(&key); - git_str_dispose(&effective_submodule_url); - - return error; -} - -int git_submodule_sync(git_submodule *sm) -{ - git_str key = GIT_STR_INIT, url = GIT_STR_INIT, remote_name = GIT_STR_INIT; - git_repository *smrepo = NULL; - git_config *cfg = NULL; - int error = 0; - - if (!sm->url) { - git_error_set(GIT_ERROR_SUBMODULE, "no URL configured for submodule '%s'", sm->name); - return -1; - } - - /* copy URL over to config only if it already exists */ - if ((error = git_repository_config__weakptr(&cfg, sm->repo)) < 0 || - (error = git_str_printf(&key, "submodule.%s.url", sm->name)) < 0 || - (error = git_submodule__resolve_url(&url, sm->repo, sm->url)) < 0 || - (error = git_config__update_entry(cfg, key.ptr, url.ptr, true, true)) < 0) - goto out; - - if (!(sm->flags & GIT_SUBMODULE_STATUS_IN_WD)) - goto out; - - /* if submodule exists in the working directory, update remote url */ - if ((error = git_submodule_open(&smrepo, sm)) < 0 || - (error = git_repository_config__weakptr(&cfg, smrepo)) < 0) - goto out; - - if (lookup_head_remote_key(&remote_name, smrepo) == 0) { - if ((error = git_str_join3(&key, '.', "remote", remote_name.ptr, "url")) < 0) - goto out; - } else if ((error = git_str_sets(&key, "remote.origin.url")) < 0) { - goto out; - } - - if ((error = git_config__update_entry(cfg, key.ptr, url.ptr, true, false)) < 0) - goto out; - -out: - git_repository_free(smrepo); - git_str_dispose(&remote_name); - git_str_dispose(&key); - git_str_dispose(&url); - return error; -} - -static int git_submodule__open( - git_repository **subrepo, git_submodule *sm, bool bare) -{ - int error; - git_str path = GIT_STR_INIT; - unsigned int flags = GIT_REPOSITORY_OPEN_NO_SEARCH; - const char *wd; - - GIT_ASSERT_ARG(sm); - GIT_ASSERT_ARG(subrepo); - - if (git_repository__ensure_not_bare( - sm->repo, "open submodule repository") < 0) - return GIT_EBAREREPO; - - wd = git_repository_workdir(sm->repo); - - if (git_str_join3(&path, '/', wd, sm->path, DOT_GIT) < 0) - return -1; - - sm->flags = sm->flags & - ~(GIT_SUBMODULE_STATUS_IN_WD | - GIT_SUBMODULE_STATUS__WD_OID_VALID | - GIT_SUBMODULE_STATUS__WD_SCANNED); - - if (bare) - flags |= GIT_REPOSITORY_OPEN_BARE; - - error = git_repository_open_ext(subrepo, path.ptr, flags, wd); - - /* if we opened the submodule successfully, grab HEAD OID, etc. */ - if (!error) { - sm->flags |= GIT_SUBMODULE_STATUS_IN_WD | - GIT_SUBMODULE_STATUS__WD_SCANNED; - - if (!git_reference_name_to_id(&sm->wd_oid, *subrepo, GIT_HEAD_FILE)) - sm->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; - else - git_error_clear(); - } else if (git_fs_path_exists(path.ptr)) { - sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED | - GIT_SUBMODULE_STATUS_IN_WD; - } else { - git_str_rtruncate_at_char(&path, '/'); /* remove "/.git" */ - - if (git_fs_path_isdir(path.ptr)) - sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; - } - - git_str_dispose(&path); - - return error; -} - -int git_submodule_open_bare(git_repository **subrepo, git_submodule *sm) -{ - return git_submodule__open(subrepo, sm, true); -} - -int git_submodule_open(git_repository **subrepo, git_submodule *sm) -{ - return git_submodule__open(subrepo, sm, false); -} - -static void submodule_update_from_index_entry( - git_submodule *sm, const git_index_entry *ie) -{ - bool already_found = (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) != 0; - - if (!S_ISGITLINK(ie->mode)) { - if (!already_found) - sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; - } else { - if (already_found) - sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES; - else - git_oid_cpy(&sm->index_oid, &ie->id); - - sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS__INDEX_OID_VALID; - } -} - -static int submodule_update_index(git_submodule *sm) -{ - git_index *index; - const git_index_entry *ie; - - if (git_repository_index__weakptr(&index, sm->repo) < 0) - return -1; - - sm->flags = sm->flags & - ~(GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS__INDEX_OID_VALID); - - if (!(ie = git_index_get_bypath(index, sm->path, 0))) - return 0; - - submodule_update_from_index_entry(sm, ie); - - return 0; -} - -static void submodule_update_from_head_data( - git_submodule *sm, mode_t mode, const git_oid *id) -{ - if (!S_ISGITLINK(mode)) - sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; - else { - git_oid_cpy(&sm->head_oid, id); - - sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS__HEAD_OID_VALID; - } -} - -static int submodule_update_head(git_submodule *submodule) -{ - git_tree *head = NULL; - git_tree_entry *te = NULL; - - submodule->flags = submodule->flags & - ~(GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS__HEAD_OID_VALID); - - /* if we can't look up file in current head, then done */ - if (git_repository_head_tree(&head, submodule->repo) < 0 || - git_tree_entry_bypath(&te, head, submodule->path) < 0) - git_error_clear(); - else - submodule_update_from_head_data(submodule, te->attr, git_tree_entry_id(te)); - - git_tree_entry_free(te); - git_tree_free(head); - return 0; -} - -int git_submodule_reload(git_submodule *sm, int force) -{ - git_config *mods = NULL; - int error; - - GIT_UNUSED(force); - - GIT_ASSERT_ARG(sm); - - if ((error = git_submodule_name_is_valid(sm->repo, sm->name, 0)) <= 0) - /* This should come with a warning, but we've no API for that */ - goto out; - - if (git_repository_is_bare(sm->repo)) - goto out; - - /* refresh config data */ - if ((error = gitmodules_snapshot(&mods, sm->repo)) < 0 && error != GIT_ENOTFOUND) - goto out; - - if (mods != NULL && (error = submodule_read_config(sm, mods)) < 0) - goto out; - - /* refresh wd data */ - sm->flags &= - ~(GIT_SUBMODULE_STATUS_IN_WD | - GIT_SUBMODULE_STATUS__WD_OID_VALID | - GIT_SUBMODULE_STATUS__WD_FLAGS); - - if ((error = submodule_load_from_wd_lite(sm)) < 0 || - (error = submodule_update_index(sm)) < 0 || - (error = submodule_update_head(sm)) < 0) - goto out; - -out: - git_config_free(mods); - return error; -} - -static void submodule_copy_oid_maybe( - git_oid *tgt, const git_oid *src, bool valid) -{ - if (tgt) { - if (valid) - memcpy(tgt, src, sizeof(*tgt)); - else - memset(tgt, 0, sizeof(*tgt)); - } -} - -int git_submodule__status( - unsigned int *out_status, - git_oid *out_head_id, - git_oid *out_index_id, - git_oid *out_wd_id, - git_submodule *sm, - git_submodule_ignore_t ign) -{ - unsigned int status; - git_repository *smrepo = NULL; - - if (ign == GIT_SUBMODULE_IGNORE_UNSPECIFIED) - ign = sm->ignore; - - /* only return location info if ignore == all */ - if (ign == GIT_SUBMODULE_IGNORE_ALL) { - *out_status = (sm->flags & GIT_SUBMODULE_STATUS__IN_FLAGS); - return 0; - } - - /* If the user has requested caching submodule state, performing these - * expensive operations (especially `submodule_update_head`, which is - * bottlenecked on `git_repository_head_tree`) eliminates much of the - * advantage. We will, therefore, interpret the request for caching to - * apply here to and skip them. - */ - - if (sm->repo->submodule_cache == NULL) { - /* refresh the index OID */ - if (submodule_update_index(sm) < 0) - return -1; - - /* refresh the HEAD OID */ - if (submodule_update_head(sm) < 0) - return -1; - } - - /* for ignore == dirty, don't scan the working directory */ - if (ign == GIT_SUBMODULE_IGNORE_DIRTY) { - /* git_submodule_open_bare will load WD OID data */ - if (git_submodule_open_bare(&smrepo, sm) < 0) - git_error_clear(); - else - git_repository_free(smrepo); - smrepo = NULL; - } else if (git_submodule_open(&smrepo, sm) < 0) { - git_error_clear(); - smrepo = NULL; - } - - status = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(sm->flags); - - submodule_get_index_status(&status, sm); - submodule_get_wd_status(&status, sm, smrepo, ign); - - git_repository_free(smrepo); - - *out_status = status; - - submodule_copy_oid_maybe(out_head_id, &sm->head_oid, - (sm->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) != 0); - submodule_copy_oid_maybe(out_index_id, &sm->index_oid, - (sm->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) != 0); - submodule_copy_oid_maybe(out_wd_id, &sm->wd_oid, - (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) != 0); - - return 0; -} - -int git_submodule_status(unsigned int *status, git_repository *repo, const char *name, git_submodule_ignore_t ignore) -{ - git_submodule *sm; - int error; - - GIT_ASSERT_ARG(status); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - if ((error = git_submodule_lookup(&sm, repo, name)) < 0) - return error; - - error = git_submodule__status(status, NULL, NULL, NULL, sm, ignore); - git_submodule_free(sm); - - return error; -} - -int git_submodule_location(unsigned int *location, git_submodule *sm) -{ - GIT_ASSERT_ARG(location); - GIT_ASSERT_ARG(sm); - - return git_submodule__status( - location, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_ALL); -} - -/* - * INTERNAL FUNCTIONS - */ - -static int submodule_alloc( - git_submodule **out, git_repository *repo, const char *name) -{ - size_t namelen; - git_submodule *sm; - - if (!name || !(namelen = strlen(name))) { - git_error_set(GIT_ERROR_SUBMODULE, "invalid submodule name"); - return -1; - } - - sm = git__calloc(1, sizeof(git_submodule)); - GIT_ERROR_CHECK_ALLOC(sm); - - sm->name = sm->path = git__strdup(name); - if (!sm->name) { - git__free(sm); - return -1; - } - - GIT_REFCOUNT_INC(sm); - sm->ignore = sm->ignore_default = GIT_SUBMODULE_IGNORE_NONE; - sm->update = sm->update_default = GIT_SUBMODULE_UPDATE_CHECKOUT; - sm->fetch_recurse = sm->fetch_recurse_default = GIT_SUBMODULE_RECURSE_NO; - sm->repo = repo; - sm->branch = NULL; - - *out = sm; - return 0; -} - -static void submodule_release(git_submodule *sm) -{ - if (!sm) - return; - - if (sm->repo) { - sm->repo = NULL; - } - - if (sm->path != sm->name) - git__free(sm->path); - git__free(sm->name); - git__free(sm->url); - git__free(sm->branch); - git__memzero(sm, sizeof(*sm)); - git__free(sm); -} - -int git_submodule_dup(git_submodule **out, git_submodule *source) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(source); - - GIT_REFCOUNT_INC(source); - - *out = source; - return 0; -} - -void git_submodule_free(git_submodule *sm) -{ - if (!sm) - return; - GIT_REFCOUNT_DEC(sm, submodule_release); -} - -static int submodule_config_error(const char *property, const char *value) -{ - git_error_set(GIT_ERROR_INVALID, - "invalid value for submodule '%s' property: '%s'", property, value); - return -1; -} - -int git_submodule_parse_ignore(git_submodule_ignore_t *out, const char *value) -{ - int val; - - if (git_config_lookup_map_value( - &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) { - *out = GIT_SUBMODULE_IGNORE_NONE; - return submodule_config_error("ignore", value); - } - - *out = (git_submodule_ignore_t)val; - return 0; -} - -int git_submodule_parse_update(git_submodule_update_t *out, const char *value) -{ - int val; - - if (git_config_lookup_map_value( - &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) { - *out = GIT_SUBMODULE_UPDATE_CHECKOUT; - return submodule_config_error("update", value); - } - - *out = (git_submodule_update_t)val; - return 0; -} - -static int submodule_parse_recurse(git_submodule_recurse_t *out, const char *value) -{ - int val; - - if (git_config_lookup_map_value( - &val, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), value) < 0) { - *out = GIT_SUBMODULE_RECURSE_YES; - return submodule_config_error("recurse", value); - } - - *out = (git_submodule_recurse_t)val; - return 0; -} - -static int get_value(const char **out, git_config *cfg, git_str *buf, const char *name, const char *field) -{ - int error; - - git_str_clear(buf); - - if ((error = git_str_printf(buf, "submodule.%s.%s", name, field)) < 0 || - (error = git_config_get_string(out, cfg, buf->ptr)) < 0) - return error; - - return error; -} - -static bool looks_like_command_line_option(const char *s) -{ - if (s && s[0] == '-') - return true; - - return false; -} - -static int submodule_read_config(git_submodule *sm, git_config *cfg) -{ - git_str key = GIT_STR_INIT; - const char *value; - int error, in_config = 0; - - /* - * TODO: Look up path in index and if it is present but not a GITLINK - * then this should be deleted (at least to match git's behavior) - */ - - if ((error = get_value(&value, cfg, &key, sm->name, "path")) == 0) { - in_config = 1; - /* We would warn here if we had that API */ - if (!looks_like_command_line_option(value)) { - /* - * TODO: if case insensitive filesystem, then the following strcmp - * should be strcasecmp - */ - if (strcmp(sm->name, value) != 0) { - if (sm->path != sm->name) - git__free(sm->path); - sm->path = git__strdup(value); - GIT_ERROR_CHECK_ALLOC(sm->path); - } - - } - } else if (error != GIT_ENOTFOUND) { - goto cleanup; - } - - if ((error = get_value(&value, cfg, &key, sm->name, "url")) == 0) { - /* We would warn here if we had that API */ - if (!looks_like_command_line_option(value)) { - in_config = 1; - sm->url = git__strdup(value); - GIT_ERROR_CHECK_ALLOC(sm->url); - } - } else if (error != GIT_ENOTFOUND) { - goto cleanup; - } - - if ((error = get_value(&value, cfg, &key, sm->name, "branch")) == 0) { - in_config = 1; - sm->branch = git__strdup(value); - GIT_ERROR_CHECK_ALLOC(sm->branch); - } else if (error != GIT_ENOTFOUND) { - goto cleanup; - } - - if ((error = get_value(&value, cfg, &key, sm->name, "update")) == 0) { - in_config = 1; - if ((error = git_submodule_parse_update(&sm->update, value)) < 0) - goto cleanup; - sm->update_default = sm->update; - } else if (error != GIT_ENOTFOUND) { - goto cleanup; - } - - if ((error = get_value(&value, cfg, &key, sm->name, "fetchRecurseSubmodules")) == 0) { - in_config = 1; - if ((error = submodule_parse_recurse(&sm->fetch_recurse, value)) < 0) - goto cleanup; - sm->fetch_recurse_default = sm->fetch_recurse; - } else if (error != GIT_ENOTFOUND) { - goto cleanup; - } - - if ((error = get_value(&value, cfg, &key, sm->name, "ignore")) == 0) { - in_config = 1; - if ((error = git_submodule_parse_ignore(&sm->ignore, value)) < 0) - goto cleanup; - sm->ignore_default = sm->ignore; - } else if (error != GIT_ENOTFOUND) { - goto cleanup; - } - - if (in_config) - sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; - - error = 0; - -cleanup: - git_str_dispose(&key); - return error; -} - -static int submodule_load_each(const git_config_entry *entry, void *payload) -{ - lfc_data *data = payload; - const char *namestart, *property; - git_strmap *map = data->map; - git_str name = GIT_STR_INIT; - git_submodule *sm; - int error, isvalid; - - if (git__prefixcmp(entry->name, "submodule.") != 0) - return 0; - - namestart = entry->name + strlen("submodule."); - property = strrchr(namestart, '.'); - - if (!property || (property == namestart)) - return 0; - - property++; - - if ((error = git_str_set(&name, namestart, property - namestart -1)) < 0) - return error; - - isvalid = git_submodule_name_is_valid(data->repo, name.ptr, 0); - if (isvalid <= 0) { - error = isvalid; - goto done; - } - - /* - * Now that we have the submodule's name, we can use that to - * figure out whether it's in the map. If it's not, we create - * a new submodule, load the config and insert it. If it's - * already inserted, we've already loaded it, so we skip. - */ - if (git_strmap_exists(map, name.ptr)) { - error = 0; - goto done; - } - - if ((error = submodule_alloc(&sm, data->repo, name.ptr)) < 0) - goto done; - - if ((error = submodule_read_config(sm, data->mods)) < 0) { - git_submodule_free(sm); - goto done; - } - - if ((error = git_strmap_set(map, sm->name, sm)) < 0) - goto done; - - error = 0; - -done: - git_str_dispose(&name); - return error; -} - -static int submodule_load_from_wd_lite(git_submodule *sm) -{ - git_str path = GIT_STR_INIT; - - if (git_repository_workdir_path(&path, sm->repo, sm->path) < 0) - return -1; - - if (git_fs_path_isdir(path.ptr)) - sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; - - if (git_fs_path_contains(&path, DOT_GIT)) - sm->flags |= GIT_SUBMODULE_STATUS_IN_WD; - - git_str_dispose(&path); - return 0; -} - -/** - * Requests a snapshot of $WORK_TREE/.gitmodules. - * - * Returns GIT_ENOTFOUND in case no .gitmodules file exist - */ -static int gitmodules_snapshot(git_config **snap, git_repository *repo) -{ - git_config *mods = NULL; - git_str path = GIT_STR_INIT; - int error; - - if (git_repository_workdir(repo) == NULL) - return GIT_ENOTFOUND; - - if ((error = git_repository_workdir_path(&path, repo, GIT_MODULES_FILE)) < 0) - return error; - - if ((error = git_config_open_ondisk(&mods, path.ptr)) < 0) - goto cleanup; - git_str_dispose(&path); - - if ((error = git_config_snapshot(snap, mods)) < 0) - goto cleanup; - - error = 0; - -cleanup: - if (mods) - git_config_free(mods); - git_str_dispose(&path); - - return error; -} - -static git_config_backend *open_gitmodules( - git_repository *repo, - int okay_to_create) -{ - git_str path = GIT_STR_INIT; - git_config_backend *mods = NULL; - - if (git_repository_workdir(repo) != NULL) { - if (git_repository_workdir_path(&path, repo, GIT_MODULES_FILE) != 0) - return NULL; - - if (okay_to_create || git_fs_path_isfile(path.ptr)) { - /* git_config_backend_from_file should only fail if OOM */ - if (git_config_backend_from_file(&mods, path.ptr) < 0) - mods = NULL; - /* open should only fail here if the file is malformed */ - else if (git_config_backend_open(mods, GIT_CONFIG_LEVEL_LOCAL, repo) < 0) { - git_config_backend_free(mods); - mods = NULL; - } - } - } - - git_str_dispose(&path); - - return mods; -} - -/* Lookup name of remote of the local tracking branch HEAD points to */ -static int lookup_head_remote_key(git_str *remote_name, git_repository *repo) -{ - int error; - git_reference *head = NULL; - git_str upstream_name = GIT_STR_INIT; - - /* lookup and dereference HEAD */ - if ((error = git_repository_head(&head, repo)) < 0) - return error; - - /** - * If head does not refer to a branch, then return - * GIT_ENOTFOUND to indicate that we could not find - * a remote key for the local tracking branch HEAD points to. - **/ - if (!git_reference_is_branch(head)) { - git_error_set(GIT_ERROR_INVALID, - "HEAD does not refer to a branch."); - error = GIT_ENOTFOUND; - goto done; - } - - /* lookup remote tracking branch of HEAD */ - if ((error = git_branch__upstream_name( - &upstream_name, - repo, - git_reference_name(head))) < 0) - goto done; - - /* lookup remote of remote tracking branch */ - if ((error = git_branch__remote_name(remote_name, repo, upstream_name.ptr)) < 0) - goto done; - -done: - git_str_dispose(&upstream_name); - git_reference_free(head); - - return error; -} - -/* Lookup the remote of the local tracking branch HEAD points to */ -static int lookup_head_remote(git_remote **remote, git_repository *repo) -{ - int error; - git_str remote_name = GIT_STR_INIT; - - /* lookup remote of remote tracking branch name */ - if (!(error = lookup_head_remote_key(&remote_name, repo))) - error = git_remote_lookup(remote, repo, remote_name.ptr); - - git_str_dispose(&remote_name); - - return error; -} - -/* Lookup remote, either from HEAD or fall back on origin */ -static int lookup_default_remote(git_remote **remote, git_repository *repo) -{ - int error = lookup_head_remote(remote, repo); - - /* if that failed, use 'origin' instead */ - if (error == GIT_ENOTFOUND || error == GIT_EUNBORNBRANCH) - error = git_remote_lookup(remote, repo, "origin"); - - if (error == GIT_ENOTFOUND) - git_error_set( - GIT_ERROR_SUBMODULE, - "cannot get default remote for submodule - no local tracking " - "branch for HEAD and origin does not exist"); - - return error; -} - -static int get_url_base(git_str *url, git_repository *repo) -{ - int error; - git_worktree *wt = NULL; - git_remote *remote = NULL; - - if ((error = lookup_default_remote(&remote, repo)) == 0) { - error = git_str_sets(url, git_remote_url(remote)); - goto out; - } else if (error != GIT_ENOTFOUND) - goto out; - else - git_error_clear(); - - /* if repository does not have a default remote, use workdir instead */ - if (git_repository_is_worktree(repo)) { - if ((error = git_worktree_open_from_repository(&wt, repo)) < 0) - goto out; - error = git_str_sets(url, wt->parent_path); - } else { - error = git_str_sets(url, git_repository_workdir(repo)); - } - -out: - git_remote_free(remote); - git_worktree_free(wt); - - return error; -} - -static void submodule_get_index_status(unsigned int *status, git_submodule *sm) -{ - const git_oid *head_oid = git_submodule_head_id(sm); - const git_oid *index_oid = git_submodule_index_id(sm); - - *status = *status & ~GIT_SUBMODULE_STATUS__INDEX_FLAGS; - - if (!head_oid) { - if (index_oid) - *status |= GIT_SUBMODULE_STATUS_INDEX_ADDED; - } - else if (!index_oid) - *status |= GIT_SUBMODULE_STATUS_INDEX_DELETED; - else if (!git_oid_equal(head_oid, index_oid)) - *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED; -} - - -static void submodule_get_wd_status( - unsigned int *status, - git_submodule *sm, - git_repository *sm_repo, - git_submodule_ignore_t ign) -{ - const git_oid *index_oid = git_submodule_index_id(sm); - const git_oid *wd_oid = - (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) ? &sm->wd_oid : NULL; - git_tree *sm_head = NULL; - git_index *index = NULL; - git_diff_options opt = GIT_DIFF_OPTIONS_INIT; - git_diff *diff; - - *status = *status & ~GIT_SUBMODULE_STATUS__WD_FLAGS; - - if (!index_oid) { - if (wd_oid) - *status |= GIT_SUBMODULE_STATUS_WD_ADDED; - } - else if (!wd_oid) { - if ((sm->flags & GIT_SUBMODULE_STATUS__WD_SCANNED) != 0 && - (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) - *status |= GIT_SUBMODULE_STATUS_WD_UNINITIALIZED; - else - *status |= GIT_SUBMODULE_STATUS_WD_DELETED; - } - else if (!git_oid_equal(index_oid, wd_oid)) - *status |= GIT_SUBMODULE_STATUS_WD_MODIFIED; - - /* if we have no repo, then we're done */ - if (!sm_repo) - return; - - /* the diffs below could be optimized with an early termination - * option to the git_diff functions, but for now this is sufficient - * (and certainly no worse that what core git does). - */ - - if (ign == GIT_SUBMODULE_IGNORE_NONE) - opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - - (void)git_repository_index__weakptr(&index, sm_repo); - - /* if we don't have an unborn head, check diff with index */ - if (git_repository_head_tree(&sm_head, sm_repo) < 0) - git_error_clear(); - else { - /* perform head to index diff on submodule */ - if (git_diff_tree_to_index(&diff, sm_repo, sm_head, index, &opt) < 0) - git_error_clear(); - else { - if (git_diff_num_deltas(diff) > 0) - *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED; - git_diff_free(diff); - diff = NULL; - } - - git_tree_free(sm_head); - } - - /* perform index-to-workdir diff on submodule */ - if (git_diff_index_to_workdir(&diff, sm_repo, index, &opt) < 0) - git_error_clear(); - else { - size_t untracked = - git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED); - - if (untracked > 0) - *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; - - if (git_diff_num_deltas(diff) != untracked) - *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; - - git_diff_free(diff); - diff = NULL; - } -} diff --git a/src/submodule.h b/src/submodule.h deleted file mode 100644 index 7fa982486..000000000 --- a/src/submodule.h +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_submodule_h__ -#define INCLUDE_submodule_h__ - -#include "common.h" - -#include "git2/submodule.h" -#include "git2/repository.h" -#include "futils.h" - -/* Notes: - * - * Submodule information can be in four places: the index, the config files - * (both .git/config and .gitmodules), the HEAD tree, and the working - * directory. - * - * In the index: - * - submodule is found by path - * - may be missing, present, or of the wrong type - * - will have an oid if present - * - * In the HEAD tree: - * - submodule is found by path - * - may be missing, present, or of the wrong type - * - will have an oid if present - * - * In the config files: - * - submodule is found by submodule "name" which is usually the path - * - may be missing or present - * - will have a name, path, url, and other properties - * - * In the working directory: - * - submodule is found by path - * - may be missing, an empty directory, a checked out directory, - * or of the wrong type - * - if checked out, will have a HEAD oid - * - if checked out, will have git history that can be used to compare oids - * - if checked out, may have modified files and/or untracked files - */ - -/** - * Description of submodule - * - * This record describes a submodule found in a repository. There should be - * an entry for every submodule found in the HEAD and index, and for every - * submodule described in .gitmodules. The fields are as follows: - * - * - `rc` tracks the refcount of how many hash table entries in the - * git_submodule_cache there are for this submodule. It only comes into - * play if the name and path of the submodule differ. - * - * - `name` is the name of the submodule from .gitmodules. - * - `path` is the path to the submodule from the repo root. It is almost - * always the same as `name`. - * - `url` is the url for the submodule. - * - `update` is a git_submodule_update_t value - see gitmodules(5) update. - * - `update_default` is the update value from the config - * - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore. - * - `ignore_default` is the ignore value from the config - * - `fetch_recurse` is a git_submodule_recurse_t value - see gitmodules(5) - * fetchRecurseSubmodules. - * - `fetch_recurse_default` is the recurse value from the config - * - * - `repo` is the parent repository that contains this submodule. - * - `flags` after for internal use, tracking where this submodule has been - * found (head, index, config, workdir) and known status info, etc. - * - `head_oid` is the SHA1 for the submodule path in the repo HEAD. - * - `index_oid` is the SHA1 for the submodule recorded in the index. - * - `wd_oid` is the SHA1 for the HEAD of the checked out submodule. - * - * If the submodule has been added to .gitmodules but not yet git added, - * then the `index_oid` will be zero but still marked valid. If the - * submodule has been deleted, but the delete has not been committed yet, - * then the `index_oid` will be set, but the `url` will be NULL. - */ -struct git_submodule { - git_refcount rc; - - /* information from config */ - char *name; - char *path; /* important: may just point to "name" string */ - char *url; - char *branch; - git_submodule_update_t update; - git_submodule_update_t update_default; - git_submodule_ignore_t ignore; - git_submodule_ignore_t ignore_default; - git_submodule_recurse_t fetch_recurse; - git_submodule_recurse_t fetch_recurse_default; - - /* internal information */ - git_repository *repo; - uint32_t flags; - git_oid head_oid; - git_oid index_oid; - git_oid wd_oid; -}; - -/* Additional flags on top of public GIT_SUBMODULE_STATUS values */ -enum { - GIT_SUBMODULE_STATUS__WD_SCANNED = (1u << 20), - GIT_SUBMODULE_STATUS__HEAD_OID_VALID = (1u << 21), - GIT_SUBMODULE_STATUS__INDEX_OID_VALID = (1u << 22), - GIT_SUBMODULE_STATUS__WD_OID_VALID = (1u << 23), - GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE = (1u << 24), - GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE = (1u << 25), - GIT_SUBMODULE_STATUS__WD_NOT_SUBMODULE = (1u << 26), - GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES = (1u << 27) -}; - -#define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \ - ((S) & ~(0xFFFFFFFFu << 20)) - -/* Initialize an external submodule cache for the provided repo. */ -extern int git_submodule_cache_init(git_strmap **out, git_repository *repo); - -/* Release the resources of the submodule cache. */ -extern int git_submodule_cache_free(git_strmap *cache); - -/* Submodule lookup with an explicit cache */ -extern int git_submodule__lookup_with_cache( - git_submodule **out, git_repository *repo, const char *path, git_strmap *cache); - -/* Internal status fn returns status and optionally the various OIDs */ -extern int git_submodule__status( - unsigned int *out_status, - git_oid *out_head_id, - git_oid *out_index_id, - git_oid *out_wd_id, - git_submodule *sm, - git_submodule_ignore_t ign); - -/* Open submodule repository as bare repo for quick HEAD check, etc. */ -extern int git_submodule_open_bare( - git_repository **repo, - git_submodule *submodule); - -extern int git_submodule_parse_ignore( - git_submodule_ignore_t *out, const char *value); -extern int git_submodule_parse_update( - git_submodule_update_t *out, const char *value); - -extern int git_submodule__map( - git_repository *repo, - git_strmap *map); - -/** - * Check whether a submodule's name is valid. - * - * Check the path against the path validity rules, either the filesystem - * defaults (like checkout does) or whichever you want to compare against. - * - * @param repo the repository which contains the submodule - * @param name the name to check - * @param flags the `GIT_PATH` flags to use for the check (0 to use filesystem defaults) - */ -extern int git_submodule_name_is_valid(git_repository *repo, const char *name, int flags); - -#endif diff --git a/src/sysdir.c b/src/sysdir.c deleted file mode 100644 index 450cb509b..000000000 --- a/src/sysdir.c +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "sysdir.h" - -#include "runtime.h" -#include "str.h" -#include "fs_path.h" -#include -#if GIT_WIN32 -#include "win32/findfile.h" -#else -#include -#include -#endif - -static int git_sysdir_guess_programdata_dirs(git_str *out) -{ -#ifdef GIT_WIN32 - return git_win32__find_programdata_dirs(out); -#else - git_str_clear(out); - return 0; -#endif -} - -static int git_sysdir_guess_system_dirs(git_str *out) -{ -#ifdef GIT_WIN32 - return git_win32__find_system_dirs(out, "etc"); -#else - return git_str_sets(out, "/etc"); -#endif -} - -#ifndef GIT_WIN32 -static int get_passwd_home(git_str *out, uid_t uid) -{ - struct passwd pwd, *pwdptr; - char *buf = NULL; - long buflen; - int error; - - GIT_ASSERT_ARG(out); - - if ((buflen = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) - buflen = 1024; - - do { - buf = git__realloc(buf, buflen); - error = getpwuid_r(uid, &pwd, buf, buflen, &pwdptr); - buflen *= 2; - } while (error == ERANGE && buflen <= 8192); - - if (error) { - git_error_set(GIT_ERROR_OS, "failed to get passwd entry"); - goto out; - } - - if (!pwdptr) { - git_error_set(GIT_ERROR_OS, "no passwd entry found for user"); - goto out; - } - - if ((error = git_str_puts(out, pwdptr->pw_dir)) < 0) - goto out; - -out: - git__free(buf); - return error; -} -#endif - -static int git_sysdir_guess_global_dirs(git_str *out) -{ -#ifdef GIT_WIN32 - return git_win32__find_global_dirs(out); -#else - int error; - uid_t uid, euid; - const char *sandbox_id; - - uid = getuid(); - euid = geteuid(); - - /** - * If APP_SANDBOX_CONTAINER_ID is set, we are running in a - * sandboxed environment on macOS. - */ - sandbox_id = getenv("APP_SANDBOX_CONTAINER_ID"); - - /* - * In case we are running setuid, use the configuration - * of the effective user. - * - * If we are running in a sandboxed environment on macOS, - * we have to get the HOME dir from the password entry file. - */ - if (!sandbox_id && uid == euid) - error = git__getenv(out, "HOME"); - else - error = get_passwd_home(out, euid); - - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - return error; -#endif -} - -static int git_sysdir_guess_xdg_dirs(git_str *out) -{ -#ifdef GIT_WIN32 - return git_win32__find_xdg_dirs(out); -#else - git_str env = GIT_STR_INIT; - int error; - uid_t uid, euid; - - uid = getuid(); - euid = geteuid(); - - /* - * In case we are running setuid, only look up passwd - * directory of the effective user. - */ - if (uid == euid) { - if ((error = git__getenv(&env, "XDG_CONFIG_HOME")) == 0) - error = git_str_joinpath(out, env.ptr, "git"); - - if (error == GIT_ENOTFOUND && (error = git__getenv(&env, "HOME")) == 0) - error = git_str_joinpath(out, env.ptr, ".config/git"); - } else { - if ((error = get_passwd_home(&env, euid)) == 0) - error = git_str_joinpath(out, env.ptr, ".config/git"); - } - - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } - - git_str_dispose(&env); - return error; -#endif -} - -static int git_sysdir_guess_template_dirs(git_str *out) -{ -#ifdef GIT_WIN32 - return git_win32__find_system_dirs(out, "share/git-core/templates"); -#else - return git_str_sets(out, "/usr/share/git-core/templates"); -#endif -} - -struct git_sysdir__dir { - git_str buf; - int (*guess)(git_str *out); -}; - -static struct git_sysdir__dir git_sysdir__dirs[] = { - { GIT_STR_INIT, git_sysdir_guess_system_dirs }, - { GIT_STR_INIT, git_sysdir_guess_global_dirs }, - { GIT_STR_INIT, git_sysdir_guess_xdg_dirs }, - { GIT_STR_INIT, git_sysdir_guess_programdata_dirs }, - { GIT_STR_INIT, git_sysdir_guess_template_dirs }, -}; - -static void git_sysdir_global_shutdown(void) -{ - size_t i; - - for (i = 0; i < ARRAY_SIZE(git_sysdir__dirs); ++i) - git_str_dispose(&git_sysdir__dirs[i].buf); -} - -int git_sysdir_global_init(void) -{ - size_t i; - int error = 0; - - for (i = 0; !error && i < ARRAY_SIZE(git_sysdir__dirs); i++) - error = git_sysdir__dirs[i].guess(&git_sysdir__dirs[i].buf); - - if (error) - return error; - - return git_runtime_shutdown_register(git_sysdir_global_shutdown); -} - -int git_sysdir_reset(void) -{ - size_t i; - int error = 0; - - for (i = 0; !error && i < ARRAY_SIZE(git_sysdir__dirs); ++i) { - git_str_dispose(&git_sysdir__dirs[i].buf); - error = git_sysdir__dirs[i].guess(&git_sysdir__dirs[i].buf); - } - - return error; -} - -static int git_sysdir_check_selector(git_sysdir_t which) -{ - if (which < ARRAY_SIZE(git_sysdir__dirs)) - return 0; - - git_error_set(GIT_ERROR_INVALID, "config directory selector out of range"); - return -1; -} - - -int git_sysdir_get(const git_str **out, git_sysdir_t which) -{ - GIT_ASSERT_ARG(out); - - *out = NULL; - - GIT_ERROR_CHECK_ERROR(git_sysdir_check_selector(which)); - - *out = &git_sysdir__dirs[which].buf; - return 0; -} - -#define PATH_MAGIC "$PATH" - -int git_sysdir_set(git_sysdir_t which, const char *search_path) -{ - const char *expand_path = NULL; - git_str merge = GIT_STR_INIT; - - GIT_ERROR_CHECK_ERROR(git_sysdir_check_selector(which)); - - if (search_path != NULL) - expand_path = strstr(search_path, PATH_MAGIC); - - /* reset the default if this path has been cleared */ - if (!search_path) - git_sysdir__dirs[which].guess(&git_sysdir__dirs[which].buf); - - /* if $PATH is not referenced, then just set the path */ - if (!expand_path) { - if (search_path) - git_str_sets(&git_sysdir__dirs[which].buf, search_path); - - goto done; - } - - /* otherwise set to join(before $PATH, old value, after $PATH) */ - if (expand_path > search_path) - git_str_set(&merge, search_path, expand_path - search_path); - - if (git_str_len(&git_sysdir__dirs[which].buf)) - git_str_join(&merge, GIT_PATH_LIST_SEPARATOR, - merge.ptr, git_sysdir__dirs[which].buf.ptr); - - expand_path += strlen(PATH_MAGIC); - if (*expand_path) - git_str_join(&merge, GIT_PATH_LIST_SEPARATOR, merge.ptr, expand_path); - - git_str_swap(&git_sysdir__dirs[which].buf, &merge); - git_str_dispose(&merge); - -done: - if (git_str_oom(&git_sysdir__dirs[which].buf)) - return -1; - - return 0; -} - -static int git_sysdir_find_in_dirlist( - git_str *path, - const char *name, - git_sysdir_t which, - const char *label) -{ - size_t len; - const char *scan, *next = NULL; - const git_str *syspath; - - GIT_ERROR_CHECK_ERROR(git_sysdir_get(&syspath, which)); - if (!syspath || !git_str_len(syspath)) - goto done; - - for (scan = git_str_cstr(syspath); scan; scan = next) { - /* find unescaped separator or end of string */ - for (next = scan; *next; ++next) { - if (*next == GIT_PATH_LIST_SEPARATOR && - (next <= scan || next[-1] != '\\')) - break; - } - - len = (size_t)(next - scan); - next = (*next ? next + 1 : NULL); - if (!len) - continue; - - GIT_ERROR_CHECK_ERROR(git_str_set(path, scan, len)); - if (name) - GIT_ERROR_CHECK_ERROR(git_str_joinpath(path, path->ptr, name)); - - if (git_fs_path_exists(path->ptr)) - return 0; - } - -done: - if (name) - git_error_set(GIT_ERROR_OS, "the %s file '%s' doesn't exist", label, name); - else - git_error_set(GIT_ERROR_OS, "the %s directory doesn't exist", label); - git_str_dispose(path); - return GIT_ENOTFOUND; -} - -int git_sysdir_find_system_file(git_str *path, const char *filename) -{ - return git_sysdir_find_in_dirlist( - path, filename, GIT_SYSDIR_SYSTEM, "system"); -} - -int git_sysdir_find_global_file(git_str *path, const char *filename) -{ - return git_sysdir_find_in_dirlist( - path, filename, GIT_SYSDIR_GLOBAL, "global"); -} - -int git_sysdir_find_xdg_file(git_str *path, const char *filename) -{ - return git_sysdir_find_in_dirlist( - path, filename, GIT_SYSDIR_XDG, "global/xdg"); -} - -int git_sysdir_find_programdata_file(git_str *path, const char *filename) -{ - return git_sysdir_find_in_dirlist( - path, filename, GIT_SYSDIR_PROGRAMDATA, "ProgramData"); -} - -int git_sysdir_find_template_dir(git_str *path) -{ - return git_sysdir_find_in_dirlist( - path, NULL, GIT_SYSDIR_TEMPLATE, "template"); -} - -int git_sysdir_expand_global_file(git_str *path, const char *filename) -{ - int error; - - if ((error = git_sysdir_find_global_file(path, NULL)) == 0) { - if (filename) - error = git_str_joinpath(path, path->ptr, filename); - } - - return error; -} diff --git a/src/sysdir.h b/src/sysdir.h deleted file mode 100644 index 568f27940..000000000 --- a/src/sysdir.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_sysdir_h__ -#define INCLUDE_sysdir_h__ - -#include "common.h" - -#include "posix.h" -#include "str.h" - -/** - * Find a "global" file (i.e. one in a user's home directory). - * - * @param path buffer to write the full path into - * @param filename name of file to find in the home directory - * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error - */ -extern int git_sysdir_find_global_file(git_str *path, const char *filename); - -/** - * Find an "XDG" file (i.e. one in user's XDG config path). - * - * @param path buffer to write the full path into - * @param filename name of file to find in the home directory - * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error - */ -extern int git_sysdir_find_xdg_file(git_str *path, const char *filename); - -/** - * Find a "system" file (i.e. one shared for all users of the system). - * - * @param path buffer to write the full path into - * @param filename name of file to find in the home directory - * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error - */ -extern int git_sysdir_find_system_file(git_str *path, const char *filename); - -/** - * Find a "ProgramData" file (i.e. one in %PROGRAMDATA%) - * - * @param path buffer to write the full path into - * @param filename name of file to find in the ProgramData directory - * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error - */ -extern int git_sysdir_find_programdata_file(git_str *path, const char *filename); - -/** - * Find template directory. - * - * @param path buffer to write the full path into - * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error - */ -extern int git_sysdir_find_template_dir(git_str *path); - -/** - * Expand the name of a "global" file (i.e. one in a user's home - * directory). Unlike `find_global_file` (above), this makes no - * attempt to check for the existence of the file, and is useful if - * you want the full path regardless of existence. - * - * @param path buffer to write the full path into - * @param filename name of file in the home directory - * @return 0 on success or -1 on error - */ -extern int git_sysdir_expand_global_file(git_str *path, const char *filename); - -typedef enum { - GIT_SYSDIR_SYSTEM = 0, - GIT_SYSDIR_GLOBAL = 1, - GIT_SYSDIR_XDG = 2, - GIT_SYSDIR_PROGRAMDATA = 3, - GIT_SYSDIR_TEMPLATE = 4, - GIT_SYSDIR__MAX = 5 -} git_sysdir_t; - -/** - * Configures global data for configuration file search paths. - * - * @return 0 on success, <0 on failure - */ -extern int git_sysdir_global_init(void); - -/** - * Get the search path for global/system/xdg files - * - * @param out pointer to git_str containing search path - * @param which which list of paths to return - * @return 0 on success, <0 on failure - */ -extern int git_sysdir_get(const git_str **out, git_sysdir_t which); - -/** - * Set search paths for global/system/xdg files - * - * The first occurrence of the magic string "$PATH" in the new value will - * be replaced with the old value of the search path. - * - * @param which Which search path to modify - * @param paths New search path (separated by GIT_PATH_LIST_SEPARATOR) - * @return 0 on success, <0 on failure (allocation error) - */ -extern int git_sysdir_set(git_sysdir_t which, const char *paths); - -/** - * Reset search paths for global/system/xdg files. - */ -extern int git_sysdir_reset(void); - -#endif diff --git a/src/tag.c b/src/tag.c deleted file mode 100644 index 5734106fa..000000000 --- a/src/tag.c +++ /dev/null @@ -1,570 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "tag.h" - -#include "commit.h" -#include "signature.h" -#include "wildmatch.h" -#include "git2/object.h" -#include "git2/repository.h" -#include "git2/signature.h" -#include "git2/odb_backend.h" - -void git_tag__free(void *_tag) -{ - git_tag *tag = _tag; - git_signature_free(tag->tagger); - git__free(tag->message); - git__free(tag->tag_name); - git__free(tag); -} - -int git_tag_target(git_object **target, const git_tag *t) -{ - GIT_ASSERT_ARG(t); - return git_object_lookup(target, t->object.repo, &t->target, t->type); -} - -const git_oid *git_tag_target_id(const git_tag *t) -{ - GIT_ASSERT_ARG_WITH_RETVAL(t, NULL); - return &t->target; -} - -git_object_t git_tag_target_type(const git_tag *t) -{ - GIT_ASSERT_ARG_WITH_RETVAL(t, GIT_OBJECT_INVALID); - return t->type; -} - -const char *git_tag_name(const git_tag *t) -{ - GIT_ASSERT_ARG_WITH_RETVAL(t, NULL); - return t->tag_name; -} - -const git_signature *git_tag_tagger(const git_tag *t) -{ - return t->tagger; -} - -const char *git_tag_message(const git_tag *t) -{ - GIT_ASSERT_ARG_WITH_RETVAL(t, NULL); - return t->message; -} - -static int tag_error(const char *str) -{ - git_error_set(GIT_ERROR_TAG, "failed to parse tag: %s", str); - return GIT_EINVALID; -} - -static int tag_parse(git_tag *tag, const char *buffer, const char *buffer_end) -{ - static const char *tag_types[] = { - NULL, "commit\n", "tree\n", "blob\n", "tag\n" - }; - size_t text_len, alloc_len; - const char *search; - unsigned int i; - int error; - - if (git_oid__parse(&tag->target, &buffer, buffer_end, "object ") < 0) - return tag_error("object field invalid"); - - if (buffer + 5 >= buffer_end) - return tag_error("object too short"); - - if (memcmp(buffer, "type ", 5) != 0) - return tag_error("type field not found"); - buffer += 5; - - tag->type = GIT_OBJECT_INVALID; - - for (i = 1; i < ARRAY_SIZE(tag_types); ++i) { - size_t type_length = strlen(tag_types[i]); - - if (buffer + type_length >= buffer_end) - return tag_error("object too short"); - - if (memcmp(buffer, tag_types[i], type_length) == 0) { - tag->type = i; - buffer += type_length; - break; - } - } - - if (tag->type == GIT_OBJECT_INVALID) - return tag_error("invalid object type"); - - if (buffer + 4 >= buffer_end) - return tag_error("object too short"); - - if (memcmp(buffer, "tag ", 4) != 0) - return tag_error("tag field not found"); - - buffer += 4; - - search = memchr(buffer, '\n', buffer_end - buffer); - if (search == NULL) - return tag_error("object too short"); - - text_len = search - buffer; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, text_len, 1); - tag->tag_name = git__malloc(alloc_len); - GIT_ERROR_CHECK_ALLOC(tag->tag_name); - - memcpy(tag->tag_name, buffer, text_len); - tag->tag_name[text_len] = '\0'; - - buffer = search + 1; - - tag->tagger = NULL; - if (buffer < buffer_end && *buffer != '\n') { - tag->tagger = git__malloc(sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(tag->tagger); - - if ((error = git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ", '\n')) < 0) - return error; - } - - tag->message = NULL; - if (buffer < buffer_end) { - /* If we're not at the end of the header, search for it */ - if(*buffer != '\n') { - search = git__memmem(buffer, buffer_end - buffer, - "\n\n", 2); - if (search) - buffer = search + 1; - else - return tag_error("tag contains no message"); - } - - text_len = buffer_end - ++buffer; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, text_len, 1); - tag->message = git__malloc(alloc_len); - GIT_ERROR_CHECK_ALLOC(tag->message); - - memcpy(tag->message, buffer, text_len); - tag->message[text_len] = '\0'; - } - - return 0; -} - -int git_tag__parse_raw(void *_tag, const char *data, size_t size) -{ - return tag_parse(_tag, data, data + size); -} - -int git_tag__parse(void *_tag, git_odb_object *odb_obj) -{ - git_tag *tag = _tag; - const char *buffer = git_odb_object_data(odb_obj); - const char *buffer_end = buffer + git_odb_object_size(odb_obj); - - return tag_parse(tag, buffer, buffer_end); -} - -static int retrieve_tag_reference( - git_reference **tag_reference_out, - git_str *ref_name_out, - git_repository *repo, - const char *tag_name) -{ - git_reference *tag_ref; - int error; - - *tag_reference_out = NULL; - - if (git_str_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) - return -1; - - error = git_reference_lookup(&tag_ref, repo, ref_name_out->ptr); - if (error < 0) - return error; /* Be it not foundo or corrupted */ - - *tag_reference_out = tag_ref; - - return 0; -} - -static int retrieve_tag_reference_oid( - git_oid *oid, - git_str *ref_name_out, - git_repository *repo, - const char *tag_name) -{ - if (git_str_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) - return -1; - - return git_reference_name_to_id(oid, repo, ref_name_out->ptr); -} - -static int write_tag_annotation( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_object *target, - const git_signature *tagger, - const char *message) -{ - git_str tag = GIT_STR_INIT; - git_odb *odb; - - git_oid__writebuf(&tag, "object ", git_object_id(target)); - git_str_printf(&tag, "type %s\n", git_object_type2string(git_object_type(target))); - git_str_printf(&tag, "tag %s\n", tag_name); - git_signature__writebuf(&tag, "tagger ", tagger); - git_str_putc(&tag, '\n'); - - if (git_str_puts(&tag, message) < 0) - goto on_error; - - if (git_repository_odb__weakptr(&odb, repo) < 0) - goto on_error; - - if (git_odb_write(oid, odb, tag.ptr, tag.size, GIT_OBJECT_TAG) < 0) - goto on_error; - - git_str_dispose(&tag); - return 0; - -on_error: - git_str_dispose(&tag); - git_error_set(GIT_ERROR_OBJECT, "failed to create tag annotation"); - return -1; -} - -static int git_tag_create__internal( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_object *target, - const git_signature *tagger, - const char *message, - int allow_ref_overwrite, - int create_tag_annotation) -{ - git_reference *new_ref = NULL; - git_str ref_name = GIT_STR_INIT; - - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(tag_name); - GIT_ASSERT_ARG(target); - GIT_ASSERT_ARG(!create_tag_annotation || (tagger && message)); - - if (git_object_owner(target) != repo) { - git_error_set(GIT_ERROR_INVALID, "the given target does not belong to this repository"); - return -1; - } - - error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag_name); - if (error < 0 && error != GIT_ENOTFOUND) - goto cleanup; - - /** Ensure the tag name doesn't conflict with an already existing - * reference unless overwriting has explicitly been requested **/ - if (error == 0 && !allow_ref_overwrite) { - git_str_dispose(&ref_name); - git_error_set(GIT_ERROR_TAG, "tag already exists"); - return GIT_EEXISTS; - } - - if (create_tag_annotation) { - if (write_tag_annotation(oid, repo, tag_name, target, tagger, message) < 0) - return -1; - } else - git_oid_cpy(oid, git_object_id(target)); - - error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL); - -cleanup: - git_reference_free(new_ref); - git_str_dispose(&ref_name); - return error; -} - -int git_tag_create( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_object *target, - const git_signature *tagger, - const char *message, - int allow_ref_overwrite) -{ - return git_tag_create__internal(oid, repo, tag_name, target, tagger, message, allow_ref_overwrite, 1); -} - -int git_tag_annotation_create( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_object *target, - const git_signature *tagger, - const char *message) -{ - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(tag_name); - GIT_ASSERT_ARG(target); - GIT_ASSERT_ARG(tagger); - GIT_ASSERT_ARG(message); - - return write_tag_annotation(oid, repo, tag_name, target, tagger, message); -} - -int git_tag_create_lightweight( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_object *target, - int allow_ref_overwrite) -{ - return git_tag_create__internal(oid, repo, tag_name, target, NULL, NULL, allow_ref_overwrite, 0); -} - -int git_tag_create_from_buffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite) -{ - git_tag tag; - int error; - git_odb *odb; - git_odb_stream *stream; - git_odb_object *target_obj; - - git_reference *new_ref = NULL; - git_str ref_name = GIT_STR_INIT; - - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(buffer); - - memset(&tag, 0, sizeof(tag)); - - if (git_repository_odb__weakptr(&odb, repo) < 0) - return -1; - - /* validate the buffer */ - if (tag_parse(&tag, buffer, buffer + strlen(buffer)) < 0) - return -1; - - /* validate the target */ - if (git_odb_read(&target_obj, odb, &tag.target) < 0) - goto on_error; - - if (tag.type != target_obj->cached.type) { - git_error_set(GIT_ERROR_TAG, "the type for the given target is invalid"); - goto on_error; - } - - error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag.tag_name); - if (error < 0 && error != GIT_ENOTFOUND) - goto on_error; - - /* We don't need these objects after this */ - git_signature_free(tag.tagger); - git__free(tag.tag_name); - git__free(tag.message); - git_odb_object_free(target_obj); - - /** Ensure the tag name doesn't conflict with an already existing - * reference unless overwriting has explicitly been requested **/ - if (error == 0 && !allow_ref_overwrite) { - git_error_set(GIT_ERROR_TAG, "tag already exists"); - return GIT_EEXISTS; - } - - /* write the buffer */ - if ((error = git_odb_open_wstream( - &stream, odb, strlen(buffer), GIT_OBJECT_TAG)) < 0) - return error; - - if (!(error = git_odb_stream_write(stream, buffer, strlen(buffer)))) - error = git_odb_stream_finalize_write(oid, stream); - - git_odb_stream_free(stream); - - if (error < 0) { - git_str_dispose(&ref_name); - return error; - } - - error = git_reference_create( - &new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL); - - git_reference_free(new_ref); - git_str_dispose(&ref_name); - - return error; - -on_error: - git_signature_free(tag.tagger); - git__free(tag.tag_name); - git__free(tag.message); - git_odb_object_free(target_obj); - return -1; -} - -int git_tag_delete(git_repository *repo, const char *tag_name) -{ - git_reference *tag_ref; - git_str ref_name = GIT_STR_INIT; - int error; - - error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name); - - git_str_dispose(&ref_name); - - if (error < 0) - return error; - - error = git_reference_delete(tag_ref); - - git_reference_free(tag_ref); - - return error; -} - -typedef struct { - git_repository *repo; - git_tag_foreach_cb cb; - void *cb_data; -} tag_cb_data; - -static int tags_cb(const char *ref, void *data) -{ - int error; - git_oid oid; - tag_cb_data *d = (tag_cb_data *)data; - - if (git__prefixcmp(ref, GIT_REFS_TAGS_DIR) != 0) - return 0; /* no tag */ - - if (!(error = git_reference_name_to_id(&oid, d->repo, ref))) { - if ((error = d->cb(ref, &oid, d->cb_data)) != 0) - git_error_set_after_callback_function(error, "git_tag_foreach"); - } - - return error; -} - -int git_tag_foreach(git_repository *repo, git_tag_foreach_cb cb, void *cb_data) -{ - tag_cb_data data; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(cb); - - data.cb = cb; - data.cb_data = cb_data; - data.repo = repo; - - return git_reference_foreach_name(repo, &tags_cb, &data); -} - -typedef struct { - git_vector *taglist; - const char *pattern; -} tag_filter_data; - -#define GIT_REFS_TAGS_DIR_LEN strlen(GIT_REFS_TAGS_DIR) - -static int tag_list_cb(const char *tag_name, git_oid *oid, void *data) -{ - tag_filter_data *filter = (tag_filter_data *)data; - GIT_UNUSED(oid); - - if (!*filter->pattern || - wildmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0) - { - char *matched = git__strdup(tag_name + GIT_REFS_TAGS_DIR_LEN); - GIT_ERROR_CHECK_ALLOC(matched); - - return git_vector_insert(filter->taglist, matched); - } - - return 0; -} - -int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_repository *repo) -{ - int error; - tag_filter_data filter; - git_vector taglist; - - GIT_ASSERT_ARG(tag_names); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(pattern); - - if ((error = git_vector_init(&taglist, 8, NULL)) < 0) - return error; - - filter.taglist = &taglist; - filter.pattern = pattern; - - error = git_tag_foreach(repo, &tag_list_cb, (void *)&filter); - - if (error < 0) - git_vector_free(&taglist); - - tag_names->strings = - (char **)git_vector_detach(&tag_names->count, NULL, &taglist); - - return 0; -} - -int git_tag_list(git_strarray *tag_names, git_repository *repo) -{ - return git_tag_list_match(tag_names, "", repo); -} - -int git_tag_peel(git_object **tag_target, const git_tag *tag) -{ - return git_object_peel(tag_target, (const git_object *)tag, GIT_OBJECT_ANY); -} - -int git_tag_name_is_valid(int *valid, const char *name) -{ - git_str ref_name = GIT_STR_INIT; - int error = 0; - - GIT_ASSERT(valid); - - *valid = 0; - - /* - * Discourage tag name starting with dash, - * https://github.com/git/git/commit/4f0accd638b8d2 - */ - if (!name || name[0] == '-') - goto done; - - if ((error = git_str_puts(&ref_name, GIT_REFS_TAGS_DIR)) < 0 || - (error = git_str_puts(&ref_name, name)) < 0) - goto done; - - error = git_reference_name_is_valid(valid, ref_name.ptr); - -done: - git_str_dispose(&ref_name); - return error; -} - -/* Deprecated Functions */ - -#ifndef GIT_DEPRECATE_HARD -int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite) -{ - return git_tag_create_from_buffer(oid, repo, buffer, allow_ref_overwrite); -} -#endif diff --git a/src/tag.h b/src/tag.h deleted file mode 100644 index 76ae1508e..000000000 --- a/src/tag.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_tag_h__ -#define INCLUDE_tag_h__ - -#include "common.h" - -#include "git2/tag.h" -#include "repository.h" -#include "odb.h" - -struct git_tag { - git_object object; - - git_oid target; - git_object_t type; - - char *tag_name; - git_signature *tagger; - char *message; -}; - -void git_tag__free(void *tag); -int git_tag__parse(void *tag, git_odb_object *obj); -int git_tag__parse_raw(void *tag, const char *data, size_t size); - -#endif diff --git a/src/thread.c b/src/thread.c deleted file mode 100644 index 3171771d7..000000000 --- a/src/thread.c +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#if !defined(GIT_THREADS) - -#define TLSDATA_MAX 16 - -typedef struct { - void *value; - void (GIT_SYSTEM_CALL *destroy_fn)(void *); -} tlsdata_value; - -static tlsdata_value tlsdata_values[TLSDATA_MAX]; -static int tlsdata_cnt = 0; - -int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) -{ - if (tlsdata_cnt >= TLSDATA_MAX) - return -1; - - tlsdata_values[tlsdata_cnt].value = NULL; - tlsdata_values[tlsdata_cnt].destroy_fn = destroy_fn; - - *key = tlsdata_cnt; - tlsdata_cnt++; - - return 0; -} - -int git_tlsdata_set(git_tlsdata_key key, void *value) -{ - if (key < 0 || key > tlsdata_cnt) - return -1; - - tlsdata_values[key].value = value; - return 0; -} - -void *git_tlsdata_get(git_tlsdata_key key) -{ - if (key < 0 || key > tlsdata_cnt) - return NULL; - - return tlsdata_values[key].value; -} - -int git_tlsdata_dispose(git_tlsdata_key key) -{ - void *value; - void (*destroy_fn)(void *) = NULL; - - if (key < 0 || key > tlsdata_cnt) - return -1; - - value = tlsdata_values[key].value; - destroy_fn = tlsdata_values[key].destroy_fn; - - tlsdata_values[key].value = NULL; - tlsdata_values[key].destroy_fn = NULL; - - if (value && destroy_fn) - destroy_fn(value); - - return 0; -} - -#elif defined(GIT_WIN32) - -int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) -{ - DWORD fls_index = FlsAlloc(destroy_fn); - - if (fls_index == FLS_OUT_OF_INDEXES) - return -1; - - *key = fls_index; - return 0; -} - -int git_tlsdata_set(git_tlsdata_key key, void *value) -{ - if (!FlsSetValue(key, value)) - return -1; - - return 0; -} - -void *git_tlsdata_get(git_tlsdata_key key) -{ - return FlsGetValue(key); -} - -int git_tlsdata_dispose(git_tlsdata_key key) -{ - if (!FlsFree(key)) - return -1; - - return 0; -} - -#elif defined(_POSIX_THREADS) - -int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) -{ - if (pthread_key_create(key, destroy_fn) != 0) - return -1; - - return 0; -} - -int git_tlsdata_set(git_tlsdata_key key, void *value) -{ - if (pthread_setspecific(key, value) != 0) - return -1; - - return 0; -} - -void *git_tlsdata_get(git_tlsdata_key key) -{ - return pthread_getspecific(key); -} - -int git_tlsdata_dispose(git_tlsdata_key key) -{ - if (pthread_key_delete(key) != 0) - return -1; - - return 0; -} - -#else -# error unknown threading model -#endif diff --git a/src/thread.h b/src/thread.h deleted file mode 100644 index 4bbac9fd8..000000000 --- a/src/thread.h +++ /dev/null @@ -1,479 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_thread_h__ -#define INCLUDE_thread_h__ - -#if defined(GIT_THREADS) - -#if defined(__clang__) - -# if (__clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ < 1)) -# error Atomic primitives do not exist on this version of clang; configure libgit2 with -DUSE_THREADS=OFF -# else -# define GIT_BUILTIN_ATOMIC -# endif - -#elif defined(__GNUC__) - -# if (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 1)) -# error Atomic primitives do not exist on this version of gcc; configure libgit2 with -DUSE_THREADS=OFF -# elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) -# define GIT_BUILTIN_ATOMIC -# else -# define GIT_BUILTIN_SYNC -# endif - -#endif - -#endif /* GIT_THREADS */ - -/* Common operations even if threading has been disabled */ -typedef struct { -#if defined(GIT_WIN32) - volatile long val; -#else - volatile int val; -#endif -} git_atomic32; - -#ifdef GIT_ARCH_64 - -typedef struct { -#if defined(GIT_WIN32) - volatile __int64 val; -#else - volatile int64_t val; -#endif -} git_atomic64; - -typedef git_atomic64 git_atomic_ssize; - -#define git_atomic_ssize_set git_atomic64_set -#define git_atomic_ssize_add git_atomic64_add -#define git_atomic_ssize_get git_atomic64_get - -#else - -typedef git_atomic32 git_atomic_ssize; - -#define git_atomic_ssize_set git_atomic32_set -#define git_atomic_ssize_add git_atomic32_add -#define git_atomic_ssize_get git_atomic32_get - -#endif - -#ifdef GIT_THREADS - -#ifdef GIT_WIN32 -# include "win32/thread.h" -#else -# include "unix/pthread.h" -#endif - -/* - * Atomically sets the contents of *a to be val. - */ -GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) -{ -#if defined(GIT_WIN32) - InterlockedExchange(&a->val, (LONG)val); -#elif defined(GIT_BUILTIN_ATOMIC) - __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - __sync_lock_test_and_set(&a->val, val); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically increments the contents of *a by 1, and stores the result back into *a. - * @return the result of the operation. - */ -GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) -{ -#if defined(GIT_WIN32) - return InterlockedIncrement(&a->val); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_add_fetch(&a->val, 1, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_add_and_fetch(&a->val, 1); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically adds the contents of *a and addend, and stores the result back into *a. - * @return the result of the operation. - */ -GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) -{ -#if defined(GIT_WIN32) - return InterlockedAdd(&a->val, addend); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_add_and_fetch(&a->val, addend); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically decrements the contents of *a by 1, and stores the result back into *a. - * @return the result of the operation. - */ -GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) -{ -#if defined(GIT_WIN32) - return InterlockedDecrement(&a->val); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_sub_fetch(&a->val, 1, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_sub_and_fetch(&a->val, 1); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically gets the contents of *a. - * @return the contents of *a. - */ -GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) -{ -#if defined(GIT_WIN32) - return (int)InterlockedCompareExchange(&a->val, 0, 0); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_val_compare_and_swap(&a->val, 0, 0); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -GIT_INLINE(void *) git_atomic__compare_and_swap( - void * volatile *ptr, void *oldval, void *newval) -{ -#if defined(GIT_WIN32) - return InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); -#elif defined(GIT_BUILTIN_ATOMIC) - void *foundval = oldval; - __atomic_compare_exchange(ptr, &foundval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); - return foundval; -#elif defined(GIT_BUILTIN_SYNC) - return __sync_val_compare_and_swap(ptr, oldval, newval); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -GIT_INLINE(volatile void *) git_atomic__swap( - void * volatile *ptr, void *newval) -{ -#if defined(GIT_WIN32) - return InterlockedExchangePointer(ptr, newval); -#elif defined(GIT_BUILTIN_ATOMIC) - void * foundval = NULL; - __atomic_exchange(ptr, &newval, &foundval, __ATOMIC_SEQ_CST); - return foundval; -#elif defined(GIT_BUILTIN_SYNC) - return (volatile void *)__sync_lock_test_and_set(ptr, newval); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) -{ -#if defined(GIT_WIN32) - void *newval = NULL, *oldval = NULL; - return (volatile void *)InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); -#elif defined(GIT_BUILTIN_ATOMIC) - return (volatile void *)__atomic_load_n(ptr, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return (volatile void *)__sync_val_compare_and_swap(ptr, 0, 0); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -#ifdef GIT_ARCH_64 - -/* - * Atomically adds the contents of *a and addend, and stores the result back into *a. - * @return the result of the operation. - */ -GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) -{ -#if defined(GIT_WIN32) - return InterlockedAdd64(&a->val, addend); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_add_and_fetch(&a->val, addend); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically sets the contents of *a to be val. - */ -GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) -{ -#if defined(GIT_WIN32) - InterlockedExchange64(&a->val, val); -#elif defined(GIT_BUILTIN_ATOMIC) - __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - __sync_lock_test_and_set(&a->val, val); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically gets the contents of *a. - * @return the contents of *a. - */ -GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) -{ -#if defined(GIT_WIN32) - return (int64_t)InterlockedCompareExchange64(&a->val, 0, 0); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_val_compare_and_swap(&a->val, 0, 0); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -#endif - -#else - -#define git_threads_global_init git__noop - -#define git_thread unsigned int -#define git_thread_create(thread, start_routine, arg) git__noop() -#define git_thread_join(id, status) git__noop() - -/* Pthreads Mutex */ -#define git_mutex unsigned int -#define git_mutex_init(a) git__noop() -#define git_mutex_init(a) git__noop() -#define git_mutex_lock(a) git__noop() -#define git_mutex_unlock(a) git__noop() -#define git_mutex_free(a) git__noop() - -/* Pthreads condition vars */ -#define git_cond unsigned int -#define git_cond_init(c) git__noop() -#define git_cond_free(c) git__noop() -#define git_cond_wait(c, l) git__noop() -#define git_cond_signal(c) git__noop() -#define git_cond_broadcast(c) git__noop() - -/* Pthreads rwlock */ -#define git_rwlock unsigned int -#define git_rwlock_init(a) git__noop() -#define git_rwlock_rdlock(a) git__noop() -#define git_rwlock_rdunlock(a) git__noop() -#define git_rwlock_wrlock(a) git__noop() -#define git_rwlock_wrunlock(a) git__noop() -#define git_rwlock_free(a) git__noop() -#define GIT_RWLOCK_STATIC_INIT 0 - - -GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) -{ - a->val = val; -} - -GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) -{ - return ++a->val; -} - -GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) -{ - a->val += addend; - return a->val; -} - -GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) -{ - return --a->val; -} - -GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) -{ - return (int)a->val; -} - -GIT_INLINE(void *) git_atomic__compare_and_swap( - void * volatile *ptr, void *oldval, void *newval) -{ - void *foundval = *ptr; - if (foundval == oldval) - *ptr = newval; - return foundval; -} - -GIT_INLINE(volatile void *) git_atomic__swap( - void * volatile *ptr, void *newval) -{ - volatile void *old = *ptr; - *ptr = newval; - return old; -} - -GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) -{ - return *ptr; -} - -#ifdef GIT_ARCH_64 - -GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) -{ - a->val += addend; - return a->val; -} - -GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) -{ - a->val = val; -} - -GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) -{ - return (int64_t)a->val; -} - -#endif - -#endif - -/* - * Atomically replace the contents of *ptr (if they are equal to oldval) with - * newval. ptr must point to a pointer or a value that is the same size as a - * pointer. This is semantically compatible with: - * - * #define git_atomic_compare_and_swap(ptr, oldval, newval) \ - * ({ \ - * void *foundval = *ptr; \ - * if (foundval == oldval) \ - * *ptr = newval; \ - * foundval; \ - * }) - * - * @return the original contents of *ptr. - */ -#define git_atomic_compare_and_swap(ptr, oldval, newval) \ - git_atomic__compare_and_swap((void * volatile *)ptr, oldval, newval) - -/* - * Atomically replace the contents of v with newval. v must be the same size as - * a pointer. This is semantically compatible with: - * - * #define git_atomic_swap(v, newval) \ - * ({ \ - * volatile void *old = v; \ - * v = newval; \ - * old; \ - * }) - * - * @return the original contents of v. - */ -#define git_atomic_swap(v, newval) \ - (void *)git_atomic__swap((void * volatile *)&(v), newval) - -/* - * Atomically reads the contents of v. v must be the same size as a pointer. - * This is semantically compatible with: - * - * #define git_atomic_load(v) v - * - * @return the contents of v. - */ -#define git_atomic_load(v) \ - (void *)git_atomic__load((void * volatile *)&(v)) - -#if defined(GIT_THREADS) - -# if defined(GIT_WIN32) -# define GIT_MEMORY_BARRIER MemoryBarrier() -# elif defined(GIT_BUILTIN_ATOMIC) -# define GIT_MEMORY_BARRIER __atomic_thread_fence(__ATOMIC_SEQ_CST) -# elif defined(GIT_BUILTIN_SYNC) -# define GIT_MEMORY_BARRIER __sync_synchronize() -# endif - -#else - -# define GIT_MEMORY_BARRIER /* noop */ - -#endif - -/* Thread-local data */ - -#if !defined(GIT_THREADS) -# define git_tlsdata_key int -#elif defined(GIT_WIN32) -# define git_tlsdata_key DWORD -#elif defined(_POSIX_THREADS) -# define git_tlsdata_key pthread_key_t -#else -# error unknown threading model -#endif - -/** - * Create a thread-local data key. The destroy function will be - * called upon thread exit. On some platforms, it may be called - * when all threads have deleted their keys. - * - * Note that the tlsdata functions do not set an error message on - * failure; this is because the error handling in libgit2 is itself - * handled by thread-local data storage. - * - * @param key the tlsdata key - * @param destroy_fn function pointer called upon thread exit - * @return 0 on success, non-zero on failure - */ -int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)); - -/** - * Set a the thread-local value for the given key. - * - * @param key the tlsdata key to store data on - * @param value the pointer to store - * @return 0 on success, non-zero on failure - */ -int git_tlsdata_set(git_tlsdata_key key, void *value); - -/** - * Get the thread-local value for the given key. - * - * @param key the tlsdata key to retrieve the value of - * @return the pointer stored with git_tlsdata_set - */ -void *git_tlsdata_get(git_tlsdata_key key); - -/** - * Delete the given thread-local key. - * - * @param key the tlsdata key to dispose - * @return 0 on success, non-zero on failure - */ -int git_tlsdata_dispose(git_tlsdata_key key); - -#endif diff --git a/src/threadstate.c b/src/threadstate.c deleted file mode 100644 index 9e3ef5818..000000000 --- a/src/threadstate.c +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "threadstate.h" -#include "runtime.h" - -/** - * Handle the thread-local state - * - * `git_threadstate_global_init` will be called as part - * of `git_libgit2_init` (which itself must be called - * before calling any other function in the library). - * - * This function allocates a TLS index to store the per- - * thread state. - * - * Any internal method that requires thread-local state - * will then call `git_threadstate_get()` which returns a - * pointer to the thread-local state structure; this - * structure is lazily allocated on each thread. - * - * This mechanism will register a shutdown handler - * (`git_threadstate_global_shutdown`) which will free the - * TLS index. This shutdown handler will be called by - * `git_libgit2_shutdown`. - */ - -static git_tlsdata_key tls_key; - -static void threadstate_dispose(git_threadstate *threadstate) -{ - if (!threadstate) - return; - - if (threadstate->error_t.message != git_str__initstr) - git__free(threadstate->error_t.message); - threadstate->error_t.message = NULL; -} - -static void GIT_SYSTEM_CALL threadstate_free(void *threadstate) -{ - threadstate_dispose(threadstate); - git__free(threadstate); -} - -static void git_threadstate_global_shutdown(void) -{ - git_threadstate *threadstate; - - threadstate = git_tlsdata_get(tls_key); - git_tlsdata_set(tls_key, NULL); - - threadstate_dispose(threadstate); - git__free(threadstate); - - git_tlsdata_dispose(tls_key); -} - -int git_threadstate_global_init(void) -{ - if (git_tlsdata_init(&tls_key, &threadstate_free) != 0) - return -1; - - return git_runtime_shutdown_register(git_threadstate_global_shutdown); -} - -git_threadstate *git_threadstate_get(void) -{ - git_threadstate *threadstate; - - if ((threadstate = git_tlsdata_get(tls_key)) != NULL) - return threadstate; - - if ((threadstate = git__calloc(1, sizeof(git_threadstate))) == NULL || - git_str_init(&threadstate->error_buf, 0) < 0) - return NULL; - - git_tlsdata_set(tls_key, threadstate); - return threadstate; -} diff --git a/src/threadstate.h b/src/threadstate.h deleted file mode 100644 index c10f26b59..000000000 --- a/src/threadstate.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_threadstate_h__ -#define INCLUDE_threadstate_h__ - -#include "common.h" - -typedef struct { - git_error *last_error; - git_error error_t; - git_str error_buf; - char oid_fmt[GIT_OID_HEXSZ+1]; -} git_threadstate; - -extern int git_threadstate_global_init(void); -extern git_threadstate *git_threadstate_get(void); - -#define GIT_THREADSTATE (git_threadstate_get()) - -#endif diff --git a/src/trace.c b/src/trace.c deleted file mode 100644 index b0c56c4dc..000000000 --- a/src/trace.c +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "trace.h" - -#include "str.h" -#include "runtime.h" -#include "git2/trace.h" - -struct git_trace_data git_trace__data = {0}; - -int git_trace_set(git_trace_level_t level, git_trace_cb callback) -{ - GIT_ASSERT_ARG(level == 0 || callback != NULL); - - git_trace__data.level = level; - git_trace__data.callback = callback; - GIT_MEMORY_BARRIER; - - return 0; -} diff --git a/src/trace.h b/src/trace.h deleted file mode 100644 index 239928dcb..000000000 --- a/src/trace.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_trace_h__ -#define INCLUDE_trace_h__ - -#include "common.h" - -#include -#include "str.h" - -struct git_trace_data { - git_trace_level_t level; - git_trace_cb callback; -}; - -extern struct git_trace_data git_trace__data; - -GIT_INLINE(void) git_trace__write_fmt( - git_trace_level_t level, - const char *fmt, - va_list ap) -{ - git_trace_cb callback = git_trace__data.callback; - git_str message = GIT_STR_INIT; - - git_str_vprintf(&message, fmt, ap); - - callback(level, git_str_cstr(&message)); - - git_str_dispose(&message); -} - -#define git_trace_level() (git_trace__data.level) - -GIT_INLINE(void) git_trace(git_trace_level_t level, const char *fmt, ...) -{ - if (git_trace__data.level >= level && - git_trace__data.callback != NULL) { - va_list ap; - - va_start(ap, fmt); - git_trace__write_fmt(level, fmt, ap); - va_end(ap); - } -} - -#endif diff --git a/src/trailer.c b/src/trailer.c deleted file mode 100644 index 4761c9922..000000000 --- a/src/trailer.c +++ /dev/null @@ -1,430 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#include "array.h" -#include "common.h" -#include "git2/message.h" - -#include -#include -#include - -#define COMMENT_LINE_CHAR '#' -#define TRAILER_SEPARATORS ":" - -static const char *const git_generated_prefixes[] = { - "Signed-off-by: ", - "(cherry picked from commit ", - NULL -}; - -static int is_blank_line(const char *str) -{ - const char *s = str; - while (*s && *s != '\n' && isspace(*s)) - s++; - return !*s || *s == '\n'; -} - -static const char *next_line(const char *str) -{ - const char *nl = strchr(str, '\n'); - - if (nl) { - return nl + 1; - } else { - /* return pointer to the NUL terminator: */ - return str + strlen(str); - } -} - -/* - * Return the position of the start of the last line. If len is 0, return 0. - */ -static bool last_line(size_t *out, const char *buf, size_t len) -{ - size_t i; - - *out = 0; - - if (len == 0) - return false; - if (len == 1) - return true; - - /* - * Skip the last character (in addition to the null terminator), - * because if the last character is a newline, it is considered as part - * of the last line anyway. - */ - i = len - 2; - - for (; i > 0; i--) { - if (buf[i] == '\n') { - *out = i + 1; - return true; - } - } - return true; -} - -/* - * If the given line is of the form - * "..." or "...", sets out - * to the location of the separator and returns true. Otherwise, returns - * false. The optional whitespace is allowed there primarily to allow things - * like "Bug #43" where is "Bug" and is "#". - * - * The separator-starts-line case (in which this function returns true and - * sets out to 0) is distinguished from the non-well-formed-line case (in - * which this function returns false) because some callers of this function - * need such a distinction. - */ -static bool find_separator(size_t *out, const char *line, const char *separators) -{ - int whitespace_found = 0; - const char *c; - for (c = line; *c; c++) { - if (strchr(separators, *c)) { - *out = c - line; - return true; - } - - if (!whitespace_found && (isalnum(*c) || *c == '-')) - continue; - if (c != line && (*c == ' ' || *c == '\t')) { - whitespace_found = 1; - continue; - } - break; - } - return false; -} - -/* - * Inspect the given string and determine the true "end" of the log message, in - * order to find where to put a new Signed-off-by: line. Ignored are - * trailing comment lines and blank lines. To support "git commit -s - * --amend" on an existing commit, we also ignore "Conflicts:". To - * support "git commit -v", we truncate at cut lines. - * - * Returns the number of bytes from the tail to ignore, to be fed as - * the second parameter to append_signoff(). - */ -static size_t ignore_non_trailer(const char *buf, size_t len) -{ - size_t boc = 0, bol = 0; - int in_old_conflicts_block = 0; - size_t cutoff = len; - - while (bol < cutoff) { - const char *next_line = memchr(buf + bol, '\n', len - bol); - - if (!next_line) - next_line = buf + len; - else - next_line++; - - if (buf[bol] == COMMENT_LINE_CHAR || buf[bol] == '\n') { - /* is this the first of the run of comments? */ - if (!boc) - boc = bol; - /* otherwise, it is just continuing */ - } else if (git__prefixcmp(buf + bol, "Conflicts:\n") == 0) { - in_old_conflicts_block = 1; - if (!boc) - boc = bol; - } else if (in_old_conflicts_block && buf[bol] == '\t') { - ; /* a pathname in the conflicts block */ - } else if (boc) { - /* the previous was not trailing comment */ - boc = 0; - in_old_conflicts_block = 0; - } - bol = next_line - buf; - } - return boc ? len - boc : len - cutoff; -} - -/* - * Return the position of the start of the patch or the length of str if there - * is no patch in the message. - */ -static size_t find_patch_start(const char *str) -{ - const char *s; - - for (s = str; *s; s = next_line(s)) { - if (git__prefixcmp(s, "---") == 0) - return s - str; - } - - return s - str; -} - -/* - * Return the position of the first trailer line or len if there are no - * trailers. - */ -static size_t find_trailer_start(const char *buf, size_t len) -{ - const char *s; - size_t end_of_title, l; - int only_spaces = 1; - int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0; - /* - * Number of possible continuation lines encountered. This will be - * reset to 0 if we encounter a trailer (since those lines are to be - * considered continuations of that trailer), and added to - * non_trailer_lines if we encounter a non-trailer (since those lines - * are to be considered non-trailers). - */ - int possible_continuation_lines = 0; - - /* The first paragraph is the title and cannot be trailers */ - for (s = buf; s < buf + len; s = next_line(s)) { - if (s[0] == COMMENT_LINE_CHAR) - continue; - if (is_blank_line(s)) - break; - } - end_of_title = s - buf; - - /* - * Get the start of the trailers by looking starting from the end for a - * blank line before a set of non-blank lines that (i) are all - * trailers, or (ii) contains at least one Git-generated trailer and - * consists of at least 25% trailers. - */ - l = len; - while (last_line(&l, buf, l) && l >= end_of_title) { - const char *bol = buf + l; - const char *const *p; - size_t separator_pos = 0; - - if (bol[0] == COMMENT_LINE_CHAR) { - non_trailer_lines += possible_continuation_lines; - possible_continuation_lines = 0; - continue; - } - if (is_blank_line(bol)) { - if (only_spaces) - continue; - non_trailer_lines += possible_continuation_lines; - if (recognized_prefix && - trailer_lines * 3 >= non_trailer_lines) - return next_line(bol) - buf; - else if (trailer_lines && !non_trailer_lines) - return next_line(bol) - buf; - return len; - } - only_spaces = 0; - - for (p = git_generated_prefixes; *p; p++) { - if (git__prefixcmp(bol, *p) == 0) { - trailer_lines++; - possible_continuation_lines = 0; - recognized_prefix = 1; - goto continue_outer_loop; - } - } - - find_separator(&separator_pos, bol, TRAILER_SEPARATORS); - if (separator_pos >= 1 && !isspace(bol[0])) { - trailer_lines++; - possible_continuation_lines = 0; - if (recognized_prefix) - continue; - } else if (isspace(bol[0])) - possible_continuation_lines++; - else { - non_trailer_lines++; - non_trailer_lines += possible_continuation_lines; - possible_continuation_lines = 0; - } -continue_outer_loop: - ; - } - - return len; -} - -/* Return the position of the end of the trailers. */ -static size_t find_trailer_end(const char *buf, size_t len) -{ - return len - ignore_non_trailer(buf, len); -} - -static char *extract_trailer_block(const char *message, size_t *len) -{ - size_t patch_start = find_patch_start(message); - size_t trailer_end = find_trailer_end(message, patch_start); - size_t trailer_start = find_trailer_start(message, trailer_end); - - size_t trailer_len = trailer_end - trailer_start; - - char *buffer = git__malloc(trailer_len + 1); - if (buffer == NULL) - return NULL; - - memcpy(buffer, message + trailer_start, trailer_len); - buffer[trailer_len] = 0; - - *len = trailer_len; - - return buffer; -} - -enum trailer_state { - S_START = 0, - S_KEY = 1, - S_KEY_WS = 2, - S_SEP_WS = 3, - S_VALUE = 4, - S_VALUE_NL = 5, - S_VALUE_END = 6, - S_IGNORE = 7 -}; - -#define NEXT(st) { state = (st); ptr++; continue; } -#define GOTO(st) { state = (st); continue; } - -typedef git_array_t(git_message_trailer) git_array_trailer_t; - -int git_message_trailers(git_message_trailer_array *trailer_arr, const char *message) -{ - enum trailer_state state = S_START; - int rc = 0; - char *ptr; - char *key = NULL; - char *value = NULL; - git_array_trailer_t arr = GIT_ARRAY_INIT; - - size_t trailer_len; - char *trailer = extract_trailer_block(message, &trailer_len); - if (trailer == NULL) - return -1; - - for (ptr = trailer;;) { - switch (state) { - case S_START: { - if (*ptr == 0) { - goto ret; - } - - key = ptr; - GOTO(S_KEY); - } - case S_KEY: { - if (*ptr == 0) { - goto ret; - } - - if (isalnum(*ptr) || *ptr == '-') { - /* legal key character */ - NEXT(S_KEY); - } - - if (*ptr == ' ' || *ptr == '\t') { - /* optional whitespace before separator */ - *ptr = 0; - NEXT(S_KEY_WS); - } - - if (strchr(TRAILER_SEPARATORS, *ptr)) { - *ptr = 0; - NEXT(S_SEP_WS); - } - - /* illegal character */ - GOTO(S_IGNORE); - } - case S_KEY_WS: { - if (*ptr == 0) { - goto ret; - } - - if (*ptr == ' ' || *ptr == '\t') { - NEXT(S_KEY_WS); - } - - if (strchr(TRAILER_SEPARATORS, *ptr)) { - NEXT(S_SEP_WS); - } - - /* illegal character */ - GOTO(S_IGNORE); - } - case S_SEP_WS: { - if (*ptr == 0) { - goto ret; - } - - if (*ptr == ' ' || *ptr == '\t') { - NEXT(S_SEP_WS); - } - - value = ptr; - NEXT(S_VALUE); - } - case S_VALUE: { - if (*ptr == 0) { - GOTO(S_VALUE_END); - } - - if (*ptr == '\n') { - NEXT(S_VALUE_NL); - } - - NEXT(S_VALUE); - } - case S_VALUE_NL: { - if (*ptr == ' ') { - /* continuation; */ - NEXT(S_VALUE); - } - - ptr[-1] = 0; - GOTO(S_VALUE_END); - } - case S_VALUE_END: { - git_message_trailer *t = git_array_alloc(arr); - - t->key = key; - t->value = value; - - key = NULL; - value = NULL; - - GOTO(S_START); - } - case S_IGNORE: { - if (*ptr == 0) { - goto ret; - } - - if (*ptr == '\n') { - NEXT(S_START); - } - - NEXT(S_IGNORE); - } - } - } - -ret: - trailer_arr->_trailer_block = trailer; - trailer_arr->trailers = arr.ptr; - trailer_arr->count = arr.size; - - return rc; -} - -void git_message_trailer_array_free(git_message_trailer_array *arr) -{ - git__free(arr->_trailer_block); - git__free(arr->trailers); -} diff --git a/src/transaction.c b/src/transaction.c deleted file mode 100644 index ccffa9984..000000000 --- a/src/transaction.c +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "transaction.h" - -#include "repository.h" -#include "strmap.h" -#include "refdb.h" -#include "pool.h" -#include "reflog.h" -#include "signature.h" -#include "config.h" - -#include "git2/transaction.h" -#include "git2/signature.h" -#include "git2/sys/refs.h" -#include "git2/sys/refdb_backend.h" - -typedef enum { - TRANSACTION_NONE, - TRANSACTION_REFS, - TRANSACTION_CONFIG -} transaction_t; - -typedef struct { - const char *name; - void *payload; - - git_reference_t ref_type; - union { - git_oid id; - char *symbolic; - } target; - git_reflog *reflog; - - const char *message; - git_signature *sig; - - unsigned int committed :1, - remove :1; -} transaction_node; - -struct git_transaction { - transaction_t type; - git_repository *repo; - git_refdb *db; - git_config *cfg; - - git_strmap *locks; - git_pool pool; -}; - -int git_transaction_config_new(git_transaction **out, git_config *cfg) -{ - git_transaction *tx; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(cfg); - - tx = git__calloc(1, sizeof(git_transaction)); - GIT_ERROR_CHECK_ALLOC(tx); - - tx->type = TRANSACTION_CONFIG; - tx->cfg = cfg; - *out = tx; - return 0; -} - -int git_transaction_new(git_transaction **out, git_repository *repo) -{ - int error; - git_pool pool; - git_transaction *tx = NULL; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - - if ((error = git_pool_init(&pool, 1)) < 0) - goto on_error; - - tx = git_pool_mallocz(&pool, sizeof(git_transaction)); - if (!tx) { - error = -1; - goto on_error; - } - - if ((error = git_strmap_new(&tx->locks)) < 0) { - error = -1; - goto on_error; - } - - if ((error = git_repository_refdb(&tx->db, repo)) < 0) - goto on_error; - - tx->type = TRANSACTION_REFS; - memcpy(&tx->pool, &pool, sizeof(git_pool)); - tx->repo = repo; - *out = tx; - return 0; - -on_error: - git_pool_clear(&pool); - return error; -} - -int git_transaction_lock_ref(git_transaction *tx, const char *refname) -{ - int error; - transaction_node *node; - - GIT_ASSERT_ARG(tx); - GIT_ASSERT_ARG(refname); - - node = git_pool_mallocz(&tx->pool, sizeof(transaction_node)); - GIT_ERROR_CHECK_ALLOC(node); - - node->name = git_pool_strdup(&tx->pool, refname); - GIT_ERROR_CHECK_ALLOC(node->name); - - if ((error = git_refdb_lock(&node->payload, tx->db, refname)) < 0) - return error; - - if ((error = git_strmap_set(tx->locks, node->name, node)) < 0) - goto cleanup; - - return 0; - -cleanup: - git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL); - - return error; -} - -static int find_locked(transaction_node **out, git_transaction *tx, const char *refname) -{ - transaction_node *node; - - if ((node = git_strmap_get(tx->locks, refname)) == NULL) { - git_error_set(GIT_ERROR_REFERENCE, "the specified reference is not locked"); - return GIT_ENOTFOUND; - } - - *out = node; - return 0; -} - -static int copy_common(transaction_node *node, git_transaction *tx, const git_signature *sig, const char *msg) -{ - if (sig && git_signature__pdup(&node->sig, sig, &tx->pool) < 0) - return -1; - - if (!node->sig) { - git_signature *tmp; - int error; - - if (git_reference__log_signature(&tmp, tx->repo) < 0) - return -1; - - /* make sure the sig we use is in our pool */ - error = git_signature__pdup(&node->sig, tmp, &tx->pool); - git_signature_free(tmp); - if (error < 0) - return error; - } - - if (msg) { - node->message = git_pool_strdup(&tx->pool, msg); - GIT_ERROR_CHECK_ALLOC(node->message); - } - - return 0; -} - -int git_transaction_set_target(git_transaction *tx, const char *refname, const git_oid *target, const git_signature *sig, const char *msg) -{ - int error; - transaction_node *node; - - GIT_ASSERT_ARG(tx); - GIT_ASSERT_ARG(refname); - GIT_ASSERT_ARG(target); - - if ((error = find_locked(&node, tx, refname)) < 0) - return error; - - if ((error = copy_common(node, tx, sig, msg)) < 0) - return error; - - git_oid_cpy(&node->target.id, target); - node->ref_type = GIT_REFERENCE_DIRECT; - - return 0; -} - -int git_transaction_set_symbolic_target(git_transaction *tx, const char *refname, const char *target, const git_signature *sig, const char *msg) -{ - int error; - transaction_node *node; - - GIT_ASSERT_ARG(tx); - GIT_ASSERT_ARG(refname); - GIT_ASSERT_ARG(target); - - if ((error = find_locked(&node, tx, refname)) < 0) - return error; - - if ((error = copy_common(node, tx, sig, msg)) < 0) - return error; - - node->target.symbolic = git_pool_strdup(&tx->pool, target); - GIT_ERROR_CHECK_ALLOC(node->target.symbolic); - node->ref_type = GIT_REFERENCE_SYMBOLIC; - - return 0; -} - -int git_transaction_remove(git_transaction *tx, const char *refname) -{ - int error; - transaction_node *node; - - if ((error = find_locked(&node, tx, refname)) < 0) - return error; - - node->remove = true; - node->ref_type = GIT_REFERENCE_DIRECT; /* the id will be ignored */ - - return 0; -} - -static int dup_reflog(git_reflog **out, const git_reflog *in, git_pool *pool) -{ - git_reflog *reflog; - git_reflog_entry *entries; - size_t len, i; - - reflog = git_pool_mallocz(pool, sizeof(git_reflog)); - GIT_ERROR_CHECK_ALLOC(reflog); - - reflog->ref_name = git_pool_strdup(pool, in->ref_name); - GIT_ERROR_CHECK_ALLOC(reflog->ref_name); - - len = in->entries.length; - reflog->entries.length = len; - reflog->entries.contents = git_pool_mallocz(pool, len * sizeof(void *)); - GIT_ERROR_CHECK_ALLOC(reflog->entries.contents); - - entries = git_pool_mallocz(pool, len * sizeof(git_reflog_entry)); - GIT_ERROR_CHECK_ALLOC(entries); - - for (i = 0; i < len; i++) { - const git_reflog_entry *src; - git_reflog_entry *tgt; - - tgt = &entries[i]; - reflog->entries.contents[i] = tgt; - - src = git_vector_get(&in->entries, i); - git_oid_cpy(&tgt->oid_old, &src->oid_old); - git_oid_cpy(&tgt->oid_cur, &src->oid_cur); - - tgt->msg = git_pool_strdup(pool, src->msg); - GIT_ERROR_CHECK_ALLOC(tgt->msg); - - if (git_signature__pdup(&tgt->committer, src->committer, pool) < 0) - return -1; - } - - - *out = reflog; - return 0; -} - -int git_transaction_set_reflog(git_transaction *tx, const char *refname, const git_reflog *reflog) -{ - int error; - transaction_node *node; - - GIT_ASSERT_ARG(tx); - GIT_ASSERT_ARG(refname); - GIT_ASSERT_ARG(reflog); - - if ((error = find_locked(&node, tx, refname)) < 0) - return error; - - if ((error = dup_reflog(&node->reflog, reflog, &tx->pool)) < 0) - return error; - - return 0; -} - -static int update_target(git_refdb *db, transaction_node *node) -{ - git_reference *ref; - int error, update_reflog; - - if (node->ref_type == GIT_REFERENCE_DIRECT) { - ref = git_reference__alloc(node->name, &node->target.id, NULL); - } else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) { - ref = git_reference__alloc_symbolic(node->name, node->target.symbolic); - } else { - abort(); - } - - GIT_ERROR_CHECK_ALLOC(ref); - update_reflog = node->reflog == NULL; - - if (node->remove) { - error = git_refdb_unlock(db, node->payload, 2, false, ref, NULL, NULL); - } else if (node->ref_type == GIT_REFERENCE_DIRECT) { - error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message); - } else if (node->ref_type == GIT_REFERENCE_SYMBOLIC) { - error = git_refdb_unlock(db, node->payload, true, update_reflog, ref, node->sig, node->message); - } else { - abort(); - } - - git_reference_free(ref); - node->committed = true; - - return error; -} - -int git_transaction_commit(git_transaction *tx) -{ - transaction_node *node; - int error = 0; - - GIT_ASSERT_ARG(tx); - - if (tx->type == TRANSACTION_CONFIG) { - error = git_config_unlock(tx->cfg, true); - tx->cfg = NULL; - - return error; - } - - git_strmap_foreach_value(tx->locks, node, { - if (node->reflog) { - if ((error = tx->db->backend->reflog_write(tx->db->backend, node->reflog)) < 0) - return error; - } - - if (node->ref_type == GIT_REFERENCE_INVALID) { - /* ref was locked but not modified */ - if ((error = git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL)) < 0) { - return error; - } - node->committed = true; - } else { - if ((error = update_target(tx->db, node)) < 0) - return error; - } - }); - - return 0; -} - -void git_transaction_free(git_transaction *tx) -{ - transaction_node *node; - git_pool pool; - - if (!tx) - return; - - if (tx->type == TRANSACTION_CONFIG) { - if (tx->cfg) { - git_config_unlock(tx->cfg, false); - git_config_free(tx->cfg); - } - - git__free(tx); - return; - } - - /* start by unlocking the ones we've left hanging, if any */ - git_strmap_foreach_value(tx->locks, node, { - if (node->committed) - continue; - - git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL); - }); - - git_refdb_free(tx->db); - git_strmap_free(tx->locks); - - /* tx is inside the pool, so we need to extract the data */ - memcpy(&pool, &tx->pool, sizeof(git_pool)); - git_pool_clear(&pool); -} diff --git a/src/transaction.h b/src/transaction.h deleted file mode 100644 index 780c06830..000000000 --- a/src/transaction.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_transaction_h__ -#define INCLUDE_transaction_h__ - -#include "common.h" - -int git_transaction_config_new(git_transaction **out, git_config *cfg); - -#endif diff --git a/src/transport.c b/src/transport.c deleted file mode 100644 index 640ccacae..000000000 --- a/src/transport.c +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "git2/types.h" -#include "git2/remote.h" -#include "git2/net.h" -#include "git2/transport.h" -#include "git2/sys/transport.h" -#include "fs_path.h" - -typedef struct transport_definition { - char *prefix; - git_transport_cb fn; - void *param; -} transport_definition; - -static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1, NULL }; -static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0, NULL }; -#ifdef GIT_SSH -static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0, NULL }; -#endif - -static transport_definition local_transport_definition = { "file://", git_transport_local, NULL }; - -static transport_definition transports[] = { - { "git://", git_transport_smart, &git_subtransport_definition }, - { "http://", git_transport_smart, &http_subtransport_definition }, - { "https://", git_transport_smart, &http_subtransport_definition }, - { "file://", git_transport_local, NULL }, -#ifdef GIT_SSH - { "ssh://", git_transport_smart, &ssh_subtransport_definition }, - { "ssh+git://", git_transport_smart, &ssh_subtransport_definition }, - { "git+ssh://", git_transport_smart, &ssh_subtransport_definition }, -#endif - { NULL, 0, 0 } -}; - -static git_vector custom_transports = GIT_VECTOR_INIT; - -#define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1 - -static transport_definition * transport_find_by_url(const char *url) -{ - size_t i = 0; - transport_definition *d; - - /* Find a user transport who wants to deal with this URI */ - git_vector_foreach(&custom_transports, i, d) { - if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) { - return d; - } - } - - /* Find a system transport for this URI */ - for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) { - d = &transports[i]; - - if (strncasecmp(url, d->prefix, strlen(d->prefix)) == 0) { - return d; - } - } - - return NULL; -} - -static int transport_find_fn( - git_transport_cb *out, - const char *url, - void **param) -{ - transport_definition *definition = transport_find_by_url(url); - -#ifdef GIT_WIN32 - /* On Windows, it might not be possible to discern between absolute local - * and ssh paths - first check if this is a valid local path that points - * to a directory and if so assume local path, else assume SSH */ - - /* Check to see if the path points to a file on the local file system */ - if (!definition && git_fs_path_exists(url) && git_fs_path_isdir(url)) - definition = &local_transport_definition; -#endif - - /* For other systems, perform the SSH check first, to avoid going to the - * filesystem if it is not necessary */ - - /* It could be a SSH remote path. Check to see if there's a : */ - if (!definition && strrchr(url, ':')) { - /* re-search transports again with ssh:// as url - * so that we can find a third party ssh transport */ - definition = transport_find_by_url("ssh://"); - } - -#ifndef GIT_WIN32 - /* Check to see if the path points to a file on the local file system */ - if (!definition && git_fs_path_exists(url) && git_fs_path_isdir(url)) - definition = &local_transport_definition; -#endif - - if (!definition) - return GIT_ENOTFOUND; - - *out = definition->fn; - *param = definition->param; - - return 0; -} - -/************** - * Public API * - **************/ - -int git_transport_new(git_transport **out, git_remote *owner, const char *url) -{ - git_transport_cb fn; - git_transport *transport; - void *param; - int error; - - if ((error = transport_find_fn(&fn, url, ¶m)) == GIT_ENOTFOUND) { - git_error_set(GIT_ERROR_NET, "unsupported URL protocol"); - return -1; - } else if (error < 0) - return error; - - if ((error = fn(&transport, owner, param)) < 0) - return error; - - GIT_ERROR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport"); - - *out = transport; - - return 0; -} - -int git_transport_register( - const char *scheme, - git_transport_cb cb, - void *param) -{ - git_str prefix = GIT_STR_INIT; - transport_definition *d, *definition = NULL; - size_t i; - int error = 0; - - GIT_ASSERT_ARG(scheme); - GIT_ASSERT_ARG(cb); - - if ((error = git_str_printf(&prefix, "%s://", scheme)) < 0) - goto on_error; - - git_vector_foreach(&custom_transports, i, d) { - if (strcasecmp(d->prefix, prefix.ptr) == 0) { - error = GIT_EEXISTS; - goto on_error; - } - } - - definition = git__calloc(1, sizeof(transport_definition)); - GIT_ERROR_CHECK_ALLOC(definition); - - definition->prefix = git_str_detach(&prefix); - definition->fn = cb; - definition->param = param; - - if (git_vector_insert(&custom_transports, definition) < 0) - goto on_error; - - return 0; - -on_error: - git_str_dispose(&prefix); - git__free(definition); - return error; -} - -int git_transport_unregister(const char *scheme) -{ - git_str prefix = GIT_STR_INIT; - transport_definition *d; - size_t i; - int error = 0; - - GIT_ASSERT_ARG(scheme); - - if ((error = git_str_printf(&prefix, "%s://", scheme)) < 0) - goto done; - - git_vector_foreach(&custom_transports, i, d) { - if (strcasecmp(d->prefix, prefix.ptr) == 0) { - if ((error = git_vector_remove(&custom_transports, i)) < 0) - goto done; - - git__free(d->prefix); - git__free(d); - - if (!custom_transports.length) - git_vector_free(&custom_transports); - - error = 0; - goto done; - } - } - - error = GIT_ENOTFOUND; - -done: - git_str_dispose(&prefix); - return error; -} - -int git_transport_init(git_transport *opts, unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE( - opts, version, git_transport, GIT_TRANSPORT_INIT); - return 0; -} diff --git a/src/transports/auth.c b/src/transports/auth.c deleted file mode 100644 index 90b6b124f..000000000 --- a/src/transports/auth.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "auth.h" - -#include "git2/sys/credential.h" - -static int basic_next_token( - git_str *out, - git_http_auth_context *ctx, - git_credential *c) -{ - git_credential_userpass_plaintext *cred; - git_str raw = GIT_STR_INIT; - int error = GIT_EAUTH; - - GIT_UNUSED(ctx); - - if (c->credtype != GIT_CREDENTIAL_USERPASS_PLAINTEXT) { - git_error_set(GIT_ERROR_INVALID, "invalid credential type for basic auth"); - goto on_error; - } - - cred = (git_credential_userpass_plaintext *)c; - - git_str_printf(&raw, "%s:%s", cred->username, cred->password); - - if (git_str_oom(&raw) || - git_str_puts(out, "Basic ") < 0 || - git_str_encode_base64(out, git_str_cstr(&raw), raw.size) < 0) - goto on_error; - - error = 0; - -on_error: - if (raw.size) - git__memzero(raw.ptr, raw.size); - - git_str_dispose(&raw); - return error; -} - -static git_http_auth_context basic_context = { - GIT_HTTP_AUTH_BASIC, - GIT_CREDENTIAL_USERPASS_PLAINTEXT, - 0, - NULL, - basic_next_token, - NULL, - NULL -}; - -int git_http_auth_basic( - git_http_auth_context **out, const git_net_url *url) -{ - GIT_UNUSED(url); - - *out = &basic_context; - return 0; -} - -int git_http_auth_dummy( - git_http_auth_context **out, const git_net_url *url) -{ - GIT_UNUSED(url); - - *out = NULL; - return GIT_PASSTHROUGH; -} - diff --git a/src/transports/auth.h b/src/transports/auth.h deleted file mode 100644 index 64680cc53..000000000 --- a/src/transports/auth.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_transports_auth_h__ -#define INCLUDE_transports_auth_h__ - -#include "common.h" - -#include "netops.h" - -typedef enum { - GIT_HTTP_AUTH_BASIC = 1, - GIT_HTTP_AUTH_NEGOTIATE = 2, - GIT_HTTP_AUTH_NTLM = 4 -} git_http_auth_t; - -typedef struct git_http_auth_context git_http_auth_context; - -struct git_http_auth_context { - /** Type of scheme */ - git_http_auth_t type; - - /** Supported credentials */ - git_credential_t credtypes; - - /** Connection affinity or request affinity */ - unsigned connection_affinity : 1; - - /** Sets the challenge on the authentication context */ - int (*set_challenge)(git_http_auth_context *ctx, const char *challenge); - - /** Gets the next authentication token from the context */ - int (*next_token)(git_str *out, git_http_auth_context *ctx, git_credential *cred); - - /** Examines if all tokens have been presented. */ - int (*is_complete)(git_http_auth_context *ctx); - - /** Frees the authentication context */ - void (*free)(git_http_auth_context *ctx); -}; - -typedef struct { - /** Type of scheme */ - git_http_auth_t type; - - /** Name of the scheme (as used in the Authorization header) */ - const char *name; - - /** Credential types this scheme supports */ - git_credential_t credtypes; - - /** Function to initialize an authentication context */ - int (*init_context)( - git_http_auth_context **out, - const git_net_url *url); -} git_http_auth_scheme; - -int git_http_auth_dummy( - git_http_auth_context **out, - const git_net_url *url); - -int git_http_auth_basic( - git_http_auth_context **out, - const git_net_url *url); - -#endif diff --git a/src/transports/auth_negotiate.c b/src/transports/auth_negotiate.c deleted file mode 100644 index 6380504be..000000000 --- a/src/transports/auth_negotiate.c +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "auth_negotiate.h" - -#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) - -#include "git2.h" -#include "auth.h" -#include "git2/sys/credential.h" - -#ifdef GIT_GSSFRAMEWORK -#import -#elif defined(GIT_GSSAPI) -#include -#include -#endif - -static gss_OID_desc negotiate_oid_spnego = - { 6, (void *) "\x2b\x06\x01\x05\x05\x02" }; -static gss_OID_desc negotiate_oid_krb5 = - { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; - -static gss_OID negotiate_oids[] = - { &negotiate_oid_spnego, &negotiate_oid_krb5, NULL }; - -typedef struct { - git_http_auth_context parent; - unsigned configured : 1, - complete : 1; - git_str target; - char *challenge; - gss_ctx_id_t gss_context; - gss_OID oid; -} http_auth_negotiate_context; - -static void negotiate_err_set( - OM_uint32 status_major, - OM_uint32 status_minor, - const char *message) -{ - gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER; - OM_uint32 status_display, context = 0; - - if (gss_display_status(&status_display, status_major, GSS_C_GSS_CODE, - GSS_C_NO_OID, &context, &buffer) == GSS_S_COMPLETE) { - git_error_set(GIT_ERROR_NET, "%s: %.*s (%d.%d)", - message, (int)buffer.length, (const char *)buffer.value, - status_major, status_minor); - gss_release_buffer(&status_minor, &buffer); - } else { - git_error_set(GIT_ERROR_NET, "%s: unknown negotiate error (%d.%d)", - message, status_major, status_minor); - } -} - -static int negotiate_set_challenge( - git_http_auth_context *c, - const char *challenge) -{ - http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; - - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(challenge); - GIT_ASSERT(ctx->configured); - - git__free(ctx->challenge); - - ctx->challenge = git__strdup(challenge); - GIT_ERROR_CHECK_ALLOC(ctx->challenge); - - return 0; -} - -static void negotiate_context_dispose(http_auth_negotiate_context *ctx) -{ - OM_uint32 status_minor; - - if (ctx->gss_context != GSS_C_NO_CONTEXT) { - gss_delete_sec_context( - &status_minor, &ctx->gss_context, GSS_C_NO_BUFFER); - ctx->gss_context = GSS_C_NO_CONTEXT; - } - - git_str_dispose(&ctx->target); - - git__free(ctx->challenge); - ctx->challenge = NULL; -} - -static int negotiate_next_token( - git_str *buf, - git_http_auth_context *c, - git_credential *cred) -{ - http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; - OM_uint32 status_major, status_minor; - gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER, - input_token = GSS_C_EMPTY_BUFFER, - output_token = GSS_C_EMPTY_BUFFER; - gss_buffer_t input_token_ptr = GSS_C_NO_BUFFER; - git_str input_buf = GIT_STR_INIT; - gss_name_t server = NULL; - gss_OID mech; - size_t challenge_len; - int error = 0; - - GIT_ASSERT_ARG(buf); - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(cred); - - GIT_ASSERT(ctx->configured); - GIT_ASSERT(cred->credtype == GIT_CREDENTIAL_DEFAULT); - - if (ctx->complete) - return 0; - - target_buffer.value = (void *)ctx->target.ptr; - target_buffer.length = ctx->target.size; - - status_major = gss_import_name(&status_minor, &target_buffer, - GSS_C_NT_HOSTBASED_SERVICE, &server); - - if (GSS_ERROR(status_major)) { - negotiate_err_set(status_major, status_minor, - "could not parse principal"); - error = -1; - goto done; - } - - challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0; - - if (challenge_len < 9 || memcmp(ctx->challenge, "Negotiate", 9) != 0) { - git_error_set(GIT_ERROR_NET, "server did not request negotiate"); - error = -1; - goto done; - } - - if (challenge_len > 9) { - if (git_str_decode_base64(&input_buf, - ctx->challenge + 10, challenge_len - 10) < 0) { - git_error_set(GIT_ERROR_NET, "invalid negotiate challenge from server"); - error = -1; - goto done; - } - - input_token.value = input_buf.ptr; - input_token.length = input_buf.size; - input_token_ptr = &input_token; - } else if (ctx->gss_context != GSS_C_NO_CONTEXT) { - negotiate_context_dispose(ctx); - } - - mech = &negotiate_oid_spnego; - - status_major = gss_init_sec_context( - &status_minor, - GSS_C_NO_CREDENTIAL, - &ctx->gss_context, - server, - mech, - GSS_C_DELEG_FLAG | GSS_C_MUTUAL_FLAG, - GSS_C_INDEFINITE, - GSS_C_NO_CHANNEL_BINDINGS, - input_token_ptr, - NULL, - &output_token, - NULL, - NULL); - - if (GSS_ERROR(status_major)) { - negotiate_err_set(status_major, status_minor, "negotiate failure"); - error = -1; - goto done; - } - - /* This message merely told us auth was complete; we do not respond. */ - if (status_major == GSS_S_COMPLETE) { - negotiate_context_dispose(ctx); - ctx->complete = 1; - goto done; - } - - if (output_token.length == 0) { - git_error_set(GIT_ERROR_NET, "GSSAPI did not return token"); - error = -1; - goto done; - } - - git_str_puts(buf, "Negotiate "); - git_str_encode_base64(buf, output_token.value, output_token.length); - - if (git_str_oom(buf)) - error = -1; - -done: - gss_release_name(&status_minor, &server); - gss_release_buffer(&status_minor, (gss_buffer_t) &output_token); - git_str_dispose(&input_buf); - return error; -} - -static int negotiate_is_complete(git_http_auth_context *c) -{ - http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; - - GIT_ASSERT_ARG(ctx); - - return (ctx->complete == 1); -} - -static void negotiate_context_free(git_http_auth_context *c) -{ - http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; - - negotiate_context_dispose(ctx); - - ctx->configured = 0; - ctx->complete = 0; - ctx->oid = NULL; - - git__free(ctx); -} - -static int negotiate_init_context( - http_auth_negotiate_context *ctx, - const git_net_url *url) -{ - OM_uint32 status_major, status_minor; - gss_OID item, *oid; - gss_OID_set mechanism_list; - size_t i; - - /* Query supported mechanisms looking for SPNEGO) */ - status_major = gss_indicate_mechs(&status_minor, &mechanism_list); - - if (GSS_ERROR(status_major)) { - negotiate_err_set(status_major, status_minor, - "could not query mechanisms"); - return -1; - } - - if (mechanism_list) { - for (oid = negotiate_oids; *oid; oid++) { - for (i = 0; i < mechanism_list->count; i++) { - item = &mechanism_list->elements[i]; - - if (item->length == (*oid)->length && - memcmp(item->elements, (*oid)->elements, item->length) == 0) { - ctx->oid = *oid; - break; - } - - } - - if (ctx->oid) - break; - } - } - - gss_release_oid_set(&status_minor, &mechanism_list); - - if (!ctx->oid) { - git_error_set(GIT_ERROR_NET, "negotiate authentication is not supported"); - return GIT_EAUTH; - } - - git_str_puts(&ctx->target, "HTTP@"); - git_str_puts(&ctx->target, url->host); - - if (git_str_oom(&ctx->target)) - return -1; - - ctx->gss_context = GSS_C_NO_CONTEXT; - ctx->configured = 1; - - return 0; -} - -int git_http_auth_negotiate( - git_http_auth_context **out, - const git_net_url *url) -{ - http_auth_negotiate_context *ctx; - - *out = NULL; - - ctx = git__calloc(1, sizeof(http_auth_negotiate_context)); - GIT_ERROR_CHECK_ALLOC(ctx); - - if (negotiate_init_context(ctx, url) < 0) { - git__free(ctx); - return -1; - } - - ctx->parent.type = GIT_HTTP_AUTH_NEGOTIATE; - ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT; - ctx->parent.connection_affinity = 1; - ctx->parent.set_challenge = negotiate_set_challenge; - ctx->parent.next_token = negotiate_next_token; - ctx->parent.is_complete = negotiate_is_complete; - ctx->parent.free = negotiate_context_free; - - *out = (git_http_auth_context *)ctx; - - return 0; -} - -#endif /* GIT_GSSAPI */ - diff --git a/src/transports/auth_negotiate.h b/src/transports/auth_negotiate.h deleted file mode 100644 index 34aff295b..000000000 --- a/src/transports/auth_negotiate.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_transports_auth_negotiate_h__ -#define INCLUDE_transports_auth_negotiate_h__ - -#include "common.h" -#include "git2.h" -#include "auth.h" - -#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) - -extern int git_http_auth_negotiate( - git_http_auth_context **out, - const git_net_url *url); - -#else - -#define git_http_auth_negotiate git_http_auth_dummy - -#endif /* GIT_GSSAPI */ - -#endif diff --git a/src/transports/auth_ntlm.c b/src/transports/auth_ntlm.c deleted file mode 100644 index f49ce101a..000000000 --- a/src/transports/auth_ntlm.c +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "auth_ntlm.h" - -#include "common.h" -#include "str.h" -#include "auth.h" -#include "git2/sys/credential.h" - -#ifdef GIT_NTLM - -#include "ntlmclient.h" - -typedef struct { - git_http_auth_context parent; - ntlm_client *ntlm; - char *challenge; - bool complete; -} http_auth_ntlm_context; - -static int ntlm_set_challenge( - git_http_auth_context *c, - const char *challenge) -{ - http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; - - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(challenge); - - git__free(ctx->challenge); - - ctx->challenge = git__strdup(challenge); - GIT_ERROR_CHECK_ALLOC(ctx->challenge); - - return 0; -} - -static int ntlm_set_credentials(http_auth_ntlm_context *ctx, git_credential *_cred) -{ - git_credential_userpass_plaintext *cred; - const char *sep, *username; - char *domain = NULL, *domainuser = NULL; - int error = 0; - - GIT_ASSERT(_cred->credtype == GIT_CREDENTIAL_USERPASS_PLAINTEXT); - cred = (git_credential_userpass_plaintext *)_cred; - - if ((sep = strchr(cred->username, '\\')) != NULL) { - domain = git__strndup(cred->username, (sep - cred->username)); - GIT_ERROR_CHECK_ALLOC(domain); - - domainuser = git__strdup(sep + 1); - GIT_ERROR_CHECK_ALLOC(domainuser); - - username = domainuser; - } else { - username = cred->username; - } - - if (ntlm_client_set_credentials(ctx->ntlm, - username, domain, cred->password) < 0) { - git_error_set(GIT_ERROR_NET, "could not set credentials: %s", - ntlm_client_errmsg(ctx->ntlm)); - error = -1; - goto done; - } - -done: - git__free(domain); - git__free(domainuser); - return error; -} - -static int ntlm_next_token( - git_str *buf, - git_http_auth_context *c, - git_credential *cred) -{ - http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; - git_str input_buf = GIT_STR_INIT; - const unsigned char *msg; - size_t challenge_len, msg_len; - int error = GIT_EAUTH; - - GIT_ASSERT_ARG(buf); - GIT_ASSERT_ARG(ctx); - - GIT_ASSERT(ctx->ntlm); - - challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0; - - if (ctx->complete) - ntlm_client_reset(ctx->ntlm); - - /* - * Set us complete now since it's the default case; the one - * incomplete case (successfully created a client request) - * will explicitly set that it requires a second step. - */ - ctx->complete = true; - - if (cred && ntlm_set_credentials(ctx, cred) != 0) - goto done; - - if (challenge_len < 4) { - git_error_set(GIT_ERROR_NET, "no ntlm challenge sent from server"); - goto done; - } else if (challenge_len == 4) { - if (memcmp(ctx->challenge, "NTLM", 4) != 0) { - git_error_set(GIT_ERROR_NET, "server did not request NTLM"); - goto done; - } - - if (ntlm_client_negotiate(&msg, &msg_len, ctx->ntlm) != 0) { - git_error_set(GIT_ERROR_NET, "ntlm authentication failed: %s", - ntlm_client_errmsg(ctx->ntlm)); - goto done; - } - - ctx->complete = false; - } else { - if (memcmp(ctx->challenge, "NTLM ", 5) != 0) { - git_error_set(GIT_ERROR_NET, "challenge from server was not NTLM"); - goto done; - } - - if (git_str_decode_base64(&input_buf, - ctx->challenge + 5, challenge_len - 5) < 0) { - git_error_set(GIT_ERROR_NET, "invalid NTLM challenge from server"); - goto done; - } - - if (ntlm_client_set_challenge(ctx->ntlm, - (const unsigned char *)input_buf.ptr, input_buf.size) != 0) { - git_error_set(GIT_ERROR_NET, "ntlm challenge failed: %s", - ntlm_client_errmsg(ctx->ntlm)); - goto done; - } - - if (ntlm_client_response(&msg, &msg_len, ctx->ntlm) != 0) { - git_error_set(GIT_ERROR_NET, "ntlm authentication failed: %s", - ntlm_client_errmsg(ctx->ntlm)); - goto done; - } - } - - git_str_puts(buf, "NTLM "); - git_str_encode_base64(buf, (const char *)msg, msg_len); - - if (git_str_oom(buf)) - goto done; - - error = 0; - -done: - git_str_dispose(&input_buf); - return error; -} - -static int ntlm_is_complete(git_http_auth_context *c) -{ - http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; - - GIT_ASSERT_ARG(ctx); - return (ctx->complete == true); -} - -static void ntlm_context_free(git_http_auth_context *c) -{ - http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; - - ntlm_client_free(ctx->ntlm); - git__free(ctx->challenge); - git__free(ctx); -} - -static int ntlm_init_context( - http_auth_ntlm_context *ctx, - const git_net_url *url) -{ - GIT_UNUSED(url); - - if ((ctx->ntlm = ntlm_client_init(NTLM_CLIENT_DEFAULTS)) == NULL) { - git_error_set_oom(); - return -1; - } - - return 0; -} - -int git_http_auth_ntlm( - git_http_auth_context **out, - const git_net_url *url) -{ - http_auth_ntlm_context *ctx; - - GIT_UNUSED(url); - - *out = NULL; - - ctx = git__calloc(1, sizeof(http_auth_ntlm_context)); - GIT_ERROR_CHECK_ALLOC(ctx); - - if (ntlm_init_context(ctx, url) < 0) { - git__free(ctx); - return -1; - } - - ctx->parent.type = GIT_HTTP_AUTH_NTLM; - ctx->parent.credtypes = GIT_CREDENTIAL_USERPASS_PLAINTEXT; - ctx->parent.connection_affinity = 1; - ctx->parent.set_challenge = ntlm_set_challenge; - ctx->parent.next_token = ntlm_next_token; - ctx->parent.is_complete = ntlm_is_complete; - ctx->parent.free = ntlm_context_free; - - *out = (git_http_auth_context *)ctx; - - return 0; -} - -#endif /* GIT_NTLM */ diff --git a/src/transports/auth_ntlm.h b/src/transports/auth_ntlm.h deleted file mode 100644 index 40689498c..000000000 --- a/src/transports/auth_ntlm.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_transports_auth_ntlm_h__ -#define INCLUDE_transports_auth_ntlm_h__ - -#include "auth.h" - -/* NTLM requires a full request/challenge/response */ -#define GIT_AUTH_STEPS_NTLM 2 - -#ifdef GIT_NTLM - -#if defined(GIT_OPENSSL) -# define CRYPT_OPENSSL -#elif defined(GIT_MBEDTLS) -# define CRYPT_MBEDTLS -#elif defined(GIT_SECURE_TRANSPORT) -# define CRYPT_COMMONCRYPTO -#endif - -extern int git_http_auth_ntlm( - git_http_auth_context **out, - const git_net_url *url); - -#else - -#define git_http_auth_ntlm git_http_auth_dummy - -#endif /* GIT_NTLM */ - -#endif - diff --git a/src/transports/credential.c b/src/transports/credential.c deleted file mode 100644 index 6e00b0282..000000000 --- a/src/transports/credential.c +++ /dev/null @@ -1,486 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "git2/credential.h" -#include "git2/sys/credential.h" -#include "git2/credential_helpers.h" - -static int git_credential_ssh_key_type_new( - git_credential **cred, - const char *username, - const char *publickey, - const char *privatekey, - const char *passphrase, - git_credential_t credtype); - -int git_credential_has_username(git_credential *cred) -{ - if (cred->credtype == GIT_CREDENTIAL_DEFAULT) - return 0; - - return 1; -} - -const char *git_credential_get_username(git_credential *cred) -{ - switch (cred->credtype) { - case GIT_CREDENTIAL_USERNAME: - { - git_credential_username *c = (git_credential_username *) cred; - return c->username; - } - case GIT_CREDENTIAL_USERPASS_PLAINTEXT: - { - git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *) cred; - return c->username; - } - case GIT_CREDENTIAL_SSH_KEY: - case GIT_CREDENTIAL_SSH_MEMORY: - { - git_credential_ssh_key *c = (git_credential_ssh_key *) cred; - return c->username; - } - case GIT_CREDENTIAL_SSH_CUSTOM: - { - git_credential_ssh_custom *c = (git_credential_ssh_custom *) cred; - return c->username; - } - case GIT_CREDENTIAL_SSH_INTERACTIVE: - { - git_credential_ssh_interactive *c = (git_credential_ssh_interactive *) cred; - return c->username; - } - - default: - return NULL; - } -} - -static void plaintext_free(struct git_credential *cred) -{ - git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred; - - git__free(c->username); - - /* Zero the memory which previously held the password */ - if (c->password) { - size_t pass_len = strlen(c->password); - git__memzero(c->password, pass_len); - git__free(c->password); - } - - git__free(c); -} - -int git_credential_userpass_plaintext_new( - git_credential **cred, - const char *username, - const char *password) -{ - git_credential_userpass_plaintext *c; - - GIT_ASSERT_ARG(cred); - GIT_ASSERT_ARG(username); - GIT_ASSERT_ARG(password); - - c = git__malloc(sizeof(git_credential_userpass_plaintext)); - GIT_ERROR_CHECK_ALLOC(c); - - c->parent.credtype = GIT_CREDENTIAL_USERPASS_PLAINTEXT; - c->parent.free = plaintext_free; - c->username = git__strdup(username); - - if (!c->username) { - git__free(c); - return -1; - } - - c->password = git__strdup(password); - - if (!c->password) { - git__free(c->username); - git__free(c); - return -1; - } - - *cred = &c->parent; - return 0; -} - -static void ssh_key_free(struct git_credential *cred) -{ - git_credential_ssh_key *c = - (git_credential_ssh_key *)cred; - - git__free(c->username); - - if (c->privatekey) { - /* Zero the memory which previously held the private key */ - size_t key_len = strlen(c->privatekey); - git__memzero(c->privatekey, key_len); - git__free(c->privatekey); - } - - if (c->passphrase) { - /* Zero the memory which previously held the passphrase */ - size_t pass_len = strlen(c->passphrase); - git__memzero(c->passphrase, pass_len); - git__free(c->passphrase); - } - - if (c->publickey) { - /* Zero the memory which previously held the public key */ - size_t key_len = strlen(c->publickey); - git__memzero(c->publickey, key_len); - git__free(c->publickey); - } - - git__free(c); -} - -static void ssh_interactive_free(struct git_credential *cred) -{ - git_credential_ssh_interactive *c = (git_credential_ssh_interactive *)cred; - - git__free(c->username); - - git__free(c); -} - -static void ssh_custom_free(struct git_credential *cred) -{ - git_credential_ssh_custom *c = (git_credential_ssh_custom *)cred; - - git__free(c->username); - - if (c->publickey) { - /* Zero the memory which previously held the publickey */ - size_t key_len = strlen(c->publickey); - git__memzero(c->publickey, key_len); - git__free(c->publickey); - } - - git__free(c); -} - -static void default_free(struct git_credential *cred) -{ - git_credential_default *c = (git_credential_default *)cred; - - git__free(c); -} - -static void username_free(struct git_credential *cred) -{ - git__free(cred); -} - -int git_credential_ssh_key_new( - git_credential **cred, - const char *username, - const char *publickey, - const char *privatekey, - const char *passphrase) -{ - return git_credential_ssh_key_type_new( - cred, - username, - publickey, - privatekey, - passphrase, - GIT_CREDENTIAL_SSH_KEY); -} - -int git_credential_ssh_key_memory_new( - git_credential **cred, - const char *username, - const char *publickey, - const char *privatekey, - const char *passphrase) -{ -#ifdef GIT_SSH_MEMORY_CREDENTIALS - return git_credential_ssh_key_type_new( - cred, - username, - publickey, - privatekey, - passphrase, - GIT_CREDENTIAL_SSH_MEMORY); -#else - GIT_UNUSED(cred); - GIT_UNUSED(username); - GIT_UNUSED(publickey); - GIT_UNUSED(privatekey); - GIT_UNUSED(passphrase); - - git_error_set(GIT_ERROR_INVALID, - "this version of libgit2 was not built with ssh memory credentials."); - return -1; -#endif -} - -static int git_credential_ssh_key_type_new( - git_credential **cred, - const char *username, - const char *publickey, - const char *privatekey, - const char *passphrase, - git_credential_t credtype) -{ - git_credential_ssh_key *c; - - GIT_ASSERT_ARG(username); - GIT_ASSERT_ARG(cred); - GIT_ASSERT_ARG(privatekey); - - c = git__calloc(1, sizeof(git_credential_ssh_key)); - GIT_ERROR_CHECK_ALLOC(c); - - c->parent.credtype = credtype; - c->parent.free = ssh_key_free; - - c->username = git__strdup(username); - GIT_ERROR_CHECK_ALLOC(c->username); - - c->privatekey = git__strdup(privatekey); - GIT_ERROR_CHECK_ALLOC(c->privatekey); - - if (publickey) { - c->publickey = git__strdup(publickey); - GIT_ERROR_CHECK_ALLOC(c->publickey); - } - - if (passphrase) { - c->passphrase = git__strdup(passphrase); - GIT_ERROR_CHECK_ALLOC(c->passphrase); - } - - *cred = &c->parent; - return 0; -} - -int git_credential_ssh_interactive_new( - git_credential **out, - const char *username, - git_credential_ssh_interactive_cb prompt_callback, - void *payload) -{ - git_credential_ssh_interactive *c; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(username); - GIT_ASSERT_ARG(prompt_callback); - - c = git__calloc(1, sizeof(git_credential_ssh_interactive)); - GIT_ERROR_CHECK_ALLOC(c); - - c->parent.credtype = GIT_CREDENTIAL_SSH_INTERACTIVE; - c->parent.free = ssh_interactive_free; - - c->username = git__strdup(username); - GIT_ERROR_CHECK_ALLOC(c->username); - - c->prompt_callback = prompt_callback; - c->payload = payload; - - *out = &c->parent; - return 0; -} - -int git_credential_ssh_key_from_agent(git_credential **cred, const char *username) { - git_credential_ssh_key *c; - - GIT_ASSERT_ARG(username); - GIT_ASSERT_ARG(cred); - - c = git__calloc(1, sizeof(git_credential_ssh_key)); - GIT_ERROR_CHECK_ALLOC(c); - - c->parent.credtype = GIT_CREDENTIAL_SSH_KEY; - c->parent.free = ssh_key_free; - - c->username = git__strdup(username); - GIT_ERROR_CHECK_ALLOC(c->username); - - c->privatekey = NULL; - - *cred = &c->parent; - return 0; -} - -int git_credential_ssh_custom_new( - git_credential **cred, - const char *username, - const char *publickey, - size_t publickey_len, - git_credential_sign_cb sign_callback, - void *payload) -{ - git_credential_ssh_custom *c; - - GIT_ASSERT_ARG(username); - GIT_ASSERT_ARG(cred); - - c = git__calloc(1, sizeof(git_credential_ssh_custom)); - GIT_ERROR_CHECK_ALLOC(c); - - c->parent.credtype = GIT_CREDENTIAL_SSH_CUSTOM; - c->parent.free = ssh_custom_free; - - c->username = git__strdup(username); - GIT_ERROR_CHECK_ALLOC(c->username); - - if (publickey_len > 0) { - c->publickey = git__malloc(publickey_len); - GIT_ERROR_CHECK_ALLOC(c->publickey); - - memcpy(c->publickey, publickey, publickey_len); - } - - c->publickey_len = publickey_len; - c->sign_callback = sign_callback; - c->payload = payload; - - *cred = &c->parent; - return 0; -} - -int git_credential_default_new(git_credential **cred) -{ - git_credential_default *c; - - GIT_ASSERT_ARG(cred); - - c = git__calloc(1, sizeof(git_credential_default)); - GIT_ERROR_CHECK_ALLOC(c); - - c->credtype = GIT_CREDENTIAL_DEFAULT; - c->free = default_free; - - *cred = c; - return 0; -} - -int git_credential_username_new(git_credential **cred, const char *username) -{ - git_credential_username *c; - size_t len, allocsize; - - GIT_ASSERT_ARG(cred); - - len = strlen(username); - - GIT_ERROR_CHECK_ALLOC_ADD(&allocsize, sizeof(git_credential_username), len); - GIT_ERROR_CHECK_ALLOC_ADD(&allocsize, allocsize, 1); - c = git__malloc(allocsize); - GIT_ERROR_CHECK_ALLOC(c); - - c->parent.credtype = GIT_CREDENTIAL_USERNAME; - c->parent.free = username_free; - memcpy(c->username, username, len + 1); - - *cred = (git_credential *) c; - return 0; -} - -void git_credential_free(git_credential *cred) -{ - if (!cred) - return; - - cred->free(cred); -} - -/* Deprecated credential functions */ - -#ifndef GIT_DEPRECATE_HARD -int git_cred_has_username(git_credential *cred) -{ - return git_credential_has_username(cred); -} - -const char *git_cred_get_username(git_credential *cred) -{ - return git_credential_get_username(cred); -} - -int git_cred_userpass_plaintext_new( - git_credential **out, - const char *username, - const char *password) -{ - return git_credential_userpass_plaintext_new(out,username, password); -} - -int git_cred_default_new(git_credential **out) -{ - return git_credential_default_new(out); -} - -int git_cred_username_new(git_credential **out, const char *username) -{ - return git_credential_username_new(out, username); -} - -int git_cred_ssh_key_new( - git_credential **out, - const char *username, - const char *publickey, - const char *privatekey, - const char *passphrase) -{ - return git_credential_ssh_key_new(out, username, - publickey, privatekey, passphrase); -} - -int git_cred_ssh_key_memory_new( - git_credential **out, - const char *username, - const char *publickey, - const char *privatekey, - const char *passphrase) -{ - return git_credential_ssh_key_memory_new(out, username, - publickey, privatekey, passphrase); -} - -int git_cred_ssh_interactive_new( - git_credential **out, - const char *username, - git_credential_ssh_interactive_cb prompt_callback, - void *payload) -{ - return git_credential_ssh_interactive_new(out, username, - prompt_callback, payload); -} - -int git_cred_ssh_key_from_agent( - git_credential **out, - const char *username) -{ - return git_credential_ssh_key_from_agent(out, username); -} - -int git_cred_ssh_custom_new( - git_credential **out, - const char *username, - const char *publickey, - size_t publickey_len, - git_credential_sign_cb sign_callback, - void *payload) -{ - return git_credential_ssh_custom_new(out, username, - publickey, publickey_len, sign_callback, payload); -} - -void git_cred_free(git_credential *cred) -{ - git_credential_free(cred); -} -#endif diff --git a/src/transports/credential_helpers.c b/src/transports/credential_helpers.c deleted file mode 100644 index 6d34a4e97..000000000 --- a/src/transports/credential_helpers.c +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "git2/credential_helpers.h" - -int git_credential_userpass( - git_credential **cred, - const char *url, - const char *user_from_url, - unsigned int allowed_types, - void *payload) -{ - git_credential_userpass_payload *userpass = (git_credential_userpass_payload*)payload; - const char *effective_username = NULL; - - GIT_UNUSED(url); - - if (!userpass || !userpass->password) return -1; - - /* Username resolution: a username can be passed with the URL, the - * credentials payload, or both. Here's what we do. Note that if we get - * this far, we know that any password the url may contain has already - * failed at least once, so we ignore it. - * - * | Payload | URL | Used | - * +-------------+----------+-----------+ - * | yes | no | payload | - * | yes | yes | payload | - * | no | yes | url | - * | no | no | FAIL | - */ - if (userpass->username) - effective_username = userpass->username; - else if (user_from_url) - effective_username = user_from_url; - else - return -1; - - if (GIT_CREDENTIAL_USERNAME & allowed_types) - return git_credential_username_new(cred, effective_username); - - if ((GIT_CREDENTIAL_USERPASS_PLAINTEXT & allowed_types) == 0 || - git_credential_userpass_plaintext_new(cred, effective_username, userpass->password) < 0) - return -1; - - return 0; -} - -/* Deprecated credential functions */ - -#ifndef GIT_DEPRECATE_HARD -int git_cred_userpass( - git_credential **out, - const char *url, - const char *user_from_url, - unsigned int allowed_types, - void *payload) -{ - return git_credential_userpass(out, url, user_from_url, - allowed_types, payload); -} -#endif diff --git a/src/transports/git.c b/src/transports/git.c deleted file mode 100644 index 591e2ab03..000000000 --- a/src/transports/git.c +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "netops.h" -#include "stream.h" -#include "streams/socket.h" -#include "git2/sys/transport.h" - -#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport) - -static const char prefix_git[] = "git://"; -static const char cmd_uploadpack[] = "git-upload-pack"; -static const char cmd_receivepack[] = "git-receive-pack"; - -typedef struct { - git_smart_subtransport_stream parent; - git_stream *io; - const char *cmd; - char *url; - unsigned sent_command : 1; -} git_proto_stream; - -typedef struct { - git_smart_subtransport parent; - git_transport *owner; - git_proto_stream *current_stream; -} git_subtransport; - -/* - * Create a git protocol request. - * - * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0 - */ -static int gen_proto(git_str *request, const char *cmd, const char *url) -{ - char *delim, *repo; - char host[] = "host="; - size_t len; - - delim = strchr(url, '/'); - if (delim == NULL) { - git_error_set(GIT_ERROR_NET, "malformed URL"); - return -1; - } - - repo = delim; - if (repo[1] == '~') - ++repo; - - delim = strchr(url, ':'); - if (delim == NULL) - delim = strchr(url, '/'); - - len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1; - - git_str_grow(request, len); - git_str_printf(request, "%04x%s %s%c%s", - (unsigned int)(len & 0x0FFFF), cmd, repo, 0, host); - git_str_put(request, url, delim - url); - git_str_putc(request, '\0'); - - if (git_str_oom(request)) - return -1; - - return 0; -} - -static int send_command(git_proto_stream *s) -{ - git_str request = GIT_STR_INIT; - int error; - - if ((error = gen_proto(&request, s->cmd, s->url)) < 0) - goto cleanup; - - if ((error = git_stream__write_full(s->io, request.ptr, request.size, 0)) < 0) - goto cleanup; - - s->sent_command = 1; - -cleanup: - git_str_dispose(&request); - return error; -} - -static int git_proto_stream_read( - git_smart_subtransport_stream *stream, - char *buffer, - size_t buf_size, - size_t *bytes_read) -{ - int error; - git_proto_stream *s = (git_proto_stream *)stream; - gitno_buffer buf; - - *bytes_read = 0; - - if (!s->sent_command && (error = send_command(s)) < 0) - return error; - - gitno_buffer_setup_fromstream(s->io, &buf, buffer, buf_size); - - if ((error = gitno_recv(&buf)) < 0) - return error; - - *bytes_read = buf.offset; - - return 0; -} - -static int git_proto_stream_write( - git_smart_subtransport_stream *stream, - const char *buffer, - size_t len) -{ - git_proto_stream *s = (git_proto_stream *)stream; - int error; - - if (!s->sent_command && (error = send_command(s)) < 0) - return error; - - return git_stream__write_full(s->io, buffer, len, 0); -} - -static void git_proto_stream_free(git_smart_subtransport_stream *stream) -{ - git_proto_stream *s; - git_subtransport *t; - - if (!stream) - return; - - s = (git_proto_stream *)stream; - t = OWNING_SUBTRANSPORT(s); - - t->current_stream = NULL; - - git_stream_close(s->io); - git_stream_free(s->io); - git__free(s->url); - git__free(s); -} - -static int git_proto_stream_alloc( - git_subtransport *t, - const char *url, - const char *cmd, - const char *host, - const char *port, - git_smart_subtransport_stream **stream) -{ - git_proto_stream *s; - - if (!stream) - return -1; - - s = git__calloc(1, sizeof(git_proto_stream)); - GIT_ERROR_CHECK_ALLOC(s); - - s->parent.subtransport = &t->parent; - s->parent.read = git_proto_stream_read; - s->parent.write = git_proto_stream_write; - s->parent.free = git_proto_stream_free; - - s->cmd = cmd; - s->url = git__strdup(url); - - if (!s->url) { - git__free(s); - return -1; - } - - if ((git_socket_stream_new(&s->io, host, port)) < 0) - return -1; - - GIT_ERROR_CHECK_VERSION(s->io, GIT_STREAM_VERSION, "git_stream"); - - *stream = &s->parent; - return 0; -} - -static int _git_uploadpack_ls( - git_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - git_net_url urldata = GIT_NET_URL_INIT; - const char *stream_url = url; - const char *host, *port; - git_proto_stream *s; - int error; - - *stream = NULL; - - if (!git__prefixcmp(url, prefix_git)) - stream_url += strlen(prefix_git); - - if ((error = git_net_url_parse(&urldata, url)) < 0) - return error; - - host = urldata.host; - port = urldata.port ? urldata.port : GIT_DEFAULT_PORT; - - error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream); - - git_net_url_dispose(&urldata); - - if (error < 0) { - git_proto_stream_free(*stream); - return error; - } - - s = (git_proto_stream *) *stream; - if ((error = git_stream_connect(s->io)) < 0) { - git_proto_stream_free(*stream); - return error; - } - - t->current_stream = s; - - return 0; -} - -static int _git_uploadpack( - git_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - GIT_UNUSED(url); - - if (t->current_stream) { - *stream = &t->current_stream->parent; - return 0; - } - - git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK"); - return -1; -} - -static int _git_receivepack_ls( - git_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - git_net_url urldata = GIT_NET_URL_INIT; - const char *stream_url = url; - git_proto_stream *s; - int error; - - *stream = NULL; - if (!git__prefixcmp(url, prefix_git)) - stream_url += strlen(prefix_git); - - if ((error = git_net_url_parse(&urldata, url)) < 0) - return error; - - error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, urldata.host, urldata.port, stream); - - git_net_url_dispose(&urldata); - - if (error < 0) { - git_proto_stream_free(*stream); - return error; - } - - s = (git_proto_stream *) *stream; - - if ((error = git_stream_connect(s->io)) < 0) - return error; - - t->current_stream = s; - - return 0; -} - -static int _git_receivepack( - git_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - GIT_UNUSED(url); - - if (t->current_stream) { - *stream = &t->current_stream->parent; - return 0; - } - - git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK"); - return -1; -} - -static int _git_action( - git_smart_subtransport_stream **stream, - git_smart_subtransport *subtransport, - const char *url, - git_smart_service_t action) -{ - git_subtransport *t = (git_subtransport *) subtransport; - - switch (action) { - case GIT_SERVICE_UPLOADPACK_LS: - return _git_uploadpack_ls(t, url, stream); - - case GIT_SERVICE_UPLOADPACK: - return _git_uploadpack(t, url, stream); - - case GIT_SERVICE_RECEIVEPACK_LS: - return _git_receivepack_ls(t, url, stream); - - case GIT_SERVICE_RECEIVEPACK: - return _git_receivepack(t, url, stream); - } - - *stream = NULL; - return -1; -} - -static int _git_close(git_smart_subtransport *subtransport) -{ - git_subtransport *t = (git_subtransport *) subtransport; - - GIT_ASSERT(!t->current_stream); - - GIT_UNUSED(t); - - return 0; -} - -static void _git_free(git_smart_subtransport *subtransport) -{ - git_subtransport *t = (git_subtransport *) subtransport; - - git__free(t); -} - -int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner, void *param) -{ - git_subtransport *t; - - GIT_UNUSED(param); - - if (!out) - return -1; - - t = git__calloc(1, sizeof(git_subtransport)); - GIT_ERROR_CHECK_ALLOC(t); - - t->owner = owner; - t->parent.action = _git_action; - t->parent.close = _git_close; - t->parent.free = _git_free; - - *out = (git_smart_subtransport *) t; - return 0; -} diff --git a/src/transports/http.c b/src/transports/http.c deleted file mode 100644 index 7db5582ca..000000000 --- a/src/transports/http.c +++ /dev/null @@ -1,760 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#ifndef GIT_WINHTTP - -#include "http_parser.h" -#include "net.h" -#include "netops.h" -#include "remote.h" -#include "smart.h" -#include "auth.h" -#include "http.h" -#include "auth_negotiate.h" -#include "auth_ntlm.h" -#include "trace.h" -#include "streams/tls.h" -#include "streams/socket.h" -#include "httpclient.h" -#include "git2/sys/credential.h" - -bool git_http__expect_continue = false; - -typedef enum { - HTTP_STATE_NONE = 0, - HTTP_STATE_SENDING_REQUEST, - HTTP_STATE_RECEIVING_RESPONSE, - HTTP_STATE_DONE -} http_state; - -typedef struct { - git_http_method method; - const char *url; - const char *request_type; - const char *response_type; - unsigned int initial : 1, - chunked : 1; -} http_service; - -typedef struct { - git_smart_subtransport_stream parent; - const http_service *service; - http_state state; - unsigned replay_count; -} http_stream; - -typedef struct { - git_net_url url; - - git_credential *cred; - unsigned auth_schemetypes; - unsigned url_cred_presented : 1; -} http_server; - -typedef struct { - git_smart_subtransport parent; - transport_smart *owner; - - http_server server; - http_server proxy; - - git_http_client *http_client; -} http_subtransport; - -static const http_service upload_pack_ls_service = { - GIT_HTTP_METHOD_GET, "/info/refs?service=git-upload-pack", - NULL, - "application/x-git-upload-pack-advertisement", - 1, - 0 -}; -static const http_service upload_pack_service = { - GIT_HTTP_METHOD_POST, "/git-upload-pack", - "application/x-git-upload-pack-request", - "application/x-git-upload-pack-result", - 0, - 0 -}; -static const http_service receive_pack_ls_service = { - GIT_HTTP_METHOD_GET, "/info/refs?service=git-receive-pack", - NULL, - "application/x-git-receive-pack-advertisement", - 1, - 0 -}; -static const http_service receive_pack_service = { - GIT_HTTP_METHOD_POST, "/git-receive-pack", - "application/x-git-receive-pack-request", - "application/x-git-receive-pack-result", - 0, - 1 -}; - -#define SERVER_TYPE_REMOTE "remote" -#define SERVER_TYPE_PROXY "proxy" - -#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport) - -static int apply_url_credentials( - git_credential **cred, - unsigned int allowed_types, - const char *username, - const char *password) -{ - GIT_ASSERT_ARG(username); - - if (!password) - password = ""; - - if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) - return git_credential_userpass_plaintext_new(cred, username, password); - - if ((allowed_types & GIT_CREDENTIAL_DEFAULT) && *username == '\0' && *password == '\0') - return git_credential_default_new(cred); - - return GIT_PASSTHROUGH; -} - -GIT_INLINE(void) free_cred(git_credential **cred) -{ - if (*cred) { - git_credential_free(*cred); - (*cred) = NULL; - } -} - -static int handle_auth( - http_server *server, - const char *server_type, - const char *url, - unsigned int allowed_schemetypes, - unsigned int allowed_credtypes, - git_credential_acquire_cb callback, - void *callback_payload) -{ - int error = 1; - - if (server->cred) - free_cred(&server->cred); - - /* Start with URL-specified credentials, if there were any. */ - if ((allowed_credtypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT) && - !server->url_cred_presented && - server->url.username) { - error = apply_url_credentials(&server->cred, allowed_credtypes, server->url.username, server->url.password); - server->url_cred_presented = 1; - - /* treat GIT_PASSTHROUGH as if callback isn't set */ - if (error == GIT_PASSTHROUGH) - error = 1; - } - - if (error > 0 && callback) { - error = callback(&server->cred, url, server->url.username, allowed_credtypes, callback_payload); - - /* treat GIT_PASSTHROUGH as if callback isn't set */ - if (error == GIT_PASSTHROUGH) - error = 1; - } - - if (error > 0) { - git_error_set(GIT_ERROR_HTTP, "%s authentication required but no callback set", server_type); - error = GIT_EAUTH; - } - - if (!error) - server->auth_schemetypes = allowed_schemetypes; - - return error; -} - -GIT_INLINE(int) handle_remote_auth( - http_stream *stream, - git_http_response *response) -{ - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - git_remote_connect_options *connect_opts = &transport->owner->connect_opts; - - if (response->server_auth_credtypes == 0) { - git_error_set(GIT_ERROR_HTTP, "server requires authentication that we do not support"); - return GIT_EAUTH; - } - - /* Otherwise, prompt for credentials. */ - return handle_auth( - &transport->server, - SERVER_TYPE_REMOTE, - transport->owner->url, - response->server_auth_schemetypes, - response->server_auth_credtypes, - connect_opts->callbacks.credentials, - connect_opts->callbacks.payload); -} - -GIT_INLINE(int) handle_proxy_auth( - http_stream *stream, - git_http_response *response) -{ - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - git_remote_connect_options *connect_opts = &transport->owner->connect_opts; - - if (response->proxy_auth_credtypes == 0) { - git_error_set(GIT_ERROR_HTTP, "proxy requires authentication that we do not support"); - return GIT_EAUTH; - } - - /* Otherwise, prompt for credentials. */ - return handle_auth( - &transport->proxy, - SERVER_TYPE_PROXY, - connect_opts->proxy_opts.url, - response->server_auth_schemetypes, - response->proxy_auth_credtypes, - connect_opts->proxy_opts.credentials, - connect_opts->proxy_opts.payload); -} - -static bool allow_redirect(http_stream *stream) -{ - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - - switch (transport->owner->connect_opts.follow_redirects) { - case GIT_REMOTE_REDIRECT_INITIAL: - return (stream->service->initial == 1); - case GIT_REMOTE_REDIRECT_ALL: - return true; - default: - return false; - } -} - -static int handle_response( - bool *complete, - http_stream *stream, - git_http_response *response, - bool allow_replay) -{ - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - int error; - - *complete = false; - - if (allow_replay && git_http_response_is_redirect(response)) { - if (!response->location) { - git_error_set(GIT_ERROR_HTTP, "redirect without location"); - return -1; - } - - if (git_net_url_apply_redirect(&transport->server.url, response->location, allow_redirect(stream), stream->service->url) < 0) { - return -1; - } - - return 0; - } else if (git_http_response_is_redirect(response)) { - git_error_set(GIT_ERROR_HTTP, "unexpected redirect"); - return -1; - } - - /* If we're in the middle of challenge/response auth, continue. */ - if (allow_replay && response->resend_credentials) { - return 0; - } else if (allow_replay && response->status == GIT_HTTP_STATUS_UNAUTHORIZED) { - if ((error = handle_remote_auth(stream, response)) < 0) - return error; - - return git_http_client_skip_body(transport->http_client); - } else if (allow_replay && response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { - if ((error = handle_proxy_auth(stream, response)) < 0) - return error; - - return git_http_client_skip_body(transport->http_client); - } else if (response->status == GIT_HTTP_STATUS_UNAUTHORIZED || - response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { - git_error_set(GIT_ERROR_HTTP, "unexpected authentication failure"); - return GIT_EAUTH; - } - - if (response->status != GIT_HTTP_STATUS_OK) { - git_error_set(GIT_ERROR_HTTP, "unexpected http status code: %d", response->status); - return -1; - } - - /* The response must contain a Content-Type header. */ - if (!response->content_type) { - git_error_set(GIT_ERROR_HTTP, "no content-type header in response"); - return -1; - } - - /* The Content-Type header must match our expectation. */ - if (strcmp(response->content_type, stream->service->response_type) != 0) { - git_error_set(GIT_ERROR_HTTP, "invalid content-type: '%s'", response->content_type); - return -1; - } - - *complete = true; - stream->state = HTTP_STATE_RECEIVING_RESPONSE; - return 0; -} - -static int lookup_proxy( - bool *out_use, - http_subtransport *transport) -{ - git_remote_connect_options *connect_opts = &transport->owner->connect_opts; - const char *proxy; - git_remote *remote; - char *config = NULL; - int error = 0; - - *out_use = false; - git_net_url_dispose(&transport->proxy.url); - - switch (connect_opts->proxy_opts.type) { - case GIT_PROXY_SPECIFIED: - proxy = connect_opts->proxy_opts.url; - break; - - case GIT_PROXY_AUTO: - remote = transport->owner->owner; - - error = git_remote__http_proxy(&config, remote, &transport->server.url); - - if (error || !config) - goto done; - - proxy = config; - break; - - default: - return 0; - } - - if (!proxy || - (error = git_net_url_parse(&transport->proxy.url, proxy)) < 0) - goto done; - - *out_use = true; - -done: - git__free(config); - return error; -} - -static int generate_request( - git_net_url *url, - git_http_request *request, - http_stream *stream, - size_t len) -{ - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - bool use_proxy = false; - int error; - - if ((error = git_net_url_joinpath(url, - &transport->server.url, stream->service->url)) < 0 || - (error = lookup_proxy(&use_proxy, transport)) < 0) - return error; - - request->method = stream->service->method; - request->url = url; - request->credentials = transport->server.cred; - request->proxy = use_proxy ? &transport->proxy.url : NULL; - request->proxy_credentials = transport->proxy.cred; - request->custom_headers = &transport->owner->connect_opts.custom_headers; - - if (stream->service->method == GIT_HTTP_METHOD_POST) { - request->chunked = stream->service->chunked; - request->content_length = stream->service->chunked ? 0 : len; - request->content_type = stream->service->request_type; - request->accept = stream->service->response_type; - request->expect_continue = git_http__expect_continue; - } - - return 0; -} - -/* - * Read from an HTTP transport - for the first invocation of this function - * (ie, when stream->state == HTTP_STATE_NONE), we'll send a GET request - * to the remote host. We will stream that data back on all subsequent - * calls. - */ -static int http_stream_read( - git_smart_subtransport_stream *s, - char *buffer, - size_t buffer_size, - size_t *out_len) -{ - http_stream *stream = (http_stream *)s; - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - git_net_url url = GIT_NET_URL_INIT; - git_net_url proxy_url = GIT_NET_URL_INIT; - git_http_request request = {0}; - git_http_response response = {0}; - bool complete; - int error; - - *out_len = 0; - - if (stream->state == HTTP_STATE_NONE) { - stream->state = HTTP_STATE_SENDING_REQUEST; - stream->replay_count = 0; - } - - /* - * Formulate the URL, send the request and read the response - * headers. Some of the request body may also be read. - */ - while (stream->state == HTTP_STATE_SENDING_REQUEST && - stream->replay_count < GIT_HTTP_REPLAY_MAX) { - git_net_url_dispose(&url); - git_net_url_dispose(&proxy_url); - git_http_response_dispose(&response); - - if ((error = generate_request(&url, &request, stream, 0)) < 0 || - (error = git_http_client_send_request( - transport->http_client, &request)) < 0 || - (error = git_http_client_read_response( - &response, transport->http_client)) < 0 || - (error = handle_response(&complete, stream, &response, true)) < 0) - goto done; - - if (complete) - break; - - stream->replay_count++; - } - - if (stream->state == HTTP_STATE_SENDING_REQUEST) { - git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); - error = GIT_ERROR; /* not GIT_EAUTH, because the exact cause is unclear */ - goto done; - } - - GIT_ASSERT(stream->state == HTTP_STATE_RECEIVING_RESPONSE); - - error = git_http_client_read_body(transport->http_client, buffer, buffer_size); - - if (error > 0) { - *out_len = error; - error = 0; - } - -done: - git_net_url_dispose(&url); - git_net_url_dispose(&proxy_url); - git_http_response_dispose(&response); - - return error; -} - -static bool needs_probe(http_stream *stream) -{ - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - - return (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM || - transport->server.auth_schemetypes == GIT_HTTP_AUTH_NEGOTIATE); -} - -static int send_probe(http_stream *stream) -{ - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - git_http_client *client = transport->http_client; - const char *probe = "0000"; - size_t len = 4; - git_net_url url = GIT_NET_URL_INIT; - git_http_request request = {0}; - git_http_response response = {0}; - bool complete = false; - size_t step, steps = 1; - int error; - - /* NTLM requires a full challenge/response */ - if (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM) - steps = GIT_AUTH_STEPS_NTLM; - - /* - * Send at most two requests: one without any authentication to see - * if we get prompted to authenticate. If we do, send a second one - * with the first authentication message. The final authentication - * message with the response will occur with the *actual* POST data. - */ - for (step = 0; step < steps && !complete; step++) { - git_net_url_dispose(&url); - git_http_response_dispose(&response); - - if ((error = generate_request(&url, &request, stream, len)) < 0 || - (error = git_http_client_send_request(client, &request)) < 0 || - (error = git_http_client_send_body(client, probe, len)) < 0 || - (error = git_http_client_read_response(&response, client)) < 0 || - (error = git_http_client_skip_body(client)) < 0 || - (error = handle_response(&complete, stream, &response, true)) < 0) - goto done; - } - -done: - git_http_response_dispose(&response); - git_net_url_dispose(&url); - return error; -} - -/* -* Write to an HTTP transport - for the first invocation of this function -* (ie, when stream->state == HTTP_STATE_NONE), we'll send a POST request -* to the remote host. If we're sending chunked data, then subsequent calls -* will write the additional data given in the buffer. If we're not chunking, -* then the caller should have given us all the data in the original call. -* The caller should call http_stream_read_response to get the result. -*/ -static int http_stream_write( - git_smart_subtransport_stream *s, - const char *buffer, - size_t len) -{ - http_stream *stream = GIT_CONTAINER_OF(s, http_stream, parent); - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - git_net_url url = GIT_NET_URL_INIT; - git_http_request request = {0}; - git_http_response response = {0}; - int error; - - while (stream->state == HTTP_STATE_NONE && - stream->replay_count < GIT_HTTP_REPLAY_MAX) { - - git_net_url_dispose(&url); - git_http_response_dispose(&response); - - /* - * If we're authenticating with a connection-based mechanism - * (NTLM, Kerberos), send a "probe" packet. Servers SHOULD - * authenticate an entire keep-alive connection, so ideally - * we should not need to authenticate but some servers do - * not support this. By sending a probe packet, we'll be - * able to follow up with a second POST using the actual - * data (and, in the degenerate case, the authentication - * header as well). - */ - if (needs_probe(stream) && (error = send_probe(stream)) < 0) - goto done; - - /* Send the regular POST request. */ - if ((error = generate_request(&url, &request, stream, len)) < 0 || - (error = git_http_client_send_request( - transport->http_client, &request)) < 0) - goto done; - - if (request.expect_continue && - git_http_client_has_response(transport->http_client)) { - bool complete; - - /* - * If we got a response to an expect/continue, then - * it's something other than a 100 and we should - * deal with the response somehow. - */ - if ((error = git_http_client_read_response(&response, transport->http_client)) < 0 || - (error = handle_response(&complete, stream, &response, true)) < 0) - goto done; - } else { - stream->state = HTTP_STATE_SENDING_REQUEST; - } - - stream->replay_count++; - } - - if (stream->state == HTTP_STATE_NONE) { - git_error_set(GIT_ERROR_HTTP, - "too many redirects or authentication replays"); - error = GIT_ERROR; /* not GIT_EAUTH because the exact cause is unclear */ - goto done; - } - - GIT_ASSERT(stream->state == HTTP_STATE_SENDING_REQUEST); - - error = git_http_client_send_body(transport->http_client, buffer, len); - -done: - git_http_response_dispose(&response); - git_net_url_dispose(&url); - return error; -} - -/* -* Read from an HTTP transport after it has been written to. This is the -* response from a POST request made by http_stream_write. -*/ -static int http_stream_read_response( - git_smart_subtransport_stream *s, - char *buffer, - size_t buffer_size, - size_t *out_len) -{ - http_stream *stream = (http_stream *)s; - http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - git_http_client *client = transport->http_client; - git_http_response response = {0}; - bool complete; - int error; - - *out_len = 0; - - if (stream->state == HTTP_STATE_SENDING_REQUEST) { - if ((error = git_http_client_read_response(&response, client)) < 0 || - (error = handle_response(&complete, stream, &response, false)) < 0) - goto done; - - GIT_ASSERT(complete); - stream->state = HTTP_STATE_RECEIVING_RESPONSE; - } - - error = git_http_client_read_body(client, buffer, buffer_size); - - if (error > 0) { - *out_len = error; - error = 0; - } - -done: - git_http_response_dispose(&response); - return error; -} - -static void http_stream_free(git_smart_subtransport_stream *stream) -{ - http_stream *s = GIT_CONTAINER_OF(stream, http_stream, parent); - git__free(s); -} - -static const http_service *select_service(git_smart_service_t action) -{ - switch (action) { - case GIT_SERVICE_UPLOADPACK_LS: - return &upload_pack_ls_service; - case GIT_SERVICE_UPLOADPACK: - return &upload_pack_service; - case GIT_SERVICE_RECEIVEPACK_LS: - return &receive_pack_ls_service; - case GIT_SERVICE_RECEIVEPACK: - return &receive_pack_service; - } - - return NULL; -} - -static int http_action( - git_smart_subtransport_stream **out, - git_smart_subtransport *t, - const char *url, - git_smart_service_t action) -{ - http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); - git_remote_connect_options *connect_opts = &transport->owner->connect_opts; - http_stream *stream; - const http_service *service; - int error; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(t); - - *out = NULL; - - /* - * If we've seen a redirect then preserve the location that we've - * been given. This is important to continue authorization against - * the redirect target, not the user-given source; the endpoint may - * have redirected us from HTTP->HTTPS and is using an auth mechanism - * that would be insecure in plaintext (eg, HTTP Basic). - */ - if (!git_net_url_valid(&transport->server.url) && - (error = git_net_url_parse(&transport->server.url, url)) < 0) - return error; - - if ((service = select_service(action)) == NULL) { - git_error_set(GIT_ERROR_HTTP, "invalid action"); - return -1; - } - - stream = git__calloc(sizeof(http_stream), 1); - GIT_ERROR_CHECK_ALLOC(stream); - - if (!transport->http_client) { - git_http_client_options opts = {0}; - - opts.server_certificate_check_cb = connect_opts->callbacks.certificate_check; - opts.server_certificate_check_payload = connect_opts->callbacks.payload; - opts.proxy_certificate_check_cb = connect_opts->proxy_opts.certificate_check; - opts.proxy_certificate_check_payload = connect_opts->proxy_opts.payload; - - if (git_http_client_new(&transport->http_client, &opts) < 0) - return -1; - } - - stream->service = service; - stream->parent.subtransport = &transport->parent; - - if (service->method == GIT_HTTP_METHOD_GET) { - stream->parent.read = http_stream_read; - } else { - stream->parent.write = http_stream_write; - stream->parent.read = http_stream_read_response; - } - - stream->parent.free = http_stream_free; - - *out = (git_smart_subtransport_stream *)stream; - return 0; -} - -static int http_close(git_smart_subtransport *t) -{ - http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); - - free_cred(&transport->server.cred); - free_cred(&transport->proxy.cred); - - transport->server.url_cred_presented = false; - transport->proxy.url_cred_presented = false; - - git_net_url_dispose(&transport->server.url); - git_net_url_dispose(&transport->proxy.url); - - return 0; -} - -static void http_free(git_smart_subtransport *t) -{ - http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); - - git_http_client_free(transport->http_client); - - http_close(t); - git__free(transport); -} - -int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param) -{ - http_subtransport *transport; - - GIT_UNUSED(param); - - GIT_ASSERT_ARG(out); - - transport = git__calloc(sizeof(http_subtransport), 1); - GIT_ERROR_CHECK_ALLOC(transport); - - transport->owner = (transport_smart *)owner; - transport->parent.action = http_action; - transport->parent.close = http_close; - transport->parent.free = http_free; - - *out = (git_smart_subtransport *) transport; - return 0; -} - -#endif /* !GIT_WINHTTP */ diff --git a/src/transports/http.h b/src/transports/http.h deleted file mode 100644 index 8e8e7226e..000000000 --- a/src/transports/http.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_transports_http_h__ -#define INCLUDE_transports_http_h__ - -#include "settings.h" -#include "httpclient.h" - -#define GIT_HTTP_REPLAY_MAX 15 - -extern bool git_http__expect_continue; - -GIT_INLINE(int) git_http__user_agent(git_str *buf) -{ - const char *ua = git_libgit2__user_agent(); - - if (!ua) - ua = "libgit2 " LIBGIT2_VERSION; - - return git_str_printf(buf, "git/2.0 (%s)", ua); -} - -#endif diff --git a/src/transports/httpclient.c b/src/transports/httpclient.c deleted file mode 100644 index 75782da82..000000000 --- a/src/transports/httpclient.c +++ /dev/null @@ -1,1579 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" -#include "git2.h" -#include "http_parser.h" -#include "vector.h" -#include "trace.h" -#include "httpclient.h" -#include "http.h" -#include "auth.h" -#include "auth_negotiate.h" -#include "auth_ntlm.h" -#include "git2/sys/credential.h" -#include "net.h" -#include "stream.h" -#include "streams/socket.h" -#include "streams/tls.h" -#include "auth.h" - -static git_http_auth_scheme auth_schemes[] = { - { GIT_HTTP_AUTH_NEGOTIATE, "Negotiate", GIT_CREDENTIAL_DEFAULT, git_http_auth_negotiate }, - { GIT_HTTP_AUTH_NTLM, "NTLM", GIT_CREDENTIAL_USERPASS_PLAINTEXT, git_http_auth_ntlm }, - { GIT_HTTP_AUTH_BASIC, "Basic", GIT_CREDENTIAL_USERPASS_PLAINTEXT, git_http_auth_basic }, -}; - -/* - * Use a 16kb read buffer to match the maximum size of a TLS packet. This - * is critical for compatibility with SecureTransport, which will always do - * a network read on every call, even if it has data buffered to return to - * you. That buffered data may be the _end_ of a keep-alive response, so - * if SecureTransport performs another network read, it will wait until the - * server ultimately times out before it returns that buffered data to you. - * Since SecureTransport only reads a single TLS packet at a time, by - * calling it with a read buffer that is the maximum size of a TLS packet, - * we ensure that it will never buffer. - */ -#define GIT_READ_BUFFER_SIZE (16 * 1024) - -typedef struct { - git_net_url url; - git_stream *stream; - - git_vector auth_challenges; - git_http_auth_context *auth_context; -} git_http_server; - -typedef enum { - PROXY = 1, - SERVER -} git_http_server_t; - -typedef enum { - NONE = 0, - SENDING_REQUEST, - SENDING_BODY, - SENT_REQUEST, - HAS_EARLY_RESPONSE, - READING_RESPONSE, - READING_BODY, - DONE -} http_client_state; - -/* Parser state */ -typedef enum { - PARSE_HEADER_NONE = 0, - PARSE_HEADER_NAME, - PARSE_HEADER_VALUE, - PARSE_HEADER_COMPLETE -} parse_header_state; - -typedef enum { - PARSE_STATUS_OK, - PARSE_STATUS_NO_OUTPUT, - PARSE_STATUS_ERROR -} parse_status; - -typedef struct { - git_http_client *client; - git_http_response *response; - - /* Temporary buffers to avoid extra mallocs */ - git_str parse_header_name; - git_str parse_header_value; - - /* Parser state */ - int error; - parse_status parse_status; - - /* Headers parsing */ - parse_header_state parse_header_state; - - /* Body parsing */ - char *output_buf; /* Caller's output buffer */ - size_t output_size; /* Size of caller's output buffer */ - size_t output_written; /* Bytes we've written to output buffer */ -} http_parser_context; - -/* HTTP client connection */ -struct git_http_client { - git_http_client_options opts; - - /* Are we writing to the proxy or server, and state of the client. */ - git_http_server_t current_server; - http_client_state state; - - http_parser parser; - - git_http_server server; - git_http_server proxy; - - unsigned request_count; - unsigned connected : 1, - proxy_connected : 1, - keepalive : 1, - request_chunked : 1; - - /* Temporary buffers to avoid extra mallocs */ - git_str request_msg; - git_str read_buf; - - /* A subset of information from the request */ - size_t request_body_len, - request_body_remain; - - /* - * When state == HAS_EARLY_RESPONSE, the response of our proxy - * that we have buffered and will deliver during read_response. - */ - git_http_response early_response; -}; - -bool git_http_response_is_redirect(git_http_response *response) -{ - return (response->status == GIT_HTTP_MOVED_PERMANENTLY || - response->status == GIT_HTTP_FOUND || - response->status == GIT_HTTP_SEE_OTHER || - response->status == GIT_HTTP_TEMPORARY_REDIRECT || - response->status == GIT_HTTP_PERMANENT_REDIRECT); -} - -void git_http_response_dispose(git_http_response *response) -{ - if (!response) - return; - - git__free(response->content_type); - git__free(response->location); - - memset(response, 0, sizeof(git_http_response)); -} - -static int on_header_complete(http_parser *parser) -{ - http_parser_context *ctx = (http_parser_context *) parser->data; - git_http_client *client = ctx->client; - git_http_response *response = ctx->response; - - git_str *name = &ctx->parse_header_name; - git_str *value = &ctx->parse_header_value; - - if (!strcasecmp("Content-Type", name->ptr)) { - if (response->content_type) { - git_error_set(GIT_ERROR_HTTP, - "multiple content-type headers"); - return -1; - } - - response->content_type = - git__strndup(value->ptr, value->size); - GIT_ERROR_CHECK_ALLOC(ctx->response->content_type); - } else if (!strcasecmp("Content-Length", name->ptr)) { - int64_t len; - - if (response->content_length) { - git_error_set(GIT_ERROR_HTTP, - "multiple content-length headers"); - return -1; - } - - if (git__strntol64(&len, value->ptr, value->size, - NULL, 10) < 0 || len < 0) { - git_error_set(GIT_ERROR_HTTP, - "invalid content-length"); - return -1; - } - - response->content_length = (size_t)len; - } else if (!strcasecmp("Transfer-Encoding", name->ptr) && - !strcasecmp("chunked", value->ptr)) { - ctx->response->chunked = 1; - } else if (!strcasecmp("Proxy-Authenticate", git_str_cstr(name))) { - char *dup = git__strndup(value->ptr, value->size); - GIT_ERROR_CHECK_ALLOC(dup); - - if (git_vector_insert(&client->proxy.auth_challenges, dup) < 0) - return -1; - } else if (!strcasecmp("WWW-Authenticate", name->ptr)) { - char *dup = git__strndup(value->ptr, value->size); - GIT_ERROR_CHECK_ALLOC(dup); - - if (git_vector_insert(&client->server.auth_challenges, dup) < 0) - return -1; - } else if (!strcasecmp("Location", name->ptr)) { - if (response->location) { - git_error_set(GIT_ERROR_HTTP, - "multiple location headers"); - return -1; - } - - response->location = git__strndup(value->ptr, value->size); - GIT_ERROR_CHECK_ALLOC(response->location); - } - - return 0; -} - -static int on_header_field(http_parser *parser, const char *str, size_t len) -{ - http_parser_context *ctx = (http_parser_context *) parser->data; - - switch (ctx->parse_header_state) { - /* - * We last saw a header value, process the name/value pair and - * get ready to handle this new name. - */ - case PARSE_HEADER_VALUE: - if (on_header_complete(parser) < 0) - return ctx->parse_status = PARSE_STATUS_ERROR; - - git_str_clear(&ctx->parse_header_name); - git_str_clear(&ctx->parse_header_value); - /* Fall through */ - - case PARSE_HEADER_NONE: - case PARSE_HEADER_NAME: - ctx->parse_header_state = PARSE_HEADER_NAME; - - if (git_str_put(&ctx->parse_header_name, str, len) < 0) - return ctx->parse_status = PARSE_STATUS_ERROR; - - break; - - default: - git_error_set(GIT_ERROR_HTTP, - "header name seen at unexpected time"); - return ctx->parse_status = PARSE_STATUS_ERROR; - } - - return 0; -} - -static int on_header_value(http_parser *parser, const char *str, size_t len) -{ - http_parser_context *ctx = (http_parser_context *) parser->data; - - switch (ctx->parse_header_state) { - case PARSE_HEADER_NAME: - case PARSE_HEADER_VALUE: - ctx->parse_header_state = PARSE_HEADER_VALUE; - - if (git_str_put(&ctx->parse_header_value, str, len) < 0) - return ctx->parse_status = PARSE_STATUS_ERROR; - - break; - - default: - git_error_set(GIT_ERROR_HTTP, - "header value seen at unexpected time"); - return ctx->parse_status = PARSE_STATUS_ERROR; - } - - return 0; -} - -GIT_INLINE(bool) challenge_matches_scheme( - const char *challenge, - git_http_auth_scheme *scheme) -{ - const char *scheme_name = scheme->name; - size_t scheme_len = strlen(scheme_name); - - if (!strncasecmp(challenge, scheme_name, scheme_len) && - (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' ')) - return true; - - return false; -} - -static git_http_auth_scheme *scheme_for_challenge(const char *challenge) -{ - size_t i; - - for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) { - if (challenge_matches_scheme(challenge, &auth_schemes[i])) - return &auth_schemes[i]; - } - - return NULL; -} - -GIT_INLINE(void) collect_authinfo( - unsigned int *schemetypes, - unsigned int *credtypes, - git_vector *challenges) -{ - git_http_auth_scheme *scheme; - const char *challenge; - size_t i; - - *schemetypes = 0; - *credtypes = 0; - - git_vector_foreach(challenges, i, challenge) { - if ((scheme = scheme_for_challenge(challenge)) != NULL) { - *schemetypes |= scheme->type; - *credtypes |= scheme->credtypes; - } - } -} - -static int resend_needed(git_http_client *client, git_http_response *response) -{ - git_http_auth_context *auth_context; - - if (response->status == GIT_HTTP_STATUS_UNAUTHORIZED && - (auth_context = client->server.auth_context) && - auth_context->is_complete && - !auth_context->is_complete(auth_context)) - return 1; - - if (response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED && - (auth_context = client->proxy.auth_context) && - auth_context->is_complete && - !auth_context->is_complete(auth_context)) - return 1; - - return 0; -} - -static int on_headers_complete(http_parser *parser) -{ - http_parser_context *ctx = (http_parser_context *) parser->data; - - /* Finalize the last seen header */ - switch (ctx->parse_header_state) { - case PARSE_HEADER_VALUE: - if (on_header_complete(parser) < 0) - return ctx->parse_status = PARSE_STATUS_ERROR; - - /* Fall through */ - - case PARSE_HEADER_NONE: - ctx->parse_header_state = PARSE_HEADER_COMPLETE; - break; - - default: - git_error_set(GIT_ERROR_HTTP, - "header completion at unexpected time"); - return ctx->parse_status = PARSE_STATUS_ERROR; - } - - ctx->response->status = parser->status_code; - ctx->client->keepalive = http_should_keep_alive(parser); - - /* Prepare for authentication */ - collect_authinfo(&ctx->response->server_auth_schemetypes, - &ctx->response->server_auth_credtypes, - &ctx->client->server.auth_challenges); - collect_authinfo(&ctx->response->proxy_auth_schemetypes, - &ctx->response->proxy_auth_credtypes, - &ctx->client->proxy.auth_challenges); - - ctx->response->resend_credentials = resend_needed(ctx->client, - ctx->response); - - /* Stop parsing. */ - http_parser_pause(parser, 1); - - if (ctx->response->content_type || ctx->response->chunked) - ctx->client->state = READING_BODY; - else - ctx->client->state = DONE; - - return 0; -} - -static int on_body(http_parser *parser, const char *buf, size_t len) -{ - http_parser_context *ctx = (http_parser_context *) parser->data; - size_t max_len; - - /* Saw data when we expected not to (eg, in consume_response_body) */ - if (ctx->output_buf == NULL && ctx->output_size == 0) { - ctx->parse_status = PARSE_STATUS_NO_OUTPUT; - return 0; - } - - GIT_ASSERT(ctx->output_size >= ctx->output_written); - - max_len = min(ctx->output_size - ctx->output_written, len); - max_len = min(max_len, INT_MAX); - - memcpy(ctx->output_buf + ctx->output_written, buf, max_len); - ctx->output_written += max_len; - - return 0; -} - -static int on_message_complete(http_parser *parser) -{ - http_parser_context *ctx = (http_parser_context *) parser->data; - - ctx->client->state = DONE; - return 0; -} - -GIT_INLINE(int) stream_write( - git_http_server *server, - const char *data, - size_t len) -{ - git_trace(GIT_TRACE_TRACE, - "Sending request:\n%.*s", (int)len, data); - - return git_stream__write_full(server->stream, data, len, 0); -} - -GIT_INLINE(int) client_write_request(git_http_client *client) -{ - git_stream *stream = client->current_server == PROXY ? - client->proxy.stream : client->server.stream; - - git_trace(GIT_TRACE_TRACE, - "Sending request:\n%.*s", - (int)client->request_msg.size, client->request_msg.ptr); - - return git_stream__write_full(stream, - client->request_msg.ptr, - client->request_msg.size, - 0); -} - -static const char *name_for_method(git_http_method method) -{ - switch (method) { - case GIT_HTTP_METHOD_GET: - return "GET"; - case GIT_HTTP_METHOD_POST: - return "POST"; - case GIT_HTTP_METHOD_CONNECT: - return "CONNECT"; - } - - return NULL; -} - -/* - * Find the scheme that is suitable for the given credentials, based on the - * server's auth challenges. - */ -static bool best_scheme_and_challenge( - git_http_auth_scheme **scheme_out, - const char **challenge_out, - git_vector *challenges, - git_credential *credentials) -{ - const char *challenge; - size_t i, j; - - for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) { - git_vector_foreach(challenges, j, challenge) { - git_http_auth_scheme *scheme = &auth_schemes[i]; - - if (challenge_matches_scheme(challenge, scheme) && - (scheme->credtypes & credentials->credtype)) { - *scheme_out = scheme; - *challenge_out = challenge; - return true; - } - } - } - - return false; -} - -/* - * Find the challenge from the server for our current auth context. - */ -static const char *challenge_for_context( - git_vector *challenges, - git_http_auth_context *auth_ctx) -{ - const char *challenge; - size_t i, j; - - for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) { - if (auth_schemes[i].type == auth_ctx->type) { - git_http_auth_scheme *scheme = &auth_schemes[i]; - - git_vector_foreach(challenges, j, challenge) { - if (challenge_matches_scheme(challenge, scheme)) - return challenge; - } - } - } - - return NULL; -} - -static const char *init_auth_context( - git_http_server *server, - git_vector *challenges, - git_credential *credentials) -{ - git_http_auth_scheme *scheme; - const char *challenge; - int error; - - if (!best_scheme_and_challenge(&scheme, &challenge, challenges, credentials)) { - git_error_set(GIT_ERROR_HTTP, "could not find appropriate mechanism for credentials"); - return NULL; - } - - error = scheme->init_context(&server->auth_context, &server->url); - - if (error == GIT_PASSTHROUGH) { - git_error_set(GIT_ERROR_HTTP, "'%s' authentication is not supported", scheme->name); - return NULL; - } - - return challenge; -} - -static void free_auth_context(git_http_server *server) -{ - if (!server->auth_context) - return; - - if (server->auth_context->free) - server->auth_context->free(server->auth_context); - - server->auth_context = NULL; -} - -static int apply_credentials( - git_str *buf, - git_http_server *server, - const char *header_name, - git_credential *credentials) -{ - git_http_auth_context *auth = server->auth_context; - git_vector *challenges = &server->auth_challenges; - const char *challenge; - git_str token = GIT_STR_INIT; - int error = 0; - - /* We've started a new request without creds; free the context. */ - if (auth && !credentials) { - free_auth_context(server); - return 0; - } - - /* We haven't authenticated, nor were we asked to. Nothing to do. */ - if (!auth && !git_vector_length(challenges)) - return 0; - - if (!auth) { - challenge = init_auth_context(server, challenges, credentials); - auth = server->auth_context; - - if (!challenge || !auth) { - error = -1; - goto done; - } - } else if (auth->set_challenge) { - challenge = challenge_for_context(challenges, auth); - } - - if (auth->set_challenge && challenge && - (error = auth->set_challenge(auth, challenge)) < 0) - goto done; - - if ((error = auth->next_token(&token, auth, credentials)) < 0) - goto done; - - if (auth->is_complete && auth->is_complete(auth)) { - /* - * If we're done with an auth mechanism with connection affinity, - * we don't need to send any more headers and can dispose the context. - */ - if (auth->connection_affinity) - free_auth_context(server); - } else if (!token.size) { - git_error_set(GIT_ERROR_HTTP, "failed to respond to authentication challenge"); - error = GIT_EAUTH; - goto done; - } - - if (token.size > 0) - error = git_str_printf(buf, "%s: %s\r\n", header_name, token.ptr); - -done: - git_str_dispose(&token); - return error; -} - -GIT_INLINE(int) apply_server_credentials( - git_str *buf, - git_http_client *client, - git_http_request *request) -{ - return apply_credentials(buf, - &client->server, - "Authorization", - request->credentials); -} - -GIT_INLINE(int) apply_proxy_credentials( - git_str *buf, - git_http_client *client, - git_http_request *request) -{ - return apply_credentials(buf, - &client->proxy, - "Proxy-Authorization", - request->proxy_credentials); -} - -static int puts_host_and_port(git_str *buf, git_net_url *url, bool force_port) -{ - bool ipv6 = git_net_url_is_ipv6(url); - - if (ipv6) - git_str_putc(buf, '['); - - git_str_puts(buf, url->host); - - if (ipv6) - git_str_putc(buf, ']'); - - if (force_port || !git_net_url_is_default_port(url)) { - git_str_putc(buf, ':'); - git_str_puts(buf, url->port); - } - - return git_str_oom(buf) ? -1 : 0; -} - -static int generate_connect_request( - git_http_client *client, - git_http_request *request) -{ - git_str *buf; - int error; - - git_str_clear(&client->request_msg); - buf = &client->request_msg; - - git_str_puts(buf, "CONNECT "); - puts_host_and_port(buf, &client->server.url, true); - git_str_puts(buf, " HTTP/1.1\r\n"); - - git_str_puts(buf, "User-Agent: "); - git_http__user_agent(buf); - git_str_puts(buf, "\r\n"); - - git_str_puts(buf, "Host: "); - puts_host_and_port(buf, &client->server.url, true); - git_str_puts(buf, "\r\n"); - - if ((error = apply_proxy_credentials(buf, client, request) < 0)) - return -1; - - git_str_puts(buf, "\r\n"); - - return git_str_oom(buf) ? -1 : 0; -} - -static bool use_connect_proxy(git_http_client *client) -{ - return client->proxy.url.host && !strcmp(client->server.url.scheme, "https"); -} - -static int generate_request( - git_http_client *client, - git_http_request *request) -{ - git_str *buf; - size_t i; - int error; - - GIT_ASSERT_ARG(client); - GIT_ASSERT_ARG(request); - - git_str_clear(&client->request_msg); - buf = &client->request_msg; - - /* GET|POST path HTTP/1.1 */ - git_str_puts(buf, name_for_method(request->method)); - git_str_putc(buf, ' '); - - if (request->proxy && strcmp(request->url->scheme, "https")) - git_net_url_fmt(buf, request->url); - else - git_net_url_fmt_path(buf, request->url); - - git_str_puts(buf, " HTTP/1.1\r\n"); - - git_str_puts(buf, "User-Agent: "); - git_http__user_agent(buf); - git_str_puts(buf, "\r\n"); - - git_str_puts(buf, "Host: "); - puts_host_and_port(buf, request->url, false); - git_str_puts(buf, "\r\n"); - - if (request->accept) - git_str_printf(buf, "Accept: %s\r\n", request->accept); - else - git_str_puts(buf, "Accept: */*\r\n"); - - if (request->content_type) - git_str_printf(buf, "Content-Type: %s\r\n", - request->content_type); - - if (request->chunked) - git_str_puts(buf, "Transfer-Encoding: chunked\r\n"); - - if (request->content_length > 0) - git_str_printf(buf, "Content-Length: %"PRIuZ "\r\n", - request->content_length); - - if (request->expect_continue) - git_str_printf(buf, "Expect: 100-continue\r\n"); - - if ((error = apply_server_credentials(buf, client, request)) < 0 || - (!use_connect_proxy(client) && - (error = apply_proxy_credentials(buf, client, request)) < 0)) - return error; - - if (request->custom_headers) { - for (i = 0; i < request->custom_headers->count; i++) { - const char *hdr = request->custom_headers->strings[i]; - - if (hdr) - git_str_printf(buf, "%s\r\n", hdr); - } - } - - git_str_puts(buf, "\r\n"); - - if (git_str_oom(buf)) - return -1; - - return 0; -} - -static int check_certificate( - git_stream *stream, - git_net_url *url, - int is_valid, - git_transport_certificate_check_cb cert_cb, - void *cert_cb_payload) -{ - git_cert *cert; - git_error_state last_error = {0}; - int error; - - if ((error = git_stream_certificate(&cert, stream)) < 0) - return error; - - git_error_state_capture(&last_error, GIT_ECERTIFICATE); - - error = cert_cb(cert, is_valid, url->host, cert_cb_payload); - - if (error == GIT_PASSTHROUGH && !is_valid) - return git_error_state_restore(&last_error); - else if (error == GIT_PASSTHROUGH) - error = 0; - else if (error && !git_error_last()) - git_error_set(GIT_ERROR_HTTP, - "user rejected certificate for %s", url->host); - - git_error_state_free(&last_error); - return error; -} - -static int server_connect_stream( - git_http_server *server, - git_transport_certificate_check_cb cert_cb, - void *cb_payload) -{ - int error; - - GIT_ERROR_CHECK_VERSION(server->stream, GIT_STREAM_VERSION, "git_stream"); - - error = git_stream_connect(server->stream); - - if (error && error != GIT_ECERTIFICATE) - return error; - - if (git_stream_is_encrypted(server->stream) && cert_cb != NULL) - error = check_certificate(server->stream, &server->url, !error, - cert_cb, cb_payload); - - return error; -} - -static void reset_auth_connection(git_http_server *server) -{ - /* - * If we've authenticated and we're doing "normal" - * authentication with a request affinity (Basic, Digest) - * then we want to _keep_ our context, since authentication - * survives even through non-keep-alive connections. If - * we've authenticated and we're doing connection-based - * authentication (NTLM, Negotiate) - indicated by the presence - * of an `is_complete` callback - then we need to restart - * authentication on a new connection. - */ - - if (server->auth_context && - server->auth_context->connection_affinity) - free_auth_context(server); -} - -/* - * Updates the server data structure with the new URL; returns 1 if the server - * has changed and we need to reconnect, returns 0 otherwise. - */ -GIT_INLINE(int) server_setup_from_url( - git_http_server *server, - git_net_url *url) -{ - if (!server->url.scheme || strcmp(server->url.scheme, url->scheme) || - !server->url.host || strcmp(server->url.host, url->host) || - !server->url.port || strcmp(server->url.port, url->port)) { - git__free(server->url.scheme); - git__free(server->url.host); - git__free(server->url.port); - - server->url.scheme = git__strdup(url->scheme); - GIT_ERROR_CHECK_ALLOC(server->url.scheme); - - server->url.host = git__strdup(url->host); - GIT_ERROR_CHECK_ALLOC(server->url.host); - - server->url.port = git__strdup(url->port); - GIT_ERROR_CHECK_ALLOC(server->url.port); - - return 1; - } - - return 0; -} - -static void reset_parser(git_http_client *client) -{ - http_parser_init(&client->parser, HTTP_RESPONSE); -} - -static int setup_hosts( - git_http_client *client, - git_http_request *request) -{ - int ret, diff = 0; - - GIT_ASSERT_ARG(client); - GIT_ASSERT_ARG(request); - - GIT_ASSERT(request->url); - - if ((ret = server_setup_from_url(&client->server, request->url)) < 0) - return ret; - - diff |= ret; - - if (request->proxy && - (ret = server_setup_from_url(&client->proxy, request->proxy)) < 0) - return ret; - - diff |= ret; - - if (diff) { - free_auth_context(&client->server); - free_auth_context(&client->proxy); - - client->connected = 0; - } - - return 0; -} - -GIT_INLINE(int) server_create_stream(git_http_server *server) -{ - git_net_url *url = &server->url; - - if (strcasecmp(url->scheme, "https") == 0) - return git_tls_stream_new(&server->stream, url->host, url->port); - else if (strcasecmp(url->scheme, "http") == 0) - return git_socket_stream_new(&server->stream, url->host, url->port); - - git_error_set(GIT_ERROR_HTTP, "unknown http scheme '%s'", url->scheme); - return -1; -} - -GIT_INLINE(void) save_early_response( - git_http_client *client, - git_http_response *response) -{ - /* Buffer the response so we can return it in read_response */ - client->state = HAS_EARLY_RESPONSE; - - memcpy(&client->early_response, response, sizeof(git_http_response)); - memset(response, 0, sizeof(git_http_response)); -} - -static int proxy_connect( - git_http_client *client, - git_http_request *request) -{ - git_http_response response = {0}; - int error; - - if (!client->proxy_connected || !client->keepalive) { - git_trace(GIT_TRACE_DEBUG, "Connecting to proxy %s port %s", - client->proxy.url.host, client->proxy.url.port); - - if ((error = server_create_stream(&client->proxy)) < 0 || - (error = server_connect_stream(&client->proxy, - client->opts.proxy_certificate_check_cb, - client->opts.proxy_certificate_check_payload)) < 0) - goto done; - - client->proxy_connected = 1; - } - - client->current_server = PROXY; - client->state = SENDING_REQUEST; - - if ((error = generate_connect_request(client, request)) < 0 || - (error = client_write_request(client)) < 0) - goto done; - - client->state = SENT_REQUEST; - - if ((error = git_http_client_read_response(&response, client)) < 0 || - (error = git_http_client_skip_body(client)) < 0) - goto done; - - GIT_ASSERT(client->state == DONE); - - if (response.status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { - save_early_response(client, &response); - - error = GIT_RETRY; - goto done; - } else if (response.status != GIT_HTTP_STATUS_OK) { - git_error_set(GIT_ERROR_HTTP, "proxy returned unexpected status: %d", response.status); - error = -1; - goto done; - } - - reset_parser(client); - client->state = NONE; - -done: - git_http_response_dispose(&response); - return error; -} - -static int server_connect(git_http_client *client) -{ - git_net_url *url = &client->server.url; - git_transport_certificate_check_cb cert_cb; - void *cert_payload; - int error; - - client->current_server = SERVER; - - if (client->proxy.stream) - error = git_tls_stream_wrap(&client->server.stream, client->proxy.stream, url->host); - else - error = server_create_stream(&client->server); - - if (error < 0) - goto done; - - cert_cb = client->opts.server_certificate_check_cb; - cert_payload = client->opts.server_certificate_check_payload; - - error = server_connect_stream(&client->server, cert_cb, cert_payload); - -done: - return error; -} - -GIT_INLINE(void) close_stream(git_http_server *server) -{ - if (server->stream) { - git_stream_close(server->stream); - git_stream_free(server->stream); - server->stream = NULL; - } -} - -static int http_client_connect( - git_http_client *client, - git_http_request *request) -{ - bool use_proxy = false; - int error; - - if ((error = setup_hosts(client, request)) < 0) - goto on_error; - - /* We're connected to our destination server; no need to reconnect */ - if (client->connected && client->keepalive && - (client->state == NONE || client->state == DONE)) - return 0; - - client->connected = 0; - client->request_count = 0; - - close_stream(&client->server); - reset_auth_connection(&client->server); - - reset_parser(client); - - /* Reconnect to the proxy if necessary. */ - use_proxy = use_connect_proxy(client); - - if (use_proxy) { - if (!client->proxy_connected || !client->keepalive || - (client->state != NONE && client->state != DONE)) { - close_stream(&client->proxy); - reset_auth_connection(&client->proxy); - - client->proxy_connected = 0; - } - - if ((error = proxy_connect(client, request)) < 0) - goto on_error; - } - - git_trace(GIT_TRACE_DEBUG, "Connecting to remote %s port %s", - client->server.url.host, client->server.url.port); - - if ((error = server_connect(client)) < 0) - goto on_error; - - client->connected = 1; - return error; - -on_error: - if (error != GIT_RETRY) - close_stream(&client->proxy); - - close_stream(&client->server); - return error; -} - -GIT_INLINE(int) client_read(git_http_client *client) -{ - http_parser_context *parser_context = client->parser.data; - git_stream *stream; - char *buf = client->read_buf.ptr + client->read_buf.size; - size_t max_len; - ssize_t read_len; - - stream = client->current_server == PROXY ? - client->proxy.stream : client->server.stream; - - /* - * We use a git_str for convenience, but statically allocate it and - * don't resize. Limit our consumption to INT_MAX since calling - * functions use an int return type to return number of bytes read. - */ - max_len = client->read_buf.asize - client->read_buf.size; - max_len = min(max_len, INT_MAX); - - if (parser_context->output_size) - max_len = min(max_len, parser_context->output_size); - - if (max_len == 0) { - git_error_set(GIT_ERROR_HTTP, "no room in output buffer"); - return -1; - } - - read_len = git_stream_read(stream, buf, max_len); - - if (read_len >= 0) { - client->read_buf.size += read_len; - - git_trace(GIT_TRACE_TRACE, "Received:\n%.*s", - (int)read_len, buf); - } - - return (int)read_len; -} - -static bool parser_settings_initialized; -static http_parser_settings parser_settings; - -GIT_INLINE(http_parser_settings *) http_client_parser_settings(void) -{ - if (!parser_settings_initialized) { - parser_settings.on_header_field = on_header_field; - parser_settings.on_header_value = on_header_value; - parser_settings.on_headers_complete = on_headers_complete; - parser_settings.on_body = on_body; - parser_settings.on_message_complete = on_message_complete; - - parser_settings_initialized = true; - } - - return &parser_settings; -} - -GIT_INLINE(int) client_read_and_parse(git_http_client *client) -{ - http_parser *parser = &client->parser; - http_parser_context *ctx = (http_parser_context *) parser->data; - unsigned char http_errno; - int read_len; - size_t parsed_len; - - /* - * If we have data in our read buffer, that means we stopped early - * when parsing headers. Use the data in the read buffer instead of - * reading more from the socket. - */ - if (!client->read_buf.size && (read_len = client_read(client)) < 0) - return read_len; - - parsed_len = http_parser_execute(parser, - http_client_parser_settings(), - client->read_buf.ptr, - client->read_buf.size); - http_errno = client->parser.http_errno; - - if (parsed_len > INT_MAX) { - git_error_set(GIT_ERROR_HTTP, "unexpectedly large parse"); - return -1; - } - - if (ctx->parse_status == PARSE_STATUS_ERROR) { - client->connected = 0; - return ctx->error ? ctx->error : -1; - } - - /* - * If we finished reading the headers or body, we paused parsing. - * Otherwise the parser will start filling the body, or even parse - * a new response if the server pipelined us multiple responses. - * (This can happen in response to an expect/continue request, - * where the server gives you a 100 and 200 simultaneously.) - */ - if (http_errno == HPE_PAUSED) { - /* - * http-parser has a "feature" where it will not deliver the - * final byte when paused in a callback. Consume that byte. - * https://github.com/nodejs/http-parser/issues/97 - */ - GIT_ASSERT(client->read_buf.size > parsed_len); - - http_parser_pause(parser, 0); - - parsed_len += http_parser_execute(parser, - http_client_parser_settings(), - client->read_buf.ptr + parsed_len, - 1); - } - - /* Most failures will be reported in http_errno */ - else if (parser->http_errno != HPE_OK) { - git_error_set(GIT_ERROR_HTTP, "http parser error: %s", - http_errno_description(http_errno)); - return -1; - } - - /* Otherwise we should have consumed the entire buffer. */ - else if (parsed_len != client->read_buf.size) { - git_error_set(GIT_ERROR_HTTP, - "http parser did not consume entire buffer: %s", - http_errno_description(http_errno)); - return -1; - } - - /* recv returned 0, the server hung up on us */ - else if (!parsed_len) { - git_error_set(GIT_ERROR_HTTP, "unexpected EOF"); - return -1; - } - - git_str_consume_bytes(&client->read_buf, parsed_len); - - return (int)parsed_len; -} - -/* - * See if we've consumed the entire response body. If the client was - * reading the body but did not consume it entirely, it's possible that - * they knew that the stream had finished (in a git response, seeing a - * final flush) and stopped reading. But if the response was chunked, - * we may have not consumed the final chunk marker. Consume it to - * ensure that we don't have it waiting in our socket. If there's - * more than just a chunk marker, close the connection. - */ -static void complete_response_body(git_http_client *client) -{ - http_parser_context parser_context = {0}; - - /* If we're not keeping alive, don't bother. */ - if (!client->keepalive) { - client->connected = 0; - goto done; - } - - parser_context.client = client; - client->parser.data = &parser_context; - - /* If there was an error, just close the connection. */ - if (client_read_and_parse(client) < 0 || - parser_context.error != HPE_OK || - (parser_context.parse_status != PARSE_STATUS_OK && - parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) { - git_error_clear(); - client->connected = 0; - } - -done: - git_str_clear(&client->read_buf); -} - -int git_http_client_send_request( - git_http_client *client, - git_http_request *request) -{ - git_http_response response = {0}; - int error = -1; - - GIT_ASSERT_ARG(client); - GIT_ASSERT_ARG(request); - - /* If the client did not finish reading, clean up the stream. */ - if (client->state == READING_BODY) - complete_response_body(client); - - /* If we're waiting for proxy auth, don't sending more requests. */ - if (client->state == HAS_EARLY_RESPONSE) - return 0; - - if (git_trace_level() >= GIT_TRACE_DEBUG) { - git_str url = GIT_STR_INIT; - git_net_url_fmt(&url, request->url); - git_trace(GIT_TRACE_DEBUG, "Sending %s request to %s", - name_for_method(request->method), - url.ptr ? url.ptr : ""); - git_str_dispose(&url); - } - - if ((error = http_client_connect(client, request)) < 0 || - (error = generate_request(client, request)) < 0 || - (error = client_write_request(client)) < 0) - goto done; - - client->state = SENT_REQUEST; - - if (request->expect_continue) { - if ((error = git_http_client_read_response(&response, client)) < 0 || - (error = git_http_client_skip_body(client)) < 0) - goto done; - - error = 0; - - if (response.status != GIT_HTTP_STATUS_CONTINUE) { - save_early_response(client, &response); - goto done; - } - } - - if (request->content_length || request->chunked) { - client->state = SENDING_BODY; - client->request_body_len = request->content_length; - client->request_body_remain = request->content_length; - client->request_chunked = request->chunked; - } - - reset_parser(client); - -done: - if (error == GIT_RETRY) - error = 0; - - git_http_response_dispose(&response); - return error; -} - -bool git_http_client_has_response(git_http_client *client) -{ - return (client->state == HAS_EARLY_RESPONSE || - client->state > SENT_REQUEST); -} - -int git_http_client_send_body( - git_http_client *client, - const char *buffer, - size_t buffer_len) -{ - git_http_server *server; - git_str hdr = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(client); - - /* If we're waiting for proxy auth, don't sending more requests. */ - if (client->state == HAS_EARLY_RESPONSE) - return 0; - - if (client->state != SENDING_BODY) { - git_error_set(GIT_ERROR_HTTP, "client is in invalid state"); - return -1; - } - - if (!buffer_len) - return 0; - - server = &client->server; - - if (client->request_body_len) { - GIT_ASSERT(buffer_len <= client->request_body_remain); - - if ((error = stream_write(server, buffer, buffer_len)) < 0) - goto done; - - client->request_body_remain -= buffer_len; - } else { - if ((error = git_str_printf(&hdr, "%" PRIxZ "\r\n", buffer_len)) < 0 || - (error = stream_write(server, hdr.ptr, hdr.size)) < 0 || - (error = stream_write(server, buffer, buffer_len)) < 0 || - (error = stream_write(server, "\r\n", 2)) < 0) - goto done; - } - -done: - git_str_dispose(&hdr); - return error; -} - -static int complete_request(git_http_client *client) -{ - int error = 0; - - GIT_ASSERT_ARG(client); - GIT_ASSERT(client->state == SENDING_BODY); - - if (client->request_body_len && client->request_body_remain) { - git_error_set(GIT_ERROR_HTTP, "truncated write"); - error = -1; - } else if (client->request_chunked) { - error = stream_write(&client->server, "0\r\n\r\n", 5); - } - - client->state = SENT_REQUEST; - return error; -} - -int git_http_client_read_response( - git_http_response *response, - git_http_client *client) -{ - http_parser_context parser_context = {0}; - int error; - - GIT_ASSERT_ARG(response); - GIT_ASSERT_ARG(client); - - if (client->state == SENDING_BODY) { - if ((error = complete_request(client)) < 0) - goto done; - } - - if (client->state == HAS_EARLY_RESPONSE) { - memcpy(response, &client->early_response, sizeof(git_http_response)); - memset(&client->early_response, 0, sizeof(git_http_response)); - client->state = DONE; - return 0; - } - - if (client->state != SENT_REQUEST) { - git_error_set(GIT_ERROR_HTTP, "client is in invalid state"); - error = -1; - goto done; - } - - git_http_response_dispose(response); - - if (client->current_server == PROXY) { - git_vector_free_deep(&client->proxy.auth_challenges); - } else if(client->current_server == SERVER) { - git_vector_free_deep(&client->server.auth_challenges); - } - - client->state = READING_RESPONSE; - client->keepalive = 0; - client->parser.data = &parser_context; - - parser_context.client = client; - parser_context.response = response; - - while (client->state == READING_RESPONSE) { - if ((error = client_read_and_parse(client)) < 0) - goto done; - } - - GIT_ASSERT(client->state == READING_BODY || client->state == DONE); - -done: - git_str_dispose(&parser_context.parse_header_name); - git_str_dispose(&parser_context.parse_header_value); - - return error; -} - -int git_http_client_read_body( - git_http_client *client, - char *buffer, - size_t buffer_size) -{ - http_parser_context parser_context = {0}; - int error = 0; - - if (client->state == DONE) - return 0; - - if (client->state != READING_BODY) { - git_error_set(GIT_ERROR_HTTP, "client is in invalid state"); - return -1; - } - - /* - * Now we'll read from the socket and http_parser will pipeline the - * data directly to the client. - */ - - parser_context.client = client; - parser_context.output_buf = buffer; - parser_context.output_size = buffer_size; - - client->parser.data = &parser_context; - - /* - * Clients expect to get a non-zero amount of data from us, - * so we either block until we have data to return, until we - * hit EOF or there's an error. Do this in a loop, since we - * may end up reading only some stream metadata (like chunk - * information). - */ - while (!parser_context.output_written) { - error = client_read_and_parse(client); - - if (error <= 0) - goto done; - - if (client->state == DONE) - break; - } - - GIT_ASSERT(parser_context.output_written <= INT_MAX); - error = (int)parser_context.output_written; - -done: - if (error < 0) - client->connected = 0; - - return error; -} - -int git_http_client_skip_body(git_http_client *client) -{ - http_parser_context parser_context = {0}; - int error; - - if (client->state == DONE) - return 0; - - if (client->state != READING_BODY) { - git_error_set(GIT_ERROR_HTTP, "client is in invalid state"); - return -1; - } - - parser_context.client = client; - client->parser.data = &parser_context; - - do { - error = client_read_and_parse(client); - - if (parser_context.error != HPE_OK || - (parser_context.parse_status != PARSE_STATUS_OK && - parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) { - git_error_set(GIT_ERROR_HTTP, - "unexpected data handled in callback"); - error = -1; - } - } while (error >= 0 && client->state != DONE); - - if (error < 0) - client->connected = 0; - - return error; -} - -/* - * Create an http_client capable of communicating with the given remote - * host. - */ -int git_http_client_new( - git_http_client **out, - git_http_client_options *opts) -{ - git_http_client *client; - - GIT_ASSERT_ARG(out); - - client = git__calloc(1, sizeof(git_http_client)); - GIT_ERROR_CHECK_ALLOC(client); - - git_str_init(&client->read_buf, GIT_READ_BUFFER_SIZE); - GIT_ERROR_CHECK_ALLOC(client->read_buf.ptr); - - if (opts) - memcpy(&client->opts, opts, sizeof(git_http_client_options)); - - *out = client; - return 0; -} - -GIT_INLINE(void) http_server_close(git_http_server *server) -{ - if (server->stream) { - git_stream_close(server->stream); - git_stream_free(server->stream); - server->stream = NULL; - } - - git_net_url_dispose(&server->url); - - git_vector_free_deep(&server->auth_challenges); - free_auth_context(server); -} - -static void http_client_close(git_http_client *client) -{ - http_server_close(&client->server); - http_server_close(&client->proxy); - - git_str_dispose(&client->request_msg); - - client->state = 0; - client->request_count = 0; - client->connected = 0; - client->keepalive = 0; -} - -void git_http_client_free(git_http_client *client) -{ - if (!client) - return; - - http_client_close(client); - git_str_dispose(&client->read_buf); - git__free(client); -} diff --git a/src/transports/httpclient.h b/src/transports/httpclient.h deleted file mode 100644 index 6d0ef9edb..000000000 --- a/src/transports/httpclient.h +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_transports_httpclient_h__ -#define INCLUDE_transports_httpclient_h__ - -#include "common.h" -#include "net.h" - -#define GIT_HTTP_STATUS_CONTINUE 100 -#define GIT_HTTP_STATUS_OK 200 -#define GIT_HTTP_MOVED_PERMANENTLY 301 -#define GIT_HTTP_FOUND 302 -#define GIT_HTTP_SEE_OTHER 303 -#define GIT_HTTP_TEMPORARY_REDIRECT 307 -#define GIT_HTTP_PERMANENT_REDIRECT 308 -#define GIT_HTTP_STATUS_UNAUTHORIZED 401 -#define GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED 407 - -typedef struct git_http_client git_http_client; - -/** Method for the HTTP request */ -typedef enum { - GIT_HTTP_METHOD_GET, - GIT_HTTP_METHOD_POST, - GIT_HTTP_METHOD_CONNECT -} git_http_method; - -/** An HTTP request */ -typedef struct { - git_http_method method; /**< Method for the request */ - git_net_url *url; /**< Full request URL */ - git_net_url *proxy; /**< Proxy to use */ - - /* Headers */ - const char *accept; /**< Contents of the Accept header */ - const char *content_type; /**< Content-Type header (for POST) */ - git_credential *credentials; /**< Credentials to authenticate with */ - git_credential *proxy_credentials; /**< Credentials for proxy */ - git_strarray *custom_headers; /**< Additional headers to deliver */ - - /* To POST a payload, either set content_length OR set chunked. */ - size_t content_length; /**< Length of the POST body */ - unsigned chunked : 1, /**< Post with chunking */ - expect_continue : 1; /**< Use expect/continue negotiation */ -} git_http_request; - -typedef struct { - int status; - - /* Headers */ - char *content_type; - size_t content_length; - char *location; - - /* Authentication headers */ - unsigned server_auth_schemetypes; /**< Schemes requested by remote */ - unsigned server_auth_credtypes; /**< Supported cred types for remote */ - - unsigned proxy_auth_schemetypes; /**< Schemes requested by proxy */ - unsigned proxy_auth_credtypes; /**< Supported cred types for proxy */ - - unsigned chunked : 1, /**< Response body is chunked */ - resend_credentials : 1; /**< Resend with authentication */ -} git_http_response; - -typedef struct { - /** Certificate check callback for the remote */ - git_transport_certificate_check_cb server_certificate_check_cb; - void *server_certificate_check_payload; - - /** Certificate check callback for the proxy */ - git_transport_certificate_check_cb proxy_certificate_check_cb; - void *proxy_certificate_check_payload; -} git_http_client_options; - -/** - * Create a new httpclient instance with the given options. - * - * @param out pointer to receive the new instance - * @param opts options to create the client with or NULL for defaults - */ -extern int git_http_client_new( - git_http_client **out, - git_http_client_options *opts); - -/* - * Sends a request to the host specified by the request URL. If the - * method is POST, either the content_length or the chunked flag must - * be specified. The body should be provided in subsequent calls to - * git_http_client_send_body. - * - * @param client the client to write the request to - * @param request the request to send - */ -extern int git_http_client_send_request( - git_http_client *client, - git_http_request *request); - -/* - * After sending a request, there may already be a response to read -- - * either because there was a non-continue response to an expect: continue - * request, or because the server pipelined a response to us before we even - * sent the request. Examine the state. - * - * @param client the client to examine - * @return true if there's already a response to read, false otherwise - */ -extern bool git_http_client_has_response(git_http_client *client); - -/** - * Sends the given buffer to the remote as part of the request body. The - * request must have specified either a content_length or the chunked flag. - * - * @param client the client to write the request body to - * @param buffer the request body - * @param buffer_len number of bytes of the buffer to send - */ -extern int git_http_client_send_body( - git_http_client *client, - const char *buffer, - size_t buffer_len); - -/** - * Reads the headers of a response to a request. This will consume the - * entirety of the headers of a response from the server. The body (if any) - * can be read by calling git_http_client_read_body. Callers must free - * the response with git_http_response_dispose. - * - * @param response pointer to the response object to fill - * @param client the client to read the response from - */ -extern int git_http_client_read_response( - git_http_response *response, - git_http_client *client); - -/** - * Reads some or all of the body of a response. At most buffer_size (or - * INT_MAX) bytes will be read and placed into the buffer provided. The - * number of bytes read will be returned, or 0 to indicate that the end of - * the body has been read. - * - * @param client the client to read the response from - * @param buffer pointer to the buffer to fill - * @param buffer_size the maximum number of bytes to read - * @return the number of bytes read, 0 on end of body, or error code - */ -extern int git_http_client_read_body( - git_http_client *client, - char *buffer, - size_t buffer_size); - -/** - * Reads all of the (remainder of the) body of the response and ignores it. - * None of the data from the body will be returned to the caller. - * - * @param client the client to read the response from - * @return 0 or an error code - */ -extern int git_http_client_skip_body(git_http_client *client); - -/** - * Examines the status code of the response to determine if it is a - * redirect of any type (eg, 301, 302, etc). - * - * @param response the response to inspect - * @return true if the response is a redirect, false otherwise - */ -extern bool git_http_response_is_redirect(git_http_response *response); - -/** - * Frees any memory associated with the response. - * - * @param response the response to free - */ -extern void git_http_response_dispose(git_http_response *response); - -/** - * Frees any memory associated with the client. If any sockets are open, - * they will be closed. - * - * @param client the client to free - */ -extern void git_http_client_free(git_http_client *client); - -#endif diff --git a/src/transports/local.c b/src/transports/local.c deleted file mode 100644 index 6c754a034..000000000 --- a/src/transports/local.c +++ /dev/null @@ -1,754 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "pack-objects.h" -#include "refs.h" -#include "posix.h" -#include "fs_path.h" -#include "repository.h" -#include "odb.h" -#include "push.h" -#include "remote.h" -#include "proxy.h" - -#include "git2/types.h" -#include "git2/net.h" -#include "git2/repository.h" -#include "git2/object.h" -#include "git2/tag.h" -#include "git2/transport.h" -#include "git2/revwalk.h" -#include "git2/odb_backend.h" -#include "git2/pack.h" -#include "git2/commit.h" -#include "git2/revparse.h" -#include "git2/sys/remote.h" - -typedef struct { - git_transport parent; - git_remote *owner; - char *url; - int direction; - git_atomic32 cancelled; - git_repository *repo; - git_remote_connect_options connect_opts; - git_vector refs; - unsigned connected : 1, - have_refs : 1; -} transport_local; - -static void free_head(git_remote_head *head) -{ - git__free(head->name); - git__free(head->symref_target); - git__free(head); -} - -static void free_heads(git_vector *heads) -{ - git_remote_head *head; - size_t i; - - git_vector_foreach(heads, i, head) - free_head(head); - - git_vector_free(heads); -} - -static int add_ref(transport_local *t, const char *name) -{ - const char peeled[] = "^{}"; - git_reference *ref, *resolved; - git_remote_head *head; - git_oid obj_id; - git_object *obj = NULL, *target = NULL; - git_str buf = GIT_STR_INIT; - int error; - - if ((error = git_reference_lookup(&ref, t->repo, name)) < 0) - return error; - - error = git_reference_resolve(&resolved, ref); - if (error < 0) { - git_reference_free(ref); - if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) { - /* This is actually okay. Empty repos often have a HEAD that - * points to a nonexistent "refs/heads/master". */ - git_error_clear(); - return 0; - } - return error; - } - - git_oid_cpy(&obj_id, git_reference_target(resolved)); - git_reference_free(resolved); - - head = git__calloc(1, sizeof(git_remote_head)); - GIT_ERROR_CHECK_ALLOC(head); - - head->name = git__strdup(name); - GIT_ERROR_CHECK_ALLOC(head->name); - - git_oid_cpy(&head->oid, &obj_id); - - if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { - head->symref_target = git__strdup(git_reference_symbolic_target(ref)); - GIT_ERROR_CHECK_ALLOC(head->symref_target); - } - git_reference_free(ref); - - if ((error = git_vector_insert(&t->refs, head)) < 0) { - free_head(head); - return error; - } - - /* If it's not a tag, we don't need to try to peel it */ - if (git__prefixcmp(name, GIT_REFS_TAGS_DIR)) - return 0; - - if ((error = git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJECT_ANY)) < 0) - return error; - - head = NULL; - - /* If it's not an annotated tag, or if we're mocking - * git-receive-pack, just get out */ - if (git_object_type(obj) != GIT_OBJECT_TAG || - t->direction != GIT_DIRECTION_FETCH) { - git_object_free(obj); - return 0; - } - - /* And if it's a tag, peel it, and add it to the list */ - head = git__calloc(1, sizeof(git_remote_head)); - GIT_ERROR_CHECK_ALLOC(head); - - if (git_str_join(&buf, 0, name, peeled) < 0) { - free_head(head); - return -1; - } - head->name = git_str_detach(&buf); - - if (!(error = git_tag_peel(&target, (git_tag *)obj))) { - git_oid_cpy(&head->oid, git_object_id(target)); - - if ((error = git_vector_insert(&t->refs, head)) < 0) { - free_head(head); - } - } - - git_object_free(obj); - git_object_free(target); - - return error; -} - -static int store_refs(transport_local *t) -{ - size_t i; - git_remote_head *head; - git_strarray ref_names = {0}; - - GIT_ASSERT_ARG(t); - - if (git_reference_list(&ref_names, t->repo) < 0) - goto on_error; - - /* Clear all heads we might have fetched in a previous connect */ - git_vector_foreach(&t->refs, i, head) { - git__free(head->name); - git__free(head); - } - - /* Clear the vector so we can reuse it */ - git_vector_clear(&t->refs); - - /* Sort the references first */ - git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb); - - /* Add HEAD iff direction is fetch */ - if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0) - goto on_error; - - for (i = 0; i < ref_names.count; ++i) { - if (add_ref(t, ref_names.strings[i]) < 0) - goto on_error; - } - - t->have_refs = 1; - git_strarray_dispose(&ref_names); - return 0; - -on_error: - git_vector_free(&t->refs); - git_strarray_dispose(&ref_names); - return -1; -} - -/* - * Try to open the url as a git directory. The direction doesn't - * matter in this case because we're calculating the heads ourselves. - */ -static int local_connect( - git_transport *transport, - const char *url, - int direction, - const git_remote_connect_options *connect_opts) -{ - git_repository *repo; - int error; - transport_local *t = (transport_local *)transport; - const char *path; - git_str buf = GIT_STR_INIT; - - if (t->connected) - return 0; - - if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0) - return -1; - - free_heads(&t->refs); - - t->url = git__strdup(url); - GIT_ERROR_CHECK_ALLOC(t->url); - t->direction = direction; - - /* 'url' may be a url or path; convert to a path */ - if ((error = git_fs_path_from_url_or_path(&buf, url)) < 0) { - git_str_dispose(&buf); - return error; - } - path = git_str_cstr(&buf); - - error = git_repository_open(&repo, path); - - git_str_dispose(&buf); - - if (error < 0) - return -1; - - t->repo = repo; - - if (store_refs(t) < 0) - return -1; - - t->connected = 1; - - return 0; -} - -static int local_set_connect_opts( - git_transport *transport, - const git_remote_connect_options *connect_opts) -{ - transport_local *t = (transport_local *)transport; - - if (!t->connected) { - git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected"); - return -1; - } - - return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts); -} - -static int local_capabilities(unsigned int *capabilities, git_transport *transport) -{ - GIT_UNUSED(transport); - - *capabilities = GIT_REMOTE_CAPABILITY_TIP_OID | - GIT_REMOTE_CAPABILITY_REACHABLE_OID; - return 0; -} - -static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport) -{ - transport_local *t = (transport_local *)transport; - - if (!t->have_refs) { - git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs"); - return -1; - } - - *out = (const git_remote_head **)t->refs.contents; - *size = t->refs.length; - - return 0; -} - -static int local_negotiate_fetch( - git_transport *transport, - git_repository *repo, - const git_remote_head * const *refs, - size_t count) -{ - transport_local *t = (transport_local*)transport; - git_remote_head *rhead; - unsigned int i; - - GIT_UNUSED(refs); - GIT_UNUSED(count); - - /* Fill in the loids */ - git_vector_foreach(&t->refs, i, rhead) { - git_object *obj; - - int error = git_revparse_single(&obj, repo, rhead->name); - if (!error) - git_oid_cpy(&rhead->loid, git_object_id(obj)); - else if (error != GIT_ENOTFOUND) - return error; - else - git_error_clear(); - git_object_free(obj); - } - - return 0; -} - -static int local_push_update_remote_ref( - git_repository *remote_repo, - const char *lref, - const char *rref, - git_oid *loid, - git_oid *roid) -{ - int error; - git_reference *remote_ref = NULL; - - /* check for lhs, if it's empty it means to delete */ - if (lref[0] != '\0') { - /* Create or update a ref */ - error = git_reference_create(NULL, remote_repo, rref, loid, - !git_oid_is_zero(roid), NULL); - } else { - /* Delete a ref */ - if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) { - if (error == GIT_ENOTFOUND) - error = 0; - return error; - } - - error = git_reference_delete(remote_ref); - git_reference_free(remote_ref); - } - - return error; -} - -static int transfer_to_push_transfer(const git_indexer_progress *stats, void *payload) -{ - const git_remote_callbacks *cbs = payload; - - if (!cbs || !cbs->push_transfer_progress) - return 0; - - return cbs->push_transfer_progress(stats->received_objects, stats->total_objects, stats->received_bytes, - cbs->payload); -} - -static int local_push( - git_transport *transport, - git_push *push) -{ - transport_local *t = (transport_local *)transport; - git_remote_callbacks *cbs = &t->connect_opts.callbacks; - git_repository *remote_repo = NULL; - push_spec *spec; - char *url = NULL; - const char *path; - git_str buf = GIT_STR_INIT, odb_path = GIT_STR_INIT; - int error; - size_t j; - - /* 'push->remote->url' may be a url or path; convert to a path */ - if ((error = git_fs_path_from_url_or_path(&buf, push->remote->url)) < 0) { - git_str_dispose(&buf); - return error; - } - path = git_str_cstr(&buf); - - error = git_repository_open(&remote_repo, path); - - git_str_dispose(&buf); - - if (error < 0) - return error; - - /* We don't currently support pushing locally to non-bare repos. Proper - non-bare repo push support would require checking configs to see if - we should override the default 'don't let this happen' behavior. - - Note that this is only an issue when pushing to the current branch, - but we forbid all pushes just in case */ - if (!remote_repo->is_bare) { - error = GIT_EBAREREPO; - git_error_set(GIT_ERROR_INVALID, "local push doesn't (yet) support pushing to non-bare repos."); - goto on_error; - } - - if ((error = git_repository__item_path(&odb_path, remote_repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0 - || (error = git_str_joinpath(&odb_path, odb_path.ptr, "pack")) < 0) - goto on_error; - - error = git_packbuilder_write(push->pb, odb_path.ptr, 0, transfer_to_push_transfer, (void *) cbs); - git_str_dispose(&odb_path); - - if (error < 0) - goto on_error; - - push->unpack_ok = 1; - - git_vector_foreach(&push->specs, j, spec) { - push_status *status; - const git_error *last; - char *ref = spec->refspec.dst; - - status = git__calloc(1, sizeof(push_status)); - if (!status) - goto on_error; - - status->ref = git__strdup(ref); - if (!status->ref) { - git_push_status_free(status); - goto on_error; - } - - error = local_push_update_remote_ref(remote_repo, spec->refspec.src, spec->refspec.dst, - &spec->loid, &spec->roid); - - switch (error) { - case GIT_OK: - break; - case GIT_EINVALIDSPEC: - status->msg = git__strdup("funny refname"); - break; - case GIT_ENOTFOUND: - status->msg = git__strdup("Remote branch not found to delete"); - break; - default: - last = git_error_last(); - - if (last && last->message) - status->msg = git__strdup(last->message); - else - status->msg = git__strdup("Unspecified error encountered"); - break; - } - - /* failed to allocate memory for a status message */ - if (error < 0 && !status->msg) { - git_push_status_free(status); - goto on_error; - } - - /* failed to insert the ref update status */ - if ((error = git_vector_insert(&push->status, status)) < 0) { - git_push_status_free(status); - goto on_error; - } - } - - if (push->specs.length) { - url = git__strdup(t->url); - - if (!url || t->parent.close(&t->parent) < 0 || - t->parent.connect(&t->parent, url, - GIT_DIRECTION_PUSH, NULL)) - goto on_error; - } - - error = 0; - -on_error: - git_repository_free(remote_repo); - git__free(url); - - return error; -} - -typedef struct foreach_data { - git_indexer_progress *stats; - git_indexer_progress_cb progress_cb; - void *progress_payload; - git_odb_writepack *writepack; -} foreach_data; - -static int foreach_cb(void *buf, size_t len, void *payload) -{ - foreach_data *data = (foreach_data*)payload; - - data->stats->received_bytes += len; - return data->writepack->append(data->writepack, buf, len, data->stats); -} - -static const char *counting_objects_fmt = "Counting objects %d\r"; -static const char *compressing_objects_fmt = "Compressing objects: %.0f%% (%d/%d)"; - -static int local_counting(int stage, unsigned int current, unsigned int total, void *payload) -{ - git_str progress_info = GIT_STR_INIT; - transport_local *t = payload; - int error; - - if (!t->connect_opts.callbacks.sideband_progress) - return 0; - - if (stage == GIT_PACKBUILDER_ADDING_OBJECTS) { - git_str_printf(&progress_info, counting_objects_fmt, current); - } else if (stage == GIT_PACKBUILDER_DELTAFICATION) { - float perc = (((float) current) / total) * 100; - git_str_printf(&progress_info, compressing_objects_fmt, perc, current, total); - if (current == total) - git_str_printf(&progress_info, ", done\n"); - else - git_str_putc(&progress_info, '\r'); - - } - - if (git_str_oom(&progress_info)) - return -1; - - if (progress_info.size > INT_MAX) { - git_error_set(GIT_ERROR_NET, "remote sent overly large progress data"); - git_str_dispose(&progress_info); - return -1; - } - - - error = t->connect_opts.callbacks.sideband_progress( - progress_info.ptr, - (int)progress_info.size, - t->connect_opts.callbacks.payload); - - git_str_dispose(&progress_info); - return error; -} - -static int foreach_reference_cb(git_reference *reference, void *payload) -{ - git_revwalk *walk = (git_revwalk *)payload; - int error; - - if (git_reference_type(reference) != GIT_REFERENCE_DIRECT) { - git_reference_free(reference); - return 0; - } - - error = git_revwalk_hide(walk, git_reference_target(reference)); - /* The reference is in the local repository, so the target may not - * exist on the remote. It also may not be a commit. */ - if (error == GIT_ENOTFOUND || error == GIT_ERROR_INVALID) { - git_error_clear(); - error = 0; - } - - git_reference_free(reference); - - return error; -} - -static int local_download_pack( - git_transport *transport, - git_repository *repo, - git_indexer_progress *stats) -{ - transport_local *t = (transport_local*)transport; - git_revwalk *walk = NULL; - git_remote_head *rhead; - unsigned int i; - int error = -1; - git_packbuilder *pack = NULL; - git_odb_writepack *writepack = NULL; - git_odb *odb = NULL; - git_str progress_info = GIT_STR_INIT; - foreach_data data = {0}; - - if ((error = git_revwalk_new(&walk, t->repo)) < 0) - goto cleanup; - - git_revwalk_sorting(walk, GIT_SORT_TIME); - - if ((error = git_packbuilder_new(&pack, t->repo)) < 0) - goto cleanup; - - git_packbuilder_set_callbacks(pack, local_counting, t); - - stats->total_objects = 0; - stats->indexed_objects = 0; - stats->received_objects = 0; - stats->received_bytes = 0; - - git_vector_foreach(&t->refs, i, rhead) { - git_object *obj; - if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJECT_ANY)) < 0) - goto cleanup; - - if (git_object_type(obj) == GIT_OBJECT_COMMIT) { - /* Revwalker includes only wanted commits */ - error = git_revwalk_push(walk, &rhead->oid); - } else { - /* Tag or some other wanted object. Add it on its own */ - error = git_packbuilder_insert_recur(pack, &rhead->oid, rhead->name); - } - git_object_free(obj); - if (error < 0) - goto cleanup; - } - - if ((error = git_reference_foreach(repo, foreach_reference_cb, walk))) - goto cleanup; - - if ((error = git_packbuilder_insert_walk(pack, walk))) - goto cleanup; - - if (t->connect_opts.callbacks.sideband_progress) { - if ((error = git_str_printf( - &progress_info, - counting_objects_fmt, - git_packbuilder_object_count(pack))) < 0 || - (error = t->connect_opts.callbacks.sideband_progress( - progress_info.ptr, - (int)progress_info.size, - t->connect_opts.callbacks.payload)) < 0) - goto cleanup; - } - - /* Walk the objects, building a packfile */ - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) - goto cleanup; - - /* One last one with the newline */ - if (t->connect_opts.callbacks.sideband_progress) { - git_str_clear(&progress_info); - - if ((error = git_str_printf( - &progress_info, - counting_objects_fmt, - git_packbuilder_object_count(pack))) < 0 || - (error = git_str_putc(&progress_info, '\n')) < 0 || - (error = t->connect_opts.callbacks.sideband_progress( - progress_info.ptr, - (int)progress_info.size, - t->connect_opts.callbacks.payload)) < 0) - goto cleanup; - } - - if ((error = git_odb_write_pack( - &writepack, - odb, - t->connect_opts.callbacks.transfer_progress, - t->connect_opts.callbacks.payload)) < 0) - goto cleanup; - - /* Write the data to the ODB */ - data.stats = stats; - data.progress_cb = t->connect_opts.callbacks.transfer_progress; - data.progress_payload = t->connect_opts.callbacks.payload; - data.writepack = writepack; - - /* autodetect */ - git_packbuilder_set_threads(pack, 0); - - if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) != 0) - goto cleanup; - - error = writepack->commit(writepack, stats); - -cleanup: - if (writepack) writepack->free(writepack); - git_str_dispose(&progress_info); - git_packbuilder_free(pack); - git_revwalk_free(walk); - return error; -} - -static int local_is_connected(git_transport *transport) -{ - transport_local *t = (transport_local *)transport; - - return t->connected; -} - -static void local_cancel(git_transport *transport) -{ - transport_local *t = (transport_local *)transport; - - git_atomic32_set(&t->cancelled, 1); -} - -static int local_close(git_transport *transport) -{ - transport_local *t = (transport_local *)transport; - - t->connected = 0; - - if (t->repo) { - git_repository_free(t->repo); - t->repo = NULL; - } - - if (t->url) { - git__free(t->url); - t->url = NULL; - } - - return 0; -} - -static void local_free(git_transport *transport) -{ - transport_local *t = (transport_local *)transport; - - free_heads(&t->refs); - - /* Close the transport, if it's still open. */ - local_close(transport); - - /* Free the transport */ - git__free(t); -} - -/************** - * Public API * - **************/ - -int git_transport_local(git_transport **out, git_remote *owner, void *param) -{ - int error; - transport_local *t; - - GIT_UNUSED(param); - - t = git__calloc(1, sizeof(transport_local)); - GIT_ERROR_CHECK_ALLOC(t); - - t->parent.version = GIT_TRANSPORT_VERSION; - t->parent.connect = local_connect; - t->parent.set_connect_opts = local_set_connect_opts; - t->parent.capabilities = local_capabilities; - t->parent.negotiate_fetch = local_negotiate_fetch; - t->parent.download_pack = local_download_pack; - t->parent.push = local_push; - t->parent.close = local_close; - t->parent.free = local_free; - t->parent.ls = local_ls; - t->parent.is_connected = local_is_connected; - t->parent.cancel = local_cancel; - - if ((error = git_vector_init(&t->refs, 0, NULL)) < 0) { - git__free(t); - return error; - } - - t->owner = owner; - - *out = (git_transport *) t; - - return 0; -} diff --git a/src/transports/smart.c b/src/transports/smart.c deleted file mode 100644 index 801fcbe53..000000000 --- a/src/transports/smart.c +++ /dev/null @@ -1,472 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "smart.h" - -#include "git2.h" -#include "git2/sys/remote.h" -#include "refs.h" -#include "refspec.h" -#include "proxy.h" - -static int git_smart__recv_cb(gitno_buffer *buf) -{ - transport_smart *t = (transport_smart *) buf->cb_data; - size_t old_len, bytes_read; - int error; - - GIT_ASSERT(t->current_stream); - - old_len = buf->offset; - - if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0) - return error; - - buf->offset += bytes_read; - - if (t->packetsize_cb && !t->cancelled.val) { - error = t->packetsize_cb(bytes_read, t->packetsize_payload); - if (error) { - git_atomic32_set(&t->cancelled, 1); - return GIT_EUSER; - } - } - - return (int)(buf->offset - old_len); -} - -GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport) -{ - if (t->current_stream) { - t->current_stream->free(t->current_stream); - t->current_stream = NULL; - } - - if (close_subtransport) { - git__free(t->url); - t->url = NULL; - - if (t->wrapped->close(t->wrapped) < 0) - return -1; - } - - return 0; -} - -int git_smart__update_heads(transport_smart *t, git_vector *symrefs) -{ - size_t i; - git_pkt *pkt; - - git_vector_clear(&t->heads); - git_vector_foreach(&t->refs, i, pkt) { - git_pkt_ref *ref = (git_pkt_ref *) pkt; - if (pkt->type != GIT_PKT_REF) - continue; - - if (symrefs) { - git_refspec *spec; - git_str buf = GIT_STR_INIT; - size_t j; - int error = 0; - - git_vector_foreach(symrefs, j, spec) { - git_str_clear(&buf); - if (git_refspec_src_matches(spec, ref->head.name) && - !(error = git_refspec__transform(&buf, spec, ref->head.name))) { - git__free(ref->head.symref_target); - ref->head.symref_target = git_str_detach(&buf); - } - } - - git_str_dispose(&buf); - - if (error < 0) - return error; - } - - if (git_vector_insert(&t->heads, &ref->head) < 0) - return -1; - } - - return 0; -} - -static void free_symrefs(git_vector *symrefs) -{ - git_refspec *spec; - size_t i; - - git_vector_foreach(symrefs, i, spec) { - git_refspec__dispose(spec); - git__free(spec); - } - - git_vector_free(symrefs); -} - -static int git_smart__connect( - git_transport *transport, - const char *url, - int direction, - const git_remote_connect_options *connect_opts) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - git_smart_subtransport_stream *stream; - int error; - git_pkt *pkt; - git_pkt_ref *first; - git_vector symrefs; - git_smart_service_t service; - - if (git_smart__reset_stream(t, true) < 0) - return -1; - - if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0) - return -1; - - t->url = git__strdup(url); - GIT_ERROR_CHECK_ALLOC(t->url); - - t->direction = direction; - - if (GIT_DIRECTION_FETCH == t->direction) { - service = GIT_SERVICE_UPLOADPACK_LS; - } else if (GIT_DIRECTION_PUSH == t->direction) { - service = GIT_SERVICE_RECEIVEPACK_LS; - } else { - git_error_set(GIT_ERROR_NET, "invalid direction"); - return -1; - } - - if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0) - return error; - - /* Save off the current stream (i.e. socket) that we are working with */ - t->current_stream = stream; - - gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); - - /* 2 flushes for RPC; 1 for stateful */ - if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0) - return error; - - /* Strip the comment packet for RPC */ - if (t->rpc) { - pkt = (git_pkt *)git_vector_get(&t->refs, 0); - - if (!pkt || GIT_PKT_COMMENT != pkt->type) { - git_error_set(GIT_ERROR_NET, "invalid response"); - return -1; - } else { - /* Remove the comment pkt from the list */ - git_vector_remove(&t->refs, 0); - git__free(pkt); - } - } - - /* We now have loaded the refs. */ - t->have_refs = 1; - - pkt = (git_pkt *)git_vector_get(&t->refs, 0); - if (pkt && GIT_PKT_REF != pkt->type) { - git_error_set(GIT_ERROR_NET, "invalid response"); - return -1; - } - first = (git_pkt_ref *)pkt; - - if ((error = git_vector_init(&symrefs, 1, NULL)) < 0) - return error; - - /* Detect capabilities */ - if ((error = git_smart__detect_caps(first, &t->caps, &symrefs)) == 0) { - /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */ - if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") && - git_oid_is_zero(&first->head.oid)) { - git_vector_clear(&t->refs); - git_pkt_free((git_pkt *)first); - } - - /* Keep a list of heads for _ls */ - git_smart__update_heads(t, &symrefs); - } else if (error == GIT_ENOTFOUND) { - /* There was no ref packet received, or the cap list was empty */ - error = 0; - } else { - git_error_set(GIT_ERROR_NET, "invalid response"); - goto cleanup; - } - - if (t->rpc && (error = git_smart__reset_stream(t, false)) < 0) - goto cleanup; - - /* We're now logically connected. */ - t->connected = 1; - -cleanup: - free_symrefs(&symrefs); - - return error; -} - -static int git_smart__set_connect_opts( - git_transport *transport, - const git_remote_connect_options *opts) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - - if (!t->connected) { - git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected"); - return -1; - } - - return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, opts); -} - -static int git_smart__capabilities(unsigned int *capabilities, git_transport *transport) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - - *capabilities = 0; - - if (t->caps.want_tip_sha1) - *capabilities |= GIT_REMOTE_CAPABILITY_TIP_OID; - - if (t->caps.want_reachable_sha1) - *capabilities |= GIT_REMOTE_CAPABILITY_REACHABLE_OID; - - return 0; -} - -static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - - if (!t->have_refs) { - git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs"); - return -1; - } - - *out = (const git_remote_head **) t->heads.contents; - *size = t->heads.length; - - return 0; -} - -int git_smart__negotiation_step(git_transport *transport, void *data, size_t len) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - git_smart_subtransport_stream *stream; - int error; - - if (t->rpc && git_smart__reset_stream(t, false) < 0) - return -1; - - if (GIT_DIRECTION_FETCH != t->direction) { - git_error_set(GIT_ERROR_NET, "this operation is only valid for fetch"); - return -1; - } - - if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0) - return error; - - /* If this is a stateful implementation, the stream we get back should be the same */ - GIT_ASSERT(t->rpc || t->current_stream == stream); - - /* Save off the current stream (i.e. socket) that we are working with */ - t->current_stream = stream; - - if ((error = stream->write(stream, (const char *)data, len)) < 0) - return error; - - gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); - - return 0; -} - -int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream) -{ - int error; - - if (t->rpc && git_smart__reset_stream(t, false) < 0) - return -1; - - if (GIT_DIRECTION_PUSH != t->direction) { - git_error_set(GIT_ERROR_NET, "this operation is only valid for push"); - return -1; - } - - if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0) - return error; - - /* If this is a stateful implementation, the stream we get back should be the same */ - GIT_ASSERT(t->rpc || t->current_stream == *stream); - - /* Save off the current stream (i.e. socket) that we are working with */ - t->current_stream = *stream; - - gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); - - return 0; -} - -static void git_smart__cancel(git_transport *transport) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - - git_atomic32_set(&t->cancelled, 1); -} - -static int git_smart__is_connected(git_transport *transport) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - - return t->connected; -} - -static int git_smart__close(git_transport *transport) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - git_vector *common = &t->common; - unsigned int i; - git_pkt *p; - int ret; - git_smart_subtransport_stream *stream; - const char flush[] = "0000"; - - /* - * If we're still connected at this point and not using RPC, - * we should say goodbye by sending a flush, or git-daemon - * will complain that we disconnected unexpectedly. - */ - if (t->connected && !t->rpc && - !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) { - t->current_stream->write(t->current_stream, flush, 4); - } - - ret = git_smart__reset_stream(t, true); - - git_vector_foreach(common, i, p) - git_pkt_free(p); - - git_vector_free(common); - - if (t->url) { - git__free(t->url); - t->url = NULL; - } - - t->connected = 0; - - return ret; -} - -static void git_smart__free(git_transport *transport) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - git_vector *refs = &t->refs; - unsigned int i; - git_pkt *p; - - /* Make sure that the current stream is closed, if we have one. */ - git_smart__close(transport); - - /* Free the subtransport */ - t->wrapped->free(t->wrapped); - - git_vector_free(&t->heads); - git_vector_foreach(refs, i, p) - git_pkt_free(p); - - git_vector_free(refs); - - git_remote_connect_options_dispose(&t->connect_opts); - - git__free(t); -} - -static int ref_name_cmp(const void *a, const void *b) -{ - const git_pkt_ref *ref_a = a, *ref_b = b; - - return strcmp(ref_a->head.name, ref_b->head.name); -} - -int git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - git_remote_connect_options *connect_opts = &t->connect_opts; - - GIT_ASSERT_ARG(transport); - GIT_ASSERT_ARG(cert); - GIT_ASSERT_ARG(hostname); - - if (!connect_opts->callbacks.certificate_check) - return GIT_PASSTHROUGH; - - return connect_opts->callbacks.certificate_check(cert, valid, hostname, connect_opts->callbacks.payload); -} - -int git_transport_smart_credentials(git_credential **out, git_transport *transport, const char *user, int methods) -{ - transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); - git_remote_connect_options *connect_opts = &t->connect_opts; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(transport); - - if (!connect_opts->callbacks.credentials) - return GIT_PASSTHROUGH; - - return connect_opts->callbacks.credentials(out, t->url, user, methods, connect_opts->callbacks.payload); -} - -int git_transport_smart(git_transport **out, git_remote *owner, void *param) -{ - transport_smart *t; - git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param; - - if (!param) - return -1; - - t = git__calloc(1, sizeof(transport_smart)); - GIT_ERROR_CHECK_ALLOC(t); - - t->parent.version = GIT_TRANSPORT_VERSION; - t->parent.connect = git_smart__connect; - t->parent.set_connect_opts = git_smart__set_connect_opts; - t->parent.capabilities = git_smart__capabilities; - t->parent.close = git_smart__close; - t->parent.free = git_smart__free; - t->parent.negotiate_fetch = git_smart__negotiate_fetch; - t->parent.download_pack = git_smart__download_pack; - t->parent.push = git_smart__push; - t->parent.ls = git_smart__ls; - t->parent.is_connected = git_smart__is_connected; - t->parent.cancel = git_smart__cancel; - - t->owner = owner; - t->rpc = definition->rpc; - - if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) { - git__free(t); - return -1; - } - - if (git_vector_init(&t->heads, 16, ref_name_cmp) < 0) { - git__free(t); - return -1; - } - - if (definition->callback(&t->wrapped, &t->parent, definition->param) < 0) { - git__free(t); - return -1; - } - - *out = (git_transport *) t; - return 0; -} diff --git a/src/transports/smart.h b/src/transports/smart.h deleted file mode 100644 index 9323d6c44..000000000 --- a/src/transports/smart.h +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_transports_smart_h__ -#define INCLUDE_transports_smart_h__ - -#include "common.h" - -#include "git2.h" -#include "vector.h" -#include "netops.h" -#include "push.h" -#include "str.h" -#include "git2/sys/transport.h" - -#define GIT_SIDE_BAND_DATA 1 -#define GIT_SIDE_BAND_PROGRESS 2 -#define GIT_SIDE_BAND_ERROR 3 - -#define GIT_CAP_OFS_DELTA "ofs-delta" -#define GIT_CAP_MULTI_ACK "multi_ack" -#define GIT_CAP_MULTI_ACK_DETAILED "multi_ack_detailed" -#define GIT_CAP_SIDE_BAND "side-band" -#define GIT_CAP_SIDE_BAND_64K "side-band-64k" -#define GIT_CAP_INCLUDE_TAG "include-tag" -#define GIT_CAP_DELETE_REFS "delete-refs" -#define GIT_CAP_REPORT_STATUS "report-status" -#define GIT_CAP_THIN_PACK "thin-pack" -#define GIT_CAP_SYMREF "symref" -#define GIT_CAP_WANT_TIP_SHA1 "allow-tip-sha1-in-want" -#define GIT_CAP_WANT_REACHABLE_SHA1 "allow-reachable-sha1-in-want" - -extern bool git_smart__ofs_delta_enabled; - -typedef enum { - GIT_PKT_CMD, - GIT_PKT_FLUSH, - GIT_PKT_REF, - GIT_PKT_HAVE, - GIT_PKT_ACK, - GIT_PKT_NAK, - GIT_PKT_COMMENT, - GIT_PKT_ERR, - GIT_PKT_DATA, - GIT_PKT_PROGRESS, - GIT_PKT_OK, - GIT_PKT_NG, - GIT_PKT_UNPACK -} git_pkt_type; - -/* Used for multi_ack and multi_ack_detailed */ -enum git_ack_status { - GIT_ACK_NONE, - GIT_ACK_CONTINUE, - GIT_ACK_COMMON, - GIT_ACK_READY -}; - -/* This would be a flush pkt */ -typedef struct { - git_pkt_type type; -} git_pkt; - -struct git_pkt_cmd { - git_pkt_type type; - char *cmd; - char *path; - char *host; -}; - -/* This is a pkt-line with some info in it */ -typedef struct { - git_pkt_type type; - git_remote_head head; - char *capabilities; -} git_pkt_ref; - -/* Useful later */ -typedef struct { - git_pkt_type type; - git_oid oid; - enum git_ack_status status; -} git_pkt_ack; - -typedef struct { - git_pkt_type type; - char comment[GIT_FLEX_ARRAY]; -} git_pkt_comment; - -typedef struct { - git_pkt_type type; - size_t len; - char data[GIT_FLEX_ARRAY]; -} git_pkt_data; - -typedef git_pkt_data git_pkt_progress; - -typedef struct { - git_pkt_type type; - size_t len; - char error[GIT_FLEX_ARRAY]; -} git_pkt_err; - -typedef struct { - git_pkt_type type; - char *ref; -} git_pkt_ok; - -typedef struct { - git_pkt_type type; - char *ref; - char *msg; -} git_pkt_ng; - -typedef struct { - git_pkt_type type; - int unpack_ok; -} git_pkt_unpack; - -typedef struct transport_smart_caps { - unsigned int common:1, - ofs_delta:1, - multi_ack:1, - multi_ack_detailed:1, - side_band:1, - side_band_64k:1, - include_tag:1, - delete_refs:1, - report_status:1, - thin_pack:1, - want_tip_sha1:1, - want_reachable_sha1:1; -} transport_smart_caps; - -typedef int (*packetsize_cb)(size_t received, void *payload); - -typedef struct { - git_transport parent; - git_remote *owner; - char *url; - git_remote_connect_options connect_opts; - int direction; - git_smart_subtransport *wrapped; - git_smart_subtransport_stream *current_stream; - transport_smart_caps caps; - git_vector refs; - git_vector heads; - git_vector common; - git_atomic32 cancelled; - packetsize_cb packetsize_cb; - void *packetsize_payload; - unsigned rpc : 1, - have_refs : 1, - connected : 1; - gitno_buffer buffer; - char buffer_data[65536]; -} transport_smart; - -/* smart_protocol.c */ -int git_smart__store_refs(transport_smart *t, int flushes); -int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs); -int git_smart__push(git_transport *transport, git_push *push); - -int git_smart__negotiate_fetch( - git_transport *transport, - git_repository *repo, - const git_remote_head * const *refs, - size_t count); - -int git_smart__download_pack( - git_transport *transport, - git_repository *repo, - git_indexer_progress *stats); - -/* smart.c */ -int git_smart__negotiation_step(git_transport *transport, void *data, size_t len); -int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out); - -int git_smart__update_heads(transport_smart *t, git_vector *symrefs); - -/* smart_pkt.c */ -int git_pkt_parse_line(git_pkt **head, const char **endptr, const char *line, size_t linelen); -int git_pkt_buffer_flush(git_str *buf); -int git_pkt_send_flush(GIT_SOCKET s); -int git_pkt_buffer_done(git_str *buf); -int git_pkt_buffer_wants(const git_remote_head * const *refs, size_t count, transport_smart_caps *caps, git_str *buf); -int git_pkt_buffer_have(git_oid *oid, git_str *buf); -void git_pkt_free(git_pkt *pkt); - -#endif diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c deleted file mode 100644 index b42edd0d6..000000000 --- a/src/transports/smart_pkt.c +++ /dev/null @@ -1,637 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "smart.h" -#include "util.h" -#include "netops.h" -#include "posix.h" -#include "str.h" - -#include "git2/types.h" -#include "git2/errors.h" -#include "git2/refs.h" -#include "git2/revwalk.h" - -#include - -#define PKT_LEN_SIZE 4 -static const char pkt_done_str[] = "0009done\n"; -static const char pkt_flush_str[] = "0000"; -static const char pkt_have_prefix[] = "0032have "; -static const char pkt_want_prefix[] = "0032want "; - -static int flush_pkt(git_pkt **out) -{ - git_pkt *pkt; - - pkt = git__malloc(sizeof(git_pkt)); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->type = GIT_PKT_FLUSH; - *out = pkt; - - return 0; -} - -/* the rest of the line will be useful for multi_ack and multi_ack_detailed */ -static int ack_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_ack *pkt; - - pkt = git__calloc(1, sizeof(git_pkt_ack)); - GIT_ERROR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_ACK; - - if (git__prefixncmp(line, len, "ACK ")) - goto out_err; - line += 4; - len -= 4; - - if (len < GIT_OID_HEXSZ || git_oid_fromstr(&pkt->oid, line) < 0) - goto out_err; - line += GIT_OID_HEXSZ; - len -= GIT_OID_HEXSZ; - - if (len && line[0] == ' ') { - line++; - len--; - - if (!git__prefixncmp(line, len, "continue")) - pkt->status = GIT_ACK_CONTINUE; - else if (!git__prefixncmp(line, len, "common")) - pkt->status = GIT_ACK_COMMON; - else if (!git__prefixncmp(line, len, "ready")) - pkt->status = GIT_ACK_READY; - else - goto out_err; - } - - *out = (git_pkt *) pkt; - - return 0; - -out_err: - git_error_set(GIT_ERROR_NET, "error parsing ACK pkt-line"); - git__free(pkt); - return -1; -} - -static int nak_pkt(git_pkt **out) -{ - git_pkt *pkt; - - pkt = git__malloc(sizeof(git_pkt)); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->type = GIT_PKT_NAK; - *out = pkt; - - return 0; -} - -static int comment_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_comment *pkt; - size_t alloclen; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_comment), len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - pkt = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->type = GIT_PKT_COMMENT; - memcpy(pkt->comment, line, len); - pkt->comment[len] = '\0'; - - *out = (git_pkt *) pkt; - - return 0; -} - -static int err_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_err *pkt = NULL; - size_t alloclen; - - /* Remove "ERR " from the line */ - if (git__prefixncmp(line, len, "ERR ")) - goto out_err; - line += 4; - len -= 4; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - pkt = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_ERR; - pkt->len = len; - - memcpy(pkt->error, line, len); - pkt->error[len] = '\0'; - - *out = (git_pkt *) pkt; - - return 0; - -out_err: - git_error_set(GIT_ERROR_NET, "error parsing ERR pkt-line"); - git__free(pkt); - return -1; -} - -static int data_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_data *pkt; - size_t alloclen; - - line++; - len--; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len); - pkt = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->type = GIT_PKT_DATA; - pkt->len = len; - memcpy(pkt->data, line, len); - - *out = (git_pkt *) pkt; - - return 0; -} - -static int sideband_progress_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_progress *pkt; - size_t alloclen; - - line++; - len--; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_pkt_progress), len); - pkt = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->type = GIT_PKT_PROGRESS; - pkt->len = len; - memcpy(pkt->data, line, len); - - *out = (git_pkt *) pkt; - - return 0; -} - -static int sideband_error_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_err *pkt; - size_t alloc_len; - - line++; - len--; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(git_pkt_err), len); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); - pkt = git__malloc(alloc_len); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->type = GIT_PKT_ERR; - pkt->len = (int)len; - memcpy(pkt->error, line, len); - pkt->error[len] = '\0'; - - *out = (git_pkt *)pkt; - - return 0; -} - -/* - * Parse an other-ref line. - */ -static int ref_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_ref *pkt; - size_t alloclen; - - pkt = git__calloc(1, sizeof(git_pkt_ref)); - GIT_ERROR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_REF; - - if (len < GIT_OID_HEXSZ || git_oid_fromstr(&pkt->head.oid, line) < 0) - goto out_err; - line += GIT_OID_HEXSZ; - len -= GIT_OID_HEXSZ; - - if (git__prefixncmp(line, len, " ")) - goto out_err; - line++; - len--; - - if (!len) - goto out_err; - - if (line[len - 1] == '\n') - --len; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); - pkt->head.name = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt->head.name); - - memcpy(pkt->head.name, line, len); - pkt->head.name[len] = '\0'; - - if (strlen(pkt->head.name) < len) - pkt->capabilities = strchr(pkt->head.name, '\0') + 1; - - *out = (git_pkt *)pkt; - return 0; - -out_err: - git_error_set(GIT_ERROR_NET, "error parsing REF pkt-line"); - if (pkt) - git__free(pkt->head.name); - git__free(pkt); - return -1; -} - -static int ok_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_ok *pkt; - size_t alloc_len; - - pkt = git__malloc(sizeof(*pkt)); - GIT_ERROR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_OK; - - if (git__prefixncmp(line, len, "ok ")) - goto out_err; - line += 3; - len -= 3; - - if (len && line[len - 1] == '\n') - --len; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1); - pkt->ref = git__malloc(alloc_len); - GIT_ERROR_CHECK_ALLOC(pkt->ref); - - memcpy(pkt->ref, line, len); - pkt->ref[len] = '\0'; - - *out = (git_pkt *)pkt; - return 0; - -out_err: - git_error_set(GIT_ERROR_NET, "error parsing OK pkt-line"); - git__free(pkt); - return -1; -} - -static int ng_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_ng *pkt; - const char *ptr, *eol; - size_t alloclen; - - pkt = git__malloc(sizeof(*pkt)); - GIT_ERROR_CHECK_ALLOC(pkt); - - pkt->ref = NULL; - pkt->type = GIT_PKT_NG; - - eol = line + len; - - if (git__prefixncmp(line, len, "ng ")) - goto out_err; - line += 3; - - if (!(ptr = memchr(line, ' ', eol - line))) - goto out_err; - len = ptr - line; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); - pkt->ref = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt->ref); - - memcpy(pkt->ref, line, len); - pkt->ref[len] = '\0'; - - line = ptr + 1; - if (line >= eol) - goto out_err; - - if (!(ptr = memchr(line, '\n', eol - line))) - goto out_err; - len = ptr - line; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); - pkt->msg = git__malloc(alloclen); - GIT_ERROR_CHECK_ALLOC(pkt->msg); - - memcpy(pkt->msg, line, len); - pkt->msg[len] = '\0'; - - *out = (git_pkt *)pkt; - return 0; - -out_err: - git_error_set(GIT_ERROR_NET, "invalid packet line"); - git__free(pkt->ref); - git__free(pkt); - return -1; -} - -static int unpack_pkt(git_pkt **out, const char *line, size_t len) -{ - git_pkt_unpack *pkt; - - pkt = git__malloc(sizeof(*pkt)); - GIT_ERROR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_UNPACK; - - if (!git__prefixncmp(line, len, "unpack ok")) - pkt->unpack_ok = 1; - else - pkt->unpack_ok = 0; - - *out = (git_pkt *)pkt; - return 0; -} - -static int parse_len(size_t *out, const char *line, size_t linelen) -{ - char num[PKT_LEN_SIZE + 1]; - int i, k, error; - int32_t len; - const char *num_end; - - /* Not even enough for the length */ - if (linelen < PKT_LEN_SIZE) - return GIT_EBUFS; - - memcpy(num, line, PKT_LEN_SIZE); - num[PKT_LEN_SIZE] = '\0'; - - for (i = 0; i < PKT_LEN_SIZE; ++i) { - if (!isxdigit(num[i])) { - /* Make sure there are no special characters before passing to error message */ - for (k = 0; k < PKT_LEN_SIZE; ++k) { - if(!isprint(num[k])) { - num[k] = '.'; - } - } - - git_error_set(GIT_ERROR_NET, "invalid hex digit in length: '%s'", num); - return -1; - } - } - - if ((error = git__strntol32(&len, num, PKT_LEN_SIZE, &num_end, 16)) < 0) - return error; - - if (len < 0) - return -1; - - *out = (size_t) len; - return 0; -} - -/* - * As per the documentation, the syntax is: - * - * pkt-line = data-pkt / flush-pkt - * data-pkt = pkt-len pkt-payload - * pkt-len = 4*(HEXDIG) - * pkt-payload = (pkt-len -4)*(OCTET) - * flush-pkt = "0000" - * - * Which means that the first four bytes are the length of the line, - * in ASCII hexadecimal (including itself) - */ - -int git_pkt_parse_line( - git_pkt **pkt, const char **endptr, const char *line, size_t linelen) -{ - int error; - size_t len; - - if ((error = parse_len(&len, line, linelen)) < 0) { - /* - * If we fail to parse the length, it might be - * because the server is trying to send us the - * packfile already or because we do not yet have - * enough data. - */ - if (error == GIT_EBUFS) - ; - else if (!git__prefixncmp(line, linelen, "PACK")) - git_error_set(GIT_ERROR_NET, "unexpected pack file"); - else - git_error_set(GIT_ERROR_NET, "bad packet length"); - return error; - } - - /* - * Make sure there is enough in the buffer to satisfy - * this line. - */ - if (linelen < len) - return GIT_EBUFS; - - /* - * The length has to be exactly 0 in case of a flush - * packet or greater than PKT_LEN_SIZE, as the decoded - * length includes its own encoded length of four bytes. - */ - if (len != 0 && len < PKT_LEN_SIZE) - return GIT_ERROR; - - line += PKT_LEN_SIZE; - /* - * The Git protocol does not specify empty lines as part - * of the protocol. Not knowing what to do with an empty - * line, we should return an error upon hitting one. - */ - if (len == PKT_LEN_SIZE) { - git_error_set_str(GIT_ERROR_NET, "Invalid empty packet"); - return GIT_ERROR; - } - - if (len == 0) { /* Flush pkt */ - *endptr = line; - return flush_pkt(pkt); - } - - len -= PKT_LEN_SIZE; /* the encoded length includes its own size */ - - if (*line == GIT_SIDE_BAND_DATA) - error = data_pkt(pkt, line, len); - else if (*line == GIT_SIDE_BAND_PROGRESS) - error = sideband_progress_pkt(pkt, line, len); - else if (*line == GIT_SIDE_BAND_ERROR) - error = sideband_error_pkt(pkt, line, len); - else if (!git__prefixncmp(line, len, "ACK")) - error = ack_pkt(pkt, line, len); - else if (!git__prefixncmp(line, len, "NAK")) - error = nak_pkt(pkt); - else if (!git__prefixncmp(line, len, "ERR")) - error = err_pkt(pkt, line, len); - else if (*line == '#') - error = comment_pkt(pkt, line, len); - else if (!git__prefixncmp(line, len, "ok")) - error = ok_pkt(pkt, line, len); - else if (!git__prefixncmp(line, len, "ng")) - error = ng_pkt(pkt, line, len); - else if (!git__prefixncmp(line, len, "unpack")) - error = unpack_pkt(pkt, line, len); - else - error = ref_pkt(pkt, line, len); - - *endptr = line + len; - - return error; -} - -void git_pkt_free(git_pkt *pkt) -{ - if (pkt == NULL) { - return; - } - if (pkt->type == GIT_PKT_REF) { - git_pkt_ref *p = (git_pkt_ref *) pkt; - git__free(p->head.name); - git__free(p->head.symref_target); - } - - if (pkt->type == GIT_PKT_OK) { - git_pkt_ok *p = (git_pkt_ok *) pkt; - git__free(p->ref); - } - - if (pkt->type == GIT_PKT_NG) { - git_pkt_ng *p = (git_pkt_ng *) pkt; - git__free(p->ref); - git__free(p->msg); - } - - git__free(pkt); -} - -int git_pkt_buffer_flush(git_str *buf) -{ - return git_str_put(buf, pkt_flush_str, strlen(pkt_flush_str)); -} - -static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_str *buf) -{ - git_str str = GIT_STR_INIT; - char oid[GIT_OID_HEXSZ +1] = {0}; - size_t len; - - /* Prefer multi_ack_detailed */ - if (caps->multi_ack_detailed) - git_str_puts(&str, GIT_CAP_MULTI_ACK_DETAILED " "); - else if (caps->multi_ack) - git_str_puts(&str, GIT_CAP_MULTI_ACK " "); - - /* Prefer side-band-64k if the server supports both */ - if (caps->side_band_64k) - git_str_printf(&str, "%s ", GIT_CAP_SIDE_BAND_64K); - else if (caps->side_band) - git_str_printf(&str, "%s ", GIT_CAP_SIDE_BAND); - - if (caps->include_tag) - git_str_puts(&str, GIT_CAP_INCLUDE_TAG " "); - - if (caps->thin_pack) - git_str_puts(&str, GIT_CAP_THIN_PACK " "); - - if (caps->ofs_delta) - git_str_puts(&str, GIT_CAP_OFS_DELTA " "); - - if (git_str_oom(&str)) - return -1; - - len = strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ + - git_str_len(&str) + 1 /* LF */; - - if (len > 0xffff) { - git_error_set(GIT_ERROR_NET, - "tried to produce packet with invalid length %" PRIuZ, len); - return -1; - } - - git_str_grow_by(buf, len); - git_oid_fmt(oid, &head->oid); - git_str_printf(buf, - "%04xwant %s %s\n", (unsigned int)len, oid, git_str_cstr(&str)); - git_str_dispose(&str); - - GIT_ERROR_CHECK_ALLOC_STR(buf); - - return 0; -} - -/* - * All "want" packets have the same length and format, so what we do - * is overwrite the OID each time. - */ - -int git_pkt_buffer_wants( - const git_remote_head * const *refs, - size_t count, - transport_smart_caps *caps, - git_str *buf) -{ - size_t i = 0; - const git_remote_head *head; - - if (caps->common) { - for (; i < count; ++i) { - head = refs[i]; - if (!head->local) - break; - } - - if (buffer_want_with_caps(refs[i], caps, buf) < 0) - return -1; - - i++; - } - - for (; i < count; ++i) { - char oid[GIT_OID_HEXSZ]; - - head = refs[i]; - if (head->local) - continue; - - git_oid_fmt(oid, &head->oid); - git_str_put(buf, pkt_want_prefix, strlen(pkt_want_prefix)); - git_str_put(buf, oid, GIT_OID_HEXSZ); - git_str_putc(buf, '\n'); - if (git_str_oom(buf)) - return -1; - } - - return git_pkt_buffer_flush(buf); -} - -int git_pkt_buffer_have(git_oid *oid, git_str *buf) -{ - char oidhex[GIT_OID_HEXSZ + 1]; - - memset(oidhex, 0x0, sizeof(oidhex)); - git_oid_fmt(oidhex, oid); - return git_str_printf(buf, "%s%s\n", pkt_have_prefix, oidhex); -} - -int git_pkt_buffer_done(git_str *buf) -{ - return git_str_puts(buf, pkt_done_str); -} diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c deleted file mode 100644 index 8cf027133..000000000 --- a/src/transports/smart_protocol.c +++ /dev/null @@ -1,1094 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "git2.h" -#include "git2/odb_backend.h" - -#include "smart.h" -#include "refs.h" -#include "repository.h" -#include "push.h" -#include "pack-objects.h" -#include "remote.h" -#include "util.h" -#include "revwalk.h" - -#define NETWORK_XFER_THRESHOLD (100*1024) -/* The minimal interval between progress updates (in seconds). */ -#define MIN_PROGRESS_UPDATE_INTERVAL 0.5 - -bool git_smart__ofs_delta_enabled = true; - -int git_smart__store_refs(transport_smart *t, int flushes) -{ - gitno_buffer *buf = &t->buffer; - git_vector *refs = &t->refs; - int error, flush = 0, recvd; - const char *line_end = NULL; - git_pkt *pkt = NULL; - size_t i; - - /* Clear existing refs in case git_remote_connect() is called again - * after git_remote_disconnect(). - */ - git_vector_foreach(refs, i, pkt) { - git_pkt_free(pkt); - } - git_vector_clear(refs); - pkt = NULL; - - do { - if (buf->offset > 0) - error = git_pkt_parse_line(&pkt, &line_end, buf->data, buf->offset); - else - error = GIT_EBUFS; - - if (error < 0 && error != GIT_EBUFS) - return error; - - if (error == GIT_EBUFS) { - if ((recvd = gitno_recv(buf)) < 0) - return recvd; - - if (recvd == 0) { - git_error_set(GIT_ERROR_NET, "early EOF"); - return GIT_EEOF; - } - - continue; - } - - if (gitno_consume(buf, line_end) < 0) - return -1; - - if (pkt->type == GIT_PKT_ERR) { - git_error_set(GIT_ERROR_NET, "remote error: %s", ((git_pkt_err *)pkt)->error); - git__free(pkt); - return -1; - } - - if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0) - return -1; - - if (pkt->type == GIT_PKT_FLUSH) { - flush++; - git_pkt_free(pkt); - } - } while (flush < flushes); - - return flush; -} - -static int append_symref(const char **out, git_vector *symrefs, const char *ptr) -{ - int error; - const char *end; - git_str buf = GIT_STR_INIT; - git_refspec *mapping = NULL; - - ptr += strlen(GIT_CAP_SYMREF); - if (*ptr != '=') - goto on_invalid; - - ptr++; - if (!(end = strchr(ptr, ' ')) && - !(end = strchr(ptr, '\0'))) - goto on_invalid; - - if ((error = git_str_put(&buf, ptr, end - ptr)) < 0) - return error; - - /* symref mapping has refspec format */ - mapping = git__calloc(1, sizeof(git_refspec)); - GIT_ERROR_CHECK_ALLOC(mapping); - - error = git_refspec__parse(mapping, git_str_cstr(&buf), true); - git_str_dispose(&buf); - - /* if the error isn't OOM, then it's a parse error; let's use a nicer message */ - if (error < 0) { - if (git_error_last()->klass != GIT_ERROR_NOMEMORY) - goto on_invalid; - - git__free(mapping); - return error; - } - - if ((error = git_vector_insert(symrefs, mapping)) < 0) - return error; - - *out = end; - return 0; - -on_invalid: - git_error_set(GIT_ERROR_NET, "remote sent invalid symref"); - git_refspec__dispose(mapping); - git__free(mapping); - return -1; -} - -int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs) -{ - const char *ptr; - - /* No refs or capabilities, odd but not a problem */ - if (pkt == NULL || pkt->capabilities == NULL) - return GIT_ENOTFOUND; - - ptr = pkt->capabilities; - while (ptr != NULL && *ptr != '\0') { - if (*ptr == ' ') - ptr++; - - if (git_smart__ofs_delta_enabled && !git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { - caps->common = caps->ofs_delta = 1; - ptr += strlen(GIT_CAP_OFS_DELTA); - continue; - } - - /* Keep multi_ack_detailed before multi_ack */ - if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK_DETAILED)) { - caps->common = caps->multi_ack_detailed = 1; - ptr += strlen(GIT_CAP_MULTI_ACK_DETAILED); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) { - caps->common = caps->multi_ack = 1; - ptr += strlen(GIT_CAP_MULTI_ACK); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) { - caps->common = caps->include_tag = 1; - ptr += strlen(GIT_CAP_INCLUDE_TAG); - continue; - } - - /* Keep side-band check after side-band-64k */ - if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) { - caps->common = caps->side_band_64k = 1; - ptr += strlen(GIT_CAP_SIDE_BAND_64K); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) { - caps->common = caps->side_band = 1; - ptr += strlen(GIT_CAP_SIDE_BAND); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_DELETE_REFS)) { - caps->common = caps->delete_refs = 1; - ptr += strlen(GIT_CAP_DELETE_REFS); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_THIN_PACK)) { - caps->common = caps->thin_pack = 1; - ptr += strlen(GIT_CAP_THIN_PACK); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_SYMREF)) { - int error; - - if ((error = append_symref(&ptr, symrefs, ptr)) < 0) - return error; - - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_WANT_TIP_SHA1)) { - caps->common = caps->want_tip_sha1 = 1; - ptr += strlen(GIT_CAP_DELETE_REFS); - continue; - } - - if (!git__prefixcmp(ptr, GIT_CAP_WANT_REACHABLE_SHA1)) { - caps->common = caps->want_reachable_sha1 = 1; - ptr += strlen(GIT_CAP_DELETE_REFS); - continue; - } - - /* We don't know this capability, so skip it */ - ptr = strchr(ptr, ' '); - } - - return 0; -} - -static int recv_pkt(git_pkt **out_pkt, git_pkt_type *out_type, gitno_buffer *buf) -{ - const char *ptr = buf->data, *line_end = ptr; - git_pkt *pkt = NULL; - int error = 0, ret; - - do { - if (buf->offset > 0) - error = git_pkt_parse_line(&pkt, &line_end, ptr, buf->offset); - else - error = GIT_EBUFS; - - if (error == 0) - break; /* return the pkt */ - - if (error < 0 && error != GIT_EBUFS) - return error; - - if ((ret = gitno_recv(buf)) < 0) { - return ret; - } else if (ret == 0) { - git_error_set(GIT_ERROR_NET, "early EOF"); - return GIT_EEOF; - } - } while (error); - - if (gitno_consume(buf, line_end) < 0) - return -1; - - if (out_type != NULL) - *out_type = pkt->type; - if (out_pkt != NULL) - *out_pkt = pkt; - else - git__free(pkt); - - return error; -} - -static int store_common(transport_smart *t) -{ - git_pkt *pkt = NULL; - gitno_buffer *buf = &t->buffer; - int error; - - do { - if ((error = recv_pkt(&pkt, NULL, buf)) < 0) - return error; - - if (pkt->type != GIT_PKT_ACK) { - git__free(pkt); - return 0; - } - - if (git_vector_insert(&t->common, pkt) < 0) { - git__free(pkt); - return -1; - } - } while (1); - - return 0; -} - -static int wait_while_ack(gitno_buffer *buf) -{ - int error; - git_pkt *pkt = NULL; - git_pkt_ack *ack = NULL; - - while (1) { - git_pkt_free(pkt); - - if ((error = recv_pkt(&pkt, NULL, buf)) < 0) - return error; - - if (pkt->type == GIT_PKT_NAK) - break; - if (pkt->type != GIT_PKT_ACK) - continue; - - ack = (git_pkt_ack*)pkt; - - if (ack->status != GIT_ACK_CONTINUE && - ack->status != GIT_ACK_COMMON && - ack->status != GIT_ACK_READY) { - break; - } - } - - git_pkt_free(pkt); - return 0; -} - -int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *wants, size_t count) -{ - transport_smart *t = (transport_smart *)transport; - git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; - gitno_buffer *buf = &t->buffer; - git_str data = GIT_STR_INIT; - git_revwalk *walk = NULL; - int error = -1; - git_pkt_type pkt_type; - unsigned int i; - git_oid oid; - - if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) - return error; - - if ((error = git_revwalk_new(&walk, repo)) < 0) - goto on_error; - - opts.insert_by_date = 1; - if ((error = git_revwalk__push_glob(walk, "refs/*", &opts)) < 0) - goto on_error; - - /* - * Our support for ACK extensions is simply to parse them. On - * the first ACK we will accept that as enough common - * objects. We give up if we haven't found an answer in the - * first 256 we send. - */ - i = 0; - while (i < 256) { - error = git_revwalk_next(&oid, walk); - - if (error < 0) { - if (GIT_ITEROVER == error) - break; - - goto on_error; - } - - git_pkt_buffer_have(&oid, &data); - i++; - if (i % 20 == 0) { - if (t->cancelled.val) { - git_error_set(GIT_ERROR_NET, "The fetch was cancelled by the user"); - error = GIT_EUSER; - goto on_error; - } - - git_pkt_buffer_flush(&data); - if (git_str_oom(&data)) { - error = -1; - goto on_error; - } - - if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0) - goto on_error; - - git_str_clear(&data); - if (t->caps.multi_ack || t->caps.multi_ack_detailed) { - if ((error = store_common(t)) < 0) - goto on_error; - } else { - if ((error = recv_pkt(NULL, &pkt_type, buf)) < 0) - goto on_error; - - if (pkt_type == GIT_PKT_ACK) { - break; - } else if (pkt_type == GIT_PKT_NAK) { - continue; - } else { - git_error_set(GIT_ERROR_NET, "unexpected pkt type"); - error = -1; - goto on_error; - } - } - } - - if (t->common.length > 0) - break; - - if (i % 20 == 0 && t->rpc) { - git_pkt_ack *pkt; - unsigned int j; - - if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) - goto on_error; - - git_vector_foreach(&t->common, j, pkt) { - if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0) - goto on_error; - } - - if (git_str_oom(&data)) { - error = -1; - goto on_error; - } - } - } - - /* Tell the other end that we're done negotiating */ - if (t->rpc && t->common.length > 0) { - git_pkt_ack *pkt; - unsigned int j; - - if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) - goto on_error; - - git_vector_foreach(&t->common, j, pkt) { - if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0) - goto on_error; - } - - if (git_str_oom(&data)) { - error = -1; - goto on_error; - } - } - - if ((error = git_pkt_buffer_done(&data)) < 0) - goto on_error; - - if (t->cancelled.val) { - git_error_set(GIT_ERROR_NET, "The fetch was cancelled by the user"); - error = GIT_EUSER; - goto on_error; - } - if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0) - goto on_error; - - git_str_dispose(&data); - git_revwalk_free(walk); - - /* Now let's eat up whatever the server gives us */ - if (!t->caps.multi_ack && !t->caps.multi_ack_detailed) { - if ((error = recv_pkt(NULL, &pkt_type, buf)) < 0) - return error; - - if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) { - git_error_set(GIT_ERROR_NET, "unexpected pkt type"); - return -1; - } - } else { - error = wait_while_ack(buf); - } - - return error; - -on_error: - git_revwalk_free(walk); - git_str_dispose(&data); - return error; -} - -static int no_sideband(transport_smart *t, struct git_odb_writepack *writepack, gitno_buffer *buf, git_indexer_progress *stats) -{ - int recvd; - - do { - if (t->cancelled.val) { - git_error_set(GIT_ERROR_NET, "the fetch was cancelled by the user"); - return GIT_EUSER; - } - - if (writepack->append(writepack, buf->data, buf->offset, stats) < 0) - return -1; - - gitno_consume_n(buf, buf->offset); - - if ((recvd = gitno_recv(buf)) < 0) - return recvd; - } while(recvd > 0); - - if (writepack->commit(writepack, stats) < 0) - return -1; - - return 0; -} - -struct network_packetsize_payload -{ - git_indexer_progress_cb callback; - void *payload; - git_indexer_progress *stats; - size_t last_fired_bytes; -}; - -static int network_packetsize(size_t received, void *payload) -{ - struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload; - - /* Accumulate bytes */ - npp->stats->received_bytes += received; - - /* Fire notification if the threshold is reached */ - if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) { - npp->last_fired_bytes = npp->stats->received_bytes; - - if (npp->callback(npp->stats, npp->payload)) - return GIT_EUSER; - } - - return 0; -} - -int git_smart__download_pack( - git_transport *transport, - git_repository *repo, - git_indexer_progress *stats) -{ - transport_smart *t = (transport_smart *)transport; - gitno_buffer *buf = &t->buffer; - git_odb *odb; - struct git_odb_writepack *writepack = NULL; - int error = 0; - struct network_packetsize_payload npp = {0}; - - git_indexer_progress_cb progress_cb = t->connect_opts.callbacks.transfer_progress; - void *progress_payload = t->connect_opts.callbacks.payload; - - memset(stats, 0, sizeof(git_indexer_progress)); - - if (progress_cb) { - npp.callback = progress_cb; - npp.payload = progress_payload; - npp.stats = stats; - t->packetsize_cb = &network_packetsize; - t->packetsize_payload = &npp; - - /* We might have something in the buffer already from negotiate_fetch */ - if (t->buffer.offset > 0 && !t->cancelled.val) - if (t->packetsize_cb(t->buffer.offset, t->packetsize_payload)) - git_atomic32_set(&t->cancelled, 1); - } - - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || - ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) != 0)) - goto done; - - /* - * If the remote doesn't support the side-band, we can feed - * the data directly to the pack writer. Otherwise, we need to - * check which one belongs there. - */ - if (!t->caps.side_band && !t->caps.side_band_64k) { - error = no_sideband(t, writepack, buf, stats); - goto done; - } - - do { - git_pkt *pkt = NULL; - - /* Check cancellation before network call */ - if (t->cancelled.val) { - git_error_clear(); - error = GIT_EUSER; - goto done; - } - - if ((error = recv_pkt(&pkt, NULL, buf)) >= 0) { - /* Check cancellation after network call */ - if (t->cancelled.val) { - git_error_clear(); - error = GIT_EUSER; - } else if (pkt->type == GIT_PKT_PROGRESS) { - if (t->connect_opts.callbacks.sideband_progress) { - git_pkt_progress *p = (git_pkt_progress *) pkt; - - if (p->len > INT_MAX) { - git_error_set(GIT_ERROR_NET, "oversized progress message"); - error = GIT_ERROR; - goto done; - } - - error = t->connect_opts.callbacks.sideband_progress(p->data, (int)p->len, t->connect_opts.callbacks.payload); - } - } else if (pkt->type == GIT_PKT_DATA) { - git_pkt_data *p = (git_pkt_data *) pkt; - - if (p->len) - error = writepack->append(writepack, p->data, p->len, stats); - } else if (pkt->type == GIT_PKT_FLUSH) { - /* A flush indicates the end of the packfile */ - git__free(pkt); - break; - } - } - - git_pkt_free(pkt); - - if (error < 0) - goto done; - - } while (1); - - /* - * Trailing execution of progress_cb, if necessary... - * Only the callback through the npp datastructure currently - * updates the last_fired_bytes value. It is possible that - * progress has already been reported with the correct - * "received_bytes" value, but until (if?) this is unified - * then we will report progress again to be sure that the - * correct last received_bytes value is reported. - */ - if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes) { - error = npp.callback(npp.stats, npp.payload); - if (error != 0) - goto done; - } - - error = writepack->commit(writepack, stats); - -done: - if (writepack) - writepack->free(writepack); - if (progress_cb) { - t->packetsize_cb = NULL; - t->packetsize_payload = NULL; - } - - return error; -} - -static int gen_pktline(git_str *buf, git_push *push) -{ - push_spec *spec; - size_t i, len; - char old_id[GIT_OID_HEXSZ+1], new_id[GIT_OID_HEXSZ+1]; - - old_id[GIT_OID_HEXSZ] = '\0'; new_id[GIT_OID_HEXSZ] = '\0'; - - git_vector_foreach(&push->specs, i, spec) { - len = 2*GIT_OID_HEXSZ + 7 + strlen(spec->refspec.dst); - - if (i == 0) { - ++len; /* '\0' */ - if (push->report_status) - len += strlen(GIT_CAP_REPORT_STATUS) + 1; - len += strlen(GIT_CAP_SIDE_BAND_64K) + 1; - } - - git_oid_fmt(old_id, &spec->roid); - git_oid_fmt(new_id, &spec->loid); - - git_str_printf(buf, "%04"PRIxZ"%s %s %s", len, old_id, new_id, spec->refspec.dst); - - if (i == 0) { - git_str_putc(buf, '\0'); - /* Core git always starts their capabilities string with a space */ - if (push->report_status) { - git_str_putc(buf, ' '); - git_str_printf(buf, GIT_CAP_REPORT_STATUS); - } - git_str_putc(buf, ' '); - git_str_printf(buf, GIT_CAP_SIDE_BAND_64K); - } - - git_str_putc(buf, '\n'); - } - - git_str_puts(buf, "0000"); - return git_str_oom(buf) ? -1 : 0; -} - -static int add_push_report_pkt(git_push *push, git_pkt *pkt) -{ - push_status *status; - - switch (pkt->type) { - case GIT_PKT_OK: - status = git__calloc(1, sizeof(push_status)); - GIT_ERROR_CHECK_ALLOC(status); - status->msg = NULL; - status->ref = git__strdup(((git_pkt_ok *)pkt)->ref); - if (!status->ref || - git_vector_insert(&push->status, status) < 0) { - git_push_status_free(status); - return -1; - } - break; - case GIT_PKT_NG: - status = git__calloc(1, sizeof(push_status)); - GIT_ERROR_CHECK_ALLOC(status); - status->ref = git__strdup(((git_pkt_ng *)pkt)->ref); - status->msg = git__strdup(((git_pkt_ng *)pkt)->msg); - if (!status->ref || !status->msg || - git_vector_insert(&push->status, status) < 0) { - git_push_status_free(status); - return -1; - } - break; - case GIT_PKT_UNPACK: - push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok; - break; - case GIT_PKT_FLUSH: - return GIT_ITEROVER; - default: - git_error_set(GIT_ERROR_NET, "report-status: protocol error"); - return -1; - } - - return 0; -} - -static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt, git_str *data_pkt_buf) -{ - git_pkt *pkt; - const char *line, *line_end = NULL; - size_t line_len; - int error; - int reading_from_buf = data_pkt_buf->size > 0; - - if (reading_from_buf) { - /* We had an existing partial packet, so add the new - * packet to the buffer and parse the whole thing */ - git_str_put(data_pkt_buf, data_pkt->data, data_pkt->len); - line = data_pkt_buf->ptr; - line_len = data_pkt_buf->size; - } - else { - line = data_pkt->data; - line_len = data_pkt->len; - } - - while (line_len > 0) { - error = git_pkt_parse_line(&pkt, &line_end, line, line_len); - - if (error == GIT_EBUFS) { - /* Buffer the data when the inner packet is split - * across multiple sideband packets */ - if (!reading_from_buf) - git_str_put(data_pkt_buf, line, line_len); - error = 0; - goto done; - } - else if (error < 0) - goto done; - - /* Advance in the buffer */ - line_len -= (line_end - line); - line = line_end; - - error = add_push_report_pkt(push, pkt); - - git_pkt_free(pkt); - - if (error < 0 && error != GIT_ITEROVER) - goto done; - } - - error = 0; - -done: - if (reading_from_buf) - git_str_consume(data_pkt_buf, line_end); - return error; -} - -static int parse_report(transport_smart *transport, git_push *push) -{ - git_pkt *pkt = NULL; - const char *line_end = NULL; - gitno_buffer *buf = &transport->buffer; - int error, recvd; - git_str data_pkt_buf = GIT_STR_INIT; - - for (;;) { - if (buf->offset > 0) - error = git_pkt_parse_line(&pkt, &line_end, - buf->data, buf->offset); - else - error = GIT_EBUFS; - - if (error < 0 && error != GIT_EBUFS) { - error = -1; - goto done; - } - - if (error == GIT_EBUFS) { - if ((recvd = gitno_recv(buf)) < 0) { - error = recvd; - goto done; - } - - if (recvd == 0) { - git_error_set(GIT_ERROR_NET, "early EOF"); - error = GIT_EEOF; - goto done; - } - continue; - } - - if (gitno_consume(buf, line_end) < 0) - return -1; - - error = 0; - - switch (pkt->type) { - case GIT_PKT_DATA: - /* This is a sideband packet which contains other packets */ - error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt, &data_pkt_buf); - break; - case GIT_PKT_ERR: - git_error_set(GIT_ERROR_NET, "report-status: Error reported: %s", - ((git_pkt_err *)pkt)->error); - error = -1; - break; - case GIT_PKT_PROGRESS: - if (transport->connect_opts.callbacks.sideband_progress) { - git_pkt_progress *p = (git_pkt_progress *) pkt; - - if (p->len > INT_MAX) { - git_error_set(GIT_ERROR_NET, "oversized progress message"); - error = GIT_ERROR; - goto done; - } - - error = transport->connect_opts.callbacks.sideband_progress(p->data, (int)p->len, transport->connect_opts.callbacks.payload); - } - break; - default: - error = add_push_report_pkt(push, pkt); - break; - } - - git_pkt_free(pkt); - - /* add_push_report_pkt returns GIT_ITEROVER when it receives a flush */ - if (error == GIT_ITEROVER) { - error = 0; - if (data_pkt_buf.size > 0) { - /* If there was data remaining in the pack data buffer, - * then the server sent a partial pkt-line */ - git_error_set(GIT_ERROR_NET, "incomplete pack data pkt-line"); - error = GIT_ERROR; - } - goto done; - } - - if (error < 0) { - goto done; - } - } -done: - git_str_dispose(&data_pkt_buf); - return error; -} - -static int add_ref_from_push_spec(git_vector *refs, push_spec *push_spec) -{ - git_pkt_ref *added = git__calloc(1, sizeof(git_pkt_ref)); - GIT_ERROR_CHECK_ALLOC(added); - - added->type = GIT_PKT_REF; - git_oid_cpy(&added->head.oid, &push_spec->loid); - added->head.name = git__strdup(push_spec->refspec.dst); - - if (!added->head.name || - git_vector_insert(refs, added) < 0) { - git_pkt_free((git_pkt *)added); - return -1; - } - - return 0; -} - -static int update_refs_from_report( - git_vector *refs, - git_vector *push_specs, - git_vector *push_report) -{ - git_pkt_ref *ref; - push_spec *push_spec; - push_status *push_status; - size_t i, j, refs_len; - int cmp; - - /* For each push spec we sent to the server, we should have - * gotten back a status packet in the push report */ - if (push_specs->length != push_report->length) { - git_error_set(GIT_ERROR_NET, "report-status: protocol error"); - return -1; - } - - /* We require that push_specs be sorted with push_spec_rref_cmp, - * and that push_report be sorted with push_status_ref_cmp */ - git_vector_sort(push_specs); - git_vector_sort(push_report); - - git_vector_foreach(push_specs, i, push_spec) { - push_status = git_vector_get(push_report, i); - - /* For each push spec we sent to the server, we should have - * gotten back a status packet in the push report which matches */ - if (strcmp(push_spec->refspec.dst, push_status->ref)) { - git_error_set(GIT_ERROR_NET, "report-status: protocol error"); - return -1; - } - } - - /* We require that refs be sorted with ref_name_cmp */ - git_vector_sort(refs); - i = j = 0; - refs_len = refs->length; - - /* Merge join push_specs with refs */ - while (i < push_specs->length && j < refs_len) { - push_spec = git_vector_get(push_specs, i); - push_status = git_vector_get(push_report, i); - ref = git_vector_get(refs, j); - - cmp = strcmp(push_spec->refspec.dst, ref->head.name); - - /* Iterate appropriately */ - if (cmp <= 0) i++; - if (cmp >= 0) j++; - - /* Add case */ - if (cmp < 0 && - !push_status->msg && - add_ref_from_push_spec(refs, push_spec) < 0) - return -1; - - /* Update case, delete case */ - if (cmp == 0 && - !push_status->msg) - git_oid_cpy(&ref->head.oid, &push_spec->loid); - } - - for (; i < push_specs->length; i++) { - push_spec = git_vector_get(push_specs, i); - push_status = git_vector_get(push_report, i); - - /* Add case */ - if (!push_status->msg && - add_ref_from_push_spec(refs, push_spec) < 0) - return -1; - } - - /* Remove any refs which we updated to have a zero OID. */ - git_vector_rforeach(refs, i, ref) { - if (git_oid_is_zero(&ref->head.oid)) { - git_vector_remove(refs, i); - git_pkt_free((git_pkt *)ref); - } - } - - git_vector_sort(refs); - - return 0; -} - -struct push_packbuilder_payload -{ - git_smart_subtransport_stream *stream; - git_packbuilder *pb; - git_push_transfer_progress_cb cb; - void *cb_payload; - size_t last_bytes; - double last_progress_report_time; -}; - -static int stream_thunk(void *buf, size_t size, void *data) -{ - int error = 0; - struct push_packbuilder_payload *payload = data; - - if ((error = payload->stream->write(payload->stream, (const char *)buf, size)) < 0) - return error; - - if (payload->cb) { - double current_time = git__timer(); - double elapsed = current_time - payload->last_progress_report_time; - payload->last_bytes += size; - - if (elapsed < 0 || elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) { - payload->last_progress_report_time = current_time; - error = payload->cb(payload->pb->nr_written, payload->pb->nr_objects, payload->last_bytes, payload->cb_payload); - } - } - - return error; -} - -int git_smart__push(git_transport *transport, git_push *push) -{ - transport_smart *t = (transport_smart *)transport; - git_remote_callbacks *cbs = &t->connect_opts.callbacks; - struct push_packbuilder_payload packbuilder_payload = {0}; - git_str pktline = GIT_STR_INIT; - int error = 0, need_pack = 0; - push_spec *spec; - unsigned int i; - - packbuilder_payload.pb = push->pb; - - if (cbs && cbs->push_transfer_progress) { - packbuilder_payload.cb = cbs->push_transfer_progress; - packbuilder_payload.cb_payload = cbs->payload; - } - -#ifdef PUSH_DEBUG -{ - git_remote_head *head; - char hex[GIT_OID_HEXSZ+1]; hex[GIT_OID_HEXSZ] = '\0'; - - git_vector_foreach(&push->remote->refs, i, head) { - git_oid_fmt(hex, &head->oid); - fprintf(stderr, "%s (%s)\n", hex, head->name); - } - - git_vector_foreach(&push->specs, i, spec) { - git_oid_fmt(hex, &spec->roid); - fprintf(stderr, "%s (%s) -> ", hex, spec->lref); - git_oid_fmt(hex, &spec->loid); - fprintf(stderr, "%s (%s)\n", hex, spec->rref ? - spec->rref : spec->lref); - } -} -#endif - - /* - * Figure out if we need to send a packfile; which is in all - * cases except when we only send delete commands - */ - git_vector_foreach(&push->specs, i, spec) { - if (spec->refspec.src && spec->refspec.src[0] != '\0') { - need_pack = 1; - break; - } - } - - /* prepare pack before sending pack header to avoid timeouts */ - if (need_pack && ((error = git_packbuilder__prepare(push->pb))) < 0) - goto done; - - if ((error = git_smart__get_push_stream(t, &packbuilder_payload.stream)) < 0 || - (error = gen_pktline(&pktline, push)) < 0 || - (error = packbuilder_payload.stream->write(packbuilder_payload.stream, git_str_cstr(&pktline), git_str_len(&pktline))) < 0) - goto done; - - if (need_pack && - (error = git_packbuilder_foreach(push->pb, &stream_thunk, &packbuilder_payload)) < 0) - goto done; - - /* If we sent nothing or the server doesn't support report-status, then - * we consider the pack to have been unpacked successfully */ - if (!push->specs.length || !push->report_status) - push->unpack_ok = 1; - else if ((error = parse_report(t, push)) < 0) - goto done; - - /* If progress is being reported write the final report */ - if (cbs && cbs->push_transfer_progress) { - error = cbs->push_transfer_progress( - push->pb->nr_written, - push->pb->nr_objects, - packbuilder_payload.last_bytes, - cbs->payload); - - if (error < 0) - goto done; - } - - if (push->status.length) { - error = update_refs_from_report(&t->refs, &push->specs, &push->status); - if (error < 0) - goto done; - - error = git_smart__update_heads(t, NULL); - } - -done: - git_str_dispose(&pktline); - return error; -} diff --git a/src/transports/ssh.c b/src/transports/ssh.c deleted file mode 100644 index 89f085230..000000000 --- a/src/transports/ssh.c +++ /dev/null @@ -1,915 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "ssh.h" - -#ifdef GIT_SSH -#include -#endif - -#include "runtime.h" -#include "net.h" -#include "netops.h" -#include "smart.h" -#include "streams/socket.h" - -#include "git2/credential.h" -#include "git2/sys/credential.h" - -#ifdef GIT_SSH - -#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport) - -static const char cmd_uploadpack[] = "git-upload-pack"; -static const char cmd_receivepack[] = "git-receive-pack"; - -typedef struct { - git_smart_subtransport_stream parent; - git_stream *io; - LIBSSH2_SESSION *session; - LIBSSH2_CHANNEL *channel; - const char *cmd; - git_net_url url; - unsigned sent_command : 1; -} ssh_stream; - -typedef struct { - git_smart_subtransport parent; - transport_smart *owner; - ssh_stream *current_stream; - git_credential *cred; - char *cmd_uploadpack; - char *cmd_receivepack; -} ssh_subtransport; - -static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username); - -static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) -{ - char *ssherr; - libssh2_session_last_error(session, &ssherr, NULL, 0); - - git_error_set(GIT_ERROR_SSH, "%s: %s", errmsg, ssherr); -} - -/* - * Create a git protocol request. - * - * For example: git-upload-pack '/libgit2/libgit2' - */ -static int gen_proto(git_str *request, const char *cmd, git_net_url *url) -{ - const char *repo; - - repo = url->path; - - if (repo && repo[0] == '/' && repo[1] == '~') - repo++; - - if (!repo || !repo[0]) { - git_error_set(GIT_ERROR_NET, "malformed git protocol URL"); - return -1; - } - - git_str_puts(request, cmd); - git_str_puts(request, " '"); - git_str_puts(request, repo); - git_str_puts(request, "'"); - - if (git_str_oom(request)) - return -1; - - return 0; -} - -static int send_command(ssh_stream *s) -{ - int error; - git_str request = GIT_STR_INIT; - - error = gen_proto(&request, s->cmd, &s->url); - if (error < 0) - goto cleanup; - - error = libssh2_channel_exec(s->channel, request.ptr); - if (error < LIBSSH2_ERROR_NONE) { - ssh_error(s->session, "SSH could not execute request"); - goto cleanup; - } - - s->sent_command = 1; - -cleanup: - git_str_dispose(&request); - return error; -} - -static int ssh_stream_read( - git_smart_subtransport_stream *stream, - char *buffer, - size_t buf_size, - size_t *bytes_read) -{ - int rc; - ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); - - *bytes_read = 0; - - if (!s->sent_command && send_command(s) < 0) - return -1; - - if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) { - ssh_error(s->session, "SSH could not read data"); - return -1; - } - - /* - * If we can't get anything out of stdout, it's typically a - * not-found error, so read from stderr and signal EOF on - * stderr. - */ - if (rc == 0) { - if ((rc = libssh2_channel_read_stderr(s->channel, buffer, buf_size)) > 0) { - git_error_set(GIT_ERROR_SSH, "%*s", rc, buffer); - return GIT_EEOF; - } else if (rc < LIBSSH2_ERROR_NONE) { - ssh_error(s->session, "SSH could not read stderr"); - return -1; - } - } - - - *bytes_read = rc; - - return 0; -} - -static int ssh_stream_write( - git_smart_subtransport_stream *stream, - const char *buffer, - size_t len) -{ - ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); - size_t off = 0; - ssize_t ret = 0; - - if (!s->sent_command && send_command(s) < 0) - return -1; - - do { - ret = libssh2_channel_write(s->channel, buffer + off, len - off); - if (ret < 0) - break; - - off += ret; - - } while (off < len); - - if (ret < 0) { - ssh_error(s->session, "SSH could not write data"); - return -1; - } - - return 0; -} - -static void ssh_stream_free(git_smart_subtransport_stream *stream) -{ - ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); - ssh_subtransport *t; - - if (!stream) - return; - - t = OWNING_SUBTRANSPORT(s); - t->current_stream = NULL; - - if (s->channel) { - libssh2_channel_close(s->channel); - libssh2_channel_free(s->channel); - s->channel = NULL; - } - - if (s->session) { - libssh2_session_disconnect(s->session, "closing transport"); - libssh2_session_free(s->session); - s->session = NULL; - } - - if (s->io) { - git_stream_close(s->io); - git_stream_free(s->io); - s->io = NULL; - } - - git_net_url_dispose(&s->url); - git__free(s); -} - -static int ssh_stream_alloc( - ssh_subtransport *t, - const char *cmd, - git_smart_subtransport_stream **stream) -{ - ssh_stream *s; - - GIT_ASSERT_ARG(stream); - - s = git__calloc(sizeof(ssh_stream), 1); - GIT_ERROR_CHECK_ALLOC(s); - - s->parent.subtransport = &t->parent; - s->parent.read = ssh_stream_read; - s->parent.write = ssh_stream_write; - s->parent.free = ssh_stream_free; - - s->cmd = cmd; - - *stream = &s->parent; - return 0; -} - -static int ssh_agent_auth(LIBSSH2_SESSION *session, git_credential_ssh_key *c) { - int rc = LIBSSH2_ERROR_NONE; - - struct libssh2_agent_publickey *curr, *prev = NULL; - - LIBSSH2_AGENT *agent = libssh2_agent_init(session); - - if (agent == NULL) - return -1; - - rc = libssh2_agent_connect(agent); - - if (rc != LIBSSH2_ERROR_NONE) - goto shutdown; - - rc = libssh2_agent_list_identities(agent); - - if (rc != LIBSSH2_ERROR_NONE) - goto shutdown; - - while (1) { - rc = libssh2_agent_get_identity(agent, &curr, prev); - - if (rc < 0) - goto shutdown; - - /* rc is set to 1 whenever the ssh agent ran out of keys to check. - * Set the error code to authentication failure rather than erroring - * out with an untranslatable error code. - */ - if (rc == 1) { - rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; - goto shutdown; - } - - rc = libssh2_agent_userauth(agent, c->username, curr); - - if (rc == 0) - break; - - prev = curr; - } - -shutdown: - - if (rc != LIBSSH2_ERROR_NONE) - ssh_error(session, "error authenticating"); - - libssh2_agent_disconnect(agent); - libssh2_agent_free(agent); - - return rc; -} - -static int _git_ssh_authenticate_session( - LIBSSH2_SESSION *session, - git_credential *cred) -{ - int rc; - - do { - git_error_clear(); - switch (cred->credtype) { - case GIT_CREDENTIAL_USERPASS_PLAINTEXT: { - git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred; - rc = libssh2_userauth_password(session, c->username, c->password); - break; - } - case GIT_CREDENTIAL_SSH_KEY: { - git_credential_ssh_key *c = (git_credential_ssh_key *)cred; - - if (c->privatekey) - rc = libssh2_userauth_publickey_fromfile( - session, c->username, c->publickey, - c->privatekey, c->passphrase); - else - rc = ssh_agent_auth(session, c); - - break; - } - case GIT_CREDENTIAL_SSH_CUSTOM: { - git_credential_ssh_custom *c = (git_credential_ssh_custom *)cred; - - rc = libssh2_userauth_publickey( - session, c->username, (const unsigned char *)c->publickey, - c->publickey_len, c->sign_callback, &c->payload); - break; - } - case GIT_CREDENTIAL_SSH_INTERACTIVE: { - void **abstract = libssh2_session_abstract(session); - git_credential_ssh_interactive *c = (git_credential_ssh_interactive *)cred; - - /* ideally, we should be able to set this by calling - * libssh2_session_init_ex() instead of libssh2_session_init(). - * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey() - * allows you to pass the `abstract` as part of the call, whereas - * libssh2_userauth_keyboard_interactive() does not! - * - * The only way to set the `abstract` pointer is by calling - * libssh2_session_abstract(), which will replace the existing - * pointer as is done below. This is safe for now (at time of writing), - * but may not be valid in future. - */ - *abstract = c->payload; - - rc = libssh2_userauth_keyboard_interactive( - session, c->username, c->prompt_callback); - break; - } -#ifdef GIT_SSH_MEMORY_CREDENTIALS - case GIT_CREDENTIAL_SSH_MEMORY: { - git_credential_ssh_key *c = (git_credential_ssh_key *)cred; - - GIT_ASSERT(c->username); - GIT_ASSERT(c->privatekey); - - rc = libssh2_userauth_publickey_frommemory( - session, - c->username, - strlen(c->username), - c->publickey, - c->publickey ? strlen(c->publickey) : 0, - c->privatekey, - strlen(c->privatekey), - c->passphrase); - break; - } -#endif - default: - rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; - } - } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - - if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || - rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED || - rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) - return GIT_EAUTH; - - if (rc != LIBSSH2_ERROR_NONE) { - if (!git_error_last()) - ssh_error(session, "Failed to authenticate SSH session"); - return -1; - } - - return 0; -} - -static int request_creds(git_credential **out, ssh_subtransport *t, const char *user, int auth_methods) -{ - int error, no_callback = 0; - git_credential *cred = NULL; - - if (!t->owner->connect_opts.callbacks.credentials) { - no_callback = 1; - } else { - error = t->owner->connect_opts.callbacks.credentials( - &cred, - t->owner->url, - user, - auth_methods, - t->owner->connect_opts.callbacks.payload); - - if (error == GIT_PASSTHROUGH) { - no_callback = 1; - } else if (error < 0) { - return error; - } else if (!cred) { - git_error_set(GIT_ERROR_SSH, "callback failed to initialize SSH credentials"); - return -1; - } - } - - if (no_callback) { - git_error_set(GIT_ERROR_SSH, "authentication required but no callback set"); - return GIT_EAUTH; - } - - if (!(cred->credtype & auth_methods)) { - cred->free(cred); - git_error_set(GIT_ERROR_SSH, "authentication callback returned unsupported credentials type"); - return GIT_EAUTH; - } - - *out = cred; - - return 0; -} - -static int _git_ssh_session_create( - LIBSSH2_SESSION **session, - git_stream *io) -{ - int rc = 0; - LIBSSH2_SESSION *s; - git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent); - - GIT_ASSERT_ARG(session); - - s = libssh2_session_init(); - if (!s) { - git_error_set(GIT_ERROR_NET, "failed to initialize SSH session"); - return -1; - } - - do { - rc = libssh2_session_handshake(s, socket->s); - } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); - - if (rc != LIBSSH2_ERROR_NONE) { - ssh_error(s, "failed to start SSH session"); - libssh2_session_free(s); - return -1; - } - - libssh2_session_set_blocking(s, 1); - - *session = s; - - return 0; -} - -#define SSH_DEFAULT_PORT "22" - -static int _git_ssh_setup_conn( - ssh_subtransport *t, - const char *url, - const char *cmd, - git_smart_subtransport_stream **stream) -{ - int auth_methods, error = 0; - ssh_stream *s; - git_credential *cred = NULL; - LIBSSH2_SESSION *session=NULL; - LIBSSH2_CHANNEL *channel=NULL; - - t->current_stream = NULL; - - *stream = NULL; - if (ssh_stream_alloc(t, cmd, stream) < 0) - return -1; - - s = (ssh_stream *)*stream; - s->session = NULL; - s->channel = NULL; - - if (git_net_str_is_url(url)) - error = git_net_url_parse(&s->url, url); - else - error = git_net_url_parse_scp(&s->url, url); - - if (error < 0) - goto done; - - if ((error = git_socket_stream_new(&s->io, s->url.host, s->url.port)) < 0 || - (error = git_stream_connect(s->io)) < 0) - goto done; - - if ((error = _git_ssh_session_create(&session, s->io)) < 0) - goto done; - - if (t->owner->connect_opts.callbacks.certificate_check != NULL) { - git_cert_hostkey cert = {{ 0 }}, *cert_ptr; - const char *key; - size_t cert_len; - int cert_type; - - cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2; - - key = libssh2_session_hostkey(session, &cert_len, &cert_type); - if (key != NULL) { - cert.type |= GIT_CERT_SSH_RAW; - cert.hostkey = key; - cert.hostkey_len = cert_len; - switch (cert_type) { - case LIBSSH2_HOSTKEY_TYPE_RSA: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_RSA; - break; - case LIBSSH2_HOSTKEY_TYPE_DSS: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_DSS; - break; - -#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256 - case LIBSSH2_HOSTKEY_TYPE_ECDSA_256: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256; - break; - case LIBSSH2_HOSTKEY_TYPE_ECDSA_384: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384; - break; - case LIBSSH2_KNOWNHOST_KEY_ECDSA_521: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521; - break; -#endif - -#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519 - case LIBSSH2_HOSTKEY_TYPE_ED25519: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ED25519; - break; -#endif - default: - cert.raw_type = GIT_CERT_SSH_RAW_TYPE_UNKNOWN; - } - } - -#ifdef LIBSSH2_HOSTKEY_HASH_SHA256 - key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256); - if (key != NULL) { - cert.type |= GIT_CERT_SSH_SHA256; - memcpy(&cert.hash_sha256, key, 32); - } -#endif - - key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); - if (key != NULL) { - cert.type |= GIT_CERT_SSH_SHA1; - memcpy(&cert.hash_sha1, key, 20); - } - - key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); - if (key != NULL) { - cert.type |= GIT_CERT_SSH_MD5; - memcpy(&cert.hash_md5, key, 16); - } - - if (cert.type == 0) { - git_error_set(GIT_ERROR_SSH, "unable to get the host key"); - error = -1; - goto done; - } - - /* We don't currently trust any hostkeys */ - git_error_clear(); - - cert_ptr = &cert; - - error = t->owner->connect_opts.callbacks.certificate_check( - (git_cert *)cert_ptr, - 0, - s->url.host, - t->owner->connect_opts.callbacks.payload); - - if (error < 0 && error != GIT_PASSTHROUGH) { - if (!git_error_last()) - git_error_set(GIT_ERROR_NET, "user cancelled hostkey check"); - - goto done; - } - } - - /* we need the username to ask for auth methods */ - if (!s->url.username) { - if ((error = request_creds(&cred, t, NULL, GIT_CREDENTIAL_USERNAME)) < 0) - goto done; - - s->url.username = git__strdup(((git_credential_username *) cred)->username); - cred->free(cred); - cred = NULL; - if (!s->url.username) - goto done; - } else if (s->url.username && s->url.password) { - if ((error = git_credential_userpass_plaintext_new(&cred, s->url.username, s->url.password)) < 0) - goto done; - } - - if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0) - goto done; - - error = GIT_EAUTH; - /* if we already have something to try */ - if (cred && auth_methods & cred->credtype) - error = _git_ssh_authenticate_session(session, cred); - - while (error == GIT_EAUTH) { - if (cred) { - cred->free(cred); - cred = NULL; - } - - if ((error = request_creds(&cred, t, s->url.username, auth_methods)) < 0) - goto done; - - if (strcmp(s->url.username, git_credential_get_username(cred))) { - git_error_set(GIT_ERROR_SSH, "username does not match previous request"); - error = -1; - goto done; - } - - error = _git_ssh_authenticate_session(session, cred); - - if (error == GIT_EAUTH) { - /* refresh auth methods */ - if ((error = list_auth_methods(&auth_methods, session, s->url.username)) < 0) - goto done; - else - error = GIT_EAUTH; - } - } - - if (error < 0) - goto done; - - channel = libssh2_channel_open_session(session); - if (!channel) { - error = -1; - ssh_error(session, "Failed to open SSH channel"); - goto done; - } - - libssh2_channel_set_blocking(channel, 1); - - s->session = session; - s->channel = channel; - - t->current_stream = s; - -done: - if (error < 0) { - ssh_stream_free(*stream); - - if (session) - libssh2_session_free(session); - } - - if (cred) - cred->free(cred); - - return error; -} - -static int ssh_uploadpack_ls( - ssh_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack; - - return _git_ssh_setup_conn(t, url, cmd, stream); -} - -static int ssh_uploadpack( - ssh_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - GIT_UNUSED(url); - - if (t->current_stream) { - *stream = &t->current_stream->parent; - return 0; - } - - git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK"); - return -1; -} - -static int ssh_receivepack_ls( - ssh_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack; - - - return _git_ssh_setup_conn(t, url, cmd, stream); -} - -static int ssh_receivepack( - ssh_subtransport *t, - const char *url, - git_smart_subtransport_stream **stream) -{ - GIT_UNUSED(url); - - if (t->current_stream) { - *stream = &t->current_stream->parent; - return 0; - } - - git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK"); - return -1; -} - -static int _ssh_action( - git_smart_subtransport_stream **stream, - git_smart_subtransport *subtransport, - const char *url, - git_smart_service_t action) -{ - ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); - - switch (action) { - case GIT_SERVICE_UPLOADPACK_LS: - return ssh_uploadpack_ls(t, url, stream); - - case GIT_SERVICE_UPLOADPACK: - return ssh_uploadpack(t, url, stream); - - case GIT_SERVICE_RECEIVEPACK_LS: - return ssh_receivepack_ls(t, url, stream); - - case GIT_SERVICE_RECEIVEPACK: - return ssh_receivepack(t, url, stream); - } - - *stream = NULL; - return -1; -} - -static int _ssh_close(git_smart_subtransport *subtransport) -{ - ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); - - GIT_ASSERT(!t->current_stream); - - GIT_UNUSED(t); - - return 0; -} - -static void _ssh_free(git_smart_subtransport *subtransport) -{ - ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); - - git__free(t->cmd_uploadpack); - git__free(t->cmd_receivepack); - git__free(t); -} - -#define SSH_AUTH_PUBLICKEY "publickey" -#define SSH_AUTH_PASSWORD "password" -#define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive" - -static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username) -{ - const char *list, *ptr; - - *out = 0; - - list = libssh2_userauth_list(session, username, strlen(username)); - - /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ - if (list == NULL && !libssh2_userauth_authenticated(session)) { - ssh_error(session, "Failed to retrieve list of SSH authentication methods"); - return GIT_EAUTH; - } - - ptr = list; - while (ptr) { - if (*ptr == ',') - ptr++; - - if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) { - *out |= GIT_CREDENTIAL_SSH_KEY; - *out |= GIT_CREDENTIAL_SSH_CUSTOM; -#ifdef GIT_SSH_MEMORY_CREDENTIALS - *out |= GIT_CREDENTIAL_SSH_MEMORY; -#endif - ptr += strlen(SSH_AUTH_PUBLICKEY); - continue; - } - - if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) { - *out |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; - ptr += strlen(SSH_AUTH_PASSWORD); - continue; - } - - if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) { - *out |= GIT_CREDENTIAL_SSH_INTERACTIVE; - ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE); - continue; - } - - /* Skip it if we don't know it */ - ptr = strchr(ptr, ','); - } - - return 0; -} -#endif - -int git_smart_subtransport_ssh( - git_smart_subtransport **out, git_transport *owner, void *param) -{ -#ifdef GIT_SSH - ssh_subtransport *t; - - GIT_ASSERT_ARG(out); - - GIT_UNUSED(param); - - t = git__calloc(sizeof(ssh_subtransport), 1); - GIT_ERROR_CHECK_ALLOC(t); - - t->owner = (transport_smart *)owner; - t->parent.action = _ssh_action; - t->parent.close = _ssh_close; - t->parent.free = _ssh_free; - - *out = (git_smart_subtransport *) t; - return 0; -#else - GIT_UNUSED(owner); - GIT_UNUSED(param); - - GIT_ASSERT_ARG(out); - *out = NULL; - - git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support"); - return -1; -#endif -} - -int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload) -{ -#ifdef GIT_SSH - git_strarray *paths = (git_strarray *) payload; - git_transport *transport; - transport_smart *smart; - ssh_subtransport *t; - int error; - git_smart_subtransport_definition ssh_definition = { - git_smart_subtransport_ssh, - 0, /* no RPC */ - NULL, - }; - - if (paths->count != 2) { - git_error_set(GIT_ERROR_SSH, "invalid ssh paths, must be two strings"); - return GIT_EINVALIDSPEC; - } - - if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0) - return error; - - smart = (transport_smart *) transport; - t = (ssh_subtransport *) smart->wrapped; - - t->cmd_uploadpack = git__strdup(paths->strings[0]); - GIT_ERROR_CHECK_ALLOC(t->cmd_uploadpack); - t->cmd_receivepack = git__strdup(paths->strings[1]); - GIT_ERROR_CHECK_ALLOC(t->cmd_receivepack); - - *out = transport; - return 0; -#else - GIT_UNUSED(owner); - GIT_UNUSED(payload); - - GIT_ASSERT_ARG(out); - *out = NULL; - - git_error_set(GIT_ERROR_INVALID, "cannot create SSH transport. Library was built without SSH support"); - return -1; -#endif -} - -#ifdef GIT_SSH -static void shutdown_ssh(void) -{ - libssh2_exit(); -} -#endif - -int git_transport_ssh_global_init(void) -{ -#ifdef GIT_SSH - if (libssh2_init(0) < 0) { - git_error_set(GIT_ERROR_SSH, "unable to initialize libssh2"); - return -1; - } - - return git_runtime_shutdown_register(shutdown_ssh); - -#else - - /* Nothing to initialize */ - return 0; - -#endif -} diff --git a/src/transports/ssh.h b/src/transports/ssh.h deleted file mode 100644 index d3e741f1d..000000000 --- a/src/transports/ssh.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_transports_ssh_h__ -#define INCLUDE_transports_ssh_h__ - -#include "common.h" - -int git_transport_ssh_global_init(void); - -#endif diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c deleted file mode 100644 index 8ec5b37c5..000000000 --- a/src/transports/winhttp.c +++ /dev/null @@ -1,1686 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#ifdef GIT_WINHTTP - -#include "git2.h" -#include "git2/transport.h" -#include "posix.h" -#include "str.h" -#include "netops.h" -#include "smart.h" -#include "remote.h" -#include "repository.h" -#include "http.h" -#include "git2/sys/credential.h" - -#include -#include - -/* For IInternetSecurityManager zone check */ -#include -#include - -#define WIDEN2(s) L ## s -#define WIDEN(s) WIDEN2(s) - -#define MAX_CONTENT_TYPE_LEN 100 -#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109 -#define CACHED_POST_BODY_BUF_SIZE 4096 -#define UUID_LENGTH_CCH 32 -#define TIMEOUT_INFINITE -1 -#define DEFAULT_CONNECT_TIMEOUT 60000 -#ifndef WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH -#define WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH 0 -#endif - -#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 -# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 0x00000200 -#endif - -#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 -# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 0x00000800 -#endif - -#ifndef WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 -# define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3 0x00002000 -#endif - -#ifndef WINHTTP_NO_CLIENT_CERT_CONTEXT -# define WINHTTP_NO_CLIENT_CERT_CONTEXT NULL -#endif - -#ifndef HTTP_STATUS_PERMANENT_REDIRECT -# define HTTP_STATUS_PERMANENT_REDIRECT 308 -#endif - -#ifndef DWORD_MAX -# define DWORD_MAX 0xffffffff -#endif - -bool git_http__expect_continue = false; - -static const char *prefix_https = "https://"; -static const char *upload_pack_service = "upload-pack"; -static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; -static const char *upload_pack_service_url = "/git-upload-pack"; -static const char *receive_pack_service = "receive-pack"; -static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack"; -static const char *receive_pack_service_url = "/git-receive-pack"; -static const wchar_t *get_verb = L"GET"; -static const wchar_t *post_verb = L"POST"; -static const wchar_t *pragma_nocache = L"Pragma: no-cache"; -static const wchar_t *transfer_encoding = L"Transfer-Encoding: chunked"; -static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | - SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | - SECURITY_FLAG_IGNORE_UNKNOWN_CA; - -#if defined(__MINGW32__) -static const CLSID CLSID_InternetSecurityManager_mingw = - { 0x7B8A2D94, 0x0AC9, 0x11D1, - { 0x89, 0x6C, 0x00, 0xC0, 0x4F, 0xB6, 0xBF, 0xC4 } }; -static const IID IID_IInternetSecurityManager_mingw = - { 0x79EAC9EE, 0xBAF9, 0x11CE, - { 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B } }; - -# define CLSID_InternetSecurityManager CLSID_InternetSecurityManager_mingw -# define IID_IInternetSecurityManager IID_IInternetSecurityManager_mingw -#endif - -#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport) - -typedef enum { - GIT_WINHTTP_AUTH_BASIC = 1, - GIT_WINHTTP_AUTH_NTLM = 2, - GIT_WINHTTP_AUTH_NEGOTIATE = 4, - GIT_WINHTTP_AUTH_DIGEST = 8 -} winhttp_authmechanism_t; - -typedef struct { - git_smart_subtransport_stream parent; - const char *service; - const char *service_url; - const wchar_t *verb; - HINTERNET request; - wchar_t *request_uri; - char *chunk_buffer; - unsigned chunk_buffer_len; - HANDLE post_body; - DWORD post_body_len; - unsigned sent_request : 1, - received_response : 1, - chunked : 1, - status_sending_request_reached: 1; -} winhttp_stream; - -typedef struct { - git_net_url url; - git_credential *cred; - int auth_mechanisms; - bool url_cred_presented; -} winhttp_server; - -typedef struct { - git_smart_subtransport parent; - transport_smart *owner; - - winhttp_server server; - winhttp_server proxy; - - HINTERNET session; - HINTERNET connection; -} winhttp_subtransport; - -static int apply_userpass_credentials(HINTERNET request, DWORD target, int mechanisms, git_credential *cred) -{ - git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred; - wchar_t *user = NULL, *pass = NULL; - int user_len = 0, pass_len = 0, error = 0; - DWORD native_scheme; - - if (mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) { - native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE; - } else if (mechanisms & GIT_WINHTTP_AUTH_NTLM) { - native_scheme = WINHTTP_AUTH_SCHEME_NTLM; - } else if (mechanisms & GIT_WINHTTP_AUTH_DIGEST) { - native_scheme = WINHTTP_AUTH_SCHEME_DIGEST; - } else if (mechanisms & GIT_WINHTTP_AUTH_BASIC) { - native_scheme = WINHTTP_AUTH_SCHEME_BASIC; - } else { - git_error_set(GIT_ERROR_HTTP, "invalid authentication scheme"); - error = GIT_EAUTH; - goto done; - } - - if ((error = user_len = git__utf8_to_16_alloc(&user, c->username)) < 0) - goto done; - - if ((error = pass_len = git__utf8_to_16_alloc(&pass, c->password)) < 0) - goto done; - - if (!WinHttpSetCredentials(request, target, native_scheme, user, pass, NULL)) { - git_error_set(GIT_ERROR_OS, "failed to set credentials"); - error = -1; - } - -done: - if (user_len > 0) - git__memzero(user, user_len * sizeof(wchar_t)); - - if (pass_len > 0) - git__memzero(pass, pass_len * sizeof(wchar_t)); - - git__free(user); - git__free(pass); - - return error; -} - -static int apply_default_credentials(HINTERNET request, DWORD target, int mechanisms) -{ - DWORD autologon_level = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW; - DWORD native_scheme = 0; - - if ((mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) != 0) { - native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE; - } else if ((mechanisms & GIT_WINHTTP_AUTH_NTLM) != 0) { - native_scheme = WINHTTP_AUTH_SCHEME_NTLM; - } else { - git_error_set(GIT_ERROR_HTTP, "invalid authentication scheme"); - return GIT_EAUTH; - } - - /* - * Autologon policy must be "low" to use default creds. - * This is safe as the user has explicitly requested it. - */ - if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &autologon_level, sizeof(DWORD))) { - git_error_set(GIT_ERROR_OS, "could not configure logon policy"); - return -1; - } - - if (!WinHttpSetCredentials(request, target, native_scheme, NULL, NULL, NULL)) { - git_error_set(GIT_ERROR_OS, "could not configure credentials"); - return -1; - } - - return 0; -} - -static int acquire_url_cred( - git_credential **cred, - unsigned int allowed_types, - const char *username, - const char *password) -{ - if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) - return git_credential_userpass_plaintext_new(cred, username, password); - - if ((allowed_types & GIT_CREDENTIAL_DEFAULT) && *username == '\0' && *password == '\0') - return git_credential_default_new(cred); - - return 1; -} - -static int acquire_fallback_cred( - git_credential **cred, - const char *url, - unsigned int allowed_types) -{ - int error = 1; - - /* If the target URI supports integrated Windows authentication - * as an authentication mechanism */ - if (GIT_CREDENTIAL_DEFAULT & allowed_types) { - wchar_t *wide_url; - HRESULT hCoInitResult; - - /* Convert URL to wide characters */ - if (git__utf8_to_16_alloc(&wide_url, url) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert string to wide form"); - return -1; - } - - hCoInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED); - - if (SUCCEEDED(hCoInitResult) || hCoInitResult == RPC_E_CHANGED_MODE) { - IInternetSecurityManager *pISM; - - /* And if the target URI is in the My Computer, Intranet, or Trusted zones */ - if (SUCCEEDED(CoCreateInstance(&CLSID_InternetSecurityManager, NULL, - CLSCTX_ALL, &IID_IInternetSecurityManager, (void **)&pISM))) { - DWORD dwZone; - - if (SUCCEEDED(pISM->lpVtbl->MapUrlToZone(pISM, wide_url, &dwZone, 0)) && - (URLZONE_LOCAL_MACHINE == dwZone || - URLZONE_INTRANET == dwZone || - URLZONE_TRUSTED == dwZone)) { - git_credential *existing = *cred; - - if (existing) - existing->free(existing); - - /* Then use default Windows credentials to authenticate this request */ - error = git_credential_default_new(cred); - } - - pISM->lpVtbl->Release(pISM); - } - - /* Only uninitialize if the call to CoInitializeEx was successful. */ - if (SUCCEEDED(hCoInitResult)) - CoUninitialize(); - } - - git__free(wide_url); - } - - return error; -} - -static int certificate_check(winhttp_stream *s, int valid) -{ - int error; - winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); - PCERT_CONTEXT cert_ctx; - DWORD cert_ctx_size = sizeof(cert_ctx); - git_cert_x509 cert; - - /* If there is no override, we should fail if WinHTTP doesn't think it's fine */ - if (t->owner->connect_opts.callbacks.certificate_check == NULL && !valid) { - if (!git_error_last()) - git_error_set(GIT_ERROR_HTTP, "unknown certificate check failure"); - - return GIT_ECERTIFICATE; - } - - if (t->owner->connect_opts.callbacks.certificate_check == NULL || git__strcmp(t->server.url.scheme, "https") != 0) - return 0; - - if (!WinHttpQueryOption(s->request, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert_ctx, &cert_ctx_size)) { - git_error_set(GIT_ERROR_OS, "failed to get server certificate"); - return -1; - } - - git_error_clear(); - cert.parent.cert_type = GIT_CERT_X509; - cert.data = cert_ctx->pbCertEncoded; - cert.len = cert_ctx->cbCertEncoded; - error = t->owner->connect_opts.callbacks.certificate_check((git_cert *) &cert, valid, t->server.url.host, t->owner->connect_opts.callbacks.payload); - CertFreeCertificateContext(cert_ctx); - - if (error == GIT_PASSTHROUGH) - error = valid ? 0 : GIT_ECERTIFICATE; - - if (error < 0 && !git_error_last()) - git_error_set(GIT_ERROR_HTTP, "user cancelled certificate check"); - - return error; -} - -static void winhttp_stream_close(winhttp_stream *s) -{ - if (s->chunk_buffer) { - git__free(s->chunk_buffer); - s->chunk_buffer = NULL; - } - - if (s->post_body) { - CloseHandle(s->post_body); - s->post_body = NULL; - } - - if (s->request_uri) { - git__free(s->request_uri); - s->request_uri = NULL; - } - - if (s->request) { - WinHttpCloseHandle(s->request); - s->request = NULL; - } - - s->sent_request = 0; -} - -static int apply_credentials( - HINTERNET request, - git_net_url *url, - int target, - git_credential *creds, - int mechanisms) -{ - int error = 0; - - GIT_UNUSED(url); - - /* If we have creds, just apply them */ - if (creds && creds->credtype == GIT_CREDENTIAL_USERPASS_PLAINTEXT) - error = apply_userpass_credentials(request, target, mechanisms, creds); - else if (creds && creds->credtype == GIT_CREDENTIAL_DEFAULT) - error = apply_default_credentials(request, target, mechanisms); - - return error; -} - -static int winhttp_stream_connect(winhttp_stream *s) -{ - winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); - git_str buf = GIT_STR_INIT; - char *proxy_url = NULL; - wchar_t ct[MAX_CONTENT_TYPE_LEN]; - LPCWSTR types[] = { L"*/*", NULL }; - BOOL peerdist = FALSE; - int error = -1; - unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS; - int default_timeout = TIMEOUT_INFINITE; - int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; - DWORD autologon_policy = WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH; - - const char *service_url = s->service_url; - size_t i; - const git_proxy_options *proxy_opts; - - /* If path already ends in /, remove the leading slash from service_url */ - if ((git__suffixcmp(t->server.url.path, "/") == 0) && (git__prefixcmp(service_url, "/") == 0)) - service_url++; - /* Prepare URL */ - git_str_printf(&buf, "%s%s", t->server.url.path, service_url); - - if (git_str_oom(&buf)) - return -1; - - /* Convert URL to wide characters */ - if (git__utf8_to_16_alloc(&s->request_uri, git_str_cstr(&buf)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert string to wide form"); - goto on_error; - } - - /* Establish request */ - s->request = WinHttpOpenRequest( - t->connection, - s->verb, - s->request_uri, - NULL, - WINHTTP_NO_REFERER, - types, - git__strcmp(t->server.url.scheme, "https") == 0 ? WINHTTP_FLAG_SECURE : 0); - - if (!s->request) { - git_error_set(GIT_ERROR_OS, "failed to open request"); - goto on_error; - } - - /* Never attempt default credentials; we'll provide them explicitly. */ - if (!WinHttpSetOption(s->request, WINHTTP_OPTION_AUTOLOGON_POLICY, &autologon_policy, sizeof(DWORD))) - return -1; - - if (!WinHttpSetTimeouts(s->request, default_timeout, default_connect_timeout, default_timeout, default_timeout)) { - git_error_set(GIT_ERROR_OS, "failed to set timeouts for WinHTTP"); - goto on_error; - } - - proxy_opts = &t->owner->connect_opts.proxy_opts; - if (proxy_opts->type == GIT_PROXY_AUTO) { - /* Set proxy if necessary */ - if (git_remote__http_proxy(&proxy_url, t->owner->owner, &t->server.url) < 0) - goto on_error; - } - else if (proxy_opts->type == GIT_PROXY_SPECIFIED) { - proxy_url = git__strdup(proxy_opts->url); - GIT_ERROR_CHECK_ALLOC(proxy_url); - } - - if (proxy_url) { - git_str processed_url = GIT_STR_INIT; - WINHTTP_PROXY_INFO proxy_info; - wchar_t *proxy_wide; - - git_net_url_dispose(&t->proxy.url); - - if ((error = git_net_url_parse(&t->proxy.url, proxy_url)) < 0) - goto on_error; - - if (strcmp(t->proxy.url.scheme, "http") != 0 && strcmp(t->proxy.url.scheme, "https") != 0) { - git_error_set(GIT_ERROR_HTTP, "invalid URL: '%s'", proxy_url); - error = -1; - goto on_error; - } - - git_str_puts(&processed_url, t->proxy.url.scheme); - git_str_PUTS(&processed_url, "://"); - - if (git_net_url_is_ipv6(&t->proxy.url)) - git_str_putc(&processed_url, '['); - - git_str_puts(&processed_url, t->proxy.url.host); - - if (git_net_url_is_ipv6(&t->proxy.url)) - git_str_putc(&processed_url, ']'); - - if (!git_net_url_is_default_port(&t->proxy.url)) - git_str_printf(&processed_url, ":%s", t->proxy.url.port); - - if (git_str_oom(&processed_url)) { - error = -1; - goto on_error; - } - - /* Convert URL to wide characters */ - error = git__utf8_to_16_alloc(&proxy_wide, processed_url.ptr); - git_str_dispose(&processed_url); - if (error < 0) - goto on_error; - - proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; - proxy_info.lpszProxy = proxy_wide; - proxy_info.lpszProxyBypass = NULL; - - if (!WinHttpSetOption(s->request, - WINHTTP_OPTION_PROXY, - &proxy_info, - sizeof(WINHTTP_PROXY_INFO))) { - git_error_set(GIT_ERROR_OS, "failed to set proxy"); - git__free(proxy_wide); - goto on_error; - } - - git__free(proxy_wide); - - if ((error = apply_credentials(s->request, &t->proxy.url, WINHTTP_AUTH_TARGET_PROXY, t->proxy.cred, t->proxy.auth_mechanisms)) < 0) - goto on_error; - } - - /* Disable WinHTTP redirects so we can handle them manually. Why, you ask? - * http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/b2ff8879-ab9f-4218-8f09-16d25dff87ae - */ - if (!WinHttpSetOption(s->request, - WINHTTP_OPTION_DISABLE_FEATURE, - &disable_redirects, - sizeof(disable_redirects))) { - git_error_set(GIT_ERROR_OS, "failed to disable redirects"); - error = -1; - goto on_error; - } - - /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP - * adds itself. This option may not be supported by the underlying - * platform, so we do not error-check it */ - WinHttpSetOption(s->request, - WINHTTP_OPTION_PEERDIST_EXTENSION_STATE, - &peerdist, - sizeof(peerdist)); - - /* Send Pragma: no-cache header */ - if (!WinHttpAddRequestHeaders(s->request, pragma_nocache, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { - git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); - goto on_error; - } - - if (post_verb == s->verb) { - /* Send Content-Type and Accept headers -- only necessary on a POST */ - git_str_clear(&buf); - if (git_str_printf(&buf, - "Content-Type: application/x-git-%s-request", - s->service) < 0) - goto on_error; - - if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert content-type to wide characters"); - goto on_error; - } - - if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, - WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { - git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); - goto on_error; - } - - git_str_clear(&buf); - if (git_str_printf(&buf, - "Accept: application/x-git-%s-result", - s->service) < 0) - goto on_error; - - if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert accept header to wide characters"); - goto on_error; - } - - if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, - WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { - git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); - goto on_error; - } - } - - for (i = 0; i < t->owner->connect_opts.custom_headers.count; i++) { - if (t->owner->connect_opts.custom_headers.strings[i]) { - git_str_clear(&buf); - git_str_puts(&buf, t->owner->connect_opts.custom_headers.strings[i]); - if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert custom header to wide characters"); - goto on_error; - } - - if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, - WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { - git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); - goto on_error; - } - } - } - - if ((error = apply_credentials(s->request, &t->server.url, WINHTTP_AUTH_TARGET_SERVER, t->server.cred, t->server.auth_mechanisms)) < 0) - goto on_error; - - /* We've done everything up to calling WinHttpSendRequest. */ - - error = 0; - -on_error: - if (error < 0) - winhttp_stream_close(s); - - git__free(proxy_url); - git_str_dispose(&buf); - return error; -} - -static int parse_unauthorized_response( - int *allowed_types, - int *allowed_mechanisms, - HINTERNET request) -{ - DWORD supported, first, target; - - *allowed_types = 0; - *allowed_mechanisms = 0; - - /* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes(). - * We can assume this was already done, since we know we are unauthorized. - */ - if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) { - git_error_set(GIT_ERROR_OS, "failed to parse supported auth schemes"); - return GIT_EAUTH; - } - - if (WINHTTP_AUTH_SCHEME_NTLM & supported) { - *allowed_types |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; - *allowed_types |= GIT_CREDENTIAL_DEFAULT; - *allowed_mechanisms |= GIT_WINHTTP_AUTH_NTLM; - } - - if (WINHTTP_AUTH_SCHEME_NEGOTIATE & supported) { - *allowed_types |= GIT_CREDENTIAL_DEFAULT; - *allowed_mechanisms |= GIT_WINHTTP_AUTH_NEGOTIATE; - } - - if (WINHTTP_AUTH_SCHEME_BASIC & supported) { - *allowed_types |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; - *allowed_mechanisms |= GIT_WINHTTP_AUTH_BASIC; - } - - if (WINHTTP_AUTH_SCHEME_DIGEST & supported) { - *allowed_types |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; - *allowed_mechanisms |= GIT_WINHTTP_AUTH_DIGEST; - } - - return 0; -} - -static int write_chunk(HINTERNET request, const char *buffer, size_t len) -{ - DWORD bytes_written; - git_str buf = GIT_STR_INIT; - - /* Chunk header */ - git_str_printf(&buf, "%"PRIXZ"\r\n", len); - - if (git_str_oom(&buf)) - return -1; - - if (!WinHttpWriteData(request, - git_str_cstr(&buf), (DWORD)git_str_len(&buf), - &bytes_written)) { - git_str_dispose(&buf); - git_error_set(GIT_ERROR_OS, "failed to write chunk header"); - return -1; - } - - git_str_dispose(&buf); - - /* Chunk body */ - if (!WinHttpWriteData(request, - buffer, (DWORD)len, - &bytes_written)) { - git_error_set(GIT_ERROR_OS, "failed to write chunk"); - return -1; - } - - /* Chunk footer */ - if (!WinHttpWriteData(request, - "\r\n", 2, - &bytes_written)) { - git_error_set(GIT_ERROR_OS, "failed to write chunk footer"); - return -1; - } - - return 0; -} - -static int winhttp_close_connection(winhttp_subtransport *t) -{ - int ret = 0; - - if (t->connection) { - if (!WinHttpCloseHandle(t->connection)) { - git_error_set(GIT_ERROR_OS, "unable to close connection"); - ret = -1; - } - - t->connection = NULL; - } - - if (t->session) { - if (!WinHttpCloseHandle(t->session)) { - git_error_set(GIT_ERROR_OS, "unable to close session"); - ret = -1; - } - - t->session = NULL; - } - - return ret; -} - -static void CALLBACK winhttp_status( - HINTERNET connection, - DWORD_PTR ctx, - DWORD code, - LPVOID info, - DWORD info_len) -{ - DWORD status; - - GIT_UNUSED(connection); - GIT_UNUSED(info_len); - - switch (code) { - case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE: - status = *((DWORD *)info); - - if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID)) - git_error_set(GIT_ERROR_HTTP, "SSL certificate issued for different common name"); - else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID)) - git_error_set(GIT_ERROR_HTTP, "SSL certificate has expired"); - else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA)) - git_error_set(GIT_ERROR_HTTP, "SSL certificate signed by unknown CA"); - else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT)) - git_error_set(GIT_ERROR_HTTP, "SSL certificate is invalid"); - else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED)) - git_error_set(GIT_ERROR_HTTP, "certificate revocation check failed"); - else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED)) - git_error_set(GIT_ERROR_HTTP, "SSL certificate was revoked"); - else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR)) - git_error_set(GIT_ERROR_HTTP, "security libraries could not be loaded"); - else - git_error_set(GIT_ERROR_HTTP, "unknown security error %lu", status); - - break; - - case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: - ((winhttp_stream *) ctx)->status_sending_request_reached = 1; - - break; - } -} - -static int winhttp_connect( - winhttp_subtransport *t) -{ - wchar_t *wide_host = NULL; - int32_t port; - wchar_t *wide_ua = NULL; - git_str ipv6 = GIT_STR_INIT, ua = GIT_STR_INIT; - const char *host; - int error = -1; - int default_timeout = TIMEOUT_INFINITE; - int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; - DWORD protocols = - WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | - WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | - WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | - WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3; - - t->session = NULL; - t->connection = NULL; - - /* Prepare port */ - if (git__strntol32(&port, t->server.url.port, - strlen(t->server.url.port), NULL, 10) < 0) - goto on_error; - - /* IPv6? Add braces around the host. */ - if (git_net_url_is_ipv6(&t->server.url)) { - if (git_str_printf(&ipv6, "[%s]", t->server.url.host) < 0) - goto on_error; - - host = ipv6.ptr; - } else { - host = t->server.url.host; - } - - /* Prepare host */ - if (git__utf8_to_16_alloc(&wide_host, host) < 0) { - git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters"); - goto on_error; - } - - - if (git_http__user_agent(&ua) < 0) - goto on_error; - - if (git__utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) { - git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters"); - goto on_error; - } - - /* Establish session */ - t->session = WinHttpOpen( - wide_ua, - WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, - WINHTTP_NO_PROXY_NAME, - WINHTTP_NO_PROXY_BYPASS, - 0); - - if (!t->session) { - git_error_set(GIT_ERROR_OS, "failed to init WinHTTP"); - goto on_error; - } - - /* - * Do a best-effort attempt to enable TLS 1.3 and 1.2 but allow this to - * fail; if TLS 1.2 or 1.3 support is not available for some reason, - * ignore the failure (it will keep the default protocols). - */ - if (WinHttpSetOption(t->session, - WINHTTP_OPTION_SECURE_PROTOCOLS, - &protocols, - sizeof(protocols)) == FALSE) { - protocols &= ~WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3; - WinHttpSetOption(t->session, - WINHTTP_OPTION_SECURE_PROTOCOLS, - &protocols, - sizeof(protocols)); - } - - if (!WinHttpSetTimeouts(t->session, default_timeout, default_connect_timeout, default_timeout, default_timeout)) { - git_error_set(GIT_ERROR_OS, "failed to set timeouts for WinHTTP"); - goto on_error; - } - - - /* Establish connection */ - t->connection = WinHttpConnect( - t->session, - wide_host, - (INTERNET_PORT) port, - 0); - - if (!t->connection) { - git_error_set(GIT_ERROR_OS, "failed to connect to host"); - goto on_error; - } - - if (WinHttpSetStatusCallback( - t->connection, - winhttp_status, - WINHTTP_CALLBACK_FLAG_SECURE_FAILURE | WINHTTP_CALLBACK_FLAG_SEND_REQUEST, - 0 - ) == WINHTTP_INVALID_STATUS_CALLBACK) { - git_error_set(GIT_ERROR_OS, "failed to set status callback"); - goto on_error; - } - - error = 0; - -on_error: - if (error < 0) - winhttp_close_connection(t); - - git_str_dispose(&ua); - git_str_dispose(&ipv6); - git__free(wide_host); - git__free(wide_ua); - - return error; -} - -static int do_send_request(winhttp_stream *s, size_t len, bool chunked) -{ - int attempts; - bool success; - - if (len > DWORD_MAX) { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return -1; - } - - for (attempts = 0; attempts < 5; attempts++) { - if (chunked) { - success = WinHttpSendRequest(s->request, - WINHTTP_NO_ADDITIONAL_HEADERS, 0, - WINHTTP_NO_REQUEST_DATA, 0, - WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, (DWORD_PTR)s); - } else { - success = WinHttpSendRequest(s->request, - WINHTTP_NO_ADDITIONAL_HEADERS, 0, - WINHTTP_NO_REQUEST_DATA, 0, - (DWORD)len, (DWORD_PTR)s); - } - - if (success || GetLastError() != (DWORD)SEC_E_BUFFER_TOO_SMALL) - break; - } - - return success ? 0 : -1; -} - -static int send_request(winhttp_stream *s, size_t len, bool chunked) -{ - int request_failed = 1, error, attempts = 0; - DWORD ignore_flags, send_request_error; - - git_error_clear(); - - while (request_failed && attempts++ < 3) { - int cert_valid = 1; - int client_cert_requested = 0; - request_failed = 0; - if ((error = do_send_request(s, len, chunked)) < 0) { - send_request_error = GetLastError(); - request_failed = 1; - switch (send_request_error) { - case ERROR_WINHTTP_SECURE_FAILURE: - cert_valid = 0; - break; - case ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED: - client_cert_requested = 1; - break; - default: - git_error_set(GIT_ERROR_OS, "failed to send request"); - return -1; - } - } - - /* - * Only check the certificate if we were able to reach the sending request phase, or - * received a secure failure error. Otherwise, the server certificate won't be available - * since the request wasn't able to complete (e.g. proxy auth required) - */ - if (!cert_valid || - (!request_failed && s->status_sending_request_reached)) { - git_error_clear(); - if ((error = certificate_check(s, cert_valid)) < 0) { - if (!git_error_last()) - git_error_set(GIT_ERROR_OS, "user cancelled certificate check"); - - return error; - } - } - - /* if neither the request nor the certificate check returned errors, we're done */ - if (!request_failed) - return 0; - - if (!cert_valid) { - ignore_flags = no_check_cert_flags; - if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, &ignore_flags, sizeof(ignore_flags))) { - git_error_set(GIT_ERROR_OS, "failed to set security options"); - return -1; - } - } - - if (client_cert_requested) { - /* - * Client certificates are not supported, explicitly tell the server that - * (it's possible a client certificate was requested but is not required) - */ - if (!WinHttpSetOption(s->request, WINHTTP_OPTION_CLIENT_CERT_CONTEXT, WINHTTP_NO_CLIENT_CERT_CONTEXT, 0)) { - git_error_set(GIT_ERROR_OS, "failed to set client cert context"); - return -1; - } - } - } - - return error; -} - -static int acquire_credentials( - HINTERNET request, - winhttp_server *server, - const char *url_str, - git_credential_acquire_cb cred_cb, - void *cred_cb_payload) -{ - int allowed_types; - int error = 1; - - if (parse_unauthorized_response(&allowed_types, &server->auth_mechanisms, request) < 0) - return -1; - - if (allowed_types) { - git_credential_free(server->cred); - server->cred = NULL; - - /* Start with URL-specified credentials, if there were any. */ - if (!server->url_cred_presented && server->url.username && server->url.password) { - error = acquire_url_cred(&server->cred, allowed_types, server->url.username, server->url.password); - server->url_cred_presented = 1; - - if (error < 0) - return error; - } - - /* Next use the user-defined callback, if there is one. */ - if (error > 0 && cred_cb) { - error = cred_cb(&server->cred, url_str, server->url.username, allowed_types, cred_cb_payload); - - /* Treat GIT_PASSTHROUGH as though git_credential_acquire_cb isn't set */ - if (error == GIT_PASSTHROUGH) - error = 1; - else if (error < 0) - return error; - } - - /* Finally, invoke the fallback default credential lookup. */ - if (error > 0) { - error = acquire_fallback_cred(&server->cred, url_str, allowed_types); - - if (error < 0) - return error; - } - } - - /* - * No error occurred but we could not find appropriate credentials. - * This behaves like a pass-through. - */ - return error; -} - -static int winhttp_stream_read( - git_smart_subtransport_stream *stream, - char *buffer, - size_t buf_size, - size_t *bytes_read) -{ - winhttp_stream *s = (winhttp_stream *)stream; - winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); - DWORD dw_bytes_read; - char replay_count = 0; - int error; - -replay: - /* Enforce a reasonable cap on the number of replays */ - if (replay_count++ >= GIT_HTTP_REPLAY_MAX) { - git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); - return GIT_ERROR; /* not GIT_EAUTH because the exact cause is not clear */ - } - - /* Connect if necessary */ - if (!s->request && winhttp_stream_connect(s) < 0) - return -1; - - if (!s->received_response) { - DWORD status_code, status_code_length, content_type_length, bytes_written; - char expected_content_type_8[MAX_CONTENT_TYPE_LEN]; - wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN]; - - if (!s->sent_request) { - - if ((error = send_request(s, s->post_body_len, false)) < 0) - return error; - - s->sent_request = 1; - } - - if (s->chunked) { - GIT_ASSERT(s->verb == post_verb); - - /* Flush, if necessary */ - if (s->chunk_buffer_len > 0 && - write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) - return -1; - - s->chunk_buffer_len = 0; - - /* Write the final chunk. */ - if (!WinHttpWriteData(s->request, - "0\r\n\r\n", 5, - &bytes_written)) { - git_error_set(GIT_ERROR_OS, "failed to write final chunk"); - return -1; - } - } - else if (s->post_body) { - char *buffer; - DWORD len = s->post_body_len, bytes_read; - - if (INVALID_SET_FILE_POINTER == SetFilePointer(s->post_body, - 0, 0, FILE_BEGIN) && - NO_ERROR != GetLastError()) { - git_error_set(GIT_ERROR_OS, "failed to reset file pointer"); - return -1; - } - - buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE); - GIT_ERROR_CHECK_ALLOC(buffer); - - while (len > 0) { - DWORD bytes_written; - - if (!ReadFile(s->post_body, buffer, - min(CACHED_POST_BODY_BUF_SIZE, len), - &bytes_read, NULL) || - !bytes_read) { - git__free(buffer); - git_error_set(GIT_ERROR_OS, "failed to read from temp file"); - return -1; - } - - if (!WinHttpWriteData(s->request, buffer, - bytes_read, &bytes_written)) { - git__free(buffer); - git_error_set(GIT_ERROR_OS, "failed to write data"); - return -1; - } - - len -= bytes_read; - GIT_ASSERT(bytes_read == bytes_written); - } - - git__free(buffer); - - /* Eagerly close the temp file */ - CloseHandle(s->post_body); - s->post_body = NULL; - } - - if (!WinHttpReceiveResponse(s->request, 0)) { - git_error_set(GIT_ERROR_OS, "failed to receive response"); - return -1; - } - - /* Verify that we got a 200 back */ - status_code_length = sizeof(status_code); - - if (!WinHttpQueryHeaders(s->request, - WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, - WINHTTP_HEADER_NAME_BY_INDEX, - &status_code, &status_code_length, - WINHTTP_NO_HEADER_INDEX)) { - git_error_set(GIT_ERROR_OS, "failed to retrieve status code"); - return -1; - } - - /* The implementation of WinHTTP prior to Windows 7 will not - * redirect to an identical URI. Some Git hosters use self-redirects - * as part of their DoS mitigation strategy. Check first to see if we - * have a redirect status code, and that we haven't already streamed - * a post body. (We can't replay a streamed POST.) */ - if (!s->chunked && - (HTTP_STATUS_MOVED == status_code || - HTTP_STATUS_REDIRECT == status_code || - (HTTP_STATUS_REDIRECT_METHOD == status_code && - get_verb == s->verb) || - HTTP_STATUS_REDIRECT_KEEP_VERB == status_code || - HTTP_STATUS_PERMANENT_REDIRECT == status_code)) { - - /* Check for Windows 7. This workaround is only necessary on - * Windows Vista and earlier. Windows 7 is version 6.1. */ - wchar_t *location; - DWORD location_length; - char *location8; - - /* OK, fetch the Location header from the redirect. */ - if (WinHttpQueryHeaders(s->request, - WINHTTP_QUERY_LOCATION, - WINHTTP_HEADER_NAME_BY_INDEX, - WINHTTP_NO_OUTPUT_BUFFER, - &location_length, - WINHTTP_NO_HEADER_INDEX) || - GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - git_error_set(GIT_ERROR_OS, "failed to read Location header"); - return -1; - } - - location = git__malloc(location_length); - GIT_ERROR_CHECK_ALLOC(location); - - if (!WinHttpQueryHeaders(s->request, - WINHTTP_QUERY_LOCATION, - WINHTTP_HEADER_NAME_BY_INDEX, - location, - &location_length, - WINHTTP_NO_HEADER_INDEX)) { - git_error_set(GIT_ERROR_OS, "failed to read Location header"); - git__free(location); - return -1; - } - - /* Convert the Location header to UTF-8 */ - if (git__utf16_to_8_alloc(&location8, location) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert Location header to UTF-8"); - git__free(location); - return -1; - } - - git__free(location); - - /* Replay the request */ - winhttp_stream_close(s); - - if (!git__prefixcmp_icase(location8, prefix_https)) { - bool follow = (t->owner->connect_opts.follow_redirects != GIT_REMOTE_REDIRECT_NONE); - - /* Upgrade to secure connection; disconnect and start over */ - if (git_net_url_apply_redirect(&t->server.url, location8, follow, s->service_url) < 0) { - git__free(location8); - return -1; - } - - winhttp_close_connection(t); - - if (winhttp_connect(t) < 0) - return -1; - } - - git__free(location8); - goto replay; - } - - /* Handle authentication failures */ - if (status_code == HTTP_STATUS_DENIED) { - int error = acquire_credentials(s->request, - &t->server, - t->owner->url, - t->owner->connect_opts.callbacks.credentials, - t->owner->connect_opts.callbacks.payload); - - if (error < 0) { - return error; - } else if (!error) { - GIT_ASSERT(t->server.cred); - winhttp_stream_close(s); - goto replay; - } - } else if (status_code == HTTP_STATUS_PROXY_AUTH_REQ) { - int error = acquire_credentials(s->request, - &t->proxy, - t->owner->connect_opts.proxy_opts.url, - t->owner->connect_opts.proxy_opts.credentials, - t->owner->connect_opts.proxy_opts.payload); - - if (error < 0) { - return error; - } else if (!error) { - GIT_ASSERT(t->proxy.cred); - winhttp_stream_close(s); - goto replay; - } - } - - if (HTTP_STATUS_OK != status_code) { - git_error_set(GIT_ERROR_HTTP, "request failed with status code: %lu", status_code); - return -1; - } - - /* Verify that we got the correct content-type back */ - if (post_verb == s->verb) - p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service); - else - p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service); - - if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) { - git_error_set(GIT_ERROR_OS, "failed to convert expected content-type to wide characters"); - return -1; - } - - content_type_length = sizeof(content_type); - - if (!WinHttpQueryHeaders(s->request, - WINHTTP_QUERY_CONTENT_TYPE, - WINHTTP_HEADER_NAME_BY_INDEX, - &content_type, &content_type_length, - WINHTTP_NO_HEADER_INDEX)) { - git_error_set(GIT_ERROR_OS, "failed to retrieve response content-type"); - return -1; - } - - if (wcscmp(expected_content_type, content_type)) { - git_error_set(GIT_ERROR_HTTP, "received unexpected content-type"); - return -1; - } - - s->received_response = 1; - } - - if (!WinHttpReadData(s->request, - (LPVOID)buffer, - (DWORD)buf_size, - &dw_bytes_read)) - { - git_error_set(GIT_ERROR_OS, "failed to read data"); - return -1; - } - - *bytes_read = dw_bytes_read; - - return 0; -} - -static int winhttp_stream_write_single( - git_smart_subtransport_stream *stream, - const char *buffer, - size_t len) -{ - winhttp_stream *s = (winhttp_stream *)stream; - DWORD bytes_written; - int error; - - if (!s->request && winhttp_stream_connect(s) < 0) - return -1; - - /* This implementation of write permits only a single call. */ - if (s->sent_request) { - git_error_set(GIT_ERROR_HTTP, "subtransport configured for only one write"); - return -1; - } - - if ((error = send_request(s, len, false)) < 0) - return error; - - s->sent_request = 1; - - if (!WinHttpWriteData(s->request, - (LPCVOID)buffer, - (DWORD)len, - &bytes_written)) { - git_error_set(GIT_ERROR_OS, "failed to write data"); - return -1; - } - - GIT_ASSERT((DWORD)len == bytes_written); - - return 0; -} - -static int put_uuid_string(LPWSTR buffer, size_t buffer_len_cch) -{ - UUID uuid; - RPC_STATUS status = UuidCreate(&uuid); - int result; - - if (RPC_S_OK != status && - RPC_S_UUID_LOCAL_ONLY != status && - RPC_S_UUID_NO_ADDRESS != status) { - git_error_set(GIT_ERROR_HTTP, "unable to generate name for temp file"); - return -1; - } - - if (buffer_len_cch < UUID_LENGTH_CCH + 1) { - git_error_set(GIT_ERROR_HTTP, "buffer too small for name of temp file"); - return -1; - } - -#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API) - result = swprintf_s(buffer, buffer_len_cch, -#else - result = wsprintfW(buffer, -#endif - L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x", - uuid.Data1, uuid.Data2, uuid.Data3, - uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3], - uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]); - - if (result < UUID_LENGTH_CCH) { - git_error_set(GIT_ERROR_OS, "unable to generate name for temp file"); - return -1; - } - - return 0; -} - -static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch) -{ - size_t len; - - if (!GetTempPathW(buffer_len_cch, buffer)) { - git_error_set(GIT_ERROR_OS, "failed to get temp path"); - return -1; - } - - len = wcslen(buffer); - - if (buffer[len - 1] != '\\' && len < buffer_len_cch) - buffer[len++] = '\\'; - - if (put_uuid_string(&buffer[len], (size_t)buffer_len_cch - len) < 0) - return -1; - - return 0; -} - -static int winhttp_stream_write_buffered( - git_smart_subtransport_stream *stream, - const char *buffer, - size_t len) -{ - winhttp_stream *s = (winhttp_stream *)stream; - DWORD bytes_written; - - if (!s->request && winhttp_stream_connect(s) < 0) - return -1; - - /* Buffer the payload, using a temporary file so we delegate - * memory management of the data to the operating system. */ - if (!s->post_body) { - wchar_t temp_path[MAX_PATH + 1]; - - if (get_temp_file(temp_path, MAX_PATH + 1) < 0) - return -1; - - s->post_body = CreateFileW(temp_path, - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_DELETE, NULL, - CREATE_NEW, - FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN, - NULL); - - if (INVALID_HANDLE_VALUE == s->post_body) { - s->post_body = NULL; - git_error_set(GIT_ERROR_OS, "failed to create temporary file"); - return -1; - } - } - - if (!WriteFile(s->post_body, buffer, (DWORD)len, &bytes_written, NULL)) { - git_error_set(GIT_ERROR_OS, "failed to write to temporary file"); - return -1; - } - - GIT_ASSERT((DWORD)len == bytes_written); - - s->post_body_len += bytes_written; - - return 0; -} - -static int winhttp_stream_write_chunked( - git_smart_subtransport_stream *stream, - const char *buffer, - size_t len) -{ - winhttp_stream *s = (winhttp_stream *)stream; - int error; - - if (!s->request && winhttp_stream_connect(s) < 0) - return -1; - - if (!s->sent_request) { - /* Send Transfer-Encoding: chunked header */ - if (!WinHttpAddRequestHeaders(s->request, - transfer_encoding, (ULONG) -1L, - WINHTTP_ADDREQ_FLAG_ADD)) { - git_error_set(GIT_ERROR_OS, "failed to add a header to the request"); - return -1; - } - - if ((error = send_request(s, 0, true)) < 0) - return error; - - s->sent_request = 1; - } - - if (len > CACHED_POST_BODY_BUF_SIZE) { - /* Flush, if necessary */ - if (s->chunk_buffer_len > 0) { - if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) - return -1; - - s->chunk_buffer_len = 0; - } - - /* Write chunk directly */ - if (write_chunk(s->request, buffer, len) < 0) - return -1; - } - else { - /* Append as much to the buffer as we can */ - int count = (int)min(CACHED_POST_BODY_BUF_SIZE - s->chunk_buffer_len, len); - - if (!s->chunk_buffer) { - s->chunk_buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE); - GIT_ERROR_CHECK_ALLOC(s->chunk_buffer); - } - - memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count); - s->chunk_buffer_len += count; - buffer += count; - len -= count; - - /* Is the buffer full? If so, then flush */ - if (CACHED_POST_BODY_BUF_SIZE == s->chunk_buffer_len) { - if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0) - return -1; - - s->chunk_buffer_len = 0; - - /* Is there any remaining data from the source? */ - if (len > 0) { - memcpy(s->chunk_buffer, buffer, len); - s->chunk_buffer_len = (unsigned int)len; - } - } - } - - return 0; -} - -static void winhttp_stream_free(git_smart_subtransport_stream *stream) -{ - winhttp_stream *s = (winhttp_stream *)stream; - - winhttp_stream_close(s); - git__free(s); -} - -static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream) -{ - winhttp_stream *s; - - if (!stream) - return -1; - - s = git__calloc(1, sizeof(winhttp_stream)); - GIT_ERROR_CHECK_ALLOC(s); - - s->parent.subtransport = &t->parent; - s->parent.read = winhttp_stream_read; - s->parent.write = winhttp_stream_write_single; - s->parent.free = winhttp_stream_free; - - *stream = s; - - return 0; -} - -static int winhttp_uploadpack_ls( - winhttp_subtransport *t, - winhttp_stream *s) -{ - GIT_UNUSED(t); - - s->service = upload_pack_service; - s->service_url = upload_pack_ls_service_url; - s->verb = get_verb; - - return 0; -} - -static int winhttp_uploadpack( - winhttp_subtransport *t, - winhttp_stream *s) -{ - GIT_UNUSED(t); - - s->service = upload_pack_service; - s->service_url = upload_pack_service_url; - s->verb = post_verb; - - return 0; -} - -static int winhttp_receivepack_ls( - winhttp_subtransport *t, - winhttp_stream *s) -{ - GIT_UNUSED(t); - - s->service = receive_pack_service; - s->service_url = receive_pack_ls_service_url; - s->verb = get_verb; - - return 0; -} - -static int winhttp_receivepack( - winhttp_subtransport *t, - winhttp_stream *s) -{ - GIT_UNUSED(t); - - /* WinHTTP only supports Transfer-Encoding: chunked - * on Windows Vista (NT 6.0) and higher. */ - s->chunked = git_has_win32_version(6, 0, 0); - - if (s->chunked) - s->parent.write = winhttp_stream_write_chunked; - else - s->parent.write = winhttp_stream_write_buffered; - - s->service = receive_pack_service; - s->service_url = receive_pack_service_url; - s->verb = post_verb; - - return 0; -} - -static int winhttp_action( - git_smart_subtransport_stream **stream, - git_smart_subtransport *subtransport, - const char *url, - git_smart_service_t action) -{ - winhttp_subtransport *t = (winhttp_subtransport *)subtransport; - winhttp_stream *s; - int ret = -1; - - if (!t->connection) - if ((ret = git_net_url_parse(&t->server.url, url)) < 0 || - (ret = winhttp_connect(t)) < 0) - return ret; - - if (winhttp_stream_alloc(t, &s) < 0) - return -1; - - if (!stream) - return -1; - - switch (action) - { - case GIT_SERVICE_UPLOADPACK_LS: - ret = winhttp_uploadpack_ls(t, s); - break; - - case GIT_SERVICE_UPLOADPACK: - ret = winhttp_uploadpack(t, s); - break; - - case GIT_SERVICE_RECEIVEPACK_LS: - ret = winhttp_receivepack_ls(t, s); - break; - - case GIT_SERVICE_RECEIVEPACK: - ret = winhttp_receivepack(t, s); - break; - - default: - GIT_ASSERT(0); - } - - if (!ret) - *stream = &s->parent; - - return ret; -} - -static int winhttp_close(git_smart_subtransport *subtransport) -{ - winhttp_subtransport *t = (winhttp_subtransport *)subtransport; - - git_net_url_dispose(&t->server.url); - git_net_url_dispose(&t->proxy.url); - - if (t->server.cred) { - t->server.cred->free(t->server.cred); - t->server.cred = NULL; - } - - if (t->proxy.cred) { - t->proxy.cred->free(t->proxy.cred); - t->proxy.cred = NULL; - } - - return winhttp_close_connection(t); -} - -static void winhttp_free(git_smart_subtransport *subtransport) -{ - winhttp_subtransport *t = (winhttp_subtransport *)subtransport; - - winhttp_close(subtransport); - - git__free(t); -} - -int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param) -{ - winhttp_subtransport *t; - - GIT_UNUSED(param); - - if (!out) - return -1; - - t = git__calloc(1, sizeof(winhttp_subtransport)); - GIT_ERROR_CHECK_ALLOC(t); - - t->owner = (transport_smart *)owner; - t->parent.action = winhttp_action; - t->parent.close = winhttp_close; - t->parent.free = winhttp_free; - - *out = (git_smart_subtransport *) t; - return 0; -} - -#endif /* GIT_WINHTTP */ diff --git a/src/tree-cache.c b/src/tree-cache.c deleted file mode 100644 index 0977c92f3..000000000 --- a/src/tree-cache.c +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "tree-cache.h" - -#include "pool.h" -#include "tree.h" - -static git_tree_cache *find_child( - const git_tree_cache *tree, const char *path, const char *end) -{ - size_t i, dirlen = end ? (size_t)(end - path) : strlen(path); - - for (i = 0; i < tree->children_count; ++i) { - git_tree_cache *child = tree->children[i]; - - if (child->namelen == dirlen && !memcmp(path, child->name, dirlen)) - return child; - } - - return NULL; -} - -void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path) -{ - const char *ptr = path, *end; - - if (tree == NULL) - return; - - tree->entry_count = -1; - - while (ptr != NULL) { - end = strchr(ptr, '/'); - - if (end == NULL) /* End of path */ - break; - - tree = find_child(tree, ptr, end); - if (tree == NULL) /* We don't have that tree */ - return; - - tree->entry_count = -1; - ptr = end + 1; - } -} - -const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path) -{ - const char *ptr = path, *end; - - if (tree == NULL) { - return NULL; - } - - while (1) { - end = strchr(ptr, '/'); - - tree = find_child(tree, ptr, end); - if (tree == NULL) /* Can't find it */ - return NULL; - - if (end == NULL || *end + 1 == '\0') - return tree; - - ptr = end + 1; - } -} - -static int read_tree_internal(git_tree_cache **out, - const char **buffer_in, const char *buffer_end, - git_pool *pool) -{ - git_tree_cache *tree = NULL; - const char *name_start, *buffer; - int count; - - buffer = name_start = *buffer_in; - - if ((buffer = memchr(buffer, '\0', buffer_end - buffer)) == NULL) - goto corrupted; - - if (++buffer >= buffer_end) - goto corrupted; - - if (git_tree_cache_new(&tree, name_start, pool) < 0) - return -1; - - /* Blank-terminated ASCII decimal number of entries in this tree */ - if (git__strntol32(&count, buffer, buffer_end - buffer, &buffer, 10) < 0) - goto corrupted; - - tree->entry_count = count; - - if (*buffer != ' ' || ++buffer >= buffer_end) - goto corrupted; - - /* Number of children of the tree, newline-terminated */ - if (git__strntol32(&count, buffer, buffer_end - buffer, &buffer, 10) < 0 || count < 0) - goto corrupted; - - tree->children_count = count; - - if (*buffer != '\n' || ++buffer > buffer_end) - goto corrupted; - - /* The SHA1 is only there if it's not invalidated */ - if (tree->entry_count >= 0) { - /* 160-bit SHA-1 for this tree and it's children */ - if (buffer + GIT_OID_RAWSZ > buffer_end) - goto corrupted; - - git_oid_fromraw(&tree->oid, (const unsigned char *)buffer); - buffer += GIT_OID_RAWSZ; - } - - /* Parse children: */ - if (tree->children_count > 0) { - size_t i, bufsize; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&bufsize, tree->children_count, sizeof(git_tree_cache*)); - - tree->children = git_pool_malloc(pool, bufsize); - GIT_ERROR_CHECK_ALLOC(tree->children); - - memset(tree->children, 0x0, bufsize); - - for (i = 0; i < tree->children_count; ++i) { - if (read_tree_internal(&tree->children[i], &buffer, buffer_end, pool) < 0) - goto corrupted; - } - } - - *buffer_in = buffer; - *out = tree; - return 0; - - corrupted: - git_error_set(GIT_ERROR_INDEX, "corrupted TREE extension in index"); - return -1; -} - -int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size, git_pool *pool) -{ - const char *buffer_end = buffer + buffer_size; - - if (read_tree_internal(tree, &buffer, buffer_end, pool) < 0) - return -1; - - if (buffer < buffer_end) { - git_error_set(GIT_ERROR_INDEX, "corrupted TREE extension in index (unexpected trailing data)"); - return -1; - } - - return 0; -} - -static int read_tree_recursive(git_tree_cache *cache, const git_tree *tree, git_pool *pool) -{ - git_repository *repo; - size_t i, j, nentries, ntrees, alloc_size; - int error; - - repo = git_tree_owner(tree); - - git_oid_cpy(&cache->oid, git_tree_id(tree)); - nentries = git_tree_entrycount(tree); - - /* - * We make sure we know how many trees we need to allocate for - * so we don't have to realloc and change the pointers for the - * parents. - */ - ntrees = 0; - for (i = 0; i < nentries; i++) { - const git_tree_entry *entry; - - entry = git_tree_entry_byindex(tree, i); - if (git_tree_entry_filemode(entry) == GIT_FILEMODE_TREE) - ntrees++; - } - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_size, ntrees, sizeof(git_tree_cache *)); - - cache->children_count = ntrees; - cache->children = git_pool_mallocz(pool, alloc_size); - GIT_ERROR_CHECK_ALLOC(cache->children); - - j = 0; - for (i = 0; i < nentries; i++) { - const git_tree_entry *entry; - git_tree *subtree; - - entry = git_tree_entry_byindex(tree, i); - if (git_tree_entry_filemode(entry) != GIT_FILEMODE_TREE) { - cache->entry_count++; - continue; - } - - if ((error = git_tree_cache_new(&cache->children[j], git_tree_entry_name(entry), pool)) < 0) - return error; - - if ((error = git_tree_lookup(&subtree, repo, git_tree_entry_id(entry))) < 0) - return error; - - error = read_tree_recursive(cache->children[j], subtree, pool); - git_tree_free(subtree); - cache->entry_count += cache->children[j]->entry_count; - j++; - - if (error < 0) - return error; - } - - return 0; -} - -int git_tree_cache_read_tree(git_tree_cache **out, const git_tree *tree, git_pool *pool) -{ - int error; - git_tree_cache *cache; - - if ((error = git_tree_cache_new(&cache, "", pool)) < 0) - return error; - - if ((error = read_tree_recursive(cache, tree, pool)) < 0) - return error; - - *out = cache; - return 0; -} - -int git_tree_cache_new(git_tree_cache **out, const char *name, git_pool *pool) -{ - size_t name_len, alloc_size; - git_tree_cache *tree; - - name_len = strlen(name); - - GIT_ERROR_CHECK_ALLOC_ADD3(&alloc_size, sizeof(git_tree_cache), name_len, 1); - - tree = git_pool_malloc(pool, alloc_size); - GIT_ERROR_CHECK_ALLOC(tree); - - memset(tree, 0x0, sizeof(git_tree_cache)); - /* NUL-terminated tree name */ - tree->namelen = name_len; - memcpy(tree->name, name, name_len); - tree->name[name_len] = '\0'; - - *out = tree; - return 0; -} - -static void write_tree(git_str *out, git_tree_cache *tree) -{ - size_t i; - - git_str_printf(out, "%s%c%"PRIdZ" %"PRIuZ"\n", tree->name, 0, tree->entry_count, tree->children_count); - - if (tree->entry_count != -1) - git_str_put(out, (const char *) &tree->oid, GIT_OID_RAWSZ); - - for (i = 0; i < tree->children_count; i++) - write_tree(out, tree->children[i]); -} - -int git_tree_cache_write(git_str *out, git_tree_cache *tree) -{ - write_tree(out, tree); - - return git_str_oom(out) ? -1 : 0; -} diff --git a/src/tree-cache.h b/src/tree-cache.h deleted file mode 100644 index a27e30466..000000000 --- a/src/tree-cache.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_tree_cache_h__ -#define INCLUDE_tree_cache_h__ - -#include "common.h" - -#include "pool.h" -#include "str.h" -#include "git2/oid.h" - -typedef struct git_tree_cache { - struct git_tree_cache **children; - size_t children_count; - - ssize_t entry_count; - git_oid oid; - size_t namelen; - char name[GIT_FLEX_ARRAY]; -} git_tree_cache; - -int git_tree_cache_write(git_str *out, git_tree_cache *tree); -int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size, git_pool *pool); -void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path); -const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path); -int git_tree_cache_new(git_tree_cache **out, const char *name, git_pool *pool); -/** - * Read a tree as the root of the tree cache (like for `git read-tree`) - */ -int git_tree_cache_read_tree(git_tree_cache **out, const git_tree *tree, git_pool *pool); -void git_tree_cache_free(git_tree_cache *tree); - -#endif diff --git a/src/tree.c b/src/tree.c deleted file mode 100644 index a1545dc2d..000000000 --- a/src/tree.c +++ /dev/null @@ -1,1331 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "tree.h" - -#include "commit.h" -#include "git2/repository.h" -#include "git2/object.h" -#include "futils.h" -#include "tree-cache.h" -#include "index.h" -#include "path.h" - -#define DEFAULT_TREE_SIZE 16 -#define MAX_FILEMODE_BYTES 6 - -#define TREE_ENTRY_CHECK_NAMELEN(n) \ - if (n > UINT16_MAX) { git_error_set(GIT_ERROR_INVALID, "tree entry path too long"); } - -static bool valid_filemode(const int filemode) -{ - return (filemode == GIT_FILEMODE_TREE - || filemode == GIT_FILEMODE_BLOB - || filemode == GIT_FILEMODE_BLOB_EXECUTABLE - || filemode == GIT_FILEMODE_LINK - || filemode == GIT_FILEMODE_COMMIT); -} - -GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode) -{ - /* Tree bits set, but it's not a commit */ - if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_TREE) - return GIT_FILEMODE_TREE; - - /* If any of the x bits are set */ - if (GIT_PERMS_IS_EXEC(filemode)) - return GIT_FILEMODE_BLOB_EXECUTABLE; - - /* 16XXXX means commit */ - if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_COMMIT) - return GIT_FILEMODE_COMMIT; - - /* 12XXXX means symlink */ - if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_LINK) - return GIT_FILEMODE_LINK; - - /* Otherwise, return a blob */ - return GIT_FILEMODE_BLOB; -} - -static int valid_entry_name(git_repository *repo, const char *filename) -{ - return *filename != '\0' && - git_path_is_valid(repo, filename, 0, - GIT_FS_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT | GIT_FS_PATH_REJECT_SLASH); -} - -static int entry_sort_cmp(const void *a, const void *b) -{ - const git_tree_entry *e1 = (const git_tree_entry *)a; - const git_tree_entry *e2 = (const git_tree_entry *)b; - - return git_fs_path_cmp( - e1->filename, e1->filename_len, git_tree_entry__is_tree(e1), - e2->filename, e2->filename_len, git_tree_entry__is_tree(e2), - git__strncmp); -} - -int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2) -{ - return entry_sort_cmp(e1, e2); -} - -/** - * Allocate a new self-contained entry, with enough space after it to - * store the filename and the id. - */ -static git_tree_entry *alloc_entry(const char *filename, size_t filename_len, const git_oid *id) -{ - git_tree_entry *entry = NULL; - size_t tree_len; - - TREE_ENTRY_CHECK_NAMELEN(filename_len); - - if (GIT_ADD_SIZET_OVERFLOW(&tree_len, sizeof(git_tree_entry), filename_len) || - GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, 1) || - GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, GIT_OID_RAWSZ)) - return NULL; - - entry = git__calloc(1, tree_len); - if (!entry) - return NULL; - - { - char *filename_ptr; - void *id_ptr; - - filename_ptr = ((char *) entry) + sizeof(git_tree_entry); - memcpy(filename_ptr, filename, filename_len); - entry->filename = filename_ptr; - - id_ptr = filename_ptr + filename_len + 1; - git_oid_cpy(id_ptr, id); - entry->oid = id_ptr; - } - - entry->filename_len = (uint16_t)filename_len; - - return entry; -} - -struct tree_key_search { - const char *filename; - uint16_t filename_len; -}; - -static int homing_search_cmp(const void *key, const void *array_member) -{ - const struct tree_key_search *ksearch = key; - const git_tree_entry *entry = array_member; - - const uint16_t len1 = ksearch->filename_len; - const uint16_t len2 = entry->filename_len; - - return memcmp( - ksearch->filename, - entry->filename, - len1 < len2 ? len1 : len2 - ); -} - -/* - * Search for an entry in a given tree. - * - * Note that this search is performed in two steps because - * of the way tree entries are sorted internally in git: - * - * Entries in a tree are not sorted alphabetically; two entries - * with the same root prefix will have different positions - * depending on whether they are folders (subtrees) or normal files. - * - * Consequently, it is not possible to find an entry on the tree - * with a binary search if you don't know whether the filename - * you're looking for is a folder or a normal file. - * - * To work around this, we first perform a homing binary search - * on the tree, using the minimal length root prefix of our filename. - * Once the comparisons for this homing search start becoming - * ambiguous because of folder vs file sorting, we look linearly - * around the area for our target file. - */ -static int tree_key_search( - size_t *at_pos, - const git_tree *tree, - const char *filename, - size_t filename_len) -{ - struct tree_key_search ksearch; - const git_tree_entry *entry; - size_t homing, i; - - TREE_ENTRY_CHECK_NAMELEN(filename_len); - - ksearch.filename = filename; - ksearch.filename_len = (uint16_t)filename_len; - - /* Initial homing search; find an entry on the tree with - * the same prefix as the filename we're looking for */ - - if (git_array_search(&homing, - tree->entries, &homing_search_cmp, &ksearch) < 0) - return GIT_ENOTFOUND; /* just a signal error; not passed back to user */ - - /* We found a common prefix. Look forward as long as - * there are entries that share the common prefix */ - for (i = homing; i < tree->entries.size; ++i) { - entry = git_array_get(tree->entries, i); - - if (homing_search_cmp(&ksearch, entry) < 0) - break; - - if (entry->filename_len == filename_len && - memcmp(filename, entry->filename, filename_len) == 0) { - if (at_pos) - *at_pos = i; - - return 0; - } - } - - /* If we haven't found our filename yet, look backwards - * too as long as we have entries with the same prefix */ - if (homing > 0) { - i = homing - 1; - - do { - entry = git_array_get(tree->entries, i); - - if (homing_search_cmp(&ksearch, entry) > 0) - break; - - if (entry->filename_len == filename_len && - memcmp(filename, entry->filename, filename_len) == 0) { - if (at_pos) - *at_pos = i; - - return 0; - } - } while (i-- > 0); - } - - /* The filename doesn't exist at all */ - return GIT_ENOTFOUND; -} - -void git_tree_entry_free(git_tree_entry *entry) -{ - if (entry == NULL) - return; - - git__free(entry); -} - -int git_tree_entry_dup(git_tree_entry **dest, const git_tree_entry *source) -{ - git_tree_entry *cpy; - - GIT_ASSERT_ARG(source); - - cpy = alloc_entry(source->filename, source->filename_len, source->oid); - if (cpy == NULL) - return -1; - - cpy->attr = source->attr; - - *dest = cpy; - return 0; -} - -void git_tree__free(void *_tree) -{ - git_tree *tree = _tree; - - git_odb_object_free(tree->odb_obj); - git_array_clear(tree->entries); - git__free(tree); -} - -git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry) -{ - return normalize_filemode(entry->attr); -} - -git_filemode_t git_tree_entry_filemode_raw(const git_tree_entry *entry) -{ - return entry->attr; -} - -const char *git_tree_entry_name(const git_tree_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); - return entry->filename; -} - -const git_oid *git_tree_entry_id(const git_tree_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, NULL); - return entry->oid; -} - -git_object_t git_tree_entry_type(const git_tree_entry *entry) -{ - GIT_ASSERT_ARG_WITH_RETVAL(entry, GIT_OBJECT_INVALID); - - if (S_ISGITLINK(entry->attr)) - return GIT_OBJECT_COMMIT; - else if (S_ISDIR(entry->attr)) - return GIT_OBJECT_TREE; - else - return GIT_OBJECT_BLOB; -} - -int git_tree_entry_to_object( - git_object **object_out, - git_repository *repo, - const git_tree_entry *entry) -{ - GIT_ASSERT_ARG(entry); - GIT_ASSERT_ARG(object_out); - - return git_object_lookup(object_out, repo, entry->oid, GIT_OBJECT_ANY); -} - -static const git_tree_entry *entry_fromname( - const git_tree *tree, const char *name, size_t name_len) -{ - size_t idx; - - if (tree_key_search(&idx, tree, name, name_len) < 0) - return NULL; - - return git_array_get(tree->entries, idx); -} - -const git_tree_entry *git_tree_entry_byname( - const git_tree *tree, const char *filename) -{ - GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(filename, NULL); - - return entry_fromname(tree, filename, strlen(filename)); -} - -const git_tree_entry *git_tree_entry_byindex( - const git_tree *tree, size_t idx) -{ - GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL); - return git_array_get(tree->entries, idx); -} - -const git_tree_entry *git_tree_entry_byid( - const git_tree *tree, const git_oid *id) -{ - size_t i; - const git_tree_entry *e; - - GIT_ASSERT_ARG_WITH_RETVAL(tree, NULL); - - git_array_foreach(tree->entries, i, e) { - if (memcmp(&e->oid->id, &id->id, sizeof(id->id)) == 0) - return e; - } - - return NULL; -} - -size_t git_tree_entrycount(const git_tree *tree) -{ - GIT_ASSERT_ARG_WITH_RETVAL(tree, 0); - return tree->entries.size; -} - -size_t git_treebuilder_entrycount(git_treebuilder *bld) -{ - GIT_ASSERT_ARG_WITH_RETVAL(bld, 0); - - return git_strmap_size(bld->map); -} - -GIT_INLINE(void) set_error(const char *str, const char *path) -{ - if (path) - git_error_set(GIT_ERROR_TREE, "%s - %s", str, path); - else - git_error_set(GIT_ERROR_TREE, "%s", str); -} - -static int tree_error(const char *str, const char *path) -{ - set_error(str, path); - return -1; -} - -static int tree_parse_error(const char *str, const char *path) -{ - set_error(str, path); - return GIT_EINVALID; -} - -static int parse_mode(uint16_t *mode_out, const char *buffer, size_t buffer_len, const char **buffer_out) -{ - int32_t mode; - int error; - - if (!buffer_len || git__isspace(*buffer)) - return -1; - - if ((error = git__strntol32(&mode, buffer, buffer_len, buffer_out, 8)) < 0) - return error; - - if (mode < 0 || mode > UINT16_MAX) - return -1; - - *mode_out = mode; - - return 0; -} - -int git_tree__parse_raw(void *_tree, const char *data, size_t size) -{ - git_tree *tree = _tree; - const char *buffer; - const char *buffer_end; - - buffer = data; - buffer_end = buffer + size; - - tree->odb_obj = NULL; - git_array_init_to_size(tree->entries, DEFAULT_TREE_SIZE); - GIT_ERROR_CHECK_ARRAY(tree->entries); - - while (buffer < buffer_end) { - git_tree_entry *entry; - size_t filename_len; - const char *nul; - uint16_t attr; - - if (parse_mode(&attr, buffer, buffer_end - buffer, &buffer) < 0 || !buffer) - return tree_parse_error("failed to parse tree: can't parse filemode", NULL); - - if (buffer >= buffer_end || (*buffer++) != ' ') - return tree_parse_error("failed to parse tree: missing space after filemode", NULL); - - if ((nul = memchr(buffer, 0, buffer_end - buffer)) == NULL) - return tree_parse_error("failed to parse tree: object is corrupted", NULL); - - if ((filename_len = nul - buffer) == 0 || filename_len > UINT16_MAX) - return tree_parse_error("failed to parse tree: can't parse filename", NULL); - - if ((buffer_end - (nul + 1)) < GIT_OID_RAWSZ) - return tree_parse_error("failed to parse tree: can't parse OID", NULL); - - /* Allocate the entry */ - { - entry = git_array_alloc(tree->entries); - GIT_ERROR_CHECK_ALLOC(entry); - - entry->attr = attr; - entry->filename_len = (uint16_t)filename_len; - entry->filename = buffer; - entry->oid = (git_oid *) ((char *) buffer + filename_len + 1); - } - - buffer += filename_len + 1; - buffer += GIT_OID_RAWSZ; - } - - return 0; -} - -int git_tree__parse(void *_tree, git_odb_object *odb_obj) -{ - git_tree *tree = _tree; - const char *data = git_odb_object_data(odb_obj); - size_t size = git_odb_object_size(odb_obj); - int error; - - if ((error = git_tree__parse_raw(tree, data, size)) < 0 || - (error = git_odb_object_dup(&tree->odb_obj, odb_obj)) < 0) - return error; - - return error; -} - -static size_t find_next_dir(const char *dirname, git_index *index, size_t start) -{ - size_t dirlen, i, entries = git_index_entrycount(index); - - dirlen = strlen(dirname); - for (i = start; i < entries; ++i) { - const git_index_entry *entry = git_index_get_byindex(index, i); - if (strlen(entry->path) < dirlen || - memcmp(entry->path, dirname, dirlen) || - (dirlen > 0 && entry->path[dirlen] != '/')) { - break; - } - } - - return i; -} - -static git_object_t otype_from_mode(git_filemode_t filemode) -{ - switch (filemode) { - case GIT_FILEMODE_TREE: - return GIT_OBJECT_TREE; - case GIT_FILEMODE_COMMIT: - return GIT_OBJECT_COMMIT; - default: - return GIT_OBJECT_BLOB; - } -} - -static int check_entry(git_repository *repo, const char *filename, const git_oid *id, git_filemode_t filemode) -{ - if (!valid_filemode(filemode)) - return tree_error("failed to insert entry: invalid filemode for file", filename); - - if (!valid_entry_name(repo, filename)) - return tree_error("failed to insert entry: invalid name for a tree entry", filename); - - if (git_oid_is_zero(id)) - return tree_error("failed to insert entry: invalid null OID", filename); - - if (filemode != GIT_FILEMODE_COMMIT && - !git_object__is_valid(repo, id, otype_from_mode(filemode))) - return tree_error("failed to insert entry: invalid object specified", filename); - - return 0; -} - -static int git_treebuilder__write_with_buffer( - git_oid *oid, - git_treebuilder *bld, - git_str *buf) -{ - int error = 0; - size_t i, entrycount; - git_odb *odb; - git_tree_entry *entry; - git_vector entries = GIT_VECTOR_INIT; - - git_str_clear(buf); - - entrycount = git_strmap_size(bld->map); - if ((error = git_vector_init(&entries, entrycount, entry_sort_cmp)) < 0) - goto out; - - if (buf->asize == 0 && - (error = git_str_grow(buf, entrycount * 72)) < 0) - goto out; - - git_strmap_foreach_value(bld->map, entry, { - if ((error = git_vector_insert(&entries, entry)) < 0) - goto out; - }); - - git_vector_sort(&entries); - - for (i = 0; i < entries.length && !error; ++i) { - entry = git_vector_get(&entries, i); - - git_str_printf(buf, "%o ", entry->attr); - git_str_put(buf, entry->filename, entry->filename_len + 1); - git_str_put(buf, (char *)entry->oid->id, GIT_OID_RAWSZ); - - if (git_str_oom(buf)) { - error = -1; - goto out; - } - } - - if ((error = git_repository_odb__weakptr(&odb, bld->repo)) == 0) - error = git_odb_write(oid, odb, buf->ptr, buf->size, GIT_OBJECT_TREE); - -out: - git_vector_free(&entries); - - return error; -} - -static int append_entry( - git_treebuilder *bld, - const char *filename, - const git_oid *id, - git_filemode_t filemode, - bool validate) -{ - git_tree_entry *entry; - int error = 0; - - if (validate && ((error = check_entry(bld->repo, filename, id, filemode)) < 0)) - return error; - - entry = alloc_entry(filename, strlen(filename), id); - GIT_ERROR_CHECK_ALLOC(entry); - - entry->attr = (uint16_t)filemode; - - if ((error = git_strmap_set(bld->map, entry->filename, entry)) < 0) { - git_tree_entry_free(entry); - git_error_set(GIT_ERROR_TREE, "failed to append entry %s to the tree builder", filename); - return -1; - } - - return 0; -} - -static int write_tree( - git_oid *oid, - git_repository *repo, - git_index *index, - const char *dirname, - size_t start, - git_str *shared_buf) -{ - git_treebuilder *bld = NULL; - size_t i, entries = git_index_entrycount(index); - int error; - size_t dirname_len = strlen(dirname); - const git_tree_cache *cache; - - cache = git_tree_cache_get(index->tree, dirname); - if (cache != NULL && cache->entry_count >= 0){ - git_oid_cpy(oid, &cache->oid); - return (int)find_next_dir(dirname, index, start); - } - - if ((error = git_treebuilder_new(&bld, repo, NULL)) < 0 || bld == NULL) - return -1; - - /* - * This loop is unfortunate, but necessary. The index doesn't have - * any directories, so we need to handle that manually, and we - * need to keep track of the current position. - */ - for (i = start; i < entries; ++i) { - const git_index_entry *entry = git_index_get_byindex(index, i); - const char *filename, *next_slash; - - /* - * If we've left our (sub)tree, exit the loop and return. The - * first check is an early out (and security for the - * third). The second check is a simple prefix comparison. The - * third check catches situations where there is a directory - * win32/sys and a file win32mmap.c. Without it, the following - * code believes there is a file win32/mmap.c - */ - if (strlen(entry->path) < dirname_len || - memcmp(entry->path, dirname, dirname_len) || - (dirname_len > 0 && entry->path[dirname_len] != '/')) { - break; - } - - filename = entry->path + dirname_len; - if (*filename == '/') - filename++; - next_slash = strchr(filename, '/'); - if (next_slash) { - git_oid sub_oid; - int written; - char *subdir, *last_comp; - - subdir = git__strndup(entry->path, next_slash - entry->path); - GIT_ERROR_CHECK_ALLOC(subdir); - - /* Write out the subtree */ - written = write_tree(&sub_oid, repo, index, subdir, i, shared_buf); - if (written < 0) { - git__free(subdir); - goto on_error; - } else { - i = written - 1; /* -1 because of the loop increment */ - } - - /* - * We need to figure out what we want toinsert - * into this tree. If we're traversing - * deps/zlib/, then we only want to write - * 'zlib' into the tree. - */ - last_comp = strrchr(subdir, '/'); - if (last_comp) { - last_comp++; /* Get rid of the '/' */ - } else { - last_comp = subdir; - } - - error = append_entry(bld, last_comp, &sub_oid, S_IFDIR, true); - git__free(subdir); - if (error < 0) - goto on_error; - } else { - error = append_entry(bld, filename, &entry->id, entry->mode, true); - if (error < 0) - goto on_error; - } - } - - if (git_treebuilder__write_with_buffer(oid, bld, shared_buf) < 0) - goto on_error; - - git_treebuilder_free(bld); - return (int)i; - -on_error: - git_treebuilder_free(bld); - return -1; -} - -int git_tree__write_index( - git_oid *oid, git_index *index, git_repository *repo) -{ - int ret; - git_tree *tree; - git_str shared_buf = GIT_STR_INIT; - bool old_ignore_case = false; - - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(index); - GIT_ASSERT_ARG(repo); - - if (git_index_has_conflicts(index)) { - git_error_set(GIT_ERROR_INDEX, - "cannot create a tree from a not fully merged index."); - return GIT_EUNMERGED; - } - - if (index->tree != NULL && index->tree->entry_count >= 0) { - git_oid_cpy(oid, &index->tree->oid); - return 0; - } - - /* The tree cache didn't help us; we'll have to write - * out a tree. If the index is ignore_case, we must - * make it case-sensitive for the duration of the tree-write - * operation. */ - - if (index->ignore_case) { - old_ignore_case = true; - git_index__set_ignore_case(index, false); - } - - ret = write_tree(oid, repo, index, "", 0, &shared_buf); - git_str_dispose(&shared_buf); - - if (old_ignore_case) - git_index__set_ignore_case(index, true); - - index->tree = NULL; - - if (ret < 0) - return ret; - - git_pool_clear(&index->tree_pool); - - if ((ret = git_tree_lookup(&tree, repo, oid)) < 0) - return ret; - - /* Read the tree cache into the index */ - ret = git_tree_cache_read_tree(&index->tree, tree, &index->tree_pool); - git_tree_free(tree); - - return ret; -} - -int git_treebuilder_new( - git_treebuilder **builder_p, - git_repository *repo, - const git_tree *source) -{ - git_treebuilder *bld; - size_t i; - - GIT_ASSERT_ARG(builder_p); - GIT_ASSERT_ARG(repo); - - bld = git__calloc(1, sizeof(git_treebuilder)); - GIT_ERROR_CHECK_ALLOC(bld); - - bld->repo = repo; - - if (git_strmap_new(&bld->map) < 0) { - git__free(bld); - return -1; - } - - if (source != NULL) { - git_tree_entry *entry_src; - - git_array_foreach(source->entries, i, entry_src) { - if (append_entry( - bld, entry_src->filename, - entry_src->oid, - entry_src->attr, - false) < 0) - goto on_error; - } - } - - *builder_p = bld; - return 0; - -on_error: - git_treebuilder_free(bld); - return -1; -} - -int git_treebuilder_insert( - const git_tree_entry **entry_out, - git_treebuilder *bld, - const char *filename, - const git_oid *id, - git_filemode_t filemode) -{ - git_tree_entry *entry; - int error; - - GIT_ASSERT_ARG(bld); - GIT_ASSERT_ARG(id); - GIT_ASSERT_ARG(filename); - - if ((error = check_entry(bld->repo, filename, id, filemode)) < 0) - return error; - - if ((entry = git_strmap_get(bld->map, filename)) != NULL) { - git_oid_cpy((git_oid *) entry->oid, id); - } else { - entry = alloc_entry(filename, strlen(filename), id); - GIT_ERROR_CHECK_ALLOC(entry); - - if ((error = git_strmap_set(bld->map, entry->filename, entry)) < 0) { - git_tree_entry_free(entry); - git_error_set(GIT_ERROR_TREE, "failed to insert %s", filename); - return -1; - } - } - - entry->attr = filemode; - - if (entry_out) - *entry_out = entry; - - return 0; -} - -static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename) -{ - GIT_ASSERT_ARG_WITH_RETVAL(bld, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(filename, NULL); - - return git_strmap_get(bld->map, filename); -} - -const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename) -{ - return treebuilder_get(bld, filename); -} - -int git_treebuilder_remove(git_treebuilder *bld, const char *filename) -{ - git_tree_entry *entry = treebuilder_get(bld, filename); - - if (entry == NULL) - return tree_error("failed to remove entry: file isn't in the tree", filename); - - git_strmap_delete(bld->map, filename); - git_tree_entry_free(entry); - - return 0; -} - -int git_treebuilder_write(git_oid *oid, git_treebuilder *bld) -{ - GIT_ASSERT_ARG(oid); - GIT_ASSERT_ARG(bld); - - return git_treebuilder__write_with_buffer(oid, bld, &bld->write_cache); -} - -int git_treebuilder_filter( - git_treebuilder *bld, - git_treebuilder_filter_cb filter, - void *payload) -{ - const char *filename; - git_tree_entry *entry; - - GIT_ASSERT_ARG(bld); - GIT_ASSERT_ARG(filter); - - git_strmap_foreach(bld->map, filename, entry, { - if (filter(entry, payload)) { - git_strmap_delete(bld->map, filename); - git_tree_entry_free(entry); - } - }); - - return 0; -} - -int git_treebuilder_clear(git_treebuilder *bld) -{ - git_tree_entry *e; - - GIT_ASSERT_ARG(bld); - - git_strmap_foreach_value(bld->map, e, git_tree_entry_free(e)); - git_strmap_clear(bld->map); - - return 0; -} - -void git_treebuilder_free(git_treebuilder *bld) -{ - if (bld == NULL) - return; - - git_str_dispose(&bld->write_cache); - git_treebuilder_clear(bld); - git_strmap_free(bld->map); - git__free(bld); -} - -static size_t subpath_len(const char *path) -{ - const char *slash_pos = strchr(path, '/'); - if (slash_pos == NULL) - return strlen(path); - - return slash_pos - path; -} - -int git_tree_entry_bypath( - git_tree_entry **entry_out, - const git_tree *root, - const char *path) -{ - int error = 0; - git_tree *subtree; - const git_tree_entry *entry; - size_t filename_len; - - /* Find how long is the current path component (i.e. - * the filename between two slashes */ - filename_len = subpath_len(path); - - if (filename_len == 0) { - git_error_set(GIT_ERROR_TREE, "invalid tree path given"); - return GIT_ENOTFOUND; - } - - entry = entry_fromname(root, path, filename_len); - - if (entry == NULL) { - git_error_set(GIT_ERROR_TREE, - "the path '%.*s' does not exist in the given tree", (int) filename_len, path); - return GIT_ENOTFOUND; - } - - switch (path[filename_len]) { - case '/': - /* If there are more components in the path... - * then this entry *must* be a tree */ - if (!git_tree_entry__is_tree(entry)) { - git_error_set(GIT_ERROR_TREE, - "the path '%.*s' exists but is not a tree", (int) filename_len, path); - return GIT_ENOTFOUND; - } - - /* If there's only a slash left in the path, we - * return the current entry; otherwise, we keep - * walking down the path */ - if (path[filename_len + 1] != '\0') - break; - /* fall through */ - case '\0': - /* If there are no more components in the path, return - * this entry */ - return git_tree_entry_dup(entry_out, entry); - } - - if (git_tree_lookup(&subtree, root->object.repo, entry->oid) < 0) - return -1; - - error = git_tree_entry_bypath( - entry_out, - subtree, - path + filename_len + 1 - ); - - git_tree_free(subtree); - return error; -} - -static int tree_walk( - const git_tree *tree, - git_treewalk_cb callback, - git_str *path, - void *payload, - bool preorder) -{ - int error = 0; - size_t i; - const git_tree_entry *entry; - - git_array_foreach(tree->entries, i, entry) { - if (preorder) { - error = callback(path->ptr, entry, payload); - if (error < 0) { /* negative value stops iteration */ - git_error_set_after_callback_function(error, "git_tree_walk"); - break; - } - if (error > 0) { /* positive value skips this entry */ - error = 0; - continue; - } - } - - if (git_tree_entry__is_tree(entry)) { - git_tree *subtree; - size_t path_len = git_str_len(path); - - error = git_tree_lookup(&subtree, tree->object.repo, entry->oid); - if (error < 0) - break; - - /* append the next entry to the path */ - git_str_puts(path, entry->filename); - git_str_putc(path, '/'); - - if (git_str_oom(path)) - error = -1; - else - error = tree_walk(subtree, callback, path, payload, preorder); - - git_tree_free(subtree); - if (error != 0) - break; - - git_str_truncate(path, path_len); - } - - if (!preorder) { - error = callback(path->ptr, entry, payload); - if (error < 0) { /* negative value stops iteration */ - git_error_set_after_callback_function(error, "git_tree_walk"); - break; - } - error = 0; - } - } - - return error; -} - -int git_tree_walk( - const git_tree *tree, - git_treewalk_mode mode, - git_treewalk_cb callback, - void *payload) -{ - int error = 0; - git_str root_path = GIT_STR_INIT; - - if (mode != GIT_TREEWALK_POST && mode != GIT_TREEWALK_PRE) { - git_error_set(GIT_ERROR_INVALID, "invalid walking mode for tree walk"); - return -1; - } - - error = tree_walk( - tree, callback, &root_path, payload, (mode == GIT_TREEWALK_PRE)); - - git_str_dispose(&root_path); - - return error; -} - -static int compare_entries(const void *_a, const void *_b) -{ - const git_tree_update *a = (git_tree_update *) _a; - const git_tree_update *b = (git_tree_update *) _b; - - return strcmp(a->path, b->path); -} - -static int on_dup_entry(void **old, void *new) -{ - GIT_UNUSED(old); GIT_UNUSED(new); - - git_error_set(GIT_ERROR_TREE, "duplicate entries given for update"); - return -1; -} - -/* - * We keep the previous tree and the new one at each level of the - * stack. When we leave a level we're done with that tree and we can - * write it out to the odb. - */ -typedef struct { - git_treebuilder *bld; - git_tree *tree; - char *name; -} tree_stack_entry; - -/** Count how many slashes (i.e. path components) there are in this string */ -GIT_INLINE(size_t) count_slashes(const char *path) -{ - size_t count = 0; - const char *slash; - - while ((slash = strchr(path, '/')) != NULL) { - count++; - path = slash + 1; - } - - return count; -} - -static bool next_component(git_str *out, const char *in) -{ - const char *slash = strchr(in, '/'); - - git_str_clear(out); - - if (slash) - git_str_put(out, in, slash - in); - - return !!slash; -} - -static int create_popped_tree(tree_stack_entry *current, tree_stack_entry *popped, git_str *component) -{ - int error; - git_oid new_tree; - - git_tree_free(popped->tree); - - /* If the tree would be empty, remove it from the one higher up */ - if (git_treebuilder_entrycount(popped->bld) == 0) { - git_treebuilder_free(popped->bld); - error = git_treebuilder_remove(current->bld, popped->name); - git__free(popped->name); - return error; - } - - error = git_treebuilder_write(&new_tree, popped->bld); - git_treebuilder_free(popped->bld); - - if (error < 0) { - git__free(popped->name); - return error; - } - - /* We've written out the tree, now we have to put the new value into its parent */ - git_str_clear(component); - git_str_puts(component, popped->name); - git__free(popped->name); - - GIT_ERROR_CHECK_ALLOC(component->ptr); - - /* Error out if this would create a D/F conflict in this update */ - if (current->tree) { - const git_tree_entry *to_replace; - to_replace = git_tree_entry_byname(current->tree, component->ptr); - if (to_replace && git_tree_entry_type(to_replace) != GIT_OBJECT_TREE) { - git_error_set(GIT_ERROR_TREE, "D/F conflict when updating tree"); - return -1; - } - } - - return git_treebuilder_insert(NULL, current->bld, component->ptr, &new_tree, GIT_FILEMODE_TREE); -} - -int git_tree_create_updated(git_oid *out, git_repository *repo, git_tree *baseline, size_t nupdates, const git_tree_update *updates) -{ - git_array_t(tree_stack_entry) stack = GIT_ARRAY_INIT; - tree_stack_entry *root_elem; - git_vector entries; - int error; - size_t i; - git_str component = GIT_STR_INIT; - - if ((error = git_vector_init(&entries, nupdates, compare_entries)) < 0) - return error; - - /* Sort the entries for treversal */ - for (i = 0 ; i < nupdates; i++) { - if ((error = git_vector_insert_sorted(&entries, (void *) &updates[i], on_dup_entry)) < 0) - goto cleanup; - } - - root_elem = git_array_alloc(stack); - GIT_ERROR_CHECK_ALLOC(root_elem); - memset(root_elem, 0, sizeof(*root_elem)); - - if (baseline && (error = git_tree_dup(&root_elem->tree, baseline)) < 0) - goto cleanup; - - if ((error = git_treebuilder_new(&root_elem->bld, repo, root_elem->tree)) < 0) - goto cleanup; - - for (i = 0; i < nupdates; i++) { - const git_tree_update *last_update = i == 0 ? NULL : git_vector_get(&entries, i-1); - const git_tree_update *update = git_vector_get(&entries, i); - size_t common_prefix = 0, steps_up, j; - const char *path; - - /* Figure out how much we need to change from the previous tree */ - if (last_update) - common_prefix = git_fs_path_common_dirlen(last_update->path, update->path); - - /* - * The entries are sorted, so when we find we're no - * longer in the same directory, we need to abandon - * the old tree (steps up) and dive down to the next - * one. - */ - steps_up = last_update == NULL ? 0 : count_slashes(&last_update->path[common_prefix]); - - for (j = 0; j < steps_up; j++) { - tree_stack_entry *current, *popped = git_array_pop(stack); - GIT_ASSERT(popped); - - current = git_array_last(stack); - GIT_ASSERT(current); - - if ((error = create_popped_tree(current, popped, &component)) < 0) - goto cleanup; - } - - /* Now that we've created the trees we popped from the stack, let's go back down */ - path = &update->path[common_prefix]; - while (next_component(&component, path)) { - tree_stack_entry *last, *new_entry; - const git_tree_entry *entry; - - last = git_array_last(stack); - entry = last->tree ? git_tree_entry_byname(last->tree, component.ptr) : NULL; - if (!entry) - entry = treebuilder_get(last->bld, component.ptr); - - if (entry && git_tree_entry_type(entry) != GIT_OBJECT_TREE) { - git_error_set(GIT_ERROR_TREE, "D/F conflict when updating tree"); - error = -1; - goto cleanup; - } - - new_entry = git_array_alloc(stack); - GIT_ERROR_CHECK_ALLOC(new_entry); - memset(new_entry, 0, sizeof(*new_entry)); - - new_entry->tree = NULL; - if (entry && (error = git_tree_lookup(&new_entry->tree, repo, git_tree_entry_id(entry))) < 0) - goto cleanup; - - if ((error = git_treebuilder_new(&new_entry->bld, repo, new_entry->tree)) < 0) - goto cleanup; - - new_entry->name = git__strdup(component.ptr); - GIT_ERROR_CHECK_ALLOC(new_entry->name); - - /* Get to the start of the next component */ - path += component.size + 1; - } - - /* After all that, we're finally at the place where we want to perform the update */ - switch (update->action) { - case GIT_TREE_UPDATE_UPSERT: - { - /* Make sure we're replacing something of the same type */ - tree_stack_entry *last = git_array_last(stack); - char *basename = git_fs_path_basename(update->path); - const git_tree_entry *e = git_treebuilder_get(last->bld, basename); - if (e && git_tree_entry_type(e) != git_object__type_from_filemode(update->filemode)) { - git__free(basename); - git_error_set(GIT_ERROR_TREE, "cannot replace '%s' with '%s' at '%s'", - git_object_type2string(git_tree_entry_type(e)), - git_object_type2string(git_object__type_from_filemode(update->filemode)), - update->path); - error = -1; - goto cleanup; - } - - error = git_treebuilder_insert(NULL, last->bld, basename, &update->id, update->filemode); - git__free(basename); - break; - } - case GIT_TREE_UPDATE_REMOVE: - { - tree_stack_entry *last = git_array_last(stack); - char *basename = git_fs_path_basename(update->path); - error = git_treebuilder_remove(last->bld, basename); - git__free(basename); - break; - } - default: - git_error_set(GIT_ERROR_TREE, "unknown action for update"); - error = -1; - goto cleanup; - } - - if (error < 0) - goto cleanup; - } - - /* We're done, go up the stack again and write out the tree */ - { - tree_stack_entry *current = NULL, *popped = NULL; - while ((popped = git_array_pop(stack)) != NULL) { - current = git_array_last(stack); - /* We've reached the top, current is the root tree */ - if (!current) - break; - - if ((error = create_popped_tree(current, popped, &component)) < 0) - goto cleanup; - } - - /* Write out the root tree */ - git__free(popped->name); - git_tree_free(popped->tree); - - error = git_treebuilder_write(out, popped->bld); - git_treebuilder_free(popped->bld); - if (error < 0) - goto cleanup; - } - -cleanup: - { - tree_stack_entry *e; - while ((e = git_array_pop(stack)) != NULL) { - git_treebuilder_free(e->bld); - git_tree_free(e->tree); - git__free(e->name); - } - } - - git_str_dispose(&component); - git_array_clear(stack); - git_vector_free(&entries); - return error; -} - -/* Deprecated Functions */ - -#ifndef GIT_DEPRECATE_HARD - -int git_treebuilder_write_with_buffer(git_oid *oid, git_treebuilder *bld, git_buf *buf) -{ - GIT_UNUSED(buf); - - return git_treebuilder_write(oid, bld); -} - -#endif diff --git a/src/tree.h b/src/tree.h deleted file mode 100644 index 6bd9ed652..000000000 --- a/src/tree.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_tree_h__ -#define INCLUDE_tree_h__ - -#include "common.h" - -#include "git2/tree.h" -#include "repository.h" -#include "odb.h" -#include "vector.h" -#include "strmap.h" -#include "pool.h" - -struct git_tree_entry { - uint16_t attr; - uint16_t filename_len; - const git_oid *oid; - const char *filename; -}; - -struct git_tree { - git_object object; - git_odb_object *odb_obj; - git_array_t(git_tree_entry) entries; -}; - -struct git_treebuilder { - git_repository *repo; - git_strmap *map; - git_str write_cache; -}; - -GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e) -{ - return (S_ISDIR(e->attr) && !S_ISGITLINK(e->attr)); -} - -void git_tree__free(void *tree); -int git_tree__parse(void *tree, git_odb_object *obj); -int git_tree__parse_raw(void *_tree, const char *data, size_t size); - -/** - * Write a tree to the given repository - */ -int git_tree__write_index( - git_oid *oid, git_index *index, git_repository *repo); - -/** - * Obsolete mode kept for compatibility reasons - */ -#define GIT_FILEMODE_BLOB_GROUP_WRITABLE 0100664 - -#endif diff --git a/src/tsort.c b/src/tsort.c deleted file mode 100644 index 045efad23..000000000 --- a/src/tsort.c +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -/** - * An array-of-pointers implementation of Python's Timsort - * Based on code by Christopher Swenson under the MIT license - * - * Copyright (c) 2010 Christopher Swenson - * Copyright (c) 2011 Vicent Marti - */ - -#ifndef MAX -# define MAX(x,y) (((x) > (y) ? (x) : (y))) -#endif - -#ifndef MIN -# define MIN(x,y) (((x) < (y) ? (x) : (y))) -#endif - -static int binsearch( - void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload) -{ - int l, c, r; - void *lx, *cx; - - l = 0; - r = (int)size - 1; - c = r >> 1; - lx = dst[l]; - - /* check for beginning conditions */ - if (cmp(x, lx, payload) < 0) - return 0; - - else if (cmp(x, lx, payload) == 0) { - int i = 1; - while (cmp(x, dst[i], payload) == 0) - i++; - return i; - } - - /* guaranteed not to be >= rx */ - cx = dst[c]; - while (1) { - const int val = cmp(x, cx, payload); - if (val < 0) { - if (c - l <= 1) return c; - r = c; - } else if (val > 0) { - if (r - c <= 1) return c + 1; - l = c; - lx = cx; - } else { - do { - cx = dst[++c]; - } while (cmp(x, cx, payload) == 0); - return c; - } - c = l + ((r - l) >> 1); - cx = dst[c]; - } -} - -/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */ -static void bisort( - void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload) -{ - size_t i; - void *x; - int location; - - for (i = start; i < size; i++) { - int j; - /* If this entry is already correct, just move along */ - if (cmp(dst[i - 1], dst[i], payload) <= 0) - continue; - - /* Else we need to find the right place, shift everything over, and squeeze in */ - x = dst[i]; - location = binsearch(dst, x, i, cmp, payload); - for (j = (int)i - 1; j >= location; j--) { - dst[j + 1] = dst[j]; - } - dst[location] = x; - } -} - - -/* timsort implementation, based on timsort.txt */ -struct tsort_run { - ssize_t start; - ssize_t length; -}; - -struct tsort_store { - size_t alloc; - git__sort_r_cmp cmp; - void *payload; - void **storage; -}; - -static void reverse_elements(void **dst, ssize_t start, ssize_t end) -{ - while (start < end) { - void *tmp = dst[start]; - dst[start] = dst[end]; - dst[end] = tmp; - - start++; - end--; - } -} - -static ssize_t count_run( - void **dst, ssize_t start, ssize_t size, struct tsort_store *store) -{ - ssize_t curr = start + 2; - - if (size - start == 1) - return 1; - - if (start >= size - 2) { - if (store->cmp(dst[size - 2], dst[size - 1], store->payload) > 0) { - void *tmp = dst[size - 1]; - dst[size - 1] = dst[size - 2]; - dst[size - 2] = tmp; - } - - return 2; - } - - if (store->cmp(dst[start], dst[start + 1], store->payload) <= 0) { - while (curr < size - 1 && - store->cmp(dst[curr - 1], dst[curr], store->payload) <= 0) - curr++; - - return curr - start; - } else { - while (curr < size - 1 && - store->cmp(dst[curr - 1], dst[curr], store->payload) > 0) - curr++; - - /* reverse in-place */ - reverse_elements(dst, start, curr - 1); - return curr - start; - } -} - -static size_t compute_minrun(size_t n) -{ - int r = 0; - while (n >= 64) { - r |= n & 1; - n >>= 1; - } - return n + r; -} - -static int check_invariant(struct tsort_run *stack, ssize_t stack_curr) -{ - if (stack_curr < 2) - return 1; - - else if (stack_curr == 2) { - const ssize_t A = stack[stack_curr - 2].length; - const ssize_t B = stack[stack_curr - 1].length; - return (A > B); - } else { - const ssize_t A = stack[stack_curr - 3].length; - const ssize_t B = stack[stack_curr - 2].length; - const ssize_t C = stack[stack_curr - 1].length; - return !((A <= B + C) || (B <= C)); - } -} - -static int resize(struct tsort_store *store, size_t new_size) -{ - if (store->alloc < new_size) { - void **tempstore; - - tempstore = git__reallocarray(store->storage, new_size, sizeof(void *)); - - /** - * Do not propagate on OOM; this will abort the sort and - * leave the array unsorted, but no error code will be - * raised - */ - if (tempstore == NULL) - return -1; - - store->storage = tempstore; - store->alloc = new_size; - } - - return 0; -} - -static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store) -{ - const ssize_t A = stack[stack_curr - 2].length; - const ssize_t B = stack[stack_curr - 1].length; - const ssize_t curr = stack[stack_curr - 2].start; - - void **storage; - ssize_t i, j, k; - - if (resize(store, MIN(A, B)) < 0) - return; - - storage = store->storage; - - /* left merge */ - if (A < B) { - memcpy(storage, &dst[curr], A * sizeof(void *)); - i = 0; - j = curr + A; - - for (k = curr; k < curr + A + B; k++) { - if ((i < A) && (j < curr + A + B)) { - if (store->cmp(storage[i], dst[j], store->payload) <= 0) - dst[k] = storage[i++]; - else - dst[k] = dst[j++]; - } else if (i < A) { - dst[k] = storage[i++]; - } else - dst[k] = dst[j++]; - } - } else { - memcpy(storage, &dst[curr + A], B * sizeof(void *)); - i = B - 1; - j = curr + A - 1; - - for (k = curr + A + B - 1; k >= curr; k--) { - if ((i >= 0) && (j >= curr)) { - if (store->cmp(dst[j], storage[i], store->payload) > 0) - dst[k] = dst[j--]; - else - dst[k] = storage[i--]; - } else if (i >= 0) - dst[k] = storage[i--]; - else - dst[k] = dst[j--]; - } - } -} - -static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store, ssize_t size) -{ - ssize_t A, B, C; - - while (1) { - /* if the stack only has one thing on it, we are done with the collapse */ - if (stack_curr <= 1) - break; - - /* if this is the last merge, just do it */ - if ((stack_curr == 2) && (stack[0].length + stack[1].length == size)) { - merge(dst, stack, stack_curr, store); - stack[0].length += stack[1].length; - stack_curr--; - break; - } - - /* check if the invariant is off for a stack of 2 elements */ - else if ((stack_curr == 2) && (stack[0].length <= stack[1].length)) { - merge(dst, stack, stack_curr, store); - stack[0].length += stack[1].length; - stack_curr--; - break; - } - else if (stack_curr == 2) - break; - - A = stack[stack_curr - 3].length; - B = stack[stack_curr - 2].length; - C = stack[stack_curr - 1].length; - - /* check first invariant */ - if (A <= B + C) { - if (A < C) { - merge(dst, stack, stack_curr - 1, store); - stack[stack_curr - 3].length += stack[stack_curr - 2].length; - stack[stack_curr - 2] = stack[stack_curr - 1]; - stack_curr--; - } else { - merge(dst, stack, stack_curr, store); - stack[stack_curr - 2].length += stack[stack_curr - 1].length; - stack_curr--; - } - } else if (B <= C) { - merge(dst, stack, stack_curr, store); - stack[stack_curr - 2].length += stack[stack_curr - 1].length; - stack_curr--; - } else - break; - } - - return stack_curr; -} - -#define PUSH_NEXT() do {\ - len = count_run(dst, curr, size, store);\ - run = minrun;\ - if (run > (ssize_t)size - curr) run = size - curr;\ - if (run > len) {\ - bisort(&dst[curr], len, run, cmp, payload);\ - len = run;\ - }\ - run_stack[stack_curr].start = curr;\ - run_stack[stack_curr++].length = len;\ - curr += len;\ - if (curr == (ssize_t)size) {\ - /* finish up */ \ - while (stack_curr > 1) { \ - merge(dst, run_stack, stack_curr, store); \ - run_stack[stack_curr - 2].length += run_stack[stack_curr - 1].length; \ - stack_curr--; \ - } \ - if (store->storage != NULL) {\ - git__free(store->storage);\ - store->storage = NULL;\ - }\ - return;\ - }\ -}\ -while (0) - -void git__tsort_r( - void **dst, size_t size, git__sort_r_cmp cmp, void *payload) -{ - struct tsort_store _store, *store = &_store; - struct tsort_run run_stack[128]; - - ssize_t stack_curr = 0; - ssize_t len, run; - ssize_t curr = 0; - ssize_t minrun; - - if (size < 64) { - bisort(dst, 1, size, cmp, payload); - return; - } - - /* compute the minimum run length */ - minrun = (ssize_t)compute_minrun(size); - - /* temporary storage for merges */ - store->alloc = 0; - store->storage = NULL; - store->cmp = cmp; - store->payload = payload; - - PUSH_NEXT(); - PUSH_NEXT(); - PUSH_NEXT(); - - while (1) { - if (!check_invariant(run_stack, stack_curr)) { - stack_curr = collapse(dst, run_stack, stack_curr, store, size); - continue; - } - - PUSH_NEXT(); - } -} - -static int tsort_r_cmp(const void *a, const void *b, void *payload) -{ - return ((git__tsort_cmp)payload)(a, b); -} - -void git__tsort(void **dst, size_t size, git__tsort_cmp cmp) -{ - git__tsort_r(dst, size, tsort_r_cmp, cmp); -} diff --git a/src/unix/map.c b/src/unix/map.c deleted file mode 100644 index 23fcb786e..000000000 --- a/src/unix/map.c +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#if !defined(GIT_WIN32) && !defined(NO_MMAP) - -#include "map.h" -#include -#include -#include - -int git__page_size(size_t *page_size) -{ - long sc_page_size = sysconf(_SC_PAGE_SIZE); - if (sc_page_size < 0) { - git_error_set(GIT_ERROR_OS, "can't determine system page size"); - return -1; - } - *page_size = (size_t) sc_page_size; - return 0; -} - -int git__mmap_alignment(size_t *alignment) -{ - return git__page_size(alignment); -} - -int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) -{ - int mprot = PROT_READ; - int mflag = 0; - - GIT_MMAP_VALIDATE(out, len, prot, flags); - - out->data = NULL; - out->len = 0; - - if (prot & GIT_PROT_WRITE) - mprot |= PROT_WRITE; - - if ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) - mflag = MAP_SHARED; - else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE) - mflag = MAP_PRIVATE; - else - mflag = MAP_SHARED; - - out->data = mmap(NULL, len, mprot, mflag, fd, offset); - - if (!out->data || out->data == MAP_FAILED) { - git_error_set(GIT_ERROR_OS, "failed to mmap. Could not write data"); - return -1; - } - - out->len = len; - - return 0; -} - -int p_munmap(git_map *map) -{ - GIT_ASSERT_ARG(map); - munmap(map->data, map->len); - map->data = NULL; - map->len = 0; - - return 0; -} - -#endif - diff --git a/src/unix/posix.h b/src/unix/posix.h deleted file mode 100644 index 49065e533..000000000 --- a/src/unix/posix.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_unix_posix_h__ -#define INCLUDE_unix_posix_h__ - -#include "common.h" - -#include -#include -#include -#include -#include - -typedef int GIT_SOCKET; -#define INVALID_SOCKET -1 - -#define p_lseek(f,n,w) lseek(f, n, w) -#define p_fstat(f,b) fstat(f, b) -#define p_lstat(p,b) lstat(p,b) -#define p_stat(p,b) stat(p, b) - -#if defined(GIT_USE_STAT_MTIMESPEC) -# define st_atime_nsec st_atimespec.tv_nsec -# define st_mtime_nsec st_mtimespec.tv_nsec -# define st_ctime_nsec st_ctimespec.tv_nsec -#elif defined(GIT_USE_STAT_MTIM) -# define st_atime_nsec st_atim.tv_nsec -# define st_mtime_nsec st_mtim.tv_nsec -# define st_ctime_nsec st_ctim.tv_nsec -#elif !defined(GIT_USE_STAT_MTIME_NSEC) && defined(GIT_USE_NSEC) -# error GIT_USE_NSEC defined but unknown struct stat nanosecond type -#endif - -#define p_utimes(f, t) utimes(f, t) - -#define p_readlink(a, b, c) readlink(a, b, c) -#define p_symlink(o,n) symlink(o, n) -#define p_link(o,n) link(o, n) -#define p_unlink(p) unlink(p) -#define p_mkdir(p,m) mkdir(p, m) -extern char *p_realpath(const char *, char *); - -GIT_INLINE(int) p_fsync(int fd) -{ - p_fsync__cnt++; - return fsync(fd); -} - -#define p_recv(s,b,l,f) recv(s,b,l,f) -#define p_send(s,b,l,f) send(s,b,l,f) -#define p_inet_pton(a, b, c) inet_pton(a, b, c) - -#define p_strcasecmp(s1, s2) strcasecmp(s1, s2) -#define p_strncasecmp(s1, s2, c) strncasecmp(s1, s2, c) -#define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a) -#define p_snprintf snprintf -#define p_chdir(p) chdir(p) -#define p_rmdir(p) rmdir(p) -#define p_access(p,m) access(p,m) -#define p_ftruncate(fd, sz) ftruncate(fd, sz) - -/* - * Pre-Android 5 did not implement a virtual filesystem atop FAT - * partitions for Unix permissions, which causes chmod to fail. However, - * Unix permissions have no effect on Android anyway as file permissions - * are not actually managed this way, so treating it as a no-op across - * all Android is safe. - */ -#ifdef __ANDROID__ -# define p_chmod(p,m) 0 -#else -# define p_chmod(p,m) chmod(p, m) -#endif - -/* see win32/posix.h for explanation about why this exists */ -#define p_lstat_posixly(p,b) lstat(p,b) - -#define p_localtime_r(c, r) localtime_r(c, r) -#define p_gmtime_r(c, r) gmtime_r(c, r) - -#define p_timeval timeval - -#ifdef GIT_USE_FUTIMENS -GIT_INLINE(int) p_futimes(int f, const struct p_timeval t[2]) -{ - struct timespec s[2]; - s[0].tv_sec = t[0].tv_sec; - s[0].tv_nsec = t[0].tv_usec * 1000; - s[1].tv_sec = t[1].tv_sec; - s[1].tv_nsec = t[1].tv_usec * 1000; - return futimens(f, s); -} -#else -# define p_futimes futimes -#endif - -#define p_pread(f, d, s, o) pread(f, d, s, o) -#define p_pwrite(f, d, s, o) pwrite(f, d, s, o) - -#endif diff --git a/src/unix/pthread.h b/src/unix/pthread.h deleted file mode 100644 index 55f4ae227..000000000 --- a/src/unix/pthread.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_unix_pthread_h__ -#define INCLUDE_unix_pthread_h__ - -typedef struct { - pthread_t thread; -} git_thread; - -GIT_INLINE(int) git_threads_global_init(void) { return 0; } - -#define git_thread_create(git_thread_ptr, start_routine, arg) \ - pthread_create(&(git_thread_ptr)->thread, NULL, start_routine, arg) -#define git_thread_join(git_thread_ptr, status) \ - pthread_join((git_thread_ptr)->thread, status) -#define git_thread_currentid() ((size_t)(pthread_self())) -#define git_thread_exit(retval) pthread_exit(retval) - -/* Git Mutex */ -#define git_mutex pthread_mutex_t -#define git_mutex_init(a) pthread_mutex_init(a, NULL) -#define git_mutex_lock(a) pthread_mutex_lock(a) -#define git_mutex_unlock(a) pthread_mutex_unlock(a) -#define git_mutex_free(a) pthread_mutex_destroy(a) - -/* Git condition vars */ -#define git_cond pthread_cond_t -#define git_cond_init(c) pthread_cond_init(c, NULL) -#define git_cond_free(c) pthread_cond_destroy(c) -#define git_cond_wait(c, l) pthread_cond_wait(c, l) -#define git_cond_signal(c) pthread_cond_signal(c) -#define git_cond_broadcast(c) pthread_cond_broadcast(c) - -/* Pthread (-ish) rwlock - * - * This differs from normal pthreads rwlocks in two ways: - * 1. Separate APIs for releasing read locks and write locks (as - * opposed to the pure POSIX API which only has one unlock fn) - * 2. You should not use recursive read locks (i.e. grabbing a read - * lock in a thread that already holds a read lock) because the - * Windows implementation doesn't support it - */ -#define git_rwlock pthread_rwlock_t -#define git_rwlock_init(a) pthread_rwlock_init(a, NULL) -#define git_rwlock_rdlock(a) pthread_rwlock_rdlock(a) -#define git_rwlock_rdunlock(a) pthread_rwlock_unlock(a) -#define git_rwlock_wrlock(a) pthread_rwlock_wrlock(a) -#define git_rwlock_wrunlock(a) pthread_rwlock_unlock(a) -#define git_rwlock_free(a) pthread_rwlock_destroy(a) -#define GIT_RWLOCK_STATIC_INIT PTHREAD_RWLOCK_INITIALIZER - -#endif diff --git a/src/unix/realpath.c b/src/unix/realpath.c deleted file mode 100644 index f1ca669f7..000000000 --- a/src/unix/realpath.c +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#ifndef GIT_WIN32 - -#include -#include -#include -#include - -char *p_realpath(const char *pathname, char *resolved) -{ - char *ret; - if ((ret = realpath(pathname, resolved)) == NULL) - return NULL; - -#ifdef __OpenBSD__ - /* The OpenBSD realpath function behaves differently, - * figure out if the file exists */ - if (access(ret, F_OK) < 0) - ret = NULL; -#endif - return ret; -} - -#endif diff --git a/src/userdiff.h b/src/userdiff.h deleted file mode 100644 index c9a80d712..000000000 --- a/src/userdiff.h +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_userdiff_h__ -#define INCLUDE_userdiff_h__ - -#include "regexp.h" - -/* - * This file isolates the built in diff driver function name patterns. - * Most of these patterns are taken from Git (with permission from the - * original authors for relicensing to libgit2). - */ - -typedef struct { - const char *name; - const char *fns; - const char *words; - int flags; -} git_diff_driver_definition; - -#define WORD_DEFAULT "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+" - -/* - * These builtin driver definition macros have same signature as in core - * git userdiff.c so that the data can be extracted verbatim - */ -#define PATTERNS(NAME, FN_PATS, WORD_PAT) \ - { NAME, FN_PATS, WORD_PAT WORD_DEFAULT, 0 } -#define IPATTERN(NAME, FN_PATS, WORD_PAT) \ - { NAME, FN_PATS, WORD_PAT WORD_DEFAULT, GIT_REGEXP_ICASE } - -/* - * The table of diff driver patterns - * - * Function name patterns are a list of newline separated patterns that - * match a function declaration (i.e. the line you want in the hunk header), - * or a negative pattern prefixed with a '!' to reject a pattern (such as - * rejecting goto labels in C code). - * - * Word boundary patterns are just a simple pattern that will be OR'ed with - * the default value above (i.e. whitespace or non-ASCII characters). - */ -static git_diff_driver_definition builtin_defs[] = { - -IPATTERN("ada", - "!^(.*[ \t])?(is[ \t]+new|renames|is[ \t]+separate)([ \t].*)?$\n" - "!^[ \t]*with[ \t].*$\n" - "^[ \t]*((procedure|function)[ \t]+.*)$\n" - "^[ \t]*((package|protected|task)[ \t]+.*)$", - /* -- */ - "[a-zA-Z][a-zA-Z0-9_]*" - "|[-+]?[0-9][0-9#_.aAbBcCdDeEfF]*([eE][+-]?[0-9_]+)?" - "|=>|\\.\\.|\\*\\*|:=|/=|>=|<=|<<|>>|<>"), - -IPATTERN("fortran", - "!^([C*]|[ \t]*!)\n" - "!^[ \t]*MODULE[ \t]+PROCEDURE[ \t]\n" - "^[ \t]*((END[ \t]+)?(PROGRAM|MODULE|BLOCK[ \t]+DATA" - "|([^'\" \t]+[ \t]+)*(SUBROUTINE|FUNCTION))[ \t]+[A-Z].*)$", - /* -- */ - "[a-zA-Z][a-zA-Z0-9_]*" - "|\\.([Ee][Qq]|[Nn][Ee]|[Gg][TtEe]|[Ll][TtEe]|[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|[Aa][Nn][Dd]|[Oo][Rr]|[Nn]?[Ee][Qq][Vv]|[Nn][Oo][Tt])\\." - /* numbers and format statements like 2E14.4, or ES12.6, 9X. - * Don't worry about format statements without leading digits since - * they would have been matched above as a variable anyway. */ - "|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?" - "|//|\\*\\*|::|[/<>=]="), - -PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", - "[^<>= \t]+"), - -PATTERNS("java", - "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n" - "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=" - "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"), - -PATTERNS("matlab", - "^[[:space:]]*((classdef|function)[[:space:]].*)$|^%%[[:space:]].*$", - "[a-zA-Z_][a-zA-Z0-9_]*|[-+0-9.e]+|[=~<>]=|\\.[*/\\^']|\\|\\||&&"), - -PATTERNS("objc", - /* Negate C statements that can look like functions */ - "!^[ \t]*(do|for|if|else|return|switch|while)\n" - /* Objective-C methods */ - "^[ \t]*([-+][ \t]*\\([ \t]*[A-Za-z_][A-Za-z_0-9* \t]*\\)[ \t]*[A-Za-z_].*)$\n" - /* C functions */ - "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$\n" - /* Objective-C class/protocol definitions */ - "^(@(implementation|interface|protocol)[ \t].*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), - -PATTERNS("pascal", - "^(((class[ \t]+)?(procedure|function)|constructor|destructor|interface|" - "implementation|initialization|finalization)[ \t]*.*)$" - "\n" - "^(.*=[ \t]*(class|record).*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" - "|<>|<=|>=|:=|\\.\\."), - -PATTERNS("perl", - "^package .*\n" - "^sub [[:alnum:]_':]+[ \t]*" - "(\\([^)]*\\)[ \t]*)?" /* prototype */ - /* - * Attributes. A regex can't count nested parentheses, - * so just slurp up whatever we see, taking care not - * to accept lines like "sub foo; # defined elsewhere". - * - * An attribute could contain a semicolon, but at that - * point it seems reasonable enough to give up. - */ - "(:[^;#]*)?" - "(\\{[ \t]*)?" /* brace can come here or on the next line */ - "(#.*)?$\n" /* comment */ - "^(BEGIN|END|INIT|CHECK|UNITCHECK|AUTOLOAD|DESTROY)[ \t]*" - "(\\{[ \t]*)?" /* brace can come here or on the next line */ - "(#.*)?$\n" - "^=head[0-9] .*", /* POD */ - /* -- */ - "[[:alpha:]_'][[:alnum:]_']*" - "|0[xb]?[0-9a-fA-F_]*" - /* taking care not to interpret 3..5 as (3.)(.5) */ - "|[0-9a-fA-F_]+(\\.[0-9a-fA-F_]+)?([eE][-+]?[0-9_]+)?" - "|=>|-[rwxoRWXOezsfdlpSugkbctTBMAC>]|~~|::" - "|&&=|\\|\\|=|//=|\\*\\*=" - "|&&|\\|\\||//|\\+\\+|--|\\*\\*|\\.\\.\\.?" - "|[-+*/%.^&<>=!|]=" - "|=~|!~" - "|<<|<>|<=>|>>"), - -PATTERNS("python", "^[ \t]*((class|def)[ \t].*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"), - -PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$", - /* -- */ - "(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?." - "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~"), - -PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$", - "[={}\"]|[^={}\" \t]+"), - -PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$", - "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"), - -PATTERNS("cpp", - /* Jump targets or access declarations */ - "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:[[:space:]]*($|/[/*])\n" - /* functions/methods, variables, and compounds at top level */ - "^((::[[:space:]]*)?[A-Za-z_].*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lLuU]*" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*"), - -PATTERNS("csharp", - /* Keywords */ - "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n" - /* Methods and constructors */ - "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n" - /* Properties */ - "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n" - /* Type definitions */ - "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct)[ \t]+.*)$\n" - /* Namespace */ - "^[ \t]*(namespace[ \t]+.*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), - -PATTERNS("php", - "^[ \t]*(((public|private|protected|static|final)[ \t]+)*((class|function)[ \t].*))$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xX]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), - -PATTERNS("javascript", - "([a-zA-Z_$][a-zA-Z0-9_$]*(\\.[a-zA-Z0-9_$]+)*[ \t]*=[ \t]*function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)\n" - "([a-zA-Z_$][a-zA-Z0-9_$]*[ \t]*:[ \t]*function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)\n" - "[^a-zA-Z0-9_\\$](function([ \t][a-zA-Z_$][a-zA-Z0-9_$]*)?[^\\{]*)", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xX]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), -}; - -#undef IPATTERN -#undef PATTERNS -#undef WORD_DEFAULT - -#endif - diff --git a/src/utf8.c b/src/utf8.c deleted file mode 100644 index 77065cb71..000000000 --- a/src/utf8.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "utf8.h" - -#include "common.h" - -/* - * git_utf8_iterate is taken from the utf8proc project, - * http://www.public-software-group.org/utf8proc - * - * Copyright (c) 2009 Public Software Group e. V., Berlin, Germany - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the ""Software""), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -static const uint8_t utf8proc_utf8class[256] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -static int utf8_charlen(const uint8_t *str, size_t str_len) -{ - uint8_t length; - size_t i; - - length = utf8proc_utf8class[str[0]]; - if (!length) - return -1; - - if (str_len > 0 && length > str_len) - return -1; - - for (i = 1; i < length; i++) { - if ((str[i] & 0xC0) != 0x80) - return -1; - } - - return (int)length; -} - -int git_utf8_iterate(uint32_t *out, const char *_str, size_t str_len) -{ - const uint8_t *str = (const uint8_t *)_str; - uint32_t uc = 0; - int length; - - *out = 0; - - if ((length = utf8_charlen(str, str_len)) < 0) - return -1; - - switch (length) { - case 1: - uc = str[0]; - break; - case 2: - uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F); - if (uc < 0x80) uc = -1; - break; - case 3: - uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6) - + (str[2] & 0x3F); - if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000) || - (uc >= 0xFDD0 && uc < 0xFDF0)) uc = -1; - break; - case 4: - uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12) - + ((str[2] & 0x3F) << 6) + (str[3] & 0x3F); - if (uc < 0x10000 || uc >= 0x110000) uc = -1; - break; - default: - return -1; - } - - if ((uc & 0xFFFF) >= 0xFFFE) - return -1; - - *out = uc; - return length; -} - -size_t git_utf8_char_length(const char *_str, size_t str_len) -{ - const uint8_t *str = (const uint8_t *)_str; - size_t offset = 0, count = 0; - - while (offset < str_len) { - int length = utf8_charlen(str + offset, str_len - offset); - - if (length < 0) - length = 1; - - offset += length; - count++; - } - - return count; -} - -size_t git_utf8_valid_buf_length(const char *_str, size_t str_len) -{ - const uint8_t *str = (const uint8_t *)_str; - size_t offset = 0; - - while (offset < str_len) { - int length = utf8_charlen(str + offset, str_len - offset); - - if (length < 0) - break; - - offset += length; - } - - return offset; -} diff --git a/src/utf8.h b/src/utf8.h deleted file mode 100644 index dff91b294..000000000 --- a/src/utf8.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_utf8_h__ -#define INCLUDE_utf8_h__ - -#include "common.h" - -/* - * Iterate through an UTF-8 string, yielding one codepoint at a time. - * - * @param out pointer where to store the current codepoint - * @param str current position in the string - * @param str_len size left in the string - * @return length in bytes of the read codepoint; -1 if the codepoint was invalid - */ -extern int git_utf8_iterate(uint32_t *out, const char *str, size_t str_len); - -/** - * Returns the number of characters in the given string. - * - * This function will count invalid codepoints; if any given byte is - * not part of a valid UTF-8 codepoint, then it will be counted toward - * the length in characters. - * - * In other words: - * 0x24 (U+0024 "$") has length 1 - * 0xc2 0xa2 (U+00A2 "¢") has length 1 - * 0x24 0xc2 0xa2 (U+0024 U+00A2 "$¢") has length 2 - * 0xf0 0x90 0x8d 0x88 (U+10348 "𐍈") has length 1 - * 0x24 0xc0 0xc1 0x34 (U+0024 "4) has length 4 - * - * @param str string to scan - * @param str_len size of the string - * @return length in characters of the string - */ -extern size_t git_utf8_char_length(const char *str, size_t str_len); - -/** - * Iterate through an UTF-8 string and stops after finding any invalid UTF-8 - * codepoints. - * - * @param str string to scan - * @param str_len size of the string - * @return length in bytes of the string that contains valid data - */ -extern size_t git_utf8_valid_buf_length(const char *str, size_t str_len); - -#endif diff --git a/src/util.c b/src/util.c deleted file mode 100644 index e06d4ca09..000000000 --- a/src/util.c +++ /dev/null @@ -1,819 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "util.h" - -#include "common.h" - -#ifdef GIT_WIN32 -# include "win32/utf-conv.h" -# include "win32/w32_buffer.h" - -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include - -# ifdef GIT_QSORT_S -# include -# endif -#endif - -#ifdef _MSC_VER -# include -#endif - -#if defined(hpux) || defined(__hpux) || defined(_hpux) -# include -#endif - -int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) -{ - const char *p; - int64_t n, nn, v; - int c, ovfl, neg, ndig; - - p = nptr; - neg = 0; - n = 0; - ndig = 0; - ovfl = 0; - - /* - * White space - */ - while (nptr_len && git__isspace(*p)) - p++, nptr_len--; - - if (!nptr_len) - goto Return; - - /* - * Sign - */ - if (*p == '-' || *p == '+') { - if (*p == '-') - neg = 1; - p++; - nptr_len--; - } - - if (!nptr_len) - goto Return; - - /* - * Automatically detect the base if none was given to us. - * Right now, we assume that a number starting with '0x' - * is hexadecimal and a number starting with '0' is - * octal. - */ - if (base == 0) { - if (*p != '0') - base = 10; - else if (nptr_len > 2 && (p[1] == 'x' || p[1] == 'X')) - base = 16; - else - base = 8; - } - - if (base < 0 || 36 < base) - goto Return; - - /* - * Skip prefix of '0x'-prefixed hexadecimal numbers. There is no - * need to do the same for '0'-prefixed octal numbers as a - * leading '0' does not have any impact. Also, if we skip a - * leading '0' in such a string, then we may end up with no - * digits left and produce an error later on which isn't one. - */ - if (base == 16 && nptr_len > 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { - p += 2; - nptr_len -= 2; - } - - /* - * Non-empty sequence of digits - */ - for (; nptr_len > 0; p++,ndig++,nptr_len--) { - c = *p; - v = base; - if ('0'<=c && c<='9') - v = c - '0'; - else if ('a'<=c && c<='z') - v = c - 'a' + 10; - else if ('A'<=c && c<='Z') - v = c - 'A' + 10; - if (v >= base) - break; - v = neg ? -v : v; - if (git__multiply_int64_overflow(&nn, n, base) || git__add_int64_overflow(&n, nn, v)) { - ovfl = 1; - /* Keep on iterating until the end of this number */ - continue; - } - } - -Return: - if (ndig == 0) { - git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: not a number"); - return -1; - } - - if (endptr) - *endptr = p; - - if (ovfl) { - git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: overflow error"); - return -1; - } - - *result = n; - return 0; -} - -int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) -{ - const char *tmp_endptr; - int32_t tmp_int; - int64_t tmp_long; - int error; - - if ((error = git__strntol64(&tmp_long, nptr, nptr_len, &tmp_endptr, base)) < 0) - return error; - - tmp_int = tmp_long & 0xFFFFFFFF; - if (tmp_int != tmp_long) { - int len = (int)(tmp_endptr - nptr); - git_error_set(GIT_ERROR_INVALID, "failed to convert: '%.*s' is too large", len, nptr); - return -1; - } - - *result = tmp_int; - if (endptr) - *endptr = tmp_endptr; - - return error; -} - -int git__strcasecmp(const char *a, const char *b) -{ - while (*a && *b && git__tolower(*a) == git__tolower(*b)) - ++a, ++b; - return ((unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b)); -} - -int git__strcasesort_cmp(const char *a, const char *b) -{ - int cmp = 0; - - while (*a && *b) { - if (*a != *b) { - if (git__tolower(*a) != git__tolower(*b)) - break; - /* use case in sort order even if not in equivalence */ - if (!cmp) - cmp = (int)(*(const uint8_t *)a) - (int)(*(const uint8_t *)b); - } - - ++a, ++b; - } - - if (*a || *b) - return (unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b); - - return cmp; -} - -int git__strncasecmp(const char *a, const char *b, size_t sz) -{ - int al, bl; - - do { - al = (unsigned char)git__tolower(*a); - bl = (unsigned char)git__tolower(*b); - ++a, ++b; - } while (--sz && al && al == bl); - - return al - bl; -} - -void git__strntolower(char *str, size_t len) -{ - size_t i; - - for (i = 0; i < len; ++i) { - str[i] = (char)git__tolower(str[i]); - } -} - -void git__strtolower(char *str) -{ - git__strntolower(str, strlen(str)); -} - -GIT_INLINE(int) prefixcmp(const char *str, size_t str_n, const char *prefix, bool icase) -{ - int s, p; - - while (str_n--) { - s = (unsigned char)*str++; - p = (unsigned char)*prefix++; - - if (icase) { - s = git__tolower(s); - p = git__tolower(p); - } - - if (!p) - return 0; - - if (s != p) - return s - p; - } - - return (0 - *prefix); -} - -int git__prefixcmp(const char *str, const char *prefix) -{ - unsigned char s, p; - - while (1) { - p = *prefix++; - s = *str++; - - if (!p) - return 0; - - if (s != p) - return s - p; - } -} - -int git__prefixncmp(const char *str, size_t str_n, const char *prefix) -{ - return prefixcmp(str, str_n, prefix, false); -} - -int git__prefixcmp_icase(const char *str, const char *prefix) -{ - return prefixcmp(str, SIZE_MAX, prefix, true); -} - -int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix) -{ - return prefixcmp(str, str_n, prefix, true); -} - -int git__suffixcmp(const char *str, const char *suffix) -{ - size_t a = strlen(str); - size_t b = strlen(suffix); - if (a < b) - return -1; - return strcmp(str + (a - b), suffix); -} - -char *git__strtok(char **end, const char *sep) -{ - char *ptr = *end; - - while (*ptr && strchr(sep, *ptr)) - ++ptr; - - if (*ptr) { - char *start = ptr; - *end = start + 1; - - while (**end && !strchr(sep, **end)) - ++*end; - - if (**end) { - **end = '\0'; - ++*end; - } - - return start; - } - - return NULL; -} - -/* Similar to strtok, but does not collapse repeated tokens. */ -char *git__strsep(char **end, const char *sep) -{ - char *start = *end, *ptr = *end; - - while (*ptr && !strchr(sep, *ptr)) - ++ptr; - - if (*ptr) { - *end = ptr + 1; - *ptr = '\0'; - - return start; - } - - return NULL; -} - -size_t git__linenlen(const char *buffer, size_t buffer_len) -{ - char *nl = memchr(buffer, '\n', buffer_len); - return nl ? (size_t)(nl - buffer) + 1 : buffer_len; -} - -/* - * Adapted Not So Naive algorithm from http://www-igm.univ-mlv.fr/~lecroq/string/ - */ -const void * git__memmem(const void *haystack, size_t haystacklen, - const void *needle, size_t needlelen) -{ - const char *h, *n; - size_t j, k, l; - - if (needlelen > haystacklen || !haystacklen || !needlelen) - return NULL; - - h = (const char *) haystack, - n = (const char *) needle; - - if (needlelen == 1) - return memchr(haystack, *n, haystacklen); - - if (n[0] == n[1]) { - k = 2; - l = 1; - } else { - k = 1; - l = 2; - } - - j = 0; - while (j <= haystacklen - needlelen) { - if (n[1] != h[j + 1]) { - j += k; - } else { - if (memcmp(n + 2, h + j + 2, needlelen - 2) == 0 && - n[0] == h[j]) - return h + j; - j += l; - } - } - - return NULL; -} - -void git__hexdump(const char *buffer, size_t len) -{ - static const size_t LINE_WIDTH = 16; - - size_t line_count, last_line, i, j; - const char *line; - - line_count = (len / LINE_WIDTH); - last_line = (len % LINE_WIDTH); - - for (i = 0; i < line_count; ++i) { - printf("%08" PRIxZ " ", (i * LINE_WIDTH)); - - line = buffer + (i * LINE_WIDTH); - for (j = 0; j < LINE_WIDTH; ++j, ++line) { - printf("%02x ", (unsigned char)*line & 0xFF); - - if (j == (LINE_WIDTH / 2)) - printf(" "); - } - - printf(" |"); - - line = buffer + (i * LINE_WIDTH); - for (j = 0; j < LINE_WIDTH; ++j, ++line) - printf("%c", (*line >= 32 && *line <= 126) ? *line : '.'); - - printf("|\n"); - } - - if (last_line > 0) { - printf("%08" PRIxZ " ", (line_count * LINE_WIDTH)); - - line = buffer + (line_count * LINE_WIDTH); - for (j = 0; j < last_line; ++j, ++line) { - printf("%02x ", (unsigned char)*line & 0xFF); - - if (j == (LINE_WIDTH / 2)) - printf(" "); - } - - if (j < (LINE_WIDTH / 2)) - printf(" "); - for (j = 0; j < (LINE_WIDTH - last_line); ++j) - printf(" "); - - printf(" |"); - - line = buffer + (line_count * LINE_WIDTH); - for (j = 0; j < last_line; ++j, ++line) - printf("%c", (*line >= 32 && *line <= 126) ? *line : '.'); - - printf("|\n"); - } - - printf("\n"); -} - -#ifdef GIT_LEGACY_HASH -uint32_t git__hash(const void *key, int len, unsigned int seed) -{ - const uint32_t m = 0x5bd1e995; - const int r = 24; - uint32_t h = seed ^ len; - - const unsigned char *data = (const unsigned char *)key; - - while(len >= 4) { - uint32_t k = *(uint32_t *)data; - - k *= m; - k ^= k >> r; - k *= m; - - h *= m; - h ^= k; - - data += 4; - len -= 4; - } - - switch(len) { - case 3: h ^= data[2] << 16; - case 2: h ^= data[1] << 8; - case 1: h ^= data[0]; - h *= m; - }; - - h ^= h >> 13; - h *= m; - h ^= h >> 15; - - return h; -} -#else -/* - Cross-platform version of Murmurhash3 - http://code.google.com/p/smhasher/wiki/MurmurHash3 - by Austin Appleby (aappleby@gmail.com) - - This code is on the public domain. -*/ -uint32_t git__hash(const void *key, int len, uint32_t seed) -{ - -#define MURMUR_BLOCK() {\ - k1 *= c1; \ - k1 = git__rotl(k1,11);\ - k1 *= c2;\ - h1 ^= k1;\ - h1 = h1*3 + 0x52dce729;\ - c1 = c1*5 + 0x7b7d159c;\ - c2 = c2*5 + 0x6bce6396;\ -} - - const uint8_t *data = (const uint8_t*)key; - const int nblocks = len / 4; - - const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4); - const uint8_t *tail = (const uint8_t *)(data + nblocks * 4); - - uint32_t h1 = 0x971e137b ^ seed; - uint32_t k1; - - uint32_t c1 = 0x95543787; - uint32_t c2 = 0x2ad7eb25; - - int i; - - for (i = -nblocks; i; i++) { - k1 = blocks[i]; - MURMUR_BLOCK(); - } - - k1 = 0; - - switch(len & 3) { - case 3: k1 ^= tail[2] << 16; - /* fall through */ - case 2: k1 ^= tail[1] << 8; - /* fall through */ - case 1: k1 ^= tail[0]; - MURMUR_BLOCK(); - } - - h1 ^= len; - h1 ^= h1 >> 16; - h1 *= 0x85ebca6b; - h1 ^= h1 >> 13; - h1 *= 0xc2b2ae35; - h1 ^= h1 >> 16; - - return h1; -} -#endif - -/** - * A modified `bsearch` from the BSD glibc. - * - * Copyright (c) 1990 Regents of the University of California. - * All rights reserved. - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. [rescinded 22 July 1999] - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -int git__bsearch( - void **array, - size_t array_len, - const void *key, - int (*compare)(const void *, const void *), - size_t *position) -{ - size_t lim; - int cmp = -1; - void **part, **base = array; - - for (lim = array_len; lim != 0; lim >>= 1) { - part = base + (lim >> 1); - cmp = (*compare)(key, *part); - if (cmp == 0) { - base = part; - break; - } - if (cmp > 0) { /* key > p; take right partition */ - base = part + 1; - lim--; - } /* else take left partition */ - } - - if (position) - *position = (base - array); - - return (cmp == 0) ? 0 : GIT_ENOTFOUND; -} - -int git__bsearch_r( - void **array, - size_t array_len, - const void *key, - int (*compare_r)(const void *, const void *, void *), - void *payload, - size_t *position) -{ - size_t lim; - int cmp = -1; - void **part, **base = array; - - for (lim = array_len; lim != 0; lim >>= 1) { - part = base + (lim >> 1); - cmp = (*compare_r)(key, *part, payload); - if (cmp == 0) { - base = part; - break; - } - if (cmp > 0) { /* key > p; take right partition */ - base = part + 1; - lim--; - } /* else take left partition */ - } - - if (position) - *position = (base - array); - - return (cmp == 0) ? 0 : GIT_ENOTFOUND; -} - -/** - * A strcmp wrapper - * - * We don't want direct pointers to the CRT on Windows, we may - * get stdcall conflicts. - */ -int git__strcmp_cb(const void *a, const void *b) -{ - return strcmp((const char *)a, (const char *)b); -} - -int git__strcasecmp_cb(const void *a, const void *b) -{ - return strcasecmp((const char *)a, (const char *)b); -} - -int git__parse_bool(int *out, const char *value) -{ - /* A missing value means true */ - if (value == NULL || - !strcasecmp(value, "true") || - !strcasecmp(value, "yes") || - !strcasecmp(value, "on")) { - *out = 1; - return 0; - } - if (!strcasecmp(value, "false") || - !strcasecmp(value, "no") || - !strcasecmp(value, "off") || - value[0] == '\0') { - *out = 0; - return 0; - } - - return -1; -} - -size_t git__unescape(char *str) -{ - char *scan, *pos = str; - - if (!str) - return 0; - - for (scan = str; *scan; pos++, scan++) { - if (*scan == '\\' && *(scan + 1) != '\0') - scan++; /* skip '\' but include next char */ - if (pos != scan) - *pos = *scan; - } - - if (pos != scan) { - *pos = '\0'; - } - - return (pos - str); -} - -#if defined(GIT_QSORT_S) || defined(GIT_QSORT_R_BSD) -typedef struct { - git__sort_r_cmp cmp; - void *payload; -} git__qsort_r_glue; - -static int GIT_LIBGIT2_CALL git__qsort_r_glue_cmp( - void *payload, const void *a, const void *b) -{ - git__qsort_r_glue *glue = payload; - return glue->cmp(a, b, glue->payload); -} -#endif - - -#if !defined(GIT_QSORT_R_BSD) && \ - !defined(GIT_QSORT_R_GNU) && \ - !defined(GIT_QSORT_S) -static void swap(uint8_t *a, uint8_t *b, size_t elsize) -{ - char tmp[256]; - - while (elsize) { - size_t n = elsize < sizeof(tmp) ? elsize : sizeof(tmp); - memcpy(tmp, a + elsize - n, n); - memcpy(a + elsize - n, b + elsize - n, n); - memcpy(b + elsize - n, tmp, n); - elsize -= n; - } -} - -static void insertsort( - void *els, size_t nel, size_t elsize, - git__sort_r_cmp cmp, void *payload) -{ - uint8_t *base = els; - uint8_t *end = base + nel * elsize; - uint8_t *i, *j; - - for (i = base + elsize; i < end; i += elsize) - for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize) - swap(j, j - elsize, elsize); -} -#endif - -void git__qsort_r( - void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload) -{ -#if defined(GIT_QSORT_R_BSD) - git__qsort_r_glue glue = { cmp, payload }; - qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp); -#elif defined(GIT_QSORT_R_GNU) - qsort_r(els, nel, elsize, cmp, payload); -#elif defined(GIT_QSORT_S) - git__qsort_r_glue glue = { cmp, payload }; - qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue); -#else - insertsort(els, nel, elsize, cmp, payload); -#endif -} - -#ifdef GIT_WIN32 -int git__getenv(git_str *out, const char *name) -{ - wchar_t *wide_name = NULL, *wide_value = NULL; - DWORD value_len; - int error = -1; - - git_str_clear(out); - - if (git__utf8_to_16_alloc(&wide_name, name) < 0) - return -1; - - if ((value_len = GetEnvironmentVariableW(wide_name, NULL, 0)) > 0) { - wide_value = git__malloc(value_len * sizeof(wchar_t)); - GIT_ERROR_CHECK_ALLOC(wide_value); - - value_len = GetEnvironmentVariableW(wide_name, wide_value, value_len); - } - - if (value_len) - error = git_str_put_w(out, wide_value, value_len); - else if (GetLastError() == ERROR_SUCCESS || GetLastError() == ERROR_ENVVAR_NOT_FOUND) - error = GIT_ENOTFOUND; - else - git_error_set(GIT_ERROR_OS, "could not read environment variable '%s'", name); - - git__free(wide_name); - git__free(wide_value); - return error; -} -#else -int git__getenv(git_str *out, const char *name) -{ - const char *val = getenv(name); - - git_str_clear(out); - - if (!val) - return GIT_ENOTFOUND; - - return git_str_puts(out, val); -} -#endif - -/* - * By doing this in two steps we can at least get - * the function to be somewhat coherent, even - * with this disgusting nest of #ifdefs. - */ -#ifndef _SC_NPROCESSORS_ONLN -# ifdef _SC_NPROC_ONLN -# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN -# elif defined _SC_CRAY_NCPU -# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU -# endif -#endif - -int git__online_cpus(void) -{ -#ifdef _SC_NPROCESSORS_ONLN - long ncpus; -#endif - -#ifdef _WIN32 - SYSTEM_INFO info; - GetSystemInfo(&info); - - if ((int)info.dwNumberOfProcessors > 0) - return (int)info.dwNumberOfProcessors; -#elif defined(hpux) || defined(__hpux) || defined(_hpux) - struct pst_dynamic psd; - - if (!pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0)) - return (int)psd.psd_proc_cnt; -#endif - -#ifdef _SC_NPROCESSORS_ONLN - if ((ncpus = (long)sysconf(_SC_NPROCESSORS_ONLN)) > 0) - return (int)ncpus; -#endif - - return 1; -} diff --git a/src/util.h b/src/util.h deleted file mode 100644 index 141779ade..000000000 --- a/src/util.h +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_util_h__ -#define INCLUDE_util_h__ - -#ifndef GIT_WIN32 -# include -#endif - -#include "str.h" -#include "common.h" -#include "strnlen.h" -#include "thread.h" - -#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) -#define bitsizeof(x) (CHAR_BIT * sizeof(x)) -#define MSB(x, bits) ((x) & (~UINT64_C(0) << (bitsizeof(x) - (bits)))) -#ifndef min -# define min(a,b) ((a) < (b) ? (a) : (b)) -#endif -#ifndef max -# define max(a,b) ((a) > (b) ? (a) : (b)) -#endif - -#if defined(__GNUC__) -# define GIT_CONTAINER_OF(ptr, type, member) \ - __builtin_choose_expr( \ - __builtin_offsetof(type, member) == 0 && \ - __builtin_types_compatible_p(__typeof__(&((type *) 0)->member), __typeof__(ptr)), \ - ((type *) (ptr)), \ - (void)0) -#else -# define GIT_CONTAINER_OF(ptr, type, member) (type *)(ptr) -#endif - -/** - * Return the length of a constant string. - * We are aware that `strlen` performs the same task and is usually - * optimized away by the compiler, whilst being safer because it returns - * valid values when passed a pointer instead of a constant string; however - * this macro will transparently work with wide-char and single-char strings. - */ -#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1) - -#define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \ - ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2))) - -#define CASESELECT(IGNORE_CASE, ICASE, CASE) \ - ((IGNORE_CASE) ? (ICASE) : (CASE)) - -extern int git__prefixcmp(const char *str, const char *prefix); -extern int git__prefixcmp_icase(const char *str, const char *prefix); -extern int git__prefixncmp(const char *str, size_t str_n, const char *prefix); -extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix); -extern int git__suffixcmp(const char *str, const char *suffix); - -GIT_INLINE(int) git__signum(int val) -{ - return ((val > 0) - (val < 0)); -} - -extern int git__strntol32(int32_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); -extern int git__strntol64(int64_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); - - -extern void git__hexdump(const char *buffer, size_t n); -extern uint32_t git__hash(const void *key, int len, uint32_t seed); - -/* 32-bit cross-platform rotl */ -#ifdef _MSC_VER /* use built-in method in MSVC */ -# define git__rotl(v, s) (uint32_t)_rotl(v, s) -#else /* use bitops in GCC; with o2 this gets optimized to a rotl instruction */ -# define git__rotl(v, s) (uint32_t)(((uint32_t)(v) << (s)) | ((uint32_t)(v) >> (32 - (s)))) -#endif - -extern char *git__strtok(char **end, const char *sep); -extern char *git__strsep(char **end, const char *sep); - -extern void git__strntolower(char *str, size_t len); -extern void git__strtolower(char *str); - -#ifdef GIT_WIN32 -GIT_INLINE(int) git__tolower(int c) -{ - return (c >= 'A' && c <= 'Z') ? (c + 32) : c; -} -#else -# define git__tolower(a) tolower(a) -#endif - -extern size_t git__linenlen(const char *buffer, size_t buffer_len); - -GIT_INLINE(const char *) git__next_line(const char *s) -{ - while (*s && *s != '\n') s++; - while (*s == '\n' || *s == '\r') s++; - return s; -} - -GIT_INLINE(const void *) git__memrchr(const void *s, int c, size_t n) -{ - const unsigned char *cp; - - if (n != 0) { - cp = (unsigned char *)s + n; - do { - if (*(--cp) == (unsigned char)c) - return cp; - } while (--n != 0); - } - - return NULL; -} - -extern const void * git__memmem(const void *haystack, size_t haystacklen, - const void *needle, size_t needlelen); - -typedef int (*git__tsort_cmp)(const void *a, const void *b); - -extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp); - -typedef int (*git__sort_r_cmp)(const void *a, const void *b, void *payload); - -extern void git__tsort_r( - void **dst, size_t size, git__sort_r_cmp cmp, void *payload); - -extern void git__qsort_r( - void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload); - -/** - * @param position If non-NULL, this will be set to the position where the - * element is or would be inserted if not found. - * @return 0 if found; GIT_ENOTFOUND if not found - */ -extern int git__bsearch( - void **array, - size_t array_len, - const void *key, - int (*compare)(const void *key, const void *element), - size_t *position); - -extern int git__bsearch_r( - void **array, - size_t array_len, - const void *key, - int (*compare_r)(const void *key, const void *element, void *payload), - void *payload, - size_t *position); - -#define git__strcmp strcmp -#define git__strncmp strncmp - -extern int git__strcmp_cb(const void *a, const void *b); -extern int git__strcasecmp_cb(const void *a, const void *b); - -extern int git__strcasecmp(const char *a, const char *b); -extern int git__strncasecmp(const char *a, const char *b, size_t sz); - -extern int git__strcasesort_cmp(const char *a, const char *b); - -/* - * Compare some NUL-terminated `a` to a possibly non-NUL terminated - * `b` of length `b_len`; like `strncmp` but ensuring that - * `strlen(a) == b_len` as well. - */ -GIT_INLINE(int) git__strlcmp(const char *a, const char *b, size_t b_len) -{ - int cmp = strncmp(a, b, b_len); - return cmp ? cmp : (int)a[b_len]; -} - -typedef struct { - git_atomic32 refcount; - void *owner; -} git_refcount; - -typedef void (*git_refcount_freeptr)(void *r); - -#define GIT_REFCOUNT_INC(r) { \ - git_atomic32_inc(&(r)->rc.refcount); \ -} - -#define GIT_REFCOUNT_DEC(_r, do_free) { \ - git_refcount *r = &(_r)->rc; \ - int val = git_atomic32_dec(&r->refcount); \ - if (val <= 0 && r->owner == NULL) { do_free(_r); } \ -} - -#define GIT_REFCOUNT_OWN(r, o) { \ - (void)git_atomic_swap((r)->rc.owner, o); \ -} - -#define GIT_REFCOUNT_OWNER(r) git_atomic_load((r)->rc.owner) - -#define GIT_REFCOUNT_VAL(r) git_atomic32_get((r)->rc.refcount) - - -static signed char from_hex[] = { --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */ --1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */ --1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */ -}; - -GIT_INLINE(int) git__fromhex(char h) -{ - return from_hex[(unsigned char) h]; -} - -GIT_INLINE(int) git__ishex(const char *str) -{ - unsigned i; - for (i=0; str[i] != '\0'; i++) - if (git__fromhex(str[i]) < 0) - return 0; - return 1; -} - -GIT_INLINE(size_t) git__size_t_bitmask(size_t v) -{ - v--; - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - - return v; -} - -GIT_INLINE(size_t) git__size_t_powerof2(size_t v) -{ - return git__size_t_bitmask(v) + 1; -} - -GIT_INLINE(bool) git__isupper(int c) -{ - return (c >= 'A' && c <= 'Z'); -} - -GIT_INLINE(bool) git__isalpha(int c) -{ - return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); -} - -GIT_INLINE(bool) git__isdigit(int c) -{ - return (c >= '0' && c <= '9'); -} - -GIT_INLINE(bool) git__isspace(int c) -{ - return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); -} - -GIT_INLINE(bool) git__isspace_nonlf(int c) -{ - return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v'); -} - -GIT_INLINE(bool) git__iswildcard(int c) -{ - return (c == '*' || c == '?' || c == '['); -} - -GIT_INLINE(bool) git__isxdigit(int c) -{ - return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); -} - -/* - * Parse a string value as a boolean, just like Core Git does. - * - * Valid values for true are: 'true', 'yes', 'on' - * Valid values for false are: 'false', 'no', 'off' - */ -extern int git__parse_bool(int *out, const char *value); - -/* - * Unescapes a string in-place. - * - * Edge cases behavior: - * - "jackie\" -> "jacky\" - * - "chan\\" -> "chan\" - */ -extern size_t git__unescape(char *str); - -/* - * Safely zero-out memory, making sure that the compiler - * doesn't optimize away the operation. - */ -GIT_INLINE(void) git__memzero(void *data, size_t size) -{ -#ifdef _MSC_VER - SecureZeroMemory((PVOID)data, size); -#else - volatile uint8_t *scan = (volatile uint8_t *)data; - - while (size--) - *scan++ = 0x0; -#endif -} - -#ifdef GIT_WIN32 - -GIT_INLINE(double) git__timer(void) -{ - /* GetTickCount64 returns the number of milliseconds that have - * elapsed since the system was started. */ - return (double) GetTickCount64() / (double) 1000; -} - -#elif __APPLE__ - -#include - -GIT_INLINE(double) git__timer(void) -{ - uint64_t time = mach_absolute_time(); - static double scaling_factor = 0; - - if (scaling_factor == 0) { - mach_timebase_info_data_t info; - (void)mach_timebase_info(&info); - scaling_factor = (double)info.numer / (double)info.denom; - } - - return (double)time * scaling_factor / 1.0E9; -} - -#elif defined(__amigaos4__) - -#include - -GIT_INLINE(double) git__timer(void) -{ - struct TimeVal tv; - ITimer->GetUpTime(&tv); - return (double)tv.Seconds + (double)tv.Microseconds / 1.0E6; -} - -#else - -#include - -GIT_INLINE(double) git__timer(void) -{ - struct timeval tv; - -#ifdef CLOCK_MONOTONIC - struct timespec tp; - if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) - return (double) tp.tv_sec + (double) tp.tv_nsec / 1.0E9; -#endif - - /* Fall back to using gettimeofday */ - gettimeofday(&tv, NULL); - return (double)tv.tv_sec + (double)tv.tv_usec / 1.0E6; -} - -#endif - -extern int git__getenv(git_str *out, const char *name); - -extern int git__online_cpus(void); - -GIT_INLINE(int) git__noop(void) { return 0; } - -#include "alloc.h" - -#endif diff --git a/src/varint.c b/src/varint.c deleted file mode 100644 index 9ffc1d744..000000000 --- a/src/varint.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "varint.h" - -uintmax_t git_decode_varint(const unsigned char *bufp, size_t *varint_len) -{ - const unsigned char *buf = bufp; - unsigned char c = *buf++; - uintmax_t val = c & 127; - while (c & 128) { - val += 1; - if (!val || MSB(val, 7)) { - /* This is not a valid varint_len, so it signals - the error */ - *varint_len = 0; - return 0; /* overflow */ - } - c = *buf++; - val = (val << 7) + (c & 127); - } - *varint_len = buf - bufp; - return val; -} - -int git_encode_varint(unsigned char *buf, size_t bufsize, uintmax_t value) -{ - unsigned char varint[16]; - unsigned pos = sizeof(varint) - 1; - varint[pos] = value & 127; - while (value >>= 7) - varint[--pos] = 128 | (--value & 127); - if (buf) { - if (bufsize < (sizeof(varint) - pos)) - return -1; - memcpy(buf, varint + pos, sizeof(varint) - pos); - } - return sizeof(varint) - pos; -} diff --git a/src/varint.h b/src/varint.h deleted file mode 100644 index 652e22486..000000000 --- a/src/varint.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_varint_h__ -#define INCLUDE_varint_h__ - -#include "common.h" - -#include - -extern int git_encode_varint(unsigned char *, size_t, uintmax_t); -extern uintmax_t git_decode_varint(const unsigned char *, size_t *); - -#endif diff --git a/src/vector.c b/src/vector.c deleted file mode 100644 index 4a4bc8c0e..000000000 --- a/src/vector.c +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "vector.h" - -#include "integer.h" - -/* In elements, not bytes */ -#define MIN_ALLOCSIZE 8 - -GIT_INLINE(size_t) compute_new_size(git_vector *v) -{ - size_t new_size = v->_alloc_size; - - /* Use a resize factor of 1.5, which is quick to compute using integer - * instructions and less than the golden ratio (1.618...) */ - if (new_size < MIN_ALLOCSIZE) - new_size = MIN_ALLOCSIZE; - else if (new_size <= (SIZE_MAX / 3) * 2) - new_size += new_size / 2; - else - new_size = SIZE_MAX; - - return new_size; -} - -GIT_INLINE(int) resize_vector(git_vector *v, size_t new_size) -{ - void *new_contents; - - if (new_size == 0) - return 0; - - new_contents = git__reallocarray(v->contents, new_size, sizeof(void *)); - GIT_ERROR_CHECK_ALLOC(new_contents); - - v->_alloc_size = new_size; - v->contents = new_contents; - - return 0; -} - -int git_vector_size_hint(git_vector *v, size_t size_hint) -{ - if (v->_alloc_size >= size_hint) - return 0; - return resize_vector(v, size_hint); -} - -int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp) -{ - GIT_ASSERT_ARG(v); - GIT_ASSERT_ARG(src); - - v->_alloc_size = 0; - v->contents = NULL; - v->_cmp = cmp ? cmp : src->_cmp; - v->length = src->length; - v->flags = src->flags; - if (cmp != src->_cmp) - git_vector_set_sorted(v, 0); - - if (src->length) { - size_t bytes; - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&bytes, src->length, sizeof(void *)); - v->contents = git__malloc(bytes); - GIT_ERROR_CHECK_ALLOC(v->contents); - v->_alloc_size = src->length; - memcpy(v->contents, src->contents, bytes); - } - - return 0; -} - -void git_vector_free(git_vector *v) -{ - if (!v) - return; - - git__free(v->contents); - v->contents = NULL; - - v->length = 0; - v->_alloc_size = 0; -} - -void git_vector_free_deep(git_vector *v) -{ - size_t i; - - if (!v) - return; - - for (i = 0; i < v->length; ++i) { - git__free(v->contents[i]); - v->contents[i] = NULL; - } - - git_vector_free(v); -} - -int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp) -{ - GIT_ASSERT_ARG(v); - - v->_alloc_size = 0; - v->_cmp = cmp; - v->length = 0; - v->flags = GIT_VECTOR_SORTED; - v->contents = NULL; - - return resize_vector(v, max(initial_size, MIN_ALLOCSIZE)); -} - -void **git_vector_detach(size_t *size, size_t *asize, git_vector *v) -{ - void **data = v->contents; - - if (size) - *size = v->length; - if (asize) - *asize = v->_alloc_size; - - v->_alloc_size = 0; - v->length = 0; - v->contents = NULL; - - return data; -} - -int git_vector_insert(git_vector *v, void *element) -{ - GIT_ASSERT_ARG(v); - - if (v->length >= v->_alloc_size && - resize_vector(v, compute_new_size(v)) < 0) - return -1; - - v->contents[v->length++] = element; - - git_vector_set_sorted(v, v->length <= 1); - - return 0; -} - -int git_vector_insert_sorted( - git_vector *v, void *element, int (*on_dup)(void **old, void *new)) -{ - int result; - size_t pos; - - GIT_ASSERT_ARG(v); - GIT_ASSERT(v->_cmp); - - if (!git_vector_is_sorted(v)) - git_vector_sort(v); - - if (v->length >= v->_alloc_size && - resize_vector(v, compute_new_size(v)) < 0) - return -1; - - /* If we find the element and have a duplicate handler callback, - * invoke it. If it returns non-zero, then cancel insert, otherwise - * proceed with normal insert. - */ - if (!git__bsearch(v->contents, v->length, element, v->_cmp, &pos) && - on_dup && (result = on_dup(&v->contents[pos], element)) < 0) - return result; - - /* shift elements to the right */ - if (pos < v->length) - memmove(v->contents + pos + 1, v->contents + pos, - (v->length - pos) * sizeof(void *)); - - v->contents[pos] = element; - v->length++; - - return 0; -} - -void git_vector_sort(git_vector *v) -{ - if (git_vector_is_sorted(v) || !v->_cmp) - return; - - if (v->length > 1) - git__tsort(v->contents, v->length, v->_cmp); - git_vector_set_sorted(v, 1); -} - -int git_vector_bsearch2( - size_t *at_pos, - git_vector *v, - git_vector_cmp key_lookup, - const void *key) -{ - GIT_ASSERT_ARG(v); - GIT_ASSERT_ARG(key); - GIT_ASSERT(key_lookup); - - /* need comparison function to sort the vector */ - if (!v->_cmp) - return -1; - - git_vector_sort(v); - - return git__bsearch(v->contents, v->length, key, key_lookup, at_pos); -} - -int git_vector_search2( - size_t *at_pos, const git_vector *v, git_vector_cmp key_lookup, const void *key) -{ - size_t i; - - GIT_ASSERT_ARG(v); - GIT_ASSERT_ARG(key); - GIT_ASSERT(key_lookup); - - for (i = 0; i < v->length; ++i) { - if (key_lookup(key, v->contents[i]) == 0) { - if (at_pos) - *at_pos = i; - - return 0; - } - } - - return GIT_ENOTFOUND; -} - -static int strict_comparison(const void *a, const void *b) -{ - return (a == b) ? 0 : -1; -} - -int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry) -{ - return git_vector_search2(at_pos, v, v->_cmp ? v->_cmp : strict_comparison, entry); -} - -int git_vector_remove(git_vector *v, size_t idx) -{ - size_t shift_count; - - GIT_ASSERT_ARG(v); - - if (idx >= v->length) - return GIT_ENOTFOUND; - - shift_count = v->length - idx - 1; - - if (shift_count) - memmove(&v->contents[idx], &v->contents[idx + 1], - shift_count * sizeof(void *)); - - v->length--; - return 0; -} - -void git_vector_pop(git_vector *v) -{ - if (v->length > 0) - v->length--; -} - -void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)) -{ - git_vector_cmp cmp; - size_t i, j; - - if (v->length <= 1) - return; - - git_vector_sort(v); - cmp = v->_cmp ? v->_cmp : strict_comparison; - - for (i = 0, j = 1 ; j < v->length; ++j) - if (!cmp(v->contents[i], v->contents[j])) { - if (git_free_cb) - git_free_cb(v->contents[i]); - - v->contents[i] = v->contents[j]; - } else - v->contents[++i] = v->contents[j]; - - v->length -= j - i - 1; -} - -void git_vector_remove_matching( - git_vector *v, - int (*match)(const git_vector *v, size_t idx, void *payload), - void *payload) -{ - size_t i, j; - - for (i = 0, j = 0; j < v->length; ++j) { - v->contents[i] = v->contents[j]; - - if (!match(v, i, payload)) - i++; - } - - v->length = i; -} - -void git_vector_clear(git_vector *v) -{ - v->length = 0; - git_vector_set_sorted(v, 1); -} - -void git_vector_swap(git_vector *a, git_vector *b) -{ - git_vector t; - - if (a != b) { - memcpy(&t, a, sizeof(t)); - memcpy(a, b, sizeof(t)); - memcpy(b, &t, sizeof(t)); - } -} - -int git_vector_resize_to(git_vector *v, size_t new_length) -{ - if (new_length > v->_alloc_size && - resize_vector(v, new_length) < 0) - return -1; - - if (new_length > v->length) - memset(&v->contents[v->length], 0, - sizeof(void *) * (new_length - v->length)); - - v->length = new_length; - - return 0; -} - -int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len) -{ - size_t new_length; - - GIT_ASSERT_ARG(insert_len > 0); - GIT_ASSERT_ARG(idx <= v->length); - - GIT_ERROR_CHECK_ALLOC_ADD(&new_length, v->length, insert_len); - - if (new_length > v->_alloc_size && resize_vector(v, new_length) < 0) - return -1; - - memmove(&v->contents[idx + insert_len], &v->contents[idx], - sizeof(void *) * (v->length - idx)); - memset(&v->contents[idx], 0, sizeof(void *) * insert_len); - - v->length = new_length; - return 0; -} - -int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len) -{ - size_t new_length = v->length - remove_len; - size_t end_idx = 0; - - GIT_ASSERT_ARG(remove_len > 0); - - if (git__add_sizet_overflow(&end_idx, idx, remove_len)) - GIT_ASSERT(0); - - GIT_ASSERT(end_idx <= v->length); - - if (end_idx < v->length) - memmove(&v->contents[idx], &v->contents[end_idx], - sizeof(void *) * (v->length - end_idx)); - - memset(&v->contents[new_length], 0, sizeof(void *) * remove_len); - - v->length = new_length; - return 0; -} - -int git_vector_set(void **old, git_vector *v, size_t position, void *value) -{ - if (position + 1 > v->length) { - if (git_vector_resize_to(v, position + 1) < 0) - return -1; - } - - if (old != NULL) - *old = v->contents[position]; - - v->contents[position] = value; - - return 0; -} - -int git_vector_verify_sorted(const git_vector *v) -{ - size_t i; - - if (!git_vector_is_sorted(v)) - return -1; - - for (i = 1; i < v->length; ++i) { - if (v->_cmp(v->contents[i - 1], v->contents[i]) > 0) - return -1; - } - - return 0; -} - -void git_vector_reverse(git_vector *v) -{ - size_t a, b; - - if (v->length == 0) - return; - - a = 0; - b = v->length - 1; - - while (a < b) { - void *tmp = v->contents[a]; - v->contents[a] = v->contents[b]; - v->contents[b] = tmp; - a++; - b--; - } -} diff --git a/src/vector.h b/src/vector.h deleted file mode 100644 index ae3c79a4c..000000000 --- a/src/vector.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_vector_h__ -#define INCLUDE_vector_h__ - -#include "common.h" - -typedef int (*git_vector_cmp)(const void *, const void *); - -enum { - GIT_VECTOR_SORTED = (1u << 0), - GIT_VECTOR_FLAG_MAX = (1u << 1) -}; - -typedef struct git_vector { - size_t _alloc_size; - git_vector_cmp _cmp; - void **contents; - size_t length; - uint32_t flags; -} git_vector; - -#define GIT_VECTOR_INIT {0} - -GIT_WARN_UNUSED_RESULT int git_vector_init( - git_vector *v, size_t initial_size, git_vector_cmp cmp); -void git_vector_free(git_vector *v); -void git_vector_free_deep(git_vector *v); /* free each entry and self */ -void git_vector_clear(git_vector *v); -GIT_WARN_UNUSED_RESULT int git_vector_dup( - git_vector *v, const git_vector *src, git_vector_cmp cmp); -void git_vector_swap(git_vector *a, git_vector *b); -int git_vector_size_hint(git_vector *v, size_t size_hint); - -void **git_vector_detach(size_t *size, size_t *asize, git_vector *v); - -void git_vector_sort(git_vector *v); - -/** Linear search for matching entry using internal comparison function */ -int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry); - -/** Linear search for matching entry using explicit comparison function */ -int git_vector_search2(size_t *at_pos, const git_vector *v, git_vector_cmp cmp, const void *key); - -/** - * Binary search for matching entry using explicit comparison function that - * returns position where item would go if not found. - */ -int git_vector_bsearch2( - size_t *at_pos, git_vector *v, git_vector_cmp cmp, const void *key); - -/** Binary search for matching entry using internal comparison function */ -GIT_INLINE(int) git_vector_bsearch(size_t *at_pos, git_vector *v, const void *key) -{ - return git_vector_bsearch2(at_pos, v, v->_cmp, key); -} - -GIT_INLINE(void *) git_vector_get(const git_vector *v, size_t position) -{ - return (position < v->length) ? v->contents[position] : NULL; -} - -#define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL) - -GIT_INLINE(size_t) git_vector_length(const git_vector *v) -{ - return v->length; -} - -GIT_INLINE(void *) git_vector_last(const git_vector *v) -{ - return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL; -} - -#define git_vector_foreach(v, iter, elem) \ - for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ ) - -#define git_vector_rforeach(v, iter, elem) \ - for ((iter) = (v)->length - 1; (iter) < SIZE_MAX && ((elem) = (v)->contents[(iter)], 1); (iter)-- ) - -int git_vector_insert(git_vector *v, void *element); -int git_vector_insert_sorted(git_vector *v, void *element, - int (*on_dup)(void **old, void *new)); -int git_vector_remove(git_vector *v, size_t idx); -void git_vector_pop(git_vector *v); -void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)); - -void git_vector_remove_matching( - git_vector *v, - int (*match)(const git_vector *v, size_t idx, void *payload), - void *payload); - -int git_vector_resize_to(git_vector *v, size_t new_length); -int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len); -int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len); - -int git_vector_set(void **old, git_vector *v, size_t position, void *value); - -/** Check if vector is sorted */ -#define git_vector_is_sorted(V) (((V)->flags & GIT_VECTOR_SORTED) != 0) - -/** Directly set sorted state of vector */ -#define git_vector_set_sorted(V,S) do { \ - (V)->flags = (S) ? ((V)->flags | GIT_VECTOR_SORTED) : \ - ((V)->flags & ~GIT_VECTOR_SORTED); } while (0) - -/** Set the comparison function used for sorting the vector */ -GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp) -{ - if (cmp != v->_cmp) { - v->_cmp = cmp; - git_vector_set_sorted(v, 0); - } -} - -/* Just use this in tests, not for realz. returns -1 if not sorted */ -int git_vector_verify_sorted(const git_vector *v); - -/** - * Reverse the vector in-place. - */ -void git_vector_reverse(git_vector *v); - -#endif diff --git a/src/wildmatch.c b/src/wildmatch.c deleted file mode 100644 index a894e4841..000000000 --- a/src/wildmatch.c +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - * - * Do shell-style pattern matching for ?, \, [], and * characters. - * It is 8bit clean. - * - * Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. - * Rich $alz is now . - * - * Modified by Wayne Davison to special-case '/' matching, to make '**' - * work differently than '*', and to fix the character-class code. - * - * Imported from git.git. - */ - -#include "wildmatch.h" - -#define GIT_SPACE 0x01 -#define GIT_DIGIT 0x02 -#define GIT_ALPHA 0x04 -#define GIT_GLOB_SPECIAL 0x08 -#define GIT_REGEX_SPECIAL 0x10 -#define GIT_PATHSPEC_MAGIC 0x20 -#define GIT_CNTRL 0x40 -#define GIT_PUNCT 0x80 - -enum { - S = GIT_SPACE, - A = GIT_ALPHA, - D = GIT_DIGIT, - G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ - R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */ - P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */ - X = GIT_CNTRL, - U = GIT_PUNCT, - Z = GIT_CNTRL | GIT_SPACE -}; - -static const unsigned char sane_ctype[256] = { - X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X, /* 0.. 15 */ - X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 16.. 31 */ - S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */ - D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */ - P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */ - A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P, /* 80.. 95 */ - P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */ - A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X, /* 112..127 */ - /* Nothing in the 128.. range */ -}; - -#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0) -#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL) - -typedef unsigned char uchar; - -/* What character marks an inverted character class? */ -#define NEGATE_CLASS '!' -#define NEGATE_CLASS2 '^' - -#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \ - && *(class) == *(litmatch) \ - && strncmp((char*)class, litmatch, len) == 0) - -#if defined STDC_HEADERS || !defined isascii -# define ISASCII(c) 1 -#else -# define ISASCII(c) isascii(c) -#endif - -#ifdef isblank -# define ISBLANK(c) (ISASCII(c) && isblank(c)) -#else -# define ISBLANK(c) ((c) == ' ' || (c) == '\t') -#endif - -#ifdef isgraph -# define ISGRAPH(c) (ISASCII(c) && isgraph(c)) -#else -# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c)) -#endif - -#define ISPRINT(c) (ISASCII(c) && isprint(c)) -#define ISDIGIT(c) (ISASCII(c) && isdigit(c)) -#define ISALNUM(c) (ISASCII(c) && isalnum(c)) -#define ISALPHA(c) (ISASCII(c) && isalpha(c)) -#define ISCNTRL(c) (ISASCII(c) && iscntrl(c)) -#define ISLOWER(c) (ISASCII(c) && islower(c)) -#define ISPUNCT(c) (ISASCII(c) && ispunct(c)) -#define ISSPACE(c) (ISASCII(c) && isspace(c)) -#define ISUPPER(c) (ISASCII(c) && isupper(c)) -#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c)) - -/* Match pattern "p" against "text" */ -static int dowild(const uchar *p, const uchar *text, unsigned int flags) -{ - uchar p_ch; - const uchar *pattern = p; - - for ( ; (p_ch = *p) != '\0'; text++, p++) { - int matched, match_slash, negated; - uchar t_ch, prev_ch; - if ((t_ch = *text) == '\0' && p_ch != '*') - return WM_ABORT_ALL; - if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) - t_ch = tolower(t_ch); - if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) - p_ch = tolower(p_ch); - switch (p_ch) { - case '\\': - /* Literal match with following character. Note that the test - * in "default" handles the p[1] == '\0' failure case. */ - p_ch = *++p; - /* FALLTHROUGH */ - default: - if (t_ch != p_ch) - return WM_NOMATCH; - continue; - case '?': - /* Match anything but '/'. */ - if ((flags & WM_PATHNAME) && t_ch == '/') - return WM_NOMATCH; - continue; - case '*': - if (*++p == '*') { - const uchar *prev_p = p - 2; - while (*++p == '*') {} - if (!(flags & WM_PATHNAME)) - /* without WM_PATHNAME, '*' == '**' */ - match_slash = 1; - else if ((prev_p < pattern || *prev_p == '/') && - (*p == '\0' || *p == '/' || - (p[0] == '\\' && p[1] == '/'))) { - /* - * Assuming we already match 'foo/' and are at - * , just assume it matches - * nothing and go ahead match the rest of the - * pattern with the remaining string. This - * helps make foo/<*><*>/bar (<> because - * otherwise it breaks C comment syntax) match - * both foo/bar and foo/a/bar. - */ - if (p[0] == '/' && - dowild(p + 1, text, flags) == WM_MATCH) - return WM_MATCH; - match_slash = 1; - } else /* WM_PATHNAME is set */ - match_slash = 0; - } else - /* without WM_PATHNAME, '*' == '**' */ - match_slash = flags & WM_PATHNAME ? 0 : 1; - if (*p == '\0') { - /* Trailing "**" matches everything. Trailing "*" matches - * only if there are no more slash characters. */ - if (!match_slash) { - if (strchr((char*)text, '/') != NULL) - return WM_NOMATCH; - } - return WM_MATCH; - } else if (!match_slash && *p == '/') { - /* - * _one_ asterisk followed by a slash - * with WM_PATHNAME matches the next - * directory - */ - const char *slash = strchr((char*)text, '/'); - if (!slash) - return WM_NOMATCH; - text = (const uchar*)slash; - /* the slash is consumed by the top-level for loop */ - break; - } - while (1) { - if (t_ch == '\0') - break; - /* - * Try to advance faster when an asterisk is - * followed by a literal. We know in this case - * that the string before the literal - * must belong to "*". - * If match_slash is false, do not look past - * the first slash as it cannot belong to '*'. - */ - if (!is_glob_special(*p)) { - p_ch = *p; - if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) - p_ch = tolower(p_ch); - while ((t_ch = *text) != '\0' && - (match_slash || t_ch != '/')) { - if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) - t_ch = tolower(t_ch); - if (t_ch == p_ch) - break; - text++; - } - if (t_ch != p_ch) - return WM_NOMATCH; - } - if ((matched = dowild(p, text, flags)) != WM_NOMATCH) { - if (!match_slash || matched != WM_ABORT_TO_STARSTAR) - return matched; - } else if (!match_slash && t_ch == '/') - return WM_ABORT_TO_STARSTAR; - t_ch = *++text; - } - return WM_ABORT_ALL; - case '[': - p_ch = *++p; -#ifdef NEGATE_CLASS2 - if (p_ch == NEGATE_CLASS2) - p_ch = NEGATE_CLASS; -#endif - /* Assign literal 1/0 because of "matched" comparison. */ - negated = p_ch == NEGATE_CLASS ? 1 : 0; - if (negated) { - /* Inverted character class. */ - p_ch = *++p; - } - prev_ch = 0; - matched = 0; - do { - if (!p_ch) - return WM_ABORT_ALL; - if (p_ch == '\\') { - p_ch = *++p; - if (!p_ch) - return WM_ABORT_ALL; - if (t_ch == p_ch) - matched = 1; - } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') { - p_ch = *++p; - if (p_ch == '\\') { - p_ch = *++p; - if (!p_ch) - return WM_ABORT_ALL; - } - if (t_ch <= p_ch && t_ch >= prev_ch) - matched = 1; - else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) { - uchar t_ch_upper = toupper(t_ch); - if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch) - matched = 1; - } - p_ch = 0; /* This makes "prev_ch" get set to 0. */ - } else if (p_ch == '[' && p[1] == ':') { - const uchar *s; - int i; - for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/ - if (!p_ch) - return WM_ABORT_ALL; - i = (int)(p - s - 1); - if (i < 0 || p[-1] != ':') { - /* Didn't find ":]", so treat like a normal set. */ - p = s - 2; - p_ch = '['; - if (t_ch == p_ch) - matched = 1; - continue; - } - if (CC_EQ(s,i, "alnum")) { - if (ISALNUM(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "alpha")) { - if (ISALPHA(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "blank")) { - if (ISBLANK(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "cntrl")) { - if (ISCNTRL(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "digit")) { - if (ISDIGIT(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "graph")) { - if (ISGRAPH(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "lower")) { - if (ISLOWER(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "print")) { - if (ISPRINT(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "punct")) { - if (ISPUNCT(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "space")) { - if (ISSPACE(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "upper")) { - if (ISUPPER(t_ch)) - matched = 1; - else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "xdigit")) { - if (ISXDIGIT(t_ch)) - matched = 1; - } else /* malformed [:class:] string */ - return WM_ABORT_ALL; - p_ch = 0; /* This makes "prev_ch" get set to 0. */ - } else if (t_ch == p_ch) - matched = 1; - } while (prev_ch = p_ch, (p_ch = *++p) != ']'); - if (matched == negated || - ((flags & WM_PATHNAME) && t_ch == '/')) - return WM_NOMATCH; - continue; - } - } - - return *text ? WM_NOMATCH : WM_MATCH; -} - -/* Match the "pattern" against the "text" string. */ -int wildmatch(const char *pattern, const char *text, unsigned int flags) -{ - return dowild((const uchar*)pattern, (const uchar*)text, flags); -} diff --git a/src/wildmatch.h b/src/wildmatch.h deleted file mode 100644 index 44bb575a6..000000000 --- a/src/wildmatch.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_wildmatch_h__ -#define INCLUDE_wildmatch_h__ - -#include "common.h" - -#define WM_CASEFOLD 1 -#define WM_PATHNAME 2 - -#define WM_NOMATCH 1 -#define WM_MATCH 0 -#define WM_ABORT_ALL -1 -#define WM_ABORT_TO_STARSTAR -2 - -int wildmatch(const char *pattern, const char *text, unsigned int flags); - -#endif diff --git a/src/win32/dir.c b/src/win32/dir.c deleted file mode 100644 index 44052caf0..000000000 --- a/src/win32/dir.c +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "dir.h" - -#define GIT__WIN32_NO_WRAP_DIR -#include "posix.h" - -git__DIR *git__opendir(const char *dir) -{ - git_win32_path filter_w; - git__DIR *new = NULL; - size_t dirlen, alloclen; - - if (!dir || !git_win32__findfirstfile_filter(filter_w, dir)) - return NULL; - - dirlen = strlen(dir); - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, sizeof(*new), dirlen) || - GIT_ADD_SIZET_OVERFLOW(&alloclen, alloclen, 1) || - !(new = git__calloc(1, alloclen))) - return NULL; - - memcpy(new->dir, dir, dirlen); - - new->h = FindFirstFileW(filter_w, &new->f); - - if (new->h == INVALID_HANDLE_VALUE) { - git_error_set(GIT_ERROR_OS, "could not open directory '%s'", dir); - git__free(new); - return NULL; - } - - new->first = 1; - return new; -} - -int git__readdir_ext( - git__DIR *d, - struct git__dirent *entry, - struct git__dirent **result, - int *is_dir) -{ - if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE) - return -1; - - *result = NULL; - - if (d->first) - d->first = 0; - else if (!FindNextFileW(d->h, &d->f)) { - if (GetLastError() == ERROR_NO_MORE_FILES) - return 0; - git_error_set(GIT_ERROR_OS, "could not read from directory '%s'", d->dir); - return -1; - } - - /* Convert the path to UTF-8 */ - if (git_win32_path_to_utf8(entry->d_name, d->f.cFileName) < 0) - return -1; - - entry->d_ino = 0; - - *result = entry; - - if (is_dir != NULL) - *is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); - - return 0; -} - -struct git__dirent *git__readdir(git__DIR *d) -{ - struct git__dirent *result; - if (git__readdir_ext(d, &d->entry, &result, NULL) < 0) - return NULL; - return result; -} - -void git__rewinddir(git__DIR *d) -{ - git_win32_path filter_w; - - if (!d) - return; - - if (d->h != INVALID_HANDLE_VALUE) { - FindClose(d->h); - d->h = INVALID_HANDLE_VALUE; - d->first = 0; - } - - if (!git_win32__findfirstfile_filter(filter_w, d->dir)) - return; - - d->h = FindFirstFileW(filter_w, &d->f); - - if (d->h == INVALID_HANDLE_VALUE) - git_error_set(GIT_ERROR_OS, "could not open directory '%s'", d->dir); - else - d->first = 1; -} - -int git__closedir(git__DIR *d) -{ - if (!d) - return 0; - - if (d->h != INVALID_HANDLE_VALUE) { - FindClose(d->h); - d->h = INVALID_HANDLE_VALUE; - } - - git__free(d); - return 0; -} - diff --git a/src/win32/dir.h b/src/win32/dir.h deleted file mode 100644 index acd64729e..000000000 --- a/src/win32/dir.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_dir_h__ -#define INCLUDE_win32_dir_h__ - -#include "common.h" - -#include "w32_util.h" - -struct git__dirent { - int d_ino; - git_win32_utf8_path d_name; -}; - -typedef struct { - HANDLE h; - WIN32_FIND_DATAW f; - struct git__dirent entry; - int first; - char dir[GIT_FLEX_ARRAY]; -} git__DIR; - -extern git__DIR *git__opendir(const char *); -extern struct git__dirent *git__readdir(git__DIR *); -extern int git__readdir_ext( - git__DIR *, struct git__dirent *, struct git__dirent **, int *); -extern void git__rewinddir(git__DIR *); -extern int git__closedir(git__DIR *); - -# ifndef GIT__WIN32_NO_WRAP_DIR -# define dirent git__dirent -# define DIR git__DIR -# define opendir git__opendir -# define readdir git__readdir -# define readdir_r(d,e,r) git__readdir_ext((d),(e),(r),NULL) -# define rewinddir git__rewinddir -# define closedir git__closedir -# endif - -#endif diff --git a/src/win32/error.c b/src/win32/error.c deleted file mode 100644 index 3a52fb5a9..000000000 --- a/src/win32/error.c +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "error.h" - -#include "utf-conv.h" - -#ifdef GIT_WINHTTP -# include -#endif - -char *git_win32_get_error_message(DWORD error_code) -{ - LPWSTR lpMsgBuf = NULL; - HMODULE hModule = NULL; - char *utf8_msg = NULL; - DWORD dwFlags = - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS; - - if (!error_code) - return NULL; - -#ifdef GIT_WINHTTP - /* Errors raised by WinHTTP are not in the system resource table */ - if (error_code >= WINHTTP_ERROR_BASE && - error_code <= WINHTTP_ERROR_LAST) - hModule = GetModuleHandleW(L"winhttp"); -#endif - - GIT_UNUSED(hModule); - - if (hModule) - dwFlags |= FORMAT_MESSAGE_FROM_HMODULE; - else - dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM; - - if (FormatMessageW(dwFlags, hModule, error_code, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPWSTR)&lpMsgBuf, 0, NULL)) { - /* Convert the message to UTF-8. If this fails, we will - * return NULL, which is a condition expected by the caller */ - if (git__utf16_to_8_alloc(&utf8_msg, lpMsgBuf) < 0) - utf8_msg = NULL; - - LocalFree(lpMsgBuf); - } - - return utf8_msg; -} diff --git a/src/win32/error.h b/src/win32/error.h deleted file mode 100644 index 9e81141ce..000000000 --- a/src/win32/error.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_win32_error_h__ -#define INCLUDE_win32_error_h__ - -#include "common.h" - -extern char *git_win32_get_error_message(DWORD error_code); - -#endif diff --git a/src/win32/findfile.c b/src/win32/findfile.c deleted file mode 100644 index 725a90167..000000000 --- a/src/win32/findfile.c +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "findfile.h" - -#include "path_w32.h" -#include "utf-conv.h" -#include "fs_path.h" - -#define REG_GITFORWINDOWS_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" -#define REG_GITFORWINDOWS_KEY_WOW64 L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" - -static int git_win32__expand_path(git_win32_path dest, const wchar_t *src) -{ - DWORD len = ExpandEnvironmentStringsW(src, dest, GIT_WIN_PATH_UTF16); - - if (!len || len > GIT_WIN_PATH_UTF16) - return -1; - - return 0; -} - -static int win32_path_to_8(git_str *dest, const wchar_t *src) -{ - git_win32_utf8_path utf8_path; - - if (git_win32_path_to_utf8(utf8_path, src) < 0) { - git_error_set(GIT_ERROR_OS, "unable to convert path to UTF-8"); - return -1; - } - - /* Convert backslashes to forward slashes */ - git_fs_path_mkposix(utf8_path); - - return git_str_sets(dest, utf8_path); -} - -static git_win32_path mock_registry; -static bool mock_registry_set; - -extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir) -{ - if (!mock_sysdir) { - mock_registry[0] = L'\0'; - mock_registry_set = false; - } else { - size_t len = wcslen(mock_sysdir); - - if (len > GIT_WIN_PATH_MAX) { - git_error_set(GIT_ERROR_INVALID, "mock path too long"); - return -1; - } - - wcscpy(mock_registry, mock_sysdir); - mock_registry_set = true; - } - - return 0; -} - -static int lookup_registry_key( - git_win32_path out, - const HKEY hive, - const wchar_t* key, - const wchar_t *value) -{ - HKEY hkey; - DWORD type, size; - int error = GIT_ENOTFOUND; - - /* - * Registry data may not be NUL terminated, provide room to do - * it ourselves. - */ - size = (DWORD)((sizeof(git_win32_path) - 1) * sizeof(wchar_t)); - - if (RegOpenKeyExW(hive, key, 0, KEY_READ, &hkey) != 0) - return GIT_ENOTFOUND; - - if (RegQueryValueExW(hkey, value, NULL, &type, (LPBYTE)out, &size) == 0 && - type == REG_SZ && - size > 0 && - size < sizeof(git_win32_path)) { - size_t wsize = size / sizeof(wchar_t); - size_t len = wsize - 1; - - if (out[wsize - 1] != L'\0') { - len = wsize; - out[wsize] = L'\0'; - } - - if (out[len - 1] == L'\\') - out[len - 1] = L'\0'; - - if (_waccess(out, F_OK) == 0) - error = 0; - } - - RegCloseKey(hkey); - return error; -} - -static int find_sysdir_in_registry(git_win32_path out) -{ - if (mock_registry_set) { - if (mock_registry[0] == L'\0') - return GIT_ENOTFOUND; - - wcscpy(out, mock_registry); - return 0; - } - - if (lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || - lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0 || - lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || - lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0) - return 0; - - return GIT_ENOTFOUND; -} - -static int find_sysdir_in_path(git_win32_path out) -{ - size_t out_len; - - if (git_win32_path_find_executable(out, L"git.exe") < 0 && - git_win32_path_find_executable(out, L"git.cmd") < 0) - return GIT_ENOTFOUND; - - out_len = wcslen(out); - - /* Trim the file name */ - if (out_len <= CONST_STRLEN(L"git.exe")) - return GIT_ENOTFOUND; - - out_len -= CONST_STRLEN(L"git.exe"); - - if (out_len && out[out_len - 1] == L'\\') - out_len--; - - /* - * Git for Windows usually places the command in a 'bin' or - * 'cmd' directory, trim that. - */ - if (out_len >= CONST_STRLEN(L"\\bin") && - wcsncmp(&out[out_len - CONST_STRLEN(L"\\bin")], L"\\bin", CONST_STRLEN(L"\\bin")) == 0) - out_len -= CONST_STRLEN(L"\\bin"); - else if (out_len >= CONST_STRLEN(L"\\cmd") && - wcsncmp(&out[out_len - CONST_STRLEN(L"\\cmd")], L"\\cmd", CONST_STRLEN(L"\\cmd")) == 0) - out_len -= CONST_STRLEN(L"\\cmd"); - - if (!out_len) - return GIT_ENOTFOUND; - - out[out_len] = L'\0'; - return 0; -} - -static int win32_find_existing_dirs( - git_str* out, - const wchar_t* tmpl[]) -{ - git_win32_path path16; - git_str buf = GIT_STR_INIT; - - git_str_clear(out); - - for (; *tmpl != NULL; tmpl++) { - if (!git_win32__expand_path(path16, *tmpl) && - path16[0] != L'%' && - !_waccess(path16, F_OK)) { - win32_path_to_8(&buf, path16); - - if (buf.size) - git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); - } - } - - git_str_dispose(&buf); - - return (git_str_oom(out) ? -1 : 0); -} - -static int append_subdir(git_str *out, git_str *path, const char *subdir) -{ - static const char* architecture_roots[] = { - "", - "mingw64", - "mingw32", - NULL - }; - const char **root; - size_t orig_path_len = path->size; - - for (root = architecture_roots; *root; root++) { - if ((*root[0] && git_str_joinpath(path, path->ptr, *root) < 0) || - git_str_joinpath(path, path->ptr, subdir) < 0) - return -1; - - if (git_fs_path_exists(path->ptr) && - git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, path->ptr) < 0) - return -1; - - git_str_truncate(path, orig_path_len); - } - - return 0; -} - -int git_win32__find_system_dirs(git_str *out, const char *subdir) -{ - git_win32_path pathdir, regdir; - git_str path8 = GIT_STR_INIT; - bool has_pathdir, has_regdir; - int error; - - has_pathdir = (find_sysdir_in_path(pathdir) == 0); - has_regdir = (find_sysdir_in_registry(regdir) == 0); - - if (!has_pathdir && !has_regdir) - return 0; - - /* - * Usually the git in the path is the same git in the registry, - * in this case there's no need to duplicate the paths. - */ - if (has_pathdir && has_regdir && wcscmp(pathdir, regdir) == 0) - has_regdir = false; - - if (has_pathdir) { - if ((error = win32_path_to_8(&path8, pathdir)) < 0 || - (error = append_subdir(out, &path8, subdir)) < 0) - goto done; - } - - if (has_regdir) { - if ((error = win32_path_to_8(&path8, regdir)) < 0 || - (error = append_subdir(out, &path8, subdir)) < 0) - goto done; - } - -done: - git_str_dispose(&path8); - return error; -} - -int git_win32__find_global_dirs(git_str *out) -{ - static const wchar_t *global_tmpls[4] = { - L"%HOME%\\", - L"%HOMEDRIVE%%HOMEPATH%\\", - L"%USERPROFILE%\\", - NULL, - }; - - return win32_find_existing_dirs(out, global_tmpls); -} - -int git_win32__find_xdg_dirs(git_str *out) -{ - static const wchar_t *global_tmpls[7] = { - L"%XDG_CONFIG_HOME%\\git", - L"%APPDATA%\\git", - L"%LOCALAPPDATA%\\git", - L"%HOME%\\.config\\git", - L"%HOMEDRIVE%%HOMEPATH%\\.config\\git", - L"%USERPROFILE%\\.config\\git", - NULL, - }; - - return win32_find_existing_dirs(out, global_tmpls); -} - -int git_win32__find_programdata_dirs(git_str *out) -{ - static const wchar_t *programdata_tmpls[2] = { - L"%PROGRAMDATA%\\Git", - NULL, - }; - - return win32_find_existing_dirs(out, programdata_tmpls); -} diff --git a/src/win32/findfile.h b/src/win32/findfile.h deleted file mode 100644 index 61fb7dbad..000000000 --- a/src/win32/findfile.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_win32_findfile_h__ -#define INCLUDE_win32_findfile_h__ - -#include "common.h" - -/** Sets the mock registry root for Git for Windows for testing. */ -extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir); - -extern int git_win32__find_system_dirs(git_str *out, const char *subpath); -extern int git_win32__find_global_dirs(git_str *out); -extern int git_win32__find_xdg_dirs(git_str *out); -extern int git_win32__find_programdata_dirs(git_str *out); - -#endif - diff --git a/src/win32/git2.rc b/src/win32/git2.rc deleted file mode 100644 index d273afd70..000000000 --- a/src/win32/git2.rc +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include "../../include/git2/version.h" - -#ifndef LIBGIT2_FILENAME -# ifdef __GNUC__ -# define LIBGIT2_FILENAME git2 -# else -# define LIBGIT2_FILENAME "git2" -# endif -#endif - -#ifndef LIBGIT2_COMMENTS -# define LIBGIT2_COMMENTS "For more information visit http://libgit2.github.com/" -#endif - -#ifdef __GNUC__ -# define _STR(x) #x -# define STR(x) _STR(x) -#else -# define STR(x) x -#endif - -#ifdef __GNUC__ -VS_VERSION_INFO VERSIONINFO -#else -VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE -#endif - FILEVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH - PRODUCTVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0 -#endif - FILEOS VOS_NT_WINDOWS32 - FILETYPE VFT_DLL - FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904E4" - //language ID = U.S. English, char set = Windows, Multilingual - BEGIN - VALUE "FileDescription", "libgit2 - the Git linkable library\0" - VALUE "FileVersion", LIBGIT2_VERSION "\0" - VALUE "InternalName", STR(LIBGIT2_FILENAME) ".dll\0" - VALUE "LegalCopyright", "Copyright (C) the libgit2 contributors. All rights reserved.\0" - VALUE "OriginalFilename", STR(LIBGIT2_FILENAME) ".dll\0" - VALUE "ProductName", "libgit2\0" - VALUE "ProductVersion", LIBGIT2_VERSION "\0" - VALUE "Comments", LIBGIT2_COMMENTS "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x0409, 1252 - END -END diff --git a/src/win32/map.c b/src/win32/map.c deleted file mode 100644 index 2aabc9b15..000000000 --- a/src/win32/map.c +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "map.h" -#include - -#ifndef NO_MMAP - -static DWORD get_page_size(void) -{ - static DWORD page_size; - SYSTEM_INFO sys; - - if (!page_size) { - GetSystemInfo(&sys); - page_size = sys.dwPageSize; - } - - return page_size; -} - -static DWORD get_allocation_granularity(void) -{ - static DWORD granularity; - SYSTEM_INFO sys; - - if (!granularity) { - GetSystemInfo(&sys); - granularity = sys.dwAllocationGranularity; - } - - return granularity; -} - -int git__page_size(size_t *page_size) -{ - *page_size = get_page_size(); - return 0; -} - -int git__mmap_alignment(size_t *page_size) -{ - *page_size = get_allocation_granularity(); - return 0; -} - -int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) -{ - HANDLE fh = (HANDLE)_get_osfhandle(fd); - DWORD alignment = get_allocation_granularity(); - DWORD fmap_prot = 0; - DWORD view_prot = 0; - DWORD off_low = 0; - DWORD off_hi = 0; - off64_t page_start; - off64_t page_offset; - - GIT_MMAP_VALIDATE(out, len, prot, flags); - - out->data = NULL; - out->len = 0; - out->fmh = NULL; - - if (fh == INVALID_HANDLE_VALUE) { - errno = EBADF; - git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value"); - return -1; - } - - if (prot & GIT_PROT_WRITE) - fmap_prot |= PAGE_READWRITE; - else if (prot & GIT_PROT_READ) - fmap_prot |= PAGE_READONLY; - - if (prot & GIT_PROT_WRITE) - view_prot |= FILE_MAP_WRITE; - if (prot & GIT_PROT_READ) - view_prot |= FILE_MAP_READ; - - page_start = (offset / alignment) * alignment; - page_offset = offset - page_start; - - if (page_offset != 0) { /* offset must be multiple of the allocation granularity */ - errno = EINVAL; - git_error_set(GIT_ERROR_OS, "failed to mmap. Offset must be multiple of allocation granularity"); - return -1; - } - - out->fmh = CreateFileMapping(fh, NULL, fmap_prot, 0, 0, NULL); - if (!out->fmh || out->fmh == INVALID_HANDLE_VALUE) { - git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value"); - out->fmh = NULL; - return -1; - } - - off_low = (DWORD)(page_start); - off_hi = (DWORD)(page_start >> 32); - out->data = MapViewOfFile(out->fmh, view_prot, off_hi, off_low, len); - if (!out->data) { - git_error_set(GIT_ERROR_OS, "failed to mmap. No data written"); - CloseHandle(out->fmh); - out->fmh = NULL; - return -1; - } - out->len = len; - - return 0; -} - -int p_munmap(git_map *map) -{ - int error = 0; - - GIT_ASSERT_ARG(map); - - if (map->data) { - if (!UnmapViewOfFile(map->data)) { - git_error_set(GIT_ERROR_OS, "failed to munmap. Could not unmap view of file"); - error = -1; - } - map->data = NULL; - } - - if (map->fmh) { - if (!CloseHandle(map->fmh)) { - git_error_set(GIT_ERROR_OS, "failed to munmap. Could not close handle"); - error = -1; - } - map->fmh = NULL; - } - - return error; -} - -#endif diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h deleted file mode 100644 index aa2bef98d..000000000 --- a/src/win32/mingw-compat.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_mingw_compat_h__ -#define INCLUDE_win32_mingw_compat_h__ - -#if defined(__MINGW32__) - -#undef stat - -#if _WIN32_WINNT < 0x0600 && !defined(__MINGW64_VERSION_MAJOR) -#undef MemoryBarrier -void __mingworg_MemoryBarrier(void); -#define MemoryBarrier __mingworg_MemoryBarrier -#define VOLUME_NAME_DOS 0x0 -#endif - -#endif - -#endif diff --git a/src/win32/msvc-compat.h b/src/win32/msvc-compat.h deleted file mode 100644 index 03f9f36dc..000000000 --- a/src/win32/msvc-compat.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_msvc_compat_h__ -#define INCLUDE_win32_msvc_compat_h__ - -#if defined(_MSC_VER) - -typedef unsigned short mode_t; -typedef SSIZE_T ssize_t; - -#ifdef _WIN64 -# define SSIZE_MAX _I64_MAX -#else -# define SSIZE_MAX LONG_MAX -#endif - -#define strcasecmp(s1, s2) _stricmp(s1, s2) -#define strncasecmp(s1, s2, c) _strnicmp(s1, s2, c) - -#endif - -/* - * Offer GIT_LIBGIT2_CALL for our calling conventions (__cdecl, always). - * This is useful for providing callbacks to userspace code. - * - * Offer GIT_SYSTEM_CALL for the system calling conventions (__stdcall on - * Win32). Useful for providing callbacks to system libraries. - */ -#define GIT_LIBGIT2_CALL __cdecl -#define GIT_SYSTEM_CALL NTAPI - -#endif diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c deleted file mode 100644 index d9fc8292b..000000000 --- a/src/win32/path_w32.c +++ /dev/null @@ -1,642 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "path_w32.h" - -#include "fs_path.h" -#include "utf-conv.h" -#include "posix.h" -#include "reparse.h" -#include "dir.h" - -#define PATH__NT_NAMESPACE L"\\\\?\\" -#define PATH__NT_NAMESPACE_LEN 4 - -#define PATH__ABSOLUTE_LEN 3 - -#define path__is_nt_namespace(p) \ - (((p)[0] == '\\' && (p)[1] == '\\' && (p)[2] == '?' && (p)[3] == '\\') || \ - ((p)[0] == '/' && (p)[1] == '/' && (p)[2] == '?' && (p)[3] == '/')) - -#define path__is_unc(p) \ - (((p)[0] == '\\' && (p)[1] == '\\') || ((p)[0] == '/' && (p)[1] == '/')) - -#define path__startswith_slash(p) \ - ((p)[0] == '\\' || (p)[0] == '/') - -GIT_INLINE(int) path__cwd(wchar_t *path, int size) -{ - int len; - - if ((len = GetCurrentDirectoryW(size, path)) == 0) { - errno = GetLastError() == ERROR_ACCESS_DENIED ? EACCES : ENOENT; - return -1; - } else if (len > size) { - errno = ENAMETOOLONG; - return -1; - } - - /* The Win32 APIs may return "\\?\" once you've used it first. - * But it may not. What a gloriously predictable API! - */ - if (wcsncmp(path, PATH__NT_NAMESPACE, PATH__NT_NAMESPACE_LEN)) - return len; - - len -= PATH__NT_NAMESPACE_LEN; - - memmove(path, path + PATH__NT_NAMESPACE_LEN, sizeof(wchar_t) * len); - return len; -} - -static wchar_t *path__skip_server(wchar_t *path) -{ - wchar_t *c; - - for (c = path; *c; c++) { - if (git_fs_path_is_dirsep(*c)) - return c + 1; - } - - return c; -} - -static wchar_t *path__skip_prefix(wchar_t *path) -{ - if (path__is_nt_namespace(path)) { - path += PATH__NT_NAMESPACE_LEN; - - if (wcsncmp(path, L"UNC\\", 4) == 0) - path = path__skip_server(path + 4); - else if (git_fs_path_is_absolute(path)) - path += PATH__ABSOLUTE_LEN; - } else if (git_fs_path_is_absolute(path)) { - path += PATH__ABSOLUTE_LEN; - } else if (path__is_unc(path)) { - path = path__skip_server(path + 2); - } - - return path; -} - -int git_win32_path_canonicalize(git_win32_path path) -{ - wchar_t *base, *from, *to, *next; - size_t len; - - base = to = path__skip_prefix(path); - - /* Unposixify if the prefix */ - for (from = path; from < to; from++) { - if (*from == L'/') - *from = L'\\'; - } - - while (*from) { - for (next = from; *next; ++next) { - if (*next == L'/') { - *next = L'\\'; - break; - } - - if (*next == L'\\') - break; - } - - len = next - from; - - if (len == 1 && from[0] == L'.') - /* do nothing with singleton dot */; - - else if (len == 2 && from[0] == L'.' && from[1] == L'.') { - if (to == base) { - /* no more path segments to strip, eat the "../" */ - if (*next == L'\\') - len++; - - base = to; - } else { - /* back up a path segment */ - while (to > base && to[-1] == L'\\') to--; - while (to > base && to[-1] != L'\\') to--; - } - } else { - if (*next == L'\\' && *from != L'\\') - len++; - - if (to != from) - memmove(to, from, sizeof(wchar_t) * len); - - to += len; - } - - from += len; - - while (*from == L'\\') from++; - } - - /* Strip trailing backslashes */ - while (to > base && to[-1] == L'\\') to--; - - *to = L'\0'; - - if ((to - path) > INT_MAX) { - SetLastError(ERROR_FILENAME_EXCED_RANGE); - return -1; - } - - return (int)(to - path); -} - -static int git_win32_path_join( - git_win32_path dest, - const wchar_t *one, - size_t one_len, - const wchar_t *two, - size_t two_len) -{ - size_t backslash = 0; - - if (one_len && two_len && one[one_len - 1] != L'\\') - backslash = 1; - - if (one_len + two_len + backslash > MAX_PATH) { - git_error_set(GIT_ERROR_INVALID, "path too long"); - return -1; - } - - memmove(dest, one, one_len * sizeof(wchar_t)); - - if (backslash) - dest[one_len] = L'\\'; - - memcpy(dest + one_len + backslash, two, two_len * sizeof(wchar_t)); - dest[one_len + backslash + two_len] = L'\0'; - - return 0; -} - -struct win32_path_iter { - wchar_t *env; - const wchar_t *current_dir; -}; - -static int win32_path_iter_init(struct win32_path_iter *iter) -{ - DWORD len = GetEnvironmentVariableW(L"PATH", NULL, 0); - - if (!len && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { - iter->env = NULL; - iter->current_dir = NULL; - return 0; - } else if (!len) { - git_error_set(GIT_ERROR_OS, "could not load PATH"); - return -1; - } - - iter->env = git__malloc(len * sizeof(wchar_t)); - GIT_ERROR_CHECK_ALLOC(iter->env); - - len = GetEnvironmentVariableW(L"PATH", iter->env, len); - - if (len == 0) { - git_error_set(GIT_ERROR_OS, "could not load PATH"); - return -1; - } - - iter->current_dir = iter->env; - return 0; -} - -static int win32_path_iter_next( - const wchar_t **out, - size_t *out_len, - struct win32_path_iter *iter) -{ - const wchar_t *start; - wchar_t term; - size_t len = 0; - - if (!iter->current_dir || !*iter->current_dir) - return GIT_ITEROVER; - - term = (*iter->current_dir == L'"') ? *iter->current_dir++ : L';'; - start = iter->current_dir; - - while (*iter->current_dir && *iter->current_dir != term) { - iter->current_dir++; - len++; - } - - *out = start; - *out_len = len; - - if (term == L'"' && *iter->current_dir) - iter->current_dir++; - - while (*iter->current_dir == L';') - iter->current_dir++; - - return 0; -} - -static void win32_path_iter_dispose(struct win32_path_iter *iter) -{ - if (!iter) - return; - - git__free(iter->env); - iter->env = NULL; - iter->current_dir = NULL; -} - -int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe) -{ - struct win32_path_iter path_iter; - const wchar_t *dir; - size_t dir_len, exe_len = wcslen(exe); - bool found = false; - - if (win32_path_iter_init(&path_iter) < 0) - return -1; - - while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER) { - if (git_win32_path_join(fullpath, dir, dir_len, exe, exe_len) < 0) - continue; - - if (_waccess(fullpath, 0) == 0) { - found = true; - break; - } - } - - win32_path_iter_dispose(&path_iter); - - if (found) - return 0; - - fullpath[0] = L'\0'; - return GIT_ENOTFOUND; -} - -static int win32_path_cwd(wchar_t *out, size_t len) -{ - int cwd_len; - - if (len > INT_MAX) { - errno = ENAMETOOLONG; - return -1; - } - - if ((cwd_len = path__cwd(out, (int)len)) < 0) - return -1; - - /* UNC paths */ - if (wcsncmp(L"\\\\", out, 2) == 0) { - /* Our buffer must be at least 5 characters larger than the - * current working directory: we swallow one of the leading - * '\'s, but we we add a 'UNC' specifier to the path, plus - * a trailing directory separator, plus a NUL. - */ - if (cwd_len > GIT_WIN_PATH_MAX - 4) { - errno = ENAMETOOLONG; - return -1; - } - - memmove(out+2, out, sizeof(wchar_t) * cwd_len); - out[0] = L'U'; - out[1] = L'N'; - out[2] = L'C'; - - cwd_len += 2; - } - - /* Our buffer must be at least 2 characters larger than the current - * working directory. (One character for the directory separator, - * one for the null. - */ - else if (cwd_len > GIT_WIN_PATH_MAX - 2) { - errno = ENAMETOOLONG; - return -1; - } - - return cwd_len; -} - -int git_win32_path_from_utf8(git_win32_path out, const char *src) -{ - wchar_t *dest = out; - - /* All win32 paths are in NT-prefixed format, beginning with "\\?\". */ - memcpy(dest, PATH__NT_NAMESPACE, sizeof(wchar_t) * PATH__NT_NAMESPACE_LEN); - dest += PATH__NT_NAMESPACE_LEN; - - /* See if this is an absolute path (beginning with a drive letter) */ - if (git_fs_path_is_absolute(src)) { - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0) - goto on_error; - } - /* File-prefixed NT-style paths beginning with \\?\ */ - else if (path__is_nt_namespace(src)) { - /* Skip the NT prefix, the destination already contains it */ - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0) - goto on_error; - } - /* UNC paths */ - else if (path__is_unc(src)) { - memcpy(dest, L"UNC\\", sizeof(wchar_t) * 4); - dest += 4; - - /* Skip the leading "\\" */ - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0) - goto on_error; - } - /* Absolute paths omitting the drive letter */ - else if (path__startswith_slash(src)) { - if (path__cwd(dest, GIT_WIN_PATH_MAX) < 0) - goto on_error; - - if (!git_fs_path_is_absolute(dest)) { - errno = ENOENT; - goto on_error; - } - - /* Skip the drive letter specification ("C:") */ - if (git__utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0) - goto on_error; - } - /* Relative paths */ - else { - int cwd_len; - - if ((cwd_len = win32_path_cwd(dest, GIT_WIN_PATH_MAX)) < 0) - goto on_error; - - dest[cwd_len++] = L'\\'; - - if (git__utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0) - goto on_error; - } - - return git_win32_path_canonicalize(out); - -on_error: - /* set windows error code so we can use its error message */ - if (errno == ENAMETOOLONG) - SetLastError(ERROR_FILENAME_EXCED_RANGE); - - return -1; -} - -int git_win32_path_relative_from_utf8(git_win32_path out, const char *src) -{ - wchar_t *dest = out, *p; - int len; - - /* Handle absolute paths */ - if (git_fs_path_is_absolute(src) || - path__is_nt_namespace(src) || - path__is_unc(src) || - path__startswith_slash(src)) { - return git_win32_path_from_utf8(out, src); - } - - if ((len = git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src)) < 0) - return -1; - - for (p = dest; p < (dest + len); p++) { - if (*p == L'/') - *p = L'\\'; - } - - return len; -} - -int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src) -{ - char *out = dest; - int len; - - /* Strip NT namespacing "\\?\" */ - if (path__is_nt_namespace(src)) { - src += 4; - - /* "\\?\UNC\server\share" -> "\\server\share" */ - if (wcsncmp(src, L"UNC\\", 4) == 0) { - src += 4; - - memcpy(dest, "\\\\", 2); - out = dest + 2; - } - } - - if ((len = git__utf16_to_8(out, GIT_WIN_PATH_UTF8, src)) < 0) - return len; - - git_fs_path_mkposix(dest); - - return len; -} - -char *git_win32_path_8dot3_name(const char *path) -{ - git_win32_path longpath, shortpath; - wchar_t *start; - char *shortname; - int len, namelen = 1; - - if (git_win32_path_from_utf8(longpath, path) < 0) - return NULL; - - len = GetShortPathNameW(longpath, shortpath, GIT_WIN_PATH_UTF16); - - while (len && shortpath[len-1] == L'\\') - shortpath[--len] = L'\0'; - - if (len == 0 || len >= GIT_WIN_PATH_UTF16) - return NULL; - - for (start = shortpath + (len - 1); - start > shortpath && *(start-1) != '/' && *(start-1) != '\\'; - start--) - namelen++; - - /* We may not have actually been given a short name. But if we have, - * it will be in the ASCII byte range, so we don't need to worry about - * multi-byte sequences and can allocate naively. - */ - if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL) - return NULL; - - if ((len = git__utf16_to_8(shortname, namelen + 1, start)) < 0) - return NULL; - - return shortname; -} - -static bool path_is_volume(wchar_t *target, size_t target_len) -{ - return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0); -} - -/* On success, returns the length, in characters, of the path stored in dest. - * On failure, returns a negative value. */ -int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path) -{ - BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; - GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf; - HANDLE handle = NULL; - DWORD ioctl_ret; - wchar_t *target; - size_t target_len; - - int error = -1; - - handle = CreateFileW(path, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); - - if (handle == INVALID_HANDLE_VALUE) { - errno = ENOENT; - return -1; - } - - if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, - reparse_buf, sizeof(buf), &ioctl_ret, NULL)) { - errno = EINVAL; - goto on_error; - } - - switch (reparse_buf->ReparseTag) { - case IO_REPARSE_TAG_SYMLINK: - target = reparse_buf->ReparseBuffer.SymbolicLink.PathBuffer + - (reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameOffset / sizeof(WCHAR)); - target_len = reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameLength / sizeof(WCHAR); - break; - case IO_REPARSE_TAG_MOUNT_POINT: - target = reparse_buf->ReparseBuffer.MountPoint.PathBuffer + - (reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset / sizeof(WCHAR)); - target_len = reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength / sizeof(WCHAR); - break; - default: - errno = EINVAL; - goto on_error; - } - - if (path_is_volume(target, target_len)) { - /* This path is a reparse point that represents another volume mounted - * at this location, it is not a symbolic link our input was canonical. - */ - errno = EINVAL; - error = -1; - } else if (target_len) { - /* The path may need to have a namespace prefix removed. */ - target_len = git_win32_path_remove_namespace(target, target_len); - - /* Need one additional character in the target buffer - * for the terminating NULL. */ - if (GIT_WIN_PATH_UTF16 > target_len) { - wcscpy(dest, target); - error = (int)target_len; - } - } - -on_error: - CloseHandle(handle); - return error; -} - -/** - * Removes any trailing backslashes from a path, except in the case of a drive - * letter path (C:\, D:\, etc.). This function cannot fail. - * - * @param path The path which should be trimmed. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_trim_end(wchar_t *str, size_t len) -{ - while (1) { - if (!len || str[len - 1] != L'\\') - break; - - /* - * Don't trim backslashes from drive letter paths, which - * are 3 characters long and of the form C:\, D:\, etc. - */ - if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':') - break; - - len--; - } - - str[len] = L'\0'; - - return len; -} - -/** - * Removes any of the following namespace prefixes from a path, - * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. - * - * @param path The path which should be converted. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_remove_namespace(wchar_t *str, size_t len) -{ - static const wchar_t dosdevices_namespace[] = L"\\\?\?\\"; - static const wchar_t nt_namespace[] = L"\\\\?\\"; - static const wchar_t unc_namespace_remainder[] = L"UNC\\"; - static const wchar_t unc_prefix[] = L"\\\\"; - - const wchar_t *prefix = NULL, *remainder = NULL; - size_t prefix_len = 0, remainder_len = 0; - - /* "\??\" -- DOS Devices prefix */ - if (len >= CONST_STRLEN(dosdevices_namespace) && - !wcsncmp(str, dosdevices_namespace, CONST_STRLEN(dosdevices_namespace))) { - remainder = str + CONST_STRLEN(dosdevices_namespace); - remainder_len = len - CONST_STRLEN(dosdevices_namespace); - } - /* "\\?\" -- NT namespace prefix */ - else if (len >= CONST_STRLEN(nt_namespace) && - !wcsncmp(str, nt_namespace, CONST_STRLEN(nt_namespace))) { - remainder = str + CONST_STRLEN(nt_namespace); - remainder_len = len - CONST_STRLEN(nt_namespace); - } - - /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */ - if (remainder_len >= CONST_STRLEN(unc_namespace_remainder) && - !wcsncmp(remainder, unc_namespace_remainder, CONST_STRLEN(unc_namespace_remainder))) { - - /* - * The proper Win32 path for a UNC share has "\\" at beginning of it - * and looks like "\\server\share\". So remove the - * UNC namespace and add a prefix of "\\" in its place. - */ - remainder += CONST_STRLEN(unc_namespace_remainder); - remainder_len -= CONST_STRLEN(unc_namespace_remainder); - - prefix = unc_prefix; - prefix_len = CONST_STRLEN(unc_prefix); - } - - /* - * Sanity check that the new string isn't longer than the old one. - * (This could only happen due to programmer error introducing a - * prefix longer than the namespace it replaces.) - */ - if (remainder && len >= remainder_len + prefix_len) { - if (prefix) - memmove(str, prefix, prefix_len * sizeof(wchar_t)); - - memmove(str + prefix_len, remainder, remainder_len * sizeof(wchar_t)); - - len = remainder_len + prefix_len; - str[len] = L'\0'; - } - - return git_win32_path_trim_end(str, len); -} diff --git a/src/win32/path_w32.h b/src/win32/path_w32.h deleted file mode 100644 index 837b11ebd..000000000 --- a/src/win32/path_w32.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_path_w32_h__ -#define INCLUDE_win32_path_w32_h__ - -#include "common.h" - -/** - * Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given - * path is relative, then it will be turned into an absolute path by having - * the current working directory prepended. - * - * @param dest The buffer to receive the wide string. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -extern int git_win32_path_from_utf8(git_win32_path dest, const char *src); - -/** - * Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given - * path is relative, then it will not be turned into an absolute path. - * - * @param dest The buffer to receive the wide string. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -extern int git_win32_path_relative_from_utf8(git_win32_path dest, const char *src); - -/** - * Canonicalize a Win32 UCS-2 path so that it is suitable for delivery to the - * Win32 APIs: remove multiple directory separators, squashing to a single one, - * strip trailing directory separators, ensure directory separators are all - * canonical (always backslashes, never forward slashes) and process any - * directory entries of '.' or '..'. - * - * Note that this is intended to be used on absolute Windows paths, those - * that start with `C:\`, `\\server\share`, `\\?\`, etc. - * - * This processes the buffer in place. - * - * @param path The buffer to process - * @return The new length of the buffer, in wchar_t's (not counting the NULL terminator) - */ -extern int git_win32_path_canonicalize(git_win32_path path); - -/** - * Create an internal format (posix-style) UTF-8 path from a Win32 UCS-2 path. - * - * @param dest The buffer to receive the UTF-8 string. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -extern int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src); - -/** - * Get the short name for the terminal path component in the given path. - * For example, given "C:\Foo\Bar\Asdf.txt", this will return the short name - * for the file "Asdf.txt". - * - * @param path The given path in UTF-8 - * @return The name of the shortname for the given path - */ -extern char *git_win32_path_8dot3_name(const char *path); - -extern int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path); - -/** - * Removes any trailing backslashes from a path, except in the case of a drive - * letter path (C:\, D:\, etc.). This function cannot fail. - * - * @param path The path which should be trimmed. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_trim_end(wchar_t *str, size_t len); - -/** - * Removes any of the following namespace prefixes from a path, - * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. - * - * @param path The path which should be converted. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_remove_namespace(wchar_t *str, size_t len); - -int git_win32_path_find_executable(git_win32_path fullpath, wchar_t* exe); - -#endif diff --git a/src/win32/posix.h b/src/win32/posix.h deleted file mode 100644 index 578347f15..000000000 --- a/src/win32/posix.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_posix_h__ -#define INCLUDE_win32_posix_h__ - -#include "common.h" -#include "../posix.h" -#include "win32-compat.h" -#include "path_w32.h" -#include "utf-conv.h" -#include "dir.h" - -extern unsigned long git_win32__createfile_sharemode; -extern int git_win32__retries; - -typedef SOCKET GIT_SOCKET; - -#define p_lseek(f,n,w) _lseeki64(f, n, w) - -extern int p_fstat(int fd, struct stat *buf); -extern int p_lstat(const char *file_name, struct stat *buf); -extern int p_stat(const char *path, struct stat *buf); - -extern int p_utimes(const char *filename, const struct p_timeval times[2]); -extern int p_futimes(int fd, const struct p_timeval times[2]); - -extern int p_readlink(const char *path, char *buf, size_t bufsiz); -extern int p_symlink(const char *old, const char *new); -extern int p_link(const char *old, const char *new); -extern int p_unlink(const char *path); -extern int p_mkdir(const char *path, mode_t mode); -extern int p_fsync(int fd); -extern char *p_realpath(const char *orig_path, char *buffer); - -extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags); -extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags); -extern int p_inet_pton(int af, const char *src, void* dst); - -extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr); -extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4); -extern int p_chdir(const char *path); -extern int p_chmod(const char *path, mode_t mode); -extern int p_rmdir(const char *path); -extern int p_access(const char *path, mode_t mode); -extern int p_ftruncate(int fd, off64_t size); - -/* p_lstat is almost but not quite POSIX correct. Specifically, the use of - * ENOTDIR is wrong, in that it does not mean precisely that a non-directory - * entry was encountered. Making it correct is potentially expensive, - * however, so this is a separate version of p_lstat to use when correct - * POSIX ENOTDIR semantics is required. - */ -extern int p_lstat_posixly(const char *filename, struct stat *buf); - -extern struct tm * p_localtime_r(const time_t *timer, struct tm *result); -extern struct tm * p_gmtime_r(const time_t *timer, struct tm *result); - -#endif diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c deleted file mode 100644 index 5f7cd0c26..000000000 --- a/src/win32/posix_w32.c +++ /dev/null @@ -1,1047 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "../posix.h" -#include "../futils.h" -#include "fs_path.h" -#include "path_w32.h" -#include "utf-conv.h" -#include "reparse.h" -#include -#include -#include -#include - -#ifndef FILE_NAME_NORMALIZED -# define FILE_NAME_NORMALIZED 0 -#endif - -#ifndef IO_REPARSE_TAG_SYMLINK -#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) -#endif - -#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE -# define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x02 -#endif - -#ifndef SYMBOLIC_LINK_FLAG_DIRECTORY -# define SYMBOLIC_LINK_FLAG_DIRECTORY 0x01 -#endif - -/* Allowable mode bits on Win32. Using mode bits that are not supported on - * Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it - * so we simply remove them. - */ -#define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE) - -unsigned long git_win32__createfile_sharemode = - FILE_SHARE_READ | FILE_SHARE_WRITE; -int git_win32__retries = 10; - -GIT_INLINE(void) set_errno(void) -{ - switch (GetLastError()) { - case ERROR_FILE_NOT_FOUND: - case ERROR_PATH_NOT_FOUND: - case ERROR_INVALID_DRIVE: - case ERROR_NO_MORE_FILES: - case ERROR_BAD_NETPATH: - case ERROR_BAD_NET_NAME: - case ERROR_BAD_PATHNAME: - case ERROR_FILENAME_EXCED_RANGE: - errno = ENOENT; - break; - case ERROR_BAD_ENVIRONMENT: - errno = E2BIG; - break; - case ERROR_BAD_FORMAT: - case ERROR_INVALID_STARTING_CODESEG: - case ERROR_INVALID_STACKSEG: - case ERROR_INVALID_MODULETYPE: - case ERROR_INVALID_EXE_SIGNATURE: - case ERROR_EXE_MARKED_INVALID: - case ERROR_BAD_EXE_FORMAT: - case ERROR_ITERATED_DATA_EXCEEDS_64k: - case ERROR_INVALID_MINALLOCSIZE: - case ERROR_DYNLINK_FROM_INVALID_RING: - case ERROR_IOPL_NOT_ENABLED: - case ERROR_INVALID_SEGDPL: - case ERROR_AUTODATASEG_EXCEEDS_64k: - case ERROR_RING2SEG_MUST_BE_MOVABLE: - case ERROR_RELOC_CHAIN_XEEDS_SEGLIM: - case ERROR_INFLOOP_IN_RELOC_CHAIN: - errno = ENOEXEC; - break; - case ERROR_INVALID_HANDLE: - case ERROR_INVALID_TARGET_HANDLE: - case ERROR_DIRECT_ACCESS_HANDLE: - errno = EBADF; - break; - case ERROR_WAIT_NO_CHILDREN: - case ERROR_CHILD_NOT_COMPLETE: - errno = ECHILD; - break; - case ERROR_NO_PROC_SLOTS: - case ERROR_MAX_THRDS_REACHED: - case ERROR_NESTING_NOT_ALLOWED: - errno = EAGAIN; - break; - case ERROR_ARENA_TRASHED: - case ERROR_NOT_ENOUGH_MEMORY: - case ERROR_INVALID_BLOCK: - case ERROR_NOT_ENOUGH_QUOTA: - errno = ENOMEM; - break; - case ERROR_ACCESS_DENIED: - case ERROR_CURRENT_DIRECTORY: - case ERROR_WRITE_PROTECT: - case ERROR_BAD_UNIT: - case ERROR_NOT_READY: - case ERROR_BAD_COMMAND: - case ERROR_CRC: - case ERROR_BAD_LENGTH: - case ERROR_SEEK: - case ERROR_NOT_DOS_DISK: - case ERROR_SECTOR_NOT_FOUND: - case ERROR_OUT_OF_PAPER: - case ERROR_WRITE_FAULT: - case ERROR_READ_FAULT: - case ERROR_GEN_FAILURE: - case ERROR_SHARING_VIOLATION: - case ERROR_LOCK_VIOLATION: - case ERROR_WRONG_DISK: - case ERROR_SHARING_BUFFER_EXCEEDED: - case ERROR_NETWORK_ACCESS_DENIED: - case ERROR_CANNOT_MAKE: - case ERROR_FAIL_I24: - case ERROR_DRIVE_LOCKED: - case ERROR_SEEK_ON_DEVICE: - case ERROR_NOT_LOCKED: - case ERROR_LOCK_FAILED: - errno = EACCES; - break; - case ERROR_FILE_EXISTS: - case ERROR_ALREADY_EXISTS: - errno = EEXIST; - break; - case ERROR_NOT_SAME_DEVICE: - errno = EXDEV; - break; - case ERROR_INVALID_FUNCTION: - case ERROR_INVALID_ACCESS: - case ERROR_INVALID_DATA: - case ERROR_INVALID_PARAMETER: - case ERROR_NEGATIVE_SEEK: - errno = EINVAL; - break; - case ERROR_TOO_MANY_OPEN_FILES: - errno = EMFILE; - break; - case ERROR_DISK_FULL: - errno = ENOSPC; - break; - case ERROR_BROKEN_PIPE: - errno = EPIPE; - break; - case ERROR_DIR_NOT_EMPTY: - errno = ENOTEMPTY; - break; - default: - errno = EINVAL; - } -} - -GIT_INLINE(bool) last_error_retryable(void) -{ - int os_error = GetLastError(); - - return (os_error == ERROR_SHARING_VIOLATION || - os_error == ERROR_ACCESS_DENIED); -} - -#define do_with_retries(fn, remediation) \ - do { \ - int __retry, __ret; \ - for (__retry = git_win32__retries; __retry; __retry--) { \ - if ((__ret = (fn)) != GIT_RETRY) \ - return __ret; \ - if (__retry > 1 && (__ret = (remediation)) != 0) { \ - if (__ret == GIT_RETRY) \ - continue; \ - return __ret; \ - } \ - Sleep(5); \ - } \ - return -1; \ - } while (0) \ - -static int ensure_writable(wchar_t *path) -{ - DWORD attrs; - - if ((attrs = GetFileAttributesW(path)) == INVALID_FILE_ATTRIBUTES) - goto on_error; - - if ((attrs & FILE_ATTRIBUTE_READONLY) == 0) - return 0; - - if (!SetFileAttributesW(path, (attrs & ~FILE_ATTRIBUTE_READONLY))) - goto on_error; - - return GIT_RETRY; - -on_error: - set_errno(); - return -1; -} - -/** - * Truncate or extend file. - * - * We now take a "git_off_t" rather than "long" because - * files may be longer than 2Gb. - */ -int p_ftruncate(int fd, off64_t size) -{ - if (size < 0) { - errno = EINVAL; - return -1; - } - -#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API) - return ((_chsize_s(fd, size) == 0) ? 0 : -1); -#else - /* TODO MINGW32 Find a replacement for _chsize() that handles big files. */ - if (size > INT32_MAX) { - errno = EFBIG; - return -1; - } - return _chsize(fd, (long)size); -#endif -} - -int p_mkdir(const char *path, mode_t mode) -{ - git_win32_path buf; - - GIT_UNUSED(mode); - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - return _wmkdir(buf); -} - -int p_link(const char *old, const char *new) -{ - GIT_UNUSED(old); - GIT_UNUSED(new); - errno = ENOSYS; - return -1; -} - -GIT_INLINE(int) unlink_once(const wchar_t *path) -{ - DWORD error; - - if (DeleteFileW(path)) - return 0; - - if ((error = GetLastError()) == ERROR_ACCESS_DENIED) { - WIN32_FILE_ATTRIBUTE_DATA fdata; - if (!GetFileAttributesExW(path, GetFileExInfoStandard, &fdata) || - !(fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) || - !(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) - goto out; - - if (RemoveDirectoryW(path)) - return 0; - } - -out: - SetLastError(error); - - if (last_error_retryable()) - return GIT_RETRY; - - set_errno(); - return -1; -} - -int p_unlink(const char *path) -{ - git_win32_path wpath; - - if (git_win32_path_from_utf8(wpath, path) < 0) - return -1; - - do_with_retries(unlink_once(wpath), ensure_writable(wpath)); -} - -int p_fsync(int fd) -{ - HANDLE fh = (HANDLE)_get_osfhandle(fd); - - p_fsync__cnt++; - - if (fh == INVALID_HANDLE_VALUE) { - errno = EBADF; - return -1; - } - - if (!FlushFileBuffers(fh)) { - DWORD code = GetLastError(); - - if (code == ERROR_INVALID_HANDLE) - errno = EINVAL; - else - errno = EIO; - - return -1; - } - - return 0; -} - -#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\') - -static int lstat_w( - wchar_t *path, - struct stat *buf, - bool posix_enotdir) -{ - WIN32_FILE_ATTRIBUTE_DATA fdata; - - if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) { - if (!buf) - return 0; - - return git_win32__file_attribute_to_stat(buf, &fdata, path); - } - - switch (GetLastError()) { - case ERROR_ACCESS_DENIED: - errno = EACCES; - break; - default: - errno = ENOENT; - break; - } - - /* To match POSIX behavior, set ENOTDIR when any of the folders in the - * file path is a regular file, otherwise set ENOENT. - */ - if (errno == ENOENT && posix_enotdir) { - size_t path_len = wcslen(path); - - /* scan up path until we find an existing item */ - while (1) { - DWORD attrs; - - /* remove last directory component */ - for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--); - - if (path_len <= 0) - break; - - path[path_len] = L'\0'; - attrs = GetFileAttributesW(path); - - if (attrs != INVALID_FILE_ATTRIBUTES) { - if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) - errno = ENOTDIR; - break; - } - } - } - - return -1; -} - -static int do_lstat(const char *path, struct stat *buf, bool posixly_correct) -{ - git_win32_path path_w; - int len; - - if ((len = git_win32_path_from_utf8(path_w, path)) < 0) - return -1; - - git_win32_path_trim_end(path_w, len); - - return lstat_w(path_w, buf, posixly_correct); -} - -int p_lstat(const char *filename, struct stat *buf) -{ - return do_lstat(filename, buf, false); -} - -int p_lstat_posixly(const char *filename, struct stat *buf) -{ - return do_lstat(filename, buf, true); -} - -int p_readlink(const char *path, char *buf, size_t bufsiz) -{ - git_win32_path path_w, target_w; - git_win32_utf8_path target; - int len; - - /* readlink(2) does not NULL-terminate the string written - * to the target buffer. Furthermore, the target buffer need - * not be large enough to hold the entire result. A truncated - * result should be written in this case. Since this truncation - * could occur in the middle of the encoding of a code point, - * we need to buffer the result on the stack. */ - - if (git_win32_path_from_utf8(path_w, path) < 0 || - git_win32_path_readlink_w(target_w, path_w) < 0 || - (len = git_win32_path_to_utf8(target, target_w)) < 0) - return -1; - - bufsiz = min((size_t)len, bufsiz); - memcpy(buf, target, bufsiz); - - return (int)bufsiz; -} - -static bool target_is_dir(const char *target, const char *path) -{ - git_str resolved = GIT_STR_INIT; - git_win32_path resolved_w; - bool isdir = true; - - if (git_fs_path_is_absolute(target)) - git_win32_path_from_utf8(resolved_w, target); - else if (git_fs_path_dirname_r(&resolved, path) < 0 || - git_fs_path_apply_relative(&resolved, target) < 0 || - git_win32_path_from_utf8(resolved_w, resolved.ptr) < 0) - goto out; - - isdir = GetFileAttributesW(resolved_w) & FILE_ATTRIBUTE_DIRECTORY; - -out: - git_str_dispose(&resolved); - return isdir; -} - -int p_symlink(const char *target, const char *path) -{ - git_win32_path target_w, path_w; - DWORD dwFlags; - - /* - * Convert both target and path to Windows-style paths. Note that we do - * not want to use `git_win32_path_from_utf8` for converting the target, - * as that function will automatically pre-pend the current working - * directory in case the path is not absolute. As Git will instead use - * relative symlinks, this is not something we want. - */ - if (git_win32_path_from_utf8(path_w, path) < 0 || - git_win32_path_relative_from_utf8(target_w, target) < 0) - return -1; - - dwFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; - if (target_is_dir(target, path)) - dwFlags |= SYMBOLIC_LINK_FLAG_DIRECTORY; - - if (!CreateSymbolicLinkW(path_w, target_w, dwFlags)) - return -1; - - return 0; -} - -struct open_opts { - DWORD access; - DWORD sharing; - SECURITY_ATTRIBUTES security; - DWORD creation_disposition; - DWORD attributes; - int osf_flags; -}; - -GIT_INLINE(void) open_opts_from_posix(struct open_opts *opts, int flags, mode_t mode) -{ - memset(opts, 0, sizeof(struct open_opts)); - - switch (flags & (O_WRONLY | O_RDWR)) { - case O_WRONLY: - opts->access = GENERIC_WRITE; - break; - case O_RDWR: - opts->access = GENERIC_READ | GENERIC_WRITE; - break; - default: - opts->access = GENERIC_READ; - break; - } - - opts->sharing = (DWORD)git_win32__createfile_sharemode; - - switch (flags & (O_CREAT | O_TRUNC | O_EXCL)) { - case O_CREAT | O_EXCL: - case O_CREAT | O_TRUNC | O_EXCL: - opts->creation_disposition = CREATE_NEW; - break; - case O_CREAT | O_TRUNC: - opts->creation_disposition = CREATE_ALWAYS; - break; - case O_TRUNC: - opts->creation_disposition = TRUNCATE_EXISTING; - break; - case O_CREAT: - opts->creation_disposition = OPEN_ALWAYS; - break; - default: - opts->creation_disposition = OPEN_EXISTING; - break; - } - - opts->attributes = ((flags & O_CREAT) && !(mode & S_IWRITE)) ? - FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL; - opts->osf_flags = flags & (O_RDONLY | O_APPEND); - - opts->security.nLength = sizeof(SECURITY_ATTRIBUTES); - opts->security.lpSecurityDescriptor = NULL; - opts->security.bInheritHandle = 0; -} - -GIT_INLINE(int) open_once( - const wchar_t *path, - struct open_opts *opts) -{ - int fd; - - HANDLE handle = CreateFileW(path, opts->access, opts->sharing, - &opts->security, opts->creation_disposition, opts->attributes, 0); - - if (handle == INVALID_HANDLE_VALUE) { - if (last_error_retryable()) - return GIT_RETRY; - - set_errno(); - return -1; - } - - if ((fd = _open_osfhandle((intptr_t)handle, opts->osf_flags)) < 0) - CloseHandle(handle); - - return fd; -} - -int p_open(const char *path, int flags, ...) -{ - git_win32_path wpath; - mode_t mode = 0; - struct open_opts opts = {0}; - - #ifdef GIT_DEBUG_STRICT_OPEN - if (strstr(path, "//") != NULL) { - errno = EACCES; - return -1; - } - #endif - - if (git_win32_path_from_utf8(wpath, path) < 0) - return -1; - - if (flags & O_CREAT) { - va_list arg_list; - - va_start(arg_list, flags); - mode = (mode_t)va_arg(arg_list, int); - va_end(arg_list); - } - - open_opts_from_posix(&opts, flags, mode); - - do_with_retries( - open_once(wpath, &opts), - 0); -} - -int p_creat(const char *path, mode_t mode) -{ - return p_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); -} - -int p_utimes(const char *path, const struct p_timeval times[2]) -{ - git_win32_path wpath; - int fd, error; - DWORD attrs_orig, attrs_new = 0; - struct open_opts opts = { 0 }; - - if (git_win32_path_from_utf8(wpath, path) < 0) - return -1; - - attrs_orig = GetFileAttributesW(wpath); - - if (attrs_orig & FILE_ATTRIBUTE_READONLY) { - attrs_new = attrs_orig & ~FILE_ATTRIBUTE_READONLY; - - if (!SetFileAttributesW(wpath, attrs_new)) { - git_error_set(GIT_ERROR_OS, "failed to set attributes"); - return -1; - } - } - - open_opts_from_posix(&opts, O_RDWR, 0); - - if ((fd = open_once(wpath, &opts)) < 0) { - error = -1; - goto done; - } - - error = p_futimes(fd, times); - close(fd); - -done: - if (attrs_orig != attrs_new) { - DWORD os_error = GetLastError(); - SetFileAttributesW(wpath, attrs_orig); - SetLastError(os_error); - } - - return error; -} - -int p_futimes(int fd, const struct p_timeval times[2]) -{ - HANDLE handle; - FILETIME atime = { 0 }, mtime = { 0 }; - - if (times == NULL) { - SYSTEMTIME st; - - GetSystemTime(&st); - SystemTimeToFileTime(&st, &atime); - SystemTimeToFileTime(&st, &mtime); - } - else { - git_win32__timeval_to_filetime(&atime, times[0]); - git_win32__timeval_to_filetime(&mtime, times[1]); - } - - if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE) - return -1; - - if (SetFileTime(handle, NULL, &atime, &mtime) == 0) - return -1; - - return 0; -} - -int p_getcwd(char *buffer_out, size_t size) -{ - git_win32_path buf; - wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16); - - if (!cwd) - return -1; - - git_win32_path_remove_namespace(cwd, wcslen(cwd)); - - /* Convert the working directory back to UTF-8 */ - if (git__utf16_to_8(buffer_out, size, cwd) < 0) { - DWORD code = GetLastError(); - - if (code == ERROR_INSUFFICIENT_BUFFER) - errno = ERANGE; - else - errno = EINVAL; - - return -1; - } - - git_fs_path_mkposix(buffer_out); - return 0; -} - -static int getfinalpath_w( - git_win32_path dest, - const wchar_t *path) -{ - HANDLE hFile; - DWORD dwChars; - - /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not - * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the - * target of the link. */ - hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - - if (INVALID_HANDLE_VALUE == hFile) - return -1; - - /* Call GetFinalPathNameByHandle */ - dwChars = GetFinalPathNameByHandleW(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED); - CloseHandle(hFile); - - if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16) - return -1; - - /* The path may be delivered to us with a namespace prefix; remove */ - return (int)git_win32_path_remove_namespace(dest, dwChars); -} - -static int follow_and_lstat_link(git_win32_path path, struct stat *buf) -{ - git_win32_path target_w; - - if (getfinalpath_w(target_w, path) < 0) - return -1; - - return lstat_w(target_w, buf, false); -} - -int p_fstat(int fd, struct stat *buf) -{ - BY_HANDLE_FILE_INFORMATION fhInfo; - - HANDLE fh = (HANDLE)_get_osfhandle(fd); - - if (fh == INVALID_HANDLE_VALUE || - !GetFileInformationByHandle(fh, &fhInfo)) { - errno = EBADF; - return -1; - } - - git_win32__file_information_to_stat(buf, &fhInfo); - return 0; -} - -int p_stat(const char *path, struct stat *buf) -{ - git_win32_path path_w; - int len; - - if ((len = git_win32_path_from_utf8(path_w, path)) < 0 || - lstat_w(path_w, buf, false) < 0) - return -1; - - /* The item is a symbolic link or mount point. No need to iterate - * to follow multiple links; use GetFinalPathNameFromHandle. */ - if (S_ISLNK(buf->st_mode)) - return follow_and_lstat_link(path_w, buf); - - return 0; -} - -int p_chdir(const char *path) -{ - git_win32_path buf; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - return _wchdir(buf); -} - -int p_chmod(const char *path, mode_t mode) -{ - git_win32_path buf; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - return _wchmod(buf, mode); -} - -int p_rmdir(const char *path) -{ - git_win32_path buf; - int error; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - error = _wrmdir(buf); - - if (error == -1) { - switch (GetLastError()) { - /* _wrmdir() is documented to return EACCES if "A program has an open - * handle to the directory." This sounds like what everybody else calls - * EBUSY. Let's convert appropriate error codes. - */ - case ERROR_SHARING_VIOLATION: - errno = EBUSY; - break; - - /* This error can be returned when trying to rmdir an extant file. */ - case ERROR_DIRECTORY: - errno = ENOTDIR; - break; - } - } - - return error; -} - -char *p_realpath(const char *orig_path, char *buffer) -{ - git_win32_path orig_path_w, buffer_w; - - if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0) - return NULL; - - /* Note that if the path provided is a relative path, then the current directory - * is used to resolve the path -- which is a concurrency issue because the current - * directory is a process-wide variable. */ - if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) { - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) - errno = ENAMETOOLONG; - else - errno = EINVAL; - - return NULL; - } - - /* The path must exist. */ - if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { - errno = ENOENT; - return NULL; - } - - if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) { - errno = ENOMEM; - return NULL; - } - - /* Convert the path to UTF-8. If the caller provided a buffer, then it - * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't, - * then we may overflow. */ - if (git_win32_path_to_utf8(buffer, buffer_w) < 0) - return NULL; - - git_fs_path_mkposix(buffer); - - return buffer; -} - -int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) -{ -#if defined(_MSC_VER) - int len; - - if (count == 0) - return _vscprintf(format, argptr); - - #if _MSC_VER >= 1500 - len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr); - #else - len = _vsnprintf(buffer, count, format, argptr); - #endif - - if (len < 0) - return _vscprintf(format, argptr); - - return len; -#else /* MinGW */ - return vsnprintf(buffer, count, format, argptr); -#endif -} - -int p_snprintf(char *buffer, size_t count, const char *format, ...) -{ - va_list va; - int r; - - va_start(va, format); - r = p_vsnprintf(buffer, count, format, va); - va_end(va); - - return r; -} - -int p_access(const char *path, mode_t mode) -{ - git_win32_path buf; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - return _waccess(buf, mode & WIN32_MODE_MASK); -} - -GIT_INLINE(int) rename_once(const wchar_t *from, const wchar_t *to) -{ - if (MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) - return 0; - - if (last_error_retryable()) - return GIT_RETRY; - - set_errno(); - return -1; -} - -int p_rename(const char *from, const char *to) -{ - git_win32_path wfrom, wto; - - if (git_win32_path_from_utf8(wfrom, from) < 0 || - git_win32_path_from_utf8(wto, to) < 0) - return -1; - - do_with_retries(rename_once(wfrom, wto), ensure_writable(wto)); -} - -int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags) -{ - if ((size_t)((int)length) != length) - return -1; /* git_error_set will be done by caller */ - - return recv(socket, buffer, (int)length, flags); -} - -int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags) -{ - if ((size_t)((int)length) != length) - return -1; /* git_error_set will be done by caller */ - - return send(socket, buffer, (int)length, flags); -} - -/** - * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html - * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that - */ -struct tm * -p_localtime_r (const time_t *timer, struct tm *result) -{ - struct tm *local_result; - local_result = localtime (timer); - - if (local_result == NULL || result == NULL) - return NULL; - - memcpy (result, local_result, sizeof (struct tm)); - return result; -} -struct tm * -p_gmtime_r (const time_t *timer, struct tm *result) -{ - struct tm *local_result; - local_result = gmtime (timer); - - if (local_result == NULL || result == NULL) - return NULL; - - memcpy (result, local_result, sizeof (struct tm)); - return result; -} - -int p_inet_pton(int af, const char *src, void *dst) -{ - struct sockaddr_storage sin; - void *addr; - int sin_len = sizeof(struct sockaddr_storage), addr_len; - int error = 0; - - if (af == AF_INET) { - addr = &((struct sockaddr_in *)&sin)->sin_addr; - addr_len = sizeof(struct in_addr); - } else if (af == AF_INET6) { - addr = &((struct sockaddr_in6 *)&sin)->sin6_addr; - addr_len = sizeof(struct in6_addr); - } else { - errno = EAFNOSUPPORT; - return -1; - } - - if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) { - memcpy(dst, addr, addr_len); - return 1; - } - - switch(WSAGetLastError()) { - case WSAEINVAL: - return 0; - case WSAEFAULT: - errno = ENOSPC; - return -1; - case WSA_NOT_ENOUGH_MEMORY: - errno = ENOMEM; - return -1; - } - - errno = EINVAL; - return -1; -} - -ssize_t p_pread(int fd, void *data, size_t size, off64_t offset) -{ - HANDLE fh; - DWORD rsize = 0; - OVERLAPPED ov = {0}; - LARGE_INTEGER pos = {0}; - off64_t final_offset = 0; - - /* Fail if the final offset would have overflowed to match POSIX semantics. */ - if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) { - errno = EINVAL; - return -1; - } - - /* - * Truncate large writes to the maximum allowable size: the caller - * needs to always call this in a loop anyways. - */ - if (size > INT32_MAX) { - size = INT32_MAX; - } - - pos.QuadPart = offset; - ov.Offset = pos.LowPart; - ov.OffsetHigh = pos.HighPart; - fh = (HANDLE)_get_osfhandle(fd); - - if (ReadFile(fh, data, (DWORD)size, &rsize, &ov)) { - return (ssize_t)rsize; - } - - set_errno(); - return -1; -} - -ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset) -{ - HANDLE fh; - DWORD wsize = 0; - OVERLAPPED ov = {0}; - LARGE_INTEGER pos = {0}; - off64_t final_offset = 0; - - /* Fail if the final offset would have overflowed to match POSIX semantics. */ - if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) { - errno = EINVAL; - return -1; - } - - /* - * Truncate large writes to the maximum allowable size: the caller - * needs to always call this in a loop anyways. - */ - if (size > INT32_MAX) { - size = INT32_MAX; - } - - pos.QuadPart = offset; - ov.Offset = pos.LowPart; - ov.OffsetHigh = pos.HighPart; - fh = (HANDLE)_get_osfhandle(fd); - - if (WriteFile(fh, data, (DWORD)size, &wsize, &ov)) { - return (ssize_t)wsize; - } - - set_errno(); - return -1; -} diff --git a/src/win32/precompiled.c b/src/win32/precompiled.c deleted file mode 100644 index 5f656a45d..000000000 --- a/src/win32/precompiled.c +++ /dev/null @@ -1 +0,0 @@ -#include "precompiled.h" diff --git a/src/win32/precompiled.h b/src/win32/precompiled.h deleted file mode 100644 index 806b1698a..000000000 --- a/src/win32/precompiled.h +++ /dev/null @@ -1,21 +0,0 @@ -#include "common.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#ifdef GIT_THREADS - #include "win32/thread.h" -#endif - -#include "git2.h" diff --git a/src/win32/reparse.h b/src/win32/reparse.h deleted file mode 100644 index 23312319f..000000000 --- a/src/win32/reparse.h +++ /dev/null @@ -1,57 +0,0 @@ -/* -* Copyright (C) the libgit2 contributors. All rights reserved. -* -* This file is part of libgit2, distributed under the GNU GPL v2 with -* a Linking Exception. For full terms see the included COPYING file. -*/ - -#ifndef INCLUDE_win32_reparse_h__ -#define INCLUDE_win32_reparse_h__ - -/* This structure is defined on MSDN at -* http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx -* -* It was formerly included in the Windows 2000 SDK and remains defined in -* MinGW, so we must define it with a silly name to avoid conflicting. -*/ -typedef struct _GIT_REPARSE_DATA_BUFFER { - ULONG ReparseTag; - USHORT ReparseDataLength; - USHORT Reserved; - union { - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - ULONG Flags; - WCHAR PathBuffer[1]; - } SymbolicLink; - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - WCHAR PathBuffer[1]; - } MountPoint; - struct { - UCHAR DataBuffer[1]; - } Generic; - } ReparseBuffer; -} GIT_REPARSE_DATA_BUFFER; - -#define REPARSE_DATA_HEADER_SIZE 8 -#define REPARSE_DATA_MOUNTPOINT_HEADER_SIZE 8 -#define REPARSE_DATA_UNION_SIZE 12 - -/* Missing in MinGW */ -#ifndef FSCTL_GET_REPARSE_POINT -# define FSCTL_GET_REPARSE_POINT 0x000900a8 -#endif - -/* Missing in MinGW */ -#ifndef FSCTL_SET_REPARSE_POINT -# define FSCTL_SET_REPARSE_POINT 0x000900a4 -#endif - -#endif diff --git a/src/win32/thread.c b/src/win32/thread.c deleted file mode 100644 index f5cacd320..000000000 --- a/src/win32/thread.c +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "thread.h" -#include "runtime.h" - -#define CLEAN_THREAD_EXIT 0x6F012842 - -typedef void (WINAPI *win32_srwlock_fn)(GIT_SRWLOCK *); - -static win32_srwlock_fn win32_srwlock_initialize; -static win32_srwlock_fn win32_srwlock_acquire_shared; -static win32_srwlock_fn win32_srwlock_release_shared; -static win32_srwlock_fn win32_srwlock_acquire_exclusive; -static win32_srwlock_fn win32_srwlock_release_exclusive; - -static DWORD fls_index; - -/* The thread procedure stub used to invoke the caller's procedure - * and capture the return value for later collection. Windows will - * only hold a DWORD, but we need to be able to store an entire - * void pointer. This requires the indirection. */ -static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter) -{ - git_thread *thread = lpParameter; - - /* Set the current thread for `git_thread_exit` */ - FlsSetValue(fls_index, thread); - - thread->result = thread->proc(thread->param); - - return CLEAN_THREAD_EXIT; -} - -static void git_threads_global_shutdown(void) -{ - FlsFree(fls_index); -} - -int git_threads_global_init(void) -{ - HMODULE hModule = GetModuleHandleW(L"kernel32"); - - if (hModule) { - win32_srwlock_initialize = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "InitializeSRWLock"); - win32_srwlock_acquire_shared = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "AcquireSRWLockShared"); - win32_srwlock_release_shared = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "ReleaseSRWLockShared"); - win32_srwlock_acquire_exclusive = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "AcquireSRWLockExclusive"); - win32_srwlock_release_exclusive = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "ReleaseSRWLockExclusive"); - } - - if ((fls_index = FlsAlloc(NULL)) == FLS_OUT_OF_INDEXES) - return -1; - - return git_runtime_shutdown_register(git_threads_global_shutdown); -} - -int git_thread_create( - git_thread *GIT_RESTRICT thread, - void *(*start_routine)(void*), - void *GIT_RESTRICT arg) -{ - thread->result = NULL; - thread->param = arg; - thread->proc = start_routine; - thread->thread = CreateThread( - NULL, 0, git_win32__threadproc, thread, 0, NULL); - - return thread->thread ? 0 : -1; -} - -int git_thread_join( - git_thread *thread, - void **value_ptr) -{ - DWORD exit; - - if (WaitForSingleObject(thread->thread, INFINITE) != WAIT_OBJECT_0) - return -1; - - if (!GetExitCodeThread(thread->thread, &exit)) { - CloseHandle(thread->thread); - return -1; - } - - /* Check for the thread having exited uncleanly. If exit was unclean, - * then we don't have a return value to give back to the caller. */ - GIT_ASSERT(exit == CLEAN_THREAD_EXIT); - - if (value_ptr) - *value_ptr = thread->result; - - CloseHandle(thread->thread); - return 0; -} - -void git_thread_exit(void *value) -{ - git_thread *thread = FlsGetValue(fls_index); - - if (thread) - thread->result = value; - - ExitThread(CLEAN_THREAD_EXIT); -} - -size_t git_thread_currentid(void) -{ - return GetCurrentThreadId(); -} - -int git_mutex_init(git_mutex *GIT_RESTRICT mutex) -{ - InitializeCriticalSection(mutex); - return 0; -} - -int git_mutex_free(git_mutex *mutex) -{ - DeleteCriticalSection(mutex); - return 0; -} - -int git_mutex_lock(git_mutex *mutex) -{ - EnterCriticalSection(mutex); - return 0; -} - -int git_mutex_unlock(git_mutex *mutex) -{ - LeaveCriticalSection(mutex); - return 0; -} - -int git_cond_init(git_cond *cond) -{ - /* This is an auto-reset event. */ - *cond = CreateEventW(NULL, FALSE, FALSE, NULL); - GIT_ASSERT(*cond); - - /* If we can't create the event, claim that the reason was out-of-memory. - * The actual reason can be fetched with GetLastError(). */ - return *cond ? 0 : ENOMEM; -} - -int git_cond_free(git_cond *cond) -{ - BOOL closed; - - if (!cond) - return EINVAL; - - closed = CloseHandle(*cond); - GIT_ASSERT(closed); - GIT_UNUSED(closed); - - *cond = NULL; - return 0; -} - -int git_cond_wait(git_cond *cond, git_mutex *mutex) -{ - int error; - DWORD wait_result; - - if (!cond || !mutex) - return EINVAL; - - /* The caller must be holding the mutex. */ - error = git_mutex_unlock(mutex); - - if (error) - return error; - - wait_result = WaitForSingleObject(*cond, INFINITE); - GIT_ASSERT(WAIT_OBJECT_0 == wait_result); - GIT_UNUSED(wait_result); - - return git_mutex_lock(mutex); -} - -int git_cond_signal(git_cond *cond) -{ - BOOL signaled; - - if (!cond) - return EINVAL; - - signaled = SetEvent(*cond); - GIT_ASSERT(signaled); - GIT_UNUSED(signaled); - - return 0; -} - -int git_rwlock_init(git_rwlock *GIT_RESTRICT lock) -{ - if (win32_srwlock_initialize) - win32_srwlock_initialize(&lock->native.srwl); - else - InitializeCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_rdlock(git_rwlock *lock) -{ - if (win32_srwlock_acquire_shared) - win32_srwlock_acquire_shared(&lock->native.srwl); - else - EnterCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_rdunlock(git_rwlock *lock) -{ - if (win32_srwlock_release_shared) - win32_srwlock_release_shared(&lock->native.srwl); - else - LeaveCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_wrlock(git_rwlock *lock) -{ - if (win32_srwlock_acquire_exclusive) - win32_srwlock_acquire_exclusive(&lock->native.srwl); - else - EnterCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_wrunlock(git_rwlock *lock) -{ - if (win32_srwlock_release_exclusive) - win32_srwlock_release_exclusive(&lock->native.srwl); - else - LeaveCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_free(git_rwlock *lock) -{ - if (!win32_srwlock_initialize) - DeleteCriticalSection(&lock->native.csec); - git__memzero(lock, sizeof(*lock)); - return 0; -} diff --git a/src/win32/thread.h b/src/win32/thread.h deleted file mode 100644 index 8305036b4..000000000 --- a/src/win32/thread.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_win32_thread_h__ -#define INCLUDE_win32_thread_h__ - -#include "common.h" - -#if defined (_MSC_VER) -# define GIT_RESTRICT __restrict -#else -# define GIT_RESTRICT __restrict__ -#endif - -typedef struct { - HANDLE thread; - void *(*proc)(void *); - void *param; - void *result; -} git_thread; - -typedef CRITICAL_SECTION git_mutex; -typedef HANDLE git_cond; - -typedef struct { void *Ptr; } GIT_SRWLOCK; - -typedef struct { - union { - GIT_SRWLOCK srwl; - CRITICAL_SECTION csec; - } native; -} git_rwlock; - -int git_threads_global_init(void); - -int git_thread_create(git_thread *GIT_RESTRICT, - void *(*) (void *), - void *GIT_RESTRICT); -int git_thread_join(git_thread *, void **); -size_t git_thread_currentid(void); -void git_thread_exit(void *); - -int git_mutex_init(git_mutex *GIT_RESTRICT mutex); -int git_mutex_free(git_mutex *); -int git_mutex_lock(git_mutex *); -int git_mutex_unlock(git_mutex *); - -int git_cond_init(git_cond *); -int git_cond_free(git_cond *); -int git_cond_wait(git_cond *, git_mutex *); -int git_cond_signal(git_cond *); - -int git_rwlock_init(git_rwlock *GIT_RESTRICT lock); -int git_rwlock_rdlock(git_rwlock *); -int git_rwlock_rdunlock(git_rwlock *); -int git_rwlock_wrlock(git_rwlock *); -int git_rwlock_wrunlock(git_rwlock *); -int git_rwlock_free(git_rwlock *); - -#endif diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c deleted file mode 100644 index 4bde3023a..000000000 --- a/src/win32/utf-conv.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "utf-conv.h" - -GIT_INLINE(void) git__set_errno(void) -{ - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) - errno = ENAMETOOLONG; - else - errno = EINVAL; -} - -/** - * Converts a UTF-8 string to wide characters. - * - * @param dest The buffer to receive the wide string. - * @param dest_size The size of the buffer, in characters. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src) -{ - int len; - - /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to - * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's - * length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */ - if ((len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1) < 0) - git__set_errno(); - - return len; -} - -/** - * Converts a wide string to UTF-8. - * - * @param dest The buffer to receive the UTF-8 string. - * @param dest_size The size of the buffer, in bytes. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src) -{ - int len; - - /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to - * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's - * length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */ - if ((len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0) - git__set_errno(); - - return len; -} - -/** - * Converts a UTF-8 string to wide characters. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to the wide string. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -int git__utf8_to_16_alloc(wchar_t **dest, const char *src) -{ - int utf16_size; - - *dest = NULL; - - /* Length of -1 indicates NULL termination of the input string */ - utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0); - - if (!utf16_size) { - git__set_errno(); - return -1; - } - - if (!(*dest = git__mallocarray(utf16_size, sizeof(wchar_t)))) { - errno = ENOMEM; - return -1; - } - - utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size); - - if (!utf16_size) { - git__set_errno(); - - git__free(*dest); - *dest = NULL; - } - - /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL - * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, - * so underflow is not possible */ - return utf16_size - 1; -} - -/** - * Converts a wide string to UTF-8. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to the UTF-8 string. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8_alloc(char **dest, const wchar_t *src) -{ - int utf8_size; - - *dest = NULL; - - /* Length of -1 indicates NULL termination of the input string */ - utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL); - - if (!utf8_size) { - git__set_errno(); - return -1; - } - - *dest = git__malloc(utf8_size); - - if (!*dest) { - errno = ENOMEM; - return -1; - } - - utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, *dest, utf8_size, NULL, NULL); - - if (!utf8_size) { - git__set_errno(); - - git__free(*dest); - *dest = NULL; - } - - /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL - * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, - * so underflow is not possible */ - return utf8_size - 1; -} diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h deleted file mode 100644 index 6090a4b35..000000000 --- a/src/win32/utf-conv.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_utf_conv_h__ -#define INCLUDE_win32_utf_conv_h__ - -#include "common.h" - -#include - -#ifndef WC_ERR_INVALID_CHARS -# define WC_ERR_INVALID_CHARS 0x80 -#endif - -/** - * Converts a UTF-8 string to wide characters. - * - * @param dest The buffer to receive the wide string. - * @param dest_size The size of the buffer, in characters. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src); - -/** - * Converts a wide string to UTF-8. - * - * @param dest The buffer to receive the UTF-8 string. - * @param dest_size The size of the buffer, in bytes. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src); - -/** - * Converts a UTF-8 string to wide characters. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to the wide string. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -int git__utf8_to_16_alloc(wchar_t **dest, const char *src); - -/** - * Converts a wide string to UTF-8. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to the UTF-8 string. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8_alloc(char **dest, const wchar_t *src); - -#endif diff --git a/src/win32/version.h b/src/win32/version.h deleted file mode 100644 index 79667697f..000000000 --- a/src/win32/version.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_version_h__ -#define INCLUDE_win32_version_h__ - -#include - -GIT_INLINE(int) git_has_win32_version(int major, int minor, int service_pack) -{ - OSVERSIONINFOEX version_test = {0}; - DWORD version_test_mask; - DWORDLONG version_condition_mask = 0; - - version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - version_test.dwMajorVersion = major; - version_test.dwMinorVersion = minor; - version_test.wServicePackMajor = (WORD)service_pack; - version_test.wServicePackMinor = 0; - - version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR); - - VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); - - if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask)) - return 0; - - return 1; -} - -#endif diff --git a/src/win32/w32_buffer.c b/src/win32/w32_buffer.c deleted file mode 100644 index 6fee8203c..000000000 --- a/src/win32/w32_buffer.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "w32_buffer.h" - -#include "utf-conv.h" - -GIT_INLINE(int) handle_wc_error(void) -{ - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) - errno = ENAMETOOLONG; - else - errno = EINVAL; - - return -1; -} - -int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w) -{ - int utf8_len, utf8_write_len; - size_t new_size; - - if (!len_w) { - return 0; - } else if (len_w > INT_MAX) { - git_error_set_oom(); - return -1; - } - - GIT_ASSERT(string_w); - - /* Measure the string necessary for conversion */ - if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, NULL, 0, NULL, NULL)) == 0) - return 0; - - GIT_ASSERT(utf8_len > 0); - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, (size_t)utf8_len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - - if (git_str_grow(buf, new_size) < 0) - return -1; - - if ((utf8_write_len = WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, &buf->ptr[buf->size], utf8_len, NULL, NULL)) == 0) - return handle_wc_error(); - - GIT_ASSERT(utf8_write_len == utf8_len); - - buf->size += utf8_write_len; - buf->ptr[buf->size] = '\0'; - return 0; -} diff --git a/src/win32/w32_buffer.h b/src/win32/w32_buffer.h deleted file mode 100644 index 4227296d8..000000000 --- a/src/win32/w32_buffer.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_w32_buffer_h__ -#define INCLUDE_win32_w32_buffer_h__ - -#include "common.h" -#include "str.h" - -/** - * Convert a wide character string to UTF-8 and append the results to the - * buffer. - */ -int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w); - -#endif diff --git a/src/win32/w32_common.h b/src/win32/w32_common.h deleted file mode 100644 index c20b3e85e..000000000 --- a/src/win32/w32_common.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_win32_w32_common_h__ -#define INCLUDE_win32_w32_common_h__ - -#include - -/* - * 4096 is the max allowed Git path. `MAX_PATH` (260) is the typical max allowed - * Windows path length, however win32 Unicode APIs generally allow up to 32,767 - * if prefixed with "\\?\" (i.e. converted to an NT-style name). - */ -#define GIT_WIN_PATH_MAX GIT_PATH_MAX - -/* - * Provides a large enough buffer to support Windows Git paths: - * GIT_WIN_PATH_MAX is 4096, corresponding to a maximum path length of 4095 - * characters plus a NULL terminator. Prefixing with "\\?\" adds 4 characters, - * but if the original was a UNC path, then we turn "\\server\share" into - * "\\?\UNC\server\share". So we replace the first two characters with - * 8 characters, a net gain of 6, so the maximum length is GIT_WIN_PATH_MAX+6. - */ -#define GIT_WIN_PATH_UTF16 GIT_WIN_PATH_MAX+6 - -/* Maximum size of a UTF-8 Win32 Git path. We remove the "\\?\" or "\\?\UNC\" - * prefixes for presentation, bringing us back to 4095 (non-NULL) - * characters. UTF-8 does have 4-byte sequences, but they are encoded in - * UTF-16 using surrogate pairs, which takes up the space of two characters. - * Two characters in the range U+0800 -> U+FFFF take up more space in UTF-8 - * (6 bytes) than one surrogate pair (4 bytes). - */ -#define GIT_WIN_PATH_UTF8 ((GIT_WIN_PATH_MAX - 1) * 3 + 1) - -/* - * The length of a Windows "shortname", for 8.3 compatibility. - */ -#define GIT_WIN_PATH_SHORTNAME 13 - -/* Win32 path types */ -typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16]; -typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8]; - -#endif diff --git a/src/win32/w32_leakcheck.c b/src/win32/w32_leakcheck.c deleted file mode 100644 index 0f095de12..000000000 --- a/src/win32/w32_leakcheck.c +++ /dev/null @@ -1,581 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "w32_leakcheck.h" - -#if defined(GIT_WIN32_LEAKCHECK) - -#include "Windows.h" -#include "Dbghelp.h" -#include "win32/posix.h" -#include "hash.h" -#include "runtime.h" - -/* Stack frames (for stack tracing, below) */ - -static bool g_win32_stack_initialized = false; -static HANDLE g_win32_stack_process = INVALID_HANDLE_VALUE; -static git_win32_leakcheck_stack_aux_cb_alloc g_aux_cb_alloc = NULL; -static git_win32_leakcheck_stack_aux_cb_lookup g_aux_cb_lookup = NULL; - -int git_win32_leakcheck_stack_set_aux_cb( - git_win32_leakcheck_stack_aux_cb_alloc cb_alloc, - git_win32_leakcheck_stack_aux_cb_lookup cb_lookup) -{ - g_aux_cb_alloc = cb_alloc; - g_aux_cb_lookup = cb_lookup; - - return 0; -} - -/** - * Load symbol table data. This should be done in the primary - * thread at startup (under a lock if there are other threads - * active). - */ -void git_win32_leakcheck_stack_init(void) -{ - if (!g_win32_stack_initialized) { - g_win32_stack_process = GetCurrentProcess(); - SymSetOptions(SYMOPT_LOAD_LINES); - SymInitialize(g_win32_stack_process, NULL, TRUE); - g_win32_stack_initialized = true; - } -} - -/** - * Cleanup symbol table data. This should be done in the - * primary thead at shutdown (under a lock if there are other - * threads active). - */ -void git_win32_leakcheck_stack_cleanup(void) -{ - if (g_win32_stack_initialized) { - SymCleanup(g_win32_stack_process); - g_win32_stack_process = INVALID_HANDLE_VALUE; - g_win32_stack_initialized = false; - } -} - -int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip) -{ - if (!g_win32_stack_initialized) { - git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); - return GIT_ERROR; - } - - memset(pdata, 0, sizeof(*pdata)); - pdata->nr_frames = RtlCaptureStackBackTrace( - skip+1, GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES, pdata->frames, NULL); - - /* If an "aux" data provider was registered, ask it to capture - * whatever data it needs and give us an "aux_id" to it so that - * we can refer to it later when reporting. - */ - if (g_aux_cb_alloc) - (g_aux_cb_alloc)(&pdata->aux_id); - - return 0; -} - -int git_win32_leakcheck_stack_compare( - git_win32_leakcheck_stack_raw_data *d1, - git_win32_leakcheck_stack_raw_data *d2) -{ - return memcmp(d1, d2, sizeof(*d1)); -} - -int git_win32_leakcheck_stack_format( - char *pbuf, size_t buf_len, - const git_win32_leakcheck_stack_raw_data *pdata, - const char *prefix, const char *suffix) -{ -#define MY_MAX_FILENAME 255 - - /* SYMBOL_INFO has char FileName[1] at the end. The docs say to - * to malloc it with extra space for your desired max filename. - */ - struct { - SYMBOL_INFO symbol; - char extra[MY_MAX_FILENAME + 1]; - } s; - - IMAGEHLP_LINE64 line; - size_t buf_used = 0; - unsigned int k; - char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */ - size_t detail_len; - - if (!g_win32_stack_initialized) { - git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); - return GIT_ERROR; - } - - if (!prefix) - prefix = "\t"; - if (!suffix) - suffix = "\n"; - - memset(pbuf, 0, buf_len); - - memset(&s, 0, sizeof(s)); - s.symbol.MaxNameLen = MY_MAX_FILENAME; - s.symbol.SizeOfStruct = sizeof(SYMBOL_INFO); - - memset(&line, 0, sizeof(line)); - line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); - - for (k=0; k < pdata->nr_frames; k++) { - DWORD64 frame_k = (DWORD64)pdata->frames[k]; - DWORD dwUnused; - - if (SymFromAddr(g_win32_stack_process, frame_k, 0, &s.symbol) && - SymGetLineFromAddr64(g_win32_stack_process, frame_k, &dwUnused, &line)) { - const char *pslash; - const char *pfile; - - pslash = strrchr(line.FileName, '\\'); - pfile = ((pslash) ? (pslash+1) : line.FileName); - p_snprintf(detail, sizeof(detail), "%s%s:%d> %s%s", - prefix, pfile, line.LineNumber, s.symbol.Name, suffix); - } else { - /* This happens when we cross into another module. - * For example, in CLAR tests, this is typically - * the CRT startup code. Just print an unknown - * frame and continue. - */ - p_snprintf(detail, sizeof(detail), "%s??%s", prefix, suffix); - } - detail_len = strlen(detail); - - if (buf_len < (buf_used + detail_len + 1)) { - /* we don't have room for this frame in the buffer, so just stop. */ - break; - } - - memcpy(&pbuf[buf_used], detail, detail_len); - buf_used += detail_len; - } - - /* "aux_id" 0 is reserved to mean no aux data. This is needed to handle - * allocs that occur before the aux callbacks were registered. - */ - if (pdata->aux_id > 0) { - p_snprintf(detail, sizeof(detail), "%saux_id: %d%s", - prefix, pdata->aux_id, suffix); - detail_len = strlen(detail); - if ((buf_used + detail_len + 1) < buf_len) { - memcpy(&pbuf[buf_used], detail, detail_len); - buf_used += detail_len; - } - - /* If an "aux" data provider is still registered, ask it to append its detailed - * data to the end of ours using the "aux_id" it gave us when this de-duped - * item was created. - */ - if (g_aux_cb_lookup) - (g_aux_cb_lookup)(pdata->aux_id, &pbuf[buf_used], (buf_len - buf_used - 1)); - } - - return GIT_OK; -} - -int git_win32_leakcheck_stack( - char * pbuf, size_t buf_len, - int skip, - const char *prefix, const char *suffix) -{ - git_win32_leakcheck_stack_raw_data data; - int error; - - if ((error = git_win32_leakcheck_stack_capture(&data, skip)) < 0) - return error; - if ((error = git_win32_leakcheck_stack_format(pbuf, buf_len, &data, prefix, suffix)) < 0) - return error; - return 0; -} - -/* Stack tracing */ - -#define STACKTRACE_UID_LEN (15) - -/** - * The stacktrace of an allocation can be distilled - * to a unique id based upon the stackframe pointers - * and ignoring any size arguments. We will use these - * UIDs as the (char const*) __FILE__ argument we - * give to the CRT malloc routines. - */ -typedef struct { - char uid[STACKTRACE_UID_LEN + 1]; -} git_win32_leakcheck_stacktrace_uid; - -/** - * All mallocs with the same stacktrace will be de-duped - * and aggregated into this row. - */ -typedef struct { - git_win32_leakcheck_stacktrace_uid uid; /* must be first */ - git_win32_leakcheck_stack_raw_data raw_data; - unsigned int count_allocs; /* times this alloc signature seen since init */ - unsigned int count_allocs_at_last_checkpoint; /* times since last mark */ - unsigned int transient_count_leaks; /* sum of leaks */ -} git_win32_leakcheck_stacktrace_row; - -static CRITICAL_SECTION g_crtdbg_stacktrace_cs; - -/** - * CRTDBG memory leak tracking takes a "char const * const file_name" - * and stores the pointer in the heap data (instead of allocing a copy - * for itself). Normally, this is not a problem, since we usually pass - * in __FILE__. But I'm going to lie to it and pass in the address of - * the UID in place of the file_name. Also, I do not want to alloc the - * stacktrace data (because we are called from inside our alloc routines). - * Therefore, I'm creating a very large static pool array to store row - * data. This also eliminates the temptation to realloc it (and move the - * UID pointers). - * - * And to efficiently look for duplicates we need an index on the rows - * so we can bsearch it. Again, without mallocing. - * - * If we observe more than MY_ROW_LIMIT unique malloc signatures, we - * fall through and use the traditional __FILE__ processing and don't - * try to de-dup them. If your testing hits this limit, just increase - * it and try again. - */ - -#define MY_ROW_LIMIT (2 * 1024 * 1024) -static git_win32_leakcheck_stacktrace_row g_cs_rows[MY_ROW_LIMIT]; -static git_win32_leakcheck_stacktrace_row *g_cs_index[MY_ROW_LIMIT]; - -static unsigned int g_cs_end = MY_ROW_LIMIT; -static unsigned int g_cs_ins = 0; /* insertion point == unique allocs seen */ -static unsigned int g_count_total_allocs = 0; /* number of allocs seen */ -static unsigned int g_transient_count_total_leaks = 0; /* number of total leaks */ -static unsigned int g_transient_count_dedup_leaks = 0; /* number of unique leaks */ -static bool g_limit_reached = false; /* had allocs after we filled row table */ - -static unsigned int g_checkpoint_id = 0; /* to better label leak checkpoints */ -static bool g_transient_leaks_since_mark = false; /* payload for hook */ - -/** - * Compare function for bsearch on g_cs_index table. - */ -static int row_cmp(const void *v1, const void *v2) -{ - git_win32_leakcheck_stack_raw_data *d1 = (git_win32_leakcheck_stack_raw_data*)v1; - git_win32_leakcheck_stacktrace_row *r2 = (git_win32_leakcheck_stacktrace_row *)v2; - - return (git_win32_leakcheck_stack_compare(d1, &r2->raw_data)); -} - -/** - * Unique insert the new data into the row and index tables. - * We have to sort by the stackframe data itself, not the uid. - */ -static git_win32_leakcheck_stacktrace_row * insert_unique( - const git_win32_leakcheck_stack_raw_data *pdata) -{ - size_t pos; - if (git__bsearch(g_cs_index, g_cs_ins, pdata, row_cmp, &pos) < 0) { - /* Append new unique item to row table. */ - memcpy(&g_cs_rows[g_cs_ins].raw_data, pdata, sizeof(*pdata)); - sprintf(g_cs_rows[g_cs_ins].uid.uid, "##%08lx", g_cs_ins); - - /* Insert pointer to it into the proper place in the index table. */ - if (pos < g_cs_ins) - memmove(&g_cs_index[pos+1], &g_cs_index[pos], (g_cs_ins - pos)*sizeof(g_cs_index[0])); - g_cs_index[pos] = &g_cs_rows[g_cs_ins]; - - g_cs_ins++; - } - - g_cs_index[pos]->count_allocs++; - - return g_cs_index[pos]; -} - -/** - * Hook function to receive leak data from the CRT. (This includes - * both ":()" data, but also each of the - * various headers and fields. - * - * Scan this for the special "##" UID forms that we substituted - * for the "". Map back to the row data and - * increment its leak count. - * - * See https://msdn.microsoft.com/en-us/library/74kabxyx.aspx - * - * We suppress the actual crtdbg output. - */ -static int __cdecl report_hook(int nRptType, char *szMsg, int *retVal) -{ - static int hook_result = TRUE; /* FALSE to get stock dump; TRUE to suppress. */ - unsigned int pos; - - *retVal = 0; /* do not invoke debugger */ - - if ((szMsg[0] != '#') || (szMsg[1] != '#')) - return hook_result; - - if (sscanf(&szMsg[2], "%08lx", &pos) < 1) - return hook_result; - if (pos >= g_cs_ins) - return hook_result; - - if (g_transient_leaks_since_mark) { - if (g_cs_rows[pos].count_allocs == g_cs_rows[pos].count_allocs_at_last_checkpoint) - return hook_result; - } - - g_cs_rows[pos].transient_count_leaks++; - - if (g_cs_rows[pos].transient_count_leaks == 1) - g_transient_count_dedup_leaks++; - - g_transient_count_total_leaks++; - - return hook_result; -} - -/** - * Write leak data to all of the various places we need. - * We force the caller to sprintf() the message first - * because we want to avoid fprintf() because it allocs. - */ -static void my_output(const char *buf) -{ - fwrite(buf, strlen(buf), 1, stderr); - OutputDebugString(buf); -} - -/** - * For each row with leaks, dump a stacktrace for it. - */ -static void dump_summary(const char *label) -{ - unsigned int k; - char buf[10 * 1024]; - - if (g_transient_count_total_leaks == 0) - return; - - fflush(stdout); - fflush(stderr); - my_output("\n"); - - if (g_limit_reached) { - sprintf(buf, - "LEAK SUMMARY: de-dup row table[%d] filled. Increase MY_ROW_LIMIT.\n", - MY_ROW_LIMIT); - my_output(buf); - } - - if (!label) - label = ""; - - if (g_transient_leaks_since_mark) { - sprintf(buf, "LEAK CHECKPOINT %d: leaks %d unique %d: %s\n", - g_checkpoint_id, g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); - my_output(buf); - } else { - sprintf(buf, "LEAK SUMMARY: TOTAL leaks %d de-duped %d: %s\n", - g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); - my_output(buf); - } - my_output("\n"); - - for (k = 0; k < g_cs_ins; k++) { - if (g_cs_rows[k].transient_count_leaks > 0) { - sprintf(buf, "LEAK: %s leaked %d of %d times:\n", - g_cs_rows[k].uid.uid, - g_cs_rows[k].transient_count_leaks, - g_cs_rows[k].count_allocs); - my_output(buf); - - if (git_win32_leakcheck_stack_format( - buf, sizeof(buf), &g_cs_rows[k].raw_data, - NULL, NULL) >= 0) { - my_output(buf); - } - - my_output("\n"); - } - } - - fflush(stderr); -} - -/** - * Initialize our memory leak tracking and de-dup data structures. - * This should ONLY be called by git_libgit2_init(). - */ -void git_win32_leakcheck_stacktrace_init(void) -{ - InitializeCriticalSection(&g_crtdbg_stacktrace_cs); - - EnterCriticalSection(&g_crtdbg_stacktrace_cs); - - _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); - - _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); - - _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); - _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); - _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); - - LeaveCriticalSection(&g_crtdbg_stacktrace_cs); -} - -int git_win32_leakcheck_stacktrace_dump( - git_win32_leakcheck_stacktrace_options opt, - const char *label) -{ - _CRT_REPORT_HOOK old; - unsigned int k; - int r = 0; - -#define IS_BIT_SET(o,b) (((o) & (b)) != 0) - - bool b_set_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK); - bool b_leaks_since_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK); - bool b_leaks_total = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL); - bool b_quiet = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET); - - if (b_leaks_since_mark && b_leaks_total) { - git_error_set(GIT_ERROR_INVALID, "cannot combine LEAKS_SINCE_MARK and LEAKS_TOTAL."); - return GIT_ERROR; - } - if (!b_set_mark && !b_leaks_since_mark && !b_leaks_total) { - git_error_set(GIT_ERROR_INVALID, "nothing to do."); - return GIT_ERROR; - } - - EnterCriticalSection(&g_crtdbg_stacktrace_cs); - - if (b_leaks_since_mark || b_leaks_total) { - /* All variables with "transient" in the name are per-dump counters - * and reset before each dump. This lets us handle checkpoints. - */ - g_transient_count_total_leaks = 0; - g_transient_count_dedup_leaks = 0; - for (k = 0; k < g_cs_ins; k++) { - g_cs_rows[k].transient_count_leaks = 0; - } - } - - g_transient_leaks_since_mark = b_leaks_since_mark; - - old = _CrtSetReportHook(report_hook); - _CrtDumpMemoryLeaks(); - _CrtSetReportHook(old); - - if (b_leaks_since_mark || b_leaks_total) { - r = g_transient_count_dedup_leaks; - - if (!b_quiet) - dump_summary(label); - } - - if (b_set_mark) { - for (k = 0; k < g_cs_ins; k++) { - g_cs_rows[k].count_allocs_at_last_checkpoint = g_cs_rows[k].count_allocs; - } - - g_checkpoint_id++; - } - - LeaveCriticalSection(&g_crtdbg_stacktrace_cs); - - return r; -} - -/** - * Shutdown our memory leak tracking and dump summary data. - * This should ONLY be called by git_libgit2_shutdown(). - * - * We explicitly call _CrtDumpMemoryLeaks() during here so - * that we can compute summary data for the leaks. We print - * the stacktrace of each unique leak. - * - * This cleanup does not happen if the app calls exit() - * without calling the libgit2 shutdown code. - * - * This info we print here is independent of any automatic - * reporting during exit() caused by _CRTDBG_LEAK_CHECK_DF. - * Set it in your app if you also want traditional reporting. - */ -void git_win32_leakcheck_stacktrace_cleanup(void) -{ - /* At shutdown/cleanup, dump cumulative leak info - * with everything since startup. This might generate - * extra noise if the caller has been doing checkpoint - * dumps, but it might also eliminate some false - * positives for resources previously reported during - * checkpoints. - */ - git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL, - "CLEANUP"); - - DeleteCriticalSection(&g_crtdbg_stacktrace_cs); -} - -const char *git_win32_leakcheck_stacktrace(int skip, const char *file) -{ - git_win32_leakcheck_stack_raw_data new_data; - git_win32_leakcheck_stacktrace_row *row; - const char * result = file; - - if (git_win32_leakcheck_stack_capture(&new_data, skip+1) < 0) - return result; - - EnterCriticalSection(&g_crtdbg_stacktrace_cs); - - if (g_cs_ins < g_cs_end) { - row = insert_unique(&new_data); - result = row->uid.uid; - } else { - g_limit_reached = true; - } - - g_count_total_allocs++; - - LeaveCriticalSection(&g_crtdbg_stacktrace_cs); - - return result; -} - -static void git_win32_leakcheck_global_shutdown(void) -{ - git_win32_leakcheck_stacktrace_cleanup(); - git_win32_leakcheck_stack_cleanup(); -} - -bool git_win32_leakcheck_has_leaks(void) -{ - return (g_transient_count_total_leaks > 0); -} - -int git_win32_leakcheck_global_init(void) -{ - git_win32_leakcheck_stacktrace_init(); - git_win32_leakcheck_stack_init(); - - return git_runtime_shutdown_register(git_win32_leakcheck_global_shutdown); -} - -#else - -int git_win32_leakcheck_global_init(void) -{ - return 0; -} - -#endif diff --git a/src/win32/w32_leakcheck.h b/src/win32/w32_leakcheck.h deleted file mode 100644 index cb45e3675..000000000 --- a/src/win32/w32_leakcheck.h +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_win32_leakcheck_h__ -#define INCLUDE_win32_leakcheck_h__ - -#include "common.h" - -/* Initialize the win32 leak checking system. */ -int git_win32_leakcheck_global_init(void); - -#if defined(GIT_WIN32_LEAKCHECK) - -#include -#include - -#include "git2/errors.h" -#include "strnlen.h" - -bool git_win32_leakcheck_has_leaks(void); - -/* Stack frames (for stack tracing, below) */ - -/** - * This type defines a callback to be used to augment a C stacktrace - * with "aux" data. This can be used, for example, to allow LibGit2Sharp - * (or other interpreted consumer libraries) to give us C# stacktrace - * data for the PInvoke. - * - * This callback will be called during crtdbg-instrumented allocs. - * - * @param aux_id [out] A returned "aux_id" representing a unique - * (de-duped at the C# layer) stacktrace. "aux_id" 0 is reserved - * to mean no aux stacktrace data. - */ -typedef void (*git_win32_leakcheck_stack_aux_cb_alloc)(unsigned int *aux_id); - -/** - * This type defines a callback to be used to augment the output of - * a stacktrace. This will be used to request the C# layer format - * the C# stacktrace associated with "aux_id" into the provided - * buffer. - * - * This callback will be called during leak reporting. - * - * @param aux_id The "aux_id" key associated with a stacktrace. - * @param aux_msg A buffer where a formatted message should be written. - * @param aux_msg_len The size of the buffer. - */ -typedef void (*git_win32_leakcheck_stack_aux_cb_lookup)(unsigned int aux_id, char *aux_msg, size_t aux_msg_len); - -/** - * Register an "aux" data provider to augment our C stacktrace data. - * - * This can be used, for example, to allow LibGit2Sharp (or other - * interpreted consumer libraries) to give us the C# stacktrace of - * the PInvoke. - * - * If you choose to use this feature, it should be registered during - * initialization and not changed for the duration of the process. - */ -int git_win32_leakcheck_stack_set_aux_cb( - git_win32_leakcheck_stack_aux_cb_alloc cb_alloc, - git_win32_leakcheck_stack_aux_cb_lookup cb_lookup); - -/** - * Maximum number of stackframes to record for a - * single stacktrace. - */ -#define GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES 30 - -/** - * Wrapper containing the raw unprocessed stackframe - * data for a single stacktrace and any "aux_id". - * - * I put the aux_id first so leaks will be sorted by it. - * So, for example, if a specific callstack in C# leaks - * a repo handle, all of the pointers within the associated - * repo pointer will be grouped together. - */ -typedef struct { - unsigned int aux_id; - unsigned int nr_frames; - void *frames[GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES]; -} git_win32_leakcheck_stack_raw_data; - -/** - * Capture raw stack trace data for the current process/thread. - * - * @param skip Number of initial frames to skip. Pass 0 to - * begin with the caller of this routine. Pass 1 to begin - * with its caller. And so on. - */ -int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip); - -/** - * Compare 2 raw stacktraces with the usual -1,0,+1 result. - * This includes any "aux_id" values in the comparison, so that - * our de-dup is also "aux" context relative. - */ -int git_win32_leakcheck_stack_compare( - git_win32_leakcheck_stack_raw_data *d1, - git_win32_leakcheck_stack_raw_data *d2); - -/** - * Format raw stacktrace data into buffer WITHOUT using any mallocs. - * - * @param prefix String written before each frame; defaults to "\t". - * @param suffix String written after each frame; defaults to "\n". - */ -int git_win32_leakcheck_stack_format( - char *pbuf, size_t buf_len, - const git_win32_leakcheck_stack_raw_data *pdata, - const char *prefix, const char *suffix); - -/** - * Convenience routine to capture and format stacktrace into - * a buffer WITHOUT using any mallocs. This is primarily a - * wrapper for testing. - * - * @param skip Number of initial frames to skip. Pass 0 to - * begin with the caller of this routine. Pass 1 to begin - * with its caller. And so on. - * @param prefix String written before each frame; defaults to "\t". - * @param suffix String written after each frame; defaults to "\n". - */ -int git_win32_leakcheck_stack( - char * pbuf, size_t buf_len, - int skip, - const char *prefix, const char *suffix); - -/* Stack tracing */ - -/* MSVC CRTDBG memory leak reporting. - * - * We DO NOT use the "_CRTDBG_MAP_ALLOC" macro described in the MSVC - * documentation because all allocs/frees in libgit2 already go through - * the "git__" routines defined in this file. Simply using the normal - * reporting mechanism causes all leaks to be attributed to a routine - * here in util.h (ie, the actual call to calloc()) rather than the - * caller of git__calloc(). - * - * Therefore, we declare a set of "git__crtdbg__" routines to replace - * the corresponding "git__" routines and re-define the "git__" symbols - * as macros. This allows us to get and report the file:line info of - * the real caller. - * - * We DO NOT replace the "git__free" routine because it needs to remain - * a function pointer because it is used as a function argument when - * setting up various structure "destructors". - * - * We also DO NOT use the "_CRTDBG_MAP_ALLOC" macro because it causes - * "free" to be remapped to "_free_dbg" and this causes problems for - * structures which define a field named "free". - * - * Finally, CRTDBG must be explicitly enabled and configured at program - * startup. See tests/main.c for an example. - */ - -/** - * Checkpoint options. - */ -typedef enum git_win32_leakcheck_stacktrace_options { - /** - * Set checkpoint marker. - */ - GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK = (1 << 0), - - /** - * Dump leaks since last checkpoint marker. - * May not be combined with _LEAKS_TOTAL. - * - * Note that this may generate false positives for global TLS - * error state and other global caches that aren't cleaned up - * until the thread/process terminates. So when using this - * around a region of interest, also check the final (at exit) - * dump before digging into leaks reported here. - */ - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK = (1 << 1), - - /** - * Dump leaks since init. May not be combined - * with _LEAKS_SINCE_MARK. - */ - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL = (1 << 2), - - /** - * Suppress printing during dumps. - * Just return leak count. - */ - GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET = (1 << 3), - -} git_win32_leakcheck_stacktrace_options; - -/** - * Checkpoint memory state and/or dump unique stack traces of - * current memory leaks. - * - * @return number of unique leaks (relative to requested starting - * point) or error. - */ -int git_win32_leakcheck_stacktrace_dump( - git_win32_leakcheck_stacktrace_options opt, - const char *label); - -/** - * Construct stacktrace and append it to the global buffer. - * Return pointer to start of this string. On any error or - * lack of buffer space, just return the given file buffer - * so it will behave as usual. - * - * This should ONLY be called by our internal memory allocations - * routines. - */ -const char *git_win32_leakcheck_stacktrace(int skip, const char *file); - -#endif -#endif diff --git a/src/win32/w32_util.c b/src/win32/w32_util.c deleted file mode 100644 index fe4b75bae..000000000 --- a/src/win32/w32_util.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "w32_util.h" - -/** - * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. - * The filter string enumerates all items in the directory. - * - * @param dest The buffer to receive the filter string. - * @param src The UTF-8 path of the directory to enumerate. - * @return True if the filter string was created successfully; false otherwise - */ -bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src) -{ - static const wchar_t suffix[] = L"\\*"; - int len = git_win32_path_from_utf8(dest, src); - - /* Ensure the path was converted */ - if (len < 0) - return false; - - /* Ensure that the path does not end with a trailing slash, - * because we're about to add one. Don't rely our trim_end - * helper, because we want to remove the backslash even for - * drive letter paths, in this case. */ - if (len > 0 && - (dest[len - 1] == L'/' || dest[len - 1] == L'\\')) { - dest[len - 1] = L'\0'; - len--; - } - - /* Ensure we have enough room to add the suffix */ - if ((size_t)len >= GIT_WIN_PATH_UTF16 - CONST_STRLEN(suffix)) - return false; - - wcscat(dest, suffix); - return true; -} - -/** - * Ensures the given path (file or folder) has the +H (hidden) attribute set. - * - * @param path The path which should receive the +H bit. - * @return 0 on success; -1 on failure - */ -int git_win32__set_hidden(const char *path, bool hidden) -{ - git_win32_path buf; - DWORD attrs, newattrs; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - attrs = GetFileAttributesW(buf); - - /* Ensure the path exists */ - if (attrs == INVALID_FILE_ATTRIBUTES) - return -1; - - if (hidden) - newattrs = attrs | FILE_ATTRIBUTE_HIDDEN; - else - newattrs = attrs & ~FILE_ATTRIBUTE_HIDDEN; - - if (attrs != newattrs && !SetFileAttributesW(buf, newattrs)) { - git_error_set(GIT_ERROR_OS, "failed to %s hidden bit for '%s'", - hidden ? "set" : "unset", path); - return -1; - } - - return 0; -} - -int git_win32__hidden(bool *out, const char *path) -{ - git_win32_path buf; - DWORD attrs; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - attrs = GetFileAttributesW(buf); - - /* Ensure the path exists */ - if (attrs == INVALID_FILE_ATTRIBUTES) - return -1; - - *out = (attrs & FILE_ATTRIBUTE_HIDDEN) ? true : false; - return 0; -} - -int git_win32__file_attribute_to_stat( - struct stat *st, - const WIN32_FILE_ATTRIBUTE_DATA *attrdata, - const wchar_t *path) -{ - git_win32__stat_init(st, - attrdata->dwFileAttributes, - attrdata->nFileSizeHigh, - attrdata->nFileSizeLow, - attrdata->ftCreationTime, - attrdata->ftLastAccessTime, - attrdata->ftLastWriteTime); - - if (attrdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && path) { - git_win32_path target; - - if (git_win32_path_readlink_w(target, path) >= 0) { - st->st_mode = (st->st_mode & ~S_IFMT) | S_IFLNK; - - /* st_size gets the UTF-8 length of the target name, in bytes, - * not counting the NULL terminator */ - if ((st->st_size = git__utf16_to_8(NULL, 0, target)) < 0) { - git_error_set(GIT_ERROR_OS, "could not convert reparse point name for '%ls'", path); - return -1; - } - } - } - - return 0; -} diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h deleted file mode 100644 index 1321d30e6..000000000 --- a/src/win32/w32_util.h +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_win32_w32_util_h__ -#define INCLUDE_win32_w32_util_h__ - -#include "common.h" - -#include "utf-conv.h" -#include "posix.h" -#include "path_w32.h" - -/* - -#include "common.h" -#include "path.h" -#include "path_w32.h" -#include "utf-conv.h" -#include "posix.h" -#include "reparse.h" -#include "dir.h" -*/ - - -GIT_INLINE(bool) git_win32__isalpha(wchar_t c) -{ - return ((c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z')); -} - -/** - * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. - * The filter string enumerates all items in the directory. - * - * @param dest The buffer to receive the filter string. - * @param src The UTF-8 path of the directory to enumerate. - * @return True if the filter string was created successfully; false otherwise - */ -bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src); - -/** - * Ensures the given path (file or folder) has the +H (hidden) attribute set - * or unset. - * - * @param path The path that should receive the +H bit. - * @param hidden true to set +H, false to unset it - * @return 0 on success; -1 on failure - */ -extern int git_win32__set_hidden(const char *path, bool hidden); - -/** - * Determines if the given file or folder has the hidden attribute set. - * @param hidden pointer to store hidden value - * @param path The path that should be queried for hiddenness. - * @return 0 on success or an error code. - */ -extern int git_win32__hidden(bool *hidden, const char *path); - -extern int git_win32__file_attribute_to_stat( - struct stat *st, - const WIN32_FILE_ATTRIBUTE_DATA *attrdata, - const wchar_t *path); - -/** - * Converts a FILETIME structure to a struct timespec. - * - * @param FILETIME A pointer to a FILETIME - * @param ts A pointer to the timespec structure to fill in - */ -GIT_INLINE(void) git_win32__filetime_to_timespec( - const FILETIME *ft, - struct timespec *ts) -{ - int64_t winTime = ((int64_t)ft->dwHighDateTime << 32) + ft->dwLowDateTime; - winTime -= INT64_C(116444736000000000); /* Windows to Unix Epoch conversion */ - ts->tv_sec = (time_t)(winTime / 10000000); -#ifdef GIT_USE_NSEC - ts->tv_nsec = (winTime % 10000000) * 100; -#else - ts->tv_nsec = 0; -#endif -} - -GIT_INLINE(void) git_win32__timeval_to_filetime( - FILETIME *ft, const struct p_timeval tv) -{ - int64_t ticks = (tv.tv_sec * INT64_C(10000000)) + - (tv.tv_usec * INT64_C(10)) + INT64_C(116444736000000000); - - ft->dwHighDateTime = ((ticks >> 32) & INT64_C(0xffffffff)); - ft->dwLowDateTime = (ticks & INT64_C(0xffffffff)); -} - -GIT_INLINE(void) git_win32__stat_init( - struct stat *st, - DWORD dwFileAttributes, - DWORD nFileSizeHigh, - DWORD nFileSizeLow, - FILETIME ftCreationTime, - FILETIME ftLastAccessTime, - FILETIME ftLastWriteTime) -{ - mode_t mode = S_IREAD; - - memset(st, 0, sizeof(struct stat)); - - if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - mode |= S_IFDIR; - else - mode |= S_IFREG; - - if ((dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) - mode |= S_IWRITE; - - st->st_ino = 0; - st->st_gid = 0; - st->st_uid = 0; - st->st_nlink = 1; - st->st_mode = mode; - st->st_size = ((int64_t)nFileSizeHigh << 32) + nFileSizeLow; - st->st_dev = _getdrive() - 1; - st->st_rdev = st->st_dev; - git_win32__filetime_to_timespec(&ftLastAccessTime, &(st->st_atim)); - git_win32__filetime_to_timespec(&ftLastWriteTime, &(st->st_mtim)); - git_win32__filetime_to_timespec(&ftCreationTime, &(st->st_ctim)); -} - -GIT_INLINE(void) git_win32__file_information_to_stat( - struct stat *st, - const BY_HANDLE_FILE_INFORMATION *fileinfo) -{ - git_win32__stat_init(st, - fileinfo->dwFileAttributes, - fileinfo->nFileSizeHigh, - fileinfo->nFileSizeLow, - fileinfo->ftCreationTime, - fileinfo->ftLastAccessTime, - fileinfo->ftLastWriteTime); -} - -#endif diff --git a/src/win32/win32-compat.h b/src/win32/win32-compat.h deleted file mode 100644 index dee40a438..000000000 --- a/src/win32/win32-compat.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_win32_compat_h__ -#define INCLUDE_win32_win32_compat_h__ - -#include -#include -#include -#include -#include - -typedef long suseconds_t; - -struct p_timeval { - time_t tv_sec; - suseconds_t tv_usec; -}; - -struct p_timespec { - time_t tv_sec; - long tv_nsec; -}; - -#define timespec p_timespec - -struct p_stat { - _dev_t st_dev; - _ino_t st_ino; - mode_t st_mode; - short st_nlink; - short st_uid; - short st_gid; - _dev_t st_rdev; - __int64 st_size; - struct timespec st_atim; - struct timespec st_mtim; - struct timespec st_ctim; -#define st_atime st_atim.tv_sec -#define st_mtime st_mtim.tv_sec -#define st_ctime st_ctim.tv_sec -#define st_atime_nsec st_atim.tv_nsec -#define st_mtime_nsec st_mtim.tv_nsec -#define st_ctime_nsec st_ctim.tv_nsec -}; - -#define stat p_stat - -#endif diff --git a/src/worktree.c b/src/worktree.c deleted file mode 100644 index 2ac2274f1..000000000 --- a/src/worktree.c +++ /dev/null @@ -1,652 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "worktree.h" - -#include "buf.h" -#include "repository.h" -#include "path.h" - -#include "git2/branch.h" -#include "git2/commit.h" -#include "git2/worktree.h" - -static bool is_worktree_dir(const char *dir) -{ - git_str buf = GIT_STR_INIT; - int error; - - if (git_str_sets(&buf, dir) < 0) - return -1; - - error = git_fs_path_contains_file(&buf, "commondir") - && git_fs_path_contains_file(&buf, "gitdir") - && git_fs_path_contains_file(&buf, "HEAD"); - - git_str_dispose(&buf); - return error; -} - -int git_worktree_list(git_strarray *wts, git_repository *repo) -{ - git_vector worktrees = GIT_VECTOR_INIT; - git_str path = GIT_STR_INIT; - char *worktree; - size_t i, len; - int error; - - GIT_ASSERT_ARG(wts); - GIT_ASSERT_ARG(repo); - - wts->count = 0; - wts->strings = NULL; - - if ((error = git_str_joinpath(&path, repo->commondir, "worktrees/")) < 0) - goto exit; - if (!git_fs_path_exists(path.ptr) || git_fs_path_is_empty_dir(path.ptr)) - goto exit; - if ((error = git_fs_path_dirload(&worktrees, path.ptr, path.size, 0x0)) < 0) - goto exit; - - len = path.size; - - git_vector_foreach(&worktrees, i, worktree) { - git_str_truncate(&path, len); - git_str_puts(&path, worktree); - - if (!is_worktree_dir(path.ptr)) { - git_vector_remove(&worktrees, i); - git__free(worktree); - } - } - - wts->strings = (char **)git_vector_detach(&wts->count, NULL, &worktrees); - -exit: - git_str_dispose(&path); - - return error; -} - -char *git_worktree__read_link(const char *base, const char *file) -{ - git_str path = GIT_STR_INIT, buf = GIT_STR_INIT; - - GIT_ASSERT_ARG_WITH_RETVAL(base, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(file, NULL); - - if (git_str_joinpath(&path, base, file) < 0) - goto err; - if (git_futils_readbuffer(&buf, path.ptr) < 0) - goto err; - git_str_dispose(&path); - - git_str_rtrim(&buf); - - if (!git_fs_path_is_relative(buf.ptr)) - return git_str_detach(&buf); - - if (git_str_sets(&path, base) < 0) - goto err; - if (git_fs_path_apply_relative(&path, buf.ptr) < 0) - goto err; - git_str_dispose(&buf); - - return git_str_detach(&path); - -err: - git_str_dispose(&buf); - git_str_dispose(&path); - - return NULL; -} - -static int write_wtfile(const char *base, const char *file, const git_str *buf) -{ - git_str path = GIT_STR_INIT; - int err; - - GIT_ASSERT_ARG(base); - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(buf); - - if ((err = git_str_joinpath(&path, base, file)) < 0) - goto out; - - if ((err = git_futils_writebuffer(buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) - goto out; - -out: - git_str_dispose(&path); - - return err; -} - -static int open_worktree_dir(git_worktree **out, const char *parent, const char *dir, const char *name) -{ - git_str gitdir = GIT_STR_INIT; - git_worktree *wt = NULL; - int error = 0; - - if (!is_worktree_dir(dir)) { - error = -1; - goto out; - } - - if ((error = git_path_validate_length(NULL, dir)) < 0) - goto out; - - if ((wt = git__calloc(1, sizeof(*wt))) == NULL) { - error = -1; - goto out; - } - - if ((wt->name = git__strdup(name)) == NULL || - (wt->commondir_path = git_worktree__read_link(dir, "commondir")) == NULL || - (wt->gitlink_path = git_worktree__read_link(dir, "gitdir")) == NULL || - (parent && (wt->parent_path = git__strdup(parent)) == NULL) || - (wt->worktree_path = git_fs_path_dirname(wt->gitlink_path)) == NULL) { - error = -1; - goto out; - } - - if ((error = git_fs_path_prettify_dir(&gitdir, dir, NULL)) < 0) - goto out; - wt->gitdir_path = git_str_detach(&gitdir); - - if ((error = git_worktree_is_locked(NULL, wt)) < 0) - goto out; - wt->locked = !!error; - error = 0; - - *out = wt; - -out: - if (error) - git_worktree_free(wt); - git_str_dispose(&gitdir); - - return error; -} - -int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name) -{ - git_str path = GIT_STR_INIT; - git_worktree *wt = NULL; - int error; - - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - - *out = NULL; - - if ((error = git_str_join3(&path, '/', repo->commondir, "worktrees", name)) < 0) - goto out; - - if ((error = (open_worktree_dir(out, git_repository_workdir(repo), path.ptr, name))) < 0) - goto out; - -out: - git_str_dispose(&path); - - if (error) - git_worktree_free(wt); - - return error; -} - -int git_worktree_open_from_repository(git_worktree **out, git_repository *repo) -{ - git_str parent = GIT_STR_INIT; - const char *gitdir, *commondir; - char *name = NULL; - int error = 0; - - if (!git_repository_is_worktree(repo)) { - git_error_set(GIT_ERROR_WORKTREE, "cannot open worktree of a non-worktree repo"); - error = -1; - goto out; - } - - gitdir = git_repository_path(repo); - commondir = git_repository_commondir(repo); - - if ((error = git_fs_path_prettify_dir(&parent, "..", commondir)) < 0) - goto out; - - /* The name is defined by the last component in '.git/worktree/%s' */ - name = git_fs_path_basename(gitdir); - - if ((error = open_worktree_dir(out, parent.ptr, gitdir, name)) < 0) - goto out; - -out: - git__free(name); - git_str_dispose(&parent); - - return error; -} - -void git_worktree_free(git_worktree *wt) -{ - if (!wt) - return; - - git__free(wt->commondir_path); - git__free(wt->worktree_path); - git__free(wt->gitlink_path); - git__free(wt->gitdir_path); - git__free(wt->parent_path); - git__free(wt->name); - git__free(wt); -} - -int git_worktree_validate(const git_worktree *wt) -{ - GIT_ASSERT_ARG(wt); - - if (!is_worktree_dir(wt->gitdir_path)) { - git_error_set(GIT_ERROR_WORKTREE, - "worktree gitdir ('%s') is not valid", - wt->gitlink_path); - return GIT_ERROR; - } - - if (wt->parent_path && !git_fs_path_exists(wt->parent_path)) { - git_error_set(GIT_ERROR_WORKTREE, - "worktree parent directory ('%s') does not exist ", - wt->parent_path); - return GIT_ERROR; - } - - if (!git_fs_path_exists(wt->commondir_path)) { - git_error_set(GIT_ERROR_WORKTREE, - "worktree common directory ('%s') does not exist ", - wt->commondir_path); - return GIT_ERROR; - } - - if (!git_fs_path_exists(wt->worktree_path)) { - git_error_set(GIT_ERROR_WORKTREE, - "worktree directory '%s' does not exist", - wt->worktree_path); - return GIT_ERROR; - } - - return 0; -} - -int git_worktree_add_options_init(git_worktree_add_options *opts, - unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, - git_worktree_add_options, GIT_WORKTREE_ADD_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_worktree_add_init_options(git_worktree_add_options *opts, - unsigned int version) -{ - return git_worktree_add_options_init(opts, version); -} -#endif - -int git_worktree_add(git_worktree **out, git_repository *repo, - const char *name, const char *worktree, - const git_worktree_add_options *opts) -{ - git_str gitdir = GIT_STR_INIT, wddir = GIT_STR_INIT, buf = GIT_STR_INIT; - git_reference *ref = NULL, *head = NULL; - git_commit *commit = NULL; - git_repository *wt = NULL; - git_checkout_options coopts; - git_worktree_add_options wtopts = GIT_WORKTREE_ADD_OPTIONS_INIT; - int err; - - GIT_ERROR_CHECK_VERSION( - opts, GIT_WORKTREE_ADD_OPTIONS_VERSION, "git_worktree_add_options"); - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(name); - GIT_ASSERT_ARG(worktree); - - *out = NULL; - - if (opts) - memcpy(&wtopts, opts, sizeof(wtopts)); - - memcpy(&coopts, &wtopts.checkout_options, sizeof(coopts)); - - if (wtopts.ref) { - if (!git_reference_is_branch(wtopts.ref)) { - git_error_set(GIT_ERROR_WORKTREE, "reference is not a branch"); - err = -1; - goto out; - } - - if (git_branch_is_checked_out(wtopts.ref)) { - git_error_set(GIT_ERROR_WORKTREE, "reference is already checked out"); - err = -1; - goto out; - } - } - - /* Create gitdir directory ".git/worktrees/" */ - if ((err = git_str_joinpath(&gitdir, repo->commondir, "worktrees")) < 0) - goto out; - if (!git_fs_path_exists(gitdir.ptr)) - if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0) - goto out; - if ((err = git_str_joinpath(&gitdir, gitdir.ptr, name)) < 0) - goto out; - if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0) - goto out; - if ((err = git_fs_path_prettify_dir(&gitdir, gitdir.ptr, NULL)) < 0) - goto out; - - /* Create worktree work dir */ - if ((err = git_futils_mkdir(worktree, 0755, GIT_MKDIR_EXCL)) < 0) - goto out; - if ((err = git_fs_path_prettify_dir(&wddir, worktree, NULL)) < 0) - goto out; - - if (wtopts.lock) { - int fd; - - if ((err = git_str_joinpath(&buf, gitdir.ptr, "locked")) < 0) - goto out; - - if ((fd = p_creat(buf.ptr, 0644)) < 0) { - err = fd; - goto out; - } - - p_close(fd); - git_str_clear(&buf); - } - - /* Create worktree .git file */ - if ((err = git_str_printf(&buf, "gitdir: %s\n", gitdir.ptr)) < 0) - goto out; - if ((err = write_wtfile(wddir.ptr, ".git", &buf)) < 0) - goto out; - - /* Create gitdir files */ - if ((err = git_fs_path_prettify_dir(&buf, repo->commondir, NULL) < 0) - || (err = git_str_putc(&buf, '\n')) < 0 - || (err = write_wtfile(gitdir.ptr, "commondir", &buf)) < 0) - goto out; - if ((err = git_str_joinpath(&buf, wddir.ptr, ".git")) < 0 - || (err = git_str_putc(&buf, '\n')) < 0 - || (err = write_wtfile(gitdir.ptr, "gitdir", &buf)) < 0) - goto out; - - /* Set up worktree reference */ - if (wtopts.ref) { - if ((err = git_reference_dup(&ref, wtopts.ref)) < 0) - goto out; - } else { - if ((err = git_repository_head(&head, repo)) < 0) - goto out; - if ((err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0) - goto out; - if ((err = git_branch_create(&ref, repo, name, commit, false)) < 0) - goto out; - } - - /* Set worktree's HEAD */ - if ((err = git_repository_create_head(gitdir.ptr, git_reference_name(ref))) < 0) - goto out; - if ((err = git_repository_open(&wt, wddir.ptr)) < 0) - goto out; - - /* Checkout worktree's HEAD */ - if ((err = git_checkout_head(wt, &coopts)) < 0) - goto out; - - /* Load result */ - if ((err = git_worktree_lookup(out, repo, name)) < 0) - goto out; - -out: - git_str_dispose(&gitdir); - git_str_dispose(&wddir); - git_str_dispose(&buf); - git_reference_free(ref); - git_reference_free(head); - git_commit_free(commit); - git_repository_free(wt); - - return err; -} - -int git_worktree_lock(git_worktree *wt, const char *reason) -{ - git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(wt); - - if ((error = git_worktree_is_locked(NULL, wt)) < 0) - goto out; - if (error) { - error = GIT_ELOCKED; - goto out; - } - - if ((error = git_str_joinpath(&path, wt->gitdir_path, "locked")) < 0) - goto out; - - if (reason) - git_str_attach_notowned(&buf, reason, strlen(reason)); - - if ((error = git_futils_writebuffer(&buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) - goto out; - - wt->locked = 1; - -out: - git_str_dispose(&path); - - return error; -} - -int git_worktree_unlock(git_worktree *wt) -{ - git_str path = GIT_STR_INIT; - int error; - - GIT_ASSERT_ARG(wt); - - if ((error = git_worktree_is_locked(NULL, wt)) < 0) - return error; - if (!error) - return 1; - - if (git_str_joinpath(&path, wt->gitdir_path, "locked") < 0) - return -1; - - if (p_unlink(path.ptr) != 0) { - git_str_dispose(&path); - return -1; - } - - wt->locked = 0; - - git_str_dispose(&path); - - return 0; -} - -static int git_worktree__is_locked(git_str *reason, const git_worktree *wt) -{ - git_str path = GIT_STR_INIT; - int error, locked; - - GIT_ASSERT_ARG(wt); - - if (reason) - git_str_clear(reason); - - if ((error = git_str_joinpath(&path, wt->gitdir_path, "locked")) < 0) - goto out; - locked = git_fs_path_exists(path.ptr); - if (locked && reason && - (error = git_futils_readbuffer(reason, path.ptr)) < 0) - goto out; - - error = locked; -out: - git_str_dispose(&path); - - return error; -} - -int git_worktree_is_locked(git_buf *reason, const git_worktree *wt) -{ - git_str str = GIT_STR_INIT; - int error = 0; - - if (reason && (error = git_buf_tostr(&str, reason)) < 0) - return error; - - error = git_worktree__is_locked(reason ? &str : NULL, wt); - - if (error >= 0 && reason) { - if (git_buf_fromstr(reason, &str) < 0) - error = -1; - } - - git_str_dispose(&str); - return error; -} - -const char *git_worktree_name(const git_worktree *wt) -{ - GIT_ASSERT_ARG_WITH_RETVAL(wt, NULL); - return wt->name; -} - -const char *git_worktree_path(const git_worktree *wt) -{ - GIT_ASSERT_ARG_WITH_RETVAL(wt, NULL); - return wt->worktree_path; -} - -int git_worktree_prune_options_init( - git_worktree_prune_options *opts, - unsigned int version) -{ - GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, - git_worktree_prune_options, GIT_WORKTREE_PRUNE_OPTIONS_INIT); - return 0; -} - -#ifndef GIT_DEPRECATE_HARD -int git_worktree_prune_init_options(git_worktree_prune_options *opts, - unsigned int version) -{ - return git_worktree_prune_options_init(opts, version); -} -#endif - -int git_worktree_is_prunable(git_worktree *wt, - git_worktree_prune_options *opts) -{ - git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; - - GIT_ERROR_CHECK_VERSION( - opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION, - "git_worktree_prune_options"); - - if (opts) - memcpy(&popts, opts, sizeof(popts)); - - if ((popts.flags & GIT_WORKTREE_PRUNE_LOCKED) == 0) { - git_str reason = GIT_STR_INIT; - int error; - - if ((error = git_worktree__is_locked(&reason, wt)) < 0) - return error; - - if (error) { - if (!reason.size) - git_str_attach_notowned(&reason, "no reason given", 15); - git_error_set(GIT_ERROR_WORKTREE, "not pruning locked working tree: '%s'", reason.ptr); - git_str_dispose(&reason); - return 0; - } - } - - if ((popts.flags & GIT_WORKTREE_PRUNE_VALID) == 0 && - git_worktree_validate(wt) == 0) { - git_error_set(GIT_ERROR_WORKTREE, "not pruning valid working tree"); - return 0; - } - - return 1; -} - -int git_worktree_prune(git_worktree *wt, - git_worktree_prune_options *opts) -{ - git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; - git_str path = GIT_STR_INIT; - char *wtpath; - int err; - - GIT_ERROR_CHECK_VERSION( - opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION, - "git_worktree_prune_options"); - - if (opts) - memcpy(&popts, opts, sizeof(popts)); - - if (!git_worktree_is_prunable(wt, &popts)) { - err = -1; - goto out; - } - - /* Delete gitdir in parent repository */ - if ((err = git_str_join3(&path, '/', wt->commondir_path, "worktrees", wt->name)) < 0) - goto out; - if (!git_fs_path_exists(path.ptr)) - { - git_error_set(GIT_ERROR_WORKTREE, "worktree gitdir '%s' does not exist", path.ptr); - err = -1; - goto out; - } - if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0) - goto out; - - /* Skip deletion of the actual working tree if it does - * not exist or deletion was not requested */ - if ((popts.flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 || - !git_fs_path_exists(wt->gitlink_path)) - { - goto out; - } - - if ((wtpath = git_fs_path_dirname(wt->gitlink_path)) == NULL) - goto out; - git_str_attach(&path, wtpath, 0); - if (!git_fs_path_exists(path.ptr)) - { - git_error_set(GIT_ERROR_WORKTREE, "working tree '%s' does not exist", path.ptr); - err = -1; - goto out; - } - if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0) - goto out; - -out: - git_str_dispose(&path); - - return err; -} diff --git a/src/worktree.h b/src/worktree.h deleted file mode 100644 index 587189f81..000000000 --- a/src/worktree.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_worktree_h__ -#define INCLUDE_worktree_h__ - -#include "common.h" - -#include "git2/common.h" -#include "git2/worktree.h" - -struct git_worktree { - /* Name of the working tree. This is the name of the - * containing directory in the `$PARENT/.git/worktrees/` - * directory. */ - char *name; - - /* Path to the where the worktree lives in the filesystem */ - char *worktree_path; - /* Path to the .git file in the working tree's repository */ - char *gitlink_path; - /* Path to the .git directory inside the parent's - * worktrees directory */ - char *gitdir_path; - /* Path to the common directory contained in the parent - * repository */ - char *commondir_path; - /* Path to the parent's working directory */ - char *parent_path; - - unsigned int locked:1; -}; - -char *git_worktree__read_link(const char *base, const char *file); - -#endif diff --git a/src/xdiff/git-xdiff.h b/src/xdiff/git-xdiff.h deleted file mode 100644 index b75dba819..000000000 --- a/src/xdiff/git-xdiff.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -/* - * This file provides the necessary indirection between xdiff and - * libgit2. libgit2-specific functionality should live here, so - * that git and libgit2 can share a common xdiff implementation. - */ - -#ifndef INCLUDE_git_xdiff_h__ -#define INCLUDE_git_xdiff_h__ - -#include "regexp.h" - -/* Work around C90-conformance issues */ -#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) -# if defined(_MSC_VER) -# define inline __inline -# elif defined(__GNUC__) -# define inline __inline__ -# else -# define inline -# endif -#endif - -#define xdl_malloc(x) git__malloc(x) -#define xdl_free(ptr) git__free(ptr) -#define xdl_realloc(ptr, x) git__realloc(ptr, x) - -#define XDL_BUG(msg) GIT_ASSERT(msg) - -#define xdl_regex_t git_regexp -#define xdl_regmatch_t git_regmatch - -GIT_INLINE(int) xdl_regexec_buf( - const xdl_regex_t *preg, const char *buf, size_t size, - size_t nmatch, xdl_regmatch_t pmatch[], int eflags) -{ - GIT_UNUSED(preg); - GIT_UNUSED(buf); - GIT_UNUSED(size); - GIT_UNUSED(nmatch); - GIT_UNUSED(pmatch); - GIT_UNUSED(eflags); - GIT_ASSERT("not implemented"); - return -1; -} - -#endif diff --git a/src/xdiff/xdiff.h b/src/xdiff/xdiff.h deleted file mode 100644 index fb47f63fb..000000000 --- a/src/xdiff/xdiff.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XDIFF_H) -#define XDIFF_H - -#ifdef __cplusplus -extern "C" { -#endif /* #ifdef __cplusplus */ - -#include "git-xdiff.h" - -/* xpparm_t.flags */ -#define XDF_NEED_MINIMAL (1 << 0) - -#define XDF_IGNORE_WHITESPACE (1 << 1) -#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 2) -#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 3) -#define XDF_IGNORE_CR_AT_EOL (1 << 4) -#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | \ - XDF_IGNORE_WHITESPACE_CHANGE | \ - XDF_IGNORE_WHITESPACE_AT_EOL | \ - XDF_IGNORE_CR_AT_EOL) - -#define XDF_IGNORE_BLANK_LINES (1 << 7) - -#define XDF_PATIENCE_DIFF (1 << 14) -#define XDF_HISTOGRAM_DIFF (1 << 15) -#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF) -#define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK) - -#define XDF_INDENT_HEURISTIC (1 << 23) - -/* xdemitconf_t.flags */ -#define XDL_EMIT_FUNCNAMES (1 << 0) -#define XDL_EMIT_NO_HUNK_HDR (1 << 1) -#define XDL_EMIT_FUNCCONTEXT (1 << 2) - -/* merge simplification levels */ -#define XDL_MERGE_MINIMAL 0 -#define XDL_MERGE_EAGER 1 -#define XDL_MERGE_ZEALOUS 2 -#define XDL_MERGE_ZEALOUS_ALNUM 3 - -/* merge favor modes */ -#define XDL_MERGE_FAVOR_OURS 1 -#define XDL_MERGE_FAVOR_THEIRS 2 -#define XDL_MERGE_FAVOR_UNION 3 - -/* merge output styles */ -#define XDL_MERGE_DIFF3 1 -#define XDL_MERGE_ZEALOUS_DIFF3 2 - -typedef struct s_mmfile { - char *ptr; - long size; -} mmfile_t; - -typedef struct s_mmbuffer { - char *ptr; - long size; -} mmbuffer_t; - -typedef struct s_xpparam { - unsigned long flags; - - /* -I */ - xdl_regex_t **ignore_regex; - size_t ignore_regex_nr; - - /* See Documentation/diff-options.txt. */ - char **anchors; - size_t anchors_nr; -} xpparam_t; - -typedef struct s_xdemitcb { - void *priv; - int (*out_hunk)(void *, - long old_begin, long old_nr, - long new_begin, long new_nr, - const char *func, long funclen); - int (*out_line)(void *, mmbuffer_t *, int); -} xdemitcb_t; - -typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv); - -typedef int (*xdl_emit_hunk_consume_func_t)(long start_a, long count_a, - long start_b, long count_b, - void *cb_data); - -typedef struct s_xdemitconf { - long ctxlen; - long interhunkctxlen; - unsigned long flags; - find_func_t find_func; - void *find_func_priv; - xdl_emit_hunk_consume_func_t hunk_func; -} xdemitconf_t; - -typedef struct s_bdiffparam { - long bsize; -} bdiffparam_t; - - -void *xdl_mmfile_first(mmfile_t *mmf, long *size); -long xdl_mmfile_size(mmfile_t *mmf); - -int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdemitconf_t const *xecfg, xdemitcb_t *ecb); - -typedef struct s_xmparam { - xpparam_t xpp; - int marker_size; - int level; - int favor; - int style; - const char *ancestor; /* label for orig */ - const char *file1; /* label for mf1 */ - const char *file2; /* label for mf2 */ -} xmparam_t; - -#define DEFAULT_CONFLICT_MARKER_SIZE 7 - -int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, - xmparam_t const *xmp, mmbuffer_t *result); - -#ifdef __cplusplus -} -#endif /* #ifdef __cplusplus */ - -#endif /* #if !defined(XDIFF_H) */ diff --git a/src/xdiff/xdiffi.c b/src/xdiff/xdiffi.c deleted file mode 100644 index af31b7f4b..000000000 --- a/src/xdiff/xdiffi.c +++ /dev/null @@ -1,1088 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#include "xinclude.h" - -#define XDL_MAX_COST_MIN 256 -#define XDL_HEUR_MIN_COST 256 -#define XDL_LINE_MAX (long)((1UL << (CHAR_BIT * sizeof(long) - 1)) - 1) -#define XDL_SNAKE_CNT 20 -#define XDL_K_HEUR 4 - -typedef struct s_xdpsplit { - long i1, i2; - int min_lo, min_hi; -} xdpsplit_t; - -/* - * See "An O(ND) Difference Algorithm and its Variations", by Eugene Myers. - * Basically considers a "box" (off1, off2, lim1, lim2) and scan from both - * the forward diagonal starting from (off1, off2) and the backward diagonal - * starting from (lim1, lim2). If the K values on the same diagonal crosses - * returns the furthest point of reach. We might encounter expensive edge cases - * using this algorithm, so a little bit of heuristic is needed to cut the - * search and to return a suboptimal point. - */ -static long xdl_split(unsigned long const *ha1, long off1, long lim1, - unsigned long const *ha2, long off2, long lim2, - long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, - xdalgoenv_t *xenv) { - long dmin = off1 - lim2, dmax = lim1 - off2; - long fmid = off1 - off2, bmid = lim1 - lim2; - long odd = (fmid - bmid) & 1; - long fmin = fmid, fmax = fmid; - long bmin = bmid, bmax = bmid; - long ec, d, i1, i2, prev1, best, dd, v, k; - - /* - * Set initial diagonal values for both forward and backward path. - */ - kvdf[fmid] = off1; - kvdb[bmid] = lim1; - - for (ec = 1;; ec++) { - int got_snake = 0; - - /* - * We need to extend the diagonal "domain" by one. If the next - * values exits the box boundaries we need to change it in the - * opposite direction because (max - min) must be a power of - * two. - * - * Also we initialize the external K value to -1 so that we can - * avoid extra conditions in the check inside the core loop. - */ - if (fmin > dmin) - kvdf[--fmin - 1] = -1; - else - ++fmin; - if (fmax < dmax) - kvdf[++fmax + 1] = -1; - else - --fmax; - - for (d = fmax; d >= fmin; d -= 2) { - if (kvdf[d - 1] >= kvdf[d + 1]) - i1 = kvdf[d - 1] + 1; - else - i1 = kvdf[d + 1]; - prev1 = i1; - i2 = i1 - d; - for (; i1 < lim1 && i2 < lim2 && ha1[i1] == ha2[i2]; i1++, i2++); - if (i1 - prev1 > xenv->snake_cnt) - got_snake = 1; - kvdf[d] = i1; - if (odd && bmin <= d && d <= bmax && kvdb[d] <= i1) { - spl->i1 = i1; - spl->i2 = i2; - spl->min_lo = spl->min_hi = 1; - return ec; - } - } - - /* - * We need to extend the diagonal "domain" by one. If the next - * values exits the box boundaries we need to change it in the - * opposite direction because (max - min) must be a power of - * two. - * - * Also we initialize the external K value to -1 so that we can - * avoid extra conditions in the check inside the core loop. - */ - if (bmin > dmin) - kvdb[--bmin - 1] = XDL_LINE_MAX; - else - ++bmin; - if (bmax < dmax) - kvdb[++bmax + 1] = XDL_LINE_MAX; - else - --bmax; - - for (d = bmax; d >= bmin; d -= 2) { - if (kvdb[d - 1] < kvdb[d + 1]) - i1 = kvdb[d - 1]; - else - i1 = kvdb[d + 1] - 1; - prev1 = i1; - i2 = i1 - d; - for (; i1 > off1 && i2 > off2 && ha1[i1 - 1] == ha2[i2 - 1]; i1--, i2--); - if (prev1 - i1 > xenv->snake_cnt) - got_snake = 1; - kvdb[d] = i1; - if (!odd && fmin <= d && d <= fmax && i1 <= kvdf[d]) { - spl->i1 = i1; - spl->i2 = i2; - spl->min_lo = spl->min_hi = 1; - return ec; - } - } - - if (need_min) - continue; - - /* - * If the edit cost is above the heuristic trigger and if - * we got a good snake, we sample current diagonals to see - * if some of them have reached an "interesting" path. Our - * measure is a function of the distance from the diagonal - * corner (i1 + i2) penalized with the distance from the - * mid diagonal itself. If this value is above the current - * edit cost times a magic factor (XDL_K_HEUR) we consider - * it interesting. - */ - if (got_snake && ec > xenv->heur_min) { - for (best = 0, d = fmax; d >= fmin; d -= 2) { - dd = d > fmid ? d - fmid: fmid - d; - i1 = kvdf[d]; - i2 = i1 - d; - v = (i1 - off1) + (i2 - off2) - dd; - - if (v > XDL_K_HEUR * ec && v > best && - off1 + xenv->snake_cnt <= i1 && i1 < lim1 && - off2 + xenv->snake_cnt <= i2 && i2 < lim2) { - for (k = 1; ha1[i1 - k] == ha2[i2 - k]; k++) - if (k == xenv->snake_cnt) { - best = v; - spl->i1 = i1; - spl->i2 = i2; - break; - } - } - } - if (best > 0) { - spl->min_lo = 1; - spl->min_hi = 0; - return ec; - } - - for (best = 0, d = bmax; d >= bmin; d -= 2) { - dd = d > bmid ? d - bmid: bmid - d; - i1 = kvdb[d]; - i2 = i1 - d; - v = (lim1 - i1) + (lim2 - i2) - dd; - - if (v > XDL_K_HEUR * ec && v > best && - off1 < i1 && i1 <= lim1 - xenv->snake_cnt && - off2 < i2 && i2 <= lim2 - xenv->snake_cnt) { - for (k = 0; ha1[i1 + k] == ha2[i2 + k]; k++) - if (k == xenv->snake_cnt - 1) { - best = v; - spl->i1 = i1; - spl->i2 = i2; - break; - } - } - } - if (best > 0) { - spl->min_lo = 0; - spl->min_hi = 1; - return ec; - } - } - - /* - * Enough is enough. We spent too much time here and now we - * collect the furthest reaching path using the (i1 + i2) - * measure. - */ - if (ec >= xenv->mxcost) { - long fbest, fbest1, bbest, bbest1; - - fbest = fbest1 = -1; - for (d = fmax; d >= fmin; d -= 2) { - i1 = XDL_MIN(kvdf[d], lim1); - i2 = i1 - d; - if (lim2 < i2) - i1 = lim2 + d, i2 = lim2; - if (fbest < i1 + i2) { - fbest = i1 + i2; - fbest1 = i1; - } - } - - bbest = bbest1 = XDL_LINE_MAX; - for (d = bmax; d >= bmin; d -= 2) { - i1 = XDL_MAX(off1, kvdb[d]); - i2 = i1 - d; - if (i2 < off2) - i1 = off2 + d, i2 = off2; - if (i1 + i2 < bbest) { - bbest = i1 + i2; - bbest1 = i1; - } - } - - if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) { - spl->i1 = fbest1; - spl->i2 = fbest - fbest1; - spl->min_lo = 1; - spl->min_hi = 0; - } else { - spl->i1 = bbest1; - spl->i2 = bbest - bbest1; - spl->min_lo = 0; - spl->min_hi = 1; - } - return ec; - } - } -} - - -/* - * Rule: "Divide et Impera" (divide & conquer). Recursively split the box in - * sub-boxes by calling the box splitting function. Note that the real job - * (marking changed lines) is done in the two boundary reaching checks. - */ -int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, - diffdata_t *dd2, long off2, long lim2, - long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) { - unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha; - - /* - * Shrink the box by walking through each diagonal snake (SW and NE). - */ - for (; off1 < lim1 && off2 < lim2 && ha1[off1] == ha2[off2]; off1++, off2++); - for (; off1 < lim1 && off2 < lim2 && ha1[lim1 - 1] == ha2[lim2 - 1]; lim1--, lim2--); - - /* - * If one dimension is empty, then all records on the other one must - * be obviously changed. - */ - if (off1 == lim1) { - char *rchg2 = dd2->rchg; - long *rindex2 = dd2->rindex; - - for (; off2 < lim2; off2++) - rchg2[rindex2[off2]] = 1; - } else if (off2 == lim2) { - char *rchg1 = dd1->rchg; - long *rindex1 = dd1->rindex; - - for (; off1 < lim1; off1++) - rchg1[rindex1[off1]] = 1; - } else { - xdpsplit_t spl; - spl.i1 = spl.i2 = 0; - - /* - * Divide ... - */ - if (xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb, - need_min, &spl, xenv) < 0) { - - return -1; - } - - /* - * ... et Impera. - */ - if (xdl_recs_cmp(dd1, off1, spl.i1, dd2, off2, spl.i2, - kvdf, kvdb, spl.min_lo, xenv) < 0 || - xdl_recs_cmp(dd1, spl.i1, lim1, dd2, spl.i2, lim2, - kvdf, kvdb, spl.min_hi, xenv) < 0) { - - return -1; - } - } - - return 0; -} - - -int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdfenv_t *xe) { - long ndiags; - long *kvd, *kvdf, *kvdb; - xdalgoenv_t xenv; - diffdata_t dd1, dd2; - - if (XDF_DIFF_ALG(xpp->flags) == XDF_PATIENCE_DIFF) - return xdl_do_patience_diff(mf1, mf2, xpp, xe); - - if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF) - return xdl_do_histogram_diff(mf1, mf2, xpp, xe); - - if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) { - - return -1; - } - - /* - * Allocate and setup K vectors to be used by the differential - * algorithm. - * - * One is to store the forward path and one to store the backward path. - */ - ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3; - if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) { - - xdl_free_env(xe); - return -1; - } - kvdf = kvd; - kvdb = kvdf + ndiags; - kvdf += xe->xdf2.nreff + 1; - kvdb += xe->xdf2.nreff + 1; - - xenv.mxcost = xdl_bogosqrt(ndiags); - if (xenv.mxcost < XDL_MAX_COST_MIN) - xenv.mxcost = XDL_MAX_COST_MIN; - xenv.snake_cnt = XDL_SNAKE_CNT; - xenv.heur_min = XDL_HEUR_MIN_COST; - - dd1.nrec = xe->xdf1.nreff; - dd1.ha = xe->xdf1.ha; - dd1.rchg = xe->xdf1.rchg; - dd1.rindex = xe->xdf1.rindex; - dd2.nrec = xe->xdf2.nreff; - dd2.ha = xe->xdf2.ha; - dd2.rchg = xe->xdf2.rchg; - dd2.rindex = xe->xdf2.rindex; - - if (xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec, - kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0, &xenv) < 0) { - - xdl_free(kvd); - xdl_free_env(xe); - return -1; - } - - xdl_free(kvd); - - return 0; -} - - -static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2) { - xdchange_t *xch; - - if (!(xch = (xdchange_t *) xdl_malloc(sizeof(xdchange_t)))) - return NULL; - - xch->next = xscr; - xch->i1 = i1; - xch->i2 = i2; - xch->chg1 = chg1; - xch->chg2 = chg2; - xch->ignore = 0; - - return xch; -} - - -static int recs_match(xrecord_t *rec1, xrecord_t *rec2) -{ - return (rec1->ha == rec2->ha); -} - -/* - * If a line is indented more than this, get_indent() just returns this value. - * This avoids having to do absurd amounts of work for data that are not - * human-readable text, and also ensures that the output of get_indent fits - * within an int. - */ -#define MAX_INDENT 200 - -/* - * Return the amount of indentation of the specified line, treating TAB as 8 - * columns. Return -1 if line is empty or contains only whitespace. Clamp the - * output value at MAX_INDENT. - */ -static int get_indent(xrecord_t *rec) -{ - long i; - int ret = 0; - - for (i = 0; i < rec->size; i++) { - char c = rec->ptr[i]; - - if (!XDL_ISSPACE(c)) - return ret; - else if (c == ' ') - ret += 1; - else if (c == '\t') - ret += 8 - ret % 8; - /* ignore other whitespace characters */ - - if (ret >= MAX_INDENT) - return MAX_INDENT; - } - - /* The line contains only whitespace. */ - return -1; -} - -/* - * If more than this number of consecutive blank rows are found, just return - * this value. This avoids requiring O(N^2) work for pathological cases, and - * also ensures that the output of score_split fits in an int. - */ -#define MAX_BLANKS 20 - -/* Characteristics measured about a hypothetical split position. */ -struct split_measurement { - /* - * Is the split at the end of the file (aside from any blank lines)? - */ - int end_of_file; - - /* - * How much is the line immediately following the split indented (or -1 - * if the line is blank): - */ - int indent; - - /* - * How many consecutive lines above the split are blank? - */ - int pre_blank; - - /* - * How much is the nearest non-blank line above the split indented (or - * -1 if there is no such line)? - */ - int pre_indent; - - /* - * How many lines after the line following the split are blank? - */ - int post_blank; - - /* - * How much is the nearest non-blank line after the line following the - * split indented (or -1 if there is no such line)? - */ - int post_indent; -}; - -struct split_score { - /* The effective indent of this split (smaller is preferred). */ - int effective_indent; - - /* Penalty for this split (smaller is preferred). */ - int penalty; -}; - -/* - * Fill m with information about a hypothetical split of xdf above line split. - */ -static void measure_split(const xdfile_t *xdf, long split, - struct split_measurement *m) -{ - long i; - - if (split >= xdf->nrec) { - m->end_of_file = 1; - m->indent = -1; - } else { - m->end_of_file = 0; - m->indent = get_indent(xdf->recs[split]); - } - - m->pre_blank = 0; - m->pre_indent = -1; - for (i = split - 1; i >= 0; i--) { - m->pre_indent = get_indent(xdf->recs[i]); - if (m->pre_indent != -1) - break; - m->pre_blank += 1; - if (m->pre_blank == MAX_BLANKS) { - m->pre_indent = 0; - break; - } - } - - m->post_blank = 0; - m->post_indent = -1; - for (i = split + 1; i < xdf->nrec; i++) { - m->post_indent = get_indent(xdf->recs[i]); - if (m->post_indent != -1) - break; - m->post_blank += 1; - if (m->post_blank == MAX_BLANKS) { - m->post_indent = 0; - break; - } - } -} - -/* - * The empirically-determined weight factors used by score_split() below. - * Larger values means that the position is a less favorable place to split. - * - * Note that scores are only ever compared against each other, so multiplying - * all of these weight/penalty values by the same factor wouldn't change the - * heuristic's behavior. Still, we need to set that arbitrary scale *somehow*. - * In practice, these numbers are chosen to be large enough that they can be - * adjusted relative to each other with sufficient precision despite using - * integer math. - */ - -/* Penalty if there are no non-blank lines before the split */ -#define START_OF_FILE_PENALTY 1 - -/* Penalty if there are no non-blank lines after the split */ -#define END_OF_FILE_PENALTY 21 - -/* Multiplier for the number of blank lines around the split */ -#define TOTAL_BLANK_WEIGHT (-30) - -/* Multiplier for the number of blank lines after the split */ -#define POST_BLANK_WEIGHT 6 - -/* - * Penalties applied if the line is indented more than its predecessor - */ -#define RELATIVE_INDENT_PENALTY (-4) -#define RELATIVE_INDENT_WITH_BLANK_PENALTY 10 - -/* - * Penalties applied if the line is indented less than both its predecessor and - * its successor - */ -#define RELATIVE_OUTDENT_PENALTY 24 -#define RELATIVE_OUTDENT_WITH_BLANK_PENALTY 17 - -/* - * Penalties applied if the line is indented less than its predecessor but not - * less than its successor - */ -#define RELATIVE_DEDENT_PENALTY 23 -#define RELATIVE_DEDENT_WITH_BLANK_PENALTY 17 - -/* - * We only consider whether the sum of the effective indents for splits are - * less than (-1), equal to (0), or greater than (+1) each other. The resulting - * value is multiplied by the following weight and combined with the penalty to - * determine the better of two scores. - */ -#define INDENT_WEIGHT 60 - -/* - * How far do we slide a hunk at most? - */ -#define INDENT_HEURISTIC_MAX_SLIDING 100 - -/* - * Compute a badness score for the hypothetical split whose measurements are - * stored in m. The weight factors were determined empirically using the tools - * and corpus described in - * - * https://github.com/mhagger/diff-slider-tools - * - * Also see that project if you want to improve the weights based on, for - * example, a larger or more diverse corpus. - */ -static void score_add_split(const struct split_measurement *m, struct split_score *s) -{ - /* - * A place to accumulate penalty factors (positive makes this index more - * favored): - */ - int post_blank, total_blank, indent, any_blanks; - - if (m->pre_indent == -1 && m->pre_blank == 0) - s->penalty += START_OF_FILE_PENALTY; - - if (m->end_of_file) - s->penalty += END_OF_FILE_PENALTY; - - /* - * Set post_blank to the number of blank lines following the split, - * including the line immediately after the split: - */ - post_blank = (m->indent == -1) ? 1 + m->post_blank : 0; - total_blank = m->pre_blank + post_blank; - - /* Penalties based on nearby blank lines: */ - s->penalty += TOTAL_BLANK_WEIGHT * total_blank; - s->penalty += POST_BLANK_WEIGHT * post_blank; - - if (m->indent != -1) - indent = m->indent; - else - indent = m->post_indent; - - any_blanks = (total_blank != 0); - - /* Note that the effective indent is -1 at the end of the file: */ - s->effective_indent += indent; - - if (indent == -1) { - /* No additional adjustments needed. */ - } else if (m->pre_indent == -1) { - /* No additional adjustments needed. */ - } else if (indent > m->pre_indent) { - /* - * The line is indented more than its predecessor. - */ - s->penalty += any_blanks ? - RELATIVE_INDENT_WITH_BLANK_PENALTY : - RELATIVE_INDENT_PENALTY; - } else if (indent == m->pre_indent) { - /* - * The line has the same indentation level as its predecessor. - * No additional adjustments needed. - */ - } else { - /* - * The line is indented less than its predecessor. It could be - * the block terminator of the previous block, but it could - * also be the start of a new block (e.g., an "else" block, or - * maybe the previous block didn't have a block terminator). - * Try to distinguish those cases based on what comes next: - */ - if (m->post_indent != -1 && m->post_indent > indent) { - /* - * The following line is indented more. So it is likely - * that this line is the start of a block. - */ - s->penalty += any_blanks ? - RELATIVE_OUTDENT_WITH_BLANK_PENALTY : - RELATIVE_OUTDENT_PENALTY; - } else { - /* - * That was probably the end of a block. - */ - s->penalty += any_blanks ? - RELATIVE_DEDENT_WITH_BLANK_PENALTY : - RELATIVE_DEDENT_PENALTY; - } - } -} - -static int score_cmp(struct split_score *s1, struct split_score *s2) -{ - /* -1 if s1.effective_indent < s2->effective_indent, etc. */ - int cmp_indents = ((s1->effective_indent > s2->effective_indent) - - (s1->effective_indent < s2->effective_indent)); - - return INDENT_WEIGHT * cmp_indents + (s1->penalty - s2->penalty); -} - -/* - * Represent a group of changed lines in an xdfile_t (i.e., a contiguous group - * of lines that was inserted or deleted from the corresponding version of the - * file). We consider there to be such a group at the beginning of the file, at - * the end of the file, and between any two unchanged lines, though most such - * groups will usually be empty. - * - * If the first line in a group is equal to the line following the group, then - * the group can be slid down. Similarly, if the last line in a group is equal - * to the line preceding the group, then the group can be slid up. See - * group_slide_down() and group_slide_up(). - * - * Note that loops that are testing for changed lines in xdf->rchg do not need - * index bounding since the array is prepared with a zero at position -1 and N. - */ -struct xdlgroup { - /* - * The index of the first changed line in the group, or the index of - * the unchanged line above which the (empty) group is located. - */ - long start; - - /* - * The index of the first unchanged line after the group. For an empty - * group, end is equal to start. - */ - long end; -}; - -/* - * Initialize g to point at the first group in xdf. - */ -static void group_init(xdfile_t *xdf, struct xdlgroup *g) -{ - g->start = g->end = 0; - while (xdf->rchg[g->end]) - g->end++; -} - -/* - * Move g to describe the next (possibly empty) group in xdf and return 0. If g - * is already at the end of the file, do nothing and return -1. - */ -static inline int group_next(xdfile_t *xdf, struct xdlgroup *g) -{ - if (g->end == xdf->nrec) - return -1; - - g->start = g->end + 1; - for (g->end = g->start; xdf->rchg[g->end]; g->end++) - ; - - return 0; -} - -/* - * Move g to describe the previous (possibly empty) group in xdf and return 0. - * If g is already at the beginning of the file, do nothing and return -1. - */ -static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g) -{ - if (g->start == 0) - return -1; - - g->end = g->start - 1; - for (g->start = g->end; xdf->rchg[g->start - 1]; g->start--) - ; - - return 0; -} - -/* - * If g can be slid toward the end of the file, do so, and if it bumps into a - * following group, expand this group to include it. Return 0 on success or -1 - * if g cannot be slid down. - */ -static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g) -{ - if (g->end < xdf->nrec && - recs_match(xdf->recs[g->start], xdf->recs[g->end])) { - xdf->rchg[g->start++] = 0; - xdf->rchg[g->end++] = 1; - - while (xdf->rchg[g->end]) - g->end++; - - return 0; - } else { - return -1; - } -} - -/* - * If g can be slid toward the beginning of the file, do so, and if it bumps - * into a previous group, expand this group to include it. Return 0 on success - * or -1 if g cannot be slid up. - */ -static int group_slide_up(xdfile_t *xdf, struct xdlgroup *g) -{ - if (g->start > 0 && - recs_match(xdf->recs[g->start - 1], xdf->recs[g->end - 1])) { - xdf->rchg[--g->start] = 1; - xdf->rchg[--g->end] = 0; - - while (xdf->rchg[g->start - 1]) - g->start--; - - return 0; - } else { - return -1; - } -} - -/* - * Move back and forward change groups for a consistent and pretty diff output. - * This also helps in finding joinable change groups and reducing the diff - * size. - */ -int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { - struct xdlgroup g, go; - long earliest_end, end_matching_other; - long groupsize; - - group_init(xdf, &g); - group_init(xdfo, &go); - - while (1) { - /* - * If the group is empty in the to-be-compacted file, skip it: - */ - if (g.end == g.start) - goto next; - - /* - * Now shift the change up and then down as far as possible in - * each direction. If it bumps into any other changes, merge - * them. - */ - do { - groupsize = g.end - g.start; - - /* - * Keep track of the last "end" index that causes this - * group to align with a group of changed lines in the - * other file. -1 indicates that we haven't found such - * a match yet: - */ - end_matching_other = -1; - - /* Shift the group backward as much as possible: */ - while (!group_slide_up(xdf, &g)) - if (group_previous(xdfo, &go)) - XDL_BUG("group sync broken sliding up"); - - /* - * This is this highest that this group can be shifted. - * Record its end index: - */ - earliest_end = g.end; - - if (go.end > go.start) - end_matching_other = g.end; - - /* Now shift the group forward as far as possible: */ - while (1) { - if (group_slide_down(xdf, &g)) - break; - if (group_next(xdfo, &go)) - XDL_BUG("group sync broken sliding down"); - - if (go.end > go.start) - end_matching_other = g.end; - } - } while (groupsize != g.end - g.start); - - /* - * If the group can be shifted, then we can possibly use this - * freedom to produce a more intuitive diff. - * - * The group is currently shifted as far down as possible, so - * the heuristics below only have to handle upwards shifts. - */ - - if (g.end == earliest_end) { - /* no shifting was possible */ - } else if (end_matching_other != -1) { - /* - * Move the possibly merged group of changes back to - * line up with the last group of changes from the - * other file that it can align with. - */ - while (go.end == go.start) { - if (group_slide_up(xdf, &g)) - XDL_BUG("match disappeared"); - if (group_previous(xdfo, &go)) - XDL_BUG("group sync broken sliding to match"); - } - } else if (flags & XDF_INDENT_HEURISTIC) { - /* - * Indent heuristic: a group of pure add/delete lines - * implies two splits, one between the end of the - * "before" context and the start of the group, and - * another between the end of the group and the - * beginning of the "after" context. Some splits are - * aesthetically better and some are worse. We compute - * a badness "score" for each split, and add the scores - * for the two splits to define a "score" for each - * position that the group can be shifted to. Then we - * pick the shift with the lowest score. - */ - long shift, best_shift = -1; - struct split_score best_score; - - shift = earliest_end; - if (g.end - groupsize - 1 > shift) - shift = g.end - groupsize - 1; - if (g.end - INDENT_HEURISTIC_MAX_SLIDING > shift) - shift = g.end - INDENT_HEURISTIC_MAX_SLIDING; - for (; shift <= g.end; shift++) { - struct split_measurement m; - struct split_score score = {0, 0}; - - measure_split(xdf, shift, &m); - score_add_split(&m, &score); - measure_split(xdf, shift - groupsize, &m); - score_add_split(&m, &score); - if (best_shift == -1 || - score_cmp(&score, &best_score) <= 0) { - best_score.effective_indent = score.effective_indent; - best_score.penalty = score.penalty; - best_shift = shift; - } - } - - while (g.end > best_shift) { - if (group_slide_up(xdf, &g)) - XDL_BUG("best shift unreached"); - if (group_previous(xdfo, &go)) - XDL_BUG("group sync broken sliding to blank line"); - } - } - - next: - /* Move past the just-processed group: */ - if (group_next(xdf, &g)) - break; - if (group_next(xdfo, &go)) - XDL_BUG("group sync broken moving to next group"); - } - - if (!group_next(xdfo, &go)) - XDL_BUG("group sync broken at end of file"); - - return 0; -} - - -int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) { - xdchange_t *cscr = NULL, *xch; - char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg; - long i1, i2, l1, l2; - - /* - * Trivial. Collects "groups" of changes and creates an edit script. - */ - for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--) - if (rchg1[i1 - 1] || rchg2[i2 - 1]) { - for (l1 = i1; rchg1[i1 - 1]; i1--); - for (l2 = i2; rchg2[i2 - 1]; i2--); - - if (!(xch = xdl_add_change(cscr, i1, i2, l1 - i1, l2 - i2))) { - xdl_free_script(cscr); - return -1; - } - cscr = xch; - } - - *xscr = cscr; - - return 0; -} - - -void xdl_free_script(xdchange_t *xscr) { - xdchange_t *xch; - - while ((xch = xscr) != NULL) { - xscr = xscr->next; - xdl_free(xch); - } -} - -static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, - xdemitconf_t const *xecfg) -{ - xdchange_t *xch, *xche; - - for (xch = xscr; xch; xch = xche->next) { - xche = xdl_get_hunk(&xch, xecfg); - if (!xch) - break; - if (xecfg->hunk_func(xch->i1, xche->i1 + xche->chg1 - xch->i1, - xch->i2, xche->i2 + xche->chg2 - xch->i2, - ecb->priv) < 0) - return -1; - } - return 0; -} - -static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags) -{ - xdchange_t *xch; - - for (xch = xscr; xch; xch = xch->next) { - int ignore = 1; - xrecord_t **rec; - long i; - - rec = &xe->xdf1.recs[xch->i1]; - for (i = 0; i < xch->chg1 && ignore; i++) - ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags); - - rec = &xe->xdf2.recs[xch->i2]; - for (i = 0; i < xch->chg2 && ignore; i++) - ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags); - - xch->ignore = ignore; - } -} - -static int record_matches_regex(xrecord_t *rec, xpparam_t const *xpp) { - xdl_regmatch_t regmatch; - int i; - - for (i = 0; i < xpp->ignore_regex_nr; i++) - if (!xdl_regexec_buf(xpp->ignore_regex[i], rec->ptr, rec->size, 1, - ®match, 0)) - return 1; - - return 0; -} - -static void xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe, - xpparam_t const *xpp) -{ - xdchange_t *xch; - - for (xch = xscr; xch; xch = xch->next) { - xrecord_t **rec; - int ignore = 1; - long i; - - /* - * Do not override --ignore-blank-lines. - */ - if (xch->ignore) - continue; - - rec = &xe->xdf1.recs[xch->i1]; - for (i = 0; i < xch->chg1 && ignore; i++) - ignore = record_matches_regex(rec[i], xpp); - - rec = &xe->xdf2.recs[xch->i2]; - for (i = 0; i < xch->chg2 && ignore; i++) - ignore = record_matches_regex(rec[i], xpp); - - xch->ignore = ignore; - } -} - -int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdemitconf_t const *xecfg, xdemitcb_t *ecb) { - xdchange_t *xscr; - xdfenv_t xe; - emit_func_t ef = xecfg->hunk_func ? xdl_call_hunk_func : xdl_emit_diff; - - if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) { - - return -1; - } - if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || - xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || - xdl_build_script(&xe, &xscr) < 0) { - - xdl_free_env(&xe); - return -1; - } - if (xscr) { - if (xpp->flags & XDF_IGNORE_BLANK_LINES) - xdl_mark_ignorable_lines(xscr, &xe, xpp->flags); - - if (xpp->ignore_regex) - xdl_mark_ignorable_regex(xscr, &xe, xpp); - - if (ef(&xe, xscr, ecb, xecfg) < 0) { - - xdl_free_script(xscr); - xdl_free_env(&xe); - return -1; - } - xdl_free_script(xscr); - } - xdl_free_env(&xe); - - return 0; -} diff --git a/src/xdiff/xdiffi.h b/src/xdiff/xdiffi.h deleted file mode 100644 index 8f1c7c8b0..000000000 --- a/src/xdiff/xdiffi.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XDIFFI_H) -#define XDIFFI_H - - -typedef struct s_diffdata { - long nrec; - unsigned long const *ha; - long *rindex; - char *rchg; -} diffdata_t; - -typedef struct s_xdalgoenv { - long mxcost; - long snake_cnt; - long heur_min; -} xdalgoenv_t; - -typedef struct s_xdchange { - struct s_xdchange *next; - long i1, i2; - long chg1, chg2; - int ignore; -} xdchange_t; - - - -int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, - diffdata_t *dd2, long off2, long lim2, - long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv); -int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdfenv_t *xe); -int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); -int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr); -void xdl_free_script(xdchange_t *xscr); -int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, - xdemitconf_t const *xecfg); -int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdfenv_t *env); -int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdfenv_t *env); - -#endif /* #if !defined(XDIFFI_H) */ diff --git a/src/xdiff/xemit.c b/src/xdiff/xemit.c deleted file mode 100644 index 1cbf2b982..000000000 --- a/src/xdiff/xemit.c +++ /dev/null @@ -1,330 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#include "xinclude.h" - -static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) { - - *rec = xdf->recs[ri]->ptr; - - return xdf->recs[ri]->size; -} - - -static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb) { - long size, psize = strlen(pre); - char const *rec; - - size = xdl_get_rec(xdf, ri, &rec); - if (xdl_emit_diffrec(rec, size, pre, psize, ecb) < 0) { - - return -1; - } - - return 0; -} - - -/* - * Starting at the passed change atom, find the latest change atom to be included - * inside the differential hunk according to the specified configuration. - * Also advance xscr if the first changes must be discarded. - */ -xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg) -{ - xdchange_t *xch, *xchp, *lxch; - long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen; - long max_ignorable = xecfg->ctxlen; - unsigned long ignored = 0; /* number of ignored blank lines */ - - /* remove ignorable changes that are too far before other changes */ - for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) { - xch = xchp->next; - - if (xch == NULL || - xch->i1 - (xchp->i1 + xchp->chg1) >= max_ignorable) - *xscr = xch; - } - - if (*xscr == NULL) - return NULL; - - lxch = *xscr; - - for (xchp = *xscr, xch = xchp->next; xch; xchp = xch, xch = xch->next) { - long distance = xch->i1 - (xchp->i1 + xchp->chg1); - if (distance > max_common) - break; - - if (distance < max_ignorable && (!xch->ignore || lxch == xchp)) { - lxch = xch; - ignored = 0; - } else if (distance < max_ignorable && xch->ignore) { - ignored += xch->chg2; - } else if (lxch != xchp && - xch->i1 + ignored - (lxch->i1 + lxch->chg1) > max_common) { - break; - } else if (!xch->ignore) { - lxch = xch; - ignored = 0; - } else { - ignored += xch->chg2; - } - } - - return lxch; -} - - -static long def_ff(const char *rec, long len, char *buf, long sz, void *priv) -{ - if (len > 0 && - (isalpha((unsigned char)*rec) || /* identifier? */ - *rec == '_' || /* also identifier? */ - *rec == '$')) { /* identifiers from VMS and other esoterico */ - if (len > sz) - len = sz; - while (0 < len && isspace((unsigned char)rec[len - 1])) - len--; - memcpy(buf, rec, len); - return len; - } - return -1; -} - -static long match_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri, - char *buf, long sz) -{ - const char *rec; - long len = xdl_get_rec(xdf, ri, &rec); - if (!xecfg->find_func) - return def_ff(rec, len, buf, sz, xecfg->find_func_priv); - return xecfg->find_func(rec, len, buf, sz, xecfg->find_func_priv); -} - -static int is_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri) -{ - char dummy[1]; - return match_func_rec(xdf, xecfg, ri, dummy, sizeof(dummy)) >= 0; -} - -struct func_line { - long len; - char buf[80]; -}; - -static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg, - struct func_line *func_line, long start, long limit) -{ - long l, size, step = (start > limit) ? -1 : 1; - char *buf, dummy[1]; - - buf = func_line ? func_line->buf : dummy; - size = func_line ? sizeof(func_line->buf) : sizeof(dummy); - - for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) { - long len = match_func_rec(&xe->xdf1, xecfg, l, buf, size); - if (len >= 0) { - if (func_line) - func_line->len = len; - return l; - } - } - return -1; -} - -static int is_empty_rec(xdfile_t *xdf, long ri) -{ - const char *rec; - long len = xdl_get_rec(xdf, ri, &rec); - - while (len > 0 && XDL_ISSPACE(*rec)) { - rec++; - len--; - } - return !len; -} - -int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, - xdemitconf_t const *xecfg) { - long s1, s2, e1, e2, lctx; - xdchange_t *xch, *xche; - long funclineprev = -1; - struct func_line func_line = { 0 }; - - for (xch = xscr; xch; xch = xche->next) { - xdchange_t *xchp = xch; - xche = xdl_get_hunk(&xch, xecfg); - if (!xch) - break; - -pre_context_calculation: - s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0); - s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0); - - if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { - long fs1, i1 = xch->i1; - - /* Appended chunk? */ - if (i1 >= xe->xdf1.nrec) { - long i2 = xch->i2; - - /* - * We don't need additional context if - * a whole function was added. - */ - while (i2 < xe->xdf2.nrec) { - if (is_func_rec(&xe->xdf2, xecfg, i2)) - goto post_context_calculation; - i2++; - } - - /* - * Otherwise get more context from the - * pre-image. - */ - i1 = xe->xdf1.nrec - 1; - } - - fs1 = get_func_line(xe, xecfg, NULL, i1, -1); - while (fs1 > 0 && !is_empty_rec(&xe->xdf1, fs1 - 1) && - !is_func_rec(&xe->xdf1, xecfg, fs1 - 1)) - fs1--; - if (fs1 < 0) - fs1 = 0; - if (fs1 < s1) { - s2 = XDL_MAX(s2 - (s1 - fs1), 0); - s1 = fs1; - - /* - * Did we extend context upwards into an - * ignored change? - */ - while (xchp != xch && - xchp->i1 + xchp->chg1 <= s1 && - xchp->i2 + xchp->chg2 <= s2) - xchp = xchp->next; - - /* If so, show it after all. */ - if (xchp != xch) { - xch = xchp; - goto pre_context_calculation; - } - } - } - - post_context_calculation: - lctx = xecfg->ctxlen; - lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1)); - lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2)); - - e1 = xche->i1 + xche->chg1 + lctx; - e2 = xche->i2 + xche->chg2 + lctx; - - if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { - long fe1 = get_func_line(xe, xecfg, NULL, - xche->i1 + xche->chg1, - xe->xdf1.nrec); - while (fe1 > 0 && is_empty_rec(&xe->xdf1, fe1 - 1)) - fe1--; - if (fe1 < 0) - fe1 = xe->xdf1.nrec; - if (fe1 > e1) { - e2 = XDL_MIN(e2 + (fe1 - e1), xe->xdf2.nrec); - e1 = fe1; - } - - /* - * Overlap with next change? Then include it - * in the current hunk and start over to find - * its new end. - */ - if (xche->next) { - long l = XDL_MIN(xche->next->i1, - xe->xdf1.nrec - 1); - if (l - xecfg->ctxlen <= e1 || - get_func_line(xe, xecfg, NULL, l, e1) < 0) { - xche = xche->next; - goto post_context_calculation; - } - } - } - - /* - * Emit current hunk header. - */ - - if (xecfg->flags & XDL_EMIT_FUNCNAMES) { - get_func_line(xe, xecfg, &func_line, - s1 - 1, funclineprev); - funclineprev = s1 - 1; - } - if (!(xecfg->flags & XDL_EMIT_NO_HUNK_HDR) && - xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2, - func_line.buf, func_line.len, ecb) < 0) - return -1; - - /* - * Emit pre-context. - */ - for (; s2 < xch->i2; s2++) - if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) - return -1; - - for (s1 = xch->i1, s2 = xch->i2;; xch = xch->next) { - /* - * Merge previous with current change atom. - */ - for (; s1 < xch->i1 && s2 < xch->i2; s1++, s2++) - if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) - return -1; - - /* - * Removes lines from the first file. - */ - for (s1 = xch->i1; s1 < xch->i1 + xch->chg1; s1++) - if (xdl_emit_record(&xe->xdf1, s1, "-", ecb) < 0) - return -1; - - /* - * Adds lines from the second file. - */ - for (s2 = xch->i2; s2 < xch->i2 + xch->chg2; s2++) - if (xdl_emit_record(&xe->xdf2, s2, "+", ecb) < 0) - return -1; - - if (xch == xche) - break; - s1 = xch->i1 + xch->chg1; - s2 = xch->i2 + xch->chg2; - } - - /* - * Emit post-context. - */ - for (s2 = xche->i2 + xche->chg2; s2 < e2; s2++) - if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) - return -1; - } - - return 0; -} diff --git a/src/xdiff/xemit.h b/src/xdiff/xemit.h deleted file mode 100644 index 1b9887e67..000000000 --- a/src/xdiff/xemit.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XEMIT_H) -#define XEMIT_H - - -typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, - xdemitconf_t const *xecfg); - -xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg); -int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, - xdemitconf_t const *xecfg); - - - -#endif /* #if !defined(XEMIT_H) */ diff --git a/src/xdiff/xhistogram.c b/src/xdiff/xhistogram.c deleted file mode 100644 index 80794748b..000000000 --- a/src/xdiff/xhistogram.c +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright (C) 2010, Google Inc. - * and other copyright owners as documented in JGit's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "xinclude.h" - -#define MAX_PTR UINT_MAX -#define MAX_CNT UINT_MAX - -#define LINE_END(n) (line##n + count##n - 1) -#define LINE_END_PTR(n) (*line##n + *count##n - 1) - -struct histindex { - struct record { - unsigned int ptr, cnt; - struct record *next; - } **records, /* an occurrence */ - **line_map; /* map of line to record chain */ - chastore_t rcha; - unsigned int *next_ptrs; - unsigned int table_bits, - records_size, - line_map_size; - - unsigned int max_chain_length, - key_shift, - ptr_shift; - - unsigned int cnt, - has_common; - - xdfenv_t *env; - xpparam_t const *xpp; -}; - -struct region { - unsigned int begin1, end1; - unsigned int begin2, end2; -}; - -#define LINE_MAP(i, a) (i->line_map[(a) - i->ptr_shift]) - -#define NEXT_PTR(index, ptr) \ - (index->next_ptrs[(ptr) - index->ptr_shift]) - -#define CNT(index, ptr) \ - ((LINE_MAP(index, ptr))->cnt) - -#define REC(env, s, l) \ - (env->xdf##s.recs[l - 1]) - -static int cmp_recs(xrecord_t *r1, xrecord_t *r2) -{ - return r1->ha == r2->ha; - -} - -#define CMP(i, s1, l1, s2, l2) \ - (cmp_recs(REC(i->env, s1, l1), REC(i->env, s2, l2))) - -#define TABLE_HASH(index, side, line) \ - XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits) - -static int scanA(struct histindex *index, int line1, int count1) -{ - unsigned int ptr, tbl_idx; - unsigned int chain_len; - struct record **rec_chain, *rec; - - for (ptr = LINE_END(1); line1 <= ptr; ptr--) { - tbl_idx = TABLE_HASH(index, 1, ptr); - rec_chain = index->records + tbl_idx; - rec = *rec_chain; - - chain_len = 0; - while (rec) { - if (CMP(index, 1, rec->ptr, 1, ptr)) { - /* - * ptr is identical to another element. Insert - * it onto the front of the existing element - * chain. - */ - NEXT_PTR(index, ptr) = rec->ptr; - rec->ptr = ptr; - /* cap rec->cnt at MAX_CNT */ - rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1); - LINE_MAP(index, ptr) = rec; - goto continue_scan; - } - - rec = rec->next; - chain_len++; - } - - if (chain_len == index->max_chain_length) - return -1; - - /* - * This is the first time we have ever seen this particular - * element in the sequence. Construct a new chain for it. - */ - if (!(rec = xdl_cha_alloc(&index->rcha))) - return -1; - rec->ptr = ptr; - rec->cnt = 1; - rec->next = *rec_chain; - *rec_chain = rec; - LINE_MAP(index, ptr) = rec; - -continue_scan: - ; /* no op */ - } - - return 0; -} - -static int try_lcs(struct histindex *index, struct region *lcs, int b_ptr, - int line1, int count1, int line2, int count2) -{ - unsigned int b_next = b_ptr + 1; - struct record *rec = index->records[TABLE_HASH(index, 2, b_ptr)]; - unsigned int as, ae, bs, be, np, rc; - int should_break; - - for (; rec; rec = rec->next) { - if (rec->cnt > index->cnt) { - if (!index->has_common) - index->has_common = CMP(index, 1, rec->ptr, 2, b_ptr); - continue; - } - - as = rec->ptr; - if (!CMP(index, 1, as, 2, b_ptr)) - continue; - - index->has_common = 1; - for (;;) { - should_break = 0; - np = NEXT_PTR(index, as); - bs = b_ptr; - ae = as; - be = bs; - rc = rec->cnt; - - while (line1 < as && line2 < bs - && CMP(index, 1, as - 1, 2, bs - 1)) { - as--; - bs--; - if (1 < rc) - rc = XDL_MIN(rc, CNT(index, as)); - } - while (ae < LINE_END(1) && be < LINE_END(2) - && CMP(index, 1, ae + 1, 2, be + 1)) { - ae++; - be++; - if (1 < rc) - rc = XDL_MIN(rc, CNT(index, ae)); - } - - if (b_next <= be) - b_next = be + 1; - if (lcs->end1 - lcs->begin1 < ae - as || rc < index->cnt) { - lcs->begin1 = as; - lcs->begin2 = bs; - lcs->end1 = ae; - lcs->end2 = be; - index->cnt = rc; - } - - if (np == 0) - break; - - while (np <= ae) { - np = NEXT_PTR(index, np); - if (np == 0) { - should_break = 1; - break; - } - } - - if (should_break) - break; - - as = np; - } - } - return b_next; -} - -static int fall_back_to_classic_diff(xpparam_t const *xpp, xdfenv_t *env, - int line1, int count1, int line2, int count2) -{ - xpparam_t xpparam; - - memset(&xpparam, 0, sizeof(xpparam)); - xpparam.flags = xpp->flags & ~XDF_DIFF_ALGORITHM_MASK; - - return xdl_fall_back_diff(env, &xpparam, - line1, count1, line2, count2); -} - -static inline void free_index(struct histindex *index) -{ - xdl_free(index->records); - xdl_free(index->line_map); - xdl_free(index->next_ptrs); - xdl_cha_free(&index->rcha); -} - -static int find_lcs(xpparam_t const *xpp, xdfenv_t *env, - struct region *lcs, - int line1, int count1, int line2, int count2) -{ - int b_ptr; - int sz, ret = -1; - struct histindex index; - - memset(&index, 0, sizeof(index)); - - index.env = env; - index.xpp = xpp; - - index.records = NULL; - index.line_map = NULL; - /* in case of early xdl_cha_free() */ - index.rcha.head = NULL; - - index.table_bits = xdl_hashbits(count1); - sz = index.records_size = 1 << index.table_bits; - sz *= sizeof(struct record *); - if (!(index.records = (struct record **) xdl_malloc(sz))) - goto cleanup; - memset(index.records, 0, sz); - - sz = index.line_map_size = count1; - sz *= sizeof(struct record *); - if (!(index.line_map = (struct record **) xdl_malloc(sz))) - goto cleanup; - memset(index.line_map, 0, sz); - - sz = index.line_map_size; - sz *= sizeof(unsigned int); - if (!(index.next_ptrs = (unsigned int *) xdl_malloc(sz))) - goto cleanup; - memset(index.next_ptrs, 0, sz); - - /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */ - if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0) - goto cleanup; - - index.ptr_shift = line1; - index.max_chain_length = 64; - - if (scanA(&index, line1, count1)) - goto cleanup; - - index.cnt = index.max_chain_length + 1; - - for (b_ptr = line2; b_ptr <= LINE_END(2); ) - b_ptr = try_lcs(&index, lcs, b_ptr, line1, count1, line2, count2); - - if (index.has_common && index.max_chain_length < index.cnt) - ret = 1; - else - ret = 0; - -cleanup: - free_index(&index); - return ret; -} - -static int histogram_diff(xpparam_t const *xpp, xdfenv_t *env, - int line1, int count1, int line2, int count2) -{ - struct region lcs; - int lcs_found; - int result; -redo: - result = -1; - - if (count1 <= 0 && count2 <= 0) - return 0; - - if (LINE_END(1) >= MAX_PTR) - return -1; - - if (!count1) { - while(count2--) - env->xdf2.rchg[line2++ - 1] = 1; - return 0; - } else if (!count2) { - while(count1--) - env->xdf1.rchg[line1++ - 1] = 1; - return 0; - } - - memset(&lcs, 0, sizeof(lcs)); - lcs_found = find_lcs(xpp, env, &lcs, line1, count1, line2, count2); - if (lcs_found < 0) - goto out; - else if (lcs_found) - result = fall_back_to_classic_diff(xpp, env, line1, count1, line2, count2); - else { - if (lcs.begin1 == 0 && lcs.begin2 == 0) { - while (count1--) - env->xdf1.rchg[line1++ - 1] = 1; - while (count2--) - env->xdf2.rchg[line2++ - 1] = 1; - result = 0; - } else { - result = histogram_diff(xpp, env, - line1, lcs.begin1 - line1, - line2, lcs.begin2 - line2); - if (result) - goto out; - /* - * result = histogram_diff(xpp, env, - * lcs.end1 + 1, LINE_END(1) - lcs.end1, - * lcs.end2 + 1, LINE_END(2) - lcs.end2); - * but let's optimize tail recursion ourself: - */ - count1 = LINE_END(1) - lcs.end1; - line1 = lcs.end1 + 1; - count2 = LINE_END(2) - lcs.end2; - line2 = lcs.end2 + 1; - goto redo; - } - } -out: - return result; -} - -int xdl_do_histogram_diff(mmfile_t *file1, mmfile_t *file2, - xpparam_t const *xpp, xdfenv_t *env) -{ - if (xdl_prepare_env(file1, file2, xpp, env) < 0) - return -1; - - return histogram_diff(xpp, env, - env->xdf1.dstart + 1, env->xdf1.dend - env->xdf1.dstart + 1, - env->xdf2.dstart + 1, env->xdf2.dend - env->xdf2.dstart + 1); -} diff --git a/src/xdiff/xinclude.h b/src/xdiff/xinclude.h deleted file mode 100644 index 75db1d8f3..000000000 --- a/src/xdiff/xinclude.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XINCLUDE_H) -#define XINCLUDE_H - -#include "git-xdiff.h" -#include "xmacros.h" -#include "xdiff.h" -#include "xtypes.h" -#include "xutils.h" -#include "xprepare.h" -#include "xdiffi.h" -#include "xemit.h" - - -#endif /* #if !defined(XINCLUDE_H) */ diff --git a/src/xdiff/xmacros.h b/src/xdiff/xmacros.h deleted file mode 100644 index 2809a28ca..000000000 --- a/src/xdiff/xmacros.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XMACROS_H) -#define XMACROS_H - - - - -#define XDL_MIN(a, b) ((a) < (b) ? (a): (b)) -#define XDL_MAX(a, b) ((a) > (b) ? (a): (b)) -#define XDL_ABS(v) ((v) >= 0 ? (v): -(v)) -#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9') -#define XDL_ISSPACE(c) (isspace((unsigned char)(c))) -#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b))) -#define XDL_MASKBITS(b) ((1UL << (b)) - 1) -#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b)) -#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0) -#define XDL_LE32_PUT(p, v) \ -do { \ - unsigned char *__p = (unsigned char *) (p); \ - *__p++ = (unsigned char) (v); \ - *__p++ = (unsigned char) ((v) >> 8); \ - *__p++ = (unsigned char) ((v) >> 16); \ - *__p = (unsigned char) ((v) >> 24); \ -} while (0) -#define XDL_LE32_GET(p, v) \ -do { \ - unsigned char const *__p = (unsigned char const *) (p); \ - (v) = (unsigned long) __p[0] | ((unsigned long) __p[1]) << 8 | \ - ((unsigned long) __p[2]) << 16 | ((unsigned long) __p[3]) << 24; \ -} while (0) - - -#endif /* #if !defined(XMACROS_H) */ diff --git a/src/xdiff/xmerge.c b/src/xdiff/xmerge.c deleted file mode 100644 index 433e2d741..000000000 --- a/src/xdiff/xmerge.c +++ /dev/null @@ -1,737 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#include "xinclude.h" - -typedef struct s_xdmerge { - struct s_xdmerge *next; - /* - * 0 = conflict, - * 1 = no conflict, take first, - * 2 = no conflict, take second. - * 3 = no conflict, take both. - */ - int mode; - /* - * These point at the respective postimages. E.g. is - * how side #1 wants to change the common ancestor; if there is no - * overlap, lines before i1 in the postimage of side #1 appear - * in the merge result as a region touched by neither side. - */ - long i1, i2; - long chg1, chg2; - /* - * These point at the preimage; of course there is just one - * preimage, that is from the shared common ancestor. - */ - long i0; - long chg0; -} xdmerge_t; - -static int xdl_append_merge(xdmerge_t **merge, int mode, - long i0, long chg0, - long i1, long chg1, - long i2, long chg2) -{ - xdmerge_t *m = *merge; - if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { - if (mode != m->mode) - m->mode = 0; - m->chg0 = i0 + chg0 - m->i0; - m->chg1 = i1 + chg1 - m->i1; - m->chg2 = i2 + chg2 - m->i2; - } else { - m = xdl_malloc(sizeof(xdmerge_t)); - if (!m) - return -1; - m->next = NULL; - m->mode = mode; - m->i0 = i0; - m->chg0 = chg0; - m->i1 = i1; - m->chg1 = chg1; - m->i2 = i2; - m->chg2 = chg2; - if (*merge) - (*merge)->next = m; - *merge = m; - } - return 0; -} - -static int xdl_cleanup_merge(xdmerge_t *c) -{ - int count = 0; - xdmerge_t *next_c; - - /* were there conflicts? */ - for (; c; c = next_c) { - if (c->mode == 0) - count++; - next_c = c->next; - xdl_free(c); - } - return count; -} - -static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, - int line_count, long flags) -{ - int i; - xrecord_t **rec1 = xe1->xdf2.recs + i1; - xrecord_t **rec2 = xe2->xdf2.recs + i2; - - for (i = 0; i < line_count; i++) { - int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size, - rec2[i]->ptr, rec2[i]->size, flags); - if (!result) - return -1; - } - return 0; -} - -static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) -{ - xrecord_t **recs; - int size = 0; - - recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i; - - if (count < 1) - return 0; - - for (i = 0; i < count; size += recs[i++]->size) - if (dest) - memcpy(dest + size, recs[i]->ptr, recs[i]->size); - if (add_nl) { - i = recs[count - 1]->size; - if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { - if (needs_cr) { - if (dest) - dest[size] = '\r'; - size++; - } - - if (dest) - dest[size] = '\n'; - size++; - } - } - return size; -} - -static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) -{ - return xdl_recs_copy_0(0, xe, i, count, needs_cr, add_nl, dest); -} - -static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) -{ - return xdl_recs_copy_0(1, xe, i, count, needs_cr, add_nl, dest); -} - -/* - * Returns 1 if the i'th line ends in CR/LF (if it is the last line and - * has no eol, the preceding line, if any), 0 if it ends in LF-only, and - * -1 if the line ending cannot be determined. - */ -static int is_eol_crlf(xdfile_t *file, int i) -{ - long size; - - if (i < file->nrec - 1) - /* All lines before the last *must* end in LF */ - return (size = file->recs[i]->size) > 1 && - file->recs[i]->ptr[size - 2] == '\r'; - if (!file->nrec) - /* Cannot determine eol style from empty file */ - return -1; - if ((size = file->recs[i]->size) && - file->recs[i]->ptr[size - 1] == '\n') - /* Last line; ends in LF; Is it CR/LF? */ - return size > 1 && - file->recs[i]->ptr[size - 2] == '\r'; - if (!i) - /* The only line has no eol */ - return -1; - /* Determine eol from second-to-last line */ - return (size = file->recs[i - 1]->size) > 1 && - file->recs[i - 1]->ptr[size - 2] == '\r'; -} - -static int is_cr_needed(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m) -{ - int needs_cr; - - /* Match post-images' preceding, or first, lines' end-of-line style */ - needs_cr = is_eol_crlf(&xe1->xdf2, m->i1 ? m->i1 - 1 : 0); - if (needs_cr) - needs_cr = is_eol_crlf(&xe2->xdf2, m->i2 ? m->i2 - 1 : 0); - /* Look at pre-image's first line, unless we already settled on LF */ - if (needs_cr) - needs_cr = is_eol_crlf(&xe1->xdf1, 0); - /* If still undecided, use LF-only */ - return needs_cr < 0 ? 0 : needs_cr; -} - -static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, - xdfenv_t *xe2, const char *name2, - const char *name3, - int size, int i, int style, - xdmerge_t *m, char *dest, int marker_size) -{ - int marker1_size = (name1 ? strlen(name1) + 1 : 0); - int marker2_size = (name2 ? strlen(name2) + 1 : 0); - int marker3_size = (name3 ? strlen(name3) + 1 : 0); - int needs_cr = is_cr_needed(xe1, xe2, m); - - if (marker_size <= 0) - marker_size = DEFAULT_CONFLICT_MARKER_SIZE; - - /* Before conflicting part */ - size += xdl_recs_copy(xe1, i, m->i1 - i, 0, 0, - dest ? dest + size : NULL); - - if (!dest) { - size += marker_size + 1 + needs_cr + marker1_size; - } else { - memset(dest + size, '<', marker_size); - size += marker_size; - if (marker1_size) { - dest[size] = ' '; - memcpy(dest + size + 1, name1, marker1_size - 1); - size += marker1_size; - } - if (needs_cr) - dest[size++] = '\r'; - dest[size++] = '\n'; - } - - /* Postimage from side #1 */ - size += xdl_recs_copy(xe1, m->i1, m->chg1, needs_cr, 1, - dest ? dest + size : NULL); - - if (style == XDL_MERGE_DIFF3 || style == XDL_MERGE_ZEALOUS_DIFF3) { - /* Shared preimage */ - if (!dest) { - size += marker_size + 1 + needs_cr + marker3_size; - } else { - memset(dest + size, '|', marker_size); - size += marker_size; - if (marker3_size) { - dest[size] = ' '; - memcpy(dest + size + 1, name3, marker3_size - 1); - size += marker3_size; - } - if (needs_cr) - dest[size++] = '\r'; - dest[size++] = '\n'; - } - size += xdl_orig_copy(xe1, m->i0, m->chg0, needs_cr, 1, - dest ? dest + size : NULL); - } - - if (!dest) { - size += marker_size + 1 + needs_cr; - } else { - memset(dest + size, '=', marker_size); - size += marker_size; - if (needs_cr) - dest[size++] = '\r'; - dest[size++] = '\n'; - } - - /* Postimage from side #2 */ - size += xdl_recs_copy(xe2, m->i2, m->chg2, needs_cr, 1, - dest ? dest + size : NULL); - if (!dest) { - size += marker_size + 1 + needs_cr + marker2_size; - } else { - memset(dest + size, '>', marker_size); - size += marker_size; - if (marker2_size) { - dest[size] = ' '; - memcpy(dest + size + 1, name2, marker2_size - 1); - size += marker2_size; - } - if (needs_cr) - dest[size++] = '\r'; - dest[size++] = '\n'; - } - return size; -} - -static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, - xdfenv_t *xe2, const char *name2, - const char *ancestor_name, - int favor, - xdmerge_t *m, char *dest, int style, - int marker_size) -{ - int size, i; - - for (size = i = 0; m; m = m->next) { - if (favor && !m->mode) - m->mode = favor; - - if (m->mode == 0) - size = fill_conflict_hunk(xe1, name1, xe2, name2, - ancestor_name, - size, i, style, m, dest, - marker_size); - else if (m->mode & 3) { - /* Before conflicting part */ - size += xdl_recs_copy(xe1, i, m->i1 - i, 0, 0, - dest ? dest + size : NULL); - /* Postimage from side #1 */ - if (m->mode & 1) { - int needs_cr = is_cr_needed(xe1, xe2, m); - - size += xdl_recs_copy(xe1, m->i1, m->chg1, needs_cr, (m->mode & 2), - dest ? dest + size : NULL); - } - /* Postimage from side #2 */ - if (m->mode & 2) - size += xdl_recs_copy(xe2, m->i2, m->chg2, 0, 0, - dest ? dest + size : NULL); - } else - continue; - i = m->i1 + m->chg1; - } - size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, 0, - dest ? dest + size : NULL); - return size; -} - -static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags) -{ - return xdl_recmatch(rec1->ptr, rec1->size, - rec2->ptr, rec2->size, flags); -} - -/* - * Remove any common lines from the beginning and end of the conflicted region. - */ -static void xdl_refine_zdiff3_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, - xpparam_t const *xpp) -{ - xrecord_t **rec1 = xe1->xdf2.recs, **rec2 = xe2->xdf2.recs; - for (; m; m = m->next) { - /* let's handle just the conflicts */ - if (m->mode) - continue; - - while(m->chg1 && m->chg2 && - recmatch(rec1[m->i1], rec2[m->i2], xpp->flags)) { - m->chg1--; - m->chg2--; - m->i1++; - m->i2++; - } - while (m->chg1 && m->chg2 && - recmatch(rec1[m->i1 + m->chg1 - 1], - rec2[m->i2 + m->chg2 - 1], xpp->flags)) { - m->chg1--; - m->chg2--; - } - } -} - -/* - * Sometimes, changes are not quite identical, but differ in only a few - * lines. Try hard to show only these few lines as conflicting. - */ -static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, - xpparam_t const *xpp) -{ - for (; m; m = m->next) { - mmfile_t t1, t2; - xdfenv_t xe; - xdchange_t *xscr, *x; - int i1 = m->i1, i2 = m->i2; - - /* let's handle just the conflicts */ - if (m->mode) - continue; - - /* no sense refining a conflict when one side is empty */ - if (m->chg1 == 0 || m->chg2 == 0) - continue; - - /* - * This probably does not work outside git, since - * we have a very simple mmfile structure. - */ - t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr; - t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr - + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr; - t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr; - t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr - + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr; - if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) - return -1; - if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || - xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || - xdl_build_script(&xe, &xscr) < 0) { - xdl_free_env(&xe); - return -1; - } - if (!xscr) { - /* If this happens, the changes are identical. */ - xdl_free_env(&xe); - m->mode = 4; - continue; - } - x = xscr; - m->i1 = xscr->i1 + i1; - m->chg1 = xscr->chg1; - m->i2 = xscr->i2 + i2; - m->chg2 = xscr->chg2; - while (xscr->next) { - xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t)); - if (!m2) { - xdl_free_env(&xe); - xdl_free_script(x); - return -1; - } - xscr = xscr->next; - m2->next = m->next; - m->next = m2; - m = m2; - m->mode = 0; - m->i1 = xscr->i1 + i1; - m->chg1 = xscr->chg1; - m->i2 = xscr->i2 + i2; - m->chg2 = xscr->chg2; - } - xdl_free_env(&xe); - xdl_free_script(x); - } - return 0; -} - -static int line_contains_alnum(const char *ptr, long size) -{ - while (size--) - if (isalnum((unsigned char)*(ptr++))) - return 1; - return 0; -} - -static int lines_contain_alnum(xdfenv_t *xe, int i, int chg) -{ - for (; chg; chg--, i++) - if (line_contains_alnum(xe->xdf2.recs[i]->ptr, - xe->xdf2.recs[i]->size)) - return 1; - return 0; -} - -/* - * This function merges m and m->next, marking everything between those hunks - * as conflicting, too. - */ -static void xdl_merge_two_conflicts(xdmerge_t *m) -{ - xdmerge_t *next_m = m->next; - m->chg1 = next_m->i1 + next_m->chg1 - m->i1; - m->chg2 = next_m->i2 + next_m->chg2 - m->i2; - m->next = next_m->next; - xdl_free(next_m); -} - -/* - * If there are less than 3 non-conflicting lines between conflicts, - * it appears simpler -- because it takes up less (or as many) lines -- - * if the lines are moved into the conflicts. - */ -static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m, - int simplify_if_no_alnum) -{ - int result = 0; - - if (!m) - return result; - for (;;) { - xdmerge_t *next_m = m->next; - int begin, end; - - if (!next_m) - return result; - - begin = m->i1 + m->chg1; - end = next_m->i1; - - if (m->mode != 0 || next_m->mode != 0 || - (end - begin > 3 && - (!simplify_if_no_alnum || - lines_contain_alnum(xe1, begin, end - begin)))) { - m = next_m; - } else { - result++; - xdl_merge_two_conflicts(m); - } - } -} - -/* - * level == 0: mark all overlapping changes as conflict - * level == 1: mark overlapping changes as conflict only if not identical - * level == 2: analyze non-identical changes for minimal conflict set - * level == 3: analyze non-identical changes for minimal conflict set, but - * treat hunks not containing any letter or number as conflicting - * - * returns < 0 on error, == 0 for no conflicts, else number of conflicts - */ -static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, - xdfenv_t *xe2, xdchange_t *xscr2, - xmparam_t const *xmp, mmbuffer_t *result) -{ - xdmerge_t *changes, *c; - xpparam_t const *xpp = &xmp->xpp; - const char *const ancestor_name = xmp->ancestor; - const char *const name1 = xmp->file1; - const char *const name2 = xmp->file2; - int i0, i1, i2, chg0, chg1, chg2; - int level = xmp->level; - int style = xmp->style; - int favor = xmp->favor; - - /* - * XDL_MERGE_DIFF3 does not attempt to refine conflicts by looking - * at common areas of sides 1 & 2, because the base (side 0) does - * not match and is being shown. Similarly, simplification of - * non-conflicts is also skipped due to the skipping of conflict - * refinement. - * - * XDL_MERGE_ZEALOUS_DIFF3, on the other hand, will attempt to - * refine conflicts looking for common areas of sides 1 & 2. - * However, since the base is being shown and does not match, - * it will only look for common areas at the beginning or end - * of the conflict block. Since XDL_MERGE_ZEALOUS_DIFF3's - * conflict refinement is much more limited in this fashion, the - * conflict simplification will be skipped. - */ - if (style == XDL_MERGE_DIFF3 || style == XDL_MERGE_ZEALOUS_DIFF3) { - /* - * "diff3 -m" output does not make sense for anything - * more aggressive than XDL_MERGE_EAGER. - */ - if (XDL_MERGE_EAGER < level) - level = XDL_MERGE_EAGER; - } - - c = changes = NULL; - - while (xscr1 && xscr2) { - if (!changes) - changes = c; - if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { - i0 = xscr1->i1; - i1 = xscr1->i2; - i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; - chg0 = xscr1->chg1; - chg1 = xscr1->chg2; - chg2 = xscr1->chg1; - if (xdl_append_merge(&c, 1, - i0, chg0, i1, chg1, i2, chg2)) { - xdl_cleanup_merge(changes); - return -1; - } - xscr1 = xscr1->next; - continue; - } - if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { - i0 = xscr2->i1; - i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; - i2 = xscr2->i2; - chg0 = xscr2->chg1; - chg1 = xscr2->chg1; - chg2 = xscr2->chg2; - if (xdl_append_merge(&c, 2, - i0, chg0, i1, chg1, i2, chg2)) { - xdl_cleanup_merge(changes); - return -1; - } - xscr2 = xscr2->next; - continue; - } - if (level == XDL_MERGE_MINIMAL || xscr1->i1 != xscr2->i1 || - xscr1->chg1 != xscr2->chg1 || - xscr1->chg2 != xscr2->chg2 || - xdl_merge_cmp_lines(xe1, xscr1->i2, - xe2, xscr2->i2, - xscr1->chg2, xpp->flags)) { - /* conflict */ - int off = xscr1->i1 - xscr2->i1; - int ffo = off + xscr1->chg1 - xscr2->chg1; - - i0 = xscr1->i1; - i1 = xscr1->i2; - i2 = xscr2->i2; - if (off > 0) { - i0 -= off; - i1 -= off; - } - else - i2 += off; - chg0 = xscr1->i1 + xscr1->chg1 - i0; - chg1 = xscr1->i2 + xscr1->chg2 - i1; - chg2 = xscr2->i2 + xscr2->chg2 - i2; - if (ffo < 0) { - chg0 -= ffo; - chg1 -= ffo; - } else - chg2 += ffo; - if (xdl_append_merge(&c, 0, - i0, chg0, i1, chg1, i2, chg2)) { - xdl_cleanup_merge(changes); - return -1; - } - } - - i1 = xscr1->i1 + xscr1->chg1; - i2 = xscr2->i1 + xscr2->chg1; - - if (i1 >= i2) - xscr2 = xscr2->next; - if (i2 >= i1) - xscr1 = xscr1->next; - } - while (xscr1) { - if (!changes) - changes = c; - i0 = xscr1->i1; - i1 = xscr1->i2; - i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; - chg0 = xscr1->chg1; - chg1 = xscr1->chg2; - chg2 = xscr1->chg1; - if (xdl_append_merge(&c, 1, - i0, chg0, i1, chg1, i2, chg2)) { - xdl_cleanup_merge(changes); - return -1; - } - xscr1 = xscr1->next; - } - while (xscr2) { - if (!changes) - changes = c; - i0 = xscr2->i1; - i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; - i2 = xscr2->i2; - chg0 = xscr2->chg1; - chg1 = xscr2->chg1; - chg2 = xscr2->chg2; - if (xdl_append_merge(&c, 2, - i0, chg0, i1, chg1, i2, chg2)) { - xdl_cleanup_merge(changes); - return -1; - } - xscr2 = xscr2->next; - } - if (!changes) - changes = c; - /* refine conflicts */ - if (style == XDL_MERGE_ZEALOUS_DIFF3) { - xdl_refine_zdiff3_conflicts(xe1, xe2, changes, xpp); - } else if (XDL_MERGE_ZEALOUS <= level && - (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 || - xdl_simplify_non_conflicts(xe1, changes, - XDL_MERGE_ZEALOUS < level) < 0)) { - xdl_cleanup_merge(changes); - return -1; - } - /* output */ - if (result) { - int marker_size = xmp->marker_size; - int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, - ancestor_name, - favor, changes, NULL, style, - marker_size); - result->ptr = xdl_malloc(size); - if (!result->ptr) { - xdl_cleanup_merge(changes); - return -1; - } - result->size = size; - xdl_fill_merge_buffer(xe1, name1, xe2, name2, - ancestor_name, favor, changes, - result->ptr, style, marker_size); - } - return xdl_cleanup_merge(changes); -} - -int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, - xmparam_t const *xmp, mmbuffer_t *result) -{ - xdchange_t *xscr1, *xscr2; - xdfenv_t xe1, xe2; - int status; - xpparam_t const *xpp = &xmp->xpp; - - result->ptr = NULL; - result->size = 0; - - if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0) { - return -1; - } - if (xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { - xdl_free_env(&xe1); - return -1; - } - if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 || - xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 || - xdl_build_script(&xe1, &xscr1) < 0) { - xdl_free_env(&xe1); - return -1; - } - if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 || - xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 || - xdl_build_script(&xe2, &xscr2) < 0) { - xdl_free_script(xscr1); - xdl_free_env(&xe1); - xdl_free_env(&xe2); - return -1; - } - status = 0; - if (!xscr1) { - result->ptr = xdl_malloc(mf2->size); - memcpy(result->ptr, mf2->ptr, mf2->size); - result->size = mf2->size; - } else if (!xscr2) { - result->ptr = xdl_malloc(mf1->size); - memcpy(result->ptr, mf1->ptr, mf1->size); - result->size = mf1->size; - } else { - status = xdl_do_merge(&xe1, xscr1, - &xe2, xscr2, - xmp, result); - } - xdl_free_script(xscr1); - xdl_free_script(xscr2); - - xdl_free_env(&xe1); - xdl_free_env(&xe2); - - return status; -} diff --git a/src/xdiff/xpatience.c b/src/xdiff/xpatience.c deleted file mode 100644 index c5d48e80a..000000000 --- a/src/xdiff/xpatience.c +++ /dev/null @@ -1,382 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003-2016 Davide Libenzi, Johannes E. Schindelin - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ -#include "xinclude.h" - -/* - * The basic idea of patience diff is to find lines that are unique in - * both files. These are intuitively the ones that we want to see as - * common lines. - * - * The maximal ordered sequence of such line pairs (where ordered means - * that the order in the sequence agrees with the order of the lines in - * both files) naturally defines an initial set of common lines. - * - * Now, the algorithm tries to extend the set of common lines by growing - * the line ranges where the files have identical lines. - * - * Between those common lines, the patience diff algorithm is applied - * recursively, until no unique line pairs can be found; these line ranges - * are handled by the well-known Myers algorithm. - */ - -#define NON_UNIQUE ULONG_MAX - -/* - * This is a hash mapping from line hash to line numbers in the first and - * second file. - */ -struct hashmap { - int nr, alloc; - struct entry { - unsigned long hash; - /* - * 0 = unused entry, 1 = first line, 2 = second, etc. - * line2 is NON_UNIQUE if the line is not unique - * in either the first or the second file. - */ - unsigned long line1, line2; - /* - * "next" & "previous" are used for the longest common - * sequence; - * initially, "next" reflects only the order in file1. - */ - struct entry *next, *previous; - - /* - * If 1, this entry can serve as an anchor. See - * Documentation/diff-options.txt for more information. - */ - unsigned anchor : 1; - } *entries, *first, *last; - /* were common records found? */ - unsigned long has_matches; - mmfile_t *file1, *file2; - xdfenv_t *env; - xpparam_t const *xpp; -}; - -static int is_anchor(xpparam_t const *xpp, const char *line) -{ - int i; - for (i = 0; i < xpp->anchors_nr; i++) { - if (!strncmp(line, xpp->anchors[i], strlen(xpp->anchors[i]))) - return 1; - } - return 0; -} - -/* The argument "pass" is 1 for the first file, 2 for the second. */ -static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map, - int pass) -{ - xrecord_t **records = pass == 1 ? - map->env->xdf1.recs : map->env->xdf2.recs; - xrecord_t *record = records[line - 1]; - /* - * After xdl_prepare_env() (or more precisely, due to - * xdl_classify_record()), the "ha" member of the records (AKA lines) - * is _not_ the hash anymore, but a linearized version of it. In - * other words, the "ha" member is guaranteed to start with 0 and - * the second record's ha can only be 0 or 1, etc. - * - * So we multiply ha by 2 in the hope that the hashing was - * "unique enough". - */ - int index = (int)((record->ha << 1) % map->alloc); - - while (map->entries[index].line1) { - if (map->entries[index].hash != record->ha) { - if (++index >= map->alloc) - index = 0; - continue; - } - if (pass == 2) - map->has_matches = 1; - if (pass == 1 || map->entries[index].line2) - map->entries[index].line2 = NON_UNIQUE; - else - map->entries[index].line2 = line; - return; - } - if (pass == 2) - return; - map->entries[index].line1 = line; - map->entries[index].hash = record->ha; - map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr); - if (!map->first) - map->first = map->entries + index; - if (map->last) { - map->last->next = map->entries + index; - map->entries[index].previous = map->last; - } - map->last = map->entries + index; - map->nr++; -} - -/* - * This function has to be called for each recursion into the inter-hunk - * parts, as previously non-unique lines can become unique when being - * restricted to a smaller part of the files. - * - * It is assumed that env has been prepared using xdl_prepare(). - */ -static int fill_hashmap(mmfile_t *file1, mmfile_t *file2, - xpparam_t const *xpp, xdfenv_t *env, - struct hashmap *result, - int line1, int count1, int line2, int count2) -{ - result->file1 = file1; - result->file2 = file2; - result->xpp = xpp; - result->env = env; - - /* We know exactly how large we want the hash map */ - result->alloc = count1 * 2; - result->entries = (struct entry *) - xdl_malloc(result->alloc * sizeof(struct entry)); - if (!result->entries) - return -1; - memset(result->entries, 0, result->alloc * sizeof(struct entry)); - - /* First, fill with entries from the first file */ - while (count1--) - insert_record(xpp, line1++, result, 1); - - /* Then search for matches in the second file */ - while (count2--) - insert_record(xpp, line2++, result, 2); - - return 0; -} - -/* - * Find the longest sequence with a smaller last element (meaning a smaller - * line2, as we construct the sequence with entries ordered by line1). - */ -static int binary_search(struct entry **sequence, int longest, - struct entry *entry) -{ - int left = -1, right = longest; - - while (left + 1 < right) { - int middle = left + (right - left) / 2; - /* by construction, no two entries can be equal */ - if (sequence[middle]->line2 > entry->line2) - right = middle; - else - left = middle; - } - /* return the index in "sequence", _not_ the sequence length */ - return left; -} - -/* - * The idea is to start with the list of common unique lines sorted by - * the order in file1. For each of these pairs, the longest (partial) - * sequence whose last element's line2 is smaller is determined. - * - * For efficiency, the sequences are kept in a list containing exactly one - * item per sequence length: the sequence with the smallest last - * element (in terms of line2). - */ -static struct entry *find_longest_common_sequence(struct hashmap *map) -{ - struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *)); - int longest = 0, i; - struct entry *entry; - - /* - * If not -1, this entry in sequence must never be overridden. - * Therefore, overriding entries before this has no effect, so - * do not do that either. - */ - int anchor_i = -1; - - for (entry = map->first; entry; entry = entry->next) { - if (!entry->line2 || entry->line2 == NON_UNIQUE) - continue; - i = binary_search(sequence, longest, entry); - entry->previous = i < 0 ? NULL : sequence[i]; - ++i; - if (i <= anchor_i) - continue; - sequence[i] = entry; - if (entry->anchor) { - anchor_i = i; - longest = anchor_i + 1; - } else if (i == longest) { - longest++; - } - } - - /* No common unique lines were found */ - if (!longest) { - xdl_free(sequence); - return NULL; - } - - /* Iterate starting at the last element, adjusting the "next" members */ - entry = sequence[longest - 1]; - entry->next = NULL; - while (entry->previous) { - entry->previous->next = entry; - entry = entry->previous; - } - xdl_free(sequence); - return entry; -} - -static int match(struct hashmap *map, int line1, int line2) -{ - xrecord_t *record1 = map->env->xdf1.recs[line1 - 1]; - xrecord_t *record2 = map->env->xdf2.recs[line2 - 1]; - return record1->ha == record2->ha; -} - -static int patience_diff(mmfile_t *file1, mmfile_t *file2, - xpparam_t const *xpp, xdfenv_t *env, - int line1, int count1, int line2, int count2); - -static int walk_common_sequence(struct hashmap *map, struct entry *first, - int line1, int count1, int line2, int count2) -{ - int end1 = line1 + count1, end2 = line2 + count2; - int next1, next2; - - for (;;) { - /* Try to grow the line ranges of common lines */ - if (first) { - next1 = first->line1; - next2 = first->line2; - while (next1 > line1 && next2 > line2 && - match(map, next1 - 1, next2 - 1)) { - next1--; - next2--; - } - } else { - next1 = end1; - next2 = end2; - } - while (line1 < next1 && line2 < next2 && - match(map, line1, line2)) { - line1++; - line2++; - } - - /* Recurse */ - if (next1 > line1 || next2 > line2) { - if (patience_diff(map->file1, map->file2, - map->xpp, map->env, - line1, next1 - line1, - line2, next2 - line2)) - return -1; - } - - if (!first) - return 0; - - while (first->next && - first->next->line1 == first->line1 + 1 && - first->next->line2 == first->line2 + 1) - first = first->next; - - line1 = first->line1 + 1; - line2 = first->line2 + 1; - - first = first->next; - } -} - -static int fall_back_to_classic_diff(struct hashmap *map, - int line1, int count1, int line2, int count2) -{ - xpparam_t xpp; - - memset(&xpp, 0, sizeof(xpp)); - xpp.flags = map->xpp->flags & ~XDF_DIFF_ALGORITHM_MASK; - - return xdl_fall_back_diff(map->env, &xpp, - line1, count1, line2, count2); -} - -/* - * Recursively find the longest common sequence of unique lines, - * and if none was found, ask xdl_do_diff() to do the job. - * - * This function assumes that env was prepared with xdl_prepare_env(). - */ -static int patience_diff(mmfile_t *file1, mmfile_t *file2, - xpparam_t const *xpp, xdfenv_t *env, - int line1, int count1, int line2, int count2) -{ - struct hashmap map; - struct entry *first; - int result = 0; - - /* trivial case: one side is empty */ - if (!count1) { - while(count2--) - env->xdf2.rchg[line2++ - 1] = 1; - return 0; - } else if (!count2) { - while(count1--) - env->xdf1.rchg[line1++ - 1] = 1; - return 0; - } - - memset(&map, 0, sizeof(map)); - if (fill_hashmap(file1, file2, xpp, env, &map, - line1, count1, line2, count2)) - return -1; - - /* are there any matching lines at all? */ - if (!map.has_matches) { - while(count1--) - env->xdf1.rchg[line1++ - 1] = 1; - while(count2--) - env->xdf2.rchg[line2++ - 1] = 1; - xdl_free(map.entries); - return 0; - } - - first = find_longest_common_sequence(&map); - if (first) - result = walk_common_sequence(&map, first, - line1, count1, line2, count2); - else - result = fall_back_to_classic_diff(&map, - line1, count1, line2, count2); - - xdl_free(map.entries); - return result; -} - -int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2, - xpparam_t const *xpp, xdfenv_t *env) -{ - if (xdl_prepare_env(file1, file2, xpp, env) < 0) - return -1; - - /* environment is cleaned up in xdl_diff() */ - return patience_diff(file1, file2, xpp, env, - 1, env->xdf1.nrec, 1, env->xdf2.nrec); -} diff --git a/src/xdiff/xprepare.c b/src/xdiff/xprepare.c deleted file mode 100644 index 4527a4a07..000000000 --- a/src/xdiff/xprepare.c +++ /dev/null @@ -1,478 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#include "xinclude.h" - - -#define XDL_KPDIS_RUN 4 -#define XDL_MAX_EQLIMIT 1024 -#define XDL_SIMSCAN_WINDOW 100 -#define XDL_GUESS_NLINES1 256 -#define XDL_GUESS_NLINES2 20 - - -typedef struct s_xdlclass { - struct s_xdlclass *next; - unsigned long ha; - char const *line; - long size; - long idx; - long len1, len2; -} xdlclass_t; - -typedef struct s_xdlclassifier { - unsigned int hbits; - long hsize; - xdlclass_t **rchash; - chastore_t ncha; - xdlclass_t **rcrecs; - long alloc; - long count; - long flags; -} xdlclassifier_t; - - - - -static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags); -static void xdl_free_classifier(xdlclassifier_t *cf); -static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, - unsigned int hbits, xrecord_t *rec); -static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, - xdlclassifier_t *cf, xdfile_t *xdf); -static void xdl_free_ctx(xdfile_t *xdf); -static int xdl_clean_mmatch(char const *dis, long i, long s, long e); -static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); -static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2); -static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); - - - - -static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) { - cf->flags = flags; - - cf->hbits = xdl_hashbits((unsigned int) size); - cf->hsize = 1 << cf->hbits; - - if (xdl_cha_init(&cf->ncha, sizeof(xdlclass_t), size / 4 + 1) < 0) { - - return -1; - } - if (!(cf->rchash = (xdlclass_t **) xdl_malloc(cf->hsize * sizeof(xdlclass_t *)))) { - - xdl_cha_free(&cf->ncha); - return -1; - } - memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *)); - - cf->alloc = size; - if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) { - - xdl_free(cf->rchash); - xdl_cha_free(&cf->ncha); - return -1; - } - - cf->count = 0; - - return 0; -} - - -static void xdl_free_classifier(xdlclassifier_t *cf) { - - xdl_free(cf->rcrecs); - xdl_free(cf->rchash); - xdl_cha_free(&cf->ncha); -} - - -static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, - unsigned int hbits, xrecord_t *rec) { - long hi; - char const *line; - xdlclass_t *rcrec; - xdlclass_t **rcrecs; - - line = rec->ptr; - hi = (long) XDL_HASHLONG(rec->ha, cf->hbits); - for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next) - if (rcrec->ha == rec->ha && - xdl_recmatch(rcrec->line, rcrec->size, - rec->ptr, rec->size, cf->flags)) - break; - - if (!rcrec) { - if (!(rcrec = xdl_cha_alloc(&cf->ncha))) { - - return -1; - } - rcrec->idx = cf->count++; - if (cf->count > cf->alloc) { - cf->alloc *= 2; - if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) { - - return -1; - } - cf->rcrecs = rcrecs; - } - cf->rcrecs[rcrec->idx] = rcrec; - rcrec->line = line; - rcrec->size = rec->size; - rcrec->ha = rec->ha; - rcrec->len1 = rcrec->len2 = 0; - rcrec->next = cf->rchash[hi]; - cf->rchash[hi] = rcrec; - } - - (pass == 1) ? rcrec->len1++ : rcrec->len2++; - - rec->ha = (unsigned long) rcrec->idx; - - hi = (long) XDL_HASHLONG(rec->ha, hbits); - rec->next = rhash[hi]; - rhash[hi] = rec; - - return 0; -} - - -static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, - xdlclassifier_t *cf, xdfile_t *xdf) { - unsigned int hbits; - long nrec, hsize, bsize; - unsigned long hav; - char const *blk, *cur, *top, *prev; - xrecord_t *crec; - xrecord_t **recs, **rrecs; - xrecord_t **rhash; - unsigned long *ha; - char *rchg; - long *rindex; - - ha = NULL; - rindex = NULL; - rchg = NULL; - rhash = NULL; - recs = NULL; - - if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0) - goto abort; - if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *)))) - goto abort; - - hbits = xdl_hashbits((unsigned int) narec); - hsize = 1 << hbits; - if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *)))) - goto abort; - memset(rhash, 0, hsize * sizeof(xrecord_t *)); - - nrec = 0; - if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) { - for (top = blk + bsize; cur < top; ) { - prev = cur; - hav = xdl_hash_record(&cur, top, xpp->flags); - if (nrec >= narec) { - narec *= 2; - if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *)))) - goto abort; - recs = rrecs; - } - if (!(crec = xdl_cha_alloc(&xdf->rcha))) - goto abort; - crec->ptr = prev; - crec->size = (long) (cur - prev); - crec->ha = hav; - recs[nrec++] = crec; - if (xdl_classify_record(pass, cf, rhash, hbits, crec) < 0) - goto abort; - } - } - - if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char)))) - goto abort; - memset(rchg, 0, (nrec + 2) * sizeof(char)); - - if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) && - (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) { - if (!(rindex = xdl_malloc((nrec + 1) * sizeof(*rindex)))) - goto abort; - if (!(ha = xdl_malloc((nrec + 1) * sizeof(*ha)))) - goto abort; - } - - xdf->nrec = nrec; - xdf->recs = recs; - xdf->hbits = hbits; - xdf->rhash = rhash; - xdf->rchg = rchg + 1; - xdf->rindex = rindex; - xdf->nreff = 0; - xdf->ha = ha; - xdf->dstart = 0; - xdf->dend = nrec - 1; - - return 0; - -abort: - xdl_free(ha); - xdl_free(rindex); - xdl_free(rchg); - xdl_free(rhash); - xdl_free(recs); - xdl_cha_free(&xdf->rcha); - return -1; -} - - -static void xdl_free_ctx(xdfile_t *xdf) { - - xdl_free(xdf->rhash); - xdl_free(xdf->rindex); - xdl_free(xdf->rchg - 1); - xdl_free(xdf->ha); - xdl_free(xdf->recs); - xdl_cha_free(&xdf->rcha); -} - - -int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdfenv_t *xe) { - long enl1, enl2, sample; - xdlclassifier_t cf; - - memset(&cf, 0, sizeof(cf)); - - /* - * For histogram diff, we can afford a smaller sample size and - * thus a poorer estimate of the number of lines, as the hash - * table (rhash) won't be filled up/grown. The number of lines - * (nrecs) will be updated correctly anyway by - * xdl_prepare_ctx(). - */ - sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF - ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1); - - enl1 = xdl_guess_lines(mf1, sample) + 1; - enl2 = xdl_guess_lines(mf2, sample) + 1; - - if (xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0) - return -1; - - if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) { - - xdl_free_classifier(&cf); - return -1; - } - if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) { - - xdl_free_ctx(&xe->xdf1); - xdl_free_classifier(&cf); - return -1; - } - - if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) && - (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) && - xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) { - - xdl_free_ctx(&xe->xdf2); - xdl_free_ctx(&xe->xdf1); - xdl_free_classifier(&cf); - return -1; - } - - xdl_free_classifier(&cf); - - return 0; -} - - -void xdl_free_env(xdfenv_t *xe) { - - xdl_free_ctx(&xe->xdf2); - xdl_free_ctx(&xe->xdf1); -} - - -static int xdl_clean_mmatch(char const *dis, long i, long s, long e) { - long r, rdis0, rpdis0, rdis1, rpdis1; - - /* - * Limits the window the is examined during the similar-lines - * scan. The loops below stops when dis[i - r] == 1 (line that - * has no match), but there are corner cases where the loop - * proceed all the way to the extremities by causing huge - * performance penalties in case of big files. - */ - if (i - s > XDL_SIMSCAN_WINDOW) - s = i - XDL_SIMSCAN_WINDOW; - if (e - i > XDL_SIMSCAN_WINDOW) - e = i + XDL_SIMSCAN_WINDOW; - - /* - * Scans the lines before 'i' to find a run of lines that either - * have no match (dis[j] == 0) or have multiple matches (dis[j] > 1). - * Note that we always call this function with dis[i] > 1, so the - * current line (i) is already a multimatch line. - */ - for (r = 1, rdis0 = 0, rpdis0 = 1; (i - r) >= s; r++) { - if (!dis[i - r]) - rdis0++; - else if (dis[i - r] == 2) - rpdis0++; - else - break; - } - /* - * If the run before the line 'i' found only multimatch lines, we - * return 0 and hence we don't make the current line (i) discarded. - * We want to discard multimatch lines only when they appear in the - * middle of runs with nomatch lines (dis[j] == 0). - */ - if (rdis0 == 0) - return 0; - for (r = 1, rdis1 = 0, rpdis1 = 1; (i + r) <= e; r++) { - if (!dis[i + r]) - rdis1++; - else if (dis[i + r] == 2) - rpdis1++; - else - break; - } - /* - * If the run after the line 'i' found only multimatch lines, we - * return 0 and hence we don't make the current line (i) discarded. - */ - if (rdis1 == 0) - return 0; - rdis1 += rdis0; - rpdis1 += rpdis0; - - return rpdis1 * XDL_KPDIS_RUN < (rpdis1 + rdis1); -} - - -/* - * Try to reduce the problem complexity, discard records that have no - * matches on the other file. Also, lines that have multiple matches - * might be potentially discarded if they happear in a run of discardable. - */ -static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { - long i, nm, nreff, mlim; - xrecord_t **recs; - xdlclass_t *rcrec; - char *dis, *dis1, *dis2; - - if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) { - - return -1; - } - memset(dis, 0, xdf1->nrec + xdf2->nrec + 2); - dis1 = dis; - dis2 = dis1 + xdf1->nrec + 1; - - if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT) - mlim = XDL_MAX_EQLIMIT; - for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) { - rcrec = cf->rcrecs[(*recs)->ha]; - nm = rcrec ? rcrec->len2 : 0; - dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; - } - - if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT) - mlim = XDL_MAX_EQLIMIT; - for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) { - rcrec = cf->rcrecs[(*recs)->ha]; - nm = rcrec ? rcrec->len1 : 0; - dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; - } - - for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; - i <= xdf1->dend; i++, recs++) { - if (dis1[i] == 1 || - (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) { - xdf1->rindex[nreff] = i; - xdf1->ha[nreff] = (*recs)->ha; - nreff++; - } else - xdf1->rchg[i] = 1; - } - xdf1->nreff = nreff; - - for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; - i <= xdf2->dend; i++, recs++) { - if (dis2[i] == 1 || - (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) { - xdf2->rindex[nreff] = i; - xdf2->ha[nreff] = (*recs)->ha; - nreff++; - } else - xdf2->rchg[i] = 1; - } - xdf2->nreff = nreff; - - xdl_free(dis); - - return 0; -} - - -/* - * Early trim initial and terminal matching records. - */ -static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) { - long i, lim; - xrecord_t **recs1, **recs2; - - recs1 = xdf1->recs; - recs2 = xdf2->recs; - for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim; - i++, recs1++, recs2++) - if ((*recs1)->ha != (*recs2)->ha) - break; - - xdf1->dstart = xdf2->dstart = i; - - recs1 = xdf1->recs + xdf1->nrec - 1; - recs2 = xdf2->recs + xdf2->nrec - 1; - for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--) - if ((*recs1)->ha != (*recs2)->ha) - break; - - xdf1->dend = xdf1->nrec - i - 1; - xdf2->dend = xdf2->nrec - i - 1; - - return 0; -} - - -static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { - - if (xdl_trim_ends(xdf1, xdf2) < 0 || - xdl_cleanup_records(cf, xdf1, xdf2) < 0) { - - return -1; - } - - return 0; -} diff --git a/src/xdiff/xprepare.h b/src/xdiff/xprepare.h deleted file mode 100644 index 947d9fc1b..000000000 --- a/src/xdiff/xprepare.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XPREPARE_H) -#define XPREPARE_H - - - -int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, - xdfenv_t *xe); -void xdl_free_env(xdfenv_t *xe); - - - -#endif /* #if !defined(XPREPARE_H) */ diff --git a/src/xdiff/xtypes.h b/src/xdiff/xtypes.h deleted file mode 100644 index 8442bd436..000000000 --- a/src/xdiff/xtypes.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XTYPES_H) -#define XTYPES_H - - - -typedef struct s_chanode { - struct s_chanode *next; - long icurr; -} chanode_t; - -typedef struct s_chastore { - chanode_t *head, *tail; - long isize, nsize; - chanode_t *ancur; - chanode_t *sncur; - long scurr; -} chastore_t; - -typedef struct s_xrecord { - struct s_xrecord *next; - char const *ptr; - long size; - unsigned long ha; -} xrecord_t; - -typedef struct s_xdfile { - chastore_t rcha; - long nrec; - unsigned int hbits; - xrecord_t **rhash; - long dstart, dend; - xrecord_t **recs; - char *rchg; - long *rindex; - long nreff; - unsigned long *ha; -} xdfile_t; - -typedef struct s_xdfenv { - xdfile_t xdf1, xdf2; -} xdfenv_t; - - - -#endif /* #if !defined(XTYPES_H) */ diff --git a/src/xdiff/xutils.c b/src/xdiff/xutils.c deleted file mode 100644 index cfa6e2220..000000000 --- a/src/xdiff/xutils.c +++ /dev/null @@ -1,434 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#include "xinclude.h" - - -long xdl_bogosqrt(long n) { - long i; - - /* - * Classical integer square root approximation using shifts. - */ - for (i = 1; n > 0; n >>= 2) - i <<= 1; - - return i; -} - - -int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, - xdemitcb_t *ecb) { - int i = 2; - mmbuffer_t mb[3]; - - mb[0].ptr = (char *) pre; - mb[0].size = psize; - mb[1].ptr = (char *) rec; - mb[1].size = size; - if (size > 0 && rec[size - 1] != '\n') { - mb[2].ptr = (char *) "\n\\ No newline at end of file\n"; - mb[2].size = strlen(mb[2].ptr); - i++; - } - if (ecb->out_line(ecb->priv, mb, i) < 0) { - - return -1; - } - - return 0; -} - -void *xdl_mmfile_first(mmfile_t *mmf, long *size) -{ - *size = mmf->size; - return mmf->ptr; -} - - -long xdl_mmfile_size(mmfile_t *mmf) -{ - return mmf->size; -} - - -int xdl_cha_init(chastore_t *cha, long isize, long icount) { - - cha->head = cha->tail = NULL; - cha->isize = isize; - cha->nsize = icount * isize; - cha->ancur = cha->sncur = NULL; - cha->scurr = 0; - - return 0; -} - - -void xdl_cha_free(chastore_t *cha) { - chanode_t *cur, *tmp; - - for (cur = cha->head; (tmp = cur) != NULL;) { - cur = cur->next; - xdl_free(tmp); - } -} - - -void *xdl_cha_alloc(chastore_t *cha) { - chanode_t *ancur; - void *data; - - if (!(ancur = cha->ancur) || ancur->icurr == cha->nsize) { - if (!(ancur = (chanode_t *) xdl_malloc(sizeof(chanode_t) + cha->nsize))) { - - return NULL; - } - ancur->icurr = 0; - ancur->next = NULL; - if (cha->tail) - cha->tail->next = ancur; - if (!cha->head) - cha->head = ancur; - cha->tail = ancur; - cha->ancur = ancur; - } - - data = (char *) ancur + sizeof(chanode_t) + ancur->icurr; - ancur->icurr += cha->isize; - - return data; -} - -long xdl_guess_lines(mmfile_t *mf, long sample) { - long nl = 0, size, tsize = 0; - char const *data, *cur, *top; - - if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) { - for (top = data + size; nl < sample && cur < top; ) { - nl++; - if (!(cur = memchr(cur, '\n', top - cur))) - cur = top; - else - cur++; - } - tsize += (long) (cur - data); - } - - if (nl && tsize) - nl = xdl_mmfile_size(mf) / (tsize / nl); - - return nl + 1; -} - -int xdl_blankline(const char *line, long size, long flags) -{ - long i; - - if (!(flags & XDF_WHITESPACE_FLAGS)) - return (size <= 1); - - for (i = 0; i < size && XDL_ISSPACE(line[i]); i++) - ; - - return (i == size); -} - -/* - * Have we eaten everything on the line, except for an optional - * CR at the very end? - */ -static int ends_with_optional_cr(const char *l, long s, long i) -{ - int complete = s && l[s-1] == '\n'; - - if (complete) - s--; - if (s == i) - return 1; - /* do not ignore CR at the end of an incomplete line */ - if (complete && s == i + 1 && l[i] == '\r') - return 1; - return 0; -} - -int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) -{ - int i1, i2; - - if (s1 == s2 && !memcmp(l1, l2, s1)) - return 1; - if (!(flags & XDF_WHITESPACE_FLAGS)) - return 0; - - i1 = 0; - i2 = 0; - - /* - * -w matches everything that matches with -b, and -b in turn - * matches everything that matches with --ignore-space-at-eol, - * which in turn matches everything that matches with --ignore-cr-at-eol. - * - * Each flavor of ignoring needs different logic to skip whitespaces - * while we have both sides to compare. - */ - if (flags & XDF_IGNORE_WHITESPACE) { - goto skip_ws; - while (i1 < s1 && i2 < s2) { - if (l1[i1++] != l2[i2++]) - return 0; - skip_ws: - while (i1 < s1 && XDL_ISSPACE(l1[i1])) - i1++; - while (i2 < s2 && XDL_ISSPACE(l2[i2])) - i2++; - } - } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { - while (i1 < s1 && i2 < s2) { - if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) { - /* Skip matching spaces and try again */ - while (i1 < s1 && XDL_ISSPACE(l1[i1])) - i1++; - while (i2 < s2 && XDL_ISSPACE(l2[i2])) - i2++; - continue; - } - if (l1[i1++] != l2[i2++]) - return 0; - } - } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) { - while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) { - i1++; - i2++; - } - } else if (flags & XDF_IGNORE_CR_AT_EOL) { - /* Find the first difference and see how the line ends */ - while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) { - i1++; - i2++; - } - return (ends_with_optional_cr(l1, s1, i1) && - ends_with_optional_cr(l2, s2, i2)); - } - - /* - * After running out of one side, the remaining side must have - * nothing but whitespace for the lines to match. Note that - * ignore-whitespace-at-eol case may break out of the loop - * while there still are characters remaining on both lines. - */ - if (i1 < s1) { - while (i1 < s1 && XDL_ISSPACE(l1[i1])) - i1++; - if (s1 != i1) - return 0; - } - if (i2 < s2) { - while (i2 < s2 && XDL_ISSPACE(l2[i2])) - i2++; - return (s2 == i2); - } - return 1; -} - -static unsigned long xdl_hash_record_with_whitespace(char const **data, - char const *top, long flags) { - unsigned long ha = 5381; - char const *ptr = *data; - int cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL; - - for (; ptr < top && *ptr != '\n'; ptr++) { - if (cr_at_eol_only) { - /* do not ignore CR at the end of an incomplete line */ - if (*ptr == '\r' && - (ptr + 1 < top && ptr[1] == '\n')) - continue; - } - else if (XDL_ISSPACE(*ptr)) { - const char *ptr2 = ptr; - int at_eol; - while (ptr + 1 < top && XDL_ISSPACE(ptr[1]) - && ptr[1] != '\n') - ptr++; - at_eol = (top <= ptr + 1 || ptr[1] == '\n'); - if (flags & XDF_IGNORE_WHITESPACE) - ; /* already handled */ - else if (flags & XDF_IGNORE_WHITESPACE_CHANGE - && !at_eol) { - ha += (ha << 5); - ha ^= (unsigned long) ' '; - } - else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL - && !at_eol) { - while (ptr2 != ptr + 1) { - ha += (ha << 5); - ha ^= (unsigned long) *ptr2; - ptr2++; - } - } - continue; - } - ha += (ha << 5); - ha ^= (unsigned long) *ptr; - } - *data = ptr < top ? ptr + 1: ptr; - - return ha; -} - -unsigned long xdl_hash_record(char const **data, char const *top, long flags) { - unsigned long ha = 5381; - char const *ptr = *data; - - if (flags & XDF_WHITESPACE_FLAGS) - return xdl_hash_record_with_whitespace(data, top, flags); - - for (; ptr < top && *ptr != '\n'; ptr++) { - ha += (ha << 5); - ha ^= (unsigned long) *ptr; - } - *data = ptr < top ? ptr + 1: ptr; - - return ha; -} - -unsigned int xdl_hashbits(unsigned int size) { - unsigned int val = 1, bits = 0; - - for (; val < size && bits < CHAR_BIT * sizeof(unsigned int); val <<= 1, bits++); - return bits ? bits: 1; -} - - -int xdl_num_out(char *out, long val) { - char *ptr, *str = out; - char buf[32]; - - ptr = buf + sizeof(buf) - 1; - *ptr = '\0'; - if (val < 0) { - *--ptr = '-'; - val = -val; - } - for (; val && ptr > buf; val /= 10) - *--ptr = "0123456789"[val % 10]; - if (*ptr) - for (; *ptr; ptr++, str++) - *str = *ptr; - else - *str++ = '0'; - *str = '\0'; - - return str - out; -} - -static int xdl_format_hunk_hdr(long s1, long c1, long s2, long c2, - const char *func, long funclen, - xdemitcb_t *ecb) { - int nb = 0; - mmbuffer_t mb; - char buf[128]; - - memcpy(buf, "@@ -", 4); - nb += 4; - - nb += xdl_num_out(buf + nb, c1 ? s1: s1 - 1); - - if (c1 != 1) { - memcpy(buf + nb, ",", 1); - nb += 1; - - nb += xdl_num_out(buf + nb, c1); - } - - memcpy(buf + nb, " +", 2); - nb += 2; - - nb += xdl_num_out(buf + nb, c2 ? s2: s2 - 1); - - if (c2 != 1) { - memcpy(buf + nb, ",", 1); - nb += 1; - - nb += xdl_num_out(buf + nb, c2); - } - - memcpy(buf + nb, " @@", 3); - nb += 3; - if (func && funclen) { - buf[nb++] = ' '; - if (funclen > sizeof(buf) - nb - 1) - funclen = sizeof(buf) - nb - 1; - memcpy(buf + nb, func, funclen); - nb += funclen; - } - buf[nb++] = '\n'; - - mb.ptr = buf; - mb.size = nb; - if (ecb->out_line(ecb->priv, &mb, 1) < 0) - return -1; - return 0; -} - -int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, - const char *func, long funclen, - xdemitcb_t *ecb) { - if (!ecb->out_hunk) - return xdl_format_hunk_hdr(s1, c1, s2, c2, func, funclen, ecb); - if (ecb->out_hunk(ecb->priv, - c1 ? s1 : s1 - 1, c1, - c2 ? s2 : s2 - 1, c2, - func, funclen) < 0) - return -1; - return 0; -} - -int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, - int line1, int count1, int line2, int count2) -{ - /* - * This probably does not work outside Git, since - * we have a very simple mmfile structure. - * - * Note: ideally, we would reuse the prepared environment, but - * the libxdiff interface does not (yet) allow for diffing only - * ranges of lines instead of the whole files. - */ - mmfile_t subfile1, subfile2; - xdfenv_t env; - - subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr; - subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr + - diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr; - subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr; - subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr + - diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr; - if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0) - return -1; - - memcpy(diff_env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1); - memcpy(diff_env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2); - - xdl_free_env(&env); - - return 0; -} diff --git a/src/xdiff/xutils.h b/src/xdiff/xutils.h deleted file mode 100644 index fba7bae03..000000000 --- a/src/xdiff/xutils.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * LibXDiff by Davide Libenzi ( File Differential Library ) - * Copyright (C) 2003 Davide Libenzi - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see - * . - * - * Davide Libenzi - * - */ - -#if !defined(XUTILS_H) -#define XUTILS_H - - - -long xdl_bogosqrt(long n); -int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, - xdemitcb_t *ecb); -int xdl_cha_init(chastore_t *cha, long isize, long icount); -void xdl_cha_free(chastore_t *cha); -void *xdl_cha_alloc(chastore_t *cha); -long xdl_guess_lines(mmfile_t *mf, long sample); -int xdl_blankline(const char *line, long size, long flags); -int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags); -unsigned long xdl_hash_record(char const **data, char const *top, long flags); -unsigned int xdl_hashbits(unsigned int size); -int xdl_num_out(char *out, long val); -int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, - const char *func, long funclen, xdemitcb_t *ecb); -int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, - int line1, int count1, int line2, int count2); - - - -#endif /* #if !defined(XUTILS_H) */ diff --git a/src/zstream.c b/src/zstream.c deleted file mode 100644 index cb8b125ed..000000000 --- a/src/zstream.c +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "zstream.h" - -#include - -#include "str.h" - -#define ZSTREAM_BUFFER_SIZE (1024 * 1024) -#define ZSTREAM_BUFFER_MIN_EXTRA 8 - -GIT_INLINE(int) zstream_seterr(git_zstream *zs) -{ - switch (zs->zerr) { - case Z_OK: - case Z_STREAM_END: - case Z_BUF_ERROR: /* not fatal; we retry with a larger buffer */ - return 0; - case Z_MEM_ERROR: - git_error_set_oom(); - break; - default: - if (zs->z.msg) - git_error_set_str(GIT_ERROR_ZLIB, zs->z.msg); - else - git_error_set(GIT_ERROR_ZLIB, "unknown compression error"); - } - - return -1; -} - -int git_zstream_init(git_zstream *zstream, git_zstream_t type) -{ - zstream->type = type; - - if (zstream->type == GIT_ZSTREAM_INFLATE) - zstream->zerr = inflateInit(&zstream->z); - else - zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION); - return zstream_seterr(zstream); -} - -void git_zstream_free(git_zstream *zstream) -{ - if (zstream->type == GIT_ZSTREAM_INFLATE) - inflateEnd(&zstream->z); - else - deflateEnd(&zstream->z); -} - -void git_zstream_reset(git_zstream *zstream) -{ - if (zstream->type == GIT_ZSTREAM_INFLATE) - inflateReset(&zstream->z); - else - deflateReset(&zstream->z); - zstream->in = NULL; - zstream->in_len = 0; - zstream->zerr = Z_STREAM_END; -} - -int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len) -{ - zstream->in = in; - zstream->in_len = in_len; - zstream->zerr = Z_OK; - return 0; -} - -bool git_zstream_done(git_zstream *zstream) -{ - return (!zstream->in_len && zstream->zerr == Z_STREAM_END); -} - -bool git_zstream_eos(git_zstream *zstream) -{ - return zstream->zerr == Z_STREAM_END; -} - -size_t git_zstream_suggest_output_len(git_zstream *zstream) -{ - if (zstream->in_len > ZSTREAM_BUFFER_SIZE) - return ZSTREAM_BUFFER_SIZE; - else if (zstream->in_len > ZSTREAM_BUFFER_MIN_EXTRA) - return zstream->in_len; - else - return ZSTREAM_BUFFER_MIN_EXTRA; -} - -int git_zstream_get_output_chunk( - void *out, size_t *out_len, git_zstream *zstream) -{ - size_t in_queued, in_used, out_queued; - - /* set up input data */ - zstream->z.next_in = (Bytef *)zstream->in; - - /* feed as much data to zlib as it can consume, at most UINT_MAX */ - if (zstream->in_len > UINT_MAX) { - zstream->z.avail_in = UINT_MAX; - zstream->flush = Z_NO_FLUSH; - } else { - zstream->z.avail_in = (uInt)zstream->in_len; - zstream->flush = Z_FINISH; - } - in_queued = (size_t)zstream->z.avail_in; - - /* set up output data */ - zstream->z.next_out = out; - zstream->z.avail_out = (uInt)*out_len; - - if ((size_t)zstream->z.avail_out != *out_len) - zstream->z.avail_out = UINT_MAX; - out_queued = (size_t)zstream->z.avail_out; - - /* compress next chunk */ - if (zstream->type == GIT_ZSTREAM_INFLATE) - zstream->zerr = inflate(&zstream->z, zstream->flush); - else - zstream->zerr = deflate(&zstream->z, zstream->flush); - - if (zstream_seterr(zstream)) - return -1; - - in_used = (in_queued - zstream->z.avail_in); - zstream->in_len -= in_used; - zstream->in += in_used; - - *out_len = (out_queued - zstream->z.avail_out); - - return 0; -} - -int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) -{ - size_t out_remain = *out_len; - - if (zstream->in_len && zstream->zerr == Z_STREAM_END) { - git_error_set(GIT_ERROR_ZLIB, "zlib input had trailing garbage"); - return -1; - } - - while (out_remain > 0 && zstream->zerr != Z_STREAM_END) { - size_t out_written = out_remain; - - if (git_zstream_get_output_chunk(out, &out_written, zstream) < 0) - return -1; - - out_remain -= out_written; - out = ((char *)out) + out_written; - } - - /* either we finished the input or we did not flush the data */ - GIT_ASSERT(zstream->in_len > 0 || zstream->flush == Z_FINISH); - - /* set out_size to number of bytes actually written to output */ - *out_len = *out_len - out_remain; - - return 0; -} - -static int zstream_buf(git_str *out, const void *in, size_t in_len, git_zstream_t type) -{ - git_zstream zs = GIT_ZSTREAM_INIT; - int error = 0; - - if ((error = git_zstream_init(&zs, type)) < 0) - return error; - - if ((error = git_zstream_set_input(&zs, in, in_len)) < 0) - goto done; - - while (!git_zstream_done(&zs)) { - size_t step = git_zstream_suggest_output_len(&zs), written; - - if ((error = git_str_grow_by(out, step)) < 0) - goto done; - - written = out->asize - out->size; - - if ((error = git_zstream_get_output( - out->ptr + out->size, &written, &zs)) < 0) - goto done; - - out->size += written; - } - - /* NULL terminate for consistency if possible */ - if (out->size < out->asize) - out->ptr[out->size] = '\0'; - -done: - git_zstream_free(&zs); - return error; -} - -int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len) -{ - return zstream_buf(out, in, in_len, GIT_ZSTREAM_DEFLATE); -} - -int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len) -{ - return zstream_buf(out, in, in_len, GIT_ZSTREAM_INFLATE); -} diff --git a/src/zstream.h b/src/zstream.h deleted file mode 100644 index 3f8b1c72f..000000000 --- a/src/zstream.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_zstream_h__ -#define INCLUDE_zstream_h__ - -#include "common.h" - -#include - -#include "str.h" - -typedef enum { - GIT_ZSTREAM_INFLATE, - GIT_ZSTREAM_DEFLATE -} git_zstream_t; - -typedef struct { - z_stream z; - git_zstream_t type; - const char *in; - size_t in_len; - int flush; - int zerr; -} git_zstream; - -#define GIT_ZSTREAM_INIT {{0}} - -int git_zstream_init(git_zstream *zstream, git_zstream_t type); -void git_zstream_free(git_zstream *zstream); - -int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len); - -size_t git_zstream_suggest_output_len(git_zstream *zstream); - -/* get as much output as is available in the input buffer */ -int git_zstream_get_output_chunk( - void *out, size_t *out_len, git_zstream *zstream); - -/* get all the output from the entire input buffer */ -int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream); - -bool git_zstream_done(git_zstream *zstream); -bool git_zstream_eos(git_zstream *zstream); - -void git_zstream_reset(git_zstream *zstream); - -int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len); -int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len); - -#endif -- cgit v1.2.1 From c3b7ace9cf3216928a31886b32c264e0cd3cde75 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 14 Nov 2021 16:43:53 -0500 Subject: refactor: make util an object library Instead of simply including the utility files directly, make them a cmake object library for easy reusability between other projects within libgit2. Now the top-level `src` is responsible for platform selection, while the next-level `libgit2` and `util` configurations are responsible for identifying what objects they include. --- cmake/SelectHashes.cmake | 3 - src/CMakeLists.txt | 178 +++ src/README.md | 10 + src/features.h.in | 53 + src/libgit2/CMakeLists.txt | 183 ++- src/libgit2/alloc.c | 56 - src/libgit2/alloc.h | 40 - src/libgit2/allocators/failalloc.c | 92 -- src/libgit2/allocators/failalloc.h | 23 - src/libgit2/allocators/stdalloc.c | 150 --- src/libgit2/allocators/stdalloc.h | 17 - src/libgit2/allocators/win32_leakcheck.c | 118 -- src/libgit2/allocators/win32_leakcheck.h | 17 - src/libgit2/array.h | 124 -- src/libgit2/assert_safe.h | 58 - src/libgit2/bitvec.h | 75 -- src/libgit2/blob.c | 2 +- src/libgit2/cc-compat.h | 106 -- src/libgit2/common.h | 189 +-- src/libgit2/date.c | 898 -------------- src/libgit2/date.h | 33 - src/libgit2/diff_xdiff.c | 1 + src/libgit2/features.h.in | 53 - src/libgit2/filebuf.c | 595 --------- src/libgit2/filebuf.h | 94 -- src/libgit2/filter.c | 2 +- src/libgit2/fs_path.c | 1912 ----------------------------- src/libgit2/fs_path.h | 752 ------------ src/libgit2/futils.c | 1194 ------------------- src/libgit2/futils.h | 402 ------- src/libgit2/git2.rc | 59 + src/libgit2/hash.c | 142 --- src/libgit2/hash.h | 46 - src/libgit2/hash/sha1.h | 40 - src/libgit2/hash/sha1/collisiondetect.c | 48 - src/libgit2/hash/sha1/collisiondetect.h | 19 - src/libgit2/hash/sha1/common_crypto.c | 57 - src/libgit2/hash/sha1/common_crypto.h | 19 - src/libgit2/hash/sha1/generic.c | 300 ----- src/libgit2/hash/sha1/generic.h | 19 - src/libgit2/hash/sha1/mbedtls.c | 46 - src/libgit2/hash/sha1/mbedtls.h | 19 - src/libgit2/hash/sha1/openssl.c | 59 - src/libgit2/hash/sha1/openssl.h | 19 - src/libgit2/hash/sha1/sha1dc/sha1.c | 1909 ----------------------------- src/libgit2/hash/sha1/sha1dc/sha1.h | 110 -- src/libgit2/hash/sha1/sha1dc/ubc_check.c | 372 ------ src/libgit2/hash/sha1/sha1dc/ubc_check.h | 52 - src/libgit2/hash/sha1/win32.c | 333 ------ src/libgit2/hash/sha1/win32.h | 128 -- src/libgit2/integer.h | 218 ---- src/libgit2/khash.h | 615 ---------- src/libgit2/map.h | 46 - src/libgit2/net.c | 750 ------------ src/libgit2/net.h | 78 -- src/libgit2/netops.c | 1 - src/libgit2/odb.c | 2 +- src/libgit2/path.c | 1 + src/libgit2/pool.c | 260 ---- src/libgit2/pool.h | 146 --- src/libgit2/posix.c | 303 ----- src/libgit2/posix.h | 196 --- src/libgit2/pqueue.c | 125 -- src/libgit2/pqueue.h | 59 - src/libgit2/regexp.c | 221 ---- src/libgit2/regexp.h | 97 -- src/libgit2/runtime.c | 162 --- src/libgit2/runtime.h | 62 - src/libgit2/sortedcache.c | 380 ------ src/libgit2/sortedcache.h | 182 --- src/libgit2/str.c | 1372 --------------------- src/libgit2/str.h | 357 ------ src/libgit2/strmap.c | 100 -- src/libgit2/strmap.h | 131 -- src/libgit2/strnlen.h | 24 - src/libgit2/thread.c | 140 --- src/libgit2/thread.h | 479 -------- src/libgit2/tsort.c | 382 ------ src/libgit2/unix/map.c | 76 -- src/libgit2/unix/posix.h | 104 -- src/libgit2/unix/pthread.h | 57 - src/libgit2/unix/realpath.c | 32 - src/libgit2/utf8.c | 150 --- src/libgit2/utf8.h | 52 - src/libgit2/util.c | 819 ------------- src/libgit2/util.h | 387 ------ src/libgit2/util/platform.h.in | 34 - src/libgit2/varint.c | 43 - src/libgit2/varint.h | 17 - src/libgit2/vector.c | 431 ------- src/libgit2/vector.h | 128 -- src/libgit2/wildmatch.c | 320 ----- src/libgit2/wildmatch.h | 23 - src/libgit2/win32/dir.c | 122 -- src/libgit2/win32/dir.h | 44 - src/libgit2/win32/error.c | 53 - src/libgit2/win32/error.h | 15 - src/libgit2/win32/findfile.c | 286 ----- src/libgit2/win32/findfile.h | 22 - src/libgit2/win32/git2.rc | 59 - src/libgit2/win32/map.c | 141 --- src/libgit2/win32/mingw-compat.h | 23 - src/libgit2/win32/msvc-compat.h | 36 - src/libgit2/win32/path_w32.c | 642 ---------- src/libgit2/win32/path_w32.h | 91 -- src/libgit2/win32/posix.h | 62 - src/libgit2/win32/posix_w32.c | 1047 ---------------- src/libgit2/win32/precompiled.c | 1 - src/libgit2/win32/precompiled.h | 21 - src/libgit2/win32/reparse.h | 57 - src/libgit2/win32/thread.c | 262 ---- src/libgit2/win32/thread.h | 64 - src/libgit2/win32/utf-conv.c | 146 --- src/libgit2/win32/utf-conv.h | 60 - src/libgit2/win32/version.h | 37 - src/libgit2/win32/w32_buffer.c | 57 - src/libgit2/win32/w32_buffer.h | 19 - src/libgit2/win32/w32_common.h | 48 - src/libgit2/win32/w32_leakcheck.c | 581 --------- src/libgit2/win32/w32_leakcheck.h | 222 ---- src/libgit2/win32/w32_util.c | 126 -- src/libgit2/win32/w32_util.h | 144 --- src/libgit2/win32/win32-compat.h | 52 - src/libgit2/zstream.c | 210 ---- src/libgit2/zstream.h | 54 - src/rand.c | 226 ---- src/rand.h | 37 - src/util/CMakeLists.txt | 59 + src/util/alloc.c | 56 + src/util/alloc.h | 40 + src/util/allocators/failalloc.c | 92 ++ src/util/allocators/failalloc.h | 23 + src/util/allocators/stdalloc.c | 150 +++ src/util/allocators/stdalloc.h | 17 + src/util/allocators/win32_leakcheck.c | 118 ++ src/util/allocators/win32_leakcheck.h | 17 + src/util/array.h | 124 ++ src/util/assert_safe.h | 58 + src/util/bitvec.h | 75 ++ src/util/cc-compat.h | 106 ++ src/util/date.c | 899 ++++++++++++++ src/util/date.h | 33 + src/util/filebuf.c | 595 +++++++++ src/util/filebuf.h | 94 ++ src/util/fs_path.c | 1920 ++++++++++++++++++++++++++++++ src/util/fs_path.h | 752 ++++++++++++ src/util/futils.c | 1194 +++++++++++++++++++ src/util/futils.h | 402 +++++++ src/util/git2_util.h | 168 +++ src/util/hash.c | 142 +++ src/util/hash.h | 46 + src/util/hash/sha1.h | 40 + src/util/hash/sha1/collisiondetect.c | 48 + src/util/hash/sha1/collisiondetect.h | 19 + src/util/hash/sha1/common_crypto.c | 57 + src/util/hash/sha1/common_crypto.h | 19 + src/util/hash/sha1/generic.c | 300 +++++ src/util/hash/sha1/generic.h | 19 + src/util/hash/sha1/mbedtls.c | 46 + src/util/hash/sha1/mbedtls.h | 19 + src/util/hash/sha1/openssl.c | 59 + src/util/hash/sha1/openssl.h | 19 + src/util/hash/sha1/sha1dc/sha1.c | 1909 +++++++++++++++++++++++++++++ src/util/hash/sha1/sha1dc/sha1.h | 110 ++ src/util/hash/sha1/sha1dc/ubc_check.c | 372 ++++++ src/util/hash/sha1/sha1dc/ubc_check.h | 52 + src/util/hash/sha1/win32.c | 333 ++++++ src/util/hash/sha1/win32.h | 128 ++ src/util/integer.h | 218 ++++ src/util/khash.h | 615 ++++++++++ src/util/map.h | 46 + src/util/net.c | 749 ++++++++++++ src/util/net.h | 78 ++ src/util/pool.c | 260 ++++ src/util/pool.h | 146 +++ src/util/posix.c | 303 +++++ src/util/posix.h | 196 +++ src/util/pqueue.c | 125 ++ src/util/pqueue.h | 59 + src/util/rand.c | 226 ++++ src/util/rand.h | 37 + src/util/regexp.c | 221 ++++ src/util/regexp.h | 97 ++ src/util/runtime.c | 162 +++ src/util/runtime.h | 62 + src/util/sortedcache.c | 380 ++++++ src/util/sortedcache.h | 182 +++ src/util/str.c | 1372 +++++++++++++++++++++ src/util/str.h | 357 ++++++ src/util/strmap.c | 100 ++ src/util/strmap.h | 131 ++ src/util/strnlen.h | 24 + src/util/thread.c | 140 +++ src/util/thread.h | 479 ++++++++ src/util/tsort.c | 382 ++++++ src/util/unix/map.c | 76 ++ src/util/unix/posix.h | 104 ++ src/util/unix/pthread.h | 57 + src/util/unix/realpath.c | 32 + src/util/utf8.c | 150 +++ src/util/utf8.h | 52 + src/util/util.c | 819 +++++++++++++ src/util/util.h | 387 ++++++ src/util/varint.c | 43 + src/util/varint.h | 17 + src/util/vector.c | 431 +++++++ src/util/vector.h | 128 ++ src/util/wildmatch.c | 320 +++++ src/util/wildmatch.h | 23 + src/util/win32/dir.c | 122 ++ src/util/win32/dir.h | 44 + src/util/win32/error.c | 53 + src/util/win32/error.h | 15 + src/util/win32/findfile.c | 286 +++++ src/util/win32/findfile.h | 22 + src/util/win32/map.c | 141 +++ src/util/win32/mingw-compat.h | 23 + src/util/win32/msvc-compat.h | 36 + src/util/win32/path_w32.c | 642 ++++++++++ src/util/win32/path_w32.h | 91 ++ src/util/win32/posix.h | 62 + src/util/win32/posix_w32.c | 1047 ++++++++++++++++ src/util/win32/precompiled.c | 1 + src/util/win32/precompiled.h | 21 + src/util/win32/reparse.h | 57 + src/util/win32/thread.c | 262 ++++ src/util/win32/thread.h | 64 + src/util/win32/utf-conv.c | 146 +++ src/util/win32/utf-conv.h | 60 + src/util/win32/version.h | 37 + src/util/win32/w32_buffer.c | 57 + src/util/win32/w32_buffer.h | 19 + src/util/win32/w32_common.h | 48 + src/util/win32/w32_leakcheck.c | 581 +++++++++ src/util/win32/w32_leakcheck.h | 222 ++++ src/util/win32/w32_util.c | 126 ++ src/util/win32/w32_util.h | 144 +++ src/util/win32/win32-compat.h | 52 + src/util/zstream.c | 210 ++++ src/util/zstream.h | 54 + 240 files changed, 25661 insertions(+), 25454 deletions(-) create mode 100644 src/README.md create mode 100644 src/features.h.in delete mode 100644 src/libgit2/alloc.c delete mode 100644 src/libgit2/alloc.h delete mode 100644 src/libgit2/allocators/failalloc.c delete mode 100644 src/libgit2/allocators/failalloc.h delete mode 100644 src/libgit2/allocators/stdalloc.c delete mode 100644 src/libgit2/allocators/stdalloc.h delete mode 100644 src/libgit2/allocators/win32_leakcheck.c delete mode 100644 src/libgit2/allocators/win32_leakcheck.h delete mode 100644 src/libgit2/array.h delete mode 100644 src/libgit2/assert_safe.h delete mode 100644 src/libgit2/bitvec.h delete mode 100644 src/libgit2/cc-compat.h delete mode 100644 src/libgit2/date.c delete mode 100644 src/libgit2/date.h delete mode 100644 src/libgit2/features.h.in delete mode 100644 src/libgit2/filebuf.c delete mode 100644 src/libgit2/filebuf.h delete mode 100644 src/libgit2/fs_path.c delete mode 100644 src/libgit2/fs_path.h delete mode 100644 src/libgit2/futils.c delete mode 100644 src/libgit2/futils.h create mode 100644 src/libgit2/git2.rc delete mode 100644 src/libgit2/hash.c delete mode 100644 src/libgit2/hash.h delete mode 100644 src/libgit2/hash/sha1.h delete mode 100644 src/libgit2/hash/sha1/collisiondetect.c delete mode 100644 src/libgit2/hash/sha1/collisiondetect.h delete mode 100644 src/libgit2/hash/sha1/common_crypto.c delete mode 100644 src/libgit2/hash/sha1/common_crypto.h delete mode 100644 src/libgit2/hash/sha1/generic.c delete mode 100644 src/libgit2/hash/sha1/generic.h delete mode 100644 src/libgit2/hash/sha1/mbedtls.c delete mode 100644 src/libgit2/hash/sha1/mbedtls.h delete mode 100644 src/libgit2/hash/sha1/openssl.c delete mode 100644 src/libgit2/hash/sha1/openssl.h delete mode 100644 src/libgit2/hash/sha1/sha1dc/sha1.c delete mode 100644 src/libgit2/hash/sha1/sha1dc/sha1.h delete mode 100644 src/libgit2/hash/sha1/sha1dc/ubc_check.c delete mode 100644 src/libgit2/hash/sha1/sha1dc/ubc_check.h delete mode 100644 src/libgit2/hash/sha1/win32.c delete mode 100644 src/libgit2/hash/sha1/win32.h delete mode 100644 src/libgit2/integer.h delete mode 100644 src/libgit2/khash.h delete mode 100644 src/libgit2/map.h delete mode 100644 src/libgit2/net.c delete mode 100644 src/libgit2/net.h delete mode 100644 src/libgit2/pool.c delete mode 100644 src/libgit2/pool.h delete mode 100644 src/libgit2/posix.c delete mode 100644 src/libgit2/posix.h delete mode 100644 src/libgit2/pqueue.c delete mode 100644 src/libgit2/pqueue.h delete mode 100644 src/libgit2/regexp.c delete mode 100644 src/libgit2/regexp.h delete mode 100644 src/libgit2/runtime.c delete mode 100644 src/libgit2/runtime.h delete mode 100644 src/libgit2/sortedcache.c delete mode 100644 src/libgit2/sortedcache.h delete mode 100644 src/libgit2/str.c delete mode 100644 src/libgit2/str.h delete mode 100644 src/libgit2/strmap.c delete mode 100644 src/libgit2/strmap.h delete mode 100644 src/libgit2/strnlen.h delete mode 100644 src/libgit2/thread.c delete mode 100644 src/libgit2/thread.h delete mode 100644 src/libgit2/tsort.c delete mode 100644 src/libgit2/unix/map.c delete mode 100644 src/libgit2/unix/posix.h delete mode 100644 src/libgit2/unix/pthread.h delete mode 100644 src/libgit2/unix/realpath.c delete mode 100644 src/libgit2/utf8.c delete mode 100644 src/libgit2/utf8.h delete mode 100644 src/libgit2/util.c delete mode 100644 src/libgit2/util.h delete mode 100644 src/libgit2/util/platform.h.in delete mode 100644 src/libgit2/varint.c delete mode 100644 src/libgit2/varint.h delete mode 100644 src/libgit2/vector.c delete mode 100644 src/libgit2/vector.h delete mode 100644 src/libgit2/wildmatch.c delete mode 100644 src/libgit2/wildmatch.h delete mode 100644 src/libgit2/win32/dir.c delete mode 100644 src/libgit2/win32/dir.h delete mode 100644 src/libgit2/win32/error.c delete mode 100644 src/libgit2/win32/error.h delete mode 100644 src/libgit2/win32/findfile.c delete mode 100644 src/libgit2/win32/findfile.h delete mode 100644 src/libgit2/win32/git2.rc delete mode 100644 src/libgit2/win32/map.c delete mode 100644 src/libgit2/win32/mingw-compat.h delete mode 100644 src/libgit2/win32/msvc-compat.h delete mode 100644 src/libgit2/win32/path_w32.c delete mode 100644 src/libgit2/win32/path_w32.h delete mode 100644 src/libgit2/win32/posix.h delete mode 100644 src/libgit2/win32/posix_w32.c delete mode 100644 src/libgit2/win32/precompiled.c delete mode 100644 src/libgit2/win32/precompiled.h delete mode 100644 src/libgit2/win32/reparse.h delete mode 100644 src/libgit2/win32/thread.c delete mode 100644 src/libgit2/win32/thread.h delete mode 100644 src/libgit2/win32/utf-conv.c delete mode 100644 src/libgit2/win32/utf-conv.h delete mode 100644 src/libgit2/win32/version.h delete mode 100644 src/libgit2/win32/w32_buffer.c delete mode 100644 src/libgit2/win32/w32_buffer.h delete mode 100644 src/libgit2/win32/w32_common.h delete mode 100644 src/libgit2/win32/w32_leakcheck.c delete mode 100644 src/libgit2/win32/w32_leakcheck.h delete mode 100644 src/libgit2/win32/w32_util.c delete mode 100644 src/libgit2/win32/w32_util.h delete mode 100644 src/libgit2/win32/win32-compat.h delete mode 100644 src/libgit2/zstream.c delete mode 100644 src/libgit2/zstream.h delete mode 100644 src/rand.c delete mode 100644 src/rand.h create mode 100644 src/util/CMakeLists.txt create mode 100644 src/util/alloc.c create mode 100644 src/util/alloc.h create mode 100644 src/util/allocators/failalloc.c create mode 100644 src/util/allocators/failalloc.h create mode 100644 src/util/allocators/stdalloc.c create mode 100644 src/util/allocators/stdalloc.h create mode 100644 src/util/allocators/win32_leakcheck.c create mode 100644 src/util/allocators/win32_leakcheck.h create mode 100644 src/util/array.h create mode 100644 src/util/assert_safe.h create mode 100644 src/util/bitvec.h create mode 100644 src/util/cc-compat.h create mode 100644 src/util/date.c create mode 100644 src/util/date.h create mode 100644 src/util/filebuf.c create mode 100644 src/util/filebuf.h create mode 100644 src/util/fs_path.c create mode 100644 src/util/fs_path.h create mode 100644 src/util/futils.c create mode 100644 src/util/futils.h create mode 100644 src/util/git2_util.h create mode 100644 src/util/hash.c create mode 100644 src/util/hash.h create mode 100644 src/util/hash/sha1.h create mode 100644 src/util/hash/sha1/collisiondetect.c create mode 100644 src/util/hash/sha1/collisiondetect.h create mode 100644 src/util/hash/sha1/common_crypto.c create mode 100644 src/util/hash/sha1/common_crypto.h create mode 100644 src/util/hash/sha1/generic.c create mode 100644 src/util/hash/sha1/generic.h create mode 100644 src/util/hash/sha1/mbedtls.c create mode 100644 src/util/hash/sha1/mbedtls.h create mode 100644 src/util/hash/sha1/openssl.c create mode 100644 src/util/hash/sha1/openssl.h create mode 100644 src/util/hash/sha1/sha1dc/sha1.c create mode 100644 src/util/hash/sha1/sha1dc/sha1.h create mode 100644 src/util/hash/sha1/sha1dc/ubc_check.c create mode 100644 src/util/hash/sha1/sha1dc/ubc_check.h create mode 100644 src/util/hash/sha1/win32.c create mode 100644 src/util/hash/sha1/win32.h create mode 100644 src/util/integer.h create mode 100644 src/util/khash.h create mode 100644 src/util/map.h create mode 100644 src/util/net.c create mode 100644 src/util/net.h create mode 100644 src/util/pool.c create mode 100644 src/util/pool.h create mode 100644 src/util/posix.c create mode 100644 src/util/posix.h create mode 100644 src/util/pqueue.c create mode 100644 src/util/pqueue.h create mode 100644 src/util/rand.c create mode 100644 src/util/rand.h create mode 100644 src/util/regexp.c create mode 100644 src/util/regexp.h create mode 100644 src/util/runtime.c create mode 100644 src/util/runtime.h create mode 100644 src/util/sortedcache.c create mode 100644 src/util/sortedcache.h create mode 100644 src/util/str.c create mode 100644 src/util/str.h create mode 100644 src/util/strmap.c create mode 100644 src/util/strmap.h create mode 100644 src/util/strnlen.h create mode 100644 src/util/thread.c create mode 100644 src/util/thread.h create mode 100644 src/util/tsort.c create mode 100644 src/util/unix/map.c create mode 100644 src/util/unix/posix.h create mode 100644 src/util/unix/pthread.h create mode 100644 src/util/unix/realpath.c create mode 100644 src/util/utf8.c create mode 100644 src/util/utf8.h create mode 100644 src/util/util.c create mode 100644 src/util/util.h create mode 100644 src/util/varint.c create mode 100644 src/util/varint.h create mode 100644 src/util/vector.c create mode 100644 src/util/vector.h create mode 100644 src/util/wildmatch.c create mode 100644 src/util/wildmatch.h create mode 100644 src/util/win32/dir.c create mode 100644 src/util/win32/dir.h create mode 100644 src/util/win32/error.c create mode 100644 src/util/win32/error.h create mode 100644 src/util/win32/findfile.c create mode 100644 src/util/win32/findfile.h create mode 100644 src/util/win32/map.c create mode 100644 src/util/win32/mingw-compat.h create mode 100644 src/util/win32/msvc-compat.h create mode 100644 src/util/win32/path_w32.c create mode 100644 src/util/win32/path_w32.h create mode 100644 src/util/win32/posix.h create mode 100644 src/util/win32/posix_w32.c create mode 100644 src/util/win32/precompiled.c create mode 100644 src/util/win32/precompiled.h create mode 100644 src/util/win32/reparse.h create mode 100644 src/util/win32/thread.c create mode 100644 src/util/win32/thread.h create mode 100644 src/util/win32/utf-conv.c create mode 100644 src/util/win32/utf-conv.h create mode 100644 src/util/win32/version.h create mode 100644 src/util/win32/w32_buffer.c create mode 100644 src/util/win32/w32_buffer.h create mode 100644 src/util/win32/w32_common.h create mode 100644 src/util/win32/w32_leakcheck.c create mode 100644 src/util/win32/w32_leakcheck.h create mode 100644 src/util/win32/w32_util.c create mode 100644 src/util/win32/w32_util.h create mode 100644 src/util/win32/win32-compat.h create mode 100644 src/util/zstream.c create mode 100644 src/util/zstream.h diff --git a/cmake/SelectHashes.cmake b/cmake/SelectHashes.cmake index bedd8c4e3..575ae8f8d 100644 --- a/cmake/SelectHashes.cmake +++ b/cmake/SelectHashes.cmake @@ -21,9 +21,6 @@ endif() if(USE_SHA1 STREQUAL "CollisionDetection") set(GIT_SHA1_COLLISIONDETECT 1) - add_definitions(-DSHA1DC_NO_STANDARD_INCLUDES=1) - add_definitions(-DSHA1DC_CUSTOM_INCLUDE_SHA1_C=\"common.h\") - add_definitions(-DSHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C=\"common.h\") elseif(USE_SHA1 STREQUAL "OpenSSL") # OPENSSL_FOUND should already be set, we're checking USE_HTTPS diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f6924eff5..bbb724057 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,182 @@ +# The main libgit2 source tree: this CMakeLists.txt identifies platform +# support and includes the subprojects that make up core libgit2 support. + +# +# Optional build configuration settings +# + +if(DEPRECATE_HARD) + add_definitions(-DGIT_DEPRECATE_HARD) +endif() + +if(USE_LEAK_CHECKER STREQUAL "valgrind") + add_definitions(-DVALGRIND) +endif() + +# +# Optional debugging functionality +# + +if(DEBUG_POOL) + set(GIT_DEBUG_POOL 1) +endif() +add_feature_info(debugpool GIT_DEBUG_POOL "debug pool allocator") + +if(DEBUG_STRICT_ALLOC) + set(GIT_DEBUG_STRICT_ALLOC 1) +endif() +add_feature_info(debugalloc GIT_DEBUG_STRICT_ALLOC "debug strict allocators") + +if(DEBUG_STRICT_OPEN) + set(GIT_DEBUG_STRICT_OPEN 1) +endif() +add_feature_info(debugopen GIT_DEBUG_STRICT_OPEN "path validation in open") + +# +# Optional feature enablement +# + +include(SelectGSSAPI) +include(SelectHTTPSBackend) +include(SelectHashes) +include(SelectHTTPParser) +include(SelectRegex) +include(SelectSSH) +include(SelectWinHTTP) +include(SelectZlib) + +# +# Platform support +# + +# futimes/futimens + +if(HAVE_FUTIMENS) + set(GIT_USE_FUTIMENS 1) +endif () +add_feature_info(futimens GIT_USE_FUTIMENS "futimens support") + +# qsort + +check_prototype_definition(qsort_r + "void qsort_r(void *base, size_t nmemb, size_t size, void *thunk, int (*compar)(void *, const void *, const void *))" + "" "stdlib.h" GIT_QSORT_R_BSD) + +check_prototype_definition(qsort_r + "void qsort_r(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void *arg)" + "" "stdlib.h" GIT_QSORT_R_GNU) + +check_function_exists(qsort_s GIT_QSORT_S) + +# determine architecture of the machine + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(GIT_ARCH_64 1) +elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(GIT_ARCH_32 1) +elseif(CMAKE_SIZEOF_VOID_P) + message(FATAL_ERROR "Unsupported architecture (pointer size is ${CMAKE_SIZEOF_VOID_P} bytes)") +else() + message(FATAL_ERROR "Unsupported architecture (CMAKE_SIZEOF_VOID_P is unset)") +endif() + +# nanosecond mtime/ctime support + +if(USE_NSEC) + set(GIT_USE_NSEC 1) +endif() + +# high-resolution stat support + +if(HAVE_STRUCT_STAT_ST_MTIM) + set(GIT_USE_STAT_MTIM 1) +elseif(HAVE_STRUCT_STAT_ST_MTIMESPEC) + set(GIT_USE_STAT_MTIMESPEC 1) +elseif(HAVE_STRUCT_STAT_ST_MTIME_NSEC) + set(GIT_USE_STAT_MTIME_NSEC 1) +endif() + +# realtime support + +check_library_exists(rt clock_gettime "time.h" NEED_LIBRT) +if(NEED_LIBRT) + list(APPEND LIBGIT2_SYSTEM_LIBS rt) + list(APPEND LIBGIT2_PC_LIBS "-lrt") +endif() + +# platform libraries + +if(WIN32) + list(APPEND LIBGIT2_SYSTEM_LIBS ws2_32) +endif() + +if(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + list(APPEND LIBGIT2_SYSTEM_LIBS socket nsl) + list(APPEND LIBGIT2_PC_LIBS "-lsocket" "-lnsl") +endif() + +if(CMAKE_SYSTEM_NAME MATCHES "Haiku") + list(APPEND LIBGIT2_SYSTEM_LIBS network) + list(APPEND LIBGIT2_PC_LIBS "-lnetwork") +endif() + +if(AMIGA) + add_definitions(-DNO_ADDRINFO -DNO_READDIR_R -DNO_MMAP) +endif() + +# threads + +if(USE_THREADS) + if(NOT WIN32) + find_package(Threads REQUIRED) + list(APPEND LIBGIT2_SYSTEM_LIBS ${CMAKE_THREAD_LIBS_INIT}) + list(APPEND LIBGIT2_PC_LIBS ${CMAKE_THREAD_LIBS_INIT}) + endif() + + set(GIT_THREADS 1) +endif() +add_feature_info(threadsafe USE_THREADS "threadsafe support") + +# +# Optional bundled features +# + +# ntlmclient +if(USE_NTLMCLIENT) + set(GIT_NTLM 1) + add_subdirectory("${libgit2_SOURCE_DIR}/deps/ntlmclient" "${libgit2_BINARY_DIR}/deps/ntlmclient") + list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${libgit2_SOURCE_DIR}/deps/ntlmclient") + list(APPEND LIBGIT2_DEPENDENCY_OBJECTS "$") +endif() +add_feature_info(ntlmclient GIT_NTLM "NTLM authentication support for Unix") + +# +# Optional external dependencies + +# iconv +if(USE_ICONV) + find_package(Iconv) +endif() +if(ICONV_FOUND) + set(GIT_USE_ICONV 1) + list(APPEND LIBGIT2_SYSTEM_INCLUDES ${ICONV_INCLUDE_DIR}) + list(APPEND LIBGIT2_SYSTEM_LIBS ${ICONV_LIBRARIES}) + list(APPEND LIBGIT2_PC_LIBS ${ICONV_LIBRARIES}) +endif() +add_feature_info(iconv GIT_USE_ICONV "iconv encoding conversion support") + +# +# Configure support +# + +configure_file(features.h.in git2/sys/features.h) + +# +# Include child projects +# + add_subdirectory(libgit2) +add_subdirectory(util) # re-export these to the root so that peer projects (tests, fuzzers, # examples) can use them diff --git a/src/README.md b/src/README.md new file mode 100644 index 000000000..12e0d0e43 --- /dev/null +++ b/src/README.md @@ -0,0 +1,10 @@ +# libgit2 sources + +This is the source that makes up the core of libgit2 and its related +projects. + +* `libgit2` + This is the libgit2 project, a cross-platform, linkable library + implementation of Git that you can use in your application. +* `util` + A shared utility library for these projects. diff --git a/src/features.h.in b/src/features.h.in new file mode 100644 index 000000000..f920135da --- /dev/null +++ b/src/features.h.in @@ -0,0 +1,53 @@ +#ifndef INCLUDE_features_h__ +#define INCLUDE_features_h__ + +#cmakedefine GIT_DEBUG_POOL 1 +#cmakedefine GIT_DEBUG_STRICT_ALLOC 1 +#cmakedefine GIT_DEBUG_STRICT_OPEN 1 + +#cmakedefine GIT_THREADS 1 +#cmakedefine GIT_WIN32_LEAKCHECK 1 + +#cmakedefine GIT_ARCH_64 1 +#cmakedefine GIT_ARCH_32 1 + +#cmakedefine GIT_USE_ICONV 1 +#cmakedefine GIT_USE_NSEC 1 +#cmakedefine GIT_USE_STAT_MTIM 1 +#cmakedefine GIT_USE_STAT_MTIMESPEC 1 +#cmakedefine GIT_USE_STAT_MTIME_NSEC 1 +#cmakedefine GIT_USE_FUTIMENS 1 + +#cmakedefine GIT_REGEX_REGCOMP_L +#cmakedefine GIT_REGEX_REGCOMP +#cmakedefine GIT_REGEX_PCRE +#cmakedefine GIT_REGEX_PCRE2 +#cmakedefine GIT_REGEX_BUILTIN 1 + +#cmakedefine GIT_QSORT_R_BSD +#cmakedefine GIT_QSORT_R_GNU +#cmakedefine GIT_QSORT_S + +#cmakedefine GIT_SSH 1 +#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1 + +#cmakedefine GIT_NTLM 1 +#cmakedefine GIT_GSSAPI 1 +#cmakedefine GIT_GSSFRAMEWORK 1 + +#cmakedefine GIT_WINHTTP 1 +#cmakedefine GIT_HTTPS 1 +#cmakedefine GIT_OPENSSL 1 +#cmakedefine GIT_OPENSSL_DYNAMIC 1 +#cmakedefine GIT_SECURE_TRANSPORT 1 +#cmakedefine GIT_MBEDTLS 1 + +#cmakedefine GIT_SHA1_COLLISIONDETECT 1 +#cmakedefine GIT_SHA1_WIN32 1 +#cmakedefine GIT_SHA1_COMMON_CRYPTO 1 +#cmakedefine GIT_SHA1_OPENSSL 1 +#cmakedefine GIT_SHA1_MBEDTLS 1 + +#cmakedefine GIT_RAND_GETENTROPY 1 + +#endif diff --git a/src/libgit2/CMakeLists.txt b/src/libgit2/CMakeLists.txt index e6cdddd49..3dac83d7a 100644 --- a/src/libgit2/CMakeLists.txt +++ b/src/libgit2/CMakeLists.txt @@ -2,94 +2,99 @@ add_library(git2internal OBJECT) set_target_properties(git2internal PROPERTIES C_STANDARD 90) set_target_properties(git2internal PROPERTIES C_EXTENSIONS OFF) - -if(DEPRECATE_HARD) - add_definitions(-DGIT_DEPRECATE_HARD) -endif() - -if(DEBUG_POOL) - set(GIT_DEBUG_POOL 1) -endif() -add_feature_info(debugpool GIT_DEBUG_POOL "debug pool allocator") - -if(DEBUG_STRICT_ALLOC) - set(GIT_DEBUG_STRICT_ALLOC 1) -endif() -add_feature_info(debugalloc GIT_DEBUG_STRICT_ALLOC "debug strict allocators") - -if(DEBUG_STRICT_OPEN) - set(GIT_DEBUG_STRICT_OPEN 1) -endif() -add_feature_info(debugopen GIT_DEBUG_STRICT_OPEN "path validation in open") - - include(PkgBuildConfig) -include(SanitizeBool) - -# This variable will contain the libraries we need to put into -# libgit2.pc's Requires.private. That is, what we're linking to or -# what someone who's statically linking us needs to link to. -set(LIBGIT2_PC_REQUIRES "") -# This will be set later if we use the system's http-parser library or -# use iconv (OSX) and will be written to the Libs.private field in the -# pc file. -set(LIBGIT2_PC_LIBS "") set(LIBGIT2_INCLUDES - "${CMAKE_CURRENT_BINARY_DIR}" + "${PROJECT_BINARY_DIR}/src" "${PROJECT_SOURCE_DIR}/src/libgit2" + "${PROJECT_SOURCE_DIR}/src/util" "${PROJECT_SOURCE_DIR}/include") -if(HAVE_FUTIMENS) - set(GIT_USE_FUTIMENS 1) -endif () -add_feature_info(futimens GIT_USE_FUTIMENS "futimens support") +if(WIN32 AND EMBED_SSH_PATH) + file(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c") + list(SORT SRC_SSH) + target_sources(git2internal PRIVATE ${SRC_SSH}) -check_prototype_definition(qsort_r - "void qsort_r(void *base, size_t nmemb, size_t size, void *thunk, int (*compar)(void *, const void *, const void *))" - "" "stdlib.h" GIT_QSORT_R_BSD) + list(APPEND LIBGIT2_SYSTEM_INCLUDES "${EMBED_SSH_PATH}/include") + file(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"") + set(GIT_SSH 1) +endif() -check_prototype_definition(qsort_r - "void qsort_r(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void *arg)" - "" "stdlib.h" GIT_QSORT_R_GNU) +<<<<<<< HEAD +include(SelectHTTPSBackend) +include(SelectHashes) +include(SelectHTTPParser) +include(SelectRegex) +include(SelectSSH) +include(SelectWinHTTP) +include(SelectZlib) -check_function_exists(qsort_s GIT_QSORT_S) -# Find required dependencies +if(USE_SHA1 STREQUAL "CollisionDetection") + file(GLOB SRC_SHA1 hash/sha1/collisiondetect.* hash/sha1/sha1dc/*) +elseif(USE_SHA1 STREQUAL "OpenSSL") + file(GLOB SRC_SHA1 hash/sha1/openssl.*) +elseif(USE_SHA1 STREQUAL "CommonCrypto") + file(GLOB SRC_SHA1 hash/sha1/common_crypto.*) +elseif(USE_SHA1 STREQUAL "mbedTLS") + file(GLOB SRC_SHA1 hash/sha1/mbedtls.*) +elseif(USE_SHA1 STREQUAL "Win32") + file(GLOB SRC_SHA1 hash/sha1/win32.*) +elseif(USE_SHA1 STREQUAL "Generic") + file(GLOB SRC_SHA1 hash/sha1/generic.*) +endif() +list(APPEND SRC_SHA1 "hash/sha1.h") +target_sources(git2internal PRIVATE ${SRC_SHA1}) -if(WIN32) - list(APPEND LIBGIT2_SYSTEM_LIBS ws2_32) -elseif(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") - list(APPEND LIBGIT2_SYSTEM_LIBS socket nsl) - list(APPEND LIBGIT2_PC_LIBS "-lsocket" "-lnsl") -elseif(CMAKE_SYSTEM_NAME MATCHES "Haiku") - list(APPEND LIBGIT2_SYSTEM_LIBS network) - list(APPEND LIBGIT2_PC_LIBS "-lnetwork") +# Optional external dependency: ntlmclient +if(USE_NTLMCLIENT) + set(GIT_NTLM 1) + add_subdirectory("${PROJECT_SOURCE_DIR}/deps/ntlmclient" "${PROJECT_BINARY_DIR}/deps/ntlmclient") + list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/ntlmclient") + list(APPEND LIBGIT2_DEPENDENCY_OBJECTS "$") endif() +add_feature_info(ntlmclient GIT_NTLM "NTLM authentication support for Unix") -check_library_exists(rt clock_gettime "time.h" NEED_LIBRT) -if(NEED_LIBRT) - list(APPEND LIBGIT2_SYSTEM_LIBS rt) - list(APPEND LIBGIT2_PC_LIBS "-lrt") +# Optional external dependency: GSSAPI + +include(SelectGSSAPI) + +# Optional external dependency: iconv +if(USE_ICONV) + find_package(Iconv) endif() +if(ICONV_FOUND) + set(GIT_USE_ICONV 1) + list(APPEND LIBGIT2_SYSTEM_INCLUDES ${ICONV_INCLUDE_DIR}) + list(APPEND LIBGIT2_SYSTEM_LIBS ${ICONV_LIBRARIES}) + list(APPEND LIBGIT2_PC_LIBS ${ICONV_LIBRARIES}) +endif() +add_feature_info(iconv GIT_USE_ICONV "iconv encoding conversion support") + if(USE_THREADS) - list(APPEND LIBGIT2_SYSTEM_LIBS ${CMAKE_THREAD_LIBS_INIT}) - list(APPEND LIBGIT2_PC_LIBS ${CMAKE_THREAD_LIBS_INIT}) -endif() -add_feature_info(threadsafe USE_THREADS "threadsafe support") + if(NOT WIN32) + find_package(Threads REQUIRED) + endif() + set(GIT_THREADS 1) +endif() -if(WIN32 AND EMBED_SSH_PATH) - file(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c") - list(SORT SRC_SSH) - target_sources(git2internal PRIVATE ${SRC_SSH}) +if(USE_NSEC) + set(GIT_USE_NSEC 1) +endif() - list(APPEND LIBGIT2_SYSTEM_INCLUDES "${EMBED_SSH_PATH}/include") - file(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"") - set(GIT_SSH 1) +if(HAVE_STRUCT_STAT_ST_MTIM) + set(GIT_USE_STAT_MTIM 1) +elseif(HAVE_STRUCT_STAT_ST_MTIMESPEC) + set(GIT_USE_STAT_MTIMESPEC 1) +elseif(HAVE_STRUCT_STAT_ST_MTIME_NSEC) + set(GIT_USE_STAT_MTIME_NSEC 1) endif() +target_compile_definitions(git2internal PRIVATE _FILE_OFFSET_BITS=64) + +||||||| parent of a930dafb4 (refactor: make util an object library) include(SelectHTTPSBackend) include(SelectHashes) include(SelectHTTPParser) @@ -163,6 +168,8 @@ endif() target_compile_definitions(git2internal PRIVATE _FILE_OFFSET_BITS=64) +======= +>>>>>>> a930dafb4 (refactor: make util an object library) # Collect sourcefiles file(GLOB SRC_H "${PROJECT_SOURCE_DIR}/include/git2.h" @@ -171,33 +178,18 @@ file(GLOB SRC_H list(SORT SRC_H) target_sources(git2internal PRIVATE ${SRC_H}) -# On Windows use specific platform sources -if(WIN32 AND NOT CYGWIN) - set(WIN_RC "win32/git2.rc") - - file(GLOB SRC_OS win32/*.c win32/*.h) - list(SORT SRC_OS) - target_sources(git2internal PRIVATE ${SRC_OS}) -elseif(AMIGA) - target_compile_definitions(git2internal PRIVATE NO_ADDRINFO NO_READDIR_R NO_MMAP) -else() - file(GLOB SRC_OS unix/*.c unix/*.h) - list(SORT SRC_OS) - target_sources(git2internal PRIVATE ${SRC_OS}) -endif() - -if(USE_LEAK_CHECKER STREQUAL "valgrind") - target_compile_definitions(git2internal PRIVATE VALGRIND) -endif() - file(GLOB SRC_GIT2 *.c *.h - allocators/*.c allocators/*.h streams/*.c streams/*.h transports/*.c transports/*.h xdiff/*.c xdiff/*.h) list(SORT SRC_GIT2) target_sources(git2internal PRIVATE ${SRC_GIT2}) +if(WIN32 AND NOT CYGWIN) + # Add resource information on Windows + set(SRC_RC "git2.rc") +endif() + if(APPLE) # The old Secure Transport API has been deprecated in macOS 10.15. set_source_files_properties(streams/stransport.c PROPERTIES COMPILE_FLAGS -Wno-deprecated) @@ -220,21 +212,8 @@ else() set_source_files_properties(xdiff/xpatience.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") endif() -# Determine architecture of the machine -if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(GIT_ARCH_64 1) -elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(GIT_ARCH_32 1) -elseif(CMAKE_SIZEOF_VOID_P) - message(FATAL_ERROR "Unsupported architecture (pointer size is ${CMAKE_SIZEOF_VOID_P} bytes)") -else() - message(FATAL_ERROR "Unsupported architecture (CMAKE_SIZEOF_VOID_P is unset)") -endif() - -configure_file(features.h.in git2/sys/features.h) - ide_split_sources(git2internal) -list(APPEND LIBGIT2_OBJECTS $ ${LIBGIT2_DEPENDENCY_OBJECTS}) +list(APPEND LIBGIT2_OBJECTS $ $ ${LIBGIT2_DEPENDENCY_OBJECTS}) target_include_directories(git2internal PRIVATE ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES} PUBLIC ${PROJECT_SOURCE_DIR}/include) target_include_directories(git2internal SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) @@ -254,7 +233,7 @@ if(XCODE_VERSION) endif() # Compile and link libgit2 -add_library(git2 ${WIN_RC} ${LIBGIT2_OBJECTS}) +add_library(git2 ${SRC_RC} ${LIBGIT2_OBJECTS}) target_link_libraries(git2 ${LIBGIT2_SYSTEM_LIBS}) set_target_properties(git2 PROPERTIES C_STANDARD 90) diff --git a/src/libgit2/alloc.c b/src/libgit2/alloc.c deleted file mode 100644 index 2820d84a2..000000000 --- a/src/libgit2/alloc.c +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "alloc.h" -#include "runtime.h" - -#include "allocators/failalloc.h" -#include "allocators/stdalloc.h" -#include "allocators/win32_leakcheck.h" - -/* Fail any allocation until git_libgit2_init is called. */ -git_allocator git__allocator = { - git_failalloc_malloc, - git_failalloc_calloc, - git_failalloc_strdup, - git_failalloc_strndup, - git_failalloc_substrdup, - git_failalloc_realloc, - git_failalloc_reallocarray, - git_failalloc_mallocarray, - git_failalloc_free -}; - -static int setup_default_allocator(void) -{ -#if defined(GIT_WIN32_LEAKCHECK) - return git_win32_leakcheck_init_allocator(&git__allocator); -#else - return git_stdalloc_init_allocator(&git__allocator); -#endif -} - -int git_allocator_global_init(void) -{ - /* - * We don't want to overwrite any allocator which has been set - * before the init function is called. - */ - if (git__allocator.gmalloc != git_failalloc_malloc) - return 0; - - return setup_default_allocator(); -} - -int git_allocator_setup(git_allocator *allocator) -{ - if (!allocator) - return setup_default_allocator(); - - memcpy(&git__allocator, allocator, sizeof(*allocator)); - return 0; -} diff --git a/src/libgit2/alloc.h b/src/libgit2/alloc.h deleted file mode 100644 index 04fb7e101..000000000 --- a/src/libgit2/alloc.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_alloc_h__ -#define INCLUDE_alloc_h__ - -#include "git2/sys/alloc.h" - -extern git_allocator git__allocator; - -#define git__malloc(len) git__allocator.gmalloc(len, __FILE__, __LINE__) -#define git__calloc(nelem, elsize) git__allocator.gcalloc(nelem, elsize, __FILE__, __LINE__) -#define git__strdup(str) git__allocator.gstrdup(str, __FILE__, __LINE__) -#define git__strndup(str, n) git__allocator.gstrndup(str, n, __FILE__, __LINE__) -#define git__substrdup(str, n) git__allocator.gsubstrdup(str, n, __FILE__, __LINE__) -#define git__realloc(ptr, size) git__allocator.grealloc(ptr, size, __FILE__, __LINE__) -#define git__reallocarray(ptr, nelem, elsize) git__allocator.greallocarray(ptr, nelem, elsize, __FILE__, __LINE__) -#define git__mallocarray(nelem, elsize) git__allocator.gmallocarray(nelem, elsize, __FILE__, __LINE__) -#define git__free git__allocator.gfree - -/** - * This function is being called by our global setup routines to - * initialize the standard allocator. - */ -int git_allocator_global_init(void); - -/** - * Switch out libgit2's global memory allocator - * - * @param allocator The new allocator that should be used. All function pointers - * of it need to be set correctly. - * @return An error code or 0. - */ -int git_allocator_setup(git_allocator *allocator); - -#endif diff --git a/src/libgit2/allocators/failalloc.c b/src/libgit2/allocators/failalloc.c deleted file mode 100644 index 5257d1dec..000000000 --- a/src/libgit2/allocators/failalloc.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "failalloc.h" - -void *git_failalloc_malloc(size_t len, const char *file, int line) -{ - GIT_UNUSED(len); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void *git_failalloc_calloc(size_t nelem, size_t elsize, const char *file, int line) -{ - GIT_UNUSED(nelem); - GIT_UNUSED(elsize); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -char *git_failalloc_strdup(const char *str, const char *file, int line) -{ - GIT_UNUSED(str); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -char *git_failalloc_strndup(const char *str, size_t n, const char *file, int line) -{ - GIT_UNUSED(str); - GIT_UNUSED(n); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -char *git_failalloc_substrdup(const char *start, size_t n, const char *file, int line) -{ - GIT_UNUSED(start); - GIT_UNUSED(n); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line) -{ - GIT_UNUSED(ptr); - GIT_UNUSED(size); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void *git_failalloc_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) -{ - GIT_UNUSED(ptr); - GIT_UNUSED(nelem); - GIT_UNUSED(elsize); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void *git_failalloc_mallocarray(size_t nelem, size_t elsize, const char *file, int line) -{ - GIT_UNUSED(nelem); - GIT_UNUSED(elsize); - GIT_UNUSED(file); - GIT_UNUSED(line); - - return NULL; -} - -void git_failalloc_free(void *ptr) -{ - GIT_UNUSED(ptr); -} diff --git a/src/libgit2/allocators/failalloc.h b/src/libgit2/allocators/failalloc.h deleted file mode 100644 index 6115e51e7..000000000 --- a/src/libgit2/allocators/failalloc.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_allocators_failalloc_h__ -#define INCLUDE_allocators_failalloc_h__ - -#include "common.h" - -extern void *git_failalloc_malloc(size_t len, const char *file, int line); -extern void *git_failalloc_calloc(size_t nelem, size_t elsize, const char *file, int line); -extern char *git_failalloc_strdup(const char *str, const char *file, int line); -extern char *git_failalloc_strndup(const char *str, size_t n, const char *file, int line); -extern char *git_failalloc_substrdup(const char *start, size_t n, const char *file, int line); -extern void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line); -extern void *git_failalloc_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line); -extern void *git_failalloc_mallocarray(size_t nelem, size_t elsize, const char *file, int line); -extern void git_failalloc_free(void *ptr); - -#endif diff --git a/src/libgit2/allocators/stdalloc.c b/src/libgit2/allocators/stdalloc.c deleted file mode 100644 index 2b36d9f3d..000000000 --- a/src/libgit2/allocators/stdalloc.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "stdalloc.h" - -static void *stdalloc__malloc(size_t len, const char *file, int line) -{ - void *ptr; - - GIT_UNUSED(file); - GIT_UNUSED(line); - -#ifdef GIT_DEBUG_STRICT_ALLOC - if (!len) - return NULL; -#endif - - ptr = malloc(len); - - if (!ptr) - git_error_set_oom(); - - return ptr; -} - -static void *stdalloc__calloc(size_t nelem, size_t elsize, const char *file, int line) -{ - void *ptr; - - GIT_UNUSED(file); - GIT_UNUSED(line); - -#ifdef GIT_DEBUG_STRICT_ALLOC - if (!elsize || !nelem) - return NULL; -#endif - - ptr = calloc(nelem, elsize); - - if (!ptr) - git_error_set_oom(); - - return ptr; -} - -static char *stdalloc__strdup(const char *str, const char *file, int line) -{ - char *ptr; - - GIT_UNUSED(file); - GIT_UNUSED(line); - - ptr = strdup(str); - - if (!ptr) - git_error_set_oom(); - - return ptr; -} - -static char *stdalloc__strndup(const char *str, size_t n, const char *file, int line) -{ - size_t length = 0, alloclength; - char *ptr; - - length = p_strnlen(str, n); - - if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) || - !(ptr = stdalloc__malloc(alloclength, file, line))) - return NULL; - - if (length) - memcpy(ptr, str, length); - - ptr[length] = '\0'; - - return ptr; -} - -static char *stdalloc__substrdup(const char *start, size_t n, const char *file, int line) -{ - char *ptr; - size_t alloclen; - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) || - !(ptr = stdalloc__malloc(alloclen, file, line))) - return NULL; - - memcpy(ptr, start, n); - ptr[n] = '\0'; - return ptr; -} - -static void *stdalloc__realloc(void *ptr, size_t size, const char *file, int line) -{ - void *new_ptr; - - GIT_UNUSED(file); - GIT_UNUSED(line); - -#ifdef GIT_DEBUG_STRICT_ALLOC - if (!size) - return NULL; -#endif - - new_ptr = realloc(ptr, size); - - if (!new_ptr) - git_error_set_oom(); - - return new_ptr; -} - -static void *stdalloc__reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) -{ - size_t newsize; - - if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize)) - return NULL; - - return stdalloc__realloc(ptr, newsize, file, line); -} - -static void *stdalloc__mallocarray(size_t nelem, size_t elsize, const char *file, int line) -{ - return stdalloc__reallocarray(NULL, nelem, elsize, file, line); -} - -static void stdalloc__free(void *ptr) -{ - free(ptr); -} - -int git_stdalloc_init_allocator(git_allocator *allocator) -{ - allocator->gmalloc = stdalloc__malloc; - allocator->gcalloc = stdalloc__calloc; - allocator->gstrdup = stdalloc__strdup; - allocator->gstrndup = stdalloc__strndup; - allocator->gsubstrdup = stdalloc__substrdup; - allocator->grealloc = stdalloc__realloc; - allocator->greallocarray = stdalloc__reallocarray; - allocator->gmallocarray = stdalloc__mallocarray; - allocator->gfree = stdalloc__free; - return 0; -} diff --git a/src/libgit2/allocators/stdalloc.h b/src/libgit2/allocators/stdalloc.h deleted file mode 100644 index fa23fe6e3..000000000 --- a/src/libgit2/allocators/stdalloc.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_allocators_stdalloc_h__ -#define INCLUDE_allocators_stdalloc_h__ - -#include "common.h" - -#include "alloc.h" - -int git_stdalloc_init_allocator(git_allocator *allocator); - -#endif diff --git a/src/libgit2/allocators/win32_leakcheck.c b/src/libgit2/allocators/win32_leakcheck.c deleted file mode 100644 index fe06a14af..000000000 --- a/src/libgit2/allocators/win32_leakcheck.c +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "win32_leakcheck.h" - -#if defined(GIT_WIN32_LEAKCHECK) - -#include "win32/w32_leakcheck.h" - -static void *leakcheck_malloc(size_t len, const char *file, int line) -{ - void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); - if (!ptr) git_error_set_oom(); - return ptr; -} - -static void *leakcheck_calloc(size_t nelem, size_t elsize, const char *file, int line) -{ - void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); - if (!ptr) git_error_set_oom(); - return ptr; -} - -static char *leakcheck_strdup(const char *str, const char *file, int line) -{ - char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); - if (!ptr) git_error_set_oom(); - return ptr; -} - -static char *leakcheck_strndup(const char *str, size_t n, const char *file, int line) -{ - size_t length = 0, alloclength; - char *ptr; - - length = p_strnlen(str, n); - - if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) || - !(ptr = leakcheck_malloc(alloclength, file, line))) - return NULL; - - if (length) - memcpy(ptr, str, length); - - ptr[length] = '\0'; - - return ptr; -} - -static char *leakcheck_substrdup(const char *start, size_t n, const char *file, int line) -{ - char *ptr; - size_t alloclen; - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) || - !(ptr = leakcheck_malloc(alloclen, file, line))) - return NULL; - - memcpy(ptr, start, n); - ptr[n] = '\0'; - return ptr; -} - -static void *leakcheck_realloc(void *ptr, size_t size, const char *file, int line) -{ - void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); - if (!new_ptr) git_error_set_oom(); - return new_ptr; -} - -static void *leakcheck_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) -{ - size_t newsize; - - if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize)) - return NULL; - - return leakcheck_realloc(ptr, newsize, file, line); -} - -static void *leakcheck_mallocarray(size_t nelem, size_t elsize, const char *file, int line) -{ - return leakcheck_reallocarray(NULL, nelem, elsize, file, line); -} - -static void leakcheck_free(void *ptr) -{ - free(ptr); -} - -int git_win32_leakcheck_init_allocator(git_allocator *allocator) -{ - allocator->gmalloc = leakcheck_malloc; - allocator->gcalloc = leakcheck_calloc; - allocator->gstrdup = leakcheck_strdup; - allocator->gstrndup = leakcheck_strndup; - allocator->gsubstrdup = leakcheck_substrdup; - allocator->grealloc = leakcheck_realloc; - allocator->greallocarray = leakcheck_reallocarray; - allocator->gmallocarray = leakcheck_mallocarray; - allocator->gfree = leakcheck_free; - return 0; -} - -#else - -int git_win32_leakcheck_init_allocator(git_allocator *allocator) -{ - GIT_UNUSED(allocator); - git_error_set(GIT_EINVALID, "leakcheck memory allocator not available"); - return -1; -} - -#endif diff --git a/src/libgit2/allocators/win32_leakcheck.h b/src/libgit2/allocators/win32_leakcheck.h deleted file mode 100644 index 089690f90..000000000 --- a/src/libgit2/allocators/win32_leakcheck.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_allocators_win32_leakcheck_h -#define INCLUDE_allocators_win32_leakcheck_h - -#include "common.h" - -#include "alloc.h" - -int git_win32_leakcheck_init_allocator(git_allocator *allocator); - -#endif diff --git a/src/libgit2/array.h b/src/libgit2/array.h deleted file mode 100644 index e97688b36..000000000 --- a/src/libgit2/array.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_array_h__ -#define INCLUDE_array_h__ - -#include "common.h" - -/* - * Use this to declare a typesafe resizable array of items, a la: - * - * git_array_t(int) my_ints = GIT_ARRAY_INIT; - * ... - * int *i = git_array_alloc(my_ints); - * GIT_ERROR_CHECK_ALLOC(i); - * ... - * git_array_clear(my_ints); - * - * You may also want to do things like: - * - * typedef git_array_t(my_struct) my_struct_array_t; - */ -#define git_array_t(type) struct { type *ptr; size_t size, asize; } - -#define GIT_ARRAY_INIT { NULL, 0, 0 } - -#define git_array_init(a) \ - do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0) - -#define git_array_init_to_size(a, desired) \ - do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0) - -#define git_array_clear(a) \ - do { git__free((a).ptr); git_array_init(a); } while (0) - -#define GIT_ERROR_CHECK_ARRAY(a) GIT_ERROR_CHECK_ALLOC((a).ptr) - - -typedef git_array_t(char) git_array_generic_t; - -/* use a generic array for growth, return 0 on success */ -GIT_INLINE(int) git_array_grow(void *_a, size_t item_size) -{ - volatile git_array_generic_t *a = _a; - size_t new_size; - char *new_array; - - if (a->size < 8) { - new_size = 8; - } else { - if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, a->size, 3)) - goto on_oom; - new_size /= 2; - } - - if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL) - goto on_oom; - - a->ptr = new_array; - a->asize = new_size; - return 0; - -on_oom: - git_array_clear(*a); - return -1; -} - -#define git_array_alloc(a) \ - (((a).size < (a).asize || git_array_grow(&(a), sizeof(*(a).ptr)) == 0) ? \ - &(a).ptr[(a).size++] : (void *)NULL) - -#define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : (void *)NULL) - -#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : (void *)NULL) - -#define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : (void *)NULL) - -#define git_array_size(a) (a).size - -#define git_array_valid_index(a, i) ((i) < (a).size) - -#define git_array_foreach(a, i, element) \ - for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++) - -GIT_INLINE(int) git_array__search( - size_t *out, - void *array_ptr, - size_t item_size, - size_t array_len, - int (*compare)(const void *, const void *), - const void *key) -{ - size_t lim; - unsigned char *part, *array = array_ptr, *base = array_ptr; - int cmp = -1; - - for (lim = array_len; lim != 0; lim >>= 1) { - part = base + (lim >> 1) * item_size; - cmp = (*compare)(key, part); - - if (cmp == 0) { - base = part; - break; - } - if (cmp > 0) { /* key > p; take right partition */ - base = part + 1 * item_size; - lim--; - } /* else take left partition */ - } - - if (out) - *out = (base - array) / item_size; - - return (cmp == 0) ? 0 : GIT_ENOTFOUND; -} - -#define git_array_search(out, a, cmp, key) \ - git_array__search(out, (a).ptr, sizeof(*(a).ptr), (a).size, \ - (cmp), (key)) - -#endif diff --git a/src/libgit2/assert_safe.h b/src/libgit2/assert_safe.h deleted file mode 100644 index 8c261100f..000000000 --- a/src/libgit2/assert_safe.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_assert_safe_h__ -#define INCLUDE_assert_safe_h__ - -/* - * In a debug build, we'll assert(3) for aide in debugging. In release - * builds, we will provide macros that will set an error message that - * indicate a failure and return. Note that memory leaks can occur in - * a release-mode assertion failure -- it is impractical to provide - * safe clean up routines in these very extreme failures, but care - * should be taken to not leak very large objects. - */ - -#if (defined(_DEBUG) || defined(GIT_ASSERT_HARD)) && GIT_ASSERT_HARD != 0 -# include - -# define GIT_ASSERT(expr) assert(expr) -# define GIT_ASSERT_ARG(expr) assert(expr) - -# define GIT_ASSERT_WITH_RETVAL(expr, fail) assert(expr) -# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) assert(expr) -#else - -/** Internal consistency check to stop the function. */ -# define GIT_ASSERT(expr) GIT_ASSERT_WITH_RETVAL(expr, -1) - -/** - * Assert that a consumer-provided argument is valid, setting an - * actionable error message and returning -1 if it is not. - */ -# define GIT_ASSERT_ARG(expr) GIT_ASSERT_ARG_WITH_RETVAL(expr, -1) - -/** Internal consistency check to return the `fail` param on failure. */ -# define GIT_ASSERT_WITH_RETVAL(expr, fail) \ - GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INTERNAL, "unrecoverable internal error", fail) - -/** - * Assert that a consumer-provided argument is valid, setting an - * actionable error message and returning the `fail` param if not. - */ -# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) \ - GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INVALID, "invalid argument", fail) - -# define GIT_ASSERT__WITH_RETVAL(expr, code, msg, fail) do { \ - if (!(expr)) { \ - git_error_set(code, "%s: '%s'", msg, #expr); \ - return fail; \ - } \ - } while(0) - -#endif /* GIT_ASSERT_HARD */ - -#endif diff --git a/src/libgit2/bitvec.h b/src/libgit2/bitvec.h deleted file mode 100644 index 544832d95..000000000 --- a/src/libgit2/bitvec.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_bitvec_h__ -#define INCLUDE_bitvec_h__ - -#include "common.h" - -/* - * This is a silly little fixed length bit vector type that will store - * vectors of 64 bits or less directly in the structure and allocate - * memory for vectors longer than 64 bits. You can use the two versions - * transparently through the API and avoid heap allocation completely when - * using a short bit vector as a result. - */ -typedef struct { - size_t length; - union { - uint64_t *words; - uint64_t bits; - } u; -} git_bitvec; - -GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity) -{ - memset(bv, 0x0, sizeof(*bv)); - - if (capacity >= 64) { - bv->length = (capacity / 64) + 1; - bv->u.words = git__calloc(bv->length, sizeof(uint64_t)); - if (!bv->u.words) - return -1; - } - - return 0; -} - -#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64)) -#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits) - -GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on) -{ - uint64_t *word = GIT_BITVEC_WORD(bv, bit); - uint64_t mask = GIT_BITVEC_MASK(bit); - - if (on) - *word |= mask; - else - *word &= ~mask; -} - -GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit) -{ - uint64_t *word = GIT_BITVEC_WORD(bv, bit); - return (*word & GIT_BITVEC_MASK(bit)) != 0; -} - -GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv) -{ - if (!bv->length) - bv->u.bits = 0; - else - memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t)); -} - -GIT_INLINE(void) git_bitvec_free(git_bitvec *bv) -{ - if (bv->length) - git__free(bv->u.words); -} - -#endif diff --git a/src/libgit2/blob.c b/src/libgit2/blob.c index 19ce8b3b5..b1680d3a8 100644 --- a/src/libgit2/blob.c +++ b/src/libgit2/blob.c @@ -101,7 +101,7 @@ static int write_file_stream( git_oid *id, git_odb *odb, const char *path, git_object_size_t file_size) { int fd, error; - char buffer[FILEIO_BUFSIZE]; + char buffer[GIT_BUFSIZE_FILEIO]; git_odb_stream *stream = NULL; ssize_t read_len = -1; git_object_size_t written = 0; diff --git a/src/libgit2/cc-compat.h b/src/libgit2/cc-compat.h deleted file mode 100644 index a0971e86c..000000000 --- a/src/libgit2/cc-compat.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_cc_compat_h__ -#define INCLUDE_cc_compat_h__ - -#include - -/* - * See if our compiler is known to support flexible array members. - */ -#ifndef GIT_FLEX_ARRAY -# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -# define GIT_FLEX_ARRAY /* empty */ -# elif defined(__GNUC__) -# if (__GNUC__ >= 3) -# define GIT_FLEX_ARRAY /* empty */ -# else -# define GIT_FLEX_ARRAY 0 /* older GNU extension */ -# endif -# endif - -/* Default to safer but a bit wasteful traditional style */ -# ifndef GIT_FLEX_ARRAY -# define GIT_FLEX_ARRAY 1 -# endif -#endif - -#if defined(__GNUC__) -# define GIT_ALIGN(x,size) x __attribute__ ((aligned(size))) -#elif defined(_MSC_VER) -# define GIT_ALIGN(x,size) __declspec(align(size)) x -#else -# define GIT_ALIGN(x,size) x -#endif - -#if defined(__GNUC__) -# define GIT_UNUSED(x) \ - do { \ - __typeof__(x) _unused __attribute__((unused)); \ - _unused = (x); \ - } while (0) -#else -# define GIT_UNUSED(x) ((void)(x)) -#endif - -/* Define the printf format specifier to use for size_t output */ -#if defined(_MSC_VER) || defined(__MINGW32__) - -/* Visual Studio 2012 and prior lack PRId64 entirely */ -# ifndef PRId64 -# define PRId64 "I64d" -# endif - -/* The first block is needed to avoid warnings on MingW amd64 */ -# if (SIZE_MAX == ULLONG_MAX) -# define PRIuZ "I64u" -# define PRIxZ "I64x" -# define PRIXZ "I64X" -# define PRIdZ "I64d" -# else -# define PRIuZ "Iu" -# define PRIxZ "Ix" -# define PRIXZ "IX" -# define PRIdZ "Id" -# endif - -#else -# define PRIuZ "zu" -# define PRIxZ "zx" -# define PRIXZ "zX" -# define PRIdZ "zd" -#endif - -/* Microsoft Visual C/C++ */ -#if defined(_MSC_VER) -/* disable "deprecated function" warnings */ -# pragma warning ( disable : 4996 ) -/* disable "conditional expression is constant" level 4 warnings */ -# pragma warning ( disable : 4127 ) -#endif - -#if defined (_MSC_VER) - typedef unsigned char bool; -# ifndef true -# define true 1 -# endif -# ifndef false -# define false 0 -# endif -#else -# include -#endif - -#ifndef va_copy -# ifdef __va_copy -# define va_copy(dst, src) __va_copy(dst, src) -# else -# define va_copy(dst, src) ((dst) = (src)) -# endif -#endif - -#endif diff --git a/src/libgit2/common.h b/src/libgit2/common.h index 549bddb59..bb9ec5ac1 100644 --- a/src/libgit2/common.h +++ b/src/libgit2/common.h @@ -7,136 +7,31 @@ #ifndef INCLUDE_common_h__ #define INCLUDE_common_h__ -#ifndef LIBGIT2_NO_FEATURES_H -# include "git2/sys/features.h" -#endif - -#include "git2/common.h" -#include "cc-compat.h" - -/** Declare a function as always inlined. */ -#if defined(_MSC_VER) -# define GIT_INLINE(type) static __inline type -#elif defined(__GNUC__) -# define GIT_INLINE(type) static __inline__ type -#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -# define GIT_INLINE(type) static inline type -#else -# define GIT_INLINE(type) static type -#endif - -/** Support for gcc/clang __has_builtin intrinsic */ -#ifndef __has_builtin -# define __has_builtin(x) 0 -#endif - -/** - * Declare that a function's return value must be used. - * - * Used mostly to guard against potential silent bugs at runtime. This is - * recommended to be added to functions that: - * - * - Allocate / reallocate memory. This prevents memory leaks or errors where - * buffers are expected to have grown to a certain size, but could not be - * resized. - * - Acquire locks. When a lock cannot be acquired, that will almost certainly - * cause a data race / undefined behavior. - */ -#if defined(__GNUC__) -# define GIT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) -#else -# define GIT_WARN_UNUSED_RESULT -#endif - -#include -#include -#include -#include -#include -#include - -#include -#include - -#ifdef GIT_WIN32 - -# include -# include -# include -# include -# include -# include "win32/msvc-compat.h" -# include "win32/mingw-compat.h" -# include "win32/w32_common.h" -# include "win32/win32-compat.h" -# include "win32/error.h" -# include "win32/version.h" -# ifdef GIT_THREADS -# include "win32/thread.h" -# endif - -#else - -# include -# include -# ifdef GIT_THREADS -# include -# include -# endif - -#define GIT_LIBGIT2_CALL -#define GIT_SYSTEM_CALL - -#ifdef GIT_USE_STAT_ATIMESPEC -# define st_atim st_atimespec -# define st_ctim st_ctimespec -# define st_mtim st_mtimespec -#endif - -# include - -#endif - -#include "git2/types.h" -#include "git2/errors.h" +#include "git2_util.h" #include "errors.h" -#include "thread.h" -#include "integer.h" -#include "assert_safe.h" -#include "utf8.h" /* - * Include the declarations for deprecated functions; this ensures - * that they're decorated with the proper extern/visibility attributes. - */ +* Include the declarations for deprecated functions; this ensures +* that they're decorated with the proper extern/visibility attributes. +*/ #include "git2/deprecated.h" #include "posix.h" -#define DEFAULT_BUFSIZE 65536 -#define FILEIO_BUFSIZE DEFAULT_BUFSIZE -#define FILTERIO_BUFSIZE DEFAULT_BUFSIZE -#define NETIO_BUFSIZE DEFAULT_BUFSIZE - -/** - * Check a pointer allocation result, returning -1 if it failed. - */ -#define GIT_ERROR_CHECK_ALLOC(ptr) do { \ - if ((ptr) == NULL) { return -1; } \ - } while(0) - /** - * Check a string buffer allocation result, returning -1 if it failed. + * Initialize a structure with a version. */ -#define GIT_ERROR_CHECK_ALLOC_STR(buf) do { \ - if ((void *)(buf) == NULL || git_str_oom(buf)) { return -1; } \ - } while(0) +GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int version) +{ + memset(structure, 0, len); + *((int*)structure) = version; +} +#define GIT_INIT_STRUCTURE(S,V) git__init_structure(S, sizeof(*S), V) -/** - * Check a return value and propagate result if non-zero. - */ -#define GIT_ERROR_CHECK_ERROR(code) \ - do { int _err = (code); if (_err) return _err; } while (0) +#define GIT_INIT_STRUCTURE_FROM_TEMPLATE(PTR,VERSION,TYPE,TPL) do { \ + TYPE _tmpl = TPL; \ + GIT_ERROR_CHECK_VERSION(&(VERSION), _tmpl.version, #TYPE); \ + memcpy((PTR), &_tmpl, sizeof(_tmpl)); } while (0) /** * Check a versioned structure for validity @@ -157,58 +52,4 @@ GIT_INLINE(int) git_error__check_version(const void *structure, unsigned int exp } #define GIT_ERROR_CHECK_VERSION(S,V,N) if (git_error__check_version(S,V,N) < 0) return -1 -/** - * Initialize a structure with a version. - */ -GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int version) -{ - memset(structure, 0, len); - *((int*)structure) = version; -} -#define GIT_INIT_STRUCTURE(S,V) git__init_structure(S, sizeof(*S), V) - -#define GIT_INIT_STRUCTURE_FROM_TEMPLATE(PTR,VERSION,TYPE,TPL) do { \ - TYPE _tmpl = TPL; \ - GIT_ERROR_CHECK_VERSION(&(VERSION), _tmpl.version, #TYPE); \ - memcpy((PTR), &_tmpl, sizeof(_tmpl)); } while (0) - - -/** Check for additive overflow, setting an error if would occur. */ -#define GIT_ADD_SIZET_OVERFLOW(out, one, two) \ - (git__add_sizet_overflow(out, one, two) ? (git_error_set_oom(), 1) : 0) - -/** Check for additive overflow, setting an error if would occur. */ -#define GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize) \ - (git__multiply_sizet_overflow(out, nelem, elsize) ? (git_error_set_oom(), 1) : 0) - -/** Check for additive overflow, failing if it would occur. */ -#define GIT_ERROR_CHECK_ALLOC_ADD(out, one, two) \ - if (GIT_ADD_SIZET_OVERFLOW(out, one, two)) { return -1; } - -#define GIT_ERROR_CHECK_ALLOC_ADD3(out, one, two, three) \ - if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), three)) { return -1; } - -#define GIT_ERROR_CHECK_ALLOC_ADD4(out, one, two, three, four) \ - if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), four)) { return -1; } - -#define GIT_ERROR_CHECK_ALLOC_ADD5(out, one, two, three, four, five) \ - if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), four) || \ - GIT_ADD_SIZET_OVERFLOW(out, *(out), five)) { return -1; } - -/** Check for multiplicative overflow, failing if it would occur. */ -#define GIT_ERROR_CHECK_ALLOC_MULTIPLY(out, nelem, elsize) \ - if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; } - -/* NOTE: other git_error functions are in the public errors.h header file */ - -/* Forward declare git_str */ -typedef struct git_str git_str; - -#include "util.h" - #endif diff --git a/src/libgit2/date.c b/src/libgit2/date.c deleted file mode 100644 index 0e5ffc96b..000000000 --- a/src/libgit2/date.c +++ /dev/null @@ -1,898 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ - -#include "common.h" - -#ifndef GIT_WIN32 -#include -#endif - -#include "util.h" -#include "posix.h" -#include "date.h" - -#include -#include - -typedef enum { - DATE_NORMAL = 0, - DATE_RELATIVE, - DATE_SHORT, - DATE_LOCAL, - DATE_ISO8601, - DATE_RFC2822, - DATE_RAW -} date_mode; - -/* - * This is like mktime, but without normalization of tm_wday and tm_yday. - */ -static git_time_t tm_to_time_t(const struct tm *tm) -{ - static const int mdays[] = { - 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 - }; - int year = tm->tm_year - 70; - int month = tm->tm_mon; - int day = tm->tm_mday; - - if (year < 0 || year > 129) /* algo only works for 1970-2099 */ - return -1; - if (month < 0 || month > 11) /* array bounds */ - return -1; - if (month < 2 || (year + 2) % 4) - day--; - if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0) - return -1; - return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL + - tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec; -} - -static const char *month_names[] = { - "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" -}; - -static const char *weekday_names[] = { - "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays" -}; - - - -/* - * Check these. And note how it doesn't do the summer-time conversion. - * - * In my world, it's always summer, and things are probably a bit off - * in other ways too. - */ -static const struct { - const char *name; - int offset; - int dst; -} timezone_names[] = { - { "IDLW", -12, 0, }, /* International Date Line West */ - { "NT", -11, 0, }, /* Nome */ - { "CAT", -10, 0, }, /* Central Alaska */ - { "HST", -10, 0, }, /* Hawaii Standard */ - { "HDT", -10, 1, }, /* Hawaii Daylight */ - { "YST", -9, 0, }, /* Yukon Standard */ - { "YDT", -9, 1, }, /* Yukon Daylight */ - { "PST", -8, 0, }, /* Pacific Standard */ - { "PDT", -8, 1, }, /* Pacific Daylight */ - { "MST", -7, 0, }, /* Mountain Standard */ - { "MDT", -7, 1, }, /* Mountain Daylight */ - { "CST", -6, 0, }, /* Central Standard */ - { "CDT", -6, 1, }, /* Central Daylight */ - { "EST", -5, 0, }, /* Eastern Standard */ - { "EDT", -5, 1, }, /* Eastern Daylight */ - { "AST", -3, 0, }, /* Atlantic Standard */ - { "ADT", -3, 1, }, /* Atlantic Daylight */ - { "WAT", -1, 0, }, /* West Africa */ - - { "GMT", 0, 0, }, /* Greenwich Mean */ - { "UTC", 0, 0, }, /* Universal (Coordinated) */ - { "Z", 0, 0, }, /* Zulu, alias for UTC */ - - { "WET", 0, 0, }, /* Western European */ - { "BST", 0, 1, }, /* British Summer */ - { "CET", +1, 0, }, /* Central European */ - { "MET", +1, 0, }, /* Middle European */ - { "MEWT", +1, 0, }, /* Middle European Winter */ - { "MEST", +1, 1, }, /* Middle European Summer */ - { "CEST", +1, 1, }, /* Central European Summer */ - { "MESZ", +1, 1, }, /* Middle European Summer */ - { "FWT", +1, 0, }, /* French Winter */ - { "FST", +1, 1, }, /* French Summer */ - { "EET", +2, 0, }, /* Eastern Europe */ - { "EEST", +2, 1, }, /* Eastern European Daylight */ - { "WAST", +7, 0, }, /* West Australian Standard */ - { "WADT", +7, 1, }, /* West Australian Daylight */ - { "CCT", +8, 0, }, /* China Coast */ - { "JST", +9, 0, }, /* Japan Standard */ - { "EAST", +10, 0, }, /* Eastern Australian Standard */ - { "EADT", +10, 1, }, /* Eastern Australian Daylight */ - { "GST", +10, 0, }, /* Guam Standard */ - { "NZT", +12, 0, }, /* New Zealand */ - { "NZST", +12, 0, }, /* New Zealand Standard */ - { "NZDT", +12, 1, }, /* New Zealand Daylight */ - { "IDLE", +12, 0, }, /* International Date Line East */ -}; - -static size_t match_string(const char *date, const char *str) -{ - size_t i = 0; - - for (i = 0; *date; date++, str++, i++) { - if (*date == *str) - continue; - if (toupper(*date) == toupper(*str)) - continue; - if (!isalnum(*date)) - break; - return 0; - } - return i; -} - -static int skip_alpha(const char *date) -{ - int i = 0; - do { - i++; - } while (isalpha(date[i])); - return i; -} - -/* -* Parse month, weekday, or timezone name -*/ -static size_t match_alpha(const char *date, struct tm *tm, int *offset) -{ - unsigned int i; - - for (i = 0; i < 12; i++) { - size_t match = match_string(date, month_names[i]); - if (match >= 3) { - tm->tm_mon = i; - return match; - } - } - - for (i = 0; i < 7; i++) { - size_t match = match_string(date, weekday_names[i]); - if (match >= 3) { - tm->tm_wday = i; - return match; - } - } - - for (i = 0; i < ARRAY_SIZE(timezone_names); i++) { - size_t match = match_string(date, timezone_names[i].name); - if (match >= 3 || match == strlen(timezone_names[i].name)) { - int off = timezone_names[i].offset; - - /* This is bogus, but we like summer */ - off += timezone_names[i].dst; - - /* Only use the tz name offset if we don't have anything better */ - if (*offset == -1) - *offset = 60*off; - - return match; - } - } - - if (match_string(date, "PM") == 2) { - tm->tm_hour = (tm->tm_hour % 12) + 12; - return 2; - } - - if (match_string(date, "AM") == 2) { - tm->tm_hour = (tm->tm_hour % 12) + 0; - return 2; - } - - /* BAD */ - return skip_alpha(date); -} - -static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm) -{ - if (month > 0 && month < 13 && day > 0 && day < 32) { - struct tm check = *tm; - struct tm *r = (now_tm ? &check : tm); - git_time_t specified; - - r->tm_mon = month - 1; - r->tm_mday = day; - if (year == -1) { - if (!now_tm) - return 1; - r->tm_year = now_tm->tm_year; - } - else if (year >= 1970 && year < 2100) - r->tm_year = year - 1900; - else if (year > 70 && year < 100) - r->tm_year = year; - else if (year < 38) - r->tm_year = year + 100; - else - return 0; - if (!now_tm) - return 1; - - specified = tm_to_time_t(r); - - /* Be it commit time or author time, it does not make - * sense to specify timestamp way into the future. Make - * sure it is not later than ten days from now... - */ - if (now + 10*24*3600 < specified) - return 0; - tm->tm_mon = r->tm_mon; - tm->tm_mday = r->tm_mday; - if (year != -1) - tm->tm_year = r->tm_year; - return 1; - } - return 0; -} - -static size_t match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm) -{ - time_t now; - struct tm now_tm; - struct tm *refuse_future; - long num2, num3; - - num2 = strtol(end+1, &end, 10); - num3 = -1; - if (*end == c && isdigit(end[1])) - num3 = strtol(end+1, &end, 10); - - /* Time? Date? */ - switch (c) { - case ':': - if (num3 < 0) - num3 = 0; - if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) { - tm->tm_hour = num; - tm->tm_min = num2; - tm->tm_sec = num3; - break; - } - return 0; - - case '-': - case '/': - case '.': - now = time(NULL); - refuse_future = NULL; - if (p_gmtime_r(&now, &now_tm)) - refuse_future = &now_tm; - - if (num > 70) { - /* yyyy-mm-dd? */ - if (is_date(num, num2, num3, refuse_future, now, tm)) - break; - /* yyyy-dd-mm? */ - if (is_date(num, num3, num2, refuse_future, now, tm)) - break; - } - /* Our eastern European friends say dd.mm.yy[yy] - * is the norm there, so giving precedence to - * mm/dd/yy[yy] form only when separator is not '.' - */ - if (c != '.' && - is_date(num3, num, num2, refuse_future, now, tm)) - break; - /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */ - if (is_date(num3, num2, num, refuse_future, now, tm)) - break; - /* Funny European mm.dd.yy */ - if (c == '.' && - is_date(num3, num, num2, refuse_future, now, tm)) - break; - return 0; - } - return end - date; -} - -/* - * Have we filled in any part of the time/date yet? - * We just do a binary 'and' to see if the sign bit - * is set in all the values. - */ -static int nodate(struct tm *tm) -{ - return (tm->tm_year & - tm->tm_mon & - tm->tm_mday & - tm->tm_hour & - tm->tm_min & - tm->tm_sec) < 0; -} - -/* - * We've seen a digit. Time? Year? Date? - */ -static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt) -{ - size_t n; - char *end; - unsigned long num; - - num = strtoul(date, &end, 10); - - /* - * Seconds since 1970? We trigger on that for any numbers with - * more than 8 digits. This is because we don't want to rule out - * numbers like 20070606 as a YYYYMMDD date. - */ - if (num >= 100000000 && nodate(tm)) { - time_t time = num; - if (p_gmtime_r(&time, tm)) { - *tm_gmt = 1; - return end - date; - } - } - - /* - * Check for special formats: num[-.:/]num[same]num - */ - switch (*end) { - case ':': - case '.': - case '/': - case '-': - if (isdigit(end[1])) { - size_t match = match_multi_number(num, *end, date, end, tm); - if (match) - return match; - } - } - - /* - * None of the special formats? Try to guess what - * the number meant. We use the number of digits - * to make a more educated guess.. - */ - n = 0; - do { - n++; - } while (isdigit(date[n])); - - /* Four-digit year or a timezone? */ - if (n == 4) { - if (num <= 1400 && *offset == -1) { - unsigned int minutes = num % 100; - unsigned int hours = num / 100; - *offset = hours*60 + minutes; - } else if (num > 1900 && num < 2100) - tm->tm_year = num - 1900; - return n; - } - - /* - * Ignore lots of numerals. We took care of 4-digit years above. - * Days or months must be one or two digits. - */ - if (n > 2) - return n; - - /* - * NOTE! We will give precedence to day-of-month over month or - * year numbers in the 1-12 range. So 05 is always "mday 5", - * unless we already have a mday.. - * - * IOW, 01 Apr 05 parses as "April 1st, 2005". - */ - if (num > 0 && num < 32 && tm->tm_mday < 0) { - tm->tm_mday = num; - return n; - } - - /* Two-digit year? */ - if (n == 2 && tm->tm_year < 0) { - if (num < 10 && tm->tm_mday >= 0) { - tm->tm_year = num + 100; - return n; - } - if (num >= 70) { - tm->tm_year = num; - return n; - } - } - - if (num > 0 && num < 13 && tm->tm_mon < 0) - tm->tm_mon = num-1; - - return n; -} - -static size_t match_tz(const char *date, int *offp) -{ - char *end; - int hour = strtoul(date + 1, &end, 10); - size_t n = end - (date + 1); - int min = 0; - - if (n == 4) { - /* hhmm */ - min = hour % 100; - hour = hour / 100; - } else if (n != 2) { - min = 99; /* random stuff */ - } else if (*end == ':') { - /* hh:mm? */ - min = strtoul(end + 1, &end, 10); - if (end - (date + 1) != 5) - min = 99; /* random stuff */ - } /* otherwise we parsed "hh" */ - - /* - * Don't accept any random stuff. Even though some places have - * offset larger than 12 hours (e.g. Pacific/Kiritimati is at - * UTC+14), there is something wrong if hour part is much - * larger than that. We might also want to check that the - * minutes are divisible by 15 or something too. (Offset of - * Kathmandu, Nepal is UTC+5:45) - */ - if (min < 60 && hour < 24) { - int offset = hour * 60 + min; - if (*date == '-') - offset = -offset; - *offp = offset; - } - return end - date; -} - -/* - * Parse a string like "0 +0000" as ancient timestamp near epoch, but - * only when it appears not as part of any other string. - */ -static int match_object_header_date(const char *date, git_time_t *timestamp, int *offset) -{ - char *end; - unsigned long stamp; - int ofs; - - if (*date < '0' || '9' <= *date) - return -1; - stamp = strtoul(date, &end, 10); - if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-')) - return -1; - date = end + 2; - ofs = strtol(date, &end, 10); - if ((*end != '\0' && (*end != '\n')) || end != date + 4) - return -1; - ofs = (ofs / 100) * 60 + (ofs % 100); - if (date[-1] == '-') - ofs = -ofs; - *timestamp = stamp; - *offset = ofs; - return 0; -} - -/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822 - (i.e. English) day/month names, and it doesn't work correctly with %z. */ -static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset) -{ - struct tm tm; - int tm_gmt; - git_time_t dummy_timestamp; - int dummy_offset; - - if (!timestamp) - timestamp = &dummy_timestamp; - if (!offset) - offset = &dummy_offset; - - memset(&tm, 0, sizeof(tm)); - tm.tm_year = -1; - tm.tm_mon = -1; - tm.tm_mday = -1; - tm.tm_isdst = -1; - tm.tm_hour = -1; - tm.tm_min = -1; - tm.tm_sec = -1; - *offset = -1; - tm_gmt = 0; - - if (*date == '@' && - !match_object_header_date(date + 1, timestamp, offset)) - return 0; /* success */ - for (;;) { - size_t match = 0; - unsigned char c = *date; - - /* Stop at end of string or newline */ - if (!c || c == '\n') - break; - - if (isalpha(c)) - match = match_alpha(date, &tm, offset); - else if (isdigit(c)) - match = match_digit(date, &tm, offset, &tm_gmt); - else if ((c == '-' || c == '+') && isdigit(date[1])) - match = match_tz(date, offset); - - if (!match) { - /* BAD */ - match = 1; - } - - date += match; - } - - /* mktime uses local timezone */ - *timestamp = tm_to_time_t(&tm); - if (*offset == -1) - *offset = (int)((time_t)*timestamp - mktime(&tm)) / 60; - - if (*timestamp == (git_time_t)-1) - return -1; - - if (!tm_gmt) - *timestamp -= *offset * 60; - return 0; /* success */ -} - - -/* - * Relative time update (eg "2 days ago"). If we haven't set the time - * yet, we need to set it from current time. - */ -static git_time_t update_tm(struct tm *tm, struct tm *now, unsigned long sec) -{ - time_t n; - - if (tm->tm_mday < 0) - tm->tm_mday = now->tm_mday; - if (tm->tm_mon < 0) - tm->tm_mon = now->tm_mon; - if (tm->tm_year < 0) { - tm->tm_year = now->tm_year; - if (tm->tm_mon > now->tm_mon) - tm->tm_year--; - } - - n = mktime(tm) - sec; - p_localtime_r(&n, tm); - return n; -} - -static void date_now(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - update_tm(tm, now, 0); -} - -static void date_yesterday(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - update_tm(tm, now, 24*60*60); -} - -static void date_time(struct tm *tm, struct tm *now, int hour) -{ - if (tm->tm_hour < hour) - date_yesterday(tm, now, NULL); - tm->tm_hour = hour; - tm->tm_min = 0; - tm->tm_sec = 0; -} - -static void date_midnight(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - date_time(tm, now, 0); -} - -static void date_noon(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - date_time(tm, now, 12); -} - -static void date_tea(struct tm *tm, struct tm *now, int *num) -{ - GIT_UNUSED(num); - date_time(tm, now, 17); -} - -static void date_pm(struct tm *tm, struct tm *now, int *num) -{ - int hour, n = *num; - *num = 0; - GIT_UNUSED(now); - - hour = tm->tm_hour; - if (n) { - hour = n; - tm->tm_min = 0; - tm->tm_sec = 0; - } - tm->tm_hour = (hour % 12) + 12; -} - -static void date_am(struct tm *tm, struct tm *now, int *num) -{ - int hour, n = *num; - *num = 0; - GIT_UNUSED(now); - - hour = tm->tm_hour; - if (n) { - hour = n; - tm->tm_min = 0; - tm->tm_sec = 0; - } - tm->tm_hour = (hour % 12); -} - -static void date_never(struct tm *tm, struct tm *now, int *num) -{ - time_t n = 0; - GIT_UNUSED(now); - GIT_UNUSED(num); - p_localtime_r(&n, tm); -} - -static const struct special { - const char *name; - void (*fn)(struct tm *, struct tm *, int *); -} special[] = { - { "yesterday", date_yesterday }, - { "noon", date_noon }, - { "midnight", date_midnight }, - { "tea", date_tea }, - { "PM", date_pm }, - { "AM", date_am }, - { "never", date_never }, - { "now", date_now }, - { NULL } -}; - -static const char *number_name[] = { - "zero", "one", "two", "three", "four", - "five", "six", "seven", "eight", "nine", "ten", -}; - -static const struct typelen { - const char *type; - int length; -} typelen[] = { - { "seconds", 1 }, - { "minutes", 60 }, - { "hours", 60*60 }, - { "days", 24*60*60 }, - { "weeks", 7*24*60*60 }, - { NULL } -}; - -static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched) -{ - const struct typelen *tl; - const struct special *s; - const char *end = date; - int i; - - while (isalpha(*++end)) - /* scan to non-alpha */; - - for (i = 0; i < 12; i++) { - size_t match = match_string(date, month_names[i]); - if (match >= 3) { - tm->tm_mon = i; - *touched = 1; - return end; - } - } - - for (s = special; s->name; s++) { - size_t len = strlen(s->name); - if (match_string(date, s->name) == len) { - s->fn(tm, now, num); - *touched = 1; - return end; - } - } - - if (!*num) { - for (i = 1; i < 11; i++) { - size_t len = strlen(number_name[i]); - if (match_string(date, number_name[i]) == len) { - *num = i; - *touched = 1; - return end; - } - } - if (match_string(date, "last") == 4) { - *num = 1; - *touched = 1; - } - return end; - } - - tl = typelen; - while (tl->type) { - size_t len = strlen(tl->type); - if (match_string(date, tl->type) >= len-1) { - update_tm(tm, now, tl->length * (unsigned long)*num); - *num = 0; - *touched = 1; - return end; - } - tl++; - } - - for (i = 0; i < 7; i++) { - size_t match = match_string(date, weekday_names[i]); - if (match >= 3) { - int diff, n = *num -1; - *num = 0; - - diff = tm->tm_wday - i; - if (diff <= 0) - n++; - diff += 7*n; - - update_tm(tm, now, diff * 24 * 60 * 60); - *touched = 1; - return end; - } - } - - if (match_string(date, "months") >= 5) { - int n; - update_tm(tm, now, 0); /* fill in date fields if needed */ - n = tm->tm_mon - *num; - *num = 0; - while (n < 0) { - n += 12; - tm->tm_year--; - } - tm->tm_mon = n; - *touched = 1; - return end; - } - - if (match_string(date, "years") >= 4) { - update_tm(tm, now, 0); /* fill in date fields if needed */ - tm->tm_year -= *num; - *num = 0; - *touched = 1; - return end; - } - - return end; -} - -static const char *approxidate_digit(const char *date, struct tm *tm, int *num) -{ - char *end; - unsigned long number = strtoul(date, &end, 10); - - switch (*end) { - case ':': - case '.': - case '/': - case '-': - if (isdigit(end[1])) { - size_t match = match_multi_number(number, *end, date, end, tm); - if (match) - return date + match; - } - } - - /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */ - if (date[0] != '0' || end - date <= 2) - *num = number; - return end; -} - -/* - * Do we have a pending number at the end, or when - * we see a new one? Let's assume it's a month day, - * as in "Dec 6, 1992" - */ -static void pending_number(struct tm *tm, int *num) -{ - int number = *num; - - if (number) { - *num = 0; - if (tm->tm_mday < 0 && number < 32) - tm->tm_mday = number; - else if (tm->tm_mon < 0 && number < 13) - tm->tm_mon = number-1; - else if (tm->tm_year < 0) { - if (number > 1969 && number < 2100) - tm->tm_year = number - 1900; - else if (number > 69 && number < 100) - tm->tm_year = number; - else if (number < 38) - tm->tm_year = 100 + number; - /* We mess up for number = 00 ? */ - } - } -} - -static git_time_t approxidate_str(const char *date, - time_t time_sec, - int *error_ret) -{ - int number = 0; - int touched = 0; - struct tm tm = {0}, now; - - p_localtime_r(&time_sec, &tm); - now = tm; - - tm.tm_year = -1; - tm.tm_mon = -1; - tm.tm_mday = -1; - - for (;;) { - unsigned char c = *date; - if (!c) - break; - date++; - if (isdigit(c)) { - pending_number(&tm, &number); - date = approxidate_digit(date-1, &tm, &number); - touched = 1; - continue; - } - if (isalpha(c)) - date = approxidate_alpha(date-1, &tm, &now, &number, &touched); - } - pending_number(&tm, &number); - if (!touched) - *error_ret = -1; - return update_tm(&tm, &now, 0); -} - -int git_date_parse(git_time_t *out, const char *date) -{ - time_t time_sec; - git_time_t timestamp; - int offset, error_ret=0; - - if (!parse_date_basic(date, ×tamp, &offset)) { - *out = timestamp; - return 0; - } - - if (time(&time_sec) == -1) - return -1; - - *out = approxidate_str(date, time_sec, &error_ret); - return error_ret; -} - -int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset) -{ - time_t t; - struct tm gmt; - - GIT_ASSERT_ARG(out); - - t = (time_t) (time + offset * 60); - - if (p_gmtime_r(&t, &gmt) == NULL) - return -1; - - return git_str_printf(out, "%.3s, %u %.3s %.4u %02u:%02u:%02u %+03d%02d", - weekday_names[gmt.tm_wday], - gmt.tm_mday, - month_names[gmt.tm_mon], - gmt.tm_year + 1900, - gmt.tm_hour, gmt.tm_min, gmt.tm_sec, - offset / 60, offset % 60); -} - diff --git a/src/libgit2/date.h b/src/libgit2/date.h deleted file mode 100644 index 7ebd3c30e..000000000 --- a/src/libgit2/date.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_date_h__ -#define INCLUDE_date_h__ - -#include "util.h" -#include "str.h" - -/* - * Parse a string into a value as a git_time_t. - * - * Sample valid input: - * - "yesterday" - * - "July 17, 2003" - * - "2003-7-17 08:23" - */ -extern int git_date_parse(git_time_t *out, const char *date); - -/* - * Format a git_time as a RFC2822 string - * - * @param out buffer to store formatted date - * @param time the time to be formatted - * @param offset the timezone offset - * @return 0 if successful; -1 on error - */ -extern int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset); - -#endif diff --git a/src/libgit2/diff_xdiff.c b/src/libgit2/diff_xdiff.c index 3f6eccac1..5f56c5209 100644 --- a/src/libgit2/diff_xdiff.c +++ b/src/libgit2/diff_xdiff.c @@ -11,6 +11,7 @@ #include "diff.h" #include "diff_driver.h" #include "patch_generate.h" +#include "utf8.h" static int git_xdiff_scan_int(const char **str, int *value) { diff --git a/src/libgit2/features.h.in b/src/libgit2/features.h.in deleted file mode 100644 index f920135da..000000000 --- a/src/libgit2/features.h.in +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef INCLUDE_features_h__ -#define INCLUDE_features_h__ - -#cmakedefine GIT_DEBUG_POOL 1 -#cmakedefine GIT_DEBUG_STRICT_ALLOC 1 -#cmakedefine GIT_DEBUG_STRICT_OPEN 1 - -#cmakedefine GIT_THREADS 1 -#cmakedefine GIT_WIN32_LEAKCHECK 1 - -#cmakedefine GIT_ARCH_64 1 -#cmakedefine GIT_ARCH_32 1 - -#cmakedefine GIT_USE_ICONV 1 -#cmakedefine GIT_USE_NSEC 1 -#cmakedefine GIT_USE_STAT_MTIM 1 -#cmakedefine GIT_USE_STAT_MTIMESPEC 1 -#cmakedefine GIT_USE_STAT_MTIME_NSEC 1 -#cmakedefine GIT_USE_FUTIMENS 1 - -#cmakedefine GIT_REGEX_REGCOMP_L -#cmakedefine GIT_REGEX_REGCOMP -#cmakedefine GIT_REGEX_PCRE -#cmakedefine GIT_REGEX_PCRE2 -#cmakedefine GIT_REGEX_BUILTIN 1 - -#cmakedefine GIT_QSORT_R_BSD -#cmakedefine GIT_QSORT_R_GNU -#cmakedefine GIT_QSORT_S - -#cmakedefine GIT_SSH 1 -#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1 - -#cmakedefine GIT_NTLM 1 -#cmakedefine GIT_GSSAPI 1 -#cmakedefine GIT_GSSFRAMEWORK 1 - -#cmakedefine GIT_WINHTTP 1 -#cmakedefine GIT_HTTPS 1 -#cmakedefine GIT_OPENSSL 1 -#cmakedefine GIT_OPENSSL_DYNAMIC 1 -#cmakedefine GIT_SECURE_TRANSPORT 1 -#cmakedefine GIT_MBEDTLS 1 - -#cmakedefine GIT_SHA1_COLLISIONDETECT 1 -#cmakedefine GIT_SHA1_WIN32 1 -#cmakedefine GIT_SHA1_COMMON_CRYPTO 1 -#cmakedefine GIT_SHA1_OPENSSL 1 -#cmakedefine GIT_SHA1_MBEDTLS 1 - -#cmakedefine GIT_RAND_GETENTROPY 1 - -#endif diff --git a/src/libgit2/filebuf.c b/src/libgit2/filebuf.c deleted file mode 100644 index eafcba3bd..000000000 --- a/src/libgit2/filebuf.c +++ /dev/null @@ -1,595 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "filebuf.h" - -#include "futils.h" - -static const size_t WRITE_BUFFER_SIZE = (4096 * 2); - -enum buferr_t { - BUFERR_OK = 0, - BUFERR_WRITE, - BUFERR_ZLIB, - BUFERR_MEM -}; - -#define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; } - -static int verify_last_error(git_filebuf *file) -{ - switch (file->last_error) { - case BUFERR_WRITE: - git_error_set(GIT_ERROR_OS, "failed to write out file"); - return -1; - - case BUFERR_MEM: - git_error_set_oom(); - return -1; - - case BUFERR_ZLIB: - git_error_set(GIT_ERROR_ZLIB, - "Buffer error when writing out ZLib data"); - return -1; - - default: - return 0; - } -} - -static int lock_file(git_filebuf *file, int flags, mode_t mode) -{ - if (git_fs_path_exists(file->path_lock) == true) { - git_error_clear(); /* actual OS error code just confuses */ - git_error_set(GIT_ERROR_OS, - "failed to lock file '%s' for writing", file->path_lock); - return GIT_ELOCKED; - } - - /* create path to the file buffer is required */ - if (flags & GIT_FILEBUF_CREATE_LEADING_DIRS) { - /* XXX: Should dirmode here be configurable? Or is 0777 always fine? */ - file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, mode); - } else { - file->fd = git_futils_creat_locked(file->path_lock, mode); - } - - if (file->fd < 0) - return file->fd; - - file->fd_is_open = true; - - if ((flags & GIT_FILEBUF_APPEND) && git_fs_path_exists(file->path_original) == true) { - git_file source; - char buffer[FILEIO_BUFSIZE]; - ssize_t read_bytes; - int error = 0; - - source = p_open(file->path_original, O_RDONLY); - if (source < 0) { - git_error_set(GIT_ERROR_OS, - "failed to open file '%s' for reading", - file->path_original); - return -1; - } - - while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) { - if ((error = p_write(file->fd, buffer, read_bytes)) < 0) - break; - if (file->compute_digest) - git_hash_update(&file->digest, buffer, read_bytes); - } - - p_close(source); - - if (read_bytes < 0) { - git_error_set(GIT_ERROR_OS, "failed to read file '%s'", file->path_original); - return -1; - } else if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to write file '%s'", file->path_lock); - return -1; - } - } - - return 0; -} - -void git_filebuf_cleanup(git_filebuf *file) -{ - if (file->fd_is_open && file->fd >= 0) - p_close(file->fd); - - if (file->created_lock && !file->did_rename && file->path_lock && git_fs_path_exists(file->path_lock)) - p_unlink(file->path_lock); - - if (file->compute_digest) { - git_hash_ctx_cleanup(&file->digest); - file->compute_digest = 0; - } - - if (file->buffer) - git__free(file->buffer); - - /* use the presence of z_buf to decide if we need to deflateEnd */ - if (file->z_buf) { - git__free(file->z_buf); - deflateEnd(&file->zs); - } - - if (file->path_original) - git__free(file->path_original); - if (file->path_lock) - git__free(file->path_lock); - - memset(file, 0x0, sizeof(git_filebuf)); - file->fd = -1; -} - -GIT_INLINE(int) flush_buffer(git_filebuf *file) -{ - int result = file->write(file, file->buffer, file->buf_pos); - file->buf_pos = 0; - return result; -} - -int git_filebuf_flush(git_filebuf *file) -{ - return flush_buffer(file); -} - -static int write_normal(git_filebuf *file, void *source, size_t len) -{ - if (len > 0) { - if (p_write(file->fd, (void *)source, len) < 0) { - file->last_error = BUFERR_WRITE; - return -1; - } - - if (file->compute_digest) - git_hash_update(&file->digest, source, len); - } - - return 0; -} - -static int write_deflate(git_filebuf *file, void *source, size_t len) -{ - z_stream *zs = &file->zs; - - if (len > 0 || file->flush_mode == Z_FINISH) { - zs->next_in = source; - zs->avail_in = (uInt)len; - - do { - size_t have; - - zs->next_out = file->z_buf; - zs->avail_out = (uInt)file->buf_size; - - if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) { - file->last_error = BUFERR_ZLIB; - return -1; - } - - have = file->buf_size - (size_t)zs->avail_out; - - if (p_write(file->fd, file->z_buf, have) < 0) { - file->last_error = BUFERR_WRITE; - return -1; - } - - } while (zs->avail_out == 0); - - GIT_ASSERT(zs->avail_in == 0); - - if (file->compute_digest) - git_hash_update(&file->digest, source, len); - } - - return 0; -} - -#define MAX_SYMLINK_DEPTH 5 - -static int resolve_symlink(git_str *out, const char *path) -{ - int i, error, root; - ssize_t ret; - struct stat st; - git_str curpath = GIT_STR_INIT, target = GIT_STR_INIT; - - if ((error = git_str_grow(&target, GIT_PATH_MAX + 1)) < 0 || - (error = git_str_puts(&curpath, path)) < 0) - return error; - - for (i = 0; i < MAX_SYMLINK_DEPTH; i++) { - error = p_lstat(curpath.ptr, &st); - if (error < 0 && errno == ENOENT) { - error = git_str_puts(out, curpath.ptr); - goto cleanup; - } - - if (error < 0) { - git_error_set(GIT_ERROR_OS, "failed to stat '%s'", curpath.ptr); - error = -1; - goto cleanup; - } - - if (!S_ISLNK(st.st_mode)) { - error = git_str_puts(out, curpath.ptr); - goto cleanup; - } - - ret = p_readlink(curpath.ptr, target.ptr, GIT_PATH_MAX); - if (ret < 0) { - git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", curpath.ptr); - error = -1; - goto cleanup; - } - - if (ret == GIT_PATH_MAX) { - git_error_set(GIT_ERROR_INVALID, "symlink target too long"); - error = -1; - goto cleanup; - } - - /* readlink(2) won't NUL-terminate for us */ - target.ptr[ret] = '\0'; - target.size = ret; - - root = git_fs_path_root(target.ptr); - if (root >= 0) { - if ((error = git_str_sets(&curpath, target.ptr)) < 0) - goto cleanup; - } else { - git_str dir = GIT_STR_INIT; - - if ((error = git_fs_path_dirname_r(&dir, curpath.ptr)) < 0) - goto cleanup; - - git_str_swap(&curpath, &dir); - git_str_dispose(&dir); - - if ((error = git_fs_path_apply_relative(&curpath, target.ptr)) < 0) - goto cleanup; - } - } - - git_error_set(GIT_ERROR_INVALID, "maximum symlink depth reached"); - error = -1; - -cleanup: - git_str_dispose(&curpath); - git_str_dispose(&target); - return error; -} - -int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode) -{ - return git_filebuf_open_withsize(file, path, flags, mode, WRITE_BUFFER_SIZE); -} - -int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size) -{ - int compression, error = -1; - size_t path_len, alloc_len; - - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(path); - GIT_ASSERT(file->buffer == NULL); - - memset(file, 0x0, sizeof(git_filebuf)); - - if (flags & GIT_FILEBUF_DO_NOT_BUFFER) - file->do_not_buffer = true; - - if (flags & GIT_FILEBUF_FSYNC) - file->do_fsync = true; - - file->buf_size = size; - file->buf_pos = 0; - file->fd = -1; - file->last_error = BUFERR_OK; - - /* Allocate the main cache buffer */ - if (!file->do_not_buffer) { - file->buffer = git__malloc(file->buf_size); - GIT_ERROR_CHECK_ALLOC(file->buffer); - } - - /* If we are hashing on-write, allocate a new hash context */ - if (flags & GIT_FILEBUF_HASH_CONTENTS) { - file->compute_digest = 1; - - if (git_hash_ctx_init(&file->digest, GIT_HASH_ALGORITHM_SHA1) < 0) - goto cleanup; - } - - compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT; - - /* If we are deflating on-write, */ - if (compression != 0) { - /* Initialize the ZLib stream */ - if (deflateInit(&file->zs, compression) != Z_OK) { - git_error_set(GIT_ERROR_ZLIB, "failed to initialize zlib"); - goto cleanup; - } - - /* Allocate the Zlib cache buffer */ - file->z_buf = git__malloc(file->buf_size); - GIT_ERROR_CHECK_ALLOC(file->z_buf); - - /* Never flush */ - file->flush_mode = Z_NO_FLUSH; - file->write = &write_deflate; - } else { - file->write = &write_normal; - } - - /* If we are writing to a temp file */ - if (flags & GIT_FILEBUF_TEMPORARY) { - git_str tmp_path = GIT_STR_INIT; - - /* Open the file as temporary for locking */ - file->fd = git_futils_mktmp(&tmp_path, path, mode); - - if (file->fd < 0) { - git_str_dispose(&tmp_path); - goto cleanup; - } - file->fd_is_open = true; - file->created_lock = true; - - /* No original path */ - file->path_original = NULL; - file->path_lock = git_str_detach(&tmp_path); - GIT_ERROR_CHECK_ALLOC(file->path_lock); - } else { - git_str resolved_path = GIT_STR_INIT; - - if ((error = resolve_symlink(&resolved_path, path)) < 0) - goto cleanup; - - /* Save the original path of the file */ - path_len = resolved_path.size; - file->path_original = git_str_detach(&resolved_path); - - /* create the locking path by appending ".lock" to the original */ - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, path_len, GIT_FILELOCK_EXTLENGTH); - file->path_lock = git__malloc(alloc_len); - GIT_ERROR_CHECK_ALLOC(file->path_lock); - - memcpy(file->path_lock, file->path_original, path_len); - memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH); - - if (git_fs_path_isdir(file->path_original)) { - git_error_set(GIT_ERROR_FILESYSTEM, "path '%s' is a directory", file->path_original); - error = GIT_EDIRECTORY; - goto cleanup; - } - - /* open the file for locking */ - if ((error = lock_file(file, flags, mode)) < 0) - goto cleanup; - - file->created_lock = true; - } - - return 0; - -cleanup: - git_filebuf_cleanup(file); - return error; -} - -int git_filebuf_hash(unsigned char *out, git_filebuf *file) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(file); - GIT_ASSERT_ARG(file->compute_digest); - - flush_buffer(file); - - if (verify_last_error(file) < 0) - return -1; - - git_hash_final(out, &file->digest); - git_hash_ctx_cleanup(&file->digest); - file->compute_digest = 0; - - return 0; -} - -int git_filebuf_commit_at(git_filebuf *file, const char *path) -{ - git__free(file->path_original); - file->path_original = git__strdup(path); - GIT_ERROR_CHECK_ALLOC(file->path_original); - - return git_filebuf_commit(file); -} - -int git_filebuf_commit(git_filebuf *file) -{ - /* temporary files cannot be committed */ - GIT_ASSERT_ARG(file); - GIT_ASSERT(file->path_original); - - file->flush_mode = Z_FINISH; - flush_buffer(file); - - if (verify_last_error(file) < 0) - goto on_error; - - file->fd_is_open = false; - - if (file->do_fsync && p_fsync(file->fd) < 0) { - git_error_set(GIT_ERROR_OS, "failed to fsync '%s'", file->path_lock); - goto on_error; - } - - if (p_close(file->fd) < 0) { - git_error_set(GIT_ERROR_OS, "failed to close file at '%s'", file->path_lock); - goto on_error; - } - - file->fd = -1; - - if (p_rename(file->path_lock, file->path_original) < 0) { - git_error_set(GIT_ERROR_OS, "failed to rename lockfile to '%s'", file->path_original); - goto on_error; - } - - if (file->do_fsync && git_futils_fsync_parent(file->path_original) < 0) - goto on_error; - - file->did_rename = true; - - git_filebuf_cleanup(file); - return 0; - -on_error: - git_filebuf_cleanup(file); - return -1; -} - -GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len) -{ - memcpy(file->buffer + file->buf_pos, buf, len); - file->buf_pos += len; -} - -int git_filebuf_write(git_filebuf *file, const void *buff, size_t len) -{ - const unsigned char *buf = buff; - - ENSURE_BUF_OK(file); - - if (file->do_not_buffer) - return file->write(file, (void *)buff, len); - - for (;;) { - size_t space_left = file->buf_size - file->buf_pos; - - /* cache if it's small */ - if (space_left > len) { - add_to_cache(file, buf, len); - return 0; - } - - add_to_cache(file, buf, space_left); - if (flush_buffer(file) < 0) - return -1; - - len -= space_left; - buf += space_left; - } -} - -int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len) -{ - size_t space_left = file->buf_size - file->buf_pos; - - *buffer = NULL; - - ENSURE_BUF_OK(file); - - if (len > file->buf_size) { - file->last_error = BUFERR_MEM; - return -1; - } - - if (space_left <= len) { - if (flush_buffer(file) < 0) - return -1; - } - - *buffer = (file->buffer + file->buf_pos); - file->buf_pos += len; - - return 0; -} - -int git_filebuf_printf(git_filebuf *file, const char *format, ...) -{ - va_list arglist; - size_t space_left, len, alloclen; - int written, res; - char *tmp_buffer; - - ENSURE_BUF_OK(file); - - space_left = file->buf_size - file->buf_pos; - - do { - va_start(arglist, format); - written = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); - va_end(arglist); - - if (written < 0) { - file->last_error = BUFERR_MEM; - return -1; - } - - len = written; - if (len + 1 <= space_left) { - file->buf_pos += len; - return 0; - } - - if (flush_buffer(file) < 0) - return -1; - - space_left = file->buf_size - file->buf_pos; - - } while (len + 1 <= space_left); - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, len, 1) || - !(tmp_buffer = git__malloc(alloclen))) { - file->last_error = BUFERR_MEM; - return -1; - } - - va_start(arglist, format); - written = p_vsnprintf(tmp_buffer, len + 1, format, arglist); - va_end(arglist); - - if (written < 0) { - git__free(tmp_buffer); - file->last_error = BUFERR_MEM; - return -1; - } - - res = git_filebuf_write(file, tmp_buffer, len); - git__free(tmp_buffer); - - return res; -} - -int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file) -{ - int res; - struct stat st; - - if (file->fd_is_open) - res = p_fstat(file->fd, &st); - else - res = p_stat(file->path_original, &st); - - if (res < 0) { - git_error_set(GIT_ERROR_OS, "could not get stat info for '%s'", - file->path_original); - return res; - } - - if (mtime) - *mtime = st.st_mtime; - if (size) - *size = (size_t)st.st_size; - - return 0; -} diff --git a/src/libgit2/filebuf.h b/src/libgit2/filebuf.h deleted file mode 100644 index adbb19936..000000000 --- a/src/libgit2/filebuf.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_filebuf_h__ -#define INCLUDE_filebuf_h__ - -#include "common.h" - -#include "futils.h" -#include "hash.h" -#include - -#ifdef GIT_THREADS -# define GIT_FILEBUF_THREADS -#endif - -#define GIT_FILEBUF_HASH_CONTENTS (1 << 0) -#define GIT_FILEBUF_APPEND (1 << 2) -#define GIT_FILEBUF_CREATE_LEADING_DIRS (1 << 3) -#define GIT_FILEBUF_TEMPORARY (1 << 4) -#define GIT_FILEBUF_DO_NOT_BUFFER (1 << 5) -#define GIT_FILEBUF_FSYNC (1 << 6) -#define GIT_FILEBUF_DEFLATE_SHIFT (7) - -#define GIT_FILELOCK_EXTENSION ".lock\0" -#define GIT_FILELOCK_EXTLENGTH 6 - -typedef struct git_filebuf git_filebuf; -struct git_filebuf { - char *path_original; - char *path_lock; - - int (*write)(git_filebuf *file, void *source, size_t len); - - bool compute_digest; - git_hash_ctx digest; - - unsigned char *buffer; - unsigned char *z_buf; - - z_stream zs; - int flush_mode; - - size_t buf_size, buf_pos; - git_file fd; - bool fd_is_open; - bool created_lock; - bool did_rename; - bool do_not_buffer; - bool do_fsync; - int last_error; -}; - -#define GIT_FILEBUF_INIT {0} - -/* - * The git_filebuf object lifecycle is: - * - Allocate git_filebuf, preferably using GIT_FILEBUF_INIT. - * - * - Call git_filebuf_open() to initialize the filebuf for use. - * - * - Make as many calls to git_filebuf_write(), git_filebuf_printf(), - * git_filebuf_reserve() as you like. The error codes for these - * functions don't need to be checked. They are stored internally - * by the file buffer. - * - * - While you are writing, you may call git_filebuf_hash() to get - * the hash of all you have written so far. This function will - * fail if any of the previous writes to the buffer failed. - * - * - To close the git_filebuf, you may call git_filebuf_commit() or - * git_filebuf_commit_at() to save the file, or - * git_filebuf_cleanup() to abandon the file. All of these will - * free the git_filebuf object. Likewise, all of these will fail - * if any of the previous writes to the buffer failed, and set - * an error code accordingly. - */ -int git_filebuf_write(git_filebuf *lock, const void *buff, size_t len); -int git_filebuf_reserve(git_filebuf *file, void **buff, size_t len); -int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); - -int git_filebuf_open(git_filebuf *lock, const char *path, int flags, mode_t mode); -int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size); -int git_filebuf_commit(git_filebuf *lock); -int git_filebuf_commit_at(git_filebuf *lock, const char *path); -void git_filebuf_cleanup(git_filebuf *lock); -int git_filebuf_hash(unsigned char *out, git_filebuf *file); -int git_filebuf_flush(git_filebuf *file); -int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file); - -#endif diff --git a/src/libgit2/filter.c b/src/libgit2/filter.c index 2712e8c60..20b215729 100644 --- a/src/libgit2/filter.c +++ b/src/libgit2/filter.c @@ -1085,7 +1085,7 @@ int git_filter_list_stream_file( const char *path, git_writestream *target) { - char buf[FILTERIO_BUFSIZE]; + char buf[GIT_BUFSIZE_FILTERIO]; git_str abspath = GIT_STR_INIT; const char *base = repo ? git_repository_workdir(repo) : NULL; git_vector filter_streams = GIT_VECTOR_INIT; diff --git a/src/libgit2/fs_path.c b/src/libgit2/fs_path.c deleted file mode 100644 index 7a657778a..000000000 --- a/src/libgit2/fs_path.c +++ /dev/null @@ -1,1912 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "fs_path.h" - -#include "posix.h" -#include "repository.h" -#ifdef GIT_WIN32 -#include "win32/posix.h" -#include "win32/w32_buffer.h" -#include "win32/w32_util.h" -#include "win32/version.h" -#include -#else -#include -#endif -#include -#include - -static int dos_drive_prefix_length(const char *path) -{ - int i; - - /* - * Does it start with an ASCII letter (i.e. highest bit not set), - * followed by a colon? - */ - if (!(0x80 & (unsigned char)*path)) - return *path && path[1] == ':' ? 2 : 0; - - /* - * While drive letters must be letters of the English alphabet, it is - * possible to assign virtually _any_ Unicode character via `subst` as - * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff - * like this: - * - * subst ֍: %USERPROFILE%\Desktop - */ - for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++) - ; /* skip first UTF-8 character */ - return path[i] == ':' ? i + 1 : 0; -} - -#ifdef GIT_WIN32 -static bool looks_like_network_computer_name(const char *path, int pos) -{ - if (pos < 3) - return false; - - if (path[0] != '/' || path[1] != '/') - return false; - - while (pos-- > 2) { - if (path[pos] == '/') - return false; - } - - return true; -} -#endif - -/* - * Based on the Android implementation, BSD licensed. - * http://android.git.kernel.org/ - * - * Copyright (C) 2008 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -int git_fs_path_basename_r(git_str *buffer, const char *path) -{ - const char *endp, *startp; - int len, result; - - /* Empty or NULL string gets treated as "." */ - if (path == NULL || *path == '\0') { - startp = "."; - len = 1; - goto Exit; - } - - /* Strip trailing slashes */ - endp = path + strlen(path) - 1; - while (endp > path && *endp == '/') - endp--; - - /* All slashes becomes "/" */ - if (endp == path && *endp == '/') { - startp = "/"; - len = 1; - goto Exit; - } - - /* Find the start of the base */ - startp = endp; - while (startp > path && *(startp - 1) != '/') - startp--; - - /* Cast is safe because max path < max int */ - len = (int)(endp - startp + 1); - -Exit: - result = len; - - if (buffer != NULL && git_str_set(buffer, startp, len) < 0) - return -1; - - return result; -} - -/* - * Determine if the path is a Windows prefix and, if so, returns - * its actual length. If it is not a prefix, returns -1. - */ -static int win32_prefix_length(const char *path, int len) -{ -#ifndef GIT_WIN32 - GIT_UNUSED(path); - GIT_UNUSED(len); -#else - /* - * Mimic unix behavior where '/.git' returns '/': 'C:/.git' - * will return 'C:/' here - */ - if (dos_drive_prefix_length(path) == len) - return len; - - /* - * Similarly checks if we're dealing with a network computer name - * '//computername/.git' will return '//computername/' - */ - if (looks_like_network_computer_name(path, len)) - return len; -#endif - - return -1; -} - -/* - * Based on the Android implementation, BSD licensed. - * Check http://android.git.kernel.org/ - */ -int git_fs_path_dirname_r(git_str *buffer, const char *path) -{ - const char *endp; - int is_prefix = 0, len; - - /* Empty or NULL string gets treated as "." */ - if (path == NULL || *path == '\0') { - path = "."; - len = 1; - goto Exit; - } - - /* Strip trailing slashes */ - endp = path + strlen(path) - 1; - while (endp > path && *endp == '/') - endp--; - - if (endp - path + 1 > INT_MAX) { - git_error_set(GIT_ERROR_INVALID, "path too long"); - len = -1; - goto Exit; - } - - if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) { - is_prefix = 1; - goto Exit; - } - - /* Find the start of the dir */ - while (endp > path && *endp != '/') - endp--; - - /* Either the dir is "/" or there are no slashes */ - if (endp == path) { - path = (*endp == '/') ? "/" : "."; - len = 1; - goto Exit; - } - - do { - endp--; - } while (endp > path && *endp == '/'); - - if (endp - path + 1 > INT_MAX) { - git_error_set(GIT_ERROR_INVALID, "path too long"); - len = -1; - goto Exit; - } - - if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) { - is_prefix = 1; - goto Exit; - } - - /* Cast is safe because max path < max int */ - len = (int)(endp - path + 1); - -Exit: - if (buffer) { - if (git_str_set(buffer, path, len) < 0) - return -1; - if (is_prefix && git_str_putc(buffer, '/') < 0) - return -1; - } - - return len; -} - - -char *git_fs_path_dirname(const char *path) -{ - git_str buf = GIT_STR_INIT; - char *dirname; - - git_fs_path_dirname_r(&buf, path); - dirname = git_str_detach(&buf); - git_str_dispose(&buf); /* avoid memleak if error occurs */ - - return dirname; -} - -char *git_fs_path_basename(const char *path) -{ - git_str buf = GIT_STR_INIT; - char *basename; - - git_fs_path_basename_r(&buf, path); - basename = git_str_detach(&buf); - git_str_dispose(&buf); /* avoid memleak if error occurs */ - - return basename; -} - -size_t git_fs_path_basename_offset(git_str *buffer) -{ - ssize_t slash; - - if (!buffer || buffer->size <= 0) - return 0; - - slash = git_str_rfind_next(buffer, '/'); - - if (slash >= 0 && buffer->ptr[slash] == '/') - return (size_t)(slash + 1); - - return 0; -} - -int git_fs_path_root(const char *path) -{ - int offset = 0, prefix_len; - - /* Does the root of the path look like a windows drive ? */ - if ((prefix_len = dos_drive_prefix_length(path))) - offset += prefix_len; - -#ifdef GIT_WIN32 - /* Are we dealing with a windows network path? */ - else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') || - (path[0] == '\\' && path[1] == '\\' && path[2] != '\\')) - { - offset += 2; - - /* Skip the computer name segment */ - while (path[offset] && path[offset] != '/' && path[offset] != '\\') - offset++; - } - - if (path[offset] == '\\') - return offset; -#endif - - if (path[offset] == '/') - return offset; - - return -1; /* Not a real error - signals that path is not rooted */ -} - -static void path_trim_slashes(git_str *path) -{ - int ceiling = git_fs_path_root(path->ptr) + 1; - - if (ceiling < 0) - return; - - while (path->size > (size_t)ceiling) { - if (path->ptr[path->size-1] != '/') - break; - - path->ptr[path->size-1] = '\0'; - path->size--; - } -} - -int git_fs_path_join_unrooted( - git_str *path_out, const char *path, const char *base, ssize_t *root_at) -{ - ssize_t root; - - GIT_ASSERT_ARG(path_out); - GIT_ASSERT_ARG(path); - - root = (ssize_t)git_fs_path_root(path); - - if (base != NULL && root < 0) { - if (git_str_joinpath(path_out, base, path) < 0) - return -1; - - root = (ssize_t)strlen(base); - } else { - if (git_str_sets(path_out, path) < 0) - return -1; - - if (root < 0) - root = 0; - else if (base) - git_fs_path_equal_or_prefixed(base, path, &root); - } - - if (root_at) - *root_at = root; - - return 0; -} - -void git_fs_path_squash_slashes(git_str *path) -{ - char *p, *q; - - if (path->size == 0) - return; - - for (p = path->ptr, q = path->ptr; *q; p++, q++) { - *p = *q; - - while (*q == '/' && *(q+1) == '/') { - path->size--; - q++; - } - } - - *p = '\0'; -} - -int git_fs_path_prettify(git_str *path_out, const char *path, const char *base) -{ - char buf[GIT_PATH_MAX]; - - GIT_ASSERT_ARG(path_out); - GIT_ASSERT_ARG(path); - - /* construct path if needed */ - if (base != NULL && git_fs_path_root(path) < 0) { - if (git_str_joinpath(path_out, base, path) < 0) - return -1; - path = path_out->ptr; - } - - if (p_realpath(path, buf) == NULL) { - /* git_error_set resets the errno when dealing with a GIT_ERROR_OS kind of error */ - int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1; - git_error_set(GIT_ERROR_OS, "failed to resolve path '%s'", path); - - git_str_clear(path_out); - - return error; - } - - return git_str_sets(path_out, buf); -} - -int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base) -{ - int error = git_fs_path_prettify(path_out, path, base); - return (error < 0) ? error : git_fs_path_to_dir(path_out); -} - -int git_fs_path_to_dir(git_str *path) -{ - if (path->asize > 0 && - git_str_len(path) > 0 && - path->ptr[git_str_len(path) - 1] != '/') - git_str_putc(path, '/'); - - return git_str_oom(path) ? -1 : 0; -} - -void git_fs_path_string_to_dir(char *path, size_t size) -{ - size_t end = strlen(path); - - if (end && path[end - 1] != '/' && end < size) { - path[end] = '/'; - path[end + 1] = '\0'; - } -} - -int git__percent_decode(git_str *decoded_out, const char *input) -{ - int len, hi, lo, i; - - GIT_ASSERT_ARG(decoded_out); - GIT_ASSERT_ARG(input); - - len = (int)strlen(input); - git_str_clear(decoded_out); - - for(i = 0; i < len; i++) - { - char c = input[i]; - - if (c != '%') - goto append; - - if (i >= len - 2) - goto append; - - hi = git__fromhex(input[i + 1]); - lo = git__fromhex(input[i + 2]); - - if (hi < 0 || lo < 0) - goto append; - - c = (char)(hi << 4 | lo); - i += 2; - -append: - if (git_str_putc(decoded_out, c) < 0) - return -1; - } - - return 0; -} - -static int error_invalid_local_file_uri(const char *uri) -{ - git_error_set(GIT_ERROR_CONFIG, "'%s' is not a valid local file URI", uri); - return -1; -} - -static int local_file_url_prefixlen(const char *file_url) -{ - int len = -1; - - if (git__prefixcmp(file_url, "file://") == 0) { - if (file_url[7] == '/') - len = 8; - else if (git__prefixcmp(file_url + 7, "localhost/") == 0) - len = 17; - } - - return len; -} - -bool git_fs_path_is_local_file_url(const char *file_url) -{ - return (local_file_url_prefixlen(file_url) > 0); -} - -int git_fs_path_fromurl(git_str *local_path_out, const char *file_url) -{ - int offset; - - GIT_ASSERT_ARG(local_path_out); - GIT_ASSERT_ARG(file_url); - - if ((offset = local_file_url_prefixlen(file_url)) < 0 || - file_url[offset] == '\0' || file_url[offset] == '/') - return error_invalid_local_file_uri(file_url); - -#ifndef GIT_WIN32 - offset--; /* A *nix absolute path starts with a forward slash */ -#endif - - git_str_clear(local_path_out); - return git__percent_decode(local_path_out, file_url + offset); -} - -int git_fs_path_walk_up( - git_str *path, - const char *ceiling, - int (*cb)(void *data, const char *), - void *data) -{ - int error = 0; - git_str iter; - ssize_t stop = 0, scan; - char oldc = '\0'; - - GIT_ASSERT_ARG(path); - GIT_ASSERT_ARG(cb); - - if (ceiling != NULL) { - if (git__prefixcmp(path->ptr, ceiling) == 0) - stop = (ssize_t)strlen(ceiling); - else - stop = git_str_len(path); - } - scan = git_str_len(path); - - /* empty path: yield only once */ - if (!scan) { - error = cb(data, ""); - if (error) - git_error_set_after_callback(error); - return error; - } - - iter.ptr = path->ptr; - iter.size = git_str_len(path); - iter.asize = path->asize; - - while (scan >= stop) { - error = cb(data, iter.ptr); - iter.ptr[scan] = oldc; - - if (error) { - git_error_set_after_callback(error); - break; - } - - scan = git_str_rfind_next(&iter, '/'); - if (scan >= 0) { - scan++; - oldc = iter.ptr[scan]; - iter.size = scan; - iter.ptr[scan] = '\0'; - } - } - - if (scan >= 0) - iter.ptr[scan] = oldc; - - /* relative path: yield for the last component */ - if (!error && stop == 0 && iter.ptr[0] != '/') { - error = cb(data, ""); - if (error) - git_error_set_after_callback(error); - } - - return error; -} - -bool git_fs_path_exists(const char *path) -{ - GIT_ASSERT_ARG_WITH_RETVAL(path, false); - return p_access(path, F_OK) == 0; -} - -bool git_fs_path_isdir(const char *path) -{ - struct stat st; - if (p_stat(path, &st) < 0) - return false; - - return S_ISDIR(st.st_mode) != 0; -} - -bool git_fs_path_isfile(const char *path) -{ - struct stat st; - - GIT_ASSERT_ARG_WITH_RETVAL(path, false); - if (p_stat(path, &st) < 0) - return false; - - return S_ISREG(st.st_mode) != 0; -} - -bool git_fs_path_islink(const char *path) -{ - struct stat st; - - GIT_ASSERT_ARG_WITH_RETVAL(path, false); - if (p_lstat(path, &st) < 0) - return false; - - return S_ISLNK(st.st_mode) != 0; -} - -#ifdef GIT_WIN32 - -bool git_fs_path_is_empty_dir(const char *path) -{ - git_win32_path filter_w; - bool empty = false; - - if (git_win32__findfirstfile_filter(filter_w, path)) { - WIN32_FIND_DATAW findData; - HANDLE hFind = FindFirstFileW(filter_w, &findData); - - /* FindFirstFile will fail if there are no children to the given - * path, which can happen if the given path is a file (and obviously - * has no children) or if the given path is an empty mount point. - * (Most directories have at least directory entries '.' and '..', - * but ridiculously another volume mounted in another drive letter's - * path space do not, and thus have nothing to enumerate.) If - * FindFirstFile fails, check if this is a directory-like thing - * (a mount point). - */ - if (hFind == INVALID_HANDLE_VALUE) - return git_fs_path_isdir(path); - - /* If the find handle was created successfully, then it's a directory */ - empty = true; - - do { - /* Allow the enumeration to return . and .. and still be considered - * empty. In the special case of drive roots (i.e. C:\) where . and - * .. do not occur, we can still consider the path to be an empty - * directory if there's nothing there. */ - if (!git_fs_path_is_dot_or_dotdotW(findData.cFileName)) { - empty = false; - break; - } - } while (FindNextFileW(hFind, &findData)); - - FindClose(hFind); - } - - return empty; -} - -#else - -static int path_found_entry(void *payload, git_str *path) -{ - GIT_UNUSED(payload); - return !git_fs_path_is_dot_or_dotdot(path->ptr); -} - -bool git_fs_path_is_empty_dir(const char *path) -{ - int error; - git_str dir = GIT_STR_INIT; - - if (!git_fs_path_isdir(path)) - return false; - - if ((error = git_str_sets(&dir, path)) != 0) - git_error_clear(); - else - error = git_fs_path_direach(&dir, 0, path_found_entry, NULL); - - git_str_dispose(&dir); - - return !error; -} - -#endif - -int git_fs_path_set_error(int errno_value, const char *path, const char *action) -{ - switch (errno_value) { - case ENOENT: - case ENOTDIR: - git_error_set(GIT_ERROR_OS, "could not find '%s' to %s", path, action); - return GIT_ENOTFOUND; - - case EINVAL: - case ENAMETOOLONG: - git_error_set(GIT_ERROR_OS, "invalid path for filesystem '%s'", path); - return GIT_EINVALIDSPEC; - - case EEXIST: - git_error_set(GIT_ERROR_OS, "failed %s - '%s' already exists", action, path); - return GIT_EEXISTS; - - case EACCES: - git_error_set(GIT_ERROR_OS, "failed %s - '%s' is locked", action, path); - return GIT_ELOCKED; - - default: - git_error_set(GIT_ERROR_OS, "could not %s '%s'", action, path); - return -1; - } -} - -int git_fs_path_lstat(const char *path, struct stat *st) -{ - if (p_lstat(path, st) == 0) - return 0; - - return git_fs_path_set_error(errno, path, "stat"); -} - -static bool _check_dir_contents( - git_str *dir, - const char *sub, - bool (*predicate)(const char *)) -{ - bool result; - size_t dir_size = git_str_len(dir); - size_t sub_size = strlen(sub); - size_t alloc_size; - - /* leave base valid even if we could not make space for subdir */ - if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, dir_size, sub_size) || - GIT_ADD_SIZET_OVERFLOW(&alloc_size, alloc_size, 2) || - git_str_try_grow(dir, alloc_size, false) < 0) - return false; - - /* save excursion */ - if (git_str_joinpath(dir, dir->ptr, sub) < 0) - return false; - - result = predicate(dir->ptr); - - /* restore path */ - git_str_truncate(dir, dir_size); - return result; -} - -bool git_fs_path_contains(git_str *dir, const char *item) -{ - return _check_dir_contents(dir, item, &git_fs_path_exists); -} - -bool git_fs_path_contains_dir(git_str *base, const char *subdir) -{ - return _check_dir_contents(base, subdir, &git_fs_path_isdir); -} - -bool git_fs_path_contains_file(git_str *base, const char *file) -{ - return _check_dir_contents(base, file, &git_fs_path_isfile); -} - -int git_fs_path_find_dir(git_str *dir) -{ - int error = 0; - char buf[GIT_PATH_MAX]; - - if (p_realpath(dir->ptr, buf) != NULL) - error = git_str_sets(dir, buf); - - /* call dirname if this is not a directory */ - if (!error) /* && git_fs_path_isdir(dir->ptr) == false) */ - error = (git_fs_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0; - - if (!error) - error = git_fs_path_to_dir(dir); - - return error; -} - -int git_fs_path_resolve_relative(git_str *path, size_t ceiling) -{ - char *base, *to, *from, *next; - size_t len; - - GIT_ERROR_CHECK_ALLOC_STR(path); - - if (ceiling > path->size) - ceiling = path->size; - - /* recognize drive prefixes, etc. that should not be backed over */ - if (ceiling == 0) - ceiling = git_fs_path_root(path->ptr) + 1; - - /* recognize URL prefixes that should not be backed over */ - if (ceiling == 0) { - for (next = path->ptr; *next && git__isalpha(*next); ++next); - if (next[0] == ':' && next[1] == '/' && next[2] == '/') - ceiling = (next + 3) - path->ptr; - } - - base = to = from = path->ptr + ceiling; - - while (*from) { - for (next = from; *next && *next != '/'; ++next); - - len = next - from; - - if (len == 1 && from[0] == '.') - /* do nothing with singleton dot */; - - else if (len == 2 && from[0] == '.' && from[1] == '.') { - /* error out if trying to up one from a hard base */ - if (to == base && ceiling != 0) { - git_error_set(GIT_ERROR_INVALID, - "cannot strip root component off url"); - return -1; - } - - /* no more path segments to strip, - * use '../' as a new base path */ - if (to == base) { - if (*next == '/') - len++; - - if (to != from) - memmove(to, from, len); - - to += len; - /* this is now the base, can't back up from a - * relative prefix */ - base = to; - } else { - /* back up a path segment */ - while (to > base && to[-1] == '/') to--; - while (to > base && to[-1] != '/') to--; - } - } else { - if (*next == '/' && *from != '/') - len++; - - if (to != from) - memmove(to, from, len); - - to += len; - } - - from += len; - - while (*from == '/') from++; - } - - *to = '\0'; - - path->size = to - path->ptr; - - return 0; -} - -int git_fs_path_apply_relative(git_str *target, const char *relpath) -{ - return git_str_joinpath(target, git_str_cstr(target), relpath) || - git_fs_path_resolve_relative(target, 0); -} - -int git_fs_path_cmp( - const char *name1, size_t len1, int isdir1, - const char *name2, size_t len2, int isdir2, - int (*compare)(const char *, const char *, size_t)) -{ - unsigned char c1, c2; - size_t len = len1 < len2 ? len1 : len2; - int cmp; - - cmp = compare(name1, name2, len); - if (cmp) - return cmp; - - c1 = name1[len]; - c2 = name2[len]; - - if (c1 == '\0' && isdir1) - c1 = '/'; - - if (c2 == '\0' && isdir2) - c2 = '/'; - - return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; -} - -size_t git_fs_path_common_dirlen(const char *one, const char *two) -{ - const char *p, *q, *dirsep = NULL; - - for (p = one, q = two; *p && *q; p++, q++) { - if (*p == '/' && *q == '/') - dirsep = p; - else if (*p != *q) - break; - } - - return dirsep ? (dirsep - one) + 1 : 0; -} - -int git_fs_path_make_relative(git_str *path, const char *parent) -{ - const char *p, *q, *p_dirsep, *q_dirsep; - size_t plen = path->size, newlen, alloclen, depth = 1, i, offset; - - for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) { - if (*p == '/' && *q == '/') { - p_dirsep = p; - q_dirsep = q; - } - else if (*p != *q) - break; - } - - /* need at least 1 common path segment */ - if ((p_dirsep == path->ptr || q_dirsep == parent) && - (*p_dirsep != '/' || *q_dirsep != '/')) { - git_error_set(GIT_ERROR_INVALID, - "%s is not a parent of %s", parent, path->ptr); - return GIT_ENOTFOUND; - } - - if (*p == '/' && !*q) - p++; - else if (!*p && *q == '/') - q++; - else if (!*p && !*q) - return git_str_clear(path), 0; - else { - p = p_dirsep + 1; - q = q_dirsep + 1; - } - - plen -= (p - path->ptr); - - if (!*q) - return git_str_set(path, p, plen); - - for (; (q = strchr(q, '/')) && *(q + 1); q++) - depth++; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3); - GIT_ERROR_CHECK_ALLOC_ADD(&newlen, newlen, plen); - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, newlen, 1); - - /* save the offset as we might realllocate the pointer */ - offset = p - path->ptr; - if (git_str_try_grow(path, alloclen, 1) < 0) - return -1; - p = path->ptr + offset; - - memmove(path->ptr + (depth * 3), p, plen + 1); - - for (i = 0; i < depth; i++) - memcpy(path->ptr + (i * 3), "../", 3); - - path->size = newlen; - return 0; -} - -bool git_fs_path_has_non_ascii(const char *path, size_t pathlen) -{ - const uint8_t *scan = (const uint8_t *)path, *end; - - for (end = scan + pathlen; scan < end; ++scan) - if (*scan & 0x80) - return true; - - return false; -} - -#ifdef GIT_USE_ICONV - -int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic) -{ - git_str_init(&ic->buf, 0); - ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING); - return 0; -} - -void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic) -{ - if (ic) { - if (ic->map != (iconv_t)-1) - iconv_close(ic->map); - git_str_dispose(&ic->buf); - } -} - -int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen) -{ - char *nfd = (char*)*in, *nfc; - size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, alloclen, rv; - int retry = 1; - - if (!ic || ic->map == (iconv_t)-1 || - !git_fs_path_has_non_ascii(*in, *inlen)) - return 0; - - git_str_clear(&ic->buf); - - while (1) { - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, wantlen, 1); - if (git_str_grow(&ic->buf, alloclen) < 0) - return -1; - - nfc = ic->buf.ptr + ic->buf.size; - nfclen = ic->buf.asize - ic->buf.size; - - rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen); - - ic->buf.size = (nfc - ic->buf.ptr); - - if (rv != (size_t)-1) - break; - - /* if we cannot convert the data (probably because iconv thinks - * it is not valid UTF-8 source data), then use original data - */ - if (errno != E2BIG) - return 0; - - /* make space for 2x the remaining data to be converted - * (with per retry overhead to avoid infinite loops) - */ - wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4); - - if (retry++ > 4) - goto fail; - } - - ic->buf.ptr[ic->buf.size] = '\0'; - - *in = ic->buf.ptr; - *inlen = ic->buf.size; - - return 0; - -fail: - git_error_set(GIT_ERROR_OS, "unable to convert unicode path data"); - return -1; -} - -static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; -static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; - -/* Check if the platform is decomposing unicode data for us. We will - * emulate core Git and prefer to use precomposed unicode data internally - * on these platforms, composing the decomposed unicode on the fly. - * - * This mainly happens on the Mac where HDFS stores filenames as - * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will - * return decomposed unicode from readdir() even when the actual - * filesystem is storing precomposed unicode. - */ -bool git_fs_path_does_decompose_unicode(const char *root) -{ - git_str nfc_path = GIT_STR_INIT; - git_str nfd_path = GIT_STR_INIT; - int fd; - bool found_decomposed = false; - size_t orig_len; - const char *trailer; - - /* Create a file using a precomposed path and then try to find it - * using the decomposed name. If the lookup fails, then we will mark - * that we should precompose unicode for this repository. - */ - if (git_str_joinpath(&nfc_path, root, nfc_file) < 0) - goto done; - - /* record original path length before trailer */ - orig_len = nfc_path.size; - - if ((fd = git_futils_mktmp(&nfc_path, nfc_path.ptr, 0666)) < 0) - goto done; - p_close(fd); - - trailer = nfc_path.ptr + orig_len; - - /* try to look up as NFD path */ - if (git_str_joinpath(&nfd_path, root, nfd_file) < 0 || - git_str_puts(&nfd_path, trailer) < 0) - goto done; - - found_decomposed = git_fs_path_exists(nfd_path.ptr); - - /* remove temporary file (using original precomposed path) */ - (void)p_unlink(nfc_path.ptr); - -done: - git_str_dispose(&nfc_path); - git_str_dispose(&nfd_path); - return found_decomposed; -} - -#else - -bool git_fs_path_does_decompose_unicode(const char *root) -{ - GIT_UNUSED(root); - return false; -} - -#endif - -#if defined(__sun) || defined(__GNU__) -typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1]; -#else -typedef struct dirent path_dirent_data; -#endif - -int git_fs_path_direach( - git_str *path, - uint32_t flags, - int (*fn)(void *, git_str *), - void *arg) -{ - int error = 0; - ssize_t wd_len; - DIR *dir; - struct dirent *de; - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT; -#endif - - GIT_UNUSED(flags); - - if (git_fs_path_to_dir(path) < 0) - return -1; - - wd_len = git_str_len(path); - - if ((dir = opendir(path->ptr)) == NULL) { - git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path->ptr); - if (errno == ENOENT) - return GIT_ENOTFOUND; - - return -1; - } - -#ifdef GIT_USE_ICONV - if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0) - (void)git_fs_path_iconv_init_precompose(&ic); -#endif - - while ((de = readdir(dir)) != NULL) { - const char *de_path = de->d_name; - size_t de_len = strlen(de_path); - - if (git_fs_path_is_dot_or_dotdot(de_path)) - continue; - -#ifdef GIT_USE_ICONV - if ((error = git_fs_path_iconv(&ic, &de_path, &de_len)) < 0) - break; -#endif - - if ((error = git_str_put(path, de_path, de_len)) < 0) - break; - - git_error_clear(); - error = fn(arg, path); - - git_str_truncate(path, wd_len); /* restore path */ - - /* Only set our own error if the callback did not set one already */ - if (error != 0) { - if (!git_error_last()) - git_error_set_after_callback(error); - - break; - } - } - - closedir(dir); - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_clear(&ic); -#endif - - return error; -} - -#if defined(GIT_WIN32) && !defined(__MINGW32__) - -/* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7 - * and better. - */ -#ifndef FIND_FIRST_EX_LARGE_FETCH -# define FIND_FIRST_EX_LARGE_FETCH 2 -#endif - -int git_fs_path_diriter_init( - git_fs_path_diriter *diriter, - const char *path, - unsigned int flags) -{ - git_win32_path path_filter; - - static int is_win7_or_later = -1; - if (is_win7_or_later < 0) - is_win7_or_later = git_has_win32_version(6, 1, 0); - - GIT_ASSERT_ARG(diriter); - GIT_ASSERT_ARG(path); - - memset(diriter, 0, sizeof(git_fs_path_diriter)); - diriter->handle = INVALID_HANDLE_VALUE; - - if (git_str_puts(&diriter->path_utf8, path) < 0) - return -1; - - path_trim_slashes(&diriter->path_utf8); - - if (diriter->path_utf8.size == 0) { - git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path); - return -1; - } - - if ((diriter->parent_len = git_win32_path_from_utf8(diriter->path, diriter->path_utf8.ptr)) < 0 || - !git_win32__findfirstfile_filter(path_filter, diriter->path_utf8.ptr)) { - git_error_set(GIT_ERROR_OS, "could not parse the directory path '%s'", path); - return -1; - } - - diriter->handle = FindFirstFileExW( - path_filter, - is_win7_or_later ? FindExInfoBasic : FindExInfoStandard, - &diriter->current, - FindExSearchNameMatch, - NULL, - is_win7_or_later ? FIND_FIRST_EX_LARGE_FETCH : 0); - - if (diriter->handle == INVALID_HANDLE_VALUE) { - git_error_set(GIT_ERROR_OS, "could not open directory '%s'", path); - return -1; - } - - diriter->parent_utf8_len = diriter->path_utf8.size; - diriter->flags = flags; - return 0; -} - -static int diriter_update_paths(git_fs_path_diriter *diriter) -{ - size_t filename_len, path_len; - - filename_len = wcslen(diriter->current.cFileName); - - if (GIT_ADD_SIZET_OVERFLOW(&path_len, diriter->parent_len, filename_len) || - GIT_ADD_SIZET_OVERFLOW(&path_len, path_len, 2)) - return -1; - - if (path_len > GIT_WIN_PATH_UTF16) { - git_error_set(GIT_ERROR_FILESYSTEM, - "invalid path '%.*ls\\%ls' (path too long)", - diriter->parent_len, diriter->path, diriter->current.cFileName); - return -1; - } - - diriter->path[diriter->parent_len] = L'\\'; - memcpy(&diriter->path[diriter->parent_len+1], - diriter->current.cFileName, filename_len * sizeof(wchar_t)); - diriter->path[path_len-1] = L'\0'; - - git_str_truncate(&diriter->path_utf8, diriter->parent_utf8_len); - - if (diriter->parent_utf8_len > 0 && - diriter->path_utf8.ptr[diriter->parent_utf8_len-1] != '/') - git_str_putc(&diriter->path_utf8, '/'); - - git_str_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len); - - if (git_str_oom(&diriter->path_utf8)) - return -1; - - return 0; -} - -int git_fs_path_diriter_next(git_fs_path_diriter *diriter) -{ - bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT); - - do { - /* Our first time through, we already have the data from - * FindFirstFileW. Use it, otherwise get the next file. - */ - if (!diriter->needs_next) - diriter->needs_next = 1; - else if (!FindNextFileW(diriter->handle, &diriter->current)) - return GIT_ITEROVER; - } while (skip_dot && git_fs_path_is_dot_or_dotdotW(diriter->current.cFileName)); - - if (diriter_update_paths(diriter) < 0) - return -1; - - return 0; -} - -int git_fs_path_diriter_filename( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(out_len); - GIT_ASSERT_ARG(diriter); - GIT_ASSERT(diriter->path_utf8.size > diriter->parent_utf8_len); - - *out = &diriter->path_utf8.ptr[diriter->parent_utf8_len+1]; - *out_len = diriter->path_utf8.size - diriter->parent_utf8_len - 1; - return 0; -} - -int git_fs_path_diriter_fullpath( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(out_len); - GIT_ASSERT_ARG(diriter); - - *out = diriter->path_utf8.ptr; - *out_len = diriter->path_utf8.size; - return 0; -} - -int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diriter); - - return git_win32__file_attribute_to_stat(out, - (WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current, - diriter->path); -} - -void git_fs_path_diriter_free(git_fs_path_diriter *diriter) -{ - if (diriter == NULL) - return; - - git_str_dispose(&diriter->path_utf8); - - if (diriter->handle != INVALID_HANDLE_VALUE) { - FindClose(diriter->handle); - diriter->handle = INVALID_HANDLE_VALUE; - } -} - -#else - -int git_fs_path_diriter_init( - git_fs_path_diriter *diriter, - const char *path, - unsigned int flags) -{ - GIT_ASSERT_ARG(diriter); - GIT_ASSERT_ARG(path); - - memset(diriter, 0, sizeof(git_fs_path_diriter)); - - if (git_str_puts(&diriter->path, path) < 0) - return -1; - - path_trim_slashes(&diriter->path); - - if (diriter->path.size == 0) { - git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path); - return -1; - } - - if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) { - git_str_dispose(&diriter->path); - - git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path); - return -1; - } - -#ifdef GIT_USE_ICONV - if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0) - (void)git_fs_path_iconv_init_precompose(&diriter->ic); -#endif - - diriter->parent_len = diriter->path.size; - diriter->flags = flags; - - return 0; -} - -int git_fs_path_diriter_next(git_fs_path_diriter *diriter) -{ - struct dirent *de; - const char *filename; - size_t filename_len; - bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT); - int error = 0; - - GIT_ASSERT_ARG(diriter); - - errno = 0; - - do { - if ((de = readdir(diriter->dir)) == NULL) { - if (!errno) - return GIT_ITEROVER; - - git_error_set(GIT_ERROR_OS, - "could not read directory '%s'", diriter->path.ptr); - return -1; - } - } while (skip_dot && git_fs_path_is_dot_or_dotdot(de->d_name)); - - filename = de->d_name; - filename_len = strlen(filename); - -#ifdef GIT_USE_ICONV - if ((diriter->flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0 && - (error = git_fs_path_iconv(&diriter->ic, &filename, &filename_len)) < 0) - return error; -#endif - - git_str_truncate(&diriter->path, diriter->parent_len); - - if (diriter->parent_len > 0 && - diriter->path.ptr[diriter->parent_len-1] != '/') - git_str_putc(&diriter->path, '/'); - - git_str_put(&diriter->path, filename, filename_len); - - if (git_str_oom(&diriter->path)) - return -1; - - return error; -} - -int git_fs_path_diriter_filename( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(out_len); - GIT_ASSERT_ARG(diriter); - GIT_ASSERT(diriter->path.size > diriter->parent_len); - - *out = &diriter->path.ptr[diriter->parent_len+1]; - *out_len = diriter->path.size - diriter->parent_len - 1; - return 0; -} - -int git_fs_path_diriter_fullpath( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(out_len); - GIT_ASSERT_ARG(diriter); - - *out = diriter->path.ptr; - *out_len = diriter->path.size; - return 0; -} - -int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter) -{ - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(diriter); - - return git_fs_path_lstat(diriter->path.ptr, out); -} - -void git_fs_path_diriter_free(git_fs_path_diriter *diriter) -{ - if (diriter == NULL) - return; - - if (diriter->dir) { - closedir(diriter->dir); - diriter->dir = NULL; - } - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_clear(&diriter->ic); -#endif - - git_str_dispose(&diriter->path); -} - -#endif - -int git_fs_path_dirload( - git_vector *contents, - const char *path, - size_t prefix_len, - uint32_t flags) -{ - git_fs_path_diriter iter = GIT_FS_PATH_DIRITER_INIT; - const char *name; - size_t name_len; - char *dup; - int error; - - GIT_ASSERT_ARG(contents); - GIT_ASSERT_ARG(path); - - if ((error = git_fs_path_diriter_init(&iter, path, flags)) < 0) - return error; - - while ((error = git_fs_path_diriter_next(&iter)) == 0) { - if ((error = git_fs_path_diriter_fullpath(&name, &name_len, &iter)) < 0) - break; - - GIT_ASSERT(name_len > prefix_len); - - dup = git__strndup(name + prefix_len, name_len - prefix_len); - GIT_ERROR_CHECK_ALLOC(dup); - - if ((error = git_vector_insert(contents, dup)) < 0) - break; - } - - if (error == GIT_ITEROVER) - error = 0; - - git_fs_path_diriter_free(&iter); - return error; -} - -int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path) -{ - if (git_fs_path_is_local_file_url(url_or_path)) - return git_fs_path_fromurl(local_path_out, url_or_path); - else - return git_str_sets(local_path_out, url_or_path); -} - -/* Reject paths like AUX or COM1, or those versions that end in a dot or - * colon. ("AUX." or "AUX:") - */ -GIT_INLINE(bool) validate_dospath( - const char *component, - size_t len, - const char dospath[3], - bool trailing_num) -{ - size_t last = trailing_num ? 4 : 3; - - if (len < last || git__strncasecmp(component, dospath, 3) != 0) - return true; - - if (trailing_num && (component[3] < '1' || component[3] > '9')) - return true; - - return (len > last && - component[last] != '.' && - component[last] != ':'); -} - -GIT_INLINE(bool) validate_char(unsigned char c, unsigned int flags) -{ - if ((flags & GIT_FS_PATH_REJECT_BACKSLASH) && c == '\\') - return false; - - if ((flags & GIT_FS_PATH_REJECT_SLASH) && c == '/') - return false; - - if (flags & GIT_FS_PATH_REJECT_NT_CHARS) { - if (c < 32) - return false; - - switch (c) { - case '<': - case '>': - case ':': - case '"': - case '|': - case '?': - case '*': - return false; - } - } - - return true; -} - -/* - * We fundamentally don't like some paths when dealing with user-inputted - * strings (to avoid escaping a sandbox): we don't want dot or dot-dot - * anywhere, we want to avoid writing weird paths on Windows that can't - * be handled by tools that use the non-\\?\ APIs, we don't want slashes - * or double slashes at the end of paths that can make them ambiguous. - * - * For checkout, we don't want to recurse into ".git" either. - */ -static bool validate_component( - const char *component, - size_t len, - unsigned int flags) -{ - if (len == 0) - return !(flags & GIT_FS_PATH_REJECT_EMPTY_COMPONENT); - - if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) && - len == 1 && component[0] == '.') - return false; - - if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) && - len == 2 && component[0] == '.' && component[1] == '.') - return false; - - if ((flags & GIT_FS_PATH_REJECT_TRAILING_DOT) && - component[len - 1] == '.') - return false; - - if ((flags & GIT_FS_PATH_REJECT_TRAILING_SPACE) && - component[len - 1] == ' ') - return false; - - if ((flags & GIT_FS_PATH_REJECT_TRAILING_COLON) && - component[len - 1] == ':') - return false; - - if (flags & GIT_FS_PATH_REJECT_DOS_PATHS) { - if (!validate_dospath(component, len, "CON", false) || - !validate_dospath(component, len, "PRN", false) || - !validate_dospath(component, len, "AUX", false) || - !validate_dospath(component, len, "NUL", false) || - !validate_dospath(component, len, "COM", true) || - !validate_dospath(component, len, "LPT", true)) - return false; - } - - return true; -} - -#ifdef GIT_WIN32 -GIT_INLINE(bool) validate_length( - const char *path, - size_t len, - size_t utf8_char_len) -{ - GIT_UNUSED(path); - GIT_UNUSED(len); - - return (utf8_char_len <= MAX_PATH); -} -#endif - -bool git_fs_path_str_is_valid_ext( - const git_str *path, - unsigned int flags, - bool (*validate_char_cb)(char ch, void *payload), - bool (*validate_component_cb)(const char *component, size_t len, void *payload), - bool (*validate_length_cb)(const char *path, size_t len, size_t utf8_char_len), - void *payload) -{ - const char *start, *c; - size_t len = 0; - - if (!flags) - return true; - - for (start = c = path->ptr; *c && len < path->size; c++, len++) { - if (!validate_char(*c, flags)) - return false; - - if (validate_char_cb && !validate_char_cb(*c, payload)) - return false; - - if (*c != '/') - continue; - - if (!validate_component(start, (c - start), flags)) - return false; - - if (validate_component_cb && - !validate_component_cb(start, (c - start), payload)) - return false; - - start = c + 1; - } - - /* - * We want to support paths specified as either `const char *` - * or `git_str *`; we pass size as `SIZE_MAX` when we use a - * `const char *` to avoid a `strlen`. Ensure that we didn't - * have a NUL in the buffer if there was a non-SIZE_MAX length. - */ - if (path->size != SIZE_MAX && len != path->size) - return false; - - if (!validate_component(start, (c - start), flags)) - return false; - - if (validate_component_cb && - !validate_component_cb(start, (c - start), payload)) - return false; - -#ifdef GIT_WIN32 - if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS) != 0) { - size_t utf8_len = git_utf8_char_length(path->ptr, len); - - if (!validate_length(path->ptr, len, utf8_len)) - return false; - - if (validate_length_cb && - !validate_length_cb(path->ptr, len, utf8_len)) - return false; - } -#else - GIT_UNUSED(validate_length_cb); -#endif - - return true; -} - -int git_fs_path_validate_str_length_with_suffix( - git_str *path, - size_t suffix_len) -{ -#ifdef GIT_WIN32 - size_t utf8_len = git_utf8_char_length(path->ptr, path->size); - size_t total_len; - - if (GIT_ADD_SIZET_OVERFLOW(&total_len, utf8_len, suffix_len) || - total_len > MAX_PATH) { - - git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%.*s'", - (int)path->size, path->ptr); - return -1; - } -#else - GIT_UNUSED(path); - GIT_UNUSED(suffix_len); -#endif - - return 0; -} - -int git_fs_path_normalize_slashes(git_str *out, const char *path) -{ - int error; - char *p; - - if ((error = git_str_puts(out, path)) < 0) - return error; - - for (p = out->ptr; *p; p++) { - if (*p == '\\') - *p = '/'; - } - - return 0; -} - -bool git_fs_path_supports_symlinks(const char *dir) -{ - git_str path = GIT_STR_INIT; - bool supported = false; - struct stat st; - int fd; - - if ((fd = git_futils_mktmp(&path, dir, 0666)) < 0 || - p_close(fd) < 0 || - p_unlink(path.ptr) < 0 || - p_symlink("testing", path.ptr) < 0 || - p_lstat(path.ptr, &st) < 0) - goto done; - - supported = (S_ISLNK(st.st_mode) != 0); -done: - if (path.size) - (void)p_unlink(path.ptr); - git_str_dispose(&path); - return supported; -} - -int git_fs_path_validate_system_file_ownership(const char *path) -{ -#ifndef GIT_WIN32 - GIT_UNUSED(path); - return GIT_OK; -#else - git_win32_path buf; - PSID owner_sid; - PSECURITY_DESCRIPTOR descriptor = NULL; - HANDLE token; - TOKEN_USER *info = NULL; - DWORD err, len; - int ret; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - err = GetNamedSecurityInfoW(buf, SE_FILE_OBJECT, - OWNER_SECURITY_INFORMATION | - DACL_SECURITY_INFORMATION, - &owner_sid, NULL, NULL, NULL, &descriptor); - - if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { - ret = GIT_ENOTFOUND; - goto cleanup; - } - - if (err != ERROR_SUCCESS) { - git_error_set(GIT_ERROR_OS, "failed to get security information"); - ret = GIT_ERROR; - goto cleanup; - } - - if (!IsValidSid(owner_sid)) { - git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is unknown"); - ret = GIT_ERROR; - goto cleanup; - } - - if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) || - IsWellKnownSid(owner_sid, WinLocalSystemSid)) { - ret = GIT_OK; - goto cleanup; - } - - /* Obtain current user's SID */ - if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) && - !GetTokenInformation(token, TokenUser, NULL, 0, &len)) { - info = git__malloc(len); - GIT_ERROR_CHECK_ALLOC(info); - if (!GetTokenInformation(token, TokenUser, info, len, &len)) { - git__free(info); - info = NULL; - } - } - - /* - * If the file is owned by the same account that is running the current - * process, it's okay to read from that file. - */ - if (info && EqualSid(owner_sid, info->User.Sid)) - ret = GIT_OK; - else { - git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is not valid"); - ret = GIT_ERROR; - } - git__free(info); - -cleanup: - if (descriptor) - LocalFree(descriptor); - - return ret; -#endif -} - -int git_fs_path_find_executable(git_str *fullpath, const char *executable) -{ -#ifdef GIT_WIN32 - git_win32_path fullpath_w, executable_w; - int error; - - if (git__utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0) - return -1; - - error = git_win32_path_find_executable(fullpath_w, executable_w); - - if (error == 0) - error = git_str_put_w(fullpath, fullpath_w, wcslen(fullpath_w)); - - return error; -#else - git_str path = GIT_STR_INIT; - const char *current_dir, *term; - bool found = false; - - if (git__getenv(&path, "PATH") < 0) - return -1; - - current_dir = path.ptr; - - while (*current_dir) { - if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR))) - term = strchr(current_dir, '\0'); - - git_str_clear(fullpath); - if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 || - git_str_putc(fullpath, '/') < 0 || - git_str_puts(fullpath, executable) < 0) - return -1; - - if (git_fs_path_isfile(fullpath->ptr)) { - found = true; - break; - } - - current_dir = term; - - while (*current_dir == GIT_PATH_LIST_SEPARATOR) - current_dir++; - } - - git_str_dispose(&path); - - if (found) - return 0; - - git_str_clear(fullpath); - return GIT_ENOTFOUND; -#endif -} diff --git a/src/libgit2/fs_path.h b/src/libgit2/fs_path.h deleted file mode 100644 index 222c44abc..000000000 --- a/src/libgit2/fs_path.h +++ /dev/null @@ -1,752 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_fs_path_h__ -#define INCLUDE_fs_path_h__ - -#include "common.h" - -#include "posix.h" -#include "str.h" -#include "vector.h" -#include "utf8.h" - -/** - * Path manipulation utils - * - * These are path utilities that munge paths without actually - * looking at the real filesystem. - */ - -/* - * The dirname() function shall take a pointer to a character string - * that contains a pathname, and return a pointer to a string that is a - * pathname of the parent directory of that file. Trailing '/' characters - * in the path are not counted as part of the path. - * - * If path does not contain a '/', then dirname() shall return a pointer to - * the string ".". If path is a null pointer or points to an empty string, - * dirname() shall return a pointer to the string "." . - * - * The `git_fs_path_dirname` implementation is thread safe. The returned - * string must be manually free'd. - * - * The `git_fs_path_dirname_r` implementation writes the dirname to a `git_str` - * if the buffer pointer is not NULL. - * It returns an error code < 0 if there is an allocation error, otherwise - * the length of the dirname (which will be > 0). - */ -extern char *git_fs_path_dirname(const char *path); -extern int git_fs_path_dirname_r(git_str *buffer, const char *path); - -/* - * This function returns the basename of the file, which is the last - * part of its full name given by fname, with the drive letter and - * leading directories stripped off. For example, the basename of - * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo. - * - * Trailing slashes and backslashes are significant: the basename of - * c:/foo/bar/ is an empty string after the rightmost slash. - * - * The `git_fs_path_basename` implementation is thread safe. The returned - * string must be manually free'd. - * - * The `git_fs_path_basename_r` implementation writes the basename to a `git_str`. - * It returns an error code < 0 if there is an allocation error, otherwise - * the length of the basename (which will be >= 0). - */ -extern char *git_fs_path_basename(const char *path); -extern int git_fs_path_basename_r(git_str *buffer, const char *path); - -/* Return the offset of the start of the basename. Unlike the other - * basename functions, this returns 0 if the path is empty. - */ -extern size_t git_fs_path_basename_offset(git_str *buffer); - -/** - * Find offset to root of path if path has one. - * - * This will return a number >= 0 which is the offset to the start of the - * path, if the path is rooted (i.e. "/rooted/path" returns 0 and - * "c:/windows/rooted/path" returns 2). If the path is not rooted, this - * returns -1. - */ -extern int git_fs_path_root(const char *path); - -/** - * Ensure path has a trailing '/'. - */ -extern int git_fs_path_to_dir(git_str *path); - -/** - * Ensure string has a trailing '/' if there is space for it. - */ -extern void git_fs_path_string_to_dir(char *path, size_t size); - -/** - * Taken from git.git; returns nonzero if the given path is "." or "..". - */ -GIT_INLINE(int) git_fs_path_is_dot_or_dotdot(const char *name) -{ - return (name[0] == '.' && - (name[1] == '\0' || - (name[1] == '.' && name[2] == '\0'))); -} - -#ifdef GIT_WIN32 -GIT_INLINE(int) git_fs_path_is_dot_or_dotdotW(const wchar_t *name) -{ - return (name[0] == L'.' && - (name[1] == L'\0' || - (name[1] == L'.' && name[2] == L'\0'))); -} - -#define git_fs_path_is_absolute(p) \ - (git__isalpha((p)[0]) && (p)[1] == ':' && ((p)[2] == '\\' || (p)[2] == '/')) - -#define git_fs_path_is_dirsep(p) \ - ((p) == '/' || (p) == '\\') - -/** - * Convert backslashes in path to forward slashes. - */ -GIT_INLINE(void) git_fs_path_mkposix(char *path) -{ - while (*path) { - if (*path == '\\') - *path = '/'; - - path++; - } -} -#else -# define git_fs_path_mkposix(p) /* blank */ - -#define git_fs_path_is_absolute(p) \ - ((p)[0] == '/') - -#define git_fs_path_is_dirsep(p) \ - ((p) == '/') - -#endif - -/** - * Check if string is a relative path (i.e. starts with "./" or "../") - */ -GIT_INLINE(int) git_fs_path_is_relative(const char *p) -{ - return (p[0] == '.' && (p[1] == '/' || (p[1] == '.' && p[2] == '/'))); -} - -/** - * Check if string is at end of path segment (i.e. looking at '/' or '\0') - */ -GIT_INLINE(int) git_fs_path_at_end_of_segment(const char *p) -{ - return !*p || *p == '/'; -} - -extern int git__percent_decode(git_str *decoded_out, const char *input); - -/** - * Extract path from file:// URL. - */ -extern int git_fs_path_fromurl(git_str *local_path_out, const char *file_url); - - -/** - * Path filesystem utils - * - * These are path utilities that actually access the filesystem. - */ - -/** - * Check if a file exists and can be accessed. - * @return true or false - */ -extern bool git_fs_path_exists(const char *path); - -/** - * Check if the given path points to a directory. - * @return true or false - */ -extern bool git_fs_path_isdir(const char *path); - -/** - * Check if the given path points to a regular file. - * @return true or false - */ -extern bool git_fs_path_isfile(const char *path); - -/** - * Check if the given path points to a symbolic link. - * @return true or false - */ -extern bool git_fs_path_islink(const char *path); - -/** - * Check if the given path is a directory, and is empty. - */ -extern bool git_fs_path_is_empty_dir(const char *path); - -/** - * Stat a file and/or link and set error if needed. - */ -extern int git_fs_path_lstat(const char *path, struct stat *st); - -/** - * Check if the parent directory contains the item. - * - * @param dir Directory to check. - * @param item Item that might be in the directory. - * @return 0 if item exists in directory, <0 otherwise. - */ -extern bool git_fs_path_contains(git_str *dir, const char *item); - -/** - * Check if the given path contains the given subdirectory. - * - * @param parent Directory path that might contain subdir - * @param subdir Subdirectory name to look for in parent - * @return true if subdirectory exists, false otherwise. - */ -extern bool git_fs_path_contains_dir(git_str *parent, const char *subdir); - -/** - * Determine the common directory length between two paths, including - * the final path separator. For example, given paths 'a/b/c/1.txt - * and 'a/b/c/d/2.txt', the common directory is 'a/b/c/', and this - * will return the length of the string 'a/b/c/', which is 6. - * - * @param one The first path - * @param two The second path - * @return The length of the common directory - */ -extern size_t git_fs_path_common_dirlen(const char *one, const char *two); - -/** - * Make the path relative to the given parent path. - * - * @param path The path to make relative - * @param parent The parent path to make path relative to - * @return 0 if path was made relative, GIT_ENOTFOUND - * if there was not common root between the paths, - * or <0. - */ -extern int git_fs_path_make_relative(git_str *path, const char *parent); - -/** - * Check if the given path contains the given file. - * - * @param dir Directory path that might contain file - * @param file File name to look for in parent - * @return true if file exists, false otherwise. - */ -extern bool git_fs_path_contains_file(git_str *dir, const char *file); - -/** - * Prepend base to unrooted path or just copy path over. - * - * This will optionally return the index into the path where the "root" - * is, either the end of the base directory prefix or the path root. - */ -extern int git_fs_path_join_unrooted( - git_str *path_out, const char *path, const char *base, ssize_t *root_at); - -/** - * Removes multiple occurrences of '/' in a row, squashing them into a - * single '/'. - */ -extern void git_fs_path_squash_slashes(git_str *path); - -/** - * Clean up path, prepending base if it is not already rooted. - */ -extern int git_fs_path_prettify(git_str *path_out, const char *path, const char *base); - -/** - * Clean up path, prepending base if it is not already rooted and - * appending a slash. - */ -extern int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base); - -/** - * Get a directory from a path. - * - * If path is a directory, this acts like `git_fs_path_prettify_dir` - * (cleaning up path and appending a '/'). If path is a normal file, - * this prettifies it, then removed the filename a la dirname and - * appends the trailing '/'. If the path does not exist, it is - * treated like a regular filename. - */ -extern int git_fs_path_find_dir(git_str *dir); - -/** - * Resolve relative references within a path. - * - * This eliminates "./" and "../" relative references inside a path, - * as well as condensing multiple slashes into single ones. It will - * not touch the path before the "ceiling" length. - * - * Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL - * prefix and not touch that part of the path. - */ -extern int git_fs_path_resolve_relative(git_str *path, size_t ceiling); - -/** - * Apply a relative path to base path. - * - * Note that the base path could be a filename or a URL and this - * should still work. The relative path is walked segment by segment - * with three rules: series of slashes will be condensed to a single - * slash, "." will be eaten with no change, and ".." will remove a - * segment from the base path. - */ -extern int git_fs_path_apply_relative(git_str *target, const char *relpath); - -enum { - GIT_FS_PATH_DIR_IGNORE_CASE = (1u << 0), - GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1), - GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT = (1u << 2), -}; - -/** - * Walk each directory entry, except '.' and '..', calling fn(state). - * - * @param pathbuf Buffer the function reads the initial directory - * path from, and updates with each successive entry's name. - * @param flags Combination of GIT_FS_PATH_DIR flags. - * @param callback Callback for each entry. Passed the `payload` and each - * successive path inside the directory as a full path. This may - * safely append text to the pathbuf if needed. Return non-zero to - * cancel iteration (and return value will be propagated back). - * @param payload Passed to callback as first argument. - * @return 0 on success or error code from OS error or from callback - */ -extern int git_fs_path_direach( - git_str *pathbuf, - uint32_t flags, - int (*callback)(void *payload, git_str *path), - void *payload); - -/** - * Sort function to order two paths - */ -extern int git_fs_path_cmp( - const char *name1, size_t len1, int isdir1, - const char *name2, size_t len2, int isdir2, - int (*compare)(const char *, const char *, size_t)); - -/** - * Invoke callback up path directory by directory until the ceiling is - * reached (inclusive of a final call at the root_path). - * - * Returning anything other than 0 from the callback function - * will stop the iteration and propagate the error to the caller. - * - * @param pathbuf Buffer the function reads the directory from and - * and updates with each successive name. - * @param ceiling Prefix of path at which to stop walking up. If NULL, - * this will walk all the way up to the root. If not a prefix of - * pathbuf, the callback will be invoked a single time on the - * original input path. - * @param callback Function to invoke on each path. Passed the `payload` - * and the buffer containing the current path. The path should not - * be modified in any way. Return non-zero to stop iteration. - * @param payload Passed to fn as the first ath. - */ -extern int git_fs_path_walk_up( - git_str *pathbuf, - const char *ceiling, - int (*callback)(void *payload, const char *path), - void *payload); - - -enum { - GIT_FS_PATH_NOTEQUAL = 0, - GIT_FS_PATH_EQUAL = 1, - GIT_FS_PATH_PREFIX = 2 -}; - -/* - * Determines if a path is equal to or potentially a child of another. - * @param parent The possible parent - * @param child The possible child - */ -GIT_INLINE(int) git_fs_path_equal_or_prefixed( - const char *parent, - const char *child, - ssize_t *prefixlen) -{ - const char *p = parent, *c = child; - int lastslash = 0; - - while (*p && *c) { - lastslash = (*p == '/'); - - if (*p++ != *c++) - return GIT_FS_PATH_NOTEQUAL; - } - - if (*p != '\0') - return GIT_FS_PATH_NOTEQUAL; - - if (*c == '\0') { - if (prefixlen) - *prefixlen = p - parent; - - return GIT_FS_PATH_EQUAL; - } - - if (*c == '/' || lastslash) { - if (prefixlen) - *prefixlen = (p - parent) - lastslash; - - return GIT_FS_PATH_PREFIX; - } - - return GIT_FS_PATH_NOTEQUAL; -} - -/* translate errno to libgit2 error code and set error message */ -extern int git_fs_path_set_error( - int errno_value, const char *path, const char *action); - -/* check if non-ascii characters are present in filename */ -extern bool git_fs_path_has_non_ascii(const char *path, size_t pathlen); - -#define GIT_PATH_REPO_ENCODING "UTF-8" - -#ifdef __APPLE__ -#define GIT_PATH_NATIVE_ENCODING "UTF-8-MAC" -#else -#define GIT_PATH_NATIVE_ENCODING "UTF-8" -#endif - -#ifdef GIT_USE_ICONV - -#include - -typedef struct { - iconv_t map; - git_str buf; -} git_fs_path_iconv_t; - -#define GIT_PATH_ICONV_INIT { (iconv_t)-1, GIT_STR_INIT } - -/* Init iconv data for converting decomposed UTF-8 to precomposed */ -extern int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic); - -/* Clear allocated iconv data */ -extern void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic); - -/* - * Rewrite `in` buffer using iconv map if necessary, replacing `in` - * pointer internal iconv buffer if rewrite happened. The `in` pointer - * will be left unchanged if no rewrite was needed. - */ -extern int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen); - -#endif /* GIT_USE_ICONV */ - -extern bool git_fs_path_does_decompose_unicode(const char *root); - - -typedef struct git_fs_path_diriter git_fs_path_diriter; - -#if defined(GIT_WIN32) && !defined(__MINGW32__) - -struct git_fs_path_diriter -{ - git_win32_path path; - size_t parent_len; - - git_str path_utf8; - size_t parent_utf8_len; - - HANDLE handle; - - unsigned int flags; - - WIN32_FIND_DATAW current; - unsigned int needs_next; -}; - -#define GIT_FS_PATH_DIRITER_INIT { {0}, 0, GIT_STR_INIT, 0, INVALID_HANDLE_VALUE } - -#else - -struct git_fs_path_diriter -{ - git_str path; - size_t parent_len; - - unsigned int flags; - - DIR *dir; - -#ifdef GIT_USE_ICONV - git_fs_path_iconv_t ic; -#endif -}; - -#define GIT_FS_PATH_DIRITER_INIT { GIT_STR_INIT } - -#endif - -/** - * Initialize a directory iterator. - * - * @param diriter Pointer to a diriter structure that will be setup. - * @param path The path that will be iterated over - * @param flags Directory reader flags - * @return 0 or an error code - */ -extern int git_fs_path_diriter_init( - git_fs_path_diriter *diriter, - const char *path, - unsigned int flags); - -/** - * Advance the directory iterator. Will return GIT_ITEROVER when - * the iteration has completed successfully. - * - * @param diriter The directory iterator - * @return 0, GIT_ITEROVER, or an error code - */ -extern int git_fs_path_diriter_next(git_fs_path_diriter *diriter); - -/** - * Returns the file name of the current item in the iterator. - * - * @param out Pointer to store the path in - * @param out_len Pointer to store the length of the path in - * @param diriter The directory iterator - * @return 0 or an error code - */ -extern int git_fs_path_diriter_filename( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter); - -/** - * Returns the full path of the current item in the iterator; that - * is the current filename plus the path of the directory that the - * iterator was constructed with. - * - * @param out Pointer to store the path in - * @param out_len Pointer to store the length of the path in - * @param diriter The directory iterator - * @return 0 or an error code - */ -extern int git_fs_path_diriter_fullpath( - const char **out, - size_t *out_len, - git_fs_path_diriter *diriter); - -/** - * Performs an `lstat` on the current item in the iterator. - * - * @param out Pointer to store the stat data in - * @param diriter The directory iterator - * @return 0 or an error code - */ -extern int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter); - -/** - * Closes the directory iterator. - * - * @param diriter The directory iterator - */ -extern void git_fs_path_diriter_free(git_fs_path_diriter *diriter); - -/** - * Load all directory entries (except '.' and '..') into a vector. - * - * For cases where `git_fs_path_direach()` is not appropriate, this - * allows you to load the filenames in a directory into a vector - * of strings. That vector can then be sorted, iterated, or whatever. - * Remember to free alloc of the allocated strings when you are done. - * - * @param contents Vector to fill with directory entry names. - * @param path The directory to read from. - * @param prefix_len When inserting entries, the trailing part of path - * will be prefixed after this length. I.e. given path "/a/b" and - * prefix_len 3, the entries will look like "b/e1", "b/e2", etc. - * @param flags Combination of GIT_FS_PATH_DIR flags. - */ -extern int git_fs_path_dirload( - git_vector *contents, - const char *path, - size_t prefix_len, - uint32_t flags); - - -/* Used for paths to repositories on the filesystem */ -extern bool git_fs_path_is_local_file_url(const char *file_url); -extern int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path); - -/* Flags to determine path validity in `git_fs_path_isvalid` */ -#define GIT_FS_PATH_REJECT_EMPTY_COMPONENT (1 << 0) -#define GIT_FS_PATH_REJECT_TRAVERSAL (1 << 1) -#define GIT_FS_PATH_REJECT_SLASH (1 << 2) -#define GIT_FS_PATH_REJECT_BACKSLASH (1 << 3) -#define GIT_FS_PATH_REJECT_TRAILING_DOT (1 << 4) -#define GIT_FS_PATH_REJECT_TRAILING_SPACE (1 << 5) -#define GIT_FS_PATH_REJECT_TRAILING_COLON (1 << 6) -#define GIT_FS_PATH_REJECT_DOS_PATHS (1 << 7) -#define GIT_FS_PATH_REJECT_NT_CHARS (1 << 8) -#define GIT_FS_PATH_REJECT_LONG_PATHS (1 << 9) - -#define GIT_FS_PATH_REJECT_MAX (1 << 9) - -/* Default path safety for writing files to disk: since we use the - * Win32 "File Namespace" APIs ("\\?\") we need to protect from - * paths that the normal Win32 APIs would not write. - */ -#ifdef GIT_WIN32 -# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \ - GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \ - GIT_FS_PATH_REJECT_TRAVERSAL | \ - GIT_FS_PATH_REJECT_BACKSLASH | \ - GIT_FS_PATH_REJECT_TRAILING_DOT | \ - GIT_FS_PATH_REJECT_TRAILING_SPACE | \ - GIT_FS_PATH_REJECT_TRAILING_COLON | \ - GIT_FS_PATH_REJECT_DOS_PATHS | \ - GIT_FS_PATH_REJECT_NT_CHARS -#else -# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \ - GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \ - GIT_FS_PATH_REJECT_TRAVERSAL -#endif - -/** - * Validate a filesystem path; with custom callbacks per-character and - * per-path component. - */ -extern bool git_fs_path_str_is_valid_ext( - const git_str *path, - unsigned int flags, - bool (*validate_char_cb)(char ch, void *payload), - bool (*validate_component_cb)(const char *component, size_t len, void *payload), - bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len), - void *payload); - -GIT_INLINE(bool) git_fs_path_is_valid_ext( - const char *path, - unsigned int flags, - bool (*validate_char_cb)(char ch, void *payload), - bool (*validate_component_cb)(const char *component, size_t len, void *payload), - bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len), - void *payload) -{ - const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); - return git_fs_path_str_is_valid_ext( - &str, - flags, - validate_char_cb, - validate_component_cb, - validate_length_cb, - payload); -} - -/** - * Validate a filesystem path. This ensures that the given path is legal - * and does not contain any "unsafe" components like path traversal ('.' - * or '..'), characters that are inappropriate for lesser filesystems - * (trailing ' ' or ':' characters), or filenames ("component names") - * that are not supported ('AUX', 'COM1"). - */ -GIT_INLINE(bool) git_fs_path_is_valid( - const char *path, - unsigned int flags) -{ - const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); - return git_fs_path_str_is_valid_ext(&str, flags, NULL, NULL, NULL, NULL); -} - -/** Validate a filesystem path in a `git_str`. */ -GIT_INLINE(bool) git_fs_path_str_is_valid( - const git_str *path, - unsigned int flags) -{ - return git_fs_path_str_is_valid_ext(path, flags, NULL, NULL, NULL, NULL); -} - -extern int git_fs_path_validate_str_length_with_suffix( - git_str *path, - size_t suffix_len); - -/** - * Validate an on-disk path, taking into account that it will have a - * suffix appended (eg, `.lock`). - */ -GIT_INLINE(int) git_fs_path_validate_filesystem_with_suffix( - const char *path, - size_t path_len, - size_t suffix_len) -{ -#ifdef GIT_WIN32 - size_t path_chars, total_chars; - - path_chars = git_utf8_char_length(path, path_len); - - if (GIT_ADD_SIZET_OVERFLOW(&total_chars, path_chars, suffix_len) || - total_chars > MAX_PATH) { - git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%s'", path); - return -1; - } - return 0; -#else - GIT_UNUSED(path); - GIT_UNUSED(path_len); - GIT_UNUSED(suffix_len); - return 0; -#endif -} - -/** - * Validate an path on the filesystem. This ensures that the given - * path is valid for the operating system/platform; for example, this - * will ensure that the given absolute path is smaller than MAX_PATH on - * Windows. - * - * For paths within the working directory, you should use ensure that - * `core.longpaths` is obeyed. Use `git_fs_path_validate_workdir`. - */ -GIT_INLINE(int) git_fs_path_validate_filesystem( - const char *path, - size_t path_len) -{ - return git_fs_path_validate_filesystem_with_suffix(path, path_len, 0); -} - -/** - * Convert any backslashes into slashes - */ -int git_fs_path_normalize_slashes(git_str *out, const char *path); - -bool git_fs_path_supports_symlinks(const char *dir); - -/** - * Validate a system file's ownership - * - * Verify that the file in question is owned by an administrator or system - * account, or at least by the current user. - * - * This function returns 0 if successful. If the file is not owned by any of - * these, or any other if there have been problems determining the file - * ownership, it returns -1. - */ -int git_fs_path_validate_system_file_ownership(const char *path); - -/** - * Search the current PATH for the given executable, returning the full - * path if it is found. - */ -int git_fs_path_find_executable(git_str *fullpath, const char *executable); - -#endif diff --git a/src/libgit2/futils.c b/src/libgit2/futils.c deleted file mode 100644 index 42c35955e..000000000 --- a/src/libgit2/futils.c +++ /dev/null @@ -1,1194 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "futils.h" - -#include "runtime.h" -#include "strmap.h" -#include "hash.h" -#include "rand.h" - -#include -#if GIT_WIN32 -#include "win32/findfile.h" -#endif - -#define GIT_FILEMODE_DEFAULT 0100666 - -int git_futils_mkpath2file(const char *file_path, const mode_t mode) -{ - return git_futils_mkdir( - file_path, mode, - GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); -} - -int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode) -{ - const int open_flags = O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC; - unsigned int tries = 32; - int fd; - - while (tries--) { - uint64_t rand = git_rand_next(); - - git_str_sets(path_out, filename); - git_str_puts(path_out, "_git2_"); - git_str_encode_hexstr(path_out, (void *)&rand, sizeof(uint64_t)); - - if (git_str_oom(path_out)) - return -1; - - /* Note that we open with O_CREAT | O_EXCL */ - if ((fd = p_open(path_out->ptr, open_flags, mode)) >= 0) - return fd; - } - - git_error_set(GIT_ERROR_OS, - "failed to create temporary file '%s'", path_out->ptr); - git_str_dispose(path_out); - return -1; -} - -int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode) -{ - int fd; - - if (git_futils_mkpath2file(path, dirmode) < 0) - return -1; - - fd = p_creat(path, mode); - if (fd < 0) { - git_error_set(GIT_ERROR_OS, "failed to create file '%s'", path); - return -1; - } - - return fd; -} - -int git_futils_creat_locked(const char *path, const mode_t mode) -{ - int fd = p_open(path, O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, - mode); - - if (fd < 0) { - int error = errno; - git_error_set(GIT_ERROR_OS, "failed to create locked file '%s'", path); - switch (error) { - case EEXIST: - return GIT_ELOCKED; - case ENOENT: - return GIT_ENOTFOUND; - default: - return -1; - } - } - - return fd; -} - -int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode) -{ - if (git_futils_mkpath2file(path, dirmode) < 0) - return -1; - - return git_futils_creat_locked(path, mode); -} - -int git_futils_open_ro(const char *path) -{ - int fd = p_open(path, O_RDONLY); - if (fd < 0) - return git_fs_path_set_error(errno, path, "open"); - return fd; -} - -int git_futils_truncate(const char *path, int mode) -{ - int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode); - if (fd < 0) - return git_fs_path_set_error(errno, path, "open"); - - close(fd); - return 0; -} - -int git_futils_filesize(uint64_t *out, git_file fd) -{ - struct stat sb; - - if (p_fstat(fd, &sb)) { - git_error_set(GIT_ERROR_OS, "failed to stat file descriptor"); - return -1; - } - - if (sb.st_size < 0) { - git_error_set(GIT_ERROR_INVALID, "invalid file size"); - return -1; - } - - *out = sb.st_size; - return 0; -} - -mode_t git_futils_canonical_mode(mode_t raw_mode) -{ - if (S_ISREG(raw_mode)) - return S_IFREG | GIT_PERMS_CANONICAL(raw_mode); - else if (S_ISLNK(raw_mode)) - return S_IFLNK; - else if (S_ISGITLINK(raw_mode)) - return S_IFGITLINK; - else if (S_ISDIR(raw_mode)) - return S_IFDIR; - else - return 0; -} - -int git_futils_readbuffer_fd(git_str *buf, git_file fd, size_t len) -{ - ssize_t read_size = 0; - size_t alloc_len; - - git_str_clear(buf); - - if (!git__is_ssizet(len)) { - git_error_set(GIT_ERROR_INVALID, "read too large"); - return -1; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1); - if (git_str_grow(buf, alloc_len) < 0) - return -1; - - /* p_read loops internally to read len bytes */ - read_size = p_read(fd, buf->ptr, len); - - if (read_size != (ssize_t)len) { - git_error_set(GIT_ERROR_OS, "failed to read descriptor"); - git_str_dispose(buf); - return -1; - } - - buf->ptr[read_size] = '\0'; - buf->size = read_size; - - return 0; -} - -int git_futils_readbuffer_updated( - git_str *out, - const char *path, - unsigned char checksum[GIT_HASH_SHA1_SIZE], - int *updated) -{ - int error; - git_file fd; - struct stat st; - git_str buf = GIT_STR_INIT; - unsigned char checksum_new[GIT_HASH_SHA1_SIZE]; - - GIT_ASSERT_ARG(out); - GIT_ASSERT_ARG(path && *path); - - if (updated != NULL) - *updated = 0; - - if (p_stat(path, &st) < 0) - return git_fs_path_set_error(errno, path, "stat"); - - - if (S_ISDIR(st.st_mode)) { - git_error_set(GIT_ERROR_INVALID, "requested file is a directory"); - return GIT_ENOTFOUND; - } - - if (!git__is_sizet(st.st_size+1)) { - git_error_set(GIT_ERROR_OS, "invalid regular file stat for '%s'", path); - return -1; - } - - if ((fd = git_futils_open_ro(path)) < 0) - return fd; - - if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) { - p_close(fd); - return -1; - } - - p_close(fd); - - if (checksum) { - if ((error = git_hash_buf(checksum_new, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0) { - git_str_dispose(&buf); - return error; - } - - /* - * If we were given a checksum, we only want to use it if it's different - */ - if (!memcmp(checksum, checksum_new, GIT_HASH_SHA1_SIZE)) { - git_str_dispose(&buf); - if (updated) - *updated = 0; - - return 0; - } - - memcpy(checksum, checksum_new, GIT_HASH_SHA1_SIZE); - } - - /* - * If we're here, the file did change, or the user didn't have an old version - */ - if (updated != NULL) - *updated = 1; - - git_str_swap(out, &buf); - git_str_dispose(&buf); - - return 0; -} - -int git_futils_readbuffer(git_str *buf, const char *path) -{ - return git_futils_readbuffer_updated(buf, path, NULL, NULL); -} - -int git_futils_writebuffer( - const git_str *buf, const char *path, int flags, mode_t mode) -{ - int fd, do_fsync = 0, error = 0; - - if (!flags) - flags = O_CREAT | O_TRUNC | O_WRONLY; - - if ((flags & O_FSYNC) != 0) - do_fsync = 1; - - flags &= ~O_FSYNC; - - if (!mode) - mode = GIT_FILEMODE_DEFAULT; - - if ((fd = p_open(path, flags, mode)) < 0) { - git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path); - return fd; - } - - if ((error = p_write(fd, git_str_cstr(buf), git_str_len(buf))) < 0) { - git_error_set(GIT_ERROR_OS, "could not write to '%s'", path); - (void)p_close(fd); - return error; - } - - if (do_fsync && (error = p_fsync(fd)) < 0) { - git_error_set(GIT_ERROR_OS, "could not fsync '%s'", path); - p_close(fd); - return error; - } - - if ((error = p_close(fd)) < 0) { - git_error_set(GIT_ERROR_OS, "error while closing '%s'", path); - return error; - } - - if (do_fsync && (flags & O_CREAT)) - error = git_futils_fsync_parent(path); - - return error; -} - -int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) -{ - if (git_futils_mkpath2file(to, dirmode) < 0) - return -1; - - if (p_rename(from, to) < 0) { - git_error_set(GIT_ERROR_OS, "failed to rename '%s' to '%s'", from, to); - return -1; - } - - return 0; -} - -int git_futils_mmap_ro(git_map *out, git_file fd, off64_t begin, size_t len) -{ - return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin); -} - -int git_futils_mmap_ro_file(git_map *out, const char *path) -{ - git_file fd = git_futils_open_ro(path); - uint64_t len; - int result; - - if (fd < 0) - return fd; - - if ((result = git_futils_filesize(&len, fd)) < 0) - goto out; - - if (!git__is_sizet(len)) { - git_error_set(GIT_ERROR_OS, "file `%s` too large to mmap", path); - result = -1; - goto out; - } - - result = git_futils_mmap_ro(out, fd, 0, (size_t)len); -out: - p_close(fd); - return result; -} - -void git_futils_mmap_free(git_map *out) -{ - p_munmap(out); -} - -GIT_INLINE(int) mkdir_validate_dir( - const char *path, - struct stat *st, - mode_t mode, - uint32_t flags, - struct git_futils_mkdir_options *opts) -{ - /* with exclusive create, existing dir is an error */ - if ((flags & GIT_MKDIR_EXCL) != 0) { - git_error_set(GIT_ERROR_FILESYSTEM, - "failed to make directory '%s': directory exists", path); - return GIT_EEXISTS; - } - - if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) || - (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) { - if (p_unlink(path) < 0) { - git_error_set(GIT_ERROR_OS, "failed to remove %s '%s'", - S_ISLNK(st->st_mode) ? "symlink" : "file", path); - return GIT_EEXISTS; - } - - opts->perfdata.mkdir_calls++; - - if (p_mkdir(path, mode) < 0) { - git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path); - return GIT_EEXISTS; - } - } - - else if (S_ISLNK(st->st_mode)) { - /* Re-stat the target, make sure it's a directory */ - opts->perfdata.stat_calls++; - - if (p_stat(path, st) < 0) { - git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path); - return GIT_EEXISTS; - } - } - - else if (!S_ISDIR(st->st_mode)) { - git_error_set(GIT_ERROR_FILESYSTEM, - "failed to make directory '%s': directory exists", path); - return GIT_EEXISTS; - } - - return 0; -} - -GIT_INLINE(int) mkdir_validate_mode( - const char *path, - struct stat *st, - bool terminal_path, - mode_t mode, - uint32_t flags, - struct git_futils_mkdir_options *opts) -{ - if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) || - (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) { - - opts->perfdata.chmod_calls++; - - if (p_chmod(path, mode) < 0) { - git_error_set(GIT_ERROR_OS, "failed to set permissions on '%s'", path); - return -1; - } - } - - return 0; -} - -GIT_INLINE(int) mkdir_canonicalize( - git_str *path, - uint32_t flags) -{ - ssize_t root_len; - - if (path->size == 0) { - git_error_set(GIT_ERROR_OS, "attempt to create empty path"); - return -1; - } - - /* Trim trailing slashes (except the root) */ - if ((root_len = git_fs_path_root(path->ptr)) < 0) - root_len = 0; - else - root_len++; - - while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/') - path->ptr[--path->size] = '\0'; - - /* if we are not supposed to made the last element, truncate it */ - if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) { - git_fs_path_dirname_r(path, path->ptr); - flags |= GIT_MKDIR_SKIP_LAST; - } - if ((flags & GIT_MKDIR_SKIP_LAST) != 0) { - git_fs_path_dirname_r(path, path->ptr); - } - - /* We were either given the root path (or trimmed it to - * the root), we don't have anything to do. - */ - if (path->size <= (size_t)root_len) - git_str_clear(path); - - return 0; -} - -int git_futils_mkdir( - const char *path, - mode_t mode, - uint32_t flags) -{ - git_str make_path = GIT_STR_INIT, parent_path = GIT_STR_INIT; - const char *relative; - struct git_futils_mkdir_options opts = { 0 }; - struct stat st; - size_t depth = 0; - int len = 0, root_len, error; - - if ((error = git_str_puts(&make_path, path)) < 0 || - (error = mkdir_canonicalize(&make_path, flags)) < 0 || - (error = git_str_puts(&parent_path, make_path.ptr)) < 0 || - make_path.size == 0) - goto done; - - root_len = git_fs_path_root(make_path.ptr); - - /* find the first parent directory that exists. this will be used - * as the base to dirname_relative. - */ - for (relative = make_path.ptr; parent_path.size; ) { - error = p_lstat(parent_path.ptr, &st); - - if (error == 0) { - break; - } else if (errno != ENOENT) { - git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr); - error = -1; - goto done; - } - - depth++; - - /* examine the parent of the current path */ - if ((len = git_fs_path_dirname_r(&parent_path, parent_path.ptr)) < 0) { - error = len; - goto done; - } - - GIT_ASSERT(len); - - /* - * We've walked all the given path's parents and it's either relative - * (the parent is simply '.') or rooted (the length is less than or - * equal to length of the root path). The path may be less than the - * root path length on Windows, where `C:` == `C:/`. - */ - if ((len == 1 && parent_path.ptr[0] == '.') || - (len == 1 && parent_path.ptr[0] == '/') || - len <= root_len) { - relative = make_path.ptr; - break; - } - - relative = make_path.ptr + len + 1; - - /* not recursive? just make this directory relative to its parent. */ - if ((flags & GIT_MKDIR_PATH) == 0) - break; - } - - /* we found an item at the location we're trying to create, - * validate it. - */ - if (depth == 0) { - error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts); - - if (!error) - error = mkdir_validate_mode( - make_path.ptr, &st, true, mode, flags, &opts); - - goto done; - } - - /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when - * canonicalizing `make_path`. - */ - flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST); - - error = git_futils_mkdir_relative(relative, - parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts); - -done: - git_str_dispose(&make_path); - git_str_dispose(&parent_path); - return error; -} - -int git_futils_mkdir_r(const char *path, const mode_t mode) -{ - return git_futils_mkdir(path, mode, GIT_MKDIR_PATH); -} - -int git_futils_mkdir_relative( - const char *relative_path, - const char *base, - mode_t mode, - uint32_t flags, - struct git_futils_mkdir_options *opts) -{ - git_str make_path = GIT_STR_INIT; - ssize_t root = 0, min_root_len; - char lastch = '/', *tail; - struct stat st; - struct git_futils_mkdir_options empty_opts = {0}; - int error; - - if (!opts) - opts = &empty_opts; - - /* build path and find "root" where we should start calling mkdir */ - if (git_fs_path_join_unrooted(&make_path, relative_path, base, &root) < 0) - return -1; - - if ((error = mkdir_canonicalize(&make_path, flags)) < 0 || - make_path.size == 0) - goto done; - - /* if we are not supposed to make the whole path, reset root */ - if ((flags & GIT_MKDIR_PATH) == 0) - root = git_str_rfind(&make_path, '/'); - - /* advance root past drive name or network mount prefix */ - min_root_len = git_fs_path_root(make_path.ptr); - if (root < min_root_len) - root = min_root_len; - while (root >= 0 && make_path.ptr[root] == '/') - ++root; - - /* clip root to make_path length */ - if (root > (ssize_t)make_path.size) - root = (ssize_t)make_path.size; /* i.e. NUL byte of string */ - if (root < 0) - root = 0; - - /* walk down tail of path making each directory */ - for (tail = &make_path.ptr[root]; *tail; *tail = lastch) { - bool mkdir_attempted = false; - - /* advance tail to include next path component */ - while (*tail == '/') - tail++; - while (*tail && *tail != '/') - tail++; - - /* truncate path at next component */ - lastch = *tail; - *tail = '\0'; - st.st_mode = 0; - - if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr)) - continue; - - /* See what's going on with this path component */ - opts->perfdata.stat_calls++; - -retry_lstat: - if (p_lstat(make_path.ptr, &st) < 0) { - if (mkdir_attempted || errno != ENOENT) { - git_error_set(GIT_ERROR_OS, "cannot access component in path '%s'", make_path.ptr); - error = -1; - goto done; - } - - git_error_clear(); - opts->perfdata.mkdir_calls++; - mkdir_attempted = true; - if (p_mkdir(make_path.ptr, mode) < 0) { - if (errno == EEXIST) - goto retry_lstat; - git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", make_path.ptr); - error = -1; - goto done; - } - } else { - if ((error = mkdir_validate_dir( - make_path.ptr, &st, mode, flags, opts)) < 0) - goto done; - } - - /* chmod if requested and necessary */ - if ((error = mkdir_validate_mode( - make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0) - goto done; - - if (opts->dir_map && opts->pool) { - char *cache_path; - size_t alloc_size; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1); - cache_path = git_pool_malloc(opts->pool, alloc_size); - GIT_ERROR_CHECK_ALLOC(cache_path); - - memcpy(cache_path, make_path.ptr, make_path.size + 1); - - if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0) - goto done; - } - } - - error = 0; - - /* check that full path really is a directory if requested & needed */ - if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 && - lastch != '\0') { - opts->perfdata.stat_calls++; - - if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) { - git_error_set(GIT_ERROR_OS, "path is not a directory '%s'", - make_path.ptr); - error = GIT_ENOTFOUND; - } - } - -done: - git_str_dispose(&make_path); - return error; -} - -typedef struct { - const char *base; - size_t baselen; - uint32_t flags; - int depth; -} futils__rmdir_data; - -#define FUTILS_MAX_DEPTH 100 - -static int futils__error_cannot_rmdir(const char *path, const char *filemsg) -{ - if (filemsg) - git_error_set(GIT_ERROR_OS, "could not remove directory '%s': %s", - path, filemsg); - else - git_error_set(GIT_ERROR_OS, "could not remove directory '%s'", path); - - return -1; -} - -static int futils__rm_first_parent(git_str *path, const char *ceiling) -{ - int error = GIT_ENOTFOUND; - struct stat st; - - while (error == GIT_ENOTFOUND) { - git_str_rtruncate_at_char(path, '/'); - - if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0) - error = 0; - else if (p_lstat_posixly(path->ptr, &st) == 0) { - if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) - error = p_unlink(path->ptr); - else if (!S_ISDIR(st.st_mode)) - error = -1; /* fail to remove non-regular file */ - } else if (errno != ENOTDIR) - error = -1; - } - - if (error) - futils__error_cannot_rmdir(path->ptr, "cannot remove parent"); - - return error; -} - -static int futils__rmdir_recurs_foreach(void *opaque, git_str *path) -{ - int error = 0; - futils__rmdir_data *data = opaque; - struct stat st; - - if (data->depth > FUTILS_MAX_DEPTH) - error = futils__error_cannot_rmdir( - path->ptr, "directory nesting too deep"); - - else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) { - if (errno == ENOENT) - error = 0; - else if (errno == ENOTDIR) { - /* asked to remove a/b/c/d/e and a/b is a normal file */ - if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0) - error = futils__rm_first_parent(path, data->base); - else - futils__error_cannot_rmdir( - path->ptr, "parent is not directory"); - } - else - error = git_fs_path_set_error(errno, path->ptr, "rmdir"); - } - - else if (S_ISDIR(st.st_mode)) { - data->depth++; - - error = git_fs_path_direach(path, 0, futils__rmdir_recurs_foreach, data); - - data->depth--; - - if (error < 0) - return error; - - if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0) - return error; - - if ((error = p_rmdir(path->ptr)) < 0) { - if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 && - (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY)) - error = 0; - else - error = git_fs_path_set_error(errno, path->ptr, "rmdir"); - } - } - - else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) { - if (p_unlink(path->ptr) < 0) - error = git_fs_path_set_error(errno, path->ptr, "remove"); - } - - else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0) - error = futils__error_cannot_rmdir(path->ptr, "still present"); - - return error; -} - -static int futils__rmdir_empty_parent(void *opaque, const char *path) -{ - futils__rmdir_data *data = opaque; - int error = 0; - - if (strlen(path) <= data->baselen) - error = GIT_ITEROVER; - - else if (p_rmdir(path) < 0) { - int en = errno; - - if (en == ENOENT || en == ENOTDIR) { - /* do nothing */ - } else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 && - en == EBUSY) { - error = git_fs_path_set_error(errno, path, "rmdir"); - } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) { - error = GIT_ITEROVER; - } else { - error = git_fs_path_set_error(errno, path, "rmdir"); - } - } - - return error; -} - -int git_futils_rmdir_r( - const char *path, const char *base, uint32_t flags) -{ - int error; - git_str fullpath = GIT_STR_INIT; - futils__rmdir_data data; - - /* build path and find "root" where we should start calling mkdir */ - if (git_fs_path_join_unrooted(&fullpath, path, base, NULL) < 0) - return -1; - - memset(&data, 0, sizeof(data)); - data.base = base ? base : ""; - data.baselen = base ? strlen(base) : 0; - data.flags = flags; - - error = futils__rmdir_recurs_foreach(&data, &fullpath); - - /* remove now-empty parents if requested */ - if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0) - error = git_fs_path_walk_up( - &fullpath, base, futils__rmdir_empty_parent, &data); - - if (error == GIT_ITEROVER) { - git_error_clear(); - error = 0; - } - - git_str_dispose(&fullpath); - - return error; -} - -int git_futils_fake_symlink(const char *target, const char *path) -{ - int retcode = GIT_ERROR; - int fd = git_futils_creat_withpath(path, 0755, 0644); - if (fd >= 0) { - retcode = p_write(fd, target, strlen(target)); - p_close(fd); - } - return retcode; -} - -static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done) -{ - int error = 0; - char buffer[FILEIO_BUFSIZE]; - ssize_t len = 0; - - while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0) - /* p_write() does not have the same semantics as write(). It loops - * internally and will return 0 when it has completed writing. - */ - error = p_write(ofd, buffer, len); - - if (len < 0) { - git_error_set(GIT_ERROR_OS, "read error while copying file"); - error = (int)len; - } - - if (error < 0) - git_error_set(GIT_ERROR_OS, "write error while copying file"); - - if (close_fd_when_done) { - p_close(ifd); - p_close(ofd); - } - - return error; -} - -int git_futils_cp(const char *from, const char *to, mode_t filemode) -{ - int ifd, ofd; - - if ((ifd = git_futils_open_ro(from)) < 0) - return ifd; - - if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) { - p_close(ifd); - return git_fs_path_set_error(errno, to, "open for writing"); - } - - return cp_by_fd(ifd, ofd, true); -} - -int git_futils_touch(const char *path, time_t *when) -{ - struct p_timeval times[2]; - int ret; - - times[0].tv_sec = times[1].tv_sec = when ? *when : time(NULL); - times[0].tv_usec = times[1].tv_usec = 0; - - ret = p_utimes(path, times); - - return (ret < 0) ? git_fs_path_set_error(errno, path, "touch") : 0; -} - -static int cp_link(const char *from, const char *to, size_t link_size) -{ - int error = 0; - ssize_t read_len; - char *link_data; - size_t alloc_size; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1); - link_data = git__malloc(alloc_size); - GIT_ERROR_CHECK_ALLOC(link_data); - - read_len = p_readlink(from, link_data, link_size); - if (read_len != (ssize_t)link_size) { - git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", from); - error = -1; - } - else { - link_data[read_len] = '\0'; - - if (p_symlink(link_data, to) < 0) { - git_error_set(GIT_ERROR_OS, "could not symlink '%s' as '%s'", - link_data, to); - error = -1; - } - } - - git__free(link_data); - return error; -} - -typedef struct { - const char *to_root; - git_str to; - ssize_t from_prefix; - uint32_t flags; - uint32_t mkdir_flags; - mode_t dirmode; -} cp_r_info; - -#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10) - -static int _cp_r_mkdir(cp_r_info *info, git_str *from) -{ - int error = 0; - - /* create root directory the first time we need to create a directory */ - if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) { - error = git_futils_mkdir( - info->to_root, info->dirmode, - (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0); - - info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT; - } - - /* create directory with root as base to prevent excess chmods */ - if (!error) - error = git_futils_mkdir_relative( - from->ptr + info->from_prefix, info->to_root, - info->dirmode, info->mkdir_flags, NULL); - - return error; -} - -static int _cp_r_callback(void *ref, git_str *from) -{ - int error = 0; - cp_r_info *info = ref; - struct stat from_st, to_st; - bool exists = false; - - if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 && - from->ptr[git_fs_path_basename_offset(from)] == '.') - return 0; - - if ((error = git_str_joinpath( - &info->to, info->to_root, from->ptr + info->from_prefix)) < 0) - return error; - - if (!(error = git_fs_path_lstat(info->to.ptr, &to_st))) - exists = true; - else if (error != GIT_ENOTFOUND) - return error; - else { - git_error_clear(); - error = 0; - } - - if ((error = git_fs_path_lstat(from->ptr, &from_st)) < 0) - return error; - - if (S_ISDIR(from_st.st_mode)) { - mode_t oldmode = info->dirmode; - - /* if we are not chmod'ing, then overwrite dirmode */ - if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0) - info->dirmode = from_st.st_mode; - - /* make directory now if CREATE_EMPTY_DIRS is requested and needed */ - if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0) - error = _cp_r_mkdir(info, from); - - /* recurse onto target directory */ - if (!error && (!exists || S_ISDIR(to_st.st_mode))) - error = git_fs_path_direach(from, 0, _cp_r_callback, info); - - if (oldmode != 0) - info->dirmode = oldmode; - - return error; - } - - if (exists) { - if ((info->flags & GIT_CPDIR_OVERWRITE) == 0) - return 0; - - if (p_unlink(info->to.ptr) < 0) { - git_error_set(GIT_ERROR_OS, "cannot overwrite existing file '%s'", - info->to.ptr); - return GIT_EEXISTS; - } - } - - /* Done if this isn't a regular file or a symlink */ - if (!S_ISREG(from_st.st_mode) && - (!S_ISLNK(from_st.st_mode) || - (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0)) - return 0; - - /* Make container directory on demand if needed */ - if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 && - (error = _cp_r_mkdir(info, from)) < 0) - return error; - - /* make symlink or regular file */ - if (info->flags & GIT_CPDIR_LINK_FILES) { - if ((error = p_link(from->ptr, info->to.ptr)) < 0) - git_error_set(GIT_ERROR_OS, "failed to link '%s'", from->ptr); - } else if (S_ISLNK(from_st.st_mode)) { - error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); - } else { - mode_t usemode = from_st.st_mode; - - if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) - usemode = GIT_PERMS_FOR_WRITE(usemode); - - error = git_futils_cp(from->ptr, info->to.ptr, usemode); - } - - return error; -} - -int git_futils_cp_r( - const char *from, - const char *to, - uint32_t flags, - mode_t dirmode) -{ - int error; - git_str path = GIT_STR_INIT; - cp_r_info info; - - if (git_str_joinpath(&path, from, "") < 0) /* ensure trailing slash */ - return -1; - - memset(&info, 0, sizeof(info)); - info.to_root = to; - info.flags = flags; - info.dirmode = dirmode; - info.from_prefix = path.size; - git_str_init(&info.to, 0); - - /* precalculate mkdir flags */ - if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) { - /* if not creating empty dirs, then use mkdir to create the path on - * demand right before files are copied. - */ - info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST; - if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) - info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH; - } else { - /* otherwise, we will do simple mkdir as directories are encountered */ - info.mkdir_flags = - ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0; - } - - error = _cp_r_callback(&info, &path); - - git_str_dispose(&path); - git_str_dispose(&info.to); - - return error; -} - -int git_futils_filestamp_check( - git_futils_filestamp *stamp, const char *path) -{ - struct stat st; - - /* if the stamp is NULL, then always reload */ - if (stamp == NULL) - return 1; - - if (p_stat(path, &st) < 0) - return GIT_ENOTFOUND; - - if (stamp->mtime.tv_sec == st.st_mtime && -#if defined(GIT_USE_NSEC) - stamp->mtime.tv_nsec == st.st_mtime_nsec && -#endif - stamp->size == (uint64_t)st.st_size && - stamp->ino == (unsigned int)st.st_ino) - return 0; - - stamp->mtime.tv_sec = st.st_mtime; -#if defined(GIT_USE_NSEC) - stamp->mtime.tv_nsec = st.st_mtime_nsec; -#endif - stamp->size = (uint64_t)st.st_size; - stamp->ino = (unsigned int)st.st_ino; - - return 1; -} - -void git_futils_filestamp_set( - git_futils_filestamp *target, const git_futils_filestamp *source) -{ - if (source) - memcpy(target, source, sizeof(*target)); - else - memset(target, 0, sizeof(*target)); -} - - -void git_futils_filestamp_set_from_stat( - git_futils_filestamp *stamp, struct stat *st) -{ - if (st) { - stamp->mtime.tv_sec = st->st_mtime; -#if defined(GIT_USE_NSEC) - stamp->mtime.tv_nsec = st->st_mtime_nsec; -#else - stamp->mtime.tv_nsec = 0; -#endif - stamp->size = (uint64_t)st->st_size; - stamp->ino = (unsigned int)st->st_ino; - } else { - memset(stamp, 0, sizeof(*stamp)); - } -} - -int git_futils_fsync_dir(const char *path) -{ -#ifdef GIT_WIN32 - GIT_UNUSED(path); - return 0; -#else - int fd, error = -1; - - if ((fd = p_open(path, O_RDONLY)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to open directory '%s' for fsync", path); - return -1; - } - - if ((error = p_fsync(fd)) < 0) - git_error_set(GIT_ERROR_OS, "failed to fsync directory '%s'", path); - - p_close(fd); - return error; -#endif -} - -int git_futils_fsync_parent(const char *path) -{ - char *parent; - int error; - - if ((parent = git_fs_path_dirname(path)) == NULL) - return -1; - - error = git_futils_fsync_dir(parent); - git__free(parent); - return error; -} diff --git a/src/libgit2/futils.h b/src/libgit2/futils.h deleted file mode 100644 index a82ec41cc..000000000 --- a/src/libgit2/futils.h +++ /dev/null @@ -1,402 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_futils_h__ -#define INCLUDE_futils_h__ - -#include "common.h" - -#include "map.h" -#include "posix.h" -#include "fs_path.h" -#include "pool.h" -#include "strmap.h" -#include "hash.h" - -/** - * Filebuffer methods - * - * Read whole files into an in-memory buffer for processing - */ -extern int git_futils_readbuffer(git_str *obj, const char *path); -extern int git_futils_readbuffer_updated( - git_str *obj, - const char *path, - unsigned char checksum[GIT_HASH_SHA1_SIZE], - int *updated); -extern int git_futils_readbuffer_fd(git_str *obj, git_file fd, size_t len); - -/* Additional constants for `git_futils_writebuffer`'s `open_flags`. We - * support these internally and they will be removed before the `open` call. - */ -#ifndef O_FSYNC -# define O_FSYNC (1 << 31) -#endif - -extern int git_futils_writebuffer( - const git_str *buf, const char *path, int open_flags, mode_t mode); - -/** - * File utils - * - * These are custom filesystem-related helper methods. They are - * rather high level, and wrap the underlying POSIX methods - * - * All these methods return 0 on success, - * or an error code on failure and an error message is set. - */ - -/** - * Create and open a file, while also - * creating all the folders in its path - */ -extern int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode); - -/** - * Create and open a process-locked file - */ -extern int git_futils_creat_locked(const char *path, const mode_t mode); - -/** - * Create and open a process-locked file, while - * also creating all the folders in its path - */ -extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode); - -/** - * Create a path recursively. - */ -extern int git_futils_mkdir_r(const char *path, const mode_t mode); - -/** - * Flags to pass to `git_futils_mkdir`. - * - * * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists. - * * GIT_MKDIR_PATH says to make all components in the path. - * * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation - * * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path - * * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path - * * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path - * * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST - * * GIT_MKDIR_REMOVE_FILES says to remove files and recreate dirs - * * GIT_MKDIR_REMOVE_SYMLINKS says to remove symlinks and recreate dirs - * - * Note that the chmod options will be executed even if the directory already - * exists, unless GIT_MKDIR_EXCL is given. - */ -typedef enum { - GIT_MKDIR_EXCL = 1, - GIT_MKDIR_PATH = 2, - GIT_MKDIR_CHMOD = 4, - GIT_MKDIR_CHMOD_PATH = 8, - GIT_MKDIR_SKIP_LAST = 16, - GIT_MKDIR_SKIP_LAST2 = 32, - GIT_MKDIR_VERIFY_DIR = 64, - GIT_MKDIR_REMOVE_FILES = 128, - GIT_MKDIR_REMOVE_SYMLINKS = 256 -} git_futils_mkdir_flags; - -struct git_futils_mkdir_perfdata -{ - size_t stat_calls; - size_t mkdir_calls; - size_t chmod_calls; -}; - -struct git_futils_mkdir_options -{ - git_strmap *dir_map; - git_pool *pool; - struct git_futils_mkdir_perfdata perfdata; -}; - -/** - * Create a directory or entire path. - * - * This makes a directory (and the entire path leading up to it if requested), - * and optionally chmods the directory immediately after (or each part of the - * path if requested). - * - * @param path The path to create, relative to base. - * @param base Root for relative path. These directories will never be made. - * @param mode The mode to use for created directories. - * @param flags Combination of the mkdir flags above. - * @param opts Extended options, or null. - * @return 0 on success, else error code - */ -extern int git_futils_mkdir_relative(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts); - -/** - * Create a directory or entire path. Similar to `git_futils_mkdir_relative` - * without performance data. - */ -extern int git_futils_mkdir(const char *path, mode_t mode, uint32_t flags); - -/** - * Create all the folders required to contain - * the full path of a file - */ -extern int git_futils_mkpath2file(const char *path, const mode_t mode); - -/** - * Flags to pass to `git_futils_rmdir_r`. - * - * * GIT_RMDIR_EMPTY_HIERARCHY - the default; remove hierarchy of empty - * dirs and generate error if any files are found. - * * GIT_RMDIR_REMOVE_FILES - attempt to remove files in the hierarchy. - * * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error. - * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base - * if removing this item leaves them empty - * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR - * * GIT_RMDIR_SKIP_ROOT - don't remove root directory itself - */ -typedef enum { - GIT_RMDIR_EMPTY_HIERARCHY = 0, - GIT_RMDIR_REMOVE_FILES = (1 << 0), - GIT_RMDIR_SKIP_NONEMPTY = (1 << 1), - GIT_RMDIR_EMPTY_PARENTS = (1 << 2), - GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3), - GIT_RMDIR_SKIP_ROOT = (1 << 4) -} git_futils_rmdir_flags; - -/** - * Remove path and any files and directories beneath it. - * - * @param path Path to the top level directory to process. - * @param base Root for relative path. - * @param flags Combination of git_futils_rmdir_flags values - * @return 0 on success; -1 on error. - */ -extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags); - -/** - * Create and open a temporary file with a `_git2_` suffix in a - * protected directory; the file created will created will honor - * the current `umask`. Writes the filename into path_out. - * - * This function uses a high-quality PRNG seeded by the system's - * entropy pool _where available_ and falls back to a simple seed - * (time plus system information) when not. This is suitable for - * writing within a protected directory, but the system's safe - * temporary file creation functions should be preferred where - * available when writing into world-writable (temp) directories. - * - * @return On success, an open file descriptor, else an error code < 0. - */ -extern int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode); - -/** - * Move a file on the filesystem, create the - * destination path if it doesn't exist - */ -extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode); - -/** - * Copy a file - * - * The filemode will be used for the newly created file. - */ -extern int git_futils_cp( - const char *from, - const char *to, - mode_t filemode); - -/** - * Set the files atime and mtime to the given time, or the current time - * if `ts` is NULL. - */ -extern int git_futils_touch(const char *path, time_t *when); - -/** - * Flags that can be passed to `git_futils_cp_r`. - * - * - GIT_CPDIR_CREATE_EMPTY_DIRS: create directories even if there are no - * files under them (otherwise directories will only be created lazily - * when a file inside them is copied). - * - GIT_CPDIR_COPY_SYMLINKS: copy symlinks, otherwise they are ignored. - * - GIT_CPDIR_COPY_DOTFILES: copy files with leading '.', otherwise ignored. - * - GIT_CPDIR_OVERWRITE: overwrite pre-existing files with source content, - * otherwise they are silently skipped. - * - GIT_CPDIR_CHMOD_DIRS: explicitly chmod directories to `dirmode` - * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the - * source file to the target; with this flag, always use 0666 (or 0777 if - * source has exec bits set) for target. - * - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files - */ -typedef enum { - GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0), - GIT_CPDIR_COPY_SYMLINKS = (1u << 1), - GIT_CPDIR_COPY_DOTFILES = (1u << 2), - GIT_CPDIR_OVERWRITE = (1u << 3), - GIT_CPDIR_CHMOD_DIRS = (1u << 4), - GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5), - GIT_CPDIR_LINK_FILES = (1u << 6) -} git_futils_cpdir_flags; - -/** - * Copy a directory tree. - * - * This copies directories and files from one root to another. You can - * pass a combination of GIT_CPDIR flags as defined above. - * - * If you pass the CHMOD flag, then the dirmode will be applied to all - * directories that are created during the copy, overriding the natural - * permissions. If you do not pass the CHMOD flag, then the dirmode - * will actually be copied from the source files and the `dirmode` arg - * will be ignored. - */ -extern int git_futils_cp_r( - const char *from, - const char *to, - uint32_t flags, - mode_t dirmode); - -/** - * Open a file readonly and set error if needed. - */ -extern int git_futils_open_ro(const char *path); - -/** - * Truncate a file, creating it if it doesn't exist. - */ -extern int git_futils_truncate(const char *path, int mode); - -/** - * Get the filesize in bytes of a file - */ -extern int git_futils_filesize(uint64_t *out, git_file fd); - -#define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0100) != 0) -#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644) -#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0777 : 0666) - -#define GIT_MODE_PERMS_MASK 0777 -#define GIT_MODE_TYPE_MASK 0170000 -#define GIT_MODE_TYPE(MODE) ((MODE) & GIT_MODE_TYPE_MASK) -#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB)) - -/** - * Convert a mode_t from the OS to a legal git mode_t value. - */ -extern mode_t git_futils_canonical_mode(mode_t raw_mode); - - -/** - * Read-only map all or part of a file into memory. - * When possible this function should favor a virtual memory - * style mapping over some form of malloc()+read(), as the - * data access will be random and is not likely to touch the - * majority of the region requested. - * - * @param out buffer to populate with the mapping information. - * @param fd open descriptor to configure the mapping from. - * @param begin first byte to map, this should be page aligned. - * @param len number of bytes to map. - * @return - * - 0 on success; - * - -1 on error. - */ -extern int git_futils_mmap_ro( - git_map *out, - git_file fd, - off64_t begin, - size_t len); - -/** - * Read-only map an entire file. - * - * @param out buffer to populate with the mapping information. - * @param path path to file to be opened. - * @return - * - 0 on success; - * - GIT_ENOTFOUND if not found; - * - -1 on an unspecified OS related error. - */ -extern int git_futils_mmap_ro_file( - git_map *out, - const char *path); - -/** - * Release the memory associated with a previous memory mapping. - * @param map the mapping description previously configured. - */ -extern void git_futils_mmap_free(git_map *map); - -/** - * Create a "fake" symlink (text file containing the target path). - * - * @param target original symlink target - * @param path symlink file to be created - * @return 0 on success, -1 on error - */ -extern int git_futils_fake_symlink(const char *target, const char *path); - -/** - * A file stamp represents a snapshot of information about a file that can - * be used to test if the file changes. This portable implementation is - * based on stat data about that file, but it is possible that OS specific - * versions could be implemented in the future. - */ -typedef struct { - struct timespec mtime; - uint64_t size; - unsigned int ino; -} git_futils_filestamp; - -/** - * Compare stat information for file with reference info. - * - * This function updates the file stamp to current data for the given path - * and returns 0 if the file is up-to-date relative to the prior setting, - * 1 if the file has been changed, or GIT_ENOTFOUND if the file doesn't - * exist. This will not call git_error_set, so you must set the error if you - * plan to return an error. - * - * @param stamp File stamp to be checked - * @param path Path to stat and check if changed - * @return 0 if up-to-date, 1 if out-of-date, GIT_ENOTFOUND if cannot stat - */ -extern int git_futils_filestamp_check( - git_futils_filestamp *stamp, const char *path); - -/** - * Set or reset file stamp data - * - * This writes the target file stamp. If the source is NULL, this will set - * the target stamp to values that will definitely be out of date. If the - * source is not NULL, this copies the source values to the target. - * - * @param tgt File stamp to write to - * @param src File stamp to copy from or NULL to clear the target - */ -extern void git_futils_filestamp_set( - git_futils_filestamp *tgt, const git_futils_filestamp *src); - -/** - * Set file stamp data from stat structure - */ -extern void git_futils_filestamp_set_from_stat( - git_futils_filestamp *stamp, struct stat *st); - -/** - * `fsync` the parent directory of the given path, if `fsync` is - * supported for directories on this platform. - * - * @param path Path of the directory to sync. - * @return 0 on success, -1 on error - */ -extern int git_futils_fsync_dir(const char *path); - -/** - * `fsync` the parent directory of the given path, if `fsync` is - * supported for directories on this platform. - * - * @param path Path of the file whose parent directory should be synced. - * @return 0 on success, -1 on error - */ -extern int git_futils_fsync_parent(const char *path); - -#endif diff --git a/src/libgit2/git2.rc b/src/libgit2/git2.rc new file mode 100644 index 000000000..d273afd70 --- /dev/null +++ b/src/libgit2/git2.rc @@ -0,0 +1,59 @@ +#include +#include "../../include/git2/version.h" + +#ifndef LIBGIT2_FILENAME +# ifdef __GNUC__ +# define LIBGIT2_FILENAME git2 +# else +# define LIBGIT2_FILENAME "git2" +# endif +#endif + +#ifndef LIBGIT2_COMMENTS +# define LIBGIT2_COMMENTS "For more information visit http://libgit2.github.com/" +#endif + +#ifdef __GNUC__ +# define _STR(x) #x +# define STR(x) _STR(x) +#else +# define STR(x) x +#endif + +#ifdef __GNUC__ +VS_VERSION_INFO VERSIONINFO +#else +VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE +#endif + FILEVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH + PRODUCTVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0 +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + BEGIN + VALUE "FileDescription", "libgit2 - the Git linkable library\0" + VALUE "FileVersion", LIBGIT2_VERSION "\0" + VALUE "InternalName", STR(LIBGIT2_FILENAME) ".dll\0" + VALUE "LegalCopyright", "Copyright (C) the libgit2 contributors. All rights reserved.\0" + VALUE "OriginalFilename", STR(LIBGIT2_FILENAME) ".dll\0" + VALUE "ProductName", "libgit2\0" + VALUE "ProductVersion", LIBGIT2_VERSION "\0" + VALUE "Comments", LIBGIT2_COMMENTS "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END diff --git a/src/libgit2/hash.c b/src/libgit2/hash.c deleted file mode 100644 index 98ceb05d2..000000000 --- a/src/libgit2/hash.c +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "hash.h" - -int git_hash_global_init(void) -{ - return git_hash_sha1_global_init(); -} - -int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm) -{ - int error; - - switch (algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - error = git_hash_sha1_ctx_init(&ctx->ctx.sha1); - break; - default: - git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); - error = -1; - } - - ctx->algorithm = algorithm; - return error; -} - -void git_hash_ctx_cleanup(git_hash_ctx *ctx) -{ - switch (ctx->algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - git_hash_sha1_ctx_cleanup(&ctx->ctx.sha1); - return; - default: - /* unreachable */ ; - } -} - -int git_hash_init(git_hash_ctx *ctx) -{ - switch (ctx->algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - return git_hash_sha1_init(&ctx->ctx.sha1); - default: - /* unreachable */ ; - } - - git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); - return -1; -} - -int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) -{ - switch (ctx->algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - return git_hash_sha1_update(&ctx->ctx.sha1, data, len); - default: - /* unreachable */ ; - } - - git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); - return -1; -} - -int git_hash_final(unsigned char *out, git_hash_ctx *ctx) -{ - switch (ctx->algorithm) { - case GIT_HASH_ALGORITHM_SHA1: - return git_hash_sha1_final(out, &ctx->ctx.sha1); - default: - /* unreachable */ ; - } - - git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); - return -1; -} - -int git_hash_buf( - unsigned char *out, - const void *data, - size_t len, - git_hash_algorithm_t algorithm) -{ - git_hash_ctx ctx; - int error = 0; - - if (git_hash_ctx_init(&ctx, algorithm) < 0) - return -1; - - if ((error = git_hash_update(&ctx, data, len)) >= 0) - error = git_hash_final(out, &ctx); - - git_hash_ctx_cleanup(&ctx); - - return error; -} - -int git_hash_vec( - unsigned char *out, - git_str_vec *vec, - size_t n, - git_hash_algorithm_t algorithm) -{ - git_hash_ctx ctx; - size_t i; - int error = 0; - - if (git_hash_ctx_init(&ctx, algorithm) < 0) - return -1; - - for (i = 0; i < n; i++) { - if ((error = git_hash_update(&ctx, vec[i].data, vec[i].len)) < 0) - goto done; - } - - error = git_hash_final(out, &ctx); - -done: - git_hash_ctx_cleanup(&ctx); - - return error; -} - -int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len) -{ - static char hex[] = "0123456789abcdef"; - char *str = out; - size_t i; - - for (i = 0; i < hash_len; i++) { - *str++ = hex[hash[i] >> 4]; - *str++ = hex[hash[i] & 0x0f]; - } - - *str++ = '\0'; - - return 0; -} diff --git a/src/libgit2/hash.h b/src/libgit2/hash.h deleted file mode 100644 index 507c1cb25..000000000 --- a/src/libgit2/hash.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_h__ -#define INCLUDE_hash_h__ - -#include "common.h" - -#include "hash/sha1.h" - -typedef struct { - void *data; - size_t len; -} git_str_vec; - -typedef enum { - GIT_HASH_ALGORITHM_NONE = 0, - GIT_HASH_ALGORITHM_SHA1 -} git_hash_algorithm_t; - -typedef struct git_hash_ctx { - union { - git_hash_sha1_ctx sha1; - } ctx; - git_hash_algorithm_t algorithm; -} git_hash_ctx; - -int git_hash_global_init(void); - -int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm); -void git_hash_ctx_cleanup(git_hash_ctx *ctx); - -int git_hash_init(git_hash_ctx *c); -int git_hash_update(git_hash_ctx *c, const void *data, size_t len); -int git_hash_final(unsigned char *out, git_hash_ctx *c); - -int git_hash_buf(unsigned char *out, const void *data, size_t len, git_hash_algorithm_t algorithm); -int git_hash_vec(unsigned char *out, git_str_vec *vec, size_t n, git_hash_algorithm_t algorithm); - -int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len); - -#endif diff --git a/src/libgit2/hash/sha1.h b/src/libgit2/hash/sha1.h deleted file mode 100644 index 4b4dae3f8..000000000 --- a/src/libgit2/hash/sha1.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_h__ -#define INCLUDE_hash_sha1_h__ - -#include "common.h" - -typedef struct git_hash_sha1_ctx git_hash_sha1_ctx; - -#if defined(GIT_SHA1_COLLISIONDETECT) -# include "sha1/collisiondetect.h" -#elif defined(GIT_SHA1_COMMON_CRYPTO) -# include "sha1/common_crypto.h" -#elif defined(GIT_SHA1_OPENSSL) -# include "sha1/openssl.h" -#elif defined(GIT_SHA1_WIN32) -# include "sha1/win32.h" -#elif defined(GIT_SHA1_MBEDTLS) -# include "sha1/mbedtls.h" -#else -# include "sha1/generic.h" -#endif - -#define GIT_HASH_SHA1_SIZE 20 - -int git_hash_sha1_global_init(void); - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx); -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx); - -int git_hash_sha1_init(git_hash_sha1_ctx *c); -int git_hash_sha1_update(git_hash_sha1_ctx *c, const void *data, size_t len); -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *c); - -#endif diff --git a/src/libgit2/hash/sha1/collisiondetect.c b/src/libgit2/hash/sha1/collisiondetect.c deleted file mode 100644 index ec7059c4c..000000000 --- a/src/libgit2/hash/sha1/collisiondetect.c +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "collisiondetect.h" - -int git_hash_sha1_global_init(void) -{ - return 0; -} - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) -{ - return git_hash_sha1_init(ctx); -} - -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) -{ - GIT_UNUSED(ctx); -} - -int git_hash_sha1_init(git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - SHA1DCInit(&ctx->c); - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - GIT_ASSERT_ARG(ctx); - SHA1DCUpdate(&ctx->c, data, len); - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - if (SHA1DCFinal(out, &ctx->c)) { - git_error_set(GIT_ERROR_SHA1, "SHA1 collision attack detected"); - return -1; - } - - return 0; -} diff --git a/src/libgit2/hash/sha1/collisiondetect.h b/src/libgit2/hash/sha1/collisiondetect.h deleted file mode 100644 index eb88e86c1..000000000 --- a/src/libgit2/hash/sha1/collisiondetect.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_collisiondetect_h__ -#define INCLUDE_hash_sha1_collisiondetect_h__ - -#include "hash/sha1.h" - -#include "sha1dc/sha1.h" - -struct git_hash_sha1_ctx { - SHA1_CTX c; -}; - -#endif diff --git a/src/libgit2/hash/sha1/common_crypto.c b/src/libgit2/hash/sha1/common_crypto.c deleted file mode 100644 index 9d608f449..000000000 --- a/src/libgit2/hash/sha1/common_crypto.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common_crypto.h" - -#define CC_LONG_MAX ((CC_LONG)-1) - -int git_hash_sha1_global_init(void) -{ - return 0; -} - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) -{ - return git_hash_sha1_init(ctx); -} - -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) -{ - GIT_UNUSED(ctx); -} - -int git_hash_sha1_init(git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - CC_SHA1_Init(&ctx->c); - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) -{ - const unsigned char *data = _data; - - GIT_ASSERT_ARG(ctx); - - while (len > 0) { - CC_LONG chunk = (len > CC_LONG_MAX) ? CC_LONG_MAX : (CC_LONG)len; - - CC_SHA1_Update(&ctx->c, data, chunk); - - data += chunk; - len -= chunk; - } - - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - CC_SHA1_Final(out, &ctx->c); - return 0; -} diff --git a/src/libgit2/hash/sha1/common_crypto.h b/src/libgit2/hash/sha1/common_crypto.h deleted file mode 100644 index a5fcfb33e..000000000 --- a/src/libgit2/hash/sha1/common_crypto.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_common_crypto_h__ -#define INCLUDE_hash_sha1_common_crypto_h__ - -#include "hash/sha1.h" - -#include - -struct git_hash_sha1_ctx { - CC_SHA1_CTX c; -}; - -#endif diff --git a/src/libgit2/hash/sha1/generic.c b/src/libgit2/hash/sha1/generic.c deleted file mode 100644 index 85b34c578..000000000 --- a/src/libgit2/hash/sha1/generic.c +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "generic.h" - -#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) - -/* - * Force usage of rol or ror by selecting the one with the smaller constant. - * It _can_ generate slightly smaller code (a constant of 1 is special), but - * perhaps more importantly it's possibly faster on any uarch that does a - * rotate with a loop. - */ - -#define SHA_ASM(op, x, n) (__extension__ ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; })) -#define SHA_ROL(x,n) SHA_ASM("rol", x, n) -#define SHA_ROR(x,n) SHA_ASM("ror", x, n) - -#else - -#define SHA_ROT(X,l,r) (((X) << (l)) | ((X) >> (r))) -#define SHA_ROL(X,n) SHA_ROT(X,n,32-(n)) -#define SHA_ROR(X,n) SHA_ROT(X,32-(n),n) - -#endif - -/* - * If you have 32 registers or more, the compiler can (and should) - * try to change the array[] accesses into registers. However, on - * machines with less than ~25 registers, that won't really work, - * and at least gcc will make an unholy mess of it. - * - * So to avoid that mess which just slows things down, we force - * the stores to memory to actually happen (we might be better off - * with a 'W(t)=(val);asm("":"+m" (W(t))' there instead, as - * suggested by Artur Skawina - that will also make gcc unable to - * try to do the silly "optimize away loads" part because it won't - * see what the value will be). - * - * Ben Herrenschmidt reports that on PPC, the C version comes close - * to the optimized asm with this (ie on PPC you don't want that - * 'volatile', since there are lots of registers). - * - * On ARM we get the best code generation by forcing a full memory barrier - * between each SHA_ROUND, otherwise gcc happily get wild with spilling and - * the stack frame size simply explode and performance goes down the drain. - */ - -#if defined(__i386__) || defined(__x86_64__) - #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val)) -#elif defined(__GNUC__) && defined(__arm__) - #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0) -#else - #define setW(x, val) (W(x) = (val)) -#endif - -/* - * Performance might be improved if the CPU architecture is OK with - * unaligned 32-bit loads and a fast ntohl() is available. - * Otherwise fall back to byte loads and shifts which is portable, - * and is faster on architectures with memory alignment issues. - */ - -#if defined(__i386__) || defined(__x86_64__) || \ - defined(_M_IX86) || defined(_M_X64) || \ - defined(__ppc__) || defined(__ppc64__) || \ - defined(__powerpc__) || defined(__powerpc64__) || \ - defined(__s390__) || defined(__s390x__) - -#define get_be32(p) ntohl(*(const unsigned int *)(p)) -#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0) - -#else - -#define get_be32(p) ( \ - (*((const unsigned char *)(p) + 0) << 24) | \ - (*((const unsigned char *)(p) + 1) << 16) | \ - (*((const unsigned char *)(p) + 2) << 8) | \ - (*((const unsigned char *)(p) + 3) << 0) ) -#define put_be32(p, v) do { \ - unsigned int __v = (v); \ - *((unsigned char *)(p) + 0) = __v >> 24; \ - *((unsigned char *)(p) + 1) = __v >> 16; \ - *((unsigned char *)(p) + 2) = __v >> 8; \ - *((unsigned char *)(p) + 3) = __v >> 0; } while (0) - -#endif - -/* This "rolls" over the 512-bit array */ -#define W(x) (array[(x)&15]) - -/* - * Where do we get the source from? The first 16 iterations get it from - * the input data, the next mix it from the 512-bit array. - */ -#define SHA_SRC(t) get_be32(data + t) -#define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1) - -#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \ - unsigned int TEMP = input(t); setW(t, TEMP); \ - E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \ - B = SHA_ROR(B, 2); } while (0) - -#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) -#define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) -#define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E ) -#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E ) -#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E ) - -static void hash__block(git_hash_sha1_ctx *ctx, const unsigned int *data) -{ - unsigned int A,B,C,D,E; - unsigned int array[16]; - - A = ctx->H[0]; - B = ctx->H[1]; - C = ctx->H[2]; - D = ctx->H[3]; - E = ctx->H[4]; - - /* Round 1 - iterations 0-16 take their input from 'data' */ - T_0_15( 0, A, B, C, D, E); - T_0_15( 1, E, A, B, C, D); - T_0_15( 2, D, E, A, B, C); - T_0_15( 3, C, D, E, A, B); - T_0_15( 4, B, C, D, E, A); - T_0_15( 5, A, B, C, D, E); - T_0_15( 6, E, A, B, C, D); - T_0_15( 7, D, E, A, B, C); - T_0_15( 8, C, D, E, A, B); - T_0_15( 9, B, C, D, E, A); - T_0_15(10, A, B, C, D, E); - T_0_15(11, E, A, B, C, D); - T_0_15(12, D, E, A, B, C); - T_0_15(13, C, D, E, A, B); - T_0_15(14, B, C, D, E, A); - T_0_15(15, A, B, C, D, E); - - /* Round 1 - tail. Input from 512-bit mixing array */ - T_16_19(16, E, A, B, C, D); - T_16_19(17, D, E, A, B, C); - T_16_19(18, C, D, E, A, B); - T_16_19(19, B, C, D, E, A); - - /* Round 2 */ - T_20_39(20, A, B, C, D, E); - T_20_39(21, E, A, B, C, D); - T_20_39(22, D, E, A, B, C); - T_20_39(23, C, D, E, A, B); - T_20_39(24, B, C, D, E, A); - T_20_39(25, A, B, C, D, E); - T_20_39(26, E, A, B, C, D); - T_20_39(27, D, E, A, B, C); - T_20_39(28, C, D, E, A, B); - T_20_39(29, B, C, D, E, A); - T_20_39(30, A, B, C, D, E); - T_20_39(31, E, A, B, C, D); - T_20_39(32, D, E, A, B, C); - T_20_39(33, C, D, E, A, B); - T_20_39(34, B, C, D, E, A); - T_20_39(35, A, B, C, D, E); - T_20_39(36, E, A, B, C, D); - T_20_39(37, D, E, A, B, C); - T_20_39(38, C, D, E, A, B); - T_20_39(39, B, C, D, E, A); - - /* Round 3 */ - T_40_59(40, A, B, C, D, E); - T_40_59(41, E, A, B, C, D); - T_40_59(42, D, E, A, B, C); - T_40_59(43, C, D, E, A, B); - T_40_59(44, B, C, D, E, A); - T_40_59(45, A, B, C, D, E); - T_40_59(46, E, A, B, C, D); - T_40_59(47, D, E, A, B, C); - T_40_59(48, C, D, E, A, B); - T_40_59(49, B, C, D, E, A); - T_40_59(50, A, B, C, D, E); - T_40_59(51, E, A, B, C, D); - T_40_59(52, D, E, A, B, C); - T_40_59(53, C, D, E, A, B); - T_40_59(54, B, C, D, E, A); - T_40_59(55, A, B, C, D, E); - T_40_59(56, E, A, B, C, D); - T_40_59(57, D, E, A, B, C); - T_40_59(58, C, D, E, A, B); - T_40_59(59, B, C, D, E, A); - - /* Round 4 */ - T_60_79(60, A, B, C, D, E); - T_60_79(61, E, A, B, C, D); - T_60_79(62, D, E, A, B, C); - T_60_79(63, C, D, E, A, B); - T_60_79(64, B, C, D, E, A); - T_60_79(65, A, B, C, D, E); - T_60_79(66, E, A, B, C, D); - T_60_79(67, D, E, A, B, C); - T_60_79(68, C, D, E, A, B); - T_60_79(69, B, C, D, E, A); - T_60_79(70, A, B, C, D, E); - T_60_79(71, E, A, B, C, D); - T_60_79(72, D, E, A, B, C); - T_60_79(73, C, D, E, A, B); - T_60_79(74, B, C, D, E, A); - T_60_79(75, A, B, C, D, E); - T_60_79(76, E, A, B, C, D); - T_60_79(77, D, E, A, B, C); - T_60_79(78, C, D, E, A, B); - T_60_79(79, B, C, D, E, A); - - ctx->H[0] += A; - ctx->H[1] += B; - ctx->H[2] += C; - ctx->H[3] += D; - ctx->H[4] += E; -} - -int git_hash_sha1_global_init(void) -{ - return 0; -} - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) -{ - return git_hash_sha1_init(ctx); -} - -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) -{ - GIT_UNUSED(ctx); -} - -int git_hash_sha1_init(git_hash_sha1_ctx *ctx) -{ - ctx->size = 0; - - /* Initialize H with the magic constants (see FIPS180 for constants) */ - ctx->H[0] = 0x67452301; - ctx->H[1] = 0xefcdab89; - ctx->H[2] = 0x98badcfe; - ctx->H[3] = 0x10325476; - ctx->H[4] = 0xc3d2e1f0; - - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - unsigned int lenW = ctx->size & 63; - - ctx->size += len; - - /* Read the data into W and process blocks as they get full */ - if (lenW) { - unsigned int left = 64 - lenW; - if (len < left) - left = (unsigned int)len; - memcpy(lenW + (char *)ctx->W, data, left); - lenW = (lenW + left) & 63; - len -= left; - data = ((const char *)data + left); - if (lenW) - return 0; - hash__block(ctx, ctx->W); - } - while (len >= 64) { - hash__block(ctx, data); - data = ((const char *)data + 64); - len -= 64; - } - if (len) - memcpy(ctx->W, data, len); - - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - static const unsigned char pad[64] = { 0x80 }; - unsigned int padlen[2]; - int i; - - /* Pad with a binary 1 (ie 0x80), then zeroes, then length */ - padlen[0] = htonl((uint32_t)(ctx->size >> 29)); - padlen[1] = htonl((uint32_t)(ctx->size << 3)); - - i = ctx->size & 63; - git_hash_sha1_update(ctx, pad, 1+ (63 & (55 - i))); - git_hash_sha1_update(ctx, padlen, 8); - - /* Output hash */ - for (i = 0; i < 5; i++) - put_be32(out + i*4, ctx->H[i]); - - return 0; -} diff --git a/src/libgit2/hash/sha1/generic.h b/src/libgit2/hash/sha1/generic.h deleted file mode 100644 index 53fc0823e..000000000 --- a/src/libgit2/hash/sha1/generic.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_generic_h__ -#define INCLUDE_hash_sha1_generic_h__ - -#include "hash/sha1.h" - -struct git_hash_sha1_ctx { - uint64_t size; - unsigned int H[5]; - unsigned int W[16]; -}; - -#endif diff --git a/src/libgit2/hash/sha1/mbedtls.c b/src/libgit2/hash/sha1/mbedtls.c deleted file mode 100644 index 56016bec8..000000000 --- a/src/libgit2/hash/sha1/mbedtls.c +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "mbedtls.h" - -int git_hash_sha1_global_init(void) -{ - return 0; -} - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) -{ - return git_hash_sha1_init(ctx); -} - -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) -{ - if (ctx) - mbedtls_sha1_free(&ctx->c); -} - -int git_hash_sha1_init(git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - mbedtls_sha1_init(&ctx->c); - mbedtls_sha1_starts(&ctx->c); - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - GIT_ASSERT_ARG(ctx); - mbedtls_sha1_update(&ctx->c, data, len); - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - mbedtls_sha1_finish(&ctx->c, out); - return 0; -} diff --git a/src/libgit2/hash/sha1/mbedtls.h b/src/libgit2/hash/sha1/mbedtls.h deleted file mode 100644 index 15f7462a4..000000000 --- a/src/libgit2/hash/sha1/mbedtls.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_mbedtls_h__ -#define INCLUDE_hash_sha1_mbedtls_h__ - -#include "hash/sha1.h" - -#include - -struct git_hash_sha1_ctx { - mbedtls_sha1_context c; -}; - -#endif /* INCLUDE_hash_sha1_mbedtls_h__ */ diff --git a/src/libgit2/hash/sha1/openssl.c b/src/libgit2/hash/sha1/openssl.c deleted file mode 100644 index 64bf99b3c..000000000 --- a/src/libgit2/hash/sha1/openssl.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "openssl.h" - -int git_hash_sha1_global_init(void) -{ - return 0; -} - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) -{ - return git_hash_sha1_init(ctx); -} - -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) -{ - GIT_UNUSED(ctx); -} - -int git_hash_sha1_init(git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - - if (SHA1_Init(&ctx->c) != 1) { - git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to initialize hash context"); - return -1; - } - - return 0; -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - GIT_ASSERT_ARG(ctx); - - if (SHA1_Update(&ctx->c, data, len) != 1) { - git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to update hash"); - return -1; - } - - return 0; -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - - if (SHA1_Final(out, &ctx->c) != 1) { - git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to finalize hash"); - return -1; - } - - return 0; -} diff --git a/src/libgit2/hash/sha1/openssl.h b/src/libgit2/hash/sha1/openssl.h deleted file mode 100644 index a223ca03e..000000000 --- a/src/libgit2/hash/sha1/openssl.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_openssl_h__ -#define INCLUDE_hash_sha1_openssl_h__ - -#include "hash/sha1.h" - -#include - -struct git_hash_sha1_ctx { - SHA_CTX c; -}; - -#endif diff --git a/src/libgit2/hash/sha1/sha1dc/sha1.c b/src/libgit2/hash/sha1/sha1dc/sha1.c deleted file mode 100644 index 929822728..000000000 --- a/src/libgit2/hash/sha1/sha1dc/sha1.c +++ /dev/null @@ -1,1909 +0,0 @@ -/*** -* Copyright 2017 Marc Stevens , Dan Shumow (danshu@microsoft.com) -* Distributed under the MIT Software License. -* See accompanying file LICENSE.txt or copy at -* https://opensource.org/licenses/MIT -***/ - -#ifndef SHA1DC_NO_STANDARD_INCLUDES -#include -#include -#include -#include -#include /* make sure macros like _BIG_ENDIAN visible */ -#endif - -#ifdef SHA1DC_CUSTOM_INCLUDE_SHA1_C -#include SHA1DC_CUSTOM_INCLUDE_SHA1_C -#endif - -#ifndef SHA1DC_INIT_SAFE_HASH_DEFAULT -#define SHA1DC_INIT_SAFE_HASH_DEFAULT 1 -#endif - -#include "sha1.h" -#include "ubc_check.h" - -#if (defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \ - defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || \ - defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || \ - defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || defined(__INTEL__) || \ - defined(__386) || defined(_M_X64) || defined(_M_AMD64)) -#define SHA1DC_ON_INTEL_LIKE_PROCESSOR -#endif - -/* - Because Little-Endian architectures are most common, - we only set SHA1DC_BIGENDIAN if one of these conditions is met. - Note that all MSFT platforms are little endian, - so none of these will be defined under the MSC compiler. - If you are compiling on a big endian platform and your compiler does not define one of these, - you will have to add whatever macros your tool chain defines to indicate Big-Endianness. - */ - -#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) -/* - * Should detect Big Endian under GCC since at least 4.6.0 (gcc svn - * rev #165881). See - * https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html - * - * This also works under clang since 3.2, it copied the GCC-ism. See - * clang.git's 3b198a97d2 ("Preprocessor: add __BYTE_ORDER__ - * predefined macro", 2012-07-27) - */ -#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -#define SHA1DC_BIGENDIAN -#endif - -/* Not under GCC-alike */ -#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) -/* - * Should detect Big Endian under glibc.git since 14245eb70e ("entered - * into RCS", 1992-11-25). Defined in which will have been - * brought in by standard headers. See glibc.git and - * https://sourceforge.net/p/predef/wiki/Endianness/ - */ -#if __BYTE_ORDER == __BIG_ENDIAN -#define SHA1DC_BIGENDIAN -#endif - -/* Not under GCC-alike or glibc */ -#elif defined(_BYTE_ORDER) && defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) -/* - * *BSD and newlib (embedded linux, cygwin, etc). - * the defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) part prevents - * this condition from matching with Solaris/sparc. - * (Solaris defines only one endian macro) - */ -#if _BYTE_ORDER == _BIG_ENDIAN -#define SHA1DC_BIGENDIAN -#endif - -/* Not under GCC-alike or glibc or *BSD or newlib */ -#elif (defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ - defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || \ - defined(__sparc)) -/* - * Should define Big Endian for a whitelist of known processors. See - * https://sourceforge.net/p/predef/wiki/Endianness/ and - * http://www.oracle.com/technetwork/server-storage/solaris/portingtosolaris-138514.html - */ -#define SHA1DC_BIGENDIAN - -/* Not under GCC-alike or glibc or *BSD or newlib or */ -#elif (defined(_AIX) || defined(__hpux)) - -/* - * Defines Big Endian on a whitelist of OSs that are known to be Big - * Endian-only. See - * https://public-inbox.org/git/93056823-2740-d072-1ebd-46b440b33d7e@felt.demon.nl/ - */ -#define SHA1DC_BIGENDIAN - -/* Not under GCC-alike or glibc or *BSD or newlib or or */ -#elif defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) -/* - * As a last resort before we do anything else we're not 100% sure - * about below, we blacklist specific processors here. We could add - * more, see e.g. https://wiki.debian.org/ArchitectureSpecificsMemo - */ -#else /* Not under GCC-alike or glibc or *BSD or newlib or or or */ - -/* We do nothing more here for now */ -/*#error "Uncomment this to see if you fall through all the detection"*/ - -#endif /* Big Endian detection */ - -#if (defined(SHA1DC_FORCE_LITTLEENDIAN) && defined(SHA1DC_BIGENDIAN)) -#undef SHA1DC_BIGENDIAN -#endif -#if (defined(SHA1DC_FORCE_BIGENDIAN) && !defined(SHA1DC_BIGENDIAN)) -#define SHA1DC_BIGENDIAN -#endif -/*ENDIANNESS SELECTION*/ - -#ifndef SHA1DC_FORCE_ALIGNED_ACCESS -#if defined(SHA1DC_FORCE_UNALIGNED_ACCESS) || defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) -#define SHA1DC_ALLOW_UNALIGNED_ACCESS -#endif /*UNALIGNED ACCESS DETECTION*/ -#endif /*FORCE ALIGNED ACCESS*/ - -#define rotate_right(x,n) (((x)>>(n))|((x)<<(32-(n)))) -#define rotate_left(x,n) (((x)<<(n))|((x)>>(32-(n)))) - -#define sha1_bswap32(x) \ - {x = ((x << 8) & 0xFF00FF00) | ((x >> 8) & 0xFF00FF); x = (x << 16) | (x >> 16);} - -#define sha1_mix(W, t) (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1)) - -#ifdef SHA1DC_BIGENDIAN - #define sha1_load(m, t, temp) { temp = m[t]; } -#else - #define sha1_load(m, t, temp) { temp = m[t]; sha1_bswap32(temp); } -#endif - -#define sha1_store(W, t, x) *(volatile uint32_t *)&W[t] = x - -#define sha1_f1(b,c,d) ((d)^((b)&((c)^(d)))) -#define sha1_f2(b,c,d) ((b)^(c)^(d)) -#define sha1_f3(b,c,d) (((b)&(c))+((d)&((b)^(c)))) -#define sha1_f4(b,c,d) ((b)^(c)^(d)) - -#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, m, t) \ - { e += rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; b = rotate_left(b, 30); } -#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, m, t) \ - { e += rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; b = rotate_left(b, 30); } -#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, m, t) \ - { e += rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; b = rotate_left(b, 30); } -#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, m, t) \ - { e += rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; b = rotate_left(b, 30); } - -#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, m, t) \ - { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; } -#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, m, t) \ - { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; } -#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, m, t) \ - { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; } -#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, m, t) \ - { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; } - -#define SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, t, temp) \ - {sha1_load(m, t, temp); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30);} - -#define SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(a, b, c, d, e, W, t, temp) \ - {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30); } - -#define SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, t, temp) \ - {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1; b = rotate_left(b, 30); } - -#define SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, t, temp) \ - {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC; b = rotate_left(b, 30); } - -#define SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, t, temp) \ - {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6; b = rotate_left(b, 30); } - - -#define SHA1_STORE_STATE(i) states[i][0] = a; states[i][1] = b; states[i][2] = c; states[i][3] = d; states[i][4] = e; - -#ifdef BUILDNOCOLLDETECTSHA1COMPRESSION -void sha1_compression(uint32_t ihv[5], const uint32_t m[16]) -{ - uint32_t W[80]; - uint32_t a,b,c,d,e; - unsigned i; - - memcpy(W, m, 16 * 4); - for (i = 16; i < 80; ++i) - W[i] = sha1_mix(W, i); - - a = ihv[0]; b = ihv[1]; c = ihv[2]; d = ihv[3]; e = ihv[4]; - - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); - - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); - - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); - - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); - - ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; -} -#endif /*BUILDNOCOLLDETECTSHA1COMPRESSION*/ - - -static void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]) -{ - uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; - - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); - HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); - - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); - HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); - - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); - HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); - - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); - HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); - - ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; -} - - - -void sha1_compression_states(uint32_t ihv[5], const uint32_t m[16], uint32_t W[80], uint32_t states[80][5]) -{ - uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; - uint32_t temp; - -#ifdef DOSTORESTATE00 - SHA1_STORE_STATE(0) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 0, temp); - -#ifdef DOSTORESTATE01 - SHA1_STORE_STATE(1) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 1, temp); - -#ifdef DOSTORESTATE02 - SHA1_STORE_STATE(2) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 2, temp); - -#ifdef DOSTORESTATE03 - SHA1_STORE_STATE(3) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 3, temp); - -#ifdef DOSTORESTATE04 - SHA1_STORE_STATE(4) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 4, temp); - -#ifdef DOSTORESTATE05 - SHA1_STORE_STATE(5) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 5, temp); - -#ifdef DOSTORESTATE06 - SHA1_STORE_STATE(6) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 6, temp); - -#ifdef DOSTORESTATE07 - SHA1_STORE_STATE(7) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 7, temp); - -#ifdef DOSTORESTATE08 - SHA1_STORE_STATE(8) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 8, temp); - -#ifdef DOSTORESTATE09 - SHA1_STORE_STATE(9) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 9, temp); - -#ifdef DOSTORESTATE10 - SHA1_STORE_STATE(10) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 10, temp); - -#ifdef DOSTORESTATE11 - SHA1_STORE_STATE(11) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 11, temp); - -#ifdef DOSTORESTATE12 - SHA1_STORE_STATE(12) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 12, temp); - -#ifdef DOSTORESTATE13 - SHA1_STORE_STATE(13) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 13, temp); - -#ifdef DOSTORESTATE14 - SHA1_STORE_STATE(14) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 14, temp); - -#ifdef DOSTORESTATE15 - SHA1_STORE_STATE(15) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 15, temp); - -#ifdef DOSTORESTATE16 - SHA1_STORE_STATE(16) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(e, a, b, c, d, W, 16, temp); - -#ifdef DOSTORESTATE17 - SHA1_STORE_STATE(17) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(d, e, a, b, c, W, 17, temp); - -#ifdef DOSTORESTATE18 - SHA1_STORE_STATE(18) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(c, d, e, a, b, W, 18, temp); - -#ifdef DOSTORESTATE19 - SHA1_STORE_STATE(19) -#endif - SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(b, c, d, e, a, W, 19, temp); - - - -#ifdef DOSTORESTATE20 - SHA1_STORE_STATE(20) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 20, temp); - -#ifdef DOSTORESTATE21 - SHA1_STORE_STATE(21) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 21, temp); - -#ifdef DOSTORESTATE22 - SHA1_STORE_STATE(22) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 22, temp); - -#ifdef DOSTORESTATE23 - SHA1_STORE_STATE(23) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 23, temp); - -#ifdef DOSTORESTATE24 - SHA1_STORE_STATE(24) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 24, temp); - -#ifdef DOSTORESTATE25 - SHA1_STORE_STATE(25) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 25, temp); - -#ifdef DOSTORESTATE26 - SHA1_STORE_STATE(26) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 26, temp); - -#ifdef DOSTORESTATE27 - SHA1_STORE_STATE(27) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 27, temp); - -#ifdef DOSTORESTATE28 - SHA1_STORE_STATE(28) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 28, temp); - -#ifdef DOSTORESTATE29 - SHA1_STORE_STATE(29) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 29, temp); - -#ifdef DOSTORESTATE30 - SHA1_STORE_STATE(30) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 30, temp); - -#ifdef DOSTORESTATE31 - SHA1_STORE_STATE(31) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 31, temp); - -#ifdef DOSTORESTATE32 - SHA1_STORE_STATE(32) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 32, temp); - -#ifdef DOSTORESTATE33 - SHA1_STORE_STATE(33) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 33, temp); - -#ifdef DOSTORESTATE34 - SHA1_STORE_STATE(34) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 34, temp); - -#ifdef DOSTORESTATE35 - SHA1_STORE_STATE(35) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 35, temp); - -#ifdef DOSTORESTATE36 - SHA1_STORE_STATE(36) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 36, temp); - -#ifdef DOSTORESTATE37 - SHA1_STORE_STATE(37) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 37, temp); - -#ifdef DOSTORESTATE38 - SHA1_STORE_STATE(38) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 38, temp); - -#ifdef DOSTORESTATE39 - SHA1_STORE_STATE(39) -#endif - SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 39, temp); - - - -#ifdef DOSTORESTATE40 - SHA1_STORE_STATE(40) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 40, temp); - -#ifdef DOSTORESTATE41 - SHA1_STORE_STATE(41) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 41, temp); - -#ifdef DOSTORESTATE42 - SHA1_STORE_STATE(42) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 42, temp); - -#ifdef DOSTORESTATE43 - SHA1_STORE_STATE(43) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 43, temp); - -#ifdef DOSTORESTATE44 - SHA1_STORE_STATE(44) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 44, temp); - -#ifdef DOSTORESTATE45 - SHA1_STORE_STATE(45) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 45, temp); - -#ifdef DOSTORESTATE46 - SHA1_STORE_STATE(46) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 46, temp); - -#ifdef DOSTORESTATE47 - SHA1_STORE_STATE(47) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 47, temp); - -#ifdef DOSTORESTATE48 - SHA1_STORE_STATE(48) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 48, temp); - -#ifdef DOSTORESTATE49 - SHA1_STORE_STATE(49) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 49, temp); - -#ifdef DOSTORESTATE50 - SHA1_STORE_STATE(50) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 50, temp); - -#ifdef DOSTORESTATE51 - SHA1_STORE_STATE(51) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 51, temp); - -#ifdef DOSTORESTATE52 - SHA1_STORE_STATE(52) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 52, temp); - -#ifdef DOSTORESTATE53 - SHA1_STORE_STATE(53) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 53, temp); - -#ifdef DOSTORESTATE54 - SHA1_STORE_STATE(54) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 54, temp); - -#ifdef DOSTORESTATE55 - SHA1_STORE_STATE(55) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 55, temp); - -#ifdef DOSTORESTATE56 - SHA1_STORE_STATE(56) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 56, temp); - -#ifdef DOSTORESTATE57 - SHA1_STORE_STATE(57) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 57, temp); - -#ifdef DOSTORESTATE58 - SHA1_STORE_STATE(58) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 58, temp); - -#ifdef DOSTORESTATE59 - SHA1_STORE_STATE(59) -#endif - SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 59, temp); - - - - -#ifdef DOSTORESTATE60 - SHA1_STORE_STATE(60) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 60, temp); - -#ifdef DOSTORESTATE61 - SHA1_STORE_STATE(61) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 61, temp); - -#ifdef DOSTORESTATE62 - SHA1_STORE_STATE(62) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 62, temp); - -#ifdef DOSTORESTATE63 - SHA1_STORE_STATE(63) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 63, temp); - -#ifdef DOSTORESTATE64 - SHA1_STORE_STATE(64) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 64, temp); - -#ifdef DOSTORESTATE65 - SHA1_STORE_STATE(65) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 65, temp); - -#ifdef DOSTORESTATE66 - SHA1_STORE_STATE(66) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 66, temp); - -#ifdef DOSTORESTATE67 - SHA1_STORE_STATE(67) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 67, temp); - -#ifdef DOSTORESTATE68 - SHA1_STORE_STATE(68) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 68, temp); - -#ifdef DOSTORESTATE69 - SHA1_STORE_STATE(69) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 69, temp); - -#ifdef DOSTORESTATE70 - SHA1_STORE_STATE(70) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 70, temp); - -#ifdef DOSTORESTATE71 - SHA1_STORE_STATE(71) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 71, temp); - -#ifdef DOSTORESTATE72 - SHA1_STORE_STATE(72) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 72, temp); - -#ifdef DOSTORESTATE73 - SHA1_STORE_STATE(73) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 73, temp); - -#ifdef DOSTORESTATE74 - SHA1_STORE_STATE(74) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 74, temp); - -#ifdef DOSTORESTATE75 - SHA1_STORE_STATE(75) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 75, temp); - -#ifdef DOSTORESTATE76 - SHA1_STORE_STATE(76) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 76, temp); - -#ifdef DOSTORESTATE77 - SHA1_STORE_STATE(77) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 77, temp); - -#ifdef DOSTORESTATE78 - SHA1_STORE_STATE(78) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 78, temp); - -#ifdef DOSTORESTATE79 - SHA1_STORE_STATE(79) -#endif - SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 79, temp); - - - - ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; -} - - - - -#define SHA1_RECOMPRESS(t) \ -static void sha1recompress_fast_ ## t (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) \ -{ \ - uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; \ - if (t > 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 79); \ - if (t > 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 78); \ - if (t > 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 77); \ - if (t > 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 76); \ - if (t > 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 75); \ - if (t > 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 74); \ - if (t > 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 73); \ - if (t > 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 72); \ - if (t > 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 71); \ - if (t > 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 70); \ - if (t > 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 69); \ - if (t > 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 68); \ - if (t > 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 67); \ - if (t > 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 66); \ - if (t > 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 65); \ - if (t > 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 64); \ - if (t > 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 63); \ - if (t > 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 62); \ - if (t > 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 61); \ - if (t > 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 60); \ - if (t > 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 59); \ - if (t > 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 58); \ - if (t > 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 57); \ - if (t > 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 56); \ - if (t > 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 55); \ - if (t > 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 54); \ - if (t > 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 53); \ - if (t > 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 52); \ - if (t > 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 51); \ - if (t > 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 50); \ - if (t > 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 49); \ - if (t > 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 48); \ - if (t > 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 47); \ - if (t > 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 46); \ - if (t > 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 45); \ - if (t > 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 44); \ - if (t > 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 43); \ - if (t > 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 42); \ - if (t > 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 41); \ - if (t > 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 40); \ - if (t > 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 39); \ - if (t > 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 38); \ - if (t > 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 37); \ - if (t > 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 36); \ - if (t > 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 35); \ - if (t > 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 34); \ - if (t > 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 33); \ - if (t > 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 32); \ - if (t > 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 31); \ - if (t > 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 30); \ - if (t > 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 29); \ - if (t > 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 28); \ - if (t > 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 27); \ - if (t > 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 26); \ - if (t > 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 25); \ - if (t > 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 24); \ - if (t > 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 23); \ - if (t > 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 22); \ - if (t > 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 21); \ - if (t > 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 20); \ - if (t > 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 19); \ - if (t > 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 18); \ - if (t > 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 17); \ - if (t > 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 16); \ - if (t > 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 15); \ - if (t > 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 14); \ - if (t > 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 13); \ - if (t > 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 12); \ - if (t > 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 11); \ - if (t > 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 10); \ - if (t > 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 9); \ - if (t > 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 8); \ - if (t > 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 7); \ - if (t > 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 6); \ - if (t > 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 5); \ - if (t > 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 4); \ - if (t > 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 3); \ - if (t > 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 2); \ - if (t > 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 1); \ - if (t > 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 0); \ - ihvin[0] = a; ihvin[1] = b; ihvin[2] = c; ihvin[3] = d; ihvin[4] = e; \ - a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; \ - if (t <= 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 0); \ - if (t <= 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 1); \ - if (t <= 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 2); \ - if (t <= 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 3); \ - if (t <= 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 4); \ - if (t <= 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 5); \ - if (t <= 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 6); \ - if (t <= 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 7); \ - if (t <= 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 8); \ - if (t <= 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 9); \ - if (t <= 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 10); \ - if (t <= 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 11); \ - if (t <= 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 12); \ - if (t <= 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 13); \ - if (t <= 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 14); \ - if (t <= 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 15); \ - if (t <= 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 16); \ - if (t <= 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 17); \ - if (t <= 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 18); \ - if (t <= 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 19); \ - if (t <= 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 20); \ - if (t <= 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 21); \ - if (t <= 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 22); \ - if (t <= 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 23); \ - if (t <= 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 24); \ - if (t <= 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 25); \ - if (t <= 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 26); \ - if (t <= 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 27); \ - if (t <= 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 28); \ - if (t <= 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 29); \ - if (t <= 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 30); \ - if (t <= 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 31); \ - if (t <= 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 32); \ - if (t <= 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 33); \ - if (t <= 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 34); \ - if (t <= 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 35); \ - if (t <= 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 36); \ - if (t <= 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 37); \ - if (t <= 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 38); \ - if (t <= 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 39); \ - if (t <= 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 40); \ - if (t <= 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 41); \ - if (t <= 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 42); \ - if (t <= 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 43); \ - if (t <= 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 44); \ - if (t <= 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 45); \ - if (t <= 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 46); \ - if (t <= 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 47); \ - if (t <= 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 48); \ - if (t <= 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 49); \ - if (t <= 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 50); \ - if (t <= 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 51); \ - if (t <= 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 52); \ - if (t <= 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 53); \ - if (t <= 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 54); \ - if (t <= 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 55); \ - if (t <= 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 56); \ - if (t <= 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 57); \ - if (t <= 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 58); \ - if (t <= 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 59); \ - if (t <= 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 60); \ - if (t <= 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 61); \ - if (t <= 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 62); \ - if (t <= 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 63); \ - if (t <= 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 64); \ - if (t <= 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 65); \ - if (t <= 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 66); \ - if (t <= 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 67); \ - if (t <= 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 68); \ - if (t <= 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 69); \ - if (t <= 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 70); \ - if (t <= 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 71); \ - if (t <= 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 72); \ - if (t <= 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 73); \ - if (t <= 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 74); \ - if (t <= 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 75); \ - if (t <= 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 76); \ - if (t <= 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 77); \ - if (t <= 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 78); \ - if (t <= 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 79); \ - ihvout[0] = ihvin[0] + a; ihvout[1] = ihvin[1] + b; ihvout[2] = ihvin[2] + c; ihvout[3] = ihvin[3] + d; ihvout[4] = ihvin[4] + e; \ -} - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4127) /* Compiler complains about the checks in the above macro being constant. */ -#endif - -#ifdef DOSTORESTATE0 -SHA1_RECOMPRESS(0) -#endif - -#ifdef DOSTORESTATE1 -SHA1_RECOMPRESS(1) -#endif - -#ifdef DOSTORESTATE2 -SHA1_RECOMPRESS(2) -#endif - -#ifdef DOSTORESTATE3 -SHA1_RECOMPRESS(3) -#endif - -#ifdef DOSTORESTATE4 -SHA1_RECOMPRESS(4) -#endif - -#ifdef DOSTORESTATE5 -SHA1_RECOMPRESS(5) -#endif - -#ifdef DOSTORESTATE6 -SHA1_RECOMPRESS(6) -#endif - -#ifdef DOSTORESTATE7 -SHA1_RECOMPRESS(7) -#endif - -#ifdef DOSTORESTATE8 -SHA1_RECOMPRESS(8) -#endif - -#ifdef DOSTORESTATE9 -SHA1_RECOMPRESS(9) -#endif - -#ifdef DOSTORESTATE10 -SHA1_RECOMPRESS(10) -#endif - -#ifdef DOSTORESTATE11 -SHA1_RECOMPRESS(11) -#endif - -#ifdef DOSTORESTATE12 -SHA1_RECOMPRESS(12) -#endif - -#ifdef DOSTORESTATE13 -SHA1_RECOMPRESS(13) -#endif - -#ifdef DOSTORESTATE14 -SHA1_RECOMPRESS(14) -#endif - -#ifdef DOSTORESTATE15 -SHA1_RECOMPRESS(15) -#endif - -#ifdef DOSTORESTATE16 -SHA1_RECOMPRESS(16) -#endif - -#ifdef DOSTORESTATE17 -SHA1_RECOMPRESS(17) -#endif - -#ifdef DOSTORESTATE18 -SHA1_RECOMPRESS(18) -#endif - -#ifdef DOSTORESTATE19 -SHA1_RECOMPRESS(19) -#endif - -#ifdef DOSTORESTATE20 -SHA1_RECOMPRESS(20) -#endif - -#ifdef DOSTORESTATE21 -SHA1_RECOMPRESS(21) -#endif - -#ifdef DOSTORESTATE22 -SHA1_RECOMPRESS(22) -#endif - -#ifdef DOSTORESTATE23 -SHA1_RECOMPRESS(23) -#endif - -#ifdef DOSTORESTATE24 -SHA1_RECOMPRESS(24) -#endif - -#ifdef DOSTORESTATE25 -SHA1_RECOMPRESS(25) -#endif - -#ifdef DOSTORESTATE26 -SHA1_RECOMPRESS(26) -#endif - -#ifdef DOSTORESTATE27 -SHA1_RECOMPRESS(27) -#endif - -#ifdef DOSTORESTATE28 -SHA1_RECOMPRESS(28) -#endif - -#ifdef DOSTORESTATE29 -SHA1_RECOMPRESS(29) -#endif - -#ifdef DOSTORESTATE30 -SHA1_RECOMPRESS(30) -#endif - -#ifdef DOSTORESTATE31 -SHA1_RECOMPRESS(31) -#endif - -#ifdef DOSTORESTATE32 -SHA1_RECOMPRESS(32) -#endif - -#ifdef DOSTORESTATE33 -SHA1_RECOMPRESS(33) -#endif - -#ifdef DOSTORESTATE34 -SHA1_RECOMPRESS(34) -#endif - -#ifdef DOSTORESTATE35 -SHA1_RECOMPRESS(35) -#endif - -#ifdef DOSTORESTATE36 -SHA1_RECOMPRESS(36) -#endif - -#ifdef DOSTORESTATE37 -SHA1_RECOMPRESS(37) -#endif - -#ifdef DOSTORESTATE38 -SHA1_RECOMPRESS(38) -#endif - -#ifdef DOSTORESTATE39 -SHA1_RECOMPRESS(39) -#endif - -#ifdef DOSTORESTATE40 -SHA1_RECOMPRESS(40) -#endif - -#ifdef DOSTORESTATE41 -SHA1_RECOMPRESS(41) -#endif - -#ifdef DOSTORESTATE42 -SHA1_RECOMPRESS(42) -#endif - -#ifdef DOSTORESTATE43 -SHA1_RECOMPRESS(43) -#endif - -#ifdef DOSTORESTATE44 -SHA1_RECOMPRESS(44) -#endif - -#ifdef DOSTORESTATE45 -SHA1_RECOMPRESS(45) -#endif - -#ifdef DOSTORESTATE46 -SHA1_RECOMPRESS(46) -#endif - -#ifdef DOSTORESTATE47 -SHA1_RECOMPRESS(47) -#endif - -#ifdef DOSTORESTATE48 -SHA1_RECOMPRESS(48) -#endif - -#ifdef DOSTORESTATE49 -SHA1_RECOMPRESS(49) -#endif - -#ifdef DOSTORESTATE50 -SHA1_RECOMPRESS(50) -#endif - -#ifdef DOSTORESTATE51 -SHA1_RECOMPRESS(51) -#endif - -#ifdef DOSTORESTATE52 -SHA1_RECOMPRESS(52) -#endif - -#ifdef DOSTORESTATE53 -SHA1_RECOMPRESS(53) -#endif - -#ifdef DOSTORESTATE54 -SHA1_RECOMPRESS(54) -#endif - -#ifdef DOSTORESTATE55 -SHA1_RECOMPRESS(55) -#endif - -#ifdef DOSTORESTATE56 -SHA1_RECOMPRESS(56) -#endif - -#ifdef DOSTORESTATE57 -SHA1_RECOMPRESS(57) -#endif - -#ifdef DOSTORESTATE58 -SHA1_RECOMPRESS(58) -#endif - -#ifdef DOSTORESTATE59 -SHA1_RECOMPRESS(59) -#endif - -#ifdef DOSTORESTATE60 -SHA1_RECOMPRESS(60) -#endif - -#ifdef DOSTORESTATE61 -SHA1_RECOMPRESS(61) -#endif - -#ifdef DOSTORESTATE62 -SHA1_RECOMPRESS(62) -#endif - -#ifdef DOSTORESTATE63 -SHA1_RECOMPRESS(63) -#endif - -#ifdef DOSTORESTATE64 -SHA1_RECOMPRESS(64) -#endif - -#ifdef DOSTORESTATE65 -SHA1_RECOMPRESS(65) -#endif - -#ifdef DOSTORESTATE66 -SHA1_RECOMPRESS(66) -#endif - -#ifdef DOSTORESTATE67 -SHA1_RECOMPRESS(67) -#endif - -#ifdef DOSTORESTATE68 -SHA1_RECOMPRESS(68) -#endif - -#ifdef DOSTORESTATE69 -SHA1_RECOMPRESS(69) -#endif - -#ifdef DOSTORESTATE70 -SHA1_RECOMPRESS(70) -#endif - -#ifdef DOSTORESTATE71 -SHA1_RECOMPRESS(71) -#endif - -#ifdef DOSTORESTATE72 -SHA1_RECOMPRESS(72) -#endif - -#ifdef DOSTORESTATE73 -SHA1_RECOMPRESS(73) -#endif - -#ifdef DOSTORESTATE74 -SHA1_RECOMPRESS(74) -#endif - -#ifdef DOSTORESTATE75 -SHA1_RECOMPRESS(75) -#endif - -#ifdef DOSTORESTATE76 -SHA1_RECOMPRESS(76) -#endif - -#ifdef DOSTORESTATE77 -SHA1_RECOMPRESS(77) -#endif - -#ifdef DOSTORESTATE78 -SHA1_RECOMPRESS(78) -#endif - -#ifdef DOSTORESTATE79 -SHA1_RECOMPRESS(79) -#endif - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -static void sha1_recompression_step(uint32_t step, uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) -{ - switch (step) - { -#ifdef DOSTORESTATE0 - case 0: - sha1recompress_fast_0(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE1 - case 1: - sha1recompress_fast_1(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE2 - case 2: - sha1recompress_fast_2(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE3 - case 3: - sha1recompress_fast_3(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE4 - case 4: - sha1recompress_fast_4(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE5 - case 5: - sha1recompress_fast_5(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE6 - case 6: - sha1recompress_fast_6(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE7 - case 7: - sha1recompress_fast_7(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE8 - case 8: - sha1recompress_fast_8(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE9 - case 9: - sha1recompress_fast_9(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE10 - case 10: - sha1recompress_fast_10(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE11 - case 11: - sha1recompress_fast_11(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE12 - case 12: - sha1recompress_fast_12(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE13 - case 13: - sha1recompress_fast_13(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE14 - case 14: - sha1recompress_fast_14(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE15 - case 15: - sha1recompress_fast_15(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE16 - case 16: - sha1recompress_fast_16(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE17 - case 17: - sha1recompress_fast_17(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE18 - case 18: - sha1recompress_fast_18(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE19 - case 19: - sha1recompress_fast_19(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE20 - case 20: - sha1recompress_fast_20(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE21 - case 21: - sha1recompress_fast_21(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE22 - case 22: - sha1recompress_fast_22(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE23 - case 23: - sha1recompress_fast_23(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE24 - case 24: - sha1recompress_fast_24(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE25 - case 25: - sha1recompress_fast_25(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE26 - case 26: - sha1recompress_fast_26(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE27 - case 27: - sha1recompress_fast_27(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE28 - case 28: - sha1recompress_fast_28(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE29 - case 29: - sha1recompress_fast_29(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE30 - case 30: - sha1recompress_fast_30(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE31 - case 31: - sha1recompress_fast_31(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE32 - case 32: - sha1recompress_fast_32(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE33 - case 33: - sha1recompress_fast_33(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE34 - case 34: - sha1recompress_fast_34(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE35 - case 35: - sha1recompress_fast_35(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE36 - case 36: - sha1recompress_fast_36(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE37 - case 37: - sha1recompress_fast_37(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE38 - case 38: - sha1recompress_fast_38(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE39 - case 39: - sha1recompress_fast_39(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE40 - case 40: - sha1recompress_fast_40(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE41 - case 41: - sha1recompress_fast_41(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE42 - case 42: - sha1recompress_fast_42(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE43 - case 43: - sha1recompress_fast_43(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE44 - case 44: - sha1recompress_fast_44(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE45 - case 45: - sha1recompress_fast_45(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE46 - case 46: - sha1recompress_fast_46(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE47 - case 47: - sha1recompress_fast_47(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE48 - case 48: - sha1recompress_fast_48(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE49 - case 49: - sha1recompress_fast_49(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE50 - case 50: - sha1recompress_fast_50(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE51 - case 51: - sha1recompress_fast_51(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE52 - case 52: - sha1recompress_fast_52(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE53 - case 53: - sha1recompress_fast_53(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE54 - case 54: - sha1recompress_fast_54(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE55 - case 55: - sha1recompress_fast_55(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE56 - case 56: - sha1recompress_fast_56(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE57 - case 57: - sha1recompress_fast_57(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE58 - case 58: - sha1recompress_fast_58(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE59 - case 59: - sha1recompress_fast_59(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE60 - case 60: - sha1recompress_fast_60(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE61 - case 61: - sha1recompress_fast_61(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE62 - case 62: - sha1recompress_fast_62(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE63 - case 63: - sha1recompress_fast_63(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE64 - case 64: - sha1recompress_fast_64(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE65 - case 65: - sha1recompress_fast_65(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE66 - case 66: - sha1recompress_fast_66(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE67 - case 67: - sha1recompress_fast_67(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE68 - case 68: - sha1recompress_fast_68(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE69 - case 69: - sha1recompress_fast_69(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE70 - case 70: - sha1recompress_fast_70(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE71 - case 71: - sha1recompress_fast_71(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE72 - case 72: - sha1recompress_fast_72(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE73 - case 73: - sha1recompress_fast_73(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE74 - case 74: - sha1recompress_fast_74(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE75 - case 75: - sha1recompress_fast_75(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE76 - case 76: - sha1recompress_fast_76(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE77 - case 77: - sha1recompress_fast_77(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE78 - case 78: - sha1recompress_fast_78(ihvin, ihvout, me2, state); - break; -#endif -#ifdef DOSTORESTATE79 - case 79: - sha1recompress_fast_79(ihvin, ihvout, me2, state); - break; -#endif - default: - abort(); - } - -} - - - -static void sha1_process(SHA1_CTX *ctx, const uint32_t block[16]) -{ - unsigned i, j; - uint32_t ubc_dv_mask[DVMASKSIZE] = { 0xFFFFFFFF }; - uint32_t ihvtmp[5]; - - ctx->ihv1[0] = ctx->ihv[0]; - ctx->ihv1[1] = ctx->ihv[1]; - ctx->ihv1[2] = ctx->ihv[2]; - ctx->ihv1[3] = ctx->ihv[3]; - ctx->ihv1[4] = ctx->ihv[4]; - - sha1_compression_states(ctx->ihv, block, ctx->m1, ctx->states); - - if (ctx->detect_coll) - { - if (ctx->ubc_check) - { - ubc_check(ctx->m1, ubc_dv_mask); - } - - if (ubc_dv_mask[0] != 0) - { - for (i = 0; sha1_dvs[i].dvType != 0; ++i) - { - if (ubc_dv_mask[0] & ((uint32_t)(1) << sha1_dvs[i].maskb)) - { - for (j = 0; j < 80; ++j) - ctx->m2[j] = ctx->m1[j] ^ sha1_dvs[i].dm[j]; - - sha1_recompression_step(sha1_dvs[i].testt, ctx->ihv2, ihvtmp, ctx->m2, ctx->states[sha1_dvs[i].testt]); - - /* to verify SHA-1 collision detection code with collisions for reduced-step SHA-1 */ - if ((0 == ((ihvtmp[0] ^ ctx->ihv[0]) | (ihvtmp[1] ^ ctx->ihv[1]) | (ihvtmp[2] ^ ctx->ihv[2]) | (ihvtmp[3] ^ ctx->ihv[3]) | (ihvtmp[4] ^ ctx->ihv[4]))) - || (ctx->reduced_round_coll && 0==((ctx->ihv1[0] ^ ctx->ihv2[0]) | (ctx->ihv1[1] ^ ctx->ihv2[1]) | (ctx->ihv1[2] ^ ctx->ihv2[2]) | (ctx->ihv1[3] ^ ctx->ihv2[3]) | (ctx->ihv1[4] ^ ctx->ihv2[4])))) - { - ctx->found_collision = 1; - - if (ctx->safe_hash) - { - sha1_compression_W(ctx->ihv, ctx->m1); - sha1_compression_W(ctx->ihv, ctx->m1); - } - - break; - } - } - } - } - } -} - -void SHA1DCInit(SHA1_CTX *ctx) -{ - ctx->total = 0; - ctx->ihv[0] = 0x67452301; - ctx->ihv[1] = 0xEFCDAB89; - ctx->ihv[2] = 0x98BADCFE; - ctx->ihv[3] = 0x10325476; - ctx->ihv[4] = 0xC3D2E1F0; - ctx->found_collision = 0; - ctx->safe_hash = SHA1DC_INIT_SAFE_HASH_DEFAULT; - ctx->ubc_check = 1; - ctx->detect_coll = 1; - ctx->reduced_round_coll = 0; - ctx->callback = NULL; -} - -void SHA1DCSetSafeHash(SHA1_CTX *ctx, int safehash) -{ - if (safehash) - ctx->safe_hash = 1; - else - ctx->safe_hash = 0; -} - - -void SHA1DCSetUseUBC(SHA1_CTX *ctx, int ubc_check) -{ - if (ubc_check) - ctx->ubc_check = 1; - else - ctx->ubc_check = 0; -} - -void SHA1DCSetUseDetectColl(SHA1_CTX *ctx, int detect_coll) -{ - if (detect_coll) - ctx->detect_coll = 1; - else - ctx->detect_coll = 0; -} - -void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX *ctx, int reduced_round_coll) -{ - if (reduced_round_coll) - ctx->reduced_round_coll = 1; - else - ctx->reduced_round_coll = 0; -} - -void SHA1DCSetCallback(SHA1_CTX *ctx, collision_block_callback callback) -{ - ctx->callback = callback; -} - -void SHA1DCUpdate(SHA1_CTX *ctx, const char *buf, size_t len) -{ - unsigned left, fill; - - if (len == 0) - return; - - left = ctx->total & 63; - fill = 64 - left; - - if (left && len >= fill) - { - ctx->total += fill; - memcpy(ctx->buffer + left, buf, fill); - sha1_process(ctx, (uint32_t*)(ctx->buffer)); - buf += fill; - len -= fill; - left = 0; - } - while (len >= 64) - { - ctx->total += 64; - -#if defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) - sha1_process(ctx, (uint32_t*)(buf)); -#else - memcpy(ctx->buffer, buf, 64); - sha1_process(ctx, (uint32_t*)(ctx->buffer)); -#endif /* defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) */ - buf += 64; - len -= 64; - } - if (len > 0) - { - ctx->total += len; - memcpy(ctx->buffer + left, buf, len); - } -} - -static const unsigned char sha1_padding[64] = -{ - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -int SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx) -{ - uint32_t last = ctx->total & 63; - uint32_t padn = (last < 56) ? (56 - last) : (120 - last); - uint64_t total; - SHA1DCUpdate(ctx, (const char*)(sha1_padding), padn); - - total = ctx->total - padn; - total <<= 3; - ctx->buffer[56] = (unsigned char)(total >> 56); - ctx->buffer[57] = (unsigned char)(total >> 48); - ctx->buffer[58] = (unsigned char)(total >> 40); - ctx->buffer[59] = (unsigned char)(total >> 32); - ctx->buffer[60] = (unsigned char)(total >> 24); - ctx->buffer[61] = (unsigned char)(total >> 16); - ctx->buffer[62] = (unsigned char)(total >> 8); - ctx->buffer[63] = (unsigned char)(total); - sha1_process(ctx, (uint32_t*)(ctx->buffer)); - output[0] = (unsigned char)(ctx->ihv[0] >> 24); - output[1] = (unsigned char)(ctx->ihv[0] >> 16); - output[2] = (unsigned char)(ctx->ihv[0] >> 8); - output[3] = (unsigned char)(ctx->ihv[0]); - output[4] = (unsigned char)(ctx->ihv[1] >> 24); - output[5] = (unsigned char)(ctx->ihv[1] >> 16); - output[6] = (unsigned char)(ctx->ihv[1] >> 8); - output[7] = (unsigned char)(ctx->ihv[1]); - output[8] = (unsigned char)(ctx->ihv[2] >> 24); - output[9] = (unsigned char)(ctx->ihv[2] >> 16); - output[10] = (unsigned char)(ctx->ihv[2] >> 8); - output[11] = (unsigned char)(ctx->ihv[2]); - output[12] = (unsigned char)(ctx->ihv[3] >> 24); - output[13] = (unsigned char)(ctx->ihv[3] >> 16); - output[14] = (unsigned char)(ctx->ihv[3] >> 8); - output[15] = (unsigned char)(ctx->ihv[3]); - output[16] = (unsigned char)(ctx->ihv[4] >> 24); - output[17] = (unsigned char)(ctx->ihv[4] >> 16); - output[18] = (unsigned char)(ctx->ihv[4] >> 8); - output[19] = (unsigned char)(ctx->ihv[4]); - return ctx->found_collision; -} - -#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C -#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C -#endif diff --git a/src/libgit2/hash/sha1/sha1dc/sha1.h b/src/libgit2/hash/sha1/sha1dc/sha1.h deleted file mode 100644 index 1e4e94be5..000000000 --- a/src/libgit2/hash/sha1/sha1dc/sha1.h +++ /dev/null @@ -1,110 +0,0 @@ -/*** -* Copyright 2017 Marc Stevens , Dan Shumow -* Distributed under the MIT Software License. -* See accompanying file LICENSE.txt or copy at -* https://opensource.org/licenses/MIT -***/ - -#ifndef SHA1DC_SHA1_H -#define SHA1DC_SHA1_H - -#if defined(__cplusplus) -extern "C" { -#endif - -#ifndef SHA1DC_NO_STANDARD_INCLUDES -#include -#endif - -/* sha-1 compression function that takes an already expanded message, and additionally store intermediate states */ -/* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is defined in ubc_check.h */ -void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]); - -/* -// Function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]). -// Where 0 <= T < 80 -// me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference.) -// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block. -// The function will return: -// ihvin: The reconstructed input chaining value. -// ihvout: The reconstructed output chaining value. -*/ -typedef void(*sha1_recompression_type)(uint32_t*, uint32_t*, const uint32_t*, const uint32_t*); - -/* A callback function type that can be set to be called when a collision block has been found: */ -/* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */ -typedef void(*collision_block_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); - -/* The SHA-1 context. */ -typedef struct { - uint64_t total; - uint32_t ihv[5]; - unsigned char buffer[64]; - int found_collision; - int safe_hash; - int detect_coll; - int ubc_check; - int reduced_round_coll; - collision_block_callback callback; - - uint32_t ihv1[5]; - uint32_t ihv2[5]; - uint32_t m1[80]; - uint32_t m2[80]; - uint32_t states[80][5]; -} SHA1_CTX; - -/* Initialize SHA-1 context. */ -void SHA1DCInit(SHA1_CTX*); - -/* - Function to enable safe SHA-1 hashing: - Collision attacks are thwarted by hashing a detected near-collision block 3 times. - Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: - The best collision attacks against SHA-1 have complexity about 2^60, - thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be 2^180. - An attacker would be better off using a generic birthday search of complexity 2^80. - - Enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected, - but it will result in a different SHA-1 hash for messages where a collision attack was detected. - This will automatically invalidate SHA-1 based digital signature forgeries. - Enabled by default. -*/ -void SHA1DCSetSafeHash(SHA1_CTX*, int); - -/* - Function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up). - Enabled by default - */ -void SHA1DCSetUseUBC(SHA1_CTX*, int); - -/* - Function to disable or enable the use of Collision Detection. - Enabled by default. - */ -void SHA1DCSetUseDetectColl(SHA1_CTX*, int); - -/* function to disable or enable the detection of reduced-round SHA-1 collisions */ -/* disabled by default */ -void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX*, int); - -/* function to set a callback function, pass NULL to disable */ -/* by default no callback set */ -void SHA1DCSetCallback(SHA1_CTX*, collision_block_callback); - -/* update SHA-1 context with buffer contents */ -void SHA1DCUpdate(SHA1_CTX*, const char*, size_t); - -/* obtain SHA-1 hash from SHA-1 context */ -/* returns: 0 = no collision detected, otherwise = collision found => warn user for active attack */ -int SHA1DCFinal(unsigned char[20], SHA1_CTX*); - -#if defined(__cplusplus) -} -#endif - -#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H -#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H -#endif - -#endif diff --git a/src/libgit2/hash/sha1/sha1dc/ubc_check.c b/src/libgit2/hash/sha1/sha1dc/ubc_check.c deleted file mode 100644 index b3beff2af..000000000 --- a/src/libgit2/hash/sha1/sha1dc/ubc_check.c +++ /dev/null @@ -1,372 +0,0 @@ -/*** -* Copyright 2017 Marc Stevens , Dan Shumow -* Distributed under the MIT Software License. -* See accompanying file LICENSE.txt or copy at -* https://opensource.org/licenses/MIT -***/ - -/* -// this file was generated by the 'parse_bitrel' program in the tools section -// using the data files from directory 'tools/data/3565' -// -// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check -// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) -// dm[80] is the expanded message block XOR-difference defined by the DV -// testt is the step to do the recompression from for collision detection -// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check -// -// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs -// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met -// thus one needs to do the recompression check for each DV that has its bit set -// -// ubc_check is programmatically generated and the unavoidable bitconditions have been hardcoded -// a directly verifiable version named ubc_check_verify can be found in ubc_check_verify.c -// ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in the tools section -*/ - -#ifndef SHA1DC_NO_STANDARD_INCLUDES -#include -#endif -#ifdef SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C -#include SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C -#endif -#include "ubc_check.h" - -static const uint32_t DV_I_43_0_bit = (uint32_t)(1) << 0; -static const uint32_t DV_I_44_0_bit = (uint32_t)(1) << 1; -static const uint32_t DV_I_45_0_bit = (uint32_t)(1) << 2; -static const uint32_t DV_I_46_0_bit = (uint32_t)(1) << 3; -static const uint32_t DV_I_46_2_bit = (uint32_t)(1) << 4; -static const uint32_t DV_I_47_0_bit = (uint32_t)(1) << 5; -static const uint32_t DV_I_47_2_bit = (uint32_t)(1) << 6; -static const uint32_t DV_I_48_0_bit = (uint32_t)(1) << 7; -static const uint32_t DV_I_48_2_bit = (uint32_t)(1) << 8; -static const uint32_t DV_I_49_0_bit = (uint32_t)(1) << 9; -static const uint32_t DV_I_49_2_bit = (uint32_t)(1) << 10; -static const uint32_t DV_I_50_0_bit = (uint32_t)(1) << 11; -static const uint32_t DV_I_50_2_bit = (uint32_t)(1) << 12; -static const uint32_t DV_I_51_0_bit = (uint32_t)(1) << 13; -static const uint32_t DV_I_51_2_bit = (uint32_t)(1) << 14; -static const uint32_t DV_I_52_0_bit = (uint32_t)(1) << 15; -static const uint32_t DV_II_45_0_bit = (uint32_t)(1) << 16; -static const uint32_t DV_II_46_0_bit = (uint32_t)(1) << 17; -static const uint32_t DV_II_46_2_bit = (uint32_t)(1) << 18; -static const uint32_t DV_II_47_0_bit = (uint32_t)(1) << 19; -static const uint32_t DV_II_48_0_bit = (uint32_t)(1) << 20; -static const uint32_t DV_II_49_0_bit = (uint32_t)(1) << 21; -static const uint32_t DV_II_49_2_bit = (uint32_t)(1) << 22; -static const uint32_t DV_II_50_0_bit = (uint32_t)(1) << 23; -static const uint32_t DV_II_50_2_bit = (uint32_t)(1) << 24; -static const uint32_t DV_II_51_0_bit = (uint32_t)(1) << 25; -static const uint32_t DV_II_51_2_bit = (uint32_t)(1) << 26; -static const uint32_t DV_II_52_0_bit = (uint32_t)(1) << 27; -static const uint32_t DV_II_53_0_bit = (uint32_t)(1) << 28; -static const uint32_t DV_II_54_0_bit = (uint32_t)(1) << 29; -static const uint32_t DV_II_55_0_bit = (uint32_t)(1) << 30; -static const uint32_t DV_II_56_0_bit = (uint32_t)(1) << 31; - -dv_info_t sha1_dvs[] = -{ - {1,43,0,58,0,0, { 0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161,0x80000599 } } -, {1,44,0,58,0,1, { 0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161 } } -, {1,45,0,58,0,2, { 0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803 } } -, {1,46,0,58,0,3, { 0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c } } -, {1,46,2,58,0,4, { 0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a,0x00000132 } } -, {1,47,0,58,0,5, { 0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6 } } -, {1,47,2,58,0,6, { 0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a } } -, {1,48,0,58,0,7, { 0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408 } } -, {1,48,2,58,0,8, { 0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020 } } -, {1,49,0,58,0,9, { 0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164 } } -, {1,49,2,58,0,10, { 0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590 } } -, {1,50,0,65,0,11, { 0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018 } } -, {1,50,2,65,0,12, { 0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060 } } -, {1,51,0,65,0,13, { 0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202 } } -, {1,51,2,65,0,14, { 0xa0000003,0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a } } -, {1,52,0,65,0,15, { 0x04000010,0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012 } } -, {2,45,0,58,0,16, { 0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054,0x00000967 } } -, {2,46,0,58,0,17, { 0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054 } } -, {2,46,2,58,0,18, { 0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6,0x0000106a,0x00000b90,0x00000152 } } -, {2,47,0,58,0,19, { 0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4 } } -, {2,48,0,58,0,20, { 0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a } } -, {2,49,0,58,0,21, { 0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d } } -, {2,49,2,58,0,22, { 0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6 } } -, {2,50,0,65,0,23, { 0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b } } -, {2,50,2,65,0,24, { 0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c } } -, {2,51,0,65,0,25, { 0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b } } -, {2,51,2,65,0,26, { 0x00000043,0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e } } -, {2,52,0,65,0,27, { 0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014 } } -, {2,53,0,65,0,28, { 0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089 } } -, {2,54,0,65,0,29, { 0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107 } } -, {2,55,0,65,0,30, { 0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b } } -, {2,56,0,65,0,31, { 0x2600001a,0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046 } } -, {0,0,0,0,0,0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}} -}; -void ubc_check(const uint32_t W[80], uint32_t dvmask[1]) -{ - uint32_t mask = ~((uint32_t)(0)); - mask &= (((((W[44]^W[45])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_I_51_0_bit|DV_I_52_0_bit|DV_II_45_0_bit|DV_II_46_0_bit|DV_II_50_0_bit|DV_II_51_0_bit)); - mask &= (((((W[49]^W[50])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_II_45_0_bit|DV_II_50_0_bit|DV_II_51_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); - mask &= (((((W[48]^W[49])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_52_0_bit|DV_II_49_0_bit|DV_II_50_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); - mask &= ((((W[47]^(W[50]>>25))&(1<<4))-(1<<4)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); - mask &= (((((W[47]^W[48])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_51_0_bit|DV_II_48_0_bit|DV_II_49_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); - mask &= (((((W[46]>>4)^(W[49]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_50_0_bit|DV_II_55_0_bit)); - mask &= (((((W[46]^W[47])>>29)&1)-1) | ~(DV_I_43_0_bit|DV_I_50_0_bit|DV_II_47_0_bit|DV_II_48_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); - mask &= (((((W[45]>>4)^(W[48]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_49_0_bit|DV_II_54_0_bit)); - mask &= (((((W[45]^W[46])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_51_0_bit|DV_II_52_0_bit)); - mask &= (((((W[44]>>4)^(W[47]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_53_0_bit)); - mask &= (((((W[43]>>4)^(W[46]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_52_0_bit)); - mask &= (((((W[43]^W[44])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_I_50_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_49_0_bit|DV_II_50_0_bit)); - mask &= (((((W[42]>>4)^(W[45]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_51_0_bit)); - mask &= (((((W[41]>>4)^(W[44]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_50_0_bit)); - mask &= (((((W[40]^W[41])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_47_0_bit|DV_I_48_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_56_0_bit)); - mask &= (((((W[54]^W[55])>>29)&1)-1) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_50_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); - mask &= (((((W[53]^W[54])>>29)&1)-1) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_49_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); - mask &= (((((W[52]^W[53])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); - mask &= ((((W[50]^(W[53]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_48_0_bit|DV_II_54_0_bit)); - mask &= (((((W[50]^W[51])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_II_46_0_bit|DV_II_51_0_bit|DV_II_52_0_bit|DV_II_56_0_bit)); - mask &= ((((W[49]^(W[52]>>25))&(1<<4))-(1<<4)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_47_0_bit|DV_II_53_0_bit)); - mask &= ((((W[48]^(W[51]>>25))&(1<<4))-(1<<4)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_52_0_bit)); - mask &= (((((W[42]^W[43])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); - mask &= (((((W[41]^W[42])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_48_0_bit)); - mask &= (((((W[40]>>4)^(W[43]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_50_0_bit|DV_II_49_0_bit|DV_II_56_0_bit)); - mask &= (((((W[39]>>4)^(W[42]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_49_0_bit|DV_II_48_0_bit|DV_II_55_0_bit)); - if (mask & (DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) - mask &= (((((W[38]>>4)^(W[41]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); - mask &= (((((W[37]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_47_0_bit|DV_II_46_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); - if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)) - mask &= (((((W[55]^W[56])>>29)&1)-1) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); - if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)) - mask &= ((((W[52]^(W[55]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)); - if (mask & (DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)) - mask &= ((((W[51]^(W[54]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)); - if (mask & (DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)) - mask &= (((((W[51]^W[52])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); - if (mask & (DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)) - mask &= (((((W[36]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)); - if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)) - mask &= ((0-(((W[53]^W[56])>>29)&1)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); - if (mask & (DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)) - mask &= ((0-(((W[51]^W[54])>>29)&1)) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)); - if (mask & (DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)) - mask &= ((0-(((W[50]^W[52])>>29)&1)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)); - if (mask & (DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)) - mask &= ((0-(((W[49]^W[51])>>29)&1)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)); - if (mask & (DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)) - mask &= ((0-(((W[48]^W[50])>>29)&1)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)); - if (mask & (DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)) - mask &= ((0-(((W[47]^W[49])>>29)&1)) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)); - if (mask & (DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)) - mask &= ((0-(((W[46]^W[48])>>29)&1)) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)); - mask &= ((((W[45]^W[47])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit|DV_I_51_2_bit)); - if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)) - mask &= ((0-(((W[45]^W[47])>>29)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)); - mask &= (((((W[44]^W[46])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit|DV_I_50_2_bit)); - if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)) - mask &= ((0-(((W[44]^W[46])>>29)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)); - mask &= ((0-((W[41]^(W[42]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_II_46_2_bit|DV_II_51_2_bit)); - mask &= ((0-((W[40]^(W[41]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_51_2_bit|DV_II_50_2_bit)); - if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)) - mask &= ((0-(((W[40]^W[42])>>4)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)); - mask &= ((0-((W[39]^(W[40]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_50_2_bit|DV_II_49_2_bit)); - if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)) - mask &= ((0-(((W[39]^W[41])>>4)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)); - if (mask & (DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) - mask &= ((0-(((W[38]^W[40])>>4)&1)) | ~(DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); - if (mask & (DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)) - mask &= ((0-(((W[37]^W[39])>>4)&1)) | ~(DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); - mask &= ((0-((W[36]^(W[37]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_50_2_bit|DV_II_46_2_bit)); - if (mask & (DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)) - mask &= (((((W[35]>>4)^(W[39]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)); - if (mask & (DV_I_48_0_bit|DV_II_48_0_bit)) - mask &= ((0-((W[63]^(W[64]>>5))&(1<<0))) | ~(DV_I_48_0_bit|DV_II_48_0_bit)); - if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) - mask &= ((0-((W[63]^(W[64]>>5))&(1<<1))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); - if (mask & (DV_I_47_0_bit|DV_II_47_0_bit)) - mask &= ((0-((W[62]^(W[63]>>5))&(1<<0))) | ~(DV_I_47_0_bit|DV_II_47_0_bit)); - if (mask & (DV_I_46_0_bit|DV_II_46_0_bit)) - mask &= ((0-((W[61]^(W[62]>>5))&(1<<0))) | ~(DV_I_46_0_bit|DV_II_46_0_bit)); - mask &= ((0-((W[61]^(W[62]>>5))&(1<<2))) | ~(DV_I_46_2_bit|DV_II_46_2_bit)); - if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) - mask &= ((0-((W[60]^(W[61]>>5))&(1<<0))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); - if (mask & (DV_II_51_0_bit|DV_II_54_0_bit)) - mask &= (((((W[58]^W[59])>>29)&1)-1) | ~(DV_II_51_0_bit|DV_II_54_0_bit)); - if (mask & (DV_II_50_0_bit|DV_II_53_0_bit)) - mask &= (((((W[57]^W[58])>>29)&1)-1) | ~(DV_II_50_0_bit|DV_II_53_0_bit)); - if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) - mask &= ((((W[56]^(W[59]>>25))&(1<<4))-(1<<4)) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); - if (mask & (DV_II_51_0_bit|DV_II_52_0_bit)) - mask &= ((0-(((W[56]^W[59])>>29)&1)) | ~(DV_II_51_0_bit|DV_II_52_0_bit)); - if (mask & (DV_II_49_0_bit|DV_II_52_0_bit)) - mask &= (((((W[56]^W[57])>>29)&1)-1) | ~(DV_II_49_0_bit|DV_II_52_0_bit)); - if (mask & (DV_II_51_0_bit|DV_II_53_0_bit)) - mask &= ((((W[55]^(W[58]>>25))&(1<<4))-(1<<4)) | ~(DV_II_51_0_bit|DV_II_53_0_bit)); - if (mask & (DV_II_50_0_bit|DV_II_52_0_bit)) - mask &= ((((W[54]^(W[57]>>25))&(1<<4))-(1<<4)) | ~(DV_II_50_0_bit|DV_II_52_0_bit)); - if (mask & (DV_II_49_0_bit|DV_II_51_0_bit)) - mask &= ((((W[53]^(W[56]>>25))&(1<<4))-(1<<4)) | ~(DV_II_49_0_bit|DV_II_51_0_bit)); - mask &= ((((W[51]^(W[50]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); - mask &= ((((W[48]^W[50])&(1<<6))-(1<<6)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); - if (mask & (DV_I_51_0_bit|DV_I_52_0_bit)) - mask &= ((0-(((W[48]^W[55])>>29)&1)) | ~(DV_I_51_0_bit|DV_I_52_0_bit)); - mask &= ((((W[47]^W[49])&(1<<6))-(1<<6)) | ~(DV_I_49_2_bit|DV_I_51_2_bit)); - mask &= ((((W[48]^(W[47]>>5))&(1<<1))-(1<<1)) | ~(DV_I_47_2_bit|DV_II_51_2_bit)); - mask &= ((((W[46]^W[48])&(1<<6))-(1<<6)) | ~(DV_I_48_2_bit|DV_I_50_2_bit)); - mask &= ((((W[47]^(W[46]>>5))&(1<<1))-(1<<1)) | ~(DV_I_46_2_bit|DV_II_50_2_bit)); - mask &= ((0-((W[44]^(W[45]>>5))&(1<<1))) | ~(DV_I_51_2_bit|DV_II_49_2_bit)); - mask &= ((((W[43]^W[45])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit)); - mask &= (((((W[42]^W[44])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit)); - mask &= ((((W[43]^(W[42]>>5))&(1<<1))-(1<<1)) | ~(DV_II_46_2_bit|DV_II_51_2_bit)); - mask &= ((((W[42]^(W[41]>>5))&(1<<1))-(1<<1)) | ~(DV_I_51_2_bit|DV_II_50_2_bit)); - mask &= ((((W[41]^(W[40]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_49_2_bit)); - if (mask & (DV_I_52_0_bit|DV_II_51_0_bit)) - mask &= ((((W[39]^(W[43]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_51_0_bit)); - if (mask & (DV_I_51_0_bit|DV_II_50_0_bit)) - mask &= ((((W[38]^(W[42]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_50_0_bit)); - if (mask & (DV_I_48_2_bit|DV_I_51_2_bit)) - mask &= ((0-((W[37]^(W[38]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_I_51_2_bit)); - if (mask & (DV_I_50_0_bit|DV_II_49_0_bit)) - mask &= ((((W[37]^(W[41]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_II_49_0_bit)); - if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) - mask &= ((0-((W[36]^W[38])&(1<<4))) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); - mask &= ((0-((W[35]^(W[36]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_49_2_bit)); - if (mask & (DV_I_51_0_bit|DV_II_47_0_bit)) - mask &= ((((W[35]^(W[39]>>25))&(1<<3))-(1<<3)) | ~(DV_I_51_0_bit|DV_II_47_0_bit)); -if (mask) { - - if (mask & DV_I_43_0_bit) - if ( - !((W[61]^(W[62]>>5)) & (1<<1)) - || !(!((W[59]^(W[63]>>25)) & (1<<5))) - || !((W[58]^(W[63]>>30)) & (1<<0)) - ) mask &= ~DV_I_43_0_bit; - if (mask & DV_I_44_0_bit) - if ( - !((W[62]^(W[63]>>5)) & (1<<1)) - || !(!((W[60]^(W[64]>>25)) & (1<<5))) - || !((W[59]^(W[64]>>30)) & (1<<0)) - ) mask &= ~DV_I_44_0_bit; - if (mask & DV_I_46_2_bit) - mask &= ((~((W[40]^W[42])>>2)) | ~DV_I_46_2_bit); - if (mask & DV_I_47_2_bit) - if ( - !((W[62]^(W[63]>>5)) & (1<<2)) - || !(!((W[41]^W[43]) & (1<<6))) - ) mask &= ~DV_I_47_2_bit; - if (mask & DV_I_48_2_bit) - if ( - !((W[63]^(W[64]>>5)) & (1<<2)) - || !(!((W[48]^(W[49]<<5)) & (1<<6))) - ) mask &= ~DV_I_48_2_bit; - if (mask & DV_I_49_2_bit) - if ( - !(!((W[49]^(W[50]<<5)) & (1<<6))) - || !((W[42]^W[50]) & (1<<1)) - || !(!((W[39]^(W[40]<<5)) & (1<<6))) - || !((W[38]^W[40]) & (1<<1)) - ) mask &= ~DV_I_49_2_bit; - if (mask & DV_I_50_0_bit) - mask &= ((((W[36]^W[37])<<7)) | ~DV_I_50_0_bit); - if (mask & DV_I_50_2_bit) - mask &= ((((W[43]^W[51])<<11)) | ~DV_I_50_2_bit); - if (mask & DV_I_51_0_bit) - mask &= ((((W[37]^W[38])<<9)) | ~DV_I_51_0_bit); - if (mask & DV_I_51_2_bit) - if ( - !(!((W[51]^(W[52]<<5)) & (1<<6))) - || !(!((W[49]^W[51]) & (1<<6))) - || !(!((W[37]^(W[37]>>5)) & (1<<1))) - || !(!((W[35]^(W[39]>>25)) & (1<<5))) - ) mask &= ~DV_I_51_2_bit; - if (mask & DV_I_52_0_bit) - mask &= ((((W[38]^W[39])<<11)) | ~DV_I_52_0_bit); - if (mask & DV_II_46_2_bit) - mask &= ((((W[47]^W[51])<<17)) | ~DV_II_46_2_bit); - if (mask & DV_II_48_0_bit) - if ( - !(!((W[36]^(W[40]>>25)) & (1<<3))) - || !((W[35]^(W[40]<<2)) & (1<<30)) - ) mask &= ~DV_II_48_0_bit; - if (mask & DV_II_49_0_bit) - if ( - !(!((W[37]^(W[41]>>25)) & (1<<3))) - || !((W[36]^(W[41]<<2)) & (1<<30)) - ) mask &= ~DV_II_49_0_bit; - if (mask & DV_II_49_2_bit) - if ( - !(!((W[53]^(W[54]<<5)) & (1<<6))) - || !(!((W[51]^W[53]) & (1<<6))) - || !((W[50]^W[54]) & (1<<1)) - || !(!((W[45]^(W[46]<<5)) & (1<<6))) - || !(!((W[37]^(W[41]>>25)) & (1<<5))) - || !((W[36]^(W[41]>>30)) & (1<<0)) - ) mask &= ~DV_II_49_2_bit; - if (mask & DV_II_50_0_bit) - if ( - !((W[55]^W[58]) & (1<<29)) - || !(!((W[38]^(W[42]>>25)) & (1<<3))) - || !((W[37]^(W[42]<<2)) & (1<<30)) - ) mask &= ~DV_II_50_0_bit; - if (mask & DV_II_50_2_bit) - if ( - !(!((W[54]^(W[55]<<5)) & (1<<6))) - || !(!((W[52]^W[54]) & (1<<6))) - || !((W[51]^W[55]) & (1<<1)) - || !((W[45]^W[47]) & (1<<1)) - || !(!((W[38]^(W[42]>>25)) & (1<<5))) - || !((W[37]^(W[42]>>30)) & (1<<0)) - ) mask &= ~DV_II_50_2_bit; - if (mask & DV_II_51_0_bit) - if ( - !(!((W[39]^(W[43]>>25)) & (1<<3))) - || !((W[38]^(W[43]<<2)) & (1<<30)) - ) mask &= ~DV_II_51_0_bit; - if (mask & DV_II_51_2_bit) - if ( - !(!((W[55]^(W[56]<<5)) & (1<<6))) - || !(!((W[53]^W[55]) & (1<<6))) - || !((W[52]^W[56]) & (1<<1)) - || !((W[46]^W[48]) & (1<<1)) - || !(!((W[39]^(W[43]>>25)) & (1<<5))) - || !((W[38]^(W[43]>>30)) & (1<<0)) - ) mask &= ~DV_II_51_2_bit; - if (mask & DV_II_52_0_bit) - if ( - !(!((W[59]^W[60]) & (1<<29))) - || !(!((W[40]^(W[44]>>25)) & (1<<3))) - || !(!((W[40]^(W[44]>>25)) & (1<<4))) - || !((W[39]^(W[44]<<2)) & (1<<30)) - ) mask &= ~DV_II_52_0_bit; - if (mask & DV_II_53_0_bit) - if ( - !((W[58]^W[61]) & (1<<29)) - || !(!((W[57]^(W[61]>>25)) & (1<<4))) - || !(!((W[41]^(W[45]>>25)) & (1<<3))) - || !(!((W[41]^(W[45]>>25)) & (1<<4))) - ) mask &= ~DV_II_53_0_bit; - if (mask & DV_II_54_0_bit) - if ( - !(!((W[58]^(W[62]>>25)) & (1<<4))) - || !(!((W[42]^(W[46]>>25)) & (1<<3))) - || !(!((W[42]^(W[46]>>25)) & (1<<4))) - ) mask &= ~DV_II_54_0_bit; - if (mask & DV_II_55_0_bit) - if ( - !(!((W[59]^(W[63]>>25)) & (1<<4))) - || !(!((W[57]^(W[59]>>25)) & (1<<4))) - || !(!((W[43]^(W[47]>>25)) & (1<<3))) - || !(!((W[43]^(W[47]>>25)) & (1<<4))) - ) mask &= ~DV_II_55_0_bit; - if (mask & DV_II_56_0_bit) - if ( - !(!((W[60]^(W[64]>>25)) & (1<<4))) - || !(!((W[44]^(W[48]>>25)) & (1<<3))) - || !(!((W[44]^(W[48]>>25)) & (1<<4))) - ) mask &= ~DV_II_56_0_bit; -} - - dvmask[0]=mask; -} - -#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C -#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C -#endif diff --git a/src/libgit2/hash/sha1/sha1dc/ubc_check.h b/src/libgit2/hash/sha1/sha1dc/ubc_check.h deleted file mode 100644 index d7e17dc73..000000000 --- a/src/libgit2/hash/sha1/sha1dc/ubc_check.h +++ /dev/null @@ -1,52 +0,0 @@ -/*** -* Copyright 2017 Marc Stevens , Dan Shumow -* Distributed under the MIT Software License. -* See accompanying file LICENSE.txt or copy at -* https://opensource.org/licenses/MIT -***/ - -/* -// this file was generated by the 'parse_bitrel' program in the tools section -// using the data files from directory 'tools/data/3565' -// -// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check -// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) -// dm[80] is the expanded message block XOR-difference defined by the DV -// testt is the step to do the recompression from for collision detection -// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check -// -// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs -// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met -// thus one needs to do the recompression check for each DV that has its bit set -*/ - -#ifndef SHA1DC_UBC_CHECK_H -#define SHA1DC_UBC_CHECK_H - -#if defined(__cplusplus) -extern "C" { -#endif - -#ifndef SHA1DC_NO_STANDARD_INCLUDES -#include -#endif - -#define DVMASKSIZE 1 -typedef struct { int dvType; int dvK; int dvB; int testt; int maski; int maskb; uint32_t dm[80]; } dv_info_t; -extern dv_info_t sha1_dvs[]; -void ubc_check(const uint32_t W[80], uint32_t dvmask[DVMASKSIZE]); - -#define DOSTORESTATE58 -#define DOSTORESTATE65 - -#define CHECK_DVMASK(_DVMASK) (0 != _DVMASK[0]) - -#if defined(__cplusplus) -} -#endif - -#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H -#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H -#endif - -#endif diff --git a/src/libgit2/hash/sha1/win32.c b/src/libgit2/hash/sha1/win32.c deleted file mode 100644 index b89dfbad8..000000000 --- a/src/libgit2/hash/sha1/win32.c +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "win32.h" - -#include "runtime.h" - -#include -#include - -#define GIT_HASH_CNG_DLL_NAME "bcrypt.dll" - -/* BCRYPT_SHA1_ALGORITHM */ -#define GIT_HASH_CNG_HASH_TYPE L"SHA1" - -/* BCRYPT_OBJECT_LENGTH */ -#define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength" - -/* BCRYPT_HASH_REUSEABLE_FLAGS */ -#define GIT_HASH_CNG_HASH_REUSABLE 0x00000020 - -static git_hash_prov hash_prov = {0}; - -/* Hash initialization */ - -/* Initialize CNG, if available */ -GIT_INLINE(int) hash_cng_prov_init(void) -{ - char dll_path[MAX_PATH]; - DWORD dll_path_len, size_len; - - /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */ - if (!git_has_win32_version(6, 0, 1)) { - git_error_set(GIT_ERROR_SHA1, "CryptoNG is not supported on this platform"); - return -1; - } - - /* Load bcrypt.dll explicitly from the system directory */ - if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || - dll_path_len > MAX_PATH || - StringCchCat(dll_path, MAX_PATH, "\\") < 0 || - StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 || - (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) { - git_error_set(GIT_ERROR_SHA1, "CryptoNG library could not be loaded"); - return -1; - } - - /* Load the function addresses */ - if ((hash_prov.prov.cng.open_algorithm_provider = (hash_win32_cng_open_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptOpenAlgorithmProvider")) == NULL || - (hash_prov.prov.cng.get_property = (hash_win32_cng_get_property_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptGetProperty")) == NULL || - (hash_prov.prov.cng.create_hash = (hash_win32_cng_create_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCreateHash")) == NULL || - (hash_prov.prov.cng.finish_hash = (hash_win32_cng_finish_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptFinishHash")) == NULL || - (hash_prov.prov.cng.hash_data = (hash_win32_cng_hash_data_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptHashData")) == NULL || - (hash_prov.prov.cng.destroy_hash = (hash_win32_cng_destroy_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptDestroyHash")) == NULL || - (hash_prov.prov.cng.close_algorithm_provider = (hash_win32_cng_close_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCloseAlgorithmProvider")) == NULL) { - FreeLibrary(hash_prov.prov.cng.dll); - - git_error_set(GIT_ERROR_OS, "CryptoNG functions could not be loaded"); - return -1; - } - - /* Load the SHA1 algorithm */ - if (hash_prov.prov.cng.open_algorithm_provider(&hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0) { - FreeLibrary(hash_prov.prov.cng.dll); - - git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized"); - return -1; - } - - /* Get storage space for the hash object */ - if (hash_prov.prov.cng.get_property(hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_prov.prov.cng.hash_object_size, sizeof(DWORD), &size_len, 0) < 0) { - hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); - FreeLibrary(hash_prov.prov.cng.dll); - - git_error_set(GIT_ERROR_OS, "algorithm handle could not be found"); - return -1; - } - - hash_prov.type = CNG; - return 0; -} - -GIT_INLINE(void) hash_cng_prov_shutdown(void) -{ - hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); - FreeLibrary(hash_prov.prov.cng.dll); - - hash_prov.type = INVALID; -} - -/* Initialize CryptoAPI */ -GIT_INLINE(int) hash_cryptoapi_prov_init() -{ - if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { - git_error_set(GIT_ERROR_OS, "legacy hash context could not be started"); - return -1; - } - - hash_prov.type = CRYPTOAPI; - return 0; -} - -GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void) -{ - CryptReleaseContext(hash_prov.prov.cryptoapi.handle, 0); - - hash_prov.type = INVALID; -} - -static void sha1_shutdown(void) -{ - if (hash_prov.type == CNG) - hash_cng_prov_shutdown(); - else if(hash_prov.type == CRYPTOAPI) - hash_cryptoapi_prov_shutdown(); -} - -int git_hash_sha1_global_init(void) -{ - int error = 0; - - if (hash_prov.type != INVALID) - return 0; - - if ((error = hash_cng_prov_init()) < 0) - error = hash_cryptoapi_prov_init(); - - if (!error) - error = git_runtime_shutdown_register(sha1_shutdown); - - return error; -} - -/* CryptoAPI: available in Windows XP and newer */ - -GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_sha1_ctx *ctx) -{ - ctx->type = CRYPTOAPI; - ctx->prov = &hash_prov; - - return git_hash_sha1_init(ctx); -} - -GIT_INLINE(int) hash_cryptoapi_init(git_hash_sha1_ctx *ctx) -{ - if (ctx->ctx.cryptoapi.valid) - CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); - - if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) { - ctx->ctx.cryptoapi.valid = 0; - git_error_set(GIT_ERROR_OS, "legacy hash implementation could not be created"); - return -1; - } - - ctx->ctx.cryptoapi.valid = 1; - return 0; -} - -GIT_INLINE(int) hash_cryptoapi_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) -{ - const BYTE *data = (BYTE *)_data; - - GIT_ASSERT(ctx->ctx.cryptoapi.valid); - - while (len > 0) { - DWORD chunk = (len > MAXDWORD) ? MAXDWORD : (DWORD)len; - - if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, data, chunk, 0)) { - git_error_set(GIT_ERROR_OS, "legacy hash data could not be updated"); - return -1; - } - - data += chunk; - len -= chunk; - } - - return 0; -} - -GIT_INLINE(int) hash_cryptoapi_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - DWORD len = GIT_HASH_SHA1_SIZE; - int error = 0; - - GIT_ASSERT(ctx->ctx.cryptoapi.valid); - - if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out, &len, 0)) { - git_error_set(GIT_ERROR_OS, "legacy hash data could not be finished"); - error = -1; - } - - CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); - ctx->ctx.cryptoapi.valid = 0; - - return error; -} - -GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_sha1_ctx *ctx) -{ - if (ctx->ctx.cryptoapi.valid) - CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); -} - -/* CNG: Available in Windows Server 2008 and newer */ - -GIT_INLINE(int) hash_ctx_cng_init(git_hash_sha1_ctx *ctx) -{ - if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL) - return -1; - - if (hash_prov.prov.cng.create_hash(hash_prov.prov.cng.handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_prov.prov.cng.hash_object_size, NULL, 0, 0) < 0) { - git__free(ctx->ctx.cng.hash_object); - - git_error_set(GIT_ERROR_OS, "hash implementation could not be created"); - return -1; - } - - ctx->type = CNG; - ctx->prov = &hash_prov; - - return 0; -} - -GIT_INLINE(int) hash_cng_init(git_hash_sha1_ctx *ctx) -{ - BYTE hash[GIT_OID_RAWSZ]; - - if (!ctx->ctx.cng.updated) - return 0; - - /* CNG needs to be finished to restart */ - if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0) { - git_error_set(GIT_ERROR_OS, "hash implementation could not be finished"); - return -1; - } - - ctx->ctx.cng.updated = 0; - - return 0; -} - -GIT_INLINE(int) hash_cng_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) -{ - PBYTE data = (PBYTE)_data; - - while (len > 0) { - ULONG chunk = (len > ULONG_MAX) ? ULONG_MAX : (ULONG)len; - - if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, data, chunk, 0) < 0) { - git_error_set(GIT_ERROR_OS, "hash could not be updated"); - return -1; - } - - data += chunk; - len -= chunk; - } - - return 0; -} - -GIT_INLINE(int) hash_cng_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out, GIT_HASH_SHA1_SIZE, 0) < 0) { - git_error_set(GIT_ERROR_OS, "hash could not be finished"); - return -1; - } - - ctx->ctx.cng.updated = 0; - - return 0; -} - -GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_sha1_ctx *ctx) -{ - ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle); - git__free(ctx->ctx.cng.hash_object); -} - -/* Indirection between CryptoAPI and CNG */ - -int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) -{ - int error = 0; - - GIT_ASSERT_ARG(ctx); - - /* - * When compiled with GIT_THREADS, the global hash_prov data is - * initialized with git_libgit2_init. Otherwise, it must be initialized - * at first use. - */ - if (hash_prov.type == INVALID && (error = git_hash_sha1_global_init()) < 0) - return error; - - memset(ctx, 0x0, sizeof(git_hash_sha1_ctx)); - - return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx); -} - -int git_hash_sha1_init(git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(ctx->type); - return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx); -} - -int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) -{ - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(ctx->type); - return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len); -} - -int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) -{ - GIT_ASSERT_ARG(ctx); - GIT_ASSERT_ARG(ctx->type); - return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx); -} - -void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) -{ - if (!ctx) - return; - else if (ctx->type == CNG) - hash_ctx_cng_cleanup(ctx); - else if(ctx->type == CRYPTOAPI) - hash_ctx_cryptoapi_cleanup(ctx); -} diff --git a/src/libgit2/hash/sha1/win32.h b/src/libgit2/hash/sha1/win32.h deleted file mode 100644 index 791d20a42..000000000 --- a/src/libgit2/hash/sha1/win32.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_hash_sha1_win32_h__ -#define INCLUDE_hash_sha1_win32_h__ - -#include "hash/sha1.h" - -#include -#include - -enum hash_win32_prov_type { - INVALID = 0, - CRYPTOAPI, - CNG -}; - -/* - * CryptoAPI is available for hashing on Windows XP and newer. - */ - -struct hash_cryptoapi_prov { - HCRYPTPROV handle; -}; - -/* - * CNG (bcrypt.dll) is significantly more performant than CryptoAPI and is - * preferred, however it is only available on Windows 2008 and newer and - * must therefore be dynamically loaded, and we must inline constants that - * would not exist when building in pre-Windows 2008 environments. - */ - -/* Function declarations for CNG */ -typedef NTSTATUS (WINAPI *hash_win32_cng_open_algorithm_provider_fn)( - HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm, - LPCWSTR pszAlgId, - LPCWSTR pszImplementation, - DWORD dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_get_property_fn)( - HANDLE /* BCRYPT_HANDLE */ hObject, - LPCWSTR pszProperty, - PUCHAR pbOutput, - ULONG cbOutput, - ULONG *pcbResult, - ULONG dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_create_hash_fn)( - HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, - HANDLE /* BCRYPT_HASH_HANDLE */ *phHash, - PUCHAR pbHashObject, ULONG cbHashObject, - PUCHAR pbSecret, - ULONG cbSecret, - ULONG dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_finish_hash_fn)( - HANDLE /* BCRYPT_HASH_HANDLE */ hHash, - PUCHAR pbOutput, - ULONG cbOutput, - ULONG dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_hash_data_fn)( - HANDLE /* BCRYPT_HASH_HANDLE */ hHash, - PUCHAR pbInput, - ULONG cbInput, - ULONG dwFlags); - -typedef NTSTATUS (WINAPI *hash_win32_cng_destroy_hash_fn)( - HANDLE /* BCRYPT_HASH_HANDLE */ hHash); - -typedef NTSTATUS (WINAPI *hash_win32_cng_close_algorithm_provider_fn)( - HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, - ULONG dwFlags); - -struct hash_cng_prov { - /* DLL for CNG */ - HINSTANCE dll; - - /* Function pointers for CNG */ - hash_win32_cng_open_algorithm_provider_fn open_algorithm_provider; - hash_win32_cng_get_property_fn get_property; - hash_win32_cng_create_hash_fn create_hash; - hash_win32_cng_finish_hash_fn finish_hash; - hash_win32_cng_hash_data_fn hash_data; - hash_win32_cng_destroy_hash_fn destroy_hash; - hash_win32_cng_close_algorithm_provider_fn close_algorithm_provider; - - HANDLE /* BCRYPT_ALG_HANDLE */ handle; - DWORD hash_object_size; -}; - -typedef struct { - enum hash_win32_prov_type type; - - union { - struct hash_cryptoapi_prov cryptoapi; - struct hash_cng_prov cng; - } prov; -} git_hash_prov; - -/* Hash contexts */ - -struct hash_cryptoapi_ctx { - bool valid; - HCRYPTHASH hash_handle; -}; - -struct hash_cng_ctx { - bool updated; - HANDLE /* BCRYPT_HASH_HANDLE */ hash_handle; - PBYTE hash_object; -}; - -struct git_hash_sha1_ctx { - enum hash_win32_prov_type type; - git_hash_prov *prov; - - union { - struct hash_cryptoapi_ctx cryptoapi; - struct hash_cng_ctx cng; - } ctx; -}; - -#endif diff --git a/src/libgit2/integer.h b/src/libgit2/integer.h deleted file mode 100644 index 63277177b..000000000 --- a/src/libgit2/integer.h +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_integer_h__ -#define INCLUDE_integer_h__ - -/** @return true if p fits into the range of a size_t */ -GIT_INLINE(int) git__is_sizet(int64_t p) -{ - size_t r = (size_t)p; - return p == (int64_t)r; -} - -/** @return true if p fits into the range of an ssize_t */ -GIT_INLINE(int) git__is_ssizet(size_t p) -{ - ssize_t r = (ssize_t)p; - return p == (size_t)r; -} - -/** @return true if p fits into the range of a uint16_t */ -GIT_INLINE(int) git__is_uint16(size_t p) -{ - uint16_t r = (uint16_t)p; - return p == (size_t)r; -} - -/** @return true if p fits into the range of a uint32_t */ -GIT_INLINE(int) git__is_uint32(size_t p) -{ - uint32_t r = (uint32_t)p; - return p == (size_t)r; -} - -/** @return true if p fits into the range of an unsigned long */ -GIT_INLINE(int) git__is_ulong(int64_t p) -{ - unsigned long r = (unsigned long)p; - return p == (int64_t)r; -} - -/** @return true if p fits into the range of an int */ -GIT_INLINE(int) git__is_int(int64_t p) -{ - int r = (int)p; - return p == (int64_t)r; -} - -/* Use clang/gcc compiler intrinsics whenever possible */ -#if (__has_builtin(__builtin_add_overflow) || \ - (defined(__GNUC__) && (__GNUC__ >= 5))) - -# if (SIZE_MAX == UINT_MAX) -# define git__add_sizet_overflow(out, one, two) \ - __builtin_uadd_overflow(one, two, out) -# define git__multiply_sizet_overflow(out, one, two) \ - __builtin_umul_overflow(one, two, out) -# elif (SIZE_MAX == ULONG_MAX) -# define git__add_sizet_overflow(out, one, two) \ - __builtin_uaddl_overflow(one, two, out) -# define git__multiply_sizet_overflow(out, one, two) \ - __builtin_umull_overflow(one, two, out) -# elif (SIZE_MAX == ULLONG_MAX) -# define git__add_sizet_overflow(out, one, two) \ - __builtin_uaddll_overflow(one, two, out) -# define git__multiply_sizet_overflow(out, one, two) \ - __builtin_umulll_overflow(one, two, out) -# else -# error compiler has add with overflow intrinsics but SIZE_MAX is unknown -# endif - -# define git__add_int_overflow(out, one, two) \ - __builtin_sadd_overflow(one, two, out) -# define git__sub_int_overflow(out, one, two) \ - __builtin_ssub_overflow(one, two, out) - -# define git__add_int64_overflow(out, one, two) \ - __builtin_add_overflow(one, two, out) - -/* clang on 32-bit systems produces an undefined reference to `__mulodi4`. */ -# if !defined(__clang__) || !defined(GIT_ARCH_32) -# define git__multiply_int64_overflow(out, one, two) \ - __builtin_mul_overflow(one, two, out) -# endif - -/* Use Microsoft's safe integer handling functions where available */ -#elif defined(_MSC_VER) - -# define ENABLE_INTSAFE_SIGNED_FUNCTIONS -# include - -# define git__add_sizet_overflow(out, one, two) \ - (SizeTAdd(one, two, out) != S_OK) -# define git__multiply_sizet_overflow(out, one, two) \ - (SizeTMult(one, two, out) != S_OK) - -#define git__add_int_overflow(out, one, two) \ - (IntAdd(one, two, out) != S_OK) -#define git__sub_int_overflow(out, one, two) \ - (IntSub(one, two, out) != S_OK) - -#define git__add_int64_overflow(out, one, two) \ - (LongLongAdd(one, two, out) != S_OK) -#define git__multiply_int64_overflow(out, one, two) \ - (LongLongMult(one, two, out) != S_OK) - -#else - -/** - * Sets `one + two` into `out`, unless the arithmetic would overflow. - * @return false if the result fits in a `size_t`, true on overflow. - */ -GIT_INLINE(bool) git__add_sizet_overflow(size_t *out, size_t one, size_t two) -{ - if (SIZE_MAX - one < two) - return true; - *out = one + two; - return false; -} - -/** - * Sets `one * two` into `out`, unless the arithmetic would overflow. - * @return false if the result fits in a `size_t`, true on overflow. - */ -GIT_INLINE(bool) git__multiply_sizet_overflow(size_t *out, size_t one, size_t two) -{ - if (one && SIZE_MAX / one < two) - return true; - *out = one * two; - return false; -} - -GIT_INLINE(bool) git__add_int_overflow(int *out, int one, int two) -{ - if ((two > 0 && one > (INT_MAX - two)) || - (two < 0 && one < (INT_MIN - two))) - return true; - *out = one + two; - return false; -} - -GIT_INLINE(bool) git__sub_int_overflow(int *out, int one, int two) -{ - if ((two > 0 && one < (INT_MIN + two)) || - (two < 0 && one > (INT_MAX + two))) - return true; - *out = one - two; - return false; -} - -GIT_INLINE(bool) git__add_int64_overflow(int64_t *out, int64_t one, int64_t two) -{ - if ((two > 0 && one > (INT64_MAX - two)) || - (two < 0 && one < (INT64_MIN - two))) - return true; - *out = one + two; - return false; -} - -#endif - -/* If we could not provide an intrinsic implementation for this, provide a (slow) fallback. */ -#if !defined(git__multiply_int64_overflow) -GIT_INLINE(bool) git__multiply_int64_overflow(int64_t *out, int64_t one, int64_t two) -{ - /* - * Detects whether `INT64_MAX < (one * two) || INT64_MIN > (one * two)`, - * without incurring in undefined behavior. That is done by performing the - * comparison with a division instead of a multiplication, which translates - * to `INT64_MAX / one < two || INT64_MIN / one > two`. Some caveats: - * - * - The comparison sign is inverted when both sides of the inequality are - * multiplied/divided by a negative number, so if `one < 0` the comparison - * needs to be flipped. - * - `INT64_MAX / -1` itself overflows (or traps), so that case should be - * avoided. - * - Since the overflow flag is defined as the discrepance between the result - * of performing the multiplication in a signed integer at twice the width - * of the operands, and the truncated+sign-extended version of that same - * result, there are four cases where the result is the opposite of what - * would be expected: - * * `INT64_MIN * -1` / `-1 * INT64_MIN` - * * `INT64_MIN * 1 / `1 * INT64_MIN` - */ - if (one && two) { - if (one > 0 && two > 0) { - if (INT64_MAX / one < two) - return true; - } else if (one < 0 && two < 0) { - if ((one == -1 && two == INT64_MIN) || - (two == -1 && one == INT64_MIN)) { - *out = INT64_MIN; - return false; - } - if (INT64_MAX / one > two) - return true; - } else if (one > 0 && two < 0) { - if ((one == 1 && two == INT64_MIN) || - (INT64_MIN / one > two)) - return true; - } else if (one == -1) { - if (INT64_MIN / two > one) - return true; - } else { - if ((one == INT64_MIN && two == 1) || - (INT64_MIN / one < two)) - return true; - } - } - *out = one * two; - return false; -} -#endif - -#endif diff --git a/src/libgit2/khash.h b/src/libgit2/khash.h deleted file mode 100644 index c9b7f131f..000000000 --- a/src/libgit2/khash.h +++ /dev/null @@ -1,615 +0,0 @@ -/* The MIT License - - Copyright (c) 2008, 2009, 2011 by Attractive Chaos - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - -/* - An example: - -#include "khash.h" -KHASH_MAP_INIT_INT(32, char) -int main() { - int ret, is_missing; - khiter_t k; - khash_t(32) *h = kh_init(32); - k = kh_put(32, h, 5, &ret); - kh_value(h, k) = 10; - k = kh_get(32, h, 10); - is_missing = (k == kh_end(h)); - k = kh_get(32, h, 5); - kh_del(32, h, k); - for (k = kh_begin(h); k != kh_end(h); ++k) - if (kh_exist(h, k)) kh_value(h, k) = 1; - kh_destroy(32, h); - return 0; -} -*/ - -/* - 2013-05-02 (0.2.8): - - * Use quadratic probing. When the capacity is power of 2, stepping function - i*(i+1)/2 guarantees to traverse each bucket. It is better than double - hashing on cache performance and is more robust than linear probing. - - In theory, double hashing should be more robust than quadratic probing. - However, my implementation is probably not for large hash tables, because - the second hash function is closely tied to the first hash function, - which reduce the effectiveness of double hashing. - - Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php - - 2011-12-29 (0.2.7): - - * Minor code clean up; no actual effect. - - 2011-09-16 (0.2.6): - - * The capacity is a power of 2. This seems to dramatically improve the - speed for simple keys. Thank Zilong Tan for the suggestion. Reference: - - - http://code.google.com/p/ulib/ - - http://nothings.org/computer/judy/ - - * Allow to optionally use linear probing which usually has better - performance for random input. Double hashing is still the default as it - is more robust to certain non-random input. - - * Added Wang's integer hash function (not used by default). This hash - function is more robust to certain non-random input. - - 2011-02-14 (0.2.5): - - * Allow to declare global functions. - - 2009-09-26 (0.2.4): - - * Improve portability - - 2008-09-19 (0.2.3): - - * Corrected the example - * Improved interfaces - - 2008-09-11 (0.2.2): - - * Improved speed a little in kh_put() - - 2008-09-10 (0.2.1): - - * Added kh_clear() - * Fixed a compiling error - - 2008-09-02 (0.2.0): - - * Changed to token concatenation which increases flexibility. - - 2008-08-31 (0.1.2): - - * Fixed a bug in kh_get(), which has not been tested previously. - - 2008-08-31 (0.1.1): - - * Added destructor -*/ - - -#ifndef __AC_KHASH_H -#define __AC_KHASH_H - -/*! - @header - - Generic hash table library. - */ - -#define AC_VERSION_KHASH_H "0.2.8" - -#include -#include -#include - -/* compiler specific configuration */ - -typedef uint32_t khint32_t; -typedef uint64_t khint64_t; - -#ifndef kh_inline -#ifdef _MSC_VER -#define kh_inline __inline -#elif defined(__GNUC__) -#define kh_inline __inline__ -#else -#define kh_inline -#endif -#endif /* kh_inline */ - -typedef khint32_t khint_t; -typedef khint_t khiter_t; - -#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) -#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) -#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) -#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) -#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) -#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) -#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) - -#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) - -#ifndef kroundup32 -#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) -#endif - -#ifndef kcalloc -#define kcalloc(N,Z) calloc(N,Z) -#endif -#ifndef kmalloc -#define kmalloc(Z) malloc(Z) -#endif -#ifndef krealloc -#define krealloc(P,Z) realloc(P,Z) -#endif -#ifndef kreallocarray -#define kreallocarray(P,N,Z) ((SIZE_MAX - N < Z) ? NULL : krealloc(P, (N*Z))) -#endif -#ifndef kfree -#define kfree(P) free(P) -#endif - -static const double __ac_HASH_UPPER = 0.77; - -#define __KHASH_TYPE(name, khkey_t, khval_t) \ - typedef struct kh_##name##_s { \ - khint_t n_buckets, size, n_occupied, upper_bound; \ - khint32_t *flags; \ - khkey_t *keys; \ - khval_t *vals; \ - } kh_##name##_t; - -#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ - extern kh_##name##_t *kh_init_##name(void); \ - extern void kh_destroy_##name(kh_##name##_t *h); \ - extern void kh_clear_##name(kh_##name##_t *h); \ - extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ - extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ - extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ - extern void kh_del_##name(kh_##name##_t *h, khint_t x); - -#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - SCOPE kh_##name##_t *kh_init_##name(void) { \ - return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ - } \ - SCOPE void kh_destroy_##name(kh_##name##_t *h) \ - { \ - if (h) { \ - kfree((void *)h->keys); kfree(h->flags); \ - kfree((void *)h->vals); \ - kfree(h); \ - } \ - } \ - SCOPE void kh_clear_##name(kh_##name##_t *h) \ - { \ - if (h && h->flags) { \ - memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ - h->size = h->n_occupied = 0; \ - } \ - } \ - SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ - { \ - if (h->n_buckets) { \ - khint_t k, i, last, mask, step = 0; \ - mask = h->n_buckets - 1; \ - k = __hash_func(key); i = k & mask; \ - last = i; \ - while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ - i = (i + (++step)) & mask; \ - if (i == last) return h->n_buckets; \ - } \ - return __ac_iseither(h->flags, i)? h->n_buckets : i; \ - } else return 0; \ - } \ - SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ - { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ - khint32_t *new_flags = 0; \ - khint_t j = 1; \ - { \ - kroundup32(new_n_buckets); \ - if (new_n_buckets < 4) new_n_buckets = 4; \ - if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ - else { /* hash table size to be changed (shrink or expand); rehash */ \ - new_flags = (khint32_t*)kreallocarray(NULL, __ac_fsize(new_n_buckets), sizeof(khint32_t)); \ - if (!new_flags) return -1; \ - memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ - if (h->n_buckets < new_n_buckets) { /* expand */ \ - khkey_t *new_keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ - if (!new_keys) { kfree(new_flags); return -1; } \ - h->keys = new_keys; \ - if (kh_is_map) { \ - khval_t *new_vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ - if (!new_vals) { kfree(new_flags); return -1; } \ - h->vals = new_vals; \ - } \ - } /* otherwise shrink */ \ - } \ - } \ - if (j) { /* rehashing is needed */ \ - for (j = 0; j != h->n_buckets; ++j) { \ - if (__ac_iseither(h->flags, j) == 0) { \ - khkey_t key = h->keys[j]; \ - khval_t val; \ - khint_t new_mask; \ - new_mask = new_n_buckets - 1; \ - if (kh_is_map) val = h->vals[j]; \ - __ac_set_isdel_true(h->flags, j); \ - while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ - khint_t k, i, step = 0; \ - k = __hash_func(key); \ - i = k & new_mask; \ - while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ - __ac_set_isempty_false(new_flags, i); \ - if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ - { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ - if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ - __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ - } else { /* write the element and jump out of the loop */ \ - h->keys[i] = key; \ - if (kh_is_map) h->vals[i] = val; \ - break; \ - } \ - } \ - } \ - } \ - if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ - h->keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ - if (kh_is_map) h->vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ - } \ - kfree(h->flags); /* free the working space */ \ - h->flags = new_flags; \ - h->n_buckets = new_n_buckets; \ - h->n_occupied = h->size; \ - h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ - } \ - return 0; \ - } \ - SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ - { \ - khint_t x; \ - if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ - if (h->n_buckets > (h->size<<1)) { \ - if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ - *ret = -1; return h->n_buckets; \ - } \ - } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ - *ret = -1; return h->n_buckets; \ - } \ - } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ - { \ - khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ - x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ - if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ - else { \ - last = i; \ - while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ - if (__ac_isdel(h->flags, i)) site = i; \ - i = (i + (++step)) & mask; \ - if (i == last) { x = site; break; } \ - } \ - if (x == h->n_buckets) { \ - if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ - else x = i; \ - } \ - } \ - } \ - if (__ac_isempty(h->flags, x)) { /* not present at all */ \ - h->keys[x] = key; \ - __ac_set_isboth_false(h->flags, x); \ - ++h->size; ++h->n_occupied; \ - *ret = 1; \ - } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ - h->keys[x] = key; \ - __ac_set_isboth_false(h->flags, x); \ - ++h->size; \ - *ret = 2; \ - } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ - return x; \ - } \ - SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ - { \ - if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ - __ac_set_isdel_true(h->flags, x); \ - --h->size; \ - } \ - } - -#define KHASH_DECLARE(name, khkey_t, khval_t) \ - __KHASH_TYPE(name, khkey_t, khval_t) \ - __KHASH_PROTOTYPES(name, khkey_t, khval_t) - -#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - __KHASH_TYPE(name, khkey_t, khval_t) \ - __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) - -#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) - -/* --- BEGIN OF HASH FUNCTIONS --- */ - -/*! @function - @abstract Integer hash function - @param key The integer [khint32_t] - @return The hash value [khint_t] - */ -#define kh_int_hash_func(key) (khint32_t)(key) -/*! @function - @abstract Integer comparison function - */ -#define kh_int_hash_equal(a, b) ((a) == (b)) -/*! @function - @abstract 64-bit integer hash function - @param key The integer [khint64_t] - @return The hash value [khint_t] - */ -#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) -/*! @function - @abstract 64-bit integer comparison function - */ -#define kh_int64_hash_equal(a, b) ((a) == (b)) -/*! @function - @abstract const char* hash function - @param s Pointer to a null terminated string - @return The hash value - */ -static kh_inline khint_t __ac_X31_hash_string(const char *s) -{ - khint_t h = (khint_t)*s; - if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; - return h; -} -/*! @function - @abstract Another interface to const char* hash function - @param key Pointer to a null terminated string [const char*] - @return The hash value [khint_t] - */ -#define kh_str_hash_func(key) __ac_X31_hash_string(key) -/*! @function - @abstract Const char* comparison function - */ -#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) - -static kh_inline khint_t __ac_Wang_hash(khint_t key) -{ - key += ~(key << 15); - key ^= (key >> 10); - key += (key << 3); - key ^= (key >> 6); - key += ~(key << 11); - key ^= (key >> 16); - return key; -} -#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) - -/* --- END OF HASH FUNCTIONS --- */ - -/* Other convenient macros... */ - -/*! - @abstract Type of the hash table. - @param name Name of the hash table [symbol] - */ -#define khash_t(name) kh_##name##_t - -/*! @function - @abstract Initiate a hash table. - @param name Name of the hash table [symbol] - @return Pointer to the hash table [khash_t(name)*] - */ -#define kh_init(name) kh_init_##name() - -/*! @function - @abstract Destroy a hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - */ -#define kh_destroy(name, h) kh_destroy_##name(h) - -/*! @function - @abstract Reset a hash table without deallocating memory. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - */ -#define kh_clear(name, h) kh_clear_##name(h) - -/*! @function - @abstract Resize a hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param s New size [khint_t] - */ -#define kh_resize(name, h, s) kh_resize_##name(h, s) - -/*! @function - @abstract Insert a key to the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Key [type of keys] - @param r Extra return code: -1 if the operation failed; - 0 if the key is present in the hash table; - 1 if the bucket is empty (never used); 2 if the element in - the bucket has been deleted [int*] - @return Iterator to the inserted element [khint_t] - */ -#define kh_put(name, h, k, r) kh_put_##name(h, k, r) - -/*! @function - @abstract Retrieve a key from the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Key [type of keys] - @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] - */ -#define kh_get(name, h, k) kh_get_##name(h, k) - -/*! @function - @abstract Remove a key from the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Iterator to the element to be deleted [khint_t] - */ -#define kh_del(name, h, k) kh_del_##name(h, k) - -/*! @function - @abstract Test whether a bucket contains data. - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return 1 if containing data; 0 otherwise [int] - */ -#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) - -/*! @function - @abstract Get key given an iterator - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return Key [type of keys] - */ -#define kh_key(h, x) ((h)->keys[x]) - -/*! @function - @abstract Get value given an iterator - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return Value [type of values] - @discussion For hash sets, calling this results in segfault. - */ -#define kh_val(h, x) ((h)->vals[x]) - -/*! @function - @abstract Alias of kh_val() - */ -#define kh_value(h, x) ((h)->vals[x]) - -/*! @function - @abstract Get the start iterator - @param h Pointer to the hash table [khash_t(name)*] - @return The start iterator [khint_t] - */ -#define kh_begin(h) (khint_t)(0) - -/*! @function - @abstract Get the end iterator - @param h Pointer to the hash table [khash_t(name)*] - @return The end iterator [khint_t] - */ -#define kh_end(h) ((h)->n_buckets) - -/*! @function - @abstract Get the number of elements in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @return Number of elements in the hash table [khint_t] - */ -#define kh_size(h) ((h)->size) - -/*! @function - @abstract Get the number of buckets in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @return Number of buckets in the hash table [khint_t] - */ -#define kh_n_buckets(h) ((h)->n_buckets) - -/*! @function - @abstract Iterate over the entries in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @param kvar Variable to which key will be assigned - @param vvar Variable to which value will be assigned - @param code Block of code to execute - */ -#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ - for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ - if (!kh_exist(h,__i)) continue; \ - (kvar) = kh_key(h,__i); \ - (vvar) = kh_val(h,__i); \ - code; \ - } } - -/*! @function - @abstract Iterate over the values in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @param vvar Variable to which value will be assigned - @param code Block of code to execute - */ -#define kh_foreach_value(h, vvar, code) { khint_t __i; \ - for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ - if (!kh_exist(h,__i)) continue; \ - (vvar) = kh_val(h,__i); \ - code; \ - } } - -/* More convenient interfaces */ - -/*! @function - @abstract Instantiate a hash set containing integer keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_INT(name) \ - KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing integer keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_INT(name, khval_t) \ - KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing 64-bit integer keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_INT64(name) \ - KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing 64-bit integer keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_INT64(name, khval_t) \ - KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) - -typedef const char *kh_cstr_t; -/*! @function - @abstract Instantiate a hash map containing const char* keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_STR(name) \ - KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing const char* keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_STR(name, khval_t) \ - KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) - -#endif /* __AC_KHASH_H */ diff --git a/src/libgit2/map.h b/src/libgit2/map.h deleted file mode 100644 index 01931d199..000000000 --- a/src/libgit2/map.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_map_h__ -#define INCLUDE_map_h__ - -#include "common.h" - - -/* p_mmap() prot values */ -#define GIT_PROT_NONE 0x0 -#define GIT_PROT_READ 0x1 -#define GIT_PROT_WRITE 0x2 -#define GIT_PROT_EXEC 0x4 - -/* git__mmmap() flags values */ -#define GIT_MAP_FILE 0 -#define GIT_MAP_SHARED 1 -#define GIT_MAP_PRIVATE 2 -#define GIT_MAP_TYPE 0xf -#define GIT_MAP_FIXED 0x10 - -#ifdef __amigaos4__ -#define MAP_FAILED 0 -#endif - -typedef struct { /* memory mapped buffer */ - void *data; /* data bytes */ - size_t len; /* data length */ -#ifdef GIT_WIN32 - HANDLE fmh; /* file mapping handle */ -#endif -} git_map; - -#define GIT_MMAP_VALIDATE(out, len, prot, flags) do { \ - GIT_ASSERT(out != NULL && len > 0); \ - GIT_ASSERT((prot & GIT_PROT_WRITE) || (prot & GIT_PROT_READ)); \ - GIT_ASSERT((flags & GIT_MAP_FIXED) == 0); } while (0) - -extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset); -extern int p_munmap(git_map *map); - -#endif diff --git a/src/libgit2/net.c b/src/libgit2/net.c deleted file mode 100644 index a76fd1d7c..000000000 --- a/src/libgit2/net.c +++ /dev/null @@ -1,750 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "net.h" -#include "netops.h" - -#include - -#include "posix.h" -#include "str.h" -#include "http_parser.h" -#include "runtime.h" - -#define DEFAULT_PORT_HTTP "80" -#define DEFAULT_PORT_HTTPS "443" -#define DEFAULT_PORT_GIT "9418" -#define DEFAULT_PORT_SSH "22" - -bool git_net_str_is_url(const char *str) -{ - const char *c; - - for (c = str; *c; c++) { - if (*c == ':' && *(c+1) == '/' && *(c+2) == '/') - return true; - - if ((*c < 'a' || *c > 'z') && - (*c < 'A' || *c > 'Z') && - (*c < '0' || *c > '9') && - (*c != '+' && *c != '-' && *c != '.')) - break; - } - - return false; -} - -static const char *default_port_for_scheme(const char *scheme) -{ - if (strcmp(scheme, "http") == 0) - return DEFAULT_PORT_HTTP; - else if (strcmp(scheme, "https") == 0) - return DEFAULT_PORT_HTTPS; - else if (strcmp(scheme, "git") == 0) - return DEFAULT_PORT_GIT; - else if (strcmp(scheme, "ssh") == 0 || - strcmp(scheme, "ssh+git") == 0 || - strcmp(scheme, "git+ssh") == 0) - return DEFAULT_PORT_SSH; - - return NULL; -} - -int git_net_url_dup(git_net_url *out, git_net_url *in) -{ - if (in->scheme) { - out->scheme = git__strdup(in->scheme); - GIT_ERROR_CHECK_ALLOC(out->scheme); - } - - if (in->host) { - out->host = git__strdup(in->host); - GIT_ERROR_CHECK_ALLOC(out->host); - } - - if (in->port) { - out->port = git__strdup(in->port); - GIT_ERROR_CHECK_ALLOC(out->port); - } - - if (in->path) { - out->path = git__strdup(in->path); - GIT_ERROR_CHECK_ALLOC(out->path); - } - - if (in->query) { - out->query = git__strdup(in->query); - GIT_ERROR_CHECK_ALLOC(out->query); - } - - if (in->username) { - out->username = git__strdup(in->username); - GIT_ERROR_CHECK_ALLOC(out->username); - } - - if (in->password) { - out->password = git__strdup(in->password); - GIT_ERROR_CHECK_ALLOC(out->password); - } - - return 0; -} - -int git_net_url_parse(git_net_url *url, const char *given) -{ - struct http_parser_url u = {0}; - bool has_scheme, has_host, has_port, has_path, has_query, has_userinfo; - git_str scheme = GIT_STR_INIT, - host = GIT_STR_INIT, - port = GIT_STR_INIT, - path = GIT_STR_INIT, - username = GIT_STR_INIT, - password = GIT_STR_INIT, - query = GIT_STR_INIT; - int error = GIT_EINVALIDSPEC; - - if (http_parser_parse_url(given, strlen(given), false, &u)) { - git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given); - goto done; - } - - has_scheme = !!(u.field_set & (1 << UF_SCHEMA)); - has_host = !!(u.field_set & (1 << UF_HOST)); - has_port = !!(u.field_set & (1 << UF_PORT)); - has_path = !!(u.field_set & (1 << UF_PATH)); - has_query = !!(u.field_set & (1 << UF_QUERY)); - has_userinfo = !!(u.field_set & (1 << UF_USERINFO)); - - if (has_scheme) { - const char *url_scheme = given + u.field_data[UF_SCHEMA].off; - size_t url_scheme_len = u.field_data[UF_SCHEMA].len; - git_str_put(&scheme, url_scheme, url_scheme_len); - git__strntolower(scheme.ptr, scheme.size); - } else { - git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given); - goto done; - } - - if (has_host) { - const char *url_host = given + u.field_data[UF_HOST].off; - size_t url_host_len = u.field_data[UF_HOST].len; - git_str_decode_percent(&host, url_host, url_host_len); - } - - if (has_port) { - const char *url_port = given + u.field_data[UF_PORT].off; - size_t url_port_len = u.field_data[UF_PORT].len; - git_str_put(&port, url_port, url_port_len); - } else { - const char *default_port = default_port_for_scheme(scheme.ptr); - - if (default_port == NULL) { - git_error_set(GIT_ERROR_NET, "unknown scheme for URL '%s'", given); - goto done; - } - - git_str_puts(&port, default_port); - } - - if (has_path) { - const char *url_path = given + u.field_data[UF_PATH].off; - size_t url_path_len = u.field_data[UF_PATH].len; - git_str_put(&path, url_path, url_path_len); - } else { - git_str_puts(&path, "/"); - } - - if (has_query) { - const char *url_query = given + u.field_data[UF_QUERY].off; - size_t url_query_len = u.field_data[UF_QUERY].len; - git_str_decode_percent(&query, url_query, url_query_len); - } - - if (has_userinfo) { - const char *url_userinfo = given + u.field_data[UF_USERINFO].off; - size_t url_userinfo_len = u.field_data[UF_USERINFO].len; - const char *colon = memchr(url_userinfo, ':', url_userinfo_len); - - if (colon) { - const char *url_username = url_userinfo; - size_t url_username_len = colon - url_userinfo; - const char *url_password = colon + 1; - size_t url_password_len = url_userinfo_len - (url_username_len + 1); - - git_str_decode_percent(&username, url_username, url_username_len); - git_str_decode_percent(&password, url_password, url_password_len); - } else { - git_str_decode_percent(&username, url_userinfo, url_userinfo_len); - } - } - - if (git_str_oom(&scheme) || - git_str_oom(&host) || - git_str_oom(&port) || - git_str_oom(&path) || - git_str_oom(&query) || - git_str_oom(&username) || - git_str_oom(&password)) - return -1; - - url->scheme = git_str_detach(&scheme); - url->host = git_str_detach(&host); - url->port = git_str_detach(&port); - url->path = git_str_detach(&path); - url->query = git_str_detach(&query); - url->username = git_str_detach(&username); - url->password = git_str_detach(&password); - - error = 0; - -done: - git_str_dispose(&scheme); - git_str_dispose(&host); - git_str_dispose(&port); - git_str_dispose(&path); - git_str_dispose(&query); - git_str_dispose(&username); - git_str_dispose(&password); - return error; -} - -static int scp_invalid(const char *message) -{ - git_error_set(GIT_ERROR_NET, "invalid scp-style path: %s", message); - return GIT_EINVALIDSPEC; -} - -static bool is_ipv6(const char *str) -{ - const char *c; - size_t colons = 0; - - if (*str++ != '[') - return false; - - for (c = str; *c; c++) { - if (*c == ':') - colons++; - - if (*c == ']') - return (colons > 1); - - if (*c != ':' && - (*c < '0' || *c > '9') && - (*c < 'a' || *c > 'f') && - (*c < 'A' || *c > 'F')) - return false; - } - - return false; -} - -static bool has_at(const char *str) -{ - const char *c; - - for (c = str; *c; c++) { - if (*c == '@') - return true; - - if (*c == ':') - break; - } - - return false; -} - -int git_net_url_parse_scp(git_net_url *url, const char *given) -{ - const char *default_port = default_port_for_scheme("ssh"); - const char *c, *user, *host, *port, *path = NULL; - size_t user_len = 0, host_len = 0, port_len = 0; - unsigned short bracket = 0; - - enum { - NONE, - USER, - HOST_START, HOST, HOST_END, - IPV6, IPV6_END, - PORT_START, PORT, PORT_END, - PATH_START - } state = NONE; - - memset(url, 0, sizeof(git_net_url)); - - for (c = given; *c && !path; c++) { - switch (state) { - case NONE: - switch (*c) { - case '@': - return scp_invalid("unexpected '@'"); - case ':': - return scp_invalid("unexpected ':'"); - case '[': - if (is_ipv6(c)) { - state = IPV6; - host = c; - } else if (bracket++ > 1) { - return scp_invalid("unexpected '['"); - } - break; - default: - if (has_at(c)) { - state = USER; - user = c; - } else { - state = HOST; - host = c; - } - break; - } - break; - - case USER: - if (*c == '@') { - user_len = (c - user); - state = HOST_START; - } - break; - - case HOST_START: - state = (*c == '[') ? IPV6 : HOST; - host = c; - break; - - case HOST: - if (*c == ':') { - host_len = (c - host); - state = bracket ? PORT_START : PATH_START; - } else if (*c == ']') { - if (bracket-- == 0) - return scp_invalid("unexpected ']'"); - - host_len = (c - host); - state = HOST_END; - } - break; - - case HOST_END: - if (*c != ':') - return scp_invalid("unexpected character after hostname"); - state = PATH_START; - break; - - case IPV6: - if (*c == ']') - state = IPV6_END; - break; - - case IPV6_END: - if (*c != ':') - return scp_invalid("unexpected character after ipv6 address"); - - host_len = (c - host); - state = bracket ? PORT_START : PATH_START; - break; - - case PORT_START: - port = c; - state = PORT; - break; - - case PORT: - if (*c == ']') { - if (bracket-- == 0) - return scp_invalid("unexpected ']'"); - - port_len = c - port; - state = PORT_END; - } - break; - - case PORT_END: - if (*c != ':') - return scp_invalid("unexpected character after ipv6 address"); - - state = PATH_START; - break; - - case PATH_START: - path = c; - break; - - default: - GIT_ASSERT("unhandled state"); - } - } - - if (!path) - return scp_invalid("path is required"); - - GIT_ERROR_CHECK_ALLOC(url->scheme = git__strdup("ssh")); - - if (user_len) - GIT_ERROR_CHECK_ALLOC(url->username = git__strndup(user, user_len)); - - GIT_ASSERT(host_len); - GIT_ERROR_CHECK_ALLOC(url->host = git__strndup(host, host_len)); - - if (port_len) - GIT_ERROR_CHECK_ALLOC(url->port = git__strndup(port, port_len)); - else - GIT_ERROR_CHECK_ALLOC(url->port = git__strdup(default_port)); - - GIT_ASSERT(path); - GIT_ERROR_CHECK_ALLOC(url->path = git__strdup(path)); - - return 0; -} - -int git_net_url_joinpath( - git_net_url *out, - git_net_url *one, - const char *two) -{ - git_str path = GIT_STR_INIT; - const char *query; - size_t one_len, two_len; - - git_net_url_dispose(out); - - if ((query = strchr(two, '?')) != NULL) { - two_len = query - two; - - if (*(++query) != '\0') { - out->query = git__strdup(query); - GIT_ERROR_CHECK_ALLOC(out->query); - } - } else { - two_len = strlen(two); - } - - /* Strip all trailing `/`s from the first path */ - one_len = one->path ? strlen(one->path) : 0; - while (one_len && one->path[one_len - 1] == '/') - one_len--; - - /* Strip all leading `/`s from the second path */ - while (*two == '/') { - two++; - two_len--; - } - - git_str_put(&path, one->path, one_len); - git_str_putc(&path, '/'); - git_str_put(&path, two, two_len); - - if (git_str_oom(&path)) - return -1; - - out->path = git_str_detach(&path); - - if (one->scheme) { - out->scheme = git__strdup(one->scheme); - GIT_ERROR_CHECK_ALLOC(out->scheme); - } - - if (one->host) { - out->host = git__strdup(one->host); - GIT_ERROR_CHECK_ALLOC(out->host); - } - - if (one->port) { - out->port = git__strdup(one->port); - GIT_ERROR_CHECK_ALLOC(out->port); - } - - if (one->username) { - out->username = git__strdup(one->username); - GIT_ERROR_CHECK_ALLOC(out->username); - } - - if (one->password) { - out->password = git__strdup(one->password); - GIT_ERROR_CHECK_ALLOC(out->password); - } - - return 0; -} - -/* - * Some servers strip the query parameters from the Location header - * when sending a redirect. Others leave it in place. - * Check for both, starting with the stripped case first, - * since it appears to be more common. - */ -static void remove_service_suffix( - git_net_url *url, - const char *service_suffix) -{ - const char *service_query = strchr(service_suffix, '?'); - size_t full_suffix_len = strlen(service_suffix); - size_t suffix_len = service_query ? - (size_t)(service_query - service_suffix) : full_suffix_len; - size_t path_len = strlen(url->path); - ssize_t truncate = -1; - - /* - * Check for a redirect without query parameters, - * like "/newloc/info/refs"' - */ - if (suffix_len && path_len >= suffix_len) { - size_t suffix_offset = path_len - suffix_len; - - if (git__strncmp(url->path + suffix_offset, service_suffix, suffix_len) == 0 && - (!service_query || git__strcmp(url->query, service_query + 1) == 0)) { - truncate = suffix_offset; - } - } - - /* - * If we haven't already found where to truncate to remove the - * suffix, check for a redirect with query parameters, like - * "/newloc/info/refs?service=git-upload-pack" - */ - if (truncate < 0 && git__suffixcmp(url->path, service_suffix) == 0) - truncate = path_len - full_suffix_len; - - /* Ensure we leave a minimum of '/' as the path */ - if (truncate == 0) - truncate++; - - if (truncate > 0) { - url->path[truncate] = '\0'; - - git__free(url->query); - url->query = NULL; - } -} - -int git_net_url_apply_redirect( - git_net_url *url, - const char *redirect_location, - bool allow_offsite, - const char *service_suffix) -{ - git_net_url tmp = GIT_NET_URL_INIT; - int error = 0; - - GIT_ASSERT(url); - GIT_ASSERT(redirect_location); - - if (redirect_location[0] == '/') { - git__free(url->path); - - if ((url->path = git__strdup(redirect_location)) == NULL) { - error = -1; - goto done; - } - } else { - git_net_url *original = url; - - if ((error = git_net_url_parse(&tmp, redirect_location)) < 0) - goto done; - - /* Validate that this is a legal redirection */ - - if (original->scheme && - strcmp(original->scheme, tmp.scheme) != 0 && - strcmp(tmp.scheme, "https") != 0) { - git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", - original->scheme, tmp.scheme); - - error = -1; - goto done; - } - - if (original->host && - !allow_offsite && - git__strcasecmp(original->host, tmp.host) != 0) { - git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", - original->host, tmp.host); - - error = -1; - goto done; - } - - git_net_url_swap(url, &tmp); - } - - /* Remove the service suffix if it was given to us */ - if (service_suffix) - remove_service_suffix(url, service_suffix); - -done: - git_net_url_dispose(&tmp); - return error; -} - -bool git_net_url_valid(git_net_url *url) -{ - return (url->host && url->port && url->path); -} - -bool git_net_url_is_default_port(git_net_url *url) -{ - const char *default_port; - - if ((default_port = default_port_for_scheme(url->scheme)) != NULL) - return (strcmp(url->port, default_port) == 0); - else - return false; -} - -bool git_net_url_is_ipv6(git_net_url *url) -{ - return (strchr(url->host, ':') != NULL); -} - -void git_net_url_swap(git_net_url *a, git_net_url *b) -{ - git_net_url tmp = GIT_NET_URL_INIT; - - memcpy(&tmp, a, sizeof(git_net_url)); - memcpy(a, b, sizeof(git_net_url)); - memcpy(b, &tmp, sizeof(git_net_url)); -} - -int git_net_url_fmt(git_str *buf, git_net_url *url) -{ - GIT_ASSERT_ARG(url); - GIT_ASSERT_ARG(url->scheme); - GIT_ASSERT_ARG(url->host); - - git_str_puts(buf, url->scheme); - git_str_puts(buf, "://"); - - if (url->username) { - git_str_puts(buf, url->username); - - if (url->password) { - git_str_puts(buf, ":"); - git_str_puts(buf, url->password); - } - - git_str_putc(buf, '@'); - } - - git_str_puts(buf, url->host); - - if (url->port && !git_net_url_is_default_port(url)) { - git_str_putc(buf, ':'); - git_str_puts(buf, url->port); - } - - git_str_puts(buf, url->path ? url->path : "/"); - - if (url->query) { - git_str_putc(buf, '?'); - git_str_puts(buf, url->query); - } - - return git_str_oom(buf) ? -1 : 0; -} - -int git_net_url_fmt_path(git_str *buf, git_net_url *url) -{ - git_str_puts(buf, url->path ? url->path : "/"); - - if (url->query) { - git_str_putc(buf, '?'); - git_str_puts(buf, url->query); - } - - return git_str_oom(buf) ? -1 : 0; -} - -static bool matches_pattern( - git_net_url *url, - const char *pattern, - size_t pattern_len) -{ - const char *domain, *port = NULL, *colon; - size_t host_len, domain_len, port_len = 0, wildcard = 0; - - GIT_UNUSED(url); - GIT_UNUSED(pattern); - - if (!pattern_len) - return false; - else if (pattern_len == 1 && pattern[0] == '*') - return true; - else if (pattern_len > 1 && pattern[0] == '*' && pattern[1] == '.') - wildcard = 2; - else if (pattern[0] == '.') - wildcard = 1; - - domain = pattern + wildcard; - domain_len = pattern_len - wildcard; - - if ((colon = memchr(domain, ':', domain_len)) != NULL) { - domain_len = colon - domain; - port = colon + 1; - port_len = pattern_len - wildcard - domain_len - 1; - } - - /* A pattern's port *must* match if it's specified */ - if (port_len && git__strlcmp(url->port, port, port_len) != 0) - return false; - - /* No wildcard? Host must match exactly. */ - if (!wildcard) - return !git__strlcmp(url->host, domain, domain_len); - - /* Wildcard: ensure there's (at least) a suffix match */ - if ((host_len = strlen(url->host)) < domain_len || - memcmp(url->host + (host_len - domain_len), domain, domain_len)) - return false; - - /* The pattern is *.domain and the host is simply domain */ - if (host_len == domain_len) - return true; - - /* The pattern is *.domain and the host is foo.domain */ - return (url->host[host_len - domain_len - 1] == '.'); -} - -bool git_net_url_matches_pattern(git_net_url *url, const char *pattern) -{ - return matches_pattern(url, pattern, strlen(pattern)); -} - -bool git_net_url_matches_pattern_list( - git_net_url *url, - const char *pattern_list) -{ - const char *pattern, *pattern_end, *sep; - - for (pattern = pattern_list; - pattern && *pattern; - pattern = sep ? sep + 1 : NULL) { - sep = strchr(pattern, ','); - pattern_end = sep ? sep : strchr(pattern, '\0'); - - if (matches_pattern(url, pattern, (pattern_end - pattern))) - return true; - } - - return false; -} - -void git_net_url_dispose(git_net_url *url) -{ - if (url->username) - git__memzero(url->username, strlen(url->username)); - - if (url->password) - git__memzero(url->password, strlen(url->password)); - - git__free(url->scheme); url->scheme = NULL; - git__free(url->host); url->host = NULL; - git__free(url->port); url->port = NULL; - git__free(url->path); url->path = NULL; - git__free(url->query); url->query = NULL; - git__free(url->username); url->username = NULL; - git__free(url->password); url->password = NULL; -} diff --git a/src/libgit2/net.h b/src/libgit2/net.h deleted file mode 100644 index 499315e6c..000000000 --- a/src/libgit2/net.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_net_h__ -#define INCLUDE_net_h__ - -#include "common.h" - -typedef struct git_net_url { - char *scheme; - char *host; - char *port; - char *path; - char *query; - char *username; - char *password; -} git_net_url; - -#define GIT_NET_URL_INIT { NULL } - -/** Is a given string a url? */ -extern bool git_net_str_is_url(const char *str); - -/** Duplicate a URL */ -extern int git_net_url_dup(git_net_url *out, git_net_url *in); - -/** Parses a string containing a URL into a structure. */ -extern int git_net_url_parse(git_net_url *url, const char *str); - -/** Parses a string containing an SCP style path into a URL structure. */ -extern int git_net_url_parse_scp(git_net_url *url, const char *str); - -/** Appends a path and/or query string to the given URL */ -extern int git_net_url_joinpath( - git_net_url *out, - git_net_url *in, - const char *path); - -/** Ensures that a URL is minimally valid (contains a host, port and path) */ -extern bool git_net_url_valid(git_net_url *url); - -/** Returns true if the URL is on the default port. */ -extern bool git_net_url_is_default_port(git_net_url *url); - -/** Returns true if the host portion of the URL is an ipv6 address. */ -extern bool git_net_url_is_ipv6(git_net_url *url); - -/* Applies a redirect to the URL with a git-aware service suffix. */ -extern int git_net_url_apply_redirect( - git_net_url *url, - const char *redirect_location, - bool allow_offsite, - const char *service_suffix); - -/** Swaps the contents of one URL for another. */ -extern void git_net_url_swap(git_net_url *a, git_net_url *b); - -/** Places the URL into the given buffer. */ -extern int git_net_url_fmt(git_str *out, git_net_url *url); - -/** Place the path and query string into the given buffer. */ -extern int git_net_url_fmt_path(git_str *buf, git_net_url *url); - -/** Determines if the url matches given pattern or pattern list */ -extern bool git_net_url_matches_pattern( - git_net_url *url, - const char *pattern); -extern bool git_net_url_matches_pattern_list( - git_net_url *url, - const char *pattern_list); - -/** Disposes the contents of the structure. */ -extern void git_net_url_dispose(git_net_url *url); - -#endif diff --git a/src/libgit2/netops.c b/src/libgit2/netops.c index 0a27365b8..00640c600 100644 --- a/src/libgit2/netops.c +++ b/src/libgit2/netops.c @@ -12,7 +12,6 @@ #include "posix.h" #include "str.h" -#include "http_parser.h" #include "runtime.h" int gitno_recv(gitno_buffer *buf) diff --git a/src/libgit2/odb.c b/src/libgit2/odb.c index 6d714ba54..7b98c72ee 100644 --- a/src/libgit2/odb.c +++ b/src/libgit2/odb.c @@ -198,7 +198,7 @@ void git_odb_object_free(git_odb_object *object) int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_object_t type) { size_t hdr_len; - char hdr[64], buffer[FILEIO_BUFSIZE]; + char hdr[64], buffer[GIT_BUFSIZE_FILEIO]; git_hash_ctx ctx; ssize_t read_len = 0; int error = 0; diff --git a/src/libgit2/path.c b/src/libgit2/path.c index 05a3dc2cf..a19340efe 100644 --- a/src/libgit2/path.c +++ b/src/libgit2/path.c @@ -9,6 +9,7 @@ #include "repository.h" #include "fs_path.h" +#include "utf8.h" typedef struct { git_repository *repo; diff --git a/src/libgit2/pool.c b/src/libgit2/pool.c deleted file mode 100644 index 16ffa398d..000000000 --- a/src/libgit2/pool.c +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "pool.h" - -#include "posix.h" -#ifndef GIT_WIN32 -#include -#endif - -struct git_pool_page { - git_pool_page *next; - size_t size; - size_t avail; - GIT_ALIGN(char data[GIT_FLEX_ARRAY], 8); -}; - -static void *pool_alloc_page(git_pool *pool, size_t size); - -#ifndef GIT_DEBUG_POOL - -static size_t system_page_size = 0; - -int git_pool_global_init(void) -{ - if (git__page_size(&system_page_size) < 0) - system_page_size = 4096; - /* allow space for malloc overhead */ - system_page_size -= (2 * sizeof(void *)) + sizeof(git_pool_page); - return 0; -} - -int git_pool_init(git_pool *pool, size_t item_size) -{ - GIT_ASSERT_ARG(pool); - GIT_ASSERT_ARG(item_size >= 1); - - memset(pool, 0, sizeof(git_pool)); - pool->item_size = item_size; - pool->page_size = system_page_size; - - return 0; -} - -void git_pool_clear(git_pool *pool) -{ - git_pool_page *scan, *next; - - for (scan = pool->pages; scan != NULL; scan = next) { - next = scan->next; - git__free(scan); - } - - pool->pages = NULL; -} - -static void *pool_alloc_page(git_pool *pool, size_t size) -{ - git_pool_page *page; - const size_t new_page_size = (size <= pool->page_size) ? pool->page_size : size; - size_t alloc_size; - - if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, new_page_size, sizeof(git_pool_page)) || - !(page = git__malloc(alloc_size))) - return NULL; - - page->size = new_page_size; - page->avail = new_page_size - size; - page->next = pool->pages; - - pool->pages = page; - - return page->data; -} - -static void *pool_alloc(git_pool *pool, size_t size) -{ - git_pool_page *page = pool->pages; - void *ptr = NULL; - - if (!page || page->avail < size) - return pool_alloc_page(pool, size); - - ptr = &page->data[page->size - page->avail]; - page->avail -= size; - - return ptr; -} - -uint32_t git_pool__open_pages(git_pool *pool) -{ - uint32_t ct = 0; - git_pool_page *scan; - for (scan = pool->pages; scan != NULL; scan = scan->next) ct++; - return ct; -} - -bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) -{ - git_pool_page *scan; - for (scan = pool->pages; scan != NULL; scan = scan->next) - if ((void *)scan->data <= ptr && - (void *)(((char *)scan->data) + scan->size) > ptr) - return true; - return false; -} - -#else - -int git_pool_global_init(void) -{ - return 0; -} - -static int git_pool__ptr_cmp(const void * a, const void * b) -{ - if(a > b) { - return 1; - } - if(a < b) { - return -1; - } - else { - return 0; - } -} - -int git_pool_init(git_pool *pool, size_t item_size) -{ - GIT_ASSERT_ARG(pool); - GIT_ASSERT_ARG(item_size >= 1); - - memset(pool, 0, sizeof(git_pool)); - pool->item_size = item_size; - pool->page_size = git_pool__system_page_size(); - git_vector_init(&pool->allocations, 100, git_pool__ptr_cmp); - - return 0; -} - -void git_pool_clear(git_pool *pool) -{ - git_vector_free_deep(&pool->allocations); -} - -static void *pool_alloc(git_pool *pool, size_t size) { - void *ptr = NULL; - if((ptr = git__malloc(size)) == NULL) { - return NULL; - } - git_vector_insert_sorted(&pool->allocations, ptr, NULL); - return ptr; -} - -bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) -{ - size_t pos; - return git_vector_bsearch(&pos, &pool->allocations, ptr) != GIT_ENOTFOUND; -} -#endif - -void git_pool_swap(git_pool *a, git_pool *b) -{ - git_pool temp; - - if (a == b) - return; - - memcpy(&temp, a, sizeof(temp)); - memcpy(a, b, sizeof(temp)); - memcpy(b, &temp, sizeof(temp)); -} - -static size_t alloc_size(git_pool *pool, size_t count) -{ - const size_t align = sizeof(void *) - 1; - - if (pool->item_size > 1) { - const size_t item_size = (pool->item_size + align) & ~align; - return item_size * count; - } - - return (count + align) & ~align; -} - -void *git_pool_malloc(git_pool *pool, size_t items) -{ - return pool_alloc(pool, alloc_size(pool, items)); -} - -void *git_pool_mallocz(git_pool *pool, size_t items) -{ - const size_t size = alloc_size(pool, items); - void *ptr = pool_alloc(pool, size); - if (ptr) - memset(ptr, 0x0, size); - return ptr; -} - -char *git_pool_strndup(git_pool *pool, const char *str, size_t n) -{ - char *ptr = NULL; - - GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(str, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); - - if (n == SIZE_MAX) - return NULL; - - if ((ptr = git_pool_malloc(pool, (n + 1))) != NULL) { - memcpy(ptr, str, n); - ptr[n] = '\0'; - } - - return ptr; -} - -char *git_pool_strdup(git_pool *pool, const char *str) -{ - GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(str, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); - - return git_pool_strndup(pool, str, strlen(str)); -} - -char *git_pool_strdup_safe(git_pool *pool, const char *str) -{ - return str ? git_pool_strdup(pool, str) : NULL; -} - -char *git_pool_strcat(git_pool *pool, const char *a, const char *b) -{ - void *ptr; - size_t len_a, len_b, total; - - GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); - GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); - - len_a = a ? strlen(a) : 0; - len_b = b ? strlen(b) : 0; - - if (GIT_ADD_SIZET_OVERFLOW(&total, len_a, len_b) || - GIT_ADD_SIZET_OVERFLOW(&total, total, 1)) - return NULL; - - if ((ptr = git_pool_malloc(pool, total)) != NULL) { - if (len_a) - memcpy(ptr, a, len_a); - if (len_b) - memcpy(((char *)ptr) + len_a, b, len_b); - *(((char *)ptr) + len_a + len_b) = '\0'; - } - return ptr; -} diff --git a/src/libgit2/pool.h b/src/libgit2/pool.h deleted file mode 100644 index cecb84665..000000000 --- a/src/libgit2/pool.h +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_pool_h__ -#define INCLUDE_pool_h__ - -#include "common.h" - -#include "vector.h" - -typedef struct git_pool_page git_pool_page; - -#ifndef GIT_DEBUG_POOL -/** - * Chunked allocator. - * - * A `git_pool` can be used when you want to cheaply allocate - * multiple items of the same type and are willing to free them - * all together with a single call. The two most common cases - * are a set of fixed size items (such as lots of OIDs) or a - * bunch of strings. - * - * Internally, a `git_pool` allocates pages of memory and then - * deals out blocks from the trailing unused portion of each page. - * The pages guarantee that the number of actual allocations done - * will be much smaller than the number of items needed. - * - * For examples of how to set up a `git_pool` see `git_pool_init`. - */ -typedef struct { - git_pool_page *pages; /* allocated pages */ - size_t item_size; /* size of single alloc unit in bytes */ - size_t page_size; /* size of page in bytes */ -} git_pool; - -#define GIT_POOL_INIT { NULL, 0, 0 } - -#else - -/** - * Debug chunked allocator. - * - * Acts just like `git_pool` but instead of actually pooling allocations it - * passes them through to `git__malloc`. This makes it possible to easily debug - * systems that use `git_pool` using valgrind. - * - * In order to track allocations during the lifetime of the pool we use a - * `git_vector`. When the pool is deallocated everything in the vector is - * freed. - * - * `API is exactly the same as the standard `git_pool` with one exception. - * Since we aren't allocating pages to hand out in chunks we can't easily - * implement `git_pool__open_pages`. - */ -typedef struct { - git_vector allocations; - size_t item_size; - size_t page_size; -} git_pool; - -#define GIT_POOL_INIT { GIT_VECTOR_INIT, 0, 0 } - -#endif - -/** - * Initialize a pool. - * - * To allocation strings, use like this: - * - * git_pool_init(&string_pool, 1); - * my_string = git_pool_strdup(&string_pool, your_string); - * - * To allocate items of fixed size, use like this: - * - * git_pool_init(&pool, sizeof(item)); - * my_item = git_pool_malloc(&pool, 1); - * - * Of course, you can use this in other ways, but those are the - * two most common patterns. - */ -extern int git_pool_init(git_pool *pool, size_t item_size); - -/** - * Free all items in pool - */ -extern void git_pool_clear(git_pool *pool); - -/** - * Swap two pools with one another - */ -extern void git_pool_swap(git_pool *a, git_pool *b); - -/** - * Allocate space for one or more items from a pool. - */ -extern void *git_pool_malloc(git_pool *pool, size_t items); -extern void *git_pool_mallocz(git_pool *pool, size_t items); - -/** - * Allocate space and duplicate string data into it. - * - * This is allowed only for pools with item_size == sizeof(char) - */ -extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n); - -/** - * Allocate space and duplicate a string into it. - * - * This is allowed only for pools with item_size == sizeof(char) - */ -extern char *git_pool_strdup(git_pool *pool, const char *str); - -/** - * Allocate space and duplicate a string into it, NULL is no error. - * - * This is allowed only for pools with item_size == sizeof(char) - */ -extern char *git_pool_strdup_safe(git_pool *pool, const char *str); - -/** - * Allocate space for the concatenation of two strings. - * - * This is allowed only for pools with item_size == sizeof(char) - */ -extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b); - -/* - * Misc utilities - */ -#ifndef GIT_DEBUG_POOL -extern uint32_t git_pool__open_pages(git_pool *pool); -#endif -extern bool git_pool__ptr_in_pool(git_pool *pool, void *ptr); - -/** - * This function is being called by our global setup routines to - * initialize the system pool size. - * - * @return 0 on success, <0 on failure - */ -extern int git_pool_global_init(void); - -#endif diff --git a/src/libgit2/posix.c b/src/libgit2/posix.c deleted file mode 100644 index b1f85dc94..000000000 --- a/src/libgit2/posix.c +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "posix.h" - -#include "fs_path.h" -#include -#include - -size_t p_fsync__cnt = 0; - -#ifndef GIT_WIN32 - -#ifdef NO_ADDRINFO - -int p_getaddrinfo( - const char *host, - const char *port, - struct addrinfo *hints, - struct addrinfo **info) -{ - struct addrinfo *ainfo, *ai; - int p = 0; - - GIT_UNUSED(hints); - - if ((ainfo = git__malloc(sizeof(struct addrinfo))) == NULL) - return -1; - - if ((ainfo->ai_hostent = gethostbyname(host)) == NULL) { - git__free(ainfo); - return -2; - } - - ainfo->ai_servent = getservbyname(port, 0); - - if (ainfo->ai_servent) - ainfo->ai_port = ainfo->ai_servent->s_port; - else - ainfo->ai_port = htons(atol(port)); - - memcpy(&ainfo->ai_addr_in.sin_addr, - ainfo->ai_hostent->h_addr_list[0], - ainfo->ai_hostent->h_length); - - ainfo->ai_protocol = 0; - ainfo->ai_socktype = hints->ai_socktype; - ainfo->ai_family = ainfo->ai_hostent->h_addrtype; - ainfo->ai_addr_in.sin_family = ainfo->ai_family; - ainfo->ai_addr_in.sin_port = ainfo->ai_port; - ainfo->ai_addr = (struct addrinfo *)&ainfo->ai_addr_in; - ainfo->ai_addrlen = sizeof(struct sockaddr_in); - - *info = ainfo; - - if (ainfo->ai_hostent->h_addr_list[1] == NULL) { - ainfo->ai_next = NULL; - return 0; - } - - ai = ainfo; - - for (p = 1; ainfo->ai_hostent->h_addr_list[p] != NULL; p++) { - if (!(ai->ai_next = git__malloc(sizeof(struct addrinfo)))) { - p_freeaddrinfo(ainfo); - return -1; - } - memcpy(ai->ai_next, ainfo, sizeof(struct addrinfo)); - memcpy(&ai->ai_next->ai_addr_in.sin_addr, - ainfo->ai_hostent->h_addr_list[p], - ainfo->ai_hostent->h_length); - ai->ai_next->ai_addr = (struct addrinfo *)&ai->ai_next->ai_addr_in; - ai = ai->ai_next; - } - - ai->ai_next = NULL; - return 0; -} - -void p_freeaddrinfo(struct addrinfo *info) -{ - struct addrinfo *p, *next; - - p = info; - - while(p != NULL) { - next = p->ai_next; - git__free(p); - p = next; - } -} - -const char *p_gai_strerror(int ret) -{ - switch(ret) { - case -1: return "Out of memory"; break; - case -2: return "Address lookup failed"; break; - default: return "Unknown error"; break; - } -} - -#endif /* NO_ADDRINFO */ - -int p_open(const char *path, volatile int flags, ...) -{ - mode_t mode = 0; - - #ifdef GIT_DEBUG_STRICT_OPEN - if (strstr(path, "//") != NULL) { - errno = EACCES; - return -1; - } - #endif - - if (flags & O_CREAT) { - va_list arg_list; - - va_start(arg_list, flags); - mode = (mode_t)va_arg(arg_list, int); - va_end(arg_list); - } - - return open(path, flags | O_BINARY | O_CLOEXEC, mode); -} - -int p_creat(const char *path, mode_t mode) -{ - return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC, mode); -} - -int p_getcwd(char *buffer_out, size_t size) -{ - char *cwd_buffer; - - GIT_ASSERT_ARG(buffer_out); - GIT_ASSERT_ARG(size > 0); - - cwd_buffer = getcwd(buffer_out, size); - - if (cwd_buffer == NULL) - return -1; - - git_fs_path_mkposix(buffer_out); - git_fs_path_string_to_dir(buffer_out, size); /* append trailing slash */ - - return 0; -} - -int p_rename(const char *from, const char *to) -{ - if (!link(from, to)) { - p_unlink(from); - return 0; - } - - if (!rename(from, to)) - return 0; - - return -1; -} - -#endif /* GIT_WIN32 */ - -ssize_t p_read(git_file fd, void *buf, size_t cnt) -{ - char *b = buf; - - if (!git__is_ssizet(cnt)) { -#ifdef GIT_WIN32 - SetLastError(ERROR_INVALID_PARAMETER); -#endif - errno = EINVAL; - return -1; - } - - while (cnt) { - ssize_t r; -#ifdef GIT_WIN32 - r = read(fd, b, cnt > INT_MAX ? INT_MAX : (unsigned int)cnt); -#else - r = read(fd, b, cnt); -#endif - if (r < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - return -1; - } - if (!r) - break; - cnt -= r; - b += r; - } - return (b - (char *)buf); -} - -int p_write(git_file fd, const void *buf, size_t cnt) -{ - const char *b = buf; - - while (cnt) { - ssize_t r; -#ifdef GIT_WIN32 - GIT_ASSERT((size_t)((unsigned int)cnt) == cnt); - r = write(fd, b, (unsigned int)cnt); -#else - r = write(fd, b, cnt); -#endif - if (r < 0) { - if (errno == EINTR || GIT_ISBLOCKED(errno)) - continue; - return -1; - } - if (!r) { - errno = EPIPE; - return -1; - } - cnt -= r; - b += r; - } - return 0; -} - -#ifdef NO_MMAP - -#include "map.h" - -int git__page_size(size_t *page_size) -{ - /* dummy; here we don't need any alignment anyway */ - *page_size = 4096; - return 0; -} - -int git__mmap_alignment(size_t *alignment) -{ - /* dummy; here we don't need any alignment anyway */ - *alignment = 4096; - return 0; -} - - -int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) -{ - const char *ptr; - size_t remaining_len; - - GIT_MMAP_VALIDATE(out, len, prot, flags); - - /* writes cannot be emulated without handling pagefaults since write happens by - * writing to mapped memory */ - if (prot & GIT_PROT_WRITE) { - git_error_set(GIT_ERROR_OS, "trying to map %s-writeable", - ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) ? "shared": "private"); - return -1; - } - - if (!git__is_ssizet(len)) { - errno = EINVAL; - return -1; - } - - out->len = 0; - out->data = git__malloc(len); - GIT_ERROR_CHECK_ALLOC(out->data); - - remaining_len = len; - ptr = (const char *)out->data; - while (remaining_len > 0) { - ssize_t nb; - HANDLE_EINTR(nb, p_pread(fd, (void *)ptr, remaining_len, offset)); - if (nb <= 0) { - git_error_set(GIT_ERROR_OS, "mmap emulation failed"); - git__free(out->data); - out->data = NULL; - return -1; - } - - ptr += nb; - offset += nb; - remaining_len -= nb; - } - - out->len = len; - return 0; -} - -int p_munmap(git_map *map) -{ - GIT_ASSERT_ARG(map); - git__free(map->data); - - /* Initializing will help debug use-after-free */ - map->len = 0; - map->data = NULL; - - return 0; -} - -#endif diff --git a/src/libgit2/posix.h b/src/libgit2/posix.h deleted file mode 100644 index e6f603078..000000000 --- a/src/libgit2/posix.h +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_posix_h__ -#define INCLUDE_posix_h__ - -#include "common.h" - -#include -#include -#include - -/* stat: file mode type testing macros */ -#ifndef S_IFGITLINK -#define S_IFGITLINK 0160000 -#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) -#endif - -#ifndef S_IFLNK -#define S_IFLNK 0120000 -#undef _S_IFLNK -#define _S_IFLNK S_IFLNK -#endif - -#ifndef S_IWUSR -#define S_IWUSR 00200 -#endif - -#ifndef S_IXUSR -#define S_IXUSR 00100 -#endif - -#ifndef S_ISLNK -#define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) -#endif - -#ifndef S_ISDIR -#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) -#endif - -#ifndef S_ISREG -#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) -#endif - -#ifndef S_ISFIFO -#define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO) -#endif - -/* if S_ISGID is not defined, then don't try to set it */ -#ifndef S_ISGID -#define S_ISGID 0 -#endif - -#ifndef O_BINARY -#define O_BINARY 0 -#endif -#ifndef O_CLOEXEC -#define O_CLOEXEC 0 -#endif -#ifndef SOCK_CLOEXEC -#define SOCK_CLOEXEC 0 -#endif - -/* access() mode parameter #defines */ -#ifndef F_OK -#define F_OK 0 /* existence check */ -#endif -#ifndef W_OK -#define W_OK 2 /* write mode check */ -#endif -#ifndef R_OK -#define R_OK 4 /* read mode check */ -#endif - -/* Determine whether an errno value indicates that a read or write failed - * because the descriptor is blocked. - */ -#if defined(EWOULDBLOCK) -#define GIT_ISBLOCKED(e) ((e) == EAGAIN || (e) == EWOULDBLOCK) -#else -#define GIT_ISBLOCKED(e) ((e) == EAGAIN) -#endif - -/* define some standard errnos that the runtime may be missing. for example, - * mingw lacks EAFNOSUPPORT. */ -#ifndef EAFNOSUPPORT -#define EAFNOSUPPORT (INT_MAX-1) -#endif - -/* Compiler independent macro to handle signal interrpted system calls */ -#define HANDLE_EINTR(result, x) do { \ - result = (x); \ - } while (result == -1 && errno == EINTR); - - -/* Provide a 64-bit size for offsets. */ - -#if defined(_MSC_VER) -typedef __int64 off64_t; -#elif defined(__HAIKU__) -typedef __haiku_std_int64 off64_t; -#elif defined(__APPLE__) -typedef __int64_t off64_t; -#else -typedef int64_t off64_t; -#endif - -typedef int git_file; - -/** - * Standard POSIX Methods - * - * All the methods starting with the `p_` prefix are - * direct ports of the standard POSIX methods. - * - * Some of the methods are slightly wrapped to provide - * saner defaults. Some of these methods are emulated - * in Windows platforms. - * - * Use your manpages to check the docs on these. - */ - -extern ssize_t p_read(git_file fd, void *buf, size_t cnt); -extern int p_write(git_file fd, const void *buf, size_t cnt); - -extern ssize_t p_pread(int fd, void *data, size_t size, off64_t offset); -extern ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset); - -#define p_close(fd) close(fd) -#define p_umask(m) umask(m) - -extern int p_open(const char *path, int flags, ...); -extern int p_creat(const char *path, mode_t mode); -extern int p_getcwd(char *buffer_out, size_t size); -extern int p_rename(const char *from, const char *to); - -extern int git__page_size(size_t *page_size); -extern int git__mmap_alignment(size_t *page_size); - -/* The number of times `p_fsync` has been called. Note that this is for - * test code only; it it not necessarily thread-safe and should not be - * relied upon in production. - */ -extern size_t p_fsync__cnt; - -/** - * Platform-dependent methods - */ -#ifdef GIT_WIN32 -# include "win32/posix.h" -#else -# include "unix/posix.h" -#endif - -#include "strnlen.h" - -#ifdef NO_READDIR_R -GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) -{ - GIT_UNUSED(entry); - *result = readdir(dirp); - return 0; -} -#else /* NO_READDIR_R */ -# define p_readdir_r(d,e,r) readdir_r(d,e,r) -#endif - -#ifdef NO_ADDRINFO -# include -struct addrinfo { - struct hostent *ai_hostent; - struct servent *ai_servent; - struct sockaddr_in ai_addr_in; - struct sockaddr *ai_addr; - size_t ai_addrlen; - int ai_family; - int ai_socktype; - int ai_protocol; - long ai_port; - struct addrinfo *ai_next; -}; - -extern int p_getaddrinfo(const char *host, const char *port, - struct addrinfo *hints, struct addrinfo **info); -extern void p_freeaddrinfo(struct addrinfo *info); -extern const char *p_gai_strerror(int ret); -#else -# define p_getaddrinfo(a, b, c, d) getaddrinfo(a, b, c, d) -# define p_freeaddrinfo(a) freeaddrinfo(a) -# define p_gai_strerror(c) gai_strerror(c) -#endif /* NO_ADDRINFO */ - -#endif diff --git a/src/libgit2/pqueue.c b/src/libgit2/pqueue.c deleted file mode 100644 index 3820e999c..000000000 --- a/src/libgit2/pqueue.c +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "pqueue.h" - -#include "util.h" - -#define PQUEUE_LCHILD_OF(I) (((I)<<1)+1) -#define PQUEUE_RCHILD_OF(I) (((I)<<1)+2) -#define PQUEUE_PARENT_OF(I) (((I)-1)>>1) - -int git_pqueue_init( - git_pqueue *pq, - uint32_t flags, - size_t init_size, - git_vector_cmp cmp) -{ - int error = git_vector_init(pq, init_size, cmp); - - if (!error) { - /* mix in our flags */ - pq->flags |= flags; - - /* if fixed size heap, pretend vector is exactly init_size elements */ - if ((flags & GIT_PQUEUE_FIXED_SIZE) && init_size > 0) - pq->_alloc_size = init_size; - } - - return error; -} - -static void pqueue_up(git_pqueue *pq, size_t el) -{ - size_t parent_el = PQUEUE_PARENT_OF(el); - void *kid = git_vector_get(pq, el); - - while (el > 0) { - void *parent = pq->contents[parent_el]; - - if (pq->_cmp(parent, kid) <= 0) - break; - - pq->contents[el] = parent; - - el = parent_el; - parent_el = PQUEUE_PARENT_OF(el); - } - - pq->contents[el] = kid; -} - -static void pqueue_down(git_pqueue *pq, size_t el) -{ - void *parent = git_vector_get(pq, el), *kid, *rkid; - - while (1) { - size_t kid_el = PQUEUE_LCHILD_OF(el); - - if ((kid = git_vector_get(pq, kid_el)) == NULL) - break; - - if ((rkid = git_vector_get(pq, kid_el + 1)) != NULL && - pq->_cmp(kid, rkid) > 0) { - kid = rkid; - kid_el += 1; - } - - if (pq->_cmp(parent, kid) <= 0) - break; - - pq->contents[el] = kid; - el = kid_el; - } - - pq->contents[el] = parent; -} - -int git_pqueue_insert(git_pqueue *pq, void *item) -{ - int error = 0; - - /* if heap is full, pop the top element if new one should replace it */ - if ((pq->flags & GIT_PQUEUE_FIXED_SIZE) != 0 && - pq->length >= pq->_alloc_size) - { - /* skip this item if below min item in heap or if - * we do not have a comparison function */ - if (!pq->_cmp || pq->_cmp(item, git_vector_get(pq, 0)) <= 0) - return 0; - /* otherwise remove the min item before inserting new */ - (void)git_pqueue_pop(pq); - } - - if (!(error = git_vector_insert(pq, item)) && pq->_cmp) - pqueue_up(pq, pq->length - 1); - - return error; -} - -void *git_pqueue_pop(git_pqueue *pq) -{ - void *rval; - - if (!pq->_cmp) { - rval = git_vector_last(pq); - } else { - rval = git_pqueue_get(pq, 0); - } - - if (git_pqueue_size(pq) > 1 && pq->_cmp) { - /* move last item to top of heap, shrink, and push item down */ - pq->contents[0] = git_vector_last(pq); - git_vector_pop(pq); - pqueue_down(pq, 0); - } else { - /* all we need to do is shrink the heap in this case */ - git_vector_pop(pq); - } - - return rval; -} diff --git a/src/libgit2/pqueue.h b/src/libgit2/pqueue.h deleted file mode 100644 index 4db74ea03..000000000 --- a/src/libgit2/pqueue.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_pqueue_h__ -#define INCLUDE_pqueue_h__ - -#include "common.h" - -#include "vector.h" - -typedef git_vector git_pqueue; - -enum { - /* flag meaning: don't grow heap, keep highest values only */ - GIT_PQUEUE_FIXED_SIZE = (GIT_VECTOR_FLAG_MAX << 1) -}; - -/** - * Initialize priority queue - * - * @param pq The priority queue struct to initialize - * @param flags Flags (see above) to control queue behavior - * @param init_size The initial queue size - * @param cmp The entry priority comparison function - * @return 0 on success, <0 on error - */ -extern int git_pqueue_init( - git_pqueue *pq, - uint32_t flags, - size_t init_size, - git_vector_cmp cmp); - -#define git_pqueue_free git_vector_free -#define git_pqueue_clear git_vector_clear -#define git_pqueue_size git_vector_length -#define git_pqueue_get git_vector_get -#define git_pqueue_reverse git_vector_reverse - -/** - * Insert a new item into the queue - * - * @param pq The priority queue - * @param item Pointer to the item data - * @return 0 on success, <0 on failure - */ -extern int git_pqueue_insert(git_pqueue *pq, void *item); - -/** - * Remove the top item in the priority queue - * - * @param pq The priority queue - * @return item from heap on success, NULL if queue is empty - */ -extern void *git_pqueue_pop(git_pqueue *pq); - -#endif diff --git a/src/libgit2/regexp.c b/src/libgit2/regexp.c deleted file mode 100644 index 2569dea0a..000000000 --- a/src/libgit2/regexp.c +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "regexp.h" - -#if defined(GIT_REGEX_BUILTIN) || defined(GIT_REGEX_PCRE) - -int git_regexp_compile(git_regexp *r, const char *pattern, int flags) -{ - int erroffset, cflags = 0; - const char *error = NULL; - - if (flags & GIT_REGEXP_ICASE) - cflags |= PCRE_CASELESS; - - if ((*r = pcre_compile(pattern, cflags, &error, &erroffset, NULL)) == NULL) { - git_error_set_str(GIT_ERROR_REGEX, error); - return GIT_EINVALIDSPEC; - } - - return 0; -} - -void git_regexp_dispose(git_regexp *r) -{ - pcre_free(*r); - *r = NULL; -} - -int git_regexp_match(const git_regexp *r, const char *string) -{ - int error; - if ((error = pcre_exec(*r, NULL, string, (int) strlen(string), 0, 0, NULL, 0)) < 0) - return (error == PCRE_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; - return 0; -} - -int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) -{ - int static_ovec[9] = {0}, *ovec; - int error; - size_t i; - - /* The ovec array always needs to be a multiple of three */ - if (nmatches <= ARRAY_SIZE(static_ovec) / 3) - ovec = static_ovec; - else - ovec = git__calloc(nmatches * 3, sizeof(*ovec)); - GIT_ERROR_CHECK_ALLOC(ovec); - - if ((error = pcre_exec(*r, NULL, string, (int) strlen(string), 0, 0, ovec, (int) nmatches * 3)) < 0) - goto out; - - if (error == 0) - error = (int) nmatches; - - for (i = 0; i < (unsigned int) error; i++) { - matches[i].start = (ovec[i * 2] < 0) ? -1 : ovec[i * 2]; - matches[i].end = (ovec[i * 2 + 1] < 0) ? -1 : ovec[i * 2 + 1]; - } - for (i = (unsigned int) error; i < nmatches; i++) - matches[i].start = matches[i].end = -1; - -out: - if (nmatches > ARRAY_SIZE(static_ovec) / 3) - git__free(ovec); - if (error < 0) - return (error == PCRE_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; - return 0; -} - -#elif defined(GIT_REGEX_PCRE2) - -int git_regexp_compile(git_regexp *r, const char *pattern, int flags) -{ - unsigned char errmsg[1024]; - PCRE2_SIZE erroff; - int error, cflags = 0; - - if (flags & GIT_REGEXP_ICASE) - cflags |= PCRE2_CASELESS; - - if ((*r = pcre2_compile((const unsigned char *) pattern, PCRE2_ZERO_TERMINATED, - cflags, &error, &erroff, NULL)) == NULL) { - pcre2_get_error_message(error, errmsg, sizeof(errmsg)); - git_error_set_str(GIT_ERROR_REGEX, (char *) errmsg); - return GIT_EINVALIDSPEC; - } - - return 0; -} - -void git_regexp_dispose(git_regexp *r) -{ - pcre2_code_free(*r); - *r = NULL; -} - -int git_regexp_match(const git_regexp *r, const char *string) -{ - pcre2_match_data *data; - int error; - - data = pcre2_match_data_create(1, NULL); - GIT_ERROR_CHECK_ALLOC(data); - - if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string), - 0, 0, data, NULL)) < 0) - return (error == PCRE2_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; - - pcre2_match_data_free(data); - return 0; -} - -int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) -{ - pcre2_match_data *data = NULL; - PCRE2_SIZE *ovec; - int error; - size_t i; - - if ((data = pcre2_match_data_create(nmatches, NULL)) == NULL) { - git_error_set_oom(); - goto out; - } - - if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string), - 0, 0, data, NULL)) < 0) - goto out; - - if (error == 0 || (unsigned int) error > nmatches) - error = nmatches; - ovec = pcre2_get_ovector_pointer(data); - - for (i = 0; i < (unsigned int) error; i++) { - matches[i].start = (ovec[i * 2] == PCRE2_UNSET) ? -1 : (ssize_t) ovec[i * 2]; - matches[i].end = (ovec[i * 2 + 1] == PCRE2_UNSET) ? -1 : (ssize_t) ovec[i * 2 + 1]; - } - for (i = (unsigned int) error; i < nmatches; i++) - matches[i].start = matches[i].end = -1; - -out: - pcre2_match_data_free(data); - if (error < 0) - return (error == PCRE2_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; - return 0; -} - -#elif defined(GIT_REGEX_REGCOMP) || defined(GIT_REGEX_REGCOMP_L) - -#if defined(GIT_REGEX_REGCOMP_L) -# include -#endif - -int git_regexp_compile(git_regexp *r, const char *pattern, int flags) -{ - int cflags = REG_EXTENDED, error; - char errmsg[1024]; - - if (flags & GIT_REGEXP_ICASE) - cflags |= REG_ICASE; - -# if defined(GIT_REGEX_REGCOMP) - if ((error = regcomp(r, pattern, cflags)) != 0) -# else - if ((error = regcomp_l(r, pattern, cflags, (locale_t) 0)) != 0) -# endif - { - regerror(error, r, errmsg, sizeof(errmsg)); - git_error_set_str(GIT_ERROR_REGEX, errmsg); - return GIT_EINVALIDSPEC; - } - - return 0; -} - -void git_regexp_dispose(git_regexp *r) -{ - regfree(r); -} - -int git_regexp_match(const git_regexp *r, const char *string) -{ - int error; - if ((error = regexec(r, string, 0, NULL, 0)) != 0) - return (error == REG_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; - return 0; -} - -int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) -{ - regmatch_t static_m[3], *m; - int error; - size_t i; - - if (nmatches <= ARRAY_SIZE(static_m)) - m = static_m; - else - m = git__calloc(nmatches, sizeof(*m)); - - if ((error = regexec(r, string, nmatches, m, 0)) != 0) - goto out; - - for (i = 0; i < nmatches; i++) { - matches[i].start = (m[i].rm_so < 0) ? -1 : m[i].rm_so; - matches[i].end = (m[i].rm_eo < 0) ? -1 : m[i].rm_eo; - } - -out: - if (nmatches > ARRAY_SIZE(static_m)) - git__free(m); - if (error) - return (error == REG_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; - return 0; -} - -#endif diff --git a/src/libgit2/regexp.h b/src/libgit2/regexp.h deleted file mode 100644 index 2592ef383..000000000 --- a/src/libgit2/regexp.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_regexp_h__ -#define INCLUDE_regexp_h__ - -#include "common.h" - -#if defined(GIT_REGEX_BUILTIN) || defined(GIT_REGEX_PCRE) -# include "pcre.h" -typedef pcre *git_regexp; -# define GIT_REGEX_INIT NULL -#elif defined(GIT_REGEX_PCRE2) -# define PCRE2_CODE_UNIT_WIDTH 8 -# include -typedef pcre2_code *git_regexp; -# define GIT_REGEX_INIT NULL -#elif defined(GIT_REGEX_REGCOMP) || defined(GIT_REGEX_REGCOMP_L) -# include -typedef regex_t git_regexp; -# define GIT_REGEX_INIT { 0 } -#else -# error "No regex backend" -#endif - -/** Options supported by @git_regexp_compile. */ -typedef enum { - /** Enable case-insensitive matching */ - GIT_REGEXP_ICASE = (1 << 0) -} git_regexp_flags_t; - -/** Structure containing information about regular expression matching groups */ -typedef struct { - /** Start of the given match. -1 if the group didn't match anything */ - ssize_t start; - /** End of the given match. -1 if the group didn't match anything */ - ssize_t end; -} git_regmatch; - -/** - * Compile a regular expression. The compiled expression needs to - * be cleaned up afterwards with `git_regexp_dispose`. - * - * @param r Pointer to the storage where to initialize the regular expression. - * @param pattern The pattern that shall be compiled. - * @param flags Flags to alter how the pattern shall be handled. - * 0 for defaults, otherwise see @git_regexp_flags_t. - * @return 0 on success, otherwise a negative return value. - */ -int git_regexp_compile(git_regexp *r, const char *pattern, int flags); - -/** - * Free memory associated with the regular expression - * - * @param r The regular expression structure to dispose. - */ -void git_regexp_dispose(git_regexp *r); - -/** - * Test whether a given string matches a compiled regular - * expression. - * - * @param r Compiled regular expression. - * @param string String to match against the regular expression. - * @return 0 if the string matches, a negative error code - * otherwise. GIT_ENOTFOUND if no match was found, - * GIT_EINVALIDSPEC if the regular expression matching - * was invalid. - */ -int git_regexp_match(const git_regexp *r, const char *string); - -/** - * Search for matches inside of a given string. - * - * Given a regular expression with capturing groups, this - * function will populate provided @git_regmatch structures with - * offsets for each of the given matches. Non-matching groups - * will have start and end values of the respective @git_regmatch - * structure set to -1. - * - * @param r Compiled regular expression. - * @param string String to match against the regular expression. - * @param nmatches Number of @git_regmatch structures provided by - * the user. - * @param matches Pointer to an array of @git_regmatch structures. - * @return 0 if the string matches, a negative error code - * otherwise. GIT_ENOTFOUND if no match was found, - * GIT_EINVALIDSPEC if the regular expression matching - * was invalid. - */ -int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches); - -#endif diff --git a/src/libgit2/runtime.c b/src/libgit2/runtime.c deleted file mode 100644 index c05dee8b9..000000000 --- a/src/libgit2/runtime.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" -#include "runtime.h" - -static git_runtime_shutdown_fn shutdown_callback[32]; -static git_atomic32 shutdown_callback_count; - -static git_atomic32 init_count; - -static int init_common(git_runtime_init_fn init_fns[], size_t cnt) -{ - size_t i; - int ret; - - /* Initialize subsystems that have global state */ - for (i = 0; i < cnt; i++) { - if ((ret = init_fns[i]()) != 0) - break; - } - - GIT_MEMORY_BARRIER; - - return ret; -} - -static void shutdown_common(void) -{ - git_runtime_shutdown_fn cb; - int pos; - - for (pos = git_atomic32_get(&shutdown_callback_count); - pos > 0; - pos = git_atomic32_dec(&shutdown_callback_count)) { - cb = git_atomic_swap(shutdown_callback[pos - 1], NULL); - - if (cb != NULL) - cb(); - } -} - -int git_runtime_shutdown_register(git_runtime_shutdown_fn callback) -{ - int count = git_atomic32_inc(&shutdown_callback_count); - - if (count > (int)ARRAY_SIZE(shutdown_callback) || count == 0) { - git_error_set(GIT_ERROR_INVALID, - "too many shutdown callbacks registered"); - git_atomic32_dec(&shutdown_callback_count); - return -1; - } - - shutdown_callback[count - 1] = callback; - - return 0; -} - -#if defined(GIT_THREADS) && defined(GIT_WIN32) - -/* - * On Win32, we use a spinlock to provide locking semantics. This is - * lighter-weight than a proper critical section. - */ -static volatile LONG init_spinlock = 0; - -GIT_INLINE(int) init_lock(void) -{ - while (InterlockedCompareExchange(&init_spinlock, 1, 0)) { Sleep(0); } - return 0; -} - -GIT_INLINE(int) init_unlock(void) -{ - InterlockedExchange(&init_spinlock, 0); - return 0; -} - -#elif defined(GIT_THREADS) && defined(_POSIX_THREADS) - -/* - * On POSIX, we need to use a proper mutex for locking. We might prefer - * a spinlock here, too, but there's no static initializer for a - * pthread_spinlock_t. - */ -static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER; - -GIT_INLINE(int) init_lock(void) -{ - return pthread_mutex_lock(&init_mutex) == 0 ? 0 : -1; -} - -GIT_INLINE(int) init_unlock(void) -{ - return pthread_mutex_unlock(&init_mutex) == 0 ? 0 : -1; -} - -#elif defined(GIT_THREADS) -# error unknown threading model -#else - -# define init_lock() git__noop() -# define init_unlock() git__noop() - -#endif - -int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt) -{ - int ret; - - if (init_lock() < 0) - return -1; - - /* Only do work on a 0 -> 1 transition of the refcount */ - if ((ret = git_atomic32_inc(&init_count)) == 1) { - if (init_common(init_fns, cnt) < 0) - ret = -1; - } - - if (init_unlock() < 0) - return -1; - - return ret; -} - -int git_runtime_init_count(void) -{ - int ret; - - if (init_lock() < 0) - return -1; - - ret = git_atomic32_get(&init_count); - - if (init_unlock() < 0) - return -1; - - return ret; -} - -int git_runtime_shutdown(void) -{ - int ret; - - /* Enter the lock */ - if (init_lock() < 0) - return -1; - - /* Only do work on a 1 -> 0 transition of the refcount */ - if ((ret = git_atomic32_dec(&init_count)) == 0) - shutdown_common(); - - /* Exit the lock */ - if (init_unlock() < 0) - return -1; - - return ret; -} diff --git a/src/libgit2/runtime.h b/src/libgit2/runtime.h deleted file mode 100644 index 24ac58ee9..000000000 --- a/src/libgit2/runtime.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_runtime_h__ -#define INCLUDE_runtime_h__ - -#include "common.h" - -typedef int (*git_runtime_init_fn)(void); -typedef void (*git_runtime_shutdown_fn)(void); - -/** - * Start up a new runtime. If this is the first time that this - * function is called within the context of the current library - * or executable, then the given `init_fns` will be invoked. If - * it is not the first time, they will be ignored. - * - * The given initialization functions _may_ register shutdown - * handlers using `git_runtime_shutdown_register` to be notified - * when the runtime is shutdown. - * - * @param init_fns The list of initialization functions to call - * @param cnt The number of init_fns - * @return The number of initializations performed (including this one) or an error - */ -int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt); - -/* - * Returns the number of initializations active (the number of calls to - * `git_runtime_init` minus the number of calls sto `git_runtime_shutdown`). - * If 0, the runtime is not currently initialized. - * - * @return The number of initializations performed or an error - */ -int git_runtime_init_count(void); - -/** - * Shut down the runtime. If this is the last shutdown call, - * such that there are no remaining `init` calls, then any - * shutdown hooks that have been registered will be invoked. - * - * The number of outstanding initializations will be returned. - * If this number is 0, then the runtime is shutdown. - * - * @return The number of outstanding initializations (after this one) or an error - */ -int git_runtime_shutdown(void); - -/** - * Register a shutdown handler for this runtime. This should be done - * by a function invoked by `git_runtime_init` to ensure that the - * appropriate locks are taken. - * - * @param callback The shutdown handler callback - * @return 0 or an error code - */ -int git_runtime_shutdown_register(git_runtime_shutdown_fn callback); - -#endif diff --git a/src/libgit2/sortedcache.c b/src/libgit2/sortedcache.c deleted file mode 100644 index 7ff900efe..000000000 --- a/src/libgit2/sortedcache.c +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "sortedcache.h" - -int git_sortedcache_new( - git_sortedcache **out, - size_t item_path_offset, - git_sortedcache_free_item_fn free_item, - void *free_item_payload, - git_vector_cmp item_cmp, - const char *path) -{ - git_sortedcache *sc; - size_t pathlen, alloclen; - - pathlen = path ? strlen(path) : 0; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_sortedcache), pathlen); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - sc = git__calloc(1, alloclen); - GIT_ERROR_CHECK_ALLOC(sc); - - if (git_pool_init(&sc->pool, 1) < 0 || - git_vector_init(&sc->items, 4, item_cmp) < 0 || - git_strmap_new(&sc->map) < 0) - goto fail; - - if (git_rwlock_init(&sc->lock)) { - git_error_set(GIT_ERROR_OS, "failed to initialize lock"); - goto fail; - } - - sc->item_path_offset = item_path_offset; - sc->free_item = free_item; - sc->free_item_payload = free_item_payload; - GIT_REFCOUNT_INC(sc); - if (pathlen) - memcpy(sc->path, path, pathlen); - - *out = sc; - return 0; - -fail: - git_strmap_free(sc->map); - git_vector_free(&sc->items); - git_pool_clear(&sc->pool); - git__free(sc); - return -1; -} - -void git_sortedcache_incref(git_sortedcache *sc) -{ - GIT_REFCOUNT_INC(sc); -} - -const char *git_sortedcache_path(git_sortedcache *sc) -{ - return sc->path; -} - -static void sortedcache_clear(git_sortedcache *sc) -{ - git_strmap_clear(sc->map); - - if (sc->free_item) { - size_t i; - void *item; - - git_vector_foreach(&sc->items, i, item) { - sc->free_item(sc->free_item_payload, item); - } - } - - git_vector_clear(&sc->items); - - git_pool_clear(&sc->pool); -} - -static void sortedcache_free(git_sortedcache *sc) -{ - /* acquire write lock to make sure everyone else is done */ - if (git_sortedcache_wlock(sc) < 0) - return; - - sortedcache_clear(sc); - git_vector_free(&sc->items); - git_strmap_free(sc->map); - - git_sortedcache_wunlock(sc); - - git_rwlock_free(&sc->lock); - git__free(sc); -} - -void git_sortedcache_free(git_sortedcache *sc) -{ - if (!sc) - return; - GIT_REFCOUNT_DEC(sc, sortedcache_free); -} - -static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item) -{ - git_sortedcache *sc = payload; - /* path will already have been copied by upsert */ - memcpy(tgt_item, src_item, sc->item_path_offset); - return 0; -} - -/* copy a sorted cache */ -int git_sortedcache_copy( - git_sortedcache **out, - git_sortedcache *src, - bool lock, - int (*copy_item)(void *payload, void *tgt_item, void *src_item), - void *payload) -{ - int error = 0; - git_sortedcache *tgt; - size_t i; - void *src_item, *tgt_item; - - /* just use memcpy if no special copy fn is passed in */ - if (!copy_item) { - copy_item = sortedcache_copy_item; - payload = src; - } - - if ((error = git_sortedcache_new( - &tgt, src->item_path_offset, - src->free_item, src->free_item_payload, - src->items._cmp, src->path)) < 0) - return error; - - if (lock && git_sortedcache_rlock(src) < 0) { - git_sortedcache_free(tgt); - return -1; - } - - git_vector_foreach(&src->items, i, src_item) { - char *path = ((char *)src_item) + src->item_path_offset; - - if ((error = git_sortedcache_upsert(&tgt_item, tgt, path)) < 0 || - (error = copy_item(payload, tgt_item, src_item)) < 0) - break; - } - - if (lock) - git_sortedcache_runlock(src); - if (error) - git_sortedcache_free(tgt); - - *out = !error ? tgt : NULL; - - return error; -} - -/* lock sortedcache while making modifications */ -int git_sortedcache_wlock(git_sortedcache *sc) -{ - GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ - - if (git_rwlock_wrlock(&sc->lock) < 0) { - git_error_set(GIT_ERROR_OS, "unable to acquire write lock on cache"); - return -1; - } - return 0; -} - -/* unlock sorted cache when done with modifications */ -void git_sortedcache_wunlock(git_sortedcache *sc) -{ - git_vector_sort(&sc->items); - git_rwlock_wrunlock(&sc->lock); -} - -/* lock sortedcache for read */ -int git_sortedcache_rlock(git_sortedcache *sc) -{ - GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ - - if (git_rwlock_rdlock(&sc->lock) < 0) { - git_error_set(GIT_ERROR_OS, "unable to acquire read lock on cache"); - return -1; - } - return 0; -} - -/* unlock sorted cache when done reading */ -void git_sortedcache_runlock(git_sortedcache *sc) -{ - GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ - git_rwlock_rdunlock(&sc->lock); -} - -/* if the file has changed, lock cache and load file contents into buf; - * returns <0 on error, >0 if file has not changed - */ -int git_sortedcache_lockandload(git_sortedcache *sc, git_str *buf) -{ - int error, fd; - struct stat st; - - if ((error = git_sortedcache_wlock(sc)) < 0) - return error; - - if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0) - goto unlock; - - if ((fd = git_futils_open_ro(sc->path)) < 0) { - error = fd; - goto unlock; - } - - if (p_fstat(fd, &st) < 0) { - git_error_set(GIT_ERROR_OS, "failed to stat file"); - error = -1; - (void)p_close(fd); - goto unlock; - } - - if (!git__is_sizet(st.st_size)) { - git_error_set(GIT_ERROR_INVALID, "unable to load file larger than size_t"); - error = -1; - (void)p_close(fd); - goto unlock; - } - - if (buf) - error = git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size); - - (void)p_close(fd); - - if (error < 0) - goto unlock; - - return 1; /* return 1 -> file needs reload and was successfully loaded */ - -unlock: - git_sortedcache_wunlock(sc); - return error; -} - -void git_sortedcache_updated(git_sortedcache *sc) -{ - /* update filestamp to latest value */ - git_futils_filestamp_check(&sc->stamp, sc->path); -} - -/* release all items in sorted cache */ -int git_sortedcache_clear(git_sortedcache *sc, bool wlock) -{ - if (wlock && git_sortedcache_wlock(sc) < 0) - return -1; - - sortedcache_clear(sc); - - if (wlock) - git_sortedcache_wunlock(sc); - - return 0; -} - -/* find and/or insert item, returning pointer to item data */ -int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) -{ - size_t keylen, itemlen; - int error = 0; - char *item_key; - void *item; - - if ((item = git_strmap_get(sc->map, key)) != NULL) - goto done; - - keylen = strlen(key); - itemlen = sc->item_path_offset + keylen + 1; - itemlen = (itemlen + 7) & ~7; - - if ((item = git_pool_mallocz(&sc->pool, itemlen)) == NULL) { - /* don't use GIT_ERROR_CHECK_ALLOC b/c of lock */ - error = -1; - goto done; - } - - /* one strange thing is that even if the vector or hash table insert - * fail, there is no way to free the pool item so we just abandon it - */ - - item_key = ((char *)item) + sc->item_path_offset; - memcpy(item_key, key, keylen); - - if ((error = git_strmap_set(sc->map, item_key, item)) < 0) - goto done; - - if ((error = git_vector_insert(&sc->items, item)) < 0) - git_strmap_delete(sc->map, item_key); - -done: - if (out) - *out = !error ? item : NULL; - return error; -} - -/* lookup item by key */ -void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key) -{ - return git_strmap_get(sc->map, key); -} - -/* find out how many items are in the cache */ -size_t git_sortedcache_entrycount(const git_sortedcache *sc) -{ - return git_vector_length(&sc->items); -} - -/* lookup item by index */ -void *git_sortedcache_entry(git_sortedcache *sc, size_t pos) -{ - /* make sure the items are sorted so this gets the correct item */ - if (!git_vector_is_sorted(&sc->items)) - git_vector_sort(&sc->items); - - return git_vector_get(&sc->items, pos); -} - -/* helper struct so bsearch callback can know offset + key value for cmp */ -struct sortedcache_magic_key { - size_t offset; - const char *key; -}; - -static int sortedcache_magic_cmp(const void *key, const void *value) -{ - const struct sortedcache_magic_key *magic = key; - const char *value_key = ((const char *)value) + magic->offset; - return strcmp(magic->key, value_key); -} - -/* lookup index of item by key */ -int git_sortedcache_lookup_index( - size_t *out, git_sortedcache *sc, const char *key) -{ - struct sortedcache_magic_key magic; - - magic.offset = sc->item_path_offset; - magic.key = key; - - return git_vector_bsearch2(out, &sc->items, sortedcache_magic_cmp, &magic); -} - -/* remove entry from cache */ -int git_sortedcache_remove(git_sortedcache *sc, size_t pos) -{ - char *item; - - /* - * Because of pool allocation, this can't actually remove the item, - * but we can remove it from the items vector and the hash table. - */ - - if ((item = git_vector_get(&sc->items, pos)) == NULL) { - git_error_set(GIT_ERROR_INVALID, "removing item out of range"); - return GIT_ENOTFOUND; - } - - (void)git_vector_remove(&sc->items, pos); - - git_strmap_delete(sc->map, item + sc->item_path_offset); - - if (sc->free_item) - sc->free_item(sc->free_item_payload, item); - - return 0; -} - diff --git a/src/libgit2/sortedcache.h b/src/libgit2/sortedcache.h deleted file mode 100644 index ef260a093..000000000 --- a/src/libgit2/sortedcache.h +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_sorted_cache_h__ -#define INCLUDE_sorted_cache_h__ - -#include "common.h" - -#include "util.h" -#include "futils.h" -#include "vector.h" -#include "thread.h" -#include "pool.h" -#include "strmap.h" - -#include - -/* - * The purpose of this data structure is to cache the parsed contents of a - * file (a.k.a. the backing file) where each item in the file can be - * identified by a key string and you want to both look them up by name - * and traverse them in sorted order. Each item is assumed to itself end - * in a GIT_FLEX_ARRAY. - */ - -typedef void (*git_sortedcache_free_item_fn)(void *payload, void *item); - -typedef struct { - git_refcount rc; - git_rwlock lock; - size_t item_path_offset; - git_sortedcache_free_item_fn free_item; - void *free_item_payload; - git_pool pool; - git_vector items; - git_strmap *map; - git_futils_filestamp stamp; - char path[GIT_FLEX_ARRAY]; -} git_sortedcache; - -/* Create a new sortedcache - * - * Even though every sortedcache stores items with a GIT_FLEX_ARRAY at - * the end containing their key string, you have to provide the item_cmp - * sorting function because the sorting function doesn't get a payload - * and therefore can't know the offset to the item key string. :-( - * - * @param out The allocated git_sortedcache - * @param item_path_offset Offset to the GIT_FLEX_ARRAY item key in the - * struct - use offsetof(struct mine, key-field) to get this - * @param free_item Optional callback to free each item - * @param free_item_payload Optional payload passed to free_item callback - * @param item_cmp Compare the keys of two items - * @param path The path to the backing store file for this cache; this - * may be NULL. The cache makes it easy to load this and check - * if it has been modified since the last load and/or write. - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_new( - git_sortedcache **out, - size_t item_path_offset, /* use offsetof(struct, path-field) macro */ - git_sortedcache_free_item_fn free_item, - void *free_item_payload, - git_vector_cmp item_cmp, - const char *path); - -/* Copy a sorted cache - * - * - `copy_item` can be NULL to just use memcpy - * - if `lock`, grabs read lock on `src` during copy and releases after - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_copy( - git_sortedcache **out, - git_sortedcache *src, - bool lock, - int (*copy_item)(void *payload, void *tgt_item, void *src_item), - void *payload); - -/* Free sorted cache (first calling `free_item` callbacks) - * - * Don't call on a locked collection - it may acquire a write lock - */ -void git_sortedcache_free(git_sortedcache *sc); - -/* Increment reference count - balance with call to free */ -void git_sortedcache_incref(git_sortedcache *sc); - -/* Get the pathname associated with this cache at creation time */ -const char *git_sortedcache_path(git_sortedcache *sc); - -/* - * CACHE WRITE FUNCTIONS - * - * The following functions require you to have a writer lock to make the - * modification. Some of the functions take a `wlock` parameter and - * will optionally lock and unlock for you if that is passed as true. - * - */ - -/* Lock sortedcache for write */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_wlock(git_sortedcache *sc); - -/* Unlock sorted cache when done with write */ -void git_sortedcache_wunlock(git_sortedcache *sc); - -/* Lock cache and load backing file into a buffer. - * - * This grabs a write lock on the cache then looks at the modification - * time and size of the file on disk. - * - * If the file appears to have changed, this loads the file contents into - * the buffer and returns a positive value leaving the cache locked - the - * caller should parse the file content, update the cache as needed, then - * release the lock. NOTE: In this case, the caller MUST unlock the cache. - * - * If the file appears to be unchanged, then this automatically releases - * the lock on the cache, clears the buffer, and returns 0. - * - * @return 0 if up-to-date, 1 if out-of-date, <0 on error - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_lockandload( - git_sortedcache *sc, git_str *buf); - -/* Refresh file timestamp after write completes - * You should already be holding the write lock when you call this. - */ -void git_sortedcache_updated(git_sortedcache *sc); - -/* Release all items in sorted cache - * - * If `wlock` is true, grabs write lock and releases when done, otherwise - * you should already be holding a write lock when you call this. - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_clear( - git_sortedcache *sc, bool wlock); - -/* Find and/or insert item, returning pointer to item data. - * You should already be holding the write lock when you call this. - */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_upsert( - void **out, git_sortedcache *sc, const char *key); - -/* Removes entry at pos from cache - * You should already be holding the write lock when you call this. - */ -int git_sortedcache_remove(git_sortedcache *sc, size_t pos); - -/* - * CACHE READ FUNCTIONS - * - * The following functions access items in the cache. To prevent the - * results from being invalidated before they can be used, you should be - * holding either a read lock or a write lock when using these functions. - * - */ - -/* Lock sortedcache for read */ -GIT_WARN_UNUSED_RESULT int git_sortedcache_rlock(git_sortedcache *sc); - -/* Unlock sorted cache when done with read */ -void git_sortedcache_runlock(git_sortedcache *sc); - -/* Lookup item by key - returns NULL if not found */ -void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key); - -/* Get how many items are in the cache - * - * You can call this function without holding a lock, but be aware - * that it may change before you use it. - */ -size_t git_sortedcache_entrycount(const git_sortedcache *sc); - -/* Lookup item by index - returns NULL if out of range */ -void *git_sortedcache_entry(git_sortedcache *sc, size_t pos); - -/* Lookup index of item by key - returns GIT_ENOTFOUND if not found */ -int git_sortedcache_lookup_index( - size_t *out, git_sortedcache *sc, const char *key); - -#endif diff --git a/src/libgit2/str.c b/src/libgit2/str.c deleted file mode 100644 index 0d405bfda..000000000 --- a/src/libgit2/str.c +++ /dev/null @@ -1,1372 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "str.h" -#include "posix.h" -#include - -/* Used as default value for git_str->ptr so that people can always - * assume ptr is non-NULL and zero terminated even for new git_strs. - */ -char git_str__initstr[1]; - -char git_str__oom[1]; - -#define ENSURE_SIZE(b, d) \ - if ((b)->ptr == git_str__oom || \ - ((d) > (b)->asize && git_str_grow((b), (d)) < 0))\ - return -1; - - -int git_str_init(git_str *buf, size_t initial_size) -{ - buf->asize = 0; - buf->size = 0; - buf->ptr = git_str__initstr; - - ENSURE_SIZE(buf, initial_size); - - return 0; -} - -int git_str_try_grow( - git_str *buf, size_t target_size, bool mark_oom) -{ - char *new_ptr; - size_t new_size; - - if (buf->ptr == git_str__oom) - return -1; - - if (buf->asize == 0 && buf->size != 0) { - git_error_set(GIT_ERROR_INVALID, "cannot grow a borrowed buffer"); - return GIT_EINVALID; - } - - if (!target_size) - target_size = buf->size; - - if (target_size <= buf->asize) - return 0; - - if (buf->asize == 0) { - new_size = target_size; - new_ptr = NULL; - } else { - new_size = buf->asize; - /* - * Grow the allocated buffer by 1.5 to allow - * re-use of memory holes resulting from the - * realloc. If this is still too small, then just - * use the target size. - */ - if ((new_size = (new_size << 1) - (new_size >> 1)) < target_size) - new_size = target_size; - new_ptr = buf->ptr; - } - - /* round allocation up to multiple of 8 */ - new_size = (new_size + 7) & ~7; - - if (new_size < buf->size) { - if (mark_oom) { - if (buf->ptr && buf->ptr != git_str__initstr) - git__free(buf->ptr); - buf->ptr = git_str__oom; - } - - git_error_set_oom(); - return -1; - } - - new_ptr = git__realloc(new_ptr, new_size); - - if (!new_ptr) { - if (mark_oom) { - if (buf->ptr && (buf->ptr != git_str__initstr)) - git__free(buf->ptr); - buf->ptr = git_str__oom; - } - return -1; - } - - buf->asize = new_size; - buf->ptr = new_ptr; - - /* truncate the existing buffer size if necessary */ - if (buf->size >= buf->asize) - buf->size = buf->asize - 1; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -int git_str_grow(git_str *buffer, size_t target_size) -{ - return git_str_try_grow(buffer, target_size, true); -} - -int git_str_grow_by(git_str *buffer, size_t additional_size) -{ - size_t newsize; - - if (GIT_ADD_SIZET_OVERFLOW(&newsize, buffer->size, additional_size)) { - buffer->ptr = git_str__oom; - return -1; - } - - return git_str_try_grow(buffer, newsize, true); -} - -void git_str_dispose(git_str *buf) -{ - if (!buf) return; - - if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_str__oom) - git__free(buf->ptr); - - git_str_init(buf, 0); -} - -void git_str_clear(git_str *buf) -{ - buf->size = 0; - - if (!buf->ptr) { - buf->ptr = git_str__initstr; - buf->asize = 0; - } - - if (buf->asize > 0) - buf->ptr[0] = '\0'; -} - -int git_str_set(git_str *buf, const void *data, size_t len) -{ - size_t alloclen; - - if (len == 0 || data == NULL) { - git_str_clear(buf); - } else { - if (data != buf->ptr) { - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); - ENSURE_SIZE(buf, alloclen); - memmove(buf->ptr, data, len); - } - - buf->size = len; - if (buf->asize > buf->size) - buf->ptr[buf->size] = '\0'; - - } - return 0; -} - -int git_str_sets(git_str *buf, const char *string) -{ - return git_str_set(buf, string, string ? strlen(string) : 0); -} - -int git_str_putc(git_str *buf, char c) -{ - size_t new_size; - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, 2); - ENSURE_SIZE(buf, new_size); - buf->ptr[buf->size++] = c; - buf->ptr[buf->size] = '\0'; - return 0; -} - -int git_str_putcn(git_str *buf, char c, size_t len) -{ - size_t new_size; - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - memset(buf->ptr + buf->size, c, len); - buf->size += len; - buf->ptr[buf->size] = '\0'; - return 0; -} - -int git_str_put(git_str *buf, const char *data, size_t len) -{ - if (len) { - size_t new_size; - - GIT_ASSERT_ARG(data); - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - memmove(buf->ptr + buf->size, data, len); - buf->size += len; - buf->ptr[buf->size] = '\0'; - } - return 0; -} - -int git_str_puts(git_str *buf, const char *string) -{ - GIT_ASSERT_ARG(string); - - return git_str_put(buf, string, strlen(string)); -} - -static char hex_encode[] = "0123456789abcdef"; - -int git_str_encode_hexstr(git_str *str, const char *data, size_t len) -{ - size_t new_size, i; - char *s; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&new_size, len, 2); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - - if (git_str_grow_by(str, new_size) < 0) - return -1; - - s = str->ptr + str->size; - - for (i = 0; i < len; i++) { - *s++ = hex_encode[(data[i] & 0xf0) >> 4]; - *s++ = hex_encode[(data[i] & 0x0f)]; - } - - str->size += (len * 2); - str->ptr[str->size] = '\0'; - - return 0; -} - -static const char base64_encode[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -int git_str_encode_base64(git_str *buf, const char *data, size_t len) -{ - size_t extra = len % 3; - uint8_t *write, a, b, c; - const uint8_t *read = (const uint8_t *)data; - size_t blocks = (len / 3) + !!extra, alloclen; - - GIT_ERROR_CHECK_ALLOC_ADD(&blocks, blocks, 1); - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 4); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size); - - ENSURE_SIZE(buf, alloclen); - write = (uint8_t *)&buf->ptr[buf->size]; - - /* convert each run of 3 bytes into 4 output bytes */ - for (len -= extra; len > 0; len -= 3) { - a = *read++; - b = *read++; - c = *read++; - - *write++ = base64_encode[a >> 2]; - *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; - *write++ = base64_encode[(b & 0x0f) << 2 | c >> 6]; - *write++ = base64_encode[c & 0x3f]; - } - - if (extra > 0) { - a = *read++; - b = (extra > 1) ? *read++ : 0; - - *write++ = base64_encode[a >> 2]; - *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; - *write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '='; - *write++ = '='; - } - - buf->size = ((char *)write) - buf->ptr; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -/* The inverse of base64_encode */ -static const int8_t base64_decode[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 -}; - -int git_str_decode_base64(git_str *buf, const char *base64, size_t len) -{ - size_t i; - int8_t a, b, c, d; - size_t orig_size = buf->size, new_size; - - if (len % 4) { - git_error_set(GIT_ERROR_INVALID, "invalid base64 input"); - return -1; - } - - GIT_ASSERT_ARG(len % 4 == 0); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - - for (i = 0; i < len; i += 4) { - if ((a = base64_decode[(unsigned char)base64[i]]) < 0 || - (b = base64_decode[(unsigned char)base64[i+1]]) < 0 || - (c = base64_decode[(unsigned char)base64[i+2]]) < 0 || - (d = base64_decode[(unsigned char)base64[i+3]]) < 0) { - buf->size = orig_size; - buf->ptr[buf->size] = '\0'; - - git_error_set(GIT_ERROR_INVALID, "invalid base64 input"); - return -1; - } - - buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4); - buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); - buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f); - } - - buf->ptr[buf->size] = '\0'; - return 0; -} - -static const char base85_encode[] = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; - -int git_str_encode_base85(git_str *buf, const char *data, size_t len) -{ - size_t blocks = (len / 4) + !!(len % 4), alloclen; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 5); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - - ENSURE_SIZE(buf, alloclen); - - while (len) { - uint32_t acc = 0; - char b85[5]; - int i; - - for (i = 24; i >= 0; i -= 8) { - uint8_t ch = *data++; - acc |= (uint32_t)ch << i; - - if (--len == 0) - break; - } - - for (i = 4; i >= 0; i--) { - int val = acc % 85; - acc /= 85; - - b85[i] = base85_encode[val]; - } - - for (i = 0; i < 5; i++) - buf->ptr[buf->size++] = b85[i]; - } - - buf->ptr[buf->size] = '\0'; - - return 0; -} - -/* The inverse of base85_encode */ -static const int8_t base85_decode[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 73, 74, 75, 76, 77, - 78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80, - 81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 -}; - -int git_str_decode_base85( - git_str *buf, - const char *base85, - size_t base85_len, - size_t output_len) -{ - size_t orig_size = buf->size, new_size; - - if (base85_len % 5 || - output_len > base85_len * 4 / 5) { - git_error_set(GIT_ERROR_INVALID, "invalid base85 input"); - return -1; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - - while (output_len) { - unsigned acc = 0; - int de, cnt = 4; - unsigned char ch; - do { - ch = *base85++; - de = base85_decode[ch]; - if (--de < 0) - goto on_error; - - acc = acc * 85 + de; - } while (--cnt); - ch = *base85++; - de = base85_decode[ch]; - if (--de < 0) - goto on_error; - - /* Detect overflow. */ - if (0xffffffff / 85 < acc || - 0xffffffff - de < (acc *= 85)) - goto on_error; - - acc += de; - - cnt = (output_len < 4) ? (int)output_len : 4; - output_len -= cnt; - do { - acc = (acc << 8) | (acc >> 24); - buf->ptr[buf->size++] = acc; - } while (--cnt); - } - - buf->ptr[buf->size] = 0; - - return 0; - -on_error: - buf->size = orig_size; - buf->ptr[buf->size] = '\0'; - - git_error_set(GIT_ERROR_INVALID, "invalid base85 input"); - return -1; -} - -#define HEX_DECODE(c) ((c | 32) % 39 - 9) - -int git_str_decode_percent( - git_str *buf, - const char *str, - size_t str_len) -{ - size_t str_pos, new_size; - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, str_len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - - for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) { - if (str[str_pos] == '%' && - str_len > str_pos + 2 && - isxdigit(str[str_pos + 1]) && - isxdigit(str[str_pos + 2])) { - buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) + - HEX_DECODE(str[str_pos + 2]); - str_pos += 2; - } else { - buf->ptr[buf->size] = str[str_pos]; - } - } - - buf->ptr[buf->size] = '\0'; - return 0; -} - -int git_str_vprintf(git_str *buf, const char *format, va_list ap) -{ - size_t expected_size, new_size; - int len; - - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&expected_size, strlen(format), 2); - GIT_ERROR_CHECK_ALLOC_ADD(&expected_size, expected_size, buf->size); - ENSURE_SIZE(buf, expected_size); - - while (1) { - va_list args; - va_copy(args, ap); - - len = p_vsnprintf( - buf->ptr + buf->size, - buf->asize - buf->size, - format, args - ); - - va_end(args); - - if (len < 0) { - git__free(buf->ptr); - buf->ptr = git_str__oom; - return -1; - } - - if ((size_t)len + 1 <= buf->asize - buf->size) { - buf->size += len; - break; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - ENSURE_SIZE(buf, new_size); - } - - return 0; -} - -int git_str_printf(git_str *buf, const char *format, ...) -{ - int r; - va_list ap; - - va_start(ap, format); - r = git_str_vprintf(buf, format, ap); - va_end(ap); - - return r; -} - -int git_str_copy_cstr(char *data, size_t datasize, const git_str *buf) -{ - size_t copylen; - - GIT_ASSERT_ARG(data); - GIT_ASSERT_ARG(datasize); - GIT_ASSERT_ARG(buf); - - data[0] = '\0'; - - if (buf->size == 0 || buf->asize <= 0) - return 0; - - copylen = buf->size; - if (copylen > datasize - 1) - copylen = datasize - 1; - memmove(data, buf->ptr, copylen); - data[copylen] = '\0'; - - return 0; -} - -void git_str_consume_bytes(git_str *buf, size_t len) -{ - git_str_consume(buf, buf->ptr + len); -} - -void git_str_consume(git_str *buf, const char *end) -{ - if (end > buf->ptr && end <= buf->ptr + buf->size) { - size_t consumed = end - buf->ptr; - memmove(buf->ptr, end, buf->size - consumed); - buf->size -= consumed; - buf->ptr[buf->size] = '\0'; - } -} - -void git_str_truncate(git_str *buf, size_t len) -{ - if (len >= buf->size) - return; - - buf->size = len; - if (buf->size < buf->asize) - buf->ptr[buf->size] = '\0'; -} - -void git_str_shorten(git_str *buf, size_t amount) -{ - if (buf->size > amount) - git_str_truncate(buf, buf->size - amount); - else - git_str_clear(buf); -} - -void git_str_truncate_at_char(git_str *buf, char separator) -{ - ssize_t idx = git_str_find(buf, separator); - if (idx >= 0) - git_str_truncate(buf, (size_t)idx); -} - -void git_str_rtruncate_at_char(git_str *buf, char separator) -{ - ssize_t idx = git_str_rfind_next(buf, separator); - git_str_truncate(buf, idx < 0 ? 0 : (size_t)idx); -} - -void git_str_swap(git_str *str_a, git_str *str_b) -{ - git_str t = *str_a; - *str_a = *str_b; - *str_b = t; -} - -char *git_str_detach(git_str *buf) -{ - char *data = buf->ptr; - - if (buf->asize == 0 || buf->ptr == git_str__oom) - return NULL; - - git_str_init(buf, 0); - - return data; -} - -int git_str_attach(git_str *buf, char *ptr, size_t asize) -{ - git_str_dispose(buf); - - if (ptr) { - buf->ptr = ptr; - buf->size = strlen(ptr); - if (asize) - buf->asize = (asize < buf->size) ? buf->size + 1 : asize; - else /* pass 0 to fall back on strlen + 1 */ - buf->asize = buf->size + 1; - } - - ENSURE_SIZE(buf, asize); - return 0; -} - -void git_str_attach_notowned(git_str *buf, const char *ptr, size_t size) -{ - if (git_str_is_allocated(buf)) - git_str_dispose(buf); - - if (!size) { - git_str_init(buf, 0); - } else { - buf->ptr = (char *)ptr; - buf->asize = 0; - buf->size = size; - } -} - -int git_str_join_n(git_str *buf, char separator, int nbuf, ...) -{ - va_list ap; - int i; - size_t total_size = 0, original_size = buf->size; - char *out, *original = buf->ptr; - - if (buf->size > 0 && buf->ptr[buf->size - 1] != separator) - ++total_size; /* space for initial separator */ - - /* Make two passes to avoid multiple reallocation */ - - va_start(ap, nbuf); - for (i = 0; i < nbuf; ++i) { - const char *segment; - size_t segment_len; - - segment = va_arg(ap, const char *); - if (!segment) - continue; - - segment_len = strlen(segment); - - GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, segment_len); - - if (segment_len == 0 || segment[segment_len - 1] != separator) - GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1); - } - va_end(ap); - - /* expand buffer if needed */ - if (total_size == 0) - return 0; - - GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1); - if (git_str_grow_by(buf, total_size) < 0) - return -1; - - out = buf->ptr + buf->size; - - /* append separator to existing buf if needed */ - if (buf->size > 0 && out[-1] != separator) - *out++ = separator; - - va_start(ap, nbuf); - for (i = 0; i < nbuf; ++i) { - const char *segment; - size_t segment_len; - - segment = va_arg(ap, const char *); - if (!segment) - continue; - - /* deal with join that references buffer's original content */ - if (segment >= original && segment < original + original_size) { - size_t offset = (segment - original); - segment = buf->ptr + offset; - segment_len = original_size - offset; - } else { - segment_len = strlen(segment); - } - - /* skip leading separators */ - if (out > buf->ptr && out[-1] == separator) - while (segment_len > 0 && *segment == separator) { - segment++; - segment_len--; - } - - /* copy over next buffer */ - if (segment_len > 0) { - memmove(out, segment, segment_len); - out += segment_len; - } - - /* append trailing separator (except for last item) */ - if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator) - *out++ = separator; - } - va_end(ap); - - /* set size based on num characters actually written */ - buf->size = out - buf->ptr; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -int git_str_join( - git_str *buf, - char separator, - const char *str_a, - const char *str_b) -{ - size_t strlen_a = str_a ? strlen(str_a) : 0; - size_t strlen_b = strlen(str_b); - size_t alloc_len; - int need_sep = 0; - ssize_t offset_a = -1; - - /* not safe to have str_b point internally to the buffer */ - if (buf->size) - GIT_ASSERT_ARG(str_b < buf->ptr || str_b >= buf->ptr + buf->size); - - /* figure out if we need to insert a separator */ - if (separator && strlen_a) { - while (*str_b == separator) { str_b++; strlen_b--; } - if (str_a[strlen_a - 1] != separator) - need_sep = 1; - } - - /* str_a could be part of the buffer */ - if (buf->size && str_a >= buf->ptr && str_a < buf->ptr + buf->size) - offset_a = str_a - buf->ptr; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); - ENSURE_SIZE(buf, alloc_len); - - /* fix up internal pointers */ - if (offset_a >= 0) - str_a = buf->ptr + offset_a; - - /* do the actual copying */ - if (offset_a != 0 && str_a) - memmove(buf->ptr, str_a, strlen_a); - if (need_sep) - buf->ptr[strlen_a] = separator; - memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b); - - buf->size = strlen_a + strlen_b + need_sep; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -int git_str_join3( - git_str *buf, - char separator, - const char *str_a, - const char *str_b, - const char *str_c) -{ - size_t len_a = strlen(str_a), - len_b = strlen(str_b), - len_c = strlen(str_c), - len_total; - int sep_a = 0, sep_b = 0; - char *tgt; - - /* for this function, disallow pointers into the existing buffer */ - GIT_ASSERT(str_a < buf->ptr || str_a >= buf->ptr + buf->size); - GIT_ASSERT(str_b < buf->ptr || str_b >= buf->ptr + buf->size); - GIT_ASSERT(str_c < buf->ptr || str_c >= buf->ptr + buf->size); - - if (separator) { - if (len_a > 0) { - while (*str_b == separator) { str_b++; len_b--; } - sep_a = (str_a[len_a - 1] != separator); - } - if (len_a > 0 || len_b > 0) - while (*str_c == separator) { str_c++; len_c--; } - if (len_b > 0) - sep_b = (str_b[len_b - 1] != separator); - } - - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_a, sep_a); - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_b); - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b); - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_c); - GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, 1); - ENSURE_SIZE(buf, len_total); - - tgt = buf->ptr; - - if (len_a) { - memcpy(tgt, str_a, len_a); - tgt += len_a; - } - if (sep_a) - *tgt++ = separator; - if (len_b) { - memcpy(tgt, str_b, len_b); - tgt += len_b; - } - if (sep_b) - *tgt++ = separator; - if (len_c) - memcpy(tgt, str_c, len_c); - - buf->size = len_a + sep_a + len_b + sep_b + len_c; - buf->ptr[buf->size] = '\0'; - - return 0; -} - -void git_str_rtrim(git_str *buf) -{ - while (buf->size > 0) { - if (!git__isspace(buf->ptr[buf->size - 1])) - break; - - buf->size--; - } - - if (buf->asize > buf->size) - buf->ptr[buf->size] = '\0'; -} - -int git_str_cmp(const git_str *a, const git_str *b) -{ - int result = memcmp(a->ptr, b->ptr, min(a->size, b->size)); - return (result != 0) ? result : - (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0; -} - -int git_str_splice( - git_str *buf, - size_t where, - size_t nb_to_remove, - const char *data, - size_t nb_to_insert) -{ - char *splice_loc; - size_t new_size, alloc_size; - - GIT_ASSERT(buf); - GIT_ASSERT(where <= buf->size); - GIT_ASSERT(nb_to_remove <= buf->size - where); - - splice_loc = buf->ptr + where; - - /* Ported from git.git - * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176 - */ - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (buf->size - nb_to_remove), nb_to_insert); - GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, new_size, 1); - ENSURE_SIZE(buf, alloc_size); - - memmove(splice_loc + nb_to_insert, - splice_loc + nb_to_remove, - buf->size - where - nb_to_remove); - - memcpy(splice_loc, data, nb_to_insert); - - buf->size = new_size; - buf->ptr[buf->size] = '\0'; - return 0; -} - -/* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */ -int git_str_quote(git_str *buf) -{ - const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' }; - git_str quoted = GIT_STR_INIT; - size_t i = 0; - bool quote = false; - int error = 0; - - /* walk to the first char that needs quoting */ - if (buf->size && buf->ptr[0] == '!') - quote = true; - - for (i = 0; !quote && i < buf->size; i++) { - if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' || - buf->ptr[i] < ' ' || buf->ptr[i] > '~') { - quote = true; - break; - } - } - - if (!quote) - goto done; - - git_str_putc("ed, '"'); - git_str_put("ed, buf->ptr, i); - - for (; i < buf->size; i++) { - /* whitespace - use the map above, which is ordered by ascii value */ - if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') { - git_str_putc("ed, '\\'); - git_str_putc("ed, whitespace[buf->ptr[i] - '\a']); - } - - /* double quote and backslash must be escaped */ - else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') { - git_str_putc("ed, '\\'); - git_str_putc("ed, buf->ptr[i]); - } - - /* escape anything unprintable as octal */ - else if (buf->ptr[i] != ' ' && - (buf->ptr[i] < '!' || buf->ptr[i] > '~')) { - git_str_printf("ed, "\\%03o", (unsigned char)buf->ptr[i]); - } - - /* yay, printable! */ - else { - git_str_putc("ed, buf->ptr[i]); - } - } - - git_str_putc("ed, '"'); - - if (git_str_oom("ed)) { - error = -1; - goto done; - } - - git_str_swap("ed, buf); - -done: - git_str_dispose("ed); - return error; -} - -/* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */ -int git_str_unquote(git_str *buf) -{ - size_t i, j; - char ch; - - git_str_rtrim(buf); - - if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"') - goto invalid; - - for (i = 0, j = 1; j < buf->size-1; i++, j++) { - ch = buf->ptr[j]; - - if (ch == '\\') { - if (j == buf->size-2) - goto invalid; - - ch = buf->ptr[++j]; - - switch (ch) { - /* \" or \\ simply copy the char in */ - case '"': case '\\': - break; - - /* add the appropriate escaped char */ - case 'a': ch = '\a'; break; - case 'b': ch = '\b'; break; - case 'f': ch = '\f'; break; - case 'n': ch = '\n'; break; - case 'r': ch = '\r'; break; - case 't': ch = '\t'; break; - case 'v': ch = '\v'; break; - - /* \xyz digits convert to the char*/ - case '0': case '1': case '2': case '3': - if (j == buf->size-3) { - git_error_set(GIT_ERROR_INVALID, - "truncated quoted character \\%c", ch); - return -1; - } - - if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' || - buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') { - git_error_set(GIT_ERROR_INVALID, - "truncated quoted character \\%c%c%c", - buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]); - return -1; - } - - ch = ((buf->ptr[j] - '0') << 6) | - ((buf->ptr[j+1] - '0') << 3) | - (buf->ptr[j+2] - '0'); - j += 2; - break; - - default: - git_error_set(GIT_ERROR_INVALID, "invalid quoted character \\%c", ch); - return -1; - } - } - - buf->ptr[i] = ch; - } - - buf->ptr[i] = '\0'; - buf->size = i; - - return 0; - -invalid: - git_error_set(GIT_ERROR_INVALID, "invalid quoted line"); - return -1; -} - -int git_str_puts_escaped( - git_str *buf, - const char *string, - const char *esc_chars, - const char *esc_with) -{ - const char *scan; - size_t total = 0, esc_len = strlen(esc_with), count, alloclen; - - if (!string) - return 0; - - for (scan = string; *scan; ) { - /* count run of non-escaped characters */ - count = strcspn(scan, esc_chars); - total += count; - scan += count; - /* count run of escaped characters */ - count = strspn(scan, esc_chars); - total += count * (esc_len + 1); - scan += count; - } - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, total, 1); - if (git_str_grow_by(buf, alloclen) < 0) - return -1; - - for (scan = string; *scan; ) { - count = strcspn(scan, esc_chars); - - memmove(buf->ptr + buf->size, scan, count); - scan += count; - buf->size += count; - - for (count = strspn(scan, esc_chars); count > 0; --count) { - /* copy escape sequence */ - memmove(buf->ptr + buf->size, esc_with, esc_len); - buf->size += esc_len; - /* copy character to be escaped */ - buf->ptr[buf->size] = *scan; - buf->size++; - scan++; - } - } - - buf->ptr[buf->size] = '\0'; - - return 0; -} - -void git_str_unescape(git_str *buf) -{ - buf->size = git__unescape(buf->ptr); -} - -int git_str_crlf_to_lf(git_str *tgt, const git_str *src) -{ - const char *scan = src->ptr; - const char *scan_end = src->ptr + src->size; - const char *next = memchr(scan, '\r', src->size); - size_t new_size; - char *out; - - GIT_ASSERT(tgt != src); - - if (!next) - return git_str_set(tgt, src->ptr, src->size); - - /* reduce reallocs while in the loop */ - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, src->size, 1); - if (git_str_grow(tgt, new_size) < 0) - return -1; - - out = tgt->ptr; - tgt->size = 0; - - /* Find the next \r and copy whole chunk up to there to tgt */ - for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) { - if (next > scan) { - size_t copylen = (size_t)(next - scan); - memcpy(out, scan, copylen); - out += copylen; - } - - /* Do not drop \r unless it is followed by \n */ - if (next + 1 == scan_end || next[1] != '\n') - *out++ = '\r'; - } - - /* Copy remaining input into dest */ - if (scan < scan_end) { - size_t remaining = (size_t)(scan_end - scan); - memcpy(out, scan, remaining); - out += remaining; - } - - tgt->size = (size_t)(out - tgt->ptr); - tgt->ptr[tgt->size] = '\0'; - - return 0; -} - -int git_str_lf_to_crlf(git_str *tgt, const git_str *src) -{ - const char *start = src->ptr; - const char *end = start + src->size; - const char *scan = start; - const char *next = memchr(scan, '\n', src->size); - size_t alloclen; - - GIT_ASSERT(tgt != src); - - if (!next) - return git_str_set(tgt, src->ptr, src->size); - - /* attempt to reduce reallocs while in the loop */ - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, src->size, src->size >> 4); - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); - if (git_str_grow(tgt, alloclen) < 0) - return -1; - tgt->size = 0; - - for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) { - size_t copylen = next - scan; - - /* if we find mixed line endings, carry on */ - if (copylen && next[-1] == '\r') - copylen--; - - GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, copylen, 3); - if (git_str_grow_by(tgt, alloclen) < 0) - return -1; - - if (copylen) { - memcpy(tgt->ptr + tgt->size, scan, copylen); - tgt->size += copylen; - } - - tgt->ptr[tgt->size++] = '\r'; - tgt->ptr[tgt->size++] = '\n'; - } - - tgt->ptr[tgt->size] = '\0'; - return git_str_put(tgt, scan, end - scan); -} - -int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count) -{ - size_t i; - const char *str, *pfx; - - git_str_clear(buf); - - if (!strings || !count) - return 0; - - /* initialize common prefix to first string */ - if (git_str_sets(buf, strings[0]) < 0) - return -1; - - /* go through the rest of the strings, truncating to shared prefix */ - for (i = 1; i < count; ++i) { - - for (str = strings[i], pfx = buf->ptr; - *str && *str == *pfx; - str++, pfx++) - /* scanning */; - - git_str_truncate(buf, pfx - buf->ptr); - - if (!buf->size) - break; - } - - return 0; -} - -int git_str_is_binary(const git_str *buf) -{ - const char *scan = buf->ptr, *end = buf->ptr + buf->size; - git_str_bom_t bom; - int printable = 0, nonprintable = 0; - - scan += git_str_detect_bom(&bom, buf); - - if (bom > GIT_STR_BOM_UTF8) - return 1; - - while (scan < end) { - unsigned char c = *scan++; - - /* Printable characters are those above SPACE (0x1F) excluding DEL, - * and including BS, ESC and FF. - */ - if ((c > 0x1F && c != 127) || c == '\b' || c == '\033' || c == '\014') - printable++; - else if (c == '\0') - return true; - else if (!git__isspace(c)) - nonprintable++; - } - - return ((printable >> 7) < nonprintable); -} - -int git_str_contains_nul(const git_str *buf) -{ - return (memchr(buf->ptr, '\0', buf->size) != NULL); -} - -int git_str_detect_bom(git_str_bom_t *bom, const git_str *buf) -{ - const char *ptr; - size_t len; - - *bom = GIT_STR_BOM_NONE; - /* need at least 2 bytes to look for any BOM */ - if (buf->size < 2) - return 0; - - ptr = buf->ptr; - len = buf->size; - - switch (*ptr++) { - case 0: - if (len >= 4 && ptr[0] == 0 && ptr[1] == '\xFE' && ptr[2] == '\xFF') { - *bom = GIT_STR_BOM_UTF32_BE; - return 4; - } - break; - case '\xEF': - if (len >= 3 && ptr[0] == '\xBB' && ptr[1] == '\xBF') { - *bom = GIT_STR_BOM_UTF8; - return 3; - } - break; - case '\xFE': - if (*ptr == '\xFF') { - *bom = GIT_STR_BOM_UTF16_BE; - return 2; - } - break; - case '\xFF': - if (*ptr != '\xFE') - break; - if (len >= 4 && ptr[1] == 0 && ptr[2] == 0) { - *bom = GIT_STR_BOM_UTF32_LE; - return 4; - } else { - *bom = GIT_STR_BOM_UTF16_LE; - return 2; - } - break; - default: - break; - } - - return 0; -} - -bool git_str_gather_text_stats( - git_str_text_stats *stats, const git_str *buf, bool skip_bom) -{ - const char *scan = buf->ptr, *end = buf->ptr + buf->size; - int skip; - - memset(stats, 0, sizeof(*stats)); - - /* BOM detection */ - skip = git_str_detect_bom(&stats->bom, buf); - if (skip_bom) - scan += skip; - - /* Ignore EOF character */ - if (buf->size > 0 && end[-1] == '\032') - end--; - - /* Counting loop */ - while (scan < end) { - unsigned char c = *scan++; - - if (c > 0x1F && c != 0x7F) - stats->printable++; - else switch (c) { - case '\0': - stats->nul++; - stats->nonprintable++; - break; - case '\n': - stats->lf++; - break; - case '\r': - stats->cr++; - if (scan < end && *scan == '\n') - stats->crlf++; - break; - case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/ - stats->printable++; - break; - default: - stats->nonprintable++; - break; - } - } - - /* Treat files with a bare CR as binary */ - return (stats->cr != stats->crlf || stats->nul > 0 || - ((stats->printable >> 7) < stats->nonprintable)); -} diff --git a/src/libgit2/str.h b/src/libgit2/str.h deleted file mode 100644 index ef769ce2f..000000000 --- a/src/libgit2/str.h +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_str_h__ -#define INCLUDE_str_h__ - -#include "common.h" - -struct git_str { - char *ptr; - size_t asize; - size_t size; -}; - -typedef enum { - GIT_STR_BOM_NONE = 0, - GIT_STR_BOM_UTF8 = 1, - GIT_STR_BOM_UTF16_LE = 2, - GIT_STR_BOM_UTF16_BE = 3, - GIT_STR_BOM_UTF32_LE = 4, - GIT_STR_BOM_UTF32_BE = 5 -} git_str_bom_t; - -typedef struct { - git_str_bom_t bom; /* BOM found at head of text */ - unsigned int nul, cr, lf, crlf; /* NUL, CR, LF and CRLF counts */ - unsigned int printable, nonprintable; /* These are just approximations! */ -} git_str_text_stats; - -extern char git_str__initstr[]; -extern char git_str__oom[]; - -/* Use to initialize string buffer structure when git_str is on stack */ -#define GIT_STR_INIT { git_str__initstr, 0, 0 } - -/** - * Static initializer for git_str from static string buffer - */ -#define GIT_STR_INIT_CONST(str, len) { (char *)(str), 0, (size_t)(len) } - -GIT_INLINE(bool) git_str_is_allocated(const git_str *str) -{ - return (str->ptr != NULL && str->asize > 0); -} - -/** - * Initialize a git_str structure. - * - * For the cases where GIT_STR_INIT cannot be used to do static - * initialization. - */ -extern int git_str_init(git_str *str, size_t initial_size); - -extern void git_str_dispose(git_str *str); - -/** - * Resize the string buffer allocation to make more space. - * - * This will attempt to grow the string buffer to accommodate the target - * size. The bstring buffer's `ptr` will be replaced with a newly - * allocated block of data. Be careful so that memory allocated by the - * caller is not lost. As a special variant, if you pass `target_size` as - * 0 and the memory is not allocated by libgit2, this will allocate a new - * buffer of size `size` and copy the external data into it. - * - * Currently, this will never shrink a buffer, only expand it. - * - * If the allocation fails, this will return an error and the buffer will be - * marked as invalid for future operations, invaliding the contents. - * - * @param str The buffer to be resized; may or may not be allocated yet - * @param target_size The desired available size - * @return 0 on success, -1 on allocation failure - */ -int git_str_grow(git_str *str, size_t target_size); - -/** - * Resize the buffer allocation to make more space. - * - * This will attempt to grow the string buffer to accommodate the - * additional size. It is similar to `git_str_grow`, but performs the - * new size calculation, checking for overflow. - * - * Like `git_str_grow`, if this is a user-supplied string buffer, - * this will allocate a new string uffer. - */ -extern int git_str_grow_by(git_str *str, size_t additional_size); - -/** - * Attempt to grow the buffer to hold at least `target_size` bytes. - * - * If the allocation fails, this will return an error. If `mark_oom` is - * true, this will mark the string buffer as invalid for future - * operations; if false, existing string buffer content will be preserved, - * but calling code must handle that string buffer was not expanded. If - * `preserve_external` is true, then any existing data pointed to be - * `ptr` even if `asize` is zero will be copied into the newly allocated - * string buffer. - */ -extern int git_str_try_grow( - git_str *str, size_t target_size, bool mark_oom); - -extern void git_str_swap(git_str *str_a, git_str *str_b); -extern char *git_str_detach(git_str *str); -extern int git_str_attach(git_str *str, char *ptr, size_t asize); - -/* Populates a `git_str` where the contents are not "owned" by the string - * buffer, and calls to `git_str_dispose` will not free the given str. - */ -extern void git_str_attach_notowned( - git_str *str, const char *ptr, size_t size); - -/** - * Test if there have been any reallocation failures with this git_str. - * - * Any function that writes to a git_str can fail due to memory allocation - * issues. If one fails, the git_str will be marked with an OOM error and - * further calls to modify the string buffer will fail. Check - * git_str_oom() at the end of your sequence and it will be true if you - * ran out of memory at any point with that string buffer. - * - * @return false if no error, true if allocation error - */ -GIT_INLINE(bool) git_str_oom(const git_str *str) -{ - return (str->ptr == git_str__oom); -} - -/* - * Functions below that return int value error codes will return 0 on - * success or -1 on failure (which generally means an allocation failed). - * Using a git_str where the allocation has failed with result in -1 from - * all further calls using that string buffer. As a result, you can - * ignore the return code of these functions and call them in a series - * then just call git_str_oom at the end. - */ - -int git_str_set(git_str *str, const void *data, size_t datalen); - -int git_str_sets(git_str *str, const char *string); -int git_str_putc(git_str *str, char c); -int git_str_putcn(git_str *str, char c, size_t len); -int git_str_put(git_str *str, const char *data, size_t len); -int git_str_puts(git_str *str, const char *string); -int git_str_printf(git_str *str, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); -int git_str_vprintf(git_str *str, const char *format, va_list ap); -void git_str_clear(git_str *str); -void git_str_consume_bytes(git_str *str, size_t len); -void git_str_consume(git_str *str, const char *end); -void git_str_truncate(git_str *str, size_t len); -void git_str_shorten(git_str *str, size_t amount); -void git_str_truncate_at_char(git_str *path, char separator); -void git_str_rtruncate_at_char(git_str *path, char separator); - -/** General join with separator */ -int git_str_join_n(git_str *str, char separator, int len, ...); -/** Fast join of two strings - first may legally point into `str` data */ -int git_str_join(git_str *str, char separator, const char *str_a, const char *str_b); -/** Fast join of three strings - cannot reference `str` data */ -int git_str_join3(git_str *str, char separator, const char *str_a, const char *str_b, const char *str_c); - -/** - * Join two strings as paths, inserting a slash between as needed. - * @return 0 on success, -1 on failure - */ -GIT_INLINE(int) git_str_joinpath(git_str *str, const char *a, const char *b) -{ - return git_str_join(str, '/', a, b); -} - -GIT_INLINE(const char *) git_str_cstr(const git_str *str) -{ - return str->ptr; -} - -GIT_INLINE(size_t) git_str_len(const git_str *str) -{ - return str->size; -} - -int git_str_copy_cstr(char *data, size_t datasize, const git_str *str); - -#define git_str_PUTS(str, cstr) git_str_put(str, cstr, sizeof(cstr) - 1) - -GIT_INLINE(ssize_t) git_str_rfind_next(const git_str *str, char ch) -{ - ssize_t idx = (ssize_t)str->size - 1; - while (idx >= 0 && str->ptr[idx] == ch) idx--; - while (idx >= 0 && str->ptr[idx] != ch) idx--; - return idx; -} - -GIT_INLINE(ssize_t) git_str_rfind(const git_str *str, char ch) -{ - ssize_t idx = (ssize_t)str->size - 1; - while (idx >= 0 && str->ptr[idx] != ch) idx--; - return idx; -} - -GIT_INLINE(ssize_t) git_str_find(const git_str *str, char ch) -{ - void *found = memchr(str->ptr, ch, str->size); - return found ? (ssize_t)((const char *)found - str->ptr) : -1; -} - -/* Remove whitespace from the end of the string buffer */ -void git_str_rtrim(git_str *str); - -int git_str_cmp(const git_str *a, const git_str *b); - -/* Quote and unquote a string buffer as specified in - * http://marc.info/?l=git&m=112927316408690&w=2 - */ -int git_str_quote(git_str *str); -int git_str_unquote(git_str *str); - -/* Write data as a hex string */ -int git_str_encode_hexstr(git_str *str, const char *data, size_t len); - -/* Write data as base64 encoded in string buffer */ -int git_str_encode_base64(git_str *str, const char *data, size_t len); -/* Decode the given bas64 and write the result to the string buffer */ -int git_str_decode_base64(git_str *str, const char *base64, size_t len); - -/* Write data as "base85" encoded in string buffer */ -int git_str_encode_base85(git_str *str, const char *data, size_t len); -/* Decode the given "base85" and write the result to the string buffer */ -int git_str_decode_base85(git_str *str, const char *base64, size_t len, size_t output_len); - -/* - * Decode the given percent-encoded string and write the result to the - * string buffer. - */ -int git_str_decode_percent(git_str *str, const char *encoded, size_t len); - -/* - * Insert, remove or replace a portion of the string buffer. - * - * @param str The string buffer to work with - * - * @param where The location in the string buffer where the transformation - * should be applied. - * - * @param nb_to_remove The number of chars to be removed. 0 to not - * remove any character in the string buffer. - * - * @param data A pointer to the data which should be inserted. - * - * @param nb_to_insert The number of chars to be inserted. 0 to not - * insert any character from the string buffer. - * - * @return 0 or an error code. - */ -int git_str_splice( - git_str *str, - size_t where, - size_t nb_to_remove, - const char *data, - size_t nb_to_insert); - -/** - * Append string to string buffer, prefixing each character from - * `esc_chars` with `esc_with` string. - * - * @param str String buffer to append data to - * @param string String to escape and append - * @param esc_chars Characters to be escaped - * @param esc_with String to insert in from of each found character - * @return 0 on success, <0 on failure (probably allocation problem) - */ -extern int git_str_puts_escaped( - git_str *str, - const char *string, - const char *esc_chars, - const char *esc_with); - -/** - * Append string escaping characters that are regex special - */ -GIT_INLINE(int) git_str_puts_escape_regex(git_str *str, const char *string) -{ - return git_str_puts_escaped(str, string, "^.[]$()|*+?{}\\", "\\"); -} - -/** - * Unescape all characters in a string buffer in place - * - * I.e. remove backslashes - */ -extern void git_str_unescape(git_str *str); - -/** - * Replace all \r\n with \n. - * - * @return 0 on success, -1 on memory error - */ -extern int git_str_crlf_to_lf(git_str *tgt, const git_str *src); - -/** - * Replace all \n with \r\n. Does not modify existing \r\n. - * - * @return 0 on success, -1 on memory error - */ -extern int git_str_lf_to_crlf(git_str *tgt, const git_str *src); - -/** - * Fill string buffer with the common prefix of a array of strings - * - * String buffer will be set to empty if there is no common prefix - */ -extern int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count); - -/** - * Check if a string buffer begins with a UTF BOM - * - * @param bom Set to the type of BOM detected or GIT_BOM_NONE - * @param str String buffer in which to check the first bytes for a BOM - * @return Number of bytes of BOM data (or 0 if no BOM found) - */ -extern int git_str_detect_bom(git_str_bom_t *bom, const git_str *str); - -/** - * Gather stats for a piece of text - * - * Fill the `stats` structure with counts of unreadable characters, carriage - * returns, etc, so it can be used in heuristics. This automatically skips - * a trailing EOF (\032 character). Also it will look for a BOM at the - * start of the text and can be told to skip that as well. - * - * @param stats Structure to be filled in - * @param str Text to process - * @param skip_bom Exclude leading BOM from stats if true - * @return Does the string buffer heuristically look like binary data - */ -extern bool git_str_gather_text_stats( - git_str_text_stats *stats, const git_str *str, bool skip_bom); - -/** -* Check quickly if string buffer looks like it contains binary data -* -* @param str string buffer to check -* @return 1 if string buffer looks like non-text data -*/ -int git_str_is_binary(const git_str *str); - -/** -* Check quickly if buffer contains a NUL byte -* -* @param str string buffer to check -* @return 1 if string buffer contains a NUL byte -*/ -int git_str_contains_nul(const git_str *str); - -#endif diff --git a/src/libgit2/strmap.c b/src/libgit2/strmap.c deleted file mode 100644 index c6e5b6dc7..000000000 --- a/src/libgit2/strmap.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "strmap.h" - -#define kmalloc git__malloc -#define kcalloc git__calloc -#define krealloc git__realloc -#define kreallocarray git__reallocarray -#define kfree git__free -#include "khash.h" - -__KHASH_TYPE(str, const char *, void *) - -__KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) - -int git_strmap_new(git_strmap **out) -{ - *out = kh_init(str); - GIT_ERROR_CHECK_ALLOC(*out); - - return 0; -} - -void git_strmap_free(git_strmap *map) -{ - kh_destroy(str, map); -} - -void git_strmap_clear(git_strmap *map) -{ - kh_clear(str, map); -} - -size_t git_strmap_size(git_strmap *map) -{ - return kh_size(map); -} - -void *git_strmap_get(git_strmap *map, const char *key) -{ - size_t idx = kh_get(str, map, key); - if (idx == kh_end(map) || !kh_exist(map, idx)) - return NULL; - return kh_val(map, idx); -} - -int git_strmap_set(git_strmap *map, const char *key, void *value) -{ - size_t idx; - int rval; - - idx = kh_put(str, map, key, &rval); - if (rval < 0) - return -1; - - if (rval == 0) - kh_key(map, idx) = key; - - kh_val(map, idx) = value; - - return 0; -} - -int git_strmap_delete(git_strmap *map, const char *key) -{ - khiter_t idx = kh_get(str, map, key); - if (idx == kh_end(map)) - return GIT_ENOTFOUND; - kh_del(str, map, idx); - return 0; -} - -int git_strmap_exists(git_strmap *map, const char *key) -{ - return kh_get(str, map, key) != kh_end(map); -} - -int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key) -{ - size_t i = *iter; - - while (i < map->n_buckets && !kh_exist(map, i)) - i++; - - if (i >= map->n_buckets) - return GIT_ITEROVER; - - if (key) - *key = kh_key(map, i); - if (value) - *value = kh_val(map, i); - *iter = ++i; - - return 0; -} diff --git a/src/libgit2/strmap.h b/src/libgit2/strmap.h deleted file mode 100644 index 9f5e4cc8b..000000000 --- a/src/libgit2/strmap.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_strmap_h__ -#define INCLUDE_strmap_h__ - -#include "common.h" - -/** A map with C strings as key. */ -typedef struct kh_str_s git_strmap; - -/** - * Allocate a new string map. - * - * @param out Pointer to the map that shall be allocated. - * @return 0 on success, an error code if allocation has failed. - */ -int git_strmap_new(git_strmap **out); - -/** - * Free memory associated with the map. - * - * Note that this function will _not_ free keys or values added - * to this map. - * - * @param map Pointer to the map that is to be free'd. May be - * `NULL`. - */ -void git_strmap_free(git_strmap *map); - -/** - * Clear all entries from the map. - * - * This function will remove all entries from the associated map. - * Memory associated with it will not be released, though. - * - * @param map Pointer to the map that shall be cleared. May be - * `NULL`. - */ -void git_strmap_clear(git_strmap *map); - -/** - * Return the number of elements in the map. - * - * @parameter map map containing the elements - * @return number of elements in the map - */ -size_t git_strmap_size(git_strmap *map); - -/** - * Return value associated with the given key. - * - * @param map map to search key in - * @param key key to search for - * @return value associated with the given key or NULL if the key was not found - */ -void *git_strmap_get(git_strmap *map, const char *key); - -/** - * Set the entry for key to value. - * - * If the map has no corresponding entry for the given key, a new - * entry will be created with the given value. If an entry exists - * already, its value will be updated to match the given value. - * - * @param map map to create new entry in - * @param key key to set - * @param value value to associate the key with; may be NULL - * @return zero if the key was successfully set, a negative error - * code otherwise - */ -int git_strmap_set(git_strmap *map, const char *key, void *value); - -/** - * Delete an entry from the map. - * - * Delete the given key and its value from the map. If no such - * key exists, this will do nothing. - * - * @param map map to delete key in - * @param key key to delete - * @return `0` if the key has been deleted, GIT_ENOTFOUND if no - * such key was found, a negative code in case of an - * error - */ -int git_strmap_delete(git_strmap *map, const char *key); - -/** - * Check whether a key exists in the given map. - * - * @param map map to query for the key - * @param key key to search for - * @return 0 if the key has not been found, 1 otherwise - */ -int git_strmap_exists(git_strmap *map, const char *key); - -/** - * Iterate over entries of the map. - * - * This functions allows to iterate over all key-value entries of - * the map. The current position is stored in the `iter` variable - * and should be initialized to `0` before the first call to this - * function. - * - * @param map map to iterate over - * @param value pointer to the variable where to store the current - * value. May be NULL. - * @param iter iterator storing the current position. Initialize - * with zero previous to the first call. - * @param key pointer to the variable where to store the current - * key. May be NULL. - * @return `0` if the next entry was correctly retrieved. - * GIT_ITEROVER if no entries are left. A negative error - * code otherwise. - */ -int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key); - -#define git_strmap_foreach(h, kvar, vvar, code) { size_t __i = 0; \ - while (git_strmap_iterate((void **) &(vvar), h, &__i, &(kvar)) == 0) { \ - code; \ - } } - -#define git_strmap_foreach_value(h, vvar, code) { size_t __i = 0; \ - while (git_strmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ - code; \ - } } - -#endif diff --git a/src/libgit2/strnlen.h b/src/libgit2/strnlen.h deleted file mode 100644 index eecfe3c02..000000000 --- a/src/libgit2/strnlen.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_strlen_h__ -#define INCLUDE_strlen_h__ - -#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) || defined(__MidnightBSD__) ||\ - (defined(_MSC_VER) && _MSC_VER < 1500) -# define NO_STRNLEN -#endif - -#ifdef NO_STRNLEN -GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) { - const char *end = memchr(s, 0, maxlen); - return end ? (size_t)(end - s) : maxlen; -} -#else -# define p_strnlen strnlen -#endif - -#endif diff --git a/src/libgit2/thread.c b/src/libgit2/thread.c deleted file mode 100644 index 3171771d7..000000000 --- a/src/libgit2/thread.c +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#if !defined(GIT_THREADS) - -#define TLSDATA_MAX 16 - -typedef struct { - void *value; - void (GIT_SYSTEM_CALL *destroy_fn)(void *); -} tlsdata_value; - -static tlsdata_value tlsdata_values[TLSDATA_MAX]; -static int tlsdata_cnt = 0; - -int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) -{ - if (tlsdata_cnt >= TLSDATA_MAX) - return -1; - - tlsdata_values[tlsdata_cnt].value = NULL; - tlsdata_values[tlsdata_cnt].destroy_fn = destroy_fn; - - *key = tlsdata_cnt; - tlsdata_cnt++; - - return 0; -} - -int git_tlsdata_set(git_tlsdata_key key, void *value) -{ - if (key < 0 || key > tlsdata_cnt) - return -1; - - tlsdata_values[key].value = value; - return 0; -} - -void *git_tlsdata_get(git_tlsdata_key key) -{ - if (key < 0 || key > tlsdata_cnt) - return NULL; - - return tlsdata_values[key].value; -} - -int git_tlsdata_dispose(git_tlsdata_key key) -{ - void *value; - void (*destroy_fn)(void *) = NULL; - - if (key < 0 || key > tlsdata_cnt) - return -1; - - value = tlsdata_values[key].value; - destroy_fn = tlsdata_values[key].destroy_fn; - - tlsdata_values[key].value = NULL; - tlsdata_values[key].destroy_fn = NULL; - - if (value && destroy_fn) - destroy_fn(value); - - return 0; -} - -#elif defined(GIT_WIN32) - -int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) -{ - DWORD fls_index = FlsAlloc(destroy_fn); - - if (fls_index == FLS_OUT_OF_INDEXES) - return -1; - - *key = fls_index; - return 0; -} - -int git_tlsdata_set(git_tlsdata_key key, void *value) -{ - if (!FlsSetValue(key, value)) - return -1; - - return 0; -} - -void *git_tlsdata_get(git_tlsdata_key key) -{ - return FlsGetValue(key); -} - -int git_tlsdata_dispose(git_tlsdata_key key) -{ - if (!FlsFree(key)) - return -1; - - return 0; -} - -#elif defined(_POSIX_THREADS) - -int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) -{ - if (pthread_key_create(key, destroy_fn) != 0) - return -1; - - return 0; -} - -int git_tlsdata_set(git_tlsdata_key key, void *value) -{ - if (pthread_setspecific(key, value) != 0) - return -1; - - return 0; -} - -void *git_tlsdata_get(git_tlsdata_key key) -{ - return pthread_getspecific(key); -} - -int git_tlsdata_dispose(git_tlsdata_key key) -{ - if (pthread_key_delete(key) != 0) - return -1; - - return 0; -} - -#else -# error unknown threading model -#endif diff --git a/src/libgit2/thread.h b/src/libgit2/thread.h deleted file mode 100644 index 4bbac9fd8..000000000 --- a/src/libgit2/thread.h +++ /dev/null @@ -1,479 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_thread_h__ -#define INCLUDE_thread_h__ - -#if defined(GIT_THREADS) - -#if defined(__clang__) - -# if (__clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ < 1)) -# error Atomic primitives do not exist on this version of clang; configure libgit2 with -DUSE_THREADS=OFF -# else -# define GIT_BUILTIN_ATOMIC -# endif - -#elif defined(__GNUC__) - -# if (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 1)) -# error Atomic primitives do not exist on this version of gcc; configure libgit2 with -DUSE_THREADS=OFF -# elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) -# define GIT_BUILTIN_ATOMIC -# else -# define GIT_BUILTIN_SYNC -# endif - -#endif - -#endif /* GIT_THREADS */ - -/* Common operations even if threading has been disabled */ -typedef struct { -#if defined(GIT_WIN32) - volatile long val; -#else - volatile int val; -#endif -} git_atomic32; - -#ifdef GIT_ARCH_64 - -typedef struct { -#if defined(GIT_WIN32) - volatile __int64 val; -#else - volatile int64_t val; -#endif -} git_atomic64; - -typedef git_atomic64 git_atomic_ssize; - -#define git_atomic_ssize_set git_atomic64_set -#define git_atomic_ssize_add git_atomic64_add -#define git_atomic_ssize_get git_atomic64_get - -#else - -typedef git_atomic32 git_atomic_ssize; - -#define git_atomic_ssize_set git_atomic32_set -#define git_atomic_ssize_add git_atomic32_add -#define git_atomic_ssize_get git_atomic32_get - -#endif - -#ifdef GIT_THREADS - -#ifdef GIT_WIN32 -# include "win32/thread.h" -#else -# include "unix/pthread.h" -#endif - -/* - * Atomically sets the contents of *a to be val. - */ -GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) -{ -#if defined(GIT_WIN32) - InterlockedExchange(&a->val, (LONG)val); -#elif defined(GIT_BUILTIN_ATOMIC) - __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - __sync_lock_test_and_set(&a->val, val); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically increments the contents of *a by 1, and stores the result back into *a. - * @return the result of the operation. - */ -GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) -{ -#if defined(GIT_WIN32) - return InterlockedIncrement(&a->val); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_add_fetch(&a->val, 1, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_add_and_fetch(&a->val, 1); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically adds the contents of *a and addend, and stores the result back into *a. - * @return the result of the operation. - */ -GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) -{ -#if defined(GIT_WIN32) - return InterlockedAdd(&a->val, addend); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_add_and_fetch(&a->val, addend); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically decrements the contents of *a by 1, and stores the result back into *a. - * @return the result of the operation. - */ -GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) -{ -#if defined(GIT_WIN32) - return InterlockedDecrement(&a->val); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_sub_fetch(&a->val, 1, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_sub_and_fetch(&a->val, 1); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically gets the contents of *a. - * @return the contents of *a. - */ -GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) -{ -#if defined(GIT_WIN32) - return (int)InterlockedCompareExchange(&a->val, 0, 0); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_val_compare_and_swap(&a->val, 0, 0); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -GIT_INLINE(void *) git_atomic__compare_and_swap( - void * volatile *ptr, void *oldval, void *newval) -{ -#if defined(GIT_WIN32) - return InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); -#elif defined(GIT_BUILTIN_ATOMIC) - void *foundval = oldval; - __atomic_compare_exchange(ptr, &foundval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); - return foundval; -#elif defined(GIT_BUILTIN_SYNC) - return __sync_val_compare_and_swap(ptr, oldval, newval); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -GIT_INLINE(volatile void *) git_atomic__swap( - void * volatile *ptr, void *newval) -{ -#if defined(GIT_WIN32) - return InterlockedExchangePointer(ptr, newval); -#elif defined(GIT_BUILTIN_ATOMIC) - void * foundval = NULL; - __atomic_exchange(ptr, &newval, &foundval, __ATOMIC_SEQ_CST); - return foundval; -#elif defined(GIT_BUILTIN_SYNC) - return (volatile void *)__sync_lock_test_and_set(ptr, newval); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) -{ -#if defined(GIT_WIN32) - void *newval = NULL, *oldval = NULL; - return (volatile void *)InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); -#elif defined(GIT_BUILTIN_ATOMIC) - return (volatile void *)__atomic_load_n(ptr, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return (volatile void *)__sync_val_compare_and_swap(ptr, 0, 0); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -#ifdef GIT_ARCH_64 - -/* - * Atomically adds the contents of *a and addend, and stores the result back into *a. - * @return the result of the operation. - */ -GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) -{ -#if defined(GIT_WIN32) - return InterlockedAdd64(&a->val, addend); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_add_and_fetch(&a->val, addend); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically sets the contents of *a to be val. - */ -GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) -{ -#if defined(GIT_WIN32) - InterlockedExchange64(&a->val, val); -#elif defined(GIT_BUILTIN_ATOMIC) - __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - __sync_lock_test_and_set(&a->val, val); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -/* - * Atomically gets the contents of *a. - * @return the contents of *a. - */ -GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) -{ -#if defined(GIT_WIN32) - return (int64_t)InterlockedCompareExchange64(&a->val, 0, 0); -#elif defined(GIT_BUILTIN_ATOMIC) - return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST); -#elif defined(GIT_BUILTIN_SYNC) - return __sync_val_compare_and_swap(&a->val, 0, 0); -#else -# error "Unsupported architecture for atomic operations" -#endif -} - -#endif - -#else - -#define git_threads_global_init git__noop - -#define git_thread unsigned int -#define git_thread_create(thread, start_routine, arg) git__noop() -#define git_thread_join(id, status) git__noop() - -/* Pthreads Mutex */ -#define git_mutex unsigned int -#define git_mutex_init(a) git__noop() -#define git_mutex_init(a) git__noop() -#define git_mutex_lock(a) git__noop() -#define git_mutex_unlock(a) git__noop() -#define git_mutex_free(a) git__noop() - -/* Pthreads condition vars */ -#define git_cond unsigned int -#define git_cond_init(c) git__noop() -#define git_cond_free(c) git__noop() -#define git_cond_wait(c, l) git__noop() -#define git_cond_signal(c) git__noop() -#define git_cond_broadcast(c) git__noop() - -/* Pthreads rwlock */ -#define git_rwlock unsigned int -#define git_rwlock_init(a) git__noop() -#define git_rwlock_rdlock(a) git__noop() -#define git_rwlock_rdunlock(a) git__noop() -#define git_rwlock_wrlock(a) git__noop() -#define git_rwlock_wrunlock(a) git__noop() -#define git_rwlock_free(a) git__noop() -#define GIT_RWLOCK_STATIC_INIT 0 - - -GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) -{ - a->val = val; -} - -GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) -{ - return ++a->val; -} - -GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) -{ - a->val += addend; - return a->val; -} - -GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) -{ - return --a->val; -} - -GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) -{ - return (int)a->val; -} - -GIT_INLINE(void *) git_atomic__compare_and_swap( - void * volatile *ptr, void *oldval, void *newval) -{ - void *foundval = *ptr; - if (foundval == oldval) - *ptr = newval; - return foundval; -} - -GIT_INLINE(volatile void *) git_atomic__swap( - void * volatile *ptr, void *newval) -{ - volatile void *old = *ptr; - *ptr = newval; - return old; -} - -GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) -{ - return *ptr; -} - -#ifdef GIT_ARCH_64 - -GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) -{ - a->val += addend; - return a->val; -} - -GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) -{ - a->val = val; -} - -GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) -{ - return (int64_t)a->val; -} - -#endif - -#endif - -/* - * Atomically replace the contents of *ptr (if they are equal to oldval) with - * newval. ptr must point to a pointer or a value that is the same size as a - * pointer. This is semantically compatible with: - * - * #define git_atomic_compare_and_swap(ptr, oldval, newval) \ - * ({ \ - * void *foundval = *ptr; \ - * if (foundval == oldval) \ - * *ptr = newval; \ - * foundval; \ - * }) - * - * @return the original contents of *ptr. - */ -#define git_atomic_compare_and_swap(ptr, oldval, newval) \ - git_atomic__compare_and_swap((void * volatile *)ptr, oldval, newval) - -/* - * Atomically replace the contents of v with newval. v must be the same size as - * a pointer. This is semantically compatible with: - * - * #define git_atomic_swap(v, newval) \ - * ({ \ - * volatile void *old = v; \ - * v = newval; \ - * old; \ - * }) - * - * @return the original contents of v. - */ -#define git_atomic_swap(v, newval) \ - (void *)git_atomic__swap((void * volatile *)&(v), newval) - -/* - * Atomically reads the contents of v. v must be the same size as a pointer. - * This is semantically compatible with: - * - * #define git_atomic_load(v) v - * - * @return the contents of v. - */ -#define git_atomic_load(v) \ - (void *)git_atomic__load((void * volatile *)&(v)) - -#if defined(GIT_THREADS) - -# if defined(GIT_WIN32) -# define GIT_MEMORY_BARRIER MemoryBarrier() -# elif defined(GIT_BUILTIN_ATOMIC) -# define GIT_MEMORY_BARRIER __atomic_thread_fence(__ATOMIC_SEQ_CST) -# elif defined(GIT_BUILTIN_SYNC) -# define GIT_MEMORY_BARRIER __sync_synchronize() -# endif - -#else - -# define GIT_MEMORY_BARRIER /* noop */ - -#endif - -/* Thread-local data */ - -#if !defined(GIT_THREADS) -# define git_tlsdata_key int -#elif defined(GIT_WIN32) -# define git_tlsdata_key DWORD -#elif defined(_POSIX_THREADS) -# define git_tlsdata_key pthread_key_t -#else -# error unknown threading model -#endif - -/** - * Create a thread-local data key. The destroy function will be - * called upon thread exit. On some platforms, it may be called - * when all threads have deleted their keys. - * - * Note that the tlsdata functions do not set an error message on - * failure; this is because the error handling in libgit2 is itself - * handled by thread-local data storage. - * - * @param key the tlsdata key - * @param destroy_fn function pointer called upon thread exit - * @return 0 on success, non-zero on failure - */ -int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)); - -/** - * Set a the thread-local value for the given key. - * - * @param key the tlsdata key to store data on - * @param value the pointer to store - * @return 0 on success, non-zero on failure - */ -int git_tlsdata_set(git_tlsdata_key key, void *value); - -/** - * Get the thread-local value for the given key. - * - * @param key the tlsdata key to retrieve the value of - * @return the pointer stored with git_tlsdata_set - */ -void *git_tlsdata_get(git_tlsdata_key key); - -/** - * Delete the given thread-local key. - * - * @param key the tlsdata key to dispose - * @return 0 on success, non-zero on failure - */ -int git_tlsdata_dispose(git_tlsdata_key key); - -#endif diff --git a/src/libgit2/tsort.c b/src/libgit2/tsort.c deleted file mode 100644 index 045efad23..000000000 --- a/src/libgit2/tsort.c +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -/** - * An array-of-pointers implementation of Python's Timsort - * Based on code by Christopher Swenson under the MIT license - * - * Copyright (c) 2010 Christopher Swenson - * Copyright (c) 2011 Vicent Marti - */ - -#ifndef MAX -# define MAX(x,y) (((x) > (y) ? (x) : (y))) -#endif - -#ifndef MIN -# define MIN(x,y) (((x) < (y) ? (x) : (y))) -#endif - -static int binsearch( - void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload) -{ - int l, c, r; - void *lx, *cx; - - l = 0; - r = (int)size - 1; - c = r >> 1; - lx = dst[l]; - - /* check for beginning conditions */ - if (cmp(x, lx, payload) < 0) - return 0; - - else if (cmp(x, lx, payload) == 0) { - int i = 1; - while (cmp(x, dst[i], payload) == 0) - i++; - return i; - } - - /* guaranteed not to be >= rx */ - cx = dst[c]; - while (1) { - const int val = cmp(x, cx, payload); - if (val < 0) { - if (c - l <= 1) return c; - r = c; - } else if (val > 0) { - if (r - c <= 1) return c + 1; - l = c; - lx = cx; - } else { - do { - cx = dst[++c]; - } while (cmp(x, cx, payload) == 0); - return c; - } - c = l + ((r - l) >> 1); - cx = dst[c]; - } -} - -/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */ -static void bisort( - void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload) -{ - size_t i; - void *x; - int location; - - for (i = start; i < size; i++) { - int j; - /* If this entry is already correct, just move along */ - if (cmp(dst[i - 1], dst[i], payload) <= 0) - continue; - - /* Else we need to find the right place, shift everything over, and squeeze in */ - x = dst[i]; - location = binsearch(dst, x, i, cmp, payload); - for (j = (int)i - 1; j >= location; j--) { - dst[j + 1] = dst[j]; - } - dst[location] = x; - } -} - - -/* timsort implementation, based on timsort.txt */ -struct tsort_run { - ssize_t start; - ssize_t length; -}; - -struct tsort_store { - size_t alloc; - git__sort_r_cmp cmp; - void *payload; - void **storage; -}; - -static void reverse_elements(void **dst, ssize_t start, ssize_t end) -{ - while (start < end) { - void *tmp = dst[start]; - dst[start] = dst[end]; - dst[end] = tmp; - - start++; - end--; - } -} - -static ssize_t count_run( - void **dst, ssize_t start, ssize_t size, struct tsort_store *store) -{ - ssize_t curr = start + 2; - - if (size - start == 1) - return 1; - - if (start >= size - 2) { - if (store->cmp(dst[size - 2], dst[size - 1], store->payload) > 0) { - void *tmp = dst[size - 1]; - dst[size - 1] = dst[size - 2]; - dst[size - 2] = tmp; - } - - return 2; - } - - if (store->cmp(dst[start], dst[start + 1], store->payload) <= 0) { - while (curr < size - 1 && - store->cmp(dst[curr - 1], dst[curr], store->payload) <= 0) - curr++; - - return curr - start; - } else { - while (curr < size - 1 && - store->cmp(dst[curr - 1], dst[curr], store->payload) > 0) - curr++; - - /* reverse in-place */ - reverse_elements(dst, start, curr - 1); - return curr - start; - } -} - -static size_t compute_minrun(size_t n) -{ - int r = 0; - while (n >= 64) { - r |= n & 1; - n >>= 1; - } - return n + r; -} - -static int check_invariant(struct tsort_run *stack, ssize_t stack_curr) -{ - if (stack_curr < 2) - return 1; - - else if (stack_curr == 2) { - const ssize_t A = stack[stack_curr - 2].length; - const ssize_t B = stack[stack_curr - 1].length; - return (A > B); - } else { - const ssize_t A = stack[stack_curr - 3].length; - const ssize_t B = stack[stack_curr - 2].length; - const ssize_t C = stack[stack_curr - 1].length; - return !((A <= B + C) || (B <= C)); - } -} - -static int resize(struct tsort_store *store, size_t new_size) -{ - if (store->alloc < new_size) { - void **tempstore; - - tempstore = git__reallocarray(store->storage, new_size, sizeof(void *)); - - /** - * Do not propagate on OOM; this will abort the sort and - * leave the array unsorted, but no error code will be - * raised - */ - if (tempstore == NULL) - return -1; - - store->storage = tempstore; - store->alloc = new_size; - } - - return 0; -} - -static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store) -{ - const ssize_t A = stack[stack_curr - 2].length; - const ssize_t B = stack[stack_curr - 1].length; - const ssize_t curr = stack[stack_curr - 2].start; - - void **storage; - ssize_t i, j, k; - - if (resize(store, MIN(A, B)) < 0) - return; - - storage = store->storage; - - /* left merge */ - if (A < B) { - memcpy(storage, &dst[curr], A * sizeof(void *)); - i = 0; - j = curr + A; - - for (k = curr; k < curr + A + B; k++) { - if ((i < A) && (j < curr + A + B)) { - if (store->cmp(storage[i], dst[j], store->payload) <= 0) - dst[k] = storage[i++]; - else - dst[k] = dst[j++]; - } else if (i < A) { - dst[k] = storage[i++]; - } else - dst[k] = dst[j++]; - } - } else { - memcpy(storage, &dst[curr + A], B * sizeof(void *)); - i = B - 1; - j = curr + A - 1; - - for (k = curr + A + B - 1; k >= curr; k--) { - if ((i >= 0) && (j >= curr)) { - if (store->cmp(dst[j], storage[i], store->payload) > 0) - dst[k] = dst[j--]; - else - dst[k] = storage[i--]; - } else if (i >= 0) - dst[k] = storage[i--]; - else - dst[k] = dst[j--]; - } - } -} - -static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store, ssize_t size) -{ - ssize_t A, B, C; - - while (1) { - /* if the stack only has one thing on it, we are done with the collapse */ - if (stack_curr <= 1) - break; - - /* if this is the last merge, just do it */ - if ((stack_curr == 2) && (stack[0].length + stack[1].length == size)) { - merge(dst, stack, stack_curr, store); - stack[0].length += stack[1].length; - stack_curr--; - break; - } - - /* check if the invariant is off for a stack of 2 elements */ - else if ((stack_curr == 2) && (stack[0].length <= stack[1].length)) { - merge(dst, stack, stack_curr, store); - stack[0].length += stack[1].length; - stack_curr--; - break; - } - else if (stack_curr == 2) - break; - - A = stack[stack_curr - 3].length; - B = stack[stack_curr - 2].length; - C = stack[stack_curr - 1].length; - - /* check first invariant */ - if (A <= B + C) { - if (A < C) { - merge(dst, stack, stack_curr - 1, store); - stack[stack_curr - 3].length += stack[stack_curr - 2].length; - stack[stack_curr - 2] = stack[stack_curr - 1]; - stack_curr--; - } else { - merge(dst, stack, stack_curr, store); - stack[stack_curr - 2].length += stack[stack_curr - 1].length; - stack_curr--; - } - } else if (B <= C) { - merge(dst, stack, stack_curr, store); - stack[stack_curr - 2].length += stack[stack_curr - 1].length; - stack_curr--; - } else - break; - } - - return stack_curr; -} - -#define PUSH_NEXT() do {\ - len = count_run(dst, curr, size, store);\ - run = minrun;\ - if (run > (ssize_t)size - curr) run = size - curr;\ - if (run > len) {\ - bisort(&dst[curr], len, run, cmp, payload);\ - len = run;\ - }\ - run_stack[stack_curr].start = curr;\ - run_stack[stack_curr++].length = len;\ - curr += len;\ - if (curr == (ssize_t)size) {\ - /* finish up */ \ - while (stack_curr > 1) { \ - merge(dst, run_stack, stack_curr, store); \ - run_stack[stack_curr - 2].length += run_stack[stack_curr - 1].length; \ - stack_curr--; \ - } \ - if (store->storage != NULL) {\ - git__free(store->storage);\ - store->storage = NULL;\ - }\ - return;\ - }\ -}\ -while (0) - -void git__tsort_r( - void **dst, size_t size, git__sort_r_cmp cmp, void *payload) -{ - struct tsort_store _store, *store = &_store; - struct tsort_run run_stack[128]; - - ssize_t stack_curr = 0; - ssize_t len, run; - ssize_t curr = 0; - ssize_t minrun; - - if (size < 64) { - bisort(dst, 1, size, cmp, payload); - return; - } - - /* compute the minimum run length */ - minrun = (ssize_t)compute_minrun(size); - - /* temporary storage for merges */ - store->alloc = 0; - store->storage = NULL; - store->cmp = cmp; - store->payload = payload; - - PUSH_NEXT(); - PUSH_NEXT(); - PUSH_NEXT(); - - while (1) { - if (!check_invariant(run_stack, stack_curr)) { - stack_curr = collapse(dst, run_stack, stack_curr, store, size); - continue; - } - - PUSH_NEXT(); - } -} - -static int tsort_r_cmp(const void *a, const void *b, void *payload) -{ - return ((git__tsort_cmp)payload)(a, b); -} - -void git__tsort(void **dst, size_t size, git__tsort_cmp cmp) -{ - git__tsort_r(dst, size, tsort_r_cmp, cmp); -} diff --git a/src/libgit2/unix/map.c b/src/libgit2/unix/map.c deleted file mode 100644 index 23fcb786e..000000000 --- a/src/libgit2/unix/map.c +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#if !defined(GIT_WIN32) && !defined(NO_MMAP) - -#include "map.h" -#include -#include -#include - -int git__page_size(size_t *page_size) -{ - long sc_page_size = sysconf(_SC_PAGE_SIZE); - if (sc_page_size < 0) { - git_error_set(GIT_ERROR_OS, "can't determine system page size"); - return -1; - } - *page_size = (size_t) sc_page_size; - return 0; -} - -int git__mmap_alignment(size_t *alignment) -{ - return git__page_size(alignment); -} - -int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) -{ - int mprot = PROT_READ; - int mflag = 0; - - GIT_MMAP_VALIDATE(out, len, prot, flags); - - out->data = NULL; - out->len = 0; - - if (prot & GIT_PROT_WRITE) - mprot |= PROT_WRITE; - - if ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) - mflag = MAP_SHARED; - else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE) - mflag = MAP_PRIVATE; - else - mflag = MAP_SHARED; - - out->data = mmap(NULL, len, mprot, mflag, fd, offset); - - if (!out->data || out->data == MAP_FAILED) { - git_error_set(GIT_ERROR_OS, "failed to mmap. Could not write data"); - return -1; - } - - out->len = len; - - return 0; -} - -int p_munmap(git_map *map) -{ - GIT_ASSERT_ARG(map); - munmap(map->data, map->len); - map->data = NULL; - map->len = 0; - - return 0; -} - -#endif - diff --git a/src/libgit2/unix/posix.h b/src/libgit2/unix/posix.h deleted file mode 100644 index 49065e533..000000000 --- a/src/libgit2/unix/posix.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_unix_posix_h__ -#define INCLUDE_unix_posix_h__ - -#include "common.h" - -#include -#include -#include -#include -#include - -typedef int GIT_SOCKET; -#define INVALID_SOCKET -1 - -#define p_lseek(f,n,w) lseek(f, n, w) -#define p_fstat(f,b) fstat(f, b) -#define p_lstat(p,b) lstat(p,b) -#define p_stat(p,b) stat(p, b) - -#if defined(GIT_USE_STAT_MTIMESPEC) -# define st_atime_nsec st_atimespec.tv_nsec -# define st_mtime_nsec st_mtimespec.tv_nsec -# define st_ctime_nsec st_ctimespec.tv_nsec -#elif defined(GIT_USE_STAT_MTIM) -# define st_atime_nsec st_atim.tv_nsec -# define st_mtime_nsec st_mtim.tv_nsec -# define st_ctime_nsec st_ctim.tv_nsec -#elif !defined(GIT_USE_STAT_MTIME_NSEC) && defined(GIT_USE_NSEC) -# error GIT_USE_NSEC defined but unknown struct stat nanosecond type -#endif - -#define p_utimes(f, t) utimes(f, t) - -#define p_readlink(a, b, c) readlink(a, b, c) -#define p_symlink(o,n) symlink(o, n) -#define p_link(o,n) link(o, n) -#define p_unlink(p) unlink(p) -#define p_mkdir(p,m) mkdir(p, m) -extern char *p_realpath(const char *, char *); - -GIT_INLINE(int) p_fsync(int fd) -{ - p_fsync__cnt++; - return fsync(fd); -} - -#define p_recv(s,b,l,f) recv(s,b,l,f) -#define p_send(s,b,l,f) send(s,b,l,f) -#define p_inet_pton(a, b, c) inet_pton(a, b, c) - -#define p_strcasecmp(s1, s2) strcasecmp(s1, s2) -#define p_strncasecmp(s1, s2, c) strncasecmp(s1, s2, c) -#define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a) -#define p_snprintf snprintf -#define p_chdir(p) chdir(p) -#define p_rmdir(p) rmdir(p) -#define p_access(p,m) access(p,m) -#define p_ftruncate(fd, sz) ftruncate(fd, sz) - -/* - * Pre-Android 5 did not implement a virtual filesystem atop FAT - * partitions for Unix permissions, which causes chmod to fail. However, - * Unix permissions have no effect on Android anyway as file permissions - * are not actually managed this way, so treating it as a no-op across - * all Android is safe. - */ -#ifdef __ANDROID__ -# define p_chmod(p,m) 0 -#else -# define p_chmod(p,m) chmod(p, m) -#endif - -/* see win32/posix.h for explanation about why this exists */ -#define p_lstat_posixly(p,b) lstat(p,b) - -#define p_localtime_r(c, r) localtime_r(c, r) -#define p_gmtime_r(c, r) gmtime_r(c, r) - -#define p_timeval timeval - -#ifdef GIT_USE_FUTIMENS -GIT_INLINE(int) p_futimes(int f, const struct p_timeval t[2]) -{ - struct timespec s[2]; - s[0].tv_sec = t[0].tv_sec; - s[0].tv_nsec = t[0].tv_usec * 1000; - s[1].tv_sec = t[1].tv_sec; - s[1].tv_nsec = t[1].tv_usec * 1000; - return futimens(f, s); -} -#else -# define p_futimes futimes -#endif - -#define p_pread(f, d, s, o) pread(f, d, s, o) -#define p_pwrite(f, d, s, o) pwrite(f, d, s, o) - -#endif diff --git a/src/libgit2/unix/pthread.h b/src/libgit2/unix/pthread.h deleted file mode 100644 index 55f4ae227..000000000 --- a/src/libgit2/unix/pthread.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_unix_pthread_h__ -#define INCLUDE_unix_pthread_h__ - -typedef struct { - pthread_t thread; -} git_thread; - -GIT_INLINE(int) git_threads_global_init(void) { return 0; } - -#define git_thread_create(git_thread_ptr, start_routine, arg) \ - pthread_create(&(git_thread_ptr)->thread, NULL, start_routine, arg) -#define git_thread_join(git_thread_ptr, status) \ - pthread_join((git_thread_ptr)->thread, status) -#define git_thread_currentid() ((size_t)(pthread_self())) -#define git_thread_exit(retval) pthread_exit(retval) - -/* Git Mutex */ -#define git_mutex pthread_mutex_t -#define git_mutex_init(a) pthread_mutex_init(a, NULL) -#define git_mutex_lock(a) pthread_mutex_lock(a) -#define git_mutex_unlock(a) pthread_mutex_unlock(a) -#define git_mutex_free(a) pthread_mutex_destroy(a) - -/* Git condition vars */ -#define git_cond pthread_cond_t -#define git_cond_init(c) pthread_cond_init(c, NULL) -#define git_cond_free(c) pthread_cond_destroy(c) -#define git_cond_wait(c, l) pthread_cond_wait(c, l) -#define git_cond_signal(c) pthread_cond_signal(c) -#define git_cond_broadcast(c) pthread_cond_broadcast(c) - -/* Pthread (-ish) rwlock - * - * This differs from normal pthreads rwlocks in two ways: - * 1. Separate APIs for releasing read locks and write locks (as - * opposed to the pure POSIX API which only has one unlock fn) - * 2. You should not use recursive read locks (i.e. grabbing a read - * lock in a thread that already holds a read lock) because the - * Windows implementation doesn't support it - */ -#define git_rwlock pthread_rwlock_t -#define git_rwlock_init(a) pthread_rwlock_init(a, NULL) -#define git_rwlock_rdlock(a) pthread_rwlock_rdlock(a) -#define git_rwlock_rdunlock(a) pthread_rwlock_unlock(a) -#define git_rwlock_wrlock(a) pthread_rwlock_wrlock(a) -#define git_rwlock_wrunlock(a) pthread_rwlock_unlock(a) -#define git_rwlock_free(a) pthread_rwlock_destroy(a) -#define GIT_RWLOCK_STATIC_INIT PTHREAD_RWLOCK_INITIALIZER - -#endif diff --git a/src/libgit2/unix/realpath.c b/src/libgit2/unix/realpath.c deleted file mode 100644 index f1ca669f7..000000000 --- a/src/libgit2/unix/realpath.c +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#ifndef GIT_WIN32 - -#include -#include -#include -#include - -char *p_realpath(const char *pathname, char *resolved) -{ - char *ret; - if ((ret = realpath(pathname, resolved)) == NULL) - return NULL; - -#ifdef __OpenBSD__ - /* The OpenBSD realpath function behaves differently, - * figure out if the file exists */ - if (access(ret, F_OK) < 0) - ret = NULL; -#endif - return ret; -} - -#endif diff --git a/src/libgit2/utf8.c b/src/libgit2/utf8.c deleted file mode 100644 index 77065cb71..000000000 --- a/src/libgit2/utf8.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "utf8.h" - -#include "common.h" - -/* - * git_utf8_iterate is taken from the utf8proc project, - * http://www.public-software-group.org/utf8proc - * - * Copyright (c) 2009 Public Software Group e. V., Berlin, Germany - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the ""Software""), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -static const uint8_t utf8proc_utf8class[256] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -static int utf8_charlen(const uint8_t *str, size_t str_len) -{ - uint8_t length; - size_t i; - - length = utf8proc_utf8class[str[0]]; - if (!length) - return -1; - - if (str_len > 0 && length > str_len) - return -1; - - for (i = 1; i < length; i++) { - if ((str[i] & 0xC0) != 0x80) - return -1; - } - - return (int)length; -} - -int git_utf8_iterate(uint32_t *out, const char *_str, size_t str_len) -{ - const uint8_t *str = (const uint8_t *)_str; - uint32_t uc = 0; - int length; - - *out = 0; - - if ((length = utf8_charlen(str, str_len)) < 0) - return -1; - - switch (length) { - case 1: - uc = str[0]; - break; - case 2: - uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F); - if (uc < 0x80) uc = -1; - break; - case 3: - uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6) - + (str[2] & 0x3F); - if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000) || - (uc >= 0xFDD0 && uc < 0xFDF0)) uc = -1; - break; - case 4: - uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12) - + ((str[2] & 0x3F) << 6) + (str[3] & 0x3F); - if (uc < 0x10000 || uc >= 0x110000) uc = -1; - break; - default: - return -1; - } - - if ((uc & 0xFFFF) >= 0xFFFE) - return -1; - - *out = uc; - return length; -} - -size_t git_utf8_char_length(const char *_str, size_t str_len) -{ - const uint8_t *str = (const uint8_t *)_str; - size_t offset = 0, count = 0; - - while (offset < str_len) { - int length = utf8_charlen(str + offset, str_len - offset); - - if (length < 0) - length = 1; - - offset += length; - count++; - } - - return count; -} - -size_t git_utf8_valid_buf_length(const char *_str, size_t str_len) -{ - const uint8_t *str = (const uint8_t *)_str; - size_t offset = 0; - - while (offset < str_len) { - int length = utf8_charlen(str + offset, str_len - offset); - - if (length < 0) - break; - - offset += length; - } - - return offset; -} diff --git a/src/libgit2/utf8.h b/src/libgit2/utf8.h deleted file mode 100644 index dff91b294..000000000 --- a/src/libgit2/utf8.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_utf8_h__ -#define INCLUDE_utf8_h__ - -#include "common.h" - -/* - * Iterate through an UTF-8 string, yielding one codepoint at a time. - * - * @param out pointer where to store the current codepoint - * @param str current position in the string - * @param str_len size left in the string - * @return length in bytes of the read codepoint; -1 if the codepoint was invalid - */ -extern int git_utf8_iterate(uint32_t *out, const char *str, size_t str_len); - -/** - * Returns the number of characters in the given string. - * - * This function will count invalid codepoints; if any given byte is - * not part of a valid UTF-8 codepoint, then it will be counted toward - * the length in characters. - * - * In other words: - * 0x24 (U+0024 "$") has length 1 - * 0xc2 0xa2 (U+00A2 "¢") has length 1 - * 0x24 0xc2 0xa2 (U+0024 U+00A2 "$¢") has length 2 - * 0xf0 0x90 0x8d 0x88 (U+10348 "𐍈") has length 1 - * 0x24 0xc0 0xc1 0x34 (U+0024 "4) has length 4 - * - * @param str string to scan - * @param str_len size of the string - * @return length in characters of the string - */ -extern size_t git_utf8_char_length(const char *str, size_t str_len); - -/** - * Iterate through an UTF-8 string and stops after finding any invalid UTF-8 - * codepoints. - * - * @param str string to scan - * @param str_len size of the string - * @return length in bytes of the string that contains valid data - */ -extern size_t git_utf8_valid_buf_length(const char *str, size_t str_len); - -#endif diff --git a/src/libgit2/util.c b/src/libgit2/util.c deleted file mode 100644 index e06d4ca09..000000000 --- a/src/libgit2/util.c +++ /dev/null @@ -1,819 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "util.h" - -#include "common.h" - -#ifdef GIT_WIN32 -# include "win32/utf-conv.h" -# include "win32/w32_buffer.h" - -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include - -# ifdef GIT_QSORT_S -# include -# endif -#endif - -#ifdef _MSC_VER -# include -#endif - -#if defined(hpux) || defined(__hpux) || defined(_hpux) -# include -#endif - -int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) -{ - const char *p; - int64_t n, nn, v; - int c, ovfl, neg, ndig; - - p = nptr; - neg = 0; - n = 0; - ndig = 0; - ovfl = 0; - - /* - * White space - */ - while (nptr_len && git__isspace(*p)) - p++, nptr_len--; - - if (!nptr_len) - goto Return; - - /* - * Sign - */ - if (*p == '-' || *p == '+') { - if (*p == '-') - neg = 1; - p++; - nptr_len--; - } - - if (!nptr_len) - goto Return; - - /* - * Automatically detect the base if none was given to us. - * Right now, we assume that a number starting with '0x' - * is hexadecimal and a number starting with '0' is - * octal. - */ - if (base == 0) { - if (*p != '0') - base = 10; - else if (nptr_len > 2 && (p[1] == 'x' || p[1] == 'X')) - base = 16; - else - base = 8; - } - - if (base < 0 || 36 < base) - goto Return; - - /* - * Skip prefix of '0x'-prefixed hexadecimal numbers. There is no - * need to do the same for '0'-prefixed octal numbers as a - * leading '0' does not have any impact. Also, if we skip a - * leading '0' in such a string, then we may end up with no - * digits left and produce an error later on which isn't one. - */ - if (base == 16 && nptr_len > 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { - p += 2; - nptr_len -= 2; - } - - /* - * Non-empty sequence of digits - */ - for (; nptr_len > 0; p++,ndig++,nptr_len--) { - c = *p; - v = base; - if ('0'<=c && c<='9') - v = c - '0'; - else if ('a'<=c && c<='z') - v = c - 'a' + 10; - else if ('A'<=c && c<='Z') - v = c - 'A' + 10; - if (v >= base) - break; - v = neg ? -v : v; - if (git__multiply_int64_overflow(&nn, n, base) || git__add_int64_overflow(&n, nn, v)) { - ovfl = 1; - /* Keep on iterating until the end of this number */ - continue; - } - } - -Return: - if (ndig == 0) { - git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: not a number"); - return -1; - } - - if (endptr) - *endptr = p; - - if (ovfl) { - git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: overflow error"); - return -1; - } - - *result = n; - return 0; -} - -int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) -{ - const char *tmp_endptr; - int32_t tmp_int; - int64_t tmp_long; - int error; - - if ((error = git__strntol64(&tmp_long, nptr, nptr_len, &tmp_endptr, base)) < 0) - return error; - - tmp_int = tmp_long & 0xFFFFFFFF; - if (tmp_int != tmp_long) { - int len = (int)(tmp_endptr - nptr); - git_error_set(GIT_ERROR_INVALID, "failed to convert: '%.*s' is too large", len, nptr); - return -1; - } - - *result = tmp_int; - if (endptr) - *endptr = tmp_endptr; - - return error; -} - -int git__strcasecmp(const char *a, const char *b) -{ - while (*a && *b && git__tolower(*a) == git__tolower(*b)) - ++a, ++b; - return ((unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b)); -} - -int git__strcasesort_cmp(const char *a, const char *b) -{ - int cmp = 0; - - while (*a && *b) { - if (*a != *b) { - if (git__tolower(*a) != git__tolower(*b)) - break; - /* use case in sort order even if not in equivalence */ - if (!cmp) - cmp = (int)(*(const uint8_t *)a) - (int)(*(const uint8_t *)b); - } - - ++a, ++b; - } - - if (*a || *b) - return (unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b); - - return cmp; -} - -int git__strncasecmp(const char *a, const char *b, size_t sz) -{ - int al, bl; - - do { - al = (unsigned char)git__tolower(*a); - bl = (unsigned char)git__tolower(*b); - ++a, ++b; - } while (--sz && al && al == bl); - - return al - bl; -} - -void git__strntolower(char *str, size_t len) -{ - size_t i; - - for (i = 0; i < len; ++i) { - str[i] = (char)git__tolower(str[i]); - } -} - -void git__strtolower(char *str) -{ - git__strntolower(str, strlen(str)); -} - -GIT_INLINE(int) prefixcmp(const char *str, size_t str_n, const char *prefix, bool icase) -{ - int s, p; - - while (str_n--) { - s = (unsigned char)*str++; - p = (unsigned char)*prefix++; - - if (icase) { - s = git__tolower(s); - p = git__tolower(p); - } - - if (!p) - return 0; - - if (s != p) - return s - p; - } - - return (0 - *prefix); -} - -int git__prefixcmp(const char *str, const char *prefix) -{ - unsigned char s, p; - - while (1) { - p = *prefix++; - s = *str++; - - if (!p) - return 0; - - if (s != p) - return s - p; - } -} - -int git__prefixncmp(const char *str, size_t str_n, const char *prefix) -{ - return prefixcmp(str, str_n, prefix, false); -} - -int git__prefixcmp_icase(const char *str, const char *prefix) -{ - return prefixcmp(str, SIZE_MAX, prefix, true); -} - -int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix) -{ - return prefixcmp(str, str_n, prefix, true); -} - -int git__suffixcmp(const char *str, const char *suffix) -{ - size_t a = strlen(str); - size_t b = strlen(suffix); - if (a < b) - return -1; - return strcmp(str + (a - b), suffix); -} - -char *git__strtok(char **end, const char *sep) -{ - char *ptr = *end; - - while (*ptr && strchr(sep, *ptr)) - ++ptr; - - if (*ptr) { - char *start = ptr; - *end = start + 1; - - while (**end && !strchr(sep, **end)) - ++*end; - - if (**end) { - **end = '\0'; - ++*end; - } - - return start; - } - - return NULL; -} - -/* Similar to strtok, but does not collapse repeated tokens. */ -char *git__strsep(char **end, const char *sep) -{ - char *start = *end, *ptr = *end; - - while (*ptr && !strchr(sep, *ptr)) - ++ptr; - - if (*ptr) { - *end = ptr + 1; - *ptr = '\0'; - - return start; - } - - return NULL; -} - -size_t git__linenlen(const char *buffer, size_t buffer_len) -{ - char *nl = memchr(buffer, '\n', buffer_len); - return nl ? (size_t)(nl - buffer) + 1 : buffer_len; -} - -/* - * Adapted Not So Naive algorithm from http://www-igm.univ-mlv.fr/~lecroq/string/ - */ -const void * git__memmem(const void *haystack, size_t haystacklen, - const void *needle, size_t needlelen) -{ - const char *h, *n; - size_t j, k, l; - - if (needlelen > haystacklen || !haystacklen || !needlelen) - return NULL; - - h = (const char *) haystack, - n = (const char *) needle; - - if (needlelen == 1) - return memchr(haystack, *n, haystacklen); - - if (n[0] == n[1]) { - k = 2; - l = 1; - } else { - k = 1; - l = 2; - } - - j = 0; - while (j <= haystacklen - needlelen) { - if (n[1] != h[j + 1]) { - j += k; - } else { - if (memcmp(n + 2, h + j + 2, needlelen - 2) == 0 && - n[0] == h[j]) - return h + j; - j += l; - } - } - - return NULL; -} - -void git__hexdump(const char *buffer, size_t len) -{ - static const size_t LINE_WIDTH = 16; - - size_t line_count, last_line, i, j; - const char *line; - - line_count = (len / LINE_WIDTH); - last_line = (len % LINE_WIDTH); - - for (i = 0; i < line_count; ++i) { - printf("%08" PRIxZ " ", (i * LINE_WIDTH)); - - line = buffer + (i * LINE_WIDTH); - for (j = 0; j < LINE_WIDTH; ++j, ++line) { - printf("%02x ", (unsigned char)*line & 0xFF); - - if (j == (LINE_WIDTH / 2)) - printf(" "); - } - - printf(" |"); - - line = buffer + (i * LINE_WIDTH); - for (j = 0; j < LINE_WIDTH; ++j, ++line) - printf("%c", (*line >= 32 && *line <= 126) ? *line : '.'); - - printf("|\n"); - } - - if (last_line > 0) { - printf("%08" PRIxZ " ", (line_count * LINE_WIDTH)); - - line = buffer + (line_count * LINE_WIDTH); - for (j = 0; j < last_line; ++j, ++line) { - printf("%02x ", (unsigned char)*line & 0xFF); - - if (j == (LINE_WIDTH / 2)) - printf(" "); - } - - if (j < (LINE_WIDTH / 2)) - printf(" "); - for (j = 0; j < (LINE_WIDTH - last_line); ++j) - printf(" "); - - printf(" |"); - - line = buffer + (line_count * LINE_WIDTH); - for (j = 0; j < last_line; ++j, ++line) - printf("%c", (*line >= 32 && *line <= 126) ? *line : '.'); - - printf("|\n"); - } - - printf("\n"); -} - -#ifdef GIT_LEGACY_HASH -uint32_t git__hash(const void *key, int len, unsigned int seed) -{ - const uint32_t m = 0x5bd1e995; - const int r = 24; - uint32_t h = seed ^ len; - - const unsigned char *data = (const unsigned char *)key; - - while(len >= 4) { - uint32_t k = *(uint32_t *)data; - - k *= m; - k ^= k >> r; - k *= m; - - h *= m; - h ^= k; - - data += 4; - len -= 4; - } - - switch(len) { - case 3: h ^= data[2] << 16; - case 2: h ^= data[1] << 8; - case 1: h ^= data[0]; - h *= m; - }; - - h ^= h >> 13; - h *= m; - h ^= h >> 15; - - return h; -} -#else -/* - Cross-platform version of Murmurhash3 - http://code.google.com/p/smhasher/wiki/MurmurHash3 - by Austin Appleby (aappleby@gmail.com) - - This code is on the public domain. -*/ -uint32_t git__hash(const void *key, int len, uint32_t seed) -{ - -#define MURMUR_BLOCK() {\ - k1 *= c1; \ - k1 = git__rotl(k1,11);\ - k1 *= c2;\ - h1 ^= k1;\ - h1 = h1*3 + 0x52dce729;\ - c1 = c1*5 + 0x7b7d159c;\ - c2 = c2*5 + 0x6bce6396;\ -} - - const uint8_t *data = (const uint8_t*)key; - const int nblocks = len / 4; - - const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4); - const uint8_t *tail = (const uint8_t *)(data + nblocks * 4); - - uint32_t h1 = 0x971e137b ^ seed; - uint32_t k1; - - uint32_t c1 = 0x95543787; - uint32_t c2 = 0x2ad7eb25; - - int i; - - for (i = -nblocks; i; i++) { - k1 = blocks[i]; - MURMUR_BLOCK(); - } - - k1 = 0; - - switch(len & 3) { - case 3: k1 ^= tail[2] << 16; - /* fall through */ - case 2: k1 ^= tail[1] << 8; - /* fall through */ - case 1: k1 ^= tail[0]; - MURMUR_BLOCK(); - } - - h1 ^= len; - h1 ^= h1 >> 16; - h1 *= 0x85ebca6b; - h1 ^= h1 >> 13; - h1 *= 0xc2b2ae35; - h1 ^= h1 >> 16; - - return h1; -} -#endif - -/** - * A modified `bsearch` from the BSD glibc. - * - * Copyright (c) 1990 Regents of the University of California. - * All rights reserved. - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. [rescinded 22 July 1999] - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -int git__bsearch( - void **array, - size_t array_len, - const void *key, - int (*compare)(const void *, const void *), - size_t *position) -{ - size_t lim; - int cmp = -1; - void **part, **base = array; - - for (lim = array_len; lim != 0; lim >>= 1) { - part = base + (lim >> 1); - cmp = (*compare)(key, *part); - if (cmp == 0) { - base = part; - break; - } - if (cmp > 0) { /* key > p; take right partition */ - base = part + 1; - lim--; - } /* else take left partition */ - } - - if (position) - *position = (base - array); - - return (cmp == 0) ? 0 : GIT_ENOTFOUND; -} - -int git__bsearch_r( - void **array, - size_t array_len, - const void *key, - int (*compare_r)(const void *, const void *, void *), - void *payload, - size_t *position) -{ - size_t lim; - int cmp = -1; - void **part, **base = array; - - for (lim = array_len; lim != 0; lim >>= 1) { - part = base + (lim >> 1); - cmp = (*compare_r)(key, *part, payload); - if (cmp == 0) { - base = part; - break; - } - if (cmp > 0) { /* key > p; take right partition */ - base = part + 1; - lim--; - } /* else take left partition */ - } - - if (position) - *position = (base - array); - - return (cmp == 0) ? 0 : GIT_ENOTFOUND; -} - -/** - * A strcmp wrapper - * - * We don't want direct pointers to the CRT on Windows, we may - * get stdcall conflicts. - */ -int git__strcmp_cb(const void *a, const void *b) -{ - return strcmp((const char *)a, (const char *)b); -} - -int git__strcasecmp_cb(const void *a, const void *b) -{ - return strcasecmp((const char *)a, (const char *)b); -} - -int git__parse_bool(int *out, const char *value) -{ - /* A missing value means true */ - if (value == NULL || - !strcasecmp(value, "true") || - !strcasecmp(value, "yes") || - !strcasecmp(value, "on")) { - *out = 1; - return 0; - } - if (!strcasecmp(value, "false") || - !strcasecmp(value, "no") || - !strcasecmp(value, "off") || - value[0] == '\0') { - *out = 0; - return 0; - } - - return -1; -} - -size_t git__unescape(char *str) -{ - char *scan, *pos = str; - - if (!str) - return 0; - - for (scan = str; *scan; pos++, scan++) { - if (*scan == '\\' && *(scan + 1) != '\0') - scan++; /* skip '\' but include next char */ - if (pos != scan) - *pos = *scan; - } - - if (pos != scan) { - *pos = '\0'; - } - - return (pos - str); -} - -#if defined(GIT_QSORT_S) || defined(GIT_QSORT_R_BSD) -typedef struct { - git__sort_r_cmp cmp; - void *payload; -} git__qsort_r_glue; - -static int GIT_LIBGIT2_CALL git__qsort_r_glue_cmp( - void *payload, const void *a, const void *b) -{ - git__qsort_r_glue *glue = payload; - return glue->cmp(a, b, glue->payload); -} -#endif - - -#if !defined(GIT_QSORT_R_BSD) && \ - !defined(GIT_QSORT_R_GNU) && \ - !defined(GIT_QSORT_S) -static void swap(uint8_t *a, uint8_t *b, size_t elsize) -{ - char tmp[256]; - - while (elsize) { - size_t n = elsize < sizeof(tmp) ? elsize : sizeof(tmp); - memcpy(tmp, a + elsize - n, n); - memcpy(a + elsize - n, b + elsize - n, n); - memcpy(b + elsize - n, tmp, n); - elsize -= n; - } -} - -static void insertsort( - void *els, size_t nel, size_t elsize, - git__sort_r_cmp cmp, void *payload) -{ - uint8_t *base = els; - uint8_t *end = base + nel * elsize; - uint8_t *i, *j; - - for (i = base + elsize; i < end; i += elsize) - for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize) - swap(j, j - elsize, elsize); -} -#endif - -void git__qsort_r( - void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload) -{ -#if defined(GIT_QSORT_R_BSD) - git__qsort_r_glue glue = { cmp, payload }; - qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp); -#elif defined(GIT_QSORT_R_GNU) - qsort_r(els, nel, elsize, cmp, payload); -#elif defined(GIT_QSORT_S) - git__qsort_r_glue glue = { cmp, payload }; - qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue); -#else - insertsort(els, nel, elsize, cmp, payload); -#endif -} - -#ifdef GIT_WIN32 -int git__getenv(git_str *out, const char *name) -{ - wchar_t *wide_name = NULL, *wide_value = NULL; - DWORD value_len; - int error = -1; - - git_str_clear(out); - - if (git__utf8_to_16_alloc(&wide_name, name) < 0) - return -1; - - if ((value_len = GetEnvironmentVariableW(wide_name, NULL, 0)) > 0) { - wide_value = git__malloc(value_len * sizeof(wchar_t)); - GIT_ERROR_CHECK_ALLOC(wide_value); - - value_len = GetEnvironmentVariableW(wide_name, wide_value, value_len); - } - - if (value_len) - error = git_str_put_w(out, wide_value, value_len); - else if (GetLastError() == ERROR_SUCCESS || GetLastError() == ERROR_ENVVAR_NOT_FOUND) - error = GIT_ENOTFOUND; - else - git_error_set(GIT_ERROR_OS, "could not read environment variable '%s'", name); - - git__free(wide_name); - git__free(wide_value); - return error; -} -#else -int git__getenv(git_str *out, const char *name) -{ - const char *val = getenv(name); - - git_str_clear(out); - - if (!val) - return GIT_ENOTFOUND; - - return git_str_puts(out, val); -} -#endif - -/* - * By doing this in two steps we can at least get - * the function to be somewhat coherent, even - * with this disgusting nest of #ifdefs. - */ -#ifndef _SC_NPROCESSORS_ONLN -# ifdef _SC_NPROC_ONLN -# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN -# elif defined _SC_CRAY_NCPU -# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU -# endif -#endif - -int git__online_cpus(void) -{ -#ifdef _SC_NPROCESSORS_ONLN - long ncpus; -#endif - -#ifdef _WIN32 - SYSTEM_INFO info; - GetSystemInfo(&info); - - if ((int)info.dwNumberOfProcessors > 0) - return (int)info.dwNumberOfProcessors; -#elif defined(hpux) || defined(__hpux) || defined(_hpux) - struct pst_dynamic psd; - - if (!pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0)) - return (int)psd.psd_proc_cnt; -#endif - -#ifdef _SC_NPROCESSORS_ONLN - if ((ncpus = (long)sysconf(_SC_NPROCESSORS_ONLN)) > 0) - return (int)ncpus; -#endif - - return 1; -} diff --git a/src/libgit2/util.h b/src/libgit2/util.h deleted file mode 100644 index 141779ade..000000000 --- a/src/libgit2/util.h +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_util_h__ -#define INCLUDE_util_h__ - -#ifndef GIT_WIN32 -# include -#endif - -#include "str.h" -#include "common.h" -#include "strnlen.h" -#include "thread.h" - -#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) -#define bitsizeof(x) (CHAR_BIT * sizeof(x)) -#define MSB(x, bits) ((x) & (~UINT64_C(0) << (bitsizeof(x) - (bits)))) -#ifndef min -# define min(a,b) ((a) < (b) ? (a) : (b)) -#endif -#ifndef max -# define max(a,b) ((a) > (b) ? (a) : (b)) -#endif - -#if defined(__GNUC__) -# define GIT_CONTAINER_OF(ptr, type, member) \ - __builtin_choose_expr( \ - __builtin_offsetof(type, member) == 0 && \ - __builtin_types_compatible_p(__typeof__(&((type *) 0)->member), __typeof__(ptr)), \ - ((type *) (ptr)), \ - (void)0) -#else -# define GIT_CONTAINER_OF(ptr, type, member) (type *)(ptr) -#endif - -/** - * Return the length of a constant string. - * We are aware that `strlen` performs the same task and is usually - * optimized away by the compiler, whilst being safer because it returns - * valid values when passed a pointer instead of a constant string; however - * this macro will transparently work with wide-char and single-char strings. - */ -#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1) - -#define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \ - ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2))) - -#define CASESELECT(IGNORE_CASE, ICASE, CASE) \ - ((IGNORE_CASE) ? (ICASE) : (CASE)) - -extern int git__prefixcmp(const char *str, const char *prefix); -extern int git__prefixcmp_icase(const char *str, const char *prefix); -extern int git__prefixncmp(const char *str, size_t str_n, const char *prefix); -extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix); -extern int git__suffixcmp(const char *str, const char *suffix); - -GIT_INLINE(int) git__signum(int val) -{ - return ((val > 0) - (val < 0)); -} - -extern int git__strntol32(int32_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); -extern int git__strntol64(int64_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); - - -extern void git__hexdump(const char *buffer, size_t n); -extern uint32_t git__hash(const void *key, int len, uint32_t seed); - -/* 32-bit cross-platform rotl */ -#ifdef _MSC_VER /* use built-in method in MSVC */ -# define git__rotl(v, s) (uint32_t)_rotl(v, s) -#else /* use bitops in GCC; with o2 this gets optimized to a rotl instruction */ -# define git__rotl(v, s) (uint32_t)(((uint32_t)(v) << (s)) | ((uint32_t)(v) >> (32 - (s)))) -#endif - -extern char *git__strtok(char **end, const char *sep); -extern char *git__strsep(char **end, const char *sep); - -extern void git__strntolower(char *str, size_t len); -extern void git__strtolower(char *str); - -#ifdef GIT_WIN32 -GIT_INLINE(int) git__tolower(int c) -{ - return (c >= 'A' && c <= 'Z') ? (c + 32) : c; -} -#else -# define git__tolower(a) tolower(a) -#endif - -extern size_t git__linenlen(const char *buffer, size_t buffer_len); - -GIT_INLINE(const char *) git__next_line(const char *s) -{ - while (*s && *s != '\n') s++; - while (*s == '\n' || *s == '\r') s++; - return s; -} - -GIT_INLINE(const void *) git__memrchr(const void *s, int c, size_t n) -{ - const unsigned char *cp; - - if (n != 0) { - cp = (unsigned char *)s + n; - do { - if (*(--cp) == (unsigned char)c) - return cp; - } while (--n != 0); - } - - return NULL; -} - -extern const void * git__memmem(const void *haystack, size_t haystacklen, - const void *needle, size_t needlelen); - -typedef int (*git__tsort_cmp)(const void *a, const void *b); - -extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp); - -typedef int (*git__sort_r_cmp)(const void *a, const void *b, void *payload); - -extern void git__tsort_r( - void **dst, size_t size, git__sort_r_cmp cmp, void *payload); - -extern void git__qsort_r( - void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload); - -/** - * @param position If non-NULL, this will be set to the position where the - * element is or would be inserted if not found. - * @return 0 if found; GIT_ENOTFOUND if not found - */ -extern int git__bsearch( - void **array, - size_t array_len, - const void *key, - int (*compare)(const void *key, const void *element), - size_t *position); - -extern int git__bsearch_r( - void **array, - size_t array_len, - const void *key, - int (*compare_r)(const void *key, const void *element, void *payload), - void *payload, - size_t *position); - -#define git__strcmp strcmp -#define git__strncmp strncmp - -extern int git__strcmp_cb(const void *a, const void *b); -extern int git__strcasecmp_cb(const void *a, const void *b); - -extern int git__strcasecmp(const char *a, const char *b); -extern int git__strncasecmp(const char *a, const char *b, size_t sz); - -extern int git__strcasesort_cmp(const char *a, const char *b); - -/* - * Compare some NUL-terminated `a` to a possibly non-NUL terminated - * `b` of length `b_len`; like `strncmp` but ensuring that - * `strlen(a) == b_len` as well. - */ -GIT_INLINE(int) git__strlcmp(const char *a, const char *b, size_t b_len) -{ - int cmp = strncmp(a, b, b_len); - return cmp ? cmp : (int)a[b_len]; -} - -typedef struct { - git_atomic32 refcount; - void *owner; -} git_refcount; - -typedef void (*git_refcount_freeptr)(void *r); - -#define GIT_REFCOUNT_INC(r) { \ - git_atomic32_inc(&(r)->rc.refcount); \ -} - -#define GIT_REFCOUNT_DEC(_r, do_free) { \ - git_refcount *r = &(_r)->rc; \ - int val = git_atomic32_dec(&r->refcount); \ - if (val <= 0 && r->owner == NULL) { do_free(_r); } \ -} - -#define GIT_REFCOUNT_OWN(r, o) { \ - (void)git_atomic_swap((r)->rc.owner, o); \ -} - -#define GIT_REFCOUNT_OWNER(r) git_atomic_load((r)->rc.owner) - -#define GIT_REFCOUNT_VAL(r) git_atomic32_get((r)->rc.refcount) - - -static signed char from_hex[] = { --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */ --1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */ --1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */ -}; - -GIT_INLINE(int) git__fromhex(char h) -{ - return from_hex[(unsigned char) h]; -} - -GIT_INLINE(int) git__ishex(const char *str) -{ - unsigned i; - for (i=0; str[i] != '\0'; i++) - if (git__fromhex(str[i]) < 0) - return 0; - return 1; -} - -GIT_INLINE(size_t) git__size_t_bitmask(size_t v) -{ - v--; - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - - return v; -} - -GIT_INLINE(size_t) git__size_t_powerof2(size_t v) -{ - return git__size_t_bitmask(v) + 1; -} - -GIT_INLINE(bool) git__isupper(int c) -{ - return (c >= 'A' && c <= 'Z'); -} - -GIT_INLINE(bool) git__isalpha(int c) -{ - return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); -} - -GIT_INLINE(bool) git__isdigit(int c) -{ - return (c >= '0' && c <= '9'); -} - -GIT_INLINE(bool) git__isspace(int c) -{ - return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); -} - -GIT_INLINE(bool) git__isspace_nonlf(int c) -{ - return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v'); -} - -GIT_INLINE(bool) git__iswildcard(int c) -{ - return (c == '*' || c == '?' || c == '['); -} - -GIT_INLINE(bool) git__isxdigit(int c) -{ - return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); -} - -/* - * Parse a string value as a boolean, just like Core Git does. - * - * Valid values for true are: 'true', 'yes', 'on' - * Valid values for false are: 'false', 'no', 'off' - */ -extern int git__parse_bool(int *out, const char *value); - -/* - * Unescapes a string in-place. - * - * Edge cases behavior: - * - "jackie\" -> "jacky\" - * - "chan\\" -> "chan\" - */ -extern size_t git__unescape(char *str); - -/* - * Safely zero-out memory, making sure that the compiler - * doesn't optimize away the operation. - */ -GIT_INLINE(void) git__memzero(void *data, size_t size) -{ -#ifdef _MSC_VER - SecureZeroMemory((PVOID)data, size); -#else - volatile uint8_t *scan = (volatile uint8_t *)data; - - while (size--) - *scan++ = 0x0; -#endif -} - -#ifdef GIT_WIN32 - -GIT_INLINE(double) git__timer(void) -{ - /* GetTickCount64 returns the number of milliseconds that have - * elapsed since the system was started. */ - return (double) GetTickCount64() / (double) 1000; -} - -#elif __APPLE__ - -#include - -GIT_INLINE(double) git__timer(void) -{ - uint64_t time = mach_absolute_time(); - static double scaling_factor = 0; - - if (scaling_factor == 0) { - mach_timebase_info_data_t info; - (void)mach_timebase_info(&info); - scaling_factor = (double)info.numer / (double)info.denom; - } - - return (double)time * scaling_factor / 1.0E9; -} - -#elif defined(__amigaos4__) - -#include - -GIT_INLINE(double) git__timer(void) -{ - struct TimeVal tv; - ITimer->GetUpTime(&tv); - return (double)tv.Seconds + (double)tv.Microseconds / 1.0E6; -} - -#else - -#include - -GIT_INLINE(double) git__timer(void) -{ - struct timeval tv; - -#ifdef CLOCK_MONOTONIC - struct timespec tp; - if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) - return (double) tp.tv_sec + (double) tp.tv_nsec / 1.0E9; -#endif - - /* Fall back to using gettimeofday */ - gettimeofday(&tv, NULL); - return (double)tv.tv_sec + (double)tv.tv_usec / 1.0E6; -} - -#endif - -extern int git__getenv(git_str *out, const char *name); - -extern int git__online_cpus(void); - -GIT_INLINE(int) git__noop(void) { return 0; } - -#include "alloc.h" - -#endif diff --git a/src/libgit2/util/platform.h.in b/src/libgit2/util/platform.h.in deleted file mode 100644 index e511fe331..000000000 --- a/src/libgit2/util/platform.h.in +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef INCLUDE_platform_h__ -#define INCLUDE_platform_h__ - -#cmakedefine GIT_DEBUG_POOL 1 -#cmakedefine GIT_DEBUG_STRICT_ALLOC 1 -#cmakedefine GIT_DEBUG_STRICT_OPEN 1 - -#cmakedefine GIT_WIN32_LEAKCHECK 1 - -#cmakedefine GIT_ARCH_64 1 -#cmakedefine GIT_ARCH_32 1 - -#cmakedefine GIT_USE_STAT_MTIM 1 -#cmakedefine GIT_USE_STAT_MTIMESPEC 1 -#cmakedefine GIT_USE_STAT_MTIME_NSEC 1 -#cmakedefine GIT_USE_FUTIMENS 1 - -#cmakedefine GIT_USE_QSORT_R_BSD 1 -#cmakedefine GIT_USE_QSORT_R_GNU 1 -#cmakedefine GIT_USE_QSORT_S 1 - -#cmakedefine GIT_REGEX_REGCOMP_L 1 -#cmakedefine GIT_REGEX_REGCOMP 1 -#cmakedefine GIT_REGEX_PCRE 1 -#cmakedefine GIT_REGEX_PCRE2 1 -#cmakedefine GIT_REGEX_BUILTIN 1 - -#cmakedefine GIT_SHA1_COLLISIONDETECT 1 -#cmakedefine GIT_SHA1_WIN32 1 -#cmakedefine GIT_SHA1_COMMON_CRYPTO 1 -#cmakedefine GIT_SHA1_OPENSSL 1 -#cmakedefine GIT_SHA1_MBEDTLS 1 - -#endif diff --git a/src/libgit2/varint.c b/src/libgit2/varint.c deleted file mode 100644 index 9ffc1d744..000000000 --- a/src/libgit2/varint.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "varint.h" - -uintmax_t git_decode_varint(const unsigned char *bufp, size_t *varint_len) -{ - const unsigned char *buf = bufp; - unsigned char c = *buf++; - uintmax_t val = c & 127; - while (c & 128) { - val += 1; - if (!val || MSB(val, 7)) { - /* This is not a valid varint_len, so it signals - the error */ - *varint_len = 0; - return 0; /* overflow */ - } - c = *buf++; - val = (val << 7) + (c & 127); - } - *varint_len = buf - bufp; - return val; -} - -int git_encode_varint(unsigned char *buf, size_t bufsize, uintmax_t value) -{ - unsigned char varint[16]; - unsigned pos = sizeof(varint) - 1; - varint[pos] = value & 127; - while (value >>= 7) - varint[--pos] = 128 | (--value & 127); - if (buf) { - if (bufsize < (sizeof(varint) - pos)) - return -1; - memcpy(buf, varint + pos, sizeof(varint) - pos); - } - return sizeof(varint) - pos; -} diff --git a/src/libgit2/varint.h b/src/libgit2/varint.h deleted file mode 100644 index 652e22486..000000000 --- a/src/libgit2/varint.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_varint_h__ -#define INCLUDE_varint_h__ - -#include "common.h" - -#include - -extern int git_encode_varint(unsigned char *, size_t, uintmax_t); -extern uintmax_t git_decode_varint(const unsigned char *, size_t *); - -#endif diff --git a/src/libgit2/vector.c b/src/libgit2/vector.c deleted file mode 100644 index 4a4bc8c0e..000000000 --- a/src/libgit2/vector.c +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "vector.h" - -#include "integer.h" - -/* In elements, not bytes */ -#define MIN_ALLOCSIZE 8 - -GIT_INLINE(size_t) compute_new_size(git_vector *v) -{ - size_t new_size = v->_alloc_size; - - /* Use a resize factor of 1.5, which is quick to compute using integer - * instructions and less than the golden ratio (1.618...) */ - if (new_size < MIN_ALLOCSIZE) - new_size = MIN_ALLOCSIZE; - else if (new_size <= (SIZE_MAX / 3) * 2) - new_size += new_size / 2; - else - new_size = SIZE_MAX; - - return new_size; -} - -GIT_INLINE(int) resize_vector(git_vector *v, size_t new_size) -{ - void *new_contents; - - if (new_size == 0) - return 0; - - new_contents = git__reallocarray(v->contents, new_size, sizeof(void *)); - GIT_ERROR_CHECK_ALLOC(new_contents); - - v->_alloc_size = new_size; - v->contents = new_contents; - - return 0; -} - -int git_vector_size_hint(git_vector *v, size_t size_hint) -{ - if (v->_alloc_size >= size_hint) - return 0; - return resize_vector(v, size_hint); -} - -int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp) -{ - GIT_ASSERT_ARG(v); - GIT_ASSERT_ARG(src); - - v->_alloc_size = 0; - v->contents = NULL; - v->_cmp = cmp ? cmp : src->_cmp; - v->length = src->length; - v->flags = src->flags; - if (cmp != src->_cmp) - git_vector_set_sorted(v, 0); - - if (src->length) { - size_t bytes; - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&bytes, src->length, sizeof(void *)); - v->contents = git__malloc(bytes); - GIT_ERROR_CHECK_ALLOC(v->contents); - v->_alloc_size = src->length; - memcpy(v->contents, src->contents, bytes); - } - - return 0; -} - -void git_vector_free(git_vector *v) -{ - if (!v) - return; - - git__free(v->contents); - v->contents = NULL; - - v->length = 0; - v->_alloc_size = 0; -} - -void git_vector_free_deep(git_vector *v) -{ - size_t i; - - if (!v) - return; - - for (i = 0; i < v->length; ++i) { - git__free(v->contents[i]); - v->contents[i] = NULL; - } - - git_vector_free(v); -} - -int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp) -{ - GIT_ASSERT_ARG(v); - - v->_alloc_size = 0; - v->_cmp = cmp; - v->length = 0; - v->flags = GIT_VECTOR_SORTED; - v->contents = NULL; - - return resize_vector(v, max(initial_size, MIN_ALLOCSIZE)); -} - -void **git_vector_detach(size_t *size, size_t *asize, git_vector *v) -{ - void **data = v->contents; - - if (size) - *size = v->length; - if (asize) - *asize = v->_alloc_size; - - v->_alloc_size = 0; - v->length = 0; - v->contents = NULL; - - return data; -} - -int git_vector_insert(git_vector *v, void *element) -{ - GIT_ASSERT_ARG(v); - - if (v->length >= v->_alloc_size && - resize_vector(v, compute_new_size(v)) < 0) - return -1; - - v->contents[v->length++] = element; - - git_vector_set_sorted(v, v->length <= 1); - - return 0; -} - -int git_vector_insert_sorted( - git_vector *v, void *element, int (*on_dup)(void **old, void *new)) -{ - int result; - size_t pos; - - GIT_ASSERT_ARG(v); - GIT_ASSERT(v->_cmp); - - if (!git_vector_is_sorted(v)) - git_vector_sort(v); - - if (v->length >= v->_alloc_size && - resize_vector(v, compute_new_size(v)) < 0) - return -1; - - /* If we find the element and have a duplicate handler callback, - * invoke it. If it returns non-zero, then cancel insert, otherwise - * proceed with normal insert. - */ - if (!git__bsearch(v->contents, v->length, element, v->_cmp, &pos) && - on_dup && (result = on_dup(&v->contents[pos], element)) < 0) - return result; - - /* shift elements to the right */ - if (pos < v->length) - memmove(v->contents + pos + 1, v->contents + pos, - (v->length - pos) * sizeof(void *)); - - v->contents[pos] = element; - v->length++; - - return 0; -} - -void git_vector_sort(git_vector *v) -{ - if (git_vector_is_sorted(v) || !v->_cmp) - return; - - if (v->length > 1) - git__tsort(v->contents, v->length, v->_cmp); - git_vector_set_sorted(v, 1); -} - -int git_vector_bsearch2( - size_t *at_pos, - git_vector *v, - git_vector_cmp key_lookup, - const void *key) -{ - GIT_ASSERT_ARG(v); - GIT_ASSERT_ARG(key); - GIT_ASSERT(key_lookup); - - /* need comparison function to sort the vector */ - if (!v->_cmp) - return -1; - - git_vector_sort(v); - - return git__bsearch(v->contents, v->length, key, key_lookup, at_pos); -} - -int git_vector_search2( - size_t *at_pos, const git_vector *v, git_vector_cmp key_lookup, const void *key) -{ - size_t i; - - GIT_ASSERT_ARG(v); - GIT_ASSERT_ARG(key); - GIT_ASSERT(key_lookup); - - for (i = 0; i < v->length; ++i) { - if (key_lookup(key, v->contents[i]) == 0) { - if (at_pos) - *at_pos = i; - - return 0; - } - } - - return GIT_ENOTFOUND; -} - -static int strict_comparison(const void *a, const void *b) -{ - return (a == b) ? 0 : -1; -} - -int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry) -{ - return git_vector_search2(at_pos, v, v->_cmp ? v->_cmp : strict_comparison, entry); -} - -int git_vector_remove(git_vector *v, size_t idx) -{ - size_t shift_count; - - GIT_ASSERT_ARG(v); - - if (idx >= v->length) - return GIT_ENOTFOUND; - - shift_count = v->length - idx - 1; - - if (shift_count) - memmove(&v->contents[idx], &v->contents[idx + 1], - shift_count * sizeof(void *)); - - v->length--; - return 0; -} - -void git_vector_pop(git_vector *v) -{ - if (v->length > 0) - v->length--; -} - -void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)) -{ - git_vector_cmp cmp; - size_t i, j; - - if (v->length <= 1) - return; - - git_vector_sort(v); - cmp = v->_cmp ? v->_cmp : strict_comparison; - - for (i = 0, j = 1 ; j < v->length; ++j) - if (!cmp(v->contents[i], v->contents[j])) { - if (git_free_cb) - git_free_cb(v->contents[i]); - - v->contents[i] = v->contents[j]; - } else - v->contents[++i] = v->contents[j]; - - v->length -= j - i - 1; -} - -void git_vector_remove_matching( - git_vector *v, - int (*match)(const git_vector *v, size_t idx, void *payload), - void *payload) -{ - size_t i, j; - - for (i = 0, j = 0; j < v->length; ++j) { - v->contents[i] = v->contents[j]; - - if (!match(v, i, payload)) - i++; - } - - v->length = i; -} - -void git_vector_clear(git_vector *v) -{ - v->length = 0; - git_vector_set_sorted(v, 1); -} - -void git_vector_swap(git_vector *a, git_vector *b) -{ - git_vector t; - - if (a != b) { - memcpy(&t, a, sizeof(t)); - memcpy(a, b, sizeof(t)); - memcpy(b, &t, sizeof(t)); - } -} - -int git_vector_resize_to(git_vector *v, size_t new_length) -{ - if (new_length > v->_alloc_size && - resize_vector(v, new_length) < 0) - return -1; - - if (new_length > v->length) - memset(&v->contents[v->length], 0, - sizeof(void *) * (new_length - v->length)); - - v->length = new_length; - - return 0; -} - -int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len) -{ - size_t new_length; - - GIT_ASSERT_ARG(insert_len > 0); - GIT_ASSERT_ARG(idx <= v->length); - - GIT_ERROR_CHECK_ALLOC_ADD(&new_length, v->length, insert_len); - - if (new_length > v->_alloc_size && resize_vector(v, new_length) < 0) - return -1; - - memmove(&v->contents[idx + insert_len], &v->contents[idx], - sizeof(void *) * (v->length - idx)); - memset(&v->contents[idx], 0, sizeof(void *) * insert_len); - - v->length = new_length; - return 0; -} - -int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len) -{ - size_t new_length = v->length - remove_len; - size_t end_idx = 0; - - GIT_ASSERT_ARG(remove_len > 0); - - if (git__add_sizet_overflow(&end_idx, idx, remove_len)) - GIT_ASSERT(0); - - GIT_ASSERT(end_idx <= v->length); - - if (end_idx < v->length) - memmove(&v->contents[idx], &v->contents[end_idx], - sizeof(void *) * (v->length - end_idx)); - - memset(&v->contents[new_length], 0, sizeof(void *) * remove_len); - - v->length = new_length; - return 0; -} - -int git_vector_set(void **old, git_vector *v, size_t position, void *value) -{ - if (position + 1 > v->length) { - if (git_vector_resize_to(v, position + 1) < 0) - return -1; - } - - if (old != NULL) - *old = v->contents[position]; - - v->contents[position] = value; - - return 0; -} - -int git_vector_verify_sorted(const git_vector *v) -{ - size_t i; - - if (!git_vector_is_sorted(v)) - return -1; - - for (i = 1; i < v->length; ++i) { - if (v->_cmp(v->contents[i - 1], v->contents[i]) > 0) - return -1; - } - - return 0; -} - -void git_vector_reverse(git_vector *v) -{ - size_t a, b; - - if (v->length == 0) - return; - - a = 0; - b = v->length - 1; - - while (a < b) { - void *tmp = v->contents[a]; - v->contents[a] = v->contents[b]; - v->contents[b] = tmp; - a++; - b--; - } -} diff --git a/src/libgit2/vector.h b/src/libgit2/vector.h deleted file mode 100644 index ae3c79a4c..000000000 --- a/src/libgit2/vector.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_vector_h__ -#define INCLUDE_vector_h__ - -#include "common.h" - -typedef int (*git_vector_cmp)(const void *, const void *); - -enum { - GIT_VECTOR_SORTED = (1u << 0), - GIT_VECTOR_FLAG_MAX = (1u << 1) -}; - -typedef struct git_vector { - size_t _alloc_size; - git_vector_cmp _cmp; - void **contents; - size_t length; - uint32_t flags; -} git_vector; - -#define GIT_VECTOR_INIT {0} - -GIT_WARN_UNUSED_RESULT int git_vector_init( - git_vector *v, size_t initial_size, git_vector_cmp cmp); -void git_vector_free(git_vector *v); -void git_vector_free_deep(git_vector *v); /* free each entry and self */ -void git_vector_clear(git_vector *v); -GIT_WARN_UNUSED_RESULT int git_vector_dup( - git_vector *v, const git_vector *src, git_vector_cmp cmp); -void git_vector_swap(git_vector *a, git_vector *b); -int git_vector_size_hint(git_vector *v, size_t size_hint); - -void **git_vector_detach(size_t *size, size_t *asize, git_vector *v); - -void git_vector_sort(git_vector *v); - -/** Linear search for matching entry using internal comparison function */ -int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry); - -/** Linear search for matching entry using explicit comparison function */ -int git_vector_search2(size_t *at_pos, const git_vector *v, git_vector_cmp cmp, const void *key); - -/** - * Binary search for matching entry using explicit comparison function that - * returns position where item would go if not found. - */ -int git_vector_bsearch2( - size_t *at_pos, git_vector *v, git_vector_cmp cmp, const void *key); - -/** Binary search for matching entry using internal comparison function */ -GIT_INLINE(int) git_vector_bsearch(size_t *at_pos, git_vector *v, const void *key) -{ - return git_vector_bsearch2(at_pos, v, v->_cmp, key); -} - -GIT_INLINE(void *) git_vector_get(const git_vector *v, size_t position) -{ - return (position < v->length) ? v->contents[position] : NULL; -} - -#define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL) - -GIT_INLINE(size_t) git_vector_length(const git_vector *v) -{ - return v->length; -} - -GIT_INLINE(void *) git_vector_last(const git_vector *v) -{ - return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL; -} - -#define git_vector_foreach(v, iter, elem) \ - for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ ) - -#define git_vector_rforeach(v, iter, elem) \ - for ((iter) = (v)->length - 1; (iter) < SIZE_MAX && ((elem) = (v)->contents[(iter)], 1); (iter)-- ) - -int git_vector_insert(git_vector *v, void *element); -int git_vector_insert_sorted(git_vector *v, void *element, - int (*on_dup)(void **old, void *new)); -int git_vector_remove(git_vector *v, size_t idx); -void git_vector_pop(git_vector *v); -void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)); - -void git_vector_remove_matching( - git_vector *v, - int (*match)(const git_vector *v, size_t idx, void *payload), - void *payload); - -int git_vector_resize_to(git_vector *v, size_t new_length); -int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len); -int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len); - -int git_vector_set(void **old, git_vector *v, size_t position, void *value); - -/** Check if vector is sorted */ -#define git_vector_is_sorted(V) (((V)->flags & GIT_VECTOR_SORTED) != 0) - -/** Directly set sorted state of vector */ -#define git_vector_set_sorted(V,S) do { \ - (V)->flags = (S) ? ((V)->flags | GIT_VECTOR_SORTED) : \ - ((V)->flags & ~GIT_VECTOR_SORTED); } while (0) - -/** Set the comparison function used for sorting the vector */ -GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp) -{ - if (cmp != v->_cmp) { - v->_cmp = cmp; - git_vector_set_sorted(v, 0); - } -} - -/* Just use this in tests, not for realz. returns -1 if not sorted */ -int git_vector_verify_sorted(const git_vector *v); - -/** - * Reverse the vector in-place. - */ -void git_vector_reverse(git_vector *v); - -#endif diff --git a/src/libgit2/wildmatch.c b/src/libgit2/wildmatch.c deleted file mode 100644 index a894e4841..000000000 --- a/src/libgit2/wildmatch.c +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - * - * Do shell-style pattern matching for ?, \, [], and * characters. - * It is 8bit clean. - * - * Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. - * Rich $alz is now . - * - * Modified by Wayne Davison to special-case '/' matching, to make '**' - * work differently than '*', and to fix the character-class code. - * - * Imported from git.git. - */ - -#include "wildmatch.h" - -#define GIT_SPACE 0x01 -#define GIT_DIGIT 0x02 -#define GIT_ALPHA 0x04 -#define GIT_GLOB_SPECIAL 0x08 -#define GIT_REGEX_SPECIAL 0x10 -#define GIT_PATHSPEC_MAGIC 0x20 -#define GIT_CNTRL 0x40 -#define GIT_PUNCT 0x80 - -enum { - S = GIT_SPACE, - A = GIT_ALPHA, - D = GIT_DIGIT, - G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ - R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */ - P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */ - X = GIT_CNTRL, - U = GIT_PUNCT, - Z = GIT_CNTRL | GIT_SPACE -}; - -static const unsigned char sane_ctype[256] = { - X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X, /* 0.. 15 */ - X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 16.. 31 */ - S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */ - D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */ - P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */ - A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P, /* 80.. 95 */ - P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */ - A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X, /* 112..127 */ - /* Nothing in the 128.. range */ -}; - -#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0) -#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL) - -typedef unsigned char uchar; - -/* What character marks an inverted character class? */ -#define NEGATE_CLASS '!' -#define NEGATE_CLASS2 '^' - -#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \ - && *(class) == *(litmatch) \ - && strncmp((char*)class, litmatch, len) == 0) - -#if defined STDC_HEADERS || !defined isascii -# define ISASCII(c) 1 -#else -# define ISASCII(c) isascii(c) -#endif - -#ifdef isblank -# define ISBLANK(c) (ISASCII(c) && isblank(c)) -#else -# define ISBLANK(c) ((c) == ' ' || (c) == '\t') -#endif - -#ifdef isgraph -# define ISGRAPH(c) (ISASCII(c) && isgraph(c)) -#else -# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c)) -#endif - -#define ISPRINT(c) (ISASCII(c) && isprint(c)) -#define ISDIGIT(c) (ISASCII(c) && isdigit(c)) -#define ISALNUM(c) (ISASCII(c) && isalnum(c)) -#define ISALPHA(c) (ISASCII(c) && isalpha(c)) -#define ISCNTRL(c) (ISASCII(c) && iscntrl(c)) -#define ISLOWER(c) (ISASCII(c) && islower(c)) -#define ISPUNCT(c) (ISASCII(c) && ispunct(c)) -#define ISSPACE(c) (ISASCII(c) && isspace(c)) -#define ISUPPER(c) (ISASCII(c) && isupper(c)) -#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c)) - -/* Match pattern "p" against "text" */ -static int dowild(const uchar *p, const uchar *text, unsigned int flags) -{ - uchar p_ch; - const uchar *pattern = p; - - for ( ; (p_ch = *p) != '\0'; text++, p++) { - int matched, match_slash, negated; - uchar t_ch, prev_ch; - if ((t_ch = *text) == '\0' && p_ch != '*') - return WM_ABORT_ALL; - if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) - t_ch = tolower(t_ch); - if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) - p_ch = tolower(p_ch); - switch (p_ch) { - case '\\': - /* Literal match with following character. Note that the test - * in "default" handles the p[1] == '\0' failure case. */ - p_ch = *++p; - /* FALLTHROUGH */ - default: - if (t_ch != p_ch) - return WM_NOMATCH; - continue; - case '?': - /* Match anything but '/'. */ - if ((flags & WM_PATHNAME) && t_ch == '/') - return WM_NOMATCH; - continue; - case '*': - if (*++p == '*') { - const uchar *prev_p = p - 2; - while (*++p == '*') {} - if (!(flags & WM_PATHNAME)) - /* without WM_PATHNAME, '*' == '**' */ - match_slash = 1; - else if ((prev_p < pattern || *prev_p == '/') && - (*p == '\0' || *p == '/' || - (p[0] == '\\' && p[1] == '/'))) { - /* - * Assuming we already match 'foo/' and are at - * , just assume it matches - * nothing and go ahead match the rest of the - * pattern with the remaining string. This - * helps make foo/<*><*>/bar (<> because - * otherwise it breaks C comment syntax) match - * both foo/bar and foo/a/bar. - */ - if (p[0] == '/' && - dowild(p + 1, text, flags) == WM_MATCH) - return WM_MATCH; - match_slash = 1; - } else /* WM_PATHNAME is set */ - match_slash = 0; - } else - /* without WM_PATHNAME, '*' == '**' */ - match_slash = flags & WM_PATHNAME ? 0 : 1; - if (*p == '\0') { - /* Trailing "**" matches everything. Trailing "*" matches - * only if there are no more slash characters. */ - if (!match_slash) { - if (strchr((char*)text, '/') != NULL) - return WM_NOMATCH; - } - return WM_MATCH; - } else if (!match_slash && *p == '/') { - /* - * _one_ asterisk followed by a slash - * with WM_PATHNAME matches the next - * directory - */ - const char *slash = strchr((char*)text, '/'); - if (!slash) - return WM_NOMATCH; - text = (const uchar*)slash; - /* the slash is consumed by the top-level for loop */ - break; - } - while (1) { - if (t_ch == '\0') - break; - /* - * Try to advance faster when an asterisk is - * followed by a literal. We know in this case - * that the string before the literal - * must belong to "*". - * If match_slash is false, do not look past - * the first slash as it cannot belong to '*'. - */ - if (!is_glob_special(*p)) { - p_ch = *p; - if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) - p_ch = tolower(p_ch); - while ((t_ch = *text) != '\0' && - (match_slash || t_ch != '/')) { - if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) - t_ch = tolower(t_ch); - if (t_ch == p_ch) - break; - text++; - } - if (t_ch != p_ch) - return WM_NOMATCH; - } - if ((matched = dowild(p, text, flags)) != WM_NOMATCH) { - if (!match_slash || matched != WM_ABORT_TO_STARSTAR) - return matched; - } else if (!match_slash && t_ch == '/') - return WM_ABORT_TO_STARSTAR; - t_ch = *++text; - } - return WM_ABORT_ALL; - case '[': - p_ch = *++p; -#ifdef NEGATE_CLASS2 - if (p_ch == NEGATE_CLASS2) - p_ch = NEGATE_CLASS; -#endif - /* Assign literal 1/0 because of "matched" comparison. */ - negated = p_ch == NEGATE_CLASS ? 1 : 0; - if (negated) { - /* Inverted character class. */ - p_ch = *++p; - } - prev_ch = 0; - matched = 0; - do { - if (!p_ch) - return WM_ABORT_ALL; - if (p_ch == '\\') { - p_ch = *++p; - if (!p_ch) - return WM_ABORT_ALL; - if (t_ch == p_ch) - matched = 1; - } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') { - p_ch = *++p; - if (p_ch == '\\') { - p_ch = *++p; - if (!p_ch) - return WM_ABORT_ALL; - } - if (t_ch <= p_ch && t_ch >= prev_ch) - matched = 1; - else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) { - uchar t_ch_upper = toupper(t_ch); - if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch) - matched = 1; - } - p_ch = 0; /* This makes "prev_ch" get set to 0. */ - } else if (p_ch == '[' && p[1] == ':') { - const uchar *s; - int i; - for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/ - if (!p_ch) - return WM_ABORT_ALL; - i = (int)(p - s - 1); - if (i < 0 || p[-1] != ':') { - /* Didn't find ":]", so treat like a normal set. */ - p = s - 2; - p_ch = '['; - if (t_ch == p_ch) - matched = 1; - continue; - } - if (CC_EQ(s,i, "alnum")) { - if (ISALNUM(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "alpha")) { - if (ISALPHA(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "blank")) { - if (ISBLANK(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "cntrl")) { - if (ISCNTRL(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "digit")) { - if (ISDIGIT(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "graph")) { - if (ISGRAPH(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "lower")) { - if (ISLOWER(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "print")) { - if (ISPRINT(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "punct")) { - if (ISPUNCT(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "space")) { - if (ISSPACE(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "upper")) { - if (ISUPPER(t_ch)) - matched = 1; - else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) - matched = 1; - } else if (CC_EQ(s,i, "xdigit")) { - if (ISXDIGIT(t_ch)) - matched = 1; - } else /* malformed [:class:] string */ - return WM_ABORT_ALL; - p_ch = 0; /* This makes "prev_ch" get set to 0. */ - } else if (t_ch == p_ch) - matched = 1; - } while (prev_ch = p_ch, (p_ch = *++p) != ']'); - if (matched == negated || - ((flags & WM_PATHNAME) && t_ch == '/')) - return WM_NOMATCH; - continue; - } - } - - return *text ? WM_NOMATCH : WM_MATCH; -} - -/* Match the "pattern" against the "text" string. */ -int wildmatch(const char *pattern, const char *text, unsigned int flags) -{ - return dowild((const uchar*)pattern, (const uchar*)text, flags); -} diff --git a/src/libgit2/wildmatch.h b/src/libgit2/wildmatch.h deleted file mode 100644 index 44bb575a6..000000000 --- a/src/libgit2/wildmatch.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_wildmatch_h__ -#define INCLUDE_wildmatch_h__ - -#include "common.h" - -#define WM_CASEFOLD 1 -#define WM_PATHNAME 2 - -#define WM_NOMATCH 1 -#define WM_MATCH 0 -#define WM_ABORT_ALL -1 -#define WM_ABORT_TO_STARSTAR -2 - -int wildmatch(const char *pattern, const char *text, unsigned int flags); - -#endif diff --git a/src/libgit2/win32/dir.c b/src/libgit2/win32/dir.c deleted file mode 100644 index 44052caf0..000000000 --- a/src/libgit2/win32/dir.c +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "dir.h" - -#define GIT__WIN32_NO_WRAP_DIR -#include "posix.h" - -git__DIR *git__opendir(const char *dir) -{ - git_win32_path filter_w; - git__DIR *new = NULL; - size_t dirlen, alloclen; - - if (!dir || !git_win32__findfirstfile_filter(filter_w, dir)) - return NULL; - - dirlen = strlen(dir); - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, sizeof(*new), dirlen) || - GIT_ADD_SIZET_OVERFLOW(&alloclen, alloclen, 1) || - !(new = git__calloc(1, alloclen))) - return NULL; - - memcpy(new->dir, dir, dirlen); - - new->h = FindFirstFileW(filter_w, &new->f); - - if (new->h == INVALID_HANDLE_VALUE) { - git_error_set(GIT_ERROR_OS, "could not open directory '%s'", dir); - git__free(new); - return NULL; - } - - new->first = 1; - return new; -} - -int git__readdir_ext( - git__DIR *d, - struct git__dirent *entry, - struct git__dirent **result, - int *is_dir) -{ - if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE) - return -1; - - *result = NULL; - - if (d->first) - d->first = 0; - else if (!FindNextFileW(d->h, &d->f)) { - if (GetLastError() == ERROR_NO_MORE_FILES) - return 0; - git_error_set(GIT_ERROR_OS, "could not read from directory '%s'", d->dir); - return -1; - } - - /* Convert the path to UTF-8 */ - if (git_win32_path_to_utf8(entry->d_name, d->f.cFileName) < 0) - return -1; - - entry->d_ino = 0; - - *result = entry; - - if (is_dir != NULL) - *is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); - - return 0; -} - -struct git__dirent *git__readdir(git__DIR *d) -{ - struct git__dirent *result; - if (git__readdir_ext(d, &d->entry, &result, NULL) < 0) - return NULL; - return result; -} - -void git__rewinddir(git__DIR *d) -{ - git_win32_path filter_w; - - if (!d) - return; - - if (d->h != INVALID_HANDLE_VALUE) { - FindClose(d->h); - d->h = INVALID_HANDLE_VALUE; - d->first = 0; - } - - if (!git_win32__findfirstfile_filter(filter_w, d->dir)) - return; - - d->h = FindFirstFileW(filter_w, &d->f); - - if (d->h == INVALID_HANDLE_VALUE) - git_error_set(GIT_ERROR_OS, "could not open directory '%s'", d->dir); - else - d->first = 1; -} - -int git__closedir(git__DIR *d) -{ - if (!d) - return 0; - - if (d->h != INVALID_HANDLE_VALUE) { - FindClose(d->h); - d->h = INVALID_HANDLE_VALUE; - } - - git__free(d); - return 0; -} - diff --git a/src/libgit2/win32/dir.h b/src/libgit2/win32/dir.h deleted file mode 100644 index acd64729e..000000000 --- a/src/libgit2/win32/dir.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_dir_h__ -#define INCLUDE_win32_dir_h__ - -#include "common.h" - -#include "w32_util.h" - -struct git__dirent { - int d_ino; - git_win32_utf8_path d_name; -}; - -typedef struct { - HANDLE h; - WIN32_FIND_DATAW f; - struct git__dirent entry; - int first; - char dir[GIT_FLEX_ARRAY]; -} git__DIR; - -extern git__DIR *git__opendir(const char *); -extern struct git__dirent *git__readdir(git__DIR *); -extern int git__readdir_ext( - git__DIR *, struct git__dirent *, struct git__dirent **, int *); -extern void git__rewinddir(git__DIR *); -extern int git__closedir(git__DIR *); - -# ifndef GIT__WIN32_NO_WRAP_DIR -# define dirent git__dirent -# define DIR git__DIR -# define opendir git__opendir -# define readdir git__readdir -# define readdir_r(d,e,r) git__readdir_ext((d),(e),(r),NULL) -# define rewinddir git__rewinddir -# define closedir git__closedir -# endif - -#endif diff --git a/src/libgit2/win32/error.c b/src/libgit2/win32/error.c deleted file mode 100644 index 3a52fb5a9..000000000 --- a/src/libgit2/win32/error.c +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "error.h" - -#include "utf-conv.h" - -#ifdef GIT_WINHTTP -# include -#endif - -char *git_win32_get_error_message(DWORD error_code) -{ - LPWSTR lpMsgBuf = NULL; - HMODULE hModule = NULL; - char *utf8_msg = NULL; - DWORD dwFlags = - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS; - - if (!error_code) - return NULL; - -#ifdef GIT_WINHTTP - /* Errors raised by WinHTTP are not in the system resource table */ - if (error_code >= WINHTTP_ERROR_BASE && - error_code <= WINHTTP_ERROR_LAST) - hModule = GetModuleHandleW(L"winhttp"); -#endif - - GIT_UNUSED(hModule); - - if (hModule) - dwFlags |= FORMAT_MESSAGE_FROM_HMODULE; - else - dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM; - - if (FormatMessageW(dwFlags, hModule, error_code, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPWSTR)&lpMsgBuf, 0, NULL)) { - /* Convert the message to UTF-8. If this fails, we will - * return NULL, which is a condition expected by the caller */ - if (git__utf16_to_8_alloc(&utf8_msg, lpMsgBuf) < 0) - utf8_msg = NULL; - - LocalFree(lpMsgBuf); - } - - return utf8_msg; -} diff --git a/src/libgit2/win32/error.h b/src/libgit2/win32/error.h deleted file mode 100644 index 9e81141ce..000000000 --- a/src/libgit2/win32/error.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_win32_error_h__ -#define INCLUDE_win32_error_h__ - -#include "common.h" - -extern char *git_win32_get_error_message(DWORD error_code); - -#endif diff --git a/src/libgit2/win32/findfile.c b/src/libgit2/win32/findfile.c deleted file mode 100644 index 725a90167..000000000 --- a/src/libgit2/win32/findfile.c +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "findfile.h" - -#include "path_w32.h" -#include "utf-conv.h" -#include "fs_path.h" - -#define REG_GITFORWINDOWS_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" -#define REG_GITFORWINDOWS_KEY_WOW64 L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" - -static int git_win32__expand_path(git_win32_path dest, const wchar_t *src) -{ - DWORD len = ExpandEnvironmentStringsW(src, dest, GIT_WIN_PATH_UTF16); - - if (!len || len > GIT_WIN_PATH_UTF16) - return -1; - - return 0; -} - -static int win32_path_to_8(git_str *dest, const wchar_t *src) -{ - git_win32_utf8_path utf8_path; - - if (git_win32_path_to_utf8(utf8_path, src) < 0) { - git_error_set(GIT_ERROR_OS, "unable to convert path to UTF-8"); - return -1; - } - - /* Convert backslashes to forward slashes */ - git_fs_path_mkposix(utf8_path); - - return git_str_sets(dest, utf8_path); -} - -static git_win32_path mock_registry; -static bool mock_registry_set; - -extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir) -{ - if (!mock_sysdir) { - mock_registry[0] = L'\0'; - mock_registry_set = false; - } else { - size_t len = wcslen(mock_sysdir); - - if (len > GIT_WIN_PATH_MAX) { - git_error_set(GIT_ERROR_INVALID, "mock path too long"); - return -1; - } - - wcscpy(mock_registry, mock_sysdir); - mock_registry_set = true; - } - - return 0; -} - -static int lookup_registry_key( - git_win32_path out, - const HKEY hive, - const wchar_t* key, - const wchar_t *value) -{ - HKEY hkey; - DWORD type, size; - int error = GIT_ENOTFOUND; - - /* - * Registry data may not be NUL terminated, provide room to do - * it ourselves. - */ - size = (DWORD)((sizeof(git_win32_path) - 1) * sizeof(wchar_t)); - - if (RegOpenKeyExW(hive, key, 0, KEY_READ, &hkey) != 0) - return GIT_ENOTFOUND; - - if (RegQueryValueExW(hkey, value, NULL, &type, (LPBYTE)out, &size) == 0 && - type == REG_SZ && - size > 0 && - size < sizeof(git_win32_path)) { - size_t wsize = size / sizeof(wchar_t); - size_t len = wsize - 1; - - if (out[wsize - 1] != L'\0') { - len = wsize; - out[wsize] = L'\0'; - } - - if (out[len - 1] == L'\\') - out[len - 1] = L'\0'; - - if (_waccess(out, F_OK) == 0) - error = 0; - } - - RegCloseKey(hkey); - return error; -} - -static int find_sysdir_in_registry(git_win32_path out) -{ - if (mock_registry_set) { - if (mock_registry[0] == L'\0') - return GIT_ENOTFOUND; - - wcscpy(out, mock_registry); - return 0; - } - - if (lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || - lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0 || - lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || - lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0) - return 0; - - return GIT_ENOTFOUND; -} - -static int find_sysdir_in_path(git_win32_path out) -{ - size_t out_len; - - if (git_win32_path_find_executable(out, L"git.exe") < 0 && - git_win32_path_find_executable(out, L"git.cmd") < 0) - return GIT_ENOTFOUND; - - out_len = wcslen(out); - - /* Trim the file name */ - if (out_len <= CONST_STRLEN(L"git.exe")) - return GIT_ENOTFOUND; - - out_len -= CONST_STRLEN(L"git.exe"); - - if (out_len && out[out_len - 1] == L'\\') - out_len--; - - /* - * Git for Windows usually places the command in a 'bin' or - * 'cmd' directory, trim that. - */ - if (out_len >= CONST_STRLEN(L"\\bin") && - wcsncmp(&out[out_len - CONST_STRLEN(L"\\bin")], L"\\bin", CONST_STRLEN(L"\\bin")) == 0) - out_len -= CONST_STRLEN(L"\\bin"); - else if (out_len >= CONST_STRLEN(L"\\cmd") && - wcsncmp(&out[out_len - CONST_STRLEN(L"\\cmd")], L"\\cmd", CONST_STRLEN(L"\\cmd")) == 0) - out_len -= CONST_STRLEN(L"\\cmd"); - - if (!out_len) - return GIT_ENOTFOUND; - - out[out_len] = L'\0'; - return 0; -} - -static int win32_find_existing_dirs( - git_str* out, - const wchar_t* tmpl[]) -{ - git_win32_path path16; - git_str buf = GIT_STR_INIT; - - git_str_clear(out); - - for (; *tmpl != NULL; tmpl++) { - if (!git_win32__expand_path(path16, *tmpl) && - path16[0] != L'%' && - !_waccess(path16, F_OK)) { - win32_path_to_8(&buf, path16); - - if (buf.size) - git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); - } - } - - git_str_dispose(&buf); - - return (git_str_oom(out) ? -1 : 0); -} - -static int append_subdir(git_str *out, git_str *path, const char *subdir) -{ - static const char* architecture_roots[] = { - "", - "mingw64", - "mingw32", - NULL - }; - const char **root; - size_t orig_path_len = path->size; - - for (root = architecture_roots; *root; root++) { - if ((*root[0] && git_str_joinpath(path, path->ptr, *root) < 0) || - git_str_joinpath(path, path->ptr, subdir) < 0) - return -1; - - if (git_fs_path_exists(path->ptr) && - git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, path->ptr) < 0) - return -1; - - git_str_truncate(path, orig_path_len); - } - - return 0; -} - -int git_win32__find_system_dirs(git_str *out, const char *subdir) -{ - git_win32_path pathdir, regdir; - git_str path8 = GIT_STR_INIT; - bool has_pathdir, has_regdir; - int error; - - has_pathdir = (find_sysdir_in_path(pathdir) == 0); - has_regdir = (find_sysdir_in_registry(regdir) == 0); - - if (!has_pathdir && !has_regdir) - return 0; - - /* - * Usually the git in the path is the same git in the registry, - * in this case there's no need to duplicate the paths. - */ - if (has_pathdir && has_regdir && wcscmp(pathdir, regdir) == 0) - has_regdir = false; - - if (has_pathdir) { - if ((error = win32_path_to_8(&path8, pathdir)) < 0 || - (error = append_subdir(out, &path8, subdir)) < 0) - goto done; - } - - if (has_regdir) { - if ((error = win32_path_to_8(&path8, regdir)) < 0 || - (error = append_subdir(out, &path8, subdir)) < 0) - goto done; - } - -done: - git_str_dispose(&path8); - return error; -} - -int git_win32__find_global_dirs(git_str *out) -{ - static const wchar_t *global_tmpls[4] = { - L"%HOME%\\", - L"%HOMEDRIVE%%HOMEPATH%\\", - L"%USERPROFILE%\\", - NULL, - }; - - return win32_find_existing_dirs(out, global_tmpls); -} - -int git_win32__find_xdg_dirs(git_str *out) -{ - static const wchar_t *global_tmpls[7] = { - L"%XDG_CONFIG_HOME%\\git", - L"%APPDATA%\\git", - L"%LOCALAPPDATA%\\git", - L"%HOME%\\.config\\git", - L"%HOMEDRIVE%%HOMEPATH%\\.config\\git", - L"%USERPROFILE%\\.config\\git", - NULL, - }; - - return win32_find_existing_dirs(out, global_tmpls); -} - -int git_win32__find_programdata_dirs(git_str *out) -{ - static const wchar_t *programdata_tmpls[2] = { - L"%PROGRAMDATA%\\Git", - NULL, - }; - - return win32_find_existing_dirs(out, programdata_tmpls); -} diff --git a/src/libgit2/win32/findfile.h b/src/libgit2/win32/findfile.h deleted file mode 100644 index 61fb7dbad..000000000 --- a/src/libgit2/win32/findfile.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_win32_findfile_h__ -#define INCLUDE_win32_findfile_h__ - -#include "common.h" - -/** Sets the mock registry root for Git for Windows for testing. */ -extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir); - -extern int git_win32__find_system_dirs(git_str *out, const char *subpath); -extern int git_win32__find_global_dirs(git_str *out); -extern int git_win32__find_xdg_dirs(git_str *out); -extern int git_win32__find_programdata_dirs(git_str *out); - -#endif - diff --git a/src/libgit2/win32/git2.rc b/src/libgit2/win32/git2.rc deleted file mode 100644 index 3f97239da..000000000 --- a/src/libgit2/win32/git2.rc +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include "../../../include/git2/version.h" - -#ifndef LIBGIT2_FILENAME -# ifdef __GNUC__ -# define LIBGIT2_FILENAME git2 -# else -# define LIBGIT2_FILENAME "git2" -# endif -#endif - -#ifndef LIBGIT2_COMMENTS -# define LIBGIT2_COMMENTS "For more information visit http://libgit2.github.com/" -#endif - -#ifdef __GNUC__ -# define _STR(x) #x -# define STR(x) _STR(x) -#else -# define STR(x) x -#endif - -#ifdef __GNUC__ -VS_VERSION_INFO VERSIONINFO -#else -VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE -#endif - FILEVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH - PRODUCTVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,LIBGIT2_VER_PATCH - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0 -#endif - FILEOS VOS_NT_WINDOWS32 - FILETYPE VFT_DLL - FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904E4" - //language ID = U.S. English, char set = Windows, Multilingual - BEGIN - VALUE "FileDescription", "libgit2 - the Git linkable library\0" - VALUE "FileVersion", LIBGIT2_VERSION "\0" - VALUE "InternalName", STR(LIBGIT2_FILENAME) ".dll\0" - VALUE "LegalCopyright", "Copyright (C) the libgit2 contributors. All rights reserved.\0" - VALUE "OriginalFilename", STR(LIBGIT2_FILENAME) ".dll\0" - VALUE "ProductName", "libgit2\0" - VALUE "ProductVersion", LIBGIT2_VERSION "\0" - VALUE "Comments", LIBGIT2_COMMENTS "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x0409, 1252 - END -END diff --git a/src/libgit2/win32/map.c b/src/libgit2/win32/map.c deleted file mode 100644 index 2aabc9b15..000000000 --- a/src/libgit2/win32/map.c +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "map.h" -#include - -#ifndef NO_MMAP - -static DWORD get_page_size(void) -{ - static DWORD page_size; - SYSTEM_INFO sys; - - if (!page_size) { - GetSystemInfo(&sys); - page_size = sys.dwPageSize; - } - - return page_size; -} - -static DWORD get_allocation_granularity(void) -{ - static DWORD granularity; - SYSTEM_INFO sys; - - if (!granularity) { - GetSystemInfo(&sys); - granularity = sys.dwAllocationGranularity; - } - - return granularity; -} - -int git__page_size(size_t *page_size) -{ - *page_size = get_page_size(); - return 0; -} - -int git__mmap_alignment(size_t *page_size) -{ - *page_size = get_allocation_granularity(); - return 0; -} - -int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) -{ - HANDLE fh = (HANDLE)_get_osfhandle(fd); - DWORD alignment = get_allocation_granularity(); - DWORD fmap_prot = 0; - DWORD view_prot = 0; - DWORD off_low = 0; - DWORD off_hi = 0; - off64_t page_start; - off64_t page_offset; - - GIT_MMAP_VALIDATE(out, len, prot, flags); - - out->data = NULL; - out->len = 0; - out->fmh = NULL; - - if (fh == INVALID_HANDLE_VALUE) { - errno = EBADF; - git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value"); - return -1; - } - - if (prot & GIT_PROT_WRITE) - fmap_prot |= PAGE_READWRITE; - else if (prot & GIT_PROT_READ) - fmap_prot |= PAGE_READONLY; - - if (prot & GIT_PROT_WRITE) - view_prot |= FILE_MAP_WRITE; - if (prot & GIT_PROT_READ) - view_prot |= FILE_MAP_READ; - - page_start = (offset / alignment) * alignment; - page_offset = offset - page_start; - - if (page_offset != 0) { /* offset must be multiple of the allocation granularity */ - errno = EINVAL; - git_error_set(GIT_ERROR_OS, "failed to mmap. Offset must be multiple of allocation granularity"); - return -1; - } - - out->fmh = CreateFileMapping(fh, NULL, fmap_prot, 0, 0, NULL); - if (!out->fmh || out->fmh == INVALID_HANDLE_VALUE) { - git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value"); - out->fmh = NULL; - return -1; - } - - off_low = (DWORD)(page_start); - off_hi = (DWORD)(page_start >> 32); - out->data = MapViewOfFile(out->fmh, view_prot, off_hi, off_low, len); - if (!out->data) { - git_error_set(GIT_ERROR_OS, "failed to mmap. No data written"); - CloseHandle(out->fmh); - out->fmh = NULL; - return -1; - } - out->len = len; - - return 0; -} - -int p_munmap(git_map *map) -{ - int error = 0; - - GIT_ASSERT_ARG(map); - - if (map->data) { - if (!UnmapViewOfFile(map->data)) { - git_error_set(GIT_ERROR_OS, "failed to munmap. Could not unmap view of file"); - error = -1; - } - map->data = NULL; - } - - if (map->fmh) { - if (!CloseHandle(map->fmh)) { - git_error_set(GIT_ERROR_OS, "failed to munmap. Could not close handle"); - error = -1; - } - map->fmh = NULL; - } - - return error; -} - -#endif diff --git a/src/libgit2/win32/mingw-compat.h b/src/libgit2/win32/mingw-compat.h deleted file mode 100644 index aa2bef98d..000000000 --- a/src/libgit2/win32/mingw-compat.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_mingw_compat_h__ -#define INCLUDE_win32_mingw_compat_h__ - -#if defined(__MINGW32__) - -#undef stat - -#if _WIN32_WINNT < 0x0600 && !defined(__MINGW64_VERSION_MAJOR) -#undef MemoryBarrier -void __mingworg_MemoryBarrier(void); -#define MemoryBarrier __mingworg_MemoryBarrier -#define VOLUME_NAME_DOS 0x0 -#endif - -#endif - -#endif diff --git a/src/libgit2/win32/msvc-compat.h b/src/libgit2/win32/msvc-compat.h deleted file mode 100644 index 03f9f36dc..000000000 --- a/src/libgit2/win32/msvc-compat.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_msvc_compat_h__ -#define INCLUDE_win32_msvc_compat_h__ - -#if defined(_MSC_VER) - -typedef unsigned short mode_t; -typedef SSIZE_T ssize_t; - -#ifdef _WIN64 -# define SSIZE_MAX _I64_MAX -#else -# define SSIZE_MAX LONG_MAX -#endif - -#define strcasecmp(s1, s2) _stricmp(s1, s2) -#define strncasecmp(s1, s2, c) _strnicmp(s1, s2, c) - -#endif - -/* - * Offer GIT_LIBGIT2_CALL for our calling conventions (__cdecl, always). - * This is useful for providing callbacks to userspace code. - * - * Offer GIT_SYSTEM_CALL for the system calling conventions (__stdcall on - * Win32). Useful for providing callbacks to system libraries. - */ -#define GIT_LIBGIT2_CALL __cdecl -#define GIT_SYSTEM_CALL NTAPI - -#endif diff --git a/src/libgit2/win32/path_w32.c b/src/libgit2/win32/path_w32.c deleted file mode 100644 index d9fc8292b..000000000 --- a/src/libgit2/win32/path_w32.c +++ /dev/null @@ -1,642 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "path_w32.h" - -#include "fs_path.h" -#include "utf-conv.h" -#include "posix.h" -#include "reparse.h" -#include "dir.h" - -#define PATH__NT_NAMESPACE L"\\\\?\\" -#define PATH__NT_NAMESPACE_LEN 4 - -#define PATH__ABSOLUTE_LEN 3 - -#define path__is_nt_namespace(p) \ - (((p)[0] == '\\' && (p)[1] == '\\' && (p)[2] == '?' && (p)[3] == '\\') || \ - ((p)[0] == '/' && (p)[1] == '/' && (p)[2] == '?' && (p)[3] == '/')) - -#define path__is_unc(p) \ - (((p)[0] == '\\' && (p)[1] == '\\') || ((p)[0] == '/' && (p)[1] == '/')) - -#define path__startswith_slash(p) \ - ((p)[0] == '\\' || (p)[0] == '/') - -GIT_INLINE(int) path__cwd(wchar_t *path, int size) -{ - int len; - - if ((len = GetCurrentDirectoryW(size, path)) == 0) { - errno = GetLastError() == ERROR_ACCESS_DENIED ? EACCES : ENOENT; - return -1; - } else if (len > size) { - errno = ENAMETOOLONG; - return -1; - } - - /* The Win32 APIs may return "\\?\" once you've used it first. - * But it may not. What a gloriously predictable API! - */ - if (wcsncmp(path, PATH__NT_NAMESPACE, PATH__NT_NAMESPACE_LEN)) - return len; - - len -= PATH__NT_NAMESPACE_LEN; - - memmove(path, path + PATH__NT_NAMESPACE_LEN, sizeof(wchar_t) * len); - return len; -} - -static wchar_t *path__skip_server(wchar_t *path) -{ - wchar_t *c; - - for (c = path; *c; c++) { - if (git_fs_path_is_dirsep(*c)) - return c + 1; - } - - return c; -} - -static wchar_t *path__skip_prefix(wchar_t *path) -{ - if (path__is_nt_namespace(path)) { - path += PATH__NT_NAMESPACE_LEN; - - if (wcsncmp(path, L"UNC\\", 4) == 0) - path = path__skip_server(path + 4); - else if (git_fs_path_is_absolute(path)) - path += PATH__ABSOLUTE_LEN; - } else if (git_fs_path_is_absolute(path)) { - path += PATH__ABSOLUTE_LEN; - } else if (path__is_unc(path)) { - path = path__skip_server(path + 2); - } - - return path; -} - -int git_win32_path_canonicalize(git_win32_path path) -{ - wchar_t *base, *from, *to, *next; - size_t len; - - base = to = path__skip_prefix(path); - - /* Unposixify if the prefix */ - for (from = path; from < to; from++) { - if (*from == L'/') - *from = L'\\'; - } - - while (*from) { - for (next = from; *next; ++next) { - if (*next == L'/') { - *next = L'\\'; - break; - } - - if (*next == L'\\') - break; - } - - len = next - from; - - if (len == 1 && from[0] == L'.') - /* do nothing with singleton dot */; - - else if (len == 2 && from[0] == L'.' && from[1] == L'.') { - if (to == base) { - /* no more path segments to strip, eat the "../" */ - if (*next == L'\\') - len++; - - base = to; - } else { - /* back up a path segment */ - while (to > base && to[-1] == L'\\') to--; - while (to > base && to[-1] != L'\\') to--; - } - } else { - if (*next == L'\\' && *from != L'\\') - len++; - - if (to != from) - memmove(to, from, sizeof(wchar_t) * len); - - to += len; - } - - from += len; - - while (*from == L'\\') from++; - } - - /* Strip trailing backslashes */ - while (to > base && to[-1] == L'\\') to--; - - *to = L'\0'; - - if ((to - path) > INT_MAX) { - SetLastError(ERROR_FILENAME_EXCED_RANGE); - return -1; - } - - return (int)(to - path); -} - -static int git_win32_path_join( - git_win32_path dest, - const wchar_t *one, - size_t one_len, - const wchar_t *two, - size_t two_len) -{ - size_t backslash = 0; - - if (one_len && two_len && one[one_len - 1] != L'\\') - backslash = 1; - - if (one_len + two_len + backslash > MAX_PATH) { - git_error_set(GIT_ERROR_INVALID, "path too long"); - return -1; - } - - memmove(dest, one, one_len * sizeof(wchar_t)); - - if (backslash) - dest[one_len] = L'\\'; - - memcpy(dest + one_len + backslash, two, two_len * sizeof(wchar_t)); - dest[one_len + backslash + two_len] = L'\0'; - - return 0; -} - -struct win32_path_iter { - wchar_t *env; - const wchar_t *current_dir; -}; - -static int win32_path_iter_init(struct win32_path_iter *iter) -{ - DWORD len = GetEnvironmentVariableW(L"PATH", NULL, 0); - - if (!len && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { - iter->env = NULL; - iter->current_dir = NULL; - return 0; - } else if (!len) { - git_error_set(GIT_ERROR_OS, "could not load PATH"); - return -1; - } - - iter->env = git__malloc(len * sizeof(wchar_t)); - GIT_ERROR_CHECK_ALLOC(iter->env); - - len = GetEnvironmentVariableW(L"PATH", iter->env, len); - - if (len == 0) { - git_error_set(GIT_ERROR_OS, "could not load PATH"); - return -1; - } - - iter->current_dir = iter->env; - return 0; -} - -static int win32_path_iter_next( - const wchar_t **out, - size_t *out_len, - struct win32_path_iter *iter) -{ - const wchar_t *start; - wchar_t term; - size_t len = 0; - - if (!iter->current_dir || !*iter->current_dir) - return GIT_ITEROVER; - - term = (*iter->current_dir == L'"') ? *iter->current_dir++ : L';'; - start = iter->current_dir; - - while (*iter->current_dir && *iter->current_dir != term) { - iter->current_dir++; - len++; - } - - *out = start; - *out_len = len; - - if (term == L'"' && *iter->current_dir) - iter->current_dir++; - - while (*iter->current_dir == L';') - iter->current_dir++; - - return 0; -} - -static void win32_path_iter_dispose(struct win32_path_iter *iter) -{ - if (!iter) - return; - - git__free(iter->env); - iter->env = NULL; - iter->current_dir = NULL; -} - -int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe) -{ - struct win32_path_iter path_iter; - const wchar_t *dir; - size_t dir_len, exe_len = wcslen(exe); - bool found = false; - - if (win32_path_iter_init(&path_iter) < 0) - return -1; - - while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER) { - if (git_win32_path_join(fullpath, dir, dir_len, exe, exe_len) < 0) - continue; - - if (_waccess(fullpath, 0) == 0) { - found = true; - break; - } - } - - win32_path_iter_dispose(&path_iter); - - if (found) - return 0; - - fullpath[0] = L'\0'; - return GIT_ENOTFOUND; -} - -static int win32_path_cwd(wchar_t *out, size_t len) -{ - int cwd_len; - - if (len > INT_MAX) { - errno = ENAMETOOLONG; - return -1; - } - - if ((cwd_len = path__cwd(out, (int)len)) < 0) - return -1; - - /* UNC paths */ - if (wcsncmp(L"\\\\", out, 2) == 0) { - /* Our buffer must be at least 5 characters larger than the - * current working directory: we swallow one of the leading - * '\'s, but we we add a 'UNC' specifier to the path, plus - * a trailing directory separator, plus a NUL. - */ - if (cwd_len > GIT_WIN_PATH_MAX - 4) { - errno = ENAMETOOLONG; - return -1; - } - - memmove(out+2, out, sizeof(wchar_t) * cwd_len); - out[0] = L'U'; - out[1] = L'N'; - out[2] = L'C'; - - cwd_len += 2; - } - - /* Our buffer must be at least 2 characters larger than the current - * working directory. (One character for the directory separator, - * one for the null. - */ - else if (cwd_len > GIT_WIN_PATH_MAX - 2) { - errno = ENAMETOOLONG; - return -1; - } - - return cwd_len; -} - -int git_win32_path_from_utf8(git_win32_path out, const char *src) -{ - wchar_t *dest = out; - - /* All win32 paths are in NT-prefixed format, beginning with "\\?\". */ - memcpy(dest, PATH__NT_NAMESPACE, sizeof(wchar_t) * PATH__NT_NAMESPACE_LEN); - dest += PATH__NT_NAMESPACE_LEN; - - /* See if this is an absolute path (beginning with a drive letter) */ - if (git_fs_path_is_absolute(src)) { - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0) - goto on_error; - } - /* File-prefixed NT-style paths beginning with \\?\ */ - else if (path__is_nt_namespace(src)) { - /* Skip the NT prefix, the destination already contains it */ - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0) - goto on_error; - } - /* UNC paths */ - else if (path__is_unc(src)) { - memcpy(dest, L"UNC\\", sizeof(wchar_t) * 4); - dest += 4; - - /* Skip the leading "\\" */ - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0) - goto on_error; - } - /* Absolute paths omitting the drive letter */ - else if (path__startswith_slash(src)) { - if (path__cwd(dest, GIT_WIN_PATH_MAX) < 0) - goto on_error; - - if (!git_fs_path_is_absolute(dest)) { - errno = ENOENT; - goto on_error; - } - - /* Skip the drive letter specification ("C:") */ - if (git__utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0) - goto on_error; - } - /* Relative paths */ - else { - int cwd_len; - - if ((cwd_len = win32_path_cwd(dest, GIT_WIN_PATH_MAX)) < 0) - goto on_error; - - dest[cwd_len++] = L'\\'; - - if (git__utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0) - goto on_error; - } - - return git_win32_path_canonicalize(out); - -on_error: - /* set windows error code so we can use its error message */ - if (errno == ENAMETOOLONG) - SetLastError(ERROR_FILENAME_EXCED_RANGE); - - return -1; -} - -int git_win32_path_relative_from_utf8(git_win32_path out, const char *src) -{ - wchar_t *dest = out, *p; - int len; - - /* Handle absolute paths */ - if (git_fs_path_is_absolute(src) || - path__is_nt_namespace(src) || - path__is_unc(src) || - path__startswith_slash(src)) { - return git_win32_path_from_utf8(out, src); - } - - if ((len = git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src)) < 0) - return -1; - - for (p = dest; p < (dest + len); p++) { - if (*p == L'/') - *p = L'\\'; - } - - return len; -} - -int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src) -{ - char *out = dest; - int len; - - /* Strip NT namespacing "\\?\" */ - if (path__is_nt_namespace(src)) { - src += 4; - - /* "\\?\UNC\server\share" -> "\\server\share" */ - if (wcsncmp(src, L"UNC\\", 4) == 0) { - src += 4; - - memcpy(dest, "\\\\", 2); - out = dest + 2; - } - } - - if ((len = git__utf16_to_8(out, GIT_WIN_PATH_UTF8, src)) < 0) - return len; - - git_fs_path_mkposix(dest); - - return len; -} - -char *git_win32_path_8dot3_name(const char *path) -{ - git_win32_path longpath, shortpath; - wchar_t *start; - char *shortname; - int len, namelen = 1; - - if (git_win32_path_from_utf8(longpath, path) < 0) - return NULL; - - len = GetShortPathNameW(longpath, shortpath, GIT_WIN_PATH_UTF16); - - while (len && shortpath[len-1] == L'\\') - shortpath[--len] = L'\0'; - - if (len == 0 || len >= GIT_WIN_PATH_UTF16) - return NULL; - - for (start = shortpath + (len - 1); - start > shortpath && *(start-1) != '/' && *(start-1) != '\\'; - start--) - namelen++; - - /* We may not have actually been given a short name. But if we have, - * it will be in the ASCII byte range, so we don't need to worry about - * multi-byte sequences and can allocate naively. - */ - if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL) - return NULL; - - if ((len = git__utf16_to_8(shortname, namelen + 1, start)) < 0) - return NULL; - - return shortname; -} - -static bool path_is_volume(wchar_t *target, size_t target_len) -{ - return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0); -} - -/* On success, returns the length, in characters, of the path stored in dest. - * On failure, returns a negative value. */ -int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path) -{ - BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; - GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf; - HANDLE handle = NULL; - DWORD ioctl_ret; - wchar_t *target; - size_t target_len; - - int error = -1; - - handle = CreateFileW(path, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); - - if (handle == INVALID_HANDLE_VALUE) { - errno = ENOENT; - return -1; - } - - if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, - reparse_buf, sizeof(buf), &ioctl_ret, NULL)) { - errno = EINVAL; - goto on_error; - } - - switch (reparse_buf->ReparseTag) { - case IO_REPARSE_TAG_SYMLINK: - target = reparse_buf->ReparseBuffer.SymbolicLink.PathBuffer + - (reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameOffset / sizeof(WCHAR)); - target_len = reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameLength / sizeof(WCHAR); - break; - case IO_REPARSE_TAG_MOUNT_POINT: - target = reparse_buf->ReparseBuffer.MountPoint.PathBuffer + - (reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset / sizeof(WCHAR)); - target_len = reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength / sizeof(WCHAR); - break; - default: - errno = EINVAL; - goto on_error; - } - - if (path_is_volume(target, target_len)) { - /* This path is a reparse point that represents another volume mounted - * at this location, it is not a symbolic link our input was canonical. - */ - errno = EINVAL; - error = -1; - } else if (target_len) { - /* The path may need to have a namespace prefix removed. */ - target_len = git_win32_path_remove_namespace(target, target_len); - - /* Need one additional character in the target buffer - * for the terminating NULL. */ - if (GIT_WIN_PATH_UTF16 > target_len) { - wcscpy(dest, target); - error = (int)target_len; - } - } - -on_error: - CloseHandle(handle); - return error; -} - -/** - * Removes any trailing backslashes from a path, except in the case of a drive - * letter path (C:\, D:\, etc.). This function cannot fail. - * - * @param path The path which should be trimmed. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_trim_end(wchar_t *str, size_t len) -{ - while (1) { - if (!len || str[len - 1] != L'\\') - break; - - /* - * Don't trim backslashes from drive letter paths, which - * are 3 characters long and of the form C:\, D:\, etc. - */ - if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':') - break; - - len--; - } - - str[len] = L'\0'; - - return len; -} - -/** - * Removes any of the following namespace prefixes from a path, - * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. - * - * @param path The path which should be converted. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_remove_namespace(wchar_t *str, size_t len) -{ - static const wchar_t dosdevices_namespace[] = L"\\\?\?\\"; - static const wchar_t nt_namespace[] = L"\\\\?\\"; - static const wchar_t unc_namespace_remainder[] = L"UNC\\"; - static const wchar_t unc_prefix[] = L"\\\\"; - - const wchar_t *prefix = NULL, *remainder = NULL; - size_t prefix_len = 0, remainder_len = 0; - - /* "\??\" -- DOS Devices prefix */ - if (len >= CONST_STRLEN(dosdevices_namespace) && - !wcsncmp(str, dosdevices_namespace, CONST_STRLEN(dosdevices_namespace))) { - remainder = str + CONST_STRLEN(dosdevices_namespace); - remainder_len = len - CONST_STRLEN(dosdevices_namespace); - } - /* "\\?\" -- NT namespace prefix */ - else if (len >= CONST_STRLEN(nt_namespace) && - !wcsncmp(str, nt_namespace, CONST_STRLEN(nt_namespace))) { - remainder = str + CONST_STRLEN(nt_namespace); - remainder_len = len - CONST_STRLEN(nt_namespace); - } - - /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */ - if (remainder_len >= CONST_STRLEN(unc_namespace_remainder) && - !wcsncmp(remainder, unc_namespace_remainder, CONST_STRLEN(unc_namespace_remainder))) { - - /* - * The proper Win32 path for a UNC share has "\\" at beginning of it - * and looks like "\\server\share\". So remove the - * UNC namespace and add a prefix of "\\" in its place. - */ - remainder += CONST_STRLEN(unc_namespace_remainder); - remainder_len -= CONST_STRLEN(unc_namespace_remainder); - - prefix = unc_prefix; - prefix_len = CONST_STRLEN(unc_prefix); - } - - /* - * Sanity check that the new string isn't longer than the old one. - * (This could only happen due to programmer error introducing a - * prefix longer than the namespace it replaces.) - */ - if (remainder && len >= remainder_len + prefix_len) { - if (prefix) - memmove(str, prefix, prefix_len * sizeof(wchar_t)); - - memmove(str + prefix_len, remainder, remainder_len * sizeof(wchar_t)); - - len = remainder_len + prefix_len; - str[len] = L'\0'; - } - - return git_win32_path_trim_end(str, len); -} diff --git a/src/libgit2/win32/path_w32.h b/src/libgit2/win32/path_w32.h deleted file mode 100644 index 837b11ebd..000000000 --- a/src/libgit2/win32/path_w32.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_path_w32_h__ -#define INCLUDE_win32_path_w32_h__ - -#include "common.h" - -/** - * Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given - * path is relative, then it will be turned into an absolute path by having - * the current working directory prepended. - * - * @param dest The buffer to receive the wide string. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -extern int git_win32_path_from_utf8(git_win32_path dest, const char *src); - -/** - * Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given - * path is relative, then it will not be turned into an absolute path. - * - * @param dest The buffer to receive the wide string. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -extern int git_win32_path_relative_from_utf8(git_win32_path dest, const char *src); - -/** - * Canonicalize a Win32 UCS-2 path so that it is suitable for delivery to the - * Win32 APIs: remove multiple directory separators, squashing to a single one, - * strip trailing directory separators, ensure directory separators are all - * canonical (always backslashes, never forward slashes) and process any - * directory entries of '.' or '..'. - * - * Note that this is intended to be used on absolute Windows paths, those - * that start with `C:\`, `\\server\share`, `\\?\`, etc. - * - * This processes the buffer in place. - * - * @param path The buffer to process - * @return The new length of the buffer, in wchar_t's (not counting the NULL terminator) - */ -extern int git_win32_path_canonicalize(git_win32_path path); - -/** - * Create an internal format (posix-style) UTF-8 path from a Win32 UCS-2 path. - * - * @param dest The buffer to receive the UTF-8 string. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -extern int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src); - -/** - * Get the short name for the terminal path component in the given path. - * For example, given "C:\Foo\Bar\Asdf.txt", this will return the short name - * for the file "Asdf.txt". - * - * @param path The given path in UTF-8 - * @return The name of the shortname for the given path - */ -extern char *git_win32_path_8dot3_name(const char *path); - -extern int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path); - -/** - * Removes any trailing backslashes from a path, except in the case of a drive - * letter path (C:\, D:\, etc.). This function cannot fail. - * - * @param path The path which should be trimmed. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_trim_end(wchar_t *str, size_t len); - -/** - * Removes any of the following namespace prefixes from a path, - * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. - * - * @param path The path which should be converted. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_remove_namespace(wchar_t *str, size_t len); - -int git_win32_path_find_executable(git_win32_path fullpath, wchar_t* exe); - -#endif diff --git a/src/libgit2/win32/posix.h b/src/libgit2/win32/posix.h deleted file mode 100644 index 578347f15..000000000 --- a/src/libgit2/win32/posix.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_posix_h__ -#define INCLUDE_win32_posix_h__ - -#include "common.h" -#include "../posix.h" -#include "win32-compat.h" -#include "path_w32.h" -#include "utf-conv.h" -#include "dir.h" - -extern unsigned long git_win32__createfile_sharemode; -extern int git_win32__retries; - -typedef SOCKET GIT_SOCKET; - -#define p_lseek(f,n,w) _lseeki64(f, n, w) - -extern int p_fstat(int fd, struct stat *buf); -extern int p_lstat(const char *file_name, struct stat *buf); -extern int p_stat(const char *path, struct stat *buf); - -extern int p_utimes(const char *filename, const struct p_timeval times[2]); -extern int p_futimes(int fd, const struct p_timeval times[2]); - -extern int p_readlink(const char *path, char *buf, size_t bufsiz); -extern int p_symlink(const char *old, const char *new); -extern int p_link(const char *old, const char *new); -extern int p_unlink(const char *path); -extern int p_mkdir(const char *path, mode_t mode); -extern int p_fsync(int fd); -extern char *p_realpath(const char *orig_path, char *buffer); - -extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags); -extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags); -extern int p_inet_pton(int af, const char *src, void* dst); - -extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr); -extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4); -extern int p_chdir(const char *path); -extern int p_chmod(const char *path, mode_t mode); -extern int p_rmdir(const char *path); -extern int p_access(const char *path, mode_t mode); -extern int p_ftruncate(int fd, off64_t size); - -/* p_lstat is almost but not quite POSIX correct. Specifically, the use of - * ENOTDIR is wrong, in that it does not mean precisely that a non-directory - * entry was encountered. Making it correct is potentially expensive, - * however, so this is a separate version of p_lstat to use when correct - * POSIX ENOTDIR semantics is required. - */ -extern int p_lstat_posixly(const char *filename, struct stat *buf); - -extern struct tm * p_localtime_r(const time_t *timer, struct tm *result); -extern struct tm * p_gmtime_r(const time_t *timer, struct tm *result); - -#endif diff --git a/src/libgit2/win32/posix_w32.c b/src/libgit2/win32/posix_w32.c deleted file mode 100644 index 5f7cd0c26..000000000 --- a/src/libgit2/win32/posix_w32.c +++ /dev/null @@ -1,1047 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" - -#include "../posix.h" -#include "../futils.h" -#include "fs_path.h" -#include "path_w32.h" -#include "utf-conv.h" -#include "reparse.h" -#include -#include -#include -#include - -#ifndef FILE_NAME_NORMALIZED -# define FILE_NAME_NORMALIZED 0 -#endif - -#ifndef IO_REPARSE_TAG_SYMLINK -#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) -#endif - -#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE -# define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x02 -#endif - -#ifndef SYMBOLIC_LINK_FLAG_DIRECTORY -# define SYMBOLIC_LINK_FLAG_DIRECTORY 0x01 -#endif - -/* Allowable mode bits on Win32. Using mode bits that are not supported on - * Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it - * so we simply remove them. - */ -#define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE) - -unsigned long git_win32__createfile_sharemode = - FILE_SHARE_READ | FILE_SHARE_WRITE; -int git_win32__retries = 10; - -GIT_INLINE(void) set_errno(void) -{ - switch (GetLastError()) { - case ERROR_FILE_NOT_FOUND: - case ERROR_PATH_NOT_FOUND: - case ERROR_INVALID_DRIVE: - case ERROR_NO_MORE_FILES: - case ERROR_BAD_NETPATH: - case ERROR_BAD_NET_NAME: - case ERROR_BAD_PATHNAME: - case ERROR_FILENAME_EXCED_RANGE: - errno = ENOENT; - break; - case ERROR_BAD_ENVIRONMENT: - errno = E2BIG; - break; - case ERROR_BAD_FORMAT: - case ERROR_INVALID_STARTING_CODESEG: - case ERROR_INVALID_STACKSEG: - case ERROR_INVALID_MODULETYPE: - case ERROR_INVALID_EXE_SIGNATURE: - case ERROR_EXE_MARKED_INVALID: - case ERROR_BAD_EXE_FORMAT: - case ERROR_ITERATED_DATA_EXCEEDS_64k: - case ERROR_INVALID_MINALLOCSIZE: - case ERROR_DYNLINK_FROM_INVALID_RING: - case ERROR_IOPL_NOT_ENABLED: - case ERROR_INVALID_SEGDPL: - case ERROR_AUTODATASEG_EXCEEDS_64k: - case ERROR_RING2SEG_MUST_BE_MOVABLE: - case ERROR_RELOC_CHAIN_XEEDS_SEGLIM: - case ERROR_INFLOOP_IN_RELOC_CHAIN: - errno = ENOEXEC; - break; - case ERROR_INVALID_HANDLE: - case ERROR_INVALID_TARGET_HANDLE: - case ERROR_DIRECT_ACCESS_HANDLE: - errno = EBADF; - break; - case ERROR_WAIT_NO_CHILDREN: - case ERROR_CHILD_NOT_COMPLETE: - errno = ECHILD; - break; - case ERROR_NO_PROC_SLOTS: - case ERROR_MAX_THRDS_REACHED: - case ERROR_NESTING_NOT_ALLOWED: - errno = EAGAIN; - break; - case ERROR_ARENA_TRASHED: - case ERROR_NOT_ENOUGH_MEMORY: - case ERROR_INVALID_BLOCK: - case ERROR_NOT_ENOUGH_QUOTA: - errno = ENOMEM; - break; - case ERROR_ACCESS_DENIED: - case ERROR_CURRENT_DIRECTORY: - case ERROR_WRITE_PROTECT: - case ERROR_BAD_UNIT: - case ERROR_NOT_READY: - case ERROR_BAD_COMMAND: - case ERROR_CRC: - case ERROR_BAD_LENGTH: - case ERROR_SEEK: - case ERROR_NOT_DOS_DISK: - case ERROR_SECTOR_NOT_FOUND: - case ERROR_OUT_OF_PAPER: - case ERROR_WRITE_FAULT: - case ERROR_READ_FAULT: - case ERROR_GEN_FAILURE: - case ERROR_SHARING_VIOLATION: - case ERROR_LOCK_VIOLATION: - case ERROR_WRONG_DISK: - case ERROR_SHARING_BUFFER_EXCEEDED: - case ERROR_NETWORK_ACCESS_DENIED: - case ERROR_CANNOT_MAKE: - case ERROR_FAIL_I24: - case ERROR_DRIVE_LOCKED: - case ERROR_SEEK_ON_DEVICE: - case ERROR_NOT_LOCKED: - case ERROR_LOCK_FAILED: - errno = EACCES; - break; - case ERROR_FILE_EXISTS: - case ERROR_ALREADY_EXISTS: - errno = EEXIST; - break; - case ERROR_NOT_SAME_DEVICE: - errno = EXDEV; - break; - case ERROR_INVALID_FUNCTION: - case ERROR_INVALID_ACCESS: - case ERROR_INVALID_DATA: - case ERROR_INVALID_PARAMETER: - case ERROR_NEGATIVE_SEEK: - errno = EINVAL; - break; - case ERROR_TOO_MANY_OPEN_FILES: - errno = EMFILE; - break; - case ERROR_DISK_FULL: - errno = ENOSPC; - break; - case ERROR_BROKEN_PIPE: - errno = EPIPE; - break; - case ERROR_DIR_NOT_EMPTY: - errno = ENOTEMPTY; - break; - default: - errno = EINVAL; - } -} - -GIT_INLINE(bool) last_error_retryable(void) -{ - int os_error = GetLastError(); - - return (os_error == ERROR_SHARING_VIOLATION || - os_error == ERROR_ACCESS_DENIED); -} - -#define do_with_retries(fn, remediation) \ - do { \ - int __retry, __ret; \ - for (__retry = git_win32__retries; __retry; __retry--) { \ - if ((__ret = (fn)) != GIT_RETRY) \ - return __ret; \ - if (__retry > 1 && (__ret = (remediation)) != 0) { \ - if (__ret == GIT_RETRY) \ - continue; \ - return __ret; \ - } \ - Sleep(5); \ - } \ - return -1; \ - } while (0) \ - -static int ensure_writable(wchar_t *path) -{ - DWORD attrs; - - if ((attrs = GetFileAttributesW(path)) == INVALID_FILE_ATTRIBUTES) - goto on_error; - - if ((attrs & FILE_ATTRIBUTE_READONLY) == 0) - return 0; - - if (!SetFileAttributesW(path, (attrs & ~FILE_ATTRIBUTE_READONLY))) - goto on_error; - - return GIT_RETRY; - -on_error: - set_errno(); - return -1; -} - -/** - * Truncate or extend file. - * - * We now take a "git_off_t" rather than "long" because - * files may be longer than 2Gb. - */ -int p_ftruncate(int fd, off64_t size) -{ - if (size < 0) { - errno = EINVAL; - return -1; - } - -#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API) - return ((_chsize_s(fd, size) == 0) ? 0 : -1); -#else - /* TODO MINGW32 Find a replacement for _chsize() that handles big files. */ - if (size > INT32_MAX) { - errno = EFBIG; - return -1; - } - return _chsize(fd, (long)size); -#endif -} - -int p_mkdir(const char *path, mode_t mode) -{ - git_win32_path buf; - - GIT_UNUSED(mode); - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - return _wmkdir(buf); -} - -int p_link(const char *old, const char *new) -{ - GIT_UNUSED(old); - GIT_UNUSED(new); - errno = ENOSYS; - return -1; -} - -GIT_INLINE(int) unlink_once(const wchar_t *path) -{ - DWORD error; - - if (DeleteFileW(path)) - return 0; - - if ((error = GetLastError()) == ERROR_ACCESS_DENIED) { - WIN32_FILE_ATTRIBUTE_DATA fdata; - if (!GetFileAttributesExW(path, GetFileExInfoStandard, &fdata) || - !(fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) || - !(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) - goto out; - - if (RemoveDirectoryW(path)) - return 0; - } - -out: - SetLastError(error); - - if (last_error_retryable()) - return GIT_RETRY; - - set_errno(); - return -1; -} - -int p_unlink(const char *path) -{ - git_win32_path wpath; - - if (git_win32_path_from_utf8(wpath, path) < 0) - return -1; - - do_with_retries(unlink_once(wpath), ensure_writable(wpath)); -} - -int p_fsync(int fd) -{ - HANDLE fh = (HANDLE)_get_osfhandle(fd); - - p_fsync__cnt++; - - if (fh == INVALID_HANDLE_VALUE) { - errno = EBADF; - return -1; - } - - if (!FlushFileBuffers(fh)) { - DWORD code = GetLastError(); - - if (code == ERROR_INVALID_HANDLE) - errno = EINVAL; - else - errno = EIO; - - return -1; - } - - return 0; -} - -#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\') - -static int lstat_w( - wchar_t *path, - struct stat *buf, - bool posix_enotdir) -{ - WIN32_FILE_ATTRIBUTE_DATA fdata; - - if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) { - if (!buf) - return 0; - - return git_win32__file_attribute_to_stat(buf, &fdata, path); - } - - switch (GetLastError()) { - case ERROR_ACCESS_DENIED: - errno = EACCES; - break; - default: - errno = ENOENT; - break; - } - - /* To match POSIX behavior, set ENOTDIR when any of the folders in the - * file path is a regular file, otherwise set ENOENT. - */ - if (errno == ENOENT && posix_enotdir) { - size_t path_len = wcslen(path); - - /* scan up path until we find an existing item */ - while (1) { - DWORD attrs; - - /* remove last directory component */ - for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--); - - if (path_len <= 0) - break; - - path[path_len] = L'\0'; - attrs = GetFileAttributesW(path); - - if (attrs != INVALID_FILE_ATTRIBUTES) { - if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) - errno = ENOTDIR; - break; - } - } - } - - return -1; -} - -static int do_lstat(const char *path, struct stat *buf, bool posixly_correct) -{ - git_win32_path path_w; - int len; - - if ((len = git_win32_path_from_utf8(path_w, path)) < 0) - return -1; - - git_win32_path_trim_end(path_w, len); - - return lstat_w(path_w, buf, posixly_correct); -} - -int p_lstat(const char *filename, struct stat *buf) -{ - return do_lstat(filename, buf, false); -} - -int p_lstat_posixly(const char *filename, struct stat *buf) -{ - return do_lstat(filename, buf, true); -} - -int p_readlink(const char *path, char *buf, size_t bufsiz) -{ - git_win32_path path_w, target_w; - git_win32_utf8_path target; - int len; - - /* readlink(2) does not NULL-terminate the string written - * to the target buffer. Furthermore, the target buffer need - * not be large enough to hold the entire result. A truncated - * result should be written in this case. Since this truncation - * could occur in the middle of the encoding of a code point, - * we need to buffer the result on the stack. */ - - if (git_win32_path_from_utf8(path_w, path) < 0 || - git_win32_path_readlink_w(target_w, path_w) < 0 || - (len = git_win32_path_to_utf8(target, target_w)) < 0) - return -1; - - bufsiz = min((size_t)len, bufsiz); - memcpy(buf, target, bufsiz); - - return (int)bufsiz; -} - -static bool target_is_dir(const char *target, const char *path) -{ - git_str resolved = GIT_STR_INIT; - git_win32_path resolved_w; - bool isdir = true; - - if (git_fs_path_is_absolute(target)) - git_win32_path_from_utf8(resolved_w, target); - else if (git_fs_path_dirname_r(&resolved, path) < 0 || - git_fs_path_apply_relative(&resolved, target) < 0 || - git_win32_path_from_utf8(resolved_w, resolved.ptr) < 0) - goto out; - - isdir = GetFileAttributesW(resolved_w) & FILE_ATTRIBUTE_DIRECTORY; - -out: - git_str_dispose(&resolved); - return isdir; -} - -int p_symlink(const char *target, const char *path) -{ - git_win32_path target_w, path_w; - DWORD dwFlags; - - /* - * Convert both target and path to Windows-style paths. Note that we do - * not want to use `git_win32_path_from_utf8` for converting the target, - * as that function will automatically pre-pend the current working - * directory in case the path is not absolute. As Git will instead use - * relative symlinks, this is not something we want. - */ - if (git_win32_path_from_utf8(path_w, path) < 0 || - git_win32_path_relative_from_utf8(target_w, target) < 0) - return -1; - - dwFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; - if (target_is_dir(target, path)) - dwFlags |= SYMBOLIC_LINK_FLAG_DIRECTORY; - - if (!CreateSymbolicLinkW(path_w, target_w, dwFlags)) - return -1; - - return 0; -} - -struct open_opts { - DWORD access; - DWORD sharing; - SECURITY_ATTRIBUTES security; - DWORD creation_disposition; - DWORD attributes; - int osf_flags; -}; - -GIT_INLINE(void) open_opts_from_posix(struct open_opts *opts, int flags, mode_t mode) -{ - memset(opts, 0, sizeof(struct open_opts)); - - switch (flags & (O_WRONLY | O_RDWR)) { - case O_WRONLY: - opts->access = GENERIC_WRITE; - break; - case O_RDWR: - opts->access = GENERIC_READ | GENERIC_WRITE; - break; - default: - opts->access = GENERIC_READ; - break; - } - - opts->sharing = (DWORD)git_win32__createfile_sharemode; - - switch (flags & (O_CREAT | O_TRUNC | O_EXCL)) { - case O_CREAT | O_EXCL: - case O_CREAT | O_TRUNC | O_EXCL: - opts->creation_disposition = CREATE_NEW; - break; - case O_CREAT | O_TRUNC: - opts->creation_disposition = CREATE_ALWAYS; - break; - case O_TRUNC: - opts->creation_disposition = TRUNCATE_EXISTING; - break; - case O_CREAT: - opts->creation_disposition = OPEN_ALWAYS; - break; - default: - opts->creation_disposition = OPEN_EXISTING; - break; - } - - opts->attributes = ((flags & O_CREAT) && !(mode & S_IWRITE)) ? - FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL; - opts->osf_flags = flags & (O_RDONLY | O_APPEND); - - opts->security.nLength = sizeof(SECURITY_ATTRIBUTES); - opts->security.lpSecurityDescriptor = NULL; - opts->security.bInheritHandle = 0; -} - -GIT_INLINE(int) open_once( - const wchar_t *path, - struct open_opts *opts) -{ - int fd; - - HANDLE handle = CreateFileW(path, opts->access, opts->sharing, - &opts->security, opts->creation_disposition, opts->attributes, 0); - - if (handle == INVALID_HANDLE_VALUE) { - if (last_error_retryable()) - return GIT_RETRY; - - set_errno(); - return -1; - } - - if ((fd = _open_osfhandle((intptr_t)handle, opts->osf_flags)) < 0) - CloseHandle(handle); - - return fd; -} - -int p_open(const char *path, int flags, ...) -{ - git_win32_path wpath; - mode_t mode = 0; - struct open_opts opts = {0}; - - #ifdef GIT_DEBUG_STRICT_OPEN - if (strstr(path, "//") != NULL) { - errno = EACCES; - return -1; - } - #endif - - if (git_win32_path_from_utf8(wpath, path) < 0) - return -1; - - if (flags & O_CREAT) { - va_list arg_list; - - va_start(arg_list, flags); - mode = (mode_t)va_arg(arg_list, int); - va_end(arg_list); - } - - open_opts_from_posix(&opts, flags, mode); - - do_with_retries( - open_once(wpath, &opts), - 0); -} - -int p_creat(const char *path, mode_t mode) -{ - return p_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); -} - -int p_utimes(const char *path, const struct p_timeval times[2]) -{ - git_win32_path wpath; - int fd, error; - DWORD attrs_orig, attrs_new = 0; - struct open_opts opts = { 0 }; - - if (git_win32_path_from_utf8(wpath, path) < 0) - return -1; - - attrs_orig = GetFileAttributesW(wpath); - - if (attrs_orig & FILE_ATTRIBUTE_READONLY) { - attrs_new = attrs_orig & ~FILE_ATTRIBUTE_READONLY; - - if (!SetFileAttributesW(wpath, attrs_new)) { - git_error_set(GIT_ERROR_OS, "failed to set attributes"); - return -1; - } - } - - open_opts_from_posix(&opts, O_RDWR, 0); - - if ((fd = open_once(wpath, &opts)) < 0) { - error = -1; - goto done; - } - - error = p_futimes(fd, times); - close(fd); - -done: - if (attrs_orig != attrs_new) { - DWORD os_error = GetLastError(); - SetFileAttributesW(wpath, attrs_orig); - SetLastError(os_error); - } - - return error; -} - -int p_futimes(int fd, const struct p_timeval times[2]) -{ - HANDLE handle; - FILETIME atime = { 0 }, mtime = { 0 }; - - if (times == NULL) { - SYSTEMTIME st; - - GetSystemTime(&st); - SystemTimeToFileTime(&st, &atime); - SystemTimeToFileTime(&st, &mtime); - } - else { - git_win32__timeval_to_filetime(&atime, times[0]); - git_win32__timeval_to_filetime(&mtime, times[1]); - } - - if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE) - return -1; - - if (SetFileTime(handle, NULL, &atime, &mtime) == 0) - return -1; - - return 0; -} - -int p_getcwd(char *buffer_out, size_t size) -{ - git_win32_path buf; - wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16); - - if (!cwd) - return -1; - - git_win32_path_remove_namespace(cwd, wcslen(cwd)); - - /* Convert the working directory back to UTF-8 */ - if (git__utf16_to_8(buffer_out, size, cwd) < 0) { - DWORD code = GetLastError(); - - if (code == ERROR_INSUFFICIENT_BUFFER) - errno = ERANGE; - else - errno = EINVAL; - - return -1; - } - - git_fs_path_mkposix(buffer_out); - return 0; -} - -static int getfinalpath_w( - git_win32_path dest, - const wchar_t *path) -{ - HANDLE hFile; - DWORD dwChars; - - /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not - * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the - * target of the link. */ - hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - - if (INVALID_HANDLE_VALUE == hFile) - return -1; - - /* Call GetFinalPathNameByHandle */ - dwChars = GetFinalPathNameByHandleW(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED); - CloseHandle(hFile); - - if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16) - return -1; - - /* The path may be delivered to us with a namespace prefix; remove */ - return (int)git_win32_path_remove_namespace(dest, dwChars); -} - -static int follow_and_lstat_link(git_win32_path path, struct stat *buf) -{ - git_win32_path target_w; - - if (getfinalpath_w(target_w, path) < 0) - return -1; - - return lstat_w(target_w, buf, false); -} - -int p_fstat(int fd, struct stat *buf) -{ - BY_HANDLE_FILE_INFORMATION fhInfo; - - HANDLE fh = (HANDLE)_get_osfhandle(fd); - - if (fh == INVALID_HANDLE_VALUE || - !GetFileInformationByHandle(fh, &fhInfo)) { - errno = EBADF; - return -1; - } - - git_win32__file_information_to_stat(buf, &fhInfo); - return 0; -} - -int p_stat(const char *path, struct stat *buf) -{ - git_win32_path path_w; - int len; - - if ((len = git_win32_path_from_utf8(path_w, path)) < 0 || - lstat_w(path_w, buf, false) < 0) - return -1; - - /* The item is a symbolic link or mount point. No need to iterate - * to follow multiple links; use GetFinalPathNameFromHandle. */ - if (S_ISLNK(buf->st_mode)) - return follow_and_lstat_link(path_w, buf); - - return 0; -} - -int p_chdir(const char *path) -{ - git_win32_path buf; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - return _wchdir(buf); -} - -int p_chmod(const char *path, mode_t mode) -{ - git_win32_path buf; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - return _wchmod(buf, mode); -} - -int p_rmdir(const char *path) -{ - git_win32_path buf; - int error; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - error = _wrmdir(buf); - - if (error == -1) { - switch (GetLastError()) { - /* _wrmdir() is documented to return EACCES if "A program has an open - * handle to the directory." This sounds like what everybody else calls - * EBUSY. Let's convert appropriate error codes. - */ - case ERROR_SHARING_VIOLATION: - errno = EBUSY; - break; - - /* This error can be returned when trying to rmdir an extant file. */ - case ERROR_DIRECTORY: - errno = ENOTDIR; - break; - } - } - - return error; -} - -char *p_realpath(const char *orig_path, char *buffer) -{ - git_win32_path orig_path_w, buffer_w; - - if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0) - return NULL; - - /* Note that if the path provided is a relative path, then the current directory - * is used to resolve the path -- which is a concurrency issue because the current - * directory is a process-wide variable. */ - if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) { - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) - errno = ENAMETOOLONG; - else - errno = EINVAL; - - return NULL; - } - - /* The path must exist. */ - if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { - errno = ENOENT; - return NULL; - } - - if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) { - errno = ENOMEM; - return NULL; - } - - /* Convert the path to UTF-8. If the caller provided a buffer, then it - * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't, - * then we may overflow. */ - if (git_win32_path_to_utf8(buffer, buffer_w) < 0) - return NULL; - - git_fs_path_mkposix(buffer); - - return buffer; -} - -int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) -{ -#if defined(_MSC_VER) - int len; - - if (count == 0) - return _vscprintf(format, argptr); - - #if _MSC_VER >= 1500 - len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr); - #else - len = _vsnprintf(buffer, count, format, argptr); - #endif - - if (len < 0) - return _vscprintf(format, argptr); - - return len; -#else /* MinGW */ - return vsnprintf(buffer, count, format, argptr); -#endif -} - -int p_snprintf(char *buffer, size_t count, const char *format, ...) -{ - va_list va; - int r; - - va_start(va, format); - r = p_vsnprintf(buffer, count, format, va); - va_end(va); - - return r; -} - -int p_access(const char *path, mode_t mode) -{ - git_win32_path buf; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - return _waccess(buf, mode & WIN32_MODE_MASK); -} - -GIT_INLINE(int) rename_once(const wchar_t *from, const wchar_t *to) -{ - if (MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) - return 0; - - if (last_error_retryable()) - return GIT_RETRY; - - set_errno(); - return -1; -} - -int p_rename(const char *from, const char *to) -{ - git_win32_path wfrom, wto; - - if (git_win32_path_from_utf8(wfrom, from) < 0 || - git_win32_path_from_utf8(wto, to) < 0) - return -1; - - do_with_retries(rename_once(wfrom, wto), ensure_writable(wto)); -} - -int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags) -{ - if ((size_t)((int)length) != length) - return -1; /* git_error_set will be done by caller */ - - return recv(socket, buffer, (int)length, flags); -} - -int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags) -{ - if ((size_t)((int)length) != length) - return -1; /* git_error_set will be done by caller */ - - return send(socket, buffer, (int)length, flags); -} - -/** - * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html - * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that - */ -struct tm * -p_localtime_r (const time_t *timer, struct tm *result) -{ - struct tm *local_result; - local_result = localtime (timer); - - if (local_result == NULL || result == NULL) - return NULL; - - memcpy (result, local_result, sizeof (struct tm)); - return result; -} -struct tm * -p_gmtime_r (const time_t *timer, struct tm *result) -{ - struct tm *local_result; - local_result = gmtime (timer); - - if (local_result == NULL || result == NULL) - return NULL; - - memcpy (result, local_result, sizeof (struct tm)); - return result; -} - -int p_inet_pton(int af, const char *src, void *dst) -{ - struct sockaddr_storage sin; - void *addr; - int sin_len = sizeof(struct sockaddr_storage), addr_len; - int error = 0; - - if (af == AF_INET) { - addr = &((struct sockaddr_in *)&sin)->sin_addr; - addr_len = sizeof(struct in_addr); - } else if (af == AF_INET6) { - addr = &((struct sockaddr_in6 *)&sin)->sin6_addr; - addr_len = sizeof(struct in6_addr); - } else { - errno = EAFNOSUPPORT; - return -1; - } - - if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) { - memcpy(dst, addr, addr_len); - return 1; - } - - switch(WSAGetLastError()) { - case WSAEINVAL: - return 0; - case WSAEFAULT: - errno = ENOSPC; - return -1; - case WSA_NOT_ENOUGH_MEMORY: - errno = ENOMEM; - return -1; - } - - errno = EINVAL; - return -1; -} - -ssize_t p_pread(int fd, void *data, size_t size, off64_t offset) -{ - HANDLE fh; - DWORD rsize = 0; - OVERLAPPED ov = {0}; - LARGE_INTEGER pos = {0}; - off64_t final_offset = 0; - - /* Fail if the final offset would have overflowed to match POSIX semantics. */ - if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) { - errno = EINVAL; - return -1; - } - - /* - * Truncate large writes to the maximum allowable size: the caller - * needs to always call this in a loop anyways. - */ - if (size > INT32_MAX) { - size = INT32_MAX; - } - - pos.QuadPart = offset; - ov.Offset = pos.LowPart; - ov.OffsetHigh = pos.HighPart; - fh = (HANDLE)_get_osfhandle(fd); - - if (ReadFile(fh, data, (DWORD)size, &rsize, &ov)) { - return (ssize_t)rsize; - } - - set_errno(); - return -1; -} - -ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset) -{ - HANDLE fh; - DWORD wsize = 0; - OVERLAPPED ov = {0}; - LARGE_INTEGER pos = {0}; - off64_t final_offset = 0; - - /* Fail if the final offset would have overflowed to match POSIX semantics. */ - if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) { - errno = EINVAL; - return -1; - } - - /* - * Truncate large writes to the maximum allowable size: the caller - * needs to always call this in a loop anyways. - */ - if (size > INT32_MAX) { - size = INT32_MAX; - } - - pos.QuadPart = offset; - ov.Offset = pos.LowPart; - ov.OffsetHigh = pos.HighPart; - fh = (HANDLE)_get_osfhandle(fd); - - if (WriteFile(fh, data, (DWORD)size, &wsize, &ov)) { - return (ssize_t)wsize; - } - - set_errno(); - return -1; -} diff --git a/src/libgit2/win32/precompiled.c b/src/libgit2/win32/precompiled.c deleted file mode 100644 index 5f656a45d..000000000 --- a/src/libgit2/win32/precompiled.c +++ /dev/null @@ -1 +0,0 @@ -#include "precompiled.h" diff --git a/src/libgit2/win32/precompiled.h b/src/libgit2/win32/precompiled.h deleted file mode 100644 index 806b1698a..000000000 --- a/src/libgit2/win32/precompiled.h +++ /dev/null @@ -1,21 +0,0 @@ -#include "common.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#ifdef GIT_THREADS - #include "win32/thread.h" -#endif - -#include "git2.h" diff --git a/src/libgit2/win32/reparse.h b/src/libgit2/win32/reparse.h deleted file mode 100644 index 23312319f..000000000 --- a/src/libgit2/win32/reparse.h +++ /dev/null @@ -1,57 +0,0 @@ -/* -* Copyright (C) the libgit2 contributors. All rights reserved. -* -* This file is part of libgit2, distributed under the GNU GPL v2 with -* a Linking Exception. For full terms see the included COPYING file. -*/ - -#ifndef INCLUDE_win32_reparse_h__ -#define INCLUDE_win32_reparse_h__ - -/* This structure is defined on MSDN at -* http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx -* -* It was formerly included in the Windows 2000 SDK and remains defined in -* MinGW, so we must define it with a silly name to avoid conflicting. -*/ -typedef struct _GIT_REPARSE_DATA_BUFFER { - ULONG ReparseTag; - USHORT ReparseDataLength; - USHORT Reserved; - union { - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - ULONG Flags; - WCHAR PathBuffer[1]; - } SymbolicLink; - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - WCHAR PathBuffer[1]; - } MountPoint; - struct { - UCHAR DataBuffer[1]; - } Generic; - } ReparseBuffer; -} GIT_REPARSE_DATA_BUFFER; - -#define REPARSE_DATA_HEADER_SIZE 8 -#define REPARSE_DATA_MOUNTPOINT_HEADER_SIZE 8 -#define REPARSE_DATA_UNION_SIZE 12 - -/* Missing in MinGW */ -#ifndef FSCTL_GET_REPARSE_POINT -# define FSCTL_GET_REPARSE_POINT 0x000900a8 -#endif - -/* Missing in MinGW */ -#ifndef FSCTL_SET_REPARSE_POINT -# define FSCTL_SET_REPARSE_POINT 0x000900a4 -#endif - -#endif diff --git a/src/libgit2/win32/thread.c b/src/libgit2/win32/thread.c deleted file mode 100644 index f5cacd320..000000000 --- a/src/libgit2/win32/thread.c +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "thread.h" -#include "runtime.h" - -#define CLEAN_THREAD_EXIT 0x6F012842 - -typedef void (WINAPI *win32_srwlock_fn)(GIT_SRWLOCK *); - -static win32_srwlock_fn win32_srwlock_initialize; -static win32_srwlock_fn win32_srwlock_acquire_shared; -static win32_srwlock_fn win32_srwlock_release_shared; -static win32_srwlock_fn win32_srwlock_acquire_exclusive; -static win32_srwlock_fn win32_srwlock_release_exclusive; - -static DWORD fls_index; - -/* The thread procedure stub used to invoke the caller's procedure - * and capture the return value for later collection. Windows will - * only hold a DWORD, but we need to be able to store an entire - * void pointer. This requires the indirection. */ -static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter) -{ - git_thread *thread = lpParameter; - - /* Set the current thread for `git_thread_exit` */ - FlsSetValue(fls_index, thread); - - thread->result = thread->proc(thread->param); - - return CLEAN_THREAD_EXIT; -} - -static void git_threads_global_shutdown(void) -{ - FlsFree(fls_index); -} - -int git_threads_global_init(void) -{ - HMODULE hModule = GetModuleHandleW(L"kernel32"); - - if (hModule) { - win32_srwlock_initialize = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "InitializeSRWLock"); - win32_srwlock_acquire_shared = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "AcquireSRWLockShared"); - win32_srwlock_release_shared = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "ReleaseSRWLockShared"); - win32_srwlock_acquire_exclusive = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "AcquireSRWLockExclusive"); - win32_srwlock_release_exclusive = (win32_srwlock_fn)(void *) - GetProcAddress(hModule, "ReleaseSRWLockExclusive"); - } - - if ((fls_index = FlsAlloc(NULL)) == FLS_OUT_OF_INDEXES) - return -1; - - return git_runtime_shutdown_register(git_threads_global_shutdown); -} - -int git_thread_create( - git_thread *GIT_RESTRICT thread, - void *(*start_routine)(void*), - void *GIT_RESTRICT arg) -{ - thread->result = NULL; - thread->param = arg; - thread->proc = start_routine; - thread->thread = CreateThread( - NULL, 0, git_win32__threadproc, thread, 0, NULL); - - return thread->thread ? 0 : -1; -} - -int git_thread_join( - git_thread *thread, - void **value_ptr) -{ - DWORD exit; - - if (WaitForSingleObject(thread->thread, INFINITE) != WAIT_OBJECT_0) - return -1; - - if (!GetExitCodeThread(thread->thread, &exit)) { - CloseHandle(thread->thread); - return -1; - } - - /* Check for the thread having exited uncleanly. If exit was unclean, - * then we don't have a return value to give back to the caller. */ - GIT_ASSERT(exit == CLEAN_THREAD_EXIT); - - if (value_ptr) - *value_ptr = thread->result; - - CloseHandle(thread->thread); - return 0; -} - -void git_thread_exit(void *value) -{ - git_thread *thread = FlsGetValue(fls_index); - - if (thread) - thread->result = value; - - ExitThread(CLEAN_THREAD_EXIT); -} - -size_t git_thread_currentid(void) -{ - return GetCurrentThreadId(); -} - -int git_mutex_init(git_mutex *GIT_RESTRICT mutex) -{ - InitializeCriticalSection(mutex); - return 0; -} - -int git_mutex_free(git_mutex *mutex) -{ - DeleteCriticalSection(mutex); - return 0; -} - -int git_mutex_lock(git_mutex *mutex) -{ - EnterCriticalSection(mutex); - return 0; -} - -int git_mutex_unlock(git_mutex *mutex) -{ - LeaveCriticalSection(mutex); - return 0; -} - -int git_cond_init(git_cond *cond) -{ - /* This is an auto-reset event. */ - *cond = CreateEventW(NULL, FALSE, FALSE, NULL); - GIT_ASSERT(*cond); - - /* If we can't create the event, claim that the reason was out-of-memory. - * The actual reason can be fetched with GetLastError(). */ - return *cond ? 0 : ENOMEM; -} - -int git_cond_free(git_cond *cond) -{ - BOOL closed; - - if (!cond) - return EINVAL; - - closed = CloseHandle(*cond); - GIT_ASSERT(closed); - GIT_UNUSED(closed); - - *cond = NULL; - return 0; -} - -int git_cond_wait(git_cond *cond, git_mutex *mutex) -{ - int error; - DWORD wait_result; - - if (!cond || !mutex) - return EINVAL; - - /* The caller must be holding the mutex. */ - error = git_mutex_unlock(mutex); - - if (error) - return error; - - wait_result = WaitForSingleObject(*cond, INFINITE); - GIT_ASSERT(WAIT_OBJECT_0 == wait_result); - GIT_UNUSED(wait_result); - - return git_mutex_lock(mutex); -} - -int git_cond_signal(git_cond *cond) -{ - BOOL signaled; - - if (!cond) - return EINVAL; - - signaled = SetEvent(*cond); - GIT_ASSERT(signaled); - GIT_UNUSED(signaled); - - return 0; -} - -int git_rwlock_init(git_rwlock *GIT_RESTRICT lock) -{ - if (win32_srwlock_initialize) - win32_srwlock_initialize(&lock->native.srwl); - else - InitializeCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_rdlock(git_rwlock *lock) -{ - if (win32_srwlock_acquire_shared) - win32_srwlock_acquire_shared(&lock->native.srwl); - else - EnterCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_rdunlock(git_rwlock *lock) -{ - if (win32_srwlock_release_shared) - win32_srwlock_release_shared(&lock->native.srwl); - else - LeaveCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_wrlock(git_rwlock *lock) -{ - if (win32_srwlock_acquire_exclusive) - win32_srwlock_acquire_exclusive(&lock->native.srwl); - else - EnterCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_wrunlock(git_rwlock *lock) -{ - if (win32_srwlock_release_exclusive) - win32_srwlock_release_exclusive(&lock->native.srwl); - else - LeaveCriticalSection(&lock->native.csec); - - return 0; -} - -int git_rwlock_free(git_rwlock *lock) -{ - if (!win32_srwlock_initialize) - DeleteCriticalSection(&lock->native.csec); - git__memzero(lock, sizeof(*lock)); - return 0; -} diff --git a/src/libgit2/win32/thread.h b/src/libgit2/win32/thread.h deleted file mode 100644 index 8305036b4..000000000 --- a/src/libgit2/win32/thread.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_win32_thread_h__ -#define INCLUDE_win32_thread_h__ - -#include "common.h" - -#if defined (_MSC_VER) -# define GIT_RESTRICT __restrict -#else -# define GIT_RESTRICT __restrict__ -#endif - -typedef struct { - HANDLE thread; - void *(*proc)(void *); - void *param; - void *result; -} git_thread; - -typedef CRITICAL_SECTION git_mutex; -typedef HANDLE git_cond; - -typedef struct { void *Ptr; } GIT_SRWLOCK; - -typedef struct { - union { - GIT_SRWLOCK srwl; - CRITICAL_SECTION csec; - } native; -} git_rwlock; - -int git_threads_global_init(void); - -int git_thread_create(git_thread *GIT_RESTRICT, - void *(*) (void *), - void *GIT_RESTRICT); -int git_thread_join(git_thread *, void **); -size_t git_thread_currentid(void); -void git_thread_exit(void *); - -int git_mutex_init(git_mutex *GIT_RESTRICT mutex); -int git_mutex_free(git_mutex *); -int git_mutex_lock(git_mutex *); -int git_mutex_unlock(git_mutex *); - -int git_cond_init(git_cond *); -int git_cond_free(git_cond *); -int git_cond_wait(git_cond *, git_mutex *); -int git_cond_signal(git_cond *); - -int git_rwlock_init(git_rwlock *GIT_RESTRICT lock); -int git_rwlock_rdlock(git_rwlock *); -int git_rwlock_rdunlock(git_rwlock *); -int git_rwlock_wrlock(git_rwlock *); -int git_rwlock_wrunlock(git_rwlock *); -int git_rwlock_free(git_rwlock *); - -#endif diff --git a/src/libgit2/win32/utf-conv.c b/src/libgit2/win32/utf-conv.c deleted file mode 100644 index 4bde3023a..000000000 --- a/src/libgit2/win32/utf-conv.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "utf-conv.h" - -GIT_INLINE(void) git__set_errno(void) -{ - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) - errno = ENAMETOOLONG; - else - errno = EINVAL; -} - -/** - * Converts a UTF-8 string to wide characters. - * - * @param dest The buffer to receive the wide string. - * @param dest_size The size of the buffer, in characters. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src) -{ - int len; - - /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to - * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's - * length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */ - if ((len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1) < 0) - git__set_errno(); - - return len; -} - -/** - * Converts a wide string to UTF-8. - * - * @param dest The buffer to receive the UTF-8 string. - * @param dest_size The size of the buffer, in bytes. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src) -{ - int len; - - /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to - * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's - * length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */ - if ((len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0) - git__set_errno(); - - return len; -} - -/** - * Converts a UTF-8 string to wide characters. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to the wide string. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -int git__utf8_to_16_alloc(wchar_t **dest, const char *src) -{ - int utf16_size; - - *dest = NULL; - - /* Length of -1 indicates NULL termination of the input string */ - utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0); - - if (!utf16_size) { - git__set_errno(); - return -1; - } - - if (!(*dest = git__mallocarray(utf16_size, sizeof(wchar_t)))) { - errno = ENOMEM; - return -1; - } - - utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size); - - if (!utf16_size) { - git__set_errno(); - - git__free(*dest); - *dest = NULL; - } - - /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL - * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, - * so underflow is not possible */ - return utf16_size - 1; -} - -/** - * Converts a wide string to UTF-8. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to the UTF-8 string. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8_alloc(char **dest, const wchar_t *src) -{ - int utf8_size; - - *dest = NULL; - - /* Length of -1 indicates NULL termination of the input string */ - utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL); - - if (!utf8_size) { - git__set_errno(); - return -1; - } - - *dest = git__malloc(utf8_size); - - if (!*dest) { - errno = ENOMEM; - return -1; - } - - utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, *dest, utf8_size, NULL, NULL); - - if (!utf8_size) { - git__set_errno(); - - git__free(*dest); - *dest = NULL; - } - - /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL - * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, - * so underflow is not possible */ - return utf8_size - 1; -} diff --git a/src/libgit2/win32/utf-conv.h b/src/libgit2/win32/utf-conv.h deleted file mode 100644 index 6090a4b35..000000000 --- a/src/libgit2/win32/utf-conv.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_utf_conv_h__ -#define INCLUDE_win32_utf_conv_h__ - -#include "common.h" - -#include - -#ifndef WC_ERR_INVALID_CHARS -# define WC_ERR_INVALID_CHARS 0x80 -#endif - -/** - * Converts a UTF-8 string to wide characters. - * - * @param dest The buffer to receive the wide string. - * @param dest_size The size of the buffer, in characters. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src); - -/** - * Converts a wide string to UTF-8. - * - * @param dest The buffer to receive the UTF-8 string. - * @param dest_size The size of the buffer, in bytes. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src); - -/** - * Converts a UTF-8 string to wide characters. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to the wide string. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -int git__utf8_to_16_alloc(wchar_t **dest, const char *src); - -/** - * Converts a wide string to UTF-8. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to the UTF-8 string. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8_alloc(char **dest, const wchar_t *src); - -#endif diff --git a/src/libgit2/win32/version.h b/src/libgit2/win32/version.h deleted file mode 100644 index 79667697f..000000000 --- a/src/libgit2/win32/version.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_version_h__ -#define INCLUDE_win32_version_h__ - -#include - -GIT_INLINE(int) git_has_win32_version(int major, int minor, int service_pack) -{ - OSVERSIONINFOEX version_test = {0}; - DWORD version_test_mask; - DWORDLONG version_condition_mask = 0; - - version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - version_test.dwMajorVersion = major; - version_test.dwMinorVersion = minor; - version_test.wServicePackMajor = (WORD)service_pack; - version_test.wServicePackMinor = 0; - - version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR); - - VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); - - if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask)) - return 0; - - return 1; -} - -#endif diff --git a/src/libgit2/win32/w32_buffer.c b/src/libgit2/win32/w32_buffer.c deleted file mode 100644 index 6fee8203c..000000000 --- a/src/libgit2/win32/w32_buffer.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "w32_buffer.h" - -#include "utf-conv.h" - -GIT_INLINE(int) handle_wc_error(void) -{ - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) - errno = ENAMETOOLONG; - else - errno = EINVAL; - - return -1; -} - -int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w) -{ - int utf8_len, utf8_write_len; - size_t new_size; - - if (!len_w) { - return 0; - } else if (len_w > INT_MAX) { - git_error_set_oom(); - return -1; - } - - GIT_ASSERT(string_w); - - /* Measure the string necessary for conversion */ - if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, NULL, 0, NULL, NULL)) == 0) - return 0; - - GIT_ASSERT(utf8_len > 0); - - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, (size_t)utf8_len); - GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); - - if (git_str_grow(buf, new_size) < 0) - return -1; - - if ((utf8_write_len = WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, &buf->ptr[buf->size], utf8_len, NULL, NULL)) == 0) - return handle_wc_error(); - - GIT_ASSERT(utf8_write_len == utf8_len); - - buf->size += utf8_write_len; - buf->ptr[buf->size] = '\0'; - return 0; -} diff --git a/src/libgit2/win32/w32_buffer.h b/src/libgit2/win32/w32_buffer.h deleted file mode 100644 index 4227296d8..000000000 --- a/src/libgit2/win32/w32_buffer.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_w32_buffer_h__ -#define INCLUDE_win32_w32_buffer_h__ - -#include "common.h" -#include "str.h" - -/** - * Convert a wide character string to UTF-8 and append the results to the - * buffer. - */ -int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w); - -#endif diff --git a/src/libgit2/win32/w32_common.h b/src/libgit2/win32/w32_common.h deleted file mode 100644 index c20b3e85e..000000000 --- a/src/libgit2/win32/w32_common.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_win32_w32_common_h__ -#define INCLUDE_win32_w32_common_h__ - -#include - -/* - * 4096 is the max allowed Git path. `MAX_PATH` (260) is the typical max allowed - * Windows path length, however win32 Unicode APIs generally allow up to 32,767 - * if prefixed with "\\?\" (i.e. converted to an NT-style name). - */ -#define GIT_WIN_PATH_MAX GIT_PATH_MAX - -/* - * Provides a large enough buffer to support Windows Git paths: - * GIT_WIN_PATH_MAX is 4096, corresponding to a maximum path length of 4095 - * characters plus a NULL terminator. Prefixing with "\\?\" adds 4 characters, - * but if the original was a UNC path, then we turn "\\server\share" into - * "\\?\UNC\server\share". So we replace the first two characters with - * 8 characters, a net gain of 6, so the maximum length is GIT_WIN_PATH_MAX+6. - */ -#define GIT_WIN_PATH_UTF16 GIT_WIN_PATH_MAX+6 - -/* Maximum size of a UTF-8 Win32 Git path. We remove the "\\?\" or "\\?\UNC\" - * prefixes for presentation, bringing us back to 4095 (non-NULL) - * characters. UTF-8 does have 4-byte sequences, but they are encoded in - * UTF-16 using surrogate pairs, which takes up the space of two characters. - * Two characters in the range U+0800 -> U+FFFF take up more space in UTF-8 - * (6 bytes) than one surrogate pair (4 bytes). - */ -#define GIT_WIN_PATH_UTF8 ((GIT_WIN_PATH_MAX - 1) * 3 + 1) - -/* - * The length of a Windows "shortname", for 8.3 compatibility. - */ -#define GIT_WIN_PATH_SHORTNAME 13 - -/* Win32 path types */ -typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16]; -typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8]; - -#endif diff --git a/src/libgit2/win32/w32_leakcheck.c b/src/libgit2/win32/w32_leakcheck.c deleted file mode 100644 index 0f095de12..000000000 --- a/src/libgit2/win32/w32_leakcheck.c +++ /dev/null @@ -1,581 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "w32_leakcheck.h" - -#if defined(GIT_WIN32_LEAKCHECK) - -#include "Windows.h" -#include "Dbghelp.h" -#include "win32/posix.h" -#include "hash.h" -#include "runtime.h" - -/* Stack frames (for stack tracing, below) */ - -static bool g_win32_stack_initialized = false; -static HANDLE g_win32_stack_process = INVALID_HANDLE_VALUE; -static git_win32_leakcheck_stack_aux_cb_alloc g_aux_cb_alloc = NULL; -static git_win32_leakcheck_stack_aux_cb_lookup g_aux_cb_lookup = NULL; - -int git_win32_leakcheck_stack_set_aux_cb( - git_win32_leakcheck_stack_aux_cb_alloc cb_alloc, - git_win32_leakcheck_stack_aux_cb_lookup cb_lookup) -{ - g_aux_cb_alloc = cb_alloc; - g_aux_cb_lookup = cb_lookup; - - return 0; -} - -/** - * Load symbol table data. This should be done in the primary - * thread at startup (under a lock if there are other threads - * active). - */ -void git_win32_leakcheck_stack_init(void) -{ - if (!g_win32_stack_initialized) { - g_win32_stack_process = GetCurrentProcess(); - SymSetOptions(SYMOPT_LOAD_LINES); - SymInitialize(g_win32_stack_process, NULL, TRUE); - g_win32_stack_initialized = true; - } -} - -/** - * Cleanup symbol table data. This should be done in the - * primary thead at shutdown (under a lock if there are other - * threads active). - */ -void git_win32_leakcheck_stack_cleanup(void) -{ - if (g_win32_stack_initialized) { - SymCleanup(g_win32_stack_process); - g_win32_stack_process = INVALID_HANDLE_VALUE; - g_win32_stack_initialized = false; - } -} - -int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip) -{ - if (!g_win32_stack_initialized) { - git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); - return GIT_ERROR; - } - - memset(pdata, 0, sizeof(*pdata)); - pdata->nr_frames = RtlCaptureStackBackTrace( - skip+1, GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES, pdata->frames, NULL); - - /* If an "aux" data provider was registered, ask it to capture - * whatever data it needs and give us an "aux_id" to it so that - * we can refer to it later when reporting. - */ - if (g_aux_cb_alloc) - (g_aux_cb_alloc)(&pdata->aux_id); - - return 0; -} - -int git_win32_leakcheck_stack_compare( - git_win32_leakcheck_stack_raw_data *d1, - git_win32_leakcheck_stack_raw_data *d2) -{ - return memcmp(d1, d2, sizeof(*d1)); -} - -int git_win32_leakcheck_stack_format( - char *pbuf, size_t buf_len, - const git_win32_leakcheck_stack_raw_data *pdata, - const char *prefix, const char *suffix) -{ -#define MY_MAX_FILENAME 255 - - /* SYMBOL_INFO has char FileName[1] at the end. The docs say to - * to malloc it with extra space for your desired max filename. - */ - struct { - SYMBOL_INFO symbol; - char extra[MY_MAX_FILENAME + 1]; - } s; - - IMAGEHLP_LINE64 line; - size_t buf_used = 0; - unsigned int k; - char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */ - size_t detail_len; - - if (!g_win32_stack_initialized) { - git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); - return GIT_ERROR; - } - - if (!prefix) - prefix = "\t"; - if (!suffix) - suffix = "\n"; - - memset(pbuf, 0, buf_len); - - memset(&s, 0, sizeof(s)); - s.symbol.MaxNameLen = MY_MAX_FILENAME; - s.symbol.SizeOfStruct = sizeof(SYMBOL_INFO); - - memset(&line, 0, sizeof(line)); - line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); - - for (k=0; k < pdata->nr_frames; k++) { - DWORD64 frame_k = (DWORD64)pdata->frames[k]; - DWORD dwUnused; - - if (SymFromAddr(g_win32_stack_process, frame_k, 0, &s.symbol) && - SymGetLineFromAddr64(g_win32_stack_process, frame_k, &dwUnused, &line)) { - const char *pslash; - const char *pfile; - - pslash = strrchr(line.FileName, '\\'); - pfile = ((pslash) ? (pslash+1) : line.FileName); - p_snprintf(detail, sizeof(detail), "%s%s:%d> %s%s", - prefix, pfile, line.LineNumber, s.symbol.Name, suffix); - } else { - /* This happens when we cross into another module. - * For example, in CLAR tests, this is typically - * the CRT startup code. Just print an unknown - * frame and continue. - */ - p_snprintf(detail, sizeof(detail), "%s??%s", prefix, suffix); - } - detail_len = strlen(detail); - - if (buf_len < (buf_used + detail_len + 1)) { - /* we don't have room for this frame in the buffer, so just stop. */ - break; - } - - memcpy(&pbuf[buf_used], detail, detail_len); - buf_used += detail_len; - } - - /* "aux_id" 0 is reserved to mean no aux data. This is needed to handle - * allocs that occur before the aux callbacks were registered. - */ - if (pdata->aux_id > 0) { - p_snprintf(detail, sizeof(detail), "%saux_id: %d%s", - prefix, pdata->aux_id, suffix); - detail_len = strlen(detail); - if ((buf_used + detail_len + 1) < buf_len) { - memcpy(&pbuf[buf_used], detail, detail_len); - buf_used += detail_len; - } - - /* If an "aux" data provider is still registered, ask it to append its detailed - * data to the end of ours using the "aux_id" it gave us when this de-duped - * item was created. - */ - if (g_aux_cb_lookup) - (g_aux_cb_lookup)(pdata->aux_id, &pbuf[buf_used], (buf_len - buf_used - 1)); - } - - return GIT_OK; -} - -int git_win32_leakcheck_stack( - char * pbuf, size_t buf_len, - int skip, - const char *prefix, const char *suffix) -{ - git_win32_leakcheck_stack_raw_data data; - int error; - - if ((error = git_win32_leakcheck_stack_capture(&data, skip)) < 0) - return error; - if ((error = git_win32_leakcheck_stack_format(pbuf, buf_len, &data, prefix, suffix)) < 0) - return error; - return 0; -} - -/* Stack tracing */ - -#define STACKTRACE_UID_LEN (15) - -/** - * The stacktrace of an allocation can be distilled - * to a unique id based upon the stackframe pointers - * and ignoring any size arguments. We will use these - * UIDs as the (char const*) __FILE__ argument we - * give to the CRT malloc routines. - */ -typedef struct { - char uid[STACKTRACE_UID_LEN + 1]; -} git_win32_leakcheck_stacktrace_uid; - -/** - * All mallocs with the same stacktrace will be de-duped - * and aggregated into this row. - */ -typedef struct { - git_win32_leakcheck_stacktrace_uid uid; /* must be first */ - git_win32_leakcheck_stack_raw_data raw_data; - unsigned int count_allocs; /* times this alloc signature seen since init */ - unsigned int count_allocs_at_last_checkpoint; /* times since last mark */ - unsigned int transient_count_leaks; /* sum of leaks */ -} git_win32_leakcheck_stacktrace_row; - -static CRITICAL_SECTION g_crtdbg_stacktrace_cs; - -/** - * CRTDBG memory leak tracking takes a "char const * const file_name" - * and stores the pointer in the heap data (instead of allocing a copy - * for itself). Normally, this is not a problem, since we usually pass - * in __FILE__. But I'm going to lie to it and pass in the address of - * the UID in place of the file_name. Also, I do not want to alloc the - * stacktrace data (because we are called from inside our alloc routines). - * Therefore, I'm creating a very large static pool array to store row - * data. This also eliminates the temptation to realloc it (and move the - * UID pointers). - * - * And to efficiently look for duplicates we need an index on the rows - * so we can bsearch it. Again, without mallocing. - * - * If we observe more than MY_ROW_LIMIT unique malloc signatures, we - * fall through and use the traditional __FILE__ processing and don't - * try to de-dup them. If your testing hits this limit, just increase - * it and try again. - */ - -#define MY_ROW_LIMIT (2 * 1024 * 1024) -static git_win32_leakcheck_stacktrace_row g_cs_rows[MY_ROW_LIMIT]; -static git_win32_leakcheck_stacktrace_row *g_cs_index[MY_ROW_LIMIT]; - -static unsigned int g_cs_end = MY_ROW_LIMIT; -static unsigned int g_cs_ins = 0; /* insertion point == unique allocs seen */ -static unsigned int g_count_total_allocs = 0; /* number of allocs seen */ -static unsigned int g_transient_count_total_leaks = 0; /* number of total leaks */ -static unsigned int g_transient_count_dedup_leaks = 0; /* number of unique leaks */ -static bool g_limit_reached = false; /* had allocs after we filled row table */ - -static unsigned int g_checkpoint_id = 0; /* to better label leak checkpoints */ -static bool g_transient_leaks_since_mark = false; /* payload for hook */ - -/** - * Compare function for bsearch on g_cs_index table. - */ -static int row_cmp(const void *v1, const void *v2) -{ - git_win32_leakcheck_stack_raw_data *d1 = (git_win32_leakcheck_stack_raw_data*)v1; - git_win32_leakcheck_stacktrace_row *r2 = (git_win32_leakcheck_stacktrace_row *)v2; - - return (git_win32_leakcheck_stack_compare(d1, &r2->raw_data)); -} - -/** - * Unique insert the new data into the row and index tables. - * We have to sort by the stackframe data itself, not the uid. - */ -static git_win32_leakcheck_stacktrace_row * insert_unique( - const git_win32_leakcheck_stack_raw_data *pdata) -{ - size_t pos; - if (git__bsearch(g_cs_index, g_cs_ins, pdata, row_cmp, &pos) < 0) { - /* Append new unique item to row table. */ - memcpy(&g_cs_rows[g_cs_ins].raw_data, pdata, sizeof(*pdata)); - sprintf(g_cs_rows[g_cs_ins].uid.uid, "##%08lx", g_cs_ins); - - /* Insert pointer to it into the proper place in the index table. */ - if (pos < g_cs_ins) - memmove(&g_cs_index[pos+1], &g_cs_index[pos], (g_cs_ins - pos)*sizeof(g_cs_index[0])); - g_cs_index[pos] = &g_cs_rows[g_cs_ins]; - - g_cs_ins++; - } - - g_cs_index[pos]->count_allocs++; - - return g_cs_index[pos]; -} - -/** - * Hook function to receive leak data from the CRT. (This includes - * both ":()" data, but also each of the - * various headers and fields. - * - * Scan this for the special "##" UID forms that we substituted - * for the "". Map back to the row data and - * increment its leak count. - * - * See https://msdn.microsoft.com/en-us/library/74kabxyx.aspx - * - * We suppress the actual crtdbg output. - */ -static int __cdecl report_hook(int nRptType, char *szMsg, int *retVal) -{ - static int hook_result = TRUE; /* FALSE to get stock dump; TRUE to suppress. */ - unsigned int pos; - - *retVal = 0; /* do not invoke debugger */ - - if ((szMsg[0] != '#') || (szMsg[1] != '#')) - return hook_result; - - if (sscanf(&szMsg[2], "%08lx", &pos) < 1) - return hook_result; - if (pos >= g_cs_ins) - return hook_result; - - if (g_transient_leaks_since_mark) { - if (g_cs_rows[pos].count_allocs == g_cs_rows[pos].count_allocs_at_last_checkpoint) - return hook_result; - } - - g_cs_rows[pos].transient_count_leaks++; - - if (g_cs_rows[pos].transient_count_leaks == 1) - g_transient_count_dedup_leaks++; - - g_transient_count_total_leaks++; - - return hook_result; -} - -/** - * Write leak data to all of the various places we need. - * We force the caller to sprintf() the message first - * because we want to avoid fprintf() because it allocs. - */ -static void my_output(const char *buf) -{ - fwrite(buf, strlen(buf), 1, stderr); - OutputDebugString(buf); -} - -/** - * For each row with leaks, dump a stacktrace for it. - */ -static void dump_summary(const char *label) -{ - unsigned int k; - char buf[10 * 1024]; - - if (g_transient_count_total_leaks == 0) - return; - - fflush(stdout); - fflush(stderr); - my_output("\n"); - - if (g_limit_reached) { - sprintf(buf, - "LEAK SUMMARY: de-dup row table[%d] filled. Increase MY_ROW_LIMIT.\n", - MY_ROW_LIMIT); - my_output(buf); - } - - if (!label) - label = ""; - - if (g_transient_leaks_since_mark) { - sprintf(buf, "LEAK CHECKPOINT %d: leaks %d unique %d: %s\n", - g_checkpoint_id, g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); - my_output(buf); - } else { - sprintf(buf, "LEAK SUMMARY: TOTAL leaks %d de-duped %d: %s\n", - g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); - my_output(buf); - } - my_output("\n"); - - for (k = 0; k < g_cs_ins; k++) { - if (g_cs_rows[k].transient_count_leaks > 0) { - sprintf(buf, "LEAK: %s leaked %d of %d times:\n", - g_cs_rows[k].uid.uid, - g_cs_rows[k].transient_count_leaks, - g_cs_rows[k].count_allocs); - my_output(buf); - - if (git_win32_leakcheck_stack_format( - buf, sizeof(buf), &g_cs_rows[k].raw_data, - NULL, NULL) >= 0) { - my_output(buf); - } - - my_output("\n"); - } - } - - fflush(stderr); -} - -/** - * Initialize our memory leak tracking and de-dup data structures. - * This should ONLY be called by git_libgit2_init(). - */ -void git_win32_leakcheck_stacktrace_init(void) -{ - InitializeCriticalSection(&g_crtdbg_stacktrace_cs); - - EnterCriticalSection(&g_crtdbg_stacktrace_cs); - - _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); - - _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); - - _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); - _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); - _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); - - LeaveCriticalSection(&g_crtdbg_stacktrace_cs); -} - -int git_win32_leakcheck_stacktrace_dump( - git_win32_leakcheck_stacktrace_options opt, - const char *label) -{ - _CRT_REPORT_HOOK old; - unsigned int k; - int r = 0; - -#define IS_BIT_SET(o,b) (((o) & (b)) != 0) - - bool b_set_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK); - bool b_leaks_since_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK); - bool b_leaks_total = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL); - bool b_quiet = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET); - - if (b_leaks_since_mark && b_leaks_total) { - git_error_set(GIT_ERROR_INVALID, "cannot combine LEAKS_SINCE_MARK and LEAKS_TOTAL."); - return GIT_ERROR; - } - if (!b_set_mark && !b_leaks_since_mark && !b_leaks_total) { - git_error_set(GIT_ERROR_INVALID, "nothing to do."); - return GIT_ERROR; - } - - EnterCriticalSection(&g_crtdbg_stacktrace_cs); - - if (b_leaks_since_mark || b_leaks_total) { - /* All variables with "transient" in the name are per-dump counters - * and reset before each dump. This lets us handle checkpoints. - */ - g_transient_count_total_leaks = 0; - g_transient_count_dedup_leaks = 0; - for (k = 0; k < g_cs_ins; k++) { - g_cs_rows[k].transient_count_leaks = 0; - } - } - - g_transient_leaks_since_mark = b_leaks_since_mark; - - old = _CrtSetReportHook(report_hook); - _CrtDumpMemoryLeaks(); - _CrtSetReportHook(old); - - if (b_leaks_since_mark || b_leaks_total) { - r = g_transient_count_dedup_leaks; - - if (!b_quiet) - dump_summary(label); - } - - if (b_set_mark) { - for (k = 0; k < g_cs_ins; k++) { - g_cs_rows[k].count_allocs_at_last_checkpoint = g_cs_rows[k].count_allocs; - } - - g_checkpoint_id++; - } - - LeaveCriticalSection(&g_crtdbg_stacktrace_cs); - - return r; -} - -/** - * Shutdown our memory leak tracking and dump summary data. - * This should ONLY be called by git_libgit2_shutdown(). - * - * We explicitly call _CrtDumpMemoryLeaks() during here so - * that we can compute summary data for the leaks. We print - * the stacktrace of each unique leak. - * - * This cleanup does not happen if the app calls exit() - * without calling the libgit2 shutdown code. - * - * This info we print here is independent of any automatic - * reporting during exit() caused by _CRTDBG_LEAK_CHECK_DF. - * Set it in your app if you also want traditional reporting. - */ -void git_win32_leakcheck_stacktrace_cleanup(void) -{ - /* At shutdown/cleanup, dump cumulative leak info - * with everything since startup. This might generate - * extra noise if the caller has been doing checkpoint - * dumps, but it might also eliminate some false - * positives for resources previously reported during - * checkpoints. - */ - git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL, - "CLEANUP"); - - DeleteCriticalSection(&g_crtdbg_stacktrace_cs); -} - -const char *git_win32_leakcheck_stacktrace(int skip, const char *file) -{ - git_win32_leakcheck_stack_raw_data new_data; - git_win32_leakcheck_stacktrace_row *row; - const char * result = file; - - if (git_win32_leakcheck_stack_capture(&new_data, skip+1) < 0) - return result; - - EnterCriticalSection(&g_crtdbg_stacktrace_cs); - - if (g_cs_ins < g_cs_end) { - row = insert_unique(&new_data); - result = row->uid.uid; - } else { - g_limit_reached = true; - } - - g_count_total_allocs++; - - LeaveCriticalSection(&g_crtdbg_stacktrace_cs); - - return result; -} - -static void git_win32_leakcheck_global_shutdown(void) -{ - git_win32_leakcheck_stacktrace_cleanup(); - git_win32_leakcheck_stack_cleanup(); -} - -bool git_win32_leakcheck_has_leaks(void) -{ - return (g_transient_count_total_leaks > 0); -} - -int git_win32_leakcheck_global_init(void) -{ - git_win32_leakcheck_stacktrace_init(); - git_win32_leakcheck_stack_init(); - - return git_runtime_shutdown_register(git_win32_leakcheck_global_shutdown); -} - -#else - -int git_win32_leakcheck_global_init(void) -{ - return 0; -} - -#endif diff --git a/src/libgit2/win32/w32_leakcheck.h b/src/libgit2/win32/w32_leakcheck.h deleted file mode 100644 index cb45e3675..000000000 --- a/src/libgit2/win32/w32_leakcheck.h +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_win32_leakcheck_h__ -#define INCLUDE_win32_leakcheck_h__ - -#include "common.h" - -/* Initialize the win32 leak checking system. */ -int git_win32_leakcheck_global_init(void); - -#if defined(GIT_WIN32_LEAKCHECK) - -#include -#include - -#include "git2/errors.h" -#include "strnlen.h" - -bool git_win32_leakcheck_has_leaks(void); - -/* Stack frames (for stack tracing, below) */ - -/** - * This type defines a callback to be used to augment a C stacktrace - * with "aux" data. This can be used, for example, to allow LibGit2Sharp - * (or other interpreted consumer libraries) to give us C# stacktrace - * data for the PInvoke. - * - * This callback will be called during crtdbg-instrumented allocs. - * - * @param aux_id [out] A returned "aux_id" representing a unique - * (de-duped at the C# layer) stacktrace. "aux_id" 0 is reserved - * to mean no aux stacktrace data. - */ -typedef void (*git_win32_leakcheck_stack_aux_cb_alloc)(unsigned int *aux_id); - -/** - * This type defines a callback to be used to augment the output of - * a stacktrace. This will be used to request the C# layer format - * the C# stacktrace associated with "aux_id" into the provided - * buffer. - * - * This callback will be called during leak reporting. - * - * @param aux_id The "aux_id" key associated with a stacktrace. - * @param aux_msg A buffer where a formatted message should be written. - * @param aux_msg_len The size of the buffer. - */ -typedef void (*git_win32_leakcheck_stack_aux_cb_lookup)(unsigned int aux_id, char *aux_msg, size_t aux_msg_len); - -/** - * Register an "aux" data provider to augment our C stacktrace data. - * - * This can be used, for example, to allow LibGit2Sharp (or other - * interpreted consumer libraries) to give us the C# stacktrace of - * the PInvoke. - * - * If you choose to use this feature, it should be registered during - * initialization and not changed for the duration of the process. - */ -int git_win32_leakcheck_stack_set_aux_cb( - git_win32_leakcheck_stack_aux_cb_alloc cb_alloc, - git_win32_leakcheck_stack_aux_cb_lookup cb_lookup); - -/** - * Maximum number of stackframes to record for a - * single stacktrace. - */ -#define GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES 30 - -/** - * Wrapper containing the raw unprocessed stackframe - * data for a single stacktrace and any "aux_id". - * - * I put the aux_id first so leaks will be sorted by it. - * So, for example, if a specific callstack in C# leaks - * a repo handle, all of the pointers within the associated - * repo pointer will be grouped together. - */ -typedef struct { - unsigned int aux_id; - unsigned int nr_frames; - void *frames[GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES]; -} git_win32_leakcheck_stack_raw_data; - -/** - * Capture raw stack trace data for the current process/thread. - * - * @param skip Number of initial frames to skip. Pass 0 to - * begin with the caller of this routine. Pass 1 to begin - * with its caller. And so on. - */ -int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip); - -/** - * Compare 2 raw stacktraces with the usual -1,0,+1 result. - * This includes any "aux_id" values in the comparison, so that - * our de-dup is also "aux" context relative. - */ -int git_win32_leakcheck_stack_compare( - git_win32_leakcheck_stack_raw_data *d1, - git_win32_leakcheck_stack_raw_data *d2); - -/** - * Format raw stacktrace data into buffer WITHOUT using any mallocs. - * - * @param prefix String written before each frame; defaults to "\t". - * @param suffix String written after each frame; defaults to "\n". - */ -int git_win32_leakcheck_stack_format( - char *pbuf, size_t buf_len, - const git_win32_leakcheck_stack_raw_data *pdata, - const char *prefix, const char *suffix); - -/** - * Convenience routine to capture and format stacktrace into - * a buffer WITHOUT using any mallocs. This is primarily a - * wrapper for testing. - * - * @param skip Number of initial frames to skip. Pass 0 to - * begin with the caller of this routine. Pass 1 to begin - * with its caller. And so on. - * @param prefix String written before each frame; defaults to "\t". - * @param suffix String written after each frame; defaults to "\n". - */ -int git_win32_leakcheck_stack( - char * pbuf, size_t buf_len, - int skip, - const char *prefix, const char *suffix); - -/* Stack tracing */ - -/* MSVC CRTDBG memory leak reporting. - * - * We DO NOT use the "_CRTDBG_MAP_ALLOC" macro described in the MSVC - * documentation because all allocs/frees in libgit2 already go through - * the "git__" routines defined in this file. Simply using the normal - * reporting mechanism causes all leaks to be attributed to a routine - * here in util.h (ie, the actual call to calloc()) rather than the - * caller of git__calloc(). - * - * Therefore, we declare a set of "git__crtdbg__" routines to replace - * the corresponding "git__" routines and re-define the "git__" symbols - * as macros. This allows us to get and report the file:line info of - * the real caller. - * - * We DO NOT replace the "git__free" routine because it needs to remain - * a function pointer because it is used as a function argument when - * setting up various structure "destructors". - * - * We also DO NOT use the "_CRTDBG_MAP_ALLOC" macro because it causes - * "free" to be remapped to "_free_dbg" and this causes problems for - * structures which define a field named "free". - * - * Finally, CRTDBG must be explicitly enabled and configured at program - * startup. See tests/main.c for an example. - */ - -/** - * Checkpoint options. - */ -typedef enum git_win32_leakcheck_stacktrace_options { - /** - * Set checkpoint marker. - */ - GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK = (1 << 0), - - /** - * Dump leaks since last checkpoint marker. - * May not be combined with _LEAKS_TOTAL. - * - * Note that this may generate false positives for global TLS - * error state and other global caches that aren't cleaned up - * until the thread/process terminates. So when using this - * around a region of interest, also check the final (at exit) - * dump before digging into leaks reported here. - */ - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK = (1 << 1), - - /** - * Dump leaks since init. May not be combined - * with _LEAKS_SINCE_MARK. - */ - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL = (1 << 2), - - /** - * Suppress printing during dumps. - * Just return leak count. - */ - GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET = (1 << 3), - -} git_win32_leakcheck_stacktrace_options; - -/** - * Checkpoint memory state and/or dump unique stack traces of - * current memory leaks. - * - * @return number of unique leaks (relative to requested starting - * point) or error. - */ -int git_win32_leakcheck_stacktrace_dump( - git_win32_leakcheck_stacktrace_options opt, - const char *label); - -/** - * Construct stacktrace and append it to the global buffer. - * Return pointer to start of this string. On any error or - * lack of buffer space, just return the given file buffer - * so it will behave as usual. - * - * This should ONLY be called by our internal memory allocations - * routines. - */ -const char *git_win32_leakcheck_stacktrace(int skip, const char *file); - -#endif -#endif diff --git a/src/libgit2/win32/w32_util.c b/src/libgit2/win32/w32_util.c deleted file mode 100644 index fe4b75bae..000000000 --- a/src/libgit2/win32/w32_util.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "w32_util.h" - -/** - * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. - * The filter string enumerates all items in the directory. - * - * @param dest The buffer to receive the filter string. - * @param src The UTF-8 path of the directory to enumerate. - * @return True if the filter string was created successfully; false otherwise - */ -bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src) -{ - static const wchar_t suffix[] = L"\\*"; - int len = git_win32_path_from_utf8(dest, src); - - /* Ensure the path was converted */ - if (len < 0) - return false; - - /* Ensure that the path does not end with a trailing slash, - * because we're about to add one. Don't rely our trim_end - * helper, because we want to remove the backslash even for - * drive letter paths, in this case. */ - if (len > 0 && - (dest[len - 1] == L'/' || dest[len - 1] == L'\\')) { - dest[len - 1] = L'\0'; - len--; - } - - /* Ensure we have enough room to add the suffix */ - if ((size_t)len >= GIT_WIN_PATH_UTF16 - CONST_STRLEN(suffix)) - return false; - - wcscat(dest, suffix); - return true; -} - -/** - * Ensures the given path (file or folder) has the +H (hidden) attribute set. - * - * @param path The path which should receive the +H bit. - * @return 0 on success; -1 on failure - */ -int git_win32__set_hidden(const char *path, bool hidden) -{ - git_win32_path buf; - DWORD attrs, newattrs; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - attrs = GetFileAttributesW(buf); - - /* Ensure the path exists */ - if (attrs == INVALID_FILE_ATTRIBUTES) - return -1; - - if (hidden) - newattrs = attrs | FILE_ATTRIBUTE_HIDDEN; - else - newattrs = attrs & ~FILE_ATTRIBUTE_HIDDEN; - - if (attrs != newattrs && !SetFileAttributesW(buf, newattrs)) { - git_error_set(GIT_ERROR_OS, "failed to %s hidden bit for '%s'", - hidden ? "set" : "unset", path); - return -1; - } - - return 0; -} - -int git_win32__hidden(bool *out, const char *path) -{ - git_win32_path buf; - DWORD attrs; - - if (git_win32_path_from_utf8(buf, path) < 0) - return -1; - - attrs = GetFileAttributesW(buf); - - /* Ensure the path exists */ - if (attrs == INVALID_FILE_ATTRIBUTES) - return -1; - - *out = (attrs & FILE_ATTRIBUTE_HIDDEN) ? true : false; - return 0; -} - -int git_win32__file_attribute_to_stat( - struct stat *st, - const WIN32_FILE_ATTRIBUTE_DATA *attrdata, - const wchar_t *path) -{ - git_win32__stat_init(st, - attrdata->dwFileAttributes, - attrdata->nFileSizeHigh, - attrdata->nFileSizeLow, - attrdata->ftCreationTime, - attrdata->ftLastAccessTime, - attrdata->ftLastWriteTime); - - if (attrdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && path) { - git_win32_path target; - - if (git_win32_path_readlink_w(target, path) >= 0) { - st->st_mode = (st->st_mode & ~S_IFMT) | S_IFLNK; - - /* st_size gets the UTF-8 length of the target name, in bytes, - * not counting the NULL terminator */ - if ((st->st_size = git__utf16_to_8(NULL, 0, target)) < 0) { - git_error_set(GIT_ERROR_OS, "could not convert reparse point name for '%ls'", path); - return -1; - } - } - } - - return 0; -} diff --git a/src/libgit2/win32/w32_util.h b/src/libgit2/win32/w32_util.h deleted file mode 100644 index 1321d30e6..000000000 --- a/src/libgit2/win32/w32_util.h +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_win32_w32_util_h__ -#define INCLUDE_win32_w32_util_h__ - -#include "common.h" - -#include "utf-conv.h" -#include "posix.h" -#include "path_w32.h" - -/* - -#include "common.h" -#include "path.h" -#include "path_w32.h" -#include "utf-conv.h" -#include "posix.h" -#include "reparse.h" -#include "dir.h" -*/ - - -GIT_INLINE(bool) git_win32__isalpha(wchar_t c) -{ - return ((c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z')); -} - -/** - * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. - * The filter string enumerates all items in the directory. - * - * @param dest The buffer to receive the filter string. - * @param src The UTF-8 path of the directory to enumerate. - * @return True if the filter string was created successfully; false otherwise - */ -bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src); - -/** - * Ensures the given path (file or folder) has the +H (hidden) attribute set - * or unset. - * - * @param path The path that should receive the +H bit. - * @param hidden true to set +H, false to unset it - * @return 0 on success; -1 on failure - */ -extern int git_win32__set_hidden(const char *path, bool hidden); - -/** - * Determines if the given file or folder has the hidden attribute set. - * @param hidden pointer to store hidden value - * @param path The path that should be queried for hiddenness. - * @return 0 on success or an error code. - */ -extern int git_win32__hidden(bool *hidden, const char *path); - -extern int git_win32__file_attribute_to_stat( - struct stat *st, - const WIN32_FILE_ATTRIBUTE_DATA *attrdata, - const wchar_t *path); - -/** - * Converts a FILETIME structure to a struct timespec. - * - * @param FILETIME A pointer to a FILETIME - * @param ts A pointer to the timespec structure to fill in - */ -GIT_INLINE(void) git_win32__filetime_to_timespec( - const FILETIME *ft, - struct timespec *ts) -{ - int64_t winTime = ((int64_t)ft->dwHighDateTime << 32) + ft->dwLowDateTime; - winTime -= INT64_C(116444736000000000); /* Windows to Unix Epoch conversion */ - ts->tv_sec = (time_t)(winTime / 10000000); -#ifdef GIT_USE_NSEC - ts->tv_nsec = (winTime % 10000000) * 100; -#else - ts->tv_nsec = 0; -#endif -} - -GIT_INLINE(void) git_win32__timeval_to_filetime( - FILETIME *ft, const struct p_timeval tv) -{ - int64_t ticks = (tv.tv_sec * INT64_C(10000000)) + - (tv.tv_usec * INT64_C(10)) + INT64_C(116444736000000000); - - ft->dwHighDateTime = ((ticks >> 32) & INT64_C(0xffffffff)); - ft->dwLowDateTime = (ticks & INT64_C(0xffffffff)); -} - -GIT_INLINE(void) git_win32__stat_init( - struct stat *st, - DWORD dwFileAttributes, - DWORD nFileSizeHigh, - DWORD nFileSizeLow, - FILETIME ftCreationTime, - FILETIME ftLastAccessTime, - FILETIME ftLastWriteTime) -{ - mode_t mode = S_IREAD; - - memset(st, 0, sizeof(struct stat)); - - if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - mode |= S_IFDIR; - else - mode |= S_IFREG; - - if ((dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) - mode |= S_IWRITE; - - st->st_ino = 0; - st->st_gid = 0; - st->st_uid = 0; - st->st_nlink = 1; - st->st_mode = mode; - st->st_size = ((int64_t)nFileSizeHigh << 32) + nFileSizeLow; - st->st_dev = _getdrive() - 1; - st->st_rdev = st->st_dev; - git_win32__filetime_to_timespec(&ftLastAccessTime, &(st->st_atim)); - git_win32__filetime_to_timespec(&ftLastWriteTime, &(st->st_mtim)); - git_win32__filetime_to_timespec(&ftCreationTime, &(st->st_ctim)); -} - -GIT_INLINE(void) git_win32__file_information_to_stat( - struct stat *st, - const BY_HANDLE_FILE_INFORMATION *fileinfo) -{ - git_win32__stat_init(st, - fileinfo->dwFileAttributes, - fileinfo->nFileSizeHigh, - fileinfo->nFileSizeLow, - fileinfo->ftCreationTime, - fileinfo->ftLastAccessTime, - fileinfo->ftLastWriteTime); -} - -#endif diff --git a/src/libgit2/win32/win32-compat.h b/src/libgit2/win32/win32-compat.h deleted file mode 100644 index dee40a438..000000000 --- a/src/libgit2/win32/win32-compat.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_win32_win32_compat_h__ -#define INCLUDE_win32_win32_compat_h__ - -#include -#include -#include -#include -#include - -typedef long suseconds_t; - -struct p_timeval { - time_t tv_sec; - suseconds_t tv_usec; -}; - -struct p_timespec { - time_t tv_sec; - long tv_nsec; -}; - -#define timespec p_timespec - -struct p_stat { - _dev_t st_dev; - _ino_t st_ino; - mode_t st_mode; - short st_nlink; - short st_uid; - short st_gid; - _dev_t st_rdev; - __int64 st_size; - struct timespec st_atim; - struct timespec st_mtim; - struct timespec st_ctim; -#define st_atime st_atim.tv_sec -#define st_mtime st_mtim.tv_sec -#define st_ctime st_ctim.tv_sec -#define st_atime_nsec st_atim.tv_nsec -#define st_mtime_nsec st_mtim.tv_nsec -#define st_ctime_nsec st_ctim.tv_nsec -}; - -#define stat p_stat - -#endif diff --git a/src/libgit2/zstream.c b/src/libgit2/zstream.c deleted file mode 100644 index cb8b125ed..000000000 --- a/src/libgit2/zstream.c +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "zstream.h" - -#include - -#include "str.h" - -#define ZSTREAM_BUFFER_SIZE (1024 * 1024) -#define ZSTREAM_BUFFER_MIN_EXTRA 8 - -GIT_INLINE(int) zstream_seterr(git_zstream *zs) -{ - switch (zs->zerr) { - case Z_OK: - case Z_STREAM_END: - case Z_BUF_ERROR: /* not fatal; we retry with a larger buffer */ - return 0; - case Z_MEM_ERROR: - git_error_set_oom(); - break; - default: - if (zs->z.msg) - git_error_set_str(GIT_ERROR_ZLIB, zs->z.msg); - else - git_error_set(GIT_ERROR_ZLIB, "unknown compression error"); - } - - return -1; -} - -int git_zstream_init(git_zstream *zstream, git_zstream_t type) -{ - zstream->type = type; - - if (zstream->type == GIT_ZSTREAM_INFLATE) - zstream->zerr = inflateInit(&zstream->z); - else - zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION); - return zstream_seterr(zstream); -} - -void git_zstream_free(git_zstream *zstream) -{ - if (zstream->type == GIT_ZSTREAM_INFLATE) - inflateEnd(&zstream->z); - else - deflateEnd(&zstream->z); -} - -void git_zstream_reset(git_zstream *zstream) -{ - if (zstream->type == GIT_ZSTREAM_INFLATE) - inflateReset(&zstream->z); - else - deflateReset(&zstream->z); - zstream->in = NULL; - zstream->in_len = 0; - zstream->zerr = Z_STREAM_END; -} - -int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len) -{ - zstream->in = in; - zstream->in_len = in_len; - zstream->zerr = Z_OK; - return 0; -} - -bool git_zstream_done(git_zstream *zstream) -{ - return (!zstream->in_len && zstream->zerr == Z_STREAM_END); -} - -bool git_zstream_eos(git_zstream *zstream) -{ - return zstream->zerr == Z_STREAM_END; -} - -size_t git_zstream_suggest_output_len(git_zstream *zstream) -{ - if (zstream->in_len > ZSTREAM_BUFFER_SIZE) - return ZSTREAM_BUFFER_SIZE; - else if (zstream->in_len > ZSTREAM_BUFFER_MIN_EXTRA) - return zstream->in_len; - else - return ZSTREAM_BUFFER_MIN_EXTRA; -} - -int git_zstream_get_output_chunk( - void *out, size_t *out_len, git_zstream *zstream) -{ - size_t in_queued, in_used, out_queued; - - /* set up input data */ - zstream->z.next_in = (Bytef *)zstream->in; - - /* feed as much data to zlib as it can consume, at most UINT_MAX */ - if (zstream->in_len > UINT_MAX) { - zstream->z.avail_in = UINT_MAX; - zstream->flush = Z_NO_FLUSH; - } else { - zstream->z.avail_in = (uInt)zstream->in_len; - zstream->flush = Z_FINISH; - } - in_queued = (size_t)zstream->z.avail_in; - - /* set up output data */ - zstream->z.next_out = out; - zstream->z.avail_out = (uInt)*out_len; - - if ((size_t)zstream->z.avail_out != *out_len) - zstream->z.avail_out = UINT_MAX; - out_queued = (size_t)zstream->z.avail_out; - - /* compress next chunk */ - if (zstream->type == GIT_ZSTREAM_INFLATE) - zstream->zerr = inflate(&zstream->z, zstream->flush); - else - zstream->zerr = deflate(&zstream->z, zstream->flush); - - if (zstream_seterr(zstream)) - return -1; - - in_used = (in_queued - zstream->z.avail_in); - zstream->in_len -= in_used; - zstream->in += in_used; - - *out_len = (out_queued - zstream->z.avail_out); - - return 0; -} - -int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) -{ - size_t out_remain = *out_len; - - if (zstream->in_len && zstream->zerr == Z_STREAM_END) { - git_error_set(GIT_ERROR_ZLIB, "zlib input had trailing garbage"); - return -1; - } - - while (out_remain > 0 && zstream->zerr != Z_STREAM_END) { - size_t out_written = out_remain; - - if (git_zstream_get_output_chunk(out, &out_written, zstream) < 0) - return -1; - - out_remain -= out_written; - out = ((char *)out) + out_written; - } - - /* either we finished the input or we did not flush the data */ - GIT_ASSERT(zstream->in_len > 0 || zstream->flush == Z_FINISH); - - /* set out_size to number of bytes actually written to output */ - *out_len = *out_len - out_remain; - - return 0; -} - -static int zstream_buf(git_str *out, const void *in, size_t in_len, git_zstream_t type) -{ - git_zstream zs = GIT_ZSTREAM_INIT; - int error = 0; - - if ((error = git_zstream_init(&zs, type)) < 0) - return error; - - if ((error = git_zstream_set_input(&zs, in, in_len)) < 0) - goto done; - - while (!git_zstream_done(&zs)) { - size_t step = git_zstream_suggest_output_len(&zs), written; - - if ((error = git_str_grow_by(out, step)) < 0) - goto done; - - written = out->asize - out->size; - - if ((error = git_zstream_get_output( - out->ptr + out->size, &written, &zs)) < 0) - goto done; - - out->size += written; - } - - /* NULL terminate for consistency if possible */ - if (out->size < out->asize) - out->ptr[out->size] = '\0'; - -done: - git_zstream_free(&zs); - return error; -} - -int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len) -{ - return zstream_buf(out, in, in_len, GIT_ZSTREAM_DEFLATE); -} - -int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len) -{ - return zstream_buf(out, in, in_len, GIT_ZSTREAM_INFLATE); -} diff --git a/src/libgit2/zstream.h b/src/libgit2/zstream.h deleted file mode 100644 index 3f8b1c72f..000000000 --- a/src/libgit2/zstream.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_zstream_h__ -#define INCLUDE_zstream_h__ - -#include "common.h" - -#include - -#include "str.h" - -typedef enum { - GIT_ZSTREAM_INFLATE, - GIT_ZSTREAM_DEFLATE -} git_zstream_t; - -typedef struct { - z_stream z; - git_zstream_t type; - const char *in; - size_t in_len; - int flush; - int zerr; -} git_zstream; - -#define GIT_ZSTREAM_INIT {{0}} - -int git_zstream_init(git_zstream *zstream, git_zstream_t type); -void git_zstream_free(git_zstream *zstream); - -int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len); - -size_t git_zstream_suggest_output_len(git_zstream *zstream); - -/* get as much output as is available in the input buffer */ -int git_zstream_get_output_chunk( - void *out, size_t *out_len, git_zstream *zstream); - -/* get all the output from the entire input buffer */ -int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream); - -bool git_zstream_done(git_zstream *zstream); -bool git_zstream_eos(git_zstream *zstream); - -void git_zstream_reset(git_zstream *zstream); - -int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len); -int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len); - -#endif diff --git a/src/rand.c b/src/rand.c deleted file mode 100644 index 0a208134e..000000000 --- a/src/rand.c +++ /dev/null @@ -1,226 +0,0 @@ -/* Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) - -To the extent possible under law, the author has dedicated all copyright -and related and neighboring rights to this software to the public domain -worldwide. This software is distributed without any warranty. - -See . */ - -#include "common.h" -#include "rand.h" -#include "runtime.h" - -#if defined(GIT_RAND_GETENTROPY) -# include -#endif - -static uint64_t state[4]; -static git_mutex state_lock; - -typedef union { - double f; - uint64_t d; -} bits; - -#if defined(GIT_WIN32) -GIT_INLINE(int) getseed(uint64_t *seed) -{ - HCRYPTPROV provider; - SYSTEMTIME systemtime; - FILETIME filetime, idletime, kerneltime, usertime; - bits convert; - - if (CryptAcquireContext(&provider, 0, 0, PROV_RSA_FULL, - CRYPT_VERIFYCONTEXT|CRYPT_SILENT)) { - BOOL success = CryptGenRandom(provider, sizeof(uint64_t), (void *)seed); - CryptReleaseContext(provider, 0); - - if (success) - return 0; - } - - GetSystemTime(&systemtime); - if (!SystemTimeToFileTime(&systemtime, &filetime)) { - git_error_set(GIT_ERROR_OS, "could not get time for random seed"); - return -1; - } - - /* Fall-through: generate a seed from the time and system state */ - *seed = 0; - *seed |= ((uint64_t)filetime.dwLowDateTime << 32); - *seed |= ((uint64_t)filetime.dwHighDateTime); - - GetSystemTimes(&idletime, &kerneltime, &usertime); - - *seed ^= ((uint64_t)idletime.dwLowDateTime << 32); - *seed ^= ((uint64_t)kerneltime.dwLowDateTime); - *seed ^= ((uint64_t)usertime.dwLowDateTime << 32); - - *seed ^= ((uint64_t)idletime.dwHighDateTime); - *seed ^= ((uint64_t)kerneltime.dwHighDateTime << 12); - *seed ^= ((uint64_t)usertime.dwHighDateTime << 24); - - *seed ^= ((uint64_t)GetCurrentProcessId() << 32); - *seed ^= ((uint64_t)GetCurrentThreadId() << 48); - - convert.f = git__timer(); *seed ^= (convert.d); - - /* Mix in the addresses of some functions and variables */ - *seed ^= (((uint64_t)((uintptr_t)seed) << 32)); - *seed ^= (((uint64_t)((uintptr_t)&errno))); - - return 0; -} - -#else - -GIT_INLINE(int) getseed(uint64_t *seed) -{ - struct timeval tv; - double loadavg[3]; - bits convert; - int fd; - -# if defined(GIT_RAND_GETENTROPY) - GIT_UNUSED((fd = 0)); - - if (getentropy(seed, sizeof(uint64_t)) == 0) - return 0; -# else - /* - * Try to read from /dev/urandom; most modern systems will have - * this, but we may be chrooted, etc, so it's not a fatal error - */ - if ((fd = open("/dev/urandom", O_RDONLY)) >= 0) { - ssize_t ret = read(fd, seed, sizeof(uint64_t)); - close(fd); - - if (ret == (ssize_t)sizeof(uint64_t)) - return 0; - } -# endif - - /* Fall-through: generate a seed from the time and system state */ - if (gettimeofday(&tv, NULL) < 0) { - git_error_set(GIT_ERROR_OS, "could get time for random seed"); - return -1; - } - - getloadavg(loadavg, 3); - - *seed = 0; - *seed |= ((uint64_t)tv.tv_usec << 40); - *seed |= ((uint64_t)tv.tv_sec); - - *seed ^= ((uint64_t)getpid() << 48); - *seed ^= ((uint64_t)getppid() << 32); - *seed ^= ((uint64_t)getpgid(0) << 28); - *seed ^= ((uint64_t)getsid(0) << 16); - *seed ^= ((uint64_t)getuid() << 8); - *seed ^= ((uint64_t)getgid()); - - convert.f = loadavg[0]; *seed ^= (convert.d >> 36); - convert.f = loadavg[1]; *seed ^= (convert.d); - convert.f = loadavg[2]; *seed ^= (convert.d >> 16); - - convert.f = git__timer(); *seed ^= (convert.d); - - /* Mix in the addresses of some variables */ - *seed ^= ((uint64_t)((size_t)((void *)seed)) << 32); - *seed ^= ((uint64_t)((size_t)((void *)&errno))); - - return 0; -} -#endif - -static void git_rand_global_shutdown(void) -{ - git_mutex_free(&state_lock); -} - -int git_rand_global_init(void) -{ - uint64_t seed = 0; - - if (git_mutex_init(&state_lock) < 0 || getseed(&seed) < 0) - return -1; - - if (!seed) { - git_error_set(GIT_ERROR_INTERNAL, "failed to generate random seed"); - return -1; - } - - git_rand_seed(seed); - git_runtime_shutdown_register(git_rand_global_shutdown); - - return 0; -} - -/* - * This is splitmix64. xoroshiro256** uses 256 bit seed; this is used - * to generate 256 bits of seed from the given 64, per the author's - * recommendation. - */ -GIT_INLINE(uint64_t) splitmix64(uint64_t *in) -{ - uint64_t z; - - *in += 0x9e3779b97f4a7c15; - - z = *in; - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - return z ^ (z >> 31); -} - -void git_rand_seed(uint64_t seed) -{ - uint64_t mixer; - - mixer = seed; - - git_mutex_lock(&state_lock); - state[0] = splitmix64(&mixer); - state[1] = splitmix64(&mixer); - state[2] = splitmix64(&mixer); - state[3] = splitmix64(&mixer); - git_mutex_unlock(&state_lock); -} - -/* This is xoshiro256** 1.0, one of our all-purpose, rock-solid - generators. It has excellent (sub-ns) speed, a state (256 bits) that is - large enough for any parallel application, and it passes all tests we - are aware of. - - For generating just floating-point numbers, xoshiro256+ is even faster. - - The state must be seeded so that it is not everywhere zero. If you have - a 64-bit seed, we suggest to seed a splitmix64 generator and use its - output to fill s. */ - -GIT_INLINE(uint64_t) rotl(const uint64_t x, int k) { - return (x << k) | (x >> (64 - k)); -} - -uint64_t git_rand_next(void) { - uint64_t t, result; - - git_mutex_lock(&state_lock); - - result = rotl(state[1] * 5, 7) * 9; - - t = state[1] << 17; - - state[2] ^= state[0]; - state[3] ^= state[1]; - state[1] ^= state[2]; - state[0] ^= state[3]; - - state[2] ^= t; - - state[3] = rotl(state[3], 45); - - git_mutex_unlock(&state_lock); - - return result; -} diff --git a/src/rand.h b/src/rand.h deleted file mode 100644 index 2e60561e5..000000000 --- a/src/rand.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_rand_h__ -#define INCLUDE_rand_h__ - -#include "common.h" - -/** - * Initialize the random number generation subsystem. This will - * seed the random number generator with the system's entropy pool, - * if available, and will fall back to the current time and - * system information if not. - */ -int git_rand_global_init(void); - -/** - * Seed the pseudo-random number generator. This is not needed to be - * called; the PRNG is seeded by `git_rand_global_init`, but it may - * be useful for testing. When the same seed is specified, the same - * sequence of random numbers from `git_rand_next` is emitted. - * - * @param seed the seed to use - */ -void git_rand_seed(uint64_t seed); - -/** - * Get the next pseudo-random number in the sequence. - * - * @return a 64-bit pseudo-random number - */ -uint64_t git_rand_next(void); - -#endif diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt new file mode 100644 index 000000000..ea2df0a56 --- /dev/null +++ b/src/util/CMakeLists.txt @@ -0,0 +1,59 @@ +# util: a shared library for common utility functions for libgit2 projects + +add_library(util OBJECT) +set_target_properties(util PROPERTIES C_STANDARD 90) +set_target_properties(util PROPERTIES C_EXTENSIONS OFF) + +set(UTIL_INCLUDES + "${PROJECT_BINARY_DIR}/src" + "${PROJECT_SOURCE_DIR}/src/util" + "${PROJECT_SOURCE_DIR}/include") + +file(GLOB UTIL_SRC *.c *.h allocators/*.c allocators/*.h hash/sha1.h) +list(SORT UTIL_SRC) + +# +# Platform specific sources +# + +if(WIN32 AND NOT CYGWIN) + file(GLOB UTIL_SRC_OS win32/*.c win32/*.h) + list(SORT UTIL_SRC_OS) +elseif(NOT AMIGA) + file(GLOB UTIL_SRC_OS unix/*.c unix/*.h) + list(SORT UTIL_SRC_OS) +endif() + +# +# Hash backend selection +# + +if(USE_SHA1 STREQUAL "CollisionDetection") + file(GLOB UTIL_SRC_HASH hash/sha1/collisiondetect.* hash/sha1/sha1dc/*) + target_compile_definitions(util PRIVATE SHA1DC_NO_STANDARD_INCLUDES=1) + target_compile_definitions(util PRIVATE SHA1DC_CUSTOM_INCLUDE_SHA1_C=\"git2_util.h\") + target_compile_definitions(util PRIVATE SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C=\"git2_util.h\") +elseif(USE_SHA1 STREQUAL "OpenSSL") + file(GLOB UTIL_SRC_HASH hash/sha1/openssl.*) +elseif(USE_SHA1 STREQUAL "CommonCrypto") + file(GLOB UTIL_SRC_HASH hash/sha1/common_crypto.*) +elseif(USE_SHA1 STREQUAL "mbedTLS") + file(GLOB UTIL_SRC_HASH hash/sha1/mbedtls.*) +elseif(USE_SHA1 STREQUAL "Win32") + file(GLOB UTIL_SRC_HASH hash/sha1/win32.*) +elseif(USE_SHA1 STREQUAL "Generic") + file(GLOB UTIL_SRC_HASH hash/sha1/generic.*) +else() + message(FATAL_ERROR "Asked for unknown SHA1 backend: ${USE_SHA1}") +endif() + +list(SORT UTIL_SRC_HASH) + +# +# Build the library +# + +target_sources(util PRIVATE ${UTIL_SRC} ${UTIL_SRC_OS} ${UTIL_SRC_HASH}) + +target_include_directories(util PRIVATE ${UTIL_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES} PUBLIC ${libgit2_SOURCE_DIR}/include) +target_include_directories(util SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) diff --git a/src/util/alloc.c b/src/util/alloc.c new file mode 100644 index 000000000..2820d84a2 --- /dev/null +++ b/src/util/alloc.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "alloc.h" +#include "runtime.h" + +#include "allocators/failalloc.h" +#include "allocators/stdalloc.h" +#include "allocators/win32_leakcheck.h" + +/* Fail any allocation until git_libgit2_init is called. */ +git_allocator git__allocator = { + git_failalloc_malloc, + git_failalloc_calloc, + git_failalloc_strdup, + git_failalloc_strndup, + git_failalloc_substrdup, + git_failalloc_realloc, + git_failalloc_reallocarray, + git_failalloc_mallocarray, + git_failalloc_free +}; + +static int setup_default_allocator(void) +{ +#if defined(GIT_WIN32_LEAKCHECK) + return git_win32_leakcheck_init_allocator(&git__allocator); +#else + return git_stdalloc_init_allocator(&git__allocator); +#endif +} + +int git_allocator_global_init(void) +{ + /* + * We don't want to overwrite any allocator which has been set + * before the init function is called. + */ + if (git__allocator.gmalloc != git_failalloc_malloc) + return 0; + + return setup_default_allocator(); +} + +int git_allocator_setup(git_allocator *allocator) +{ + if (!allocator) + return setup_default_allocator(); + + memcpy(&git__allocator, allocator, sizeof(*allocator)); + return 0; +} diff --git a/src/util/alloc.h b/src/util/alloc.h new file mode 100644 index 000000000..04fb7e101 --- /dev/null +++ b/src/util/alloc.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_alloc_h__ +#define INCLUDE_alloc_h__ + +#include "git2/sys/alloc.h" + +extern git_allocator git__allocator; + +#define git__malloc(len) git__allocator.gmalloc(len, __FILE__, __LINE__) +#define git__calloc(nelem, elsize) git__allocator.gcalloc(nelem, elsize, __FILE__, __LINE__) +#define git__strdup(str) git__allocator.gstrdup(str, __FILE__, __LINE__) +#define git__strndup(str, n) git__allocator.gstrndup(str, n, __FILE__, __LINE__) +#define git__substrdup(str, n) git__allocator.gsubstrdup(str, n, __FILE__, __LINE__) +#define git__realloc(ptr, size) git__allocator.grealloc(ptr, size, __FILE__, __LINE__) +#define git__reallocarray(ptr, nelem, elsize) git__allocator.greallocarray(ptr, nelem, elsize, __FILE__, __LINE__) +#define git__mallocarray(nelem, elsize) git__allocator.gmallocarray(nelem, elsize, __FILE__, __LINE__) +#define git__free git__allocator.gfree + +/** + * This function is being called by our global setup routines to + * initialize the standard allocator. + */ +int git_allocator_global_init(void); + +/** + * Switch out libgit2's global memory allocator + * + * @param allocator The new allocator that should be used. All function pointers + * of it need to be set correctly. + * @return An error code or 0. + */ +int git_allocator_setup(git_allocator *allocator); + +#endif diff --git a/src/util/allocators/failalloc.c b/src/util/allocators/failalloc.c new file mode 100644 index 000000000..5257d1dec --- /dev/null +++ b/src/util/allocators/failalloc.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "failalloc.h" + +void *git_failalloc_malloc(size_t len, const char *file, int line) +{ + GIT_UNUSED(len); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void *git_failalloc_calloc(size_t nelem, size_t elsize, const char *file, int line) +{ + GIT_UNUSED(nelem); + GIT_UNUSED(elsize); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +char *git_failalloc_strdup(const char *str, const char *file, int line) +{ + GIT_UNUSED(str); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +char *git_failalloc_strndup(const char *str, size_t n, const char *file, int line) +{ + GIT_UNUSED(str); + GIT_UNUSED(n); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +char *git_failalloc_substrdup(const char *start, size_t n, const char *file, int line) +{ + GIT_UNUSED(start); + GIT_UNUSED(n); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line) +{ + GIT_UNUSED(ptr); + GIT_UNUSED(size); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void *git_failalloc_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) +{ + GIT_UNUSED(ptr); + GIT_UNUSED(nelem); + GIT_UNUSED(elsize); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void *git_failalloc_mallocarray(size_t nelem, size_t elsize, const char *file, int line) +{ + GIT_UNUSED(nelem); + GIT_UNUSED(elsize); + GIT_UNUSED(file); + GIT_UNUSED(line); + + return NULL; +} + +void git_failalloc_free(void *ptr) +{ + GIT_UNUSED(ptr); +} diff --git a/src/util/allocators/failalloc.h b/src/util/allocators/failalloc.h new file mode 100644 index 000000000..91264a0bb --- /dev/null +++ b/src/util/allocators/failalloc.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_allocators_failalloc_h__ +#define INCLUDE_allocators_failalloc_h__ + +#include "git2_util.h" + +extern void *git_failalloc_malloc(size_t len, const char *file, int line); +extern void *git_failalloc_calloc(size_t nelem, size_t elsize, const char *file, int line); +extern char *git_failalloc_strdup(const char *str, const char *file, int line); +extern char *git_failalloc_strndup(const char *str, size_t n, const char *file, int line); +extern char *git_failalloc_substrdup(const char *start, size_t n, const char *file, int line); +extern void *git_failalloc_realloc(void *ptr, size_t size, const char *file, int line); +extern void *git_failalloc_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line); +extern void *git_failalloc_mallocarray(size_t nelem, size_t elsize, const char *file, int line); +extern void git_failalloc_free(void *ptr); + +#endif diff --git a/src/util/allocators/stdalloc.c b/src/util/allocators/stdalloc.c new file mode 100644 index 000000000..2b36d9f3d --- /dev/null +++ b/src/util/allocators/stdalloc.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "stdalloc.h" + +static void *stdalloc__malloc(size_t len, const char *file, int line) +{ + void *ptr; + + GIT_UNUSED(file); + GIT_UNUSED(line); + +#ifdef GIT_DEBUG_STRICT_ALLOC + if (!len) + return NULL; +#endif + + ptr = malloc(len); + + if (!ptr) + git_error_set_oom(); + + return ptr; +} + +static void *stdalloc__calloc(size_t nelem, size_t elsize, const char *file, int line) +{ + void *ptr; + + GIT_UNUSED(file); + GIT_UNUSED(line); + +#ifdef GIT_DEBUG_STRICT_ALLOC + if (!elsize || !nelem) + return NULL; +#endif + + ptr = calloc(nelem, elsize); + + if (!ptr) + git_error_set_oom(); + + return ptr; +} + +static char *stdalloc__strdup(const char *str, const char *file, int line) +{ + char *ptr; + + GIT_UNUSED(file); + GIT_UNUSED(line); + + ptr = strdup(str); + + if (!ptr) + git_error_set_oom(); + + return ptr; +} + +static char *stdalloc__strndup(const char *str, size_t n, const char *file, int line) +{ + size_t length = 0, alloclength; + char *ptr; + + length = p_strnlen(str, n); + + if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) || + !(ptr = stdalloc__malloc(alloclength, file, line))) + return NULL; + + if (length) + memcpy(ptr, str, length); + + ptr[length] = '\0'; + + return ptr; +} + +static char *stdalloc__substrdup(const char *start, size_t n, const char *file, int line) +{ + char *ptr; + size_t alloclen; + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) || + !(ptr = stdalloc__malloc(alloclen, file, line))) + return NULL; + + memcpy(ptr, start, n); + ptr[n] = '\0'; + return ptr; +} + +static void *stdalloc__realloc(void *ptr, size_t size, const char *file, int line) +{ + void *new_ptr; + + GIT_UNUSED(file); + GIT_UNUSED(line); + +#ifdef GIT_DEBUG_STRICT_ALLOC + if (!size) + return NULL; +#endif + + new_ptr = realloc(ptr, size); + + if (!new_ptr) + git_error_set_oom(); + + return new_ptr; +} + +static void *stdalloc__reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) +{ + size_t newsize; + + if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize)) + return NULL; + + return stdalloc__realloc(ptr, newsize, file, line); +} + +static void *stdalloc__mallocarray(size_t nelem, size_t elsize, const char *file, int line) +{ + return stdalloc__reallocarray(NULL, nelem, elsize, file, line); +} + +static void stdalloc__free(void *ptr) +{ + free(ptr); +} + +int git_stdalloc_init_allocator(git_allocator *allocator) +{ + allocator->gmalloc = stdalloc__malloc; + allocator->gcalloc = stdalloc__calloc; + allocator->gstrdup = stdalloc__strdup; + allocator->gstrndup = stdalloc__strndup; + allocator->gsubstrdup = stdalloc__substrdup; + allocator->grealloc = stdalloc__realloc; + allocator->greallocarray = stdalloc__reallocarray; + allocator->gmallocarray = stdalloc__mallocarray; + allocator->gfree = stdalloc__free; + return 0; +} diff --git a/src/util/allocators/stdalloc.h b/src/util/allocators/stdalloc.h new file mode 100644 index 000000000..955038cb0 --- /dev/null +++ b/src/util/allocators/stdalloc.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_allocators_stdalloc_h__ +#define INCLUDE_allocators_stdalloc_h__ + +#include "git2_util.h" + +#include "alloc.h" + +int git_stdalloc_init_allocator(git_allocator *allocator); + +#endif diff --git a/src/util/allocators/win32_leakcheck.c b/src/util/allocators/win32_leakcheck.c new file mode 100644 index 000000000..fe06a14af --- /dev/null +++ b/src/util/allocators/win32_leakcheck.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "win32_leakcheck.h" + +#if defined(GIT_WIN32_LEAKCHECK) + +#include "win32/w32_leakcheck.h" + +static void *leakcheck_malloc(size_t len, const char *file, int line) +{ + void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); + if (!ptr) git_error_set_oom(); + return ptr; +} + +static void *leakcheck_calloc(size_t nelem, size_t elsize, const char *file, int line) +{ + void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); + if (!ptr) git_error_set_oom(); + return ptr; +} + +static char *leakcheck_strdup(const char *str, const char *file, int line) +{ + char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); + if (!ptr) git_error_set_oom(); + return ptr; +} + +static char *leakcheck_strndup(const char *str, size_t n, const char *file, int line) +{ + size_t length = 0, alloclength; + char *ptr; + + length = p_strnlen(str, n); + + if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) || + !(ptr = leakcheck_malloc(alloclength, file, line))) + return NULL; + + if (length) + memcpy(ptr, str, length); + + ptr[length] = '\0'; + + return ptr; +} + +static char *leakcheck_substrdup(const char *start, size_t n, const char *file, int line) +{ + char *ptr; + size_t alloclen; + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) || + !(ptr = leakcheck_malloc(alloclen, file, line))) + return NULL; + + memcpy(ptr, start, n); + ptr[n] = '\0'; + return ptr; +} + +static void *leakcheck_realloc(void *ptr, size_t size, const char *file, int line) +{ + void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32_leakcheck_stacktrace(1,file), line); + if (!new_ptr) git_error_set_oom(); + return new_ptr; +} + +static void *leakcheck_reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) +{ + size_t newsize; + + if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize)) + return NULL; + + return leakcheck_realloc(ptr, newsize, file, line); +} + +static void *leakcheck_mallocarray(size_t nelem, size_t elsize, const char *file, int line) +{ + return leakcheck_reallocarray(NULL, nelem, elsize, file, line); +} + +static void leakcheck_free(void *ptr) +{ + free(ptr); +} + +int git_win32_leakcheck_init_allocator(git_allocator *allocator) +{ + allocator->gmalloc = leakcheck_malloc; + allocator->gcalloc = leakcheck_calloc; + allocator->gstrdup = leakcheck_strdup; + allocator->gstrndup = leakcheck_strndup; + allocator->gsubstrdup = leakcheck_substrdup; + allocator->grealloc = leakcheck_realloc; + allocator->greallocarray = leakcheck_reallocarray; + allocator->gmallocarray = leakcheck_mallocarray; + allocator->gfree = leakcheck_free; + return 0; +} + +#else + +int git_win32_leakcheck_init_allocator(git_allocator *allocator) +{ + GIT_UNUSED(allocator); + git_error_set(GIT_EINVALID, "leakcheck memory allocator not available"); + return -1; +} + +#endif diff --git a/src/util/allocators/win32_leakcheck.h b/src/util/allocators/win32_leakcheck.h new file mode 100644 index 000000000..edcd9307f --- /dev/null +++ b/src/util/allocators/win32_leakcheck.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_allocators_win32_leakcheck_h +#define INCLUDE_allocators_win32_leakcheck_h + +#include "git2_util.h" + +#include "alloc.h" + +int git_win32_leakcheck_init_allocator(git_allocator *allocator); + +#endif diff --git a/src/util/array.h b/src/util/array.h new file mode 100644 index 000000000..cbab52ad1 --- /dev/null +++ b/src/util/array.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_array_h__ +#define INCLUDE_array_h__ + +#include "git2_util.h" + +/* + * Use this to declare a typesafe resizable array of items, a la: + * + * git_array_t(int) my_ints = GIT_ARRAY_INIT; + * ... + * int *i = git_array_alloc(my_ints); + * GIT_ERROR_CHECK_ALLOC(i); + * ... + * git_array_clear(my_ints); + * + * You may also want to do things like: + * + * typedef git_array_t(my_struct) my_struct_array_t; + */ +#define git_array_t(type) struct { type *ptr; size_t size, asize; } + +#define GIT_ARRAY_INIT { NULL, 0, 0 } + +#define git_array_init(a) \ + do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0) + +#define git_array_init_to_size(a, desired) \ + do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0) + +#define git_array_clear(a) \ + do { git__free((a).ptr); git_array_init(a); } while (0) + +#define GIT_ERROR_CHECK_ARRAY(a) GIT_ERROR_CHECK_ALLOC((a).ptr) + + +typedef git_array_t(char) git_array_generic_t; + +/* use a generic array for growth, return 0 on success */ +GIT_INLINE(int) git_array_grow(void *_a, size_t item_size) +{ + volatile git_array_generic_t *a = _a; + size_t new_size; + char *new_array; + + if (a->size < 8) { + new_size = 8; + } else { + if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, a->size, 3)) + goto on_oom; + new_size /= 2; + } + + if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL) + goto on_oom; + + a->ptr = new_array; + a->asize = new_size; + return 0; + +on_oom: + git_array_clear(*a); + return -1; +} + +#define git_array_alloc(a) \ + (((a).size < (a).asize || git_array_grow(&(a), sizeof(*(a).ptr)) == 0) ? \ + &(a).ptr[(a).size++] : (void *)NULL) + +#define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : (void *)NULL) + +#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : (void *)NULL) + +#define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : (void *)NULL) + +#define git_array_size(a) (a).size + +#define git_array_valid_index(a, i) ((i) < (a).size) + +#define git_array_foreach(a, i, element) \ + for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++) + +GIT_INLINE(int) git_array__search( + size_t *out, + void *array_ptr, + size_t item_size, + size_t array_len, + int (*compare)(const void *, const void *), + const void *key) +{ + size_t lim; + unsigned char *part, *array = array_ptr, *base = array_ptr; + int cmp = -1; + + for (lim = array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1) * item_size; + cmp = (*compare)(key, part); + + if (cmp == 0) { + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ + base = part + 1 * item_size; + lim--; + } /* else take left partition */ + } + + if (out) + *out = (base - array) / item_size; + + return (cmp == 0) ? 0 : GIT_ENOTFOUND; +} + +#define git_array_search(out, a, cmp, key) \ + git_array__search(out, (a).ptr, sizeof(*(a).ptr), (a).size, \ + (cmp), (key)) + +#endif diff --git a/src/util/assert_safe.h b/src/util/assert_safe.h new file mode 100644 index 000000000..8c261100f --- /dev/null +++ b/src/util/assert_safe.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_assert_safe_h__ +#define INCLUDE_assert_safe_h__ + +/* + * In a debug build, we'll assert(3) for aide in debugging. In release + * builds, we will provide macros that will set an error message that + * indicate a failure and return. Note that memory leaks can occur in + * a release-mode assertion failure -- it is impractical to provide + * safe clean up routines in these very extreme failures, but care + * should be taken to not leak very large objects. + */ + +#if (defined(_DEBUG) || defined(GIT_ASSERT_HARD)) && GIT_ASSERT_HARD != 0 +# include + +# define GIT_ASSERT(expr) assert(expr) +# define GIT_ASSERT_ARG(expr) assert(expr) + +# define GIT_ASSERT_WITH_RETVAL(expr, fail) assert(expr) +# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) assert(expr) +#else + +/** Internal consistency check to stop the function. */ +# define GIT_ASSERT(expr) GIT_ASSERT_WITH_RETVAL(expr, -1) + +/** + * Assert that a consumer-provided argument is valid, setting an + * actionable error message and returning -1 if it is not. + */ +# define GIT_ASSERT_ARG(expr) GIT_ASSERT_ARG_WITH_RETVAL(expr, -1) + +/** Internal consistency check to return the `fail` param on failure. */ +# define GIT_ASSERT_WITH_RETVAL(expr, fail) \ + GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INTERNAL, "unrecoverable internal error", fail) + +/** + * Assert that a consumer-provided argument is valid, setting an + * actionable error message and returning the `fail` param if not. + */ +# define GIT_ASSERT_ARG_WITH_RETVAL(expr, fail) \ + GIT_ASSERT__WITH_RETVAL(expr, GIT_ERROR_INVALID, "invalid argument", fail) + +# define GIT_ASSERT__WITH_RETVAL(expr, code, msg, fail) do { \ + if (!(expr)) { \ + git_error_set(code, "%s: '%s'", msg, #expr); \ + return fail; \ + } \ + } while(0) + +#endif /* GIT_ASSERT_HARD */ + +#endif diff --git a/src/util/bitvec.h b/src/util/bitvec.h new file mode 100644 index 000000000..544832d95 --- /dev/null +++ b/src/util/bitvec.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_bitvec_h__ +#define INCLUDE_bitvec_h__ + +#include "common.h" + +/* + * This is a silly little fixed length bit vector type that will store + * vectors of 64 bits or less directly in the structure and allocate + * memory for vectors longer than 64 bits. You can use the two versions + * transparently through the API and avoid heap allocation completely when + * using a short bit vector as a result. + */ +typedef struct { + size_t length; + union { + uint64_t *words; + uint64_t bits; + } u; +} git_bitvec; + +GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity) +{ + memset(bv, 0x0, sizeof(*bv)); + + if (capacity >= 64) { + bv->length = (capacity / 64) + 1; + bv->u.words = git__calloc(bv->length, sizeof(uint64_t)); + if (!bv->u.words) + return -1; + } + + return 0; +} + +#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64)) +#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits) + +GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on) +{ + uint64_t *word = GIT_BITVEC_WORD(bv, bit); + uint64_t mask = GIT_BITVEC_MASK(bit); + + if (on) + *word |= mask; + else + *word &= ~mask; +} + +GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit) +{ + uint64_t *word = GIT_BITVEC_WORD(bv, bit); + return (*word & GIT_BITVEC_MASK(bit)) != 0; +} + +GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv) +{ + if (!bv->length) + bv->u.bits = 0; + else + memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t)); +} + +GIT_INLINE(void) git_bitvec_free(git_bitvec *bv) +{ + if (bv->length) + git__free(bv->u.words); +} + +#endif diff --git a/src/util/cc-compat.h b/src/util/cc-compat.h new file mode 100644 index 000000000..a0971e86c --- /dev/null +++ b/src/util/cc-compat.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_cc_compat_h__ +#define INCLUDE_cc_compat_h__ + +#include + +/* + * See if our compiler is known to support flexible array members. + */ +#ifndef GIT_FLEX_ARRAY +# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define GIT_FLEX_ARRAY /* empty */ +# elif defined(__GNUC__) +# if (__GNUC__ >= 3) +# define GIT_FLEX_ARRAY /* empty */ +# else +# define GIT_FLEX_ARRAY 0 /* older GNU extension */ +# endif +# endif + +/* Default to safer but a bit wasteful traditional style */ +# ifndef GIT_FLEX_ARRAY +# define GIT_FLEX_ARRAY 1 +# endif +#endif + +#if defined(__GNUC__) +# define GIT_ALIGN(x,size) x __attribute__ ((aligned(size))) +#elif defined(_MSC_VER) +# define GIT_ALIGN(x,size) __declspec(align(size)) x +#else +# define GIT_ALIGN(x,size) x +#endif + +#if defined(__GNUC__) +# define GIT_UNUSED(x) \ + do { \ + __typeof__(x) _unused __attribute__((unused)); \ + _unused = (x); \ + } while (0) +#else +# define GIT_UNUSED(x) ((void)(x)) +#endif + +/* Define the printf format specifier to use for size_t output */ +#if defined(_MSC_VER) || defined(__MINGW32__) + +/* Visual Studio 2012 and prior lack PRId64 entirely */ +# ifndef PRId64 +# define PRId64 "I64d" +# endif + +/* The first block is needed to avoid warnings on MingW amd64 */ +# if (SIZE_MAX == ULLONG_MAX) +# define PRIuZ "I64u" +# define PRIxZ "I64x" +# define PRIXZ "I64X" +# define PRIdZ "I64d" +# else +# define PRIuZ "Iu" +# define PRIxZ "Ix" +# define PRIXZ "IX" +# define PRIdZ "Id" +# endif + +#else +# define PRIuZ "zu" +# define PRIxZ "zx" +# define PRIXZ "zX" +# define PRIdZ "zd" +#endif + +/* Microsoft Visual C/C++ */ +#if defined(_MSC_VER) +/* disable "deprecated function" warnings */ +# pragma warning ( disable : 4996 ) +/* disable "conditional expression is constant" level 4 warnings */ +# pragma warning ( disable : 4127 ) +#endif + +#if defined (_MSC_VER) + typedef unsigned char bool; +# ifndef true +# define true 1 +# endif +# ifndef false +# define false 0 +# endif +#else +# include +#endif + +#ifndef va_copy +# ifdef __va_copy +# define va_copy(dst, src) __va_copy(dst, src) +# else +# define va_copy(dst, src) ((dst) = (src)) +# endif +#endif + +#endif diff --git a/src/util/date.c b/src/util/date.c new file mode 100644 index 000000000..4d757e21a --- /dev/null +++ b/src/util/date.c @@ -0,0 +1,899 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2_util.h" + +#ifndef GIT_WIN32 +#include +#endif + +#include "util.h" +#include "posix.h" +#include "date.h" + +#include +#include + +typedef enum { + DATE_NORMAL = 0, + DATE_RELATIVE, + DATE_SHORT, + DATE_LOCAL, + DATE_ISO8601, + DATE_RFC2822, + DATE_RAW +} date_mode; + +/* + * This is like mktime, but without normalization of tm_wday and tm_yday. + */ +static git_time_t tm_to_time_t(const struct tm *tm) +{ + static const int mdays[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 + }; + int year = tm->tm_year - 70; + int month = tm->tm_mon; + int day = tm->tm_mday; + + if (year < 0 || year > 129) /* algo only works for 1970-2099 */ + return -1; + if (month < 0 || month > 11) /* array bounds */ + return -1; + if (month < 2 || (year + 2) % 4) + day--; + if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0) + return -1; + return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL + + tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec; +} + +static const char *month_names[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" +}; + +static const char *weekday_names[] = { + "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays" +}; + + + +/* + * Check these. And note how it doesn't do the summer-time conversion. + * + * In my world, it's always summer, and things are probably a bit off + * in other ways too. + */ +static const struct { + const char *name; + int offset; + int dst; +} timezone_names[] = { + { "IDLW", -12, 0, }, /* International Date Line West */ + { "NT", -11, 0, }, /* Nome */ + { "CAT", -10, 0, }, /* Central Alaska */ + { "HST", -10, 0, }, /* Hawaii Standard */ + { "HDT", -10, 1, }, /* Hawaii Daylight */ + { "YST", -9, 0, }, /* Yukon Standard */ + { "YDT", -9, 1, }, /* Yukon Daylight */ + { "PST", -8, 0, }, /* Pacific Standard */ + { "PDT", -8, 1, }, /* Pacific Daylight */ + { "MST", -7, 0, }, /* Mountain Standard */ + { "MDT", -7, 1, }, /* Mountain Daylight */ + { "CST", -6, 0, }, /* Central Standard */ + { "CDT", -6, 1, }, /* Central Daylight */ + { "EST", -5, 0, }, /* Eastern Standard */ + { "EDT", -5, 1, }, /* Eastern Daylight */ + { "AST", -3, 0, }, /* Atlantic Standard */ + { "ADT", -3, 1, }, /* Atlantic Daylight */ + { "WAT", -1, 0, }, /* West Africa */ + + { "GMT", 0, 0, }, /* Greenwich Mean */ + { "UTC", 0, 0, }, /* Universal (Coordinated) */ + { "Z", 0, 0, }, /* Zulu, alias for UTC */ + + { "WET", 0, 0, }, /* Western European */ + { "BST", 0, 1, }, /* British Summer */ + { "CET", +1, 0, }, /* Central European */ + { "MET", +1, 0, }, /* Middle European */ + { "MEWT", +1, 0, }, /* Middle European Winter */ + { "MEST", +1, 1, }, /* Middle European Summer */ + { "CEST", +1, 1, }, /* Central European Summer */ + { "MESZ", +1, 1, }, /* Middle European Summer */ + { "FWT", +1, 0, }, /* French Winter */ + { "FST", +1, 1, }, /* French Summer */ + { "EET", +2, 0, }, /* Eastern Europe */ + { "EEST", +2, 1, }, /* Eastern European Daylight */ + { "WAST", +7, 0, }, /* West Australian Standard */ + { "WADT", +7, 1, }, /* West Australian Daylight */ + { "CCT", +8, 0, }, /* China Coast */ + { "JST", +9, 0, }, /* Japan Standard */ + { "EAST", +10, 0, }, /* Eastern Australian Standard */ + { "EADT", +10, 1, }, /* Eastern Australian Daylight */ + { "GST", +10, 0, }, /* Guam Standard */ + { "NZT", +12, 0, }, /* New Zealand */ + { "NZST", +12, 0, }, /* New Zealand Standard */ + { "NZDT", +12, 1, }, /* New Zealand Daylight */ + { "IDLE", +12, 0, }, /* International Date Line East */ +}; + +static size_t match_string(const char *date, const char *str) +{ + size_t i = 0; + + for (i = 0; *date; date++, str++, i++) { + if (*date == *str) + continue; + if (toupper(*date) == toupper(*str)) + continue; + if (!isalnum(*date)) + break; + return 0; + } + return i; +} + +static int skip_alpha(const char *date) +{ + int i = 0; + do { + i++; + } while (isalpha(date[i])); + return i; +} + +/* +* Parse month, weekday, or timezone name +*/ +static size_t match_alpha(const char *date, struct tm *tm, int *offset) +{ + unsigned int i; + + for (i = 0; i < 12; i++) { + size_t match = match_string(date, month_names[i]); + if (match >= 3) { + tm->tm_mon = i; + return match; + } + } + + for (i = 0; i < 7; i++) { + size_t match = match_string(date, weekday_names[i]); + if (match >= 3) { + tm->tm_wday = i; + return match; + } + } + + for (i = 0; i < ARRAY_SIZE(timezone_names); i++) { + size_t match = match_string(date, timezone_names[i].name); + if (match >= 3 || match == strlen(timezone_names[i].name)) { + int off = timezone_names[i].offset; + + /* This is bogus, but we like summer */ + off += timezone_names[i].dst; + + /* Only use the tz name offset if we don't have anything better */ + if (*offset == -1) + *offset = 60*off; + + return match; + } + } + + if (match_string(date, "PM") == 2) { + tm->tm_hour = (tm->tm_hour % 12) + 12; + return 2; + } + + if (match_string(date, "AM") == 2) { + tm->tm_hour = (tm->tm_hour % 12) + 0; + return 2; + } + + /* BAD */ + return skip_alpha(date); +} + +static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm) +{ + if (month > 0 && month < 13 && day > 0 && day < 32) { + struct tm check = *tm; + struct tm *r = (now_tm ? &check : tm); + git_time_t specified; + + r->tm_mon = month - 1; + r->tm_mday = day; + if (year == -1) { + if (!now_tm) + return 1; + r->tm_year = now_tm->tm_year; + } + else if (year >= 1970 && year < 2100) + r->tm_year = year - 1900; + else if (year > 70 && year < 100) + r->tm_year = year; + else if (year < 38) + r->tm_year = year + 100; + else + return 0; + if (!now_tm) + return 1; + + specified = tm_to_time_t(r); + + /* Be it commit time or author time, it does not make + * sense to specify timestamp way into the future. Make + * sure it is not later than ten days from now... + */ + if (now + 10*24*3600 < specified) + return 0; + tm->tm_mon = r->tm_mon; + tm->tm_mday = r->tm_mday; + if (year != -1) + tm->tm_year = r->tm_year; + return 1; + } + return 0; +} + +static size_t match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm) +{ + time_t now; + struct tm now_tm; + struct tm *refuse_future; + long num2, num3; + + num2 = strtol(end+1, &end, 10); + num3 = -1; + if (*end == c && isdigit(end[1])) + num3 = strtol(end+1, &end, 10); + + /* Time? Date? */ + switch (c) { + case ':': + if (num3 < 0) + num3 = 0; + if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) { + tm->tm_hour = num; + tm->tm_min = num2; + tm->tm_sec = num3; + break; + } + return 0; + + case '-': + case '/': + case '.': + now = time(NULL); + refuse_future = NULL; + if (p_gmtime_r(&now, &now_tm)) + refuse_future = &now_tm; + + if (num > 70) { + /* yyyy-mm-dd? */ + if (is_date(num, num2, num3, refuse_future, now, tm)) + break; + /* yyyy-dd-mm? */ + if (is_date(num, num3, num2, refuse_future, now, tm)) + break; + } + /* Our eastern European friends say dd.mm.yy[yy] + * is the norm there, so giving precedence to + * mm/dd/yy[yy] form only when separator is not '.' + */ + if (c != '.' && + is_date(num3, num, num2, refuse_future, now, tm)) + break; + /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */ + if (is_date(num3, num2, num, refuse_future, now, tm)) + break; + /* Funny European mm.dd.yy */ + if (c == '.' && + is_date(num3, num, num2, refuse_future, now, tm)) + break; + return 0; + } + return end - date; +} + +/* + * Have we filled in any part of the time/date yet? + * We just do a binary 'and' to see if the sign bit + * is set in all the values. + */ +static int nodate(struct tm *tm) +{ + return (tm->tm_year & + tm->tm_mon & + tm->tm_mday & + tm->tm_hour & + tm->tm_min & + tm->tm_sec) < 0; +} + +/* + * We've seen a digit. Time? Year? Date? + */ +static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt) +{ + size_t n; + char *end; + unsigned long num; + + num = strtoul(date, &end, 10); + + /* + * Seconds since 1970? We trigger on that for any numbers with + * more than 8 digits. This is because we don't want to rule out + * numbers like 20070606 as a YYYYMMDD date. + */ + if (num >= 100000000 && nodate(tm)) { + time_t time = num; + if (p_gmtime_r(&time, tm)) { + *tm_gmt = 1; + return end - date; + } + } + + /* + * Check for special formats: num[-.:/]num[same]num + */ + switch (*end) { + case ':': + case '.': + case '/': + case '-': + if (isdigit(end[1])) { + size_t match = match_multi_number(num, *end, date, end, tm); + if (match) + return match; + } + } + + /* + * None of the special formats? Try to guess what + * the number meant. We use the number of digits + * to make a more educated guess.. + */ + n = 0; + do { + n++; + } while (isdigit(date[n])); + + /* Four-digit year or a timezone? */ + if (n == 4) { + if (num <= 1400 && *offset == -1) { + unsigned int minutes = num % 100; + unsigned int hours = num / 100; + *offset = hours*60 + minutes; + } else if (num > 1900 && num < 2100) + tm->tm_year = num - 1900; + return n; + } + + /* + * Ignore lots of numerals. We took care of 4-digit years above. + * Days or months must be one or two digits. + */ + if (n > 2) + return n; + + /* + * NOTE! We will give precedence to day-of-month over month or + * year numbers in the 1-12 range. So 05 is always "mday 5", + * unless we already have a mday.. + * + * IOW, 01 Apr 05 parses as "April 1st, 2005". + */ + if (num > 0 && num < 32 && tm->tm_mday < 0) { + tm->tm_mday = num; + return n; + } + + /* Two-digit year? */ + if (n == 2 && tm->tm_year < 0) { + if (num < 10 && tm->tm_mday >= 0) { + tm->tm_year = num + 100; + return n; + } + if (num >= 70) { + tm->tm_year = num; + return n; + } + } + + if (num > 0 && num < 13 && tm->tm_mon < 0) + tm->tm_mon = num-1; + + return n; +} + +static size_t match_tz(const char *date, int *offp) +{ + char *end; + int hour = strtoul(date + 1, &end, 10); + size_t n = end - (date + 1); + int min = 0; + + if (n == 4) { + /* hhmm */ + min = hour % 100; + hour = hour / 100; + } else if (n != 2) { + min = 99; /* random stuff */ + } else if (*end == ':') { + /* hh:mm? */ + min = strtoul(end + 1, &end, 10); + if (end - (date + 1) != 5) + min = 99; /* random stuff */ + } /* otherwise we parsed "hh" */ + + /* + * Don't accept any random stuff. Even though some places have + * offset larger than 12 hours (e.g. Pacific/Kiritimati is at + * UTC+14), there is something wrong if hour part is much + * larger than that. We might also want to check that the + * minutes are divisible by 15 or something too. (Offset of + * Kathmandu, Nepal is UTC+5:45) + */ + if (min < 60 && hour < 24) { + int offset = hour * 60 + min; + if (*date == '-') + offset = -offset; + *offp = offset; + } + return end - date; +} + +/* + * Parse a string like "0 +0000" as ancient timestamp near epoch, but + * only when it appears not as part of any other string. + */ +static int match_object_header_date(const char *date, git_time_t *timestamp, int *offset) +{ + char *end; + unsigned long stamp; + int ofs; + + if (*date < '0' || '9' <= *date) + return -1; + stamp = strtoul(date, &end, 10); + if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-')) + return -1; + date = end + 2; + ofs = strtol(date, &end, 10); + if ((*end != '\0' && (*end != '\n')) || end != date + 4) + return -1; + ofs = (ofs / 100) * 60 + (ofs % 100); + if (date[-1] == '-') + ofs = -ofs; + *timestamp = stamp; + *offset = ofs; + return 0; +} + +/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822 + (i.e. English) day/month names, and it doesn't work correctly with %z. */ +static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset) +{ + struct tm tm; + int tm_gmt; + git_time_t dummy_timestamp; + int dummy_offset; + + if (!timestamp) + timestamp = &dummy_timestamp; + if (!offset) + offset = &dummy_offset; + + memset(&tm, 0, sizeof(tm)); + tm.tm_year = -1; + tm.tm_mon = -1; + tm.tm_mday = -1; + tm.tm_isdst = -1; + tm.tm_hour = -1; + tm.tm_min = -1; + tm.tm_sec = -1; + *offset = -1; + tm_gmt = 0; + + if (*date == '@' && + !match_object_header_date(date + 1, timestamp, offset)) + return 0; /* success */ + for (;;) { + size_t match = 0; + unsigned char c = *date; + + /* Stop at end of string or newline */ + if (!c || c == '\n') + break; + + if (isalpha(c)) + match = match_alpha(date, &tm, offset); + else if (isdigit(c)) + match = match_digit(date, &tm, offset, &tm_gmt); + else if ((c == '-' || c == '+') && isdigit(date[1])) + match = match_tz(date, offset); + + if (!match) { + /* BAD */ + match = 1; + } + + date += match; + } + + /* mktime uses local timezone */ + *timestamp = tm_to_time_t(&tm); + if (*offset == -1) + *offset = (int)((time_t)*timestamp - mktime(&tm)) / 60; + + if (*timestamp == (git_time_t)-1) + return -1; + + if (!tm_gmt) + *timestamp -= *offset * 60; + return 0; /* success */ +} + + +/* + * Relative time update (eg "2 days ago"). If we haven't set the time + * yet, we need to set it from current time. + */ +static git_time_t update_tm(struct tm *tm, struct tm *now, unsigned long sec) +{ + time_t n; + + if (tm->tm_mday < 0) + tm->tm_mday = now->tm_mday; + if (tm->tm_mon < 0) + tm->tm_mon = now->tm_mon; + if (tm->tm_year < 0) { + tm->tm_year = now->tm_year; + if (tm->tm_mon > now->tm_mon) + tm->tm_year--; + } + + n = mktime(tm) - sec; + p_localtime_r(&n, tm); + return n; +} + +static void date_now(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + update_tm(tm, now, 0); +} + +static void date_yesterday(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + update_tm(tm, now, 24*60*60); +} + +static void date_time(struct tm *tm, struct tm *now, int hour) +{ + if (tm->tm_hour < hour) + date_yesterday(tm, now, NULL); + tm->tm_hour = hour; + tm->tm_min = 0; + tm->tm_sec = 0; +} + +static void date_midnight(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + date_time(tm, now, 0); +} + +static void date_noon(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + date_time(tm, now, 12); +} + +static void date_tea(struct tm *tm, struct tm *now, int *num) +{ + GIT_UNUSED(num); + date_time(tm, now, 17); +} + +static void date_pm(struct tm *tm, struct tm *now, int *num) +{ + int hour, n = *num; + *num = 0; + GIT_UNUSED(now); + + hour = tm->tm_hour; + if (n) { + hour = n; + tm->tm_min = 0; + tm->tm_sec = 0; + } + tm->tm_hour = (hour % 12) + 12; +} + +static void date_am(struct tm *tm, struct tm *now, int *num) +{ + int hour, n = *num; + *num = 0; + GIT_UNUSED(now); + + hour = tm->tm_hour; + if (n) { + hour = n; + tm->tm_min = 0; + tm->tm_sec = 0; + } + tm->tm_hour = (hour % 12); +} + +static void date_never(struct tm *tm, struct tm *now, int *num) +{ + time_t n = 0; + GIT_UNUSED(now); + GIT_UNUSED(num); + p_localtime_r(&n, tm); +} + +static const struct special { + const char *name; + void (*fn)(struct tm *, struct tm *, int *); +} special[] = { + { "yesterday", date_yesterday }, + { "noon", date_noon }, + { "midnight", date_midnight }, + { "tea", date_tea }, + { "PM", date_pm }, + { "AM", date_am }, + { "never", date_never }, + { "now", date_now }, + { NULL } +}; + +static const char *number_name[] = { + "zero", "one", "two", "three", "four", + "five", "six", "seven", "eight", "nine", "ten", +}; + +static const struct typelen { + const char *type; + int length; +} typelen[] = { + { "seconds", 1 }, + { "minutes", 60 }, + { "hours", 60*60 }, + { "days", 24*60*60 }, + { "weeks", 7*24*60*60 }, + { NULL } +}; + +static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched) +{ + const struct typelen *tl; + const struct special *s; + const char *end = date; + int i; + + while (isalpha(*++end)) + /* scan to non-alpha */; + + for (i = 0; i < 12; i++) { + size_t match = match_string(date, month_names[i]); + if (match >= 3) { + tm->tm_mon = i; + *touched = 1; + return end; + } + } + + for (s = special; s->name; s++) { + size_t len = strlen(s->name); + if (match_string(date, s->name) == len) { + s->fn(tm, now, num); + *touched = 1; + return end; + } + } + + if (!*num) { + for (i = 1; i < 11; i++) { + size_t len = strlen(number_name[i]); + if (match_string(date, number_name[i]) == len) { + *num = i; + *touched = 1; + return end; + } + } + if (match_string(date, "last") == 4) { + *num = 1; + *touched = 1; + } + return end; + } + + tl = typelen; + while (tl->type) { + size_t len = strlen(tl->type); + if (match_string(date, tl->type) >= len-1) { + update_tm(tm, now, tl->length * (unsigned long)*num); + *num = 0; + *touched = 1; + return end; + } + tl++; + } + + for (i = 0; i < 7; i++) { + size_t match = match_string(date, weekday_names[i]); + if (match >= 3) { + int diff, n = *num -1; + *num = 0; + + diff = tm->tm_wday - i; + if (diff <= 0) + n++; + diff += 7*n; + + update_tm(tm, now, diff * 24 * 60 * 60); + *touched = 1; + return end; + } + } + + if (match_string(date, "months") >= 5) { + int n; + update_tm(tm, now, 0); /* fill in date fields if needed */ + n = tm->tm_mon - *num; + *num = 0; + while (n < 0) { + n += 12; + tm->tm_year--; + } + tm->tm_mon = n; + *touched = 1; + return end; + } + + if (match_string(date, "years") >= 4) { + update_tm(tm, now, 0); /* fill in date fields if needed */ + tm->tm_year -= *num; + *num = 0; + *touched = 1; + return end; + } + + return end; +} + +static const char *approxidate_digit(const char *date, struct tm *tm, int *num) +{ + char *end; + unsigned long number = strtoul(date, &end, 10); + + switch (*end) { + case ':': + case '.': + case '/': + case '-': + if (isdigit(end[1])) { + size_t match = match_multi_number(number, *end, date, end, tm); + if (match) + return date + match; + } + } + + /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */ + if (date[0] != '0' || end - date <= 2) + *num = number; + return end; +} + +/* + * Do we have a pending number at the end, or when + * we see a new one? Let's assume it's a month day, + * as in "Dec 6, 1992" + */ +static void pending_number(struct tm *tm, int *num) +{ + int number = *num; + + if (number) { + *num = 0; + if (tm->tm_mday < 0 && number < 32) + tm->tm_mday = number; + else if (tm->tm_mon < 0 && number < 13) + tm->tm_mon = number-1; + else if (tm->tm_year < 0) { + if (number > 1969 && number < 2100) + tm->tm_year = number - 1900; + else if (number > 69 && number < 100) + tm->tm_year = number; + else if (number < 38) + tm->tm_year = 100 + number; + /* We mess up for number = 00 ? */ + } + } +} + +static git_time_t approxidate_str(const char *date, + time_t time_sec, + int *error_ret) +{ + int number = 0; + int touched = 0; + struct tm tm = {0}, now; + + p_localtime_r(&time_sec, &tm); + now = tm; + + tm.tm_year = -1; + tm.tm_mon = -1; + tm.tm_mday = -1; + + for (;;) { + unsigned char c = *date; + if (!c) + break; + date++; + if (isdigit(c)) { + pending_number(&tm, &number); + date = approxidate_digit(date-1, &tm, &number); + touched = 1; + continue; + } + if (isalpha(c)) + date = approxidate_alpha(date-1, &tm, &now, &number, &touched); + } + pending_number(&tm, &number); + if (!touched) + *error_ret = -1; + return update_tm(&tm, &now, 0); +} + +int git_date_parse(git_time_t *out, const char *date) +{ + time_t time_sec; + git_time_t timestamp; + int offset, error_ret=0; + + if (!parse_date_basic(date, ×tamp, &offset)) { + *out = timestamp; + return 0; + } + + if (time(&time_sec) == -1) + return -1; + + *out = approxidate_str(date, time_sec, &error_ret); + return error_ret; +} + +int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset) +{ + time_t t; + struct tm gmt; + + GIT_ASSERT_ARG(out); + + t = (time_t) (time + offset * 60); + + if (p_gmtime_r(&t, &gmt) == NULL) + return -1; + + return git_str_printf(out, "%.3s, %u %.3s %.4u %02u:%02u:%02u %+03d%02d", + weekday_names[gmt.tm_wday], + gmt.tm_mday, + month_names[gmt.tm_mon], + gmt.tm_year + 1900, + gmt.tm_hour, gmt.tm_min, gmt.tm_sec, + offset / 60, offset % 60); +} + diff --git a/src/util/date.h b/src/util/date.h new file mode 100644 index 000000000..7ebd3c30e --- /dev/null +++ b/src/util/date.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_date_h__ +#define INCLUDE_date_h__ + +#include "util.h" +#include "str.h" + +/* + * Parse a string into a value as a git_time_t. + * + * Sample valid input: + * - "yesterday" + * - "July 17, 2003" + * - "2003-7-17 08:23" + */ +extern int git_date_parse(git_time_t *out, const char *date); + +/* + * Format a git_time as a RFC2822 string + * + * @param out buffer to store formatted date + * @param time the time to be formatted + * @param offset the timezone offset + * @return 0 if successful; -1 on error + */ +extern int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset); + +#endif diff --git a/src/util/filebuf.c b/src/util/filebuf.c new file mode 100644 index 000000000..e014d43b2 --- /dev/null +++ b/src/util/filebuf.c @@ -0,0 +1,595 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "filebuf.h" + +#include "futils.h" + +static const size_t WRITE_BUFFER_SIZE = (4096 * 2); + +enum buferr_t { + BUFERR_OK = 0, + BUFERR_WRITE, + BUFERR_ZLIB, + BUFERR_MEM +}; + +#define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; } + +static int verify_last_error(git_filebuf *file) +{ + switch (file->last_error) { + case BUFERR_WRITE: + git_error_set(GIT_ERROR_OS, "failed to write out file"); + return -1; + + case BUFERR_MEM: + git_error_set_oom(); + return -1; + + case BUFERR_ZLIB: + git_error_set(GIT_ERROR_ZLIB, + "Buffer error when writing out ZLib data"); + return -1; + + default: + return 0; + } +} + +static int lock_file(git_filebuf *file, int flags, mode_t mode) +{ + if (git_fs_path_exists(file->path_lock) == true) { + git_error_clear(); /* actual OS error code just confuses */ + git_error_set(GIT_ERROR_OS, + "failed to lock file '%s' for writing", file->path_lock); + return GIT_ELOCKED; + } + + /* create path to the file buffer is required */ + if (flags & GIT_FILEBUF_CREATE_LEADING_DIRS) { + /* XXX: Should dirmode here be configurable? Or is 0777 always fine? */ + file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, mode); + } else { + file->fd = git_futils_creat_locked(file->path_lock, mode); + } + + if (file->fd < 0) + return file->fd; + + file->fd_is_open = true; + + if ((flags & GIT_FILEBUF_APPEND) && git_fs_path_exists(file->path_original) == true) { + git_file source; + char buffer[GIT_BUFSIZE_FILEIO]; + ssize_t read_bytes; + int error = 0; + + source = p_open(file->path_original, O_RDONLY); + if (source < 0) { + git_error_set(GIT_ERROR_OS, + "failed to open file '%s' for reading", + file->path_original); + return -1; + } + + while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) { + if ((error = p_write(file->fd, buffer, read_bytes)) < 0) + break; + if (file->compute_digest) + git_hash_update(&file->digest, buffer, read_bytes); + } + + p_close(source); + + if (read_bytes < 0) { + git_error_set(GIT_ERROR_OS, "failed to read file '%s'", file->path_original); + return -1; + } else if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to write file '%s'", file->path_lock); + return -1; + } + } + + return 0; +} + +void git_filebuf_cleanup(git_filebuf *file) +{ + if (file->fd_is_open && file->fd >= 0) + p_close(file->fd); + + if (file->created_lock && !file->did_rename && file->path_lock && git_fs_path_exists(file->path_lock)) + p_unlink(file->path_lock); + + if (file->compute_digest) { + git_hash_ctx_cleanup(&file->digest); + file->compute_digest = 0; + } + + if (file->buffer) + git__free(file->buffer); + + /* use the presence of z_buf to decide if we need to deflateEnd */ + if (file->z_buf) { + git__free(file->z_buf); + deflateEnd(&file->zs); + } + + if (file->path_original) + git__free(file->path_original); + if (file->path_lock) + git__free(file->path_lock); + + memset(file, 0x0, sizeof(git_filebuf)); + file->fd = -1; +} + +GIT_INLINE(int) flush_buffer(git_filebuf *file) +{ + int result = file->write(file, file->buffer, file->buf_pos); + file->buf_pos = 0; + return result; +} + +int git_filebuf_flush(git_filebuf *file) +{ + return flush_buffer(file); +} + +static int write_normal(git_filebuf *file, void *source, size_t len) +{ + if (len > 0) { + if (p_write(file->fd, (void *)source, len) < 0) { + file->last_error = BUFERR_WRITE; + return -1; + } + + if (file->compute_digest) + git_hash_update(&file->digest, source, len); + } + + return 0; +} + +static int write_deflate(git_filebuf *file, void *source, size_t len) +{ + z_stream *zs = &file->zs; + + if (len > 0 || file->flush_mode == Z_FINISH) { + zs->next_in = source; + zs->avail_in = (uInt)len; + + do { + size_t have; + + zs->next_out = file->z_buf; + zs->avail_out = (uInt)file->buf_size; + + if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) { + file->last_error = BUFERR_ZLIB; + return -1; + } + + have = file->buf_size - (size_t)zs->avail_out; + + if (p_write(file->fd, file->z_buf, have) < 0) { + file->last_error = BUFERR_WRITE; + return -1; + } + + } while (zs->avail_out == 0); + + GIT_ASSERT(zs->avail_in == 0); + + if (file->compute_digest) + git_hash_update(&file->digest, source, len); + } + + return 0; +} + +#define MAX_SYMLINK_DEPTH 5 + +static int resolve_symlink(git_str *out, const char *path) +{ + int i, error, root; + ssize_t ret; + struct stat st; + git_str curpath = GIT_STR_INIT, target = GIT_STR_INIT; + + if ((error = git_str_grow(&target, GIT_PATH_MAX + 1)) < 0 || + (error = git_str_puts(&curpath, path)) < 0) + return error; + + for (i = 0; i < MAX_SYMLINK_DEPTH; i++) { + error = p_lstat(curpath.ptr, &st); + if (error < 0 && errno == ENOENT) { + error = git_str_puts(out, curpath.ptr); + goto cleanup; + } + + if (error < 0) { + git_error_set(GIT_ERROR_OS, "failed to stat '%s'", curpath.ptr); + error = -1; + goto cleanup; + } + + if (!S_ISLNK(st.st_mode)) { + error = git_str_puts(out, curpath.ptr); + goto cleanup; + } + + ret = p_readlink(curpath.ptr, target.ptr, GIT_PATH_MAX); + if (ret < 0) { + git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", curpath.ptr); + error = -1; + goto cleanup; + } + + if (ret == GIT_PATH_MAX) { + git_error_set(GIT_ERROR_INVALID, "symlink target too long"); + error = -1; + goto cleanup; + } + + /* readlink(2) won't NUL-terminate for us */ + target.ptr[ret] = '\0'; + target.size = ret; + + root = git_fs_path_root(target.ptr); + if (root >= 0) { + if ((error = git_str_sets(&curpath, target.ptr)) < 0) + goto cleanup; + } else { + git_str dir = GIT_STR_INIT; + + if ((error = git_fs_path_dirname_r(&dir, curpath.ptr)) < 0) + goto cleanup; + + git_str_swap(&curpath, &dir); + git_str_dispose(&dir); + + if ((error = git_fs_path_apply_relative(&curpath, target.ptr)) < 0) + goto cleanup; + } + } + + git_error_set(GIT_ERROR_INVALID, "maximum symlink depth reached"); + error = -1; + +cleanup: + git_str_dispose(&curpath); + git_str_dispose(&target); + return error; +} + +int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode) +{ + return git_filebuf_open_withsize(file, path, flags, mode, WRITE_BUFFER_SIZE); +} + +int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size) +{ + int compression, error = -1; + size_t path_len, alloc_len; + + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(path); + GIT_ASSERT(file->buffer == NULL); + + memset(file, 0x0, sizeof(git_filebuf)); + + if (flags & GIT_FILEBUF_DO_NOT_BUFFER) + file->do_not_buffer = true; + + if (flags & GIT_FILEBUF_FSYNC) + file->do_fsync = true; + + file->buf_size = size; + file->buf_pos = 0; + file->fd = -1; + file->last_error = BUFERR_OK; + + /* Allocate the main cache buffer */ + if (!file->do_not_buffer) { + file->buffer = git__malloc(file->buf_size); + GIT_ERROR_CHECK_ALLOC(file->buffer); + } + + /* If we are hashing on-write, allocate a new hash context */ + if (flags & GIT_FILEBUF_HASH_CONTENTS) { + file->compute_digest = 1; + + if (git_hash_ctx_init(&file->digest, GIT_HASH_ALGORITHM_SHA1) < 0) + goto cleanup; + } + + compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT; + + /* If we are deflating on-write, */ + if (compression != 0) { + /* Initialize the ZLib stream */ + if (deflateInit(&file->zs, compression) != Z_OK) { + git_error_set(GIT_ERROR_ZLIB, "failed to initialize zlib"); + goto cleanup; + } + + /* Allocate the Zlib cache buffer */ + file->z_buf = git__malloc(file->buf_size); + GIT_ERROR_CHECK_ALLOC(file->z_buf); + + /* Never flush */ + file->flush_mode = Z_NO_FLUSH; + file->write = &write_deflate; + } else { + file->write = &write_normal; + } + + /* If we are writing to a temp file */ + if (flags & GIT_FILEBUF_TEMPORARY) { + git_str tmp_path = GIT_STR_INIT; + + /* Open the file as temporary for locking */ + file->fd = git_futils_mktmp(&tmp_path, path, mode); + + if (file->fd < 0) { + git_str_dispose(&tmp_path); + goto cleanup; + } + file->fd_is_open = true; + file->created_lock = true; + + /* No original path */ + file->path_original = NULL; + file->path_lock = git_str_detach(&tmp_path); + GIT_ERROR_CHECK_ALLOC(file->path_lock); + } else { + git_str resolved_path = GIT_STR_INIT; + + if ((error = resolve_symlink(&resolved_path, path)) < 0) + goto cleanup; + + /* Save the original path of the file */ + path_len = resolved_path.size; + file->path_original = git_str_detach(&resolved_path); + + /* create the locking path by appending ".lock" to the original */ + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, path_len, GIT_FILELOCK_EXTLENGTH); + file->path_lock = git__malloc(alloc_len); + GIT_ERROR_CHECK_ALLOC(file->path_lock); + + memcpy(file->path_lock, file->path_original, path_len); + memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH); + + if (git_fs_path_isdir(file->path_original)) { + git_error_set(GIT_ERROR_FILESYSTEM, "path '%s' is a directory", file->path_original); + error = GIT_EDIRECTORY; + goto cleanup; + } + + /* open the file for locking */ + if ((error = lock_file(file, flags, mode)) < 0) + goto cleanup; + + file->created_lock = true; + } + + return 0; + +cleanup: + git_filebuf_cleanup(file); + return error; +} + +int git_filebuf_hash(unsigned char *out, git_filebuf *file) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(file); + GIT_ASSERT_ARG(file->compute_digest); + + flush_buffer(file); + + if (verify_last_error(file) < 0) + return -1; + + git_hash_final(out, &file->digest); + git_hash_ctx_cleanup(&file->digest); + file->compute_digest = 0; + + return 0; +} + +int git_filebuf_commit_at(git_filebuf *file, const char *path) +{ + git__free(file->path_original); + file->path_original = git__strdup(path); + GIT_ERROR_CHECK_ALLOC(file->path_original); + + return git_filebuf_commit(file); +} + +int git_filebuf_commit(git_filebuf *file) +{ + /* temporary files cannot be committed */ + GIT_ASSERT_ARG(file); + GIT_ASSERT(file->path_original); + + file->flush_mode = Z_FINISH; + flush_buffer(file); + + if (verify_last_error(file) < 0) + goto on_error; + + file->fd_is_open = false; + + if (file->do_fsync && p_fsync(file->fd) < 0) { + git_error_set(GIT_ERROR_OS, "failed to fsync '%s'", file->path_lock); + goto on_error; + } + + if (p_close(file->fd) < 0) { + git_error_set(GIT_ERROR_OS, "failed to close file at '%s'", file->path_lock); + goto on_error; + } + + file->fd = -1; + + if (p_rename(file->path_lock, file->path_original) < 0) { + git_error_set(GIT_ERROR_OS, "failed to rename lockfile to '%s'", file->path_original); + goto on_error; + } + + if (file->do_fsync && git_futils_fsync_parent(file->path_original) < 0) + goto on_error; + + file->did_rename = true; + + git_filebuf_cleanup(file); + return 0; + +on_error: + git_filebuf_cleanup(file); + return -1; +} + +GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len) +{ + memcpy(file->buffer + file->buf_pos, buf, len); + file->buf_pos += len; +} + +int git_filebuf_write(git_filebuf *file, const void *buff, size_t len) +{ + const unsigned char *buf = buff; + + ENSURE_BUF_OK(file); + + if (file->do_not_buffer) + return file->write(file, (void *)buff, len); + + for (;;) { + size_t space_left = file->buf_size - file->buf_pos; + + /* cache if it's small */ + if (space_left > len) { + add_to_cache(file, buf, len); + return 0; + } + + add_to_cache(file, buf, space_left); + if (flush_buffer(file) < 0) + return -1; + + len -= space_left; + buf += space_left; + } +} + +int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len) +{ + size_t space_left = file->buf_size - file->buf_pos; + + *buffer = NULL; + + ENSURE_BUF_OK(file); + + if (len > file->buf_size) { + file->last_error = BUFERR_MEM; + return -1; + } + + if (space_left <= len) { + if (flush_buffer(file) < 0) + return -1; + } + + *buffer = (file->buffer + file->buf_pos); + file->buf_pos += len; + + return 0; +} + +int git_filebuf_printf(git_filebuf *file, const char *format, ...) +{ + va_list arglist; + size_t space_left, len, alloclen; + int written, res; + char *tmp_buffer; + + ENSURE_BUF_OK(file); + + space_left = file->buf_size - file->buf_pos; + + do { + va_start(arglist, format); + written = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); + va_end(arglist); + + if (written < 0) { + file->last_error = BUFERR_MEM; + return -1; + } + + len = written; + if (len + 1 <= space_left) { + file->buf_pos += len; + return 0; + } + + if (flush_buffer(file) < 0) + return -1; + + space_left = file->buf_size - file->buf_pos; + + } while (len + 1 <= space_left); + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, len, 1) || + !(tmp_buffer = git__malloc(alloclen))) { + file->last_error = BUFERR_MEM; + return -1; + } + + va_start(arglist, format); + written = p_vsnprintf(tmp_buffer, len + 1, format, arglist); + va_end(arglist); + + if (written < 0) { + git__free(tmp_buffer); + file->last_error = BUFERR_MEM; + return -1; + } + + res = git_filebuf_write(file, tmp_buffer, len); + git__free(tmp_buffer); + + return res; +} + +int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file) +{ + int res; + struct stat st; + + if (file->fd_is_open) + res = p_fstat(file->fd, &st); + else + res = p_stat(file->path_original, &st); + + if (res < 0) { + git_error_set(GIT_ERROR_OS, "could not get stat info for '%s'", + file->path_original); + return res; + } + + if (mtime) + *mtime = st.st_mtime; + if (size) + *size = (size_t)st.st_size; + + return 0; +} diff --git a/src/util/filebuf.h b/src/util/filebuf.h new file mode 100644 index 000000000..4a61ae4e3 --- /dev/null +++ b/src/util/filebuf.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_filebuf_h__ +#define INCLUDE_filebuf_h__ + +#include "git2_util.h" + +#include "futils.h" +#include "hash.h" +#include + +#ifdef GIT_THREADS +# define GIT_FILEBUF_THREADS +#endif + +#define GIT_FILEBUF_HASH_CONTENTS (1 << 0) +#define GIT_FILEBUF_APPEND (1 << 2) +#define GIT_FILEBUF_CREATE_LEADING_DIRS (1 << 3) +#define GIT_FILEBUF_TEMPORARY (1 << 4) +#define GIT_FILEBUF_DO_NOT_BUFFER (1 << 5) +#define GIT_FILEBUF_FSYNC (1 << 6) +#define GIT_FILEBUF_DEFLATE_SHIFT (7) + +#define GIT_FILELOCK_EXTENSION ".lock\0" +#define GIT_FILELOCK_EXTLENGTH 6 + +typedef struct git_filebuf git_filebuf; +struct git_filebuf { + char *path_original; + char *path_lock; + + int (*write)(git_filebuf *file, void *source, size_t len); + + bool compute_digest; + git_hash_ctx digest; + + unsigned char *buffer; + unsigned char *z_buf; + + z_stream zs; + int flush_mode; + + size_t buf_size, buf_pos; + git_file fd; + bool fd_is_open; + bool created_lock; + bool did_rename; + bool do_not_buffer; + bool do_fsync; + int last_error; +}; + +#define GIT_FILEBUF_INIT {0} + +/* + * The git_filebuf object lifecycle is: + * - Allocate git_filebuf, preferably using GIT_FILEBUF_INIT. + * + * - Call git_filebuf_open() to initialize the filebuf for use. + * + * - Make as many calls to git_filebuf_write(), git_filebuf_printf(), + * git_filebuf_reserve() as you like. The error codes for these + * functions don't need to be checked. They are stored internally + * by the file buffer. + * + * - While you are writing, you may call git_filebuf_hash() to get + * the hash of all you have written so far. This function will + * fail if any of the previous writes to the buffer failed. + * + * - To close the git_filebuf, you may call git_filebuf_commit() or + * git_filebuf_commit_at() to save the file, or + * git_filebuf_cleanup() to abandon the file. All of these will + * free the git_filebuf object. Likewise, all of these will fail + * if any of the previous writes to the buffer failed, and set + * an error code accordingly. + */ +int git_filebuf_write(git_filebuf *lock, const void *buff, size_t len); +int git_filebuf_reserve(git_filebuf *file, void **buff, size_t len); +int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); + +int git_filebuf_open(git_filebuf *lock, const char *path, int flags, mode_t mode); +int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size); +int git_filebuf_commit(git_filebuf *lock); +int git_filebuf_commit_at(git_filebuf *lock, const char *path); +void git_filebuf_cleanup(git_filebuf *lock); +int git_filebuf_hash(unsigned char *out, git_filebuf *file); +int git_filebuf_flush(git_filebuf *file); +int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file); + +#endif diff --git a/src/util/fs_path.c b/src/util/fs_path.c new file mode 100644 index 000000000..920c39073 --- /dev/null +++ b/src/util/fs_path.c @@ -0,0 +1,1920 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "fs_path.h" + +#include "git2_util.h" +#include "futils.h" +#include "posix.h" +#ifdef GIT_WIN32 +#include "win32/posix.h" +#include "win32/w32_buffer.h" +#include "win32/w32_util.h" +#include "win32/version.h" +#include +#else +#include +#endif +#include +#include + +#define ensure_error_set(code) do { \ + const git_error *e = git_error_last(); \ + if (!e || !e->message) \ + git_error_set(e ? e->klass : GIT_ERROR_CALLBACK, \ + "filesystem callback returned %d", code); \ + } while(0) + +static int dos_drive_prefix_length(const char *path) +{ + int i; + + /* + * Does it start with an ASCII letter (i.e. highest bit not set), + * followed by a colon? + */ + if (!(0x80 & (unsigned char)*path)) + return *path && path[1] == ':' ? 2 : 0; + + /* + * While drive letters must be letters of the English alphabet, it is + * possible to assign virtually _any_ Unicode character via `subst` as + * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff + * like this: + * + * subst ֍: %USERPROFILE%\Desktop + */ + for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++) + ; /* skip first UTF-8 character */ + return path[i] == ':' ? i + 1 : 0; +} + +#ifdef GIT_WIN32 +static bool looks_like_network_computer_name(const char *path, int pos) +{ + if (pos < 3) + return false; + + if (path[0] != '/' || path[1] != '/') + return false; + + while (pos-- > 2) { + if (path[pos] == '/') + return false; + } + + return true; +} +#endif + +/* + * Based on the Android implementation, BSD licensed. + * http://android.git.kernel.org/ + * + * Copyright (C) 2008 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +int git_fs_path_basename_r(git_str *buffer, const char *path) +{ + const char *endp, *startp; + int len, result; + + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + startp = "."; + len = 1; + goto Exit; + } + + /* Strip trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + /* All slashes becomes "/" */ + if (endp == path && *endp == '/') { + startp = "/"; + len = 1; + goto Exit; + } + + /* Find the start of the base */ + startp = endp; + while (startp > path && *(startp - 1) != '/') + startp--; + + /* Cast is safe because max path < max int */ + len = (int)(endp - startp + 1); + +Exit: + result = len; + + if (buffer != NULL && git_str_set(buffer, startp, len) < 0) + return -1; + + return result; +} + +/* + * Determine if the path is a Windows prefix and, if so, returns + * its actual length. If it is not a prefix, returns -1. + */ +static int win32_prefix_length(const char *path, int len) +{ +#ifndef GIT_WIN32 + GIT_UNUSED(path); + GIT_UNUSED(len); +#else + /* + * Mimic unix behavior where '/.git' returns '/': 'C:/.git' + * will return 'C:/' here + */ + if (dos_drive_prefix_length(path) == len) + return len; + + /* + * Similarly checks if we're dealing with a network computer name + * '//computername/.git' will return '//computername/' + */ + if (looks_like_network_computer_name(path, len)) + return len; +#endif + + return -1; +} + +/* + * Based on the Android implementation, BSD licensed. + * Check http://android.git.kernel.org/ + */ +int git_fs_path_dirname_r(git_str *buffer, const char *path) +{ + const char *endp; + int is_prefix = 0, len; + + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + path = "."; + len = 1; + goto Exit; + } + + /* Strip trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + if (endp - path + 1 > INT_MAX) { + git_error_set(GIT_ERROR_INVALID, "path too long"); + len = -1; + goto Exit; + } + + if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) { + is_prefix = 1; + goto Exit; + } + + /* Find the start of the dir */ + while (endp > path && *endp != '/') + endp--; + + /* Either the dir is "/" or there are no slashes */ + if (endp == path) { + path = (*endp == '/') ? "/" : "."; + len = 1; + goto Exit; + } + + do { + endp--; + } while (endp > path && *endp == '/'); + + if (endp - path + 1 > INT_MAX) { + git_error_set(GIT_ERROR_INVALID, "path too long"); + len = -1; + goto Exit; + } + + if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) { + is_prefix = 1; + goto Exit; + } + + /* Cast is safe because max path < max int */ + len = (int)(endp - path + 1); + +Exit: + if (buffer) { + if (git_str_set(buffer, path, len) < 0) + return -1; + if (is_prefix && git_str_putc(buffer, '/') < 0) + return -1; + } + + return len; +} + + +char *git_fs_path_dirname(const char *path) +{ + git_str buf = GIT_STR_INIT; + char *dirname; + + git_fs_path_dirname_r(&buf, path); + dirname = git_str_detach(&buf); + git_str_dispose(&buf); /* avoid memleak if error occurs */ + + return dirname; +} + +char *git_fs_path_basename(const char *path) +{ + git_str buf = GIT_STR_INIT; + char *basename; + + git_fs_path_basename_r(&buf, path); + basename = git_str_detach(&buf); + git_str_dispose(&buf); /* avoid memleak if error occurs */ + + return basename; +} + +size_t git_fs_path_basename_offset(git_str *buffer) +{ + ssize_t slash; + + if (!buffer || buffer->size <= 0) + return 0; + + slash = git_str_rfind_next(buffer, '/'); + + if (slash >= 0 && buffer->ptr[slash] == '/') + return (size_t)(slash + 1); + + return 0; +} + +int git_fs_path_root(const char *path) +{ + int offset = 0, prefix_len; + + /* Does the root of the path look like a windows drive ? */ + if ((prefix_len = dos_drive_prefix_length(path))) + offset += prefix_len; + +#ifdef GIT_WIN32 + /* Are we dealing with a windows network path? */ + else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') || + (path[0] == '\\' && path[1] == '\\' && path[2] != '\\')) + { + offset += 2; + + /* Skip the computer name segment */ + while (path[offset] && path[offset] != '/' && path[offset] != '\\') + offset++; + } + + if (path[offset] == '\\') + return offset; +#endif + + if (path[offset] == '/') + return offset; + + return -1; /* Not a real error - signals that path is not rooted */ +} + +static void path_trim_slashes(git_str *path) +{ + int ceiling = git_fs_path_root(path->ptr) + 1; + + if (ceiling < 0) + return; + + while (path->size > (size_t)ceiling) { + if (path->ptr[path->size-1] != '/') + break; + + path->ptr[path->size-1] = '\0'; + path->size--; + } +} + +int git_fs_path_join_unrooted( + git_str *path_out, const char *path, const char *base, ssize_t *root_at) +{ + ssize_t root; + + GIT_ASSERT_ARG(path_out); + GIT_ASSERT_ARG(path); + + root = (ssize_t)git_fs_path_root(path); + + if (base != NULL && root < 0) { + if (git_str_joinpath(path_out, base, path) < 0) + return -1; + + root = (ssize_t)strlen(base); + } else { + if (git_str_sets(path_out, path) < 0) + return -1; + + if (root < 0) + root = 0; + else if (base) + git_fs_path_equal_or_prefixed(base, path, &root); + } + + if (root_at) + *root_at = root; + + return 0; +} + +void git_fs_path_squash_slashes(git_str *path) +{ + char *p, *q; + + if (path->size == 0) + return; + + for (p = path->ptr, q = path->ptr; *q; p++, q++) { + *p = *q; + + while (*q == '/' && *(q+1) == '/') { + path->size--; + q++; + } + } + + *p = '\0'; +} + +int git_fs_path_prettify(git_str *path_out, const char *path, const char *base) +{ + char buf[GIT_PATH_MAX]; + + GIT_ASSERT_ARG(path_out); + GIT_ASSERT_ARG(path); + + /* construct path if needed */ + if (base != NULL && git_fs_path_root(path) < 0) { + if (git_str_joinpath(path_out, base, path) < 0) + return -1; + path = path_out->ptr; + } + + if (p_realpath(path, buf) == NULL) { + /* git_error_set resets the errno when dealing with a GIT_ERROR_OS kind of error */ + int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1; + git_error_set(GIT_ERROR_OS, "failed to resolve path '%s'", path); + + git_str_clear(path_out); + + return error; + } + + return git_str_sets(path_out, buf); +} + +int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base) +{ + int error = git_fs_path_prettify(path_out, path, base); + return (error < 0) ? error : git_fs_path_to_dir(path_out); +} + +int git_fs_path_to_dir(git_str *path) +{ + if (path->asize > 0 && + git_str_len(path) > 0 && + path->ptr[git_str_len(path) - 1] != '/') + git_str_putc(path, '/'); + + return git_str_oom(path) ? -1 : 0; +} + +void git_fs_path_string_to_dir(char *path, size_t size) +{ + size_t end = strlen(path); + + if (end && path[end - 1] != '/' && end < size) { + path[end] = '/'; + path[end + 1] = '\0'; + } +} + +int git__percent_decode(git_str *decoded_out, const char *input) +{ + int len, hi, lo, i; + + GIT_ASSERT_ARG(decoded_out); + GIT_ASSERT_ARG(input); + + len = (int)strlen(input); + git_str_clear(decoded_out); + + for(i = 0; i < len; i++) + { + char c = input[i]; + + if (c != '%') + goto append; + + if (i >= len - 2) + goto append; + + hi = git__fromhex(input[i + 1]); + lo = git__fromhex(input[i + 2]); + + if (hi < 0 || lo < 0) + goto append; + + c = (char)(hi << 4 | lo); + i += 2; + +append: + if (git_str_putc(decoded_out, c) < 0) + return -1; + } + + return 0; +} + +static int error_invalid_local_file_uri(const char *uri) +{ + git_error_set(GIT_ERROR_CONFIG, "'%s' is not a valid local file URI", uri); + return -1; +} + +static int local_file_url_prefixlen(const char *file_url) +{ + int len = -1; + + if (git__prefixcmp(file_url, "file://") == 0) { + if (file_url[7] == '/') + len = 8; + else if (git__prefixcmp(file_url + 7, "localhost/") == 0) + len = 17; + } + + return len; +} + +bool git_fs_path_is_local_file_url(const char *file_url) +{ + return (local_file_url_prefixlen(file_url) > 0); +} + +int git_fs_path_fromurl(git_str *local_path_out, const char *file_url) +{ + int offset; + + GIT_ASSERT_ARG(local_path_out); + GIT_ASSERT_ARG(file_url); + + if ((offset = local_file_url_prefixlen(file_url)) < 0 || + file_url[offset] == '\0' || file_url[offset] == '/') + return error_invalid_local_file_uri(file_url); + +#ifndef GIT_WIN32 + offset--; /* A *nix absolute path starts with a forward slash */ +#endif + + git_str_clear(local_path_out); + return git__percent_decode(local_path_out, file_url + offset); +} + +int git_fs_path_walk_up( + git_str *path, + const char *ceiling, + int (*cb)(void *data, const char *), + void *data) +{ + int error = 0; + git_str iter; + ssize_t stop = 0, scan; + char oldc = '\0'; + + GIT_ASSERT_ARG(path); + GIT_ASSERT_ARG(cb); + + if (ceiling != NULL) { + if (git__prefixcmp(path->ptr, ceiling) == 0) + stop = (ssize_t)strlen(ceiling); + else + stop = git_str_len(path); + } + scan = git_str_len(path); + + /* empty path: yield only once */ + if (!scan) { + error = cb(data, ""); + if (error) + ensure_error_set(error); + return error; + } + + iter.ptr = path->ptr; + iter.size = git_str_len(path); + iter.asize = path->asize; + + while (scan >= stop) { + error = cb(data, iter.ptr); + iter.ptr[scan] = oldc; + + if (error) { + ensure_error_set(error); + break; + } + + scan = git_str_rfind_next(&iter, '/'); + if (scan >= 0) { + scan++; + oldc = iter.ptr[scan]; + iter.size = scan; + iter.ptr[scan] = '\0'; + } + } + + if (scan >= 0) + iter.ptr[scan] = oldc; + + /* relative path: yield for the last component */ + if (!error && stop == 0 && iter.ptr[0] != '/') { + error = cb(data, ""); + if (error) + ensure_error_set(error); + } + + return error; +} + +bool git_fs_path_exists(const char *path) +{ + GIT_ASSERT_ARG_WITH_RETVAL(path, false); + return p_access(path, F_OK) == 0; +} + +bool git_fs_path_isdir(const char *path) +{ + struct stat st; + if (p_stat(path, &st) < 0) + return false; + + return S_ISDIR(st.st_mode) != 0; +} + +bool git_fs_path_isfile(const char *path) +{ + struct stat st; + + GIT_ASSERT_ARG_WITH_RETVAL(path, false); + if (p_stat(path, &st) < 0) + return false; + + return S_ISREG(st.st_mode) != 0; +} + +bool git_fs_path_islink(const char *path) +{ + struct stat st; + + GIT_ASSERT_ARG_WITH_RETVAL(path, false); + if (p_lstat(path, &st) < 0) + return false; + + return S_ISLNK(st.st_mode) != 0; +} + +#ifdef GIT_WIN32 + +bool git_fs_path_is_empty_dir(const char *path) +{ + git_win32_path filter_w; + bool empty = false; + + if (git_win32__findfirstfile_filter(filter_w, path)) { + WIN32_FIND_DATAW findData; + HANDLE hFind = FindFirstFileW(filter_w, &findData); + + /* FindFirstFile will fail if there are no children to the given + * path, which can happen if the given path is a file (and obviously + * has no children) or if the given path is an empty mount point. + * (Most directories have at least directory entries '.' and '..', + * but ridiculously another volume mounted in another drive letter's + * path space do not, and thus have nothing to enumerate.) If + * FindFirstFile fails, check if this is a directory-like thing + * (a mount point). + */ + if (hFind == INVALID_HANDLE_VALUE) + return git_fs_path_isdir(path); + + /* If the find handle was created successfully, then it's a directory */ + empty = true; + + do { + /* Allow the enumeration to return . and .. and still be considered + * empty. In the special case of drive roots (i.e. C:\) where . and + * .. do not occur, we can still consider the path to be an empty + * directory if there's nothing there. */ + if (!git_fs_path_is_dot_or_dotdotW(findData.cFileName)) { + empty = false; + break; + } + } while (FindNextFileW(hFind, &findData)); + + FindClose(hFind); + } + + return empty; +} + +#else + +static int path_found_entry(void *payload, git_str *path) +{ + GIT_UNUSED(payload); + return !git_fs_path_is_dot_or_dotdot(path->ptr); +} + +bool git_fs_path_is_empty_dir(const char *path) +{ + int error; + git_str dir = GIT_STR_INIT; + + if (!git_fs_path_isdir(path)) + return false; + + if ((error = git_str_sets(&dir, path)) != 0) + git_error_clear(); + else + error = git_fs_path_direach(&dir, 0, path_found_entry, NULL); + + git_str_dispose(&dir); + + return !error; +} + +#endif + +int git_fs_path_set_error(int errno_value, const char *path, const char *action) +{ + switch (errno_value) { + case ENOENT: + case ENOTDIR: + git_error_set(GIT_ERROR_OS, "could not find '%s' to %s", path, action); + return GIT_ENOTFOUND; + + case EINVAL: + case ENAMETOOLONG: + git_error_set(GIT_ERROR_OS, "invalid path for filesystem '%s'", path); + return GIT_EINVALIDSPEC; + + case EEXIST: + git_error_set(GIT_ERROR_OS, "failed %s - '%s' already exists", action, path); + return GIT_EEXISTS; + + case EACCES: + git_error_set(GIT_ERROR_OS, "failed %s - '%s' is locked", action, path); + return GIT_ELOCKED; + + default: + git_error_set(GIT_ERROR_OS, "could not %s '%s'", action, path); + return -1; + } +} + +int git_fs_path_lstat(const char *path, struct stat *st) +{ + if (p_lstat(path, st) == 0) + return 0; + + return git_fs_path_set_error(errno, path, "stat"); +} + +static bool _check_dir_contents( + git_str *dir, + const char *sub, + bool (*predicate)(const char *)) +{ + bool result; + size_t dir_size = git_str_len(dir); + size_t sub_size = strlen(sub); + size_t alloc_size; + + /* leave base valid even if we could not make space for subdir */ + if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, dir_size, sub_size) || + GIT_ADD_SIZET_OVERFLOW(&alloc_size, alloc_size, 2) || + git_str_try_grow(dir, alloc_size, false) < 0) + return false; + + /* save excursion */ + if (git_str_joinpath(dir, dir->ptr, sub) < 0) + return false; + + result = predicate(dir->ptr); + + /* restore path */ + git_str_truncate(dir, dir_size); + return result; +} + +bool git_fs_path_contains(git_str *dir, const char *item) +{ + return _check_dir_contents(dir, item, &git_fs_path_exists); +} + +bool git_fs_path_contains_dir(git_str *base, const char *subdir) +{ + return _check_dir_contents(base, subdir, &git_fs_path_isdir); +} + +bool git_fs_path_contains_file(git_str *base, const char *file) +{ + return _check_dir_contents(base, file, &git_fs_path_isfile); +} + +int git_fs_path_find_dir(git_str *dir) +{ + int error = 0; + char buf[GIT_PATH_MAX]; + + if (p_realpath(dir->ptr, buf) != NULL) + error = git_str_sets(dir, buf); + + /* call dirname if this is not a directory */ + if (!error) /* && git_fs_path_isdir(dir->ptr) == false) */ + error = (git_fs_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0; + + if (!error) + error = git_fs_path_to_dir(dir); + + return error; +} + +int git_fs_path_resolve_relative(git_str *path, size_t ceiling) +{ + char *base, *to, *from, *next; + size_t len; + + GIT_ERROR_CHECK_ALLOC_STR(path); + + if (ceiling > path->size) + ceiling = path->size; + + /* recognize drive prefixes, etc. that should not be backed over */ + if (ceiling == 0) + ceiling = git_fs_path_root(path->ptr) + 1; + + /* recognize URL prefixes that should not be backed over */ + if (ceiling == 0) { + for (next = path->ptr; *next && git__isalpha(*next); ++next); + if (next[0] == ':' && next[1] == '/' && next[2] == '/') + ceiling = (next + 3) - path->ptr; + } + + base = to = from = path->ptr + ceiling; + + while (*from) { + for (next = from; *next && *next != '/'; ++next); + + len = next - from; + + if (len == 1 && from[0] == '.') + /* do nothing with singleton dot */; + + else if (len == 2 && from[0] == '.' && from[1] == '.') { + /* error out if trying to up one from a hard base */ + if (to == base && ceiling != 0) { + git_error_set(GIT_ERROR_INVALID, + "cannot strip root component off url"); + return -1; + } + + /* no more path segments to strip, + * use '../' as a new base path */ + if (to == base) { + if (*next == '/') + len++; + + if (to != from) + memmove(to, from, len); + + to += len; + /* this is now the base, can't back up from a + * relative prefix */ + base = to; + } else { + /* back up a path segment */ + while (to > base && to[-1] == '/') to--; + while (to > base && to[-1] != '/') to--; + } + } else { + if (*next == '/' && *from != '/') + len++; + + if (to != from) + memmove(to, from, len); + + to += len; + } + + from += len; + + while (*from == '/') from++; + } + + *to = '\0'; + + path->size = to - path->ptr; + + return 0; +} + +int git_fs_path_apply_relative(git_str *target, const char *relpath) +{ + return git_str_joinpath(target, git_str_cstr(target), relpath) || + git_fs_path_resolve_relative(target, 0); +} + +int git_fs_path_cmp( + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2, + int (*compare)(const char *, const char *, size_t)) +{ + unsigned char c1, c2; + size_t len = len1 < len2 ? len1 : len2; + int cmp; + + cmp = compare(name1, name2, len); + if (cmp) + return cmp; + + c1 = name1[len]; + c2 = name2[len]; + + if (c1 == '\0' && isdir1) + c1 = '/'; + + if (c2 == '\0' && isdir2) + c2 = '/'; + + return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; +} + +size_t git_fs_path_common_dirlen(const char *one, const char *two) +{ + const char *p, *q, *dirsep = NULL; + + for (p = one, q = two; *p && *q; p++, q++) { + if (*p == '/' && *q == '/') + dirsep = p; + else if (*p != *q) + break; + } + + return dirsep ? (dirsep - one) + 1 : 0; +} + +int git_fs_path_make_relative(git_str *path, const char *parent) +{ + const char *p, *q, *p_dirsep, *q_dirsep; + size_t plen = path->size, newlen, alloclen, depth = 1, i, offset; + + for (p_dirsep = p = path->ptr, q_dirsep = q = parent; *p && *q; p++, q++) { + if (*p == '/' && *q == '/') { + p_dirsep = p; + q_dirsep = q; + } + else if (*p != *q) + break; + } + + /* need at least 1 common path segment */ + if ((p_dirsep == path->ptr || q_dirsep == parent) && + (*p_dirsep != '/' || *q_dirsep != '/')) { + git_error_set(GIT_ERROR_INVALID, + "%s is not a parent of %s", parent, path->ptr); + return GIT_ENOTFOUND; + } + + if (*p == '/' && !*q) + p++; + else if (!*p && *q == '/') + q++; + else if (!*p && !*q) + return git_str_clear(path), 0; + else { + p = p_dirsep + 1; + q = q_dirsep + 1; + } + + plen -= (p - path->ptr); + + if (!*q) + return git_str_set(path, p, plen); + + for (; (q = strchr(q, '/')) && *(q + 1); q++) + depth++; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newlen, depth, 3); + GIT_ERROR_CHECK_ALLOC_ADD(&newlen, newlen, plen); + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, newlen, 1); + + /* save the offset as we might realllocate the pointer */ + offset = p - path->ptr; + if (git_str_try_grow(path, alloclen, 1) < 0) + return -1; + p = path->ptr + offset; + + memmove(path->ptr + (depth * 3), p, plen + 1); + + for (i = 0; i < depth; i++) + memcpy(path->ptr + (i * 3), "../", 3); + + path->size = newlen; + return 0; +} + +bool git_fs_path_has_non_ascii(const char *path, size_t pathlen) +{ + const uint8_t *scan = (const uint8_t *)path, *end; + + for (end = scan + pathlen; scan < end; ++scan) + if (*scan & 0x80) + return true; + + return false; +} + +#ifdef GIT_USE_ICONV + +int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic) +{ + git_str_init(&ic->buf, 0); + ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING); + return 0; +} + +void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic) +{ + if (ic) { + if (ic->map != (iconv_t)-1) + iconv_close(ic->map); + git_str_dispose(&ic->buf); + } +} + +int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen) +{ + char *nfd = (char*)*in, *nfc; + size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, alloclen, rv; + int retry = 1; + + if (!ic || ic->map == (iconv_t)-1 || + !git_fs_path_has_non_ascii(*in, *inlen)) + return 0; + + git_str_clear(&ic->buf); + + while (1) { + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, wantlen, 1); + if (git_str_grow(&ic->buf, alloclen) < 0) + return -1; + + nfc = ic->buf.ptr + ic->buf.size; + nfclen = ic->buf.asize - ic->buf.size; + + rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen); + + ic->buf.size = (nfc - ic->buf.ptr); + + if (rv != (size_t)-1) + break; + + /* if we cannot convert the data (probably because iconv thinks + * it is not valid UTF-8 source data), then use original data + */ + if (errno != E2BIG) + return 0; + + /* make space for 2x the remaining data to be converted + * (with per retry overhead to avoid infinite loops) + */ + wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4); + + if (retry++ > 4) + goto fail; + } + + ic->buf.ptr[ic->buf.size] = '\0'; + + *in = ic->buf.ptr; + *inlen = ic->buf.size; + + return 0; + +fail: + git_error_set(GIT_ERROR_OS, "unable to convert unicode path data"); + return -1; +} + +static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; +static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; + +/* Check if the platform is decomposing unicode data for us. We will + * emulate core Git and prefer to use precomposed unicode data internally + * on these platforms, composing the decomposed unicode on the fly. + * + * This mainly happens on the Mac where HDFS stores filenames as + * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will + * return decomposed unicode from readdir() even when the actual + * filesystem is storing precomposed unicode. + */ +bool git_fs_path_does_decompose_unicode(const char *root) +{ + git_str nfc_path = GIT_STR_INIT; + git_str nfd_path = GIT_STR_INIT; + int fd; + bool found_decomposed = false; + size_t orig_len; + const char *trailer; + + /* Create a file using a precomposed path and then try to find it + * using the decomposed name. If the lookup fails, then we will mark + * that we should precompose unicode for this repository. + */ + if (git_str_joinpath(&nfc_path, root, nfc_file) < 0) + goto done; + + /* record original path length before trailer */ + orig_len = nfc_path.size; + + if ((fd = git_futils_mktmp(&nfc_path, nfc_path.ptr, 0666)) < 0) + goto done; + p_close(fd); + + trailer = nfc_path.ptr + orig_len; + + /* try to look up as NFD path */ + if (git_str_joinpath(&nfd_path, root, nfd_file) < 0 || + git_str_puts(&nfd_path, trailer) < 0) + goto done; + + found_decomposed = git_fs_path_exists(nfd_path.ptr); + + /* remove temporary file (using original precomposed path) */ + (void)p_unlink(nfc_path.ptr); + +done: + git_str_dispose(&nfc_path); + git_str_dispose(&nfd_path); + return found_decomposed; +} + +#else + +bool git_fs_path_does_decompose_unicode(const char *root) +{ + GIT_UNUSED(root); + return false; +} + +#endif + +#if defined(__sun) || defined(__GNU__) +typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1]; +#else +typedef struct dirent path_dirent_data; +#endif + +int git_fs_path_direach( + git_str *path, + uint32_t flags, + int (*fn)(void *, git_str *), + void *arg) +{ + int error = 0; + ssize_t wd_len; + DIR *dir; + struct dirent *de; + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_t ic = GIT_PATH_ICONV_INIT; +#endif + + GIT_UNUSED(flags); + + if (git_fs_path_to_dir(path) < 0) + return -1; + + wd_len = git_str_len(path); + + if ((dir = opendir(path->ptr)) == NULL) { + git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path->ptr); + if (errno == ENOENT) + return GIT_ENOTFOUND; + + return -1; + } + +#ifdef GIT_USE_ICONV + if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0) + (void)git_fs_path_iconv_init_precompose(&ic); +#endif + + while ((de = readdir(dir)) != NULL) { + const char *de_path = de->d_name; + size_t de_len = strlen(de_path); + + if (git_fs_path_is_dot_or_dotdot(de_path)) + continue; + +#ifdef GIT_USE_ICONV + if ((error = git_fs_path_iconv(&ic, &de_path, &de_len)) < 0) + break; +#endif + + if ((error = git_str_put(path, de_path, de_len)) < 0) + break; + + git_error_clear(); + error = fn(arg, path); + + git_str_truncate(path, wd_len); /* restore path */ + + /* Only set our own error if the callback did not set one already */ + if (error != 0) { + if (!git_error_last()) + ensure_error_set(error); + + break; + } + } + + closedir(dir); + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_clear(&ic); +#endif + + return error; +} + +#if defined(GIT_WIN32) && !defined(__MINGW32__) + +/* Using _FIND_FIRST_EX_LARGE_FETCH may increase performance in Windows 7 + * and better. + */ +#ifndef FIND_FIRST_EX_LARGE_FETCH +# define FIND_FIRST_EX_LARGE_FETCH 2 +#endif + +int git_fs_path_diriter_init( + git_fs_path_diriter *diriter, + const char *path, + unsigned int flags) +{ + git_win32_path path_filter; + + static int is_win7_or_later = -1; + if (is_win7_or_later < 0) + is_win7_or_later = git_has_win32_version(6, 1, 0); + + GIT_ASSERT_ARG(diriter); + GIT_ASSERT_ARG(path); + + memset(diriter, 0, sizeof(git_fs_path_diriter)); + diriter->handle = INVALID_HANDLE_VALUE; + + if (git_str_puts(&diriter->path_utf8, path) < 0) + return -1; + + path_trim_slashes(&diriter->path_utf8); + + if (diriter->path_utf8.size == 0) { + git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path); + return -1; + } + + if ((diriter->parent_len = git_win32_path_from_utf8(diriter->path, diriter->path_utf8.ptr)) < 0 || + !git_win32__findfirstfile_filter(path_filter, diriter->path_utf8.ptr)) { + git_error_set(GIT_ERROR_OS, "could not parse the directory path '%s'", path); + return -1; + } + + diriter->handle = FindFirstFileExW( + path_filter, + is_win7_or_later ? FindExInfoBasic : FindExInfoStandard, + &diriter->current, + FindExSearchNameMatch, + NULL, + is_win7_or_later ? FIND_FIRST_EX_LARGE_FETCH : 0); + + if (diriter->handle == INVALID_HANDLE_VALUE) { + git_error_set(GIT_ERROR_OS, "could not open directory '%s'", path); + return -1; + } + + diriter->parent_utf8_len = diriter->path_utf8.size; + diriter->flags = flags; + return 0; +} + +static int diriter_update_paths(git_fs_path_diriter *diriter) +{ + size_t filename_len, path_len; + + filename_len = wcslen(diriter->current.cFileName); + + if (GIT_ADD_SIZET_OVERFLOW(&path_len, diriter->parent_len, filename_len) || + GIT_ADD_SIZET_OVERFLOW(&path_len, path_len, 2)) + return -1; + + if (path_len > GIT_WIN_PATH_UTF16) { + git_error_set(GIT_ERROR_FILESYSTEM, + "invalid path '%.*ls\\%ls' (path too long)", + diriter->parent_len, diriter->path, diriter->current.cFileName); + return -1; + } + + diriter->path[diriter->parent_len] = L'\\'; + memcpy(&diriter->path[diriter->parent_len+1], + diriter->current.cFileName, filename_len * sizeof(wchar_t)); + diriter->path[path_len-1] = L'\0'; + + git_str_truncate(&diriter->path_utf8, diriter->parent_utf8_len); + + if (diriter->parent_utf8_len > 0 && + diriter->path_utf8.ptr[diriter->parent_utf8_len-1] != '/') + git_str_putc(&diriter->path_utf8, '/'); + + git_str_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len); + + if (git_str_oom(&diriter->path_utf8)) + return -1; + + return 0; +} + +int git_fs_path_diriter_next(git_fs_path_diriter *diriter) +{ + bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT); + + do { + /* Our first time through, we already have the data from + * FindFirstFileW. Use it, otherwise get the next file. + */ + if (!diriter->needs_next) + diriter->needs_next = 1; + else if (!FindNextFileW(diriter->handle, &diriter->current)) + return GIT_ITEROVER; + } while (skip_dot && git_fs_path_is_dot_or_dotdotW(diriter->current.cFileName)); + + if (diriter_update_paths(diriter) < 0) + return -1; + + return 0; +} + +int git_fs_path_diriter_filename( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(out_len); + GIT_ASSERT_ARG(diriter); + GIT_ASSERT(diriter->path_utf8.size > diriter->parent_utf8_len); + + *out = &diriter->path_utf8.ptr[diriter->parent_utf8_len+1]; + *out_len = diriter->path_utf8.size - diriter->parent_utf8_len - 1; + return 0; +} + +int git_fs_path_diriter_fullpath( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(out_len); + GIT_ASSERT_ARG(diriter); + + *out = diriter->path_utf8.ptr; + *out_len = diriter->path_utf8.size; + return 0; +} + +int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diriter); + + return git_win32__file_attribute_to_stat(out, + (WIN32_FILE_ATTRIBUTE_DATA *)&diriter->current, + diriter->path); +} + +void git_fs_path_diriter_free(git_fs_path_diriter *diriter) +{ + if (diriter == NULL) + return; + + git_str_dispose(&diriter->path_utf8); + + if (diriter->handle != INVALID_HANDLE_VALUE) { + FindClose(diriter->handle); + diriter->handle = INVALID_HANDLE_VALUE; + } +} + +#else + +int git_fs_path_diriter_init( + git_fs_path_diriter *diriter, + const char *path, + unsigned int flags) +{ + GIT_ASSERT_ARG(diriter); + GIT_ASSERT_ARG(path); + + memset(diriter, 0, sizeof(git_fs_path_diriter)); + + if (git_str_puts(&diriter->path, path) < 0) + return -1; + + path_trim_slashes(&diriter->path); + + if (diriter->path.size == 0) { + git_error_set(GIT_ERROR_FILESYSTEM, "could not open directory '%s'", path); + return -1; + } + + if ((diriter->dir = opendir(diriter->path.ptr)) == NULL) { + git_str_dispose(&diriter->path); + + git_error_set(GIT_ERROR_OS, "failed to open directory '%s'", path); + return -1; + } + +#ifdef GIT_USE_ICONV + if ((flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0) + (void)git_fs_path_iconv_init_precompose(&diriter->ic); +#endif + + diriter->parent_len = diriter->path.size; + diriter->flags = flags; + + return 0; +} + +int git_fs_path_diriter_next(git_fs_path_diriter *diriter) +{ + struct dirent *de; + const char *filename; + size_t filename_len; + bool skip_dot = !(diriter->flags & GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT); + int error = 0; + + GIT_ASSERT_ARG(diriter); + + errno = 0; + + do { + if ((de = readdir(diriter->dir)) == NULL) { + if (!errno) + return GIT_ITEROVER; + + git_error_set(GIT_ERROR_OS, + "could not read directory '%s'", diriter->path.ptr); + return -1; + } + } while (skip_dot && git_fs_path_is_dot_or_dotdot(de->d_name)); + + filename = de->d_name; + filename_len = strlen(filename); + +#ifdef GIT_USE_ICONV + if ((diriter->flags & GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE) != 0 && + (error = git_fs_path_iconv(&diriter->ic, &filename, &filename_len)) < 0) + return error; +#endif + + git_str_truncate(&diriter->path, diriter->parent_len); + + if (diriter->parent_len > 0 && + diriter->path.ptr[diriter->parent_len-1] != '/') + git_str_putc(&diriter->path, '/'); + + git_str_put(&diriter->path, filename, filename_len); + + if (git_str_oom(&diriter->path)) + return -1; + + return error; +} + +int git_fs_path_diriter_filename( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(out_len); + GIT_ASSERT_ARG(diriter); + GIT_ASSERT(diriter->path.size > diriter->parent_len); + + *out = &diriter->path.ptr[diriter->parent_len+1]; + *out_len = diriter->path.size - diriter->parent_len - 1; + return 0; +} + +int git_fs_path_diriter_fullpath( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(out_len); + GIT_ASSERT_ARG(diriter); + + *out = diriter->path.ptr; + *out_len = diriter->path.size; + return 0; +} + +int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter) +{ + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(diriter); + + return git_fs_path_lstat(diriter->path.ptr, out); +} + +void git_fs_path_diriter_free(git_fs_path_diriter *diriter) +{ + if (diriter == NULL) + return; + + if (diriter->dir) { + closedir(diriter->dir); + diriter->dir = NULL; + } + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_clear(&diriter->ic); +#endif + + git_str_dispose(&diriter->path); +} + +#endif + +int git_fs_path_dirload( + git_vector *contents, + const char *path, + size_t prefix_len, + uint32_t flags) +{ + git_fs_path_diriter iter = GIT_FS_PATH_DIRITER_INIT; + const char *name; + size_t name_len; + char *dup; + int error; + + GIT_ASSERT_ARG(contents); + GIT_ASSERT_ARG(path); + + if ((error = git_fs_path_diriter_init(&iter, path, flags)) < 0) + return error; + + while ((error = git_fs_path_diriter_next(&iter)) == 0) { + if ((error = git_fs_path_diriter_fullpath(&name, &name_len, &iter)) < 0) + break; + + GIT_ASSERT(name_len > prefix_len); + + dup = git__strndup(name + prefix_len, name_len - prefix_len); + GIT_ERROR_CHECK_ALLOC(dup); + + if ((error = git_vector_insert(contents, dup)) < 0) + break; + } + + if (error == GIT_ITEROVER) + error = 0; + + git_fs_path_diriter_free(&iter); + return error; +} + +int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path) +{ + if (git_fs_path_is_local_file_url(url_or_path)) + return git_fs_path_fromurl(local_path_out, url_or_path); + else + return git_str_sets(local_path_out, url_or_path); +} + +/* Reject paths like AUX or COM1, or those versions that end in a dot or + * colon. ("AUX." or "AUX:") + */ +GIT_INLINE(bool) validate_dospath( + const char *component, + size_t len, + const char dospath[3], + bool trailing_num) +{ + size_t last = trailing_num ? 4 : 3; + + if (len < last || git__strncasecmp(component, dospath, 3) != 0) + return true; + + if (trailing_num && (component[3] < '1' || component[3] > '9')) + return true; + + return (len > last && + component[last] != '.' && + component[last] != ':'); +} + +GIT_INLINE(bool) validate_char(unsigned char c, unsigned int flags) +{ + if ((flags & GIT_FS_PATH_REJECT_BACKSLASH) && c == '\\') + return false; + + if ((flags & GIT_FS_PATH_REJECT_SLASH) && c == '/') + return false; + + if (flags & GIT_FS_PATH_REJECT_NT_CHARS) { + if (c < 32) + return false; + + switch (c) { + case '<': + case '>': + case ':': + case '"': + case '|': + case '?': + case '*': + return false; + } + } + + return true; +} + +/* + * We fundamentally don't like some paths when dealing with user-inputted + * strings (to avoid escaping a sandbox): we don't want dot or dot-dot + * anywhere, we want to avoid writing weird paths on Windows that can't + * be handled by tools that use the non-\\?\ APIs, we don't want slashes + * or double slashes at the end of paths that can make them ambiguous. + * + * For checkout, we don't want to recurse into ".git" either. + */ +static bool validate_component( + const char *component, + size_t len, + unsigned int flags) +{ + if (len == 0) + return !(flags & GIT_FS_PATH_REJECT_EMPTY_COMPONENT); + + if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) && + len == 1 && component[0] == '.') + return false; + + if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) && + len == 2 && component[0] == '.' && component[1] == '.') + return false; + + if ((flags & GIT_FS_PATH_REJECT_TRAILING_DOT) && + component[len - 1] == '.') + return false; + + if ((flags & GIT_FS_PATH_REJECT_TRAILING_SPACE) && + component[len - 1] == ' ') + return false; + + if ((flags & GIT_FS_PATH_REJECT_TRAILING_COLON) && + component[len - 1] == ':') + return false; + + if (flags & GIT_FS_PATH_REJECT_DOS_PATHS) { + if (!validate_dospath(component, len, "CON", false) || + !validate_dospath(component, len, "PRN", false) || + !validate_dospath(component, len, "AUX", false) || + !validate_dospath(component, len, "NUL", false) || + !validate_dospath(component, len, "COM", true) || + !validate_dospath(component, len, "LPT", true)) + return false; + } + + return true; +} + +#ifdef GIT_WIN32 +GIT_INLINE(bool) validate_length( + const char *path, + size_t len, + size_t utf8_char_len) +{ + GIT_UNUSED(path); + GIT_UNUSED(len); + + return (utf8_char_len <= MAX_PATH); +} +#endif + +bool git_fs_path_str_is_valid_ext( + const git_str *path, + unsigned int flags, + bool (*validate_char_cb)(char ch, void *payload), + bool (*validate_component_cb)(const char *component, size_t len, void *payload), + bool (*validate_length_cb)(const char *path, size_t len, size_t utf8_char_len), + void *payload) +{ + const char *start, *c; + size_t len = 0; + + if (!flags) + return true; + + for (start = c = path->ptr; *c && len < path->size; c++, len++) { + if (!validate_char(*c, flags)) + return false; + + if (validate_char_cb && !validate_char_cb(*c, payload)) + return false; + + if (*c != '/') + continue; + + if (!validate_component(start, (c - start), flags)) + return false; + + if (validate_component_cb && + !validate_component_cb(start, (c - start), payload)) + return false; + + start = c + 1; + } + + /* + * We want to support paths specified as either `const char *` + * or `git_str *`; we pass size as `SIZE_MAX` when we use a + * `const char *` to avoid a `strlen`. Ensure that we didn't + * have a NUL in the buffer if there was a non-SIZE_MAX length. + */ + if (path->size != SIZE_MAX && len != path->size) + return false; + + if (!validate_component(start, (c - start), flags)) + return false; + + if (validate_component_cb && + !validate_component_cb(start, (c - start), payload)) + return false; + +#ifdef GIT_WIN32 + if ((flags & GIT_FS_PATH_REJECT_LONG_PATHS) != 0) { + size_t utf8_len = git_utf8_char_length(path->ptr, len); + + if (!validate_length(path->ptr, len, utf8_len)) + return false; + + if (validate_length_cb && + !validate_length_cb(path->ptr, len, utf8_len)) + return false; + } +#else + GIT_UNUSED(validate_length_cb); +#endif + + return true; +} + +int git_fs_path_validate_str_length_with_suffix( + git_str *path, + size_t suffix_len) +{ +#ifdef GIT_WIN32 + size_t utf8_len = git_utf8_char_length(path->ptr, path->size); + size_t total_len; + + if (GIT_ADD_SIZET_OVERFLOW(&total_len, utf8_len, suffix_len) || + total_len > MAX_PATH) { + + git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%.*s'", + (int)path->size, path->ptr); + return -1; + } +#else + GIT_UNUSED(path); + GIT_UNUSED(suffix_len); +#endif + + return 0; +} + +int git_fs_path_normalize_slashes(git_str *out, const char *path) +{ + int error; + char *p; + + if ((error = git_str_puts(out, path)) < 0) + return error; + + for (p = out->ptr; *p; p++) { + if (*p == '\\') + *p = '/'; + } + + return 0; +} + +bool git_fs_path_supports_symlinks(const char *dir) +{ + git_str path = GIT_STR_INIT; + bool supported = false; + struct stat st; + int fd; + + if ((fd = git_futils_mktmp(&path, dir, 0666)) < 0 || + p_close(fd) < 0 || + p_unlink(path.ptr) < 0 || + p_symlink("testing", path.ptr) < 0 || + p_lstat(path.ptr, &st) < 0) + goto done; + + supported = (S_ISLNK(st.st_mode) != 0); +done: + if (path.size) + (void)p_unlink(path.ptr); + git_str_dispose(&path); + return supported; +} + +int git_fs_path_validate_system_file_ownership(const char *path) +{ +#ifndef GIT_WIN32 + GIT_UNUSED(path); + return GIT_OK; +#else + git_win32_path buf; + PSID owner_sid; + PSECURITY_DESCRIPTOR descriptor = NULL; + HANDLE token; + TOKEN_USER *info = NULL; + DWORD err, len; + int ret; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + err = GetNamedSecurityInfoW(buf, SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION, + &owner_sid, NULL, NULL, NULL, &descriptor); + + if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { + ret = GIT_ENOTFOUND; + goto cleanup; + } + + if (err != ERROR_SUCCESS) { + git_error_set(GIT_ERROR_OS, "failed to get security information"); + ret = GIT_ERROR; + goto cleanup; + } + + if (!IsValidSid(owner_sid)) { + git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is unknown"); + ret = GIT_ERROR; + goto cleanup; + } + + if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) || + IsWellKnownSid(owner_sid, WinLocalSystemSid)) { + ret = GIT_OK; + goto cleanup; + } + + /* Obtain current user's SID */ + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) && + !GetTokenInformation(token, TokenUser, NULL, 0, &len)) { + info = git__malloc(len); + GIT_ERROR_CHECK_ALLOC(info); + if (!GetTokenInformation(token, TokenUser, info, len, &len)) { + git__free(info); + info = NULL; + } + } + + /* + * If the file is owned by the same account that is running the current + * process, it's okay to read from that file. + */ + if (info && EqualSid(owner_sid, info->User.Sid)) + ret = GIT_OK; + else { + git_error_set(GIT_ERROR_INVALID, "programdata configuration file owner is not valid"); + ret = GIT_ERROR; + } + git__free(info); + +cleanup: + if (descriptor) + LocalFree(descriptor); + + return ret; +#endif +} + +int git_fs_path_find_executable(git_str *fullpath, const char *executable) +{ +#ifdef GIT_WIN32 + git_win32_path fullpath_w, executable_w; + int error; + + if (git__utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0) + return -1; + + error = git_win32_path_find_executable(fullpath_w, executable_w); + + if (error == 0) + error = git_str_put_w(fullpath, fullpath_w, wcslen(fullpath_w)); + + return error; +#else + git_str path = GIT_STR_INIT; + const char *current_dir, *term; + bool found = false; + + if (git__getenv(&path, "PATH") < 0) + return -1; + + current_dir = path.ptr; + + while (*current_dir) { + if (! (term = strchr(current_dir, GIT_PATH_LIST_SEPARATOR))) + term = strchr(current_dir, '\0'); + + git_str_clear(fullpath); + if (git_str_put(fullpath, current_dir, (term - current_dir)) < 0 || + git_str_putc(fullpath, '/') < 0 || + git_str_puts(fullpath, executable) < 0) + return -1; + + if (git_fs_path_isfile(fullpath->ptr)) { + found = true; + break; + } + + current_dir = term; + + while (*current_dir == GIT_PATH_LIST_SEPARATOR) + current_dir++; + } + + git_str_dispose(&path); + + if (found) + return 0; + + git_str_clear(fullpath); + return GIT_ENOTFOUND; +#endif +} diff --git a/src/util/fs_path.h b/src/util/fs_path.h new file mode 100644 index 000000000..bb840c43c --- /dev/null +++ b/src/util/fs_path.h @@ -0,0 +1,752 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_fs_path_h__ +#define INCLUDE_fs_path_h__ + +#include "git2_util.h" + +#include "posix.h" +#include "str.h" +#include "vector.h" +#include "utf8.h" + +/** + * Path manipulation utils + * + * These are path utilities that munge paths without actually + * looking at the real filesystem. + */ + +/* + * The dirname() function shall take a pointer to a character string + * that contains a pathname, and return a pointer to a string that is a + * pathname of the parent directory of that file. Trailing '/' characters + * in the path are not counted as part of the path. + * + * If path does not contain a '/', then dirname() shall return a pointer to + * the string ".". If path is a null pointer or points to an empty string, + * dirname() shall return a pointer to the string "." . + * + * The `git_fs_path_dirname` implementation is thread safe. The returned + * string must be manually free'd. + * + * The `git_fs_path_dirname_r` implementation writes the dirname to a `git_str` + * if the buffer pointer is not NULL. + * It returns an error code < 0 if there is an allocation error, otherwise + * the length of the dirname (which will be > 0). + */ +extern char *git_fs_path_dirname(const char *path); +extern int git_fs_path_dirname_r(git_str *buffer, const char *path); + +/* + * This function returns the basename of the file, which is the last + * part of its full name given by fname, with the drive letter and + * leading directories stripped off. For example, the basename of + * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo. + * + * Trailing slashes and backslashes are significant: the basename of + * c:/foo/bar/ is an empty string after the rightmost slash. + * + * The `git_fs_path_basename` implementation is thread safe. The returned + * string must be manually free'd. + * + * The `git_fs_path_basename_r` implementation writes the basename to a `git_str`. + * It returns an error code < 0 if there is an allocation error, otherwise + * the length of the basename (which will be >= 0). + */ +extern char *git_fs_path_basename(const char *path); +extern int git_fs_path_basename_r(git_str *buffer, const char *path); + +/* Return the offset of the start of the basename. Unlike the other + * basename functions, this returns 0 if the path is empty. + */ +extern size_t git_fs_path_basename_offset(git_str *buffer); + +/** + * Find offset to root of path if path has one. + * + * This will return a number >= 0 which is the offset to the start of the + * path, if the path is rooted (i.e. "/rooted/path" returns 0 and + * "c:/windows/rooted/path" returns 2). If the path is not rooted, this + * returns -1. + */ +extern int git_fs_path_root(const char *path); + +/** + * Ensure path has a trailing '/'. + */ +extern int git_fs_path_to_dir(git_str *path); + +/** + * Ensure string has a trailing '/' if there is space for it. + */ +extern void git_fs_path_string_to_dir(char *path, size_t size); + +/** + * Taken from git.git; returns nonzero if the given path is "." or "..". + */ +GIT_INLINE(int) git_fs_path_is_dot_or_dotdot(const char *name) +{ + return (name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0'))); +} + +#ifdef GIT_WIN32 +GIT_INLINE(int) git_fs_path_is_dot_or_dotdotW(const wchar_t *name) +{ + return (name[0] == L'.' && + (name[1] == L'\0' || + (name[1] == L'.' && name[2] == L'\0'))); +} + +#define git_fs_path_is_absolute(p) \ + (git__isalpha((p)[0]) && (p)[1] == ':' && ((p)[2] == '\\' || (p)[2] == '/')) + +#define git_fs_path_is_dirsep(p) \ + ((p) == '/' || (p) == '\\') + +/** + * Convert backslashes in path to forward slashes. + */ +GIT_INLINE(void) git_fs_path_mkposix(char *path) +{ + while (*path) { + if (*path == '\\') + *path = '/'; + + path++; + } +} +#else +# define git_fs_path_mkposix(p) /* blank */ + +#define git_fs_path_is_absolute(p) \ + ((p)[0] == '/') + +#define git_fs_path_is_dirsep(p) \ + ((p) == '/') + +#endif + +/** + * Check if string is a relative path (i.e. starts with "./" or "../") + */ +GIT_INLINE(int) git_fs_path_is_relative(const char *p) +{ + return (p[0] == '.' && (p[1] == '/' || (p[1] == '.' && p[2] == '/'))); +} + +/** + * Check if string is at end of path segment (i.e. looking at '/' or '\0') + */ +GIT_INLINE(int) git_fs_path_at_end_of_segment(const char *p) +{ + return !*p || *p == '/'; +} + +extern int git__percent_decode(git_str *decoded_out, const char *input); + +/** + * Extract path from file:// URL. + */ +extern int git_fs_path_fromurl(git_str *local_path_out, const char *file_url); + + +/** + * Path filesystem utils + * + * These are path utilities that actually access the filesystem. + */ + +/** + * Check if a file exists and can be accessed. + * @return true or false + */ +extern bool git_fs_path_exists(const char *path); + +/** + * Check if the given path points to a directory. + * @return true or false + */ +extern bool git_fs_path_isdir(const char *path); + +/** + * Check if the given path points to a regular file. + * @return true or false + */ +extern bool git_fs_path_isfile(const char *path); + +/** + * Check if the given path points to a symbolic link. + * @return true or false + */ +extern bool git_fs_path_islink(const char *path); + +/** + * Check if the given path is a directory, and is empty. + */ +extern bool git_fs_path_is_empty_dir(const char *path); + +/** + * Stat a file and/or link and set error if needed. + */ +extern int git_fs_path_lstat(const char *path, struct stat *st); + +/** + * Check if the parent directory contains the item. + * + * @param dir Directory to check. + * @param item Item that might be in the directory. + * @return 0 if item exists in directory, <0 otherwise. + */ +extern bool git_fs_path_contains(git_str *dir, const char *item); + +/** + * Check if the given path contains the given subdirectory. + * + * @param parent Directory path that might contain subdir + * @param subdir Subdirectory name to look for in parent + * @return true if subdirectory exists, false otherwise. + */ +extern bool git_fs_path_contains_dir(git_str *parent, const char *subdir); + +/** + * Determine the common directory length between two paths, including + * the final path separator. For example, given paths 'a/b/c/1.txt + * and 'a/b/c/d/2.txt', the common directory is 'a/b/c/', and this + * will return the length of the string 'a/b/c/', which is 6. + * + * @param one The first path + * @param two The second path + * @return The length of the common directory + */ +extern size_t git_fs_path_common_dirlen(const char *one, const char *two); + +/** + * Make the path relative to the given parent path. + * + * @param path The path to make relative + * @param parent The parent path to make path relative to + * @return 0 if path was made relative, GIT_ENOTFOUND + * if there was not common root between the paths, + * or <0. + */ +extern int git_fs_path_make_relative(git_str *path, const char *parent); + +/** + * Check if the given path contains the given file. + * + * @param dir Directory path that might contain file + * @param file File name to look for in parent + * @return true if file exists, false otherwise. + */ +extern bool git_fs_path_contains_file(git_str *dir, const char *file); + +/** + * Prepend base to unrooted path or just copy path over. + * + * This will optionally return the index into the path where the "root" + * is, either the end of the base directory prefix or the path root. + */ +extern int git_fs_path_join_unrooted( + git_str *path_out, const char *path, const char *base, ssize_t *root_at); + +/** + * Removes multiple occurrences of '/' in a row, squashing them into a + * single '/'. + */ +extern void git_fs_path_squash_slashes(git_str *path); + +/** + * Clean up path, prepending base if it is not already rooted. + */ +extern int git_fs_path_prettify(git_str *path_out, const char *path, const char *base); + +/** + * Clean up path, prepending base if it is not already rooted and + * appending a slash. + */ +extern int git_fs_path_prettify_dir(git_str *path_out, const char *path, const char *base); + +/** + * Get a directory from a path. + * + * If path is a directory, this acts like `git_fs_path_prettify_dir` + * (cleaning up path and appending a '/'). If path is a normal file, + * this prettifies it, then removed the filename a la dirname and + * appends the trailing '/'. If the path does not exist, it is + * treated like a regular filename. + */ +extern int git_fs_path_find_dir(git_str *dir); + +/** + * Resolve relative references within a path. + * + * This eliminates "./" and "../" relative references inside a path, + * as well as condensing multiple slashes into single ones. It will + * not touch the path before the "ceiling" length. + * + * Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL + * prefix and not touch that part of the path. + */ +extern int git_fs_path_resolve_relative(git_str *path, size_t ceiling); + +/** + * Apply a relative path to base path. + * + * Note that the base path could be a filename or a URL and this + * should still work. The relative path is walked segment by segment + * with three rules: series of slashes will be condensed to a single + * slash, "." will be eaten with no change, and ".." will remove a + * segment from the base path. + */ +extern int git_fs_path_apply_relative(git_str *target, const char *relpath); + +enum { + GIT_FS_PATH_DIR_IGNORE_CASE = (1u << 0), + GIT_FS_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1), + GIT_FS_PATH_DIR_INCLUDE_DOT_AND_DOTDOT = (1u << 2), +}; + +/** + * Walk each directory entry, except '.' and '..', calling fn(state). + * + * @param pathbuf Buffer the function reads the initial directory + * path from, and updates with each successive entry's name. + * @param flags Combination of GIT_FS_PATH_DIR flags. + * @param callback Callback for each entry. Passed the `payload` and each + * successive path inside the directory as a full path. This may + * safely append text to the pathbuf if needed. Return non-zero to + * cancel iteration (and return value will be propagated back). + * @param payload Passed to callback as first argument. + * @return 0 on success or error code from OS error or from callback + */ +extern int git_fs_path_direach( + git_str *pathbuf, + uint32_t flags, + int (*callback)(void *payload, git_str *path), + void *payload); + +/** + * Sort function to order two paths + */ +extern int git_fs_path_cmp( + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2, + int (*compare)(const char *, const char *, size_t)); + +/** + * Invoke callback up path directory by directory until the ceiling is + * reached (inclusive of a final call at the root_path). + * + * Returning anything other than 0 from the callback function + * will stop the iteration and propagate the error to the caller. + * + * @param pathbuf Buffer the function reads the directory from and + * and updates with each successive name. + * @param ceiling Prefix of path at which to stop walking up. If NULL, + * this will walk all the way up to the root. If not a prefix of + * pathbuf, the callback will be invoked a single time on the + * original input path. + * @param callback Function to invoke on each path. Passed the `payload` + * and the buffer containing the current path. The path should not + * be modified in any way. Return non-zero to stop iteration. + * @param payload Passed to fn as the first ath. + */ +extern int git_fs_path_walk_up( + git_str *pathbuf, + const char *ceiling, + int (*callback)(void *payload, const char *path), + void *payload); + + +enum { + GIT_FS_PATH_NOTEQUAL = 0, + GIT_FS_PATH_EQUAL = 1, + GIT_FS_PATH_PREFIX = 2 +}; + +/* + * Determines if a path is equal to or potentially a child of another. + * @param parent The possible parent + * @param child The possible child + */ +GIT_INLINE(int) git_fs_path_equal_or_prefixed( + const char *parent, + const char *child, + ssize_t *prefixlen) +{ + const char *p = parent, *c = child; + int lastslash = 0; + + while (*p && *c) { + lastslash = (*p == '/'); + + if (*p++ != *c++) + return GIT_FS_PATH_NOTEQUAL; + } + + if (*p != '\0') + return GIT_FS_PATH_NOTEQUAL; + + if (*c == '\0') { + if (prefixlen) + *prefixlen = p - parent; + + return GIT_FS_PATH_EQUAL; + } + + if (*c == '/' || lastslash) { + if (prefixlen) + *prefixlen = (p - parent) - lastslash; + + return GIT_FS_PATH_PREFIX; + } + + return GIT_FS_PATH_NOTEQUAL; +} + +/* translate errno to libgit2 error code and set error message */ +extern int git_fs_path_set_error( + int errno_value, const char *path, const char *action); + +/* check if non-ascii characters are present in filename */ +extern bool git_fs_path_has_non_ascii(const char *path, size_t pathlen); + +#define GIT_PATH_REPO_ENCODING "UTF-8" + +#ifdef __APPLE__ +#define GIT_PATH_NATIVE_ENCODING "UTF-8-MAC" +#else +#define GIT_PATH_NATIVE_ENCODING "UTF-8" +#endif + +#ifdef GIT_USE_ICONV + +#include + +typedef struct { + iconv_t map; + git_str buf; +} git_fs_path_iconv_t; + +#define GIT_PATH_ICONV_INIT { (iconv_t)-1, GIT_STR_INIT } + +/* Init iconv data for converting decomposed UTF-8 to precomposed */ +extern int git_fs_path_iconv_init_precompose(git_fs_path_iconv_t *ic); + +/* Clear allocated iconv data */ +extern void git_fs_path_iconv_clear(git_fs_path_iconv_t *ic); + +/* + * Rewrite `in` buffer using iconv map if necessary, replacing `in` + * pointer internal iconv buffer if rewrite happened. The `in` pointer + * will be left unchanged if no rewrite was needed. + */ +extern int git_fs_path_iconv(git_fs_path_iconv_t *ic, const char **in, size_t *inlen); + +#endif /* GIT_USE_ICONV */ + +extern bool git_fs_path_does_decompose_unicode(const char *root); + + +typedef struct git_fs_path_diriter git_fs_path_diriter; + +#if defined(GIT_WIN32) && !defined(__MINGW32__) + +struct git_fs_path_diriter +{ + git_win32_path path; + size_t parent_len; + + git_str path_utf8; + size_t parent_utf8_len; + + HANDLE handle; + + unsigned int flags; + + WIN32_FIND_DATAW current; + unsigned int needs_next; +}; + +#define GIT_FS_PATH_DIRITER_INIT { {0}, 0, GIT_STR_INIT, 0, INVALID_HANDLE_VALUE } + +#else + +struct git_fs_path_diriter +{ + git_str path; + size_t parent_len; + + unsigned int flags; + + DIR *dir; + +#ifdef GIT_USE_ICONV + git_fs_path_iconv_t ic; +#endif +}; + +#define GIT_FS_PATH_DIRITER_INIT { GIT_STR_INIT } + +#endif + +/** + * Initialize a directory iterator. + * + * @param diriter Pointer to a diriter structure that will be setup. + * @param path The path that will be iterated over + * @param flags Directory reader flags + * @return 0 or an error code + */ +extern int git_fs_path_diriter_init( + git_fs_path_diriter *diriter, + const char *path, + unsigned int flags); + +/** + * Advance the directory iterator. Will return GIT_ITEROVER when + * the iteration has completed successfully. + * + * @param diriter The directory iterator + * @return 0, GIT_ITEROVER, or an error code + */ +extern int git_fs_path_diriter_next(git_fs_path_diriter *diriter); + +/** + * Returns the file name of the current item in the iterator. + * + * @param out Pointer to store the path in + * @param out_len Pointer to store the length of the path in + * @param diriter The directory iterator + * @return 0 or an error code + */ +extern int git_fs_path_diriter_filename( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter); + +/** + * Returns the full path of the current item in the iterator; that + * is the current filename plus the path of the directory that the + * iterator was constructed with. + * + * @param out Pointer to store the path in + * @param out_len Pointer to store the length of the path in + * @param diriter The directory iterator + * @return 0 or an error code + */ +extern int git_fs_path_diriter_fullpath( + const char **out, + size_t *out_len, + git_fs_path_diriter *diriter); + +/** + * Performs an `lstat` on the current item in the iterator. + * + * @param out Pointer to store the stat data in + * @param diriter The directory iterator + * @return 0 or an error code + */ +extern int git_fs_path_diriter_stat(struct stat *out, git_fs_path_diriter *diriter); + +/** + * Closes the directory iterator. + * + * @param diriter The directory iterator + */ +extern void git_fs_path_diriter_free(git_fs_path_diriter *diriter); + +/** + * Load all directory entries (except '.' and '..') into a vector. + * + * For cases where `git_fs_path_direach()` is not appropriate, this + * allows you to load the filenames in a directory into a vector + * of strings. That vector can then be sorted, iterated, or whatever. + * Remember to free alloc of the allocated strings when you are done. + * + * @param contents Vector to fill with directory entry names. + * @param path The directory to read from. + * @param prefix_len When inserting entries, the trailing part of path + * will be prefixed after this length. I.e. given path "/a/b" and + * prefix_len 3, the entries will look like "b/e1", "b/e2", etc. + * @param flags Combination of GIT_FS_PATH_DIR flags. + */ +extern int git_fs_path_dirload( + git_vector *contents, + const char *path, + size_t prefix_len, + uint32_t flags); + + +/* Used for paths to repositories on the filesystem */ +extern bool git_fs_path_is_local_file_url(const char *file_url); +extern int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path); + +/* Flags to determine path validity in `git_fs_path_isvalid` */ +#define GIT_FS_PATH_REJECT_EMPTY_COMPONENT (1 << 0) +#define GIT_FS_PATH_REJECT_TRAVERSAL (1 << 1) +#define GIT_FS_PATH_REJECT_SLASH (1 << 2) +#define GIT_FS_PATH_REJECT_BACKSLASH (1 << 3) +#define GIT_FS_PATH_REJECT_TRAILING_DOT (1 << 4) +#define GIT_FS_PATH_REJECT_TRAILING_SPACE (1 << 5) +#define GIT_FS_PATH_REJECT_TRAILING_COLON (1 << 6) +#define GIT_FS_PATH_REJECT_DOS_PATHS (1 << 7) +#define GIT_FS_PATH_REJECT_NT_CHARS (1 << 8) +#define GIT_FS_PATH_REJECT_LONG_PATHS (1 << 9) + +#define GIT_FS_PATH_REJECT_MAX (1 << 9) + +/* Default path safety for writing files to disk: since we use the + * Win32 "File Namespace" APIs ("\\?\") we need to protect from + * paths that the normal Win32 APIs would not write. + */ +#ifdef GIT_WIN32 +# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \ + GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \ + GIT_FS_PATH_REJECT_TRAVERSAL | \ + GIT_FS_PATH_REJECT_BACKSLASH | \ + GIT_FS_PATH_REJECT_TRAILING_DOT | \ + GIT_FS_PATH_REJECT_TRAILING_SPACE | \ + GIT_FS_PATH_REJECT_TRAILING_COLON | \ + GIT_FS_PATH_REJECT_DOS_PATHS | \ + GIT_FS_PATH_REJECT_NT_CHARS +#else +# define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \ + GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \ + GIT_FS_PATH_REJECT_TRAVERSAL +#endif + +/** + * Validate a filesystem path; with custom callbacks per-character and + * per-path component. + */ +extern bool git_fs_path_str_is_valid_ext( + const git_str *path, + unsigned int flags, + bool (*validate_char_cb)(char ch, void *payload), + bool (*validate_component_cb)(const char *component, size_t len, void *payload), + bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len), + void *payload); + +GIT_INLINE(bool) git_fs_path_is_valid_ext( + const char *path, + unsigned int flags, + bool (*validate_char_cb)(char ch, void *payload), + bool (*validate_component_cb)(const char *component, size_t len, void *payload), + bool (*validate_length_cb)(const char *component, size_t len, size_t utf8_char_len), + void *payload) +{ + const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); + return git_fs_path_str_is_valid_ext( + &str, + flags, + validate_char_cb, + validate_component_cb, + validate_length_cb, + payload); +} + +/** + * Validate a filesystem path. This ensures that the given path is legal + * and does not contain any "unsafe" components like path traversal ('.' + * or '..'), characters that are inappropriate for lesser filesystems + * (trailing ' ' or ':' characters), or filenames ("component names") + * that are not supported ('AUX', 'COM1"). + */ +GIT_INLINE(bool) git_fs_path_is_valid( + const char *path, + unsigned int flags) +{ + const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX); + return git_fs_path_str_is_valid_ext(&str, flags, NULL, NULL, NULL, NULL); +} + +/** Validate a filesystem path in a `git_str`. */ +GIT_INLINE(bool) git_fs_path_str_is_valid( + const git_str *path, + unsigned int flags) +{ + return git_fs_path_str_is_valid_ext(path, flags, NULL, NULL, NULL, NULL); +} + +extern int git_fs_path_validate_str_length_with_suffix( + git_str *path, + size_t suffix_len); + +/** + * Validate an on-disk path, taking into account that it will have a + * suffix appended (eg, `.lock`). + */ +GIT_INLINE(int) git_fs_path_validate_filesystem_with_suffix( + const char *path, + size_t path_len, + size_t suffix_len) +{ +#ifdef GIT_WIN32 + size_t path_chars, total_chars; + + path_chars = git_utf8_char_length(path, path_len); + + if (GIT_ADD_SIZET_OVERFLOW(&total_chars, path_chars, suffix_len) || + total_chars > MAX_PATH) { + git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%s'", path); + return -1; + } + return 0; +#else + GIT_UNUSED(path); + GIT_UNUSED(path_len); + GIT_UNUSED(suffix_len); + return 0; +#endif +} + +/** + * Validate an path on the filesystem. This ensures that the given + * path is valid for the operating system/platform; for example, this + * will ensure that the given absolute path is smaller than MAX_PATH on + * Windows. + * + * For paths within the working directory, you should use ensure that + * `core.longpaths` is obeyed. Use `git_fs_path_validate_workdir`. + */ +GIT_INLINE(int) git_fs_path_validate_filesystem( + const char *path, + size_t path_len) +{ + return git_fs_path_validate_filesystem_with_suffix(path, path_len, 0); +} + +/** + * Convert any backslashes into slashes + */ +int git_fs_path_normalize_slashes(git_str *out, const char *path); + +bool git_fs_path_supports_symlinks(const char *dir); + +/** + * Validate a system file's ownership + * + * Verify that the file in question is owned by an administrator or system + * account, or at least by the current user. + * + * This function returns 0 if successful. If the file is not owned by any of + * these, or any other if there have been problems determining the file + * ownership, it returns -1. + */ +int git_fs_path_validate_system_file_ownership(const char *path); + +/** + * Search the current PATH for the given executable, returning the full + * path if it is found. + */ +int git_fs_path_find_executable(git_str *fullpath, const char *executable); + +#endif diff --git a/src/util/futils.c b/src/util/futils.c new file mode 100644 index 000000000..2b0dbf362 --- /dev/null +++ b/src/util/futils.c @@ -0,0 +1,1194 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "futils.h" + +#include "runtime.h" +#include "strmap.h" +#include "hash.h" +#include "rand.h" + +#include +#if GIT_WIN32 +#include "win32/findfile.h" +#endif + +#define GIT_FILEMODE_DEFAULT 0100666 + +int git_futils_mkpath2file(const char *file_path, const mode_t mode) +{ + return git_futils_mkdir( + file_path, mode, + GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); +} + +int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode) +{ + const int open_flags = O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC; + unsigned int tries = 32; + int fd; + + while (tries--) { + uint64_t rand = git_rand_next(); + + git_str_sets(path_out, filename); + git_str_puts(path_out, "_git2_"); + git_str_encode_hexstr(path_out, (void *)&rand, sizeof(uint64_t)); + + if (git_str_oom(path_out)) + return -1; + + /* Note that we open with O_CREAT | O_EXCL */ + if ((fd = p_open(path_out->ptr, open_flags, mode)) >= 0) + return fd; + } + + git_error_set(GIT_ERROR_OS, + "failed to create temporary file '%s'", path_out->ptr); + git_str_dispose(path_out); + return -1; +} + +int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode) +{ + int fd; + + if (git_futils_mkpath2file(path, dirmode) < 0) + return -1; + + fd = p_creat(path, mode); + if (fd < 0) { + git_error_set(GIT_ERROR_OS, "failed to create file '%s'", path); + return -1; + } + + return fd; +} + +int git_futils_creat_locked(const char *path, const mode_t mode) +{ + int fd = p_open(path, O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, + mode); + + if (fd < 0) { + int error = errno; + git_error_set(GIT_ERROR_OS, "failed to create locked file '%s'", path); + switch (error) { + case EEXIST: + return GIT_ELOCKED; + case ENOENT: + return GIT_ENOTFOUND; + default: + return -1; + } + } + + return fd; +} + +int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode) +{ + if (git_futils_mkpath2file(path, dirmode) < 0) + return -1; + + return git_futils_creat_locked(path, mode); +} + +int git_futils_open_ro(const char *path) +{ + int fd = p_open(path, O_RDONLY); + if (fd < 0) + return git_fs_path_set_error(errno, path, "open"); + return fd; +} + +int git_futils_truncate(const char *path, int mode) +{ + int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode); + if (fd < 0) + return git_fs_path_set_error(errno, path, "open"); + + close(fd); + return 0; +} + +int git_futils_filesize(uint64_t *out, git_file fd) +{ + struct stat sb; + + if (p_fstat(fd, &sb)) { + git_error_set(GIT_ERROR_OS, "failed to stat file descriptor"); + return -1; + } + + if (sb.st_size < 0) { + git_error_set(GIT_ERROR_INVALID, "invalid file size"); + return -1; + } + + *out = sb.st_size; + return 0; +} + +mode_t git_futils_canonical_mode(mode_t raw_mode) +{ + if (S_ISREG(raw_mode)) + return S_IFREG | GIT_PERMS_CANONICAL(raw_mode); + else if (S_ISLNK(raw_mode)) + return S_IFLNK; + else if (S_ISGITLINK(raw_mode)) + return S_IFGITLINK; + else if (S_ISDIR(raw_mode)) + return S_IFDIR; + else + return 0; +} + +int git_futils_readbuffer_fd(git_str *buf, git_file fd, size_t len) +{ + ssize_t read_size = 0; + size_t alloc_len; + + git_str_clear(buf); + + if (!git__is_ssizet(len)) { + git_error_set(GIT_ERROR_INVALID, "read too large"); + return -1; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1); + if (git_str_grow(buf, alloc_len) < 0) + return -1; + + /* p_read loops internally to read len bytes */ + read_size = p_read(fd, buf->ptr, len); + + if (read_size != (ssize_t)len) { + git_error_set(GIT_ERROR_OS, "failed to read descriptor"); + git_str_dispose(buf); + return -1; + } + + buf->ptr[read_size] = '\0'; + buf->size = read_size; + + return 0; +} + +int git_futils_readbuffer_updated( + git_str *out, + const char *path, + unsigned char checksum[GIT_HASH_SHA1_SIZE], + int *updated) +{ + int error; + git_file fd; + struct stat st; + git_str buf = GIT_STR_INIT; + unsigned char checksum_new[GIT_HASH_SHA1_SIZE]; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(path && *path); + + if (updated != NULL) + *updated = 0; + + if (p_stat(path, &st) < 0) + return git_fs_path_set_error(errno, path, "stat"); + + + if (S_ISDIR(st.st_mode)) { + git_error_set(GIT_ERROR_INVALID, "requested file is a directory"); + return GIT_ENOTFOUND; + } + + if (!git__is_sizet(st.st_size+1)) { + git_error_set(GIT_ERROR_OS, "invalid regular file stat for '%s'", path); + return -1; + } + + if ((fd = git_futils_open_ro(path)) < 0) + return fd; + + if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) { + p_close(fd); + return -1; + } + + p_close(fd); + + if (checksum) { + if ((error = git_hash_buf(checksum_new, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0) { + git_str_dispose(&buf); + return error; + } + + /* + * If we were given a checksum, we only want to use it if it's different + */ + if (!memcmp(checksum, checksum_new, GIT_HASH_SHA1_SIZE)) { + git_str_dispose(&buf); + if (updated) + *updated = 0; + + return 0; + } + + memcpy(checksum, checksum_new, GIT_HASH_SHA1_SIZE); + } + + /* + * If we're here, the file did change, or the user didn't have an old version + */ + if (updated != NULL) + *updated = 1; + + git_str_swap(out, &buf); + git_str_dispose(&buf); + + return 0; +} + +int git_futils_readbuffer(git_str *buf, const char *path) +{ + return git_futils_readbuffer_updated(buf, path, NULL, NULL); +} + +int git_futils_writebuffer( + const git_str *buf, const char *path, int flags, mode_t mode) +{ + int fd, do_fsync = 0, error = 0; + + if (!flags) + flags = O_CREAT | O_TRUNC | O_WRONLY; + + if ((flags & O_FSYNC) != 0) + do_fsync = 1; + + flags &= ~O_FSYNC; + + if (!mode) + mode = GIT_FILEMODE_DEFAULT; + + if ((fd = p_open(path, flags, mode)) < 0) { + git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path); + return fd; + } + + if ((error = p_write(fd, git_str_cstr(buf), git_str_len(buf))) < 0) { + git_error_set(GIT_ERROR_OS, "could not write to '%s'", path); + (void)p_close(fd); + return error; + } + + if (do_fsync && (error = p_fsync(fd)) < 0) { + git_error_set(GIT_ERROR_OS, "could not fsync '%s'", path); + p_close(fd); + return error; + } + + if ((error = p_close(fd)) < 0) { + git_error_set(GIT_ERROR_OS, "error while closing '%s'", path); + return error; + } + + if (do_fsync && (flags & O_CREAT)) + error = git_futils_fsync_parent(path); + + return error; +} + +int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) +{ + if (git_futils_mkpath2file(to, dirmode) < 0) + return -1; + + if (p_rename(from, to) < 0) { + git_error_set(GIT_ERROR_OS, "failed to rename '%s' to '%s'", from, to); + return -1; + } + + return 0; +} + +int git_futils_mmap_ro(git_map *out, git_file fd, off64_t begin, size_t len) +{ + return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin); +} + +int git_futils_mmap_ro_file(git_map *out, const char *path) +{ + git_file fd = git_futils_open_ro(path); + uint64_t len; + int result; + + if (fd < 0) + return fd; + + if ((result = git_futils_filesize(&len, fd)) < 0) + goto out; + + if (!git__is_sizet(len)) { + git_error_set(GIT_ERROR_OS, "file `%s` too large to mmap", path); + result = -1; + goto out; + } + + result = git_futils_mmap_ro(out, fd, 0, (size_t)len); +out: + p_close(fd); + return result; +} + +void git_futils_mmap_free(git_map *out) +{ + p_munmap(out); +} + +GIT_INLINE(int) mkdir_validate_dir( + const char *path, + struct stat *st, + mode_t mode, + uint32_t flags, + struct git_futils_mkdir_options *opts) +{ + /* with exclusive create, existing dir is an error */ + if ((flags & GIT_MKDIR_EXCL) != 0) { + git_error_set(GIT_ERROR_FILESYSTEM, + "failed to make directory '%s': directory exists", path); + return GIT_EEXISTS; + } + + if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) || + (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) { + if (p_unlink(path) < 0) { + git_error_set(GIT_ERROR_OS, "failed to remove %s '%s'", + S_ISLNK(st->st_mode) ? "symlink" : "file", path); + return GIT_EEXISTS; + } + + opts->perfdata.mkdir_calls++; + + if (p_mkdir(path, mode) < 0) { + git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path); + return GIT_EEXISTS; + } + } + + else if (S_ISLNK(st->st_mode)) { + /* Re-stat the target, make sure it's a directory */ + opts->perfdata.stat_calls++; + + if (p_stat(path, st) < 0) { + git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path); + return GIT_EEXISTS; + } + } + + else if (!S_ISDIR(st->st_mode)) { + git_error_set(GIT_ERROR_FILESYSTEM, + "failed to make directory '%s': directory exists", path); + return GIT_EEXISTS; + } + + return 0; +} + +GIT_INLINE(int) mkdir_validate_mode( + const char *path, + struct stat *st, + bool terminal_path, + mode_t mode, + uint32_t flags, + struct git_futils_mkdir_options *opts) +{ + if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) || + (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) { + + opts->perfdata.chmod_calls++; + + if (p_chmod(path, mode) < 0) { + git_error_set(GIT_ERROR_OS, "failed to set permissions on '%s'", path); + return -1; + } + } + + return 0; +} + +GIT_INLINE(int) mkdir_canonicalize( + git_str *path, + uint32_t flags) +{ + ssize_t root_len; + + if (path->size == 0) { + git_error_set(GIT_ERROR_OS, "attempt to create empty path"); + return -1; + } + + /* Trim trailing slashes (except the root) */ + if ((root_len = git_fs_path_root(path->ptr)) < 0) + root_len = 0; + else + root_len++; + + while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/') + path->ptr[--path->size] = '\0'; + + /* if we are not supposed to made the last element, truncate it */ + if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) { + git_fs_path_dirname_r(path, path->ptr); + flags |= GIT_MKDIR_SKIP_LAST; + } + if ((flags & GIT_MKDIR_SKIP_LAST) != 0) { + git_fs_path_dirname_r(path, path->ptr); + } + + /* We were either given the root path (or trimmed it to + * the root), we don't have anything to do. + */ + if (path->size <= (size_t)root_len) + git_str_clear(path); + + return 0; +} + +int git_futils_mkdir( + const char *path, + mode_t mode, + uint32_t flags) +{ + git_str make_path = GIT_STR_INIT, parent_path = GIT_STR_INIT; + const char *relative; + struct git_futils_mkdir_options opts = { 0 }; + struct stat st; + size_t depth = 0; + int len = 0, root_len, error; + + if ((error = git_str_puts(&make_path, path)) < 0 || + (error = mkdir_canonicalize(&make_path, flags)) < 0 || + (error = git_str_puts(&parent_path, make_path.ptr)) < 0 || + make_path.size == 0) + goto done; + + root_len = git_fs_path_root(make_path.ptr); + + /* find the first parent directory that exists. this will be used + * as the base to dirname_relative. + */ + for (relative = make_path.ptr; parent_path.size; ) { + error = p_lstat(parent_path.ptr, &st); + + if (error == 0) { + break; + } else if (errno != ENOENT) { + git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr); + error = -1; + goto done; + } + + depth++; + + /* examine the parent of the current path */ + if ((len = git_fs_path_dirname_r(&parent_path, parent_path.ptr)) < 0) { + error = len; + goto done; + } + + GIT_ASSERT(len); + + /* + * We've walked all the given path's parents and it's either relative + * (the parent is simply '.') or rooted (the length is less than or + * equal to length of the root path). The path may be less than the + * root path length on Windows, where `C:` == `C:/`. + */ + if ((len == 1 && parent_path.ptr[0] == '.') || + (len == 1 && parent_path.ptr[0] == '/') || + len <= root_len) { + relative = make_path.ptr; + break; + } + + relative = make_path.ptr + len + 1; + + /* not recursive? just make this directory relative to its parent. */ + if ((flags & GIT_MKDIR_PATH) == 0) + break; + } + + /* we found an item at the location we're trying to create, + * validate it. + */ + if (depth == 0) { + error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts); + + if (!error) + error = mkdir_validate_mode( + make_path.ptr, &st, true, mode, flags, &opts); + + goto done; + } + + /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when + * canonicalizing `make_path`. + */ + flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST); + + error = git_futils_mkdir_relative(relative, + parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts); + +done: + git_str_dispose(&make_path); + git_str_dispose(&parent_path); + return error; +} + +int git_futils_mkdir_r(const char *path, const mode_t mode) +{ + return git_futils_mkdir(path, mode, GIT_MKDIR_PATH); +} + +int git_futils_mkdir_relative( + const char *relative_path, + const char *base, + mode_t mode, + uint32_t flags, + struct git_futils_mkdir_options *opts) +{ + git_str make_path = GIT_STR_INIT; + ssize_t root = 0, min_root_len; + char lastch = '/', *tail; + struct stat st; + struct git_futils_mkdir_options empty_opts = {0}; + int error; + + if (!opts) + opts = &empty_opts; + + /* build path and find "root" where we should start calling mkdir */ + if (git_fs_path_join_unrooted(&make_path, relative_path, base, &root) < 0) + return -1; + + if ((error = mkdir_canonicalize(&make_path, flags)) < 0 || + make_path.size == 0) + goto done; + + /* if we are not supposed to make the whole path, reset root */ + if ((flags & GIT_MKDIR_PATH) == 0) + root = git_str_rfind(&make_path, '/'); + + /* advance root past drive name or network mount prefix */ + min_root_len = git_fs_path_root(make_path.ptr); + if (root < min_root_len) + root = min_root_len; + while (root >= 0 && make_path.ptr[root] == '/') + ++root; + + /* clip root to make_path length */ + if (root > (ssize_t)make_path.size) + root = (ssize_t)make_path.size; /* i.e. NUL byte of string */ + if (root < 0) + root = 0; + + /* walk down tail of path making each directory */ + for (tail = &make_path.ptr[root]; *tail; *tail = lastch) { + bool mkdir_attempted = false; + + /* advance tail to include next path component */ + while (*tail == '/') + tail++; + while (*tail && *tail != '/') + tail++; + + /* truncate path at next component */ + lastch = *tail; + *tail = '\0'; + st.st_mode = 0; + + if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr)) + continue; + + /* See what's going on with this path component */ + opts->perfdata.stat_calls++; + +retry_lstat: + if (p_lstat(make_path.ptr, &st) < 0) { + if (mkdir_attempted || errno != ENOENT) { + git_error_set(GIT_ERROR_OS, "cannot access component in path '%s'", make_path.ptr); + error = -1; + goto done; + } + + git_error_clear(); + opts->perfdata.mkdir_calls++; + mkdir_attempted = true; + if (p_mkdir(make_path.ptr, mode) < 0) { + if (errno == EEXIST) + goto retry_lstat; + git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", make_path.ptr); + error = -1; + goto done; + } + } else { + if ((error = mkdir_validate_dir( + make_path.ptr, &st, mode, flags, opts)) < 0) + goto done; + } + + /* chmod if requested and necessary */ + if ((error = mkdir_validate_mode( + make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0) + goto done; + + if (opts->dir_map && opts->pool) { + char *cache_path; + size_t alloc_size; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1); + cache_path = git_pool_malloc(opts->pool, alloc_size); + GIT_ERROR_CHECK_ALLOC(cache_path); + + memcpy(cache_path, make_path.ptr, make_path.size + 1); + + if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0) + goto done; + } + } + + error = 0; + + /* check that full path really is a directory if requested & needed */ + if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 && + lastch != '\0') { + opts->perfdata.stat_calls++; + + if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) { + git_error_set(GIT_ERROR_OS, "path is not a directory '%s'", + make_path.ptr); + error = GIT_ENOTFOUND; + } + } + +done: + git_str_dispose(&make_path); + return error; +} + +typedef struct { + const char *base; + size_t baselen; + uint32_t flags; + int depth; +} futils__rmdir_data; + +#define FUTILS_MAX_DEPTH 100 + +static int futils__error_cannot_rmdir(const char *path, const char *filemsg) +{ + if (filemsg) + git_error_set(GIT_ERROR_OS, "could not remove directory '%s': %s", + path, filemsg); + else + git_error_set(GIT_ERROR_OS, "could not remove directory '%s'", path); + + return -1; +} + +static int futils__rm_first_parent(git_str *path, const char *ceiling) +{ + int error = GIT_ENOTFOUND; + struct stat st; + + while (error == GIT_ENOTFOUND) { + git_str_rtruncate_at_char(path, '/'); + + if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0) + error = 0; + else if (p_lstat_posixly(path->ptr, &st) == 0) { + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) + error = p_unlink(path->ptr); + else if (!S_ISDIR(st.st_mode)) + error = -1; /* fail to remove non-regular file */ + } else if (errno != ENOTDIR) + error = -1; + } + + if (error) + futils__error_cannot_rmdir(path->ptr, "cannot remove parent"); + + return error; +} + +static int futils__rmdir_recurs_foreach(void *opaque, git_str *path) +{ + int error = 0; + futils__rmdir_data *data = opaque; + struct stat st; + + if (data->depth > FUTILS_MAX_DEPTH) + error = futils__error_cannot_rmdir( + path->ptr, "directory nesting too deep"); + + else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) { + if (errno == ENOENT) + error = 0; + else if (errno == ENOTDIR) { + /* asked to remove a/b/c/d/e and a/b is a normal file */ + if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0) + error = futils__rm_first_parent(path, data->base); + else + futils__error_cannot_rmdir( + path->ptr, "parent is not directory"); + } + else + error = git_fs_path_set_error(errno, path->ptr, "rmdir"); + } + + else if (S_ISDIR(st.st_mode)) { + data->depth++; + + error = git_fs_path_direach(path, 0, futils__rmdir_recurs_foreach, data); + + data->depth--; + + if (error < 0) + return error; + + if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0) + return error; + + if ((error = p_rmdir(path->ptr)) < 0) { + if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 && + (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY)) + error = 0; + else + error = git_fs_path_set_error(errno, path->ptr, "rmdir"); + } + } + + else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) { + if (p_unlink(path->ptr) < 0) + error = git_fs_path_set_error(errno, path->ptr, "remove"); + } + + else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0) + error = futils__error_cannot_rmdir(path->ptr, "still present"); + + return error; +} + +static int futils__rmdir_empty_parent(void *opaque, const char *path) +{ + futils__rmdir_data *data = opaque; + int error = 0; + + if (strlen(path) <= data->baselen) + error = GIT_ITEROVER; + + else if (p_rmdir(path) < 0) { + int en = errno; + + if (en == ENOENT || en == ENOTDIR) { + /* do nothing */ + } else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 && + en == EBUSY) { + error = git_fs_path_set_error(errno, path, "rmdir"); + } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) { + error = GIT_ITEROVER; + } else { + error = git_fs_path_set_error(errno, path, "rmdir"); + } + } + + return error; +} + +int git_futils_rmdir_r( + const char *path, const char *base, uint32_t flags) +{ + int error; + git_str fullpath = GIT_STR_INIT; + futils__rmdir_data data; + + /* build path and find "root" where we should start calling mkdir */ + if (git_fs_path_join_unrooted(&fullpath, path, base, NULL) < 0) + return -1; + + memset(&data, 0, sizeof(data)); + data.base = base ? base : ""; + data.baselen = base ? strlen(base) : 0; + data.flags = flags; + + error = futils__rmdir_recurs_foreach(&data, &fullpath); + + /* remove now-empty parents if requested */ + if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0) + error = git_fs_path_walk_up( + &fullpath, base, futils__rmdir_empty_parent, &data); + + if (error == GIT_ITEROVER) { + git_error_clear(); + error = 0; + } + + git_str_dispose(&fullpath); + + return error; +} + +int git_futils_fake_symlink(const char *target, const char *path) +{ + int retcode = GIT_ERROR; + int fd = git_futils_creat_withpath(path, 0755, 0644); + if (fd >= 0) { + retcode = p_write(fd, target, strlen(target)); + p_close(fd); + } + return retcode; +} + +static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done) +{ + int error = 0; + char buffer[GIT_BUFSIZE_FILEIO]; + ssize_t len = 0; + + while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0) + /* p_write() does not have the same semantics as write(). It loops + * internally and will return 0 when it has completed writing. + */ + error = p_write(ofd, buffer, len); + + if (len < 0) { + git_error_set(GIT_ERROR_OS, "read error while copying file"); + error = (int)len; + } + + if (error < 0) + git_error_set(GIT_ERROR_OS, "write error while copying file"); + + if (close_fd_when_done) { + p_close(ifd); + p_close(ofd); + } + + return error; +} + +int git_futils_cp(const char *from, const char *to, mode_t filemode) +{ + int ifd, ofd; + + if ((ifd = git_futils_open_ro(from)) < 0) + return ifd; + + if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) { + p_close(ifd); + return git_fs_path_set_error(errno, to, "open for writing"); + } + + return cp_by_fd(ifd, ofd, true); +} + +int git_futils_touch(const char *path, time_t *when) +{ + struct p_timeval times[2]; + int ret; + + times[0].tv_sec = times[1].tv_sec = when ? *when : time(NULL); + times[0].tv_usec = times[1].tv_usec = 0; + + ret = p_utimes(path, times); + + return (ret < 0) ? git_fs_path_set_error(errno, path, "touch") : 0; +} + +static int cp_link(const char *from, const char *to, size_t link_size) +{ + int error = 0; + ssize_t read_len; + char *link_data; + size_t alloc_size; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1); + link_data = git__malloc(alloc_size); + GIT_ERROR_CHECK_ALLOC(link_data); + + read_len = p_readlink(from, link_data, link_size); + if (read_len != (ssize_t)link_size) { + git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", from); + error = -1; + } + else { + link_data[read_len] = '\0'; + + if (p_symlink(link_data, to) < 0) { + git_error_set(GIT_ERROR_OS, "could not symlink '%s' as '%s'", + link_data, to); + error = -1; + } + } + + git__free(link_data); + return error; +} + +typedef struct { + const char *to_root; + git_str to; + ssize_t from_prefix; + uint32_t flags; + uint32_t mkdir_flags; + mode_t dirmode; +} cp_r_info; + +#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10) + +static int _cp_r_mkdir(cp_r_info *info, git_str *from) +{ + int error = 0; + + /* create root directory the first time we need to create a directory */ + if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) { + error = git_futils_mkdir( + info->to_root, info->dirmode, + (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0); + + info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT; + } + + /* create directory with root as base to prevent excess chmods */ + if (!error) + error = git_futils_mkdir_relative( + from->ptr + info->from_prefix, info->to_root, + info->dirmode, info->mkdir_flags, NULL); + + return error; +} + +static int _cp_r_callback(void *ref, git_str *from) +{ + int error = 0; + cp_r_info *info = ref; + struct stat from_st, to_st; + bool exists = false; + + if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 && + from->ptr[git_fs_path_basename_offset(from)] == '.') + return 0; + + if ((error = git_str_joinpath( + &info->to, info->to_root, from->ptr + info->from_prefix)) < 0) + return error; + + if (!(error = git_fs_path_lstat(info->to.ptr, &to_st))) + exists = true; + else if (error != GIT_ENOTFOUND) + return error; + else { + git_error_clear(); + error = 0; + } + + if ((error = git_fs_path_lstat(from->ptr, &from_st)) < 0) + return error; + + if (S_ISDIR(from_st.st_mode)) { + mode_t oldmode = info->dirmode; + + /* if we are not chmod'ing, then overwrite dirmode */ + if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0) + info->dirmode = from_st.st_mode; + + /* make directory now if CREATE_EMPTY_DIRS is requested and needed */ + if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0) + error = _cp_r_mkdir(info, from); + + /* recurse onto target directory */ + if (!error && (!exists || S_ISDIR(to_st.st_mode))) + error = git_fs_path_direach(from, 0, _cp_r_callback, info); + + if (oldmode != 0) + info->dirmode = oldmode; + + return error; + } + + if (exists) { + if ((info->flags & GIT_CPDIR_OVERWRITE) == 0) + return 0; + + if (p_unlink(info->to.ptr) < 0) { + git_error_set(GIT_ERROR_OS, "cannot overwrite existing file '%s'", + info->to.ptr); + return GIT_EEXISTS; + } + } + + /* Done if this isn't a regular file or a symlink */ + if (!S_ISREG(from_st.st_mode) && + (!S_ISLNK(from_st.st_mode) || + (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0)) + return 0; + + /* Make container directory on demand if needed */ + if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 && + (error = _cp_r_mkdir(info, from)) < 0) + return error; + + /* make symlink or regular file */ + if (info->flags & GIT_CPDIR_LINK_FILES) { + if ((error = p_link(from->ptr, info->to.ptr)) < 0) + git_error_set(GIT_ERROR_OS, "failed to link '%s'", from->ptr); + } else if (S_ISLNK(from_st.st_mode)) { + error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); + } else { + mode_t usemode = from_st.st_mode; + + if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) + usemode = GIT_PERMS_FOR_WRITE(usemode); + + error = git_futils_cp(from->ptr, info->to.ptr, usemode); + } + + return error; +} + +int git_futils_cp_r( + const char *from, + const char *to, + uint32_t flags, + mode_t dirmode) +{ + int error; + git_str path = GIT_STR_INIT; + cp_r_info info; + + if (git_str_joinpath(&path, from, "") < 0) /* ensure trailing slash */ + return -1; + + memset(&info, 0, sizeof(info)); + info.to_root = to; + info.flags = flags; + info.dirmode = dirmode; + info.from_prefix = path.size; + git_str_init(&info.to, 0); + + /* precalculate mkdir flags */ + if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) { + /* if not creating empty dirs, then use mkdir to create the path on + * demand right before files are copied. + */ + info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST; + if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) + info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH; + } else { + /* otherwise, we will do simple mkdir as directories are encountered */ + info.mkdir_flags = + ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0; + } + + error = _cp_r_callback(&info, &path); + + git_str_dispose(&path); + git_str_dispose(&info.to); + + return error; +} + +int git_futils_filestamp_check( + git_futils_filestamp *stamp, const char *path) +{ + struct stat st; + + /* if the stamp is NULL, then always reload */ + if (stamp == NULL) + return 1; + + if (p_stat(path, &st) < 0) + return GIT_ENOTFOUND; + + if (stamp->mtime.tv_sec == st.st_mtime && +#if defined(GIT_USE_NSEC) + stamp->mtime.tv_nsec == st.st_mtime_nsec && +#endif + stamp->size == (uint64_t)st.st_size && + stamp->ino == (unsigned int)st.st_ino) + return 0; + + stamp->mtime.tv_sec = st.st_mtime; +#if defined(GIT_USE_NSEC) + stamp->mtime.tv_nsec = st.st_mtime_nsec; +#endif + stamp->size = (uint64_t)st.st_size; + stamp->ino = (unsigned int)st.st_ino; + + return 1; +} + +void git_futils_filestamp_set( + git_futils_filestamp *target, const git_futils_filestamp *source) +{ + if (source) + memcpy(target, source, sizeof(*target)); + else + memset(target, 0, sizeof(*target)); +} + + +void git_futils_filestamp_set_from_stat( + git_futils_filestamp *stamp, struct stat *st) +{ + if (st) { + stamp->mtime.tv_sec = st->st_mtime; +#if defined(GIT_USE_NSEC) + stamp->mtime.tv_nsec = st->st_mtime_nsec; +#else + stamp->mtime.tv_nsec = 0; +#endif + stamp->size = (uint64_t)st->st_size; + stamp->ino = (unsigned int)st->st_ino; + } else { + memset(stamp, 0, sizeof(*stamp)); + } +} + +int git_futils_fsync_dir(const char *path) +{ +#ifdef GIT_WIN32 + GIT_UNUSED(path); + return 0; +#else + int fd, error = -1; + + if ((fd = p_open(path, O_RDONLY)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to open directory '%s' for fsync", path); + return -1; + } + + if ((error = p_fsync(fd)) < 0) + git_error_set(GIT_ERROR_OS, "failed to fsync directory '%s'", path); + + p_close(fd); + return error; +#endif +} + +int git_futils_fsync_parent(const char *path) +{ + char *parent; + int error; + + if ((parent = git_fs_path_dirname(path)) == NULL) + return -1; + + error = git_futils_fsync_dir(parent); + git__free(parent); + return error; +} diff --git a/src/util/futils.h b/src/util/futils.h new file mode 100644 index 000000000..fb1afcbd5 --- /dev/null +++ b/src/util/futils.h @@ -0,0 +1,402 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_futils_h__ +#define INCLUDE_futils_h__ + +#include "git2_util.h" + +#include "map.h" +#include "posix.h" +#include "fs_path.h" +#include "pool.h" +#include "strmap.h" +#include "hash.h" + +/** + * Filebuffer methods + * + * Read whole files into an in-memory buffer for processing + */ +extern int git_futils_readbuffer(git_str *obj, const char *path); +extern int git_futils_readbuffer_updated( + git_str *obj, + const char *path, + unsigned char checksum[GIT_HASH_SHA1_SIZE], + int *updated); +extern int git_futils_readbuffer_fd(git_str *obj, git_file fd, size_t len); + +/* Additional constants for `git_futils_writebuffer`'s `open_flags`. We + * support these internally and they will be removed before the `open` call. + */ +#ifndef O_FSYNC +# define O_FSYNC (1 << 31) +#endif + +extern int git_futils_writebuffer( + const git_str *buf, const char *path, int open_flags, mode_t mode); + +/** + * File utils + * + * These are custom filesystem-related helper methods. They are + * rather high level, and wrap the underlying POSIX methods + * + * All these methods return 0 on success, + * or an error code on failure and an error message is set. + */ + +/** + * Create and open a file, while also + * creating all the folders in its path + */ +extern int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode); + +/** + * Create and open a process-locked file + */ +extern int git_futils_creat_locked(const char *path, const mode_t mode); + +/** + * Create and open a process-locked file, while + * also creating all the folders in its path + */ +extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode); + +/** + * Create a path recursively. + */ +extern int git_futils_mkdir_r(const char *path, const mode_t mode); + +/** + * Flags to pass to `git_futils_mkdir`. + * + * * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists. + * * GIT_MKDIR_PATH says to make all components in the path. + * * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation + * * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path + * * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path + * * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path + * * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST + * * GIT_MKDIR_REMOVE_FILES says to remove files and recreate dirs + * * GIT_MKDIR_REMOVE_SYMLINKS says to remove symlinks and recreate dirs + * + * Note that the chmod options will be executed even if the directory already + * exists, unless GIT_MKDIR_EXCL is given. + */ +typedef enum { + GIT_MKDIR_EXCL = 1, + GIT_MKDIR_PATH = 2, + GIT_MKDIR_CHMOD = 4, + GIT_MKDIR_CHMOD_PATH = 8, + GIT_MKDIR_SKIP_LAST = 16, + GIT_MKDIR_SKIP_LAST2 = 32, + GIT_MKDIR_VERIFY_DIR = 64, + GIT_MKDIR_REMOVE_FILES = 128, + GIT_MKDIR_REMOVE_SYMLINKS = 256 +} git_futils_mkdir_flags; + +struct git_futils_mkdir_perfdata +{ + size_t stat_calls; + size_t mkdir_calls; + size_t chmod_calls; +}; + +struct git_futils_mkdir_options +{ + git_strmap *dir_map; + git_pool *pool; + struct git_futils_mkdir_perfdata perfdata; +}; + +/** + * Create a directory or entire path. + * + * This makes a directory (and the entire path leading up to it if requested), + * and optionally chmods the directory immediately after (or each part of the + * path if requested). + * + * @param path The path to create, relative to base. + * @param base Root for relative path. These directories will never be made. + * @param mode The mode to use for created directories. + * @param flags Combination of the mkdir flags above. + * @param opts Extended options, or null. + * @return 0 on success, else error code + */ +extern int git_futils_mkdir_relative(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts); + +/** + * Create a directory or entire path. Similar to `git_futils_mkdir_relative` + * without performance data. + */ +extern int git_futils_mkdir(const char *path, mode_t mode, uint32_t flags); + +/** + * Create all the folders required to contain + * the full path of a file + */ +extern int git_futils_mkpath2file(const char *path, const mode_t mode); + +/** + * Flags to pass to `git_futils_rmdir_r`. + * + * * GIT_RMDIR_EMPTY_HIERARCHY - the default; remove hierarchy of empty + * dirs and generate error if any files are found. + * * GIT_RMDIR_REMOVE_FILES - attempt to remove files in the hierarchy. + * * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error. + * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base + * if removing this item leaves them empty + * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR + * * GIT_RMDIR_SKIP_ROOT - don't remove root directory itself + */ +typedef enum { + GIT_RMDIR_EMPTY_HIERARCHY = 0, + GIT_RMDIR_REMOVE_FILES = (1 << 0), + GIT_RMDIR_SKIP_NONEMPTY = (1 << 1), + GIT_RMDIR_EMPTY_PARENTS = (1 << 2), + GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3), + GIT_RMDIR_SKIP_ROOT = (1 << 4) +} git_futils_rmdir_flags; + +/** + * Remove path and any files and directories beneath it. + * + * @param path Path to the top level directory to process. + * @param base Root for relative path. + * @param flags Combination of git_futils_rmdir_flags values + * @return 0 on success; -1 on error. + */ +extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags); + +/** + * Create and open a temporary file with a `_git2_` suffix in a + * protected directory; the file created will created will honor + * the current `umask`. Writes the filename into path_out. + * + * This function uses a high-quality PRNG seeded by the system's + * entropy pool _where available_ and falls back to a simple seed + * (time plus system information) when not. This is suitable for + * writing within a protected directory, but the system's safe + * temporary file creation functions should be preferred where + * available when writing into world-writable (temp) directories. + * + * @return On success, an open file descriptor, else an error code < 0. + */ +extern int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode); + +/** + * Move a file on the filesystem, create the + * destination path if it doesn't exist + */ +extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode); + +/** + * Copy a file + * + * The filemode will be used for the newly created file. + */ +extern int git_futils_cp( + const char *from, + const char *to, + mode_t filemode); + +/** + * Set the files atime and mtime to the given time, or the current time + * if `ts` is NULL. + */ +extern int git_futils_touch(const char *path, time_t *when); + +/** + * Flags that can be passed to `git_futils_cp_r`. + * + * - GIT_CPDIR_CREATE_EMPTY_DIRS: create directories even if there are no + * files under them (otherwise directories will only be created lazily + * when a file inside them is copied). + * - GIT_CPDIR_COPY_SYMLINKS: copy symlinks, otherwise they are ignored. + * - GIT_CPDIR_COPY_DOTFILES: copy files with leading '.', otherwise ignored. + * - GIT_CPDIR_OVERWRITE: overwrite pre-existing files with source content, + * otherwise they are silently skipped. + * - GIT_CPDIR_CHMOD_DIRS: explicitly chmod directories to `dirmode` + * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the + * source file to the target; with this flag, always use 0666 (or 0777 if + * source has exec bits set) for target. + * - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files + */ +typedef enum { + GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0), + GIT_CPDIR_COPY_SYMLINKS = (1u << 1), + GIT_CPDIR_COPY_DOTFILES = (1u << 2), + GIT_CPDIR_OVERWRITE = (1u << 3), + GIT_CPDIR_CHMOD_DIRS = (1u << 4), + GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5), + GIT_CPDIR_LINK_FILES = (1u << 6) +} git_futils_cpdir_flags; + +/** + * Copy a directory tree. + * + * This copies directories and files from one root to another. You can + * pass a combination of GIT_CPDIR flags as defined above. + * + * If you pass the CHMOD flag, then the dirmode will be applied to all + * directories that are created during the copy, overriding the natural + * permissions. If you do not pass the CHMOD flag, then the dirmode + * will actually be copied from the source files and the `dirmode` arg + * will be ignored. + */ +extern int git_futils_cp_r( + const char *from, + const char *to, + uint32_t flags, + mode_t dirmode); + +/** + * Open a file readonly and set error if needed. + */ +extern int git_futils_open_ro(const char *path); + +/** + * Truncate a file, creating it if it doesn't exist. + */ +extern int git_futils_truncate(const char *path, int mode); + +/** + * Get the filesize in bytes of a file + */ +extern int git_futils_filesize(uint64_t *out, git_file fd); + +#define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0100) != 0) +#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644) +#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0777 : 0666) + +#define GIT_MODE_PERMS_MASK 0777 +#define GIT_MODE_TYPE_MASK 0170000 +#define GIT_MODE_TYPE(MODE) ((MODE) & GIT_MODE_TYPE_MASK) +#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB)) + +/** + * Convert a mode_t from the OS to a legal git mode_t value. + */ +extern mode_t git_futils_canonical_mode(mode_t raw_mode); + + +/** + * Read-only map all or part of a file into memory. + * When possible this function should favor a virtual memory + * style mapping over some form of malloc()+read(), as the + * data access will be random and is not likely to touch the + * majority of the region requested. + * + * @param out buffer to populate with the mapping information. + * @param fd open descriptor to configure the mapping from. + * @param begin first byte to map, this should be page aligned. + * @param len number of bytes to map. + * @return + * - 0 on success; + * - -1 on error. + */ +extern int git_futils_mmap_ro( + git_map *out, + git_file fd, + off64_t begin, + size_t len); + +/** + * Read-only map an entire file. + * + * @param out buffer to populate with the mapping information. + * @param path path to file to be opened. + * @return + * - 0 on success; + * - GIT_ENOTFOUND if not found; + * - -1 on an unspecified OS related error. + */ +extern int git_futils_mmap_ro_file( + git_map *out, + const char *path); + +/** + * Release the memory associated with a previous memory mapping. + * @param map the mapping description previously configured. + */ +extern void git_futils_mmap_free(git_map *map); + +/** + * Create a "fake" symlink (text file containing the target path). + * + * @param target original symlink target + * @param path symlink file to be created + * @return 0 on success, -1 on error + */ +extern int git_futils_fake_symlink(const char *target, const char *path); + +/** + * A file stamp represents a snapshot of information about a file that can + * be used to test if the file changes. This portable implementation is + * based on stat data about that file, but it is possible that OS specific + * versions could be implemented in the future. + */ +typedef struct { + struct timespec mtime; + uint64_t size; + unsigned int ino; +} git_futils_filestamp; + +/** + * Compare stat information for file with reference info. + * + * This function updates the file stamp to current data for the given path + * and returns 0 if the file is up-to-date relative to the prior setting, + * 1 if the file has been changed, or GIT_ENOTFOUND if the file doesn't + * exist. This will not call git_error_set, so you must set the error if you + * plan to return an error. + * + * @param stamp File stamp to be checked + * @param path Path to stat and check if changed + * @return 0 if up-to-date, 1 if out-of-date, GIT_ENOTFOUND if cannot stat + */ +extern int git_futils_filestamp_check( + git_futils_filestamp *stamp, const char *path); + +/** + * Set or reset file stamp data + * + * This writes the target file stamp. If the source is NULL, this will set + * the target stamp to values that will definitely be out of date. If the + * source is not NULL, this copies the source values to the target. + * + * @param tgt File stamp to write to + * @param src File stamp to copy from or NULL to clear the target + */ +extern void git_futils_filestamp_set( + git_futils_filestamp *tgt, const git_futils_filestamp *src); + +/** + * Set file stamp data from stat structure + */ +extern void git_futils_filestamp_set_from_stat( + git_futils_filestamp *stamp, struct stat *st); + +/** + * `fsync` the parent directory of the given path, if `fsync` is + * supported for directories on this platform. + * + * @param path Path of the directory to sync. + * @return 0 on success, -1 on error + */ +extern int git_futils_fsync_dir(const char *path); + +/** + * `fsync` the parent directory of the given path, if `fsync` is + * supported for directories on this platform. + * + * @param path Path of the file whose parent directory should be synced. + * @return 0 on success, -1 on error + */ +extern int git_futils_fsync_parent(const char *path); + +#endif diff --git a/src/util/git2_util.h b/src/util/git2_util.h new file mode 100644 index 000000000..ad3f1c71f --- /dev/null +++ b/src/util/git2_util.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git2_util_h__ +#define INCLUDE_git2_util_h__ + +#ifndef LIBGIT2_NO_FEATURES_H +# include "git2/sys/features.h" +#endif + +#include "git2/common.h" +#include "cc-compat.h" + +typedef struct git_str git_str; + +/** Declare a function as always inlined. */ +#if defined(_MSC_VER) +# define GIT_INLINE(type) static __inline type +#elif defined(__GNUC__) +# define GIT_INLINE(type) static __inline__ type +#else +# define GIT_INLINE(type) static type +#endif + +/** Support for gcc/clang __has_builtin intrinsic */ +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + +/** + * Declare that a function's return value must be used. + * + * Used mostly to guard against potential silent bugs at runtime. This is + * recommended to be added to functions that: + * + * - Allocate / reallocate memory. This prevents memory leaks or errors where + * buffers are expected to have grown to a certain size, but could not be + * resized. + * - Acquire locks. When a lock cannot be acquired, that will almost certainly + * cause a data race / undefined behavior. + */ +#if defined(__GNUC__) +# define GIT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +# define GIT_WARN_UNUSED_RESULT +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef GIT_WIN32 + +# include +# include +# include +# include +# include +# include "win32/msvc-compat.h" +# include "win32/mingw-compat.h" +# include "win32/win32-compat.h" +# include "win32/w32_common.h" +# include "win32/version.h" +# include "win32/error.h" +# ifdef GIT_THREADS +# include "win32/thread.h" +# endif + +#else + +# include +# include +# ifdef GIT_THREADS +# include +# include +# endif + +#define GIT_LIBGIT2_CALL +#define GIT_SYSTEM_CALL + +#ifdef GIT_USE_STAT_ATIMESPEC +# define st_atim st_atimespec +# define st_ctim st_ctimespec +# define st_mtim st_mtimespec +#endif + +# include + +#endif + +#include "git2/types.h" +#include "git2/errors.h" +#include "thread.h" +#include "integer.h" +#include "assert_safe.h" + +#include "posix.h" + +#define GIT_BUFSIZE_DEFAULT 65536 +#define GIT_BUFSIZE_FILEIO GIT_BUFSIZE_DEFAULT +#define GIT_BUFSIZE_FILTERIO GIT_BUFSIZE_DEFAULT +#define GIT_BUFSIZE_NETIO GIT_BUFSIZE_DEFAULT + + +/** + * Check a pointer allocation result, returning -1 if it failed. + */ +#define GIT_ERROR_CHECK_ALLOC(ptr) do { \ + if ((ptr) == NULL) { return -1; } \ + } while(0) + +/** + * Check a buffer allocation result, returning -1 if it failed. + */ +#define GIT_ERROR_CHECK_ALLOC_STR(buf) do { \ + if ((void *)(buf) == NULL || git_str_oom(buf)) { return -1; } \ + } while(0) + +/** + * Check a return value and propagate result if non-zero. + */ +#define GIT_ERROR_CHECK_ERROR(code) \ + do { int _err = (code); if (_err) return _err; } while (0) + + +/** Check for additive overflow, setting an error if would occur. */ +#define GIT_ADD_SIZET_OVERFLOW(out, one, two) \ + (git__add_sizet_overflow(out, one, two) ? (git_error_set_oom(), 1) : 0) + +/** Check for additive overflow, setting an error if would occur. */ +#define GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize) \ + (git__multiply_sizet_overflow(out, nelem, elsize) ? (git_error_set_oom(), 1) : 0) + +/** Check for additive overflow, failing if it would occur. */ +#define GIT_ERROR_CHECK_ALLOC_ADD(out, one, two) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two)) { return -1; } + +#define GIT_ERROR_CHECK_ALLOC_ADD3(out, one, two, three) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three)) { return -1; } + +#define GIT_ERROR_CHECK_ALLOC_ADD4(out, one, two, three, four) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), four)) { return -1; } + +#define GIT_ERROR_CHECK_ALLOC_ADD5(out, one, two, three, four, five) \ + if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), four) || \ + GIT_ADD_SIZET_OVERFLOW(out, *(out), five)) { return -1; } + +/** Check for multiplicative overflow, failing if it would occur. */ +#define GIT_ERROR_CHECK_ALLOC_MULTIPLY(out, nelem, elsize) \ + if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; } + +#include "util.h" + +#endif diff --git a/src/util/hash.c b/src/util/hash.c new file mode 100644 index 000000000..98ceb05d2 --- /dev/null +++ b/src/util/hash.c @@ -0,0 +1,142 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "hash.h" + +int git_hash_global_init(void) +{ + return git_hash_sha1_global_init(); +} + +int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm) +{ + int error; + + switch (algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + error = git_hash_sha1_ctx_init(&ctx->ctx.sha1); + break; + default: + git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); + error = -1; + } + + ctx->algorithm = algorithm; + return error; +} + +void git_hash_ctx_cleanup(git_hash_ctx *ctx) +{ + switch (ctx->algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + git_hash_sha1_ctx_cleanup(&ctx->ctx.sha1); + return; + default: + /* unreachable */ ; + } +} + +int git_hash_init(git_hash_ctx *ctx) +{ + switch (ctx->algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + return git_hash_sha1_init(&ctx->ctx.sha1); + default: + /* unreachable */ ; + } + + git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); + return -1; +} + +int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +{ + switch (ctx->algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + return git_hash_sha1_update(&ctx->ctx.sha1, data, len); + default: + /* unreachable */ ; + } + + git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); + return -1; +} + +int git_hash_final(unsigned char *out, git_hash_ctx *ctx) +{ + switch (ctx->algorithm) { + case GIT_HASH_ALGORITHM_SHA1: + return git_hash_sha1_final(out, &ctx->ctx.sha1); + default: + /* unreachable */ ; + } + + git_error_set(GIT_ERROR_INTERNAL, "unknown hash algorithm"); + return -1; +} + +int git_hash_buf( + unsigned char *out, + const void *data, + size_t len, + git_hash_algorithm_t algorithm) +{ + git_hash_ctx ctx; + int error = 0; + + if (git_hash_ctx_init(&ctx, algorithm) < 0) + return -1; + + if ((error = git_hash_update(&ctx, data, len)) >= 0) + error = git_hash_final(out, &ctx); + + git_hash_ctx_cleanup(&ctx); + + return error; +} + +int git_hash_vec( + unsigned char *out, + git_str_vec *vec, + size_t n, + git_hash_algorithm_t algorithm) +{ + git_hash_ctx ctx; + size_t i; + int error = 0; + + if (git_hash_ctx_init(&ctx, algorithm) < 0) + return -1; + + for (i = 0; i < n; i++) { + if ((error = git_hash_update(&ctx, vec[i].data, vec[i].len)) < 0) + goto done; + } + + error = git_hash_final(out, &ctx); + +done: + git_hash_ctx_cleanup(&ctx); + + return error; +} + +int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len) +{ + static char hex[] = "0123456789abcdef"; + char *str = out; + size_t i; + + for (i = 0; i < hash_len; i++) { + *str++ = hex[hash[i] >> 4]; + *str++ = hex[hash[i] & 0x0f]; + } + + *str++ = '\0'; + + return 0; +} diff --git a/src/util/hash.h b/src/util/hash.h new file mode 100644 index 000000000..5f1386563 --- /dev/null +++ b/src/util/hash.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_h__ +#define INCLUDE_hash_h__ + +#include "git2_util.h" + +#include "hash/sha1.h" + +typedef struct { + void *data; + size_t len; +} git_str_vec; + +typedef enum { + GIT_HASH_ALGORITHM_NONE = 0, + GIT_HASH_ALGORITHM_SHA1 +} git_hash_algorithm_t; + +typedef struct git_hash_ctx { + union { + git_hash_sha1_ctx sha1; + } ctx; + git_hash_algorithm_t algorithm; +} git_hash_ctx; + +int git_hash_global_init(void); + +int git_hash_ctx_init(git_hash_ctx *ctx, git_hash_algorithm_t algorithm); +void git_hash_ctx_cleanup(git_hash_ctx *ctx); + +int git_hash_init(git_hash_ctx *c); +int git_hash_update(git_hash_ctx *c, const void *data, size_t len); +int git_hash_final(unsigned char *out, git_hash_ctx *c); + +int git_hash_buf(unsigned char *out, const void *data, size_t len, git_hash_algorithm_t algorithm); +int git_hash_vec(unsigned char *out, git_str_vec *vec, size_t n, git_hash_algorithm_t algorithm); + +int git_hash_fmt(char *out, unsigned char *hash, size_t hash_len); + +#endif diff --git a/src/util/hash/sha1.h b/src/util/hash/sha1.h new file mode 100644 index 000000000..9d32bce42 --- /dev/null +++ b/src/util/hash/sha1.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_h__ +#define INCLUDE_hash_sha1_h__ + +#include "git2_util.h" + +typedef struct git_hash_sha1_ctx git_hash_sha1_ctx; + +#if defined(GIT_SHA1_COLLISIONDETECT) +# include "sha1/collisiondetect.h" +#elif defined(GIT_SHA1_COMMON_CRYPTO) +# include "sha1/common_crypto.h" +#elif defined(GIT_SHA1_OPENSSL) +# include "sha1/openssl.h" +#elif defined(GIT_SHA1_WIN32) +# include "sha1/win32.h" +#elif defined(GIT_SHA1_MBEDTLS) +# include "sha1/mbedtls.h" +#else +# include "sha1/generic.h" +#endif + +#define GIT_HASH_SHA1_SIZE 20 + +int git_hash_sha1_global_init(void); + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx); +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx); + +int git_hash_sha1_init(git_hash_sha1_ctx *c); +int git_hash_sha1_update(git_hash_sha1_ctx *c, const void *data, size_t len); +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *c); + +#endif diff --git a/src/util/hash/sha1/collisiondetect.c b/src/util/hash/sha1/collisiondetect.c new file mode 100644 index 000000000..ec7059c4c --- /dev/null +++ b/src/util/hash/sha1/collisiondetect.c @@ -0,0 +1,48 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "collisiondetect.h" + +int git_hash_sha1_global_init(void) +{ + return 0; +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + SHA1DCInit(&ctx->c); + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + SHA1DCUpdate(&ctx->c, data, len); + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + if (SHA1DCFinal(out, &ctx->c)) { + git_error_set(GIT_ERROR_SHA1, "SHA1 collision attack detected"); + return -1; + } + + return 0; +} diff --git a/src/util/hash/sha1/collisiondetect.h b/src/util/hash/sha1/collisiondetect.h new file mode 100644 index 000000000..eb88e86c1 --- /dev/null +++ b/src/util/hash/sha1/collisiondetect.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_collisiondetect_h__ +#define INCLUDE_hash_sha1_collisiondetect_h__ + +#include "hash/sha1.h" + +#include "sha1dc/sha1.h" + +struct git_hash_sha1_ctx { + SHA1_CTX c; +}; + +#endif diff --git a/src/util/hash/sha1/common_crypto.c b/src/util/hash/sha1/common_crypto.c new file mode 100644 index 000000000..9d608f449 --- /dev/null +++ b/src/util/hash/sha1/common_crypto.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common_crypto.h" + +#define CC_LONG_MAX ((CC_LONG)-1) + +int git_hash_sha1_global_init(void) +{ + return 0; +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + CC_SHA1_Init(&ctx->c); + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) +{ + const unsigned char *data = _data; + + GIT_ASSERT_ARG(ctx); + + while (len > 0) { + CC_LONG chunk = (len > CC_LONG_MAX) ? CC_LONG_MAX : (CC_LONG)len; + + CC_SHA1_Update(&ctx->c, data, chunk); + + data += chunk; + len -= chunk; + } + + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + CC_SHA1_Final(out, &ctx->c); + return 0; +} diff --git a/src/util/hash/sha1/common_crypto.h b/src/util/hash/sha1/common_crypto.h new file mode 100644 index 000000000..a5fcfb33e --- /dev/null +++ b/src/util/hash/sha1/common_crypto.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_common_crypto_h__ +#define INCLUDE_hash_sha1_common_crypto_h__ + +#include "hash/sha1.h" + +#include + +struct git_hash_sha1_ctx { + CC_SHA1_CTX c; +}; + +#endif diff --git a/src/util/hash/sha1/generic.c b/src/util/hash/sha1/generic.c new file mode 100644 index 000000000..85b34c578 --- /dev/null +++ b/src/util/hash/sha1/generic.c @@ -0,0 +1,300 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "generic.h" + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + +/* + * Force usage of rol or ror by selecting the one with the smaller constant. + * It _can_ generate slightly smaller code (a constant of 1 is special), but + * perhaps more importantly it's possibly faster on any uarch that does a + * rotate with a loop. + */ + +#define SHA_ASM(op, x, n) (__extension__ ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; })) +#define SHA_ROL(x,n) SHA_ASM("rol", x, n) +#define SHA_ROR(x,n) SHA_ASM("ror", x, n) + +#else + +#define SHA_ROT(X,l,r) (((X) << (l)) | ((X) >> (r))) +#define SHA_ROL(X,n) SHA_ROT(X,n,32-(n)) +#define SHA_ROR(X,n) SHA_ROT(X,32-(n),n) + +#endif + +/* + * If you have 32 registers or more, the compiler can (and should) + * try to change the array[] accesses into registers. However, on + * machines with less than ~25 registers, that won't really work, + * and at least gcc will make an unholy mess of it. + * + * So to avoid that mess which just slows things down, we force + * the stores to memory to actually happen (we might be better off + * with a 'W(t)=(val);asm("":"+m" (W(t))' there instead, as + * suggested by Artur Skawina - that will also make gcc unable to + * try to do the silly "optimize away loads" part because it won't + * see what the value will be). + * + * Ben Herrenschmidt reports that on PPC, the C version comes close + * to the optimized asm with this (ie on PPC you don't want that + * 'volatile', since there are lots of registers). + * + * On ARM we get the best code generation by forcing a full memory barrier + * between each SHA_ROUND, otherwise gcc happily get wild with spilling and + * the stack frame size simply explode and performance goes down the drain. + */ + +#if defined(__i386__) || defined(__x86_64__) + #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val)) +#elif defined(__GNUC__) && defined(__arm__) + #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0) +#else + #define setW(x, val) (W(x) = (val)) +#endif + +/* + * Performance might be improved if the CPU architecture is OK with + * unaligned 32-bit loads and a fast ntohl() is available. + * Otherwise fall back to byte loads and shifts which is portable, + * and is faster on architectures with memory alignment issues. + */ + +#if defined(__i386__) || defined(__x86_64__) || \ + defined(_M_IX86) || defined(_M_X64) || \ + defined(__ppc__) || defined(__ppc64__) || \ + defined(__powerpc__) || defined(__powerpc64__) || \ + defined(__s390__) || defined(__s390x__) + +#define get_be32(p) ntohl(*(const unsigned int *)(p)) +#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0) + +#else + +#define get_be32(p) ( \ + (*((const unsigned char *)(p) + 0) << 24) | \ + (*((const unsigned char *)(p) + 1) << 16) | \ + (*((const unsigned char *)(p) + 2) << 8) | \ + (*((const unsigned char *)(p) + 3) << 0) ) +#define put_be32(p, v) do { \ + unsigned int __v = (v); \ + *((unsigned char *)(p) + 0) = __v >> 24; \ + *((unsigned char *)(p) + 1) = __v >> 16; \ + *((unsigned char *)(p) + 2) = __v >> 8; \ + *((unsigned char *)(p) + 3) = __v >> 0; } while (0) + +#endif + +/* This "rolls" over the 512-bit array */ +#define W(x) (array[(x)&15]) + +/* + * Where do we get the source from? The first 16 iterations get it from + * the input data, the next mix it from the 512-bit array. + */ +#define SHA_SRC(t) get_be32(data + t) +#define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1) + +#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \ + unsigned int TEMP = input(t); setW(t, TEMP); \ + E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \ + B = SHA_ROR(B, 2); } while (0) + +#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) +#define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) +#define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E ) +#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E ) +#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E ) + +static void hash__block(git_hash_sha1_ctx *ctx, const unsigned int *data) +{ + unsigned int A,B,C,D,E; + unsigned int array[16]; + + A = ctx->H[0]; + B = ctx->H[1]; + C = ctx->H[2]; + D = ctx->H[3]; + E = ctx->H[4]; + + /* Round 1 - iterations 0-16 take their input from 'data' */ + T_0_15( 0, A, B, C, D, E); + T_0_15( 1, E, A, B, C, D); + T_0_15( 2, D, E, A, B, C); + T_0_15( 3, C, D, E, A, B); + T_0_15( 4, B, C, D, E, A); + T_0_15( 5, A, B, C, D, E); + T_0_15( 6, E, A, B, C, D); + T_0_15( 7, D, E, A, B, C); + T_0_15( 8, C, D, E, A, B); + T_0_15( 9, B, C, D, E, A); + T_0_15(10, A, B, C, D, E); + T_0_15(11, E, A, B, C, D); + T_0_15(12, D, E, A, B, C); + T_0_15(13, C, D, E, A, B); + T_0_15(14, B, C, D, E, A); + T_0_15(15, A, B, C, D, E); + + /* Round 1 - tail. Input from 512-bit mixing array */ + T_16_19(16, E, A, B, C, D); + T_16_19(17, D, E, A, B, C); + T_16_19(18, C, D, E, A, B); + T_16_19(19, B, C, D, E, A); + + /* Round 2 */ + T_20_39(20, A, B, C, D, E); + T_20_39(21, E, A, B, C, D); + T_20_39(22, D, E, A, B, C); + T_20_39(23, C, D, E, A, B); + T_20_39(24, B, C, D, E, A); + T_20_39(25, A, B, C, D, E); + T_20_39(26, E, A, B, C, D); + T_20_39(27, D, E, A, B, C); + T_20_39(28, C, D, E, A, B); + T_20_39(29, B, C, D, E, A); + T_20_39(30, A, B, C, D, E); + T_20_39(31, E, A, B, C, D); + T_20_39(32, D, E, A, B, C); + T_20_39(33, C, D, E, A, B); + T_20_39(34, B, C, D, E, A); + T_20_39(35, A, B, C, D, E); + T_20_39(36, E, A, B, C, D); + T_20_39(37, D, E, A, B, C); + T_20_39(38, C, D, E, A, B); + T_20_39(39, B, C, D, E, A); + + /* Round 3 */ + T_40_59(40, A, B, C, D, E); + T_40_59(41, E, A, B, C, D); + T_40_59(42, D, E, A, B, C); + T_40_59(43, C, D, E, A, B); + T_40_59(44, B, C, D, E, A); + T_40_59(45, A, B, C, D, E); + T_40_59(46, E, A, B, C, D); + T_40_59(47, D, E, A, B, C); + T_40_59(48, C, D, E, A, B); + T_40_59(49, B, C, D, E, A); + T_40_59(50, A, B, C, D, E); + T_40_59(51, E, A, B, C, D); + T_40_59(52, D, E, A, B, C); + T_40_59(53, C, D, E, A, B); + T_40_59(54, B, C, D, E, A); + T_40_59(55, A, B, C, D, E); + T_40_59(56, E, A, B, C, D); + T_40_59(57, D, E, A, B, C); + T_40_59(58, C, D, E, A, B); + T_40_59(59, B, C, D, E, A); + + /* Round 4 */ + T_60_79(60, A, B, C, D, E); + T_60_79(61, E, A, B, C, D); + T_60_79(62, D, E, A, B, C); + T_60_79(63, C, D, E, A, B); + T_60_79(64, B, C, D, E, A); + T_60_79(65, A, B, C, D, E); + T_60_79(66, E, A, B, C, D); + T_60_79(67, D, E, A, B, C); + T_60_79(68, C, D, E, A, B); + T_60_79(69, B, C, D, E, A); + T_60_79(70, A, B, C, D, E); + T_60_79(71, E, A, B, C, D); + T_60_79(72, D, E, A, B, C); + T_60_79(73, C, D, E, A, B); + T_60_79(74, B, C, D, E, A); + T_60_79(75, A, B, C, D, E); + T_60_79(76, E, A, B, C, D); + T_60_79(77, D, E, A, B, C); + T_60_79(78, C, D, E, A, B); + T_60_79(79, B, C, D, E, A); + + ctx->H[0] += A; + ctx->H[1] += B; + ctx->H[2] += C; + ctx->H[3] += D; + ctx->H[4] += E; +} + +int git_hash_sha1_global_init(void) +{ + return 0; +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + ctx->size = 0; + + /* Initialize H with the magic constants (see FIPS180 for constants) */ + ctx->H[0] = 0x67452301; + ctx->H[1] = 0xefcdab89; + ctx->H[2] = 0x98badcfe; + ctx->H[3] = 0x10325476; + ctx->H[4] = 0xc3d2e1f0; + + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + unsigned int lenW = ctx->size & 63; + + ctx->size += len; + + /* Read the data into W and process blocks as they get full */ + if (lenW) { + unsigned int left = 64 - lenW; + if (len < left) + left = (unsigned int)len; + memcpy(lenW + (char *)ctx->W, data, left); + lenW = (lenW + left) & 63; + len -= left; + data = ((const char *)data + left); + if (lenW) + return 0; + hash__block(ctx, ctx->W); + } + while (len >= 64) { + hash__block(ctx, data); + data = ((const char *)data + 64); + len -= 64; + } + if (len) + memcpy(ctx->W, data, len); + + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + static const unsigned char pad[64] = { 0x80 }; + unsigned int padlen[2]; + int i; + + /* Pad with a binary 1 (ie 0x80), then zeroes, then length */ + padlen[0] = htonl((uint32_t)(ctx->size >> 29)); + padlen[1] = htonl((uint32_t)(ctx->size << 3)); + + i = ctx->size & 63; + git_hash_sha1_update(ctx, pad, 1+ (63 & (55 - i))); + git_hash_sha1_update(ctx, padlen, 8); + + /* Output hash */ + for (i = 0; i < 5; i++) + put_be32(out + i*4, ctx->H[i]); + + return 0; +} diff --git a/src/util/hash/sha1/generic.h b/src/util/hash/sha1/generic.h new file mode 100644 index 000000000..53fc0823e --- /dev/null +++ b/src/util/hash/sha1/generic.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_generic_h__ +#define INCLUDE_hash_sha1_generic_h__ + +#include "hash/sha1.h" + +struct git_hash_sha1_ctx { + uint64_t size; + unsigned int H[5]; + unsigned int W[16]; +}; + +#endif diff --git a/src/util/hash/sha1/mbedtls.c b/src/util/hash/sha1/mbedtls.c new file mode 100644 index 000000000..56016bec8 --- /dev/null +++ b/src/util/hash/sha1/mbedtls.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "mbedtls.h" + +int git_hash_sha1_global_init(void) +{ + return 0; +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + if (ctx) + mbedtls_sha1_free(&ctx->c); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + mbedtls_sha1_init(&ctx->c); + mbedtls_sha1_starts(&ctx->c); + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + mbedtls_sha1_update(&ctx->c, data, len); + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + mbedtls_sha1_finish(&ctx->c, out); + return 0; +} diff --git a/src/util/hash/sha1/mbedtls.h b/src/util/hash/sha1/mbedtls.h new file mode 100644 index 000000000..15f7462a4 --- /dev/null +++ b/src/util/hash/sha1/mbedtls.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_mbedtls_h__ +#define INCLUDE_hash_sha1_mbedtls_h__ + +#include "hash/sha1.h" + +#include + +struct git_hash_sha1_ctx { + mbedtls_sha1_context c; +}; + +#endif /* INCLUDE_hash_sha1_mbedtls_h__ */ diff --git a/src/util/hash/sha1/openssl.c b/src/util/hash/sha1/openssl.c new file mode 100644 index 000000000..64bf99b3c --- /dev/null +++ b/src/util/hash/sha1/openssl.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "openssl.h" + +int git_hash_sha1_global_init(void) +{ + return 0; +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + + if (SHA1_Init(&ctx->c) != 1) { + git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to initialize hash context"); + return -1; + } + + return 0; +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + + if (SHA1_Update(&ctx->c, data, len) != 1) { + git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to update hash"); + return -1; + } + + return 0; +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + + if (SHA1_Final(out, &ctx->c) != 1) { + git_error_set(GIT_ERROR_SHA1, "hash_openssl: failed to finalize hash"); + return -1; + } + + return 0; +} diff --git a/src/util/hash/sha1/openssl.h b/src/util/hash/sha1/openssl.h new file mode 100644 index 000000000..a223ca03e --- /dev/null +++ b/src/util/hash/sha1/openssl.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_openssl_h__ +#define INCLUDE_hash_sha1_openssl_h__ + +#include "hash/sha1.h" + +#include + +struct git_hash_sha1_ctx { + SHA_CTX c; +}; + +#endif diff --git a/src/util/hash/sha1/sha1dc/sha1.c b/src/util/hash/sha1/sha1dc/sha1.c new file mode 100644 index 000000000..929822728 --- /dev/null +++ b/src/util/hash/sha1/sha1dc/sha1.c @@ -0,0 +1,1909 @@ +/*** +* Copyright 2017 Marc Stevens , Dan Shumow (danshu@microsoft.com) +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include +#include +#include +#include +#include /* make sure macros like _BIG_ENDIAN visible */ +#endif + +#ifdef SHA1DC_CUSTOM_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_INCLUDE_SHA1_C +#endif + +#ifndef SHA1DC_INIT_SAFE_HASH_DEFAULT +#define SHA1DC_INIT_SAFE_HASH_DEFAULT 1 +#endif + +#include "sha1.h" +#include "ubc_check.h" + +#if (defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \ + defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || \ + defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || defined(__INTEL__) || \ + defined(__386) || defined(_M_X64) || defined(_M_AMD64)) +#define SHA1DC_ON_INTEL_LIKE_PROCESSOR +#endif + +/* + Because Little-Endian architectures are most common, + we only set SHA1DC_BIGENDIAN if one of these conditions is met. + Note that all MSFT platforms are little endian, + so none of these will be defined under the MSC compiler. + If you are compiling on a big endian platform and your compiler does not define one of these, + you will have to add whatever macros your tool chain defines to indicate Big-Endianness. + */ + +#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) +/* + * Should detect Big Endian under GCC since at least 4.6.0 (gcc svn + * rev #165881). See + * https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html + * + * This also works under clang since 3.2, it copied the GCC-ism. See + * clang.git's 3b198a97d2 ("Preprocessor: add __BYTE_ORDER__ + * predefined macro", 2012-07-27) + */ +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike */ +#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) +/* + * Should detect Big Endian under glibc.git since 14245eb70e ("entered + * into RCS", 1992-11-25). Defined in which will have been + * brought in by standard headers. See glibc.git and + * https://sourceforge.net/p/predef/wiki/Endianness/ + */ +#if __BYTE_ORDER == __BIG_ENDIAN +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike or glibc */ +#elif defined(_BYTE_ORDER) && defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) +/* + * *BSD and newlib (embedded linux, cygwin, etc). + * the defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) part prevents + * this condition from matching with Solaris/sparc. + * (Solaris defines only one endian macro) + */ +#if _BYTE_ORDER == _BIG_ENDIAN +#define SHA1DC_BIGENDIAN +#endif + +/* Not under GCC-alike or glibc or *BSD or newlib */ +#elif (defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ + defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || \ + defined(__sparc)) +/* + * Should define Big Endian for a whitelist of known processors. See + * https://sourceforge.net/p/predef/wiki/Endianness/ and + * http://www.oracle.com/technetwork/server-storage/solaris/portingtosolaris-138514.html + */ +#define SHA1DC_BIGENDIAN + +/* Not under GCC-alike or glibc or *BSD or newlib or */ +#elif (defined(_AIX) || defined(__hpux)) + +/* + * Defines Big Endian on a whitelist of OSs that are known to be Big + * Endian-only. See + * https://public-inbox.org/git/93056823-2740-d072-1ebd-46b440b33d7e@felt.demon.nl/ + */ +#define SHA1DC_BIGENDIAN + +/* Not under GCC-alike or glibc or *BSD or newlib or or */ +#elif defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) +/* + * As a last resort before we do anything else we're not 100% sure + * about below, we blacklist specific processors here. We could add + * more, see e.g. https://wiki.debian.org/ArchitectureSpecificsMemo + */ +#else /* Not under GCC-alike or glibc or *BSD or newlib or or or */ + +/* We do nothing more here for now */ +/*#error "Uncomment this to see if you fall through all the detection"*/ + +#endif /* Big Endian detection */ + +#if (defined(SHA1DC_FORCE_LITTLEENDIAN) && defined(SHA1DC_BIGENDIAN)) +#undef SHA1DC_BIGENDIAN +#endif +#if (defined(SHA1DC_FORCE_BIGENDIAN) && !defined(SHA1DC_BIGENDIAN)) +#define SHA1DC_BIGENDIAN +#endif +/*ENDIANNESS SELECTION*/ + +#ifndef SHA1DC_FORCE_ALIGNED_ACCESS +#if defined(SHA1DC_FORCE_UNALIGNED_ACCESS) || defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) +#define SHA1DC_ALLOW_UNALIGNED_ACCESS +#endif /*UNALIGNED ACCESS DETECTION*/ +#endif /*FORCE ALIGNED ACCESS*/ + +#define rotate_right(x,n) (((x)>>(n))|((x)<<(32-(n)))) +#define rotate_left(x,n) (((x)<<(n))|((x)>>(32-(n)))) + +#define sha1_bswap32(x) \ + {x = ((x << 8) & 0xFF00FF00) | ((x >> 8) & 0xFF00FF); x = (x << 16) | (x >> 16);} + +#define sha1_mix(W, t) (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1)) + +#ifdef SHA1DC_BIGENDIAN + #define sha1_load(m, t, temp) { temp = m[t]; } +#else + #define sha1_load(m, t, temp) { temp = m[t]; sha1_bswap32(temp); } +#endif + +#define sha1_store(W, t, x) *(volatile uint32_t *)&W[t] = x + +#define sha1_f1(b,c,d) ((d)^((b)&((c)^(d)))) +#define sha1_f2(b,c,d) ((b)^(c)^(d)) +#define sha1_f3(b,c,d) (((b)&(c))+((d)&((b)^(c)))) +#define sha1_f4(b,c,d) ((b)^(c)^(d)) + +#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; b = rotate_left(b, 30); } +#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; b = rotate_left(b, 30); } +#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; b = rotate_left(b, 30); } +#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, m, t) \ + { e += rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; b = rotate_left(b, 30); } + +#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999 + m[t]; } +#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1 + m[t]; } +#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC + m[t]; } +#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, m, t) \ + { b = rotate_right(b, 30); e -= rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6 + m[t]; } + +#define SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, t, temp) \ + {sha1_load(m, t, temp); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30);} + +#define SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f1(b,c,d) + 0x5A827999; b = rotate_left(b, 30); } + +#define SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f2(b,c,d) + 0x6ED9EBA1; b = rotate_left(b, 30); } + +#define SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f3(b,c,d) + 0x8F1BBCDC; b = rotate_left(b, 30); } + +#define SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, t, temp) \ + {temp = sha1_mix(W, t); sha1_store(W, t, temp); e += temp + rotate_left(a, 5) + sha1_f4(b,c,d) + 0xCA62C1D6; b = rotate_left(b, 30); } + + +#define SHA1_STORE_STATE(i) states[i][0] = a; states[i][1] = b; states[i][2] = c; states[i][3] = d; states[i][4] = e; + +#ifdef BUILDNOCOLLDETECTSHA1COMPRESSION +void sha1_compression(uint32_t ihv[5], const uint32_t m[16]) +{ + uint32_t W[80]; + uint32_t a,b,c,d,e; + unsigned i; + + memcpy(W, m, 16 * 4); + for (i = 16; i < 80; ++i) + W[i] = sha1_mix(W, i); + + a = ihv[0]; b = ihv[1]; c = ihv[2]; d = ihv[3]; e = ihv[4]; + + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); + + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); + + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); + + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); + + ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; +} +#endif /*BUILDNOCOLLDETECTSHA1COMPRESSION*/ + + +static void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]) +{ + uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; + + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18); + HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19); + + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38); + HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39); + + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58); + HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59); + + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78); + HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79); + + ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; +} + + + +void sha1_compression_states(uint32_t ihv[5], const uint32_t m[16], uint32_t W[80], uint32_t states[80][5]) +{ + uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4]; + uint32_t temp; + +#ifdef DOSTORESTATE00 + SHA1_STORE_STATE(0) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 0, temp); + +#ifdef DOSTORESTATE01 + SHA1_STORE_STATE(1) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 1, temp); + +#ifdef DOSTORESTATE02 + SHA1_STORE_STATE(2) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 2, temp); + +#ifdef DOSTORESTATE03 + SHA1_STORE_STATE(3) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 3, temp); + +#ifdef DOSTORESTATE04 + SHA1_STORE_STATE(4) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 4, temp); + +#ifdef DOSTORESTATE05 + SHA1_STORE_STATE(5) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 5, temp); + +#ifdef DOSTORESTATE06 + SHA1_STORE_STATE(6) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 6, temp); + +#ifdef DOSTORESTATE07 + SHA1_STORE_STATE(7) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 7, temp); + +#ifdef DOSTORESTATE08 + SHA1_STORE_STATE(8) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 8, temp); + +#ifdef DOSTORESTATE09 + SHA1_STORE_STATE(9) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 9, temp); + +#ifdef DOSTORESTATE10 + SHA1_STORE_STATE(10) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 10, temp); + +#ifdef DOSTORESTATE11 + SHA1_STORE_STATE(11) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 11, temp); + +#ifdef DOSTORESTATE12 + SHA1_STORE_STATE(12) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 12, temp); + +#ifdef DOSTORESTATE13 + SHA1_STORE_STATE(13) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 13, temp); + +#ifdef DOSTORESTATE14 + SHA1_STORE_STATE(14) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 14, temp); + +#ifdef DOSTORESTATE15 + SHA1_STORE_STATE(15) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 15, temp); + +#ifdef DOSTORESTATE16 + SHA1_STORE_STATE(16) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(e, a, b, c, d, W, 16, temp); + +#ifdef DOSTORESTATE17 + SHA1_STORE_STATE(17) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(d, e, a, b, c, W, 17, temp); + +#ifdef DOSTORESTATE18 + SHA1_STORE_STATE(18) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(c, d, e, a, b, W, 18, temp); + +#ifdef DOSTORESTATE19 + SHA1_STORE_STATE(19) +#endif + SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(b, c, d, e, a, W, 19, temp); + + + +#ifdef DOSTORESTATE20 + SHA1_STORE_STATE(20) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 20, temp); + +#ifdef DOSTORESTATE21 + SHA1_STORE_STATE(21) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 21, temp); + +#ifdef DOSTORESTATE22 + SHA1_STORE_STATE(22) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 22, temp); + +#ifdef DOSTORESTATE23 + SHA1_STORE_STATE(23) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 23, temp); + +#ifdef DOSTORESTATE24 + SHA1_STORE_STATE(24) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 24, temp); + +#ifdef DOSTORESTATE25 + SHA1_STORE_STATE(25) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 25, temp); + +#ifdef DOSTORESTATE26 + SHA1_STORE_STATE(26) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 26, temp); + +#ifdef DOSTORESTATE27 + SHA1_STORE_STATE(27) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 27, temp); + +#ifdef DOSTORESTATE28 + SHA1_STORE_STATE(28) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 28, temp); + +#ifdef DOSTORESTATE29 + SHA1_STORE_STATE(29) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 29, temp); + +#ifdef DOSTORESTATE30 + SHA1_STORE_STATE(30) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 30, temp); + +#ifdef DOSTORESTATE31 + SHA1_STORE_STATE(31) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 31, temp); + +#ifdef DOSTORESTATE32 + SHA1_STORE_STATE(32) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 32, temp); + +#ifdef DOSTORESTATE33 + SHA1_STORE_STATE(33) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 33, temp); + +#ifdef DOSTORESTATE34 + SHA1_STORE_STATE(34) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 34, temp); + +#ifdef DOSTORESTATE35 + SHA1_STORE_STATE(35) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 35, temp); + +#ifdef DOSTORESTATE36 + SHA1_STORE_STATE(36) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 36, temp); + +#ifdef DOSTORESTATE37 + SHA1_STORE_STATE(37) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 37, temp); + +#ifdef DOSTORESTATE38 + SHA1_STORE_STATE(38) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 38, temp); + +#ifdef DOSTORESTATE39 + SHA1_STORE_STATE(39) +#endif + SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 39, temp); + + + +#ifdef DOSTORESTATE40 + SHA1_STORE_STATE(40) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 40, temp); + +#ifdef DOSTORESTATE41 + SHA1_STORE_STATE(41) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 41, temp); + +#ifdef DOSTORESTATE42 + SHA1_STORE_STATE(42) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 42, temp); + +#ifdef DOSTORESTATE43 + SHA1_STORE_STATE(43) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 43, temp); + +#ifdef DOSTORESTATE44 + SHA1_STORE_STATE(44) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 44, temp); + +#ifdef DOSTORESTATE45 + SHA1_STORE_STATE(45) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 45, temp); + +#ifdef DOSTORESTATE46 + SHA1_STORE_STATE(46) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 46, temp); + +#ifdef DOSTORESTATE47 + SHA1_STORE_STATE(47) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 47, temp); + +#ifdef DOSTORESTATE48 + SHA1_STORE_STATE(48) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 48, temp); + +#ifdef DOSTORESTATE49 + SHA1_STORE_STATE(49) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 49, temp); + +#ifdef DOSTORESTATE50 + SHA1_STORE_STATE(50) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 50, temp); + +#ifdef DOSTORESTATE51 + SHA1_STORE_STATE(51) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 51, temp); + +#ifdef DOSTORESTATE52 + SHA1_STORE_STATE(52) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 52, temp); + +#ifdef DOSTORESTATE53 + SHA1_STORE_STATE(53) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 53, temp); + +#ifdef DOSTORESTATE54 + SHA1_STORE_STATE(54) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 54, temp); + +#ifdef DOSTORESTATE55 + SHA1_STORE_STATE(55) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 55, temp); + +#ifdef DOSTORESTATE56 + SHA1_STORE_STATE(56) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 56, temp); + +#ifdef DOSTORESTATE57 + SHA1_STORE_STATE(57) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 57, temp); + +#ifdef DOSTORESTATE58 + SHA1_STORE_STATE(58) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 58, temp); + +#ifdef DOSTORESTATE59 + SHA1_STORE_STATE(59) +#endif + SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 59, temp); + + + + +#ifdef DOSTORESTATE60 + SHA1_STORE_STATE(60) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 60, temp); + +#ifdef DOSTORESTATE61 + SHA1_STORE_STATE(61) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 61, temp); + +#ifdef DOSTORESTATE62 + SHA1_STORE_STATE(62) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 62, temp); + +#ifdef DOSTORESTATE63 + SHA1_STORE_STATE(63) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 63, temp); + +#ifdef DOSTORESTATE64 + SHA1_STORE_STATE(64) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 64, temp); + +#ifdef DOSTORESTATE65 + SHA1_STORE_STATE(65) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 65, temp); + +#ifdef DOSTORESTATE66 + SHA1_STORE_STATE(66) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 66, temp); + +#ifdef DOSTORESTATE67 + SHA1_STORE_STATE(67) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 67, temp); + +#ifdef DOSTORESTATE68 + SHA1_STORE_STATE(68) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 68, temp); + +#ifdef DOSTORESTATE69 + SHA1_STORE_STATE(69) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 69, temp); + +#ifdef DOSTORESTATE70 + SHA1_STORE_STATE(70) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 70, temp); + +#ifdef DOSTORESTATE71 + SHA1_STORE_STATE(71) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 71, temp); + +#ifdef DOSTORESTATE72 + SHA1_STORE_STATE(72) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 72, temp); + +#ifdef DOSTORESTATE73 + SHA1_STORE_STATE(73) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 73, temp); + +#ifdef DOSTORESTATE74 + SHA1_STORE_STATE(74) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 74, temp); + +#ifdef DOSTORESTATE75 + SHA1_STORE_STATE(75) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 75, temp); + +#ifdef DOSTORESTATE76 + SHA1_STORE_STATE(76) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 76, temp); + +#ifdef DOSTORESTATE77 + SHA1_STORE_STATE(77) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 77, temp); + +#ifdef DOSTORESTATE78 + SHA1_STORE_STATE(78) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 78, temp); + +#ifdef DOSTORESTATE79 + SHA1_STORE_STATE(79) +#endif + SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 79, temp); + + + + ihv[0] += a; ihv[1] += b; ihv[2] += c; ihv[3] += d; ihv[4] += e; +} + + + + +#define SHA1_RECOMPRESS(t) \ +static void sha1recompress_fast_ ## t (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) \ +{ \ + uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; \ + if (t > 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 79); \ + if (t > 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 78); \ + if (t > 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 77); \ + if (t > 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 76); \ + if (t > 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 75); \ + if (t > 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 74); \ + if (t > 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 73); \ + if (t > 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 72); \ + if (t > 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 71); \ + if (t > 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 70); \ + if (t > 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 69); \ + if (t > 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 68); \ + if (t > 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 67); \ + if (t > 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 66); \ + if (t > 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 65); \ + if (t > 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 64); \ + if (t > 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 63); \ + if (t > 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 62); \ + if (t > 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 61); \ + if (t > 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 60); \ + if (t > 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 59); \ + if (t > 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 58); \ + if (t > 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 57); \ + if (t > 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 56); \ + if (t > 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 55); \ + if (t > 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 54); \ + if (t > 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 53); \ + if (t > 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 52); \ + if (t > 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 51); \ + if (t > 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 50); \ + if (t > 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 49); \ + if (t > 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 48); \ + if (t > 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 47); \ + if (t > 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 46); \ + if (t > 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 45); \ + if (t > 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 44); \ + if (t > 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 43); \ + if (t > 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 42); \ + if (t > 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 41); \ + if (t > 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 40); \ + if (t > 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 39); \ + if (t > 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 38); \ + if (t > 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 37); \ + if (t > 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 36); \ + if (t > 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 35); \ + if (t > 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 34); \ + if (t > 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 33); \ + if (t > 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 32); \ + if (t > 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 31); \ + if (t > 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 30); \ + if (t > 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 29); \ + if (t > 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 28); \ + if (t > 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 27); \ + if (t > 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 26); \ + if (t > 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 25); \ + if (t > 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 24); \ + if (t > 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 23); \ + if (t > 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 22); \ + if (t > 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 21); \ + if (t > 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 20); \ + if (t > 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 19); \ + if (t > 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 18); \ + if (t > 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 17); \ + if (t > 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 16); \ + if (t > 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 15); \ + if (t > 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 14); \ + if (t > 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 13); \ + if (t > 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 12); \ + if (t > 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 11); \ + if (t > 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 10); \ + if (t > 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 9); \ + if (t > 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 8); \ + if (t > 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 7); \ + if (t > 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 6); \ + if (t > 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 5); \ + if (t > 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 4); \ + if (t > 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 3); \ + if (t > 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 2); \ + if (t > 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 1); \ + if (t > 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 0); \ + ihvin[0] = a; ihvin[1] = b; ihvin[2] = c; ihvin[3] = d; ihvin[4] = e; \ + a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; \ + if (t <= 0) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 0); \ + if (t <= 1) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 1); \ + if (t <= 2) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 2); \ + if (t <= 3) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 3); \ + if (t <= 4) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 4); \ + if (t <= 5) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 5); \ + if (t <= 6) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 6); \ + if (t <= 7) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 7); \ + if (t <= 8) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 8); \ + if (t <= 9) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 9); \ + if (t <= 10) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 10); \ + if (t <= 11) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 11); \ + if (t <= 12) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 12); \ + if (t <= 13) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 13); \ + if (t <= 14) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 14); \ + if (t <= 15) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 15); \ + if (t <= 16) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 16); \ + if (t <= 17) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 17); \ + if (t <= 18) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 18); \ + if (t <= 19) HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 19); \ + if (t <= 20) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 20); \ + if (t <= 21) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 21); \ + if (t <= 22) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 22); \ + if (t <= 23) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 23); \ + if (t <= 24) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 24); \ + if (t <= 25) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 25); \ + if (t <= 26) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 26); \ + if (t <= 27) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 27); \ + if (t <= 28) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 28); \ + if (t <= 29) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 29); \ + if (t <= 30) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 30); \ + if (t <= 31) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 31); \ + if (t <= 32) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 32); \ + if (t <= 33) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 33); \ + if (t <= 34) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 34); \ + if (t <= 35) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 35); \ + if (t <= 36) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 36); \ + if (t <= 37) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 37); \ + if (t <= 38) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 38); \ + if (t <= 39) HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 39); \ + if (t <= 40) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 40); \ + if (t <= 41) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 41); \ + if (t <= 42) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 42); \ + if (t <= 43) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 43); \ + if (t <= 44) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 44); \ + if (t <= 45) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 45); \ + if (t <= 46) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 46); \ + if (t <= 47) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 47); \ + if (t <= 48) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 48); \ + if (t <= 49) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 49); \ + if (t <= 50) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 50); \ + if (t <= 51) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 51); \ + if (t <= 52) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 52); \ + if (t <= 53) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 53); \ + if (t <= 54) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 54); \ + if (t <= 55) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 55); \ + if (t <= 56) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 56); \ + if (t <= 57) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 57); \ + if (t <= 58) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 58); \ + if (t <= 59) HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 59); \ + if (t <= 60) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 60); \ + if (t <= 61) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 61); \ + if (t <= 62) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 62); \ + if (t <= 63) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 63); \ + if (t <= 64) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 64); \ + if (t <= 65) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 65); \ + if (t <= 66) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 66); \ + if (t <= 67) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 67); \ + if (t <= 68) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 68); \ + if (t <= 69) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 69); \ + if (t <= 70) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 70); \ + if (t <= 71) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 71); \ + if (t <= 72) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 72); \ + if (t <= 73) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 73); \ + if (t <= 74) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 74); \ + if (t <= 75) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 75); \ + if (t <= 76) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 76); \ + if (t <= 77) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 77); \ + if (t <= 78) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 78); \ + if (t <= 79) HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 79); \ + ihvout[0] = ihvin[0] + a; ihvout[1] = ihvin[1] + b; ihvout[2] = ihvin[2] + c; ihvout[3] = ihvin[3] + d; ihvout[4] = ihvin[4] + e; \ +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4127) /* Compiler complains about the checks in the above macro being constant. */ +#endif + +#ifdef DOSTORESTATE0 +SHA1_RECOMPRESS(0) +#endif + +#ifdef DOSTORESTATE1 +SHA1_RECOMPRESS(1) +#endif + +#ifdef DOSTORESTATE2 +SHA1_RECOMPRESS(2) +#endif + +#ifdef DOSTORESTATE3 +SHA1_RECOMPRESS(3) +#endif + +#ifdef DOSTORESTATE4 +SHA1_RECOMPRESS(4) +#endif + +#ifdef DOSTORESTATE5 +SHA1_RECOMPRESS(5) +#endif + +#ifdef DOSTORESTATE6 +SHA1_RECOMPRESS(6) +#endif + +#ifdef DOSTORESTATE7 +SHA1_RECOMPRESS(7) +#endif + +#ifdef DOSTORESTATE8 +SHA1_RECOMPRESS(8) +#endif + +#ifdef DOSTORESTATE9 +SHA1_RECOMPRESS(9) +#endif + +#ifdef DOSTORESTATE10 +SHA1_RECOMPRESS(10) +#endif + +#ifdef DOSTORESTATE11 +SHA1_RECOMPRESS(11) +#endif + +#ifdef DOSTORESTATE12 +SHA1_RECOMPRESS(12) +#endif + +#ifdef DOSTORESTATE13 +SHA1_RECOMPRESS(13) +#endif + +#ifdef DOSTORESTATE14 +SHA1_RECOMPRESS(14) +#endif + +#ifdef DOSTORESTATE15 +SHA1_RECOMPRESS(15) +#endif + +#ifdef DOSTORESTATE16 +SHA1_RECOMPRESS(16) +#endif + +#ifdef DOSTORESTATE17 +SHA1_RECOMPRESS(17) +#endif + +#ifdef DOSTORESTATE18 +SHA1_RECOMPRESS(18) +#endif + +#ifdef DOSTORESTATE19 +SHA1_RECOMPRESS(19) +#endif + +#ifdef DOSTORESTATE20 +SHA1_RECOMPRESS(20) +#endif + +#ifdef DOSTORESTATE21 +SHA1_RECOMPRESS(21) +#endif + +#ifdef DOSTORESTATE22 +SHA1_RECOMPRESS(22) +#endif + +#ifdef DOSTORESTATE23 +SHA1_RECOMPRESS(23) +#endif + +#ifdef DOSTORESTATE24 +SHA1_RECOMPRESS(24) +#endif + +#ifdef DOSTORESTATE25 +SHA1_RECOMPRESS(25) +#endif + +#ifdef DOSTORESTATE26 +SHA1_RECOMPRESS(26) +#endif + +#ifdef DOSTORESTATE27 +SHA1_RECOMPRESS(27) +#endif + +#ifdef DOSTORESTATE28 +SHA1_RECOMPRESS(28) +#endif + +#ifdef DOSTORESTATE29 +SHA1_RECOMPRESS(29) +#endif + +#ifdef DOSTORESTATE30 +SHA1_RECOMPRESS(30) +#endif + +#ifdef DOSTORESTATE31 +SHA1_RECOMPRESS(31) +#endif + +#ifdef DOSTORESTATE32 +SHA1_RECOMPRESS(32) +#endif + +#ifdef DOSTORESTATE33 +SHA1_RECOMPRESS(33) +#endif + +#ifdef DOSTORESTATE34 +SHA1_RECOMPRESS(34) +#endif + +#ifdef DOSTORESTATE35 +SHA1_RECOMPRESS(35) +#endif + +#ifdef DOSTORESTATE36 +SHA1_RECOMPRESS(36) +#endif + +#ifdef DOSTORESTATE37 +SHA1_RECOMPRESS(37) +#endif + +#ifdef DOSTORESTATE38 +SHA1_RECOMPRESS(38) +#endif + +#ifdef DOSTORESTATE39 +SHA1_RECOMPRESS(39) +#endif + +#ifdef DOSTORESTATE40 +SHA1_RECOMPRESS(40) +#endif + +#ifdef DOSTORESTATE41 +SHA1_RECOMPRESS(41) +#endif + +#ifdef DOSTORESTATE42 +SHA1_RECOMPRESS(42) +#endif + +#ifdef DOSTORESTATE43 +SHA1_RECOMPRESS(43) +#endif + +#ifdef DOSTORESTATE44 +SHA1_RECOMPRESS(44) +#endif + +#ifdef DOSTORESTATE45 +SHA1_RECOMPRESS(45) +#endif + +#ifdef DOSTORESTATE46 +SHA1_RECOMPRESS(46) +#endif + +#ifdef DOSTORESTATE47 +SHA1_RECOMPRESS(47) +#endif + +#ifdef DOSTORESTATE48 +SHA1_RECOMPRESS(48) +#endif + +#ifdef DOSTORESTATE49 +SHA1_RECOMPRESS(49) +#endif + +#ifdef DOSTORESTATE50 +SHA1_RECOMPRESS(50) +#endif + +#ifdef DOSTORESTATE51 +SHA1_RECOMPRESS(51) +#endif + +#ifdef DOSTORESTATE52 +SHA1_RECOMPRESS(52) +#endif + +#ifdef DOSTORESTATE53 +SHA1_RECOMPRESS(53) +#endif + +#ifdef DOSTORESTATE54 +SHA1_RECOMPRESS(54) +#endif + +#ifdef DOSTORESTATE55 +SHA1_RECOMPRESS(55) +#endif + +#ifdef DOSTORESTATE56 +SHA1_RECOMPRESS(56) +#endif + +#ifdef DOSTORESTATE57 +SHA1_RECOMPRESS(57) +#endif + +#ifdef DOSTORESTATE58 +SHA1_RECOMPRESS(58) +#endif + +#ifdef DOSTORESTATE59 +SHA1_RECOMPRESS(59) +#endif + +#ifdef DOSTORESTATE60 +SHA1_RECOMPRESS(60) +#endif + +#ifdef DOSTORESTATE61 +SHA1_RECOMPRESS(61) +#endif + +#ifdef DOSTORESTATE62 +SHA1_RECOMPRESS(62) +#endif + +#ifdef DOSTORESTATE63 +SHA1_RECOMPRESS(63) +#endif + +#ifdef DOSTORESTATE64 +SHA1_RECOMPRESS(64) +#endif + +#ifdef DOSTORESTATE65 +SHA1_RECOMPRESS(65) +#endif + +#ifdef DOSTORESTATE66 +SHA1_RECOMPRESS(66) +#endif + +#ifdef DOSTORESTATE67 +SHA1_RECOMPRESS(67) +#endif + +#ifdef DOSTORESTATE68 +SHA1_RECOMPRESS(68) +#endif + +#ifdef DOSTORESTATE69 +SHA1_RECOMPRESS(69) +#endif + +#ifdef DOSTORESTATE70 +SHA1_RECOMPRESS(70) +#endif + +#ifdef DOSTORESTATE71 +SHA1_RECOMPRESS(71) +#endif + +#ifdef DOSTORESTATE72 +SHA1_RECOMPRESS(72) +#endif + +#ifdef DOSTORESTATE73 +SHA1_RECOMPRESS(73) +#endif + +#ifdef DOSTORESTATE74 +SHA1_RECOMPRESS(74) +#endif + +#ifdef DOSTORESTATE75 +SHA1_RECOMPRESS(75) +#endif + +#ifdef DOSTORESTATE76 +SHA1_RECOMPRESS(76) +#endif + +#ifdef DOSTORESTATE77 +SHA1_RECOMPRESS(77) +#endif + +#ifdef DOSTORESTATE78 +SHA1_RECOMPRESS(78) +#endif + +#ifdef DOSTORESTATE79 +SHA1_RECOMPRESS(79) +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +static void sha1_recompression_step(uint32_t step, uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) +{ + switch (step) + { +#ifdef DOSTORESTATE0 + case 0: + sha1recompress_fast_0(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE1 + case 1: + sha1recompress_fast_1(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE2 + case 2: + sha1recompress_fast_2(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE3 + case 3: + sha1recompress_fast_3(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE4 + case 4: + sha1recompress_fast_4(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE5 + case 5: + sha1recompress_fast_5(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE6 + case 6: + sha1recompress_fast_6(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE7 + case 7: + sha1recompress_fast_7(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE8 + case 8: + sha1recompress_fast_8(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE9 + case 9: + sha1recompress_fast_9(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE10 + case 10: + sha1recompress_fast_10(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE11 + case 11: + sha1recompress_fast_11(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE12 + case 12: + sha1recompress_fast_12(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE13 + case 13: + sha1recompress_fast_13(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE14 + case 14: + sha1recompress_fast_14(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE15 + case 15: + sha1recompress_fast_15(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE16 + case 16: + sha1recompress_fast_16(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE17 + case 17: + sha1recompress_fast_17(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE18 + case 18: + sha1recompress_fast_18(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE19 + case 19: + sha1recompress_fast_19(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE20 + case 20: + sha1recompress_fast_20(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE21 + case 21: + sha1recompress_fast_21(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE22 + case 22: + sha1recompress_fast_22(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE23 + case 23: + sha1recompress_fast_23(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE24 + case 24: + sha1recompress_fast_24(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE25 + case 25: + sha1recompress_fast_25(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE26 + case 26: + sha1recompress_fast_26(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE27 + case 27: + sha1recompress_fast_27(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE28 + case 28: + sha1recompress_fast_28(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE29 + case 29: + sha1recompress_fast_29(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE30 + case 30: + sha1recompress_fast_30(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE31 + case 31: + sha1recompress_fast_31(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE32 + case 32: + sha1recompress_fast_32(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE33 + case 33: + sha1recompress_fast_33(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE34 + case 34: + sha1recompress_fast_34(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE35 + case 35: + sha1recompress_fast_35(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE36 + case 36: + sha1recompress_fast_36(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE37 + case 37: + sha1recompress_fast_37(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE38 + case 38: + sha1recompress_fast_38(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE39 + case 39: + sha1recompress_fast_39(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE40 + case 40: + sha1recompress_fast_40(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE41 + case 41: + sha1recompress_fast_41(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE42 + case 42: + sha1recompress_fast_42(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE43 + case 43: + sha1recompress_fast_43(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE44 + case 44: + sha1recompress_fast_44(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE45 + case 45: + sha1recompress_fast_45(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE46 + case 46: + sha1recompress_fast_46(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE47 + case 47: + sha1recompress_fast_47(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE48 + case 48: + sha1recompress_fast_48(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE49 + case 49: + sha1recompress_fast_49(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE50 + case 50: + sha1recompress_fast_50(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE51 + case 51: + sha1recompress_fast_51(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE52 + case 52: + sha1recompress_fast_52(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE53 + case 53: + sha1recompress_fast_53(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE54 + case 54: + sha1recompress_fast_54(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE55 + case 55: + sha1recompress_fast_55(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE56 + case 56: + sha1recompress_fast_56(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE57 + case 57: + sha1recompress_fast_57(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE58 + case 58: + sha1recompress_fast_58(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE59 + case 59: + sha1recompress_fast_59(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE60 + case 60: + sha1recompress_fast_60(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE61 + case 61: + sha1recompress_fast_61(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE62 + case 62: + sha1recompress_fast_62(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE63 + case 63: + sha1recompress_fast_63(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE64 + case 64: + sha1recompress_fast_64(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE65 + case 65: + sha1recompress_fast_65(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE66 + case 66: + sha1recompress_fast_66(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE67 + case 67: + sha1recompress_fast_67(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE68 + case 68: + sha1recompress_fast_68(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE69 + case 69: + sha1recompress_fast_69(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE70 + case 70: + sha1recompress_fast_70(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE71 + case 71: + sha1recompress_fast_71(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE72 + case 72: + sha1recompress_fast_72(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE73 + case 73: + sha1recompress_fast_73(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE74 + case 74: + sha1recompress_fast_74(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE75 + case 75: + sha1recompress_fast_75(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE76 + case 76: + sha1recompress_fast_76(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE77 + case 77: + sha1recompress_fast_77(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE78 + case 78: + sha1recompress_fast_78(ihvin, ihvout, me2, state); + break; +#endif +#ifdef DOSTORESTATE79 + case 79: + sha1recompress_fast_79(ihvin, ihvout, me2, state); + break; +#endif + default: + abort(); + } + +} + + + +static void sha1_process(SHA1_CTX *ctx, const uint32_t block[16]) +{ + unsigned i, j; + uint32_t ubc_dv_mask[DVMASKSIZE] = { 0xFFFFFFFF }; + uint32_t ihvtmp[5]; + + ctx->ihv1[0] = ctx->ihv[0]; + ctx->ihv1[1] = ctx->ihv[1]; + ctx->ihv1[2] = ctx->ihv[2]; + ctx->ihv1[3] = ctx->ihv[3]; + ctx->ihv1[4] = ctx->ihv[4]; + + sha1_compression_states(ctx->ihv, block, ctx->m1, ctx->states); + + if (ctx->detect_coll) + { + if (ctx->ubc_check) + { + ubc_check(ctx->m1, ubc_dv_mask); + } + + if (ubc_dv_mask[0] != 0) + { + for (i = 0; sha1_dvs[i].dvType != 0; ++i) + { + if (ubc_dv_mask[0] & ((uint32_t)(1) << sha1_dvs[i].maskb)) + { + for (j = 0; j < 80; ++j) + ctx->m2[j] = ctx->m1[j] ^ sha1_dvs[i].dm[j]; + + sha1_recompression_step(sha1_dvs[i].testt, ctx->ihv2, ihvtmp, ctx->m2, ctx->states[sha1_dvs[i].testt]); + + /* to verify SHA-1 collision detection code with collisions for reduced-step SHA-1 */ + if ((0 == ((ihvtmp[0] ^ ctx->ihv[0]) | (ihvtmp[1] ^ ctx->ihv[1]) | (ihvtmp[2] ^ ctx->ihv[2]) | (ihvtmp[3] ^ ctx->ihv[3]) | (ihvtmp[4] ^ ctx->ihv[4]))) + || (ctx->reduced_round_coll && 0==((ctx->ihv1[0] ^ ctx->ihv2[0]) | (ctx->ihv1[1] ^ ctx->ihv2[1]) | (ctx->ihv1[2] ^ ctx->ihv2[2]) | (ctx->ihv1[3] ^ ctx->ihv2[3]) | (ctx->ihv1[4] ^ ctx->ihv2[4])))) + { + ctx->found_collision = 1; + + if (ctx->safe_hash) + { + sha1_compression_W(ctx->ihv, ctx->m1); + sha1_compression_W(ctx->ihv, ctx->m1); + } + + break; + } + } + } + } + } +} + +void SHA1DCInit(SHA1_CTX *ctx) +{ + ctx->total = 0; + ctx->ihv[0] = 0x67452301; + ctx->ihv[1] = 0xEFCDAB89; + ctx->ihv[2] = 0x98BADCFE; + ctx->ihv[3] = 0x10325476; + ctx->ihv[4] = 0xC3D2E1F0; + ctx->found_collision = 0; + ctx->safe_hash = SHA1DC_INIT_SAFE_HASH_DEFAULT; + ctx->ubc_check = 1; + ctx->detect_coll = 1; + ctx->reduced_round_coll = 0; + ctx->callback = NULL; +} + +void SHA1DCSetSafeHash(SHA1_CTX *ctx, int safehash) +{ + if (safehash) + ctx->safe_hash = 1; + else + ctx->safe_hash = 0; +} + + +void SHA1DCSetUseUBC(SHA1_CTX *ctx, int ubc_check) +{ + if (ubc_check) + ctx->ubc_check = 1; + else + ctx->ubc_check = 0; +} + +void SHA1DCSetUseDetectColl(SHA1_CTX *ctx, int detect_coll) +{ + if (detect_coll) + ctx->detect_coll = 1; + else + ctx->detect_coll = 0; +} + +void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX *ctx, int reduced_round_coll) +{ + if (reduced_round_coll) + ctx->reduced_round_coll = 1; + else + ctx->reduced_round_coll = 0; +} + +void SHA1DCSetCallback(SHA1_CTX *ctx, collision_block_callback callback) +{ + ctx->callback = callback; +} + +void SHA1DCUpdate(SHA1_CTX *ctx, const char *buf, size_t len) +{ + unsigned left, fill; + + if (len == 0) + return; + + left = ctx->total & 63; + fill = 64 - left; + + if (left && len >= fill) + { + ctx->total += fill; + memcpy(ctx->buffer + left, buf, fill); + sha1_process(ctx, (uint32_t*)(ctx->buffer)); + buf += fill; + len -= fill; + left = 0; + } + while (len >= 64) + { + ctx->total += 64; + +#if defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) + sha1_process(ctx, (uint32_t*)(buf)); +#else + memcpy(ctx->buffer, buf, 64); + sha1_process(ctx, (uint32_t*)(ctx->buffer)); +#endif /* defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) */ + buf += 64; + len -= 64; + } + if (len > 0) + { + ctx->total += len; + memcpy(ctx->buffer + left, buf, len); + } +} + +static const unsigned char sha1_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +int SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx) +{ + uint32_t last = ctx->total & 63; + uint32_t padn = (last < 56) ? (56 - last) : (120 - last); + uint64_t total; + SHA1DCUpdate(ctx, (const char*)(sha1_padding), padn); + + total = ctx->total - padn; + total <<= 3; + ctx->buffer[56] = (unsigned char)(total >> 56); + ctx->buffer[57] = (unsigned char)(total >> 48); + ctx->buffer[58] = (unsigned char)(total >> 40); + ctx->buffer[59] = (unsigned char)(total >> 32); + ctx->buffer[60] = (unsigned char)(total >> 24); + ctx->buffer[61] = (unsigned char)(total >> 16); + ctx->buffer[62] = (unsigned char)(total >> 8); + ctx->buffer[63] = (unsigned char)(total); + sha1_process(ctx, (uint32_t*)(ctx->buffer)); + output[0] = (unsigned char)(ctx->ihv[0] >> 24); + output[1] = (unsigned char)(ctx->ihv[0] >> 16); + output[2] = (unsigned char)(ctx->ihv[0] >> 8); + output[3] = (unsigned char)(ctx->ihv[0]); + output[4] = (unsigned char)(ctx->ihv[1] >> 24); + output[5] = (unsigned char)(ctx->ihv[1] >> 16); + output[6] = (unsigned char)(ctx->ihv[1] >> 8); + output[7] = (unsigned char)(ctx->ihv[1]); + output[8] = (unsigned char)(ctx->ihv[2] >> 24); + output[9] = (unsigned char)(ctx->ihv[2] >> 16); + output[10] = (unsigned char)(ctx->ihv[2] >> 8); + output[11] = (unsigned char)(ctx->ihv[2]); + output[12] = (unsigned char)(ctx->ihv[3] >> 24); + output[13] = (unsigned char)(ctx->ihv[3] >> 16); + output[14] = (unsigned char)(ctx->ihv[3] >> 8); + output[15] = (unsigned char)(ctx->ihv[3]); + output[16] = (unsigned char)(ctx->ihv[4] >> 24); + output[17] = (unsigned char)(ctx->ihv[4] >> 16); + output[18] = (unsigned char)(ctx->ihv[4] >> 8); + output[19] = (unsigned char)(ctx->ihv[4]); + return ctx->found_collision; +} + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C +#endif diff --git a/src/util/hash/sha1/sha1dc/sha1.h b/src/util/hash/sha1/sha1dc/sha1.h new file mode 100644 index 000000000..1e4e94be5 --- /dev/null +++ b/src/util/hash/sha1/sha1dc/sha1.h @@ -0,0 +1,110 @@ +/*** +* Copyright 2017 Marc Stevens , Dan Shumow +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +#ifndef SHA1DC_SHA1_H +#define SHA1DC_SHA1_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include +#endif + +/* sha-1 compression function that takes an already expanded message, and additionally store intermediate states */ +/* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is defined in ubc_check.h */ +void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]); + +/* +// Function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]). +// Where 0 <= T < 80 +// me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference.) +// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block. +// The function will return: +// ihvin: The reconstructed input chaining value. +// ihvout: The reconstructed output chaining value. +*/ +typedef void(*sha1_recompression_type)(uint32_t*, uint32_t*, const uint32_t*, const uint32_t*); + +/* A callback function type that can be set to be called when a collision block has been found: */ +/* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */ +typedef void(*collision_block_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); + +/* The SHA-1 context. */ +typedef struct { + uint64_t total; + uint32_t ihv[5]; + unsigned char buffer[64]; + int found_collision; + int safe_hash; + int detect_coll; + int ubc_check; + int reduced_round_coll; + collision_block_callback callback; + + uint32_t ihv1[5]; + uint32_t ihv2[5]; + uint32_t m1[80]; + uint32_t m2[80]; + uint32_t states[80][5]; +} SHA1_CTX; + +/* Initialize SHA-1 context. */ +void SHA1DCInit(SHA1_CTX*); + +/* + Function to enable safe SHA-1 hashing: + Collision attacks are thwarted by hashing a detected near-collision block 3 times. + Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks: + The best collision attacks against SHA-1 have complexity about 2^60, + thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be 2^180. + An attacker would be better off using a generic birthday search of complexity 2^80. + + Enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected, + but it will result in a different SHA-1 hash for messages where a collision attack was detected. + This will automatically invalidate SHA-1 based digital signature forgeries. + Enabled by default. +*/ +void SHA1DCSetSafeHash(SHA1_CTX*, int); + +/* + Function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up). + Enabled by default + */ +void SHA1DCSetUseUBC(SHA1_CTX*, int); + +/* + Function to disable or enable the use of Collision Detection. + Enabled by default. + */ +void SHA1DCSetUseDetectColl(SHA1_CTX*, int); + +/* function to disable or enable the detection of reduced-round SHA-1 collisions */ +/* disabled by default */ +void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX*, int); + +/* function to set a callback function, pass NULL to disable */ +/* by default no callback set */ +void SHA1DCSetCallback(SHA1_CTX*, collision_block_callback); + +/* update SHA-1 context with buffer contents */ +void SHA1DCUpdate(SHA1_CTX*, const char*, size_t); + +/* obtain SHA-1 hash from SHA-1 context */ +/* returns: 0 = no collision detected, otherwise = collision found => warn user for active attack */ +int SHA1DCFinal(unsigned char[20], SHA1_CTX*); + +#if defined(__cplusplus) +} +#endif + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H +#endif + +#endif diff --git a/src/util/hash/sha1/sha1dc/ubc_check.c b/src/util/hash/sha1/sha1dc/ubc_check.c new file mode 100644 index 000000000..b3beff2af --- /dev/null +++ b/src/util/hash/sha1/sha1dc/ubc_check.c @@ -0,0 +1,372 @@ +/*** +* Copyright 2017 Marc Stevens , Dan Shumow +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +/* +// this file was generated by the 'parse_bitrel' program in the tools section +// using the data files from directory 'tools/data/3565' +// +// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check +// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) +// dm[80] is the expanded message block XOR-difference defined by the DV +// testt is the step to do the recompression from for collision detection +// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check +// +// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs +// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met +// thus one needs to do the recompression check for each DV that has its bit set +// +// ubc_check is programmatically generated and the unavoidable bitconditions have been hardcoded +// a directly verifiable version named ubc_check_verify can be found in ubc_check_verify.c +// ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in the tools section +*/ + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include +#endif +#ifdef SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C +#endif +#include "ubc_check.h" + +static const uint32_t DV_I_43_0_bit = (uint32_t)(1) << 0; +static const uint32_t DV_I_44_0_bit = (uint32_t)(1) << 1; +static const uint32_t DV_I_45_0_bit = (uint32_t)(1) << 2; +static const uint32_t DV_I_46_0_bit = (uint32_t)(1) << 3; +static const uint32_t DV_I_46_2_bit = (uint32_t)(1) << 4; +static const uint32_t DV_I_47_0_bit = (uint32_t)(1) << 5; +static const uint32_t DV_I_47_2_bit = (uint32_t)(1) << 6; +static const uint32_t DV_I_48_0_bit = (uint32_t)(1) << 7; +static const uint32_t DV_I_48_2_bit = (uint32_t)(1) << 8; +static const uint32_t DV_I_49_0_bit = (uint32_t)(1) << 9; +static const uint32_t DV_I_49_2_bit = (uint32_t)(1) << 10; +static const uint32_t DV_I_50_0_bit = (uint32_t)(1) << 11; +static const uint32_t DV_I_50_2_bit = (uint32_t)(1) << 12; +static const uint32_t DV_I_51_0_bit = (uint32_t)(1) << 13; +static const uint32_t DV_I_51_2_bit = (uint32_t)(1) << 14; +static const uint32_t DV_I_52_0_bit = (uint32_t)(1) << 15; +static const uint32_t DV_II_45_0_bit = (uint32_t)(1) << 16; +static const uint32_t DV_II_46_0_bit = (uint32_t)(1) << 17; +static const uint32_t DV_II_46_2_bit = (uint32_t)(1) << 18; +static const uint32_t DV_II_47_0_bit = (uint32_t)(1) << 19; +static const uint32_t DV_II_48_0_bit = (uint32_t)(1) << 20; +static const uint32_t DV_II_49_0_bit = (uint32_t)(1) << 21; +static const uint32_t DV_II_49_2_bit = (uint32_t)(1) << 22; +static const uint32_t DV_II_50_0_bit = (uint32_t)(1) << 23; +static const uint32_t DV_II_50_2_bit = (uint32_t)(1) << 24; +static const uint32_t DV_II_51_0_bit = (uint32_t)(1) << 25; +static const uint32_t DV_II_51_2_bit = (uint32_t)(1) << 26; +static const uint32_t DV_II_52_0_bit = (uint32_t)(1) << 27; +static const uint32_t DV_II_53_0_bit = (uint32_t)(1) << 28; +static const uint32_t DV_II_54_0_bit = (uint32_t)(1) << 29; +static const uint32_t DV_II_55_0_bit = (uint32_t)(1) << 30; +static const uint32_t DV_II_56_0_bit = (uint32_t)(1) << 31; + +dv_info_t sha1_dvs[] = +{ + {1,43,0,58,0,0, { 0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161,0x80000599 } } +, {1,44,0,58,0,1, { 0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803,0x80000161 } } +, {1,45,0,58,0,2, { 0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c,0x00000803 } } +, {1,46,0,58,0,3, { 0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6,0x8000004c } } +, {1,46,2,58,0,4, { 0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a,0x00000132 } } +, {1,47,0,58,0,5, { 0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408,0x800000e6 } } +, {1,47,2,58,0,6, { 0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020,0x0000039a } } +, {1,48,0,58,0,7, { 0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164,0x00000408 } } +, {1,48,2,58,0,8, { 0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590,0x00001020 } } +, {1,49,0,58,0,9, { 0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018,0x00000164 } } +, {1,49,2,58,0,10, { 0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060,0x00000590 } } +, {1,50,0,65,0,11, { 0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202,0x00000018 } } +, {1,50,2,65,0,12, { 0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a,0x00000060 } } +, {1,51,0,65,0,13, { 0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012,0x80000202 } } +, {1,51,2,65,0,14, { 0xa0000003,0x20000030,0x60000000,0xe000002a,0x20000043,0xb0000040,0xd0000053,0xd0000022,0x20000000,0x60000032,0x60000043,0x20000040,0xe0000042,0x60000002,0x80000001,0x00000020,0x00000003,0x40000052,0x40000040,0xe0000052,0xa0000000,0x80000040,0x20000001,0x20000060,0x80000001,0x40000042,0xc0000043,0x40000022,0x00000003,0x40000042,0xc0000043,0xc0000022,0x00000001,0x40000002,0xc0000043,0x40000062,0x80000001,0x40000042,0x40000042,0x40000002,0x00000002,0x00000040,0x80000002,0x80000000,0x80000002,0x80000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000000,0x00000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000101,0x00000009,0x00000012,0x00000202,0x0000001a,0x00000124,0x0000040c,0x00000026,0x0000004a,0x0000080a } } +, {1,52,0,65,0,15, { 0x04000010,0xe8000000,0x0800000c,0x18000000,0xb800000a,0xc8000010,0x2c000010,0xf4000014,0xb4000008,0x08000000,0x9800000c,0xd8000010,0x08000010,0xb8000010,0x98000000,0x60000000,0x00000008,0xc0000000,0x90000014,0x10000010,0xb8000014,0x28000000,0x20000010,0x48000000,0x08000018,0x60000000,0x90000010,0xf0000010,0x90000008,0xc0000000,0x90000010,0xf0000010,0xb0000008,0x40000000,0x90000000,0xf0000010,0x90000018,0x60000000,0x90000010,0x90000010,0x90000000,0x80000000,0x00000010,0xa0000000,0x20000000,0xa0000000,0x20000010,0x00000000,0x20000010,0x20000000,0x00000010,0x20000000,0x00000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000040,0x40000002,0x80000004,0x80000080,0x80000006,0x00000049,0x00000103,0x80000009,0x80000012 } } +, {2,45,0,58,0,16, { 0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054,0x00000967 } } +, {2,46,0,58,0,17, { 0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4,0x80000054 } } +, {2,46,2,58,0,18, { 0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6,0x0000106a,0x00000b90,0x00000152 } } +, {2,47,0,58,0,19, { 0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a,0x000002e4 } } +, {2,48,0,58,0,20, { 0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d,0x8000041a } } +, {2,49,0,58,0,21, { 0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b,0x8000016d } } +, {2,49,2,58,0,22, { 0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c,0x000005b6 } } +, {2,50,0,65,0,23, { 0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b,0x0000011b } } +, {2,50,2,65,0,24, { 0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e,0x0000046c } } +, {2,51,0,65,0,25, { 0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014,0x8000024b } } +, {2,51,2,65,0,26, { 0x00000043,0xd0000072,0xf0000010,0xf000006a,0x80000040,0x90000070,0xb0000053,0x30000008,0x00000043,0xd0000072,0xb0000010,0xf0000062,0xc0000042,0x00000030,0xe0000042,0x20000060,0xe0000041,0x20000050,0xc0000041,0xe0000072,0xa0000003,0xc0000012,0x60000041,0xc0000032,0x20000001,0xc0000002,0xe0000042,0x60000042,0x80000002,0x00000000,0x00000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000000,0x00000040,0x80000001,0x00000060,0x80000003,0x40000002,0xc0000040,0xc0000002,0x80000000,0x80000000,0x80000002,0x00000040,0x00000002,0x80000000,0x80000000,0x80000000,0x00000002,0x00000040,0x00000000,0x80000040,0x80000002,0x00000000,0x80000000,0x80000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000004,0x00000080,0x00000004,0x00000009,0x00000105,0x00000089,0x00000016,0x0000020b,0x0000011b,0x0000012d,0x0000041e,0x00000224,0x00000050,0x0000092e } } +, {2,52,0,65,0,27, { 0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089,0x00000014 } } +, {2,53,0,65,0,28, { 0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107,0x00000089 } } +, {2,54,0,65,0,29, { 0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b,0x80000107 } } +, {2,55,0,65,0,30, { 0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046,0x4000004b } } +, {2,56,0,65,0,31, { 0x2600001a,0x00000010,0x0400001c,0xcc000014,0x0c000002,0xc0000010,0xb400001c,0x3c000004,0xbc00001a,0x20000010,0x2400001c,0xec000014,0x0c000002,0xc0000010,0xb400001c,0x2c000004,0xbc000018,0xb0000010,0x0000000c,0xb8000010,0x08000018,0x78000010,0x08000014,0x70000010,0xb800001c,0xe8000000,0xb0000004,0x58000010,0xb000000c,0x48000000,0xb0000000,0xb8000010,0x98000010,0xa0000000,0x00000000,0x00000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0x20000000,0x00000010,0x60000000,0x00000018,0xe0000000,0x90000000,0x30000010,0xb0000000,0x20000000,0x20000000,0xa0000000,0x00000010,0x80000000,0x20000000,0x20000000,0x20000000,0x80000000,0x00000010,0x00000000,0x20000010,0xa0000000,0x00000000,0x20000000,0x20000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001,0x00000020,0x00000001,0x40000002,0x40000041,0x40000022,0x80000005,0xc0000082,0xc0000046 } } +, {0,0,0,0,0,0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}} +}; +void ubc_check(const uint32_t W[80], uint32_t dvmask[1]) +{ + uint32_t mask = ~((uint32_t)(0)); + mask &= (((((W[44]^W[45])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_I_51_0_bit|DV_I_52_0_bit|DV_II_45_0_bit|DV_II_46_0_bit|DV_II_50_0_bit|DV_II_51_0_bit)); + mask &= (((((W[49]^W[50])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_II_45_0_bit|DV_II_50_0_bit|DV_II_51_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); + mask &= (((((W[48]^W[49])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_52_0_bit|DV_II_49_0_bit|DV_II_50_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); + mask &= ((((W[47]^(W[50]>>25))&(1<<4))-(1<<4)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); + mask &= (((((W[47]^W[48])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_51_0_bit|DV_II_48_0_bit|DV_II_49_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); + mask &= (((((W[46]>>4)^(W[49]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_50_0_bit|DV_II_55_0_bit)); + mask &= (((((W[46]^W[47])>>29)&1)-1) | ~(DV_I_43_0_bit|DV_I_50_0_bit|DV_II_47_0_bit|DV_II_48_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); + mask &= (((((W[45]>>4)^(W[48]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit|DV_II_49_0_bit|DV_II_54_0_bit)); + mask &= (((((W[45]^W[46])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_51_0_bit|DV_II_52_0_bit)); + mask &= (((((W[44]>>4)^(W[47]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_53_0_bit)); + mask &= (((((W[43]>>4)^(W[46]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_52_0_bit)); + mask &= (((((W[43]^W[44])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_I_50_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_49_0_bit|DV_II_50_0_bit)); + mask &= (((((W[42]>>4)^(W[45]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_51_0_bit)); + mask &= (((((W[41]>>4)^(W[44]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_50_0_bit)); + mask &= (((((W[40]^W[41])>>29)&1)-1) | ~(DV_I_44_0_bit|DV_I_47_0_bit|DV_I_48_0_bit|DV_II_46_0_bit|DV_II_47_0_bit|DV_II_56_0_bit)); + mask &= (((((W[54]^W[55])>>29)&1)-1) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_50_0_bit|DV_II_55_0_bit|DV_II_56_0_bit)); + mask &= (((((W[53]^W[54])>>29)&1)-1) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_49_0_bit|DV_II_54_0_bit|DV_II_55_0_bit)); + mask &= (((((W[52]^W[53])>>29)&1)-1) | ~(DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit|DV_II_53_0_bit|DV_II_54_0_bit)); + mask &= ((((W[50]^(W[53]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_48_0_bit|DV_II_54_0_bit)); + mask &= (((((W[50]^W[51])>>29)&1)-1) | ~(DV_I_47_0_bit|DV_II_46_0_bit|DV_II_51_0_bit|DV_II_52_0_bit|DV_II_56_0_bit)); + mask &= ((((W[49]^(W[52]>>25))&(1<<4))-(1<<4)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit|DV_II_47_0_bit|DV_II_53_0_bit)); + mask &= ((((W[48]^(W[51]>>25))&(1<<4))-(1<<4)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit|DV_II_46_0_bit|DV_II_52_0_bit)); + mask &= (((((W[42]^W[43])>>29)&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_I_50_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); + mask &= (((((W[41]^W[42])>>29)&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_I_49_0_bit|DV_II_47_0_bit|DV_II_48_0_bit)); + mask &= (((((W[40]>>4)^(W[43]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_50_0_bit|DV_II_49_0_bit|DV_II_56_0_bit)); + mask &= (((((W[39]>>4)^(W[42]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_49_0_bit|DV_II_48_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) + mask &= (((((W[38]>>4)^(W[41]>>29))&1)-1) | ~(DV_I_44_0_bit|DV_I_48_0_bit|DV_II_47_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); + mask &= (((((W[37]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_43_0_bit|DV_I_47_0_bit|DV_II_46_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)) + mask &= (((((W[55]^W[56])>>29)&1)-1) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_51_0_bit|DV_II_56_0_bit)); + if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)) + mask &= ((((W[52]^(W[55]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_50_0_bit|DV_II_56_0_bit)); + if (mask & (DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)) + mask &= ((((W[51]^(W[54]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_47_0_bit|DV_II_49_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)) + mask &= (((((W[51]^W[52])>>29)&1)-1) | ~(DV_I_48_0_bit|DV_II_47_0_bit|DV_II_52_0_bit|DV_II_53_0_bit)); + if (mask & (DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)) + mask &= (((((W[36]>>4)^(W[40]>>29))&1)-1) | ~(DV_I_46_0_bit|DV_I_49_0_bit|DV_II_45_0_bit|DV_II_48_0_bit)); + if (mask & (DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)) + mask &= ((0-(((W[53]^W[56])>>29)&1)) | ~(DV_I_52_0_bit|DV_II_48_0_bit|DV_II_49_0_bit)); + if (mask & (DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)) + mask &= ((0-(((W[51]^W[54])>>29)&1)) | ~(DV_I_50_0_bit|DV_II_46_0_bit|DV_II_47_0_bit)); + if (mask & (DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)) + mask &= ((0-(((W[50]^W[52])>>29)&1)) | ~(DV_I_49_0_bit|DV_I_51_0_bit|DV_II_45_0_bit)); + if (mask & (DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)) + mask &= ((0-(((W[49]^W[51])>>29)&1)) | ~(DV_I_48_0_bit|DV_I_50_0_bit|DV_I_52_0_bit)); + if (mask & (DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)) + mask &= ((0-(((W[48]^W[50])>>29)&1)) | ~(DV_I_47_0_bit|DV_I_49_0_bit|DV_I_51_0_bit)); + if (mask & (DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)) + mask &= ((0-(((W[47]^W[49])>>29)&1)) | ~(DV_I_46_0_bit|DV_I_48_0_bit|DV_I_50_0_bit)); + if (mask & (DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)) + mask &= ((0-(((W[46]^W[48])>>29)&1)) | ~(DV_I_45_0_bit|DV_I_47_0_bit|DV_I_49_0_bit)); + mask &= ((((W[45]^W[47])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit|DV_I_51_2_bit)); + if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)) + mask &= ((0-(((W[45]^W[47])>>29)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_I_48_0_bit)); + mask &= (((((W[44]^W[46])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit|DV_I_50_2_bit)); + if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)) + mask &= ((0-(((W[44]^W[46])>>29)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_I_47_0_bit)); + mask &= ((0-((W[41]^(W[42]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_II_46_2_bit|DV_II_51_2_bit)); + mask &= ((0-((W[40]^(W[41]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_51_2_bit|DV_II_50_2_bit)); + if (mask & (DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)) + mask &= ((0-(((W[40]^W[42])>>4)&1)) | ~(DV_I_44_0_bit|DV_I_46_0_bit|DV_II_56_0_bit)); + mask &= ((0-((W[39]^(W[40]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_50_2_bit|DV_II_49_2_bit)); + if (mask & (DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)) + mask &= ((0-(((W[39]^W[41])>>4)&1)) | ~(DV_I_43_0_bit|DV_I_45_0_bit|DV_II_55_0_bit)); + if (mask & (DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)) + mask &= ((0-(((W[38]^W[40])>>4)&1)) | ~(DV_I_44_0_bit|DV_II_54_0_bit|DV_II_56_0_bit)); + if (mask & (DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)) + mask &= ((0-(((W[37]^W[39])>>4)&1)) | ~(DV_I_43_0_bit|DV_II_53_0_bit|DV_II_55_0_bit)); + mask &= ((0-((W[36]^(W[37]>>5))&(1<<1))) | ~(DV_I_47_2_bit|DV_I_50_2_bit|DV_II_46_2_bit)); + if (mask & (DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)) + mask &= (((((W[35]>>4)^(W[39]>>29))&1)-1) | ~(DV_I_45_0_bit|DV_I_48_0_bit|DV_II_47_0_bit)); + if (mask & (DV_I_48_0_bit|DV_II_48_0_bit)) + mask &= ((0-((W[63]^(W[64]>>5))&(1<<0))) | ~(DV_I_48_0_bit|DV_II_48_0_bit)); + if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) + mask &= ((0-((W[63]^(W[64]>>5))&(1<<1))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); + if (mask & (DV_I_47_0_bit|DV_II_47_0_bit)) + mask &= ((0-((W[62]^(W[63]>>5))&(1<<0))) | ~(DV_I_47_0_bit|DV_II_47_0_bit)); + if (mask & (DV_I_46_0_bit|DV_II_46_0_bit)) + mask &= ((0-((W[61]^(W[62]>>5))&(1<<0))) | ~(DV_I_46_0_bit|DV_II_46_0_bit)); + mask &= ((0-((W[61]^(W[62]>>5))&(1<<2))) | ~(DV_I_46_2_bit|DV_II_46_2_bit)); + if (mask & (DV_I_45_0_bit|DV_II_45_0_bit)) + mask &= ((0-((W[60]^(W[61]>>5))&(1<<0))) | ~(DV_I_45_0_bit|DV_II_45_0_bit)); + if (mask & (DV_II_51_0_bit|DV_II_54_0_bit)) + mask &= (((((W[58]^W[59])>>29)&1)-1) | ~(DV_II_51_0_bit|DV_II_54_0_bit)); + if (mask & (DV_II_50_0_bit|DV_II_53_0_bit)) + mask &= (((((W[57]^W[58])>>29)&1)-1) | ~(DV_II_50_0_bit|DV_II_53_0_bit)); + if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) + mask &= ((((W[56]^(W[59]>>25))&(1<<4))-(1<<4)) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); + if (mask & (DV_II_51_0_bit|DV_II_52_0_bit)) + mask &= ((0-(((W[56]^W[59])>>29)&1)) | ~(DV_II_51_0_bit|DV_II_52_0_bit)); + if (mask & (DV_II_49_0_bit|DV_II_52_0_bit)) + mask &= (((((W[56]^W[57])>>29)&1)-1) | ~(DV_II_49_0_bit|DV_II_52_0_bit)); + if (mask & (DV_II_51_0_bit|DV_II_53_0_bit)) + mask &= ((((W[55]^(W[58]>>25))&(1<<4))-(1<<4)) | ~(DV_II_51_0_bit|DV_II_53_0_bit)); + if (mask & (DV_II_50_0_bit|DV_II_52_0_bit)) + mask &= ((((W[54]^(W[57]>>25))&(1<<4))-(1<<4)) | ~(DV_II_50_0_bit|DV_II_52_0_bit)); + if (mask & (DV_II_49_0_bit|DV_II_51_0_bit)) + mask &= ((((W[53]^(W[56]>>25))&(1<<4))-(1<<4)) | ~(DV_II_49_0_bit|DV_II_51_0_bit)); + mask &= ((((W[51]^(W[50]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); + mask &= ((((W[48]^W[50])&(1<<6))-(1<<6)) | ~(DV_I_50_2_bit|DV_II_46_2_bit)); + if (mask & (DV_I_51_0_bit|DV_I_52_0_bit)) + mask &= ((0-(((W[48]^W[55])>>29)&1)) | ~(DV_I_51_0_bit|DV_I_52_0_bit)); + mask &= ((((W[47]^W[49])&(1<<6))-(1<<6)) | ~(DV_I_49_2_bit|DV_I_51_2_bit)); + mask &= ((((W[48]^(W[47]>>5))&(1<<1))-(1<<1)) | ~(DV_I_47_2_bit|DV_II_51_2_bit)); + mask &= ((((W[46]^W[48])&(1<<6))-(1<<6)) | ~(DV_I_48_2_bit|DV_I_50_2_bit)); + mask &= ((((W[47]^(W[46]>>5))&(1<<1))-(1<<1)) | ~(DV_I_46_2_bit|DV_II_50_2_bit)); + mask &= ((0-((W[44]^(W[45]>>5))&(1<<1))) | ~(DV_I_51_2_bit|DV_II_49_2_bit)); + mask &= ((((W[43]^W[45])&(1<<6))-(1<<6)) | ~(DV_I_47_2_bit|DV_I_49_2_bit)); + mask &= (((((W[42]^W[44])>>6)&1)-1) | ~(DV_I_46_2_bit|DV_I_48_2_bit)); + mask &= ((((W[43]^(W[42]>>5))&(1<<1))-(1<<1)) | ~(DV_II_46_2_bit|DV_II_51_2_bit)); + mask &= ((((W[42]^(W[41]>>5))&(1<<1))-(1<<1)) | ~(DV_I_51_2_bit|DV_II_50_2_bit)); + mask &= ((((W[41]^(W[40]>>5))&(1<<1))-(1<<1)) | ~(DV_I_50_2_bit|DV_II_49_2_bit)); + if (mask & (DV_I_52_0_bit|DV_II_51_0_bit)) + mask &= ((((W[39]^(W[43]>>25))&(1<<4))-(1<<4)) | ~(DV_I_52_0_bit|DV_II_51_0_bit)); + if (mask & (DV_I_51_0_bit|DV_II_50_0_bit)) + mask &= ((((W[38]^(W[42]>>25))&(1<<4))-(1<<4)) | ~(DV_I_51_0_bit|DV_II_50_0_bit)); + if (mask & (DV_I_48_2_bit|DV_I_51_2_bit)) + mask &= ((0-((W[37]^(W[38]>>5))&(1<<1))) | ~(DV_I_48_2_bit|DV_I_51_2_bit)); + if (mask & (DV_I_50_0_bit|DV_II_49_0_bit)) + mask &= ((((W[37]^(W[41]>>25))&(1<<4))-(1<<4)) | ~(DV_I_50_0_bit|DV_II_49_0_bit)); + if (mask & (DV_II_52_0_bit|DV_II_54_0_bit)) + mask &= ((0-((W[36]^W[38])&(1<<4))) | ~(DV_II_52_0_bit|DV_II_54_0_bit)); + mask &= ((0-((W[35]^(W[36]>>5))&(1<<1))) | ~(DV_I_46_2_bit|DV_I_49_2_bit)); + if (mask & (DV_I_51_0_bit|DV_II_47_0_bit)) + mask &= ((((W[35]^(W[39]>>25))&(1<<3))-(1<<3)) | ~(DV_I_51_0_bit|DV_II_47_0_bit)); +if (mask) { + + if (mask & DV_I_43_0_bit) + if ( + !((W[61]^(W[62]>>5)) & (1<<1)) + || !(!((W[59]^(W[63]>>25)) & (1<<5))) + || !((W[58]^(W[63]>>30)) & (1<<0)) + ) mask &= ~DV_I_43_0_bit; + if (mask & DV_I_44_0_bit) + if ( + !((W[62]^(W[63]>>5)) & (1<<1)) + || !(!((W[60]^(W[64]>>25)) & (1<<5))) + || !((W[59]^(W[64]>>30)) & (1<<0)) + ) mask &= ~DV_I_44_0_bit; + if (mask & DV_I_46_2_bit) + mask &= ((~((W[40]^W[42])>>2)) | ~DV_I_46_2_bit); + if (mask & DV_I_47_2_bit) + if ( + !((W[62]^(W[63]>>5)) & (1<<2)) + || !(!((W[41]^W[43]) & (1<<6))) + ) mask &= ~DV_I_47_2_bit; + if (mask & DV_I_48_2_bit) + if ( + !((W[63]^(W[64]>>5)) & (1<<2)) + || !(!((W[48]^(W[49]<<5)) & (1<<6))) + ) mask &= ~DV_I_48_2_bit; + if (mask & DV_I_49_2_bit) + if ( + !(!((W[49]^(W[50]<<5)) & (1<<6))) + || !((W[42]^W[50]) & (1<<1)) + || !(!((W[39]^(W[40]<<5)) & (1<<6))) + || !((W[38]^W[40]) & (1<<1)) + ) mask &= ~DV_I_49_2_bit; + if (mask & DV_I_50_0_bit) + mask &= ((((W[36]^W[37])<<7)) | ~DV_I_50_0_bit); + if (mask & DV_I_50_2_bit) + mask &= ((((W[43]^W[51])<<11)) | ~DV_I_50_2_bit); + if (mask & DV_I_51_0_bit) + mask &= ((((W[37]^W[38])<<9)) | ~DV_I_51_0_bit); + if (mask & DV_I_51_2_bit) + if ( + !(!((W[51]^(W[52]<<5)) & (1<<6))) + || !(!((W[49]^W[51]) & (1<<6))) + || !(!((W[37]^(W[37]>>5)) & (1<<1))) + || !(!((W[35]^(W[39]>>25)) & (1<<5))) + ) mask &= ~DV_I_51_2_bit; + if (mask & DV_I_52_0_bit) + mask &= ((((W[38]^W[39])<<11)) | ~DV_I_52_0_bit); + if (mask & DV_II_46_2_bit) + mask &= ((((W[47]^W[51])<<17)) | ~DV_II_46_2_bit); + if (mask & DV_II_48_0_bit) + if ( + !(!((W[36]^(W[40]>>25)) & (1<<3))) + || !((W[35]^(W[40]<<2)) & (1<<30)) + ) mask &= ~DV_II_48_0_bit; + if (mask & DV_II_49_0_bit) + if ( + !(!((W[37]^(W[41]>>25)) & (1<<3))) + || !((W[36]^(W[41]<<2)) & (1<<30)) + ) mask &= ~DV_II_49_0_bit; + if (mask & DV_II_49_2_bit) + if ( + !(!((W[53]^(W[54]<<5)) & (1<<6))) + || !(!((W[51]^W[53]) & (1<<6))) + || !((W[50]^W[54]) & (1<<1)) + || !(!((W[45]^(W[46]<<5)) & (1<<6))) + || !(!((W[37]^(W[41]>>25)) & (1<<5))) + || !((W[36]^(W[41]>>30)) & (1<<0)) + ) mask &= ~DV_II_49_2_bit; + if (mask & DV_II_50_0_bit) + if ( + !((W[55]^W[58]) & (1<<29)) + || !(!((W[38]^(W[42]>>25)) & (1<<3))) + || !((W[37]^(W[42]<<2)) & (1<<30)) + ) mask &= ~DV_II_50_0_bit; + if (mask & DV_II_50_2_bit) + if ( + !(!((W[54]^(W[55]<<5)) & (1<<6))) + || !(!((W[52]^W[54]) & (1<<6))) + || !((W[51]^W[55]) & (1<<1)) + || !((W[45]^W[47]) & (1<<1)) + || !(!((W[38]^(W[42]>>25)) & (1<<5))) + || !((W[37]^(W[42]>>30)) & (1<<0)) + ) mask &= ~DV_II_50_2_bit; + if (mask & DV_II_51_0_bit) + if ( + !(!((W[39]^(W[43]>>25)) & (1<<3))) + || !((W[38]^(W[43]<<2)) & (1<<30)) + ) mask &= ~DV_II_51_0_bit; + if (mask & DV_II_51_2_bit) + if ( + !(!((W[55]^(W[56]<<5)) & (1<<6))) + || !(!((W[53]^W[55]) & (1<<6))) + || !((W[52]^W[56]) & (1<<1)) + || !((W[46]^W[48]) & (1<<1)) + || !(!((W[39]^(W[43]>>25)) & (1<<5))) + || !((W[38]^(W[43]>>30)) & (1<<0)) + ) mask &= ~DV_II_51_2_bit; + if (mask & DV_II_52_0_bit) + if ( + !(!((W[59]^W[60]) & (1<<29))) + || !(!((W[40]^(W[44]>>25)) & (1<<3))) + || !(!((W[40]^(W[44]>>25)) & (1<<4))) + || !((W[39]^(W[44]<<2)) & (1<<30)) + ) mask &= ~DV_II_52_0_bit; + if (mask & DV_II_53_0_bit) + if ( + !((W[58]^W[61]) & (1<<29)) + || !(!((W[57]^(W[61]>>25)) & (1<<4))) + || !(!((W[41]^(W[45]>>25)) & (1<<3))) + || !(!((W[41]^(W[45]>>25)) & (1<<4))) + ) mask &= ~DV_II_53_0_bit; + if (mask & DV_II_54_0_bit) + if ( + !(!((W[58]^(W[62]>>25)) & (1<<4))) + || !(!((W[42]^(W[46]>>25)) & (1<<3))) + || !(!((W[42]^(W[46]>>25)) & (1<<4))) + ) mask &= ~DV_II_54_0_bit; + if (mask & DV_II_55_0_bit) + if ( + !(!((W[59]^(W[63]>>25)) & (1<<4))) + || !(!((W[57]^(W[59]>>25)) & (1<<4))) + || !(!((W[43]^(W[47]>>25)) & (1<<3))) + || !(!((W[43]^(W[47]>>25)) & (1<<4))) + ) mask &= ~DV_II_55_0_bit; + if (mask & DV_II_56_0_bit) + if ( + !(!((W[60]^(W[64]>>25)) & (1<<4))) + || !(!((W[44]^(W[48]>>25)) & (1<<3))) + || !(!((W[44]^(W[48]>>25)) & (1<<4))) + ) mask &= ~DV_II_56_0_bit; +} + + dvmask[0]=mask; +} + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C +#endif diff --git a/src/util/hash/sha1/sha1dc/ubc_check.h b/src/util/hash/sha1/sha1dc/ubc_check.h new file mode 100644 index 000000000..d7e17dc73 --- /dev/null +++ b/src/util/hash/sha1/sha1dc/ubc_check.h @@ -0,0 +1,52 @@ +/*** +* Copyright 2017 Marc Stevens , Dan Shumow +* Distributed under the MIT Software License. +* See accompanying file LICENSE.txt or copy at +* https://opensource.org/licenses/MIT +***/ + +/* +// this file was generated by the 'parse_bitrel' program in the tools section +// using the data files from directory 'tools/data/3565' +// +// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check +// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper) +// dm[80] is the expanded message block XOR-difference defined by the DV +// testt is the step to do the recompression from for collision detection +// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check +// +// ubc_check takes as input an expanded message block and verifies the unavoidable bitconditions for all listed DVs +// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions for that DV have been met +// thus one needs to do the recompression check for each DV that has its bit set +*/ + +#ifndef SHA1DC_UBC_CHECK_H +#define SHA1DC_UBC_CHECK_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef SHA1DC_NO_STANDARD_INCLUDES +#include +#endif + +#define DVMASKSIZE 1 +typedef struct { int dvType; int dvK; int dvB; int testt; int maski; int maskb; uint32_t dm[80]; } dv_info_t; +extern dv_info_t sha1_dvs[]; +void ubc_check(const uint32_t W[80], uint32_t dvmask[DVMASKSIZE]); + +#define DOSTORESTATE58 +#define DOSTORESTATE65 + +#define CHECK_DVMASK(_DVMASK) (0 != _DVMASK[0]) + +#if defined(__cplusplus) +} +#endif + +#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H +#endif + +#endif diff --git a/src/util/hash/sha1/win32.c b/src/util/hash/sha1/win32.c new file mode 100644 index 000000000..b89dfbad8 --- /dev/null +++ b/src/util/hash/sha1/win32.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "win32.h" + +#include "runtime.h" + +#include +#include + +#define GIT_HASH_CNG_DLL_NAME "bcrypt.dll" + +/* BCRYPT_SHA1_ALGORITHM */ +#define GIT_HASH_CNG_HASH_TYPE L"SHA1" + +/* BCRYPT_OBJECT_LENGTH */ +#define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength" + +/* BCRYPT_HASH_REUSEABLE_FLAGS */ +#define GIT_HASH_CNG_HASH_REUSABLE 0x00000020 + +static git_hash_prov hash_prov = {0}; + +/* Hash initialization */ + +/* Initialize CNG, if available */ +GIT_INLINE(int) hash_cng_prov_init(void) +{ + char dll_path[MAX_PATH]; + DWORD dll_path_len, size_len; + + /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */ + if (!git_has_win32_version(6, 0, 1)) { + git_error_set(GIT_ERROR_SHA1, "CryptoNG is not supported on this platform"); + return -1; + } + + /* Load bcrypt.dll explicitly from the system directory */ + if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || + dll_path_len > MAX_PATH || + StringCchCat(dll_path, MAX_PATH, "\\") < 0 || + StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 || + (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) { + git_error_set(GIT_ERROR_SHA1, "CryptoNG library could not be loaded"); + return -1; + } + + /* Load the function addresses */ + if ((hash_prov.prov.cng.open_algorithm_provider = (hash_win32_cng_open_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptOpenAlgorithmProvider")) == NULL || + (hash_prov.prov.cng.get_property = (hash_win32_cng_get_property_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptGetProperty")) == NULL || + (hash_prov.prov.cng.create_hash = (hash_win32_cng_create_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCreateHash")) == NULL || + (hash_prov.prov.cng.finish_hash = (hash_win32_cng_finish_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptFinishHash")) == NULL || + (hash_prov.prov.cng.hash_data = (hash_win32_cng_hash_data_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptHashData")) == NULL || + (hash_prov.prov.cng.destroy_hash = (hash_win32_cng_destroy_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptDestroyHash")) == NULL || + (hash_prov.prov.cng.close_algorithm_provider = (hash_win32_cng_close_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCloseAlgorithmProvider")) == NULL) { + FreeLibrary(hash_prov.prov.cng.dll); + + git_error_set(GIT_ERROR_OS, "CryptoNG functions could not be loaded"); + return -1; + } + + /* Load the SHA1 algorithm */ + if (hash_prov.prov.cng.open_algorithm_provider(&hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0) { + FreeLibrary(hash_prov.prov.cng.dll); + + git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized"); + return -1; + } + + /* Get storage space for the hash object */ + if (hash_prov.prov.cng.get_property(hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_prov.prov.cng.hash_object_size, sizeof(DWORD), &size_len, 0) < 0) { + hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); + FreeLibrary(hash_prov.prov.cng.dll); + + git_error_set(GIT_ERROR_OS, "algorithm handle could not be found"); + return -1; + } + + hash_prov.type = CNG; + return 0; +} + +GIT_INLINE(void) hash_cng_prov_shutdown(void) +{ + hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); + FreeLibrary(hash_prov.prov.cng.dll); + + hash_prov.type = INVALID; +} + +/* Initialize CryptoAPI */ +GIT_INLINE(int) hash_cryptoapi_prov_init() +{ + if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + git_error_set(GIT_ERROR_OS, "legacy hash context could not be started"); + return -1; + } + + hash_prov.type = CRYPTOAPI; + return 0; +} + +GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void) +{ + CryptReleaseContext(hash_prov.prov.cryptoapi.handle, 0); + + hash_prov.type = INVALID; +} + +static void sha1_shutdown(void) +{ + if (hash_prov.type == CNG) + hash_cng_prov_shutdown(); + else if(hash_prov.type == CRYPTOAPI) + hash_cryptoapi_prov_shutdown(); +} + +int git_hash_sha1_global_init(void) +{ + int error = 0; + + if (hash_prov.type != INVALID) + return 0; + + if ((error = hash_cng_prov_init()) < 0) + error = hash_cryptoapi_prov_init(); + + if (!error) + error = git_runtime_shutdown_register(sha1_shutdown); + + return error; +} + +/* CryptoAPI: available in Windows XP and newer */ + +GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_sha1_ctx *ctx) +{ + ctx->type = CRYPTOAPI; + ctx->prov = &hash_prov; + + return git_hash_sha1_init(ctx); +} + +GIT_INLINE(int) hash_cryptoapi_init(git_hash_sha1_ctx *ctx) +{ + if (ctx->ctx.cryptoapi.valid) + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); + + if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) { + ctx->ctx.cryptoapi.valid = 0; + git_error_set(GIT_ERROR_OS, "legacy hash implementation could not be created"); + return -1; + } + + ctx->ctx.cryptoapi.valid = 1; + return 0; +} + +GIT_INLINE(int) hash_cryptoapi_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) +{ + const BYTE *data = (BYTE *)_data; + + GIT_ASSERT(ctx->ctx.cryptoapi.valid); + + while (len > 0) { + DWORD chunk = (len > MAXDWORD) ? MAXDWORD : (DWORD)len; + + if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, data, chunk, 0)) { + git_error_set(GIT_ERROR_OS, "legacy hash data could not be updated"); + return -1; + } + + data += chunk; + len -= chunk; + } + + return 0; +} + +GIT_INLINE(int) hash_cryptoapi_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + DWORD len = GIT_HASH_SHA1_SIZE; + int error = 0; + + GIT_ASSERT(ctx->ctx.cryptoapi.valid); + + if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out, &len, 0)) { + git_error_set(GIT_ERROR_OS, "legacy hash data could not be finished"); + error = -1; + } + + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); + ctx->ctx.cryptoapi.valid = 0; + + return error; +} + +GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_sha1_ctx *ctx) +{ + if (ctx->ctx.cryptoapi.valid) + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); +} + +/* CNG: Available in Windows Server 2008 and newer */ + +GIT_INLINE(int) hash_ctx_cng_init(git_hash_sha1_ctx *ctx) +{ + if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL) + return -1; + + if (hash_prov.prov.cng.create_hash(hash_prov.prov.cng.handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_prov.prov.cng.hash_object_size, NULL, 0, 0) < 0) { + git__free(ctx->ctx.cng.hash_object); + + git_error_set(GIT_ERROR_OS, "hash implementation could not be created"); + return -1; + } + + ctx->type = CNG; + ctx->prov = &hash_prov; + + return 0; +} + +GIT_INLINE(int) hash_cng_init(git_hash_sha1_ctx *ctx) +{ + BYTE hash[GIT_OID_RAWSZ]; + + if (!ctx->ctx.cng.updated) + return 0; + + /* CNG needs to be finished to restart */ + if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0) { + git_error_set(GIT_ERROR_OS, "hash implementation could not be finished"); + return -1; + } + + ctx->ctx.cng.updated = 0; + + return 0; +} + +GIT_INLINE(int) hash_cng_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) +{ + PBYTE data = (PBYTE)_data; + + while (len > 0) { + ULONG chunk = (len > ULONG_MAX) ? ULONG_MAX : (ULONG)len; + + if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, data, chunk, 0) < 0) { + git_error_set(GIT_ERROR_OS, "hash could not be updated"); + return -1; + } + + data += chunk; + len -= chunk; + } + + return 0; +} + +GIT_INLINE(int) hash_cng_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out, GIT_HASH_SHA1_SIZE, 0) < 0) { + git_error_set(GIT_ERROR_OS, "hash could not be finished"); + return -1; + } + + ctx->ctx.cng.updated = 0; + + return 0; +} + +GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_sha1_ctx *ctx) +{ + ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle); + git__free(ctx->ctx.cng.hash_object); +} + +/* Indirection between CryptoAPI and CNG */ + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + int error = 0; + + GIT_ASSERT_ARG(ctx); + + /* + * When compiled with GIT_THREADS, the global hash_prov data is + * initialized with git_libgit2_init. Otherwise, it must be initialized + * at first use. + */ + if (hash_prov.type == INVALID && (error = git_hash_sha1_global_init()) < 0) + return error; + + memset(ctx, 0x0, sizeof(git_hash_sha1_ctx)); + + return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + GIT_ASSERT_ARG(ctx->type); + return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx); +} + +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) +{ + GIT_ASSERT_ARG(ctx); + GIT_ASSERT_ARG(ctx->type); + return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len); +} + +int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) +{ + GIT_ASSERT_ARG(ctx); + GIT_ASSERT_ARG(ctx->type); + return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + if (!ctx) + return; + else if (ctx->type == CNG) + hash_ctx_cng_cleanup(ctx); + else if(ctx->type == CRYPTOAPI) + hash_ctx_cryptoapi_cleanup(ctx); +} diff --git a/src/util/hash/sha1/win32.h b/src/util/hash/sha1/win32.h new file mode 100644 index 000000000..791d20a42 --- /dev/null +++ b/src/util/hash/sha1/win32.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_win32_h__ +#define INCLUDE_hash_sha1_win32_h__ + +#include "hash/sha1.h" + +#include +#include + +enum hash_win32_prov_type { + INVALID = 0, + CRYPTOAPI, + CNG +}; + +/* + * CryptoAPI is available for hashing on Windows XP and newer. + */ + +struct hash_cryptoapi_prov { + HCRYPTPROV handle; +}; + +/* + * CNG (bcrypt.dll) is significantly more performant than CryptoAPI and is + * preferred, however it is only available on Windows 2008 and newer and + * must therefore be dynamically loaded, and we must inline constants that + * would not exist when building in pre-Windows 2008 environments. + */ + +/* Function declarations for CNG */ +typedef NTSTATUS (WINAPI *hash_win32_cng_open_algorithm_provider_fn)( + HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm, + LPCWSTR pszAlgId, + LPCWSTR pszImplementation, + DWORD dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_get_property_fn)( + HANDLE /* BCRYPT_HANDLE */ hObject, + LPCWSTR pszProperty, + PUCHAR pbOutput, + ULONG cbOutput, + ULONG *pcbResult, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_create_hash_fn)( + HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, + HANDLE /* BCRYPT_HASH_HANDLE */ *phHash, + PUCHAR pbHashObject, ULONG cbHashObject, + PUCHAR pbSecret, + ULONG cbSecret, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_finish_hash_fn)( + HANDLE /* BCRYPT_HASH_HANDLE */ hHash, + PUCHAR pbOutput, + ULONG cbOutput, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_hash_data_fn)( + HANDLE /* BCRYPT_HASH_HANDLE */ hHash, + PUCHAR pbInput, + ULONG cbInput, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_destroy_hash_fn)( + HANDLE /* BCRYPT_HASH_HANDLE */ hHash); + +typedef NTSTATUS (WINAPI *hash_win32_cng_close_algorithm_provider_fn)( + HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, + ULONG dwFlags); + +struct hash_cng_prov { + /* DLL for CNG */ + HINSTANCE dll; + + /* Function pointers for CNG */ + hash_win32_cng_open_algorithm_provider_fn open_algorithm_provider; + hash_win32_cng_get_property_fn get_property; + hash_win32_cng_create_hash_fn create_hash; + hash_win32_cng_finish_hash_fn finish_hash; + hash_win32_cng_hash_data_fn hash_data; + hash_win32_cng_destroy_hash_fn destroy_hash; + hash_win32_cng_close_algorithm_provider_fn close_algorithm_provider; + + HANDLE /* BCRYPT_ALG_HANDLE */ handle; + DWORD hash_object_size; +}; + +typedef struct { + enum hash_win32_prov_type type; + + union { + struct hash_cryptoapi_prov cryptoapi; + struct hash_cng_prov cng; + } prov; +} git_hash_prov; + +/* Hash contexts */ + +struct hash_cryptoapi_ctx { + bool valid; + HCRYPTHASH hash_handle; +}; + +struct hash_cng_ctx { + bool updated; + HANDLE /* BCRYPT_HASH_HANDLE */ hash_handle; + PBYTE hash_object; +}; + +struct git_hash_sha1_ctx { + enum hash_win32_prov_type type; + git_hash_prov *prov; + + union { + struct hash_cryptoapi_ctx cryptoapi; + struct hash_cng_ctx cng; + } ctx; +}; + +#endif diff --git a/src/util/integer.h b/src/util/integer.h new file mode 100644 index 000000000..63277177b --- /dev/null +++ b/src/util/integer.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_integer_h__ +#define INCLUDE_integer_h__ + +/** @return true if p fits into the range of a size_t */ +GIT_INLINE(int) git__is_sizet(int64_t p) +{ + size_t r = (size_t)p; + return p == (int64_t)r; +} + +/** @return true if p fits into the range of an ssize_t */ +GIT_INLINE(int) git__is_ssizet(size_t p) +{ + ssize_t r = (ssize_t)p; + return p == (size_t)r; +} + +/** @return true if p fits into the range of a uint16_t */ +GIT_INLINE(int) git__is_uint16(size_t p) +{ + uint16_t r = (uint16_t)p; + return p == (size_t)r; +} + +/** @return true if p fits into the range of a uint32_t */ +GIT_INLINE(int) git__is_uint32(size_t p) +{ + uint32_t r = (uint32_t)p; + return p == (size_t)r; +} + +/** @return true if p fits into the range of an unsigned long */ +GIT_INLINE(int) git__is_ulong(int64_t p) +{ + unsigned long r = (unsigned long)p; + return p == (int64_t)r; +} + +/** @return true if p fits into the range of an int */ +GIT_INLINE(int) git__is_int(int64_t p) +{ + int r = (int)p; + return p == (int64_t)r; +} + +/* Use clang/gcc compiler intrinsics whenever possible */ +#if (__has_builtin(__builtin_add_overflow) || \ + (defined(__GNUC__) && (__GNUC__ >= 5))) + +# if (SIZE_MAX == UINT_MAX) +# define git__add_sizet_overflow(out, one, two) \ + __builtin_uadd_overflow(one, two, out) +# define git__multiply_sizet_overflow(out, one, two) \ + __builtin_umul_overflow(one, two, out) +# elif (SIZE_MAX == ULONG_MAX) +# define git__add_sizet_overflow(out, one, two) \ + __builtin_uaddl_overflow(one, two, out) +# define git__multiply_sizet_overflow(out, one, two) \ + __builtin_umull_overflow(one, two, out) +# elif (SIZE_MAX == ULLONG_MAX) +# define git__add_sizet_overflow(out, one, two) \ + __builtin_uaddll_overflow(one, two, out) +# define git__multiply_sizet_overflow(out, one, two) \ + __builtin_umulll_overflow(one, two, out) +# else +# error compiler has add with overflow intrinsics but SIZE_MAX is unknown +# endif + +# define git__add_int_overflow(out, one, two) \ + __builtin_sadd_overflow(one, two, out) +# define git__sub_int_overflow(out, one, two) \ + __builtin_ssub_overflow(one, two, out) + +# define git__add_int64_overflow(out, one, two) \ + __builtin_add_overflow(one, two, out) + +/* clang on 32-bit systems produces an undefined reference to `__mulodi4`. */ +# if !defined(__clang__) || !defined(GIT_ARCH_32) +# define git__multiply_int64_overflow(out, one, two) \ + __builtin_mul_overflow(one, two, out) +# endif + +/* Use Microsoft's safe integer handling functions where available */ +#elif defined(_MSC_VER) + +# define ENABLE_INTSAFE_SIGNED_FUNCTIONS +# include + +# define git__add_sizet_overflow(out, one, two) \ + (SizeTAdd(one, two, out) != S_OK) +# define git__multiply_sizet_overflow(out, one, two) \ + (SizeTMult(one, two, out) != S_OK) + +#define git__add_int_overflow(out, one, two) \ + (IntAdd(one, two, out) != S_OK) +#define git__sub_int_overflow(out, one, two) \ + (IntSub(one, two, out) != S_OK) + +#define git__add_int64_overflow(out, one, two) \ + (LongLongAdd(one, two, out) != S_OK) +#define git__multiply_int64_overflow(out, one, two) \ + (LongLongMult(one, two, out) != S_OK) + +#else + +/** + * Sets `one + two` into `out`, unless the arithmetic would overflow. + * @return false if the result fits in a `size_t`, true on overflow. + */ +GIT_INLINE(bool) git__add_sizet_overflow(size_t *out, size_t one, size_t two) +{ + if (SIZE_MAX - one < two) + return true; + *out = one + two; + return false; +} + +/** + * Sets `one * two` into `out`, unless the arithmetic would overflow. + * @return false if the result fits in a `size_t`, true on overflow. + */ +GIT_INLINE(bool) git__multiply_sizet_overflow(size_t *out, size_t one, size_t two) +{ + if (one && SIZE_MAX / one < two) + return true; + *out = one * two; + return false; +} + +GIT_INLINE(bool) git__add_int_overflow(int *out, int one, int two) +{ + if ((two > 0 && one > (INT_MAX - two)) || + (two < 0 && one < (INT_MIN - two))) + return true; + *out = one + two; + return false; +} + +GIT_INLINE(bool) git__sub_int_overflow(int *out, int one, int two) +{ + if ((two > 0 && one < (INT_MIN + two)) || + (two < 0 && one > (INT_MAX + two))) + return true; + *out = one - two; + return false; +} + +GIT_INLINE(bool) git__add_int64_overflow(int64_t *out, int64_t one, int64_t two) +{ + if ((two > 0 && one > (INT64_MAX - two)) || + (two < 0 && one < (INT64_MIN - two))) + return true; + *out = one + two; + return false; +} + +#endif + +/* If we could not provide an intrinsic implementation for this, provide a (slow) fallback. */ +#if !defined(git__multiply_int64_overflow) +GIT_INLINE(bool) git__multiply_int64_overflow(int64_t *out, int64_t one, int64_t two) +{ + /* + * Detects whether `INT64_MAX < (one * two) || INT64_MIN > (one * two)`, + * without incurring in undefined behavior. That is done by performing the + * comparison with a division instead of a multiplication, which translates + * to `INT64_MAX / one < two || INT64_MIN / one > two`. Some caveats: + * + * - The comparison sign is inverted when both sides of the inequality are + * multiplied/divided by a negative number, so if `one < 0` the comparison + * needs to be flipped. + * - `INT64_MAX / -1` itself overflows (or traps), so that case should be + * avoided. + * - Since the overflow flag is defined as the discrepance between the result + * of performing the multiplication in a signed integer at twice the width + * of the operands, and the truncated+sign-extended version of that same + * result, there are four cases where the result is the opposite of what + * would be expected: + * * `INT64_MIN * -1` / `-1 * INT64_MIN` + * * `INT64_MIN * 1 / `1 * INT64_MIN` + */ + if (one && two) { + if (one > 0 && two > 0) { + if (INT64_MAX / one < two) + return true; + } else if (one < 0 && two < 0) { + if ((one == -1 && two == INT64_MIN) || + (two == -1 && one == INT64_MIN)) { + *out = INT64_MIN; + return false; + } + if (INT64_MAX / one > two) + return true; + } else if (one > 0 && two < 0) { + if ((one == 1 && two == INT64_MIN) || + (INT64_MIN / one > two)) + return true; + } else if (one == -1) { + if (INT64_MIN / two > one) + return true; + } else { + if ((one == INT64_MIN && two == 1) || + (INT64_MIN / one < two)) + return true; + } + } + *out = one * two; + return false; +} +#endif + +#endif diff --git a/src/util/khash.h b/src/util/khash.h new file mode 100644 index 000000000..c9b7f131f --- /dev/null +++ b/src/util/khash.h @@ -0,0 +1,615 @@ +/* The MIT License + + Copyright (c) 2008, 2009, 2011 by Attractive Chaos + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +/* + An example: + +#include "khash.h" +KHASH_MAP_INIT_INT(32, char) +int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; +} +*/ + +/* + 2013-05-02 (0.2.8): + + * Use quadratic probing. When the capacity is power of 2, stepping function + i*(i+1)/2 guarantees to traverse each bucket. It is better than double + hashing on cache performance and is more robust than linear probing. + + In theory, double hashing should be more robust than quadratic probing. + However, my implementation is probably not for large hash tables, because + the second hash function is closely tied to the first hash function, + which reduce the effectiveness of double hashing. + + Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php + + 2011-12-29 (0.2.7): + + * Minor code clean up; no actual effect. + + 2011-09-16 (0.2.6): + + * The capacity is a power of 2. This seems to dramatically improve the + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: + + - http://code.google.com/p/ulib/ + - http://nothings.org/computer/judy/ + + * Allow to optionally use linear probing which usually has better + performance for random input. Double hashing is still the default as it + is more robust to certain non-random input. + + * Added Wang's integer hash function (not used by default). This hash + function is more robust to certain non-random input. + + 2011-02-14 (0.2.5): + + * Allow to declare global functions. + + 2009-09-26 (0.2.4): + + * Improve portability + + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor +*/ + + +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +/*! + @header + + Generic hash table library. + */ + +#define AC_VERSION_KHASH_H "0.2.8" + +#include +#include +#include + +/* compiler specific configuration */ + +typedef uint32_t khint32_t; +typedef uint64_t khint64_t; + +#ifndef kh_inline +#ifdef _MSC_VER +#define kh_inline __inline +#elif defined(__GNUC__) +#define kh_inline __inline__ +#else +#define kh_inline +#endif +#endif /* kh_inline */ + +typedef khint32_t khint_t; +typedef khint_t khiter_t; + +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) + +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) + +#ifndef kroundup32 +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) +#endif + +#ifndef kcalloc +#define kcalloc(N,Z) calloc(N,Z) +#endif +#ifndef kmalloc +#define kmalloc(Z) malloc(Z) +#endif +#ifndef krealloc +#define krealloc(P,Z) realloc(P,Z) +#endif +#ifndef kreallocarray +#define kreallocarray(P,N,Z) ((SIZE_MAX - N < Z) ? NULL : krealloc(P, (N*Z))) +#endif +#ifndef kfree +#define kfree(P) free(P) +#endif + +static const double __ac_HASH_UPPER = 0.77; + +#define __KHASH_TYPE(name, khkey_t, khval_t) \ + typedef struct kh_##name##_s { \ + khint_t n_buckets, size, n_occupied, upper_bound; \ + khint32_t *flags; \ + khkey_t *keys; \ + khval_t *vals; \ + } kh_##name##_t; + +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ + extern kh_##name##_t *kh_init_##name(void); \ + extern void kh_destroy_##name(kh_##name##_t *h); \ + extern void kh_clear_##name(kh_##name##_t *h); \ + extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ + extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ + extern void kh_del_##name(kh_##name##_t *h, khint_t x); + +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + SCOPE kh_##name##_t *kh_init_##name(void) { \ + return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ + } \ + SCOPE void kh_destroy_##name(kh_##name##_t *h) \ + { \ + if (h) { \ + kfree((void *)h->keys); kfree(h->flags); \ + kfree((void *)h->vals); \ + kfree(h); \ + } \ + } \ + SCOPE void kh_clear_##name(kh_##name##_t *h) \ + { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ + { \ + if (h->n_buckets) { \ + khint_t k, i, last, mask, step = 0; \ + mask = h->n_buckets - 1; \ + k = __hash_func(key); i = k & mask; \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + i = (i + (++step)) & mask; \ + if (i == last) return h->n_buckets; \ + } \ + return __ac_iseither(h->flags, i)? h->n_buckets : i; \ + } else return 0; \ + } \ + SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ + { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ + khint32_t *new_flags = 0; \ + khint_t j = 1; \ + { \ + kroundup32(new_n_buckets); \ + if (new_n_buckets < 4) new_n_buckets = 4; \ + if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ + else { /* hash table size to be changed (shrink or expand); rehash */ \ + new_flags = (khint32_t*)kreallocarray(NULL, __ac_fsize(new_n_buckets), sizeof(khint32_t)); \ + if (!new_flags) return -1; \ + memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (h->n_buckets < new_n_buckets) { /* expand */ \ + khkey_t *new_keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ + if (!new_keys) { kfree(new_flags); return -1; } \ + h->keys = new_keys; \ + if (kh_is_map) { \ + khval_t *new_vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ + if (!new_vals) { kfree(new_flags); return -1; } \ + h->vals = new_vals; \ + } \ + } /* otherwise shrink */ \ + } \ + } \ + if (j) { /* rehashing is needed */ \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (__ac_iseither(h->flags, j) == 0) { \ + khkey_t key = h->keys[j]; \ + khval_t val; \ + khint_t new_mask; \ + new_mask = new_n_buckets - 1; \ + if (kh_is_map) val = h->vals[j]; \ + __ac_set_isdel_true(h->flags, j); \ + while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ + khint_t k, i, step = 0; \ + k = __hash_func(key); \ + i = k & new_mask; \ + while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ + __ac_set_isempty_false(new_flags, i); \ + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ + { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ + if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ + __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ + } else { /* write the element and jump out of the loop */ \ + h->keys[i] = key; \ + if (kh_is_map) h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ + h->keys = (khkey_t*)kreallocarray((void *)h->keys, new_n_buckets, sizeof(khkey_t)); \ + if (kh_is_map) h->vals = (khval_t*)kreallocarray((void *)h->vals, new_n_buckets, sizeof(khval_t)); \ + } \ + kfree(h->flags); /* free the working space */ \ + h->flags = new_flags; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ + } \ + return 0; \ + } \ + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ + { \ + khint_t x; \ + if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ + if (h->n_buckets > (h->size<<1)) { \ + if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ + *ret = -1; return h->n_buckets; \ + } \ + } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ + *ret = -1; return h->n_buckets; \ + } \ + } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ + { \ + khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ + x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ + if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ + else { \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (__ac_isdel(h->flags, i)) site = i; \ + i = (i + (++step)) & mask; \ + if (i == last) { x = site; break; } \ + } \ + if (x == h->n_buckets) { \ + if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ + else x = i; \ + } \ + } \ + } \ + if (__ac_isempty(h->flags, x)) { /* not present at all */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; ++h->n_occupied; \ + *ret = 1; \ + } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + *ret = 2; \ + } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ + return x; \ + } \ + SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ + { \ + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ + __ac_set_isdel_true(h->flags, x); \ + --h->size; \ + } \ + } + +#define KHASH_DECLARE(name, khkey_t, khval_t) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_PROTOTYPES(name, khkey_t, khval_t) + +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +/*! @function + @abstract Integer hash function + @param key The integer [khint32_t] + @return The hash value [khint_t] + */ +#define kh_int_hash_func(key) (khint32_t)(key) +/*! @function + @abstract Integer comparison function + */ +#define kh_int_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract 64-bit integer hash function + @param key The integer [khint64_t] + @return The hash value [khint_t] + */ +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) +/*! @function + @abstract 64-bit integer comparison function + */ +#define kh_int64_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract const char* hash function + @param s Pointer to a null terminated string + @return The hash value + */ +static kh_inline khint_t __ac_X31_hash_string(const char *s) +{ + khint_t h = (khint_t)*s; + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; + return h; +} +/*! @function + @abstract Another interface to const char* hash function + @param key Pointer to a null terminated string [const char*] + @return The hash value [khint_t] + */ +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +/*! @function + @abstract Const char* comparison function + */ +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) + +static kh_inline khint_t __ac_Wang_hash(khint_t key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} +#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other convenient macros... */ + +/*! + @abstract Type of the hash table. + @param name Name of the hash table [symbol] + */ +#define khash_t(name) kh_##name##_t + +/*! @function + @abstract Initiate a hash table. + @param name Name of the hash table [symbol] + @return Pointer to the hash table [khash_t(name)*] + */ +#define kh_init(name) kh_init_##name() + +/*! @function + @abstract Destroy a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_destroy(name, h) kh_destroy_##name(h) + +/*! @function + @abstract Reset a hash table without deallocating memory. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_clear(name, h) kh_clear_##name(h) + +/*! @function + @abstract Resize a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param s New size [khint_t] + */ +#define kh_resize(name, h, s) kh_resize_##name(h, s) + +/*! @function + @abstract Insert a key to the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @param r Extra return code: -1 if the operation failed; + 0 if the key is present in the hash table; + 1 if the bucket is empty (never used); 2 if the element in + the bucket has been deleted [int*] + @return Iterator to the inserted element [khint_t] + */ +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) + +/*! @function + @abstract Retrieve a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] + */ +#define kh_get(name, h, k) kh_get_##name(h, k) + +/*! @function + @abstract Remove a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Iterator to the element to be deleted [khint_t] + */ +#define kh_del(name, h, k) kh_del_##name(h, k) + +/*! @function + @abstract Test whether a bucket contains data. + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return 1 if containing data; 0 otherwise [int] + */ +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) + +/*! @function + @abstract Get key given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Key [type of keys] + */ +#define kh_key(h, x) ((h)->keys[x]) + +/*! @function + @abstract Get value given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Value [type of values] + @discussion For hash sets, calling this results in segfault. + */ +#define kh_val(h, x) ((h)->vals[x]) + +/*! @function + @abstract Alias of kh_val() + */ +#define kh_value(h, x) ((h)->vals[x]) + +/*! @function + @abstract Get the start iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The start iterator [khint_t] + */ +#define kh_begin(h) (khint_t)(0) + +/*! @function + @abstract Get the end iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The end iterator [khint_t] + */ +#define kh_end(h) ((h)->n_buckets) + +/*! @function + @abstract Get the number of elements in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of elements in the hash table [khint_t] + */ +#define kh_size(h) ((h)->size) + +/*! @function + @abstract Get the number of buckets in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of buckets in the hash table [khint_t] + */ +#define kh_n_buckets(h) ((h)->n_buckets) + +/*! @function + @abstract Iterate over the entries in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param kvar Variable to which key will be assigned + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (kvar) = kh_key(h,__i); \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/*! @function + @abstract Iterate over the values in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach_value(h, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/* More convenient interfaces */ + +/*! @function + @abstract Instantiate a hash set containing integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT(name) \ + KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT(name, khval_t) \ + KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT64(name) \ + KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT64(name, khval_t) \ + KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const char *kh_cstr_t; +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_STR(name) \ + KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_STR(name, khval_t) \ + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#endif /* __AC_KHASH_H */ diff --git a/src/util/map.h b/src/util/map.h new file mode 100644 index 000000000..c101e46f6 --- /dev/null +++ b/src/util/map.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_map_h__ +#define INCLUDE_map_h__ + +#include "git2_util.h" + + +/* p_mmap() prot values */ +#define GIT_PROT_NONE 0x0 +#define GIT_PROT_READ 0x1 +#define GIT_PROT_WRITE 0x2 +#define GIT_PROT_EXEC 0x4 + +/* git__mmmap() flags values */ +#define GIT_MAP_FILE 0 +#define GIT_MAP_SHARED 1 +#define GIT_MAP_PRIVATE 2 +#define GIT_MAP_TYPE 0xf +#define GIT_MAP_FIXED 0x10 + +#ifdef __amigaos4__ +#define MAP_FAILED 0 +#endif + +typedef struct { /* memory mapped buffer */ + void *data; /* data bytes */ + size_t len; /* data length */ +#ifdef GIT_WIN32 + HANDLE fmh; /* file mapping handle */ +#endif +} git_map; + +#define GIT_MMAP_VALIDATE(out, len, prot, flags) do { \ + GIT_ASSERT(out != NULL && len > 0); \ + GIT_ASSERT((prot & GIT_PROT_WRITE) || (prot & GIT_PROT_READ)); \ + GIT_ASSERT((flags & GIT_MAP_FIXED) == 0); } while (0) + +extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset); +extern int p_munmap(git_map *map); + +#endif diff --git a/src/util/net.c b/src/util/net.c new file mode 100644 index 000000000..b2236daf8 --- /dev/null +++ b/src/util/net.c @@ -0,0 +1,749 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "net.h" + +#include + +#include "posix.h" +#include "str.h" +#include "http_parser.h" +#include "runtime.h" + +#define DEFAULT_PORT_HTTP "80" +#define DEFAULT_PORT_HTTPS "443" +#define DEFAULT_PORT_GIT "9418" +#define DEFAULT_PORT_SSH "22" + +bool git_net_str_is_url(const char *str) +{ + const char *c; + + for (c = str; *c; c++) { + if (*c == ':' && *(c+1) == '/' && *(c+2) == '/') + return true; + + if ((*c < 'a' || *c > 'z') && + (*c < 'A' || *c > 'Z') && + (*c < '0' || *c > '9') && + (*c != '+' && *c != '-' && *c != '.')) + break; + } + + return false; +} + +static const char *default_port_for_scheme(const char *scheme) +{ + if (strcmp(scheme, "http") == 0) + return DEFAULT_PORT_HTTP; + else if (strcmp(scheme, "https") == 0) + return DEFAULT_PORT_HTTPS; + else if (strcmp(scheme, "git") == 0) + return DEFAULT_PORT_GIT; + else if (strcmp(scheme, "ssh") == 0 || + strcmp(scheme, "ssh+git") == 0 || + strcmp(scheme, "git+ssh") == 0) + return DEFAULT_PORT_SSH; + + return NULL; +} + +int git_net_url_dup(git_net_url *out, git_net_url *in) +{ + if (in->scheme) { + out->scheme = git__strdup(in->scheme); + GIT_ERROR_CHECK_ALLOC(out->scheme); + } + + if (in->host) { + out->host = git__strdup(in->host); + GIT_ERROR_CHECK_ALLOC(out->host); + } + + if (in->port) { + out->port = git__strdup(in->port); + GIT_ERROR_CHECK_ALLOC(out->port); + } + + if (in->path) { + out->path = git__strdup(in->path); + GIT_ERROR_CHECK_ALLOC(out->path); + } + + if (in->query) { + out->query = git__strdup(in->query); + GIT_ERROR_CHECK_ALLOC(out->query); + } + + if (in->username) { + out->username = git__strdup(in->username); + GIT_ERROR_CHECK_ALLOC(out->username); + } + + if (in->password) { + out->password = git__strdup(in->password); + GIT_ERROR_CHECK_ALLOC(out->password); + } + + return 0; +} + +int git_net_url_parse(git_net_url *url, const char *given) +{ + struct http_parser_url u = {0}; + bool has_scheme, has_host, has_port, has_path, has_query, has_userinfo; + git_str scheme = GIT_STR_INIT, + host = GIT_STR_INIT, + port = GIT_STR_INIT, + path = GIT_STR_INIT, + username = GIT_STR_INIT, + password = GIT_STR_INIT, + query = GIT_STR_INIT; + int error = GIT_EINVALIDSPEC; + + if (http_parser_parse_url(given, strlen(given), false, &u)) { + git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given); + goto done; + } + + has_scheme = !!(u.field_set & (1 << UF_SCHEMA)); + has_host = !!(u.field_set & (1 << UF_HOST)); + has_port = !!(u.field_set & (1 << UF_PORT)); + has_path = !!(u.field_set & (1 << UF_PATH)); + has_query = !!(u.field_set & (1 << UF_QUERY)); + has_userinfo = !!(u.field_set & (1 << UF_USERINFO)); + + if (has_scheme) { + const char *url_scheme = given + u.field_data[UF_SCHEMA].off; + size_t url_scheme_len = u.field_data[UF_SCHEMA].len; + git_str_put(&scheme, url_scheme, url_scheme_len); + git__strntolower(scheme.ptr, scheme.size); + } else { + git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given); + goto done; + } + + if (has_host) { + const char *url_host = given + u.field_data[UF_HOST].off; + size_t url_host_len = u.field_data[UF_HOST].len; + git_str_decode_percent(&host, url_host, url_host_len); + } + + if (has_port) { + const char *url_port = given + u.field_data[UF_PORT].off; + size_t url_port_len = u.field_data[UF_PORT].len; + git_str_put(&port, url_port, url_port_len); + } else { + const char *default_port = default_port_for_scheme(scheme.ptr); + + if (default_port == NULL) { + git_error_set(GIT_ERROR_NET, "unknown scheme for URL '%s'", given); + goto done; + } + + git_str_puts(&port, default_port); + } + + if (has_path) { + const char *url_path = given + u.field_data[UF_PATH].off; + size_t url_path_len = u.field_data[UF_PATH].len; + git_str_put(&path, url_path, url_path_len); + } else { + git_str_puts(&path, "/"); + } + + if (has_query) { + const char *url_query = given + u.field_data[UF_QUERY].off; + size_t url_query_len = u.field_data[UF_QUERY].len; + git_str_decode_percent(&query, url_query, url_query_len); + } + + if (has_userinfo) { + const char *url_userinfo = given + u.field_data[UF_USERINFO].off; + size_t url_userinfo_len = u.field_data[UF_USERINFO].len; + const char *colon = memchr(url_userinfo, ':', url_userinfo_len); + + if (colon) { + const char *url_username = url_userinfo; + size_t url_username_len = colon - url_userinfo; + const char *url_password = colon + 1; + size_t url_password_len = url_userinfo_len - (url_username_len + 1); + + git_str_decode_percent(&username, url_username, url_username_len); + git_str_decode_percent(&password, url_password, url_password_len); + } else { + git_str_decode_percent(&username, url_userinfo, url_userinfo_len); + } + } + + if (git_str_oom(&scheme) || + git_str_oom(&host) || + git_str_oom(&port) || + git_str_oom(&path) || + git_str_oom(&query) || + git_str_oom(&username) || + git_str_oom(&password)) + return -1; + + url->scheme = git_str_detach(&scheme); + url->host = git_str_detach(&host); + url->port = git_str_detach(&port); + url->path = git_str_detach(&path); + url->query = git_str_detach(&query); + url->username = git_str_detach(&username); + url->password = git_str_detach(&password); + + error = 0; + +done: + git_str_dispose(&scheme); + git_str_dispose(&host); + git_str_dispose(&port); + git_str_dispose(&path); + git_str_dispose(&query); + git_str_dispose(&username); + git_str_dispose(&password); + return error; +} + +static int scp_invalid(const char *message) +{ + git_error_set(GIT_ERROR_NET, "invalid scp-style path: %s", message); + return GIT_EINVALIDSPEC; +} + +static bool is_ipv6(const char *str) +{ + const char *c; + size_t colons = 0; + + if (*str++ != '[') + return false; + + for (c = str; *c; c++) { + if (*c == ':') + colons++; + + if (*c == ']') + return (colons > 1); + + if (*c != ':' && + (*c < '0' || *c > '9') && + (*c < 'a' || *c > 'f') && + (*c < 'A' || *c > 'F')) + return false; + } + + return false; +} + +static bool has_at(const char *str) +{ + const char *c; + + for (c = str; *c; c++) { + if (*c == '@') + return true; + + if (*c == ':') + break; + } + + return false; +} + +int git_net_url_parse_scp(git_net_url *url, const char *given) +{ + const char *default_port = default_port_for_scheme("ssh"); + const char *c, *user, *host, *port, *path = NULL; + size_t user_len = 0, host_len = 0, port_len = 0; + unsigned short bracket = 0; + + enum { + NONE, + USER, + HOST_START, HOST, HOST_END, + IPV6, IPV6_END, + PORT_START, PORT, PORT_END, + PATH_START + } state = NONE; + + memset(url, 0, sizeof(git_net_url)); + + for (c = given; *c && !path; c++) { + switch (state) { + case NONE: + switch (*c) { + case '@': + return scp_invalid("unexpected '@'"); + case ':': + return scp_invalid("unexpected ':'"); + case '[': + if (is_ipv6(c)) { + state = IPV6; + host = c; + } else if (bracket++ > 1) { + return scp_invalid("unexpected '['"); + } + break; + default: + if (has_at(c)) { + state = USER; + user = c; + } else { + state = HOST; + host = c; + } + break; + } + break; + + case USER: + if (*c == '@') { + user_len = (c - user); + state = HOST_START; + } + break; + + case HOST_START: + state = (*c == '[') ? IPV6 : HOST; + host = c; + break; + + case HOST: + if (*c == ':') { + host_len = (c - host); + state = bracket ? PORT_START : PATH_START; + } else if (*c == ']') { + if (bracket-- == 0) + return scp_invalid("unexpected ']'"); + + host_len = (c - host); + state = HOST_END; + } + break; + + case HOST_END: + if (*c != ':') + return scp_invalid("unexpected character after hostname"); + state = PATH_START; + break; + + case IPV6: + if (*c == ']') + state = IPV6_END; + break; + + case IPV6_END: + if (*c != ':') + return scp_invalid("unexpected character after ipv6 address"); + + host_len = (c - host); + state = bracket ? PORT_START : PATH_START; + break; + + case PORT_START: + port = c; + state = PORT; + break; + + case PORT: + if (*c == ']') { + if (bracket-- == 0) + return scp_invalid("unexpected ']'"); + + port_len = c - port; + state = PORT_END; + } + break; + + case PORT_END: + if (*c != ':') + return scp_invalid("unexpected character after ipv6 address"); + + state = PATH_START; + break; + + case PATH_START: + path = c; + break; + + default: + GIT_ASSERT("unhandled state"); + } + } + + if (!path) + return scp_invalid("path is required"); + + GIT_ERROR_CHECK_ALLOC(url->scheme = git__strdup("ssh")); + + if (user_len) + GIT_ERROR_CHECK_ALLOC(url->username = git__strndup(user, user_len)); + + GIT_ASSERT(host_len); + GIT_ERROR_CHECK_ALLOC(url->host = git__strndup(host, host_len)); + + if (port_len) + GIT_ERROR_CHECK_ALLOC(url->port = git__strndup(port, port_len)); + else + GIT_ERROR_CHECK_ALLOC(url->port = git__strdup(default_port)); + + GIT_ASSERT(path); + GIT_ERROR_CHECK_ALLOC(url->path = git__strdup(path)); + + return 0; +} + +int git_net_url_joinpath( + git_net_url *out, + git_net_url *one, + const char *two) +{ + git_str path = GIT_STR_INIT; + const char *query; + size_t one_len, two_len; + + git_net_url_dispose(out); + + if ((query = strchr(two, '?')) != NULL) { + two_len = query - two; + + if (*(++query) != '\0') { + out->query = git__strdup(query); + GIT_ERROR_CHECK_ALLOC(out->query); + } + } else { + two_len = strlen(two); + } + + /* Strip all trailing `/`s from the first path */ + one_len = one->path ? strlen(one->path) : 0; + while (one_len && one->path[one_len - 1] == '/') + one_len--; + + /* Strip all leading `/`s from the second path */ + while (*two == '/') { + two++; + two_len--; + } + + git_str_put(&path, one->path, one_len); + git_str_putc(&path, '/'); + git_str_put(&path, two, two_len); + + if (git_str_oom(&path)) + return -1; + + out->path = git_str_detach(&path); + + if (one->scheme) { + out->scheme = git__strdup(one->scheme); + GIT_ERROR_CHECK_ALLOC(out->scheme); + } + + if (one->host) { + out->host = git__strdup(one->host); + GIT_ERROR_CHECK_ALLOC(out->host); + } + + if (one->port) { + out->port = git__strdup(one->port); + GIT_ERROR_CHECK_ALLOC(out->port); + } + + if (one->username) { + out->username = git__strdup(one->username); + GIT_ERROR_CHECK_ALLOC(out->username); + } + + if (one->password) { + out->password = git__strdup(one->password); + GIT_ERROR_CHECK_ALLOC(out->password); + } + + return 0; +} + +/* + * Some servers strip the query parameters from the Location header + * when sending a redirect. Others leave it in place. + * Check for both, starting with the stripped case first, + * since it appears to be more common. + */ +static void remove_service_suffix( + git_net_url *url, + const char *service_suffix) +{ + const char *service_query = strchr(service_suffix, '?'); + size_t full_suffix_len = strlen(service_suffix); + size_t suffix_len = service_query ? + (size_t)(service_query - service_suffix) : full_suffix_len; + size_t path_len = strlen(url->path); + ssize_t truncate = -1; + + /* + * Check for a redirect without query parameters, + * like "/newloc/info/refs"' + */ + if (suffix_len && path_len >= suffix_len) { + size_t suffix_offset = path_len - suffix_len; + + if (git__strncmp(url->path + suffix_offset, service_suffix, suffix_len) == 0 && + (!service_query || git__strcmp(url->query, service_query + 1) == 0)) { + truncate = suffix_offset; + } + } + + /* + * If we haven't already found where to truncate to remove the + * suffix, check for a redirect with query parameters, like + * "/newloc/info/refs?service=git-upload-pack" + */ + if (truncate < 0 && git__suffixcmp(url->path, service_suffix) == 0) + truncate = path_len - full_suffix_len; + + /* Ensure we leave a minimum of '/' as the path */ + if (truncate == 0) + truncate++; + + if (truncate > 0) { + url->path[truncate] = '\0'; + + git__free(url->query); + url->query = NULL; + } +} + +int git_net_url_apply_redirect( + git_net_url *url, + const char *redirect_location, + bool allow_offsite, + const char *service_suffix) +{ + git_net_url tmp = GIT_NET_URL_INIT; + int error = 0; + + GIT_ASSERT(url); + GIT_ASSERT(redirect_location); + + if (redirect_location[0] == '/') { + git__free(url->path); + + if ((url->path = git__strdup(redirect_location)) == NULL) { + error = -1; + goto done; + } + } else { + git_net_url *original = url; + + if ((error = git_net_url_parse(&tmp, redirect_location)) < 0) + goto done; + + /* Validate that this is a legal redirection */ + + if (original->scheme && + strcmp(original->scheme, tmp.scheme) != 0 && + strcmp(tmp.scheme, "https") != 0) { + git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", + original->scheme, tmp.scheme); + + error = -1; + goto done; + } + + if (original->host && + !allow_offsite && + git__strcasecmp(original->host, tmp.host) != 0) { + git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", + original->host, tmp.host); + + error = -1; + goto done; + } + + git_net_url_swap(url, &tmp); + } + + /* Remove the service suffix if it was given to us */ + if (service_suffix) + remove_service_suffix(url, service_suffix); + +done: + git_net_url_dispose(&tmp); + return error; +} + +bool git_net_url_valid(git_net_url *url) +{ + return (url->host && url->port && url->path); +} + +bool git_net_url_is_default_port(git_net_url *url) +{ + const char *default_port; + + if ((default_port = default_port_for_scheme(url->scheme)) != NULL) + return (strcmp(url->port, default_port) == 0); + else + return false; +} + +bool git_net_url_is_ipv6(git_net_url *url) +{ + return (strchr(url->host, ':') != NULL); +} + +void git_net_url_swap(git_net_url *a, git_net_url *b) +{ + git_net_url tmp = GIT_NET_URL_INIT; + + memcpy(&tmp, a, sizeof(git_net_url)); + memcpy(a, b, sizeof(git_net_url)); + memcpy(b, &tmp, sizeof(git_net_url)); +} + +int git_net_url_fmt(git_str *buf, git_net_url *url) +{ + GIT_ASSERT_ARG(url); + GIT_ASSERT_ARG(url->scheme); + GIT_ASSERT_ARG(url->host); + + git_str_puts(buf, url->scheme); + git_str_puts(buf, "://"); + + if (url->username) { + git_str_puts(buf, url->username); + + if (url->password) { + git_str_puts(buf, ":"); + git_str_puts(buf, url->password); + } + + git_str_putc(buf, '@'); + } + + git_str_puts(buf, url->host); + + if (url->port && !git_net_url_is_default_port(url)) { + git_str_putc(buf, ':'); + git_str_puts(buf, url->port); + } + + git_str_puts(buf, url->path ? url->path : "/"); + + if (url->query) { + git_str_putc(buf, '?'); + git_str_puts(buf, url->query); + } + + return git_str_oom(buf) ? -1 : 0; +} + +int git_net_url_fmt_path(git_str *buf, git_net_url *url) +{ + git_str_puts(buf, url->path ? url->path : "/"); + + if (url->query) { + git_str_putc(buf, '?'); + git_str_puts(buf, url->query); + } + + return git_str_oom(buf) ? -1 : 0; +} + +static bool matches_pattern( + git_net_url *url, + const char *pattern, + size_t pattern_len) +{ + const char *domain, *port = NULL, *colon; + size_t host_len, domain_len, port_len = 0, wildcard = 0; + + GIT_UNUSED(url); + GIT_UNUSED(pattern); + + if (!pattern_len) + return false; + else if (pattern_len == 1 && pattern[0] == '*') + return true; + else if (pattern_len > 1 && pattern[0] == '*' && pattern[1] == '.') + wildcard = 2; + else if (pattern[0] == '.') + wildcard = 1; + + domain = pattern + wildcard; + domain_len = pattern_len - wildcard; + + if ((colon = memchr(domain, ':', domain_len)) != NULL) { + domain_len = colon - domain; + port = colon + 1; + port_len = pattern_len - wildcard - domain_len - 1; + } + + /* A pattern's port *must* match if it's specified */ + if (port_len && git__strlcmp(url->port, port, port_len) != 0) + return false; + + /* No wildcard? Host must match exactly. */ + if (!wildcard) + return !git__strlcmp(url->host, domain, domain_len); + + /* Wildcard: ensure there's (at least) a suffix match */ + if ((host_len = strlen(url->host)) < domain_len || + memcmp(url->host + (host_len - domain_len), domain, domain_len)) + return false; + + /* The pattern is *.domain and the host is simply domain */ + if (host_len == domain_len) + return true; + + /* The pattern is *.domain and the host is foo.domain */ + return (url->host[host_len - domain_len - 1] == '.'); +} + +bool git_net_url_matches_pattern(git_net_url *url, const char *pattern) +{ + return matches_pattern(url, pattern, strlen(pattern)); +} + +bool git_net_url_matches_pattern_list( + git_net_url *url, + const char *pattern_list) +{ + const char *pattern, *pattern_end, *sep; + + for (pattern = pattern_list; + pattern && *pattern; + pattern = sep ? sep + 1 : NULL) { + sep = strchr(pattern, ','); + pattern_end = sep ? sep : strchr(pattern, '\0'); + + if (matches_pattern(url, pattern, (pattern_end - pattern))) + return true; + } + + return false; +} + +void git_net_url_dispose(git_net_url *url) +{ + if (url->username) + git__memzero(url->username, strlen(url->username)); + + if (url->password) + git__memzero(url->password, strlen(url->password)); + + git__free(url->scheme); url->scheme = NULL; + git__free(url->host); url->host = NULL; + git__free(url->port); url->port = NULL; + git__free(url->path); url->path = NULL; + git__free(url->query); url->query = NULL; + git__free(url->username); url->username = NULL; + git__free(url->password); url->password = NULL; +} diff --git a/src/util/net.h b/src/util/net.h new file mode 100644 index 000000000..88030a952 --- /dev/null +++ b/src/util/net.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_net_h__ +#define INCLUDE_net_h__ + +#include "git2_util.h" + +typedef struct git_net_url { + char *scheme; + char *host; + char *port; + char *path; + char *query; + char *username; + char *password; +} git_net_url; + +#define GIT_NET_URL_INIT { NULL } + +/** Is a given string a url? */ +extern bool git_net_str_is_url(const char *str); + +/** Duplicate a URL */ +extern int git_net_url_dup(git_net_url *out, git_net_url *in); + +/** Parses a string containing a URL into a structure. */ +extern int git_net_url_parse(git_net_url *url, const char *str); + +/** Parses a string containing an SCP style path into a URL structure. */ +extern int git_net_url_parse_scp(git_net_url *url, const char *str); + +/** Appends a path and/or query string to the given URL */ +extern int git_net_url_joinpath( + git_net_url *out, + git_net_url *in, + const char *path); + +/** Ensures that a URL is minimally valid (contains a host, port and path) */ +extern bool git_net_url_valid(git_net_url *url); + +/** Returns true if the URL is on the default port. */ +extern bool git_net_url_is_default_port(git_net_url *url); + +/** Returns true if the host portion of the URL is an ipv6 address. */ +extern bool git_net_url_is_ipv6(git_net_url *url); + +/* Applies a redirect to the URL with a git-aware service suffix. */ +extern int git_net_url_apply_redirect( + git_net_url *url, + const char *redirect_location, + bool allow_offsite, + const char *service_suffix); + +/** Swaps the contents of one URL for another. */ +extern void git_net_url_swap(git_net_url *a, git_net_url *b); + +/** Places the URL into the given buffer. */ +extern int git_net_url_fmt(git_str *out, git_net_url *url); + +/** Place the path and query string into the given buffer. */ +extern int git_net_url_fmt_path(git_str *buf, git_net_url *url); + +/** Determines if the url matches given pattern or pattern list */ +extern bool git_net_url_matches_pattern( + git_net_url *url, + const char *pattern); +extern bool git_net_url_matches_pattern_list( + git_net_url *url, + const char *pattern_list); + +/** Disposes the contents of the structure. */ +extern void git_net_url_dispose(git_net_url *url); + +#endif diff --git a/src/util/pool.c b/src/util/pool.c new file mode 100644 index 000000000..16ffa398d --- /dev/null +++ b/src/util/pool.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "pool.h" + +#include "posix.h" +#ifndef GIT_WIN32 +#include +#endif + +struct git_pool_page { + git_pool_page *next; + size_t size; + size_t avail; + GIT_ALIGN(char data[GIT_FLEX_ARRAY], 8); +}; + +static void *pool_alloc_page(git_pool *pool, size_t size); + +#ifndef GIT_DEBUG_POOL + +static size_t system_page_size = 0; + +int git_pool_global_init(void) +{ + if (git__page_size(&system_page_size) < 0) + system_page_size = 4096; + /* allow space for malloc overhead */ + system_page_size -= (2 * sizeof(void *)) + sizeof(git_pool_page); + return 0; +} + +int git_pool_init(git_pool *pool, size_t item_size) +{ + GIT_ASSERT_ARG(pool); + GIT_ASSERT_ARG(item_size >= 1); + + memset(pool, 0, sizeof(git_pool)); + pool->item_size = item_size; + pool->page_size = system_page_size; + + return 0; +} + +void git_pool_clear(git_pool *pool) +{ + git_pool_page *scan, *next; + + for (scan = pool->pages; scan != NULL; scan = next) { + next = scan->next; + git__free(scan); + } + + pool->pages = NULL; +} + +static void *pool_alloc_page(git_pool *pool, size_t size) +{ + git_pool_page *page; + const size_t new_page_size = (size <= pool->page_size) ? pool->page_size : size; + size_t alloc_size; + + if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, new_page_size, sizeof(git_pool_page)) || + !(page = git__malloc(alloc_size))) + return NULL; + + page->size = new_page_size; + page->avail = new_page_size - size; + page->next = pool->pages; + + pool->pages = page; + + return page->data; +} + +static void *pool_alloc(git_pool *pool, size_t size) +{ + git_pool_page *page = pool->pages; + void *ptr = NULL; + + if (!page || page->avail < size) + return pool_alloc_page(pool, size); + + ptr = &page->data[page->size - page->avail]; + page->avail -= size; + + return ptr; +} + +uint32_t git_pool__open_pages(git_pool *pool) +{ + uint32_t ct = 0; + git_pool_page *scan; + for (scan = pool->pages; scan != NULL; scan = scan->next) ct++; + return ct; +} + +bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) +{ + git_pool_page *scan; + for (scan = pool->pages; scan != NULL; scan = scan->next) + if ((void *)scan->data <= ptr && + (void *)(((char *)scan->data) + scan->size) > ptr) + return true; + return false; +} + +#else + +int git_pool_global_init(void) +{ + return 0; +} + +static int git_pool__ptr_cmp(const void * a, const void * b) +{ + if(a > b) { + return 1; + } + if(a < b) { + return -1; + } + else { + return 0; + } +} + +int git_pool_init(git_pool *pool, size_t item_size) +{ + GIT_ASSERT_ARG(pool); + GIT_ASSERT_ARG(item_size >= 1); + + memset(pool, 0, sizeof(git_pool)); + pool->item_size = item_size; + pool->page_size = git_pool__system_page_size(); + git_vector_init(&pool->allocations, 100, git_pool__ptr_cmp); + + return 0; +} + +void git_pool_clear(git_pool *pool) +{ + git_vector_free_deep(&pool->allocations); +} + +static void *pool_alloc(git_pool *pool, size_t size) { + void *ptr = NULL; + if((ptr = git__malloc(size)) == NULL) { + return NULL; + } + git_vector_insert_sorted(&pool->allocations, ptr, NULL); + return ptr; +} + +bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) +{ + size_t pos; + return git_vector_bsearch(&pos, &pool->allocations, ptr) != GIT_ENOTFOUND; +} +#endif + +void git_pool_swap(git_pool *a, git_pool *b) +{ + git_pool temp; + + if (a == b) + return; + + memcpy(&temp, a, sizeof(temp)); + memcpy(a, b, sizeof(temp)); + memcpy(b, &temp, sizeof(temp)); +} + +static size_t alloc_size(git_pool *pool, size_t count) +{ + const size_t align = sizeof(void *) - 1; + + if (pool->item_size > 1) { + const size_t item_size = (pool->item_size + align) & ~align; + return item_size * count; + } + + return (count + align) & ~align; +} + +void *git_pool_malloc(git_pool *pool, size_t items) +{ + return pool_alloc(pool, alloc_size(pool, items)); +} + +void *git_pool_mallocz(git_pool *pool, size_t items) +{ + const size_t size = alloc_size(pool, items); + void *ptr = pool_alloc(pool, size); + if (ptr) + memset(ptr, 0x0, size); + return ptr; +} + +char *git_pool_strndup(git_pool *pool, const char *str, size_t n) +{ + char *ptr = NULL; + + GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(str, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); + + if (n == SIZE_MAX) + return NULL; + + if ((ptr = git_pool_malloc(pool, (n + 1))) != NULL) { + memcpy(ptr, str, n); + ptr[n] = '\0'; + } + + return ptr; +} + +char *git_pool_strdup(git_pool *pool, const char *str) +{ + GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(str, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); + + return git_pool_strndup(pool, str, strlen(str)); +} + +char *git_pool_strdup_safe(git_pool *pool, const char *str) +{ + return str ? git_pool_strdup(pool, str) : NULL; +} + +char *git_pool_strcat(git_pool *pool, const char *a, const char *b) +{ + void *ptr; + size_t len_a, len_b, total; + + GIT_ASSERT_ARG_WITH_RETVAL(pool, NULL); + GIT_ASSERT_ARG_WITH_RETVAL(pool->item_size == sizeof(char), NULL); + + len_a = a ? strlen(a) : 0; + len_b = b ? strlen(b) : 0; + + if (GIT_ADD_SIZET_OVERFLOW(&total, len_a, len_b) || + GIT_ADD_SIZET_OVERFLOW(&total, total, 1)) + return NULL; + + if ((ptr = git_pool_malloc(pool, total)) != NULL) { + if (len_a) + memcpy(ptr, a, len_a); + if (len_b) + memcpy(((char *)ptr) + len_a, b, len_b); + *(((char *)ptr) + len_a + len_b) = '\0'; + } + return ptr; +} diff --git a/src/util/pool.h b/src/util/pool.h new file mode 100644 index 000000000..0238431b0 --- /dev/null +++ b/src/util/pool.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_pool_h__ +#define INCLUDE_pool_h__ + +#include "git2_util.h" + +#include "vector.h" + +typedef struct git_pool_page git_pool_page; + +#ifndef GIT_DEBUG_POOL +/** + * Chunked allocator. + * + * A `git_pool` can be used when you want to cheaply allocate + * multiple items of the same type and are willing to free them + * all together with a single call. The two most common cases + * are a set of fixed size items (such as lots of OIDs) or a + * bunch of strings. + * + * Internally, a `git_pool` allocates pages of memory and then + * deals out blocks from the trailing unused portion of each page. + * The pages guarantee that the number of actual allocations done + * will be much smaller than the number of items needed. + * + * For examples of how to set up a `git_pool` see `git_pool_init`. + */ +typedef struct { + git_pool_page *pages; /* allocated pages */ + size_t item_size; /* size of single alloc unit in bytes */ + size_t page_size; /* size of page in bytes */ +} git_pool; + +#define GIT_POOL_INIT { NULL, 0, 0 } + +#else + +/** + * Debug chunked allocator. + * + * Acts just like `git_pool` but instead of actually pooling allocations it + * passes them through to `git__malloc`. This makes it possible to easily debug + * systems that use `git_pool` using valgrind. + * + * In order to track allocations during the lifetime of the pool we use a + * `git_vector`. When the pool is deallocated everything in the vector is + * freed. + * + * `API is exactly the same as the standard `git_pool` with one exception. + * Since we aren't allocating pages to hand out in chunks we can't easily + * implement `git_pool__open_pages`. + */ +typedef struct { + git_vector allocations; + size_t item_size; + size_t page_size; +} git_pool; + +#define GIT_POOL_INIT { GIT_VECTOR_INIT, 0, 0 } + +#endif + +/** + * Initialize a pool. + * + * To allocation strings, use like this: + * + * git_pool_init(&string_pool, 1); + * my_string = git_pool_strdup(&string_pool, your_string); + * + * To allocate items of fixed size, use like this: + * + * git_pool_init(&pool, sizeof(item)); + * my_item = git_pool_malloc(&pool, 1); + * + * Of course, you can use this in other ways, but those are the + * two most common patterns. + */ +extern int git_pool_init(git_pool *pool, size_t item_size); + +/** + * Free all items in pool + */ +extern void git_pool_clear(git_pool *pool); + +/** + * Swap two pools with one another + */ +extern void git_pool_swap(git_pool *a, git_pool *b); + +/** + * Allocate space for one or more items from a pool. + */ +extern void *git_pool_malloc(git_pool *pool, size_t items); +extern void *git_pool_mallocz(git_pool *pool, size_t items); + +/** + * Allocate space and duplicate string data into it. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n); + +/** + * Allocate space and duplicate a string into it. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strdup(git_pool *pool, const char *str); + +/** + * Allocate space and duplicate a string into it, NULL is no error. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strdup_safe(git_pool *pool, const char *str); + +/** + * Allocate space for the concatenation of two strings. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b); + +/* + * Misc utilities + */ +#ifndef GIT_DEBUG_POOL +extern uint32_t git_pool__open_pages(git_pool *pool); +#endif +extern bool git_pool__ptr_in_pool(git_pool *pool, void *ptr); + +/** + * This function is being called by our global setup routines to + * initialize the system pool size. + * + * @return 0 on success, <0 on failure + */ +extern int git_pool_global_init(void); + +#endif diff --git a/src/util/posix.c b/src/util/posix.c new file mode 100644 index 000000000..b1f85dc94 --- /dev/null +++ b/src/util/posix.c @@ -0,0 +1,303 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "posix.h" + +#include "fs_path.h" +#include +#include + +size_t p_fsync__cnt = 0; + +#ifndef GIT_WIN32 + +#ifdef NO_ADDRINFO + +int p_getaddrinfo( + const char *host, + const char *port, + struct addrinfo *hints, + struct addrinfo **info) +{ + struct addrinfo *ainfo, *ai; + int p = 0; + + GIT_UNUSED(hints); + + if ((ainfo = git__malloc(sizeof(struct addrinfo))) == NULL) + return -1; + + if ((ainfo->ai_hostent = gethostbyname(host)) == NULL) { + git__free(ainfo); + return -2; + } + + ainfo->ai_servent = getservbyname(port, 0); + + if (ainfo->ai_servent) + ainfo->ai_port = ainfo->ai_servent->s_port; + else + ainfo->ai_port = htons(atol(port)); + + memcpy(&ainfo->ai_addr_in.sin_addr, + ainfo->ai_hostent->h_addr_list[0], + ainfo->ai_hostent->h_length); + + ainfo->ai_protocol = 0; + ainfo->ai_socktype = hints->ai_socktype; + ainfo->ai_family = ainfo->ai_hostent->h_addrtype; + ainfo->ai_addr_in.sin_family = ainfo->ai_family; + ainfo->ai_addr_in.sin_port = ainfo->ai_port; + ainfo->ai_addr = (struct addrinfo *)&ainfo->ai_addr_in; + ainfo->ai_addrlen = sizeof(struct sockaddr_in); + + *info = ainfo; + + if (ainfo->ai_hostent->h_addr_list[1] == NULL) { + ainfo->ai_next = NULL; + return 0; + } + + ai = ainfo; + + for (p = 1; ainfo->ai_hostent->h_addr_list[p] != NULL; p++) { + if (!(ai->ai_next = git__malloc(sizeof(struct addrinfo)))) { + p_freeaddrinfo(ainfo); + return -1; + } + memcpy(ai->ai_next, ainfo, sizeof(struct addrinfo)); + memcpy(&ai->ai_next->ai_addr_in.sin_addr, + ainfo->ai_hostent->h_addr_list[p], + ainfo->ai_hostent->h_length); + ai->ai_next->ai_addr = (struct addrinfo *)&ai->ai_next->ai_addr_in; + ai = ai->ai_next; + } + + ai->ai_next = NULL; + return 0; +} + +void p_freeaddrinfo(struct addrinfo *info) +{ + struct addrinfo *p, *next; + + p = info; + + while(p != NULL) { + next = p->ai_next; + git__free(p); + p = next; + } +} + +const char *p_gai_strerror(int ret) +{ + switch(ret) { + case -1: return "Out of memory"; break; + case -2: return "Address lookup failed"; break; + default: return "Unknown error"; break; + } +} + +#endif /* NO_ADDRINFO */ + +int p_open(const char *path, volatile int flags, ...) +{ + mode_t mode = 0; + + #ifdef GIT_DEBUG_STRICT_OPEN + if (strstr(path, "//") != NULL) { + errno = EACCES; + return -1; + } + #endif + + if (flags & O_CREAT) { + va_list arg_list; + + va_start(arg_list, flags); + mode = (mode_t)va_arg(arg_list, int); + va_end(arg_list); + } + + return open(path, flags | O_BINARY | O_CLOEXEC, mode); +} + +int p_creat(const char *path, mode_t mode) +{ + return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC, mode); +} + +int p_getcwd(char *buffer_out, size_t size) +{ + char *cwd_buffer; + + GIT_ASSERT_ARG(buffer_out); + GIT_ASSERT_ARG(size > 0); + + cwd_buffer = getcwd(buffer_out, size); + + if (cwd_buffer == NULL) + return -1; + + git_fs_path_mkposix(buffer_out); + git_fs_path_string_to_dir(buffer_out, size); /* append trailing slash */ + + return 0; +} + +int p_rename(const char *from, const char *to) +{ + if (!link(from, to)) { + p_unlink(from); + return 0; + } + + if (!rename(from, to)) + return 0; + + return -1; +} + +#endif /* GIT_WIN32 */ + +ssize_t p_read(git_file fd, void *buf, size_t cnt) +{ + char *b = buf; + + if (!git__is_ssizet(cnt)) { +#ifdef GIT_WIN32 + SetLastError(ERROR_INVALID_PARAMETER); +#endif + errno = EINVAL; + return -1; + } + + while (cnt) { + ssize_t r; +#ifdef GIT_WIN32 + r = read(fd, b, cnt > INT_MAX ? INT_MAX : (unsigned int)cnt); +#else + r = read(fd, b, cnt); +#endif + if (r < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!r) + break; + cnt -= r; + b += r; + } + return (b - (char *)buf); +} + +int p_write(git_file fd, const void *buf, size_t cnt) +{ + const char *b = buf; + + while (cnt) { + ssize_t r; +#ifdef GIT_WIN32 + GIT_ASSERT((size_t)((unsigned int)cnt) == cnt); + r = write(fd, b, (unsigned int)cnt); +#else + r = write(fd, b, cnt); +#endif + if (r < 0) { + if (errno == EINTR || GIT_ISBLOCKED(errno)) + continue; + return -1; + } + if (!r) { + errno = EPIPE; + return -1; + } + cnt -= r; + b += r; + } + return 0; +} + +#ifdef NO_MMAP + +#include "map.h" + +int git__page_size(size_t *page_size) +{ + /* dummy; here we don't need any alignment anyway */ + *page_size = 4096; + return 0; +} + +int git__mmap_alignment(size_t *alignment) +{ + /* dummy; here we don't need any alignment anyway */ + *alignment = 4096; + return 0; +} + + +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) +{ + const char *ptr; + size_t remaining_len; + + GIT_MMAP_VALIDATE(out, len, prot, flags); + + /* writes cannot be emulated without handling pagefaults since write happens by + * writing to mapped memory */ + if (prot & GIT_PROT_WRITE) { + git_error_set(GIT_ERROR_OS, "trying to map %s-writeable", + ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) ? "shared": "private"); + return -1; + } + + if (!git__is_ssizet(len)) { + errno = EINVAL; + return -1; + } + + out->len = 0; + out->data = git__malloc(len); + GIT_ERROR_CHECK_ALLOC(out->data); + + remaining_len = len; + ptr = (const char *)out->data; + while (remaining_len > 0) { + ssize_t nb; + HANDLE_EINTR(nb, p_pread(fd, (void *)ptr, remaining_len, offset)); + if (nb <= 0) { + git_error_set(GIT_ERROR_OS, "mmap emulation failed"); + git__free(out->data); + out->data = NULL; + return -1; + } + + ptr += nb; + offset += nb; + remaining_len -= nb; + } + + out->len = len; + return 0; +} + +int p_munmap(git_map *map) +{ + GIT_ASSERT_ARG(map); + git__free(map->data); + + /* Initializing will help debug use-after-free */ + map->len = 0; + map->data = NULL; + + return 0; +} + +#endif diff --git a/src/util/posix.h b/src/util/posix.h new file mode 100644 index 000000000..c8f8cd9d2 --- /dev/null +++ b/src/util/posix.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_posix_h__ +#define INCLUDE_posix_h__ + +#include "git2_util.h" + +#include +#include +#include + +/* stat: file mode type testing macros */ +#ifndef S_IFGITLINK +#define S_IFGITLINK 0160000 +#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) +#endif + +#ifndef S_IFLNK +#define S_IFLNK 0120000 +#undef _S_IFLNK +#define _S_IFLNK S_IFLNK +#endif + +#ifndef S_IWUSR +#define S_IWUSR 00200 +#endif + +#ifndef S_IXUSR +#define S_IXUSR 00100 +#endif + +#ifndef S_ISLNK +#define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) +#endif + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) +#endif + +#ifndef S_ISREG +#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) +#endif + +#ifndef S_ISFIFO +#define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO) +#endif + +/* if S_ISGID is not defined, then don't try to set it */ +#ifndef S_ISGID +#define S_ISGID 0 +#endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif +#ifndef SOCK_CLOEXEC +#define SOCK_CLOEXEC 0 +#endif + +/* access() mode parameter #defines */ +#ifndef F_OK +#define F_OK 0 /* existence check */ +#endif +#ifndef W_OK +#define W_OK 2 /* write mode check */ +#endif +#ifndef R_OK +#define R_OK 4 /* read mode check */ +#endif + +/* Determine whether an errno value indicates that a read or write failed + * because the descriptor is blocked. + */ +#if defined(EWOULDBLOCK) +#define GIT_ISBLOCKED(e) ((e) == EAGAIN || (e) == EWOULDBLOCK) +#else +#define GIT_ISBLOCKED(e) ((e) == EAGAIN) +#endif + +/* define some standard errnos that the runtime may be missing. for example, + * mingw lacks EAFNOSUPPORT. */ +#ifndef EAFNOSUPPORT +#define EAFNOSUPPORT (INT_MAX-1) +#endif + +/* Compiler independent macro to handle signal interrpted system calls */ +#define HANDLE_EINTR(result, x) do { \ + result = (x); \ + } while (result == -1 && errno == EINTR); + + +/* Provide a 64-bit size for offsets. */ + +#if defined(_MSC_VER) +typedef __int64 off64_t; +#elif defined(__HAIKU__) +typedef __haiku_std_int64 off64_t; +#elif defined(__APPLE__) +typedef __int64_t off64_t; +#else +typedef int64_t off64_t; +#endif + +typedef int git_file; + +/** + * Standard POSIX Methods + * + * All the methods starting with the `p_` prefix are + * direct ports of the standard POSIX methods. + * + * Some of the methods are slightly wrapped to provide + * saner defaults. Some of these methods are emulated + * in Windows platforms. + * + * Use your manpages to check the docs on these. + */ + +extern ssize_t p_read(git_file fd, void *buf, size_t cnt); +extern int p_write(git_file fd, const void *buf, size_t cnt); + +extern ssize_t p_pread(int fd, void *data, size_t size, off64_t offset); +extern ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset); + +#define p_close(fd) close(fd) +#define p_umask(m) umask(m) + +extern int p_open(const char *path, int flags, ...); +extern int p_creat(const char *path, mode_t mode); +extern int p_getcwd(char *buffer_out, size_t size); +extern int p_rename(const char *from, const char *to); + +extern int git__page_size(size_t *page_size); +extern int git__mmap_alignment(size_t *page_size); + +/* The number of times `p_fsync` has been called. Note that this is for + * test code only; it it not necessarily thread-safe and should not be + * relied upon in production. + */ +extern size_t p_fsync__cnt; + +/** + * Platform-dependent methods + */ +#ifdef GIT_WIN32 +# include "win32/posix.h" +#else +# include "unix/posix.h" +#endif + +#include "strnlen.h" + +#ifdef NO_READDIR_R +GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) +{ + GIT_UNUSED(entry); + *result = readdir(dirp); + return 0; +} +#else /* NO_READDIR_R */ +# define p_readdir_r(d,e,r) readdir_r(d,e,r) +#endif + +#ifdef NO_ADDRINFO +# include +struct addrinfo { + struct hostent *ai_hostent; + struct servent *ai_servent; + struct sockaddr_in ai_addr_in; + struct sockaddr *ai_addr; + size_t ai_addrlen; + int ai_family; + int ai_socktype; + int ai_protocol; + long ai_port; + struct addrinfo *ai_next; +}; + +extern int p_getaddrinfo(const char *host, const char *port, + struct addrinfo *hints, struct addrinfo **info); +extern void p_freeaddrinfo(struct addrinfo *info); +extern const char *p_gai_strerror(int ret); +#else +# define p_getaddrinfo(a, b, c, d) getaddrinfo(a, b, c, d) +# define p_freeaddrinfo(a) freeaddrinfo(a) +# define p_gai_strerror(c) gai_strerror(c) +#endif /* NO_ADDRINFO */ + +#endif diff --git a/src/util/pqueue.c b/src/util/pqueue.c new file mode 100644 index 000000000..3820e999c --- /dev/null +++ b/src/util/pqueue.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "pqueue.h" + +#include "util.h" + +#define PQUEUE_LCHILD_OF(I) (((I)<<1)+1) +#define PQUEUE_RCHILD_OF(I) (((I)<<1)+2) +#define PQUEUE_PARENT_OF(I) (((I)-1)>>1) + +int git_pqueue_init( + git_pqueue *pq, + uint32_t flags, + size_t init_size, + git_vector_cmp cmp) +{ + int error = git_vector_init(pq, init_size, cmp); + + if (!error) { + /* mix in our flags */ + pq->flags |= flags; + + /* if fixed size heap, pretend vector is exactly init_size elements */ + if ((flags & GIT_PQUEUE_FIXED_SIZE) && init_size > 0) + pq->_alloc_size = init_size; + } + + return error; +} + +static void pqueue_up(git_pqueue *pq, size_t el) +{ + size_t parent_el = PQUEUE_PARENT_OF(el); + void *kid = git_vector_get(pq, el); + + while (el > 0) { + void *parent = pq->contents[parent_el]; + + if (pq->_cmp(parent, kid) <= 0) + break; + + pq->contents[el] = parent; + + el = parent_el; + parent_el = PQUEUE_PARENT_OF(el); + } + + pq->contents[el] = kid; +} + +static void pqueue_down(git_pqueue *pq, size_t el) +{ + void *parent = git_vector_get(pq, el), *kid, *rkid; + + while (1) { + size_t kid_el = PQUEUE_LCHILD_OF(el); + + if ((kid = git_vector_get(pq, kid_el)) == NULL) + break; + + if ((rkid = git_vector_get(pq, kid_el + 1)) != NULL && + pq->_cmp(kid, rkid) > 0) { + kid = rkid; + kid_el += 1; + } + + if (pq->_cmp(parent, kid) <= 0) + break; + + pq->contents[el] = kid; + el = kid_el; + } + + pq->contents[el] = parent; +} + +int git_pqueue_insert(git_pqueue *pq, void *item) +{ + int error = 0; + + /* if heap is full, pop the top element if new one should replace it */ + if ((pq->flags & GIT_PQUEUE_FIXED_SIZE) != 0 && + pq->length >= pq->_alloc_size) + { + /* skip this item if below min item in heap or if + * we do not have a comparison function */ + if (!pq->_cmp || pq->_cmp(item, git_vector_get(pq, 0)) <= 0) + return 0; + /* otherwise remove the min item before inserting new */ + (void)git_pqueue_pop(pq); + } + + if (!(error = git_vector_insert(pq, item)) && pq->_cmp) + pqueue_up(pq, pq->length - 1); + + return error; +} + +void *git_pqueue_pop(git_pqueue *pq) +{ + void *rval; + + if (!pq->_cmp) { + rval = git_vector_last(pq); + } else { + rval = git_pqueue_get(pq, 0); + } + + if (git_pqueue_size(pq) > 1 && pq->_cmp) { + /* move last item to top of heap, shrink, and push item down */ + pq->contents[0] = git_vector_last(pq); + git_vector_pop(pq); + pqueue_down(pq, 0); + } else { + /* all we need to do is shrink the heap in this case */ + git_vector_pop(pq); + } + + return rval; +} diff --git a/src/util/pqueue.h b/src/util/pqueue.h new file mode 100644 index 000000000..97232b4a9 --- /dev/null +++ b/src/util/pqueue.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_pqueue_h__ +#define INCLUDE_pqueue_h__ + +#include "git2_util.h" + +#include "vector.h" + +typedef git_vector git_pqueue; + +enum { + /* flag meaning: don't grow heap, keep highest values only */ + GIT_PQUEUE_FIXED_SIZE = (GIT_VECTOR_FLAG_MAX << 1) +}; + +/** + * Initialize priority queue + * + * @param pq The priority queue struct to initialize + * @param flags Flags (see above) to control queue behavior + * @param init_size The initial queue size + * @param cmp The entry priority comparison function + * @return 0 on success, <0 on error + */ +extern int git_pqueue_init( + git_pqueue *pq, + uint32_t flags, + size_t init_size, + git_vector_cmp cmp); + +#define git_pqueue_free git_vector_free +#define git_pqueue_clear git_vector_clear +#define git_pqueue_size git_vector_length +#define git_pqueue_get git_vector_get +#define git_pqueue_reverse git_vector_reverse + +/** + * Insert a new item into the queue + * + * @param pq The priority queue + * @param item Pointer to the item data + * @return 0 on success, <0 on failure + */ +extern int git_pqueue_insert(git_pqueue *pq, void *item); + +/** + * Remove the top item in the priority queue + * + * @param pq The priority queue + * @return item from heap on success, NULL if queue is empty + */ +extern void *git_pqueue_pop(git_pqueue *pq); + +#endif diff --git a/src/util/rand.c b/src/util/rand.c new file mode 100644 index 000000000..432494902 --- /dev/null +++ b/src/util/rand.c @@ -0,0 +1,226 @@ +/* Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + +To the extent possible under law, the author has dedicated all copyright +and related and neighboring rights to this software to the public domain +worldwide. This software is distributed without any warranty. + +See . */ + +#include "git2_util.h" +#include "rand.h" +#include "runtime.h" + +#if defined(GIT_RAND_GETENTROPY) +# include +#endif + +static uint64_t state[4]; +static git_mutex state_lock; + +typedef union { + double f; + uint64_t d; +} bits; + +#if defined(GIT_WIN32) +GIT_INLINE(int) getseed(uint64_t *seed) +{ + HCRYPTPROV provider; + SYSTEMTIME systemtime; + FILETIME filetime, idletime, kerneltime, usertime; + bits convert; + + if (CryptAcquireContext(&provider, 0, 0, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT|CRYPT_SILENT)) { + BOOL success = CryptGenRandom(provider, sizeof(uint64_t), (void *)seed); + CryptReleaseContext(provider, 0); + + if (success) + return 0; + } + + GetSystemTime(&systemtime); + if (!SystemTimeToFileTime(&systemtime, &filetime)) { + git_error_set(GIT_ERROR_OS, "could not get time for random seed"); + return -1; + } + + /* Fall-through: generate a seed from the time and system state */ + *seed = 0; + *seed |= ((uint64_t)filetime.dwLowDateTime << 32); + *seed |= ((uint64_t)filetime.dwHighDateTime); + + GetSystemTimes(&idletime, &kerneltime, &usertime); + + *seed ^= ((uint64_t)idletime.dwLowDateTime << 32); + *seed ^= ((uint64_t)kerneltime.dwLowDateTime); + *seed ^= ((uint64_t)usertime.dwLowDateTime << 32); + + *seed ^= ((uint64_t)idletime.dwHighDateTime); + *seed ^= ((uint64_t)kerneltime.dwHighDateTime << 12); + *seed ^= ((uint64_t)usertime.dwHighDateTime << 24); + + *seed ^= ((uint64_t)GetCurrentProcessId() << 32); + *seed ^= ((uint64_t)GetCurrentThreadId() << 48); + + convert.f = git__timer(); *seed ^= (convert.d); + + /* Mix in the addresses of some functions and variables */ + *seed ^= (((uint64_t)((uintptr_t)seed) << 32)); + *seed ^= (((uint64_t)((uintptr_t)&errno))); + + return 0; +} + +#else + +GIT_INLINE(int) getseed(uint64_t *seed) +{ + struct timeval tv; + double loadavg[3]; + bits convert; + int fd; + +# if defined(GIT_RAND_GETENTROPY) + GIT_UNUSED((fd = 0)); + + if (getentropy(seed, sizeof(uint64_t)) == 0) + return 0; +# else + /* + * Try to read from /dev/urandom; most modern systems will have + * this, but we may be chrooted, etc, so it's not a fatal error + */ + if ((fd = open("/dev/urandom", O_RDONLY)) >= 0) { + ssize_t ret = read(fd, seed, sizeof(uint64_t)); + close(fd); + + if (ret == (ssize_t)sizeof(uint64_t)) + return 0; + } +# endif + + /* Fall-through: generate a seed from the time and system state */ + if (gettimeofday(&tv, NULL) < 0) { + git_error_set(GIT_ERROR_OS, "could get time for random seed"); + return -1; + } + + getloadavg(loadavg, 3); + + *seed = 0; + *seed |= ((uint64_t)tv.tv_usec << 40); + *seed |= ((uint64_t)tv.tv_sec); + + *seed ^= ((uint64_t)getpid() << 48); + *seed ^= ((uint64_t)getppid() << 32); + *seed ^= ((uint64_t)getpgid(0) << 28); + *seed ^= ((uint64_t)getsid(0) << 16); + *seed ^= ((uint64_t)getuid() << 8); + *seed ^= ((uint64_t)getgid()); + + convert.f = loadavg[0]; *seed ^= (convert.d >> 36); + convert.f = loadavg[1]; *seed ^= (convert.d); + convert.f = loadavg[2]; *seed ^= (convert.d >> 16); + + convert.f = git__timer(); *seed ^= (convert.d); + + /* Mix in the addresses of some variables */ + *seed ^= ((uint64_t)((size_t)((void *)seed)) << 32); + *seed ^= ((uint64_t)((size_t)((void *)&errno))); + + return 0; +} +#endif + +static void git_rand_global_shutdown(void) +{ + git_mutex_free(&state_lock); +} + +int git_rand_global_init(void) +{ + uint64_t seed = 0; + + if (git_mutex_init(&state_lock) < 0 || getseed(&seed) < 0) + return -1; + + if (!seed) { + git_error_set(GIT_ERROR_INTERNAL, "failed to generate random seed"); + return -1; + } + + git_rand_seed(seed); + git_runtime_shutdown_register(git_rand_global_shutdown); + + return 0; +} + +/* + * This is splitmix64. xoroshiro256** uses 256 bit seed; this is used + * to generate 256 bits of seed from the given 64, per the author's + * recommendation. + */ +GIT_INLINE(uint64_t) splitmix64(uint64_t *in) +{ + uint64_t z; + + *in += 0x9e3779b97f4a7c15; + + z = *in; + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); +} + +void git_rand_seed(uint64_t seed) +{ + uint64_t mixer; + + mixer = seed; + + git_mutex_lock(&state_lock); + state[0] = splitmix64(&mixer); + state[1] = splitmix64(&mixer); + state[2] = splitmix64(&mixer); + state[3] = splitmix64(&mixer); + git_mutex_unlock(&state_lock); +} + +/* This is xoshiro256** 1.0, one of our all-purpose, rock-solid + generators. It has excellent (sub-ns) speed, a state (256 bits) that is + large enough for any parallel application, and it passes all tests we + are aware of. + + For generating just floating-point numbers, xoshiro256+ is even faster. + + The state must be seeded so that it is not everywhere zero. If you have + a 64-bit seed, we suggest to seed a splitmix64 generator and use its + output to fill s. */ + +GIT_INLINE(uint64_t) rotl(const uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +uint64_t git_rand_next(void) { + uint64_t t, result; + + git_mutex_lock(&state_lock); + + result = rotl(state[1] * 5, 7) * 9; + + t = state[1] << 17; + + state[2] ^= state[0]; + state[3] ^= state[1]; + state[1] ^= state[2]; + state[0] ^= state[3]; + + state[2] ^= t; + + state[3] = rotl(state[3], 45); + + git_mutex_unlock(&state_lock); + + return result; +} diff --git a/src/util/rand.h b/src/util/rand.h new file mode 100644 index 000000000..fa0619aa2 --- /dev/null +++ b/src/util/rand.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_rand_h__ +#define INCLUDE_rand_h__ + +#include "git2_util.h" + +/** + * Initialize the random number generation subsystem. This will + * seed the random number generator with the system's entropy pool, + * if available, and will fall back to the current time and + * system information if not. + */ +int git_rand_global_init(void); + +/** + * Seed the pseudo-random number generator. This is not needed to be + * called; the PRNG is seeded by `git_rand_global_init`, but it may + * be useful for testing. When the same seed is specified, the same + * sequence of random numbers from `git_rand_next` is emitted. + * + * @param seed the seed to use + */ +void git_rand_seed(uint64_t seed); + +/** + * Get the next pseudo-random number in the sequence. + * + * @return a 64-bit pseudo-random number + */ +uint64_t git_rand_next(void); + +#endif diff --git a/src/util/regexp.c b/src/util/regexp.c new file mode 100644 index 000000000..2569dea0a --- /dev/null +++ b/src/util/regexp.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "regexp.h" + +#if defined(GIT_REGEX_BUILTIN) || defined(GIT_REGEX_PCRE) + +int git_regexp_compile(git_regexp *r, const char *pattern, int flags) +{ + int erroffset, cflags = 0; + const char *error = NULL; + + if (flags & GIT_REGEXP_ICASE) + cflags |= PCRE_CASELESS; + + if ((*r = pcre_compile(pattern, cflags, &error, &erroffset, NULL)) == NULL) { + git_error_set_str(GIT_ERROR_REGEX, error); + return GIT_EINVALIDSPEC; + } + + return 0; +} + +void git_regexp_dispose(git_regexp *r) +{ + pcre_free(*r); + *r = NULL; +} + +int git_regexp_match(const git_regexp *r, const char *string) +{ + int error; + if ((error = pcre_exec(*r, NULL, string, (int) strlen(string), 0, 0, NULL, 0)) < 0) + return (error == PCRE_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) +{ + int static_ovec[9] = {0}, *ovec; + int error; + size_t i; + + /* The ovec array always needs to be a multiple of three */ + if (nmatches <= ARRAY_SIZE(static_ovec) / 3) + ovec = static_ovec; + else + ovec = git__calloc(nmatches * 3, sizeof(*ovec)); + GIT_ERROR_CHECK_ALLOC(ovec); + + if ((error = pcre_exec(*r, NULL, string, (int) strlen(string), 0, 0, ovec, (int) nmatches * 3)) < 0) + goto out; + + if (error == 0) + error = (int) nmatches; + + for (i = 0; i < (unsigned int) error; i++) { + matches[i].start = (ovec[i * 2] < 0) ? -1 : ovec[i * 2]; + matches[i].end = (ovec[i * 2 + 1] < 0) ? -1 : ovec[i * 2 + 1]; + } + for (i = (unsigned int) error; i < nmatches; i++) + matches[i].start = matches[i].end = -1; + +out: + if (nmatches > ARRAY_SIZE(static_ovec) / 3) + git__free(ovec); + if (error < 0) + return (error == PCRE_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +#elif defined(GIT_REGEX_PCRE2) + +int git_regexp_compile(git_regexp *r, const char *pattern, int flags) +{ + unsigned char errmsg[1024]; + PCRE2_SIZE erroff; + int error, cflags = 0; + + if (flags & GIT_REGEXP_ICASE) + cflags |= PCRE2_CASELESS; + + if ((*r = pcre2_compile((const unsigned char *) pattern, PCRE2_ZERO_TERMINATED, + cflags, &error, &erroff, NULL)) == NULL) { + pcre2_get_error_message(error, errmsg, sizeof(errmsg)); + git_error_set_str(GIT_ERROR_REGEX, (char *) errmsg); + return GIT_EINVALIDSPEC; + } + + return 0; +} + +void git_regexp_dispose(git_regexp *r) +{ + pcre2_code_free(*r); + *r = NULL; +} + +int git_regexp_match(const git_regexp *r, const char *string) +{ + pcre2_match_data *data; + int error; + + data = pcre2_match_data_create(1, NULL); + GIT_ERROR_CHECK_ALLOC(data); + + if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string), + 0, 0, data, NULL)) < 0) + return (error == PCRE2_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + + pcre2_match_data_free(data); + return 0; +} + +int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) +{ + pcre2_match_data *data = NULL; + PCRE2_SIZE *ovec; + int error; + size_t i; + + if ((data = pcre2_match_data_create(nmatches, NULL)) == NULL) { + git_error_set_oom(); + goto out; + } + + if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string), + 0, 0, data, NULL)) < 0) + goto out; + + if (error == 0 || (unsigned int) error > nmatches) + error = nmatches; + ovec = pcre2_get_ovector_pointer(data); + + for (i = 0; i < (unsigned int) error; i++) { + matches[i].start = (ovec[i * 2] == PCRE2_UNSET) ? -1 : (ssize_t) ovec[i * 2]; + matches[i].end = (ovec[i * 2 + 1] == PCRE2_UNSET) ? -1 : (ssize_t) ovec[i * 2 + 1]; + } + for (i = (unsigned int) error; i < nmatches; i++) + matches[i].start = matches[i].end = -1; + +out: + pcre2_match_data_free(data); + if (error < 0) + return (error == PCRE2_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +#elif defined(GIT_REGEX_REGCOMP) || defined(GIT_REGEX_REGCOMP_L) + +#if defined(GIT_REGEX_REGCOMP_L) +# include +#endif + +int git_regexp_compile(git_regexp *r, const char *pattern, int flags) +{ + int cflags = REG_EXTENDED, error; + char errmsg[1024]; + + if (flags & GIT_REGEXP_ICASE) + cflags |= REG_ICASE; + +# if defined(GIT_REGEX_REGCOMP) + if ((error = regcomp(r, pattern, cflags)) != 0) +# else + if ((error = regcomp_l(r, pattern, cflags, (locale_t) 0)) != 0) +# endif + { + regerror(error, r, errmsg, sizeof(errmsg)); + git_error_set_str(GIT_ERROR_REGEX, errmsg); + return GIT_EINVALIDSPEC; + } + + return 0; +} + +void git_regexp_dispose(git_regexp *r) +{ + regfree(r); +} + +int git_regexp_match(const git_regexp *r, const char *string) +{ + int error; + if ((error = regexec(r, string, 0, NULL, 0)) != 0) + return (error == REG_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) +{ + regmatch_t static_m[3], *m; + int error; + size_t i; + + if (nmatches <= ARRAY_SIZE(static_m)) + m = static_m; + else + m = git__calloc(nmatches, sizeof(*m)); + + if ((error = regexec(r, string, nmatches, m, 0)) != 0) + goto out; + + for (i = 0; i < nmatches; i++) { + matches[i].start = (m[i].rm_so < 0) ? -1 : m[i].rm_so; + matches[i].end = (m[i].rm_eo < 0) ? -1 : m[i].rm_eo; + } + +out: + if (nmatches > ARRAY_SIZE(static_m)) + git__free(m); + if (error) + return (error == REG_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +#endif diff --git a/src/util/regexp.h b/src/util/regexp.h new file mode 100644 index 000000000..d0862b107 --- /dev/null +++ b/src/util/regexp.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_regexp_h__ +#define INCLUDE_regexp_h__ + +#include "git2_util.h" + +#if defined(GIT_REGEX_BUILTIN) || defined(GIT_REGEX_PCRE) +# include "pcre.h" +typedef pcre *git_regexp; +# define GIT_REGEX_INIT NULL +#elif defined(GIT_REGEX_PCRE2) +# define PCRE2_CODE_UNIT_WIDTH 8 +# include +typedef pcre2_code *git_regexp; +# define GIT_REGEX_INIT NULL +#elif defined(GIT_REGEX_REGCOMP) || defined(GIT_REGEX_REGCOMP_L) +# include +typedef regex_t git_regexp; +# define GIT_REGEX_INIT { 0 } +#else +# error "No regex backend" +#endif + +/** Options supported by @git_regexp_compile. */ +typedef enum { + /** Enable case-insensitive matching */ + GIT_REGEXP_ICASE = (1 << 0) +} git_regexp_flags_t; + +/** Structure containing information about regular expression matching groups */ +typedef struct { + /** Start of the given match. -1 if the group didn't match anything */ + ssize_t start; + /** End of the given match. -1 if the group didn't match anything */ + ssize_t end; +} git_regmatch; + +/** + * Compile a regular expression. The compiled expression needs to + * be cleaned up afterwards with `git_regexp_dispose`. + * + * @param r Pointer to the storage where to initialize the regular expression. + * @param pattern The pattern that shall be compiled. + * @param flags Flags to alter how the pattern shall be handled. + * 0 for defaults, otherwise see @git_regexp_flags_t. + * @return 0 on success, otherwise a negative return value. + */ +int git_regexp_compile(git_regexp *r, const char *pattern, int flags); + +/** + * Free memory associated with the regular expression + * + * @param r The regular expression structure to dispose. + */ +void git_regexp_dispose(git_regexp *r); + +/** + * Test whether a given string matches a compiled regular + * expression. + * + * @param r Compiled regular expression. + * @param string String to match against the regular expression. + * @return 0 if the string matches, a negative error code + * otherwise. GIT_ENOTFOUND if no match was found, + * GIT_EINVALIDSPEC if the regular expression matching + * was invalid. + */ +int git_regexp_match(const git_regexp *r, const char *string); + +/** + * Search for matches inside of a given string. + * + * Given a regular expression with capturing groups, this + * function will populate provided @git_regmatch structures with + * offsets for each of the given matches. Non-matching groups + * will have start and end values of the respective @git_regmatch + * structure set to -1. + * + * @param r Compiled regular expression. + * @param string String to match against the regular expression. + * @param nmatches Number of @git_regmatch structures provided by + * the user. + * @param matches Pointer to an array of @git_regmatch structures. + * @return 0 if the string matches, a negative error code + * otherwise. GIT_ENOTFOUND if no match was found, + * GIT_EINVALIDSPEC if the regular expression matching + * was invalid. + */ +int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches); + +#endif diff --git a/src/util/runtime.c b/src/util/runtime.c new file mode 100644 index 000000000..a7711ffc4 --- /dev/null +++ b/src/util/runtime.c @@ -0,0 +1,162 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2_util.h" +#include "runtime.h" + +static git_runtime_shutdown_fn shutdown_callback[32]; +static git_atomic32 shutdown_callback_count; + +static git_atomic32 init_count; + +static int init_common(git_runtime_init_fn init_fns[], size_t cnt) +{ + size_t i; + int ret; + + /* Initialize subsystems that have global state */ + for (i = 0; i < cnt; i++) { + if ((ret = init_fns[i]()) != 0) + break; + } + + GIT_MEMORY_BARRIER; + + return ret; +} + +static void shutdown_common(void) +{ + git_runtime_shutdown_fn cb; + int pos; + + for (pos = git_atomic32_get(&shutdown_callback_count); + pos > 0; + pos = git_atomic32_dec(&shutdown_callback_count)) { + cb = git_atomic_swap(shutdown_callback[pos - 1], NULL); + + if (cb != NULL) + cb(); + } +} + +int git_runtime_shutdown_register(git_runtime_shutdown_fn callback) +{ + int count = git_atomic32_inc(&shutdown_callback_count); + + if (count > (int)ARRAY_SIZE(shutdown_callback) || count == 0) { + git_error_set(GIT_ERROR_INVALID, + "too many shutdown callbacks registered"); + git_atomic32_dec(&shutdown_callback_count); + return -1; + } + + shutdown_callback[count - 1] = callback; + + return 0; +} + +#if defined(GIT_THREADS) && defined(GIT_WIN32) + +/* + * On Win32, we use a spinlock to provide locking semantics. This is + * lighter-weight than a proper critical section. + */ +static volatile LONG init_spinlock = 0; + +GIT_INLINE(int) init_lock(void) +{ + while (InterlockedCompareExchange(&init_spinlock, 1, 0)) { Sleep(0); } + return 0; +} + +GIT_INLINE(int) init_unlock(void) +{ + InterlockedExchange(&init_spinlock, 0); + return 0; +} + +#elif defined(GIT_THREADS) && defined(_POSIX_THREADS) + +/* + * On POSIX, we need to use a proper mutex for locking. We might prefer + * a spinlock here, too, but there's no static initializer for a + * pthread_spinlock_t. + */ +static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER; + +GIT_INLINE(int) init_lock(void) +{ + return pthread_mutex_lock(&init_mutex) == 0 ? 0 : -1; +} + +GIT_INLINE(int) init_unlock(void) +{ + return pthread_mutex_unlock(&init_mutex) == 0 ? 0 : -1; +} + +#elif defined(GIT_THREADS) +# error unknown threading model +#else + +# define init_lock() git__noop() +# define init_unlock() git__noop() + +#endif + +int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt) +{ + int ret; + + if (init_lock() < 0) + return -1; + + /* Only do work on a 0 -> 1 transition of the refcount */ + if ((ret = git_atomic32_inc(&init_count)) == 1) { + if (init_common(init_fns, cnt) < 0) + ret = -1; + } + + if (init_unlock() < 0) + return -1; + + return ret; +} + +int git_runtime_init_count(void) +{ + int ret; + + if (init_lock() < 0) + return -1; + + ret = git_atomic32_get(&init_count); + + if (init_unlock() < 0) + return -1; + + return ret; +} + +int git_runtime_shutdown(void) +{ + int ret; + + /* Enter the lock */ + if (init_lock() < 0) + return -1; + + /* Only do work on a 1 -> 0 transition of the refcount */ + if ((ret = git_atomic32_dec(&init_count)) == 0) + shutdown_common(); + + /* Exit the lock */ + if (init_unlock() < 0) + return -1; + + return ret; +} diff --git a/src/util/runtime.h b/src/util/runtime.h new file mode 100644 index 000000000..6cbfd6043 --- /dev/null +++ b/src/util/runtime.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_runtime_h__ +#define INCLUDE_runtime_h__ + +#include "git2_util.h" + +typedef int (*git_runtime_init_fn)(void); +typedef void (*git_runtime_shutdown_fn)(void); + +/** + * Start up a new runtime. If this is the first time that this + * function is called within the context of the current library + * or executable, then the given `init_fns` will be invoked. If + * it is not the first time, they will be ignored. + * + * The given initialization functions _may_ register shutdown + * handlers using `git_runtime_shutdown_register` to be notified + * when the runtime is shutdown. + * + * @param init_fns The list of initialization functions to call + * @param cnt The number of init_fns + * @return The number of initializations performed (including this one) or an error + */ +int git_runtime_init(git_runtime_init_fn init_fns[], size_t cnt); + +/* + * Returns the number of initializations active (the number of calls to + * `git_runtime_init` minus the number of calls sto `git_runtime_shutdown`). + * If 0, the runtime is not currently initialized. + * + * @return The number of initializations performed or an error + */ +int git_runtime_init_count(void); + +/** + * Shut down the runtime. If this is the last shutdown call, + * such that there are no remaining `init` calls, then any + * shutdown hooks that have been registered will be invoked. + * + * The number of outstanding initializations will be returned. + * If this number is 0, then the runtime is shutdown. + * + * @return The number of outstanding initializations (after this one) or an error + */ +int git_runtime_shutdown(void); + +/** + * Register a shutdown handler for this runtime. This should be done + * by a function invoked by `git_runtime_init` to ensure that the + * appropriate locks are taken. + * + * @param callback The shutdown handler callback + * @return 0 or an error code + */ +int git_runtime_shutdown_register(git_runtime_shutdown_fn callback); + +#endif diff --git a/src/util/sortedcache.c b/src/util/sortedcache.c new file mode 100644 index 000000000..7ff900efe --- /dev/null +++ b/src/util/sortedcache.c @@ -0,0 +1,380 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "sortedcache.h" + +int git_sortedcache_new( + git_sortedcache **out, + size_t item_path_offset, + git_sortedcache_free_item_fn free_item, + void *free_item_payload, + git_vector_cmp item_cmp, + const char *path) +{ + git_sortedcache *sc; + size_t pathlen, alloclen; + + pathlen = path ? strlen(path) : 0; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_sortedcache), pathlen); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + sc = git__calloc(1, alloclen); + GIT_ERROR_CHECK_ALLOC(sc); + + if (git_pool_init(&sc->pool, 1) < 0 || + git_vector_init(&sc->items, 4, item_cmp) < 0 || + git_strmap_new(&sc->map) < 0) + goto fail; + + if (git_rwlock_init(&sc->lock)) { + git_error_set(GIT_ERROR_OS, "failed to initialize lock"); + goto fail; + } + + sc->item_path_offset = item_path_offset; + sc->free_item = free_item; + sc->free_item_payload = free_item_payload; + GIT_REFCOUNT_INC(sc); + if (pathlen) + memcpy(sc->path, path, pathlen); + + *out = sc; + return 0; + +fail: + git_strmap_free(sc->map); + git_vector_free(&sc->items); + git_pool_clear(&sc->pool); + git__free(sc); + return -1; +} + +void git_sortedcache_incref(git_sortedcache *sc) +{ + GIT_REFCOUNT_INC(sc); +} + +const char *git_sortedcache_path(git_sortedcache *sc) +{ + return sc->path; +} + +static void sortedcache_clear(git_sortedcache *sc) +{ + git_strmap_clear(sc->map); + + if (sc->free_item) { + size_t i; + void *item; + + git_vector_foreach(&sc->items, i, item) { + sc->free_item(sc->free_item_payload, item); + } + } + + git_vector_clear(&sc->items); + + git_pool_clear(&sc->pool); +} + +static void sortedcache_free(git_sortedcache *sc) +{ + /* acquire write lock to make sure everyone else is done */ + if (git_sortedcache_wlock(sc) < 0) + return; + + sortedcache_clear(sc); + git_vector_free(&sc->items); + git_strmap_free(sc->map); + + git_sortedcache_wunlock(sc); + + git_rwlock_free(&sc->lock); + git__free(sc); +} + +void git_sortedcache_free(git_sortedcache *sc) +{ + if (!sc) + return; + GIT_REFCOUNT_DEC(sc, sortedcache_free); +} + +static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item) +{ + git_sortedcache *sc = payload; + /* path will already have been copied by upsert */ + memcpy(tgt_item, src_item, sc->item_path_offset); + return 0; +} + +/* copy a sorted cache */ +int git_sortedcache_copy( + git_sortedcache **out, + git_sortedcache *src, + bool lock, + int (*copy_item)(void *payload, void *tgt_item, void *src_item), + void *payload) +{ + int error = 0; + git_sortedcache *tgt; + size_t i; + void *src_item, *tgt_item; + + /* just use memcpy if no special copy fn is passed in */ + if (!copy_item) { + copy_item = sortedcache_copy_item; + payload = src; + } + + if ((error = git_sortedcache_new( + &tgt, src->item_path_offset, + src->free_item, src->free_item_payload, + src->items._cmp, src->path)) < 0) + return error; + + if (lock && git_sortedcache_rlock(src) < 0) { + git_sortedcache_free(tgt); + return -1; + } + + git_vector_foreach(&src->items, i, src_item) { + char *path = ((char *)src_item) + src->item_path_offset; + + if ((error = git_sortedcache_upsert(&tgt_item, tgt, path)) < 0 || + (error = copy_item(payload, tgt_item, src_item)) < 0) + break; + } + + if (lock) + git_sortedcache_runlock(src); + if (error) + git_sortedcache_free(tgt); + + *out = !error ? tgt : NULL; + + return error; +} + +/* lock sortedcache while making modifications */ +int git_sortedcache_wlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + + if (git_rwlock_wrlock(&sc->lock) < 0) { + git_error_set(GIT_ERROR_OS, "unable to acquire write lock on cache"); + return -1; + } + return 0; +} + +/* unlock sorted cache when done with modifications */ +void git_sortedcache_wunlock(git_sortedcache *sc) +{ + git_vector_sort(&sc->items); + git_rwlock_wrunlock(&sc->lock); +} + +/* lock sortedcache for read */ +int git_sortedcache_rlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + + if (git_rwlock_rdlock(&sc->lock) < 0) { + git_error_set(GIT_ERROR_OS, "unable to acquire read lock on cache"); + return -1; + } + return 0; +} + +/* unlock sorted cache when done reading */ +void git_sortedcache_runlock(git_sortedcache *sc) +{ + GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */ + git_rwlock_rdunlock(&sc->lock); +} + +/* if the file has changed, lock cache and load file contents into buf; + * returns <0 on error, >0 if file has not changed + */ +int git_sortedcache_lockandload(git_sortedcache *sc, git_str *buf) +{ + int error, fd; + struct stat st; + + if ((error = git_sortedcache_wlock(sc)) < 0) + return error; + + if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0) + goto unlock; + + if ((fd = git_futils_open_ro(sc->path)) < 0) { + error = fd; + goto unlock; + } + + if (p_fstat(fd, &st) < 0) { + git_error_set(GIT_ERROR_OS, "failed to stat file"); + error = -1; + (void)p_close(fd); + goto unlock; + } + + if (!git__is_sizet(st.st_size)) { + git_error_set(GIT_ERROR_INVALID, "unable to load file larger than size_t"); + error = -1; + (void)p_close(fd); + goto unlock; + } + + if (buf) + error = git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size); + + (void)p_close(fd); + + if (error < 0) + goto unlock; + + return 1; /* return 1 -> file needs reload and was successfully loaded */ + +unlock: + git_sortedcache_wunlock(sc); + return error; +} + +void git_sortedcache_updated(git_sortedcache *sc) +{ + /* update filestamp to latest value */ + git_futils_filestamp_check(&sc->stamp, sc->path); +} + +/* release all items in sorted cache */ +int git_sortedcache_clear(git_sortedcache *sc, bool wlock) +{ + if (wlock && git_sortedcache_wlock(sc) < 0) + return -1; + + sortedcache_clear(sc); + + if (wlock) + git_sortedcache_wunlock(sc); + + return 0; +} + +/* find and/or insert item, returning pointer to item data */ +int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) +{ + size_t keylen, itemlen; + int error = 0; + char *item_key; + void *item; + + if ((item = git_strmap_get(sc->map, key)) != NULL) + goto done; + + keylen = strlen(key); + itemlen = sc->item_path_offset + keylen + 1; + itemlen = (itemlen + 7) & ~7; + + if ((item = git_pool_mallocz(&sc->pool, itemlen)) == NULL) { + /* don't use GIT_ERROR_CHECK_ALLOC b/c of lock */ + error = -1; + goto done; + } + + /* one strange thing is that even if the vector or hash table insert + * fail, there is no way to free the pool item so we just abandon it + */ + + item_key = ((char *)item) + sc->item_path_offset; + memcpy(item_key, key, keylen); + + if ((error = git_strmap_set(sc->map, item_key, item)) < 0) + goto done; + + if ((error = git_vector_insert(&sc->items, item)) < 0) + git_strmap_delete(sc->map, item_key); + +done: + if (out) + *out = !error ? item : NULL; + return error; +} + +/* lookup item by key */ +void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key) +{ + return git_strmap_get(sc->map, key); +} + +/* find out how many items are in the cache */ +size_t git_sortedcache_entrycount(const git_sortedcache *sc) +{ + return git_vector_length(&sc->items); +} + +/* lookup item by index */ +void *git_sortedcache_entry(git_sortedcache *sc, size_t pos) +{ + /* make sure the items are sorted so this gets the correct item */ + if (!git_vector_is_sorted(&sc->items)) + git_vector_sort(&sc->items); + + return git_vector_get(&sc->items, pos); +} + +/* helper struct so bsearch callback can know offset + key value for cmp */ +struct sortedcache_magic_key { + size_t offset; + const char *key; +}; + +static int sortedcache_magic_cmp(const void *key, const void *value) +{ + const struct sortedcache_magic_key *magic = key; + const char *value_key = ((const char *)value) + magic->offset; + return strcmp(magic->key, value_key); +} + +/* lookup index of item by key */ +int git_sortedcache_lookup_index( + size_t *out, git_sortedcache *sc, const char *key) +{ + struct sortedcache_magic_key magic; + + magic.offset = sc->item_path_offset; + magic.key = key; + + return git_vector_bsearch2(out, &sc->items, sortedcache_magic_cmp, &magic); +} + +/* remove entry from cache */ +int git_sortedcache_remove(git_sortedcache *sc, size_t pos) +{ + char *item; + + /* + * Because of pool allocation, this can't actually remove the item, + * but we can remove it from the items vector and the hash table. + */ + + if ((item = git_vector_get(&sc->items, pos)) == NULL) { + git_error_set(GIT_ERROR_INVALID, "removing item out of range"); + return GIT_ENOTFOUND; + } + + (void)git_vector_remove(&sc->items, pos); + + git_strmap_delete(sc->map, item + sc->item_path_offset); + + if (sc->free_item) + sc->free_item(sc->free_item_payload, item); + + return 0; +} + diff --git a/src/util/sortedcache.h b/src/util/sortedcache.h new file mode 100644 index 000000000..3eee4659f --- /dev/null +++ b/src/util/sortedcache.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_sorted_cache_h__ +#define INCLUDE_sorted_cache_h__ + +#include "git2_util.h" + +#include "util.h" +#include "futils.h" +#include "vector.h" +#include "thread.h" +#include "pool.h" +#include "strmap.h" + +#include + +/* + * The purpose of this data structure is to cache the parsed contents of a + * file (a.k.a. the backing file) where each item in the file can be + * identified by a key string and you want to both look them up by name + * and traverse them in sorted order. Each item is assumed to itself end + * in a GIT_FLEX_ARRAY. + */ + +typedef void (*git_sortedcache_free_item_fn)(void *payload, void *item); + +typedef struct { + git_refcount rc; + git_rwlock lock; + size_t item_path_offset; + git_sortedcache_free_item_fn free_item; + void *free_item_payload; + git_pool pool; + git_vector items; + git_strmap *map; + git_futils_filestamp stamp; + char path[GIT_FLEX_ARRAY]; +} git_sortedcache; + +/* Create a new sortedcache + * + * Even though every sortedcache stores items with a GIT_FLEX_ARRAY at + * the end containing their key string, you have to provide the item_cmp + * sorting function because the sorting function doesn't get a payload + * and therefore can't know the offset to the item key string. :-( + * + * @param out The allocated git_sortedcache + * @param item_path_offset Offset to the GIT_FLEX_ARRAY item key in the + * struct - use offsetof(struct mine, key-field) to get this + * @param free_item Optional callback to free each item + * @param free_item_payload Optional payload passed to free_item callback + * @param item_cmp Compare the keys of two items + * @param path The path to the backing store file for this cache; this + * may be NULL. The cache makes it easy to load this and check + * if it has been modified since the last load and/or write. + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_new( + git_sortedcache **out, + size_t item_path_offset, /* use offsetof(struct, path-field) macro */ + git_sortedcache_free_item_fn free_item, + void *free_item_payload, + git_vector_cmp item_cmp, + const char *path); + +/* Copy a sorted cache + * + * - `copy_item` can be NULL to just use memcpy + * - if `lock`, grabs read lock on `src` during copy and releases after + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_copy( + git_sortedcache **out, + git_sortedcache *src, + bool lock, + int (*copy_item)(void *payload, void *tgt_item, void *src_item), + void *payload); + +/* Free sorted cache (first calling `free_item` callbacks) + * + * Don't call on a locked collection - it may acquire a write lock + */ +void git_sortedcache_free(git_sortedcache *sc); + +/* Increment reference count - balance with call to free */ +void git_sortedcache_incref(git_sortedcache *sc); + +/* Get the pathname associated with this cache at creation time */ +const char *git_sortedcache_path(git_sortedcache *sc); + +/* + * CACHE WRITE FUNCTIONS + * + * The following functions require you to have a writer lock to make the + * modification. Some of the functions take a `wlock` parameter and + * will optionally lock and unlock for you if that is passed as true. + * + */ + +/* Lock sortedcache for write */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_wlock(git_sortedcache *sc); + +/* Unlock sorted cache when done with write */ +void git_sortedcache_wunlock(git_sortedcache *sc); + +/* Lock cache and load backing file into a buffer. + * + * This grabs a write lock on the cache then looks at the modification + * time and size of the file on disk. + * + * If the file appears to have changed, this loads the file contents into + * the buffer and returns a positive value leaving the cache locked - the + * caller should parse the file content, update the cache as needed, then + * release the lock. NOTE: In this case, the caller MUST unlock the cache. + * + * If the file appears to be unchanged, then this automatically releases + * the lock on the cache, clears the buffer, and returns 0. + * + * @return 0 if up-to-date, 1 if out-of-date, <0 on error + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_lockandload( + git_sortedcache *sc, git_str *buf); + +/* Refresh file timestamp after write completes + * You should already be holding the write lock when you call this. + */ +void git_sortedcache_updated(git_sortedcache *sc); + +/* Release all items in sorted cache + * + * If `wlock` is true, grabs write lock and releases when done, otherwise + * you should already be holding a write lock when you call this. + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_clear( + git_sortedcache *sc, bool wlock); + +/* Find and/or insert item, returning pointer to item data. + * You should already be holding the write lock when you call this. + */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_upsert( + void **out, git_sortedcache *sc, const char *key); + +/* Removes entry at pos from cache + * You should already be holding the write lock when you call this. + */ +int git_sortedcache_remove(git_sortedcache *sc, size_t pos); + +/* + * CACHE READ FUNCTIONS + * + * The following functions access items in the cache. To prevent the + * results from being invalidated before they can be used, you should be + * holding either a read lock or a write lock when using these functions. + * + */ + +/* Lock sortedcache for read */ +GIT_WARN_UNUSED_RESULT int git_sortedcache_rlock(git_sortedcache *sc); + +/* Unlock sorted cache when done with read */ +void git_sortedcache_runlock(git_sortedcache *sc); + +/* Lookup item by key - returns NULL if not found */ +void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key); + +/* Get how many items are in the cache + * + * You can call this function without holding a lock, but be aware + * that it may change before you use it. + */ +size_t git_sortedcache_entrycount(const git_sortedcache *sc); + +/* Lookup item by index - returns NULL if out of range */ +void *git_sortedcache_entry(git_sortedcache *sc, size_t pos); + +/* Lookup index of item by key - returns GIT_ENOTFOUND if not found */ +int git_sortedcache_lookup_index( + size_t *out, git_sortedcache *sc, const char *key); + +#endif diff --git a/src/util/str.c b/src/util/str.c new file mode 100644 index 000000000..0d405bfda --- /dev/null +++ b/src/util/str.c @@ -0,0 +1,1372 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "str.h" +#include "posix.h" +#include + +/* Used as default value for git_str->ptr so that people can always + * assume ptr is non-NULL and zero terminated even for new git_strs. + */ +char git_str__initstr[1]; + +char git_str__oom[1]; + +#define ENSURE_SIZE(b, d) \ + if ((b)->ptr == git_str__oom || \ + ((d) > (b)->asize && git_str_grow((b), (d)) < 0))\ + return -1; + + +int git_str_init(git_str *buf, size_t initial_size) +{ + buf->asize = 0; + buf->size = 0; + buf->ptr = git_str__initstr; + + ENSURE_SIZE(buf, initial_size); + + return 0; +} + +int git_str_try_grow( + git_str *buf, size_t target_size, bool mark_oom) +{ + char *new_ptr; + size_t new_size; + + if (buf->ptr == git_str__oom) + return -1; + + if (buf->asize == 0 && buf->size != 0) { + git_error_set(GIT_ERROR_INVALID, "cannot grow a borrowed buffer"); + return GIT_EINVALID; + } + + if (!target_size) + target_size = buf->size; + + if (target_size <= buf->asize) + return 0; + + if (buf->asize == 0) { + new_size = target_size; + new_ptr = NULL; + } else { + new_size = buf->asize; + /* + * Grow the allocated buffer by 1.5 to allow + * re-use of memory holes resulting from the + * realloc. If this is still too small, then just + * use the target size. + */ + if ((new_size = (new_size << 1) - (new_size >> 1)) < target_size) + new_size = target_size; + new_ptr = buf->ptr; + } + + /* round allocation up to multiple of 8 */ + new_size = (new_size + 7) & ~7; + + if (new_size < buf->size) { + if (mark_oom) { + if (buf->ptr && buf->ptr != git_str__initstr) + git__free(buf->ptr); + buf->ptr = git_str__oom; + } + + git_error_set_oom(); + return -1; + } + + new_ptr = git__realloc(new_ptr, new_size); + + if (!new_ptr) { + if (mark_oom) { + if (buf->ptr && (buf->ptr != git_str__initstr)) + git__free(buf->ptr); + buf->ptr = git_str__oom; + } + return -1; + } + + buf->asize = new_size; + buf->ptr = new_ptr; + + /* truncate the existing buffer size if necessary */ + if (buf->size >= buf->asize) + buf->size = buf->asize - 1; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +int git_str_grow(git_str *buffer, size_t target_size) +{ + return git_str_try_grow(buffer, target_size, true); +} + +int git_str_grow_by(git_str *buffer, size_t additional_size) +{ + size_t newsize; + + if (GIT_ADD_SIZET_OVERFLOW(&newsize, buffer->size, additional_size)) { + buffer->ptr = git_str__oom; + return -1; + } + + return git_str_try_grow(buffer, newsize, true); +} + +void git_str_dispose(git_str *buf) +{ + if (!buf) return; + + if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_str__oom) + git__free(buf->ptr); + + git_str_init(buf, 0); +} + +void git_str_clear(git_str *buf) +{ + buf->size = 0; + + if (!buf->ptr) { + buf->ptr = git_str__initstr; + buf->asize = 0; + } + + if (buf->asize > 0) + buf->ptr[0] = '\0'; +} + +int git_str_set(git_str *buf, const void *data, size_t len) +{ + size_t alloclen; + + if (len == 0 || data == NULL) { + git_str_clear(buf); + } else { + if (data != buf->ptr) { + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, len, 1); + ENSURE_SIZE(buf, alloclen); + memmove(buf->ptr, data, len); + } + + buf->size = len; + if (buf->asize > buf->size) + buf->ptr[buf->size] = '\0'; + + } + return 0; +} + +int git_str_sets(git_str *buf, const char *string) +{ + return git_str_set(buf, string, string ? strlen(string) : 0); +} + +int git_str_putc(git_str *buf, char c) +{ + size_t new_size; + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, 2); + ENSURE_SIZE(buf, new_size); + buf->ptr[buf->size++] = c; + buf->ptr[buf->size] = '\0'; + return 0; +} + +int git_str_putcn(git_str *buf, char c, size_t len) +{ + size_t new_size; + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + memset(buf->ptr + buf->size, c, len); + buf->size += len; + buf->ptr[buf->size] = '\0'; + return 0; +} + +int git_str_put(git_str *buf, const char *data, size_t len) +{ + if (len) { + size_t new_size; + + GIT_ASSERT_ARG(data); + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + memmove(buf->ptr + buf->size, data, len); + buf->size += len; + buf->ptr[buf->size] = '\0'; + } + return 0; +} + +int git_str_puts(git_str *buf, const char *string) +{ + GIT_ASSERT_ARG(string); + + return git_str_put(buf, string, strlen(string)); +} + +static char hex_encode[] = "0123456789abcdef"; + +int git_str_encode_hexstr(git_str *str, const char *data, size_t len) +{ + size_t new_size, i; + char *s; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&new_size, len, 2); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + + if (git_str_grow_by(str, new_size) < 0) + return -1; + + s = str->ptr + str->size; + + for (i = 0; i < len; i++) { + *s++ = hex_encode[(data[i] & 0xf0) >> 4]; + *s++ = hex_encode[(data[i] & 0x0f)]; + } + + str->size += (len * 2); + str->ptr[str->size] = '\0'; + + return 0; +} + +static const char base64_encode[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +int git_str_encode_base64(git_str *buf, const char *data, size_t len) +{ + size_t extra = len % 3; + uint8_t *write, a, b, c; + const uint8_t *read = (const uint8_t *)data; + size_t blocks = (len / 3) + !!extra, alloclen; + + GIT_ERROR_CHECK_ALLOC_ADD(&blocks, blocks, 1); + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 4); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size); + + ENSURE_SIZE(buf, alloclen); + write = (uint8_t *)&buf->ptr[buf->size]; + + /* convert each run of 3 bytes into 4 output bytes */ + for (len -= extra; len > 0; len -= 3) { + a = *read++; + b = *read++; + c = *read++; + + *write++ = base64_encode[a >> 2]; + *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; + *write++ = base64_encode[(b & 0x0f) << 2 | c >> 6]; + *write++ = base64_encode[c & 0x3f]; + } + + if (extra > 0) { + a = *read++; + b = (extra > 1) ? *read++ : 0; + + *write++ = base64_encode[a >> 2]; + *write++ = base64_encode[(a & 0x03) << 4 | b >> 4]; + *write++ = (extra > 1) ? base64_encode[(b & 0x0f) << 2] : '='; + *write++ = '='; + } + + buf->size = ((char *)write) - buf->ptr; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +/* The inverse of base64_encode */ +static const int8_t base64_decode[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +int git_str_decode_base64(git_str *buf, const char *base64, size_t len) +{ + size_t i; + int8_t a, b, c, d; + size_t orig_size = buf->size, new_size; + + if (len % 4) { + git_error_set(GIT_ERROR_INVALID, "invalid base64 input"); + return -1; + } + + GIT_ASSERT_ARG(len % 4 == 0); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + + for (i = 0; i < len; i += 4) { + if ((a = base64_decode[(unsigned char)base64[i]]) < 0 || + (b = base64_decode[(unsigned char)base64[i+1]]) < 0 || + (c = base64_decode[(unsigned char)base64[i+2]]) < 0 || + (d = base64_decode[(unsigned char)base64[i+3]]) < 0) { + buf->size = orig_size; + buf->ptr[buf->size] = '\0'; + + git_error_set(GIT_ERROR_INVALID, "invalid base64 input"); + return -1; + } + + buf->ptr[buf->size++] = ((a << 2) | (b & 0x30) >> 4); + buf->ptr[buf->size++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + buf->ptr[buf->size++] = (c & 0x03) << 6 | (d & 0x3f); + } + + buf->ptr[buf->size] = '\0'; + return 0; +} + +static const char base85_encode[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; + +int git_str_encode_base85(git_str *buf, const char *data, size_t len) +{ + size_t blocks = (len / 4) + !!(len % 4), alloclen; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloclen, blocks, 5); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, buf->size); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + + ENSURE_SIZE(buf, alloclen); + + while (len) { + uint32_t acc = 0; + char b85[5]; + int i; + + for (i = 24; i >= 0; i -= 8) { + uint8_t ch = *data++; + acc |= (uint32_t)ch << i; + + if (--len == 0) + break; + } + + for (i = 4; i >= 0; i--) { + int val = acc % 85; + acc /= 85; + + b85[i] = base85_encode[val]; + } + + for (i = 0; i < 5; i++) + buf->ptr[buf->size++] = b85[i]; + } + + buf->ptr[buf->size] = '\0'; + + return 0; +} + +/* The inverse of base85_encode */ +static const int8_t base85_decode[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 73, 74, 75, 76, 77, + 78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80, + 81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +int git_str_decode_base85( + git_str *buf, + const char *base85, + size_t base85_len, + size_t output_len) +{ + size_t orig_size = buf->size, new_size; + + if (base85_len % 5 || + output_len > base85_len * 4 / 5) { + git_error_set(GIT_ERROR_INVALID, "invalid base85 input"); + return -1; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + + while (output_len) { + unsigned acc = 0; + int de, cnt = 4; + unsigned char ch; + do { + ch = *base85++; + de = base85_decode[ch]; + if (--de < 0) + goto on_error; + + acc = acc * 85 + de; + } while (--cnt); + ch = *base85++; + de = base85_decode[ch]; + if (--de < 0) + goto on_error; + + /* Detect overflow. */ + if (0xffffffff / 85 < acc || + 0xffffffff - de < (acc *= 85)) + goto on_error; + + acc += de; + + cnt = (output_len < 4) ? (int)output_len : 4; + output_len -= cnt; + do { + acc = (acc << 8) | (acc >> 24); + buf->ptr[buf->size++] = acc; + } while (--cnt); + } + + buf->ptr[buf->size] = 0; + + return 0; + +on_error: + buf->size = orig_size; + buf->ptr[buf->size] = '\0'; + + git_error_set(GIT_ERROR_INVALID, "invalid base85 input"); + return -1; +} + +#define HEX_DECODE(c) ((c | 32) % 39 - 9) + +int git_str_decode_percent( + git_str *buf, + const char *str, + size_t str_len) +{ + size_t str_pos, new_size; + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, str_len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + + for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) { + if (str[str_pos] == '%' && + str_len > str_pos + 2 && + isxdigit(str[str_pos + 1]) && + isxdigit(str[str_pos + 2])) { + buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) + + HEX_DECODE(str[str_pos + 2]); + str_pos += 2; + } else { + buf->ptr[buf->size] = str[str_pos]; + } + } + + buf->ptr[buf->size] = '\0'; + return 0; +} + +int git_str_vprintf(git_str *buf, const char *format, va_list ap) +{ + size_t expected_size, new_size; + int len; + + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&expected_size, strlen(format), 2); + GIT_ERROR_CHECK_ALLOC_ADD(&expected_size, expected_size, buf->size); + ENSURE_SIZE(buf, expected_size); + + while (1) { + va_list args; + va_copy(args, ap); + + len = p_vsnprintf( + buf->ptr + buf->size, + buf->asize - buf->size, + format, args + ); + + va_end(args); + + if (len < 0) { + git__free(buf->ptr); + buf->ptr = git_str__oom; + return -1; + } + + if ((size_t)len + 1 <= buf->asize - buf->size) { + buf->size += len; + break; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + } + + return 0; +} + +int git_str_printf(git_str *buf, const char *format, ...) +{ + int r; + va_list ap; + + va_start(ap, format); + r = git_str_vprintf(buf, format, ap); + va_end(ap); + + return r; +} + +int git_str_copy_cstr(char *data, size_t datasize, const git_str *buf) +{ + size_t copylen; + + GIT_ASSERT_ARG(data); + GIT_ASSERT_ARG(datasize); + GIT_ASSERT_ARG(buf); + + data[0] = '\0'; + + if (buf->size == 0 || buf->asize <= 0) + return 0; + + copylen = buf->size; + if (copylen > datasize - 1) + copylen = datasize - 1; + memmove(data, buf->ptr, copylen); + data[copylen] = '\0'; + + return 0; +} + +void git_str_consume_bytes(git_str *buf, size_t len) +{ + git_str_consume(buf, buf->ptr + len); +} + +void git_str_consume(git_str *buf, const char *end) +{ + if (end > buf->ptr && end <= buf->ptr + buf->size) { + size_t consumed = end - buf->ptr; + memmove(buf->ptr, end, buf->size - consumed); + buf->size -= consumed; + buf->ptr[buf->size] = '\0'; + } +} + +void git_str_truncate(git_str *buf, size_t len) +{ + if (len >= buf->size) + return; + + buf->size = len; + if (buf->size < buf->asize) + buf->ptr[buf->size] = '\0'; +} + +void git_str_shorten(git_str *buf, size_t amount) +{ + if (buf->size > amount) + git_str_truncate(buf, buf->size - amount); + else + git_str_clear(buf); +} + +void git_str_truncate_at_char(git_str *buf, char separator) +{ + ssize_t idx = git_str_find(buf, separator); + if (idx >= 0) + git_str_truncate(buf, (size_t)idx); +} + +void git_str_rtruncate_at_char(git_str *buf, char separator) +{ + ssize_t idx = git_str_rfind_next(buf, separator); + git_str_truncate(buf, idx < 0 ? 0 : (size_t)idx); +} + +void git_str_swap(git_str *str_a, git_str *str_b) +{ + git_str t = *str_a; + *str_a = *str_b; + *str_b = t; +} + +char *git_str_detach(git_str *buf) +{ + char *data = buf->ptr; + + if (buf->asize == 0 || buf->ptr == git_str__oom) + return NULL; + + git_str_init(buf, 0); + + return data; +} + +int git_str_attach(git_str *buf, char *ptr, size_t asize) +{ + git_str_dispose(buf); + + if (ptr) { + buf->ptr = ptr; + buf->size = strlen(ptr); + if (asize) + buf->asize = (asize < buf->size) ? buf->size + 1 : asize; + else /* pass 0 to fall back on strlen + 1 */ + buf->asize = buf->size + 1; + } + + ENSURE_SIZE(buf, asize); + return 0; +} + +void git_str_attach_notowned(git_str *buf, const char *ptr, size_t size) +{ + if (git_str_is_allocated(buf)) + git_str_dispose(buf); + + if (!size) { + git_str_init(buf, 0); + } else { + buf->ptr = (char *)ptr; + buf->asize = 0; + buf->size = size; + } +} + +int git_str_join_n(git_str *buf, char separator, int nbuf, ...) +{ + va_list ap; + int i; + size_t total_size = 0, original_size = buf->size; + char *out, *original = buf->ptr; + + if (buf->size > 0 && buf->ptr[buf->size - 1] != separator) + ++total_size; /* space for initial separator */ + + /* Make two passes to avoid multiple reallocation */ + + va_start(ap, nbuf); + for (i = 0; i < nbuf; ++i) { + const char *segment; + size_t segment_len; + + segment = va_arg(ap, const char *); + if (!segment) + continue; + + segment_len = strlen(segment); + + GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, segment_len); + + if (segment_len == 0 || segment[segment_len - 1] != separator) + GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1); + } + va_end(ap); + + /* expand buffer if needed */ + if (total_size == 0) + return 0; + + GIT_ERROR_CHECK_ALLOC_ADD(&total_size, total_size, 1); + if (git_str_grow_by(buf, total_size) < 0) + return -1; + + out = buf->ptr + buf->size; + + /* append separator to existing buf if needed */ + if (buf->size > 0 && out[-1] != separator) + *out++ = separator; + + va_start(ap, nbuf); + for (i = 0; i < nbuf; ++i) { + const char *segment; + size_t segment_len; + + segment = va_arg(ap, const char *); + if (!segment) + continue; + + /* deal with join that references buffer's original content */ + if (segment >= original && segment < original + original_size) { + size_t offset = (segment - original); + segment = buf->ptr + offset; + segment_len = original_size - offset; + } else { + segment_len = strlen(segment); + } + + /* skip leading separators */ + if (out > buf->ptr && out[-1] == separator) + while (segment_len > 0 && *segment == separator) { + segment++; + segment_len--; + } + + /* copy over next buffer */ + if (segment_len > 0) { + memmove(out, segment, segment_len); + out += segment_len; + } + + /* append trailing separator (except for last item) */ + if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator) + *out++ = separator; + } + va_end(ap); + + /* set size based on num characters actually written */ + buf->size = out - buf->ptr; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +int git_str_join( + git_str *buf, + char separator, + const char *str_a, + const char *str_b) +{ + size_t strlen_a = str_a ? strlen(str_a) : 0; + size_t strlen_b = strlen(str_b); + size_t alloc_len; + int need_sep = 0; + ssize_t offset_a = -1; + + /* not safe to have str_b point internally to the buffer */ + if (buf->size) + GIT_ASSERT_ARG(str_b < buf->ptr || str_b >= buf->ptr + buf->size); + + /* figure out if we need to insert a separator */ + if (separator && strlen_a) { + while (*str_b == separator) { str_b++; strlen_b--; } + if (str_a[strlen_a - 1] != separator) + need_sep = 1; + } + + /* str_a could be part of the buffer */ + if (buf->size && str_a >= buf->ptr && str_a < buf->ptr + buf->size) + offset_a = str_a - buf->ptr; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, strlen_a, strlen_b); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, need_sep); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1); + ENSURE_SIZE(buf, alloc_len); + + /* fix up internal pointers */ + if (offset_a >= 0) + str_a = buf->ptr + offset_a; + + /* do the actual copying */ + if (offset_a != 0 && str_a) + memmove(buf->ptr, str_a, strlen_a); + if (need_sep) + buf->ptr[strlen_a] = separator; + memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b); + + buf->size = strlen_a + strlen_b + need_sep; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +int git_str_join3( + git_str *buf, + char separator, + const char *str_a, + const char *str_b, + const char *str_c) +{ + size_t len_a = strlen(str_a), + len_b = strlen(str_b), + len_c = strlen(str_c), + len_total; + int sep_a = 0, sep_b = 0; + char *tgt; + + /* for this function, disallow pointers into the existing buffer */ + GIT_ASSERT(str_a < buf->ptr || str_a >= buf->ptr + buf->size); + GIT_ASSERT(str_b < buf->ptr || str_b >= buf->ptr + buf->size); + GIT_ASSERT(str_c < buf->ptr || str_c >= buf->ptr + buf->size); + + if (separator) { + if (len_a > 0) { + while (*str_b == separator) { str_b++; len_b--; } + sep_a = (str_a[len_a - 1] != separator); + } + if (len_a > 0 || len_b > 0) + while (*str_c == separator) { str_c++; len_c--; } + if (len_b > 0) + sep_b = (str_b[len_b - 1] != separator); + } + + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_a, sep_a); + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_b); + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, sep_b); + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, len_c); + GIT_ERROR_CHECK_ALLOC_ADD(&len_total, len_total, 1); + ENSURE_SIZE(buf, len_total); + + tgt = buf->ptr; + + if (len_a) { + memcpy(tgt, str_a, len_a); + tgt += len_a; + } + if (sep_a) + *tgt++ = separator; + if (len_b) { + memcpy(tgt, str_b, len_b); + tgt += len_b; + } + if (sep_b) + *tgt++ = separator; + if (len_c) + memcpy(tgt, str_c, len_c); + + buf->size = len_a + sep_a + len_b + sep_b + len_c; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +void git_str_rtrim(git_str *buf) +{ + while (buf->size > 0) { + if (!git__isspace(buf->ptr[buf->size - 1])) + break; + + buf->size--; + } + + if (buf->asize > buf->size) + buf->ptr[buf->size] = '\0'; +} + +int git_str_cmp(const git_str *a, const git_str *b) +{ + int result = memcmp(a->ptr, b->ptr, min(a->size, b->size)); + return (result != 0) ? result : + (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0; +} + +int git_str_splice( + git_str *buf, + size_t where, + size_t nb_to_remove, + const char *data, + size_t nb_to_insert) +{ + char *splice_loc; + size_t new_size, alloc_size; + + GIT_ASSERT(buf); + GIT_ASSERT(where <= buf->size); + GIT_ASSERT(nb_to_remove <= buf->size - where); + + splice_loc = buf->ptr + where; + + /* Ported from git.git + * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176 + */ + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, (buf->size - nb_to_remove), nb_to_insert); + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, new_size, 1); + ENSURE_SIZE(buf, alloc_size); + + memmove(splice_loc + nb_to_insert, + splice_loc + nb_to_remove, + buf->size - where - nb_to_remove); + + memcpy(splice_loc, data, nb_to_insert); + + buf->size = new_size; + buf->ptr[buf->size] = '\0'; + return 0; +} + +/* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */ +int git_str_quote(git_str *buf) +{ + const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' }; + git_str quoted = GIT_STR_INIT; + size_t i = 0; + bool quote = false; + int error = 0; + + /* walk to the first char that needs quoting */ + if (buf->size && buf->ptr[0] == '!') + quote = true; + + for (i = 0; !quote && i < buf->size; i++) { + if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' || + buf->ptr[i] < ' ' || buf->ptr[i] > '~') { + quote = true; + break; + } + } + + if (!quote) + goto done; + + git_str_putc("ed, '"'); + git_str_put("ed, buf->ptr, i); + + for (; i < buf->size; i++) { + /* whitespace - use the map above, which is ordered by ascii value */ + if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') { + git_str_putc("ed, '\\'); + git_str_putc("ed, whitespace[buf->ptr[i] - '\a']); + } + + /* double quote and backslash must be escaped */ + else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') { + git_str_putc("ed, '\\'); + git_str_putc("ed, buf->ptr[i]); + } + + /* escape anything unprintable as octal */ + else if (buf->ptr[i] != ' ' && + (buf->ptr[i] < '!' || buf->ptr[i] > '~')) { + git_str_printf("ed, "\\%03o", (unsigned char)buf->ptr[i]); + } + + /* yay, printable! */ + else { + git_str_putc("ed, buf->ptr[i]); + } + } + + git_str_putc("ed, '"'); + + if (git_str_oom("ed)) { + error = -1; + goto done; + } + + git_str_swap("ed, buf); + +done: + git_str_dispose("ed); + return error; +} + +/* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */ +int git_str_unquote(git_str *buf) +{ + size_t i, j; + char ch; + + git_str_rtrim(buf); + + if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"') + goto invalid; + + for (i = 0, j = 1; j < buf->size-1; i++, j++) { + ch = buf->ptr[j]; + + if (ch == '\\') { + if (j == buf->size-2) + goto invalid; + + ch = buf->ptr[++j]; + + switch (ch) { + /* \" or \\ simply copy the char in */ + case '"': case '\\': + break; + + /* add the appropriate escaped char */ + case 'a': ch = '\a'; break; + case 'b': ch = '\b'; break; + case 'f': ch = '\f'; break; + case 'n': ch = '\n'; break; + case 'r': ch = '\r'; break; + case 't': ch = '\t'; break; + case 'v': ch = '\v'; break; + + /* \xyz digits convert to the char*/ + case '0': case '1': case '2': case '3': + if (j == buf->size-3) { + git_error_set(GIT_ERROR_INVALID, + "truncated quoted character \\%c", ch); + return -1; + } + + if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' || + buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') { + git_error_set(GIT_ERROR_INVALID, + "truncated quoted character \\%c%c%c", + buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]); + return -1; + } + + ch = ((buf->ptr[j] - '0') << 6) | + ((buf->ptr[j+1] - '0') << 3) | + (buf->ptr[j+2] - '0'); + j += 2; + break; + + default: + git_error_set(GIT_ERROR_INVALID, "invalid quoted character \\%c", ch); + return -1; + } + } + + buf->ptr[i] = ch; + } + + buf->ptr[i] = '\0'; + buf->size = i; + + return 0; + +invalid: + git_error_set(GIT_ERROR_INVALID, "invalid quoted line"); + return -1; +} + +int git_str_puts_escaped( + git_str *buf, + const char *string, + const char *esc_chars, + const char *esc_with) +{ + const char *scan; + size_t total = 0, esc_len = strlen(esc_with), count, alloclen; + + if (!string) + return 0; + + for (scan = string; *scan; ) { + /* count run of non-escaped characters */ + count = strcspn(scan, esc_chars); + total += count; + scan += count; + /* count run of escaped characters */ + count = strspn(scan, esc_chars); + total += count * (esc_len + 1); + scan += count; + } + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, total, 1); + if (git_str_grow_by(buf, alloclen) < 0) + return -1; + + for (scan = string; *scan; ) { + count = strcspn(scan, esc_chars); + + memmove(buf->ptr + buf->size, scan, count); + scan += count; + buf->size += count; + + for (count = strspn(scan, esc_chars); count > 0; --count) { + /* copy escape sequence */ + memmove(buf->ptr + buf->size, esc_with, esc_len); + buf->size += esc_len; + /* copy character to be escaped */ + buf->ptr[buf->size] = *scan; + buf->size++; + scan++; + } + } + + buf->ptr[buf->size] = '\0'; + + return 0; +} + +void git_str_unescape(git_str *buf) +{ + buf->size = git__unescape(buf->ptr); +} + +int git_str_crlf_to_lf(git_str *tgt, const git_str *src) +{ + const char *scan = src->ptr; + const char *scan_end = src->ptr + src->size; + const char *next = memchr(scan, '\r', src->size); + size_t new_size; + char *out; + + GIT_ASSERT(tgt != src); + + if (!next) + return git_str_set(tgt, src->ptr, src->size); + + /* reduce reallocs while in the loop */ + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, src->size, 1); + if (git_str_grow(tgt, new_size) < 0) + return -1; + + out = tgt->ptr; + tgt->size = 0; + + /* Find the next \r and copy whole chunk up to there to tgt */ + for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) { + if (next > scan) { + size_t copylen = (size_t)(next - scan); + memcpy(out, scan, copylen); + out += copylen; + } + + /* Do not drop \r unless it is followed by \n */ + if (next + 1 == scan_end || next[1] != '\n') + *out++ = '\r'; + } + + /* Copy remaining input into dest */ + if (scan < scan_end) { + size_t remaining = (size_t)(scan_end - scan); + memcpy(out, scan, remaining); + out += remaining; + } + + tgt->size = (size_t)(out - tgt->ptr); + tgt->ptr[tgt->size] = '\0'; + + return 0; +} + +int git_str_lf_to_crlf(git_str *tgt, const git_str *src) +{ + const char *start = src->ptr; + const char *end = start + src->size; + const char *scan = start; + const char *next = memchr(scan, '\n', src->size); + size_t alloclen; + + GIT_ASSERT(tgt != src); + + if (!next) + return git_str_set(tgt, src->ptr, src->size); + + /* attempt to reduce reallocs while in the loop */ + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, src->size, src->size >> 4); + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1); + if (git_str_grow(tgt, alloclen) < 0) + return -1; + tgt->size = 0; + + for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) { + size_t copylen = next - scan; + + /* if we find mixed line endings, carry on */ + if (copylen && next[-1] == '\r') + copylen--; + + GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, copylen, 3); + if (git_str_grow_by(tgt, alloclen) < 0) + return -1; + + if (copylen) { + memcpy(tgt->ptr + tgt->size, scan, copylen); + tgt->size += copylen; + } + + tgt->ptr[tgt->size++] = '\r'; + tgt->ptr[tgt->size++] = '\n'; + } + + tgt->ptr[tgt->size] = '\0'; + return git_str_put(tgt, scan, end - scan); +} + +int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count) +{ + size_t i; + const char *str, *pfx; + + git_str_clear(buf); + + if (!strings || !count) + return 0; + + /* initialize common prefix to first string */ + if (git_str_sets(buf, strings[0]) < 0) + return -1; + + /* go through the rest of the strings, truncating to shared prefix */ + for (i = 1; i < count; ++i) { + + for (str = strings[i], pfx = buf->ptr; + *str && *str == *pfx; + str++, pfx++) + /* scanning */; + + git_str_truncate(buf, pfx - buf->ptr); + + if (!buf->size) + break; + } + + return 0; +} + +int git_str_is_binary(const git_str *buf) +{ + const char *scan = buf->ptr, *end = buf->ptr + buf->size; + git_str_bom_t bom; + int printable = 0, nonprintable = 0; + + scan += git_str_detect_bom(&bom, buf); + + if (bom > GIT_STR_BOM_UTF8) + return 1; + + while (scan < end) { + unsigned char c = *scan++; + + /* Printable characters are those above SPACE (0x1F) excluding DEL, + * and including BS, ESC and FF. + */ + if ((c > 0x1F && c != 127) || c == '\b' || c == '\033' || c == '\014') + printable++; + else if (c == '\0') + return true; + else if (!git__isspace(c)) + nonprintable++; + } + + return ((printable >> 7) < nonprintable); +} + +int git_str_contains_nul(const git_str *buf) +{ + return (memchr(buf->ptr, '\0', buf->size) != NULL); +} + +int git_str_detect_bom(git_str_bom_t *bom, const git_str *buf) +{ + const char *ptr; + size_t len; + + *bom = GIT_STR_BOM_NONE; + /* need at least 2 bytes to look for any BOM */ + if (buf->size < 2) + return 0; + + ptr = buf->ptr; + len = buf->size; + + switch (*ptr++) { + case 0: + if (len >= 4 && ptr[0] == 0 && ptr[1] == '\xFE' && ptr[2] == '\xFF') { + *bom = GIT_STR_BOM_UTF32_BE; + return 4; + } + break; + case '\xEF': + if (len >= 3 && ptr[0] == '\xBB' && ptr[1] == '\xBF') { + *bom = GIT_STR_BOM_UTF8; + return 3; + } + break; + case '\xFE': + if (*ptr == '\xFF') { + *bom = GIT_STR_BOM_UTF16_BE; + return 2; + } + break; + case '\xFF': + if (*ptr != '\xFE') + break; + if (len >= 4 && ptr[1] == 0 && ptr[2] == 0) { + *bom = GIT_STR_BOM_UTF32_LE; + return 4; + } else { + *bom = GIT_STR_BOM_UTF16_LE; + return 2; + } + break; + default: + break; + } + + return 0; +} + +bool git_str_gather_text_stats( + git_str_text_stats *stats, const git_str *buf, bool skip_bom) +{ + const char *scan = buf->ptr, *end = buf->ptr + buf->size; + int skip; + + memset(stats, 0, sizeof(*stats)); + + /* BOM detection */ + skip = git_str_detect_bom(&stats->bom, buf); + if (skip_bom) + scan += skip; + + /* Ignore EOF character */ + if (buf->size > 0 && end[-1] == '\032') + end--; + + /* Counting loop */ + while (scan < end) { + unsigned char c = *scan++; + + if (c > 0x1F && c != 0x7F) + stats->printable++; + else switch (c) { + case '\0': + stats->nul++; + stats->nonprintable++; + break; + case '\n': + stats->lf++; + break; + case '\r': + stats->cr++; + if (scan < end && *scan == '\n') + stats->crlf++; + break; + case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/ + stats->printable++; + break; + default: + stats->nonprintable++; + break; + } + } + + /* Treat files with a bare CR as binary */ + return (stats->cr != stats->crlf || stats->nul > 0 || + ((stats->printable >> 7) < stats->nonprintable)); +} diff --git a/src/util/str.h b/src/util/str.h new file mode 100644 index 000000000..588e6fc22 --- /dev/null +++ b/src/util/str.h @@ -0,0 +1,357 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_str_h__ +#define INCLUDE_str_h__ + +#include "git2_util.h" + +struct git_str { + char *ptr; + size_t asize; + size_t size; +}; + +typedef enum { + GIT_STR_BOM_NONE = 0, + GIT_STR_BOM_UTF8 = 1, + GIT_STR_BOM_UTF16_LE = 2, + GIT_STR_BOM_UTF16_BE = 3, + GIT_STR_BOM_UTF32_LE = 4, + GIT_STR_BOM_UTF32_BE = 5 +} git_str_bom_t; + +typedef struct { + git_str_bom_t bom; /* BOM found at head of text */ + unsigned int nul, cr, lf, crlf; /* NUL, CR, LF and CRLF counts */ + unsigned int printable, nonprintable; /* These are just approximations! */ +} git_str_text_stats; + +extern char git_str__initstr[]; +extern char git_str__oom[]; + +/* Use to initialize string buffer structure when git_str is on stack */ +#define GIT_STR_INIT { git_str__initstr, 0, 0 } + +/** + * Static initializer for git_str from static string buffer + */ +#define GIT_STR_INIT_CONST(str, len) { (char *)(str), 0, (size_t)(len) } + +GIT_INLINE(bool) git_str_is_allocated(const git_str *str) +{ + return (str->ptr != NULL && str->asize > 0); +} + +/** + * Initialize a git_str structure. + * + * For the cases where GIT_STR_INIT cannot be used to do static + * initialization. + */ +extern int git_str_init(git_str *str, size_t initial_size); + +extern void git_str_dispose(git_str *str); + +/** + * Resize the string buffer allocation to make more space. + * + * This will attempt to grow the string buffer to accommodate the target + * size. The bstring buffer's `ptr` will be replaced with a newly + * allocated block of data. Be careful so that memory allocated by the + * caller is not lost. As a special variant, if you pass `target_size` as + * 0 and the memory is not allocated by libgit2, this will allocate a new + * buffer of size `size` and copy the external data into it. + * + * Currently, this will never shrink a buffer, only expand it. + * + * If the allocation fails, this will return an error and the buffer will be + * marked as invalid for future operations, invaliding the contents. + * + * @param str The buffer to be resized; may or may not be allocated yet + * @param target_size The desired available size + * @return 0 on success, -1 on allocation failure + */ +int git_str_grow(git_str *str, size_t target_size); + +/** + * Resize the buffer allocation to make more space. + * + * This will attempt to grow the string buffer to accommodate the + * additional size. It is similar to `git_str_grow`, but performs the + * new size calculation, checking for overflow. + * + * Like `git_str_grow`, if this is a user-supplied string buffer, + * this will allocate a new string uffer. + */ +extern int git_str_grow_by(git_str *str, size_t additional_size); + +/** + * Attempt to grow the buffer to hold at least `target_size` bytes. + * + * If the allocation fails, this will return an error. If `mark_oom` is + * true, this will mark the string buffer as invalid for future + * operations; if false, existing string buffer content will be preserved, + * but calling code must handle that string buffer was not expanded. If + * `preserve_external` is true, then any existing data pointed to be + * `ptr` even if `asize` is zero will be copied into the newly allocated + * string buffer. + */ +extern int git_str_try_grow( + git_str *str, size_t target_size, bool mark_oom); + +extern void git_str_swap(git_str *str_a, git_str *str_b); +extern char *git_str_detach(git_str *str); +extern int git_str_attach(git_str *str, char *ptr, size_t asize); + +/* Populates a `git_str` where the contents are not "owned" by the string + * buffer, and calls to `git_str_dispose` will not free the given str. + */ +extern void git_str_attach_notowned( + git_str *str, const char *ptr, size_t size); + +/** + * Test if there have been any reallocation failures with this git_str. + * + * Any function that writes to a git_str can fail due to memory allocation + * issues. If one fails, the git_str will be marked with an OOM error and + * further calls to modify the string buffer will fail. Check + * git_str_oom() at the end of your sequence and it will be true if you + * ran out of memory at any point with that string buffer. + * + * @return false if no error, true if allocation error + */ +GIT_INLINE(bool) git_str_oom(const git_str *str) +{ + return (str->ptr == git_str__oom); +} + +/* + * Functions below that return int value error codes will return 0 on + * success or -1 on failure (which generally means an allocation failed). + * Using a git_str where the allocation has failed with result in -1 from + * all further calls using that string buffer. As a result, you can + * ignore the return code of these functions and call them in a series + * then just call git_str_oom at the end. + */ + +int git_str_set(git_str *str, const void *data, size_t datalen); + +int git_str_sets(git_str *str, const char *string); +int git_str_putc(git_str *str, char c); +int git_str_putcn(git_str *str, char c, size_t len); +int git_str_put(git_str *str, const char *data, size_t len); +int git_str_puts(git_str *str, const char *string); +int git_str_printf(git_str *str, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); +int git_str_vprintf(git_str *str, const char *format, va_list ap); +void git_str_clear(git_str *str); +void git_str_consume_bytes(git_str *str, size_t len); +void git_str_consume(git_str *str, const char *end); +void git_str_truncate(git_str *str, size_t len); +void git_str_shorten(git_str *str, size_t amount); +void git_str_truncate_at_char(git_str *path, char separator); +void git_str_rtruncate_at_char(git_str *path, char separator); + +/** General join with separator */ +int git_str_join_n(git_str *str, char separator, int len, ...); +/** Fast join of two strings - first may legally point into `str` data */ +int git_str_join(git_str *str, char separator, const char *str_a, const char *str_b); +/** Fast join of three strings - cannot reference `str` data */ +int git_str_join3(git_str *str, char separator, const char *str_a, const char *str_b, const char *str_c); + +/** + * Join two strings as paths, inserting a slash between as needed. + * @return 0 on success, -1 on failure + */ +GIT_INLINE(int) git_str_joinpath(git_str *str, const char *a, const char *b) +{ + return git_str_join(str, '/', a, b); +} + +GIT_INLINE(const char *) git_str_cstr(const git_str *str) +{ + return str->ptr; +} + +GIT_INLINE(size_t) git_str_len(const git_str *str) +{ + return str->size; +} + +int git_str_copy_cstr(char *data, size_t datasize, const git_str *str); + +#define git_str_PUTS(str, cstr) git_str_put(str, cstr, sizeof(cstr) - 1) + +GIT_INLINE(ssize_t) git_str_rfind_next(const git_str *str, char ch) +{ + ssize_t idx = (ssize_t)str->size - 1; + while (idx >= 0 && str->ptr[idx] == ch) idx--; + while (idx >= 0 && str->ptr[idx] != ch) idx--; + return idx; +} + +GIT_INLINE(ssize_t) git_str_rfind(const git_str *str, char ch) +{ + ssize_t idx = (ssize_t)str->size - 1; + while (idx >= 0 && str->ptr[idx] != ch) idx--; + return idx; +} + +GIT_INLINE(ssize_t) git_str_find(const git_str *str, char ch) +{ + void *found = memchr(str->ptr, ch, str->size); + return found ? (ssize_t)((const char *)found - str->ptr) : -1; +} + +/* Remove whitespace from the end of the string buffer */ +void git_str_rtrim(git_str *str); + +int git_str_cmp(const git_str *a, const git_str *b); + +/* Quote and unquote a string buffer as specified in + * http://marc.info/?l=git&m=112927316408690&w=2 + */ +int git_str_quote(git_str *str); +int git_str_unquote(git_str *str); + +/* Write data as a hex string */ +int git_str_encode_hexstr(git_str *str, const char *data, size_t len); + +/* Write data as base64 encoded in string buffer */ +int git_str_encode_base64(git_str *str, const char *data, size_t len); +/* Decode the given bas64 and write the result to the string buffer */ +int git_str_decode_base64(git_str *str, const char *base64, size_t len); + +/* Write data as "base85" encoded in string buffer */ +int git_str_encode_base85(git_str *str, const char *data, size_t len); +/* Decode the given "base85" and write the result to the string buffer */ +int git_str_decode_base85(git_str *str, const char *base64, size_t len, size_t output_len); + +/* + * Decode the given percent-encoded string and write the result to the + * string buffer. + */ +int git_str_decode_percent(git_str *str, const char *encoded, size_t len); + +/* + * Insert, remove or replace a portion of the string buffer. + * + * @param str The string buffer to work with + * + * @param where The location in the string buffer where the transformation + * should be applied. + * + * @param nb_to_remove The number of chars to be removed. 0 to not + * remove any character in the string buffer. + * + * @param data A pointer to the data which should be inserted. + * + * @param nb_to_insert The number of chars to be inserted. 0 to not + * insert any character from the string buffer. + * + * @return 0 or an error code. + */ +int git_str_splice( + git_str *str, + size_t where, + size_t nb_to_remove, + const char *data, + size_t nb_to_insert); + +/** + * Append string to string buffer, prefixing each character from + * `esc_chars` with `esc_with` string. + * + * @param str String buffer to append data to + * @param string String to escape and append + * @param esc_chars Characters to be escaped + * @param esc_with String to insert in from of each found character + * @return 0 on success, <0 on failure (probably allocation problem) + */ +extern int git_str_puts_escaped( + git_str *str, + const char *string, + const char *esc_chars, + const char *esc_with); + +/** + * Append string escaping characters that are regex special + */ +GIT_INLINE(int) git_str_puts_escape_regex(git_str *str, const char *string) +{ + return git_str_puts_escaped(str, string, "^.[]$()|*+?{}\\", "\\"); +} + +/** + * Unescape all characters in a string buffer in place + * + * I.e. remove backslashes + */ +extern void git_str_unescape(git_str *str); + +/** + * Replace all \r\n with \n. + * + * @return 0 on success, -1 on memory error + */ +extern int git_str_crlf_to_lf(git_str *tgt, const git_str *src); + +/** + * Replace all \n with \r\n. Does not modify existing \r\n. + * + * @return 0 on success, -1 on memory error + */ +extern int git_str_lf_to_crlf(git_str *tgt, const git_str *src); + +/** + * Fill string buffer with the common prefix of a array of strings + * + * String buffer will be set to empty if there is no common prefix + */ +extern int git_str_common_prefix(git_str *buf, char *const *const strings, size_t count); + +/** + * Check if a string buffer begins with a UTF BOM + * + * @param bom Set to the type of BOM detected or GIT_BOM_NONE + * @param str String buffer in which to check the first bytes for a BOM + * @return Number of bytes of BOM data (or 0 if no BOM found) + */ +extern int git_str_detect_bom(git_str_bom_t *bom, const git_str *str); + +/** + * Gather stats for a piece of text + * + * Fill the `stats` structure with counts of unreadable characters, carriage + * returns, etc, so it can be used in heuristics. This automatically skips + * a trailing EOF (\032 character). Also it will look for a BOM at the + * start of the text and can be told to skip that as well. + * + * @param stats Structure to be filled in + * @param str Text to process + * @param skip_bom Exclude leading BOM from stats if true + * @return Does the string buffer heuristically look like binary data + */ +extern bool git_str_gather_text_stats( + git_str_text_stats *stats, const git_str *str, bool skip_bom); + +/** +* Check quickly if string buffer looks like it contains binary data +* +* @param str string buffer to check +* @return 1 if string buffer looks like non-text data +*/ +int git_str_is_binary(const git_str *str); + +/** +* Check quickly if buffer contains a NUL byte +* +* @param str string buffer to check +* @return 1 if string buffer contains a NUL byte +*/ +int git_str_contains_nul(const git_str *str); + +#endif diff --git a/src/util/strmap.c b/src/util/strmap.c new file mode 100644 index 000000000..c6e5b6dc7 --- /dev/null +++ b/src/util/strmap.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "strmap.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kreallocarray git__reallocarray +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(str, const char *, void *) + +__KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) + +int git_strmap_new(git_strmap **out) +{ + *out = kh_init(str); + GIT_ERROR_CHECK_ALLOC(*out); + + return 0; +} + +void git_strmap_free(git_strmap *map) +{ + kh_destroy(str, map); +} + +void git_strmap_clear(git_strmap *map) +{ + kh_clear(str, map); +} + +size_t git_strmap_size(git_strmap *map) +{ + return kh_size(map); +} + +void *git_strmap_get(git_strmap *map, const char *key) +{ + size_t idx = kh_get(str, map, key); + if (idx == kh_end(map) || !kh_exist(map, idx)) + return NULL; + return kh_val(map, idx); +} + +int git_strmap_set(git_strmap *map, const char *key, void *value) +{ + size_t idx; + int rval; + + idx = kh_put(str, map, key, &rval); + if (rval < 0) + return -1; + + if (rval == 0) + kh_key(map, idx) = key; + + kh_val(map, idx) = value; + + return 0; +} + +int git_strmap_delete(git_strmap *map, const char *key) +{ + khiter_t idx = kh_get(str, map, key); + if (idx == kh_end(map)) + return GIT_ENOTFOUND; + kh_del(str, map, idx); + return 0; +} + +int git_strmap_exists(git_strmap *map, const char *key) +{ + return kh_get(str, map, key) != kh_end(map); +} + +int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key) +{ + size_t i = *iter; + + while (i < map->n_buckets && !kh_exist(map, i)) + i++; + + if (i >= map->n_buckets) + return GIT_ITEROVER; + + if (key) + *key = kh_key(map, i); + if (value) + *value = kh_val(map, i); + *iter = ++i; + + return 0; +} diff --git a/src/util/strmap.h b/src/util/strmap.h new file mode 100644 index 000000000..b64d3dcb5 --- /dev/null +++ b/src/util/strmap.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_strmap_h__ +#define INCLUDE_strmap_h__ + +#include "git2_util.h" + +/** A map with C strings as key. */ +typedef struct kh_str_s git_strmap; + +/** + * Allocate a new string map. + * + * @param out Pointer to the map that shall be allocated. + * @return 0 on success, an error code if allocation has failed. + */ +int git_strmap_new(git_strmap **out); + +/** + * Free memory associated with the map. + * + * Note that this function will _not_ free keys or values added + * to this map. + * + * @param map Pointer to the map that is to be free'd. May be + * `NULL`. + */ +void git_strmap_free(git_strmap *map); + +/** + * Clear all entries from the map. + * + * This function will remove all entries from the associated map. + * Memory associated with it will not be released, though. + * + * @param map Pointer to the map that shall be cleared. May be + * `NULL`. + */ +void git_strmap_clear(git_strmap *map); + +/** + * Return the number of elements in the map. + * + * @parameter map map containing the elements + * @return number of elements in the map + */ +size_t git_strmap_size(git_strmap *map); + +/** + * Return value associated with the given key. + * + * @param map map to search key in + * @param key key to search for + * @return value associated with the given key or NULL if the key was not found + */ +void *git_strmap_get(git_strmap *map, const char *key); + +/** + * Set the entry for key to value. + * + * If the map has no corresponding entry for the given key, a new + * entry will be created with the given value. If an entry exists + * already, its value will be updated to match the given value. + * + * @param map map to create new entry in + * @param key key to set + * @param value value to associate the key with; may be NULL + * @return zero if the key was successfully set, a negative error + * code otherwise + */ +int git_strmap_set(git_strmap *map, const char *key, void *value); + +/** + * Delete an entry from the map. + * + * Delete the given key and its value from the map. If no such + * key exists, this will do nothing. + * + * @param map map to delete key in + * @param key key to delete + * @return `0` if the key has been deleted, GIT_ENOTFOUND if no + * such key was found, a negative code in case of an + * error + */ +int git_strmap_delete(git_strmap *map, const char *key); + +/** + * Check whether a key exists in the given map. + * + * @param map map to query for the key + * @param key key to search for + * @return 0 if the key has not been found, 1 otherwise + */ +int git_strmap_exists(git_strmap *map, const char *key); + +/** + * Iterate over entries of the map. + * + * This functions allows to iterate over all key-value entries of + * the map. The current position is stored in the `iter` variable + * and should be initialized to `0` before the first call to this + * function. + * + * @param map map to iterate over + * @param value pointer to the variable where to store the current + * value. May be NULL. + * @param iter iterator storing the current position. Initialize + * with zero previous to the first call. + * @param key pointer to the variable where to store the current + * key. May be NULL. + * @return `0` if the next entry was correctly retrieved. + * GIT_ITEROVER if no entries are left. A negative error + * code otherwise. + */ +int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key); + +#define git_strmap_foreach(h, kvar, vvar, code) { size_t __i = 0; \ + while (git_strmap_iterate((void **) &(vvar), h, &__i, &(kvar)) == 0) { \ + code; \ + } } + +#define git_strmap_foreach_value(h, vvar, code) { size_t __i = 0; \ + while (git_strmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ + code; \ + } } + +#endif diff --git a/src/util/strnlen.h b/src/util/strnlen.h new file mode 100644 index 000000000..eecfe3c02 --- /dev/null +++ b/src/util/strnlen.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_strlen_h__ +#define INCLUDE_strlen_h__ + +#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) || defined(__MidnightBSD__) ||\ + (defined(_MSC_VER) && _MSC_VER < 1500) +# define NO_STRNLEN +#endif + +#ifdef NO_STRNLEN +GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) { + const char *end = memchr(s, 0, maxlen); + return end ? (size_t)(end - s) : maxlen; +} +#else +# define p_strnlen strnlen +#endif + +#endif diff --git a/src/util/thread.c b/src/util/thread.c new file mode 100644 index 000000000..bc7364f8c --- /dev/null +++ b/src/util/thread.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2_util.h" + +#if !defined(GIT_THREADS) + +#define TLSDATA_MAX 16 + +typedef struct { + void *value; + void (GIT_SYSTEM_CALL *destroy_fn)(void *); +} tlsdata_value; + +static tlsdata_value tlsdata_values[TLSDATA_MAX]; +static int tlsdata_cnt = 0; + +int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) +{ + if (tlsdata_cnt >= TLSDATA_MAX) + return -1; + + tlsdata_values[tlsdata_cnt].value = NULL; + tlsdata_values[tlsdata_cnt].destroy_fn = destroy_fn; + + *key = tlsdata_cnt; + tlsdata_cnt++; + + return 0; +} + +int git_tlsdata_set(git_tlsdata_key key, void *value) +{ + if (key < 0 || key > tlsdata_cnt) + return -1; + + tlsdata_values[key].value = value; + return 0; +} + +void *git_tlsdata_get(git_tlsdata_key key) +{ + if (key < 0 || key > tlsdata_cnt) + return NULL; + + return tlsdata_values[key].value; +} + +int git_tlsdata_dispose(git_tlsdata_key key) +{ + void *value; + void (*destroy_fn)(void *) = NULL; + + if (key < 0 || key > tlsdata_cnt) + return -1; + + value = tlsdata_values[key].value; + destroy_fn = tlsdata_values[key].destroy_fn; + + tlsdata_values[key].value = NULL; + tlsdata_values[key].destroy_fn = NULL; + + if (value && destroy_fn) + destroy_fn(value); + + return 0; +} + +#elif defined(GIT_WIN32) + +int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) +{ + DWORD fls_index = FlsAlloc(destroy_fn); + + if (fls_index == FLS_OUT_OF_INDEXES) + return -1; + + *key = fls_index; + return 0; +} + +int git_tlsdata_set(git_tlsdata_key key, void *value) +{ + if (!FlsSetValue(key, value)) + return -1; + + return 0; +} + +void *git_tlsdata_get(git_tlsdata_key key) +{ + return FlsGetValue(key); +} + +int git_tlsdata_dispose(git_tlsdata_key key) +{ + if (!FlsFree(key)) + return -1; + + return 0; +} + +#elif defined(_POSIX_THREADS) + +int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)) +{ + if (pthread_key_create(key, destroy_fn) != 0) + return -1; + + return 0; +} + +int git_tlsdata_set(git_tlsdata_key key, void *value) +{ + if (pthread_setspecific(key, value) != 0) + return -1; + + return 0; +} + +void *git_tlsdata_get(git_tlsdata_key key) +{ + return pthread_getspecific(key); +} + +int git_tlsdata_dispose(git_tlsdata_key key) +{ + if (pthread_key_delete(key) != 0) + return -1; + + return 0; +} + +#else +# error unknown threading model +#endif diff --git a/src/util/thread.h b/src/util/thread.h new file mode 100644 index 000000000..4bbac9fd8 --- /dev/null +++ b/src/util/thread.h @@ -0,0 +1,479 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_thread_h__ +#define INCLUDE_thread_h__ + +#if defined(GIT_THREADS) + +#if defined(__clang__) + +# if (__clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ < 1)) +# error Atomic primitives do not exist on this version of clang; configure libgit2 with -DUSE_THREADS=OFF +# else +# define GIT_BUILTIN_ATOMIC +# endif + +#elif defined(__GNUC__) + +# if (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 1)) +# error Atomic primitives do not exist on this version of gcc; configure libgit2 with -DUSE_THREADS=OFF +# elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) +# define GIT_BUILTIN_ATOMIC +# else +# define GIT_BUILTIN_SYNC +# endif + +#endif + +#endif /* GIT_THREADS */ + +/* Common operations even if threading has been disabled */ +typedef struct { +#if defined(GIT_WIN32) + volatile long val; +#else + volatile int val; +#endif +} git_atomic32; + +#ifdef GIT_ARCH_64 + +typedef struct { +#if defined(GIT_WIN32) + volatile __int64 val; +#else + volatile int64_t val; +#endif +} git_atomic64; + +typedef git_atomic64 git_atomic_ssize; + +#define git_atomic_ssize_set git_atomic64_set +#define git_atomic_ssize_add git_atomic64_add +#define git_atomic_ssize_get git_atomic64_get + +#else + +typedef git_atomic32 git_atomic_ssize; + +#define git_atomic_ssize_set git_atomic32_set +#define git_atomic_ssize_add git_atomic32_add +#define git_atomic_ssize_get git_atomic32_get + +#endif + +#ifdef GIT_THREADS + +#ifdef GIT_WIN32 +# include "win32/thread.h" +#else +# include "unix/pthread.h" +#endif + +/* + * Atomically sets the contents of *a to be val. + */ +GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) +{ +#if defined(GIT_WIN32) + InterlockedExchange(&a->val, (LONG)val); +#elif defined(GIT_BUILTIN_ATOMIC) + __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + __sync_lock_test_and_set(&a->val, val); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically increments the contents of *a by 1, and stores the result back into *a. + * @return the result of the operation. + */ +GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) +{ +#if defined(GIT_WIN32) + return InterlockedIncrement(&a->val); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_add_fetch(&a->val, 1, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_add_and_fetch(&a->val, 1); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically adds the contents of *a and addend, and stores the result back into *a. + * @return the result of the operation. + */ +GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) +{ +#if defined(GIT_WIN32) + return InterlockedAdd(&a->val, addend); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_add_and_fetch(&a->val, addend); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically decrements the contents of *a by 1, and stores the result back into *a. + * @return the result of the operation. + */ +GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) +{ +#if defined(GIT_WIN32) + return InterlockedDecrement(&a->val); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_sub_fetch(&a->val, 1, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_sub_and_fetch(&a->val, 1); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically gets the contents of *a. + * @return the contents of *a. + */ +GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) +{ +#if defined(GIT_WIN32) + return (int)InterlockedCompareExchange(&a->val, 0, 0); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_val_compare_and_swap(&a->val, 0, 0); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +GIT_INLINE(void *) git_atomic__compare_and_swap( + void * volatile *ptr, void *oldval, void *newval) +{ +#if defined(GIT_WIN32) + return InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); +#elif defined(GIT_BUILTIN_ATOMIC) + void *foundval = oldval; + __atomic_compare_exchange(ptr, &foundval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + return foundval; +#elif defined(GIT_BUILTIN_SYNC) + return __sync_val_compare_and_swap(ptr, oldval, newval); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +GIT_INLINE(volatile void *) git_atomic__swap( + void * volatile *ptr, void *newval) +{ +#if defined(GIT_WIN32) + return InterlockedExchangePointer(ptr, newval); +#elif defined(GIT_BUILTIN_ATOMIC) + void * foundval = NULL; + __atomic_exchange(ptr, &newval, &foundval, __ATOMIC_SEQ_CST); + return foundval; +#elif defined(GIT_BUILTIN_SYNC) + return (volatile void *)__sync_lock_test_and_set(ptr, newval); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) +{ +#if defined(GIT_WIN32) + void *newval = NULL, *oldval = NULL; + return (volatile void *)InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval); +#elif defined(GIT_BUILTIN_ATOMIC) + return (volatile void *)__atomic_load_n(ptr, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return (volatile void *)__sync_val_compare_and_swap(ptr, 0, 0); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +#ifdef GIT_ARCH_64 + +/* + * Atomically adds the contents of *a and addend, and stores the result back into *a. + * @return the result of the operation. + */ +GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) +{ +#if defined(GIT_WIN32) + return InterlockedAdd64(&a->val, addend); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_add_and_fetch(&a->val, addend); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically sets the contents of *a to be val. + */ +GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) +{ +#if defined(GIT_WIN32) + InterlockedExchange64(&a->val, val); +#elif defined(GIT_BUILTIN_ATOMIC) + __atomic_store_n(&a->val, val, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + __sync_lock_test_and_set(&a->val, val); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +/* + * Atomically gets the contents of *a. + * @return the contents of *a. + */ +GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) +{ +#if defined(GIT_WIN32) + return (int64_t)InterlockedCompareExchange64(&a->val, 0, 0); +#elif defined(GIT_BUILTIN_ATOMIC) + return __atomic_load_n(&a->val, __ATOMIC_SEQ_CST); +#elif defined(GIT_BUILTIN_SYNC) + return __sync_val_compare_and_swap(&a->val, 0, 0); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +#endif + +#else + +#define git_threads_global_init git__noop + +#define git_thread unsigned int +#define git_thread_create(thread, start_routine, arg) git__noop() +#define git_thread_join(id, status) git__noop() + +/* Pthreads Mutex */ +#define git_mutex unsigned int +#define git_mutex_init(a) git__noop() +#define git_mutex_init(a) git__noop() +#define git_mutex_lock(a) git__noop() +#define git_mutex_unlock(a) git__noop() +#define git_mutex_free(a) git__noop() + +/* Pthreads condition vars */ +#define git_cond unsigned int +#define git_cond_init(c) git__noop() +#define git_cond_free(c) git__noop() +#define git_cond_wait(c, l) git__noop() +#define git_cond_signal(c) git__noop() +#define git_cond_broadcast(c) git__noop() + +/* Pthreads rwlock */ +#define git_rwlock unsigned int +#define git_rwlock_init(a) git__noop() +#define git_rwlock_rdlock(a) git__noop() +#define git_rwlock_rdunlock(a) git__noop() +#define git_rwlock_wrlock(a) git__noop() +#define git_rwlock_wrunlock(a) git__noop() +#define git_rwlock_free(a) git__noop() +#define GIT_RWLOCK_STATIC_INIT 0 + + +GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) +{ + a->val = val; +} + +GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) +{ + return ++a->val; +} + +GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) +{ + a->val += addend; + return a->val; +} + +GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) +{ + return --a->val; +} + +GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) +{ + return (int)a->val; +} + +GIT_INLINE(void *) git_atomic__compare_and_swap( + void * volatile *ptr, void *oldval, void *newval) +{ + void *foundval = *ptr; + if (foundval == oldval) + *ptr = newval; + return foundval; +} + +GIT_INLINE(volatile void *) git_atomic__swap( + void * volatile *ptr, void *newval) +{ + volatile void *old = *ptr; + *ptr = newval; + return old; +} + +GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) +{ + return *ptr; +} + +#ifdef GIT_ARCH_64 + +GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) +{ + a->val += addend; + return a->val; +} + +GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) +{ + a->val = val; +} + +GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) +{ + return (int64_t)a->val; +} + +#endif + +#endif + +/* + * Atomically replace the contents of *ptr (if they are equal to oldval) with + * newval. ptr must point to a pointer or a value that is the same size as a + * pointer. This is semantically compatible with: + * + * #define git_atomic_compare_and_swap(ptr, oldval, newval) \ + * ({ \ + * void *foundval = *ptr; \ + * if (foundval == oldval) \ + * *ptr = newval; \ + * foundval; \ + * }) + * + * @return the original contents of *ptr. + */ +#define git_atomic_compare_and_swap(ptr, oldval, newval) \ + git_atomic__compare_and_swap((void * volatile *)ptr, oldval, newval) + +/* + * Atomically replace the contents of v with newval. v must be the same size as + * a pointer. This is semantically compatible with: + * + * #define git_atomic_swap(v, newval) \ + * ({ \ + * volatile void *old = v; \ + * v = newval; \ + * old; \ + * }) + * + * @return the original contents of v. + */ +#define git_atomic_swap(v, newval) \ + (void *)git_atomic__swap((void * volatile *)&(v), newval) + +/* + * Atomically reads the contents of v. v must be the same size as a pointer. + * This is semantically compatible with: + * + * #define git_atomic_load(v) v + * + * @return the contents of v. + */ +#define git_atomic_load(v) \ + (void *)git_atomic__load((void * volatile *)&(v)) + +#if defined(GIT_THREADS) + +# if defined(GIT_WIN32) +# define GIT_MEMORY_BARRIER MemoryBarrier() +# elif defined(GIT_BUILTIN_ATOMIC) +# define GIT_MEMORY_BARRIER __atomic_thread_fence(__ATOMIC_SEQ_CST) +# elif defined(GIT_BUILTIN_SYNC) +# define GIT_MEMORY_BARRIER __sync_synchronize() +# endif + +#else + +# define GIT_MEMORY_BARRIER /* noop */ + +#endif + +/* Thread-local data */ + +#if !defined(GIT_THREADS) +# define git_tlsdata_key int +#elif defined(GIT_WIN32) +# define git_tlsdata_key DWORD +#elif defined(_POSIX_THREADS) +# define git_tlsdata_key pthread_key_t +#else +# error unknown threading model +#endif + +/** + * Create a thread-local data key. The destroy function will be + * called upon thread exit. On some platforms, it may be called + * when all threads have deleted their keys. + * + * Note that the tlsdata functions do not set an error message on + * failure; this is because the error handling in libgit2 is itself + * handled by thread-local data storage. + * + * @param key the tlsdata key + * @param destroy_fn function pointer called upon thread exit + * @return 0 on success, non-zero on failure + */ +int git_tlsdata_init(git_tlsdata_key *key, void (GIT_SYSTEM_CALL *destroy_fn)(void *)); + +/** + * Set a the thread-local value for the given key. + * + * @param key the tlsdata key to store data on + * @param value the pointer to store + * @return 0 on success, non-zero on failure + */ +int git_tlsdata_set(git_tlsdata_key key, void *value); + +/** + * Get the thread-local value for the given key. + * + * @param key the tlsdata key to retrieve the value of + * @return the pointer stored with git_tlsdata_set + */ +void *git_tlsdata_get(git_tlsdata_key key); + +/** + * Delete the given thread-local key. + * + * @param key the tlsdata key to dispose + * @return 0 on success, non-zero on failure + */ +int git_tlsdata_dispose(git_tlsdata_key key); + +#endif diff --git a/src/util/tsort.c b/src/util/tsort.c new file mode 100644 index 000000000..2ef03d03a --- /dev/null +++ b/src/util/tsort.c @@ -0,0 +1,382 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2_util.h" + +/** + * An array-of-pointers implementation of Python's Timsort + * Based on code by Christopher Swenson under the MIT license + * + * Copyright (c) 2010 Christopher Swenson + * Copyright (c) 2011 Vicent Marti + */ + +#ifndef MAX +# define MAX(x,y) (((x) > (y) ? (x) : (y))) +#endif + +#ifndef MIN +# define MIN(x,y) (((x) < (y) ? (x) : (y))) +#endif + +static int binsearch( + void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload) +{ + int l, c, r; + void *lx, *cx; + + l = 0; + r = (int)size - 1; + c = r >> 1; + lx = dst[l]; + + /* check for beginning conditions */ + if (cmp(x, lx, payload) < 0) + return 0; + + else if (cmp(x, lx, payload) == 0) { + int i = 1; + while (cmp(x, dst[i], payload) == 0) + i++; + return i; + } + + /* guaranteed not to be >= rx */ + cx = dst[c]; + while (1) { + const int val = cmp(x, cx, payload); + if (val < 0) { + if (c - l <= 1) return c; + r = c; + } else if (val > 0) { + if (r - c <= 1) return c + 1; + l = c; + lx = cx; + } else { + do { + cx = dst[++c]; + } while (cmp(x, cx, payload) == 0); + return c; + } + c = l + ((r - l) >> 1); + cx = dst[c]; + } +} + +/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */ +static void bisort( + void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload) +{ + size_t i; + void *x; + int location; + + for (i = start; i < size; i++) { + int j; + /* If this entry is already correct, just move along */ + if (cmp(dst[i - 1], dst[i], payload) <= 0) + continue; + + /* Else we need to find the right place, shift everything over, and squeeze in */ + x = dst[i]; + location = binsearch(dst, x, i, cmp, payload); + for (j = (int)i - 1; j >= location; j--) { + dst[j + 1] = dst[j]; + } + dst[location] = x; + } +} + + +/* timsort implementation, based on timsort.txt */ +struct tsort_run { + ssize_t start; + ssize_t length; +}; + +struct tsort_store { + size_t alloc; + git__sort_r_cmp cmp; + void *payload; + void **storage; +}; + +static void reverse_elements(void **dst, ssize_t start, ssize_t end) +{ + while (start < end) { + void *tmp = dst[start]; + dst[start] = dst[end]; + dst[end] = tmp; + + start++; + end--; + } +} + +static ssize_t count_run( + void **dst, ssize_t start, ssize_t size, struct tsort_store *store) +{ + ssize_t curr = start + 2; + + if (size - start == 1) + return 1; + + if (start >= size - 2) { + if (store->cmp(dst[size - 2], dst[size - 1], store->payload) > 0) { + void *tmp = dst[size - 1]; + dst[size - 1] = dst[size - 2]; + dst[size - 2] = tmp; + } + + return 2; + } + + if (store->cmp(dst[start], dst[start + 1], store->payload) <= 0) { + while (curr < size - 1 && + store->cmp(dst[curr - 1], dst[curr], store->payload) <= 0) + curr++; + + return curr - start; + } else { + while (curr < size - 1 && + store->cmp(dst[curr - 1], dst[curr], store->payload) > 0) + curr++; + + /* reverse in-place */ + reverse_elements(dst, start, curr - 1); + return curr - start; + } +} + +static size_t compute_minrun(size_t n) +{ + int r = 0; + while (n >= 64) { + r |= n & 1; + n >>= 1; + } + return n + r; +} + +static int check_invariant(struct tsort_run *stack, ssize_t stack_curr) +{ + if (stack_curr < 2) + return 1; + + else if (stack_curr == 2) { + const ssize_t A = stack[stack_curr - 2].length; + const ssize_t B = stack[stack_curr - 1].length; + return (A > B); + } else { + const ssize_t A = stack[stack_curr - 3].length; + const ssize_t B = stack[stack_curr - 2].length; + const ssize_t C = stack[stack_curr - 1].length; + return !((A <= B + C) || (B <= C)); + } +} + +static int resize(struct tsort_store *store, size_t new_size) +{ + if (store->alloc < new_size) { + void **tempstore; + + tempstore = git__reallocarray(store->storage, new_size, sizeof(void *)); + + /** + * Do not propagate on OOM; this will abort the sort and + * leave the array unsorted, but no error code will be + * raised + */ + if (tempstore == NULL) + return -1; + + store->storage = tempstore; + store->alloc = new_size; + } + + return 0; +} + +static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store) +{ + const ssize_t A = stack[stack_curr - 2].length; + const ssize_t B = stack[stack_curr - 1].length; + const ssize_t curr = stack[stack_curr - 2].start; + + void **storage; + ssize_t i, j, k; + + if (resize(store, MIN(A, B)) < 0) + return; + + storage = store->storage; + + /* left merge */ + if (A < B) { + memcpy(storage, &dst[curr], A * sizeof(void *)); + i = 0; + j = curr + A; + + for (k = curr; k < curr + A + B; k++) { + if ((i < A) && (j < curr + A + B)) { + if (store->cmp(storage[i], dst[j], store->payload) <= 0) + dst[k] = storage[i++]; + else + dst[k] = dst[j++]; + } else if (i < A) { + dst[k] = storage[i++]; + } else + dst[k] = dst[j++]; + } + } else { + memcpy(storage, &dst[curr + A], B * sizeof(void *)); + i = B - 1; + j = curr + A - 1; + + for (k = curr + A + B - 1; k >= curr; k--) { + if ((i >= 0) && (j >= curr)) { + if (store->cmp(dst[j], storage[i], store->payload) > 0) + dst[k] = dst[j--]; + else + dst[k] = storage[i--]; + } else if (i >= 0) + dst[k] = storage[i--]; + else + dst[k] = dst[j--]; + } + } +} + +static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store, ssize_t size) +{ + ssize_t A, B, C; + + while (1) { + /* if the stack only has one thing on it, we are done with the collapse */ + if (stack_curr <= 1) + break; + + /* if this is the last merge, just do it */ + if ((stack_curr == 2) && (stack[0].length + stack[1].length == size)) { + merge(dst, stack, stack_curr, store); + stack[0].length += stack[1].length; + stack_curr--; + break; + } + + /* check if the invariant is off for a stack of 2 elements */ + else if ((stack_curr == 2) && (stack[0].length <= stack[1].length)) { + merge(dst, stack, stack_curr, store); + stack[0].length += stack[1].length; + stack_curr--; + break; + } + else if (stack_curr == 2) + break; + + A = stack[stack_curr - 3].length; + B = stack[stack_curr - 2].length; + C = stack[stack_curr - 1].length; + + /* check first invariant */ + if (A <= B + C) { + if (A < C) { + merge(dst, stack, stack_curr - 1, store); + stack[stack_curr - 3].length += stack[stack_curr - 2].length; + stack[stack_curr - 2] = stack[stack_curr - 1]; + stack_curr--; + } else { + merge(dst, stack, stack_curr, store); + stack[stack_curr - 2].length += stack[stack_curr - 1].length; + stack_curr--; + } + } else if (B <= C) { + merge(dst, stack, stack_curr, store); + stack[stack_curr - 2].length += stack[stack_curr - 1].length; + stack_curr--; + } else + break; + } + + return stack_curr; +} + +#define PUSH_NEXT() do {\ + len = count_run(dst, curr, size, store);\ + run = minrun;\ + if (run > (ssize_t)size - curr) run = size - curr;\ + if (run > len) {\ + bisort(&dst[curr], len, run, cmp, payload);\ + len = run;\ + }\ + run_stack[stack_curr].start = curr;\ + run_stack[stack_curr++].length = len;\ + curr += len;\ + if (curr == (ssize_t)size) {\ + /* finish up */ \ + while (stack_curr > 1) { \ + merge(dst, run_stack, stack_curr, store); \ + run_stack[stack_curr - 2].length += run_stack[stack_curr - 1].length; \ + stack_curr--; \ + } \ + if (store->storage != NULL) {\ + git__free(store->storage);\ + store->storage = NULL;\ + }\ + return;\ + }\ +}\ +while (0) + +void git__tsort_r( + void **dst, size_t size, git__sort_r_cmp cmp, void *payload) +{ + struct tsort_store _store, *store = &_store; + struct tsort_run run_stack[128]; + + ssize_t stack_curr = 0; + ssize_t len, run; + ssize_t curr = 0; + ssize_t minrun; + + if (size < 64) { + bisort(dst, 1, size, cmp, payload); + return; + } + + /* compute the minimum run length */ + minrun = (ssize_t)compute_minrun(size); + + /* temporary storage for merges */ + store->alloc = 0; + store->storage = NULL; + store->cmp = cmp; + store->payload = payload; + + PUSH_NEXT(); + PUSH_NEXT(); + PUSH_NEXT(); + + while (1) { + if (!check_invariant(run_stack, stack_curr)) { + stack_curr = collapse(dst, run_stack, stack_curr, store, size); + continue; + } + + PUSH_NEXT(); + } +} + +static int tsort_r_cmp(const void *a, const void *b, void *payload) +{ + return ((git__tsort_cmp)payload)(a, b); +} + +void git__tsort(void **dst, size_t size, git__tsort_cmp cmp) +{ + git__tsort_r(dst, size, tsort_r_cmp, cmp); +} diff --git a/src/util/unix/map.c b/src/util/unix/map.c new file mode 100644 index 000000000..933077689 --- /dev/null +++ b/src/util/unix/map.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2_util.h" + +#if !defined(GIT_WIN32) && !defined(NO_MMAP) + +#include "map.h" +#include +#include +#include + +int git__page_size(size_t *page_size) +{ + long sc_page_size = sysconf(_SC_PAGE_SIZE); + if (sc_page_size < 0) { + git_error_set(GIT_ERROR_OS, "can't determine system page size"); + return -1; + } + *page_size = (size_t) sc_page_size; + return 0; +} + +int git__mmap_alignment(size_t *alignment) +{ + return git__page_size(alignment); +} + +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) +{ + int mprot = PROT_READ; + int mflag = 0; + + GIT_MMAP_VALIDATE(out, len, prot, flags); + + out->data = NULL; + out->len = 0; + + if (prot & GIT_PROT_WRITE) + mprot |= PROT_WRITE; + + if ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) + mflag = MAP_SHARED; + else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE) + mflag = MAP_PRIVATE; + else + mflag = MAP_SHARED; + + out->data = mmap(NULL, len, mprot, mflag, fd, offset); + + if (!out->data || out->data == MAP_FAILED) { + git_error_set(GIT_ERROR_OS, "failed to mmap. Could not write data"); + return -1; + } + + out->len = len; + + return 0; +} + +int p_munmap(git_map *map) +{ + GIT_ASSERT_ARG(map); + munmap(map->data, map->len); + map->data = NULL; + map->len = 0; + + return 0; +} + +#endif + diff --git a/src/util/unix/posix.h b/src/util/unix/posix.h new file mode 100644 index 000000000..778477e8e --- /dev/null +++ b/src/util/unix/posix.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_unix_posix_h__ +#define INCLUDE_unix_posix_h__ + +#include "git2_util.h" + +#include +#include +#include +#include +#include + +typedef int GIT_SOCKET; +#define INVALID_SOCKET -1 + +#define p_lseek(f,n,w) lseek(f, n, w) +#define p_fstat(f,b) fstat(f, b) +#define p_lstat(p,b) lstat(p,b) +#define p_stat(p,b) stat(p, b) + +#if defined(GIT_USE_STAT_MTIMESPEC) +# define st_atime_nsec st_atimespec.tv_nsec +# define st_mtime_nsec st_mtimespec.tv_nsec +# define st_ctime_nsec st_ctimespec.tv_nsec +#elif defined(GIT_USE_STAT_MTIM) +# define st_atime_nsec st_atim.tv_nsec +# define st_mtime_nsec st_mtim.tv_nsec +# define st_ctime_nsec st_ctim.tv_nsec +#elif !defined(GIT_USE_STAT_MTIME_NSEC) && defined(GIT_USE_NSEC) +# error GIT_USE_NSEC defined but unknown struct stat nanosecond type +#endif + +#define p_utimes(f, t) utimes(f, t) + +#define p_readlink(a, b, c) readlink(a, b, c) +#define p_symlink(o,n) symlink(o, n) +#define p_link(o,n) link(o, n) +#define p_unlink(p) unlink(p) +#define p_mkdir(p,m) mkdir(p, m) +extern char *p_realpath(const char *, char *); + +GIT_INLINE(int) p_fsync(int fd) +{ + p_fsync__cnt++; + return fsync(fd); +} + +#define p_recv(s,b,l,f) recv(s,b,l,f) +#define p_send(s,b,l,f) send(s,b,l,f) +#define p_inet_pton(a, b, c) inet_pton(a, b, c) + +#define p_strcasecmp(s1, s2) strcasecmp(s1, s2) +#define p_strncasecmp(s1, s2, c) strncasecmp(s1, s2, c) +#define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a) +#define p_snprintf snprintf +#define p_chdir(p) chdir(p) +#define p_rmdir(p) rmdir(p) +#define p_access(p,m) access(p,m) +#define p_ftruncate(fd, sz) ftruncate(fd, sz) + +/* + * Pre-Android 5 did not implement a virtual filesystem atop FAT + * partitions for Unix permissions, which causes chmod to fail. However, + * Unix permissions have no effect on Android anyway as file permissions + * are not actually managed this way, so treating it as a no-op across + * all Android is safe. + */ +#ifdef __ANDROID__ +# define p_chmod(p,m) 0 +#else +# define p_chmod(p,m) chmod(p, m) +#endif + +/* see win32/posix.h for explanation about why this exists */ +#define p_lstat_posixly(p,b) lstat(p,b) + +#define p_localtime_r(c, r) localtime_r(c, r) +#define p_gmtime_r(c, r) gmtime_r(c, r) + +#define p_timeval timeval + +#ifdef GIT_USE_FUTIMENS +GIT_INLINE(int) p_futimes(int f, const struct p_timeval t[2]) +{ + struct timespec s[2]; + s[0].tv_sec = t[0].tv_sec; + s[0].tv_nsec = t[0].tv_usec * 1000; + s[1].tv_sec = t[1].tv_sec; + s[1].tv_nsec = t[1].tv_usec * 1000; + return futimens(f, s); +} +#else +# define p_futimes futimes +#endif + +#define p_pread(f, d, s, o) pread(f, d, s, o) +#define p_pwrite(f, d, s, o) pwrite(f, d, s, o) + +#endif diff --git a/src/util/unix/pthread.h b/src/util/unix/pthread.h new file mode 100644 index 000000000..55f4ae227 --- /dev/null +++ b/src/util/unix/pthread.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_unix_pthread_h__ +#define INCLUDE_unix_pthread_h__ + +typedef struct { + pthread_t thread; +} git_thread; + +GIT_INLINE(int) git_threads_global_init(void) { return 0; } + +#define git_thread_create(git_thread_ptr, start_routine, arg) \ + pthread_create(&(git_thread_ptr)->thread, NULL, start_routine, arg) +#define git_thread_join(git_thread_ptr, status) \ + pthread_join((git_thread_ptr)->thread, status) +#define git_thread_currentid() ((size_t)(pthread_self())) +#define git_thread_exit(retval) pthread_exit(retval) + +/* Git Mutex */ +#define git_mutex pthread_mutex_t +#define git_mutex_init(a) pthread_mutex_init(a, NULL) +#define git_mutex_lock(a) pthread_mutex_lock(a) +#define git_mutex_unlock(a) pthread_mutex_unlock(a) +#define git_mutex_free(a) pthread_mutex_destroy(a) + +/* Git condition vars */ +#define git_cond pthread_cond_t +#define git_cond_init(c) pthread_cond_init(c, NULL) +#define git_cond_free(c) pthread_cond_destroy(c) +#define git_cond_wait(c, l) pthread_cond_wait(c, l) +#define git_cond_signal(c) pthread_cond_signal(c) +#define git_cond_broadcast(c) pthread_cond_broadcast(c) + +/* Pthread (-ish) rwlock + * + * This differs from normal pthreads rwlocks in two ways: + * 1. Separate APIs for releasing read locks and write locks (as + * opposed to the pure POSIX API which only has one unlock fn) + * 2. You should not use recursive read locks (i.e. grabbing a read + * lock in a thread that already holds a read lock) because the + * Windows implementation doesn't support it + */ +#define git_rwlock pthread_rwlock_t +#define git_rwlock_init(a) pthread_rwlock_init(a, NULL) +#define git_rwlock_rdlock(a) pthread_rwlock_rdlock(a) +#define git_rwlock_rdunlock(a) pthread_rwlock_unlock(a) +#define git_rwlock_wrlock(a) pthread_rwlock_wrlock(a) +#define git_rwlock_wrunlock(a) pthread_rwlock_unlock(a) +#define git_rwlock_free(a) pthread_rwlock_destroy(a) +#define GIT_RWLOCK_STATIC_INIT PTHREAD_RWLOCK_INITIALIZER + +#endif diff --git a/src/util/unix/realpath.c b/src/util/unix/realpath.c new file mode 100644 index 000000000..9e31a63b9 --- /dev/null +++ b/src/util/unix/realpath.c @@ -0,0 +1,32 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2_util.h" + +#ifndef GIT_WIN32 + +#include +#include +#include +#include + +char *p_realpath(const char *pathname, char *resolved) +{ + char *ret; + if ((ret = realpath(pathname, resolved)) == NULL) + return NULL; + +#ifdef __OpenBSD__ + /* The OpenBSD realpath function behaves differently, + * figure out if the file exists */ + if (access(ret, F_OK) < 0) + ret = NULL; +#endif + return ret; +} + +#endif diff --git a/src/util/utf8.c b/src/util/utf8.c new file mode 100644 index 000000000..c566fdf20 --- /dev/null +++ b/src/util/utf8.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "utf8.h" + +#include "git2_util.h" + +/* + * git_utf8_iterate is taken from the utf8proc project, + * http://www.public-software-group.org/utf8proc + * + * Copyright (c) 2009 Public Software Group e. V., Berlin, Germany + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the ""Software""), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +static const uint8_t utf8proc_utf8class[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static int utf8_charlen(const uint8_t *str, size_t str_len) +{ + uint8_t length; + size_t i; + + length = utf8proc_utf8class[str[0]]; + if (!length) + return -1; + + if (str_len > 0 && length > str_len) + return -1; + + for (i = 1; i < length; i++) { + if ((str[i] & 0xC0) != 0x80) + return -1; + } + + return (int)length; +} + +int git_utf8_iterate(uint32_t *out, const char *_str, size_t str_len) +{ + const uint8_t *str = (const uint8_t *)_str; + uint32_t uc = 0; + int length; + + *out = 0; + + if ((length = utf8_charlen(str, str_len)) < 0) + return -1; + + switch (length) { + case 1: + uc = str[0]; + break; + case 2: + uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F); + if (uc < 0x80) uc = -1; + break; + case 3: + uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6) + + (str[2] & 0x3F); + if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000) || + (uc >= 0xFDD0 && uc < 0xFDF0)) uc = -1; + break; + case 4: + uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12) + + ((str[2] & 0x3F) << 6) + (str[3] & 0x3F); + if (uc < 0x10000 || uc >= 0x110000) uc = -1; + break; + default: + return -1; + } + + if ((uc & 0xFFFF) >= 0xFFFE) + return -1; + + *out = uc; + return length; +} + +size_t git_utf8_char_length(const char *_str, size_t str_len) +{ + const uint8_t *str = (const uint8_t *)_str; + size_t offset = 0, count = 0; + + while (offset < str_len) { + int length = utf8_charlen(str + offset, str_len - offset); + + if (length < 0) + length = 1; + + offset += length; + count++; + } + + return count; +} + +size_t git_utf8_valid_buf_length(const char *_str, size_t str_len) +{ + const uint8_t *str = (const uint8_t *)_str; + size_t offset = 0; + + while (offset < str_len) { + int length = utf8_charlen(str + offset, str_len - offset); + + if (length < 0) + break; + + offset += length; + } + + return offset; +} diff --git a/src/util/utf8.h b/src/util/utf8.h new file mode 100644 index 000000000..753ab07e2 --- /dev/null +++ b/src/util/utf8.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_utf8_h__ +#define INCLUDE_utf8_h__ + +#include "git2_util.h" + +/* + * Iterate through an UTF-8 string, yielding one codepoint at a time. + * + * @param out pointer where to store the current codepoint + * @param str current position in the string + * @param str_len size left in the string + * @return length in bytes of the read codepoint; -1 if the codepoint was invalid + */ +extern int git_utf8_iterate(uint32_t *out, const char *str, size_t str_len); + +/** + * Returns the number of characters in the given string. + * + * This function will count invalid codepoints; if any given byte is + * not part of a valid UTF-8 codepoint, then it will be counted toward + * the length in characters. + * + * In other words: + * 0x24 (U+0024 "$") has length 1 + * 0xc2 0xa2 (U+00A2 "¢") has length 1 + * 0x24 0xc2 0xa2 (U+0024 U+00A2 "$¢") has length 2 + * 0xf0 0x90 0x8d 0x88 (U+10348 "𐍈") has length 1 + * 0x24 0xc0 0xc1 0x34 (U+0024 "4) has length 4 + * + * @param str string to scan + * @param str_len size of the string + * @return length in characters of the string + */ +extern size_t git_utf8_char_length(const char *str, size_t str_len); + +/** + * Iterate through an UTF-8 string and stops after finding any invalid UTF-8 + * codepoints. + * + * @param str string to scan + * @param str_len size of the string + * @return length in bytes of the string that contains valid data + */ +extern size_t git_utf8_valid_buf_length(const char *str, size_t str_len); + +#endif diff --git a/src/util/util.c b/src/util/util.c new file mode 100644 index 000000000..aee95fddf --- /dev/null +++ b/src/util/util.c @@ -0,0 +1,819 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "util.h" + +#include "git2_util.h" + +#ifdef GIT_WIN32 +# include "win32/utf-conv.h" +# include "win32/w32_buffer.h" + +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include + +# ifdef GIT_QSORT_S +# include +# endif +#endif + +#ifdef _MSC_VER +# include +#endif + +#if defined(hpux) || defined(__hpux) || defined(_hpux) +# include +#endif + +int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) +{ + const char *p; + int64_t n, nn, v; + int c, ovfl, neg, ndig; + + p = nptr; + neg = 0; + n = 0; + ndig = 0; + ovfl = 0; + + /* + * White space + */ + while (nptr_len && git__isspace(*p)) + p++, nptr_len--; + + if (!nptr_len) + goto Return; + + /* + * Sign + */ + if (*p == '-' || *p == '+') { + if (*p == '-') + neg = 1; + p++; + nptr_len--; + } + + if (!nptr_len) + goto Return; + + /* + * Automatically detect the base if none was given to us. + * Right now, we assume that a number starting with '0x' + * is hexadecimal and a number starting with '0' is + * octal. + */ + if (base == 0) { + if (*p != '0') + base = 10; + else if (nptr_len > 2 && (p[1] == 'x' || p[1] == 'X')) + base = 16; + else + base = 8; + } + + if (base < 0 || 36 < base) + goto Return; + + /* + * Skip prefix of '0x'-prefixed hexadecimal numbers. There is no + * need to do the same for '0'-prefixed octal numbers as a + * leading '0' does not have any impact. Also, if we skip a + * leading '0' in such a string, then we may end up with no + * digits left and produce an error later on which isn't one. + */ + if (base == 16 && nptr_len > 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + p += 2; + nptr_len -= 2; + } + + /* + * Non-empty sequence of digits + */ + for (; nptr_len > 0; p++,ndig++,nptr_len--) { + c = *p; + v = base; + if ('0'<=c && c<='9') + v = c - '0'; + else if ('a'<=c && c<='z') + v = c - 'a' + 10; + else if ('A'<=c && c<='Z') + v = c - 'A' + 10; + if (v >= base) + break; + v = neg ? -v : v; + if (git__multiply_int64_overflow(&nn, n, base) || git__add_int64_overflow(&n, nn, v)) { + ovfl = 1; + /* Keep on iterating until the end of this number */ + continue; + } + } + +Return: + if (ndig == 0) { + git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: not a number"); + return -1; + } + + if (endptr) + *endptr = p; + + if (ovfl) { + git_error_set(GIT_ERROR_INVALID, "failed to convert string to long: overflow error"); + return -1; + } + + *result = n; + return 0; +} + +int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) +{ + const char *tmp_endptr; + int32_t tmp_int; + int64_t tmp_long; + int error; + + if ((error = git__strntol64(&tmp_long, nptr, nptr_len, &tmp_endptr, base)) < 0) + return error; + + tmp_int = tmp_long & 0xFFFFFFFF; + if (tmp_int != tmp_long) { + int len = (int)(tmp_endptr - nptr); + git_error_set(GIT_ERROR_INVALID, "failed to convert: '%.*s' is too large", len, nptr); + return -1; + } + + *result = tmp_int; + if (endptr) + *endptr = tmp_endptr; + + return error; +} + +int git__strcasecmp(const char *a, const char *b) +{ + while (*a && *b && git__tolower(*a) == git__tolower(*b)) + ++a, ++b; + return ((unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b)); +} + +int git__strcasesort_cmp(const char *a, const char *b) +{ + int cmp = 0; + + while (*a && *b) { + if (*a != *b) { + if (git__tolower(*a) != git__tolower(*b)) + break; + /* use case in sort order even if not in equivalence */ + if (!cmp) + cmp = (int)(*(const uint8_t *)a) - (int)(*(const uint8_t *)b); + } + + ++a, ++b; + } + + if (*a || *b) + return (unsigned char)git__tolower(*a) - (unsigned char)git__tolower(*b); + + return cmp; +} + +int git__strncasecmp(const char *a, const char *b, size_t sz) +{ + int al, bl; + + do { + al = (unsigned char)git__tolower(*a); + bl = (unsigned char)git__tolower(*b); + ++a, ++b; + } while (--sz && al && al == bl); + + return al - bl; +} + +void git__strntolower(char *str, size_t len) +{ + size_t i; + + for (i = 0; i < len; ++i) { + str[i] = (char)git__tolower(str[i]); + } +} + +void git__strtolower(char *str) +{ + git__strntolower(str, strlen(str)); +} + +GIT_INLINE(int) prefixcmp(const char *str, size_t str_n, const char *prefix, bool icase) +{ + int s, p; + + while (str_n--) { + s = (unsigned char)*str++; + p = (unsigned char)*prefix++; + + if (icase) { + s = git__tolower(s); + p = git__tolower(p); + } + + if (!p) + return 0; + + if (s != p) + return s - p; + } + + return (0 - *prefix); +} + +int git__prefixcmp(const char *str, const char *prefix) +{ + unsigned char s, p; + + while (1) { + p = *prefix++; + s = *str++; + + if (!p) + return 0; + + if (s != p) + return s - p; + } +} + +int git__prefixncmp(const char *str, size_t str_n, const char *prefix) +{ + return prefixcmp(str, str_n, prefix, false); +} + +int git__prefixcmp_icase(const char *str, const char *prefix) +{ + return prefixcmp(str, SIZE_MAX, prefix, true); +} + +int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix) +{ + return prefixcmp(str, str_n, prefix, true); +} + +int git__suffixcmp(const char *str, const char *suffix) +{ + size_t a = strlen(str); + size_t b = strlen(suffix); + if (a < b) + return -1; + return strcmp(str + (a - b), suffix); +} + +char *git__strtok(char **end, const char *sep) +{ + char *ptr = *end; + + while (*ptr && strchr(sep, *ptr)) + ++ptr; + + if (*ptr) { + char *start = ptr; + *end = start + 1; + + while (**end && !strchr(sep, **end)) + ++*end; + + if (**end) { + **end = '\0'; + ++*end; + } + + return start; + } + + return NULL; +} + +/* Similar to strtok, but does not collapse repeated tokens. */ +char *git__strsep(char **end, const char *sep) +{ + char *start = *end, *ptr = *end; + + while (*ptr && !strchr(sep, *ptr)) + ++ptr; + + if (*ptr) { + *end = ptr + 1; + *ptr = '\0'; + + return start; + } + + return NULL; +} + +size_t git__linenlen(const char *buffer, size_t buffer_len) +{ + char *nl = memchr(buffer, '\n', buffer_len); + return nl ? (size_t)(nl - buffer) + 1 : buffer_len; +} + +/* + * Adapted Not So Naive algorithm from http://www-igm.univ-mlv.fr/~lecroq/string/ + */ +const void * git__memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen) +{ + const char *h, *n; + size_t j, k, l; + + if (needlelen > haystacklen || !haystacklen || !needlelen) + return NULL; + + h = (const char *) haystack, + n = (const char *) needle; + + if (needlelen == 1) + return memchr(haystack, *n, haystacklen); + + if (n[0] == n[1]) { + k = 2; + l = 1; + } else { + k = 1; + l = 2; + } + + j = 0; + while (j <= haystacklen - needlelen) { + if (n[1] != h[j + 1]) { + j += k; + } else { + if (memcmp(n + 2, h + j + 2, needlelen - 2) == 0 && + n[0] == h[j]) + return h + j; + j += l; + } + } + + return NULL; +} + +void git__hexdump(const char *buffer, size_t len) +{ + static const size_t LINE_WIDTH = 16; + + size_t line_count, last_line, i, j; + const char *line; + + line_count = (len / LINE_WIDTH); + last_line = (len % LINE_WIDTH); + + for (i = 0; i < line_count; ++i) { + printf("%08" PRIxZ " ", (i * LINE_WIDTH)); + + line = buffer + (i * LINE_WIDTH); + for (j = 0; j < LINE_WIDTH; ++j, ++line) { + printf("%02x ", (unsigned char)*line & 0xFF); + + if (j == (LINE_WIDTH / 2)) + printf(" "); + } + + printf(" |"); + + line = buffer + (i * LINE_WIDTH); + for (j = 0; j < LINE_WIDTH; ++j, ++line) + printf("%c", (*line >= 32 && *line <= 126) ? *line : '.'); + + printf("|\n"); + } + + if (last_line > 0) { + printf("%08" PRIxZ " ", (line_count * LINE_WIDTH)); + + line = buffer + (line_count * LINE_WIDTH); + for (j = 0; j < last_line; ++j, ++line) { + printf("%02x ", (unsigned char)*line & 0xFF); + + if (j == (LINE_WIDTH / 2)) + printf(" "); + } + + if (j < (LINE_WIDTH / 2)) + printf(" "); + for (j = 0; j < (LINE_WIDTH - last_line); ++j) + printf(" "); + + printf(" |"); + + line = buffer + (line_count * LINE_WIDTH); + for (j = 0; j < last_line; ++j, ++line) + printf("%c", (*line >= 32 && *line <= 126) ? *line : '.'); + + printf("|\n"); + } + + printf("\n"); +} + +#ifdef GIT_LEGACY_HASH +uint32_t git__hash(const void *key, int len, unsigned int seed) +{ + const uint32_t m = 0x5bd1e995; + const int r = 24; + uint32_t h = seed ^ len; + + const unsigned char *data = (const unsigned char *)key; + + while(len >= 4) { + uint32_t k = *(uint32_t *)data; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + data += 4; + len -= 4; + } + + switch(len) { + case 3: h ^= data[2] << 16; + case 2: h ^= data[1] << 8; + case 1: h ^= data[0]; + h *= m; + }; + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} +#else +/* + Cross-platform version of Murmurhash3 + http://code.google.com/p/smhasher/wiki/MurmurHash3 + by Austin Appleby (aappleby@gmail.com) + + This code is on the public domain. +*/ +uint32_t git__hash(const void *key, int len, uint32_t seed) +{ + +#define MURMUR_BLOCK() {\ + k1 *= c1; \ + k1 = git__rotl(k1,11);\ + k1 *= c2;\ + h1 ^= k1;\ + h1 = h1*3 + 0x52dce729;\ + c1 = c1*5 + 0x7b7d159c;\ + c2 = c2*5 + 0x6bce6396;\ +} + + const uint8_t *data = (const uint8_t*)key; + const int nblocks = len / 4; + + const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4); + const uint8_t *tail = (const uint8_t *)(data + nblocks * 4); + + uint32_t h1 = 0x971e137b ^ seed; + uint32_t k1; + + uint32_t c1 = 0x95543787; + uint32_t c2 = 0x2ad7eb25; + + int i; + + for (i = -nblocks; i; i++) { + k1 = blocks[i]; + MURMUR_BLOCK(); + } + + k1 = 0; + + switch(len & 3) { + case 3: k1 ^= tail[2] << 16; + /* fall through */ + case 2: k1 ^= tail[1] << 8; + /* fall through */ + case 1: k1 ^= tail[0]; + MURMUR_BLOCK(); + } + + h1 ^= len; + h1 ^= h1 >> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >> 16; + + return h1; +} +#endif + +/** + * A modified `bsearch` from the BSD glibc. + * + * Copyright (c) 1990 Regents of the University of California. + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. [rescinded 22 July 1999] + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +int git__bsearch( + void **array, + size_t array_len, + const void *key, + int (*compare)(const void *, const void *), + size_t *position) +{ + size_t lim; + int cmp = -1; + void **part, **base = array; + + for (lim = array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1); + cmp = (*compare)(key, *part); + if (cmp == 0) { + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ + base = part + 1; + lim--; + } /* else take left partition */ + } + + if (position) + *position = (base - array); + + return (cmp == 0) ? 0 : GIT_ENOTFOUND; +} + +int git__bsearch_r( + void **array, + size_t array_len, + const void *key, + int (*compare_r)(const void *, const void *, void *), + void *payload, + size_t *position) +{ + size_t lim; + int cmp = -1; + void **part, **base = array; + + for (lim = array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1); + cmp = (*compare_r)(key, *part, payload); + if (cmp == 0) { + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ + base = part + 1; + lim--; + } /* else take left partition */ + } + + if (position) + *position = (base - array); + + return (cmp == 0) ? 0 : GIT_ENOTFOUND; +} + +/** + * A strcmp wrapper + * + * We don't want direct pointers to the CRT on Windows, we may + * get stdcall conflicts. + */ +int git__strcmp_cb(const void *a, const void *b) +{ + return strcmp((const char *)a, (const char *)b); +} + +int git__strcasecmp_cb(const void *a, const void *b) +{ + return strcasecmp((const char *)a, (const char *)b); +} + +int git__parse_bool(int *out, const char *value) +{ + /* A missing value means true */ + if (value == NULL || + !strcasecmp(value, "true") || + !strcasecmp(value, "yes") || + !strcasecmp(value, "on")) { + *out = 1; + return 0; + } + if (!strcasecmp(value, "false") || + !strcasecmp(value, "no") || + !strcasecmp(value, "off") || + value[0] == '\0') { + *out = 0; + return 0; + } + + return -1; +} + +size_t git__unescape(char *str) +{ + char *scan, *pos = str; + + if (!str) + return 0; + + for (scan = str; *scan; pos++, scan++) { + if (*scan == '\\' && *(scan + 1) != '\0') + scan++; /* skip '\' but include next char */ + if (pos != scan) + *pos = *scan; + } + + if (pos != scan) { + *pos = '\0'; + } + + return (pos - str); +} + +#if defined(GIT_QSORT_S) || defined(GIT_QSORT_R_BSD) +typedef struct { + git__sort_r_cmp cmp; + void *payload; +} git__qsort_r_glue; + +static int GIT_LIBGIT2_CALL git__qsort_r_glue_cmp( + void *payload, const void *a, const void *b) +{ + git__qsort_r_glue *glue = payload; + return glue->cmp(a, b, glue->payload); +} +#endif + + +#if !defined(GIT_QSORT_R_BSD) && \ + !defined(GIT_QSORT_R_GNU) && \ + !defined(GIT_QSORT_S) +static void swap(uint8_t *a, uint8_t *b, size_t elsize) +{ + char tmp[256]; + + while (elsize) { + size_t n = elsize < sizeof(tmp) ? elsize : sizeof(tmp); + memcpy(tmp, a + elsize - n, n); + memcpy(a + elsize - n, b + elsize - n, n); + memcpy(b + elsize - n, tmp, n); + elsize -= n; + } +} + +static void insertsort( + void *els, size_t nel, size_t elsize, + git__sort_r_cmp cmp, void *payload) +{ + uint8_t *base = els; + uint8_t *end = base + nel * elsize; + uint8_t *i, *j; + + for (i = base + elsize; i < end; i += elsize) + for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize) + swap(j, j - elsize, elsize); +} +#endif + +void git__qsort_r( + void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload) +{ +#if defined(GIT_QSORT_R_BSD) + git__qsort_r_glue glue = { cmp, payload }; + qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp); +#elif defined(GIT_QSORT_R_GNU) + qsort_r(els, nel, elsize, cmp, payload); +#elif defined(GIT_QSORT_S) + git__qsort_r_glue glue = { cmp, payload }; + qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue); +#else + insertsort(els, nel, elsize, cmp, payload); +#endif +} + +#ifdef GIT_WIN32 +int git__getenv(git_str *out, const char *name) +{ + wchar_t *wide_name = NULL, *wide_value = NULL; + DWORD value_len; + int error = -1; + + git_str_clear(out); + + if (git__utf8_to_16_alloc(&wide_name, name) < 0) + return -1; + + if ((value_len = GetEnvironmentVariableW(wide_name, NULL, 0)) > 0) { + wide_value = git__malloc(value_len * sizeof(wchar_t)); + GIT_ERROR_CHECK_ALLOC(wide_value); + + value_len = GetEnvironmentVariableW(wide_name, wide_value, value_len); + } + + if (value_len) + error = git_str_put_w(out, wide_value, value_len); + else if (GetLastError() == ERROR_SUCCESS || GetLastError() == ERROR_ENVVAR_NOT_FOUND) + error = GIT_ENOTFOUND; + else + git_error_set(GIT_ERROR_OS, "could not read environment variable '%s'", name); + + git__free(wide_name); + git__free(wide_value); + return error; +} +#else +int git__getenv(git_str *out, const char *name) +{ + const char *val = getenv(name); + + git_str_clear(out); + + if (!val) + return GIT_ENOTFOUND; + + return git_str_puts(out, val); +} +#endif + +/* + * By doing this in two steps we can at least get + * the function to be somewhat coherent, even + * with this disgusting nest of #ifdefs. + */ +#ifndef _SC_NPROCESSORS_ONLN +# ifdef _SC_NPROC_ONLN +# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN +# elif defined _SC_CRAY_NCPU +# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU +# endif +#endif + +int git__online_cpus(void) +{ +#ifdef _SC_NPROCESSORS_ONLN + long ncpus; +#endif + +#ifdef _WIN32 + SYSTEM_INFO info; + GetSystemInfo(&info); + + if ((int)info.dwNumberOfProcessors > 0) + return (int)info.dwNumberOfProcessors; +#elif defined(hpux) || defined(__hpux) || defined(_hpux) + struct pst_dynamic psd; + + if (!pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0)) + return (int)psd.psd_proc_cnt; +#endif + +#ifdef _SC_NPROCESSORS_ONLN + if ((ncpus = (long)sysconf(_SC_NPROCESSORS_ONLN)) > 0) + return (int)ncpus; +#endif + + return 1; +} diff --git a/src/util/util.h b/src/util/util.h new file mode 100644 index 000000000..8d6d1d6b6 --- /dev/null +++ b/src/util/util.h @@ -0,0 +1,387 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_util_h__ +#define INCLUDE_util_h__ + +#ifndef GIT_WIN32 +# include +#endif + +#include "str.h" +#include "git2_util.h" +#include "strnlen.h" +#include "thread.h" + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) +#define bitsizeof(x) (CHAR_BIT * sizeof(x)) +#define MSB(x, bits) ((x) & (~UINT64_C(0) << (bitsizeof(x) - (bits)))) +#ifndef min +# define min(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef max +# define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#if defined(__GNUC__) +# define GIT_CONTAINER_OF(ptr, type, member) \ + __builtin_choose_expr( \ + __builtin_offsetof(type, member) == 0 && \ + __builtin_types_compatible_p(__typeof__(&((type *) 0)->member), __typeof__(ptr)), \ + ((type *) (ptr)), \ + (void)0) +#else +# define GIT_CONTAINER_OF(ptr, type, member) (type *)(ptr) +#endif + +/** + * Return the length of a constant string. + * We are aware that `strlen` performs the same task and is usually + * optimized away by the compiler, whilst being safer because it returns + * valid values when passed a pointer instead of a constant string; however + * this macro will transparently work with wide-char and single-char strings. + */ +#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1) + +#define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \ + ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2))) + +#define CASESELECT(IGNORE_CASE, ICASE, CASE) \ + ((IGNORE_CASE) ? (ICASE) : (CASE)) + +extern int git__prefixcmp(const char *str, const char *prefix); +extern int git__prefixcmp_icase(const char *str, const char *prefix); +extern int git__prefixncmp(const char *str, size_t str_n, const char *prefix); +extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix); +extern int git__suffixcmp(const char *str, const char *suffix); + +GIT_INLINE(int) git__signum(int val) +{ + return ((val > 0) - (val < 0)); +} + +extern int git__strntol32(int32_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); +extern int git__strntol64(int64_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); + + +extern void git__hexdump(const char *buffer, size_t n); +extern uint32_t git__hash(const void *key, int len, uint32_t seed); + +/* 32-bit cross-platform rotl */ +#ifdef _MSC_VER /* use built-in method in MSVC */ +# define git__rotl(v, s) (uint32_t)_rotl(v, s) +#else /* use bitops in GCC; with o2 this gets optimized to a rotl instruction */ +# define git__rotl(v, s) (uint32_t)(((uint32_t)(v) << (s)) | ((uint32_t)(v) >> (32 - (s)))) +#endif + +extern char *git__strtok(char **end, const char *sep); +extern char *git__strsep(char **end, const char *sep); + +extern void git__strntolower(char *str, size_t len); +extern void git__strtolower(char *str); + +#ifdef GIT_WIN32 +GIT_INLINE(int) git__tolower(int c) +{ + return (c >= 'A' && c <= 'Z') ? (c + 32) : c; +} +#else +# define git__tolower(a) tolower(a) +#endif + +extern size_t git__linenlen(const char *buffer, size_t buffer_len); + +GIT_INLINE(const char *) git__next_line(const char *s) +{ + while (*s && *s != '\n') s++; + while (*s == '\n' || *s == '\r') s++; + return s; +} + +GIT_INLINE(const void *) git__memrchr(const void *s, int c, size_t n) +{ + const unsigned char *cp; + + if (n != 0) { + cp = (unsigned char *)s + n; + do { + if (*(--cp) == (unsigned char)c) + return cp; + } while (--n != 0); + } + + return NULL; +} + +extern const void * git__memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); + +typedef int (*git__tsort_cmp)(const void *a, const void *b); + +extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp); + +typedef int (*git__sort_r_cmp)(const void *a, const void *b, void *payload); + +extern void git__tsort_r( + void **dst, size_t size, git__sort_r_cmp cmp, void *payload); + +extern void git__qsort_r( + void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload); + +/** + * @param position If non-NULL, this will be set to the position where the + * element is or would be inserted if not found. + * @return 0 if found; GIT_ENOTFOUND if not found + */ +extern int git__bsearch( + void **array, + size_t array_len, + const void *key, + int (*compare)(const void *key, const void *element), + size_t *position); + +extern int git__bsearch_r( + void **array, + size_t array_len, + const void *key, + int (*compare_r)(const void *key, const void *element, void *payload), + void *payload, + size_t *position); + +#define git__strcmp strcmp +#define git__strncmp strncmp + +extern int git__strcmp_cb(const void *a, const void *b); +extern int git__strcasecmp_cb(const void *a, const void *b); + +extern int git__strcasecmp(const char *a, const char *b); +extern int git__strncasecmp(const char *a, const char *b, size_t sz); + +extern int git__strcasesort_cmp(const char *a, const char *b); + +/* + * Compare some NUL-terminated `a` to a possibly non-NUL terminated + * `b` of length `b_len`; like `strncmp` but ensuring that + * `strlen(a) == b_len` as well. + */ +GIT_INLINE(int) git__strlcmp(const char *a, const char *b, size_t b_len) +{ + int cmp = strncmp(a, b, b_len); + return cmp ? cmp : (int)a[b_len]; +} + +typedef struct { + git_atomic32 refcount; + void *owner; +} git_refcount; + +typedef void (*git_refcount_freeptr)(void *r); + +#define GIT_REFCOUNT_INC(r) { \ + git_atomic32_inc(&(r)->rc.refcount); \ +} + +#define GIT_REFCOUNT_DEC(_r, do_free) { \ + git_refcount *r = &(_r)->rc; \ + int val = git_atomic32_dec(&r->refcount); \ + if (val <= 0 && r->owner == NULL) { do_free(_r); } \ +} + +#define GIT_REFCOUNT_OWN(r, o) { \ + (void)git_atomic_swap((r)->rc.owner, o); \ +} + +#define GIT_REFCOUNT_OWNER(r) git_atomic_load((r)->rc.owner) + +#define GIT_REFCOUNT_VAL(r) git_atomic32_get((r)->rc.refcount) + + +static signed char from_hex[] = { +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */ +-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */ +-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */ +}; + +GIT_INLINE(int) git__fromhex(char h) +{ + return from_hex[(unsigned char) h]; +} + +GIT_INLINE(int) git__ishex(const char *str) +{ + unsigned i; + for (i=0; str[i] != '\0'; i++) + if (git__fromhex(str[i]) < 0) + return 0; + return 1; +} + +GIT_INLINE(size_t) git__size_t_bitmask(size_t v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + + return v; +} + +GIT_INLINE(size_t) git__size_t_powerof2(size_t v) +{ + return git__size_t_bitmask(v) + 1; +} + +GIT_INLINE(bool) git__isupper(int c) +{ + return (c >= 'A' && c <= 'Z'); +} + +GIT_INLINE(bool) git__isalpha(int c) +{ + return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); +} + +GIT_INLINE(bool) git__isdigit(int c) +{ + return (c >= '0' && c <= '9'); +} + +GIT_INLINE(bool) git__isspace(int c) +{ + return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); +} + +GIT_INLINE(bool) git__isspace_nonlf(int c) +{ + return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v'); +} + +GIT_INLINE(bool) git__iswildcard(int c) +{ + return (c == '*' || c == '?' || c == '['); +} + +GIT_INLINE(bool) git__isxdigit(int c) +{ + return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); +} + +/* + * Parse a string value as a boolean, just like Core Git does. + * + * Valid values for true are: 'true', 'yes', 'on' + * Valid values for false are: 'false', 'no', 'off' + */ +extern int git__parse_bool(int *out, const char *value); + +/* + * Unescapes a string in-place. + * + * Edge cases behavior: + * - "jackie\" -> "jacky\" + * - "chan\\" -> "chan\" + */ +extern size_t git__unescape(char *str); + +/* + * Safely zero-out memory, making sure that the compiler + * doesn't optimize away the operation. + */ +GIT_INLINE(void) git__memzero(void *data, size_t size) +{ +#ifdef _MSC_VER + SecureZeroMemory((PVOID)data, size); +#else + volatile uint8_t *scan = (volatile uint8_t *)data; + + while (size--) + *scan++ = 0x0; +#endif +} + +#ifdef GIT_WIN32 + +GIT_INLINE(double) git__timer(void) +{ + /* GetTickCount64 returns the number of milliseconds that have + * elapsed since the system was started. */ + return (double) GetTickCount64() / (double) 1000; +} + +#elif __APPLE__ + +#include + +GIT_INLINE(double) git__timer(void) +{ + uint64_t time = mach_absolute_time(); + static double scaling_factor = 0; + + if (scaling_factor == 0) { + mach_timebase_info_data_t info; + (void)mach_timebase_info(&info); + scaling_factor = (double)info.numer / (double)info.denom; + } + + return (double)time * scaling_factor / 1.0E9; +} + +#elif defined(__amigaos4__) + +#include + +GIT_INLINE(double) git__timer(void) +{ + struct TimeVal tv; + ITimer->GetUpTime(&tv); + return (double)tv.Seconds + (double)tv.Microseconds / 1.0E6; +} + +#else + +#include + +GIT_INLINE(double) git__timer(void) +{ + struct timeval tv; + +#ifdef CLOCK_MONOTONIC + struct timespec tp; + if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) + return (double) tp.tv_sec + (double) tp.tv_nsec / 1.0E9; +#endif + + /* Fall back to using gettimeofday */ + gettimeofday(&tv, NULL); + return (double)tv.tv_sec + (double)tv.tv_usec / 1.0E6; +} + +#endif + +extern int git__getenv(git_str *out, const char *name); + +extern int git__online_cpus(void); + +GIT_INLINE(int) git__noop(void) { return 0; } + +#include "alloc.h" + +#endif diff --git a/src/util/varint.c b/src/util/varint.c new file mode 100644 index 000000000..9ffc1d744 --- /dev/null +++ b/src/util/varint.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "varint.h" + +uintmax_t git_decode_varint(const unsigned char *bufp, size_t *varint_len) +{ + const unsigned char *buf = bufp; + unsigned char c = *buf++; + uintmax_t val = c & 127; + while (c & 128) { + val += 1; + if (!val || MSB(val, 7)) { + /* This is not a valid varint_len, so it signals + the error */ + *varint_len = 0; + return 0; /* overflow */ + } + c = *buf++; + val = (val << 7) + (c & 127); + } + *varint_len = buf - bufp; + return val; +} + +int git_encode_varint(unsigned char *buf, size_t bufsize, uintmax_t value) +{ + unsigned char varint[16]; + unsigned pos = sizeof(varint) - 1; + varint[pos] = value & 127; + while (value >>= 7) + varint[--pos] = 128 | (--value & 127); + if (buf) { + if (bufsize < (sizeof(varint) - pos)) + return -1; + memcpy(buf, varint + pos, sizeof(varint) - pos); + } + return sizeof(varint) - pos; +} diff --git a/src/util/varint.h b/src/util/varint.h new file mode 100644 index 000000000..79b8f5548 --- /dev/null +++ b/src/util/varint.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_varint_h__ +#define INCLUDE_varint_h__ + +#include "git2_util.h" + +#include + +extern int git_encode_varint(unsigned char *, size_t, uintmax_t); +extern uintmax_t git_decode_varint(const unsigned char *, size_t *); + +#endif diff --git a/src/util/vector.c b/src/util/vector.c new file mode 100644 index 000000000..4a4bc8c0e --- /dev/null +++ b/src/util/vector.c @@ -0,0 +1,431 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "vector.h" + +#include "integer.h" + +/* In elements, not bytes */ +#define MIN_ALLOCSIZE 8 + +GIT_INLINE(size_t) compute_new_size(git_vector *v) +{ + size_t new_size = v->_alloc_size; + + /* Use a resize factor of 1.5, which is quick to compute using integer + * instructions and less than the golden ratio (1.618...) */ + if (new_size < MIN_ALLOCSIZE) + new_size = MIN_ALLOCSIZE; + else if (new_size <= (SIZE_MAX / 3) * 2) + new_size += new_size / 2; + else + new_size = SIZE_MAX; + + return new_size; +} + +GIT_INLINE(int) resize_vector(git_vector *v, size_t new_size) +{ + void *new_contents; + + if (new_size == 0) + return 0; + + new_contents = git__reallocarray(v->contents, new_size, sizeof(void *)); + GIT_ERROR_CHECK_ALLOC(new_contents); + + v->_alloc_size = new_size; + v->contents = new_contents; + + return 0; +} + +int git_vector_size_hint(git_vector *v, size_t size_hint) +{ + if (v->_alloc_size >= size_hint) + return 0; + return resize_vector(v, size_hint); +} + +int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp) +{ + GIT_ASSERT_ARG(v); + GIT_ASSERT_ARG(src); + + v->_alloc_size = 0; + v->contents = NULL; + v->_cmp = cmp ? cmp : src->_cmp; + v->length = src->length; + v->flags = src->flags; + if (cmp != src->_cmp) + git_vector_set_sorted(v, 0); + + if (src->length) { + size_t bytes; + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&bytes, src->length, sizeof(void *)); + v->contents = git__malloc(bytes); + GIT_ERROR_CHECK_ALLOC(v->contents); + v->_alloc_size = src->length; + memcpy(v->contents, src->contents, bytes); + } + + return 0; +} + +void git_vector_free(git_vector *v) +{ + if (!v) + return; + + git__free(v->contents); + v->contents = NULL; + + v->length = 0; + v->_alloc_size = 0; +} + +void git_vector_free_deep(git_vector *v) +{ + size_t i; + + if (!v) + return; + + for (i = 0; i < v->length; ++i) { + git__free(v->contents[i]); + v->contents[i] = NULL; + } + + git_vector_free(v); +} + +int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp) +{ + GIT_ASSERT_ARG(v); + + v->_alloc_size = 0; + v->_cmp = cmp; + v->length = 0; + v->flags = GIT_VECTOR_SORTED; + v->contents = NULL; + + return resize_vector(v, max(initial_size, MIN_ALLOCSIZE)); +} + +void **git_vector_detach(size_t *size, size_t *asize, git_vector *v) +{ + void **data = v->contents; + + if (size) + *size = v->length; + if (asize) + *asize = v->_alloc_size; + + v->_alloc_size = 0; + v->length = 0; + v->contents = NULL; + + return data; +} + +int git_vector_insert(git_vector *v, void *element) +{ + GIT_ASSERT_ARG(v); + + if (v->length >= v->_alloc_size && + resize_vector(v, compute_new_size(v)) < 0) + return -1; + + v->contents[v->length++] = element; + + git_vector_set_sorted(v, v->length <= 1); + + return 0; +} + +int git_vector_insert_sorted( + git_vector *v, void *element, int (*on_dup)(void **old, void *new)) +{ + int result; + size_t pos; + + GIT_ASSERT_ARG(v); + GIT_ASSERT(v->_cmp); + + if (!git_vector_is_sorted(v)) + git_vector_sort(v); + + if (v->length >= v->_alloc_size && + resize_vector(v, compute_new_size(v)) < 0) + return -1; + + /* If we find the element and have a duplicate handler callback, + * invoke it. If it returns non-zero, then cancel insert, otherwise + * proceed with normal insert. + */ + if (!git__bsearch(v->contents, v->length, element, v->_cmp, &pos) && + on_dup && (result = on_dup(&v->contents[pos], element)) < 0) + return result; + + /* shift elements to the right */ + if (pos < v->length) + memmove(v->contents + pos + 1, v->contents + pos, + (v->length - pos) * sizeof(void *)); + + v->contents[pos] = element; + v->length++; + + return 0; +} + +void git_vector_sort(git_vector *v) +{ + if (git_vector_is_sorted(v) || !v->_cmp) + return; + + if (v->length > 1) + git__tsort(v->contents, v->length, v->_cmp); + git_vector_set_sorted(v, 1); +} + +int git_vector_bsearch2( + size_t *at_pos, + git_vector *v, + git_vector_cmp key_lookup, + const void *key) +{ + GIT_ASSERT_ARG(v); + GIT_ASSERT_ARG(key); + GIT_ASSERT(key_lookup); + + /* need comparison function to sort the vector */ + if (!v->_cmp) + return -1; + + git_vector_sort(v); + + return git__bsearch(v->contents, v->length, key, key_lookup, at_pos); +} + +int git_vector_search2( + size_t *at_pos, const git_vector *v, git_vector_cmp key_lookup, const void *key) +{ + size_t i; + + GIT_ASSERT_ARG(v); + GIT_ASSERT_ARG(key); + GIT_ASSERT(key_lookup); + + for (i = 0; i < v->length; ++i) { + if (key_lookup(key, v->contents[i]) == 0) { + if (at_pos) + *at_pos = i; + + return 0; + } + } + + return GIT_ENOTFOUND; +} + +static int strict_comparison(const void *a, const void *b) +{ + return (a == b) ? 0 : -1; +} + +int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry) +{ + return git_vector_search2(at_pos, v, v->_cmp ? v->_cmp : strict_comparison, entry); +} + +int git_vector_remove(git_vector *v, size_t idx) +{ + size_t shift_count; + + GIT_ASSERT_ARG(v); + + if (idx >= v->length) + return GIT_ENOTFOUND; + + shift_count = v->length - idx - 1; + + if (shift_count) + memmove(&v->contents[idx], &v->contents[idx + 1], + shift_count * sizeof(void *)); + + v->length--; + return 0; +} + +void git_vector_pop(git_vector *v) +{ + if (v->length > 0) + v->length--; +} + +void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)) +{ + git_vector_cmp cmp; + size_t i, j; + + if (v->length <= 1) + return; + + git_vector_sort(v); + cmp = v->_cmp ? v->_cmp : strict_comparison; + + for (i = 0, j = 1 ; j < v->length; ++j) + if (!cmp(v->contents[i], v->contents[j])) { + if (git_free_cb) + git_free_cb(v->contents[i]); + + v->contents[i] = v->contents[j]; + } else + v->contents[++i] = v->contents[j]; + + v->length -= j - i - 1; +} + +void git_vector_remove_matching( + git_vector *v, + int (*match)(const git_vector *v, size_t idx, void *payload), + void *payload) +{ + size_t i, j; + + for (i = 0, j = 0; j < v->length; ++j) { + v->contents[i] = v->contents[j]; + + if (!match(v, i, payload)) + i++; + } + + v->length = i; +} + +void git_vector_clear(git_vector *v) +{ + v->length = 0; + git_vector_set_sorted(v, 1); +} + +void git_vector_swap(git_vector *a, git_vector *b) +{ + git_vector t; + + if (a != b) { + memcpy(&t, a, sizeof(t)); + memcpy(a, b, sizeof(t)); + memcpy(b, &t, sizeof(t)); + } +} + +int git_vector_resize_to(git_vector *v, size_t new_length) +{ + if (new_length > v->_alloc_size && + resize_vector(v, new_length) < 0) + return -1; + + if (new_length > v->length) + memset(&v->contents[v->length], 0, + sizeof(void *) * (new_length - v->length)); + + v->length = new_length; + + return 0; +} + +int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len) +{ + size_t new_length; + + GIT_ASSERT_ARG(insert_len > 0); + GIT_ASSERT_ARG(idx <= v->length); + + GIT_ERROR_CHECK_ALLOC_ADD(&new_length, v->length, insert_len); + + if (new_length > v->_alloc_size && resize_vector(v, new_length) < 0) + return -1; + + memmove(&v->contents[idx + insert_len], &v->contents[idx], + sizeof(void *) * (v->length - idx)); + memset(&v->contents[idx], 0, sizeof(void *) * insert_len); + + v->length = new_length; + return 0; +} + +int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len) +{ + size_t new_length = v->length - remove_len; + size_t end_idx = 0; + + GIT_ASSERT_ARG(remove_len > 0); + + if (git__add_sizet_overflow(&end_idx, idx, remove_len)) + GIT_ASSERT(0); + + GIT_ASSERT(end_idx <= v->length); + + if (end_idx < v->length) + memmove(&v->contents[idx], &v->contents[end_idx], + sizeof(void *) * (v->length - end_idx)); + + memset(&v->contents[new_length], 0, sizeof(void *) * remove_len); + + v->length = new_length; + return 0; +} + +int git_vector_set(void **old, git_vector *v, size_t position, void *value) +{ + if (position + 1 > v->length) { + if (git_vector_resize_to(v, position + 1) < 0) + return -1; + } + + if (old != NULL) + *old = v->contents[position]; + + v->contents[position] = value; + + return 0; +} + +int git_vector_verify_sorted(const git_vector *v) +{ + size_t i; + + if (!git_vector_is_sorted(v)) + return -1; + + for (i = 1; i < v->length; ++i) { + if (v->_cmp(v->contents[i - 1], v->contents[i]) > 0) + return -1; + } + + return 0; +} + +void git_vector_reverse(git_vector *v) +{ + size_t a, b; + + if (v->length == 0) + return; + + a = 0; + b = v->length - 1; + + while (a < b) { + void *tmp = v->contents[a]; + v->contents[a] = v->contents[b]; + v->contents[b] = tmp; + a++; + b--; + } +} diff --git a/src/util/vector.h b/src/util/vector.h new file mode 100644 index 000000000..e50cdfefc --- /dev/null +++ b/src/util/vector.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_vector_h__ +#define INCLUDE_vector_h__ + +#include "git2_util.h" + +typedef int (*git_vector_cmp)(const void *, const void *); + +enum { + GIT_VECTOR_SORTED = (1u << 0), + GIT_VECTOR_FLAG_MAX = (1u << 1) +}; + +typedef struct git_vector { + size_t _alloc_size; + git_vector_cmp _cmp; + void **contents; + size_t length; + uint32_t flags; +} git_vector; + +#define GIT_VECTOR_INIT {0} + +GIT_WARN_UNUSED_RESULT int git_vector_init( + git_vector *v, size_t initial_size, git_vector_cmp cmp); +void git_vector_free(git_vector *v); +void git_vector_free_deep(git_vector *v); /* free each entry and self */ +void git_vector_clear(git_vector *v); +GIT_WARN_UNUSED_RESULT int git_vector_dup( + git_vector *v, const git_vector *src, git_vector_cmp cmp); +void git_vector_swap(git_vector *a, git_vector *b); +int git_vector_size_hint(git_vector *v, size_t size_hint); + +void **git_vector_detach(size_t *size, size_t *asize, git_vector *v); + +void git_vector_sort(git_vector *v); + +/** Linear search for matching entry using internal comparison function */ +int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry); + +/** Linear search for matching entry using explicit comparison function */ +int git_vector_search2(size_t *at_pos, const git_vector *v, git_vector_cmp cmp, const void *key); + +/** + * Binary search for matching entry using explicit comparison function that + * returns position where item would go if not found. + */ +int git_vector_bsearch2( + size_t *at_pos, git_vector *v, git_vector_cmp cmp, const void *key); + +/** Binary search for matching entry using internal comparison function */ +GIT_INLINE(int) git_vector_bsearch(size_t *at_pos, git_vector *v, const void *key) +{ + return git_vector_bsearch2(at_pos, v, v->_cmp, key); +} + +GIT_INLINE(void *) git_vector_get(const git_vector *v, size_t position) +{ + return (position < v->length) ? v->contents[position] : NULL; +} + +#define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL) + +GIT_INLINE(size_t) git_vector_length(const git_vector *v) +{ + return v->length; +} + +GIT_INLINE(void *) git_vector_last(const git_vector *v) +{ + return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL; +} + +#define git_vector_foreach(v, iter, elem) \ + for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ ) + +#define git_vector_rforeach(v, iter, elem) \ + for ((iter) = (v)->length - 1; (iter) < SIZE_MAX && ((elem) = (v)->contents[(iter)], 1); (iter)-- ) + +int git_vector_insert(git_vector *v, void *element); +int git_vector_insert_sorted(git_vector *v, void *element, + int (*on_dup)(void **old, void *new)); +int git_vector_remove(git_vector *v, size_t idx); +void git_vector_pop(git_vector *v); +void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *)); + +void git_vector_remove_matching( + git_vector *v, + int (*match)(const git_vector *v, size_t idx, void *payload), + void *payload); + +int git_vector_resize_to(git_vector *v, size_t new_length); +int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len); +int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len); + +int git_vector_set(void **old, git_vector *v, size_t position, void *value); + +/** Check if vector is sorted */ +#define git_vector_is_sorted(V) (((V)->flags & GIT_VECTOR_SORTED) != 0) + +/** Directly set sorted state of vector */ +#define git_vector_set_sorted(V,S) do { \ + (V)->flags = (S) ? ((V)->flags | GIT_VECTOR_SORTED) : \ + ((V)->flags & ~GIT_VECTOR_SORTED); } while (0) + +/** Set the comparison function used for sorting the vector */ +GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp) +{ + if (cmp != v->_cmp) { + v->_cmp = cmp; + git_vector_set_sorted(v, 0); + } +} + +/* Just use this in tests, not for realz. returns -1 if not sorted */ +int git_vector_verify_sorted(const git_vector *v); + +/** + * Reverse the vector in-place. + */ +void git_vector_reverse(git_vector *v); + +#endif diff --git a/src/util/wildmatch.c b/src/util/wildmatch.c new file mode 100644 index 000000000..a894e4841 --- /dev/null +++ b/src/util/wildmatch.c @@ -0,0 +1,320 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + * + * Do shell-style pattern matching for ?, \, [], and * characters. + * It is 8bit clean. + * + * Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. + * Rich $alz is now . + * + * Modified by Wayne Davison to special-case '/' matching, to make '**' + * work differently than '*', and to fix the character-class code. + * + * Imported from git.git. + */ + +#include "wildmatch.h" + +#define GIT_SPACE 0x01 +#define GIT_DIGIT 0x02 +#define GIT_ALPHA 0x04 +#define GIT_GLOB_SPECIAL 0x08 +#define GIT_REGEX_SPECIAL 0x10 +#define GIT_PATHSPEC_MAGIC 0x20 +#define GIT_CNTRL 0x40 +#define GIT_PUNCT 0x80 + +enum { + S = GIT_SPACE, + A = GIT_ALPHA, + D = GIT_DIGIT, + G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ + R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */ + P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */ + X = GIT_CNTRL, + U = GIT_PUNCT, + Z = GIT_CNTRL | GIT_SPACE +}; + +static const unsigned char sane_ctype[256] = { + X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X, /* 0.. 15 */ + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 16.. 31 */ + S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */ + D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */ + P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */ + A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P, /* 80.. 95 */ + P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */ + A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X, /* 112..127 */ + /* Nothing in the 128.. range */ +}; + +#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0) +#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL) + +typedef unsigned char uchar; + +/* What character marks an inverted character class? */ +#define NEGATE_CLASS '!' +#define NEGATE_CLASS2 '^' + +#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \ + && *(class) == *(litmatch) \ + && strncmp((char*)class, litmatch, len) == 0) + +#if defined STDC_HEADERS || !defined isascii +# define ISASCII(c) 1 +#else +# define ISASCII(c) isascii(c) +#endif + +#ifdef isblank +# define ISBLANK(c) (ISASCII(c) && isblank(c)) +#else +# define ISBLANK(c) ((c) == ' ' || (c) == '\t') +#endif + +#ifdef isgraph +# define ISGRAPH(c) (ISASCII(c) && isgraph(c)) +#else +# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c)) +#endif + +#define ISPRINT(c) (ISASCII(c) && isprint(c)) +#define ISDIGIT(c) (ISASCII(c) && isdigit(c)) +#define ISALNUM(c) (ISASCII(c) && isalnum(c)) +#define ISALPHA(c) (ISASCII(c) && isalpha(c)) +#define ISCNTRL(c) (ISASCII(c) && iscntrl(c)) +#define ISLOWER(c) (ISASCII(c) && islower(c)) +#define ISPUNCT(c) (ISASCII(c) && ispunct(c)) +#define ISSPACE(c) (ISASCII(c) && isspace(c)) +#define ISUPPER(c) (ISASCII(c) && isupper(c)) +#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c)) + +/* Match pattern "p" against "text" */ +static int dowild(const uchar *p, const uchar *text, unsigned int flags) +{ + uchar p_ch; + const uchar *pattern = p; + + for ( ; (p_ch = *p) != '\0'; text++, p++) { + int matched, match_slash, negated; + uchar t_ch, prev_ch; + if ((t_ch = *text) == '\0' && p_ch != '*') + return WM_ABORT_ALL; + if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) + t_ch = tolower(t_ch); + if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) + p_ch = tolower(p_ch); + switch (p_ch) { + case '\\': + /* Literal match with following character. Note that the test + * in "default" handles the p[1] == '\0' failure case. */ + p_ch = *++p; + /* FALLTHROUGH */ + default: + if (t_ch != p_ch) + return WM_NOMATCH; + continue; + case '?': + /* Match anything but '/'. */ + if ((flags & WM_PATHNAME) && t_ch == '/') + return WM_NOMATCH; + continue; + case '*': + if (*++p == '*') { + const uchar *prev_p = p - 2; + while (*++p == '*') {} + if (!(flags & WM_PATHNAME)) + /* without WM_PATHNAME, '*' == '**' */ + match_slash = 1; + else if ((prev_p < pattern || *prev_p == '/') && + (*p == '\0' || *p == '/' || + (p[0] == '\\' && p[1] == '/'))) { + /* + * Assuming we already match 'foo/' and are at + * , just assume it matches + * nothing and go ahead match the rest of the + * pattern with the remaining string. This + * helps make foo/<*><*>/bar (<> because + * otherwise it breaks C comment syntax) match + * both foo/bar and foo/a/bar. + */ + if (p[0] == '/' && + dowild(p + 1, text, flags) == WM_MATCH) + return WM_MATCH; + match_slash = 1; + } else /* WM_PATHNAME is set */ + match_slash = 0; + } else + /* without WM_PATHNAME, '*' == '**' */ + match_slash = flags & WM_PATHNAME ? 0 : 1; + if (*p == '\0') { + /* Trailing "**" matches everything. Trailing "*" matches + * only if there are no more slash characters. */ + if (!match_slash) { + if (strchr((char*)text, '/') != NULL) + return WM_NOMATCH; + } + return WM_MATCH; + } else if (!match_slash && *p == '/') { + /* + * _one_ asterisk followed by a slash + * with WM_PATHNAME matches the next + * directory + */ + const char *slash = strchr((char*)text, '/'); + if (!slash) + return WM_NOMATCH; + text = (const uchar*)slash; + /* the slash is consumed by the top-level for loop */ + break; + } + while (1) { + if (t_ch == '\0') + break; + /* + * Try to advance faster when an asterisk is + * followed by a literal. We know in this case + * that the string before the literal + * must belong to "*". + * If match_slash is false, do not look past + * the first slash as it cannot belong to '*'. + */ + if (!is_glob_special(*p)) { + p_ch = *p; + if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) + p_ch = tolower(p_ch); + while ((t_ch = *text) != '\0' && + (match_slash || t_ch != '/')) { + if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) + t_ch = tolower(t_ch); + if (t_ch == p_ch) + break; + text++; + } + if (t_ch != p_ch) + return WM_NOMATCH; + } + if ((matched = dowild(p, text, flags)) != WM_NOMATCH) { + if (!match_slash || matched != WM_ABORT_TO_STARSTAR) + return matched; + } else if (!match_slash && t_ch == '/') + return WM_ABORT_TO_STARSTAR; + t_ch = *++text; + } + return WM_ABORT_ALL; + case '[': + p_ch = *++p; +#ifdef NEGATE_CLASS2 + if (p_ch == NEGATE_CLASS2) + p_ch = NEGATE_CLASS; +#endif + /* Assign literal 1/0 because of "matched" comparison. */ + negated = p_ch == NEGATE_CLASS ? 1 : 0; + if (negated) { + /* Inverted character class. */ + p_ch = *++p; + } + prev_ch = 0; + matched = 0; + do { + if (!p_ch) + return WM_ABORT_ALL; + if (p_ch == '\\') { + p_ch = *++p; + if (!p_ch) + return WM_ABORT_ALL; + if (t_ch == p_ch) + matched = 1; + } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') { + p_ch = *++p; + if (p_ch == '\\') { + p_ch = *++p; + if (!p_ch) + return WM_ABORT_ALL; + } + if (t_ch <= p_ch && t_ch >= prev_ch) + matched = 1; + else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) { + uchar t_ch_upper = toupper(t_ch); + if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch) + matched = 1; + } + p_ch = 0; /* This makes "prev_ch" get set to 0. */ + } else if (p_ch == '[' && p[1] == ':') { + const uchar *s; + int i; + for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/ + if (!p_ch) + return WM_ABORT_ALL; + i = (int)(p - s - 1); + if (i < 0 || p[-1] != ':') { + /* Didn't find ":]", so treat like a normal set. */ + p = s - 2; + p_ch = '['; + if (t_ch == p_ch) + matched = 1; + continue; + } + if (CC_EQ(s,i, "alnum")) { + if (ISALNUM(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "alpha")) { + if (ISALPHA(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "blank")) { + if (ISBLANK(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "cntrl")) { + if (ISCNTRL(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "digit")) { + if (ISDIGIT(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "graph")) { + if (ISGRAPH(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "lower")) { + if (ISLOWER(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "print")) { + if (ISPRINT(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "punct")) { + if (ISPUNCT(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "space")) { + if (ISSPACE(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "upper")) { + if (ISUPPER(t_ch)) + matched = 1; + else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "xdigit")) { + if (ISXDIGIT(t_ch)) + matched = 1; + } else /* malformed [:class:] string */ + return WM_ABORT_ALL; + p_ch = 0; /* This makes "prev_ch" get set to 0. */ + } else if (t_ch == p_ch) + matched = 1; + } while (prev_ch = p_ch, (p_ch = *++p) != ']'); + if (matched == negated || + ((flags & WM_PATHNAME) && t_ch == '/')) + return WM_NOMATCH; + continue; + } + } + + return *text ? WM_NOMATCH : WM_MATCH; +} + +/* Match the "pattern" against the "text" string. */ +int wildmatch(const char *pattern, const char *text, unsigned int flags) +{ + return dowild((const uchar*)pattern, (const uchar*)text, flags); +} diff --git a/src/util/wildmatch.h b/src/util/wildmatch.h new file mode 100644 index 000000000..f20640500 --- /dev/null +++ b/src/util/wildmatch.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_wildmatch_h__ +#define INCLUDE_wildmatch_h__ + +#include "git2_util.h" + +#define WM_CASEFOLD 1 +#define WM_PATHNAME 2 + +#define WM_NOMATCH 1 +#define WM_MATCH 0 +#define WM_ABORT_ALL -1 +#define WM_ABORT_TO_STARSTAR -2 + +int wildmatch(const char *pattern, const char *text, unsigned int flags); + +#endif diff --git a/src/util/win32/dir.c b/src/util/win32/dir.c new file mode 100644 index 000000000..44052caf0 --- /dev/null +++ b/src/util/win32/dir.c @@ -0,0 +1,122 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "dir.h" + +#define GIT__WIN32_NO_WRAP_DIR +#include "posix.h" + +git__DIR *git__opendir(const char *dir) +{ + git_win32_path filter_w; + git__DIR *new = NULL; + size_t dirlen, alloclen; + + if (!dir || !git_win32__findfirstfile_filter(filter_w, dir)) + return NULL; + + dirlen = strlen(dir); + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, sizeof(*new), dirlen) || + GIT_ADD_SIZET_OVERFLOW(&alloclen, alloclen, 1) || + !(new = git__calloc(1, alloclen))) + return NULL; + + memcpy(new->dir, dir, dirlen); + + new->h = FindFirstFileW(filter_w, &new->f); + + if (new->h == INVALID_HANDLE_VALUE) { + git_error_set(GIT_ERROR_OS, "could not open directory '%s'", dir); + git__free(new); + return NULL; + } + + new->first = 1; + return new; +} + +int git__readdir_ext( + git__DIR *d, + struct git__dirent *entry, + struct git__dirent **result, + int *is_dir) +{ + if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE) + return -1; + + *result = NULL; + + if (d->first) + d->first = 0; + else if (!FindNextFileW(d->h, &d->f)) { + if (GetLastError() == ERROR_NO_MORE_FILES) + return 0; + git_error_set(GIT_ERROR_OS, "could not read from directory '%s'", d->dir); + return -1; + } + + /* Convert the path to UTF-8 */ + if (git_win32_path_to_utf8(entry->d_name, d->f.cFileName) < 0) + return -1; + + entry->d_ino = 0; + + *result = entry; + + if (is_dir != NULL) + *is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); + + return 0; +} + +struct git__dirent *git__readdir(git__DIR *d) +{ + struct git__dirent *result; + if (git__readdir_ext(d, &d->entry, &result, NULL) < 0) + return NULL; + return result; +} + +void git__rewinddir(git__DIR *d) +{ + git_win32_path filter_w; + + if (!d) + return; + + if (d->h != INVALID_HANDLE_VALUE) { + FindClose(d->h); + d->h = INVALID_HANDLE_VALUE; + d->first = 0; + } + + if (!git_win32__findfirstfile_filter(filter_w, d->dir)) + return; + + d->h = FindFirstFileW(filter_w, &d->f); + + if (d->h == INVALID_HANDLE_VALUE) + git_error_set(GIT_ERROR_OS, "could not open directory '%s'", d->dir); + else + d->first = 1; +} + +int git__closedir(git__DIR *d) +{ + if (!d) + return 0; + + if (d->h != INVALID_HANDLE_VALUE) { + FindClose(d->h); + d->h = INVALID_HANDLE_VALUE; + } + + git__free(d); + return 0; +} + diff --git a/src/util/win32/dir.h b/src/util/win32/dir.h new file mode 100644 index 000000000..810111534 --- /dev/null +++ b/src/util/win32/dir.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_dir_h__ +#define INCLUDE_win32_dir_h__ + +#include "git2_util.h" + +#include "w32_util.h" + +struct git__dirent { + int d_ino; + git_win32_utf8_path d_name; +}; + +typedef struct { + HANDLE h; + WIN32_FIND_DATAW f; + struct git__dirent entry; + int first; + char dir[GIT_FLEX_ARRAY]; +} git__DIR; + +extern git__DIR *git__opendir(const char *); +extern struct git__dirent *git__readdir(git__DIR *); +extern int git__readdir_ext( + git__DIR *, struct git__dirent *, struct git__dirent **, int *); +extern void git__rewinddir(git__DIR *); +extern int git__closedir(git__DIR *); + +# ifndef GIT__WIN32_NO_WRAP_DIR +# define dirent git__dirent +# define DIR git__DIR +# define opendir git__opendir +# define readdir git__readdir +# define readdir_r(d,e,r) git__readdir_ext((d),(e),(r),NULL) +# define rewinddir git__rewinddir +# define closedir git__closedir +# endif + +#endif diff --git a/src/util/win32/error.c b/src/util/win32/error.c new file mode 100644 index 000000000..3a52fb5a9 --- /dev/null +++ b/src/util/win32/error.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "error.h" + +#include "utf-conv.h" + +#ifdef GIT_WINHTTP +# include +#endif + +char *git_win32_get_error_message(DWORD error_code) +{ + LPWSTR lpMsgBuf = NULL; + HMODULE hModule = NULL; + char *utf8_msg = NULL; + DWORD dwFlags = + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS; + + if (!error_code) + return NULL; + +#ifdef GIT_WINHTTP + /* Errors raised by WinHTTP are not in the system resource table */ + if (error_code >= WINHTTP_ERROR_BASE && + error_code <= WINHTTP_ERROR_LAST) + hModule = GetModuleHandleW(L"winhttp"); +#endif + + GIT_UNUSED(hModule); + + if (hModule) + dwFlags |= FORMAT_MESSAGE_FROM_HMODULE; + else + dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM; + + if (FormatMessageW(dwFlags, hModule, error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&lpMsgBuf, 0, NULL)) { + /* Convert the message to UTF-8. If this fails, we will + * return NULL, which is a condition expected by the caller */ + if (git__utf16_to_8_alloc(&utf8_msg, lpMsgBuf) < 0) + utf8_msg = NULL; + + LocalFree(lpMsgBuf); + } + + return utf8_msg; +} diff --git a/src/util/win32/error.h b/src/util/win32/error.h new file mode 100644 index 000000000..fd53b7f99 --- /dev/null +++ b/src/util/win32/error.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_win32_error_h__ +#define INCLUDE_win32_error_h__ + +#include "git2_util.h" + +extern char *git_win32_get_error_message(DWORD error_code); + +#endif diff --git a/src/util/win32/findfile.c b/src/util/win32/findfile.c new file mode 100644 index 000000000..725a90167 --- /dev/null +++ b/src/util/win32/findfile.c @@ -0,0 +1,286 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "findfile.h" + +#include "path_w32.h" +#include "utf-conv.h" +#include "fs_path.h" + +#define REG_GITFORWINDOWS_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" +#define REG_GITFORWINDOWS_KEY_WOW64 L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" + +static int git_win32__expand_path(git_win32_path dest, const wchar_t *src) +{ + DWORD len = ExpandEnvironmentStringsW(src, dest, GIT_WIN_PATH_UTF16); + + if (!len || len > GIT_WIN_PATH_UTF16) + return -1; + + return 0; +} + +static int win32_path_to_8(git_str *dest, const wchar_t *src) +{ + git_win32_utf8_path utf8_path; + + if (git_win32_path_to_utf8(utf8_path, src) < 0) { + git_error_set(GIT_ERROR_OS, "unable to convert path to UTF-8"); + return -1; + } + + /* Convert backslashes to forward slashes */ + git_fs_path_mkposix(utf8_path); + + return git_str_sets(dest, utf8_path); +} + +static git_win32_path mock_registry; +static bool mock_registry_set; + +extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir) +{ + if (!mock_sysdir) { + mock_registry[0] = L'\0'; + mock_registry_set = false; + } else { + size_t len = wcslen(mock_sysdir); + + if (len > GIT_WIN_PATH_MAX) { + git_error_set(GIT_ERROR_INVALID, "mock path too long"); + return -1; + } + + wcscpy(mock_registry, mock_sysdir); + mock_registry_set = true; + } + + return 0; +} + +static int lookup_registry_key( + git_win32_path out, + const HKEY hive, + const wchar_t* key, + const wchar_t *value) +{ + HKEY hkey; + DWORD type, size; + int error = GIT_ENOTFOUND; + + /* + * Registry data may not be NUL terminated, provide room to do + * it ourselves. + */ + size = (DWORD)((sizeof(git_win32_path) - 1) * sizeof(wchar_t)); + + if (RegOpenKeyExW(hive, key, 0, KEY_READ, &hkey) != 0) + return GIT_ENOTFOUND; + + if (RegQueryValueExW(hkey, value, NULL, &type, (LPBYTE)out, &size) == 0 && + type == REG_SZ && + size > 0 && + size < sizeof(git_win32_path)) { + size_t wsize = size / sizeof(wchar_t); + size_t len = wsize - 1; + + if (out[wsize - 1] != L'\0') { + len = wsize; + out[wsize] = L'\0'; + } + + if (out[len - 1] == L'\\') + out[len - 1] = L'\0'; + + if (_waccess(out, F_OK) == 0) + error = 0; + } + + RegCloseKey(hkey); + return error; +} + +static int find_sysdir_in_registry(git_win32_path out) +{ + if (mock_registry_set) { + if (mock_registry[0] == L'\0') + return GIT_ENOTFOUND; + + wcscpy(out, mock_registry); + return 0; + } + + if (lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || + lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0 || + lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 || + lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0) + return 0; + + return GIT_ENOTFOUND; +} + +static int find_sysdir_in_path(git_win32_path out) +{ + size_t out_len; + + if (git_win32_path_find_executable(out, L"git.exe") < 0 && + git_win32_path_find_executable(out, L"git.cmd") < 0) + return GIT_ENOTFOUND; + + out_len = wcslen(out); + + /* Trim the file name */ + if (out_len <= CONST_STRLEN(L"git.exe")) + return GIT_ENOTFOUND; + + out_len -= CONST_STRLEN(L"git.exe"); + + if (out_len && out[out_len - 1] == L'\\') + out_len--; + + /* + * Git for Windows usually places the command in a 'bin' or + * 'cmd' directory, trim that. + */ + if (out_len >= CONST_STRLEN(L"\\bin") && + wcsncmp(&out[out_len - CONST_STRLEN(L"\\bin")], L"\\bin", CONST_STRLEN(L"\\bin")) == 0) + out_len -= CONST_STRLEN(L"\\bin"); + else if (out_len >= CONST_STRLEN(L"\\cmd") && + wcsncmp(&out[out_len - CONST_STRLEN(L"\\cmd")], L"\\cmd", CONST_STRLEN(L"\\cmd")) == 0) + out_len -= CONST_STRLEN(L"\\cmd"); + + if (!out_len) + return GIT_ENOTFOUND; + + out[out_len] = L'\0'; + return 0; +} + +static int win32_find_existing_dirs( + git_str* out, + const wchar_t* tmpl[]) +{ + git_win32_path path16; + git_str buf = GIT_STR_INIT; + + git_str_clear(out); + + for (; *tmpl != NULL; tmpl++) { + if (!git_win32__expand_path(path16, *tmpl) && + path16[0] != L'%' && + !_waccess(path16, F_OK)) { + win32_path_to_8(&buf, path16); + + if (buf.size) + git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); + } + } + + git_str_dispose(&buf); + + return (git_str_oom(out) ? -1 : 0); +} + +static int append_subdir(git_str *out, git_str *path, const char *subdir) +{ + static const char* architecture_roots[] = { + "", + "mingw64", + "mingw32", + NULL + }; + const char **root; + size_t orig_path_len = path->size; + + for (root = architecture_roots; *root; root++) { + if ((*root[0] && git_str_joinpath(path, path->ptr, *root) < 0) || + git_str_joinpath(path, path->ptr, subdir) < 0) + return -1; + + if (git_fs_path_exists(path->ptr) && + git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, path->ptr) < 0) + return -1; + + git_str_truncate(path, orig_path_len); + } + + return 0; +} + +int git_win32__find_system_dirs(git_str *out, const char *subdir) +{ + git_win32_path pathdir, regdir; + git_str path8 = GIT_STR_INIT; + bool has_pathdir, has_regdir; + int error; + + has_pathdir = (find_sysdir_in_path(pathdir) == 0); + has_regdir = (find_sysdir_in_registry(regdir) == 0); + + if (!has_pathdir && !has_regdir) + return 0; + + /* + * Usually the git in the path is the same git in the registry, + * in this case there's no need to duplicate the paths. + */ + if (has_pathdir && has_regdir && wcscmp(pathdir, regdir) == 0) + has_regdir = false; + + if (has_pathdir) { + if ((error = win32_path_to_8(&path8, pathdir)) < 0 || + (error = append_subdir(out, &path8, subdir)) < 0) + goto done; + } + + if (has_regdir) { + if ((error = win32_path_to_8(&path8, regdir)) < 0 || + (error = append_subdir(out, &path8, subdir)) < 0) + goto done; + } + +done: + git_str_dispose(&path8); + return error; +} + +int git_win32__find_global_dirs(git_str *out) +{ + static const wchar_t *global_tmpls[4] = { + L"%HOME%\\", + L"%HOMEDRIVE%%HOMEPATH%\\", + L"%USERPROFILE%\\", + NULL, + }; + + return win32_find_existing_dirs(out, global_tmpls); +} + +int git_win32__find_xdg_dirs(git_str *out) +{ + static const wchar_t *global_tmpls[7] = { + L"%XDG_CONFIG_HOME%\\git", + L"%APPDATA%\\git", + L"%LOCALAPPDATA%\\git", + L"%HOME%\\.config\\git", + L"%HOMEDRIVE%%HOMEPATH%\\.config\\git", + L"%USERPROFILE%\\.config\\git", + NULL, + }; + + return win32_find_existing_dirs(out, global_tmpls); +} + +int git_win32__find_programdata_dirs(git_str *out) +{ + static const wchar_t *programdata_tmpls[2] = { + L"%PROGRAMDATA%\\Git", + NULL, + }; + + return win32_find_existing_dirs(out, programdata_tmpls); +} diff --git a/src/util/win32/findfile.h b/src/util/win32/findfile.h new file mode 100644 index 000000000..7b191d1fe --- /dev/null +++ b/src/util/win32/findfile.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_win32_findfile_h__ +#define INCLUDE_win32_findfile_h__ + +#include "git2_util.h" + +/** Sets the mock registry root for Git for Windows for testing. */ +extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir); + +extern int git_win32__find_system_dirs(git_str *out, const char *subpath); +extern int git_win32__find_global_dirs(git_str *out); +extern int git_win32__find_xdg_dirs(git_str *out); +extern int git_win32__find_programdata_dirs(git_str *out); + +#endif + diff --git a/src/util/win32/map.c b/src/util/win32/map.c new file mode 100644 index 000000000..52e1363ea --- /dev/null +++ b/src/util/win32/map.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2_util.h" + +#include "map.h" +#include + +#ifndef NO_MMAP + +static DWORD get_page_size(void) +{ + static DWORD page_size; + SYSTEM_INFO sys; + + if (!page_size) { + GetSystemInfo(&sys); + page_size = sys.dwPageSize; + } + + return page_size; +} + +static DWORD get_allocation_granularity(void) +{ + static DWORD granularity; + SYSTEM_INFO sys; + + if (!granularity) { + GetSystemInfo(&sys); + granularity = sys.dwAllocationGranularity; + } + + return granularity; +} + +int git__page_size(size_t *page_size) +{ + *page_size = get_page_size(); + return 0; +} + +int git__mmap_alignment(size_t *page_size) +{ + *page_size = get_allocation_granularity(); + return 0; +} + +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) +{ + HANDLE fh = (HANDLE)_get_osfhandle(fd); + DWORD alignment = get_allocation_granularity(); + DWORD fmap_prot = 0; + DWORD view_prot = 0; + DWORD off_low = 0; + DWORD off_hi = 0; + off64_t page_start; + off64_t page_offset; + + GIT_MMAP_VALIDATE(out, len, prot, flags); + + out->data = NULL; + out->len = 0; + out->fmh = NULL; + + if (fh == INVALID_HANDLE_VALUE) { + errno = EBADF; + git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value"); + return -1; + } + + if (prot & GIT_PROT_WRITE) + fmap_prot |= PAGE_READWRITE; + else if (prot & GIT_PROT_READ) + fmap_prot |= PAGE_READONLY; + + if (prot & GIT_PROT_WRITE) + view_prot |= FILE_MAP_WRITE; + if (prot & GIT_PROT_READ) + view_prot |= FILE_MAP_READ; + + page_start = (offset / alignment) * alignment; + page_offset = offset - page_start; + + if (page_offset != 0) { /* offset must be multiple of the allocation granularity */ + errno = EINVAL; + git_error_set(GIT_ERROR_OS, "failed to mmap. Offset must be multiple of allocation granularity"); + return -1; + } + + out->fmh = CreateFileMapping(fh, NULL, fmap_prot, 0, 0, NULL); + if (!out->fmh || out->fmh == INVALID_HANDLE_VALUE) { + git_error_set(GIT_ERROR_OS, "failed to mmap. Invalid handle value"); + out->fmh = NULL; + return -1; + } + + off_low = (DWORD)(page_start); + off_hi = (DWORD)(page_start >> 32); + out->data = MapViewOfFile(out->fmh, view_prot, off_hi, off_low, len); + if (!out->data) { + git_error_set(GIT_ERROR_OS, "failed to mmap. No data written"); + CloseHandle(out->fmh); + out->fmh = NULL; + return -1; + } + out->len = len; + + return 0; +} + +int p_munmap(git_map *map) +{ + int error = 0; + + GIT_ASSERT_ARG(map); + + if (map->data) { + if (!UnmapViewOfFile(map->data)) { + git_error_set(GIT_ERROR_OS, "failed to munmap. Could not unmap view of file"); + error = -1; + } + map->data = NULL; + } + + if (map->fmh) { + if (!CloseHandle(map->fmh)) { + git_error_set(GIT_ERROR_OS, "failed to munmap. Could not close handle"); + error = -1; + } + map->fmh = NULL; + } + + return error; +} + +#endif diff --git a/src/util/win32/mingw-compat.h b/src/util/win32/mingw-compat.h new file mode 100644 index 000000000..aa2bef98d --- /dev/null +++ b/src/util/win32/mingw-compat.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_mingw_compat_h__ +#define INCLUDE_win32_mingw_compat_h__ + +#if defined(__MINGW32__) + +#undef stat + +#if _WIN32_WINNT < 0x0600 && !defined(__MINGW64_VERSION_MAJOR) +#undef MemoryBarrier +void __mingworg_MemoryBarrier(void); +#define MemoryBarrier __mingworg_MemoryBarrier +#define VOLUME_NAME_DOS 0x0 +#endif + +#endif + +#endif diff --git a/src/util/win32/msvc-compat.h b/src/util/win32/msvc-compat.h new file mode 100644 index 000000000..03f9f36dc --- /dev/null +++ b/src/util/win32/msvc-compat.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_msvc_compat_h__ +#define INCLUDE_win32_msvc_compat_h__ + +#if defined(_MSC_VER) + +typedef unsigned short mode_t; +typedef SSIZE_T ssize_t; + +#ifdef _WIN64 +# define SSIZE_MAX _I64_MAX +#else +# define SSIZE_MAX LONG_MAX +#endif + +#define strcasecmp(s1, s2) _stricmp(s1, s2) +#define strncasecmp(s1, s2, c) _strnicmp(s1, s2, c) + +#endif + +/* + * Offer GIT_LIBGIT2_CALL for our calling conventions (__cdecl, always). + * This is useful for providing callbacks to userspace code. + * + * Offer GIT_SYSTEM_CALL for the system calling conventions (__stdcall on + * Win32). Useful for providing callbacks to system libraries. + */ +#define GIT_LIBGIT2_CALL __cdecl +#define GIT_SYSTEM_CALL NTAPI + +#endif diff --git a/src/util/win32/path_w32.c b/src/util/win32/path_w32.c new file mode 100644 index 000000000..d9fc8292b --- /dev/null +++ b/src/util/win32/path_w32.c @@ -0,0 +1,642 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "path_w32.h" + +#include "fs_path.h" +#include "utf-conv.h" +#include "posix.h" +#include "reparse.h" +#include "dir.h" + +#define PATH__NT_NAMESPACE L"\\\\?\\" +#define PATH__NT_NAMESPACE_LEN 4 + +#define PATH__ABSOLUTE_LEN 3 + +#define path__is_nt_namespace(p) \ + (((p)[0] == '\\' && (p)[1] == '\\' && (p)[2] == '?' && (p)[3] == '\\') || \ + ((p)[0] == '/' && (p)[1] == '/' && (p)[2] == '?' && (p)[3] == '/')) + +#define path__is_unc(p) \ + (((p)[0] == '\\' && (p)[1] == '\\') || ((p)[0] == '/' && (p)[1] == '/')) + +#define path__startswith_slash(p) \ + ((p)[0] == '\\' || (p)[0] == '/') + +GIT_INLINE(int) path__cwd(wchar_t *path, int size) +{ + int len; + + if ((len = GetCurrentDirectoryW(size, path)) == 0) { + errno = GetLastError() == ERROR_ACCESS_DENIED ? EACCES : ENOENT; + return -1; + } else if (len > size) { + errno = ENAMETOOLONG; + return -1; + } + + /* The Win32 APIs may return "\\?\" once you've used it first. + * But it may not. What a gloriously predictable API! + */ + if (wcsncmp(path, PATH__NT_NAMESPACE, PATH__NT_NAMESPACE_LEN)) + return len; + + len -= PATH__NT_NAMESPACE_LEN; + + memmove(path, path + PATH__NT_NAMESPACE_LEN, sizeof(wchar_t) * len); + return len; +} + +static wchar_t *path__skip_server(wchar_t *path) +{ + wchar_t *c; + + for (c = path; *c; c++) { + if (git_fs_path_is_dirsep(*c)) + return c + 1; + } + + return c; +} + +static wchar_t *path__skip_prefix(wchar_t *path) +{ + if (path__is_nt_namespace(path)) { + path += PATH__NT_NAMESPACE_LEN; + + if (wcsncmp(path, L"UNC\\", 4) == 0) + path = path__skip_server(path + 4); + else if (git_fs_path_is_absolute(path)) + path += PATH__ABSOLUTE_LEN; + } else if (git_fs_path_is_absolute(path)) { + path += PATH__ABSOLUTE_LEN; + } else if (path__is_unc(path)) { + path = path__skip_server(path + 2); + } + + return path; +} + +int git_win32_path_canonicalize(git_win32_path path) +{ + wchar_t *base, *from, *to, *next; + size_t len; + + base = to = path__skip_prefix(path); + + /* Unposixify if the prefix */ + for (from = path; from < to; from++) { + if (*from == L'/') + *from = L'\\'; + } + + while (*from) { + for (next = from; *next; ++next) { + if (*next == L'/') { + *next = L'\\'; + break; + } + + if (*next == L'\\') + break; + } + + len = next - from; + + if (len == 1 && from[0] == L'.') + /* do nothing with singleton dot */; + + else if (len == 2 && from[0] == L'.' && from[1] == L'.') { + if (to == base) { + /* no more path segments to strip, eat the "../" */ + if (*next == L'\\') + len++; + + base = to; + } else { + /* back up a path segment */ + while (to > base && to[-1] == L'\\') to--; + while (to > base && to[-1] != L'\\') to--; + } + } else { + if (*next == L'\\' && *from != L'\\') + len++; + + if (to != from) + memmove(to, from, sizeof(wchar_t) * len); + + to += len; + } + + from += len; + + while (*from == L'\\') from++; + } + + /* Strip trailing backslashes */ + while (to > base && to[-1] == L'\\') to--; + + *to = L'\0'; + + if ((to - path) > INT_MAX) { + SetLastError(ERROR_FILENAME_EXCED_RANGE); + return -1; + } + + return (int)(to - path); +} + +static int git_win32_path_join( + git_win32_path dest, + const wchar_t *one, + size_t one_len, + const wchar_t *two, + size_t two_len) +{ + size_t backslash = 0; + + if (one_len && two_len && one[one_len - 1] != L'\\') + backslash = 1; + + if (one_len + two_len + backslash > MAX_PATH) { + git_error_set(GIT_ERROR_INVALID, "path too long"); + return -1; + } + + memmove(dest, one, one_len * sizeof(wchar_t)); + + if (backslash) + dest[one_len] = L'\\'; + + memcpy(dest + one_len + backslash, two, two_len * sizeof(wchar_t)); + dest[one_len + backslash + two_len] = L'\0'; + + return 0; +} + +struct win32_path_iter { + wchar_t *env; + const wchar_t *current_dir; +}; + +static int win32_path_iter_init(struct win32_path_iter *iter) +{ + DWORD len = GetEnvironmentVariableW(L"PATH", NULL, 0); + + if (!len && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { + iter->env = NULL; + iter->current_dir = NULL; + return 0; + } else if (!len) { + git_error_set(GIT_ERROR_OS, "could not load PATH"); + return -1; + } + + iter->env = git__malloc(len * sizeof(wchar_t)); + GIT_ERROR_CHECK_ALLOC(iter->env); + + len = GetEnvironmentVariableW(L"PATH", iter->env, len); + + if (len == 0) { + git_error_set(GIT_ERROR_OS, "could not load PATH"); + return -1; + } + + iter->current_dir = iter->env; + return 0; +} + +static int win32_path_iter_next( + const wchar_t **out, + size_t *out_len, + struct win32_path_iter *iter) +{ + const wchar_t *start; + wchar_t term; + size_t len = 0; + + if (!iter->current_dir || !*iter->current_dir) + return GIT_ITEROVER; + + term = (*iter->current_dir == L'"') ? *iter->current_dir++ : L';'; + start = iter->current_dir; + + while (*iter->current_dir && *iter->current_dir != term) { + iter->current_dir++; + len++; + } + + *out = start; + *out_len = len; + + if (term == L'"' && *iter->current_dir) + iter->current_dir++; + + while (*iter->current_dir == L';') + iter->current_dir++; + + return 0; +} + +static void win32_path_iter_dispose(struct win32_path_iter *iter) +{ + if (!iter) + return; + + git__free(iter->env); + iter->env = NULL; + iter->current_dir = NULL; +} + +int git_win32_path_find_executable(git_win32_path fullpath, wchar_t *exe) +{ + struct win32_path_iter path_iter; + const wchar_t *dir; + size_t dir_len, exe_len = wcslen(exe); + bool found = false; + + if (win32_path_iter_init(&path_iter) < 0) + return -1; + + while (win32_path_iter_next(&dir, &dir_len, &path_iter) != GIT_ITEROVER) { + if (git_win32_path_join(fullpath, dir, dir_len, exe, exe_len) < 0) + continue; + + if (_waccess(fullpath, 0) == 0) { + found = true; + break; + } + } + + win32_path_iter_dispose(&path_iter); + + if (found) + return 0; + + fullpath[0] = L'\0'; + return GIT_ENOTFOUND; +} + +static int win32_path_cwd(wchar_t *out, size_t len) +{ + int cwd_len; + + if (len > INT_MAX) { + errno = ENAMETOOLONG; + return -1; + } + + if ((cwd_len = path__cwd(out, (int)len)) < 0) + return -1; + + /* UNC paths */ + if (wcsncmp(L"\\\\", out, 2) == 0) { + /* Our buffer must be at least 5 characters larger than the + * current working directory: we swallow one of the leading + * '\'s, but we we add a 'UNC' specifier to the path, plus + * a trailing directory separator, plus a NUL. + */ + if (cwd_len > GIT_WIN_PATH_MAX - 4) { + errno = ENAMETOOLONG; + return -1; + } + + memmove(out+2, out, sizeof(wchar_t) * cwd_len); + out[0] = L'U'; + out[1] = L'N'; + out[2] = L'C'; + + cwd_len += 2; + } + + /* Our buffer must be at least 2 characters larger than the current + * working directory. (One character for the directory separator, + * one for the null. + */ + else if (cwd_len > GIT_WIN_PATH_MAX - 2) { + errno = ENAMETOOLONG; + return -1; + } + + return cwd_len; +} + +int git_win32_path_from_utf8(git_win32_path out, const char *src) +{ + wchar_t *dest = out; + + /* All win32 paths are in NT-prefixed format, beginning with "\\?\". */ + memcpy(dest, PATH__NT_NAMESPACE, sizeof(wchar_t) * PATH__NT_NAMESPACE_LEN); + dest += PATH__NT_NAMESPACE_LEN; + + /* See if this is an absolute path (beginning with a drive letter) */ + if (git_fs_path_is_absolute(src)) { + if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0) + goto on_error; + } + /* File-prefixed NT-style paths beginning with \\?\ */ + else if (path__is_nt_namespace(src)) { + /* Skip the NT prefix, the destination already contains it */ + if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0) + goto on_error; + } + /* UNC paths */ + else if (path__is_unc(src)) { + memcpy(dest, L"UNC\\", sizeof(wchar_t) * 4); + dest += 4; + + /* Skip the leading "\\" */ + if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0) + goto on_error; + } + /* Absolute paths omitting the drive letter */ + else if (path__startswith_slash(src)) { + if (path__cwd(dest, GIT_WIN_PATH_MAX) < 0) + goto on_error; + + if (!git_fs_path_is_absolute(dest)) { + errno = ENOENT; + goto on_error; + } + + /* Skip the drive letter specification ("C:") */ + if (git__utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0) + goto on_error; + } + /* Relative paths */ + else { + int cwd_len; + + if ((cwd_len = win32_path_cwd(dest, GIT_WIN_PATH_MAX)) < 0) + goto on_error; + + dest[cwd_len++] = L'\\'; + + if (git__utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0) + goto on_error; + } + + return git_win32_path_canonicalize(out); + +on_error: + /* set windows error code so we can use its error message */ + if (errno == ENAMETOOLONG) + SetLastError(ERROR_FILENAME_EXCED_RANGE); + + return -1; +} + +int git_win32_path_relative_from_utf8(git_win32_path out, const char *src) +{ + wchar_t *dest = out, *p; + int len; + + /* Handle absolute paths */ + if (git_fs_path_is_absolute(src) || + path__is_nt_namespace(src) || + path__is_unc(src) || + path__startswith_slash(src)) { + return git_win32_path_from_utf8(out, src); + } + + if ((len = git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src)) < 0) + return -1; + + for (p = dest; p < (dest + len); p++) { + if (*p == L'/') + *p = L'\\'; + } + + return len; +} + +int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src) +{ + char *out = dest; + int len; + + /* Strip NT namespacing "\\?\" */ + if (path__is_nt_namespace(src)) { + src += 4; + + /* "\\?\UNC\server\share" -> "\\server\share" */ + if (wcsncmp(src, L"UNC\\", 4) == 0) { + src += 4; + + memcpy(dest, "\\\\", 2); + out = dest + 2; + } + } + + if ((len = git__utf16_to_8(out, GIT_WIN_PATH_UTF8, src)) < 0) + return len; + + git_fs_path_mkposix(dest); + + return len; +} + +char *git_win32_path_8dot3_name(const char *path) +{ + git_win32_path longpath, shortpath; + wchar_t *start; + char *shortname; + int len, namelen = 1; + + if (git_win32_path_from_utf8(longpath, path) < 0) + return NULL; + + len = GetShortPathNameW(longpath, shortpath, GIT_WIN_PATH_UTF16); + + while (len && shortpath[len-1] == L'\\') + shortpath[--len] = L'\0'; + + if (len == 0 || len >= GIT_WIN_PATH_UTF16) + return NULL; + + for (start = shortpath + (len - 1); + start > shortpath && *(start-1) != '/' && *(start-1) != '\\'; + start--) + namelen++; + + /* We may not have actually been given a short name. But if we have, + * it will be in the ASCII byte range, so we don't need to worry about + * multi-byte sequences and can allocate naively. + */ + if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL) + return NULL; + + if ((len = git__utf16_to_8(shortname, namelen + 1, start)) < 0) + return NULL; + + return shortname; +} + +static bool path_is_volume(wchar_t *target, size_t target_len) +{ + return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0); +} + +/* On success, returns the length, in characters, of the path stored in dest. + * On failure, returns a negative value. */ +int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path) +{ + BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf; + HANDLE handle = NULL; + DWORD ioctl_ret; + wchar_t *target; + size_t target_len; + + int error = -1; + + handle = CreateFileW(path, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (handle == INVALID_HANDLE_VALUE) { + errno = ENOENT; + return -1; + } + + if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, + reparse_buf, sizeof(buf), &ioctl_ret, NULL)) { + errno = EINVAL; + goto on_error; + } + + switch (reparse_buf->ReparseTag) { + case IO_REPARSE_TAG_SYMLINK: + target = reparse_buf->ReparseBuffer.SymbolicLink.PathBuffer + + (reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameOffset / sizeof(WCHAR)); + target_len = reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameLength / sizeof(WCHAR); + break; + case IO_REPARSE_TAG_MOUNT_POINT: + target = reparse_buf->ReparseBuffer.MountPoint.PathBuffer + + (reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset / sizeof(WCHAR)); + target_len = reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength / sizeof(WCHAR); + break; + default: + errno = EINVAL; + goto on_error; + } + + if (path_is_volume(target, target_len)) { + /* This path is a reparse point that represents another volume mounted + * at this location, it is not a symbolic link our input was canonical. + */ + errno = EINVAL; + error = -1; + } else if (target_len) { + /* The path may need to have a namespace prefix removed. */ + target_len = git_win32_path_remove_namespace(target, target_len); + + /* Need one additional character in the target buffer + * for the terminating NULL. */ + if (GIT_WIN_PATH_UTF16 > target_len) { + wcscpy(dest, target); + error = (int)target_len; + } + } + +on_error: + CloseHandle(handle); + return error; +} + +/** + * Removes any trailing backslashes from a path, except in the case of a drive + * letter path (C:\, D:\, etc.). This function cannot fail. + * + * @param path The path which should be trimmed. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_trim_end(wchar_t *str, size_t len) +{ + while (1) { + if (!len || str[len - 1] != L'\\') + break; + + /* + * Don't trim backslashes from drive letter paths, which + * are 3 characters long and of the form C:\, D:\, etc. + */ + if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':') + break; + + len--; + } + + str[len] = L'\0'; + + return len; +} + +/** + * Removes any of the following namespace prefixes from a path, + * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. + * + * @param path The path which should be converted. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_remove_namespace(wchar_t *str, size_t len) +{ + static const wchar_t dosdevices_namespace[] = L"\\\?\?\\"; + static const wchar_t nt_namespace[] = L"\\\\?\\"; + static const wchar_t unc_namespace_remainder[] = L"UNC\\"; + static const wchar_t unc_prefix[] = L"\\\\"; + + const wchar_t *prefix = NULL, *remainder = NULL; + size_t prefix_len = 0, remainder_len = 0; + + /* "\??\" -- DOS Devices prefix */ + if (len >= CONST_STRLEN(dosdevices_namespace) && + !wcsncmp(str, dosdevices_namespace, CONST_STRLEN(dosdevices_namespace))) { + remainder = str + CONST_STRLEN(dosdevices_namespace); + remainder_len = len - CONST_STRLEN(dosdevices_namespace); + } + /* "\\?\" -- NT namespace prefix */ + else if (len >= CONST_STRLEN(nt_namespace) && + !wcsncmp(str, nt_namespace, CONST_STRLEN(nt_namespace))) { + remainder = str + CONST_STRLEN(nt_namespace); + remainder_len = len - CONST_STRLEN(nt_namespace); + } + + /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */ + if (remainder_len >= CONST_STRLEN(unc_namespace_remainder) && + !wcsncmp(remainder, unc_namespace_remainder, CONST_STRLEN(unc_namespace_remainder))) { + + /* + * The proper Win32 path for a UNC share has "\\" at beginning of it + * and looks like "\\server\share\". So remove the + * UNC namespace and add a prefix of "\\" in its place. + */ + remainder += CONST_STRLEN(unc_namespace_remainder); + remainder_len -= CONST_STRLEN(unc_namespace_remainder); + + prefix = unc_prefix; + prefix_len = CONST_STRLEN(unc_prefix); + } + + /* + * Sanity check that the new string isn't longer than the old one. + * (This could only happen due to programmer error introducing a + * prefix longer than the namespace it replaces.) + */ + if (remainder && len >= remainder_len + prefix_len) { + if (prefix) + memmove(str, prefix, prefix_len * sizeof(wchar_t)); + + memmove(str + prefix_len, remainder, remainder_len * sizeof(wchar_t)); + + len = remainder_len + prefix_len; + str[len] = L'\0'; + } + + return git_win32_path_trim_end(str, len); +} diff --git a/src/util/win32/path_w32.h b/src/util/win32/path_w32.h new file mode 100644 index 000000000..b241d5c8a --- /dev/null +++ b/src/util/win32/path_w32.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_path_w32_h__ +#define INCLUDE_win32_path_w32_h__ + +#include "git2_util.h" + +/** + * Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given + * path is relative, then it will be turned into an absolute path by having + * the current working directory prepended. + * + * @param dest The buffer to receive the wide string. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +extern int git_win32_path_from_utf8(git_win32_path dest, const char *src); + +/** + * Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given + * path is relative, then it will not be turned into an absolute path. + * + * @param dest The buffer to receive the wide string. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +extern int git_win32_path_relative_from_utf8(git_win32_path dest, const char *src); + +/** + * Canonicalize a Win32 UCS-2 path so that it is suitable for delivery to the + * Win32 APIs: remove multiple directory separators, squashing to a single one, + * strip trailing directory separators, ensure directory separators are all + * canonical (always backslashes, never forward slashes) and process any + * directory entries of '.' or '..'. + * + * Note that this is intended to be used on absolute Windows paths, those + * that start with `C:\`, `\\server\share`, `\\?\`, etc. + * + * This processes the buffer in place. + * + * @param path The buffer to process + * @return The new length of the buffer, in wchar_t's (not counting the NULL terminator) + */ +extern int git_win32_path_canonicalize(git_win32_path path); + +/** + * Create an internal format (posix-style) UTF-8 path from a Win32 UCS-2 path. + * + * @param dest The buffer to receive the UTF-8 string. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +extern int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src); + +/** + * Get the short name for the terminal path component in the given path. + * For example, given "C:\Foo\Bar\Asdf.txt", this will return the short name + * for the file "Asdf.txt". + * + * @param path The given path in UTF-8 + * @return The name of the shortname for the given path + */ +extern char *git_win32_path_8dot3_name(const char *path); + +extern int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path); + +/** + * Removes any trailing backslashes from a path, except in the case of a drive + * letter path (C:\, D:\, etc.). This function cannot fail. + * + * @param path The path which should be trimmed. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_trim_end(wchar_t *str, size_t len); + +/** + * Removes any of the following namespace prefixes from a path, + * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. + * + * @param path The path which should be converted. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_remove_namespace(wchar_t *str, size_t len); + +int git_win32_path_find_executable(git_win32_path fullpath, wchar_t* exe); + +#endif diff --git a/src/util/win32/posix.h b/src/util/win32/posix.h new file mode 100644 index 000000000..03fa2ac52 --- /dev/null +++ b/src/util/win32/posix.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_posix_h__ +#define INCLUDE_win32_posix_h__ + +#include "git2_util.h" +#include "../posix.h" +#include "win32-compat.h" +#include "path_w32.h" +#include "utf-conv.h" +#include "dir.h" + +extern unsigned long git_win32__createfile_sharemode; +extern int git_win32__retries; + +typedef SOCKET GIT_SOCKET; + +#define p_lseek(f,n,w) _lseeki64(f, n, w) + +extern int p_fstat(int fd, struct stat *buf); +extern int p_lstat(const char *file_name, struct stat *buf); +extern int p_stat(const char *path, struct stat *buf); + +extern int p_utimes(const char *filename, const struct p_timeval times[2]); +extern int p_futimes(int fd, const struct p_timeval times[2]); + +extern int p_readlink(const char *path, char *buf, size_t bufsiz); +extern int p_symlink(const char *old, const char *new); +extern int p_link(const char *old, const char *new); +extern int p_unlink(const char *path); +extern int p_mkdir(const char *path, mode_t mode); +extern int p_fsync(int fd); +extern char *p_realpath(const char *orig_path, char *buffer); + +extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags); +extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags); +extern int p_inet_pton(int af, const char *src, void* dst); + +extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr); +extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4); +extern int p_chdir(const char *path); +extern int p_chmod(const char *path, mode_t mode); +extern int p_rmdir(const char *path); +extern int p_access(const char *path, mode_t mode); +extern int p_ftruncate(int fd, off64_t size); + +/* p_lstat is almost but not quite POSIX correct. Specifically, the use of + * ENOTDIR is wrong, in that it does not mean precisely that a non-directory + * entry was encountered. Making it correct is potentially expensive, + * however, so this is a separate version of p_lstat to use when correct + * POSIX ENOTDIR semantics is required. + */ +extern int p_lstat_posixly(const char *filename, struct stat *buf); + +extern struct tm * p_localtime_r(const time_t *timer, struct tm *result); +extern struct tm * p_gmtime_r(const time_t *timer, struct tm *result); + +#endif diff --git a/src/util/win32/posix_w32.c b/src/util/win32/posix_w32.c new file mode 100644 index 000000000..5862e5c9a --- /dev/null +++ b/src/util/win32/posix_w32.c @@ -0,0 +1,1047 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2_util.h" + +#include "../posix.h" +#include "../futils.h" +#include "fs_path.h" +#include "path_w32.h" +#include "utf-conv.h" +#include "reparse.h" +#include +#include +#include +#include + +#ifndef FILE_NAME_NORMALIZED +# define FILE_NAME_NORMALIZED 0 +#endif + +#ifndef IO_REPARSE_TAG_SYMLINK +#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) +#endif + +#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE +# define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x02 +#endif + +#ifndef SYMBOLIC_LINK_FLAG_DIRECTORY +# define SYMBOLIC_LINK_FLAG_DIRECTORY 0x01 +#endif + +/* Allowable mode bits on Win32. Using mode bits that are not supported on + * Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it + * so we simply remove them. + */ +#define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE) + +unsigned long git_win32__createfile_sharemode = + FILE_SHARE_READ | FILE_SHARE_WRITE; +int git_win32__retries = 10; + +GIT_INLINE(void) set_errno(void) +{ + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + case ERROR_NO_MORE_FILES: + case ERROR_BAD_NETPATH: + case ERROR_BAD_NET_NAME: + case ERROR_BAD_PATHNAME: + case ERROR_FILENAME_EXCED_RANGE: + errno = ENOENT; + break; + case ERROR_BAD_ENVIRONMENT: + errno = E2BIG; + break; + case ERROR_BAD_FORMAT: + case ERROR_INVALID_STARTING_CODESEG: + case ERROR_INVALID_STACKSEG: + case ERROR_INVALID_MODULETYPE: + case ERROR_INVALID_EXE_SIGNATURE: + case ERROR_EXE_MARKED_INVALID: + case ERROR_BAD_EXE_FORMAT: + case ERROR_ITERATED_DATA_EXCEEDS_64k: + case ERROR_INVALID_MINALLOCSIZE: + case ERROR_DYNLINK_FROM_INVALID_RING: + case ERROR_IOPL_NOT_ENABLED: + case ERROR_INVALID_SEGDPL: + case ERROR_AUTODATASEG_EXCEEDS_64k: + case ERROR_RING2SEG_MUST_BE_MOVABLE: + case ERROR_RELOC_CHAIN_XEEDS_SEGLIM: + case ERROR_INFLOOP_IN_RELOC_CHAIN: + errno = ENOEXEC; + break; + case ERROR_INVALID_HANDLE: + case ERROR_INVALID_TARGET_HANDLE: + case ERROR_DIRECT_ACCESS_HANDLE: + errno = EBADF; + break; + case ERROR_WAIT_NO_CHILDREN: + case ERROR_CHILD_NOT_COMPLETE: + errno = ECHILD; + break; + case ERROR_NO_PROC_SLOTS: + case ERROR_MAX_THRDS_REACHED: + case ERROR_NESTING_NOT_ALLOWED: + errno = EAGAIN; + break; + case ERROR_ARENA_TRASHED: + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_INVALID_BLOCK: + case ERROR_NOT_ENOUGH_QUOTA: + errno = ENOMEM; + break; + case ERROR_ACCESS_DENIED: + case ERROR_CURRENT_DIRECTORY: + case ERROR_WRITE_PROTECT: + case ERROR_BAD_UNIT: + case ERROR_NOT_READY: + case ERROR_BAD_COMMAND: + case ERROR_CRC: + case ERROR_BAD_LENGTH: + case ERROR_SEEK: + case ERROR_NOT_DOS_DISK: + case ERROR_SECTOR_NOT_FOUND: + case ERROR_OUT_OF_PAPER: + case ERROR_WRITE_FAULT: + case ERROR_READ_FAULT: + case ERROR_GEN_FAILURE: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + case ERROR_WRONG_DISK: + case ERROR_SHARING_BUFFER_EXCEEDED: + case ERROR_NETWORK_ACCESS_DENIED: + case ERROR_CANNOT_MAKE: + case ERROR_FAIL_I24: + case ERROR_DRIVE_LOCKED: + case ERROR_SEEK_ON_DEVICE: + case ERROR_NOT_LOCKED: + case ERROR_LOCK_FAILED: + errno = EACCES; + break; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + errno = EEXIST; + break; + case ERROR_NOT_SAME_DEVICE: + errno = EXDEV; + break; + case ERROR_INVALID_FUNCTION: + case ERROR_INVALID_ACCESS: + case ERROR_INVALID_DATA: + case ERROR_INVALID_PARAMETER: + case ERROR_NEGATIVE_SEEK: + errno = EINVAL; + break; + case ERROR_TOO_MANY_OPEN_FILES: + errno = EMFILE; + break; + case ERROR_DISK_FULL: + errno = ENOSPC; + break; + case ERROR_BROKEN_PIPE: + errno = EPIPE; + break; + case ERROR_DIR_NOT_EMPTY: + errno = ENOTEMPTY; + break; + default: + errno = EINVAL; + } +} + +GIT_INLINE(bool) last_error_retryable(void) +{ + int os_error = GetLastError(); + + return (os_error == ERROR_SHARING_VIOLATION || + os_error == ERROR_ACCESS_DENIED); +} + +#define do_with_retries(fn, remediation) \ + do { \ + int __retry, __ret; \ + for (__retry = git_win32__retries; __retry; __retry--) { \ + if ((__ret = (fn)) != GIT_RETRY) \ + return __ret; \ + if (__retry > 1 && (__ret = (remediation)) != 0) { \ + if (__ret == GIT_RETRY) \ + continue; \ + return __ret; \ + } \ + Sleep(5); \ + } \ + return -1; \ + } while (0) \ + +static int ensure_writable(wchar_t *path) +{ + DWORD attrs; + + if ((attrs = GetFileAttributesW(path)) == INVALID_FILE_ATTRIBUTES) + goto on_error; + + if ((attrs & FILE_ATTRIBUTE_READONLY) == 0) + return 0; + + if (!SetFileAttributesW(path, (attrs & ~FILE_ATTRIBUTE_READONLY))) + goto on_error; + + return GIT_RETRY; + +on_error: + set_errno(); + return -1; +} + +/** + * Truncate or extend file. + * + * We now take a "git_off_t" rather than "long" because + * files may be longer than 2Gb. + */ +int p_ftruncate(int fd, off64_t size) +{ + if (size < 0) { + errno = EINVAL; + return -1; + } + +#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API) + return ((_chsize_s(fd, size) == 0) ? 0 : -1); +#else + /* TODO MINGW32 Find a replacement for _chsize() that handles big files. */ + if (size > INT32_MAX) { + errno = EFBIG; + return -1; + } + return _chsize(fd, (long)size); +#endif +} + +int p_mkdir(const char *path, mode_t mode) +{ + git_win32_path buf; + + GIT_UNUSED(mode); + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + return _wmkdir(buf); +} + +int p_link(const char *old, const char *new) +{ + GIT_UNUSED(old); + GIT_UNUSED(new); + errno = ENOSYS; + return -1; +} + +GIT_INLINE(int) unlink_once(const wchar_t *path) +{ + DWORD error; + + if (DeleteFileW(path)) + return 0; + + if ((error = GetLastError()) == ERROR_ACCESS_DENIED) { + WIN32_FILE_ATTRIBUTE_DATA fdata; + if (!GetFileAttributesExW(path, GetFileExInfoStandard, &fdata) || + !(fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) || + !(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + goto out; + + if (RemoveDirectoryW(path)) + return 0; + } + +out: + SetLastError(error); + + if (last_error_retryable()) + return GIT_RETRY; + + set_errno(); + return -1; +} + +int p_unlink(const char *path) +{ + git_win32_path wpath; + + if (git_win32_path_from_utf8(wpath, path) < 0) + return -1; + + do_with_retries(unlink_once(wpath), ensure_writable(wpath)); +} + +int p_fsync(int fd) +{ + HANDLE fh = (HANDLE)_get_osfhandle(fd); + + p_fsync__cnt++; + + if (fh == INVALID_HANDLE_VALUE) { + errno = EBADF; + return -1; + } + + if (!FlushFileBuffers(fh)) { + DWORD code = GetLastError(); + + if (code == ERROR_INVALID_HANDLE) + errno = EINVAL; + else + errno = EIO; + + return -1; + } + + return 0; +} + +#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\') + +static int lstat_w( + wchar_t *path, + struct stat *buf, + bool posix_enotdir) +{ + WIN32_FILE_ATTRIBUTE_DATA fdata; + + if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) { + if (!buf) + return 0; + + return git_win32__file_attribute_to_stat(buf, &fdata, path); + } + + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + errno = EACCES; + break; + default: + errno = ENOENT; + break; + } + + /* To match POSIX behavior, set ENOTDIR when any of the folders in the + * file path is a regular file, otherwise set ENOENT. + */ + if (errno == ENOENT && posix_enotdir) { + size_t path_len = wcslen(path); + + /* scan up path until we find an existing item */ + while (1) { + DWORD attrs; + + /* remove last directory component */ + for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--); + + if (path_len <= 0) + break; + + path[path_len] = L'\0'; + attrs = GetFileAttributesW(path); + + if (attrs != INVALID_FILE_ATTRIBUTES) { + if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) + errno = ENOTDIR; + break; + } + } + } + + return -1; +} + +static int do_lstat(const char *path, struct stat *buf, bool posixly_correct) +{ + git_win32_path path_w; + int len; + + if ((len = git_win32_path_from_utf8(path_w, path)) < 0) + return -1; + + git_win32_path_trim_end(path_w, len); + + return lstat_w(path_w, buf, posixly_correct); +} + +int p_lstat(const char *filename, struct stat *buf) +{ + return do_lstat(filename, buf, false); +} + +int p_lstat_posixly(const char *filename, struct stat *buf) +{ + return do_lstat(filename, buf, true); +} + +int p_readlink(const char *path, char *buf, size_t bufsiz) +{ + git_win32_path path_w, target_w; + git_win32_utf8_path target; + int len; + + /* readlink(2) does not NULL-terminate the string written + * to the target buffer. Furthermore, the target buffer need + * not be large enough to hold the entire result. A truncated + * result should be written in this case. Since this truncation + * could occur in the middle of the encoding of a code point, + * we need to buffer the result on the stack. */ + + if (git_win32_path_from_utf8(path_w, path) < 0 || + git_win32_path_readlink_w(target_w, path_w) < 0 || + (len = git_win32_path_to_utf8(target, target_w)) < 0) + return -1; + + bufsiz = min((size_t)len, bufsiz); + memcpy(buf, target, bufsiz); + + return (int)bufsiz; +} + +static bool target_is_dir(const char *target, const char *path) +{ + git_str resolved = GIT_STR_INIT; + git_win32_path resolved_w; + bool isdir = true; + + if (git_fs_path_is_absolute(target)) + git_win32_path_from_utf8(resolved_w, target); + else if (git_fs_path_dirname_r(&resolved, path) < 0 || + git_fs_path_apply_relative(&resolved, target) < 0 || + git_win32_path_from_utf8(resolved_w, resolved.ptr) < 0) + goto out; + + isdir = GetFileAttributesW(resolved_w) & FILE_ATTRIBUTE_DIRECTORY; + +out: + git_str_dispose(&resolved); + return isdir; +} + +int p_symlink(const char *target, const char *path) +{ + git_win32_path target_w, path_w; + DWORD dwFlags; + + /* + * Convert both target and path to Windows-style paths. Note that we do + * not want to use `git_win32_path_from_utf8` for converting the target, + * as that function will automatically pre-pend the current working + * directory in case the path is not absolute. As Git will instead use + * relative symlinks, this is not something we want. + */ + if (git_win32_path_from_utf8(path_w, path) < 0 || + git_win32_path_relative_from_utf8(target_w, target) < 0) + return -1; + + dwFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + if (target_is_dir(target, path)) + dwFlags |= SYMBOLIC_LINK_FLAG_DIRECTORY; + + if (!CreateSymbolicLinkW(path_w, target_w, dwFlags)) + return -1; + + return 0; +} + +struct open_opts { + DWORD access; + DWORD sharing; + SECURITY_ATTRIBUTES security; + DWORD creation_disposition; + DWORD attributes; + int osf_flags; +}; + +GIT_INLINE(void) open_opts_from_posix(struct open_opts *opts, int flags, mode_t mode) +{ + memset(opts, 0, sizeof(struct open_opts)); + + switch (flags & (O_WRONLY | O_RDWR)) { + case O_WRONLY: + opts->access = GENERIC_WRITE; + break; + case O_RDWR: + opts->access = GENERIC_READ | GENERIC_WRITE; + break; + default: + opts->access = GENERIC_READ; + break; + } + + opts->sharing = (DWORD)git_win32__createfile_sharemode; + + switch (flags & (O_CREAT | O_TRUNC | O_EXCL)) { + case O_CREAT | O_EXCL: + case O_CREAT | O_TRUNC | O_EXCL: + opts->creation_disposition = CREATE_NEW; + break; + case O_CREAT | O_TRUNC: + opts->creation_disposition = CREATE_ALWAYS; + break; + case O_TRUNC: + opts->creation_disposition = TRUNCATE_EXISTING; + break; + case O_CREAT: + opts->creation_disposition = OPEN_ALWAYS; + break; + default: + opts->creation_disposition = OPEN_EXISTING; + break; + } + + opts->attributes = ((flags & O_CREAT) && !(mode & S_IWRITE)) ? + FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL; + opts->osf_flags = flags & (O_RDONLY | O_APPEND); + + opts->security.nLength = sizeof(SECURITY_ATTRIBUTES); + opts->security.lpSecurityDescriptor = NULL; + opts->security.bInheritHandle = 0; +} + +GIT_INLINE(int) open_once( + const wchar_t *path, + struct open_opts *opts) +{ + int fd; + + HANDLE handle = CreateFileW(path, opts->access, opts->sharing, + &opts->security, opts->creation_disposition, opts->attributes, 0); + + if (handle == INVALID_HANDLE_VALUE) { + if (last_error_retryable()) + return GIT_RETRY; + + set_errno(); + return -1; + } + + if ((fd = _open_osfhandle((intptr_t)handle, opts->osf_flags)) < 0) + CloseHandle(handle); + + return fd; +} + +int p_open(const char *path, int flags, ...) +{ + git_win32_path wpath; + mode_t mode = 0; + struct open_opts opts = {0}; + + #ifdef GIT_DEBUG_STRICT_OPEN + if (strstr(path, "//") != NULL) { + errno = EACCES; + return -1; + } + #endif + + if (git_win32_path_from_utf8(wpath, path) < 0) + return -1; + + if (flags & O_CREAT) { + va_list arg_list; + + va_start(arg_list, flags); + mode = (mode_t)va_arg(arg_list, int); + va_end(arg_list); + } + + open_opts_from_posix(&opts, flags, mode); + + do_with_retries( + open_once(wpath, &opts), + 0); +} + +int p_creat(const char *path, mode_t mode) +{ + return p_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); +} + +int p_utimes(const char *path, const struct p_timeval times[2]) +{ + git_win32_path wpath; + int fd, error; + DWORD attrs_orig, attrs_new = 0; + struct open_opts opts = { 0 }; + + if (git_win32_path_from_utf8(wpath, path) < 0) + return -1; + + attrs_orig = GetFileAttributesW(wpath); + + if (attrs_orig & FILE_ATTRIBUTE_READONLY) { + attrs_new = attrs_orig & ~FILE_ATTRIBUTE_READONLY; + + if (!SetFileAttributesW(wpath, attrs_new)) { + git_error_set(GIT_ERROR_OS, "failed to set attributes"); + return -1; + } + } + + open_opts_from_posix(&opts, O_RDWR, 0); + + if ((fd = open_once(wpath, &opts)) < 0) { + error = -1; + goto done; + } + + error = p_futimes(fd, times); + close(fd); + +done: + if (attrs_orig != attrs_new) { + DWORD os_error = GetLastError(); + SetFileAttributesW(wpath, attrs_orig); + SetLastError(os_error); + } + + return error; +} + +int p_futimes(int fd, const struct p_timeval times[2]) +{ + HANDLE handle; + FILETIME atime = { 0 }, mtime = { 0 }; + + if (times == NULL) { + SYSTEMTIME st; + + GetSystemTime(&st); + SystemTimeToFileTime(&st, &atime); + SystemTimeToFileTime(&st, &mtime); + } + else { + git_win32__timeval_to_filetime(&atime, times[0]); + git_win32__timeval_to_filetime(&mtime, times[1]); + } + + if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE) + return -1; + + if (SetFileTime(handle, NULL, &atime, &mtime) == 0) + return -1; + + return 0; +} + +int p_getcwd(char *buffer_out, size_t size) +{ + git_win32_path buf; + wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16); + + if (!cwd) + return -1; + + git_win32_path_remove_namespace(cwd, wcslen(cwd)); + + /* Convert the working directory back to UTF-8 */ + if (git__utf16_to_8(buffer_out, size, cwd) < 0) { + DWORD code = GetLastError(); + + if (code == ERROR_INSUFFICIENT_BUFFER) + errno = ERANGE; + else + errno = EINVAL; + + return -1; + } + + git_fs_path_mkposix(buffer_out); + return 0; +} + +static int getfinalpath_w( + git_win32_path dest, + const wchar_t *path) +{ + HANDLE hFile; + DWORD dwChars; + + /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not + * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the + * target of the link. */ + hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (INVALID_HANDLE_VALUE == hFile) + return -1; + + /* Call GetFinalPathNameByHandle */ + dwChars = GetFinalPathNameByHandleW(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED); + CloseHandle(hFile); + + if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16) + return -1; + + /* The path may be delivered to us with a namespace prefix; remove */ + return (int)git_win32_path_remove_namespace(dest, dwChars); +} + +static int follow_and_lstat_link(git_win32_path path, struct stat *buf) +{ + git_win32_path target_w; + + if (getfinalpath_w(target_w, path) < 0) + return -1; + + return lstat_w(target_w, buf, false); +} + +int p_fstat(int fd, struct stat *buf) +{ + BY_HANDLE_FILE_INFORMATION fhInfo; + + HANDLE fh = (HANDLE)_get_osfhandle(fd); + + if (fh == INVALID_HANDLE_VALUE || + !GetFileInformationByHandle(fh, &fhInfo)) { + errno = EBADF; + return -1; + } + + git_win32__file_information_to_stat(buf, &fhInfo); + return 0; +} + +int p_stat(const char *path, struct stat *buf) +{ + git_win32_path path_w; + int len; + + if ((len = git_win32_path_from_utf8(path_w, path)) < 0 || + lstat_w(path_w, buf, false) < 0) + return -1; + + /* The item is a symbolic link or mount point. No need to iterate + * to follow multiple links; use GetFinalPathNameFromHandle. */ + if (S_ISLNK(buf->st_mode)) + return follow_and_lstat_link(path_w, buf); + + return 0; +} + +int p_chdir(const char *path) +{ + git_win32_path buf; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + return _wchdir(buf); +} + +int p_chmod(const char *path, mode_t mode) +{ + git_win32_path buf; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + return _wchmod(buf, mode); +} + +int p_rmdir(const char *path) +{ + git_win32_path buf; + int error; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + error = _wrmdir(buf); + + if (error == -1) { + switch (GetLastError()) { + /* _wrmdir() is documented to return EACCES if "A program has an open + * handle to the directory." This sounds like what everybody else calls + * EBUSY. Let's convert appropriate error codes. + */ + case ERROR_SHARING_VIOLATION: + errno = EBUSY; + break; + + /* This error can be returned when trying to rmdir an extant file. */ + case ERROR_DIRECTORY: + errno = ENOTDIR; + break; + } + } + + return error; +} + +char *p_realpath(const char *orig_path, char *buffer) +{ + git_win32_path orig_path_w, buffer_w; + + if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0) + return NULL; + + /* Note that if the path provided is a relative path, then the current directory + * is used to resolve the path -- which is a concurrency issue because the current + * directory is a process-wide variable. */ + if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + errno = ENAMETOOLONG; + else + errno = EINVAL; + + return NULL; + } + + /* The path must exist. */ + if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { + errno = ENOENT; + return NULL; + } + + if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) { + errno = ENOMEM; + return NULL; + } + + /* Convert the path to UTF-8. If the caller provided a buffer, then it + * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't, + * then we may overflow. */ + if (git_win32_path_to_utf8(buffer, buffer_w) < 0) + return NULL; + + git_fs_path_mkposix(buffer); + + return buffer; +} + +int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) +{ +#if defined(_MSC_VER) + int len; + + if (count == 0) + return _vscprintf(format, argptr); + + #if _MSC_VER >= 1500 + len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr); + #else + len = _vsnprintf(buffer, count, format, argptr); + #endif + + if (len < 0) + return _vscprintf(format, argptr); + + return len; +#else /* MinGW */ + return vsnprintf(buffer, count, format, argptr); +#endif +} + +int p_snprintf(char *buffer, size_t count, const char *format, ...) +{ + va_list va; + int r; + + va_start(va, format); + r = p_vsnprintf(buffer, count, format, va); + va_end(va); + + return r; +} + +int p_access(const char *path, mode_t mode) +{ + git_win32_path buf; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + return _waccess(buf, mode & WIN32_MODE_MASK); +} + +GIT_INLINE(int) rename_once(const wchar_t *from, const wchar_t *to) +{ + if (MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) + return 0; + + if (last_error_retryable()) + return GIT_RETRY; + + set_errno(); + return -1; +} + +int p_rename(const char *from, const char *to) +{ + git_win32_path wfrom, wto; + + if (git_win32_path_from_utf8(wfrom, from) < 0 || + git_win32_path_from_utf8(wto, to) < 0) + return -1; + + do_with_retries(rename_once(wfrom, wto), ensure_writable(wto)); +} + +int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags) +{ + if ((size_t)((int)length) != length) + return -1; /* git_error_set will be done by caller */ + + return recv(socket, buffer, (int)length, flags); +} + +int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags) +{ + if ((size_t)((int)length) != length) + return -1; /* git_error_set will be done by caller */ + + return send(socket, buffer, (int)length, flags); +} + +/** + * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html + * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that + */ +struct tm * +p_localtime_r (const time_t *timer, struct tm *result) +{ + struct tm *local_result; + local_result = localtime (timer); + + if (local_result == NULL || result == NULL) + return NULL; + + memcpy (result, local_result, sizeof (struct tm)); + return result; +} +struct tm * +p_gmtime_r (const time_t *timer, struct tm *result) +{ + struct tm *local_result; + local_result = gmtime (timer); + + if (local_result == NULL || result == NULL) + return NULL; + + memcpy (result, local_result, sizeof (struct tm)); + return result; +} + +int p_inet_pton(int af, const char *src, void *dst) +{ + struct sockaddr_storage sin; + void *addr; + int sin_len = sizeof(struct sockaddr_storage), addr_len; + int error = 0; + + if (af == AF_INET) { + addr = &((struct sockaddr_in *)&sin)->sin_addr; + addr_len = sizeof(struct in_addr); + } else if (af == AF_INET6) { + addr = &((struct sockaddr_in6 *)&sin)->sin6_addr; + addr_len = sizeof(struct in6_addr); + } else { + errno = EAFNOSUPPORT; + return -1; + } + + if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) { + memcpy(dst, addr, addr_len); + return 1; + } + + switch(WSAGetLastError()) { + case WSAEINVAL: + return 0; + case WSAEFAULT: + errno = ENOSPC; + return -1; + case WSA_NOT_ENOUGH_MEMORY: + errno = ENOMEM; + return -1; + } + + errno = EINVAL; + return -1; +} + +ssize_t p_pread(int fd, void *data, size_t size, off64_t offset) +{ + HANDLE fh; + DWORD rsize = 0; + OVERLAPPED ov = {0}; + LARGE_INTEGER pos = {0}; + off64_t final_offset = 0; + + /* Fail if the final offset would have overflowed to match POSIX semantics. */ + if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) { + errno = EINVAL; + return -1; + } + + /* + * Truncate large writes to the maximum allowable size: the caller + * needs to always call this in a loop anyways. + */ + if (size > INT32_MAX) { + size = INT32_MAX; + } + + pos.QuadPart = offset; + ov.Offset = pos.LowPart; + ov.OffsetHigh = pos.HighPart; + fh = (HANDLE)_get_osfhandle(fd); + + if (ReadFile(fh, data, (DWORD)size, &rsize, &ov)) { + return (ssize_t)rsize; + } + + set_errno(); + return -1; +} + +ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset) +{ + HANDLE fh; + DWORD wsize = 0; + OVERLAPPED ov = {0}; + LARGE_INTEGER pos = {0}; + off64_t final_offset = 0; + + /* Fail if the final offset would have overflowed to match POSIX semantics. */ + if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) { + errno = EINVAL; + return -1; + } + + /* + * Truncate large writes to the maximum allowable size: the caller + * needs to always call this in a loop anyways. + */ + if (size > INT32_MAX) { + size = INT32_MAX; + } + + pos.QuadPart = offset; + ov.Offset = pos.LowPart; + ov.OffsetHigh = pos.HighPart; + fh = (HANDLE)_get_osfhandle(fd); + + if (WriteFile(fh, data, (DWORD)size, &wsize, &ov)) { + return (ssize_t)wsize; + } + + set_errno(); + return -1; +} diff --git a/src/util/win32/precompiled.c b/src/util/win32/precompiled.c new file mode 100644 index 000000000..5f656a45d --- /dev/null +++ b/src/util/win32/precompiled.c @@ -0,0 +1 @@ +#include "precompiled.h" diff --git a/src/util/win32/precompiled.h b/src/util/win32/precompiled.h new file mode 100644 index 000000000..1163c3d63 --- /dev/null +++ b/src/util/win32/precompiled.h @@ -0,0 +1,21 @@ +#include "git2_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#ifdef GIT_THREADS + #include "win32/thread.h" +#endif + +#include "git2.h" diff --git a/src/util/win32/reparse.h b/src/util/win32/reparse.h new file mode 100644 index 000000000..23312319f --- /dev/null +++ b/src/util/win32/reparse.h @@ -0,0 +1,57 @@ +/* +* Copyright (C) the libgit2 contributors. All rights reserved. +* +* This file is part of libgit2, distributed under the GNU GPL v2 with +* a Linking Exception. For full terms see the included COPYING file. +*/ + +#ifndef INCLUDE_win32_reparse_h__ +#define INCLUDE_win32_reparse_h__ + +/* This structure is defined on MSDN at +* http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx +* +* It was formerly included in the Windows 2000 SDK and remains defined in +* MinGW, so we must define it with a silly name to avoid conflicting. +*/ +typedef struct _GIT_REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLink; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPoint; + struct { + UCHAR DataBuffer[1]; + } Generic; + } ReparseBuffer; +} GIT_REPARSE_DATA_BUFFER; + +#define REPARSE_DATA_HEADER_SIZE 8 +#define REPARSE_DATA_MOUNTPOINT_HEADER_SIZE 8 +#define REPARSE_DATA_UNION_SIZE 12 + +/* Missing in MinGW */ +#ifndef FSCTL_GET_REPARSE_POINT +# define FSCTL_GET_REPARSE_POINT 0x000900a8 +#endif + +/* Missing in MinGW */ +#ifndef FSCTL_SET_REPARSE_POINT +# define FSCTL_SET_REPARSE_POINT 0x000900a4 +#endif + +#endif diff --git a/src/util/win32/thread.c b/src/util/win32/thread.c new file mode 100644 index 000000000..f5cacd320 --- /dev/null +++ b/src/util/win32/thread.c @@ -0,0 +1,262 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "thread.h" +#include "runtime.h" + +#define CLEAN_THREAD_EXIT 0x6F012842 + +typedef void (WINAPI *win32_srwlock_fn)(GIT_SRWLOCK *); + +static win32_srwlock_fn win32_srwlock_initialize; +static win32_srwlock_fn win32_srwlock_acquire_shared; +static win32_srwlock_fn win32_srwlock_release_shared; +static win32_srwlock_fn win32_srwlock_acquire_exclusive; +static win32_srwlock_fn win32_srwlock_release_exclusive; + +static DWORD fls_index; + +/* The thread procedure stub used to invoke the caller's procedure + * and capture the return value for later collection. Windows will + * only hold a DWORD, but we need to be able to store an entire + * void pointer. This requires the indirection. */ +static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter) +{ + git_thread *thread = lpParameter; + + /* Set the current thread for `git_thread_exit` */ + FlsSetValue(fls_index, thread); + + thread->result = thread->proc(thread->param); + + return CLEAN_THREAD_EXIT; +} + +static void git_threads_global_shutdown(void) +{ + FlsFree(fls_index); +} + +int git_threads_global_init(void) +{ + HMODULE hModule = GetModuleHandleW(L"kernel32"); + + if (hModule) { + win32_srwlock_initialize = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "InitializeSRWLock"); + win32_srwlock_acquire_shared = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "AcquireSRWLockShared"); + win32_srwlock_release_shared = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "ReleaseSRWLockShared"); + win32_srwlock_acquire_exclusive = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "AcquireSRWLockExclusive"); + win32_srwlock_release_exclusive = (win32_srwlock_fn)(void *) + GetProcAddress(hModule, "ReleaseSRWLockExclusive"); + } + + if ((fls_index = FlsAlloc(NULL)) == FLS_OUT_OF_INDEXES) + return -1; + + return git_runtime_shutdown_register(git_threads_global_shutdown); +} + +int git_thread_create( + git_thread *GIT_RESTRICT thread, + void *(*start_routine)(void*), + void *GIT_RESTRICT arg) +{ + thread->result = NULL; + thread->param = arg; + thread->proc = start_routine; + thread->thread = CreateThread( + NULL, 0, git_win32__threadproc, thread, 0, NULL); + + return thread->thread ? 0 : -1; +} + +int git_thread_join( + git_thread *thread, + void **value_ptr) +{ + DWORD exit; + + if (WaitForSingleObject(thread->thread, INFINITE) != WAIT_OBJECT_0) + return -1; + + if (!GetExitCodeThread(thread->thread, &exit)) { + CloseHandle(thread->thread); + return -1; + } + + /* Check for the thread having exited uncleanly. If exit was unclean, + * then we don't have a return value to give back to the caller. */ + GIT_ASSERT(exit == CLEAN_THREAD_EXIT); + + if (value_ptr) + *value_ptr = thread->result; + + CloseHandle(thread->thread); + return 0; +} + +void git_thread_exit(void *value) +{ + git_thread *thread = FlsGetValue(fls_index); + + if (thread) + thread->result = value; + + ExitThread(CLEAN_THREAD_EXIT); +} + +size_t git_thread_currentid(void) +{ + return GetCurrentThreadId(); +} + +int git_mutex_init(git_mutex *GIT_RESTRICT mutex) +{ + InitializeCriticalSection(mutex); + return 0; +} + +int git_mutex_free(git_mutex *mutex) +{ + DeleteCriticalSection(mutex); + return 0; +} + +int git_mutex_lock(git_mutex *mutex) +{ + EnterCriticalSection(mutex); + return 0; +} + +int git_mutex_unlock(git_mutex *mutex) +{ + LeaveCriticalSection(mutex); + return 0; +} + +int git_cond_init(git_cond *cond) +{ + /* This is an auto-reset event. */ + *cond = CreateEventW(NULL, FALSE, FALSE, NULL); + GIT_ASSERT(*cond); + + /* If we can't create the event, claim that the reason was out-of-memory. + * The actual reason can be fetched with GetLastError(). */ + return *cond ? 0 : ENOMEM; +} + +int git_cond_free(git_cond *cond) +{ + BOOL closed; + + if (!cond) + return EINVAL; + + closed = CloseHandle(*cond); + GIT_ASSERT(closed); + GIT_UNUSED(closed); + + *cond = NULL; + return 0; +} + +int git_cond_wait(git_cond *cond, git_mutex *mutex) +{ + int error; + DWORD wait_result; + + if (!cond || !mutex) + return EINVAL; + + /* The caller must be holding the mutex. */ + error = git_mutex_unlock(mutex); + + if (error) + return error; + + wait_result = WaitForSingleObject(*cond, INFINITE); + GIT_ASSERT(WAIT_OBJECT_0 == wait_result); + GIT_UNUSED(wait_result); + + return git_mutex_lock(mutex); +} + +int git_cond_signal(git_cond *cond) +{ + BOOL signaled; + + if (!cond) + return EINVAL; + + signaled = SetEvent(*cond); + GIT_ASSERT(signaled); + GIT_UNUSED(signaled); + + return 0; +} + +int git_rwlock_init(git_rwlock *GIT_RESTRICT lock) +{ + if (win32_srwlock_initialize) + win32_srwlock_initialize(&lock->native.srwl); + else + InitializeCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_rdlock(git_rwlock *lock) +{ + if (win32_srwlock_acquire_shared) + win32_srwlock_acquire_shared(&lock->native.srwl); + else + EnterCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_rdunlock(git_rwlock *lock) +{ + if (win32_srwlock_release_shared) + win32_srwlock_release_shared(&lock->native.srwl); + else + LeaveCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_wrlock(git_rwlock *lock) +{ + if (win32_srwlock_acquire_exclusive) + win32_srwlock_acquire_exclusive(&lock->native.srwl); + else + EnterCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_wrunlock(git_rwlock *lock) +{ + if (win32_srwlock_release_exclusive) + win32_srwlock_release_exclusive(&lock->native.srwl); + else + LeaveCriticalSection(&lock->native.csec); + + return 0; +} + +int git_rwlock_free(git_rwlock *lock) +{ + if (!win32_srwlock_initialize) + DeleteCriticalSection(&lock->native.csec); + git__memzero(lock, sizeof(*lock)); + return 0; +} diff --git a/src/util/win32/thread.h b/src/util/win32/thread.h new file mode 100644 index 000000000..184762e2a --- /dev/null +++ b/src/util/win32/thread.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_win32_thread_h__ +#define INCLUDE_win32_thread_h__ + +#include "git2_util.h" + +#if defined (_MSC_VER) +# define GIT_RESTRICT __restrict +#else +# define GIT_RESTRICT __restrict__ +#endif + +typedef struct { + HANDLE thread; + void *(*proc)(void *); + void *param; + void *result; +} git_thread; + +typedef CRITICAL_SECTION git_mutex; +typedef HANDLE git_cond; + +typedef struct { void *Ptr; } GIT_SRWLOCK; + +typedef struct { + union { + GIT_SRWLOCK srwl; + CRITICAL_SECTION csec; + } native; +} git_rwlock; + +int git_threads_global_init(void); + +int git_thread_create(git_thread *GIT_RESTRICT, + void *(*) (void *), + void *GIT_RESTRICT); +int git_thread_join(git_thread *, void **); +size_t git_thread_currentid(void); +void git_thread_exit(void *); + +int git_mutex_init(git_mutex *GIT_RESTRICT mutex); +int git_mutex_free(git_mutex *); +int git_mutex_lock(git_mutex *); +int git_mutex_unlock(git_mutex *); + +int git_cond_init(git_cond *); +int git_cond_free(git_cond *); +int git_cond_wait(git_cond *, git_mutex *); +int git_cond_signal(git_cond *); + +int git_rwlock_init(git_rwlock *GIT_RESTRICT lock); +int git_rwlock_rdlock(git_rwlock *); +int git_rwlock_rdunlock(git_rwlock *); +int git_rwlock_wrlock(git_rwlock *); +int git_rwlock_wrunlock(git_rwlock *); +int git_rwlock_free(git_rwlock *); + +#endif diff --git a/src/util/win32/utf-conv.c b/src/util/win32/utf-conv.c new file mode 100644 index 000000000..4bde3023a --- /dev/null +++ b/src/util/win32/utf-conv.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "utf-conv.h" + +GIT_INLINE(void) git__set_errno(void) +{ + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + errno = ENAMETOOLONG; + else + errno = EINVAL; +} + +/** + * Converts a UTF-8 string to wide characters. + * + * @param dest The buffer to receive the wide string. + * @param dest_size The size of the buffer, in characters. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src) +{ + int len; + + /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to + * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's + * length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */ + if ((len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1) < 0) + git__set_errno(); + + return len; +} + +/** + * Converts a wide string to UTF-8. + * + * @param dest The buffer to receive the UTF-8 string. + * @param dest_size The size of the buffer, in bytes. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src) +{ + int len; + + /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to + * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's + * length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */ + if ((len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0) + git__set_errno(); + + return len; +} + +/** + * Converts a UTF-8 string to wide characters. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to the wide string. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +int git__utf8_to_16_alloc(wchar_t **dest, const char *src) +{ + int utf16_size; + + *dest = NULL; + + /* Length of -1 indicates NULL termination of the input string */ + utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0); + + if (!utf16_size) { + git__set_errno(); + return -1; + } + + if (!(*dest = git__mallocarray(utf16_size, sizeof(wchar_t)))) { + errno = ENOMEM; + return -1; + } + + utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size); + + if (!utf16_size) { + git__set_errno(); + + git__free(*dest); + *dest = NULL; + } + + /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL + * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, + * so underflow is not possible */ + return utf16_size - 1; +} + +/** + * Converts a wide string to UTF-8. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to the UTF-8 string. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +int git__utf16_to_8_alloc(char **dest, const wchar_t *src) +{ + int utf8_size; + + *dest = NULL; + + /* Length of -1 indicates NULL termination of the input string */ + utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL); + + if (!utf8_size) { + git__set_errno(); + return -1; + } + + *dest = git__malloc(utf8_size); + + if (!*dest) { + errno = ENOMEM; + return -1; + } + + utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, *dest, utf8_size, NULL, NULL); + + if (!utf8_size) { + git__set_errno(); + + git__free(*dest); + *dest = NULL; + } + + /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL + * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, + * so underflow is not possible */ + return utf8_size - 1; +} diff --git a/src/util/win32/utf-conv.h b/src/util/win32/utf-conv.h new file mode 100644 index 000000000..120d647ef --- /dev/null +++ b/src/util/win32/utf-conv.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_utf_conv_h__ +#define INCLUDE_win32_utf_conv_h__ + +#include "git2_util.h" + +#include + +#ifndef WC_ERR_INVALID_CHARS +# define WC_ERR_INVALID_CHARS 0x80 +#endif + +/** + * Converts a UTF-8 string to wide characters. + * + * @param dest The buffer to receive the wide string. + * @param dest_size The size of the buffer, in characters. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src); + +/** + * Converts a wide string to UTF-8. + * + * @param dest The buffer to receive the UTF-8 string. + * @param dest_size The size of the buffer, in bytes. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src); + +/** + * Converts a UTF-8 string to wide characters. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to the wide string. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +int git__utf8_to_16_alloc(wchar_t **dest, const char *src); + +/** + * Converts a wide string to UTF-8. + * Memory is allocated to hold the converted string. + * The caller is responsible for freeing the string with git__free. + * + * @param dest Receives a pointer to the UTF-8 string. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + */ +int git__utf16_to_8_alloc(char **dest, const wchar_t *src); + +#endif diff --git a/src/util/win32/version.h b/src/util/win32/version.h new file mode 100644 index 000000000..79667697f --- /dev/null +++ b/src/util/win32/version.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_version_h__ +#define INCLUDE_win32_version_h__ + +#include + +GIT_INLINE(int) git_has_win32_version(int major, int minor, int service_pack) +{ + OSVERSIONINFOEX version_test = {0}; + DWORD version_test_mask; + DWORDLONG version_condition_mask = 0; + + version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + version_test.dwMajorVersion = major; + version_test.dwMinorVersion = minor; + version_test.wServicePackMajor = (WORD)service_pack; + version_test.wServicePackMinor = 0; + + version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR); + + VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); + + if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask)) + return 0; + + return 1; +} + +#endif diff --git a/src/util/win32/w32_buffer.c b/src/util/win32/w32_buffer.c new file mode 100644 index 000000000..6fee8203c --- /dev/null +++ b/src/util/win32/w32_buffer.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "w32_buffer.h" + +#include "utf-conv.h" + +GIT_INLINE(int) handle_wc_error(void) +{ + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + errno = ENAMETOOLONG; + else + errno = EINVAL; + + return -1; +} + +int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w) +{ + int utf8_len, utf8_write_len; + size_t new_size; + + if (!len_w) { + return 0; + } else if (len_w > INT_MAX) { + git_error_set_oom(); + return -1; + } + + GIT_ASSERT(string_w); + + /* Measure the string necessary for conversion */ + if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, NULL, 0, NULL, NULL)) == 0) + return 0; + + GIT_ASSERT(utf8_len > 0); + + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, buf->size, (size_t)utf8_len); + GIT_ERROR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + + if (git_str_grow(buf, new_size) < 0) + return -1; + + if ((utf8_write_len = WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, &buf->ptr[buf->size], utf8_len, NULL, NULL)) == 0) + return handle_wc_error(); + + GIT_ASSERT(utf8_write_len == utf8_len); + + buf->size += utf8_write_len; + buf->ptr[buf->size] = '\0'; + return 0; +} diff --git a/src/util/win32/w32_buffer.h b/src/util/win32/w32_buffer.h new file mode 100644 index 000000000..68ea96035 --- /dev/null +++ b/src/util/win32/w32_buffer.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_w32_buffer_h__ +#define INCLUDE_win32_w32_buffer_h__ + +#include "git2_util.h" +#include "str.h" + +/** + * Convert a wide character string to UTF-8 and append the results to the + * buffer. + */ +int git_str_put_w(git_str *buf, const wchar_t *string_w, size_t len_w); + +#endif diff --git a/src/util/win32/w32_common.h b/src/util/win32/w32_common.h new file mode 100644 index 000000000..c20b3e85e --- /dev/null +++ b/src/util/win32/w32_common.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_win32_w32_common_h__ +#define INCLUDE_win32_w32_common_h__ + +#include + +/* + * 4096 is the max allowed Git path. `MAX_PATH` (260) is the typical max allowed + * Windows path length, however win32 Unicode APIs generally allow up to 32,767 + * if prefixed with "\\?\" (i.e. converted to an NT-style name). + */ +#define GIT_WIN_PATH_MAX GIT_PATH_MAX + +/* + * Provides a large enough buffer to support Windows Git paths: + * GIT_WIN_PATH_MAX is 4096, corresponding to a maximum path length of 4095 + * characters plus a NULL terminator. Prefixing with "\\?\" adds 4 characters, + * but if the original was a UNC path, then we turn "\\server\share" into + * "\\?\UNC\server\share". So we replace the first two characters with + * 8 characters, a net gain of 6, so the maximum length is GIT_WIN_PATH_MAX+6. + */ +#define GIT_WIN_PATH_UTF16 GIT_WIN_PATH_MAX+6 + +/* Maximum size of a UTF-8 Win32 Git path. We remove the "\\?\" or "\\?\UNC\" + * prefixes for presentation, bringing us back to 4095 (non-NULL) + * characters. UTF-8 does have 4-byte sequences, but they are encoded in + * UTF-16 using surrogate pairs, which takes up the space of two characters. + * Two characters in the range U+0800 -> U+FFFF take up more space in UTF-8 + * (6 bytes) than one surrogate pair (4 bytes). + */ +#define GIT_WIN_PATH_UTF8 ((GIT_WIN_PATH_MAX - 1) * 3 + 1) + +/* + * The length of a Windows "shortname", for 8.3 compatibility. + */ +#define GIT_WIN_PATH_SHORTNAME 13 + +/* Win32 path types */ +typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16]; +typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8]; + +#endif diff --git a/src/util/win32/w32_leakcheck.c b/src/util/win32/w32_leakcheck.c new file mode 100644 index 000000000..0f095de12 --- /dev/null +++ b/src/util/win32/w32_leakcheck.c @@ -0,0 +1,581 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "w32_leakcheck.h" + +#if defined(GIT_WIN32_LEAKCHECK) + +#include "Windows.h" +#include "Dbghelp.h" +#include "win32/posix.h" +#include "hash.h" +#include "runtime.h" + +/* Stack frames (for stack tracing, below) */ + +static bool g_win32_stack_initialized = false; +static HANDLE g_win32_stack_process = INVALID_HANDLE_VALUE; +static git_win32_leakcheck_stack_aux_cb_alloc g_aux_cb_alloc = NULL; +static git_win32_leakcheck_stack_aux_cb_lookup g_aux_cb_lookup = NULL; + +int git_win32_leakcheck_stack_set_aux_cb( + git_win32_leakcheck_stack_aux_cb_alloc cb_alloc, + git_win32_leakcheck_stack_aux_cb_lookup cb_lookup) +{ + g_aux_cb_alloc = cb_alloc; + g_aux_cb_lookup = cb_lookup; + + return 0; +} + +/** + * Load symbol table data. This should be done in the primary + * thread at startup (under a lock if there are other threads + * active). + */ +void git_win32_leakcheck_stack_init(void) +{ + if (!g_win32_stack_initialized) { + g_win32_stack_process = GetCurrentProcess(); + SymSetOptions(SYMOPT_LOAD_LINES); + SymInitialize(g_win32_stack_process, NULL, TRUE); + g_win32_stack_initialized = true; + } +} + +/** + * Cleanup symbol table data. This should be done in the + * primary thead at shutdown (under a lock if there are other + * threads active). + */ +void git_win32_leakcheck_stack_cleanup(void) +{ + if (g_win32_stack_initialized) { + SymCleanup(g_win32_stack_process); + g_win32_stack_process = INVALID_HANDLE_VALUE; + g_win32_stack_initialized = false; + } +} + +int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip) +{ + if (!g_win32_stack_initialized) { + git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); + return GIT_ERROR; + } + + memset(pdata, 0, sizeof(*pdata)); + pdata->nr_frames = RtlCaptureStackBackTrace( + skip+1, GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES, pdata->frames, NULL); + + /* If an "aux" data provider was registered, ask it to capture + * whatever data it needs and give us an "aux_id" to it so that + * we can refer to it later when reporting. + */ + if (g_aux_cb_alloc) + (g_aux_cb_alloc)(&pdata->aux_id); + + return 0; +} + +int git_win32_leakcheck_stack_compare( + git_win32_leakcheck_stack_raw_data *d1, + git_win32_leakcheck_stack_raw_data *d2) +{ + return memcmp(d1, d2, sizeof(*d1)); +} + +int git_win32_leakcheck_stack_format( + char *pbuf, size_t buf_len, + const git_win32_leakcheck_stack_raw_data *pdata, + const char *prefix, const char *suffix) +{ +#define MY_MAX_FILENAME 255 + + /* SYMBOL_INFO has char FileName[1] at the end. The docs say to + * to malloc it with extra space for your desired max filename. + */ + struct { + SYMBOL_INFO symbol; + char extra[MY_MAX_FILENAME + 1]; + } s; + + IMAGEHLP_LINE64 line; + size_t buf_used = 0; + unsigned int k; + char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */ + size_t detail_len; + + if (!g_win32_stack_initialized) { + git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); + return GIT_ERROR; + } + + if (!prefix) + prefix = "\t"; + if (!suffix) + suffix = "\n"; + + memset(pbuf, 0, buf_len); + + memset(&s, 0, sizeof(s)); + s.symbol.MaxNameLen = MY_MAX_FILENAME; + s.symbol.SizeOfStruct = sizeof(SYMBOL_INFO); + + memset(&line, 0, sizeof(line)); + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + for (k=0; k < pdata->nr_frames; k++) { + DWORD64 frame_k = (DWORD64)pdata->frames[k]; + DWORD dwUnused; + + if (SymFromAddr(g_win32_stack_process, frame_k, 0, &s.symbol) && + SymGetLineFromAddr64(g_win32_stack_process, frame_k, &dwUnused, &line)) { + const char *pslash; + const char *pfile; + + pslash = strrchr(line.FileName, '\\'); + pfile = ((pslash) ? (pslash+1) : line.FileName); + p_snprintf(detail, sizeof(detail), "%s%s:%d> %s%s", + prefix, pfile, line.LineNumber, s.symbol.Name, suffix); + } else { + /* This happens when we cross into another module. + * For example, in CLAR tests, this is typically + * the CRT startup code. Just print an unknown + * frame and continue. + */ + p_snprintf(detail, sizeof(detail), "%s??%s", prefix, suffix); + } + detail_len = strlen(detail); + + if (buf_len < (buf_used + detail_len + 1)) { + /* we don't have room for this frame in the buffer, so just stop. */ + break; + } + + memcpy(&pbuf[buf_used], detail, detail_len); + buf_used += detail_len; + } + + /* "aux_id" 0 is reserved to mean no aux data. This is needed to handle + * allocs that occur before the aux callbacks were registered. + */ + if (pdata->aux_id > 0) { + p_snprintf(detail, sizeof(detail), "%saux_id: %d%s", + prefix, pdata->aux_id, suffix); + detail_len = strlen(detail); + if ((buf_used + detail_len + 1) < buf_len) { + memcpy(&pbuf[buf_used], detail, detail_len); + buf_used += detail_len; + } + + /* If an "aux" data provider is still registered, ask it to append its detailed + * data to the end of ours using the "aux_id" it gave us when this de-duped + * item was created. + */ + if (g_aux_cb_lookup) + (g_aux_cb_lookup)(pdata->aux_id, &pbuf[buf_used], (buf_len - buf_used - 1)); + } + + return GIT_OK; +} + +int git_win32_leakcheck_stack( + char * pbuf, size_t buf_len, + int skip, + const char *prefix, const char *suffix) +{ + git_win32_leakcheck_stack_raw_data data; + int error; + + if ((error = git_win32_leakcheck_stack_capture(&data, skip)) < 0) + return error; + if ((error = git_win32_leakcheck_stack_format(pbuf, buf_len, &data, prefix, suffix)) < 0) + return error; + return 0; +} + +/* Stack tracing */ + +#define STACKTRACE_UID_LEN (15) + +/** + * The stacktrace of an allocation can be distilled + * to a unique id based upon the stackframe pointers + * and ignoring any size arguments. We will use these + * UIDs as the (char const*) __FILE__ argument we + * give to the CRT malloc routines. + */ +typedef struct { + char uid[STACKTRACE_UID_LEN + 1]; +} git_win32_leakcheck_stacktrace_uid; + +/** + * All mallocs with the same stacktrace will be de-duped + * and aggregated into this row. + */ +typedef struct { + git_win32_leakcheck_stacktrace_uid uid; /* must be first */ + git_win32_leakcheck_stack_raw_data raw_data; + unsigned int count_allocs; /* times this alloc signature seen since init */ + unsigned int count_allocs_at_last_checkpoint; /* times since last mark */ + unsigned int transient_count_leaks; /* sum of leaks */ +} git_win32_leakcheck_stacktrace_row; + +static CRITICAL_SECTION g_crtdbg_stacktrace_cs; + +/** + * CRTDBG memory leak tracking takes a "char const * const file_name" + * and stores the pointer in the heap data (instead of allocing a copy + * for itself). Normally, this is not a problem, since we usually pass + * in __FILE__. But I'm going to lie to it and pass in the address of + * the UID in place of the file_name. Also, I do not want to alloc the + * stacktrace data (because we are called from inside our alloc routines). + * Therefore, I'm creating a very large static pool array to store row + * data. This also eliminates the temptation to realloc it (and move the + * UID pointers). + * + * And to efficiently look for duplicates we need an index on the rows + * so we can bsearch it. Again, without mallocing. + * + * If we observe more than MY_ROW_LIMIT unique malloc signatures, we + * fall through and use the traditional __FILE__ processing and don't + * try to de-dup them. If your testing hits this limit, just increase + * it and try again. + */ + +#define MY_ROW_LIMIT (2 * 1024 * 1024) +static git_win32_leakcheck_stacktrace_row g_cs_rows[MY_ROW_LIMIT]; +static git_win32_leakcheck_stacktrace_row *g_cs_index[MY_ROW_LIMIT]; + +static unsigned int g_cs_end = MY_ROW_LIMIT; +static unsigned int g_cs_ins = 0; /* insertion point == unique allocs seen */ +static unsigned int g_count_total_allocs = 0; /* number of allocs seen */ +static unsigned int g_transient_count_total_leaks = 0; /* number of total leaks */ +static unsigned int g_transient_count_dedup_leaks = 0; /* number of unique leaks */ +static bool g_limit_reached = false; /* had allocs after we filled row table */ + +static unsigned int g_checkpoint_id = 0; /* to better label leak checkpoints */ +static bool g_transient_leaks_since_mark = false; /* payload for hook */ + +/** + * Compare function for bsearch on g_cs_index table. + */ +static int row_cmp(const void *v1, const void *v2) +{ + git_win32_leakcheck_stack_raw_data *d1 = (git_win32_leakcheck_stack_raw_data*)v1; + git_win32_leakcheck_stacktrace_row *r2 = (git_win32_leakcheck_stacktrace_row *)v2; + + return (git_win32_leakcheck_stack_compare(d1, &r2->raw_data)); +} + +/** + * Unique insert the new data into the row and index tables. + * We have to sort by the stackframe data itself, not the uid. + */ +static git_win32_leakcheck_stacktrace_row * insert_unique( + const git_win32_leakcheck_stack_raw_data *pdata) +{ + size_t pos; + if (git__bsearch(g_cs_index, g_cs_ins, pdata, row_cmp, &pos) < 0) { + /* Append new unique item to row table. */ + memcpy(&g_cs_rows[g_cs_ins].raw_data, pdata, sizeof(*pdata)); + sprintf(g_cs_rows[g_cs_ins].uid.uid, "##%08lx", g_cs_ins); + + /* Insert pointer to it into the proper place in the index table. */ + if (pos < g_cs_ins) + memmove(&g_cs_index[pos+1], &g_cs_index[pos], (g_cs_ins - pos)*sizeof(g_cs_index[0])); + g_cs_index[pos] = &g_cs_rows[g_cs_ins]; + + g_cs_ins++; + } + + g_cs_index[pos]->count_allocs++; + + return g_cs_index[pos]; +} + +/** + * Hook function to receive leak data from the CRT. (This includes + * both ":()" data, but also each of the + * various headers and fields. + * + * Scan this for the special "##" UID forms that we substituted + * for the "". Map back to the row data and + * increment its leak count. + * + * See https://msdn.microsoft.com/en-us/library/74kabxyx.aspx + * + * We suppress the actual crtdbg output. + */ +static int __cdecl report_hook(int nRptType, char *szMsg, int *retVal) +{ + static int hook_result = TRUE; /* FALSE to get stock dump; TRUE to suppress. */ + unsigned int pos; + + *retVal = 0; /* do not invoke debugger */ + + if ((szMsg[0] != '#') || (szMsg[1] != '#')) + return hook_result; + + if (sscanf(&szMsg[2], "%08lx", &pos) < 1) + return hook_result; + if (pos >= g_cs_ins) + return hook_result; + + if (g_transient_leaks_since_mark) { + if (g_cs_rows[pos].count_allocs == g_cs_rows[pos].count_allocs_at_last_checkpoint) + return hook_result; + } + + g_cs_rows[pos].transient_count_leaks++; + + if (g_cs_rows[pos].transient_count_leaks == 1) + g_transient_count_dedup_leaks++; + + g_transient_count_total_leaks++; + + return hook_result; +} + +/** + * Write leak data to all of the various places we need. + * We force the caller to sprintf() the message first + * because we want to avoid fprintf() because it allocs. + */ +static void my_output(const char *buf) +{ + fwrite(buf, strlen(buf), 1, stderr); + OutputDebugString(buf); +} + +/** + * For each row with leaks, dump a stacktrace for it. + */ +static void dump_summary(const char *label) +{ + unsigned int k; + char buf[10 * 1024]; + + if (g_transient_count_total_leaks == 0) + return; + + fflush(stdout); + fflush(stderr); + my_output("\n"); + + if (g_limit_reached) { + sprintf(buf, + "LEAK SUMMARY: de-dup row table[%d] filled. Increase MY_ROW_LIMIT.\n", + MY_ROW_LIMIT); + my_output(buf); + } + + if (!label) + label = ""; + + if (g_transient_leaks_since_mark) { + sprintf(buf, "LEAK CHECKPOINT %d: leaks %d unique %d: %s\n", + g_checkpoint_id, g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); + my_output(buf); + } else { + sprintf(buf, "LEAK SUMMARY: TOTAL leaks %d de-duped %d: %s\n", + g_transient_count_total_leaks, g_transient_count_dedup_leaks, label); + my_output(buf); + } + my_output("\n"); + + for (k = 0; k < g_cs_ins; k++) { + if (g_cs_rows[k].transient_count_leaks > 0) { + sprintf(buf, "LEAK: %s leaked %d of %d times:\n", + g_cs_rows[k].uid.uid, + g_cs_rows[k].transient_count_leaks, + g_cs_rows[k].count_allocs); + my_output(buf); + + if (git_win32_leakcheck_stack_format( + buf, sizeof(buf), &g_cs_rows[k].raw_data, + NULL, NULL) >= 0) { + my_output(buf); + } + + my_output("\n"); + } + } + + fflush(stderr); +} + +/** + * Initialize our memory leak tracking and de-dup data structures. + * This should ONLY be called by git_libgit2_init(). + */ +void git_win32_leakcheck_stacktrace_init(void) +{ + InitializeCriticalSection(&g_crtdbg_stacktrace_cs); + + EnterCriticalSection(&g_crtdbg_stacktrace_cs); + + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE); + + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + + LeaveCriticalSection(&g_crtdbg_stacktrace_cs); +} + +int git_win32_leakcheck_stacktrace_dump( + git_win32_leakcheck_stacktrace_options opt, + const char *label) +{ + _CRT_REPORT_HOOK old; + unsigned int k; + int r = 0; + +#define IS_BIT_SET(o,b) (((o) & (b)) != 0) + + bool b_set_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK); + bool b_leaks_since_mark = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK); + bool b_leaks_total = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL); + bool b_quiet = IS_BIT_SET(opt, GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET); + + if (b_leaks_since_mark && b_leaks_total) { + git_error_set(GIT_ERROR_INVALID, "cannot combine LEAKS_SINCE_MARK and LEAKS_TOTAL."); + return GIT_ERROR; + } + if (!b_set_mark && !b_leaks_since_mark && !b_leaks_total) { + git_error_set(GIT_ERROR_INVALID, "nothing to do."); + return GIT_ERROR; + } + + EnterCriticalSection(&g_crtdbg_stacktrace_cs); + + if (b_leaks_since_mark || b_leaks_total) { + /* All variables with "transient" in the name are per-dump counters + * and reset before each dump. This lets us handle checkpoints. + */ + g_transient_count_total_leaks = 0; + g_transient_count_dedup_leaks = 0; + for (k = 0; k < g_cs_ins; k++) { + g_cs_rows[k].transient_count_leaks = 0; + } + } + + g_transient_leaks_since_mark = b_leaks_since_mark; + + old = _CrtSetReportHook(report_hook); + _CrtDumpMemoryLeaks(); + _CrtSetReportHook(old); + + if (b_leaks_since_mark || b_leaks_total) { + r = g_transient_count_dedup_leaks; + + if (!b_quiet) + dump_summary(label); + } + + if (b_set_mark) { + for (k = 0; k < g_cs_ins; k++) { + g_cs_rows[k].count_allocs_at_last_checkpoint = g_cs_rows[k].count_allocs; + } + + g_checkpoint_id++; + } + + LeaveCriticalSection(&g_crtdbg_stacktrace_cs); + + return r; +} + +/** + * Shutdown our memory leak tracking and dump summary data. + * This should ONLY be called by git_libgit2_shutdown(). + * + * We explicitly call _CrtDumpMemoryLeaks() during here so + * that we can compute summary data for the leaks. We print + * the stacktrace of each unique leak. + * + * This cleanup does not happen if the app calls exit() + * without calling the libgit2 shutdown code. + * + * This info we print here is independent of any automatic + * reporting during exit() caused by _CRTDBG_LEAK_CHECK_DF. + * Set it in your app if you also want traditional reporting. + */ +void git_win32_leakcheck_stacktrace_cleanup(void) +{ + /* At shutdown/cleanup, dump cumulative leak info + * with everything since startup. This might generate + * extra noise if the caller has been doing checkpoint + * dumps, but it might also eliminate some false + * positives for resources previously reported during + * checkpoints. + */ + git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL, + "CLEANUP"); + + DeleteCriticalSection(&g_crtdbg_stacktrace_cs); +} + +const char *git_win32_leakcheck_stacktrace(int skip, const char *file) +{ + git_win32_leakcheck_stack_raw_data new_data; + git_win32_leakcheck_stacktrace_row *row; + const char * result = file; + + if (git_win32_leakcheck_stack_capture(&new_data, skip+1) < 0) + return result; + + EnterCriticalSection(&g_crtdbg_stacktrace_cs); + + if (g_cs_ins < g_cs_end) { + row = insert_unique(&new_data); + result = row->uid.uid; + } else { + g_limit_reached = true; + } + + g_count_total_allocs++; + + LeaveCriticalSection(&g_crtdbg_stacktrace_cs); + + return result; +} + +static void git_win32_leakcheck_global_shutdown(void) +{ + git_win32_leakcheck_stacktrace_cleanup(); + git_win32_leakcheck_stack_cleanup(); +} + +bool git_win32_leakcheck_has_leaks(void) +{ + return (g_transient_count_total_leaks > 0); +} + +int git_win32_leakcheck_global_init(void) +{ + git_win32_leakcheck_stacktrace_init(); + git_win32_leakcheck_stack_init(); + + return git_runtime_shutdown_register(git_win32_leakcheck_global_shutdown); +} + +#else + +int git_win32_leakcheck_global_init(void) +{ + return 0; +} + +#endif diff --git a/src/util/win32/w32_leakcheck.h b/src/util/win32/w32_leakcheck.h new file mode 100644 index 000000000..82d863851 --- /dev/null +++ b/src/util/win32/w32_leakcheck.h @@ -0,0 +1,222 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_win32_leakcheck_h__ +#define INCLUDE_win32_leakcheck_h__ + +#include "git2_util.h" + +/* Initialize the win32 leak checking system. */ +int git_win32_leakcheck_global_init(void); + +#if defined(GIT_WIN32_LEAKCHECK) + +#include +#include + +#include "git2/errors.h" +#include "strnlen.h" + +bool git_win32_leakcheck_has_leaks(void); + +/* Stack frames (for stack tracing, below) */ + +/** + * This type defines a callback to be used to augment a C stacktrace + * with "aux" data. This can be used, for example, to allow LibGit2Sharp + * (or other interpreted consumer libraries) to give us C# stacktrace + * data for the PInvoke. + * + * This callback will be called during crtdbg-instrumented allocs. + * + * @param aux_id [out] A returned "aux_id" representing a unique + * (de-duped at the C# layer) stacktrace. "aux_id" 0 is reserved + * to mean no aux stacktrace data. + */ +typedef void (*git_win32_leakcheck_stack_aux_cb_alloc)(unsigned int *aux_id); + +/** + * This type defines a callback to be used to augment the output of + * a stacktrace. This will be used to request the C# layer format + * the C# stacktrace associated with "aux_id" into the provided + * buffer. + * + * This callback will be called during leak reporting. + * + * @param aux_id The "aux_id" key associated with a stacktrace. + * @param aux_msg A buffer where a formatted message should be written. + * @param aux_msg_len The size of the buffer. + */ +typedef void (*git_win32_leakcheck_stack_aux_cb_lookup)(unsigned int aux_id, char *aux_msg, size_t aux_msg_len); + +/** + * Register an "aux" data provider to augment our C stacktrace data. + * + * This can be used, for example, to allow LibGit2Sharp (or other + * interpreted consumer libraries) to give us the C# stacktrace of + * the PInvoke. + * + * If you choose to use this feature, it should be registered during + * initialization and not changed for the duration of the process. + */ +int git_win32_leakcheck_stack_set_aux_cb( + git_win32_leakcheck_stack_aux_cb_alloc cb_alloc, + git_win32_leakcheck_stack_aux_cb_lookup cb_lookup); + +/** + * Maximum number of stackframes to record for a + * single stacktrace. + */ +#define GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES 30 + +/** + * Wrapper containing the raw unprocessed stackframe + * data for a single stacktrace and any "aux_id". + * + * I put the aux_id first so leaks will be sorted by it. + * So, for example, if a specific callstack in C# leaks + * a repo handle, all of the pointers within the associated + * repo pointer will be grouped together. + */ +typedef struct { + unsigned int aux_id; + unsigned int nr_frames; + void *frames[GIT_WIN32_LEAKCHECK_STACK_MAX_FRAMES]; +} git_win32_leakcheck_stack_raw_data; + +/** + * Capture raw stack trace data for the current process/thread. + * + * @param skip Number of initial frames to skip. Pass 0 to + * begin with the caller of this routine. Pass 1 to begin + * with its caller. And so on. + */ +int git_win32_leakcheck_stack_capture(git_win32_leakcheck_stack_raw_data *pdata, int skip); + +/** + * Compare 2 raw stacktraces with the usual -1,0,+1 result. + * This includes any "aux_id" values in the comparison, so that + * our de-dup is also "aux" context relative. + */ +int git_win32_leakcheck_stack_compare( + git_win32_leakcheck_stack_raw_data *d1, + git_win32_leakcheck_stack_raw_data *d2); + +/** + * Format raw stacktrace data into buffer WITHOUT using any mallocs. + * + * @param prefix String written before each frame; defaults to "\t". + * @param suffix String written after each frame; defaults to "\n". + */ +int git_win32_leakcheck_stack_format( + char *pbuf, size_t buf_len, + const git_win32_leakcheck_stack_raw_data *pdata, + const char *prefix, const char *suffix); + +/** + * Convenience routine to capture and format stacktrace into + * a buffer WITHOUT using any mallocs. This is primarily a + * wrapper for testing. + * + * @param skip Number of initial frames to skip. Pass 0 to + * begin with the caller of this routine. Pass 1 to begin + * with its caller. And so on. + * @param prefix String written before each frame; defaults to "\t". + * @param suffix String written after each frame; defaults to "\n". + */ +int git_win32_leakcheck_stack( + char * pbuf, size_t buf_len, + int skip, + const char *prefix, const char *suffix); + +/* Stack tracing */ + +/* MSVC CRTDBG memory leak reporting. + * + * We DO NOT use the "_CRTDBG_MAP_ALLOC" macro described in the MSVC + * documentation because all allocs/frees in libgit2 already go through + * the "git__" routines defined in this file. Simply using the normal + * reporting mechanism causes all leaks to be attributed to a routine + * here in util.h (ie, the actual call to calloc()) rather than the + * caller of git__calloc(). + * + * Therefore, we declare a set of "git__crtdbg__" routines to replace + * the corresponding "git__" routines and re-define the "git__" symbols + * as macros. This allows us to get and report the file:line info of + * the real caller. + * + * We DO NOT replace the "git__free" routine because it needs to remain + * a function pointer because it is used as a function argument when + * setting up various structure "destructors". + * + * We also DO NOT use the "_CRTDBG_MAP_ALLOC" macro because it causes + * "free" to be remapped to "_free_dbg" and this causes problems for + * structures which define a field named "free". + * + * Finally, CRTDBG must be explicitly enabled and configured at program + * startup. See tests/main.c for an example. + */ + +/** + * Checkpoint options. + */ +typedef enum git_win32_leakcheck_stacktrace_options { + /** + * Set checkpoint marker. + */ + GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK = (1 << 0), + + /** + * Dump leaks since last checkpoint marker. + * May not be combined with _LEAKS_TOTAL. + * + * Note that this may generate false positives for global TLS + * error state and other global caches that aren't cleaned up + * until the thread/process terminates. So when using this + * around a region of interest, also check the final (at exit) + * dump before digging into leaks reported here. + */ + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK = (1 << 1), + + /** + * Dump leaks since init. May not be combined + * with _LEAKS_SINCE_MARK. + */ + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL = (1 << 2), + + /** + * Suppress printing during dumps. + * Just return leak count. + */ + GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET = (1 << 3), + +} git_win32_leakcheck_stacktrace_options; + +/** + * Checkpoint memory state and/or dump unique stack traces of + * current memory leaks. + * + * @return number of unique leaks (relative to requested starting + * point) or error. + */ +int git_win32_leakcheck_stacktrace_dump( + git_win32_leakcheck_stacktrace_options opt, + const char *label); + +/** + * Construct stacktrace and append it to the global buffer. + * Return pointer to start of this string. On any error or + * lack of buffer space, just return the given file buffer + * so it will behave as usual. + * + * This should ONLY be called by our internal memory allocations + * routines. + */ +const char *git_win32_leakcheck_stacktrace(int skip, const char *file); + +#endif +#endif diff --git a/src/util/win32/w32_util.c b/src/util/win32/w32_util.c new file mode 100644 index 000000000..fe4b75bae --- /dev/null +++ b/src/util/win32/w32_util.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "w32_util.h" + +/** + * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. + * The filter string enumerates all items in the directory. + * + * @param dest The buffer to receive the filter string. + * @param src The UTF-8 path of the directory to enumerate. + * @return True if the filter string was created successfully; false otherwise + */ +bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src) +{ + static const wchar_t suffix[] = L"\\*"; + int len = git_win32_path_from_utf8(dest, src); + + /* Ensure the path was converted */ + if (len < 0) + return false; + + /* Ensure that the path does not end with a trailing slash, + * because we're about to add one. Don't rely our trim_end + * helper, because we want to remove the backslash even for + * drive letter paths, in this case. */ + if (len > 0 && + (dest[len - 1] == L'/' || dest[len - 1] == L'\\')) { + dest[len - 1] = L'\0'; + len--; + } + + /* Ensure we have enough room to add the suffix */ + if ((size_t)len >= GIT_WIN_PATH_UTF16 - CONST_STRLEN(suffix)) + return false; + + wcscat(dest, suffix); + return true; +} + +/** + * Ensures the given path (file or folder) has the +H (hidden) attribute set. + * + * @param path The path which should receive the +H bit. + * @return 0 on success; -1 on failure + */ +int git_win32__set_hidden(const char *path, bool hidden) +{ + git_win32_path buf; + DWORD attrs, newattrs; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + attrs = GetFileAttributesW(buf); + + /* Ensure the path exists */ + if (attrs == INVALID_FILE_ATTRIBUTES) + return -1; + + if (hidden) + newattrs = attrs | FILE_ATTRIBUTE_HIDDEN; + else + newattrs = attrs & ~FILE_ATTRIBUTE_HIDDEN; + + if (attrs != newattrs && !SetFileAttributesW(buf, newattrs)) { + git_error_set(GIT_ERROR_OS, "failed to %s hidden bit for '%s'", + hidden ? "set" : "unset", path); + return -1; + } + + return 0; +} + +int git_win32__hidden(bool *out, const char *path) +{ + git_win32_path buf; + DWORD attrs; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + attrs = GetFileAttributesW(buf); + + /* Ensure the path exists */ + if (attrs == INVALID_FILE_ATTRIBUTES) + return -1; + + *out = (attrs & FILE_ATTRIBUTE_HIDDEN) ? true : false; + return 0; +} + +int git_win32__file_attribute_to_stat( + struct stat *st, + const WIN32_FILE_ATTRIBUTE_DATA *attrdata, + const wchar_t *path) +{ + git_win32__stat_init(st, + attrdata->dwFileAttributes, + attrdata->nFileSizeHigh, + attrdata->nFileSizeLow, + attrdata->ftCreationTime, + attrdata->ftLastAccessTime, + attrdata->ftLastWriteTime); + + if (attrdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && path) { + git_win32_path target; + + if (git_win32_path_readlink_w(target, path) >= 0) { + st->st_mode = (st->st_mode & ~S_IFMT) | S_IFLNK; + + /* st_size gets the UTF-8 length of the target name, in bytes, + * not counting the NULL terminator */ + if ((st->st_size = git__utf16_to_8(NULL, 0, target)) < 0) { + git_error_set(GIT_ERROR_OS, "could not convert reparse point name for '%ls'", path); + return -1; + } + } + } + + return 0; +} diff --git a/src/util/win32/w32_util.h b/src/util/win32/w32_util.h new file mode 100644 index 000000000..519663720 --- /dev/null +++ b/src/util/win32/w32_util.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_win32_w32_util_h__ +#define INCLUDE_win32_w32_util_h__ + +#include "git2_util.h" + +#include "utf-conv.h" +#include "posix.h" +#include "path_w32.h" + +/* + +#include "common.h" +#include "path.h" +#include "path_w32.h" +#include "utf-conv.h" +#include "posix.h" +#include "reparse.h" +#include "dir.h" +*/ + + +GIT_INLINE(bool) git_win32__isalpha(wchar_t c) +{ + return ((c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z')); +} + +/** + * Creates a FindFirstFile(Ex) filter string from a UTF-8 path. + * The filter string enumerates all items in the directory. + * + * @param dest The buffer to receive the filter string. + * @param src The UTF-8 path of the directory to enumerate. + * @return True if the filter string was created successfully; false otherwise + */ +bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src); + +/** + * Ensures the given path (file or folder) has the +H (hidden) attribute set + * or unset. + * + * @param path The path that should receive the +H bit. + * @param hidden true to set +H, false to unset it + * @return 0 on success; -1 on failure + */ +extern int git_win32__set_hidden(const char *path, bool hidden); + +/** + * Determines if the given file or folder has the hidden attribute set. + * @param hidden pointer to store hidden value + * @param path The path that should be queried for hiddenness. + * @return 0 on success or an error code. + */ +extern int git_win32__hidden(bool *hidden, const char *path); + +extern int git_win32__file_attribute_to_stat( + struct stat *st, + const WIN32_FILE_ATTRIBUTE_DATA *attrdata, + const wchar_t *path); + +/** + * Converts a FILETIME structure to a struct timespec. + * + * @param FILETIME A pointer to a FILETIME + * @param ts A pointer to the timespec structure to fill in + */ +GIT_INLINE(void) git_win32__filetime_to_timespec( + const FILETIME *ft, + struct timespec *ts) +{ + int64_t winTime = ((int64_t)ft->dwHighDateTime << 32) + ft->dwLowDateTime; + winTime -= INT64_C(116444736000000000); /* Windows to Unix Epoch conversion */ + ts->tv_sec = (time_t)(winTime / 10000000); +#ifdef GIT_USE_NSEC + ts->tv_nsec = (winTime % 10000000) * 100; +#else + ts->tv_nsec = 0; +#endif +} + +GIT_INLINE(void) git_win32__timeval_to_filetime( + FILETIME *ft, const struct p_timeval tv) +{ + int64_t ticks = (tv.tv_sec * INT64_C(10000000)) + + (tv.tv_usec * INT64_C(10)) + INT64_C(116444736000000000); + + ft->dwHighDateTime = ((ticks >> 32) & INT64_C(0xffffffff)); + ft->dwLowDateTime = (ticks & INT64_C(0xffffffff)); +} + +GIT_INLINE(void) git_win32__stat_init( + struct stat *st, + DWORD dwFileAttributes, + DWORD nFileSizeHigh, + DWORD nFileSizeLow, + FILETIME ftCreationTime, + FILETIME ftLastAccessTime, + FILETIME ftLastWriteTime) +{ + mode_t mode = S_IREAD; + + memset(st, 0, sizeof(struct stat)); + + if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + mode |= S_IFDIR; + else + mode |= S_IFREG; + + if ((dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) + mode |= S_IWRITE; + + st->st_ino = 0; + st->st_gid = 0; + st->st_uid = 0; + st->st_nlink = 1; + st->st_mode = mode; + st->st_size = ((int64_t)nFileSizeHigh << 32) + nFileSizeLow; + st->st_dev = _getdrive() - 1; + st->st_rdev = st->st_dev; + git_win32__filetime_to_timespec(&ftLastAccessTime, &(st->st_atim)); + git_win32__filetime_to_timespec(&ftLastWriteTime, &(st->st_mtim)); + git_win32__filetime_to_timespec(&ftCreationTime, &(st->st_ctim)); +} + +GIT_INLINE(void) git_win32__file_information_to_stat( + struct stat *st, + const BY_HANDLE_FILE_INFORMATION *fileinfo) +{ + git_win32__stat_init(st, + fileinfo->dwFileAttributes, + fileinfo->nFileSizeHigh, + fileinfo->nFileSizeLow, + fileinfo->ftCreationTime, + fileinfo->ftLastAccessTime, + fileinfo->ftLastWriteTime); +} + +#endif diff --git a/src/util/win32/win32-compat.h b/src/util/win32/win32-compat.h new file mode 100644 index 000000000..dee40a438 --- /dev/null +++ b/src/util/win32/win32-compat.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_win32_win32_compat_h__ +#define INCLUDE_win32_win32_compat_h__ + +#include +#include +#include +#include +#include + +typedef long suseconds_t; + +struct p_timeval { + time_t tv_sec; + suseconds_t tv_usec; +}; + +struct p_timespec { + time_t tv_sec; + long tv_nsec; +}; + +#define timespec p_timespec + +struct p_stat { + _dev_t st_dev; + _ino_t st_ino; + mode_t st_mode; + short st_nlink; + short st_uid; + short st_gid; + _dev_t st_rdev; + __int64 st_size; + struct timespec st_atim; + struct timespec st_mtim; + struct timespec st_ctim; +#define st_atime st_atim.tv_sec +#define st_mtime st_mtim.tv_sec +#define st_ctime st_ctim.tv_sec +#define st_atime_nsec st_atim.tv_nsec +#define st_mtime_nsec st_mtim.tv_nsec +#define st_ctime_nsec st_ctim.tv_nsec +}; + +#define stat p_stat + +#endif diff --git a/src/util/zstream.c b/src/util/zstream.c new file mode 100644 index 000000000..cb8b125ed --- /dev/null +++ b/src/util/zstream.c @@ -0,0 +1,210 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "zstream.h" + +#include + +#include "str.h" + +#define ZSTREAM_BUFFER_SIZE (1024 * 1024) +#define ZSTREAM_BUFFER_MIN_EXTRA 8 + +GIT_INLINE(int) zstream_seterr(git_zstream *zs) +{ + switch (zs->zerr) { + case Z_OK: + case Z_STREAM_END: + case Z_BUF_ERROR: /* not fatal; we retry with a larger buffer */ + return 0; + case Z_MEM_ERROR: + git_error_set_oom(); + break; + default: + if (zs->z.msg) + git_error_set_str(GIT_ERROR_ZLIB, zs->z.msg); + else + git_error_set(GIT_ERROR_ZLIB, "unknown compression error"); + } + + return -1; +} + +int git_zstream_init(git_zstream *zstream, git_zstream_t type) +{ + zstream->type = type; + + if (zstream->type == GIT_ZSTREAM_INFLATE) + zstream->zerr = inflateInit(&zstream->z); + else + zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION); + return zstream_seterr(zstream); +} + +void git_zstream_free(git_zstream *zstream) +{ + if (zstream->type == GIT_ZSTREAM_INFLATE) + inflateEnd(&zstream->z); + else + deflateEnd(&zstream->z); +} + +void git_zstream_reset(git_zstream *zstream) +{ + if (zstream->type == GIT_ZSTREAM_INFLATE) + inflateReset(&zstream->z); + else + deflateReset(&zstream->z); + zstream->in = NULL; + zstream->in_len = 0; + zstream->zerr = Z_STREAM_END; +} + +int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len) +{ + zstream->in = in; + zstream->in_len = in_len; + zstream->zerr = Z_OK; + return 0; +} + +bool git_zstream_done(git_zstream *zstream) +{ + return (!zstream->in_len && zstream->zerr == Z_STREAM_END); +} + +bool git_zstream_eos(git_zstream *zstream) +{ + return zstream->zerr == Z_STREAM_END; +} + +size_t git_zstream_suggest_output_len(git_zstream *zstream) +{ + if (zstream->in_len > ZSTREAM_BUFFER_SIZE) + return ZSTREAM_BUFFER_SIZE; + else if (zstream->in_len > ZSTREAM_BUFFER_MIN_EXTRA) + return zstream->in_len; + else + return ZSTREAM_BUFFER_MIN_EXTRA; +} + +int git_zstream_get_output_chunk( + void *out, size_t *out_len, git_zstream *zstream) +{ + size_t in_queued, in_used, out_queued; + + /* set up input data */ + zstream->z.next_in = (Bytef *)zstream->in; + + /* feed as much data to zlib as it can consume, at most UINT_MAX */ + if (zstream->in_len > UINT_MAX) { + zstream->z.avail_in = UINT_MAX; + zstream->flush = Z_NO_FLUSH; + } else { + zstream->z.avail_in = (uInt)zstream->in_len; + zstream->flush = Z_FINISH; + } + in_queued = (size_t)zstream->z.avail_in; + + /* set up output data */ + zstream->z.next_out = out; + zstream->z.avail_out = (uInt)*out_len; + + if ((size_t)zstream->z.avail_out != *out_len) + zstream->z.avail_out = UINT_MAX; + out_queued = (size_t)zstream->z.avail_out; + + /* compress next chunk */ + if (zstream->type == GIT_ZSTREAM_INFLATE) + zstream->zerr = inflate(&zstream->z, zstream->flush); + else + zstream->zerr = deflate(&zstream->z, zstream->flush); + + if (zstream_seterr(zstream)) + return -1; + + in_used = (in_queued - zstream->z.avail_in); + zstream->in_len -= in_used; + zstream->in += in_used; + + *out_len = (out_queued - zstream->z.avail_out); + + return 0; +} + +int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) +{ + size_t out_remain = *out_len; + + if (zstream->in_len && zstream->zerr == Z_STREAM_END) { + git_error_set(GIT_ERROR_ZLIB, "zlib input had trailing garbage"); + return -1; + } + + while (out_remain > 0 && zstream->zerr != Z_STREAM_END) { + size_t out_written = out_remain; + + if (git_zstream_get_output_chunk(out, &out_written, zstream) < 0) + return -1; + + out_remain -= out_written; + out = ((char *)out) + out_written; + } + + /* either we finished the input or we did not flush the data */ + GIT_ASSERT(zstream->in_len > 0 || zstream->flush == Z_FINISH); + + /* set out_size to number of bytes actually written to output */ + *out_len = *out_len - out_remain; + + return 0; +} + +static int zstream_buf(git_str *out, const void *in, size_t in_len, git_zstream_t type) +{ + git_zstream zs = GIT_ZSTREAM_INIT; + int error = 0; + + if ((error = git_zstream_init(&zs, type)) < 0) + return error; + + if ((error = git_zstream_set_input(&zs, in, in_len)) < 0) + goto done; + + while (!git_zstream_done(&zs)) { + size_t step = git_zstream_suggest_output_len(&zs), written; + + if ((error = git_str_grow_by(out, step)) < 0) + goto done; + + written = out->asize - out->size; + + if ((error = git_zstream_get_output( + out->ptr + out->size, &written, &zs)) < 0) + goto done; + + out->size += written; + } + + /* NULL terminate for consistency if possible */ + if (out->size < out->asize) + out->ptr[out->size] = '\0'; + +done: + git_zstream_free(&zs); + return error; +} + +int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len) +{ + return zstream_buf(out, in, in_len, GIT_ZSTREAM_DEFLATE); +} + +int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len) +{ + return zstream_buf(out, in, in_len, GIT_ZSTREAM_INFLATE); +} diff --git a/src/util/zstream.h b/src/util/zstream.h new file mode 100644 index 000000000..d78b11291 --- /dev/null +++ b/src/util/zstream.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_zstream_h__ +#define INCLUDE_zstream_h__ + +#include "git2_util.h" + +#include + +#include "str.h" + +typedef enum { + GIT_ZSTREAM_INFLATE, + GIT_ZSTREAM_DEFLATE +} git_zstream_t; + +typedef struct { + z_stream z; + git_zstream_t type; + const char *in; + size_t in_len; + int flush; + int zerr; +} git_zstream; + +#define GIT_ZSTREAM_INIT {{0}} + +int git_zstream_init(git_zstream *zstream, git_zstream_t type); +void git_zstream_free(git_zstream *zstream); + +int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len); + +size_t git_zstream_suggest_output_len(git_zstream *zstream); + +/* get as much output as is available in the input buffer */ +int git_zstream_get_output_chunk( + void *out, size_t *out_len, git_zstream *zstream); + +/* get all the output from the entire input buffer */ +int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream); + +bool git_zstream_done(git_zstream *zstream); +bool git_zstream_eos(git_zstream *zstream); + +void git_zstream_reset(git_zstream *zstream); + +int git_zstream_deflatebuf(git_str *out, const void *in, size_t in_len); +int git_zstream_inflatebuf(git_str *out, const void *in, size_t in_len); + +#endif -- cgit v1.2.1 From 5fcfada500073ddd4acbfa0b0906e37025b45556 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 15 Nov 2021 07:45:16 -0500 Subject: cmake: document CMakeLists.txt hierarchy --- CMakeLists.txt | 5 ++++- examples/CMakeLists.txt | 2 ++ fuzzers/CMakeLists.txt | 2 ++ src/libgit2/CMakeLists.txt | 3 +++ src/util/CMakeLists.txt | 1 + tests/CMakeLists.txt | 2 ++ 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eb137ecdd..90ecc92fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,8 @@ -# CMake build script for the libgit2 project +# libgit2: the cross-platform, linkable library implementation of git. # See `README.md` for build instructions. +# +# This top-level CMakeLists.txt sets up configuration options and +# determines which subprojects to build. cmake_minimum_required(VERSION 3.5.1) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 235e72ada..956b03827 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,3 +1,5 @@ +# examples: code usage examples of libgit2 + file(GLOB SRC_EXAMPLES *.c *.h) add_executable(lg2 ${SRC_EXAMPLES}) diff --git a/fuzzers/CMakeLists.txt b/fuzzers/CMakeLists.txt index eaa490fd9..a2c19ed40 100644 --- a/fuzzers/CMakeLists.txt +++ b/fuzzers/CMakeLists.txt @@ -1,3 +1,5 @@ +# fuzzers: libFuzzer and standalone fuzzing utilities + if(BUILD_FUZZERS AND NOT USE_STANDALONE_FUZZERS) set(CMAKE_REQUIRED_FLAGS "-fsanitize=fuzzer-no-link") add_c_flag(-fsanitize=fuzzer) diff --git a/src/libgit2/CMakeLists.txt b/src/libgit2/CMakeLists.txt index 3dac83d7a..52fdf0d88 100644 --- a/src/libgit2/CMakeLists.txt +++ b/src/libgit2/CMakeLists.txt @@ -1,3 +1,6 @@ +# libgit2: the shared library: this CMakeLists.txt compiles the core +# git library functionality. + add_library(git2internal OBJECT) set_target_properties(git2internal PROPERTIES C_STANDARD 90) set_target_properties(git2internal PROPERTIES C_EXTENSIONS OFF) diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index ea2df0a56..b725d5426 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -54,6 +54,7 @@ list(SORT UTIL_SRC_HASH) # target_sources(util PRIVATE ${UTIL_SRC} ${UTIL_SRC_OS} ${UTIL_SRC_HASH}) +ide_split_sources(util) target_include_directories(util PRIVATE ${UTIL_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES} PUBLIC ${libgit2_SOURCE_DIR}/include) target_include_directories(util SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f293c158d..33dfd0ae8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,5 @@ +# tests: the unit and integration tests for libgit2 + set(Python_ADDITIONAL_VERSIONS 3 2.7) find_package(PythonInterp) -- cgit v1.2.1 From d7b49ed4427bf4823ac5a18a176f317c6d2717ac Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 15 Nov 2021 14:54:17 -0500 Subject: cmake: remove unnecessary xcode hack --- src/libgit2/CMakeLists.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/libgit2/CMakeLists.txt b/src/libgit2/CMakeLists.txt index 52fdf0d88..7d8b9aef6 100644 --- a/src/libgit2/CMakeLists.txt +++ b/src/libgit2/CMakeLists.txt @@ -228,13 +228,6 @@ set(LIBGIT2_DEPENDENCY_OBJECTS ${LIBGIT2_DEPENDENCY_OBJECTS} PARENT_SCOPE) set(LIBGIT2_SYSTEM_INCLUDES ${LIBGIT2_SYSTEM_INCLUDES} PARENT_SCOPE) set(LIBGIT2_SYSTEM_LIBS ${LIBGIT2_SYSTEM_LIBS} PARENT_SCOPE) -if(XCODE_VERSION) - # This is required for Xcode to actually link the libgit2 library - # when using only object libraries. - file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/dummy.c "") - list(APPEND LIBGIT2_OBJECTS ${CMAKE_CURRENT_BINARY_DIR}/dummy.c) -endif() - # Compile and link libgit2 add_library(git2 ${SRC_RC} ${LIBGIT2_OBJECTS}) target_link_libraries(git2 ${LIBGIT2_SYSTEM_LIBS}) -- cgit v1.2.1 From 91ba089663f5efc3bd4ba14a5099372cf5ce57a6 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 15 Nov 2021 09:54:00 -0500 Subject: cmake: rename git2internal target to libgit2 The `git2internal` target is actually the git library; call it such so that IDE users have visibility into it. --- examples/CMakeLists.txt | 4 +- src/libgit2/CMakeLists.txt | 217 +++++++-------------------------------------- 2 files changed, 36 insertions(+), 185 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 956b03827..8e38c7d4e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -12,7 +12,7 @@ target_include_directories(lg2 PRIVATE ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_ target_include_directories(lg2 SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) if(WIN32 OR ANDROID) - target_link_libraries(lg2 git2) + target_link_libraries(lg2 libgit2package) else() - target_link_libraries(lg2 git2 pthread) + target_link_libraries(lg2 libgit2package pthread) endif() diff --git a/src/libgit2/CMakeLists.txt b/src/libgit2/CMakeLists.txt index 7d8b9aef6..0c7ddddba 100644 --- a/src/libgit2/CMakeLists.txt +++ b/src/libgit2/CMakeLists.txt @@ -1,9 +1,9 @@ # libgit2: the shared library: this CMakeLists.txt compiles the core # git library functionality. -add_library(git2internal OBJECT) -set_target_properties(git2internal PROPERTIES C_STANDARD 90) -set_target_properties(git2internal PROPERTIES C_EXTENSIONS OFF) +add_library(libgit2 OBJECT) +set_target_properties(libgit2 PROPERTIES C_STANDARD 90) +set_target_properties(libgit2 PROPERTIES C_EXTENSIONS OFF) include(PkgBuildConfig) @@ -16,177 +16,27 @@ set(LIBGIT2_INCLUDES if(WIN32 AND EMBED_SSH_PATH) file(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c") list(SORT SRC_SSH) - target_sources(git2internal PRIVATE ${SRC_SSH}) + target_sources(libgit2 PRIVATE ${SRC_SSH}) list(APPEND LIBGIT2_SYSTEM_INCLUDES "${EMBED_SSH_PATH}/include") file(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"") set(GIT_SSH 1) endif() -<<<<<<< HEAD -include(SelectHTTPSBackend) -include(SelectHashes) -include(SelectHTTPParser) -include(SelectRegex) -include(SelectSSH) -include(SelectWinHTTP) -include(SelectZlib) - - -if(USE_SHA1 STREQUAL "CollisionDetection") - file(GLOB SRC_SHA1 hash/sha1/collisiondetect.* hash/sha1/sha1dc/*) -elseif(USE_SHA1 STREQUAL "OpenSSL") - file(GLOB SRC_SHA1 hash/sha1/openssl.*) -elseif(USE_SHA1 STREQUAL "CommonCrypto") - file(GLOB SRC_SHA1 hash/sha1/common_crypto.*) -elseif(USE_SHA1 STREQUAL "mbedTLS") - file(GLOB SRC_SHA1 hash/sha1/mbedtls.*) -elseif(USE_SHA1 STREQUAL "Win32") - file(GLOB SRC_SHA1 hash/sha1/win32.*) -elseif(USE_SHA1 STREQUAL "Generic") - file(GLOB SRC_SHA1 hash/sha1/generic.*) -endif() -list(APPEND SRC_SHA1 "hash/sha1.h") -target_sources(git2internal PRIVATE ${SRC_SHA1}) - -# Optional external dependency: ntlmclient -if(USE_NTLMCLIENT) - set(GIT_NTLM 1) - add_subdirectory("${PROJECT_SOURCE_DIR}/deps/ntlmclient" "${PROJECT_BINARY_DIR}/deps/ntlmclient") - list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/ntlmclient") - list(APPEND LIBGIT2_DEPENDENCY_OBJECTS "$") -endif() -add_feature_info(ntlmclient GIT_NTLM "NTLM authentication support for Unix") - -# Optional external dependency: GSSAPI - -include(SelectGSSAPI) - -# Optional external dependency: iconv -if(USE_ICONV) - find_package(Iconv) -endif() -if(ICONV_FOUND) - set(GIT_USE_ICONV 1) - list(APPEND LIBGIT2_SYSTEM_INCLUDES ${ICONV_INCLUDE_DIR}) - list(APPEND LIBGIT2_SYSTEM_LIBS ${ICONV_LIBRARIES}) - list(APPEND LIBGIT2_PC_LIBS ${ICONV_LIBRARIES}) -endif() -add_feature_info(iconv GIT_USE_ICONV "iconv encoding conversion support") - - -if(USE_THREADS) - if(NOT WIN32) - find_package(Threads REQUIRED) - endif() - - set(GIT_THREADS 1) -endif() - -if(USE_NSEC) - set(GIT_USE_NSEC 1) -endif() - -if(HAVE_STRUCT_STAT_ST_MTIM) - set(GIT_USE_STAT_MTIM 1) -elseif(HAVE_STRUCT_STAT_ST_MTIMESPEC) - set(GIT_USE_STAT_MTIMESPEC 1) -elseif(HAVE_STRUCT_STAT_ST_MTIME_NSEC) - set(GIT_USE_STAT_MTIME_NSEC 1) -endif() - -target_compile_definitions(git2internal PRIVATE _FILE_OFFSET_BITS=64) - -||||||| parent of a930dafb4 (refactor: make util an object library) -include(SelectHTTPSBackend) -include(SelectHashes) -include(SelectHTTPParser) -include(SelectRegex) -include(SelectSSH) -include(SelectWinHTTP) -include(SelectZlib) - - -if(USE_SHA1 STREQUAL "CollisionDetection") - file(GLOB SRC_SHA1 hash/sha1/collisiondetect.* hash/sha1/sha1dc/*) -elseif(USE_SHA1 STREQUAL "OpenSSL") - file(GLOB SRC_SHA1 hash/sha1/openssl.*) -elseif(USE_SHA1 STREQUAL "CommonCrypto") - file(GLOB SRC_SHA1 hash/sha1/common_crypto.*) -elseif(USE_SHA1 STREQUAL "mbedTLS") - file(GLOB SRC_SHA1 hash/sha1/mbedtls.*) -elseif(USE_SHA1 STREQUAL "Win32") - file(GLOB SRC_SHA1 hash/sha1/win32.*) -elseif(USE_SHA1 STREQUAL "Generic") - file(GLOB SRC_SHA1 hash/sha1/generic.*) -endif() -list(APPEND SRC_SHA1 "hash/sha1.h") -target_sources(git2internal PRIVATE ${SRC_SHA1}) - -# Optional external dependency: ntlmclient -if(USE_NTLMCLIENT) - set(GIT_NTLM 1) - add_subdirectory("${PROJECT_SOURCE_DIR}/deps/ntlmclient" "${PROJECT_BINARY_DIR}/deps/ntlmclient") - list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/ntlmclient") - list(APPEND LIBGIT2_DEPENDENCY_OBJECTS "$") -endif() -add_feature_info(ntlmclient GIT_NTLM "NTLM authentication support for Unix") - -# Optional external dependency: GSSAPI - -include(SelectGSSAPI) - -# Optional external dependency: iconv -if(USE_ICONV) - find_package(Iconv) -endif() -if(ICONV_FOUND) - set(GIT_USE_ICONV 1) - list(APPEND LIBGIT2_SYSTEM_INCLUDES ${ICONV_INCLUDE_DIR}) - list(APPEND LIBGIT2_SYSTEM_LIBS ${ICONV_LIBRARIES}) - list(APPEND LIBGIT2_PC_LIBS ${ICONV_LIBRARIES}) -endif() -add_feature_info(iconv GIT_USE_ICONV "iconv encoding conversion support") - - -if(USE_THREADS) - if(NOT WIN32) - find_package(Threads REQUIRED) - endif() - - set(GIT_THREADS 1) -endif() - -if(USE_NSEC) - set(GIT_USE_NSEC 1) -endif() - -if(HAVE_STRUCT_STAT_ST_MTIM) - set(GIT_USE_STAT_MTIM 1) -elseif(HAVE_STRUCT_STAT_ST_MTIMESPEC) - set(GIT_USE_STAT_MTIMESPEC 1) -elseif(HAVE_STRUCT_STAT_ST_MTIME_NSEC) - set(GIT_USE_STAT_MTIME_NSEC 1) -endif() - -target_compile_definitions(git2internal PRIVATE _FILE_OFFSET_BITS=64) - -======= ->>>>>>> a930dafb4 (refactor: make util an object library) # Collect sourcefiles file(GLOB SRC_H "${PROJECT_SOURCE_DIR}/include/git2.h" "${PROJECT_SOURCE_DIR}/include/git2/*.h" "${PROJECT_SOURCE_DIR}/include/git2/sys/*.h") list(SORT SRC_H) -target_sources(git2internal PRIVATE ${SRC_H}) +target_sources(libgit2 PRIVATE ${SRC_H}) file(GLOB SRC_GIT2 *.c *.h streams/*.c streams/*.h transports/*.c transports/*.h xdiff/*.c xdiff/*.h) list(SORT SRC_GIT2) -target_sources(git2internal PRIVATE ${SRC_GIT2}) +target_sources(libgit2 PRIVATE ${SRC_GIT2}) if(WIN32 AND NOT CYGWIN) # Add resource information on Windows @@ -198,9 +48,9 @@ if(APPLE) set_source_files_properties(streams/stransport.c PROPERTIES COMPILE_FLAGS -Wno-deprecated) endif() -# the xdiff dependency is not (yet) warning-free, disable warnings as -# errors for the xdiff sources until we've sorted them out - if(MSVC) +# the xdiff dependency is not (yet) warning-free, disable warnings +# as errors for the xdiff sources until we've sorted them out +if(MSVC) set_source_files_properties(xdiff/xdiffi.c PROPERTIES COMPILE_FLAGS -WX-) set_source_files_properties(xdiff/xemit.c PROPERTIES COMPILE_FLAGS -WX-) set_source_files_properties(xdiff/xhistogram.c PROPERTIES COMPILE_FLAGS -WX-) @@ -213,13 +63,13 @@ else() set_source_files_properties(xdiff/xhistogram.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") set_source_files_properties(xdiff/xutils.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") set_source_files_properties(xdiff/xpatience.c PROPERTIES COMPILE_FLAGS "-Wno-sign-compare") - endif() +endif() -ide_split_sources(git2internal) -list(APPEND LIBGIT2_OBJECTS $ $ ${LIBGIT2_DEPENDENCY_OBJECTS}) +ide_split_sources(libgit2) +list(APPEND LIBGIT2_OBJECTS $ $ ${LIBGIT2_DEPENDENCY_OBJECTS}) -target_include_directories(git2internal PRIVATE ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES} PUBLIC ${PROJECT_SOURCE_DIR}/include) -target_include_directories(git2internal SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) +target_include_directories(libgit2 PRIVATE ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES} PUBLIC ${PROJECT_SOURCE_DIR}/include) +target_include_directories(libgit2 SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) set(LIBGIT2_INCLUDES ${LIBGIT2_INCLUDES} PARENT_SCOPE) set(LIBGIT2_OBJECTS ${LIBGIT2_OBJECTS} PARENT_SCOPE) @@ -228,31 +78,34 @@ set(LIBGIT2_DEPENDENCY_OBJECTS ${LIBGIT2_DEPENDENCY_OBJECTS} PARENT_SCOPE) set(LIBGIT2_SYSTEM_INCLUDES ${LIBGIT2_SYSTEM_INCLUDES} PARENT_SCOPE) set(LIBGIT2_SYSTEM_LIBS ${LIBGIT2_SYSTEM_LIBS} PARENT_SCOPE) +# # Compile and link libgit2 -add_library(git2 ${SRC_RC} ${LIBGIT2_OBJECTS}) -target_link_libraries(git2 ${LIBGIT2_SYSTEM_LIBS}) +# + +add_library(libgit2package ${SRC_RC} ${LIBGIT2_OBJECTS}) +target_link_libraries(libgit2package ${LIBGIT2_SYSTEM_LIBS}) -set_target_properties(git2 PROPERTIES C_STANDARD 90) -set_target_properties(git2 PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) -set_target_properties(git2 PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) -set_target_properties(git2 PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +set_target_properties(libgit2package PROPERTIES C_STANDARD 90) +set_target_properties(libgit2package PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +set_target_properties(libgit2package PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +set_target_properties(libgit2package PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) # Workaround for Cmake bug #0011240 (see http://public.kitware.com/Bug/view.php?id=11240) # Win64+MSVC+static libs = linker error if(MSVC AND GIT_ARCH_64 AND NOT BUILD_SHARED_LIBS) - set_target_properties(git2 PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64") + set_target_properties(libgit2package PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64") endif() -ide_split_sources(git2) +ide_split_sources(libgit2package) if(SONAME) - set_target_properties(git2 PROPERTIES VERSION ${libgit2_VERSION}) - set_target_properties(git2 PROPERTIES SOVERSION "${libgit2_VERSION_MAJOR}.${libgit2_VERSION_MINOR}") + set_target_properties(libgit2package PROPERTIES VERSION ${libgit2_VERSION}) + set_target_properties(libgit2package PROPERTIES SOVERSION "${libgit2_VERSION_MAJOR}.${libgit2_VERSION_MINOR}") if(LIBGIT2_FILENAME) - target_compile_definitions(git2 PRIVATE LIBGIT2_FILENAME=\"${LIBGIT2_FILENAME}\") - set_target_properties(git2 PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME}) + target_compile_definitions(libgit2package PRIVATE LIBGIT2_FILENAME=\"${LIBGIT2_FILENAME}\") + set_target_properties(libgit2package PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME}) elseif(DEFINED LIBGIT2_PREFIX) - set_target_properties(git2 PROPERTIES PREFIX "${LIBGIT2_PREFIX}") + set_target_properties(libgit2package PROPERTIES PREFIX "${LIBGIT2_PREFIX}") endif() endif() @@ -261,20 +114,18 @@ pkg_build_config(NAME libgit2 DESCRIPTION "The git library, take 2" LIBS_SELF git2 PRIVATE_LIBS ${LIBGIT2_PC_LIBS} - REQUIRES ${LIBGIT2_PC_REQUIRES} -) + REQUIRES ${LIBGIT2_PC_REQUIRES}) if(MSVC_IDE) # Precompiled headers - set_target_properties(git2 PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") + set_target_properties(libgit2package PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") set_source_files_properties(win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h") endif() # Install -install(TARGETS git2 +install(TARGETS libgit2package RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/git2 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(FILES ${PROJECT_SOURCE_DIR}/include/git2.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -- cgit v1.2.1 From 3344fddc97bbdea9c1b6ebb6f7fb6dbd70b41dfb Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 16 Nov 2021 23:29:22 -0500 Subject: refactor: `tests` is now `tests/libgit2` Like we want to separate libgit2 and utility source code, we want to separate libgit2 and utility tests. Start by moving all the tests into libgit2. --- src/CMakeLists.txt | 4 +- tests/CMakeLists.txt | 100 +- tests/README.md | 62 +- tests/apply/apply_helpers.c | 135 -- tests/apply/apply_helpers.h | 497 ------ tests/apply/both.c | 747 -------- tests/apply/callbacks.c | 128 -- tests/apply/check.c | 121 -- tests/apply/fromdiff.c | 378 ---- tests/apply/fromfile.c | 449 ----- tests/apply/index.c | 321 ---- tests/apply/partial.c | 231 --- tests/apply/tree.c | 94 - tests/apply/workdir.c | 358 ---- tests/attr/attr_expect.h | 43 - tests/attr/file.c | 243 --- tests/attr/flags.c | 108 -- tests/attr/lookup.c | 263 --- tests/attr/macro.c | 197 --- tests/attr/repo.c | 405 ----- tests/blame/blame_helpers.c | 67 - tests/blame/blame_helpers.h | 14 - tests/blame/buffer.c | 192 -- tests/blame/getters.c | 56 - tests/blame/harder.c | 79 - tests/blame/simple.c | 362 ---- tests/checkout/binaryunicode.c | 58 - tests/checkout/checkout_helpers.c | 151 -- tests/checkout/checkout_helpers.h | 30 - tests/checkout/conflict.c | 1145 ------------ tests/checkout/crlf.c | 496 ------ tests/checkout/head.c | 292 --- tests/checkout/icase.c | 292 --- tests/checkout/index.c | 881 ---------- tests/checkout/nasty.c | 386 ---- tests/checkout/tree.c | 1685 ------------------ tests/checkout/typechange.c | 335 ---- tests/cherrypick/bare.c | 105 -- tests/cherrypick/workdir.c | 469 ----- tests/clar.c | 788 --------- tests/clar.h | 173 -- tests/clar/fixtures.h | 50 - tests/clar/fs.h | 520 ------ tests/clar/print.h | 200 --- tests/clar/sandbox.h | 154 -- tests/clar/summary.h | 134 -- tests/clar_libgit2.c | 623 ------- tests/clar_libgit2.h | 236 --- tests/clar_libgit2_timer.c | 30 - tests/clar_libgit2_timer.h | 35 - tests/clar_libgit2_trace.c | 263 --- tests/clar_libgit2_trace.h | 7 - tests/clone/empty.c | 102 -- tests/clone/local.c | 212 --- tests/clone/nonetwork.c | 361 ---- tests/clone/transport.c | 51 - tests/commit/commit.c | 189 -- tests/commit/parent.c | 60 - tests/commit/parse.c | 551 ------ tests/commit/signature.c | 148 -- tests/commit/write.c | 424 ----- tests/config/add.c | 37 - tests/config/backend.c | 24 - tests/config/conditionals.c | 175 -- tests/config/config_helpers.c | 67 - tests/config/config_helpers.h | 13 - tests/config/configlevel.c | 73 - tests/config/global.c | 172 -- tests/config/include.c | 259 --- tests/config/memory.c | 139 -- tests/config/multivar.c | 288 --- tests/config/new.c | 34 - tests/config/read.c | 1040 ----------- tests/config/readonly.c | 65 - tests/config/rename.c | 89 - tests/config/snapshot.c | 139 -- tests/config/stress.c | 206 --- tests/config/validkeyname.c | 49 - tests/config/write.c | 764 -------- tests/core/array.c | 57 - tests/core/assert.c | 94 - tests/core/bitvec.c | 64 - tests/core/buf.c | 54 - tests/core/copy.c | 153 -- tests/core/dirent.c | 306 ---- tests/core/encoding.c | 42 - tests/core/env.c | 320 ---- tests/core/errors.c | 222 --- tests/core/features.c | 35 - tests/core/filebuf.c | 267 --- tests/core/ftruncate.c | 48 - tests/core/futils.c | 115 -- tests/core/gitstr.c | 1225 ------------- tests/core/hex.c | 22 - tests/core/iconv.c | 78 - tests/core/init.c | 54 - tests/core/integer.c | 253 --- tests/core/link.c | 630 ------- tests/core/memmem.c | 46 - tests/core/mkdir.c | 291 --- tests/core/oid.c | 79 - tests/core/oidmap.c | 127 -- tests/core/opts.c | 71 - tests/core/path.c | 739 -------- tests/core/pool.c | 92 - tests/core/posix.c | 238 --- tests/core/pqueue.c | 150 -- tests/core/qsort.c | 90 - tests/core/regexp.c | 213 --- tests/core/rmdir.c | 120 -- tests/core/sha1.c | 70 - tests/core/sortedcache.c | 363 ---- tests/core/stat.c | 113 -- tests/core/string.c | 136 -- tests/core/strmap.c | 190 -- tests/core/strtol.c | 128 -- tests/core/structinit.c | 201 --- tests/core/useragent.c | 17 - tests/core/utf8.c | 20 - tests/core/vector.c | 430 ----- tests/core/wildmatch.c | 248 --- tests/core/zstream.c | 167 -- tests/date/date.c | 22 - tests/date/rfc2822.c | 37 - tests/delta/apply.c | 21 - tests/describe/describe.c | 55 - tests/describe/describe_helpers.c | 44 - tests/describe/describe_helpers.h | 14 - tests/describe/t6120.c | 156 -- tests/diff/binary.c | 545 ------ tests/diff/blob.c | 1063 ----------- tests/diff/diff_helpers.c | 316 ---- tests/diff/diff_helpers.h | 73 - tests/diff/diffiter.c | 453 ----- tests/diff/drivers.c | 279 --- tests/diff/externalmodifications.c | 133 -- tests/diff/format_email.c | 523 ------ tests/diff/index.c | 302 ---- tests/diff/notify.c | 258 --- tests/diff/parse.c | 450 ----- tests/diff/patch.c | 703 -------- tests/diff/patchid.c | 93 - tests/diff/pathspec.c | 93 - tests/diff/racediffiter.c | 129 -- tests/diff/rename.c | 2034 --------------------- tests/diff/stats.c | 378 ---- tests/diff/submodules.c | 543 ------ tests/diff/tree.c | 575 ------ tests/diff/workdir.c | 2242 ------------------------ tests/email/create.c | 364 ---- tests/email/create.c.bak | 386 ---- tests/fetch/local.c | 67 - tests/fetchhead/fetchhead_data.h | 48 - tests/fetchhead/nonetwork.c | 542 ------ tests/filter/bare.c | 188 -- tests/filter/blob.c | 145 -- tests/filter/crlf.c | 253 --- tests/filter/crlf.h | 30 - tests/filter/custom.c | 268 --- tests/filter/custom_helpers.c | 163 -- tests/filter/custom_helpers.h | 19 - tests/filter/file.c | 98 -- tests/filter/ident.c | 133 -- tests/filter/query.c | 90 - tests/filter/stream.c | 215 --- tests/filter/systemattrs.c | 85 - tests/filter/wildcard.c | 188 -- tests/generate.py | 316 ---- tests/graph/ahead_behind.c | 58 - tests/graph/commitgraph.c | 126 -- tests/graph/descendant_of.c | 55 - tests/graph/reachable_from_any.c | 236 --- tests/headertest.c | 13 - tests/ignore/path.c | 585 ------- tests/ignore/status.c | 1337 -------------- tests/index/add.c | 84 - tests/index/addall.c | 496 ------ tests/index/bypath.c | 359 ---- tests/index/cache.c | 238 --- tests/index/collision.c | 149 -- tests/index/conflicts.c | 462 ----- tests/index/conflicts.h | 7 - tests/index/crlf.c | 402 ----- tests/index/filemodes.c | 319 ---- tests/index/inmemory.c | 22 - tests/index/names.c | 187 -- tests/index/nsec.c | 129 -- tests/index/racy.c | 323 ---- tests/index/read_index.c | 232 --- tests/index/read_tree.c | 46 - tests/index/rename.c | 86 - tests/index/reuc.c | 376 ---- tests/index/splitindex.c | 21 - tests/index/stage.c | 62 - tests/index/tests.c | 1173 ------------- tests/index/version.c | 140 -- tests/iterator/index.c | 1385 --------------- tests/iterator/iterator_helpers.c | 143 -- tests/iterator/iterator_helpers.h | 16 - tests/iterator/tree.c | 1080 ------------ tests/iterator/workdir.c | 1523 ---------------- tests/libgit2/CMakeLists.txt | 98 ++ tests/libgit2/apply/apply_helpers.c | 135 ++ tests/libgit2/apply/apply_helpers.h | 497 ++++++ tests/libgit2/apply/both.c | 747 ++++++++ tests/libgit2/apply/callbacks.c | 128 ++ tests/libgit2/apply/check.c | 121 ++ tests/libgit2/apply/fromdiff.c | 378 ++++ tests/libgit2/apply/fromfile.c | 449 +++++ tests/libgit2/apply/index.c | 321 ++++ tests/libgit2/apply/partial.c | 231 +++ tests/libgit2/apply/tree.c | 94 + tests/libgit2/apply/workdir.c | 358 ++++ tests/libgit2/attr/attr_expect.h | 43 + tests/libgit2/attr/file.c | 243 +++ tests/libgit2/attr/flags.c | 108 ++ tests/libgit2/attr/lookup.c | 263 +++ tests/libgit2/attr/macro.c | 197 +++ tests/libgit2/attr/repo.c | 405 +++++ tests/libgit2/blame/blame_helpers.c | 67 + tests/libgit2/blame/blame_helpers.h | 14 + tests/libgit2/blame/buffer.c | 192 ++ tests/libgit2/blame/getters.c | 56 + tests/libgit2/blame/harder.c | 79 + tests/libgit2/blame/simple.c | 362 ++++ tests/libgit2/checkout/binaryunicode.c | 58 + tests/libgit2/checkout/checkout_helpers.c | 151 ++ tests/libgit2/checkout/checkout_helpers.h | 30 + tests/libgit2/checkout/conflict.c | 1145 ++++++++++++ tests/libgit2/checkout/crlf.c | 496 ++++++ tests/libgit2/checkout/head.c | 292 +++ tests/libgit2/checkout/icase.c | 292 +++ tests/libgit2/checkout/index.c | 881 ++++++++++ tests/libgit2/checkout/nasty.c | 386 ++++ tests/libgit2/checkout/tree.c | 1685 ++++++++++++++++++ tests/libgit2/checkout/typechange.c | 335 ++++ tests/libgit2/cherrypick/bare.c | 105 ++ tests/libgit2/cherrypick/workdir.c | 469 +++++ tests/libgit2/clar.c | 788 +++++++++ tests/libgit2/clar.h | 173 ++ tests/libgit2/clar/fixtures.h | 50 + tests/libgit2/clar/fs.h | 520 ++++++ tests/libgit2/clar/print.h | 200 +++ tests/libgit2/clar/sandbox.h | 154 ++ tests/libgit2/clar/summary.h | 134 ++ tests/libgit2/clar_libgit2.c | 623 +++++++ tests/libgit2/clar_libgit2.h | 236 +++ tests/libgit2/clar_libgit2_timer.c | 30 + tests/libgit2/clar_libgit2_timer.h | 35 + tests/libgit2/clar_libgit2_trace.c | 263 +++ tests/libgit2/clar_libgit2_trace.h | 7 + tests/libgit2/clone/empty.c | 102 ++ tests/libgit2/clone/local.c | 212 +++ tests/libgit2/clone/nonetwork.c | 361 ++++ tests/libgit2/clone/transport.c | 51 + tests/libgit2/commit/commit.c | 189 ++ tests/libgit2/commit/parent.c | 60 + tests/libgit2/commit/parse.c | 551 ++++++ tests/libgit2/commit/signature.c | 148 ++ tests/libgit2/commit/write.c | 424 +++++ tests/libgit2/config/add.c | 37 + tests/libgit2/config/backend.c | 24 + tests/libgit2/config/conditionals.c | 175 ++ tests/libgit2/config/config_helpers.c | 67 + tests/libgit2/config/config_helpers.h | 13 + tests/libgit2/config/configlevel.c | 73 + tests/libgit2/config/global.c | 172 ++ tests/libgit2/config/include.c | 259 +++ tests/libgit2/config/memory.c | 139 ++ tests/libgit2/config/multivar.c | 288 +++ tests/libgit2/config/new.c | 34 + tests/libgit2/config/read.c | 1040 +++++++++++ tests/libgit2/config/readonly.c | 65 + tests/libgit2/config/rename.c | 89 + tests/libgit2/config/snapshot.c | 139 ++ tests/libgit2/config/stress.c | 206 +++ tests/libgit2/config/validkeyname.c | 49 + tests/libgit2/config/write.c | 764 ++++++++ tests/libgit2/core/array.c | 57 + tests/libgit2/core/assert.c | 94 + tests/libgit2/core/bitvec.c | 64 + tests/libgit2/core/buf.c | 54 + tests/libgit2/core/copy.c | 153 ++ tests/libgit2/core/dirent.c | 306 ++++ tests/libgit2/core/encoding.c | 42 + tests/libgit2/core/env.c | 320 ++++ tests/libgit2/core/errors.c | 222 +++ tests/libgit2/core/features.c | 35 + tests/libgit2/core/filebuf.c | 267 +++ tests/libgit2/core/ftruncate.c | 48 + tests/libgit2/core/futils.c | 115 ++ tests/libgit2/core/gitstr.c | 1225 +++++++++++++ tests/libgit2/core/hex.c | 22 + tests/libgit2/core/iconv.c | 78 + tests/libgit2/core/init.c | 54 + tests/libgit2/core/integer.c | 253 +++ tests/libgit2/core/link.c | 630 +++++++ tests/libgit2/core/memmem.c | 46 + tests/libgit2/core/mkdir.c | 291 +++ tests/libgit2/core/oid.c | 79 + tests/libgit2/core/oidmap.c | 127 ++ tests/libgit2/core/opts.c | 71 + tests/libgit2/core/path.c | 739 ++++++++ tests/libgit2/core/pool.c | 92 + tests/libgit2/core/posix.c | 238 +++ tests/libgit2/core/pqueue.c | 150 ++ tests/libgit2/core/qsort.c | 90 + tests/libgit2/core/regexp.c | 213 +++ tests/libgit2/core/rmdir.c | 120 ++ tests/libgit2/core/sha1.c | 70 + tests/libgit2/core/sortedcache.c | 363 ++++ tests/libgit2/core/stat.c | 113 ++ tests/libgit2/core/string.c | 136 ++ tests/libgit2/core/strmap.c | 190 ++ tests/libgit2/core/strtol.c | 128 ++ tests/libgit2/core/structinit.c | 201 +++ tests/libgit2/core/useragent.c | 17 + tests/libgit2/core/utf8.c | 20 + tests/libgit2/core/vector.c | 430 +++++ tests/libgit2/core/wildmatch.c | 248 +++ tests/libgit2/core/zstream.c | 167 ++ tests/libgit2/date/date.c | 22 + tests/libgit2/date/rfc2822.c | 37 + tests/libgit2/delta/apply.c | 21 + tests/libgit2/describe/describe.c | 55 + tests/libgit2/describe/describe_helpers.c | 44 + tests/libgit2/describe/describe_helpers.h | 14 + tests/libgit2/describe/t6120.c | 156 ++ tests/libgit2/diff/binary.c | 545 ++++++ tests/libgit2/diff/blob.c | 1063 +++++++++++ tests/libgit2/diff/diff_helpers.c | 316 ++++ tests/libgit2/diff/diff_helpers.h | 73 + tests/libgit2/diff/diffiter.c | 453 +++++ tests/libgit2/diff/drivers.c | 279 +++ tests/libgit2/diff/externalmodifications.c | 133 ++ tests/libgit2/diff/format_email.c | 523 ++++++ tests/libgit2/diff/index.c | 302 ++++ tests/libgit2/diff/notify.c | 258 +++ tests/libgit2/diff/parse.c | 450 +++++ tests/libgit2/diff/patch.c | 703 ++++++++ tests/libgit2/diff/patchid.c | 93 + tests/libgit2/diff/pathspec.c | 93 + tests/libgit2/diff/racediffiter.c | 129 ++ tests/libgit2/diff/rename.c | 2034 +++++++++++++++++++++ tests/libgit2/diff/stats.c | 378 ++++ tests/libgit2/diff/submodules.c | 543 ++++++ tests/libgit2/diff/tree.c | 575 ++++++ tests/libgit2/diff/workdir.c | 2242 ++++++++++++++++++++++++ tests/libgit2/email/create.c | 364 ++++ tests/libgit2/email/create.c.bak | 386 ++++ tests/libgit2/fetch/local.c | 67 + tests/libgit2/fetchhead/fetchhead_data.h | 48 + tests/libgit2/fetchhead/nonetwork.c | 542 ++++++ tests/libgit2/filter/bare.c | 188 ++ tests/libgit2/filter/blob.c | 145 ++ tests/libgit2/filter/crlf.c | 253 +++ tests/libgit2/filter/crlf.h | 30 + tests/libgit2/filter/custom.c | 268 +++ tests/libgit2/filter/custom_helpers.c | 163 ++ tests/libgit2/filter/custom_helpers.h | 19 + tests/libgit2/filter/file.c | 98 ++ tests/libgit2/filter/ident.c | 133 ++ tests/libgit2/filter/query.c | 90 + tests/libgit2/filter/stream.c | 215 +++ tests/libgit2/filter/systemattrs.c | 85 + tests/libgit2/filter/wildcard.c | 188 ++ tests/libgit2/generate.py | 316 ++++ tests/libgit2/graph/ahead_behind.c | 58 + tests/libgit2/graph/commitgraph.c | 126 ++ tests/libgit2/graph/descendant_of.c | 55 + tests/libgit2/graph/reachable_from_any.c | 236 +++ tests/libgit2/headertest.c | 13 + tests/libgit2/ignore/path.c | 585 +++++++ tests/libgit2/ignore/status.c | 1337 ++++++++++++++ tests/libgit2/index/add.c | 84 + tests/libgit2/index/addall.c | 496 ++++++ tests/libgit2/index/bypath.c | 359 ++++ tests/libgit2/index/cache.c | 238 +++ tests/libgit2/index/collision.c | 149 ++ tests/libgit2/index/conflicts.c | 462 +++++ tests/libgit2/index/conflicts.h | 7 + tests/libgit2/index/crlf.c | 402 +++++ tests/libgit2/index/filemodes.c | 319 ++++ tests/libgit2/index/inmemory.c | 22 + tests/libgit2/index/names.c | 187 ++ tests/libgit2/index/nsec.c | 129 ++ tests/libgit2/index/racy.c | 323 ++++ tests/libgit2/index/read_index.c | 232 +++ tests/libgit2/index/read_tree.c | 46 + tests/libgit2/index/rename.c | 86 + tests/libgit2/index/reuc.c | 376 ++++ tests/libgit2/index/splitindex.c | 21 + tests/libgit2/index/stage.c | 62 + tests/libgit2/index/tests.c | 1173 +++++++++++++ tests/libgit2/index/version.c | 140 ++ tests/libgit2/iterator/index.c | 1385 +++++++++++++++ tests/libgit2/iterator/iterator_helpers.c | 143 ++ tests/libgit2/iterator/iterator_helpers.h | 16 + tests/libgit2/iterator/tree.c | 1080 ++++++++++++ tests/libgit2/iterator/workdir.c | 1523 ++++++++++++++++ tests/libgit2/mailmap/basic.c | 101 ++ tests/libgit2/mailmap/blame.c | 64 + tests/libgit2/mailmap/mailmap_testdata.h | 21 + tests/libgit2/mailmap/parsing.c | 269 +++ tests/libgit2/main.c | 50 + tests/libgit2/merge/analysis.c | 184 ++ tests/libgit2/merge/annotated_commit.c | 26 + tests/libgit2/merge/conflict_data.h | 112 ++ tests/libgit2/merge/driver.c | 396 +++++ tests/libgit2/merge/files.c | 465 +++++ tests/libgit2/merge/merge_helpers.c | 365 ++++ tests/libgit2/merge/merge_helpers.h | 72 + tests/libgit2/merge/trees/automerge.c | 195 +++ tests/libgit2/merge/trees/commits.c | 148 ++ tests/libgit2/merge/trees/modeconflict.c | 58 + tests/libgit2/merge/trees/recursive.c | 458 +++++ tests/libgit2/merge/trees/renames.c | 352 ++++ tests/libgit2/merge/trees/treediff.c | 555 ++++++ tests/libgit2/merge/trees/trivial.c | 306 ++++ tests/libgit2/merge/trees/whitespace.c | 81 + tests/libgit2/merge/workdir/dirty.c | 351 ++++ tests/libgit2/merge/workdir/recursive.c | 84 + tests/libgit2/merge/workdir/renames.c | 155 ++ tests/libgit2/merge/workdir/setup.c | 1096 ++++++++++++ tests/libgit2/merge/workdir/simple.c | 778 ++++++++ tests/libgit2/merge/workdir/submodules.c | 130 ++ tests/libgit2/merge/workdir/trivial.c | 262 +++ tests/libgit2/message/trailer.c | 164 ++ tests/libgit2/network/cred.c | 46 + tests/libgit2/network/fetchlocal.c | 552 ++++++ tests/libgit2/network/matchhost.c | 13 + tests/libgit2/network/refspecs.c | 191 ++ tests/libgit2/network/remote/defaultbranch.c | 107 ++ tests/libgit2/network/remote/delete.c | 46 + tests/libgit2/network/remote/isvalidname.c | 24 + tests/libgit2/network/remote/local.c | 483 +++++ tests/libgit2/network/remote/push.c | 114 ++ tests/libgit2/network/remote/remotes.c | 575 ++++++ tests/libgit2/network/remote/rename.c | 245 +++ tests/libgit2/network/url/joinpath.c | 194 ++ tests/libgit2/network/url/parse.c | 557 ++++++ tests/libgit2/network/url/pattern.c | 103 ++ tests/libgit2/network/url/redirect.c | 147 ++ tests/libgit2/network/url/scp.c | 321 ++++ tests/libgit2/network/url/valid.c | 17 + tests/libgit2/notes/notes.c | 658 +++++++ tests/libgit2/notes/notesref.c | 67 + tests/libgit2/object/blob/filter.c | 149 ++ tests/libgit2/object/blob/fromstream.c | 86 + tests/libgit2/object/blob/write.c | 68 + tests/libgit2/object/cache.c | 276 +++ tests/libgit2/object/commit/commitstagedfile.c | 218 +++ tests/libgit2/object/commit/parse.c | 232 +++ tests/libgit2/object/lookup.c | 122 ++ tests/libgit2/object/lookupbypath.c | 83 + tests/libgit2/object/message.c | 197 +++ tests/libgit2/object/peel.c | 118 ++ tests/libgit2/object/raw/chars.c | 41 + tests/libgit2/object/raw/compare.c | 123 ++ tests/libgit2/object/raw/convert.c | 112 ++ tests/libgit2/object/raw/data.h | 323 ++++ tests/libgit2/object/raw/fromstr.c | 30 + tests/libgit2/object/raw/hash.c | 169 ++ tests/libgit2/object/raw/short.c | 137 ++ tests/libgit2/object/raw/size.c | 13 + tests/libgit2/object/raw/type2string.c | 54 + tests/libgit2/object/raw/write.c | 462 +++++ tests/libgit2/object/shortid.c | 51 + tests/libgit2/object/tag/list.c | 117 ++ tests/libgit2/object/tag/parse.c | 218 +++ tests/libgit2/object/tag/peel.c | 61 + tests/libgit2/object/tag/read.c | 179 ++ tests/libgit2/object/tag/write.c | 260 +++ tests/libgit2/object/tree/attributes.c | 118 ++ tests/libgit2/object/tree/duplicateentries.c | 157 ++ tests/libgit2/object/tree/frompath.c | 68 + tests/libgit2/object/tree/parse.c | 164 ++ tests/libgit2/object/tree/read.c | 119 ++ tests/libgit2/object/tree/update.c | 302 ++++ tests/libgit2/object/tree/walk.c | 177 ++ tests/libgit2/object/tree/write.c | 526 ++++++ tests/libgit2/object/validate.c | 50 + tests/libgit2/odb/alternates.c | 80 + tests/libgit2/odb/backend/backend_helpers.c | 172 ++ tests/libgit2/odb/backend/backend_helpers.h | 24 + tests/libgit2/odb/backend/mempack.c | 60 + tests/libgit2/odb/backend/multiple.c | 121 ++ tests/libgit2/odb/backend/nobackend.c | 46 + tests/libgit2/odb/backend/nonrefreshing.c | 147 ++ tests/libgit2/odb/backend/refreshing.c | 176 ++ tests/libgit2/odb/backend/simple.c | 250 +++ tests/libgit2/odb/emptyobjects.c | 58 + tests/libgit2/odb/foreach.c | 140 ++ tests/libgit2/odb/freshen.c | 183 ++ tests/libgit2/odb/largefiles.c | 189 ++ tests/libgit2/odb/loose.c | 291 +++ tests/libgit2/odb/loose_data.h | 522 ++++++ tests/libgit2/odb/mixed.c | 286 +++ tests/libgit2/odb/pack_data.h | 151 ++ tests/libgit2/odb/pack_data_one.h | 19 + tests/libgit2/odb/packed.c | 79 + tests/libgit2/odb/packed_one.c | 60 + tests/libgit2/odb/sorting.c | 99 ++ tests/libgit2/odb/streamwrite.c | 56 + tests/libgit2/online/badssl.c | 75 + tests/libgit2/online/clone.c | 1004 +++++++++++ tests/libgit2/online/customcert.c | 79 + tests/libgit2/online/fetch.c | 323 ++++ tests/libgit2/online/fetchhead.c | 169 ++ tests/libgit2/online/push.c | 917 ++++++++++ tests/libgit2/online/push_util.c | 141 ++ tests/libgit2/online/push_util.h | 83 + tests/libgit2/online/remotes.c | 127 ++ tests/libgit2/pack/filelimit.c | 136 ++ tests/libgit2/pack/indexer.c | 320 ++++ tests/libgit2/pack/midx.c | 111 ++ tests/libgit2/pack/packbuilder.c | 276 +++ tests/libgit2/pack/sharing.c | 42 + tests/libgit2/pack/threadsafety.c | 62 + tests/libgit2/patch/parse.c | 221 +++ tests/libgit2/patch/patch_common.h | 1005 +++++++++++ tests/libgit2/patch/print.c | 186 ++ tests/libgit2/path/core.c | 405 +++++ tests/libgit2/path/dotgit.c | 206 +++ tests/libgit2/path/win32.c | 282 +++ tests/libgit2/perf/helper__perf__do_merge.c | 75 + tests/libgit2/perf/helper__perf__do_merge.h | 4 + tests/libgit2/perf/helper__perf__timer.c | 73 + tests/libgit2/perf/helper__perf__timer.h | 27 + tests/libgit2/perf/merge.c | 31 + tests/libgit2/precompiled.c | 1 + tests/libgit2/precompiled.h | 4 + tests/libgit2/rebase/abort.c | 250 +++ tests/libgit2/rebase/inmemory.c | 210 +++ tests/libgit2/rebase/iterator.c | 140 ++ tests/libgit2/rebase/merge.c | 855 +++++++++ tests/libgit2/rebase/setup.c | 596 +++++++ tests/libgit2/rebase/sign.c | 491 ++++++ tests/libgit2/rebase/submodule.c | 95 + tests/libgit2/refs/basic.c | 85 + tests/libgit2/refs/branches/checkedout.c | 53 + tests/libgit2/refs/branches/create.c | 279 +++ tests/libgit2/refs/branches/delete.c | 188 ++ tests/libgit2/refs/branches/ishead.c | 98 ++ tests/libgit2/refs/branches/iterator.c | 151 ++ tests/libgit2/refs/branches/lookup.c | 68 + tests/libgit2/refs/branches/move.c | 212 +++ tests/libgit2/refs/branches/name.c | 62 + tests/libgit2/refs/branches/remote.c | 65 + tests/libgit2/refs/branches/upstream.c | 218 +++ tests/libgit2/refs/branches/upstreamname.c | 35 + tests/libgit2/refs/crashes.c | 44 + tests/libgit2/refs/create.c | 362 ++++ tests/libgit2/refs/delete.c | 118 ++ tests/libgit2/refs/dup.c | 42 + tests/libgit2/refs/foreachglob.c | 100 ++ tests/libgit2/refs/isvalidname.c | 38 + tests/libgit2/refs/iterator.c | 274 +++ tests/libgit2/refs/list.c | 57 + tests/libgit2/refs/listall.c | 47 + tests/libgit2/refs/lookup.c | 68 + tests/libgit2/refs/namespaces.c | 36 + tests/libgit2/refs/normalize.c | 401 +++++ tests/libgit2/refs/overwrite.c | 136 ++ tests/libgit2/refs/pack.c | 105 ++ tests/libgit2/refs/peel.c | 131 ++ tests/libgit2/refs/races.c | 170 ++ tests/libgit2/refs/read.c | 299 ++++ tests/libgit2/refs/ref_helpers.c | 25 + tests/libgit2/refs/ref_helpers.h | 1 + tests/libgit2/refs/reflog/drop.c | 115 ++ tests/libgit2/refs/reflog/messages.c | 421 +++++ tests/libgit2/refs/reflog/reflog.c | 469 +++++ tests/libgit2/refs/reflog/reflog_helpers.c | 120 ++ tests/libgit2/refs/reflog/reflog_helpers.h | 12 + tests/libgit2/refs/rename.c | 368 ++++ tests/libgit2/refs/revparse.c | 890 ++++++++++ tests/libgit2/refs/setter.c | 99 ++ tests/libgit2/refs/shorthand.c | 27 + tests/libgit2/refs/tags/name.c | 17 + tests/libgit2/refs/transactions.c | 157 ++ tests/libgit2/refs/unicode.c | 54 + tests/libgit2/refs/update.c | 26 + tests/libgit2/remote/create.c | 388 ++++ tests/libgit2/remote/fetch.c | 169 ++ tests/libgit2/remote/httpproxy.c | 188 ++ tests/libgit2/remote/insteadof.c | 154 ++ tests/libgit2/remote/list.c | 43 + tests/libgit2/repo/config.c | 211 +++ tests/libgit2/repo/discover.c | 210 +++ tests/libgit2/repo/env.c | 277 +++ tests/libgit2/repo/extensions.c | 72 + tests/libgit2/repo/getters.c | 53 + tests/libgit2/repo/hashfile.c | 171 ++ tests/libgit2/repo/head.c | 182 ++ tests/libgit2/repo/headtree.c | 53 + tests/libgit2/repo/init.c | 738 ++++++++ tests/libgit2/repo/message.c | 39 + tests/libgit2/repo/new.c | 27 + tests/libgit2/repo/open.c | 455 +++++ tests/libgit2/repo/pathspec.c | 385 ++++ tests/libgit2/repo/repo_helpers.c | 37 + tests/libgit2/repo/repo_helpers.h | 7 + tests/libgit2/repo/reservedname.c | 132 ++ tests/libgit2/repo/setters.c | 108 ++ tests/libgit2/repo/shallow.c | 39 + tests/libgit2/repo/state.c | 131 ++ tests/libgit2/repo/template.c | 305 ++++ tests/libgit2/reset/default.c | 212 +++ tests/libgit2/reset/hard.c | 293 ++++ tests/libgit2/reset/mixed.c | 85 + tests/libgit2/reset/reset_helpers.c | 20 + tests/libgit2/reset/reset_helpers.h | 7 + tests/libgit2/reset/soft.c | 189 ++ tests/libgit2/revert/bare.c | 106 ++ tests/libgit2/revert/rename.c | 49 + tests/libgit2/revert/workdir.c | 576 ++++++ tests/libgit2/revwalk/basic.c | 627 +++++++ tests/libgit2/revwalk/hidecb.c | 230 +++ tests/libgit2/revwalk/mergebase.c | 514 ++++++ tests/libgit2/revwalk/signatureparsing.c | 47 + tests/libgit2/revwalk/simplify.c | 56 + tests/libgit2/stash/apply.c | 449 +++++ tests/libgit2/stash/drop.c | 174 ++ tests/libgit2/stash/foreach.c | 126 ++ tests/libgit2/stash/save.c | 490 ++++++ tests/libgit2/stash/stash_helpers.c | 57 + tests/libgit2/stash/stash_helpers.h | 8 + tests/libgit2/stash/submodules.c | 83 + tests/libgit2/status/renames.c | 844 +++++++++ tests/libgit2/status/single.c | 45 + tests/libgit2/status/status_data.h | 326 ++++ tests/libgit2/status/status_helpers.c | 97 + tests/libgit2/status/status_helpers.h | 51 + tests/libgit2/status/submodules.c | 563 ++++++ tests/libgit2/status/worktree.c | 1356 ++++++++++++++ tests/libgit2/status/worktree_init.c | 338 ++++ tests/libgit2/str/basic.c | 50 + tests/libgit2/str/oom.c | 58 + tests/libgit2/str/percent.c | 48 + tests/libgit2/str/quote.c | 87 + tests/libgit2/str/splice.c | 92 + tests/libgit2/stream/deprecated.c | 60 + tests/libgit2/stream/registration.c | 119 ++ tests/libgit2/stress/diff.c | 146 ++ tests/libgit2/submodule/add.c | 251 +++ tests/libgit2/submodule/escape.c | 98 ++ tests/libgit2/submodule/init.c | 115 ++ tests/libgit2/submodule/inject_option.c | 80 + tests/libgit2/submodule/lookup.c | 516 ++++++ tests/libgit2/submodule/modify.c | 233 +++ tests/libgit2/submodule/nosubs.c | 130 ++ tests/libgit2/submodule/open.c | 90 + tests/libgit2/submodule/repository_init.c | 38 + tests/libgit2/submodule/status.c | 354 ++++ tests/libgit2/submodule/submodule_helpers.c | 245 +++ tests/libgit2/submodule/submodule_helpers.h | 25 + tests/libgit2/submodule/update.c | 440 +++++ tests/libgit2/threads/atomic.c | 125 ++ tests/libgit2/threads/basic.c | 83 + tests/libgit2/threads/diff.c | 218 +++ tests/libgit2/threads/iterator.c | 55 + tests/libgit2/threads/refdb.c | 220 +++ tests/libgit2/threads/thread_helpers.c | 44 + tests/libgit2/threads/thread_helpers.h | 8 + tests/libgit2/threads/tlsdata.c | 65 + tests/libgit2/trace/trace.c | 106 ++ tests/libgit2/trace/windows/stacktrace.c | 152 ++ tests/libgit2/transport/register.c | 79 + tests/libgit2/transports/smart/packet.c | 340 ++++ tests/libgit2/valgrind-supp-mac.txt | 176 ++ tests/libgit2/win32/forbidden.c | 182 ++ tests/libgit2/win32/longpath.c | 130 ++ tests/libgit2/win32/systemdir.c | 338 ++++ tests/libgit2/worktree/bare.c | 72 + tests/libgit2/worktree/config.c | 47 + tests/libgit2/worktree/merge.c | 121 ++ tests/libgit2/worktree/open.c | 126 ++ tests/libgit2/worktree/reflog.c | 91 + tests/libgit2/worktree/refs.c | 198 +++ tests/libgit2/worktree/repository.c | 67 + tests/libgit2/worktree/submodule.c | 92 + tests/libgit2/worktree/worktree.c | 647 +++++++ tests/libgit2/worktree/worktree_helpers.c | 30 + tests/libgit2/worktree/worktree_helpers.h | 11 + tests/mailmap/basic.c | 101 -- tests/mailmap/blame.c | 64 - tests/mailmap/mailmap_testdata.h | 21 - tests/mailmap/parsing.c | 269 --- tests/main.c | 50 - tests/merge/analysis.c | 184 -- tests/merge/annotated_commit.c | 26 - tests/merge/conflict_data.h | 112 -- tests/merge/driver.c | 396 ----- tests/merge/files.c | 465 ----- tests/merge/merge_helpers.c | 365 ---- tests/merge/merge_helpers.h | 72 - tests/merge/trees/automerge.c | 195 --- tests/merge/trees/commits.c | 148 -- tests/merge/trees/modeconflict.c | 58 - tests/merge/trees/recursive.c | 458 ----- tests/merge/trees/renames.c | 352 ---- tests/merge/trees/treediff.c | 555 ------ tests/merge/trees/trivial.c | 306 ---- tests/merge/trees/whitespace.c | 81 - tests/merge/workdir/dirty.c | 351 ---- tests/merge/workdir/recursive.c | 84 - tests/merge/workdir/renames.c | 155 -- tests/merge/workdir/setup.c | 1096 ------------ tests/merge/workdir/simple.c | 778 -------- tests/merge/workdir/submodules.c | 130 -- tests/merge/workdir/trivial.c | 262 --- tests/message/trailer.c | 164 -- tests/network/cred.c | 46 - tests/network/fetchlocal.c | 552 ------ tests/network/matchhost.c | 13 - tests/network/refspecs.c | 191 -- tests/network/remote/defaultbranch.c | 107 -- tests/network/remote/delete.c | 46 - tests/network/remote/isvalidname.c | 24 - tests/network/remote/local.c | 483 ----- tests/network/remote/push.c | 114 -- tests/network/remote/remotes.c | 575 ------ tests/network/remote/rename.c | 245 --- tests/network/url/joinpath.c | 194 -- tests/network/url/parse.c | 557 ------ tests/network/url/pattern.c | 103 -- tests/network/url/redirect.c | 147 -- tests/network/url/scp.c | 321 ---- tests/network/url/valid.c | 17 - tests/notes/notes.c | 658 ------- tests/notes/notesref.c | 67 - tests/object/blob/filter.c | 149 -- tests/object/blob/fromstream.c | 86 - tests/object/blob/write.c | 68 - tests/object/cache.c | 276 --- tests/object/commit/commitstagedfile.c | 218 --- tests/object/commit/parse.c | 232 --- tests/object/lookup.c | 122 -- tests/object/lookupbypath.c | 83 - tests/object/message.c | 197 --- tests/object/peel.c | 118 -- tests/object/raw/chars.c | 41 - tests/object/raw/compare.c | 123 -- tests/object/raw/convert.c | 112 -- tests/object/raw/data.h | 323 ---- tests/object/raw/fromstr.c | 30 - tests/object/raw/hash.c | 169 -- tests/object/raw/short.c | 137 -- tests/object/raw/size.c | 13 - tests/object/raw/type2string.c | 54 - tests/object/raw/write.c | 462 ----- tests/object/shortid.c | 51 - tests/object/tag/list.c | 117 -- tests/object/tag/parse.c | 218 --- tests/object/tag/peel.c | 61 - tests/object/tag/read.c | 179 -- tests/object/tag/write.c | 260 --- tests/object/tree/attributes.c | 118 -- tests/object/tree/duplicateentries.c | 157 -- tests/object/tree/frompath.c | 68 - tests/object/tree/parse.c | 164 -- tests/object/tree/read.c | 119 -- tests/object/tree/update.c | 302 ---- tests/object/tree/walk.c | 177 -- tests/object/tree/write.c | 526 ------ tests/object/validate.c | 50 - tests/odb/alternates.c | 80 - tests/odb/backend/backend_helpers.c | 172 -- tests/odb/backend/backend_helpers.h | 24 - tests/odb/backend/mempack.c | 60 - tests/odb/backend/multiple.c | 121 -- tests/odb/backend/nobackend.c | 46 - tests/odb/backend/nonrefreshing.c | 147 -- tests/odb/backend/refreshing.c | 176 -- tests/odb/backend/simple.c | 250 --- tests/odb/emptyobjects.c | 58 - tests/odb/foreach.c | 140 -- tests/odb/freshen.c | 183 -- tests/odb/largefiles.c | 189 -- tests/odb/loose.c | 291 --- tests/odb/loose_data.h | 522 ------ tests/odb/mixed.c | 286 --- tests/odb/pack_data.h | 151 -- tests/odb/pack_data_one.h | 19 - tests/odb/packed.c | 79 - tests/odb/packed_one.c | 60 - tests/odb/sorting.c | 99 -- tests/odb/streamwrite.c | 56 - tests/online/badssl.c | 75 - tests/online/clone.c | 1004 ----------- tests/online/customcert.c | 79 - tests/online/fetch.c | 323 ---- tests/online/fetchhead.c | 169 -- tests/online/push.c | 917 ---------- tests/online/push_util.c | 141 -- tests/online/push_util.h | 83 - tests/online/remotes.c | 127 -- tests/pack/filelimit.c | 136 -- tests/pack/indexer.c | 320 ---- tests/pack/midx.c | 111 -- tests/pack/packbuilder.c | 276 --- tests/pack/sharing.c | 42 - tests/pack/threadsafety.c | 62 - tests/patch/parse.c | 221 --- tests/patch/patch_common.h | 1005 ----------- tests/patch/print.c | 186 -- tests/path/core.c | 405 ----- tests/path/dotgit.c | 206 --- tests/path/win32.c | 282 --- tests/perf/helper__perf__do_merge.c | 75 - tests/perf/helper__perf__do_merge.h | 4 - tests/perf/helper__perf__timer.c | 73 - tests/perf/helper__perf__timer.h | 27 - tests/perf/merge.c | 31 - tests/precompiled.c | 1 - tests/precompiled.h | 4 - tests/rebase/abort.c | 250 --- tests/rebase/inmemory.c | 210 --- tests/rebase/iterator.c | 140 -- tests/rebase/merge.c | 855 --------- tests/rebase/setup.c | 596 ------- tests/rebase/sign.c | 491 ------ tests/rebase/submodule.c | 95 - tests/refs/basic.c | 85 - tests/refs/branches/checkedout.c | 53 - tests/refs/branches/create.c | 279 --- tests/refs/branches/delete.c | 188 -- tests/refs/branches/ishead.c | 98 -- tests/refs/branches/iterator.c | 151 -- tests/refs/branches/lookup.c | 68 - tests/refs/branches/move.c | 212 --- tests/refs/branches/name.c | 62 - tests/refs/branches/remote.c | 65 - tests/refs/branches/upstream.c | 218 --- tests/refs/branches/upstreamname.c | 35 - tests/refs/crashes.c | 44 - tests/refs/create.c | 362 ---- tests/refs/delete.c | 118 -- tests/refs/dup.c | 42 - tests/refs/foreachglob.c | 100 -- tests/refs/isvalidname.c | 38 - tests/refs/iterator.c | 274 --- tests/refs/list.c | 57 - tests/refs/listall.c | 47 - tests/refs/lookup.c | 68 - tests/refs/namespaces.c | 36 - tests/refs/normalize.c | 401 ----- tests/refs/overwrite.c | 136 -- tests/refs/pack.c | 105 -- tests/refs/peel.c | 131 -- tests/refs/races.c | 170 -- tests/refs/read.c | 299 ---- tests/refs/ref_helpers.c | 25 - tests/refs/ref_helpers.h | 1 - tests/refs/reflog/drop.c | 115 -- tests/refs/reflog/messages.c | 421 ----- tests/refs/reflog/reflog.c | 469 ----- tests/refs/reflog/reflog_helpers.c | 120 -- tests/refs/reflog/reflog_helpers.h | 12 - tests/refs/rename.c | 368 ---- tests/refs/revparse.c | 890 ---------- tests/refs/setter.c | 99 -- tests/refs/shorthand.c | 27 - tests/refs/tags/name.c | 17 - tests/refs/transactions.c | 157 -- tests/refs/unicode.c | 54 - tests/refs/update.c | 26 - tests/remote/create.c | 388 ---- tests/remote/fetch.c | 169 -- tests/remote/httpproxy.c | 188 -- tests/remote/insteadof.c | 154 -- tests/remote/list.c | 43 - tests/repo/config.c | 211 --- tests/repo/discover.c | 210 --- tests/repo/env.c | 277 --- tests/repo/extensions.c | 72 - tests/repo/getters.c | 53 - tests/repo/hashfile.c | 171 -- tests/repo/head.c | 182 -- tests/repo/headtree.c | 53 - tests/repo/init.c | 738 -------- tests/repo/message.c | 39 - tests/repo/new.c | 27 - tests/repo/open.c | 455 ----- tests/repo/pathspec.c | 385 ---- tests/repo/repo_helpers.c | 37 - tests/repo/repo_helpers.h | 7 - tests/repo/reservedname.c | 132 -- tests/repo/setters.c | 108 -- tests/repo/shallow.c | 39 - tests/repo/state.c | 131 -- tests/repo/template.c | 305 ---- tests/reset/default.c | 212 --- tests/reset/hard.c | 293 ---- tests/reset/mixed.c | 85 - tests/reset/reset_helpers.c | 20 - tests/reset/reset_helpers.h | 7 - tests/reset/soft.c | 189 -- tests/revert/bare.c | 106 -- tests/revert/rename.c | 49 - tests/revert/workdir.c | 576 ------ tests/revwalk/basic.c | 627 ------- tests/revwalk/hidecb.c | 230 --- tests/revwalk/mergebase.c | 514 ------ tests/revwalk/signatureparsing.c | 47 - tests/revwalk/simplify.c | 56 - tests/stash/apply.c | 449 ----- tests/stash/drop.c | 174 -- tests/stash/foreach.c | 126 -- tests/stash/save.c | 490 ------ tests/stash/stash_helpers.c | 57 - tests/stash/stash_helpers.h | 8 - tests/stash/submodules.c | 83 - tests/status/renames.c | 844 --------- tests/status/single.c | 45 - tests/status/status_data.h | 326 ---- tests/status/status_helpers.c | 97 - tests/status/status_helpers.h | 51 - tests/status/submodules.c | 563 ------ tests/status/worktree.c | 1356 -------------- tests/status/worktree_init.c | 338 ---- tests/str/basic.c | 50 - tests/str/oom.c | 58 - tests/str/percent.c | 48 - tests/str/quote.c | 87 - tests/str/splice.c | 92 - tests/stream/deprecated.c | 60 - tests/stream/registration.c | 119 -- tests/stress/diff.c | 146 -- tests/submodule/add.c | 251 --- tests/submodule/escape.c | 98 -- tests/submodule/init.c | 115 -- tests/submodule/inject_option.c | 80 - tests/submodule/lookup.c | 516 ------ tests/submodule/modify.c | 233 --- tests/submodule/nosubs.c | 130 -- tests/submodule/open.c | 90 - tests/submodule/repository_init.c | 38 - tests/submodule/status.c | 354 ---- tests/submodule/submodule_helpers.c | 245 --- tests/submodule/submodule_helpers.h | 25 - tests/submodule/update.c | 440 ----- tests/threads/atomic.c | 125 -- tests/threads/basic.c | 83 - tests/threads/diff.c | 218 --- tests/threads/iterator.c | 55 - tests/threads/refdb.c | 220 --- tests/threads/thread_helpers.c | 44 - tests/threads/thread_helpers.h | 8 - tests/threads/tlsdata.c | 65 - tests/trace/trace.c | 106 -- tests/trace/windows/stacktrace.c | 152 -- tests/transport/register.c | 79 - tests/transports/smart/packet.c | 340 ---- tests/valgrind-supp-mac.txt | 176 -- tests/win32/forbidden.c | 182 -- tests/win32/longpath.c | 130 -- tests/win32/systemdir.c | 338 ---- tests/worktree/bare.c | 72 - tests/worktree/config.c | 47 - tests/worktree/merge.c | 121 -- tests/worktree/open.c | 126 -- tests/worktree/reflog.c | 91 - tests/worktree/refs.c | 198 --- tests/worktree/repository.c | 67 - tests/worktree/submodule.c | 92 - tests/worktree/worktree.c | 647 ------- tests/worktree/worktree_helpers.c | 30 - tests/worktree/worktree_helpers.h | 11 - 970 files changed, 112286 insertions(+), 112256 deletions(-) delete mode 100644 tests/apply/apply_helpers.c delete mode 100644 tests/apply/apply_helpers.h delete mode 100644 tests/apply/both.c delete mode 100644 tests/apply/callbacks.c delete mode 100644 tests/apply/check.c delete mode 100644 tests/apply/fromdiff.c delete mode 100644 tests/apply/fromfile.c delete mode 100644 tests/apply/index.c delete mode 100644 tests/apply/partial.c delete mode 100644 tests/apply/tree.c delete mode 100644 tests/apply/workdir.c delete mode 100644 tests/attr/attr_expect.h delete mode 100644 tests/attr/file.c delete mode 100644 tests/attr/flags.c delete mode 100644 tests/attr/lookup.c delete mode 100644 tests/attr/macro.c delete mode 100644 tests/attr/repo.c delete mode 100644 tests/blame/blame_helpers.c delete mode 100644 tests/blame/blame_helpers.h delete mode 100644 tests/blame/buffer.c delete mode 100644 tests/blame/getters.c delete mode 100644 tests/blame/harder.c delete mode 100644 tests/blame/simple.c delete mode 100644 tests/checkout/binaryunicode.c delete mode 100644 tests/checkout/checkout_helpers.c delete mode 100644 tests/checkout/checkout_helpers.h delete mode 100644 tests/checkout/conflict.c delete mode 100644 tests/checkout/crlf.c delete mode 100644 tests/checkout/head.c delete mode 100644 tests/checkout/icase.c delete mode 100644 tests/checkout/index.c delete mode 100644 tests/checkout/nasty.c delete mode 100644 tests/checkout/tree.c delete mode 100644 tests/checkout/typechange.c delete mode 100644 tests/cherrypick/bare.c delete mode 100644 tests/cherrypick/workdir.c delete mode 100644 tests/clar.c delete mode 100644 tests/clar.h delete mode 100644 tests/clar/fixtures.h delete mode 100644 tests/clar/fs.h delete mode 100644 tests/clar/print.h delete mode 100644 tests/clar/sandbox.h delete mode 100644 tests/clar/summary.h delete mode 100644 tests/clar_libgit2.c delete mode 100644 tests/clar_libgit2.h delete mode 100644 tests/clar_libgit2_timer.c delete mode 100644 tests/clar_libgit2_timer.h delete mode 100644 tests/clar_libgit2_trace.c delete mode 100644 tests/clar_libgit2_trace.h delete mode 100644 tests/clone/empty.c delete mode 100644 tests/clone/local.c delete mode 100644 tests/clone/nonetwork.c delete mode 100644 tests/clone/transport.c delete mode 100644 tests/commit/commit.c delete mode 100644 tests/commit/parent.c delete mode 100644 tests/commit/parse.c delete mode 100644 tests/commit/signature.c delete mode 100644 tests/commit/write.c delete mode 100644 tests/config/add.c delete mode 100644 tests/config/backend.c delete mode 100644 tests/config/conditionals.c delete mode 100644 tests/config/config_helpers.c delete mode 100644 tests/config/config_helpers.h delete mode 100644 tests/config/configlevel.c delete mode 100644 tests/config/global.c delete mode 100644 tests/config/include.c delete mode 100644 tests/config/memory.c delete mode 100644 tests/config/multivar.c delete mode 100644 tests/config/new.c delete mode 100644 tests/config/read.c delete mode 100644 tests/config/readonly.c delete mode 100644 tests/config/rename.c delete mode 100644 tests/config/snapshot.c delete mode 100644 tests/config/stress.c delete mode 100644 tests/config/validkeyname.c delete mode 100644 tests/config/write.c delete mode 100644 tests/core/array.c delete mode 100644 tests/core/assert.c delete mode 100644 tests/core/bitvec.c delete mode 100644 tests/core/buf.c delete mode 100644 tests/core/copy.c delete mode 100644 tests/core/dirent.c delete mode 100644 tests/core/encoding.c delete mode 100644 tests/core/env.c delete mode 100644 tests/core/errors.c delete mode 100644 tests/core/features.c delete mode 100644 tests/core/filebuf.c delete mode 100644 tests/core/ftruncate.c delete mode 100644 tests/core/futils.c delete mode 100644 tests/core/gitstr.c delete mode 100644 tests/core/hex.c delete mode 100644 tests/core/iconv.c delete mode 100644 tests/core/init.c delete mode 100644 tests/core/integer.c delete mode 100644 tests/core/link.c delete mode 100644 tests/core/memmem.c delete mode 100644 tests/core/mkdir.c delete mode 100644 tests/core/oid.c delete mode 100644 tests/core/oidmap.c delete mode 100644 tests/core/opts.c delete mode 100644 tests/core/path.c delete mode 100644 tests/core/pool.c delete mode 100644 tests/core/posix.c delete mode 100644 tests/core/pqueue.c delete mode 100644 tests/core/qsort.c delete mode 100644 tests/core/regexp.c delete mode 100644 tests/core/rmdir.c delete mode 100644 tests/core/sha1.c delete mode 100644 tests/core/sortedcache.c delete mode 100644 tests/core/stat.c delete mode 100644 tests/core/string.c delete mode 100644 tests/core/strmap.c delete mode 100644 tests/core/strtol.c delete mode 100644 tests/core/structinit.c delete mode 100644 tests/core/useragent.c delete mode 100644 tests/core/utf8.c delete mode 100644 tests/core/vector.c delete mode 100644 tests/core/wildmatch.c delete mode 100644 tests/core/zstream.c delete mode 100644 tests/date/date.c delete mode 100644 tests/date/rfc2822.c delete mode 100644 tests/delta/apply.c delete mode 100644 tests/describe/describe.c delete mode 100644 tests/describe/describe_helpers.c delete mode 100644 tests/describe/describe_helpers.h delete mode 100644 tests/describe/t6120.c delete mode 100644 tests/diff/binary.c delete mode 100644 tests/diff/blob.c delete mode 100644 tests/diff/diff_helpers.c delete mode 100644 tests/diff/diff_helpers.h delete mode 100644 tests/diff/diffiter.c delete mode 100644 tests/diff/drivers.c delete mode 100644 tests/diff/externalmodifications.c delete mode 100644 tests/diff/format_email.c delete mode 100644 tests/diff/index.c delete mode 100644 tests/diff/notify.c delete mode 100644 tests/diff/parse.c delete mode 100644 tests/diff/patch.c delete mode 100644 tests/diff/patchid.c delete mode 100644 tests/diff/pathspec.c delete mode 100644 tests/diff/racediffiter.c delete mode 100644 tests/diff/rename.c delete mode 100644 tests/diff/stats.c delete mode 100644 tests/diff/submodules.c delete mode 100644 tests/diff/tree.c delete mode 100644 tests/diff/workdir.c delete mode 100644 tests/email/create.c delete mode 100644 tests/email/create.c.bak delete mode 100644 tests/fetch/local.c delete mode 100644 tests/fetchhead/fetchhead_data.h delete mode 100644 tests/fetchhead/nonetwork.c delete mode 100644 tests/filter/bare.c delete mode 100644 tests/filter/blob.c delete mode 100644 tests/filter/crlf.c delete mode 100644 tests/filter/crlf.h delete mode 100644 tests/filter/custom.c delete mode 100644 tests/filter/custom_helpers.c delete mode 100644 tests/filter/custom_helpers.h delete mode 100644 tests/filter/file.c delete mode 100644 tests/filter/ident.c delete mode 100644 tests/filter/query.c delete mode 100644 tests/filter/stream.c delete mode 100644 tests/filter/systemattrs.c delete mode 100644 tests/filter/wildcard.c delete mode 100644 tests/generate.py delete mode 100644 tests/graph/ahead_behind.c delete mode 100644 tests/graph/commitgraph.c delete mode 100644 tests/graph/descendant_of.c delete mode 100644 tests/graph/reachable_from_any.c delete mode 100644 tests/headertest.c delete mode 100644 tests/ignore/path.c delete mode 100644 tests/ignore/status.c delete mode 100644 tests/index/add.c delete mode 100644 tests/index/addall.c delete mode 100644 tests/index/bypath.c delete mode 100644 tests/index/cache.c delete mode 100644 tests/index/collision.c delete mode 100644 tests/index/conflicts.c delete mode 100644 tests/index/conflicts.h delete mode 100644 tests/index/crlf.c delete mode 100644 tests/index/filemodes.c delete mode 100644 tests/index/inmemory.c delete mode 100644 tests/index/names.c delete mode 100644 tests/index/nsec.c delete mode 100644 tests/index/racy.c delete mode 100644 tests/index/read_index.c delete mode 100644 tests/index/read_tree.c delete mode 100644 tests/index/rename.c delete mode 100644 tests/index/reuc.c delete mode 100644 tests/index/splitindex.c delete mode 100644 tests/index/stage.c delete mode 100644 tests/index/tests.c delete mode 100644 tests/index/version.c delete mode 100644 tests/iterator/index.c delete mode 100644 tests/iterator/iterator_helpers.c delete mode 100644 tests/iterator/iterator_helpers.h delete mode 100644 tests/iterator/tree.c delete mode 100644 tests/iterator/workdir.c create mode 100644 tests/libgit2/CMakeLists.txt create mode 100644 tests/libgit2/apply/apply_helpers.c create mode 100644 tests/libgit2/apply/apply_helpers.h create mode 100644 tests/libgit2/apply/both.c create mode 100644 tests/libgit2/apply/callbacks.c create mode 100644 tests/libgit2/apply/check.c create mode 100644 tests/libgit2/apply/fromdiff.c create mode 100644 tests/libgit2/apply/fromfile.c create mode 100644 tests/libgit2/apply/index.c create mode 100644 tests/libgit2/apply/partial.c create mode 100644 tests/libgit2/apply/tree.c create mode 100644 tests/libgit2/apply/workdir.c create mode 100644 tests/libgit2/attr/attr_expect.h create mode 100644 tests/libgit2/attr/file.c create mode 100644 tests/libgit2/attr/flags.c create mode 100644 tests/libgit2/attr/lookup.c create mode 100644 tests/libgit2/attr/macro.c create mode 100644 tests/libgit2/attr/repo.c create mode 100644 tests/libgit2/blame/blame_helpers.c create mode 100644 tests/libgit2/blame/blame_helpers.h create mode 100644 tests/libgit2/blame/buffer.c create mode 100644 tests/libgit2/blame/getters.c create mode 100644 tests/libgit2/blame/harder.c create mode 100644 tests/libgit2/blame/simple.c create mode 100644 tests/libgit2/checkout/binaryunicode.c create mode 100644 tests/libgit2/checkout/checkout_helpers.c create mode 100644 tests/libgit2/checkout/checkout_helpers.h create mode 100644 tests/libgit2/checkout/conflict.c create mode 100644 tests/libgit2/checkout/crlf.c create mode 100644 tests/libgit2/checkout/head.c create mode 100644 tests/libgit2/checkout/icase.c create mode 100644 tests/libgit2/checkout/index.c create mode 100644 tests/libgit2/checkout/nasty.c create mode 100644 tests/libgit2/checkout/tree.c create mode 100644 tests/libgit2/checkout/typechange.c create mode 100644 tests/libgit2/cherrypick/bare.c create mode 100644 tests/libgit2/cherrypick/workdir.c create mode 100644 tests/libgit2/clar.c create mode 100644 tests/libgit2/clar.h create mode 100644 tests/libgit2/clar/fixtures.h create mode 100644 tests/libgit2/clar/fs.h create mode 100644 tests/libgit2/clar/print.h create mode 100644 tests/libgit2/clar/sandbox.h create mode 100644 tests/libgit2/clar/summary.h create mode 100644 tests/libgit2/clar_libgit2.c create mode 100644 tests/libgit2/clar_libgit2.h create mode 100644 tests/libgit2/clar_libgit2_timer.c create mode 100644 tests/libgit2/clar_libgit2_timer.h create mode 100644 tests/libgit2/clar_libgit2_trace.c create mode 100644 tests/libgit2/clar_libgit2_trace.h create mode 100644 tests/libgit2/clone/empty.c create mode 100644 tests/libgit2/clone/local.c create mode 100644 tests/libgit2/clone/nonetwork.c create mode 100644 tests/libgit2/clone/transport.c create mode 100644 tests/libgit2/commit/commit.c create mode 100644 tests/libgit2/commit/parent.c create mode 100644 tests/libgit2/commit/parse.c create mode 100644 tests/libgit2/commit/signature.c create mode 100644 tests/libgit2/commit/write.c create mode 100644 tests/libgit2/config/add.c create mode 100644 tests/libgit2/config/backend.c create mode 100644 tests/libgit2/config/conditionals.c create mode 100644 tests/libgit2/config/config_helpers.c create mode 100644 tests/libgit2/config/config_helpers.h create mode 100644 tests/libgit2/config/configlevel.c create mode 100644 tests/libgit2/config/global.c create mode 100644 tests/libgit2/config/include.c create mode 100644 tests/libgit2/config/memory.c create mode 100644 tests/libgit2/config/multivar.c create mode 100644 tests/libgit2/config/new.c create mode 100644 tests/libgit2/config/read.c create mode 100644 tests/libgit2/config/readonly.c create mode 100644 tests/libgit2/config/rename.c create mode 100644 tests/libgit2/config/snapshot.c create mode 100644 tests/libgit2/config/stress.c create mode 100644 tests/libgit2/config/validkeyname.c create mode 100644 tests/libgit2/config/write.c create mode 100644 tests/libgit2/core/array.c create mode 100644 tests/libgit2/core/assert.c create mode 100644 tests/libgit2/core/bitvec.c create mode 100644 tests/libgit2/core/buf.c create mode 100644 tests/libgit2/core/copy.c create mode 100644 tests/libgit2/core/dirent.c create mode 100644 tests/libgit2/core/encoding.c create mode 100644 tests/libgit2/core/env.c create mode 100644 tests/libgit2/core/errors.c create mode 100644 tests/libgit2/core/features.c create mode 100644 tests/libgit2/core/filebuf.c create mode 100644 tests/libgit2/core/ftruncate.c create mode 100644 tests/libgit2/core/futils.c create mode 100644 tests/libgit2/core/gitstr.c create mode 100644 tests/libgit2/core/hex.c create mode 100644 tests/libgit2/core/iconv.c create mode 100644 tests/libgit2/core/init.c create mode 100644 tests/libgit2/core/integer.c create mode 100644 tests/libgit2/core/link.c create mode 100644 tests/libgit2/core/memmem.c create mode 100644 tests/libgit2/core/mkdir.c create mode 100644 tests/libgit2/core/oid.c create mode 100644 tests/libgit2/core/oidmap.c create mode 100644 tests/libgit2/core/opts.c create mode 100644 tests/libgit2/core/path.c create mode 100644 tests/libgit2/core/pool.c create mode 100644 tests/libgit2/core/posix.c create mode 100644 tests/libgit2/core/pqueue.c create mode 100644 tests/libgit2/core/qsort.c create mode 100644 tests/libgit2/core/regexp.c create mode 100644 tests/libgit2/core/rmdir.c create mode 100644 tests/libgit2/core/sha1.c create mode 100644 tests/libgit2/core/sortedcache.c create mode 100644 tests/libgit2/core/stat.c create mode 100644 tests/libgit2/core/string.c create mode 100644 tests/libgit2/core/strmap.c create mode 100644 tests/libgit2/core/strtol.c create mode 100644 tests/libgit2/core/structinit.c create mode 100644 tests/libgit2/core/useragent.c create mode 100644 tests/libgit2/core/utf8.c create mode 100644 tests/libgit2/core/vector.c create mode 100644 tests/libgit2/core/wildmatch.c create mode 100644 tests/libgit2/core/zstream.c create mode 100644 tests/libgit2/date/date.c create mode 100644 tests/libgit2/date/rfc2822.c create mode 100644 tests/libgit2/delta/apply.c create mode 100644 tests/libgit2/describe/describe.c create mode 100644 tests/libgit2/describe/describe_helpers.c create mode 100644 tests/libgit2/describe/describe_helpers.h create mode 100644 tests/libgit2/describe/t6120.c create mode 100644 tests/libgit2/diff/binary.c create mode 100644 tests/libgit2/diff/blob.c create mode 100644 tests/libgit2/diff/diff_helpers.c create mode 100644 tests/libgit2/diff/diff_helpers.h create mode 100644 tests/libgit2/diff/diffiter.c create mode 100644 tests/libgit2/diff/drivers.c create mode 100644 tests/libgit2/diff/externalmodifications.c create mode 100644 tests/libgit2/diff/format_email.c create mode 100644 tests/libgit2/diff/index.c create mode 100644 tests/libgit2/diff/notify.c create mode 100644 tests/libgit2/diff/parse.c create mode 100644 tests/libgit2/diff/patch.c create mode 100644 tests/libgit2/diff/patchid.c create mode 100644 tests/libgit2/diff/pathspec.c create mode 100644 tests/libgit2/diff/racediffiter.c create mode 100644 tests/libgit2/diff/rename.c create mode 100644 tests/libgit2/diff/stats.c create mode 100644 tests/libgit2/diff/submodules.c create mode 100644 tests/libgit2/diff/tree.c create mode 100644 tests/libgit2/diff/workdir.c create mode 100644 tests/libgit2/email/create.c create mode 100644 tests/libgit2/email/create.c.bak create mode 100644 tests/libgit2/fetch/local.c create mode 100644 tests/libgit2/fetchhead/fetchhead_data.h create mode 100644 tests/libgit2/fetchhead/nonetwork.c create mode 100644 tests/libgit2/filter/bare.c create mode 100644 tests/libgit2/filter/blob.c create mode 100644 tests/libgit2/filter/crlf.c create mode 100644 tests/libgit2/filter/crlf.h create mode 100644 tests/libgit2/filter/custom.c create mode 100644 tests/libgit2/filter/custom_helpers.c create mode 100644 tests/libgit2/filter/custom_helpers.h create mode 100644 tests/libgit2/filter/file.c create mode 100644 tests/libgit2/filter/ident.c create mode 100644 tests/libgit2/filter/query.c create mode 100644 tests/libgit2/filter/stream.c create mode 100644 tests/libgit2/filter/systemattrs.c create mode 100644 tests/libgit2/filter/wildcard.c create mode 100644 tests/libgit2/generate.py create mode 100644 tests/libgit2/graph/ahead_behind.c create mode 100644 tests/libgit2/graph/commitgraph.c create mode 100644 tests/libgit2/graph/descendant_of.c create mode 100644 tests/libgit2/graph/reachable_from_any.c create mode 100644 tests/libgit2/headertest.c create mode 100644 tests/libgit2/ignore/path.c create mode 100644 tests/libgit2/ignore/status.c create mode 100644 tests/libgit2/index/add.c create mode 100644 tests/libgit2/index/addall.c create mode 100644 tests/libgit2/index/bypath.c create mode 100644 tests/libgit2/index/cache.c create mode 100644 tests/libgit2/index/collision.c create mode 100644 tests/libgit2/index/conflicts.c create mode 100644 tests/libgit2/index/conflicts.h create mode 100644 tests/libgit2/index/crlf.c create mode 100644 tests/libgit2/index/filemodes.c create mode 100644 tests/libgit2/index/inmemory.c create mode 100644 tests/libgit2/index/names.c create mode 100644 tests/libgit2/index/nsec.c create mode 100644 tests/libgit2/index/racy.c create mode 100644 tests/libgit2/index/read_index.c create mode 100644 tests/libgit2/index/read_tree.c create mode 100644 tests/libgit2/index/rename.c create mode 100644 tests/libgit2/index/reuc.c create mode 100644 tests/libgit2/index/splitindex.c create mode 100644 tests/libgit2/index/stage.c create mode 100644 tests/libgit2/index/tests.c create mode 100644 tests/libgit2/index/version.c create mode 100644 tests/libgit2/iterator/index.c create mode 100644 tests/libgit2/iterator/iterator_helpers.c create mode 100644 tests/libgit2/iterator/iterator_helpers.h create mode 100644 tests/libgit2/iterator/tree.c create mode 100644 tests/libgit2/iterator/workdir.c create mode 100644 tests/libgit2/mailmap/basic.c create mode 100644 tests/libgit2/mailmap/blame.c create mode 100644 tests/libgit2/mailmap/mailmap_testdata.h create mode 100644 tests/libgit2/mailmap/parsing.c create mode 100644 tests/libgit2/main.c create mode 100644 tests/libgit2/merge/analysis.c create mode 100644 tests/libgit2/merge/annotated_commit.c create mode 100644 tests/libgit2/merge/conflict_data.h create mode 100644 tests/libgit2/merge/driver.c create mode 100644 tests/libgit2/merge/files.c create mode 100644 tests/libgit2/merge/merge_helpers.c create mode 100644 tests/libgit2/merge/merge_helpers.h create mode 100644 tests/libgit2/merge/trees/automerge.c create mode 100644 tests/libgit2/merge/trees/commits.c create mode 100644 tests/libgit2/merge/trees/modeconflict.c create mode 100644 tests/libgit2/merge/trees/recursive.c create mode 100644 tests/libgit2/merge/trees/renames.c create mode 100644 tests/libgit2/merge/trees/treediff.c create mode 100644 tests/libgit2/merge/trees/trivial.c create mode 100644 tests/libgit2/merge/trees/whitespace.c create mode 100644 tests/libgit2/merge/workdir/dirty.c create mode 100644 tests/libgit2/merge/workdir/recursive.c create mode 100644 tests/libgit2/merge/workdir/renames.c create mode 100644 tests/libgit2/merge/workdir/setup.c create mode 100644 tests/libgit2/merge/workdir/simple.c create mode 100644 tests/libgit2/merge/workdir/submodules.c create mode 100644 tests/libgit2/merge/workdir/trivial.c create mode 100644 tests/libgit2/message/trailer.c create mode 100644 tests/libgit2/network/cred.c create mode 100644 tests/libgit2/network/fetchlocal.c create mode 100644 tests/libgit2/network/matchhost.c create mode 100644 tests/libgit2/network/refspecs.c create mode 100644 tests/libgit2/network/remote/defaultbranch.c create mode 100644 tests/libgit2/network/remote/delete.c create mode 100644 tests/libgit2/network/remote/isvalidname.c create mode 100644 tests/libgit2/network/remote/local.c create mode 100644 tests/libgit2/network/remote/push.c create mode 100644 tests/libgit2/network/remote/remotes.c create mode 100644 tests/libgit2/network/remote/rename.c create mode 100644 tests/libgit2/network/url/joinpath.c create mode 100644 tests/libgit2/network/url/parse.c create mode 100644 tests/libgit2/network/url/pattern.c create mode 100644 tests/libgit2/network/url/redirect.c create mode 100644 tests/libgit2/network/url/scp.c create mode 100644 tests/libgit2/network/url/valid.c create mode 100644 tests/libgit2/notes/notes.c create mode 100644 tests/libgit2/notes/notesref.c create mode 100644 tests/libgit2/object/blob/filter.c create mode 100644 tests/libgit2/object/blob/fromstream.c create mode 100644 tests/libgit2/object/blob/write.c create mode 100644 tests/libgit2/object/cache.c create mode 100644 tests/libgit2/object/commit/commitstagedfile.c create mode 100644 tests/libgit2/object/commit/parse.c create mode 100644 tests/libgit2/object/lookup.c create mode 100644 tests/libgit2/object/lookupbypath.c create mode 100644 tests/libgit2/object/message.c create mode 100644 tests/libgit2/object/peel.c create mode 100644 tests/libgit2/object/raw/chars.c create mode 100644 tests/libgit2/object/raw/compare.c create mode 100644 tests/libgit2/object/raw/convert.c create mode 100644 tests/libgit2/object/raw/data.h create mode 100644 tests/libgit2/object/raw/fromstr.c create mode 100644 tests/libgit2/object/raw/hash.c create mode 100644 tests/libgit2/object/raw/short.c create mode 100644 tests/libgit2/object/raw/size.c create mode 100644 tests/libgit2/object/raw/type2string.c create mode 100644 tests/libgit2/object/raw/write.c create mode 100644 tests/libgit2/object/shortid.c create mode 100644 tests/libgit2/object/tag/list.c create mode 100644 tests/libgit2/object/tag/parse.c create mode 100644 tests/libgit2/object/tag/peel.c create mode 100644 tests/libgit2/object/tag/read.c create mode 100644 tests/libgit2/object/tag/write.c create mode 100644 tests/libgit2/object/tree/attributes.c create mode 100644 tests/libgit2/object/tree/duplicateentries.c create mode 100644 tests/libgit2/object/tree/frompath.c create mode 100644 tests/libgit2/object/tree/parse.c create mode 100644 tests/libgit2/object/tree/read.c create mode 100644 tests/libgit2/object/tree/update.c create mode 100644 tests/libgit2/object/tree/walk.c create mode 100644 tests/libgit2/object/tree/write.c create mode 100644 tests/libgit2/object/validate.c create mode 100644 tests/libgit2/odb/alternates.c create mode 100644 tests/libgit2/odb/backend/backend_helpers.c create mode 100644 tests/libgit2/odb/backend/backend_helpers.h create mode 100644 tests/libgit2/odb/backend/mempack.c create mode 100644 tests/libgit2/odb/backend/multiple.c create mode 100644 tests/libgit2/odb/backend/nobackend.c create mode 100644 tests/libgit2/odb/backend/nonrefreshing.c create mode 100644 tests/libgit2/odb/backend/refreshing.c create mode 100644 tests/libgit2/odb/backend/simple.c create mode 100644 tests/libgit2/odb/emptyobjects.c create mode 100644 tests/libgit2/odb/foreach.c create mode 100644 tests/libgit2/odb/freshen.c create mode 100644 tests/libgit2/odb/largefiles.c create mode 100644 tests/libgit2/odb/loose.c create mode 100644 tests/libgit2/odb/loose_data.h create mode 100644 tests/libgit2/odb/mixed.c create mode 100644 tests/libgit2/odb/pack_data.h create mode 100644 tests/libgit2/odb/pack_data_one.h create mode 100644 tests/libgit2/odb/packed.c create mode 100644 tests/libgit2/odb/packed_one.c create mode 100644 tests/libgit2/odb/sorting.c create mode 100644 tests/libgit2/odb/streamwrite.c create mode 100644 tests/libgit2/online/badssl.c create mode 100644 tests/libgit2/online/clone.c create mode 100644 tests/libgit2/online/customcert.c create mode 100644 tests/libgit2/online/fetch.c create mode 100644 tests/libgit2/online/fetchhead.c create mode 100644 tests/libgit2/online/push.c create mode 100644 tests/libgit2/online/push_util.c create mode 100644 tests/libgit2/online/push_util.h create mode 100644 tests/libgit2/online/remotes.c create mode 100644 tests/libgit2/pack/filelimit.c create mode 100644 tests/libgit2/pack/indexer.c create mode 100644 tests/libgit2/pack/midx.c create mode 100644 tests/libgit2/pack/packbuilder.c create mode 100644 tests/libgit2/pack/sharing.c create mode 100644 tests/libgit2/pack/threadsafety.c create mode 100644 tests/libgit2/patch/parse.c create mode 100644 tests/libgit2/patch/patch_common.h create mode 100644 tests/libgit2/patch/print.c create mode 100644 tests/libgit2/path/core.c create mode 100644 tests/libgit2/path/dotgit.c create mode 100644 tests/libgit2/path/win32.c create mode 100644 tests/libgit2/perf/helper__perf__do_merge.c create mode 100644 tests/libgit2/perf/helper__perf__do_merge.h create mode 100644 tests/libgit2/perf/helper__perf__timer.c create mode 100644 tests/libgit2/perf/helper__perf__timer.h create mode 100644 tests/libgit2/perf/merge.c create mode 100644 tests/libgit2/precompiled.c create mode 100644 tests/libgit2/precompiled.h create mode 100644 tests/libgit2/rebase/abort.c create mode 100644 tests/libgit2/rebase/inmemory.c create mode 100644 tests/libgit2/rebase/iterator.c create mode 100644 tests/libgit2/rebase/merge.c create mode 100644 tests/libgit2/rebase/setup.c create mode 100644 tests/libgit2/rebase/sign.c create mode 100644 tests/libgit2/rebase/submodule.c create mode 100644 tests/libgit2/refs/basic.c create mode 100644 tests/libgit2/refs/branches/checkedout.c create mode 100644 tests/libgit2/refs/branches/create.c create mode 100644 tests/libgit2/refs/branches/delete.c create mode 100644 tests/libgit2/refs/branches/ishead.c create mode 100644 tests/libgit2/refs/branches/iterator.c create mode 100644 tests/libgit2/refs/branches/lookup.c create mode 100644 tests/libgit2/refs/branches/move.c create mode 100644 tests/libgit2/refs/branches/name.c create mode 100644 tests/libgit2/refs/branches/remote.c create mode 100644 tests/libgit2/refs/branches/upstream.c create mode 100644 tests/libgit2/refs/branches/upstreamname.c create mode 100644 tests/libgit2/refs/crashes.c create mode 100644 tests/libgit2/refs/create.c create mode 100644 tests/libgit2/refs/delete.c create mode 100644 tests/libgit2/refs/dup.c create mode 100644 tests/libgit2/refs/foreachglob.c create mode 100644 tests/libgit2/refs/isvalidname.c create mode 100644 tests/libgit2/refs/iterator.c create mode 100644 tests/libgit2/refs/list.c create mode 100644 tests/libgit2/refs/listall.c create mode 100644 tests/libgit2/refs/lookup.c create mode 100644 tests/libgit2/refs/namespaces.c create mode 100644 tests/libgit2/refs/normalize.c create mode 100644 tests/libgit2/refs/overwrite.c create mode 100644 tests/libgit2/refs/pack.c create mode 100644 tests/libgit2/refs/peel.c create mode 100644 tests/libgit2/refs/races.c create mode 100644 tests/libgit2/refs/read.c create mode 100644 tests/libgit2/refs/ref_helpers.c create mode 100644 tests/libgit2/refs/ref_helpers.h create mode 100644 tests/libgit2/refs/reflog/drop.c create mode 100644 tests/libgit2/refs/reflog/messages.c create mode 100644 tests/libgit2/refs/reflog/reflog.c create mode 100644 tests/libgit2/refs/reflog/reflog_helpers.c create mode 100644 tests/libgit2/refs/reflog/reflog_helpers.h create mode 100644 tests/libgit2/refs/rename.c create mode 100644 tests/libgit2/refs/revparse.c create mode 100644 tests/libgit2/refs/setter.c create mode 100644 tests/libgit2/refs/shorthand.c create mode 100644 tests/libgit2/refs/tags/name.c create mode 100644 tests/libgit2/refs/transactions.c create mode 100644 tests/libgit2/refs/unicode.c create mode 100644 tests/libgit2/refs/update.c create mode 100644 tests/libgit2/remote/create.c create mode 100644 tests/libgit2/remote/fetch.c create mode 100644 tests/libgit2/remote/httpproxy.c create mode 100644 tests/libgit2/remote/insteadof.c create mode 100644 tests/libgit2/remote/list.c create mode 100644 tests/libgit2/repo/config.c create mode 100644 tests/libgit2/repo/discover.c create mode 100644 tests/libgit2/repo/env.c create mode 100644 tests/libgit2/repo/extensions.c create mode 100644 tests/libgit2/repo/getters.c create mode 100644 tests/libgit2/repo/hashfile.c create mode 100644 tests/libgit2/repo/head.c create mode 100644 tests/libgit2/repo/headtree.c create mode 100644 tests/libgit2/repo/init.c create mode 100644 tests/libgit2/repo/message.c create mode 100644 tests/libgit2/repo/new.c create mode 100644 tests/libgit2/repo/open.c create mode 100644 tests/libgit2/repo/pathspec.c create mode 100644 tests/libgit2/repo/repo_helpers.c create mode 100644 tests/libgit2/repo/repo_helpers.h create mode 100644 tests/libgit2/repo/reservedname.c create mode 100644 tests/libgit2/repo/setters.c create mode 100644 tests/libgit2/repo/shallow.c create mode 100644 tests/libgit2/repo/state.c create mode 100644 tests/libgit2/repo/template.c create mode 100644 tests/libgit2/reset/default.c create mode 100644 tests/libgit2/reset/hard.c create mode 100644 tests/libgit2/reset/mixed.c create mode 100644 tests/libgit2/reset/reset_helpers.c create mode 100644 tests/libgit2/reset/reset_helpers.h create mode 100644 tests/libgit2/reset/soft.c create mode 100644 tests/libgit2/revert/bare.c create mode 100644 tests/libgit2/revert/rename.c create mode 100644 tests/libgit2/revert/workdir.c create mode 100644 tests/libgit2/revwalk/basic.c create mode 100644 tests/libgit2/revwalk/hidecb.c create mode 100644 tests/libgit2/revwalk/mergebase.c create mode 100644 tests/libgit2/revwalk/signatureparsing.c create mode 100644 tests/libgit2/revwalk/simplify.c create mode 100644 tests/libgit2/stash/apply.c create mode 100644 tests/libgit2/stash/drop.c create mode 100644 tests/libgit2/stash/foreach.c create mode 100644 tests/libgit2/stash/save.c create mode 100644 tests/libgit2/stash/stash_helpers.c create mode 100644 tests/libgit2/stash/stash_helpers.h create mode 100644 tests/libgit2/stash/submodules.c create mode 100644 tests/libgit2/status/renames.c create mode 100644 tests/libgit2/status/single.c create mode 100644 tests/libgit2/status/status_data.h create mode 100644 tests/libgit2/status/status_helpers.c create mode 100644 tests/libgit2/status/status_helpers.h create mode 100644 tests/libgit2/status/submodules.c create mode 100644 tests/libgit2/status/worktree.c create mode 100644 tests/libgit2/status/worktree_init.c create mode 100644 tests/libgit2/str/basic.c create mode 100644 tests/libgit2/str/oom.c create mode 100644 tests/libgit2/str/percent.c create mode 100644 tests/libgit2/str/quote.c create mode 100644 tests/libgit2/str/splice.c create mode 100644 tests/libgit2/stream/deprecated.c create mode 100644 tests/libgit2/stream/registration.c create mode 100644 tests/libgit2/stress/diff.c create mode 100644 tests/libgit2/submodule/add.c create mode 100644 tests/libgit2/submodule/escape.c create mode 100644 tests/libgit2/submodule/init.c create mode 100644 tests/libgit2/submodule/inject_option.c create mode 100644 tests/libgit2/submodule/lookup.c create mode 100644 tests/libgit2/submodule/modify.c create mode 100644 tests/libgit2/submodule/nosubs.c create mode 100644 tests/libgit2/submodule/open.c create mode 100644 tests/libgit2/submodule/repository_init.c create mode 100644 tests/libgit2/submodule/status.c create mode 100644 tests/libgit2/submodule/submodule_helpers.c create mode 100644 tests/libgit2/submodule/submodule_helpers.h create mode 100644 tests/libgit2/submodule/update.c create mode 100644 tests/libgit2/threads/atomic.c create mode 100644 tests/libgit2/threads/basic.c create mode 100644 tests/libgit2/threads/diff.c create mode 100644 tests/libgit2/threads/iterator.c create mode 100644 tests/libgit2/threads/refdb.c create mode 100644 tests/libgit2/threads/thread_helpers.c create mode 100644 tests/libgit2/threads/thread_helpers.h create mode 100644 tests/libgit2/threads/tlsdata.c create mode 100644 tests/libgit2/trace/trace.c create mode 100644 tests/libgit2/trace/windows/stacktrace.c create mode 100644 tests/libgit2/transport/register.c create mode 100644 tests/libgit2/transports/smart/packet.c create mode 100644 tests/libgit2/valgrind-supp-mac.txt create mode 100644 tests/libgit2/win32/forbidden.c create mode 100644 tests/libgit2/win32/longpath.c create mode 100644 tests/libgit2/win32/systemdir.c create mode 100644 tests/libgit2/worktree/bare.c create mode 100644 tests/libgit2/worktree/config.c create mode 100644 tests/libgit2/worktree/merge.c create mode 100644 tests/libgit2/worktree/open.c create mode 100644 tests/libgit2/worktree/reflog.c create mode 100644 tests/libgit2/worktree/refs.c create mode 100644 tests/libgit2/worktree/repository.c create mode 100644 tests/libgit2/worktree/submodule.c create mode 100644 tests/libgit2/worktree/worktree.c create mode 100644 tests/libgit2/worktree/worktree_helpers.c create mode 100644 tests/libgit2/worktree/worktree_helpers.h delete mode 100644 tests/mailmap/basic.c delete mode 100644 tests/mailmap/blame.c delete mode 100644 tests/mailmap/mailmap_testdata.h delete mode 100644 tests/mailmap/parsing.c delete mode 100644 tests/main.c delete mode 100644 tests/merge/analysis.c delete mode 100644 tests/merge/annotated_commit.c delete mode 100644 tests/merge/conflict_data.h delete mode 100644 tests/merge/driver.c delete mode 100644 tests/merge/files.c delete mode 100644 tests/merge/merge_helpers.c delete mode 100644 tests/merge/merge_helpers.h delete mode 100644 tests/merge/trees/automerge.c delete mode 100644 tests/merge/trees/commits.c delete mode 100644 tests/merge/trees/modeconflict.c delete mode 100644 tests/merge/trees/recursive.c delete mode 100644 tests/merge/trees/renames.c delete mode 100644 tests/merge/trees/treediff.c delete mode 100644 tests/merge/trees/trivial.c delete mode 100644 tests/merge/trees/whitespace.c delete mode 100644 tests/merge/workdir/dirty.c delete mode 100644 tests/merge/workdir/recursive.c delete mode 100644 tests/merge/workdir/renames.c delete mode 100644 tests/merge/workdir/setup.c delete mode 100644 tests/merge/workdir/simple.c delete mode 100644 tests/merge/workdir/submodules.c delete mode 100644 tests/merge/workdir/trivial.c delete mode 100644 tests/message/trailer.c delete mode 100644 tests/network/cred.c delete mode 100644 tests/network/fetchlocal.c delete mode 100644 tests/network/matchhost.c delete mode 100644 tests/network/refspecs.c delete mode 100644 tests/network/remote/defaultbranch.c delete mode 100644 tests/network/remote/delete.c delete mode 100644 tests/network/remote/isvalidname.c delete mode 100644 tests/network/remote/local.c delete mode 100644 tests/network/remote/push.c delete mode 100644 tests/network/remote/remotes.c delete mode 100644 tests/network/remote/rename.c delete mode 100644 tests/network/url/joinpath.c delete mode 100644 tests/network/url/parse.c delete mode 100644 tests/network/url/pattern.c delete mode 100644 tests/network/url/redirect.c delete mode 100644 tests/network/url/scp.c delete mode 100644 tests/network/url/valid.c delete mode 100644 tests/notes/notes.c delete mode 100644 tests/notes/notesref.c delete mode 100644 tests/object/blob/filter.c delete mode 100644 tests/object/blob/fromstream.c delete mode 100644 tests/object/blob/write.c delete mode 100644 tests/object/cache.c delete mode 100644 tests/object/commit/commitstagedfile.c delete mode 100644 tests/object/commit/parse.c delete mode 100644 tests/object/lookup.c delete mode 100644 tests/object/lookupbypath.c delete mode 100644 tests/object/message.c delete mode 100644 tests/object/peel.c delete mode 100644 tests/object/raw/chars.c delete mode 100644 tests/object/raw/compare.c delete mode 100644 tests/object/raw/convert.c delete mode 100644 tests/object/raw/data.h delete mode 100644 tests/object/raw/fromstr.c delete mode 100644 tests/object/raw/hash.c delete mode 100644 tests/object/raw/short.c delete mode 100644 tests/object/raw/size.c delete mode 100644 tests/object/raw/type2string.c delete mode 100644 tests/object/raw/write.c delete mode 100644 tests/object/shortid.c delete mode 100644 tests/object/tag/list.c delete mode 100644 tests/object/tag/parse.c delete mode 100644 tests/object/tag/peel.c delete mode 100644 tests/object/tag/read.c delete mode 100644 tests/object/tag/write.c delete mode 100644 tests/object/tree/attributes.c delete mode 100644 tests/object/tree/duplicateentries.c delete mode 100644 tests/object/tree/frompath.c delete mode 100644 tests/object/tree/parse.c delete mode 100644 tests/object/tree/read.c delete mode 100644 tests/object/tree/update.c delete mode 100644 tests/object/tree/walk.c delete mode 100644 tests/object/tree/write.c delete mode 100644 tests/object/validate.c delete mode 100644 tests/odb/alternates.c delete mode 100644 tests/odb/backend/backend_helpers.c delete mode 100644 tests/odb/backend/backend_helpers.h delete mode 100644 tests/odb/backend/mempack.c delete mode 100644 tests/odb/backend/multiple.c delete mode 100644 tests/odb/backend/nobackend.c delete mode 100644 tests/odb/backend/nonrefreshing.c delete mode 100644 tests/odb/backend/refreshing.c delete mode 100644 tests/odb/backend/simple.c delete mode 100644 tests/odb/emptyobjects.c delete mode 100644 tests/odb/foreach.c delete mode 100644 tests/odb/freshen.c delete mode 100644 tests/odb/largefiles.c delete mode 100644 tests/odb/loose.c delete mode 100644 tests/odb/loose_data.h delete mode 100644 tests/odb/mixed.c delete mode 100644 tests/odb/pack_data.h delete mode 100644 tests/odb/pack_data_one.h delete mode 100644 tests/odb/packed.c delete mode 100644 tests/odb/packed_one.c delete mode 100644 tests/odb/sorting.c delete mode 100644 tests/odb/streamwrite.c delete mode 100644 tests/online/badssl.c delete mode 100644 tests/online/clone.c delete mode 100644 tests/online/customcert.c delete mode 100644 tests/online/fetch.c delete mode 100644 tests/online/fetchhead.c delete mode 100644 tests/online/push.c delete mode 100644 tests/online/push_util.c delete mode 100644 tests/online/push_util.h delete mode 100644 tests/online/remotes.c delete mode 100644 tests/pack/filelimit.c delete mode 100644 tests/pack/indexer.c delete mode 100644 tests/pack/midx.c delete mode 100644 tests/pack/packbuilder.c delete mode 100644 tests/pack/sharing.c delete mode 100644 tests/pack/threadsafety.c delete mode 100644 tests/patch/parse.c delete mode 100644 tests/patch/patch_common.h delete mode 100644 tests/patch/print.c delete mode 100644 tests/path/core.c delete mode 100644 tests/path/dotgit.c delete mode 100644 tests/path/win32.c delete mode 100644 tests/perf/helper__perf__do_merge.c delete mode 100644 tests/perf/helper__perf__do_merge.h delete mode 100644 tests/perf/helper__perf__timer.c delete mode 100644 tests/perf/helper__perf__timer.h delete mode 100644 tests/perf/merge.c delete mode 100644 tests/precompiled.c delete mode 100644 tests/precompiled.h delete mode 100644 tests/rebase/abort.c delete mode 100644 tests/rebase/inmemory.c delete mode 100644 tests/rebase/iterator.c delete mode 100644 tests/rebase/merge.c delete mode 100644 tests/rebase/setup.c delete mode 100644 tests/rebase/sign.c delete mode 100644 tests/rebase/submodule.c delete mode 100644 tests/refs/basic.c delete mode 100644 tests/refs/branches/checkedout.c delete mode 100644 tests/refs/branches/create.c delete mode 100644 tests/refs/branches/delete.c delete mode 100644 tests/refs/branches/ishead.c delete mode 100644 tests/refs/branches/iterator.c delete mode 100644 tests/refs/branches/lookup.c delete mode 100644 tests/refs/branches/move.c delete mode 100644 tests/refs/branches/name.c delete mode 100644 tests/refs/branches/remote.c delete mode 100644 tests/refs/branches/upstream.c delete mode 100644 tests/refs/branches/upstreamname.c delete mode 100644 tests/refs/crashes.c delete mode 100644 tests/refs/create.c delete mode 100644 tests/refs/delete.c delete mode 100644 tests/refs/dup.c delete mode 100644 tests/refs/foreachglob.c delete mode 100644 tests/refs/isvalidname.c delete mode 100644 tests/refs/iterator.c delete mode 100644 tests/refs/list.c delete mode 100644 tests/refs/listall.c delete mode 100644 tests/refs/lookup.c delete mode 100644 tests/refs/namespaces.c delete mode 100644 tests/refs/normalize.c delete mode 100644 tests/refs/overwrite.c delete mode 100644 tests/refs/pack.c delete mode 100644 tests/refs/peel.c delete mode 100644 tests/refs/races.c delete mode 100644 tests/refs/read.c delete mode 100644 tests/refs/ref_helpers.c delete mode 100644 tests/refs/ref_helpers.h delete mode 100644 tests/refs/reflog/drop.c delete mode 100644 tests/refs/reflog/messages.c delete mode 100644 tests/refs/reflog/reflog.c delete mode 100644 tests/refs/reflog/reflog_helpers.c delete mode 100644 tests/refs/reflog/reflog_helpers.h delete mode 100644 tests/refs/rename.c delete mode 100644 tests/refs/revparse.c delete mode 100644 tests/refs/setter.c delete mode 100644 tests/refs/shorthand.c delete mode 100644 tests/refs/tags/name.c delete mode 100644 tests/refs/transactions.c delete mode 100644 tests/refs/unicode.c delete mode 100644 tests/refs/update.c delete mode 100644 tests/remote/create.c delete mode 100644 tests/remote/fetch.c delete mode 100644 tests/remote/httpproxy.c delete mode 100644 tests/remote/insteadof.c delete mode 100644 tests/remote/list.c delete mode 100644 tests/repo/config.c delete mode 100644 tests/repo/discover.c delete mode 100644 tests/repo/env.c delete mode 100644 tests/repo/extensions.c delete mode 100644 tests/repo/getters.c delete mode 100644 tests/repo/hashfile.c delete mode 100644 tests/repo/head.c delete mode 100644 tests/repo/headtree.c delete mode 100644 tests/repo/init.c delete mode 100644 tests/repo/message.c delete mode 100644 tests/repo/new.c delete mode 100644 tests/repo/open.c delete mode 100644 tests/repo/pathspec.c delete mode 100644 tests/repo/repo_helpers.c delete mode 100644 tests/repo/repo_helpers.h delete mode 100644 tests/repo/reservedname.c delete mode 100644 tests/repo/setters.c delete mode 100644 tests/repo/shallow.c delete mode 100644 tests/repo/state.c delete mode 100644 tests/repo/template.c delete mode 100644 tests/reset/default.c delete mode 100644 tests/reset/hard.c delete mode 100644 tests/reset/mixed.c delete mode 100644 tests/reset/reset_helpers.c delete mode 100644 tests/reset/reset_helpers.h delete mode 100644 tests/reset/soft.c delete mode 100644 tests/revert/bare.c delete mode 100644 tests/revert/rename.c delete mode 100644 tests/revert/workdir.c delete mode 100644 tests/revwalk/basic.c delete mode 100644 tests/revwalk/hidecb.c delete mode 100644 tests/revwalk/mergebase.c delete mode 100644 tests/revwalk/signatureparsing.c delete mode 100644 tests/revwalk/simplify.c delete mode 100644 tests/stash/apply.c delete mode 100644 tests/stash/drop.c delete mode 100644 tests/stash/foreach.c delete mode 100644 tests/stash/save.c delete mode 100644 tests/stash/stash_helpers.c delete mode 100644 tests/stash/stash_helpers.h delete mode 100644 tests/stash/submodules.c delete mode 100644 tests/status/renames.c delete mode 100644 tests/status/single.c delete mode 100644 tests/status/status_data.h delete mode 100644 tests/status/status_helpers.c delete mode 100644 tests/status/status_helpers.h delete mode 100644 tests/status/submodules.c delete mode 100644 tests/status/worktree.c delete mode 100644 tests/status/worktree_init.c delete mode 100644 tests/str/basic.c delete mode 100644 tests/str/oom.c delete mode 100644 tests/str/percent.c delete mode 100644 tests/str/quote.c delete mode 100644 tests/str/splice.c delete mode 100644 tests/stream/deprecated.c delete mode 100644 tests/stream/registration.c delete mode 100644 tests/stress/diff.c delete mode 100644 tests/submodule/add.c delete mode 100644 tests/submodule/escape.c delete mode 100644 tests/submodule/init.c delete mode 100644 tests/submodule/inject_option.c delete mode 100644 tests/submodule/lookup.c delete mode 100644 tests/submodule/modify.c delete mode 100644 tests/submodule/nosubs.c delete mode 100644 tests/submodule/open.c delete mode 100644 tests/submodule/repository_init.c delete mode 100644 tests/submodule/status.c delete mode 100644 tests/submodule/submodule_helpers.c delete mode 100644 tests/submodule/submodule_helpers.h delete mode 100644 tests/submodule/update.c delete mode 100644 tests/threads/atomic.c delete mode 100644 tests/threads/basic.c delete mode 100644 tests/threads/diff.c delete mode 100644 tests/threads/iterator.c delete mode 100644 tests/threads/refdb.c delete mode 100644 tests/threads/thread_helpers.c delete mode 100644 tests/threads/thread_helpers.h delete mode 100644 tests/threads/tlsdata.c delete mode 100644 tests/trace/trace.c delete mode 100644 tests/trace/windows/stacktrace.c delete mode 100644 tests/transport/register.c delete mode 100644 tests/transports/smart/packet.c delete mode 100644 tests/valgrind-supp-mac.txt delete mode 100644 tests/win32/forbidden.c delete mode 100644 tests/win32/longpath.c delete mode 100644 tests/win32/systemdir.c delete mode 100644 tests/worktree/bare.c delete mode 100644 tests/worktree/config.c delete mode 100644 tests/worktree/merge.c delete mode 100644 tests/worktree/open.c delete mode 100644 tests/worktree/reflog.c delete mode 100644 tests/worktree/refs.c delete mode 100644 tests/worktree/repository.c delete mode 100644 tests/worktree/submodule.c delete mode 100644 tests/worktree/worktree.c delete mode 100644 tests/worktree/worktree_helpers.c delete mode 100644 tests/worktree/worktree_helpers.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bbb724057..8b0d7f443 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -144,8 +144,8 @@ add_feature_info(threadsafe USE_THREADS "threadsafe support") # ntlmclient if(USE_NTLMCLIENT) set(GIT_NTLM 1) - add_subdirectory("${libgit2_SOURCE_DIR}/deps/ntlmclient" "${libgit2_BINARY_DIR}/deps/ntlmclient") - list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${libgit2_SOURCE_DIR}/deps/ntlmclient") + add_subdirectory("${PROJECT_SOURCE_DIR}/deps/ntlmclient" "${PROJECT_BINARY_DIR}/deps/ntlmclient") + list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/ntlmclient") list(APPEND LIBGIT2_DEPENDENCY_OBJECTS "$") endif() add_feature_info(ntlmclient GIT_NTLM "NTLM authentication support for Unix") diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 33dfd0ae8..d17f52589 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,98 +1,4 @@ -# tests: the unit and integration tests for libgit2 +# The main libgit2 tests tree: this CMakeLists.txt includes the +# subprojects that make up core libgit2 support. -set(Python_ADDITIONAL_VERSIONS 3 2.7) -find_package(PythonInterp) - -if(NOT PYTHONINTERP_FOUND) - message(FATAL_ERROR "Could not find a python interpreter, which is needed to build the tests. " - "Make sure python is available, or pass -DBUILD_TESTS=OFF to skip building the tests") -ENDIF() - -set(CLAR_FIXTURES "${CMAKE_CURRENT_SOURCE_DIR}/resources/") -set(CLAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}") -add_definitions(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\") -add_definitions(-DCLAR_TMPDIR=\"libgit2_tests\") -add_definitions(-DCLAR_WIN32_LONGPATHS) -add_definitions(-D_FILE_OFFSET_BITS=64) - -# Ensure that we do not use deprecated functions internally -add_definitions(-DGIT_DEPRECATE_HARD) - -set(TEST_INCLUDES "${CLAR_PATH}" "${CMAKE_CURRENT_BINARY_DIR}") -file(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c ${CLAR_PATH}/*/*.h) -set(SRC_CLAR "main.c" "clar_libgit2.c" "clar_libgit2_trace.c" "clar_libgit2_timer.c" "clar.c") - -if(MSVC_IDE) - list(APPEND SRC_CLAR "precompiled.c") -endif() - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/clar.suite ${CMAKE_CURRENT_BINARY_DIR}/clar_suite.h - COMMAND ${PYTHON_EXECUTABLE} generate.py -o "${CMAKE_CURRENT_BINARY_DIR}" -f -xonline -xstress -xperf . - DEPENDS ${SRC_TEST} - WORKING_DIRECTORY ${CLAR_PATH} -) - -set_source_files_properties( - ${CLAR_PATH}/clar.c - PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/clar.suite) - -add_executable(libgit2_tests ${SRC_CLAR} ${SRC_TEST} ${LIBGIT2_OBJECTS}) - -set_target_properties(libgit2_tests PROPERTIES C_STANDARD 90) -set_target_properties(libgit2_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) - -target_include_directories(libgit2_tests PRIVATE ${TEST_INCLUDES} ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES}) -target_include_directories(libgit2_tests SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) -target_link_libraries(libgit2_tests ${LIBGIT2_SYSTEM_LIBS}) - -ide_split_sources(libgit2_tests) - -# -# Old versions of gcc require us to declare our test functions; don't do -# this on newer compilers to avoid unnecessary recompilation. -# -if(CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) - target_compile_options(libgit2_tests PRIVATE -include "clar_suite.h") -endif() - -if(MSVC_IDE) - # Precompiled headers - set_target_properties(libgit2_tests PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") - set_source_files_properties("precompiled.c" COMPILE_FLAGS "/Ycprecompiled.h") -endif() - -function(ADD_CLAR_TEST name) - if(NOT USE_LEAK_CHECKER STREQUAL "OFF") - add_test(${name} "${PROJECT_SOURCE_DIR}/script/${USE_LEAK_CHECKER}.sh" "${PROJECT_BINARY_DIR}/libgit2_tests" ${ARGN}) - else() - add_test(${name} "${PROJECT_BINARY_DIR}/libgit2_tests" ${ARGN}) - endif() -endfunction(ADD_CLAR_TEST) - -add_clar_test(offline -v -xonline) -add_clar_test(invasive -v -score::ftruncate -sfilter::stream::bigfile -sodb::largefiles -siterator::workdir::filesystem_gunk -srepo::init -srepo::init::at_filesystem_root) -add_clar_test(online -v -sonline -xonline::customcert) -add_clar_test(online_customcert -v -sonline::customcert) -add_clar_test(gitdaemon -v -sonline::push) -add_clar_test(ssh -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths -sonline::clone::path_whitespace_ssh) -add_clar_test(proxy -v -sonline::clone::proxy) -add_clar_test(auth_clone -v -sonline::clone::cred) -add_clar_test(auth_clone_and_push -v -sonline::clone::push -sonline::push) - -# -# Header file validation project: ensure that we do not publish any sloppy -# definitions in our headers and that a consumer can include -# even when they have aggressive C90 warnings enabled. -# - -add_executable(headertest headertest.c) -set_target_properties(headertest PROPERTIES C_STANDARD 90) -set_target_properties(headertest PROPERTIES C_EXTENSIONS OFF) -target_include_directories(headertest PRIVATE ${LIBGIT2_INCLUDES}) - -if (MSVC) - target_compile_options(headertest PUBLIC /W4 /WX) -else() - target_compile_options(headertest PUBLIC -Wall -Wextra -pedantic -Werror) -endif() +add_subdirectory(libgit2) diff --git a/tests/README.md b/tests/README.md index 4369a8f33..68c2788ba 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,33 +1,49 @@ -Writing Clar tests for libgit2 -============================== +# libgit2 tests -For information on the Clar testing framework and a detailed introduction -please visit: +These are the unit and integration tests for the libgit2 projects. -https://github.com/vmg/clar +* `libgit2` + These tests exercise the core git functionality in libgit2 itself. +* `resources` + These are the resources for the tests, including files and git + repositories. +## Writing tests for libgit2 -* Write your modules and tests. Use good, meaningful names. +libgit2 uses the [clar test framework](http://github.com/clar-test/clar), a +C testing framework. -* Make sure you actually build the tests by setting: +The best resources for learning clar are [clar itself](https://github.com/clar-test/clar) +and the existing tests within libgit2. In general: - cmake -DBUILD_TESTS=ON build/ +* If you place a `.c` file into a test directory, it is eligible to contain +test cases. +* The function name for your test is important; test function names begin + with `test_`, followed by the folder path (underscore separated), two + underscores as a delimiter, then the test name. For example, a file + `merge/analysis.c` may contain a test `uptodate`: -* Test: + ``` + void test_merge_analysis__uptodate(void) + { + ... + } + ``` - ./build/libgit2_tests +* You can run an individual test by passing `-s` to the test runner. Tests + are referred to by their function names; for example, the function + `test_merge_analysis__uptodate` is referred to as `merge::analysis::uptodate`. + To run only that function you can use the `-s` option on the test runner: -* Make sure everything is fine. + ``` + libgit2_tests -smerge::analysis::uptodate + ``` -* Send your pull request. That's it. - - -Memory leak checks ------------------- +## Memory leak checking These are automatically run as part of CI, but if you want to check locally: -#### Linux +### Linux Uses [`valgrind`](http://www.valgrind.org/): @@ -38,7 +54,7 @@ $ valgrind --leak-check=full --show-reachable=yes --num-callers=50 --suppression ./libgit2_tests ``` -#### macOS +### macOS Uses [`leaks`](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/FindingLeaks.html), which requires XCode installed: @@ -46,3 +62,13 @@ Uses [`leaks`](https://developer.apple.com/library/archive/documentation/Perform $ MallocStackLogging=1 MallocScribble=1 MallocLogFile=/dev/null CLAR_AT_EXIT="leaks -quiet \$PPID" \ ./libgit2_tests ``` + +### Windows + +Build with the `WIN32_LEAKCHECK` option: + +```console +$ cmake -DBUILD_TESTS=ON -DWIN32_LEAKCHECK=ON .. +$ cmake --build . +$ ./libgit2_tests +``` diff --git a/tests/apply/apply_helpers.c b/tests/apply/apply_helpers.c deleted file mode 100644 index 91cc51a71..000000000 --- a/tests/apply/apply_helpers.c +++ /dev/null @@ -1,135 +0,0 @@ -#include "clar_libgit2.h" -#include "apply_helpers.h" - -struct iterator_compare_data { - struct merge_index_entry *expected; - size_t cnt; - size_t idx; -}; - -static int iterator_compare(const git_index_entry *entry, void *_data) -{ - git_oid expected_id; - - struct iterator_compare_data *data = (struct iterator_compare_data *)_data; - - cl_assert_equal_i(GIT_INDEX_ENTRY_STAGE(entry), data->expected[data->idx].stage); - cl_git_pass(git_oid_fromstr(&expected_id, data->expected[data->idx].oid_str)); - cl_assert_equal_oid(&entry->id, &expected_id); - cl_assert_equal_i(entry->mode, data->expected[data->idx].mode); - cl_assert_equal_s(entry->path, data->expected[data->idx].path); - - if (data->idx >= data->cnt) - return -1; - - data->idx++; - - return 0; -} - -void validate_apply_workdir( - git_repository *repo, - struct merge_index_entry *workdir_entries, - size_t workdir_cnt) -{ - git_index *index; - git_iterator *iterator; - git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; - struct iterator_compare_data data = { workdir_entries, workdir_cnt }; - - opts.flags |= GIT_ITERATOR_INCLUDE_HASH; - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_iterator_for_workdir(&iterator, repo, index, NULL, &opts)); - - cl_git_pass(git_iterator_foreach(iterator, iterator_compare, &data)); - cl_assert_equal_i(data.idx, data.cnt); - - git_iterator_free(iterator); - git_index_free(index); -} - -void validate_apply_index( - git_repository *repo, - struct merge_index_entry *index_entries, - size_t index_cnt) -{ - git_index *index; - git_iterator *iterator; - struct iterator_compare_data data = { index_entries, index_cnt }; - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_iterator_for_index(&iterator, repo, index, NULL)); - - cl_git_pass(git_iterator_foreach(iterator, iterator_compare, &data)); - cl_assert_equal_i(data.idx, data.cnt); - - git_iterator_free(iterator); - git_index_free(index); -} - -static int iterator_eq(const git_index_entry **entry, void *_data) -{ - GIT_UNUSED(_data); - - if (!entry[0] || !entry[1]) - return -1; - - cl_assert_equal_i(GIT_INDEX_ENTRY_STAGE(entry[0]), GIT_INDEX_ENTRY_STAGE(entry[1])); - cl_assert_equal_oid(&entry[0]->id, &entry[1]->id); - cl_assert_equal_i(entry[0]->mode, entry[1]->mode); - cl_assert_equal_s(entry[0]->path, entry[1]->path); - - return 0; -} - -void validate_index_unchanged(git_repository *repo) -{ - git_tree *head; - git_index *index; - git_iterator *head_iterator, *index_iterator, *iterators[2]; - - cl_git_pass(git_repository_head_tree(&head, repo)); - cl_git_pass(git_repository_index(&index, repo)); - - cl_git_pass(git_iterator_for_tree(&head_iterator, head, NULL)); - cl_git_pass(git_iterator_for_index(&index_iterator, repo, index, NULL)); - - iterators[0] = head_iterator; - iterators[1] = index_iterator; - - cl_git_pass(git_iterator_walk(iterators, 2, iterator_eq, NULL)); - - git_iterator_free(head_iterator); - git_iterator_free(index_iterator); - - git_tree_free(head); - git_index_free(index); -} - -void validate_workdir_unchanged(git_repository *repo) -{ - git_tree *head; - git_index *index; - git_iterator *head_iterator, *workdir_iterator, *iterators[2]; - git_iterator_options workdir_opts = GIT_ITERATOR_OPTIONS_INIT; - - cl_git_pass(git_repository_head_tree(&head, repo)); - cl_git_pass(git_repository_index(&index, repo)); - - workdir_opts.flags |= GIT_ITERATOR_INCLUDE_HASH; - - cl_git_pass(git_iterator_for_tree(&head_iterator, head, NULL)); - cl_git_pass(git_iterator_for_workdir(&workdir_iterator, repo, index, NULL, &workdir_opts)); - - iterators[0] = head_iterator; - iterators[1] = workdir_iterator; - - cl_git_pass(git_iterator_walk(iterators, 2, iterator_eq, NULL)); - - git_iterator_free(head_iterator); - git_iterator_free(workdir_iterator); - - git_tree_free(head); - git_index_free(index); -} diff --git a/tests/apply/apply_helpers.h b/tests/apply/apply_helpers.h deleted file mode 100644 index 82094773e..000000000 --- a/tests/apply/apply_helpers.h +++ /dev/null @@ -1,497 +0,0 @@ -#include "../merge/merge_helpers.h" - -#define TEST_REPO_PATH "merge-recursive" - -#define DIFF_MODIFY_TWO_FILES \ - "diff --git a/asparagus.txt b/asparagus.txt\n" \ - "index f516580..ffb36e5 100644\n" \ - "--- a/asparagus.txt\n" \ - "+++ b/asparagus.txt\n" \ - "@@ -1 +1 @@\n" \ - "-ASPARAGUS SOUP!\n" \ - "+ASPARAGUS SOUP.\n" \ - "diff --git a/veal.txt b/veal.txt\n" \ - "index 94d2c01..a7b0665 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/veal.txt\n" \ - "@@ -1 +1 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP.\n" \ - "@@ -7 +7 @@ occasionally, then put into it a shin of veal, let it boil two hours\n" \ - "-longer. take out the slices of ham, and skim off the grease if any\n" \ - "+longer; take out the slices of ham, and skim off the grease if any\n" - -/* This is the binary equivalent of DIFF_MODIFY_TWO_FILES */ -#define DIFF_MODIFY_TWO_FILES_BINARY \ - "diff --git a/asparagus.txt b/asparagus.txt\n" \ - "index f51658077d85f2264fa179b4d0848268cb3475c3..ffb36e513f5fdf8a6ba850a20142676a2ac4807d 100644\n" \ - "GIT binary patch\n" \ - "delta 24\n" \ - "fcmX@ja+-zTF*v|6$k9DCSRvRyG(c}7zYP-rT_OhP\n" \ - "\n" \ - "delta 24\n" \ - "fcmX@ja+-zTF*v|6$k9DCSRvRyG(d49zYP-rT;T@W\n" \ - "\n" \ - "diff --git a/veal.txt b/veal.txt\n" \ - "index 94d2c01087f48213bd157222d54edfefd77c9bba..a7b066537e6be7109abfe4ff97b675d4e077da20 100644\n" \ - "GIT binary patch\n" \ - "delta 26\n" \ - "hcmX@kah!uI%+=9HA=p1OKyM?L03)OIW@$zpW&mXg25bNT\n" \ - "\n" \ - "delta 26\n" \ - "hcmX@kah!uI%+=9HA=p1OKyf3N03)N`W@$zpW&mU#22ub3\n" \ - "\n" - -#define DIFF_DELETE_FILE \ - "diff --git a/gravy.txt b/gravy.txt\n" \ - "deleted file mode 100644\n" \ - "index c4e6cca..0000000\n" \ - "--- a/gravy.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,8 +0,0 @@\n" \ - "-GRAVY SOUP.\n" \ - "-\n" \ - "-Get eight pounds of coarse lean beef--wash it clean and lay it in your\n" \ - "-pot, put in the same ingredients as for the shin soup, with the same\n" \ - "-quantity of water, and follow the process directed for that. Strain the\n" \ - "-soup through a sieve, and serve it up clear, with nothing more than\n" \ - "-toasted bread in it; two table-spoonsful of mushroom catsup will add a\n" \ - "-fine flavour to the soup.\n" - -#define DIFF_ADD_FILE \ - "diff --git a/newfile.txt b/newfile.txt\n" \ - "new file mode 100644\n" \ - "index 0000000..6370543\n" \ - "--- /dev/null\n" \ - "+++ b/newfile.txt\n" \ - "@@ -0,0 +1,2 @@\n" \ - "+This is a new file!\n" \ - "+Added by a patch.\n" - -#define DIFF_EXECUTABLE_FILE \ - "diff --git a/beef.txt b/beef.txt\n" \ - "old mode 100644\n" \ - "new mode 100755\n" - -#define DIFF_MANY_CHANGES_ONE \ - "diff --git a/veal.txt b/veal.txt\n" \ - "index 94d2c01..c9d7d5d 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/veal.txt\n" \ - "@@ -1,2 +1,2 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP\n" \ - " \n" \ - "@@ -4,3 +4,2 @@\n" \ - " spoonful of black pepper pounded, and two of salt, with two or three\n" \ - "-slices of lean ham; let it boil steadily two hours; skim it\n" \ - " occasionally, then put into it a shin of veal, let it boil two hours\n" \ - "@@ -8,3 +7,3 @@\n" \ - " should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ - "-of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ - "+OF FLOUR very nicely, and the yelks of two eggs beaten well, strain this\n" \ - " mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ - "@@ -12,2 +11,3 @@\n" \ - " boiled two or three minutes to take off the raw taste of the eggs. If\n" \ - "+Inserted line.\n" \ - " the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ - "@@ -15,3 +15,3 @@\n" \ - " in, first taking off their skins, by letting them stand a few minutes in\n" \ - "-hot water, when they may be easily peeled. When made in this way you\n" \ - "+Changed line.\n" \ - " must thicken it with the flour only. Any part of the veal may be used,\n" - -#define DIFF_MANY_CHANGES_TWO \ - "diff --git a/veal.txt b/veal.txt\n" \ - "index 94d2c01..6b943d6 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/veal.txt\n" \ - "@@ -1,2 +1,2 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP!!!\n" \ - " \n" \ - "@@ -4,3 +4,2 @@\n" \ - " spoonful of black pepper pounded, and two of salt, with two or three\n" \ - "-slices of lean ham; let it boil steadily two hours; skim it\n" \ - " occasionally, then put into it a shin of veal, let it boil two hours\n" \ - "@@ -8,3 +7,3 @@\n" \ - " should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ - "-of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ - "+of flour very nicely, AND the yelks of two eggs beaten well, strain this\n" \ - " mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ - "@@ -12,2 +11,3 @@\n" \ - " boiled two or three minutes to take off the raw taste of the eggs. If\n" \ - "+New line.\n" \ - " the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ - "@@ -15,4 +15,5 @@\n" \ - " in, first taking off their skins, by letting them stand a few minutes in\n" \ - "-hot water, when they may be easily peeled. When made in this way you\n" \ - "-must thicken it with the flour only. Any part of the veal may be used,\n" \ - "-but the shin or knuckle is the nicest.\n" \ - "+HOT water, when they may be easily peeled. When made in this way you\n" \ - "+must THICKEN it with the flour only. Any part of the veal may be used,\n" \ - "+but the shin OR knuckle is the nicest.\n" \ - "+Another new line.\n" \ - -#define DIFF_RENAME_FILE \ - "diff --git a/beef.txt b/notbeef.txt\n" \ - "similarity index 100%\n" \ - "rename from beef.txt\n" \ - "rename to notbeef.txt\n" - -#define DIFF_RENAME_AND_MODIFY_FILE \ - "diff --git a/beef.txt b/notbeef.txt\n" \ - "similarity index 97%\n" \ - "rename from beef.txt\n" \ - "rename to notbeef.txt\n" \ - "index 68f6182..6fa1014 100644\n" \ - "--- a/beef.txt\n" \ - "+++ b/notbeef.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-BEEF SOUP.\n" \ - "+THIS IS NOT BEEF SOUP, IT HAS A NEW NAME.\n" \ - "\n" \ - " Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \ - " which must be taken away entirely, or the soup will be greasy. Wash the\n" - -#define DIFF_RENAME_A_TO_B_TO_C \ - "diff --git a/asparagus.txt b/asparagus.txt\n" \ - "deleted file mode 100644\n" \ - "index f516580..0000000\n" \ - "--- a/asparagus.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,10 +0,0 @@\n" \ - "-ASPARAGUS SOUP!\n" \ - "-\n" \ - "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ - "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ - "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ - "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ - "-pulp them through a sieve, and strain the water to it, which must be put\n" \ - "-back in the pot; put into it a chicken cut up, with the tops of\n" \ - "-asparagus which had been laid by, boil it until these last articles are\n" \ - "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ - "diff --git a/beef.txt b/beef.txt\n" \ - "index 68f6182..f516580 100644\n" \ - "--- a/beef.txt\n" \ - "+++ b/beef.txt\n" \ - "@@ -1,22 +1,10 @@\n" \ - "-BEEF SOUP.\n" \ - "+ASPARAGUS SOUP!\n" \ - "\n" \ - "-Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \ - "-which must be taken away entirely, or the soup will be greasy. Wash the\n" \ - "-meat clean and lay it in a pot, sprinkle over it one small\n" \ - "-table-spoonful of pounded black pepper, and two of salt; three onions\n" \ - "-the size of a hen's egg, cut small, six small carrots scraped and cut\n" \ - "-up, two small turnips pared and cut into dice; pour on three quarts of\n" \ - "-water, cover the pot close, and keep it gently and steadily boiling five\n" \ - "-hours, which will leave about three pints of clear soup; do not let the\n" \ - "-pot boil over, but take off the scum carefully, as it rises. When it has\n" \ - "-boiled four hours, put in a small bundle of thyme and parsley, and a\n" \ - "-pint of celery cut small, or a tea-spoonful of celery seed pounded.\n" \ - "-These latter ingredients would lose their delicate flavour if boiled too\n" \ - "-much. Just before you take it up, brown it in the following manner: put\n" \ - "-a small table-spoonful of nice brown sugar into an iron skillet, set it\n" \ - "-on the fire and stir it till it melts and looks very dark, pour into it\n" \ - "-a ladle full of the soup, a little at a time; stirring it all the while.\n" \ - "-Strain this browning and mix it well with the soup; take out the bundle\n" \ - "-of thyme and parsley, put the nicest pieces of meat in your tureen, and\n" \ - "-pour on the soup and vegetables; put in some toasted bread cut in dice,\n" \ - "-and serve it up.\n" \ - "+Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ - "+of the tops, and lay them in water, chop the stalks and put them on the\n" \ - "+fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ - "+add two quarts of water, boil them till the stalks are quite soft, then\n" \ - "+pulp them through a sieve, and strain the water to it, which must be put\n" \ - "+back in the pot; put into it a chicken cut up, with the tops of\n" \ - "+asparagus which had been laid by, boil it until these last articles are\n" \ - "+sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ - "diff --git a/notbeef.txt b/notbeef.txt\n" \ - "new file mode 100644\n" \ - "index 0000000..68f6182\n" \ - "--- /dev/null\n" \ - "+++ b/notbeef.txt\n" \ - "@@ -0,0 +1,22 @@\n" \ - "+BEEF SOUP.\n" \ - "+\n" \ - "+Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \ - "+which must be taken away entirely, or the soup will be greasy. Wash the\n" \ - "+meat clean and lay it in a pot, sprinkle over it one small\n" \ - "+table-spoonful of pounded black pepper, and two of salt; three onions\n" \ - "+the size of a hen's egg, cut small, six small carrots scraped and cut\n" \ - "+up, two small turnips pared and cut into dice; pour on three quarts of\n" \ - "+water, cover the pot close, and keep it gently and steadily boiling five\n" \ - "+hours, which will leave about three pints of clear soup; do not let the\n" \ - "+pot boil over, but take off the scum carefully, as it rises. When it has\n" \ - "+boiled four hours, put in a small bundle of thyme and parsley, and a\n" \ - "+pint of celery cut small, or a tea-spoonful of celery seed pounded.\n" \ - "+These latter ingredients would lose their delicate flavour if boiled too\n" \ - "+much. Just before you take it up, brown it in the following manner: put\n" \ - "+a small table-spoonful of nice brown sugar into an iron skillet, set it\n" \ - "+on the fire and stir it till it melts and looks very dark, pour into it\n" \ - "+a ladle full of the soup, a little at a time; stirring it all the while.\n" \ - "+Strain this browning and mix it well with the soup; take out the bundle\n" \ - "+of thyme and parsley, put the nicest pieces of meat in your tureen, and\n" \ - "+pour on the soup and vegetables; put in some toasted bread cut in dice,\n" \ - "+and serve it up.\n" - -#define DIFF_RENAME_A_TO_B_TO_C_EXACT \ - "diff --git a/asparagus.txt b/beef.txt\n" \ - "similarity index 100%\n" \ - "rename from asparagus.txt\n" \ - "rename to beef.txt\n" \ - "diff --git a/beef.txt b/notbeef.txt\n" \ - "similarity index 100%\n" \ - "rename from beef.txt\n" \ - "rename to notbeef.txt\n" - -#define DIFF_RENAME_CIRCULAR \ - "diff --git a/asparagus.txt b/beef.txt\n" \ - "similarity index 100%\n" \ - "rename from asparagus.txt\n" \ - "rename to beef.txt\n" \ - "diff --git a/beef.txt b/notbeef.txt\n" \ - "similarity index 100%\n" \ - "rename from beef.txt\n" \ - "rename to asparagus.txt\n" - -#define DIFF_RENAME_2_TO_1 \ - "diff --git a/asparagus.txt b/2.txt\n" \ - "similarity index 100%\n" \ - "rename from asparagus.txt\n" \ - "rename to 2.txt\n" \ - "diff --git a/beef.txt b/2.txt\n" \ - "similarity index 100%\n" \ - "rename from beef.txt\n" \ - "rename to 2.txt\n" - -#define DIFF_RENAME_1_TO_2 \ - "diff --git a/asparagus.txt b/2.txt\n" \ - "similarity index 100%\n" \ - "rename from asparagus.txt\n" \ - "rename to 1.txt\n" \ - "diff --git a/asparagus.txt b/2.txt\n" \ - "similarity index 100%\n" \ - "rename from asparagus.txt\n" \ - "rename to 2.txt\n" - -#define DIFF_TWO_DELTAS_ONE_FILE \ - "diff --git a/beef.txt b/beef.txt\n" \ - "index 68f6182..235069d 100644\n" \ - "--- a/beef.txt\n" \ - "+++ b/beef.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-BEEF SOUP.\n" \ - "+BEEF SOUP!\n" \ - "\n" \ - " Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \ - " which must be taken away entirely, or the soup will be greasy. Wash the\n" \ - "diff --git a/beef.txt b/beef.txt\n" \ - "index 68f6182..e059eb5 100644\n" \ - "--- a/beef.txt\n" \ - "+++ b/beef.txt\n" \ - "@@ -19,4 +19,4 @@ a ladle full of the soup, a little at a time; stirring it all the while.\n" \ - " Strain this browning and mix it well with the soup; take out the bundle\n" \ - " of thyme and parsley, put the nicest pieces of meat in your tureen, and\n" \ - " pour on the soup and vegetables; put in some toasted bread cut in dice,\n" \ - "-and serve it up.\n" \ - "+and serve it up!\n" - -#define DIFF_TWO_DELTAS_ONE_NEW_FILE \ - "diff --git a/newfile.txt b/newfile.txt\n" \ - "new file mode 100644\n" \ - "index 0000000..6434b13\n" \ - "--- /dev/null\n" \ - "+++ b/newfile.txt\n" \ - "@@ -0,0 +1 @@\n" \ - "+This is a new file.\n" \ - "diff --git a/newfile.txt b/newfile.txt\n" \ - "index 6434b13..08d4c44 100644\n" \ - "--- a/newfile.txt\n" \ - "+++ b/newfile.txt\n" \ - "@@ -1 +1,3 @@\n" \ - " This is a new file.\n" \ - "+\n" \ - "+This is another change to a new file.\n" - -#define DIFF_RENAME_AND_MODIFY_DELTAS \ - "diff --git a/veal.txt b/asdf.txt\n" \ - "similarity index 96%\n" \ - "rename from veal.txt\n" \ - "rename to asdf.txt\n" \ - "index 94d2c01..292cb60 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/asdf.txt\n" \ - "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ - " in, first taking off their skins, by letting them stand a few minutes in\n" \ - " hot water, when they may be easily peeled. When made in this way you\n" \ - " must thicken it with the flour only. Any part of the veal may be used,\n" \ - "-but the shin or knuckle is the nicest.\n" \ - "+but the shin or knuckle is the nicest!\n" \ - "diff --git a/asdf.txt b/asdf.txt\n" \ - "index 292cb60..61c686b 100644\n" \ - "--- a/asdf.txt\n" \ - "+++ b/asdf.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP\n" \ - "\n" \ - " Put into a pot three quarts of water, three onions cut small, one\n" \ - " spoonful of black pepper pounded, and two of salt, with two or three\n" - -#define DIFF_RENAME_AFTER_MODIFY \ - "diff --git a/veal.txt b/veal.txt\n" \ - "index 292cb60..61c686b 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/veal.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP\n" \ - "\n" \ - " Put into a pot three quarts of water, three onions cut small, one\n" \ - " spoonful of black pepper pounded, and two of salt, with two or three\n" \ - "diff --git a/veal.txt b/other.txt\n" \ - "similarity index 96%\n" \ - "rename from veal.txt\n" \ - "rename to other.txt\n" \ - "index 94d2c01..292cb60 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/other.txt\n" \ - "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ - " in, first taking off their skins, by letting them stand a few minutes in\n" \ - " hot water, when they may be easily peeled. When made in this way you\n" \ - " must thicken it with the flour only. Any part of the veal may be used,\n" \ - "-but the shin or knuckle is the nicest.\n" \ - "+but the shin or knuckle is the nicest!\n" - -#define DIFF_RENAME_AFTER_MODIFY_TARGET_PATH \ - "diff --git a/beef.txt b/beef.txt\n" \ - "index 292cb60..61c686b 100644\n" \ - "--- a/beef.txt\n" \ - "+++ b/beef.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP\n" \ - "\n" \ - " Put into a pot three quarts of water, three onions cut small, one\n" \ - " spoonful of black pepper pounded, and two of salt, with two or three\n" \ - "diff --git a/veal.txt b/beef.txt\n" \ - "similarity index 96%\n" \ - "rename from veal.txt\n" \ - "rename to beef.txt\n" \ - "index 94d2c01..292cb60 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/beef.txt\n" \ - "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ - " in, first taking off their skins, by letting them stand a few minutes in\n" \ - " hot water, when they may be easily peeled. When made in this way you\n" \ - " must thicken it with the flour only. Any part of the veal may be used,\n" \ - "-but the shin or knuckle is the nicest.\n" \ - "+but the shin or knuckle is the nicest!\n" - -#define DIFF_RENAME_AND_MODIFY_SOURCE_PATH \ - "diff --git a/veal.txt b/asdf.txt\n" \ - "similarity index 96%\n" \ - "rename from veal.txt\n" \ - "rename to asdf.txt\n" \ - "index 94d2c01..292cb60 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/asdf.txt\n" \ - "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ - " in, first taking off their skins, by letting them stand a few minutes in\n" \ - " hot water, when they may be easily peeled. When made in this way you\n" \ - " must thicken it with the flour only. Any part of the veal may be used,\n" \ - "-but the shin or knuckle is the nicest.\n" \ - "+but the shin or knuckle is the nicest!\n" \ - "diff --git a/veal.txt b/veal.txt\n" \ - "index 292cb60..61c686b 100644\n" \ - "--- a/veal.txt\n" \ - "+++ b/veal.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-VEAL SOUP!\n" \ - "+VEAL SOUP\n" \ - "\n" \ - " Put into a pot three quarts of water, three onions cut small, one\n" \ - " spoonful of black pepper pounded, and two of salt, with two or three\n" - -#define DIFF_DELETE_AND_READD_FILE \ - "diff --git a/asparagus.txt b/asparagus.txt\n" \ - "deleted file mode 100644\n" \ - "index f516580..0000000\n" \ - "--- a/asparagus.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,10 +0,0 @@\n" \ - "-ASPARAGUS SOUP!\n" \ - "-\n" \ - "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ - "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ - "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ - "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ - "-pulp them through a sieve, and strain the water to it, which must be put\n" \ - "-back in the pot; put into it a chicken cut up, with the tops of\n" \ - "-asparagus which had been laid by, boil it until these last articles are\n" \ - "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ - "diff --git a/asparagus.txt b/asparagus.txt\n" \ - "new file mode 100644\n" \ - "index 0000000..2dc7f8b\n" \ - "--- /dev/null\n" \ - "+++ b/asparagus.txt\n" \ - "@@ -0,0 +1 @@\n" \ - "+New file.\n" \ - -#define DIFF_REMOVE_FILE_TWICE \ - "diff --git a/asparagus.txt b/asparagus.txt\n" \ - "deleted file mode 100644\n" \ - "index f516580..0000000\n" \ - "--- a/asparagus.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,10 +0,0 @@\n" \ - "-ASPARAGUS SOUP!\n" \ - "-\n" \ - "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ - "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ - "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ - "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ - "-pulp them through a sieve, and strain the water to it, which must be put\n" \ - "-back in the pot; put into it a chicken cut up, with the tops of\n" \ - "-asparagus which had been laid by, boil it until these last articles are\n" \ - "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ - "diff --git a/asparagus.txt b/asparagus.txt\n" \ - "deleted file mode 100644\n" \ - "index f516580..0000000\n" \ - "--- a/asparagus.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,10 +0,0 @@\n" \ - "-ASPARAGUS SOUP!\n" \ - "-\n" \ - "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ - "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ - "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ - "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ - "-pulp them through a sieve, and strain the water to it, which must be put\n" \ - "-back in the pot; put into it a chicken cut up, with the tops of\n" \ - "-asparagus which had been laid by, boil it until these last articles are\n" \ - "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" - -#define DIFF_ADD_INVALID_FILENAME \ - "diff --git a/.git/hello_world.txt b/.git/hello_world.txt\n" \ - "new file mode 100644\n" \ - "index 0000000..f75ba05\n" \ - "--- /dev/null\n" \ - "+++ b/.git/hello_world.txt\n" \ - "@@ -0,0 +1 @@\n" \ - "+Hello, world.\n" - -void validate_apply_workdir( - git_repository *repo, - struct merge_index_entry *workdir_entries, - size_t workdir_cnt); - -void validate_apply_index( - git_repository *repo, - struct merge_index_entry *index_entries, - size_t index_cnt); - -void validate_index_unchanged(git_repository *repo); -void validate_workdir_unchanged(git_repository *repo); diff --git a/tests/apply/both.c b/tests/apply/both.c deleted file mode 100644 index 108963270..000000000 --- a/tests/apply/both.c +++ /dev/null @@ -1,747 +0,0 @@ -#include "clar_libgit2.h" -#include "apply_helpers.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - -void test_apply_both__initialize(void) -{ - git_oid oid; - git_commit *commit; - - repo = cl_git_sandbox_init(TEST_REPO_PATH); - - git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); - git_commit_free(commit); -} - -void test_apply_both__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_apply_both__generated_diff(void) -{ - git_oid a_oid, b_oid; - git_commit *a_commit, *b_commit; - git_tree *a_tree, *b_tree; - git_diff *diff; - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - - struct merge_index_entry both_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f"); - cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); - cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); - - cl_git_pass(git_commit_tree(&a_tree, a_commit)); - cl_git_pass(git_commit_tree(&b_tree, b_commit)); - - cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &diff_opts)); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); - git_tree_free(a_tree); - git_tree_free(b_tree); - git_commit_free(a_commit); - git_commit_free(b_commit); -} - -void test_apply_both__parsed_diff(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__removes_file(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_FILE, - strlen(DIFF_DELETE_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__adds_file(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__application_failure_leaves_index_unmodified(void) -{ - git_diff *diff; - git_index *index; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - /* mutate the index */ - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_remove(index, "veal.txt", 0)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_both__index_must_match_workdir(void) -{ - git_diff *diff; - git_index *index; - git_index_entry idx_entry; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - /* - * Append a line to the end of the file in both the index and the - * working directory. Although the appended line would allow for - * patch application in each, the line appended is different in - * each, so the application should not be allowed. - */ - cl_git_append2file("merge-recursive/asparagus.txt", - "This is a modification.\n"); - - cl_git_pass(git_repository_index(&index, repo)); - - memset(&idx_entry, 0, sizeof(git_index_entry)); - idx_entry.mode = 0100644; - idx_entry.path = "asparagus.txt"; - cl_git_pass(git_oid_fromstr(&idx_entry.id, "06d3fefb8726ab1099acc76e02dfb85e034b2538")); - cl_git_pass(git_index_add(index, &idx_entry)); - - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - git_diff_free(diff); -} - -void test_apply_both__index_mode_must_match_workdir(void) -{ - git_diff *diff; - - if (!cl_is_chmod_supported()) - clar__skip(); - - /* Set a file in the working directory executable. */ - cl_must_pass(p_chmod("merge-recursive/asparagus.txt", 0755)); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_MODIFY_TWO_FILES, - strlen(DIFF_MODIFY_TWO_FILES))); - cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - git_diff_free(diff); -} - -void test_apply_both__application_failure_leaves_workdir_unmodified(void) -{ - git_diff *diff; - git_index *index; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "8684724651336001c5dbce74bed6736d2443958d", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - /* mutate the workdir */ - cl_git_rewritefile("merge-recursive/veal.txt", - "This is a modification.\n"); - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, "veal.txt")); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__keeps_nonconflicting_changes(void) -{ - git_diff *diff; - git_index *index; - git_index_entry idx_entry; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "beef.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - struct merge_index_entry workdir_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "f75ba05f340c51065cbea2e1fdbfe5fe13144c97", 0, "gravy.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - /* mutate the index */ - cl_git_pass(git_repository_index(&index, repo)); - - memset(&idx_entry, 0, sizeof(git_index_entry)); - idx_entry.mode = 0100644; - idx_entry.path = "beef.txt"; - cl_git_pass(git_oid_fromstr(&idx_entry.id, "898d12687fb35be271c27c795a6b32c8b51da79e")); - cl_git_pass(git_index_add(index, &idx_entry)); - - cl_git_pass(git_index_remove(index, "bouilli.txt", 0)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - /* and mutate the working directory */ - cl_git_rmfile("merge-recursive/oyster.txt"); - cl_git_rewritefile("merge-recursive/gravy.txt", "Hello, world.\n"); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__can_apply_nonconflicting_file_changes(void) -{ - git_diff *diff; - git_index *index; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry both_expected[] = { - { 0100644, "f8a701c8a1a22c1729ee50faff1111f2d64f96fc", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - /* - * Replace the workdir file with a version that is different than - * HEAD but such that the patch still applies cleanly. This item - * has a new line appended. - */ - cl_git_append2file("merge-recursive/asparagus.txt", - "This line is added in the index and the workdir.\n"); - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, "asparagus.txt")); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__honors_crlf_attributes(void) -{ - git_diff *diff; - git_oid oid; - git_commit *commit; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - struct merge_index_entry workdir_expected[] = { - { 0100644, "176a458f94e0ea5272ce67c36bf30b6be9caf623", 0, ".gitattributes" }, - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_mkfile("merge-recursive/.gitattributes", "* text=auto\n"); - - cl_git_rmfile("merge-recursive/asparagus.txt"); - cl_git_rmfile("merge-recursive/veal.txt"); - - git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); - git_commit_free(commit); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "notbeef.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_FILE, - strlen(DIFF_RENAME_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_and_modify(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "6fa10147f00fe1fab1d5e835529a9dad53db8552", 0, "notbeef.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AND_MODIFY_FILE, - strlen(DIFF_RENAME_AND_MODIFY_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_a_to_b_to_c(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "notbeef.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_A_TO_B_TO_C, - strlen(DIFF_RENAME_A_TO_B_TO_C))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_a_to_b_to_c_exact(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "notbeef.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_A_TO_B_TO_C_EXACT, - strlen(DIFF_RENAME_A_TO_B_TO_C_EXACT))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_circular(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "asparagus.txt" }, - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_CIRCULAR, - strlen(DIFF_RENAME_CIRCULAR))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_2_to_1(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "2.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_2_TO_1, - strlen(DIFF_RENAME_2_TO_1))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_1_to_2(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "1.txt" }, - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "2.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_1_TO_2, - strlen(DIFF_RENAME_1_TO_2))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__two_deltas_one_file(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "0a9fd4415635e72573f0f6b5e68084cfe18f5075", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_TWO_DELTAS_ONE_FILE, - strlen(DIFF_TWO_DELTAS_ONE_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__two_deltas_one_new_file(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "08d4c445cf0078f3d9b604b82f32f4d87e083325", 0, "newfile.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_TWO_DELTAS_ONE_NEW_FILE, - strlen(DIFF_TWO_DELTAS_ONE_NEW_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_and_modify_deltas(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "61c686bed39684eee8a2757ceb1291004a21333f", 0, "asdf.txt" }, - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AND_MODIFY_DELTAS, - strlen(DIFF_RENAME_AND_MODIFY_DELTAS))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__rename_delta_after_modify_delta(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "292cb60ce5e25c337c5b6e12957bbbfe1be4bf49", 0, "other.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "c8c120f466591bbe3b8867361d5ec3cdd9fda756", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AFTER_MODIFY, - strlen(DIFF_RENAME_AFTER_MODIFY))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__cant_rename_after_modify_nonexistent_target_path(void) -{ - git_diff *diff; - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AFTER_MODIFY_TARGET_PATH, - strlen(DIFF_RENAME_AFTER_MODIFY_TARGET_PATH))); - cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - git_diff_free(diff); -} - -void test_apply_both__cant_modify_source_path_after_rename(void) -{ - git_diff *diff; - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AND_MODIFY_SOURCE_PATH, - strlen(DIFF_RENAME_AND_MODIFY_SOURCE_PATH))); - cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - git_diff_free(diff); -} - -void test_apply_both__readd_deleted_file(void) -{ - git_diff *diff; - - struct merge_index_entry both_expected[] = { - { 0100644, "2dc7f8b24ba27f3888368bd180df03ff4c6c6fab", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } - }; - size_t both_expected_cnt = sizeof(both_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_AND_READD_FILE, - strlen(DIFF_DELETE_AND_READD_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - validate_apply_index(repo, both_expected, both_expected_cnt); - validate_apply_workdir(repo, both_expected, both_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_both__cant_remove_file_twice(void) -{ - git_diff *diff; - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_REMOVE_FILE_TWICE, - strlen(DIFF_REMOVE_FILE_TWICE))); - cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - git_diff_free(diff); -} - -void test_apply_both__cant_add_invalid_filename(void) -{ - git_diff *diff; - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_ADD_INVALID_FILENAME, - strlen(DIFF_ADD_INVALID_FILENAME))); - cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); - - git_diff_free(diff); -} diff --git a/tests/apply/callbacks.c b/tests/apply/callbacks.c deleted file mode 100644 index 1b759dc9b..000000000 --- a/tests/apply/callbacks.c +++ /dev/null @@ -1,128 +0,0 @@ -#include "clar_libgit2.h" -#include "apply_helpers.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - -void test_apply_callbacks__initialize(void) -{ - git_oid oid; - git_commit *commit; - - repo = cl_git_sandbox_init(TEST_REPO_PATH); - - git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); - git_commit_free(commit); -} - -void test_apply_callbacks__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int delta_abort_cb(const git_diff_delta *delta, void *payload) -{ - GIT_UNUSED(payload); - - if (!strcmp(delta->old_file.path, "veal.txt")) - return -99; - - return 0; -} - -void test_apply_callbacks__delta_aborts(void) -{ - git_diff *diff; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - - opts.delta_cb = delta_abort_cb; - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); - cl_git_fail_with(-99, - git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); - - validate_index_unchanged(repo); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -static int delta_skip_cb(const git_diff_delta *delta, void *payload) -{ - GIT_UNUSED(payload); - - if (!strcmp(delta->old_file.path, "asparagus.txt")) - return 1; - - return 0; -} - -void test_apply_callbacks__delta_can_skip(void) -{ - git_diff *diff; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - opts.delta_cb = delta_skip_cb; - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -static int hunk_skip_odds_cb(const git_diff_hunk *hunk, void *payload) -{ - int *count = (int *)payload; - GIT_UNUSED(hunk); - - return ((*count)++ % 2 == 1); -} - -void test_apply_callbacks__hunk_can_skip(void) -{ - git_diff *diff; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - int count = 0; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "06f751b6ba4f017ddbf4248015768300268e092a", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - opts.hunk_cb = hunk_skip_odds_cb; - opts.payload = &count; - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MANY_CHANGES_ONE, strlen(DIFF_MANY_CHANGES_ONE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} diff --git a/tests/apply/check.c b/tests/apply/check.c deleted file mode 100644 index 9e42365ed..000000000 --- a/tests/apply/check.c +++ /dev/null @@ -1,121 +0,0 @@ -#include "clar_libgit2.h" -#include "apply_helpers.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - -void test_apply_check__initialize(void) -{ - git_oid oid; - git_commit *commit; - - repo = cl_git_sandbox_init(TEST_REPO_PATH); - - git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); - git_commit_free(commit); -} - -void test_apply_check__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_apply_check__generate_diff(void) -{ - git_oid a_oid, b_oid; - git_commit *a_commit, *b_commit; - git_tree *a_tree, *b_tree; - git_diff *diff; - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - - cl_git_pass(git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707")); - cl_git_pass(git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f")); - cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); - cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); - - cl_git_pass(git_commit_tree(&a_tree, a_commit)); - cl_git_pass(git_commit_tree(&b_tree, b_commit)); - - opts.flags |= GIT_APPLY_CHECK; - cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &diff_opts)); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, &opts)); - - validate_index_unchanged(repo); - validate_workdir_unchanged(repo); - - git_diff_free(diff); - git_tree_free(a_tree); - git_tree_free(b_tree); - git_commit_free(a_commit); - git_commit_free(b_commit); -} - -void test_apply_check__parsed_diff(void) -{ - git_diff *diff; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - - opts.flags |= GIT_APPLY_CHECK; - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); - - validate_index_unchanged(repo); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_check__binary(void) -{ - git_diff *diff; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - - opts.flags |= GIT_APPLY_CHECK; - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MODIFY_TWO_FILES_BINARY, - strlen(DIFF_MODIFY_TWO_FILES_BINARY))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); - - validate_index_unchanged(repo); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_check__does_not_apply(void) -{ - git_diff *diff; - git_index *index; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - /* mutate the index */ - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_remove(index, "veal.txt", 0)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - opts.flags |= GIT_APPLY_CHECK; - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - - git_diff_free(diff); -} diff --git a/tests/apply/fromdiff.c b/tests/apply/fromdiff.c deleted file mode 100644 index 9da6ac923..000000000 --- a/tests/apply/fromdiff.c +++ /dev/null @@ -1,378 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/repository.h" - -#include "apply.h" -#include "repository.h" - -#include "../patch/patch_common.h" - -static git_repository *repo = NULL; -static git_diff_options binary_opts = GIT_DIFF_OPTIONS_INIT; - -void test_apply_fromdiff__initialize(void) -{ - repo = cl_git_sandbox_init("renames"); - - binary_opts.flags |= GIT_DIFF_SHOW_BINARY; -} - -void test_apply_fromdiff__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int apply_gitbuf( - const git_str *old, - const char *oldname, - const git_str *new, - const char *newname, - const char *patch_expected, - const git_diff_options *diff_opts) -{ - git_patch *patch; - git_str result = GIT_STR_INIT; - git_buf patchbuf = GIT_BUF_INIT; - char *filename; - unsigned int mode; - int error; - - cl_git_pass(git_patch_from_buffers(&patch, - old ? old->ptr : NULL, old ? old->size : 0, - oldname, - new ? new->ptr : NULL, new ? new->size : 0, - newname, - diff_opts)); - - if (patch_expected) { - cl_git_pass(git_patch_to_buf(&patchbuf, patch)); - cl_assert_equal_s(patch_expected, patchbuf.ptr); - } - - error = git_apply__patch(&result, &filename, &mode, old ? old->ptr : NULL, old ? old->size : 0, patch, NULL); - - if (error == 0 && new == NULL) { - cl_assert_equal_i(0, result.size); - cl_assert_equal_p(NULL, filename); - cl_assert_equal_i(0, mode); - } - else if (error == 0) { - cl_assert_equal_s(new->ptr, result.ptr); - cl_assert_equal_s(newname ? newname : oldname, filename); - cl_assert_equal_i(0100644, mode); - } - - git__free(filename); - git_str_dispose(&result); - git_buf_dispose(&patchbuf); - git_patch_free(patch); - - return error; -} - -static int apply_buf( - const char *old, - const char *oldname, - const char *new, - const char *newname, - const char *patch_expected, - const git_diff_options *diff_opts) -{ - git_str o = GIT_STR_INIT, n = GIT_STR_INIT, - *optr = NULL, *nptr = NULL; - - if (old) { - o.ptr = (char *)old; - o.size = strlen(old); - optr = &o; - } - - if (new) { - n.ptr = (char *)new; - n.size = strlen(new); - nptr = &n; - } - - return apply_gitbuf(optr, oldname, nptr, newname, patch_expected, diff_opts); -} - -void test_apply_fromdiff__change_middle(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_CHANGE_MIDDLE, "file.txt", - PATCH_ORIGINAL_TO_CHANGE_MIDDLE, NULL)); -} - -void test_apply_fromdiff__change_middle_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_CHANGE_MIDDLE, "file.txt", - PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, &diff_opts)); -} - -void test_apply_fromdiff__change_firstline(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_CHANGE_FIRSTLINE, "file.txt", - PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, NULL)); -} - -void test_apply_fromdiff__lastline(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_CHANGE_LASTLINE, "file.txt", - PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL)); -} - -void test_apply_fromdiff__change_middle_and_lastline_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_CHANGE_MIDDLE_AND_LASTLINE, "file.txt", - PATCH_ORIGINAL_TO_CHANGE_MIDDLE_AND_LASTLINE_NOCONTEXT, &diff_opts)); -} - -void test_apply_fromdiff__prepend(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND, "file.txt", - PATCH_ORIGINAL_TO_PREPEND, NULL)); -} - -void test_apply_fromdiff__prepend_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND, "file.txt", - PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts)); -} - -void test_apply_fromdiff__prepend_and_change(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_CHANGE, "file.txt", - PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE, NULL)); -} - -void test_apply_fromdiff__prepend_and_change_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_CHANGE, "file.txt", - PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE_NOCONTEXT, &diff_opts)); -} - -void test_apply_fromdiff__delete_and_change(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_AND_CHANGE, "file.txt", - PATCH_ORIGINAL_TO_DELETE_AND_CHANGE, NULL)); -} - -void test_apply_fromdiff__delete_and_change_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_AND_CHANGE, "file.txt", - PATCH_ORIGINAL_TO_DELETE_AND_CHANGE_NOCONTEXT, &diff_opts)); -} - -void test_apply_fromdiff__delete_firstline(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_FIRSTLINE, "file.txt", - PATCH_ORIGINAL_TO_DELETE_FIRSTLINE, NULL)); -} - -void test_apply_fromdiff__append(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_APPEND, "file.txt", - PATCH_ORIGINAL_TO_APPEND, NULL)); -} - -void test_apply_fromdiff__append_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_APPEND, "file.txt", - PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, &diff_opts)); -} - -void test_apply_fromdiff__prepend_and_append(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_APPEND, "file.txt", - PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, NULL)); -} - -void test_apply_fromdiff__to_empty_file(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - "", NULL, - PATCH_ORIGINAL_TO_EMPTY_FILE, NULL)); -} - -void test_apply_fromdiff__from_empty_file(void) -{ - cl_git_pass(apply_buf( - "", NULL, - FILE_ORIGINAL, "file.txt", - PATCH_EMPTY_FILE_TO_ORIGINAL, NULL)); -} - -void test_apply_fromdiff__add(void) -{ - cl_git_pass(apply_buf( - NULL, NULL, - FILE_ORIGINAL, "file.txt", - PATCH_ADD_ORIGINAL, NULL)); -} - -void test_apply_fromdiff__delete(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - NULL, NULL, - PATCH_DELETE_ORIGINAL, NULL)); -} - -void test_apply_fromdiff__no_change(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_ORIGINAL, "file.txt", - "", NULL)); -} - -void test_apply_fromdiff__binary_add(void) -{ - git_str newfile = GIT_STR_INIT; - - newfile.ptr = FILE_BINARY_DELTA_MODIFIED; - newfile.size = FILE_BINARY_DELTA_MODIFIED_LEN; - - cl_git_pass(apply_gitbuf( - NULL, NULL, - &newfile, "binary.bin", - NULL, &binary_opts)); -} - -void test_apply_fromdiff__binary_no_change(void) -{ - git_str original = GIT_STR_INIT; - - original.ptr = FILE_BINARY_DELTA_ORIGINAL; - original.size = FILE_BINARY_DELTA_ORIGINAL_LEN; - - cl_git_pass(apply_gitbuf( - &original, "binary.bin", - &original, "binary.bin", - "", &binary_opts)); -} - -void test_apply_fromdiff__binary_change_delta(void) -{ - git_str original = GIT_STR_INIT, modified = GIT_STR_INIT; - - original.ptr = FILE_BINARY_DELTA_ORIGINAL; - original.size = FILE_BINARY_DELTA_ORIGINAL_LEN; - - modified.ptr = FILE_BINARY_DELTA_MODIFIED; - modified.size = FILE_BINARY_DELTA_MODIFIED_LEN; - - cl_git_pass(apply_gitbuf( - &original, "binary.bin", - &modified, "binary.bin", - NULL, &binary_opts)); -} - -void test_apply_fromdiff__binary_change_literal(void) -{ - git_str original = GIT_STR_INIT, modified = GIT_STR_INIT; - - original.ptr = FILE_BINARY_LITERAL_ORIGINAL; - original.size = FILE_BINARY_LITERAL_ORIGINAL_LEN; - - modified.ptr = FILE_BINARY_LITERAL_MODIFIED; - modified.size = FILE_BINARY_LITERAL_MODIFIED_LEN; - - cl_git_pass(apply_gitbuf( - &original, "binary.bin", - &modified, "binary.bin", - NULL, &binary_opts)); -} - -void test_apply_fromdiff__binary_delete(void) -{ - git_str original = GIT_STR_INIT; - - original.ptr = FILE_BINARY_DELTA_MODIFIED; - original.size = FILE_BINARY_DELTA_MODIFIED_LEN; - - cl_git_pass(apply_gitbuf( - &original, "binary.bin", - NULL, NULL, - NULL, &binary_opts)); -} - -void test_apply_fromdiff__patching_correctly_truncates_source(void) -{ - git_str original = GIT_STR_INIT, patched = GIT_STR_INIT; - git_patch *patch; - unsigned int mode; - char *path; - - cl_git_pass(git_patch_from_buffers(&patch, - "foo\nbar", 7, "file.txt", - "foo\nfoo", 7, "file.txt", NULL)); - - /* - * Previously, we would fail to correctly truncate the source buffer if - * the source has more than one line and ends with a non-newline - * character. In the following call, we thus truncate the source string - * in the middle of the second line. Without the bug fixed, we would - * successfully apply the patch to the source and return success. With - * the overflow being fixed, we should return an error. - */ - cl_git_fail_with(GIT_EAPPLYFAIL, - git_apply__patch(&patched, &path, &mode, - "foo\nbar\n", 5, patch, NULL)); - - /* Verify that the patch succeeds if we do not truncate */ - cl_git_pass(git_apply__patch(&patched, &path, &mode, - "foo\nbar\n", 7, patch, NULL)); - - git_str_dispose(&original); - git_str_dispose(&patched); - git_patch_free(patch); - git__free(path); -} diff --git a/tests/apply/fromfile.c b/tests/apply/fromfile.c deleted file mode 100644 index 8cde62392..000000000 --- a/tests/apply/fromfile.c +++ /dev/null @@ -1,449 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/repository.h" - -#include "apply.h" -#include "patch.h" -#include "patch_parse.h" -#include "repository.h" - -#include "../patch/patch_common.h" - -static git_repository *repo = NULL; - -void test_apply_fromfile__initialize(void) -{ - repo = cl_git_sandbox_init("renames"); -} - -void test_apply_fromfile__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int apply_patchfile( - const char *old, - size_t old_len, - const char *new, - size_t new_len, - const char *patchfile, - const char *filename_expected, - unsigned int mode_expected) -{ - git_patch *patch; - git_str result = GIT_STR_INIT; - git_str patchbuf = GIT_STR_INIT; - char *filename; - unsigned int mode; - int error; - - cl_git_pass(git_patch_from_buffer(&patch, patchfile, strlen(patchfile), NULL)); - - error = git_apply__patch(&result, &filename, &mode, old, old_len, patch, NULL); - - if (error == 0) { - cl_assert_equal_i(new_len, result.size); - if (new_len) - cl_assert(memcmp(new, result.ptr, new_len) == 0); - - cl_assert_equal_s(filename_expected, filename); - cl_assert_equal_i(mode_expected, mode); - } - - git__free(filename); - git_str_dispose(&result); - git_str_dispose(&patchbuf); - git_patch_free(patch); - - return error; -} - -static int validate_and_apply_patchfile( - const char *old, - size_t old_len, - const char *new, - size_t new_len, - const char *patchfile, - const git_diff_options *diff_opts, - const char *filename_expected, - unsigned int mode_expected) -{ - git_patch *patch_fromdiff; - git_buf validated = GIT_BUF_INIT; - int error; - - cl_git_pass(git_patch_from_buffers(&patch_fromdiff, - old, old_len, "file.txt", - new, new_len, "file.txt", - diff_opts)); - cl_git_pass(git_patch_to_buf(&validated, patch_fromdiff)); - - cl_assert_equal_s(patchfile, validated.ptr); - - error = apply_patchfile(old, old_len, new, new_len, patchfile, filename_expected, mode_expected); - - git_buf_dispose(&validated); - git_patch_free(patch_fromdiff); - - return error; -} - -void test_apply_fromfile__change_middle(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_ORIGINAL_TO_CHANGE_MIDDLE, NULL, - "file.txt", 0100644)); -} - -void test_apply_fromfile__change_middle_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, - &diff_opts, "file.txt", 0100644)); -} - - -void test_apply_fromfile__change_firstline(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_FIRSTLINE, strlen(FILE_CHANGE_FIRSTLINE), - PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, NULL, - "file.txt", 0100644)); -} - -void test_apply_fromfile__lastline(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_LASTLINE, strlen(FILE_CHANGE_LASTLINE), - PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL, - "file.txt", 0100644)); -} - -void test_apply_fromfile__change_middle_shrink(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE_SHRINK, strlen(FILE_CHANGE_MIDDLE_SHRINK), - PATCH_ORIGINAL_TO_CHANGE_MIDDLE_SHRINK, NULL, - "file.txt", 0100644)); -} - -void test_apply_fromfile__change_middle_shrink_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE_SHRINK, strlen(FILE_CHANGE_MIDDLE_SHRINK), - PATCH_ORIGINAL_TO_MIDDLE_SHRINK_NOCONTEXT, &diff_opts, - "file.txt", 0100644)); -} - -void test_apply_fromfile__change_middle_grow(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE_GROW, strlen(FILE_CHANGE_MIDDLE_GROW), - PATCH_ORIGINAL_TO_CHANGE_MIDDLE_GROW, NULL, - "file.txt", 0100644)); -} - -void test_apply_fromfile__change_middle_grow_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE_GROW, strlen(FILE_CHANGE_MIDDLE_GROW), - PATCH_ORIGINAL_TO_MIDDLE_GROW_NOCONTEXT, &diff_opts, - "file.txt", 0100644)); -} - -void test_apply_fromfile__prepend(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_PREPEND, strlen(FILE_PREPEND), - PATCH_ORIGINAL_TO_PREPEND, NULL, "file.txt", 0100644)); -} - -void test_apply_fromfile__prepend_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_PREPEND, strlen(FILE_PREPEND), - PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts, - "file.txt", 0100644)); -} - -void test_apply_fromfile__append(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_APPEND, strlen(FILE_APPEND), - PATCH_ORIGINAL_TO_APPEND, NULL, "file.txt", 0100644)); -} - -void test_apply_fromfile__append_nocontext(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_APPEND, strlen(FILE_APPEND), - PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, &diff_opts, - "file.txt", 0100644)); -} - -void test_apply_fromfile__prepend_and_append(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_PREPEND_AND_APPEND, strlen(FILE_PREPEND_AND_APPEND), - PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, NULL, - "file.txt", 0100644)); -} - -void test_apply_fromfile__to_empty_file(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - "", 0, - PATCH_ORIGINAL_TO_EMPTY_FILE, NULL, "file.txt", 0100644)); -} - -void test_apply_fromfile__from_empty_file(void) -{ - cl_git_pass(validate_and_apply_patchfile( - "", 0, - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - PATCH_EMPTY_FILE_TO_ORIGINAL, NULL, "file.txt", 0100644)); -} - -void test_apply_fromfile__add(void) -{ - cl_git_pass(validate_and_apply_patchfile( - NULL, 0, - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - PATCH_ADD_ORIGINAL, NULL, "file.txt", 0100644)); -} - -void test_apply_fromfile__delete(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - NULL, 0, - PATCH_DELETE_ORIGINAL, NULL, NULL, 0)); -} - - -void test_apply_fromfile__rename_exact(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - PATCH_RENAME_EXACT, "newfile.txt", 0100644)); -} - -void test_apply_fromfile__rename_similar(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_RENAME_SIMILAR, "newfile.txt", 0100644)); -} - -void test_apply_fromfile__rename_similar_quotedname(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_RENAME_SIMILAR_QUOTEDNAME, "foo\"bar.txt", 0100644)); -} - -void test_apply_fromfile__modechange(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - PATCH_MODECHANGE_UNCHANGED, "file.txt", 0100755)); -} - -void test_apply_fromfile__modechange_with_modification(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_MODECHANGE_MODIFIED, "file.txt", 0100755)); -} - -void test_apply_fromfile__noisy(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_NOISY, "file.txt", 0100644)); -} - -void test_apply_fromfile__noisy_nocontext(void) -{ - cl_git_pass(apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), - PATCH_NOISY_NOCONTEXT, "file.txt", 0100644)); -} - -void test_apply_fromfile__fail_truncated_1(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_1, - strlen(PATCH_TRUNCATED_1), NULL)); -} - -void test_apply_fromfile__fail_truncated_2(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_2, - strlen(PATCH_TRUNCATED_2), NULL)); -} - -void test_apply_fromfile__fail_truncated_3(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_3, - strlen(PATCH_TRUNCATED_3), NULL)); -} - -void test_apply_fromfile__fail_corrupt_githeader(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_CORRUPT_GIT_HEADER, - strlen(PATCH_CORRUPT_GIT_HEADER), NULL)); -} - -void test_apply_fromfile__empty_context(void) -{ - cl_git_pass(apply_patchfile( - FILE_EMPTY_CONTEXT_ORIGINAL, strlen(FILE_EMPTY_CONTEXT_ORIGINAL), - FILE_EMPTY_CONTEXT_MODIFIED, strlen(FILE_EMPTY_CONTEXT_MODIFIED), - PATCH_EMPTY_CONTEXT, - "file.txt", 0100644)); -} - -void test_apply_fromfile__append_no_nl(void) -{ - cl_git_pass(validate_and_apply_patchfile( - FILE_ORIGINAL, strlen(FILE_ORIGINAL), - FILE_APPEND_NO_NL, strlen(FILE_APPEND_NO_NL), - PATCH_APPEND_NO_NL, NULL, "file.txt", 0100644)); -} - -void test_apply_fromfile__fail_missing_new_file(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, - PATCH_CORRUPT_MISSING_NEW_FILE, - strlen(PATCH_CORRUPT_MISSING_NEW_FILE), NULL)); -} - -void test_apply_fromfile__fail_missing_old_file(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, - PATCH_CORRUPT_MISSING_OLD_FILE, - strlen(PATCH_CORRUPT_MISSING_OLD_FILE), NULL)); -} - -void test_apply_fromfile__fail_no_changes(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, - PATCH_CORRUPT_NO_CHANGES, - strlen(PATCH_CORRUPT_NO_CHANGES), NULL)); -} - -void test_apply_fromfile__fail_missing_hunk_header(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, - PATCH_CORRUPT_MISSING_HUNK_HEADER, - strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL)); -} - -void test_apply_fromfile__fail_not_a_patch(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_NOT_A_PATCH, - strlen(PATCH_NOT_A_PATCH), NULL)); -} - -void test_apply_fromfile__binary_add(void) -{ - cl_git_pass(apply_patchfile( - NULL, 0, - FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, - PATCH_BINARY_ADD, "binary.bin", 0100644)); -} - -void test_apply_fromfile__binary_change_delta(void) -{ - cl_git_pass(apply_patchfile( - FILE_BINARY_DELTA_ORIGINAL, FILE_BINARY_DELTA_ORIGINAL_LEN, - FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, - PATCH_BINARY_DELTA, "binary.bin", 0100644)); -} - -void test_apply_fromfile__binary_change_literal(void) -{ - cl_git_pass(apply_patchfile( - FILE_BINARY_LITERAL_ORIGINAL, FILE_BINARY_LITERAL_ORIGINAL_LEN, - FILE_BINARY_LITERAL_MODIFIED, FILE_BINARY_LITERAL_MODIFIED_LEN, - PATCH_BINARY_LITERAL, "binary.bin", 0100644)); -} - -void test_apply_fromfile__binary_delete(void) -{ - cl_git_pass(apply_patchfile( - FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, - NULL, 0, - PATCH_BINARY_DELETE, NULL, 0)); -} - -void test_apply_fromfile__binary_change_does_not_apply(void) -{ - /* try to apply patch backwards, ensure it does not apply */ - cl_git_fail(apply_patchfile( - FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, - FILE_BINARY_DELTA_ORIGINAL, FILE_BINARY_DELTA_ORIGINAL_LEN, - PATCH_BINARY_DELTA, "binary.bin", 0100644)); -} - -void test_apply_fromfile__binary_change_must_be_reversible(void) -{ - cl_git_fail(apply_patchfile( - FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, - NULL, 0, - PATCH_BINARY_NOT_REVERSIBLE, NULL, 0)); -} - -void test_apply_fromfile__empty_file_not_allowed(void) -{ - git_patch *patch; - - cl_git_fail(git_patch_from_buffer(&patch, "", 0, NULL)); - cl_git_fail(git_patch_from_buffer(&patch, NULL, 0, NULL)); -} diff --git a/tests/apply/index.c b/tests/apply/index.c deleted file mode 100644 index 9c9094cce..000000000 --- a/tests/apply/index.c +++ /dev/null @@ -1,321 +0,0 @@ -#include "clar_libgit2.h" -#include "apply_helpers.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - -void test_apply_index__initialize(void) -{ - git_oid oid; - git_commit *commit; - - repo = cl_git_sandbox_init(TEST_REPO_PATH); - - git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); - git_commit_free(commit); -} - -void test_apply_index__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_apply_index__generate_diff(void) -{ - git_oid a_oid, b_oid; - git_commit *a_commit, *b_commit; - git_tree *a_tree, *b_tree; - git_diff *diff; - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - - struct merge_index_entry index_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f"); - cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); - cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); - - cl_git_pass(git_commit_tree(&a_tree, a_commit)); - cl_git_pass(git_commit_tree(&b_tree, b_commit)); - - cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &diff_opts)); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); - git_tree_free(a_tree); - git_tree_free(b_tree); - git_commit_free(a_commit); - git_commit_free(b_commit); -} - -void test_apply_index__parsed_diff(void) -{ - git_diff *diff; - - struct merge_index_entry index_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_index__removes_file(void) -{ - git_diff *diff; - - struct merge_index_entry index_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_FILE, - strlen(DIFF_DELETE_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_index__adds_file(void) -{ - git_diff *diff; - - struct merge_index_entry index_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_index__modified_workdir_with_unmodified_index_is_ok(void) -{ - git_diff *diff; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - struct merge_index_entry workdir_expected[] = { - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "f75ba05f340c51065cbea2e1fdbfe5fe13144c97", 0, "veal.txt" } - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - /* mutate the workdir and leave the index matching HEAD */ - cl_git_rmfile("merge-recursive/asparagus.txt"); - cl_git_rewritefile("merge-recursive/veal.txt", "Hello, world.\n"); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_index__application_failure_leaves_index_unmodified(void) -{ - git_diff *diff; - git_index *index; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - /* mutate the index */ - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_remove(index, "veal.txt", 0)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_index__keeps_nonconflicting_changes(void) -{ - git_diff *diff; - git_index *index; - git_index_entry idx_entry; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "beef.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - /* mutate the index */ - cl_git_pass(git_repository_index(&index, repo)); - - memset(&idx_entry, 0, sizeof(git_index_entry)); - idx_entry.mode = 0100644; - idx_entry.path = "beef.txt"; - cl_git_pass(git_oid_fromstr(&idx_entry.id, "898d12687fb35be271c27c795a6b32c8b51da79e")); - cl_git_pass(git_index_add(index, &idx_entry)); - - cl_git_pass(git_index_remove(index, "bouilli.txt", 0)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_index__can_apply_nonconflicting_file_changes(void) -{ - git_diff *diff; - git_index *index; - git_index_entry idx_entry; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "4f2d1645dee99ced096877911de540c65ade2ef8", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - /* - * Replace the index entry with a version that is different than - * HEAD but such that the patch still applies cleanly. This item - * has a new line appended. - */ - - cl_git_pass(git_repository_index(&index, repo)); - - memset(&idx_entry, 0, sizeof(git_index_entry)); - idx_entry.mode = 0100644; - idx_entry.path = "asparagus.txt"; - cl_git_pass(git_oid_fromstr(&idx_entry.id, "06d3fefb8726ab1099acc76e02dfb85e034b2538")); - cl_git_pass(git_index_add(index, &idx_entry)); - - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} - -void test_apply_index__change_mode(void) -{ - git_diff *diff; - - const char *diff_file = DIFF_EXECUTABLE_FILE; - - struct merge_index_entry index_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100755, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_workdir_unchanged(repo); - - git_diff_free(diff); -} diff --git a/tests/apply/partial.c b/tests/apply/partial.c deleted file mode 100644 index fd4908d4b..000000000 --- a/tests/apply/partial.c +++ /dev/null @@ -1,231 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/repository.h" - -#include "apply.h" -#include "repository.h" - -#include "../patch/patch_common.h" - -static git_repository *repo = NULL; - -void test_apply_partial__initialize(void) -{ - repo = cl_git_sandbox_init("renames"); -} - -void test_apply_partial__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int skip_addition( - const git_diff_hunk *hunk, - void *payload) -{ - GIT_UNUSED(payload); - - return (hunk->new_lines > hunk->old_lines) ? 1 : 0; -} - -static int skip_deletion( - const git_diff_hunk *hunk, - void *payload) -{ - GIT_UNUSED(payload); - - return (hunk->new_lines < hunk->old_lines) ? 1 : 0; -} - -static int skip_change( - const git_diff_hunk *hunk, - void *payload) -{ - GIT_UNUSED(payload); - - return (hunk->new_lines == hunk->old_lines) ? 1 : 0; -} - -static int abort_addition( - const git_diff_hunk *hunk, - void *payload) -{ - GIT_UNUSED(payload); - - return (hunk->new_lines > hunk->old_lines) ? GIT_EUSER : 0; -} - -static int abort_deletion( - const git_diff_hunk *hunk, - void *payload) -{ - GIT_UNUSED(payload); - - return (hunk->new_lines < hunk->old_lines) ? GIT_EUSER : 0; -} - -static int abort_change( - const git_diff_hunk *hunk, - void *payload) -{ - GIT_UNUSED(payload); - - return (hunk->new_lines == hunk->old_lines) ? GIT_EUSER : 0; -} - -static int apply_buf( - const char *old, - const char *oldname, - const char *new, - const char *newname, - const char *expected, - const git_diff_options *diff_opts, - git_apply_hunk_cb hunk_cb, - void *payload) -{ - git_patch *patch; - git_str result = GIT_STR_INIT; - git_str patchbuf = GIT_STR_INIT; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - char *filename; - unsigned int mode; - int error; - size_t oldsize = strlen(old); - size_t newsize = strlen(new); - - opts.hunk_cb = hunk_cb; - opts.payload = payload; - - cl_git_pass(git_patch_from_buffers(&patch, old, oldsize, oldname, new, newsize, newname, diff_opts)); - if ((error = git_apply__patch(&result, &filename, &mode, old, oldsize, patch, &opts)) == 0) { - cl_assert_equal_s(expected, result.ptr); - cl_assert_equal_s(newname, filename); - cl_assert_equal_i(0100644, mode); - } - - git__free(filename); - git_str_dispose(&result); - git_str_dispose(&patchbuf); - git_patch_free(patch); - - return error; -} - -void test_apply_partial__prepend_and_change_skip_addition(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_CHANGE, "file.txt", - FILE_ORIGINAL, NULL, skip_addition, NULL)); -} - -void test_apply_partial__prepend_and_change_nocontext_skip_addition(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_CHANGE, "file.txt", - FILE_CHANGE_MIDDLE, &diff_opts, skip_addition, NULL)); -} - -void test_apply_partial__prepend_and_change_nocontext_abort_addition(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_fail(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_CHANGE, "file.txt", - FILE_ORIGINAL, &diff_opts, abort_addition, NULL)); -} - -void test_apply_partial__prepend_and_change_skip_change(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_CHANGE, "file.txt", - FILE_PREPEND_AND_CHANGE, NULL, skip_change, NULL)); -} - -void test_apply_partial__prepend_and_change_nocontext_skip_change(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_CHANGE, "file.txt", - FILE_PREPEND, &diff_opts, skip_change, NULL)); -} - -void test_apply_partial__prepend_and_change_nocontext_abort_change(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_fail(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_PREPEND_AND_CHANGE, "file.txt", - FILE_PREPEND, &diff_opts, abort_change, NULL)); -} - -void test_apply_partial__delete_and_change_skip_deletion(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_AND_CHANGE, "file.txt", - FILE_ORIGINAL, NULL, skip_deletion, NULL)); -} - -void test_apply_partial__delete_and_change_nocontext_skip_deletion(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_AND_CHANGE, "file.txt", - FILE_CHANGE_MIDDLE, &diff_opts, skip_deletion, NULL)); -} - -void test_apply_partial__delete_and_change_nocontext_abort_deletion(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_fail(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_AND_CHANGE, "file.txt", - FILE_ORIGINAL, &diff_opts, abort_deletion, NULL)); -} - -void test_apply_partial__delete_and_change_skip_change(void) -{ - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_AND_CHANGE, "file.txt", - FILE_DELETE_AND_CHANGE, NULL, skip_change, NULL)); -} - -void test_apply_partial__delete_and_change_nocontext_skip_change(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_pass(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_AND_CHANGE, "file.txt", - FILE_DELETE_FIRSTLINE, &diff_opts, skip_change, NULL)); -} - -void test_apply_partial__delete_and_change_nocontext_abort_change(void) -{ - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - diff_opts.context_lines = 0; - - cl_git_fail(apply_buf( - FILE_ORIGINAL, "file.txt", - FILE_DELETE_AND_CHANGE, "file.txt", - FILE_DELETE_FIRSTLINE, &diff_opts, abort_change, NULL)); -} diff --git a/tests/apply/tree.c b/tests/apply/tree.c deleted file mode 100644 index 5154f134f..000000000 --- a/tests/apply/tree.c +++ /dev/null @@ -1,94 +0,0 @@ -#include "clar_libgit2.h" -#include "apply_helpers.h" -#include "../merge/merge_helpers.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - - -void test_apply_tree__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_apply_tree__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_apply_tree__one(void) -{ - git_oid a_oid, b_oid; - git_commit *a_commit, *b_commit; - git_tree *a_tree, *b_tree; - git_diff *diff; - git_index *index = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - - struct merge_index_entry expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - - git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f"); - - cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); - cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); - - cl_git_pass(git_commit_tree(&a_tree, a_commit)); - cl_git_pass(git_commit_tree(&b_tree, b_commit)); - - cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &opts)); - - cl_git_pass(git_apply_to_tree(&index, repo, a_tree, diff, NULL)); - merge_test_index(index, expected, 6); - - git_index_free(index); - git_diff_free(diff); - git_tree_free(a_tree); - git_tree_free(b_tree); - git_commit_free(a_commit); - git_commit_free(b_commit); -} - -void test_apply_tree__adds_file(void) -{ - git_oid a_oid; - git_commit *a_commit; - git_tree *a_tree; - git_diff *diff; - git_index *index = NULL; - - struct merge_index_entry expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - - git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - - cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); - - cl_git_pass(git_commit_tree(&a_tree, a_commit)); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); - - cl_git_pass(git_apply_to_tree(&index, repo, a_tree, diff, NULL)); - merge_test_index(index, expected, 7); - - git_index_free(index); - git_diff_free(diff); - git_tree_free(a_tree); - git_commit_free(a_commit); -} diff --git a/tests/apply/workdir.c b/tests/apply/workdir.c deleted file mode 100644 index d0d9c1aba..000000000 --- a/tests/apply/workdir.c +++ /dev/null @@ -1,358 +0,0 @@ -#include "clar_libgit2.h" -#include "apply_helpers.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - -void test_apply_workdir__initialize(void) -{ - git_oid oid; - git_commit *commit; - - repo = cl_git_sandbox_init(TEST_REPO_PATH); - - git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); - git_commit_free(commit); -} - -void test_apply_workdir__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_apply_workdir__generated_diff(void) -{ - git_oid a_oid, b_oid; - git_commit *a_commit, *b_commit; - git_tree *a_tree, *b_tree; - git_diff *diff; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f"); cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); - cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); - - cl_git_pass(git_commit_tree(&a_tree, a_commit)); - cl_git_pass(git_commit_tree(&b_tree, b_commit)); - - cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &opts)); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); - git_tree_free(a_tree); - git_tree_free(b_tree); - git_commit_free(a_commit); - git_commit_free(b_commit); -} - -void test_apply_workdir__parsed_diff(void) -{ - git_diff *diff; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__removes_file(void) -{ - git_diff *diff; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_FILE, - strlen(DIFF_DELETE_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__adds_file(void) -{ - git_diff *diff; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__modified_index_with_unmodified_workdir_is_ok(void) -{ - git_index *index; - git_index_entry idx_entry = {{0}}; - git_diff *diff; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry index_expected[] = { - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "veal.txt" } - }; - size_t index_expected_cnt = sizeof(index_expected) / - sizeof(struct merge_index_entry); - - struct merge_index_entry workdir_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - /* mutate the index and leave the workdir matching HEAD */ - cl_git_pass(git_repository_index(&index, repo)); - - idx_entry.mode = 0100644; - idx_entry.path = "veal.txt"; - cl_git_pass(git_oid_fromstr(&idx_entry.id, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d")); - - cl_git_pass(git_index_add(index, &idx_entry)); - cl_git_pass(git_index_remove(index, "asparagus.txt", 0)); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_apply_index(repo, index_expected, index_expected_cnt); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_index_free(index); - git_diff_free(diff); -} - -void test_apply_workdir__application_failure_leaves_workdir_unmodified(void) -{ - git_diff *diff; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "8684724651336001c5dbce74bed6736d2443958d", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - /* mutate the workdir */ - cl_git_rewritefile("merge-recursive/veal.txt", - "This is a modification.\n"); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__keeps_nonconflicting_changes(void) -{ - git_diff *diff; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "f75ba05f340c51065cbea2e1fdbfe5fe13144c97", 0, "gravy.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_rmfile("merge-recursive/oyster.txt"); - cl_git_rewritefile("merge-recursive/gravy.txt", "Hello, world.\n"); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__can_apply_nonconflicting_file_changes(void) -{ - git_diff *diff; - - const char *diff_file = DIFF_MODIFY_TWO_FILES; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "5db1a0fef164cb66cc0c00d35cc5af979ddc1a64", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - /* - * Replace the workdir file with a version that is different than - * HEAD but such that the patch still applies cleanly. This item - * has a new line appended. - */ - cl_git_append2file("merge-recursive/asparagus.txt", - "This line is added in the workdir.\n"); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__change_mode(void) -{ -#ifndef GIT_WIN32 - git_diff *diff; - - const char *diff_file = DIFF_EXECUTABLE_FILE; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100755, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -#endif -} - -void test_apply_workdir__apply_many_changes_one(void) -{ - git_diff *diff; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "c9d7d5d58088bc91f6e06f17ca3a205091568d3a", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MANY_CHANGES_ONE, strlen(DIFF_MANY_CHANGES_ONE))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} - -void test_apply_workdir__apply_many_changes_two(void) -{ - git_diff *diff; - git_apply_options opts = GIT_APPLY_OPTIONS_INIT; - - struct merge_index_entry workdir_expected[] = { - { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "6b943d65af6d8db74d747284fa4ca7d716ad5bbb", 0, "veal.txt" }, - }; - size_t workdir_expected_cnt = sizeof(workdir_expected) / - sizeof(struct merge_index_entry); - - cl_git_pass(git_diff_from_buffer(&diff, - DIFF_MANY_CHANGES_TWO, strlen(DIFF_MANY_CHANGES_TWO))); - cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); - - validate_index_unchanged(repo); - validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); - - git_diff_free(diff); -} diff --git a/tests/attr/attr_expect.h b/tests/attr/attr_expect.h deleted file mode 100644 index faf7a1181..000000000 --- a/tests/attr/attr_expect.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef __CLAR_TEST_ATTR_EXPECT__ -#define __CLAR_TEST_ATTR_EXPECT__ - -enum attr_expect_t { - EXPECT_FALSE, - EXPECT_TRUE, - EXPECT_UNDEFINED, - EXPECT_STRING -}; - -struct attr_expected { - const char *path; - const char *attr; - enum attr_expect_t expected; - const char *expected_str; -}; - -GIT_INLINE(void) attr_check_expected( - enum attr_expect_t expected, - const char *expected_str, - const char *name, - const char *value) -{ - switch (expected) { - case EXPECT_TRUE: - cl_assert_(GIT_ATTR_IS_TRUE(value), name); - break; - - case EXPECT_FALSE: - cl_assert_(GIT_ATTR_IS_FALSE(value), name); - break; - - case EXPECT_UNDEFINED: - cl_assert_(GIT_ATTR_IS_UNSPECIFIED(value), name); - break; - - case EXPECT_STRING: - cl_assert_equal_s(expected_str, value); - break; - } -} - -#endif diff --git a/tests/attr/file.c b/tests/attr/file.c deleted file mode 100644 index 1ac48c0fe..000000000 --- a/tests/attr/file.c +++ /dev/null @@ -1,243 +0,0 @@ -#include "clar_libgit2.h" -#include "attr_file.h" -#include "attr_expect.h" - -#define get_rule(X) ((git_attr_rule *)git_vector_get(&file->rules,(X))) -#define get_assign(R,Y) ((git_attr_assignment *)git_vector_get(&(R)->assigns,(Y))) - -void test_attr_file__simple_read(void) -{ - git_attr_file *file; - git_attr_assignment *assign; - git_attr_rule *rule; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr0"))); - - cl_assert_equal_s(cl_fixture("attr/attr0"), file->entry->path); - cl_assert(file->rules.length == 1); - - rule = get_rule(0); - cl_assert(rule != NULL); - cl_assert_equal_s("*", rule->match.pattern); - cl_assert(rule->match.length == 1); - cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); - - cl_assert(rule->assigns.length == 1); - assign = get_assign(rule, 0); - cl_assert(assign != NULL); - cl_assert_equal_s("binary", assign->name); - cl_assert(GIT_ATTR_IS_TRUE(assign->value)); - - git_attr_file__free(file); -} - -void test_attr_file__match_variants(void) -{ - git_attr_file *file; - git_attr_rule *rule; - git_attr_assignment *assign; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr1"))); - - cl_assert_equal_s(cl_fixture("attr/attr1"), file->entry->path); - cl_assert(file->rules.length == 10); - - /* let's do a thorough check of this rule, then just verify - * the things that are unique for the later rules - */ - rule = get_rule(0); - cl_assert(rule); - cl_assert_equal_s("pat0", rule->match.pattern); - cl_assert(rule->match.length == strlen("pat0")); - cl_assert(rule->assigns.length == 1); - assign = get_assign(rule,0); - cl_assert_equal_s("attr0", assign->name); - cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name)); - cl_assert(GIT_ATTR_IS_TRUE(assign->value)); - - rule = get_rule(1); - cl_assert_equal_s("pat1", rule->match.pattern); - cl_assert(rule->match.length == strlen("pat1")); - cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0); - - rule = get_rule(2); - cl_assert_equal_s("pat2", rule->match.pattern); - cl_assert(rule->match.length == strlen("pat2")); - cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_DIRECTORY) != 0); - - rule = get_rule(3); - cl_assert_equal_s("pat3dir/pat3file", rule->match.pattern); - cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_FULLPATH) != 0); - - rule = get_rule(4); - cl_assert_equal_s("pat4.*", rule->match.pattern); - cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); - - rule = get_rule(5); - cl_assert_equal_s("*.pat5", rule->match.pattern); - cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); - - rule = get_rule(7); - cl_assert_equal_s("pat7[a-e]??[xyz]", rule->match.pattern); - cl_assert(rule->assigns.length == 1); - cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); - assign = get_assign(rule,0); - cl_assert_equal_s("attr7", assign->name); - cl_assert(GIT_ATTR_IS_TRUE(assign->value)); - - rule = get_rule(8); - cl_assert_equal_s("pat8 with spaces", rule->match.pattern); - cl_assert(rule->match.length == strlen("pat8 with spaces")); - - rule = get_rule(9); - cl_assert_equal_s("pat9", rule->match.pattern); - - git_attr_file__free(file); -} - -static void check_one_assign( - git_attr_file *file, - int rule_idx, - int assign_idx, - const char *pattern, - const char *name, - enum attr_expect_t expected, - const char *expected_str) -{ - git_attr_rule *rule = get_rule(rule_idx); - git_attr_assignment *assign = get_assign(rule, assign_idx); - - cl_assert_equal_s(pattern, rule->match.pattern); - cl_assert(rule->assigns.length == 1); - cl_assert_equal_s(name, assign->name); - cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name)); - - attr_check_expected(expected, expected_str, assign->name, assign->value); -} - -void test_attr_file__assign_variants(void) -{ - git_attr_file *file; - git_attr_rule *rule; - git_attr_assignment *assign; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr2"))); - - cl_assert_equal_s(cl_fixture("attr/attr2"), file->entry->path); - cl_assert(file->rules.length == 11); - - check_one_assign(file, 0, 0, "pat0", "simple", EXPECT_TRUE, NULL); - check_one_assign(file, 1, 0, "pat1", "neg", EXPECT_FALSE, NULL); - check_one_assign(file, 2, 0, "*", "notundef", EXPECT_TRUE, NULL); - check_one_assign(file, 3, 0, "pat2", "notundef", EXPECT_UNDEFINED, NULL); - check_one_assign(file, 4, 0, "pat3", "assigned", EXPECT_STRING, "test-value"); - check_one_assign(file, 5, 0, "pat4", "rule-with-more-chars", EXPECT_STRING, "value-with-more-chars"); - check_one_assign(file, 6, 0, "pat5", "empty", EXPECT_TRUE, NULL); - check_one_assign(file, 7, 0, "pat6", "negempty", EXPECT_FALSE, NULL); - - rule = get_rule(8); - cl_assert_equal_s("pat7", rule->match.pattern); - cl_assert(rule->assigns.length == 5); - /* assignments will be sorted by hash value, so we have to do - * lookups by search instead of by position - */ - assign = git_attr_rule__lookup_assignment(rule, "multiple"); - cl_assert(assign); - cl_assert_equal_s("multiple", assign->name); - cl_assert(GIT_ATTR_IS_TRUE(assign->value)); - assign = git_attr_rule__lookup_assignment(rule, "single"); - cl_assert(assign); - cl_assert_equal_s("single", assign->name); - cl_assert(GIT_ATTR_IS_FALSE(assign->value)); - assign = git_attr_rule__lookup_assignment(rule, "values"); - cl_assert(assign); - cl_assert_equal_s("values", assign->name); - cl_assert_equal_s("1", assign->value); - assign = git_attr_rule__lookup_assignment(rule, "also"); - cl_assert(assign); - cl_assert_equal_s("also", assign->name); - cl_assert_equal_s("a-really-long-value/*", assign->value); - assign = git_attr_rule__lookup_assignment(rule, "happy"); - cl_assert(assign); - cl_assert_equal_s("happy", assign->name); - cl_assert_equal_s("yes!", assign->value); - assign = git_attr_rule__lookup_assignment(rule, "other"); - cl_assert(!assign); - - rule = get_rule(9); - cl_assert_equal_s("pat8", rule->match.pattern); - cl_assert(rule->assigns.length == 2); - assign = git_attr_rule__lookup_assignment(rule, "again"); - cl_assert(assign); - cl_assert_equal_s("again", assign->name); - cl_assert(GIT_ATTR_IS_TRUE(assign->value)); - assign = git_attr_rule__lookup_assignment(rule, "another"); - cl_assert(assign); - cl_assert_equal_s("another", assign->name); - cl_assert_equal_s("12321", assign->value); - - check_one_assign(file, 10, 0, "pat9", "at-eof", EXPECT_FALSE, NULL); - - git_attr_file__free(file); -} - -static void assert_examples(git_attr_file *file) -{ - git_attr_rule *rule; - git_attr_assignment *assign; - - rule = get_rule(0); - cl_assert_equal_s("*.java", rule->match.pattern); - cl_assert(rule->assigns.length == 3); - assign = git_attr_rule__lookup_assignment(rule, "diff"); - cl_assert_equal_s("diff", assign->name); - cl_assert_equal_s("java", assign->value); - assign = git_attr_rule__lookup_assignment(rule, "crlf"); - cl_assert_equal_s("crlf", assign->name); - cl_assert(GIT_ATTR_IS_FALSE(assign->value)); - assign = git_attr_rule__lookup_assignment(rule, "myAttr"); - cl_assert_equal_s("myAttr", assign->name); - cl_assert(GIT_ATTR_IS_TRUE(assign->value)); - assign = git_attr_rule__lookup_assignment(rule, "missing"); - cl_assert(assign == NULL); - - rule = get_rule(1); - cl_assert_equal_s("NoMyAttr.java", rule->match.pattern); - cl_assert(rule->assigns.length == 1); - assign = get_assign(rule, 0); - cl_assert_equal_s("myAttr", assign->name); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(assign->value)); - - rule = get_rule(2); - cl_assert_equal_s("README", rule->match.pattern); - cl_assert(rule->assigns.length == 1); - assign = get_assign(rule, 0); - cl_assert_equal_s("caveat", assign->name); - cl_assert_equal_s("unspecified", assign->value); -} - -void test_attr_file__check_attr_examples(void) -{ - git_attr_file *file; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr3"))); - cl_assert_equal_s(cl_fixture("attr/attr3"), file->entry->path); - cl_assert(file->rules.length == 3); - - assert_examples(file); - - git_attr_file__free(file); -} - -void test_attr_file__whitespace(void) -{ - git_attr_file *file; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr4"))); - cl_assert_equal_s(cl_fixture("attr/attr4"), file->entry->path); - cl_assert(file->rules.length == 3); - - assert_examples(file); - - git_attr_file__free(file); -} diff --git a/tests/attr/flags.c b/tests/attr/flags.c deleted file mode 100644 index 6470ec732..000000000 --- a/tests/attr/flags.c +++ /dev/null @@ -1,108 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/attr.h" - -void test_attr_flags__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_attr_flags__bare(void) -{ - git_repository *repo = cl_git_sandbox_init("testrepo.git"); - const char *value; - - cl_assert(git_repository_is_bare(repo)); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM, "README.md", "diff")); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(value)); -} - -void test_attr_flags__index_vs_workdir(void) -{ - git_repository *repo = cl_git_sandbox_init("attr_index"); - const char *value; - - cl_assert(!git_repository_is_bare(repo)); - - /* wd then index */ - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, - "README.md", "bar")); - cl_assert(GIT_ATTR_IS_FALSE(value)); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, - "README.md", "blargh")); - cl_assert_equal_s(value, "goop"); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, - "README.txt", "foo")); - cl_assert(GIT_ATTR_IS_FALSE(value)); - - /* index then wd */ - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, - "README.md", "bar")); - cl_assert(GIT_ATTR_IS_TRUE(value)); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, - "README.md", "blargh")); - cl_assert_equal_s(value, "garble"); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, - "README.txt", "foo")); - cl_assert(GIT_ATTR_IS_TRUE(value)); -} - -void test_attr_flags__subdir(void) -{ - git_repository *repo = cl_git_sandbox_init("attr_index"); - const char *value; - - /* wd then index */ - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, - "sub/sub/README.md", "bar")); - cl_assert_equal_s(value, "1234"); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, - "sub/sub/README.txt", "another")); - cl_assert_equal_s(value, "one"); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, - "sub/sub/README.txt", "again")); - cl_assert(GIT_ATTR_IS_TRUE(value)); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, - "sub/sub/README.txt", "beep")); - cl_assert_equal_s(value, "10"); - - /* index then wd */ - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, - "sub/sub/README.md", "bar")); - cl_assert_equal_s(value, "1337"); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, - "sub/sub/README.txt", "another")); - cl_assert_equal_s(value, "one"); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, - "sub/sub/README.txt", "again")); - cl_assert(GIT_ATTR_IS_TRUE(value)); - - cl_git_pass(git_attr_get( - &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, - "sub/sub/README.txt", "beep")); - cl_assert_equal_s(value, "5"); -} - diff --git a/tests/attr/lookup.c b/tests/attr/lookup.c deleted file mode 100644 index f19f38fbb..000000000 --- a/tests/attr/lookup.c +++ /dev/null @@ -1,263 +0,0 @@ -#include "clar_libgit2.h" -#include "attr_file.h" - -#include "attr_expect.h" - -void test_attr_lookup__simple(void) -{ - git_attr_file *file; - git_attr_path path; - const char *value = NULL; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr0"))); - cl_assert_equal_s(cl_fixture("attr/attr0"), file->entry->path); - cl_assert(file->rules.length == 1); - - cl_git_pass(git_attr_path__init(&path, "test", NULL, GIT_DIR_FLAG_UNKNOWN)); - cl_assert_equal_s("test", path.path); - cl_assert_equal_s("test", path.basename); - cl_assert(!path.is_dir); - - cl_git_pass(git_attr_file__lookup_one(file,&path,"binary",&value)); - cl_assert(GIT_ATTR_IS_TRUE(value)); - - cl_git_pass(git_attr_file__lookup_one(file,&path,"missing",&value)); - cl_assert(!value); - - git_attr_path__free(&path); - git_attr_file__free(file); -} - -static void run_test_cases(git_attr_file *file, struct attr_expected *cases, int force_dir) -{ - git_attr_path path; - const char *value = NULL; - struct attr_expected *c; - int error; - - for (c = cases; c->path != NULL; c++) { - cl_git_pass(git_attr_path__init(&path, c->path, NULL, GIT_DIR_FLAG_UNKNOWN)); - - if (force_dir) - path.is_dir = 1; - - error = git_attr_file__lookup_one(file,&path,c->attr,&value); - cl_git_pass(error); - - attr_check_expected(c->expected, c->expected_str, c->attr, value); - - git_attr_path__free(&path); - } -} - -void test_attr_lookup__match_variants(void) -{ - git_attr_file *file; - git_attr_path path; - - struct attr_expected dir_cases[] = { - { "pat2", "attr2", EXPECT_TRUE, NULL }, - { "/testing/for/pat2", "attr2", EXPECT_TRUE, NULL }, - { "/not/pat2/yousee", "attr2", EXPECT_UNDEFINED, NULL }, - { "/fun/fun/fun/pat4.dir", "attr4", EXPECT_TRUE, NULL }, - { "foo.pat5", "attr5", EXPECT_TRUE, NULL }, - { NULL, NULL, 0, NULL } - }; - - struct attr_expected cases[] = { - /* pat0 -> simple match */ - { "pat0", "attr0", EXPECT_TRUE, NULL }, - { "/testing/for/pat0", "attr0", EXPECT_TRUE, NULL }, - { "relative/to/pat0", "attr0", EXPECT_TRUE, NULL }, - { "this-contains-pat0-inside", "attr0", EXPECT_UNDEFINED, NULL }, - { "this-aint-right", "attr0", EXPECT_UNDEFINED, NULL }, - { "/this/pat0/dont/match", "attr0", EXPECT_UNDEFINED, NULL }, - /* negative match */ - { "pat0", "attr1", EXPECT_TRUE, NULL }, - { "pat1", "attr1", EXPECT_UNDEFINED, NULL }, - { "/testing/for/pat1", "attr1", EXPECT_UNDEFINED, NULL }, - { "/testing/for/pat0", "attr1", EXPECT_TRUE, NULL }, - { "/testing/for/pat1/inside", "attr1", EXPECT_TRUE, NULL }, - { "misc", "attr1", EXPECT_TRUE, NULL }, - /* dir match */ - { "pat2", "attr2", EXPECT_UNDEFINED, NULL }, - { "/testing/for/pat2", "attr2", EXPECT_UNDEFINED, NULL }, - { "/not/pat2/yousee", "attr2", EXPECT_UNDEFINED, NULL }, - /* path match */ - { "pat3file", "attr3", EXPECT_UNDEFINED, NULL }, - { "/pat3dir/pat3file", "attr3", EXPECT_TRUE, NULL }, - { "pat3dir/pat3file", "attr3", EXPECT_TRUE, NULL }, - /* pattern* match */ - { "pat4.txt", "attr4", EXPECT_TRUE, NULL }, - { "/fun/fun/fun/pat4.c", "attr4", EXPECT_TRUE, NULL }, - { "pat4.", "attr4", EXPECT_TRUE, NULL }, - { "pat4", "attr4", EXPECT_UNDEFINED, NULL }, - /* *pattern match */ - { "foo.pat5", "attr5", EXPECT_TRUE, NULL }, - { "/this/is/ok.pat5", "attr5", EXPECT_TRUE, NULL }, - { "/this/is/bad.pat5/yousee.txt", "attr5", EXPECT_UNDEFINED, NULL }, - { "foo.pat5", "attr100", EXPECT_UNDEFINED, NULL }, - /* glob match with slashes */ - { "foo.pat6", "attr6", EXPECT_UNDEFINED, NULL }, - { "pat6/pat6/foobar.pat6", "attr6", EXPECT_TRUE, NULL }, - { "pat6/pat6/.pat6", "attr6", EXPECT_TRUE, NULL }, - { "pat6/pat6/extra/foobar.pat6", "attr6", EXPECT_UNDEFINED, NULL }, - { "/prefix/pat6/pat6/foobar.pat6", "attr6", EXPECT_UNDEFINED, NULL }, - { "/pat6/pat6/foobar.pat6", "attr6", EXPECT_TRUE, NULL }, - /* complex pattern */ - { "pat7a12z", "attr7", EXPECT_TRUE, NULL }, - { "pat7e__x", "attr7", EXPECT_TRUE, NULL }, - { "pat7b/1y", "attr7", EXPECT_UNDEFINED, NULL }, /* ? does not match / */ - { "pat7e_x", "attr7", EXPECT_UNDEFINED, NULL }, - { "pat7aaaa", "attr7", EXPECT_UNDEFINED, NULL }, - { "pat7zzzz", "attr7", EXPECT_UNDEFINED, NULL }, - { "/this/can/be/anything/pat7a12z", "attr7", EXPECT_TRUE, NULL }, - { "but/it/still/must/match/pat7aaaa", "attr7", EXPECT_UNDEFINED, NULL }, - { "pat7aaay.fail", "attr7", EXPECT_UNDEFINED, NULL }, - /* pattern with spaces */ - { "pat8 with spaces", "attr8", EXPECT_TRUE, NULL }, - { "/gotta love/pat8 with spaces", "attr8", EXPECT_TRUE, NULL }, - { "failing pat8 with spaces", "attr8", EXPECT_UNDEFINED, NULL }, - { "spaces", "attr8", EXPECT_UNDEFINED, NULL }, - /* pattern at eof */ - { "pat9", "attr9", EXPECT_TRUE, NULL }, - { "/eof/pat9", "attr9", EXPECT_TRUE, NULL }, - { "pat", "attr9", EXPECT_UNDEFINED, NULL }, - { "at9", "attr9", EXPECT_UNDEFINED, NULL }, - { "pat9.fail", "attr9", EXPECT_UNDEFINED, NULL }, - /* sentinel at end */ - { NULL, NULL, 0, NULL } - }; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr1"))); - cl_assert_equal_s(cl_fixture("attr/attr1"), file->entry->path); - cl_assert(file->rules.length == 10); - - cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL, GIT_DIR_FLAG_UNKNOWN)); - cl_assert_equal_s("pat0", path.basename); - - run_test_cases(file, cases, 0); - run_test_cases(file, dir_cases, 1); - - git_attr_file__free(file); - git_attr_path__free(&path); -} - -void test_attr_lookup__assign_variants(void) -{ - git_attr_file *file; - - struct attr_expected cases[] = { - /* pat0 -> simple assign */ - { "pat0", "simple", EXPECT_TRUE, NULL }, - { "/testing/pat0", "simple", EXPECT_TRUE, NULL }, - { "pat0", "fail", EXPECT_UNDEFINED, NULL }, - { "/testing/pat0", "fail", EXPECT_UNDEFINED, NULL }, - /* negative assign */ - { "pat1", "neg", EXPECT_FALSE, NULL }, - { "/testing/pat1", "neg", EXPECT_FALSE, NULL }, - { "pat1", "fail", EXPECT_UNDEFINED, NULL }, - { "/testing/pat1", "fail", EXPECT_UNDEFINED, NULL }, - /* forced undef */ - { "pat1", "notundef", EXPECT_TRUE, NULL }, - { "pat2", "notundef", EXPECT_UNDEFINED, NULL }, - { "/lead/in/pat1", "notundef", EXPECT_TRUE, NULL }, - { "/lead/in/pat2", "notundef", EXPECT_UNDEFINED, NULL }, - /* assign value */ - { "pat3", "assigned", EXPECT_STRING, "test-value" }, - { "pat3", "notassigned", EXPECT_UNDEFINED, NULL }, - /* assign value */ - { "pat4", "rule-with-more-chars", EXPECT_STRING, "value-with-more-chars" }, - { "pat4", "notassigned-rule-with-more-chars", EXPECT_UNDEFINED, NULL }, - /* empty assignments */ - { "pat5", "empty", EXPECT_TRUE, NULL }, - { "pat6", "negempty", EXPECT_FALSE, NULL }, - /* multiple assignment */ - { "pat7", "multiple", EXPECT_TRUE, NULL }, - { "pat7", "single", EXPECT_FALSE, NULL }, - { "pat7", "values", EXPECT_STRING, "1" }, - { "pat7", "also", EXPECT_STRING, "a-really-long-value/*" }, - { "pat7", "happy", EXPECT_STRING, "yes!" }, - { "pat8", "again", EXPECT_TRUE, NULL }, - { "pat8", "another", EXPECT_STRING, "12321" }, - /* bad assignment */ - { "patbad0", "simple", EXPECT_UNDEFINED, NULL }, - { "patbad0", "notundef", EXPECT_TRUE, NULL }, - { "patbad1", "simple", EXPECT_UNDEFINED, NULL }, - /* eof assignment */ - { "pat9", "at-eof", EXPECT_FALSE, NULL }, - /* sentinel at end */ - { NULL, NULL, 0, NULL } - }; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr2"))); - cl_assert(file->rules.length == 11); - - run_test_cases(file, cases, 0); - - git_attr_file__free(file); -} - -void test_attr_lookup__check_attr_examples(void) -{ - git_attr_file *file; - - struct attr_expected cases[] = { - { "foo.java", "diff", EXPECT_STRING, "java" }, - { "foo.java", "crlf", EXPECT_FALSE, NULL }, - { "foo.java", "myAttr", EXPECT_TRUE, NULL }, - { "foo.java", "other", EXPECT_UNDEFINED, NULL }, - { "/prefix/dir/foo.java", "diff", EXPECT_STRING, "java" }, - { "/prefix/dir/foo.java", "crlf", EXPECT_FALSE, NULL }, - { "/prefix/dir/foo.java", "myAttr", EXPECT_TRUE, NULL }, - { "/prefix/dir/foo.java", "other", EXPECT_UNDEFINED, NULL }, - { "NoMyAttr.java", "crlf", EXPECT_FALSE, NULL }, - { "NoMyAttr.java", "myAttr", EXPECT_UNDEFINED, NULL }, - { "NoMyAttr.java", "other", EXPECT_UNDEFINED, NULL }, - { "/prefix/dir/NoMyAttr.java", "crlf", EXPECT_FALSE, NULL }, - { "/prefix/dir/NoMyAttr.java", "myAttr", EXPECT_UNDEFINED, NULL }, - { "/prefix/dir/NoMyAttr.java", "other", EXPECT_UNDEFINED, NULL }, - { "README", "caveat", EXPECT_STRING, "unspecified" }, - { "/specific/path/README", "caveat", EXPECT_STRING, "unspecified" }, - { "README", "missing", EXPECT_UNDEFINED, NULL }, - { "/specific/path/README", "missing", EXPECT_UNDEFINED, NULL }, - /* sentinel at end */ - { NULL, NULL, 0, NULL } - }; - - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr3"))); - cl_assert(file->rules.length == 3); - - run_test_cases(file, cases, 0); - - git_attr_file__free(file); -} - -void test_attr_lookup__from_buffer(void) -{ - git_attr_file *file; - git_attr_file_source source = {0}; - - struct attr_expected cases[] = { - { "abc", "foo", EXPECT_TRUE, NULL }, - { "abc", "bar", EXPECT_TRUE, NULL }, - { "abc", "baz", EXPECT_TRUE, NULL }, - { "aaa", "foo", EXPECT_TRUE, NULL }, - { "aaa", "bar", EXPECT_UNDEFINED, NULL }, - { "aaa", "baz", EXPECT_TRUE, NULL }, - { "qqq", "foo", EXPECT_UNDEFINED, NULL }, - { "qqq", "bar", EXPECT_UNDEFINED, NULL }, - { "qqq", "baz", EXPECT_TRUE, NULL }, - { NULL, NULL, 0, NULL } - }; - - cl_git_pass(git_attr_file__new(&file, NULL, &source)); - - cl_git_pass(git_attr_file__parse_buffer(NULL, file, "a* foo\nabc bar\n* baz", true)); - - cl_assert(file->rules.length == 3); - - run_test_cases(file, cases, 0); - - git_attr_file__free(file); -} diff --git a/tests/attr/macro.c b/tests/attr/macro.c deleted file mode 100644 index 1fbfd137f..000000000 --- a/tests/attr/macro.c +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "clar_libgit2.h" - -#include "git2/sys/repository.h" -#include "attr.h" - -static git_repository *g_repo = NULL; - -void test_attr_macro__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -void test_attr_macro__macros(void) -{ - const char *names[7] = { "rootattr", "binary", "diff", "crlf", "merge", "text", "frotz" }; - const char *names2[5] = { "mymacro", "positive", "negative", "rootattr", "another" }; - const char *names3[3] = { "macro2", "multi2", "multi3" }; - const char *values[7]; - - g_repo = cl_git_sandbox_init("attr"); - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "binfile", 7, names)); - - cl_assert(GIT_ATTR_IS_TRUE(values[0])); - cl_assert(GIT_ATTR_IS_TRUE(values[1])); - cl_assert(GIT_ATTR_IS_FALSE(values[2])); - cl_assert(GIT_ATTR_IS_FALSE(values[3])); - cl_assert(GIT_ATTR_IS_FALSE(values[4])); - cl_assert(GIT_ATTR_IS_FALSE(values[5])); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[6])); - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_test", 5, names2)); - - cl_assert(GIT_ATTR_IS_TRUE(values[0])); - cl_assert(GIT_ATTR_IS_TRUE(values[1])); - cl_assert(GIT_ATTR_IS_FALSE(values[2])); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); - cl_assert_equal_s("77", values[4]); - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_test", 3, names3)); - - cl_assert(GIT_ATTR_IS_TRUE(values[0])); - cl_assert(GIT_ATTR_IS_FALSE(values[1])); - cl_assert_equal_s("answer", values[2]); -} - -void test_attr_macro__bad_macros(void) -{ - const char *names[6] = { "rootattr", "positive", "negative", - "firstmacro", "secondmacro", "thirdmacro" }; - const char *values[6]; - - g_repo = cl_git_sandbox_init("attr"); - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_bad", 6, names)); - - /* these three just confirm that the "mymacro" rule ran */ - cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[0])); - cl_assert(GIT_ATTR_IS_TRUE(values[1])); - cl_assert(GIT_ATTR_IS_FALSE(values[2])); - - /* file contains: - * # let's try some malicious macro defs - * [attr]firstmacro -thirdmacro -secondmacro - * [attr]secondmacro firstmacro -firstmacro - * [attr]thirdmacro secondmacro=hahaha -firstmacro - * macro_bad firstmacro secondmacro thirdmacro - * - * firstmacro assignment list ends up with: - * -thirdmacro -secondmacro - * secondmacro assignment list expands "firstmacro" and ends up with: - * -thirdmacro -secondmacro -firstmacro - * thirdmacro assignment don't expand so list ends up with: - * secondmacro="hahaha" - * - * macro_bad assignment list ends up with: - * -thirdmacro -secondmacro firstmacro && - * -thirdmacro -secondmacro -firstmacro secondmacro && - * secondmacro="hahaha" thirdmacro - * - * so summary results should be: - * -firstmacro secondmacro="hahaha" thirdmacro - */ - cl_assert(GIT_ATTR_IS_FALSE(values[3])); - cl_assert_equal_s("hahaha", values[4]); - cl_assert(GIT_ATTR_IS_TRUE(values[5])); -} - -void test_attr_macro__macros_in_root_wd_apply(void) -{ - const char *value; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_pass(p_mkdir("empty_standard_repo/dir", 0777)); - cl_git_rewritefile("empty_standard_repo/.gitattributes", "[attr]customattr key=value\n"); - cl_git_rewritefile("empty_standard_repo/dir/.gitattributes", "file customattr\n"); - - cl_git_pass(git_attr_get(&value, g_repo, 0, "dir/file", "key")); - cl_assert_equal_s(value, "value"); -} - -void test_attr_macro__changing_macro_in_root_wd_updates_attributes(void) -{ - const char *value; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_rewritefile("empty_standard_repo/.gitattributes", - "[attr]customattr key=first\n" - "file customattr\n"); - cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "key")); - cl_assert_equal_s(value, "first"); - - cl_git_rewritefile("empty_standard_repo/.gitattributes", - "[attr]customattr key=second\n" - "file customattr\n"); - cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "key")); - cl_assert_equal_s(value, "second"); -} - -void test_attr_macro__macros_in_subdir_do_not_apply(void) -{ - const char *value; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_pass(p_mkdir("empty_standard_repo/dir", 0777)); - cl_git_rewritefile("empty_standard_repo/dir/.gitattributes", - "[attr]customattr key=value\n" - "file customattr\n"); - - /* This should _not_ pass, as macros in subdirectories shall be ignored */ - cl_git_pass(git_attr_get(&value, g_repo, 0, "dir/file", "key")); - cl_assert_equal_p(value, NULL); -} - -void test_attr_macro__adding_macro_succeeds(void) -{ - const char *value; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_pass(git_attr_add_macro(g_repo, "macro", "key=value")); - cl_git_rewritefile("empty_standard_repo/.gitattributes", "file.txt macro\n"); - - cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "key")); - cl_assert_equal_s(value, "value"); -} - -void test_attr_macro__adding_boolean_macros_succeeds(void) -{ - const char *value; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_pass(git_attr_add_macro(g_repo, "macro-pos", "positive")); - cl_git_pass(git_attr_add_macro(g_repo, "macro-neg", "-negative")); - cl_git_rewritefile("empty_standard_repo/.gitattributes", "file.txt macro-pos macro-neg\n"); - - cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "positive")); - cl_assert(GIT_ATTR_IS_TRUE(value)); - cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "negative")); - cl_assert(GIT_ATTR_IS_FALSE(value)); -} - -void test_attr_macro__redefining_macro_succeeds(void) -{ - const char *value; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_pass(git_attr_add_macro(g_repo, "macro", "key=value1")); - cl_git_pass(git_attr_add_macro(g_repo, "macro", "key=value2")); - cl_git_rewritefile("empty_standard_repo/.gitattributes", "file.txt macro"); - - cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "key")); - cl_assert_equal_s(value, "value2"); -} - -void test_attr_macro__recursive_macro_resolves(void) -{ - const char *value; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_pass(git_attr_add_macro(g_repo, "expandme", "key=value")); - cl_git_pass(git_attr_add_macro(g_repo, "macro", "expandme")); - cl_git_rewritefile("empty_standard_repo/.gitattributes", "file.txt macro"); - - cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "key")); - cl_assert_equal_s(value, "value"); -} diff --git a/tests/attr/repo.c b/tests/attr/repo.c deleted file mode 100644 index abd238154..000000000 --- a/tests/attr/repo.c +++ /dev/null @@ -1,405 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "git2/attr.h" -#include "attr.h" -#include "sysdir.h" - -#include "attr_expect.h" -#include "git2/sys/repository.h" - -static git_repository *g_repo = NULL; - -void test_attr_repo__initialize(void) -{ - g_repo = cl_git_sandbox_init("attr"); -} - -void test_attr_repo__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; - cl_sandbox_set_search_path_defaults(); -} - -static struct attr_expected get_one_test_cases[] = { - { "root_test1", "repoattr", EXPECT_TRUE, NULL }, - { "root_test1", "rootattr", EXPECT_TRUE, NULL }, - { "root_test1", "missingattr", EXPECT_UNDEFINED, NULL }, - { "root_test1", "subattr", EXPECT_UNDEFINED, NULL }, - { "root_test1", "negattr", EXPECT_UNDEFINED, NULL }, - { "root_test2", "repoattr", EXPECT_TRUE, NULL }, - { "root_test2", "rootattr", EXPECT_FALSE, NULL }, - { "root_test2", "missingattr", EXPECT_UNDEFINED, NULL }, - { "root_test2", "multiattr", EXPECT_FALSE, NULL }, - { "root_test3", "repoattr", EXPECT_TRUE, NULL }, - { "root_test3", "rootattr", EXPECT_UNDEFINED, NULL }, - { "root_test3", "multiattr", EXPECT_STRING, "3" }, - { "root_test3", "multi2", EXPECT_UNDEFINED, NULL }, - { "sub/subdir_test1", "repoattr", EXPECT_TRUE, NULL }, - { "sub/subdir_test1", "rootattr", EXPECT_TRUE, NULL }, - { "sub/subdir_test1", "missingattr", EXPECT_UNDEFINED, NULL }, - { "sub/subdir_test1", "subattr", EXPECT_STRING, "yes" }, - { "sub/subdir_test1", "negattr", EXPECT_FALSE, NULL }, - { "sub/subdir_test1", "another", EXPECT_UNDEFINED, NULL }, - { "sub/subdir_test2.txt", "repoattr", EXPECT_TRUE, NULL }, - { "sub/subdir_test2.txt", "rootattr", EXPECT_TRUE, NULL }, - { "sub/subdir_test2.txt", "missingattr", EXPECT_UNDEFINED, NULL }, - { "sub/subdir_test2.txt", "subattr", EXPECT_STRING, "yes" }, - { "sub/subdir_test2.txt", "negattr", EXPECT_FALSE, NULL }, - { "sub/subdir_test2.txt", "another", EXPECT_STRING, "zero" }, - { "sub/subdir_test2.txt", "reposub", EXPECT_TRUE, NULL }, - { "sub/sub/subdir.txt", "another", EXPECT_STRING, "one" }, - { "sub/sub/subdir.txt", "reposubsub", EXPECT_TRUE, NULL }, - { "sub/sub/subdir.txt", "reposub", EXPECT_UNDEFINED, NULL }, - { "does-not-exist", "foo", EXPECT_STRING, "yes" }, - { "sub/deep/file", "deepdeep", EXPECT_TRUE, NULL }, - { "sub/sub/d/no", "test", EXPECT_STRING, "a/b/d/*" }, - { "sub/sub/d/yes", "test", EXPECT_UNDEFINED, NULL }, -}; - -void test_attr_repo__get_one(void) -{ - int i; - - for (i = 0; i < (int)ARRAY_SIZE(get_one_test_cases); ++i) { - struct attr_expected *scan = &get_one_test_cases[i]; - const char *value; - - cl_git_pass(git_attr_get(&value, g_repo, 0, scan->path, scan->attr)); - attr_check_expected( - scan->expected, scan->expected_str, scan->attr, value); - } - - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".git/info/attributes")); - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".gitattributes")); - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, "sub/.gitattributes")); -} - -void test_attr_repo__get_one_start_deep(void) -{ - int i; - - for (i = (int)ARRAY_SIZE(get_one_test_cases) - 1; i >= 0; --i) { - struct attr_expected *scan = &get_one_test_cases[i]; - const char *value; - - cl_git_pass(git_attr_get(&value, g_repo, 0, scan->path, scan->attr)); - attr_check_expected( - scan->expected, scan->expected_str, scan->attr, value); - } - - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".git/info/attributes")); - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".gitattributes")); - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, "sub/.gitattributes")); -} - -void test_attr_repo__get_many(void) -{ - const char *names[4] = { "repoattr", "rootattr", "missingattr", "subattr" }; - const char *values[4]; - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "root_test1", 4, names)); - - cl_assert(GIT_ATTR_IS_TRUE(values[0])); - cl_assert(GIT_ATTR_IS_TRUE(values[1])); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[2])); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "root_test2", 4, names)); - - cl_assert(GIT_ATTR_IS_TRUE(values[0])); - cl_assert(GIT_ATTR_IS_FALSE(values[1])); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[2])); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "sub/subdir_test1", 4, names)); - - cl_assert(GIT_ATTR_IS_TRUE(values[0])); - cl_assert(GIT_ATTR_IS_TRUE(values[1])); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[2])); - cl_assert_equal_s("yes", values[3]); -} - -void test_attr_repo__get_many_in_place(void) -{ - const char *vals[4] = { "repoattr", "rootattr", "missingattr", "subattr" }; - - /* it should be legal to look up values into the same array that has - * the attribute names, overwriting each name as the value is found. - */ - - cl_git_pass(git_attr_get_many(vals, g_repo, 0, "sub/subdir_test1", 4, vals)); - - cl_assert(GIT_ATTR_IS_TRUE(vals[0])); - cl_assert(GIT_ATTR_IS_TRUE(vals[1])); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(vals[2])); - cl_assert_equal_s("yes", vals[3]); -} - -static int count_attrs( - const char *name, - const char *value, - void *payload) -{ - GIT_UNUSED(name); - GIT_UNUSED(value); - - *((int *)payload) += 1; - - return 0; -} - -#define CANCEL_VALUE 12345 - -static int cancel_iteration( - const char *name, - const char *value, - void *payload) -{ - GIT_UNUSED(name); - GIT_UNUSED(value); - - *((int *)payload) -= 1; - - if (*((int *)payload) < 0) - return CANCEL_VALUE; - - return 0; -} - -void test_attr_repo__foreach(void) -{ - int count; - - count = 0; - cl_git_pass(git_attr_foreach( - g_repo, 0, "root_test1", &count_attrs, &count)); - cl_assert(count == 2); - - count = 0; - cl_git_pass(git_attr_foreach(g_repo, 0, "sub/subdir_test1", - &count_attrs, &count)); - cl_assert(count == 4); /* repoattr, rootattr, subattr, negattr */ - - count = 0; - cl_git_pass(git_attr_foreach(g_repo, 0, "sub/subdir_test2.txt", - &count_attrs, &count)); - cl_assert(count == 6); /* repoattr, rootattr, subattr, reposub, negattr, another */ - - count = 2; - cl_assert_equal_i( - CANCEL_VALUE, git_attr_foreach( - g_repo, 0, "sub/subdir_test1", &cancel_iteration, &count) - ); -} - -void test_attr_repo__manpage_example(void) -{ - const char *value; - - cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "foo")); - cl_assert(GIT_ATTR_IS_TRUE(value)); - - cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "bar")); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(value)); - - cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "baz")); - cl_assert(GIT_ATTR_IS_FALSE(value)); - - cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "merge")); - cl_assert_equal_s("filfre", value); - - cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "frotz")); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(value)); -} - -#define CONTENT "I'm going to be dynamically processed\r\n" \ - "And my line endings...\r\n" \ - "...are going to be\n" \ - "normalized!\r\n" - -#define GITATTR "* text=auto\n" \ - "*.txt text\n" \ - "*.data binary\n" - -static void add_to_workdir(const char *filename, const char *content) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&buf, "attr", filename)); - cl_git_rewritefile(git_str_cstr(&buf), content); - - git_str_dispose(&buf); -} - -static void assert_proper_normalization(git_index *index, const char *filename, const char *expected_sha) -{ - size_t index_pos; - const git_index_entry *entry; - - add_to_workdir(filename, CONTENT); - cl_git_pass(git_index_add_bypath(index, filename)); - - cl_assert(!git_index_find(&index_pos, index, filename)); - - entry = git_index_get_byindex(index, index_pos); - cl_assert_equal_i(0, git_oid_streq(&entry->id, expected_sha)); -} - -void test_attr_repo__staging_properly_normalizes_line_endings_according_to_gitattributes_directives(void) -{ - git_index* index; - - cl_git_pass(git_repository_index(&index, g_repo)); - - add_to_workdir(".gitattributes", GITATTR); - - assert_proper_normalization(index, "text.txt", "22c74203bace3c2e950278c7ab08da0fca9f4e9b"); - assert_proper_normalization(index, "huh.dunno", "22c74203bace3c2e950278c7ab08da0fca9f4e9b"); - assert_proper_normalization(index, "binary.data", "66eeff1fcbacf589e6d70aa70edd3fce5be2b37c"); - - git_index_free(index); -} - -void test_attr_repo__bare_repo_with_index(void) -{ - const char *names[4] = { "test1", "test2", "test3", "test4" }; - const char *values[4]; - git_index *index; - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_mkfile( - "attr/.gitattributes", - "*.txt test1 test2=foobar -test3\n" - "trial.txt -test1 test2=barfoo !test3 test4\n"); - cl_git_pass(git_index_add_bypath(index, ".gitattributes")); - git_index_free(index); - - cl_must_pass(p_unlink("attr/.gitattributes")); - cl_assert(!git_fs_path_exists("attr/.gitattributes")); - - cl_git_pass(git_repository_set_bare(g_repo)); - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "file.txt", 4, names)); - - cl_assert(GIT_ATTR_IS_TRUE(values[0])); - cl_assert_equal_s("foobar", values[1]); - cl_assert(GIT_ATTR_IS_FALSE(values[2])); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "trial.txt", 4, names)); - - cl_assert(GIT_ATTR_IS_FALSE(values[0])); - cl_assert_equal_s("barfoo", values[1]); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[2])); - cl_assert(GIT_ATTR_IS_TRUE(values[3])); - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "sub/sub/subdir.txt", 4, names)); - - cl_assert(GIT_ATTR_IS_TRUE(values[0])); - cl_assert_equal_s("foobar", values[1]); - cl_assert(GIT_ATTR_IS_FALSE(values[2])); - cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); -} - -void test_attr_repo__sysdir(void) -{ - git_str sysdir = GIT_STR_INIT; - const char *value; - - cl_git_pass(p_mkdir("system", 0777)); - cl_git_rewritefile("system/gitattributes", "file merge=foo"); - cl_git_pass(git_str_joinpath(&sysdir, clar_sandbox_path(), "system")); - cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, sysdir.ptr)); - g_repo = cl_git_sandbox_reopen(); - - cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "merge")); - cl_assert_equal_s(value, "foo"); - - cl_git_pass(p_unlink("system/gitattributes")); - cl_git_pass(p_rmdir("system")); - git_str_dispose(&sysdir); -} - -void test_attr_repo__sysdir_with_session(void) -{ - const char *values[2], *attrs[2] = { "foo", "bar" }; - git_str sysdir = GIT_STR_INIT; - git_attr_session session; - - cl_git_pass(p_mkdir("system", 0777)); - cl_git_rewritefile("system/gitattributes", "file foo=1 bar=2"); - cl_git_pass(git_str_joinpath(&sysdir, clar_sandbox_path(), "system")); - cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, sysdir.ptr)); - g_repo = cl_git_sandbox_reopen(); - - cl_git_pass(git_attr_session__init(&session, g_repo)); - cl_git_pass(git_attr_get_many_with_session(values, g_repo, &session, NULL, "file", ARRAY_SIZE(attrs), attrs)); - - cl_assert_equal_s(values[0], "1"); - cl_assert_equal_s(values[1], "2"); - - cl_git_pass(p_unlink("system/gitattributes")); - cl_git_pass(p_rmdir("system")); - git_str_dispose(&sysdir); - git_attr_session__free(&session); -} - -void test_attr_repo__rewrite(void) -{ - const char *value; - - cl_git_rewritefile("attr/.gitattributes", "file.txt foo=first\n"); - cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); - cl_assert_equal_s(value, "first"); - - cl_git_rewritefile("attr/.gitattributes", "file.txt foo=second\n"); - cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); - cl_assert_equal_s(value, "second"); - - cl_git_rewritefile("attr/.gitattributes", "file.txt other=value\n"); - cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); - cl_assert_equal_p(value, NULL); -} - -void test_attr_repo__rewrite_sysdir(void) -{ - git_str sysdir = GIT_STR_INIT; - const char *value; - - cl_git_pass(p_mkdir("system", 0777)); - cl_git_pass(git_str_joinpath(&sysdir, clar_sandbox_path(), "system")); - cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, sysdir.ptr)); - g_repo = cl_git_sandbox_reopen(); - - cl_git_rewritefile("system/gitattributes", "file foo=first"); - cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "foo")); - cl_assert_equal_s(value, "first"); - - cl_git_rewritefile("system/gitattributes", "file foo=second"); - cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "foo")); - cl_assert_equal_s(value, "second"); - - git_str_dispose(&sysdir); -} - -void test_attr_repo__unlink(void) -{ - const char *value; - - cl_git_rewritefile("attr/.gitattributes", "file.txt foo=value1\n"); - cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); - cl_assert_equal_s(value, "value1"); - - cl_git_pass(p_unlink("attr/.gitattributes")); - - cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); - cl_assert_equal_p(value, NULL); -} diff --git a/tests/blame/blame_helpers.c b/tests/blame/blame_helpers.c deleted file mode 100644 index 6b3ce677d..000000000 --- a/tests/blame/blame_helpers.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "blame_helpers.h" - -void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...) -{ - va_list arglist; - - printf("Hunk %"PRIuZ" (line %"PRIuZ" +%"PRIuZ"): ", idx, - hunk->final_start_line_number, hunk->lines_in_hunk-1); - - va_start(arglist, fmt); - vprintf(fmt, arglist); - va_end(arglist); - - printf("\n"); -} - -void check_blame_hunk_index(git_repository *repo, git_blame *blame, int idx, - size_t start_line, size_t len, char boundary, const char *commit_id, const char *orig_path) -{ - char expected[GIT_OID_HEXSZ+1] = {0}, actual[GIT_OID_HEXSZ+1] = {0}; - const git_blame_hunk *hunk = git_blame_get_hunk_byindex(blame, idx); - cl_assert(hunk); - - if (!strncmp(commit_id, "0000", 4)) { - strcpy(expected, "0000000000000000000000000000000000000000"); - } else { - git_object *obj; - cl_git_pass(git_revparse_single(&obj, repo, commit_id)); - git_oid_fmt(expected, git_object_id(obj)); - git_object_free(obj); - } - - if (hunk->final_start_line_number != start_line) { - hunk_message(idx, hunk, "mismatched start line number: expected %"PRIuZ", got %"PRIuZ, - start_line, hunk->final_start_line_number); - } - cl_assert_equal_i(hunk->final_start_line_number, start_line); - - if (hunk->lines_in_hunk != len) { - hunk_message(idx, hunk, "mismatched line count: expected %"PRIuZ", got %"PRIuZ, - len, hunk->lines_in_hunk); - } - cl_assert_equal_i(hunk->lines_in_hunk, len); - - git_oid_fmt(actual, &hunk->final_commit_id); - if (strcmp(expected, actual)) { - hunk_message(idx, hunk, "has mismatched original id (got %s, expected %s)\n", - actual, expected); - } - cl_assert_equal_s(actual, expected); - cl_assert_equal_oid(&hunk->final_commit_id, &hunk->orig_commit_id); - - - if (strcmp(hunk->orig_path, orig_path)) { - hunk_message(idx, hunk, "has mismatched original path (got '%s', expected '%s')\n", - hunk->orig_path, orig_path); - } - cl_assert_equal_s(hunk->orig_path, orig_path); - - if (hunk->boundary != boundary) { - hunk_message(idx, hunk, "doesn't match boundary flag (got %d, expected %d)\n", - hunk->boundary, boundary); - } - cl_assert_equal_i(boundary, hunk->boundary); -} - - diff --git a/tests/blame/blame_helpers.h b/tests/blame/blame_helpers.h deleted file mode 100644 index 5b34b4aef..000000000 --- a/tests/blame/blame_helpers.h +++ /dev/null @@ -1,14 +0,0 @@ -#include "clar_libgit2.h" -#include "blame.h" - -void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...) GIT_FORMAT_PRINTF(3, 4); - -void check_blame_hunk_index( - git_repository *repo, - git_blame *blame, - int idx, - size_t start_line, - size_t len, - char boundary, - const char *commit_id, - const char *orig_path); diff --git a/tests/blame/buffer.c b/tests/blame/buffer.c deleted file mode 100644 index 06d5042dd..000000000 --- a/tests/blame/buffer.c +++ /dev/null @@ -1,192 +0,0 @@ -#include "blame_helpers.h" - -static git_repository *g_repo; -static git_blame *g_fileblame, *g_bufferblame; - -void test_blame_buffer__initialize(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); - cl_git_pass(git_blame_file(&g_fileblame, g_repo, "b.txt", NULL)); - g_bufferblame = NULL; -} - -void test_blame_buffer__cleanup(void) -{ - git_blame_free(g_fileblame); - git_blame_free(g_bufferblame); - git_repository_free(g_repo); -} - -void test_blame_buffer__index(void) -{ - const git_blame_hunk *hunk; - const char *buffer = "Hello\nWorld!"; - - /* - * We need to open a different file from the ones used in other tests. Close - * the one opened in test_blame_buffer__initialize() to avoid a leak. - */ - git_blame_free(g_fileblame); - g_fileblame = NULL; - cl_git_pass(git_blame_file(&g_fileblame, g_repo, "file.txt", NULL)); - - cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); - cl_assert_equal_i(2, git_blame_get_hunk_count(g_bufferblame)); - - check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 1, 0, "836bc00b", "file.txt"); - hunk = git_blame_get_hunk_byline(g_bufferblame, 1); - cl_assert(hunk); - cl_assert_equal_s("lhchavez", hunk->final_signature->name); - check_blame_hunk_index(g_repo, g_bufferblame, 1, 2, 1, 0, "00000000", "file.txt"); - hunk = git_blame_get_hunk_byline(g_bufferblame, 2); - cl_assert(hunk); - cl_assert(hunk->final_signature == NULL); -} - -void test_blame_buffer__added_line(void) -{ - const git_blame_hunk *hunk; - - const char *buffer = "\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -\n\ -abcdefg\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; - - cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); - cl_assert_equal_i(5, git_blame_get_hunk_count(g_bufferblame)); - check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "000000", "b.txt"); - - hunk = git_blame_get_hunk_byline(g_bufferblame, 16); - cl_assert(hunk); - cl_assert_equal_s("Ben Straub", hunk->final_signature->name); -} - -void test_blame_buffer__deleted_line(void) -{ - const char *buffer = "\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; - - cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); - check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 3, 0, "63d671eb", "b.txt"); - check_blame_hunk_index(g_repo, g_bufferblame, 3, 9, 1, 0, "63d671eb", "b.txt"); - check_blame_hunk_index(g_repo, g_bufferblame, 4, 10, 5, 0, "aa06ecca", "b.txt"); -} - -void test_blame_buffer__add_splits_hunk(void) -{ - const char *buffer = "\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -abc\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; - - cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); - check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 2, 0, "63d671eb", "b.txt"); - check_blame_hunk_index(g_repo, g_bufferblame, 3, 8, 1, 0, "00000000", "b.txt"); - check_blame_hunk_index(g_repo, g_bufferblame, 4, 9, 3, 0, "63d671eb", "b.txt"); -} - -void test_blame_buffer__delete_crosses_hunk_boundary(void) -{ - const char *buffer = "\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; - - cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); - check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "63d671eb", "b.txt"); - check_blame_hunk_index(g_repo, g_bufferblame, 3, 7, 2, 0, "aa06ecca", "b.txt"); -} - -void test_blame_buffer__replace_line(void) -{ - const char *buffer = "\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -abc\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; - - cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); - check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "63d671eb", "b.txt"); - check_blame_hunk_index(g_repo, g_bufferblame, 3, 7, 1, 0, "00000000", "b.txt"); - check_blame_hunk_index(g_repo, g_bufferblame, 4, 8, 3, 0, "63d671eb", "b.txt"); -} - -void test_blame_buffer__add_lines_at_end(void) -{ - const char *buffer = "\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ -\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ -\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ -\n\ -abc\n\ -def\n"; - cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); - - cl_assert_equal_i(5, git_blame_get_hunk_count(g_bufferblame)); - check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 4, 0, "da237394", "b.txt"); - check_blame_hunk_index(g_repo, g_bufferblame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); - check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 5, 0, "63d671eb", "b.txt"); - check_blame_hunk_index(g_repo, g_bufferblame, 3, 11, 5, 0, "aa06ecca", "b.txt"); - check_blame_hunk_index(g_repo, g_bufferblame, 4, 16, 2, 0, "00000000", "b.txt"); -} diff --git a/tests/blame/getters.c b/tests/blame/getters.c deleted file mode 100644 index 66eaeecf9..000000000 --- a/tests/blame/getters.c +++ /dev/null @@ -1,56 +0,0 @@ -#include "clar_libgit2.h" - -#include "blame.h" - -git_blame *g_blame; - -void test_blame_getters__initialize(void) -{ - size_t i; - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - - git_blame_hunk hunks[] = { - { 3, {{0}}, 1, NULL, {{0}}, "a", 0}, - { 3, {{0}}, 4, NULL, {{0}}, "b", 0}, - { 3, {{0}}, 7, NULL, {{0}}, "c", 0}, - { 3, {{0}}, 10, NULL, {{0}}, "d", 0}, - { 3, {{0}}, 13, NULL, {{0}}, "e", 0}, - }; - - g_blame = git_blame__alloc(NULL, opts, ""); - - for (i=0; i<5; i++) { - git_blame_hunk *h = git__calloc(1, sizeof(git_blame_hunk)); - h->final_start_line_number = hunks[i].final_start_line_number; - h->orig_path = git__strdup(hunks[i].orig_path); - h->lines_in_hunk = hunks[i].lines_in_hunk; - - git_vector_insert(&g_blame->hunks, h); - } -} - -void test_blame_getters__cleanup(void) -{ - git_blame_free(g_blame); -} - - -void test_blame_getters__byindex(void) -{ - const git_blame_hunk *h = git_blame_get_hunk_byindex(g_blame, 2); - cl_assert(h); - cl_assert_equal_s(h->orig_path, "c"); - - h = git_blame_get_hunk_byindex(g_blame, 95); - cl_assert_equal_p(h, NULL); -} - -void test_blame_getters__byline(void) -{ - const git_blame_hunk *h = git_blame_get_hunk_byline(g_blame, 5); - cl_assert(h); - cl_assert_equal_s(h->orig_path, "b"); - - h = git_blame_get_hunk_byline(g_blame, 95); - cl_assert_equal_p(h, NULL); -} diff --git a/tests/blame/harder.c b/tests/blame/harder.c deleted file mode 100644 index e77741720..000000000 --- a/tests/blame/harder.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "clar_libgit2.h" - -#include "blame.h" - - -/** - * The test repo has a history that looks like this: - * - * * (A) bc7c5ac - * |\ - * | * (B) aa06ecc - * * | (C) 63d671e - * |/ - * * (D) da23739 - * * (E) b99f7ac - * - */ - -static git_repository *g_repo = NULL; - -void test_blame_harder__initialize(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); -} - -void test_blame_harder__cleanup(void) -{ - git_repository_free(g_repo); - g_repo = NULL; -} - - - -void test_blame_harder__m(void) -{ - /* TODO */ - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - - GIT_UNUSED(opts); - - opts.flags = GIT_BLAME_TRACK_COPIES_SAME_FILE; -} - - -void test_blame_harder__c(void) -{ - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - - GIT_UNUSED(opts); - - /* Attribute the first hunk in b.txt to (E), since it was cut/pasted from - * a.txt in (D). - */ - opts.flags = GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; -} - -void test_blame_harder__cc(void) -{ - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - - GIT_UNUSED(opts); - - /* Attribute the second hunk in b.txt to (E), since it was copy/pasted from - * a.txt in (C). - */ - opts.flags = GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; -} - -void test_blame_harder__ccc(void) -{ - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - - GIT_UNUSED(opts); - - /* Attribute the third hunk in b.txt to (E). This hunk was deleted from - * a.txt in (D), but reintroduced in (B). - */ - opts.flags = GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES; -} diff --git a/tests/blame/simple.c b/tests/blame/simple.c deleted file mode 100644 index 6b13cccd4..000000000 --- a/tests/blame/simple.c +++ /dev/null @@ -1,362 +0,0 @@ -#include "blame_helpers.h" - -static git_repository *g_repo; -static git_blame *g_blame; - -void test_blame_simple__initialize(void) -{ - g_repo = NULL; - g_blame = NULL; -} - -void test_blame_simple__cleanup(void) -{ - git_blame_free(g_blame); - git_repository_free(g_repo); -} - -/* - * $ git blame -s branch_file.txt - * orig line no final line no - * commit V author timestamp V - * c47800c7 1 (Scott Chacon 2010-05-25 11:58:14 -0700 1 - * a65fedf3 2 (Scott Chacon 2011-08-09 19:33:46 -0700 2 - */ -void test_blame_simple__trivial_testrepo(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo/.gitted"))); - cl_git_pass(git_blame_file(&g_blame, g_repo, "branch_file.txt", NULL)); - - cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); - check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 0, "c47800c7", "branch_file.txt"); - check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "a65fedf3", "branch_file.txt"); -} - -/* - * $ git blame -n b.txt - * orig line no final line no - * commit V author timestamp V - * da237394 1 (Ben Straub 2013-02-12 15:11:30 -0800 1 - * da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2 - * da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3 - * da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4 - * ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5 - * 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6 - * 63d671eb 7 (Ben Straub 2013-02-12 15:13:04 -0800 7 - * 63d671eb 8 (Ben Straub 2013-02-12 15:13:04 -0800 8 - * 63d671eb 9 (Ben Straub 2013-02-12 15:13:04 -0800 9 - * 63d671eb 10 (Ben Straub 2013-02-12 15:13:04 -0800 10 - * aa06ecca 6 (Ben Straub 2013-02-12 15:14:46 -0800 11 - * aa06ecca 7 (Ben Straub 2013-02-12 15:14:46 -0800 12 - * aa06ecca 8 (Ben Straub 2013-02-12 15:14:46 -0800 13 - * aa06ecca 9 (Ben Straub 2013-02-12 15:14:46 -0800 14 - * aa06ecca 10 (Ben Straub 2013-02-12 15:14:46 -0800 15 - */ -void test_blame_simple__trivial_blamerepo(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); - cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", NULL)); - - cl_assert_equal_i(4, git_blame_get_hunk_count(g_blame)); - check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt"); - check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); - check_blame_hunk_index(g_repo, g_blame, 2, 6, 5, 0, "63d671eb", "b.txt"); - check_blame_hunk_index(g_repo, g_blame, 3, 11, 5, 0, "aa06ecca", "b.txt"); -} - - -/* - * $ git blame -n 359fc2d -- include/git2.h - * orig line no final line no - * commit orig path V author timestamp V - * d12299fe src/git.h 1 (Vicent Martí 2010-12-03 22:22:10 +0200 1 - * 359fc2d2 include/git2.h 2 (Edward Thomson 2013-01-08 17:07:25 -0600 2 - * d12299fe src/git.h 5 (Vicent Martí 2010-12-03 22:22:10 +0200 3 - * bb742ede include/git2.h 4 (Vicent Martí 2011-09-19 01:54:32 +0300 4 - * bb742ede include/git2.h 5 (Vicent Martí 2011-09-19 01:54:32 +0300 5 - * d12299fe src/git.h 24 (Vicent Martí 2010-12-03 22:22:10 +0200 6 - * d12299fe src/git.h 25 (Vicent Martí 2010-12-03 22:22:10 +0200 7 - * d12299fe src/git.h 26 (Vicent Martí 2010-12-03 22:22:10 +0200 8 - * d12299fe src/git.h 27 (Vicent Martí 2010-12-03 22:22:10 +0200 9 - * d12299fe src/git.h 28 (Vicent Martí 2010-12-03 22:22:10 +0200 10 - * 96fab093 include/git2.h 11 (Sven Strickroth 2011-10-09 18:37:41 +0200 11 - * 9d1dcca2 src/git2.h 33 (Vicent Martí 2011-02-07 10:35:58 +0200 12 - * 44908fe7 src/git2.h 29 (Vicent Martí 2010-12-06 23:03:16 +0200 13 - * a15c550d include/git2.h 14 (Vicent Martí 2011-11-16 14:09:44 +0100 14 - * 44908fe7 src/git2.h 30 (Vicent Martí 2010-12-06 23:03:16 +0200 15 - * d12299fe src/git.h 32 (Vicent Martí 2010-12-03 22:22:10 +0200 16 - * 44908fe7 src/git2.h 33 (Vicent Martí 2010-12-06 23:03:16 +0200 17 - * d12299fe src/git.h 34 (Vicent Martí 2010-12-03 22:22:10 +0200 18 - * 44908fe7 src/git2.h 35 (Vicent Martí 2010-12-06 23:03:16 +0200 19 - * 638c2ca4 src/git2.h 36 (Vicent Martí 2010-12-18 02:10:25 +0200 20 - * 44908fe7 src/git2.h 36 (Vicent Martí 2010-12-06 23:03:16 +0200 21 - * d12299fe src/git.h 37 (Vicent Martí 2010-12-03 22:22:10 +0200 22 - * 44908fe7 src/git2.h 38 (Vicent Martí 2010-12-06 23:03:16 +0200 23 - * 44908fe7 src/git2.h 39 (Vicent Martí 2010-12-06 23:03:16 +0200 24 - * bf787bd8 include/git2.h 25 (Carlos Martín Nieto 2012-04-08 18:56:50 +0200 25 - * 0984c876 include/git2.h 26 (Scott J. Goldman 2012-11-28 18:27:43 -0800 26 - * 2f8a8ab2 src/git2.h 41 (Vicent Martí 2011-01-29 01:56:25 +0200 27 - * 27df4275 include/git2.h 47 (Michael Schubert 2011-06-28 14:13:12 +0200 28 - * a346992f include/git2.h 28 (Ben Straub 2012-05-10 09:47:14 -0700 29 - * d12299fe src/git.h 40 (Vicent Martí 2010-12-03 22:22:10 +0200 30 - * 44908fe7 src/git2.h 41 (Vicent Martí 2010-12-06 23:03:16 +0200 31 - * 44908fe7 src/git2.h 42 (Vicent Martí 2010-12-06 23:03:16 +0200 32 - * 44908fe7 src/git2.h 43 (Vicent Martí 2010-12-06 23:03:16 +0200 33 - * 44908fe7 src/git2.h 44 (Vicent Martí 2010-12-06 23:03:16 +0200 34 - * 44908fe7 src/git2.h 45 (Vicent Martí 2010-12-06 23:03:16 +0200 35 - * 65b09b1d include/git2.h 33 (Russell Belfer 2012-02-02 18:03:43 -0800 36 - * d12299fe src/git.h 46 (Vicent Martí 2010-12-03 22:22:10 +0200 37 - * 44908fe7 src/git2.h 47 (Vicent Martí 2010-12-06 23:03:16 +0200 38 - * 5d4cd003 include/git2.h 55 (Carlos Martín Nieto 2011-03-28 17:02:45 +0200 39 - * 41fb1ca0 include/git2.h 39 (Philip Kelley 2012-10-29 13:41:14 -0400 40 - * 2dc31040 include/git2.h 56 (Carlos Martín Nieto 2011-06-20 18:58:57 +0200 41 - * 764df57e include/git2.h 40 (Ben Straub 2012-06-15 13:14:43 -0700 42 - * 5280f4e6 include/git2.h 41 (Ben Straub 2012-07-31 19:39:06 -0700 43 - * 613d5eb9 include/git2.h 43 (Philip Kelley 2012-11-28 11:42:37 -0500 44 - * d12299fe src/git.h 48 (Vicent Martí 2010-12-03 22:22:10 +0200 45 - * 111ee3fe include/git2.h 41 (Vicent Martí 2012-07-11 14:37:26 +0200 46 - * f004c4a8 include/git2.h 44 (Russell Belfer 2012-08-21 17:26:39 -0700 47 - * 111ee3fe include/git2.h 42 (Vicent Martí 2012-07-11 14:37:26 +0200 48 - * 9c82357b include/git2.h 58 (Carlos Martín Nieto 2011-06-17 18:13:14 +0200 49 - * d6258deb include/git2.h 61 (Carlos Martín Nieto 2011-06-25 15:10:09 +0200 50 - * b311e313 include/git2.h 63 (Julien Miotte 2011-07-27 18:31:13 +0200 51 - * 3412391d include/git2.h 63 (Carlos Martín Nieto 2011-07-07 11:47:31 +0200 52 - * bfc9ca59 include/git2.h 43 (Russell Belfer 2012-03-28 16:45:36 -0700 53 - * bf477ed4 include/git2.h 44 (Michael Schubert 2012-02-15 00:33:38 +0100 54 - * edebceff include/git2.h 46 (nulltoken 2012-05-01 13:57:45 +0200 55 - * 743a4b3b include/git2.h 48 (nulltoken 2012-06-15 22:24:59 +0200 56 - * 0a32dca5 include/git2.h 54 (Michael Schubert 2012-08-19 22:26:32 +0200 57 - * 590fb68b include/git2.h 55 (nulltoken 2012-10-04 13:47:45 +0200 58 - * bf477ed4 include/git2.h 45 (Michael Schubert 2012-02-15 00:33:38 +0100 59 - * d12299fe src/git.h 49 (Vicent Martí 2010-12-03 22:22:10 +0200 60 - */ -void test_blame_simple__trivial_libgit2(void) -{ - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - git_object *obj; - - /* If we can't open the libgit2 repo or if it isn't a full repo - * with proper history, just skip this test */ - if (git_repository_open(&g_repo, cl_fixture("../..")) < 0) - cl_skip(); - - if (git_repository_is_shallow(g_repo)) - cl_skip(); - - if (git_revparse_single(&obj, g_repo, "359fc2d") < 0) - cl_skip(); - - git_oid_cpy(&opts.newest_commit, git_object_id(obj)); - git_object_free(obj); - - cl_git_pass(git_blame_file(&g_blame, g_repo, "include/git2.h", &opts)); - - check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 0, "d12299fe", "src/git.h"); - check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "359fc2d2", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 2, 3, 1, 0, "d12299fe", "src/git.h"); - check_blame_hunk_index(g_repo, g_blame, 3, 4, 2, 0, "bb742ede", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 4, 6, 5, 0, "d12299fe", "src/git.h"); - check_blame_hunk_index(g_repo, g_blame, 5, 11, 1, 0, "96fab093", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 6, 12, 1, 0, "9d1dcca2", "src/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 7, 13, 1, 0, "44908fe7", "src/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 8, 14, 1, 0, "a15c550d", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 9, 15, 1, 0, "44908fe7", "src/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 10, 16, 1, 0, "d12299fe", "src/git.h"); - check_blame_hunk_index(g_repo, g_blame, 11, 17, 1, 0, "44908fe7", "src/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 12, 18, 1, 0, "d12299fe", "src/git.h"); - check_blame_hunk_index(g_repo, g_blame, 13, 19, 1, 0, "44908fe7", "src/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 14, 20, 1, 0, "638c2ca4", "src/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 15, 21, 1, 0, "44908fe7", "src/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 16, 22, 1, 0, "d12299fe", "src/git.h"); - check_blame_hunk_index(g_repo, g_blame, 17, 23, 2, 0, "44908fe7", "src/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 18, 25, 1, 0, "bf787bd8", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 19, 26, 1, 0, "0984c876", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 20, 27, 1, 0, "2f8a8ab2", "src/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 21, 28, 1, 0, "27df4275", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 22, 29, 1, 0, "a346992f", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 23, 30, 1, 0, "d12299fe", "src/git.h"); - check_blame_hunk_index(g_repo, g_blame, 24, 31, 5, 0, "44908fe7", "src/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 25, 36, 1, 0, "65b09b1d", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 26, 37, 1, 0, "d12299fe", "src/git.h"); - check_blame_hunk_index(g_repo, g_blame, 27, 38, 1, 0, "44908fe7", "src/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 28, 39, 1, 0, "5d4cd003", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 29, 40, 1, 0, "41fb1ca0", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 30, 41, 1, 0, "2dc31040", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 31, 42, 1, 0, "764df57e", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 32, 43, 1, 0, "5280f4e6", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 33, 44, 1, 0, "613d5eb9", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 34, 45, 1, 0, "d12299fe", "src/git.h"); - check_blame_hunk_index(g_repo, g_blame, 35, 46, 1, 0, "111ee3fe", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 36, 47, 1, 0, "f004c4a8", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 37, 48, 1, 0, "111ee3fe", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 38, 49, 1, 0, "9c82357b", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 39, 50, 1, 0, "d6258deb", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 40, 51, 1, 0, "b311e313", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 41, 52, 1, 0, "3412391d", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 42, 53, 1, 0, "bfc9ca59", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 43, 54, 1, 0, "bf477ed4", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 44, 55, 1, 0, "edebceff", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 45, 56, 1, 0, "743a4b3b", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 46, 57, 1, 0, "0a32dca5", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 47, 58, 1, 0, "590fb68b", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 48, 59, 1, 0, "bf477ed4", "include/git2.h"); - check_blame_hunk_index(g_repo, g_blame, 49, 60, 1, 0, "d12299fe", "src/git.h"); -} - -/* This was leading to segfaults on some systems during cache eviction. */ -void test_blame_simple__trivial_libgit2_under_cache_pressure(void) -{ - ssize_t old_max_storage = git_cache__max_storage; - git_cache__max_storage = 1024 * 1024; - test_blame_simple__trivial_libgit2(); - git_cache__max_storage = old_max_storage; -} - -/* - * $ git blame -n b.txt -L 8 - * orig line no final line no - * commit V author timestamp V - * 63d671eb 8 (Ben Straub 2013-02-12 15:13:04 -0800 8 - * 63d671eb 9 (Ben Straub 2013-02-12 15:13:04 -0800 9 - * 63d671eb 10 (Ben Straub 2013-02-12 15:13:04 -0800 10 - * aa06ecca 6 (Ben Straub 2013-02-12 15:14:46 -0800 11 - * aa06ecca 7 (Ben Straub 2013-02-12 15:14:46 -0800 12 - * aa06ecca 8 (Ben Straub 2013-02-12 15:14:46 -0800 13 - * aa06ecca 9 (Ben Straub 2013-02-12 15:14:46 -0800 14 - * aa06ecca 10 (Ben Straub 2013-02-12 15:14:46 -0800 15 - */ -void test_blame_simple__can_restrict_lines_min(void) -{ - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - - cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); - - opts.min_line = 8; - cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); - cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); - check_blame_hunk_index(g_repo, g_blame, 0, 8, 3, 0, "63d671eb", "b.txt"); - check_blame_hunk_index(g_repo, g_blame, 1, 11, 5, 0, "aa06ecca", "b.txt"); -} - -/* - * $ git blame -n c.txt - * orig line no final line no - * commit V author timestamp V - * 702c7aa5 1 (Carl Schwan 2020-01-29 01:52:31 +0100 4 - */ -void test_blame_simple__can_ignore_whitespace_change(void) -{ - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - - cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); - - opts.flags |= GIT_BLAME_IGNORE_WHITESPACE; - cl_git_pass(git_blame_file(&g_blame, g_repo, "c.txt", &opts)); - cl_assert_equal_i(1, git_blame_get_hunk_count(g_blame)); - check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "702c7aa5", "c.txt"); -} - -/* - * $ git blame -n b.txt -L ,6 - * orig line no final line no - * commit V author timestamp V - * da237394 1 (Ben Straub 2013-02-12 15:11:30 -0800 1 - * da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2 - * da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3 - * da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4 - * ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5 - * 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6 - */ -void test_blame_simple__can_restrict_lines_max(void) -{ - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - - cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); - - opts.max_line = 6; - cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); - cl_assert_equal_i(3, git_blame_get_hunk_count(g_blame)); - check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt"); - check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); - check_blame_hunk_index(g_repo, g_blame, 2, 6, 1, 0, "63d671eb", "b.txt"); -} - -/* - * $ git blame -n b.txt -L 2,7 - * orig line no final line no - * commit V author timestamp V - * da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2 - * da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3 - * da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4 - * ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5 - * 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6 - * 63d671eb 7 (Ben Straub 2013-02-12 15:13:04 -0800 7 - */ -void test_blame_simple__can_restrict_lines_both(void) -{ - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - - cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); - - opts.min_line = 2; - opts.max_line = 7; - cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); - cl_assert_equal_i(3, git_blame_get_hunk_count(g_blame)); - check_blame_hunk_index(g_repo, g_blame, 0, 2, 3, 0, "da237394", "b.txt"); - check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); - check_blame_hunk_index(g_repo, g_blame, 2, 6, 2, 0, "63d671eb", "b.txt"); -} - -void test_blame_simple__can_blame_huge_file(void) -{ - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - - cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); - - cl_git_pass(git_blame_file(&g_blame, g_repo, "huge.txt", &opts)); - cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); - check_blame_hunk_index(g_repo, g_blame, 0, 1, 65536, 0, "4eecfea", "huge.txt"); - check_blame_hunk_index(g_repo, g_blame, 1, 65537, 1, 0, "6653ff4", "huge.txt"); -} - -/* - * $ git blame -n branch_file.txt be3563a..HEAD - * orig line no final line no - * commit V author timestamp V - * ^be3563a 1 (Scott Chacon 2010-05-25 11:58:27 -0700 1) hi - * a65fedf3 2 (Scott Chacon 2011-08-09 19:33:46 -0700 2) bye! - */ -void test_blame_simple__can_restrict_to_newish_commits(void) -{ - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - - cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); - - { - git_object *obj; - cl_git_pass(git_revparse_single(&obj, g_repo, "be3563a")); - git_oid_cpy(&opts.oldest_commit, git_object_id(obj)); - git_object_free(obj); - } - - cl_git_pass(git_blame_file(&g_blame, g_repo, "branch_file.txt", &opts)); - - cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); - check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 1, "be3563a", "branch_file.txt"); - check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "a65fedf", "branch_file.txt"); -} - -void test_blame_simple__can_restrict_to_first_parent_commits(void) -{ - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - opts.flags |= GIT_BLAME_FIRST_PARENT; - - cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); - - cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); - cl_assert_equal_i(4, git_blame_get_hunk_count(g_blame)); - check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt"); - check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); - check_blame_hunk_index(g_repo, g_blame, 2, 6, 5, 0, "63d671eb", "b.txt"); - check_blame_hunk_index(g_repo, g_blame, 3, 11, 5, 0, "bc7c5ac2", "b.txt"); -} diff --git a/tests/checkout/binaryunicode.c b/tests/checkout/binaryunicode.c deleted file mode 100644 index edb5cfaf5..000000000 --- a/tests/checkout/binaryunicode.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" -#include "repo/repo_helpers.h" -#include "path.h" -#include "futils.h" - -static git_repository *g_repo; - -void test_checkout_binaryunicode__initialize(void) -{ - g_repo = cl_git_sandbox_init("binaryunicode"); -} - -void test_checkout_binaryunicode__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void execute_test(void) -{ - git_oid oid, check; - git_commit *commit; - git_tree *tree; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/branch1")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); - cl_git_pass(git_commit_tree(&tree, commit)); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_checkout_tree(g_repo, (git_object *)tree, &opts)); - - git_tree_free(tree); - git_commit_free(commit); - - /* Verify that the lenna.jpg file was checked out correctly */ - cl_git_pass(git_oid_fromstr(&check, "8ab005d890fe53f65eda14b23672f60d9f4ec5a1")); - cl_git_pass(git_odb_hashfile(&oid, "binaryunicode/lenna.jpg", GIT_OBJECT_BLOB)); - cl_assert_equal_oid(&oid, &check); - - /* Verify that the text file was checked out correctly */ - cl_git_pass(git_oid_fromstr(&check, "965b223880dd4249e2c66a0cc0b4cffe1dc40f5a")); - cl_git_pass(git_odb_hashfile(&oid, "binaryunicode/utf16_withbom_noeol_crlf.txt", GIT_OBJECT_BLOB)); - cl_assert_equal_oid(&oid, &check); -} - -void test_checkout_binaryunicode__noautocrlf(void) -{ - cl_repo_set_bool(g_repo, "core.autocrlf", false); - execute_test(); -} - -void test_checkout_binaryunicode__autocrlf(void) -{ - cl_repo_set_bool(g_repo, "core.autocrlf", true); - execute_test(); -} diff --git a/tests/checkout/checkout_helpers.c b/tests/checkout/checkout_helpers.c deleted file mode 100644 index 1e9c21bdc..000000000 --- a/tests/checkout/checkout_helpers.c +++ /dev/null @@ -1,151 +0,0 @@ -#include "clar_libgit2.h" -#include "checkout_helpers.h" -#include "refs.h" -#include "futils.h" -#include "index.h" - -void assert_on_branch(git_repository *repo, const char *branch) -{ - git_reference *head; - git_str bname = GIT_STR_INIT; - - cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); - cl_assert_(git_reference_type(head) == GIT_REFERENCE_SYMBOLIC, branch); - - cl_git_pass(git_str_joinpath(&bname, "refs/heads", branch)); - cl_assert_equal_s(bname.ptr, git_reference_symbolic_target(head)); - - git_reference_free(head); - git_str_dispose(&bname); -} - -void reset_index_to_treeish(git_object *treeish) -{ - git_object *tree; - git_index *index; - git_repository *repo = git_object_owner(treeish); - - cl_git_pass(git_object_peel(&tree, treeish, GIT_OBJECT_TREE)); - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_read_tree(index, (git_tree *)tree)); - cl_git_pass(git_index_write(index)); - - git_object_free(tree); - git_index_free(index); -} - -int checkout_count_callback( - git_checkout_notify_t why, - const char *path, - const git_diff_file *baseline, - const git_diff_file *target, - const git_diff_file *workdir, - void *payload) -{ - checkout_counts *ct = payload; - - GIT_UNUSED(baseline); GIT_UNUSED(target); GIT_UNUSED(workdir); - - if (why & GIT_CHECKOUT_NOTIFY_CONFLICT) { - ct->n_conflicts++; - - if (ct->debug) { - if (workdir) { - if (baseline) { - if (target) - fprintf(stderr, "M %s (conflicts with M %s)\n", - workdir->path, target->path); - else - fprintf(stderr, "M %s (conflicts with D %s)\n", - workdir->path, baseline->path); - } else { - if (target) - fprintf(stderr, "Existing %s (conflicts with A %s)\n", - workdir->path, target->path); - else - fprintf(stderr, "How can an untracked file be a conflict (%s)\n", workdir->path); - } - } else { - if (baseline) { - if (target) - fprintf(stderr, "D %s (conflicts with M %s)\n", - target->path, baseline->path); - else - fprintf(stderr, "D %s (conflicts with D %s)\n", - baseline->path, baseline->path); - } else { - if (target) - fprintf(stderr, "How can an added file with no workdir be a conflict (%s)\n", target->path); - else - fprintf(stderr, "How can a nonexistent file be a conflict (%s)\n", path); - } - } - } - } - - if (why & GIT_CHECKOUT_NOTIFY_DIRTY) { - ct->n_dirty++; - - if (ct->debug) { - if (workdir) - fprintf(stderr, "M %s\n", workdir->path); - else - fprintf(stderr, "D %s\n", baseline->path); - } - } - - if (why & GIT_CHECKOUT_NOTIFY_UPDATED) { - ct->n_updates++; - - if (ct->debug) { - if (baseline) { - if (target) - fprintf(stderr, "update: M %s\n", path); - else - fprintf(stderr, "update: D %s\n", path); - } else { - if (target) - fprintf(stderr, "update: A %s\n", path); - else - fprintf(stderr, "update: this makes no sense %s\n", path); - } - } - } - - if (why & GIT_CHECKOUT_NOTIFY_UNTRACKED) { - ct->n_untracked++; - - if (ct->debug) - fprintf(stderr, "? %s\n", path); - } - - if (why & GIT_CHECKOUT_NOTIFY_IGNORED) { - ct->n_ignored++; - - if (ct->debug) - fprintf(stderr, "I %s\n", path); - } - - return 0; -} - -void tick_index(git_index *index) -{ - struct timespec ts; - struct p_timeval times[2]; - - cl_assert(index->on_disk); - cl_assert(git_index_path(index)); - - cl_git_pass(git_index_read(index, true)); - ts = index->stamp.mtime; - - times[0].tv_sec = ts.tv_sec; - times[0].tv_usec = ts.tv_nsec / 1000; - times[1].tv_sec = ts.tv_sec + 5; - times[1].tv_usec = ts.tv_nsec / 1000; - - cl_git_pass(p_utimes(git_index_path(index), times)); - cl_git_pass(git_index_read(index, true)); -} diff --git a/tests/checkout/checkout_helpers.h b/tests/checkout/checkout_helpers.h deleted file mode 100644 index 879b48b06..000000000 --- a/tests/checkout/checkout_helpers.h +++ /dev/null @@ -1,30 +0,0 @@ -#include "git2/object.h" -#include "git2/repository.h" - -extern void assert_on_branch(git_repository *repo, const char *branch); -extern void reset_index_to_treeish(git_object *treeish); - -#define check_file_contents(PATH,EXP) \ - cl_assert_equal_file(EXP,0,PATH) - -#define check_file_contents_nocr(PATH,EXP) \ - cl_assert_equal_file_ignore_cr(EXP,0,PATH) - -typedef struct { - int n_conflicts; - int n_dirty; - int n_updates; - int n_untracked; - int n_ignored; - int debug; -} checkout_counts; - -extern int checkout_count_callback( - git_checkout_notify_t why, - const char *path, - const git_diff_file *baseline, - const git_diff_file *target, - const git_diff_file *workdir, - void *payload); - -extern void tick_index(git_index *index); diff --git a/tests/checkout/conflict.c b/tests/checkout/conflict.c deleted file mode 100644 index 4a9e4b9fa..000000000 --- a/tests/checkout/conflict.c +++ /dev/null @@ -1,1145 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/sys/index.h" -#include "futils.h" -#include "repository.h" - -static git_repository *g_repo; -static git_index *g_index; - -#define TEST_REPO_PATH "merge-resolve" - -#define CONFLICTING_ANCESTOR_OID "d427e0b2e138501a3d15cc376077a3631e15bd46" -#define CONFLICTING_OURS_OID "4e886e602529caa9ab11d71f86634bd1b6e0de10" -#define CONFLICTING_THEIRS_OID "2bd0a343aeef7a2cf0d158478966a6e587ff3863" - -#define AUTOMERGEABLE_ANCESTOR_OID "6212c31dab5e482247d7977e4f0dd3601decf13b" -#define AUTOMERGEABLE_OURS_OID "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf" -#define AUTOMERGEABLE_THEIRS_OID "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe" - -#define LINK_ANCESTOR_OID "1a010b1c0f081b2e8901d55307a15c29ff30af0e" -#define LINK_OURS_OID "72ea499e108df5ff0a4a913e7655bbeeb1fb69f2" -#define LINK_THEIRS_OID "8bfb012a6d809e499bd8d3e194a3929bc8995b93" - -#define LINK_ANCESTOR_TARGET "file" -#define LINK_OURS_TARGET "other-file" -#define LINK_THEIRS_TARGET "still-another-file" - -#define CONFLICTING_OURS_FILE \ - "this file is changed in master and branch\n" -#define CONFLICTING_THEIRS_FILE \ - "this file is changed in branch and master\n" -#define CONFLICTING_DIFF3_FILE \ - "<<<<<<< ours\n" \ - "this file is changed in master and branch\n" \ - "=======\n" \ - "this file is changed in branch and master\n" \ - ">>>>>>> theirs\n" - -#define AUTOMERGEABLE_MERGED_FILE \ - "this file is changed in master\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is changed in branch\n" - -struct checkout_index_entry { - uint16_t mode; - char oid_str[GIT_OID_HEXSZ+1]; - int stage; - char path[128]; -}; - -struct checkout_name_entry { - char ancestor[64]; - char ours[64]; - char theirs[64]; -}; - -void test_checkout_conflict__initialize(void) -{ - git_config *cfg; - - g_repo = cl_git_sandbox_init(TEST_REPO_PATH); - git_repository_index(&g_index, g_repo); - - cl_git_rewritefile( - TEST_REPO_PATH "/.gitattributes", - "* text eol=lf\n"); - - /* Ensure that the user's merge.conflictstyle doesn't interfere */ - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge")); - git_config_free(cfg); -} - -void test_checkout_conflict__cleanup(void) -{ - git_index_free(g_index); - cl_git_sandbox_cleanup(); -} - -static void create_index(struct checkout_index_entry *entries, size_t entries_len) -{ - git_str path = GIT_STR_INIT; - size_t i; - - for (i = 0; i < entries_len; i++) { - git_str_joinpath(&path, TEST_REPO_PATH, entries[i].path); - - if (entries[i].stage == 3 && (i == 0 || strcmp(entries[i-1].path, entries[i].path) != 0 || entries[i-1].stage != 2)) - p_unlink(git_str_cstr(&path)); - - cl_git_pass(git_index_remove_bypath(g_index, entries[i].path)); - } - - for (i = 0; i < entries_len; i++) { - git_index_entry entry; - - memset(&entry, 0x0, sizeof(git_index_entry)); - - entry.mode = entries[i].mode; - GIT_INDEX_ENTRY_STAGE_SET(&entry, entries[i].stage); - git_oid_fromstr(&entry.id, entries[i].oid_str); - entry.path = entries[i].path; - - cl_git_pass(git_index_add(g_index, &entry)); - } - - git_str_dispose(&path); -} - -static void create_index_names(struct checkout_name_entry *entries, size_t entries_len) -{ - size_t i; - - for (i = 0; i < entries_len; i++) { - cl_git_pass(git_index_name_add(g_index, - strlen(entries[i].ancestor) == 0 ? NULL : entries[i].ancestor, - strlen(entries[i].ours) == 0 ? NULL : entries[i].ours, - strlen(entries[i].theirs) == 0 ? NULL : entries[i].theirs)); - } -} - -static void create_conflicting_index(void) -{ - struct checkout_index_entry checkout_index_entries[] = { - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting.txt" }, - { 0100644, CONFLICTING_OURS_OID, 2, "conflicting.txt" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting.txt" }, - }; - - create_index(checkout_index_entries, 3); - cl_git_pass(git_index_write(g_index)); -} - -static void ensure_workdir_contents(const char *path, const char *contents) -{ - git_str fullpath = GIT_STR_INIT, data_buf = GIT_STR_INIT; - - cl_git_pass( - git_str_joinpath(&fullpath, git_repository_workdir(g_repo), path)); - - cl_git_pass(git_futils_readbuffer(&data_buf, git_str_cstr(&fullpath))); - cl_assert(strcmp(git_str_cstr(&data_buf), contents) == 0); - - git_str_dispose(&fullpath); - git_str_dispose(&data_buf); -} - -static void ensure_workdir_oid(const char *path, const char *oid_str) -{ - git_oid expected, actual; - - cl_git_pass(git_oid_fromstr(&expected, oid_str)); - cl_git_pass(git_repository_hashfile(&actual, g_repo, path, GIT_OBJECT_BLOB, NULL)); - cl_assert_equal_oid(&expected, &actual); -} - -static void ensure_workdir_mode(const char *path, int mode) -{ -#ifdef GIT_WIN32 - GIT_UNUSED(path); - GIT_UNUSED(mode); -#else - git_str fullpath = GIT_STR_INIT; - struct stat st; - - cl_git_pass( - git_str_joinpath(&fullpath, git_repository_workdir(g_repo), path)); - - cl_git_pass(p_stat(git_str_cstr(&fullpath), &st)); - cl_assert_equal_i((mode & S_IRWXU), (st.st_mode & S_IRWXU)); - - git_str_dispose(&fullpath); -#endif -} - -static void ensure_workdir(const char *path, int mode, const char *oid_str) -{ - ensure_workdir_mode(path, mode); - ensure_workdir_oid(path, oid_str); -} - -static void ensure_workdir_link( - git_repository *repo, - const char *path, - const char *target) -{ - int symlinks; - - cl_git_pass(git_repository__configmap_lookup(&symlinks, repo, GIT_CONFIGMAP_SYMLINKS)); - - if (!symlinks) { - ensure_workdir_contents(path, target); - } else { - git_str fullpath = GIT_STR_INIT; - char actual[1024]; - struct stat st; - int len; - - cl_git_pass( - git_str_joinpath(&fullpath, git_repository_workdir(g_repo), path)); - - cl_git_pass(p_lstat(git_str_cstr(&fullpath), &st)); - cl_assert(S_ISLNK(st.st_mode)); - - cl_assert((len = p_readlink(git_str_cstr(&fullpath), actual, 1024)) > 0); - actual[len] = '\0'; - cl_assert(strcmp(actual, target) == 0); - - git_str_dispose(&fullpath); - } -} - -void test_checkout_conflict__ignored(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy |= GIT_CHECKOUT_SKIP_UNMERGED; - - create_conflicting_index(); - cl_git_pass(p_unlink(TEST_REPO_PATH "/conflicting.txt")); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/conflicting.txt")); -} - -void test_checkout_conflict__ours(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy |= GIT_CHECKOUT_USE_OURS; - - create_conflicting_index(); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - ensure_workdir_contents("conflicting.txt", CONFLICTING_OURS_FILE); -} - -void test_checkout_conflict__theirs(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy |= GIT_CHECKOUT_USE_THEIRS; - - create_conflicting_index(); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - ensure_workdir_contents("conflicting.txt", CONFLICTING_THEIRS_FILE); - -} - -void test_checkout_conflict__diff3(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - create_conflicting_index(); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - ensure_workdir_contents("conflicting.txt", CONFLICTING_DIFF3_FILE); -} - -void test_checkout_conflict__automerge(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - struct checkout_index_entry checkout_index_entries[] = { - { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "automergeable.txt" }, - { 0100644, AUTOMERGEABLE_OURS_OID, 2, "automergeable.txt" }, - { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "automergeable.txt" }, - }; - - create_index(checkout_index_entries, 3); - cl_git_pass(git_index_write(g_index)); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - ensure_workdir_contents("automergeable.txt", AUTOMERGEABLE_MERGED_FILE); -} - -void test_checkout_conflict__directory_file(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - struct checkout_index_entry checkout_index_entries[] = { - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-1" }, - { 0100644, CONFLICTING_OURS_OID, 2, "df-1" }, - { 0100644, CONFLICTING_THEIRS_OID, 0, "df-1/file" }, - - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-2" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "df-2" }, - { 0100644, CONFLICTING_OURS_OID, 0, "df-2/file" }, - - { 0100644, CONFLICTING_THEIRS_OID, 3, "df-3" }, - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-3/file" }, - { 0100644, CONFLICTING_OURS_OID, 2, "df-3/file" }, - - { 0100644, CONFLICTING_OURS_OID, 2, "df-4" }, - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-4/file" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "df-4/file" }, - }; - - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - - create_index(checkout_index_entries, 12); - cl_git_pass(git_index_write(g_index)); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - ensure_workdir_oid("df-1/file", CONFLICTING_THEIRS_OID); - ensure_workdir_oid("df-1~ours", CONFLICTING_OURS_OID); - ensure_workdir_oid("df-2/file", CONFLICTING_OURS_OID); - ensure_workdir_oid("df-2~theirs", CONFLICTING_THEIRS_OID); - ensure_workdir_oid("df-3/file", CONFLICTING_OURS_OID); - ensure_workdir_oid("df-3~theirs", CONFLICTING_THEIRS_OID); - ensure_workdir_oid("df-4~ours", CONFLICTING_OURS_OID); - ensure_workdir_oid("df-4/file", CONFLICTING_THEIRS_OID); -} - -void test_checkout_conflict__directory_file_with_custom_labels(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - struct checkout_index_entry checkout_index_entries[] = { - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-1" }, - { 0100644, CONFLICTING_OURS_OID, 2, "df-1" }, - { 0100644, CONFLICTING_THEIRS_OID, 0, "df-1/file" }, - - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-2" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "df-2" }, - { 0100644, CONFLICTING_OURS_OID, 0, "df-2/file" }, - - { 0100644, CONFLICTING_THEIRS_OID, 3, "df-3" }, - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-3/file" }, - { 0100644, CONFLICTING_OURS_OID, 2, "df-3/file" }, - - { 0100644, CONFLICTING_OURS_OID, 2, "df-4" }, - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-4/file" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "df-4/file" }, - }; - - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - opts.our_label = "HEAD"; - opts.their_label = "branch"; - - create_index(checkout_index_entries, 12); - cl_git_pass(git_index_write(g_index)); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - ensure_workdir_oid("df-1/file", CONFLICTING_THEIRS_OID); - ensure_workdir_oid("df-1~HEAD", CONFLICTING_OURS_OID); - ensure_workdir_oid("df-2/file", CONFLICTING_OURS_OID); - ensure_workdir_oid("df-2~branch", CONFLICTING_THEIRS_OID); - ensure_workdir_oid("df-3/file", CONFLICTING_OURS_OID); - ensure_workdir_oid("df-3~branch", CONFLICTING_THEIRS_OID); - ensure_workdir_oid("df-4~HEAD", CONFLICTING_OURS_OID); - ensure_workdir_oid("df-4/file", CONFLICTING_THEIRS_OID); -} - -void test_checkout_conflict__link_file(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - struct checkout_index_entry checkout_index_entries[] = { - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "link-1" }, - { 0100644, CONFLICTING_OURS_OID, 2, "link-1" }, - { 0120000, LINK_THEIRS_OID, 3, "link-1" }, - - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "link-2" }, - { 0120000, LINK_OURS_OID, 2, "link-2" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "link-2" }, - - { 0120000, LINK_ANCESTOR_OID, 1, "link-3" }, - { 0100644, CONFLICTING_OURS_OID, 2, "link-3" }, - { 0120000, LINK_THEIRS_OID, 3, "link-3" }, - - { 0120000, LINK_ANCESTOR_OID, 1, "link-4" }, - { 0120000, LINK_OURS_OID, 2, "link-4" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "link-4" }, - }; - - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - - create_index(checkout_index_entries, 12); - cl_git_pass(git_index_write(g_index)); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - /* Typechange conflicts always keep the file in the workdir */ - ensure_workdir_oid("link-1", CONFLICTING_OURS_OID); - ensure_workdir_oid("link-2", CONFLICTING_THEIRS_OID); - ensure_workdir_oid("link-3", CONFLICTING_OURS_OID); - ensure_workdir_oid("link-4", CONFLICTING_THEIRS_OID); -} - -void test_checkout_conflict__links(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - struct checkout_index_entry checkout_index_entries[] = { - { 0120000, LINK_ANCESTOR_OID, 1, "link-1" }, - { 0120000, LINK_OURS_OID, 2, "link-1" }, - { 0120000, LINK_THEIRS_OID, 3, "link-1" }, - - { 0120000, LINK_OURS_OID, 2, "link-2" }, - { 0120000, LINK_THEIRS_OID, 3, "link-2" }, - }; - - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - - create_index(checkout_index_entries, 5); - cl_git_pass(git_index_write(g_index)); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - /* Conflicts with links always keep the ours side (even with -Xtheirs) */ - ensure_workdir_link(g_repo, "link-1", LINK_OURS_TARGET); - ensure_workdir_link(g_repo, "link-2", LINK_OURS_TARGET); -} - -void test_checkout_conflict__add_add(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - struct checkout_index_entry checkout_index_entries[] = { - { 0100644, CONFLICTING_OURS_OID, 2, "conflicting.txt" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting.txt" }, - }; - - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - - create_index(checkout_index_entries, 2); - cl_git_pass(git_index_write(g_index)); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - /* Add/add writes diff3 files */ - ensure_workdir_contents("conflicting.txt", CONFLICTING_DIFF3_FILE); -} - -void test_checkout_conflict__mode_change(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - struct checkout_index_entry checkout_index_entries[] = { - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-1" }, - { 0100755, CONFLICTING_ANCESTOR_OID, 2, "executable-1" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "executable-1" }, - - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-2" }, - { 0100644, CONFLICTING_OURS_OID, 2, "executable-2" }, - { 0100755, CONFLICTING_ANCESTOR_OID, 3, "executable-2" }, - - { 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-3" }, - { 0100644, CONFLICTING_ANCESTOR_OID, 2, "executable-3" }, - { 0100755, CONFLICTING_THEIRS_OID, 3, "executable-3" }, - - { 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-4" }, - { 0100755, CONFLICTING_OURS_OID, 2, "executable-4" }, - { 0100644, CONFLICTING_ANCESTOR_OID, 3, "executable-4" }, - - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-5" }, - { 0100755, CONFLICTING_OURS_OID, 2, "executable-5" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "executable-5" }, - - { 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-6" }, - { 0100644, CONFLICTING_OURS_OID, 2, "executable-6" }, - { 0100755, CONFLICTING_THEIRS_OID, 3, "executable-6" }, - }; - - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - - create_index(checkout_index_entries, 18); - cl_git_pass(git_index_write(g_index)); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - /* Keep the modified mode */ - ensure_workdir_oid("executable-1", CONFLICTING_THEIRS_OID); - ensure_workdir_mode("executable-1", 0100755); - - ensure_workdir_oid("executable-2", CONFLICTING_OURS_OID); - ensure_workdir_mode("executable-2", 0100755); - - ensure_workdir_oid("executable-3", CONFLICTING_THEIRS_OID); - ensure_workdir_mode("executable-3", 0100644); - - ensure_workdir_oid("executable-4", CONFLICTING_OURS_OID); - ensure_workdir_mode("executable-4", 0100644); - - ensure_workdir_contents("executable-5", CONFLICTING_DIFF3_FILE); - ensure_workdir_mode("executable-5", 0100755); - - ensure_workdir_contents("executable-6", CONFLICTING_DIFF3_FILE); - ensure_workdir_mode("executable-6", 0100644); -} - -void test_checkout_conflict__renames(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - struct checkout_index_entry checkout_index_entries[] = { - { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, - { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, - { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" }, - { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" }, - { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" }, - { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, - { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" }, - { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" }, - { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" }, - { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, - { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, - { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, - { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, - { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, - { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 2, "3a-newname-in-ours-deleted-in-theirs.txt" }, - { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 1, "3a-renamed-in-ours-deleted-in-theirs.txt" }, - { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 3, "3b-newname-in-theirs-deleted-in-ours.txt" }, - { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 1, "3b-renamed-in-theirs-deleted-in-ours.txt" }, - { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 1, "4a-renamed-in-ours-added-in-theirs.txt" }, - { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 1, "4b-renamed-in-theirs-added-in-ours.txt" }, - { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 1, "5a-renamed-in-ours-added-in-theirs.txt" }, - { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 3, "5a-renamed-in-ours-added-in-theirs.txt" }, - { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 1, "5b-renamed-in-theirs-added-in-ours.txt" }, - { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 2, "5b-renamed-in-theirs-added-in-ours.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 2, "6-both-renamed-1-to-2-ours.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 3, "6-both-renamed-1-to-2-theirs.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 1, "6-both-renamed-1-to-2.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "7-both-renamed-side-1.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "7-both-renamed-side-1.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "7-both-renamed-side-2.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "7-both-renamed-side-2.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" } - }; - - struct checkout_name_entry checkout_name_entries[] = { - { - "3a-renamed-in-ours-deleted-in-theirs.txt", - "3a-newname-in-ours-deleted-in-theirs.txt", - "" - }, - - { - "3b-renamed-in-theirs-deleted-in-ours.txt", - "", - "3b-newname-in-theirs-deleted-in-ours.txt" - }, - - { - "4a-renamed-in-ours-added-in-theirs.txt", - "4a-newname-in-ours-added-in-theirs.txt", - "" - }, - - { - "4b-renamed-in-theirs-added-in-ours.txt", - "", - "4b-newname-in-theirs-added-in-ours.txt" - }, - - { - "5a-renamed-in-ours-added-in-theirs.txt", - "5a-newname-in-ours-added-in-theirs.txt", - "5a-renamed-in-ours-added-in-theirs.txt" - }, - - { - "5b-renamed-in-theirs-added-in-ours.txt", - "5b-renamed-in-theirs-added-in-ours.txt", - "5b-newname-in-theirs-added-in-ours.txt" - }, - - { - "6-both-renamed-1-to-2.txt", - "6-both-renamed-1-to-2-ours.txt", - "6-both-renamed-1-to-2-theirs.txt" - }, - - { - "7-both-renamed-side-1.txt", - "7-both-renamed.txt", - "7-both-renamed-side-1.txt" - }, - - { - "7-both-renamed-side-2.txt", - "7-both-renamed-side-2.txt", - "7-both-renamed.txt" - } - }; - - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - - create_index(checkout_index_entries, 41); - create_index_names(checkout_name_entries, 9); - cl_git_pass(git_index_write(g_index)); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - ensure_workdir("0a-no-change.txt", - 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e"); - - ensure_workdir("0b-duplicated-in-ours.txt", - 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6"); - - ensure_workdir("0b-rewritten-in-ours.txt", - 0100644, "4c7e515d6d52d820496858f2f059ece69e99e2e3"); - - ensure_workdir("0c-duplicated-in-theirs.txt", - 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31"); - - ensure_workdir("0c-rewritten-in-theirs.txt", - 0100644, "4648d658682d1155c2a3db5b0c53305e26884ea5"); - - ensure_workdir("1a-newname-in-ours-edited-in-theirs.txt", - 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638"); - - ensure_workdir("1a-newname-in-ours.txt", - 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb"); - - ensure_workdir("1b-newname-in-theirs-edited-in-ours.txt", - 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a"); - - ensure_workdir("1b-newname-in-theirs.txt", - 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136"); - - ensure_workdir("2-newname-in-both.txt", - 0100644, "178940b450f238a56c0d75b7955cb57b38191982"); - - ensure_workdir("3a-newname-in-ours-deleted-in-theirs.txt", - 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9"); - - ensure_workdir("3b-newname-in-theirs-deleted-in-ours.txt", - 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495"); - - ensure_workdir("4a-newname-in-ours-added-in-theirs.txt~ours", - 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c"); - - ensure_workdir("4a-newname-in-ours-added-in-theirs.txt~theirs", - 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a"); - - ensure_workdir("4b-newname-in-theirs-added-in-ours.txt~ours", - 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9"); - - ensure_workdir("4b-newname-in-theirs-added-in-ours.txt~theirs", - 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db"); - - ensure_workdir("5a-newname-in-ours-added-in-theirs.txt~ours", - 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436"); - - ensure_workdir("5a-newname-in-ours-added-in-theirs.txt~theirs", - 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714"); - - ensure_workdir("5b-newname-in-theirs-added-in-ours.txt~ours", - 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced"); - - ensure_workdir("5b-newname-in-theirs-added-in-ours.txt~theirs", - 0100644, "63247125386de9ec90a27ad36169307bf8a11a38"); - - ensure_workdir("6-both-renamed-1-to-2-ours.txt", - 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450"); - - ensure_workdir("6-both-renamed-1-to-2-theirs.txt", - 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450"); - - ensure_workdir("7-both-renamed.txt~ours", - 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); - - ensure_workdir("7-both-renamed.txt~theirs", - 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); -} - -void test_checkout_conflict__rename_keep_ours(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - struct checkout_index_entry checkout_index_entries[] = { - { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, - { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, - { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" }, - { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" }, - { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" }, - { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, - { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" }, - { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" }, - { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" }, - { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, - { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, - { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, - { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, - { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, - { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 2, "3a-newname-in-ours-deleted-in-theirs.txt" }, - { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 1, "3a-renamed-in-ours-deleted-in-theirs.txt" }, - { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 3, "3b-newname-in-theirs-deleted-in-ours.txt" }, - { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 1, "3b-renamed-in-theirs-deleted-in-ours.txt" }, - { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 1, "4a-renamed-in-ours-added-in-theirs.txt" }, - { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 1, "4b-renamed-in-theirs-added-in-ours.txt" }, - { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 1, "5a-renamed-in-ours-added-in-theirs.txt" }, - { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 3, "5a-renamed-in-ours-added-in-theirs.txt" }, - { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 1, "5b-renamed-in-theirs-added-in-ours.txt" }, - { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 2, "5b-renamed-in-theirs-added-in-ours.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 2, "6-both-renamed-1-to-2-ours.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 3, "6-both-renamed-1-to-2-theirs.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 1, "6-both-renamed-1-to-2.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "7-both-renamed-side-1.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "7-both-renamed-side-1.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "7-both-renamed-side-2.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "7-both-renamed-side-2.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" } - }; - - struct checkout_name_entry checkout_name_entries[] = { - { - "3a-renamed-in-ours-deleted-in-theirs.txt", - "3a-newname-in-ours-deleted-in-theirs.txt", - "" - }, - - { - "3b-renamed-in-theirs-deleted-in-ours.txt", - "", - "3b-newname-in-theirs-deleted-in-ours.txt" - }, - - { - "4a-renamed-in-ours-added-in-theirs.txt", - "4a-newname-in-ours-added-in-theirs.txt", - "" - }, - - { - "4b-renamed-in-theirs-added-in-ours.txt", - "", - "4b-newname-in-theirs-added-in-ours.txt" - }, - - { - "5a-renamed-in-ours-added-in-theirs.txt", - "5a-newname-in-ours-added-in-theirs.txt", - "5a-renamed-in-ours-added-in-theirs.txt" - }, - - { - "5b-renamed-in-theirs-added-in-ours.txt", - "5b-renamed-in-theirs-added-in-ours.txt", - "5b-newname-in-theirs-added-in-ours.txt" - }, - - { - "6-both-renamed-1-to-2.txt", - "6-both-renamed-1-to-2-ours.txt", - "6-both-renamed-1-to-2-theirs.txt" - }, - - { - "7-both-renamed-side-1.txt", - "7-both-renamed.txt", - "7-both-renamed-side-1.txt" - }, - - { - "7-both-renamed-side-2.txt", - "7-both-renamed-side-2.txt", - "7-both-renamed.txt" - } - }; - - opts.checkout_strategy |= GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; - - create_index(checkout_index_entries, 41); - create_index_names(checkout_name_entries, 9); - cl_git_pass(git_index_write(g_index)); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - ensure_workdir("0a-no-change.txt", - 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e"); - - ensure_workdir("0b-duplicated-in-ours.txt", - 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6"); - - ensure_workdir("0b-rewritten-in-ours.txt", - 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e"); - - ensure_workdir("0c-duplicated-in-theirs.txt", - 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31"); - - ensure_workdir("0c-rewritten-in-theirs.txt", - 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09"); - - ensure_workdir("1a-newname-in-ours-edited-in-theirs.txt", - 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638"); - - ensure_workdir("1a-newname-in-ours.txt", - 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb"); - - ensure_workdir("1b-newname-in-theirs-edited-in-ours.txt", - 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a"); - - ensure_workdir("1b-newname-in-theirs.txt", - 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136"); - - ensure_workdir("2-newname-in-both.txt", - 0100644, "178940b450f238a56c0d75b7955cb57b38191982"); - - ensure_workdir("3a-newname-in-ours-deleted-in-theirs.txt", - 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9"); - - ensure_workdir("3b-newname-in-theirs-deleted-in-ours.txt", - 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495"); - - ensure_workdir("4a-newname-in-ours-added-in-theirs.txt", - 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c"); - - ensure_workdir("4b-newname-in-theirs-added-in-ours.txt", - 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9"); - - ensure_workdir("5a-newname-in-ours-added-in-theirs.txt", - 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436"); - - ensure_workdir("5b-newname-in-theirs-added-in-ours.txt", - 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced"); - - ensure_workdir("6-both-renamed-1-to-2-ours.txt", - 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450"); - - ensure_workdir("7-both-renamed.txt", - 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); -} - -void test_checkout_conflict__name_mangled_file_exists_in_workdir(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - struct checkout_index_entry checkout_index_entries[] = { - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-one-side-one.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-one-side-one.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-one-side-two.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-one-side-two.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-one.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-one.txt" }, - - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-two-side-one.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-two-side-one.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-two-side-two.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-two-side-two.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-two.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-two.txt" }, - - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-three-side-one.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-three-side-one.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-three-side-two.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-three-side-two.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-three.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-three.txt" }, - - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-one" }, - { 0100644, CONFLICTING_OURS_OID, 2, "directory_file-one" }, - { 0100644, CONFLICTING_THEIRS_OID, 0, "directory_file-one/file" }, - - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-two" }, - { 0100644, CONFLICTING_OURS_OID, 0, "directory_file-two/file" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "directory_file-two" }, - }; - - struct checkout_name_entry checkout_name_entries[] = { - { - "test-one-side-one.txt", - "test-one.txt", - "test-one-side-one.txt" - }, - { - "test-one-side-two.txt", - "test-one-side-two.txt", - "test-one.txt" - }, - - { - "test-two-side-one.txt", - "test-two.txt", - "test-two-side-one.txt" - }, - { - "test-two-side-two.txt", - "test-two-side-two.txt", - "test-two.txt" - }, - - { - "test-three-side-one.txt", - "test-three.txt", - "test-three-side-one.txt" - }, - { - "test-three-side-two.txt", - "test-three-side-two.txt", - "test-three.txt" - } - }; - - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - - create_index(checkout_index_entries, 24); - create_index_names(checkout_name_entries, 6); - cl_git_pass(git_index_write(g_index)); - - /* Add some files on disk that conflict with the names that would be chosen - * for the files written for each side. */ - - cl_git_rewritefile("merge-resolve/test-one.txt~ours", - "Expect index contents to be written to ~ours_0"); - cl_git_rewritefile("merge-resolve/test-one.txt~theirs", - "Expect index contents to be written to ~theirs_0"); - - cl_git_rewritefile("merge-resolve/test-two.txt~ours", - "Expect index contents to be written to ~ours_3"); - cl_git_rewritefile("merge-resolve/test-two.txt~theirs", - "Expect index contents to be written to ~theirs_3"); - cl_git_rewritefile("merge-resolve/test-two.txt~ours_0", - "Expect index contents to be written to ~ours_3"); - cl_git_rewritefile("merge-resolve/test-two.txt~theirs_0", - "Expect index contents to be written to ~theirs_3"); - cl_git_rewritefile("merge-resolve/test-two.txt~ours_1", - "Expect index contents to be written to ~ours_3"); - cl_git_rewritefile("merge-resolve/test-two.txt~theirs_1", - "Expect index contents to be written to ~theirs_3"); - cl_git_rewritefile("merge-resolve/test-two.txt~ours_2", - "Expect index contents to be written to ~ours_3"); - cl_git_rewritefile("merge-resolve/test-two.txt~theirs_2", - "Expect index contents to be written to ~theirs_3"); - - cl_git_rewritefile("merge-resolve/test-three.txt~Ours", - "Expect case insensitive filesystems to create ~ours_0"); - cl_git_rewritefile("merge-resolve/test-three.txt~THEIRS", - "Expect case insensitive filesystems to create ~theirs_0"); - - cl_git_rewritefile("merge-resolve/directory_file-one~ours", - "Index contents written to ~ours_0 in this D/F conflict"); - cl_git_rewritefile("merge-resolve/directory_file-two~theirs", - "Index contents written to ~theirs_0 in this D/F conflict"); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - ensure_workdir("test-one.txt~ours_0", - 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); - ensure_workdir("test-one.txt~theirs_0", - 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); - - ensure_workdir("test-two.txt~ours_3", - 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); - ensure_workdir("test-two.txt~theirs_3", - 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); - - /* Name is mangled on case insensitive only */ -#if defined(GIT_WIN32) || defined(__APPLE__) - ensure_workdir("test-three.txt~ours_0", - 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); - ensure_workdir("test-three.txt~theirs_0", - 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); -#else - ensure_workdir("test-three.txt~ours", - 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); - ensure_workdir("test-three.txt~theirs", - 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); -#endif - - ensure_workdir("directory_file-one~ours_0", 0100644, CONFLICTING_OURS_OID); - ensure_workdir("directory_file-two~theirs_0", 0100644, CONFLICTING_THEIRS_OID); -} - -void test_checkout_conflict__update_only(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - struct checkout_index_entry checkout_index_entries[] = { - { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "automergeable.txt" }, - { 0100644, AUTOMERGEABLE_OURS_OID, 2, "automergeable.txt" }, - { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "automergeable.txt" }, - - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "modify-delete" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "modify-delete" }, - - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-one" }, - { 0100644, CONFLICTING_OURS_OID, 2, "directory_file-one" }, - { 0100644, CONFLICTING_THEIRS_OID, 0, "directory_file-one/file" }, - - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-two" }, - { 0100644, CONFLICTING_OURS_OID, 0, "directory_file-two/file" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "directory_file-two" }, - }; - - opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_ONLY; - - create_index(checkout_index_entries, 3); - cl_git_pass(git_index_write(g_index)); - - cl_git_pass(p_mkdir("merge-resolve/directory_file-two", 0777)); - cl_git_rewritefile("merge-resolve/directory_file-two/file", CONFLICTING_OURS_FILE); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - ensure_workdir_contents("automergeable.txt", AUTOMERGEABLE_MERGED_FILE); - ensure_workdir("directory_file-two/file", 0100644, CONFLICTING_OURS_OID); - - cl_assert(!git_fs_path_exists("merge-resolve/modify-delete")); - cl_assert(!git_fs_path_exists("merge-resolve/test-one.txt")); - cl_assert(!git_fs_path_exists("merge-resolve/test-one-side-one.txt")); - cl_assert(!git_fs_path_exists("merge-resolve/test-one-side-two.txt")); - cl_assert(!git_fs_path_exists("merge-resolve/test-one.txt~ours")); - cl_assert(!git_fs_path_exists("merge-resolve/test-one.txt~theirs")); - cl_assert(!git_fs_path_exists("merge-resolve/directory_file-one/file")); - cl_assert(!git_fs_path_exists("merge-resolve/directory_file-one~ours")); - cl_assert(!git_fs_path_exists("merge-resolve/directory_file-two~theirs")); -} - -void test_checkout_conflict__path_filters(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - char *paths[] = { "conflicting-1.txt", "conflicting-3.txt" }; - git_strarray patharray = {0}; - - struct checkout_index_entry checkout_index_entries[] = { - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-1.txt" }, - { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-1.txt" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-1.txt" }, - - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-2.txt" }, - { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-2.txt" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-2.txt" }, - - { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-3.txt" }, - { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-3.txt" }, - { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-3.txt" }, - - { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-4.txt" }, - { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-4.txt" }, - { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-4.txt" }, - }; - - patharray.count = 2; - patharray.strings = paths; - - opts.paths = patharray; - - create_index(checkout_index_entries, 12); - cl_git_pass(git_index_write(g_index)); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - ensure_workdir_contents("conflicting-1.txt", CONFLICTING_DIFF3_FILE); - cl_assert(!git_fs_path_exists("merge-resolve/conflicting-2.txt")); - ensure_workdir_contents("conflicting-3.txt", AUTOMERGEABLE_MERGED_FILE); - cl_assert(!git_fs_path_exists("merge-resolve/conflicting-4.txt")); -} - -static void collect_progress( - const char *path, - size_t completed_steps, - size_t total_steps, - void *payload) -{ - git_vector *paths = payload; - - GIT_UNUSED(completed_steps); - GIT_UNUSED(total_steps); - - if (path == NULL) - return; - - git_vector_insert(paths, strdup(path)); -} - -void test_checkout_conflict__report_progress(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_vector paths = GIT_VECTOR_INIT; - char *path; - size_t i; - - struct checkout_index_entry checkout_index_entries[] = { - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-1.txt" }, - { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-1.txt" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-1.txt" }, - - { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-2.txt" }, - { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-2.txt" }, - { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-2.txt" }, - - { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-3.txt" }, - { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-3.txt" }, - { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-3.txt" }, - - { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-4.txt" }, - { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-4.txt" }, - { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-4.txt" }, - }; - - opts.progress_cb = collect_progress; - opts.progress_payload = &paths; - - - create_index(checkout_index_entries, 12); - cl_git_pass(git_index_write(g_index)); - - cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); - - cl_assert_equal_i(4, git_vector_length(&paths)); - cl_assert_equal_s("conflicting-1.txt", git_vector_get(&paths, 0)); - cl_assert_equal_s("conflicting-2.txt", git_vector_get(&paths, 1)); - cl_assert_equal_s("conflicting-3.txt", git_vector_get(&paths, 2)); - cl_assert_equal_s("conflicting-4.txt", git_vector_get(&paths, 3)); - - git_vector_foreach(&paths, i, path) - git__free(path); - - git_vector_free(&paths); -} diff --git a/tests/checkout/crlf.c b/tests/checkout/crlf.c deleted file mode 100644 index 21f8a852a..000000000 --- a/tests/checkout/crlf.c +++ /dev/null @@ -1,496 +0,0 @@ -#include "clar_libgit2.h" -#include "checkout_helpers.h" -#include "../filter/crlf.h" -#include "futils.h" - -#include "git2/checkout.h" -#include "repository.h" -#include "index.h" -#include "posix.h" - -static git_repository *g_repo; - -static const char *systype; -static git_str expected_fixture = GIT_STR_INIT; - -static int unlink_file(void *payload, git_str *path) -{ - char *fn; - - cl_assert(fn = git_fs_path_basename(path->ptr)); - - GIT_UNUSED(payload); - - if (strcmp(fn, ".git")) - cl_must_pass(p_unlink(path->ptr)); - - git__free(fn); - return 0; -} - -void test_checkout_crlf__initialize(void) -{ - git_str reponame = GIT_STR_INIT; - - g_repo = cl_git_sandbox_init("crlf"); - - /* - * remove the contents of the working directory so that we can - * check out over it. - */ - cl_git_pass(git_str_puts(&reponame, "crlf")); - cl_git_pass(git_fs_path_direach(&reponame, 0, unlink_file, NULL)); - - if (GIT_EOL_NATIVE == GIT_EOL_CRLF) - systype = "windows"; - else - systype = "posix"; - - git_str_dispose(&reponame); -} - -void test_checkout_crlf__cleanup(void) -{ - cl_git_sandbox_cleanup(); - - if (expected_fixture.size) { - cl_fixture_cleanup(expected_fixture.ptr); - git_str_dispose(&expected_fixture); - } -} - -struct compare_data -{ - const char *dirname; - const char *autocrlf; - const char *attrs; -}; - -static int compare_file(void *payload, git_str *actual_path) -{ - git_str expected_path = GIT_STR_INIT; - git_str actual_contents = GIT_STR_INIT; - git_str expected_contents = GIT_STR_INIT; - struct compare_data *cd = payload; - bool failed = true; - int cmp_git, cmp_gitattributes; - char *basename; - - basename = git_fs_path_basename(actual_path->ptr); - cmp_git = strcmp(basename, ".git"); - cmp_gitattributes = strcmp(basename, ".gitattributes"); - - if (cmp_git == 0 || cmp_gitattributes == 0) { - failed = false; - goto done; - } - - cl_git_pass(git_str_joinpath(&expected_path, cd->dirname, basename)); - - if (!git_fs_path_isfile(expected_path.ptr) || - !git_fs_path_isfile(actual_path->ptr)) - goto done; - - if (git_futils_readbuffer(&actual_contents, actual_path->ptr) < 0 || - git_futils_readbuffer(&expected_contents, expected_path.ptr) < 0) - goto done; - - if (actual_contents.size != expected_contents.size) - goto done; - - if (memcmp(actual_contents.ptr, expected_contents.ptr, expected_contents.size) != 0) - goto done; - - failed = false; - -done: - if (failed) { - git_str details = GIT_STR_INIT; - git_str_printf(&details, "filename=%s, system=%s, autocrlf=%s, attrs={%s}", - git_fs_path_basename(actual_path->ptr), systype, cd->autocrlf, cd->attrs); - clar__fail(__FILE__, __func__, __LINE__, - "checked out contents did not match expected", details.ptr, 0); - git_str_dispose(&details); - } - - git__free(basename); - git_str_dispose(&expected_contents); - git_str_dispose(&actual_contents); - git_str_dispose(&expected_path); - - return 0; -} - -static void test_checkout(const char *autocrlf, const char *attrs) -{ - git_str attrbuf = GIT_STR_INIT; - git_str expected_dirname = GIT_STR_INIT; - git_str systype_and_direction = GIT_STR_INIT; - git_str sandboxname = GIT_STR_INIT; - git_str reponame = GIT_STR_INIT; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - struct compare_data compare_data = { NULL, autocrlf, attrs }; - const char *c; - - cl_git_pass(git_str_puts(&reponame, "crlf")); - - cl_git_pass(git_str_puts(&systype_and_direction, systype)); - cl_git_pass(git_str_puts(&systype_and_direction, "_to_workdir")); - - cl_git_pass(git_str_puts(&sandboxname, "autocrlf_")); - cl_git_pass(git_str_puts(&sandboxname, autocrlf)); - - if (*attrs) { - cl_git_pass(git_str_puts(&sandboxname, ",")); - - for (c = attrs; *c; c++) { - if (*c == ' ') - cl_git_pass(git_str_putc(&sandboxname, ',')); - else if (*c == '=') - cl_git_pass(git_str_putc(&sandboxname, '_')); - else - cl_git_pass(git_str_putc(&sandboxname, *c)); - } - - cl_git_pass(git_str_printf(&attrbuf, "* %s\n", attrs)); - cl_git_mkfile("crlf/.gitattributes", attrbuf.ptr); - } - - cl_repo_set_string(g_repo, "core.autocrlf", autocrlf); - - cl_git_pass(git_str_joinpath(&expected_dirname, systype_and_direction.ptr, sandboxname.ptr)); - cl_git_pass(git_str_joinpath(&expected_fixture, "crlf_data", expected_dirname.ptr)); - cl_fixture_sandbox(expected_fixture.ptr); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - cl_git_pass(git_checkout_head(g_repo, &opts)); - - compare_data.dirname = sandboxname.ptr; - cl_git_pass(git_fs_path_direach(&reponame, 0, compare_file, &compare_data)); - - cl_fixture_cleanup(expected_fixture.ptr); - git_str_dispose(&expected_fixture); - - git_str_dispose(&attrbuf); - git_str_dispose(&expected_fixture); - git_str_dispose(&expected_dirname); - git_str_dispose(&sandboxname); - git_str_dispose(&systype_and_direction); - git_str_dispose(&reponame); -} - -static void empty_workdir(const char *name) -{ - git_vector contents = GIT_VECTOR_INIT; - char *basename; - int cmp; - size_t i; - const char *fn; - - cl_git_pass(git_fs_path_dirload(&contents, name, 0, 0)); - git_vector_foreach(&contents, i, fn) { - cl_assert(basename = git_fs_path_basename(fn)); - cmp = strncasecmp(basename, ".git", 4); - - git__free(basename); - - if (cmp) - cl_git_pass(p_unlink(fn)); - } - git_vector_free_deep(&contents); -} - -void test_checkout_crlf__matches_core_git(void) -{ - const char *autocrlf[] = { "true", "false", "input", NULL }; - const char *attrs[] = { "", "-crlf", "-text", "eol=crlf", "eol=lf", - "text", "text eol=crlf", "text eol=lf", - "text=auto", "text=auto eol=crlf", "text=auto eol=lf", - NULL }; - const char **a, **b; - - for (a = autocrlf; *a; a++) { - for (b = attrs; *b; b++) { - empty_workdir("crlf"); - test_checkout(*a, *b); - } - } -} - -void test_checkout_crlf__detect_crlf_autocrlf_false(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_repo_set_bool(g_repo, "core.autocrlf", false); - - git_checkout_head(g_repo, &opts); - - check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); - check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); -} - -void test_checkout_crlf__autocrlf_false_index_size_is_unfiltered_size(void) -{ - git_index *index; - const git_index_entry *entry; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_repo_set_bool(g_repo, "core.autocrlf", false); - - cl_git_pass(git_repository_index(&index, g_repo)); - tick_index(index); - - cl_git_pass(git_checkout_head(g_repo, &opts)); - - cl_assert((entry = git_index_get_bypath(index, "all-lf", 0)) != NULL); - cl_assert(entry->file_size == strlen(ALL_LF_TEXT_RAW)); - - cl_assert((entry = git_index_get_bypath(index, "all-crlf", 0)) != NULL); - cl_assert(entry->file_size == strlen(ALL_CRLF_TEXT_RAW)); - - git_index_free(index); -} - -void test_checkout_crlf__detect_crlf_autocrlf_true(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - git_checkout_head(g_repo, &opts); - - check_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF); - check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); -} - -void test_checkout_crlf__detect_crlf_autocrlf_true_utf8(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - git_repository_set_head(g_repo, "refs/heads/master"); - git_checkout_head(g_repo, &opts); - - check_file_contents("./crlf/few-utf8-chars-lf", FEW_UTF8_CRLF_RAW); - check_file_contents("./crlf/many-utf8-chars-lf", MANY_UTF8_CRLF_RAW); - - check_file_contents("./crlf/few-utf8-chars-crlf", FEW_UTF8_CRLF_RAW); - check_file_contents("./crlf/many-utf8-chars-crlf", MANY_UTF8_CRLF_RAW); -} - -void test_checkout_crlf__autocrlf_true_index_size_is_filtered_size(void) -{ - git_index *index; - const git_index_entry *entry; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - git_repository_index(&index, g_repo); - tick_index(index); - - git_checkout_head(g_repo, &opts); - - cl_assert((entry = git_index_get_bypath(index, "all-lf", 0)) != NULL); - - cl_assert_equal_sz(strlen(ALL_LF_TEXT_AS_CRLF), entry->file_size); - - cl_assert((entry = git_index_get_bypath(index, "all-crlf", 0)) != NULL); - cl_assert_equal_sz(strlen(ALL_CRLF_TEXT_RAW), entry->file_size); - - git_index_free(index); -} - -void test_checkout_crlf__with_ident(void) -{ - git_index *index; - const git_index_entry *entry; - git_blob *blob; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_mkfile("crlf/.gitattributes", - "*.txt text\n*.bin binary\n" - "*.crlf text eol=crlf\n" - "*.lf text eol=lf\n" - "*.ident text ident\n" - "*.identcrlf ident text eol=crlf\n" - "*.identlf ident text eol=lf\n"); - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - /* add files with $Id$ */ - - cl_git_mkfile("crlf/lf.ident", ALL_LF_TEXT_RAW "\n$Id: initial content$\n"); - cl_git_mkfile("crlf/crlf.ident", ALL_CRLF_TEXT_RAW "\r\n$Id$\r\n\r\n"); - cl_git_mkfile("crlf/more1.identlf", "$Id$\n" MORE_LF_TEXT_RAW); - cl_git_mkfile("crlf/more2.identcrlf", "\r\n$Id: $\r\n" MORE_CRLF_TEXT_RAW); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_add_bypath(index, "lf.ident")); - cl_git_pass(git_index_add_bypath(index, "crlf.ident")); - cl_git_pass(git_index_add_bypath(index, "more1.identlf")); - cl_git_pass(git_index_add_bypath(index, "more2.identcrlf")); - cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "Some ident files\n"); - - git_checkout_head(g_repo, &opts); - - /* check that blobs have $Id$ */ - - cl_assert((entry = git_index_get_bypath(index, "lf.ident", 0))); - cl_git_pass(git_blob_lookup(&blob, g_repo, &entry->id)); - cl_assert_equal_s( - ALL_LF_TEXT_RAW "\n$Id$\n", git_blob_rawcontent(blob)); - git_blob_free(blob); - - cl_assert((entry = git_index_get_bypath(index, "more2.identcrlf", 0))); - cl_git_pass(git_blob_lookup(&blob, g_repo, &entry->id)); - cl_assert_equal_s( - "\n$Id$\n" MORE_CRLF_TEXT_AS_LF, git_blob_rawcontent(blob)); - git_blob_free(blob); - - /* check that filesystem is initially untouched - matching core Git */ - - cl_assert_equal_file( - ALL_LF_TEXT_RAW "\n$Id: initial content$\n", 0, "crlf/lf.ident"); - - /* check that forced checkout rewrites correctly */ - - p_unlink("crlf/lf.ident"); - p_unlink("crlf/crlf.ident"); - p_unlink("crlf/more1.identlf"); - p_unlink("crlf/more2.identcrlf"); - - cl_git_pass(git_checkout_head(g_repo, &opts)); - - cl_assert_equal_file( - ALL_LF_TEXT_AS_CRLF - "\r\n$Id: fcf6d4d9c212dc66563b1171b1cd99953c756467 $\r\n", - 0, "crlf/lf.ident"); - cl_assert_equal_file( - ALL_CRLF_TEXT_RAW - "\r\n$Id: f2c66ad9b2b5a734d9bf00d5000cc10a62b8a857 $\r\n\r\n", - 0, "crlf/crlf.ident"); - - cl_assert_equal_file( - "$Id: f7830382dac1f1583422be5530fdfbd26289431b $\n" - MORE_LF_TEXT_AS_LF, 0, "crlf/more1.identlf"); - - cl_assert_equal_file( - "\r\n$Id: 74677a68413012ce8d7e7cfc3f12603df3a3eac4 $\r\n" - MORE_CRLF_TEXT_AS_CRLF, 0, "crlf/more2.identcrlf"); - - git_index_free(index); -} - -void test_checkout_crlf__autocrlf_false_no_attrs(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_repo_set_bool(g_repo, "core.autocrlf", false); - - cl_git_pass(git_checkout_head(g_repo, &opts)); - - check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); - check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); -} - -void test_checkout_crlf__autocrlf_true_no_attrs(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - cl_git_pass(git_checkout_head(g_repo, &opts)); - - check_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF); - check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_AS_CRLF); -} - -void test_checkout_crlf__autocrlf_input_no_attrs(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_repo_set_string(g_repo, "core.autocrlf", "input"); - - cl_git_pass(git_checkout_head(g_repo, &opts)); - - check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); - check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); -} - -void test_checkout_crlf__autocrlf_false_text_auto_attr(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); - - cl_repo_set_bool(g_repo, "core.autocrlf", false); - - cl_git_pass(git_checkout_head(g_repo, &opts)); - - if (GIT_EOL_NATIVE == GIT_EOL_CRLF) { - check_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF); - check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_AS_CRLF); - } else { - check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); - check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); - } -} - -void test_checkout_crlf__autocrlf_true_text_auto_attr(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - cl_git_pass(git_checkout_head(g_repo, &opts)); - - check_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF); - check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_AS_CRLF); -} - -void test_checkout_crlf__autocrlf_input_text_auto_attr(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); - - cl_repo_set_string(g_repo, "core.autocrlf", "input"); - - cl_git_pass(git_checkout_head(g_repo, &opts)); - - check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); - check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); -} - -void test_checkout_crlf__can_write_empty_file(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/empty-files")); - cl_git_pass(git_checkout_head(g_repo, &opts)); - - check_file_contents("./crlf/test1.txt", ""); - - check_file_contents("./crlf/test2.txt", "test2.txt's content\r\n"); - - check_file_contents("./crlf/test3.txt", ""); -} diff --git a/tests/checkout/head.c b/tests/checkout/head.c deleted file mode 100644 index 3b0bf4729..000000000 --- a/tests/checkout/head.c +++ /dev/null @@ -1,292 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" -#include "repo/repo_helpers.h" -#include "path.h" -#include "futils.h" - -static git_repository *g_repo; - -void test_checkout_head__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_checkout_head__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_checkout_head__unborn_head_returns_GIT_EUNBORNBRANCH(void) -{ - make_head_unborn(g_repo, NON_EXISTING_HEAD); - - cl_assert_equal_i(GIT_EUNBORNBRANCH, git_checkout_head(g_repo, NULL)); -} - -void test_checkout_head__with_index_only_tree(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_index *index; - - /* let's start by getting things into a known state */ - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - cl_git_pass(git_checkout_head(g_repo, &opts)); - - /* now let's stage some new stuff including a new directory */ - - cl_git_pass(git_repository_index(&index, g_repo)); - - p_mkdir("testrepo/newdir", 0777); - cl_git_mkfile("testrepo/newdir/newfile.txt", "new file\n"); - - cl_git_pass(git_index_add_bypath(index, "newdir/newfile.txt")); - cl_git_pass(git_index_write(index)); - - cl_assert(git_fs_path_isfile("testrepo/newdir/newfile.txt")); - cl_assert(git_index_get_bypath(index, "newdir/newfile.txt", 0) != NULL); - - git_index_free(index); - - /* okay, so now we have staged this new file; let's see if we can remove */ - - opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; - cl_git_pass(git_checkout_head(g_repo, &opts)); - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_assert(!git_fs_path_isfile("testrepo/newdir/newfile.txt")); - cl_assert(git_index_get_bypath(index, "newdir/newfile.txt", 0) == NULL); - - git_index_free(index); -} - -void test_checkout_head__do_not_remove_untracked_file(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_index *index; - - cl_git_pass(p_mkdir("testrepo/tracked", 0755)); - cl_git_mkfile("testrepo/tracked/tracked", "tracked\n"); - cl_git_mkfile("testrepo/tracked/untracked", "untracked\n"); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_add_bypath(index, "tracked/tracked")); - cl_git_pass(git_index_write(index)); - - git_index_free(index); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - cl_git_pass(git_checkout_head(g_repo, &opts)); - - cl_assert(!git_fs_path_isfile("testrepo/tracked/tracked")); - cl_assert(git_fs_path_isfile("testrepo/tracked/untracked")); -} - -void test_checkout_head__do_not_remove_untracked_file_in_subdir(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_index *index; - - cl_git_pass(p_mkdir("testrepo/tracked", 0755)); - cl_git_pass(p_mkdir("testrepo/tracked/subdir", 0755)); - cl_git_mkfile("testrepo/tracked/tracked", "tracked\n"); - cl_git_mkfile("testrepo/tracked/subdir/tracked", "tracked\n"); - cl_git_mkfile("testrepo/tracked/subdir/untracked", "untracked\n"); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_add_bypath(index, "tracked/tracked")); - cl_git_pass(git_index_add_bypath(index, "tracked/subdir/tracked")); - cl_git_pass(git_index_write(index)); - - git_index_free(index); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - cl_git_pass(git_checkout_head(g_repo, &opts)); - - cl_assert(!git_fs_path_isfile("testrepo/tracked/tracked")); - cl_assert(!git_fs_path_isfile("testrepo/tracked/subdir/tracked")); - cl_assert(git_fs_path_isfile("testrepo/tracked/subdir/untracked")); -} - -void test_checkout_head__do_remove_untracked_paths(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_index *index; - char *paths[] = {"tracked/untracked"}; - - cl_git_pass(p_mkdir("testrepo/tracked", 0755)); - cl_git_pass(p_mkdir("testrepo/tracked/subdir", 0755)); - cl_git_mkfile("testrepo/tracked/tracked", "tracked\n"); - cl_git_mkfile("testrepo/tracked/untracked", "untracked\n"); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_add_bypath(index, "tracked/tracked")); - cl_git_pass(git_index_write(index)); - - git_index_free(index); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; - opts.paths.strings = paths; - opts.paths.count = 1; - cl_git_pass(git_checkout_head(g_repo, &opts)); - - cl_assert(git_fs_path_isfile("testrepo/tracked/tracked")); - cl_assert(!git_fs_path_isfile("testrepo/tracked/untracked")); -} - -void test_checkout_head__do_remove_tracked_subdir(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_index *index; - - cl_git_pass(p_mkdir("testrepo/subdir", 0755)); - cl_git_pass(p_mkdir("testrepo/subdir/tracked", 0755)); - cl_git_mkfile("testrepo/subdir/tracked-file", "tracked\n"); - cl_git_mkfile("testrepo/subdir/untracked-file", "untracked\n"); - cl_git_mkfile("testrepo/subdir/tracked/tracked1", "tracked\n"); - cl_git_mkfile("testrepo/subdir/tracked/tracked2", "tracked\n"); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_add_bypath(index, "subdir/tracked-file")); - cl_git_pass(git_index_add_bypath(index, "subdir/tracked/tracked1")); - cl_git_pass(git_index_add_bypath(index, "subdir/tracked/tracked2")); - cl_git_pass(git_index_write(index)); - - git_index_free(index); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - cl_git_pass(git_checkout_head(g_repo, &opts)); - - cl_assert(!git_fs_path_isdir("testrepo/subdir/tracked")); - cl_assert(!git_fs_path_isfile("testrepo/subdir/tracked-file")); - cl_assert(git_fs_path_isfile("testrepo/subdir/untracked-file")); -} - -void test_checkout_head__typechange_workdir(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_object *target; - struct stat st; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_revparse_single(&target, g_repo, "HEAD")); - cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); - - cl_must_pass(p_chmod("testrepo/new.txt", 0755)); - cl_git_pass(git_checkout_head(g_repo, &opts)); - - cl_git_pass(p_stat("testrepo/new.txt", &st)); - cl_assert(!GIT_PERMS_IS_EXEC(st.st_mode)); - - git_object_free(target); -} - -void test_checkout_head__typechange_index_and_workdir(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_object *target; - git_index *index; - struct stat st; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_revparse_single(&target, g_repo, "HEAD")); - cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); - - cl_must_pass(p_chmod("testrepo/new.txt", 0755)); - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_add_bypath(index, "new.txt")); - cl_git_pass(git_index_write(index)); - cl_git_pass(git_checkout_head(g_repo, &opts)); - - cl_git_pass(p_stat("testrepo/new.txt", &st)); - cl_assert(!GIT_PERMS_IS_EXEC(st.st_mode)); - - git_object_free(target); - git_index_free(index); -} - -void test_checkout_head__workdir_filemode_is_simplified(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_object *target, *branch; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_revparse_single(&target, g_repo, "a38d028f71eaa590febb7d716b1ca32350cf70da")); - cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); - - cl_must_pass(p_chmod("testrepo/branch_file.txt", 0666)); - - /* - * Checkout should not fail with a conflict; though the file mode - * on disk is literally different to the base (0666 vs 0644), Git - * ignores the actual mode and simply treats both as non-executable. - */ - cl_git_pass(git_revparse_single(&branch, g_repo, "099fabac3a9ea935598528c27f866e34089c2eff")); - - opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - cl_git_pass(git_checkout_tree(g_repo, branch, NULL)); - - git_object_free(branch); - git_object_free(target); -} - -void test_checkout_head__obeys_filemode_true(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_object *target, *branch; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - /* In this commit, `README` is executable */ - cl_git_pass(git_revparse_single(&target, g_repo, "f9ed4af42472941da45a3c")); - cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); - - cl_repo_set_bool(g_repo, "core.filemode", true); - cl_must_pass(p_chmod("testrepo/README", 0644)); - - /* - * Checkout will fail with a conflict; the file mode is updated in - * the checkout target, but the contents have changed in our branch. - */ - cl_git_pass(git_revparse_single(&branch, g_repo, "099fabac3a9ea935598528c27f866e34089c2eff")); - - opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - cl_git_fail_with(GIT_ECONFLICT, git_checkout_tree(g_repo, branch, NULL)); - - git_object_free(branch); - git_object_free(target); -} - -void test_checkout_head__obeys_filemode_false(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_object *target, *branch; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - /* In this commit, `README` is executable */ - cl_git_pass(git_revparse_single(&target, g_repo, "f9ed4af42472941da45a3c")); - cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); - - cl_repo_set_bool(g_repo, "core.filemode", false); - cl_must_pass(p_chmod("testrepo/README", 0644)); - - /* - * Checkout will fail with a conflict; the file contents are updated - * in the checkout target, but the filemode has changed in our branch. - */ - cl_git_pass(git_revparse_single(&branch, g_repo, "099fabac3a9ea935598528c27f866e34089c2eff")); - - opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; - opts.checkout_strategy |= GIT_CHECKOUT_SAFE; - cl_git_pass(git_checkout_tree(g_repo, branch, NULL)); - - git_object_free(branch); - git_object_free(target); -} diff --git a/tests/checkout/icase.c b/tests/checkout/icase.c deleted file mode 100644 index d77c7abd5..000000000 --- a/tests/checkout/icase.c +++ /dev/null @@ -1,292 +0,0 @@ -#include "clar_libgit2.h" - -#include "git2/checkout.h" -#include "refs.h" -#include "path.h" -#include "repository.h" - -#ifdef GIT_WIN32 -# include -#else -# include -#endif - -static git_repository *repo; -static git_object *obj; -static git_checkout_options checkout_opts; - -void test_checkout_icase__initialize(void) -{ - git_oid id; - git_config *cfg; - int icase = 0; - - repo = cl_git_sandbox_init("testrepo"); - - cl_git_pass(git_repository_config_snapshot(&cfg, repo)); - git_config_get_bool(&icase, cfg, "core.ignorecase"); - git_config_free(cfg); - - if (!icase) - cl_skip(); - - cl_git_pass(git_reference_name_to_id(&id, repo, "refs/heads/dir")); - cl_git_pass(git_object_lookup(&obj, repo, &id, GIT_OBJECT_ANY)); - - git_checkout_options_init(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION); - checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE; -} - -void test_checkout_icase__cleanup(void) -{ - git_object_free(obj); - cl_git_sandbox_cleanup(); -} - -static char *get_filename(const char *in) -{ - char *search_dirname, *search_filename, *filename = NULL; - git_str out = GIT_STR_INIT; - DIR *dir; - struct dirent *de; - - cl_assert(search_dirname = git_fs_path_dirname(in)); - cl_assert(search_filename = git_fs_path_basename(in)); - - cl_assert(dir = opendir(search_dirname)); - - while ((de = readdir(dir))) { - if (strcasecmp(de->d_name, search_filename) == 0) { - git_str_join(&out, '/', search_dirname, de->d_name); - filename = git_str_detach(&out); - break; - } - } - - closedir(dir); - - git__free(search_dirname); - git__free(search_filename); - git_str_dispose(&out); - - return filename; -} - -static void assert_name_is(const char *expected) -{ - char *actual; - size_t actual_len, expected_len, start; - - cl_assert(actual = get_filename(expected)); - - expected_len = strlen(expected); - actual_len = strlen(actual); - cl_assert(actual_len >= expected_len); - - start = actual_len - expected_len; - cl_assert_equal_s(expected, actual + start); - - if (start) - cl_assert_equal_strn("/", actual + (start - 1), 1); - - free(actual); -} - -static int symlink_or_fake(git_repository *repo, const char *a, const char *b) -{ - int symlinks; - - cl_git_pass(git_repository__configmap_lookup(&symlinks, repo, GIT_CONFIGMAP_SYMLINKS)); - - if (symlinks) - return p_symlink(a, b); - else - return git_futils_fake_symlink(a, b); -} - -void test_checkout_icase__refuses_to_overwrite_files_for_files(void) -{ - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; - - cl_git_write2file("testrepo/BRANCH_FILE.txt", "neue file\n", 10, \ - O_WRONLY | O_CREAT | O_TRUNC, 0644); - - cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); - assert_name_is("testrepo/BRANCH_FILE.txt"); -} - -void test_checkout_icase__overwrites_files_for_files_when_forced(void) -{ - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_write2file("testrepo/NEW.txt", "neue file\n", 10, \ - O_WRONLY | O_CREAT | O_TRUNC, 0644); - - cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); - assert_name_is("testrepo/new.txt"); -} - -void test_checkout_icase__refuses_to_overwrite_links_for_files(void) -{ - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; - - cl_must_pass(symlink_or_fake(repo, "../tmp", "testrepo/BRANCH_FILE.txt")); - - cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); - - cl_assert(!git_fs_path_exists("tmp")); - assert_name_is("testrepo/BRANCH_FILE.txt"); -} - -void test_checkout_icase__overwrites_links_for_files_when_forced(void) -{ - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_must_pass(symlink_or_fake(repo, "../tmp", "testrepo/NEW.txt")); - - cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); - - cl_assert(!git_fs_path_exists("tmp")); - assert_name_is("testrepo/new.txt"); -} - -void test_checkout_icase__overwrites_empty_folders_for_files(void) -{ - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; - - cl_must_pass(p_mkdir("testrepo/NEW.txt", 0777)); - - cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); - - assert_name_is("testrepo/new.txt"); - cl_assert(!git_fs_path_isdir("testrepo/new.txt")); -} - -void test_checkout_icase__refuses_to_overwrite_populated_folders_for_files(void) -{ - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; - - cl_must_pass(p_mkdir("testrepo/BRANCH_FILE.txt", 0777)); - cl_git_write2file("testrepo/BRANCH_FILE.txt/foobar", "neue file\n", 10, \ - O_WRONLY | O_CREAT | O_TRUNC, 0644); - - cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); - - assert_name_is("testrepo/BRANCH_FILE.txt"); - cl_assert(git_fs_path_isdir("testrepo/BRANCH_FILE.txt")); -} - -void test_checkout_icase__overwrites_folders_for_files_when_forced(void) -{ - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_must_pass(p_mkdir("testrepo/NEW.txt", 0777)); - cl_git_write2file("testrepo/NEW.txt/foobar", "neue file\n", 10, \ - O_WRONLY | O_CREAT | O_TRUNC, 0644); - - cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); - - assert_name_is("testrepo/new.txt"); - cl_assert(!git_fs_path_isdir("testrepo/new.txt")); -} - -void test_checkout_icase__refuses_to_overwrite_files_for_folders(void) -{ - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; - - cl_git_write2file("testrepo/A", "neue file\n", 10, \ - O_WRONLY | O_CREAT | O_TRUNC, 0644); - - cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); - assert_name_is("testrepo/A"); - cl_assert(!git_fs_path_isdir("testrepo/A")); -} - -void test_checkout_icase__overwrites_files_for_folders_when_forced(void) -{ - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_write2file("testrepo/A", "neue file\n", 10, \ - O_WRONLY | O_CREAT | O_TRUNC, 0644); - - cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); - assert_name_is("testrepo/a"); - cl_assert(git_fs_path_isdir("testrepo/a")); -} - -void test_checkout_icase__refuses_to_overwrite_links_for_folders(void) -{ - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; - - cl_must_pass(symlink_or_fake(repo, "..", "testrepo/A")); - - cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); - - cl_assert(!git_fs_path_exists("b.txt")); - assert_name_is("testrepo/A"); -} - -void test_checkout_icase__overwrites_links_for_folders_when_forced(void) -{ - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_must_pass(symlink_or_fake(repo, "..", "testrepo/A")); - - cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); - - cl_assert(!git_fs_path_exists("b.txt")); - assert_name_is("testrepo/a"); -} - -void test_checkout_icase__ignores_unstaged_casechange(void) -{ - git_reference *orig_ref, *br2_ref; - git_commit *orig, *br2; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_reference_lookup_resolved(&orig_ref, repo, "HEAD", 100)); - cl_git_pass(git_commit_lookup(&orig, repo, git_reference_target(orig_ref))); - cl_git_pass(git_reset(repo, (git_object *)orig, GIT_RESET_HARD, NULL)); - - cl_rename("testrepo/branch_file.txt", "testrepo/Branch_File.txt"); - - cl_git_pass(git_reference_lookup_resolved(&br2_ref, repo, "refs/heads/br2", 100)); - cl_git_pass(git_commit_lookup(&br2, repo, git_reference_target(br2_ref))); - - cl_git_pass(git_checkout_tree(repo, (const git_object *)br2, &checkout_opts)); - - git_commit_free(orig); - git_commit_free(br2); - git_reference_free(orig_ref); - git_reference_free(br2_ref); -} - -void test_checkout_icase__conflicts_with_casechanged_subtrees(void) -{ - git_reference *orig_ref; - git_object *orig, *subtrees; - git_oid oid; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_reference_lookup_resolved(&orig_ref, repo, "HEAD", 100)); - cl_git_pass(git_object_lookup(&orig, repo, git_reference_target(orig_ref), GIT_OBJECT_COMMIT)); - cl_git_pass(git_reset(repo, (git_object *)orig, GIT_RESET_HARD, NULL)); - - cl_must_pass(p_mkdir("testrepo/AB", 0777)); - cl_must_pass(p_mkdir("testrepo/AB/C", 0777)); - cl_git_write2file("testrepo/AB/C/3.txt", "Foobar!\n", 8, O_RDWR|O_CREAT, 0666); - - cl_git_pass(git_reference_name_to_id(&oid, repo, "refs/heads/subtrees")); - cl_git_pass(git_object_lookup(&subtrees, repo, &oid, GIT_OBJECT_ANY)); - - cl_git_fail(git_checkout_tree(repo, subtrees, &checkout_opts)); - - git_object_free(orig); - git_object_free(subtrees); - git_reference_free(orig_ref); -} - diff --git a/tests/checkout/index.c b/tests/checkout/index.c deleted file mode 100644 index 6a80d22c5..000000000 --- a/tests/checkout/index.c +++ /dev/null @@ -1,881 +0,0 @@ -#include "clar_libgit2.h" -#include "checkout_helpers.h" - -#include "git2/checkout.h" -#include "futils.h" -#include "repository.h" -#include "remote.h" -#include "repo/repo_helpers.h" - -static git_repository *g_repo; -static git_str g_global_path = GIT_STR_INIT; - -void test_checkout_index__initialize(void) -{ - git_tree *tree; - - g_repo = cl_git_sandbox_init("testrepo"); - - cl_git_pass(git_repository_head_tree(&tree, g_repo)); - - reset_index_to_treeish((git_object *)tree); - git_tree_free(tree); - - cl_git_rewritefile( - "./testrepo/.gitattributes", - "* text eol=lf\n"); - - git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, - &g_global_path); -} - -void test_checkout_index__cleanup(void) -{ - git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, - g_global_path.ptr); - git_str_dispose(&g_global_path); - - cl_git_sandbox_cleanup(); - - /* try to remove directories created by tests */ - cl_fixture_cleanup("alternative"); - cl_fixture_cleanup("symlink"); - cl_fixture_cleanup("symlink.git"); - cl_fixture_cleanup("tmp_global_path"); -} - -void test_checkout_index__cannot_checkout_a_bare_repository(void) -{ - cl_git_sandbox_cleanup(); - g_repo = cl_git_sandbox_init("testrepo.git"); - - cl_git_fail(git_checkout_index(g_repo, NULL, NULL)); -} - -void test_checkout_index__can_create_missing_files(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/README")); - cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/branch_file.txt")); - cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/new.txt")); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - check_file_contents("./testrepo/README", "hey there\n"); - check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); - check_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_index__can_remove_untracked_files(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - git_futils_mkdir("./testrepo/dir/subdir/subsubdir", 0755, GIT_MKDIR_PATH); - cl_git_mkfile("./testrepo/dir/one", "one\n"); - cl_git_mkfile("./testrepo/dir/subdir/two", "two\n"); - - cl_assert_equal_i(true, git_fs_path_isdir("./testrepo/dir/subdir/subsubdir")); - - opts.checkout_strategy = - GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_RECREATE_MISSING | - GIT_CHECKOUT_REMOVE_UNTRACKED; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/dir")); -} - -void test_checkout_index__can_disable_pathspec_match(void) -{ - char *files_to_checkout[] = { "test10.txt", "test11.txt"}; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_object *objects; - git_index *index; - - /* reset to beginning of history (i.e. just a README file) */ - opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; - - cl_git_pass(git_revparse_single(&objects, g_repo, "8496071c1b46c854b31185ea97743be6a8774479")); - cl_git_pass(git_checkout_tree(g_repo, objects, &opts)); - cl_git_pass(git_repository_set_head_detached(g_repo, git_object_id(objects))); - git_object_free(objects); - - cl_git_pass(git_repository_index(&index, g_repo)); - - /* We create 4 files and commit them */ - cl_git_mkfile("testrepo/test9.txt", "original\n"); - cl_git_mkfile("testrepo/test10.txt", "original\n"); - cl_git_mkfile("testrepo/test11.txt", "original\n"); - cl_git_mkfile("testrepo/test12.txt", "original\n"); - - cl_git_pass(git_index_add_bypath(index, "test9.txt")); - cl_git_pass(git_index_add_bypath(index, "test10.txt")); - cl_git_pass(git_index_add_bypath(index, "test11.txt")); - cl_git_pass(git_index_add_bypath(index, "test12.txt")); - cl_git_pass(git_index_write(index)); - - cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "commit our test files"); - - /* We modify the content of all 4 of our files */ - cl_git_rewritefile("testrepo/test9.txt", "modified\n"); - cl_git_rewritefile("testrepo/test10.txt", "modified\n"); - cl_git_rewritefile("testrepo/test11.txt", "modified\n"); - cl_git_rewritefile("testrepo/test12.txt", "modified\n"); - - /* We checkout only test10.txt and test11.txt */ - opts.checkout_strategy = - GIT_CHECKOUT_FORCE | - GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; - opts.paths.strings = files_to_checkout; - opts.paths.count = ARRAY_SIZE(files_to_checkout); - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - /* The only files that have been reverted to their original content - should be test10.txt and test11.txt */ - check_file_contents("testrepo/test9.txt", "modified\n"); - check_file_contents("testrepo/test10.txt", "original\n"); - check_file_contents("testrepo/test11.txt", "original\n"); - check_file_contents("testrepo/test12.txt", "modified\n"); - - git_index_free(index); -} - -void test_checkout_index__honor_the_specified_pathspecs(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - char *entries[] = { "*.txt" }; - - opts.paths.strings = entries; - opts.paths.count = 1; - - cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/README")); - cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/branch_file.txt")); - cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/new.txt")); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/README")); - check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); - check_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_index__honor_the_gitattributes_directives(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - const char *attributes = - "branch_file.txt text eol=crlf\n" - "new.txt text eol=lf\n"; - - cl_git_mkfile("./testrepo/.gitattributes", attributes); - cl_repo_set_bool(g_repo, "core.autocrlf", false); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - check_file_contents("./testrepo/README", "hey there\n"); - check_file_contents("./testrepo/new.txt", "my new file\n"); - check_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); -} - -void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) -{ -#ifdef GIT_WIN32 - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - const char *expected_readme_text = "hey there\r\n"; - - cl_git_pass(p_unlink("./testrepo/.gitattributes")); - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - check_file_contents("./testrepo/README", expected_readme_text); -#endif -} - -static void populate_symlink_workdir(void) -{ - git_str path = GIT_STR_INIT; - git_repository *repo; - git_remote *origin; - git_object *target; - - const char *url = git_repository_path(g_repo); - - cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), "symlink.git")); - cl_git_pass(git_repository_init(&repo, path.ptr, true)); - cl_git_pass(git_repository_set_workdir(repo, "symlink", 1)); - - /* Delete the `origin` repo (if it exists) so we can recreate it. */ - git_remote_delete(repo, GIT_REMOTE_ORIGIN); - - cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); - cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); - git_remote_free(origin); - - cl_git_pass(git_revparse_single(&target, repo, "remotes/origin/master")); - cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); - - git_object_free(target); - git_repository_free(repo); - git_str_dispose(&path); -} - -void test_checkout_index__honor_coresymlinks_default_true(void) -{ - char link_data[GIT_PATH_MAX]; - int link_size = GIT_PATH_MAX; - - cl_must_pass(p_mkdir("symlink", 0777)); - - if (!git_fs_path_supports_symlinks("symlink/test")) - cl_skip(); - -#ifdef GIT_WIN32 - /* - * Windows explicitly requires the global configuration to have - * core.symlinks=true in addition to actual filesystem support. - */ - create_tmp_global_config("tmp_global_path", "core.symlinks", "true"); -#endif - - populate_symlink_workdir(); - - link_size = p_readlink("./symlink/link_to_new.txt", link_data, link_size); - cl_assert(link_size >= 0); - - link_data[link_size] = '\0'; - cl_assert_equal_i(link_size, strlen("new.txt")); - cl_assert_equal_s(link_data, "new.txt"); - check_file_contents("./symlink/link_to_new.txt", "my new file\n"); -} - -void test_checkout_index__honor_coresymlinks_default_false(void) -{ - cl_must_pass(p_mkdir("symlink", 0777)); - -#ifndef GIT_WIN32 - /* - * This test is largely for Windows platforms to ensure that - * we respect an unset core.symlinks even when the platform - * supports symlinks. Bail entirely on POSIX platforms that - * do support symlinks. - */ - if (git_fs_path_supports_symlinks("symlink/test")) - cl_skip(); -#endif - - populate_symlink_workdir(); - check_file_contents("./symlink/link_to_new.txt", "new.txt"); -} - -void test_checkout_index__coresymlinks_set_to_true_fails_when_unsupported(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - if (git_fs_path_supports_symlinks("testrepo/test")) { - cl_skip(); - } - - cl_repo_set_bool(g_repo, "core.symlinks", true); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; - cl_git_fail(git_checkout_index(g_repo, NULL, &opts)); -} - -void test_checkout_index__honor_coresymlinks_setting_set_to_true(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - char link_data[GIT_PATH_MAX]; - size_t link_size = GIT_PATH_MAX; - - if (!git_fs_path_supports_symlinks("testrepo/test")) { - cl_skip(); - } - - cl_repo_set_bool(g_repo, "core.symlinks", true); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size); - link_data[link_size] = '\0'; - cl_assert_equal_i(link_size, strlen("new.txt")); - cl_assert_equal_s(link_data, "new.txt"); - check_file_contents("./testrepo/link_to_new.txt", "my new file\n"); -} - -void test_checkout_index__honor_coresymlinks_setting_set_to_false(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_repo_set_bool(g_repo, "core.symlinks", false); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - check_file_contents("./testrepo/link_to_new.txt", "new.txt"); -} - -void test_checkout_index__donot_overwrite_modified_file_by_default(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - - /* set this up to not return an error code on conflicts, but it - * still will not have permission to overwrite anything... - */ - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - check_file_contents("./testrepo/new.txt", "This isn't what's stored!"); -} - -void test_checkout_index__can_overwrite_modified_file(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - check_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_index__options_disable_filters(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; - opts.disable_filters = false; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - check_file_contents("./testrepo/new.txt", "my new file\r\n"); - - p_unlink("./testrepo/new.txt"); - - opts.disable_filters = true; - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - check_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_index__options_dir_modes(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - struct stat st; - git_oid oid; - git_commit *commit; - mode_t um; - - if (!cl_is_chmod_supported()) - return; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); - - reset_index_to_treeish((git_object *)commit); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; - opts.dir_mode = 0701; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - /* umask will influence actual directory creation mode */ - (void)p_umask(um = p_umask(022)); - - cl_git_pass(p_stat("./testrepo/a", &st)); - /* Haiku & Hurd use other mode bits, so we must mask them out */ - cl_assert_equal_i_fmt(st.st_mode & (S_IFMT | 07777), (GIT_FILEMODE_TREE | 0701) & ~um, "%07o"); - - /* File-mode test, since we're on the 'dir' branch */ - cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); - cl_assert_equal_i_fmt(st.st_mode & (S_IFMT | 07777), GIT_FILEMODE_BLOB_EXECUTABLE & ~um, "%07o"); - - git_commit_free(commit); -} - -void test_checkout_index__options_override_file_modes(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - struct stat st; - - if (!cl_is_chmod_supported()) - return; - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; - opts.file_mode = 0700; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - cl_git_pass(p_stat("./testrepo/new.txt", &st)); - cl_assert_equal_i_fmt(st.st_mode & GIT_MODE_PERMS_MASK, 0700, "%07o"); -} - -void test_checkout_index__options_open_flags(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_git_mkfile("./testrepo/new.txt", "hi\n"); - - opts.checkout_strategy = - GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_REMOVE_EXISTING; - opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - check_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); -} - -struct notify_data { - const char *file; - const char *sha; -}; - -static int test_checkout_notify_cb( - git_checkout_notify_t why, - const char *path, - const git_diff_file *baseline, - const git_diff_file *target, - const git_diff_file *workdir, - void *payload) -{ - struct notify_data *expectations = (struct notify_data *)payload; - - GIT_UNUSED(workdir); - - cl_assert_equal_i(GIT_CHECKOUT_NOTIFY_CONFLICT, why); - cl_assert_equal_s(expectations->file, path); - cl_assert_equal_i(0, git_oid_streq(&baseline->id, expectations->sha)); - cl_assert_equal_i(0, git_oid_streq(&target->id, expectations->sha)); - - return 0; -} - -void test_checkout_index__can_notify_of_skipped_files(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - struct notify_data data; - - cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - - /* - * $ git ls-tree HEAD - * 100644 blob a8233120f6ad708f843d861ce2b7228ec4e3dec6 README - * 100644 blob 3697d64be941a53d4ae8f6a271e4e3fa56b022cc branch_file.txt - * 100644 blob a71586c1dfe8a71c6cbf6c129f404c5642ff31bd new.txt - */ - data.file = "new.txt"; - data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"; - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_RECREATE_MISSING | - GIT_CHECKOUT_ALLOW_CONFLICTS; - opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT; - opts.notify_cb = test_checkout_notify_cb; - opts.notify_payload = &data; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); -} - -static int dont_notify_cb( - git_checkout_notify_t why, - const char *path, - const git_diff_file *baseline, - const git_diff_file *target, - const git_diff_file *workdir, - void *payload) -{ - GIT_UNUSED(why); - GIT_UNUSED(path); - GIT_UNUSED(baseline); - GIT_UNUSED(target); - GIT_UNUSED(workdir); - GIT_UNUSED(payload); - - cl_assert(false); - - return 0; -} - -void test_checkout_index__wont_notify_of_expected_line_ending_changes(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_git_pass(p_unlink("./testrepo/.gitattributes")); - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - cl_git_mkfile("./testrepo/new.txt", "my new file\r\n"); - - opts.checkout_strategy = - GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_RECREATE_MISSING | - GIT_CHECKOUT_ALLOW_CONFLICTS; - opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT; - opts.notify_cb = dont_notify_cb; - opts.notify_payload = NULL; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); -} - -static void checkout_progress_counter( - const char *path, size_t cur, size_t tot, void *payload) -{ - GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); - (*(int *)payload)++; -} - -void test_checkout_index__calls_progress_callback(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - int calls = 0; - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; - opts.progress_cb = checkout_progress_counter; - opts.progress_payload = &calls; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - cl_assert(calls > 0); -} - -void test_checkout_index__can_overcome_name_clashes(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_index *index; - - cl_git_pass(git_repository_index(&index, g_repo)); - git_index_clear(index); - - cl_git_mkfile("./testrepo/path0", "content\r\n"); - cl_git_pass(p_mkdir("./testrepo/path1", 0777)); - cl_git_mkfile("./testrepo/path1/file1", "content\r\n"); - - cl_git_pass(git_index_add_bypath(index, "path0")); - cl_git_pass(git_index_add_bypath(index, "path1/file1")); - - cl_git_pass(p_unlink("./testrepo/path0")); - cl_git_pass(git_futils_rmdir_r( - "./testrepo/path1", NULL, GIT_RMDIR_REMOVE_FILES)); - - cl_git_mkfile("./testrepo/path1", "content\r\n"); - cl_git_pass(p_mkdir("./testrepo/path0", 0777)); - cl_git_mkfile("./testrepo/path0/file0", "content\r\n"); - - cl_assert(git_fs_path_isfile("./testrepo/path1")); - cl_assert(git_fs_path_isfile("./testrepo/path0/file0")); - - opts.checkout_strategy = - GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_RECREATE_MISSING | - GIT_CHECKOUT_ALLOW_CONFLICTS; - cl_git_pass(git_checkout_index(g_repo, index, &opts)); - - cl_assert(git_fs_path_isfile("./testrepo/path1")); - cl_assert(git_fs_path_isfile("./testrepo/path0/file0")); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - cl_git_pass(git_checkout_index(g_repo, index, &opts)); - - cl_assert(git_fs_path_isfile("./testrepo/path0")); - cl_assert(git_fs_path_isfile("./testrepo/path1/file1")); - - git_index_free(index); -} - -void test_checkout_index__validates_struct_version(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - const git_error *err; - - opts.version = 1024; - cl_git_fail(git_checkout_index(g_repo, NULL, &opts)); - - err = git_error_last(); - cl_assert_equal_i(err->klass, GIT_ERROR_INVALID); - - opts.version = 0; - git_error_clear(); - cl_git_fail(git_checkout_index(g_repo, NULL, &opts)); - - err = git_error_last(); - cl_assert_equal_i(err->klass, GIT_ERROR_INVALID); -} - -void test_checkout_index__can_update_prefixed_files(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/README")); - cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/branch_file.txt")); - cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/new.txt")); - - cl_git_mkfile("./testrepo/READ", "content\n"); - cl_git_mkfile("./testrepo/README.after", "content\n"); - cl_git_pass(p_mkdir("./testrepo/branch_file", 0777)); - cl_git_pass(p_mkdir("./testrepo/branch_file/contained_dir", 0777)); - cl_git_mkfile("./testrepo/branch_file/contained_file", "content\n"); - cl_git_pass(p_mkdir("./testrepo/branch_file.txt.after", 0777)); - - opts.checkout_strategy = - GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_RECREATE_MISSING | - GIT_CHECKOUT_REMOVE_UNTRACKED; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - /* remove untracked will remove the .gitattributes file before the blobs - * were created, so they will have had crlf filtering applied on Windows - */ - check_file_contents_nocr("./testrepo/README", "hey there\n"); - check_file_contents_nocr("./testrepo/branch_file.txt", "hi\nbye!\n"); - check_file_contents_nocr("./testrepo/new.txt", "my new file\n"); - - cl_assert(!git_fs_path_exists("testrepo/READ")); - cl_assert(!git_fs_path_exists("testrepo/README.after")); - cl_assert(!git_fs_path_exists("testrepo/branch_file")); - cl_assert(!git_fs_path_exists("testrepo/branch_file.txt.after")); -} - -void test_checkout_index__can_checkout_a_newly_initialized_repository(void) -{ - cl_git_sandbox_cleanup(); - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_remove_placeholders(git_repository_path(g_repo), "dummy-marker.txt"); - - cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); -} - -void test_checkout_index__issue_1397(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_git_sandbox_cleanup(); - g_repo = cl_git_sandbox_init("issue_1397"); - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - check_file_contents("./issue_1397/crlf_file.txt", "first line\r\nsecond line\r\nboth with crlf"); -} - -void test_checkout_index__target_directory(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - checkout_counts cts; - memset(&cts, 0, sizeof(cts)); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_RECREATE_MISSING; - opts.target_directory = "alternative"; - cl_assert(!git_fs_path_isdir("alternative")); - - opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; - opts.notify_cb = checkout_count_callback; - opts.notify_payload = &cts; - - /* create some files that *would* conflict if we were using the wd */ - cl_git_mkfile("testrepo/README", "I'm in the way!\n"); - cl_git_mkfile("testrepo/new.txt", "my new file\n"); - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - cl_assert_equal_i(0, cts.n_untracked); - cl_assert_equal_i(0, cts.n_ignored); - cl_assert_equal_i(4, cts.n_updates); - - check_file_contents("./alternative/README", "hey there\n"); - check_file_contents("./alternative/branch_file.txt", "hi\nbye!\n"); - check_file_contents("./alternative/new.txt", "my new file\n"); - - cl_git_pass(git_futils_rmdir_r( - "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_checkout_index__target_directory_from_bare(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_index *index; - git_object *head = NULL; - checkout_counts cts; - memset(&cts, 0, sizeof(cts)); - - cl_git_sandbox_cleanup(); - g_repo = cl_git_sandbox_init("testrepo.git"); - cl_assert(git_repository_is_bare(g_repo)); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_revparse_single(&head, g_repo, "HEAD^{tree}")); - cl_git_pass(git_index_read_tree(index, (const git_tree *)head)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_RECREATE_MISSING; - - opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; - opts.notify_cb = checkout_count_callback; - opts.notify_payload = &cts; - - /* fail to checkout a bare repo */ - cl_git_fail(git_checkout_index(g_repo, NULL, &opts)); - - opts.target_directory = "alternative"; - cl_assert(!git_fs_path_isdir("alternative")); - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - cl_assert_equal_i(0, cts.n_untracked); - cl_assert_equal_i(0, cts.n_ignored); - cl_assert_equal_i(3, cts.n_updates); - - /* files will have been filtered if needed, so strip CR */ - check_file_contents_nocr("./alternative/README", "hey there\n"); - check_file_contents_nocr("./alternative/branch_file.txt", "hi\nbye!\n"); - check_file_contents_nocr("./alternative/new.txt", "my new file\n"); - - cl_git_pass(git_futils_rmdir_r( - "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); - - git_object_free(head); -} - -void test_checkout_index__can_get_repo_from_index(void) -{ - git_index *index; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/README")); - cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/branch_file.txt")); - cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/new.txt")); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(git_checkout_index(NULL, index, &opts)); - - check_file_contents("./testrepo/README", "hey there\n"); - check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); - check_file_contents("./testrepo/new.txt", "my new file\n"); - - git_index_free(index); -} - -static void add_conflict(git_index *index, const char *path) -{ - git_index_entry entry; - - memset(&entry, 0, sizeof(git_index_entry)); - - entry.mode = 0100644; - entry.path = path; - - git_oid_fromstr(&entry.id, "d427e0b2e138501a3d15cc376077a3631e15bd46"); - GIT_INDEX_ENTRY_STAGE_SET(&entry, 1); - cl_git_pass(git_index_add(index, &entry)); - - git_oid_fromstr(&entry.id, "4e886e602529caa9ab11d71f86634bd1b6e0de10"); - GIT_INDEX_ENTRY_STAGE_SET(&entry, 2); - cl_git_pass(git_index_add(index, &entry)); - - git_oid_fromstr(&entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); - GIT_INDEX_ENTRY_STAGE_SET(&entry, 3); - cl_git_pass(git_index_add(index, &entry)); -} - -void test_checkout_index__writes_conflict_file(void) -{ - git_index *index; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_str conflicting_buf = GIT_STR_INIT; - - cl_git_pass(git_repository_index(&index, g_repo)); - - add_conflict(index, "conflicting.txt"); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, "testrepo/conflicting.txt")); - cl_assert(strcmp(conflicting_buf.ptr, - "<<<<<<< ours\n" - "this file is changed in master and branch\n" - "=======\n" - "this file is changed in branch and master\n" - ">>>>>>> theirs\n") == 0); - git_str_dispose(&conflicting_buf); - - git_index_free(index); -} - -void test_checkout_index__adding_conflict_removes_stage_0(void) -{ - git_index *new_index, *index; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_git_pass(git_index_new(&new_index)); - - add_conflict(new_index, "new.txt"); - cl_git_pass(git_checkout_index(g_repo, new_index, &opts)); - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_assert(git_index_get_bypath(index, "new.txt", 0) == NULL); - cl_assert(git_index_get_bypath(index, "new.txt", 1) != NULL); - cl_assert(git_index_get_bypath(index, "new.txt", 2) != NULL); - cl_assert(git_index_get_bypath(index, "new.txt", 3) != NULL); - - git_index_free(index); - git_index_free(new_index); -} - -void test_checkout_index__conflicts_honor_coreautocrlf(void) -{ -#ifdef GIT_WIN32 - git_index *index; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_str conflicting_buf = GIT_STR_INIT; - - cl_git_pass(p_unlink("./testrepo/.gitattributes")); - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - cl_git_pass(git_repository_index(&index, g_repo)); - - add_conflict(index, "conflicting.txt"); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, "testrepo/conflicting.txt")); - cl_assert(strcmp(conflicting_buf.ptr, - "<<<<<<< ours\r\n" - "this file is changed in master and branch\r\n" - "=======\r\n" - "this file is changed in branch and master\r\n" - ">>>>>>> theirs\r\n") == 0); - git_str_dispose(&conflicting_buf); - - git_index_free(index); -#endif -} diff --git a/tests/checkout/nasty.c b/tests/checkout/nasty.c deleted file mode 100644 index 732f1d5e8..000000000 --- a/tests/checkout/nasty.c +++ /dev/null @@ -1,386 +0,0 @@ -#include "clar_libgit2.h" -#include "checkout_helpers.h" - -#include "git2/checkout.h" -#include "repository.h" -#include "futils.h" - -static const char *repo_name = "nasty"; -static git_repository *repo; -static git_checkout_options checkout_opts; - -void test_checkout_nasty__initialize(void) -{ - repo = cl_git_sandbox_init(repo_name); - - GIT_INIT_STRUCTURE(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION); - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; -} - -void test_checkout_nasty__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void test_checkout_passes(const char *refname, const char *filename) -{ - git_oid commit_id; - git_commit *commit; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_str path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&path, repo_name, filename)); - - cl_git_pass(git_reference_name_to_id(&commit_id, repo, refname)); - cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE | - GIT_CHECKOUT_DONT_UPDATE_INDEX; - - cl_git_pass(git_checkout_tree(repo, (const git_object *)commit, &opts)); - cl_assert(!git_fs_path_exists(path.ptr)); - - git_commit_free(commit); - git_str_dispose(&path); -} - -static void test_checkout_fails(const char *refname, const char *filename) -{ - git_oid commit_id; - git_commit *commit; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_str path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&path, repo_name, filename)); - - cl_git_pass(git_reference_name_to_id(&commit_id, repo, refname)); - cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_fail(git_checkout_tree(repo, (const git_object *)commit, &opts)); - cl_assert(!git_fs_path_exists(path.ptr)); - - git_commit_free(commit); - git_str_dispose(&path); -} - -/* A tree that contains ".git" as a tree, with a blob inside - * (".git/foobar"). - */ -void test_checkout_nasty__dotgit_tree(void) -{ - test_checkout_fails("refs/heads/dotgit_tree", ".git/foobar"); -} - -/* A tree that contains ".GIT" as a tree, with a blob inside - * (".GIT/foobar"). - */ -void test_checkout_nasty__dotcapitalgit_tree(void) -{ - test_checkout_fails("refs/heads/dotcapitalgit_tree", ".GIT/foobar"); -} - -/* A tree that contains a tree ".", with a blob inside ("./foobar"). - */ -void test_checkout_nasty__dot_tree(void) -{ - test_checkout_fails("refs/heads/dot_tree", "foobar"); -} - -/* A tree that contains a tree ".", with a tree ".git", with a blob - * inside ("./.git/foobar"). - */ -void test_checkout_nasty__dot_dotgit_tree(void) -{ - test_checkout_fails("refs/heads/dot_dotgit_tree", ".git/foobar"); -} - -/* A tree that contains a tree, with a tree "..", with a tree ".git", with a - * blob inside ("foo/../.git/foobar"). - */ -void test_checkout_nasty__dotdot_dotgit_tree(void) -{ - test_checkout_fails("refs/heads/dotdot_dotgit_tree", ".git/foobar"); -} - -/* A tree that contains a tree, with a tree "..", with a blob inside - * ("foo/../foobar"). - */ -void test_checkout_nasty__dotdot_tree(void) -{ - test_checkout_fails("refs/heads/dotdot_tree", "foobar"); -} - -/* A tree that contains a blob with the rogue name ".git/foobar" */ -void test_checkout_nasty__dotgit_path(void) -{ - test_checkout_fails("refs/heads/dotgit_path", ".git/foobar"); -} - -/* A tree that contains a blob with the rogue name ".GIT/foobar" */ -void test_checkout_nasty__dotcapitalgit_path(void) -{ - test_checkout_fails("refs/heads/dotcapitalgit_path", ".GIT/foobar"); -} - -/* A tree that contains a blob with the rogue name "./.git/foobar" */ -void test_checkout_nasty__dot_dotgit_path(void) -{ - test_checkout_fails("refs/heads/dot_dotgit_path", ".git/foobar"); -} - -/* A tree that contains a blob with the rogue name "./.GIT/foobar" */ -void test_checkout_nasty__dot_dotcapitalgit_path(void) -{ - test_checkout_fails("refs/heads/dot_dotcapitalgit_path", ".GIT/foobar"); -} - -/* A tree that contains a blob with the rogue name "foo/../.git/foobar" */ -void test_checkout_nasty__dotdot_dotgit_path(void) -{ - test_checkout_fails("refs/heads/dotdot_dotgit_path", ".git/foobar"); -} - -/* A tree that contains a blob with the rogue name "foo/../.GIT/foobar" */ -void test_checkout_nasty__dotdot_dotcapitalgit_path(void) -{ - test_checkout_fails("refs/heads/dotdot_dotcapitalgit_path", ".GIT/foobar"); -} - -/* A tree that contains a blob with the rogue name "foo/." */ -void test_checkout_nasty__dot_path(void) -{ - test_checkout_fails("refs/heads/dot_path", "./foobar"); -} - -/* A tree that contains a blob with the rogue name "foo/." */ -void test_checkout_nasty__dot_path_two(void) -{ - test_checkout_fails("refs/heads/dot_path_two", "foo/."); -} - -/* A tree that contains a blob with the rogue name "foo/../foobar" */ -void test_checkout_nasty__dotdot_path(void) -{ - test_checkout_fails("refs/heads/dotdot_path", "foobar"); -} - -/* A tree that contains an entry with a backslash ".git\foobar" */ -void test_checkout_nasty__dotgit_backslash_path(void) -{ -#ifdef GIT_WIN32 - test_checkout_fails("refs/heads/dotgit_backslash_path", ".git/foobar"); -#endif -} - -/* A tree that contains an entry with a backslash ".GIT\foobar" */ -void test_checkout_nasty__dotcapitalgit_backslash_path(void) -{ -#ifdef GIT_WIN32 - test_checkout_fails("refs/heads/dotcapitalgit_backslash_path", ".GIT/foobar"); -#endif -} - -/* A tree that contains an entry with a backslash ".\.GIT\foobar" */ -void test_checkout_nasty__dot_backslash_dotcapitalgit_path(void) -{ -#ifdef GIT_WIN32 - test_checkout_fails("refs/heads/dot_backslash_dotcapitalgit_path", ".GIT/foobar"); -#endif -} - -/* A tree that contains an entry ".git.", because Win32 APIs will drop the - * trailing slash. - */ -void test_checkout_nasty__dot_git_dot(void) -{ -#ifdef GIT_WIN32 - test_checkout_fails("refs/heads/dot_git_dot", ".git/foobar"); -#endif -} - -/* A tree that contains an entry "git~1", because that is typically the - * short name for ".git". - */ -void test_checkout_nasty__git_tilde1(void) -{ - test_checkout_fails("refs/heads/git_tilde1", ".git/foobar"); - test_checkout_fails("refs/heads/git_tilde1", "git~1/foobar"); -} - -/* A tree that contains an entry "git~2", when we have forced the short - * name for ".git" into "GIT~2". - */ -void test_checkout_nasty__git_custom_shortname(void) -{ -#ifdef GIT_WIN32 - if (!cl_sandbox_supports_8dot3()) - clar__skip(); - - cl_must_pass(p_rename("nasty/.git", "nasty/_temp")); - cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666); - cl_must_pass(p_rename("nasty/_temp", "nasty/.git")); - test_checkout_fails("refs/heads/git_tilde2", ".git/foobar"); -#endif -} - -/* A tree that contains an entry "git~3", which should be allowed, since - * it is not the typical short name ("GIT~1") or the actual short name - * ("GIT~2") for ".git". - */ -void test_checkout_nasty__only_looks_like_a_git_shortname(void) -{ -#ifdef GIT_WIN32 - git_oid commit_id; - git_commit *commit; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_must_pass(p_rename("nasty/.git", "nasty/_temp")); - cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666); - cl_must_pass(p_rename("nasty/_temp", "nasty/.git")); - - cl_git_pass(git_reference_name_to_id(&commit_id, repo, "refs/heads/git_tilde3")); - cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_checkout_tree(repo, (const git_object *)commit, &opts)); - cl_assert(git_fs_path_exists("nasty/git~3/foobar")); - - git_commit_free(commit); -#endif -} - -/* A tree that contains an entry "git:", because Win32 APIs will reject - * that as looking too similar to a drive letter. - */ -void test_checkout_nasty__dot_git_colon(void) -{ -#ifdef GIT_WIN32 - test_checkout_fails("refs/heads/dot_git_colon", ".git/foobar"); -#endif -} - -/* A tree that contains an entry "git:foo", because Win32 APIs will turn - * that into ".git". - */ -void test_checkout_nasty__dot_git_colon_stuff(void) -{ -#ifdef GIT_WIN32 - test_checkout_fails("refs/heads/dot_git_colon_stuff", ".git/foobar"); -#endif -} - -/* A tree that contains an entry ".git::$INDEX_ALLOCATION" because NTFS - * will interpret that as a synonym to ".git", even when mounted via SMB - * on macOS. - */ -void test_checkout_nasty__dotgit_alternate_data_stream(void) -{ - test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git/dummy-file"); - test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git::$INDEX_ALLOCATION/dummy-file"); -} - -/* Trees that contains entries with a tree ".git" that contain - * byte sequences: - * { 0xe2, 0x80, 0x8c } - * { 0xe2, 0x80, 0x8d } - * { 0xe2, 0x80, 0x8e } - * { 0xe2, 0x80, 0x8f } - * { 0xe2, 0x80, 0xaa } - * { 0xe2, 0x80, 0xab } - * { 0xe2, 0x80, 0xac } - * { 0xe2, 0x80, 0xad } - * { 0xe2, 0x81, 0xae } - * { 0xe2, 0x81, 0xaa } - * { 0xe2, 0x81, 0xab } - * { 0xe2, 0x81, 0xac } - * { 0xe2, 0x81, 0xad } - * { 0xe2, 0x81, 0xae } - * { 0xe2, 0x81, 0xaf } - * { 0xef, 0xbb, 0xbf } - * Because these map to characters that HFS filesystems "ignore". Thus - * ".git" will map to ".git". - */ -void test_checkout_nasty__dot_git_hfs_ignorable(void) -{ -#ifdef __APPLE__ - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_1", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_2", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_3", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_4", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_5", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_6", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_7", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_8", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_9", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_10", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_11", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_12", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_13", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_14", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_15", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_16", ".git/foobar"); -#endif -} - -void test_checkout_nasty__honors_core_protecthfs(void) -{ - cl_repo_set_bool(repo, "core.protectHFS", true); - - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_1", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_2", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_3", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_4", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_5", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_6", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_7", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_8", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_9", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_10", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_11", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_12", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_13", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_14", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_15", ".git/foobar"); - test_checkout_fails("refs/heads/dotgit_hfs_ignorable_16", ".git/foobar"); -} - -void test_checkout_nasty__honors_core_protectntfs(void) -{ - cl_repo_set_bool(repo, "core.protectNTFS", true); - - test_checkout_fails("refs/heads/dotgit_backslash_path", ".git/foobar"); - test_checkout_fails("refs/heads/dotcapitalgit_backslash_path", ".GIT/foobar"); - test_checkout_fails("refs/heads/dot_git_dot", ".git/foobar"); - test_checkout_fails("refs/heads/git_tilde1", ".git/foobar"); -} - -void test_checkout_nasty__symlink1(void) -{ - test_checkout_passes("refs/heads/symlink1", ".git/foobar"); -} - -void test_checkout_nasty__symlink2(void) -{ - test_checkout_passes("refs/heads/symlink2", ".git/foobar"); -} - -void test_checkout_nasty__symlink3(void) -{ - test_checkout_passes("refs/heads/symlink3", ".git/foobar"); -} - -void test_checkout_nasty__gitmodules_symlink(void) -{ - cl_repo_set_bool(repo, "core.protectHFS", true); - test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules"); - cl_repo_set_bool(repo, "core.protectHFS", false); - - cl_repo_set_bool(repo, "core.protectNTFS", true); - test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules"); - cl_repo_set_bool(repo, "core.protectNTFS", false); - - test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules"); -} diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c deleted file mode 100644 index d4b57f5d1..000000000 --- a/tests/checkout/tree.c +++ /dev/null @@ -1,1685 +0,0 @@ -#include "clar_libgit2.h" -#include "checkout_helpers.h" - -#include "git2/checkout.h" -#include "repository.h" -#include "futils.h" - -static git_repository *g_repo; -static git_checkout_options g_opts; -static git_object *g_object; - -static void assert_status_entrycount(git_repository *repo, size_t count) -{ - git_status_list *status; - - cl_git_pass(git_status_list_new(&status, repo, NULL)); - cl_assert_equal_i(count, git_status_list_entrycount(status)); - - git_status_list_free(status); -} - -void test_checkout_tree__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); - - GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTIONS_VERSION); - g_opts.checkout_strategy = GIT_CHECKOUT_FORCE; -} - -void test_checkout_tree__cleanup(void) -{ - git_object_free(g_object); - g_object = NULL; - - cl_git_sandbox_cleanup(); - - if (git_fs_path_isdir("alternative")) - git_futils_rmdir_r("alternative", NULL, GIT_RMDIR_REMOVE_FILES); -} - -void test_checkout_tree__cannot_checkout_a_non_treeish(void) -{ - /* blob */ - cl_git_pass(git_revparse_single(&g_object, g_repo, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); - cl_git_fail(git_checkout_tree(g_repo, g_object, NULL)); -} - -void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) -{ - char *entries[] = { "ab/de/" }; - - g_opts.paths.strings = entries; - g_opts.paths.count = 1; - - cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); - - cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/ab/")); - - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - - cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/ab/de/2.txt")); - cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/ab/de/fgh/1.txt")); -} - -void test_checkout_tree__can_checkout_and_remove_directory(void) -{ - cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/ab/")); - - /* Checkout branch "subtrees" and update HEAD, so that HEAD matches the - * current working tree - */ - cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees")); - - cl_assert_equal_i(true, git_fs_path_isdir("./testrepo/ab/")); - cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/ab/de/2.txt")); - cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/ab/de/fgh/1.txt")); - - git_object_free(g_object); - g_object = NULL; - - /* Checkout branch "master" and update HEAD, so that HEAD matches the - * current working tree - */ - cl_git_pass(git_revparse_single(&g_object, g_repo, "master")); - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master")); - - /* This directory should no longer exist */ - cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/ab/")); -} - -void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void) -{ - char *entries[] = { "de/" }; - - g_opts.paths.strings = entries; - g_opts.paths.count = 1; - - cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees:ab")); - - cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/de/")); - - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - - cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/de/2.txt")); - cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/de/fgh/1.txt")); -} - -static void progress(const char *path, size_t cur, size_t tot, void *payload) -{ - bool *was_called = (bool*)payload; - GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); - *was_called = true; -} - -void test_checkout_tree__calls_progress_callback(void) -{ - bool was_called = 0; - - g_opts.progress_cb = progress; - g_opts.progress_payload = &was_called; - - cl_git_pass(git_revparse_single(&g_object, g_repo, "master")); - - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - - cl_assert_equal_i(was_called, true); -} - -void test_checkout_tree__doesnt_write_unrequested_files_to_worktree(void) -{ - git_oid master_oid; - git_oid chomped_oid; - git_commit* p_master_commit; - git_commit* p_chomped_commit; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - git_oid_fromstr(&master_oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - git_oid_fromstr(&chomped_oid, "e90810b8df3e80c413d903f631643c716887138d"); - cl_git_pass(git_commit_lookup(&p_master_commit, g_repo, &master_oid)); - cl_git_pass(git_commit_lookup(&p_chomped_commit, g_repo, &chomped_oid)); - - /* GIT_CHECKOUT_NONE should not add any file to the working tree from the - * index as it is supposed to be a dry run. - */ - opts.checkout_strategy = GIT_CHECKOUT_NONE; - git_checkout_tree(g_repo, (git_object*)p_chomped_commit, &opts); - cl_assert_equal_i(false, git_fs_path_isfile("testrepo/readme.txt")); - - git_commit_free(p_master_commit); - git_commit_free(p_chomped_commit); -} - -void test_checkout_tree__can_switch_branches(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid oid; - git_object *obj = NULL; - - assert_on_branch(g_repo, "master"); - - /* do first checkout with FORCE because we don't know if testrepo - * base data is clean for a checkout or not - */ - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir")); - - cl_assert(git_fs_path_isfile("testrepo/README")); - cl_assert(git_fs_path_isfile("testrepo/branch_file.txt")); - cl_assert(git_fs_path_isfile("testrepo/new.txt")); - cl_assert(git_fs_path_isfile("testrepo/a/b.txt")); - - cl_assert(!git_fs_path_isdir("testrepo/ab")); - - assert_on_branch(g_repo, "dir"); - - git_object_free(obj); - - /* do second checkout safe because we should be clean after first */ - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees")); - - cl_assert(git_fs_path_isfile("testrepo/README")); - cl_assert(git_fs_path_isfile("testrepo/branch_file.txt")); - cl_assert(git_fs_path_isfile("testrepo/new.txt")); - cl_assert(git_fs_path_isfile("testrepo/ab/4.txt")); - cl_assert(git_fs_path_isfile("testrepo/ab/c/3.txt")); - cl_assert(git_fs_path_isfile("testrepo/ab/de/2.txt")); - cl_assert(git_fs_path_isfile("testrepo/ab/de/fgh/1.txt")); - - cl_assert(!git_fs_path_isdir("testrepo/a")); - - assert_on_branch(g_repo, "subtrees"); - - git_object_free(obj); -} - -void test_checkout_tree__can_remove_untracked(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_UNTRACKED; - - cl_git_mkfile("testrepo/untracked_file", "as you wish"); - cl_assert(git_fs_path_isfile("testrepo/untracked_file")); - - cl_git_pass(git_checkout_head(g_repo, &opts)); - - cl_assert(!git_fs_path_isfile("testrepo/untracked_file")); -} - -void test_checkout_tree__can_remove_ignored(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - int ignored = 0; - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_IGNORED; - - cl_git_mkfile("testrepo/ignored_file", "as you wish"); - - cl_git_pass(git_ignore_add_rule(g_repo, "ignored_file\n")); - - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "ignored_file")); - cl_assert_equal_i(1, ignored); - - cl_assert(git_fs_path_isfile("testrepo/ignored_file")); - - cl_git_pass(git_checkout_head(g_repo, &opts)); - - cl_assert(!git_fs_path_isfile("testrepo/ignored_file")); -} - -static int checkout_tree_with_blob_ignored_in_workdir(int strategy, bool isdir) -{ - git_oid oid; - git_object *obj = NULL; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - int ignored = 0, error; - - assert_on_branch(g_repo, "master"); - - /* do first checkout with FORCE because we don't know if testrepo - * base data is clean for a checkout or not - */ - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir")); - - cl_assert(git_fs_path_isfile("testrepo/README")); - cl_assert(git_fs_path_isfile("testrepo/branch_file.txt")); - cl_assert(git_fs_path_isfile("testrepo/new.txt")); - cl_assert(git_fs_path_isfile("testrepo/a/b.txt")); - - cl_assert(!git_fs_path_isdir("testrepo/ab")); - - assert_on_branch(g_repo, "dir"); - - git_object_free(obj); - - opts.checkout_strategy = strategy; - - if (isdir) { - cl_must_pass(p_mkdir("testrepo/ab", 0777)); - cl_must_pass(p_mkdir("testrepo/ab/4.txt", 0777)); - - cl_git_mkfile("testrepo/ab/4.txt/file1.txt", "as you wish"); - cl_git_mkfile("testrepo/ab/4.txt/file2.txt", "foo bar foo"); - cl_git_mkfile("testrepo/ab/4.txt/file3.txt", "inky blinky pinky clyde"); - - cl_assert(git_fs_path_isdir("testrepo/ab/4.txt")); - } else { - cl_must_pass(p_mkdir("testrepo/ab", 0777)); - cl_git_mkfile("testrepo/ab/4.txt", "as you wish"); - - cl_assert(git_fs_path_isfile("testrepo/ab/4.txt")); - } - - cl_git_pass(git_ignore_add_rule(g_repo, "ab/4.txt\n")); - - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "ab/4.txt")); - cl_assert_equal_i(1, ignored); - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - error = git_checkout_tree(g_repo, obj, &opts); - - git_object_free(obj); - - return error; -} - -void test_checkout_tree__conflict_on_ignored_when_not_overwriting(void) -{ - int error; - - cl_git_fail(error = checkout_tree_with_blob_ignored_in_workdir( - GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, false)); - - cl_assert_equal_i(GIT_ECONFLICT, error); -} - -void test_checkout_tree__can_overwrite_ignored_by_default(void) -{ - cl_git_pass(checkout_tree_with_blob_ignored_in_workdir(GIT_CHECKOUT_SAFE, false)); - - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees")); - - cl_assert(git_fs_path_isfile("testrepo/ab/4.txt")); - - assert_on_branch(g_repo, "subtrees"); -} - -void test_checkout_tree__conflict_on_ignored_folder_when_not_overwriting(void) -{ - int error; - - cl_git_fail(error = checkout_tree_with_blob_ignored_in_workdir( - GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, true)); - - cl_assert_equal_i(GIT_ECONFLICT, error); -} - -void test_checkout_tree__can_overwrite_ignored_folder_by_default(void) -{ - cl_git_pass(checkout_tree_with_blob_ignored_in_workdir(GIT_CHECKOUT_SAFE, true)); - - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees")); - - cl_assert(git_fs_path_isfile("testrepo/ab/4.txt")); - - assert_on_branch(g_repo, "subtrees"); - -} - -void test_checkout_tree__can_update_only(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid oid; - git_object *obj = NULL; - - /* first let's get things into a known state - by checkout out the HEAD */ - - assert_on_branch(g_repo, "master"); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - cl_git_pass(git_checkout_head(g_repo, &opts)); - - cl_assert(!git_fs_path_isdir("testrepo/a")); - - check_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n"); - - /* now checkout branch but with update only */ - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir")); - - assert_on_branch(g_repo, "dir"); - - /* this normally would have been created (which was tested separately in - * the test_checkout_tree__can_switch_branches test), but with - * UPDATE_ONLY it will not have been created. - */ - cl_assert(!git_fs_path_isdir("testrepo/a")); - - /* but this file still should have been updated */ - check_file_contents_nocr("testrepo/branch_file.txt", "hi\n"); - - git_object_free(obj); -} - -void test_checkout_tree__can_checkout_with_pattern(void) -{ - char *entries[] = { "[l-z]*.txt" }; - - /* reset to beginning of history (i.e. just a README file) */ - - g_opts.checkout_strategy = - GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; - - cl_git_pass(git_revparse_single(&g_object, g_repo, "8496071c1b46c854b31185ea97743be6a8774479")); - - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - cl_git_pass( - git_repository_set_head_detached(g_repo, git_object_id(g_object))); - - git_object_free(g_object); - g_object = NULL; - - cl_assert(git_fs_path_exists("testrepo/README")); - cl_assert(!git_fs_path_exists("testrepo/branch_file.txt")); - cl_assert(!git_fs_path_exists("testrepo/link_to_new.txt")); - cl_assert(!git_fs_path_exists("testrepo/new.txt")); - - /* now to a narrow patterned checkout */ - - g_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - g_opts.paths.strings = entries; - g_opts.paths.count = 1; - - cl_git_pass(git_revparse_single(&g_object, g_repo, "refs/heads/master")); - - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - - cl_assert(git_fs_path_exists("testrepo/README")); - cl_assert(!git_fs_path_exists("testrepo/branch_file.txt")); - cl_assert(git_fs_path_exists("testrepo/link_to_new.txt")); - cl_assert(git_fs_path_exists("testrepo/new.txt")); -} - -void test_checkout_tree__pathlist_checkout_ignores_non_matches(void) -{ - char *entries[] = { "branch_file.txt", "link_to_new.txt" }; - - /* reset to beginning of history (i.e. just a README file) */ - - g_opts.checkout_strategy = - GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; - - cl_git_pass(git_revparse_single(&g_object, g_repo, "refs/heads/master")); - - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master")); - - cl_assert(git_fs_path_exists("testrepo/README")); - cl_assert(git_fs_path_exists("testrepo/branch_file.txt")); - cl_assert(git_fs_path_exists("testrepo/link_to_new.txt")); - cl_assert(git_fs_path_exists("testrepo/new.txt")); - - git_object_free(g_object); - cl_git_pass(git_revparse_single(&g_object, g_repo, "8496071c1b46c854b31185ea97743be6a8774479")); - - g_opts.checkout_strategy = - GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; - g_opts.paths.strings = entries; - g_opts.paths.count = 2; - - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - - cl_assert(git_fs_path_exists("testrepo/README")); - cl_assert(!git_fs_path_exists("testrepo/branch_file.txt")); - cl_assert(!git_fs_path_exists("testrepo/link_to_new.txt")); - cl_assert(git_fs_path_exists("testrepo/new.txt")); -} - -void test_checkout_tree__can_disable_pattern_match(void) -{ - char *entries[] = { "b*.txt" }; - - /* reset to beginning of history (i.e. just a README file) */ - - g_opts.checkout_strategy = - GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; - - cl_git_pass(git_revparse_single(&g_object, g_repo, "8496071c1b46c854b31185ea97743be6a8774479")); - - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - cl_git_pass( - git_repository_set_head_detached(g_repo, git_object_id(g_object))); - - git_object_free(g_object); - g_object = NULL; - - cl_assert(!git_fs_path_isfile("testrepo/branch_file.txt")); - - /* now to a narrow patterned checkout, but disable pattern */ - - g_opts.checkout_strategy = - GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; - g_opts.paths.strings = entries; - g_opts.paths.count = 1; - - cl_git_pass(git_revparse_single(&g_object, g_repo, "refs/heads/master")); - - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - - cl_assert(!git_fs_path_isfile("testrepo/branch_file.txt")); - - /* let's try that again, but allow the pattern match */ - - g_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - - cl_assert(git_fs_path_isfile("testrepo/branch_file.txt")); -} - -static void assert_conflict( - const char *entry_path, - const char *new_content, - const char *parent_sha, - const char *commit_sha) -{ - git_index *index; - git_object *hack_tree; - git_reference *branch, *head; - git_str file_path = GIT_STR_INIT; - - cl_git_pass(git_repository_index(&index, g_repo)); - - /* Create a branch pointing at the parent */ - cl_git_pass(git_revparse_single(&g_object, g_repo, parent_sha)); - cl_git_pass(git_branch_create(&branch, g_repo, - "potential_conflict", (git_commit *)g_object, 0)); - - /* Make HEAD point to this branch */ - cl_git_pass(git_reference_symbolic_create( - &head, g_repo, "HEAD", git_reference_name(branch), 1, NULL)); - git_reference_free(head); - git_reference_free(branch); - - /* Checkout the parent */ - g_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - - /* Hack-ishy workaround to ensure *all* the index entries - * match the content of the tree - */ - cl_git_pass(git_object_peel(&hack_tree, g_object, GIT_OBJECT_TREE)); - cl_git_pass(git_index_read_tree(index, (git_tree *)hack_tree)); - cl_git_pass(git_index_write(index)); - git_object_free(hack_tree); - git_object_free(g_object); - g_object = NULL; - - /* Create a conflicting file */ - cl_git_pass(git_str_joinpath(&file_path, "./testrepo", entry_path)); - cl_git_mkfile(git_str_cstr(&file_path), new_content); - git_str_dispose(&file_path); - - /* Trying to checkout the original commit */ - cl_git_pass(git_revparse_single(&g_object, g_repo, commit_sha)); - - g_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - cl_assert_equal_i( - GIT_ECONFLICT, git_checkout_tree(g_repo, g_object, &g_opts)); - - /* Stage the conflicting change */ - cl_git_pass(git_index_add_bypath(index, entry_path)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_assert_equal_i( - GIT_ECONFLICT, git_checkout_tree(g_repo, g_object, &g_opts)); -} - -void test_checkout_tree__checking_out_a_conflicting_type_change_returns_ECONFLICT(void) -{ - /* - * 099faba adds a symlink named 'link_to_new.txt' - * a65fedf is the parent of 099faba - */ - - assert_conflict("link_to_new.txt", "old.txt", "a65fedf", "099faba"); -} - -void test_checkout_tree__checking_out_a_conflicting_type_change_returns_ECONFLICT_2(void) -{ - /* - * cf80f8d adds a directory named 'a/' - * a4a7dce is the parent of cf80f8d - */ - - assert_conflict("a", "hello\n", "a4a7dce", "cf80f8d"); -} - -void test_checkout_tree__checking_out_a_conflicting_content_change_returns_ECONFLICT(void) -{ - /* - * c47800c adds a symlink named 'branch_file.txt' - * 5b5b025 is the parent of 763d71a - */ - - assert_conflict("branch_file.txt", "hello\n", "5b5b025", "c47800c"); -} - -void test_checkout_tree__donot_update_deleted_file_by_default(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid old_id, new_id; - git_commit *old_commit = NULL, *new_commit = NULL; - git_index *index = NULL; - checkout_counts ct; - - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - memset(&ct, 0, sizeof(ct)); - opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; - opts.notify_cb = checkout_count_callback; - opts.notify_payload = &ct; - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(git_oid_fromstr(&old_id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); - cl_git_pass(git_commit_lookup(&old_commit, g_repo, &old_id)); - cl_git_pass(git_reset(g_repo, (git_object *)old_commit, GIT_RESET_HARD, NULL)); - - cl_git_pass(p_unlink("testrepo/branch_file.txt")); - cl_git_pass(git_index_remove_bypath(index ,"branch_file.txt")); - cl_git_pass(git_index_write(index)); - - cl_assert(!git_fs_path_exists("testrepo/branch_file.txt")); - - cl_git_pass(git_oid_fromstr(&new_id, "099fabac3a9ea935598528c27f866e34089c2eff")); - cl_git_pass(git_commit_lookup(&new_commit, g_repo, &new_id)); - - - cl_git_fail(git_checkout_tree(g_repo, (git_object *)new_commit, &opts)); - - cl_assert_equal_i(1, ct.n_conflicts); - cl_assert_equal_i(1, ct.n_updates); - - git_commit_free(old_commit); - git_commit_free(new_commit); - git_index_free(index); -} - -struct checkout_cancel_at { - const char *filename; - int error; - int count; -}; - -static int checkout_cancel_cb( - git_checkout_notify_t why, - const char *path, - const git_diff_file *b, - const git_diff_file *t, - const git_diff_file *w, - void *payload) -{ - struct checkout_cancel_at *ca = payload; - - GIT_UNUSED(why); GIT_UNUSED(b); GIT_UNUSED(t); GIT_UNUSED(w); - - ca->count++; - - if (!strcmp(path, ca->filename)) - return ca->error; - - return 0; -} - -void test_checkout_tree__can_cancel_checkout_from_notify(void) -{ - struct checkout_cancel_at ca; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid oid; - git_object *obj = NULL; - - assert_on_branch(g_repo, "master"); - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - ca.filename = "new.txt"; - ca.error = -5555; - ca.count = 0; - - opts.notify_flags = GIT_CHECKOUT_NOTIFY_UPDATED; - opts.notify_cb = checkout_cancel_cb; - opts.notify_payload = &ca; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_assert(!git_fs_path_exists("testrepo/new.txt")); - - cl_git_fail_with(git_checkout_tree(g_repo, obj, &opts), -5555); - - cl_assert(!git_fs_path_exists("testrepo/new.txt")); - - /* on case-insensitive FS = a/b.txt, branch_file.txt, new.txt */ - /* on case-sensitive FS = README, then above */ - - if (git_fs_path_exists("testrepo/.git/CoNfIg")) /* case insensitive */ - cl_assert_equal_i(3, ca.count); - else - cl_assert_equal_i(4, ca.count); - - /* and again with a different stopping point and return code */ - ca.filename = "README"; - ca.error = 123; - ca.count = 0; - - cl_git_fail_with(git_checkout_tree(g_repo, obj, &opts), 123); - - cl_assert(!git_fs_path_exists("testrepo/new.txt")); - - if (git_fs_path_exists("testrepo/.git/CoNfIg")) /* case insensitive */ - cl_assert_equal_i(4, ca.count); - else - cl_assert_equal_i(1, ca.count); - - git_object_free(obj); -} - -void test_checkout_tree__can_checkout_with_last_workdir_item_missing(void) -{ - git_index *index = NULL; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid tree_id, commit_id; - git_tree *tree = NULL; - git_commit *commit = NULL; - - git_repository_index(&index, g_repo); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_reference_name_to_id(&commit_id, g_repo, "refs/heads/master")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id)); - - cl_git_pass(git_checkout_tree(g_repo, (git_object *)commit, &opts)); - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master")); - - cl_git_pass(p_mkdir("./testrepo/this-is-dir", 0777)); - cl_git_mkfile("./testrepo/this-is-dir/contained_file", "content\n"); - - cl_git_pass(git_index_add_bypath(index, "this-is-dir/contained_file")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_index_write_tree(&tree_id, index)); - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - - cl_git_pass(p_unlink("./testrepo/this-is-dir/contained_file")); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - opts.checkout_strategy = 1; - git_checkout_tree(g_repo, (git_object *)tree, &opts); - - git_tree_free(tree); - git_commit_free(commit); - git_index_free(index); -} - -void test_checkout_tree__issue_1397(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - const char *partial_oid = "8a7ef04"; - git_object *tree = NULL; - - test_checkout_tree__cleanup(); /* cleanup default checkout */ - - g_repo = cl_git_sandbox_init("issue_1397"); - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - cl_git_pass(git_revparse_single(&tree, g_repo, partial_oid)); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_checkout_tree(g_repo, tree, &opts)); - - check_file_contents("./issue_1397/crlf_file.txt", "first line\r\nsecond line\r\nboth with crlf"); - - git_object_free(tree); -} - -void test_checkout_tree__can_write_to_empty_dirs(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid oid; - git_object *obj = NULL; - - assert_on_branch(g_repo, "master"); - - cl_git_pass(p_mkdir("testrepo/a", 0777)); - - /* do first checkout with FORCE because we don't know if testrepo - * base data is clean for a checkout or not - */ - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - - cl_assert(git_fs_path_isfile("testrepo/a/b.txt")); - - git_object_free(obj); -} - -void test_checkout_tree__fails_when_dir_in_use(void) -{ -#ifdef GIT_WIN32 - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid oid; - git_object *obj = NULL; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - - cl_assert(git_fs_path_isfile("testrepo/a/b.txt")); - - git_object_free(obj); - - cl_git_pass(p_chdir("testrepo/a")); - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/master")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_fail(git_checkout_tree(g_repo, obj, &opts)); - - cl_git_pass(p_chdir("../..")); - - cl_assert(git_fs_path_is_empty_dir("testrepo/a")); - - git_object_free(obj); -#endif -} - -void test_checkout_tree__can_continue_when_dir_in_use(void) -{ -#ifdef GIT_WIN32 - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid oid; - git_object *obj = NULL; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE | - GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - - cl_assert(git_fs_path_isfile("testrepo/a/b.txt")); - - git_object_free(obj); - - cl_git_pass(p_chdir("testrepo/a")); - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/master")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - - cl_git_pass(p_chdir("../..")); - - cl_assert(git_fs_path_is_empty_dir("testrepo/a")); - - git_object_free(obj); -#endif -} - -void test_checkout_tree__target_directory_from_bare(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid oid; - checkout_counts cts; - memset(&cts, 0, sizeof(cts)); - - test_checkout_tree__cleanup(); /* cleanup default checkout */ - - g_repo = cl_git_sandbox_init("testrepo.git"); - cl_assert(git_repository_is_bare(g_repo)); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | - GIT_CHECKOUT_RECREATE_MISSING; - - opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; - opts.notify_cb = checkout_count_callback; - opts.notify_payload = &cts; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD")); - cl_git_pass(git_object_lookup(&g_object, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_fail(git_checkout_tree(g_repo, g_object, &opts)); - - opts.target_directory = "alternative"; - cl_assert(!git_fs_path_isdir("alternative")); - - cl_git_pass(git_checkout_tree(g_repo, g_object, &opts)); - - cl_assert_equal_i(0, cts.n_untracked); - cl_assert_equal_i(0, cts.n_ignored); - cl_assert_equal_i(3, cts.n_updates); - - check_file_contents_nocr("./alternative/README", "hey there\n"); - check_file_contents_nocr("./alternative/branch_file.txt", "hi\nbye!\n"); - check_file_contents_nocr("./alternative/new.txt", "my new file\n"); - - cl_git_pass(git_futils_rmdir_r( - "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_checkout_tree__extremely_long_file_name(void) -{ - /* A utf-8 string with 83 characters, but 249 bytes. */ - const char *longname = "\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97"; - char path[1024] = {0}; - - g_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - cl_git_pass(git_revparse_single(&g_object, g_repo, "long-file-name")); - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - - sprintf(path, "testrepo/%s.txt", longname); - cl_assert(git_fs_path_exists(path)); - - git_object_free(g_object); - cl_git_pass(git_revparse_single(&g_object, g_repo, "master")); - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); - cl_assert(!git_fs_path_exists(path)); -} - -static void create_conflict(const char *path) -{ - git_index *index; - git_index_entry entry; - - cl_git_pass(git_repository_index(&index, g_repo)); - - memset(&entry, 0x0, sizeof(git_index_entry)); - entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&entry, 1); - git_oid_fromstr(&entry.id, "d427e0b2e138501a3d15cc376077a3631e15bd46"); - entry.path = path; - cl_git_pass(git_index_add(index, &entry)); - - GIT_INDEX_ENTRY_STAGE_SET(&entry, 2); - git_oid_fromstr(&entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); - cl_git_pass(git_index_add(index, &entry)); - - GIT_INDEX_ENTRY_STAGE_SET(&entry, 3); - git_oid_fromstr(&entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); - cl_git_pass(git_index_add(index, &entry)); - - cl_git_pass(git_index_write(index)); - git_index_free(index); -} - -void test_checkout_tree__fails_when_conflicts_exist_in_index(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid oid; - git_object *obj = NULL; - - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - create_conflict("conflicts.txt"); - - cl_git_fail(git_checkout_tree(g_repo, obj, &opts)); - - git_object_free(obj); -} - -void test_checkout_tree__filemode_preserved_in_index(void) -{ - git_oid executable_oid; - git_commit *commit; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_index *index; - const git_index_entry *entry; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_repository_index(&index, g_repo)); - - /* test a freshly added executable */ - cl_git_pass(git_oid_fromstr(&executable_oid, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); - - cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - cl_assert(entry = git_index_get_bypath(index, "executable.txt", 0)); - cl_assert(GIT_PERMS_IS_EXEC(entry->mode)); - - git_commit_free(commit); - - - /* Now start with a commit which has a text file */ - cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); - - cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0)); - cl_assert(!GIT_PERMS_IS_EXEC(entry->mode)); - - git_commit_free(commit); - - - /* And then check out to a commit which converts the text file to an executable */ - cl_git_pass(git_oid_fromstr(&executable_oid, "144344043ba4d4a405da03de3844aa829ae8be0e")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); - - cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0)); - cl_assert(GIT_PERMS_IS_EXEC(entry->mode)); - - git_commit_free(commit); - - - /* Finally, check out the text file again and check that the exec bit is cleared */ - cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); - - cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0)); - cl_assert(!GIT_PERMS_IS_EXEC(entry->mode)); - - git_commit_free(commit); - - - git_index_free(index); -} - -#ifndef GIT_WIN32 -static mode_t read_filemode(const char *path) -{ - git_str fullpath = GIT_STR_INIT; - struct stat st; - mode_t result; - - git_str_joinpath(&fullpath, "testrepo", path); - cl_must_pass(p_stat(fullpath.ptr, &st)); - - result = GIT_PERMS_IS_EXEC(st.st_mode) ? - GIT_FILEMODE_BLOB_EXECUTABLE : GIT_FILEMODE_BLOB; - - git_str_dispose(&fullpath); - - return result; -} -#endif - -void test_checkout_tree__filemode_preserved_in_workdir(void) -{ -#ifndef GIT_WIN32 - git_oid executable_oid; - git_commit *commit; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - /* test a freshly added executable */ - cl_git_pass(git_oid_fromstr(&executable_oid, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); - - cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - cl_assert(GIT_PERMS_IS_EXEC(read_filemode("executable.txt"))); - - git_commit_free(commit); - - - /* Now start with a commit which has a text file */ - cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); - - cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - cl_assert(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); - - git_commit_free(commit); - - - /* And then check out to a commit which converts the text file to an executable */ - cl_git_pass(git_oid_fromstr(&executable_oid, "144344043ba4d4a405da03de3844aa829ae8be0e")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); - - cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - cl_assert(GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); - - git_commit_free(commit); - - - /* Finally, check out the text file again and check that the exec bit is cleared */ - cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); - - cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - cl_assert(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); - - git_commit_free(commit); -#else - cl_skip(); -#endif -} - -void test_checkout_tree__removes_conflicts(void) -{ - git_oid commit_id; - git_commit *commit; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_index *index; - - cl_git_pass(git_oid_fromstr(&commit_id, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id)); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_remove(index, "executable.txt", 0)); - - create_conflict("executable.txt"); - cl_git_mkfile("testrepo/executable.txt", "This is the conflict file.\n"); - - create_conflict("other.txt"); - cl_git_mkfile("testrepo/other.txt", "This is another conflict file.\n"); - - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - - cl_assert_equal_p(NULL, git_index_get_bypath(index, "executable.txt", 1)); - cl_assert_equal_p(NULL, git_index_get_bypath(index, "executable.txt", 2)); - cl_assert_equal_p(NULL, git_index_get_bypath(index, "executable.txt", 3)); - - cl_assert_equal_p(NULL, git_index_get_bypath(index, "other.txt", 1)); - cl_assert_equal_p(NULL, git_index_get_bypath(index, "other.txt", 2)); - cl_assert_equal_p(NULL, git_index_get_bypath(index, "other.txt", 3)); - - cl_assert(!git_fs_path_exists("testrepo/other.txt")); - - git_commit_free(commit); - git_index_free(index); -} - - -void test_checkout_tree__removes_conflicts_only_by_pathscope(void) -{ - git_oid commit_id; - git_commit *commit; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_index *index; - const char *path = "executable.txt"; - - cl_git_pass(git_oid_fromstr(&commit_id, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id)); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - opts.paths.count = 1; - opts.paths.strings = (char **)&path; - - cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_remove(index, "executable.txt", 0)); - - create_conflict("executable.txt"); - cl_git_mkfile("testrepo/executable.txt", "This is the conflict file.\n"); - - create_conflict("other.txt"); - cl_git_mkfile("testrepo/other.txt", "This is another conflict file.\n"); - - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - - cl_assert_equal_p(NULL, git_index_get_bypath(index, "executable.txt", 1)); - cl_assert_equal_p(NULL, git_index_get_bypath(index, "executable.txt", 2)); - cl_assert_equal_p(NULL, git_index_get_bypath(index, "executable.txt", 3)); - - cl_assert(git_index_get_bypath(index, "other.txt", 1) != NULL); - cl_assert(git_index_get_bypath(index, "other.txt", 2) != NULL); - cl_assert(git_index_get_bypath(index, "other.txt", 3) != NULL); - - cl_assert(git_fs_path_exists("testrepo/other.txt")); - - git_commit_free(commit); - git_index_free(index); -} - -void test_checkout_tree__case_changing_rename(void) -{ - git_index *index; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid master_id, dir_commit_id, tree_id, commit_id; - git_commit *master_commit, *dir_commit; - git_tree *tree; - git_signature *signature; - const git_index_entry *index_entry; - bool case_sensitive; - - assert_on_branch(g_repo, "master"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - /* Switch branches and perform a case-changing rename */ - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_reference_name_to_id(&dir_commit_id, g_repo, "refs/heads/dir")); - cl_git_pass(git_commit_lookup(&dir_commit, g_repo, &dir_commit_id)); - - cl_git_pass(git_checkout_tree(g_repo, (git_object *)dir_commit, &opts)); - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir")); - - cl_assert(git_fs_path_isfile("testrepo/README")); - case_sensitive = !git_fs_path_isfile("testrepo/readme"); - - cl_assert(index_entry = git_index_get_bypath(index, "README", 0)); - cl_assert_equal_s("README", index_entry->path); - - cl_git_pass(git_index_remove_bypath(index, "README")); - cl_git_pass(p_rename("testrepo/README", "testrepo/__readme__")); - cl_git_pass(p_rename("testrepo/__readme__", "testrepo/readme")); - cl_git_append2file("testrepo/readme", "An addendum..."); - cl_git_pass(git_index_add_bypath(index, "readme")); - - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_index_write_tree(&tree_id, index)); - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - - cl_git_pass(git_signature_new(&signature, "Renamer", "rename@contoso.com", time(NULL), 0)); - - cl_git_pass(git_commit_create(&commit_id, g_repo, "refs/heads/dir", signature, signature, NULL, "case-changing rename", tree, 1, (const git_commit **)&dir_commit)); - - cl_assert(git_fs_path_isfile("testrepo/readme")); - if (case_sensitive) - cl_assert(!git_fs_path_isfile("testrepo/README")); - - cl_assert(index_entry = git_index_get_bypath(index, "readme", 0)); - cl_assert_equal_s("readme", index_entry->path); - - /* Switching back to master should rename readme -> README */ - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_reference_name_to_id(&master_id, g_repo, "refs/heads/master")); - cl_git_pass(git_commit_lookup(&master_commit, g_repo, &master_id)); - - cl_git_pass(git_checkout_tree(g_repo, (git_object *)master_commit, &opts)); - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master")); - - assert_on_branch(g_repo, "master"); - - cl_assert(git_fs_path_isfile("testrepo/README")); - if (case_sensitive) - cl_assert(!git_fs_path_isfile("testrepo/readme")); - - cl_assert(index_entry = git_index_get_bypath(index, "README", 0)); - cl_assert_equal_s("README", index_entry->path); - - git_index_free(index); - git_signature_free(signature); - git_tree_free(tree); - git_commit_free(dir_commit); - git_commit_free(master_commit); -} - -static void perfdata_cb(const git_checkout_perfdata *in, void *payload) -{ - memcpy(payload, in, sizeof(git_checkout_perfdata)); -} - -void test_checkout_tree__can_collect_perfdata(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid oid; - git_object *obj = NULL; - git_checkout_perfdata perfdata = {0}; - - opts.perfdata_cb = perfdata_cb; - opts.perfdata_payload = &perfdata; - - assert_on_branch(g_repo, "master"); - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - - cl_assert(perfdata.mkdir_calls > 0); - cl_assert(perfdata.stat_calls > 0); - - git_object_free(obj); -} - -static void update_attr_callback( - const char *path, - size_t completed_steps, - size_t total_steps, - void *payload) -{ - GIT_UNUSED(completed_steps); - GIT_UNUSED(total_steps); - GIT_UNUSED(payload); - - if (path && strcmp(path, "ident1.txt") == 0) - cl_git_write2file("testrepo/.gitattributes", - "*.txt ident\n", 12, O_RDWR|O_CREAT, 0666); -} - -void test_checkout_tree__caches_attributes_during_checkout(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid oid; - git_object *obj = NULL; - git_str ident1 = GIT_STR_INIT, ident2 = GIT_STR_INIT; - char *ident_paths[] = { "ident1.txt", "ident2.txt" }; - - opts.progress_cb = update_attr_callback; - - assert_on_branch(g_repo, "master"); - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - opts.paths.strings = ident_paths; - opts.paths.count = 2; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/ident")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - - cl_git_pass(git_futils_readbuffer(&ident1, "testrepo/ident1.txt")); - cl_git_pass(git_futils_readbuffer(&ident2, "testrepo/ident2.txt")); - - cl_assert_equal_strn(ident1.ptr, "# $Id$", 6); - cl_assert_equal_strn(ident2.ptr, "# $Id$", 6); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - - cl_git_pass(git_futils_readbuffer(&ident1, "testrepo/ident1.txt")); - cl_git_pass(git_futils_readbuffer(&ident2, "testrepo/ident2.txt")); - - cl_assert_equal_strn(ident1.ptr, "# $Id: ", 7); - cl_assert_equal_strn(ident2.ptr, "# $Id: ", 7); - - git_str_dispose(&ident1); - git_str_dispose(&ident2); - git_object_free(obj); -} - -void test_checkout_tree__can_not_update_index(void) -{ - git_oid oid; - git_object *head; - unsigned int status; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_index *index; - - opts.checkout_strategy |= - GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_UPDATE_INDEX; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD")); - cl_git_pass(git_object_lookup(&head, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_reset(g_repo, head, GIT_RESET_HARD, &g_opts)); - - cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/ab/")); - - cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); - - cl_git_pass(git_checkout_tree(g_repo, g_object, &opts)); - - cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/ab/de/2.txt")); - cl_git_pass(git_status_file(&status, g_repo, "ab/de/2.txt")); - cl_assert_equal_i(GIT_STATUS_WT_NEW, status); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_status_file(&status, g_repo, "ab/de/2.txt")); - cl_assert_equal_i(GIT_STATUS_WT_NEW, status); - - git_object_free(head); - git_index_free(index); -} - -void test_checkout_tree__can_update_but_not_write_index(void) -{ - git_oid oid; - git_object *head; - unsigned int status; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_index *index; - git_repository *other; - - opts.checkout_strategy |= - GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_WRITE_INDEX; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD")); - cl_git_pass(git_object_lookup(&head, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_reset(g_repo, head, GIT_RESET_HARD, &g_opts)); - - cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/ab/")); - - cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); - - cl_git_pass(git_checkout_tree(g_repo, g_object, &opts)); - - cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/ab/de/2.txt")); - cl_git_pass(git_status_file(&status, g_repo, "ab/de/2.txt")); - cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status); - - cl_git_pass(git_repository_open(&other, "testrepo")); - cl_git_pass(git_status_file(&status, other, "ab/de/2.txt")); - cl_assert_equal_i(GIT_STATUS_WT_NEW, status); - git_repository_free(other); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_repository_open(&other, "testrepo")); - cl_git_pass(git_status_file(&status, other, "ab/de/2.txt")); - cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status); - git_repository_free(other); - - git_object_free(head); - git_index_free(index); -} - -/* Emulate checking out in a repo created by clone --no-checkout, - * which would not have written an index. */ -void test_checkout_tree__safe_proceeds_if_no_index(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid oid; - git_object *obj = NULL; - - assert_on_branch(g_repo, "master"); - cl_must_pass(p_unlink("testrepo/.git/index")); - - /* do second checkout safe because we should be clean after first */ - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees")); - - cl_assert(git_fs_path_isfile("testrepo/README")); - cl_assert(git_fs_path_isfile("testrepo/branch_file.txt")); - cl_assert(git_fs_path_isfile("testrepo/new.txt")); - cl_assert(git_fs_path_isfile("testrepo/ab/4.txt")); - cl_assert(git_fs_path_isfile("testrepo/ab/c/3.txt")); - cl_assert(git_fs_path_isfile("testrepo/ab/de/2.txt")); - cl_assert(git_fs_path_isfile("testrepo/ab/de/fgh/1.txt")); - - cl_assert(!git_fs_path_isdir("testrepo/a")); - - assert_on_branch(g_repo, "subtrees"); - - git_object_free(obj); -} - -static int checkout_conflict_count_cb( - git_checkout_notify_t why, - const char *path, - const git_diff_file *b, - const git_diff_file *t, - const git_diff_file *w, - void *payload) -{ - size_t *n = payload; - - GIT_UNUSED(why); - GIT_UNUSED(path); - GIT_UNUSED(b); - GIT_UNUSED(t); - GIT_UNUSED(w); - - (*n)++; - - return 0; -} - -/* A repo that has a HEAD (even a properly born HEAD that peels to - * a commit) but no index should be treated as if it's an empty baseline - */ -void test_checkout_tree__baseline_is_empty_when_no_index(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_reference *head; - git_object *obj; - size_t conflicts = 0; - - assert_on_branch(g_repo, "master"); - - cl_git_pass(git_repository_head(&head, g_repo)); - cl_git_pass(git_reference_peel(&obj, head, GIT_OBJECT_COMMIT)); - - cl_git_pass(git_reset(g_repo, obj, GIT_RESET_HARD, NULL)); - - cl_must_pass(p_unlink("testrepo/.git/index")); - - /* for a safe checkout, we should have checkout conflicts with - * the existing untracked files. - */ - opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; - opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT; - opts.notify_cb = checkout_conflict_count_cb; - opts.notify_payload = &conflicts; - - cl_git_fail_with(GIT_ECONFLICT, git_checkout_tree(g_repo, obj, &opts)); - cl_assert_equal_i(4, conflicts); - - /* but force should succeed and update the index */ - opts.checkout_strategy |= GIT_CHECKOUT_FORCE; - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - - assert_status_entrycount(g_repo, 0); - - git_object_free(obj); - git_reference_free(head); -} - -void test_checkout_tree__mode_change_is_force_updated(void) -{ - git_index *index; - git_reference *head; - git_object *obj; - - if (!cl_is_chmod_supported()) - clar__skip(); - - assert_on_branch(g_repo, "master"); - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_repository_head(&head, g_repo)); - cl_git_pass(git_reference_peel(&obj, head, GIT_OBJECT_COMMIT)); - - cl_git_pass(git_reset(g_repo, obj, GIT_RESET_HARD, NULL)); - assert_status_entrycount(g_repo, 0); - - /* update the mode on-disk */ - cl_must_pass(p_chmod("testrepo/README", 0755)); - - assert_status_entrycount(g_repo, 1); - cl_git_pass(git_checkout_tree(g_repo, obj, &g_opts)); - assert_status_entrycount(g_repo, 0); - - /* update the mode on-disk and in the index */ - cl_must_pass(p_chmod("testrepo/README", 0755)); - cl_must_pass(git_index_add_bypath(index, "README")); - - cl_git_pass(git_index_write(index)); - assert_status_entrycount(g_repo, 1); - - cl_git_pass(git_checkout_tree(g_repo, obj, &g_opts)); - cl_git_pass(git_index_write(index)); - - assert_status_entrycount(g_repo, 0); - - git_object_free(obj); - git_reference_free(head); - git_index_free(index); -} - -void test_checkout_tree__nullopts(void) -{ - cl_git_pass(git_checkout_tree(g_repo, NULL, NULL)); -} - -static void modify_index_ondisk(void) -{ - git_repository *other_repo; - git_index *other_index; - git_index_entry entry = {{0}}; - - cl_git_pass(git_repository_open(&other_repo, git_repository_workdir(g_repo))); - cl_git_pass(git_repository_index(&other_index, other_repo)); - - cl_git_pass(git_oid_fromstr(&entry.id, "1385f264afb75a56a5bec74243be9b367ba4ca08")); - entry.mode = 0100644; - entry.path = "README"; - - cl_git_pass(git_index_add(other_index, &entry)); - cl_git_pass(git_index_write(other_index)); - - git_index_free(other_index); - git_repository_free(other_repo); -} - -static void modify_index_and_checkout_tree(git_checkout_options *opts) -{ - git_index *index; - git_reference *head; - git_object *obj; - - /* External changes to the index are maintained by default */ - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_repository_head(&head, g_repo)); - cl_git_pass(git_reference_peel(&obj, head, GIT_OBJECT_COMMIT)); - - cl_git_pass(git_reset(g_repo, obj, GIT_RESET_HARD, NULL)); - assert_status_entrycount(g_repo, 0); - - modify_index_ondisk(); - - /* The file in the index remains modified */ - cl_git_pass(git_checkout_tree(g_repo, obj, opts)); - - git_object_free(obj); - git_reference_free(head); - git_index_free(index); -} - -void test_checkout_tree__retains_external_index_changes(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - modify_index_and_checkout_tree(&opts); - assert_status_entrycount(g_repo, 1); -} - -void test_checkout_tree__no_index_refresh(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_NO_REFRESH; - - modify_index_and_checkout_tree(&opts); - assert_status_entrycount(g_repo, 0); -} - -void test_checkout_tree__dry_run(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_oid oid; - git_object *obj = NULL; - checkout_counts ct; - - /* first let's get things into a known state - by checkout out the HEAD */ - - assert_on_branch(g_repo, "master"); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - cl_git_pass(git_checkout_head(g_repo, &opts)); - - cl_assert(!git_fs_path_isdir("testrepo/a")); - - check_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n"); - - /* now checkout branch but with dry run enabled */ - - memset(&ct, 0, sizeof(ct)); - opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DRY_RUN; - opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; - opts.notify_cb = checkout_count_callback; - opts.notify_payload = &ct; - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir")); - - assert_on_branch(g_repo, "dir"); - - /* these normally would have been created and updated, but with - * DRY_RUN they will be unchanged. - */ - cl_assert(!git_fs_path_isdir("testrepo/a")); - check_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n"); - - /* check that notify callback was invoked */ - cl_assert_equal_i(ct.n_updates, 2); - - git_object_free(obj); -} diff --git a/tests/checkout/typechange.c b/tests/checkout/typechange.c deleted file mode 100644 index b888843f0..000000000 --- a/tests/checkout/typechange.c +++ /dev/null @@ -1,335 +0,0 @@ -#include "clar_libgit2.h" -#include "diff_generate.h" -#include "git2/checkout.h" -#include "path.h" -#include "posix.h" -#include "futils.h" - -static git_repository *g_repo = NULL; - -/* -From the test repo used for this test: --------------------------------------- - -This is a test repo for libgit2 where tree entries have type changes - -The key types that could be found in tree entries are: - -1 - GIT_FILEMODE_NEW = 0000000 -2 - GIT_FILEMODE_TREE = 0040000 -3 - GIT_FILEMODE_BLOB = 0100644 -4 - GIT_FILEMODE_BLOB_EXECUTABLE = 0100755 -5 - GIT_FILEMODE_LINK = 0120000 -6 - GIT_FILEMODE_COMMIT = 0160000 - -I will try to have every type of transition somewhere in the history -of this repo. - -Commits -------- -Initial commit - a(1) b(1) c(1) d(1) e(1) -Create content - a(1->2) b(1->3) c(1->4) d(1->5) e(1->6) -Changes #1 - a(2->3) b(3->4) c(4->5) d(5->6) e(6->2) -Changes #2 - a(3->5) b(4->6) c(5->2) d(6->3) e(2->4) -Changes #3 - a(5->3) b(6->4) c(2->5) d(3->6) e(4->2) -Changes #4 - a(3->2) b(4->3) c(5->4) d(6->5) e(2->6) -Changes #5 - a(2->1) b(3->1) c(4->1) d(5->1) e(6->1) - -*/ - -static const char *g_typechange_oids[] = { - "79b9f23e85f55ea36a472a902e875bc1121a94cb", - "9bdb75b73836a99e3dbeea640a81de81031fdc29", - "0e7ed140b514b8cae23254cb8656fe1674403aff", - "9d0235c7a7edc0889a18f97a42ee6db9fe688447", - "9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a", - "1b63caae4a5ca96f78e8dfefc376c6a39a142475", - "6eae26c90e8ccc4d16208972119c40635489c6f0", - NULL -}; - -static bool g_typechange_empty[] = { - true, false, false, false, false, false, true, true -}; - -static const int g_typechange_expected_conflicts[] = { - 1, 2, 3, 3, 2, 3, 2 -}; - -static const int g_typechange_expected_untracked[] = { - 6, 4, 3, 2, 3, 2, 5 -}; - -void test_checkout_typechange__initialize(void) -{ - g_repo = cl_git_sandbox_init("typechanges"); - - cl_fixture_sandbox("submod2_target"); - p_rename("submod2_target/.gitted", "submod2_target/.git"); -} - -void test_checkout_typechange__cleanup(void) -{ - cl_git_sandbox_cleanup(); - cl_fixture_cleanup("submod2_target"); -} - -static void assert_file_exists(const char *path) -{ - cl_assert_(git_fs_path_isfile(path), path); -} - -static void assert_dir_exists(const char *path) -{ - cl_assert_(git_fs_path_isdir(path), path); -} - -static void assert_workdir_matches_tree( - git_repository *repo, const git_oid *id, const char *root, bool recurse) -{ - git_object *obj; - git_tree *tree; - size_t i, max_i; - git_str path = GIT_STR_INIT; - - if (!root) - root = git_repository_workdir(repo); - cl_assert(root); - - cl_git_pass(git_object_lookup(&obj, repo, id, GIT_OBJECT_ANY)); - cl_git_pass(git_object_peel((git_object **)&tree, obj, GIT_OBJECT_TREE)); - git_object_free(obj); - - max_i = git_tree_entrycount(tree); - - for (i = 0; i < max_i; ++i) { - const git_tree_entry *te = git_tree_entry_byindex(tree, i); - cl_assert(te); - - cl_git_pass(git_str_joinpath(&path, root, git_tree_entry_name(te))); - - switch (git_tree_entry_type(te)) { - case GIT_OBJECT_COMMIT: - assert_dir_exists(path.ptr); - break; - case GIT_OBJECT_TREE: - assert_dir_exists(path.ptr); - if (recurse) - assert_workdir_matches_tree( - repo, git_tree_entry_id(te), path.ptr, true); - break; - case GIT_OBJECT_BLOB: - switch (git_tree_entry_filemode(te)) { - case GIT_FILEMODE_BLOB: - case GIT_FILEMODE_BLOB_EXECUTABLE: - assert_file_exists(path.ptr); - /* because of cross-platform, don't confirm exec bit yet */ - break; - case GIT_FILEMODE_LINK: - cl_assert_(git_fs_path_exists(path.ptr), path.ptr); - /* because of cross-platform, don't confirm link yet */ - break; - default: - cl_assert(false); /* really?! */ - } - break; - default: - cl_assert(false); /* really?!! */ - } - } - - git_tree_free(tree); - git_str_dispose(&path); -} - -void test_checkout_typechange__checkout_typechanges_safe(void) -{ - int i; - git_object *obj; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - for (i = 0; g_typechange_oids[i] != NULL; ++i) { - cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i])); - - opts.checkout_strategy = !i ? GIT_CHECKOUT_FORCE : GIT_CHECKOUT_SAFE; - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - - cl_git_pass( - git_repository_set_head_detached(g_repo, git_object_id(obj))); - - assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true); - - git_object_free(obj); - - if (!g_typechange_empty[i]) { - cl_assert(git_fs_path_isdir("typechanges")); - cl_assert(git_fs_path_exists("typechanges/a")); - cl_assert(git_fs_path_exists("typechanges/b")); - cl_assert(git_fs_path_exists("typechanges/c")); - cl_assert(git_fs_path_exists("typechanges/d")); - cl_assert(git_fs_path_exists("typechanges/e")); - } else { - cl_assert(git_fs_path_isdir("typechanges")); - cl_assert(!git_fs_path_exists("typechanges/a")); - cl_assert(!git_fs_path_exists("typechanges/b")); - cl_assert(!git_fs_path_exists("typechanges/c")); - cl_assert(!git_fs_path_exists("typechanges/d")); - cl_assert(!git_fs_path_exists("typechanges/e")); - } - } -} - -typedef struct { - int conflicts; - int dirty; - int updates; - int untracked; - int ignored; -} notify_counts; - -static int notify_counter( - git_checkout_notify_t why, - const char *path, - const git_diff_file *baseline, - const git_diff_file *target, - const git_diff_file *workdir, - void *payload) -{ - notify_counts *cts = payload; - - GIT_UNUSED(path); - GIT_UNUSED(baseline); - GIT_UNUSED(target); - GIT_UNUSED(workdir); - - switch (why) { - case GIT_CHECKOUT_NOTIFY_CONFLICT: cts->conflicts++; break; - case GIT_CHECKOUT_NOTIFY_DIRTY: cts->dirty++; break; - case GIT_CHECKOUT_NOTIFY_UPDATED: cts->updates++; break; - case GIT_CHECKOUT_NOTIFY_UNTRACKED: cts->untracked++; break; - case GIT_CHECKOUT_NOTIFY_IGNORED: cts->ignored++; break; - default: break; - } - - return 0; -} - -static void force_create_file(const char *file) -{ - int error = git_futils_rmdir_r(file, NULL, - GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS); - cl_assert(!error || error == GIT_ENOTFOUND); - cl_git_pass(git_futils_mkpath2file(file, 0777)); - cl_git_rewritefile(file, "yowza!!"); -} - -static int make_submodule_dirty(git_submodule *sm, const char *name, void *payload) -{ - git_str submodulepath = GIT_STR_INIT; - git_str dirtypath = GIT_STR_INIT; - git_repository *submodule_repo; - - GIT_UNUSED(name); - GIT_UNUSED(payload); - - /* remove submodule directory in preparation for init and repo_init */ - cl_git_pass(git_str_joinpath( - &submodulepath, - git_repository_workdir(g_repo), - git_submodule_path(sm) - )); - git_futils_rmdir_r(git_str_cstr(&submodulepath), NULL, GIT_RMDIR_REMOVE_FILES); - - /* initialize submodule's repository */ - cl_git_pass(git_submodule_repo_init(&submodule_repo, sm, 0)); - - /* create a file in the submodule workdir to make it dirty */ - cl_git_pass( - git_str_joinpath(&dirtypath, git_repository_workdir(submodule_repo), "dirty")); - force_create_file(git_str_cstr(&dirtypath)); - - git_str_dispose(&dirtypath); - git_str_dispose(&submodulepath); - git_repository_free(submodule_repo); - - return 0; -} - -void test_checkout_typechange__checkout_with_conflicts(void) -{ - int i; - git_object *obj; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - notify_counts cts = {0}; - - opts.notify_flags = - GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_UNTRACKED; - opts.notify_cb = notify_counter; - opts.notify_payload = &cts; - - for (i = 0; g_typechange_oids[i] != NULL; ++i) { - cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i])); - - force_create_file("typechanges/a/blocker"); - force_create_file("typechanges/b"); - force_create_file("typechanges/c/sub/sub/file"); - git_futils_rmdir_r("typechanges/d", NULL, GIT_RMDIR_REMOVE_FILES); - p_mkdir("typechanges/d", 0777); /* intentionally empty dir */ - force_create_file("typechanges/untracked"); - cl_git_pass(git_submodule_foreach(g_repo, make_submodule_dirty, NULL)); - - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - memset(&cts, 0, sizeof(cts)); - - cl_git_fail(git_checkout_tree(g_repo, obj, &opts)); - cl_assert_equal_i(cts.conflicts, g_typechange_expected_conflicts[i]); - cl_assert_equal_i(cts.untracked, g_typechange_expected_untracked[i]); - cl_assert_equal_i(cts.dirty, 0); - cl_assert_equal_i(cts.updates, 0); - cl_assert_equal_i(cts.ignored, 0); - - opts.checkout_strategy = - GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; - memset(&cts, 0, sizeof(cts)); - - cl_assert(git_fs_path_exists("typechanges/untracked")); - - cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); - cl_assert_equal_i(0, cts.conflicts); - - cl_assert(!git_fs_path_exists("typechanges/untracked")); - - cl_git_pass( - git_repository_set_head_detached(g_repo, git_object_id(obj))); - - assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true); - - git_object_free(obj); - } -} - -void test_checkout_typechange__status_char(void) -{ - size_t i; - git_oid oid; - git_commit *commit; - git_diff *diff; - const git_diff_delta *delta; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - char expected[8] = {'M', 'M', 'R', 'T', 'D', 'R', 'A', 'R'}; - - git_oid_fromstr(&oid, "9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a"); - cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); - diffopts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; - cl_git_pass(git_diff__commit(&diff, g_repo, commit, &diffopts)); - cl_git_pass(git_diff_find_similar(diff, NULL)); - - for (i = 0; i < git_diff_num_deltas(diff); i++) { - delta = git_diff_get_delta(diff, i); - cl_assert_equal_i(expected[i], git_diff_status_char(delta->status)); - } - - git_diff_free(diff); - git_commit_free(commit); -} diff --git a/tests/cherrypick/bare.c b/tests/cherrypick/bare.c deleted file mode 100644 index f90ce0226..000000000 --- a/tests/cherrypick/bare.c +++ /dev/null @@ -1,105 +0,0 @@ -#include "clar.h" -#include "clar_libgit2.h" - -#include "futils.h" -#include "git2/cherrypick.h" - -#include "../merge/merge_helpers.h" - -#define TEST_REPO_PATH "cherrypick" - -static git_repository *repo; - -void test_cherrypick_bare__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_cherrypick_bare__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_cherrypick_bare__automerge(void) -{ - git_commit *head = NULL, *commit = NULL; - git_index *index = NULL; - git_oid head_oid, cherry_oid; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "38c05a857e831a7e759d83778bfc85d003e21c45", 0, "file1.txt" }, - { 0100644, "a661b5dec1004e2c62654ded3762370c27cf266b", 0, "file2.txt" }, - { 0100644, "df6b290e0bd6a89b01d69f66687e8abf385283ca", 0, "file3.txt" }, - }; - - git_oid_fromstr(&head_oid, "d3d77487660ee3c0194ee01dc5eaf478782b1c7e"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - - git_oid_fromstr(&cherry_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); - cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); - - cl_git_pass(git_cherrypick_commit(&index, repo, commit, head, 0, NULL)); - cl_assert(merge_test_index(index, merge_index_entries, 3)); - - git_index_free(index); - git_commit_free(head); - git_commit_free(commit); -} - -void test_cherrypick_bare__conflicts(void) -{ - git_commit *head = NULL, *commit = NULL; - git_index *index = NULL; - git_oid head_oid, cherry_oid; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "242e7977ba73637822ffb265b46004b9b0e5153b", 0, "file1.txt" }, - { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 1, "file2.txt" }, - { 0100644, "bd6ffc8c6c41f0f85ff9e3d61c9479516bac0024", 2, "file2.txt" }, - { 0100644, "563f6473a3858f99b80e5f93c660512ed38e1e6f", 3, "file2.txt" }, - { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 1, "file3.txt" }, - { 0100644, "1124c2c1ae07b26fded662d6c3f3631d9dc16f88", 2, "file3.txt" }, - { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 3, "file3.txt" }, - }; - - git_oid_fromstr(&head_oid, "bafbf6912c09505ac60575cd43d3f2aba3bd84d8"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - - git_oid_fromstr(&cherry_oid, "e9b63f3655b2ad80c0ff587389b5a9589a3a7110"); - cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); - - cl_git_pass(git_cherrypick_commit(&index, repo, commit, head, 0, NULL)); - cl_assert(merge_test_index(index, merge_index_entries, 7)); - - git_index_free(index); - git_commit_free(head); - git_commit_free(commit); -} - -void test_cherrypick_bare__orphan(void) -{ - git_commit *head = NULL, *commit = NULL; - git_index *index = NULL; - git_oid head_oid, cherry_oid; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "38c05a857e831a7e759d83778bfc85d003e21c45", 0, "file1.txt" }, - { 0100644, "a661b5dec1004e2c62654ded3762370c27cf266b", 0, "file2.txt" }, - { 0100644, "85a4a1d791973644f24c72f5e89420d3064cc452", 0, "file3.txt" }, - { 0100644, "9ccb9bf50c011fd58dcbaa65df917bf79539717f", 0, "orphan.txt" }, - }; - - git_oid_fromstr(&head_oid, "d3d77487660ee3c0194ee01dc5eaf478782b1c7e"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - - git_oid_fromstr(&cherry_oid, "74f06b5bfec6d33d7264f73606b57a7c0b963819"); - cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); - - cl_git_pass(git_cherrypick_commit(&index, repo, commit, head, 0, NULL)); - cl_assert(merge_test_index(index, merge_index_entries, 4)); - - git_index_free(index); - git_commit_free(head); - git_commit_free(commit); -} - diff --git a/tests/cherrypick/workdir.c b/tests/cherrypick/workdir.c deleted file mode 100644 index 8fd1ecbdf..000000000 --- a/tests/cherrypick/workdir.c +++ /dev/null @@ -1,469 +0,0 @@ -#include "clar.h" -#include "clar_libgit2.h" - -#include "futils.h" -#include "git2/cherrypick.h" - -#include "../merge/merge_helpers.h" - -#define TEST_REPO_PATH "cherrypick" - -static git_repository *repo; -static git_index *repo_index; - -/* Fixture setup and teardown */ -void test_cherrypick_workdir__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); - git_repository_index(&repo_index, repo); -} - -void test_cherrypick_workdir__cleanup(void) -{ - git_index_free(repo_index); - cl_git_sandbox_cleanup(); -} - -/* git reset --hard d3d77487660ee3c0194ee01dc5eaf478782b1c7e - * git cherry-pick cfc4f0999a8367568e049af4f72e452d40828a15 - * git cherry-pick 964ea3da044d9083181a88ba6701de9e35778bf4 - * git cherry-pick a43a050c588d4e92f11a6b139680923e9728477d - */ -void test_cherrypick_workdir__automerge(void) -{ - git_oid head_oid; - git_signature *signature = NULL; - size_t i; - - const char *cherrypick_oids[] = { - "cfc4f0999a8367568e049af4f72e452d40828a15", - "964ea3da044d9083181a88ba6701de9e35778bf4", - "a43a050c588d4e92f11a6b139680923e9728477d", - }; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "38c05a857e831a7e759d83778bfc85d003e21c45", 0, "file1.txt" }, - { 0100644, "a661b5dec1004e2c62654ded3762370c27cf266b", 0, "file2.txt" }, - { 0100644, "df6b290e0bd6a89b01d69f66687e8abf385283ca", 0, "file3.txt" }, - - { 0100644, "38c05a857e831a7e759d83778bfc85d003e21c45", 0, "file1.txt" }, - { 0100644, "bd8fc3c59fb52d3c8b5907ace7defa5803f82419", 0, "file2.txt" }, - { 0100644, "df6b290e0bd6a89b01d69f66687e8abf385283ca", 0, "file3.txt" }, - - { 0100644, "f06427bee380364bc7e0cb26a9245158e4726ce0", 0, "file1.txt" }, - { 0100644, "bd8fc3c59fb52d3c8b5907ace7defa5803f82419", 0, "file2.txt" }, - { 0100644, "df6b290e0bd6a89b01d69f66687e8abf385283ca", 0, "file3.txt" }, - }; - - cl_git_pass(git_signature_new(&signature, "Picker", "picker@example.org", time(NULL), 0)); - - git_oid_fromstr(&head_oid, "d3d77487660ee3c0194ee01dc5eaf478782b1c7e"); - - for (i = 0; i < 3; ++i) { - git_commit *head = NULL, *commit = NULL; - git_oid cherry_oid, cherrypicked_oid, cherrypicked_tree_oid; - git_tree *cherrypicked_tree = NULL; - - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&cherry_oid, cherrypick_oids[i]); - cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); - cl_git_pass(git_cherrypick(repo, commit, NULL)); - - cl_assert(git_fs_path_exists(TEST_REPO_PATH "/.git/CHERRY_PICK_HEAD")); - cl_assert(git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); - - cl_git_pass(git_index_write_tree(&cherrypicked_tree_oid, repo_index)); - cl_git_pass(git_tree_lookup(&cherrypicked_tree, repo, &cherrypicked_tree_oid)); - cl_git_pass(git_commit_create(&cherrypicked_oid, repo, "HEAD", signature, signature, NULL, - "Cherry picked!", cherrypicked_tree, 1, (const git_commit **)&head)); - - cl_assert(merge_test_index(repo_index, merge_index_entries + i * 3, 3)); - - git_oid_cpy(&head_oid, &cherrypicked_oid); - - git_tree_free(cherrypicked_tree); - git_commit_free(head); - git_commit_free(commit); - } - - git_signature_free(signature); -} - -/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15 - * git cherry-pick a43a050c588d4e92f11a6b139680923e9728477d*/ -void test_cherrypick_workdir__empty_result(void) -{ - git_oid head_oid; - git_signature *signature = NULL; - git_commit *head = NULL, *commit = NULL; - git_oid cherry_oid; - - const char *cherrypick_oid = "a43a050c588d4e92f11a6b139680923e9728477d"; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "19c5c7207054604b69c84d08a7571ef9672bb5c2", 0, "file1.txt" }, - { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 0, "file2.txt" }, - { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 0, "file3.txt" }, - }; - - cl_git_pass(git_signature_new(&signature, "Picker", "picker@example.org", time(NULL), 0)); - - git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); - - /* Create an untracked file that should not conflict */ - cl_git_mkfile(TEST_REPO_PATH "/file4.txt", ""); - cl_assert(git_fs_path_exists(TEST_REPO_PATH "/file4.txt")); - - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&cherry_oid, cherrypick_oid); - cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); - cl_git_pass(git_cherrypick(repo, commit, NULL)); - - /* The resulting tree should not have changed, the change was already on HEAD */ - cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); - - git_commit_free(head); - git_commit_free(commit); - - git_signature_free(signature); -} - -/* git reset --hard bafbf6912c09505ac60575cd43d3f2aba3bd84d8 - * git cherry-pick e9b63f3655b2ad80c0ff587389b5a9589a3a7110 - */ -void test_cherrypick_workdir__conflicts(void) -{ - git_commit *head = NULL, *commit = NULL; - git_oid head_oid, cherry_oid; - git_str conflicting_buf = GIT_STR_INIT, mergemsg_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "242e7977ba73637822ffb265b46004b9b0e5153b", 0, "file1.txt" }, - { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 1, "file2.txt" }, - { 0100644, "bd6ffc8c6c41f0f85ff9e3d61c9479516bac0024", 2, "file2.txt" }, - { 0100644, "563f6473a3858f99b80e5f93c660512ed38e1e6f", 3, "file2.txt" }, - { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 1, "file3.txt" }, - { 0100644, "1124c2c1ae07b26fded662d6c3f3631d9dc16f88", 2, "file3.txt" }, - { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 3, "file3.txt" }, - }; - - git_oid_fromstr(&head_oid, "bafbf6912c09505ac60575cd43d3f2aba3bd84d8"); - - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&cherry_oid, "e9b63f3655b2ad80c0ff587389b5a9589a3a7110"); - cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); - cl_git_pass(git_cherrypick(repo, commit, NULL)); - - cl_assert(git_fs_path_exists(TEST_REPO_PATH "/.git/CHERRY_PICK_HEAD")); - cl_assert(git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 7)); - - cl_git_pass(git_futils_readbuffer(&mergemsg_buf, - TEST_REPO_PATH "/.git/MERGE_MSG")); - cl_assert(strcmp(git_str_cstr(&mergemsg_buf), - "Change all files\n" \ - "\n" \ - "#Conflicts:\n" \ - "#\tfile2.txt\n" \ - "#\tfile3.txt\n") == 0); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, - TEST_REPO_PATH "/file2.txt")); - - cl_assert(strcmp(git_str_cstr(&conflicting_buf), - "!File 2\n" \ - "File 2\n" \ - "File 2\n" \ - "File 2\n" \ - "File 2\n" \ - "File 2\n" \ - "File 2\n" \ - "File 2\n" \ - "File 2\n" \ - "File 2\n" \ - "File 2!!\n" \ - "File 2\n" \ - "File 2\n" \ - "File 2\n" \ - "<<<<<<< HEAD\n" \ - "File 2\n" \ - "=======\n" \ - "File 2!\n" \ - "File 2\n" \ - "File 2!\n" \ - ">>>>>>> e9b63f3... Change all files\n") == 0); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, - TEST_REPO_PATH "/file3.txt")); - - cl_assert(strcmp(git_str_cstr(&conflicting_buf), - "!File 3\n" \ - "File 3\n" \ - "File 3\n" \ - "File 3\n" \ - "File 3\n" \ - "File 3\n" \ - "File 3\n" \ - "File 3\n" \ - "File 3\n" \ - "File 3\n" \ - "File 3\n" \ - "File 3!!\n" \ - "File 3\n" \ - "File 3\n" \ - "File 3\n" \ - "<<<<<<< HEAD\n" \ - "=======\n" \ - "File 3!\n" \ - "File 3!\n" \ - ">>>>>>> e9b63f3... Change all files\n") == 0); - - git_commit_free(commit); - git_commit_free(head); - git_str_dispose(&mergemsg_buf); - git_str_dispose(&conflicting_buf); -} - -/* git reset --hard bafbf6912c09505ac60575cd43d3f2aba3bd84d8 - * git cherry-pick -X ours e9b63f3655b2ad80c0ff587389b5a9589a3a7110 - */ -void test_cherrypick_workdir__conflict_use_ours(void) -{ - git_commit *head = NULL, *commit = NULL; - git_oid head_oid, cherry_oid; - git_cherrypick_options opts = GIT_CHERRYPICK_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "242e7977ba73637822ffb265b46004b9b0e5153b", 0, "file1.txt" }, - { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 1, "file2.txt" }, - { 0100644, "bd6ffc8c6c41f0f85ff9e3d61c9479516bac0024", 2, "file2.txt" }, - { 0100644, "563f6473a3858f99b80e5f93c660512ed38e1e6f", 3, "file2.txt" }, - { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 1, "file3.txt" }, - { 0100644, "1124c2c1ae07b26fded662d6c3f3631d9dc16f88", 2, "file3.txt" }, - { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 3, "file3.txt" }, - }; - - struct merge_index_entry merge_filesystem_entries[] = { - { 0100644, "242e7977ba73637822ffb265b46004b9b0e5153b", 0, "file1.txt" }, - { 0100644, "bd6ffc8c6c41f0f85ff9e3d61c9479516bac0024", 0, "file2.txt" }, - { 0100644, "1124c2c1ae07b26fded662d6c3f3631d9dc16f88", 0, "file3.txt" }, - }; - - /* leave the index in a conflicted state, but checkout "ours" to the workdir */ - opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; - - git_oid_fromstr(&head_oid, "bafbf6912c09505ac60575cd43d3f2aba3bd84d8"); - - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&cherry_oid, "e9b63f3655b2ad80c0ff587389b5a9589a3a7110"); - cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); - cl_git_pass(git_cherrypick(repo, commit, &opts)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 7)); - cl_assert(merge_test_workdir(repo, merge_filesystem_entries, 3)); - - /* resolve conflicts in the index by taking "ours" */ - opts.merge_opts.file_favor = GIT_MERGE_FILE_FAVOR_OURS; - - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - cl_git_pass(git_cherrypick(repo, commit, &opts)); - - cl_assert(merge_test_index(repo_index, merge_filesystem_entries, 3)); - cl_assert(merge_test_workdir(repo, merge_filesystem_entries, 3)); - - git_commit_free(commit); - git_commit_free(head); -} - -/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15 - * git cherry-pick 2a26c7e88b285613b302ba76712bc998863f3cbc - */ -void test_cherrypick_workdir__rename(void) -{ - git_commit *head, *commit; - git_oid head_oid, cherry_oid; - git_cherrypick_options opts = GIT_CHERRYPICK_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "19c5c7207054604b69c84d08a7571ef9672bb5c2", 0, "file1.txt" }, - { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 0, "file2.txt" }, - { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 0, "file3.txt.renamed" }, - }; - - opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES; - opts.merge_opts.rename_threshold = 50; - - git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&cherry_oid, "2a26c7e88b285613b302ba76712bc998863f3cbc"); - cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); - cl_git_pass(git_cherrypick(repo, commit, &opts)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); - - git_commit_free(commit); - git_commit_free(head); -} - -/* git reset --hard 44cd2ed2052c9c68f9a439d208e9614dc2a55c70 - * git cherry-pick 2a26c7e88b285613b302ba76712bc998863f3cbc - */ -void test_cherrypick_workdir__both_renamed(void) -{ - git_commit *head, *commit; - git_oid head_oid, cherry_oid; - git_str mergemsg_buf = GIT_STR_INIT; - git_cherrypick_options opts = GIT_CHERRYPICK_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "19c5c7207054604b69c84d08a7571ef9672bb5c2", 0, "file1.txt" }, - { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 0, "file2.txt" }, - { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 1, "file3.txt" }, - { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 3, "file3.txt.renamed" }, - { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 2, "file3.txt.renamed_on_branch" }, - }; - - opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES; - opts.merge_opts.rename_threshold = 50; - - git_oid_fromstr(&head_oid, "44cd2ed2052c9c68f9a439d208e9614dc2a55c70"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&cherry_oid, "2a26c7e88b285613b302ba76712bc998863f3cbc"); - cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); - cl_git_pass(git_cherrypick(repo, commit, &opts)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 5)); - - cl_git_pass(git_futils_readbuffer(&mergemsg_buf, - TEST_REPO_PATH "/.git/MERGE_MSG")); - cl_assert(strcmp(git_str_cstr(&mergemsg_buf), - "Renamed file3.txt -> file3.txt.renamed\n" \ - "\n" \ - "#Conflicts:\n" \ - "#\tfile3.txt\n" \ - "#\tfile3.txt.renamed\n" \ - "#\tfile3.txt.renamed_on_branch\n") == 0); - - git_str_dispose(&mergemsg_buf); - git_commit_free(commit); - git_commit_free(head); -} - -void test_cherrypick_workdir__nonmerge_fails_mainline_specified(void) -{ - git_reference *head; - git_commit *commit; - git_cherrypick_options opts = GIT_CHERRYPICK_OPTIONS_INIT; - - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel((git_object **)&commit, head, GIT_OBJECT_COMMIT)); - - opts.mainline = 1; - cl_must_fail(git_cherrypick(repo, commit, &opts)); - cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/CHERRY_PICK_HEAD")); - cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); - - git_reference_free(head); - git_commit_free(commit); -} - -/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15 - * git cherry-pick abe4603bc7cd5b8167a267e0e2418fd2348f8cff - */ -void test_cherrypick_workdir__merge_fails_without_mainline_specified(void) -{ - git_commit *head, *commit; - git_oid head_oid, cherry_oid; - - git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&cherry_oid, "abe4603bc7cd5b8167a267e0e2418fd2348f8cff"); - cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); - - cl_must_fail(git_cherrypick(repo, commit, NULL)); - cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/CHERRY_PICK_HEAD")); - cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); - - git_commit_free(commit); - git_commit_free(head); -} - -/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15 - * git cherry-pick -m1 abe4603bc7cd5b8167a267e0e2418fd2348f8cff - */ -void test_cherrypick_workdir__merge_first_parent(void) -{ - git_commit *head, *commit; - git_oid head_oid, cherry_oid; - git_cherrypick_options opts = GIT_CHERRYPICK_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "f90f9dcbdac2cce5cc166346160e19cb693ef4e8", 0, "file1.txt" }, - { 0100644, "563f6473a3858f99b80e5f93c660512ed38e1e6f", 0, "file2.txt" }, - { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 0, "file3.txt" }, - }; - - opts.mainline = 1; - - git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&cherry_oid, "abe4603bc7cd5b8167a267e0e2418fd2348f8cff"); - cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); - - cl_git_pass(git_cherrypick(repo, commit, &opts)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); - - git_commit_free(commit); - git_commit_free(head); -} - -/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15 - * git cherry-pick -m2 abe4603bc7cd5b8167a267e0e2418fd2348f8cff - */ -void test_cherrypick_workdir__merge_second_parent(void) -{ - git_commit *head, *commit; - git_oid head_oid, cherry_oid; - git_cherrypick_options opts = GIT_CHERRYPICK_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "487434cace79238a7091e2220611d4f20a765690", 0, "file1.txt" }, - { 0100644, "e5183bfd18e3a0a691fadde2f0d5610b73282d31", 0, "file2.txt" }, - { 0100644, "409a1bec58bf35348e8b62b72bb9c1f45cf5a587", 0, "file3.txt" }, - }; - - opts.mainline = 2; - - git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&cherry_oid, "abe4603bc7cd5b8167a267e0e2418fd2348f8cff"); - cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); - - cl_git_pass(git_cherrypick(repo, commit, &opts)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); - - git_commit_free(commit); - git_commit_free(head); -} - diff --git a/tests/clar.c b/tests/clar.c deleted file mode 100644 index ca508d073..000000000 --- a/tests/clar.c +++ /dev/null @@ -1,788 +0,0 @@ -/* - * Copyright (c) Vicent Marti. All rights reserved. - * - * This file is part of clar, distributed under the ISC license. - * For full terms see the included COPYING file. - */ -#include -#include -#include -#include -#include -#include -#include -#include - -/* required for sandboxing */ -#include -#include - -#ifdef _WIN32 -# include -# include -# include -# include - -# define _MAIN_CC __cdecl - -# ifndef stat -# define stat(path, st) _stat(path, st) -# endif -# ifndef mkdir -# define mkdir(path, mode) _mkdir(path) -# endif -# ifndef chdir -# define chdir(path) _chdir(path) -# endif -# ifndef access -# define access(path, mode) _access(path, mode) -# endif -# ifndef strdup -# define strdup(str) _strdup(str) -# endif -# ifndef strcasecmp -# define strcasecmp(a,b) _stricmp(a,b) -# endif - -# ifndef __MINGW32__ -# pragma comment(lib, "shell32") -# ifndef strncpy -# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) -# endif -# ifndef W_OK -# define W_OK 02 -# endif -# ifndef S_ISDIR -# define S_ISDIR(x) ((x & _S_IFDIR) != 0) -# endif -# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__) -# else -# define p_snprintf snprintf -# endif - -# ifndef PRIuZ -# define PRIuZ "Iu" -# endif -# ifndef PRIxZ -# define PRIxZ "Ix" -# endif - -# if defined(_MSC_VER) || defined(__MINGW32__) - typedef struct stat STAT_T; -# else - typedef struct _stat STAT_T; -# endif -#else -# include /* waitpid(2) */ -# include -# define _MAIN_CC -# define p_snprintf snprintf -# ifndef PRIuZ -# define PRIuZ "zu" -# endif -# ifndef PRIxZ -# define PRIxZ "zx" -# endif - typedef struct stat STAT_T; -#endif - -#include "clar.h" - -static void fs_rm(const char *_source); -static void fs_copy(const char *_source, const char *dest); - -static const char * -fixture_path(const char *base, const char *fixture_name); - -struct clar_error { - const char *file; - const char *function; - size_t line_number; - const char *error_msg; - char *description; - - struct clar_error *next; -}; - -struct clar_explicit { - size_t suite_idx; - const char *filter; - - struct clar_explicit *next; -}; - -struct clar_report { - const char *test; - int test_number; - const char *suite; - - enum cl_test_status status; - - struct clar_error *errors; - struct clar_error *last_error; - - struct clar_report *next; -}; - -struct clar_summary { - const char *filename; - FILE *fp; -}; - -static struct { - enum cl_test_status test_status; - - const char *active_test; - const char *active_suite; - - int total_skipped; - int total_errors; - - int tests_ran; - int suites_ran; - - enum cl_output_format output_format; - - int report_errors_only; - int exit_on_error; - int report_suite_names; - - int write_summary; - char *summary_filename; - struct clar_summary *summary; - - struct clar_explicit *explicit; - struct clar_explicit *last_explicit; - - struct clar_report *reports; - struct clar_report *last_report; - - void (*local_cleanup)(void *); - void *local_cleanup_payload; - - jmp_buf trampoline; - int trampoline_enabled; - - cl_trace_cb *pfn_trace_cb; - void *trace_payload; - -} _clar; - -struct clar_func { - const char *name; - void (*ptr)(void); -}; - -struct clar_suite { - const char *name; - struct clar_func initialize; - struct clar_func cleanup; - const struct clar_func *tests; - size_t test_count; - int enabled; -}; - -/* From clar_print_*.c */ -static void clar_print_init(int test_count, int suite_count, const char *suite_names); -static void clar_print_shutdown(int test_count, int suite_count, int error_count); -static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error); -static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status failed); -static void clar_print_onsuite(const char *suite_name, int suite_index); -static void clar_print_onabort(const char *msg, ...); - -/* From clar_sandbox.c */ -static void clar_unsandbox(void); -static int clar_sandbox(void); - -/* From summary.h */ -static struct clar_summary *clar_summary_init(const char *filename); -static int clar_summary_shutdown(struct clar_summary *fp); - -/* Load the declarations for the test suite */ -#include "clar.suite" - - -#define CL_TRACE(ev) \ - do { \ - if (_clar.pfn_trace_cb) \ - _clar.pfn_trace_cb(ev, \ - _clar.active_suite, \ - _clar.active_test, \ - _clar.trace_payload); \ - } while (0) - -void cl_trace_register(cl_trace_cb *cb, void *payload) -{ - _clar.pfn_trace_cb = cb; - _clar.trace_payload = payload; -} - - -/* Core test functions */ -static void -clar_report_errors(struct clar_report *report) -{ - struct clar_error *error; - int i = 1; - - for (error = report->errors; error; error = error->next) - clar_print_error(i++, _clar.last_report, error); -} - -static void -clar_report_all(void) -{ - struct clar_report *report; - struct clar_error *error; - int i = 1; - - for (report = _clar.reports; report; report = report->next) { - if (report->status != CL_TEST_FAILURE) - continue; - - for (error = report->errors; error; error = error->next) - clar_print_error(i++, report, error); - } -} - -static void -clar_run_test( - const struct clar_func *test, - const struct clar_func *initialize, - const struct clar_func *cleanup) -{ - _clar.trampoline_enabled = 1; - - CL_TRACE(CL_TRACE__TEST__BEGIN); - - if (setjmp(_clar.trampoline) == 0) { - if (initialize->ptr != NULL) - initialize->ptr(); - - CL_TRACE(CL_TRACE__TEST__RUN_BEGIN); - test->ptr(); - CL_TRACE(CL_TRACE__TEST__RUN_END); - } - - _clar.trampoline_enabled = 0; - - if (_clar.last_report->status == CL_TEST_NOTRUN) - _clar.last_report->status = CL_TEST_OK; - - if (_clar.local_cleanup != NULL) - _clar.local_cleanup(_clar.local_cleanup_payload); - - if (cleanup->ptr != NULL) - cleanup->ptr(); - - CL_TRACE(CL_TRACE__TEST__END); - - _clar.tests_ran++; - - /* remove any local-set cleanup methods */ - _clar.local_cleanup = NULL; - _clar.local_cleanup_payload = NULL; - - if (_clar.report_errors_only) { - clar_report_errors(_clar.last_report); - } else { - clar_print_ontest(test->name, _clar.tests_ran, _clar.last_report->status); - } -} - -static void -clar_run_suite(const struct clar_suite *suite, const char *filter) -{ - const struct clar_func *test = suite->tests; - size_t i, matchlen; - struct clar_report *report; - int exact = 0; - - if (!suite->enabled) - return; - - if (_clar.exit_on_error && _clar.total_errors) - return; - - if (!_clar.report_errors_only) - clar_print_onsuite(suite->name, ++_clar.suites_ran); - - _clar.active_suite = suite->name; - _clar.active_test = NULL; - CL_TRACE(CL_TRACE__SUITE_BEGIN); - - if (filter) { - size_t suitelen = strlen(suite->name); - matchlen = strlen(filter); - if (matchlen <= suitelen) { - filter = NULL; - } else { - filter += suitelen; - while (*filter == ':') - ++filter; - matchlen = strlen(filter); - - if (matchlen && filter[matchlen - 1] == '$') { - exact = 1; - matchlen--; - } - } - } - - for (i = 0; i < suite->test_count; ++i) { - if (filter && strncmp(test[i].name, filter, matchlen)) - continue; - - if (exact && strlen(test[i].name) != matchlen) - continue; - - _clar.active_test = test[i].name; - - report = calloc(1, sizeof(struct clar_report)); - report->suite = _clar.active_suite; - report->test = _clar.active_test; - report->test_number = _clar.tests_ran; - report->status = CL_TEST_NOTRUN; - - if (_clar.reports == NULL) - _clar.reports = report; - - if (_clar.last_report != NULL) - _clar.last_report->next = report; - - _clar.last_report = report; - - clar_run_test(&test[i], &suite->initialize, &suite->cleanup); - - if (_clar.exit_on_error && _clar.total_errors) - return; - } - - _clar.active_test = NULL; - CL_TRACE(CL_TRACE__SUITE_END); -} - -static void -clar_usage(const char *arg) -{ - printf("Usage: %s [options]\n\n", arg); - printf("Options:\n"); - printf(" -sname Run only the suite with `name` (can go to individual test name)\n"); - printf(" -iname Include the suite with `name`\n"); - printf(" -xname Exclude the suite with `name`\n"); - printf(" -v Increase verbosity (show suite names)\n"); - printf(" -q Only report tests that had an error\n"); - printf(" -Q Quit as soon as a test fails\n"); - printf(" -t Display results in tap format\n"); - printf(" -l Print suite names\n"); - printf(" -r[filename] Write summary file (to the optional filename)\n"); - exit(-1); -} - -static void -clar_parse_args(int argc, char **argv) -{ - int i; - - /* Verify options before execute */ - for (i = 1; i < argc; ++i) { - char *argument = argv[i]; - - if (argument[0] != '-' || argument[1] == '\0' - || strchr("sixvqQtlr", argument[1]) == NULL) { - clar_usage(argv[0]); - } - } - - for (i = 1; i < argc; ++i) { - char *argument = argv[i]; - - switch (argument[1]) { - case 's': - case 'i': - case 'x': { /* given suite name */ - int offset = (argument[2] == '=') ? 3 : 2, found = 0; - char action = argument[1]; - size_t j, arglen, suitelen, cmplen; - - argument += offset; - arglen = strlen(argument); - - if (arglen == 0) - clar_usage(argv[0]); - - for (j = 0; j < _clar_suite_count; ++j) { - suitelen = strlen(_clar_suites[j].name); - cmplen = (arglen < suitelen) ? arglen : suitelen; - - if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) { - int exact = (arglen >= suitelen); - - /* Do we have a real suite prefix separated by a - * trailing '::' or just a matching substring? */ - if (arglen > suitelen && (argument[suitelen] != ':' - || argument[suitelen + 1] != ':')) - continue; - - ++found; - - if (!exact) - _clar.report_suite_names = 1; - - switch (action) { - case 's': { - struct clar_explicit *explicit = - calloc(1, sizeof(struct clar_explicit)); - assert(explicit); - - explicit->suite_idx = j; - explicit->filter = argument; - - if (_clar.explicit == NULL) - _clar.explicit = explicit; - - if (_clar.last_explicit != NULL) - _clar.last_explicit->next = explicit; - - _clar_suites[j].enabled = 1; - _clar.last_explicit = explicit; - break; - } - case 'i': _clar_suites[j].enabled = 1; break; - case 'x': _clar_suites[j].enabled = 0; break; - } - - if (exact) - break; - } - } - - if (!found) { - clar_print_onabort("No suite matching '%s' found.\n", argument); - exit(-1); - } - break; - } - - case 'q': - _clar.report_errors_only = 1; - break; - - case 'Q': - _clar.exit_on_error = 1; - break; - - case 't': - _clar.output_format = CL_OUTPUT_TAP; - break; - - case 'l': { - size_t j; - printf("Test suites (use -s to run just one):\n"); - for (j = 0; j < _clar_suite_count; ++j) - printf(" %3d: %s\n", (int)j, _clar_suites[j].name); - - exit(0); - } - - case 'v': - _clar.report_suite_names = 1; - break; - - case 'r': - _clar.write_summary = 1; - free(_clar.summary_filename); - _clar.summary_filename = strdup(*(argument + 2) ? (argument + 2) : "summary.xml"); - break; - - default: - assert(!"Unexpected commandline argument!"); - } - } -} - -void -clar_test_init(int argc, char **argv) -{ - if (argc > 1) - clar_parse_args(argc, argv); - - clar_print_init( - (int)_clar_callback_count, - (int)_clar_suite_count, - "" - ); - - if ((_clar.summary_filename = getenv("CLAR_SUMMARY")) != NULL) { - _clar.write_summary = 1; - _clar.summary_filename = strdup(_clar.summary_filename); - } - - if (_clar.write_summary && - !(_clar.summary = clar_summary_init(_clar.summary_filename))) { - clar_print_onabort("Failed to open the summary file\n"); - exit(-1); - } - - if (clar_sandbox() < 0) { - clar_print_onabort("Failed to sandbox the test runner.\n"); - exit(-1); - } -} - -int -clar_test_run(void) -{ - size_t i; - struct clar_explicit *explicit; - - if (_clar.explicit) { - for (explicit = _clar.explicit; explicit; explicit = explicit->next) - clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter); - } else { - for (i = 0; i < _clar_suite_count; ++i) - clar_run_suite(&_clar_suites[i], NULL); - } - - return _clar.total_errors; -} - -void -clar_test_shutdown(void) -{ - struct clar_explicit *explicit, *explicit_next; - struct clar_report *report, *report_next; - - clar_print_shutdown( - _clar.tests_ran, - (int)_clar_suite_count, - _clar.total_errors - ); - - clar_unsandbox(); - - if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) { - clar_print_onabort("Failed to write the summary file\n"); - exit(-1); - } - - for (explicit = _clar.explicit; explicit; explicit = explicit_next) { - explicit_next = explicit->next; - free(explicit); - } - - for (report = _clar.reports; report; report = report_next) { - report_next = report->next; - free(report); - } - - free(_clar.summary_filename); -} - -int -clar_test(int argc, char **argv) -{ - int errors; - - clar_test_init(argc, argv); - errors = clar_test_run(); - clar_test_shutdown(); - - return errors; -} - -static void abort_test(void) -{ - if (!_clar.trampoline_enabled) { - clar_print_onabort( - "Fatal error: a cleanup method raised an exception."); - clar_report_errors(_clar.last_report); - exit(-1); - } - - CL_TRACE(CL_TRACE__TEST__LONGJMP); - longjmp(_clar.trampoline, -1); -} - -void clar__skip(void) -{ - _clar.last_report->status = CL_TEST_SKIP; - _clar.total_skipped++; - abort_test(); -} - -void clar__fail( - const char *file, - const char *function, - size_t line, - const char *error_msg, - const char *description, - int should_abort) -{ - struct clar_error *error = calloc(1, sizeof(struct clar_error)); - - if (_clar.last_report->errors == NULL) - _clar.last_report->errors = error; - - if (_clar.last_report->last_error != NULL) - _clar.last_report->last_error->next = error; - - _clar.last_report->last_error = error; - - error->file = file; - error->function = function; - error->line_number = line; - error->error_msg = error_msg; - - if (description != NULL) - error->description = strdup(description); - - _clar.total_errors++; - _clar.last_report->status = CL_TEST_FAILURE; - - if (should_abort) - abort_test(); -} - -void clar__assert( - int condition, - const char *file, - const char *function, - size_t line, - const char *error_msg, - const char *description, - int should_abort) -{ - if (condition) - return; - - clar__fail(file, function, line, error_msg, description, should_abort); -} - -void clar__assert_equal( - const char *file, - const char *function, - size_t line, - const char *err, - int should_abort, - const char *fmt, - ...) -{ - va_list args; - char buf[4096]; - int is_equal = 1; - - va_start(args, fmt); - - if (!strcmp("%s", fmt)) { - const char *s1 = va_arg(args, const char *); - const char *s2 = va_arg(args, const char *); - is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2); - - if (!is_equal) { - if (s1 && s2) { - int pos; - for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) - /* find differing byte offset */; - p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", - s1, s2, pos); - } else { - p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2); - } - } - } - else if(!strcmp("%.*s", fmt)) { - const char *s1 = va_arg(args, const char *); - const char *s2 = va_arg(args, const char *); - int len = va_arg(args, int); - is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len); - - if (!is_equal) { - if (s1 && s2) { - int pos; - for (pos = 0; s1[pos] == s2[pos] && pos < len; ++pos) - /* find differing byte offset */; - p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s' (at byte %d)", - len, s1, len, s2, pos); - } else { - p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s'", len, s1, len, s2); - } - } - } - else if (!strcmp("%ls", fmt)) { - const wchar_t *wcs1 = va_arg(args, const wchar_t *); - const wchar_t *wcs2 = va_arg(args, const wchar_t *); - is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2); - - if (!is_equal) { - if (wcs1 && wcs2) { - int pos; - for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos) - /* find differing byte offset */; - p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)", - wcs1, wcs2, pos); - } else { - p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2); - } - } - } - else if(!strcmp("%.*ls", fmt)) { - const wchar_t *wcs1 = va_arg(args, const wchar_t *); - const wchar_t *wcs2 = va_arg(args, const wchar_t *); - int len = va_arg(args, int); - is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len); - - if (!is_equal) { - if (wcs1 && wcs2) { - int pos; - for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos) - /* find differing byte offset */; - p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls' (at byte %d)", - len, wcs1, len, wcs2, pos); - } else { - p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls'", len, wcs1, len, wcs2); - } - } - } - else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) { - size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t); - is_equal = (sz1 == sz2); - if (!is_equal) { - int offset = p_snprintf(buf, sizeof(buf), fmt, sz1); - strncat(buf, " != ", sizeof(buf) - offset); - p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2); - } - } - else if (!strcmp("%p", fmt)) { - void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *); - is_equal = (p1 == p2); - if (!is_equal) - p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2); - } - else { - int i1 = va_arg(args, int), i2 = va_arg(args, int); - is_equal = (i1 == i2); - if (!is_equal) { - int offset = p_snprintf(buf, sizeof(buf), fmt, i1); - strncat(buf, " != ", sizeof(buf) - offset); - p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2); - } - } - - va_end(args); - - if (!is_equal) - clar__fail(file, function, line, err, buf, should_abort); -} - -void cl_set_cleanup(void (*cleanup)(void *), void *opaque) -{ - _clar.local_cleanup = cleanup; - _clar.local_cleanup_payload = opaque; -} - -#include "clar/sandbox.h" -#include "clar/fixtures.h" -#include "clar/fs.h" -#include "clar/print.h" -#include "clar/summary.h" diff --git a/tests/clar.h b/tests/clar.h deleted file mode 100644 index 3f659c2f6..000000000 --- a/tests/clar.h +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (c) Vicent Marti. All rights reserved. - * - * This file is part of clar, distributed under the ISC license. - * For full terms see the included COPYING file. - */ -#ifndef __CLAR_TEST_H__ -#define __CLAR_TEST_H__ - -#include - -enum cl_test_status { - CL_TEST_OK, - CL_TEST_FAILURE, - CL_TEST_SKIP, - CL_TEST_NOTRUN -}; - -enum cl_output_format { - CL_OUTPUT_CLAP, - CL_OUTPUT_TAP -}; - -/** Setup clar environment */ -void clar_test_init(int argc, char *argv[]); -int clar_test_run(void); -void clar_test_shutdown(void); - -/** One shot setup & run */ -int clar_test(int argc, char *argv[]); - -const char *clar_sandbox_path(void); - -void cl_set_cleanup(void (*cleanup)(void *), void *opaque); -void cl_fs_cleanup(void); - -/** - * cl_trace_* is a hook to provide a simple global tracing - * mechanism. - * - * The goal here is to let main() provide clar-proper - * with a callback to optionally write log info for - * test operations into the same stream used by their - * actual tests. This would let them print test names - * and maybe performance data as they choose. - * - * The goal is NOT to alter the flow of control or to - * override test selection/skipping. (So the callback - * does not return a value.) - * - * The goal is NOT to duplicate the existing - * pass/fail/skip reporting. (So the callback - * does not accept a status/errorcode argument.) - * - */ -typedef enum cl_trace_event { - CL_TRACE__SUITE_BEGIN, - CL_TRACE__SUITE_END, - CL_TRACE__TEST__BEGIN, - CL_TRACE__TEST__END, - CL_TRACE__TEST__RUN_BEGIN, - CL_TRACE__TEST__RUN_END, - CL_TRACE__TEST__LONGJMP -} cl_trace_event; - -typedef void (cl_trace_cb)( - cl_trace_event ev, - const char *suite_name, - const char *test_name, - void *payload); - -/** - * Register a callback into CLAR to send global trace events. - * Pass NULL to disable. - */ -void cl_trace_register(cl_trace_cb *cb, void *payload); - - -#ifdef CLAR_FIXTURE_PATH -const char *cl_fixture(const char *fixture_name); -void cl_fixture_sandbox(const char *fixture_name); -void cl_fixture_cleanup(const char *fixture_name); -const char *cl_fixture_basename(const char *fixture_name); -#endif - -/** - * Assertion macros with explicit error message - */ -#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 1) -#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 1) -#define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 1) - -/** - * Check macros with explicit error message - */ -#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 0) -#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 0) -#define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 0) - -/** - * Assertion macros with no error message - */ -#define cl_must_pass(expr) cl_must_pass_(expr, NULL) -#define cl_must_fail(expr) cl_must_fail_(expr, NULL) -#define cl_assert(expr) cl_assert_(expr, NULL) - -/** - * Check macros with no error message - */ -#define cl_check_pass(expr) cl_check_pass_(expr, NULL) -#define cl_check_fail(expr) cl_check_fail_(expr, NULL) -#define cl_check(expr) cl_check_(expr, NULL) - -/** - * Forced failure/warning - */ -#define cl_fail(desc) clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1) -#define cl_warning(desc) clar__fail(__FILE__, __func__, __LINE__, "Warning during test execution:", desc, 0) - -#define cl_skip() clar__skip() - -/** - * Typed assertion macros - */ -#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2)) -#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2)) - -#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2)) -#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2)) - -#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len)) -#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len)) - -#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len)) -#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len)) - -#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) -#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2)) -#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2)) - -#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__func__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0)) - -#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__func__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2)) - -void clar__skip(void); - -void clar__fail( - const char *file, - const char *func, - size_t line, - const char *error, - const char *description, - int should_abort); - -void clar__assert( - int condition, - const char *file, - const char *func, - size_t line, - const char *error, - const char *description, - int should_abort); - -void clar__assert_equal( - const char *file, - const char *func, - size_t line, - const char *err, - int should_abort, - const char *fmt, - ...); - -#endif diff --git a/tests/clar/fixtures.h b/tests/clar/fixtures.h deleted file mode 100644 index 77033d365..000000000 --- a/tests/clar/fixtures.h +++ /dev/null @@ -1,50 +0,0 @@ -static const char * -fixture_path(const char *base, const char *fixture_name) -{ - static char _path[4096]; - size_t root_len; - - root_len = strlen(base); - strncpy(_path, base, sizeof(_path)); - - if (_path[root_len - 1] != '/') - _path[root_len++] = '/'; - - if (fixture_name[0] == '/') - fixture_name++; - - strncpy(_path + root_len, - fixture_name, - sizeof(_path) - root_len); - - return _path; -} - -#ifdef CLAR_FIXTURE_PATH -const char *cl_fixture(const char *fixture_name) -{ - return fixture_path(CLAR_FIXTURE_PATH, fixture_name); -} - -void cl_fixture_sandbox(const char *fixture_name) -{ - fs_copy(cl_fixture(fixture_name), _clar_path); -} - -const char *cl_fixture_basename(const char *fixture_name) -{ - const char *p; - - for (p = fixture_name; *p; p++) { - if (p[0] == '/' && p[1] && p[1] != '/') - fixture_name = p+1; - } - - return fixture_name; -} - -void cl_fixture_cleanup(const char *fixture_name) -{ - fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name))); -} -#endif diff --git a/tests/clar/fs.h b/tests/clar/fs.h deleted file mode 100644 index 44ede4572..000000000 --- a/tests/clar/fs.h +++ /dev/null @@ -1,520 +0,0 @@ -/* - * By default, use a read/write loop to copy files on POSIX systems. - * On Linux, use sendfile by default as it's slightly faster. On - * macOS, we avoid fcopyfile by default because it's slightly slower. - */ -#undef USE_FCOPYFILE -#define USE_SENDFILE 1 - -#ifdef _WIN32 - -#ifdef CLAR_WIN32_LONGPATHS -# define CLAR_MAX_PATH 4096 -#else -# define CLAR_MAX_PATH MAX_PATH -#endif - -#define RM_RETRY_COUNT 5 -#define RM_RETRY_DELAY 10 - -#ifdef __MINGW32__ - -/* These security-enhanced functions are not available - * in MinGW, so just use the vanilla ones */ -#define wcscpy_s(a, b, c) wcscpy((a), (c)) -#define wcscat_s(a, b, c) wcscat((a), (c)) - -#endif /* __MINGW32__ */ - -static int -fs__dotordotdot(WCHAR *_tocheck) -{ - return _tocheck[0] == '.' && - (_tocheck[1] == '\0' || - (_tocheck[1] == '.' && _tocheck[2] == '\0')); -} - -static int -fs_rmdir_rmdir(WCHAR *_wpath) -{ - unsigned retries = 1; - - while (!RemoveDirectoryW(_wpath)) { - /* Only retry when we have retries remaining, and the - * error was ERROR_DIR_NOT_EMPTY. */ - if (retries++ > RM_RETRY_COUNT || - ERROR_DIR_NOT_EMPTY != GetLastError()) - return -1; - - /* Give whatever has a handle to a child item some time - * to release it before trying again */ - Sleep(RM_RETRY_DELAY * retries * retries); - } - - return 0; -} - -static void translate_path(WCHAR *path, size_t path_size) -{ - size_t path_len, i; - - if (wcsncmp(path, L"\\\\?\\", 4) == 0) - return; - - path_len = wcslen(path); - cl_assert(path_size > path_len + 4); - - for (i = path_len; i > 0; i--) { - WCHAR c = path[i - 1]; - - if (c == L'/') - path[i + 3] = L'\\'; - else - path[i + 3] = path[i - 1]; - } - - path[0] = L'\\'; - path[1] = L'\\'; - path[2] = L'?'; - path[3] = L'\\'; - path[path_len + 4] = L'\0'; -} - -static void -fs_rmdir_helper(WCHAR *_wsource) -{ - WCHAR buffer[CLAR_MAX_PATH]; - HANDLE find_handle; - WIN32_FIND_DATAW find_data; - size_t buffer_prefix_len; - - /* Set up the buffer and capture the length */ - wcscpy_s(buffer, CLAR_MAX_PATH, _wsource); - translate_path(buffer, CLAR_MAX_PATH); - wcscat_s(buffer, CLAR_MAX_PATH, L"\\"); - buffer_prefix_len = wcslen(buffer); - - /* FindFirstFile needs a wildcard to match multiple items */ - wcscat_s(buffer, CLAR_MAX_PATH, L"*"); - find_handle = FindFirstFileW(buffer, &find_data); - cl_assert(INVALID_HANDLE_VALUE != find_handle); - - do { - /* FindFirstFile/FindNextFile gives back . and .. - * entries at the beginning */ - if (fs__dotordotdot(find_data.cFileName)) - continue; - - wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName); - - if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) - fs_rmdir_helper(buffer); - else { - /* If set, the +R bit must be cleared before deleting */ - if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes) - cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY)); - - cl_assert(DeleteFileW(buffer)); - } - } - while (FindNextFileW(find_handle, &find_data)); - - /* Ensure that we successfully completed the enumeration */ - cl_assert(ERROR_NO_MORE_FILES == GetLastError()); - - /* Close the find handle */ - FindClose(find_handle); - - /* Now that the directory is empty, remove it */ - cl_assert(0 == fs_rmdir_rmdir(_wsource)); -} - -static int -fs_rm_wait(WCHAR *_wpath) -{ - unsigned retries = 1; - DWORD last_error; - - do { - if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath)) - last_error = GetLastError(); - else - last_error = ERROR_SUCCESS; - - /* Is the item gone? */ - if (ERROR_FILE_NOT_FOUND == last_error || - ERROR_PATH_NOT_FOUND == last_error) - return 0; - - Sleep(RM_RETRY_DELAY * retries * retries); - } - while (retries++ <= RM_RETRY_COUNT); - - return -1; -} - -static void -fs_rm(const char *_source) -{ - WCHAR wsource[CLAR_MAX_PATH]; - DWORD attrs; - - /* The input path is UTF-8. Convert it to wide characters - * for use with the Windows API */ - cl_assert(MultiByteToWideChar(CP_UTF8, - MB_ERR_INVALID_CHARS, - _source, - -1, /* Indicates NULL termination */ - wsource, - CLAR_MAX_PATH)); - - translate_path(wsource, CLAR_MAX_PATH); - - /* Does the item exist? If not, we have no work to do */ - attrs = GetFileAttributesW(wsource); - - if (INVALID_FILE_ATTRIBUTES == attrs) - return; - - if (FILE_ATTRIBUTE_DIRECTORY & attrs) - fs_rmdir_helper(wsource); - else { - /* The item is a file. Strip the +R bit */ - if (FILE_ATTRIBUTE_READONLY & attrs) - cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY)); - - cl_assert(DeleteFileW(wsource)); - } - - /* Wait for the DeleteFile or RemoveDirectory call to complete */ - cl_assert(0 == fs_rm_wait(wsource)); -} - -static void -fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest) -{ - WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH]; - HANDLE find_handle; - WIN32_FIND_DATAW find_data; - size_t buf_source_prefix_len, buf_dest_prefix_len; - - wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource); - wcscat_s(buf_source, CLAR_MAX_PATH, L"\\"); - translate_path(buf_source, CLAR_MAX_PATH); - buf_source_prefix_len = wcslen(buf_source); - - wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest); - wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\"); - translate_path(buf_dest, CLAR_MAX_PATH); - buf_dest_prefix_len = wcslen(buf_dest); - - /* Get an enumerator for the items in the source. */ - wcscat_s(buf_source, CLAR_MAX_PATH, L"*"); - find_handle = FindFirstFileW(buf_source, &find_data); - cl_assert(INVALID_HANDLE_VALUE != find_handle); - - /* Create the target directory. */ - cl_assert(CreateDirectoryW(_wdest, NULL)); - - do { - /* FindFirstFile/FindNextFile gives back . and .. - * entries at the beginning */ - if (fs__dotordotdot(find_data.cFileName)) - continue; - - wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName); - wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName); - - if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) - fs_copydir_helper(buf_source, buf_dest); - else - cl_assert(CopyFileW(buf_source, buf_dest, TRUE)); - } - while (FindNextFileW(find_handle, &find_data)); - - /* Ensure that we successfully completed the enumeration */ - cl_assert(ERROR_NO_MORE_FILES == GetLastError()); - - /* Close the find handle */ - FindClose(find_handle); -} - -static void -fs_copy(const char *_source, const char *_dest) -{ - WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH]; - DWORD source_attrs, dest_attrs; - HANDLE find_handle; - WIN32_FIND_DATAW find_data; - - /* The input paths are UTF-8. Convert them to wide characters - * for use with the Windows API. */ - cl_assert(MultiByteToWideChar(CP_UTF8, - MB_ERR_INVALID_CHARS, - _source, - -1, - wsource, - CLAR_MAX_PATH)); - - cl_assert(MultiByteToWideChar(CP_UTF8, - MB_ERR_INVALID_CHARS, - _dest, - -1, - wdest, - CLAR_MAX_PATH)); - - translate_path(wsource, CLAR_MAX_PATH); - translate_path(wdest, CLAR_MAX_PATH); - - /* Check the source for existence */ - source_attrs = GetFileAttributesW(wsource); - cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs); - - /* Check the target for existence */ - dest_attrs = GetFileAttributesW(wdest); - - if (INVALID_FILE_ATTRIBUTES != dest_attrs) { - /* Target exists; append last path part of source to target. - * Use FindFirstFile to parse the path */ - find_handle = FindFirstFileW(wsource, &find_data); - cl_assert(INVALID_HANDLE_VALUE != find_handle); - wcscat_s(wdest, CLAR_MAX_PATH, L"\\"); - wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName); - FindClose(find_handle); - - /* Check the new target for existence */ - cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest)); - } - - if (FILE_ATTRIBUTE_DIRECTORY & source_attrs) - fs_copydir_helper(wsource, wdest); - else - cl_assert(CopyFileW(wsource, wdest, TRUE)); -} - -void -cl_fs_cleanup(void) -{ - fs_rm(fixture_path(_clar_path, "*")); -} - -#else - -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(__linux__) -# include -#endif - -#if defined(__APPLE__) -# include -#endif - -static void basename_r(const char **out, int *out_len, const char *in) -{ - size_t in_len = strlen(in), start_pos; - - for (in_len = strlen(in); in_len; in_len--) { - if (in[in_len - 1] != '/') - break; - } - - for (start_pos = in_len; start_pos; start_pos--) { - if (in[start_pos - 1] == '/') - break; - } - - cl_assert(in_len - start_pos < INT_MAX); - - if (in_len - start_pos > 0) { - *out = &in[start_pos]; - *out_len = (in_len - start_pos); - } else { - *out = "/"; - *out_len = 1; - } -} - -static char *joinpath(const char *dir, const char *base, int base_len) -{ - char *out; - int len; - - if (base_len == -1) { - size_t bl = strlen(base); - - cl_assert(bl < INT_MAX); - base_len = (int)bl; - } - - len = strlen(dir) + base_len + 2; - cl_assert(len > 0); - - cl_assert(out = malloc(len)); - cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len); - - return out; -} - -static void -fs_copydir_helper(const char *source, const char *dest, int dest_mode) -{ - DIR *source_dir; - struct dirent *d; - - mkdir(dest, dest_mode); - - cl_assert_(source_dir = opendir(source), "Could not open source dir"); - while ((d = (errno = 0, readdir(source_dir))) != NULL) { - char *child; - - if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) - continue; - - child = joinpath(source, d->d_name, -1); - fs_copy(child, dest); - free(child); - } - - cl_assert_(errno == 0, "Failed to iterate source dir"); - - closedir(source_dir); -} - -static void -fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode) -{ - int in, out; - - cl_must_pass((in = open(source, O_RDONLY))); - cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode))); - -#if USE_FCOPYFILE && defined(__APPLE__) - ((void)(source_len)); /* unused */ - cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA)); -#elif USE_SENDFILE && defined(__linux__) - { - ssize_t ret = 0; - - while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) { - source_len -= (size_t)ret; - } - cl_assert(ret >= 0); - } -#else - { - char buf[131072]; - ssize_t ret; - - ((void)(source_len)); /* unused */ - - while ((ret = read(in, buf, sizeof(buf))) > 0) { - size_t len = (size_t)ret; - - while (len && (ret = write(out, buf, len)) > 0) { - cl_assert(ret <= (ssize_t)len); - len -= ret; - } - cl_assert(ret >= 0); - } - cl_assert(ret == 0); - } -#endif - - close(in); - close(out); -} - -static void -fs_copy(const char *source, const char *_dest) -{ - char *dbuf = NULL; - const char *dest = NULL; - struct stat source_st, dest_st; - - cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source"); - - if (lstat(_dest, &dest_st) == 0) { - const char *base; - int base_len; - - /* Target exists and is directory; append basename */ - cl_assert(S_ISDIR(dest_st.st_mode)); - - basename_r(&base, &base_len, source); - cl_assert(base_len < INT_MAX); - - dbuf = joinpath(_dest, base, base_len); - dest = dbuf; - } else if (errno != ENOENT) { - cl_fail("Cannot copy; cannot stat destination"); - } else { - dest = _dest; - } - - if (S_ISDIR(source_st.st_mode)) { - fs_copydir_helper(source, dest, source_st.st_mode); - } else { - fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode); - } - - free(dbuf); -} - -static void -fs_rmdir_helper(const char *path) -{ - DIR *dir; - struct dirent *d; - - cl_assert_(dir = opendir(path), "Could not open dir"); - while ((d = (errno = 0, readdir(dir))) != NULL) { - char *child; - - if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) - continue; - - child = joinpath(path, d->d_name, -1); - fs_rm(child); - free(child); - } - - cl_assert_(errno == 0, "Failed to iterate source dir"); - closedir(dir); - - cl_must_pass_(rmdir(path), "Could not remove directory"); -} - -static void -fs_rm(const char *path) -{ - struct stat st; - - if (lstat(path, &st)) { - if (errno == ENOENT) - return; - - cl_fail("Cannot copy; cannot stat destination"); - } - - if (S_ISDIR(st.st_mode)) { - fs_rmdir_helper(path); - } else { - cl_must_pass(unlink(path)); - } -} - -void -cl_fs_cleanup(void) -{ - clar_unsandbox(); - clar_sandbox(); -} -#endif diff --git a/tests/clar/print.h b/tests/clar/print.h deleted file mode 100644 index dbfd27655..000000000 --- a/tests/clar/print.h +++ /dev/null @@ -1,200 +0,0 @@ -/* clap: clar protocol, the traditional clar output format */ - -static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names) -{ - (void)test_count; - printf("Loaded %d suites: %s\n", (int)suite_count, suite_names); - printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n"); -} - -static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count) -{ - (void)test_count; - (void)suite_count; - (void)error_count; - - printf("\n\n"); - clar_report_all(); -} - -static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error) -{ - printf(" %d) Failure:\n", num); - - printf("%s::%s [%s:%"PRIuZ"]\n", - report->suite, - report->test, - error->file, - error->line_number); - - printf(" %s\n", error->error_msg); - - if (error->description != NULL) - printf(" %s\n", error->description); - - printf("\n"); - fflush(stdout); -} - -static void clar_print_clap_ontest(const char *test_name, int test_number, enum cl_test_status status) -{ - (void)test_name; - (void)test_number; - - switch(status) { - case CL_TEST_OK: printf("."); break; - case CL_TEST_FAILURE: printf("F"); break; - case CL_TEST_SKIP: printf("S"); break; - case CL_TEST_NOTRUN: printf("N"); break; - } - - fflush(stdout); -} - -static void clar_print_clap_onsuite(const char *suite_name, int suite_index) -{ - if (_clar.report_suite_names) - printf("\n%s", suite_name); - - (void)suite_index; -} - -static void clar_print_clap_onabort(const char *fmt, va_list arg) -{ - vfprintf(stderr, fmt, arg); -} - -/* tap: test anywhere protocol format */ - -static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names) -{ - (void)test_count; - (void)suite_count; - (void)suite_names; - printf("TAP version 13\n"); -} - -static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count) -{ - (void)suite_count; - (void)error_count; - - printf("1..%d\n", test_count); -} - -static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error) -{ - (void)num; - (void)report; - (void)error; -} - -static void print_escaped(const char *str) -{ - char *c; - - while ((c = strchr(str, '\'')) != NULL) { - printf("%.*s", (int)(c - str), str); - printf("''"); - str = c + 1; - } - - printf("%s", str); -} - -static void clar_print_tap_ontest(const char *test_name, int test_number, enum cl_test_status status) -{ - const struct clar_error *error = _clar.last_report->errors; - - (void)test_name; - (void)test_number; - - switch(status) { - case CL_TEST_OK: - printf("ok %d - %s::%s\n", test_number, _clar.active_suite, test_name); - break; - case CL_TEST_FAILURE: - printf("not ok %d - %s::%s\n", test_number, _clar.active_suite, test_name); - - printf(" ---\n"); - printf(" reason: |\n"); - printf(" %s\n", error->error_msg); - - if (error->description) - printf(" %s\n", error->description); - - printf(" at:\n"); - printf(" file: '"); print_escaped(error->file); printf("'\n"); - printf(" line: %" PRIuZ "\n", error->line_number); - printf(" function: '%s'\n", error->function); - printf(" ---\n"); - - break; - case CL_TEST_SKIP: - case CL_TEST_NOTRUN: - printf("ok %d - # SKIP %s::%s\n", test_number, _clar.active_suite, test_name); - break; - } - - fflush(stdout); -} - -static void clar_print_tap_onsuite(const char *suite_name, int suite_index) -{ - printf("# start of suite %d: %s\n", suite_index, suite_name); -} - -static void clar_print_tap_onabort(const char *fmt, va_list arg) -{ - printf("Bail out! "); - vprintf(fmt, arg); - fflush(stdout); -} - -/* indirection between protocol output selection */ - -#define PRINT(FN, ...) do { \ - switch (_clar.output_format) { \ - case CL_OUTPUT_CLAP: \ - clar_print_clap_##FN (__VA_ARGS__); \ - break; \ - case CL_OUTPUT_TAP: \ - clar_print_tap_##FN (__VA_ARGS__); \ - break; \ - default: \ - abort(); \ - } \ - } while (0) - -static void clar_print_init(int test_count, int suite_count, const char *suite_names) -{ - PRINT(init, test_count, suite_count, suite_names); -} - -static void clar_print_shutdown(int test_count, int suite_count, int error_count) -{ - PRINT(shutdown, test_count, suite_count, error_count); -} - -static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error) -{ - PRINT(error, num, report, error); -} - -static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status status) -{ - PRINT(ontest, test_name, test_number, status); -} - -static void clar_print_onsuite(const char *suite_name, int suite_index) -{ - PRINT(onsuite, suite_name, suite_index); -} - -static void clar_print_onabort(const char *msg, ...) -{ - va_list argp; - va_start(argp, msg); - PRINT(onabort, msg, argp); - va_end(argp); -} diff --git a/tests/clar/sandbox.h b/tests/clar/sandbox.h deleted file mode 100644 index 0ba147962..000000000 --- a/tests/clar/sandbox.h +++ /dev/null @@ -1,154 +0,0 @@ -#ifdef __APPLE__ -#include -#endif - -static char _clar_path[4096 + 1]; - -static int -is_valid_tmp_path(const char *path) -{ - STAT_T st; - - if (stat(path, &st) != 0) - return 0; - - if (!S_ISDIR(st.st_mode)) - return 0; - - return (access(path, W_OK) == 0); -} - -static int -find_tmp_path(char *buffer, size_t length) -{ -#ifndef _WIN32 - static const size_t var_count = 5; - static const char *env_vars[] = { - "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE" - }; - - size_t i; - - for (i = 0; i < var_count; ++i) { - const char *env = getenv(env_vars[i]); - if (!env) - continue; - - if (is_valid_tmp_path(env)) { -#ifdef __APPLE__ - if (length >= PATH_MAX && realpath(env, buffer) != NULL) - return 0; -#endif - strncpy(buffer, env, length - 1); - buffer[length - 1] = '\0'; - return 0; - } - } - - /* If the environment doesn't say anything, try to use /tmp */ - if (is_valid_tmp_path("/tmp")) { -#ifdef __APPLE__ - if (length >= PATH_MAX && realpath("/tmp", buffer) != NULL) - return 0; -#endif - strncpy(buffer, "/tmp", length - 1); - buffer[length - 1] = '\0'; - return 0; - } - -#else - DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length); - if (env_len > 0 && env_len < (DWORD)length) - return 0; - - if (GetTempPath((DWORD)length, buffer)) - return 0; -#endif - - /* This system doesn't like us, try to use the current directory */ - if (is_valid_tmp_path(".")) { - strncpy(buffer, ".", length - 1); - buffer[length - 1] = '\0'; - return 0; - } - - return -1; -} - -static void clar_unsandbox(void) -{ - if (_clar_path[0] == '\0') - return; - - cl_must_pass(chdir("..")); - - fs_rm(_clar_path); -} - -static int build_sandbox_path(void) -{ -#ifdef CLAR_TMPDIR - const char path_tail[] = CLAR_TMPDIR "_XXXXXX"; -#else - const char path_tail[] = "clar_tmp_XXXXXX"; -#endif - - size_t len; - - if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0) - return -1; - - len = strlen(_clar_path); - -#ifdef _WIN32 - { /* normalize path to POSIX forward slashes */ - size_t i; - for (i = 0; i < len; ++i) { - if (_clar_path[i] == '\\') - _clar_path[i] = '/'; - } - } -#endif - - if (_clar_path[len - 1] != '/') { - _clar_path[len++] = '/'; - } - - strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len); - -#if defined(__MINGW32__) - if (_mktemp(_clar_path) == NULL) - return -1; - - if (mkdir(_clar_path, 0700) != 0) - return -1; -#elif defined(_WIN32) - if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0) - return -1; - - if (mkdir(_clar_path, 0700) != 0) - return -1; -#else - if (mkdtemp(_clar_path) == NULL) - return -1; -#endif - - return 0; -} - -static int clar_sandbox(void) -{ - if (_clar_path[0] == '\0' && build_sandbox_path() < 0) - return -1; - - if (chdir(_clar_path) != 0) - return -1; - - return 0; -} - -const char *clar_sandbox_path(void) -{ - return _clar_path; -} - diff --git a/tests/clar/summary.h b/tests/clar/summary.h deleted file mode 100644 index 6279f5057..000000000 --- a/tests/clar/summary.h +++ /dev/null @@ -1,134 +0,0 @@ - -#include -#include - -static int clar_summary_close_tag( - struct clar_summary *summary, const char *tag, int indent) -{ - const char *indt; - - if (indent == 0) indt = ""; - else if (indent == 1) indt = "\t"; - else indt = "\t\t"; - - return fprintf(summary->fp, "%s\n", indt, tag); -} - -static int clar_summary_testsuites(struct clar_summary *summary) -{ - return fprintf(summary->fp, "\n"); -} - -static int clar_summary_testsuite(struct clar_summary *summary, - int idn, const char *name, const char *pkg, time_t timestamp, - double elapsed, int test_count, int fail_count, int error_count) -{ - struct tm *tm = localtime(×tamp); - char iso_dt[20]; - - if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0) - return -1; - - return fprintf(summary->fp, "\t\n", - idn, name, pkg, iso_dt, elapsed, test_count, fail_count, error_count); -} - -static int clar_summary_testcase(struct clar_summary *summary, - const char *name, const char *classname, double elapsed) -{ - return fprintf(summary->fp, - "\t\t\n", - name, classname, elapsed); -} - -static int clar_summary_failure(struct clar_summary *summary, - const char *type, const char *message, const char *desc) -{ - return fprintf(summary->fp, - "\t\t\t\n", - type, message, desc); -} - -struct clar_summary *clar_summary_init(const char *filename) -{ - struct clar_summary *summary; - FILE *fp; - - if ((fp = fopen(filename, "w")) == NULL) - return NULL; - - if ((summary = malloc(sizeof(struct clar_summary))) == NULL) { - fclose(fp); - return NULL; - } - - summary->filename = filename; - summary->fp = fp; - - return summary; -} - -int clar_summary_shutdown(struct clar_summary *summary) -{ - struct clar_report *report; - const char *last_suite = NULL; - - if (clar_summary_testsuites(summary) < 0) - goto on_error; - - report = _clar.reports; - while (report != NULL) { - struct clar_error *error = report->errors; - - if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) { - if (clar_summary_testsuite(summary, 0, report->suite, "", - time(NULL), 0, _clar.tests_ran, _clar.total_errors, 0) < 0) - goto on_error; - } - - last_suite = report->suite; - - clar_summary_testcase(summary, report->test, "what", 0); - - while (error != NULL) { - if (clar_summary_failure(summary, "assert", - error->error_msg, error->description) < 0) - goto on_error; - - error = error->next; - } - - if (clar_summary_close_tag(summary, "testcase", 2) < 0) - goto on_error; - - report = report->next; - - if (!report || strcmp(last_suite, report->suite) != 0) { - if (clar_summary_close_tag(summary, "testsuite", 1) < 0) - goto on_error; - } - } - - if (clar_summary_close_tag(summary, "testsuites", 0) < 0 || - fclose(summary->fp) != 0) - goto on_error; - - printf("written summary file to %s\n", summary->filename); - - free(summary); - return 0; - -on_error: - fclose(summary->fp); - free(summary); - return -1; -} diff --git a/tests/clar_libgit2.c b/tests/clar_libgit2.c deleted file mode 100644 index 55a09d111..000000000 --- a/tests/clar_libgit2.c +++ /dev/null @@ -1,623 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "fs_path.h" -#include "git2/sys/repository.h" - -void cl_git_report_failure( - int error, int expected, const char *file, const char *func, int line, const char *fncall) -{ - char msg[4096]; - const git_error *last = git_error_last(); - - if (expected) - p_snprintf(msg, 4096, "error %d (expected %d) - %s", - error, expected, last ? last->message : ""); - else if (error || last) - p_snprintf(msg, 4096, "error %d - %s", - error, last ? last->message : ""); - else - p_snprintf(msg, 4096, "no error, expected non-zero return"); - - clar__assert(0, file, func, line, fncall, msg, 1); -} - -void cl_git_mkfile(const char *filename, const char *content) -{ - int fd; - - fd = p_creat(filename, 0666); - cl_assert(fd != -1); - - if (content) { - cl_must_pass(p_write(fd, content, strlen(content))); - } else { - cl_must_pass(p_write(fd, filename, strlen(filename))); - cl_must_pass(p_write(fd, "\n", 1)); - } - - cl_must_pass(p_close(fd)); -} - -void cl_git_write2file( - const char *path, const char *content, size_t content_len, - int flags, unsigned int mode) -{ - int fd; - cl_assert(path && content); - cl_assert((fd = p_open(path, flags, mode)) >= 0); - if (!content_len) - content_len = strlen(content); - cl_must_pass(p_write(fd, content, content_len)); - cl_must_pass(p_close(fd)); -} - -void cl_git_append2file(const char *path, const char *content) -{ - cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_APPEND, 0644); -} - -void cl_git_rewritefile(const char *path, const char *content) -{ - cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_TRUNC, 0644); -} - -void cl_git_rmfile(const char *filename) -{ - cl_must_pass(p_unlink(filename)); -} - -char *cl_getenv(const char *name) -{ - git_str out = GIT_STR_INIT; - int error = git__getenv(&out, name); - - cl_assert(error >= 0 || error == GIT_ENOTFOUND); - - if (error == GIT_ENOTFOUND) - return NULL; - - if (out.size == 0) { - char *dup = git__strdup(""); - cl_assert(dup); - - return dup; - } - - return git_str_detach(&out); -} - -bool cl_is_env_set(const char *name) -{ - char *env = cl_getenv(name); - bool result = (env != NULL); - git__free(env); - return result; -} - -#ifdef GIT_WIN32 - -#include "win32/utf-conv.h" - -int cl_setenv(const char *name, const char *value) -{ - wchar_t *wide_name, *wide_value = NULL; - - cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0); - - if (value) { - cl_assert(git__utf8_to_16_alloc(&wide_value, value) >= 0); - cl_assert(SetEnvironmentVariableW(wide_name, wide_value)); - } else { - /* Windows XP returns 0 (failed) when passing NULL for lpValue when - * lpName does not exist in the environment block. This behavior - * seems to have changed in later versions. Don't check the return value - * of SetEnvironmentVariable when passing NULL for lpValue. */ - SetEnvironmentVariableW(wide_name, NULL); - } - - git__free(wide_name); - git__free(wide_value); - return 0; -} - -/* This function performs retries on calls to MoveFile in order - * to provide enhanced reliability in the face of antivirus - * agents that may be scanning the source (or in the case that - * the source is a directory, a child of the source). */ -int cl_rename(const char *source, const char *dest) -{ - git_win32_path source_utf16; - git_win32_path dest_utf16; - unsigned retries = 1; - - cl_assert(git_win32_path_from_utf8(source_utf16, source) >= 0); - cl_assert(git_win32_path_from_utf8(dest_utf16, dest) >= 0); - - while (!MoveFileW(source_utf16, dest_utf16)) { - /* Only retry if the error is ERROR_ACCESS_DENIED; - * this may indicate that an antivirus agent is - * preventing the rename from source to target */ - if (retries > 5 || - ERROR_ACCESS_DENIED != GetLastError()) - return -1; - - /* With 5 retries and a coefficient of 10ms, the maximum - * delay here is 550 ms */ - Sleep(10 * retries * retries); - retries++; - } - - return 0; -} - -#else - -#include - -int cl_setenv(const char *name, const char *value) -{ - return (value == NULL) ? unsetenv(name) : setenv(name, value, 1); -} - -int cl_rename(const char *source, const char *dest) -{ - return p_rename(source, dest); -} - -#endif - -static const char *_cl_sandbox = NULL; -static git_repository *_cl_repo = NULL; - -git_repository *cl_git_sandbox_init(const char *sandbox) -{ - /* Get the name of the sandbox folder which will be created */ - const char *basename = cl_fixture_basename(sandbox); - - /* Copy the whole sandbox folder from our fixtures to our test sandbox - * area. After this it can be accessed with `./sandbox` - */ - cl_fixture_sandbox(sandbox); - _cl_sandbox = sandbox; - - cl_git_pass(p_chdir(basename)); - - /* If this is not a bare repo, then rename `sandbox/.gitted` to - * `sandbox/.git` which must be done since we cannot store a folder - * named `.git` inside the fixtures folder of our libgit2 repo. - */ - if (p_access(".gitted", F_OK) == 0) - cl_git_pass(cl_rename(".gitted", ".git")); - - /* If we have `gitattributes`, rename to `.gitattributes`. This may - * be necessary if we don't want the attributes to be applied in the - * libgit2 repo, but just during testing. - */ - if (p_access("gitattributes", F_OK) == 0) - cl_git_pass(cl_rename("gitattributes", ".gitattributes")); - - /* As with `gitattributes`, we may need `gitignore` just for testing. */ - if (p_access("gitignore", F_OK) == 0) - cl_git_pass(cl_rename("gitignore", ".gitignore")); - - cl_git_pass(p_chdir("..")); - - /* Now open the sandbox repository and make it available for tests */ - cl_git_pass(git_repository_open(&_cl_repo, basename)); - - /* Adjust configs after copying to new filesystem */ - cl_git_pass(git_repository_reinit_filesystem(_cl_repo, 0)); - - return _cl_repo; -} - -git_repository *cl_git_sandbox_init_new(const char *sandbox) -{ - cl_git_pass(git_repository_init(&_cl_repo, sandbox, false)); - _cl_sandbox = sandbox; - - return _cl_repo; -} - -git_repository *cl_git_sandbox_reopen(void) -{ - if (_cl_repo) { - git_repository_free(_cl_repo); - _cl_repo = NULL; - - cl_git_pass(git_repository_open( - &_cl_repo, cl_fixture_basename(_cl_sandbox))); - } - - return _cl_repo; -} - -void cl_git_sandbox_cleanup(void) -{ - if (_cl_repo) { - git_repository_free(_cl_repo); - _cl_repo = NULL; - } - if (_cl_sandbox) { - cl_fixture_cleanup(_cl_sandbox); - _cl_sandbox = NULL; - } -} - -bool cl_toggle_filemode(const char *filename) -{ - struct stat st1, st2; - - cl_must_pass(p_stat(filename, &st1)); - cl_must_pass(p_chmod(filename, st1.st_mode ^ 0100)); - cl_must_pass(p_stat(filename, &st2)); - - return (st1.st_mode != st2.st_mode); -} - -bool cl_is_chmod_supported(void) -{ - static int _is_supported = -1; - - if (_is_supported < 0) { - cl_git_mkfile("filemode.t", "Test if filemode can be modified"); - _is_supported = cl_toggle_filemode("filemode.t"); - cl_must_pass(p_unlink("filemode.t")); - } - - return _is_supported; -} - -const char* cl_git_fixture_url(const char *fixturename) -{ - return cl_git_path_url(cl_fixture(fixturename)); -} - -const char* cl_git_path_url(const char *path) -{ - static char url[4096 + 1]; - - const char *in_buf; - git_str path_buf = GIT_STR_INIT; - git_str url_buf = GIT_STR_INIT; - - cl_git_pass(git_fs_path_prettify_dir(&path_buf, path, NULL)); - cl_git_pass(git_str_puts(&url_buf, "file://")); - -#ifdef GIT_WIN32 - /* - * A FILE uri matches the following format: file://[host]/path - * where "host" can be empty and "path" is an absolute path to the resource. - * - * In this test, no hostname is used, but we have to ensure the leading triple slashes: - * - * *nix: file:///usr/home/... - * Windows: file:///C:/Users/... - */ - cl_git_pass(git_str_putc(&url_buf, '/')); -#endif - - in_buf = git_str_cstr(&path_buf); - - /* - * A very hacky Url encoding that only takes care of escaping the spaces - */ - while (*in_buf) { - if (*in_buf == ' ') - cl_git_pass(git_str_puts(&url_buf, "%20")); - else - cl_git_pass(git_str_putc(&url_buf, *in_buf)); - - in_buf++; - } - - cl_assert(url_buf.size < sizeof(url) - 1); - - strncpy(url, git_str_cstr(&url_buf), sizeof(url) - 1); - url[sizeof(url) - 1] = '\0'; - git_str_dispose(&url_buf); - git_str_dispose(&path_buf); - return url; -} - -const char *cl_git_sandbox_path(int is_dir, ...) -{ - const char *path = NULL; - static char _temp[GIT_PATH_MAX]; - git_str buf = GIT_STR_INIT; - va_list arg; - - cl_git_pass(git_str_sets(&buf, clar_sandbox_path())); - - va_start(arg, is_dir); - - while ((path = va_arg(arg, const char *)) != NULL) { - cl_git_pass(git_str_joinpath(&buf, buf.ptr, path)); - } - va_end(arg); - - cl_git_pass(git_fs_path_prettify(&buf, buf.ptr, NULL)); - if (is_dir) - git_fs_path_to_dir(&buf); - - /* make sure we won't truncate */ - cl_assert(git_str_len(&buf) < sizeof(_temp)); - git_str_copy_cstr(_temp, sizeof(_temp), &buf); - - git_str_dispose(&buf); - - return _temp; -} - -typedef struct { - const char *filename; - size_t filename_len; -} remove_data; - -static int remove_placeholders_recurs(void *_data, git_str *path) -{ - remove_data *data = (remove_data *)_data; - size_t pathlen; - - if (git_fs_path_isdir(path->ptr) == true) - return git_fs_path_direach(path, 0, remove_placeholders_recurs, data); - - pathlen = path->size; - - if (pathlen < data->filename_len) - return 0; - - /* if path ends in '/'+filename (or equals filename) */ - if (!strcmp(data->filename, path->ptr + pathlen - data->filename_len) && - (pathlen == data->filename_len || - path->ptr[pathlen - data->filename_len - 1] == '/')) - return p_unlink(path->ptr); - - return 0; -} - -int cl_git_remove_placeholders(const char *directory_path, const char *filename) -{ - int error; - remove_data data; - git_str buffer = GIT_STR_INIT; - - if (git_fs_path_isdir(directory_path) == false) - return -1; - - if (git_str_sets(&buffer, directory_path) < 0) - return -1; - - data.filename = filename; - data.filename_len = strlen(filename); - - error = remove_placeholders_recurs(&data, &buffer); - - git_str_dispose(&buffer); - - return error; -} - -#define CL_COMMIT_NAME "Libgit2 Tester" -#define CL_COMMIT_EMAIL "libgit2-test@github.com" -#define CL_COMMIT_MSG "Test commit of tree " - -void cl_repo_commit_from_index( - git_oid *out, - git_repository *repo, - git_signature *sig, - git_time_t time, - const char *msg) -{ - git_index *index; - git_oid commit_id, tree_id; - git_object *parent = NULL; - git_reference *ref = NULL; - git_tree *tree = NULL; - char buf[128]; - int free_sig = (sig == NULL); - - /* it is fine if looking up HEAD fails - we make this the first commit */ - git_revparse_ext(&parent, &ref, repo, "HEAD"); - - /* write the index content as a tree */ - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_write_tree(&tree_id, index)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_tree_lookup(&tree, repo, &tree_id)); - - if (sig) - cl_assert(sig->name && sig->email); - else if (!time) - cl_git_pass(git_signature_now(&sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL)); - else - cl_git_pass(git_signature_new( - &sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL, time, 0)); - - if (!msg) { - strcpy(buf, CL_COMMIT_MSG); - git_oid_tostr(buf + strlen(CL_COMMIT_MSG), - sizeof(buf) - strlen(CL_COMMIT_MSG), &tree_id); - msg = buf; - } - - cl_git_pass(git_commit_create_v( - &commit_id, repo, ref ? git_reference_name(ref) : "HEAD", - sig, sig, NULL, msg, tree, parent ? 1 : 0, parent)); - - if (out) - git_oid_cpy(out, &commit_id); - - git_object_free(parent); - git_reference_free(ref); - if (free_sig) - git_signature_free(sig); - git_tree_free(tree); -} - -void cl_repo_set_bool(git_repository *repo, const char *cfg, int value) -{ - git_config *config; - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_bool(config, cfg, value != 0)); - git_config_free(config); -} - -int cl_repo_get_bool(git_repository *repo, const char *cfg) -{ - int val = 0; - git_config *config; - cl_git_pass(git_repository_config(&config, repo)); - if (git_config_get_bool(&val, config, cfg) < 0) - git_error_clear(); - git_config_free(config); - return val; -} - -void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value) -{ - git_config *config; - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_string(config, cfg, value)); - git_config_free(config); -} - -/* this is essentially the code from git__unescape modified slightly */ -static size_t strip_cr_from_buf(char *start, size_t len) -{ - char *scan, *trail, *end = start + len; - - for (scan = trail = start; scan < end; trail++, scan++) { - while (*scan == '\r') - scan++; /* skip '\r' */ - - if (trail != scan) - *trail = *scan; - } - - *trail = '\0'; - - return (trail - start); -} - -void 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) -{ - char buf[4000]; - ssize_t bytes, total_bytes = 0; - int fd = p_open(path, O_RDONLY | O_BINARY); - cl_assert(fd >= 0); - - if (expected_data && !expected_bytes) - expected_bytes = strlen(expected_data); - - while ((bytes = p_read(fd, buf, sizeof(buf))) != 0) { - clar__assert( - bytes > 0, file, func, line, "error reading from file", path, 1); - - if (ignore_cr) - bytes = strip_cr_from_buf(buf, bytes); - - if (memcmp(expected_data, buf, bytes) != 0) { - int pos; - for (pos = 0; pos < bytes && expected_data[pos] == buf[pos]; ++pos) - /* find differing byte offset */; - p_snprintf( - buf, sizeof(buf), "file content mismatch at byte %"PRIdZ, - (ssize_t)(total_bytes + pos)); - p_close(fd); - clar__fail(file, func, line, path, buf, 1); - } - - expected_data += bytes; - total_bytes += bytes; - } - - p_close(fd); - - clar__assert(!bytes, file, func, line, "error reading from file", path, 1); - clar__assert_equal(file, func, line, "mismatched file length", 1, "%"PRIuZ, - (size_t)expected_bytes, (size_t)total_bytes); -} - -static git_buf _cl_restore_home = GIT_BUF_INIT; - -void cl_fake_home_cleanup(void *payload) -{ - GIT_UNUSED(payload); - - if (_cl_restore_home.ptr) { - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, _cl_restore_home.ptr)); - git_buf_dispose(&_cl_restore_home); - } -} - -void cl_fake_home(void) -{ - git_str path = GIT_STR_INIT; - - cl_git_pass(git_libgit2_opts( - GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &_cl_restore_home)); - - cl_set_cleanup(cl_fake_home_cleanup, NULL); - - if (!git_fs_path_exists("home")) - cl_must_pass(p_mkdir("home", 0777)); - cl_git_pass(git_fs_path_prettify(&path, "home", NULL)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); - git_str_dispose(&path); -} - -void cl_sandbox_set_search_path_defaults(void) -{ - git_str path = GIT_STR_INIT; - - git_str_joinpath(&path, clar_sandbox_path(), "__config"); - - if (!git_fs_path_exists(path.ptr)) - cl_must_pass(p_mkdir(path.ptr, 0777)); - - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr); - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr); - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr); - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_PROGRAMDATA, path.ptr); - - git_str_dispose(&path); -} - -#ifdef GIT_WIN32 -bool cl_sandbox_supports_8dot3(void) -{ - git_str longpath = GIT_STR_INIT; - char *shortname; - bool supported; - - cl_git_pass( - git_str_joinpath(&longpath, clar_sandbox_path(), "longer_than_8dot3")); - - cl_git_write2file(longpath.ptr, "", 0, O_RDWR|O_CREAT, 0666); - shortname = git_win32_path_8dot3_name(longpath.ptr); - - supported = (shortname != NULL); - - git__free(shortname); - git_str_dispose(&longpath); - - return supported; -} -#endif - diff --git a/tests/clar_libgit2.h b/tests/clar_libgit2.h deleted file mode 100644 index e3b7bd9f8..000000000 --- a/tests/clar_libgit2.h +++ /dev/null @@ -1,236 +0,0 @@ -#ifndef __CLAR_LIBGIT2__ -#define __CLAR_LIBGIT2__ - -#include "clar.h" -#include -#include "common.h" -#include "posix.h" - -/** - * Replace for `clar_must_pass` that passes the last library error as the - * test failure message. - * - * Use this wrapper around all `git_` library calls that return error codes! - */ -#define cl_git_pass(expr) cl_git_expect((expr), 0, __FILE__, __func__, __LINE__) - -#define cl_git_fail_with(error, expr) cl_git_expect((expr), error, __FILE__, __func__, __LINE__) - -#define cl_git_expect(expr, expected, file, func, line) do { \ - int _lg2_error; \ - git_error_clear(); \ - if ((_lg2_error = (expr)) != expected) \ - cl_git_report_failure(_lg2_error, expected, file, func, line, "Function call failed: " #expr); \ - } while (0) - -/** - * Wrapper for `clar_must_fail` -- this one is - * just for consistency. Use with `git_` library - * calls that are supposed to fail! - */ -#define cl_git_fail(expr) do { \ - if ((expr) == 0) \ - git_error_clear(), \ - cl_git_report_failure(0, 0, __FILE__, __func__, __LINE__, "Function call succeeded: " #expr); \ - } while (0) - -/** - * Like cl_git_pass, only for Win32 error code conventions - */ -#define cl_win32_pass(expr) do { \ - int _win32_res; \ - if ((_win32_res = (expr)) == 0) { \ - git_error_set(GIT_ERROR_OS, "Returned: %d, system error code: %lu", _win32_res, GetLastError()); \ - cl_git_report_failure(_win32_res, 0, __FILE__, __func__, __LINE__, "System call failed: " #expr); \ - } \ - } while(0) - -/** - * Thread safe assertions; you cannot use `cl_git_report_failure` from a - * child thread since it will try to `longjmp` to abort and "the effect of - * a call to longjmp() where initialization of the jmp_buf structure was - * not performed in the calling thread is undefined." - * - * Instead, callers can provide a clar thread error context to a thread, - * which will populate and return it on failure. Callers can check the - * status with `cl_git_thread_check`. - */ -typedef struct { - int error; - const char *file; - const char *func; - int line; - const char *expr; - char error_msg[4096]; -} cl_git_thread_err; - -#ifdef GIT_THREADS -# define cl_git_thread_pass(threaderr, expr) cl_git_thread_pass_(threaderr, (expr), __FILE__, __func__, __LINE__) -#else -# define cl_git_thread_pass(threaderr, expr) cl_git_pass(expr) -#endif - -#define cl_git_thread_pass_(__threaderr, __expr, __file, __func, __line) do { \ - git_error_clear(); \ - if ((((cl_git_thread_err *)__threaderr)->error = (__expr)) != 0) { \ - const git_error *_last = git_error_last(); \ - ((cl_git_thread_err *)__threaderr)->file = __file; \ - ((cl_git_thread_err *)__threaderr)->func = __func; \ - ((cl_git_thread_err *)__threaderr)->line = __line; \ - ((cl_git_thread_err *)__threaderr)->expr = "Function call failed: " #__expr; \ - p_snprintf(((cl_git_thread_err *)__threaderr)->error_msg, 4096, "thread 0x%" PRIxZ " - error %d - %s", \ - git_thread_currentid(), ((cl_git_thread_err *)__threaderr)->error, \ - _last ? _last->message : ""); \ - git_thread_exit(__threaderr); \ - } \ - } while (0) - -GIT_INLINE(void) cl_git_thread_check(void *data) -{ - cl_git_thread_err *threaderr = (cl_git_thread_err *)data; - if (threaderr->error != 0) - clar__assert(0, threaderr->file, threaderr->func, threaderr->line, threaderr->expr, threaderr->error_msg, 1); -} - -void cl_git_report_failure(int, int, const char *, const char *, int, const char *); - -#define cl_assert_at_line(expr,file,func,line) \ - clar__assert((expr) != 0, file, func, line, "Expression is not true: " #expr, NULL, 1) - -GIT_INLINE(void) clar__assert_in_range( - int lo, int val, int hi, - const char *file, const char *func, int line, - const char *err, int should_abort) -{ - if (lo > val || hi < val) { - char buf[128]; - p_snprintf(buf, sizeof(buf), "%d not in [%d,%d]", val, lo, hi); - clar__fail(file, func, line, err, buf, should_abort); - } -} - -#define cl_assert_equal_sz(sz1,sz2) do { \ - size_t __sz1 = (size_t)(sz1), __sz2 = (size_t)(sz2); \ - clar__assert_equal(__FILE__,__func__,__LINE__,#sz1 " != " #sz2, 1, "%"PRIuZ, __sz1, __sz2); \ -} while (0) - -#define cl_assert_in_range(L,V,H) \ - clar__assert_in_range((L),(V),(H),__FILE__,__func__,__LINE__,"Range check: " #V " in [" #L "," #H "]", 1) - -#define cl_assert_equal_file(DATA,SIZE,PATH) \ - clar__assert_equal_file(DATA,SIZE,0,PATH,__FILE__,__func__,(int)__LINE__) - -#define cl_assert_equal_file_ignore_cr(DATA,SIZE,PATH) \ - clar__assert_equal_file(DATA,SIZE,1,PATH,__FILE__,__func__,(int)__LINE__) - -void clar__assert_equal_file( - const char *expected_data, - size_t expected_size, - int ignore_cr, - const char *path, - const char *file, - const char *func, - int line); - -GIT_INLINE(void) clar__assert_equal_oid( - const char *file, const char *func, int line, const char *desc, - const git_oid *one, const git_oid *two) -{ - if (git_oid_cmp(one, two)) { - char err[] = "\"........................................\" != \"........................................\""; - - git_oid_fmt(&err[1], one); - git_oid_fmt(&err[47], two); - - clar__fail(file, func, line, desc, err, 1); - } -} - -#define cl_assert_equal_oid(one, two) \ - clar__assert_equal_oid(__FILE__, __func__, __LINE__, \ - "OID mismatch: " #one " != " #two, (one), (two)) - -/* - * Some utility macros for building long strings - */ -#define REP4(STR) STR STR STR STR -#define REP15(STR) REP4(STR) REP4(STR) REP4(STR) STR STR STR -#define REP16(STR) REP4(REP4(STR)) -#define REP256(STR) REP16(REP16(STR)) -#define REP1024(STR) REP4(REP256(STR)) - -/* Write the contents of a buffer to disk */ -void cl_git_mkfile(const char *filename, const char *content); -void cl_git_append2file(const char *filename, const char *new_content); -void cl_git_rewritefile(const char *filename, const char *new_content); -void cl_git_write2file(const char *path, const char *data, - size_t datalen, int flags, unsigned int mode); -void cl_git_rmfile(const char *filename); - -bool cl_toggle_filemode(const char *filename); -bool cl_is_chmod_supported(void); - -/* Environment wrappers */ -char *cl_getenv(const char *name); -bool cl_is_env_set(const char *name); -int cl_setenv(const char *name, const char *value); - -/* Reliable rename */ -int cl_rename(const char *source, const char *dest); - -/* Git sandbox setup helpers */ - -git_repository *cl_git_sandbox_init(const char *sandbox); -git_repository *cl_git_sandbox_init_new(const char *name); -void cl_git_sandbox_cleanup(void); -git_repository *cl_git_sandbox_reopen(void); - -/* - * build a sandbox-relative from path segments - * is_dir will add a trailing slash - * vararg must be a NULL-terminated char * list - */ -const char *cl_git_sandbox_path(int is_dir, ...); - -/* Local-repo url helpers */ -const char* cl_git_fixture_url(const char *fixturename); -const char* cl_git_path_url(const char *path); - -/* Test repository cleaner */ -int cl_git_remove_placeholders(const char *directory_path, const char *filename); - -/* commit creation helpers */ -void cl_repo_commit_from_index( - git_oid *out, - git_repository *repo, - git_signature *sig, - git_time_t time, - const char *msg); - -/* config setting helpers */ -void cl_repo_set_bool(git_repository *repo, const char *cfg, int value); -int cl_repo_get_bool(git_repository *repo, const char *cfg); - -void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value); - -/* set up a fake "home" directory and set libgit2 GLOBAL search path. - * - * automatically configures cleanup function to restore the regular search - * path, although you can call it explicitly if you wish (with NULL). - */ -void cl_fake_home(void); -void cl_fake_home_cleanup(void *); - -void cl_sandbox_set_search_path_defaults(void); - -#ifdef GIT_WIN32 -# define cl_msleep(x) Sleep(x) -#else -# define cl_msleep(x) usleep(1000 * (x)) -#endif - -#ifdef GIT_WIN32 -bool cl_sandbox_supports_8dot3(void); -#endif - -#endif diff --git a/tests/clar_libgit2_timer.c b/tests/clar_libgit2_timer.c deleted file mode 100644 index 2330f9351..000000000 --- a/tests/clar_libgit2_timer.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "clar_libgit2.h" -#include "clar_libgit2_timer.h" - -void cl_perf_timer__init(cl_perf_timer *t) -{ - memset(t, 0, sizeof(cl_perf_timer)); -} - -void cl_perf_timer__start(cl_perf_timer *t) -{ - t->time_started = git__timer(); -} - -void cl_perf_timer__stop(cl_perf_timer *t) -{ - double time_now = git__timer(); - - t->last = time_now - t->time_started; - t->sum += t->last; -} - -double cl_perf_timer__last(const cl_perf_timer *t) -{ - return t->last; -} - -double cl_perf_timer__sum(const cl_perf_timer *t) -{ - return t->sum; -} diff --git a/tests/clar_libgit2_timer.h b/tests/clar_libgit2_timer.h deleted file mode 100644 index 7571a52e9..000000000 --- a/tests/clar_libgit2_timer.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef __CLAR_LIBGIT2_TIMER__ -#define __CLAR_LIBGIT2_TIMER__ - -struct cl_perf_timer -{ - /* cumulative running time across all start..stop intervals */ - double sum; - - /* value of last start..stop interval */ - double last; - - /* clock value at start */ - double time_started; -}; - -#define CL_PERF_TIMER_INIT {0} - -typedef struct cl_perf_timer cl_perf_timer; - -void cl_perf_timer__init(cl_perf_timer *t); -void cl_perf_timer__start(cl_perf_timer *t); -void cl_perf_timer__stop(cl_perf_timer *t); - -/** - * return value of last start..stop interval in seconds. - */ -double cl_perf_timer__last(const cl_perf_timer *t); - -/** - * return cumulative running time across all start..stop - * intervals in seconds. - */ -double cl_perf_timer__sum(const cl_perf_timer *t); - -#endif /* __CLAR_LIBGIT2_TIMER__ */ diff --git a/tests/clar_libgit2_trace.c b/tests/clar_libgit2_trace.c deleted file mode 100644 index ebb0f41dd..000000000 --- a/tests/clar_libgit2_trace.c +++ /dev/null @@ -1,263 +0,0 @@ -#include "clar_libgit2_trace.h" -#include "clar_libgit2.h" -#include "clar_libgit2_timer.h" -#include "trace.h" - -struct method { - const char *name; - void (*git_trace_cb)(git_trace_level_t level, const char *msg); - void (*close)(void); -}; - -static const char *message_prefix(git_trace_level_t level) -{ - switch (level) { - case GIT_TRACE_NONE: - return "[NONE]: "; - case GIT_TRACE_FATAL: - return "[FATAL]: "; - case GIT_TRACE_ERROR: - return "[ERROR]: "; - case GIT_TRACE_WARN: - return "[WARN]: "; - case GIT_TRACE_INFO: - return "[INFO]: "; - case GIT_TRACE_DEBUG: - return "[DEBUG]: "; - case GIT_TRACE_TRACE: - return "[TRACE]: "; - default: - return "[?????]: "; - } -} - -static void _git_trace_cb__printf(git_trace_level_t level, const char *msg) -{ - printf("%s%s\n", message_prefix(level), msg); -} - -#if defined(GIT_WIN32) -static void _git_trace_cb__debug(git_trace_level_t level, const char *msg) -{ - OutputDebugString(message_prefix(level)); - OutputDebugString(msg); - OutputDebugString("\n"); - - printf("%s%s\n", message_prefix(level), msg); -} -#else -#define _git_trace_cb__debug _git_trace_cb__printf -#endif - - -static void _trace_printf_close(void) -{ - fflush(stdout); -} - -#define _trace_debug_close _trace_printf_close - - -static struct method s_methods[] = { - { "printf", _git_trace_cb__printf, _trace_printf_close }, - { "debug", _git_trace_cb__debug, _trace_debug_close }, - /* TODO add file method */ - {0}, -}; - - -static int s_trace_loaded = 0; -static int s_trace_level = GIT_TRACE_NONE; -static struct method *s_trace_method = NULL; -static int s_trace_tests = 0; - -static int set_method(const char *name) -{ - int k; - - if (!name || !*name) - name = "printf"; - - for (k=0; (s_methods[k].name); k++) { - if (strcmp(name, s_methods[k].name) == 0) { - s_trace_method = &s_methods[k]; - return 0; - } - } - fprintf(stderr, "Unknown CLAR_TRACE_METHOD: '%s'\n", name); - return -1; -} - - -/** - * Lookup CLAR_TRACE_LEVEL and CLAR_TRACE_METHOD from - * the environment and set the above s_trace_* fields. - * - * If CLAR_TRACE_LEVEL is not set, we disable tracing. - * - * TODO If set, we assume GIT_TRACE_TRACE level, which - * logs everything. Later, we may want to parse the - * value of the environment variable and set a specific - * level. - * - * We assume the "printf" method. This can be changed - * with the CLAR_TRACE_METHOD environment variable. - * Currently, this is only needed on Windows for a "debug" - * version which also writes to the debug output window - * in Visual Studio. - * - * TODO add a "file" method that would open and write - * to a well-known file. This would help keep trace - * output and clar output separate. - * - */ -static void _load_trace_params(void) -{ - char *sz_level; - char *sz_method; - char *sz_tests; - - s_trace_loaded = 1; - - sz_level = cl_getenv("CLAR_TRACE_LEVEL"); - if (!sz_level || !*sz_level) { - s_trace_level = GIT_TRACE_NONE; - s_trace_method = NULL; - return; - } - - /* TODO Parse sz_level and set s_trace_level. */ - s_trace_level = GIT_TRACE_TRACE; - - sz_method = cl_getenv("CLAR_TRACE_METHOD"); - if (set_method(sz_method) < 0) - set_method(NULL); - - sz_tests = cl_getenv("CLAR_TRACE_TESTS"); - if (sz_tests != NULL) - s_trace_tests = 1; -} - -#define HR "================================================================" - -/** - * Timer to report the take spend in a test's run() method. - */ -static cl_perf_timer s_timer_run = CL_PERF_TIMER_INIT; - -/** - * Timer to report total time in a test (init, run, cleanup). - */ -static cl_perf_timer s_timer_test = CL_PERF_TIMER_INIT; - -static void _cl_trace_cb__event_handler( - cl_trace_event ev, - const char *suite_name, - const char *test_name, - void *payload) -{ - GIT_UNUSED(payload); - - if (!s_trace_tests) - return; - - switch (ev) { - case CL_TRACE__SUITE_BEGIN: - git_trace(GIT_TRACE_TRACE, "\n\n%s\n%s: Begin Suite", HR, suite_name); -#if 0 && defined(GIT_WIN32_LEAKCHECK) - git_win32__crtdbg_stacktrace__dump( - GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK, - suite_name); -#endif - break; - - case CL_TRACE__SUITE_END: -#if 0 && defined(GIT_WIN32_LEAKCHECK) - /* As an example of checkpointing, dump leaks within this suite. - * This may generate false positives for things like the global - * TLS error state and maybe the odb cache since they aren't - * freed until the global shutdown and outside the scope of this - * set of tests. - * - * This may under-report if the test itself uses a checkpoint. - * See tests/trace/windows/stacktrace.c - */ - git_win32__crtdbg_stacktrace__dump( - GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK, - suite_name); -#endif - git_trace(GIT_TRACE_TRACE, "\n\n%s: End Suite\n%s", suite_name, HR); - break; - - case CL_TRACE__TEST__BEGIN: - git_trace(GIT_TRACE_TRACE, "\n%s::%s: Begin Test", suite_name, test_name); - cl_perf_timer__init(&s_timer_test); - cl_perf_timer__start(&s_timer_test); - break; - - case CL_TRACE__TEST__END: - cl_perf_timer__stop(&s_timer_test); - git_trace(GIT_TRACE_TRACE, "%s::%s: End Test (%.3f %.3f)", suite_name, test_name, - cl_perf_timer__last(&s_timer_run), - cl_perf_timer__last(&s_timer_test)); - break; - - case CL_TRACE__TEST__RUN_BEGIN: - git_trace(GIT_TRACE_TRACE, "%s::%s: Begin Run", suite_name, test_name); - cl_perf_timer__init(&s_timer_run); - cl_perf_timer__start(&s_timer_run); - break; - - case CL_TRACE__TEST__RUN_END: - cl_perf_timer__stop(&s_timer_run); - git_trace(GIT_TRACE_TRACE, "%s::%s: End Run", suite_name, test_name); - break; - - case CL_TRACE__TEST__LONGJMP: - cl_perf_timer__stop(&s_timer_run); - git_trace(GIT_TRACE_TRACE, "%s::%s: Aborted", suite_name, test_name); - break; - - default: - break; - } -} - -/** - * Setup/Enable git_trace() based upon settings user's environment. - */ -void cl_global_trace_register(void) -{ - if (!s_trace_loaded) - _load_trace_params(); - - if (s_trace_level == GIT_TRACE_NONE) - return; - if (s_trace_method == NULL) - return; - if (s_trace_method->git_trace_cb == NULL) - return; - - git_trace_set(s_trace_level, s_trace_method->git_trace_cb); - cl_trace_register(_cl_trace_cb__event_handler, NULL); -} - -/** - * If we turned on git_trace() earlier, turn it off. - * - * This is intended to let us close/flush any buffered - * IO if necessary. - * - */ -void cl_global_trace_disable(void) -{ - cl_trace_register(NULL, NULL); - git_trace_set(GIT_TRACE_NONE, NULL); - if (s_trace_method && s_trace_method->close) - s_trace_method->close(); - - /* Leave s_trace_ vars set so they can restart tracing - * since we only want to hit the environment variables - * once. - */ -} diff --git a/tests/clar_libgit2_trace.h b/tests/clar_libgit2_trace.h deleted file mode 100644 index 09d1e050f..000000000 --- a/tests/clar_libgit2_trace.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef __CLAR_LIBGIT2_TRACE__ -#define __CLAR_LIBGIT2_TRACE__ - -void cl_global_trace_register(void); -void cl_global_trace_disable(void); - -#endif diff --git a/tests/clone/empty.c b/tests/clone/empty.c deleted file mode 100644 index 94847bc73..000000000 --- a/tests/clone/empty.c +++ /dev/null @@ -1,102 +0,0 @@ -#include "clar_libgit2.h" - -#include "git2/clone.h" -#include "repository.h" -#include "repo/repo_helpers.h" - -static git_clone_options g_options; -static git_repository *g_repo; -static git_repository *g_repo_cloned; - -void test_clone_empty__initialize(void) -{ - git_repository *sandbox = cl_git_sandbox_init("empty_bare.git"); - git_fetch_options dummy_options = GIT_FETCH_OPTIONS_INIT; - cl_git_remove_placeholders(git_repository_path(sandbox), "dummy-marker.txt"); - - g_repo = NULL; - - memset(&g_options, 0, sizeof(git_clone_options)); - g_options.version = GIT_CLONE_OPTIONS_VERSION; - g_options.fetch_opts = dummy_options; -} - -void test_clone_empty__cleanup(void) -{ - cl_fixture_cleanup("tmp_global_path"); - cl_git_sandbox_cleanup(); -} - -static void cleanup_repository(void *path) -{ - cl_fixture_cleanup((const char *)path); - - git_repository_free(g_repo_cloned); - g_repo_cloned = NULL; -} - -void test_clone_empty__can_clone_an_empty_local_repo_barely(void) -{ - char *local_name = "refs/heads/master"; - const char *expected_tracked_branch_name = "refs/remotes/origin/master"; - const char *expected_remote_name = "origin"; - git_buf buf = GIT_BUF_INIT; - git_reference *ref; - - cl_set_cleanup(&cleanup_repository, "./empty"); - - g_options.bare = true; - cl_git_pass(git_clone(&g_repo_cloned, "./empty_bare.git", "./empty", &g_options)); - - /* Although the HEAD is unborn... */ - cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo_cloned, local_name)); - - /* ...one can still retrieve the name of the remote tracking reference */ - cl_git_pass(git_branch_upstream_name(&buf, g_repo_cloned, local_name)); - - cl_assert_equal_s(expected_tracked_branch_name, buf.ptr); - git_buf_dispose(&buf); - - /* ...and the name of the remote... */ - cl_git_pass(git_branch_remote_name(&buf, g_repo_cloned, expected_tracked_branch_name)); - - cl_assert_equal_s(expected_remote_name, buf.ptr); - git_buf_dispose(&buf); - - /* ...even when the remote HEAD is unborn as well */ - cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo_cloned, - expected_tracked_branch_name)); -} - -void test_clone_empty__respects_initialbranch_config(void) -{ - git_buf buf = GIT_BUF_INIT; - - create_tmp_global_config("tmp_global_path", "init.defaultbranch", "my_default_branch"); - - cl_set_cleanup(&cleanup_repository, "./empty"); - - g_options.bare = true; - cl_git_pass(git_clone(&g_repo_cloned, "./empty_bare.git", "./empty", &g_options)); - cl_git_pass(git_branch_upstream_name(&buf, g_repo_cloned, "refs/heads/my_default_branch")); - cl_assert_equal_s("refs/remotes/origin/my_default_branch", buf.ptr); - git_buf_dispose(&buf); -} - -void test_clone_empty__can_clone_an_empty_local_repo(void) -{ - cl_set_cleanup(&cleanup_repository, "./empty"); - - cl_git_pass(git_clone(&g_repo_cloned, "./empty_bare.git", "./empty", &g_options)); -} - -void test_clone_empty__can_clone_an_empty_standard_repo(void) -{ - cl_git_sandbox_cleanup(); - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_remove_placeholders(git_repository_path(g_repo), "dummy-marker.txt"); - - cl_set_cleanup(&cleanup_repository, "./empty"); - - cl_git_pass(git_clone(&g_repo_cloned, "./empty_standard_repo", "./empty", &g_options)); -} diff --git a/tests/clone/local.c b/tests/clone/local.c deleted file mode 100644 index e0bd74df7..000000000 --- a/tests/clone/local.c +++ /dev/null @@ -1,212 +0,0 @@ -#include "clar_libgit2.h" - -#include "git2/clone.h" -#include "clone.h" -#include "path.h" -#include "posix.h" -#include "futils.h" - -static int file_url(git_str *buf, const char *host, const char *path) -{ - if (path[0] == '/') - path++; - - git_str_clear(buf); - return git_str_printf(buf, "file://%s/%s", host, path); -} - -#ifdef GIT_WIN32 -static int git_style_unc_path(git_str *buf, const char *host, const char *path) -{ - git_str_clear(buf); - - if (host) - git_str_printf(buf, "//%s/", host); - - if (path[0] == '/') - path++; - - if (git__isalpha(path[0]) && path[1] == ':' && path[2] == '/') { - git_str_printf(buf, "%c$/", path[0]); - path += 3; - } - - git_str_puts(buf, path); - - return git_str_oom(buf) ? -1 : 0; -} - -static int unc_path(git_str *buf, const char *host, const char *path) -{ - char *c; - - if (git_style_unc_path(buf, host, path) < 0) - return -1; - - for (c = buf->ptr; *c; c++) - if (*c == '/') - *c = '\\'; - - return 0; -} -#endif - -void test_clone_local__should_clone_local(void) -{ - git_str buf = GIT_STR_INIT; - - /* we use a fixture path because it needs to exist for us to want to clone */ - const char *path = cl_fixture("testrepo.git"); - - cl_git_pass(file_url(&buf, "", path)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); - cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); - cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); - - cl_git_pass(file_url(&buf, "localhost", path)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); - cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); - cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); - - cl_git_pass(file_url(&buf, "other-host.mycompany.com", path)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); - - /* Ensure that file:/// urls are percent decoded: .git == %2e%67%69%74 */ - cl_git_pass(file_url(&buf, "", path)); - git_str_shorten(&buf, 4); - cl_git_pass(git_str_puts(&buf, "%2e%67%69%74")); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); - cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); - cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); - cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); - - cl_assert_equal_i(1, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_AUTO)); - cl_assert_equal_i(1, git_clone__should_clone_local(path, GIT_CLONE_LOCAL)); - cl_assert_equal_i(1, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_NO_LINKS)); - cl_assert_equal_i(0, git_clone__should_clone_local(path, GIT_CLONE_NO_LOCAL)); - - git_str_dispose(&buf); -} - -void test_clone_local__hardlinks(void) -{ - git_repository *repo; - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - git_str buf = GIT_STR_INIT; - struct stat st; - - /* - * In this first clone, we just copy over, since the temp dir - * will often be in a different filesystem, so we cannot - * link. It also allows us to control the number of links - */ - opts.bare = true; - opts.local = GIT_CLONE_LOCAL_NO_LINKS; - cl_git_pass(git_clone(&repo, cl_fixture("testrepo.git"), "./clone.git", &opts)); - git_repository_free(repo); - - /* This second clone is in the same filesystem, so we can hardlink */ - - opts.local = GIT_CLONE_LOCAL; - cl_git_pass(git_clone(&repo, cl_git_path_url("clone.git"), "./clone2.git", &opts)); - -#ifndef GIT_WIN32 - git_str_clear(&buf); - cl_git_pass(git_str_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); - - cl_git_pass(p_stat(buf.ptr, &st)); - cl_assert_equal_i(2, st.st_nlink); -#endif - - git_repository_free(repo); - git_str_clear(&buf); - - opts.local = GIT_CLONE_LOCAL_NO_LINKS; - cl_git_pass(git_clone(&repo, cl_git_path_url("clone.git"), "./clone3.git", &opts)); - - git_str_clear(&buf); - cl_git_pass(git_str_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); - - cl_git_pass(p_stat(buf.ptr, &st)); - cl_assert_equal_i(1, st.st_nlink); - - git_repository_free(repo); - - /* this one should automatically use links */ - cl_git_pass(git_clone(&repo, "./clone.git", "./clone4.git", NULL)); - -#ifndef GIT_WIN32 - git_str_clear(&buf); - cl_git_pass(git_str_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); - - cl_git_pass(p_stat(buf.ptr, &st)); - cl_assert_equal_i(3, st.st_nlink); -#endif - - git_str_dispose(&buf); - git_repository_free(repo); - - cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_git_pass(git_futils_rmdir_r("./clone2.git", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_git_pass(git_futils_rmdir_r("./clone3.git", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_git_pass(git_futils_rmdir_r("./clone4.git", NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_clone_local__standard_unc_paths_are_written_git_style(void) -{ -#ifdef GIT_WIN32 - git_repository *repo; - git_remote *remote; - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - git_str unc = GIT_STR_INIT, git_unc = GIT_STR_INIT; - - /* we use a fixture path because it needs to exist for us to want to clone */ - const char *path = cl_fixture("testrepo.git"); - - cl_git_pass(unc_path(&unc, "localhost", path)); - cl_git_pass(git_style_unc_path(&git_unc, "localhost", path)); - - cl_git_pass(git_clone(&repo, unc.ptr, "./clone.git", &opts)); - cl_git_pass(git_remote_lookup(&remote, repo, "origin")); - - cl_assert_equal_s(git_unc.ptr, git_remote_url(remote)); - - git_remote_free(remote); - git_repository_free(repo); - git_str_dispose(&unc); - git_str_dispose(&git_unc); - - cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); -#endif -} - -void test_clone_local__git_style_unc_paths(void) -{ -#ifdef GIT_WIN32 - git_repository *repo; - git_remote *remote; - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - git_str git_unc = GIT_STR_INIT; - - /* we use a fixture path because it needs to exist for us to want to clone */ - const char *path = cl_fixture("testrepo.git"); - - cl_git_pass(git_style_unc_path(&git_unc, "localhost", path)); - - cl_git_pass(git_clone(&repo, git_unc.ptr, "./clone.git", &opts)); - cl_git_pass(git_remote_lookup(&remote, repo, "origin")); - - cl_assert_equal_s(git_unc.ptr, git_remote_url(remote)); - - git_remote_free(remote); - git_repository_free(repo); - git_str_dispose(&git_unc); - - cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); -#endif -} diff --git a/tests/clone/nonetwork.c b/tests/clone/nonetwork.c deleted file mode 100644 index eab633635..000000000 --- a/tests/clone/nonetwork.c +++ /dev/null @@ -1,361 +0,0 @@ -#include "clar_libgit2.h" - -#include "git2/clone.h" -#include "../submodule/submodule_helpers.h" -#include "remote.h" -#include "futils.h" -#include "repository.h" - -#define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository" - -static git_clone_options g_options; -static git_repository *g_repo; -static git_reference* g_ref; -static git_remote* g_remote; - -void test_clone_nonetwork__initialize(void) -{ - git_checkout_options dummy_opts = GIT_CHECKOUT_OPTIONS_INIT; - git_fetch_options dummy_fetch = GIT_FETCH_OPTIONS_INIT; - - g_repo = NULL; - - memset(&g_options, 0, sizeof(git_clone_options)); - g_options.version = GIT_CLONE_OPTIONS_VERSION; - g_options.checkout_opts = dummy_opts; - g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - g_options.fetch_opts = dummy_fetch; -} - -void test_clone_nonetwork__cleanup(void) -{ - if (g_repo) { - git_repository_free(g_repo); - g_repo = NULL; - } - - if (g_ref) { - git_reference_free(g_ref); - g_ref = NULL; - } - - if (g_remote) { - git_remote_free(g_remote); - g_remote = NULL; - } - - cl_fixture_cleanup("./foo"); -} - -void test_clone_nonetwork__bad_urls(void) -{ - /* Clone should clean up the mess if the URL isn't a git repository */ - cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options)); - cl_assert(!git_fs_path_exists("./foo")); - g_options.bare = true; - cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options)); - cl_assert(!git_fs_path_exists("./foo")); - - cl_git_fail(git_clone(&g_repo, "git://example.com:asdf", "./foo", &g_options)); - cl_git_fail(git_clone(&g_repo, "https://example.com:asdf/foo", "./foo", &g_options)); - cl_git_fail(git_clone(&g_repo, "git://github.com/git://github.com/foo/bar.git.git", - "./foo", &g_options)); - cl_git_fail(git_clone(&g_repo, "arrbee:my/bad:password@github.com:1111/strange:words.git", - "./foo", &g_options)); -} - -void test_clone_nonetwork__do_not_clean_existing_directory(void) -{ - /* Clone should not remove the directory if it already exists, but - * Should clean up entries it creates. */ - p_mkdir("./foo", GIT_DIR_MODE); - cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options)); - cl_assert(git_fs_path_is_empty_dir("./foo")); - - /* Try again with a bare repository. */ - g_options.bare = true; - cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options)); - cl_assert(git_fs_path_is_empty_dir("./foo")); -} - -void test_clone_nonetwork__local(void) -{ - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); -} - -void test_clone_nonetwork__local_absolute_path(void) -{ - const char *local_src; - local_src = cl_fixture("testrepo.git"); - cl_git_pass(git_clone(&g_repo, local_src, "./foo", &g_options)); -} - -void test_clone_nonetwork__local_bare(void) -{ - g_options.bare = true; - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); -} - -void test_clone_nonetwork__fail_when_the_target_is_a_file(void) -{ - cl_git_mkfile("./foo", "Bar!"); - cl_git_fail(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); -} - -void test_clone_nonetwork__fail_with_already_existing_but_non_empty_directory(void) -{ - p_mkdir("./foo", GIT_DIR_MODE); - cl_git_mkfile("./foo/bar", "Baz!"); - cl_git_fail(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); -} - -static int custom_origin_name_remote_create( - git_remote **out, - git_repository *repo, - const char *name, - const char *url, - void *payload) -{ - GIT_UNUSED(name); - GIT_UNUSED(payload); - - return git_remote_create(out, repo, "my_origin", url); -} - -void test_clone_nonetwork__custom_origin_name(void) -{ - g_options.remote_cb = custom_origin_name_remote_create; - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); - - cl_git_pass(git_remote_lookup(&g_remote, g_repo, "my_origin")); -} - -void test_clone_nonetwork__defaults(void) -{ - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", NULL)); - cl_assert(g_repo); - cl_git_pass(git_remote_lookup(&g_remote, g_repo, "origin")); -} - -void test_clone_nonetwork__cope_with_already_existing_directory(void) -{ - p_mkdir("./foo", GIT_DIR_MODE); - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); -} - -void test_clone_nonetwork__can_prevent_the_checkout_of_a_standard_repo(void) -{ - git_str path = GIT_STR_INIT; - - g_options.checkout_opts.checkout_strategy = 0; - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "master.txt")); - cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&path))); - - git_str_dispose(&path); -} - -void test_clone_nonetwork__can_checkout_given_branch(void) -{ - git_reference *remote_head; - - g_options.checkout_branch = "test"; - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); - - cl_assert_equal_i(0, git_repository_head_unborn(g_repo)); - - cl_git_pass(git_repository_head(&g_ref, g_repo)); - cl_assert_equal_s(git_reference_name(g_ref), "refs/heads/test"); - - cl_assert(git_fs_path_exists("foo/readme.txt")); - - cl_git_pass(git_reference_lookup(&remote_head, g_repo, "refs/remotes/origin/HEAD")); - cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(remote_head)); - cl_assert_equal_s("refs/remotes/origin/master", git_reference_symbolic_target(remote_head)); - - git_reference_free(remote_head); -} - -static int clone_cancel_fetch_transfer_progress_cb( - const git_indexer_progress *stats, void *data) -{ - GIT_UNUSED(stats); GIT_UNUSED(data); - return -54321; -} - -void test_clone_nonetwork__can_cancel_clone_in_fetch(void) -{ - g_options.checkout_branch = "test"; - - g_options.fetch_opts.callbacks.transfer_progress = - clone_cancel_fetch_transfer_progress_cb; - - cl_git_fail_with(git_clone( - &g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options), - -54321); - - cl_assert(!g_repo); - cl_assert(!git_fs_path_exists("foo/readme.txt")); -} - -static int clone_cancel_checkout_cb( - git_checkout_notify_t why, - const char *path, - const git_diff_file *b, - const git_diff_file *t, - const git_diff_file *w, - void *payload) -{ - const char *at_file = payload; - GIT_UNUSED(why); GIT_UNUSED(b); GIT_UNUSED(t); GIT_UNUSED(w); - if (!strcmp(path, at_file)) - return -12345; - return 0; -} - -void test_clone_nonetwork__can_cancel_clone_in_checkout(void) -{ - g_options.checkout_branch = "test"; - - g_options.checkout_opts.notify_flags = GIT_CHECKOUT_NOTIFY_UPDATED; - g_options.checkout_opts.notify_cb = clone_cancel_checkout_cb; - g_options.checkout_opts.notify_payload = "readme.txt"; - - cl_git_fail_with(git_clone( - &g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options), - -12345); - - cl_assert(!g_repo); - cl_assert(!git_fs_path_exists("foo/readme.txt")); -} - -void test_clone_nonetwork__can_detached_head(void) -{ - git_object *obj; - git_repository *cloned; - git_reference *cloned_head; - - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); - - cl_git_pass(git_revparse_single(&obj, g_repo, "master~1")); - cl_git_pass(git_repository_set_head_detached(g_repo, git_object_id(obj))); - - cl_git_pass(git_clone(&cloned, "./foo", "./foo1", &g_options)); - - cl_assert(git_repository_head_detached(cloned)); - - cl_git_pass(git_repository_head(&cloned_head, cloned)); - cl_assert_equal_oid(git_object_id(obj), git_reference_target(cloned_head)); - - git_object_free(obj); - git_reference_free(cloned_head); - git_repository_free(cloned); - - cl_fixture_cleanup("./foo1"); -} - -void test_clone_nonetwork__clone_tag_to_tree(void) -{ - git_repository *stage; - git_index_entry entry; - git_index *index; - git_odb *odb; - git_oid tree_id; - git_tree *tree; - git_reference *tag; - git_tree_entry *tentry; - const char *file_path = "some/deep/path.txt"; - const char *file_content = "some content\n"; - const char *tag_name = "refs/tags/tree-tag"; - - stage = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_odb(&odb, stage)); - cl_git_pass(git_index_new(&index)); - - memset(&entry, 0, sizeof(git_index_entry)); - entry.path = file_path; - entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_odb_write(&entry.id, odb, file_content, strlen(file_content), GIT_OBJECT_BLOB)); - - cl_git_pass(git_index_add(index, &entry)); - cl_git_pass(git_index_write_tree_to(&tree_id, index, stage)); - cl_git_pass(git_reference_create(&tag, stage, tag_name, &tree_id, 0, NULL)); - git_reference_free(tag); - git_odb_free(odb); - git_index_free(index); - - g_options.local = GIT_CLONE_NO_LOCAL; - cl_git_pass(git_clone(&g_repo, cl_git_path_url(git_repository_path(stage)), "./foo", &g_options)); - git_repository_free(stage); - - cl_git_pass(git_reference_lookup(&tag, g_repo, tag_name)); - cl_git_pass(git_tree_lookup(&tree, g_repo, git_reference_target(tag))); - git_reference_free(tag); - - cl_git_pass(git_tree_entry_bypath(&tentry, tree, file_path)); - git_tree_entry_free(tentry); - git_tree_free(tree); - - cl_fixture_cleanup("testrepo.git"); -} - -static void assert_correct_reflog(const char *name) -{ - git_reflog *log; - const git_reflog_entry *entry; - git_str expected_message = GIT_STR_INIT; - - git_str_printf(&expected_message, - "clone: from %s", cl_git_fixture_url("testrepo.git")); - - cl_git_pass(git_reflog_read(&log, g_repo, name)); - cl_assert_equal_i(1, git_reflog_entrycount(log)); - entry = git_reflog_entry_byindex(log, 0); - cl_assert_equal_s(expected_message.ptr, git_reflog_entry_message(entry)); - - git_reflog_free(log); - - git_str_dispose(&expected_message); -} - -void test_clone_nonetwork__clone_updates_reflog_properly(void) -{ - cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); - assert_correct_reflog("HEAD"); - assert_correct_reflog("refs/heads/master"); -} - -static void cleanup_repository(void *path) -{ - if (g_repo) { - git_repository_free(g_repo); - g_repo = NULL; - } - - cl_fixture_cleanup((const char *)path); -} - -void test_clone_nonetwork__clone_from_empty_sets_upstream(void) -{ - git_config *config; - git_repository *repo; - const char *str; - - /* Create an empty repo to clone from */ - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - cl_set_cleanup(&cleanup_repository, "./repowithunborn"); - cl_git_pass(git_clone(&repo, "./test1", "./repowithunborn", NULL)); - - cl_git_pass(git_repository_config_snapshot(&config, repo)); - - cl_git_pass(git_config_get_string(&str, config, "branch.master.remote")); - cl_assert_equal_s("origin", str); - cl_git_pass(git_config_get_string(&str, config, "branch.master.merge")); - cl_assert_equal_s("refs/heads/master", str); - - git_config_free(config); - git_repository_free(repo); - cl_fixture_cleanup("./repowithunborn"); -} diff --git a/tests/clone/transport.c b/tests/clone/transport.c deleted file mode 100644 index fa4f65357..000000000 --- a/tests/clone/transport.c +++ /dev/null @@ -1,51 +0,0 @@ -#include "clar_libgit2.h" - -#include "git2/clone.h" -#include "git2/transport.h" -#include "git2/sys/transport.h" -#include "futils.h" - -static int custom_transport( - git_transport **out, - git_remote *owner, - void *payload) -{ - *((int*)payload) = 1; - - return git_transport_local(out, owner, payload); -} - -static int custom_transport_remote_create( - git_remote **out, - git_repository *repo, - const char *name, - const char *url, - void *payload) -{ - int error; - - GIT_UNUSED(payload); - - if ((error = git_remote_create(out, repo, name, url)) < 0) - return error; - - return 0; -} - -void test_clone_transport__custom_transport(void) -{ - git_repository *repo; - git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; - int custom_transport_used = 0; - - clone_opts.remote_cb = custom_transport_remote_create; - clone_opts.fetch_opts.callbacks.transport = custom_transport; - clone_opts.fetch_opts.callbacks.payload = &custom_transport_used; - - cl_git_pass(git_clone(&repo, cl_fixture("testrepo.git"), "./custom_transport.git", &clone_opts)); - git_repository_free(repo); - - cl_git_pass(git_futils_rmdir_r("./custom_transport.git", NULL, GIT_RMDIR_REMOVE_FILES)); - - cl_assert(custom_transport_used == 1); -} diff --git a/tests/commit/commit.c b/tests/commit/commit.c deleted file mode 100644 index fd574f7f2..000000000 --- a/tests/commit/commit.c +++ /dev/null @@ -1,189 +0,0 @@ -#include "clar_libgit2.h" -#include "commit.h" -#include "git2/commit.h" - -static git_repository *_repo; - -void test_commit_commit__initialize(void) -{ - cl_fixture_sandbox("testrepo.git"); - cl_git_pass(git_repository_open(&_repo, "testrepo.git")); -} - -void test_commit_commit__cleanup(void) -{ - git_repository_free(_repo); - _repo = NULL; - - cl_fixture_cleanup("testrepo.git"); -} - -void test_commit_commit__create_unexisting_update_ref(void) -{ - git_oid oid; - git_tree *tree; - git_commit *commit; - git_signature *s; - git_reference *ref; - - git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); - - git_oid_fromstr(&oid, "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); - cl_git_pass(git_tree_lookup(&tree, _repo, &oid)); - - cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); - - cl_git_fail(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); - cl_git_pass(git_commit_create(&oid, _repo, "refs/heads/foo/bar", s, s, - NULL, "some msg", tree, 1, (const git_commit **) &commit)); - - /* fail because the parent isn't the tip of the branch anymore */ - cl_git_fail(git_commit_create(&oid, _repo, "refs/heads/foo/bar", s, s, - NULL, "some msg", tree, 1, (const git_commit **) &commit)); - - cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); - cl_assert_equal_oid(&oid, git_reference_target(ref)); - - git_tree_free(tree); - git_commit_free(commit); - git_signature_free(s); - git_reference_free(ref); -} - -void test_commit_commit__create_initial_commit(void) -{ - git_oid oid; - git_tree *tree; - git_commit *commit; - git_signature *s; - git_reference *ref; - - git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); - - git_oid_fromstr(&oid, "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); - cl_git_pass(git_tree_lookup(&tree, _repo, &oid)); - - cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); - - cl_git_fail(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); - cl_git_pass(git_commit_create(&oid, _repo, "refs/heads/foo/bar", s, s, - NULL, "initial commit", tree, 0, NULL)); - - cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); - - cl_assert_equal_oid(&oid, git_reference_target(ref)); - - git_tree_free(tree); - git_commit_free(commit); - git_signature_free(s); - git_reference_free(ref); -} - -void test_commit_commit__create_initial_commit_parent_not_current(void) -{ - git_oid oid; - git_oid original_oid; - git_tree *tree; - git_commit *commit; - git_signature *s; - - git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); - - git_oid_fromstr(&oid, "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); - cl_git_pass(git_tree_lookup(&tree, _repo, &oid)); - - cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); - - cl_git_pass(git_reference_name_to_id(&original_oid, _repo, "HEAD")); - - cl_git_fail(git_commit_create(&oid, _repo, "HEAD", s, s, - NULL, "initial commit", tree, 0, NULL)); - - cl_git_pass(git_reference_name_to_id(&oid, _repo, "HEAD")); - - cl_assert_equal_oid(&oid, &original_oid); - - git_tree_free(tree); - git_commit_free(commit); - git_signature_free(s); -} - -static void assert_commit_summary(const char *expected, const char *given) -{ - git_commit *dummy; - - cl_assert(dummy = git__calloc(1, sizeof(struct git_commit))); - - dummy->raw_message = git__strdup(given); - cl_assert_equal_s(expected, git_commit_summary(dummy)); - - git_commit__free(dummy); -} - -static void assert_commit_body(const char *expected, const char *given) -{ - git_commit *dummy; - - cl_assert(dummy = git__calloc(1, sizeof(struct git_commit))); - - dummy->raw_message = git__strdup(given); - cl_assert_equal_s(expected, git_commit_body(dummy)); - - git_commit__free(dummy); -} - -void test_commit_commit__summary(void) -{ - assert_commit_summary("One-liner with no trailing newline", "One-liner with no trailing newline"); - assert_commit_summary("One-liner with trailing newline", "One-liner with trailing newline\n"); - assert_commit_summary("One-liner with trailing newline and space", "One-liner with trailing newline and space\n "); - assert_commit_summary("Trimmed leading&trailing newlines", "\n\nTrimmed leading&trailing newlines\n\n"); - assert_commit_summary("First paragraph only", "\nFirst paragraph only\n\n(There are more!)"); - assert_commit_summary("First paragraph only with space in the next line", "\nFirst paragraph only with space in the next line\n \n(There are more!)"); - assert_commit_summary("First paragraph only with spaces in the next line", "\nFirst paragraph only with spaces in the next line\n \n(There are more!)"); - assert_commit_summary("First paragraph with unwrapped trailing\tlines", "\nFirst paragraph\nwith unwrapped\ntrailing\tlines\n\n(Yes, unwrapped!)"); - assert_commit_summary("\tLeading tabs", "\tLeading\n\ttabs\n\nare preserved"); /* tabs around newlines are collapsed down to a single space */ - assert_commit_summary(" Leading Spaces", " Leading\n Spaces\n\nare preserved"); /* spaces around newlines are collapsed down to a single space */ - assert_commit_summary("Trailing tabs\tare removed", "Trailing tabs\tare removed\t\t"); - assert_commit_summary("Trailing spaces are removed", "Trailing spaces are removed "); - assert_commit_summary("Trailing tabs", "Trailing tabs\t\n\nare removed"); - assert_commit_summary("Trailing spaces", "Trailing spaces \n\nare removed"); - assert_commit_summary("Newlines are replaced by spaces", "Newlines\nare\nreplaced by spaces\n"); - assert_commit_summary(" Spaces after newlines are collapsed", "\n Spaces after newlines\n are\n collapsed\n "); /* newlines at the very beginning are ignored and not collapsed */ - assert_commit_summary(" Spaces before newlines are collapsed", " \nSpaces before newlines \nare \ncollapsed \n"); - assert_commit_summary(" Spaces around newlines are collapsed", " \n Spaces around newlines \n are \n collapsed \n "); - assert_commit_summary(" Trailing newlines are" , " \n Trailing newlines \n are \n\n collapsed \n "); - assert_commit_summary(" Trailing spaces are stripped", " \n Trailing spaces \n are stripped \n\n \n \t "); - assert_commit_summary("", ""); - assert_commit_summary("", " "); - assert_commit_summary("", "\n"); - assert_commit_summary("", "\n \n"); -} - -void test_commit_commit__body(void) -{ - assert_commit_body(NULL, "One-liner with no trailing newline"); - assert_commit_body(NULL, "One-liner with trailing newline\n"); - assert_commit_body(NULL, "\n\nTrimmed leading&trailing newlines\n\n"); - assert_commit_body("(There are more!)", "\nFirst paragraph only\n\n(There are more!)"); - assert_commit_body("(Yes, unwrapped!)", "\nFirst paragraph\nwith unwrapped\ntrailing\tlines\n\n(Yes, unwrapped!)"); - assert_commit_body("are preserved", "\tLeading\n\ttabs\n\nare preserved"); /* tabs around newlines are collapsed down to a single space */ - assert_commit_body("are preserved", " Leading\n Spaces\n\nare preserved"); /* spaces around newlines are collapsed down to a single space */ - assert_commit_body(NULL, "Trailing tabs\tare removed\t\t"); - assert_commit_body(NULL, "Trailing spaces are removed "); - assert_commit_body("are removed", "Trailing tabs\t\n\nare removed"); - assert_commit_body("are removed", "Trailing spaces \n\nare removed"); - assert_commit_body(NULL,"Newlines\nare\nreplaced by spaces\n"); - assert_commit_body(NULL , "\n Spaces after newlines\n are\n collapsed\n "); /* newlines at the very beginning are ignored and not collapsed */ - assert_commit_body(NULL , " \nSpaces before newlines \nare \ncollapsed \n"); - assert_commit_body(NULL , " \n Spaces around newlines \n are \n collapsed \n "); - assert_commit_body("collapsed" , " \n Trailing newlines \n are \n\n collapsed \n "); - assert_commit_body(NULL, " \n Trailing spaces \n are stripped \n\n \n \t "); - assert_commit_body(NULL , ""); - assert_commit_body(NULL , " "); - assert_commit_body(NULL , "\n"); - assert_commit_body(NULL , "\n \n"); -} diff --git a/tests/commit/parent.c b/tests/commit/parent.c deleted file mode 100644 index 18ce0bba6..000000000 --- a/tests/commit/parent.c +++ /dev/null @@ -1,60 +0,0 @@ -#include "clar_libgit2.h" - -static git_repository *_repo; -static git_commit *commit; - -void test_commit_parent__initialize(void) -{ - git_oid oid; - - cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); - - git_oid_fromstr(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); -} - -void test_commit_parent__cleanup(void) -{ - git_commit_free(commit); - commit = NULL; - - git_repository_free(_repo); - _repo = NULL; -} - -static void assert_nth_gen_parent(unsigned int gen, const char *expected_oid) -{ - git_commit *parent = NULL; - int error; - - error = git_commit_nth_gen_ancestor(&parent, commit, gen); - - if (expected_oid != NULL) { - cl_assert_equal_i(0, error); - cl_assert_equal_i(0, git_oid_streq(git_commit_id(parent), expected_oid)); - } else - cl_assert_equal_i(GIT_ENOTFOUND, error); - - git_commit_free(parent); -} - -/* - * $ git show be35~0 - * commit be3563ae3f795b2b4353bcce3a527ad0a4f7f644 - * - * $ git show be35~1 - * commit 9fd738e8f7967c078dceed8190330fc8648ee56a - * - * $ git show be35~3 - * commit 5b5b025afb0b4c913b4c338a42934a3863bf3644 - * - * $ git show be35~42 - * fatal: ambiguous argument 'be35~42': unknown revision or path not in the working tree. - */ -void test_commit_parent__can_retrieve_nth_generation_parent(void) -{ - assert_nth_gen_parent(0, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - assert_nth_gen_parent(1, "9fd738e8f7967c078dceed8190330fc8648ee56a"); - assert_nth_gen_parent(3, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); - assert_nth_gen_parent(42, NULL); -} diff --git a/tests/commit/parse.c b/tests/commit/parse.c deleted file mode 100644 index 04366d7d2..000000000 --- a/tests/commit/parse.c +++ /dev/null @@ -1,551 +0,0 @@ -#include "clar_libgit2.h" -#include -#include "commit.h" -#include "signature.h" - -/* Fixture setup */ -static git_repository *g_repo; -void test_commit_parse__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} -void test_commit_parse__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - - -/* Header parsing */ -typedef struct { - const char *line; - const char *header; -} parse_test_case; - -static parse_test_case passing_header_cases[] = { - { "parent 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "parent " }, - { "tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " }, - { "random_heading 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "random_heading " }, - { "stuck_heading05452d6349abcd67aa396dfb28660d765d8b2a36\n", "stuck_heading" }, - { "tree 5F4BEFFC0759261D015AA63A3A85613FF2F235DE\n", "tree " }, - { "tree 1A669B8AB81B5EB7D9DB69562D34952A38A9B504\n", "tree " }, - { "tree 5B20DCC6110FCC75D31C6CEDEBD7F43ECA65B503\n", "tree " }, - { "tree 173E7BF00EA5C33447E99E6C1255954A13026BE4\n", "tree " }, - { NULL, NULL } -}; - -static parse_test_case failing_header_cases[] = { - { "parent 05452d6349abcd67aa396dfb28660d765d8b2a36", "parent " }, - { "05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " }, - { "parent05452d6349abcd67aa396dfb28660d765d8b2a6a\n", "parent " }, - { "parent 05452d6349abcd67aa396dfb280d765d8b2a6\n", "parent " }, - { "tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " }, - { "parent 0545xd6349abcd67aa396dfb28660d765d8b2a36\n", "parent " }, - { "parent 0545xd6349abcd67aa396dfb28660d765d8b2a36FF\n", "parent " }, - { "", "tree " }, - { "", "" }, - { NULL, NULL } -}; - -void test_commit_parse__header(void) -{ - git_oid oid; - - parse_test_case *testcase; - for (testcase = passing_header_cases; testcase->line != NULL; testcase++) - { - const char *line = testcase->line; - const char *line_end = line + strlen(line); - - cl_git_pass(git_oid__parse(&oid, &line, line_end, testcase->header)); - cl_assert(line == line_end); - } - - for (testcase = failing_header_cases; testcase->line != NULL; testcase++) - { - const char *line = testcase->line; - const char *line_end = line + strlen(line); - - cl_git_fail(git_oid__parse(&oid, &line, line_end, testcase->header)); - } -} - - -/* Signature parsing */ -typedef struct { - const char *string; - const char *header; - const char *name; - const char *email; - git_time_t time; - int offset; -} passing_signature_test_case; - -passing_signature_test_case passing_signature_cases[] = { - {"author Vicent Marti 12345 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 12345, 0}, - {"author Vicent Marti <> 12345 \n", "author ", "Vicent Marti", "", 12345, 0}, - {"author Vicent Marti 231301 +1020\n", "author ", "Vicent Marti", "tanoku@gmail.com", 231301, 620}, - {"author Vicent Marti with an outrageously long name which will probably overflow the buffer 12345 \n", "author ", "Vicent Marti with an outrageously long name which will probably overflow the buffer", "tanoku@gmail.com", 12345, 0}, - {"author Vicent Marti 12345 \n", "author ", "Vicent Marti", "tanokuwithaveryveryverylongemailwhichwillprobablyvoverflowtheemailbuffer@gmail.com", 12345, 0}, - {"committer Vicent Marti 123456 +0000 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 0}, - {"committer Vicent Marti 123456 +0100 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 60}, - {"committer Vicent Marti 123456 -0100 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, -60}, - /* Parse a signature without an author field */ - {"committer 123456 -0100 \n", "committer ", "", "tanoku@gmail.com", 123456, -60}, - /* Parse a signature without an author field */ - {"committer 123456 -0100 \n", "committer ", "", "tanoku@gmail.com", 123456, -60}, - /* Parse a signature with an empty author field */ - {"committer 123456 -0100 \n", "committer ", "", "tanoku@gmail.com", 123456, -60}, - /* Parse a signature with an empty email field */ - {"committer Vicent Marti <> 123456 -0100 \n", "committer ", "Vicent Marti", "", 123456, -60}, - /* Parse a signature with an empty email field */ - {"committer Vicent Marti < > 123456 -0100 \n", "committer ", "Vicent Marti", "", 123456, -60}, - /* Parse a signature with empty name and email */ - {"committer <> 123456 -0100 \n", "committer ", "", "", 123456, -60}, - /* Parse a signature with empty name and email */ - {"committer <> 123456 -0100 \n", "committer ", "", "", 123456, -60}, - /* Parse a signature with empty name and email */ - {"committer < > 123456 -0100 \n", "committer ", "", "", 123456, -60}, - /* Parse an obviously invalid signature */ - {"committer foo<@bar> 123456 -0100 \n", "committer ", "foo", "@bar", 123456, -60}, - /* Parse an obviously invalid signature */ - {"committer foo<@bar> 123456 -0100 \n", "committer ", "foo", "@bar", 123456, -60}, - /* Parse an obviously invalid signature */ - {"committer <>\n", "committer ", "", "", 0, 0}, - {"committer Vicent Marti 123456 -1500 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 0}, - {"committer Vicent Marti 123456 +0163 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 0}, - {"author Vicent Marti \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0, 0}, - /* a variety of dates */ - {"author Vicent Marti 0 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0, 0}, - {"author Vicent Marti 1234567890 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 1234567890, 0}, - {"author Vicent Marti 2147483647 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0x7fffffff, 0}, - {"author Vicent Marti 4294967295 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0xffffffff, 0}, - {"author Vicent Marti 4294967296 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 4294967296, 0}, - {"author Vicent Marti 8589934592 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 8589934592, 0}, - - {NULL,NULL,NULL,NULL,0,0} -}; - -typedef struct { - const char *string; - const char *header; -} failing_signature_test_case; - -failing_signature_test_case failing_signature_cases[] = { - {"committer Vicent Marti tanoku@gmail.com> 123456 -0100 \n", "committer "}, - {"author Vicent Marti 12345 \n", "author "}, - {"author Vicent Marti 12345 \n", "committer "}, - {"author Vicent Marti 12345 \n", "author "}, - {"author Vicent Marti <\n", "committer "}, - {"author ", "author "}, - {NULL, NULL,} -}; - -void test_commit_parse__signature(void) -{ - passing_signature_test_case *passcase; - failing_signature_test_case *failcase; - - for (passcase = passing_signature_cases; passcase->string != NULL; passcase++) - { - const char *str = passcase->string; - size_t len = strlen(passcase->string); - struct git_signature person = {0}; - - cl_git_pass(git_signature__parse(&person, &str, str + len, passcase->header, '\n')); - cl_assert_equal_s(passcase->name, person.name); - cl_assert_equal_s(passcase->email, person.email); - cl_assert_equal_i((int)passcase->time, (int)person.when.time); - cl_assert_equal_i(passcase->offset, person.when.offset); - git__free(person.name); git__free(person.email); - } - - for (failcase = failing_signature_cases; failcase->string != NULL; failcase++) - { - const char *str = failcase->string; - size_t len = strlen(failcase->string); - git_signature person = {0}; - cl_git_fail(git_signature__parse(&person, &str, str + len, failcase->header, '\n')); - git__free(person.name); git__free(person.email); - } -} - - - -static char *failing_commit_cases[] = { -/* empty commit */ -"", -/* random garbage */ -"asd97sa9du902e9a0jdsuusad09as9du098709aweu8987sd\n", -/* broken endlines 1 */ -"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\r\n\ -parent 05452d6349abcd67aa396dfb28660d765d8b2a36\r\n\ -author Vicent Marti 1273848544 +0200\r\n\ -committer Vicent Marti 1273848544 +0200\r\n\ -\r\n\ -a test commit with broken endlines\r\n", -/* broken endlines 2 */ -"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\ -parent 05452d6349abcd67aa396dfb28660d765d8b2a36\ -author Vicent Marti 1273848544 +0200\ -committer Vicent Marti 1273848544 +0200\ -\ -another test commit with broken endlines", -/* starting endlines */ -"\ntree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ -parent 05452d6349abcd67aa396dfb28660d765d8b2a36\n\ -author Vicent Marti 1273848544 +0200\n\ -committer Vicent Marti 1273848544 +0200\n\ -\n\ -a test commit with a starting endline\n", -/* corrupted commit 1 */ -"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ -parent 05452d6349abcd67aa396df", -/* corrupted commit 2 */ -"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ -parent ", -/* corrupted commit 3 */ -"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ -parent ", -/* corrupted commit 4 */ -"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ -par", -}; - - -static char *passing_commit_cases[] = { -/* simple commit with no message */ -"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ -author Vicent Marti 1273848544 +0200\n\ -committer Vicent Marti 1273848544 +0200\n\ -\n", -/* simple commit, no parent */ -"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ -author Vicent Marti 1273848544 +0200\n\ -committer Vicent Marti 1273848544 +0200\n\ -\n\ -a simple commit which works\n", -/* simple commit, no parent, no newline in message */ -"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ -author Vicent Marti 1273848544 +0200\n\ -committer Vicent Marti 1273848544 +0200\n\ -\n\ -a simple commit which works", -/* simple commit, 1 parent */ -"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ -parent e90810b8df3e80c413d903f631643c716887138d\n\ -author Vicent Marti 1273848544 +0200\n\ -committer Vicent Marti 1273848544 +0200\n\ -\n\ -a simple commit which works\n", -/* simple commit with GPG signature */ -"tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ -parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ -author Ben Burkert 1358451456 -0800\n\ -committer Ben Burkert 1358451456 -0800\n\ -gpgsig -----BEGIN PGP SIGNATURE-----\n\ - Version: GnuPG v1.4.12 (Darwin)\n\ - \n\ - iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ - o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ - JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ - AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ - SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ - who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ - 6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ - cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ - c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ - ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ - 7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ - cpxtDQQMGYFpXK/71stq\n\ - =ozeK\n\ - -----END PGP SIGNATURE-----\n\ -\n\ -a simple commit which works\n", -/* some tools create two author entries */ -"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ -author Vicent Marti 1273848544 +0200\n\ -author Helpful Coworker 1273848544 +0200\n\ -committer Vicent Marti 1273848544 +0200\n\ -\n\ -a simple commit which works", -}; - -static int parse_commit(git_commit **out, const char *buffer) -{ - git_commit *commit; - git_odb_object fake_odb_object; - int error; - - commit = (git_commit*)git__malloc(sizeof(git_commit)); - memset(commit, 0x0, sizeof(git_commit)); - commit->object.repo = g_repo; - - memset(&fake_odb_object, 0x0, sizeof(git_odb_object)); - fake_odb_object.buffer = (char *)buffer; - fake_odb_object.cached.size = strlen(fake_odb_object.buffer); - - error = git_commit__parse(commit, &fake_odb_object); - - *out = commit; - return error; -} - -void test_commit_parse__entire_commit(void) -{ - const int failing_commit_count = ARRAY_SIZE(failing_commit_cases); - const int passing_commit_count = ARRAY_SIZE(passing_commit_cases); - int i; - git_commit *commit; - - for (i = 0; i < failing_commit_count; ++i) { - cl_git_fail(parse_commit(&commit, failing_commit_cases[i])); - git_commit__free(commit); - } - - for (i = 0; i < passing_commit_count; ++i) { - cl_git_pass(parse_commit(&commit, passing_commit_cases[i])); - - if (!i) - cl_assert_equal_s("", git_commit_message(commit)); - else - cl_assert(git__prefixcmp( - git_commit_message(commit), "a simple commit which works") == 0); - - git_commit__free(commit); - } -} - - -/* query the details on a parsed commit */ -void test_commit_parse__details0(void) { - static const char *commit_ids[] = { - "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ - "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */ - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */ - "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ - "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */ - "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */ - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", /* 6 */ - }; - const size_t commit_count = sizeof(commit_ids) / sizeof(const char *); - unsigned int i; - - for (i = 0; i < commit_count; ++i) { - git_oid id; - git_commit *commit; - - const git_signature *author, *committer; - const char *message; - git_time_t commit_time; - unsigned int parents, p; - git_commit *parent = NULL, *old_parent = NULL; - - git_oid_fromstr(&id, commit_ids[i]); - - cl_git_pass(git_commit_lookup(&commit, g_repo, &id)); - - message = git_commit_message(commit); - author = git_commit_author(commit); - committer = git_commit_committer(commit); - commit_time = git_commit_time(commit); - parents = git_commit_parentcount(commit); - - cl_assert_equal_s("Scott Chacon", author->name); - cl_assert_equal_s("schacon@gmail.com", author->email); - cl_assert_equal_s("Scott Chacon", committer->name); - cl_assert_equal_s("schacon@gmail.com", committer->email); - cl_assert(message != NULL); - cl_assert(commit_time > 0); - cl_assert(parents <= 2); - for (p = 0;p < parents;p++) { - if (old_parent != NULL) - git_commit_free(old_parent); - - old_parent = parent; - cl_git_pass(git_commit_parent(&parent, commit, p)); - cl_assert(parent != NULL); - cl_assert(git_commit_author(parent) != NULL); /* is it really a commit? */ - } - git_commit_free(old_parent); - git_commit_free(parent); - - cl_git_fail(git_commit_parent(&parent, commit, parents)); - git_commit_free(commit); - } -} - -void test_commit_parse__leading_lf(void) -{ - git_commit *commit; - const char *buffer = -"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ -parent e90810b8df3e80c413d903f631643c716887138d\n\ -author Vicent Marti 1273848544 +0200\n\ -committer Vicent Marti 1273848544 +0200\n\ -\n\ -\n\ -\n\ -This commit has a few LF at the start of the commit message"; - const char *message = -"This commit has a few LF at the start of the commit message"; - const char *raw_message = -"\n\ -\n\ -This commit has a few LF at the start of the commit message"; - cl_git_pass(parse_commit(&commit, buffer)); - cl_assert_equal_s(message, git_commit_message(commit)); - cl_assert_equal_s(raw_message, git_commit_message_raw(commit)); - git_commit__free(commit); -} - -void test_commit_parse__only_lf(void) -{ - git_commit *commit; - const char *buffer = -"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ -parent e90810b8df3e80c413d903f631643c716887138d\n\ -author Vicent Marti 1273848544 +0200\n\ -committer Vicent Marti 1273848544 +0200\n\ -\n\ -\n\ -\n"; - const char *message = ""; - const char *raw_message = "\n\n"; - - cl_git_pass(parse_commit(&commit, buffer)); - cl_assert_equal_s(message, git_commit_message(commit)); - cl_assert_equal_s(raw_message, git_commit_message_raw(commit)); - git_commit__free(commit); -} - -void test_commit_parse__arbitrary_field(void) -{ - git_commit *commit; - git_buf buf = GIT_BUF_INIT; - const char *gpgsig = "-----BEGIN PGP SIGNATURE-----\n\ -Version: GnuPG v1.4.12 (Darwin)\n\ -\n\ -iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ -o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ -JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ -AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ -SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ -who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ -6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ -cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ -c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ -ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ -7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ -cpxtDQQMGYFpXK/71stq\n\ -=ozeK\n\ ------END PGP SIGNATURE-----"; - - cl_git_pass(parse_commit(&commit, passing_commit_cases[4])); - - cl_git_pass(git_commit_header_field(&buf, commit, "tree")); - cl_assert_equal_s("6b79e22d69bf46e289df0345a14ca059dfc9bdf6", buf.ptr); - git_buf_dispose(&buf); - - cl_git_pass(git_commit_header_field(&buf, commit, "parent")); - cl_assert_equal_s("34734e478d6cf50c27c9d69026d93974d052c454", buf.ptr); - git_buf_dispose(&buf); - - cl_git_pass(git_commit_header_field(&buf, commit, "gpgsig")); - cl_assert_equal_s(gpgsig, buf.ptr); - git_buf_dispose(&buf); - - cl_git_fail_with(GIT_ENOTFOUND, git_commit_header_field(&buf, commit, "awesomeness")); - cl_git_fail_with(GIT_ENOTFOUND, git_commit_header_field(&buf, commit, "par")); - - git_commit__free(commit); - cl_git_pass(parse_commit(&commit, passing_commit_cases[0])); - - cl_git_pass(git_commit_header_field(&buf, commit, "committer")); - cl_assert_equal_s("Vicent Marti 1273848544 +0200", buf.ptr); - - git_buf_dispose(&buf); - git_commit__free(commit); -} - -void test_commit_parse__extract_signature(void) -{ - git_odb *odb; - git_oid commit_id; - git_buf signature = GIT_BUF_INIT, signed_data = GIT_BUF_INIT; - const char *gpgsig = "-----BEGIN PGP SIGNATURE-----\n\ -Version: GnuPG v1.4.12 (Darwin)\n\ -\n\ -iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ -o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ -JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ -AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ -SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ -who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ -6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ -cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ -c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ -ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ -7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ -cpxtDQQMGYFpXK/71stq\n\ -=ozeK\n\ ------END PGP SIGNATURE-----"; - - const char *data = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ -parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ -author Ben Burkert 1358451456 -0800\n\ -committer Ben Burkert 1358451456 -0800\n\ -\n\ -a simple commit which works\n"; - - const char *oneline_signature = "tree 51832e6397b30309c8bcad9c55fa6ae67778f378\n\ -parent a1b6decaaac768b5e01e1b5dbf5b2cc081bed1eb\n\ -author Some User 1454537944 -0700\n\ -committer Some User 1454537944 -0700\n\ -gpgsig bad\n\ -\n\ -corrupt signature\n"; - - const char *oneline_data = "tree 51832e6397b30309c8bcad9c55fa6ae67778f378\n\ -parent a1b6decaaac768b5e01e1b5dbf5b2cc081bed1eb\n\ -author Some User 1454537944 -0700\n\ -committer Some User 1454537944 -0700\n\ -\n\ -corrupt signature\n"; - - cl_git_pass(git_repository_odb__weakptr(&odb, g_repo)); - cl_git_pass(git_odb_write(&commit_id, odb, passing_commit_cases[4], strlen(passing_commit_cases[4]), GIT_OBJECT_COMMIT)); - - cl_git_pass(git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, NULL)); - cl_assert_equal_s(gpgsig, signature.ptr); - cl_assert_equal_s(data, signed_data.ptr); - - git_buf_dispose(&signature); - git_buf_dispose(&signed_data); - - cl_git_pass(git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, "gpgsig")); - cl_assert_equal_s(gpgsig, signature.ptr); - cl_assert_equal_s(data, signed_data.ptr); - - git_buf_dispose(&signature); - git_buf_dispose(&signed_data); - - /* Try to parse a tree */ - cl_git_pass(git_oid_fromstr(&commit_id, "45dd856fdd4d89b884c340ba0e047752d9b085d6")); - cl_git_fail_with(GIT_ENOTFOUND, git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, NULL)); - cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); - - /* Try to parse an unsigned commit */ - cl_git_pass(git_odb_write(&commit_id, odb, passing_commit_cases[1], strlen(passing_commit_cases[1]), GIT_OBJECT_COMMIT)); - cl_git_fail_with(GIT_ENOTFOUND, git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, NULL)); - cl_assert_equal_i(GIT_ERROR_OBJECT, git_error_last()->klass); - - /* Parse the commit with a single-line signature */ - cl_git_pass(git_odb_write(&commit_id, odb, oneline_signature, strlen(oneline_signature), GIT_OBJECT_COMMIT)); - cl_git_pass(git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, NULL)); - cl_assert_equal_s("bad", signature.ptr); - cl_assert_equal_s(oneline_data, signed_data.ptr); - - git_buf_dispose(&signature); - git_buf_dispose(&signed_data); -} diff --git a/tests/commit/signature.c b/tests/commit/signature.c deleted file mode 100644 index a91551415..000000000 --- a/tests/commit/signature.c +++ /dev/null @@ -1,148 +0,0 @@ -#include "clar_libgit2.h" -#include "signature.h" - -static int try_build_signature(const char *name, const char *email, git_time_t time, int offset) -{ - git_signature *sign; - int error = 0; - - if ((error = git_signature_new(&sign, name, email, time, offset)) < 0) - return error; - - git_signature_free((git_signature *)sign); - - return error; -} - -static void assert_name_and_email( - const char *expected_name, - const char *expected_email, - const char *name, - const char *email) -{ - git_signature *sign; - - cl_git_pass(git_signature_new(&sign, name, email, 1234567890, 60)); - cl_assert_equal_s(expected_name, sign->name); - cl_assert_equal_s(expected_email, sign->email); - - git_signature_free(sign); -} - -void test_commit_signature__leading_and_trailing_spaces_are_trimmed(void) -{ - assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", " nulltoken ", " emeric.fermas@gmail.com "); - assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", " nulltoken ", " emeric.fermas@gmail.com \n"); - assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", " \t nulltoken \n", " \n emeric.fermas@gmail.com \n"); -} - -void test_commit_signature__leading_and_trailing_crud_is_trimmed(void) -{ - assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", "\"nulltoken\"", "\"emeric.fermas@gmail.com\""); - assert_name_and_email("nulltoken w", "emeric.fermas@gmail.com", "nulltoken w.", "emeric.fermas@gmail.com"); - assert_name_and_email("nulltoken \xe2\x98\xba", "emeric.fermas@gmail.com", "nulltoken \xe2\x98\xba", "emeric.fermas@gmail.com"); -} - -void test_commit_signature__timezone_does_not_read_oob(void) -{ - const char *header = "A 1461698487 +1234", *header_end; - git_signature *sig; - - /* Let the buffer end midway between the timezone offeset's "+12" and "34" */ - header_end = header + strlen(header) - 2; - - sig = git__calloc(1, sizeof(git_signature)); - cl_assert(sig); - - cl_git_pass(git_signature__parse(sig, &header, header_end, NULL, '\0')); - cl_assert_equal_s(sig->name, "A"); - cl_assert_equal_s(sig->email, "a@example.com"); - cl_assert_equal_i(sig->when.time, 1461698487); - cl_assert_equal_i(sig->when.offset, 12); - - git_signature_free(sig); -} - -void test_commit_signature__angle_brackets_in_names_are_not_supported(void) -{ - cl_git_fail(try_build_signature("Haack", "phil@haack", 1234567890, 60)); - cl_git_fail(try_build_signature("", "phil@haack", 1234567890, 60)); -} - -void test_commit_signature__angle_brackets_in_email_are_not_supported(void) -{ - cl_git_fail(try_build_signature("Phil Haack", ">phil@haack", 1234567890, 60)); - cl_git_fail(try_build_signature("Phil Haack", "phil@>haack", 1234567890, 60)); - cl_git_fail(try_build_signature("Phil Haack", "", 1234567890, 60)); -} - -void test_commit_signature__create_empties(void) -{ - /* can not create a signature with empty name or email */ - cl_git_pass(try_build_signature("nulltoken", "emeric.fermas@gmail.com", 1234567890, 60)); - - cl_git_fail(try_build_signature("", "emeric.fermas@gmail.com", 1234567890, 60)); - cl_git_fail(try_build_signature(" ", "emeric.fermas@gmail.com", 1234567890, 60)); - cl_git_fail(try_build_signature("nulltoken", "", 1234567890, 60)); - cl_git_fail(try_build_signature("nulltoken", " ", 1234567890, 60)); -} - -void test_commit_signature__create_one_char(void) -{ - /* creating a one character signature */ - assert_name_and_email("x", "foo@bar.baz", "x", "foo@bar.baz"); -} - -void test_commit_signature__create_two_char(void) -{ - /* creating a two character signature */ - assert_name_and_email("xx", "foo@bar.baz", "xx", "foo@bar.baz"); -} - -void test_commit_signature__create_zero_char(void) -{ - /* creating a zero character signature */ - git_signature *sign; - cl_git_fail(git_signature_new(&sign, "", "x@y.z", 1234567890, 60)); - cl_assert(sign == NULL); -} - -void test_commit_signature__from_buf(void) -{ - git_signature *sign; - - cl_git_pass(git_signature_from_buffer(&sign, "Test User 1461698487 +0200")); - cl_assert_equal_s("Test User", sign->name); - cl_assert_equal_s("test@test.tt", sign->email); - cl_assert_equal_i(1461698487, sign->when.time); - cl_assert_equal_i(120, sign->when.offset); - git_signature_free(sign); -} - -void test_commit_signature__from_buf_with_neg_zero_offset(void) -{ - git_signature *sign; - - cl_git_pass(git_signature_from_buffer(&sign, "Test User 1461698487 -0000")); - cl_assert_equal_s("Test User", sign->name); - cl_assert_equal_s("test@test.tt", sign->email); - cl_assert_equal_i(1461698487, sign->when.time); - cl_assert_equal_i(0, sign->when.offset); - cl_assert_equal_i('-', sign->when.sign); - git_signature_free(sign); -} - -void test_commit_signature__pos_and_neg_zero_offsets_dont_match(void) -{ - git_signature *with_neg_zero; - git_signature *with_pos_zero; - - cl_git_pass(git_signature_from_buffer(&with_neg_zero, "Test User 1461698487 -0000")); - cl_git_pass(git_signature_from_buffer(&with_pos_zero, "Test User 1461698487 +0000")); - - cl_assert(!git_signature__equal(with_neg_zero, with_pos_zero)); - - git_signature_free((git_signature *)with_neg_zero); - git_signature_free((git_signature *)with_pos_zero); -} diff --git a/tests/commit/write.c b/tests/commit/write.c deleted file mode 100644 index 5a9c9d5a5..000000000 --- a/tests/commit/write.c +++ /dev/null @@ -1,424 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/commit.h" - -static const char *committer_name = "Vicent Marti"; -static const char *committer_email = "vicent@github.com"; -static const char *commit_message = "This commit has been created in memory\n\ - This is a commit created in memory and it will be written back to disk\n"; -static const char *tree_id_str = "1810dff58d8a660512d4832e740f692884338ccd"; -static const char *parent_id_str = "8496071c1b46c854b31185ea97743be6a8774479"; -static const char *root_commit_message = "This is a root commit\n\ - This is a root commit and should be the only one in this branch\n"; -static const char *root_reflog_message = "commit (initial): This is a root commit \ -This is a root commit and should be the only one in this branch"; -static char *head_old; -static git_reference *head, *branch; -static git_commit *commit; - -/* Fixture setup */ -static git_repository *g_repo; -void test_commit_write__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_commit_write__cleanup(void) -{ - git_reference_free(head); - head = NULL; - - git_reference_free(branch); - branch = NULL; - - git_commit_free(commit); - commit = NULL; - - git__free(head_old); - head_old = NULL; - - cl_git_sandbox_cleanup(); - - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 1)); -} - - -/* write a new commit object from memory to disk */ -void test_commit_write__from_memory(void) -{ - git_oid tree_id, parent_id, commit_id; - git_signature *author, *committer; - const git_signature *author1, *committer1; - git_commit *parent; - git_tree *tree; - - git_oid_fromstr(&tree_id, tree_id_str); - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - - git_oid_fromstr(&parent_id, parent_id_str); - cl_git_pass(git_commit_lookup(&parent, g_repo, &parent_id)); - - /* create signatures */ - cl_git_pass(git_signature_new(&committer, committer_name, committer_email, 123456789, 60)); - cl_git_pass(git_signature_new(&author, committer_name, committer_email, 987654321, 90)); - - cl_git_pass(git_commit_create_v( - &commit_id, /* out id */ - g_repo, - NULL, /* do not update the HEAD */ - author, - committer, - NULL, - commit_message, - tree, - 1, parent)); - - git_object_free((git_object *)parent); - git_object_free((git_object *)tree); - - git_signature_free(committer); - git_signature_free(author); - - cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id)); - - /* Check attributes were set correctly */ - author1 = git_commit_author(commit); - cl_assert(author1 != NULL); - cl_assert_equal_s(committer_name, author1->name); - cl_assert_equal_s(committer_email, author1->email); - cl_assert(author1->when.time == 987654321); - cl_assert(author1->when.offset == 90); - - committer1 = git_commit_committer(commit); - cl_assert(committer1 != NULL); - cl_assert_equal_s(committer_name, committer1->name); - cl_assert_equal_s(committer_email, committer1->email); - cl_assert(committer1->when.time == 123456789); - cl_assert(committer1->when.offset == 60); - - cl_assert_equal_s(commit_message, git_commit_message(commit)); -} - -void test_commit_write__into_buf(void) -{ - git_oid tree_id; - git_signature *author, *committer; - git_tree *tree; - git_commit *parent; - git_oid parent_id; - git_buf commit = GIT_BUF_INIT; - - git_oid_fromstr(&tree_id, tree_id_str); - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - - /* create signatures */ - cl_git_pass(git_signature_new(&committer, committer_name, committer_email, 123456789, 60)); - cl_git_pass(git_signature_new(&author, committer_name, committer_email, 987654321, 90)); - - git_oid_fromstr(&parent_id, parent_id_str); - cl_git_pass(git_commit_lookup(&parent, g_repo, &parent_id)); - - cl_git_pass(git_commit_create_buffer(&commit, g_repo, author, committer, - NULL, root_commit_message, tree, 1, (const git_commit **) &parent)); - - cl_assert_equal_s(commit.ptr, - "tree 1810dff58d8a660512d4832e740f692884338ccd\n\ -parent 8496071c1b46c854b31185ea97743be6a8774479\n\ -author Vicent Marti 987654321 +0130\n\ -committer Vicent Marti 123456789 +0100\n\ -\n\ -This is a root commit\n\ - This is a root commit and should be the only one in this branch\n\ -"); - - git_buf_dispose(&commit); - git_tree_free(tree); - git_commit_free(parent); - git_signature_free(author); - git_signature_free(committer); -} - -/* create a root commit */ -void test_commit_write__root(void) -{ - git_oid tree_id, commit_id; - const git_oid *branch_oid; - git_signature *author, *committer; - const char *branch_name = "refs/heads/root-commit-branch"; - git_tree *tree; - git_reflog *log; - const git_reflog_entry *entry; - - git_oid_fromstr(&tree_id, tree_id_str); - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - - /* create signatures */ - cl_git_pass(git_signature_new(&committer, committer_name, committer_email, 123456789, 60)); - cl_git_pass(git_signature_new(&author, committer_name, committer_email, 987654321, 90)); - - /* First we need to update HEAD so it points to our non-existent branch */ - cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); - cl_assert(git_reference_type(head) == GIT_REFERENCE_SYMBOLIC); - head_old = git__strdup(git_reference_symbolic_target(head)); - cl_assert(head_old != NULL); - git_reference_free(head); - - cl_git_pass(git_reference_symbolic_create(&head, g_repo, "HEAD", branch_name, 1, NULL)); - - cl_git_pass(git_commit_create_v( - &commit_id, /* out id */ - g_repo, - "HEAD", - author, - committer, - NULL, - root_commit_message, - tree, - 0)); - - git_object_free((git_object *)tree); - git_signature_free(author); - - /* - * The fact that creating a commit works has already been - * tested. Here we just make sure it's our commit and that it was - * written as a root commit. - */ - cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id)); - cl_assert(git_commit_parentcount(commit) == 0); - cl_git_pass(git_reference_lookup(&branch, g_repo, branch_name)); - branch_oid = git_reference_target(branch); - cl_assert_equal_oid(branch_oid, &commit_id); - cl_assert_equal_s(root_commit_message, git_commit_message(commit)); - - cl_git_pass(git_reflog_read(&log, g_repo, branch_name)); - cl_assert_equal_i(1, git_reflog_entrycount(log)); - entry = git_reflog_entry_byindex(log, 0); - cl_assert_equal_s(committer->email, git_reflog_entry_committer(entry)->email); - cl_assert_equal_s(committer->name, git_reflog_entry_committer(entry)->name); - cl_assert_equal_s(root_reflog_message, git_reflog_entry_message(entry)); - - git_signature_free(committer); - git_reflog_free(log); -} - -static int create_commit_from_ids( - git_oid *result, - const git_oid *tree_id, - const git_oid *parent_id) -{ - git_signature *author, *committer; - const git_oid *parent_ids[1]; - int ret; - - cl_git_pass(git_signature_new( - &committer, committer_name, committer_email, 123456789, 60)); - cl_git_pass(git_signature_new( - &author, committer_name, committer_email, 987654321, 90)); - - parent_ids[0] = parent_id; - - ret = git_commit_create_from_ids( - result, - g_repo, - NULL, - author, - committer, - NULL, - root_commit_message, - tree_id, - 1, - parent_ids); - - git_signature_free(committer); - git_signature_free(author); - - return ret; -} - -void test_commit_write__can_write_invalid_objects(void) -{ - git_oid expected_id, tree_id, parent_id, commit_id; - - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 0)); - - /* this is a valid tree and parent */ - git_oid_fromstr(&tree_id, tree_id_str); - git_oid_fromstr(&parent_id, parent_id_str); - - git_oid_fromstr(&expected_id, "c8571bbec3a72c4bcad31648902e5a453f1adece"); - cl_git_pass(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); - cl_assert_equal_oid(&expected_id, &commit_id); - - /* this is a wholly invented tree id */ - git_oid_fromstr(&tree_id, "1234567890123456789012345678901234567890"); - git_oid_fromstr(&parent_id, parent_id_str); - - git_oid_fromstr(&expected_id, "996008340b8e68d69bf3c28d7c57fb7ec3c8e202"); - cl_git_pass(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); - cl_assert_equal_oid(&expected_id, &commit_id); - - /* this is a wholly invented parent id */ - git_oid_fromstr(&tree_id, tree_id_str); - git_oid_fromstr(&parent_id, "1234567890123456789012345678901234567890"); - - git_oid_fromstr(&expected_id, "d78f660cab89d9791ca6714b57978bf2a7e709fd"); - cl_git_pass(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); - cl_assert_equal_oid(&expected_id, &commit_id); - - /* these are legitimate objects, but of the wrong type */ - git_oid_fromstr(&tree_id, parent_id_str); - git_oid_fromstr(&parent_id, tree_id_str); - - git_oid_fromstr(&expected_id, "5d80c07414e3f18792949699dfcacadf7748f361"); - cl_git_pass(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); - cl_assert_equal_oid(&expected_id, &commit_id); -} - -void test_commit_write__can_validate_objects(void) -{ - git_oid tree_id, parent_id, commit_id; - - /* this is a valid tree and parent */ - git_oid_fromstr(&tree_id, tree_id_str); - git_oid_fromstr(&parent_id, parent_id_str); - cl_git_pass(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); - - /* this is a wholly invented tree id */ - git_oid_fromstr(&tree_id, "1234567890123456789012345678901234567890"); - git_oid_fromstr(&parent_id, parent_id_str); - cl_git_fail(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); - - /* this is a wholly invented parent id */ - git_oid_fromstr(&tree_id, tree_id_str); - git_oid_fromstr(&parent_id, "1234567890123456789012345678901234567890"); - cl_git_fail(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); - - /* these are legitimate objects, but of the wrong type */ - git_oid_fromstr(&tree_id, parent_id_str); - git_oid_fromstr(&parent_id, tree_id_str); - cl_git_fail(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); -} - -void test_commit_write__attach_signature_checks_objects(void) -{ - const char *sig = "magic word: pretty please"; - const char *badtree = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ -parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ -author Ben Burkert 1358451456 -0800\n\ -committer Ben Burkert 1358451456 -0800\n\ -\n\ -a simple commit which does not work\n"; - - const char *badparent = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ -parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ -author Ben Burkert 1358451456 -0800\n\ -committer Ben Burkert 1358451456 -0800\n\ -\n\ -a simple commit which does not work\n"; - - git_oid id; - - cl_git_fail_with(-1, git_commit_create_with_signature(&id, g_repo, badtree, sig, "magicsig")); - cl_git_fail_with(-1, git_commit_create_with_signature(&id, g_repo, badparent, sig, "magicsig")); - -} - -void test_commit_write__attach_singleline_signature(void) -{ - const char *sig = "magic word: pretty please"; - - const char *data = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ -parent 8496071c1b46c854b31185ea97743be6a8774479\n\ -author Ben Burkert 1358451456 -0800\n\ -committer Ben Burkert 1358451456 -0800\n\ -\n\ -a simple commit which works\n"; - - const char *complete = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ -parent 8496071c1b46c854b31185ea97743be6a8774479\n\ -author Ben Burkert 1358451456 -0800\n\ -committer Ben Burkert 1358451456 -0800\n\ -magicsig magic word: pretty please\n\ -\n\ -a simple commit which works\n"; - - git_oid id; - git_odb *odb; - git_odb_object *obj; - - cl_git_pass(git_commit_create_with_signature(&id, g_repo, data, sig, "magicsig")); - - cl_git_pass(git_repository_odb(&odb, g_repo)); - cl_git_pass(git_odb_read(&obj, odb, &id)); - cl_assert_equal_s(complete, git_odb_object_data(obj)); - - git_odb_object_free(obj); - git_odb_free(odb); -} - -void test_commit_write__attach_multiline_signature(void) -{ - const char *gpgsig = "-----BEGIN PGP SIGNATURE-----\n\ -Version: GnuPG v1.4.12 (Darwin)\n\ -\n\ -iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ -o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ -JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ -AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ -SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ -who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ -6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ -cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ -c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ -ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ -7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ -cpxtDQQMGYFpXK/71stq\n\ -=ozeK\n\ ------END PGP SIGNATURE-----"; - - const char *data = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ -parent 8496071c1b46c854b31185ea97743be6a8774479\n\ -author Ben Burkert 1358451456 -0800\n\ -committer Ben Burkert 1358451456 -0800\n\ -\n\ -a simple commit which works\n"; - -const char *complete = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ -parent 8496071c1b46c854b31185ea97743be6a8774479\n\ -author Ben Burkert 1358451456 -0800\n\ -committer Ben Burkert 1358451456 -0800\n\ -gpgsig -----BEGIN PGP SIGNATURE-----\n\ - Version: GnuPG v1.4.12 (Darwin)\n\ - \n\ - iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ - o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ - JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ - AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ - SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ - who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ - 6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ - cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ - c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ - ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ - 7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ - cpxtDQQMGYFpXK/71stq\n\ - =ozeK\n\ - -----END PGP SIGNATURE-----\n\ -\n\ -a simple commit which works\n"; - - git_oid one, two; - git_odb *odb; - git_odb_object *obj; - - cl_git_pass(git_commit_create_with_signature(&one, g_repo, data, gpgsig, "gpgsig")); - cl_git_pass(git_commit_create_with_signature(&two, g_repo, data, gpgsig, NULL)); - - cl_assert(!git_oid_cmp(&one, &two)); - cl_git_pass(git_repository_odb(&odb, g_repo)); - cl_git_pass(git_odb_read(&obj, odb, &one)); - cl_assert_equal_s(complete, git_odb_object_data(obj)); - - git_odb_object_free(obj); - git_odb_free(odb); -} diff --git a/tests/config/add.c b/tests/config/add.c deleted file mode 100644 index 405f1e2c9..000000000 --- a/tests/config/add.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "clar_libgit2.h" - -void test_config_add__initialize(void) -{ - cl_fixture_sandbox("config/config10"); -} - -void test_config_add__cleanup(void) -{ - cl_fixture_cleanup("config10"); -} - -void test_config_add__to_existing_section(void) -{ - git_config *cfg; - int32_t i; - - cl_git_pass(git_config_open_ondisk(&cfg, "config10")); - cl_git_pass(git_config_set_int32(cfg, "empty.tmp", 5)); - cl_git_pass(git_config_get_int32(&i, cfg, "empty.tmp")); - cl_assert(i == 5); - cl_git_pass(git_config_delete_entry(cfg, "empty.tmp")); - git_config_free(cfg); -} - -void test_config_add__to_new_section(void) -{ - git_config *cfg; - int32_t i; - - cl_git_pass(git_config_open_ondisk(&cfg, "config10")); - cl_git_pass(git_config_set_int32(cfg, "section.tmp", 5)); - cl_git_pass(git_config_get_int32(&i, cfg, "section.tmp")); - cl_assert(i == 5); - cl_git_pass(git_config_delete_entry(cfg, "section.tmp")); - git_config_free(cfg); -} diff --git a/tests/config/backend.c b/tests/config/backend.c deleted file mode 100644 index 96feecc4a..000000000 --- a/tests/config/backend.c +++ /dev/null @@ -1,24 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/config.h" - -void test_config_backend__checks_version(void) -{ - git_config *cfg; - git_config_backend backend = GIT_CONFIG_BACKEND_INIT; - const git_error *err; - - backend.version = 1024; - - cl_git_pass(git_config_new(&cfg)); - cl_git_fail(git_config_add_backend(cfg, &backend, 0, NULL, false)); - err = git_error_last(); - cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); - - git_error_clear(); - backend.version = 1024; - cl_git_fail(git_config_add_backend(cfg, &backend, 0, NULL, false)); - err = git_error_last(); - cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); - - git_config_free(cfg); -} diff --git a/tests/config/conditionals.c b/tests/config/conditionals.c deleted file mode 100644 index 564719dcb..000000000 --- a/tests/config/conditionals.c +++ /dev/null @@ -1,175 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "repository.h" - -#ifdef GIT_WIN32 -# define ROOT_PREFIX "C:" -#else -# define ROOT_PREFIX -#endif - -static git_repository *_repo; - -void test_config_conditionals__initialize(void) -{ - _repo = cl_git_sandbox_init("empty_standard_repo"); -} - -void test_config_conditionals__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void assert_condition_includes(const char *keyword, const char *path, bool expected) -{ - git_buf value = GIT_BUF_INIT; - git_str buf = GIT_STR_INIT; - git_config *cfg; - - cl_git_pass(git_str_printf(&buf, "[includeIf \"%s:%s\"]\n", keyword, path)); - cl_git_pass(git_str_puts(&buf, "path = other\n")); - - cl_git_mkfile("empty_standard_repo/.git/config", buf.ptr); - cl_git_mkfile("empty_standard_repo/.git/other", "[foo]\nbar=baz\n"); - _repo = cl_git_sandbox_reopen(); - - git_str_dispose(&buf); - - cl_git_pass(git_repository_config(&cfg, _repo)); - - if (expected) { - cl_git_pass(git_config_get_string_buf(&value, cfg, "foo.bar")); - cl_assert_equal_s("baz", value.ptr); - } else { - cl_git_fail_with(GIT_ENOTFOUND, - git_config_get_string_buf(&value, cfg, "foo.bar")); - } - - git_str_dispose(&buf); - git_buf_dispose(&value); - git_config_free(cfg); -} - -static char *sandbox_path(git_str *buf, const char *suffix) -{ - char *path = p_realpath(clar_sandbox_path(), NULL); - cl_assert(path); - cl_git_pass(git_str_attach(buf, path, 0)); - cl_git_pass(git_str_joinpath(buf, buf->ptr, suffix)); - return buf->ptr; -} - -void test_config_conditionals__gitdir(void) -{ - git_str path = GIT_STR_INIT; - - assert_condition_includes("gitdir", ROOT_PREFIX "/", true); - assert_condition_includes("gitdir", "empty_stand", false); - assert_condition_includes("gitdir", "empty_stand/", false); - assert_condition_includes("gitdir", "empty_stand/.git", false); - assert_condition_includes("gitdir", "empty_stand/.git/", false); - assert_condition_includes("gitdir", "empty_stand*/", true); - assert_condition_includes("gitdir", "empty_stand*/.git", true); - assert_condition_includes("gitdir", "empty_stand*/.git/", false); - assert_condition_includes("gitdir", "empty_standard_repo", false); - assert_condition_includes("gitdir", "empty_standard_repo/", true); - assert_condition_includes("gitdir", "empty_standard_repo/.git", true); - assert_condition_includes("gitdir", "empty_standard_repo/.git/", false); - - assert_condition_includes("gitdir", "./", false); - - assert_condition_includes("gitdir", ROOT_PREFIX "/nonexistent", false); - assert_condition_includes("gitdir", ROOT_PREFIX "/empty_standard_repo", false); - assert_condition_includes("gitdir", "~/empty_standard_repo", false); - - assert_condition_includes("gitdir", sandbox_path(&path, "/"), true); - assert_condition_includes("gitdir", sandbox_path(&path, "/*"), false); - assert_condition_includes("gitdir", sandbox_path(&path, "/**"), true); - - assert_condition_includes("gitdir", sandbox_path(&path, "empty_standard_repo"), false); - assert_condition_includes("gitdir", sandbox_path(&path, "empty_standard_repo/"), true); - assert_condition_includes("gitdir", sandbox_path(&path, "empty_standard_repo/"), true); - assert_condition_includes("gitdir", sandbox_path(&path, "Empty_Standard_Repo"), false); - assert_condition_includes("gitdir", sandbox_path(&path, "Empty_Standard_Repo/"), false); - - git_str_dispose(&path); -} - -void test_config_conditionals__gitdir_i(void) -{ - git_str path = GIT_STR_INIT; - - assert_condition_includes("gitdir/i", sandbox_path(&path, "empty_standard_repo/"), true); - assert_condition_includes("gitdir/i", sandbox_path(&path, "EMPTY_STANDARD_REPO/"), true); - - git_str_dispose(&path); -} - -void test_config_conditionals__invalid_conditional_fails(void) -{ - assert_condition_includes("foobar", ".git", false); -} - -static void set_head(git_repository *repo, const char *name) -{ - cl_git_pass(git_repository_create_head(git_repository_path(repo), name)); -} - -void test_config_conditionals__onbranch(void) -{ - assert_condition_includes("onbranch", "master", true); - assert_condition_includes("onbranch", "m*", true); - assert_condition_includes("onbranch", "*", true); - assert_condition_includes("onbranch", "master/", false); - assert_condition_includes("onbranch", "foo", false); - - set_head(_repo, "foo"); - assert_condition_includes("onbranch", "master", false); - assert_condition_includes("onbranch", "foo", true); - assert_condition_includes("onbranch", "f*o", true); - - set_head(_repo, "dir/ref"); - assert_condition_includes("onbranch", "dir/ref", true); - assert_condition_includes("onbranch", "dir/", true); - assert_condition_includes("onbranch", "dir/*", true); - assert_condition_includes("onbranch", "dir/**", true); - assert_condition_includes("onbranch", "**", true); - assert_condition_includes("onbranch", "dir", false); - assert_condition_includes("onbranch", "dir*", false); - - set_head(_repo, "dir/subdir/ref"); - assert_condition_includes("onbranch", "dir/subdir/", true); - assert_condition_includes("onbranch", "dir/subdir/*", true); - assert_condition_includes("onbranch", "dir/subdir/ref", true); - assert_condition_includes("onbranch", "dir/", true); - assert_condition_includes("onbranch", "dir/**", true); - assert_condition_includes("onbranch", "**", true); - assert_condition_includes("onbranch", "dir", false); - assert_condition_includes("onbranch", "dir*", false); - assert_condition_includes("onbranch", "dir/*", false); -} - -void test_config_conditionals__empty(void) -{ - git_buf value = GIT_BUF_INIT; - git_str buf = GIT_STR_INIT; - git_config *cfg; - - cl_git_pass(git_str_puts(&buf, "[includeIf]\n")); - cl_git_pass(git_str_puts(&buf, "path = other\n")); - - cl_git_mkfile("empty_standard_repo/.git/config", buf.ptr); - cl_git_mkfile("empty_standard_repo/.git/other", "[foo]\nbar=baz\n"); - _repo = cl_git_sandbox_reopen(); - - git_str_dispose(&buf); - - cl_git_pass(git_repository_config(&cfg, _repo)); - - cl_git_fail_with(GIT_ENOTFOUND, - git_config_get_string_buf(&value, cfg, "foo.bar")); - - git_str_dispose(&buf); - git_buf_dispose(&value); - git_config_free(cfg); -} diff --git a/tests/config/config_helpers.c b/tests/config/config_helpers.c deleted file mode 100644 index ecdab5bf6..000000000 --- a/tests/config/config_helpers.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "clar_libgit2.h" -#include "config_helpers.h" -#include "repository.h" - -void assert_config_entry_existence( - git_repository *repo, - const char *name, - bool is_supposed_to_exist) -{ - git_config *config; - git_config_entry *entry = NULL; - int result; - - cl_git_pass(git_repository_config__weakptr(&config, repo)); - - result = git_config_get_entry(&entry, config, name); - git_config_entry_free(entry); - - if (is_supposed_to_exist) - cl_git_pass(result); - else - cl_assert_equal_i(GIT_ENOTFOUND, result); -} - -void assert_config_entry_value( - git_repository *repo, - const char *name, - const char *expected_value) -{ - git_config *config; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_repository_config__weakptr(&config, repo)); - - cl_git_pass(git_config_get_string_buf(&buf, config, name)); - - cl_assert_equal_s(expected_value, buf.ptr); - git_buf_dispose(&buf); -} - -static int count_config_entries_cb( - const git_config_entry *entry, - void *payload) -{ - int *how_many = (int *)payload; - - GIT_UNUSED(entry); - - (*how_many)++; - - return 0; -} - -int count_config_entries_match(git_repository *repo, const char *pattern) -{ - git_config *config; - int how_many = 0; - - cl_git_pass(git_repository_config(&config, repo)); - - cl_assert_equal_i(0, git_config_foreach_match( - config, pattern, count_config_entries_cb, &how_many)); - - git_config_free(config); - - return how_many; -} diff --git a/tests/config/config_helpers.h b/tests/config/config_helpers.h deleted file mode 100644 index 440645730..000000000 --- a/tests/config/config_helpers.h +++ /dev/null @@ -1,13 +0,0 @@ -extern void assert_config_entry_existence( - git_repository *repo, - const char *name, - bool is_supposed_to_exist); - -extern void assert_config_entry_value( - git_repository *repo, - const char *name, - const char *expected_value); - -extern int count_config_entries_match( - git_repository *repo, - const char *pattern); diff --git a/tests/config/configlevel.c b/tests/config/configlevel.c deleted file mode 100644 index 8422d32c9..000000000 --- a/tests/config/configlevel.c +++ /dev/null @@ -1,73 +0,0 @@ -#include "clar_libgit2.h" - -void test_config_configlevel__adding_the_same_level_twice_returns_EEXISTS(void) -{ - int error; - git_config *cfg; - - cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), - GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - error = git_config_add_file_ondisk(cfg, cl_fixture("config/config16"), - GIT_CONFIG_LEVEL_GLOBAL, NULL, 0); - - cl_git_fail(error); - cl_assert_equal_i(GIT_EEXISTS, error); - - git_config_free(cfg); -} - -void test_config_configlevel__can_replace_a_config_file_at_an_existing_level(void) -{ - git_config *cfg; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), - GIT_CONFIG_LEVEL_LOCAL, NULL, 1)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), - GIT_CONFIG_LEVEL_LOCAL, NULL, 1)); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.stringglobal")); - cl_assert_equal_s("don't find me!", buf.ptr); - - git_buf_dispose(&buf); - git_config_free(cfg); -} - -void test_config_configlevel__can_read_from_a_single_level_focused_file_after_parent_config_has_been_freed(void) -{ - git_config *cfg; - git_config *single_level_cfg; - git_buf buf = {0}; - - cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), - GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), - GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); - - cl_git_pass(git_config_open_level(&single_level_cfg, cfg, GIT_CONFIG_LEVEL_LOCAL)); - - git_config_free(cfg); - - cl_git_pass(git_config_get_string_buf(&buf, single_level_cfg, "core.stringglobal")); - cl_assert_equal_s("don't find me!", buf.ptr); - - git_buf_dispose(&buf); - git_config_free(single_level_cfg); -} - -void test_config_configlevel__fetching_a_level_from_an_empty_compound_config_returns_ENOTFOUND(void) -{ - git_config *cfg; - git_config *local_cfg; - - cl_git_pass(git_config_new(&cfg)); - - cl_assert_equal_i(GIT_ENOTFOUND, git_config_open_level(&local_cfg, cfg, GIT_CONFIG_LEVEL_LOCAL)); - - git_config_free(cfg); -} diff --git a/tests/config/global.c b/tests/config/global.c deleted file mode 100644 index 5aba4eec6..000000000 --- a/tests/config/global.c +++ /dev/null @@ -1,172 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" - -void test_config_global__initialize(void) -{ - git_str path = GIT_STR_INIT; - - cl_git_pass(git_futils_mkdir_r("home", 0777)); - cl_git_pass(git_fs_path_prettify(&path, "home", NULL)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); - - cl_git_pass(git_futils_mkdir_r("xdg/git", 0777)); - cl_git_pass(git_fs_path_prettify(&path, "xdg/git", NULL)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); - - cl_git_pass(git_futils_mkdir_r("etc", 0777)); - cl_git_pass(git_fs_path_prettify(&path, "etc", NULL)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); - - git_str_dispose(&path); -} - -void test_config_global__cleanup(void) -{ - cl_sandbox_set_search_path_defaults(); - cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_git_pass(git_futils_rmdir_r("xdg", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_git_pass(git_futils_rmdir_r("etc", NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_config_global__open_global(void) -{ - git_config *cfg, *global, *selected, *dummy; - int32_t value; - - cl_git_mkfile("home/.gitconfig", "[global]\n test = 4567\n"); - - cl_git_pass(git_config_open_default(&cfg)); - cl_git_pass(git_config_get_int32(&value, cfg, "global.test")); - cl_assert_equal_i(4567, value); - - cl_git_pass(git_config_open_level(&global, cfg, GIT_CONFIG_LEVEL_GLOBAL)); - cl_git_pass(git_config_get_int32(&value, global, "global.test")); - cl_assert_equal_i(4567, value); - - cl_git_fail(git_config_open_level(&dummy, cfg, GIT_CONFIG_LEVEL_XDG)); - - cl_git_pass(git_config_open_global(&selected, cfg)); - cl_git_pass(git_config_get_int32(&value, selected, "global.test")); - cl_assert_equal_i(4567, value); - - git_config_free(selected); - git_config_free(global); - git_config_free(cfg); -} - -void test_config_global__open_symlinked_global(void) -{ -#ifndef GIT_WIN32 - git_config *cfg; - int32_t value; - - cl_git_mkfile("home/.gitconfig.linked", "[global]\n test = 4567\n"); - cl_must_pass(symlink(".gitconfig.linked", "home/.gitconfig")); - - cl_git_pass(git_config_open_default(&cfg)); - cl_git_pass(git_config_get_int32(&value, cfg, "global.test")); - cl_assert_equal_i(4567, value); - - git_config_free(cfg); -#endif -} - -void test_config_global__lock_missing_global_config(void) -{ - git_config *cfg; - git_config_entry *entry; - git_transaction *transaction; - - (void)p_unlink("home/.gitconfig"); /* No global config */ - - cl_git_pass(git_config_open_default(&cfg)); - cl_git_pass(git_config_lock(&transaction, cfg)); - cl_git_pass(git_config_set_string(cfg, "assertion.fail", "boom")); - cl_git_pass(git_transaction_commit(transaction)); - git_transaction_free(transaction); - - /* cfg is updated */ - cl_git_pass(git_config_get_entry(&entry, cfg, "assertion.fail")); - cl_assert_equal_s("boom", entry->value); - - git_config_entry_free(entry); - git_config_free(cfg); - - /* We can reread the new value from the global config */ - cl_git_pass(git_config_open_default(&cfg)); - cl_git_pass(git_config_get_entry(&entry, cfg, "assertion.fail")); - cl_assert_equal_s("boom", entry->value); - - git_config_entry_free(entry); - git_config_free(cfg); -} - -void test_config_global__open_xdg(void) -{ - git_config *cfg, *xdg, *selected; - const char *str = "teststring"; - const char *key = "this.variable"; - git_buf buf = {0}; - - cl_git_mkfile("xdg/git/config", "# XDG config\n[core]\n test = 1\n"); - - cl_git_pass(git_config_open_default(&cfg)); - cl_git_pass(git_config_open_level(&xdg, cfg, GIT_CONFIG_LEVEL_XDG)); - cl_git_pass(git_config_open_global(&selected, cfg)); - - cl_git_pass(git_config_set_string(xdg, key, str)); - cl_git_pass(git_config_get_string_buf(&buf, selected, key)); - cl_assert_equal_s(str, buf.ptr); - - git_buf_dispose(&buf); - git_config_free(selected); - git_config_free(xdg); - git_config_free(cfg); -} - -void test_config_global__open_programdata(void) -{ - git_config *cfg; - git_repository *repo; - git_buf dir_path = GIT_BUF_INIT; - git_str config_path = GIT_STR_INIT; - git_buf var_contents = GIT_BUF_INIT; - - if (cl_is_env_set("GITTEST_INVASIVE_FS_STRUCTURE")) - cl_skip(); - - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, - GIT_CONFIG_LEVEL_PROGRAMDATA, &dir_path)); - - if (!git_fs_path_isdir(dir_path.ptr)) - cl_git_pass(p_mkdir(dir_path.ptr, 0777)); - - cl_git_pass(git_str_joinpath(&config_path, dir_path.ptr, "config")); - - cl_git_pass(git_config_open_ondisk(&cfg, config_path.ptr)); - cl_git_pass(git_config_set_string(cfg, "programdata.var", "even higher level")); - - git_str_dispose(&config_path); - git_config_free(cfg); - - git_config_open_default(&cfg); - cl_git_pass(git_config_get_string_buf(&var_contents, cfg, "programdata.var")); - cl_assert_equal_s("even higher level", var_contents.ptr); - - git_config_free(cfg); - git_buf_dispose(&var_contents); - - cl_git_pass(git_repository_init(&repo, "./foo.git", true)); - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass(git_config_get_string_buf(&var_contents, cfg, "programdata.var")); - cl_assert_equal_s("even higher level", var_contents.ptr); - - git_config_free(cfg); - git_buf_dispose(&dir_path); - git_buf_dispose(&var_contents); - git_repository_free(repo); - cl_fixture_cleanup("./foo.git"); -} diff --git a/tests/config/include.c b/tests/config/include.c deleted file mode 100644 index 9328f3cf6..000000000 --- a/tests/config/include.c +++ /dev/null @@ -1,259 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" - -static git_config *cfg; -static git_buf buf; - -void test_config_include__initialize(void) -{ - cfg = NULL; - memset(&buf, 0, sizeof(git_buf)); -} - -void test_config_include__cleanup(void) -{ - git_config_free(cfg); - git_buf_dispose(&buf); -} - -void test_config_include__relative(void) -{ - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config-include"))); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); - cl_assert_equal_s("huzzah", buf.ptr); -} - -void test_config_include__absolute(void) -{ - git_str cfgdata = GIT_STR_INIT; - cl_git_pass(git_str_printf(&cfgdata, "[include]\npath = %s/config-included", cl_fixture("config"))); - - cl_git_mkfile("config-include-absolute", cfgdata.ptr); - git_str_dispose(&cfgdata); - - cl_git_pass(git_config_open_ondisk(&cfg, "config-include-absolute")); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); - cl_assert_equal_s("huzzah", buf.ptr); - - cl_git_pass(p_unlink("config-include-absolute")); -} - -void test_config_include__homedir(void) -{ - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, cl_fixture("config"))); - cl_git_mkfile("config-include-homedir", "[include]\npath = ~/config-included"); - - cl_git_pass(git_config_open_ondisk(&cfg, "config-include-homedir")); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); - cl_assert_equal_s("huzzah", buf.ptr); - - cl_sandbox_set_search_path_defaults(); - - cl_git_pass(p_unlink("config-include-homedir")); -} - -/* We need to pretend that the variables were defined where the file was included */ -void test_config_include__ordering(void) -{ - cl_git_mkfile("included", "[foo \"bar\"]\nbaz = hurrah\nfrotz = hiya"); - cl_git_mkfile("including", - "[foo \"bar\"]\nfrotz = hello\n" - "[include]\npath = included\n" - "[foo \"bar\"]\nbaz = huzzah\n"); - - cl_git_pass(git_config_open_ondisk(&cfg, "including")); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.frotz")); - cl_assert_equal_s("hiya", buf.ptr); - git_buf_dispose(&buf); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); - cl_assert_equal_s("huzzah", buf.ptr); - - cl_git_pass(p_unlink("included")); - cl_git_pass(p_unlink("including")); -} - -/* We need to pretend that the variables were defined where the file was included */ -void test_config_include__depth(void) -{ - cl_git_mkfile("a", "[include]\npath = b"); - cl_git_mkfile("b", "[include]\npath = a"); - - cl_git_fail(git_config_open_ondisk(&cfg, "a")); - - cl_git_pass(p_unlink("a")); - cl_git_pass(p_unlink("b")); -} - -void test_config_include__empty_path_sanely_handled(void) -{ - cl_git_mkfile("a", "[include]\npath"); - cl_git_pass(git_config_open_ondisk(&cfg, "a")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "include.path")); - cl_assert_equal_s("", buf.ptr); - - cl_git_pass(p_unlink("a")); -} - -void test_config_include__missing(void) -{ - cl_git_mkfile("including", "[include]\npath = nonexistentfile\n[foo]\nbar = baz"); - - git_error_clear(); - cl_git_pass(git_config_open_ondisk(&cfg, "including")); - cl_assert(git_error_last() == NULL); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); - cl_assert_equal_s("baz", buf.ptr); - - cl_git_pass(p_unlink("including")); -} - -void test_config_include__missing_homedir(void) -{ - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, cl_fixture("config"))); - cl_git_mkfile("including", "[include]\npath = ~/.nonexistentfile\n[foo]\nbar = baz"); - - git_error_clear(); - cl_git_pass(git_config_open_ondisk(&cfg, "including")); - cl_assert(git_error_last() == NULL); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); - cl_assert_equal_s("baz", buf.ptr); - - cl_sandbox_set_search_path_defaults(); - cl_git_pass(p_unlink("including")); -} - -#define replicate10(s) s s s s s s s s s s -void test_config_include__depth2(void) -{ - const char *content = "[include]\n" replicate10(replicate10("path=bottom\n")); - - cl_git_mkfile("top-level", "[include]\npath = middle\n[foo]\nbar = baz"); - cl_git_mkfile("middle", content); - cl_git_mkfile("bottom", "[foo]\nbar2 = baz2"); - - cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); - cl_assert_equal_s("baz", buf.ptr); - - git_buf_dispose(&buf); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar2")); - cl_assert_equal_s("baz2", buf.ptr); - - cl_git_pass(p_unlink("top-level")); - cl_git_pass(p_unlink("middle")); - cl_git_pass(p_unlink("bottom")); -} - -void test_config_include__removing_include_removes_values(void) -{ - cl_git_mkfile("top-level", "[include]\npath = included"); - cl_git_mkfile("included", "[foo]\nbar = value"); - - cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); - cl_git_mkfile("top-level", ""); - cl_git_fail(git_config_get_string_buf(&buf, cfg, "foo.bar")); - - cl_git_pass(p_unlink("top-level")); - cl_git_pass(p_unlink("included")); -} - -void test_config_include__rewriting_include_refreshes_values(void) -{ - cl_git_mkfile("top-level", "[include]\npath = first\n[include]\npath = second"); - cl_git_mkfile("first", "[first]\nfoo = bar"); - cl_git_mkfile("second", "[second]\nfoo = bar"); - - cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); - cl_git_mkfile("first", "[first]\nother = value"); - cl_git_fail(git_config_get_string_buf(&buf, cfg, "foo.bar")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "first.other")); - cl_assert_equal_s(buf.ptr, "value"); - - cl_git_pass(p_unlink("top-level")); - cl_git_pass(p_unlink("first")); - cl_git_pass(p_unlink("second")); -} - -void test_config_include__rewriting_include_twice_refreshes_values(void) -{ - cl_git_mkfile("top-level", "[include]\npath = included"); - cl_git_mkfile("included", "[foo]\nbar = first-value"); - - cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); - - git_buf_dispose(&buf); - cl_git_mkfile("included", "[foo]\nother = value2"); - cl_git_fail(git_config_get_string_buf(&buf, cfg, "foo.bar")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.other")); - cl_assert_equal_s(buf.ptr, "value2"); - - git_buf_dispose(&buf); - cl_git_mkfile("included", "[foo]\nanother = bar"); - cl_git_fail(git_config_get_string_buf(&buf, cfg, "foo.other")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.another")); - cl_assert_equal_s(buf.ptr, "bar"); - - cl_git_pass(p_unlink("top-level")); - cl_git_pass(p_unlink("included")); -} - -void test_config_include__included_variables_cannot_be_deleted(void) -{ - cl_git_mkfile("top-level", "[include]\npath = included\n"); - cl_git_mkfile("included", "[foo]\nbar = value"); - - cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); - cl_git_fail(git_config_delete_entry(cfg, "foo.bar")); - - cl_git_pass(p_unlink("top-level")); - cl_git_pass(p_unlink("included")); -} - -void test_config_include__included_variables_cannot_be_modified(void) -{ - cl_git_mkfile("top-level", "[include]\npath = included\n"); - - cl_git_mkfile("included", "[foo]\nbar = value"); - - cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); - cl_git_fail(git_config_set_string(cfg, "foo.bar", "other-value")); - - cl_git_pass(p_unlink("top-level")); - cl_git_pass(p_unlink("included")); -} - -void test_config_include__variables_in_included_override_including(void) -{ - int i; - - cl_git_mkfile("top-level", "[foo]\nbar = 1\n[include]\npath = included"); - cl_git_mkfile("included", "[foo]\nbar = 2"); - - cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); - cl_git_pass(git_config_get_int32(&i, cfg, "foo.bar")); - cl_assert_equal_i(i, 2); - - cl_git_pass(p_unlink("top-level")); - cl_git_pass(p_unlink("included")); -} - -void test_config_include__variables_in_including_override_included(void) -{ - int i; - - cl_git_mkfile("top-level", "[include]\npath = included\n[foo]\nbar = 1"); - cl_git_mkfile("included", "[foo]\nbar = 2"); - - cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); - cl_git_pass(git_config_get_int32(&i, cfg, "foo.bar")); - cl_assert_equal_i(i, 1); - - cl_git_pass(p_unlink("top-level")); - cl_git_pass(p_unlink("included")); -} diff --git a/tests/config/memory.c b/tests/config/memory.c deleted file mode 100644 index ae661899d..000000000 --- a/tests/config/memory.c +++ /dev/null @@ -1,139 +0,0 @@ -#include "clar_libgit2.h" - -#include "config_backend.h" - -static git_config_backend *backend; - -void test_config_memory__initialize(void) -{ - backend = NULL; -} - -void test_config_memory__cleanup(void) -{ - git_config_backend_free(backend); -} - -static void assert_config_contains(git_config_backend *backend, - const char *name, const char *value) -{ - git_config_entry *entry; - cl_git_pass(git_config_backend_get_string(&entry, backend, name)); - cl_assert_equal_s(entry->value, value); -} - -struct expected_entry { - const char *name; - const char *value; - int seen; -}; - -static int contains_all_cb(const git_config_entry *entry, void *payload) -{ - struct expected_entry *entries = (struct expected_entry *) payload; - int i; - - for (i = 0; entries[i].name; i++) { - if (strcmp(entries[i].name, entry->name) || - strcmp(entries[i].value , entry->value)) - continue; - - if (entries[i].seen) - cl_fail("Entry seen more than once"); - entries[i].seen = 1; - return 0; - } - - cl_fail("Unexpected entry"); - return -1; -} - -static void assert_config_contains_all(git_config_backend *backend, - struct expected_entry *entries) -{ - int i; - - cl_git_pass(git_config_backend_foreach(backend, contains_all_cb, entries)); - - for (i = 0; entries[i].name; i++) - cl_assert(entries[i].seen); -} - -static void setup_backend(const char *cfg) -{ - cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg))); - cl_git_pass(git_config_backend_open(backend, 0, NULL)); -} - -void test_config_memory__write_operations_fail(void) -{ - setup_backend(""); - cl_git_fail(git_config_backend_set_string(backend, "general.foo", "var")); - cl_git_fail(git_config_backend_delete(backend, "general.foo")); - cl_git_fail(git_config_backend_lock(backend)); - cl_git_fail(git_config_backend_unlock(backend, 0)); -} - -void test_config_memory__simple(void) -{ - setup_backend( - "[general]\n" - "foo=bar\n"); - - assert_config_contains(backend, "general.foo", "bar"); -} - -void test_config_memory__malformed_fails_to_open(void) -{ - const char *cfg = - "[general\n" - "foo=bar\n"; - cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg))); - cl_git_fail(git_config_backend_open(backend, 0, NULL)); -} - -void test_config_memory__multiple_vars(void) -{ - setup_backend( - "[general]\n" - "foo=bar\n" - "key=value\n"); - assert_config_contains(backend, "general.foo", "bar"); - assert_config_contains(backend, "general.key", "value"); -} - -void test_config_memory__multiple_sections(void) -{ - setup_backend( - "[general]\n" - "foo=bar\n" - "\n" - "[other]\n" - "key=value\n"); - assert_config_contains(backend, "general.foo", "bar"); - assert_config_contains(backend, "other.key", "value"); -} - -void test_config_memory__multivar_gets_correct_string(void) -{ - setup_backend( - "[general]\n" - "foo=bar1\n" - "foo=bar2\n"); - assert_config_contains(backend, "general.foo", "bar2"); -} - -void test_config_memory__foreach_sees_multivar(void) -{ - struct expected_entry entries[] = { - { "general.foo", "bar1", 0 }, - { "general.foo", "bar2", 0 }, - { NULL, NULL, 0 }, - }; - - setup_backend( - "[general]\n" - "foo=bar1\n" - "foo=bar2\n"); - assert_config_contains_all(backend, entries); -} diff --git a/tests/config/multivar.c b/tests/config/multivar.c deleted file mode 100644 index 244e37559..000000000 --- a/tests/config/multivar.c +++ /dev/null @@ -1,288 +0,0 @@ -#include "clar_libgit2.h" - -static const char *_name = "remote.ab.url"; - -void test_config_multivar__initialize(void) -{ - cl_fixture_sandbox("config"); -} - -void test_config_multivar__cleanup(void) -{ - cl_fixture_cleanup("config"); -} - -static int mv_read_cb(const git_config_entry *entry, void *data) -{ - int *n = (int *) data; - - if (!strcmp(entry->name, _name)) - (*n)++; - - return 0; -} - -void test_config_multivar__foreach(void) -{ - git_config *cfg; - int n = 0; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config11"))); - - cl_git_pass(git_config_foreach(cfg, mv_read_cb, &n)); - cl_assert(n == 2); - - git_config_free(cfg); -} - -static int cb(const git_config_entry *entry, void *data) -{ - int *n = (int *) data; - - GIT_UNUSED(entry); - - (*n)++; - - return 0; -} - -static void check_get_multivar_foreach( - git_config *cfg, int expected, int expected_patterned) -{ - int n = 0; - - if (expected > 0) { - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); - cl_assert_equal_i(expected, n); - } else { - cl_assert_equal_i(GIT_ENOTFOUND, - git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); - } - - n = 0; - - if (expected_patterned > 0) { - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "example", cb, &n)); - cl_assert_equal_i(expected_patterned, n); - } else { - cl_assert_equal_i(GIT_ENOTFOUND, - git_config_get_multivar_foreach(cfg, _name, "example", cb, &n)); - } -} - -static void check_get_multivar(git_config *cfg, int expected) -{ - git_config_iterator *iter; - git_config_entry *entry; - int n = 0; - - cl_git_pass(git_config_multivar_iterator_new(&iter, cfg, _name, NULL)); - - while (git_config_next(&entry, iter) == 0) - n++; - - cl_assert_equal_i(expected, n); - git_config_iterator_free(iter); - -} - -void test_config_multivar__get(void) -{ - git_config *cfg; - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - check_get_multivar_foreach(cfg, 2, 1); - - /* add another that has the _name entry */ - cl_git_pass(git_config_add_file_ondisk(cfg, "config/config9", GIT_CONFIG_LEVEL_SYSTEM, NULL, 1)); - check_get_multivar_foreach(cfg, 3, 2); - - /* add another that does not have the _name entry */ - cl_git_pass(git_config_add_file_ondisk(cfg, "config/config0", GIT_CONFIG_LEVEL_GLOBAL, NULL, 1)); - check_get_multivar_foreach(cfg, 3, 2); - - /* add another that does not have the _name entry at the end */ - cl_git_pass(git_config_add_file_ondisk(cfg, "config/config1", GIT_CONFIG_LEVEL_APP, NULL, 1)); - check_get_multivar_foreach(cfg, 3, 2); - - /* drop original file */ - cl_git_pass(git_config_add_file_ondisk(cfg, "config/config2", GIT_CONFIG_LEVEL_LOCAL, NULL, 1)); - check_get_multivar_foreach(cfg, 1, 1); - - /* drop other file with match */ - cl_git_pass(git_config_add_file_ondisk(cfg, "config/config3", GIT_CONFIG_LEVEL_SYSTEM, NULL, 1)); - check_get_multivar_foreach(cfg, 0, 0); - - /* reload original file (add different place in order) */ - cl_git_pass(git_config_add_file_ondisk(cfg, "config/config11", GIT_CONFIG_LEVEL_SYSTEM, NULL, 1)); - check_get_multivar_foreach(cfg, 2, 1); - - check_get_multivar(cfg, 2); - - git_config_free(cfg); -} - -void test_config_multivar__add(void) -{ - git_config *cfg; - int n; - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - cl_git_pass(git_config_set_multivar(cfg, _name, "non-existent", "git://git.otherplace.org/libgit2")); - - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); - cl_assert_equal_i(n, 3); - - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); - cl_assert_equal_i(n, 1); - - git_config_free(cfg); - - /* We know it works in memory, let's see if the file is written correctly */ - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); - cl_assert_equal_i(n, 3); - - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); - cl_assert_equal_i(n, 1); - - git_config_free(cfg); -} - -void test_config_multivar__add_new(void) -{ - const char *var = "a.brand.new"; - git_config *cfg; - int n; - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - cl_git_pass(git_config_set_multivar(cfg, var, "$^", "variable")); - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, var, NULL, cb, &n)); - cl_assert_equal_i(n, 1); - - git_config_free(cfg); -} - -void test_config_multivar__replace(void) -{ - git_config *cfg; - int n; - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); - cl_assert(n == 2); - - cl_git_pass(git_config_set_multivar(cfg, _name, "github", "git://git.otherplace.org/libgit2")); - - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); - cl_assert(n == 2); - - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); - cl_assert(n == 2); - - git_config_free(cfg); -} - -void test_config_multivar__replace_multiple(void) -{ - git_config *cfg; - int n; - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - cl_git_pass(git_config_set_multivar(cfg, _name, "git://", "git://git.otherplace.org/libgit2")); - - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); - cl_assert_equal_i(n, 2); - - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); - cl_assert_equal_i(n, 2); - - git_config_free(cfg); -} - -void test_config_multivar__delete(void) -{ - git_config *cfg; - int n; - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); - cl_assert_equal_i(2, n); - - cl_git_pass(git_config_delete_multivar(cfg, _name, "github")); - - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); - cl_assert_equal_i(1, n); - - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); - cl_assert_equal_i(1, n); - - git_config_free(cfg); -} - -void test_config_multivar__delete_multiple(void) -{ - git_config *cfg; - int n; - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - n = 0; - cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); - cl_assert(n == 2); - - cl_git_pass(git_config_delete_multivar(cfg, _name, "git")); - - n = 0; - cl_git_fail_with(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n), GIT_ENOTFOUND); - - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - n = 0; - cl_git_fail_with(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n), GIT_ENOTFOUND); - - git_config_free(cfg); -} - -void test_config_multivar__delete_notfound(void) -{ - git_config *cfg; - - cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); - - cl_git_fail_with(git_config_delete_multivar(cfg, "remote.ab.noturl", "git"), GIT_ENOTFOUND); - - git_config_free(cfg); -} diff --git a/tests/config/new.c b/tests/config/new.c deleted file mode 100644 index baca05332..000000000 --- a/tests/config/new.c +++ /dev/null @@ -1,34 +0,0 @@ -#include "clar_libgit2.h" - -#include "filebuf.h" -#include "futils.h" -#include "posix.h" - -#define TEST_CONFIG "git-new-config" - -void test_config_new__write_new_config(void) -{ - git_config *config; - git_buf buf = GIT_BUF_INIT; - - cl_git_mkfile(TEST_CONFIG, ""); - cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); - - cl_git_pass(git_config_set_string(config, "color.ui", "auto")); - cl_git_pass(git_config_set_string(config, "core.editor", "ed")); - - git_config_free(config); - - cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); - - cl_git_pass(git_config_get_string_buf(&buf, config, "color.ui")); - cl_assert_equal_s("auto", buf.ptr); - git_buf_dispose(&buf); - cl_git_pass(git_config_get_string_buf(&buf, config, "core.editor")); - cl_assert_equal_s("ed", buf.ptr); - - git_buf_dispose(&buf); - git_config_free(config); - - cl_must_pass(p_unlink(TEST_CONFIG)); -} diff --git a/tests/config/read.c b/tests/config/read.c deleted file mode 100644 index a2e668c20..000000000 --- a/tests/config/read.c +++ /dev/null @@ -1,1040 +0,0 @@ -#include "clar_libgit2.h" -#include "fs_path.h" - -static git_buf buf = GIT_BUF_INIT; - -void test_config_read__cleanup(void) -{ - git_buf_dispose(&buf); -} - -void test_config_read__simple_read(void) -{ - git_config *cfg; - int32_t i; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config0"))); - - cl_git_pass(git_config_get_int32(&i, cfg, "core.repositoryformatversion")); - cl_assert(i == 0); - cl_git_pass(git_config_get_bool(&i, cfg, "core.filemode")); - cl_assert(i == 1); - cl_git_pass(git_config_get_bool(&i, cfg, "core.bare")); - cl_assert(i == 0); - cl_git_pass(git_config_get_bool(&i, cfg, "core.logallrefupdates")); - cl_assert(i == 1); - - git_config_free(cfg); -} - -void test_config_read__case_sensitive(void) -{ - git_config *cfg; - int i; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config1"))); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "this.that.other")); - cl_assert_equal_s("true", buf.ptr); - git_buf_dispose(&buf); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "this.That.other")); - cl_assert_equal_s("yes", buf.ptr); - - cl_git_pass(git_config_get_bool(&i, cfg, "this.that.other")); - cl_assert(i == 1); - cl_git_pass(git_config_get_bool(&i, cfg, "this.That.other")); - cl_assert(i == 1); - - /* This one doesn't exist */ - cl_must_fail(git_config_get_bool(&i, cfg, "this.thaT.other")); - - git_config_free(cfg); -} - -/* - * If \ is the last non-space character on the line, we read the next - * one, separating each line with SP. - */ -void test_config_read__multiline_value(void) -{ - git_config *cfg; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config2"))); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "this.That.and")); - cl_assert_equal_s("one one one two two three three", buf.ptr); - - git_config_free(cfg); -} - -static void clean_test_config(void *unused) -{ - GIT_UNUSED(unused); - cl_fixture_cleanup("./testconfig"); -} - -void test_config_read__multiline_value_and_eof(void) -{ - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[header]\n key1 = foo\\\n"); - cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "header.key1")); - cl_assert_equal_s("foo", buf.ptr); - - git_config_free(cfg); -} - -void test_config_read__multiline_eof(void) -{ - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[header]\n key1 = \\\n"); - cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "header.key1")); - cl_assert_equal_s("", buf.ptr); - - git_config_free(cfg); -} - -/* - * This kind of subsection declaration is case-insensitive - */ -void test_config_read__subsection_header(void) -{ - git_config *cfg; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config3"))); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "section.subsection.var")); - cl_assert_equal_s("hello", buf.ptr); - - /* The subsection is transformed to lower-case */ - cl_must_fail(git_config_get_string_buf(&buf, cfg, "section.subSectIon.var")); - - git_config_free(cfg); -} - -void test_config_read__lone_variable(void) -{ - git_config *cfg; - int i; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config4"))); - - cl_git_fail(git_config_get_int32(&i, cfg, "some.section.variable")); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.section.variable")); - cl_assert_equal_s("", buf.ptr); - git_buf_dispose(&buf); - - cl_git_pass(git_config_get_bool(&i, cfg, "some.section.variable")); - cl_assert(i == 1); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.section.variableeq")); - cl_assert_equal_s("", buf.ptr); - - cl_git_pass(git_config_get_bool(&i, cfg, "some.section.variableeq")); - cl_assert(i == 0); - - git_config_free(cfg); -} - -void test_config_read__number_suffixes(void) -{ - git_config *cfg; - int64_t i; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config5"))); - - cl_git_pass(git_config_get_int64(&i, cfg, "number.simple")); - cl_assert(i == 1); - - cl_git_pass(git_config_get_int64(&i, cfg, "number.k")); - cl_assert(i == 1 * 1024); - - cl_git_pass(git_config_get_int64(&i, cfg, "number.kk")); - cl_assert(i == 1 * 1024); - - cl_git_pass(git_config_get_int64(&i, cfg, "number.m")); - cl_assert(i == 1 * 1024 * 1024); - - cl_git_pass(git_config_get_int64(&i, cfg, "number.mm")); - cl_assert(i == 1 * 1024 * 1024); - - cl_git_pass(git_config_get_int64(&i, cfg, "number.g")); - cl_assert(i == 1 * 1024 * 1024 * 1024); - - cl_git_pass(git_config_get_int64(&i, cfg, "number.gg")); - cl_assert(i == 1 * 1024 * 1024 * 1024); - - git_config_free(cfg); -} - -void test_config_read__blank_lines(void) -{ - git_config *cfg; - int i; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config6"))); - - cl_git_pass(git_config_get_bool(&i, cfg, "valid.subsection.something")); - cl_assert(i == 1); - - cl_git_pass(git_config_get_bool(&i, cfg, "something.else.something")); - cl_assert(i == 0); - - git_config_free(cfg); -} - -void test_config_read__invalid_ext_headers(void) -{ - git_config *cfg; - cl_must_fail(git_config_open_ondisk(&cfg, cl_fixture("config/config7"))); -} - -void test_config_read__empty_files(void) -{ - git_config *cfg; - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config8"))); - git_config_free(cfg); -} - -void test_config_read__symbol_headers(void) -{ - git_config *cfg; - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config20"))); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "valid.[subsection].something")); - cl_assert_equal_s("a", buf.ptr); - git_buf_dispose(&buf); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "sec.[subsec]/child.parent")); - cl_assert_equal_s("grand", buf.ptr); - git_buf_dispose(&buf); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "sec2.[subsec2]/child2.type")); - cl_assert_equal_s("dvcs", buf.ptr); - git_buf_dispose(&buf); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "sec3.escape\"quote.vcs")); - cl_assert_equal_s("git", buf.ptr); - git_buf_dispose(&buf); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "sec4.escaping\\slash.lib")); - cl_assert_equal_s("git2", buf.ptr); - git_buf_dispose(&buf); - git_config_free(cfg); -} - -void test_config_read__multiline_multiple_quoted_comment_chars(void) -{ - git_config *cfg; - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config21"))); - git_config_free(cfg); -} - -void test_config_read__multiline_multiple_quoted_quote_at_beginning_of_line(void) -{ - git_config* cfg; - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config22"))); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "alias.m")); - cl_assert_equal_s("cmd ;; ;; bar", buf.ptr); - git_buf_dispose(&buf); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "alias.m2")); - cl_assert_equal_s("'; ; something '", buf.ptr); - git_buf_dispose(&buf); - git_config_free(cfg); -} - -void test_config_read__header_in_last_line(void) -{ - git_config *cfg; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config10"))); - git_config_free(cfg); -} - -void test_config_read__prefixes(void) -{ - git_config *cfg; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "remote.ab.url")); - cl_assert_equal_s("http://example.com/git/ab", buf.ptr); - git_buf_dispose(&buf); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "remote.abba.url")); - cl_assert_equal_s("http://example.com/git/abba", buf.ptr); - - git_config_free(cfg); -} - -void test_config_read__escaping_quotes(void) -{ - git_config *cfg; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config13"))); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.editor")); - cl_assert_equal_s("\"C:/Program Files/Nonsense/bah.exe\" \"--some option\"", buf.ptr); - - git_config_free(cfg); -} - -void test_config_read__invalid_escape_sequence(void) -{ - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[header]\n key1 = \\\\\\;\n key2 = value2\n"); - cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); - - git_config_free(cfg); -} - -static int count_cfg_entries_and_compare_levels( - const git_config_entry *entry, void *payload) -{ - int *count = payload; - - if (!strcmp(entry->value, "7") || !strcmp(entry->value, "17")) - cl_assert(entry->level == GIT_CONFIG_LEVEL_GLOBAL); - else - cl_assert(entry->level == GIT_CONFIG_LEVEL_SYSTEM); - - (*count)++; - return 0; -} - -static int cfg_callback_countdown(const git_config_entry *entry, void *payload) -{ - int *count = payload; - GIT_UNUSED(entry); - (*count)--; - if (*count == 0) - return -100; - return 0; -} - -void test_config_read__foreach(void) -{ - git_config *cfg; - int count, ret; - - cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), - GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - - count = 0; - cl_git_pass(git_config_foreach(cfg, count_cfg_entries_and_compare_levels, &count)); - cl_assert_equal_i(7, count); - - count = 3; - cl_git_fail(ret = git_config_foreach(cfg, cfg_callback_countdown, &count)); - cl_assert_equal_i(-100, ret); - - git_config_free(cfg); -} - -void test_config_read__iterator(void) -{ - const char *keys[] = { - "core.dummy2", - "core.verylong", - "core.dummy", - "remote.ab.url", - "remote.abba.url", - "core.dummy2", - "core.global" - }; - git_config *cfg; - git_config_iterator *iter; - git_config_entry *entry; - int count, ret; - - cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), - GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - - count = 0; - cl_git_pass(git_config_iterator_new(&iter, cfg)); - - while ((ret = git_config_next(&entry, iter)) == 0) { - cl_assert_equal_s(entry->name, keys[count]); - count++; - } - - git_config_iterator_free(iter); - cl_assert_equal_i(GIT_ITEROVER, ret); - cl_assert_equal_i(7, count); - - count = 3; - cl_git_pass(git_config_iterator_new(&iter, cfg)); - - git_config_iterator_free(iter); - git_config_free(cfg); -} - -static int count_cfg_entries(const git_config_entry *entry, void *payload) -{ - int *count = payload; - GIT_UNUSED(entry); - (*count)++; - return 0; -} - -void test_config_read__foreach_match(void) -{ - git_config *cfg; - int count; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); - - count = 0; - cl_git_pass( - git_config_foreach_match(cfg, "core.*", count_cfg_entries, &count)); - cl_assert_equal_i(3, count); - - count = 0; - cl_git_pass( - git_config_foreach_match(cfg, "remote\\.ab.*", count_cfg_entries, &count)); - cl_assert_equal_i(2, count); - - count = 0; - cl_git_pass( - git_config_foreach_match(cfg, ".*url$", count_cfg_entries, &count)); - cl_assert_equal_i(2, count); - - count = 0; - cl_git_pass( - git_config_foreach_match(cfg, ".*dummy.*", count_cfg_entries, &count)); - cl_assert_equal_i(2, count); - - count = 0; - cl_git_pass( - git_config_foreach_match(cfg, ".*nomatch.*", count_cfg_entries, &count)); - cl_assert_equal_i(0, count); - - git_config_free(cfg); -} - -static void check_glob_iter(git_config *cfg, const char *regexp, int expected) -{ - git_config_iterator *iter; - git_config_entry *entry; - int count, error; - - cl_git_pass(git_config_iterator_glob_new(&iter, cfg, regexp)); - - count = 0; - while ((error = git_config_next(&entry, iter)) == 0) - count++; - - cl_assert_equal_i(GIT_ITEROVER, error); - cl_assert_equal_i(expected, count); - git_config_iterator_free(iter); -} - -void test_config_read__iterator_invalid_glob(void) -{ - git_config *cfg; - git_config_iterator *iter; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); - - cl_git_fail(git_config_iterator_glob_new(&iter, cfg, "*")); - - git_config_free(cfg); -} - -void test_config_read__iterator_glob(void) -{ - git_config *cfg; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); - - check_glob_iter(cfg, "core.*", 3); - check_glob_iter(cfg, "remote\\.ab.*", 2); - check_glob_iter(cfg, ".*url$", 2); - check_glob_iter(cfg, ".*dummy.*", 2); - check_glob_iter(cfg, ".*nomatch.*", 0); - - git_config_free(cfg); -} - -void test_config_read__whitespace_not_required_around_assignment(void) -{ - git_config *cfg; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config14"))); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "a.b")); - cl_assert_equal_s("c", buf.ptr); - git_buf_dispose(&buf); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "d.e")); - cl_assert_equal_s("f", buf.ptr); - - git_config_free(cfg); -} - -void test_config_read__read_git_config_entry(void) -{ - git_config *cfg; - git_config_entry *entry; - - cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); - - cl_git_pass(git_config_get_entry(&entry, cfg, "core.dummy2")); - cl_assert_equal_s("core.dummy2", entry->name); - cl_assert_equal_s("42", entry->value); - cl_assert_equal_i(GIT_CONFIG_LEVEL_SYSTEM, entry->level); - - git_config_entry_free(entry); - git_config_free(cfg); -} - -/* - * At the beginning of the test: - * - config9 has: core.dummy2=42 - * - config15 has: core.dummy2=7 - * - config16 has: core.dummy2=28 - */ -void test_config_read__local_config_overrides_global_config_overrides_system_config(void) -{ - git_config *cfg; - int32_t i; - - cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), - GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config16"), - GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); - - cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2")); - cl_assert_equal_i(28, i); - - git_config_free(cfg); - - cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), - GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - - cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2")); - cl_assert_equal_i(7, i); - - git_config_free(cfg); -} - -/* - * At the beginning of the test: - * - config9 has: core.global does not exist - * - config15 has: core.global=17 - * - config16 has: core.global=29 - * - * And also: - * - config9 has: core.system does not exist - * - config15 has: core.system does not exist - * - config16 has: core.system=11 - */ -void test_config_read__fallback_from_local_to_global_and_from_global_to_system(void) -{ - git_config *cfg; - int32_t i; - - cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), - GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), - GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config16"), - GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); - - cl_git_pass(git_config_get_int32(&i, cfg, "core.global")); - cl_assert_equal_i(17, i); - cl_git_pass(git_config_get_int32(&i, cfg, "core.system")); - cl_assert_equal_i(11, i); - - git_config_free(cfg); -} - -void test_config_read__parent_dir_is_file(void) -{ - git_config *cfg; - int count; - - cl_git_pass(git_config_new(&cfg)); - /* - * Verify we can add non-existing files when the parent directory is not - * a directory. - */ - cl_git_pass(git_config_add_file_ondisk(cfg, "/dev/null/.gitconfig", - GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); - - count = 0; - cl_git_pass(git_config_foreach(cfg, count_cfg_entries_and_compare_levels, &count)); - cl_assert_equal_i(0, count); - - git_config_free(cfg); -} - -/* - * At the beginning of the test, config18 has: - * int32global = 28 - * int64global = 9223372036854775803 - * boolglobal = true - * stringglobal = I'm a global config value! - * - * And config19 has: - * int32global = -1 - * int64global = -2 - * boolglobal = false - * stringglobal = don't find me! - * - */ -void test_config_read__simple_read_from_specific_level(void) -{ - git_config *cfg, *cfg_specific; - int i; - int64_t l, expected = +9223372036854775803; - - cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), - GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), - GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); - - cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL)); - - cl_git_pass(git_config_get_int32(&i, cfg_specific, "core.int32global")); - cl_assert_equal_i(28, i); - cl_git_pass(git_config_get_int64(&l, cfg_specific, "core.int64global")); - cl_assert(l == expected); - cl_git_pass(git_config_get_bool(&i, cfg_specific, "core.boolglobal")); - cl_assert_equal_b(true, i); - cl_git_pass(git_config_get_string_buf(&buf, cfg_specific, "core.stringglobal")); - cl_assert_equal_s("I'm a global config value!", buf.ptr); - - git_config_free(cfg_specific); - git_config_free(cfg); -} - -void test_config_read__can_load_and_parse_an_empty_config_file(void) -{ - git_config *cfg; - int i; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", ""); - cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - cl_assert_equal_i(GIT_ENOTFOUND, git_config_get_int32(&i, cfg, "nope.neither")); - - git_config_free(cfg); -} - -void test_config_read__corrupt_header(void) -{ - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[sneaky ] \"quoted closing quote mark\\\""); - cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); - - git_config_free(cfg); -} - -void test_config_read__corrupt_header2(void) -{ - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[unclosed \"bracket\"\n lib = git2\n"); - cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); - - git_config_free(cfg); -} - -void test_config_read__corrupt_header3(void) -{ - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[unclosed \"slash\\\"]\n lib = git2\n"); - cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); - - git_config_free(cfg); -} - -void test_config_read__invalid_key_chars(void) -{ - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[foo]\n has_underscore = git2\n"); - cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); - - cl_git_rewritefile("./testconfig", "[foo]\n has/slash = git2\n"); - cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); - - cl_git_rewritefile("./testconfig", "[foo]\n has+plus = git2\n"); - cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); - - cl_git_rewritefile("./testconfig", "[no_key]\n = git2\n"); - cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); - - git_config_free(cfg); -} - -void test_config_read__lone_variable_with_trailing_whitespace(void) -{ - git_config *cfg; - int b; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[foo]\n lonevariable \n"); - cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - - cl_git_pass(git_config_get_bool(&b, cfg, "foo.lonevariable")); - cl_assert_equal_b(true, b); - - git_config_free(cfg); -} - -void test_config_read__override_variable(void) -{ - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[some] var = one\nvar = two"); - cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - - cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); - cl_assert_equal_s("two", buf.ptr); - - git_config_free(cfg); -} - -void test_config_read__path(void) -{ - git_config *cfg; - git_buf path = GIT_BUF_INIT; - git_buf old_path = GIT_BUF_INIT; - git_str home_path = GIT_STR_INIT; - git_str expected_path = GIT_STR_INIT; - - cl_git_pass(p_mkdir("fakehome", 0777)); - cl_git_pass(git_fs_path_prettify(&home_path, "fakehome", NULL)); - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &old_path)); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, home_path.ptr)); - cl_git_mkfile("./testconfig", "[some]\n path = ~/somefile"); - cl_git_pass(git_fs_path_join_unrooted(&expected_path, "somefile", home_path.ptr, NULL)); - - cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - cl_git_pass(git_config_get_path(&path, cfg, "some.path")); - cl_assert_equal_s(expected_path.ptr, path.ptr); - git_buf_dispose(&path); - - cl_git_mkfile("./testconfig", "[some]\n path = ~/"); - cl_git_pass(git_fs_path_join_unrooted(&expected_path, "", home_path.ptr, NULL)); - - cl_git_pass(git_config_get_path(&path, cfg, "some.path")); - cl_assert_equal_s(expected_path.ptr, path.ptr); - git_buf_dispose(&path); - - cl_git_mkfile("./testconfig", "[some]\n path = ~"); - cl_git_pass(git_str_sets(&expected_path, home_path.ptr)); - - cl_git_pass(git_config_get_path(&path, cfg, "some.path")); - cl_assert_equal_s(expected_path.ptr, path.ptr); - git_buf_dispose(&path); - - cl_git_mkfile("./testconfig", "[some]\n path = ~user/foo"); - cl_git_fail(git_config_get_path(&path, cfg, "some.path")); - - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, old_path.ptr)); - git_buf_dispose(&old_path); - git_str_dispose(&home_path); - git_str_dispose(&expected_path); - git_config_free(cfg); -} - -void test_config_read__crlf_style_line_endings(void) -{ - git_buf buf = GIT_BUF_INIT; - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[some]\r\n var = value\r\n"); - cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); - cl_assert_equal_s(buf.ptr, "value"); - - git_config_free(cfg); - git_buf_dispose(&buf); -} - -void test_config_read__trailing_crlf(void) -{ - git_buf buf = GIT_BUF_INIT; - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[some]\r\n var = value\r\n\r\n"); - cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); - cl_assert_equal_s(buf.ptr, "value"); - - git_config_free(cfg); - git_buf_dispose(&buf); -} - -void test_config_read__bom(void) -{ - git_buf buf = GIT_BUF_INIT; - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[some]\n var = value\n"); - cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); - cl_assert_equal_s(buf.ptr, "value"); - - git_config_free(cfg); - git_buf_dispose(&buf); -} - -void test_config_read__arbitrary_whitespace_before_subsection(void) -{ - git_buf buf = GIT_BUF_INIT; - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[some \t \"subsection\"]\n var = value\n"); - cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.subsection.var")); - cl_assert_equal_s(buf.ptr, "value"); - - git_config_free(cfg); - git_buf_dispose(&buf); -} - -void test_config_read__no_whitespace_after_subsection(void) -{ - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[some \"subsection\" ]\n var = value\n"); - cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); - - git_config_free(cfg); -} - -void test_config_read__invalid_space_section(void) -{ - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[some section]\n var = value\n"); - cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); - - git_config_free(cfg); -} - -void test_config_read__invalid_quoted_first_section(void) -{ - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[\"some\"]\n var = value\n"); - cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); - - git_config_free(cfg); -} - -void test_config_read__invalid_unquoted_subsection(void) -{ - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[some sub section]\n var = value\n"); - cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); - - git_config_free(cfg); -} - -void test_config_read__invalid_quoted_third_section(void) -{ - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[some sub \"section\"]\n var = value\n"); - cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); - - git_config_free(cfg); -} - -void test_config_read__unreadable_file_ignored(void) -{ - git_buf buf = GIT_BUF_INIT; - git_config *cfg; - int ret; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[some] var = value\n[some \"OtheR\"] var = value"); - cl_git_pass(p_chmod("./testconfig", 0)); - - ret = git_config_open_ondisk(&cfg, "./test/config"); - cl_assert(ret == 0 || ret == GIT_ENOTFOUND); - - git_config_free(cfg); - git_buf_dispose(&buf); -} - -void test_config_read__single_line(void) -{ - git_buf buf = GIT_BUF_INIT; - git_config *cfg; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[some] var = value\n[some \"OtheR\"] var = value"); - cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); - cl_assert_equal_s(buf.ptr, "value"); - - git_buf_dispose(&buf); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.OtheR.var")); - cl_assert_equal_s(buf.ptr, "value"); - - git_config_free(cfg); - cl_git_mkfile("./testconfig", "[some] var = value\n[some \"OtheR\"]var = value"); - cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - git_buf_dispose(&buf); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); - cl_assert_equal_s(buf.ptr, "value"); - - git_buf_dispose(&buf); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.OtheR.var")); - cl_assert_equal_s(buf.ptr, "value"); - - git_config_free(cfg); - git_buf_dispose(&buf); -} - -static int read_nosection_cb(const git_config_entry *entry, void *payload) { - int *seen = (int*)payload; - if (strcmp(entry->name, "key") == 0) { - (*seen)++; - } - return 0; -} - -/* This would ideally issue a warning, if we had a way to do so. */ -void test_config_read__nosection(void) -{ - git_config *cfg; - git_buf buf = GIT_BUF_INIT; - int seen = 0; - - cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config-nosection"))); - - /* - * Given a key with no section, we do not allow reading it, - * but we do include it in an iteration over the config - * store. This appears to match how git's own APIs (and - * git-config(1)) behave. - */ - - cl_git_fail_with(git_config_get_string_buf(&buf, cfg, "key"), GIT_EINVALIDSPEC); - - cl_git_pass(git_config_foreach(cfg, read_nosection_cb, &seen)); - cl_assert_equal_i(seen, 1); - - git_buf_dispose(&buf); - git_config_free(cfg); -} - -enum { - MAP_TRUE = 0, - MAP_FALSE = 1, - MAP_ALWAYS = 2 -}; - -static git_configmap _test_map1[] = { - {GIT_CONFIGMAP_STRING, "always", MAP_ALWAYS}, - {GIT_CONFIGMAP_FALSE, NULL, MAP_FALSE}, - {GIT_CONFIGMAP_TRUE, NULL, MAP_TRUE}, -}; - -static git_configmap _test_map2[] = { - {GIT_CONFIGMAP_INT32, NULL, 0}, -}; - -void test_config_read__get_mapped(void) -{ - git_config *cfg; - int val; - int known_good; - - cl_set_cleanup(&clean_test_config, NULL); - cl_git_mkfile("./testconfig", "[header]\n" - " key1 = 1\n" - " key2 = true\n" - " key3\n" - " key4 = always\n" - " key5 = false\n" - " key6 = 0\n" - " key7 = never\n" - " key8 = On\n" - " key9 = off\n"); - cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); - - /* check parsing bool and string */ - cl_git_pass(git_config_get_mapped(&val, cfg, "header.key1", _test_map1, ARRAY_SIZE(_test_map1))); - cl_assert_equal_i(val, MAP_TRUE); - cl_git_pass(git_config_get_mapped(&val, cfg, "header.key2", _test_map1, ARRAY_SIZE(_test_map1))); - cl_assert_equal_i(val, MAP_TRUE); - cl_git_pass(git_config_get_mapped(&val, cfg, "header.key3", _test_map1, ARRAY_SIZE(_test_map1))); - cl_assert_equal_i(val, MAP_TRUE); - cl_git_pass(git_config_get_mapped(&val, cfg, "header.key8", _test_map1, ARRAY_SIZE(_test_map1))); - cl_assert_equal_i(val, MAP_TRUE); - - cl_git_pass(git_config_get_mapped(&val, cfg, "header.key4", _test_map1, ARRAY_SIZE(_test_map1))); - cl_assert_equal_i(val, MAP_ALWAYS); - - cl_git_pass(git_config_get_mapped(&val, cfg, "header.key5", _test_map1, ARRAY_SIZE(_test_map1))); - cl_assert_equal_i(val, MAP_FALSE); - cl_git_pass(git_config_get_mapped(&val, cfg, "header.key6", _test_map1, ARRAY_SIZE(_test_map1))); - cl_assert_equal_i(val, MAP_FALSE); - cl_git_pass(git_config_get_mapped(&val, cfg, "header.key9", _test_map1, ARRAY_SIZE(_test_map1))); - cl_assert_equal_i(val, MAP_FALSE); - - cl_git_fail(git_config_get_mapped(&val, cfg, "header.key7", _test_map1, ARRAY_SIZE(_test_map1))); - - /* check parsing int values */ - cl_git_pass(git_config_get_mapped(&val, cfg, "header.key1", _test_map2, ARRAY_SIZE(_test_map2))); - cl_git_pass(git_config_get_int32(&known_good, cfg, "header.key1")); - cl_assert_equal_i(val, known_good); - cl_git_pass(git_config_get_mapped(&val, cfg, "header.key6", _test_map2, ARRAY_SIZE(_test_map2))); - cl_git_pass(git_config_get_int32(&known_good, cfg, "header.key6")); - cl_assert_equal_i(val, known_good); - - cl_git_fail(git_config_get_mapped(&val, cfg, "header.key2", _test_map2, ARRAY_SIZE(_test_map2))); - cl_git_fail(git_config_get_mapped(&val, cfg, "header.key3", _test_map2, ARRAY_SIZE(_test_map2))); - cl_git_fail(git_config_get_mapped(&val, cfg, "header.key4", _test_map2, ARRAY_SIZE(_test_map2))); - cl_git_fail(git_config_get_mapped(&val, cfg, "header.key5", _test_map2, ARRAY_SIZE(_test_map2))); - cl_git_fail(git_config_get_mapped(&val, cfg, "header.key7", _test_map2, ARRAY_SIZE(_test_map2))); - cl_git_fail(git_config_get_mapped(&val, cfg, "header.key8", _test_map2, ARRAY_SIZE(_test_map2))); - cl_git_fail(git_config_get_mapped(&val, cfg, "header.key9", _test_map2, ARRAY_SIZE(_test_map2))); - - git_config_free(cfg); -} diff --git a/tests/config/readonly.c b/tests/config/readonly.c deleted file mode 100644 index a8901e394..000000000 --- a/tests/config/readonly.c +++ /dev/null @@ -1,65 +0,0 @@ -#include "clar_libgit2.h" -#include "config_backend.h" -#include "config.h" -#include "path.h" - -static git_config *cfg; - -void test_config_readonly__initialize(void) -{ - cl_git_pass(git_config_new(&cfg)); -} - -void test_config_readonly__cleanup(void) -{ - git_config_free(cfg); - cfg = NULL; -} - -void test_config_readonly__writing_to_readonly_fails(void) -{ - git_config_backend *backend; - - cl_git_pass(git_config_backend_from_file(&backend, "global")); - backend->readonly = 1; - cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - - cl_git_fail_with(GIT_ENOTFOUND, git_config_set_string(cfg, "foo.bar", "baz")); - cl_assert(!git_fs_path_exists("global")); -} - -void test_config_readonly__writing_to_cfg_with_rw_precedence_succeeds(void) -{ - git_config_backend *backend; - - cl_git_pass(git_config_backend_from_file(&backend, "global")); - backend->readonly = 1; - cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - - cl_git_pass(git_config_backend_from_file(&backend, "local")); - cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); - - cl_git_pass(git_config_set_string(cfg, "foo.bar", "baz")); - - cl_assert(git_fs_path_exists("local")); - cl_assert(!git_fs_path_exists("global")); - cl_git_pass(p_unlink("local")); -} - -void test_config_readonly__writing_to_cfg_with_ro_precedence_succeeds(void) -{ - git_config_backend *backend; - - cl_git_pass(git_config_backend_from_file(&backend, "local")); - backend->readonly = 1; - cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); - - cl_git_pass(git_config_backend_from_file(&backend, "global")); - cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - - cl_git_pass(git_config_set_string(cfg, "foo.bar", "baz")); - - cl_assert(!git_fs_path_exists("local")); - cl_assert(git_fs_path_exists("global")); - cl_git_pass(p_unlink("global")); -} diff --git a/tests/config/rename.c b/tests/config/rename.c deleted file mode 100644 index be620c307..000000000 --- a/tests/config/rename.c +++ /dev/null @@ -1,89 +0,0 @@ -#include "clar_libgit2.h" -#include "config.h" - -static git_repository *g_repo = NULL; -static git_config *g_config = NULL; - -void test_config_rename__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_config(&g_config, g_repo)); -} - -void test_config_rename__cleanup(void) -{ - git_config_free(g_config); - g_config = NULL; - - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -void test_config_rename__can_rename(void) -{ - git_config_entry *ce; - - cl_git_pass(git_config_get_entry( - &ce, g_config, "branch.track-local.remote")); - cl_assert_equal_s(".", ce->value); - git_config_entry_free(ce); - - cl_git_fail(git_config_get_entry( - &ce, g_config, "branch.local-track.remote")); - - cl_git_pass(git_config_rename_section( - g_repo, "branch.track-local", "branch.local-track")); - - cl_git_pass(git_config_get_entry( - &ce, g_config, "branch.local-track.remote")); - cl_assert_equal_s(".", ce->value); - git_config_entry_free(ce); - - cl_git_fail(git_config_get_entry( - &ce, g_config, "branch.track-local.remote")); -} - -void test_config_rename__prevent_overwrite(void) -{ - git_config_entry *ce; - - cl_git_pass(git_config_set_string( - g_config, "branch.local-track.remote", "yellow")); - - cl_git_pass(git_config_get_entry( - &ce, g_config, "branch.local-track.remote")); - cl_assert_equal_s("yellow", ce->value); - git_config_entry_free(ce); - - cl_git_pass(git_config_rename_section( - g_repo, "branch.track-local", "branch.local-track")); - - cl_git_pass(git_config_get_entry( - &ce, g_config, "branch.local-track.remote")); - cl_assert_equal_s(".", ce->value); - git_config_entry_free(ce); - - /* so, we don't currently prevent overwrite... */ - /* { - const git_error *err; - cl_assert((err = git_error_last()) != NULL); - cl_assert(err->message != NULL); - } */ -} - -static void assert_invalid_config_section_name( - git_repository *repo, const char *name) -{ - cl_git_fail_with( - git_config_rename_section(repo, "branch.remoteless", name), - GIT_EINVALIDSPEC); -} - -void test_config_rename__require_a_valid_new_name(void) -{ - assert_invalid_config_section_name(g_repo, ""); - assert_invalid_config_section_name(g_repo, "bra\nch"); - assert_invalid_config_section_name(g_repo, "branc#"); - assert_invalid_config_section_name(g_repo, "bra\nch.duh"); - assert_invalid_config_section_name(g_repo, "branc#.duh"); -} diff --git a/tests/config/snapshot.c b/tests/config/snapshot.c deleted file mode 100644 index 5cc08a721..000000000 --- a/tests/config/snapshot.c +++ /dev/null @@ -1,139 +0,0 @@ -#include "clar_libgit2.h" - -#include "config_backend.h" - -static git_config *cfg; -static git_config *snapshot; - -void test_config_snapshot__cleanup(void) -{ - git_config_free(cfg); - cfg = NULL; - git_config_free(snapshot); - snapshot = NULL; -} - -void test_config_snapshot__create_snapshot(void) -{ - int32_t i; - - cl_git_mkfile("config", "[old]\nvalue = 5\n"); - cl_git_pass(git_config_open_ondisk(&cfg, "config")); - cl_git_pass(git_config_get_int32(&i, cfg, "old.value")); - cl_assert_equal_i(5, i); - - cl_git_pass(git_config_snapshot(&snapshot, cfg)); - - /* Change the value on the file itself (simulate external process) */ - cl_git_mkfile("config", "[old]\nvalue = 56\n"); - - cl_git_pass(git_config_get_int32(&i, cfg, "old.value")); - cl_assert_equal_i(56, i); - cl_git_pass(git_config_get_int32(&i, snapshot, "old.value")); - cl_assert_equal_i(5, i); - - /* Change the value on the file itself (simulate external process) */ - cl_git_mkfile("config", "[old]\nvalue = 999\n"); - - /* Old snapshot should still have the old value */ - cl_git_pass(git_config_get_int32(&i, snapshot, "old.value")); - cl_assert_equal_i(5, i); - - /* New snapshot should see new value */ - git_config_free(snapshot); - cl_git_pass(git_config_snapshot(&snapshot, cfg)); - cl_git_pass(git_config_get_int32(&i, snapshot, "old.value")); - cl_assert_equal_i(999, i); - - cl_git_pass(p_unlink("config")); -} - -static int count_me(const git_config_entry *entry, void *payload) -{ - int *n = (int *) payload; - - GIT_UNUSED(entry); - - (*n)++; - - return 0; -} - -void test_config_snapshot__multivar(void) -{ - int count; - - count = 0; - cl_git_mkfile("config", "[old]\nvalue = 5\nvalue = 6\n"); - cl_git_pass(git_config_open_ondisk(&cfg, "config")); - cl_git_pass(git_config_get_multivar_foreach(cfg, "old.value", NULL, count_me, &count)); - cl_assert_equal_i(2, count); - - count = 0; - cl_git_pass(git_config_snapshot(&snapshot, cfg)); - cl_git_pass(git_config_get_multivar_foreach(snapshot, "old.value", NULL, count_me, &count)); - cl_assert_equal_i(2, count); - - cl_git_pass(p_unlink("config")); -} - -void test_config_snapshot__includes(void) -{ - int i; - - cl_git_mkfile("including", "[include]\npath = included"); - cl_git_mkfile("included", "[section]\nkey = 1\n"); - - cl_git_pass(git_config_open_ondisk(&cfg, "including")); - cl_git_pass(git_config_snapshot(&snapshot, cfg)); - - cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); - cl_assert_equal_i(i, 1); - - /* Rewrite "included" config */ - cl_git_mkfile("included", "[section]\nkey = 11\n"); - - /* Assert that the live config changed, but snapshot remained the same */ - cl_git_pass(git_config_get_int32(&i, cfg, "section.key")); - cl_assert_equal_i(i, 11); - cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); - cl_assert_equal_i(i, 1); - - cl_git_pass(p_unlink("including")); - cl_git_pass(p_unlink("included")); -} - -void test_config_snapshot__snapshot(void) -{ - git_config *snapshot_snapshot; - int i; - - cl_git_mkfile("configfile", "[section]\nkey = 1\n"); - - cl_git_pass(git_config_open_ondisk(&cfg, "configfile")); - cl_git_pass(git_config_snapshot(&snapshot, cfg)); - - cl_git_pass(git_config_snapshot(&snapshot_snapshot, snapshot)); - - cl_git_pass(git_config_get_int32(&i, snapshot_snapshot, "section.key")); - cl_assert_equal_i(i, 1); - - git_config_free(snapshot_snapshot); - - cl_git_pass(p_unlink("configfile")); -} - -void test_config_snapshot__snapshot_from_in_memony(void) -{ - const char *configuration = "[section]\nkey = 1\n"; - git_config_backend *backend; - int i; - - cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_backend_from_string(&backend, configuration, strlen(configuration))); - cl_git_pass(git_config_add_backend(cfg, backend, 0, NULL, 0)); - - cl_git_pass(git_config_snapshot(&snapshot, cfg)); - cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); - cl_assert_equal_i(i, 1); -} diff --git a/tests/config/stress.c b/tests/config/stress.c deleted file mode 100644 index 69e6810fa..000000000 --- a/tests/config/stress.c +++ /dev/null @@ -1,206 +0,0 @@ -#include "clar_libgit2.h" - -#include "filebuf.h" -#include "futils.h" -#include "posix.h" - -#define TEST_CONFIG "git-test-config" - -static git_buf buf = GIT_BUF_INIT; - -void test_config_stress__initialize(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - - cl_git_pass(git_filebuf_open(&file, TEST_CONFIG, 0, 0666)); - - git_filebuf_printf(&file, "[color]\n\tui = auto\n"); - git_filebuf_printf(&file, "[core]\n\teditor = \n"); - - cl_git_pass(git_filebuf_commit(&file)); -} - -void test_config_stress__cleanup(void) -{ - git_buf_dispose(&buf); - p_unlink(TEST_CONFIG); -} - -void test_config_stress__dont_break_on_invalid_input(void) -{ - git_config *config; - - cl_assert(git_fs_path_exists(TEST_CONFIG)); - cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); - - cl_git_pass(git_config_get_string_buf(&buf, config, "color.ui")); - cl_git_pass(git_config_get_string_buf(&buf, config, "core.editor")); - - git_config_free(config); -} - -static void assert_config_value(git_config *config, const char *key, const char *value) -{ - git_buf_dispose(&buf); - cl_git_pass(git_config_get_string_buf(&buf, config, key)); - cl_assert_equal_s(value, buf.ptr); -} - -void test_config_stress__comments(void) -{ - git_config *config; - - cl_git_pass(git_config_open_ondisk(&config, cl_fixture("config/config12"))); - - assert_config_value(config, "some.section.test2", "hello"); - assert_config_value(config, "some.section.test3", "welcome"); - assert_config_value(config, "some.section.other", "hello! \" ; ; ; "); - assert_config_value(config, "some.section.other2", "cool! \" # # # "); - assert_config_value(config, "some.section.multi", "hi, this is a ; multiline comment # with ;\n special chars and other stuff !@#"); - assert_config_value(config, "some.section.multi2", "good, this is a ; multiline comment # with ;\n special chars and other stuff !@#"); - assert_config_value(config, "some.section.back", "this is \ba phrase"); - assert_config_value(config, "some.section.dollar", "some $sign"); - assert_config_value(config, "some.section.multiquotes", "!ls x ls # comment2 $HOME"); - assert_config_value(config, "some.section.multiquotes2", "!ls x ls \"# comment2 $HOME\""); - assert_config_value(config, "some.section.multiquotes3", "hi # ho there are # more quotes"); - assert_config_value(config, "some.section.quotecomment", "hi # ho there are # more"); - - git_config_free(config); -} - -void test_config_stress__escape_subsection_names(void) -{ - git_config *config; - - cl_assert(git_fs_path_exists("git-test-config")); - cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); - - cl_git_pass(git_config_set_string(config, "some.sec\\tion.other", "foo")); - git_config_free(config); - - cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); - - assert_config_value(config, "some.sec\\tion.other", "foo"); - - git_config_free(config); -} - -void test_config_stress__trailing_backslash(void) -{ - git_config *config; - const char *path = "C:\\iam\\some\\windows\\path\\"; - - cl_assert(git_fs_path_exists("git-test-config")); - cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); - cl_git_pass(git_config_set_string(config, "windows.path", path)); - git_config_free(config); - - cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); - assert_config_value(config, "windows.path", path); - - git_config_free(config); -} - -void test_config_stress__complex(void) -{ - git_config *config; - const char *path = "./config-immediate-multiline"; - - cl_git_mkfile(path, "[imm]\n multi = \"\\\nfoo\""); - cl_git_pass(git_config_open_ondisk(&config, path)); - assert_config_value(config, "imm.multi", "foo"); - - git_config_free(config); -} - -void test_config_stress__quick_write(void) -{ - git_config *config_w, *config_r; - const char *path = "./config-quick-write"; - const char *key = "quick.write"; - int32_t i; - - /* Create an external writer for one instance with the other one */ - cl_git_pass(git_config_open_ondisk(&config_w, path)); - cl_git_pass(git_config_open_ondisk(&config_r, path)); - - /* Write and read in the same second (repeat to increase the chance of it happening) */ - for (i = 0; i < 10; i++) { - int32_t val; - cl_git_pass(git_config_set_int32(config_w, key, i)); - cl_msleep(1); - cl_git_pass(git_config_get_int32(&val, config_r, key)); - cl_assert_equal_i(i, val); - } - - git_config_free(config_r); - git_config_free(config_w); -} - -static int foreach_cb(const git_config_entry *entry, void *payload) -{ - if (!strcmp(entry->name, "key.value")) { - *(char **)payload = git__strdup(entry->value); - return 0; - } - return -1; -} - -void test_config_stress__foreach_refreshes(void) -{ - git_config *config_w, *config_r; - char *value = NULL; - - cl_git_pass(git_config_open_ondisk(&config_w, "./cfg")); - cl_git_pass(git_config_open_ondisk(&config_r, "./cfg")); - - cl_git_pass(git_config_set_string(config_w, "key.value", "1")); - cl_git_pass(git_config_foreach_match(config_r, "key.value", foreach_cb, &value)); - - cl_assert_equal_s(value, "1"); - - git_config_free(config_r); - git_config_free(config_w); - git__free(value); -} - -void test_config_stress__foreach_refreshes_snapshot(void) -{ - git_config *config, *snapshot; - char *value = NULL; - - cl_git_pass(git_config_open_ondisk(&config, "./cfg")); - - cl_git_pass(git_config_set_string(config, "key.value", "1")); - cl_git_pass(git_config_snapshot(&snapshot, config)); - cl_git_pass(git_config_foreach_match(snapshot, "key.value", foreach_cb, &value)); - - cl_assert_equal_s(value, "1"); - - git_config_free(snapshot); - git_config_free(config); - git__free(value); -} - -void test_config_stress__huge_section_with_many_values(void) -{ - git_config *config; - - if (!cl_is_env_set("GITTEST_INVASIVE_SPEED")) - cl_skip(); - - /* - * The config file is structured in such a way that is - * has a section header that is approximately 500kb of - * size followed by 40k entries. While the resulting - * configuration file itself is roughly 650kb in size and - * thus considered to be rather small, in the past we'd - * balloon to more than 20GB of memory (20000x500kb) - * while parsing the file. It thus was a trivial way to - * cause an out-of-memory situation and thus cause denial - * of service, e.g. via gitmodules. - */ - cl_git_pass(git_config_open_ondisk(&config, cl_fixture("config/config-oom"))); - - git_config_free(config); -} diff --git a/tests/config/validkeyname.c b/tests/config/validkeyname.c deleted file mode 100644 index 4b36509af..000000000 --- a/tests/config/validkeyname.c +++ /dev/null @@ -1,49 +0,0 @@ -#include "clar_libgit2.h" - -#include "config.h" - -static git_config *cfg; - -void test_config_validkeyname__initialize(void) -{ - cl_fixture_sandbox("config/config10"); - - cl_git_pass(git_config_open_ondisk(&cfg, "config10")); -} - -void test_config_validkeyname__cleanup(void) -{ - git_config_free(cfg); - cfg = NULL; - - cl_fixture_cleanup("config10"); -} - -static void assert_invalid_config_key_name(const char *name) -{ - git_buf buf = GIT_BUF_INIT; - - cl_git_fail_with(git_config_get_string_buf(&buf, cfg, name), - GIT_EINVALIDSPEC); - cl_git_fail_with(git_config_set_string(cfg, name, "42"), - GIT_EINVALIDSPEC); - cl_git_fail_with(git_config_delete_entry(cfg, name), - GIT_EINVALIDSPEC); - cl_git_fail_with(git_config_get_multivar_foreach(cfg, name, "*", NULL, NULL), - GIT_EINVALIDSPEC); - cl_git_fail_with(git_config_set_multivar(cfg, name, "*", "42"), - GIT_EINVALIDSPEC); -} - -void test_config_validkeyname__accessing_requires_a_valid_name(void) -{ - assert_invalid_config_key_name(""); - assert_invalid_config_key_name("."); - assert_invalid_config_key_name(".."); - assert_invalid_config_key_name("core."); - assert_invalid_config_key_name("d#ff.dirstat.lines"); - assert_invalid_config_key_name("diff.dirstat.lines#"); - assert_invalid_config_key_name("dif\nf.dirstat.lines"); - assert_invalid_config_key_name("dif.dir\nstat.lines"); - assert_invalid_config_key_name("dif.dirstat.li\nes"); -} diff --git a/tests/config/write.c b/tests/config/write.c deleted file mode 100644 index 9d8c3fe94..000000000 --- a/tests/config/write.c +++ /dev/null @@ -1,764 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "git2/sys/config.h" -#include "config.h" - -void test_config_write__initialize(void) -{ - cl_fixture_sandbox("config/config9"); - cl_fixture_sandbox("config/config15"); - cl_fixture_sandbox("config/config17"); - cl_fixture_sandbox("config/config22"); -} - -void test_config_write__cleanup(void) -{ - cl_fixture_cleanup("config9"); - cl_fixture_cleanup("config15"); - cl_fixture_cleanup("config17"); - cl_fixture_cleanup("config22"); -} - -void test_config_write__replace_value(void) -{ - git_config *cfg; - int i; - int64_t l, expected = +9223372036854775803; - - /* By freeing the config, we make sure we flush the values */ - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_set_int32(cfg, "core.dummy", 5)); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy")); - cl_assert(i == 5); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_set_int32(cfg, "core.dummy", 1)); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_set_int64(cfg, "core.verylong", expected)); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_get_int64(&l, cfg, "core.verylong")); - cl_assert(l == expected); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_must_fail(git_config_get_int32(&i, cfg, "core.verylong")); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_set_int64(cfg, "core.verylong", 1)); - git_config_free(cfg); -} - -void test_config_write__delete_value(void) -{ - git_config *cfg; - int32_t i; - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_set_int32(cfg, "core.dummy", 5)); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_delete_entry(cfg, "core.dummy")); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_assert(git_config_get_int32(&i, cfg, "core.dummy") == GIT_ENOTFOUND); - cl_git_pass(git_config_set_int32(cfg, "core.dummy", 1)); - git_config_free(cfg); -} - -/* - * At the beginning of the test: - * - config9 has: core.dummy2=42 - * - config15 has: core.dummy2=7 - */ -void test_config_write__delete_value_at_specific_level(void) -{ - git_config *cfg, *cfg_specific; - int32_t i; - - cl_git_pass(git_config_open_ondisk(&cfg, "config15")); - cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2")); - cl_assert(i == 7); - git_config_free(cfg); - - cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_add_file_ondisk(cfg, "config9", - GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); - cl_git_pass(git_config_add_file_ondisk(cfg, "config15", - GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - - cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL)); - - cl_git_pass(git_config_delete_entry(cfg_specific, "core.dummy2")); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config15")); - cl_assert(git_config_get_int32(&i, cfg, "core.dummy2") == GIT_ENOTFOUND); - cl_git_pass(git_config_set_int32(cfg, "core.dummy2", 7)); - - git_config_free(cfg_specific); - git_config_free(cfg); -} - -/* - * This test exposes a bug where duplicate empty section headers could prevent - * deletion of config entries. - */ -void test_config_write__delete_value_with_duplicate_header(void) -{ - const char *file_name = "config-duplicate-header"; - const char *entry_name = "remote.origin.url"; - git_config *cfg; - git_config_entry *entry; - - /* This config can occur after removing and re-adding the origin remote */ - const char *file_content = - "[remote \"origin\"]\n" \ - "[branch \"master\"]\n" \ - " remote = \"origin\"\n" \ - "[remote \"origin\"]\n" \ - " url = \"foo\"\n"; - - /* Write the test config and make sure the expected entry exists */ - cl_git_mkfile(file_name, file_content); - cl_git_pass(git_config_open_ondisk(&cfg, file_name)); - cl_git_pass(git_config_get_entry(&entry, cfg, entry_name)); - - /* Delete that entry */ - cl_git_pass(git_config_delete_entry(cfg, entry_name)); - - /* Reopen the file and make sure the entry no longer exists */ - git_config_entry_free(entry); - git_config_free(cfg); - cl_git_pass(git_config_open_ondisk(&cfg, file_name)); - cl_git_fail(git_config_get_entry(&entry, cfg, entry_name)); - - /* Cleanup */ - git_config_entry_free(entry); - git_config_free(cfg); -} - -/* - * This test exposes a bug where duplicate section headers could cause - * config_write to add a new entry when one already exists. - */ -void test_config_write__add_value_with_duplicate_header(void) -{ - const char *file_name = "config-duplicate-insert"; - const char *entry_name = "foo.c"; - const char *old_val = "old"; - const char *new_val = "new"; - const char *str; - git_config *cfg, *snapshot; - - /* c = old should be replaced by c = new. - * The bug causes c = new to be inserted under the first 'foo' header. - */ - const char *file_content = - "[foo]\n" \ - " a = b\n" \ - "[other]\n" \ - " a = b\n" \ - "[foo]\n" \ - " c = old\n"; - - /* Write the test config */ - cl_git_mkfile(file_name, file_content); - cl_git_pass(git_config_open_ondisk(&cfg, file_name)); - - /* make sure the expected entry (foo.c) exists */ - cl_git_pass(git_config_snapshot(&snapshot, cfg)); - cl_git_pass(git_config_get_string(&str, snapshot, entry_name)); - cl_assert_equal_s(old_val, str); - git_config_free(snapshot); - - /* Try setting foo.c to something else */ - cl_git_pass(git_config_set_string(cfg, entry_name, new_val)); - git_config_free(cfg); - - /* Reopen the file and make sure the new value was set */ - cl_git_pass(git_config_open_ondisk(&cfg, file_name)); - cl_git_pass(git_config_snapshot(&snapshot, cfg)); - cl_git_pass(git_config_get_string(&str, snapshot, entry_name)); - cl_assert_equal_s(new_val, str); - - /* Cleanup */ - git_config_free(snapshot); - git_config_free(cfg); -} - -void test_config_write__overwrite_value_with_duplicate_header(void) -{ - const char *file_name = "config-duplicate-header"; - const char *entry_name = "remote.origin.url"; - git_config *cfg; - git_config_entry *entry; - - /* This config can occur after removing and re-adding the origin remote */ - const char *file_content = - "[remote \"origin\"]\n" \ - "[branch \"master\"]\n" \ - " remote = \"origin\"\n" \ - "[remote \"origin\"]\n" \ - " url = \"foo\"\n"; - - /* Write the test config and make sure the expected entry exists */ - cl_git_mkfile(file_name, file_content); - cl_git_pass(git_config_open_ondisk(&cfg, file_name)); - cl_git_pass(git_config_get_entry(&entry, cfg, entry_name)); - - /* Update that entry */ - cl_git_pass(git_config_set_string(cfg, entry_name, "newurl")); - - /* Reopen the file and make sure the entry was updated */ - git_config_entry_free(entry); - git_config_free(cfg); - cl_git_pass(git_config_open_ondisk(&cfg, file_name)); - cl_git_pass(git_config_get_entry(&entry, cfg, entry_name)); - - cl_assert_equal_s("newurl", entry->value); - - /* Cleanup */ - git_config_entry_free(entry); - git_config_free(cfg); -} - -static int multivar_cb(const git_config_entry *entry, void *data) -{ - int *n = (int *)data; - - cl_assert_equal_s(entry->value, "newurl"); - - (*n)++; - - return 0; -} - -void test_config_write__overwrite_multivar_within_duplicate_header(void) -{ - const char *file_name = "config-duplicate-header"; - const char *entry_name = "remote.origin.url"; - git_config *cfg; - git_config_entry *entry; - int n = 0; - - /* This config can occur after removing and re-adding the origin remote */ - const char *file_content = - "[remote \"origin\"]\n" \ - " url = \"bar\"\n" \ - "[branch \"master\"]\n" \ - " remote = \"origin\"\n" \ - "[remote \"origin\"]\n" \ - " url = \"foo\"\n"; - - /* Write the test config and make sure the expected entry exists */ - cl_git_mkfile(file_name, file_content); - cl_git_pass(git_config_open_ondisk(&cfg, file_name)); - cl_git_pass(git_config_get_entry(&entry, cfg, entry_name)); - - /* Update that entry */ - cl_git_pass(git_config_set_multivar(cfg, entry_name, ".*", "newurl")); - git_config_entry_free(entry); - git_config_free(cfg); - - /* Reopen the file and make sure the entry was updated */ - cl_git_pass(git_config_open_ondisk(&cfg, file_name)); - cl_git_pass(git_config_get_multivar_foreach(cfg, entry_name, NULL, multivar_cb, &n)); - cl_assert_equal_i(2, n); - - /* Cleanup */ - git_config_free(cfg); -} - -void test_config_write__write_subsection(void) -{ - git_config *cfg; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_set_string(cfg, "my.own.var", "works")); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "my.own.var")); - cl_assert_equal_s("works", buf.ptr); - - git_buf_dispose(&buf); - git_config_free(cfg); -} - -void test_config_write__delete_inexistent(void) -{ - git_config *cfg; - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_assert(git_config_delete_entry(cfg, "core.imaginary") == GIT_ENOTFOUND); - git_config_free(cfg); -} - -void test_config_write__value_containing_quotes(void) -{ - git_config *cfg; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_set_string(cfg, "core.somevar", "this \"has\" quotes")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.somevar")); - cl_assert_equal_s("this \"has\" quotes", buf.ptr); - git_buf_dispose(&buf); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.somevar")); - cl_assert_equal_s("this \"has\" quotes", buf.ptr); - git_buf_dispose(&buf); - git_config_free(cfg); - - /* The code path for values that already exist is different, check that one as well */ - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_set_string(cfg, "core.somevar", "this also \"has\" quotes")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.somevar")); - cl_assert_equal_s("this also \"has\" quotes", buf.ptr); - git_buf_dispose(&buf); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.somevar")); - cl_assert_equal_s("this also \"has\" quotes", buf.ptr); - git_buf_dispose(&buf); - git_config_free(cfg); -} - -void test_config_write__escape_value(void) -{ - git_config *cfg; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_set_string(cfg, "core.somevar", "this \"has\" quotes and \t")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.somevar")); - cl_assert_equal_s("this \"has\" quotes and \t", buf.ptr); - git_buf_dispose(&buf); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.somevar")); - cl_assert_equal_s("this \"has\" quotes and \t", buf.ptr); - git_buf_dispose(&buf); - git_config_free(cfg); -} - -void test_config_write__add_value_at_specific_level(void) -{ - git_config *cfg, *cfg_specific; - int i; - int64_t l, expected = +9223372036854775803; - git_buf buf = GIT_BUF_INIT; - - /* open config15 as global level config file */ - cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_add_file_ondisk(cfg, "config9", - GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); - cl_git_pass(git_config_add_file_ondisk(cfg, "config15", - GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); - - cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL)); - - cl_git_pass(git_config_set_int32(cfg_specific, "core.int32global", 28)); - cl_git_pass(git_config_set_int64(cfg_specific, "core.int64global", expected)); - cl_git_pass(git_config_set_bool(cfg_specific, "core.boolglobal", true)); - cl_git_pass(git_config_set_string(cfg_specific, "core.stringglobal", "I'm a global config value!")); - git_config_free(cfg_specific); - git_config_free(cfg); - - /* open config15 as local level config file */ - cl_git_pass(git_config_open_ondisk(&cfg, "config15")); - - cl_git_pass(git_config_get_int32(&i, cfg, "core.int32global")); - cl_assert_equal_i(28, i); - cl_git_pass(git_config_get_int64(&l, cfg, "core.int64global")); - cl_assert(l == expected); - cl_git_pass(git_config_get_bool(&i, cfg, "core.boolglobal")); - cl_assert_equal_b(true, i); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.stringglobal")); - cl_assert_equal_s("I'm a global config value!", buf.ptr); - - git_buf_dispose(&buf); - git_config_free(cfg); -} - -void test_config_write__add_value_at_file_with_no_clrf_at_the_end(void) -{ - git_config *cfg; - int i; - - cl_git_pass(git_config_open_ondisk(&cfg, "config17")); - cl_git_pass(git_config_set_int32(cfg, "core.newline", 7)); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config17")); - cl_git_pass(git_config_get_int32(&i, cfg, "core.newline")); - cl_assert_equal_i(7, i); - - git_config_free(cfg); -} - -void test_config_write__add_section_at_file_with_no_clrf_at_the_end(void) -{ - git_config *cfg; - int i; - - cl_git_pass(git_config_open_ondisk(&cfg, "config17")); - cl_git_pass(git_config_set_int32(cfg, "diff.context", 10)); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, "config17")); - cl_git_pass(git_config_get_int32(&i, cfg, "diff.context")); - cl_assert_equal_i(10, i); - - git_config_free(cfg); -} - -void test_config_write__add_value_which_needs_quotes(void) -{ - git_config *cfg, *base; - const char* str1; - const char* str2; - const char* str3; - const char* str4; - const char* str5; - - cl_git_pass(git_config_open_ondisk(&cfg, "config17")); - cl_git_pass(git_config_set_string(cfg, "core.startwithspace", " Something")); - cl_git_pass(git_config_set_string(cfg, "core.endwithspace", "Something ")); - cl_git_pass(git_config_set_string(cfg, "core.containscommentchar1", "some#thing")); - cl_git_pass(git_config_set_string(cfg, "core.containscommentchar2", "some;thing")); - cl_git_pass(git_config_set_string(cfg, "core.startwhithsapceandcontainsdoublequote", " some\"thing")); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&base, "config17")); - cl_git_pass(git_config_snapshot(&cfg, base)); - cl_git_pass(git_config_get_string(&str1, cfg, "core.startwithspace")); - cl_assert_equal_s(" Something", str1); - cl_git_pass(git_config_get_string(&str2, cfg, "core.endwithspace")); - cl_assert_equal_s("Something ", str2); - cl_git_pass(git_config_get_string(&str3, cfg, "core.containscommentchar1")); - cl_assert_equal_s("some#thing", str3); - cl_git_pass(git_config_get_string(&str4, cfg, "core.containscommentchar2")); - cl_assert_equal_s("some;thing", str4); - cl_git_pass(git_config_get_string(&str5, cfg, "core.startwhithsapceandcontainsdoublequote")); - cl_assert_equal_s(" some\"thing", str5); - git_config_free(cfg); - git_config_free(base); -} - -void test_config_write__can_set_a_value_to_NULL(void) -{ - git_repository *repository; - git_config *config; - - repository = cl_git_sandbox_init("testrepo.git"); - - cl_git_pass(git_repository_config(&config, repository)); - cl_git_fail(git_config_set_string(config, "a.b.c", NULL)); - git_config_free(config); - - cl_git_sandbox_cleanup(); -} - -void test_config_write__can_set_an_empty_value(void) -{ - git_repository *repository; - git_config *config; - git_buf buf = {0}; - - repository = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_config(&config, repository)); - - cl_git_pass(git_config_set_string(config, "core.somevar", "")); - cl_git_pass(git_config_get_string_buf(&buf, config, "core.somevar")); - cl_assert_equal_s("", buf.ptr); - - git_buf_dispose(&buf); - git_config_free(config); - cl_git_sandbox_cleanup(); -} - -void test_config_write__updating_a_locked_config_file_returns_ELOCKED(void) -{ - git_config *cfg; - - cl_git_pass(git_config_open_ondisk(&cfg, "config9")); - - cl_git_mkfile("config9.lock", "[core]\n"); - - cl_git_fail_with(git_config_set_string(cfg, "core.dump", "boom"), GIT_ELOCKED); - - git_config_free(cfg); -} - -void test_config_write__outside_change(void) -{ - int32_t tmp; - git_config *cfg; - const char *filename = "config-ext-change"; - - cl_git_mkfile(filename, "[old]\nvalue = 5\n"); - - cl_git_pass(git_config_open_ondisk(&cfg, filename)); - - cl_git_pass(git_config_get_int32(&tmp, cfg, "old.value")); - - /* Change the value on the file itself (simulate external process) */ - cl_git_mkfile(filename, "[old]\nvalue = 6\n"); - - cl_git_pass(git_config_set_int32(cfg, "new.value", 7)); - - cl_git_pass(git_config_get_int32(&tmp, cfg, "old.value")); - cl_assert_equal_i(6, tmp); - - git_config_free(cfg); -} - -#define FOO_COMMENT \ - "; another comment!\n" - -#define SECTION_FOO \ - "\n" \ - " \n" \ - " [section \"foo\"] \n" \ - " # here's a comment\n" \ - "\tname = \"value\"\n" \ - " name2 = \"value2\"\n" \ - -#define SECTION_FOO_WITH_COMMENT SECTION_FOO FOO_COMMENT - -#define SECTION_BAR \ - "[section \"bar\"]\t\n" \ - "\t \n" \ - " barname=\"value\"\n" - - -void test_config_write__preserves_whitespace_and_comments(void) -{ - const char *file_name = "config-duplicate-header"; - const char *n; - git_config *cfg; - git_str newfile = GIT_STR_INIT; - - /* This config can occur after removing and re-adding the origin remote */ - const char *file_content = SECTION_FOO_WITH_COMMENT SECTION_BAR; - - /* Write the test config and make sure the expected entry exists */ - cl_git_mkfile(file_name, file_content); - cl_git_pass(git_config_open_ondisk(&cfg, file_name)); - cl_git_pass(git_config_set_string(cfg, "section.foo.other", "otherval")); - cl_git_pass(git_config_set_string(cfg, "newsection.newname", "new_value")); - - /* Ensure that we didn't needlessly mangle the config file */ - cl_git_pass(git_futils_readbuffer(&newfile, file_name)); - n = newfile.ptr; - - cl_assert_equal_strn(SECTION_FOO, n, strlen(SECTION_FOO)); - n += strlen(SECTION_FOO); - cl_assert_equal_strn("\tother = otherval\n", n, strlen("\tother = otherval\n")); - n += strlen("\tother = otherval\n"); - cl_assert_equal_strn(FOO_COMMENT, n, strlen(FOO_COMMENT)); - n += strlen(FOO_COMMENT); - - cl_assert_equal_strn(SECTION_BAR, n, strlen(SECTION_BAR)); - n += strlen(SECTION_BAR); - - cl_assert_equal_s("[newsection]\n\tnewname = new_value\n", n); - - git_str_dispose(&newfile); - git_config_free(cfg); -} - -void test_config_write__preserves_entry_with_name_only(void) -{ - const char *file_name = "config-empty-value"; - git_config *cfg; - git_str newfile = GIT_STR_INIT; - - /* Write the test config and make sure the expected entry exists */ - cl_git_mkfile(file_name, "[section \"foo\"]\n\tname\n"); - cl_git_pass(git_config_open_ondisk(&cfg, file_name)); - cl_git_pass(git_config_set_string(cfg, "newsection.newname", "new_value")); - cl_git_pass(git_config_set_string(cfg, "section.foo.other", "otherval")); - - cl_git_pass(git_futils_readbuffer(&newfile, file_name)); - cl_assert_equal_s("[section \"foo\"]\n\tname\n\tother = otherval\n[newsection]\n\tnewname = new_value\n", newfile.ptr); - - git_str_dispose(&newfile); - git_config_free(cfg); -} - -void test_config_write__to_empty_file(void) -{ - git_config *cfg; - const char *filename = "config-file"; - git_str result = GIT_STR_INIT; - - cl_git_mkfile(filename, ""); - cl_git_pass(git_config_open_ondisk(&cfg, filename)); - cl_git_pass(git_config_set_string(cfg, "section.name", "value")); - git_config_free(cfg); - - cl_git_pass(git_futils_readbuffer(&result, "config-file")); - cl_assert_equal_s("[section]\n\tname = value\n", result.ptr); - - git_str_dispose(&result); -} - -void test_config_write__to_file_with_only_comment(void) -{ - git_config *cfg; - const char *filename = "config-file"; - git_str result = GIT_STR_INIT; - - cl_git_mkfile(filename, "\n\n"); - cl_git_pass(git_config_open_ondisk(&cfg, filename)); - cl_git_pass(git_config_set_string(cfg, "section.name", "value")); - git_config_free(cfg); - - cl_git_pass(git_futils_readbuffer(&result, "config-file")); - cl_assert_equal_s("\n\n[section]\n\tname = value\n", result.ptr); - - git_str_dispose(&result); -} - -void test_config_write__locking(void) -{ - git_config *cfg, *cfg2; - git_config_entry *entry; - git_transaction *tx; - const char *filename = "locked-file"; - - /* Open the config and lock it */ - cl_git_mkfile(filename, "[section]\n\tname = value\n"); - cl_git_pass(git_config_open_ondisk(&cfg, filename)); - cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); - cl_assert_equal_s("value", entry->value); - git_config_entry_free(entry); - cl_git_pass(git_config_lock(&tx, cfg)); - - /* Change entries in the locked backend */ - cl_git_pass(git_config_set_string(cfg, "section.name", "other value")); - cl_git_pass(git_config_set_string(cfg, "section2.name3", "more value")); - - /* We can see that the file we read from hasn't changed */ - cl_git_pass(git_config_open_ondisk(&cfg2, filename)); - cl_git_pass(git_config_get_entry(&entry, cfg2, "section.name")); - cl_assert_equal_s("value", entry->value); - git_config_entry_free(entry); - cl_git_fail_with(GIT_ENOTFOUND, git_config_get_entry(&entry, cfg2, "section2.name3")); - git_config_free(cfg2); - - /* And we also get the old view when we read from the locked config */ - cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); - cl_assert_equal_s("value", entry->value); - git_config_entry_free(entry); - cl_git_fail_with(GIT_ENOTFOUND, git_config_get_entry(&entry, cfg, "section2.name3")); - - cl_git_pass(git_transaction_commit(tx)); - git_transaction_free(tx); - - /* Now that we've unlocked it, we should see both updates */ - cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); - cl_assert_equal_s("other value", entry->value); - git_config_entry_free(entry); - cl_git_pass(git_config_get_entry(&entry, cfg, "section2.name3")); - cl_assert_equal_s("more value", entry->value); - git_config_entry_free(entry); - - git_config_free(cfg); - - /* We should also see the changes after reopening the config */ - cl_git_pass(git_config_open_ondisk(&cfg, filename)); - cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); - cl_assert_equal_s("other value", entry->value); - git_config_entry_free(entry); - cl_git_pass(git_config_get_entry(&entry, cfg, "section2.name3")); - cl_assert_equal_s("more value", entry->value); - git_config_entry_free(entry); - - git_config_free(cfg); -} - -void test_config_write__repeated(void) -{ - const char *filename = "config-repeated"; - git_config *cfg; - git_str result = GIT_STR_INIT; - const char *expected = "[sample \"prefix\"]\n\ -\tsetting1 = someValue1\n\ -\tsetting2 = someValue2\n\ -\tsetting3 = someValue3\n\ -\tsetting4 = someValue4\n\ -"; - cl_git_pass(git_config_open_ondisk(&cfg, filename)); - cl_git_pass(git_config_set_string(cfg, "sample.prefix.setting1", "someValue1")); - cl_git_pass(git_config_set_string(cfg, "sample.prefix.setting2", "someValue2")); - cl_git_pass(git_config_set_string(cfg, "sample.prefix.setting3", "someValue3")); - cl_git_pass(git_config_set_string(cfg, "sample.prefix.setting4", "someValue4")); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, filename)); - - cl_git_pass(git_futils_readbuffer(&result, filename)); - cl_assert_equal_s(expected, result.ptr); - git_str_dispose(&result); - - git_config_free(cfg); -} - -void test_config_write__preserve_case(void) -{ - const char *filename = "config-preserve-case"; - git_config *cfg; - git_str result = GIT_STR_INIT; - const char *expected = "[sOMe]\n" \ - "\tThInG = foo\n" \ - "\tOtheR = thing\n"; - - cl_git_pass(git_config_open_ondisk(&cfg, filename)); - cl_git_pass(git_config_set_string(cfg, "sOMe.ThInG", "foo")); - cl_git_pass(git_config_set_string(cfg, "SomE.OtheR", "thing")); - git_config_free(cfg); - - cl_git_pass(git_config_open_ondisk(&cfg, filename)); - - cl_git_pass(git_futils_readbuffer(&result, filename)); - cl_assert_equal_s(expected, result.ptr); - git_str_dispose(&result); - - git_config_free(cfg); -} - -void test_config_write__write_config_file_with_multi_line_value(void) -{ - git_config* cfg; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_config_open_ondisk(&cfg, "config22")); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "alias.m")); - cl_assert_equal_s("cmd ;; ;; bar", buf.ptr); - cl_git_pass(git_config_set_string(cfg, "sOMe.ThInG", "foo")); - git_buf_dispose(&buf); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "alias.m")); - cl_assert_equal_s("cmd ;; ;; bar", buf.ptr); - git_buf_dispose(&buf); - - git_config_free(cfg); -} diff --git a/tests/core/array.c b/tests/core/array.c deleted file mode 100644 index 8e626a506..000000000 --- a/tests/core/array.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "clar_libgit2.h" -#include "array.h" - -static int int_lookup(const void *k, const void *a) -{ - const int *one = (const int *)k; - int *two = (int *)a; - - return *one - *two; -} - -#define expect_pos(k, n, ret) \ - key = (k); \ - cl_assert_equal_i((ret), \ - git_array_search(&p, integers, int_lookup, &key)); \ - cl_assert_equal_i((n), p); - -void test_core_array__bsearch2(void) -{ - git_array_t(int) integers = GIT_ARRAY_INIT; - int *i, key; - size_t p; - - i = git_array_alloc(integers); *i = 2; - i = git_array_alloc(integers); *i = 3; - i = git_array_alloc(integers); *i = 5; - i = git_array_alloc(integers); *i = 7; - i = git_array_alloc(integers); *i = 7; - i = git_array_alloc(integers); *i = 8; - i = git_array_alloc(integers); *i = 13; - i = git_array_alloc(integers); *i = 21; - i = git_array_alloc(integers); *i = 25; - i = git_array_alloc(integers); *i = 42; - i = git_array_alloc(integers); *i = 69; - i = git_array_alloc(integers); *i = 121; - i = git_array_alloc(integers); *i = 256; - i = git_array_alloc(integers); *i = 512; - i = git_array_alloc(integers); *i = 513; - i = git_array_alloc(integers); *i = 514; - i = git_array_alloc(integers); *i = 516; - i = git_array_alloc(integers); *i = 516; - i = git_array_alloc(integers); *i = 517; - - /* value to search for, expected position, return code */ - expect_pos(3, 1, GIT_OK); - expect_pos(2, 0, GIT_OK); - expect_pos(1, 0, GIT_ENOTFOUND); - expect_pos(25, 8, GIT_OK); - expect_pos(26, 9, GIT_ENOTFOUND); - expect_pos(42, 9, GIT_OK); - expect_pos(50, 10, GIT_ENOTFOUND); - expect_pos(68, 10, GIT_ENOTFOUND); - expect_pos(256, 12, GIT_OK); - - git_array_clear(integers); -} - diff --git a/tests/core/assert.c b/tests/core/assert.c deleted file mode 100644 index ef75624b9..000000000 --- a/tests/core/assert.c +++ /dev/null @@ -1,94 +0,0 @@ -#ifdef GIT_ASSERT_HARD -# undef GIT_ASSERT_HARD -#endif - -#define GIT_ASSERT_HARD 0 - -#include "clar_libgit2.h" - -static const char *hello_world = "hello, world"; -static const char *fail = "FAIL"; - -static int dummy_fn(const char *myarg) -{ - GIT_ASSERT_ARG(myarg); - GIT_ASSERT_ARG(myarg != hello_world); - return 0; -} - -static const char *fn_returns_string(const char *myarg) -{ - GIT_ASSERT_ARG_WITH_RETVAL(myarg, fail); - GIT_ASSERT_ARG_WITH_RETVAL(myarg != hello_world, fail); - - return myarg; -} - -static int bad_math(void) -{ - GIT_ASSERT(1 + 1 == 3); - return 42; -} - -static const char *bad_returns_string(void) -{ - GIT_ASSERT_WITH_RETVAL(1 + 1 == 3, NULL); - return hello_world; -} - -void test_core_assert__argument(void) -{ - cl_git_fail(dummy_fn(NULL)); - cl_assert(git_error_last()); - cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); - cl_assert_equal_s("invalid argument: 'myarg'", git_error_last()->message); - - cl_git_fail(dummy_fn(hello_world)); - cl_assert(git_error_last()); - cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); - cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message); - - cl_git_pass(dummy_fn("foo")); -} - -void test_core_assert__argument_with_non_int_return_type(void) -{ - const char *foo = "foo"; - - cl_assert_equal_p(fail, fn_returns_string(NULL)); - cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); - cl_assert_equal_s("invalid argument: 'myarg'", git_error_last()->message); - - cl_assert_equal_p(fail, fn_returns_string(hello_world)); - cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); - cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message); - - cl_assert_equal_p(foo, fn_returns_string(foo)); -} - -void test_core_assert__argument_with_void_return_type(void) -{ - const char *foo = "foo"; - - git_error_clear(); - fn_returns_string(hello_world); - cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); - cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message); - - git_error_clear(); - cl_assert_equal_p(foo, fn_returns_string(foo)); - cl_assert_equal_p(NULL, git_error_last()); -} - -void test_core_assert__internal(void) -{ - cl_git_fail(bad_math()); - cl_assert(git_error_last()); - cl_assert_equal_i(GIT_ERROR_INTERNAL, git_error_last()->klass); - cl_assert_equal_s("unrecoverable internal error: '1 + 1 == 3'", git_error_last()->message); - - cl_assert_equal_p(NULL, bad_returns_string()); - cl_assert(git_error_last()); - cl_assert_equal_i(GIT_ERROR_INTERNAL, git_error_last()->klass); - cl_assert_equal_s("unrecoverable internal error: '1 + 1 == 3'", git_error_last()->message); -} diff --git a/tests/core/bitvec.c b/tests/core/bitvec.c deleted file mode 100644 index 48d7b99f0..000000000 --- a/tests/core/bitvec.c +++ /dev/null @@ -1,64 +0,0 @@ -#include "clar_libgit2.h" -#include "bitvec.h" - -#if 0 -static void print_bitvec(git_bitvec *bv) -{ - int b; - - if (!bv->length) { - for (b = 63; b >= 0; --b) - fprintf(stderr, "%d", (bv->u.bits & (1ul << b)) ? 1 : 0); - } else { - for (b = bv->length * 8; b >= 0; --b) - fprintf(stderr, "%d", (bv->u.ptr[b >> 3] & (b & 0x0ff)) ? 1 : 0); - } - fprintf(stderr, "\n"); -} -#endif - -static void set_some_bits(git_bitvec *bv, size_t length) -{ - size_t i; - - for (i = 0; i < length; ++i) { - if (i % 3 == 0 || i % 7 == 0) - git_bitvec_set(bv, i, true); - } -} - -static void check_some_bits(git_bitvec *bv, size_t length) -{ - size_t i; - - for (i = 0; i < length; ++i) - cl_assert_equal_b(i % 3 == 0 || i % 7 == 0, git_bitvec_get(bv, i)); -} - -void test_core_bitvec__0(void) -{ - git_bitvec bv; - - cl_git_pass(git_bitvec_init(&bv, 32)); - set_some_bits(&bv, 16); - check_some_bits(&bv, 16); - git_bitvec_clear(&bv); - set_some_bits(&bv, 32); - check_some_bits(&bv, 32); - git_bitvec_clear(&bv); - set_some_bits(&bv, 64); - check_some_bits(&bv, 64); - git_bitvec_free(&bv); - - cl_git_pass(git_bitvec_init(&bv, 128)); - set_some_bits(&bv, 32); - check_some_bits(&bv, 32); - set_some_bits(&bv, 128); - check_some_bits(&bv, 128); - git_bitvec_free(&bv); - - cl_git_pass(git_bitvec_init(&bv, 4000)); - set_some_bits(&bv, 4000); - check_some_bits(&bv, 4000); - git_bitvec_free(&bv); -} diff --git a/tests/core/buf.c b/tests/core/buf.c deleted file mode 100644 index 3959fa883..000000000 --- a/tests/core/buf.c +++ /dev/null @@ -1,54 +0,0 @@ -#include "clar_libgit2.h" -#include "buf.h" - -void test_core_buf__sanitize(void) -{ - git_buf buf = { (char *)0x42, 0, 16 }; - - cl_git_pass(git_buf_sanitize(&buf)); - cl_assert_equal_s(buf.ptr, ""); - cl_assert_equal_i(buf.reserved, 0); - cl_assert_equal_i(buf.size, 0); - - git_buf_dispose(&buf); -} - -void test_core_buf__tostr(void) -{ - git_str str = GIT_STR_INIT; - git_buf buf = { (char *)0x42, 0, 16 }; - - cl_git_pass(git_buf_tostr(&str, &buf)); - - cl_assert_equal_s(buf.ptr, ""); - cl_assert_equal_i(buf.reserved, 0); - cl_assert_equal_i(buf.size, 0); - - cl_assert_equal_s(str.ptr, ""); - cl_assert_equal_i(str.asize, 0); - cl_assert_equal_i(str.size, 0); - - git_buf_dispose(&buf); - git_str_dispose(&str); -} - -void test_core_buf__fromstr(void) -{ - git_str str = GIT_STR_INIT; - git_buf buf = { (char *)0x42, 0, 16 }; - - cl_git_pass(git_buf_tostr(&str, &buf)); - cl_git_pass(git_str_puts(&str, "Hello, world.")); - cl_git_pass(git_buf_fromstr(&buf, &str)); - - cl_assert(buf.reserved > 14); - cl_assert_equal_i(buf.size, 13); - cl_assert_equal_s(buf.ptr, "Hello, world."); - - cl_assert_equal_s(str.ptr, ""); - cl_assert_equal_i(str.asize, 0); - cl_assert_equal_i(str.size, 0); - - git_buf_dispose(&buf); - git_str_dispose(&str); -} diff --git a/tests/core/copy.c b/tests/core/copy.c deleted file mode 100644 index 6d22b503d..000000000 --- a/tests/core/copy.c +++ /dev/null @@ -1,153 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "posix.h" - -void test_core_copy__file(void) -{ - struct stat st; - const char *content = "This is some stuff to copy\n"; - - cl_git_mkfile("copy_me", content); - - cl_git_pass(git_futils_cp("copy_me", "copy_me_two", 0664)); - - cl_git_pass(git_fs_path_lstat("copy_me_two", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert(strlen(content) == (size_t)st.st_size); - - cl_git_pass(p_unlink("copy_me_two")); - cl_git_pass(p_unlink("copy_me")); -} - -void test_core_copy__file_in_dir(void) -{ - struct stat st; - const char *content = "This is some other stuff to copy\n"; - - cl_git_pass(git_futils_mkdir("an_dir/in_a_dir", 0775, GIT_MKDIR_PATH)); - cl_git_mkfile("an_dir/in_a_dir/copy_me", content); - cl_assert(git_fs_path_isdir("an_dir")); - - cl_git_pass(git_futils_mkpath2file - ("an_dir/second_dir/and_more/copy_me_two", 0775)); - - cl_git_pass(git_futils_cp - ("an_dir/in_a_dir/copy_me", - "an_dir/second_dir/and_more/copy_me_two", - 0664)); - - cl_git_pass(git_fs_path_lstat("an_dir/second_dir/and_more/copy_me_two", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert(strlen(content) == (size_t)st.st_size); - - cl_git_pass(git_futils_rmdir_r("an_dir", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_assert(!git_fs_path_isdir("an_dir")); -} - -#ifndef GIT_WIN32 -static void assert_hard_link(const char *path) -{ - /* we assert this by checking that there's more than one link to the file */ - struct stat st; - - cl_assert(git_fs_path_isfile(path)); - cl_git_pass(p_stat(path, &st)); - cl_assert(st.st_nlink > 1); -} -#endif - -void test_core_copy__tree(void) -{ - struct stat st; - const char *content = "File content\n"; - - cl_git_pass(git_futils_mkdir("src/b", 0775, GIT_MKDIR_PATH)); - cl_git_pass(git_futils_mkdir("src/c/d", 0775, GIT_MKDIR_PATH)); - cl_git_pass(git_futils_mkdir("src/c/e", 0775, GIT_MKDIR_PATH)); - - cl_git_mkfile("src/f1", content); - cl_git_mkfile("src/b/f2", content); - cl_git_mkfile("src/c/f3", content); - cl_git_mkfile("src/c/d/f4", content); - cl_git_mkfile("src/c/d/.f5", content); - -#ifndef GIT_WIN32 - cl_assert(p_symlink("../../b/f2", "src/c/d/l1") == 0); -#endif - - cl_assert(git_fs_path_isdir("src")); - cl_assert(git_fs_path_isdir("src/b")); - cl_assert(git_fs_path_isdir("src/c/d")); - cl_assert(git_fs_path_isfile("src/c/d/f4")); - - /* copy with no empty dirs, yes links, no dotfiles, no overwrite */ - - cl_git_pass( - git_futils_cp_r("src", "t1", GIT_CPDIR_COPY_SYMLINKS, 0) ); - - cl_assert(git_fs_path_isdir("t1")); - cl_assert(git_fs_path_isdir("t1/b")); - cl_assert(git_fs_path_isdir("t1/c")); - cl_assert(git_fs_path_isdir("t1/c/d")); - cl_assert(!git_fs_path_isdir("t1/c/e")); - - cl_assert(git_fs_path_isfile("t1/f1")); - cl_assert(git_fs_path_isfile("t1/b/f2")); - cl_assert(git_fs_path_isfile("t1/c/f3")); - cl_assert(git_fs_path_isfile("t1/c/d/f4")); - cl_assert(!git_fs_path_isfile("t1/c/d/.f5")); - - cl_git_pass(git_fs_path_lstat("t1/c/f3", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert(strlen(content) == (size_t)st.st_size); - -#ifndef GIT_WIN32 - cl_git_pass(git_fs_path_lstat("t1/c/d/l1", &st)); - cl_assert(S_ISLNK(st.st_mode)); -#endif - - cl_git_pass(git_futils_rmdir_r("t1", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_assert(!git_fs_path_isdir("t1")); - - /* copy with empty dirs, no links, yes dotfiles, no overwrite */ - - cl_git_pass( - git_futils_cp_r("src", "t2", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_COPY_DOTFILES, 0) ); - - cl_assert(git_fs_path_isdir("t2")); - cl_assert(git_fs_path_isdir("t2/b")); - cl_assert(git_fs_path_isdir("t2/c")); - cl_assert(git_fs_path_isdir("t2/c/d")); - cl_assert(git_fs_path_isdir("t2/c/e")); - - cl_assert(git_fs_path_isfile("t2/f1")); - cl_assert(git_fs_path_isfile("t2/b/f2")); - cl_assert(git_fs_path_isfile("t2/c/f3")); - cl_assert(git_fs_path_isfile("t2/c/d/f4")); - cl_assert(git_fs_path_isfile("t2/c/d/.f5")); - -#ifndef GIT_WIN32 - cl_git_fail(git_fs_path_lstat("t2/c/d/l1", &st)); -#endif - - cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_assert(!git_fs_path_isdir("t2")); - -#ifndef GIT_WIN32 - cl_git_pass(git_futils_cp_r("src", "t3", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_LINK_FILES, 0)); - cl_assert(git_fs_path_isdir("t3")); - - cl_assert(git_fs_path_isdir("t3")); - cl_assert(git_fs_path_isdir("t3/b")); - cl_assert(git_fs_path_isdir("t3/c")); - cl_assert(git_fs_path_isdir("t3/c/d")); - cl_assert(git_fs_path_isdir("t3/c/e")); - - assert_hard_link("t3/f1"); - assert_hard_link("t3/b/f2"); - assert_hard_link("t3/c/f3"); - assert_hard_link("t3/c/d/f4"); -#endif - - cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_RMDIR_REMOVE_FILES)); -} diff --git a/tests/core/dirent.c b/tests/core/dirent.c deleted file mode 100644 index 2419ec7ab..000000000 --- a/tests/core/dirent.c +++ /dev/null @@ -1,306 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" - -typedef struct name_data { - int count; /* return count */ - char *name; /* filename */ -} name_data; - -typedef struct walk_data { - char *sub; /* sub-directory name */ - name_data *names; /* name state data */ - git_str path; -} walk_data; - - -static char *top_dir = "dir-walk"; -static walk_data *state_loc; - -static void setup(walk_data *d) -{ - name_data *n; - - cl_must_pass(p_mkdir(top_dir, 0777)); - - cl_must_pass(p_chdir(top_dir)); - - if (strcmp(d->sub, ".") != 0) - cl_must_pass(p_mkdir(d->sub, 0777)); - - cl_git_pass(git_str_sets(&d->path, d->sub)); - - state_loc = d; - - for (n = d->names; n->name; n++) { - git_file fd = p_creat(n->name, 0666); - cl_assert(fd >= 0); - p_close(fd); - n->count = 0; - } -} - -static void dirent_cleanup__cb(void *_d) -{ - walk_data *d = _d; - name_data *n; - - for (n = d->names; n->name; n++) { - cl_must_pass(p_unlink(n->name)); - } - - if (strcmp(d->sub, ".") != 0) - cl_must_pass(p_rmdir(d->sub)); - - cl_must_pass(p_chdir("..")); - - cl_must_pass(p_rmdir(top_dir)); - - git_str_dispose(&d->path); -} - -static void check_counts(walk_data *d) -{ - name_data *n; - - for (n = d->names; n->name; n++) { - cl_assert(n->count == 1); - } -} - -static int update_count(name_data *data, const char *name) -{ - name_data *n; - - for (n = data; n->name; n++) { - if (!strcmp(n->name, name)) { - n->count++; - return 0; - } - } - - return GIT_ERROR; -} - -static int one_entry(void *state, git_str *path) -{ - walk_data *d = (walk_data *) state; - - if (state != state_loc) - return GIT_ERROR; - - if (path != &d->path) - return GIT_ERROR; - - return update_count(d->names, path->ptr); -} - - -static name_data dot_names[] = { - { 0, "./a" }, - { 0, "./asdf" }, - { 0, "./pack-foo.pack" }, - { 0, NULL } -}; -static walk_data dot = { - ".", - dot_names, - GIT_STR_INIT -}; - -/* make sure that the '.' folder is not traversed */ -void test_core_dirent__dont_traverse_dot(void) -{ - cl_set_cleanup(&dirent_cleanup__cb, &dot); - setup(&dot); - - cl_git_pass(git_fs_path_direach(&dot.path, 0, one_entry, &dot)); - - check_counts(&dot); -} - - -static name_data sub_names[] = { - { 0, "sub/a" }, - { 0, "sub/asdf" }, - { 0, "sub/pack-foo.pack" }, - { 0, NULL } -}; -static walk_data sub = { - "sub", - sub_names, - GIT_STR_INIT -}; - -/* traverse a subfolder */ -void test_core_dirent__traverse_subfolder(void) -{ - cl_set_cleanup(&dirent_cleanup__cb, &sub); - setup(&sub); - - cl_git_pass(git_fs_path_direach(&sub.path, 0, one_entry, &sub)); - - check_counts(&sub); -} - - -static walk_data sub_slash = { - "sub/", - sub_names, - GIT_STR_INIT -}; - -/* traverse a slash-terminated subfolder */ -void test_core_dirent__traverse_slash_terminated_folder(void) -{ - cl_set_cleanup(&dirent_cleanup__cb, &sub_slash); - setup(&sub_slash); - - cl_git_pass(git_fs_path_direach(&sub_slash.path, 0, one_entry, &sub_slash)); - - check_counts(&sub_slash); -} - - -static name_data empty_names[] = { - { 0, NULL } -}; -static walk_data empty = { - "empty", - empty_names, - GIT_STR_INIT -}; - -/* make sure that empty folders are not traversed */ -void test_core_dirent__dont_traverse_empty_folders(void) -{ - cl_set_cleanup(&dirent_cleanup__cb, &empty); - setup(&empty); - - cl_git_pass(git_fs_path_direach(&empty.path, 0, one_entry, &empty)); - - check_counts(&empty); - - /* make sure callback not called */ - cl_assert(git_fs_path_is_empty_dir(empty.path.ptr)); -} - -static name_data odd_names[] = { - { 0, "odd/.a" }, - { 0, "odd/..c" }, - /* the following don't work on cygwin/win32 */ - /* { 0, "odd/.b." }, */ - /* { 0, "odd/..d.." }, */ - { 0, NULL } -}; -static walk_data odd = { - "odd", - odd_names, - GIT_STR_INIT -}; - -/* make sure that strange looking filenames ('..c') are traversed */ -void test_core_dirent__traverse_weird_filenames(void) -{ - cl_set_cleanup(&dirent_cleanup__cb, &odd); - setup(&odd); - - cl_git_pass(git_fs_path_direach(&odd.path, 0, one_entry, &odd)); - - check_counts(&odd); -} - -/* test filename length limits */ -void test_core_dirent__length_limits(void) -{ - char *big_filename = (char *)git__malloc(FILENAME_MAX + 1); - memset(big_filename, 'a', FILENAME_MAX + 1); - big_filename[FILENAME_MAX] = 0; - - cl_must_fail(p_creat(big_filename, 0666)); - - git__free(big_filename); -} - -void test_core_dirent__empty_dir(void) -{ - cl_must_pass(p_mkdir("empty_dir", 0777)); - cl_assert(git_fs_path_is_empty_dir("empty_dir")); - - cl_git_mkfile("empty_dir/content", "whatever\n"); - cl_assert(!git_fs_path_is_empty_dir("empty_dir")); - cl_assert(!git_fs_path_is_empty_dir("empty_dir/content")); - - cl_must_pass(p_unlink("empty_dir/content")); - - cl_must_pass(p_mkdir("empty_dir/content", 0777)); - cl_assert(!git_fs_path_is_empty_dir("empty_dir")); - cl_assert(git_fs_path_is_empty_dir("empty_dir/content")); - - cl_must_pass(p_rmdir("empty_dir/content")); - - cl_must_pass(p_rmdir("empty_dir")); -} - -static void handle_next(git_fs_path_diriter *diriter, walk_data *walk) -{ - const char *fullpath, *filename; - size_t fullpath_len, filename_len; - - cl_git_pass(git_fs_path_diriter_fullpath(&fullpath, &fullpath_len, diriter)); - cl_git_pass(git_fs_path_diriter_filename(&filename, &filename_len, diriter)); - - cl_assert_equal_strn(fullpath, "sub/", 4); - cl_assert_equal_s(fullpath+4, filename); - - update_count(walk->names, fullpath); -} - -/* test directory iterator */ -void test_core_dirent__diriter_with_fullname(void) -{ - git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT; - int error; - - cl_set_cleanup(&dirent_cleanup__cb, &sub); - setup(&sub); - - cl_git_pass(git_fs_path_diriter_init(&diriter, sub.path.ptr, 0)); - - while ((error = git_fs_path_diriter_next(&diriter)) == 0) - handle_next(&diriter, &sub); - - cl_assert_equal_i(error, GIT_ITEROVER); - - git_fs_path_diriter_free(&diriter); - - check_counts(&sub); -} - -void test_core_dirent__diriter_at_directory_root(void) -{ - git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT; - const char *sandbox_path, *path; - char *root_path; - size_t path_len; - int root_offset, error; - - sandbox_path = clar_sandbox_path(); - cl_assert((root_offset = git_fs_path_root(sandbox_path)) >= 0); - - cl_assert(root_path = git__calloc(1, root_offset + 2)); - strncpy(root_path, sandbox_path, root_offset + 1); - - cl_git_pass(git_fs_path_diriter_init(&diriter, root_path, 0)); - - while ((error = git_fs_path_diriter_next(&diriter)) == 0) { - cl_git_pass(git_fs_path_diriter_fullpath(&path, &path_len, &diriter)); - - cl_assert(path_len > (size_t)(root_offset + 1)); - cl_assert(path[root_offset+1] != '/'); - } - - cl_assert_equal_i(error, GIT_ITEROVER); - - git_fs_path_diriter_free(&diriter); - git__free(root_path); -} diff --git a/tests/core/encoding.c b/tests/core/encoding.c deleted file mode 100644 index 6cec24679..000000000 --- a/tests/core/encoding.c +++ /dev/null @@ -1,42 +0,0 @@ -#include "clar_libgit2.h" -#include "varint.h" - -void test_core_encoding__decode(void) -{ - const unsigned char *buf = (unsigned char *)"AB"; - size_t size; - - cl_assert(git_decode_varint(buf, &size) == 65); - cl_assert(size == 1); - - buf = (unsigned char *)"\xfe\xdc\xbaXY"; - cl_assert(git_decode_varint(buf, &size) == 267869656); - cl_assert(size == 4); - - buf = (unsigned char *)"\xaa\xaa\xfe\xdc\xbaXY"; - cl_assert(git_decode_varint(buf, &size) == UINT64_C(1489279344088)); - cl_assert(size == 6); - - buf = (unsigned char *)"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xfe\xdc\xbaXY"; - cl_assert(git_decode_varint(buf, &size) == 0); - cl_assert(size == 0); - -} - -void test_core_encoding__encode(void) -{ - unsigned char buf[100]; - cl_assert(git_encode_varint(buf, 100, 65) == 1); - cl_assert(buf[0] == 'A'); - - cl_assert(git_encode_varint(buf, 1, 1) == 1); - cl_assert(!memcmp(buf, "\x01", 1)); - - cl_assert(git_encode_varint(buf, 100, 267869656) == 4); - cl_assert(!memcmp(buf, "\xfe\xdc\xbaX", 4)); - - cl_assert(git_encode_varint(buf, 100, UINT64_C(1489279344088)) == 6); - cl_assert(!memcmp(buf, "\xaa\xaa\xfe\xdc\xbaX", 6)); - - cl_assert(git_encode_varint(buf, 1, UINT64_C(1489279344088)) == -1); -} diff --git a/tests/core/env.c b/tests/core/env.c deleted file mode 100644 index 8ba9b9124..000000000 --- a/tests/core/env.c +++ /dev/null @@ -1,320 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "sysdir.h" - -#ifdef GIT_WIN32 -#define NUM_VARS 5 -static const char *env_vars[NUM_VARS] = { - "HOME", "HOMEDRIVE", "HOMEPATH", "USERPROFILE", "PROGRAMFILES" -}; -#else -#define NUM_VARS 1 -static const char *env_vars[NUM_VARS] = { "HOME" }; -#endif - -static char *env_save[NUM_VARS]; - -static char *home_values[] = { - "fake_home", - "f\xc3\xa1ke_h\xc3\xb5me", /* all in latin-1 supplement */ - "f\xc4\x80ke_\xc4\xa4ome", /* latin extended */ - "f\xce\xb1\xce\xba\xce\xb5_h\xce\xbfm\xce\xad", /* having fun with greek */ - "fa\xe0" "\xb8" "\x87" "e_\xe0" "\xb8" "\x99" "ome", /* thai characters */ - "f\xe1\x9c\x80ke_\xe1\x9c\x91ome", /* tagalog characters */ - "\xe1\xb8\x9f\xe1\xba\xa2" "ke_ho" "\xe1" "\xb9" "\x81" "e", /* latin extended additional */ - "\xf0\x9f\x98\x98\xf0\x9f\x98\x82", /* emoticons */ - NULL -}; - -void test_core_env__initialize(void) -{ - int i; - for (i = 0; i < NUM_VARS; ++i) - env_save[i] = cl_getenv(env_vars[i]); -} - -static void set_global_search_path_from_env(void) -{ - cl_git_pass(git_sysdir_set(GIT_SYSDIR_GLOBAL, NULL)); -} - -static void set_system_search_path_from_env(void) -{ - cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, NULL)); -} - -void test_core_env__cleanup(void) -{ - int i; - char **val; - - for (i = 0; i < NUM_VARS; ++i) { - cl_setenv(env_vars[i], env_save[i]); - git__free(env_save[i]); - env_save[i] = NULL; - } - - /* these will probably have already been cleaned up, but if a test - * fails, then it's probably good to try and clear out these dirs - */ - for (val = home_values; *val != NULL; val++) { - if (**val != '\0') - (void)p_rmdir(*val); - } - - cl_sandbox_set_search_path_defaults(); -} - -static void setenv_and_check(const char *name, const char *value) -{ - char *check; - - cl_git_pass(cl_setenv(name, value)); - check = cl_getenv(name); - - if (value) - cl_assert_equal_s(value, check); - else - cl_assert(check == NULL); - - git__free(check); -} - -void test_core_env__0(void) -{ - git_str path = GIT_STR_INIT, found = GIT_STR_INIT; - char testfile[16], tidx = '0'; - char **val; - const char *testname = "testfile"; - size_t testlen = strlen(testname); - - strncpy(testfile, testname, sizeof(testfile)); - cl_assert_equal_s(testname, testfile); - - for (val = home_values; *val != NULL; val++) { - - /* if we can't make the directory, let's just assume - * we are on a filesystem that doesn't support the - * characters in question and skip this test... - */ - if (p_mkdir(*val, 0777) != 0) { - *val = ""; /* mark as not created */ - continue; - } - - cl_git_pass(git_fs_path_prettify(&path, *val, NULL)); - - /* vary testfile name in each directory so accidentally leaving - * an environment variable set from a previous iteration won't - * accidentally make this test pass... - */ - testfile[testlen] = tidx++; - cl_git_pass(git_str_joinpath(&path, path.ptr, testfile)); - cl_git_mkfile(path.ptr, "find me"); - git_str_rtruncate_at_char(&path, '/'); - - cl_assert_equal_i( - GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); - - setenv_and_check("HOME", path.ptr); - set_global_search_path_from_env(); - - cl_git_pass(git_sysdir_find_global_file(&found, testfile)); - - cl_setenv("HOME", env_save[0]); - set_global_search_path_from_env(); - - cl_assert_equal_i( - GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); - -#ifdef GIT_WIN32 - setenv_and_check("HOMEDRIVE", NULL); - setenv_and_check("HOMEPATH", NULL); - setenv_and_check("USERPROFILE", path.ptr); - set_global_search_path_from_env(); - - cl_git_pass(git_sysdir_find_global_file(&found, testfile)); - - { - int root = git_fs_path_root(path.ptr); - char old; - - if (root >= 0) { - setenv_and_check("USERPROFILE", NULL); - set_global_search_path_from_env(); - - cl_assert_equal_i( - GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); - - old = path.ptr[root]; - path.ptr[root] = '\0'; - setenv_and_check("HOMEDRIVE", path.ptr); - path.ptr[root] = old; - setenv_and_check("HOMEPATH", &path.ptr[root]); - set_global_search_path_from_env(); - - cl_git_pass(git_sysdir_find_global_file(&found, testfile)); - } - } -#endif - - (void)p_rmdir(*val); - } - - git_str_dispose(&path); - git_str_dispose(&found); -} - - -void test_core_env__1(void) -{ - git_str path = GIT_STR_INIT; - - cl_assert_equal_i( - GIT_ENOTFOUND, git_sysdir_find_global_file(&path, "nonexistentfile")); - - cl_git_pass(cl_setenv("HOME", "doesnotexist")); -#ifdef GIT_WIN32 - cl_git_pass(cl_setenv("HOMEPATH", "doesnotexist")); - cl_git_pass(cl_setenv("USERPROFILE", "doesnotexist")); -#endif - set_global_search_path_from_env(); - - cl_assert_equal_i( - GIT_ENOTFOUND, git_sysdir_find_global_file(&path, "nonexistentfile")); - - cl_git_pass(cl_setenv("HOME", NULL)); -#ifdef GIT_WIN32 - cl_git_pass(cl_setenv("HOMEPATH", NULL)); - cl_git_pass(cl_setenv("USERPROFILE", NULL)); -#endif - set_global_search_path_from_env(); - set_system_search_path_from_env(); - - cl_assert_equal_i( - GIT_ENOTFOUND, git_sysdir_find_global_file(&path, "nonexistentfile")); - - cl_assert_equal_i( - GIT_ENOTFOUND, git_sysdir_find_system_file(&path, "nonexistentfile")); - -#ifdef GIT_WIN32 - cl_git_pass(cl_setenv("PROGRAMFILES", NULL)); - set_system_search_path_from_env(); - - cl_assert_equal_i( - GIT_ENOTFOUND, git_sysdir_find_system_file(&path, "nonexistentfile")); -#endif - - git_str_dispose(&path); -} - -static void check_global_searchpath( - const char *path, int position, const char *file, git_str *temp) -{ - git_str out = GIT_STR_INIT; - - /* build and set new path */ - if (position < 0) - cl_git_pass(git_str_join(temp, GIT_PATH_LIST_SEPARATOR, path, "$PATH")); - else if (position > 0) - cl_git_pass(git_str_join(temp, GIT_PATH_LIST_SEPARATOR, "$PATH", path)); - else - cl_git_pass(git_str_sets(temp, path)); - - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, temp->ptr)); - - /* get path and make sure $PATH expansion worked */ - cl_git_pass(git_libgit2_opts( - GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &out)); - - if (position < 0) - cl_assert(git__prefixcmp(out.ptr, path) == 0); - else if (position > 0) - cl_assert(git__suffixcmp(out.ptr, path) == 0); - else - cl_assert_equal_s(out.ptr, path); - - /* find file using new path */ - cl_git_pass(git_sysdir_find_global_file(temp, file)); - - /* reset path and confirm file not found */ - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, NULL)); - cl_assert_equal_i( - GIT_ENOTFOUND, git_sysdir_find_global_file(temp, file)); - - git_str_dispose(&out); -} - -void test_core_env__2(void) -{ - git_str path = GIT_STR_INIT, found = GIT_STR_INIT; - char testfile[16], tidx = '0'; - char **val; - const char *testname = "alternate"; - size_t testlen = strlen(testname); - - strncpy(testfile, testname, sizeof(testfile)); - cl_assert_equal_s(testname, testfile); - - for (val = home_values; *val != NULL; val++) { - - /* if we can't make the directory, let's just assume - * we are on a filesystem that doesn't support the - * characters in question and skip this test... - */ - if (p_mkdir(*val, 0777) != 0 && errno != EEXIST) { - *val = ""; /* mark as not created */ - continue; - } - - cl_git_pass(git_fs_path_prettify(&path, *val, NULL)); - - /* vary testfile name so any sloppiness is resetting variables or - * deleting files won't accidentally make a test pass. - */ - testfile[testlen] = tidx++; - cl_git_pass(git_str_joinpath(&path, path.ptr, testfile)); - cl_git_mkfile(path.ptr, "find me"); - git_str_rtruncate_at_char(&path, '/'); - - /* default should be NOTFOUND */ - cl_assert_equal_i( - GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); - - /* try plain, append $PATH, and prepend $PATH */ - check_global_searchpath(path.ptr, 0, testfile, &found); - check_global_searchpath(path.ptr, -1, testfile, &found); - check_global_searchpath(path.ptr, 1, testfile, &found); - - /* cleanup */ - cl_git_pass(git_str_joinpath(&path, path.ptr, testfile)); - (void)p_unlink(path.ptr); - (void)p_rmdir(*val); - } - - git_str_dispose(&path); - git_str_dispose(&found); -} - -void test_core_env__substitution(void) -{ - git_str buf = GIT_STR_INIT, expected = GIT_STR_INIT; - - /* Set it to something non-default so we have controllable values */ - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, "/tmp/a")); - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &buf)); - cl_assert_equal_s("/tmp/a", buf.ptr); - - git_str_clear(&buf); - cl_git_pass(git_str_join(&buf, GIT_PATH_LIST_SEPARATOR, "$PATH", "/tmp/b")); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, buf.ptr)); - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &buf)); - - cl_git_pass(git_str_join(&expected, GIT_PATH_LIST_SEPARATOR, "/tmp/a", "/tmp/b")); - cl_assert_equal_s(expected.ptr, buf.ptr); - - git_str_dispose(&expected); - git_str_dispose(&buf); -} diff --git a/tests/core/errors.c b/tests/core/errors.c deleted file mode 100644 index 386ecdc5f..000000000 --- a/tests/core/errors.c +++ /dev/null @@ -1,222 +0,0 @@ -#include "clar_libgit2.h" - -void test_core_errors__public_api(void) -{ - char *str_in_error; - - git_error_clear(); - cl_assert(git_error_last() == NULL); - - git_error_set_oom(); - - cl_assert(git_error_last() != NULL); - cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); - str_in_error = strstr(git_error_last()->message, "memory"); - cl_assert(str_in_error != NULL); - - git_error_clear(); - - git_error_set_str(GIT_ERROR_REPOSITORY, "This is a test"); - - cl_assert(git_error_last() != NULL); - str_in_error = strstr(git_error_last()->message, "This is a test"); - cl_assert(str_in_error != NULL); - - git_error_clear(); - cl_assert(git_error_last() == NULL); -} - -#include "common.h" -#include "util.h" -#include "posix.h" - -void test_core_errors__new_school(void) -{ - char *str_in_error; - - git_error_clear(); - cl_assert(git_error_last() == NULL); - - git_error_set_oom(); /* internal fn */ - - cl_assert(git_error_last() != NULL); - cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); - str_in_error = strstr(git_error_last()->message, "memory"); - cl_assert(str_in_error != NULL); - - git_error_clear(); - - git_error_set(GIT_ERROR_REPOSITORY, "This is a test"); /* internal fn */ - - cl_assert(git_error_last() != NULL); - str_in_error = strstr(git_error_last()->message, "This is a test"); - cl_assert(str_in_error != NULL); - - git_error_clear(); - cl_assert(git_error_last() == NULL); - - do { - struct stat st; - memset(&st, 0, sizeof(st)); - cl_assert(p_lstat("this_file_does_not_exist", &st) < 0); - GIT_UNUSED(st); - } while (false); - git_error_set(GIT_ERROR_OS, "stat failed"); /* internal fn */ - - cl_assert(git_error_last() != NULL); - str_in_error = strstr(git_error_last()->message, "stat failed"); - cl_assert(str_in_error != NULL); - cl_assert(git__prefixcmp(str_in_error, "stat failed: ") == 0); - cl_assert(strlen(str_in_error) > strlen("stat failed: ")); - -#ifdef GIT_WIN32 - git_error_clear(); - - /* The MSDN docs use this to generate a sample error */ - cl_assert(GetProcessId(NULL) == 0); - git_error_set(GIT_ERROR_OS, "GetProcessId failed"); /* internal fn */ - - cl_assert(git_error_last() != NULL); - str_in_error = strstr(git_error_last()->message, "GetProcessId failed"); - cl_assert(str_in_error != NULL); - cl_assert(git__prefixcmp(str_in_error, "GetProcessId failed: ") == 0); - cl_assert(strlen(str_in_error) > strlen("GetProcessId failed: ")); -#endif - - git_error_clear(); -} - -void test_core_errors__restore(void) -{ - git_error_state err_state = {0}; - - git_error_clear(); - cl_assert(git_error_last() == NULL); - - cl_assert_equal_i(0, git_error_state_capture(&err_state, 0)); - - memset(&err_state, 0x0, sizeof(git_error_state)); - - git_error_set(42, "Foo: %s", "bar"); - cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); - - cl_assert(git_error_last() == NULL); - - git_error_set(99, "Bar: %s", "foo"); - - git_error_state_restore(&err_state); - - cl_assert_equal_i(42, git_error_last()->klass); - cl_assert_equal_s("Foo: bar", git_error_last()->message); -} - -void test_core_errors__free_state(void) -{ - git_error_state err_state = {0}; - - git_error_clear(); - - git_error_set(42, "Foo: %s", "bar"); - cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); - - git_error_set(99, "Bar: %s", "foo"); - - git_error_state_free(&err_state); - - cl_assert_equal_i(99, git_error_last()->klass); - cl_assert_equal_s("Bar: foo", git_error_last()->message); - - git_error_state_restore(&err_state); - - cl_assert(git_error_last() == NULL); -} - -void test_core_errors__restore_oom(void) -{ - git_error_state err_state = {0}; - const git_error *oom_error = NULL; - - git_error_clear(); - - git_error_set_oom(); /* internal fn */ - oom_error = git_error_last(); - cl_assert(oom_error); - - cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); - - cl_assert(git_error_last() == NULL); - cl_assert_equal_i(GIT_ERROR_NOMEMORY, err_state.error_msg.klass); - cl_assert_equal_s("Out of memory", err_state.error_msg.message); - - git_error_state_restore(&err_state); - - cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); - cl_assert_(git_error_last() == oom_error, "static oom error not restored"); - - git_error_clear(); -} - -static int test_arraysize_multiply(size_t nelem, size_t size) -{ - size_t out; - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&out, nelem, size); - return 0; -} - -void test_core_errors__integer_overflow_alloc_multiply(void) -{ - cl_git_pass(test_arraysize_multiply(10, 10)); - cl_git_pass(test_arraysize_multiply(1000, 1000)); - cl_git_pass(test_arraysize_multiply(SIZE_MAX/sizeof(void *), sizeof(void *))); - cl_git_pass(test_arraysize_multiply(0, 10)); - cl_git_pass(test_arraysize_multiply(10, 0)); - - cl_git_fail(test_arraysize_multiply(SIZE_MAX-1, sizeof(void *))); - cl_git_fail(test_arraysize_multiply((SIZE_MAX/sizeof(void *))+1, sizeof(void *))); - - cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); - cl_assert_equal_s("Out of memory", git_error_last()->message); -} - -static int test_arraysize_add(size_t one, size_t two) -{ - size_t out; - GIT_ERROR_CHECK_ALLOC_ADD(&out, one, two); - return 0; -} - -void test_core_errors__integer_overflow_alloc_add(void) -{ - cl_git_pass(test_arraysize_add(10, 10)); - cl_git_pass(test_arraysize_add(1000, 1000)); - cl_git_pass(test_arraysize_add(SIZE_MAX-10, 10)); - - cl_git_fail(test_arraysize_multiply(SIZE_MAX-1, 2)); - cl_git_fail(test_arraysize_multiply(SIZE_MAX, SIZE_MAX)); - - cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); - cl_assert_equal_s("Out of memory", git_error_last()->message); -} - -void test_core_errors__integer_overflow_sets_oom(void) -{ - size_t out; - - git_error_clear(); - cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX-1, 1)); - cl_assert_equal_p(NULL, git_error_last()); - - git_error_clear(); - cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, 42, 69)); - cl_assert_equal_p(NULL, git_error_last()); - - git_error_clear(); - cl_assert(GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX, SIZE_MAX)); - cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); - cl_assert_equal_s("Out of memory", git_error_last()->message); - - git_error_clear(); - cl_assert(GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX, SIZE_MAX)); - cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); - cl_assert_equal_s("Out of memory", git_error_last()->message); -} diff --git a/tests/core/features.c b/tests/core/features.c deleted file mode 100644 index 7b28cc0cb..000000000 --- a/tests/core/features.c +++ /dev/null @@ -1,35 +0,0 @@ -#include "clar_libgit2.h" - -void test_core_features__0(void) -{ - int major, minor, rev, caps; - - git_libgit2_version(&major, &minor, &rev); - cl_assert_equal_i(LIBGIT2_VER_MAJOR, major); - cl_assert_equal_i(LIBGIT2_VER_MINOR, minor); - cl_assert_equal_i(LIBGIT2_VER_REVISION, rev); - - caps = git_libgit2_features(); - -#ifdef GIT_THREADS - cl_assert((caps & GIT_FEATURE_THREADS) != 0); -#else - cl_assert((caps & GIT_FEATURE_THREADS) == 0); -#endif - -#ifdef GIT_HTTPS - cl_assert((caps & GIT_FEATURE_HTTPS) != 0); -#endif - -#if defined(GIT_SSH) - cl_assert((caps & GIT_FEATURE_SSH) != 0); -#else - cl_assert((caps & GIT_FEATURE_SSH) == 0); -#endif - -#if defined(GIT_USE_NSEC) - cl_assert((caps & GIT_FEATURE_NSEC) != 0); -#else - cl_assert((caps & GIT_FEATURE_NSEC) == 0); -#endif -} diff --git a/tests/core/filebuf.c b/tests/core/filebuf.c deleted file mode 100644 index 6f40c2456..000000000 --- a/tests/core/filebuf.c +++ /dev/null @@ -1,267 +0,0 @@ -#include "clar_libgit2.h" -#include "filebuf.h" - -/* make sure git_filebuf_open doesn't delete an existing lock */ -void test_core_filebuf__0(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - int fd; - char test[] = "test", testlock[] = "test.lock"; - - fd = p_creat(testlock, 0744); /* -V536 */ - - cl_must_pass(fd); - cl_must_pass(p_close(fd)); - - cl_git_fail(git_filebuf_open(&file, test, 0, 0666)); - cl_assert(git_fs_path_exists(testlock)); - - cl_must_pass(p_unlink(testlock)); -} - - -/* make sure GIT_FILEBUF_APPEND works as expected */ -void test_core_filebuf__1(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - char test[] = "test"; - - cl_git_mkfile(test, "libgit2 rocks\n"); - - cl_git_pass(git_filebuf_open(&file, test, GIT_FILEBUF_APPEND, 0666)); - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - cl_git_pass(git_filebuf_commit(&file)); - - cl_assert_equal_file("libgit2 rocks\nlibgit2 rocks\n", 0, test); - - cl_must_pass(p_unlink(test)); -} - - -/* make sure git_filebuf_write writes large buffer correctly */ -void test_core_filebuf__2(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - char test[] = "test"; - unsigned char buf[4096 * 4]; /* 2 * WRITE_BUFFER_SIZE */ - - memset(buf, 0xfe, sizeof(buf)); - - cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); - cl_git_pass(git_filebuf_write(&file, buf, sizeof(buf))); - cl_git_pass(git_filebuf_commit(&file)); - - cl_assert_equal_file((char *)buf, sizeof(buf), test); - - cl_must_pass(p_unlink(test)); -} - -/* make sure git_filebuf_cleanup clears the buffer */ -void test_core_filebuf__4(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - char test[] = "test"; - - cl_assert(file.buffer == NULL); - - cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); - cl_assert(file.buffer != NULL); - - git_filebuf_cleanup(&file); - cl_assert(file.buffer == NULL); -} - - -/* make sure git_filebuf_commit clears the buffer */ -void test_core_filebuf__5(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - char test[] = "test"; - - cl_assert(file.buffer == NULL); - - cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); - cl_assert(file.buffer != NULL); - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - cl_assert(file.buffer != NULL); - - cl_git_pass(git_filebuf_commit(&file)); - cl_assert(file.buffer == NULL); - - cl_must_pass(p_unlink(test)); -} - - -/* make sure git_filebuf_commit takes umask into account */ -void test_core_filebuf__umask(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - char test[] = "test"; - struct stat statbuf; - mode_t mask, os_mask; - -#ifdef GIT_WIN32 - os_mask = 0600; -#else - os_mask = 0777; -#endif - - p_umask(mask = p_umask(0)); - - cl_assert(file.buffer == NULL); - - cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); - cl_assert(file.buffer != NULL); - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - cl_assert(file.buffer != NULL); - - cl_git_pass(git_filebuf_commit(&file)); - cl_assert(file.buffer == NULL); - - cl_must_pass(p_stat("test", &statbuf)); - cl_assert_equal_i(statbuf.st_mode & os_mask, (0666 & ~mask) & os_mask); - - cl_must_pass(p_unlink(test)); -} - -void test_core_filebuf__rename_error(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - char *dir = "subdir", *test = "subdir/test", *test_lock = "subdir/test.lock"; - int fd; - -#ifndef GIT_WIN32 - cl_skip(); -#endif - - cl_git_pass(p_mkdir(dir, 0666)); - cl_git_mkfile(test, "dummy content"); - fd = p_open(test, O_RDONLY); - cl_assert(fd > 0); - cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); - - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - - cl_assert_equal_i(true, git_fs_path_exists(test_lock)); - - cl_git_fail(git_filebuf_commit(&file)); - p_close(fd); - - git_filebuf_cleanup(&file); - - cl_assert_equal_i(false, git_fs_path_exists(test_lock)); -} - -void test_core_filebuf__symlink_follow(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - const char *dir = "linkdir", *source = "linkdir/link"; - - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - cl_skip(); - - cl_git_pass(p_mkdir(dir, 0777)); - cl_git_pass(p_symlink("target", source)); - - cl_git_pass(git_filebuf_open(&file, source, 0, 0666)); - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - - cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock")); - - cl_git_pass(git_filebuf_commit(&file)); - cl_assert_equal_i(true, git_fs_path_exists("linkdir/target")); - - git_filebuf_cleanup(&file); - - /* The second time around, the target file does exist */ - cl_git_pass(git_filebuf_open(&file, source, 0, 0666)); - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - - cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock")); - - cl_git_pass(git_filebuf_commit(&file)); - cl_assert_equal_i(true, git_fs_path_exists("linkdir/target")); - - git_filebuf_cleanup(&file); - cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_core_filebuf__symlink_follow_absolute_paths(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str source = GIT_STR_INIT, target = GIT_STR_INIT; - - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - cl_skip(); - - cl_git_pass(git_str_joinpath(&source, clar_sandbox_path(), "linkdir/link")); - cl_git_pass(git_str_joinpath(&target, clar_sandbox_path(), "linkdir/target")); - cl_git_pass(p_mkdir("linkdir", 0777)); - cl_git_pass(p_symlink(target.ptr, source.ptr)); - - cl_git_pass(git_filebuf_open(&file, source.ptr, 0, 0666)); - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - - cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock")); - - cl_git_pass(git_filebuf_commit(&file)); - cl_assert_equal_i(true, git_fs_path_exists("linkdir/target")); - - git_filebuf_cleanup(&file); - git_str_dispose(&source); - git_str_dispose(&target); - - cl_git_pass(git_futils_rmdir_r("linkdir", NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_core_filebuf__symlink_depth(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - const char *dir = "linkdir", *source = "linkdir/link"; - - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - cl_skip(); - - cl_git_pass(p_mkdir(dir, 0777)); - /* Endless loop */ - cl_git_pass(p_symlink("link", source)); - - cl_git_fail(git_filebuf_open(&file, source, 0, 0666)); - - cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_core_filebuf__hidden_file(void) -{ -#ifndef GIT_WIN32 - cl_skip(); -#else - git_filebuf file = GIT_FILEBUF_INIT; - char *dir = "hidden", *test = "hidden/test"; - bool hidden; - - cl_git_pass(p_mkdir(dir, 0666)); - cl_git_mkfile(test, "dummy content"); - - cl_git_pass(git_win32__set_hidden(test, true)); - cl_git_pass(git_win32__hidden(&hidden, test)); - cl_assert(hidden); - - cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); - - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - - cl_git_pass(git_filebuf_commit(&file)); - - git_filebuf_cleanup(&file); -#endif -} - -void test_core_filebuf__detects_directory(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - - cl_must_pass(p_mkdir("foo", 0777)); - cl_git_fail_with(GIT_EDIRECTORY, git_filebuf_open(&file, "foo", 0, 0666)); - cl_must_pass(p_rmdir("foo")); -} diff --git a/tests/core/ftruncate.c b/tests/core/ftruncate.c deleted file mode 100644 index 0c731cb1e..000000000 --- a/tests/core/ftruncate.c +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Some tests for p_ftruncate() to ensure that - * properly handles large (2Gb+) files. - */ - -#include "clar_libgit2.h" - -static const char *filename = "core_ftruncate.txt"; -static int fd = -1; - -void test_core_ftruncate__initialize(void) -{ - if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE")) - cl_skip(); - - cl_must_pass((fd = p_open(filename, O_CREAT | O_RDWR, 0644))); -} - -void test_core_ftruncate__cleanup(void) -{ - if (fd < 0) - return; - - p_close(fd); - fd = 0; - - p_unlink(filename); -} - -static void _extend(off64_t i64len) -{ - struct stat st; - int error; - - cl_assert((error = p_ftruncate(fd, i64len)) == 0); - cl_assert((error = p_fstat(fd, &st)) == 0); - cl_assert(st.st_size == i64len); -} - -void test_core_ftruncate__2gb(void) -{ - _extend(0x80000001); -} - -void test_core_ftruncate__4gb(void) -{ - _extend(0x100000001); -} diff --git a/tests/core/futils.c b/tests/core/futils.c deleted file mode 100644 index 3501765f6..000000000 --- a/tests/core/futils.c +++ /dev/null @@ -1,115 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" - -/* Fixture setup and teardown */ -void test_core_futils__initialize(void) -{ - cl_must_pass(p_mkdir("futils", 0777)); -} - -void test_core_futils__cleanup(void) -{ - cl_fixture_cleanup("futils"); -} - -void test_core_futils__writebuffer(void) -{ - git_str out = GIT_STR_INIT, - append = GIT_STR_INIT; - - /* create a new file */ - git_str_puts(&out, "hello!\n"); - git_str_printf(&out, "this is a %s\n", "test"); - - cl_git_pass(git_futils_writebuffer(&out, "futils/test-file", O_RDWR|O_CREAT, 0666)); - - cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); - - /* append some more data */ - git_str_puts(&append, "And some more!\n"); - git_str_put(&out, append.ptr, append.size); - - cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR|O_APPEND, 0666)); - - cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); - - git_str_dispose(&out); - git_str_dispose(&append); -} - -void test_core_futils__write_hidden_file(void) -{ -#ifndef GIT_WIN32 - cl_skip(); -#else - git_str out = GIT_STR_INIT, append = GIT_STR_INIT; - bool hidden; - - git_str_puts(&out, "hidden file.\n"); - git_futils_writebuffer(&out, "futils/test-file", O_RDWR | O_CREAT, 0666); - - cl_git_pass(git_win32__set_hidden("futils/test-file", true)); - - /* append some more data */ - git_str_puts(&append, "And some more!\n"); - git_str_put(&out, append.ptr, append.size); - - cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR | O_APPEND, 0666)); - - cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); - - cl_git_pass(git_win32__hidden(&hidden, "futils/test-file")); - cl_assert(hidden); - - git_str_dispose(&out); - git_str_dispose(&append); -#endif -} - -void test_core_futils__recursive_rmdir_keeps_symlink_targets(void) -{ - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - cl_skip(); - - cl_git_pass(git_futils_mkdir_r("a/b", 0777)); - cl_git_pass(git_futils_mkdir_r("dir-target", 0777)); - cl_git_mkfile("dir-target/file", "Contents"); - cl_git_mkfile("file-target", "Contents"); - cl_must_pass(p_symlink("dir-target", "a/symlink")); - cl_must_pass(p_symlink("file-target", "a/b/symlink")); - - cl_git_pass(git_futils_rmdir_r("a", NULL, GIT_RMDIR_REMOVE_FILES)); - - cl_assert(git_fs_path_exists("dir-target")); - cl_assert(git_fs_path_exists("file-target")); - - cl_must_pass(p_unlink("dir-target/file")); - cl_must_pass(p_rmdir("dir-target")); - cl_must_pass(p_unlink("file-target")); -} - -void test_core_futils__mktmp_umask(void) -{ -#ifdef GIT_WIN32 - cl_skip(); -#else - git_str path = GIT_STR_INIT; - struct stat st; - int fd; - - umask(0); - cl_assert((fd = git_futils_mktmp(&path, "foo", 0777)) >= 0); - cl_must_pass(p_fstat(fd, &st)); - cl_assert_equal_i(st.st_mode & 0777, 0777); - cl_must_pass(p_unlink(path.ptr)); - close(fd); - - umask(077); - cl_assert((fd = git_futils_mktmp(&path, "foo", 0777)) >= 0); - cl_must_pass(p_fstat(fd, &st)); - cl_assert_equal_i(st.st_mode & 0777, 0700); - cl_must_pass(p_unlink(path.ptr)); - close(fd); - git_str_dispose(&path); -#endif -} diff --git a/tests/core/gitstr.c b/tests/core/gitstr.c deleted file mode 100644 index 0a624e28c..000000000 --- a/tests/core/gitstr.c +++ /dev/null @@ -1,1225 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/hashsig.h" -#include "futils.h" - -#define TESTSTR "Have you seen that? Have you seeeen that??" -const char *test_string = TESTSTR; -const char *test_string_x2 = TESTSTR TESTSTR; - -#define TESTSTR_4096 REP1024("1234") -#define TESTSTR_8192 REP1024("12341234") -const char *test_4096 = TESTSTR_4096; -const char *test_8192 = TESTSTR_8192; - -/* test basic data concatenation */ -void test_core_gitstr__0(void) -{ - git_str buf = GIT_STR_INIT; - - cl_assert(buf.size == 0); - - git_str_puts(&buf, test_string); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_string, git_str_cstr(&buf)); - - git_str_puts(&buf, test_string); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_string_x2, git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -/* test git_str_printf */ -void test_core_gitstr__1(void) -{ - git_str buf = GIT_STR_INIT; - - git_str_printf(&buf, "%s %s %d ", "shoop", "da", 23); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s("shoop da 23 ", git_str_cstr(&buf)); - - git_str_printf(&buf, "%s %d", "woop", 42); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s("shoop da 23 woop 42", git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -/* more thorough test of concatenation options */ -void test_core_gitstr__2(void) -{ - git_str buf = GIT_STR_INIT; - int i; - char data[128]; - - cl_assert(buf.size == 0); - - /* this must be safe to do */ - git_str_dispose(&buf); - cl_assert(buf.size == 0); - cl_assert(buf.asize == 0); - - /* empty buffer should be empty string */ - cl_assert_equal_s("", git_str_cstr(&buf)); - cl_assert(buf.size == 0); - /* cl_assert(buf.asize == 0); -- should not assume what git_str does */ - - /* free should set us back to the beginning */ - git_str_dispose(&buf); - cl_assert(buf.size == 0); - cl_assert(buf.asize == 0); - - /* add letter */ - git_str_putc(&buf, '+'); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s("+", git_str_cstr(&buf)); - - /* add letter again */ - git_str_putc(&buf, '+'); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s("++", git_str_cstr(&buf)); - - /* let's try that a few times */ - for (i = 0; i < 16; ++i) { - git_str_putc(&buf, '+'); - cl_assert(git_str_oom(&buf) == 0); - } - cl_assert_equal_s("++++++++++++++++++", git_str_cstr(&buf)); - - git_str_dispose(&buf); - - /* add data */ - git_str_put(&buf, "xo", 2); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s("xo", git_str_cstr(&buf)); - - /* add letter again */ - git_str_put(&buf, "xo", 2); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s("xoxo", git_str_cstr(&buf)); - - /* let's try that a few times */ - for (i = 0; i < 16; ++i) { - git_str_put(&buf, "xo", 2); - cl_assert(git_str_oom(&buf) == 0); - } - cl_assert_equal_s("xoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxo", - git_str_cstr(&buf)); - - git_str_dispose(&buf); - - /* set to string */ - git_str_sets(&buf, test_string); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_string, git_str_cstr(&buf)); - - /* append string */ - git_str_puts(&buf, test_string); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_string_x2, git_str_cstr(&buf)); - - /* set to string again (should overwrite - not append) */ - git_str_sets(&buf, test_string); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_string, git_str_cstr(&buf)); - - /* test clear */ - git_str_clear(&buf); - cl_assert_equal_s("", git_str_cstr(&buf)); - - git_str_dispose(&buf); - - /* test extracting data into buffer */ - git_str_puts(&buf, REP4("0123456789")); - cl_assert(git_str_oom(&buf) == 0); - - git_str_copy_cstr(data, sizeof(data), &buf); - cl_assert_equal_s(REP4("0123456789"), data); - git_str_copy_cstr(data, 11, &buf); - cl_assert_equal_s("0123456789", data); - git_str_copy_cstr(data, 3, &buf); - cl_assert_equal_s("01", data); - git_str_copy_cstr(data, 1, &buf); - cl_assert_equal_s("", data); - - git_str_copy_cstr(data, sizeof(data), &buf); - cl_assert_equal_s(REP4("0123456789"), data); - - git_str_sets(&buf, REP256("x")); - git_str_copy_cstr(data, sizeof(data), &buf); - /* since sizeof(data) == 128, only 127 bytes should be copied */ - cl_assert_equal_s(REP4(REP16("x")) REP16("x") REP16("x") - REP16("x") "xxxxxxxxxxxxxxx", data); - - git_str_dispose(&buf); - - git_str_copy_cstr(data, sizeof(data), &buf); - cl_assert_equal_s("", data); -} - -/* let's do some tests with larger buffers to push our limits */ -void test_core_gitstr__3(void) -{ - git_str buf = GIT_STR_INIT; - - /* set to string */ - git_str_set(&buf, test_4096, 4096); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_4096, git_str_cstr(&buf)); - - /* append string */ - git_str_puts(&buf, test_4096); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_8192, git_str_cstr(&buf)); - - /* set to string again (should overwrite - not append) */ - git_str_set(&buf, test_4096, 4096); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_4096, git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -/* let's try some producer/consumer tests */ -void test_core_gitstr__4(void) -{ - git_str buf = GIT_STR_INIT; - int i; - - for (i = 0; i < 10; ++i) { - git_str_puts(&buf, "1234"); /* add 4 */ - cl_assert(git_str_oom(&buf) == 0); - git_str_consume(&buf, buf.ptr + 2); /* eat the first two */ - cl_assert(strlen(git_str_cstr(&buf)) == (size_t)((i + 1) * 2)); - } - /* we have appended 1234 10x and removed the first 20 letters */ - cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); - - git_str_consume(&buf, NULL); - cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); - - git_str_consume(&buf, "invalid pointer"); - cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); - - git_str_consume(&buf, buf.ptr); - cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); - - git_str_consume(&buf, buf.ptr + 1); - cl_assert_equal_s("2341234123412341234", git_str_cstr(&buf)); - - git_str_consume(&buf, buf.ptr + buf.size); - cl_assert_equal_s("", git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - - -static void -check_buf_append( - const char* data_a, - const char* data_b, - const char* expected_data, - size_t expected_size, - size_t expected_asize) -{ - git_str tgt = GIT_STR_INIT; - - git_str_sets(&tgt, data_a); - cl_assert(git_str_oom(&tgt) == 0); - git_str_puts(&tgt, data_b); - cl_assert(git_str_oom(&tgt) == 0); - cl_assert_equal_s(expected_data, git_str_cstr(&tgt)); - cl_assert_equal_i(tgt.size, expected_size); - if (expected_asize > 0) - cl_assert_equal_i(tgt.asize, expected_asize); - - git_str_dispose(&tgt); -} - -static void -check_buf_append_abc( - const char* buf_a, - const char* buf_b, - const char* buf_c, - const char* expected_ab, - const char* expected_abc, - const char* expected_abca, - const char* expected_abcab, - const char* expected_abcabc) -{ - git_str buf = GIT_STR_INIT; - - git_str_sets(&buf, buf_a); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(buf_a, git_str_cstr(&buf)); - - git_str_puts(&buf, buf_b); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected_ab, git_str_cstr(&buf)); - - git_str_puts(&buf, buf_c); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected_abc, git_str_cstr(&buf)); - - git_str_puts(&buf, buf_a); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected_abca, git_str_cstr(&buf)); - - git_str_puts(&buf, buf_b); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected_abcab, git_str_cstr(&buf)); - - git_str_puts(&buf, buf_c); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected_abcabc, git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -/* more variations on append tests */ -void test_core_gitstr__5(void) -{ - check_buf_append("", "", "", 0, 0); - check_buf_append("a", "", "a", 1, 0); - check_buf_append("", "a", "a", 1, 8); - check_buf_append("", "a", "a", 1, 8); - check_buf_append("a", "b", "ab", 2, 8); - check_buf_append("", "abcdefgh", "abcdefgh", 8, 16); - check_buf_append("abcdefgh", "", "abcdefgh", 8, 16); - - /* buffer with starting asize will grow to: - * 1 -> 2, 2 -> 3, 3 -> 5, 4 -> 6, 5 -> 8, 6 -> 9, - * 7 -> 11, 8 -> 12, 9 -> 14, 10 -> 15, 11 -> 17, 12 -> 18, - * 13 -> 20, 14 -> 21, 15 -> 23, 16 -> 24, 17 -> 26, 18 -> 27, - * 19 -> 29, 20 -> 30, 21 -> 32, 22 -> 33, 23 -> 35, 24 -> 36, - * ... - * follow sequence until value > target size, - * then round up to nearest multiple of 8. - */ - - check_buf_append("abcdefgh", "/", "abcdefgh/", 9, 16); - check_buf_append("abcdefgh", "ijklmno", "abcdefghijklmno", 15, 16); - check_buf_append("abcdefgh", "ijklmnop", "abcdefghijklmnop", 16, 24); - check_buf_append("0123456789", "0123456789", - "01234567890123456789", 20, 24); - check_buf_append(REP16("x"), REP16("o"), - REP16("x") REP16("o"), 32, 40); - - check_buf_append(test_4096, "", test_4096, 4096, 4104); - check_buf_append(test_4096, test_4096, test_8192, 8192, 8200); - - /* check sequences of appends */ - check_buf_append_abc("a", "b", "c", - "ab", "abc", "abca", "abcab", "abcabc"); - check_buf_append_abc("a1", "b2", "c3", - "a1b2", "a1b2c3", "a1b2c3a1", - "a1b2c3a1b2", "a1b2c3a1b2c3"); - check_buf_append_abc("a1/", "b2/", "c3/", - "a1/b2/", "a1/b2/c3/", "a1/b2/c3/a1/", - "a1/b2/c3/a1/b2/", "a1/b2/c3/a1/b2/c3/"); -} - -/* test swap */ -void test_core_gitstr__6(void) -{ - git_str a = GIT_STR_INIT; - git_str b = GIT_STR_INIT; - - git_str_sets(&a, "foo"); - cl_assert(git_str_oom(&a) == 0); - git_str_sets(&b, "bar"); - cl_assert(git_str_oom(&b) == 0); - - cl_assert_equal_s("foo", git_str_cstr(&a)); - cl_assert_equal_s("bar", git_str_cstr(&b)); - - git_str_swap(&a, &b); - - cl_assert_equal_s("bar", git_str_cstr(&a)); - cl_assert_equal_s("foo", git_str_cstr(&b)); - - git_str_dispose(&a); - git_str_dispose(&b); -} - - -/* test detach/attach data */ -void test_core_gitstr__7(void) -{ - const char *fun = "This is fun"; - git_str a = GIT_STR_INIT; - char *b = NULL; - - git_str_sets(&a, "foo"); - cl_assert(git_str_oom(&a) == 0); - cl_assert_equal_s("foo", git_str_cstr(&a)); - - b = git_str_detach(&a); - - cl_assert_equal_s("foo", b); - cl_assert_equal_s("", a.ptr); - git__free(b); - - b = git_str_detach(&a); - - cl_assert_equal_s(NULL, b); - cl_assert_equal_s("", a.ptr); - - git_str_dispose(&a); - - b = git__strdup(fun); - git_str_attach(&a, b, 0); - - cl_assert_equal_s(fun, a.ptr); - cl_assert(a.size == strlen(fun)); - cl_assert(a.asize == strlen(fun) + 1); - - git_str_dispose(&a); - - b = git__strdup(fun); - git_str_attach(&a, b, strlen(fun) + 1); - - cl_assert_equal_s(fun, a.ptr); - cl_assert(a.size == strlen(fun)); - cl_assert(a.asize == strlen(fun) + 1); - - git_str_dispose(&a); -} - - -static void -check_joinbuf_2( - const char *a, - const char *b, - const char *expected) -{ - char sep = '/'; - git_str buf = GIT_STR_INIT; - - git_str_join(&buf, sep, a, b); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected, git_str_cstr(&buf)); - git_str_dispose(&buf); -} - -static void -check_joinbuf_overlapped( - const char *oldval, - int ofs_a, - const char *b, - const char *expected) -{ - char sep = '/'; - git_str buf = GIT_STR_INIT; - - git_str_sets(&buf, oldval); - git_str_join(&buf, sep, buf.ptr + ofs_a, b); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected, git_str_cstr(&buf)); - git_str_dispose(&buf); -} - -static void -check_joinbuf_n_2( - const char *a, - const char *b, - const char *expected) -{ - char sep = '/'; - git_str buf = GIT_STR_INIT; - - git_str_sets(&buf, a); - cl_assert(git_str_oom(&buf) == 0); - - git_str_join_n(&buf, sep, 1, b); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected, git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -static void -check_joinbuf_n_4( - const char *a, - const char *b, - const char *c, - const char *d, - const char *expected) -{ - char sep = ';'; - git_str buf = GIT_STR_INIT; - git_str_join_n(&buf, sep, 4, a, b, c, d); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected, git_str_cstr(&buf)); - git_str_dispose(&buf); -} - -/* test join */ -void test_core_gitstr__8(void) -{ - git_str a = GIT_STR_INIT; - - git_str_join_n(&a, '/', 1, "foo"); - cl_assert(git_str_oom(&a) == 0); - cl_assert_equal_s("foo", git_str_cstr(&a)); - - git_str_join_n(&a, '/', 1, "bar"); - cl_assert(git_str_oom(&a) == 0); - cl_assert_equal_s("foo/bar", git_str_cstr(&a)); - - git_str_join_n(&a, '/', 1, "baz"); - cl_assert(git_str_oom(&a) == 0); - cl_assert_equal_s("foo/bar/baz", git_str_cstr(&a)); - - git_str_dispose(&a); - - check_joinbuf_2(NULL, "", ""); - check_joinbuf_2(NULL, "a", "a"); - check_joinbuf_2(NULL, "/a", "/a"); - check_joinbuf_2("", "", ""); - check_joinbuf_2("", "a", "a"); - check_joinbuf_2("", "/a", "/a"); - check_joinbuf_2("a", "", "a/"); - check_joinbuf_2("a", "/", "a/"); - check_joinbuf_2("a", "b", "a/b"); - check_joinbuf_2("/", "a", "/a"); - check_joinbuf_2("/", "", "/"); - check_joinbuf_2("/a", "/b", "/a/b"); - check_joinbuf_2("/a", "/b/", "/a/b/"); - check_joinbuf_2("/a/", "b/", "/a/b/"); - check_joinbuf_2("/a/", "/b/", "/a/b/"); - check_joinbuf_2("/a/", "//b/", "/a/b/"); - check_joinbuf_2("/abcd", "/defg", "/abcd/defg"); - check_joinbuf_2("/abcd", "/defg/", "/abcd/defg/"); - check_joinbuf_2("/abcd/", "defg/", "/abcd/defg/"); - check_joinbuf_2("/abcd/", "/defg/", "/abcd/defg/"); - - check_joinbuf_overlapped("abcd", 0, "efg", "abcd/efg"); - check_joinbuf_overlapped("abcd", 1, "efg", "bcd/efg"); - check_joinbuf_overlapped("abcd", 2, "efg", "cd/efg"); - check_joinbuf_overlapped("abcd", 3, "efg", "d/efg"); - check_joinbuf_overlapped("abcd", 4, "efg", "efg"); - check_joinbuf_overlapped("abc/", 2, "efg", "c/efg"); - check_joinbuf_overlapped("abc/", 3, "efg", "/efg"); - check_joinbuf_overlapped("abc/", 4, "efg", "efg"); - check_joinbuf_overlapped("abcd", 3, "", "d/"); - check_joinbuf_overlapped("abcd", 4, "", ""); - check_joinbuf_overlapped("abc/", 2, "", "c/"); - check_joinbuf_overlapped("abc/", 3, "", "/"); - check_joinbuf_overlapped("abc/", 4, "", ""); - - check_joinbuf_n_2("", "", ""); - check_joinbuf_n_2("", "a", "a"); - check_joinbuf_n_2("", "/a", "/a"); - check_joinbuf_n_2("a", "", "a/"); - check_joinbuf_n_2("a", "/", "a/"); - check_joinbuf_n_2("a", "b", "a/b"); - check_joinbuf_n_2("/", "a", "/a"); - check_joinbuf_n_2("/", "", "/"); - check_joinbuf_n_2("/a", "/b", "/a/b"); - check_joinbuf_n_2("/a", "/b/", "/a/b/"); - check_joinbuf_n_2("/a/", "b/", "/a/b/"); - check_joinbuf_n_2("/a/", "/b/", "/a/b/"); - check_joinbuf_n_2("/abcd", "/defg", "/abcd/defg"); - check_joinbuf_n_2("/abcd", "/defg/", "/abcd/defg/"); - check_joinbuf_n_2("/abcd/", "defg/", "/abcd/defg/"); - check_joinbuf_n_2("/abcd/", "/defg/", "/abcd/defg/"); - - check_joinbuf_n_4("", "", "", "", ""); - check_joinbuf_n_4("", "a", "", "", "a;"); - check_joinbuf_n_4("a", "", "", "", "a;"); - check_joinbuf_n_4("", "", "", "a", "a"); - check_joinbuf_n_4("a", "b", "", ";c;d;", "a;b;c;d;"); - check_joinbuf_n_4("a", "b", "", ";c;d", "a;b;c;d"); - check_joinbuf_n_4("abcd", "efgh", "ijkl", "mnop", "abcd;efgh;ijkl;mnop"); - check_joinbuf_n_4("abcd;", "efgh;", "ijkl;", "mnop;", "abcd;efgh;ijkl;mnop;"); - check_joinbuf_n_4(";abcd;", ";efgh;", ";ijkl;", ";mnop;", ";abcd;efgh;ijkl;mnop;"); -} - -void test_core_gitstr__9(void) -{ - git_str buf = GIT_STR_INIT; - - /* just some exhaustive tests of various separator placement */ - char *a[] = { "", "-", "a-", "-a", "-a-" }; - char *b[] = { "", "-", "b-", "-b", "-b-" }; - char sep[] = { 0, '-', '/' }; - char *expect_null[] = { "", "-", "a-", "-a", "-a-", - "-", "--", "a--", "-a-", "-a--", - "b-", "-b-", "a-b-", "-ab-", "-a-b-", - "-b", "--b", "a--b", "-a-b", "-a--b", - "-b-", "--b-", "a--b-", "-a-b-", "-a--b-" }; - char *expect_dash[] = { "", "-", "a-", "-a-", "-a-", - "-", "-", "a-", "-a-", "-a-", - "b-", "-b-", "a-b-", "-a-b-", "-a-b-", - "-b", "-b", "a-b", "-a-b", "-a-b", - "-b-", "-b-", "a-b-", "-a-b-", "-a-b-" }; - char *expect_slas[] = { "", "-/", "a-/", "-a/", "-a-/", - "-", "-/-", "a-/-", "-a/-", "-a-/-", - "b-", "-/b-", "a-/b-", "-a/b-", "-a-/b-", - "-b", "-/-b", "a-/-b", "-a/-b", "-a-/-b", - "-b-", "-/-b-", "a-/-b-", "-a/-b-", "-a-/-b-" }; - char **expect_values[] = { expect_null, expect_dash, expect_slas }; - char separator, **expect; - unsigned int s, i, j; - - for (s = 0; s < sizeof(sep) / sizeof(char); ++s) { - separator = sep[s]; - expect = expect_values[s]; - - for (j = 0; j < sizeof(b) / sizeof(char*); ++j) { - for (i = 0; i < sizeof(a) / sizeof(char*); ++i) { - git_str_join(&buf, separator, a[i], b[j]); - cl_assert_equal_s(*expect, buf.ptr); - expect++; - } - } - } - - git_str_dispose(&buf); -} - -void test_core_gitstr__10(void) -{ - git_str a = GIT_STR_INIT; - - cl_git_pass(git_str_join_n(&a, '/', 1, "test")); - cl_assert_equal_s(a.ptr, "test"); - cl_git_pass(git_str_join_n(&a, '/', 1, "string")); - cl_assert_equal_s(a.ptr, "test/string"); - git_str_clear(&a); - cl_git_pass(git_str_join_n(&a, '/', 3, "test", "string", "join")); - cl_assert_equal_s(a.ptr, "test/string/join"); - cl_git_pass(git_str_join_n(&a, '/', 2, a.ptr, "more")); - cl_assert_equal_s(a.ptr, "test/string/join/test/string/join/more"); - - git_str_dispose(&a); -} - -void test_core_gitstr__join3(void) -{ - git_str a = GIT_STR_INIT; - - cl_git_pass(git_str_join3(&a, '/', "test", "string", "join")); - cl_assert_equal_s("test/string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "test/", "string", "join")); - cl_assert_equal_s("test/string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "test/", "/string", "join")); - cl_assert_equal_s("test/string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "test/", "/string/", "join")); - cl_assert_equal_s("test/string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "test/", "/string/", "/join")); - cl_assert_equal_s("test/string/join", a.ptr); - - cl_git_pass(git_str_join3(&a, '/', "", "string", "join")); - cl_assert_equal_s("string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "", "string/", "join")); - cl_assert_equal_s("string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "", "string/", "/join")); - cl_assert_equal_s("string/join", a.ptr); - - cl_git_pass(git_str_join3(&a, '/', "string", "", "join")); - cl_assert_equal_s("string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "string/", "", "join")); - cl_assert_equal_s("string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "string/", "", "/join")); - cl_assert_equal_s("string/join", a.ptr); - - git_str_dispose(&a); -} - -void test_core_gitstr__11(void) -{ - git_str a = GIT_STR_INIT; - char *t1[] = { "nothing", "in", "common" }; - char *t2[] = { "something", "something else", "some other" }; - char *t3[] = { "something", "some fun", "no fun" }; - char *t4[] = { "happy", "happier", "happiest" }; - char *t5[] = { "happiest", "happier", "happy" }; - char *t6[] = { "no", "nope", "" }; - char *t7[] = { "", "doesn't matter" }; - - cl_git_pass(git_str_common_prefix(&a, t1, 3)); - cl_assert_equal_s(a.ptr, ""); - - cl_git_pass(git_str_common_prefix(&a, t2, 3)); - cl_assert_equal_s(a.ptr, "some"); - - cl_git_pass(git_str_common_prefix(&a, t3, 3)); - cl_assert_equal_s(a.ptr, ""); - - cl_git_pass(git_str_common_prefix(&a, t4, 3)); - cl_assert_equal_s(a.ptr, "happ"); - - cl_git_pass(git_str_common_prefix(&a, t5, 3)); - cl_assert_equal_s(a.ptr, "happ"); - - cl_git_pass(git_str_common_prefix(&a, t6, 3)); - cl_assert_equal_s(a.ptr, ""); - - cl_git_pass(git_str_common_prefix(&a, t7, 3)); - cl_assert_equal_s(a.ptr, ""); - - git_str_dispose(&a); -} - -void test_core_gitstr__rfind_variants(void) -{ - git_str a = GIT_STR_INIT; - ssize_t len; - - cl_git_pass(git_str_sets(&a, "/this/is/it/")); - - len = (ssize_t)git_str_len(&a); - - cl_assert(git_str_rfind(&a, '/') == len - 1); - cl_assert(git_str_rfind_next(&a, '/') == len - 4); - - cl_assert(git_str_rfind(&a, 'i') == len - 3); - cl_assert(git_str_rfind_next(&a, 'i') == len - 3); - - cl_assert(git_str_rfind(&a, 'h') == 2); - cl_assert(git_str_rfind_next(&a, 'h') == 2); - - cl_assert(git_str_rfind(&a, 'q') == -1); - cl_assert(git_str_rfind_next(&a, 'q') == -1); - - git_str_dispose(&a); -} - -void test_core_gitstr__puts_escaped(void) -{ - git_str a = GIT_STR_INIT; - - git_str_clear(&a); - cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", "")); - cl_assert_equal_s("this is a test", a.ptr); - - git_str_clear(&a); - cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\")); - cl_assert_equal_s("\\this is a \\tes\\t", a.ptr); - - git_str_clear(&a); - cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__")); - cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr); - - git_str_clear(&a); - cl_git_pass(git_str_puts_escape_regex(&a, "^match\\s*[A-Z]+.*")); - cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr); - - git_str_dispose(&a); -} - -static void assert_unescape(char *expected, char *to_unescape) { - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_sets(&buf, to_unescape)); - git_str_unescape(&buf); - cl_assert_equal_s(expected, buf.ptr); - cl_assert_equal_sz(strlen(expected), buf.size); - - git_str_dispose(&buf); -} - -void test_core_gitstr__unescape(void) -{ - assert_unescape("Escaped\\", "Es\\ca\\ped\\"); - assert_unescape("Es\\caped\\", "Es\\\\ca\\ped\\\\"); - assert_unescape("\\", "\\"); - assert_unescape("\\", "\\\\"); - assert_unescape("", ""); -} - -void test_core_gitstr__encode_base64(void) -{ - git_str buf = GIT_STR_INIT; - - /* t h i s - * 0x 74 68 69 73 - * 0b 01110100 01101000 01101001 01110011 - * 0b 011101 000110 100001 101001 011100 110000 - * 0x 1d 06 21 29 1c 30 - * d G h p c w - */ - cl_git_pass(git_str_encode_base64(&buf, "this", 4)); - cl_assert_equal_s("dGhpcw==", buf.ptr); - - git_str_clear(&buf); - cl_git_pass(git_str_encode_base64(&buf, "this!", 5)); - cl_assert_equal_s("dGhpcyE=", buf.ptr); - - git_str_clear(&buf); - cl_git_pass(git_str_encode_base64(&buf, "this!\n", 6)); - cl_assert_equal_s("dGhpcyEK", buf.ptr); - - git_str_dispose(&buf); -} - -void test_core_gitstr__decode_base64(void) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_decode_base64(&buf, "dGhpcw==", 8)); - cl_assert_equal_s("this", buf.ptr); - - git_str_clear(&buf); - cl_git_pass(git_str_decode_base64(&buf, "dGhpcyE=", 8)); - cl_assert_equal_s("this!", buf.ptr); - - git_str_clear(&buf); - cl_git_pass(git_str_decode_base64(&buf, "dGhpcyEK", 8)); - cl_assert_equal_s("this!\n", buf.ptr); - - cl_git_fail(git_str_decode_base64(&buf, "This is not a valid base64 string!!!", 36)); - cl_assert_equal_s("this!\n", buf.ptr); - - git_str_dispose(&buf); -} - -void test_core_gitstr__encode_base85(void) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_encode_base85(&buf, "this", 4)); - cl_assert_equal_s("bZBXF", buf.ptr); - git_str_clear(&buf); - - cl_git_pass(git_str_encode_base85(&buf, "two rnds", 8)); - cl_assert_equal_s("ba!tca&BaE", buf.ptr); - git_str_clear(&buf); - - cl_git_pass(git_str_encode_base85(&buf, "this is base 85 encoded", - strlen("this is base 85 encoded"))); - cl_assert_equal_s("bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", buf.ptr); - git_str_clear(&buf); - - git_str_dispose(&buf); -} - -void test_core_gitstr__decode_base85(void) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_decode_base85(&buf, "bZBXF", 5, 4)); - cl_assert_equal_sz(4, buf.size); - cl_assert_equal_s("this", buf.ptr); - git_str_clear(&buf); - - cl_git_pass(git_str_decode_base85(&buf, "ba!tca&BaE", 10, 8)); - cl_assert_equal_sz(8, buf.size); - cl_assert_equal_s("two rnds", buf.ptr); - git_str_clear(&buf); - - cl_git_pass(git_str_decode_base85(&buf, "bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", 30, 23)); - cl_assert_equal_sz(23, buf.size); - cl_assert_equal_s("this is base 85 encoded", buf.ptr); - git_str_clear(&buf); - - git_str_dispose(&buf); -} - -void test_core_gitstr__decode_base85_fails_gracefully(void) -{ - git_str buf = GIT_STR_INIT; - - git_str_puts(&buf, "foobar"); - - cl_git_fail(git_str_decode_base85(&buf, "invalid charsZZ", 15, 42)); - cl_git_fail(git_str_decode_base85(&buf, "invalidchars__ ", 15, 42)); - cl_git_fail(git_str_decode_base85(&buf, "overflowZZ~~~~~", 15, 42)); - cl_git_fail(git_str_decode_base85(&buf, "truncated", 9, 42)); - cl_assert_equal_sz(6, buf.size); - cl_assert_equal_s("foobar", buf.ptr); - - git_str_dispose(&buf); -} - -void test_core_gitstr__classify_with_utf8(void) -{ - char *data0 = "Simple text\n"; - size_t data0len = 12; - char *data1 = "Is that UTF-8 data I see…\nYep!\n"; - size_t data1len = 31; - char *data2 = "Internal NUL!!!\000\n\nI see you!\n"; - size_t data2len = 29; - char *data3 = "\xef\xbb\xbfThis is UTF-8 with a BOM.\n"; - size_t data3len = 20; - git_str b; - - b.ptr = data0; b.size = b.asize = data0len; - cl_assert(!git_str_is_binary(&b)); - cl_assert(!git_str_contains_nul(&b)); - - b.ptr = data1; b.size = b.asize = data1len; - cl_assert(!git_str_is_binary(&b)); - cl_assert(!git_str_contains_nul(&b)); - - b.ptr = data2; b.size = b.asize = data2len; - cl_assert(git_str_is_binary(&b)); - cl_assert(git_str_contains_nul(&b)); - - b.ptr = data3; b.size = b.asize = data3len; - cl_assert(!git_str_is_binary(&b)); - cl_assert(!git_str_contains_nul(&b)); -} - -#define SIMILARITY_TEST_DATA_1 \ - "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ - "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ - "020\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \ - "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \ - "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n" - -void test_core_gitstr__similarity_metric(void) -{ - git_hashsig *a, *b; - git_str buf = GIT_STR_INIT; - int sim; - - /* in the first case, we compare data to itself and expect 100% match */ - - cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); - cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - - cl_assert_equal_i(100, git_hashsig_compare(a, b)); - - git_hashsig_free(a); - git_hashsig_free(b); - - /* if we change just a single byte, how much does that change magnify? */ - - cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); - cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - cl_git_pass(git_str_sets(&buf, - "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ - "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ - "x020x\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \ - "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \ - "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n" - )); - cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - - sim = git_hashsig_compare(a, b); - - cl_assert_in_range(95, sim, 100); /* expect >95% similarity */ - - git_hashsig_free(a); - git_hashsig_free(b); - - /* let's try comparing data to a superset of itself */ - - cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); - cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1 - "050\n051\n052\n053\n054\n055\n056\n057\n058\n059\n")); - cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - - sim = git_hashsig_compare(a, b); - /* 20% lines added ~= 10% lines changed */ - - cl_assert_in_range(85, sim, 95); /* expect similarity around 90% */ - - git_hashsig_free(a); - git_hashsig_free(b); - - /* what if we keep about half the original data and add half new */ - - cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); - cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - cl_git_pass(git_str_sets(&buf, - "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ - "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ - "020x\n021\n022\n023\n024\n" \ - "x25\nx26\nx27\nx28\nx29\n" \ - "x30\nx31\nx32\nx33\nx34\nx35\nx36\nx37\nx38\nx39\n" \ - "x40\nx41\nx42\nx43\nx44\nx45\nx46\nx47\nx48\nx49\n" - )); - cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - - sim = git_hashsig_compare(a, b); - /* 50% lines changed */ - - cl_assert_in_range(40, sim, 60); /* expect in the 40-60% similarity range */ - - git_hashsig_free(a); - git_hashsig_free(b); - - /* lastly, let's check that we can hash file content as well */ - - cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); - cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - - cl_git_pass(git_futils_mkdir("scratch", 0755, GIT_MKDIR_PATH)); - cl_git_mkfile("scratch/testdata", SIMILARITY_TEST_DATA_1); - cl_git_pass(git_hashsig_create_fromfile( - &b, "scratch/testdata", GIT_HASHSIG_NORMAL)); - - cl_assert_equal_i(100, git_hashsig_compare(a, b)); - - git_hashsig_free(a); - git_hashsig_free(b); - - git_str_dispose(&buf); - git_futils_rmdir_r("scratch", NULL, GIT_RMDIR_REMOVE_FILES); -} - - -void test_core_gitstr__similarity_metric_whitespace(void) -{ - git_hashsig *a, *b; - git_str buf = GIT_STR_INIT; - int sim, i, j; - git_hashsig_option_t opt; - const char *tabbed = - " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\n" - " separator = sep[s];\n" - " expect = expect_values[s];\n" - "\n" - " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\n" - " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\n" - " git_str_join(&buf, separator, a[i], b[j]);\n" - " cl_assert_equal_s(*expect, buf.ptr);\n" - " expect++;\n" - " }\n" - " }\n" - " }\n"; - const char *spaced = - " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\n" - " separator = sep[s];\n" - " expect = expect_values[s];\n" - "\n" - " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\n" - " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\n" - " git_str_join(&buf, separator, a[i], b[j]);\n" - " cl_assert_equal_s(*expect, buf.ptr);\n" - " expect++;\n" - " }\n" - " }\n" - " }\n"; - const char *crlf_spaced2 = - " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\r\n" - " separator = sep[s];\r\n" - " expect = expect_values[s];\r\n" - "\r\n" - " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\r\n" - " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\r\n" - " git_str_join(&buf, separator, a[i], b[j]);\r\n" - " cl_assert_equal_s(*expect, buf.ptr);\r\n" - " expect++;\r\n" - " }\r\n" - " }\r\n" - " }\r\n"; - const char *text[3] = { tabbed, spaced, crlf_spaced2 }; - - /* let's try variations of our own code with whitespace changes */ - - for (opt = GIT_HASHSIG_NORMAL; opt <= GIT_HASHSIG_SMART_WHITESPACE; ++opt) { - for (i = 0; i < 3; ++i) { - for (j = 0; j < 3; ++j) { - cl_git_pass(git_str_sets(&buf, text[i])); - cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, opt)); - - cl_git_pass(git_str_sets(&buf, text[j])); - cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, opt)); - - sim = git_hashsig_compare(a, b); - - if (opt == GIT_HASHSIG_NORMAL) { - if (i == j) - cl_assert_equal_i(100, sim); - else - cl_assert_in_range(0, sim, 30); /* pretty different */ - } else { - cl_assert_equal_i(100, sim); - } - - git_hashsig_free(a); - git_hashsig_free(b); - } - } - } - - git_str_dispose(&buf); -} - -#include "../filter/crlf.h" - -#define check_buf(expected,buf) do { \ - cl_assert_equal_s(expected, buf.ptr); \ - cl_assert_equal_sz(strlen(expected), buf.size); } while (0) - -void test_core_gitstr__lf_and_crlf_conversions(void) -{ - git_str src = GIT_STR_INIT, tgt = GIT_STR_INIT; - - /* LF source */ - - git_str_sets(&src, "lf\nlf\nlf\nlf\n"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("lf\r\nlf\r\nlf\r\nlf\r\n", tgt); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf(src.ptr, tgt); - - git_str_sets(&src, "\nlf\nlf\nlf\nlf\nlf"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("\r\nlf\r\nlf\r\nlf\r\nlf\r\nlf", tgt); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf(src.ptr, tgt); - - /* CRLF source */ - - git_str_sets(&src, "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n", tgt); - - git_str_sets(&src, "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n"); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf("crlf\ncrlf\ncrlf\ncrlf\n", tgt); - - git_str_sets(&src, "\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf", tgt); - - git_str_sets(&src, "\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf"); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf("\ncrlf\ncrlf\ncrlf\ncrlf\ncrlf", tgt); - - /* CRLF in LF text */ - - git_str_sets(&src, "\nlf\nlf\ncrlf\r\nlf\nlf\ncrlf\r\n"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("\r\nlf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\ncrlf\r\n", tgt); - - git_str_sets(&src, "\nlf\nlf\ncrlf\r\nlf\nlf\ncrlf\r\n"); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf("\nlf\nlf\ncrlf\nlf\nlf\ncrlf\n", tgt); - - /* LF in CRLF text */ - - git_str_sets(&src, "\ncrlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("\r\ncrlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf", tgt); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf("\ncrlf\ncrlf\nlf\ncrlf\ncrlf", tgt); - - /* bare CR test */ - - git_str_sets(&src, "\rcrlf\r\nlf\nlf\ncr\rcrlf\r\nlf\ncr\r"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("\rcrlf\r\nlf\r\nlf\r\ncr\rcrlf\r\nlf\r\ncr\r", tgt); - - git_str_sets(&src, "\rcrlf\r\nlf\nlf\ncr\rcrlf\r\nlf\ncr\r"); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf("\rcrlf\nlf\nlf\ncr\rcrlf\nlf\ncr\r", tgt); - - git_str_sets(&src, "\rcr\r"); - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf(src.ptr, tgt); - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf("\rcr\r", tgt); - - git_str_dispose(&src); - git_str_dispose(&tgt); - - /* blob correspondence tests */ - - git_str_sets(&src, ALL_CRLF_TEXT_RAW); - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf(ALL_CRLF_TEXT_AS_CRLF, tgt); - git_str_sets(&src, ALL_CRLF_TEXT_RAW); - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf(ALL_CRLF_TEXT_AS_LF, tgt); - git_str_dispose(&src); - git_str_dispose(&tgt); - - git_str_sets(&src, ALL_LF_TEXT_RAW); - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf(ALL_LF_TEXT_AS_CRLF, tgt); - git_str_sets(&src, ALL_LF_TEXT_RAW); - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf(ALL_LF_TEXT_AS_LF, tgt); - git_str_dispose(&src); - git_str_dispose(&tgt); - - git_str_sets(&src, MORE_CRLF_TEXT_RAW); - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf(MORE_CRLF_TEXT_AS_CRLF, tgt); - git_str_sets(&src, MORE_CRLF_TEXT_RAW); - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf(MORE_CRLF_TEXT_AS_LF, tgt); - git_str_dispose(&src); - git_str_dispose(&tgt); - - git_str_sets(&src, MORE_LF_TEXT_RAW); - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf(MORE_LF_TEXT_AS_CRLF, tgt); - git_str_sets(&src, MORE_LF_TEXT_RAW); - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf(MORE_LF_TEXT_AS_LF, tgt); - git_str_dispose(&src); - git_str_dispose(&tgt); -} - -void test_core_gitstr__dont_grow_borrowed(void) -{ - const char *somestring = "blah blah"; - git_str buf = GIT_STR_INIT; - - git_str_attach_notowned(&buf, somestring, strlen(somestring) + 1); - cl_assert_equal_p(somestring, buf.ptr); - cl_assert_equal_i(0, buf.asize); - cl_assert_equal_i(strlen(somestring) + 1, buf.size); - - cl_git_fail_with(GIT_EINVALID, git_str_grow(&buf, 1024)); -} - -void test_core_gitstr__dont_hit_infinite_loop_when_resizing(void) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_puts(&buf, "foobar")); - /* - * We do not care whether this succeeds or fails, which - * would depend on platform-specific allocation - * semantics. We only want to know that the function - * actually returns. - */ - (void)git_str_try_grow(&buf, SIZE_MAX, true); - - git_str_dispose(&buf); -} - -void test_core_gitstr__avoid_printing_into_oom_buffer(void) -{ - git_str buf = GIT_STR_INIT; - - /* Emulate OOM situation with a previous allocation */ - buf.asize = 8; - buf.ptr = git_str__oom; - - /* - * Print the same string again. As the buffer still has - * an `asize` of 8 due to the previous print, - * `ENSURE_SIZE` would not try to reallocate the array at - * all. As it didn't explicitly check for `git_str__oom` - * in earlier versions, this would've resulted in it - * returning successfully and thus `git_str_puts` would - * just print into the `git_str__oom` array. - */ - cl_git_fail(git_str_puts(&buf, "foobar")); -} diff --git a/tests/core/hex.c b/tests/core/hex.c deleted file mode 100644 index 930af1670..000000000 --- a/tests/core/hex.c +++ /dev/null @@ -1,22 +0,0 @@ -#include "clar_libgit2.h" -#include "util.h" - -void test_core_hex__fromhex(void) -{ - /* Passing cases */ - cl_assert(git__fromhex('0') == 0x0); - cl_assert(git__fromhex('1') == 0x1); - cl_assert(git__fromhex('3') == 0x3); - cl_assert(git__fromhex('9') == 0x9); - cl_assert(git__fromhex('A') == 0xa); - cl_assert(git__fromhex('C') == 0xc); - cl_assert(git__fromhex('F') == 0xf); - cl_assert(git__fromhex('a') == 0xa); - cl_assert(git__fromhex('c') == 0xc); - cl_assert(git__fromhex('f') == 0xf); - - /* Failing cases */ - cl_assert(git__fromhex('g') == -1); - cl_assert(git__fromhex('z') == -1); - cl_assert(git__fromhex('X') == -1); -} diff --git a/tests/core/iconv.c b/tests/core/iconv.c deleted file mode 100644 index af1b4eabf..000000000 --- a/tests/core/iconv.c +++ /dev/null @@ -1,78 +0,0 @@ -#include "clar_libgit2.h" -#include "fs_path.h" - -#ifdef GIT_USE_ICONV -static git_fs_path_iconv_t ic; -static char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; -static char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; -#endif - -void test_core_iconv__initialize(void) -{ -#ifdef GIT_USE_ICONV - cl_git_pass(git_fs_path_iconv_init_precompose(&ic)); -#endif -} - -void test_core_iconv__cleanup(void) -{ -#ifdef GIT_USE_ICONV - git_fs_path_iconv_clear(&ic); -#endif -} - -void test_core_iconv__unchanged(void) -{ -#ifdef GIT_USE_ICONV - const char *data = "Ascii data", *original = data; - size_t datalen = strlen(data); - - cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); - GIT_UNUSED(datalen); - - /* There are no high bits set, so this should leave data untouched */ - cl_assert(data == original); -#endif -} - -void test_core_iconv__decomposed_to_precomposed(void) -{ -#ifdef GIT_USE_ICONV - const char *data = nfd; - size_t datalen, nfdlen = strlen(nfd); - - datalen = nfdlen; - cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); - GIT_UNUSED(datalen); - - /* The decomposed nfd string should be transformed to the nfc form - * (on platforms where iconv is enabled, of course). - */ - cl_assert_equal_s(nfc, data); - - /* should be able to do it multiple times with the same git_fs_path_iconv_t */ - data = nfd; datalen = nfdlen; - cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); - cl_assert_equal_s(nfc, data); - - data = nfd; datalen = nfdlen; - cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); - cl_assert_equal_s(nfc, data); -#endif -} - -void test_core_iconv__precomposed_is_unmodified(void) -{ -#ifdef GIT_USE_ICONV - const char *data = nfc; - size_t datalen = strlen(nfc); - - cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); - GIT_UNUSED(datalen); - - /* data is already in precomposed form, so even though some bytes have - * the high-bit set, the iconv transform should result in no change. - */ - cl_assert_equal_s(nfc, data); -#endif -} diff --git a/tests/core/init.c b/tests/core/init.c deleted file mode 100644 index eba77ef52..000000000 --- a/tests/core/init.c +++ /dev/null @@ -1,54 +0,0 @@ -#include "clar_libgit2.h" - -void test_core_init__returns_count(void) -{ - /* libgit2_tests initializes us first, so we have an existing - * initialization. - */ - cl_assert_equal_i(2, git_libgit2_init()); - cl_assert_equal_i(3, git_libgit2_init()); - - cl_assert_equal_i(2, git_libgit2_shutdown()); - cl_assert_equal_i(1, git_libgit2_shutdown()); -} - -void test_core_init__reinit_succeeds(void) -{ - cl_assert_equal_i(0, git_libgit2_shutdown()); - cl_assert_equal_i(1, git_libgit2_init()); - cl_sandbox_set_search_path_defaults(); -} - -#ifdef GIT_THREADS -static void *reinit(void *unused) -{ - unsigned i; - - for (i = 0; i < 20; i++) { - cl_assert(git_libgit2_init() > 0); - cl_assert(git_libgit2_shutdown() >= 0); - } - - return unused; -} -#endif - -void test_core_init__concurrent_init_succeeds(void) -{ -#ifdef GIT_THREADS - git_thread threads[10]; - unsigned i; - - cl_assert_equal_i(2, git_libgit2_init()); - - for (i = 0; i < ARRAY_SIZE(threads); i++) - git_thread_create(&threads[i], reinit, NULL); - for (i = 0; i < ARRAY_SIZE(threads); i++) - git_thread_join(&threads[i], NULL); - - cl_assert_equal_i(1, git_libgit2_shutdown()); - cl_sandbox_set_search_path_defaults(); -#else - cl_skip(); -#endif -} diff --git a/tests/core/integer.c b/tests/core/integer.c deleted file mode 100644 index 18364ba62..000000000 --- a/tests/core/integer.c +++ /dev/null @@ -1,253 +0,0 @@ -#include "clar_libgit2.h" - -void test_core_integer__multiply_int64_no_overflow(void) -{ -#if !defined(git__multiply_int64_overflow) - int64_t result = 0; - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x800000000000000))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x800000000000000))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x7fffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x7fffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x8000000000000000))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(0x1)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(-0x1)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(-0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x800000000000000))); - cl_assert_equal_i(result, INT64_C(0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x800000000000000))); - cl_assert_equal_i(result, INT64_C(-0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x7fffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x7fffffffffffffff))); - cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(-0x1)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(0x1)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(-0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x800000000000000))); - cl_assert_equal_i(result, INT64_C(-0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x800000000000000))); - cl_assert_equal_i(result, INT64_C(0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x7fffffffffffffff))); - cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x7fffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(-0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(0x4)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(-0x4)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x800000000000000))); - cl_assert_equal_i(result, INT64_C(0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x800000000000000))); - cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(-0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(-0x4)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(0x4)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x800000000000000))); - cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x800000000000000))); - cl_assert_equal_i(result, INT64_C(0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x4000000000000000))); - cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(-0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(-0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x4000000000000000), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); -#endif -} - -void test_core_integer__multiply_int64_overflow(void) -{ -#if !defined(git__multiply_int64_overflow) - int64_t result = 0; - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x4000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x4000000000000000), INT64_C(0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x8000000000000000))); -#endif -} - -void test_core_integer__multiply_int64_edge_cases(void) -{ -#if !defined(git__multiply_int64_overflow) - int64_t result = 0; - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x8000000000000000))); - cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x1))); -#endif -} diff --git a/tests/core/link.c b/tests/core/link.c deleted file mode 100644 index a1e2706b2..000000000 --- a/tests/core/link.c +++ /dev/null @@ -1,630 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" - -#ifdef GIT_WIN32 -# include "win32/reparse.h" -#endif - -void test_core_link__cleanup(void) -{ -#ifdef GIT_WIN32 - RemoveDirectory("lstat_junction"); - RemoveDirectory("lstat_dangling"); - RemoveDirectory("lstat_dangling_dir"); - RemoveDirectory("lstat_dangling_junction"); - - RemoveDirectory("stat_junction"); - RemoveDirectory("stat_dangling"); - RemoveDirectory("stat_dangling_dir"); - RemoveDirectory("stat_dangling_junction"); -#endif -} - -#ifdef GIT_WIN32 -static bool should_run(void) -{ - static SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY }; - PSID admin_sid; - BOOL is_admin; - - cl_win32_pass(AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &admin_sid)); - cl_win32_pass(CheckTokenMembership(NULL, admin_sid, &is_admin)); - FreeSid(admin_sid); - - return is_admin ? true : false; -} -#else -static bool should_run(void) -{ - return true; -} -#endif - -static void do_symlink(const char *old, const char *new, int is_dir) -{ -#ifndef GIT_WIN32 - GIT_UNUSED(is_dir); - - cl_must_pass(symlink(old, new)); -#else - typedef DWORD (WINAPI *create_symlink_func)(LPCTSTR, LPCTSTR, DWORD); - HMODULE module; - create_symlink_func pCreateSymbolicLink; - - cl_assert(module = GetModuleHandle("kernel32")); - cl_assert(pCreateSymbolicLink = (create_symlink_func)(void *)GetProcAddress(module, "CreateSymbolicLinkA")); - - cl_win32_pass(pCreateSymbolicLink(new, old, is_dir)); -#endif -} - -static void do_hardlink(const char *old, const char *new) -{ -#ifndef GIT_WIN32 - cl_must_pass(link(old, new)); -#else - typedef DWORD (WINAPI *create_hardlink_func)(LPCTSTR, LPCTSTR, LPSECURITY_ATTRIBUTES); - HMODULE module; - create_hardlink_func pCreateHardLink; - - cl_assert(module = GetModuleHandle("kernel32")); - cl_assert(pCreateHardLink = (create_hardlink_func)(void *)GetProcAddress(module, "CreateHardLinkA")); - - cl_win32_pass(pCreateHardLink(new, old, 0)); -#endif -} - -#ifdef GIT_WIN32 - -static void do_junction(const char *old, const char *new) -{ - GIT_REPARSE_DATA_BUFFER *reparse_buf; - HANDLE handle; - git_str unparsed_buf = GIT_STR_INIT; - wchar_t *subst_utf16, *print_utf16; - DWORD ioctl_ret; - int subst_utf16_len, subst_byte_len, print_utf16_len, print_byte_len, ret; - USHORT reparse_buflen; - size_t i; - - /* Junction targets must be the unparsed name, starting with \??\, using - * backslashes instead of forward, and end in a trailing backslash. - * eg: \??\C:\Foo\ - */ - git_str_puts(&unparsed_buf, "\\??\\"); - - for (i = 0; i < strlen(old); i++) - git_str_putc(&unparsed_buf, old[i] == '/' ? '\\' : old[i]); - - git_str_putc(&unparsed_buf, '\\'); - - subst_utf16_len = git__utf8_to_16(NULL, 0, git_str_cstr(&unparsed_buf)); - subst_byte_len = subst_utf16_len * sizeof(WCHAR); - - print_utf16_len = subst_utf16_len - 4; - print_byte_len = subst_byte_len - (4 * sizeof(WCHAR)); - - /* The junction must be an empty directory before the junction attribute - * can be added. - */ - cl_win32_pass(CreateDirectoryA(new, NULL)); - - handle = CreateFileA(new, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); - cl_win32_pass(handle != INVALID_HANDLE_VALUE); - - reparse_buflen = (USHORT)(REPARSE_DATA_HEADER_SIZE + - REPARSE_DATA_MOUNTPOINT_HEADER_SIZE + - subst_byte_len + sizeof(WCHAR) + - print_byte_len + sizeof(WCHAR)); - - reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen); - cl_assert(reparse_buf); - - subst_utf16 = reparse_buf->ReparseBuffer.MountPoint.PathBuffer; - print_utf16 = subst_utf16 + subst_utf16_len + 1; - - ret = git__utf8_to_16(subst_utf16, subst_utf16_len + 1, - git_str_cstr(&unparsed_buf)); - cl_assert_equal_i(subst_utf16_len, ret); - - ret = git__utf8_to_16(print_utf16, - print_utf16_len + 1, git_str_cstr(&unparsed_buf) + 4); - cl_assert_equal_i(print_utf16_len, ret); - - reparse_buf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; - reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset = 0; - reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength = subst_byte_len; - reparse_buf->ReparseBuffer.MountPoint.PrintNameOffset = (USHORT)(subst_byte_len + sizeof(WCHAR)); - reparse_buf->ReparseBuffer.MountPoint.PrintNameLength = print_byte_len; - reparse_buf->ReparseDataLength = reparse_buflen - REPARSE_DATA_HEADER_SIZE; - - cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, - reparse_buf, reparse_buflen, NULL, 0, &ioctl_ret, NULL)); - - CloseHandle(handle); - LocalFree(reparse_buf); - - git_str_dispose(&unparsed_buf); -} - -static void do_custom_reparse(const char *path) -{ - REPARSE_GUID_DATA_BUFFER *reparse_buf; - HANDLE handle; - DWORD ioctl_ret; - - const char *reparse_data = "Reparse points are silly."; - size_t reparse_buflen = REPARSE_GUID_DATA_BUFFER_HEADER_SIZE + - strlen(reparse_data) + 1; - - reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen); - cl_assert(reparse_buf); - - reparse_buf->ReparseTag = 42; - reparse_buf->ReparseDataLength = (WORD)(strlen(reparse_data) + 1); - - reparse_buf->ReparseGuid.Data1 = 0xdeadbeef; - reparse_buf->ReparseGuid.Data2 = 0xdead; - reparse_buf->ReparseGuid.Data3 = 0xbeef; - reparse_buf->ReparseGuid.Data4[0] = 42; - reparse_buf->ReparseGuid.Data4[1] = 42; - reparse_buf->ReparseGuid.Data4[2] = 42; - reparse_buf->ReparseGuid.Data4[3] = 42; - reparse_buf->ReparseGuid.Data4[4] = 42; - reparse_buf->ReparseGuid.Data4[5] = 42; - reparse_buf->ReparseGuid.Data4[6] = 42; - reparse_buf->ReparseGuid.Data4[7] = 42; - reparse_buf->ReparseGuid.Data4[8] = 42; - - memcpy(reparse_buf->GenericReparseBuffer.DataBuffer, - reparse_data, strlen(reparse_data) + 1); - - handle = CreateFileA(path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); - cl_win32_pass(handle != INVALID_HANDLE_VALUE); - - cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, - reparse_buf, - reparse_buf->ReparseDataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, - NULL, 0, &ioctl_ret, NULL)); - - CloseHandle(handle); - LocalFree(reparse_buf); -} - -#endif - -void test_core_link__stat_regular_file(void) -{ - struct stat st; - - cl_git_rewritefile("stat_regfile", "This is a regular file!\n"); - - cl_must_pass(p_stat("stat_regfile", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(24, st.st_size); -} - -void test_core_link__lstat_regular_file(void) -{ - struct stat st; - - cl_git_rewritefile("lstat_regfile", "This is a regular file!\n"); - - cl_must_pass(p_stat("lstat_regfile", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(24, st.st_size); -} - -void test_core_link__stat_symlink(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - cl_git_rewritefile("stat_target", "This is the target of a symbolic link.\n"); - do_symlink("stat_target", "stat_symlink", 0); - - cl_must_pass(p_stat("stat_target", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(39, st.st_size); - - cl_must_pass(p_stat("stat_symlink", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(39, st.st_size); -} - -void test_core_link__stat_symlink_directory(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - p_mkdir("stat_dirtarget", 0777); - do_symlink("stat_dirtarget", "stat_dirlink", 1); - - cl_must_pass(p_stat("stat_dirtarget", &st)); - cl_assert(S_ISDIR(st.st_mode)); - - cl_must_pass(p_stat("stat_dirlink", &st)); - cl_assert(S_ISDIR(st.st_mode)); -} - -void test_core_link__stat_symlink_chain(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - cl_git_rewritefile("stat_final_target", "Final target of some symbolic links...\n"); - do_symlink("stat_final_target", "stat_chain_3", 0); - do_symlink("stat_chain_3", "stat_chain_2", 0); - do_symlink("stat_chain_2", "stat_chain_1", 0); - - cl_must_pass(p_stat("stat_chain_1", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(39, st.st_size); -} - -void test_core_link__stat_dangling_symlink(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - do_symlink("stat_nonexistent", "stat_dangling", 0); - - cl_must_fail(p_stat("stat_nonexistent", &st)); - cl_must_fail(p_stat("stat_dangling", &st)); -} - -void test_core_link__stat_dangling_symlink_directory(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - do_symlink("stat_nonexistent", "stat_dangling_dir", 1); - - cl_must_fail(p_stat("stat_nonexistent_dir", &st)); - cl_must_fail(p_stat("stat_dangling", &st)); -} - -void test_core_link__lstat_symlink(void) -{ - git_str target_path = GIT_STR_INIT; - struct stat st; - - if (!should_run()) - clar__skip(); - - /* Windows always writes the canonical path as the link target, so - * write the full path on all platforms. - */ - git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_target"); - - cl_git_rewritefile("lstat_target", "This is the target of a symbolic link.\n"); - do_symlink(git_str_cstr(&target_path), "lstat_symlink", 0); - - cl_must_pass(p_lstat("lstat_target", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(39, st.st_size); - - cl_must_pass(p_lstat("lstat_symlink", &st)); - cl_assert(S_ISLNK(st.st_mode)); - cl_assert_equal_i(git_str_len(&target_path), st.st_size); - - git_str_dispose(&target_path); -} - -void test_core_link__lstat_symlink_directory(void) -{ - git_str target_path = GIT_STR_INIT; - struct stat st; - - if (!should_run()) - clar__skip(); - - git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_dirtarget"); - - p_mkdir("lstat_dirtarget", 0777); - do_symlink(git_str_cstr(&target_path), "lstat_dirlink", 1); - - cl_must_pass(p_lstat("lstat_dirtarget", &st)); - cl_assert(S_ISDIR(st.st_mode)); - - cl_must_pass(p_lstat("lstat_dirlink", &st)); - cl_assert(S_ISLNK(st.st_mode)); - cl_assert_equal_i(git_str_len(&target_path), st.st_size); - - git_str_dispose(&target_path); -} - -void test_core_link__lstat_dangling_symlink(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - do_symlink("lstat_nonexistent", "lstat_dangling", 0); - - cl_must_fail(p_lstat("lstat_nonexistent", &st)); - - cl_must_pass(p_lstat("lstat_dangling", &st)); - cl_assert(S_ISLNK(st.st_mode)); - cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size); -} - -void test_core_link__lstat_dangling_symlink_directory(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - do_symlink("lstat_nonexistent", "lstat_dangling_dir", 1); - - cl_must_fail(p_lstat("lstat_nonexistent", &st)); - - cl_must_pass(p_lstat("lstat_dangling_dir", &st)); - cl_assert(S_ISLNK(st.st_mode)); - cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size); -} - -void test_core_link__stat_junction(void) -{ -#ifdef GIT_WIN32 - git_str target_path = GIT_STR_INIT; - struct stat st; - - git_str_join(&target_path, '/', clar_sandbox_path(), "stat_junctarget"); - - p_mkdir("stat_junctarget", 0777); - do_junction(git_str_cstr(&target_path), "stat_junction"); - - cl_must_pass(p_stat("stat_junctarget", &st)); - cl_assert(S_ISDIR(st.st_mode)); - - cl_must_pass(p_stat("stat_junction", &st)); - cl_assert(S_ISDIR(st.st_mode)); - - git_str_dispose(&target_path); -#endif -} - -void test_core_link__stat_dangling_junction(void) -{ -#ifdef GIT_WIN32 - git_str target_path = GIT_STR_INIT; - struct stat st; - - git_str_join(&target_path, '/', clar_sandbox_path(), "stat_nonexistent_junctarget"); - - p_mkdir("stat_nonexistent_junctarget", 0777); - do_junction(git_str_cstr(&target_path), "stat_dangling_junction"); - - RemoveDirectory("stat_nonexistent_junctarget"); - - cl_must_fail(p_stat("stat_nonexistent_junctarget", &st)); - cl_must_fail(p_stat("stat_dangling_junction", &st)); - - git_str_dispose(&target_path); -#endif -} - -void test_core_link__lstat_junction(void) -{ -#ifdef GIT_WIN32 - git_str target_path = GIT_STR_INIT; - struct stat st; - - git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_junctarget"); - - p_mkdir("lstat_junctarget", 0777); - do_junction(git_str_cstr(&target_path), "lstat_junction"); - - cl_must_pass(p_lstat("lstat_junctarget", &st)); - cl_assert(S_ISDIR(st.st_mode)); - - cl_must_pass(p_lstat("lstat_junction", &st)); - cl_assert(S_ISLNK(st.st_mode)); - - git_str_dispose(&target_path); -#endif -} - -void test_core_link__lstat_dangling_junction(void) -{ -#ifdef GIT_WIN32 - git_str target_path = GIT_STR_INIT; - struct stat st; - - git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_nonexistent_junctarget"); - - p_mkdir("lstat_nonexistent_junctarget", 0777); - do_junction(git_str_cstr(&target_path), "lstat_dangling_junction"); - - RemoveDirectory("lstat_nonexistent_junctarget"); - - cl_must_fail(p_lstat("lstat_nonexistent_junctarget", &st)); - - cl_must_pass(p_lstat("lstat_dangling_junction", &st)); - cl_assert(S_ISLNK(st.st_mode)); - cl_assert_equal_i(git_str_len(&target_path), st.st_size); - - git_str_dispose(&target_path); -#endif -} - -void test_core_link__stat_hardlink(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - cl_git_rewritefile("stat_hardlink1", "This file has many names!\n"); - do_hardlink("stat_hardlink1", "stat_hardlink2"); - - cl_must_pass(p_stat("stat_hardlink1", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(26, st.st_size); - - cl_must_pass(p_stat("stat_hardlink2", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(26, st.st_size); -} - -void test_core_link__lstat_hardlink(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - cl_git_rewritefile("lstat_hardlink1", "This file has many names!\n"); - do_hardlink("lstat_hardlink1", "lstat_hardlink2"); - - cl_must_pass(p_lstat("lstat_hardlink1", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(26, st.st_size); - - cl_must_pass(p_lstat("lstat_hardlink2", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(26, st.st_size); -} - -void test_core_link__stat_reparse_point(void) -{ -#ifdef GIT_WIN32 - struct stat st; - - /* Generic reparse points should be treated as regular files, only - * symlinks and junctions should be treated as links. - */ - - cl_git_rewritefile("stat_reparse", "This is a reparse point!\n"); - do_custom_reparse("stat_reparse"); - - cl_must_pass(p_lstat("stat_reparse", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(25, st.st_size); -#endif -} - -void test_core_link__lstat_reparse_point(void) -{ -#ifdef GIT_WIN32 - struct stat st; - - cl_git_rewritefile("lstat_reparse", "This is a reparse point!\n"); - do_custom_reparse("lstat_reparse"); - - cl_must_pass(p_lstat("lstat_reparse", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(25, st.st_size); -#endif -} - -void test_core_link__readlink_nonexistent_file(void) -{ - char buf[2048]; - - cl_must_fail(p_readlink("readlink_nonexistent", buf, 2048)); - cl_assert_equal_i(ENOENT, errno); -} - -void test_core_link__readlink_normal_file(void) -{ - char buf[2048]; - - cl_git_rewritefile("readlink_regfile", "This is a regular file!\n"); - cl_must_fail(p_readlink("readlink_regfile", buf, 2048)); - cl_assert_equal_i(EINVAL, errno); -} - -void test_core_link__readlink_symlink(void) -{ - git_str target_path = GIT_STR_INIT; - int len; - char buf[2048]; - - if (!should_run()) - clar__skip(); - - git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_target"); - - cl_git_rewritefile("readlink_target", "This is the target of a symlink\n"); - do_symlink(git_str_cstr(&target_path), "readlink_link", 0); - - len = p_readlink("readlink_link", buf, 2048); - cl_must_pass(len); - - buf[len] = 0; - - cl_assert_equal_s(git_str_cstr(&target_path), buf); - - git_str_dispose(&target_path); -} - -void test_core_link__readlink_dangling(void) -{ - git_str target_path = GIT_STR_INIT; - int len; - char buf[2048]; - - if (!should_run()) - clar__skip(); - - git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_nonexistent"); - - do_symlink(git_str_cstr(&target_path), "readlink_dangling", 0); - - len = p_readlink("readlink_dangling", buf, 2048); - cl_must_pass(len); - - buf[len] = 0; - - cl_assert_equal_s(git_str_cstr(&target_path), buf); - - git_str_dispose(&target_path); -} - -void test_core_link__readlink_multiple(void) -{ - git_str target_path = GIT_STR_INIT, - path3 = GIT_STR_INIT, path2 = GIT_STR_INIT, path1 = GIT_STR_INIT; - int len; - char buf[2048]; - - if (!should_run()) - clar__skip(); - - git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_final"); - git_str_join(&path3, '/', clar_sandbox_path(), "readlink_3"); - git_str_join(&path2, '/', clar_sandbox_path(), "readlink_2"); - git_str_join(&path1, '/', clar_sandbox_path(), "readlink_1"); - - do_symlink(git_str_cstr(&target_path), git_str_cstr(&path3), 0); - do_symlink(git_str_cstr(&path3), git_str_cstr(&path2), 0); - do_symlink(git_str_cstr(&path2), git_str_cstr(&path1), 0); - - len = p_readlink("readlink_1", buf, 2048); - cl_must_pass(len); - - buf[len] = 0; - - cl_assert_equal_s(git_str_cstr(&path2), buf); - - git_str_dispose(&path1); - git_str_dispose(&path2); - git_str_dispose(&path3); - git_str_dispose(&target_path); -} diff --git a/tests/core/memmem.c b/tests/core/memmem.c deleted file mode 100644 index fd9986d01..000000000 --- a/tests/core/memmem.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "clar_libgit2.h" - -static void assert_found(const char *haystack, const char *needle, size_t expected_pos) -{ - cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0, - needle, needle ? strlen(needle) : 0), - haystack + expected_pos); -} - -static void assert_absent(const char *haystack, const char *needle) -{ - cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0, - needle, needle ? strlen(needle) : 0), - NULL); -} - -void test_core_memmem__found(void) -{ - assert_found("a", "a", 0); - assert_found("ab", "a", 0); - assert_found("ba", "a", 1); - assert_found("aa", "a", 0); - assert_found("aab", "aa", 0); - assert_found("baa", "aa", 1); - assert_found("dabc", "abc", 1); - assert_found("abababc", "abc", 4); -} - -void test_core_memmem__absent(void) -{ - assert_absent("a", "b"); - assert_absent("a", "aa"); - assert_absent("ba", "ab"); - assert_absent("ba", "ab"); - assert_absent("abc", "abcd"); - assert_absent("abcabcabc", "bcac"); -} - -void test_core_memmem__edgecases(void) -{ - assert_absent(NULL, NULL); - assert_absent("a", NULL); - assert_absent(NULL, "a"); - assert_absent("", "a"); - assert_absent("a", ""); -} diff --git a/tests/core/mkdir.c b/tests/core/mkdir.c deleted file mode 100644 index 58a4cfcdb..000000000 --- a/tests/core/mkdir.c +++ /dev/null @@ -1,291 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "posix.h" - -static void cleanup_basic_dirs(void *ref) -{ - GIT_UNUSED(ref); - git_futils_rmdir_r("d0", NULL, GIT_RMDIR_EMPTY_HIERARCHY); - git_futils_rmdir_r("d1", NULL, GIT_RMDIR_EMPTY_HIERARCHY); - git_futils_rmdir_r("d2", NULL, GIT_RMDIR_EMPTY_HIERARCHY); - git_futils_rmdir_r("d3", NULL, GIT_RMDIR_EMPTY_HIERARCHY); - git_futils_rmdir_r("d4", NULL, GIT_RMDIR_EMPTY_HIERARCHY); -} - -void test_core_mkdir__absolute(void) -{ - git_str path = GIT_STR_INIT; - - cl_set_cleanup(cleanup_basic_dirs, NULL); - - git_str_joinpath(&path, clar_sandbox_path(), "d0"); - - /* make a directory */ - cl_assert(!git_fs_path_isdir(path.ptr)); - cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); - cl_assert(git_fs_path_isdir(path.ptr)); - - git_str_joinpath(&path, path.ptr, "subdir"); - cl_assert(!git_fs_path_isdir(path.ptr)); - cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); - cl_assert(git_fs_path_isdir(path.ptr)); - - /* ensure mkdir_r works for a single subdir */ - git_str_joinpath(&path, path.ptr, "another"); - cl_assert(!git_fs_path_isdir(path.ptr)); - cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); - cl_assert(git_fs_path_isdir(path.ptr)); - - /* ensure mkdir_r works */ - git_str_joinpath(&path, clar_sandbox_path(), "d1/foo/bar/asdf"); - cl_assert(!git_fs_path_isdir(path.ptr)); - cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); - cl_assert(git_fs_path_isdir(path.ptr)); - - /* ensure we don't imply recursive */ - git_str_joinpath(&path, clar_sandbox_path(), "d2/foo/bar/asdf"); - cl_assert(!git_fs_path_isdir(path.ptr)); - cl_git_fail(git_futils_mkdir(path.ptr, 0755, 0)); - cl_assert(!git_fs_path_isdir(path.ptr)); - - git_str_dispose(&path); -} - -void test_core_mkdir__basic(void) -{ - cl_set_cleanup(cleanup_basic_dirs, NULL); - - /* make a directory */ - cl_assert(!git_fs_path_isdir("d0")); - cl_git_pass(git_futils_mkdir("d0", 0755, 0)); - cl_assert(git_fs_path_isdir("d0")); - - /* make a path */ - cl_assert(!git_fs_path_isdir("d1")); - cl_git_pass(git_futils_mkdir("d1/d1.1/d1.2", 0755, GIT_MKDIR_PATH)); - cl_assert(git_fs_path_isdir("d1")); - cl_assert(git_fs_path_isdir("d1/d1.1")); - cl_assert(git_fs_path_isdir("d1/d1.1/d1.2")); - - /* make a dir exclusively */ - cl_assert(!git_fs_path_isdir("d2")); - cl_git_pass(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL)); - cl_assert(git_fs_path_isdir("d2")); - - /* make exclusive failure */ - cl_git_fail(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL)); - - /* make a path exclusively */ - cl_assert(!git_fs_path_isdir("d3")); - cl_git_pass(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); - cl_assert(git_fs_path_isdir("d3")); - cl_assert(git_fs_path_isdir("d3/d3.1/d3.2")); - - /* make exclusive path failure */ - cl_git_fail(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); - /* ??? Should EXCL only apply to the last item in the path? */ - - /* path with trailing slash? */ - cl_assert(!git_fs_path_isdir("d4")); - cl_git_pass(git_futils_mkdir("d4/d4.1/", 0755, GIT_MKDIR_PATH)); - cl_assert(git_fs_path_isdir("d4/d4.1")); -} - -static void cleanup_basedir(void *ref) -{ - GIT_UNUSED(ref); - git_futils_rmdir_r("base", NULL, GIT_RMDIR_EMPTY_HIERARCHY); -} - -void test_core_mkdir__with_base(void) -{ -#define BASEDIR "base/dir/here" - - cl_set_cleanup(cleanup_basedir, NULL); - - cl_git_pass(git_futils_mkdir(BASEDIR, 0755, GIT_MKDIR_PATH)); - - cl_git_pass(git_futils_mkdir_relative("a", BASEDIR, 0755, 0, NULL)); - cl_assert(git_fs_path_isdir(BASEDIR "/a")); - - cl_git_pass(git_futils_mkdir_relative("b/b1/b2", BASEDIR, 0755, GIT_MKDIR_PATH, NULL)); - cl_assert(git_fs_path_isdir(BASEDIR "/b/b1/b2")); - - /* exclusive with existing base */ - cl_git_pass(git_futils_mkdir_relative("c/c1/c2", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); - - /* fail: exclusive with duplicated suffix */ - cl_git_fail(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); - - /* fail: exclusive with any duplicated component */ - cl_git_fail(git_futils_mkdir_relative("c/cz/cz", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); - - /* success: exclusive without path */ - cl_git_pass(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_EXCL, NULL)); - - /* path with shorter base and existing dirs */ - cl_git_pass(git_futils_mkdir_relative("dir/here/d/", "base", 0755, GIT_MKDIR_PATH, NULL)); - cl_assert(git_fs_path_isdir("base/dir/here/d")); - - /* fail: path with shorter base and existing dirs */ - cl_git_fail(git_futils_mkdir_relative("dir/here/e/", "base", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); - - /* fail: base with missing components */ - cl_git_fail(git_futils_mkdir_relative("f/", "base/missing", 0755, GIT_MKDIR_PATH, NULL)); - - /* success: shift missing component to path */ - cl_git_pass(git_futils_mkdir_relative("missing/f/", "base/", 0755, GIT_MKDIR_PATH, NULL)); -} - -static void cleanup_chmod_root(void *ref) -{ - mode_t *mode = ref; - - if (mode != NULL) { - (void)p_umask(*mode); - git__free(mode); - } - - git_futils_rmdir_r("r", NULL, GIT_RMDIR_EMPTY_HIERARCHY); -} - -#define check_mode(X,A) check_mode_at_line((X), (A), __FILE__, __func__, __LINE__) - -static void check_mode_at_line( - mode_t expected, mode_t actual, - const char *file, const char *func, int line) -{ - /* FAT filesystems don't support exec bit, nor group/world bits */ - if (!cl_is_chmod_supported()) { - expected &= 0600; - actual &= 0600; - } - - clar__assert_equal( - file, func, line, "expected_mode != actual_mode", 1, - "%07o", (int)expected, (int)(actual & 0777)); -} - -void test_core_mkdir__chmods(void) -{ - struct stat st; - mode_t *old = git__malloc(sizeof(mode_t)); - *old = p_umask(022); - - cl_set_cleanup(cleanup_chmod_root, old); - - cl_git_pass(git_futils_mkdir("r", 0777, 0)); - - cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL)); - - cl_git_pass(git_fs_path_lstat("r/mode", &st)); - check_mode(0755, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode/is", &st)); - check_mode(0755, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode/is/important", &st)); - check_mode(0755, st.st_mode); - - cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL)); - - cl_git_pass(git_fs_path_lstat("r/mode2", &st)); - check_mode(0755, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode2/is2", &st)); - check_mode(0755, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode2/is2/important2", &st)); - check_mode(0777, st.st_mode); - - cl_git_pass(git_futils_mkdir_relative("mode3/is3/important3", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL)); - - cl_git_pass(git_fs_path_lstat("r/mode3", &st)); - check_mode(0777, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode3/is3", &st)); - check_mode(0777, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode3/is3/important3", &st)); - check_mode(0777, st.st_mode); - - /* test that we chmod existing dir */ - - cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL)); - - cl_git_pass(git_fs_path_lstat("r/mode", &st)); - check_mode(0755, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode/is", &st)); - check_mode(0755, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode/is/important", &st)); - check_mode(0777, st.st_mode); - - /* test that we chmod even existing dirs if CHMOD_PATH is set */ - - cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2.1", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL)); - - cl_git_pass(git_fs_path_lstat("r/mode2", &st)); - check_mode(0777, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode2/is2", &st)); - check_mode(0777, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode2/is2/important2.1", &st)); - check_mode(0777, st.st_mode); -} - -void test_core_mkdir__keeps_parent_symlinks(void) -{ -#ifndef GIT_WIN32 - git_str path = GIT_STR_INIT; - - cl_set_cleanup(cleanup_basic_dirs, NULL); - - /* make a directory */ - cl_assert(!git_fs_path_isdir("d0")); - cl_git_pass(git_futils_mkdir("d0", 0755, 0)); - cl_assert(git_fs_path_isdir("d0")); - - cl_must_pass(symlink("d0", "d1")); - cl_assert(git_fs_path_islink("d1")); - - cl_git_pass(git_futils_mkdir("d1/foo/bar", 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS)); - cl_assert(git_fs_path_islink("d1")); - cl_assert(git_fs_path_isdir("d1/foo/bar")); - cl_assert(git_fs_path_isdir("d0/foo/bar")); - - cl_must_pass(symlink("d0", "d2")); - cl_assert(git_fs_path_islink("d2")); - - git_str_joinpath(&path, clar_sandbox_path(), "d2/other/dir"); - - cl_git_pass(git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS)); - cl_assert(git_fs_path_islink("d2")); - cl_assert(git_fs_path_isdir("d2/other/dir")); - cl_assert(git_fs_path_isdir("d0/other/dir")); - - git_str_dispose(&path); -#endif -} - -void test_core_mkdir__mkdir_path_inside_unwriteable_parent(void) -{ - struct stat st; - mode_t *old; - - /* FAT filesystems don't support exec bit, nor group/world bits */ - if (!cl_is_chmod_supported()) - return; - - cl_assert((old = git__malloc(sizeof(mode_t))) != NULL); - *old = p_umask(022); - cl_set_cleanup(cleanup_chmod_root, old); - - cl_git_pass(git_futils_mkdir("r", 0777, 0)); - cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL)); - cl_git_pass(git_fs_path_lstat("r/mode", &st)); - check_mode(0755, st.st_mode); - - cl_must_pass(p_chmod("r/mode", 0111)); - cl_git_pass(git_fs_path_lstat("r/mode", &st)); - check_mode(0111, st.st_mode); - - cl_git_pass( - git_futils_mkdir_relative("mode/is/okay/inside", "r", 0777, GIT_MKDIR_PATH, NULL)); - cl_git_pass(git_fs_path_lstat("r/mode/is/okay/inside", &st)); - check_mode(0755, st.st_mode); - - cl_must_pass(p_chmod("r/mode", 0777)); -} diff --git a/tests/core/oid.c b/tests/core/oid.c deleted file mode 100644 index 894feadf6..000000000 --- a/tests/core/oid.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "clar_libgit2.h" -#include "oid.h" - -static git_oid id; -static git_oid idp; -static git_oid idm; -const char *str_oid = "ae90f12eea699729ed24555e40b9fd669da12a12"; -const char *str_oid_p = "ae90f12eea699729ed"; -const char *str_oid_m = "ae90f12eea699729ed24555e40b9fd669da12a12THIS IS EXTRA TEXT THAT SHOULD GET IGNORED"; - -void test_core_oid__initialize(void) -{ - cl_git_pass(git_oid_fromstr(&id, str_oid)); - cl_git_pass(git_oid_fromstrp(&idp, str_oid_p)); - cl_git_fail(git_oid_fromstrp(&idm, str_oid_m)); -} - -void test_core_oid__streq(void) -{ - cl_assert_equal_i(0, git_oid_streq(&id, str_oid)); - cl_assert_equal_i(-1, git_oid_streq(&id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); - - cl_assert_equal_i(-1, git_oid_streq(&id, "deadbeef")); - cl_assert_equal_i(-1, git_oid_streq(&id, "I'm not an oid.... :)")); - - cl_assert_equal_i(0, git_oid_streq(&idp, "ae90f12eea699729ed0000000000000000000000")); - cl_assert_equal_i(0, git_oid_streq(&idp, "ae90f12eea699729ed")); - cl_assert_equal_i(-1, git_oid_streq(&idp, "ae90f12eea699729ed1")); - cl_assert_equal_i(-1, git_oid_streq(&idp, "ae90f12eea699729ec")); - cl_assert_equal_i(-1, git_oid_streq(&idp, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); - - cl_assert_equal_i(-1, git_oid_streq(&idp, "deadbeef")); - cl_assert_equal_i(-1, git_oid_streq(&idp, "I'm not an oid.... :)")); -} - -void test_core_oid__strcmp(void) -{ - cl_assert_equal_i(0, git_oid_strcmp(&id, str_oid)); - cl_assert(git_oid_strcmp(&id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") < 0); - - cl_assert(git_oid_strcmp(&id, "deadbeef") < 0); - cl_assert_equal_i(-1, git_oid_strcmp(&id, "I'm not an oid.... :)")); - - cl_assert_equal_i(0, git_oid_strcmp(&idp, "ae90f12eea699729ed0000000000000000000000")); - cl_assert_equal_i(0, git_oid_strcmp(&idp, "ae90f12eea699729ed")); - cl_assert(git_oid_strcmp(&idp, "ae90f12eea699729ed1") < 0); - cl_assert(git_oid_strcmp(&idp, "ae90f12eea699729ec") > 0); - cl_assert(git_oid_strcmp(&idp, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") < 0); - - cl_assert(git_oid_strcmp(&idp, "deadbeef") < 0); - cl_assert_equal_i(-1, git_oid_strcmp(&idp, "I'm not an oid.... :)")); -} - -void test_core_oid__ncmp(void) -{ - cl_assert(!git_oid_ncmp(&id, &idp, 0)); - cl_assert(!git_oid_ncmp(&id, &idp, 1)); - cl_assert(!git_oid_ncmp(&id, &idp, 2)); - cl_assert(!git_oid_ncmp(&id, &idp, 17)); - cl_assert(!git_oid_ncmp(&id, &idp, 18)); - cl_assert(git_oid_ncmp(&id, &idp, 19)); - cl_assert(git_oid_ncmp(&id, &idp, 40)); - cl_assert(git_oid_ncmp(&id, &idp, 41)); - cl_assert(git_oid_ncmp(&id, &idp, 42)); - - cl_assert(!git_oid_ncmp(&id, &id, 0)); - cl_assert(!git_oid_ncmp(&id, &id, 1)); - cl_assert(!git_oid_ncmp(&id, &id, 39)); - cl_assert(!git_oid_ncmp(&id, &id, 40)); - cl_assert(!git_oid_ncmp(&id, &id, 41)); -} - -void test_core_oid__is_hexstr(void) -{ - cl_assert(git_oid__is_hexstr("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); - cl_assert(!git_oid__is_hexstr("deadbeefdeadbeef")); - cl_assert(!git_oid__is_hexstr("zeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); - cl_assert(!git_oid__is_hexstr("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef1")); -} diff --git a/tests/core/oidmap.c b/tests/core/oidmap.c deleted file mode 100644 index 7f98287a6..000000000 --- a/tests/core/oidmap.c +++ /dev/null @@ -1,127 +0,0 @@ -#include "clar_libgit2.h" -#include "oidmap.h" - -static struct { - git_oid oid; - size_t extra; -} test_oids[0x0FFF]; - -static git_oidmap *g_map; - -void test_core_oidmap__initialize(void) -{ - uint32_t i, j; - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { - uint32_t segment = i / 8; - int modi = i - (segment * 8); - - test_oids[i].extra = i; - - for (j = 0; j < GIT_OID_RAWSZ / 4; ++j) { - test_oids[i].oid.id[j * 4 ] = (unsigned char)modi; - test_oids[i].oid.id[j * 4 + 1] = (unsigned char)(modi >> 8); - test_oids[i].oid.id[j * 4 + 2] = (unsigned char)(modi >> 16); - test_oids[i].oid.id[j * 4 + 3] = (unsigned char)(modi >> 24); - } - - test_oids[i].oid.id[ 8] = (unsigned char)i; - test_oids[i].oid.id[ 9] = (unsigned char)(i >> 8); - test_oids[i].oid.id[10] = (unsigned char)(i >> 16); - test_oids[i].oid.id[11] = (unsigned char)(i >> 24); - } - - cl_git_pass(git_oidmap_new(&g_map)); -} - -void test_core_oidmap__cleanup(void) -{ - git_oidmap_free(g_map); -} - -void test_core_oidmap__basic(void) -{ - size_t i; - - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { - cl_assert(!git_oidmap_exists(g_map, &test_oids[i].oid)); - cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); - } - - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { - cl_assert(git_oidmap_exists(g_map, &test_oids[i].oid)); - cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[i].oid), &test_oids[i]); - } -} - -void test_core_oidmap__hash_collision(void) -{ - size_t i; - - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { - cl_assert(!git_oidmap_exists(g_map, &test_oids[i].oid)); - cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); - } - - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { - cl_assert(git_oidmap_exists(g_map, &test_oids[i].oid)); - cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[i].oid), &test_oids[i]); - } -} - -void test_core_oidmap__get_succeeds_with_existing_keys(void) -{ - size_t i; - - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) - cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); - - for (i = 0; i < ARRAY_SIZE(test_oids); ++i) - cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[i].oid), &test_oids[i]); -} - -void test_core_oidmap__get_fails_with_nonexisting_key(void) -{ - size_t i; - - /* Do _not_ add last OID to verify that we cannot look it up */ - for (i = 0; i < ARRAY_SIZE(test_oids) - 1; ++i) - cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); - - cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[ARRAY_SIZE(test_oids) - 1].oid), NULL); -} - -void test_core_oidmap__setting_oid_persists(void) -{ - git_oid oids[] = { - {{ 0x01 }}, - {{ 0x02 }}, - {{ 0x03 }} - }; - - cl_git_pass(git_oidmap_set(g_map, &oids[0], "one")); - cl_git_pass(git_oidmap_set(g_map, &oids[1], "two")); - cl_git_pass(git_oidmap_set(g_map, &oids[2], "three")); - - cl_assert_equal_s(git_oidmap_get(g_map, &oids[0]), "one"); - cl_assert_equal_s(git_oidmap_get(g_map, &oids[1]), "two"); - cl_assert_equal_s(git_oidmap_get(g_map, &oids[2]), "three"); -} - -void test_core_oidmap__setting_existing_key_updates(void) -{ - git_oid oids[] = { - {{ 0x01 }}, - {{ 0x02 }}, - {{ 0x03 }} - }; - - cl_git_pass(git_oidmap_set(g_map, &oids[0], "one")); - cl_git_pass(git_oidmap_set(g_map, &oids[1], "two")); - cl_git_pass(git_oidmap_set(g_map, &oids[2], "three")); - cl_assert_equal_i(git_oidmap_size(g_map), 3); - - cl_git_pass(git_oidmap_set(g_map, &oids[1], "other")); - cl_assert_equal_i(git_oidmap_size(g_map), 3); - - cl_assert_equal_s(git_oidmap_get(g_map, &oids[1]), "other"); -} diff --git a/tests/core/opts.c b/tests/core/opts.c deleted file mode 100644 index e8f65d510..000000000 --- a/tests/core/opts.c +++ /dev/null @@ -1,71 +0,0 @@ -#include "clar_libgit2.h" -#include "cache.h" - -void test_core_opts__cleanup(void) -{ - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, NULL, 0)); -} - -void test_core_opts__readwrite(void) -{ - size_t old_val = 0; - size_t new_val = 0; - - git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &old_val); - git_libgit2_opts(GIT_OPT_SET_MWINDOW_SIZE, (size_t)1234); - git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &new_val); - - cl_assert(new_val == 1234); - - git_libgit2_opts(GIT_OPT_SET_MWINDOW_SIZE, old_val); - git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &new_val); - - cl_assert(new_val == old_val); -} - -void test_core_opts__invalid_option(void) -{ - cl_git_fail(git_libgit2_opts(-1, "foobar")); -} - -void test_core_opts__extensions_query(void) -{ - git_strarray out = { 0 }; - - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - - cl_assert_equal_sz(out.count, 1); - cl_assert_equal_s("noop", out.strings[0]); - - git_strarray_dispose(&out); -} - -void test_core_opts__extensions_add(void) -{ - const char *in[] = { "foo" }; - git_strarray out = { 0 }; - - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - - cl_assert_equal_sz(out.count, 2); - cl_assert_equal_s("noop", out.strings[0]); - cl_assert_equal_s("foo", out.strings[1]); - - git_strarray_dispose(&out); -} - -void test_core_opts__extensions_remove(void) -{ - const char *in[] = { "bar", "!negate", "!noop", "baz" }; - git_strarray out = { 0 }; - - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); - - cl_assert_equal_sz(out.count, 2); - cl_assert_equal_s("bar", out.strings[0]); - cl_assert_equal_s("baz", out.strings[1]); - - git_strarray_dispose(&out); -} diff --git a/tests/core/path.c b/tests/core/path.c deleted file mode 100644 index a0ae77f1c..000000000 --- a/tests/core/path.c +++ /dev/null @@ -1,739 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "fs_path.h" - -static char *path_save; - -void test_core_path__initialize(void) -{ - path_save = cl_getenv("PATH"); -} - -void test_core_path__cleanup(void) -{ - cl_setenv("PATH", path_save); - git__free(path_save); - path_save = NULL; -} - -static void -check_dirname(const char *A, const char *B) -{ - git_str dir = GIT_STR_INIT; - char *dir2; - - cl_assert(git_fs_path_dirname_r(&dir, A) >= 0); - cl_assert_equal_s(B, dir.ptr); - git_str_dispose(&dir); - - cl_assert((dir2 = git_fs_path_dirname(A)) != NULL); - cl_assert_equal_s(B, dir2); - git__free(dir2); -} - -static void -check_basename(const char *A, const char *B) -{ - git_str base = GIT_STR_INIT; - char *base2; - - cl_assert(git_fs_path_basename_r(&base, A) >= 0); - cl_assert_equal_s(B, base.ptr); - git_str_dispose(&base); - - cl_assert((base2 = git_fs_path_basename(A)) != NULL); - cl_assert_equal_s(B, base2); - git__free(base2); -} - -static void -check_joinpath(const char *path_a, const char *path_b, const char *expected_path) -{ - git_str joined_path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&joined_path, path_a, path_b)); - cl_assert_equal_s(expected_path, joined_path.ptr); - - git_str_dispose(&joined_path); -} - -static void -check_joinpath_n( - const char *path_a, - const char *path_b, - const char *path_c, - const char *path_d, - const char *expected_path) -{ - git_str joined_path = GIT_STR_INIT; - - cl_git_pass(git_str_join_n(&joined_path, '/', 4, - path_a, path_b, path_c, path_d)); - cl_assert_equal_s(expected_path, joined_path.ptr); - - git_str_dispose(&joined_path); -} - -static void check_setenv(const char* name, const char* value) -{ - char* check; - - cl_git_pass(cl_setenv(name, value)); - check = cl_getenv(name); - - if (value) - cl_assert_equal_s(value, check); - else - cl_assert(check == NULL); - - git__free(check); -} - -/* get the dirname of a path */ -void test_core_path__00_dirname(void) -{ - check_dirname(NULL, "."); - check_dirname("", "."); - check_dirname("a", "."); - check_dirname("/", "/"); - check_dirname("/usr", "/"); - check_dirname("/usr/", "/"); - check_dirname("/usr/lib", "/usr"); - check_dirname("/usr/lib/", "/usr"); - check_dirname("/usr/lib//", "/usr"); - check_dirname("usr/lib", "usr"); - check_dirname("usr/lib/", "usr"); - check_dirname("usr/lib//", "usr"); - check_dirname(".git/", "."); - - check_dirname(REP16("/abc"), REP15("/abc")); - -#ifdef GIT_WIN32 - check_dirname("C:/", "C:/"); - check_dirname("C:", "C:/"); - check_dirname("C:/path/", "C:/"); - check_dirname("C:/path", "C:/"); - check_dirname("//computername/", "//computername/"); - check_dirname("//computername", "//computername/"); - check_dirname("//computername/path/", "//computername/"); - check_dirname("//computername/path", "//computername/"); - check_dirname("//computername/sub/path/", "//computername/sub"); - check_dirname("//computername/sub/path", "//computername/sub"); -#endif -} - -/* get the base name of a path */ -void test_core_path__01_basename(void) -{ - check_basename(NULL, "."); - check_basename("", "."); - check_basename("a", "a"); - check_basename("/", "/"); - check_basename("/usr", "usr"); - check_basename("/usr/", "usr"); - check_basename("/usr/lib", "lib"); - check_basename("/usr/lib//", "lib"); - check_basename("usr/lib", "lib"); - - check_basename(REP16("/abc"), "abc"); - check_basename(REP1024("/abc"), "abc"); -} - -/* properly join path components */ -void test_core_path__05_joins(void) -{ - check_joinpath("", "", ""); - check_joinpath("", "a", "a"); - check_joinpath("", "/a", "/a"); - check_joinpath("a", "", "a/"); - check_joinpath("a", "/", "a/"); - check_joinpath("a", "b", "a/b"); - check_joinpath("/", "a", "/a"); - check_joinpath("/", "", "/"); - check_joinpath("/a", "/b", "/a/b"); - check_joinpath("/a", "/b/", "/a/b/"); - check_joinpath("/a/", "b/", "/a/b/"); - check_joinpath("/a/", "/b/", "/a/b/"); - - check_joinpath("/abcd", "/defg", "/abcd/defg"); - check_joinpath("/abcd", "/defg/", "/abcd/defg/"); - check_joinpath("/abcd/", "defg/", "/abcd/defg/"); - check_joinpath("/abcd/", "/defg/", "/abcd/defg/"); - - check_joinpath("/abcdefgh", "/12345678", "/abcdefgh/12345678"); - check_joinpath("/abcdefgh", "/12345678/", "/abcdefgh/12345678/"); - check_joinpath("/abcdefgh/", "12345678/", "/abcdefgh/12345678/"); - - check_joinpath(REP1024("aaaa"), "", REP1024("aaaa") "/"); - check_joinpath(REP1024("aaaa/"), "", REP1024("aaaa/")); - check_joinpath(REP1024("/aaaa"), "", REP1024("/aaaa") "/"); - - check_joinpath(REP1024("aaaa"), REP1024("bbbb"), - REP1024("aaaa") "/" REP1024("bbbb")); - check_joinpath(REP1024("/aaaa"), REP1024("/bbbb"), - REP1024("/aaaa") REP1024("/bbbb")); -} - -/* properly join path components for more than one path */ -void test_core_path__06_long_joins(void) -{ - check_joinpath_n("", "", "", "", ""); - check_joinpath_n("", "a", "", "", "a/"); - check_joinpath_n("a", "", "", "", "a/"); - check_joinpath_n("", "", "", "a", "a"); - check_joinpath_n("a", "b", "", "/c/d/", "a/b/c/d/"); - check_joinpath_n("a", "b", "", "/c/d", "a/b/c/d"); - check_joinpath_n("abcd", "efgh", "ijkl", "mnop", "abcd/efgh/ijkl/mnop"); - check_joinpath_n("abcd/", "efgh/", "ijkl/", "mnop/", "abcd/efgh/ijkl/mnop/"); - check_joinpath_n("/abcd/", "/efgh/", "/ijkl/", "/mnop/", "/abcd/efgh/ijkl/mnop/"); - - check_joinpath_n(REP1024("a"), REP1024("b"), REP1024("c"), REP1024("d"), - REP1024("a") "/" REP1024("b") "/" - REP1024("c") "/" REP1024("d")); - check_joinpath_n(REP1024("/a"), REP1024("/b"), REP1024("/c"), REP1024("/d"), - REP1024("/a") REP1024("/b") - REP1024("/c") REP1024("/d")); -} - - -static void -check_path_to_dir( - const char* path, - const char* expected) -{ - git_str tgt = GIT_STR_INIT; - - git_str_sets(&tgt, path); - cl_git_pass(git_fs_path_to_dir(&tgt)); - cl_assert_equal_s(expected, tgt.ptr); - - git_str_dispose(&tgt); -} - -static void -check_string_to_dir( - const char* path, - size_t maxlen, - const char* expected) -{ - size_t len = strlen(path); - char *buf = git__malloc(len + 2); - cl_assert(buf); - - strncpy(buf, path, len + 2); - - git_fs_path_string_to_dir(buf, maxlen); - - cl_assert_equal_s(expected, buf); - - git__free(buf); -} - -/* convert paths to dirs */ -void test_core_path__07_path_to_dir(void) -{ - check_path_to_dir("", ""); - check_path_to_dir(".", "./"); - check_path_to_dir("./", "./"); - check_path_to_dir("a/", "a/"); - check_path_to_dir("ab", "ab/"); - /* make sure we try just under and just over an expansion that will - * require a realloc - */ - check_path_to_dir("abcdef", "abcdef/"); - check_path_to_dir("abcdefg", "abcdefg/"); - check_path_to_dir("abcdefgh", "abcdefgh/"); - check_path_to_dir("abcdefghi", "abcdefghi/"); - check_path_to_dir(REP1024("abcd") "/", REP1024("abcd") "/"); - check_path_to_dir(REP1024("abcd"), REP1024("abcd") "/"); - - check_string_to_dir("", 1, ""); - check_string_to_dir(".", 1, "."); - check_string_to_dir(".", 2, "./"); - check_string_to_dir(".", 3, "./"); - check_string_to_dir("abcd", 3, "abcd"); - check_string_to_dir("abcd", 4, "abcd"); - check_string_to_dir("abcd", 5, "abcd/"); - check_string_to_dir("abcd", 6, "abcd/"); -} - -/* join path to itself */ -void test_core_path__08_self_join(void) -{ - git_str path = GIT_STR_INIT; - size_t asize = 0; - - asize = path.asize; - cl_git_pass(git_str_sets(&path, "/foo")); - cl_assert_equal_s(path.ptr, "/foo"); - cl_assert(asize < path.asize); - - asize = path.asize; - cl_git_pass(git_str_joinpath(&path, path.ptr, "this is a new string")); - cl_assert_equal_s(path.ptr, "/foo/this is a new string"); - cl_assert(asize < path.asize); - - asize = path.asize; - cl_git_pass(git_str_joinpath(&path, path.ptr, "/grow the buffer, grow the buffer, grow the buffer")); - cl_assert_equal_s(path.ptr, "/foo/this is a new string/grow the buffer, grow the buffer, grow the buffer"); - cl_assert(asize < path.asize); - - git_str_dispose(&path); - cl_git_pass(git_str_sets(&path, "/foo/bar")); - - cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "baz")); - cl_assert_equal_s(path.ptr, "/bar/baz"); - - asize = path.asize; - cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "somethinglongenoughtorealloc")); - cl_assert_equal_s(path.ptr, "/baz/somethinglongenoughtorealloc"); - cl_assert(asize < path.asize); - - git_str_dispose(&path); -} - -static void check_percent_decoding(const char *expected_result, const char *input) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git__percent_decode(&buf, input)); - cl_assert_equal_s(expected_result, git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -void test_core_path__09_percent_decode(void) -{ - check_percent_decoding("abcd", "abcd"); - check_percent_decoding("a2%", "a2%"); - check_percent_decoding("a2%3", "a2%3"); - check_percent_decoding("a2%%3", "a2%%3"); - check_percent_decoding("a2%3z", "a2%3z"); - check_percent_decoding("a,", "a%2c"); - check_percent_decoding("a21", "a2%31"); - check_percent_decoding("a2%1", "a2%%31"); - check_percent_decoding("a bc ", "a%20bc%20"); - check_percent_decoding("Vicent Mart" "\355", "Vicent%20Mart%ED"); -} - -static void check_fromurl(const char *expected_result, const char *input, int should_fail) -{ - git_str buf = GIT_STR_INIT; - - assert(should_fail || expected_result); - - if (!should_fail) { - cl_git_pass(git_fs_path_fromurl(&buf, input)); - cl_assert_equal_s(expected_result, git_str_cstr(&buf)); - } else - cl_git_fail(git_fs_path_fromurl(&buf, input)); - - git_str_dispose(&buf); -} - -#ifdef GIT_WIN32 -#define ABS_PATH_MARKER "" -#else -#define ABS_PATH_MARKER "/" -#endif - -void test_core_path__10_fromurl(void) -{ - /* Failing cases */ - check_fromurl(NULL, "a", 1); - check_fromurl(NULL, "http:///c:/Temp%20folder/note.txt", 1); - check_fromurl(NULL, "file://c:/Temp%20folder/note.txt", 1); - check_fromurl(NULL, "file:////c:/Temp%20folder/note.txt", 1); - check_fromurl(NULL, "file:///", 1); - check_fromurl(NULL, "file:////", 1); - check_fromurl(NULL, "file://servername/c:/Temp%20folder/note.txt", 1); - - /* Passing cases */ - check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file:///c:/Temp%20folder/note.txt", 0); - check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file://localhost/c:/Temp%20folder/note.txt", 0); - check_fromurl(ABS_PATH_MARKER "c:/Temp+folder/note.txt", "file:///c:/Temp+folder/note.txt", 0); - check_fromurl(ABS_PATH_MARKER "a", "file:///a", 0); -} - -typedef struct { - int expect_idx; - int cancel_after; - char **expect; -} check_walkup_info; - -#define CANCEL_VALUE 1234 - -static int check_one_walkup_step(void *ref, const char *path) -{ - check_walkup_info *info = (check_walkup_info *)ref; - - if (!info->cancel_after) { - cl_assert_equal_s(info->expect[info->expect_idx], "[CANCEL]"); - return CANCEL_VALUE; - } - info->cancel_after--; - - cl_assert(info->expect[info->expect_idx] != NULL); - cl_assert_equal_s(info->expect[info->expect_idx], path); - info->expect_idx++; - - return 0; -} - -void test_core_path__11_walkup(void) -{ - git_str p = GIT_STR_INIT; - - char *expect[] = { - /* 1 */ "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 2 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 3 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 4 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 5 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, - /* 6 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, - /* 7 */ "this_is_a_path", "", NULL, - /* 8 */ "this_is_a_path/", "", NULL, - /* 9 */ "///a///b///c///d///e///", "///a///b///c///d///", "///a///b///c///", "///a///b///", "///a///", "///", NULL, - /* 10 */ "a/b/c/", "a/b/", "a/", "", NULL, - /* 11 */ "a/b/c", "a/b/", "a/", "", NULL, - /* 12 */ "a/b/c/", "a/b/", "a/", NULL, - /* 13 */ "", NULL, - /* 14 */ "/", NULL, - /* 15 */ NULL - }; - - char *root[] = { - /* 1 */ NULL, - /* 2 */ NULL, - /* 3 */ "/", - /* 4 */ "", - /* 5 */ "/a/b", - /* 6 */ "/a/b/", - /* 7 */ NULL, - /* 8 */ NULL, - /* 9 */ NULL, - /* 10 */ NULL, - /* 11 */ NULL, - /* 12 */ "a/", - /* 13 */ NULL, - /* 14 */ NULL, - }; - - int i, j; - check_walkup_info info; - - info.expect = expect; - info.cancel_after = -1; - - for (i = 0, j = 0; expect[i] != NULL; i++, j++) { - - git_str_sets(&p, expect[i]); - - info.expect_idx = i; - cl_git_pass( - git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) - ); - - cl_assert_equal_s(p.ptr, expect[i]); - cl_assert(expect[info.expect_idx] == NULL); - i = info.expect_idx; - } - - git_str_dispose(&p); -} - -void test_core_path__11a_walkup_cancel(void) -{ - git_str p = GIT_STR_INIT; - int cancel[] = { 3, 2, 1, 0 }; - char *expect[] = { - "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "[CANCEL]", NULL, - "/a/b/c/d/e", "/a/b/c/d/", "[CANCEL]", NULL, - "/a/b/c/d/e", "[CANCEL]", NULL, - "[CANCEL]", NULL, - NULL - }; - char *root[] = { NULL, NULL, "/", "", NULL }; - int i, j; - check_walkup_info info; - - info.expect = expect; - - for (i = 0, j = 0; expect[i] != NULL; i++, j++) { - - git_str_sets(&p, expect[i]); - - info.cancel_after = cancel[j]; - info.expect_idx = i; - - cl_assert_equal_i( - CANCEL_VALUE, - git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) - ); - - /* skip to next run of expectations */ - while (expect[i] != NULL) i++; - } - - git_str_dispose(&p); -} - -void test_core_path__12_offset_to_path_root(void) -{ - cl_assert(git_fs_path_root("non/rooted/path") == -1); - cl_assert(git_fs_path_root("/rooted/path") == 0); - -#ifdef GIT_WIN32 - /* Windows specific tests */ - cl_assert(git_fs_path_root("C:non/rooted/path") == -1); - cl_assert(git_fs_path_root("C:/rooted/path") == 2); - cl_assert(git_fs_path_root("//computername/sharefolder/resource") == 14); - cl_assert(git_fs_path_root("//computername/sharefolder") == 14); - cl_assert(git_fs_path_root("//computername") == -1); -#endif -} - -#define NON_EXISTING_FILEPATH "i_hope_i_do_not_exist" - -void test_core_path__13_cannot_prettify_a_non_existing_file(void) -{ - git_str p = GIT_STR_INIT; - - cl_assert_equal_b(git_fs_path_exists(NON_EXISTING_FILEPATH), false); - cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH, NULL)); - cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH "/so-do-i", NULL)); - - git_str_dispose(&p); -} - -void test_core_path__14_apply_relative(void) -{ - git_str p = GIT_STR_INIT; - - cl_git_pass(git_str_sets(&p, "/this/is/a/base")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../test")); - cl_assert_equal_s("/this/is/a/test", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../the/./end")); - cl_assert_equal_s("/this/is/the/end", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "./of/this/../the/string")); - cl_assert_equal_s("/this/is/the/end/of/the/string", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../../../../..")); - cl_assert_equal_s("/this/", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../")); - cl_assert_equal_s("/", p.ptr); - - cl_git_fail(git_fs_path_apply_relative(&p, "../../..")); - - - cl_git_pass(git_str_sets(&p, "d:/another/test")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../..")); - cl_assert_equal_s("d:/", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "from/here/to/../and/./back/.")); - cl_assert_equal_s("d:/from/here/and/back/", p.ptr); - - - cl_git_pass(git_str_sets(&p, "https://my.url.com/test.git")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../another.git")); - cl_assert_equal_s("https://my.url.com/another.git", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../full/path/url.patch")); - cl_assert_equal_s("https://my.url.com/full/path/url.patch", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "..")); - cl_assert_equal_s("https://my.url.com/full/path/", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../../")); - cl_assert_equal_s("https://", p.ptr); - - - cl_git_pass(git_str_sets(&p, "../../this/is/relative")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../preserves/the/prefix")); - cl_assert_equal_s("../../this/preserves/the/prefix", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../../../that")); - cl_assert_equal_s("../../that", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../there")); - cl_assert_equal_s("../../there", p.ptr); - git_str_dispose(&p); -} - -static void assert_resolve_relative( - git_str *buf, const char *expected, const char *path) -{ - cl_git_pass(git_str_sets(buf, path)); - cl_git_pass(git_fs_path_resolve_relative(buf, 0)); - cl_assert_equal_s(expected, buf->ptr); -} - -void test_core_path__15_resolve_relative(void) -{ - git_str buf = GIT_STR_INIT; - - assert_resolve_relative(&buf, "", ""); - assert_resolve_relative(&buf, "", "."); - assert_resolve_relative(&buf, "", "./"); - assert_resolve_relative(&buf, "..", ".."); - assert_resolve_relative(&buf, "../", "../"); - assert_resolve_relative(&buf, "..", "./.."); - assert_resolve_relative(&buf, "../", "./../"); - assert_resolve_relative(&buf, "../", "../."); - assert_resolve_relative(&buf, "../", ".././"); - assert_resolve_relative(&buf, "../..", "../.."); - assert_resolve_relative(&buf, "../../", "../../"); - - assert_resolve_relative(&buf, "/", "/"); - assert_resolve_relative(&buf, "/", "/."); - - assert_resolve_relative(&buf, "", "a/.."); - assert_resolve_relative(&buf, "", "a/../"); - assert_resolve_relative(&buf, "", "a/../."); - - assert_resolve_relative(&buf, "/a", "/a"); - assert_resolve_relative(&buf, "/a/", "/a/."); - assert_resolve_relative(&buf, "/", "/a/../"); - assert_resolve_relative(&buf, "/", "/a/../."); - assert_resolve_relative(&buf, "/", "/a/.././"); - - assert_resolve_relative(&buf, "a", "a"); - assert_resolve_relative(&buf, "a/", "a/"); - assert_resolve_relative(&buf, "a/", "a/."); - assert_resolve_relative(&buf, "a/", "a/./"); - - assert_resolve_relative(&buf, "a/b", "a//b"); - assert_resolve_relative(&buf, "a/b/c", "a/b/c"); - assert_resolve_relative(&buf, "b/c", "./b/c"); - assert_resolve_relative(&buf, "a/c", "a/./c"); - assert_resolve_relative(&buf, "a/b/", "a/b/."); - - assert_resolve_relative(&buf, "/a/b/c", "///a/b/c"); - assert_resolve_relative(&buf, "/", "////"); - assert_resolve_relative(&buf, "/a", "///a"); - assert_resolve_relative(&buf, "/", "///."); - assert_resolve_relative(&buf, "/", "///a/.."); - - assert_resolve_relative(&buf, "../../path", "../../test//../././path"); - assert_resolve_relative(&buf, "../d", "a/b/../../../c/../d"); - - cl_git_pass(git_str_sets(&buf, "/..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/./..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/.//..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/../.")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/../.././../a")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "////..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - /* things that start with Windows network paths */ -#ifdef GIT_WIN32 - assert_resolve_relative(&buf, "//a/b/c", "//a/b/c"); - assert_resolve_relative(&buf, "//a/", "//a/b/.."); - assert_resolve_relative(&buf, "//a/b/c", "//a/Q/../b/x/y/../../c"); - - cl_git_pass(git_str_sets(&buf, "//a/b/../..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); -#else - assert_resolve_relative(&buf, "/a/b/c", "//a/b/c"); - assert_resolve_relative(&buf, "/a/", "//a/b/.."); - assert_resolve_relative(&buf, "/a/b/c", "//a/Q/../b/x/y/../../c"); - assert_resolve_relative(&buf, "/", "//a/b/../.."); -#endif - - git_str_dispose(&buf); -} - -#define assert_common_dirlen(i, p, q) \ - cl_assert_equal_i((i), git_fs_path_common_dirlen((p), (q))); - -void test_core_path__16_resolve_relative(void) -{ - assert_common_dirlen(0, "", ""); - assert_common_dirlen(0, "", "bar.txt"); - assert_common_dirlen(0, "foo.txt", "bar.txt"); - assert_common_dirlen(0, "foo.txt", ""); - assert_common_dirlen(0, "foo/bar.txt", "bar/foo.txt"); - assert_common_dirlen(0, "foo/bar.txt", "../foo.txt"); - - assert_common_dirlen(1, "/one.txt", "/two.txt"); - assert_common_dirlen(4, "foo/one.txt", "foo/two.txt"); - assert_common_dirlen(5, "/foo/one.txt", "/foo/two.txt"); - - assert_common_dirlen(6, "a/b/c/foo.txt", "a/b/c/d/e/bar.txt"); - assert_common_dirlen(7, "/a/b/c/foo.txt", "/a/b/c/d/e/bar.txt"); -} - -static void fix_path(git_str *s) -{ -#ifndef GIT_WIN32 - GIT_UNUSED(s); -#else - char* c; - - for (c = s->ptr; *c; c++) { - if (*c == '/') - *c = '\\'; - } -#endif -} - -void test_core_path__find_exe_in_path(void) -{ - char *orig_path; - git_str sandbox_path = GIT_STR_INIT; - git_str new_path = GIT_STR_INIT, full_path = GIT_STR_INIT, - dummy_path = GIT_STR_INIT; - -#ifdef GIT_WIN32 - static const char *bogus_path_1 = "c:\\does\\not\\exist\\"; - static const char *bogus_path_2 = "e:\\non\\existent"; -#else - static const char *bogus_path_1 = "/this/path/does/not/exist/"; - static const char *bogus_path_2 = "/non/existent"; -#endif - - orig_path = cl_getenv("PATH"); - - git_str_puts(&sandbox_path, clar_sandbox_path()); - git_str_joinpath(&dummy_path, sandbox_path.ptr, "dummmmmmmy_libgit2_file"); - cl_git_rewritefile(dummy_path.ptr, "this is a dummy file"); - - fix_path(&sandbox_path); - fix_path(&dummy_path); - - cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s", - bogus_path_1, GIT_PATH_LIST_SEPARATOR, - orig_path, GIT_PATH_LIST_SEPARATOR, - sandbox_path.ptr, GIT_PATH_LIST_SEPARATOR, - bogus_path_2)); - - check_setenv("PATH", new_path.ptr); - - cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "this_file_does_not_exist")); - cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file")); - - cl_assert_equal_s(full_path.ptr, dummy_path.ptr); - - git_str_dispose(&full_path); - git_str_dispose(&new_path); - git_str_dispose(&dummy_path); - git_str_dispose(&sandbox_path); - git__free(orig_path); -} diff --git a/tests/core/pool.c b/tests/core/pool.c deleted file mode 100644 index b07da0abd..000000000 --- a/tests/core/pool.c +++ /dev/null @@ -1,92 +0,0 @@ -#include "clar_libgit2.h" -#include "pool.h" -#include "git2/oid.h" - -void test_core_pool__0(void) -{ - int i; - git_pool p; - void *ptr; - - git_pool_init(&p, 1); - - for (i = 1; i < 10000; i *= 2) { - ptr = git_pool_malloc(&p, i); - cl_assert(ptr != NULL); - cl_assert(git_pool__ptr_in_pool(&p, ptr)); - cl_assert(!git_pool__ptr_in_pool(&p, &i)); - } - - git_pool_clear(&p); -} - -void test_core_pool__1(void) -{ - int i; - git_pool p; - - git_pool_init(&p, 1); - p.page_size = 4000; - - for (i = 2010; i > 0; i--) - cl_assert(git_pool_malloc(&p, i) != NULL); - -#ifndef GIT_DEBUG_POOL - /* with fixed page size, allocation must end up with these values */ - cl_assert_equal_i(591, git_pool__open_pages(&p)); -#endif - git_pool_clear(&p); - - git_pool_init(&p, 1); - p.page_size = 4120; - - for (i = 2010; i > 0; i--) - cl_assert(git_pool_malloc(&p, i) != NULL); - -#ifndef GIT_DEBUG_POOL - /* with fixed page size, allocation must end up with these values */ - cl_assert_equal_i(sizeof(void *) == 8 ? 575 : 573, git_pool__open_pages(&p)); -#endif - git_pool_clear(&p); -} - -static char to_hex[] = "0123456789abcdef"; - -void test_core_pool__2(void) -{ - git_pool p; - char oid_hex[GIT_OID_HEXSZ]; - git_oid *oid; - int i, j; - - memset(oid_hex, '0', sizeof(oid_hex)); - - git_pool_init(&p, sizeof(git_oid)); - p.page_size = 4000; - - for (i = 1000; i < 10000; i++) { - oid = git_pool_malloc(&p, 1); - cl_assert(oid != NULL); - - for (j = 0; j < 8; j++) - oid_hex[j] = to_hex[(i >> (4 * j)) & 0x0f]; - cl_git_pass(git_oid_fromstr(oid, oid_hex)); - } - -#ifndef GIT_DEBUG_POOL - /* with fixed page size, allocation must end up with these values */ - cl_assert_equal_i(sizeof(void *) == 8 ? 55 : 45, git_pool__open_pages(&p)); -#endif - git_pool_clear(&p); -} - -void test_core_pool__strndup_limit(void) -{ - git_pool p; - - git_pool_init(&p, 1); - /* ensure 64 bit doesn't overflow */ - cl_assert(git_pool_strndup(&p, "foo", (size_t)-1) == NULL); - git_pool_clear(&p); -} - diff --git a/tests/core/posix.c b/tests/core/posix.c deleted file mode 100644 index cba312913..000000000 --- a/tests/core/posix.c +++ /dev/null @@ -1,238 +0,0 @@ -#ifndef _WIN32 -# include -# include -# include -#else -# include -# ifdef _MSC_VER -# pragma comment(lib, "ws2_32") -# endif -#endif - -#include "clar_libgit2.h" -#include "futils.h" -#include "posix.h" - -void test_core_posix__initialize(void) -{ -#ifdef GIT_WIN32 - /* on win32, the WSA context needs to be initialized - * before any socket calls can be performed */ - WSADATA wsd; - - cl_git_pass(WSAStartup(MAKEWORD(2,2), &wsd)); - cl_assert(LOBYTE(wsd.wVersion) == 2 && HIBYTE(wsd.wVersion) == 2); -#endif -} - -static bool supports_ipv6(void) -{ -#ifdef GIT_WIN32 - /* IPv6 is supported on Vista and newer */ - return git_has_win32_version(6, 0, 0); -#else - return 1; -#endif -} - -void test_core_posix__inet_pton(void) -{ - struct in_addr addr; - struct in6_addr addr6; - size_t i; - - struct in_addr_data { - const char *p; - const uint8_t n[4]; - }; - - struct in6_addr_data { - const char *p; - const uint8_t n[16]; - }; - - static struct in_addr_data in_addr_data[] = { - { "0.0.0.0", { 0, 0, 0, 0 } }, - { "10.42.101.8", { 10, 42, 101, 8 } }, - { "127.0.0.1", { 127, 0, 0, 1 } }, - { "140.177.10.12", { 140, 177, 10, 12 } }, - { "204.232.175.90", { 204, 232, 175, 90 } }, - { "255.255.255.255", { 255, 255, 255, 255 } }, - }; - - static struct in6_addr_data in6_addr_data[] = { - { "::", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { "::1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, - { "0:0:0:0:0:0:0:1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, - { "2001:db8:8714:3a90::12", { 0x20, 0x01, 0x0d, 0xb8, 0x87, 0x14, 0x3a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12 } }, - { "fe80::f8ba:c2d6:86be:3645", { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xba, 0xc2, 0xd6, 0x86, 0xbe, 0x36, 0x45 } }, - { "::ffff:204.152.189.116", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcc, 0x98, 0xbd, 0x74 } }, - }; - - /* Test some ipv4 addresses */ - for (i = 0; i < 6; i++) { - cl_assert(p_inet_pton(AF_INET, in_addr_data[i].p, &addr) == 1); - cl_assert(memcmp(&addr, in_addr_data[i].n, sizeof(struct in_addr)) == 0); - } - - /* Test some ipv6 addresses */ - if (supports_ipv6()) - { - for (i = 0; i < 6; i++) { - cl_assert(p_inet_pton(AF_INET6, in6_addr_data[i].p, &addr6) == 1); - cl_assert(memcmp(&addr6, in6_addr_data[i].n, sizeof(struct in6_addr)) == 0); - } - } - - /* Test some invalid strings */ - cl_assert(p_inet_pton(AF_INET, "", &addr) == 0); - cl_assert(p_inet_pton(AF_INET, "foo", &addr) == 0); - cl_assert(p_inet_pton(AF_INET, " 127.0.0.1", &addr) == 0); - cl_assert(p_inet_pton(AF_INET, "bar", &addr) == 0); - cl_assert(p_inet_pton(AF_INET, "10.foo.bar.1", &addr) == 0); - - /* Test unsupported address families */ - cl_git_fail(p_inet_pton(INT_MAX-1, "52.472", &addr)); - cl_assert_equal_i(EAFNOSUPPORT, errno); -} - -void test_core_posix__utimes(void) -{ - struct p_timeval times[2]; - struct stat st; - time_t curtime; - int fd; - - /* test p_utimes */ - times[0].tv_sec = 1234567890; - times[0].tv_usec = 0; - times[1].tv_sec = 1234567890; - times[1].tv_usec = 0; - - cl_git_mkfile("foo", "Dummy file."); - cl_must_pass(p_utimes("foo", times)); - - cl_must_pass(p_stat("foo", &st)); - cl_assert_equal_i(1234567890, st.st_atime); - cl_assert_equal_i(1234567890, st.st_mtime); - - - /* test p_futimes */ - times[0].tv_sec = 1414141414; - times[0].tv_usec = 0; - times[1].tv_sec = 1414141414; - times[1].tv_usec = 0; - - cl_must_pass(fd = p_open("foo", O_RDWR)); - cl_must_pass(p_futimes(fd, times)); - cl_must_pass(p_close(fd)); - - cl_must_pass(p_stat("foo", &st)); - cl_assert_equal_i(1414141414, st.st_atime); - cl_assert_equal_i(1414141414, st.st_mtime); - - - /* test p_utimes with current time, assume that - * it takes < 5 seconds to get the time...! - */ - cl_must_pass(p_utimes("foo", NULL)); - - curtime = time(NULL); - cl_must_pass(p_stat("foo", &st)); - cl_assert((st.st_atime - curtime) < 5); - cl_assert((st.st_mtime - curtime) < 5); - - cl_must_pass(p_unlink("foo")); -} - -void test_core_posix__unlink_removes_symlink(void) -{ - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - clar__skip(); - - cl_git_mkfile("file", "Dummy file."); - cl_git_pass(git_futils_mkdir("dir", 0777, 0)); - - cl_must_pass(p_symlink("file", "file-symlink")); - cl_must_pass(p_symlink("dir", "dir-symlink")); - - cl_must_pass(p_unlink("file-symlink")); - cl_must_pass(p_unlink("dir-symlink")); - - cl_assert(git_fs_path_exists("file")); - cl_assert(git_fs_path_exists("dir")); - - cl_must_pass(p_unlink("file")); - cl_must_pass(p_rmdir("dir")); -} - -void test_core_posix__symlink_resolves_to_correct_type(void) -{ - git_str contents = GIT_STR_INIT; - - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - clar__skip(); - - cl_must_pass(git_futils_mkdir("dir", 0777, 0)); - cl_must_pass(git_futils_mkdir("file", 0777, 0)); - cl_git_mkfile("dir/file", "symlink target"); - - cl_git_pass(p_symlink("file", "dir/link")); - - cl_git_pass(git_futils_readbuffer(&contents, "dir/file")); - cl_assert_equal_s(contents.ptr, "symlink target"); - - cl_must_pass(p_unlink("dir/link")); - cl_must_pass(p_unlink("dir/file")); - cl_must_pass(p_rmdir("dir")); - cl_must_pass(p_rmdir("file")); - - git_str_dispose(&contents); -} - -void test_core_posix__relative_symlink(void) -{ - git_str contents = GIT_STR_INIT; - - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - clar__skip(); - - cl_must_pass(git_futils_mkdir("dir", 0777, 0)); - cl_git_mkfile("file", "contents"); - cl_git_pass(p_symlink("../file", "dir/link")); - cl_git_pass(git_futils_readbuffer(&contents, "dir/link")); - cl_assert_equal_s(contents.ptr, "contents"); - - cl_must_pass(p_unlink("file")); - cl_must_pass(p_unlink("dir/link")); - cl_must_pass(p_rmdir("dir")); - - git_str_dispose(&contents); -} - -void test_core_posix__symlink_to_file_across_dirs(void) -{ - git_str contents = GIT_STR_INIT; - - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - clar__skip(); - - /* - * Create a relative symlink that points into another - * directory. This used to not work on Win32, where we - * forgot to convert directory separators to - * Windows-style ones. - */ - cl_must_pass(git_futils_mkdir("dir", 0777, 0)); - cl_git_mkfile("dir/target", "symlink target"); - cl_git_pass(p_symlink("dir/target", "link")); - - cl_git_pass(git_futils_readbuffer(&contents, "dir/target")); - cl_assert_equal_s(contents.ptr, "symlink target"); - - cl_must_pass(p_unlink("dir/target")); - cl_must_pass(p_unlink("link")); - cl_must_pass(p_rmdir("dir")); - - git_str_dispose(&contents); -} diff --git a/tests/core/pqueue.c b/tests/core/pqueue.c deleted file mode 100644 index 2b90f4172..000000000 --- a/tests/core/pqueue.c +++ /dev/null @@ -1,150 +0,0 @@ -#include "clar_libgit2.h" -#include "pqueue.h" - -static int cmp_ints(const void *v1, const void *v2) -{ - int i1 = *(int *)v1, i2 = *(int *)v2; - return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0; -} - -void test_core_pqueue__items_are_put_in_order(void) -{ - git_pqueue pq; - int i, vals[20]; - - cl_git_pass(git_pqueue_init(&pq, 0, 20, cmp_ints)); - - for (i = 0; i < 20; ++i) { - if (i < 10) - vals[i] = 10 - i; /* 10 down to 1 */ - else - vals[i] = i + 1; /* 11 up to 20 */ - - cl_git_pass(git_pqueue_insert(&pq, &vals[i])); - } - - cl_assert_equal_i(20, git_pqueue_size(&pq)); - - for (i = 1; i <= 20; ++i) { - void *p = git_pqueue_pop(&pq); - cl_assert(p); - cl_assert_equal_i(i, *(int *)p); - } - - cl_assert_equal_i(0, git_pqueue_size(&pq)); - - git_pqueue_free(&pq); -} - -void test_core_pqueue__interleave_inserts_and_pops(void) -{ - git_pqueue pq; - int chunk, v, i, vals[200]; - - cl_git_pass(git_pqueue_init(&pq, 0, 20, cmp_ints)); - - for (v = 0, chunk = 20; chunk <= 200; chunk += 20) { - /* push the next 20 */ - for (; v < chunk; ++v) { - vals[v] = (v & 1) ? 200 - v : v; - cl_git_pass(git_pqueue_insert(&pq, &vals[v])); - } - - /* pop the lowest 10 */ - for (i = 0; i < 10; ++i) - (void)git_pqueue_pop(&pq); - } - - cl_assert_equal_i(100, git_pqueue_size(&pq)); - - /* at this point, we've popped 0-99 */ - - for (v = 100; v < 200; ++v) { - void *p = git_pqueue_pop(&pq); - cl_assert(p); - cl_assert_equal_i(v, *(int *)p); - } - - cl_assert_equal_i(0, git_pqueue_size(&pq)); - - git_pqueue_free(&pq); -} - -void test_core_pqueue__max_heap_size(void) -{ - git_pqueue pq; - int i, vals[100]; - - cl_git_pass(git_pqueue_init(&pq, GIT_PQUEUE_FIXED_SIZE, 50, cmp_ints)); - - for (i = 0; i < 100; ++i) { - vals[i] = (i & 1) ? 100 - i : i; - cl_git_pass(git_pqueue_insert(&pq, &vals[i])); - } - - cl_assert_equal_i(50, git_pqueue_size(&pq)); - - for (i = 50; i < 100; ++i) { - void *p = git_pqueue_pop(&pq); - cl_assert(p); - cl_assert_equal_i(i, *(int *)p); - } - - cl_assert_equal_i(0, git_pqueue_size(&pq)); - - git_pqueue_free(&pq); -} - -void test_core_pqueue__max_heap_size_without_comparison(void) -{ - git_pqueue pq; - int i, vals[100] = { 0 }; - - cl_git_pass(git_pqueue_init(&pq, GIT_PQUEUE_FIXED_SIZE, 50, NULL)); - - for (i = 0; i < 100; ++i) - cl_git_pass(git_pqueue_insert(&pq, &vals[i])); - - cl_assert_equal_i(50, git_pqueue_size(&pq)); - - /* As we have no comparison function, we cannot make any - * actual assumptions about which entries are part of the - * pqueue */ - for (i = 0; i < 50; ++i) - cl_assert(git_pqueue_pop(&pq)); - - cl_assert_equal_i(0, git_pqueue_size(&pq)); - - git_pqueue_free(&pq); -} - -static int cmp_ints_like_commit_time(const void *a, const void *b) -{ - return *((const int *)a) < *((const int *)b); -} - -void test_core_pqueue__interleaved_pushes_and_pops(void) -{ - git_pqueue pq; - int i, j, *val; - static int commands[] = - { 6, 9, 8, 0, 5, 0, 7, 0, 4, 3, 0, 0, 0, 4, 0, 2, 0, 1, 0, 0, -1 }; - static int expected[] = - { 9, 8, 7, 6, 5, 4, 4, 3, 2, 1, -1 }; - - cl_git_pass(git_pqueue_init(&pq, 0, 10, cmp_ints_like_commit_time)); - - for (i = 0, j = 0; commands[i] >= 0; ++i) { - if (!commands[i]) { - cl_assert((val = git_pqueue_pop(&pq)) != NULL); - cl_assert_equal_i(expected[j], *val); - ++j; - } else { - cl_git_pass(git_pqueue_insert(&pq, &commands[i])); - } - } - - cl_assert_equal_i(0, git_pqueue_size(&pq)); - git_pqueue_free(&pq); -} - diff --git a/tests/core/qsort.c b/tests/core/qsort.c deleted file mode 100644 index efb79e6e9..000000000 --- a/tests/core/qsort.c +++ /dev/null @@ -1,90 +0,0 @@ -#include "clar_libgit2.h" - -#define assert_sorted(a, cmp) \ - _assert_sorted(a, ARRAY_SIZE(a), sizeof(*a), cmp) - -struct big_entries { - char c[311]; -}; - -static void _assert_sorted(void *els, size_t n, size_t elsize, git__sort_r_cmp cmp) -{ - int8_t *p = els; - - git__qsort_r(p, n, elsize, cmp, NULL); - while (n-- > 1) { - cl_assert(cmp(p, p + elsize, NULL) <= 0); - p += elsize; - } -} - -static int cmp_big(const void *_a, const void *_b, void *payload) -{ - const struct big_entries *a = (const struct big_entries *)_a, *b = (const struct big_entries *)_b; - GIT_UNUSED(payload); - return (a->c[0] < b->c[0]) ? -1 : (a->c[0] > b->c[0]) ? +1 : 0; -} - -static int cmp_int(const void *_a, const void *_b, void *payload) -{ - int a = *(const int *)_a, b = *(const int *)_b; - GIT_UNUSED(payload); - return (a < b) ? -1 : (a > b) ? +1 : 0; -} - -static int cmp_str(const void *_a, const void *_b, void *payload) -{ - GIT_UNUSED(payload); - return strcmp((const char *) _a, (const char *) _b); -} - -void test_core_qsort__array_with_single_entry(void) -{ - int a[] = { 10 }; - assert_sorted(a, cmp_int); -} - -void test_core_qsort__array_with_equal_entries(void) -{ - int a[] = { 4, 4, 4, 4 }; - assert_sorted(a, cmp_int); -} - -void test_core_qsort__sorted_array(void) -{ - int a[] = { 1, 10 }; - assert_sorted(a, cmp_int); -} - -void test_core_qsort__unsorted_array(void) -{ - int a[] = { 123, 9, 412938, 10, 234, 89 }; - assert_sorted(a, cmp_int); -} - -void test_core_qsort__sorting_strings(void) -{ - char *a[] = { "foo", "bar", "baz" }; - assert_sorted(a, cmp_str); -} - -void test_core_qsort__sorting_big_entries(void) -{ - struct big_entries a[5]; - - memset(&a, 0, sizeof(a)); - - memset(a[0].c, 'w', sizeof(a[0].c) - 1); - memset(a[1].c, 'c', sizeof(a[1].c) - 1); - memset(a[2].c, 'w', sizeof(a[2].c) - 1); - memset(a[3].c, 'h', sizeof(a[3].c) - 1); - memset(a[4].c, 'a', sizeof(a[4].c) - 1); - - assert_sorted(a, cmp_big); - - cl_assert_equal_i(strspn(a[0].c, "a"), sizeof(a[0].c) - 1); - cl_assert_equal_i(strspn(a[1].c, "c"), sizeof(a[1].c) - 1); - cl_assert_equal_i(strspn(a[2].c, "h"), sizeof(a[2].c) - 1); - cl_assert_equal_i(strspn(a[3].c, "w"), sizeof(a[3].c) - 1); - cl_assert_equal_i(strspn(a[4].c, "w"), sizeof(a[4].c) - 1); -} diff --git a/tests/core/regexp.c b/tests/core/regexp.c deleted file mode 100644 index 8db5641e5..000000000 --- a/tests/core/regexp.c +++ /dev/null @@ -1,213 +0,0 @@ -#include "clar_libgit2.h" - -#include - -#include "regexp.h" -#include "userdiff.h" - -#if LC_ALL > 0 -static const char *old_locales[LC_ALL]; -#endif - -static git_regexp regex; - -void test_core_regexp__initialize(void) -{ -#if LC_ALL > 0 - memset(&old_locales, 0, sizeof(old_locales)); -#endif -} - -void test_core_regexp__cleanup(void) -{ - git_regexp_dispose(®ex); -} - -static void try_set_locale(int category) -{ -#if LC_ALL > 0 - old_locales[category] = setlocale(category, NULL); -#endif - - if (!setlocale(category, "UTF-8") && - !setlocale(category, "c.utf8") && - !setlocale(category, "en_US.UTF-8")) - cl_skip(); - - if (MB_CUR_MAX == 1) - cl_fail("Expected locale to be switched to multibyte"); -} - - -void test_core_regexp__compile_ignores_global_locale_ctype(void) -{ - try_set_locale(LC_CTYPE); - cl_git_pass(git_regexp_compile(®ex, "[\xc0-\xff][\x80-\xbf]", 0)); -} - -void test_core_regexp__compile_ignores_global_locale_collate(void) -{ -#ifdef GIT_WIN32 - cl_skip(); -#endif - - try_set_locale(LC_COLLATE); - cl_git_pass(git_regexp_compile(®ex, "[\xc0-\xff][\x80-\xbf]", 0)); -} - -void test_core_regexp__regex_matches_digits_with_locale(void) -{ - char c, str[2]; - -#ifdef GIT_WIN32 - cl_skip(); -#endif - - try_set_locale(LC_COLLATE); - try_set_locale(LC_CTYPE); - - cl_git_pass(git_regexp_compile(®ex, "[[:digit:]]", 0)); - - str[1] = '\0'; - for (c = '0'; c <= '9'; c++) { - str[0] = c; - cl_git_pass(git_regexp_match(®ex, str)); - } -} - -void test_core_regexp__regex_matches_alphabet_with_locale(void) -{ - char c, str[2]; - -#ifdef GIT_WIN32 - cl_skip(); -#endif - - try_set_locale(LC_COLLATE); - try_set_locale(LC_CTYPE); - - cl_git_pass(git_regexp_compile(®ex, "[[:alpha:]]", 0)); - - str[1] = '\0'; - for (c = 'a'; c <= 'z'; c++) { - str[0] = c; - cl_git_pass(git_regexp_match(®ex, str)); - } - for (c = 'A'; c <= 'Z'; c++) { - str[0] = c; - cl_git_pass(git_regexp_match(®ex, str)); - } -} - -void test_core_regexp__compile_userdiff_regexps(void) -{ - size_t idx; - - for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) { - git_diff_driver_definition ddef = builtin_defs[idx]; - - cl_git_pass(git_regexp_compile(®ex, ddef.fns, ddef.flags)); - git_regexp_dispose(®ex); - - cl_git_pass(git_regexp_compile(®ex, ddef.words, 0)); - git_regexp_dispose(®ex); - } -} - -void test_core_regexp__simple_search_matches(void) -{ - cl_git_pass(git_regexp_compile(®ex, "a", 0)); - cl_git_pass(git_regexp_search(®ex, "a", 0, NULL)); -} - -void test_core_regexp__case_insensitive_search_matches(void) -{ - cl_git_pass(git_regexp_compile(®ex, "a", GIT_REGEXP_ICASE)); - cl_git_pass(git_regexp_search(®ex, "A", 0, NULL)); -} - -void test_core_regexp__nonmatching_search_returns_error(void) -{ - cl_git_pass(git_regexp_compile(®ex, "a", 0)); - cl_git_fail(git_regexp_search(®ex, "b", 0, NULL)); -} - -void test_core_regexp__search_finds_complete_match(void) -{ - git_regmatch matches[1]; - - cl_git_pass(git_regexp_compile(®ex, "abc", 0)); - cl_git_pass(git_regexp_search(®ex, "abc", 1, matches)); - cl_assert_equal_i(matches[0].start, 0); - cl_assert_equal_i(matches[0].end, 3); -} - -void test_core_regexp__search_finds_correct_offsets(void) -{ - git_regmatch matches[3]; - - cl_git_pass(git_regexp_compile(®ex, "(a*)(b*)", 0)); - cl_git_pass(git_regexp_search(®ex, "ab", 3, matches)); - cl_assert_equal_i(matches[0].start, 0); - cl_assert_equal_i(matches[0].end, 2); - cl_assert_equal_i(matches[1].start, 0); - cl_assert_equal_i(matches[1].end, 1); - cl_assert_equal_i(matches[2].start, 1); - cl_assert_equal_i(matches[2].end, 2); -} - -void test_core_regexp__search_finds_empty_group(void) -{ - git_regmatch matches[3]; - - cl_git_pass(git_regexp_compile(®ex, "(a*)(b*)c", 0)); - cl_git_pass(git_regexp_search(®ex, "ac", 3, matches)); - cl_assert_equal_i(matches[0].start, 0); - cl_assert_equal_i(matches[0].end, 2); - cl_assert_equal_i(matches[1].start, 0); - cl_assert_equal_i(matches[1].end, 1); - cl_assert_equal_i(matches[2].start, 1); - cl_assert_equal_i(matches[2].end, 1); -} - -void test_core_regexp__search_fills_matches_with_first_matching_groups(void) -{ - git_regmatch matches[2]; - - cl_git_pass(git_regexp_compile(®ex, "(a)(b)(c)", 0)); - cl_git_pass(git_regexp_search(®ex, "abc", 2, matches)); - cl_assert_equal_i(matches[0].start, 0); - cl_assert_equal_i(matches[0].end, 3); - cl_assert_equal_i(matches[1].start, 0); - cl_assert_equal_i(matches[1].end, 1); -} - -void test_core_regexp__search_skips_nonmatching_group(void) -{ - git_regmatch matches[4]; - - cl_git_pass(git_regexp_compile(®ex, "(a)(b)?(c)", 0)); - cl_git_pass(git_regexp_search(®ex, "ac", 4, matches)); - cl_assert_equal_i(matches[0].start, 0); - cl_assert_equal_i(matches[0].end, 2); - cl_assert_equal_i(matches[1].start, 0); - cl_assert_equal_i(matches[1].end, 1); - cl_assert_equal_i(matches[2].start, -1); - cl_assert_equal_i(matches[2].end, -1); - cl_assert_equal_i(matches[3].start, 1); - cl_assert_equal_i(matches[3].end, 2); -} - -void test_core_regexp__search_initializes_trailing_nonmatching_groups(void) -{ - git_regmatch matches[3]; - - cl_git_pass(git_regexp_compile(®ex, "(a)bc", 0)); - cl_git_pass(git_regexp_search(®ex, "abc", 3, matches)); - cl_assert_equal_i(matches[0].start, 0); - cl_assert_equal_i(matches[0].end, 3); - cl_assert_equal_i(matches[1].start, 0); - cl_assert_equal_i(matches[1].end, 1); - cl_assert_equal_i(matches[2].start, -1); - cl_assert_equal_i(matches[2].end, -1); -} diff --git a/tests/core/rmdir.c b/tests/core/rmdir.c deleted file mode 100644 index f6c66b3a4..000000000 --- a/tests/core/rmdir.c +++ /dev/null @@ -1,120 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" - -static const char *empty_tmp_dir = "test_gitfo_rmdir_recurs_test"; - -void test_core_rmdir__initialize(void) -{ - git_str path = GIT_STR_INIT; - - cl_must_pass(p_mkdir(empty_tmp_dir, 0777)); - - cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one")); - cl_must_pass(p_mkdir(path.ptr, 0777)); - - cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_one")); - cl_must_pass(p_mkdir(path.ptr, 0777)); - - cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_two")); - cl_must_pass(p_mkdir(path.ptr, 0777)); - - cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_two/three")); - cl_must_pass(p_mkdir(path.ptr, 0777)); - - cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/two")); - cl_must_pass(p_mkdir(path.ptr, 0777)); - - git_str_dispose(&path); -} - -void test_core_rmdir__cleanup(void) -{ - if (git_fs_path_exists(empty_tmp_dir)) - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_REMOVE_FILES)); -} - -/* make sure empty dir can be deleted recursively */ -void test_core_rmdir__delete_recursive(void) -{ - git_str path = GIT_STR_INIT; - cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one")); - cl_assert(git_fs_path_exists(git_str_cstr(&path))); - - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); - - cl_assert(!git_fs_path_exists(git_str_cstr(&path))); - - git_str_dispose(&path); -} - -/* make sure non-empty dir cannot be deleted recursively */ -void test_core_rmdir__fail_to_delete_non_empty_dir(void) -{ - git_str file = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&file, empty_tmp_dir, "/two/file.txt")); - - cl_git_mkfile(git_str_cstr(&file), "dummy"); - - cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); - - cl_must_pass(p_unlink(file.ptr)); - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); - - cl_assert(!git_fs_path_exists(empty_tmp_dir)); - - git_str_dispose(&file); -} - -void test_core_rmdir__keep_base(void) -{ - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_SKIP_ROOT)); - cl_assert(git_fs_path_exists(empty_tmp_dir)); -} - -void test_core_rmdir__can_skip_non_empty_dir(void) -{ - git_str file = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&file, empty_tmp_dir, "/two/file.txt")); - - cl_git_mkfile(git_str_cstr(&file), "dummy"); - - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_SKIP_NONEMPTY)); - cl_assert(git_fs_path_exists(git_str_cstr(&file)) == true); - - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_REMOVE_FILES)); - cl_assert(git_fs_path_exists(empty_tmp_dir) == false); - - git_str_dispose(&file); -} - -void test_core_rmdir__can_remove_empty_parents(void) -{ - git_str file = GIT_STR_INIT; - - cl_git_pass( - git_str_joinpath(&file, empty_tmp_dir, "/one/two_two/three/file.txt")); - cl_git_mkfile(git_str_cstr(&file), "dummy"); - cl_assert(git_fs_path_isfile(git_str_cstr(&file))); - - cl_git_pass(git_futils_rmdir_r("one/two_two/three/file.txt", empty_tmp_dir, - GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS)); - - cl_assert(!git_fs_path_exists(git_str_cstr(&file))); - - git_str_rtruncate_at_char(&file, '/'); /* three (only contained file.txt) */ - cl_assert(!git_fs_path_exists(git_str_cstr(&file))); - - git_str_rtruncate_at_char(&file, '/'); /* two_two (only contained three) */ - cl_assert(!git_fs_path_exists(git_str_cstr(&file))); - - git_str_rtruncate_at_char(&file, '/'); /* one (contained two_one also) */ - cl_assert(git_fs_path_exists(git_str_cstr(&file))); - - cl_assert(git_fs_path_exists(empty_tmp_dir) == true); - - git_str_dispose(&file); - - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); -} diff --git a/tests/core/sha1.c b/tests/core/sha1.c deleted file mode 100644 index 9ccdaab3c..000000000 --- a/tests/core/sha1.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "clar_libgit2.h" -#include "hash.h" - -#define FIXTURE_DIR "sha1" - -void test_core_sha1__initialize(void) -{ - cl_fixture_sandbox(FIXTURE_DIR); -} - -void test_core_sha1__cleanup(void) -{ - cl_fixture_cleanup(FIXTURE_DIR); -} - -static int sha1_file(unsigned char *out, const char *filename) -{ - git_hash_ctx ctx; - char buf[2048]; - int fd, ret; - ssize_t read_len; - - fd = p_open(filename, O_RDONLY); - cl_assert(fd >= 0); - - cl_git_pass(git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)); - - while ((read_len = p_read(fd, buf, 2048)) > 0) - cl_git_pass(git_hash_update(&ctx, buf, (size_t)read_len)); - - cl_assert_equal_i(0, read_len); - p_close(fd); - - ret = git_hash_final(out, &ctx); - git_hash_ctx_cleanup(&ctx); - - return ret; -} - -void test_core_sha1__sum(void) -{ - unsigned char expected[GIT_HASH_SHA1_SIZE] = { - 0x4e, 0x72, 0x67, 0x9e, 0x3e, 0xa4, 0xd0, 0x4e, 0x0c, 0x64, - 0x2f, 0x02, 0x9e, 0x61, 0xeb, 0x80, 0x56, 0xc7, 0xed, 0x94 - }; - unsigned char actual[GIT_HASH_SHA1_SIZE]; - - cl_git_pass(sha1_file(actual, FIXTURE_DIR "/hello_c")); - cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA1_SIZE)); -} - -/* test that sha1 collision detection works when enabled */ -void test_core_sha1__detect_collision_attack(void) -{ - unsigned char actual[GIT_HASH_SHA1_SIZE]; - unsigned char expected[GIT_HASH_SHA1_SIZE] = { - 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, - 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a - }; - -#ifdef GIT_SHA1_COLLISIONDETECT - GIT_UNUSED(&expected); - cl_git_fail(sha1_file(actual, FIXTURE_DIR "/shattered-1.pdf")); - cl_assert_equal_s("SHA1 collision attack detected", git_error_last()->message); -#else - cl_git_pass(sha1_file(actual, FIXTURE_DIR "/shattered-1.pdf")); - cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA1_SIZE)); -#endif -} - diff --git a/tests/core/sortedcache.c b/tests/core/sortedcache.c deleted file mode 100644 index cb4e34efa..000000000 --- a/tests/core/sortedcache.c +++ /dev/null @@ -1,363 +0,0 @@ -#include "clar_libgit2.h" -#include "sortedcache.h" - -static int name_only_cmp(const void *a, const void *b) -{ - return strcmp(a, b); -} - -void test_core_sortedcache__name_only(void) -{ - git_sortedcache *sc; - void *item; - size_t pos; - - cl_git_pass(git_sortedcache_new( - &sc, 0, NULL, NULL, name_only_cmp, NULL)); - - cl_git_pass(git_sortedcache_wlock(sc)); - cl_git_pass(git_sortedcache_upsert(&item, sc, "aaa")); - cl_git_pass(git_sortedcache_upsert(&item, sc, "bbb")); - cl_git_pass(git_sortedcache_upsert(&item, sc, "zzz")); - cl_git_pass(git_sortedcache_upsert(&item, sc, "mmm")); - cl_git_pass(git_sortedcache_upsert(&item, sc, "iii")); - git_sortedcache_wunlock(sc); - - cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); - - cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL); - cl_assert_equal_s("aaa", item); - cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL); - cl_assert_equal_s("mmm", item); - cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL); - cl_assert_equal_s("zzz", item); - cl_assert(git_sortedcache_lookup(sc, "qqq") == NULL); - - cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); - cl_assert_equal_s("aaa", item); - cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); - cl_assert_equal_s("bbb", item); - cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); - cl_assert_equal_s("iii", item); - cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); - cl_assert_equal_s("mmm", item); - cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL); - cl_assert_equal_s("zzz", item); - cl_assert(git_sortedcache_entry(sc, 5) == NULL); - - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa")); - cl_assert_equal_sz(0, pos); - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "iii")); - cl_assert_equal_sz(2, pos); - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz")); - cl_assert_equal_sz(4, pos); - cl_assert_equal_i( - GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "abc")); - - cl_git_pass(git_sortedcache_clear(sc, true)); - - cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); - cl_assert(git_sortedcache_entry(sc, 0) == NULL); - cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); - cl_assert(git_sortedcache_entry(sc, 0) == NULL); - - git_sortedcache_free(sc); -} - -typedef struct { - int value; - char smaller_value; - char path[GIT_FLEX_ARRAY]; -} sortedcache_test_struct; - -static int sortedcache_test_struct_cmp(const void *a_, const void *b_) -{ - const sortedcache_test_struct *a = a_, *b = b_; - return strcmp(a->path, b->path); -} - -static void sortedcache_test_struct_free(void *payload, void *item_) -{ - sortedcache_test_struct *item = item_; - int *count = payload; - (*count)++; - item->smaller_value = 0; -} - -void test_core_sortedcache__in_memory(void) -{ - git_sortedcache *sc; - sortedcache_test_struct *item; - int free_count = 0; - - cl_git_pass(git_sortedcache_new( - &sc, offsetof(sortedcache_test_struct, path), - sortedcache_test_struct_free, &free_count, - sortedcache_test_struct_cmp, NULL)); - - cl_git_pass(git_sortedcache_wlock(sc)); - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "aaa")); - item->value = 10; - item->smaller_value = 1; - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "bbb")); - item->value = 20; - item->smaller_value = 2; - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "zzz")); - item->value = 30; - item->smaller_value = 26; - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "mmm")); - item->value = 40; - item->smaller_value = 14; - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "iii")); - item->value = 50; - item->smaller_value = 9; - git_sortedcache_wunlock(sc); - - cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); - - cl_git_pass(git_sortedcache_rlock(sc)); - - cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL); - cl_assert_equal_s("aaa", item->path); - cl_assert_equal_i(10, item->value); - cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL); - cl_assert_equal_s("mmm", item->path); - cl_assert_equal_i(40, item->value); - cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL); - cl_assert_equal_s("zzz", item->path); - cl_assert_equal_i(30, item->value); - cl_assert(git_sortedcache_lookup(sc, "abc") == NULL); - - /* not on Windows: - * cl_git_pass(git_sortedcache_rlock(sc)); -- grab more than one - */ - - cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); - cl_assert_equal_s("aaa", item->path); - cl_assert_equal_i(10, item->value); - cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); - cl_assert_equal_s("bbb", item->path); - cl_assert_equal_i(20, item->value); - cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); - cl_assert_equal_s("iii", item->path); - cl_assert_equal_i(50, item->value); - cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); - cl_assert_equal_s("mmm", item->path); - cl_assert_equal_i(40, item->value); - cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL); - cl_assert_equal_s("zzz", item->path); - cl_assert_equal_i(30, item->value); - cl_assert(git_sortedcache_entry(sc, 5) == NULL); - - git_sortedcache_runlock(sc); - /* git_sortedcache_runlock(sc); */ - - cl_assert_equal_i(0, free_count); - - cl_git_pass(git_sortedcache_clear(sc, true)); - - cl_assert_equal_i(5, free_count); - - cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); - cl_assert(git_sortedcache_entry(sc, 0) == NULL); - cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); - cl_assert(git_sortedcache_entry(sc, 0) == NULL); - - free_count = 0; - - cl_git_pass(git_sortedcache_wlock(sc)); - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "testing")); - item->value = 10; - item->smaller_value = 3; - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "again")); - item->value = 20; - item->smaller_value = 1; - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "final")); - item->value = 30; - item->smaller_value = 2; - git_sortedcache_wunlock(sc); - - cl_assert_equal_sz(3, git_sortedcache_entrycount(sc)); - - cl_assert((item = git_sortedcache_lookup(sc, "testing")) != NULL); - cl_assert_equal_s("testing", item->path); - cl_assert_equal_i(10, item->value); - cl_assert((item = git_sortedcache_lookup(sc, "again")) != NULL); - cl_assert_equal_s("again", item->path); - cl_assert_equal_i(20, item->value); - cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL); - cl_assert_equal_s("final", item->path); - cl_assert_equal_i(30, item->value); - cl_assert(git_sortedcache_lookup(sc, "zzz") == NULL); - - cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); - cl_assert_equal_s("again", item->path); - cl_assert_equal_i(20, item->value); - cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); - cl_assert_equal_s("final", item->path); - cl_assert_equal_i(30, item->value); - cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); - cl_assert_equal_s("testing", item->path); - cl_assert_equal_i(10, item->value); - cl_assert(git_sortedcache_entry(sc, 3) == NULL); - - { - size_t pos; - - cl_git_pass(git_sortedcache_wlock(sc)); - - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "again")); - cl_assert_equal_sz(0, pos); - cl_git_pass(git_sortedcache_remove(sc, pos)); - cl_assert_equal_i( - GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "again")); - - cl_assert_equal_sz(2, git_sortedcache_entrycount(sc)); - - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "testing")); - cl_assert_equal_sz(1, pos); - cl_git_pass(git_sortedcache_remove(sc, pos)); - cl_assert_equal_i( - GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "testing")); - - cl_assert_equal_sz(1, git_sortedcache_entrycount(sc)); - - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final")); - cl_assert_equal_sz(0, pos); - cl_git_pass(git_sortedcache_remove(sc, pos)); - cl_assert_equal_i( - GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "final")); - - cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); - - git_sortedcache_wunlock(sc); - } - - git_sortedcache_free(sc); - - cl_assert_equal_i(3, free_count); -} - -static void sortedcache_test_reload(git_sortedcache *sc) -{ - int count = 0; - git_str buf = GIT_STR_INIT; - char *scan, *after; - sortedcache_test_struct *item; - - cl_assert(git_sortedcache_lockandload(sc, &buf) > 0); - - cl_git_pass(git_sortedcache_clear(sc, false)); /* clear once we already have lock */ - - for (scan = buf.ptr; *scan; scan = after + 1) { - int val = strtol(scan, &after, 0); - cl_assert(after > scan); - scan = after; - - for (scan = after; git__isspace(*scan); ++scan) /* find start */; - for (after = scan; *after && *after != '\n'; ++after) /* find eol */; - *after = '\0'; - - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, scan)); - - item->value = val; - item->smaller_value = (char)(count++); - } - - git_sortedcache_wunlock(sc); - - git_str_dispose(&buf); -} - -void test_core_sortedcache__on_disk(void) -{ - git_sortedcache *sc; - sortedcache_test_struct *item; - int free_count = 0; - size_t pos; - - cl_git_mkfile("cacheitems.txt", "10 abc\n20 bcd\n30 cde\n"); - - cl_git_pass(git_sortedcache_new( - &sc, offsetof(sortedcache_test_struct, path), - sortedcache_test_struct_free, &free_count, - sortedcache_test_struct_cmp, "cacheitems.txt")); - - /* should need to reload the first time */ - - sortedcache_test_reload(sc); - - /* test what we loaded */ - - cl_assert_equal_sz(3, git_sortedcache_entrycount(sc)); - - cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL); - cl_assert_equal_s("abc", item->path); - cl_assert_equal_i(10, item->value); - cl_assert((item = git_sortedcache_lookup(sc, "cde")) != NULL); - cl_assert_equal_s("cde", item->path); - cl_assert_equal_i(30, item->value); - cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); - - cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); - cl_assert_equal_s("abc", item->path); - cl_assert_equal_i(10, item->value); - cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); - cl_assert_equal_s("bcd", item->path); - cl_assert_equal_i(20, item->value); - cl_assert(git_sortedcache_entry(sc, 3) == NULL); - - /* should not need to reload this time */ - - cl_assert_equal_i(0, git_sortedcache_lockandload(sc, NULL)); - - /* rewrite ondisk file and reload */ - - cl_assert_equal_i(0, free_count); - - cl_git_rewritefile( - "cacheitems.txt", "100 abc\n200 zzz\n500 aaa\n10 final\n"); - sortedcache_test_reload(sc); - - cl_assert_equal_i(3, free_count); - - /* test what we loaded */ - - cl_assert_equal_sz(4, git_sortedcache_entrycount(sc)); - - cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL); - cl_assert_equal_s("abc", item->path); - cl_assert_equal_i(100, item->value); - cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL); - cl_assert_equal_s("final", item->path); - cl_assert_equal_i(10, item->value); - cl_assert(git_sortedcache_lookup(sc, "cde") == NULL); - - cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); - cl_assert_equal_s("aaa", item->path); - cl_assert_equal_i(500, item->value); - cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); - cl_assert_equal_s("final", item->path); - cl_assert_equal_i(10, item->value); - cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); - cl_assert_equal_s("zzz", item->path); - cl_assert_equal_i(200, item->value); - - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa")); - cl_assert_equal_sz(0, pos); - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "abc")); - cl_assert_equal_sz(1, pos); - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final")); - cl_assert_equal_sz(2, pos); - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz")); - cl_assert_equal_sz(3, pos); - cl_assert_equal_i( - GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "missing")); - cl_assert_equal_i( - GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "cde")); - - git_sortedcache_free(sc); - - cl_assert_equal_i(7, free_count); -} - diff --git a/tests/core/stat.c b/tests/core/stat.c deleted file mode 100644 index 210072fe3..000000000 --- a/tests/core/stat.c +++ /dev/null @@ -1,113 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "posix.h" - -void test_core_stat__initialize(void) -{ - cl_git_pass(git_futils_mkdir("root/d1/d2", 0755, GIT_MKDIR_PATH)); - cl_git_mkfile("root/file", "whatever\n"); - cl_git_mkfile("root/d1/file", "whatever\n"); -} - -void test_core_stat__cleanup(void) -{ - git_futils_rmdir_r("root", NULL, GIT_RMDIR_REMOVE_FILES); -} - -#define cl_assert_error(val) \ - do { err = errno; cl_assert_equal_i((val), err); } while (0) - -void test_core_stat__0(void) -{ - struct stat st; - int err; - - cl_assert_equal_i(0, p_lstat("root", &st)); - cl_assert(S_ISDIR(st.st_mode)); - cl_assert_error(0); - - cl_assert_equal_i(0, p_lstat("root/", &st)); - cl_assert(S_ISDIR(st.st_mode)); - cl_assert_error(0); - - cl_assert_equal_i(0, p_lstat("root/file", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_error(0); - - cl_assert_equal_i(0, p_lstat("root/d1", &st)); - cl_assert(S_ISDIR(st.st_mode)); - cl_assert_error(0); - - cl_assert_equal_i(0, p_lstat("root/d1/", &st)); - cl_assert(S_ISDIR(st.st_mode)); - cl_assert_error(0); - - cl_assert_equal_i(0, p_lstat("root/d1/file", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_error(0); - - cl_assert(p_lstat("root/missing", &st) < 0); - cl_assert_error(ENOENT); - - cl_assert(p_lstat("root/missing/but/could/be/created", &st) < 0); - cl_assert_error(ENOENT); - - cl_assert(p_lstat_posixly("root/missing/but/could/be/created", &st) < 0); - cl_assert_error(ENOENT); - - cl_assert(p_lstat("root/d1/missing", &st) < 0); - cl_assert_error(ENOENT); - - cl_assert(p_lstat("root/d1/missing/deeper/path", &st) < 0); - cl_assert_error(ENOENT); - - cl_assert(p_lstat_posixly("root/d1/missing/deeper/path", &st) < 0); - cl_assert_error(ENOENT); - - cl_assert(p_lstat_posixly("root/d1/file/deeper/path", &st) < 0); - cl_assert_error(ENOTDIR); - - cl_assert(p_lstat("root/file/invalid", &st) < 0); -#ifdef GIT_WIN32 - cl_assert_error(ENOENT); -#else - cl_assert_error(ENOTDIR); -#endif - - cl_assert(p_lstat_posixly("root/file/invalid", &st) < 0); - cl_assert_error(ENOTDIR); - - cl_assert(p_lstat("root/file/invalid/deeper_path", &st) < 0); -#ifdef GIT_WIN32 - cl_assert_error(ENOENT); -#else - cl_assert_error(ENOTDIR); -#endif - - cl_assert(p_lstat_posixly("root/file/invalid/deeper_path", &st) < 0); - cl_assert_error(ENOTDIR); - - cl_assert(p_lstat_posixly("root/d1/file/extra", &st) < 0); - cl_assert_error(ENOTDIR); - - cl_assert(p_lstat_posixly("root/d1/file/further/invalid/items", &st) < 0); - cl_assert_error(ENOTDIR); -} - -void test_core_stat__root(void) -{ - const char *sandbox = clar_sandbox_path(); - git_str root = GIT_STR_INIT; - int root_len; - struct stat st; - - root_len = git_fs_path_root(sandbox); - cl_assert(root_len >= 0); - - git_str_set(&root, sandbox, root_len+1); - - cl_must_pass(p_stat(root.ptr, &st)); - cl_assert(S_ISDIR(st.st_mode)); - - git_str_dispose(&root); -} diff --git a/tests/core/string.c b/tests/core/string.c deleted file mode 100644 index 928dfbcc1..000000000 --- a/tests/core/string.c +++ /dev/null @@ -1,136 +0,0 @@ -#include "clar_libgit2.h" - -/* compare prefixes */ -void test_core_string__0(void) -{ - cl_assert(git__prefixcmp("", "") == 0); - cl_assert(git__prefixcmp("a", "") == 0); - cl_assert(git__prefixcmp("", "a") < 0); - cl_assert(git__prefixcmp("a", "b") < 0); - cl_assert(git__prefixcmp("b", "a") > 0); - cl_assert(git__prefixcmp("ab", "a") == 0); - cl_assert(git__prefixcmp("ab", "ac") < 0); - cl_assert(git__prefixcmp("ab", "aa") > 0); -} - -/* compare suffixes */ -void test_core_string__1(void) -{ - cl_assert(git__suffixcmp("", "") == 0); - cl_assert(git__suffixcmp("a", "") == 0); - cl_assert(git__suffixcmp("", "a") < 0); - cl_assert(git__suffixcmp("a", "b") < 0); - cl_assert(git__suffixcmp("b", "a") > 0); - cl_assert(git__suffixcmp("ba", "a") == 0); - cl_assert(git__suffixcmp("zaa", "ac") < 0); - cl_assert(git__suffixcmp("zaz", "ac") > 0); -} - -/* compare icase sorting with case equality */ -void test_core_string__2(void) -{ - cl_assert(git__strcasesort_cmp("", "") == 0); - cl_assert(git__strcasesort_cmp("foo", "foo") == 0); - cl_assert(git__strcasesort_cmp("foo", "bar") > 0); - cl_assert(git__strcasesort_cmp("bar", "foo") < 0); - cl_assert(git__strcasesort_cmp("foo", "FOO") > 0); - cl_assert(git__strcasesort_cmp("FOO", "foo") < 0); - cl_assert(git__strcasesort_cmp("foo", "BAR") > 0); - cl_assert(git__strcasesort_cmp("BAR", "foo") < 0); - cl_assert(git__strcasesort_cmp("fooBar", "foobar") < 0); -} - -/* compare prefixes with len */ -void test_core_string__prefixncmp(void) -{ - cl_assert(git__prefixncmp("", 0, "") == 0); - cl_assert(git__prefixncmp("a", 1, "") == 0); - cl_assert(git__prefixncmp("", 0, "a") < 0); - cl_assert(git__prefixncmp("a", 1, "b") < 0); - cl_assert(git__prefixncmp("b", 1, "a") > 0); - cl_assert(git__prefixncmp("ab", 2, "a") == 0); - cl_assert(git__prefixncmp("ab", 1, "a") == 0); - cl_assert(git__prefixncmp("ab", 2, "ac") < 0); - cl_assert(git__prefixncmp("a", 1, "ac") < 0); - cl_assert(git__prefixncmp("ab", 1, "ac") < 0); - cl_assert(git__prefixncmp("ab", 2, "aa") > 0); - cl_assert(git__prefixncmp("ab", 1, "aa") < 0); -} - -/* compare prefixes with len */ -void test_core_string__prefixncmp_icase(void) -{ - cl_assert(git__prefixncmp_icase("", 0, "") == 0); - cl_assert(git__prefixncmp_icase("a", 1, "") == 0); - cl_assert(git__prefixncmp_icase("", 0, "a") < 0); - cl_assert(git__prefixncmp_icase("a", 1, "b") < 0); - cl_assert(git__prefixncmp_icase("A", 1, "b") < 0); - cl_assert(git__prefixncmp_icase("a", 1, "B") < 0); - cl_assert(git__prefixncmp_icase("b", 1, "a") > 0); - cl_assert(git__prefixncmp_icase("B", 1, "a") > 0); - cl_assert(git__prefixncmp_icase("b", 1, "A") > 0); - cl_assert(git__prefixncmp_icase("ab", 2, "a") == 0); - cl_assert(git__prefixncmp_icase("Ab", 2, "a") == 0); - cl_assert(git__prefixncmp_icase("ab", 2, "A") == 0); - cl_assert(git__prefixncmp_icase("ab", 1, "a") == 0); - cl_assert(git__prefixncmp_icase("ab", 2, "ac") < 0); - cl_assert(git__prefixncmp_icase("Ab", 2, "ac") < 0); - cl_assert(git__prefixncmp_icase("ab", 2, "Ac") < 0); - cl_assert(git__prefixncmp_icase("a", 1, "ac") < 0); - cl_assert(git__prefixncmp_icase("ab", 1, "ac") < 0); - cl_assert(git__prefixncmp_icase("ab", 2, "aa") > 0); - cl_assert(git__prefixncmp_icase("ab", 1, "aa") < 0); -} - -void test_core_string__strcmp(void) -{ - cl_assert(git__strcmp("", "") == 0); - cl_assert(git__strcmp("foo", "foo") == 0); - cl_assert(git__strcmp("Foo", "foo") < 0); - cl_assert(git__strcmp("foo", "FOO") > 0); - cl_assert(git__strcmp("foo", "fOO") > 0); - - cl_assert(strcmp("rt\303\202of", "rt dev\302\266h") > 0); - cl_assert(strcmp("e\342\202\254ghi=", "et") > 0); - cl_assert(strcmp("rt dev\302\266h", "rt\303\202of") < 0); - cl_assert(strcmp("et", "e\342\202\254ghi=") < 0); - cl_assert(strcmp("\303\215", "\303\255") < 0); - - cl_assert(git__strcmp("rt\303\202of", "rt dev\302\266h") > 0); - cl_assert(git__strcmp("e\342\202\254ghi=", "et") > 0); - cl_assert(git__strcmp("rt dev\302\266h", "rt\303\202of") < 0); - cl_assert(git__strcmp("et", "e\342\202\254ghi=") < 0); - cl_assert(git__strcmp("\303\215", "\303\255") < 0); -} - -void test_core_string__strcasecmp(void) -{ - cl_assert(git__strcasecmp("", "") == 0); - cl_assert(git__strcasecmp("foo", "foo") == 0); - cl_assert(git__strcasecmp("foo", "Foo") == 0); - cl_assert(git__strcasecmp("foo", "FOO") == 0); - cl_assert(git__strcasecmp("foo", "fOO") == 0); - - cl_assert(strcasecmp("rt\303\202of", "rt dev\302\266h") > 0); - cl_assert(strcasecmp("e\342\202\254ghi=", "et") > 0); - cl_assert(strcasecmp("rt dev\302\266h", "rt\303\202of") < 0); - cl_assert(strcasecmp("et", "e\342\202\254ghi=") < 0); - cl_assert(strcasecmp("\303\215", "\303\255") < 0); - - cl_assert(git__strcasecmp("rt\303\202of", "rt dev\302\266h") > 0); - cl_assert(git__strcasecmp("e\342\202\254ghi=", "et") > 0); - cl_assert(git__strcasecmp("rt dev\302\266h", "rt\303\202of") < 0); - cl_assert(git__strcasecmp("et", "e\342\202\254ghi=") < 0); - cl_assert(git__strcasecmp("\303\215", "\303\255") < 0); -} - -void test_core_string__strlcmp(void) -{ - const char foo[3] = { 'f', 'o', 'o' }; - - cl_assert(git__strlcmp("foo", "foo", 3) == 0); - cl_assert(git__strlcmp("foo", foo, 3) == 0); - cl_assert(git__strlcmp("foo", "foobar", 3) == 0); - cl_assert(git__strlcmp("foobar", "foo", 3) > 0); - cl_assert(git__strlcmp("foo", "foobar", 6) < 0); -} diff --git a/tests/core/strmap.c b/tests/core/strmap.c deleted file mode 100644 index ba118ae1e..000000000 --- a/tests/core/strmap.c +++ /dev/null @@ -1,190 +0,0 @@ -#include "clar_libgit2.h" -#include "strmap.h" - -static git_strmap *g_table; - -void test_core_strmap__initialize(void) -{ - cl_git_pass(git_strmap_new(&g_table)); - cl_assert(g_table != NULL); -} - -void test_core_strmap__cleanup(void) -{ - git_strmap_free(g_table); -} - -void test_core_strmap__0(void) -{ - cl_assert(git_strmap_size(g_table) == 0); -} - -static void insert_strings(git_strmap *table, size_t count) -{ - size_t i, j, over; - char *str; - - for (i = 0; i < count; ++i) { - str = malloc(10); - for (j = 0; j < 10; ++j) - str[j] = 'a' + (i % 26); - str[9] = '\0'; - - /* if > 26, then encode larger value in first letters */ - for (j = 0, over = i / 26; over > 0; j++, over = over / 26) - str[j] = 'A' + (over % 26); - - cl_git_pass(git_strmap_set(table, str, str)); - } - - cl_assert_equal_i(git_strmap_size(table), count); -} - -void test_core_strmap__inserted_strings_can_be_retrieved(void) -{ - char *str; - int i; - - insert_strings(g_table, 20); - - cl_assert(git_strmap_exists(g_table, "aaaaaaaaa")); - cl_assert(git_strmap_exists(g_table, "ggggggggg")); - cl_assert(!git_strmap_exists(g_table, "aaaaaaaab")); - cl_assert(!git_strmap_exists(g_table, "abcdefghi")); - - i = 0; - git_strmap_foreach_value(g_table, str, { i++; free(str); }); - cl_assert(i == 20); -} - -void test_core_strmap__deleted_entry_cannot_be_retrieved(void) -{ - char *str; - int i; - - insert_strings(g_table, 20); - - cl_assert(git_strmap_exists(g_table, "bbbbbbbbb")); - str = git_strmap_get(g_table, "bbbbbbbbb"); - cl_assert_equal_s(str, "bbbbbbbbb"); - cl_git_pass(git_strmap_delete(g_table, "bbbbbbbbb")); - free(str); - - cl_assert(!git_strmap_exists(g_table, "bbbbbbbbb")); - - i = 0; - git_strmap_foreach_value(g_table, str, { i++; free(str); }); - cl_assert_equal_i(i, 19); -} - -void test_core_strmap__inserting_many_keys_succeeds(void) -{ - char *str; - int i; - - insert_strings(g_table, 10000); - - i = 0; - git_strmap_foreach_value(g_table, str, { i++; free(str); }); - cl_assert_equal_i(i, 10000); -} - -void test_core_strmap__get_succeeds_with_existing_entries(void) -{ - const char *keys[] = { "foo", "bar", "gobble" }; - char *values[] = { "oof", "rab", "elbbog" }; - size_t i; - - for (i = 0; i < ARRAY_SIZE(keys); i++) - cl_git_pass(git_strmap_set(g_table, keys[i], values[i])); - - cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); - cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab"); - cl_assert_equal_s(git_strmap_get(g_table, "gobble"), "elbbog"); -} - -void test_core_strmap__get_returns_null_on_nonexisting_key(void) -{ - const char *keys[] = { "foo", "bar", "gobble" }; - char *values[] = { "oof", "rab", "elbbog" }; - size_t i; - - for (i = 0; i < ARRAY_SIZE(keys); i++) - cl_git_pass(git_strmap_set(g_table, keys[i], values[i])); - - cl_assert_equal_p(git_strmap_get(g_table, "other"), NULL); -} - -void test_core_strmap__set_persists_key(void) -{ - cl_git_pass(git_strmap_set(g_table, "foo", "oof")); - cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); -} - -void test_core_strmap__set_persists_multpile_keys(void) -{ - cl_git_pass(git_strmap_set(g_table, "foo", "oof")); - cl_git_pass(git_strmap_set(g_table, "bar", "rab")); - cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); - cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab"); -} - -void test_core_strmap__set_updates_existing_key(void) -{ - cl_git_pass(git_strmap_set(g_table, "foo", "oof")); - cl_git_pass(git_strmap_set(g_table, "bar", "rab")); - cl_git_pass(git_strmap_set(g_table, "gobble", "elbbog")); - cl_assert_equal_i(git_strmap_size(g_table), 3); - - cl_git_pass(git_strmap_set(g_table, "foo", "other")); - cl_assert_equal_i(git_strmap_size(g_table), 3); - - cl_assert_equal_s(git_strmap_get(g_table, "foo"), "other"); -} - -void test_core_strmap__iteration(void) -{ - struct { - char *key; - char *value; - int seen; - } entries[] = { - { "foo", "oof" }, - { "bar", "rab" }, - { "gobble", "elbbog" }, - }; - const char *key, *value; - size_t i, n; - - for (i = 0; i < ARRAY_SIZE(entries); i++) - cl_git_pass(git_strmap_set(g_table, entries[i].key, entries[i].value)); - - i = 0, n = 0; - while (git_strmap_iterate((void **) &value, g_table, &i, &key) == 0) { - size_t j; - - for (j = 0; j < ARRAY_SIZE(entries); j++) { - if (strcmp(entries[j].key, key)) - continue; - - cl_assert_equal_i(entries[j].seen, 0); - cl_assert_equal_s(entries[j].value, value); - entries[j].seen++; - break; - } - - n++; - } - - for (i = 0; i < ARRAY_SIZE(entries); i++) - cl_assert_equal_i(entries[i].seen, 1); - - cl_assert_equal_i(n, ARRAY_SIZE(entries)); -} - -void test_core_strmap__iterating_empty_map_stops_immediately(void) -{ - size_t i = 0; - - cl_git_fail_with(git_strmap_iterate(NULL, g_table, &i, NULL), GIT_ITEROVER); -} diff --git a/tests/core/strtol.c b/tests/core/strtol.c deleted file mode 100644 index 851b91b0a..000000000 --- a/tests/core/strtol.c +++ /dev/null @@ -1,128 +0,0 @@ -#include "clar_libgit2.h" - -static void assert_l32_parses(const char *string, int32_t expected, int base) -{ - int32_t i; - cl_git_pass(git__strntol32(&i, string, strlen(string), NULL, base)); - cl_assert_equal_i(i, expected); -} - -static void assert_l32_fails(const char *string, int base) -{ - int32_t i; - cl_git_fail(git__strntol32(&i, string, strlen(string), NULL, base)); -} - -static void assert_l64_parses(const char *string, int64_t expected, int base) -{ - int64_t i; - cl_git_pass(git__strntol64(&i, string, strlen(string), NULL, base)); - cl_assert_equal_i(i, expected); -} - -static void assert_l64_fails(const char *string, int base) -{ - int64_t i; - cl_git_fail(git__strntol64(&i, string, strlen(string), NULL, base)); -} - -void test_core_strtol__int32(void) -{ - assert_l32_parses("123", 123, 10); - assert_l32_parses(" +123 ", 123, 10); - assert_l32_parses(" -123 ", -123, 10); - assert_l32_parses(" +2147483647 ", 2147483647, 10); - assert_l32_parses(" -2147483648 ", INT64_C(-2147483648), 10); - assert_l32_parses("A", 10, 16); - assert_l32_parses("1x1", 1, 10); - - assert_l32_fails("", 10); - assert_l32_fails("a", 10); - assert_l32_fails("x10x", 10); - assert_l32_fails(" 2147483657 ", 10); - assert_l32_fails(" -2147483657 ", 10); -} - -void test_core_strtol__int64(void) -{ - assert_l64_parses("123", 123, 10); - assert_l64_parses(" +123 ", 123, 10); - assert_l64_parses(" -123 ", -123, 10); - assert_l64_parses(" +2147483647 ", 2147483647, 10); - assert_l64_parses(" -2147483648 ", INT64_C(-2147483648), 10); - assert_l64_parses(" 2147483657 ", INT64_C(2147483657), 10); - assert_l64_parses(" -2147483657 ", INT64_C(-2147483657), 10); - assert_l64_parses(" 9223372036854775807 ", INT64_MAX, 10); - assert_l64_parses(" -9223372036854775808 ", INT64_MIN, 10); - assert_l64_parses(" 0x7fffffffffffffff ", INT64_MAX, 16); - assert_l64_parses(" -0x8000000000000000 ", INT64_MIN, 16); - assert_l64_parses("1a", 26, 16); - assert_l64_parses("1A", 26, 16); - - assert_l64_fails("", 10); - assert_l64_fails("a", 10); - assert_l64_fails("x10x", 10); - assert_l64_fails("0x8000000000000000", 16); - assert_l64_fails("-0x8000000000000001", 16); -} - -void test_core_strtol__base_autodetection(void) -{ - assert_l64_parses("0", 0, 0); - assert_l64_parses("00", 0, 0); - assert_l64_parses("0x", 0, 0); - assert_l64_parses("0foobar", 0, 0); - assert_l64_parses("07", 7, 0); - assert_l64_parses("017", 15, 0); - assert_l64_parses("0x8", 8, 0); - assert_l64_parses("0x18", 24, 0); -} - -void test_core_strtol__buffer_length_with_autodetection_truncates(void) -{ - int64_t i64; - - cl_git_pass(git__strntol64(&i64, "011", 2, NULL, 0)); - cl_assert_equal_i(i64, 1); - cl_git_pass(git__strntol64(&i64, "0x11", 3, NULL, 0)); - cl_assert_equal_i(i64, 1); -} - -void test_core_strtol__buffer_length_truncates(void) -{ - int32_t i32; - int64_t i64; - - cl_git_pass(git__strntol32(&i32, "11", 1, NULL, 10)); - cl_assert_equal_i(i32, 1); - - cl_git_pass(git__strntol64(&i64, "11", 1, NULL, 10)); - cl_assert_equal_i(i64, 1); -} - -void test_core_strtol__buffer_length_with_leading_ws_truncates(void) -{ - int64_t i64; - - cl_git_fail(git__strntol64(&i64, " 1", 1, NULL, 10)); - - cl_git_pass(git__strntol64(&i64, " 11", 2, NULL, 10)); - cl_assert_equal_i(i64, 1); -} - -void test_core_strtol__buffer_length_with_leading_sign_truncates(void) -{ - int64_t i64; - - cl_git_fail(git__strntol64(&i64, "-1", 1, NULL, 10)); - - cl_git_pass(git__strntol64(&i64, "-11", 2, NULL, 10)); - cl_assert_equal_i(i64, -1); -} - -void test_core_strtol__error_message_cuts_off(void) -{ - assert_l32_fails("2147483657foobar", 10); - cl_assert(strstr(git_error_last()->message, "2147483657") != NULL); - cl_assert(strstr(git_error_last()->message, "foobar") == NULL); -} diff --git a/tests/core/structinit.c b/tests/core/structinit.c deleted file mode 100644 index 160e2f612..000000000 --- a/tests/core/structinit.c +++ /dev/null @@ -1,201 +0,0 @@ -#include "clar_libgit2.h" -#include -#include -#include -#include -#include -#include - -#define STRINGIFY(s) #s - -/* Checks two conditions for the specified structure: - * 1. That the initializers for the latest version produces the same - * in-memory representation. - * 2. That the function-based initializer supports all versions from 1...n, - * where n is the latest version (often represented by GIT_*_VERSION). - * - * Parameters: - * structname: The name of the structure to test, e.g. git_blame_options. - * structver: The latest version of the specified structure. - * macroinit: The macro that initializes the latest version of the structure. - * funcinitname: The function that initializes the structure. Must have the - * signature "int (structname* instance, int version)". - */ -#define CHECK_MACRO_FUNC_INIT_EQUAL(structname, structver, macroinit, funcinitname) \ -do { \ - structname structname##_macro_latest = macroinit; \ - structname structname##_func_latest; \ - int structname##_curr_ver = structver - 1; \ - memset(&structname##_func_latest, 0, sizeof(structname##_func_latest)); \ - cl_git_pass(funcinitname(&structname##_func_latest, structver)); \ - options_cmp(&structname##_macro_latest, &structname##_func_latest, \ - sizeof(structname), STRINGIFY(structname)); \ - \ - while (structname##_curr_ver > 0) \ - { \ - structname macro; \ - cl_git_pass(funcinitname(¯o, structname##_curr_ver)); \ - structname##_curr_ver--; \ - }\ -} while(0) - -static void options_cmp(void *one, void *two, size_t size, const char *name) -{ - size_t i; - - for (i = 0; i < size; i++) { - if (((char *)one)[i] != ((char *)two)[i]) { - char desc[1024]; - - p_snprintf(desc, 1024, "Difference in %s at byte %" PRIuZ ": macro=%u / func=%u", - name, i, ((char *)one)[i], ((char *)two)[i]); - clar__fail(__FILE__, __func__, __LINE__, - "Difference between macro and function options initializer", - desc, 0); - return; - } - } -} - -void test_core_structinit__compare(void) -{ - /* These tests assume that they can memcmp() two structures that were - * initialized with the same static initializer. Eg, - * git_blame_options = GIT_BLAME_OPTIONS_INIT; - * - * This assumption fails when there is padding between structure members, - * which is not guaranteed to be initialized to anything sane at all. - * - * Assume most compilers, in a debug build, will clear that memory for - * us or set it to sentinel markers. Etc. - */ -#if !defined(DEBUG) && !defined(_DEBUG) - clar__skip(); -#endif - - /* apply */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_apply_options, GIT_APPLY_OPTIONS_VERSION, \ - GIT_APPLY_OPTIONS_INIT, git_apply_options_init); - - /* blame */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_blame_options, GIT_BLAME_OPTIONS_VERSION, \ - GIT_BLAME_OPTIONS_INIT, git_blame_options_init); - - /* blob_filter_options */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_blob_filter_options, GIT_BLOB_FILTER_OPTIONS_VERSION, \ - GIT_BLOB_FILTER_OPTIONS_INIT, git_blob_filter_options_init); - - /* checkout */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_checkout_options, GIT_CHECKOUT_OPTIONS_VERSION, \ - GIT_CHECKOUT_OPTIONS_INIT, git_checkout_options_init); - - /* clone */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_clone_options, GIT_CLONE_OPTIONS_VERSION, \ - GIT_CLONE_OPTIONS_INIT, git_clone_options_init); - - /* commit_graph_writer */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_commit_graph_writer_options, \ - GIT_COMMIT_GRAPH_WRITER_OPTIONS_VERSION, \ - GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT, \ - git_commit_graph_writer_options_init); - - /* diff */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_diff_options, GIT_DIFF_OPTIONS_VERSION, \ - GIT_DIFF_OPTIONS_INIT, git_diff_options_init); - - /* diff_find */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_diff_find_options, GIT_DIFF_FIND_OPTIONS_VERSION, \ - GIT_DIFF_FIND_OPTIONS_INIT, git_diff_find_options_init); - - /* filter */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_filter, GIT_FILTER_VERSION, \ - GIT_FILTER_INIT, git_filter_init); - - /* merge_file_input */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_merge_file_input, GIT_MERGE_FILE_INPUT_VERSION, \ - GIT_MERGE_FILE_INPUT_INIT, git_merge_file_input_init); - - /* merge_file */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_merge_file_options, GIT_MERGE_FILE_OPTIONS_VERSION, \ - GIT_MERGE_FILE_OPTIONS_INIT, git_merge_file_options_init); - - /* merge_tree */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_merge_options, GIT_MERGE_OPTIONS_VERSION, \ - GIT_MERGE_OPTIONS_INIT, git_merge_options_init); - - /* push */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_push_options, GIT_PUSH_OPTIONS_VERSION, \ - GIT_PUSH_OPTIONS_INIT, git_push_options_init); - - /* remote */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_remote_callbacks, GIT_REMOTE_CALLBACKS_VERSION, \ - GIT_REMOTE_CALLBACKS_INIT, git_remote_init_callbacks); - - /* repository_init */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_repository_init_options, GIT_REPOSITORY_INIT_OPTIONS_VERSION, \ - GIT_REPOSITORY_INIT_OPTIONS_INIT, git_repository_init_options_init); - - /* revert */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_revert_options, GIT_REVERT_OPTIONS_VERSION, \ - GIT_REVERT_OPTIONS_INIT, git_revert_options_init); - - /* stash apply */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_stash_apply_options, GIT_STASH_APPLY_OPTIONS_VERSION, \ - GIT_STASH_APPLY_OPTIONS_INIT, git_stash_apply_options_init); - - /* status */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_status_options, GIT_STATUS_OPTIONS_VERSION, \ - GIT_STATUS_OPTIONS_INIT, git_status_options_init); - - /* transport */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_transport, GIT_TRANSPORT_VERSION, \ - GIT_TRANSPORT_INIT, git_transport_init); - - /* config_backend */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_config_backend, GIT_CONFIG_BACKEND_VERSION, \ - GIT_CONFIG_BACKEND_INIT, git_config_init_backend); - - /* odb_backend */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_odb_backend, GIT_ODB_BACKEND_VERSION, \ - GIT_ODB_BACKEND_INIT, git_odb_init_backend); - - /* refdb_backend */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_refdb_backend, GIT_REFDB_BACKEND_VERSION, \ - GIT_REFDB_BACKEND_INIT, git_refdb_init_backend); - - /* submodule update */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_submodule_update_options, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, \ - GIT_SUBMODULE_UPDATE_OPTIONS_INIT, git_submodule_update_options_init); - - /* submodule update */ - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_proxy_options, GIT_PROXY_OPTIONS_VERSION, \ - GIT_PROXY_OPTIONS_INIT, git_proxy_options_init); - - CHECK_MACRO_FUNC_INIT_EQUAL( \ - git_diff_patchid_options, GIT_DIFF_PATCHID_OPTIONS_VERSION, \ - GIT_DIFF_PATCHID_OPTIONS_INIT, git_diff_patchid_options_init); -} diff --git a/tests/core/useragent.c b/tests/core/useragent.c deleted file mode 100644 index a4ece902f..000000000 --- a/tests/core/useragent.c +++ /dev/null @@ -1,17 +0,0 @@ -#include "clar_libgit2.h" -#include "settings.h" - -void test_core_useragent__get(void) -{ - const char *custom_name = "super duper git"; - git_str buf = GIT_STR_INIT; - - cl_assert_equal_p(NULL, git_libgit2__user_agent()); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, custom_name)); - cl_assert_equal_s(custom_name, git_libgit2__user_agent()); - - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT, &buf)); - cl_assert_equal_s(custom_name, buf.ptr); - - git_str_dispose(&buf); -} diff --git a/tests/core/utf8.c b/tests/core/utf8.c deleted file mode 100644 index e1987b8d6..000000000 --- a/tests/core/utf8.c +++ /dev/null @@ -1,20 +0,0 @@ -#include "clar_libgit2.h" -#include "utf8.h" - -void test_core_utf8__char_length(void) -{ - cl_assert_equal_i(0, git_utf8_char_length("", 0)); - cl_assert_equal_i(1, git_utf8_char_length("$", 1)); - cl_assert_equal_i(5, git_utf8_char_length("abcde", 5)); - cl_assert_equal_i(1, git_utf8_char_length("\xc2\xa2", 2)); - cl_assert_equal_i(2, git_utf8_char_length("\x24\xc2\xa2", 3)); - cl_assert_equal_i(1, git_utf8_char_length("\xf0\x90\x8d\x88", 4)); - - /* uncontinued character counted as single characters */ - cl_assert_equal_i(2, git_utf8_char_length("\x24\xc2", 2)); - cl_assert_equal_i(3, git_utf8_char_length("\x24\xc2\xc2\xa2", 4)); - - /* invalid characters are counted as single characters */ - cl_assert_equal_i(4, git_utf8_char_length("\x24\xc0\xc0\x34", 4)); - cl_assert_equal_i(4, git_utf8_char_length("\x24\xf5\xfd\xc2", 4)); -} diff --git a/tests/core/vector.c b/tests/core/vector.c deleted file mode 100644 index 08cd2c19b..000000000 --- a/tests/core/vector.c +++ /dev/null @@ -1,430 +0,0 @@ -#include - -#include "clar_libgit2.h" -#include "vector.h" - -/* initial size of 1 would cause writing past array bounds */ -void test_core_vector__0(void) -{ - git_vector x; - int i; - cl_git_pass(git_vector_init(&x, 1, NULL)); - for (i = 0; i < 10; ++i) { - git_vector_insert(&x, (void*) 0xabc); - } - git_vector_free(&x); -} - - -/* don't read past array bounds on remove() */ -void test_core_vector__1(void) -{ - git_vector x; - /* make initial capacity exact for our insertions. */ - cl_git_pass(git_vector_init(&x, 3, NULL)); - git_vector_insert(&x, (void*) 0xabc); - git_vector_insert(&x, (void*) 0xdef); - git_vector_insert(&x, (void*) 0x123); - - git_vector_remove(&x, 0); /* used to read past array bounds. */ - git_vector_free(&x); -} - - -static int test_cmp(const void *a, const void *b) -{ - return *(const int *)a - *(const int *)b; -} - -/* remove duplicates */ -void test_core_vector__2(void) -{ - git_vector x; - int *ptrs[2]; - - ptrs[0] = git__malloc(sizeof(int)); - ptrs[1] = git__malloc(sizeof(int)); - - *ptrs[0] = 2; - *ptrs[1] = 1; - - cl_git_pass(git_vector_init(&x, 5, test_cmp)); - cl_git_pass(git_vector_insert(&x, ptrs[0])); - cl_git_pass(git_vector_insert(&x, ptrs[1])); - cl_git_pass(git_vector_insert(&x, ptrs[1])); - cl_git_pass(git_vector_insert(&x, ptrs[0])); - cl_git_pass(git_vector_insert(&x, ptrs[1])); - cl_assert(x.length == 5); - - git_vector_uniq(&x, NULL); - cl_assert(x.length == 2); - - git_vector_free(&x); - - git__free(ptrs[0]); - git__free(ptrs[1]); -} - - -static int compare_them(const void *a, const void *b) -{ - return (int)((intptr_t)a - (intptr_t)b); -} - -/* insert_sorted */ -void test_core_vector__3(void) -{ - git_vector x; - intptr_t i; - cl_git_pass(git_vector_init(&x, 1, &compare_them)); - - for (i = 0; i < 10; i += 2) { - git_vector_insert_sorted(&x, (void*)(i + 1), NULL); - } - - for (i = 9; i > 0; i -= 2) { - git_vector_insert_sorted(&x, (void*)(i + 1), NULL); - } - - cl_assert(x.length == 10); - for (i = 0; i < 10; ++i) { - cl_assert(git_vector_get(&x, i) == (void*)(i + 1)); - } - - git_vector_free(&x); -} - -/* insert_sorted with duplicates */ -void test_core_vector__4(void) -{ - git_vector x; - intptr_t i; - cl_git_pass(git_vector_init(&x, 1, &compare_them)); - - for (i = 0; i < 10; i += 2) { - git_vector_insert_sorted(&x, (void*)(i + 1), NULL); - } - - for (i = 9; i > 0; i -= 2) { - git_vector_insert_sorted(&x, (void*)(i + 1), NULL); - } - - for (i = 0; i < 10; i += 2) { - git_vector_insert_sorted(&x, (void*)(i + 1), NULL); - } - - for (i = 9; i > 0; i -= 2) { - git_vector_insert_sorted(&x, (void*)(i + 1), NULL); - } - - cl_assert(x.length == 20); - for (i = 0; i < 20; ++i) { - cl_assert(git_vector_get(&x, i) == (void*)(i / 2 + 1)); - } - - git_vector_free(&x); -} - -typedef struct { - int content; - int count; -} my_struct; - -static int _struct_count = 0; - -static int compare_structs(const void *a, const void *b) -{ - return ((const my_struct *)a)->content - - ((const my_struct *)b)->content; -} - -static int merge_structs(void **old_raw, void *new) -{ - my_struct *old = *(my_struct **)old_raw; - cl_assert(((my_struct *)old)->content == ((my_struct *)new)->content); - ((my_struct *)old)->count += 1; - git__free(new); - _struct_count--; - return GIT_EEXISTS; -} - -static my_struct *alloc_struct(int value) -{ - my_struct *st = git__malloc(sizeof(my_struct)); - st->content = value; - st->count = 0; - _struct_count++; - return st; -} - -/* insert_sorted with duplicates and special handling */ -void test_core_vector__5(void) -{ - git_vector x; - int i; - - cl_git_pass(git_vector_init(&x, 1, &compare_structs)); - - for (i = 0; i < 10; i += 2) - git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); - - for (i = 9; i > 0; i -= 2) - git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); - - cl_assert(x.length == 10); - cl_assert(_struct_count == 10); - - for (i = 0; i < 10; i += 2) - git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); - - for (i = 9; i > 0; i -= 2) - git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); - - cl_assert(x.length == 10); - cl_assert(_struct_count == 10); - - for (i = 0; i < 10; ++i) { - cl_assert(((my_struct *)git_vector_get(&x, i))->content == i); - git__free(git_vector_get(&x, i)); - _struct_count--; - } - - git_vector_free(&x); -} - -static int remove_ones(const git_vector *v, size_t idx, void *p) -{ - GIT_UNUSED(p); - return (git_vector_get(v, idx) == (void *)0x001); -} - -/* Test removal based on callback */ -void test_core_vector__remove_matching(void) -{ - git_vector x; - size_t i; - void *compare; - - cl_git_pass(git_vector_init(&x, 1, NULL)); - git_vector_insert(&x, (void*) 0x001); - - cl_assert(x.length == 1); - git_vector_remove_matching(&x, remove_ones, NULL); - cl_assert(x.length == 0); - - git_vector_insert(&x, (void*) 0x001); - git_vector_insert(&x, (void*) 0x001); - git_vector_insert(&x, (void*) 0x001); - - cl_assert(x.length == 3); - git_vector_remove_matching(&x, remove_ones, NULL); - cl_assert(x.length == 0); - - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x001); - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x001); - - cl_assert(x.length == 4); - git_vector_remove_matching(&x, remove_ones, NULL); - cl_assert(x.length == 2); - - git_vector_foreach(&x, i, compare) { - cl_assert(compare != (void *)0x001); - } - - git_vector_clear(&x); - - git_vector_insert(&x, (void*) 0x001); - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x001); - - cl_assert(x.length == 4); - git_vector_remove_matching(&x, remove_ones, NULL); - cl_assert(x.length == 2); - - git_vector_foreach(&x, i, compare) { - cl_assert(compare != (void *)0x001); - } - - git_vector_clear(&x); - - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x001); - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x001); - - cl_assert(x.length == 4); - git_vector_remove_matching(&x, remove_ones, NULL); - cl_assert(x.length == 2); - - git_vector_foreach(&x, i, compare) { - cl_assert(compare != (void *)0x001); - } - - git_vector_clear(&x); - - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x003); - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x003); - - cl_assert(x.length == 4); - git_vector_remove_matching(&x, remove_ones, NULL); - cl_assert(x.length == 4); - - git_vector_free(&x); -} - -static void assert_vector(git_vector *x, void *expected[], size_t len) -{ - size_t i; - - cl_assert_equal_i(len, x->length); - - for (i = 0; i < len; i++) - cl_assert(expected[i] == x->contents[i]); -} - -void test_core_vector__grow_and_shrink(void) -{ - git_vector x = GIT_VECTOR_INIT; - void *expected1[] = { - (void *)0x02, (void *)0x03, (void *)0x04, (void *)0x05, - (void *)0x06, (void *)0x07, (void *)0x08, (void *)0x09, - (void *)0x0a - }; - void *expected2[] = { - (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06, - (void *)0x07, (void *)0x08, (void *)0x09, (void *)0x0a - }; - void *expected3[] = { - (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06, - (void *)0x0a - }; - void *expected4[] = { - (void *)0x02, (void *)0x04, (void *)0x05 - }; - void *expected5[] = { - (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, - (void *)0x05 - }; - void *expected6[] = { - (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, - (void *)0x05, (void *)0x00 - }; - void *expected7[] = { - (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, - (void *)0x00, (void *)0x00, (void *)0x00, (void *)0x05, - (void *)0x00 - }; - void *expected8[] = { - (void *)0x04, (void *)0x00, (void *)0x00, (void *)0x00, - (void *)0x05, (void *)0x00 - }; - void *expected9[] = { - (void *)0x04, (void *)0x00, (void *)0x05, (void *)0x00 - }; - void *expectedA[] = { (void *)0x04, (void *)0x00 }; - void *expectedB[] = { (void *)0x04 }; - - git_vector_insert(&x, (void *)0x01); - git_vector_insert(&x, (void *)0x02); - git_vector_insert(&x, (void *)0x03); - git_vector_insert(&x, (void *)0x04); - git_vector_insert(&x, (void *)0x05); - git_vector_insert(&x, (void *)0x06); - git_vector_insert(&x, (void *)0x07); - git_vector_insert(&x, (void *)0x08); - git_vector_insert(&x, (void *)0x09); - git_vector_insert(&x, (void *)0x0a); - - git_vector_remove_range(&x, 0, 1); - assert_vector(&x, expected1, ARRAY_SIZE(expected1)); - - git_vector_remove_range(&x, 1, 1); - assert_vector(&x, expected2, ARRAY_SIZE(expected2)); - - git_vector_remove_range(&x, 4, 3); - assert_vector(&x, expected3, ARRAY_SIZE(expected3)); - - git_vector_remove_range(&x, 3, 2); - assert_vector(&x, expected4, ARRAY_SIZE(expected4)); - - git_vector_insert_null(&x, 0, 2); - assert_vector(&x, expected5, ARRAY_SIZE(expected5)); - - git_vector_insert_null(&x, 5, 1); - assert_vector(&x, expected6, ARRAY_SIZE(expected6)); - - git_vector_insert_null(&x, 4, 3); - assert_vector(&x, expected7, ARRAY_SIZE(expected7)); - - git_vector_remove_range(&x, 0, 3); - assert_vector(&x, expected8, ARRAY_SIZE(expected8)); - - git_vector_remove_range(&x, 1, 2); - assert_vector(&x, expected9, ARRAY_SIZE(expected9)); - - git_vector_remove_range(&x, 2, 2); - assert_vector(&x, expectedA, ARRAY_SIZE(expectedA)); - - git_vector_remove_range(&x, 1, 1); - assert_vector(&x, expectedB, ARRAY_SIZE(expectedB)); - - git_vector_remove_range(&x, 0, 1); - assert_vector(&x, NULL, 0); - - git_vector_free(&x); -} - -void test_core_vector__reverse(void) -{ - git_vector v = GIT_VECTOR_INIT; - size_t i; - - void *in1[] = {(void *) 0x0, (void *) 0x1, (void *) 0x2, (void *) 0x3}; - void *out1[] = {(void *) 0x3, (void *) 0x2, (void *) 0x1, (void *) 0x0}; - - void *in2[] = {(void *) 0x0, (void *) 0x1, (void *) 0x2, (void *) 0x3, (void *) 0x4}; - void *out2[] = {(void *) 0x4, (void *) 0x3, (void *) 0x2, (void *) 0x1, (void *) 0x0}; - - for (i = 0; i < 4; i++) - cl_git_pass(git_vector_insert(&v, in1[i])); - - git_vector_reverse(&v); - - for (i = 0; i < 4; i++) - cl_assert_equal_p(out1[i], git_vector_get(&v, i)); - - git_vector_clear(&v); - for (i = 0; i < 5; i++) - cl_git_pass(git_vector_insert(&v, in2[i])); - - git_vector_reverse(&v); - - for (i = 0; i < 5; i++) - cl_assert_equal_p(out2[i], git_vector_get(&v, i)); - - git_vector_free(&v); -} - -void test_core_vector__dup_empty_vector(void) -{ - git_vector v = GIT_VECTOR_INIT; - git_vector dup = GIT_VECTOR_INIT; - int dummy; - - cl_assert_equal_i(0, v.length); - - cl_git_pass(git_vector_dup(&dup, &v, v._cmp)); - cl_assert_equal_i(0, dup._alloc_size); - cl_assert_equal_i(0, dup.length); - - cl_git_pass(git_vector_insert(&dup, &dummy)); - cl_assert_equal_i(8, dup._alloc_size); - cl_assert_equal_i(1, dup.length); - - git_vector_free(&dup); -} diff --git a/tests/core/wildmatch.c b/tests/core/wildmatch.c deleted file mode 100644 index 7c56ee7b8..000000000 --- a/tests/core/wildmatch.c +++ /dev/null @@ -1,248 +0,0 @@ -#include "clar_libgit2.h" - -#include "wildmatch.h" - -#define assert_matches(string, pattern, wildmatch, iwildmatch, pathmatch, ipathmatch) \ - assert_matches_(string, pattern, wildmatch, iwildmatch, pathmatch, ipathmatch, __FILE__, __func__, __LINE__) - -static void assert_matches_(const char *string, const char *pattern, - char expected_wildmatch, char expected_iwildmatch, - char expected_pathmatch, char expected_ipathmatch, - const char *file, const char *func, size_t line) -{ - if (wildmatch(pattern, string, WM_PATHNAME) == expected_wildmatch) - clar__fail(file, func, line, "Test failed (wildmatch).", string, 1); - if (wildmatch(pattern, string, WM_PATHNAME|WM_CASEFOLD) == expected_iwildmatch) - clar__fail(file, func, line, "Test failed (iwildmatch).", string, 1); - if (wildmatch(pattern, string, 0) == expected_pathmatch) - clar__fail(file, func, line, "Test failed (pathmatch).", string, 1); - if (wildmatch(pattern, string, WM_CASEFOLD) == expected_ipathmatch) - clar__fail(file, func, line, "Test failed (ipathmatch).", string, 1); -} - -/* - * Below testcases are imported from git.git, t3070-wildmatch,sh at tag v2.22.0. - * Note that we've only imported the direct wildcard tests, but not the matching - * tests for git-ls-files. - */ - -void test_core_wildmatch__basic_wildmatch(void) -{ - assert_matches("foo", "foo", 1, 1, 1, 1); - assert_matches("foo", "bar", 0, 0, 0, 0); - assert_matches("", "", 1, 1, 1, 1); - assert_matches("foo", "???", 1, 1, 1, 1); - assert_matches("foo", "??", 0, 0, 0, 0); - assert_matches("foo", "*", 1, 1, 1, 1); - assert_matches("foo", "f*", 1, 1, 1, 1); - assert_matches("foo", "*f", 0, 0, 0, 0); - assert_matches("foo", "*foo*", 1, 1, 1, 1); - assert_matches("foobar", "*ob*a*r*", 1, 1, 1, 1); - assert_matches("aaaaaaabababab", "*ab", 1, 1, 1, 1); - assert_matches("foo*", "foo\\*", 1, 1, 1, 1); - assert_matches("foobar", "foo\\*bar", 0, 0, 0, 0); - assert_matches("f\\oo", "f\\\\oo", 1, 1, 1, 1); - assert_matches("ball", "*[al]?", 1, 1, 1, 1); - assert_matches("ten", "[ten]", 0, 0, 0, 0); - assert_matches("ten", "**[!te]", 1, 1, 1, 1); - assert_matches("ten", "**[!ten]", 0, 0, 0, 0); - assert_matches("ten", "t[a-g]n", 1, 1, 1, 1); - assert_matches("ten", "t[!a-g]n", 0, 0, 0, 0); - assert_matches("ton", "t[!a-g]n", 1, 1, 1, 1); - assert_matches("ton", "t[^a-g]n", 1, 1, 1, 1); - assert_matches("a]b", "a[]]b", 1, 1, 1, 1); - assert_matches("a-b", "a[]-]b", 1, 1, 1, 1); - assert_matches("a]b", "a[]-]b", 1, 1, 1, 1); - assert_matches("aab", "a[]-]b", 0, 0, 0, 0); - assert_matches("aab", "a[]a-]b", 1, 1, 1, 1); - assert_matches("]", "]", 1, 1, 1, 1); -} - -void test_core_wildmatch__slash_matching_features(void) -{ - assert_matches("foo/baz/bar", "foo*bar", 0, 0, 1, 1); - assert_matches("foo/baz/bar", "foo**bar", 0, 0, 1, 1); - assert_matches("foobazbar", "foo**bar", 1, 1, 1, 1); - assert_matches("foo/baz/bar", "foo/**/bar", 1, 1, 1, 1); - assert_matches("foo/baz/bar", "foo/**/**/bar", 1, 1, 0, 0); - assert_matches("foo/b/a/z/bar", "foo/**/bar", 1, 1, 1, 1); - assert_matches("foo/b/a/z/bar", "foo/**/**/bar", 1, 1, 1, 1); - assert_matches("foo/bar", "foo/**/bar", 1, 1, 0, 0); - assert_matches("foo/bar", "foo/**/**/bar", 1, 1, 0, 0); - assert_matches("foo/bar", "foo?bar", 0, 0, 1, 1); - assert_matches("foo/bar", "foo[/]bar", 0, 0, 1, 1); - assert_matches("foo/bar", "foo[^a-z]bar", 0, 0, 1, 1); - assert_matches("foo/bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r", 0, 0, 1, 1); - assert_matches("foo-bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r", 1, 1, 1, 1); - assert_matches("foo", "**/foo", 1, 1, 0, 0); - assert_matches("XXX/foo", "**/foo", 1, 1, 1, 1); - assert_matches("bar/baz/foo", "**/foo", 1, 1, 1, 1); - assert_matches("bar/baz/foo", "*/foo", 0, 0, 1, 1); - assert_matches("foo/bar/baz", "**/bar*", 0, 0, 1, 1); - assert_matches("deep/foo/bar/baz", "**/bar/*", 1, 1, 1, 1); - assert_matches("deep/foo/bar/baz/", "**/bar/*", 0, 0, 1, 1); - assert_matches("deep/foo/bar/baz/", "**/bar/**", 1, 1, 1, 1); - assert_matches("deep/foo/bar", "**/bar/*", 0, 0, 0, 0); - assert_matches("deep/foo/bar/", "**/bar/**", 1, 1, 1, 1); - assert_matches("foo/bar/baz", "**/bar**", 0, 0, 1, 1); - assert_matches("foo/bar/baz/x", "*/bar/**", 1, 1, 1, 1); - assert_matches("deep/foo/bar/baz/x", "*/bar/**", 0, 0, 1, 1); - assert_matches("deep/foo/bar/baz/x", "**/bar/*/*", 1, 1, 1, 1); -} - -void test_core_wildmatch__various_additional(void) -{ - assert_matches("acrt", "a[c-c]st", 0, 0, 0, 0); - assert_matches("acrt", "a[c-c]rt", 1, 1, 1, 1); - assert_matches("]", "[!]-]", 0, 0, 0, 0); - assert_matches("a", "[!]-]", 1, 1, 1, 1); - assert_matches("", "\\", 0, 0, 0, 0); - assert_matches("\\", "\\", 0, 0, 0, 0); - assert_matches("XXX/\\", "*/\\", 0, 0, 0, 0); - assert_matches("XXX/\\", "*/\\\\", 1, 1, 1, 1); - assert_matches("foo", "foo", 1, 1, 1, 1); - assert_matches("@foo", "@foo", 1, 1, 1, 1); - assert_matches("foo", "@foo", 0, 0, 0, 0); - assert_matches("[ab]", "\\[ab]", 1, 1, 1, 1); - assert_matches("[ab]", "[[]ab]", 1, 1, 1, 1); - assert_matches("[ab]", "[[:]ab]", 1, 1, 1, 1); - assert_matches("[ab]", "[[::]ab]", 0, 0, 0, 0); - assert_matches("[ab]", "[[:digit]ab]", 1, 1, 1, 1); - assert_matches("[ab]", "[\\[:]ab]", 1, 1, 1, 1); - assert_matches("?a?b", "\\??\\?b", 1, 1, 1, 1); - assert_matches("abc", "\\a\\b\\c", 1, 1, 1, 1); - assert_matches("foo", "", 0, 0, 0, 0); - assert_matches("foo/bar/baz/to", "**/t[o]", 1, 1, 1, 1); -} - -void test_core_wildmatch__character_classes(void) -{ - assert_matches("a1B", "[[:alpha:]][[:digit:]][[:upper:]]", 1, 1, 1, 1); - assert_matches("a", "[[:digit:][:upper:][:space:]]", 0, 1, 0, 1); - assert_matches("A", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); - assert_matches("1", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); - assert_matches("1", "[[:digit:][:upper:][:spaci:]]", 0, 0, 0, 0); - assert_matches(" ", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); - assert_matches(".", "[[:digit:][:upper:][:space:]]", 0, 0, 0, 0); - assert_matches(".", "[[:digit:][:punct:][:space:]]", 1, 1, 1, 1); - assert_matches("5", "[[:xdigit:]]", 1, 1, 1, 1); - assert_matches("f", "[[:xdigit:]]", 1, 1, 1, 1); - assert_matches("D", "[[:xdigit:]]", 1, 1, 1, 1); - assert_matches("_", "[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]", 1, 1, 1, 1); - assert_matches(".", "[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]", 1, 1, 1, 1); - assert_matches("5", "[a-c[:digit:]x-z]", 1, 1, 1, 1); - assert_matches("b", "[a-c[:digit:]x-z]", 1, 1, 1, 1); - assert_matches("y", "[a-c[:digit:]x-z]", 1, 1, 1, 1); - assert_matches("q", "[a-c[:digit:]x-z]", 0, 0, 0, 0); -} - -void test_core_wildmatch__additional_with_malformed(void) -{ - assert_matches("]", "[\\\\-^]", 1, 1, 1, 1); - assert_matches("[", "[\\\\-^]", 0, 0, 0, 0); - assert_matches("-", "[\\-_]", 1, 1, 1, 1); - assert_matches("]", "[\\]]", 1, 1, 1, 1); - assert_matches("\\]", "[\\]]", 0, 0, 0, 0); - assert_matches("\\", "[\\]]", 0, 0, 0, 0); - assert_matches("ab", "a[]b", 0, 0, 0, 0); - assert_matches("a[]b", "a[]b", 0, 0, 0, 0); - assert_matches("ab[", "ab[", 0, 0, 0, 0); - assert_matches("ab", "[!", 0, 0, 0, 0); - assert_matches("ab", "[-", 0, 0, 0, 0); - assert_matches("-", "[-]", 1, 1, 1, 1); - assert_matches("-", "[a-", 0, 0, 0, 0); - assert_matches("-", "[!a-", 0, 0, 0, 0); - assert_matches("-", "[--A]", 1, 1, 1, 1); - assert_matches("5", "[--A]", 1, 1, 1, 1); - assert_matches(" ", "[ --]", 1, 1, 1, 1); - assert_matches("$", "[ --]", 1, 1, 1, 1); - assert_matches("-", "[ --]", 1, 1, 1, 1); - assert_matches("0", "[ --]", 0, 0, 0, 0); - assert_matches("-", "[---]", 1, 1, 1, 1); - assert_matches("-", "[------]", 1, 1, 1, 1); - assert_matches("j", "[a-e-n]", 0, 0, 0, 0); - assert_matches("-", "[a-e-n]", 1, 1, 1, 1); - assert_matches("a", "[!------]", 1, 1, 1, 1); - assert_matches("[", "[]-a]", 0, 0, 0, 0); - assert_matches("^", "[]-a]", 1, 1, 1, 1); - assert_matches("^", "[!]-a]", 0, 0, 0, 0); - assert_matches("[", "[!]-a]", 1, 1, 1, 1); - assert_matches("^", "[a^bc]", 1, 1, 1, 1); - assert_matches("-b]", "[a-]b]", 1, 1, 1, 1); - assert_matches("\\", "[\\]", 0, 0, 0, 0); - assert_matches("\\", "[\\\\]", 1, 1, 1, 1); - assert_matches("\\", "[!\\\\]", 0, 0, 0, 0); - assert_matches("G", "[A-\\\\]", 1, 1, 1, 1); - assert_matches("aaabbb", "b*a", 0, 0, 0, 0); - assert_matches("aabcaa", "*ba*", 0, 0, 0, 0); - assert_matches(",", "[,]", 1, 1, 1, 1); - assert_matches(",", "[\\\\,]", 1, 1, 1, 1); - assert_matches("\\", "[\\\\,]", 1, 1, 1, 1); - assert_matches("-", "[,-.]", 1, 1, 1, 1); - assert_matches("+", "[,-.]", 0, 0, 0, 0); - assert_matches("-.]", "[,-.]", 0, 0, 0, 0); - assert_matches("2", "[\\1-\\3]", 1, 1, 1, 1); - assert_matches("3", "[\\1-\\3]", 1, 1, 1, 1); - assert_matches("4", "[\\1-\\3]", 0, 0, 0, 0); - assert_matches("\\", "[[-\\]]", 1, 1, 1, 1); - assert_matches("[", "[[-\\]]", 1, 1, 1, 1); - assert_matches("]", "[[-\\]]", 1, 1, 1, 1); - assert_matches("-", "[[-\\]]", 0, 0, 0, 0); -} - -void test_core_wildmatch__recursion(void) -{ - assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 1, 1, 1, 1); - assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 0, 0, 0, 0); - assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 0, 0, 0, 0); - assert_matches("XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*", 1, 1, 1, 1); - assert_matches("XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*", 0, 0, 0, 0); - assert_matches("abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt", "**/*a*b*g*n*t", 1, 1, 1, 1); - assert_matches("abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz", "**/*a*b*g*n*t", 0, 0, 0, 0); - assert_matches("foo", "*/*/*", 0, 0, 0, 0); - assert_matches("foo/bar", "*/*/*", 0, 0, 0, 0); - assert_matches("foo/bba/arr", "*/*/*", 1, 1, 1, 1); - assert_matches("foo/bb/aa/rr", "*/*/*", 0, 0, 1, 1); - assert_matches("foo/bb/aa/rr", "**/**/**", 1, 1, 1, 1); - assert_matches("abcXdefXghi", "*X*i", 1, 1, 1, 1); - assert_matches("ab/cXd/efXg/hi", "*X*i", 0, 0, 1, 1); - assert_matches("ab/cXd/efXg/hi", "*/*X*/*/*i", 1, 1, 1, 1); - assert_matches("ab/cXd/efXg/hi", "**/*X*/**/*i", 1, 1, 1, 1); -} - -void test_core_wildmatch__pathmatch(void) -{ - assert_matches("foo", "fo", 0, 0, 0, 0); - assert_matches("foo/bar", "foo/bar", 1, 1, 1, 1); - assert_matches("foo/bar", "foo/*", 1, 1, 1, 1); - assert_matches("foo/bba/arr", "foo/*", 0, 0, 1, 1); - assert_matches("foo/bba/arr", "foo/**", 1, 1, 1, 1); - assert_matches("foo/bba/arr", "foo*", 0, 0, 1, 1); - assert_matches("foo/bba/arr", "foo**", 0, 0, 1, 1); - assert_matches("foo/bba/arr", "foo/*arr", 0, 0, 1, 1); - assert_matches("foo/bba/arr", "foo/**arr", 0, 0, 1, 1); - assert_matches("foo/bba/arr", "foo/*z", 0, 0, 0, 0); - assert_matches("foo/bba/arr", "foo/**z", 0, 0, 0, 0); - assert_matches("foo/bar", "foo?bar", 0, 0, 1, 1); - assert_matches("foo/bar", "foo[/]bar", 0, 0, 1, 1); - assert_matches("foo/bar", "foo[^a-z]bar", 0, 0, 1, 1); - assert_matches("ab/cXd/efXg/hi", "*Xg*i", 0, 0, 1, 1); -} - -void test_core_wildmatch__case_sensitivity(void) -{ - assert_matches("a", "[A-Z]", 0, 1, 0, 1); - assert_matches("A", "[A-Z]", 1, 1, 1, 1); - assert_matches("A", "[a-z]", 0, 1, 0, 1); - assert_matches("a", "[a-z]", 1, 1, 1, 1); - assert_matches("a", "[[:upper:]]", 0, 1, 0, 1); - assert_matches("A", "[[:upper:]]", 1, 1, 1, 1); - assert_matches("A", "[[:lower:]]", 0, 1, 0, 1); - assert_matches("a", "[[:lower:]]", 1, 1, 1, 1); - assert_matches("A", "[B-Za]", 0, 1, 0, 1); - assert_matches("a", "[B-Za]", 1, 1, 1, 1); - assert_matches("A", "[B-a]", 0, 1, 0, 1); - assert_matches("a", "[B-a]", 1, 1, 1, 1); - assert_matches("z", "[Z-y]", 0, 1, 0, 1); - assert_matches("Z", "[Z-y]", 1, 1, 1, 1); -} diff --git a/tests/core/zstream.c b/tests/core/zstream.c deleted file mode 100644 index c22e81008..000000000 --- a/tests/core/zstream.c +++ /dev/null @@ -1,167 +0,0 @@ -#include "clar_libgit2.h" -#include "zstream.h" - -static const char *data = "This is a test test test of This is a test"; - -#define INFLATE_EXTRA 2 - -static void assert_zlib_equal_( - const void *expected, size_t e_len, - const void *compressed, size_t c_len, - const char *msg, const char *file, const char *func, int line) -{ - z_stream stream; - char *expanded = git__calloc(1, e_len + INFLATE_EXTRA); - cl_assert(expanded); - - memset(&stream, 0, sizeof(stream)); - stream.next_out = (Bytef *)expanded; - stream.avail_out = (uInt)(e_len + INFLATE_EXTRA); - stream.next_in = (Bytef *)compressed; - stream.avail_in = (uInt)c_len; - - cl_assert(inflateInit(&stream) == Z_OK); - cl_assert(inflate(&stream, Z_FINISH)); - inflateEnd(&stream); - - clar__assert_equal( - file, func, line, msg, 1, - "%d", (int)stream.total_out, (int)e_len); - clar__assert_equal( - file, func, line, "Buffer len was not exact match", 1, - "%d", (int)stream.avail_out, (int)INFLATE_EXTRA); - - clar__assert( - memcmp(expanded, expected, e_len) == 0, - file, func, line, "uncompressed data did not match", NULL, 1); - - git__free(expanded); -} - -#define assert_zlib_equal(E,EL,C,CL) \ - assert_zlib_equal_(E, EL, C, CL, #EL " != " #CL, __FILE__, __func__, (int)__LINE__) - -void test_core_zstream__basic(void) -{ - git_zstream z = GIT_ZSTREAM_INIT; - char out[128]; - size_t outlen = sizeof(out); - - cl_git_pass(git_zstream_init(&z, GIT_ZSTREAM_DEFLATE)); - cl_git_pass(git_zstream_set_input(&z, data, strlen(data) + 1)); - cl_git_pass(git_zstream_get_output(out, &outlen, &z)); - cl_assert(git_zstream_done(&z)); - cl_assert(outlen > 0); - git_zstream_free(&z); - - assert_zlib_equal(data, strlen(data) + 1, out, outlen); -} - -void test_core_zstream__fails_on_trailing_garbage(void) -{ - git_str deflated = GIT_STR_INIT, inflated = GIT_STR_INIT; - char i = 0; - - /* compress a simple string */ - git_zstream_deflatebuf(&deflated, "foobar!!", 8); - - /* append some garbage */ - for (i = 0; i < 10; i++) { - git_str_putc(&deflated, i); - } - - cl_git_fail(git_zstream_inflatebuf(&inflated, deflated.ptr, deflated.size)); - - git_str_dispose(&deflated); - git_str_dispose(&inflated); -} - -void test_core_zstream__buffer(void) -{ - git_str out = GIT_STR_INIT; - cl_git_pass(git_zstream_deflatebuf(&out, data, strlen(data) + 1)); - assert_zlib_equal(data, strlen(data) + 1, out.ptr, out.size); - git_str_dispose(&out); -} - -#define BIG_STRING_PART "Big Data IS Big - Long Data IS Long - We need a buffer larger than 1024 x 1024 to make sure we trigger chunked compression - Big Big Data IS Bigger than Big - Long Long Data IS Longer than Long" - -static void compress_and_decompress_input_various_ways(git_str *input) -{ - git_str out1 = GIT_STR_INIT, out2 = GIT_STR_INIT; - git_str inflated = GIT_STR_INIT; - size_t i, fixed_size = max(input->size / 2, 256); - char *fixed = git__malloc(fixed_size); - cl_assert(fixed); - - /* compress with deflatebuf */ - - cl_git_pass(git_zstream_deflatebuf(&out1, input->ptr, input->size)); - assert_zlib_equal(input->ptr, input->size, out1.ptr, out1.size); - - /* compress with various fixed size buffer (accumulating the output) */ - - for (i = 0; i < 3; ++i) { - git_zstream zs = GIT_ZSTREAM_INIT; - size_t use_fixed_size; - - switch (i) { - case 0: use_fixed_size = 256; break; - case 1: use_fixed_size = fixed_size / 2; break; - case 2: use_fixed_size = fixed_size; break; - } - cl_assert(use_fixed_size <= fixed_size); - - cl_git_pass(git_zstream_init(&zs, GIT_ZSTREAM_DEFLATE)); - cl_git_pass(git_zstream_set_input(&zs, input->ptr, input->size)); - - while (!git_zstream_done(&zs)) { - size_t written = use_fixed_size; - cl_git_pass(git_zstream_get_output(fixed, &written, &zs)); - cl_git_pass(git_str_put(&out2, fixed, written)); - } - - git_zstream_free(&zs); - assert_zlib_equal(input->ptr, input->size, out2.ptr, out2.size); - - /* did both approaches give the same data? */ - cl_assert_equal_sz(out1.size, out2.size); - cl_assert(!memcmp(out1.ptr, out2.ptr, out1.size)); - - git_str_dispose(&out2); - } - - cl_git_pass(git_zstream_inflatebuf(&inflated, out1.ptr, out1.size)); - cl_assert_equal_i(input->size, inflated.size); - cl_assert(memcmp(input->ptr, inflated.ptr, inflated.size) == 0); - - git_str_dispose(&out1); - git_str_dispose(&inflated); - git__free(fixed); -} - -void test_core_zstream__big_data(void) -{ - git_str in = GIT_STR_INIT; - size_t scan, target; - - for (target = 1024; target <= 1024 * 1024 * 4; target *= 8) { - - /* make a big string that's easy to compress */ - git_str_clear(&in); - while (in.size < target) - cl_git_pass( - git_str_put(&in, BIG_STRING_PART, strlen(BIG_STRING_PART))); - - compress_and_decompress_input_various_ways(&in); - - /* make a big string that's hard to compress */ - srand(0xabad1dea); - for (scan = 0; scan < in.size; ++scan) - in.ptr[scan] = (char)rand(); - - compress_and_decompress_input_various_ways(&in); - } - - git_str_dispose(&in); -} diff --git a/tests/date/date.c b/tests/date/date.c deleted file mode 100644 index 82b5c6728..000000000 --- a/tests/date/date.c +++ /dev/null @@ -1,22 +0,0 @@ -#include "clar_libgit2.h" - -#include "date.h" - -void test_date_date__overflow(void) -{ -#ifdef __LP64__ - git_time_t d2038, d2039; - - /* This is expected to fail on a 32-bit machine. */ - cl_git_pass(git_date_parse(&d2038, "2038-1-1")); - cl_git_pass(git_date_parse(&d2039, "2039-1-1")); - cl_assert(d2038 < d2039); -#endif -} - -void test_date_date__invalid_date(void) -{ - git_time_t d; - cl_git_fail(git_date_parse(&d, "")); - cl_git_fail(git_date_parse(&d, "NEITHER_INTEGER_NOR_DATETIME")); -} diff --git a/tests/date/rfc2822.c b/tests/date/rfc2822.c deleted file mode 100644 index b0bbcfce5..000000000 --- a/tests/date/rfc2822.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "clar_libgit2.h" - -#include "date.h" - -void test_date_rfc2822__format_rfc2822_no_offset(void) -{ - git_time t = {1397031663, 0}; - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_date_rfc2822_fmt(&buf, t.time, t.offset)); - cl_assert_equal_s("Wed, 9 Apr 2014 08:21:03 +0000", buf.ptr); - - git_str_dispose(&buf); -} - -void test_date_rfc2822__format_rfc2822_positive_offset(void) -{ - git_time t = {1397031663, 120}; - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_date_rfc2822_fmt(&buf, t.time, t.offset)); - cl_assert_equal_s("Wed, 9 Apr 2014 10:21:03 +0200", buf.ptr); - - git_str_dispose(&buf); -} - -void test_date_rfc2822__format_rfc2822_negative_offset(void) -{ - git_time t = {1397031663, -120}; - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_date_rfc2822_fmt(&buf, t.time, t.offset)); - cl_assert_equal_s("Wed, 9 Apr 2014 06:21:03 -0200", buf.ptr); - - git_str_dispose(&buf); -} - diff --git a/tests/delta/apply.c b/tests/delta/apply.c deleted file mode 100644 index 5bb95a283..000000000 --- a/tests/delta/apply.c +++ /dev/null @@ -1,21 +0,0 @@ -#include "clar_libgit2.h" - -#include "delta.h" - -void test_delta_apply__read_at_off(void) -{ - unsigned char base[16] = { 0 }, delta[] = { 0x10, 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00 }; - void *out; - size_t outlen; - - cl_git_fail(git_delta_apply(&out, &outlen, base, sizeof(base), delta, sizeof(delta))); -} - -void test_delta_apply__read_after_limit(void) -{ - unsigned char base[16] = { 0 }, delta[] = { 0x10, 0x70, 0xff }; - void *out; - size_t outlen; - - cl_git_fail(git_delta_apply(&out, &outlen, base, sizeof(base), delta, sizeof(delta))); -} diff --git a/tests/describe/describe.c b/tests/describe/describe.c deleted file mode 100644 index ba67ca46b..000000000 --- a/tests/describe/describe.c +++ /dev/null @@ -1,55 +0,0 @@ -#include "clar_libgit2.h" -#include "describe_helpers.h" - -void test_describe_describe__can_describe_against_a_bare_repo(void) -{ - git_repository *repo; - git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; - git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - - assert_describe("hard_tag", "HEAD", repo, &opts, &fmt_opts); - - opts.show_commit_oid_as_fallback = 1; - - assert_describe("be3563a*", "HEAD^", repo, &opts, &fmt_opts); - - git_repository_free(repo); -} - -static int delete_cb(git_reference *ref, void *payload) -{ - GIT_UNUSED(payload); - - cl_git_pass(git_reference_delete(ref)); - git_reference_free(ref); - - return 0; -} - -void test_describe_describe__describe_a_repo_with_no_refs(void) -{ - git_repository *repo; - git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; - git_str buf = GIT_STR_INIT; - git_object *object; - git_describe_result *result = NULL; - - repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_revparse_single(&object, repo, "HEAD")); - - cl_git_pass(git_reference_foreach(repo, delete_cb, NULL)); - - /* Impossible to describe without falling back to OIDs */ - cl_git_fail(git_describe_commit(&result, object, &opts)); - - /* Try again with OID fallbacks */ - opts.show_commit_oid_as_fallback = 1; - cl_git_pass(git_describe_commit(&result, object, &opts)); - - git_describe_result_free(result); - git_object_free(object); - git_str_dispose(&buf); - cl_git_sandbox_cleanup(); -} diff --git a/tests/describe/describe_helpers.c b/tests/describe/describe_helpers.c deleted file mode 100644 index 3df3b7c59..000000000 --- a/tests/describe/describe_helpers.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "describe_helpers.h" - -#include "wildmatch.h" - -void assert_describe( - const char *expected_output, - const char *revparse_spec, - git_repository *repo, - git_describe_options *opts, - git_describe_format_options *fmt_opts) -{ - git_object *object; - git_buf label = GIT_BUF_INIT; - git_describe_result *result; - - cl_git_pass(git_revparse_single(&object, repo, revparse_spec)); - - cl_git_pass(git_describe_commit(&result, object, opts)); - cl_git_pass(git_describe_format(&label, result, fmt_opts)); - - cl_must_pass(wildmatch(expected_output, label.ptr, 0)); - - git_describe_result_free(result); - git_object_free(object); - git_buf_dispose(&label); -} - -void assert_describe_workdir( - const char *expected_output, - git_repository *repo, - git_describe_options *opts, - git_describe_format_options *fmt_opts) -{ - git_buf label = GIT_BUF_INIT; - git_describe_result *result; - - cl_git_pass(git_describe_workdir(&result, repo, opts)); - cl_git_pass(git_describe_format(&label, result, fmt_opts)); - - cl_must_pass(wildmatch(expected_output, label.ptr, 0)); - - git_describe_result_free(result); - git_buf_dispose(&label); -} diff --git a/tests/describe/describe_helpers.h b/tests/describe/describe_helpers.h deleted file mode 100644 index 43e8c5e19..000000000 --- a/tests/describe/describe_helpers.h +++ /dev/null @@ -1,14 +0,0 @@ -#include "clar_libgit2.h" - -extern void assert_describe( - const char *expected_output, - const char *revparse_spec, - git_repository *repo, - git_describe_options *opts, - git_describe_format_options *fmt_opts); - -extern void assert_describe_workdir( - const char *expected_output, - git_repository *repo, - git_describe_options *opts, - git_describe_format_options *fmt_opts); diff --git a/tests/describe/t6120.c b/tests/describe/t6120.c deleted file mode 100644 index 5ec176b87..000000000 --- a/tests/describe/t6120.c +++ /dev/null @@ -1,156 +0,0 @@ -#include "clar_libgit2.h" -#include "describe_helpers.h" -#include "repository.h" - -/* Ported from https://github.com/git/git/blob/adfc1857bdb090786fd9d22c1acec39371c76048/t/t6120-describe.sh */ - -static git_repository *repo; - -void test_describe_t6120__initialize(void) -{ - repo = cl_git_sandbox_init("describe"); -} - -void test_describe_t6120__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_describe_t6120__default(void) -{ - git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; - git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; - - assert_describe("A-*", "HEAD", repo, &opts, &fmt_opts); - assert_describe("A-*", "HEAD^", repo, &opts, &fmt_opts); - assert_describe("R-*", "HEAD^^", repo, &opts, &fmt_opts); - assert_describe("A-*", "HEAD^^2", repo, &opts, &fmt_opts); - assert_describe("B", "HEAD^^2^", repo, &opts, &fmt_opts); - assert_describe("R-*", "HEAD^^^", repo, &opts, &fmt_opts); -} - -void test_describe_t6120__tags(void) -{ - git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; - git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; - opts.describe_strategy = GIT_DESCRIBE_TAGS; - - assert_describe("c-*", "HEAD", repo, &opts, &fmt_opts); - assert_describe("c-*", "HEAD^", repo, &opts, &fmt_opts); - assert_describe("e-*", "HEAD^^", repo, &opts, &fmt_opts); - assert_describe("c-*", "HEAD^^2", repo, &opts, &fmt_opts); - assert_describe("B", "HEAD^^2^", repo, &opts, &fmt_opts); - assert_describe("e", "HEAD^^^", repo, &opts, &fmt_opts); -} - -void test_describe_t6120__all(void) -{ - git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; - git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; - opts.describe_strategy = GIT_DESCRIBE_ALL; - - assert_describe("heads/master", "HEAD", repo, &opts, &fmt_opts); - assert_describe("tags/c-*", "HEAD^", repo, &opts, &fmt_opts); - assert_describe("tags/e", "HEAD^^^", repo, &opts, &fmt_opts); -} - -void test_describe_t6120__longformat(void) -{ - git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; - git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; - - fmt_opts.always_use_long_format = 1; - - assert_describe("B-0-*", "HEAD^^2^", repo, &opts, &fmt_opts); - assert_describe("A-3-*", "HEAD^^2", repo, &opts, &fmt_opts); -} - -void test_describe_t6120__firstparent(void) -{ - git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; - git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; - opts.describe_strategy = GIT_DESCRIBE_TAGS; - - assert_describe("c-7-*", "HEAD", repo, &opts, &fmt_opts); - - opts.only_follow_first_parent = 1; - assert_describe("e-3-*", "HEAD", repo, &opts, &fmt_opts); -} - -void test_describe_t6120__workdir(void) -{ - git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; - git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; - - assert_describe_workdir("A-*[0-9a-f]", repo, &opts, &fmt_opts); - cl_git_mkfile("describe/file", "something different"); - - fmt_opts.dirty_suffix = "-dirty"; - assert_describe_workdir("A-*[0-9a-f]-dirty", repo, &opts, &fmt_opts); - fmt_opts.dirty_suffix = ".mod"; - assert_describe_workdir("A-*[0-9a-f].mod", repo, &opts, &fmt_opts); -} - -static void commit_and_tag( - git_time_t *time, - const char *commit_msg, - const char *tag_name) -{ - git_index *index; - git_oid commit_id; - git_reference *ref; - - cl_git_pass(git_repository_index__weakptr(&index, repo)); - - cl_git_append2file("describe/file", "\n"); - - cl_git_pass(git_index_add_bypath(index, "file")); - cl_git_pass(git_index_write(index)); - - *time += 10; - cl_repo_commit_from_index(&commit_id, repo, NULL, *time, commit_msg); - - if (tag_name == NULL) - return; - - cl_git_pass(git_reference_create(&ref, repo, tag_name, &commit_id, 0, NULL)); - git_reference_free(ref); -} - -void test_describe_t6120__pattern(void) -{ - git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; - git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; - git_oid tag_id; - git_object *head; - git_signature *tagger; - git_time_t time; - - /* set-up matching pattern tests */ - cl_git_pass(git_revparse_single(&head, repo, "HEAD")); - - time = 1380553019; - cl_git_pass(git_signature_new(&tagger, "tagger", "tagger@libgit2.org", time, 0)); - cl_git_pass(git_tag_create(&tag_id, repo, "test-annotated", head, tagger, "test-annotated", 0)); - git_signature_free(tagger); - git_object_free(head); - - commit_and_tag(&time, "one more", "refs/tags/test1-lightweight"); - commit_and_tag(&time, "yet another", "refs/tags/test2-lightweight"); - commit_and_tag(&time, "even more", NULL); - - - /* Exercize */ - opts.pattern = "test-*"; - assert_describe("test-annotated-*", "HEAD", repo, &opts, &fmt_opts); - - opts.describe_strategy = GIT_DESCRIBE_TAGS; - opts.pattern = "test1-*"; - assert_describe("test1-lightweight-*", "HEAD", repo, &opts, &fmt_opts); - - opts.pattern = "test2-*"; - assert_describe("test2-lightweight-*", "HEAD", repo, &opts, &fmt_opts); - - fmt_opts.always_use_long_format = 1; - assert_describe("test2-lightweight-*", "HEAD^", repo, &opts, &fmt_opts); -} diff --git a/tests/diff/binary.c b/tests/diff/binary.c deleted file mode 100644 index 4e71f39c6..000000000 --- a/tests/diff/binary.c +++ /dev/null @@ -1,545 +0,0 @@ -#include "clar_libgit2.h" - -#include "git2/sys/diff.h" - -#include "delta.h" -#include "filebuf.h" -#include "repository.h" - -static git_repository *repo; - -void test_diff_binary__initialize(void) -{ -} - -void test_diff_binary__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void test_patch( - const char *one, - const char *two, - const git_diff_options *opts, - const char *expected) -{ - git_oid id_one, id_two; - git_index *index = NULL; - git_commit *commit_one, *commit_two = NULL; - git_tree *tree_one, *tree_two; - git_diff *diff; - git_patch *patch; - git_buf actual = GIT_BUF_INIT; - - cl_git_pass(git_oid_fromstr(&id_one, one)); - cl_git_pass(git_commit_lookup(&commit_one, repo, &id_one)); - cl_git_pass(git_commit_tree(&tree_one, commit_one)); - - if (two) { - cl_git_pass(git_oid_fromstr(&id_two, two)); - cl_git_pass(git_commit_lookup(&commit_two, repo, &id_two)); - cl_git_pass(git_commit_tree(&tree_two, commit_two)); - } else { - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_write_tree(&id_two, index)); - cl_git_pass(git_tree_lookup(&tree_two, repo, &id_two)); - } - - cl_git_pass(git_diff_tree_to_tree(&diff, repo, tree_one, tree_two, opts)); - - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&actual, patch)); - - cl_assert_equal_s(expected, actual.ptr); - - git_buf_dispose(&actual); - cl_git_pass(git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, git_diff_print_callback__to_buf, &actual)); - - cl_assert_equal_s(expected, actual.ptr); - - git_buf_dispose(&actual); - git_patch_free(patch); - git_diff_free(diff); - git_tree_free(tree_one); - git_tree_free(tree_two); - git_commit_free(commit_one); - git_commit_free(commit_two); - git_index_free(index); -} - -void test_diff_binary__add_normal(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - const char *expected = - "diff --git a/binary.bin b/binary.bin\n" \ - "new file mode 100644\n" \ - "index 0000000..bd474b2\n" \ - "Binary files /dev/null and b/binary.bin differ\n"; - - repo = cl_git_sandbox_init("diff_format_email"); - test_patch( - "873806f6f27e631eb0b23e4b56bea2bfac14a373", - "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", - &opts, - expected); -} - -void test_diff_binary__add(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - const char *expected = - "diff --git a/binary.bin b/binary.bin\n" \ - "new file mode 100644\n" \ - "index 0000000000000000000000000000000000000000..bd474b2519cc15eab801ff851cc7d50f0dee49a1\n" \ - "GIT binary patch\n" \ - "literal 3\n" \ - "Kc${Nk-~s>u4FC%O\n" - "\n" \ - "literal 0\n" \ - "Hc$@u4FC%O\n" \ - "\n"; - - opts.flags = GIT_DIFF_SHOW_BINARY; - - repo = cl_git_sandbox_init("diff_format_email"); - test_patch( - "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", - "8d7523f6fcb2404257889abe0d96f093d9f524f9", - &opts, - expected); -} - -void test_diff_binary__delete_normal(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - const char *expected = - "diff --git a/binary.bin b/binary.bin\n" \ - "deleted file mode 100644\n" \ - "index bd474b2..0000000\n" \ - "Binary files a/binary.bin and /dev/null differ\n"; - - repo = cl_git_sandbox_init("diff_format_email"); - test_patch( - "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", - "873806f6f27e631eb0b23e4b56bea2bfac14a373", - &opts, - expected); -} - -void test_diff_binary__delete(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - const char *expected = - "diff --git a/binary.bin b/binary.bin\n" \ - "deleted file mode 100644\n" \ - "index bd474b2519cc15eab801ff851cc7d50f0dee49a1..0000000000000000000000000000000000000000\n" \ - "GIT binary patch\n" \ - "literal 0\n" \ - "Hc$@u4FC%O\n" \ - "\n"; - - opts.flags = GIT_DIFF_SHOW_BINARY; - opts.id_abbrev = GIT_OID_HEXSZ; - - repo = cl_git_sandbox_init("diff_format_email"); - test_patch( - "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", - "873806f6f27e631eb0b23e4b56bea2bfac14a373", - &opts, - expected); -} - -void test_diff_binary__delta(void) -{ - git_index *index; - git_str contents = GIT_STR_INIT; - size_t i; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - const char *expected = - "diff --git a/songof7cities.txt b/songof7cities.txt\n" \ - "index 4210ffd5c390b21dd5483375e75288dea9ede512..cc84ec183351c9944ed90a619ca08911924055b5 100644\n" \ - "GIT binary patch\n" \ - "delta 198\n" \ - "zc$}LmI8{(0BqLQJI6p64AwNwaIJGP_Pa)Ye#M3o+qJ$PQ;Y(X&QMK*C5^Br3bjG4d=XI^5@\n" \ - "JfH567LIG)KJdFSV\n" \ - "\n" \ - "delta 198\n" \ - "zc$}LmI8{(0BqLQJI6p64AwNwaIJGP_Pr*5}Br~;mqJ$PQ;Y(X&QMK*C5^Br3bjG4d=XI^5@\n" \ - "JfH567LIF3FM2!Fd\n" \ - "\n"; - - opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; - opts.id_abbrev = GIT_OID_HEXSZ; - - repo = cl_git_sandbox_init("renames"); - cl_git_pass(git_repository_index(&index, repo)); - - cl_git_pass(git_futils_readbuffer(&contents, "renames/songof7cities.txt")); - - for (i = 0; i < contents.size - 6; i++) { - if (strncmp(&contents.ptr[i], "Cities", 6) == 0) - memcpy(&contents.ptr[i], "cITIES", 6); - } - - cl_git_rewritefile("renames/songof7cities.txt", contents.ptr); - cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); - cl_git_pass(git_index_write(index)); - - test_patch( - "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13", - NULL, - &opts, - expected); - - git_index_free(index); - git_str_dispose(&contents); -} - -void test_diff_binary__delta_append(void) -{ - git_index *index; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - const char *expected = - "diff --git a/untimely.txt b/untimely.txt\n" \ - "index 9a69d960ae94b060f56c2a8702545e2bb1abb935..1111d4f11f4b35bf6759e0fb714fe09731ef0840 100644\n" \ - "GIT binary patch\n" \ - "delta 32\n" \ - "nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \ - "\n" \ - "delta 7\n" \ - "Oc%18D`@*{63ljhg(E~C7\n" \ - "\n"; - - opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; - opts.id_abbrev = GIT_OID_HEXSZ; - - repo = cl_git_sandbox_init("renames"); - cl_git_pass(git_repository_index(&index, repo)); - - cl_git_append2file("renames/untimely.txt", "Oh that crazy Kipling!\r\n"); - cl_git_pass(git_index_add_bypath(index, "untimely.txt")); - cl_git_pass(git_index_write(index)); - - test_patch( - "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13", - NULL, - &opts, - expected); - - git_index_free(index); -} - -void test_diff_binary__empty_for_no_diff(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_oid id; - git_commit *commit; - git_tree *tree; - git_diff *diff; - git_str actual = GIT_STR_INIT; - - opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; - opts.id_abbrev = GIT_OID_HEXSZ; - - repo = cl_git_sandbox_init("renames"); - - cl_git_pass(git_oid_fromstr(&id, "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13")); - cl_git_pass(git_commit_lookup(&commit, repo, &id)); - cl_git_pass(git_commit_tree(&tree, commit)); - - cl_git_pass(git_diff_tree_to_tree(&diff, repo, tree, tree, &opts)); - cl_git_pass(git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, git_diff_print_callback__to_buf, &actual)); - - cl_assert_equal_s("", actual.ptr); - - git_str_dispose(&actual); - git_diff_free(diff); - git_commit_free(commit); - git_tree_free(tree); -} - -void test_diff_binary__index_to_workdir(void) -{ - git_index *index; - git_diff *diff; - git_patch *patch; - git_buf actual = GIT_BUF_INIT; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - const char *expected = - "diff --git a/untimely.txt b/untimely.txt\n" \ - "index 9a69d960ae94b060f56c2a8702545e2bb1abb935..1111d4f11f4b35bf6759e0fb714fe09731ef0840 100644\n" \ - "GIT binary patch\n" \ - "delta 32\n" \ - "nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \ - "\n" \ - "delta 7\n" \ - "Oc%18D`@*{63ljhg(E~C7\n" \ - "\n"; - - opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; - opts.id_abbrev = GIT_OID_HEXSZ; - - repo = cl_git_sandbox_init("renames"); - cl_git_pass(git_repository_index(&index, repo)); - - cl_git_append2file("renames/untimely.txt", "Oh that crazy Kipling!\r\n"); - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, &opts)); - - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&actual, patch)); - - cl_assert_equal_s(expected, actual.ptr); - - cl_git_pass(git_index_add_bypath(index, "untimely.txt")); - cl_git_pass(git_index_write(index)); - - test_patch( - "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13", - NULL, - &opts, - expected); - - git_buf_dispose(&actual); - git_patch_free(patch); - git_diff_free(diff); - git_index_free(index); -} - -static int print_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *payload) -{ - git_str *buf = (git_str *)payload; - - GIT_UNUSED(delta); - - if (hunk) - git_str_put(buf, hunk->header, hunk->header_len); - - if (line) - git_str_put(buf, line->content, line->content_len); - - return git_str_oom(buf) ? -1 : 0; -} - -void test_diff_binary__print_patch_from_diff(void) -{ - git_index *index; - git_diff *diff; - git_str actual = GIT_STR_INIT; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - const char *expected = - "diff --git a/untimely.txt b/untimely.txt\n" \ - "index 9a69d960ae94b060f56c2a8702545e2bb1abb935..1111d4f11f4b35bf6759e0fb714fe09731ef0840 100644\n" \ - "GIT binary patch\n" \ - "delta 32\n" \ - "nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \ - "\n" \ - "delta 7\n" \ - "Oc%18D`@*{63ljhg(E~C7\n" \ - "\n"; - - opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; - opts.id_abbrev = GIT_OID_HEXSZ; - - repo = cl_git_sandbox_init("renames"); - cl_git_pass(git_repository_index(&index, repo)); - - cl_git_append2file("renames/untimely.txt", "Oh that crazy Kipling!\r\n"); - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, &opts)); - - cl_git_pass(git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, print_cb, &actual)); - - cl_assert_equal_s(expected, actual.ptr); - - git_str_dispose(&actual); - git_diff_free(diff); - git_index_free(index); -} - -struct diff_data { - char *old_path; - git_oid old_id; - git_str old_binary_base85; - size_t old_binary_inflatedlen; - git_diff_binary_t old_binary_type; - - char *new_path; - git_oid new_id; - git_str new_binary_base85; - size_t new_binary_inflatedlen; - git_diff_binary_t new_binary_type; -}; - -static int file_cb( - const git_diff_delta *delta, - float progress, - void *payload) -{ - struct diff_data *diff_data = payload; - - GIT_UNUSED(progress); - - if (delta->old_file.path) - diff_data->old_path = git__strdup(delta->old_file.path); - - if (delta->new_file.path) - diff_data->new_path = git__strdup(delta->new_file.path); - - git_oid_cpy(&diff_data->old_id, &delta->old_file.id); - git_oid_cpy(&diff_data->new_id, &delta->new_file.id); - - return 0; -} - -static int binary_cb( - const git_diff_delta *delta, - const git_diff_binary *binary, - void *payload) -{ - struct diff_data *diff_data = payload; - - GIT_UNUSED(delta); - - git_str_encode_base85(&diff_data->old_binary_base85, - binary->old_file.data, binary->old_file.datalen); - diff_data->old_binary_inflatedlen = binary->old_file.inflatedlen; - diff_data->old_binary_type = binary->old_file.type; - - git_str_encode_base85(&diff_data->new_binary_base85, - binary->new_file.data, binary->new_file.datalen); - diff_data->new_binary_inflatedlen = binary->new_file.inflatedlen; - diff_data->new_binary_type = binary->new_file.type; - - return 0; -} - -static int hunk_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - void *payload) -{ - GIT_UNUSED(delta); - GIT_UNUSED(hunk); - GIT_UNUSED(payload); - - cl_fail("did not expect hunk callback"); - return 0; -} - -static int line_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *payload) -{ - GIT_UNUSED(delta); - GIT_UNUSED(hunk); - GIT_UNUSED(line); - GIT_UNUSED(payload); - - cl_fail("did not expect line callback"); - return 0; -} - -void test_diff_binary__blob_to_blob(void) -{ - git_index *index; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_blob *old_blob, *new_blob; - git_oid old_id, new_id; - struct diff_data diff_data = {0}; - - opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; - opts.id_abbrev = GIT_OID_HEXSZ; - - repo = cl_git_sandbox_init("renames"); - cl_git_pass(git_repository_index__weakptr(&index, repo)); - - cl_git_append2file("renames/untimely.txt", "Oh that crazy Kipling!\r\n"); - cl_git_pass(git_index_add_bypath(index, "untimely.txt")); - cl_git_pass(git_index_write(index)); - - git_oid_fromstr(&old_id, "9a69d960ae94b060f56c2a8702545e2bb1abb935"); - git_oid_fromstr(&new_id, "1111d4f11f4b35bf6759e0fb714fe09731ef0840"); - - cl_git_pass(git_blob_lookup(&old_blob, repo, &old_id)); - cl_git_pass(git_blob_lookup(&new_blob, repo, &new_id)); - - cl_git_pass(git_diff_blobs(old_blob, - "untimely.txt", new_blob, "untimely.txt", &opts, - file_cb, binary_cb, hunk_cb, line_cb, &diff_data)); - - cl_assert_equal_s("untimely.txt", diff_data.old_path); - cl_assert_equal_oid(&old_id, &diff_data.old_id); - cl_assert_equal_i(GIT_DIFF_BINARY_DELTA, diff_data.old_binary_type); - cl_assert_equal_i(7, diff_data.old_binary_inflatedlen); - cl_assert_equal_s("c%18D`@*{63ljhg(E~C7", - diff_data.old_binary_base85.ptr); - - cl_assert_equal_s("untimely.txt", diff_data.new_path); - cl_assert_equal_oid(&new_id, &diff_data.new_id); - cl_assert_equal_i(GIT_DIFF_BINARY_DELTA, diff_data.new_binary_type); - cl_assert_equal_i(32, diff_data.new_binary_inflatedlen); - cl_assert_equal_s("c%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW", - diff_data.new_binary_base85.ptr); - - git_blob_free(old_blob); - git_blob_free(new_blob); - - git__free(diff_data.old_path); - git__free(diff_data.new_path); - - git_str_dispose(&diff_data.old_binary_base85); - git_str_dispose(&diff_data.new_binary_base85); -} diff --git a/tests/diff/blob.c b/tests/diff/blob.c deleted file mode 100644 index d2f42207d..000000000 --- a/tests/diff/blob.c +++ /dev/null @@ -1,1063 +0,0 @@ -#include "clar_libgit2.h" -#include "diff_helpers.h" - -#define BLOB_DIFF \ - "diff --git a/file b/file\n" \ - "index 45141a7..4d713dc 100644\n" \ - "--- a/file\n" \ - "+++ b/file\n" \ - "@@ -1 +1,6 @@\n" \ - " Hello from the root\n" \ - "+\n" \ - "+Some additional lines\n" \ - "+\n" \ - "+Down here below\n" \ - "+\n" - -static git_repository *g_repo = NULL; -static diff_expects expected; -static git_diff_options opts; -static git_blob *d, *alien; - -static void quick_diff_blob_to_str( - const git_blob *blob, const char *blob_path, - const char *str, size_t len, const char *str_path) -{ - memset(&expected, 0, sizeof(expected)); - - if (str && !len) - len = strlen(str); - - cl_git_pass(git_diff_blob_to_buffer( - blob, blob_path, str, len, str_path, - &opts, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); -} - -void test_diff_blob__initialize(void) -{ - git_oid oid; - - g_repo = cl_git_sandbox_init("attr"); - - cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION)); - opts.context_lines = 1; - - memset(&expected, 0, sizeof(expected)); - - /* tests/resources/attr/root_test4.txt */ - cl_git_pass(git_oid_fromstrn(&oid, "a0f7217a", 8)); - cl_git_pass(git_blob_lookup_prefix(&d, g_repo, &oid, 8)); - - /* alien.png */ - cl_git_pass(git_oid_fromstrn(&oid, "edf3dcee", 8)); - cl_git_pass(git_blob_lookup_prefix(&alien, g_repo, &oid, 8)); -} - -void test_diff_blob__cleanup(void) -{ - git_blob_free(d); - d = NULL; - - git_blob_free(alien); - alien = NULL; - - cl_git_sandbox_cleanup(); -} - -static void assert_one_modified( - int hunks, int lines, int ctxt, int adds, int dels, diff_expects *exp) -{ - cl_assert_equal_i(1, exp->files); - cl_assert_equal_i(1, exp->file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp->files_binary); - - cl_assert_equal_i(hunks, exp->hunks); - cl_assert_equal_i(lines, exp->lines); - cl_assert_equal_i(ctxt, exp->line_ctxt); - cl_assert_equal_i(adds, exp->line_adds); - cl_assert_equal_i(dels, exp->line_dels); -} - -void test_diff_blob__patch_with_freed_blobs(void) -{ - git_oid a_oid, b_oid; - git_blob *a, *b; - git_patch *p; - git_buf buf = GIT_BUF_INIT; - - /* tests/resources/attr/root_test1 */ - cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8)); - cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4)); - /* tests/resources/attr/root_test2 */ - cl_git_pass(git_oid_fromstrn(&b_oid, "4d713dc4", 8)); - cl_git_pass(git_blob_lookup_prefix(&b, g_repo, &b_oid, 4)); - - cl_git_pass(git_patch_from_blobs(&p, a, NULL, b, NULL, NULL)); - - git_blob_free(a); - git_blob_free(b); - - cl_git_pass(git_patch_to_buf(&buf, p)); - cl_assert_equal_s(buf.ptr, BLOB_DIFF); - - git_patch_free(p); - git_buf_dispose(&buf); -} - -void test_diff_blob__can_compare_text_blobs(void) -{ - git_blob *a, *b, *c; - git_oid a_oid, b_oid, c_oid; - - /* tests/resources/attr/root_test1 */ - cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8)); - cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4)); - - /* tests/resources/attr/root_test2 */ - cl_git_pass(git_oid_fromstrn(&b_oid, "4d713dc4", 8)); - cl_git_pass(git_blob_lookup_prefix(&b, g_repo, &b_oid, 4)); - - /* tests/resources/attr/root_test3 */ - cl_git_pass(git_oid_fromstrn(&c_oid, "c96bbb2c2557a832", 16)); - cl_git_pass(git_blob_lookup_prefix(&c, g_repo, &c_oid, 16)); - - /* Doing the equivalent of a `git diff -U1` on these files */ - - /* diff on tests/resources/attr/root_test1 */ - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blobs( - a, NULL, b, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - assert_one_modified(1, 6, 1, 5, 0, &expected); - - /* same diff but use direct buffers */ - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_buffers( - git_blob_rawcontent(a), (size_t)git_blob_rawsize(a), NULL, - git_blob_rawcontent(b), (size_t)git_blob_rawsize(b), NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - assert_one_modified(1, 6, 1, 5, 0, &expected); - - /* diff on tests/resources/attr/root_test2 */ - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blobs( - b, NULL, c, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - assert_one_modified(1, 15, 3, 9, 3, &expected); - - /* diff on tests/resources/attr/root_test3 */ - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blobs( - a, NULL, c, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - assert_one_modified(1, 13, 0, 12, 1, &expected); - - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blobs( - c, NULL, d, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - assert_one_modified(2, 14, 4, 6, 4, &expected); - - git_blob_free(a); - git_blob_free(b); - git_blob_free(c); -} - -static void assert_patch_matches_blobs( - git_patch *p, git_blob *a, git_blob *b, - int hunks, int l0, int l1, int ctxt, int adds, int dels) -{ - const git_diff_delta *delta; - size_t tc, ta, td; - - cl_assert(p != NULL); - - delta = git_patch_get_delta(p); - cl_assert(delta != NULL); - - cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status); - cl_assert_equal_oid(git_blob_id(a), &delta->old_file.id); - cl_assert_equal_sz(git_blob_rawsize(a), delta->old_file.size); - cl_assert_equal_oid(git_blob_id(b), &delta->new_file.id); - cl_assert_equal_sz(git_blob_rawsize(b), delta->new_file.size); - - cl_assert_equal_i(hunks, (int)git_patch_num_hunks(p)); - - if (hunks > 0) - cl_assert_equal_i(l0, git_patch_num_lines_in_hunk(p, 0)); - if (hunks > 1) - cl_assert_equal_i(l1, git_patch_num_lines_in_hunk(p, 1)); - - cl_git_pass(git_patch_line_stats(&tc, &ta, &td, p)); - cl_assert_equal_i(ctxt, (int)tc); - cl_assert_equal_i(adds, (int)ta); - cl_assert_equal_i(dels, (int)td); -} - -void test_diff_blob__can_compare_text_blobs_with_patch(void) -{ - git_blob *a, *b, *c; - git_oid a_oid, b_oid, c_oid; - git_patch *p; - - /* tests/resources/attr/root_test1 */ - cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8)); - cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 8)); - - /* tests/resources/attr/root_test2 */ - cl_git_pass(git_oid_fromstrn(&b_oid, "4d713dc4", 8)); - cl_git_pass(git_blob_lookup_prefix(&b, g_repo, &b_oid, 8)); - - /* tests/resources/attr/root_test3 */ - cl_git_pass(git_oid_fromstrn(&c_oid, "c96bbb2c2557a832", 16)); - cl_git_pass(git_blob_lookup_prefix(&c, g_repo, &c_oid, 16)); - - /* Doing the equivalent of a `git diff -U1` on these files */ - - /* diff on tests/resources/attr/root_test1 */ - cl_git_pass(git_patch_from_blobs(&p, a, NULL, b, NULL, &opts)); - assert_patch_matches_blobs(p, a, b, 1, 6, 0, 1, 5, 0); - git_patch_free(p); - - /* diff on tests/resources/attr/root_test2 */ - cl_git_pass(git_patch_from_blobs(&p, b, NULL, c, NULL, &opts)); - assert_patch_matches_blobs(p, b, c, 1, 15, 0, 3, 9, 3); - git_patch_free(p); - - /* diff on tests/resources/attr/root_test3 */ - cl_git_pass(git_patch_from_blobs(&p, a, NULL, c, NULL, &opts)); - assert_patch_matches_blobs(p, a, c, 1, 13, 0, 0, 12, 1); - git_patch_free(p); - - /* one more */ - cl_git_pass(git_patch_from_blobs(&p, c, NULL, d, NULL, &opts)); - assert_patch_matches_blobs(p, c, d, 2, 5, 9, 4, 6, 4); - git_patch_free(p); - - git_blob_free(a); - git_blob_free(b); - git_blob_free(c); -} - -void test_diff_blob__can_compare_against_null_blobs(void) -{ - git_blob *e = NULL; - - cl_git_pass(git_diff_blobs( - d, NULL, e, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(0, expected.files_binary); - - cl_assert_equal_i(1, expected.hunks); - cl_assert_equal_i(14, expected.hunk_old_lines); - cl_assert_equal_i(14, expected.lines); - cl_assert_equal_i(14, expected.line_dels); - - opts.flags |= GIT_DIFF_REVERSE; - memset(&expected, 0, sizeof(expected)); - - cl_git_pass(git_diff_blobs( - d, NULL, e, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, expected.files_binary); - - cl_assert_equal_i(1, expected.hunks); - cl_assert_equal_i(14, expected.hunk_new_lines); - cl_assert_equal_i(14, expected.lines); - cl_assert_equal_i(14, expected.line_adds); - - opts.flags ^= GIT_DIFF_REVERSE; - memset(&expected, 0, sizeof(expected)); - - cl_git_pass(git_diff_blobs( - alien, NULL, NULL, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.files_binary); - cl_assert_equal_i(1, expected.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(0, expected.hunks); - cl_assert_equal_i(0, expected.lines); - - memset(&expected, 0, sizeof(expected)); - - cl_git_pass(git_diff_blobs( - NULL, NULL, alien, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.files_binary); - cl_assert_equal_i(1, expected.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, expected.hunks); - cl_assert_equal_i(0, expected.lines); -} - -void test_diff_blob__can_compare_against_null_blobs_with_patch(void) -{ - git_blob *e = NULL; - git_patch *p; - const git_diff_delta *delta; - const git_diff_line *line; - int l, max_l; - - cl_git_pass(git_patch_from_blobs(&p, d, NULL, e, NULL, &opts)); - - cl_assert(p != NULL); - - delta = git_patch_get_delta(p); - cl_assert(delta != NULL); - cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); - cl_assert_equal_oid(git_blob_id(d), &delta->old_file.id); - cl_assert_equal_sz(git_blob_rawsize(d), delta->old_file.size); - cl_assert(git_oid_is_zero(&delta->new_file.id)); - cl_assert_equal_sz(0, delta->new_file.size); - - cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); - cl_assert_equal_i(14, git_patch_num_lines_in_hunk(p, 0)); - - max_l = git_patch_num_lines_in_hunk(p, 0); - for (l = 0; l < max_l; ++l) { - cl_git_pass(git_patch_get_line_in_hunk(&line, p, 0, l)); - cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin); - } - - git_patch_free(p); - - opts.flags |= GIT_DIFF_REVERSE; - - cl_git_pass(git_patch_from_blobs(&p, d, NULL, e, NULL, &opts)); - - cl_assert(p != NULL); - - delta = git_patch_get_delta(p); - cl_assert(delta != NULL); - cl_assert_equal_i(GIT_DELTA_ADDED, delta->status); - cl_assert(git_oid_is_zero(&delta->old_file.id)); - cl_assert_equal_sz(0, delta->old_file.size); - cl_assert_equal_oid(git_blob_id(d), &delta->new_file.id); - cl_assert_equal_sz(git_blob_rawsize(d), delta->new_file.size); - - cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); - cl_assert_equal_i(14, git_patch_num_lines_in_hunk(p, 0)); - - max_l = git_patch_num_lines_in_hunk(p, 0); - for (l = 0; l < max_l; ++l) { - cl_git_pass(git_patch_get_line_in_hunk(&line, p, 0, l)); - cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin); - } - - git_patch_free(p); - - opts.flags ^= GIT_DIFF_REVERSE; - - cl_git_pass(git_patch_from_blobs(&p, alien, NULL, NULL, NULL, &opts)); - - cl_assert(p != NULL); - - delta = git_patch_get_delta(p); - cl_assert(delta != NULL); - cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); - cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); - - cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); - - git_patch_free(p); - - cl_git_pass(git_patch_from_blobs(&p, NULL, NULL, alien, NULL, &opts)); - - cl_assert(p != NULL); - - delta = git_patch_get_delta(p); - cl_assert(delta != NULL); - cl_assert_equal_i(GIT_DELTA_ADDED, delta->status); - cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); - - cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); - - git_patch_free(p); -} - -static void assert_identical_blobs_comparison(diff_expects *expected) -{ - cl_assert_equal_i(1, expected->files); - cl_assert_equal_i(1, expected->file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(0, expected->hunks); - cl_assert_equal_i(0, expected->lines); -} - -void test_diff_blob__can_compare_identical_blobs(void) -{ - opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - - cl_git_pass(git_diff_blobs( - d, NULL, d, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - assert_identical_blobs_comparison(&expected); - cl_assert_equal_i(0, expected.files_binary); - - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blobs( - NULL, NULL, NULL, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - assert_identical_blobs_comparison(&expected); - cl_assert_equal_i(0, expected.files_binary); - - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blobs( - alien, NULL, alien, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - assert_identical_blobs_comparison(&expected); - cl_assert(expected.files_binary > 0); -} - -void test_diff_blob__can_compare_identical_blobs_with_patch(void) -{ - git_patch *p; - const git_diff_delta *delta; - - cl_git_pass(git_patch_from_blobs(&p, d, NULL, d, NULL, &opts)); - cl_assert(p != NULL); - - delta = git_patch_get_delta(p); - cl_assert(delta != NULL); - cl_assert_equal_i(GIT_DELTA_UNMODIFIED, delta->status); - cl_assert_equal_sz(delta->old_file.size, git_blob_rawsize(d)); - cl_assert_equal_oid(git_blob_id(d), &delta->old_file.id); - cl_assert_equal_sz(delta->new_file.size, git_blob_rawsize(d)); - cl_assert_equal_oid(git_blob_id(d), &delta->new_file.id); - - cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); - git_patch_free(p); - - cl_git_pass(git_patch_from_blobs(&p, NULL, NULL, NULL, NULL, &opts)); - cl_assert(p != NULL); - - delta = git_patch_get_delta(p); - cl_assert(delta != NULL); - cl_assert_equal_i(GIT_DELTA_UNMODIFIED, delta->status); - cl_assert_equal_sz(0, delta->old_file.size); - cl_assert(git_oid_is_zero(&delta->old_file.id)); - cl_assert_equal_sz(0, delta->new_file.size); - cl_assert(git_oid_is_zero(&delta->new_file.id)); - - cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); - git_patch_free(p); - - cl_git_pass(git_patch_from_blobs(&p, alien, NULL, alien, NULL, &opts)); - cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_patch_get_delta(p)->status); - cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); - git_patch_free(p); -} - -static void assert_binary_blobs_comparison(diff_expects *expected) -{ - cl_assert(expected->files_binary > 0); - - cl_assert_equal_i(1, expected->files); - cl_assert_equal_i(1, expected->file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, expected->hunks); - cl_assert_equal_i(0, expected->lines); -} - -void test_diff_blob__can_compare_two_binary_blobs(void) -{ - git_blob *heart; - git_oid h_oid; - - /* heart.png */ - cl_git_pass(git_oid_fromstrn(&h_oid, "de863bff", 8)); - cl_git_pass(git_blob_lookup_prefix(&heart, g_repo, &h_oid, 8)); - - cl_git_pass(git_diff_blobs( - alien, NULL, heart, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - assert_binary_blobs_comparison(&expected); - - memset(&expected, 0, sizeof(expected)); - - cl_git_pass(git_diff_blobs( - heart, NULL, alien, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - assert_binary_blobs_comparison(&expected); - - git_blob_free(heart); -} - -void test_diff_blob__can_compare_a_binary_blob_and_a_text_blob(void) -{ - cl_git_pass(git_diff_blobs( - alien, NULL, d, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - assert_binary_blobs_comparison(&expected); - - memset(&expected, 0, sizeof(expected)); - - cl_git_pass(git_diff_blobs( - d, NULL, alien, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - assert_binary_blobs_comparison(&expected); -} - -/* - * $ git diff fe773770 a0f7217 - * diff --git a/fe773770 b/a0f7217 - * index fe77377..a0f7217 100644 - * --- a/fe773770 - * +++ b/a0f7217 - * @@ -1,6 +1,6 @@ - * Here is some stuff at the start - * - * -This should go in one hunk - * +This should go in one hunk (first) - * - * Some additional lines - * - * @@ -8,7 +8,7 @@ Down here below the other lines - * - * With even more at the end - * - * -Followed by a second hunk of stuff - * +Followed by a second hunk of stuff (second) - * - * That happens down here - */ -void test_diff_blob__comparing_two_text_blobs_honors_interhunkcontext(void) -{ - git_blob *old_d; - git_oid old_d_oid; - - opts.context_lines = 3; - - /* tests/resources/attr/root_test1 from commit f5b0af1 */ - cl_git_pass(git_oid_fromstrn(&old_d_oid, "fe773770", 8)); - cl_git_pass(git_blob_lookup_prefix(&old_d, g_repo, &old_d_oid, 8)); - - /* Test with default inter-hunk-context (not set) => default is 0 */ - cl_git_pass(git_diff_blobs( - old_d, NULL, d, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - cl_assert_equal_i(2, expected.hunks); - - /* Test with inter-hunk-context explicitly set to 0 */ - opts.interhunk_lines = 0; - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blobs( - old_d, NULL, d, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - cl_assert_equal_i(2, expected.hunks); - - /* Test with inter-hunk-context explicitly set to 1 */ - opts.interhunk_lines = 1; - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blobs( - old_d, NULL, d, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - - cl_assert_equal_i(1, expected.hunks); - - git_blob_free(old_d); -} - -void test_diff_blob__checks_options_version_too_low(void) -{ - const git_error *err; - - opts.version = 0; - cl_git_fail(git_diff_blobs( - d, NULL, alien, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - err = git_error_last(); - cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); -} - -void test_diff_blob__checks_options_version_too_high(void) -{ - const git_error *err; - - opts.version = 1024; - cl_git_fail(git_diff_blobs( - d, NULL, alien, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - err = git_error_last(); - cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); -} - -void test_diff_blob__can_correctly_detect_a_binary_blob_as_binary(void) -{ - /* alien.png */ - cl_assert_equal_i(true, git_blob_is_binary(alien)); -} - -void test_diff_blob__can_correctly_detect_binary_blob_data_as_binary(void) -{ - /* alien.png */ - const char *content = git_blob_rawcontent(alien); - size_t len = (size_t)git_blob_rawsize(alien); - cl_assert_equal_i(true, git_blob_data_is_binary(content, len)); -} - -void test_diff_blob__can_correctly_detect_a_textual_blob_as_non_binary(void) -{ - /* tests/resources/attr/root_test4.txt */ - cl_assert_equal_i(false, git_blob_is_binary(d)); -} - -void test_diff_blob__can_correctly_detect_textual_blob_data_as_non_binary(void) -{ - /* tests/resources/attr/root_test4.txt */ - const char *content = git_blob_rawcontent(d); - size_t len = (size_t)git_blob_rawsize(d); - cl_assert_equal_i(false, git_blob_data_is_binary(content, len)); -} - -/* - * git_diff_blob_to_buffer tests - */ - -static void assert_changed_single_one_line_file( - diff_expects *expected, git_delta_t mod) -{ - cl_assert_equal_i(1, expected->files); - cl_assert_equal_i(1, expected->file_status[mod]); - cl_assert_equal_i(1, expected->hunks); - cl_assert_equal_i(1, expected->lines); - - if (mod == GIT_DELTA_ADDED) - cl_assert_equal_i(1, expected->line_adds); - else if (mod == GIT_DELTA_DELETED) - cl_assert_equal_i(1, expected->line_dels); -} - -void test_diff_blob__can_compare_blob_to_buffer(void) -{ - git_blob *a; - git_oid a_oid; - const char *a_content = "Hello from the root\n"; - const char *b_content = "Hello from the root\n\nSome additional lines\n\nDown here below\n\n"; - - /* tests/resources/attr/root_test1 */ - cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8)); - cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 8)); - - /* diff from blob a to content of b */ - quick_diff_blob_to_str(a, NULL, b_content, 0, NULL); - assert_one_modified(1, 6, 1, 5, 0, &expected); - - /* diff from blob a to content of a */ - opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - quick_diff_blob_to_str(a, NULL, a_content, 0, NULL); - assert_identical_blobs_comparison(&expected); - - /* diff from NULL blob to content of a */ - memset(&expected, 0, sizeof(expected)); - quick_diff_blob_to_str(NULL, NULL, a_content, 0, NULL); - assert_changed_single_one_line_file(&expected, GIT_DELTA_ADDED); - - /* diff from blob a to NULL buffer */ - memset(&expected, 0, sizeof(expected)); - quick_diff_blob_to_str(a, NULL, NULL, 0, NULL); - assert_changed_single_one_line_file(&expected, GIT_DELTA_DELETED); - - /* diff with reverse */ - opts.flags ^= GIT_DIFF_REVERSE; - - memset(&expected, 0, sizeof(expected)); - quick_diff_blob_to_str(a, NULL, NULL, 0, NULL); - assert_changed_single_one_line_file(&expected, GIT_DELTA_ADDED); - - git_blob_free(a); -} - -void test_diff_blob__can_compare_blob_to_buffer_with_patch(void) -{ - git_patch *p; - git_blob *a; - git_oid a_oid; - const char *a_content = "Hello from the root\n"; - const char *b_content = "Hello from the root\n\nSome additional lines\n\nDown here below\n\n"; - size_t tc, ta, td; - - /* tests/resources/attr/root_test1 */ - cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8)); - cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 8)); - - /* diff from blob a to content of b */ - cl_git_pass(git_patch_from_blob_and_buffer( - &p, a, NULL, b_content, strlen(b_content), NULL, &opts)); - - cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_MODIFIED, git_patch_get_delta(p)->status); - cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); - cl_assert_equal_i(6, git_patch_num_lines_in_hunk(p, 0)); - - cl_git_pass(git_patch_line_stats(&tc, &ta, &td, p)); - cl_assert_equal_i(1, (int)tc); - cl_assert_equal_i(5, (int)ta); - cl_assert_equal_i(0, (int)td); - - git_patch_free(p); - - /* diff from blob a to content of a */ - opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - cl_git_pass(git_patch_from_blob_and_buffer( - &p, a, NULL, a_content, strlen(a_content), NULL, &opts)); - cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_patch_get_delta(p)->status); - cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); - git_patch_free(p); - - /* diff from NULL blob to content of a */ - cl_git_pass(git_patch_from_blob_and_buffer( - &p, NULL, NULL, a_content, strlen(a_content), NULL, &opts)); - cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_ADDED, git_patch_get_delta(p)->status); - cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); - cl_assert_equal_i(1, git_patch_num_lines_in_hunk(p, 0)); - git_patch_free(p); - - /* diff from blob a to NULL buffer */ - cl_git_pass(git_patch_from_blob_and_buffer( - &p, a, NULL, NULL, 0, NULL, &opts)); - cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_DELETED, git_patch_get_delta(p)->status); - cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); - cl_assert_equal_i(1, git_patch_num_lines_in_hunk(p, 0)); - git_patch_free(p); - - /* diff with reverse */ - opts.flags ^= GIT_DIFF_REVERSE; - - cl_git_pass(git_patch_from_blob_and_buffer( - &p, a, NULL, NULL, 0, NULL, &opts)); - cl_assert(p != NULL); - cl_assert_equal_i(GIT_DELTA_ADDED, git_patch_get_delta(p)->status); - cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); - cl_assert_equal_i(1, git_patch_num_lines_in_hunk(p, 0)); - git_patch_free(p); - - git_blob_free(a); -} - -static void assert_one_modified_with_lines(diff_expects *expected, int lines) -{ - cl_assert_equal_i(1, expected->files); - cl_assert_equal_i(1, expected->file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, expected->files_binary); - cl_assert_equal_i(lines, expected->lines); -} - -void test_diff_blob__binary_data_comparisons(void) -{ - git_blob *bin, *nonbin; - git_oid oid; - const char *nonbin_content = "Hello from the root\n"; - size_t nonbin_len = 20; - const char *bin_content = "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n"; - size_t bin_len = 33; - - opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - - cl_git_pass(git_oid_fromstrn(&oid, "45141a79", 8)); - cl_git_pass(git_blob_lookup_prefix(&nonbin, g_repo, &oid, 8)); - - cl_git_pass(git_oid_fromstrn(&oid, "b435cd56", 8)); - cl_git_pass(git_blob_lookup_prefix(&bin, g_repo, &oid, 8)); - - /* non-binary to reference content */ - - quick_diff_blob_to_str(nonbin, NULL, nonbin_content, nonbin_len, NULL); - assert_identical_blobs_comparison(&expected); - cl_assert_equal_i(0, expected.files_binary); - - /* binary to reference content */ - - quick_diff_blob_to_str(bin, NULL, bin_content, bin_len, NULL); - assert_identical_blobs_comparison(&expected); - - cl_assert_equal_i(1, expected.files_binary); - - /* non-binary to binary content */ - - quick_diff_blob_to_str(nonbin, NULL, bin_content, bin_len, NULL); - assert_binary_blobs_comparison(&expected); - - /* binary to non-binary content */ - - quick_diff_blob_to_str(bin, NULL, nonbin_content, nonbin_len, NULL); - assert_binary_blobs_comparison(&expected); - - /* non-binary to binary blob */ - - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blobs( - bin, NULL, nonbin, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - assert_binary_blobs_comparison(&expected); - - /* - * repeat with FORCE_TEXT - */ - - opts.flags |= GIT_DIFF_FORCE_TEXT; - - quick_diff_blob_to_str(bin, NULL, bin_content, bin_len, NULL); - assert_identical_blobs_comparison(&expected); - - quick_diff_blob_to_str(nonbin, NULL, bin_content, bin_len, NULL); - assert_one_modified_with_lines(&expected, 4); - - quick_diff_blob_to_str(bin, NULL, nonbin_content, nonbin_len, NULL); - assert_one_modified_with_lines(&expected, 4); - - memset(&expected, 0, sizeof(expected)); - cl_git_pass(git_diff_blobs( - bin, NULL, nonbin, NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - assert_one_modified_with_lines(&expected, 4); - - /* cleanup */ - git_blob_free(bin); - git_blob_free(nonbin); -} - -void test_diff_blob__using_path_and_attributes(void) -{ - git_config *cfg; - git_blob *bin, *nonbin; - git_oid oid; - const char *nonbin_content = "Hello from the root\n"; - const char *bin_content = - "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n"; - size_t bin_len = 33; - const char *changed; - git_patch *p; - git_buf buf = GIT_BUF_INIT; - - /* set up custom diff drivers and 'diff' attribute mappings for them */ - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, "diff.iam_binary.binary", 1)); - cl_git_pass(git_config_set_bool(cfg, "diff.iam_text.binary", 0)); - cl_git_pass(git_config_set_string( - cfg, "diff.iam_alphactx.xfuncname", "^[A-Za-z].*$")); - cl_git_pass(git_config_set_bool(cfg, "diff.iam_textalpha.binary", 0)); - cl_git_pass(git_config_set_string( - cfg, "diff.iam_textalpha.xfuncname", "^[A-Za-z].*$")); - cl_git_pass(git_config_set_string( - cfg, "diff.iam_numctx.funcname", "^[0-9][0-9]*")); - cl_git_pass(git_config_set_bool(cfg, "diff.iam_textnum.binary", 0)); - cl_git_pass(git_config_set_string( - cfg, "diff.iam_textnum.funcname", "^[0-9][0-9]*")); - git_config_free(cfg); - - cl_git_append2file( - "attr/.gitattributes", - "\n\n# test_diff_blob__using_path_and_attributes extra\n\n" - "*.binary diff=iam_binary\n" - "*.textary diff=iam_text\n" - "*.alphary diff=iam_alphactx\n" - "*.textalphary diff=iam_textalpha\n" - "*.textnumary diff=iam_textnum\n" - "*.numary diff=iam_numctx\n\n"); - - opts.context_lines = 0; - opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - - cl_git_pass(git_oid_fromstrn(&oid, "45141a79", 8)); - cl_git_pass(git_blob_lookup_prefix(&nonbin, g_repo, &oid, 8)); - /* 20b: "Hello from the root\n" */ - - cl_git_pass(git_oid_fromstrn(&oid, "b435cd56", 8)); - cl_git_pass(git_blob_lookup_prefix(&bin, g_repo, &oid, 8)); - /* 33b: "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\n0123456789\n" */ - - /* non-binary to reference content */ - - quick_diff_blob_to_str(nonbin, NULL, nonbin_content, 0, NULL); - assert_identical_blobs_comparison(&expected); - cl_assert_equal_i(0, expected.files_binary); - - /* binary to reference content */ - - quick_diff_blob_to_str(bin, NULL, bin_content, bin_len, NULL); - assert_identical_blobs_comparison(&expected); - cl_assert_equal_i(1, expected.files_binary); - - /* add some text */ - - changed = "Hello from the root\nMore lines\nAnd more\nGo here\n"; - - quick_diff_blob_to_str(nonbin, NULL, changed, 0, NULL); - assert_one_modified(1, 3, 0, 3, 0, &expected); - - quick_diff_blob_to_str(nonbin, "foo/bar.binary", changed, 0, NULL); - cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, expected.files_binary); - cl_assert_equal_i(0, expected.hunks); - cl_assert_equal_i(0, expected.lines); - - quick_diff_blob_to_str(nonbin, "foo/bar.textary", changed, 0, NULL); - assert_one_modified(1, 3, 0, 3, 0, &expected); - - quick_diff_blob_to_str(nonbin, "foo/bar.alphary", changed, 0, NULL); - assert_one_modified(1, 3, 0, 3, 0, &expected); - - cl_git_pass(git_patch_from_blob_and_buffer( - &p, nonbin, "zzz.normal", changed, strlen(changed), NULL, &opts)); - cl_git_pass(git_patch_to_buf(&buf, p)); - cl_assert_equal_s( - "diff --git a/zzz.normal b/zzz.normal\n" - "index 45141a7..75b0dbb 100644\n" - "--- a/zzz.normal\n" - "+++ b/zzz.normal\n" - "@@ -1,0 +2,3 @@ Hello from the root\n" - "+More lines\n" - "+And more\n" - "+Go here\n", buf.ptr); - git_buf_dispose(&buf); - git_patch_free(p); - - cl_git_pass(git_patch_from_blob_and_buffer( - &p, nonbin, "zzz.binary", changed, strlen(changed), NULL, &opts)); - cl_git_pass(git_patch_to_buf(&buf, p)); - cl_assert_equal_s( - "diff --git a/zzz.binary b/zzz.binary\n" - "index 45141a7..75b0dbb 100644\n" - "Binary files a/zzz.binary and b/zzz.binary differ\n", buf.ptr); - git_buf_dispose(&buf); - git_patch_free(p); - - cl_git_pass(git_patch_from_blob_and_buffer( - &p, nonbin, "zzz.alphary", changed, strlen(changed), NULL, &opts)); - cl_git_pass(git_patch_to_buf(&buf, p)); - cl_assert_equal_s( - "diff --git a/zzz.alphary b/zzz.alphary\n" - "index 45141a7..75b0dbb 100644\n" - "--- a/zzz.alphary\n" - "+++ b/zzz.alphary\n" - "@@ -1,0 +2,3 @@ Hello from the root\n" - "+More lines\n" - "+And more\n" - "+Go here\n", buf.ptr); - git_buf_dispose(&buf); - git_patch_free(p); - - cl_git_pass(git_patch_from_blob_and_buffer( - &p, nonbin, "zzz.numary", changed, strlen(changed), NULL, &opts)); - cl_git_pass(git_patch_to_buf(&buf, p)); - cl_assert_equal_s( - "diff --git a/zzz.numary b/zzz.numary\n" - "index 45141a7..75b0dbb 100644\n" - "--- a/zzz.numary\n" - "+++ b/zzz.numary\n" - "@@ -1,0 +2,3 @@\n" - "+More lines\n" - "+And more\n" - "+Go here\n", buf.ptr); - git_buf_dispose(&buf); - git_patch_free(p); - - /* "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n" - * 33 bytes - */ - - changed = "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\nreplace a line\n"; - - cl_git_pass(git_patch_from_blob_and_buffer( - &p, bin, "zzz.normal", changed, 37, NULL, &opts)); - cl_git_pass(git_patch_to_buf(&buf, p)); - cl_assert_equal_s( - "diff --git a/zzz.normal b/zzz.normal\n" - "index b435cd5..1604519 100644\n" - "Binary files a/zzz.normal and b/zzz.normal differ\n", buf.ptr); - git_buf_dispose(&buf); - git_patch_free(p); - - cl_git_pass(git_patch_from_blob_and_buffer( - &p, bin, "zzz.textary", changed, 37, NULL, &opts)); - cl_git_pass(git_patch_to_buf(&buf, p)); - cl_assert_equal_s( - "diff --git a/zzz.textary b/zzz.textary\n" - "index b435cd5..1604519 100644\n" - "--- a/zzz.textary\n" - "+++ b/zzz.textary\n" - "@@ -3 +3 @@\n" - "-0123456789\n" - "+replace a line\n", buf.ptr); - git_buf_dispose(&buf); - git_patch_free(p); - - cl_git_pass(git_patch_from_blob_and_buffer( - &p, bin, "zzz.textalphary", changed, 37, NULL, &opts)); - cl_git_pass(git_patch_to_buf(&buf, p)); - cl_assert_equal_s( - "diff --git a/zzz.textalphary b/zzz.textalphary\n" - "index b435cd5..1604519 100644\n" - "--- a/zzz.textalphary\n" - "+++ b/zzz.textalphary\n" - "@@ -3 +3 @@\n" - "-0123456789\n" - "+replace a line\n", buf.ptr); - git_buf_dispose(&buf); - git_patch_free(p); - - cl_git_pass(git_patch_from_blob_and_buffer( - &p, bin, "zzz.textnumary", changed, 37, NULL, &opts)); - cl_git_pass(git_patch_to_buf(&buf, p)); - cl_assert_equal_s( - "diff --git a/zzz.textnumary b/zzz.textnumary\n" - "index b435cd5..1604519 100644\n" - "--- a/zzz.textnumary\n" - "+++ b/zzz.textnumary\n" - "@@ -3 +3 @@ 0123456789\n" - "-0123456789\n" - "+replace a line\n", buf.ptr); - git_buf_dispose(&buf); - git_patch_free(p); - - git_buf_dispose(&buf); - git_blob_free(nonbin); - git_blob_free(bin); -} - -void test_diff_blob__can_compare_buffer_to_buffer(void) -{ - const char *a = "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n"; - const char *b = "a\nB\nc\nd\nE\nF\nh\nj\nk\n"; - - opts.interhunk_lines = 0; - opts.context_lines = 0; - - memset(&expected, 0, sizeof(expected)); - - cl_git_pass(git_diff_buffers( - a, strlen(a), NULL, b, strlen(b), NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - assert_one_modified(4, 9, 0, 4, 5, &expected); - - opts.flags ^= GIT_DIFF_REVERSE; - - memset(&expected, 0, sizeof(expected)); - - cl_git_pass(git_diff_buffers( - a, strlen(a), NULL, b, strlen(b), NULL, &opts, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); - assert_one_modified(4, 9, 0, 5, 4, &expected); -} diff --git a/tests/diff/diff_helpers.c b/tests/diff/diff_helpers.c deleted file mode 100644 index e9900339f..000000000 --- a/tests/diff/diff_helpers.c +++ /dev/null @@ -1,316 +0,0 @@ -#include "clar_libgit2.h" -#include "diff_helpers.h" -#include "git2/sys/diff.h" - -git_tree *resolve_commit_oid_to_tree( - git_repository *repo, - const char *partial_oid) -{ - size_t len = strlen(partial_oid); - git_oid oid; - git_object *obj = NULL; - git_tree *tree = NULL; - - if (git_oid_fromstrn(&oid, partial_oid, len) == 0) - cl_git_pass(git_object_lookup_prefix(&obj, repo, &oid, len, GIT_OBJECT_ANY)); - - cl_git_pass(git_object_peel((git_object **) &tree, obj, GIT_OBJECT_TREE)); - git_object_free(obj); - return tree; -} - -static char diff_pick_suffix(int mode) -{ - if (S_ISDIR(mode)) - return '/'; - else if (GIT_PERMS_IS_EXEC(mode)) - return '*'; - else - return ' '; -} - -static void fprintf_delta(FILE *fp, const git_diff_delta *delta, float progress) -{ - char code = git_diff_status_char(delta->status); - char old_suffix = diff_pick_suffix(delta->old_file.mode); - char new_suffix = diff_pick_suffix(delta->new_file.mode); - - fprintf(fp, "%c\t%s", code, delta->old_file.path); - - if ((delta->old_file.path != delta->new_file.path && - strcmp(delta->old_file.path, delta->new_file.path) != 0) || - (delta->old_file.mode != delta->new_file.mode && - delta->old_file.mode != 0 && delta->new_file.mode != 0)) - fprintf(fp, "%c %s%c", old_suffix, delta->new_file.path, new_suffix); - else if (old_suffix != ' ') - fprintf(fp, "%c", old_suffix); - - fprintf(fp, "\t[%.2f]\n", progress); -} - -int diff_file_cb( - const git_diff_delta *delta, - float progress, - void *payload) -{ - diff_expects *e = payload; - - if (e->debug) - fprintf_delta(stderr, delta, progress); - - if (e->names) - cl_assert_equal_s(e->names[e->files], delta->old_file.path); - if (e->statuses) - cl_assert_equal_i(e->statuses[e->files], (int)delta->status); - - e->files++; - - if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0) - e->files_binary++; - - cl_assert(delta->status <= GIT_DELTA_CONFLICTED); - - e->file_status[delta->status] += 1; - - return 0; -} - -int diff_print_file_cb( - const git_diff_delta *delta, - float progress, - void *payload) -{ - if (!payload) { - fprintf_delta(stderr, delta, progress); - return 0; - } - - if (!((diff_expects *)payload)->debug) - fprintf_delta(stderr, delta, progress); - - return diff_file_cb(delta, progress, payload); -} - -int diff_binary_cb( - const git_diff_delta *delta, - const git_diff_binary *binary, - void *payload) -{ - GIT_UNUSED(delta); - GIT_UNUSED(binary); - GIT_UNUSED(payload); - - return 0; -} - -int diff_hunk_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - void *payload) -{ - diff_expects *e = payload; - const char *scan = hunk->header, *scan_end = scan + hunk->header_len; - - GIT_UNUSED(delta); - - /* confirm no NUL bytes in header text */ - while (scan < scan_end) - cl_assert('\0' != *scan++); - - e->hunks++; - e->hunk_old_lines += hunk->old_lines; - e->hunk_new_lines += hunk->new_lines; - return 0; -} - -int diff_line_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *payload) -{ - diff_expects *e = payload; - - GIT_UNUSED(delta); - GIT_UNUSED(hunk); - - e->lines++; - switch (line->origin) { - case GIT_DIFF_LINE_CONTEXT: - case GIT_DIFF_LINE_CONTEXT_EOFNL: /* techically not a line */ - e->line_ctxt++; - break; - case GIT_DIFF_LINE_ADDITION: - case GIT_DIFF_LINE_ADD_EOFNL: /* technically not a line add */ - e->line_adds++; - break; - case GIT_DIFF_LINE_DELETION: - case GIT_DIFF_LINE_DEL_EOFNL: /* technically not a line delete */ - e->line_dels++; - break; - default: - break; - } - return 0; -} - -int diff_foreach_via_iterator( - git_diff *diff, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb line_cb, - void *data) -{ - size_t d, num_d = git_diff_num_deltas(diff); - - GIT_UNUSED(binary_cb); - - for (d = 0; d < num_d; ++d) { - git_patch *patch; - const git_diff_delta *delta; - size_t h, num_h; - - cl_git_pass(git_patch_from_diff(&patch, diff, d)); - cl_assert((delta = git_patch_get_delta(patch)) != NULL); - - /* call file_cb for this file */ - if (file_cb != NULL && file_cb(delta, (float)d / num_d, data) != 0) { - git_patch_free(patch); - goto abort; - } - - /* if there are no changes, then the patch will be NULL */ - if (!patch) { - cl_assert(delta->status == GIT_DELTA_UNMODIFIED || - (delta->flags & GIT_DIFF_FLAG_BINARY) != 0); - continue; - } - - if (!hunk_cb && !line_cb) { - git_patch_free(patch); - continue; - } - - num_h = git_patch_num_hunks(patch); - - for (h = 0; h < num_h; h++) { - const git_diff_hunk *hunk; - size_t l, num_l; - - cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h)); - - if (hunk_cb && hunk_cb(delta, hunk, data) != 0) { - git_patch_free(patch); - goto abort; - } - - for (l = 0; l < num_l; ++l) { - const git_diff_line *line; - - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l)); - - if (line_cb && - line_cb(delta, hunk, line, data) != 0) { - git_patch_free(patch); - goto abort; - } - } - } - - git_patch_free(patch); - } - - return 0; - -abort: - git_error_clear(); - return GIT_EUSER; -} - -void diff_print(FILE *fp, git_diff *diff) -{ - cl_git_pass( - git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, - git_diff_print_callback__to_file_handle, fp ? fp : stderr)); -} - -void diff_print_raw(FILE *fp, git_diff *diff) -{ - cl_git_pass( - git_diff_print(diff, GIT_DIFF_FORMAT_RAW, - git_diff_print_callback__to_file_handle, fp ? fp : stderr)); -} - -static size_t num_modified_deltas(git_diff *diff) -{ - const git_diff_delta *delta; - size_t i, cnt = 0; - - for (i = 0; i < git_diff_num_deltas(diff); i++) { - delta = git_diff_get_delta(diff, i); - - if (delta->status != GIT_DELTA_UNMODIFIED) - cnt++; - } - - return cnt; -} - -void diff_assert_equal(git_diff *a, git_diff *b) -{ - const git_diff_delta *ad, *bd; - size_t i, j; - - assert(a && b); - - cl_assert_equal_i(num_modified_deltas(a), num_modified_deltas(b)); - - for (i = 0, j = 0; - i < git_diff_num_deltas(a) && j < git_diff_num_deltas(b); ) { - - ad = git_diff_get_delta(a, i); - bd = git_diff_get_delta(b, j); - - if (ad->status == GIT_DELTA_UNMODIFIED) { - i++; - continue; - } - if (bd->status == GIT_DELTA_UNMODIFIED) { - j++; - continue; - } - - cl_assert_equal_i(ad->status, bd->status); - cl_assert_equal_i(ad->flags, bd->flags); - cl_assert_equal_i(ad->similarity, bd->similarity); - cl_assert_equal_i(ad->nfiles, bd->nfiles); - - /* Don't examine the size or the flags of the deltas; - * computed deltas have sizes (parsed deltas do not) and - * computed deltas will have flags of `VALID_ID` and - * `EXISTS` (parsed deltas will not query the ODB.) - */ - - /* an empty id indicates that it wasn't presented, because - * the diff was identical. (eg, pure rename, mode change only, etc) - */ - if (ad->old_file.id_abbrev && bd->old_file.id_abbrev) { - cl_assert_equal_i(ad->old_file.id_abbrev, bd->old_file.id_abbrev); - cl_assert_equal_oid(&ad->old_file.id, &bd->old_file.id); - cl_assert_equal_i(ad->old_file.mode, bd->old_file.mode); - } - cl_assert_equal_s(ad->old_file.path, bd->old_file.path); - - if (ad->new_file.id_abbrev && bd->new_file.id_abbrev) { - cl_assert_equal_oid(&ad->new_file.id, &bd->new_file.id); - cl_assert_equal_i(ad->new_file.id_abbrev, bd->new_file.id_abbrev); - cl_assert_equal_i(ad->new_file.mode, bd->new_file.mode); - } - cl_assert_equal_s(ad->new_file.path, bd->new_file.path); - - i++; - j++; - } -} - diff --git a/tests/diff/diff_helpers.h b/tests/diff/diff_helpers.h deleted file mode 100644 index af855ce68..000000000 --- a/tests/diff/diff_helpers.h +++ /dev/null @@ -1,73 +0,0 @@ -#include "futils.h" -#include "git2/diff.h" - -extern git_tree *resolve_commit_oid_to_tree( - git_repository *repo, const char *partial_oid); - -typedef struct { - int files; - int files_binary; - - int file_status[11]; /* indexed by git_delta_t value */ - - int hunks; - int hunk_new_lines; - int hunk_old_lines; - - int lines; - int line_ctxt; - int line_adds; - int line_dels; - - /* optional arrays of expected specific values */ - const char **names; - int *statuses; - - int debug; - -} diff_expects; - -typedef struct { - const char *path; - const char *matched_pathspec; -} notify_expected; - -extern int diff_file_cb( - const git_diff_delta *delta, - float progress, - void *cb_data); - -extern int diff_print_file_cb( - const git_diff_delta *delta, - float progress, - void *cb_data); - -extern int diff_binary_cb( - const git_diff_delta *delta, - const git_diff_binary *binary, - void *cb_data); - -extern int diff_hunk_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - void *cb_data); - -extern int diff_line_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *cb_data); - -extern int diff_foreach_via_iterator( - git_diff *diff, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb line_cb, - void *data); - -extern void diff_print(FILE *fp, git_diff *diff); -extern void diff_print_raw(FILE *fp, git_diff *diff); - -extern void diff_assert_equal(git_diff *a, git_diff *b); - diff --git a/tests/diff/diffiter.c b/tests/diff/diffiter.c deleted file mode 100644 index 991c73bf4..000000000 --- a/tests/diff/diffiter.c +++ /dev/null @@ -1,453 +0,0 @@ -#include "clar_libgit2.h" -#include "diff_helpers.h" - -void test_diff_diffiter__initialize(void) -{ -} - -void test_diff_diffiter__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_diff_diffiter__create(void) -{ - git_repository *repo = cl_git_sandbox_init("attr"); - git_diff *diff; - size_t d, num_d; - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL)); - - num_d = git_diff_num_deltas(diff); - for (d = 0; d < num_d; ++d) { - const git_diff_delta *delta = git_diff_get_delta(diff, d); - cl_assert(delta != NULL); - } - - cl_assert(!git_diff_get_delta(diff, num_d)); - - git_diff_free(diff); -} - -void test_diff_diffiter__iterate_files_1(void) -{ - git_repository *repo = cl_git_sandbox_init("attr"); - git_diff *diff; - size_t d, num_d; - diff_expects exp = { 0 }; - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL)); - - num_d = git_diff_num_deltas(diff); - - for (d = 0; d < num_d; ++d) { - const git_diff_delta *delta = git_diff_get_delta(diff, d); - cl_assert(delta != NULL); - - diff_file_cb(delta, (float)d / (float)num_d, &exp); - } - cl_assert_equal_sz(6, exp.files); - - git_diff_free(diff); -} - -void test_diff_diffiter__iterate_files_2(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_diff *diff; - size_t d, num_d; - int count = 0; - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL)); - - num_d = git_diff_num_deltas(diff); - cl_assert_equal_i(8, (int)num_d); - - for (d = 0; d < num_d; ++d) { - const git_diff_delta *delta = git_diff_get_delta(diff, d); - cl_assert(delta != NULL); - count++; - } - cl_assert_equal_i(8, count); - - git_diff_free(diff); -} - -void test_diff_diffiter__iterate_files_and_hunks(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - size_t d, num_d; - int file_count = 0, hunk_count = 0; - - opts.context_lines = 3; - opts.interhunk_lines = 1; - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); - - num_d = git_diff_num_deltas(diff); - - for (d = 0; d < num_d; ++d) { - git_patch *patch; - size_t h, num_h; - - cl_git_pass(git_patch_from_diff(&patch, diff, d)); - cl_assert(patch); - - file_count++; - - num_h = git_patch_num_hunks(patch); - - for (h = 0; h < num_h; h++) { - const git_diff_hunk *hunk; - - cl_git_pass(git_patch_get_hunk(&hunk, NULL, patch, h)); - cl_assert(hunk); - - hunk_count++; - } - - git_patch_free(patch); - } - - cl_assert_equal_i(13, file_count); - cl_assert_equal_i(8, hunk_count); - - git_diff_free(diff); -} - -void test_diff_diffiter__max_size_threshold(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - int file_count = 0, binary_count = 0, hunk_count = 0; - size_t d, num_d; - - opts.context_lines = 3; - opts.interhunk_lines = 1; - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); - num_d = git_diff_num_deltas(diff); - - for (d = 0; d < num_d; ++d) { - git_patch *patch; - const git_diff_delta *delta; - - cl_git_pass(git_patch_from_diff(&patch, diff, d)); - cl_assert(patch); - delta = git_patch_get_delta(patch); - cl_assert(delta); - - file_count++; - hunk_count += (int)git_patch_num_hunks(patch); - - assert((delta->flags & (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)) != 0); - binary_count += ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); - - git_patch_free(patch); - } - - cl_assert_equal_i(13, file_count); - cl_assert_equal_i(0, binary_count); - cl_assert_equal_i(8, hunk_count); - - git_diff_free(diff); - - /* try again with low file size threshold */ - - file_count = binary_count = hunk_count = 0; - - opts.context_lines = 3; - opts.interhunk_lines = 1; - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - opts.max_size = 50; /* treat anything over 50 bytes as binary! */ - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); - num_d = git_diff_num_deltas(diff); - - for (d = 0; d < num_d; ++d) { - git_patch *patch; - const git_diff_delta *delta; - - cl_git_pass(git_patch_from_diff(&patch, diff, d)); - delta = git_patch_get_delta(patch); - - file_count++; - hunk_count += (int)git_patch_num_hunks(patch); - - assert((delta->flags & (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)) != 0); - binary_count += ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); - - git_patch_free(patch); - } - - cl_assert_equal_i(13, file_count); - /* Three files are over the 50 byte threshold: - * - staged_changes_file_deleted - * - staged_changes_modified_file - * - staged_new_file_modified_file - */ - cl_assert_equal_i(3, binary_count); - cl_assert_equal_i(5, hunk_count); - - git_diff_free(diff); -} - - -void test_diff_diffiter__iterate_all(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - diff_expects exp = {0}; - size_t d, num_d; - - opts.context_lines = 3; - opts.interhunk_lines = 1; - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); - - num_d = git_diff_num_deltas(diff); - for (d = 0; d < num_d; ++d) { - git_patch *patch; - size_t h, num_h; - - cl_git_pass(git_patch_from_diff(&patch, diff, d)); - cl_assert(patch); - exp.files++; - - num_h = git_patch_num_hunks(patch); - for (h = 0; h < num_h; h++) { - const git_diff_hunk *range; - size_t l, num_l; - - cl_git_pass(git_patch_get_hunk(&range, &num_l, patch, h)); - cl_assert(range); - exp.hunks++; - - for (l = 0; l < num_l; ++l) { - const git_diff_line *line; - - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l)); - cl_assert(line && line->content); - exp.lines++; - } - } - - git_patch_free(patch); - } - - cl_assert_equal_i(13, exp.files); - cl_assert_equal_i(8, exp.hunks); - cl_assert_equal_i(14, exp.lines); - - git_diff_free(diff); -} - -static void iterate_over_patch(git_patch *patch, diff_expects *exp) -{ - size_t h, num_h = git_patch_num_hunks(patch), num_l; - - exp->files++; - exp->hunks += (int)num_h; - - /* let's iterate in reverse, just because we can! */ - for (h = 1, num_l = 0; h <= num_h; ++h) - num_l += git_patch_num_lines_in_hunk(patch, num_h - h); - - exp->lines += (int)num_l; -} - -#define PATCH_CACHE 5 - -void test_diff_diffiter__iterate_randomly_while_saving_state(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - diff_expects exp = {0}; - git_patch *patches[PATCH_CACHE]; - size_t p, d, num_d; - - memset(patches, 0, sizeof(patches)); - - opts.context_lines = 3; - opts.interhunk_lines = 1; - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); - - num_d = git_diff_num_deltas(diff); - - /* To make sure that references counts work for diff and patch objects, - * this generates patches and randomly caches them. Only when the patch - * is removed from the cache are hunks and lines counted. At the end, - * there are still patches in the cache, so free the diff and try to - * process remaining patches after the diff is freed. - */ - - srand(121212); - p = rand() % PATCH_CACHE; - - for (d = 0; d < num_d; ++d) { - /* take old patch */ - git_patch *patch = patches[p]; - patches[p] = NULL; - - /* cache new patch */ - cl_git_pass(git_patch_from_diff(&patches[p], diff, d)); - cl_assert(patches[p] != NULL); - - /* process old patch if non-NULL */ - if (patch != NULL) { - iterate_over_patch(patch, &exp); - git_patch_free(patch); - } - - p = rand() % PATCH_CACHE; - } - - /* free diff list now - refcounts should keep things safe */ - git_diff_free(diff); - - /* process remaining unprocessed patches */ - for (p = 0; p < PATCH_CACHE; p++) { - git_patch *patch = patches[p]; - - if (patch != NULL) { - iterate_over_patch(patch, &exp); - git_patch_free(patch); - } - } - - /* hopefully it all still added up right */ - cl_assert_equal_i(13, exp.files); - cl_assert_equal_i(8, exp.hunks); - cl_assert_equal_i(14, exp.lines); -} - -/* This output is taken directly from `git diff` on the status test data */ -static const char *expected_patch_text[8] = { - /* 0 */ - "diff --git a/file_deleted b/file_deleted\n" - "deleted file mode 100644\n" - "index 5452d32..0000000\n" - "--- a/file_deleted\n" - "+++ /dev/null\n" - "@@ -1 +0,0 @@\n" - "-file_deleted\n", - /* 1 */ - "diff --git a/modified_file b/modified_file\n" - "index 452e424..0a53963 100644\n" - "--- a/modified_file\n" - "+++ b/modified_file\n" - "@@ -1 +1,2 @@\n" - " modified_file\n" - "+modified_file\n", - /* 2 */ - "diff --git a/staged_changes_file_deleted b/staged_changes_file_deleted\n" - "deleted file mode 100644\n" - "index a6be623..0000000\n" - "--- a/staged_changes_file_deleted\n" - "+++ /dev/null\n" - "@@ -1,2 +0,0 @@\n" - "-staged_changes_file_deleted\n" - "-staged_changes_file_deleted\n", - /* 3 */ - "diff --git a/staged_changes_modified_file b/staged_changes_modified_file\n" - "index 906ee77..011c344 100644\n" - "--- a/staged_changes_modified_file\n" - "+++ b/staged_changes_modified_file\n" - "@@ -1,2 +1,3 @@\n" - " staged_changes_modified_file\n" - " staged_changes_modified_file\n" - "+staged_changes_modified_file\n", - /* 4 */ - "diff --git a/staged_new_file_deleted_file b/staged_new_file_deleted_file\n" - "deleted file mode 100644\n" - "index 90b8c29..0000000\n" - "--- a/staged_new_file_deleted_file\n" - "+++ /dev/null\n" - "@@ -1 +0,0 @@\n" - "-staged_new_file_deleted_file\n", - /* 5 */ - "diff --git a/staged_new_file_modified_file b/staged_new_file_modified_file\n" - "index ed06290..8b090c0 100644\n" - "--- a/staged_new_file_modified_file\n" - "+++ b/staged_new_file_modified_file\n" - "@@ -1 +1,2 @@\n" - " staged_new_file_modified_file\n" - "+staged_new_file_modified_file\n", - /* 6 */ - "diff --git a/subdir/deleted_file b/subdir/deleted_file\n" - "deleted file mode 100644\n" - "index 1888c80..0000000\n" - "--- a/subdir/deleted_file\n" - "+++ /dev/null\n" - "@@ -1 +0,0 @@\n" - "-subdir/deleted_file\n", - /* 7 */ - "diff --git a/subdir/modified_file b/subdir/modified_file\n" - "index a619198..57274b7 100644\n" - "--- a/subdir/modified_file\n" - "+++ b/subdir/modified_file\n" - "@@ -1 +1,2 @@\n" - " subdir/modified_file\n" - "+subdir/modified_file\n" -}; - -void test_diff_diffiter__iterate_and_generate_patch_text(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_diff *diff; - size_t d, num_d; - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL)); - - num_d = git_diff_num_deltas(diff); - cl_assert_equal_i(8, (int)num_d); - - for (d = 0; d < num_d; ++d) { - git_patch *patch; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_patch_from_diff(&patch, diff, d)); - cl_assert(patch != NULL); - - cl_git_pass(git_patch_to_buf(&buf, patch)); - - cl_assert_equal_s(expected_patch_text[d], buf.ptr); - - git_buf_dispose(&buf); - git_patch_free(patch); - } - - git_diff_free(diff); -} - -void test_diff_diffiter__checks_options_version(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - const git_error *err; - - opts.version = 0; - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - - cl_git_fail(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); - err = git_error_last(); - cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); - - git_error_clear(); - opts.version = 1024; - cl_git_fail(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); - err = git_error_last(); - cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); -} - diff --git a/tests/diff/drivers.c b/tests/diff/drivers.c deleted file mode 100644 index 304a54b56..000000000 --- a/tests/diff/drivers.c +++ /dev/null @@ -1,279 +0,0 @@ -#include "clar_libgit2.h" -#include "diff_helpers.h" -#include "repository.h" -#include "diff_driver.h" - -static git_repository *g_repo = NULL; - -void test_diff_drivers__initialize(void) -{ -} - -void test_diff_drivers__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -static void overwrite_filemode(const char *expected, git_buf *actual) -{ - size_t offset; - char *found; - - found = strstr(expected, "100644"); - if (!found) - return; - - offset = ((const char *)found) - expected; - if (actual->size < offset + 6) - return; - - if (memcmp(&actual->ptr[offset], "100644", 6) != 0) - memcpy(&actual->ptr[offset], "100644", 6); -} - -void test_diff_drivers__patterns(void) -{ - git_config *cfg; - const char *one_sha = "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13"; - git_tree *one; - git_diff *diff; - git_patch *patch; - git_buf actual = GIT_BUF_INIT; - const char *expected0 = "diff --git a/untimely.txt b/untimely.txt\nindex 9a69d96..57fd0cf 100644\n--- a/untimely.txt\n+++ b/untimely.txt\n@@ -22,3 +22,5 @@ Comes through the blood of the vanguards who\n dreamed--too soon--it had sounded.\r\n \r\n -- Rudyard Kipling\r\n+\r\n+Some new stuff\r\n"; - const char *expected1 = "diff --git a/untimely.txt b/untimely.txt\nindex 9a69d96..57fd0cf 100644\nBinary files a/untimely.txt and b/untimely.txt differ\n"; - const char *expected2 = "diff --git a/untimely.txt b/untimely.txt\nindex 9a69d96..57fd0cf 100644\n--- a/untimely.txt\n+++ b/untimely.txt\n@@ -22,3 +22,5 @@ Heaven delivers on earth the Hour that cannot be\n dreamed--too soon--it had sounded.\r\n \r\n -- Rudyard Kipling\r\n+\r\n+Some new stuff\r\n"; - - g_repo = cl_git_sandbox_init("renames"); - - one = resolve_commit_oid_to_tree(g_repo, one_sha); - - /* no diff */ - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); - cl_assert_equal_i(0, (int)git_diff_num_deltas(diff)); - git_diff_free(diff); - - /* default diff */ - - cl_git_append2file("renames/untimely.txt", "\r\nSome new stuff\r\n"); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); - cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&actual, patch)); - cl_assert_equal_s(expected0, actual.ptr); - - git_buf_dispose(&actual); - git_patch_free(patch); - git_diff_free(diff); - - /* attribute diff set to false */ - - cl_git_rewritefile("renames/.gitattributes", "untimely.txt -diff\n"); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); - cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&actual, patch)); - cl_assert_equal_s(expected1, actual.ptr); - - git_buf_dispose(&actual); - git_patch_free(patch); - git_diff_free(diff); - - /* attribute diff set to unconfigured value (should use default) */ - - cl_git_rewritefile("renames/.gitattributes", "untimely.txt diff=kipling0\n"); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); - cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&actual, patch)); - cl_assert_equal_s(expected0, actual.ptr); - - git_buf_dispose(&actual); - git_patch_free(patch); - git_diff_free(diff); - - /* let's define that driver */ - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, "diff.kipling0.binary", 1)); - git_config_free(cfg); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); - cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&actual, patch)); - cl_assert_equal_s(expected1, actual.ptr); - - git_buf_dispose(&actual); - git_patch_free(patch); - git_diff_free(diff); - - /* let's use a real driver with some regular expressions */ - - git_diff_driver_registry_free(g_repo->diff_drivers); - g_repo->diff_drivers = NULL; - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, "diff.kipling0.binary", 0)); - cl_git_pass(git_config_set_string(cfg, "diff.kipling0.xfuncname", "^H.*$")); - git_config_free(cfg); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); - cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&actual, patch)); - cl_assert_equal_s(expected2, actual.ptr); - - git_buf_dispose(&actual); - git_patch_free(patch); - git_diff_free(diff); - - git_tree_free(one); -} - -void test_diff_drivers__long_lines(void) -{ - const char *base = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non nisi ligula. Ut viverra enim sed lobortis suscipit.\nPhasellus eget erat odio. Praesent at est iaculis, ultricies augue vel, dignissim risus. Suspendisse at nisi quis turpis fringilla rutrum id sit amet nulla.\nNam eget dolor fermentum, aliquet nisl at, convallis tellus. Pellentesque rhoncus erat enim, id porttitor elit euismod quis.\nMauris sollicitudin magna odio, non egestas libero vehicula ut. Etiam et quam velit. Fusce eget libero rhoncus, ultricies felis sit amet, egestas purus.\nAliquam in semper tellus. Pellentesque adipiscing rutrum velit, quis malesuada lacus consequat eget.\n"; - git_index *idx; - git_diff *diff; - git_patch *patch; - git_buf actual = GIT_BUF_INIT; - const char *expected = "diff --git a/longlines.txt b/longlines.txt\nindex c1ce6ef..0134431 100644\n--- a/longlines.txt\n+++ b/longlines.txt\n@@ -3,3 +3,5 @@ Phasellus eget erat odio. Praesent at est iaculis, ultricies augue vel, dignissi\n Nam eget dolor fermentum, aliquet nisl at, convallis tellus. Pellentesque rhoncus erat enim, id porttitor elit euismod quis.\n Mauris sollicitudin magna odio, non egestas libero vehicula ut. Etiam et quam velit. Fusce eget libero rhoncus, ultricies felis sit amet, egestas purus.\n Aliquam in semper tellus. Pellentesque adipiscing rutrum velit, quis malesuada lacus consequat eget.\n+newline\n+newline\n"; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_mkfile("empty_standard_repo/longlines.txt", base); - cl_git_pass(git_repository_index(&idx, g_repo)); - cl_git_pass(git_index_add_bypath(idx, "longlines.txt")); - cl_git_pass(git_index_write(idx)); - git_index_free(idx); - - cl_git_append2file("empty_standard_repo/longlines.txt", "newline\nnewline\n"); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); - cl_assert_equal_sz(1, git_diff_num_deltas(diff)); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&actual, patch)); - - /* if chmod not supported, overwrite mode bits since anything is possible */ - overwrite_filemode(expected, &actual); - - cl_assert_equal_s(expected, actual.ptr); - - git_buf_dispose(&actual); - git_patch_free(patch); - git_diff_free(diff); -} - -void test_diff_drivers__builtins(void) -{ - git_diff *diff; - git_patch *patch; - git_str file = GIT_STR_INIT, expected = GIT_STR_INIT; - git_buf actual = GIT_BUF_INIT; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_vector files = GIT_VECTOR_INIT; - size_t i; - char *path, *extension; - - g_repo = cl_git_sandbox_init("userdiff"); - - cl_git_pass(git_fs_path_dirload(&files, "userdiff/files", 9, 0)); - - opts.interhunk_lines = 1; - opts.context_lines = 1; - opts.pathspec.count = 1; - - git_vector_foreach(&files, i, path) { - if (git__prefixcmp(path, "files/file.")) - continue; - extension = path + strlen("files/file."); - opts.pathspec.strings = &path; - - /* do diff with no special driver */ - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - cl_assert_equal_sz(1, git_diff_num_deltas(diff)); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&actual, patch)); - - git_str_sets(&expected, "userdiff/expected/nodriver/diff."); - git_str_puts(&expected, extension); - cl_git_pass(git_futils_readbuffer(&expected, expected.ptr)); - - overwrite_filemode(expected.ptr, &actual); - - cl_assert_equal_s(expected.ptr, actual.ptr); - - git_buf_dispose(&actual); - git_patch_free(patch); - git_diff_free(diff); - - /* do diff with driver */ - - { - FILE *fp = fopen("userdiff/.gitattributes", "w"); - fprintf(fp, "*.%s diff=%s\n", extension, extension); - fclose(fp); - } - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - cl_assert_equal_sz(1, git_diff_num_deltas(diff)); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&actual, patch)); - - git_str_sets(&expected, "userdiff/expected/driver/diff."); - git_str_puts(&expected, extension); - cl_git_pass(git_futils_readbuffer(&expected, expected.ptr)); - - overwrite_filemode(expected.ptr, &actual); - - cl_assert_equal_s(expected.ptr, actual.ptr); - - git_buf_dispose(&actual); - git_patch_free(patch); - git_diff_free(diff); - - git__free(path); - } - - git_buf_dispose(&actual); - git_str_dispose(&file); - git_str_dispose(&expected); - git_vector_free(&files); -} - -void test_diff_drivers__invalid_pattern(void) -{ - git_config *cfg; - git_index *idx; - git_diff *diff; - git_patch *patch; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("userdiff"); - cl_git_mkfile("userdiff/.gitattributes", "*.storyboard diff=storyboard\n"); - - cl_git_pass(git_repository_config__weakptr(&cfg, g_repo)); - cl_git_pass(git_config_set_string(cfg, "diff.storyboard.xfuncname", "")); - - cl_git_mkfile("userdiff/dummy.storyboard", ""); - cl_git_pass(git_repository_index__weakptr(&idx, g_repo)); - cl_git_pass(git_index_add_bypath(idx, "dummy.storyboard")); - cl_git_mkfile("userdiff/dummy.storyboard", "some content\n"); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - - git_patch_free(patch); - git_diff_free(diff); -} diff --git a/tests/diff/externalmodifications.c b/tests/diff/externalmodifications.c deleted file mode 100644 index df62c3316..000000000 --- a/tests/diff/externalmodifications.c +++ /dev/null @@ -1,133 +0,0 @@ -#include "clar_libgit2.h" -#include "../checkout/checkout_helpers.h" - -#include "index.h" -#include "repository.h" - -static git_repository *g_repo; - -void test_diff_externalmodifications__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo2"); -} - -void test_diff_externalmodifications__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -void test_diff_externalmodifications__file_becomes_smaller(void) -{ - git_index *index; - git_diff *diff; - git_patch* patch; - git_str path = GIT_STR_INIT; - char big_string[500001]; - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "README")); - - /* Modify the file with a large string */ - memset(big_string, '\n', sizeof(big_string) - 1); - big_string[sizeof(big_string) - 1] = '\0'; - cl_git_mkfile(path.ptr, big_string); - - /* Get a diff */ - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); - cl_assert_equal_i(1, git_diff_num_deltas(diff)); - cl_assert_equal_i(500000, git_diff_get_delta(diff, 0)->new_file.size); - - /* Simulate file modification after we've gotten the diff. - * Write a shorter string to ensure that we don't mmap 500KB from - * the previous revision, which would most likely crash. */ - cl_git_mkfile(path.ptr, "hello"); - - /* Attempt to get a patch */ - cl_git_fail(git_patch_from_diff(&patch, diff, 0)); - - git_index_free(index); - git_diff_free(diff); - git_str_dispose(&path); -} - -void test_diff_externalmodifications__file_becomes_empty(void) -{ - git_index *index; - git_diff *diff; - git_patch* patch; - git_str path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "README")); - - /* Modify the file */ - cl_git_mkfile(path.ptr, "hello"); - - /* Get a diff */ - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); - cl_assert_equal_i(1, git_diff_num_deltas(diff)); - cl_assert_equal_i(5, git_diff_get_delta(diff, 0)->new_file.size); - - /* Empty out the file after we've gotten the diff */ - cl_git_mkfile(path.ptr, ""); - - /* Attempt to get a patch */ - cl_git_fail(git_patch_from_diff(&patch, diff, 0)); - - git_index_free(index); - git_diff_free(diff); - git_str_dispose(&path); -} - -void test_diff_externalmodifications__file_deleted(void) -{ - git_index *index; - git_diff *diff; - git_patch* patch; - git_str path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "README")); - - /* Get a diff */ - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); - cl_assert_equal_i(0, git_diff_num_deltas(diff)); - - /* Delete the file */ - cl_git_rmfile(path.ptr); - - /* Attempt to get a patch */ - cl_git_fail(git_patch_from_diff(&patch, diff, 0)); - - git_index_free(index); - git_diff_free(diff); - git_str_dispose(&path); -} - -void test_diff_externalmodifications__empty_file_becomes_non_empty(void) -{ - git_index *index; - git_diff *diff; - git_patch* patch; - git_str path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "README")); - - /* Empty out the file */ - cl_git_mkfile(path.ptr, ""); - - /* Get a diff */ - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); - cl_assert_equal_i(1, git_diff_num_deltas(diff)); - cl_assert_equal_i(0, git_diff_get_delta(diff, 0)->new_file.size); - - /* Simulate file modification after we've gotten the diff */ - cl_git_mkfile(path.ptr, "hello"); - cl_git_fail(git_patch_from_diff(&patch, diff, 0)); - - git_index_free(index); - git_diff_free(diff); - git_str_dispose(&path); -} diff --git a/tests/diff/format_email.c b/tests/diff/format_email.c deleted file mode 100644 index 612804c42..000000000 --- a/tests/diff/format_email.c +++ /dev/null @@ -1,523 +0,0 @@ -#include "clar.h" -#include "clar_libgit2.h" - -#include "commit.h" -#include "diff.h" -#include "diff_generate.h" - -static git_repository *repo; - -void test_diff_format_email__initialize(void) -{ - repo = cl_git_sandbox_init("diff_format_email"); -} - -void test_diff_format_email__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -#ifndef GIT_DEPRECATE_HARD -static void assert_email_match( - const char *expected, - const char *oidstr, - git_diff_format_email_options *opts) -{ - git_oid oid; - git_commit *commit = NULL; - git_diff *diff = NULL; - git_buf buf = GIT_BUF_INIT; - - git_oid_fromstr(&oid, oidstr); - - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - - opts->id = git_commit_id(commit); - opts->author = git_commit_author(commit); - if (!opts->summary) - opts->summary = git_commit_summary(commit); - - cl_git_pass(git_diff__commit(&diff, repo, commit, NULL)); - cl_git_pass(git_diff_format_email(&buf, diff, opts)); - - cl_assert_equal_s(expected, buf.ptr); - git_buf_dispose(&buf); - - cl_git_pass(git_diff_commit_as_email( - &buf, repo, commit, 1, 1, opts->flags, NULL)); - cl_assert_equal_s(expected, buf.ptr); - - git_diff_free(diff); - git_commit_free(commit); - git_buf_dispose(&buf); -} -#endif - -void test_diff_format_email__simple(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; - const char *email = - "From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \ - "Subject: [PATCH] Modify some content\n" \ - "\n" \ - "---\n" \ - " file1.txt | 8 +++++---\n" \ - " 1 file changed, 5 insertions(+), 3 deletions(-)\n" \ - "\n" \ - "diff --git a/file1.txt b/file1.txt\n" \ - "index 94aaae8..af8f41d 100644\n" \ - "--- a/file1.txt\n" \ - "+++ b/file1.txt\n" \ - "@@ -1,15 +1,17 @@\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "+_file1.txt_\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "+\n" \ - "+\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "+_file1.txt_\n" \ - "+_file1.txt_\n" \ - " file1.txt\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - assert_email_match( - email, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); -#endif -} - -void test_diff_format_email__with_message(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; - const char *email = "From 627e7e12d87e07a83fad5b6bfa25e86ead4a5270 Mon Sep 17 00:00:00 2001\n" \ - "From: Patrick Steinhardt \n" \ - "Date: Tue, 24 Nov 2015 13:34:39 +0100\n" \ - "Subject: [PATCH] Modify content with message\n" \ - "\n" \ - "Modify content of file3.txt by appending a new line. Make this\n" \ - "commit message somewhat longer to test behavior with newlines\n" \ - "embedded in the message body.\n" \ - "\n" \ - "Also test if new paragraphs are included correctly.\n" \ - "---\n" \ - " file3.txt | 1 +\n" \ - " 1 file changed, 1 insertion(+)\n" \ - "\n" \ - "diff --git a/file3.txt b/file3.txt\n" \ - "index 9a2d780..7309653 100644\n" \ - "--- a/file3.txt\n" \ - "+++ b/file3.txt\n" \ - "@@ -3,3 +3,4 @@ file3!\n" \ - " file3\n" \ - " file3\n" \ - " file3\n" \ - "+file3\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - opts.body = "Modify content of file3.txt by appending a new line. Make this\n" \ - "commit message somewhat longer to test behavior with newlines\n" \ - "embedded in the message body.\n" \ - "\n" \ - "Also test if new paragraphs are included correctly."; - - assert_email_match( - email, "627e7e12d87e07a83fad5b6bfa25e86ead4a5270", &opts); -#endif -} - - -void test_diff_format_email__multiple(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_oid oid; - git_commit *commit = NULL; - git_diff *diff = NULL; - git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; - git_buf buf = GIT_BUF_INIT; - - const char *email = - "From 10808fe9c9be5a190c0ba68d1a002233fb363508 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Thu, 10 Apr 2014 19:37:05 +0200\n" \ - "Subject: [PATCH 1/2] Added file2.txt file3.txt\n" \ - "\n" \ - "---\n" \ - " file2.txt | 5 +++++\n" \ - " file3.txt | 5 +++++\n" \ - " 2 files changed, 10 insertions(+)\n" \ - " create mode 100644 file2.txt\n" \ - " create mode 100644 file3.txt\n" \ - "\n" \ - "diff --git a/file2.txt b/file2.txt\n" \ - "new file mode 100644\n" \ - "index 0000000..e909123\n" \ - "--- /dev/null\n" \ - "+++ b/file2.txt\n" \ - "@@ -0,0 +1,5 @@\n" \ - "+file2\n" \ - "+file2\n" \ - "+file2\n" \ - "+file2\n" \ - "+file2\n" \ - "diff --git a/file3.txt b/file3.txt\n" \ - "new file mode 100644\n" \ - "index 0000000..9435022\n" \ - "--- /dev/null\n" \ - "+++ b/file3.txt\n" \ - "@@ -0,0 +1,5 @@\n" \ - "+file3\n" \ - "+file3\n" \ - "+file3\n" \ - "+file3\n" \ - "+file3\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n" \ - "From 873806f6f27e631eb0b23e4b56bea2bfac14a373 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Thu, 10 Apr 2014 19:37:36 +0200\n" \ - "Subject: [PATCH 2/2] Modified file2.txt, file3.txt\n" \ - "\n" \ - "---\n" \ - " file2.txt | 2 +-\n" \ - " file3.txt | 2 +-\n" \ - " 2 files changed, 2 insertions(+), 2 deletions(-)\n" \ - "\n" \ - "diff --git a/file2.txt b/file2.txt\n" \ - "index e909123..7aff11d 100644\n" \ - "--- a/file2.txt\n" \ - "+++ b/file2.txt\n" \ - "@@ -1,5 +1,5 @@\n" \ - " file2\n" \ - " file2\n" \ - " file2\n" \ - "-file2\n" \ - "+file2!\n" \ - " file2\n" \ - "diff --git a/file3.txt b/file3.txt\n" \ - "index 9435022..9a2d780 100644\n" \ - "--- a/file3.txt\n" \ - "+++ b/file3.txt\n" \ - "@@ -1,5 +1,5 @@\n" \ - " file3\n" \ - "-file3\n" \ - "+file3!\n" \ - " file3\n" \ - " file3\n" \ - " file3\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - - git_oid_fromstr(&oid, "10808fe9c9be5a190c0ba68d1a002233fb363508"); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - - opts.id = git_commit_id(commit); - opts.author = git_commit_author(commit); - opts.summary = git_commit_summary(commit); - opts.patch_no = 1; - opts.total_patches = 2; - - cl_git_pass(git_diff__commit(&diff, repo, commit, NULL)); - cl_git_pass(git_diff_format_email(&buf, diff, &opts)); - - git_diff_free(diff); - git_commit_free(commit); - diff = NULL; - commit = NULL; - - git_oid_fromstr(&oid, "873806f6f27e631eb0b23e4b56bea2bfac14a373"); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - - opts.id = git_commit_id(commit); - opts.author = git_commit_author(commit); - opts.summary = git_commit_summary(commit); - opts.patch_no = 2; - opts.total_patches = 2; - - cl_git_pass(git_diff__commit(&diff, repo, commit, NULL)); - cl_git_pass(git_diff_format_email(&buf, diff, &opts)); - - cl_assert_equal_s(email, buf.ptr); - - git_diff_free(diff); - git_commit_free(commit); - git_buf_dispose(&buf); -#endif -} - -void test_diff_format_email__exclude_marker(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; - const char *email = - "From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \ - "Subject: Modify some content\n" \ - "\n" \ - "---\n" \ - " file1.txt | 8 +++++---\n" \ - " 1 file changed, 5 insertions(+), 3 deletions(-)\n" \ - "\n" \ - "diff --git a/file1.txt b/file1.txt\n" \ - "index 94aaae8..af8f41d 100644\n" \ - "--- a/file1.txt\n" \ - "+++ b/file1.txt\n" \ - "@@ -1,15 +1,17 @@\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "+_file1.txt_\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "+\n" \ - "+\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "+_file1.txt_\n" \ - "+_file1.txt_\n" \ - " file1.txt\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - opts.flags |= GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER; - - assert_email_match( - email, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); -#endif -} - -void test_diff_format_email__invalid_no(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_oid oid; - git_commit *commit = NULL; - git_diff *diff = NULL; - git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; - git_buf buf = GIT_BUF_INIT; - - git_oid_fromstr(&oid, "9264b96c6d104d0e07ae33d3007b6a48246c6f92"); - - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - - opts.id = git_commit_id(commit); - opts.author = git_commit_author(commit); - opts.summary = git_commit_summary(commit); - opts.patch_no = 2; - opts.total_patches = 1; - - cl_git_pass(git_diff__commit(&diff, repo, commit, NULL)); - cl_git_fail(git_diff_format_email(&buf, diff, &opts)); - cl_git_fail(git_diff_commit_as_email(&buf, repo, commit, 2, 1, 0, NULL)); - - git_diff_free(diff); - git_commit_free(commit); - git_buf_dispose(&buf); -#endif -} - -void test_diff_format_email__mode_change(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; - const char *email = - "From 7ade76dd34bba4733cf9878079f9fd4a456a9189 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Thu, 10 Apr 2014 10:05:03 +0200\n" \ - "Subject: [PATCH] Update permissions\n" \ - "\n" \ - "---\n" \ - " file1.txt.renamed | 0\n" \ - " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ - " mode change 100644 => 100755 file1.txt.renamed\n" \ - "\n" \ - "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \ - "old mode 100644\n" \ - "new mode 100755\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - assert_email_match( - email, "7ade76dd34bba4733cf9878079f9fd4a456a9189", &opts); -#endif -} - -void test_diff_format_email__rename_add_remove(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; - const char *email = - "From 6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Wed, 9 Apr 2014 21:15:56 +0200\n" \ - "Subject: [PATCH] Renamed file1.txt -> file1.txt.renamed\n" \ - "\n" \ - "---\n" \ - " file1.txt | 17 -----------------\n" \ - " file1.txt.renamed | 17 +++++++++++++++++\n" \ - " 2 files changed, 17 insertions(+), 17 deletions(-)\n" \ - " delete mode 100644 file1.txt\n" \ - " create mode 100644 file1.txt.renamed\n" \ - "\n" \ - "diff --git a/file1.txt b/file1.txt\n" \ - "deleted file mode 100644\n" \ - "index af8f41d..0000000\n" \ - "--- a/file1.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,17 +0,0 @@\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-_file1.txt_\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-\n" \ - "-\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-_file1.txt_\n" \ - "-_file1.txt_\n" \ - "-file1.txt\n" \ - "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \ - "new file mode 100644\n" \ - "index 0000000..a97157a\n" \ - "--- /dev/null\n" \ - "+++ b/file1.txt.renamed\n" \ - "@@ -0,0 +1,17 @@\n" \ - "+file1.txt\n" \ - "+file1.txt\n" \ - "+_file1.txt_\n" \ - "+file1.txt\n" \ - "+file1.txt\n" \ - "+file1.txt_renamed\n" \ - "+file1.txt\n" \ - "+\n" \ - "+\n" \ - "+file1.txt\n" \ - "+file1.txt\n" \ - "+file1.txt_renamed\n" \ - "+file1.txt\n" \ - "+file1.txt\n" \ - "+_file1.txt_\n" \ - "+_file1.txt_\n" \ - "+file1.txt\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - assert_email_match( - email, "6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d", &opts); -#endif -} - -void test_diff_format_email__multiline_summary(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; - const char *email = - "From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \ - "Subject: [PATCH] Modify some content\n" \ - "\n" \ - "---\n" \ - " file1.txt | 8 +++++---\n" \ - " 1 file changed, 5 insertions(+), 3 deletions(-)\n" \ - "\n" \ - "diff --git a/file1.txt b/file1.txt\n" \ - "index 94aaae8..af8f41d 100644\n" \ - "--- a/file1.txt\n" \ - "+++ b/file1.txt\n" \ - "@@ -1,15 +1,17 @@\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "+_file1.txt_\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "+\n" \ - "+\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "+_file1.txt_\n" \ - "+_file1.txt_\n" \ - " file1.txt\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - opts.summary = "Modify some content\nSome extra stuff here"; - - assert_email_match( - email, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); -#endif -} - -void test_diff_format_email__binary(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; - const char *email = - "From 8d7523f6fcb2404257889abe0d96f093d9f524f9 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Sun, 13 Apr 2014 18:10:18 +0200\n" \ - "Subject: [PATCH] Modified binary file\n" \ - "\n" \ - "---\n" \ - " binary.bin | Bin 3 -> 5 bytes\n" \ - " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ - "\n" \ - "diff --git a/binary.bin b/binary.bin\n" \ - "index bd474b2..9ac35ff 100644\n" \ - "Binary files a/binary.bin and b/binary.bin differ\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - opts.summary = "Modified binary file"; - - assert_email_match( - email, "8d7523f6fcb2404257889abe0d96f093d9f524f9", &opts); -#endif -} - diff --git a/tests/diff/index.c b/tests/diff/index.c deleted file mode 100644 index b616a372b..000000000 --- a/tests/diff/index.c +++ /dev/null @@ -1,302 +0,0 @@ -#include "clar_libgit2.h" -#include "diff_helpers.h" - -static git_repository *g_repo = NULL; - -void test_diff_index__initialize(void) -{ - g_repo = cl_git_sandbox_init("status"); -} - -void test_diff_index__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_diff_index__0(void) -{ - /* grabbed a couple of commit oids from the history of the attr repo */ - const char *a_commit = "26a125ee1bf"; /* the current HEAD */ - const char *b_commit = "0017bd4ab1ec3"; /* the start */ - git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); - git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - diff_expects exp; - - cl_assert(a); - cl_assert(b); - - opts.context_lines = 1; - opts.interhunk_lines = 1; - - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - /* to generate these values: - * - cd to tests/resources/status, - * - mv .gitted .git - * - git diff --name-status --cached 26a125ee1bf - * - git diff -U1 --cached 26a125ee1bf - * - mv .git .gitted - */ - cl_assert_equal_i(8, exp.files); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); - - cl_assert_equal_i(8, exp.hunks); - - cl_assert_equal_i(11, exp.lines); - cl_assert_equal_i(3, exp.line_ctxt); - cl_assert_equal_i(6, exp.line_adds); - cl_assert_equal_i(2, exp.line_dels); - - git_diff_free(diff); - diff = NULL; - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - /* to generate these values: - * - cd to tests/resources/status, - * - mv .gitted .git - * - git diff --name-status --cached 0017bd4ab1ec3 - * - git diff -U1 --cached 0017bd4ab1ec3 - * - mv .git .gitted - */ - cl_assert_equal_i(12, exp.files); - cl_assert_equal_i(7, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); - - cl_assert_equal_i(12, exp.hunks); - - cl_assert_equal_i(16, exp.lines); - cl_assert_equal_i(3, exp.line_ctxt); - cl_assert_equal_i(11, exp.line_adds); - cl_assert_equal_i(2, exp.line_dels); - - git_diff_free(diff); - diff = NULL; - - git_tree_free(a); - git_tree_free(b); -} - -static int diff_stop_after_2_files( - const git_diff_delta *delta, - float progress, - void *payload) -{ - diff_expects *e = payload; - - GIT_UNUSED(progress); - GIT_UNUSED(delta); - - e->files++; - - return (e->files == 2); -} - -void test_diff_index__1(void) -{ - /* grabbed a couple of commit oids from the history of the attr repo */ - const char *a_commit = "26a125ee1bf"; /* the current HEAD */ - const char *b_commit = "0017bd4ab1ec3"; /* the start */ - git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); - git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - diff_expects exp; - - cl_assert(a); - cl_assert(b); - - opts.context_lines = 1; - opts.interhunk_lines = 1; - - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); - - cl_assert_equal_i(1, git_diff_foreach( - diff, diff_stop_after_2_files, NULL, NULL, NULL, &exp) ); - - cl_assert_equal_i(2, exp.files); - - git_diff_free(diff); - diff = NULL; - - git_tree_free(a); - git_tree_free(b); -} - -void test_diff_index__checks_options_version(void) -{ - const char *a_commit = "26a125ee1bf"; - git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - const git_error *err; - - opts.version = 0; - cl_git_fail(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); - err = git_error_last(); - cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); - cl_assert_equal_p(diff, NULL); - - git_error_clear(); - opts.version = 1024; - cl_git_fail(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); - err = git_error_last(); - cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); - cl_assert_equal_p(diff, NULL); - - git_tree_free(a); -} - -static void do_conflicted_diff(diff_expects *exp, unsigned long flags) -{ - const char *a_commit = "26a125ee1bf"; /* the current HEAD */ - git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_index_entry ancestor = {{0}}, ours = {{0}}, theirs = {{0}}; - git_diff *diff = NULL; - git_index *index; - - cl_assert(a); - - opts.context_lines = 1; - opts.interhunk_lines = 1; - opts.flags |= flags; - - memset(exp, 0, sizeof(diff_expects)); - - cl_git_pass(git_repository_index(&index, g_repo)); - - ancestor.path = ours.path = theirs.path = "staged_changes"; - ancestor.mode = ours.mode = theirs.mode = GIT_FILEMODE_BLOB; - - git_oid_fromstr(&ancestor.id, "d427e0b2e138501a3d15cc376077a3631e15bd46"); - git_oid_fromstr(&ours.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); - git_oid_fromstr(&theirs.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); - - cl_git_pass(git_index_conflict_add(index, &ancestor, &ours, &theirs)); - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, index, &opts)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, exp)); - - git_diff_free(diff); - git_tree_free(a); - git_index_free(index); -} - -void test_diff_index__reports_conflicts(void) -{ - diff_expects exp; - - do_conflicted_diff(&exp, 0); - - cl_assert_equal_i(8, exp.files); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_CONFLICTED]); - - cl_assert_equal_i(7, exp.hunks); - - cl_assert_equal_i(9, exp.lines); - cl_assert_equal_i(2, exp.line_ctxt); - cl_assert_equal_i(5, exp.line_adds); - cl_assert_equal_i(2, exp.line_dels); -} - -void test_diff_index__reports_conflicts_when_reversed(void) -{ - diff_expects exp; - - do_conflicted_diff(&exp, GIT_DIFF_REVERSE); - - cl_assert_equal_i(8, exp.files); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_CONFLICTED]); - - cl_assert_equal_i(7, exp.hunks); - - cl_assert_equal_i(9, exp.lines); - cl_assert_equal_i(2, exp.line_ctxt); - cl_assert_equal_i(2, exp.line_adds); - cl_assert_equal_i(5, exp.line_dels); -} - -void test_diff_index__not_in_head_conflicted(void) -{ - const char *a_commit = "26a125ee1bf"; /* the current HEAD */ - git_index_entry theirs = {{0}}; - git_index *index; - git_diff *diff; - const git_diff_delta *delta; - - git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_read_tree(index, a)); - - theirs.path = "file_not_in_head"; - theirs.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&theirs.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); - cl_git_pass(git_index_conflict_add(index, NULL, NULL, &theirs)); - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, index, NULL)); - - cl_assert_equal_i(git_diff_num_deltas(diff), 1); - delta = git_diff_get_delta(diff, 0); - cl_assert_equal_i(delta->status, GIT_DELTA_CONFLICTED); - - git_diff_free(diff); - git_index_free(index); - git_tree_free(a); -} - -void test_diff_index__to_index(void) -{ - const char *a_commit = "26a125ee1bf"; /* the current HEAD */ - git_tree *old_tree; - git_index *old_index; - git_index *new_index; - git_diff *diff; - diff_expects exp; - - cl_git_pass(git_index_new(&old_index)); - old_tree = resolve_commit_oid_to_tree(g_repo, a_commit); - cl_git_pass(git_index_read_tree(old_index, old_tree)); - - cl_git_pass(git_repository_index(&new_index, g_repo)); - - cl_git_pass(git_diff_index_to_index(&diff, g_repo, old_index, new_index, NULL)); - - memset(&exp, 0, sizeof(diff_expects)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(8, exp.files); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_CONFLICTED]); - - git_diff_free(diff); - git_index_free(new_index); - git_index_free(old_index); - git_tree_free(old_tree); -} diff --git a/tests/diff/notify.c b/tests/diff/notify.c deleted file mode 100644 index 653512795..000000000 --- a/tests/diff/notify.c +++ /dev/null @@ -1,258 +0,0 @@ -#include "clar_libgit2.h" -#include "diff_helpers.h" - -static git_repository *g_repo = NULL; - -void test_diff_notify__initialize(void) -{ -} - -void test_diff_notify__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int assert_called_notifications( - const git_diff *diff_so_far, - const git_diff_delta *delta_to_add, - const char *matched_pathspec, - void *payload) -{ - bool found = false; - notify_expected *exp = (notify_expected*)payload; - notify_expected *e; - - GIT_UNUSED(diff_so_far); - - for (e = exp; e->path != NULL; e++) { - if (strcmp(e->path, delta_to_add->new_file.path)) - continue; - - cl_assert_equal_s(e->matched_pathspec, matched_pathspec); - - found = true; - break; - } - - cl_assert(found); - return 0; -} - -static void test_notify( - char **searched_pathspecs, - int pathspecs_count, - notify_expected *expected_matched_pathspecs, - int expected_diffed_files_count) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - diff_expects exp; - - g_repo = cl_git_sandbox_init("status"); - - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - opts.notify_cb = assert_called_notifications; - opts.pathspec.strings = searched_pathspecs; - opts.pathspec.count = pathspecs_count; - - opts.payload = expected_matched_pathspecs; - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(expected_diffed_files_count, exp.files); - - git_diff_free(diff); -} - -void test_diff_notify__notify_single_pathspec(void) -{ - char *searched_pathspecs[] = { - "*_deleted", - }; - notify_expected expected_matched_pathspecs[] = { - { "file_deleted", "*_deleted" }, - { "staged_changes_file_deleted", "*_deleted" }, - { NULL, NULL } - }; - - test_notify(searched_pathspecs, 1, expected_matched_pathspecs, 2); -} - -void test_diff_notify__notify_multiple_pathspec(void) -{ - char *searched_pathspecs[] = { - "staged_changes_cant_find_me", - "subdir/modified_cant_find_me", - "subdir/*", - "staged*" - }; - notify_expected expected_matched_pathspecs[] = { - { "staged_changes_file_deleted", "staged*" }, - { "staged_changes_modified_file", "staged*" }, - { "staged_delete_modified_file", "staged*" }, - { "staged_new_file_deleted_file", "staged*" }, - { "staged_new_file_modified_file", "staged*" }, - { "subdir/deleted_file", "subdir/*" }, - { "subdir/modified_file", "subdir/*" }, - { "subdir/new_file", "subdir/*" }, - { NULL, NULL } - }; - - test_notify(searched_pathspecs, 4, expected_matched_pathspecs, 8); -} - -void test_diff_notify__notify_catchall_with_empty_pathspecs(void) -{ - char *searched_pathspecs[] = { - "", - "" - }; - notify_expected expected_matched_pathspecs[] = { - { "file_deleted", NULL }, - { "ignored_file", NULL }, - { "modified_file", NULL }, - { "new_file", NULL }, - { "\xe8\xbf\x99", NULL }, - { "staged_changes_file_deleted", NULL }, - { "staged_changes_modified_file", NULL }, - { "staged_delete_modified_file", NULL }, - { "staged_new_file_deleted_file", NULL }, - { "staged_new_file_modified_file", NULL }, - { "subdir/deleted_file", NULL }, - { "subdir/modified_file", NULL }, - { "subdir/new_file", NULL }, - { NULL, NULL } - }; - - test_notify(searched_pathspecs, 1, expected_matched_pathspecs, 13); -} - -void test_diff_notify__notify_catchall(void) -{ - char *searched_pathspecs[] = { - "*", - }; - notify_expected expected_matched_pathspecs[] = { - { "file_deleted", "*" }, - { "ignored_file", "*" }, - { "modified_file", "*" }, - { "new_file", "*" }, - { "\xe8\xbf\x99", "*" }, - { "staged_changes_file_deleted", "*" }, - { "staged_changes_modified_file", "*" }, - { "staged_delete_modified_file", "*" }, - { "staged_new_file_deleted_file", "*" }, - { "staged_new_file_modified_file", "*" }, - { "subdir/deleted_file", "*" }, - { "subdir/modified_file", "*" }, - { "subdir/new_file", "*" }, - { NULL, NULL } - }; - - test_notify(searched_pathspecs, 1, expected_matched_pathspecs, 13); -} - -static int abort_diff( - const git_diff *diff_so_far, - const git_diff_delta *delta_to_add, - const char *matched_pathspec, - void *payload) -{ - GIT_UNUSED(diff_so_far); - GIT_UNUSED(delta_to_add); - GIT_UNUSED(matched_pathspec); - GIT_UNUSED(payload); - - return -42; -} - -void test_diff_notify__notify_cb_can_abort_diff(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - char *pathspec = NULL; - - g_repo = cl_git_sandbox_init("status"); - - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - opts.notify_cb = abort_diff; - opts.pathspec.strings = &pathspec; - opts.pathspec.count = 1; - - pathspec = "file_deleted"; - cl_git_fail_with( - git_diff_index_to_workdir(&diff, g_repo, NULL, &opts), -42); - - pathspec = "staged_changes_modified_file"; - cl_git_fail_with( - git_diff_index_to_workdir(&diff, g_repo, NULL, &opts), -42); -} - -static int filter_all( - const git_diff *diff_so_far, - const git_diff_delta *delta_to_add, - const char *matched_pathspec, - void *payload) -{ - GIT_UNUSED(diff_so_far); - GIT_UNUSED(delta_to_add); - GIT_UNUSED(matched_pathspec); - GIT_UNUSED(payload); - - return 42; -} - -void test_diff_notify__notify_cb_can_be_used_as_filtering_function(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - char *pathspec = NULL; - diff_expects exp; - - g_repo = cl_git_sandbox_init("status"); - - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - opts.notify_cb = filter_all; - opts.pathspec.strings = &pathspec; - opts.pathspec.count = 1; - - pathspec = "*_deleted"; - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(0, exp.files); - - git_diff_free(diff); -} - -static int progress_abort_diff( - const git_diff *diff_so_far, - const char *old_path, - const char *new_path, - void *payload) -{ - GIT_UNUSED(diff_so_far); - GIT_UNUSED(old_path); - GIT_UNUSED(new_path); - GIT_UNUSED(payload); - - return -42; -} - -void test_diff_notify__progress_cb_can_abort_diff(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - - g_repo = cl_git_sandbox_init("status"); - - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - opts.progress_cb = progress_abort_diff; - - cl_git_fail_with( - git_diff_index_to_workdir(&diff, g_repo, NULL, &opts), -42); -} diff --git a/tests/diff/parse.c b/tests/diff/parse.c deleted file mode 100644 index d3a0c8de6..000000000 --- a/tests/diff/parse.c +++ /dev/null @@ -1,450 +0,0 @@ -#include "clar_libgit2.h" -#include "patch.h" -#include "patch_parse.h" -#include "diff_helpers.h" - -#include "../patch/patch_common.h" - -void test_diff_parse__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_diff_parse__nonpatches_fail_with_notfound(void) -{ - git_diff *diff; - const char *not = PATCH_NOT_A_PATCH; - const char *not_with_leading = "Leading text.\n" PATCH_NOT_A_PATCH; - const char *not_with_trailing = PATCH_NOT_A_PATCH "Trailing text.\n"; - const char *not_with_both = "Lead.\n" PATCH_NOT_A_PATCH "Trail.\n"; - - cl_git_fail_with(GIT_ENOTFOUND, - git_diff_from_buffer(&diff, - not, - strlen(not))); - cl_git_fail_with(GIT_ENOTFOUND, - git_diff_from_buffer(&diff, - not_with_leading, - strlen(not_with_leading))); - cl_git_fail_with(GIT_ENOTFOUND, - git_diff_from_buffer(&diff, - not_with_trailing, - strlen(not_with_trailing))); - cl_git_fail_with(GIT_ENOTFOUND, - git_diff_from_buffer(&diff, - not_with_both, - strlen(not_with_both))); -} - -static void test_parse_invalid_diff(const char *invalid_diff) -{ - git_diff *diff; - git_str buf = GIT_STR_INIT; - - /* throw some random (legitimate) diffs in with the given invalid - * one. - */ - git_str_puts(&buf, PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE); - git_str_puts(&buf, PATCH_BINARY_DELTA); - git_str_puts(&buf, invalid_diff); - git_str_puts(&buf, PATCH_ORIGINAL_TO_CHANGE_MIDDLE); - git_str_puts(&buf, PATCH_BINARY_LITERAL); - - cl_git_fail_with(GIT_ERROR, - git_diff_from_buffer(&diff, buf.ptr, buf.size)); - - git_str_dispose(&buf); -} - -void test_diff_parse__exact_rename(void) -{ - const char *content = - "---\n" - " old_name.c => new_name.c | 0\n" - " 1 file changed, 0 insertions(+), 0 deletions(-)\n" - " rename old_name.c => new_name.c (100%)\n" - "\n" - "diff --git a/old_name.c b/new_name.c\n" - "similarity index 100%\n" - "rename from old_name.c\n" - "rename to new_name.c\n" - "-- \n" - "2.9.3\n"; - git_diff *diff; - - cl_git_pass(git_diff_from_buffer( - &diff, content, strlen(content))); - git_diff_free(diff); -} - -void test_diff_parse__empty_file(void) -{ - const char *content = - "---\n" - " file | 0\n" - " 1 file changed, 0 insertions(+), 0 deletions(-)\n" - " created mode 100644 file\n" - "\n" - "diff --git a/file b/file\n" - "new file mode 100644\n" - "index 0000000..e69de29\n" - "-- \n" - "2.20.1\n"; - git_diff *diff; - - cl_git_pass(git_diff_from_buffer( - &diff, content, strlen(content))); - git_diff_free(diff); -} - -void test_diff_parse__no_extended_headers(void) -{ - const char *content = PATCH_NO_EXTENDED_HEADERS; - git_diff *diff; - - cl_git_pass(git_diff_from_buffer( - &diff, content, strlen(content))); - git_diff_free(diff); -} - -void test_diff_parse__add_delete_no_index(void) -{ - const char *content = - "diff --git a/file.txt b/file.txt\n" - "new file mode 100644\n" - "--- /dev/null\n" - "+++ b/file.txt\n" - "@@ -0,0 +1,2 @@\n" - "+one\n" - "+two\n" - "diff --git a/otherfile.txt b/otherfile.txt\n" - "deleted file mode 100644\n" - "--- a/otherfile.txt\n" - "+++ /dev/null\n" - "@@ -1,1 +0,0 @@\n" - "-three\n"; - git_diff *diff; - - cl_git_pass(git_diff_from_buffer( - &diff, content, strlen(content))); - git_diff_free(diff); -} - -void test_diff_parse__invalid_patches_fails(void) -{ - test_parse_invalid_diff(PATCH_CORRUPT_MISSING_NEW_FILE); - test_parse_invalid_diff(PATCH_CORRUPT_MISSING_OLD_FILE); - test_parse_invalid_diff(PATCH_CORRUPT_NO_CHANGES); - test_parse_invalid_diff(PATCH_CORRUPT_MISSING_HUNK_HEADER); -} - -static void test_tree_to_tree_computed_to_parsed( - const char *sandbox, const char *a_id, const char *b_id, - uint32_t diff_flags, uint32_t find_flags) -{ - git_repository *repo; - git_diff *computed, *parsed; - git_tree *a, *b; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - git_buf computed_buf = GIT_BUF_INIT; - - repo = cl_git_sandbox_init(sandbox); - - opts.id_abbrev = GIT_OID_HEXSZ; - opts.flags = GIT_DIFF_SHOW_BINARY | diff_flags; - findopts.flags = find_flags; - - cl_assert((a = resolve_commit_oid_to_tree(repo, a_id)) != NULL); - cl_assert((b = resolve_commit_oid_to_tree(repo, b_id)) != NULL); - - cl_git_pass(git_diff_tree_to_tree(&computed, repo, a, b, &opts)); - - if (find_flags) - cl_git_pass(git_diff_find_similar(computed, &findopts)); - - cl_git_pass(git_diff_to_buf(&computed_buf, - computed, GIT_DIFF_FORMAT_PATCH)); - - cl_git_pass(git_diff_from_buffer(&parsed, - computed_buf.ptr, computed_buf.size)); - - diff_assert_equal(computed, parsed); - - git_tree_free(a); - git_tree_free(b); - - git_diff_free(computed); - git_diff_free(parsed); - - git_buf_dispose(&computed_buf); - - cl_git_sandbox_cleanup(); -} - -void test_diff_parse__can_parse_generated_diff(void) -{ - test_tree_to_tree_computed_to_parsed( - "diff", "d70d245e", "7a9e0b02", 0, 0); - test_tree_to_tree_computed_to_parsed( - "unsymlinked.git", "806999", "a8595c", 0, 0); - test_tree_to_tree_computed_to_parsed("diff", - "d70d245ed97ed2aa596dd1af6536e4bfdb047b69", - "7a9e0b02e63179929fed24f0a3e0f19168114d10", 0, 0); - test_tree_to_tree_computed_to_parsed( - "unsymlinked.git", "7fccd7", "806999", 0, 0); - test_tree_to_tree_computed_to_parsed( - "unsymlinked.git", "7fccd7", "a8595c", 0, 0); - test_tree_to_tree_computed_to_parsed( - "attr", "605812a", "370fe9ec22", 0, 0); - test_tree_to_tree_computed_to_parsed( - "attr", "f5b0af1fb4f5c", "370fe9ec22", 0, 0); - test_tree_to_tree_computed_to_parsed( - "diff", "d70d245e", "d70d245e", 0, 0); - test_tree_to_tree_computed_to_parsed("diff_format_email", - "873806f6f27e631eb0b23e4b56bea2bfac14a373", - "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", - GIT_DIFF_SHOW_BINARY, 0); - test_tree_to_tree_computed_to_parsed("diff_format_email", - "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", - "873806f6f27e631eb0b23e4b56bea2bfac14a373", - GIT_DIFF_SHOW_BINARY, 0); - test_tree_to_tree_computed_to_parsed("renames", - "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2", - "2bc7f351d20b53f1c72c16c4b036e491c478c49a", - 0, GIT_DIFF_FIND_RENAMES); - test_tree_to_tree_computed_to_parsed("renames", - "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2", - "2bc7f351d20b53f1c72c16c4b036e491c478c49a", - GIT_DIFF_INCLUDE_UNMODIFIED, - 0); - test_tree_to_tree_computed_to_parsed("renames", - "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2", - "2bc7f351d20b53f1c72c16c4b036e491c478c49a", - GIT_DIFF_INCLUDE_UNMODIFIED, - GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED | GIT_DIFF_FIND_EXACT_MATCH_ONLY); -} - -void test_diff_parse__get_patch_from_diff(void) -{ - git_repository *repo; - git_diff *computed, *parsed; - git_tree *a, *b; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_buf computed_buf = GIT_BUF_INIT; - git_patch *patch_computed, *patch_parsed; - - repo = cl_git_sandbox_init("diff"); - - opts.flags = GIT_DIFF_SHOW_BINARY; - - cl_assert((a = resolve_commit_oid_to_tree(repo, - "d70d245ed97ed2aa596dd1af6536e4bfdb047b69")) != NULL); - cl_assert((b = resolve_commit_oid_to_tree(repo, - "7a9e0b02e63179929fed24f0a3e0f19168114d10")) != NULL); - - cl_git_pass(git_diff_tree_to_tree(&computed, repo, a, b, &opts)); - cl_git_pass(git_diff_to_buf(&computed_buf, - computed, GIT_DIFF_FORMAT_PATCH)); - cl_git_pass(git_patch_from_diff(&patch_computed, computed, 0)); - - cl_git_pass(git_diff_from_buffer(&parsed, - computed_buf.ptr, computed_buf.size)); - cl_git_pass(git_patch_from_diff(&patch_parsed, parsed, 0)); - - cl_assert_equal_i( - git_patch_num_hunks(patch_computed), - git_patch_num_hunks(patch_parsed)); - - git_patch_free(patch_computed); - git_patch_free(patch_parsed); - - git_tree_free(a); - git_tree_free(b); - - git_diff_free(computed); - git_diff_free(parsed); - - git_buf_dispose(&computed_buf); - - cl_git_sandbox_cleanup(); -} - -static int file_cb(const git_diff_delta *delta, float progress, void *payload) -{ - int *called = (int *) payload; - GIT_UNUSED(delta); - GIT_UNUSED(progress); - (*called)++; - return 0; -} - -void test_diff_parse__foreach_works_with_parsed_patch(void) -{ - const char patch[] = - "diff --git a/obj1 b/obj2\n" - "index 1234567..7654321 10644\n" - "--- a/obj1\n" - "+++ b/obj2\n" - "@@ -1 +1 @@\n" - "-abcde\n" - "+12345\n"; - int called = 0; - git_diff *diff; - - cl_git_pass(git_diff_from_buffer(&diff, patch, strlen(patch))); - cl_git_pass(git_diff_foreach(diff, file_cb, NULL, NULL, NULL, &called)); - cl_assert_equal_i(called, 1); - - git_diff_free(diff); -} - -void test_diff_parse__parsing_minimal_patch_succeeds(void) -{ - const char patch[] = - "diff --git a/obj1 b/obj2\n" - "index 1234567..7654321 10644\n" - "--- a/obj1\n" - "+++ b/obj2\n" - "@@ -1 +1 @@\n" - "-a\n" - "+\n"; - git_buf buf = GIT_BUF_INIT; - git_diff *diff; - - cl_git_pass(git_diff_from_buffer(&diff, patch, strlen(patch))); - cl_git_pass(git_diff_to_buf(&buf, diff, GIT_DIFF_FORMAT_PATCH)); - cl_assert_equal_s(patch, buf.ptr); - - git_diff_free(diff); - git_buf_dispose(&buf); -} - -void test_diff_parse__patch_roundtrip_succeeds(void) -{ - const char buf1[] = "a\n", buf2[] = "b\n"; - git_buf patchbuf = GIT_BUF_INIT, diffbuf = GIT_BUF_INIT; - git_patch *patch; - git_diff *diff; - - cl_git_pass(git_patch_from_buffers(&patch, buf1, strlen(buf1), "obj1", buf2, strlen(buf2), "obj2", NULL)); - cl_git_pass(git_patch_to_buf(&patchbuf, patch)); - - cl_git_pass(git_diff_from_buffer(&diff, patchbuf.ptr, patchbuf.size)); - cl_git_pass(git_diff_to_buf(&diffbuf, diff, GIT_DIFF_FORMAT_PATCH)); - - cl_assert_equal_s(patchbuf.ptr, diffbuf.ptr); - - git_patch_free(patch); - git_diff_free(diff); - git_buf_dispose(&patchbuf); - git_buf_dispose(&diffbuf); -} - -#define cl_assert_equal_i_src(i1,i2,file,func,line) clar__assert_equal(file,func,line,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) - -static void cl_git_assert_lineinfo_(int old_lineno, int new_lineno, int num_lines, git_patch *patch, size_t hunk_idx, size_t line_idx, const char *file, const char *func, int lineno) -{ - const git_diff_line *line; - - cl_git_expect(git_patch_get_line_in_hunk(&line, patch, hunk_idx, line_idx), 0, file, func, lineno); - cl_assert_equal_i_src(old_lineno, line->old_lineno, file, func, lineno); - cl_assert_equal_i_src(new_lineno, line->new_lineno, file, func, lineno); - cl_assert_equal_i_src(num_lines, line->num_lines, file, func, lineno); -} - -#define cl_git_assert_lineinfo(old, new, num, p, h, l) \ - cl_git_assert_lineinfo_(old,new,num,p,h,l,__FILE__,__func__,__LINE__) - - -void test_diff_parse__issue4672(void) -{ - const char *text = "diff --git a/a b/a\n" - "index 7f129fd..af431f2 100644\n" - "--- a/a\n" - "+++ b/a\n" - "@@ -3 +3 @@\n" - "-a contents 2\n" - "+a contents\n"; - - git_diff *diff; - git_patch *patch; - const git_diff_hunk *hunk; - size_t n, l = 0; - - cl_git_pass(git_diff_from_buffer(&diff, text, strlen(text))); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_get_hunk(&hunk, &n, patch, 0)); - - cl_git_assert_lineinfo(3, -1, 1, patch, 0, l++); - cl_git_assert_lineinfo(-1, 3, 1, patch, 0, l++); - - cl_assert_equal_i(n, l); - - git_patch_free(patch); - git_diff_free(diff); -} - -void test_diff_parse__lineinfo(void) -{ - const char *text = PATCH_ORIGINAL_TO_CHANGE_MIDDLE; - git_diff *diff; - git_patch *patch; - const git_diff_hunk *hunk; - size_t n, l = 0; - - cl_git_pass(git_diff_from_buffer(&diff, text, strlen(text))); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_get_hunk(&hunk, &n, patch, 0)); - - cl_git_assert_lineinfo(3, 3, 1, patch, 0, l++); - cl_git_assert_lineinfo(4, 4, 1, patch, 0, l++); - cl_git_assert_lineinfo(5, 5, 1, patch, 0, l++); - cl_git_assert_lineinfo(6, -1, 1, patch, 0, l++); - cl_git_assert_lineinfo(-1, 6, 1, patch, 0, l++); - cl_git_assert_lineinfo(7, 7, 1, patch, 0, l++); - cl_git_assert_lineinfo(8, 8, 1, patch, 0, l++); - cl_git_assert_lineinfo(9, 9, 1, patch, 0, l++); - - cl_assert_equal_i(n, l); - - git_patch_free(patch); - git_diff_free(diff); -} - - -void test_diff_parse__new_file_with_space(void) -{ - const char *content = PATCH_ORIGINAL_NEW_FILE_WITH_SPACE; - git_patch *patch; - git_diff *diff; - - cl_git_pass(git_diff_from_buffer(&diff, content, strlen(content))); - cl_git_pass(git_patch_from_diff((git_patch **) &patch, diff, 0)); - - cl_assert_equal_p(patch->diff_opts.old_prefix, NULL); - cl_assert_equal_p(patch->delta->old_file.path, NULL); - cl_assert_equal_s(patch->diff_opts.new_prefix, "b/"); - cl_assert_equal_s(patch->delta->new_file.path, "sp ace.txt"); - - git_patch_free(patch); - git_diff_free(diff); -} - -void test_diff_parse__crlf(void) -{ - const char *text = PATCH_CRLF; - git_diff *diff; - git_patch *patch; - const git_diff_delta *delta; - - cl_git_pass(git_diff_from_buffer(&diff, text, strlen(text))); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - delta = git_patch_get_delta(patch); - - cl_assert_equal_s(delta->old_file.path, "test-file"); - cl_assert_equal_s(delta->new_file.path, "test-file"); - - git_patch_free(patch); - git_diff_free(diff); -} diff --git a/tests/diff/patch.c b/tests/diff/patch.c deleted file mode 100644 index 8945afc26..000000000 --- a/tests/diff/patch.c +++ /dev/null @@ -1,703 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/repository.h" - -#include "diff_helpers.h" -#include "diff.h" -#include "repository.h" - -static git_repository *g_repo = NULL; - -void test_diff_patch__initialize(void) -{ -} - -void test_diff_patch__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -#define EXPECTED_HEADER "diff --git a/subdir.txt b/subdir.txt\n" \ - "deleted file mode 100644\n" \ - "index e8ee89e..0000000\n" \ - "--- a/subdir.txt\n" \ - "+++ /dev/null\n" - -#define EXPECTED_HUNK "@@ -1,2 +0,0 @@\n" - -#define UTF8_HUNK_HEADER "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\n" - -#define UTF8_TRUNCATED_A_HUNK_HEADER "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\n" - -#define UTF8_TRUNCATED_L_HUNK_HEADER "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\n" - -static int check_removal_cb( - const git_diff_delta *delta, - const git_diff_hunk *hunk, - const git_diff_line *line, - void *payload) -{ - switch (line->origin) { - case GIT_DIFF_LINE_FILE_HDR: - cl_assert_equal_s(EXPECTED_HEADER, line->content); - cl_assert(hunk == NULL); - goto check_delta; - - case GIT_DIFF_LINE_HUNK_HDR: - cl_assert_equal_s(EXPECTED_HUNK, line->content); - goto check_hunk; - - case GIT_DIFF_LINE_CONTEXT: - case GIT_DIFF_LINE_DELETION: - if (payload != NULL) - return *(int *)payload; - goto check_hunk; - - default: - /* unexpected code path */ - return -1; - } - -check_hunk: - cl_assert(hunk != NULL); - cl_assert_equal_i(1, hunk->old_start); - cl_assert_equal_i(2, hunk->old_lines); - cl_assert_equal_i(0, hunk->new_start); - cl_assert_equal_i(0, hunk->new_lines); - -check_delta: - cl_assert_equal_s("subdir.txt", delta->old_file.path); - cl_assert_equal_s("subdir.txt", delta->new_file.path); - cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); - - return 0; -} - -void test_diff_patch__can_properly_display_the_removal_of_a_file(void) -{ - /* - * $ git diff 26a125e..735b6a2 - * diff --git a/subdir.txt b/subdir.txt - * deleted file mode 100644 - * index e8ee89e..0000000 - * --- a/subdir.txt - * +++ /dev/null - * @@ -1,2 +0,0 @@ - * -Is it a bird? - * -Is it a plane? - */ - - const char *one_sha = "26a125e"; - const char *another_sha = "735b6a2"; - git_tree *one, *another; - git_diff *diff; - - g_repo = cl_git_sandbox_init("status"); - - one = resolve_commit_oid_to_tree(g_repo, one_sha); - another = resolve_commit_oid_to_tree(g_repo, another_sha); - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL)); - - cl_git_pass(git_diff_print( - diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, NULL)); - - git_diff_free(diff); - - git_tree_free(another); - git_tree_free(one); -} - -void test_diff_patch__can_cancel_diff_print(void) -{ - const char *one_sha = "26a125e"; - const char *another_sha = "735b6a2"; - git_tree *one, *another; - git_diff *diff; - int fail_with; - - g_repo = cl_git_sandbox_init("status"); - - one = resolve_commit_oid_to_tree(g_repo, one_sha); - another = resolve_commit_oid_to_tree(g_repo, another_sha); - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL)); - - fail_with = -2323; - - cl_git_fail_with(git_diff_print( - diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, &fail_with), - fail_with); - - fail_with = 45; - - cl_git_fail_with(git_diff_print( - diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, &fail_with), - fail_with); - - git_diff_free(diff); - - git_tree_free(another); - git_tree_free(one); -} - -void test_diff_patch__to_string(void) -{ - const char *one_sha = "26a125e"; - const char *another_sha = "735b6a2"; - git_tree *one, *another; - git_diff *diff; - git_patch *patch; - git_buf buf = GIT_BUF_INIT; - const char *expected = "diff --git a/subdir.txt b/subdir.txt\ndeleted file mode 100644\nindex e8ee89e..0000000\n--- a/subdir.txt\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-Is it a bird?\n-Is it a plane?\n"; - - g_repo = cl_git_sandbox_init("status"); - - one = resolve_commit_oid_to_tree(g_repo, one_sha); - another = resolve_commit_oid_to_tree(g_repo, another_sha); - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL)); - - cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - - cl_git_pass(git_patch_to_buf(&buf, patch)); - - cl_assert_equal_s(expected, buf.ptr); - - cl_assert_equal_sz(31, git_patch_size(patch, 0, 0, 0)); - cl_assert_equal_sz(31, git_patch_size(patch, 1, 0, 0)); - cl_assert_equal_sz(31 + 16, git_patch_size(patch, 1, 1, 0)); - cl_assert_equal_sz(strlen(expected), git_patch_size(patch, 1, 1, 1)); - - git_buf_dispose(&buf); - git_patch_free(patch); - git_diff_free(diff); - git_tree_free(another); - git_tree_free(one); -} - -void test_diff_patch__config_options(void) -{ - const char *one_sha = "26a125e"; /* current HEAD */ - git_tree *one; - git_config *cfg; - git_diff *diff; - git_patch *patch; - git_buf buf = GIT_BUF_INIT; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - char *onefile = "staged_changes_modified_file"; - const char *expected1 = "diff --git c/staged_changes_modified_file i/staged_changes_modified_file\nindex 70bd944..906ee77 100644\n--- c/staged_changes_modified_file\n+++ i/staged_changes_modified_file\n@@ -1 +1,2 @@\n staged_changes_modified_file\n+staged_changes_modified_file\n"; - const char *expected2 = "diff --git i/staged_changes_modified_file w/staged_changes_modified_file\nindex 906ee77..011c344 100644\n--- i/staged_changes_modified_file\n+++ w/staged_changes_modified_file\n@@ -1,2 +1,3 @@\n staged_changes_modified_file\n staged_changes_modified_file\n+staged_changes_modified_file\n"; - const char *expected3 = "diff --git staged_changes_modified_file staged_changes_modified_file\nindex 906ee77..011c344 100644\n--- staged_changes_modified_file\n+++ staged_changes_modified_file\n@@ -1,2 +1,3 @@\n staged_changes_modified_file\n staged_changes_modified_file\n+staged_changes_modified_file\n"; - const char *expected4 = "diff --git staged_changes_modified_file staged_changes_modified_file\nindex 70bd9443ada0..906ee7711f4f 100644\n--- staged_changes_modified_file\n+++ staged_changes_modified_file\n@@ -1 +1,2 @@\n staged_changes_modified_file\n+staged_changes_modified_file\n"; - - g_repo = cl_git_sandbox_init("status"); - cl_git_pass(git_repository_config(&cfg, g_repo)); - one = resolve_commit_oid_to_tree(g_repo, one_sha); - opts.pathspec.count = 1; - opts.pathspec.strings = &onefile; - - - cl_git_pass(git_config_set_string(cfg, "diff.mnemonicprefix", "true")); - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, one, NULL, &opts)); - - cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&buf, patch)); - cl_assert_equal_s(expected1, buf.ptr); - - git_buf_dispose(&buf); - git_patch_free(patch); - git_diff_free(diff); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&buf, patch)); - cl_assert_equal_s(expected2, buf.ptr); - - git_buf_dispose(&buf); - git_patch_free(patch); - git_diff_free(diff); - - - cl_git_pass(git_config_set_string(cfg, "diff.noprefix", "true")); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&buf, patch)); - cl_assert_equal_s(expected3, buf.ptr); - - git_buf_dispose(&buf); - git_patch_free(patch); - git_diff_free(diff); - - - cl_git_pass(git_config_set_int32(cfg, "core.abbrev", 12)); - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, one, NULL, &opts)); - - cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&buf, patch)); - cl_assert_equal_s(expected4, buf.ptr); - - git_buf_dispose(&buf); - git_patch_free(patch); - git_diff_free(diff); - - git_buf_dispose(&buf); - git_tree_free(one); - git_config_free(cfg); -} - -void test_diff_patch__hunks_have_correct_line_numbers(void) -{ - git_config *cfg; - git_tree *head; - git_diff_options opt = GIT_DIFF_OPTIONS_INIT; - git_diff *diff; - git_patch *patch; - const git_diff_delta *delta; - const git_diff_hunk *hunk; - const git_diff_line *line; - size_t hunklen; - git_str old_content = GIT_STR_INIT, actual = GIT_STR_INIT; - const char *new_content = "The Song of Seven Cities\n------------------------\n\nI WAS Lord of Cities very sumptuously builded.\nSeven roaring Cities paid me tribute from afar.\nIvory their outposts were--the guardrooms of them gilded,\nAnd garrisoned with Amazons invincible in war.\n\nThis is some new text;\nNot as good as the old text;\nBut here it is.\n\nSo they warred and trafficked only yesterday, my Cities.\nTo-day there is no mark or mound of where my Cities stood.\nFor the River rose at midnight and it washed away my Cities.\nThey are evened with Atlantis and the towns before the Flood.\n\nRain on rain-gorged channels raised the water-levels round them,\nFreshet backed on freshet swelled and swept their world from sight,\nTill the emboldened floods linked arms and, flashing forward, drowned them--\nDrowned my Seven Cities and their peoples in one night!\n\nLow among the alders lie their derelict foundations,\nThe beams wherein they trusted and the plinths whereon they built--\nMy rulers and their treasure and their unborn populations,\nDead, destroyed, aborted, and defiled with mud and silt!\n\nAnother replacement;\nBreaking up the poem;\nGenerating some hunks.\n\nTo the sound of trumpets shall their seed restore my Cities\nWealthy and well-weaponed, that once more may I behold\nAll the world go softly when it walks before my Cities,\nAnd the horses and the chariots fleeing from them as of old!\n\n -- Rudyard Kipling\n"; - - g_repo = cl_git_sandbox_init("renames"); - - cl_git_pass(git_config_new(&cfg)); - git_repository_set_config(g_repo, cfg); - git_config_free(cfg); - - git_repository_reinit_filesystem(g_repo, false); - - cl_git_pass( - git_futils_readbuffer(&old_content, "renames/songof7cities.txt")); - - cl_git_rewritefile("renames/songof7cities.txt", new_content); - - cl_git_pass(git_repository_head_tree(&head, g_repo)); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, head, &opt)); - - cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_assert((delta = git_patch_get_delta(patch)) != NULL); - - cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); - cl_assert_equal_i(2, (int)git_patch_num_hunks(patch)); - - /* check hunk 0 */ - - cl_git_pass( - git_patch_get_hunk(&hunk, &hunklen, patch, 0)); - - cl_assert_equal_i(18, (int)hunklen); - - cl_assert_equal_i(6, (int)hunk->old_start); - cl_assert_equal_i(15, (int)hunk->old_lines); - cl_assert_equal_i(6, (int)hunk->new_start); - cl_assert_equal_i(9, (int)hunk->new_lines); - - cl_assert_equal_i(18, (int)git_patch_num_lines_in_hunk(patch, 0)); - - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 0)); - cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin); - cl_git_pass(git_str_set(&actual, line->content, line->content_len)); - cl_assert_equal_s("Ivory their outposts were--the guardrooms of them gilded,\n", actual.ptr); - cl_assert_equal_i(6, line->old_lineno); - cl_assert_equal_i(6, line->new_lineno); - cl_assert_equal_i(-1, line->content_offset); - - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 3)); - cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin); - cl_git_pass(git_str_set(&actual, line->content, line->content_len)); - cl_assert_equal_s("All the world went softly when it walked before my Cities--\n", actual.ptr); - cl_assert_equal_i(9, line->old_lineno); - cl_assert_equal_i(-1, line->new_lineno); - cl_assert_equal_i(252, line->content_offset); - - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 12)); - cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin); - cl_git_pass(git_str_set(&actual, line->content, line->content_len)); - cl_assert_equal_s("This is some new text;\n", actual.ptr); - cl_assert_equal_i(-1, line->old_lineno); - cl_assert_equal_i(9, line->new_lineno); - cl_assert_equal_i(252, line->content_offset); - - /* check hunk 1 */ - - cl_git_pass(git_patch_get_hunk(&hunk, &hunklen, patch, 1)); - - cl_assert_equal_i(18, (int)hunklen); - - cl_assert_equal_i(31, (int)hunk->old_start); - cl_assert_equal_i(15, (int)hunk->old_lines); - cl_assert_equal_i(25, (int)hunk->new_start); - cl_assert_equal_i(9, (int)hunk->new_lines); - - cl_assert_equal_i(18, (int)git_patch_num_lines_in_hunk(patch, 1)); - - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 0)); - cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin); - cl_git_pass(git_str_set(&actual, line->content, line->content_len)); - cl_assert_equal_s("My rulers and their treasure and their unborn populations,\n", actual.ptr); - cl_assert_equal_i(31, line->old_lineno); - cl_assert_equal_i(25, line->new_lineno); - cl_assert_equal_i(-1, line->content_offset); - - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 3)); - cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin); - cl_git_pass(git_str_set(&actual, line->content, line->content_len)); - cl_assert_equal_s("The Daughters of the Palace whom they cherished in my Cities,\n", actual.ptr); - cl_assert_equal_i(34, line->old_lineno); - cl_assert_equal_i(-1, line->new_lineno); - cl_assert_equal_i(1468, line->content_offset); - - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 12)); - cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin); - cl_git_pass(git_str_set(&actual, line->content, line->content_len)); - cl_assert_equal_s("Another replacement;\n", actual.ptr); - cl_assert_equal_i(-1, line->old_lineno); - cl_assert_equal_i(28, line->new_lineno); - cl_assert_equal_i(1066, line->content_offset); - - git_patch_free(patch); - git_diff_free(diff); - - /* Let's check line numbers when there is no newline */ - - git_str_rtrim(&old_content); - cl_git_rewritefile("renames/songof7cities.txt", old_content.ptr); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, head, &opt)); - - cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_assert((delta = git_patch_get_delta(patch)) != NULL); - - cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); - cl_assert_equal_i(1, (int)git_patch_num_hunks(patch)); - - /* check hunk 0 */ - - cl_git_pass(git_patch_get_hunk(&hunk, &hunklen, patch, 0)); - - cl_assert_equal_i(6, (int)hunklen); - - cl_assert_equal_i(46, (int)hunk->old_start); - cl_assert_equal_i(4, (int)hunk->old_lines); - cl_assert_equal_i(46, (int)hunk->new_start); - cl_assert_equal_i(4, (int)hunk->new_lines); - - cl_assert_equal_i(6, (int)git_patch_num_lines_in_hunk(patch, 0)); - - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 1)); - cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin); - cl_git_pass(git_str_set(&actual, line->content, line->content_len)); - cl_assert_equal_s("And the horses and the chariots fleeing from them as of old!\n", actual.ptr); - cl_assert_equal_i(47, line->old_lineno); - cl_assert_equal_i(47, line->new_lineno); - - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 2)); - cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin); - cl_git_pass(git_str_set(&actual, line->content, line->content_len)); - cl_assert_equal_s("\n", actual.ptr); - cl_assert_equal_i(48, line->old_lineno); - cl_assert_equal_i(48, line->new_lineno); - - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 3)); - cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin); - cl_git_pass(git_str_set(&actual, line->content, line->content_len)); - cl_assert_equal_s(" -- Rudyard Kipling\n", actual.ptr); - cl_assert_equal_i(49, line->old_lineno); - cl_assert_equal_i(-1, line->new_lineno); - - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 4)); - cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin); - cl_git_pass(git_str_set(&actual, line->content, line->content_len)); - cl_assert_equal_s(" -- Rudyard Kipling", actual.ptr); - cl_assert_equal_i(-1, line->old_lineno); - cl_assert_equal_i(49, line->new_lineno); - - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 5)); - cl_assert_equal_i(GIT_DIFF_LINE_DEL_EOFNL, (int)line->origin); - cl_git_pass(git_str_set(&actual, line->content, line->content_len)); - cl_assert_equal_s("\n\\ No newline at end of file\n", actual.ptr); - cl_assert_equal_i(-1, line->old_lineno); - cl_assert_equal_i(49, line->new_lineno); - - git_patch_free(patch); - git_diff_free(diff); - - git_str_dispose(&actual); - git_str_dispose(&old_content); - git_tree_free(head); -} - -static void check_single_patch_stats( - git_repository *repo, size_t hunks, - size_t adds, size_t dels, size_t ctxt, size_t *sizes, - const char *expected) -{ - git_diff *diff; - git_patch *patch; - const git_diff_delta *delta; - size_t actual_ctxt, actual_adds, actual_dels; - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL)); - - cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); - - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_assert((delta = git_patch_get_delta(patch)) != NULL); - cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); - - cl_assert_equal_i((int)hunks, (int)git_patch_num_hunks(patch)); - - cl_git_pass( git_patch_line_stats( - &actual_ctxt, &actual_adds, &actual_dels, patch) ); - - cl_assert_equal_sz(ctxt, actual_ctxt); - cl_assert_equal_sz(adds, actual_adds); - cl_assert_equal_sz(dels, actual_dels); - - if (expected != NULL) { - git_buf buf = GIT_BUF_INIT; - cl_git_pass(git_patch_to_buf(&buf, patch)); - cl_assert_equal_s(expected, buf.ptr); - git_buf_dispose(&buf); - - cl_assert_equal_sz( - strlen(expected), git_patch_size(patch, 1, 1, 1)); - } - - if (sizes) { - if (sizes[0]) - cl_assert_equal_sz(sizes[0], git_patch_size(patch, 0, 0, 0)); - if (sizes[1]) - cl_assert_equal_sz(sizes[1], git_patch_size(patch, 1, 0, 0)); - if (sizes[2]) - cl_assert_equal_sz(sizes[2], git_patch_size(patch, 1, 1, 0)); - } - - /* walk lines in hunk with basic sanity checks */ - for (; hunks > 0; --hunks) { - size_t i, max_i; - const git_diff_line *line; - int last_new_lineno = -1, last_old_lineno = -1; - - max_i = git_patch_num_lines_in_hunk(patch, hunks - 1); - - for (i = 0; i < max_i; ++i) { - int expected = 1; - - cl_git_pass( - git_patch_get_line_in_hunk(&line, patch, hunks - 1, i)); - - if (line->origin == GIT_DIFF_LINE_ADD_EOFNL || - line->origin == GIT_DIFF_LINE_DEL_EOFNL || - line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) - expected = 0; - - if (line->old_lineno >= 0) { - if (last_old_lineno >= 0) - cl_assert_equal_i( - expected, line->old_lineno - last_old_lineno); - last_old_lineno = line->old_lineno; - } - - if (line->new_lineno >= 0) { - if (last_new_lineno >= 0) - cl_assert_equal_i( - expected, line->new_lineno - last_new_lineno); - last_new_lineno = line->new_lineno; - } - } - } - - git_patch_free(patch); - git_diff_free(diff); -} - -void test_diff_patch__line_counts_with_eofnl(void) -{ - git_config *cfg; - git_str content = GIT_STR_INIT; - const char *end; - git_index *index; - const char *expected = - /* below is pasted output of 'git diff' with fn context removed */ - "diff --git a/songof7cities.txt b/songof7cities.txt\n" - "index 378a7d9..3d0154e 100644\n" - "--- a/songof7cities.txt\n" - "+++ b/songof7cities.txt\n" - "@@ -42,7 +42,7 @@ With peoples undefeated of the dark, enduring blood.\n" - " \n" - " To the sound of trumpets shall their seed restore my Cities\n" - " Wealthy and well-weaponed, that once more may I behold\n" - "-All the world go softly when it walks before my Cities,\n" - "+#All the world go softly when it walks before my Cities,\n" - " And the horses and the chariots fleeing from them as of old!\n" - " \n" - " -- Rudyard Kipling\n" - "\\ No newline at end of file\n"; - size_t expected_sizes[3] = { 115, 119 + 115 + 114, 119 + 115 + 114 + 71 }; - - g_repo = cl_git_sandbox_init("renames"); - - cl_git_pass(git_config_new(&cfg)); - git_repository_set_config(g_repo, cfg); - git_config_free(cfg); - - git_repository_reinit_filesystem(g_repo, false); - - cl_git_pass(git_futils_readbuffer(&content, "renames/songof7cities.txt")); - - /* remove first line */ - - end = git_str_cstr(&content) + git_str_find(&content, '\n') + 1; - git_str_consume(&content, end); - cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - - check_single_patch_stats(g_repo, 1, 0, 1, 3, NULL, NULL); - - /* remove trailing whitespace */ - - git_str_rtrim(&content); - cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - - check_single_patch_stats(g_repo, 2, 1, 2, 6, NULL, NULL); - - /* add trailing whitespace */ - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_str_putc(&content, '\n')); - cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - - check_single_patch_stats(g_repo, 1, 1, 1, 3, NULL, NULL); - - /* no trailing whitespace as context line */ - - { - /* walk back a couple lines, make space and insert char */ - char *scan = content.ptr + content.size; - int i; - - for (i = 0; i < 5; ++i) { - for (--scan; scan > content.ptr && *scan != '\n'; --scan) - /* seek to prev \n */; - } - cl_assert(scan > content.ptr); - - /* overwrite trailing \n with right-shifted content */ - memmove(scan + 1, scan, content.size - (scan - content.ptr) - 1); - /* insert '#' char into space we created */ - scan[1] = '#'; - } - cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - - check_single_patch_stats( - g_repo, 1, 1, 1, 6, expected_sizes, expected); - - git_str_dispose(&content); -} - -void test_diff_patch__can_strip_bad_utf8(void) -{ - const char *a = "A " UTF8_HUNK_HEADER - " B\n" - " C\n" - " D\n" - " E\n" - " F\n" - " G\n" - " H\n" - " I\n" - " J\n" - " K\n" - "L " UTF8_HUNK_HEADER - " M\n" - " N\n" - " O\n" - " P\n" - " Q\n" - " R\n" - " S\n" - " T\n" - " U\n" - " V\n"; - - const char *b = "A " UTF8_HUNK_HEADER - " B\n" - " C\n" - " D\n" - " E modified\n" - " F\n" - " G\n" - " H\n" - " I\n" - " J\n" - " K\n" - "L " UTF8_HUNK_HEADER - " M\n" - " N\n" - " O\n" - " P modified\n" - " Q\n" - " R\n" - " S\n" - " T\n" - " U\n" - " V\n"; - - const char *expected = "diff --git a/file b/file\n" - "index d0647c4..7827ce5 100644\n" - "--- a/file\n" - "+++ b/file\n" - "@@ -2,7 +2,7 @@ A " UTF8_TRUNCATED_A_HUNK_HEADER - " B\n" - " C\n" - " D\n" - "- E\n" - "+ E modified\n" - " F\n" - " G\n" - " H\n" - "@@ -13,7 +13,7 @@ L " UTF8_TRUNCATED_L_HUNK_HEADER - " M\n" - " N\n" - " O\n" - "- P\n" - "+ P modified\n" - " Q\n" - " R\n" - " S\n"; - - git_diff_options opts; - git_patch *patch; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION)); - - cl_git_pass(git_patch_from_buffers(&patch, a, strlen(a), NULL, b, strlen(b), NULL, &opts)); - cl_git_pass(git_patch_to_buf(&buf, patch)); - - cl_assert_equal_s(expected, buf.ptr); - - git_patch_free(patch); - git_buf_dispose(&buf); -} diff --git a/tests/diff/patchid.c b/tests/diff/patchid.c deleted file mode 100644 index 621a720f7..000000000 --- a/tests/diff/patchid.c +++ /dev/null @@ -1,93 +0,0 @@ -#include "clar_libgit2.h" -#include "patch/patch_common.h" - -static void verify_patch_id(const char *diff_content, const char *expected_id) -{ - git_oid expected_oid, actual_oid; - git_diff *diff; - - cl_git_pass(git_oid_fromstr(&expected_oid, expected_id)); - cl_git_pass(git_diff_from_buffer(&diff, diff_content, strlen(diff_content))); - cl_git_pass(git_diff_patchid(&actual_oid, diff, NULL)); - - cl_assert_equal_oid(&expected_oid, &actual_oid); - - git_diff_free(diff); -} - -void test_diff_patchid__simple_commit(void) -{ - verify_patch_id(PATCH_SIMPLE_COMMIT, "06094b1948b878b7d9ff7560b4eae672a014b0ec"); -} - -void test_diff_patchid__deleted_file(void) -{ - verify_patch_id(PATCH_DELETE_ORIGINAL, "d18507fe189f49c028b32c8c34e1ad98dd6a1aad"); - verify_patch_id(PATCH_DELETED_FILE_2_HUNKS, "f31412498a17e6c3fbc635f2c5f9aa3ef4c1a9b7"); -} - -void test_diff_patchid__created_file(void) -{ - verify_patch_id(PATCH_ADD_ORIGINAL, "a7d39379308021465ae2ce65e338c048a3110db6"); -} - -void test_diff_patchid__binary_file(void) -{ - verify_patch_id(PATCH_ADD_BINARY_NOT_PRINTED, "2b31236b485faa30cf4dd33e4d6539829996739f"); -} - -void test_diff_patchid__renamed_file(void) -{ - verify_patch_id(PATCH_RENAME_EXACT, "4666d50cea4976f6f727448046d43461912058fd"); - verify_patch_id(PATCH_RENAME_SIMILAR, "a795087575fcb940227be524488bedd6b3d3f438"); -} - -void test_diff_patchid__modechange(void) -{ - verify_patch_id(PATCH_MODECHANGE_UNCHANGED, "dbf3423ee98375ef1c72a79fbd29a049a2bae771"); - verify_patch_id(PATCH_MODECHANGE_MODIFIED, "93aba696e1bbd2bbb73e3e3e62ed71f232137657"); -} - -void test_diff_patchid__shuffle_hunks(void) -{ - verify_patch_id(PATCH_DELETED_FILE_2_HUNKS_SHUFFLED, "f31412498a17e6c3fbc635f2c5f9aa3ef4c1a9b7"); -} - -void test_diff_patchid__filename_with_spaces(void) -{ - verify_patch_id(PATCH_APPEND_NO_NL, "f0ba05413beaef743b630e796153839462ee477a"); -} - -void test_diff_patchid__multiple_hunks(void) -{ - verify_patch_id(PATCH_MULTIPLE_HUNKS, "81e26c34643d17f521e57c483a6a637e18ba1f57"); -} - -void test_diff_patchid__multiple_files(void) -{ - verify_patch_id(PATCH_MULTIPLE_FILES, "192d1f49d23f2004517963aecd3f8a6c467f50ff"); -} - -void test_diff_patchid__same_diff_with_differing_whitespace_has_same_id(void) -{ - const char *tabs = - "diff --git a/file.txt b/file.txt\n" - "index 8fecc09..1d43a92 100644\n" - "--- a/file.txt\n" - "+++ b/file.txt\n" - "@@ -1 +1 @@\n" - "-old text\n" - "+ new text\n"; - const char *spaces = - "diff --git a/file.txt b/file.txt\n" - "index 8fecc09..1d43a92 100644\n" - "--- a/file.txt\n" - "+++ b/file.txt\n" - "@@ -1 +1 @@\n" - "-old text\n" - "+ new text\n"; - const char *id = "11efdd13c30f7a1056eac2ae2fb952da475e2c23"; - - verify_patch_id(tabs, id); - verify_patch_id(spaces, id); -} diff --git a/tests/diff/pathspec.c b/tests/diff/pathspec.c deleted file mode 100644 index 5761d2d2b..000000000 --- a/tests/diff/pathspec.c +++ /dev/null @@ -1,93 +0,0 @@ -#include "clar_libgit2.h" -#include "diff_helpers.h" - -static git_repository *g_repo = NULL; - -void test_diff_pathspec__initialize(void) -{ - g_repo = cl_git_sandbox_init("status"); -} - -void test_diff_pathspec__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_diff_pathspec__0(void) -{ - const char *a_commit = "26a125ee"; /* the current HEAD */ - const char *b_commit = "0017bd4a"; /* the start */ - git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); - git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_strarray paths = { NULL, 1 }; - char *path; - git_pathspec *ps; - git_pathspec_match_list *matches; - - cl_assert(a); - cl_assert(b); - - path = "*_file"; - paths.strings = &path; - cl_git_pass(git_pathspec_new(&ps, &paths)); - - cl_git_pass(git_pathspec_match_tree(&matches, a, GIT_PATHSPEC_DEFAULT, ps)); - cl_assert_equal_i(7, (int)git_pathspec_match_list_entrycount(matches)); - cl_assert_equal_s("current_file", git_pathspec_match_list_entry(matches,0)); - cl_assert(git_pathspec_match_list_diff_entry(matches,0) == NULL); - git_pathspec_match_list_free(matches); - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, NULL, a, &opts)); - - cl_git_pass(git_pathspec_match_diff( - &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); - cl_assert_equal_i(7, (int)git_pathspec_match_list_entrycount(matches)); - cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); - cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); - cl_assert_equal_s("current_file", - git_pathspec_match_list_diff_entry(matches,0)->new_file.path); - cl_assert_equal_i(GIT_DELTA_ADDED, - (int)git_pathspec_match_list_diff_entry(matches,0)->status); - git_pathspec_match_list_free(matches); - - git_diff_free(diff); - diff = NULL; - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); - - cl_git_pass(git_pathspec_match_diff( - &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); - cl_assert_equal_i(3, (int)git_pathspec_match_list_entrycount(matches)); - cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); - cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); - cl_assert_equal_s("subdir/current_file", - git_pathspec_match_list_diff_entry(matches,0)->new_file.path); - cl_assert_equal_i(GIT_DELTA_DELETED, - (int)git_pathspec_match_list_diff_entry(matches,0)->status); - git_pathspec_match_list_free(matches); - - git_diff_free(diff); - diff = NULL; - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); - - cl_git_pass(git_pathspec_match_diff( - &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); - cl_assert_equal_i(4, (int)git_pathspec_match_list_entrycount(matches)); - cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); - cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); - cl_assert_equal_s("modified_file", - git_pathspec_match_list_diff_entry(matches,0)->new_file.path); - cl_assert_equal_i(GIT_DELTA_MODIFIED, - (int)git_pathspec_match_list_diff_entry(matches,0)->status); - git_pathspec_match_list_free(matches); - - git_diff_free(diff); - diff = NULL; - - git_tree_free(a); - git_tree_free(b); - git_pathspec_free(ps); -} diff --git a/tests/diff/racediffiter.c b/tests/diff/racediffiter.c deleted file mode 100644 index d364d6b21..000000000 --- a/tests/diff/racediffiter.c +++ /dev/null @@ -1,129 +0,0 @@ -/* This test exercises the problem described in -** https://github.com/libgit2/libgit2/pull/3568 -** where deleting a directory during a diff/status -** operation can cause an access violation. -** -** The "test_diff_racediffiter__basic() test confirms -** the normal operation of diff on the given repo. -** -** The "test_diff_racediffiter__racy_rmdir() test -** uses the new diff progress callback to delete -** a directory (after the initial readdir() and -** before the directory itself is visited) causing -** the recursion and iteration to fail. -*/ - -#include "clar_libgit2.h" -#include "diff_helpers.h" - -#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) - -void test_diff_racediffiter__initialize(void) -{ -} - -void test_diff_racediffiter__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -typedef struct -{ - const char *path; - git_delta_t t; - -} basic_payload; - -static int notify_cb__basic( - const git_diff *diff_so_far, - const git_diff_delta *delta_to_add, - const char *matched_pathspec, - void *payload) -{ - basic_payload *exp = (basic_payload *)payload; - basic_payload *e; - - GIT_UNUSED(diff_so_far); - GIT_UNUSED(matched_pathspec); - - for (e = exp; e->path; e++) { - if (strcmp(e->path, delta_to_add->new_file.path) == 0) { - cl_assert_equal_i(e->t, delta_to_add->status); - return 0; - } - } - cl_assert(0); - return GIT_ENOTFOUND; -} - -void test_diff_racediffiter__basic(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_repository *repo = cl_git_sandbox_init("diff"); - git_diff *diff; - - basic_payload exp_a[] = { - { "another.txt", GIT_DELTA_MODIFIED }, - { "readme.txt", GIT_DELTA_MODIFIED }, - { "zzzzz/", GIT_DELTA_IGNORED }, - { NULL, 0 } - }; - - cl_must_pass(p_mkdir("diff/zzzzz", 0777)); - - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS; - opts.notify_cb = notify_cb__basic; - opts.payload = exp_a; - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); - - git_diff_free(diff); -} - - -typedef struct { - bool first_time; - const char *dir; - basic_payload *basic_payload; -} racy_payload; - -static int notify_cb__racy_rmdir( - const git_diff *diff_so_far, - const git_diff_delta *delta_to_add, - const char *matched_pathspec, - void *payload) -{ - racy_payload *pay = (racy_payload *)payload; - - if (pay->first_time) { - cl_must_pass(p_rmdir(pay->dir)); - pay->first_time = false; - } - - return notify_cb__basic(diff_so_far, delta_to_add, matched_pathspec, pay->basic_payload); -} - -void test_diff_racediffiter__racy(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_repository *repo = cl_git_sandbox_init("diff"); - git_diff *diff; - - basic_payload exp_a[] = { - { "another.txt", GIT_DELTA_MODIFIED }, - { "readme.txt", GIT_DELTA_MODIFIED }, - { NULL, 0 } - }; - - racy_payload pay = { true, "diff/zzzzz", exp_a }; - - cl_must_pass(p_mkdir("diff/zzzzz", 0777)); - - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS; - opts.notify_cb = notify_cb__racy_rmdir; - opts.payload = &pay; - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); - - git_diff_free(diff); -} diff --git a/tests/diff/rename.c b/tests/diff/rename.c deleted file mode 100644 index 30e9ea432..000000000 --- a/tests/diff/rename.c +++ /dev/null @@ -1,2034 +0,0 @@ -#include "clar_libgit2.h" -#include "diff_helpers.h" - -static git_repository *g_repo = NULL; - -void test_diff_rename__initialize(void) -{ - g_repo = cl_git_sandbox_init("renames"); - - cl_repo_set_bool(g_repo, "core.autocrlf", false); -} - -void test_diff_rename__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -#define INITIAL_COMMIT "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2" -#define COPY_RENAME_COMMIT "2bc7f351d20b53f1c72c16c4b036e491c478c49a" -#define REWRITE_COPY_COMMIT "1c068dee5790ef1580cfc4cd670915b48d790084" -#define RENAME_MODIFICATION_COMMIT "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13" -#define REWRITE_DELETE_COMMIT "84d8efa38af7ace2b302de0adbda16b1f1cd2e1b" -#define DELETE_RENAME_COMMIT "be053a189b0bbde545e0a3f59ce00b46ad29ce0d" -#define BREAK_REWRITE_BASE_COMMIT "db98035f715427eef1f5e17f03e1801c05301e9e" -#define BREAK_REWRITE_COMMIT "7e7bfb88ba9bc65fd700fee1819cf1c317aafa56" - -/* - * Renames repo has: - * - * commit 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 - - * serving.txt (25 lines) - * sevencities.txt (50 lines) - * commit 2bc7f351d20b53f1c72c16c4b036e491c478c49a - - * serving.txt -> sixserving.txt (rename, no change, 100% match) - * sevencities.txt -> sevencities.txt (no change) - * sevencities.txt -> songofseven.txt (copy, no change, 100% match) - * commit 1c068dee5790ef1580cfc4cd670915b48d790084 - * songofseven.txt -> songofseven.txt (major rewrite, <20% match - split) - * sixserving.txt -> sixserving.txt (indentation change) - * sixserving.txt -> ikeepsix.txt (copy, add title, >80% match) - * sevencities.txt (no change) - * commit 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 - * songofseven.txt -> untimely.txt (rename, convert to crlf) - * ikeepsix.txt -> ikeepsix.txt (reorder sections in file) - * sixserving.txt -> sixserving.txt (whitespace change - not just indent) - * sevencities.txt -> songof7cities.txt (rename, small text changes) - * commit 84d8efa38af7ace2b302de0adbda16b1f1cd2e1b - * songof7cities.txt -> songof7citie.txt (major rewrite, <20% match) - * ikeepsix.txt -> (deleted) - * untimely.txt (no change) - * sixserving.txt (no change) - * commit be053a189b0bbde545e0a3f59ce00b46ad29ce0d - * ikeepsix.txt -> (deleted) - * songof7cities.txt -> ikeepsix.txt (rename, 100% match) - * untimely.txt (no change) - * sixserving.txt (no change) - */ - -void test_diff_rename__match_oid(void) -{ - const char *old_sha = INITIAL_COMMIT; - const char *new_sha = COPY_RENAME_COMMIT; - git_tree *old_tree, *new_tree; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - diff_expects exp; - - old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); - new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); - - /* Must pass GIT_DIFF_INCLUDE_UNMODIFIED if you expect to emulate - * --find-copies-harder during rename transformion... - */ - diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, old_tree, new_tree, &diffopts)); - - /* git diff --no-renames \ - * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ - * 2bc7f351d20b53f1c72c16c4b036e491c478c49a - */ - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - - /* git diff 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ - * 2bc7f351d20b53f1c72c16c4b036e491c478c49a - * don't use NULL opts to avoid config `diff.renames` contamination - */ - opts.flags = GIT_DIFF_FIND_RENAMES; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - - git_diff_free(diff); - - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, old_tree, new_tree, &diffopts)); - - /* git diff --find-copies-harder \ - * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ - * 2bc7f351d20b53f1c72c16c4b036e491c478c49a - */ - opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - - git_diff_free(diff); - - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, old_tree, new_tree, &diffopts)); - - /* git diff --find-copies-harder -M100 -B100 \ - * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ - * 2bc7f351d20b53f1c72c16c4b036e491c478c49a - */ - opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED | - GIT_DIFF_FIND_EXACT_MATCH_ONLY; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - - git_diff_free(diff); - - git_tree_free(old_tree); - git_tree_free(new_tree); -} - -void test_diff_rename__checks_options_version(void) -{ - const char *old_sha = INITIAL_COMMIT; - const char *new_sha = COPY_RENAME_COMMIT; - git_tree *old_tree, *new_tree; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - const git_error *err; - - old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); - new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); - diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, old_tree, new_tree, &diffopts)); - - opts.version = 0; - cl_git_fail(git_diff_find_similar(diff, &opts)); - err = git_error_last(); - cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); - - git_error_clear(); - opts.version = 1024; - cl_git_fail(git_diff_find_similar(diff, &opts)); - err = git_error_last(); - cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); - - git_diff_free(diff); - git_tree_free(old_tree); - git_tree_free(new_tree); -} - -void test_diff_rename__not_exact_match(void) -{ - const char *sha0 = COPY_RENAME_COMMIT; - const char *sha1 = REWRITE_COPY_COMMIT; - const char *sha2 = RENAME_MODIFICATION_COMMIT; - git_tree *old_tree, *new_tree; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - diff_expects exp; - - /* == Changes ===================================================== - * songofseven.txt -> songofseven.txt (major rewrite, <20% match - split) - * sixserving.txt -> sixserving.txt (indentation change) - * sixserving.txt -> ikeepsix.txt (copy, add title, >80% match) - * sevencities.txt (no change) - */ - - old_tree = resolve_commit_oid_to_tree(g_repo, sha0); - new_tree = resolve_commit_oid_to_tree(g_repo, sha1); - - /* Must pass GIT_DIFF_INCLUDE_UNMODIFIED if you expect to emulate - * --find-copies-harder during rename transformion... - */ - diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, old_tree, new_tree, &diffopts)); - - /* git diff --no-renames \ - * 2bc7f351d20b53f1c72c16c4b036e491c478c49a \ - * 1c068dee5790ef1580cfc4cd670915b48d790084 - */ - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); - - /* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a \ - * 1c068dee5790ef1580cfc4cd670915b48d790084 - * - * must not pass NULL for opts because it will pick up environment - * values for "diff.renames" and test won't be consistent. - */ - opts.flags = GIT_DIFF_FIND_RENAMES; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); - - git_diff_free(diff); - - /* git diff -M -C \ - * 2bc7f351d20b53f1c72c16c4b036e491c478c49a \ - * 1c068dee5790ef1580cfc4cd670915b48d790084 - */ - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, old_tree, new_tree, &diffopts)); - - opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); - - git_diff_free(diff); - - /* git diff -M -C --find-copies-harder --break-rewrites \ - * 2bc7f351d20b53f1c72c16c4b036e491c478c49a \ - * 1c068dee5790ef1580cfc4cd670915b48d790084 - */ - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, old_tree, new_tree, &diffopts)); - - opts.flags = GIT_DIFF_FIND_ALL; - opts.break_rewrite_threshold = 70; - - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(5, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); - - git_diff_free(diff); - - /* == Changes ===================================================== - * songofseven.txt -> untimely.txt (rename, convert to crlf) - * ikeepsix.txt -> ikeepsix.txt (reorder sections in file) - * sixserving.txt -> sixserving.txt (whitespace - not just indent) - * sevencities.txt -> songof7cities.txt (rename, small text changes) - */ - - git_tree_free(old_tree); - old_tree = new_tree; - new_tree = resolve_commit_oid_to_tree(g_repo, sha2); - - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, old_tree, new_tree, &diffopts)); - - /* git diff --no-renames \ - * 1c068dee5790ef1580cfc4cd670915b48d790084 \ - * 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 - */ - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(6, exp.files); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); - git_diff_free(diff); - - /* git diff -M -C \ - * 1c068dee5790ef1580cfc4cd670915b48d790084 \ - * 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 - */ - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, old_tree, new_tree, &diffopts)); - - opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); - - git_diff_free(diff); - - /* git diff -M -C --find-copies-harder --break-rewrites \ - * 1c068dee5790ef1580cfc4cd670915b48d790084 \ - * 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 - * with libgit2 default similarity comparison... - */ - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, old_tree, new_tree, &diffopts)); - - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - /* the default match algorithm is going to find the internal - * whitespace differences in the lines of sixserving.txt to be - * significant enough that this will decide to split it into an ADD - * and a DELETE - */ - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(5, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); - - git_diff_free(diff); - - /* git diff -M -C --find-copies-harder --break-rewrites \ - * 1c068dee5790ef1580cfc4cd670915b48d790084 \ - * 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 - * with ignore_space whitespace comparison - */ - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, old_tree, new_tree, &diffopts)); - - opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_IGNORE_WHITESPACE; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - /* Ignoring whitespace, this should no longer split sixserver.txt */ - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); - - git_diff_free(diff); - - git_tree_free(old_tree); - git_tree_free(new_tree); -} - -void test_diff_rename__test_small_files(void) -{ - git_index *index; - git_reference *head_reference; - git_commit *head_commit; - git_tree *head_tree; - git_tree *commit_tree; - git_signature *signature; - git_diff *diff; - git_oid oid; - const git_diff_delta *delta; - git_diff_options diff_options = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options find_options = GIT_DIFF_FIND_OPTIONS_INIT; - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_mkfile("renames/small.txt", "Hello World!\n"); - cl_git_pass(git_index_add_bypath(index, "small.txt")); - - cl_git_pass(git_repository_head(&head_reference, g_repo)); - cl_git_pass(git_reference_peel((git_object**)&head_commit, head_reference, GIT_OBJECT_COMMIT)); - cl_git_pass(git_commit_tree(&head_tree, head_commit)); - cl_git_pass(git_index_write_tree(&oid, index)); - cl_git_pass(git_tree_lookup(&commit_tree, g_repo, &oid)); - cl_git_pass(git_signature_new(&signature, "Rename", "rename@example.com", 1404157834, 0)); - cl_git_pass(git_commit_create(&oid, g_repo, "HEAD", signature, signature, NULL, "Test commit", commit_tree, 1, (const git_commit**)&head_commit)); - - cl_git_mkfile("renames/copy.txt", "Hello World!\n"); - cl_git_rmfile("renames/small.txt"); - - diff_options.flags = GIT_DIFF_INCLUDE_UNTRACKED; - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, commit_tree, &diff_options)); - find_options.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_FOR_UNTRACKED; - cl_git_pass(git_diff_find_similar(diff, &find_options)); - - cl_assert_equal_i(git_diff_num_deltas(diff), 1); - delta = git_diff_get_delta(diff, 0); - cl_assert_equal_i(delta->status, GIT_DELTA_RENAMED); - cl_assert_equal_s(delta->old_file.path, "small.txt"); - cl_assert_equal_s(delta->new_file.path, "copy.txt"); - - git_diff_free(diff); - git_signature_free(signature); - git_tree_free(commit_tree); - git_tree_free(head_tree); - git_commit_free(head_commit); - git_reference_free(head_reference); - git_index_free(index); -} - -void test_diff_rename__working_directory_changes(void) -{ - const char *sha0 = COPY_RENAME_COMMIT; - const char *blobsha = "66311f5cfbe7836c27510a3ba2f43e282e2c8bba"; - git_oid id; - git_tree *tree; - git_blob *blob; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - diff_expects exp; - git_str old_content = GIT_STR_INIT, content = GIT_STR_INIT;; - - tree = resolve_commit_oid_to_tree(g_repo, sha0); - diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED | GIT_DIFF_INCLUDE_UNTRACKED; - - /* - $ git cat-file -p 2bc7f351d20b53f1c72c16c4b036e491c478c49a^{tree} - - 100644 blob 66311f5cfbe7836c27510a3ba2f43e282e2c8bba sevencities.txt - 100644 blob ad0a8e55a104ac54a8a29ed4b84b49e76837a113 sixserving.txt - 100644 blob 66311f5cfbe7836c27510a3ba2f43e282e2c8bba songofseven.txt - - $ for f in *.txt; do - echo `git hash-object -t blob $f` $f - done - - eaf4a3e3bfe68585e90cada20736ace491cd100b ikeepsix.txt - f90d4fc20ecddf21eebe6a37e9225d244339d2b5 sixserving.txt - 4210ffd5c390b21dd5483375e75288dea9ede512 songof7cities.txt - 9a69d960ae94b060f56c2a8702545e2bb1abb935 untimely.txt - */ - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts)); - - /* git diff --no-renames 2bc7f351d20b53f1c72c16c4b036e491c478c49a */ - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(6, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); - - /* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a */ - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(5, exp.files); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); - - /* rewrite files in the working directory with / without CRLF changes */ - - cl_git_pass( - git_futils_readbuffer(&old_content, "renames/songof7cities.txt")); - cl_git_pass( - git_str_lf_to_crlf(&content, &old_content)); - cl_git_pass( - git_futils_writebuffer(&content, "renames/songof7cities.txt", 0, 0)); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts)); - - /* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a */ - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(5, exp.files); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); - - /* try a different whitespace option */ - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts)); - - opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE; - opts.rename_threshold = 70; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(6, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); - - /* try a different matching option */ - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts)); - - opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_EXACT_MATCH_ONLY; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(6, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); - - git_diff_free(diff); - - /* again with exact match blob */ - - cl_git_pass(git_oid_fromstr(&id, blobsha)); - cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); - cl_git_pass(git_str_set( - &content, git_blob_rawcontent(blob), (size_t)git_blob_rawsize(blob))); - cl_git_rewritefile("renames/songof7cities.txt", content.ptr); - git_blob_free(blob); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts)); - - opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_EXACT_MATCH_ONLY; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - /* - fprintf(stderr, "\n\n"); - diff_print_raw(stderr, diff); - */ - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(5, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); - - git_tree_free(tree); - git_str_dispose(&content); - git_str_dispose(&old_content); -} - -void test_diff_rename__patch(void) -{ - const char *sha0 = COPY_RENAME_COMMIT; - const char *sha1 = REWRITE_COPY_COMMIT; - git_tree *old_tree, *new_tree; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - git_patch *patch; - const git_diff_delta *delta; - git_buf buf = GIT_BUF_INIT; - const char *expected = "diff --git a/sixserving.txt b/ikeepsix.txt\nindex ad0a8e5..36020db 100644\n--- a/sixserving.txt\n+++ b/ikeepsix.txt\n@@ -1,3 +1,6 @@\n+I Keep Six Honest Serving-Men\n+=============================\n+\n I KEEP six honest serving-men\n (They taught me all I knew);\n Their names are What and Why and When\n@@ -21,4 +24,4 @@ She sends'em abroad on her own affairs,\n One million Hows, two million Wheres,\n And seven million Whys!\n \n- -- Rudyard Kipling\n+ -- Rudyard Kipling\n"; - - old_tree = resolve_commit_oid_to_tree(g_repo, sha0); - new_tree = resolve_commit_oid_to_tree(g_repo, sha1); - - diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, old_tree, new_tree, &diffopts)); - - opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - /* == Changes ===================================================== - * sixserving.txt -> ikeepsix.txt (copy, add title, >80% match) - * sevencities.txt (no change) - * sixserving.txt -> sixserving.txt (indentation change) - * songofseven.txt -> songofseven.txt (major rewrite, <20% match - split) - */ - - cl_assert_equal_i(4, (int)git_diff_num_deltas(diff)); - - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_assert((delta = git_patch_get_delta(patch)) != NULL); - cl_assert_equal_i(GIT_DELTA_COPIED, (int)delta->status); - - cl_git_pass(git_patch_to_buf(&buf, patch)); - cl_assert_equal_s(expected, buf.ptr); - git_buf_dispose(&buf); - - git_patch_free(patch); - - cl_assert((delta = git_diff_get_delta(diff, 1)) != NULL); - cl_assert_equal_i(GIT_DELTA_UNMODIFIED, (int)delta->status); - - cl_assert((delta = git_diff_get_delta(diff, 2)) != NULL); - cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); - - cl_assert((delta = git_diff_get_delta(diff, 3)) != NULL); - cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); - - git_diff_free(diff); - git_tree_free(old_tree); - git_tree_free(new_tree); -} - -void test_diff_rename__file_exchange(void) -{ - git_str c1 = GIT_STR_INIT, c2 = GIT_STR_INIT; - git_index *index; - git_tree *tree; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - diff_expects exp; - - cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt")); - cl_git_pass(git_futils_readbuffer(&c2, "renames/songof7cities.txt")); - cl_git_pass(git_futils_writebuffer(&c1, "renames/songof7cities.txt", 0, 0)); - cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0)); - - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_read_tree(index, tree)); - cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); - cl_git_pass(git_index_add_bypath(index, "untimely.txt")); - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(2, exp.files); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(2, exp.files); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); - - git_diff_free(diff); - git_tree_free(tree); - git_index_free(index); - - git_str_dispose(&c1); - git_str_dispose(&c2); -} - -void test_diff_rename__file_exchange_three(void) -{ - git_str c1 = GIT_STR_INIT, c2 = GIT_STR_INIT, c3 = GIT_STR_INIT; - git_index *index; - git_tree *tree; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - diff_expects exp; - - cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt")); - cl_git_pass(git_futils_readbuffer(&c2, "renames/songof7cities.txt")); - cl_git_pass(git_futils_readbuffer(&c3, "renames/ikeepsix.txt")); - - cl_git_pass(git_futils_writebuffer(&c1, "renames/ikeepsix.txt", 0, 0)); - cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0)); - cl_git_pass(git_futils_writebuffer(&c3, "renames/songof7cities.txt", 0, 0)); - - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_read_tree(index, tree)); - cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); - cl_git_pass(git_index_add_bypath(index, "untimely.txt")); - cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); - - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_RENAMED]); - - git_diff_free(diff); - git_tree_free(tree); - git_index_free(index); - - git_str_dispose(&c1); - git_str_dispose(&c2); - git_str_dispose(&c3); -} - -void test_diff_rename__file_partial_exchange(void) -{ - git_str c1 = GIT_STR_INIT, c2 = GIT_STR_INIT; - git_index *index; - git_tree *tree; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - diff_expects exp; - int i; - - cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt")); - cl_git_pass(git_futils_writebuffer(&c1, "renames/songof7cities.txt", 0, 0)); - for (i = 0; i < 100; ++i) - cl_git_pass(git_str_puts(&c2, "this is not the content you are looking for\n")); - cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0)); - - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_read_tree(index, tree)); - cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); - cl_git_pass(git_index_add_bypath(index, "untimely.txt")); - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(2, exp.files); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - - git_diff_free(diff); - git_tree_free(tree); - git_index_free(index); - - git_str_dispose(&c1); - git_str_dispose(&c2); -} - -void test_diff_rename__rename_and_copy_from_same_source(void) -{ - git_str c1 = GIT_STR_INIT, c2 = GIT_STR_INIT; - git_index *index; - git_tree *tree; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - diff_expects exp; - - /* put the first 2/3 of file into one new place - * and the second 2/3 of file into another new place - */ - cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt")); - cl_git_pass(git_str_set(&c2, c1.ptr, c1.size)); - git_str_truncate(&c1, c1.size * 2 / 3); - git_str_consume(&c2, ((char *)c2.ptr) + (c2.size / 3)); - cl_git_pass(git_futils_writebuffer(&c1, "renames/song_a.txt", 0, 0)); - cl_git_pass(git_futils_writebuffer(&c2, "renames/song_b.txt", 0, 0)); - - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_read_tree(index, tree)); - cl_git_pass(git_index_add_bypath(index, "song_a.txt")); - cl_git_pass(git_index_add_bypath(index, "song_b.txt")); - - diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED; - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(6, exp.files); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNMODIFIED]); - - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(6, exp.files); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_COPIED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNMODIFIED]); - - git_diff_free(diff); - git_tree_free(tree); - git_index_free(index); - - git_str_dispose(&c1); - git_str_dispose(&c2); -} - -void test_diff_rename__from_deleted_to_split(void) -{ - git_str c1 = GIT_STR_INIT; - git_index *index; - git_tree *tree; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - diff_expects exp; - - /* old file is missing, new file is actually old file renamed */ - - cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt")); - cl_git_pass(git_futils_writebuffer(&c1, "renames/untimely.txt", 0, 0)); - - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_read_tree(index, tree)); - cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt")); - cl_git_pass(git_index_add_bypath(index, "untimely.txt")); - - diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED; - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNMODIFIED]); - - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNMODIFIED]); - - git_diff_free(diff); - git_tree_free(tree); - git_index_free(index); - - git_str_dispose(&c1); -} - -struct rename_expected -{ - size_t len; - - unsigned int *status; - const char **sources; - const char **targets; - - size_t idx; -}; - -static int test_names_expected(const git_diff_delta *delta, float progress, void *p) -{ - struct rename_expected *expected = p; - - GIT_UNUSED(progress); - - cl_assert(expected->idx < expected->len); - - cl_assert_equal_i(delta->status, expected->status[expected->idx]); - - cl_assert(git__strcmp(expected->sources[expected->idx], - delta->old_file.path) == 0); - cl_assert(git__strcmp(expected->targets[expected->idx], - delta->new_file.path) == 0); - - expected->idx++; - - return 0; -} - -void test_diff_rename__rejected_match_can_match_others(void) -{ - git_reference *head, *selfsimilar; - git_index *index; - git_tree *tree; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - git_str one = GIT_STR_INIT, two = GIT_STR_INIT; - unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED }; - const char *sources[] = { "Class1.cs", "Class2.cs" }; - const char *targets[] = { "ClassA.cs", "ClassB.cs" }; - struct rename_expected expect = { 2, status, sources, targets }; - char *ptr; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - findopts.flags = GIT_DIFF_FIND_RENAMES; - - cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); - cl_git_pass(git_reference_symbolic_set_target( - &selfsimilar, head, "refs/heads/renames_similar", NULL)); - cl_git_pass(git_checkout_head(g_repo, &opts)); - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(git_futils_readbuffer(&one, "renames/Class1.cs")); - cl_git_pass(git_futils_readbuffer(&two, "renames/Class2.cs")); - - cl_git_pass(p_unlink("renames/Class1.cs")); - cl_git_pass(p_unlink("renames/Class2.cs")); - - cl_git_pass(git_index_remove_bypath(index, "Class1.cs")); - cl_git_pass(git_index_remove_bypath(index, "Class2.cs")); - - cl_assert(ptr = strstr(one.ptr, "Class1")); - ptr[5] = 'A'; - - cl_assert(ptr = strstr(two.ptr, "Class2")); - ptr[5] = 'B'; - - cl_git_pass( - git_futils_writebuffer(&one, "renames/ClassA.cs", O_RDWR|O_CREAT, 0777)); - cl_git_pass( - git_futils_writebuffer(&two, "renames/ClassB.cs", O_RDWR|O_CREAT, 0777)); - - cl_git_pass(git_index_add_bypath(index, "ClassA.cs")); - cl_git_pass(git_index_add_bypath(index, "ClassB.cs")); - - cl_git_pass(git_index_write(index)); - - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass( - git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - cl_git_pass(git_diff_find_similar(diff, &findopts)); - - cl_git_pass(git_diff_foreach( - diff, test_names_expected, NULL, NULL, NULL, &expect)); - - git_diff_free(diff); - git_tree_free(tree); - git_index_free(index); - git_reference_free(head); - git_reference_free(selfsimilar); - git_str_dispose(&one); - git_str_dispose(&two); -} - -static void write_similarity_file_two(const char *filename, size_t b_lines) -{ - git_str contents = GIT_STR_INIT; - size_t i; - - for (i = 0; i < b_lines; i++) - git_str_printf(&contents, "%02d - bbbbb\r\n", (int)(i+1)); - - for (i = b_lines; i < 50; i++) - git_str_printf(&contents, "%02d - aaaaa%s", (int)(i+1), (i == 49 ? "" : "\r\n")); - - cl_git_pass( - git_futils_writebuffer(&contents, filename, O_RDWR|O_CREAT, 0777)); - - git_str_dispose(&contents); -} - -void test_diff_rename__rejected_match_can_match_others_two(void) -{ - git_reference *head, *selfsimilar; - git_index *index; - git_tree *tree; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED }; - const char *sources[] = { "a.txt", "b.txt" }; - const char *targets[] = { "c.txt", "d.txt" }; - struct rename_expected expect = { 2, status, sources, targets }; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - findopts.flags = GIT_DIFF_FIND_RENAMES; - - cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); - cl_git_pass(git_reference_symbolic_set_target( - &selfsimilar, head, "refs/heads/renames_similar_two", NULL)); - cl_git_pass(git_checkout_head(g_repo, &opts)); - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(p_unlink("renames/a.txt")); - cl_git_pass(p_unlink("renames/b.txt")); - - cl_git_pass(git_index_remove_bypath(index, "a.txt")); - cl_git_pass(git_index_remove_bypath(index, "b.txt")); - - write_similarity_file_two("renames/c.txt", 7); - write_similarity_file_two("renames/d.txt", 8); - - cl_git_pass(git_index_add_bypath(index, "c.txt")); - cl_git_pass(git_index_add_bypath(index, "d.txt")); - - cl_git_pass(git_index_write(index)); - - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass( - git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - cl_git_pass(git_diff_find_similar(diff, &findopts)); - - cl_git_pass(git_diff_foreach( - diff, test_names_expected, NULL, NULL, NULL, &expect)); - cl_assert(expect.idx > 0); - - git_diff_free(diff); - git_tree_free(tree); - git_index_free(index); - git_reference_free(head); - git_reference_free(selfsimilar); -} - -void test_diff_rename__rejected_match_can_match_others_three(void) -{ - git_reference *head, *selfsimilar; - git_index *index; - git_tree *tree; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - - /* Both cannot be renames from a.txt */ - unsigned int status[] = { GIT_DELTA_ADDED, GIT_DELTA_RENAMED }; - const char *sources[] = { "0001.txt", "a.txt" }; - const char *targets[] = { "0001.txt", "0002.txt" }; - struct rename_expected expect = { 2, status, sources, targets }; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - findopts.flags = GIT_DIFF_FIND_RENAMES; - - cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); - cl_git_pass(git_reference_symbolic_set_target( - &selfsimilar, head, "refs/heads/renames_similar_two", NULL)); - cl_git_pass(git_checkout_head(g_repo, &opts)); - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(p_unlink("renames/a.txt")); - - cl_git_pass(git_index_remove_bypath(index, "a.txt")); - - write_similarity_file_two("renames/0001.txt", 7); - write_similarity_file_two("renames/0002.txt", 0); - - cl_git_pass(git_index_add_bypath(index, "0001.txt")); - cl_git_pass(git_index_add_bypath(index, "0002.txt")); - - cl_git_pass(git_index_write(index)); - - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass( - git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - cl_git_pass(git_diff_find_similar(diff, &findopts)); - - cl_git_pass(git_diff_foreach( - diff, test_names_expected, NULL, NULL, NULL, &expect)); - - cl_assert(expect.idx == expect.len); - - git_diff_free(diff); - git_tree_free(tree); - git_index_free(index); - git_reference_free(head); - git_reference_free(selfsimilar); -} - -void test_diff_rename__can_rename_from_rewrite(void) -{ - git_index *index; - git_tree *tree; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - - unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED }; - const char *sources[] = { "ikeepsix.txt", "songof7cities.txt" }; - const char *targets[] = { "songof7cities.txt", "this-is-a-rename.txt" }; - struct rename_expected expect = { 2, status, sources, targets }; - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(p_rename("renames/songof7cities.txt", "renames/this-is-a-rename.txt")); - cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/songof7cities.txt")); - - cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); - - cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); - cl_git_pass(git_index_add_bypath(index, "this-is-a-rename.txt")); - - cl_git_pass(git_index_write(index)); - - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass( - git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - findopts.flags |= GIT_DIFF_FIND_AND_BREAK_REWRITES | - GIT_DIFF_FIND_REWRITES | - GIT_DIFF_FIND_RENAMES_FROM_REWRITES; - - cl_git_pass(git_diff_find_similar(diff, &findopts)); - - cl_git_pass(git_diff_foreach( - diff, test_names_expected, NULL, NULL, NULL, &expect)); - - cl_assert(expect.idx == expect.len); - - git_diff_free(diff); - git_tree_free(tree); - git_index_free(index); -} - -void test_diff_rename__case_changes_are_split(void) -{ - git_index *index; - git_tree *tree; - git_diff *diff = NULL; - diff_expects exp; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/IKEEPSIX.txt")); - - cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_add_bypath(index, "IKEEPSIX.txt")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, NULL)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(2, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); - - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - - git_diff_free(diff); - git_index_free(index); - git_tree_free(tree); -} - -void test_diff_rename__unmodified_can_be_renamed(void) -{ - git_index *index; - git_tree *tree; - git_diff *diff = NULL; - diff_expects exp; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt")); - - cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(2, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); - - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - - git_diff_free(diff); - git_index_free(index); - git_tree_free(tree); -} - -void test_diff_rename__rewrite_on_single_file(void) -{ - git_index *index; - git_diff *diff = NULL; - diff_expects exp; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - - diffopts.flags = GIT_DIFF_INCLUDE_UNTRACKED; - - findopts.flags = GIT_DIFF_FIND_FOR_UNTRACKED | - GIT_DIFF_FIND_AND_BREAK_REWRITES | - GIT_DIFF_FIND_RENAMES_FROM_REWRITES; - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_rewritefile("renames/ikeepsix.txt", - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n"); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &diffopts)); - cl_git_pass(git_diff_find_similar(diff, &findopts)); - - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(2, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); - git_index_free(index); -} - -void test_diff_rename__can_find_copy_to_split(void) -{ - git_str c1 = GIT_STR_INIT; - git_index *index; - git_tree *tree; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - diff_expects exp; - - cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt")); - cl_git_pass(git_futils_writebuffer(&c1, "renames/untimely.txt", 0, 0)); - - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_read_tree(index, tree)); - cl_git_pass(git_index_add_bypath(index, "untimely.txt")); - - diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED; - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNMODIFIED]); - - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(5, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNMODIFIED]); - - git_diff_free(diff); - git_tree_free(tree); - git_index_free(index); - - git_str_dispose(&c1); -} - -void test_diff_rename__can_delete_unmodified_deltas(void) -{ - git_str c1 = GIT_STR_INIT; - git_index *index; - git_tree *tree; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - diff_expects exp; - - cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt")); - cl_git_pass(git_futils_writebuffer(&c1, "renames/untimely.txt", 0, 0)); - - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_read_tree(index, tree)); - cl_git_pass(git_index_add_bypath(index, "untimely.txt")); - - diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED; - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNMODIFIED]); - - opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_REMOVE_UNMODIFIED; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(2, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); - - git_diff_free(diff); - git_tree_free(tree); - git_index_free(index); - - git_str_dispose(&c1); -} - -void test_diff_rename__matches_config_behavior(void) -{ - const char *sha0 = INITIAL_COMMIT; - const char *sha1 = COPY_RENAME_COMMIT; - const char *sha2 = REWRITE_COPY_COMMIT; - - git_tree *tree0, *tree1, *tree2; - git_config *cfg; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - diff_expects exp; - - opts.flags = GIT_DIFF_FIND_BY_CONFIG; - tree0 = resolve_commit_oid_to_tree(g_repo, sha0); - tree1 = resolve_commit_oid_to_tree(g_repo, sha1); - tree2 = resolve_commit_oid_to_tree(g_repo, sha2); - - diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - cl_git_pass(git_repository_config(&cfg, g_repo)); - - /* diff.renames = false; no rename detection should happen */ - cl_git_pass(git_config_set_bool(cfg, "diff.renames", false)); - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, tree0, tree1, &diffopts)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_find_similar(diff, &opts)); - cl_git_pass(git_diff_foreach(diff, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - git_diff_free(diff); - - /* diff.renames = true; should act like -M */ - cl_git_pass(git_config_set_bool(cfg, "diff.renames", true)); - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, tree0, tree1, &diffopts)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_find_similar(diff, &opts)); - cl_git_pass(git_diff_foreach(diff, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - git_diff_free(diff); - - /* diff.renames = copies; should act like -M -C */ - cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies")); - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, tree1, tree2, &diffopts)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_find_similar(diff, &opts)); - cl_git_pass(git_diff_foreach(diff, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); - git_diff_free(diff); - - /* NULL find options is the same as GIT_DIFF_FIND_BY_CONFIG */ - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, tree1, tree2, &diffopts)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_find_similar(diff, NULL)); - cl_git_pass(git_diff_foreach(diff, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); - git_diff_free(diff); - - /* Cleanup */ - git_tree_free(tree0); - git_tree_free(tree1); - git_tree_free(tree2); - git_config_free(cfg); -} - -void test_diff_rename__can_override_thresholds_when_obeying_config(void) -{ - const char *sha1 = COPY_RENAME_COMMIT; - const char *sha2 = REWRITE_COPY_COMMIT; - - git_tree *tree1, *tree2; - git_config *cfg; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - diff_expects exp; - - tree1 = resolve_commit_oid_to_tree(g_repo, sha1); - tree2 = resolve_commit_oid_to_tree(g_repo, sha2); - - diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - opts.flags = GIT_DIFF_FIND_BY_CONFIG; - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies")); - git_config_free(cfg); - - /* copy threshold = 96%, should see creation of ikeepsix.txt */ - opts.copy_threshold = 96; - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, tree1, tree2, &diffopts)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_find_similar(diff, &opts)); - cl_git_pass(git_diff_foreach(diff, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); - git_diff_free(diff); - - /* copy threshold = 20%, should see sixserving.txt => ikeepsix.txt */ - opts.copy_threshold = 20; - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, tree1, tree2, &diffopts)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_find_similar(diff, &opts)); - cl_git_pass(git_diff_foreach(diff, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); - git_diff_free(diff); - - /* Cleanup */ - git_tree_free(tree1); - git_tree_free(tree2); -} - -void test_diff_rename__by_config_doesnt_mess_with_whitespace_settings(void) -{ - const char *sha1 = REWRITE_COPY_COMMIT; - const char *sha2 = RENAME_MODIFICATION_COMMIT; - - git_tree *tree1, *tree2; - git_config *cfg; - git_diff *diff; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - diff_expects exp; - - tree1 = resolve_commit_oid_to_tree(g_repo, sha1); - tree2 = resolve_commit_oid_to_tree(g_repo, sha2); - - diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - opts.flags = GIT_DIFF_FIND_BY_CONFIG; - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies")); - git_config_free(cfg); - - /* Don't ignore whitespace; this should find a change in sixserving.txt */ - opts.flags |= 0 | GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE; - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, tree1, tree2, &diffopts)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_find_similar(diff, &opts)); - cl_git_pass(git_diff_foreach(diff, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(5, exp.files); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); - git_diff_free(diff); - - /* Cleanup */ - git_tree_free(tree1); - git_tree_free(tree2); -} - -static void expect_files_renamed(const char *one, const char *two, uint32_t whitespace_flags) -{ - git_index *index; - git_diff *diff = NULL; - diff_expects exp; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - - diffopts.flags = GIT_DIFF_INCLUDE_UNTRACKED; - findopts.flags = GIT_DIFF_FIND_FOR_UNTRACKED | - GIT_DIFF_FIND_AND_BREAK_REWRITES | - GIT_DIFF_FIND_RENAMES_FROM_REWRITES | - whitespace_flags; - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_rewritefile("renames/ikeepsix.txt", one); - cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); - - cl_git_rmfile("renames/ikeepsix.txt"); - cl_git_rewritefile("renames/ikeepsix2.txt", two); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &diffopts)); - cl_git_pass(git_diff_find_similar(diff, &findopts)); - - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - - git_diff_free(diff); - git_index_free(index); -} - -/* test some variations on empty and blank files */ -void test_diff_rename__empty_files_renamed(void) -{ - /* empty files are identical when ignoring whitespace or not */ - expect_files_renamed("", "", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE); - expect_files_renamed("", "", GIT_DIFF_FIND_IGNORE_WHITESPACE); -} - -/* test that blank files are similar when ignoring whitespace */ -void test_diff_rename__blank_files_renamed_when_ignoring_whitespace(void) -{ - expect_files_renamed("", "\n\n", GIT_DIFF_FIND_IGNORE_WHITESPACE); - expect_files_renamed("", "\r\n\r\n", GIT_DIFF_FIND_IGNORE_WHITESPACE); - expect_files_renamed("\r\n\r\n", "\n\n\n", GIT_DIFF_FIND_IGNORE_WHITESPACE); - - expect_files_renamed(" ", "\n\n", GIT_DIFF_FIND_IGNORE_WHITESPACE); - expect_files_renamed(" \n \n", "\n\n", GIT_DIFF_FIND_IGNORE_WHITESPACE); -} - -/* blank files are not similar when whitespace is not ignored */ -static void expect_files_not_renamed(const char *one, const char *two, uint32_t whitespace_flags) -{ - git_index *index; - git_diff *diff = NULL; - diff_expects exp; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; - - diffopts.flags = GIT_DIFF_INCLUDE_UNTRACKED; - - findopts.flags = GIT_DIFF_FIND_FOR_UNTRACKED | - whitespace_flags; - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_rewritefile("renames/ikeepsix.txt", one); - cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); - - cl_git_rmfile("renames/ikeepsix.txt"); - cl_git_rewritefile("renames/ikeepsix2.txt", two); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &diffopts)); - cl_git_pass(git_diff_find_similar(diff, &findopts)); - - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(2, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); - git_index_free(index); -} - -/* test that blank files are similar when ignoring renames */ -void test_diff_rename__blank_files_not_renamed_when_not_ignoring_whitespace(void) -{ - expect_files_not_renamed("", "\r\n\r\n\r\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE); - expect_files_not_renamed("", "\n\n\n\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE); - expect_files_not_renamed("\n\n\n\n", "\r\n\r\n\r\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE); -} - -/* test that 100% renames and copies emit the correct patch file - * git diff --find-copies-harder -M100 -B100 \ - * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ - * 2bc7f351d20b53f1c72c16c4b036e491c478c49a - */ -void test_diff_rename__identical(void) -{ - const char *old_sha = INITIAL_COMMIT; - const char *new_sha = COPY_RENAME_COMMIT; - git_tree *old_tree, *new_tree; - git_diff *diff; - git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT; - git_buf diff_buf = GIT_BUF_INIT; - const char *expected = - "diff --git a/serving.txt b/sixserving.txt\n" - "similarity index 100%\n" - "rename from serving.txt\n" - "rename to sixserving.txt\n" - "diff --git a/sevencities.txt b/songofseven.txt\n" - "similarity index 100%\n" - "copy from sevencities.txt\n" - "copy to songofseven.txt\n"; - - old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); - new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); - - diff_opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; - find_opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED | - GIT_DIFF_FIND_EXACT_MATCH_ONLY; - - cl_git_pass(git_diff_tree_to_tree(&diff, - g_repo, old_tree, new_tree, &diff_opts)); - cl_git_pass(git_diff_find_similar(diff, &find_opts)); - - cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH)); - - cl_assert_equal_s(expected, diff_buf.ptr); - - git_buf_dispose(&diff_buf); - git_diff_free(diff); - git_tree_free(old_tree); - git_tree_free(new_tree); -} - -void test_diff_rename__rewrite_and_delete(void) -{ - const char *old_sha = RENAME_MODIFICATION_COMMIT; - const char *new_sha = REWRITE_DELETE_COMMIT; - git_tree *old_tree, *new_tree; - git_diff *diff; - git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT; - git_buf diff_buf = GIT_BUF_INIT; - const char *expected = - "diff --git a/ikeepsix.txt b/ikeepsix.txt\n" - "deleted file mode 100644\n" - "index eaf4a3e..0000000\n" - "--- a/ikeepsix.txt\n" - "+++ /dev/null\n" - "@@ -1,27 +0,0 @@\n" - "-I Keep Six Honest Serving-Men\n" - "-=============================\n" - "-\n" - "-She sends'em abroad on her own affairs,\n" - "- From the second she opens her eyes—\n" - "-One million Hows, two million Wheres,\n" - "-And seven million Whys!\n" - "-\n" - "-I let them rest from nine till five,\n" - "- For I am busy then,\n" - "-As well as breakfast, lunch, and tea,\n" - "- For they are hungry men.\n" - "-But different folk have different views;\n" - "-I know a person small—\n" - "-She keeps ten million serving-men,\n" - "-Who get no rest at all!\n" - "-\n" - "- -- Rudyard Kipling\n" - "-\n" - "-I KEEP six honest serving-men\n" - "- (They taught me all I knew);\n" - "-Their names are What and Why and When\n" - "- And How and Where and Who.\n" - "-I send them over land and sea,\n" - "- I send them east and west;\n" - "-But after they have worked for me,\n" - "- I give them all a rest.\n" - "diff --git a/songof7cities.txt b/songof7cities.txt\n" - "index 4210ffd..95ceb12 100644\n" - "--- a/songof7cities.txt\n" - "+++ b/songof7cities.txt\n" - "@@ -1,45 +1,45 @@\n" - "-The Song of Seven Cities\n" - "+THE SONG OF SEVEN CITIES\n" - " ------------------------\n" - " \n" - "-I WAS Lord of Cities very sumptuously builded.\n" - "-Seven roaring Cities paid me tribute from afar.\n" - "-Ivory their outposts were--the guardrooms of them gilded,\n" - "-And garrisoned with Amazons invincible in war.\n" - "-\n" - "-All the world went softly when it walked before my Cities--\n" - "-Neither King nor Army vexed my peoples at their toil,\n" - "-Never horse nor chariot irked or overbore my Cities,\n" - "-Never Mob nor Ruler questioned whence they drew their spoil.\n" - "-\n" - "-Banded, mailed and arrogant from sunrise unto sunset;\n" - "-Singing while they sacked it, they possessed the land at large.\n" - "-Yet when men would rob them, they resisted, they made onset\n" - "-And pierced the smoke of battle with a thousand-sabred charge.\n" - "-\n" - "-So they warred and trafficked only yesterday, my Cities.\n" - "-To-day there is no mark or mound of where my Cities stood.\n" - "-For the River rose at midnight and it washed away my Cities.\n" - "-They are evened with Atlantis and the towns before the Flood.\n" - "-\n" - "-Rain on rain-gorged channels raised the water-levels round them,\n" - "-Freshet backed on freshet swelled and swept their world from sight,\n" - "-Till the emboldened floods linked arms and, flashing forward, drowned them--\n" - "-Drowned my Seven Cities and their peoples in one night!\n" - "-\n" - "-Low among the alders lie their derelict foundations,\n" - "-The beams wherein they trusted and the plinths whereon they built--\n" - "-My rulers and their treasure and their unborn populations,\n" - "-Dead, destroyed, aborted, and defiled with mud and silt!\n" - "-\n" - "-The Daughters of the Palace whom they cherished in my Cities,\n" - "-My silver-tongued Princesses, and the promise of their May--\n" - "-Their bridegrooms of the June-tide--all have perished in my Cities,\n" - "-With the harsh envenomed virgins that can neither love nor play.\n" - "-\n" - "-I was Lord of Cities--I will build anew my Cities,\n" - "-Seven, set on rocks, above the wrath of any flood.\n" - "-Nor will I rest from search till I have filled anew my Cities\n" - "-With peoples undefeated of the dark, enduring blood.\n" - "+I WAS LORD OF CITIES VERY SUMPTUOUSLY BUILDED.\n" - "+SEVEN ROARING CITIES PAID ME TRIBUTE FROM AFAR.\n" - "+IVORY THEIR OUTPOSTS WERE--THE GUARDROOMS OF THEM GILDED,\n" - "+AND GARRISONED WITH AMAZONS INVINCIBLE IN WAR.\n" - "+\n" - "+ALL THE WORLD WENT SOFTLY WHEN IT WALKED BEFORE MY CITIES--\n" - "+NEITHER KING NOR ARMY VEXED MY PEOPLES AT THEIR TOIL,\n" - "+NEVER HORSE NOR CHARIOT IRKED OR OVERBORE MY CITIES,\n" - "+NEVER MOB NOR RULER QUESTIONED WHENCE THEY DREW THEIR SPOIL.\n" - "+\n" - "+BANDED, MAILED AND ARROGANT FROM SUNRISE UNTO SUNSET;\n" - "+SINGING WHILE THEY SACKED IT, THEY POSSESSED THE LAND AT LARGE.\n" - "+YET WHEN MEN WOULD ROB THEM, THEY RESISTED, THEY MADE ONSET\n" - "+AND PIERCED THE SMOKE OF BATTLE WITH A THOUSAND-SABRED CHARGE.\n" - "+\n" - "+SO THEY WARRED AND TRAFFICKED ONLY YESTERDAY, MY CITIES.\n" - "+TO-DAY THERE IS NO MARK OR MOUND OF WHERE MY CITIES STOOD.\n" - "+FOR THE RIVER ROSE AT MIDNIGHT AND IT WASHED AWAY MY CITIES.\n" - "+THEY ARE EVENED WITH ATLANTIS AND THE TOWNS BEFORE THE FLOOD.\n" - "+\n" - "+RAIN ON RAIN-GORGED CHANNELS RAISED THE WATER-LEVELS ROUND THEM,\n" - "+FRESHET BACKED ON FRESHET SWELLED AND SWEPT THEIR WORLD FROM SIGHT,\n" - "+TILL THE EMBOLDENED FLOODS LINKED ARMS AND, FLASHING FORWARD, DROWNED THEM--\n" - "+DROWNED MY SEVEN CITIES AND THEIR PEOPLES IN ONE NIGHT!\n" - "+\n" - "+LOW AMONG THE ALDERS LIE THEIR DERELICT FOUNDATIONS,\n" - "+THE BEAMS WHEREIN THEY TRUSTED AND THE PLINTHS WHEREON THEY BUILT--\n" - "+MY RULERS AND THEIR TREASURE AND THEIR UNBORN POPULATIONS,\n" - "+DEAD, DESTROYED, ABORTED, AND DEFILED WITH MUD AND SILT!\n" - "+\n" - "+THE DAUGHTERS OF THE PALACE WHOM THEY CHERISHED IN MY CITIES,\n" - "+MY SILVER-TONGUED PRINCESSES, AND THE PROMISE OF THEIR MAY--\n" - "+THEIR BRIDEGROOMS OF THE JUNE-TIDE--ALL HAVE PERISHED IN MY CITIES,\n" - "+WITH THE HARSH ENVENOMED VIRGINS THAT CAN NEITHER LOVE NOR PLAY.\n" - "+\n" - "+I WAS LORD OF CITIES--I WILL BUILD ANEW MY CITIES,\n" - "+SEVEN, SET ON ROCKS, ABOVE THE WRATH OF ANY FLOOD.\n" - "+NOR WILL I REST FROM SEARCH TILL I HAVE FILLED ANEW MY CITIES\n" - "+WITH PEOPLES UNDEFEATED OF THE DARK, ENDURING BLOOD.\n" - " \n" - " To the sound of trumpets shall their seed restore my Cities\n" - " Wealthy and well-weaponed, that once more may I behold\n"; - - old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); - new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); - - find_opts.flags = GIT_DIFF_FIND_RENAMES_FROM_REWRITES; - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, old_tree, new_tree, NULL)); - cl_git_pass(git_diff_find_similar(diff, &find_opts)); - - cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH)); - - cl_assert_equal_s(expected, diff_buf.ptr); - - git_buf_dispose(&diff_buf); - git_diff_free(diff); - git_tree_free(old_tree); - git_tree_free(new_tree); -} - -void test_diff_rename__delete_and_rename(void) -{ - const char *old_sha = RENAME_MODIFICATION_COMMIT; - const char *new_sha = DELETE_RENAME_COMMIT; - git_tree *old_tree, *new_tree; - git_diff *diff; - git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT; - git_buf diff_buf = GIT_BUF_INIT; - const char *expected = - "diff --git a/sixserving.txt b/sixserving.txt\n" - "deleted file mode 100644\n" - "index f90d4fc..0000000\n" - "--- a/sixserving.txt\n" - "+++ /dev/null\n" - "@@ -1,25 +0,0 @@\n" - "-I KEEP six honest serving-men\n" - "- (They taught me all I knew);\n" - "-Their names are What and Why and When\n" - "- And How and Where and Who.\n" - "-I send them over land and sea,\n" - "- I send them east and west;\n" - "-But after they have worked for me,\n" - "- I give them all a rest.\n" - "-\n" - "-I let them rest from nine till five,\n" - "- For I am busy then,\n" - "-As well as breakfast, lunch, and tea,\n" - "- For they are hungry men.\n" - "-But different folk have different views;\n" - "-I know a person small—\n" - "-She keeps ten million serving-men,\n" - "-Who get no rest at all!\n" - "-\n" - "-She sends'em abroad on her own affairs,\n" - "- From the second she opens her eyes—\n" - "-One million Hows, two million Wheres,\n" - "-And seven million Whys!\n" - "-\n" - "- -- Rudyard Kipling\n" - "-\n" - "diff --git a/songof7cities.txt b/sixserving.txt\n" - "similarity index 100%\n" - "rename from songof7cities.txt\n" - "rename to sixserving.txt\n"; - - old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); - new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); - - find_opts.flags = GIT_DIFF_FIND_RENAMES_FROM_REWRITES; - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, old_tree, new_tree, NULL)); - cl_git_pass(git_diff_find_similar(diff, &find_opts)); - - cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH)); - - cl_assert_equal_s(expected, diff_buf.ptr); - - git_buf_dispose(&diff_buf); - git_diff_free(diff); - git_tree_free(old_tree); - git_tree_free(new_tree); -} - -/* - * The break_rewrite branch contains a testcase reduced from - * a real-world scenario, rather than being "constructed" like - * the above tests seem to be. There are two commits layered - * on top of the repo's initial commit; the base commit which - * clears out the files from the initial commit and installs - * four files. And then there's the modification commit which - * mutates the files in such a way as to trigger the bug in - * libgit2. - * commit db98035f715427eef1f5e17f03e1801c05301e9e - * serving.txt (deleted) - * sevencities.txt (deleted) - * AAA (313 lines) - * BBB (314 lines) - * CCC (704 lines) - * DDD (314 lines, identical to BBB) - * commit 7e7bfb88ba9bc65fd700fee1819cf1c317aafa56 - * This deletes CCC and makes slight modifications - * to AAA, BBB, and DDD. The find_best_matches loop - * for git_diff_find_similar computes the following: - * CCC moved to AAA (similarity 91) - * CCC copied to AAA (similarity 91) - * DDD moved to BBB (similarity 52) - * CCC copied to BBB (similarity 90) - * BBB moved to DDD (similarity 52) - * CCC copied to DDD (similarity 90) - * The code to rewrite the diffs by resolving these - * copies/renames would resolve the BBB <-> DDD moves - * but then still leave BBB as a rename target for - * the deleted file CCC. Since the split flag on BBB - * was cleared, this would trigger an error. - */ -void test_diff_rename__break_rewrite(void) -{ - const char *old_sha = BREAK_REWRITE_BASE_COMMIT; - const char *new_sha = BREAK_REWRITE_COMMIT; - git_tree *old_tree, *new_tree; - git_diff *diff; - git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT; - - old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); - new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); - - find_opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES | GIT_DIFF_BREAK_REWRITES | GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY; - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, old_tree, new_tree, NULL)); - cl_git_pass(git_diff_find_similar(diff, &find_opts)); - - git_diff_free(diff); - git_tree_free(old_tree); - git_tree_free(new_tree); -} diff --git a/tests/diff/stats.c b/tests/diff/stats.c deleted file mode 100644 index f69dba9b3..000000000 --- a/tests/diff/stats.c +++ /dev/null @@ -1,378 +0,0 @@ -#include "clar.h" -#include "clar_libgit2.h" - -#include "commit.h" -#include "diff.h" -#include "diff_generate.h" - -static git_repository *_repo; -static git_diff_stats *_stats; - -void test_diff_stats__initialize(void) -{ - _repo = cl_git_sandbox_init("diff_format_email"); -} - -void test_diff_stats__cleanup(void) -{ - git_diff_stats_free(_stats); _stats = NULL; - cl_git_sandbox_cleanup(); -} - -static void diff_stats_from_commit_oid( - git_diff_stats **stats, const char *oidstr, bool rename) -{ - git_oid oid; - git_commit *commit; - git_diff *diff; - - git_oid_fromstr(&oid, oidstr); - cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); - cl_git_pass(git_diff__commit(&diff, _repo, commit, NULL)); - if (rename) - cl_git_pass(git_diff_find_similar(diff, NULL)); - cl_git_pass(git_diff_get_stats(stats, diff)); - - git_diff_free(diff); - git_commit_free(commit); -} - -void test_diff_stats__stat(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " file1.txt | 8 +++++---\n" \ - " 1 file changed, 5 insertions(+), 3 deletions(-)\n"; - - diff_stats_from_commit_oid( - &_stats, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", false); - - cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); - cl_assert_equal_sz(5, git_diff_stats_insertions(_stats)); - cl_assert_equal_sz(3, git_diff_stats_deletions(_stats)); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); - cl_assert(strcmp(buf.ptr, stat) == 0); - git_buf_dispose(&buf); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 80)); - cl_assert(strcmp(buf.ptr, stat) == 0); - git_buf_dispose(&buf); -} - -void test_diff_stats__multiple_hunks(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " file2.txt | 5 +++--\n" \ - " file3.txt | 6 ++++--\n" \ - " 2 files changed, 7 insertions(+), 4 deletions(-)\n"; - - diff_stats_from_commit_oid( - &_stats, "cd471f0d8770371e1bc78bcbb38db4c7e4106bd2", false); - - cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats)); - cl_assert_equal_sz(7, git_diff_stats_insertions(_stats)); - cl_assert_equal_sz(4, git_diff_stats_deletions(_stats)); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__numstat(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - "3 2 file2.txt\n" - "4 2 file3.txt\n"; - - diff_stats_from_commit_oid( - &_stats, "cd471f0d8770371e1bc78bcbb38db4c7e4106bd2", false); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_NUMBER, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__shortstat(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " 1 file changed, 5 insertions(+), 3 deletions(-)\n"; - - diff_stats_from_commit_oid( - &_stats, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", false); - - cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); - cl_assert_equal_sz(5, git_diff_stats_insertions(_stats)); - cl_assert_equal_sz(3, git_diff_stats_deletions(_stats)); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_SHORT, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__shortstat_noinsertions(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " 1 file changed, 2 deletions(-)\n"; - - diff_stats_from_commit_oid( - &_stats, "06b7b69a62cbd1e53c6c4e0c3f16473dcfdb4af6", false); - - cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); - cl_assert_equal_sz(0, git_diff_stats_insertions(_stats)); - cl_assert_equal_sz(2, git_diff_stats_deletions(_stats)); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_SHORT, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__shortstat_nodeletions(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " 1 file changed, 3 insertions(+)\n"; - - diff_stats_from_commit_oid( - &_stats, "5219b9784f9a92d7bd7cb567a6d6a21bfb86697e", false); - - cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); - cl_assert_equal_sz(3, git_diff_stats_insertions(_stats)); - cl_assert_equal_sz(0, git_diff_stats_deletions(_stats)); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_SHORT, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__rename(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " file2.txt => file2.txt.renamed | 1 +\n" - " file3.txt => file3.txt.renamed | 4 +++-\n" - " 2 files changed, 4 insertions(+), 1 deletion(-)\n"; - - diff_stats_from_commit_oid( - &_stats, "8947a46e2097638ca6040ad4877246f4186ec3bd", true); - - cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats)); - cl_assert_equal_sz(4, git_diff_stats_insertions(_stats)); - cl_assert_equal_sz(1, git_diff_stats_deletions(_stats)); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__rename_nochanges(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " file2.txt.renamed => file2.txt.renamed2 | 0\n" - " file3.txt.renamed => file3.txt.renamed2 | 0\n" - " 2 files changed, 0 insertions(+), 0 deletions(-)\n"; - - diff_stats_from_commit_oid( - &_stats, "3991dce9e71a0641ca49a6a4eea6c9e7ff402ed4", true); - - cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats)); - cl_assert_equal_sz(0, git_diff_stats_insertions(_stats)); - cl_assert_equal_sz(0, git_diff_stats_deletions(_stats)); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__rename_and_modifiy(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " file2.txt.renamed2 | 2 +-\n" - " file3.txt.renamed2 => file3.txt.renamed | 0\n" - " 2 files changed, 1 insertion(+), 1 deletion(-)\n"; - - diff_stats_from_commit_oid( - &_stats, "4ca10087e696d2ba78d07b146a118e9a7096ed4f", true); - - cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats)); - cl_assert_equal_sz(1, git_diff_stats_insertions(_stats)); - cl_assert_equal_sz(1, git_diff_stats_deletions(_stats)); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__rename_in_subdirectory(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " dir/{orig.txt => renamed.txt} | 0\n" - " 1 file changed, 0 insertions(+), 0 deletions(-)\n"; - - diff_stats_from_commit_oid( - &_stats, "0db2a262bc8c5c3cba55254730045a8258da7a37", true); - - cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); - cl_assert_equal_sz(0, git_diff_stats_insertions(_stats)); - cl_assert_equal_sz(0, git_diff_stats_deletions(_stats)); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__rename_no_find(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " file2.txt | 5 -----\n" - " file2.txt.renamed | 6 ++++++\n" - " file3.txt | 5 -----\n" - " file3.txt.renamed | 7 +++++++\n" - " 4 files changed, 13 insertions(+), 10 deletions(-)\n"; - - diff_stats_from_commit_oid( - &_stats, "8947a46e2097638ca6040ad4877246f4186ec3bd", false); - - cl_assert_equal_sz(4, git_diff_stats_files_changed(_stats)); - cl_assert_equal_sz(13, git_diff_stats_insertions(_stats)); - cl_assert_equal_sz(10, git_diff_stats_deletions(_stats)); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__rename_nochanges_no_find(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " file2.txt.renamed | 6 ------\n" - " file2.txt.renamed2 | 6 ++++++\n" - " file3.txt.renamed | 7 -------\n" - " file3.txt.renamed2 | 7 +++++++\n" - " 4 files changed, 13 insertions(+), 13 deletions(-)\n"; - - diff_stats_from_commit_oid( - &_stats, "3991dce9e71a0641ca49a6a4eea6c9e7ff402ed4", false); - - cl_assert_equal_sz(4, git_diff_stats_files_changed(_stats)); - cl_assert_equal_sz(13, git_diff_stats_insertions(_stats)); - cl_assert_equal_sz(13, git_diff_stats_deletions(_stats)); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__rename_and_modify_no_find(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " file2.txt.renamed2 | 2 +-\n" - " file3.txt.renamed | 7 +++++++\n" - " file3.txt.renamed2 | 7 -------\n" - " 3 files changed, 8 insertions(+), 8 deletions(-)\n"; - - diff_stats_from_commit_oid( - &_stats, "4ca10087e696d2ba78d07b146a118e9a7096ed4f", false); - - cl_assert_equal_sz(3, git_diff_stats_files_changed(_stats)); - cl_assert_equal_sz(8, git_diff_stats_insertions(_stats)); - cl_assert_equal_sz(8, git_diff_stats_deletions(_stats)); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__binary(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " binary.bin | Bin 3 -> 5 bytes\n" - " 1 file changed, 0 insertions(+), 0 deletions(-)\n"; - - diff_stats_from_commit_oid( - &_stats, "8d7523f6fcb2404257889abe0d96f093d9f524f9", false); - - cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); - cl_assert_equal_sz(0, git_diff_stats_insertions(_stats)); - cl_assert_equal_sz(0, git_diff_stats_deletions(_stats)); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__binary_numstat(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - "- - binary.bin\n"; - - diff_stats_from_commit_oid( - &_stats, "8d7523f6fcb2404257889abe0d96f093d9f524f9", false); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_NUMBER, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__mode_change(void) -{ - git_buf buf = GIT_BUF_INIT; - const char *stat = - " file1.txt.renamed | 0\n" \ - " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ - " mode change 100644 => 100755 file1.txt.renamed\n"; - - diff_stats_from_commit_oid( - &_stats, "7ade76dd34bba4733cf9878079f9fd4a456a9189", false); - - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY, 0)); - cl_assert_equal_s(stat, buf.ptr); - git_buf_dispose(&buf); -} - -void test_diff_stats__new_file(void) -{ - git_diff *diff; - git_buf buf = GIT_BUF_INIT; - - const char *input = - "---\n" - " Gurjeet Singh | 1 +\n" - " 1 file changed, 1 insertion(+)\n" - " create mode 100644 Gurjeet Singh\n" - "\n" - "diff --git a/Gurjeet Singh b/Gurjeet Singh\n" - "new file mode 100644\n" - "index 0000000..6d0ecfd\n" - "--- /dev/null\n" - "+++ b/Gurjeet Singh \n" - "@@ -0,0 +1 @@\n" - "+I'm about to try git send-email\n" - "-- \n" - "2.21.0\n"; - - const char *stat = - " Gurjeet Singh | 1 +\n" - " 1 file changed, 1 insertion(+)\n" - " create mode 100644 Gurjeet Singh\n"; - - cl_git_pass(git_diff_from_buffer(&diff, input, strlen(input))); - cl_git_pass(git_diff_get_stats(&_stats, diff)); - cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY, 0)); - cl_assert_equal_s(stat, buf.ptr); - - git_buf_dispose(&buf); - git_diff_free(diff); -} diff --git a/tests/diff/submodules.c b/tests/diff/submodules.c deleted file mode 100644 index 0436ca5c8..000000000 --- a/tests/diff/submodules.c +++ /dev/null @@ -1,543 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" -#include "posix.h" -#include "diff_helpers.h" -#include "../submodule/submodule_helpers.h" - -static git_repository *g_repo = NULL; - -void test_diff_submodules__initialize(void) -{ -} - -void test_diff_submodules__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -#define get_buf_ptr(buf) ((buf)->size ? (buf)->ptr : NULL) - -static void check_diff_patches_at_line( - git_diff *diff, const char **expected, - const char *file, const char *func, int line) -{ - const git_diff_delta *delta; - git_patch *patch = NULL; - size_t d, num_d = git_diff_num_deltas(diff); - git_buf buf = GIT_BUF_INIT; - - for (d = 0; d < num_d; ++d, git_patch_free(patch)) { - cl_git_pass(git_patch_from_diff(&patch, diff, d)); - cl_assert((delta = git_patch_get_delta(patch)) != NULL); - - if (delta->status == GIT_DELTA_UNMODIFIED) { - cl_assert_at_line(expected[d] == NULL, file, func, line); - continue; - } - - if (expected[d] && !strcmp(expected[d], "")) - continue; - if (expected[d] && !strcmp(expected[d], "")) { - cl_assert_at_line(delta->status == GIT_DELTA_UNTRACKED, file, func, line); - continue; - } - if (expected[d] && !strcmp(expected[d], "")) { - cl_git_pass(git_patch_to_buf(&buf, patch)); - cl_assert_at_line(!strcmp(expected[d], ""), file, func, line); - } - - cl_git_pass(git_patch_to_buf(&buf, patch)); - - clar__assert_equal( - file, func, line, "expected diff did not match actual diff", 1, - "%s", expected[d], get_buf_ptr(&buf)); - git_buf_dispose(&buf); - } - - cl_assert_at_line(expected[d] && !strcmp(expected[d], ""), file, func, line); -} - -#define check_diff_patches(diff, exp) \ - check_diff_patches_at_line(diff, exp, __FILE__, __func__, __LINE__) - -void test_diff_submodules__unmodified_submodule(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - static const char *expected[] = { - "", /* .gitmodules */ - NULL, /* added */ - NULL, /* ignored */ - "diff --git a/modified b/modified\nindex 092bfb9..452216e 100644\n--- a/modified\n+++ b/modified\n@@ -1 +1,2 @@\n-yo\n+changed\n+\n", /* modified */ - NULL, /* testrepo.git */ - NULL, /* unmodified */ - NULL, /* untracked */ - "" - }; - - g_repo = setup_fixture_submodules(); - - opts.flags = GIT_DIFF_INCLUDE_IGNORED | - GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_INCLUDE_UNMODIFIED; - opts.old_prefix = "a"; opts.new_prefix = "b"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected); - git_diff_free(diff); -} - -void test_diff_submodules__dirty_submodule(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - static const char *expected[] = { - "", /* .gitmodules */ - NULL, /* added */ - NULL, /* ignored */ - "diff --git a/modified b/modified\nindex 092bfb9..452216e 100644\n--- a/modified\n+++ b/modified\n@@ -1 +1,2 @@\n-yo\n+changed\n+\n", /* modified */ - "diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */ - NULL, /* unmodified */ - NULL, /* untracked */ - "" - }; - - g_repo = setup_fixture_submodules(); - - cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); - cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before"); - - opts.flags = GIT_DIFF_INCLUDE_IGNORED | - GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_INCLUDE_UNMODIFIED; - opts.old_prefix = "a"; opts.new_prefix = "b"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected); - git_diff_free(diff); -} - -void test_diff_submodules__dirty_submodule_2(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL, *diff2 = NULL; - char *smpath = "testrepo"; - static const char *expected_none[] = { - "" - }; - static const char *expected_dirty[] = { - "diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */ - "" - }; - - g_repo = setup_fixture_submodules(); - - opts.flags = GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_SHOW_UNTRACKED_CONTENT | - GIT_DIFF_RECURSE_UNTRACKED_DIRS | - GIT_DIFF_DISABLE_PATHSPEC_MATCH; - opts.old_prefix = "a"; opts.new_prefix = "b"; - opts.pathspec.count = 1; - opts.pathspec.strings = &smpath; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_none); - git_diff_free(diff); - - cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); - cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before"); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_dirty); - - { - git_tree *head; - - cl_git_pass(git_repository_head_tree(&head, g_repo)); - cl_git_pass(git_diff_tree_to_index(&diff2, g_repo, head, NULL, &opts)); - cl_git_pass(git_diff_merge(diff, diff2)); - git_diff_free(diff2); - git_tree_free(head); - - check_diff_patches(diff, expected_dirty); - } - - git_diff_free(diff); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_dirty); - git_diff_free(diff); -} - -void test_diff_submodules__submod2_index_to_wd(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - static const char *expected[] = { - "", /* .gitmodules */ - "", /* not-submodule */ - "", /* not */ - "diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */ - "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ - "", /* sm_changed_head- */ - "", /* sm_changed_head_ */ - "diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */ - "diff --git a/sm_changed_untracked_file b/sm_changed_untracked_file\nindex 4800958..4800958 160000\n--- a/sm_changed_untracked_file\n+++ b/sm_changed_untracked_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_untracked_file */ - "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */ - "" - }; - - g_repo = setup_fixture_submod2(); - - /* bracket existing submodule with similarly named items */ - cl_git_mkfile("submod2/sm_changed_head-", "hello"); - cl_git_mkfile("submod2/sm_changed_head_", "hello"); - - opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; - opts.old_prefix = "a"; opts.new_prefix = "b"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected); - git_diff_free(diff); -} - -void test_diff_submodules__submod2_head_to_index(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_tree *head; - git_diff *diff = NULL; - static const char *expected[] = { - "", /* .gitmodules */ - "diff --git a/sm_added_and_uncommited b/sm_added_and_uncommited\nnew file mode 160000\nindex 0000000..4800958\n--- /dev/null\n+++ b/sm_added_and_uncommited\n@@ -0,0 +1 @@\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n", /* sm_added_and_uncommited */ - "" - }; - - g_repo = setup_fixture_submod2(); - - cl_git_pass(git_repository_head_tree(&head, g_repo)); - - opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; - opts.old_prefix = "a"; opts.new_prefix = "b"; - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, head, NULL, &opts)); - check_diff_patches(diff, expected); - git_diff_free(diff); - - git_tree_free(head); -} - -void test_diff_submodules__invalid_cache(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_submodule *sm; - char *smpath = "sm_changed_head"; - git_repository *smrepo; - git_index *smindex; - static const char *expected_baseline[] = { - "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ - "" - }; - static const char *expected_unchanged[] = { "" }; - static const char *expected_dirty[] = { - "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247-dirty\n", - "" - }; - static const char *expected_moved[] = { - "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..7002348 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 700234833f6ccc20d744b238612646be071acaae\n", - "" - }; - static const char *expected_moved_dirty[] = { - "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..7002348 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 700234833f6ccc20d744b238612646be071acaae-dirty\n", - "" - }; - - g_repo = setup_fixture_submod2(); - - opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; - opts.old_prefix = "a"; opts.new_prefix = "b"; - opts.pathspec.count = 1; - opts.pathspec.strings = &smpath; - - /* baseline */ - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_baseline); - git_diff_free(diff); - - /* update index with new HEAD */ - cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath)); - cl_git_pass(git_submodule_add_to_index(sm, 1)); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_unchanged); - git_diff_free(diff); - - /* create untracked file in submodule working directory */ - cl_git_mkfile("submod2/sm_changed_head/new_around_here", "hello"); - git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_NONE); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_dirty); - git_diff_free(diff); - - git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_UNTRACKED); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_unchanged); - git_diff_free(diff); - - /* modify tracked file in submodule working directory */ - cl_git_append2file( - "submod2/sm_changed_head/file_to_modify", "\nmore stuff\n"); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_dirty); - git_diff_free(diff); - - git_submodule_free(sm); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath)); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_dirty); - git_diff_free(diff); - - git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_DIRTY); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_unchanged); - git_diff_free(diff); - - /* add file to index in submodule */ - cl_git_pass(git_submodule_open(&smrepo, sm)); - cl_git_pass(git_repository_index(&smindex, smrepo)); - cl_git_pass(git_index_add_bypath(smindex, "file_to_modify")); - - git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_UNTRACKED); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_dirty); - git_diff_free(diff); - - git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_DIRTY); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_unchanged); - git_diff_free(diff); - - /* commit changed index of submodule */ - cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Move it"); - - git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_DIRTY); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_moved); - git_diff_free(diff); - - git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_ALL); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_unchanged); - git_diff_free(diff); - - git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_NONE); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_moved_dirty); - git_diff_free(diff); - - p_unlink("submod2/sm_changed_head/new_around_here"); - - git_submodule_free(sm); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_moved); - git_diff_free(diff); - - git_index_free(smindex); - git_repository_free(smrepo); -} - -void test_diff_submodules__diff_ignore_options(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_config *cfg; - static const char *expected_normal[] = { - "", /* .gitmodules */ - "", /* not-submodule */ - "", /* not */ - "diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */ - "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ - "diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */ - "diff --git a/sm_changed_untracked_file b/sm_changed_untracked_file\nindex 4800958..4800958 160000\n--- a/sm_changed_untracked_file\n+++ b/sm_changed_untracked_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_untracked_file */ - "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */ - "" - }; - static const char *expected_ignore_all[] = { - "", /* .gitmodules */ - "", /* not-submodule */ - "", /* not */ - "" - }; - static const char *expected_ignore_dirty[] = { - "", /* .gitmodules */ - "", /* not-submodule */ - "", /* not */ - "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ - "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */ - "" - }; - - g_repo = setup_fixture_submod2(); - - opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; - opts.old_prefix = "a"; opts.new_prefix = "b"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_normal); - git_diff_free(diff); - - opts.flags |= GIT_DIFF_IGNORE_SUBMODULES; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_ignore_all); - git_diff_free(diff); - - opts.flags &= ~GIT_DIFF_IGNORE_SUBMODULES; - opts.ignore_submodules = GIT_SUBMODULE_IGNORE_ALL; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_ignore_all); - git_diff_free(diff); - - opts.ignore_submodules = GIT_SUBMODULE_IGNORE_DIRTY; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_ignore_dirty); - git_diff_free(diff); - - opts.ignore_submodules = 0; - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, "diff.ignoreSubmodules", false)); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_normal); - git_diff_free(diff); - - cl_git_pass(git_config_set_bool(cfg, "diff.ignoreSubmodules", true)); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_ignore_all); - git_diff_free(diff); - - cl_git_pass(git_config_set_string(cfg, "diff.ignoreSubmodules", "none")); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_normal); - git_diff_free(diff); - - cl_git_pass(git_config_set_string(cfg, "diff.ignoreSubmodules", "dirty")); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - check_diff_patches(diff, expected_ignore_dirty); - git_diff_free(diff); - - git_config_free(cfg); -} - -void test_diff_submodules__skips_empty_includes_used(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - diff_expects exp; - - /* A side effect of of Git's handling of untracked directories and - * auto-ignoring of ".git" entries is that a newly initialized Git - * repo inside another repo will be skipped by diff, but one that - * actually has a commit it in will show as an untracked directory. - * Let's make sure that works. - */ - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(0, exp.files); - git_diff_free(diff); - - { - git_repository *r2; - cl_git_pass(git_repository_init(&r2, "empty_standard_repo/subrepo", 0)); - git_repository_free(r2); - } - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - git_diff_free(diff); - - cl_git_mkfile("empty_standard_repo/subrepo/README.txt", "hello\n"); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - git_diff_free(diff); -} - -static void ensure_submodules_found( - git_repository *repo, - const char **paths, - size_t cnt) -{ - git_diff *diff = NULL; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - const git_diff_delta *delta; - size_t i, pathlen; - - opts.pathspec.strings = (char **)paths; - opts.pathspec.count = cnt; - - git_diff_index_to_workdir(&diff, repo, NULL, &opts); - - cl_assert_equal_i(cnt, git_diff_num_deltas(diff)); - - for (i = 0; i < cnt; i++) { - delta = git_diff_get_delta(diff, i); - - /* ensure that the given path is returned w/o trailing slashes. */ - pathlen = strlen(opts.pathspec.strings[i]); - - while (pathlen && opts.pathspec.strings[i][pathlen - 1] == '/') - pathlen--; - - cl_assert_equal_strn(opts.pathspec.strings[i], delta->new_file.path, pathlen); - } - - git_diff_free(diff); -} - -void test_diff_submodules__can_be_identified_by_trailing_slash_in_pathspec(void) -{ - const char *one_path_without_slash[] = { "sm_changed_head" }; - const char *one_path_with_slash[] = { "sm_changed_head/" }; - const char *many_paths_without_slashes[] = { "sm_changed_head", "sm_changed_index" }; - const char *many_paths_with_slashes[] = { "sm_changed_head/", "sm_changed_index/" }; - - g_repo = setup_fixture_submod2(); - - ensure_submodules_found(g_repo, one_path_without_slash, ARRAY_SIZE(one_path_without_slash)); - ensure_submodules_found(g_repo, one_path_with_slash, ARRAY_SIZE(one_path_with_slash)); - ensure_submodules_found(g_repo, many_paths_without_slashes, ARRAY_SIZE(many_paths_without_slashes)); - ensure_submodules_found(g_repo, many_paths_with_slashes, ARRAY_SIZE(many_paths_with_slashes)); -} diff --git a/tests/diff/tree.c b/tests/diff/tree.c deleted file mode 100644 index e03ee7b22..000000000 --- a/tests/diff/tree.c +++ /dev/null @@ -1,575 +0,0 @@ -#include "clar_libgit2.h" -#include "diff_helpers.h" - -static git_repository *g_repo = NULL; -static git_diff_options opts; -static git_diff *diff; -static git_tree *a, *b; -static diff_expects expect; - -void test_diff_tree__initialize(void) -{ - cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION)); - - memset(&expect, 0, sizeof(expect)); - - diff = NULL; - a = NULL; - b = NULL; -} - -void test_diff_tree__cleanup(void) -{ - git_diff_free(diff); - git_tree_free(a); - git_tree_free(b); - - cl_git_sandbox_cleanup(); - -} - -void test_diff_tree__0(void) -{ - /* grabbed a couple of commit oids from the history of the attr repo */ - const char *a_commit = "605812a"; - const char *b_commit = "370fe9ec22"; - const char *c_commit = "f5b0af1fb4f5c"; - git_tree *c; - - g_repo = cl_git_sandbox_init("attr"); - - cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); - cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); - cl_assert((c = resolve_commit_oid_to_tree(g_repo, c_commit)) != NULL); - - opts.context_lines = 1; - opts.interhunk_lines = 1; - - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); - - cl_assert_equal_i(5, expect.files); - cl_assert_equal_i(2, expect.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, expect.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]); - - cl_assert_equal_i(5, expect.hunks); - - cl_assert_equal_i(7 + 24 + 1 + 6 + 6, expect.lines); - cl_assert_equal_i(1, expect.line_ctxt); - cl_assert_equal_i(24 + 1 + 5 + 5, expect.line_adds); - cl_assert_equal_i(7 + 1, expect.line_dels); - - git_diff_free(diff); - diff = NULL; - - memset(&expect, 0, sizeof(expect)); - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, c, b, &opts)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); - - cl_assert_equal_i(2, expect.files); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]); - - cl_assert_equal_i(2, expect.hunks); - - cl_assert_equal_i(8 + 15, expect.lines); - cl_assert_equal_i(1, expect.line_ctxt); - cl_assert_equal_i(1, expect.line_adds); - cl_assert_equal_i(7 + 14, expect.line_dels); - - git_tree_free(c); -} - -#define DIFF_OPTS(FLAGS, CTXT) \ - {GIT_DIFF_OPTIONS_VERSION, (FLAGS), GIT_SUBMODULE_IGNORE_UNSPECIFIED, \ - {NULL,0}, NULL, NULL, NULL, (CTXT), 1} - -void test_diff_tree__options(void) -{ - /* grabbed a couple of commit oids from the history of the attr repo */ - const char *a_commit = "6bab5c79cd5140d0"; - const char *b_commit = "605812ab7fe421fdd"; - const char *c_commit = "f5b0af1fb4f5"; - const char *d_commit = "a97cc019851"; - git_tree *c, *d; - diff_expects actual; - int test_ab_or_cd[] = { 0, 0, 0, 0, 1, 1, 1, 1, 1 }; - git_diff_options test_options[] = { - /* a vs b tests */ - DIFF_OPTS(GIT_DIFF_NORMAL, 1), - DIFF_OPTS(GIT_DIFF_NORMAL, 3), - DIFF_OPTS(GIT_DIFF_REVERSE, 2), - DIFF_OPTS(GIT_DIFF_FORCE_TEXT, 2), - /* c vs d tests */ - DIFF_OPTS(GIT_DIFF_NORMAL, 3), - DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE, 3), - DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE_CHANGE, 3), - DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE_EOL, 3), - DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE | GIT_DIFF_REVERSE, 1), - }; - - /* to generate these values: - * - cd to tests/resources/attr, - * - mv .gitted .git - * - git diff [options] 6bab5c79cd5140d0 605812ab7fe421fdd - * - mv .git .gitted - */ -#define EXPECT_STATUS_ADM(ADDS,DELS,MODS) { 0, ADDS, DELS, MODS, 0, 0, 0, 0, 0 } - - diff_expects test_expects[] = { - /* a vs b tests */ - { 5, 0, EXPECT_STATUS_ADM(3, 0, 2), 4, 0, 0, 51, 2, 46, 3 }, - { 5, 0, EXPECT_STATUS_ADM(3, 0, 2), 4, 0, 0, 53, 4, 46, 3 }, - { 5, 0, EXPECT_STATUS_ADM(0, 3, 2), 4, 0, 0, 52, 3, 3, 46 }, - { 5, 0, EXPECT_STATUS_ADM(3, 0, 2), 5, 0, 0, 54, 3, 47, 4 }, - /* c vs d tests */ - { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 22, 9, 10, 3 }, - { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 19, 12, 7, 0 }, - { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 20, 11, 8, 1 }, - { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 20, 11, 8, 1 }, - { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 18, 11, 0, 7 }, - { 0 }, - }; - diff_expects *expected; - int i, j; - - g_repo = cl_git_sandbox_init("attr"); - - cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); - cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); - cl_assert((c = resolve_commit_oid_to_tree(g_repo, c_commit)) != NULL); - cl_assert((d = resolve_commit_oid_to_tree(g_repo, d_commit)) != NULL); - - for (i = 0; test_expects[i].files > 0; i++) { - memset(&actual, 0, sizeof(actual)); /* clear accumulator */ - opts = test_options[i]; - - if (test_ab_or_cd[i] == 0) - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); - else - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, c, d, &opts)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &actual)); - - expected = &test_expects[i]; - cl_assert_equal_i(actual.files, expected->files); - for (j = GIT_DELTA_UNMODIFIED; j <= GIT_DELTA_TYPECHANGE; ++j) - cl_assert_equal_i(expected->file_status[j], actual.file_status[j]); - cl_assert_equal_i(actual.hunks, expected->hunks); - cl_assert_equal_i(actual.lines, expected->lines); - cl_assert_equal_i(actual.line_ctxt, expected->line_ctxt); - cl_assert_equal_i(actual.line_adds, expected->line_adds); - cl_assert_equal_i(actual.line_dels, expected->line_dels); - - git_diff_free(diff); - diff = NULL; - } - - git_tree_free(c); - git_tree_free(d); -} - -void test_diff_tree__bare(void) -{ - const char *a_commit = "8496071c1b46c85"; - const char *b_commit = "be3563ae3f79"; - - g_repo = cl_git_sandbox_init("testrepo.git"); - - cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); - cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); - - opts.context_lines = 1; - opts.interhunk_lines = 1; - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); - - cl_assert_equal_i(3, expect.files); - cl_assert_equal_i(2, expect.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, expect.file_status[GIT_DELTA_MODIFIED]); - - cl_assert_equal_i(3, expect.hunks); - - cl_assert_equal_i(4, expect.lines); - cl_assert_equal_i(0, expect.line_ctxt); - cl_assert_equal_i(3, expect.line_adds); - cl_assert_equal_i(1, expect.line_dels); -} - -void test_diff_tree__merge(void) -{ - /* grabbed a couple of commit oids from the history of the attr repo */ - const char *a_commit = "605812a"; - const char *b_commit = "370fe9ec22"; - const char *c_commit = "f5b0af1fb4f5c"; - git_tree *c; - git_diff *diff1 = NULL, *diff2 = NULL; - - g_repo = cl_git_sandbox_init("attr"); - - cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); - cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); - cl_assert((c = resolve_commit_oid_to_tree(g_repo, c_commit)) != NULL); - - cl_git_pass(git_diff_tree_to_tree(&diff1, g_repo, a, b, NULL)); - - cl_git_pass(git_diff_tree_to_tree(&diff2, g_repo, c, b, NULL)); - - git_tree_free(c); - - cl_git_pass(git_diff_merge(diff1, diff2)); - - git_diff_free(diff2); - - cl_git_pass(git_diff_foreach( - diff1, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); - - cl_assert_equal_i(6, expect.files); - cl_assert_equal_i(2, expect.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, expect.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(3, expect.file_status[GIT_DELTA_MODIFIED]); - - cl_assert_equal_i(6, expect.hunks); - - cl_assert_equal_i(59, expect.lines); - cl_assert_equal_i(1, expect.line_ctxt); - cl_assert_equal_i(36, expect.line_adds); - cl_assert_equal_i(22, expect.line_dels); - - git_diff_free(diff1); -} - -void test_diff_tree__larger_hunks(void) -{ - const char *a_commit = "d70d245ed97ed2aa596dd1af6536e4bfdb047b69"; - const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10"; - size_t d, num_d, h, num_h, l, num_l; - git_patch *patch; - const git_diff_hunk *hunk; - const git_diff_line *line; - - g_repo = cl_git_sandbox_init("diff"); - - cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); - cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); - - opts.context_lines = 1; - opts.interhunk_lines = 0; - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); - - num_d = git_diff_num_deltas(diff); - for (d = 0; d < num_d; ++d) { - cl_git_pass(git_patch_from_diff(&patch, diff, d)); - cl_assert(patch); - - num_h = git_patch_num_hunks(patch); - for (h = 0; h < num_h; h++) { - cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h)); - - for (l = 0; l < num_l; ++l) { - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l)); - cl_assert(line); - } - - cl_git_fail(git_patch_get_line_in_hunk(&line, patch, h, num_l)); - } - - cl_git_fail(git_patch_get_hunk(&hunk, &num_l, patch, num_h)); - - git_patch_free(patch); - } - - cl_git_fail(git_patch_from_diff(&patch, diff, num_d)); - - cl_assert_equal_i(2, (int)num_d); -} - -void test_diff_tree__checks_options_version(void) -{ - const char *a_commit = "8496071c1b46c85"; - const char *b_commit = "be3563ae3f79"; - const git_error *err; - - g_repo = cl_git_sandbox_init("testrepo.git"); - - cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); - cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); - - opts.version = 0; - cl_git_fail(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); - err = git_error_last(); - cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); - - git_error_clear(); - opts.version = 1024; - cl_git_fail(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); - err = git_error_last(); -} - -static void process_tree_to_tree_diffing( - const char *old_commit, - const char *new_commit) -{ - g_repo = cl_git_sandbox_init("unsymlinked.git"); - - cl_assert((a = resolve_commit_oid_to_tree(g_repo, old_commit)) != NULL); - cl_assert((b = resolve_commit_oid_to_tree(g_repo, new_commit)) != NULL); - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, NULL, NULL, NULL, &expect)); -} - -void test_diff_tree__symlink_blob_mode_changed_to_regular_file(void) -{ - /* - * $ git diff 7fccd7..806999 - * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h - * deleted file mode 120000 - * index 19bf568..0000000 - * --- a/include/Nu/Nu.h - * +++ /dev/null - * @@ -1 +0,0 @@ - * -../../objc/Nu.h - * \ No newline at end of file - * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h - * new file mode 100644 - * index 0000000..f9e6561 - * --- /dev/null - * +++ b/include/Nu/Nu.h - * @@ -0,0 +1 @@ - * +awesome content - * diff --git a/objc/Nu.h b/objc/Nu.h - * deleted file mode 100644 - * index f9e6561..0000000 - * --- a/objc/Nu.h - * +++ /dev/null - * @@ -1 +0,0 @@ - * -awesome content - */ - - process_tree_to_tree_diffing("7fccd7", "806999"); - - cl_assert_equal_i(3, expect.files); - cl_assert_equal_i(2, expect.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, expect.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_TYPECHANGE]); -} - -void test_diff_tree__symlink_blob_mode_changed_to_regular_file_as_typechange(void) -{ - /* - * $ git diff 7fccd7..a8595c - * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h - * deleted file mode 120000 - * index 19bf568..0000000 - * --- a/include/Nu/Nu.h - * +++ /dev/null - * @@ -1 +0,0 @@ - * -../../objc/Nu.h - * \ No newline at end of file - * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h - * new file mode 100755 - * index 0000000..f9e6561 - * --- /dev/null - * +++ b/include/Nu/Nu.h - * @@ -0,0 +1 @@ - * +awesome content - * diff --git a/objc/Nu.h b/objc/Nu.h - * deleted file mode 100644 - * index f9e6561..0000000 - * --- a/objc/Nu.h - * +++ /dev/null - * @@ -1 +0,0 @@ - * -awesome content - */ - - opts.flags = GIT_DIFF_INCLUDE_TYPECHANGE; - process_tree_to_tree_diffing("7fccd7", "a8595c"); - - cl_assert_equal_i(2, expect.files); - cl_assert_equal_i(1, expect.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, expect.file_status[GIT_DELTA_TYPECHANGE]); -} - -void test_diff_tree__regular_blob_mode_changed_to_executable_file(void) -{ - /* - * $ git diff 806999..a8595c - * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h - * old mode 100644 - * new mode 100755 - */ - - process_tree_to_tree_diffing("806999", "a8595c"); - - cl_assert_equal_i(1, expect.files); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, expect.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_TYPECHANGE]); -} - -void test_diff_tree__issue_1397(void) -{ - /* this test shows that it is not needed */ - - g_repo = cl_git_sandbox_init("issue_1397"); - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - cl_assert((a = resolve_commit_oid_to_tree(g_repo, "8a7ef04")) != NULL); - cl_assert((b = resolve_commit_oid_to_tree(g_repo, "7f483a7")) != NULL); - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); - - cl_git_pass(git_diff_foreach(diff, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); - - cl_assert_equal_i(1, expect.files); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, expect.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_TYPECHANGE]); -} - -static void set_config_int(git_repository *repo, const char *name, int value) -{ - git_config *cfg; - - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass(git_config_set_int32(cfg, name, value)); - git_config_free(cfg); -} - -void test_diff_tree__diff_configs(void) -{ - const char *a_commit = "d70d245e"; - const char *b_commit = "7a9e0b02"; - - g_repo = cl_git_sandbox_init("diff"); - - cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); - cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL)); - - cl_git_pass(git_diff_foreach(diff, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); - - cl_assert_equal_i(2, expect.files); - cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(6, expect.hunks); - cl_assert_equal_i(55, expect.lines); - cl_assert_equal_i(33, expect.line_ctxt); - cl_assert_equal_i(7, expect.line_adds); - cl_assert_equal_i(15, expect.line_dels); - - git_diff_free(diff); - diff = NULL; - - set_config_int(g_repo, "diff.context", 1); - - memset(&expect, 0, sizeof(expect)); - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL)); - - cl_git_pass(git_diff_foreach(diff, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); - - cl_assert_equal_i(2, expect.files); - cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(7, expect.hunks); - cl_assert_equal_i(34, expect.lines); - cl_assert_equal_i(12, expect.line_ctxt); - cl_assert_equal_i(7, expect.line_adds); - cl_assert_equal_i(15, expect.line_dels); - - git_diff_free(diff); - diff = NULL; - - set_config_int(g_repo, "diff.context", 0); - set_config_int(g_repo, "diff.noprefix", 1); - - memset(&expect, 0, sizeof(expect)); - - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL)); - - cl_git_pass(git_diff_foreach(diff, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); - - cl_assert_equal_i(2, expect.files); - cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(7, expect.hunks); - cl_assert_equal_i(22, expect.lines); - cl_assert_equal_i(0, expect.line_ctxt); - cl_assert_equal_i(7, expect.line_adds); - cl_assert_equal_i(15, expect.line_dels); -} - -void test_diff_tree__diff_tree_with_empty_dir_entry_succeeds(void) -{ - const char *content = "This is a blob\n"; - const git_diff_delta *delta; - git_oid empty_tree, invalid_tree, blob; - git_buf patch = GIT_BUF_INIT; - git_treebuilder *builder; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_pass(git_blob_create_from_buffer(&blob, g_repo, content, strlen(content))); - cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); - cl_git_pass(git_treebuilder_write(&empty_tree, builder)); - cl_git_pass(git_treebuilder_insert(NULL, builder, "empty_tree", &empty_tree, GIT_FILEMODE_TREE)); - cl_git_pass(git_treebuilder_insert(NULL, builder, "blob", &blob, GIT_FILEMODE_BLOB)); - cl_git_pass(git_treebuilder_write(&invalid_tree, builder)); - - cl_git_pass(git_tree_lookup(&a, g_repo, &empty_tree)); - cl_git_pass(git_tree_lookup(&b, g_repo, &invalid_tree)); - cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL)); - - cl_git_pass(git_diff_foreach(diff, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); - cl_assert_equal_i(1, expect.files); - cl_assert_equal_i(0, expect.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, expect.hunks); - cl_assert_equal_i(1, expect.lines); - cl_assert_equal_i(0, expect.line_ctxt); - cl_assert_equal_i(1, expect.line_adds); - cl_assert_equal_i(0, expect.line_dels); - - cl_git_pass(git_diff_to_buf(&patch, diff, GIT_DIFF_FORMAT_PATCH)); - cl_assert_equal_s(patch.ptr, - "diff --git a/blob b/blob\n" - "new file mode 100644\n" - "index 0000000..bbf2e80\n" - "--- /dev/null\n" - "+++ b/blob\n" - "@@ -0,0 +1 @@\n" - "+This is a blob\n"); - - cl_assert_equal_i(git_diff_num_deltas(diff), 1); - delta = git_diff_get_delta(diff, 0); - cl_assert_equal_s(delta->new_file.path, "blob"); - - git_treebuilder_free(builder); - git_buf_dispose(&patch); -} diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c deleted file mode 100644 index cdf53faa1..000000000 --- a/tests/diff/workdir.c +++ /dev/null @@ -1,2242 +0,0 @@ -#include "clar_libgit2.h" -#include "diff_helpers.h" -#include "repository.h" -#include "index.h" -#include "git2/sys/diff.h" -#include "../checkout/checkout_helpers.h" - -static git_repository *g_repo = NULL; - -void test_diff_workdir__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_diff_workdir__to_index(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - diff_expects exp; - int use_iterator; - - g_repo = cl_git_sandbox_init("status"); - - opts.context_lines = 3; - opts.interhunk_lines = 1; - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - else - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - /* to generate these values: - * - cd to tests/resources/status, - * - mv .gitted .git - * - git diff --name-status - * - git diff - * - mv .git .gitted - */ - cl_assert_equal_i(13, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); - - cl_assert_equal_i(8, exp.hunks); - - cl_assert_equal_i(14, exp.lines); - cl_assert_equal_i(5, exp.line_ctxt); - cl_assert_equal_i(4, exp.line_adds); - cl_assert_equal_i(5, exp.line_dels); - } - - { - git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT; - cl_git_pass(git_diff_get_perfdata(&perf, diff)); - cl_assert_equal_sz( - 13 /* in root */ + 3 /* in subdir */, perf.stat_calls); - cl_assert_equal_sz(5, perf.oid_calculations); - } - - git_diff_free(diff); -} - -void test_diff_workdir__to_index_with_conflicts(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_index *index; - git_index_entry our_entry = {{0}}, their_entry = {{0}}; - diff_expects exp = {0}; - - g_repo = cl_git_sandbox_init("status"); - - opts.context_lines = 3; - opts.interhunk_lines = 1; - - /* Adding an entry that represents a rename gets two files in conflict */ - our_entry.path = "subdir/modified_file"; - our_entry.mode = 0100644; - git_oid_fromstr(&our_entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); - - their_entry.path = "subdir/rename_conflict"; - their_entry.mode = 0100644; - git_oid_fromstr(&their_entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_conflict_add(index, NULL, &our_entry, &their_entry)); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts)); - - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(9, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_CONFLICTED]); - - cl_assert_equal_i(7, exp.hunks); - - cl_assert_equal_i(12, exp.lines); - cl_assert_equal_i(4, exp.line_ctxt); - cl_assert_equal_i(3, exp.line_adds); - cl_assert_equal_i(5, exp.line_dels); - - git_diff_free(diff); - git_index_free(index); -} - -void test_diff_workdir__to_index_with_assume_unchanged(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_index *idx = NULL; - diff_expects exp; - const git_index_entry *iep; - git_index_entry ie; - - g_repo = cl_git_sandbox_init("status"); - - /* do initial diff */ - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(8, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); - git_diff_free(diff); - - /* mark a couple of entries with ASSUME_UNCHANGED */ - - cl_git_pass(git_repository_index(&idx, g_repo)); - - cl_assert((iep = git_index_get_bypath(idx, "modified_file", 0)) != NULL); - memcpy(&ie, iep, sizeof(ie)); - ie.flags |= GIT_INDEX_ENTRY_VALID; - cl_git_pass(git_index_add(idx, &ie)); - - cl_assert((iep = git_index_get_bypath(idx, "file_deleted", 0)) != NULL); - memcpy(&ie, iep, sizeof(ie)); - ie.flags |= GIT_INDEX_ENTRY_VALID; - cl_git_pass(git_index_add(idx, &ie)); - - cl_git_pass(git_index_write(idx)); - git_index_free(idx); - - /* redo diff and see that entries are skipped */ - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(6, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); - git_diff_free(diff); - -} - -void test_diff_workdir__to_tree(void) -{ - /* grabbed a couple of commit oids from the history of the attr repo */ - const char *a_commit = "26a125ee1bf"; /* the current HEAD */ - const char *b_commit = "0017bd4ab1ec3"; /* the start */ - git_tree *a, *b; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_diff *diff2 = NULL; - diff_expects exp; - int use_iterator; - - g_repo = cl_git_sandbox_init("status"); - - a = resolve_commit_oid_to_tree(g_repo, a_commit); - b = resolve_commit_oid_to_tree(g_repo, b_commit); - - opts.context_lines = 3; - opts.interhunk_lines = 1; - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - - /* You can't really generate the equivalent of git_diff_tree_to_workdir() - * using C git. It really wants to interpose the index into the diff. - * - * To validate the following results with command line git, I ran the - * following: - * - git ls-tree 26a125 - * - find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths - * The results are documented at the bottom of this file in the - * long comment entitled "PREPARATION OF TEST DATA". - */ - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - else - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(14, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(5, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - /* Since there is no git diff equivalent, let's just assume that the - * text diffs produced by git_diff_foreach are accurate here. We will - * do more apples-to-apples test comparison below. - */ - - git_diff_free(diff); - diff = NULL; - memset(&exp, 0, sizeof(exp)); - - /* This is a compatible emulation of "git diff " which looks like - * a workdir to tree diff (even though it is not really). This is what - * you would get from "git diff --name-status 26a125ee1bf" - */ - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); - cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); - cl_git_pass(git_diff_merge(diff, diff2)); - git_diff_free(diff2); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - else - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(15, exp.files); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(5, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); - - cl_assert_equal_i(11, exp.hunks); - - cl_assert_equal_i(17, exp.lines); - cl_assert_equal_i(4, exp.line_ctxt); - cl_assert_equal_i(8, exp.line_adds); - cl_assert_equal_i(5, exp.line_dels); - } - - git_diff_free(diff); - diff = NULL; - memset(&exp, 0, sizeof(exp)); - - /* Again, emulating "git diff " for testing purposes using - * "git diff --name-status 0017bd4ab1ec3" instead. - */ - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts)); - cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); - cl_git_pass(git_diff_merge(diff, diff2)); - git_diff_free(diff2); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - else - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(16, exp.files); - cl_assert_equal_i(5, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); - - cl_assert_equal_i(12, exp.hunks); - - cl_assert_equal_i(19, exp.lines); - cl_assert_equal_i(3, exp.line_ctxt); - cl_assert_equal_i(12, exp.line_adds); - cl_assert_equal_i(4, exp.line_dels); - } - - git_diff_free(diff); - - /* Let's try that once more with a reversed diff */ - - opts.flags |= GIT_DIFF_REVERSE; - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts)); - cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); - cl_git_pass(git_diff_merge(diff, diff2)); - git_diff_free(diff2); - - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(16, exp.files); - cl_assert_equal_i(5, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); - - cl_assert_equal_i(12, exp.hunks); - - cl_assert_equal_i(19, exp.lines); - cl_assert_equal_i(3, exp.line_ctxt); - cl_assert_equal_i(12, exp.line_dels); - cl_assert_equal_i(4, exp.line_adds); - - git_diff_free(diff); - - /* all done now */ - - git_tree_free(a); - git_tree_free(b); -} - -void test_diff_workdir__to_index_with_pathspec(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - diff_expects exp; - char *pathspec = NULL; - int use_iterator; - - g_repo = cl_git_sandbox_init("status"); - - opts.context_lines = 3; - opts.interhunk_lines = 1; - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - opts.pathspec.strings = &pathspec; - opts.pathspec.count = 1; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - else - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(13, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - git_diff_free(diff); - - pathspec = "modified_file"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - else - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - git_diff_free(diff); - - pathspec = "subdir"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - else - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - git_diff_free(diff); - - pathspec = "*_deleted"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - else - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(2, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - git_diff_free(diff); -} - -void test_diff_workdir__to_index_with_pathlist_disabling_fnmatch(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - diff_expects exp; - char *pathspec = NULL; - int use_iterator; - - g_repo = cl_git_sandbox_init("status"); - - opts.context_lines = 3; - opts.interhunk_lines = 1; - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_DISABLE_PATHSPEC_MATCH; - opts.pathspec.strings = &pathspec; - opts.pathspec.count = 0; - - /* ensure that an empty pathspec list is ignored */ - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - else - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(13, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - git_diff_free(diff); - - /* ensure that a single NULL pathspec is filtered out (like when using - * fnmatch filtering) - */ - - opts.pathspec.count = 1; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - else - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(13, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - git_diff_free(diff); - - pathspec = "modified_file"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - else - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - git_diff_free(diff); - - /* ensure that subdirs can be specified */ - pathspec = "subdir"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - else - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - git_diff_free(diff); - - /* ensure that subdirs can be specified with a trailing slash */ - pathspec = "subdir/"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - else - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - git_diff_free(diff); - - /* ensure that fnmatching is completely disabled */ - pathspec = "subdir/*"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - else - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - git_diff_free(diff); - - /* ensure that the prefix matching isn't completely braindead */ - pathspec = "subdi"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - else - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - git_diff_free(diff); - - /* ensure that fnmatching isn't working at all */ - pathspec = "*_deleted"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - else - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - git_diff_free(diff); -} - -void test_diff_workdir__filemode_changes(void) -{ - git_diff *diff = NULL; - diff_expects exp; - int use_iterator; - - if (!cl_is_chmod_supported()) - return; - - g_repo = cl_git_sandbox_init("issue_592"); - - cl_repo_set_bool(g_repo, "core.filemode", true); - - /* test once with no mods */ - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - else - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.hunks); - } - - git_diff_free(diff); - - /* chmod file and test again */ - - cl_assert(cl_toggle_filemode("issue_592/a.txt")); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - else - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.hunks); - } - - git_diff_free(diff); - - cl_assert(cl_toggle_filemode("issue_592/a.txt")); -} - -void test_diff_workdir__filemode_changes_with_filemode_false(void) -{ - git_diff *diff = NULL; - diff_expects exp; - - if (!cl_is_chmod_supported()) - return; - - g_repo = cl_git_sandbox_init("issue_592"); - - cl_repo_set_bool(g_repo, "core.filemode", false); - - /* test once with no mods */ - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.hunks); - - git_diff_free(diff); - - /* chmod file and test again */ - - cl_assert(cl_toggle_filemode("issue_592/a.txt")); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach(diff, - diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.hunks); - - git_diff_free(diff); - - cl_assert(cl_toggle_filemode("issue_592/a.txt")); -} - -void test_diff_workdir__head_index_and_workdir_all_differ(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff_i2t = NULL, *diff_w2i = NULL; - diff_expects exp; - char *pathspec = "staged_changes_modified_file"; - git_tree *tree; - int use_iterator; - - /* For this file, - * - head->index diff has 1 line of context, 1 line of diff - * - index->workdir diff has 2 lines of context, 1 line of diff - * but - * - head->workdir diff has 1 line of context, 2 lines of diff - * Let's make sure the right one is returned from each fn. - */ - - g_repo = cl_git_sandbox_init("status"); - - tree = resolve_commit_oid_to_tree(g_repo, "26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f"); - - opts.pathspec.strings = &pathspec; - opts.pathspec.count = 1; - - cl_git_pass(git_diff_tree_to_index(&diff_i2t, g_repo, tree, NULL, &opts)); - cl_git_pass(git_diff_index_to_workdir(&diff_w2i, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff_i2t, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - else - cl_git_pass(git_diff_foreach( - diff_i2t, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.hunks); - cl_assert_equal_i(2, exp.lines); - cl_assert_equal_i(1, exp.line_ctxt); - cl_assert_equal_i(1, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); - } - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff_w2i, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - else - cl_git_pass(git_diff_foreach( - diff_w2i, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.hunks); - cl_assert_equal_i(3, exp.lines); - cl_assert_equal_i(2, exp.line_ctxt); - cl_assert_equal_i(1, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); - } - - cl_git_pass(git_diff_merge(diff_i2t, diff_w2i)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff_i2t, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - else - cl_git_pass(git_diff_foreach( - diff_i2t, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.hunks); - cl_assert_equal_i(3, exp.lines); - cl_assert_equal_i(1, exp.line_ctxt); - cl_assert_equal_i(2, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); - } - - git_diff_free(diff_i2t); - git_diff_free(diff_w2i); - - git_tree_free(tree); -} - -void test_diff_workdir__eof_newline_changes(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - diff_expects exp; - char *pathspec = "current_file"; - int use_iterator; - - g_repo = cl_git_sandbox_init("status"); - - opts.pathspec.strings = &pathspec; - opts.pathspec.count = 1; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - else - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.hunks); - cl_assert_equal_i(0, exp.lines); - cl_assert_equal_i(0, exp.line_ctxt); - cl_assert_equal_i(0, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); - } - - git_diff_free(diff); - - cl_git_append2file("status/current_file", "\n"); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - else - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.hunks); - cl_assert_equal_i(2, exp.lines); - cl_assert_equal_i(1, exp.line_ctxt); - cl_assert_equal_i(1, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); - } - - git_diff_free(diff); - - cl_git_rewritefile("status/current_file", "current_file"); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - else - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.hunks); - cl_assert_equal_i(3, exp.lines); - cl_assert_equal_i(0, exp.line_ctxt); - cl_assert_equal_i(1, exp.line_adds); - cl_assert_equal_i(2, exp.line_dels); - } - - git_diff_free(diff); -} - -/* PREPARATION OF TEST DATA - * - * Since there is no command line equivalent of git_diff_tree_to_workdir, - * it was a bit of a pain to confirm that I was getting the expected - * results in the first part of this tests. Here is what I ended up - * doing to set my expectation for the file counts and results: - * - * Running "git ls-tree 26a125" and "git ls-tree aa27a6" shows: - * - * A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file - * B 5452d32f1dd538eb0405e8a83cc185f79e25e80f file_deleted - * C 452e4244b5d083ddf0460acf1ecc74db9dcfa11a modified_file - * D 32504b727382542f9f089e24fddac5e78533e96c staged_changes - * E 061d42a44cacde5726057b67558821d95db96f19 staged_changes_file_deleted - * F 70bd9443ada07063e7fbf0b3ff5c13f7494d89c2 staged_changes_modified_file - * G e9b9107f290627c04d097733a10055af941f6bca staged_delete_file_deleted - * H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file - * I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file - * J 1888c805345ba265b0ee9449b8877b6064592058 subdir/deleted_file - * K a6191982709b746d5650e93c2acf34ef74e11504 subdir/modified_file - * L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt - * - * -------- - * - * find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths - * - * A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file - * M 6a79f808a9c6bc9531ac726c184bbcd9351ccf11 ignored_file - * C 0a539630525aca2e7bc84975958f92f10a64c9b6 modified_file - * N d4fa8600b4f37d7516bef4816ae2c64dbf029e3a new_file - * D 55d316c9ba708999f1918e9677d01dfcae69c6b9 staged_changes - * F 011c3440d5c596e21d836aa6d7b10eb581f68c49 staged_changes_modified_file - * H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file - * O 529a16e8e762d4acb7b9636ff540a00831f9155a staged_new_file - * P 8b090c06d14ffa09c4e880088ebad33893f921d1 staged_new_file_modified_file - * I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file - * K 57274b75eeb5f36fd55527806d567b2240a20c57 subdir/modified_file - * Q 80a86a6931b91bc01c2dbf5ca55bdd24ad1ef466 subdir/new_file - * L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt - * - * -------- - * - * A - current_file (UNMODIFIED) -> not in results - * B D file_deleted - * M I ignored_file (IGNORED) - * C M modified_file - * N U new_file (UNTRACKED) - * D M staged_changes - * E D staged_changes_file_deleted - * F M staged_changes_modified_file - * G D staged_delete_file_deleted - * H - staged_delete_modified_file (UNMODIFIED) -> not in results - * O U staged_new_file - * P U staged_new_file_modified_file - * I - subdir/current_file (UNMODIFIED) -> not in results - * J D subdir/deleted_file - * K M subdir/modified_file - * Q U subdir/new_file - * L - subdir.txt (UNMODIFIED) -> not in results - * - * Expect 13 files, 0 ADD, 4 DEL, 4 MOD, 1 IGN, 4 UNTR - */ - - -void test_diff_workdir__larger_hunks(void) -{ - const char *a_commit = "d70d245ed97ed2aa596dd1af6536e4bfdb047b69"; - const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10"; - git_tree *a, *b; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - size_t i, d, num_d, h, num_h, l, num_l; - - g_repo = cl_git_sandbox_init("diff"); - - cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); - cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); - - opts.context_lines = 1; - opts.interhunk_lines = 0; - - for (i = 0; i <= 2; ++i) { - git_diff *diff = NULL; - git_patch *patch; - const git_diff_hunk *hunk; - const git_diff_line *line; - - /* okay, this is a bit silly, but oh well */ - switch (i) { - case 0: - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - break; - case 1: - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); - break; - case 2: - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, b, &opts)); - break; - } - - num_d = git_diff_num_deltas(diff); - cl_assert_equal_i(2, (int)num_d); - - for (d = 0; d < num_d; ++d) { - cl_git_pass(git_patch_from_diff(&patch, diff, d)); - cl_assert(patch); - - num_h = git_patch_num_hunks(patch); - for (h = 0; h < num_h; h++) { - cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h)); - - for (l = 0; l < num_l; ++l) { - cl_git_pass( - git_patch_get_line_in_hunk(&line, patch, h, l)); - cl_assert(line); - } - - /* confirm fail after the last item */ - cl_git_fail( - git_patch_get_line_in_hunk(&line, patch, h, num_l)); - } - - /* confirm fail after the last item */ - cl_git_fail(git_patch_get_hunk(&hunk, &num_l, patch, num_h)); - - git_patch_free(patch); - } - - git_diff_free(diff); - } - - git_tree_free(a); - git_tree_free(b); -} - -/* Set up a test that exercises this code. The easiest test using existing - * test data is probably to create a sandbox of submod2 and then run a - * git_diff_tree_to_workdir against tree - * 873585b94bdeabccea991ea5e3ec1a277895b698. As for what you should actually - * test, you can start by just checking that the number of lines of diff - * content matches the actual output of git diff. That will at least - * demonstrate that the submodule content is being used to generate somewhat - * comparable outputs. It is a test that would fail without this code and - * will succeed with it. - */ - -#include "../submodule/submodule_helpers.h" - -void test_diff_workdir__submodules(void) -{ - const char *a_commit = "873585b94bdeabccea991ea5e3ec1a277895b698"; - git_tree *a; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - diff_expects exp; - - g_repo = setup_fixture_submod2(); - - a = resolve_commit_oid_to_tree(g_repo, a_commit); - - opts.flags = - GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_INCLUDE_IGNORED | - GIT_DIFF_RECURSE_UNTRACKED_DIRS | - GIT_DIFF_SHOW_UNTRACKED_CONTENT; - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); - - /* diff_print(stderr, diff); */ - - /* essentially doing: git diff 873585b94bdeabccea991ea5e3ec1a277895b698 */ - - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - /* so "git diff 873585" returns: - * M .gitmodules - * A just_a_dir/contents - * A just_a_file - * A sm_added_and_uncommited - * A sm_changed_file - * A sm_changed_head - * A sm_changed_index - * A sm_changed_untracked_file - * M sm_missing_commits - * A sm_unchanged - * which is a little deceptive because of the difference between the - * "git diff " results from "git_diff_tree_to_workdir". The - * only significant difference is that those Added items will show up - * as Untracked items in the pure libgit2 diff. - * - * Then add in the two extra untracked items "not" and "not-submodule" - * to get the 12 files reported here. - */ - - cl_assert_equal_i(12, exp.files); - - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(10, exp.file_status[GIT_DELTA_UNTRACKED]); - - /* the following numbers match "git diff 873585" exactly */ - - cl_assert_equal_i(9, exp.hunks); - - cl_assert_equal_i(33, exp.lines); - cl_assert_equal_i(2, exp.line_ctxt); - cl_assert_equal_i(30, exp.line_adds); - cl_assert_equal_i(1, exp.line_dels); - - git_diff_free(diff); - git_tree_free(a); -} - -void test_diff_workdir__cannot_diff_against_a_bare_repository(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_tree *tree; - - g_repo = cl_git_sandbox_init("testrepo.git"); - - cl_assert_equal_i( - GIT_EBAREREPO, git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - cl_git_pass(git_repository_head_tree(&tree, g_repo)); - - cl_assert_equal_i( - GIT_EBAREREPO, git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); - - git_tree_free(tree); -} - -void test_diff_workdir__to_null_tree(void) -{ - git_diff *diff; - diff_expects exp; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - - opts.flags = GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_RECURSE_UNTRACKED_DIRS; - - g_repo = cl_git_sandbox_init("status"); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts)); - - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(exp.files, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); -} - -void test_diff_workdir__checks_options_version(void) -{ - git_diff *diff; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - const git_error *err; - - g_repo = cl_git_sandbox_init("status"); - - opts.version = 0; - cl_git_fail(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts)); - err = git_error_last(); - cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); - - git_error_clear(); - opts.version = 1024; - cl_git_fail(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts)); - err = git_error_last(); - cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); -} - -void test_diff_workdir__can_diff_empty_file(void) -{ - git_diff *diff; - git_tree *tree; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - struct stat st; - git_patch *patch; - - g_repo = cl_git_sandbox_init("attr_index"); - - tree = resolve_commit_oid_to_tree(g_repo, "3812cfef3661"); /* HEAD */ - - /* baseline - make sure there are no outstanding diffs */ - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); - cl_assert_equal_i(2, (int)git_diff_num_deltas(diff)); - git_diff_free(diff); - - /* empty contents of file */ - - cl_git_rewritefile("attr_index/README.txt", ""); - cl_git_pass(git_fs_path_lstat("attr_index/README.txt", &st)); - cl_assert_equal_i(0, (int)st.st_size); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); - cl_assert_equal_i(3, (int)git_diff_num_deltas(diff)); - /* diffs are: .gitattributes, README.txt, sub/sub/.gitattributes */ - cl_git_pass(git_patch_from_diff(&patch, diff, 1)); - git_patch_free(patch); - git_diff_free(diff); - - /* remove a file altogether */ - - cl_git_pass(p_unlink("attr_index/README.txt")); - cl_assert(!git_fs_path_exists("attr_index/README.txt")); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); - cl_assert_equal_i(3, (int)git_diff_num_deltas(diff)); - cl_git_pass(git_patch_from_diff(&patch, diff, 1)); - git_patch_free(patch); - git_diff_free(diff); - - git_tree_free(tree); -} - -void test_diff_workdir__to_index_issue_1397(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - diff_expects exp; - - g_repo = cl_git_sandbox_init("issue_1397"); - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - opts.context_lines = 3; - opts.interhunk_lines = 1; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.hunks); - cl_assert_equal_i(0, exp.lines); - - git_diff_free(diff); - diff = NULL; - - cl_git_rewritefile("issue_1397/crlf_file.txt", - "first line\r\nsecond line modified\r\nboth with crlf"); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - - cl_assert_equal_i(1, exp.hunks); - - cl_assert_equal_i(5, exp.lines); - cl_assert_equal_i(3, exp.line_ctxt); - cl_assert_equal_i(1, exp.line_adds); - cl_assert_equal_i(1, exp.line_dels); - - git_diff_free(diff); -} - -void test_diff_workdir__to_tree_issue_1397(void) -{ - const char *a_commit = "7f483a738"; /* the current HEAD */ - git_tree *a; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_diff *diff2 = NULL; - diff_expects exp; - - g_repo = cl_git_sandbox_init("issue_1397"); - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - a = resolve_commit_oid_to_tree(g_repo, a_commit); - - opts.context_lines = 3; - opts.interhunk_lines = 1; - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.hunks); - cl_assert_equal_i(0, exp.lines); - - git_diff_free(diff); - diff = NULL; - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); - cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); - cl_git_pass(git_diff_merge(diff, diff2)); - git_diff_free(diff2); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.hunks); - cl_assert_equal_i(0, exp.lines); - - git_diff_free(diff); - git_tree_free(a); -} - -void test_diff_workdir__untracked_directory_scenarios(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - diff_expects exp; - char *pathspec = NULL; - static const char *files0[] = { - "subdir/deleted_file", - "subdir/modified_file", - "subdir/new_file", - NULL - }; - static const char *files1[] = { - "subdir/deleted_file", - "subdir/directory/", - "subdir/modified_file", - "subdir/new_file", - NULL - }; - static const char *files2[] = { - "subdir/deleted_file", - "subdir/directory/more/notignored", - "subdir/modified_file", - "subdir/new_file", - NULL - }; - - g_repo = cl_git_sandbox_init("status"); - cl_git_mkfile("status/.gitignore", "ignored\n"); - - opts.context_lines = 3; - opts.interhunk_lines = 1; - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - opts.pathspec.strings = &pathspec; - opts.pathspec.count = 1; - pathspec = "subdir"; - - /* baseline for "subdir" pathspec */ - - memset(&exp, 0, sizeof(exp)); - exp.names = files0; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); - - /* empty directory */ - - cl_git_pass(p_mkdir("status/subdir/directory", 0777)); - - memset(&exp, 0, sizeof(exp)); - exp.names = files1; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); - - /* empty directory in empty directory */ - - cl_git_pass(p_mkdir("status/subdir/directory/empty", 0777)); - - memset(&exp, 0, sizeof(exp)); - exp.names = files1; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); - - /* directory with only ignored files */ - - cl_git_pass(p_mkdir("status/subdir/directory/deeper", 0777)); - cl_git_mkfile("status/subdir/directory/deeper/ignored", "ignore me\n"); - - cl_git_pass(p_mkdir("status/subdir/directory/another", 0777)); - cl_git_mkfile("status/subdir/directory/another/ignored", "ignore me\n"); - - memset(&exp, 0, sizeof(exp)); - exp.names = files1; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); - - /* directory with ignored directory (contents irrelevant) */ - - cl_git_pass(p_mkdir("status/subdir/directory/more", 0777)); - cl_git_pass(p_mkdir("status/subdir/directory/more/ignored", 0777)); - cl_git_mkfile("status/subdir/directory/more/ignored/notignored", - "inside ignored dir\n"); - - memset(&exp, 0, sizeof(exp)); - exp.names = files1; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); - - /* quick version avoids directory scan */ - - opts.flags = opts.flags | GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS; - - memset(&exp, 0, sizeof(exp)); - exp.names = files1; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); - - /* directory with nested non-ignored content */ - - opts.flags = opts.flags & ~GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS; - - cl_git_mkfile("status/subdir/directory/more/notignored", - "not ignored deep under untracked\n"); - - memset(&exp, 0, sizeof(exp)); - exp.names = files1; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); - - /* use RECURSE_UNTRACKED_DIRS to get actual untracked files (no ignores) */ - - opts.flags = opts.flags & ~GIT_DIFF_INCLUDE_IGNORED; - opts.flags = opts.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; - - memset(&exp, 0, sizeof(exp)); - exp.names = files2; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(4, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); - - git_diff_free(diff); -} - - -void test_diff_workdir__untracked_directory_comes_last(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - - g_repo = cl_git_sandbox_init("renames"); - - cl_git_mkfile("renames/.gitignore", "*.ign\n"); - cl_git_pass(p_mkdir("renames/zzz_untracked", 0777)); - cl_git_mkfile("renames/zzz_untracked/an.ign", "ignore me please"); - cl_git_mkfile("renames/zzz_untracked/skip.ign", "ignore me really"); - cl_git_mkfile("renames/zzz_untracked/test.ign", "ignore me now"); - - opts.context_lines = 3; - opts.interhunk_lines = 1; - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - cl_assert(diff != NULL); - - git_diff_free(diff); -} - -void test_diff_workdir__untracked_with_bom(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - const git_diff_delta *delta; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - cl_git_write2file("empty_standard_repo/bom.txt", - "\xFF\xFE\x31\x00\x32\x00\x33\x00\x34\x00", 10, O_WRONLY|O_CREAT, 0664); - - opts.flags = - GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_SHOW_UNTRACKED_CONTENT; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - cl_assert_equal_i(1, git_diff_num_deltas(diff)); - cl_assert((delta = git_diff_get_delta(diff, 0)) != NULL); - cl_assert_equal_i(GIT_DELTA_UNTRACKED, delta->status); - - /* not known at this point - * cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); - */ - - git_diff_free(diff); -} - -void test_diff_workdir__patience_diff(void) -{ - git_index *index; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_patch *patch = NULL; - git_buf buf = GIT_BUF_INIT; - const char *expected_normal = "diff --git a/test.txt b/test.txt\nindex 34a5acc..d52725f 100644\n--- a/test.txt\n+++ b/test.txt\n@@ -1,10 +1,7 @@\n When I wrote this\n I did not know\n-how to create\n-a patience diff\n I did not know\n how to create\n+a patience diff\n another problem\n-I did not know\n-how to create\n a minimal diff\n"; - const char *expected_patience = "diff --git a/test.txt b/test.txt\nindex 34a5acc..d52725f 100644\n--- a/test.txt\n+++ b/test.txt\n@@ -1,10 +1,7 @@\n When I wrote this\n I did not know\n+I did not know\n how to create\n a patience diff\n-I did not know\n-how to create\n another problem\n-I did not know\n-how to create\n a minimal diff\n"; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_repo_set_bool(g_repo, "core.autocrlf", true); - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_mkfile( - "empty_standard_repo/test.txt", - "When I wrote this\nI did not know\nhow to create\na patience diff\nI did not know\nhow to create\nanother problem\nI did not know\nhow to create\na minimal diff\n"); - cl_git_pass(git_index_add_bypath(index, "test.txt")); - cl_repo_commit_from_index(NULL, g_repo, NULL, 1372350000, "Base"); - git_index_free(index); - - cl_git_rewritefile( - "empty_standard_repo/test.txt", - "When I wrote this\nI did not know\nI did not know\nhow to create\na patience diff\nanother problem\na minimal diff\n"); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - cl_assert_equal_i(1, git_diff_num_deltas(diff)); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&buf, patch)); - - cl_assert_equal_s(expected_normal, buf.ptr); - git_buf_dispose(&buf); - git_patch_free(patch); - git_diff_free(diff); - - opts.flags |= GIT_DIFF_PATIENCE; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - cl_assert_equal_i(1, git_diff_num_deltas(diff)); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&buf, patch)); - - cl_assert_equal_s(expected_patience, buf.ptr); - git_buf_dispose(&buf); - - git_patch_free(patch); - git_diff_free(diff); -} - -void test_diff_workdir__with_stale_index(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_index *idx = NULL; - diff_expects exp; - - g_repo = cl_git_sandbox_init("status"); - cl_git_pass(git_repository_index(&idx, g_repo)); - - /* make the in-memory index invalid */ - { - git_repository *r2; - git_index *idx2; - cl_git_pass(git_repository_open(&r2, "status")); - cl_git_pass(git_repository_index(&idx2, r2)); - cl_git_pass(git_index_add_bypath(idx2, "new_file")); - cl_git_pass(git_index_add_bypath(idx2, "subdir/new_file")); - cl_git_pass(git_index_remove_bypath(idx2, "staged_new_file")); - cl_git_pass(git_index_remove_bypath(idx2, "staged_changes_file_deleted")); - cl_git_pass(git_index_write(idx2)); - git_index_free(idx2); - git_repository_free(r2); - } - - opts.context_lines = 3; - opts.interhunk_lines = 1; - opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_INCLUDE_UNMODIFIED; - - /* first try with index pointer which should prevent reload */ - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, idx, &opts)); - - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(17, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); - cl_assert_equal_i(5, exp.file_status[GIT_DELTA_UNMODIFIED]); - - git_diff_free(diff); - - /* now let's try without the index pointer which should trigger reload */ - - /* two files that were UNTRACKED should have become UNMODIFIED */ - /* one file that was UNMODIFIED should now have become UNTRACKED */ - /* one file that was DELETED should now be gone completely */ - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - git_diff_free(diff); - - cl_assert_equal_i(16, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); - cl_assert_equal_i(6, exp.file_status[GIT_DELTA_UNMODIFIED]); - - git_index_free(idx); -} - -static int touch_file(void *payload, git_str *path) -{ - struct stat st; - struct p_timeval times[2]; - - GIT_UNUSED(payload); - if (git_fs_path_isdir(path->ptr)) - return 0; - - cl_must_pass(p_stat(path->ptr, &st)); - - times[0].tv_sec = st.st_mtime + 3; - times[0].tv_usec = 0; - times[1].tv_sec = st.st_mtime + 3; - times[1].tv_usec = 0; - - cl_must_pass(p_utimes(path->ptr, times)); - return 0; -} - -static void basic_diff_status(git_diff **out, const git_diff_options *opts) -{ - diff_expects exp; - - cl_git_pass(git_diff_index_to_workdir(out, g_repo, NULL, opts)); - - memset(&exp, 0, sizeof(exp)); - - cl_git_pass(git_diff_foreach( - *out, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - - cl_assert_equal_i(13, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); -} - -void test_diff_workdir__can_update_index(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT; - git_index *index; - - g_repo = cl_git_sandbox_init("status"); - - /* touch all the files so stat times are different */ - { - git_str path = GIT_STR_INIT; - cl_git_pass(git_str_sets(&path, "status")); - cl_git_pass(git_fs_path_direach(&path, 0, touch_file, NULL)); - git_str_dispose(&path); - } - - opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - - basic_diff_status(&diff, &opts); - - cl_git_pass(git_diff_get_perfdata(&perf, diff)); - cl_assert_equal_sz(13 + 3, perf.stat_calls); - cl_assert_equal_sz(5, perf.oid_calculations); - - git_diff_free(diff); - - /* now allow diff to update stat cache */ - opts.flags |= GIT_DIFF_UPDATE_INDEX; - - /* advance a tick for the index so we don't re-calculate racily-clean entries */ - cl_git_pass(git_repository_index__weakptr(&index, g_repo)); - tick_index(index); - - basic_diff_status(&diff, &opts); - - cl_git_pass(git_diff_get_perfdata(&perf, diff)); - cl_assert_equal_sz(13 + 3, perf.stat_calls); - cl_assert_equal_sz(5, perf.oid_calculations); - - git_diff_free(diff); - - /* now if we do it again, we should see fewer OID calculations */ - - /* tick again as the index updating from the previous diff might have reset the timestamp */ - tick_index(index); - basic_diff_status(&diff, &opts); - - cl_git_pass(git_diff_get_perfdata(&perf, diff)); - cl_assert_equal_sz(13 + 3, perf.stat_calls); - cl_assert_equal_sz(0, perf.oid_calculations); - - git_diff_free(diff); -} - -#define STR7 "0123456" -#define STR8 "01234567" -#define STR40 STR8 STR8 STR8 STR8 STR8 -#define STR200 STR40 STR40 STR40 STR40 STR40 -#define STR999Z STR200 STR200 STR200 STR200 STR40 STR40 STR40 STR40 \ - STR8 STR8 STR8 STR8 STR7 "\0" -#define STR1000 STR200 STR200 STR200 STR200 STR200 -#define STR3999Z STR1000 STR1000 STR1000 STR999Z -#define STR4000 STR1000 STR1000 STR1000 STR1000 - -static void assert_delta_binary(git_diff *diff, size_t idx, int is_binary) -{ - git_patch *patch; - const git_diff_delta *delta; - - cl_git_pass(git_patch_from_diff(&patch, diff, idx)); - delta = git_patch_get_delta(patch); - cl_assert_equal_b((delta->flags & GIT_DIFF_FLAG_BINARY), is_binary); - git_patch_free(patch); -} - -void test_diff_workdir__binary_detection(void) -{ - git_index *idx; - git_diff *diff = NULL; - git_str b = GIT_STR_INIT; - int i; - git_str data[10] = { - { "1234567890", 0, 10 }, /* 0 - all ascii text control */ - { "\xC3\x85\xC3\xBC\xE2\x80\xA0\x48\xC3\xB8\xCF\x80\xCE\xA9", 0, 14 }, /* 1 - UTF-8 multibyte text */ - { "\xEF\xBB\xBF\xC3\x9C\xE2\xA4\x92\xC6\x92\x38\xC2\xA3\xE2\x82\xAC", 0, 16 }, /* 2 - UTF-8 with BOM */ - { STR999Z, 0, 1000 }, /* 3 - ASCII with NUL at 1000 */ - { STR3999Z, 0, 4000 }, /* 4 - ASCII with NUL at 4000 */ - { STR4000 STR3999Z "x", 0, 8001 }, /* 5 - ASCII with NUL at 8000 */ - { STR4000 STR4000 "\0", 0, 8001 }, /* 6 - ASCII with NUL at 8001 */ - { "\x00\xDC\x00\x6E\x21\x39\xFE\x0E\x00\x63\x00\xF8" - "\x00\x64\x00\x65\x20\x48", 0, 18 }, /* 7 - UTF-16 text */ - { "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d" - "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d", - 0, 26 }, /* 8 - All non-printable characters (no NUL) */ - { "Hello \x01\x02\x03\x04\x05\x06 World!\x01\x02\x03\x04" - "\x05\x06\x07", 0, 26 }, /* 9 - 50-50 non-printable (no NUL) */ - }; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_pass(git_repository_index(&idx, g_repo)); - - /* We start with ASCII in index and test data in workdir, - * then we will try with test data in index and ASCII in workdir. - */ - - cl_git_pass(git_str_sets(&b, "empty_standard_repo/0")); - for (i = 0; i < 10; ++i) { - b.ptr[b.size - 1] = '0' + i; - cl_git_mkfile(b.ptr, "baseline"); - cl_git_pass(git_index_add_bypath(idx, &b.ptr[b.size - 1])); - - if (data[i].size == 0) - data[i].size = strlen(data[i].ptr); - cl_git_write2file( - b.ptr, data[i].ptr, data[i].size, O_WRONLY|O_TRUNC, 0664); - } - cl_git_pass(git_index_write(idx)); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); - - cl_assert_equal_i(10, git_diff_num_deltas(diff)); - - /* using diff binary detection (i.e. looking for NUL byte) */ - assert_delta_binary(diff, 0, false); - assert_delta_binary(diff, 1, false); - assert_delta_binary(diff, 2, false); - assert_delta_binary(diff, 3, true); - assert_delta_binary(diff, 4, true); - assert_delta_binary(diff, 5, true); - assert_delta_binary(diff, 6, false); - assert_delta_binary(diff, 7, true); - assert_delta_binary(diff, 8, false); - assert_delta_binary(diff, 9, false); - /* The above have been checked to match command-line Git */ - - git_diff_free(diff); - - cl_git_pass(git_str_sets(&b, "empty_standard_repo/0")); - for (i = 0; i < 10; ++i) { - b.ptr[b.size - 1] = '0' + i; - cl_git_pass(git_index_add_bypath(idx, &b.ptr[b.size - 1])); - - cl_git_write2file(b.ptr, "baseline\n", 9, O_WRONLY|O_TRUNC, 0664); - } - cl_git_pass(git_index_write(idx)); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); - - cl_assert_equal_i(10, git_diff_num_deltas(diff)); - - /* using diff binary detection (i.e. looking for NUL byte) */ - assert_delta_binary(diff, 0, false); - assert_delta_binary(diff, 1, false); - assert_delta_binary(diff, 2, false); - assert_delta_binary(diff, 3, true); - assert_delta_binary(diff, 4, true); - assert_delta_binary(diff, 5, true); - assert_delta_binary(diff, 6, false); - assert_delta_binary(diff, 7, true); - assert_delta_binary(diff, 8, false); - assert_delta_binary(diff, 9, false); - - git_diff_free(diff); - - git_index_free(idx); - git_str_dispose(&b); -} - -void test_diff_workdir__to_index_conflicted(void) { - const char *a_commit = "26a125ee1bf"; /* the current HEAD */ - git_index_entry ancestor = {{0}}, ours = {{0}}, theirs = {{0}}; - git_tree *a; - git_index *index; - git_diff *diff1, *diff2; - const git_diff_delta *delta; - - g_repo = cl_git_sandbox_init("status"); - a = resolve_commit_oid_to_tree(g_repo, a_commit); - - cl_git_pass(git_repository_index(&index, g_repo)); - - ancestor.path = ours.path = theirs.path = "_file"; - ancestor.mode = ours.mode = theirs.mode = 0100644; - git_oid_fromstr(&ancestor.id, "d427e0b2e138501a3d15cc376077a3631e15bd46"); - git_oid_fromstr(&ours.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); - git_oid_fromstr(&theirs.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); - cl_git_pass(git_index_conflict_add(index, &ancestor, &ours, &theirs)); - - cl_git_pass(git_diff_tree_to_index(&diff1, g_repo, a, index, NULL)); - cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, index, NULL)); - cl_git_pass(git_diff_merge(diff1, diff2)); - - cl_assert_equal_i(git_diff_num_deltas(diff1), 12); - delta = git_diff_get_delta(diff1, 0); - cl_assert_equal_s(delta->old_file.path, "_file"); - cl_assert_equal_i(delta->nfiles, 1); - cl_assert_equal_i(delta->status, GIT_DELTA_CONFLICTED); - - git_diff_free(diff2); - git_diff_free(diff1); - git_index_free(index); - git_tree_free(a); -} - -void test_diff_workdir__only_writes_index_when_necessary(void) -{ - git_index *index; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_reference *head; - git_object *head_object; - unsigned char initial[GIT_HASH_SHA1_SIZE], - first[GIT_HASH_SHA1_SIZE], - second[GIT_HASH_SHA1_SIZE]; - git_str path = GIT_STR_INIT; - struct stat st; - struct p_timeval times[2]; - - opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_UPDATE_INDEX; - - g_repo = cl_git_sandbox_init("status"); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_repository_head(&head, g_repo)); - cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT)); - - cl_git_pass(git_reset(g_repo, head_object, GIT_RESET_HARD, NULL)); - - memcpy(initial, git_index__checksum(index), GIT_HASH_SHA1_SIZE); - - /* update the index timestamp to avoid raciness */ - cl_must_pass(p_stat("status/.git/index", &st)); - - times[0].tv_sec = st.st_mtime + 5; - times[0].tv_usec = 0; - times[1].tv_sec = st.st_mtime + 5; - times[1].tv_usec = 0; - - cl_must_pass(p_utimes("status/.git/index", times)); - - /* ensure diff doesn't touch the index */ - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - git_diff_free(diff); - - memcpy(first, git_index__checksum(index), GIT_HASH_SHA1_SIZE); - cl_assert(memcmp(initial, first, GIT_HASH_SHA1_SIZE) != 0); - - /* touch all the files so stat times are different */ - cl_git_pass(git_str_sets(&path, "status")); - cl_git_pass(git_fs_path_direach(&path, 0, touch_file, NULL)); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - git_diff_free(diff); - - /* ensure the second diff did update the index */ - memcpy(second, git_index__checksum(index), GIT_HASH_SHA1_SIZE); - cl_assert(memcmp(first, second, GIT_HASH_SHA1_SIZE) != 0); - - git_str_dispose(&path); - git_object_free(head_object); - git_reference_free(head); - git_index_free(index); -} - -void test_diff_workdir__to_index_pathlist(void) -{ - git_index *index; - git_diff *diff; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_vector pathlist = GIT_VECTOR_INIT; - - git_vector_insert(&pathlist, "foobar/asdf"); - git_vector_insert(&pathlist, "subdir/asdf"); - git_vector_insert(&pathlist, "ignored/asdf"); - - g_repo = cl_git_sandbox_init("status"); - - cl_git_mkfile("status/.gitignore", ".gitignore\n" "ignored/\n"); - - cl_must_pass(p_mkdir("status/foobar", 0777)); - cl_git_mkfile("status/foobar/one", "one\n"); - - cl_must_pass(p_mkdir("status/ignored", 0777)); - cl_git_mkfile("status/ignored/one", "one\n"); - cl_git_mkfile("status/ignored/two", "two\n"); - cl_git_mkfile("status/ignored/three", "three\n"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - opts.flags = GIT_DIFF_INCLUDE_IGNORED; - opts.pathspec.strings = (char **)pathlist.contents; - opts.pathspec.count = pathlist.length; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts)); - cl_assert_equal_i(0, git_diff_num_deltas(diff)); - git_diff_free(diff); - - opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts)); - cl_assert_equal_i(0, git_diff_num_deltas(diff)); - git_diff_free(diff); - - git_index_free(index); - git_vector_free(&pathlist); -} - -void test_diff_workdir__symlink_changed_on_non_symlink_platform(void) -{ - git_tree *tree; - git_diff *diff; - diff_expects exp = {0}; - const git_diff_delta *delta; - const char *commit = "7fccd7"; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_vector pathlist = GIT_VECTOR_INIT; - int symlinks; - - g_repo = cl_git_sandbox_init("unsymlinked.git"); - - cl_git_pass(git_repository__configmap_lookup(&symlinks, g_repo, GIT_CONFIGMAP_SYMLINKS)); - - if (symlinks) - cl_skip(); - - cl_git_pass(git_vector_insert(&pathlist, "include/Nu/Nu.h")); - - opts.pathspec.strings = (char **)pathlist.contents; - opts.pathspec.count = pathlist.length; - - cl_must_pass(p_mkdir("symlink", 0777)); - cl_git_pass(git_repository_set_workdir(g_repo, "symlink", false)); - - cl_assert((tree = resolve_commit_oid_to_tree(g_repo, commit)) != NULL); - - /* first, do the diff with the original contents */ - - cl_git_pass(git_futils_mkpath2file("symlink/include/Nu/Nu.h", 0755)); - cl_git_mkfile("symlink/include/Nu/Nu.h", "../../objc/Nu.h"); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); - cl_assert_equal_i(0, git_diff_num_deltas(diff)); - git_diff_free(diff); - - /* now update the contents and expect a difference, but that the file - * mode has persisted as a symbolic link. - */ - - cl_git_rewritefile("symlink/include/Nu/Nu.h", "awesome content\n"); - - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); - - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); - cl_assert_equal_i(1, exp.files); - - cl_assert_equal_i(1, git_diff_num_deltas(diff)); - delta = git_diff_get_delta(diff, 0); - cl_assert_equal_i(GIT_FILEMODE_LINK, delta->old_file.mode); - cl_assert_equal_i(GIT_FILEMODE_LINK, delta->new_file.mode); - - git_diff_free(diff); - - cl_git_pass(git_futils_rmdir_r("symlink", NULL, GIT_RMDIR_REMOVE_FILES)); - - git_tree_free(tree); - git_vector_free(&pathlist); -} - -void test_diff_workdir__order(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_buf patch = GIT_BUF_INIT; - git_oid tree_oid, blob_oid; - git_treebuilder *builder; - git_tree *tree; - git_diff *diff; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - /* Build tree with a single file "abc.txt" */ - cl_git_pass(git_blob_create_from_buffer(&blob_oid, g_repo, "foo\n", 4)); - cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); - cl_git_pass(git_treebuilder_insert(NULL, builder, "abc.txt", &blob_oid, GIT_FILEMODE_BLOB)); - cl_git_pass(git_treebuilder_write(&tree_oid, builder)); - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid)); - - /* Create a directory that sorts before and one that sorts after "abc.txt" */ - cl_git_mkfile("empty_standard_repo/abc.txt", "bar\n"); - cl_must_pass(p_mkdir("empty_standard_repo/abb", 0777)); - cl_must_pass(p_mkdir("empty_standard_repo/abd", 0777)); - - opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; - cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); - - cl_assert_equal_i(1, git_diff_num_deltas(diff)); - cl_git_pass(git_diff_to_buf(&patch, diff, GIT_DIFF_FORMAT_PATCH)); - cl_assert_equal_s(patch.ptr, - "diff --git a/abc.txt b/abc.txt\n" - "index 257cc56..5716ca5 100644\n" - "--- a/abc.txt\n" - "+++ b/abc.txt\n" - "@@ -1 +1 @@\n" - "-foo\n" - "+bar\n"); - - git_treebuilder_free(builder); - git_buf_dispose(&patch); - git_diff_free(diff); - git_tree_free(tree); -} - -void test_diff_workdir__ignore_blank_lines(void) -{ - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff; - git_patch *patch; - git_buf buf = GIT_BUF_INIT; - - g_repo = cl_git_sandbox_init("rebase"); - cl_git_rewritefile("rebase/gravy.txt", "GRAVY SOUP.\n\n\nGet eight pounds of coarse lean beef--wash it clean and lay it in your\n\npot, put in the same ingredients as for the shin soup, with the same\nquantity of water, and follow the process directed for that. Strain the\nsoup through a sieve, and serve it up clear, with nothing more than\ntoasted bread in it; two table-spoonsful of mushroom catsup will add a\nfine flavour to the soup!\n"); - - /* Perform the diff normally */ - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&buf, patch)); - - cl_assert_equal_s("diff --git a/gravy.txt b/gravy.txt\nindex c4e6cca..3c617e6 100644\n--- a/gravy.txt\n+++ b/gravy.txt\n@@ -1,8 +1,10 @@\n GRAVY SOUP.\n \n+\n Get eight pounds of coarse lean beef--wash it clean and lay it in your\n+\n pot, put in the same ingredients as for the shin soup, with the same\n quantity of water, and follow the process directed for that. Strain the\n soup through a sieve, and serve it up clear, with nothing more than\n toasted bread in it; two table-spoonsful of mushroom catsup will add a\n-fine flavour to the soup.\n+fine flavour to the soup!\n", buf.ptr); - - git_buf_dispose(&buf); - git_patch_free(patch); - git_diff_free(diff); - - /* Perform the diff ignoring blank lines */ - opts.flags |= GIT_DIFF_IGNORE_BLANK_LINES; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - cl_git_pass(git_patch_from_diff(&patch, diff, 0)); - cl_git_pass(git_patch_to_buf(&buf, patch)); - - cl_assert_equal_s("diff --git a/gravy.txt b/gravy.txt\nindex c4e6cca..3c617e6 100644\n--- a/gravy.txt\n+++ b/gravy.txt\n@@ -5,4 +7,4 @@ pot, put in the same ingredients as for the shin soup, with the same\n quantity of water, and follow the process directed for that. Strain the\n soup through a sieve, and serve it up clear, with nothing more than\n toasted bread in it; two table-spoonsful of mushroom catsup will add a\n-fine flavour to the soup.\n+fine flavour to the soup!\n", buf.ptr); - - git_buf_dispose(&buf); - git_patch_free(patch); - git_diff_free(diff); -} diff --git a/tests/email/create.c b/tests/email/create.c deleted file mode 100644 index 27a665582..000000000 --- a/tests/email/create.c +++ /dev/null @@ -1,364 +0,0 @@ -#include "clar.h" -#include "clar_libgit2.h" - -#include "diff_generate.h" - -static git_repository *repo; - -void test_email_create__initialize(void) -{ - repo = cl_git_sandbox_init("diff_format_email"); -} - -void test_email_create__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void email_for_commit( - git_buf *out, - const char *commit_id, - git_email_create_options *opts) -{ - git_oid oid; - git_commit *commit = NULL; - git_diff *diff = NULL; - - git_oid_fromstr(&oid, commit_id); - - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - - cl_git_pass(git_email_create_from_commit(out, commit, opts)); - - git_diff_free(diff); - git_commit_free(commit); -} - -static void assert_email_match( - const char *expected, - const char *commit_id, - git_email_create_options *opts) -{ - git_buf buf = GIT_BUF_INIT; - - email_for_commit(&buf, commit_id, opts); - cl_assert_equal_s(expected, buf.ptr); - - git_buf_dispose(&buf); -} - -static void assert_subject_match( - const char *expected, - const char *commit_id, - git_email_create_options *opts) -{ - git_buf buf = GIT_BUF_INIT; - char *subject, *nl; - - email_for_commit(&buf, commit_id, opts); - - cl_assert((subject = strstr(buf.ptr, "\nSubject: ")) != NULL); - subject += 10; - - if ((nl = strchr(subject, '\n')) != NULL) - *nl = '\0'; - - cl_assert_equal_s(expected, subject); - - git_buf_dispose(&buf); -} - -void test_email_create__commit(void) -{ - const char *expected = - "From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \ - "Subject: [PATCH] Modify some content\n" \ - "\n" \ - "---\n" \ - " file1.txt | 8 +++++---\n" \ - " 1 file changed, 5 insertions(+), 3 deletions(-)\n" \ - "\n" \ - "diff --git a/file1.txt b/file1.txt\n" \ - "index 94aaae8..af8f41d 100644\n" \ - "--- a/file1.txt\n" \ - "+++ b/file1.txt\n" \ - "@@ -1,15 +1,17 @@\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "+_file1.txt_\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "+\n" \ - "+\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "+_file1.txt_\n" \ - "+_file1.txt_\n" \ - " file1.txt\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - assert_email_match( - expected, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", NULL); -} - -void test_email_create__rename(void) -{ - const char *expected = - "From 6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Wed, 9 Apr 2014 21:15:56 +0200\n" \ - "Subject: [PATCH] Renamed file1.txt -> file1.txt.renamed\n" \ - "\n" \ - "---\n" \ - " file1.txt => file1.txt.renamed | 4 ++--\n" \ - " 1 file changed, 2 insertions(+), 2 deletions(-)\n" \ - "\n" \ - "diff --git a/file1.txt b/file1.txt.renamed\n" \ - "similarity index 86%\n" \ - "rename from file1.txt\n" \ - "rename to file1.txt.renamed\n" \ - "index af8f41d..a97157a 100644\n" \ - "--- a/file1.txt\n" \ - "+++ b/file1.txt.renamed\n" \ - "@@ -3,13 +3,13 @@ file1.txt\n" \ - " _file1.txt_\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "-file1.txt\n" \ - "+file1.txt_renamed\n" \ - " file1.txt\n" \ - " \n" \ - " \n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "-file1.txt\n" \ - "+file1.txt_renamed\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " _file1.txt_\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - assert_email_match(expected, "6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d", NULL); -} - -void test_email_create__rename_as_add_delete(void) -{ - const char *expected = - "From 6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Wed, 9 Apr 2014 21:15:56 +0200\n" \ - "Subject: [PATCH] Renamed file1.txt -> file1.txt.renamed\n" \ - "\n" \ - "---\n" \ - " file1.txt | 17 -----------------\n" \ - " file1.txt.renamed | 17 +++++++++++++++++\n" \ - " 2 files changed, 17 insertions(+), 17 deletions(-)\n" \ - " delete mode 100644 file1.txt\n" \ - " create mode 100644 file1.txt.renamed\n" \ - "\n" \ - "diff --git a/file1.txt b/file1.txt\n" \ - "deleted file mode 100644\n" \ - "index af8f41d..0000000\n" \ - "--- a/file1.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,17 +0,0 @@\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-_file1.txt_\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-\n" \ - "-\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-_file1.txt_\n" \ - "-_file1.txt_\n" \ - "-file1.txt\n" \ - "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \ - "new file mode 100644\n" \ - "index 0000000..a97157a\n" \ - "--- /dev/null\n" \ - "+++ b/file1.txt.renamed\n" \ - "@@ -0,0 +1,17 @@\n" \ - "+file1.txt\n" \ - "+file1.txt\n" \ - "+_file1.txt_\n" \ - "+file1.txt\n" \ - "+file1.txt\n" \ - "+file1.txt_renamed\n" \ - "+file1.txt\n" \ - "+\n" \ - "+\n" \ - "+file1.txt\n" \ - "+file1.txt\n" \ - "+file1.txt_renamed\n" \ - "+file1.txt\n" \ - "+file1.txt\n" \ - "+_file1.txt_\n" \ - "+_file1.txt_\n" \ - "+file1.txt\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - opts.flags |= GIT_EMAIL_CREATE_NO_RENAMES; - - assert_email_match(expected, "6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d", &opts); -} - -void test_email_create__binary(void) -{ - const char *expected = - "From 8d7523f6fcb2404257889abe0d96f093d9f524f9 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Sun, 13 Apr 2014 18:10:18 +0200\n" \ - "Subject: [PATCH] Modified binary file\n" \ - "\n" \ - "---\n" \ - " binary.bin | Bin 3 -> 5 bytes\n" \ - " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ - "\n" \ - "diff --git a/binary.bin b/binary.bin\n" \ - "index bd474b2519cc15eab801ff851cc7d50f0dee49a1..9ac35ff15cd8864aeafd889e4826a3150f0b06c4 100644\n" \ - "GIT binary patch\n" \ - "literal 5\n" \ - "Mc${NkU}WL~000&M4gdfE\n" \ - "\n" \ - "literal 3\n" \ - "Kc${Nk-~s>u4FC%O\n" \ - "\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - assert_email_match(expected, "8d7523f6fcb2404257889abe0d96f093d9f524f9", NULL); -} - -void test_email_create__binary_not_included(void) -{ - const char *expected = - "From 8d7523f6fcb2404257889abe0d96f093d9f524f9 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Sun, 13 Apr 2014 18:10:18 +0200\n" \ - "Subject: [PATCH] Modified binary file\n" \ - "\n" \ - "---\n" \ - " binary.bin | Bin 3 -> 5 bytes\n" \ - " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ - "\n" \ - "diff --git a/binary.bin b/binary.bin\n" \ - "index bd474b2..9ac35ff 100644\n" \ - "Binary files a/binary.bin and b/binary.bin differ\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - opts.diff_opts.flags &= ~GIT_DIFF_SHOW_BINARY; - - assert_email_match(expected, "8d7523f6fcb2404257889abe0d96f093d9f524f9", &opts); -} - -void test_email_create__custom_summary_and_body(void) -{ - const char *expected = "From 627e7e12d87e07a83fad5b6bfa25e86ead4a5270 Mon Sep 17 00:00:00 2001\n" \ - "From: Patrick Steinhardt \n" \ - "Date: Tue, 24 Nov 2015 13:34:39 +0100\n" \ - "Subject: [PPPPPATCH 2/4] This is a subject\n" \ - "\n" \ - "Modify content of file3.txt by appending a new line. Make this\n" \ - "commit message somewhat longer to test behavior with newlines\n" \ - "embedded in the message body.\n" \ - "\n" \ - "Also test if new paragraphs are included correctly.\n" \ - "---\n" \ - " file3.txt | 1 +\n" \ - " 1 file changed, 1 insertion(+)\n" \ - "\n" \ - "diff --git a/file3.txt b/file3.txt\n" \ - "index 9a2d780..7309653 100644\n" \ - "--- a/file3.txt\n" \ - "+++ b/file3.txt\n" \ - "@@ -3,3 +3,4 @@ file3!\n" \ - " file3\n" \ - " file3\n" \ - " file3\n" \ - "+file3\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - const char *summary = "This is a subject\nwith\nnewlines"; - const char *body = "Modify content of file3.txt by appending a new line. Make this\n" \ - "commit message somewhat longer to test behavior with newlines\n" \ - "embedded in the message body.\n" \ - "\n" \ - "Also test if new paragraphs are included correctly."; - - git_oid oid; - git_commit *commit = NULL; - git_diff *diff = NULL; - git_buf buf = GIT_BUF_INIT; - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - - opts.subject_prefix = "PPPPPATCH"; - - git_oid_fromstr(&oid, "627e7e12d87e07a83fad5b6bfa25e86ead4a5270"); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - cl_git_pass(git_diff__commit(&diff, repo, commit, NULL)); - cl_git_pass(git_email_create_from_diff(&buf, diff, 2, 4, &oid, summary, body, git_commit_author(commit), &opts)); - - cl_assert_equal_s(expected, buf.ptr); - - git_diff_free(diff); - git_commit_free(commit); - git_buf_dispose(&buf); -} - -void test_email_create__commit_subjects(void) -{ - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - - assert_subject_match("[PATCH] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.reroll_number = 42; - assert_subject_match("[PATCH v42] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.flags |= GIT_EMAIL_CREATE_ALWAYS_NUMBER; - assert_subject_match("[PATCH v42 1/1] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.start_number = 9; - assert_subject_match("[PATCH v42 9/9] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.subject_prefix = ""; - assert_subject_match("[v42 9/9] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.reroll_number = 0; - assert_subject_match("[9/9] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.start_number = 0; - assert_subject_match("[1/1] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.flags = GIT_EMAIL_CREATE_OMIT_NUMBERS; - assert_subject_match("Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); -} diff --git a/tests/email/create.c.bak b/tests/email/create.c.bak deleted file mode 100644 index 3bb95a6f6..000000000 --- a/tests/email/create.c.bak +++ /dev/null @@ -1,386 +0,0 @@ -#include "clar.h" -#include "clar_libgit2.h" - -#include "buffer.h" -#include "diff_generate.h" - -static git_repository *repo; - -void test_email_create__initialize(void) -{ - repo = cl_git_sandbox_init("diff_format_email"); -} - -void test_email_create__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void email_for_commit( - git_buf *out, - const char *commit_id, - git_email_create_options *opts) -{ - git_oid oid; - git_commit *commit = NULL; - git_diff *diff = NULL; - - git_oid_fromstr(&oid, commit_id); - - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - - cl_git_pass(git_email_create_from_commit(out, commit, opts)); - - git_diff_free(diff); - git_commit_free(commit); -} - -static void assert_email_match( - const char *expected, - const char *commit_id, - git_email_create_options *opts) -{ - git_buf buf = GIT_BUF_INIT; - - email_for_commit(&buf, commit_id, opts); - cl_assert_equal_s(expected, git_buf_cstr(&buf)); - - git_buf_dispose(&buf); -} - -static void assert_subject_match( - const char *expected, - const char *commit_id, - git_email_create_options *opts) -{ - git_buf buf = GIT_BUF_INIT; - const char *loc; - - email_for_commit(&buf, commit_id, opts); - - cl_assert((loc = strstr(buf.ptr, "\nSubject: ")) != NULL); - git_buf_consume(&buf, (loc + 10)); - git_buf_truncate_at_char(&buf, '\n'); - - cl_assert_equal_s(expected, git_buf_cstr(&buf)); - - git_buf_dispose(&buf); -} - -void test_email_create__commit(void) -{ - const char *expected = - "From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \ - "Subject: [PATCH] Modify some content\n" \ - "\n" \ - "---\n" \ - " file1.txt | 8 +++++---\n" \ - " 1 file changed, 5 insertions(+), 3 deletions(-)\n" \ - "\n" \ - "diff --git a/file1.txt b/file1.txt\n" \ - "index 94aaae8..af8f41d 100644\n" \ - "--- a/file1.txt\n" \ - "+++ b/file1.txt\n" \ - "@@ -1,15 +1,17 @@\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "+_file1.txt_\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "+\n" \ - "+\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "+_file1.txt_\n" \ - "+_file1.txt_\n" \ - " file1.txt\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - assert_email_match( - expected, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", NULL); -} - -void test_email_create__custom_summary_and_body(void) -{ - const char *expected = "From 627e7e12d87e07a83fad5b6bfa25e86ead4a5270 Mon Sep 17 00:00:00 2001\n" \ - "From: Patrick Steinhardt \n" \ - "Date: Tue, 24 Nov 2015 13:34:39 +0100\n" \ - "Subject: [PPPPPATCH 2/4] This is a subject\n" \ - "\n" \ - "Modify content of file3.txt by appending a new line. Make this\n" \ - "commit message somewhat longer to test behavior with newlines\n" \ - "embedded in the message body.\n" \ - "\n" \ - "Also test if new paragraphs are included correctly.\n" \ - "---\n" \ - " file3.txt | 1 +\n" \ - " 1 file changed, 1 insertion(+)\n" \ - "\n" \ - "diff --git a/file3.txt b/file3.txt\n" \ - "index 9a2d780..7309653 100644\n" \ - "--- a/file3.txt\n" \ - "+++ b/file3.txt\n" \ - "@@ -3,3 +3,4 @@ file3!\n" \ - " file3\n" \ - " file3\n" \ - " file3\n" \ - "+file3\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - const char *summary = "This is a subject\nwith\nnewlines"; - const char *body = "Modify content of file3.txt by appending a new line. Make this\n" \ - "commit message somewhat longer to test behavior with newlines\n" \ - "embedded in the message body.\n" \ - "\n" \ - "Also test if new paragraphs are included correctly."; - - git_oid oid; - git_commit *commit = NULL; - git_diff *diff = NULL; - git_buf buf = GIT_BUF_INIT; - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - - opts.subject_prefix = "PPPPPATCH"; - - git_oid_fromstr(&oid, "627e7e12d87e07a83fad5b6bfa25e86ead4a5270"); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - cl_git_pass(git_diff__commit(&diff, repo, commit, NULL)); - cl_git_pass(git_email_create_from_diff(&buf, diff, 2, 4, &oid, summary, body, git_commit_author(commit), &opts)); - - cl_assert_equal_s(expected, git_buf_cstr(&buf)); - - git_diff_free(diff); - git_commit_free(commit); - git_buf_dispose(&buf); -} - -void test_email_create__mode_change(void) -{ - const char *expected = - "From 7ade76dd34bba4733cf9878079f9fd4a456a9189 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Thu, 10 Apr 2014 10:05:03 +0200\n" \ - "Subject: [PATCH] Update permissions\n" \ - "\n" \ - "---\n" \ - " file1.txt.renamed | 0\n" \ - " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ - " mode change 100644 => 100755 file1.txt.renamed\n" \ - "\n" \ - "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \ - "old mode 100644\n" \ - "new mode 100755\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - assert_email_match(expected, "7ade76dd34bba4733cf9878079f9fd4a456a9189", NULL); -} - -void test_email_create__rename(void) -{ - const char *expected = - "From 6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Wed, 9 Apr 2014 21:15:56 +0200\n" \ - "Subject: [PATCH] Renamed file1.txt -> file1.txt.renamed\n" \ - "\n" \ - "---\n" \ - " file1.txt => file1.txt.renamed | 4 ++--\n" \ - " 1 file changed, 2 insertions(+), 2 deletions(-)\n" \ - "\n" \ - "diff --git a/file1.txt b/file1.txt.renamed\n" \ - "similarity index 86%\n" \ - "rename from file1.txt\n" \ - "rename to file1.txt.renamed\n" \ - "index af8f41d..a97157a 100644\n" \ - "--- a/file1.txt\n" \ - "+++ b/file1.txt.renamed\n" \ - "@@ -3,13 +3,13 @@ file1.txt\n" \ - " _file1.txt_\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "-file1.txt\n" \ - "+file1.txt_renamed\n" \ - " file1.txt\n" \ - " \n" \ - " \n" \ - " file1.txt\n" \ - " file1.txt\n" \ - "-file1.txt\n" \ - "+file1.txt_renamed\n" \ - " file1.txt\n" \ - " file1.txt\n" \ - " _file1.txt_\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - assert_email_match(expected, "6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d", NULL); -} - -void test_email_create__rename_as_add_delete(void) -{ - const char *expected = - "From 6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Wed, 9 Apr 2014 21:15:56 +0200\n" \ - "Subject: [PATCH] Renamed file1.txt -> file1.txt.renamed\n" \ - "\n" \ - "---\n" \ - " file1.txt | 17 -----------------\n" \ - " file1.txt.renamed | 17 +++++++++++++++++\n" \ - " 2 files changed, 17 insertions(+), 17 deletions(-)\n" \ - " delete mode 100644 file1.txt\n" \ - " create mode 100644 file1.txt.renamed\n" \ - "\n" \ - "diff --git a/file1.txt b/file1.txt\n" \ - "deleted file mode 100644\n" \ - "index af8f41d..0000000\n" \ - "--- a/file1.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,17 +0,0 @@\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-_file1.txt_\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-\n" \ - "-\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-file1.txt\n" \ - "-_file1.txt_\n" \ - "-_file1.txt_\n" \ - "-file1.txt\n" \ - "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \ - "new file mode 100644\n" \ - "index 0000000..a97157a\n" \ - "--- /dev/null\n" \ - "+++ b/file1.txt.renamed\n" \ - "@@ -0,0 +1,17 @@\n" \ - "+file1.txt\n" \ - "+file1.txt\n" \ - "+_file1.txt_\n" \ - "+file1.txt\n" \ - "+file1.txt\n" \ - "+file1.txt_renamed\n" \ - "+file1.txt\n" \ - "+\n" \ - "+\n" \ - "+file1.txt\n" \ - "+file1.txt\n" \ - "+file1.txt_renamed\n" \ - "+file1.txt\n" \ - "+file1.txt\n" \ - "+_file1.txt_\n" \ - "+_file1.txt_\n" \ - "+file1.txt\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - opts.flags |= GIT_EMAIL_CREATE_NO_RENAMES; - - assert_email_match(expected, "6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d", &opts); -} - -void test_email_create__binary(void) -{ - const char *expected = - "From 8d7523f6fcb2404257889abe0d96f093d9f524f9 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Sun, 13 Apr 2014 18:10:18 +0200\n" \ - "Subject: [PATCH] Modified binary file\n" \ - "\n" \ - "---\n" \ - " binary.bin | Bin 3 -> 5 bytes\n" \ - " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ - "\n" \ - "diff --git a/binary.bin b/binary.bin\n" \ - "index bd474b2519cc15eab801ff851cc7d50f0dee49a1..9ac35ff15cd8864aeafd889e4826a3150f0b06c4 100644\n" \ - "GIT binary patch\n" \ - "literal 5\n" \ - "Mc${NkU}WL~000&M4gdfE\n" \ - "\n" \ - "literal 3\n" \ - "Kc${Nk-~s>u4FC%O\n" \ - "\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - assert_email_match(expected, "8d7523f6fcb2404257889abe0d96f093d9f524f9", NULL); -} - -void test_email_create__binary_not_included(void) -{ - const char *expected = - "From 8d7523f6fcb2404257889abe0d96f093d9f524f9 Mon Sep 17 00:00:00 2001\n" \ - "From: Jacques Germishuys \n" \ - "Date: Sun, 13 Apr 2014 18:10:18 +0200\n" \ - "Subject: [PATCH] Modified binary file\n" \ - "\n" \ - "---\n" \ - " binary.bin | Bin 3 -> 5 bytes\n" \ - " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ - "\n" \ - "diff --git a/binary.bin b/binary.bin\n" \ - "index bd474b2..9ac35ff 100644\n" \ - "Binary files a/binary.bin and b/binary.bin differ\n" \ - "--\n" \ - "libgit2 " LIBGIT2_VERSION "\n" \ - "\n"; - - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - opts.diff_opts.flags &= ~GIT_DIFF_SHOW_BINARY; - - assert_email_match(expected, "8d7523f6fcb2404257889abe0d96f093d9f524f9", &opts); -} - -void test_email_create__commit_subjects(void) -{ - git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; - - assert_subject_match("[PATCH] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.reroll_number = 42; - assert_subject_match("[PATCH v42] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.flags |= GIT_EMAIL_CREATE_ALWAYS_NUMBER; - assert_subject_match("[PATCH v42 1/1] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.start_number = 9; - assert_subject_match("[PATCH v42 9/9] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.subject_prefix = ""; - assert_subject_match("[v42 9/9] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.reroll_number = 0; - assert_subject_match("[9/9] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.start_number = 0; - assert_subject_match("[1/1] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); - - opts.flags = GIT_EMAIL_CREATE_OMIT_NUMBERS; - assert_subject_match("Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); -} diff --git a/tests/fetch/local.c b/tests/fetch/local.c deleted file mode 100644 index 20bd7adf4..000000000 --- a/tests/fetch/local.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" - -static git_repository *repo; - -void test_fetch_local__initialize(void) -{ - cl_git_pass(git_repository_init(&repo, "./fetch", 0)); -} - -void test_fetch_local__cleanup(void) -{ - git_repository_free(repo); - repo = NULL; - - cl_fixture_cleanup("./fetch"); -} - -void test_fetch_local__defaults(void) -{ - git_remote *remote; - git_object *obj; - git_oid expected_id; - - cl_git_pass(git_remote_create(&remote, repo, "test", - cl_fixture("testrepo.git"))); - cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); - - git_oid_fromstr(&expected_id, "258f0e2a959a364e40ed6603d5d44fbb24765b10"); - - cl_git_pass(git_revparse_single(&obj, repo, "refs/remotes/test/haacked")); - cl_assert_equal_oid(&expected_id, git_object_id(obj)); - - git_object_free(obj); - git_remote_free(remote); -} - -void test_fetch_local__reachable_commit(void) -{ - git_remote *remote; - git_strarray refspecs; - git_object *obj; - git_oid expected_id; - git_str fetchhead = GIT_STR_INIT; - char *refspec = "+5b5b025afb0b4c913b4c338a42934a3863bf3644:refs/success"; - - refspecs.strings = &refspec; - refspecs.count = 1; - - git_oid_fromstr(&expected_id, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); - - cl_git_pass(git_remote_create(&remote, repo, "test", - cl_fixture("testrepo.git"))); - cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, NULL)); - - cl_git_pass(git_revparse_single(&obj, repo, "refs/success")); - cl_assert_equal_oid(&expected_id, git_object_id(obj)); - - cl_git_pass(git_futils_readbuffer(&fetchhead, "./fetch/.git/FETCH_HEAD")); - cl_assert_equal_strn(fetchhead.ptr, - "5b5b025afb0b4c913b4c338a42934a3863bf3644\t\t'5b5b025afb0b4c913b4c338a42934a3863bf3644' of ", - strlen("5b5b025afb0b4c913b4c338a42934a3863bf3644\t\t'5b5b025afb0b4c913b4c338a42934a3863bf3644' of ")); - - git_str_dispose(&fetchhead); - git_object_free(obj); - git_remote_free(remote); -} diff --git a/tests/fetchhead/fetchhead_data.h b/tests/fetchhead/fetchhead_data.h deleted file mode 100644 index 77c322001..000000000 --- a/tests/fetchhead/fetchhead_data.h +++ /dev/null @@ -1,48 +0,0 @@ - -#define FETCH_HEAD_WILDCARD_DATA_LOCAL \ - "49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \ - "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \ - "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \ - "d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of https://github.com/libgit2/TestGitRepository\n" \ - "55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of https://github.com/libgit2/TestGitRepository\n" \ - "8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of https://github.com/libgit2/TestGitRepository\n" - -#define FETCH_HEAD_WILDCARD_DATA \ - "49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \ - "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \ - "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \ - "d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of https://github.com/libgit2/TestGitRepository\n" \ - "55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of https://github.com/libgit2/TestGitRepository\n" \ - "8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of https://github.com/libgit2/TestGitRepository\n" \ - "6e0c7bdb9b4ed93212491ee778ca1c65047cab4e\tnot-for-merge\ttag 'nearly-dangling' of https://github.com/libgit2/TestGitRepository\n" - -#define FETCH_HEAD_WILDCARD_DATA2 \ - "49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \ - "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \ - "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \ - -#define FETCH_HEAD_NO_MERGE_DATA \ - "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \ - "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \ - "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \ - "d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of https://github.com/libgit2/TestGitRepository\n" \ - "55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of https://github.com/libgit2/TestGitRepository\n" \ - "8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of https://github.com/libgit2/TestGitRepository\n" \ - "6e0c7bdb9b4ed93212491ee778ca1c65047cab4e\tnot-for-merge\ttag 'nearly-dangling' of https://github.com/libgit2/TestGitRepository\n" - -#define FETCH_HEAD_NO_MERGE_DATA2 \ - "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \ - "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \ - "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \ - -#define FETCH_HEAD_NO_MERGE_DATA3 \ - "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \ - "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \ - "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \ - "8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of https://github.com/libgit2/TestGitRepository\n" \ - -#define FETCH_HEAD_EXPLICIT_DATA \ - "0966a434eb1a025db6b71485ab63a3bfbea520b6\t\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" - -#define FETCH_HEAD_QUOTE_DATA \ - "0966a434eb1a025db6b71485ab63a3bfbea520b6\t\tbranch 'first's-merge' of https://github.com/libgit2/TestGitRepository\n" diff --git a/tests/fetchhead/nonetwork.c b/tests/fetchhead/nonetwork.c deleted file mode 100644 index aadcc7880..000000000 --- a/tests/fetchhead/nonetwork.c +++ /dev/null @@ -1,542 +0,0 @@ -#include "clar_libgit2.h" - -#include "futils.h" -#include "fetchhead.h" - -#include "fetchhead_data.h" - -#define DO_LOCAL_TEST 0 - -static git_repository *g_repo; - -void test_fetchhead_nonetwork__initialize(void) -{ - g_repo = NULL; -} - -static void cleanup_repository(void *path) -{ - if (g_repo) { - git_repository_free(g_repo); - g_repo = NULL; - } - - cl_fixture_cleanup((const char *)path); -} - -static void populate_fetchhead(git_vector *out, git_repository *repo) -{ - git_fetchhead_ref *fetchhead_ref; - git_oid oid; - - cl_git_pass(git_oid_fromstr(&oid, - "49322bb17d3acc9146f98c97d078513228bbf3c0")); - cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 1, - "refs/heads/master", - "https://github.com/libgit2/TestGitRepository")); - cl_git_pass(git_vector_insert(out, fetchhead_ref)); - - cl_git_pass(git_oid_fromstr(&oid, - "0966a434eb1a025db6b71485ab63a3bfbea520b6")); - cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, - "refs/heads/first-merge", - "https://github.com/libgit2/TestGitRepository")); - cl_git_pass(git_vector_insert(out, fetchhead_ref)); - - cl_git_pass(git_oid_fromstr(&oid, - "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1")); - cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, - "refs/heads/no-parent", - "https://github.com/libgit2/TestGitRepository")); - cl_git_pass(git_vector_insert(out, fetchhead_ref)); - - cl_git_pass(git_oid_fromstr(&oid, - "d96c4e80345534eccee5ac7b07fc7603b56124cb")); - cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, - "refs/tags/annotated_tag", - "https://github.com/libgit2/TestGitRepository")); - cl_git_pass(git_vector_insert(out, fetchhead_ref)); - - cl_git_pass(git_oid_fromstr(&oid, - "55a1a760df4b86a02094a904dfa511deb5655905")); - cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, - "refs/tags/blob", - "https://github.com/libgit2/TestGitRepository")); - cl_git_pass(git_vector_insert(out, fetchhead_ref)); - - cl_git_pass(git_oid_fromstr(&oid, - "8f50ba15d49353813cc6e20298002c0d17b0a9ee")); - cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, - "refs/tags/commit_tree", - "https://github.com/libgit2/TestGitRepository")); - cl_git_pass(git_vector_insert(out, fetchhead_ref)); - - cl_git_pass(git_fetchhead_write(repo, out)); -} - -void test_fetchhead_nonetwork__write(void) -{ - git_vector fetchhead_vector = GIT_VECTOR_INIT; - git_fetchhead_ref *fetchhead_ref; - git_str fetchhead_buf = GIT_STR_INIT; - int equals = 0; - size_t i; - - cl_git_pass(git_vector_init(&fetchhead_vector, 6, NULL)); - - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - populate_fetchhead(&fetchhead_vector, g_repo); - - cl_git_pass(git_futils_readbuffer(&fetchhead_buf, - "./test1/.git/FETCH_HEAD")); - - equals = (strcmp(fetchhead_buf.ptr, FETCH_HEAD_WILDCARD_DATA_LOCAL) == 0); - - git_str_dispose(&fetchhead_buf); - - git_vector_foreach(&fetchhead_vector, i, fetchhead_ref) { - git_fetchhead_ref_free(fetchhead_ref); - } - - git_vector_free(&fetchhead_vector); - - cl_assert(equals); -} - -typedef struct { - git_vector *fetchhead_vector; - size_t idx; -} fetchhead_ref_cb_data; - -static int fetchhead_ref_cb(const char *name, const char *url, - const git_oid *oid, unsigned int is_merge, void *payload) -{ - fetchhead_ref_cb_data *cb_data = payload; - git_fetchhead_ref *expected; - - cl_assert(payload); - - expected = git_vector_get(cb_data->fetchhead_vector, cb_data->idx); - - cl_assert_equal_oid(&expected->oid, oid); - cl_assert(expected->is_merge == is_merge); - - if (expected->ref_name) - cl_assert_equal_s(expected->ref_name, name); - else - cl_assert(name == NULL); - - if (expected->remote_url) - cl_assert_equal_s(expected->remote_url, url); - else - cl_assert(url == NULL); - - cb_data->idx++; - - return 0; -} - -void test_fetchhead_nonetwork__read(void) -{ - git_vector fetchhead_vector = GIT_VECTOR_INIT; - git_fetchhead_ref *fetchhead_ref; - fetchhead_ref_cb_data cb_data; - size_t i; - - memset(&cb_data, 0x0, sizeof(fetchhead_ref_cb_data)); - - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - populate_fetchhead(&fetchhead_vector, g_repo); - - cb_data.fetchhead_vector = &fetchhead_vector; - - cl_git_pass(git_repository_fetchhead_foreach(g_repo, fetchhead_ref_cb, &cb_data)); - - git_vector_foreach(&fetchhead_vector, i, fetchhead_ref) { - git_fetchhead_ref_free(fetchhead_ref); - } - - git_vector_free(&fetchhead_vector); -} - -static int read_old_style_cb(const char *name, const char *url, - const git_oid *oid, unsigned int is_merge, void *payload) -{ - git_oid expected; - - GIT_UNUSED(payload); - - git_oid_fromstr(&expected, "49322bb17d3acc9146f98c97d078513228bbf3c0"); - - cl_assert(name == NULL); - cl_assert(url == NULL); - cl_assert_equal_oid(&expected, oid); - cl_assert(is_merge == 1); - - return 0; -} - -void test_fetchhead_nonetwork__read_old_style(void) -{ - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\n"); - - cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_old_style_cb, NULL)); -} - -static int read_type_missing(const char *ref_name, const char *remote_url, - const git_oid *oid, unsigned int is_merge, void *payload) -{ - git_oid expected; - - GIT_UNUSED(payload); - - git_oid_fromstr(&expected, "49322bb17d3acc9146f98c97d078513228bbf3c0"); - - cl_assert_equal_s("name", ref_name); - cl_assert_equal_s("remote_url", remote_url); - cl_assert_equal_oid(&expected, oid); - cl_assert(is_merge == 0); - - return 0; -} - -void test_fetchhead_nonetwork__type_missing(void) -{ - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\t'name' of remote_url\n"); - - cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_type_missing, NULL)); -} - -static int read_name_missing(const char *ref_name, const char *remote_url, - const git_oid *oid, unsigned int is_merge, void *payload) -{ - git_oid expected; - - GIT_UNUSED(payload); - - git_oid_fromstr(&expected, "49322bb17d3acc9146f98c97d078513228bbf3c0"); - - cl_assert(ref_name == NULL); - cl_assert_equal_s("remote_url", remote_url); - cl_assert_equal_oid(&expected, oid); - cl_assert(is_merge == 0); - - return 0; -} - -void test_fetchhead_nonetwork__name_missing(void) -{ - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tremote_url\n"); - - cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_name_missing, NULL)); -} - -static int read_noop(const char *ref_name, const char *remote_url, - const git_oid *oid, unsigned int is_merge, void *payload) -{ - GIT_UNUSED(ref_name); - GIT_UNUSED(remote_url); - GIT_UNUSED(oid); - GIT_UNUSED(is_merge); - GIT_UNUSED(payload); - - return 0; -} - -void test_fetchhead_nonetwork__nonexistent(void) -{ - int error; - - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - cl_git_fail((error = git_repository_fetchhead_foreach(g_repo, read_noop, NULL))); - cl_assert(error == GIT_ENOTFOUND); -} - -void test_fetchhead_nonetwork__invalid_unterminated_last_line(void) -{ - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - cl_git_rewritefile("./test1/.git/FETCH_HEAD", "unterminated"); - cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); -} - -void test_fetchhead_nonetwork__invalid_oid(void) -{ - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - cl_git_rewritefile("./test1/.git/FETCH_HEAD", "shortoid\n"); - cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); -} - -void test_fetchhead_nonetwork__invalid_for_merge(void) -{ - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tinvalid-merge\t\n"); - cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); - - cl_assert(git__prefixcmp(git_error_last()->message, "invalid for-merge") == 0); -} - -void test_fetchhead_nonetwork__invalid_description(void) -{ - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\n"); - cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); - - cl_assert(git__prefixcmp(git_error_last()->message, "invalid description") == 0); -} - -static int assert_master_for_merge(const char *ref, const char *url, const git_oid *id, unsigned int is_merge, void *data) -{ - GIT_UNUSED(url); - GIT_UNUSED(id); - GIT_UNUSED(data); - - if (!strcmp("refs/heads/master", ref) && !is_merge) - return -1; - - return 0; -} - -static int assert_none_for_merge(const char *ref, const char *url, const git_oid *id, unsigned int is_merge, void *data) -{ - GIT_UNUSED(ref); - GIT_UNUSED(url); - GIT_UNUSED(id); - GIT_UNUSED(data); - - return is_merge ? -1 : 0; -} - -void test_fetchhead_nonetwork__unborn_with_upstream(void) -{ - git_repository *repo; - git_remote *remote; - - /* Create an empty repo to clone from */ - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - cl_set_cleanup(&cleanup_repository, "./repowithunborn"); - cl_git_pass(git_clone(&repo, "./test1", "./repowithunborn", NULL)); - - /* Simulate someone pushing to it by changing to one that has stuff */ - cl_git_pass(git_remote_set_url(repo, "origin", cl_fixture("testrepo.git"))); - cl_git_pass(git_remote_lookup(&remote, repo, "origin")); - - cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); - git_remote_free(remote); - - cl_git_pass(git_repository_fetchhead_foreach(repo, assert_master_for_merge, NULL)); - - git_repository_free(repo); - cl_fixture_cleanup("./repowithunborn"); -} - -void test_fetchhead_nonetwork__fetch_into_repo_with_symrefs(void) -{ - git_repository *repo; - git_remote *remote; - git_reference *symref; - - repo = cl_git_sandbox_init("empty_standard_repo"); - - /* - * Testing for a specific constellation where the repository has at - * least one symbolic reference in its refdb. - */ - cl_git_pass(git_reference_symbolic_create(&symref, repo, "refs/heads/symref", "refs/heads/master", 0, NULL)); - - cl_git_pass(git_remote_set_url(repo, "origin", cl_fixture("testrepo.git"))); - cl_git_pass(git_remote_lookup(&remote, repo, "origin")); - cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); - - git_remote_free(remote); - git_reference_free(symref); - cl_git_sandbox_cleanup(); -} - -void test_fetchhead_nonetwork__fetch_into_repo_with_invalid_head(void) -{ - git_remote *remote; - char *strings[] = { "refs/heads/*:refs/remotes/origin/*" }; - git_strarray refspecs = { strings, 1 }; - - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - /* HEAD pointing to nonexistent branch */ - cl_git_rewritefile("./test1/.git/HEAD", "ref: refs/heads/\n"); - - cl_git_pass(git_remote_create_anonymous(&remote, g_repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, NULL)); - cl_git_pass(git_repository_fetchhead_foreach(g_repo, assert_none_for_merge, NULL)); - - git_remote_free(remote); -} - -void test_fetchhead_nonetwork__quote_in_branch_name(void) -{ - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - cl_git_rewritefile("./test1/.git/FETCH_HEAD", FETCH_HEAD_QUOTE_DATA); - cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); -} - -static bool found_master; -static bool found_haacked; -static bool find_master_haacked_called; - -static int find_master_haacked(const char *ref_name, const char *remote_url, const git_oid *oid, unsigned int is_merge, void *payload) -{ - GIT_UNUSED(remote_url); - GIT_UNUSED(oid); - GIT_UNUSED(payload); - - find_master_haacked_called = true; - - if (!strcmp("refs/heads/master", ref_name)) { - cl_assert(is_merge); - found_master = true; - } - if (!strcmp("refs/heads/haacked", ref_name)) { - cl_assert(is_merge); - found_haacked = true; - } - - return 0; -} - -void test_fetchhead_nonetwork__create_when_refpecs_given(void) -{ - git_remote *remote; - git_str path = GIT_STR_INIT; - char *refspec1 = "refs/heads/master"; - char *refspec2 = "refs/heads/haacked"; - char *refspecs[] = { refspec1, refspec2 }; - git_strarray specs = { - refspecs, - 2, - }; - - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - cl_git_pass(git_str_joinpath(&path, git_repository_path(g_repo), "FETCH_HEAD")); - cl_git_pass(git_remote_create(&remote, g_repo, "origin", cl_fixture("testrepo.git"))); - - cl_assert(!git_fs_path_exists(path.ptr)); - cl_git_pass(git_remote_fetch(remote, &specs, NULL, NULL)); - cl_assert(git_fs_path_exists(path.ptr)); - - cl_git_pass(git_repository_fetchhead_foreach(g_repo, find_master_haacked, NULL)); - cl_assert(find_master_haacked_called); - cl_assert(found_master); - cl_assert(found_haacked); - - git_remote_free(remote); - git_str_dispose(&path); -} - -static bool count_refs_called; -struct prefix_count { - const char *prefix; - int count; - int expected; -}; - -static int count_refs(const char *ref_name, const char *remote_url, const git_oid *oid, unsigned int is_merge, void *payload) -{ - int i; - struct prefix_count *prefix_counts = (struct prefix_count *) payload; - - GIT_UNUSED(remote_url); - GIT_UNUSED(oid); - GIT_UNUSED(is_merge); - - count_refs_called = true; - - for (i = 0; prefix_counts[i].prefix; i++) { - if (!git__prefixcmp(ref_name, prefix_counts[i].prefix)) - prefix_counts[i].count++; - } - - return 0; -} - -void test_fetchhead_nonetwork__create_with_multiple_refspecs(void) -{ - git_remote *remote; - git_str path = GIT_STR_INIT; - - cl_set_cleanup(&cleanup_repository, "./test1"); - cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); - - cl_git_pass(git_remote_create(&remote, g_repo, "origin", cl_fixture("testrepo.git"))); - git_remote_free(remote); - cl_git_pass(git_remote_add_fetch(g_repo, "origin", "+refs/notes/*:refs/origin/notes/*")); - /* Pick up the new refspec */ - cl_git_pass(git_remote_lookup(&remote, g_repo, "origin")); - - cl_git_pass(git_str_joinpath(&path, git_repository_path(g_repo), "FETCH_HEAD")); - cl_assert(!git_fs_path_exists(path.ptr)); - cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); - cl_assert(git_fs_path_exists(path.ptr)); - - { - int i; - struct prefix_count prefix_counts[] = { - {"refs/notes/", 0, 1}, - {"refs/heads/", 0, 13}, - {"refs/tags/", 0, 7}, - {NULL, 0, 0}, - }; - - cl_git_pass(git_repository_fetchhead_foreach(g_repo, count_refs, &prefix_counts)); - cl_assert(count_refs_called); - for (i = 0; prefix_counts[i].prefix; i++) - cl_assert_equal_i(prefix_counts[i].expected, prefix_counts[i].count); - } - - git_remote_free(remote); - git_str_dispose(&path); -} - -void test_fetchhead_nonetwork__credentials_are_stripped(void) -{ - git_fetchhead_ref *ref; - git_oid oid; - - cl_git_pass(git_oid_fromstr(&oid, "49322bb17d3acc9146f98c97d078513228bbf3c0")); - cl_git_pass(git_fetchhead_ref_create(&ref, &oid, 0, - "refs/tags/commit_tree", "http://foo:bar@github.com/libgit2/TestGitRepository")); - cl_assert_equal_s(ref->remote_url, "http://github.com/libgit2/TestGitRepository"); - git_fetchhead_ref_free(ref); - - cl_git_pass(git_oid_fromstr(&oid, "49322bb17d3acc9146f98c97d078513228bbf3c0")); - cl_git_pass(git_fetchhead_ref_create(&ref, &oid, 0, - "refs/tags/commit_tree", "https://foo:bar@github.com/libgit2/TestGitRepository")); - cl_assert_equal_s(ref->remote_url, "https://github.com/libgit2/TestGitRepository"); - git_fetchhead_ref_free(ref); -} diff --git a/tests/filter/bare.c b/tests/filter/bare.c deleted file mode 100644 index 8402638ce..000000000 --- a/tests/filter/bare.c +++ /dev/null @@ -1,188 +0,0 @@ -#include "clar_libgit2.h" -#include "crlf.h" - -static git_repository *g_repo = NULL; -static git_blob_filter_options filter_opts = GIT_BLOB_FILTER_OPTIONS_INIT; - -void test_filter_bare__initialize(void) -{ - cl_fixture_sandbox("crlf.git"); - cl_git_pass(git_repository_open(&g_repo, "crlf.git")); - - filter_opts.flags |= GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES; - filter_opts.flags |= GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD; -} - -void test_filter_bare__cleanup(void) -{ - git_repository_free(g_repo); - cl_fixture_cleanup("crlf.git"); -} - -void test_filter_bare__all_crlf(void) -{ - git_blob *blob; - git_buf buf = { 0 }; - - cl_git_pass(git_revparse_single( - (git_object **)&blob, g_repo, "a9a2e89")); /* all-crlf */ - - cl_assert_equal_s(ALL_CRLF_TEXT_RAW, git_blob_rawcontent(blob)); - - cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &filter_opts)); - - cl_assert_equal_s(ALL_CRLF_TEXT_RAW, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &filter_opts)); - - /* in this case, raw content has crlf in it already */ - cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &filter_opts)); - - /* we never convert CRLF -> LF on platforms that have LF */ - cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "file.txt", &filter_opts)); - - /* in this case, raw content has crlf in it already */ - cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); - - git_buf_dispose(&buf); - git_blob_free(blob); -} - -void test_filter_bare__from_lf(void) -{ - git_blob *blob; - git_buf buf = { 0 }; - - cl_git_pass(git_revparse_single( - (git_object **)&blob, g_repo, "799770d")); /* all-lf */ - - cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); - - cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &filter_opts)); - - cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &filter_opts)); - - /* in this case, raw content has crlf in it already */ - cl_assert_equal_s(ALL_LF_TEXT_AS_CRLF, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &filter_opts)); - - /* we never convert CRLF -> LF on platforms that have LF */ - cl_assert_equal_s(ALL_LF_TEXT_AS_LF, buf.ptr); - - git_buf_dispose(&buf); - git_blob_free(blob); -} - -void test_filter_bare__nested_attributes(void) -{ - git_blob *blob; - git_buf buf = { 0 }; - - cl_git_pass(git_revparse_single( - (git_object **)&blob, g_repo, "799770d")); /* all-lf */ - - cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); - - cl_git_pass(git_blob_filter(&buf, blob, "raw/file.bin", &filter_opts)); - cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "raw/file.crlf", &filter_opts)); - cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "raw/file.lf", &filter_opts)); - cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); - - git_buf_dispose(&buf); - git_blob_free(blob); -} - -void test_filter_bare__sanitizes(void) -{ - git_blob *blob; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_revparse_single( - (git_object **)&blob, g_repo, "e69de29")); /* zero-byte */ - - cl_assert_equal_i(0, git_blob_rawsize(blob)); - cl_assert_equal_s("", git_blob_rawcontent(blob)); - - cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &filter_opts)); - cl_assert_equal_sz(0, buf.size); - cl_assert_equal_s("", buf.ptr); - git_buf_dispose(&buf); - - cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &filter_opts)); - cl_assert_equal_sz(0, buf.size); - cl_assert_equal_s("", buf.ptr); - git_buf_dispose(&buf); - - cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &filter_opts)); - cl_assert_equal_sz(0, buf.size); - cl_assert_equal_s("", buf.ptr); - git_buf_dispose(&buf); - - git_blob_free(blob); -} - -void test_filter_bare__from_specific_commit_one(void) -{ - git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; - git_blob *blob; - git_buf buf = { 0 }; - - opts.flags |= GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES; - opts.flags |= GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT; - cl_git_pass(git_oid_fromstr(&opts.attr_commit_id, "b8986fec0f7bde90f78ac72706e782d82f24f2f0")); - - cl_git_pass(git_revparse_single( - (git_object **)&blob, g_repo, "055c872")); /* ident */ - - cl_assert_equal_s("$Id$\n", git_blob_rawcontent(blob)); - - cl_git_pass(git_blob_filter(&buf, blob, "ident.bin", &opts)); - cl_assert_equal_s("$Id$\n", buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "ident.identlf", &opts)); - cl_assert_equal_s("$Id: 055c8729cdcc372500a08db659c045e16c4409fb $\n", buf.ptr); - - git_buf_dispose(&buf); - git_blob_free(blob); -} - -void test_filter_bare__from_specific_commit_with_no_attributes_file(void) -{ - git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; - git_blob *blob; - git_buf buf = { 0 }; - - opts.flags |= GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES; - opts.flags |= GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT; - cl_git_pass(git_oid_fromstr(&opts.attr_commit_id, "5afb6a14a864e30787857dd92af837e8cdd2cb1b")); - - cl_git_pass(git_revparse_single( - (git_object **)&blob, g_repo, "799770d")); /* all-lf */ - - cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); - - cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &opts)); - cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); - - /* we never convert CRLF -> LF on platforms that have LF */ - cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &opts)); - cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); - - /* we never convert CRLF -> LF on platforms that have LF */ - cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &opts)); - cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); - - git_buf_dispose(&buf); - git_blob_free(blob); -} diff --git a/tests/filter/blob.c b/tests/filter/blob.c deleted file mode 100644 index 1cc33d42f..000000000 --- a/tests/filter/blob.c +++ /dev/null @@ -1,145 +0,0 @@ -#include "clar_libgit2.h" -#include "crlf.h" - -static git_repository *g_repo = NULL; - -void test_filter_blob__initialize(void) -{ - g_repo = cl_git_sandbox_init("crlf"); - cl_git_mkfile("crlf/.gitattributes", - "*.txt text\n*.bin binary\n" - "*.crlf text eol=crlf\n" - "*.lf text eol=lf\n" - "*.ident text ident\n" - "*.identcrlf ident text eol=crlf\n" - "*.identlf ident text eol=lf\n"); -} - -void test_filter_blob__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_filter_blob__all_crlf(void) -{ - git_blob *blob; - git_buf buf = { 0 }; - - cl_git_pass(git_revparse_single( - (git_object **)&blob, g_repo, "a9a2e891")); /* all-crlf */ - - cl_assert_equal_s(ALL_CRLF_TEXT_RAW, git_blob_rawcontent(blob)); - - cl_git_pass(git_blob_filter(&buf, blob, "file.bin", NULL)); - - cl_assert_equal_s(ALL_CRLF_TEXT_RAW, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", NULL)); - - /* in this case, raw content has crlf in it already */ - cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "file.lf", NULL)); - - /* we never convert CRLF -> LF on platforms that have LF */ - cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); - - git_buf_dispose(&buf); - git_blob_free(blob); -} - -void test_filter_blob__from_lf(void) -{ - git_blob *blob; - git_buf buf = { 0 }; - - cl_git_pass(git_revparse_single( - (git_object **)&blob, g_repo, "799770d")); /* all-lf */ - - cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); - - cl_git_pass(git_blob_filter(&buf, blob, "file.bin", NULL)); - - cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", NULL)); - - /* in this case, raw content has crlf in it already */ - cl_assert_equal_s(ALL_LF_TEXT_AS_CRLF, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "file.lf", NULL)); - - /* we never convert CRLF -> LF on platforms that have LF */ - cl_assert_equal_s(ALL_LF_TEXT_AS_LF, buf.ptr); - - git_buf_dispose(&buf); - git_blob_free(blob); -} - -void test_filter_blob__sanitizes(void) -{ - git_blob *blob; - git_buf buf; - - cl_git_pass(git_revparse_single( - (git_object **)&blob, g_repo, "e69de29")); /* zero-byte */ - - cl_assert_equal_i(0, git_blob_rawsize(blob)); - cl_assert_equal_s("", git_blob_rawcontent(blob)); - - memset(&buf, 0, sizeof(git_buf)); - cl_git_pass(git_blob_filter(&buf, blob, "file.bin", NULL)); - cl_assert_equal_sz(0, buf.size); - cl_assert_equal_s("", buf.ptr); - git_buf_dispose(&buf); - - memset(&buf, 0, sizeof(git_buf)); - cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", NULL)); - cl_assert_equal_sz(0, buf.size); - cl_assert_equal_s("", buf.ptr); - git_buf_dispose(&buf); - - memset(&buf, 0, sizeof(git_buf)); - cl_git_pass(git_blob_filter(&buf, blob, "file.lf", NULL)); - cl_assert_equal_sz(0, buf.size); - cl_assert_equal_s("", buf.ptr); - git_buf_dispose(&buf); - - git_blob_free(blob); -} - -void test_filter_blob__ident(void) -{ - git_oid id; - git_blob *blob; - git_buf buf = { 0 }; - - cl_git_mkfile("crlf/test.ident", "Some text\n$Id$\nGoes there\n"); - cl_git_pass(git_blob_create_from_workdir(&id, g_repo, "test.ident")); - cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); - cl_assert_equal_s( - "Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob)); - git_blob_free(blob); - - cl_git_mkfile("crlf/test.ident", "Some text\n$Id: Any old just you want$\nGoes there\n"); - cl_git_pass(git_blob_create_from_workdir(&id, g_repo, "test.ident")); - cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); - cl_assert_equal_s( - "Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob)); - - cl_git_pass(git_blob_filter(&buf, blob, "filter.bin", NULL)); - cl_assert_equal_s( - "Some text\n$Id$\nGoes there\n", buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "filter.identcrlf", NULL)); - cl_assert_equal_s( - "Some text\r\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845 $\r\nGoes there\r\n", buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "filter.identlf", NULL)); - cl_assert_equal_s( - "Some text\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845 $\nGoes there\n", buf.ptr); - - git_buf_dispose(&buf); - git_blob_free(blob); - -} diff --git a/tests/filter/crlf.c b/tests/filter/crlf.c deleted file mode 100644 index 925ea58d2..000000000 --- a/tests/filter/crlf.c +++ /dev/null @@ -1,253 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/filter.h" - -static git_repository *g_repo = NULL; - -void test_filter_crlf__initialize(void) -{ - g_repo = cl_git_sandbox_init("crlf"); - - cl_git_mkfile("crlf/.gitattributes", - "*.txt text\n*.bin binary\n*.crlf text eol=crlf\n*.lf text eol=lf\n"); - - cl_repo_set_bool(g_repo, "core.autocrlf", true); -} - -void test_filter_crlf__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_filter_crlf__to_worktree(void) -{ - git_filter_list *fl; - git_filter *crlf; - git_buf out = GIT_BUF_INIT; - const char *in; - size_t in_len; - - cl_git_pass(git_filter_list_new( - &fl, g_repo, GIT_FILTER_TO_WORKTREE, 0)); - - crlf = git_filter_lookup(GIT_FILTER_CRLF); - cl_assert(crlf != NULL); - - cl_git_pass(git_filter_list_push(fl, crlf, NULL)); - - in = "Some text\nRight here\n"; - in_len = strlen(in); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - - cl_assert_equal_s("Some text\r\nRight here\r\n", out.ptr); - - git_filter_list_free(fl); - git_buf_dispose(&out); -} - -void test_filter_crlf__to_odb(void) -{ - git_filter_list *fl; - git_filter *crlf; - git_buf out = GIT_BUF_INIT; - const char *in; - size_t in_len; - - cl_git_pass(git_filter_list_new( - &fl, g_repo, GIT_FILTER_TO_ODB, 0)); - - crlf = git_filter_lookup(GIT_FILTER_CRLF); - cl_assert(crlf != NULL); - - cl_git_pass(git_filter_list_push(fl, crlf, NULL)); - - in = "Some text\r\nRight here\r\n"; - in_len = strlen(in); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - - cl_assert_equal_s("Some text\nRight here\n", out.ptr); - - git_filter_list_free(fl); - git_buf_dispose(&out); -} - -void test_filter_crlf__with_safecrlf(void) -{ - git_filter_list *fl; - git_filter *crlf; - git_buf out = GIT_BUF_INIT; - const char *in; - size_t in_len; - - cl_repo_set_bool(g_repo, "core.safecrlf", true); - - cl_git_pass(git_filter_list_new( - &fl, g_repo, GIT_FILTER_TO_ODB, 0)); - - crlf = git_filter_lookup(GIT_FILTER_CRLF); - cl_assert(crlf != NULL); - - cl_git_pass(git_filter_list_push(fl, crlf, NULL)); - - /* Normalized \r\n succeeds with safecrlf */ - in = "Normal\r\nCRLF\r\nline-endings.\r\n"; - in_len = strlen(in); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); - - /* Mix of line endings fails with safecrlf */ - in = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; - in_len = strlen(in); - - cl_git_fail(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - cl_assert_equal_i(git_error_last()->klass, GIT_ERROR_FILTER); - - /* Normalized \n fails for autocrlf=true when safecrlf=true */ - in = "Normal\nLF\nonly\nline-endings.\n"; - in_len = strlen(in); - - cl_git_fail(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - cl_assert_equal_i(git_error_last()->klass, GIT_ERROR_FILTER); - - /* String with \r but without \r\n does not fail with safecrlf */ - in = "Normal\nCR only\rand some more\nline-endings.\n"; - in_len = strlen(in); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - cl_assert_equal_s("Normal\nCR only\rand some more\nline-endings.\n", out.ptr); - - git_filter_list_free(fl); - git_buf_dispose(&out); -} - -void test_filter_crlf__with_safecrlf_and_unsafe_allowed(void) -{ - git_filter_list *fl; - git_filter *crlf; - git_buf out = GIT_BUF_INIT; - const char *in; - size_t in_len; - - cl_repo_set_bool(g_repo, "core.safecrlf", true); - - cl_git_pass(git_filter_list_new( - &fl, g_repo, GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)); - - crlf = git_filter_lookup(GIT_FILTER_CRLF); - cl_assert(crlf != NULL); - - cl_git_pass(git_filter_list_push(fl, crlf, NULL)); - - /* Normalized \r\n succeeds with safecrlf */ - in = "Normal\r\nCRLF\r\nline-endings.\r\n"; - in_len = strlen(in); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); - - /* Mix of line endings fails with safecrlf, but allowed to pass */ - in = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; - in_len = strlen(in); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - /* TODO: check for warning */ - cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr); - - /* Normalized \n fails with safecrlf, but allowed to pass */ - in = "Normal\nLF\nonly\nline-endings.\n"; - in_len = strlen(in); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - /* TODO: check for warning */ - cl_assert_equal_s("Normal\nLF\nonly\nline-endings.\n", out.ptr); - - git_filter_list_free(fl); - git_buf_dispose(&out); -} - -void test_filter_crlf__no_safecrlf(void) -{ - git_filter_list *fl; - git_filter *crlf; - git_buf out = GIT_BUF_INIT; - const char *in; - size_t in_len; - - cl_git_pass(git_filter_list_new( - &fl, g_repo, GIT_FILTER_TO_ODB, 0)); - - crlf = git_filter_lookup(GIT_FILTER_CRLF); - cl_assert(crlf != NULL); - - cl_git_pass(git_filter_list_push(fl, crlf, NULL)); - - /* Normalized \r\n succeeds with safecrlf */ - in = "Normal\r\nCRLF\r\nline-endings.\r\n"; - in_len = strlen(in); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); - - /* Mix of line endings fails with safecrlf */ - in = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; - in_len = strlen(in); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr); - - /* Normalized \n fails with safecrlf */ - in = "Normal\nLF\nonly\nline-endings.\n"; - in_len = strlen(in); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - cl_assert_equal_s("Normal\nLF\nonly\nline-endings.\n", out.ptr); - - git_filter_list_free(fl); - git_buf_dispose(&out); -} - -void test_filter_crlf__safecrlf_warn(void) -{ - git_filter_list *fl; - git_filter *crlf; - git_buf out = GIT_BUF_INIT; - const char *in; - size_t in_len; - - cl_repo_set_string(g_repo, "core.safecrlf", "warn"); - - cl_git_pass(git_filter_list_new( - &fl, g_repo, GIT_FILTER_TO_ODB, 0)); - - crlf = git_filter_lookup(GIT_FILTER_CRLF); - cl_assert(crlf != NULL); - - cl_git_pass(git_filter_list_push(fl, crlf, NULL)); - - /* Normalized \r\n succeeds with safecrlf=warn */ - in = "Normal\r\nCRLF\r\nline-endings.\r\n"; - in_len = strlen(in); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); - - /* Mix of line endings succeeds with safecrlf=warn */ - in = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; - in_len = strlen(in); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - /* TODO: check for warning */ - cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr); - - /* Normalized \n is reversible, so does not fail with safecrlf=warn */ - in = "Normal\nLF\nonly\nline-endings.\n"; - in_len = strlen(in); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - cl_assert_equal_s(in, out.ptr); - - git_filter_list_free(fl); - git_buf_dispose(&out); -} diff --git a/tests/filter/crlf.h b/tests/filter/crlf.h deleted file mode 100644 index 786edfc96..000000000 --- a/tests/filter/crlf.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef INCLUDE_filter_crlf_h__ -#define INCLUDE_filter_crlf_h__ - -/* - * file content for files in the resources/crlf repository - */ - -#define UTF8_BOM "\xEF\xBB\xBF" - -#define ALL_CRLF_TEXT_RAW "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n" -#define ALL_LF_TEXT_RAW "lf\nlf\nlf\nlf\nlf\n" -#define MORE_CRLF_TEXT_RAW "crlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf\r\n" -#define MORE_LF_TEXT_RAW "lf\nlf\ncrlf\r\nlf\nlf\n" - -#define ALL_CRLF_TEXT_AS_CRLF ALL_CRLF_TEXT_RAW -#define ALL_LF_TEXT_AS_CRLF "lf\r\nlf\r\nlf\r\nlf\r\nlf\r\n" -#define MORE_CRLF_TEXT_AS_CRLF "crlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf\r\n" -#define MORE_LF_TEXT_AS_CRLF "lf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\n" - -#define ALL_CRLF_TEXT_AS_LF "crlf\ncrlf\ncrlf\ncrlf\n" -#define ALL_LF_TEXT_AS_LF ALL_LF_TEXT_RAW -#define MORE_CRLF_TEXT_AS_LF "crlf\ncrlf\nlf\ncrlf\ncrlf\n" -#define MORE_LF_TEXT_AS_LF "lf\nlf\ncrlf\nlf\nlf\n" - -#define FEW_UTF8_CRLF_RAW "\xe2\x9a\xbdThe rest is ASCII01.\r\nThe rest is ASCII02.\r\nThe rest is ASCII03.\r\nThe rest is ASCII04.\r\nThe rest is ASCII05.\r\nThe rest is ASCII06.\r\nThe rest is ASCII07.\r\nThe rest is ASCII08.\r\nThe rest is ASCII09.\r\nThe rest is ASCII10.\r\nThe rest is ASCII11.\r\nThe rest is ASCII12.\r\nThe rest is ASCII13.\r\nThe rest is ASCII14.\r\nThe rest is ASCII15.\r\nThe rest is ASCII16.\r\nThe rest is ASCII17.\r\nThe rest is ASCII18.\r\nThe rest is ASCII19.\r\nThe rest is ASCII20.\r\nThe rest is ASCII21.\r\nThe rest is ASCII22.\r\n" -#define FEW_UTF8_LF_RAW "\xe2\x9a\xbdThe rest is ASCII01.\nThe rest is ASCII02.\nThe rest is ASCII03.\nThe rest is ASCII04.\nThe rest is ASCII05.\nThe rest is ASCII06.\nThe rest is ASCII07.\nThe rest is ASCII08.\nThe rest is ASCII09.\nThe rest is ASCII10.\nThe rest is ASCII11.\nThe rest is ASCII12.\nThe rest is ASCII13.\nThe rest is ASCII14.\nThe rest is ASCII15.\nThe rest is ASCII16.\nThe rest is ASCII17.\nThe rest is ASCII18.\nThe rest is ASCII19.\nThe rest is ASCII20.\nThe rest is ASCII21.\nThe rest is ASCII22.\n" -#define MANY_UTF8_CRLF_RAW "Lets sing!\r\n\xe2\x99\xab\xe2\x99\xaa\xe2\x99\xac\xe2\x99\xa9\r\nEat food\r\n\xf0\x9f\x8d\x85\xf0\x9f\x8d\x95\r\n" -#define MANY_UTF8_LF_RAW "Lets sing!\n\xe2\x99\xab\xe2\x99\xaa\xe2\x99\xac\xe2\x99\xa9\nEat food\n\xf0\x9f\x8d\x85\xf0\x9f\x8d\x95\n" - -#endif diff --git a/tests/filter/custom.c b/tests/filter/custom.c deleted file mode 100644 index 57cef33ce..000000000 --- a/tests/filter/custom.c +++ /dev/null @@ -1,268 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "blob.h" -#include "filter.h" -#include "git2/sys/filter.h" -#include "git2/sys/repository.h" -#include "custom_helpers.h" - -/* going TO_WORKDIR, filters are executed low to high - * going TO_ODB, filters are executed high to low - */ -#define BITFLIP_FILTER_PRIORITY -1 -#define REVERSE_FILTER_PRIORITY -2 - -#ifdef GIT_WIN32 -# define NEWLINE "\r\n" -#else -# define NEWLINE "\n" -#endif - -static char workdir_data[] = - "some simple" NEWLINE - "data" NEWLINE - "that will be" NEWLINE - "trivially" NEWLINE - "scrambled." NEWLINE; - -#define REVERSED_DATA_LEN 51 - -/* Represents the data above scrambled (bits flipped) after \r\n -> \n - * conversion, then bytewise reversed - */ -static unsigned char bitflipped_and_reversed_data[] = - { 0xf5, 0xd1, 0x9b, 0x9a, 0x93, 0x9d, 0x92, 0x9e, 0x8d, 0x9c, 0x8c, - 0xf5, 0x86, 0x93, 0x93, 0x9e, 0x96, 0x89, 0x96, 0x8d, 0x8b, 0xf5, - 0x9a, 0x9d, 0xdf, 0x93, 0x93, 0x96, 0x88, 0xdf, 0x8b, 0x9e, 0x97, - 0x8b, 0xf5, 0x9e, 0x8b, 0x9e, 0x9b, 0xf5, 0x9a, 0x93, 0x8f, 0x92, - 0x96, 0x8c, 0xdf, 0x9a, 0x92, 0x90, 0x8c }; - -#define BITFLIPPED_AND_REVERSED_DATA_LEN 51 - -static git_repository *g_repo = NULL; - -static void register_custom_filters(void); - -void test_filter_custom__initialize(void) -{ - register_custom_filters(); - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_mkfile( - "empty_standard_repo/.gitattributes", - "hero* bitflip reverse\n" - "herofile text\n" - "heroflip -reverse binary\n" - "villain erroneous\n" - "*.bin binary\n"); -} - -void test_filter_custom__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -static void register_custom_filters(void) -{ - static int filters_registered = 0; - - if (!filters_registered) { - cl_git_pass(git_filter_register( - "bitflip", create_bitflip_filter(), BITFLIP_FILTER_PRIORITY)); - - cl_git_pass(git_filter_register( - "reverse", create_reverse_filter("+reverse"), - REVERSE_FILTER_PRIORITY)); - - /* re-register reverse filter with standard filter=xyz priority */ - cl_git_pass(git_filter_register( - "pre-reverse", - create_reverse_filter("+prereverse"), - GIT_FILTER_DRIVER_PRIORITY)); - - cl_git_pass(git_filter_register( - "erroneous", - create_erroneous_filter("+erroneous"), - GIT_FILTER_DRIVER_PRIORITY)); - - filters_registered = 1; - } -} - -void test_filter_custom__to_odb(void) -{ - git_filter_list *fl; - git_buf out = GIT_BUF_INIT; - const char *in; - size_t in_len; - - cl_git_pass(git_filter_list_load( - &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_ODB, 0)); - - in = workdir_data; - in_len = strlen(workdir_data); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - - cl_assert_equal_i(BITFLIPPED_AND_REVERSED_DATA_LEN, out.size); - - cl_assert_equal_i( - 0, memcmp(bitflipped_and_reversed_data, out.ptr, out.size)); - - git_filter_list_free(fl); - git_buf_dispose(&out); -} - -void test_filter_custom__to_workdir(void) -{ - git_filter_list *fl; - git_buf out = GIT_BUF_INIT; - const char *in; - size_t in_len; - - cl_git_pass(git_filter_list_load( - &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_WORKTREE, 0)); - - in = (char *)bitflipped_and_reversed_data; - in_len = BITFLIPPED_AND_REVERSED_DATA_LEN; - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); - - cl_assert_equal_i(strlen(workdir_data), out.size); - - cl_assert_equal_i( - 0, memcmp(workdir_data, out.ptr, out.size)); - - git_filter_list_free(fl); - git_buf_dispose(&out); -} - -void test_filter_custom__can_register_a_custom_filter_in_the_repository(void) -{ - git_filter_list *fl; - - cl_git_pass(git_filter_list_load( - &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_WORKTREE, 0)); - /* expect: bitflip, reverse, crlf */ - cl_assert_equal_sz(3, git_filter_list_length(fl)); - git_filter_list_free(fl); - - cl_git_pass(git_filter_list_load( - &fl, g_repo, NULL, "herocorp", GIT_FILTER_TO_WORKTREE, 0)); - /* expect: bitflip, reverse - possibly crlf depending on global config */ - { - size_t flen = git_filter_list_length(fl); - cl_assert(flen == 2 || flen == 3); - } - git_filter_list_free(fl); - - cl_git_pass(git_filter_list_load( - &fl, g_repo, NULL, "hero.bin", GIT_FILTER_TO_WORKTREE, 0)); - /* expect: bitflip, reverse */ - cl_assert_equal_sz(2, git_filter_list_length(fl)); - git_filter_list_free(fl); - - cl_git_pass(git_filter_list_load( - &fl, g_repo, NULL, "heroflip", GIT_FILTER_TO_WORKTREE, 0)); - /* expect: bitflip (because of -reverse) */ - cl_assert_equal_sz(1, git_filter_list_length(fl)); - git_filter_list_free(fl); - - cl_git_pass(git_filter_list_load( - &fl, g_repo, NULL, "doesntapplytome.bin", - GIT_FILTER_TO_WORKTREE, 0)); - /* expect: none */ - cl_assert_equal_sz(0, git_filter_list_length(fl)); - git_filter_list_free(fl); -} - -void test_filter_custom__order_dependency(void) -{ - git_index *index; - git_blob *blob; - git_buf buf = { 0 }; - - /* so if ident and reverse are used together, an interesting thing - * happens - a reversed "$Id$" string is no longer going to trigger - * ident correctly. When checking out, the filters should be applied - * in order CLRF, then ident, then reverse, so ident expansion should - * work correctly. On check in, the content should be reversed, then - * ident, then CRLF filtered. Let's make sure that works... - */ - - cl_git_mkfile( - "empty_standard_repo/.gitattributes", - "hero.*.rev-ident text ident prereverse eol=lf\n"); - - cl_git_mkfile( - "empty_standard_repo/hero.1.rev-ident", - "This is a test\n$Id$\nHave fun!\n"); - - cl_git_mkfile( - "empty_standard_repo/hero.2.rev-ident", - "Another test\n$dI$\nCrazy!\n"); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_add_bypath(index, "hero.1.rev-ident")); - cl_git_pass(git_index_add_bypath(index, "hero.2.rev-ident")); - cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "Filter chains\n"); - git_index_free(index); - - cl_git_pass(git_blob_lookup(&blob, g_repo, - & git_index_get_bypath(index, "hero.1.rev-ident", 0)->id)); - cl_assert_equal_s( - "\n!nuf evaH\n$dI$\ntset a si sihT", git_blob_rawcontent(blob)); - cl_git_pass(git_blob_filter(&buf, blob, "hero.1.rev-ident", NULL)); - /* no expansion because id was reversed at checkin and now at ident - * time, reverse is not applied yet */ - cl_assert_equal_s( - "This is a test\n$Id$\nHave fun!\n", buf.ptr); - git_blob_free(blob); - - cl_git_pass(git_blob_lookup(&blob, g_repo, - & git_index_get_bypath(index, "hero.2.rev-ident", 0)->id)); - cl_assert_equal_s( - "\n!yzarC\n$Id$\ntset rehtonA", git_blob_rawcontent(blob)); - cl_git_pass(git_blob_filter(&buf, blob, "hero.2.rev-ident", NULL)); - /* expansion because reverse was applied at checkin and at ident time, - * reverse is not applied yet */ - cl_assert_equal_s( - "Another test\n$ 59001fe193103b1016b27027c0c827d036fd0ac8 :dI$\nCrazy!\n", buf.ptr); - cl_assert_equal_i(0, git_oid_strcmp( - git_blob_id(blob), "8ca0df630d728c0c72072b6101b301391ef10095")); - git_blob_free(blob); - - git_buf_dispose(&buf); -} - -void test_filter_custom__filter_registry_failure_cases(void) -{ - git_filter fake = { GIT_FILTER_VERSION, 0 }; - - cl_assert_equal_i(GIT_EEXISTS, git_filter_register("bitflip", &fake, 0)); - - cl_git_fail(git_filter_unregister(GIT_FILTER_CRLF)); - cl_git_fail(git_filter_unregister(GIT_FILTER_IDENT)); - cl_assert_equal_i(GIT_ENOTFOUND, git_filter_unregister("not-a-filter")); -} - -void test_filter_custom__erroneous_filter_fails(void) -{ - git_filter_list *filters; - git_buf out = GIT_BUF_INIT; - const char *in; - size_t in_len; - - cl_git_pass(git_filter_list_load( - &filters, g_repo, NULL, "villain", GIT_FILTER_TO_WORKTREE, 0)); - - in = workdir_data; - in_len = strlen(workdir_data); - - cl_git_fail(git_filter_list_apply_to_buffer(&out, filters, in, in_len)); - - git_filter_list_free(filters); - git_buf_dispose(&out); -} diff --git a/tests/filter/custom_helpers.c b/tests/filter/custom_helpers.c deleted file mode 100644 index 95a9f978e..000000000 --- a/tests/filter/custom_helpers.c +++ /dev/null @@ -1,163 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "filter.h" -#include "git2/sys/filter.h" -#include "custom_helpers.h" - -#define VERY_SECURE_ENCRYPTION(b) ((b) ^ 0xff) - -int bitflip_filter_apply( - git_filter *self, - void **payload, - git_str *to, - const git_str *from, - const git_filter_source *source) -{ - const unsigned char *src = (const unsigned char *)from->ptr; - unsigned char *dst; - size_t i; - - GIT_UNUSED(self); GIT_UNUSED(payload); - - /* verify that attribute path match worked as expected */ - cl_assert_equal_i( - 0, git__strncmp("hero", git_filter_source_path(source), 4)); - - if (!from->size) - return 0; - - cl_git_pass(git_str_grow(to, from->size)); - - dst = (unsigned char *)to->ptr; - - for (i = 0; i < from->size; i++) - dst[i] = VERY_SECURE_ENCRYPTION(src[i]); - - to->size = from->size; - - return 0; -} - -static int bitflip_filter_stream( - git_writestream **out, - git_filter *self, - void **payload, - const git_filter_source *src, - git_writestream *next) -{ - return git_filter_buffered_stream_new(out, - self, bitflip_filter_apply, NULL, payload, src, next); -} - -static void bitflip_filter_free(git_filter *f) -{ - git__free(f); -} - -git_filter *create_bitflip_filter(void) -{ - git_filter *filter = git__calloc(1, sizeof(git_filter)); - cl_assert(filter); - - filter->version = GIT_FILTER_VERSION; - filter->attributes = "+bitflip"; - filter->shutdown = bitflip_filter_free; - filter->stream = bitflip_filter_stream; - - return filter; -} - - -int reverse_filter_apply( - git_filter *self, - void **payload, - git_str *to, - const git_str *from, - const git_filter_source *source) -{ - const unsigned char *src = (const unsigned char *)from->ptr; - const unsigned char *end = src + from->size; - unsigned char *dst; - - GIT_UNUSED(self); GIT_UNUSED(payload); GIT_UNUSED(source); - - /* verify that attribute path match worked as expected */ - cl_assert_equal_i( - 0, git__strncmp("hero", git_filter_source_path(source), 4)); - - if (!from->size) - return 0; - - cl_git_pass(git_str_grow(to, from->size)); - - dst = (unsigned char *)to->ptr + from->size - 1; - - while (src < end) - *dst-- = *src++; - - to->size = from->size; - - return 0; -} - -static int reverse_filter_stream( - git_writestream **out, - git_filter *self, - void **payload, - const git_filter_source *src, - git_writestream *next) -{ - return git_filter_buffered_stream_new(out, - self, reverse_filter_apply, NULL, payload, src, next); -} - -static void reverse_filter_free(git_filter *f) -{ - git__free(f); -} - -git_filter *create_reverse_filter(const char *attrs) -{ - git_filter *filter = git__calloc(1, sizeof(git_filter)); - cl_assert(filter); - - filter->version = GIT_FILTER_VERSION; - filter->attributes = attrs; - filter->shutdown = reverse_filter_free; - filter->stream = reverse_filter_stream; - - return filter; -} - -static int erroneous_filter_stream( - git_writestream **out, - git_filter *self, - void **payload, - const git_filter_source *src, - git_writestream *next) -{ - GIT_UNUSED(out); - GIT_UNUSED(self); - GIT_UNUSED(payload); - GIT_UNUSED(src); - GIT_UNUSED(next); - return -1; -} - -static void erroneous_filter_free(git_filter *f) -{ - git__free(f); -} - -git_filter *create_erroneous_filter(const char *attrs) -{ - git_filter *filter = git__calloc(1, sizeof(git_filter)); - cl_assert(filter); - - filter->version = GIT_FILTER_VERSION; - filter->attributes = attrs; - filter->stream = erroneous_filter_stream; - filter->shutdown = erroneous_filter_free; - - return filter; -} diff --git a/tests/filter/custom_helpers.h b/tests/filter/custom_helpers.h deleted file mode 100644 index 0936c581b..000000000 --- a/tests/filter/custom_helpers.h +++ /dev/null @@ -1,19 +0,0 @@ -#include "git2/sys/filter.h" - -extern git_filter *create_bitflip_filter(void); -extern git_filter *create_reverse_filter(const char *attr); -extern git_filter *create_erroneous_filter(const char *attr); - -extern int bitflip_filter_apply( - git_filter *self, - void **payload, - git_str *to, - const git_str *from, - const git_filter_source *source); - -extern int reverse_filter_apply( - git_filter *self, - void **payload, - git_str *to, - const git_str *from, - const git_filter_source *source); diff --git a/tests/filter/file.c b/tests/filter/file.c deleted file mode 100644 index 14b33bab9..000000000 --- a/tests/filter/file.c +++ /dev/null @@ -1,98 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/filter.h" -#include "crlf.h" - -static git_repository *g_repo = NULL; - -void test_filter_file__initialize(void) -{ - git_reference *head_ref; - git_commit *head; - - g_repo = cl_git_sandbox_init("crlf"); - - cl_git_mkfile("crlf/.gitattributes", - "*.txt text\n*.bin binary\n*.crlf text eol=crlf\n*.lf text eol=lf\n"); - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - cl_git_pass(git_repository_head(&head_ref, g_repo)); - cl_git_pass(git_reference_peel((git_object **)&head, head_ref, GIT_OBJECT_COMMIT)); - cl_git_pass(git_reset(g_repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_commit_free(head); - git_reference_free(head_ref); -} - -void test_filter_file__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_filter_file__apply(void) -{ - git_filter_list *fl; - git_filter *crlf; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_filter_list_new( - &fl, g_repo, GIT_FILTER_TO_ODB, 0)); - - crlf = git_filter_lookup(GIT_FILTER_CRLF); - cl_assert(crlf != NULL); - - cl_git_pass(git_filter_list_push(fl, crlf, NULL)); - - cl_git_pass(git_filter_list_apply_to_file(&buf, fl, g_repo, "all-crlf")); - cl_assert_equal_s("crlf\ncrlf\ncrlf\ncrlf\n", buf.ptr); - - git_buf_dispose(&buf); - git_filter_list_free(fl); -} - -struct buf_writestream { - git_writestream base; - git_str buf; -}; - -static int buf_writestream_write(git_writestream *s, const char *buf, size_t len) -{ - struct buf_writestream *stream = (struct buf_writestream *)s; - return git_str_put(&stream->buf, buf, len); -} - -static int buf_writestream_close(git_writestream *s) -{ - GIT_UNUSED(s); - return 0; -} - -static void buf_writestream_free(git_writestream *s) -{ - struct buf_writestream *stream = (struct buf_writestream *)s; - git_str_dispose(&stream->buf); -} - -void test_filter_file__apply_stream(void) -{ - git_filter_list *fl; - git_filter *crlf; - struct buf_writestream write_target = { { - buf_writestream_write, - buf_writestream_close, - buf_writestream_free } }; - - cl_git_pass(git_filter_list_new( - &fl, g_repo, GIT_FILTER_TO_ODB, 0)); - - crlf = git_filter_lookup(GIT_FILTER_CRLF); - cl_assert(crlf != NULL); - - cl_git_pass(git_filter_list_push(fl, crlf, NULL)); - - cl_git_pass(git_filter_list_stream_file(fl, g_repo, "all-crlf", &write_target.base)); - cl_assert_equal_s("crlf\ncrlf\ncrlf\ncrlf\n", write_target.buf.ptr); - - git_filter_list_free(fl); - write_target.base.free(&write_target.base); -} diff --git a/tests/filter/ident.c b/tests/filter/ident.c deleted file mode 100644 index ed2e4677f..000000000 --- a/tests/filter/ident.c +++ /dev/null @@ -1,133 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/filter.h" - -static git_repository *g_repo = NULL; - -void test_filter_ident__initialize(void) -{ - g_repo = cl_git_sandbox_init("crlf"); -} - -void test_filter_ident__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void add_blob_and_filter( - const char *data, - git_filter_list *fl, - const char *expected) -{ - git_oid id; - git_blob *blob; - git_buf out = { 0 }; - - cl_git_mkfile("crlf/identtest", data); - cl_git_pass(git_blob_create_from_workdir(&id, g_repo, "identtest")); - cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); - - cl_git_pass(git_filter_list_apply_to_blob(&out, fl, blob)); - - cl_assert_equal_s(expected, out.ptr); - - git_blob_free(blob); - git_buf_dispose(&out); -} - -void test_filter_ident__to_worktree(void) -{ - git_filter_list *fl; - git_filter *ident; - - cl_git_pass(git_filter_list_new( - &fl, g_repo, GIT_FILTER_TO_WORKTREE, 0)); - - ident = git_filter_lookup(GIT_FILTER_IDENT); - cl_assert(ident != NULL); - - cl_git_pass(git_filter_list_push(fl, ident, NULL)); - - add_blob_and_filter( - "Hello\n$Id$\nFun stuff\n", fl, - "Hello\n$Id: b69e2387aafcaf73c4de5b9ab59abe27fdadee30 $\nFun stuff\n"); - add_blob_and_filter( - "Hello\n$Id: Junky$\nFun stuff\n", fl, - "Hello\n$Id: 45cd107a7102911cb2a7df08404674327fa050b9 $\nFun stuff\n"); - add_blob_and_filter( - "$Id$\nAt the start\n", fl, - "$Id: b13415c767abc196fb95bd17070e8c1113e32160 $\nAt the start\n"); - add_blob_and_filter( - "At the end\n$Id$", fl, - "At the end\n$Id: 1344925c6bc65b34c5a7b50f86bf688e48e9a272 $"); - add_blob_and_filter( - "$Id$", fl, - "$Id: b3f5ebfb5843bc43ceecff6d4f26bb37c615beb1 $"); - add_blob_and_filter( - "$Id: Some sort of junk goes here$", fl, - "$Id: ab2dd3853c7c9a4bff55aca2bea077a73c32ac06 $"); - - add_blob_and_filter("$Id: ", fl, "$Id: "); - add_blob_and_filter("$Id", fl, "$Id"); - add_blob_and_filter("$I", fl, "$I"); - add_blob_and_filter("Id$", fl, "Id$"); - - git_filter_list_free(fl); -} - -void test_filter_ident__to_odb(void) -{ - git_filter_list *fl; - git_filter *ident; - - cl_git_pass(git_filter_list_new( - &fl, g_repo, GIT_FILTER_TO_ODB, 0)); - - ident = git_filter_lookup(GIT_FILTER_IDENT); - cl_assert(ident != NULL); - - cl_git_pass(git_filter_list_push(fl, ident, NULL)); - - add_blob_and_filter( - "Hello\n$Id$\nFun stuff\n", - fl, "Hello\n$Id$\nFun stuff\n"); - add_blob_and_filter( - "Hello\n$Id: b69e2387aafcaf73c4de5b9ab59abe27fdadee30$\nFun stuff\n", - fl, "Hello\n$Id$\nFun stuff\n"); - add_blob_and_filter( - "Hello\n$Id: Any junk you may have left here$\nFun stuff\n", - fl, "Hello\n$Id$\nFun stuff\n"); - add_blob_and_filter( - "Hello\n$Id:$\nFun stuff\n", - fl, "Hello\n$Id$\nFun stuff\n"); - add_blob_and_filter( - "Hello\n$Id:x$\nFun stuff\n", - fl, "Hello\n$Id$\nFun stuff\n"); - - add_blob_and_filter( - "$Id$\nAt the start\n", fl, "$Id$\nAt the start\n"); - add_blob_and_filter( - "$Id: lots of random text that should be removed from here$\nAt the start\n", fl, "$Id$\nAt the start\n"); - add_blob_and_filter( - "$Id: lots of random text that should not be removed without a terminator\nAt the start\n", fl, "$Id: lots of random text that should not be removed without a terminator\nAt the start\n"); - - add_blob_and_filter( - "At the end\n$Id$", fl, "At the end\n$Id$"); - add_blob_and_filter( - "At the end\n$Id:$", fl, "At the end\n$Id$"); - add_blob_and_filter( - "At the end\n$Id:asdfasdf$", fl, "At the end\n$Id$"); - add_blob_and_filter( - "At the end\n$Id", fl, "At the end\n$Id"); - add_blob_and_filter( - "At the end\n$IddI", fl, "At the end\n$IddI"); - - add_blob_and_filter("$Id$", fl, "$Id$"); - add_blob_and_filter("$Id: any$", fl, "$Id$"); - add_blob_and_filter("$Id: any long stuff goes here you see$", fl, "$Id$"); - add_blob_and_filter("$Id: ", fl, "$Id: "); - add_blob_and_filter("$Id", fl, "$Id"); - add_blob_and_filter("$I", fl, "$I"); - add_blob_and_filter("Id$", fl, "Id$"); - - git_filter_list_free(fl); -} diff --git a/tests/filter/query.c b/tests/filter/query.c deleted file mode 100644 index 429c10443..000000000 --- a/tests/filter/query.c +++ /dev/null @@ -1,90 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/filter.h" -#include "crlf.h" - -static git_repository *g_repo = NULL; - -void test_filter_query__initialize(void) -{ - g_repo = cl_git_sandbox_init("crlf"); - - cl_git_mkfile("crlf/.gitattributes", - "*.txt text\n" - "*.bin binary\n" - "*.crlf text eol=crlf\n" - "*.lf text eol=lf\n" - "*.binident binary ident\n" - "*.ident text ident\n" - "*.identcrlf ident text eol=crlf\n" - "*.identlf ident text eol=lf\n" - "*.custom custom ident text\n"); -} - -void test_filter_query__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int filter_for(const char *filename, const char *filter) -{ - git_filter_list *fl; - int filtered; - - cl_git_pass(git_filter_list_load( - &fl, g_repo, NULL, filename, GIT_FILTER_TO_WORKTREE, 0)); - filtered = git_filter_list_contains(fl, filter); - git_filter_list_free(fl); - - return filtered; -} - -void test_filter_query__filters(void) -{ - cl_assert_equal_i(1, filter_for("text.txt", "crlf")); - cl_assert_equal_i(0, filter_for("binary.bin", "crlf")); - - cl_assert_equal_i(1, filter_for("foo.lf", "crlf")); - cl_assert_equal_i(0, filter_for("foo.lf", "ident")); - - cl_assert_equal_i(1, filter_for("id.ident", "crlf")); - cl_assert_equal_i(1, filter_for("id.ident", "ident")); - - cl_assert_equal_i(0, filter_for("id.binident", "crlf")); - cl_assert_equal_i(1, filter_for("id.binident", "ident")); -} - -void test_filter_query__autocrlf_true_implies_crlf(void) -{ - cl_repo_set_bool(g_repo, "core.autocrlf", true); - cl_assert_equal_i(1, filter_for("not_in_gitattributes", "crlf")); - cl_assert_equal_i(1, filter_for("foo.txt", "crlf")); - cl_assert_equal_i(0, filter_for("foo.bin", "crlf")); - cl_assert_equal_i(1, filter_for("foo.lf", "crlf")); - - cl_repo_set_bool(g_repo, "core.autocrlf", false); - cl_assert_equal_i(0, filter_for("not_in_gitattributes", "crlf")); - cl_assert_equal_i(1, filter_for("foo.txt", "crlf")); - cl_assert_equal_i(0, filter_for("foo.bin", "crlf")); - cl_assert_equal_i(1, filter_for("foo.lf", "crlf")); -} - -void test_filter_query__unknown(void) -{ - cl_assert_equal_i(1, filter_for("foo.custom", "crlf")); - cl_assert_equal_i(1, filter_for("foo.custom", "ident")); - cl_assert_equal_i(0, filter_for("foo.custom", "custom")); -} - -void test_filter_query__custom(void) -{ - git_filter custom = { GIT_FILTER_VERSION }; - - cl_git_pass(git_filter_register( - "custom", &custom, 42)); - - cl_assert_equal_i(1, filter_for("foo.custom", "crlf")); - cl_assert_equal_i(1, filter_for("foo.custom", "ident")); - cl_assert_equal_i(1, filter_for("foo.custom", "custom")); - - git_filter_unregister("custom"); -} diff --git a/tests/filter/stream.c b/tests/filter/stream.c deleted file mode 100644 index 0f85f9c47..000000000 --- a/tests/filter/stream.c +++ /dev/null @@ -1,215 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "blob.h" -#include "filter.h" -#include "git2/sys/filter.h" -#include "git2/sys/repository.h" - -static git_repository *g_repo = NULL; - -static git_filter *create_compress_filter(void); -static git_filter *compress_filter; - -void test_filter_stream__initialize(void) -{ - compress_filter = create_compress_filter(); - - cl_git_pass(git_filter_register("compress", compress_filter, 50)); - g_repo = cl_git_sandbox_init("empty_standard_repo"); -} - -void test_filter_stream__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; - - git_filter_unregister("compress"); - git__free(compress_filter); -} - -#define CHUNKSIZE 10240 - -struct compress_stream { - git_writestream parent; - git_writestream *next; - git_filter_mode_t mode; - char current; - size_t current_chunk; -}; - -static int compress_stream_write__deflated(struct compress_stream *stream, const char *buffer, size_t len) -{ - size_t idx = 0; - - while (len > 0) { - size_t chunkremain, chunksize; - - if (stream->current_chunk == 0) - stream->current = buffer[idx]; - - chunkremain = CHUNKSIZE - stream->current_chunk; - chunksize = min(chunkremain, len); - - stream->current_chunk += chunksize; - len -= chunksize; - idx += chunksize; - - if (stream->current_chunk == CHUNKSIZE) { - cl_git_pass(stream->next->write(stream->next, &stream->current, 1)); - stream->current_chunk = 0; - } - } - - return 0; -} - -static int compress_stream_write__inflated(struct compress_stream *stream, const char *buffer, size_t len) -{ - char inflated[CHUNKSIZE]; - size_t i, j; - - for (i = 0; i < len; i++) { - for (j = 0; j < CHUNKSIZE; j++) - inflated[j] = buffer[i]; - - cl_git_pass(stream->next->write(stream->next, inflated, CHUNKSIZE)); - } - - return 0; -} - -static int compress_stream_write(git_writestream *s, const char *buffer, size_t len) -{ - struct compress_stream *stream = (struct compress_stream *)s; - - return (stream->mode == GIT_FILTER_TO_ODB) ? - compress_stream_write__deflated(stream, buffer, len) : - compress_stream_write__inflated(stream, buffer, len); -} - -static int compress_stream_close(git_writestream *s) -{ - struct compress_stream *stream = (struct compress_stream *)s; - cl_assert_equal_i(0, stream->current_chunk); - stream->next->close(stream->next); - return 0; -} - -static void compress_stream_free(git_writestream *stream) -{ - git__free(stream); -} - -static int compress_filter_stream_init( - git_writestream **out, - git_filter *self, - void **payload, - const git_filter_source *src, - git_writestream *next) -{ - struct compress_stream *stream = git__calloc(1, sizeof(struct compress_stream)); - cl_assert(stream); - - GIT_UNUSED(self); - GIT_UNUSED(payload); - - stream->parent.write = compress_stream_write; - stream->parent.close = compress_stream_close; - stream->parent.free = compress_stream_free; - stream->next = next; - stream->mode = git_filter_source_mode(src); - - *out = (git_writestream *)stream; - return 0; -} - -git_filter *create_compress_filter(void) -{ - git_filter *filter = git__calloc(1, sizeof(git_filter)); - cl_assert(filter); - - filter->version = GIT_FILTER_VERSION; - filter->attributes = "+compress"; - filter->stream = compress_filter_stream_init; - - return filter; -} - -static void writefile(const char *filename, size_t numchunks) -{ - git_str path = GIT_STR_INIT; - char buf[CHUNKSIZE]; - size_t i = 0, j = 0; - int fd; - - cl_git_pass(git_str_joinpath(&path, "empty_standard_repo", filename)); - - fd = p_open(path.ptr, O_RDWR|O_CREAT, 0666); - cl_assert(fd >= 0); - - for (i = 0; i < numchunks; i++) { - for (j = 0; j < CHUNKSIZE; j++) { - buf[j] = i % 256; - } - - cl_git_pass(p_write(fd, buf, CHUNKSIZE)); - } - p_close(fd); - - git_str_dispose(&path); -} - -static void test_stream(size_t numchunks) -{ - git_index *index; - const git_index_entry *entry; - git_blob *blob; - struct stat st; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_mkfile( - "empty_standard_repo/.gitattributes", - "* compress\n"); - - /* write a file to disk */ - writefile("streamed_file", numchunks); - - /* place it in the index */ - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_add_bypath(index, "streamed_file")); - cl_git_pass(git_index_write(index)); - - /* ensure it was appropriately compressed */ - cl_assert(entry = git_index_get_bypath(index, "streamed_file", 0)); - - cl_git_pass(git_blob_lookup(&blob, g_repo, &entry->id)); - cl_assert_equal_i(numchunks, git_blob_rawsize(blob)); - - /* check the file back out */ - cl_must_pass(p_unlink("empty_standard_repo/streamed_file")); - cl_git_pass(git_checkout_index(g_repo, index, &checkout_opts)); - - /* ensure it was decompressed */ - cl_must_pass(p_stat("empty_standard_repo/streamed_file", &st)); - cl_assert_equal_sz((numchunks * CHUNKSIZE), st.st_size); - - git_index_free(index); - git_blob_free(blob); -} - -/* write a 50KB file through the "compression" stream */ -void test_filter_stream__smallfile(void) -{ - test_stream(5); -} - -/* optionally write a 500 MB file through the compression stream */ -void test_filter_stream__bigfile(void) -{ - if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE")) - cl_skip(); - - test_stream(51200); -} diff --git a/tests/filter/systemattrs.c b/tests/filter/systemattrs.c deleted file mode 100644 index b687b4b44..000000000 --- a/tests/filter/systemattrs.c +++ /dev/null @@ -1,85 +0,0 @@ -#include "clar_libgit2.h" -#include "crlf.h" -#include "path.h" - -static git_repository *g_repo = NULL; -static git_str system_attr_path = GIT_STR_INIT; - -void test_filter_systemattrs__initialize(void) -{ - git_buf system_path = GIT_BUF_INIT; - - g_repo = cl_git_sandbox_init("crlf"); - cl_must_pass(p_unlink("crlf/.gitattributes")); - - cl_git_pass(git_libgit2_opts( - GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, &system_path)); - cl_git_pass(git_str_joinpath(&system_attr_path, - system_path.ptr, "gitattributes")); - - cl_git_mkfile(system_attr_path.ptr, - "*.txt text\n" - "*.bin binary\n" - "*.crlf text eol=crlf\n" - "*.lf text eol=lf\n"); - - git_buf_dispose(&system_path); -} - -void test_filter_systemattrs__cleanup(void) -{ - cl_must_pass(p_unlink(system_attr_path.ptr)); - git_str_dispose(&system_attr_path); - - cl_git_sandbox_cleanup(); -} - -void test_filter_systemattrs__reads_system_attributes(void) -{ - git_blob *blob; - git_buf buf = { 0 }; - - cl_git_pass(git_revparse_single( - (git_object **)&blob, g_repo, "799770d")); /* all-lf */ - - cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); - - cl_git_pass(git_blob_filter(&buf, blob, "file.bin", NULL)); - cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", NULL)); - cl_assert_equal_s(ALL_LF_TEXT_AS_CRLF, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "file.lf", NULL)); - cl_assert_equal_s(ALL_LF_TEXT_AS_LF, buf.ptr); - - git_buf_dispose(&buf); - git_blob_free(blob); -} - -void test_filter_systemattrs__disables_system_attributes(void) -{ - git_blob *blob; - git_buf buf = { 0 }; - git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; - - opts.flags |= GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES; - - cl_git_pass(git_revparse_single( - (git_object **)&blob, g_repo, "799770d")); /* all-lf */ - - cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); - - cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &opts)); - cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); - - /* No attributes mean these are all treated literally */ - cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &opts)); - cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); - - cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &opts)); - cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); - - git_buf_dispose(&buf); - git_blob_free(blob); -} diff --git a/tests/filter/wildcard.c b/tests/filter/wildcard.c deleted file mode 100644 index ee61a8dba..000000000 --- a/tests/filter/wildcard.c +++ /dev/null @@ -1,188 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "blob.h" -#include "filter.h" -#include "git2/sys/filter.h" -#include "git2/sys/repository.h" -#include "custom_helpers.h" - -static git_repository *g_repo = NULL; - -static git_filter *create_wildcard_filter(void); - -#define DATA_LEN 32 - -static unsigned char input[] = { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, -}; - -static unsigned char reversed[] = { - 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, - 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, - 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, - 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, -}; - -static unsigned char flipped[] = { - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, - 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, - 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, - 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, -}; - -void test_filter_wildcard__initialize(void) -{ - cl_git_pass(git_filter_register( - "wildcard", create_wildcard_filter(), GIT_FILTER_DRIVER_PRIORITY)); - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_rewritefile( - "empty_standard_repo/.gitattributes", - "* binary\n" - "hero-flip-* filter=wcflip\n" - "hero-reverse-* filter=wcreverse\n" - "none-* filter=unregistered\n"); -} - -void test_filter_wildcard__cleanup(void) -{ - cl_git_pass(git_filter_unregister("wildcard")); - - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -static int wildcard_filter_check( - git_filter *self, - void **payload, - const git_filter_source *src, - const char **attr_values) -{ - GIT_UNUSED(self); - GIT_UNUSED(src); - - if (strcmp(attr_values[0], "wcflip") == 0 || - strcmp(attr_values[0], "wcreverse") == 0) { - *payload = git__strdup(attr_values[0]); - GIT_ERROR_CHECK_ALLOC(*payload); - return 0; - } - - return GIT_PASSTHROUGH; -} - -static int wildcard_filter_apply( - git_filter *self, - void **payload, - git_str *to, - const git_str *from, - const git_filter_source *source) -{ - const char *filtername = *payload; - - if (filtername && strcmp(filtername, "wcflip") == 0) - return bitflip_filter_apply(self, payload, to, from, source); - else if (filtername && strcmp(filtername, "wcreverse") == 0) - return reverse_filter_apply(self, payload, to, from, source); - - cl_fail("Unexpected attribute"); - return GIT_PASSTHROUGH; -} - -static int wildcard_filter_stream( - git_writestream **out, - git_filter *self, - void **payload, - const git_filter_source *src, - git_writestream *next) -{ - return git_filter_buffered_stream_new(out, - self, wildcard_filter_apply, NULL, payload, src, next); -} - -static void wildcard_filter_cleanup(git_filter *self, void *payload) -{ - GIT_UNUSED(self); - git__free(payload); -} - -static void wildcard_filter_free(git_filter *f) -{ - git__free(f); -} - -static git_filter *create_wildcard_filter(void) -{ - git_filter *filter = git__calloc(1, sizeof(git_filter)); - cl_assert(filter); - - filter->version = GIT_FILTER_VERSION; - filter->attributes = "filter=*"; - filter->check = wildcard_filter_check; - filter->stream = wildcard_filter_stream; - filter->cleanup = wildcard_filter_cleanup; - filter->shutdown = wildcard_filter_free; - - return filter; -} - -void test_filter_wildcard__reverse(void) -{ - git_filter_list *fl; - git_buf out = GIT_BUF_INIT; - - cl_git_pass(git_filter_list_load( - &fl, g_repo, NULL, "hero-reverse-foo", GIT_FILTER_TO_ODB, 0)); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, (char *)input, DATA_LEN)); - - cl_assert_equal_i(DATA_LEN, out.size); - - cl_assert_equal_i( - 0, memcmp(reversed, out.ptr, out.size)); - - git_filter_list_free(fl); - git_buf_dispose(&out); -} - -void test_filter_wildcard__flip(void) -{ - git_filter_list *fl; - git_buf out = GIT_BUF_INIT; - - cl_git_pass(git_filter_list_load( - &fl, g_repo, NULL, "hero-flip-foo", GIT_FILTER_TO_ODB, 0)); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, (char *)input, DATA_LEN)); - - cl_assert_equal_i(DATA_LEN, out.size); - - cl_assert_equal_i( - 0, memcmp(flipped, out.ptr, out.size)); - - git_filter_list_free(fl); - git_buf_dispose(&out); -} - -void test_filter_wildcard__none(void) -{ - git_filter_list *fl; - git_buf out = GIT_BUF_INIT; - - cl_git_pass(git_filter_list_load( - &fl, g_repo, NULL, "none-foo", GIT_FILTER_TO_ODB, 0)); - - cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, (char *)input, DATA_LEN)); - - cl_assert_equal_i(DATA_LEN, out.size); - - cl_assert_equal_i( - 0, memcmp(input, out.ptr, out.size)); - - git_filter_list_free(fl); - git_buf_dispose(&out); -} diff --git a/tests/generate.py b/tests/generate.py deleted file mode 100644 index d2cdb684a..000000000 --- a/tests/generate.py +++ /dev/null @@ -1,316 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) Vicent Marti. All rights reserved. -# -# This file is part of clar, distributed under the ISC license. -# For full terms see the included COPYING file. -# - -from __future__ import with_statement -from string import Template -import re, fnmatch, os, sys, codecs, pickle, io - -class Module(object): - class Template(object): - def __init__(self, module): - self.module = module - - def _render_callback(self, cb): - if not cb: - return ' { NULL, NULL }' - return ' { "%s", &%s }' % (cb['short_name'], cb['symbol']) - - class DeclarationTemplate(Template): - def render(self): - out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n" - - for initializer in self.module.initializers: - out += "extern %s;\n" % initializer['declaration'] - - if self.module.cleanup: - out += "extern %s;\n" % self.module.cleanup['declaration'] - - return out - - class CallbacksTemplate(Template): - def render(self): - out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name - out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks) - out += "\n};\n" - return out - - class InfoTemplate(Template): - def render(self): - templates = [] - - initializers = self.module.initializers - if len(initializers) == 0: - initializers = [ None ] - - for initializer in initializers: - name = self.module.clean_name() - if initializer and initializer['short_name'].startswith('initialize_'): - variant = initializer['short_name'][len('initialize_'):] - name += " (%s)" % variant.replace('_', ' ') - - template = Template( - r""" - { - "${clean_name}", - ${initialize}, - ${cleanup}, - ${cb_ptr}, ${cb_count}, ${enabled} - }""" - ).substitute( - clean_name = name, - initialize = self._render_callback(initializer), - cleanup = self._render_callback(self.module.cleanup), - cb_ptr = "_clar_cb_%s" % self.module.name, - cb_count = len(self.module.callbacks), - enabled = int(self.module.enabled) - ) - templates.append(template) - - return ','.join(templates) - - def __init__(self, name): - self.name = name - - self.mtime = 0 - self.enabled = True - self.modified = False - - def clean_name(self): - return self.name.replace("_", "::") - - def _skip_comments(self, text): - SKIP_COMMENTS_REGEX = re.compile( - r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', - re.DOTALL | re.MULTILINE) - - def _replacer(match): - s = match.group(0) - return "" if s.startswith('/') else s - - return re.sub(SKIP_COMMENTS_REGEX, _replacer, text) - - def parse(self, contents): - TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{" - - contents = self._skip_comments(contents) - regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE) - - self.callbacks = [] - self.initializers = [] - self.cleanup = None - - for (declaration, symbol, short_name) in regex.findall(contents): - data = { - "short_name" : short_name, - "declaration" : declaration, - "symbol" : symbol - } - - if short_name.startswith('initialize'): - self.initializers.append(data) - elif short_name == 'cleanup': - self.cleanup = data - else: - self.callbacks.append(data) - - return self.callbacks != [] - - def refresh(self, path): - self.modified = False - - try: - st = os.stat(path) - - # Not modified - if st.st_mtime == self.mtime: - return True - - self.modified = True - self.mtime = st.st_mtime - - with codecs.open(path, encoding='utf-8') as fp: - raw_content = fp.read() - - except IOError: - return False - - return self.parse(raw_content) - -class TestSuite(object): - - def __init__(self, path, output): - self.path = path - self.output = output - - def maybe_generate(self, path): - if not os.path.isfile(path): - return True - - if any(module.modified for module in self.modules.values()): - return True - - return False - - def find_modules(self): - modules = [] - for root, _, files in os.walk(self.path): - module_root = root[len(self.path):] - module_root = [c for c in module_root.split(os.sep) if c] - - tests_in_module = fnmatch.filter(files, "*.c") - - for test_file in tests_in_module: - full_path = os.path.join(root, test_file) - module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_") - - modules.append((full_path, module_name)) - - return modules - - def load_cache(self): - path = os.path.join(self.output, '.clarcache') - cache = {} - - try: - fp = open(path, 'rb') - cache = pickle.load(fp) - fp.close() - except (IOError, ValueError): - pass - - return cache - - def save_cache(self): - path = os.path.join(self.output, '.clarcache') - with open(path, 'wb') as cache: - pickle.dump(self.modules, cache) - - def load(self, force = False): - module_data = self.find_modules() - self.modules = {} if force else self.load_cache() - - for path, name in module_data: - if name not in self.modules: - self.modules[name] = Module(name) - - if not self.modules[name].refresh(path): - del self.modules[name] - - def disable(self, excluded): - for exclude in excluded: - for module in self.modules.values(): - name = module.clean_name() - if name.startswith(exclude): - module.enabled = False - module.modified = True - - def suite_count(self): - return sum(max(1, len(m.initializers)) for m in self.modules.values()) - - def callback_count(self): - return sum(len(module.callbacks) for module in self.modules.values()) - - def write(self): - wrote_suite = self.write_suite() - wrote_header = self.write_header() - - if wrote_suite or wrote_header: - self.save_cache() - return True - - return False - - def write_output(self, fn, data): - if not self.maybe_generate(fn): - return False - - current = None - - try: - with open(fn, 'r') as input: - current = input.read() - except OSError: - pass - except IOError: - pass - - if current == data: - return False - - with open(fn, 'w') as output: - output.write(data) - - return True - - def write_suite(self): - suite_fn = os.path.join(self.output, 'clar.suite') - - with io.StringIO() as suite_file: - modules = sorted(self.modules.values(), key=lambda module: module.name) - - for module in modules: - t = Module.DeclarationTemplate(module) - suite_file.write(t.render()) - - for module in modules: - t = Module.CallbacksTemplate(module) - suite_file.write(t.render()) - - suites = "static struct clar_suite _clar_suites[] = {" + ','.join( - Module.InfoTemplate(module).render() for module in modules - ) + "\n};\n" - - suite_file.write(suites) - - suite_file.write(u"static const size_t _clar_suite_count = %d;\n" % self.suite_count()) - suite_file.write(u"static const size_t _clar_callback_count = %d;\n" % self.callback_count()) - - return self.write_output(suite_fn, suite_file.getvalue()) - - return False - - def write_header(self): - header_fn = os.path.join(self.output, 'clar_suite.h') - - with io.StringIO() as header_file: - header_file.write(u"#ifndef _____clar_suite_h_____\n") - header_file.write(u"#define _____clar_suite_h_____\n") - - modules = sorted(self.modules.values(), key=lambda module: module.name) - - for module in modules: - t = Module.DeclarationTemplate(module) - header_file.write(t.render()) - - header_file.write(u"#endif\n") - - return self.write_output(header_fn, header_file.getvalue()) - - return False - -if __name__ == '__main__': - from optparse import OptionParser - - parser = OptionParser() - parser.add_option('-f', '--force', action="store_true", dest='force', default=False) - parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[]) - parser.add_option('-o', '--output', dest='output') - - options, args = parser.parse_args() - if len(args) > 1: - print("More than one path given") - sys.exit(1) - - path = args.pop() if args else '.' - output = options.output or path - suite = TestSuite(path, output) - suite.load(options.force) - suite.disable(options.excluded) - if suite.write(): - print("Written `clar.suite`, `clar_suite.h` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count())) - diff --git a/tests/graph/ahead_behind.c b/tests/graph/ahead_behind.c deleted file mode 100644 index 77d7768af..000000000 --- a/tests/graph/ahead_behind.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "clar_libgit2.h" - -static git_repository *_repo; -static git_commit *commit; -static size_t ahead; -static size_t behind; - -void test_graph_ahead_behind__initialize(void) -{ - git_oid oid; - cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); - - cl_git_pass(git_oid_fromstr(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); - cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); -} - -void test_graph_ahead_behind__cleanup(void) -{ - git_commit_free(commit); - commit = NULL; - - git_repository_free(_repo); - _repo = NULL; -} - -void test_graph_ahead_behind__returns_correct_result(void) -{ - git_oid oid; - git_oid oid2; - git_commit *other; - - cl_git_pass(git_oid_fromstr(&oid, "e90810b8df3e80c413d903f631643c716887138d")); - cl_git_pass(git_oid_fromstr(&oid2, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); - - cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &oid, &oid2)); - cl_assert_equal_sz(2, ahead); - cl_assert_equal_sz(6, behind); - - cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(commit), git_commit_id(commit))); - cl_assert_equal_sz(ahead, behind); - cl_git_pass(git_commit_nth_gen_ancestor(&other, commit, 1)); - - cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(commit), git_commit_id(other))); - cl_assert_equal_sz(ahead, behind + 2); - cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(other), git_commit_id(commit))); - cl_assert_equal_sz(ahead + 2, behind); - - git_commit_free(other); - - cl_git_pass(git_commit_nth_gen_ancestor(&other, commit, 3)); - - cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(commit), git_commit_id(other))); - cl_assert_equal_sz(ahead, behind + 4); - cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(other), git_commit_id(commit))); - cl_assert_equal_sz(ahead + 4, behind); - - git_commit_free(other); -} diff --git a/tests/graph/commitgraph.c b/tests/graph/commitgraph.c deleted file mode 100644 index 7607c35a3..000000000 --- a/tests/graph/commitgraph.c +++ /dev/null @@ -1,126 +0,0 @@ -#include "clar_libgit2.h" - -#include -#include - -#include "commit_graph.h" -#include "futils.h" - -void test_graph_commitgraph__parse(void) -{ - git_repository *repo; - struct git_commit_graph_file *file; - struct git_commit_graph_entry e, parent; - git_oid id; - git_str commit_graph_path = GIT_STR_INIT; - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_str_joinpath(&commit_graph_path, git_repository_path(repo), "objects/info/commit-graph")); - cl_git_pass(git_commit_graph_file_open(&file, git_str_cstr(&commit_graph_path))); - cl_assert_equal_i(git_commit_graph_file_needs_refresh(file, git_str_cstr(&commit_graph_path)), 0); - - cl_git_pass(git_oid_fromstr(&id, "5001298e0c09ad9c34e4249bc5801c75e9754fa5")); - cl_git_pass(git_commit_graph_entry_find(&e, file, &id, GIT_OID_HEXSZ)); - cl_assert_equal_oid(&e.sha1, &id); - cl_git_pass(git_oid_fromstr(&id, "418382dff1ffb8bdfba833f4d8bbcde58b1e7f47")); - cl_assert_equal_oid(&e.tree_oid, &id); - cl_assert_equal_i(e.generation, 1); - cl_assert_equal_i(e.commit_time, UINT64_C(1273610423)); - cl_assert_equal_i(e.parent_count, 0); - - cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); - cl_git_pass(git_commit_graph_entry_find(&e, file, &id, GIT_OID_HEXSZ)); - cl_assert_equal_oid(&e.sha1, &id); - cl_assert_equal_i(e.generation, 5); - cl_assert_equal_i(e.commit_time, UINT64_C(1274813907)); - cl_assert_equal_i(e.parent_count, 2); - - cl_git_pass(git_oid_fromstr(&id, "9fd738e8f7967c078dceed8190330fc8648ee56a")); - cl_git_pass(git_commit_graph_entry_parent(&parent, file, &e, 0)); - cl_assert_equal_oid(&parent.sha1, &id); - cl_assert_equal_i(parent.generation, 4); - - cl_git_pass(git_oid_fromstr(&id, "c47800c7266a2be04c571c04d5a6614691ea99bd")); - cl_git_pass(git_commit_graph_entry_parent(&parent, file, &e, 1)); - cl_assert_equal_oid(&parent.sha1, &id); - cl_assert_equal_i(parent.generation, 3); - - git_commit_graph_file_free(file); - git_repository_free(repo); - git_str_dispose(&commit_graph_path); -} - -void test_graph_commitgraph__parse_octopus_merge(void) -{ - git_repository *repo; - struct git_commit_graph_file *file; - struct git_commit_graph_entry e, parent; - git_oid id; - git_str commit_graph_path = GIT_STR_INIT; - - cl_git_pass(git_repository_open(&repo, cl_fixture("merge-recursive/.gitted"))); - cl_git_pass(git_str_joinpath(&commit_graph_path, git_repository_path(repo), "objects/info/commit-graph")); - cl_git_pass(git_commit_graph_file_open(&file, git_str_cstr(&commit_graph_path))); - - cl_git_pass(git_oid_fromstr(&id, "d71c24b3b113fd1d1909998c5bfe33b86a65ee03")); - cl_git_pass(git_commit_graph_entry_find(&e, file, &id, GIT_OID_HEXSZ)); - cl_assert_equal_oid(&e.sha1, &id); - cl_git_pass(git_oid_fromstr(&id, "348f16ffaeb73f319a75cec5b16a0a47d2d5e27c")); - cl_assert_equal_oid(&e.tree_oid, &id); - cl_assert_equal_i(e.generation, 7); - cl_assert_equal_i(e.commit_time, UINT64_C(1447083009)); - cl_assert_equal_i(e.parent_count, 3); - - cl_git_pass(git_oid_fromstr(&id, "ad2ace9e15f66b3d1138922e6ffdc3ea3f967fa6")); - cl_git_pass(git_commit_graph_entry_parent(&parent, file, &e, 0)); - cl_assert_equal_oid(&parent.sha1, &id); - cl_assert_equal_i(parent.generation, 6); - - cl_git_pass(git_oid_fromstr(&id, "483065df53c0f4a02cdc6b2910b05d388fc17ffb")); - cl_git_pass(git_commit_graph_entry_parent(&parent, file, &e, 1)); - cl_assert_equal_oid(&parent.sha1, &id); - cl_assert_equal_i(parent.generation, 2); - - cl_git_pass(git_oid_fromstr(&id, "815b5a1c80ca749d705c7aa0cb294a00cbedd340")); - cl_git_pass(git_commit_graph_entry_parent(&parent, file, &e, 2)); - cl_assert_equal_oid(&parent.sha1, &id); - cl_assert_equal_i(parent.generation, 6); - - git_commit_graph_file_free(file); - git_repository_free(repo); - git_str_dispose(&commit_graph_path); -} - -void test_graph_commitgraph__writer(void) -{ - git_repository *repo; - git_commit_graph_writer *w = NULL; - git_revwalk *walk; - git_commit_graph_writer_options opts = GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT; - git_buf cgraph = GIT_BUF_INIT; - git_str expected_cgraph = GIT_STR_INIT, path = GIT_STR_INIT; - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - - cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "objects/info")); - cl_git_pass(git_commit_graph_writer_new(&w, git_str_cstr(&path))); - - /* This is equivalent to `git commit-graph write --reachable`. */ - cl_git_pass(git_revwalk_new(&walk, repo)); - cl_git_pass(git_revwalk_push_glob(walk, "refs/*")); - cl_git_pass(git_commit_graph_writer_add_revwalk(w, walk)); - git_revwalk_free(walk); - - cl_git_pass(git_commit_graph_writer_dump(&cgraph, w, &opts)); - cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "objects/info/commit-graph")); - cl_git_pass(git_futils_readbuffer(&expected_cgraph, git_str_cstr(&path))); - - cl_assert_equal_i(cgraph.size, git_str_len(&expected_cgraph)); - cl_assert_equal_i(memcmp(cgraph.ptr, git_str_cstr(&expected_cgraph), cgraph.size), 0); - - git_buf_dispose(&cgraph); - git_str_dispose(&expected_cgraph); - git_str_dispose(&path); - git_commit_graph_writer_free(w); - git_repository_free(repo); -} diff --git a/tests/graph/descendant_of.c b/tests/graph/descendant_of.c deleted file mode 100644 index 8e9952a09..000000000 --- a/tests/graph/descendant_of.c +++ /dev/null @@ -1,55 +0,0 @@ -#include "clar_libgit2.h" - -static git_repository *_repo; -static git_commit *commit; - -void test_graph_descendant_of__initialize(void) -{ - git_oid oid; - - cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); - - git_oid_fromstr(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); -} - -void test_graph_descendant_of__cleanup(void) -{ - git_commit_free(commit); - commit = NULL; - - git_repository_free(_repo); - _repo = NULL; -} - -void test_graph_descendant_of__returns_correct_result(void) -{ - git_commit *other; - - cl_assert_equal_i(0, git_graph_descendant_of(_repo, git_commit_id(commit), git_commit_id(commit))); - - - cl_git_pass(git_commit_nth_gen_ancestor(&other, commit, 1)); - - cl_assert_equal_i(1, git_graph_descendant_of(_repo, git_commit_id(commit), git_commit_id(other))); - cl_assert_equal_i(0, git_graph_descendant_of(_repo, git_commit_id(other), git_commit_id(commit))); - - git_commit_free(other); - - - cl_git_pass(git_commit_nth_gen_ancestor(&other, commit, 3)); - - cl_assert_equal_i(1, git_graph_descendant_of(_repo, git_commit_id(commit), git_commit_id(other))); - cl_assert_equal_i(0, git_graph_descendant_of(_repo, git_commit_id(other), git_commit_id(commit))); - - git_commit_free(other); - -} - -void test_graph_descendant_of__nopath(void) -{ - git_oid oid; - - git_oid_fromstr(&oid, "e90810b8df3e80c413d903f631643c716887138d"); - cl_assert_equal_i(0, git_graph_descendant_of(_repo, git_commit_id(commit), &oid)); -} diff --git a/tests/graph/reachable_from_any.c b/tests/graph/reachable_from_any.c deleted file mode 100644 index 9693d7d67..000000000 --- a/tests/graph/reachable_from_any.c +++ /dev/null @@ -1,236 +0,0 @@ -#include "clar_libgit2.h" - -#include - -#include "commit_graph.h" -#include "bitvec.h" -#include "vector.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - -void test_graph_reachable_from_any__initialize(void) -{ - git_oid oid; - git_commit *commit; - - repo = cl_git_sandbox_init(TEST_REPO_PATH); - - git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); - git_commit_free(commit); -} - -void test_graph_reachable_from_any__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_graph_reachable_from_any__returns_correct_result(void) -{ - git_object *branchA1, *branchA2, *branchB1, *branchB2, *branchC1, *branchC2, *branchH1, - *branchH2; - git_oid descendants[7]; - - cl_git_pass(git_revparse_single(&branchA1, repo, "branchA-1")); - cl_git_pass(git_revparse_single(&branchA2, repo, "branchA-2")); - cl_git_pass(git_revparse_single(&branchB1, repo, "branchB-1")); - cl_git_pass(git_revparse_single(&branchB2, repo, "branchB-2")); - cl_git_pass(git_revparse_single(&branchC1, repo, "branchC-1")); - cl_git_pass(git_revparse_single(&branchC2, repo, "branchC-2")); - cl_git_pass(git_revparse_single(&branchH1, repo, "branchH-1")); - cl_git_pass(git_revparse_single(&branchH2, repo, "branchH-2")); - - cl_assert_equal_i( - git_graph_reachable_from_any( - repo, git_object_id(branchH1), git_object_id(branchA1), 1), - 0); - cl_assert_equal_i( - git_graph_reachable_from_any( - repo, git_object_id(branchH1), git_object_id(branchA2), 1), - 0); - - cl_git_pass(git_oid_cpy(&descendants[0], git_object_id(branchA1))); - cl_git_pass(git_oid_cpy(&descendants[1], git_object_id(branchA2))); - cl_git_pass(git_oid_cpy(&descendants[2], git_object_id(branchB1))); - cl_git_pass(git_oid_cpy(&descendants[3], git_object_id(branchB2))); - cl_git_pass(git_oid_cpy(&descendants[4], git_object_id(branchC1))); - cl_git_pass(git_oid_cpy(&descendants[5], git_object_id(branchC2))); - cl_git_pass(git_oid_cpy(&descendants[6], git_object_id(branchH2))); - cl_assert_equal_i( - git_graph_reachable_from_any(repo, git_object_id(branchH2), descendants, 6), - 0); - cl_assert_equal_i( - git_graph_reachable_from_any(repo, git_object_id(branchH2), descendants, 7), - 1); - - git_object_free(branchA1); - git_object_free(branchA2); - git_object_free(branchB1); - git_object_free(branchB2); - git_object_free(branchC1); - git_object_free(branchC2); - git_object_free(branchH1); - git_object_free(branchH2); -} - -struct exhaustive_state { - git_odb *db; - git_vector commits; -}; - -/** Get all commits from the repository. */ -static int exhaustive_commits(const git_oid *id, void *payload) -{ - struct exhaustive_state *mc = (struct exhaustive_state *)payload; - size_t header_len; - git_object_t header_type; - int error = 0; - - error = git_odb_read_header(&header_len, &header_type, mc->db, id); - if (error < 0) - return error; - - if (header_type == GIT_OBJECT_COMMIT) { - git_commit *commit = NULL; - - cl_git_pass(git_commit_lookup(&commit, repo, id)); - cl_git_pass(git_vector_insert(&mc->commits, commit)); - } - - return 0; -} - -/** Compare the `git_oid`s of two `git_commit` objects. */ -static int commit_id_cmp(const void *a, const void *b) -{ - return git_oid_cmp( - git_commit_id((const git_commit *)a), git_commit_id((const git_commit *)b)); -} - -/** Find a `git_commit` whose ID matches the provided `git_oid` key. */ -static int id_commit_id_cmp(const void *key, const void *commit) -{ - return git_oid_cmp((const git_oid *)key, git_commit_id((const git_commit *)commit)); -} - -void test_graph_reachable_from_any__exhaustive(void) -{ - struct exhaustive_state mc = { - .db = NULL, - .commits = GIT_VECTOR_INIT, - }; - size_t child_idx, commit_count; - size_t n_descendants; - git_commit *child_commit; - git_bitvec reachable; - - cl_git_pass(git_repository_odb(&mc.db, repo)); - cl_git_pass(git_odb_foreach(mc.db, &exhaustive_commits, &mc)); - git_vector_set_cmp(&mc.commits, commit_id_cmp); - git_vector_sort(&mc.commits); - cl_git_pass(git_bitvec_init( - &reachable, - git_vector_length(&mc.commits) * git_vector_length(&mc.commits))); - - commit_count = git_vector_length(&mc.commits); - git_vector_foreach (&mc.commits, child_idx, child_commit) { - unsigned int parent_i; - - /* We treat each commit as being able to reach itself. */ - git_bitvec_set(&reachable, child_idx * commit_count + child_idx, true); - - for (parent_i = 0; parent_i < git_commit_parentcount(child_commit); ++parent_i) { - size_t parent_idx = -1; - cl_git_pass(git_vector_bsearch2( - &parent_idx, - &mc.commits, - id_commit_id_cmp, - git_commit_parent_id(child_commit, parent_i))); - - /* We have established that parent_idx is reachable from child_idx */ - git_bitvec_set(&reachable, parent_idx * commit_count + child_idx, true); - } - } - - /* Floyd-Warshall */ - { - size_t i, j, k; - for (k = 0; k < commit_count; ++k) { - for (i = 0; i < commit_count; ++i) { - if (!git_bitvec_get(&reachable, i * commit_count + k)) - continue; - for (j = 0; j < commit_count; ++j) { - if (!git_bitvec_get(&reachable, k * commit_count + j)) - continue; - git_bitvec_set(&reachable, i * commit_count + j, true); - } - } - } - } - - /* Try 1000 subsets of 1 through 10 entries each. */ - srand(0x223ddc4b); - for (n_descendants = 1; n_descendants < 10; ++n_descendants) { - size_t test_iteration; - git_oid descendants[10]; - - for (test_iteration = 0; test_iteration < 1000; ++test_iteration) { - size_t descendant_i; - size_t child_idx, parent_idx; - int expected_reachable = false, actual_reachable; - git_commit *child_commit, *parent_commit; - - parent_idx = rand() % commit_count; - parent_commit = (git_commit *)git_vector_get(&mc.commits, parent_idx); - for (descendant_i = 0; descendant_i < n_descendants; ++descendant_i) { - child_idx = rand() % commit_count; - child_commit = (git_commit *)git_vector_get(&mc.commits, child_idx); - expected_reachable |= git_bitvec_get( - &reachable, parent_idx * commit_count + child_idx); - git_oid_cpy(&descendants[descendant_i], - git_commit_id(child_commit)); - } - - actual_reachable = git_graph_reachable_from_any( - repo, - git_commit_id(parent_commit), - descendants, - n_descendants); - if (actual_reachable != expected_reachable) { - git_str error_message_buf = GIT_STR_INIT; - char parent_oidbuf[9] = {0}, child_oidbuf[9] = {0}; - - cl_git_pass(git_oid_nfmt( - parent_oidbuf, 8, git_commit_id(parent_commit))); - git_str_printf(&error_message_buf, - "git_graph_reachable_from_any(\"%s\", %zu, " - "{", - parent_oidbuf, - n_descendants); - for (descendant_i = 0; descendant_i < n_descendants; - ++descendant_i) { - cl_git_pass( - git_oid_nfmt(child_oidbuf, - 8, - &descendants[descendant_i])); - git_str_printf(&error_message_buf, " \"%s\"", child_oidbuf); - } - git_str_printf(&error_message_buf, - " }) = %d, expected = %d", - actual_reachable, - expected_reachable); - cl_check_(actual_reachable == expected_reachable, - git_str_cstr(&error_message_buf)); - } - } - } - - git_vector_foreach (&mc.commits, child_idx, child_commit) - git_commit_free(child_commit); - git_bitvec_free(&reachable); - git_vector_free(&mc.commits); - git_odb_free(mc.db); -} diff --git a/tests/headertest.c b/tests/headertest.c deleted file mode 100644 index 2af8a14ec..000000000 --- a/tests/headertest.c +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Dummy project to validate header files - * - * This project is not intended to be executed, it should only include all - * header files to make sure that they can be used with stricter compiler - * settings than the libgit2 source files generally supports. - */ -#include "git2.h" - -int main(void) -{ - return 0; -} diff --git a/tests/ignore/path.c b/tests/ignore/path.c deleted file mode 100644 index a574d1d79..000000000 --- a/tests/ignore/path.c +++ /dev/null @@ -1,585 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "path.h" -#include "futils.h" - -static git_repository *g_repo = NULL; - -void test_ignore_path__initialize(void) -{ - g_repo = cl_git_sandbox_init("attr"); -} - -void test_ignore_path__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -static void assert_is_ignored_( - bool expected, const char *filepath, - const char *file, const char *func, int line) -{ - int is_ignored = 0; - - cl_git_expect( - git_ignore_path_is_ignored(&is_ignored, g_repo, filepath), 0, file, func, line); - - clar__assert_equal( - file, func, line, "expected != is_ignored", 1, "%d", - (int)(expected != 0), (int)(is_ignored != 0)); -} -#define assert_is_ignored(expected, filepath) \ - assert_is_ignored_(expected, filepath, __FILE__, __func__, __LINE__) - -void test_ignore_path__honor_temporary_rules(void) -{ - cl_git_rewritefile("attr/.gitignore", "/NewFolder\n/NewFolder/NewFolder"); - - assert_is_ignored(false, "File.txt"); - assert_is_ignored(true, "NewFolder"); - assert_is_ignored(true, "NewFolder/NewFolder"); - assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); -} - -void test_ignore_path__allow_root(void) -{ - cl_git_rewritefile("attr/.gitignore", "/"); - - assert_is_ignored(false, "File.txt"); - assert_is_ignored(false, "NewFolder"); - assert_is_ignored(false, "NewFolder/NewFolder"); - assert_is_ignored(false, "NewFolder/NewFolder/File.txt"); -} - -void test_ignore_path__ignore_space(void) -{ - cl_git_rewritefile("attr/.gitignore", "/\n\n/NewFolder \n/NewFolder/NewFolder"); - - assert_is_ignored(false, "File.txt"); - assert_is_ignored(true, "NewFolder"); - assert_is_ignored(true, "NewFolder/NewFolder"); - assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); -} - -void test_ignore_path__intermittent_space(void) -{ - cl_git_rewritefile("attr/.gitignore", "foo bar\n"); - - assert_is_ignored(false, "foo"); - assert_is_ignored(false, "bar"); - assert_is_ignored(true, "foo bar"); -} - -void test_ignore_path__trailing_space(void) -{ - cl_git_rewritefile( - "attr/.gitignore", - "foo \n" - "bar \n" - ); - - assert_is_ignored(true, "foo"); - assert_is_ignored(false, "foo "); - assert_is_ignored(true, "bar"); - assert_is_ignored(false, "bar "); - assert_is_ignored(false, "bar "); -} - -void test_ignore_path__escaped_trailing_spaces(void) -{ - cl_git_rewritefile( - "attr/.gitignore", - "foo\\ \n" - "bar\\ \\ \n" - "baz \\ \n" - "qux\\ \n" - ); - - assert_is_ignored(false, "foo"); - assert_is_ignored(true, "foo "); - assert_is_ignored(false, "bar"); - assert_is_ignored(false, "bar "); - assert_is_ignored(true, "bar "); - assert_is_ignored(true, "baz "); - assert_is_ignored(false, "baz "); - assert_is_ignored(true, "qux "); - assert_is_ignored(false, "qux"); - assert_is_ignored(false, "qux "); -} - -void test_ignore_path__ignore_dir(void) -{ - cl_git_rewritefile("attr/.gitignore", "dir/\n"); - - assert_is_ignored(true, "dir"); - assert_is_ignored(true, "dir/file"); -} - -void test_ignore_path__ignore_dir_with_trailing_space(void) -{ - cl_git_rewritefile("attr/.gitignore", "dir/ \n"); - - assert_is_ignored(true, "dir"); - assert_is_ignored(true, "dir/file"); -} - -void test_ignore_path__ignore_root(void) -{ - cl_git_rewritefile("attr/.gitignore", "/\n\n/NewFolder\n/NewFolder/NewFolder"); - - assert_is_ignored(false, "File.txt"); - assert_is_ignored(true, "NewFolder"); - assert_is_ignored(true, "NewFolder/NewFolder"); - assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); -} - -void test_ignore_path__full_paths(void) -{ - cl_git_rewritefile("attr/.gitignore", "Folder/*/Contained"); - - assert_is_ignored(true, "Folder/Middle/Contained"); - assert_is_ignored(false, "Folder/Middle/More/More/Contained"); - - cl_git_rewritefile("attr/.gitignore", "Folder/**/Contained"); - - assert_is_ignored(true, "Folder/Middle/Contained"); - assert_is_ignored(true, "Folder/Middle/More/More/Contained"); - - cl_git_rewritefile("attr/.gitignore", "Folder/**/Contained/*/Child"); - - assert_is_ignored(true, "Folder/Middle/Contained/Happy/Child"); - assert_is_ignored(false, "Folder/Middle/Contained/Not/Happy/Child"); - assert_is_ignored(true, "Folder/Middle/More/More/Contained/Happy/Child"); - assert_is_ignored(false, "Folder/Middle/More/More/Contained/Not/Happy/Child"); -} - -void test_ignore_path__more_starstar_cases(void) -{ - cl_must_pass(p_unlink("attr/.gitignore")); - cl_git_mkfile( - "attr/dir/.gitignore", - "sub/**/*.html\n"); - - assert_is_ignored(false, "aaa.html"); - assert_is_ignored(false, "dir"); - assert_is_ignored(false, "dir/sub"); - assert_is_ignored(true, "dir/sub/sub2/aaa.html"); - assert_is_ignored(true, "dir/sub/aaa.html"); - assert_is_ignored(false, "dir/aaa.html"); - assert_is_ignored(false, "sub"); - assert_is_ignored(false, "sub/aaa.html"); - assert_is_ignored(false, "sub/sub2/aaa.html"); -} - -void test_ignore_path__leading_stars(void) -{ - cl_git_rewritefile( - "attr/.gitignore", - "*/onestar\n" - "**/twostars\n" - "*/parent1/kid1/*\n" - "**/parent2/kid2/*\n"); - - assert_is_ignored(true, "dir1/onestar"); - assert_is_ignored(true, "dir1/onestar/child"); /* in ignored dir */ - assert_is_ignored(false, "dir1/dir2/onestar"); - - assert_is_ignored(true, "dir1/twostars"); - assert_is_ignored(true, "dir1/twostars/child"); /* in ignored dir */ - assert_is_ignored(true, "dir1/dir2/twostars"); - assert_is_ignored(true, "dir1/dir2/twostars/child"); /* in ignored dir */ - assert_is_ignored(true, "dir1/dir2/dir3/twostars"); - - assert_is_ignored(true, "dir1/parent1/kid1/file"); - assert_is_ignored(true, "dir1/parent1/kid1/file/inside/parent"); - assert_is_ignored(false, "dir1/dir2/parent1/kid1/file"); - assert_is_ignored(false, "dir1/parent1/file"); - assert_is_ignored(false, "dir1/kid1/file"); - - assert_is_ignored(true, "dir1/parent2/kid2/file"); - assert_is_ignored(true, "dir1/parent2/kid2/file/inside/parent"); - assert_is_ignored(true, "dir1/dir2/parent2/kid2/file"); - assert_is_ignored(true, "dir1/dir2/dir3/parent2/kid2/file"); - assert_is_ignored(false, "dir1/parent2/file"); - assert_is_ignored(false, "dir1/kid2/file"); -} - -void test_ignore_path__globs_and_path_delimiters(void) -{ - cl_git_rewritefile("attr/.gitignore", "foo/bar/**"); - assert_is_ignored(true, "foo/bar/baz"); - assert_is_ignored(true, "foo/bar/baz/quux"); - - cl_git_rewritefile("attr/.gitignore", "_*/"); - assert_is_ignored(true, "sub/_test/a/file"); - assert_is_ignored(false, "test_folder/file"); - assert_is_ignored(true, "_test/file"); - assert_is_ignored(true, "_test/a/file"); - - cl_git_rewritefile("attr/.gitignore", "**/_*/"); - assert_is_ignored(true, "sub/_test/a/file"); - assert_is_ignored(false, "test_folder/file"); - assert_is_ignored(true, "_test/file"); - assert_is_ignored(true, "_test/a/file"); - - cl_git_rewritefile("attr/.gitignore", "**/_*/foo/bar/*ux"); - - assert_is_ignored(true, "sub/_test/foo/bar/qux/file"); - assert_is_ignored(true, "_test/foo/bar/qux/file"); - assert_is_ignored(true, "_test/foo/bar/crux/file"); - assert_is_ignored(false, "_test/foo/bar/code/file"); -} - -void test_ignore_path__globs_without_star(void) -{ - cl_git_rewritefile( - "attr/.gitignore", - "*.foo\n" - "**.bar\n" - ); - - assert_is_ignored(true, ".foo"); - assert_is_ignored(true, "xyz.foo"); - assert_is_ignored(true, ".bar"); - assert_is_ignored(true, "x.bar"); - assert_is_ignored(true, "xyz.bar"); - - assert_is_ignored(true, "test/.foo"); - assert_is_ignored(true, "test/x.foo"); - assert_is_ignored(true, "test/xyz.foo"); - assert_is_ignored(true, "test/.bar"); - assert_is_ignored(true, "test/x.bar"); - assert_is_ignored(true, "test/xyz.bar"); -} - -void test_ignore_path__skip_gitignore_directory(void) -{ - cl_git_rewritefile("attr/.git/info/exclude", "/NewFolder\n/NewFolder/NewFolder"); - cl_must_pass(p_unlink("attr/.gitignore")); - cl_assert(!git_fs_path_exists("attr/.gitignore")); - p_mkdir("attr/.gitignore", 0777); - cl_git_mkfile("attr/.gitignore/garbage.txt", "new_file\n"); - - assert_is_ignored(false, "File.txt"); - assert_is_ignored(true, "NewFolder"); - assert_is_ignored(true, "NewFolder/NewFolder"); - assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); -} - -void test_ignore_path__subdirectory_gitignore(void) -{ - cl_must_pass(p_unlink("attr/.gitignore")); - cl_assert(!git_fs_path_exists("attr/.gitignore")); - cl_git_mkfile( - "attr/.gitignore", - "file1\n"); - cl_git_mkfile( - "attr/dir/.gitignore", - "file2/\n"); - - assert_is_ignored(true, "file1"); - assert_is_ignored(true, "dir/file1"); - assert_is_ignored(true, "dir/file2/actual_file"); /* in ignored dir */ - assert_is_ignored(false, "dir/file3"); -} - -void test_ignore_path__expand_tilde_to_homedir(void) -{ - git_config *cfg; - - assert_is_ignored(false, "example.global_with_tilde"); - - cl_fake_home(); - - /* construct fake home with fake global excludes */ - cl_git_mkfile("home/globalexclude", "# found me\n*.global_with_tilde\n"); - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_string(cfg, "core.excludesfile", "~/globalexclude")); - git_config_free(cfg); - - git_attr_cache_flush(g_repo); /* must reset to pick up change */ - - assert_is_ignored(true, "example.global_with_tilde"); - - cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES)); - - cl_fake_home_cleanup(NULL); - - git_attr_cache_flush(g_repo); /* must reset to pick up change */ - - assert_is_ignored(false, "example.global_with_tilde"); -} - -/* Ensure that the .gitignore in the subdirectory only affects - * items in the subdirectory. */ -void test_ignore_path__gitignore_in_subdir(void) -{ - cl_git_rmfile("attr/.gitignore"); - - cl_must_pass(p_mkdir("attr/dir1", 0777)); - cl_must_pass(p_mkdir("attr/dir1/dir2", 0777)); - cl_must_pass(p_mkdir("attr/dir1/dir2/dir3", 0777)); - - cl_git_mkfile("attr/dir1/dir2/dir3/.gitignore", "dir1/\ndir1/subdir/"); - - assert_is_ignored(false, "dir1/file"); - assert_is_ignored(false, "dir1/dir2/file"); - assert_is_ignored(false, "dir1/dir2/dir3/file"); - assert_is_ignored(true, "dir1/dir2/dir3/dir1/file"); - assert_is_ignored(true, "dir1/dir2/dir3/dir1/subdir/foo"); - - if (cl_repo_get_bool(g_repo, "core.ignorecase")) { - cl_git_mkfile("attr/dir1/dir2/dir3/.gitignore", "DiR1/\nDiR1/subdir/\n"); - - assert_is_ignored(false, "dir1/file"); - assert_is_ignored(false, "dir1/dir2/file"); - assert_is_ignored(false, "dir1/dir2/dir3/file"); - assert_is_ignored(true, "dir1/dir2/dir3/dir1/file"); - assert_is_ignored(true, "dir1/dir2/dir3/dir1/subdir/foo"); - } -} - -/* Ensure that files do not match folder cases */ -void test_ignore_path__dont_ignore_files_for_folder(void) -{ - cl_git_rmfile("attr/.gitignore"); - - cl_git_mkfile("attr/dir/.gitignore", "test/\n"); - - /* Create "test" as a file; ensure it is not ignored. */ - cl_git_mkfile("attr/dir/test", "This is a file."); - - assert_is_ignored(false, "dir/test"); - if (cl_repo_get_bool(g_repo, "core.ignorecase")) - assert_is_ignored(false, "dir/TeSt"); - - /* Create "test" as a directory; ensure it is ignored. */ - cl_git_rmfile("attr/dir/test"); - cl_must_pass(p_mkdir("attr/dir/test", 0777)); - - assert_is_ignored(true, "dir/test"); - if (cl_repo_get_bool(g_repo, "core.ignorecase")) - assert_is_ignored(true, "dir/TeSt"); - - /* Remove "test" entirely; ensure it is not ignored. - * (As it doesn't exist, it is not a directory.) - */ - cl_must_pass(p_rmdir("attr/dir/test")); - - assert_is_ignored(false, "dir/test"); - if (cl_repo_get_bool(g_repo, "core.ignorecase")) - assert_is_ignored(false, "dir/TeSt"); -} - -void test_ignore_path__symlink_to_outside(void) -{ -#ifdef GIT_WIN32 - cl_skip(); -#endif - - cl_git_rewritefile("attr/.gitignore", "symlink\n"); - cl_git_mkfile("target", "target"); - cl_git_pass(p_symlink("../target", "attr/symlink")); - assert_is_ignored(true, "symlink"); - assert_is_ignored(true, "lala/../symlink"); -} - -void test_ignore_path__test(void) -{ - cl_git_rewritefile("attr/.gitignore", - "/*/\n" - "!/src\n"); - assert_is_ignored(false, "src/foo.c"); - assert_is_ignored(false, "src/foo/foo.c"); - assert_is_ignored(false, "README.md"); - assert_is_ignored(true, "dist/foo.o"); - assert_is_ignored(true, "bin/foo"); -} - -void test_ignore_path__unignore_dir_succeeds(void) -{ - cl_git_rewritefile("attr/.gitignore", - "*.c\n" - "!src/*.c\n"); - assert_is_ignored(false, "src/foo.c"); - assert_is_ignored(true, "src/foo/foo.c"); -} - -void test_ignore_path__case_insensitive_unignores_previous_rule(void) -{ - git_config *cfg; - - cl_git_rewritefile("attr/.gitignore", - "/case\n" - "!/Case/\n"); - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, "core.ignorecase", true)); - - cl_must_pass(p_mkdir("attr/case", 0755)); - cl_git_mkfile("attr/case/file", "content"); - - assert_is_ignored(false, "case/file"); -} - -void test_ignore_path__case_sensitive_unignore_does_nothing(void) -{ - git_config *cfg; - - cl_git_rewritefile("attr/.gitignore", - "/case\n" - "!/Case/\n"); - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, "core.ignorecase", false)); - - cl_must_pass(p_mkdir("attr/case", 0755)); - cl_git_mkfile("attr/case/file", "content"); - - assert_is_ignored(true, "case/file"); -} - -void test_ignore_path__ignored_subdirfiles_with_subdir_rule(void) -{ - cl_git_rewritefile( - "attr/.gitignore", - "dir/*\n" - "!dir/sub1/sub2/**\n"); - - assert_is_ignored(true, "dir/a.test"); - assert_is_ignored(true, "dir/sub1/a.test"); - assert_is_ignored(true, "dir/sub1/sub2"); -} - -void test_ignore_path__ignored_subdirfiles_with_negations(void) -{ - cl_git_rewritefile( - "attr/.gitignore", - "dir/*\n" - "!dir/a.test\n"); - - assert_is_ignored(false, "dir/a.test"); - assert_is_ignored(true, "dir/b.test"); - assert_is_ignored(true, "dir/sub1/c.test"); -} - -void test_ignore_path__negative_directory_rules_only_match_directories(void) -{ - cl_git_rewritefile( - "attr/.gitignore", - "*\n" - "!/**/\n" - "!*.keep\n" - "!.gitignore\n" - ); - - assert_is_ignored(true, "src"); - assert_is_ignored(true, "src/A"); - assert_is_ignored(false, "src/"); - assert_is_ignored(false, "src/A.keep"); - assert_is_ignored(false, ".gitignore"); -} - -void test_ignore_path__escaped_character(void) -{ - cl_git_rewritefile("attr/.gitignore", "\\c\n"); - assert_is_ignored(true, "c"); - assert_is_ignored(false, "\\c"); -} - -void test_ignore_path__escaped_newline(void) -{ - cl_git_rewritefile( - "attr/.gitignore", - "\\\nnewline\n" - ); - - assert_is_ignored(true, "\nnewline"); -} - -void test_ignore_path__escaped_glob(void) -{ - cl_git_rewritefile("attr/.gitignore", "\\*\n"); - assert_is_ignored(true, "*"); - assert_is_ignored(false, "foo"); -} - -void test_ignore_path__escaped_comments(void) -{ - cl_git_rewritefile( - "attr/.gitignore", - "#foo\n" - "\\#bar\n" - "\\##baz\n" - "\\#\\\\#qux\n" - ); - - assert_is_ignored(false, "#foo"); - assert_is_ignored(true, "#bar"); - assert_is_ignored(false, "\\#bar"); - assert_is_ignored(true, "##baz"); - assert_is_ignored(false, "\\##baz"); - assert_is_ignored(true, "#\\#qux"); - assert_is_ignored(false, "##qux"); - assert_is_ignored(false, "\\##qux"); -} - -void test_ignore_path__escaped_slash(void) -{ - cl_git_rewritefile( - "attr/.gitignore", - "\\\\\n" - "\\\\preceding\n" - "inter\\\\mittent\n" - "trailing\\\\\n" - ); - -#ifndef GIT_WIN32 - assert_is_ignored(true, "\\"); - assert_is_ignored(true, "\\preceding"); -#endif - assert_is_ignored(true, "inter\\mittent"); - assert_is_ignored(true, "trailing\\"); -} - -void test_ignore_path__escaped_space(void) -{ - cl_git_rewritefile( - "attr/.gitignore", - "foo\\\\ \n" - "bar\\\\\\ \n"); - assert_is_ignored(true, "foo\\"); - assert_is_ignored(false, "foo\\ "); - assert_is_ignored(false, "foo\\\\ "); - assert_is_ignored(false, "foo\\\\"); - assert_is_ignored(true, "bar\\ "); - assert_is_ignored(false, "bar\\\\"); - assert_is_ignored(false, "bar\\\\ "); - assert_is_ignored(false, "bar\\\\\\"); - assert_is_ignored(false, "bar\\\\\\ "); -} - -void test_ignore_path__invalid_pattern(void) -{ - cl_git_rewritefile("attr/.gitignore", "["); - assert_is_ignored(false, "[f"); - assert_is_ignored(false, "f"); -} - -void test_ignore_path__negative_prefix_rule(void) -{ - cl_git_rewritefile("attr/.gitignore", "ff*\n!f\n"); - assert_is_ignored(true, "fff"); - assert_is_ignored(true, "ff"); - assert_is_ignored(false, "f"); -} - -void test_ignore_path__negative_more_specific(void) -{ - cl_git_rewritefile("attr/.gitignore", "*.txt\n!/dir/test.txt\n"); - assert_is_ignored(true, "test.txt"); - assert_is_ignored(false, "dir/test.txt"); - assert_is_ignored(true, "outer/dir/test.txt"); -} diff --git a/tests/ignore/status.c b/tests/ignore/status.c deleted file mode 100644 index deb717590..000000000 --- a/tests/ignore/status.c +++ /dev/null @@ -1,1337 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "git2/attr.h" -#include "ignore.h" -#include "attr.h" -#include "status/status_helpers.h" - -static git_repository *g_repo = NULL; - -void test_ignore_status__initialize(void) -{ -} - -void test_ignore_status__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void assert_ignored_( - bool expected, const char *filepath, - const char *file, const char *func, int line) -{ - int is_ignored = 0; - cl_git_expect( - git_status_should_ignore(&is_ignored, g_repo, filepath), 0, file, func, line); - clar__assert( - (expected != 0) == (is_ignored != 0), - file, func, line, "expected != is_ignored", filepath, 1); -} -#define assert_ignored(expected, filepath) \ - assert_ignored_(expected, filepath, __FILE__, __func__, __LINE__) -#define assert_is_ignored(filepath) \ - assert_ignored_(true, filepath, __FILE__, __func__, __LINE__) -#define refute_is_ignored(filepath) \ - assert_ignored_(false, filepath, __FILE__, __func__, __LINE__) - -void test_ignore_status__0(void) -{ - struct { - const char *path; - int expected; - } test_cases[] = { - /* pattern "ign" from .gitignore */ - { "file", 0 }, - { "ign", 1 }, - { "sub", 0 }, - { "sub/file", 0 }, - { "sub/ign", 1 }, - { "sub/ign/file", 1 }, - { "sub/ign/sub", 1 }, - { "sub/ign/sub/file", 1 }, - { "sub/sub", 0 }, - { "sub/sub/file", 0 }, - { "sub/sub/ign", 1 }, - { "sub/sub/sub", 0 }, - /* pattern "dir/" from .gitignore */ - { "dir", 1 }, - { "dir/", 1 }, - { "sub/dir", 1 }, - { "sub/dir/", 1 }, - { "sub/dir/file", 1 }, /* contained in ignored parent */ - { "sub/sub/dir", 0 }, /* dir is not actually a dir, but a file */ - { NULL, 0 } - }, *one_test; - - g_repo = cl_git_sandbox_init("attr"); - - for (one_test = test_cases; one_test->path != NULL; one_test++) - assert_ignored(one_test->expected, one_test->path); - - /* confirm that ignore files were cached */ - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".git/info/exclude")); - cl_assert(git_attr_cache__is_cached( - g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".gitignore")); -} - - -void test_ignore_status__1(void) -{ - g_repo = cl_git_sandbox_init("attr"); - - cl_git_rewritefile("attr/.gitignore", "/*.txt\n/dir/\n"); - git_attr_cache_flush(g_repo); - - assert_is_ignored("root_test4.txt"); - refute_is_ignored("sub/subdir_test2.txt"); - assert_is_ignored("dir"); - assert_is_ignored("dir/"); - refute_is_ignored("sub/dir"); - refute_is_ignored("sub/dir/"); -} - -void test_ignore_status__empty_repo_with_gitignore_rewrite(void) -{ - status_entry_single st; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_mkfile( - "empty_standard_repo/look-ma.txt", "I'm going to be ignored!"); - - memset(&st, 0, sizeof(st)); - cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); - cl_assert(st.count == 1); - cl_assert(st.status == GIT_STATUS_WT_NEW); - - cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt")); - cl_assert(st.status == GIT_STATUS_WT_NEW); - - refute_is_ignored("look-ma.txt"); - - cl_git_rewritefile("empty_standard_repo/.gitignore", "*.nomatch\n"); - - memset(&st, 0, sizeof(st)); - cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); - cl_assert(st.count == 2); - cl_assert(st.status == GIT_STATUS_WT_NEW); - - cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt")); - cl_assert(st.status == GIT_STATUS_WT_NEW); - - refute_is_ignored("look-ma.txt"); - - cl_git_rewritefile("empty_standard_repo/.gitignore", "*.txt\n"); - - memset(&st, 0, sizeof(st)); - cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); - cl_assert(st.count == 2); - cl_assert(st.status == GIT_STATUS_IGNORED); - - cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt")); - cl_assert(st.status == GIT_STATUS_IGNORED); - - assert_is_ignored("look-ma.txt"); -} - -void test_ignore_status__ignore_pattern_contains_space(void) -{ - unsigned int flags; - const mode_t mode = 0777; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_rewritefile("empty_standard_repo/.gitignore", "foo bar.txt\n"); - - cl_git_mkfile( - "empty_standard_repo/foo bar.txt", "I'm going to be ignored!"); - - cl_git_pass(git_status_file(&flags, g_repo, "foo bar.txt")); - cl_assert(flags == GIT_STATUS_IGNORED); - - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/foo", mode)); - cl_git_mkfile("empty_standard_repo/foo/look-ma.txt", "I'm not going to be ignored!"); - - cl_git_pass(git_status_file(&flags, g_repo, "foo/look-ma.txt")); - cl_assert(flags == GIT_STATUS_WT_NEW); -} - -void test_ignore_status__ignore_pattern_ignorecase(void) -{ - unsigned int flags; - bool ignore_case; - git_index *index; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_rewritefile("empty_standard_repo/.gitignore", "a.txt\n"); - - cl_git_mkfile("empty_standard_repo/A.txt", "Differs in case"); - - cl_git_pass(git_repository_index(&index, g_repo)); - ignore_case = (git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0; - git_index_free(index); - - cl_git_pass(git_status_file(&flags, g_repo, "A.txt")); - cl_assert(flags == ignore_case ? GIT_STATUS_IGNORED : GIT_STATUS_WT_NEW); -} - -void test_ignore_status__subdirectories(void) -{ - status_entry_single st; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_mkfile( - "empty_standard_repo/ignore_me", "I'm going to be ignored!"); - - cl_git_rewritefile("empty_standard_repo/.gitignore", "ignore_me\n"); - - memset(&st, 0, sizeof(st)); - cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); - cl_assert_equal_i(2, st.count); - cl_assert(st.status == GIT_STATUS_IGNORED); - - cl_git_pass(git_status_file(&st.status, g_repo, "ignore_me")); - cl_assert(st.status == GIT_STATUS_IGNORED); - - assert_is_ignored("ignore_me"); - - /* I've changed libgit2 so that the behavior here now differs from - * core git but seems to make more sense. In core git, the following - * items are skipped completed, even if --ignored is passed to status. - * It you mirror these steps and run "git status -uall --ignored" then - * you will not see "test/ignore_me/" in the results. - * - * However, we had a couple reports of this as a bug, plus there is a - * similar circumstance where we were differing for core git when you - * used a rooted path for an ignore, so I changed this behavior. - */ - cl_git_pass(git_futils_mkdir_r( - "empty_standard_repo/test/ignore_me", 0775)); - cl_git_mkfile( - "empty_standard_repo/test/ignore_me/file", "I'm going to be ignored!"); - cl_git_mkfile( - "empty_standard_repo/test/ignore_me/file2", "Me, too!"); - - memset(&st, 0, sizeof(st)); - cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); - cl_assert_equal_i(3, st.count); - - cl_git_pass(git_status_file(&st.status, g_repo, "test/ignore_me/file")); - cl_assert(st.status == GIT_STATUS_IGNORED); - - assert_is_ignored("test/ignore_me/file"); -} - -static void make_test_data(const char *reponame, const char **files) -{ - const char **scan; - size_t repolen = strlen(reponame) + 1; - - g_repo = cl_git_sandbox_init(reponame); - - for (scan = files; *scan != NULL; ++scan) { - cl_git_pass(git_futils_mkdir_relative( - *scan + repolen, reponame, - 0777, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST, NULL)); - cl_git_mkfile(*scan, "contents"); - } -} - -static const char *test_repo_1 = "empty_standard_repo"; -static const char *test_files_1[] = { - "empty_standard_repo/dir/a/ignore_me", - "empty_standard_repo/dir/b/ignore_me", - "empty_standard_repo/dir/ignore_me", - "empty_standard_repo/ignore_also/file", - "empty_standard_repo/ignore_me", - "empty_standard_repo/test/ignore_me/file", - "empty_standard_repo/test/ignore_me/file2", - "empty_standard_repo/test/ignore_me/and_me/file", - NULL -}; - -void test_ignore_status__subdirectories_recursion(void) -{ - /* Let's try again with recursing into ignored dirs turned on */ - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts; - static const char *paths_r[] = { - ".gitignore", - "dir/a/ignore_me", - "dir/b/ignore_me", - "dir/ignore_me", - "ignore_also/file", - "ignore_me", - "test/ignore_me/and_me/file", - "test/ignore_me/file", - "test/ignore_me/file2", - }; - static const unsigned int statuses_r[] = { - GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, - GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, - GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, - }; - static const char *paths_nr[] = { - ".gitignore", - "dir/a/ignore_me", - "dir/b/ignore_me", - "dir/ignore_me", - "ignore_also/", - "ignore_me", - "test/ignore_me/", - }; - static const unsigned int statuses_nr[] = { - GIT_STATUS_WT_NEW, - GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, - GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, - }; - - make_test_data(test_repo_1, test_files_1); - cl_git_rewritefile("empty_standard_repo/.gitignore", "ignore_me\n/ignore_also\n"); - - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = 9; - counts.expected_paths = paths_r; - counts.expected_statuses = statuses_r; - - opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; - - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__normal, &counts)); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); - - - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = 7; - counts.expected_paths = paths_nr; - counts.expected_statuses = statuses_nr; - - opts.flags = GIT_STATUS_OPT_DEFAULTS; - - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__normal, &counts)); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -void test_ignore_status__subdirectories_not_at_root(void) -{ - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts; - static const char *paths_1[] = { - "dir/.gitignore", - "dir/a/ignore_me", - "dir/b/ignore_me", - "dir/ignore_me", - "ignore_also/file", - "ignore_me", - "test/.gitignore", - "test/ignore_me/and_me/file", - "test/ignore_me/file", - "test/ignore_me/file2", - }; - static const unsigned int statuses_1[] = { - GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, - GIT_STATUS_IGNORED, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, - GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, - }; - - make_test_data(test_repo_1, test_files_1); - cl_git_rewritefile("empty_standard_repo/dir/.gitignore", "ignore_me\n/ignore_also\n"); - cl_git_rewritefile("empty_standard_repo/test/.gitignore", "and_me\n"); - - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = 10; - counts.expected_paths = paths_1; - counts.expected_statuses = statuses_1; - - opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; - - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__normal, &counts)); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -void test_ignore_status__leading_slash_ignores(void) -{ - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts; - static const char *paths_2[] = { - "dir/.gitignore", - "dir/a/ignore_me", - "dir/b/ignore_me", - "dir/ignore_me", - "ignore_also/file", - "ignore_me", - "test/.gitignore", - "test/ignore_me/and_me/file", - "test/ignore_me/file", - "test/ignore_me/file2", - }; - static const unsigned int statuses_2[] = { - GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, - GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, - GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, - }; - - make_test_data(test_repo_1, test_files_1); - - cl_fake_home(); - cl_git_mkfile("home/.gitignore", "/ignore_me\n"); - { - git_config *cfg; - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_string( - cfg, "core.excludesfile", "~/.gitignore")); - git_config_free(cfg); - } - - cl_git_rewritefile("empty_standard_repo/.git/info/exclude", "/ignore_also\n"); - cl_git_rewritefile("empty_standard_repo/dir/.gitignore", "/ignore_me\n"); - cl_git_rewritefile("empty_standard_repo/test/.gitignore", "/and_me\n"); - - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = 10; - counts.expected_paths = paths_2; - counts.expected_statuses = statuses_2; - - opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; - - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__normal, &counts)); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -void test_ignore_status__multiple_leading_slash(void) -{ - static const char *test_files[] = { - "empty_standard_repo/a.test", - "empty_standard_repo/b.test", - "empty_standard_repo/c.test", - "empty_standard_repo/d.test", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "a.test\n" - "/b.test\n" - "//c.test\n" - "///d.test\n"); - - assert_is_ignored("a.test"); - assert_is_ignored("b.test"); - refute_is_ignored("c.test"); - refute_is_ignored("d.test"); -} - -void test_ignore_status__contained_dir_with_matching_name(void) -{ - static const char *test_files[] = { - "empty_standard_repo/subdir_match/aaa/subdir_match/file", - "empty_standard_repo/subdir_match/zzz_ignoreme", - NULL - }; - static const char *expected_paths[] = { - "subdir_match/.gitignore", - "subdir_match/aaa/subdir_match/file", - "subdir_match/zzz_ignoreme", - }; - static const unsigned int expected_statuses[] = { - GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED - }; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/subdir_match/.gitignore", "*_ignoreme\n"); - - refute_is_ignored("subdir_match/aaa/subdir_match/file"); - assert_is_ignored("subdir_match/zzz_ignoreme"); - - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = 3; - counts.expected_paths = expected_paths; - counts.expected_statuses = expected_statuses; - - opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; - - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__normal, &counts)); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -void test_ignore_status__trailing_slash_star(void) -{ - static const char *test_files[] = { - "empty_standard_repo/file", - "empty_standard_repo/subdir/file", - "empty_standard_repo/subdir/sub2/sub3/file", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/subdir/.gitignore", "/**/*\n"); - - refute_is_ignored("file"); - assert_is_ignored("subdir/sub2/sub3/file"); - assert_is_ignored("subdir/file"); -} - -void test_ignore_status__adding_internal_ignores(void) -{ - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - refute_is_ignored("one.txt"); - refute_is_ignored("two.bar"); - - cl_git_pass(git_ignore_add_rule(g_repo, "*.nomatch\n")); - - refute_is_ignored("one.txt"); - refute_is_ignored("two.bar"); - - cl_git_pass(git_ignore_add_rule(g_repo, "*.txt\n")); - - assert_is_ignored("one.txt"); - refute_is_ignored("two.bar"); - - cl_git_pass(git_ignore_add_rule(g_repo, "*.bar\n")); - - assert_is_ignored("one.txt"); - assert_is_ignored("two.bar"); - - cl_git_pass(git_ignore_clear_internal_rules(g_repo)); - - refute_is_ignored("one.txt"); - refute_is_ignored("two.bar"); - - cl_git_pass(git_ignore_add_rule( - g_repo, "multiple\n*.rules\n# comment line\n*.bar\n")); - - refute_is_ignored("one.txt"); - assert_is_ignored("two.bar"); -} - -void test_ignore_status__add_internal_as_first_thing(void) -{ - const char *add_me = "\n#################\n## Eclipse\n#################\n\n*.pydevproject\n.project\n.metadata\nbin/\ntmp/\n*.tmp\n\n"; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_pass(git_ignore_add_rule(g_repo, add_me)); - - assert_is_ignored("one.tmp"); - refute_is_ignored("two.bar"); -} - -void test_ignore_status__internal_ignores_inside_deep_paths(void) -{ - const char *add_me = "Debug\nthis/is/deep\npatterned*/dir\n"; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_pass(git_ignore_add_rule(g_repo, add_me)); - - assert_is_ignored("Debug"); - assert_is_ignored("and/Debug"); - assert_is_ignored("really/Debug/this/file"); - assert_is_ignored("Debug/what/I/say"); - - refute_is_ignored("and/NoDebug"); - refute_is_ignored("NoDebug/this"); - refute_is_ignored("please/NoDebug/this"); - - assert_is_ignored("this/is/deep"); - /* pattern containing slash gets FNM_PATHNAME so all slashes must match */ - refute_is_ignored("and/this/is/deep"); - assert_is_ignored("this/is/deep/too"); - /* pattern containing slash gets FNM_PATHNAME so all slashes must match */ - refute_is_ignored("but/this/is/deep/and/ignored"); - - refute_is_ignored("this/is/not/deep"); - refute_is_ignored("is/this/not/as/deep"); - refute_is_ignored("this/is/deepish"); - refute_is_ignored("xthis/is/deep"); -} - -void test_ignore_status__automatically_ignore_bad_files(void) -{ - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - assert_is_ignored(".git"); - assert_is_ignored("this/file/."); - assert_is_ignored("path/../funky"); - refute_is_ignored("path/whatever.c"); - - cl_git_pass(git_ignore_add_rule(g_repo, "*.c\n")); - - assert_is_ignored(".git"); - assert_is_ignored("this/file/."); - assert_is_ignored("path/../funky"); - assert_is_ignored("path/whatever.c"); - - cl_git_pass(git_ignore_clear_internal_rules(g_repo)); - - assert_is_ignored(".git"); - assert_is_ignored("this/file/."); - assert_is_ignored("path/../funky"); - refute_is_ignored("path/whatever.c"); -} - -void test_ignore_status__filenames_with_special_prefixes_do_not_interfere_with_status_retrieval(void) -{ - status_entry_single st; - char *test_cases[] = { - "!file", - "#blah", - "[blah]", - "[attr]", - "[attr]blah", - NULL - }; - int i; - - for (i = 0; *(test_cases + i) != NULL; i++) { - git_str file = GIT_STR_INIT; - char *file_name = *(test_cases + i); - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_pass(git_str_joinpath(&file, "empty_standard_repo", file_name)); - cl_git_mkfile(git_str_cstr(&file), "Please don't ignore me!"); - - memset(&st, 0, sizeof(st)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &st)); - cl_assert(st.count == 1); - cl_assert(st.status == GIT_STATUS_WT_NEW); - - cl_git_pass(git_status_file(&st.status, repo, file_name)); - cl_assert(st.status == GIT_STATUS_WT_NEW); - - cl_git_sandbox_cleanup(); - git_str_dispose(&file); - } -} - -void test_ignore_status__issue_1766_negated_ignores(void) -{ - unsigned int status; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_pass(git_futils_mkdir_r( - "empty_standard_repo/a", 0775)); - cl_git_mkfile( - "empty_standard_repo/a/.gitignore", "*\n!.gitignore\n"); - cl_git_mkfile( - "empty_standard_repo/a/ignoreme", "I should be ignored\n"); - - refute_is_ignored("a/.gitignore"); - assert_is_ignored("a/ignoreme"); - - cl_git_pass(git_futils_mkdir_r( - "empty_standard_repo/b", 0775)); - cl_git_mkfile( - "empty_standard_repo/b/.gitignore", "*\n!.gitignore\n"); - cl_git_mkfile( - "empty_standard_repo/b/ignoreme", "I should be ignored\n"); - - refute_is_ignored("b/.gitignore"); - assert_is_ignored("b/ignoreme"); - - /* shouldn't have changed results from first couple either */ - refute_is_ignored("a/.gitignore"); - assert_is_ignored("a/ignoreme"); - - /* status should find the two ignore files and nothing else */ - - cl_git_pass(git_status_file(&status, g_repo, "a/.gitignore")); - cl_assert_equal_i(GIT_STATUS_WT_NEW, (int)status); - - cl_git_pass(git_status_file(&status, g_repo, "a/ignoreme")); - cl_assert_equal_i(GIT_STATUS_IGNORED, (int)status); - - cl_git_pass(git_status_file(&status, g_repo, "b/.gitignore")); - cl_assert_equal_i(GIT_STATUS_WT_NEW, (int)status); - - cl_git_pass(git_status_file(&status, g_repo, "b/ignoreme")); - cl_assert_equal_i(GIT_STATUS_IGNORED, (int)status); - - { - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts; - static const char *paths[] = { - "a/.gitignore", - "a/ignoreme", - "b/.gitignore", - "b/ignoreme", - }; - static const unsigned int statuses[] = { - GIT_STATUS_WT_NEW, - GIT_STATUS_IGNORED, - GIT_STATUS_WT_NEW, - GIT_STATUS_IGNORED, - }; - - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = 4; - counts.expected_paths = paths; - counts.expected_statuses = statuses; - - opts.flags = GIT_STATUS_OPT_DEFAULTS; - - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__normal, &counts)); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); - } -} - -static void add_one_to_index(const char *file) -{ - git_index *index; - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_add_bypath(index, file)); - git_index_free(index); -} - -/* Some further broken scenarios that have been reported */ -void test_ignore_status__more_breakage(void) -{ - static const char *test_files[] = { - "empty_standard_repo/d1/pfx-d2/d3/d4/d5/tracked", - "empty_standard_repo/d1/pfx-d2/d3/d4/d5/untracked", - "empty_standard_repo/d1/pfx-d2/d3/d4/untracked", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "/d1/pfx-*\n" - "!/d1/pfx-d2/\n" - "/d1/pfx-d2/*\n" - "!/d1/pfx-d2/d3/\n" - "/d1/pfx-d2/d3/*\n" - "!/d1/pfx-d2/d3/d4/\n"); - add_one_to_index("d1/pfx-d2/d3/d4/d5/tracked"); - - { - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts; - static const char *files[] = { - ".gitignore", - "d1/pfx-d2/d3/d4/d5/tracked", - "d1/pfx-d2/d3/d4/d5/untracked", - "d1/pfx-d2/d3/d4/untracked", - }; - static const unsigned int statuses[] = { - GIT_STATUS_WT_NEW, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_NEW, - }; - - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = 4; - counts.expected_paths = files; - counts.expected_statuses = statuses; - opts.flags = GIT_STATUS_OPT_DEFAULTS | - GIT_STATUS_OPT_INCLUDE_IGNORED | - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__normal, &counts)); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); - } - - refute_is_ignored("d1/pfx-d2/d3/d4/d5/tracked"); - refute_is_ignored("d1/pfx-d2/d3/d4/d5/untracked"); - refute_is_ignored("d1/pfx-d2/d3/d4/untracked"); -} - -void test_ignore_status__negative_ignores_inside_ignores(void) -{ - static const char *test_files[] = { - "empty_standard_repo/top/mid/btm/tracked", - "empty_standard_repo/top/mid/btm/untracked", - "empty_standard_repo/zoo/bar", - "empty_standard_repo/zoo/foo/bar", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "top\n" - "!top/mid/btm\n" - "zoo/*\n" - "!zoo/bar\n" - "!zoo/foo/bar\n"); - add_one_to_index("top/mid/btm/tracked"); - - { - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts; - static const char *files[] = { - ".gitignore", "top/mid/btm/tracked", "top/mid/btm/untracked", - "zoo/bar", "zoo/foo/bar", - }; - static const unsigned int statuses[] = { - GIT_STATUS_WT_NEW, GIT_STATUS_INDEX_NEW, GIT_STATUS_IGNORED, - GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, - }; - - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = 5; - counts.expected_paths = files; - counts.expected_statuses = statuses; - opts.flags = GIT_STATUS_OPT_DEFAULTS | - GIT_STATUS_OPT_INCLUDE_IGNORED | - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__normal, &counts)); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); - } - - assert_is_ignored("top/mid/btm/tracked"); - assert_is_ignored("top/mid/btm/untracked"); - refute_is_ignored("foo/bar"); -} - -void test_ignore_status__negative_ignores_in_slash_star(void) -{ - git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; - git_status_list *list; - int found_look_ma = 0, found_what_about = 0; - size_t i; - static const char *test_files[] = { - "empty_standard_repo/bin/look-ma.txt", - "empty_standard_repo/bin/what-about-me.txt", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "bin/*\n" - "!bin/w*\n"); - - assert_is_ignored("bin/look-ma.txt"); - refute_is_ignored("bin/what-about-me.txt"); - - status_opts.flags = GIT_STATUS_OPT_DEFAULTS; - cl_git_pass(git_status_list_new(&list, g_repo, &status_opts)); - for (i = 0; i < git_status_list_entrycount(list); i++) { - const git_status_entry *entry = git_status_byindex(list, i); - - if (!strcmp("bin/look-ma.txt", entry->index_to_workdir->new_file.path)) - found_look_ma = 1; - - if (!strcmp("bin/what-about-me.txt", entry->index_to_workdir->new_file.path)) - found_what_about = 1; - } - git_status_list_free(list); - - cl_assert(found_look_ma); - cl_assert(found_what_about); -} - -void test_ignore_status__negative_ignores_without_trailing_slash_inside_ignores(void) -{ - git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; - git_status_list *list; - int found_parent_file = 0, found_parent_child1_file = 0, found_parent_child2_file = 0; - size_t i; - static const char *test_files[] = { - "empty_standard_repo/parent/file.txt", - "empty_standard_repo/parent/force.txt", - "empty_standard_repo/parent/child1/file.txt", - "empty_standard_repo/parent/child2/file.txt", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "parent/*\n" - "!parent/force.txt\n" - "!parent/child1\n" - "!parent/child2/\n"); - - add_one_to_index("parent/force.txt"); - - assert_is_ignored("parent/file.txt"); - refute_is_ignored("parent/force.txt"); - refute_is_ignored("parent/child1/file.txt"); - refute_is_ignored("parent/child2/file.txt"); - - status_opts.flags = GIT_STATUS_OPT_DEFAULTS; - cl_git_pass(git_status_list_new(&list, g_repo, &status_opts)); - for (i = 0; i < git_status_list_entrycount(list); i++) { - const git_status_entry *entry = git_status_byindex(list, i); - - if (!entry->index_to_workdir) - continue; - - if (!strcmp("parent/file.txt", entry->index_to_workdir->new_file.path)) - found_parent_file = 1; - - if (!strcmp("parent/force.txt", entry->index_to_workdir->new_file.path)) - found_parent_file = 1; - - if (!strcmp("parent/child1/file.txt", entry->index_to_workdir->new_file.path)) - found_parent_child1_file = 1; - - if (!strcmp("parent/child2/file.txt", entry->index_to_workdir->new_file.path)) - found_parent_child2_file = 1; - } - git_status_list_free(list); - - cl_assert(found_parent_file); - cl_assert(found_parent_child1_file); - cl_assert(found_parent_child2_file); -} - -void test_ignore_status__negative_directory_ignores(void) -{ - static const char *test_files[] = { - "empty_standard_repo/parent/child1/bar.txt", - "empty_standard_repo/parent/child2/bar.txt", - "empty_standard_repo/parent/child3/foo.txt", - "empty_standard_repo/parent/child4/bar.txt", - "empty_standard_repo/parent/nested/child5/bar.txt", - "empty_standard_repo/parent/nested/child6/bar.txt", - "empty_standard_repo/parent/nested/child7/bar.txt", - "empty_standard_repo/padded_parent/child8/bar.txt", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "foo.txt\n" - "parent/child1\n" - "parent/child2\n" - "parent/child4\n" - "parent/nested/child5\n" - "nested/child6\n" - "nested/child7\n" - "padded_parent/child8\n" - /* test simple exact match */ - "!parent/child1\n" - /* test negating file without negating dir */ - "!parent/child2/bar.txt\n" - /* test negative pattern on dir with its content - * being ignored */ - "!parent/child3\n" - /* test with partial match at end */ - "!child4\n" - /* test with partial match with '/' at end */ - "!nested/child5\n" - /* test with complete match */ - "!nested/child6\n" - /* test with trailing '/' */ - "!child7/\n" - /* test with partial dir match */ - "!_parent/child8\n"); - - refute_is_ignored("parent/child1/bar.txt"); - assert_is_ignored("parent/child2/bar.txt"); - assert_is_ignored("parent/child3/foo.txt"); - refute_is_ignored("parent/child4/bar.txt"); - assert_is_ignored("parent/nested/child5/bar.txt"); - refute_is_ignored("parent/nested/child6/bar.txt"); - refute_is_ignored("parent/nested/child7/bar.txt"); - assert_is_ignored("padded_parent/child8/bar.txt"); -} - -void test_ignore_status__unignore_entry_in_ignored_dir(void) -{ - static const char *test_files[] = { - "empty_standard_repo/bar.txt", - "empty_standard_repo/parent/bar.txt", - "empty_standard_repo/parent/child/bar.txt", - "empty_standard_repo/nested/parent/child/bar.txt", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "bar.txt\n" - "!parent/child/bar.txt\n"); - - assert_is_ignored("bar.txt"); - assert_is_ignored("parent/bar.txt"); - refute_is_ignored("parent/child/bar.txt"); - assert_is_ignored("nested/parent/child/bar.txt"); -} - -void test_ignore_status__do_not_unignore_basename_prefix(void) -{ - static const char *test_files[] = { - "empty_standard_repo/foo_bar.txt", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "foo_bar.txt\n" - "!bar.txt\n"); - - assert_is_ignored("foo_bar.txt"); -} - -void test_ignore_status__filename_with_cr(void) -{ - int ignored; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_mkfile("empty_standard_repo/.gitignore", "Icon\r\r\n"); - - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Icon\r")); - cl_assert_equal_i(1, ignored); - - cl_git_mkfile("empty_standard_repo/.gitignore", "Ico\rn\n"); - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Ico\rn")); - cl_assert_equal_i(1, ignored); - - cl_git_mkfile("empty_standard_repo/.gitignore", "Ico\rn\r\n"); - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Ico\rn")); - cl_assert_equal_i(1, ignored); - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Ico\rn\r")); - cl_assert_equal_i(0, ignored); - - cl_git_mkfile("empty_standard_repo/.gitignore", "Ico\rn\r\r\n"); - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Ico\rn\r")); - cl_assert_equal_i(1, ignored); - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Icon\r")); - cl_assert_equal_i(0, ignored); - - cl_git_mkfile("empty_standard_repo/.gitignore", "Icon\r\n"); - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Icon\r")); - cl_assert_equal_i(0, ignored); - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Icon")); - cl_assert_equal_i(1, ignored); -} - -void test_ignore_status__subdir_doesnt_match_above(void) -{ - int ignored, icase = 0, error; - git_config *cfg; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); - error = git_config_get_bool(&icase, cfg, "core.ignorecase"); - git_config_free(cfg); - if (error == GIT_ENOTFOUND) - error = 0; - - cl_git_pass(error); - - cl_git_pass(p_mkdir("empty_standard_repo/src", 0777)); - cl_git_pass(p_mkdir("empty_standard_repo/src/src", 0777)); - cl_git_mkfile("empty_standard_repo/src/.gitignore", "src\n"); - cl_git_mkfile("empty_standard_repo/.gitignore", ""); - - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "src/test.txt")); - cl_assert_equal_i(0, ignored); - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "src/src/test.txt")); - cl_assert_equal_i(1, ignored); - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "src/foo/test.txt")); - cl_assert_equal_i(0, ignored); - - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "SRC/src/test.txt")); - cl_assert_equal_i(icase, ignored); - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "src/SRC/test.txt")); - cl_assert_equal_i(icase, ignored); -} - -void test_ignore_status__negate_exact_previous(void) -{ - int ignored; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_mkfile("empty_standard_repo/.gitignore", "*.com\ntags\n!tags/\n.buildpath"); - cl_git_mkfile("empty_standard_repo/.buildpath", ""); - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, ".buildpath")); - cl_assert_equal_i(1, ignored); -} - -void test_ignore_status__negate_starstar(void) -{ - int ignored; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_mkfile("empty_standard_repo/.gitignore", - "code/projects/**/packages/*\n" - "!code/projects/**/packages/repositories.config"); - - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/code/projects/foo/bar/packages", 0777)); - cl_git_mkfile("empty_standard_repo/code/projects/foo/bar/packages/repositories.config", ""); - - cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "code/projects/foo/bar/packages/repositories.config")); - cl_assert_equal_i(0, ignored); -} - -void test_ignore_status__ignore_all_toplevel_dirs_include_files(void) -{ - static const char *test_files[] = { - "empty_standard_repo/README.md", - "empty_standard_repo/src/main.c", - "empty_standard_repo/src/foo/foo.c", - "empty_standard_repo/dist/foo.o", - "empty_standard_repo/dist/main.o", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "/*/\n" - "!/src\n"); - - assert_is_ignored("dist/foo.o"); - assert_is_ignored("dist/main.o"); - - refute_is_ignored("README.md"); - refute_is_ignored("src/foo.c"); - refute_is_ignored("src/foo/foo.c"); -} - -void test_ignore_status__subdir_ignore_all_toplevel_dirs_include_files(void) -{ - static const char *test_files[] = { - "empty_standard_repo/project/README.md", - "empty_standard_repo/project/src/main.c", - "empty_standard_repo/project/src/foo/foo.c", - "empty_standard_repo/project/dist/foo.o", - "empty_standard_repo/project/dist/main.o", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/project/.gitignore", - "/*/\n" - "!/src\n"); - - assert_is_ignored("project/dist/foo.o"); - assert_is_ignored("project/dist/main.o"); - - refute_is_ignored("project/src/foo.c"); - refute_is_ignored("project/src/foo/foo.c"); - refute_is_ignored("project/README.md"); -} - -void test_ignore_status__subdir_ignore_everything_except_certain_files(void) -{ - static const char *test_files[] = { - "empty_standard_repo/project/README.md", - "empty_standard_repo/project/some_file", - "empty_standard_repo/project/src/main.c", - "empty_standard_repo/project/src/foo/foo.c", - "empty_standard_repo/project/dist/foo.o", - "empty_standard_repo/project/dist/main.o", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/project/.gitignore", - "/*\n" - "!/src\n" - "!README.md\n"); - - assert_is_ignored("project/some_file"); - assert_is_ignored("project/dist/foo.o"); - assert_is_ignored("project/dist/main.o"); - - refute_is_ignored("project/README.md"); - refute_is_ignored("project/src/foo.c"); - refute_is_ignored("project/src/foo/foo.c"); -} - -void test_ignore_status__deeper(void) -{ - const char *test_files[] = { - "empty_standard_repo/foo.data", - "empty_standard_repo/bar.data", - "empty_standard_repo/dont_ignore/foo.data", - "empty_standard_repo/dont_ignore/bar.data", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile("empty_standard_repo/.gitignore", - "*.data\n" - "!dont_ignore/*.data\n"); - - assert_is_ignored("foo.data"); - assert_is_ignored("bar.data"); - - refute_is_ignored("dont_ignore/foo.data"); - refute_is_ignored("dont_ignore/bar.data"); -} - -void test_ignore_status__unignored_dir_with_ignored_contents(void) -{ - static const char *test_files[] = { - "empty_standard_repo/dir/a.test", - "empty_standard_repo/dir/subdir/a.test", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "*.test\n" - "!dir/*\n"); - - refute_is_ignored("dir/a.test"); - assert_is_ignored("dir/subdir/a.test"); -} - -void test_ignore_status__unignored_subdirs(void) -{ - static const char *test_files[] = { - "empty_standard_repo/dir/a.test", - "empty_standard_repo/dir/subdir/a.test", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "dir/*\n" - "!dir/*/\n"); - - assert_is_ignored("dir/a.test"); - refute_is_ignored("dir/subdir/a.test"); -} - -void test_ignore_status__skips_bom(void) -{ - static const char *test_files[] = { - "empty_standard_repo/a.test", - "empty_standard_repo/b.test", - "empty_standard_repo/c.test", - "empty_standard_repo/foo.txt", - "empty_standard_repo/bar.txt", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - "\xEF\xBB\xBF*.test\n"); - - assert_is_ignored("a.test"); - assert_is_ignored("b.test"); - assert_is_ignored("c.test"); - refute_is_ignored("foo.txt"); - refute_is_ignored("bar.txt"); -} - -void test_ignore_status__leading_spaces_are_significant(void) -{ - static const char *test_files[] = { - "empty_standard_repo/a.test", - "empty_standard_repo/b.test", - "empty_standard_repo/c.test", - "empty_standard_repo/d.test", - NULL - }; - - make_test_data("empty_standard_repo", test_files); - cl_git_mkfile( - "empty_standard_repo/.gitignore", - " a.test\n" - "# this is a comment\n" - "b.test\n" - "\tc.test\n" - " # not a comment\n" - "d.test\n"); - - refute_is_ignored("a.test"); - assert_is_ignored(" a.test"); - refute_is_ignored("# this is a comment"); - assert_is_ignored("b.test"); - refute_is_ignored("c.test"); - assert_is_ignored("\tc.test"); - assert_is_ignored(" # not a comment"); - assert_is_ignored("d.test"); -} - -void test_ignore_status__override_nested_wildcard_unignore(void) -{ - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - const git_status_entry *status; - - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/dir", 0777)); - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/dir/subdir", 0777)); - cl_git_mkfile("empty_standard_repo/.gitignore", "a.test\n"); - cl_git_mkfile("empty_standard_repo/dir/.gitignore", "!*.test\n"); - cl_git_mkfile("empty_standard_repo/dir/subdir/.gitignore", "a.test\n"); - cl_git_mkfile("empty_standard_repo/dir/a.test", "pong"); - cl_git_mkfile("empty_standard_repo/dir/subdir/a.test", "pong"); - - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - - cl_git_pass(git_status_list_new(&statuslist, repo, &opts)); - cl_assert_equal_sz(4, git_status_list_entrycount(statuslist)); - - status = git_status_byindex(statuslist, 0); - cl_assert(status != NULL); - cl_assert_equal_s(".gitignore", status->index_to_workdir->old_file.path); - cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status); - - status = git_status_byindex(statuslist, 1); - cl_assert(status != NULL); - cl_assert_equal_s("dir/.gitignore", status->index_to_workdir->old_file.path); - cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status); - - status = git_status_byindex(statuslist, 2); - cl_assert(status != NULL); - cl_assert_equal_s("dir/a.test", status->index_to_workdir->old_file.path); - cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status); - - status = git_status_byindex(statuslist, 3); - cl_assert(status != NULL); - cl_assert_equal_s("dir/subdir/.gitignore", status->index_to_workdir->old_file.path); - cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status); - - git_status_list_free(statuslist); -} diff --git a/tests/index/add.c b/tests/index/add.c deleted file mode 100644 index f101ea266..000000000 --- a/tests/index/add.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "clar_libgit2.h" - -static git_repository *g_repo = NULL; -static git_index *g_index = NULL; - -static const char *valid_blob_id = "fa49b077972391ad58037050f2a75f74e3671e92"; -static const char *valid_tree_id = "181037049a54a1eb5fab404658a3a250b44335d7"; -static const char *valid_commit_id = "763d71aadf09a7951596c9746c024e7eece7c7af"; -static const char *invalid_id = "1234567890123456789012345678901234567890"; - -void test_index_add__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); - cl_git_pass(git_repository_index(&g_index, g_repo)); -} - -void test_index_add__cleanup(void) -{ - git_index_free(g_index); - cl_git_sandbox_cleanup(); - g_repo = NULL; - - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 1)); -} - -static void test_add_entry( - bool should_succeed, const char *idstr, git_filemode_t mode) -{ - git_index_entry entry = {{0}}; - - cl_git_pass(git_oid_fromstr(&entry.id, idstr)); - - entry.path = mode == GIT_FILEMODE_TREE ? "test_folder" : "test_file"; - entry.mode = mode; - - if (should_succeed) - cl_git_pass(git_index_add(g_index, &entry)); - else - cl_git_fail(git_index_add(g_index, &entry)); -} - -void test_index_add__invalid_entries_succeeds_by_default(void) -{ - /* - * Ensure that there is validation on object ids by default - */ - - /* ensure that we can add some actually good entries */ - test_add_entry(true, valid_blob_id, GIT_FILEMODE_BLOB); - test_add_entry(true, valid_blob_id, GIT_FILEMODE_BLOB_EXECUTABLE); - test_add_entry(true, valid_blob_id, GIT_FILEMODE_LINK); - - /* test that we fail to add some invalid (missing) blobs and trees */ - test_add_entry(false, invalid_id, GIT_FILEMODE_BLOB); - test_add_entry(false, invalid_id, GIT_FILEMODE_BLOB_EXECUTABLE); - test_add_entry(false, invalid_id, GIT_FILEMODE_LINK); - - /* test that we validate the types of objects */ - test_add_entry(false, valid_commit_id, GIT_FILEMODE_BLOB); - test_add_entry(false, valid_tree_id, GIT_FILEMODE_BLOB_EXECUTABLE); - test_add_entry(false, valid_commit_id, GIT_FILEMODE_LINK); - - /* - * Ensure that there we can disable validation - */ - - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 0)); - - /* ensure that we can add some actually good entries */ - test_add_entry(true, valid_blob_id, GIT_FILEMODE_BLOB); - test_add_entry(true, valid_blob_id, GIT_FILEMODE_BLOB_EXECUTABLE); - test_add_entry(true, valid_blob_id, GIT_FILEMODE_LINK); - - /* test that we can now add some invalid (missing) blobs and trees */ - test_add_entry(true, invalid_id, GIT_FILEMODE_BLOB); - test_add_entry(true, invalid_id, GIT_FILEMODE_BLOB_EXECUTABLE); - test_add_entry(true, invalid_id, GIT_FILEMODE_LINK); - - /* test that we do not validate the types of objects */ - test_add_entry(true, valid_commit_id, GIT_FILEMODE_BLOB); - test_add_entry(true, valid_tree_id, GIT_FILEMODE_BLOB_EXECUTABLE); - test_add_entry(true, valid_commit_id, GIT_FILEMODE_LINK); -} - diff --git a/tests/index/addall.c b/tests/index/addall.c deleted file mode 100644 index 6f95f6386..000000000 --- a/tests/index/addall.c +++ /dev/null @@ -1,496 +0,0 @@ -#include "clar_libgit2.h" -#include "../status/status_helpers.h" -#include "posix.h" -#include "futils.h" - -static git_repository *g_repo = NULL; -#define TEST_DIR "addall" - -void test_index_addall__initialize(void) -{ -} - -void test_index_addall__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -#define STATUS_INDEX_FLAGS \ - (GIT_STATUS_INDEX_NEW | GIT_STATUS_INDEX_MODIFIED | \ - GIT_STATUS_INDEX_DELETED | GIT_STATUS_INDEX_RENAMED | \ - GIT_STATUS_INDEX_TYPECHANGE) - -#define STATUS_WT_FLAGS \ - (GIT_STATUS_WT_NEW | GIT_STATUS_WT_MODIFIED | \ - GIT_STATUS_WT_DELETED | GIT_STATUS_WT_TYPECHANGE | \ - GIT_STATUS_WT_RENAMED) - -typedef struct { - size_t index_adds; - size_t index_dels; - size_t index_mods; - size_t wt_adds; - size_t wt_dels; - size_t wt_mods; - size_t ignores; - size_t conflicts; -} index_status_counts; - -static int index_status_cb( - const char *path, unsigned int status_flags, void *payload) -{ - index_status_counts *vals = payload; - - /* cb_status__print(path, status_flags, NULL); */ - - GIT_UNUSED(path); - - if (status_flags & GIT_STATUS_INDEX_NEW) - vals->index_adds++; - if (status_flags & GIT_STATUS_INDEX_MODIFIED) - vals->index_mods++; - if (status_flags & GIT_STATUS_INDEX_DELETED) - vals->index_dels++; - if (status_flags & GIT_STATUS_INDEX_TYPECHANGE) - vals->index_mods++; - - if (status_flags & GIT_STATUS_WT_NEW) - vals->wt_adds++; - if (status_flags & GIT_STATUS_WT_MODIFIED) - vals->wt_mods++; - if (status_flags & GIT_STATUS_WT_DELETED) - vals->wt_dels++; - if (status_flags & GIT_STATUS_WT_TYPECHANGE) - vals->wt_mods++; - - if (status_flags & GIT_STATUS_IGNORED) - vals->ignores++; - if (status_flags & GIT_STATUS_CONFLICTED) - vals->conflicts++; - - return 0; -} - -static void check_status_at_line( - git_repository *repo, - size_t index_adds, size_t index_dels, size_t index_mods, - size_t wt_adds, size_t wt_dels, size_t wt_mods, size_t ignores, - size_t conflicts, const char *file, const char *func, int line) -{ - index_status_counts vals; - - memset(&vals, 0, sizeof(vals)); - - cl_git_pass(git_status_foreach(repo, index_status_cb, &vals)); - - clar__assert_equal( - file,func,line,"wrong index adds", 1, "%"PRIuZ, index_adds, vals.index_adds); - clar__assert_equal( - file,func,line,"wrong index dels", 1, "%"PRIuZ, index_dels, vals.index_dels); - clar__assert_equal( - file,func,line,"wrong index mods", 1, "%"PRIuZ, index_mods, vals.index_mods); - clar__assert_equal( - file,func,line,"wrong workdir adds", 1, "%"PRIuZ, wt_adds, vals.wt_adds); - clar__assert_equal( - file,func,line,"wrong workdir dels", 1, "%"PRIuZ, wt_dels, vals.wt_dels); - clar__assert_equal( - file,func,line,"wrong workdir mods", 1, "%"PRIuZ, wt_mods, vals.wt_mods); - clar__assert_equal( - file,func,line,"wrong ignores", 1, "%"PRIuZ, ignores, vals.ignores); - clar__assert_equal( - file,func,line,"wrong conflicts", 1, "%"PRIuZ, conflicts, vals.conflicts); -} - -#define check_status(R,IA,ID,IM,WA,WD,WM,IG,C) \ - check_status_at_line(R,IA,ID,IM,WA,WD,WM,IG,C,__FILE__,__func__,__LINE__) - -static void check_stat_data(git_index *index, const char *path, bool match) -{ - const git_index_entry *entry; - struct stat st; - - cl_must_pass(p_lstat(path, &st)); - - /* skip repo base dir name */ - while (*path != '/') - ++path; - ++path; - - entry = git_index_get_bypath(index, path, 0); - cl_assert(entry); - - if (match) { - cl_assert(st.st_ctime == entry->ctime.seconds); - cl_assert(st.st_mtime == entry->mtime.seconds); - cl_assert(st.st_size == entry->file_size); - cl_assert((uint32_t)st.st_uid == entry->uid); - cl_assert((uint32_t)st.st_gid == entry->gid); - cl_assert_equal_i_fmt( - GIT_MODE_TYPE(st.st_mode), GIT_MODE_TYPE(entry->mode), "%07o"); - if (cl_is_chmod_supported()) - cl_assert_equal_b( - GIT_PERMS_IS_EXEC(st.st_mode), GIT_PERMS_IS_EXEC(entry->mode)); - } else { - /* most things will still match */ - cl_assert(st.st_size != entry->file_size); - /* would check mtime, but with second resolution it won't work :( */ - } -} - -static void addall_create_test_repo(bool check_every_step) -{ - g_repo = cl_git_sandbox_init_new(TEST_DIR); - - if (check_every_step) - check_status(g_repo, 0, 0, 0, 0, 0, 0, 0, 0); - - cl_git_mkfile(TEST_DIR "/file.foo", "a file"); - if (check_every_step) - check_status(g_repo, 0, 0, 0, 1, 0, 0, 0, 0); - - cl_git_mkfile(TEST_DIR "/.gitignore", "*.foo\n"); - if (check_every_step) - check_status(g_repo, 0, 0, 0, 1, 0, 0, 1, 0); - - cl_git_mkfile(TEST_DIR "/file.bar", "another file"); - if (check_every_step) - check_status(g_repo, 0, 0, 0, 2, 0, 0, 1, 0); -} - -void test_index_addall__repo_lifecycle(void) -{ - int error; - git_index *index; - git_strarray paths = { NULL, 0 }; - char *strs[1]; - - addall_create_test_repo(true); - - cl_git_pass(git_repository_index(&index, g_repo)); - - strs[0] = "file.*"; - paths.strings = strs; - paths.count = 1; - - cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); - cl_git_pass(git_index_write(index)); - check_stat_data(index, TEST_DIR "/file.bar", true); - check_status(g_repo, 1, 0, 0, 1, 0, 0, 1, 0); - - cl_git_rewritefile(TEST_DIR "/file.bar", "new content for file"); - check_stat_data(index, TEST_DIR "/file.bar", false); - check_status(g_repo, 1, 0, 0, 1, 0, 1, 1, 0); - - cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one"); - cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one"); - cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one"); - check_status(g_repo, 1, 0, 0, 4, 0, 1, 1, 0); - - cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); - check_stat_data(index, TEST_DIR "/file.bar", true); - check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0); - - cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); - cl_git_pass(git_index_write(index)); - check_stat_data(index, TEST_DIR "/file.zzz", true); - check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); - - cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "first commit"); - check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); - - if (cl_repo_get_bool(g_repo, "core.filemode")) { - cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); - cl_must_pass(p_chmod(TEST_DIR "/file.zzz", 0777)); - cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); - check_status(g_repo, 0, 0, 1, 3, 0, 0, 1, 0); - - /* go back to what we had before */ - cl_must_pass(p_chmod(TEST_DIR "/file.zzz", 0666)); - cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); - check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); - } - - - /* attempt to add an ignored file - does nothing */ - strs[0] = "file.foo"; - cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); - cl_git_pass(git_index_write(index)); - check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); - - /* add with check - should generate error */ - error = git_index_add_all( - index, &paths, GIT_INDEX_ADD_CHECK_PATHSPEC, NULL, NULL); - cl_assert_equal_i(GIT_EINVALIDSPEC, error); - cl_git_pass(git_index_write(index)); - check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); - - /* add with force - should allow */ - cl_git_pass(git_index_add_all( - index, &paths, GIT_INDEX_ADD_FORCE, NULL, NULL)); - cl_git_pass(git_index_write(index)); - check_stat_data(index, TEST_DIR "/file.foo", true); - check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0); - - /* now it's in the index, so regular add should work */ - cl_git_rewritefile(TEST_DIR "/file.foo", "new content for file"); - check_stat_data(index, TEST_DIR "/file.foo", false); - check_status(g_repo, 1, 0, 0, 3, 0, 1, 0, 0); - - cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); - cl_git_pass(git_index_write(index)); - check_stat_data(index, TEST_DIR "/file.foo", true); - check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0); - - cl_git_pass(git_index_add_bypath(index, "more.zzz")); - check_stat_data(index, TEST_DIR "/more.zzz", true); - check_status(g_repo, 2, 0, 0, 2, 0, 0, 0, 0); - - cl_git_rewritefile(TEST_DIR "/file.zzz", "new content for file"); - check_status(g_repo, 2, 0, 0, 2, 0, 1, 0, 0); - - cl_git_pass(git_index_add_bypath(index, "file.zzz")); - check_stat_data(index, TEST_DIR "/file.zzz", true); - check_status(g_repo, 2, 0, 1, 2, 0, 0, 0, 0); - - strs[0] = "*.zzz"; - cl_git_pass(git_index_remove_all(index, &paths, NULL, NULL)); - check_status(g_repo, 1, 1, 0, 4, 0, 0, 0, 0); - - cl_git_pass(git_index_add_bypath(index, "file.zzz")); - check_status(g_repo, 1, 0, 1, 3, 0, 0, 0, 0); - - cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "second commit"); - check_status(g_repo, 0, 0, 0, 3, 0, 0, 0, 0); - - cl_must_pass(p_unlink(TEST_DIR "/file.zzz")); - check_status(g_repo, 0, 0, 0, 3, 1, 0, 0, 0); - - /* update_all should be able to remove entries */ - cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); - check_status(g_repo, 0, 1, 0, 3, 0, 0, 0, 0); - - strs[0] = "*"; - cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); - cl_git_pass(git_index_write(index)); - check_status(g_repo, 3, 1, 0, 0, 0, 0, 0, 0); - - /* must be able to remove at any position while still updating other files */ - cl_must_pass(p_unlink(TEST_DIR "/.gitignore")); - cl_git_rewritefile(TEST_DIR "/file.zzz", "reconstructed file"); - cl_git_rewritefile(TEST_DIR "/more.zzz", "altered file reality"); - check_status(g_repo, 3, 1, 0, 1, 1, 1, 0, 0); - - cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); - check_status(g_repo, 2, 1, 0, 1, 0, 0, 0, 0); - /* this behavior actually matches 'git add -u' where "file.zzz" has - * been removed from the index, so when you go to update, even though - * it exists in the HEAD, it is not re-added to the index, leaving it - * as a DELETE when comparing HEAD to index and as an ADD comparing - * index to worktree - */ - - git_index_free(index); -} - -void test_index_addall__files_in_folders(void) -{ - git_index *index; - - addall_create_test_repo(true); - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); - cl_git_pass(git_index_write(index)); - check_stat_data(index, TEST_DIR "/file.bar", true); - check_status(g_repo, 2, 0, 0, 0, 0, 0, 1, 0); - - cl_must_pass(p_mkdir(TEST_DIR "/subdir", 0777)); - cl_git_mkfile(TEST_DIR "/subdir/file", "hello!\n"); - check_status(g_repo, 2, 0, 0, 1, 0, 0, 1, 0); - - cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); - cl_git_pass(git_index_write(index)); - check_status(g_repo, 3, 0, 0, 0, 0, 0, 1, 0); - - git_index_free(index); -} - -void test_index_addall__hidden_files(void) -{ -#ifdef GIT_WIN32 - git_index *index; - - addall_create_test_repo(true); - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); - cl_git_pass(git_index_write(index)); - check_stat_data(index, TEST_DIR "/file.bar", true); - check_status(g_repo, 2, 0, 0, 0, 0, 0, 1, 0); - - cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one"); - cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one"); - cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one"); - - check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); - - cl_git_pass(git_win32__set_hidden(TEST_DIR "/file.zzz", true)); - cl_git_pass(git_win32__set_hidden(TEST_DIR "/more.zzz", true)); - cl_git_pass(git_win32__set_hidden(TEST_DIR "/other.zzz", true)); - - check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); - - cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); - cl_git_pass(git_index_write(index)); - check_stat_data(index, TEST_DIR "/file.bar", true); - check_status(g_repo, 5, 0, 0, 0, 0, 0, 1, 0); - - git_index_free(index); -#endif -} - -static int addall_match_prefix( - const char *path, const char *matched_pathspec, void *payload) -{ - GIT_UNUSED(matched_pathspec); - return !git__prefixcmp(path, payload) ? 0 : 1; -} - -static int addall_match_suffix( - const char *path, const char *matched_pathspec, void *payload) -{ - GIT_UNUSED(matched_pathspec); - return !git__suffixcmp(path, payload) ? 0 : 1; -} - -static int addall_cancel_at( - const char *path, const char *matched_pathspec, void *payload) -{ - GIT_UNUSED(matched_pathspec); - return !strcmp(path, payload) ? -123 : 0; -} - -void test_index_addall__callback_filtering(void) -{ - git_index *index; - - addall_create_test_repo(false); - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass( - git_index_add_all(index, NULL, 0, addall_match_prefix, "file.")); - cl_git_pass(git_index_write(index)); - check_stat_data(index, TEST_DIR "/file.bar", true); - check_status(g_repo, 1, 0, 0, 1, 0, 0, 1, 0); - - cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one"); - cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one"); - cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one"); - - cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); - check_stat_data(index, TEST_DIR "/file.bar", true); - check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0); - - cl_git_pass( - git_index_add_all(index, NULL, 0, addall_match_prefix, "other")); - cl_git_pass(git_index_write(index)); - check_stat_data(index, TEST_DIR "/other.zzz", true); - check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); - - cl_git_pass( - git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz")); - cl_git_pass(git_index_write(index)); - check_status(g_repo, 4, 0, 0, 1, 0, 0, 1, 0); - - cl_git_pass( - git_index_remove_all(index, NULL, addall_match_suffix, ".zzz")); - check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0); - - cl_git_fail_with( - git_index_add_all(index, NULL, 0, addall_cancel_at, "more.zzz"), -123); - check_status(g_repo, 3, 0, 0, 2, 0, 0, 1, 0); - - cl_git_fail_with( - git_index_add_all(index, NULL, 0, addall_cancel_at, "other.zzz"), -123); - check_status(g_repo, 4, 0, 0, 1, 0, 0, 1, 0); - - cl_git_pass( - git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz")); - cl_git_pass(git_index_write(index)); - check_status(g_repo, 5, 0, 0, 0, 0, 0, 1, 0); - - cl_must_pass(p_unlink(TEST_DIR "/file.zzz")); - cl_must_pass(p_unlink(TEST_DIR "/more.zzz")); - cl_must_pass(p_unlink(TEST_DIR "/other.zzz")); - - cl_git_fail_with( - git_index_update_all(index, NULL, addall_cancel_at, "more.zzz"), -123); - /* file.zzz removed from index (so Index Adds 5 -> 4) and - * more.zzz + other.zzz removed (so Worktree Dels 0 -> 2) */ - check_status(g_repo, 4, 0, 0, 0, 2, 0, 1, 0); - - cl_git_fail_with( - git_index_update_all(index, NULL, addall_cancel_at, "other.zzz"), -123); - /* more.zzz removed from index (so Index Adds 4 -> 3) and - * Just other.zzz removed (so Worktree Dels 2 -> 1) */ - check_status(g_repo, 3, 0, 0, 0, 1, 0, 1, 0); - - git_index_free(index); -} - -void test_index_addall__adds_conflicts(void) -{ - git_index *index; - git_reference *ref; - git_annotated_commit *annotated; - - g_repo = cl_git_sandbox_init("merge-resolve"); - cl_git_pass(git_repository_index(&index, g_repo)); - - check_status(g_repo, 0, 0, 0, 0, 0, 0, 0, 0); - - cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/branch")); - cl_git_pass(git_annotated_commit_from_ref(&annotated, g_repo, ref)); - - cl_git_pass(git_merge(g_repo, (const git_annotated_commit**)&annotated, 1, NULL, NULL)); - check_status(g_repo, 0, 1, 2, 0, 0, 0, 0, 1); - - cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); - cl_git_pass(git_index_write(index)); - check_status(g_repo, 0, 1, 3, 0, 0, 0, 0, 0); - - git_annotated_commit_free(annotated); - git_reference_free(ref); - git_index_free(index); -} - -void test_index_addall__removes_deleted_conflicted_files(void) -{ - git_index *index; - git_reference *ref; - git_annotated_commit *annotated; - - g_repo = cl_git_sandbox_init("merge-resolve"); - cl_git_pass(git_repository_index(&index, g_repo)); - - check_status(g_repo, 0, 0, 0, 0, 0, 0, 0, 0); - - cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/branch")); - cl_git_pass(git_annotated_commit_from_ref(&annotated, g_repo, ref)); - - cl_git_pass(git_merge(g_repo, (const git_annotated_commit**)&annotated, 1, NULL, NULL)); - check_status(g_repo, 0, 1, 2, 0, 0, 0, 0, 1); - - cl_git_rmfile("merge-resolve/conflicting.txt"); - - cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); - cl_git_pass(git_index_write(index)); - check_status(g_repo, 0, 2, 2, 0, 0, 0, 0, 0); - - git_annotated_commit_free(annotated); - git_reference_free(ref); - git_index_free(index); -} diff --git a/tests/index/bypath.c b/tests/index/bypath.c deleted file mode 100644 index b32a0a789..000000000 --- a/tests/index/bypath.c +++ /dev/null @@ -1,359 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" -#include "../submodule/submodule_helpers.h" - -static git_repository *g_repo; -static git_index *g_idx; - -void test_index_bypath__initialize(void) -{ - g_repo = setup_fixture_submod2(); - cl_git_pass(git_repository_index__weakptr(&g_idx, g_repo)); -} - -void test_index_bypath__cleanup(void) -{ - g_repo = NULL; - g_idx = NULL; -} - -void test_index_bypath__add_directory(void) -{ - cl_git_fail_with(GIT_EDIRECTORY, git_index_add_bypath(g_idx, "just_a_dir")); -} - -void test_index_bypath__add_submodule(void) -{ - unsigned int status; - const char *sm_name = "sm_changed_head"; - - cl_git_pass(git_submodule_status(&status, g_repo, sm_name, 0)); - cl_assert_equal_i(GIT_SUBMODULE_STATUS_WD_MODIFIED, status & GIT_SUBMODULE_STATUS_WD_MODIFIED); - cl_git_pass(git_index_add_bypath(g_idx, sm_name)); - cl_git_pass(git_submodule_status(&status, g_repo, sm_name, 0)); - cl_assert_equal_i(0, status & GIT_SUBMODULE_STATUS_WD_MODIFIED); -} - -void test_index_bypath__add_submodule_unregistered(void) -{ - const char *sm_name = "not-submodule"; - const char *sm_head = "68e92c611b80ee1ed8f38314ff9577f0d15b2444"; - const git_index_entry *entry; - - cl_git_pass(git_index_add_bypath(g_idx, sm_name)); - - cl_assert(entry = git_index_get_bypath(g_idx, sm_name, 0)); - cl_assert_equal_s(sm_head, git_oid_tostr_s(&entry->id)); - cl_assert_equal_s(sm_name, entry->path); -} - -void test_index_bypath__add_hidden(void) -{ -#ifdef GIT_WIN32 - const git_index_entry *entry; - bool hidden; - - cl_git_mkfile("submod2/hidden_file", "you can't see me"); - - cl_git_pass(git_win32__hidden(&hidden, "submod2/hidden_file")); - cl_assert(!hidden); - - cl_git_pass(git_win32__set_hidden("submod2/hidden_file", true)); - - cl_git_pass(git_win32__hidden(&hidden, "submod2/hidden_file")); - cl_assert(hidden); - - cl_git_pass(git_index_add_bypath(g_idx, "hidden_file")); - - cl_assert(entry = git_index_get_bypath(g_idx, "hidden_file", 0)); - cl_assert_equal_i(GIT_FILEMODE_BLOB, entry->mode); -#endif -} - -void test_index_bypath__add_keeps_existing_case(void) -{ - const git_index_entry *entry; - - if (!cl_repo_get_bool(g_repo, "core.ignorecase")) - clar__skip(); - - cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file"); - cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/file1.txt")); - - cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/file1.txt", 0)); - cl_assert_equal_s("just_a_dir/file1.txt", entry->path); - - cl_git_rewritefile("submod2/just_a_dir/file1.txt", "Updated!"); - cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/FILE1.txt")); - - cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/FILE1.txt", 0)); - cl_assert_equal_s("just_a_dir/file1.txt", entry->path); -} - -void test_index_bypath__add_honors_existing_case(void) -{ - const git_index_entry *entry; - - if (!cl_repo_get_bool(g_repo, "core.ignorecase")) - clar__skip(); - - cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file"); - cl_git_mkfile("submod2/just_a_dir/file2.txt", "This is another file"); - cl_git_mkfile("submod2/just_a_dir/file3.txt", "This is another file"); - cl_git_mkfile("submod2/just_a_dir/file4.txt", "And another file"); - - cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/File1.txt")); - cl_git_pass(git_index_add_bypath(g_idx, "JUST_A_DIR/file2.txt")); - cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/FILE3.txt")); - - cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/File1.txt", 0)); - cl_assert_equal_s("just_a_dir/File1.txt", entry->path); - - cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/file2.txt", 0)); - cl_assert_equal_s("just_a_dir/file2.txt", entry->path); - - cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/FILE3.txt", 0)); - cl_assert_equal_s("just_a_dir/FILE3.txt", entry->path); - - cl_git_rewritefile("submod2/just_a_dir/file3.txt", "Rewritten"); - cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/file3.txt")); - - cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/file3.txt", 0)); - cl_assert_equal_s("just_a_dir/FILE3.txt", entry->path); -} - -void test_index_bypath__add_honors_existing_case_2(void) -{ - git_index_entry dummy = { { 0 } }; - const git_index_entry *entry; - - if (!cl_repo_get_bool(g_repo, "core.ignorecase")) - clar__skip(); - - dummy.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_oid_fromstr(&dummy.id, "f990a25a74d1a8281ce2ab018ea8df66795cd60b")); - - /* note that `git_index_add` does no checking to canonical directories */ - dummy.path = "Just_a_dir/file0.txt"; - cl_git_pass(git_index_add(g_idx, &dummy)); - - dummy.path = "just_a_dir/fileA.txt"; - cl_git_pass(git_index_add(g_idx, &dummy)); - - dummy.path = "Just_A_Dir/fileB.txt"; - cl_git_pass(git_index_add(g_idx, &dummy)); - - dummy.path = "JUST_A_DIR/fileC.txt"; - cl_git_pass(git_index_add(g_idx, &dummy)); - - dummy.path = "just_A_dir/fileD.txt"; - cl_git_pass(git_index_add(g_idx, &dummy)); - - dummy.path = "JUST_a_DIR/fileE.txt"; - cl_git_pass(git_index_add(g_idx, &dummy)); - - cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file"); - cl_git_mkfile("submod2/just_a_dir/file2.txt", "This is another file"); - cl_git_mkfile("submod2/just_a_dir/file3.txt", "This is another file"); - cl_git_mkfile("submod2/just_a_dir/file4.txt", "And another file"); - - cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/File1.txt")); - cl_git_pass(git_index_add_bypath(g_idx, "JUST_A_DIR/file2.txt")); - cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/FILE3.txt")); - cl_git_pass(git_index_add_bypath(g_idx, "JusT_A_DIR/FILE4.txt")); - - cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/File1.txt", 0)); - cl_assert_equal_s("just_a_dir/File1.txt", entry->path); - - cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/file2.txt", 0)); - cl_assert_equal_s("JUST_A_DIR/file2.txt", entry->path); - - cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/FILE3.txt", 0)); - cl_assert_equal_s("Just_A_Dir/FILE3.txt", entry->path); - - cl_git_rewritefile("submod2/just_a_dir/file3.txt", "Rewritten"); - cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/file3.txt")); - - cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/file3.txt", 0)); - cl_assert_equal_s("Just_A_Dir/FILE3.txt", entry->path); -} - -void test_index_bypath__add_honors_existing_case_3(void) -{ - git_index_entry dummy = { { 0 } }; - const git_index_entry *entry; - - if (!cl_repo_get_bool(g_repo, "core.ignorecase")) - clar__skip(); - - dummy.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_oid_fromstr(&dummy.id, "f990a25a74d1a8281ce2ab018ea8df66795cd60b")); - - dummy.path = "just_a_dir/filea.txt"; - cl_git_pass(git_index_add(g_idx, &dummy)); - - dummy.path = "Just_A_Dir/fileB.txt"; - cl_git_pass(git_index_add(g_idx, &dummy)); - - dummy.path = "just_A_DIR/FILEC.txt"; - cl_git_pass(git_index_add(g_idx, &dummy)); - - dummy.path = "Just_a_DIR/FileD.txt"; - cl_git_pass(git_index_add(g_idx, &dummy)); - - cl_git_mkfile("submod2/JuSt_A_DiR/fILEE.txt", "This is a file"); - - cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/fILEE.txt")); - - cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/fILEE.txt", 0)); - cl_assert_equal_s("just_a_dir/fILEE.txt", entry->path); -} - -void test_index_bypath__add_honors_existing_case_4(void) -{ - git_index_entry dummy = { { 0 } }; - const git_index_entry *entry; - - if (!cl_repo_get_bool(g_repo, "core.ignorecase")) - clar__skip(); - - dummy.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_oid_fromstr(&dummy.id, "f990a25a74d1a8281ce2ab018ea8df66795cd60b")); - - dummy.path = "just_a_dir/a/b/c/d/e/file1.txt"; - cl_git_pass(git_index_add(g_idx, &dummy)); - - dummy.path = "just_a_dir/a/B/C/D/E/file2.txt"; - cl_git_pass(git_index_add(g_idx, &dummy)); - - cl_must_pass(p_mkdir("submod2/just_a_dir/a", 0777)); - cl_must_pass(p_mkdir("submod2/just_a_dir/a/b", 0777)); - cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z", 0777)); - cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z/y", 0777)); - cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z/y/x", 0777)); - - cl_git_mkfile("submod2/just_a_dir/a/b/z/y/x/FOO.txt", "This is a file"); - - cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/A/b/Z/y/X/foo.txt")); - - cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/A/b/Z/y/X/foo.txt", 0)); - cl_assert_equal_s("just_a_dir/a/b/Z/y/X/foo.txt", entry->path); -} - -void test_index_bypath__add_honors_mode(void) -{ - const git_index_entry *entry; - git_index_entry new_entry; - - cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); - - memcpy(&new_entry, entry, sizeof(git_index_entry)); - new_entry.path = "README.txt"; - new_entry.mode = GIT_FILEMODE_BLOB_EXECUTABLE; - - cl_must_pass(p_chmod("submod2/README.txt", GIT_FILEMODE_BLOB_EXECUTABLE)); - - cl_git_pass(git_index_add(g_idx, &new_entry)); - cl_git_pass(git_index_write(g_idx)); - - cl_git_rewritefile("submod2/README.txt", "Modified but still executable"); - - cl_git_pass(git_index_add_bypath(g_idx, "README.txt")); - cl_git_pass(git_index_write(g_idx)); - - cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); - cl_assert_equal_i(GIT_FILEMODE_BLOB_EXECUTABLE, entry->mode); -} - -void test_index_bypath__add_honors_conflict_mode(void) -{ - const git_index_entry *entry; - git_index_entry new_entry; - int stage = 0; - - cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); - - memcpy(&new_entry, entry, sizeof(git_index_entry)); - new_entry.path = "README.txt"; - new_entry.mode = GIT_FILEMODE_BLOB_EXECUTABLE; - - cl_must_pass(p_chmod("submod2/README.txt", GIT_FILEMODE_BLOB_EXECUTABLE)); - - cl_git_pass(git_index_remove_bypath(g_idx, "README.txt")); - - for (stage = 1; stage <= 3; stage++) { - new_entry.flags = stage << GIT_INDEX_ENTRY_STAGESHIFT; - cl_git_pass(git_index_add(g_idx, &new_entry)); - } - - cl_git_pass(git_index_write(g_idx)); - - cl_git_rewritefile("submod2/README.txt", "Modified but still executable"); - - cl_git_pass(git_index_add_bypath(g_idx, "README.txt")); - cl_git_pass(git_index_write(g_idx)); - - cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); - cl_assert_equal_i(GIT_FILEMODE_BLOB_EXECUTABLE, entry->mode); -} - -void test_index_bypath__add_honors_conflict_case(void) -{ - const git_index_entry *entry; - git_index_entry new_entry; - int stage = 0; - - cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); - - memcpy(&new_entry, entry, sizeof(git_index_entry)); - new_entry.path = "README.txt"; - new_entry.mode = GIT_FILEMODE_BLOB_EXECUTABLE; - - cl_must_pass(p_chmod("submod2/README.txt", GIT_FILEMODE_BLOB_EXECUTABLE)); - - cl_git_pass(git_index_remove_bypath(g_idx, "README.txt")); - - for (stage = 1; stage <= 3; stage++) { - new_entry.flags = stage << GIT_INDEX_ENTRY_STAGESHIFT; - cl_git_pass(git_index_add(g_idx, &new_entry)); - } - - cl_git_pass(git_index_write(g_idx)); - - cl_git_rewritefile("submod2/README.txt", "Modified but still executable"); - - cl_git_pass(git_index_add_bypath(g_idx, "README.txt")); - cl_git_pass(git_index_write(g_idx)); - - cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); - cl_assert_equal_i(GIT_FILEMODE_BLOB_EXECUTABLE, entry->mode); -} - -void test_index_bypath__add_honors_symlink(void) -{ - const git_index_entry *entry; - git_index_entry new_entry; - int symlinks; - - cl_git_pass(git_repository__configmap_lookup(&symlinks, g_repo, GIT_CONFIGMAP_SYMLINKS)); - - if (symlinks) - cl_skip(); - - cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); - - memcpy(&new_entry, entry, sizeof(git_index_entry)); - new_entry.path = "README.txt"; - new_entry.mode = GIT_FILEMODE_LINK; - - cl_git_pass(git_index_add(g_idx, &new_entry)); - cl_git_pass(git_index_write(g_idx)); - - cl_git_rewritefile("submod2/README.txt", "Modified but still a (fake) symlink"); - - cl_git_pass(git_index_add_bypath(g_idx, "README.txt")); - cl_git_pass(git_index_write(g_idx)); - - cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); - cl_assert_equal_i(GIT_FILEMODE_LINK, entry->mode); -} diff --git a/tests/index/cache.c b/tests/index/cache.c deleted file mode 100644 index 56885aff7..000000000 --- a/tests/index/cache.c +++ /dev/null @@ -1,238 +0,0 @@ -#include "clar_libgit2.h" -#include "git2.h" -#include "index.h" -#include "tree-cache.h" - -static git_repository *g_repo; - -void test_index_cache__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_index_cache__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -void test_index_cache__write_extension_at_root(void) -{ - git_index *index; - git_tree *tree; - git_oid id; - const char *tree_id_str = "45dd856fdd4d89b884c340ba0e047752d9b085d6"; - const char *index_file = "index-tree"; - - cl_git_pass(git_index_open(&index, index_file)); - cl_assert(index->tree == NULL); - cl_git_pass(git_oid_fromstr(&id, tree_id_str)); - cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - cl_git_pass(git_index_read_tree(index, tree)); - git_tree_free(tree); - - cl_assert(index->tree); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_index_open(&index, index_file)); - cl_assert(index->tree); - - cl_assert_equal_i(git_index_entrycount(index), index->tree->entry_count); - cl_assert_equal_i(0, index->tree->children_count); - - cl_assert(git_oid_equal(&id, &index->tree->oid)); - - cl_git_pass(p_unlink(index_file)); - git_index_free(index); -} - -void test_index_cache__write_extension_invalidated_root(void) -{ - git_index *index; - git_tree *tree; - git_oid id; - const char *tree_id_str = "45dd856fdd4d89b884c340ba0e047752d9b085d6"; - const char *index_file = "index-tree-invalidated"; - git_index_entry entry; - - cl_git_pass(git_index_open(&index, index_file)); - cl_assert(index->tree == NULL); - cl_git_pass(git_oid_fromstr(&id, tree_id_str)); - cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - cl_git_pass(git_index_read_tree(index, tree)); - git_tree_free(tree); - - cl_assert(index->tree); - - memset(&entry, 0x0, sizeof(git_index_entry)); - git_oid_cpy(&entry.id, &git_index_get_byindex(index, 0)->id); - entry.mode = GIT_FILEMODE_BLOB; - entry.path = "some-new-file.txt"; - - cl_git_pass(git_index_add(index, &entry)); - - cl_assert_equal_i(-1, index->tree->entry_count); - - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_index_open(&index, index_file)); - cl_assert(index->tree); - - cl_assert_equal_i(-1, index->tree->entry_count); - cl_assert_equal_i(0, index->tree->children_count); - - cl_assert(git_oid_cmp(&id, &index->tree->oid)); - - cl_git_pass(p_unlink(index_file)); - git_index_free(index); -} - -void test_index_cache__read_tree_no_children(void) -{ - git_index *index; - git_index_entry entry; - git_tree *tree; - git_oid id; - - cl_git_pass(git_index_new(&index)); - cl_assert(index->tree == NULL); - cl_git_pass(git_oid_fromstr(&id, "45dd856fdd4d89b884c340ba0e047752d9b085d6")); - cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - cl_git_pass(git_index_read_tree(index, tree)); - git_tree_free(tree); - - cl_assert(index->tree); - cl_assert(git_oid_equal(&id, &index->tree->oid)); - cl_assert_equal_i(0, index->tree->children_count); - cl_assert_equal_i(git_index_entrycount(index), index->tree->entry_count); - - memset(&entry, 0x0, sizeof(git_index_entry)); - entry.path = "new.txt"; - entry.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&entry.id, "d4bcc68acd4410bf836a39f20afb2c2ece09584e"); - - cl_git_pass(git_index_add(index, &entry)); - cl_assert_equal_i(-1, index->tree->entry_count); - - git_index_free(index); -} - -void test_index_cache__two_levels(void) -{ - git_tree *tree; - git_oid tree_id; - git_index *index; - git_index_entry entry; - const git_tree_cache *tree_cache; - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_clear(index)); - - memset(&entry, 0x0, sizeof(entry)); - entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_oid_fromstr(&entry.id, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); - entry.path = "top-level.txt"; - cl_git_pass(git_index_add(index, &entry)); - - entry.path = "subdir/file.txt"; - cl_git_pass(git_index_add(index, &entry)); - - /* the read-tree fills the tree cache */ - cl_git_pass(git_index_write_tree(&tree_id, index)); - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - cl_git_pass(git_index_read_tree(index, tree)); - git_tree_free(tree); - cl_git_pass(git_index_write(index)); - - /* we now must have cache entries for "" and "subdir" */ - cl_assert(index->tree); - cl_assert(git_tree_cache_get(index->tree, "subdir")); - - cl_git_pass(git_index_read(index, true)); - /* we must still have cache entries for "" and "subdir", since we wrote it out */ - cl_assert(index->tree); - cl_assert(git_tree_cache_get(index->tree, "subdir")); - - entry.path = "top-level.txt"; - cl_git_pass(git_oid_fromstr(&entry.id, "3697d64be941a53d4ae8f6a271e4e3fa56b022cc")); - cl_git_pass(git_index_add(index, &entry)); - - /* writ out the index after we invalidate the root */ - cl_git_pass(git_index_write(index)); - cl_git_pass(git_index_read(index, true)); - - /* the cache for the subtree must still be valid, even if the root isn't */ - cl_assert(index->tree); - cl_assert_equal_i(-1, index->tree->entry_count); - cl_assert_equal_i(1, index->tree->children_count); - tree_cache = git_tree_cache_get(index->tree, "subdir"); - cl_assert(tree_cache); - cl_assert_equal_i(1, tree_cache->entry_count); - - git_index_free(index); -} - -void test_index_cache__read_tree_children(void) -{ - git_index *index; - git_index_entry entry; - git_tree *tree; - const git_tree_cache *cache; - git_oid tree_id; - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_clear(index)); - cl_assert(index->tree == NULL); - - - /* add a bunch of entries at different levels */ - memset(&entry, 0x0, sizeof(git_index_entry)); - entry.path = "top-level"; - entry.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); - cl_git_pass(git_index_add(index, &entry)); - - - entry.path = "subdir/some-file"; - cl_git_pass(git_index_add(index, &entry)); - - entry.path = "subdir/even-deeper/some-file"; - cl_git_pass(git_index_add(index, &entry)); - - entry.path = "subdir2/some-file"; - cl_git_pass(git_index_add(index, &entry)); - - cl_git_pass(git_index_write_tree(&tree_id, index)); - cl_git_pass(git_index_clear(index)); - cl_assert(index->tree == NULL); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - cl_git_pass(git_index_read_tree(index, tree)); - git_tree_free(tree); - - cl_assert(index->tree); - cl_assert_equal_i(2, index->tree->children_count); - - /* override with a slightly different id, also dummy */ - entry.path = "subdir/some-file"; - git_oid_fromstr(&entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); - cl_git_pass(git_index_add(index, &entry)); - - cl_assert_equal_i(-1, index->tree->entry_count); - - cache = git_tree_cache_get(index->tree, "subdir"); - cl_assert(cache); - cl_assert_equal_i(-1, cache->entry_count); - - cache = git_tree_cache_get(index->tree, "subdir/even-deeper"); - cl_assert(cache); - cl_assert_equal_i(1, cache->entry_count); - - cache = git_tree_cache_get(index->tree, "subdir2"); - cl_assert(cache); - cl_assert_equal_i(1, cache->entry_count); - - git_index_free(index); -} diff --git a/tests/index/collision.c b/tests/index/collision.c deleted file mode 100644 index 18d2664f0..000000000 --- a/tests/index/collision.c +++ /dev/null @@ -1,149 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/index.h" - -static git_repository *g_repo; -static git_odb *g_odb; -static git_index *g_index; -static git_oid g_empty_id; - -void test_index_collision__initialize(void) -{ - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_pass(git_repository_odb(&g_odb, g_repo)); - cl_git_pass(git_repository_index(&g_index, g_repo)); - - cl_git_pass(git_odb_write(&g_empty_id, g_odb, "", 0, GIT_OBJECT_BLOB)); -} - -void test_index_collision__cleanup(void) -{ - git_index_free(g_index); - git_odb_free(g_odb); - cl_git_sandbox_cleanup(); -} - -void test_index_collision__add_blob_with_conflicting_file(void) -{ - git_index_entry entry; - git_tree_entry *tentry; - git_oid tree_id; - git_tree *tree; - - memset(&entry, 0, sizeof(entry)); - entry.ctime.seconds = 12346789; - entry.mtime.seconds = 12346789; - entry.mode = 0100644; - entry.file_size = 0; - git_oid_cpy(&entry.id, &g_empty_id); - - entry.path = "a/b"; - cl_git_pass(git_index_add(g_index, &entry)); - - /* Check a/b exists here */ - cl_git_pass(git_index_write_tree(&tree_id, g_index)); - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b")); - git_tree_entry_free(tentry); - git_tree_free(tree); - - /* create a tree/blob collision */ - entry.path = "a/b/c"; - cl_git_pass(git_index_add(g_index, &entry)); - - /* a/b should now be a tree and a/b/c a blob */ - cl_git_pass(git_index_write_tree(&tree_id, g_index)); - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b/c")); - git_tree_entry_free(tentry); - git_tree_free(tree); -} - -void test_index_collision__add_blob_with_conflicting_dir(void) -{ - git_index_entry entry; - git_tree_entry *tentry; - git_oid tree_id; - git_tree *tree; - - memset(&entry, 0, sizeof(entry)); - entry.ctime.seconds = 12346789; - entry.mtime.seconds = 12346789; - entry.mode = 0100644; - entry.file_size = 0; - git_oid_cpy(&entry.id, &g_empty_id); - - entry.path = "a/b/c"; - cl_git_pass(git_index_add(g_index, &entry)); - - /* Check a/b/c exists here */ - cl_git_pass(git_index_write_tree(&tree_id, g_index)); - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b/c")); - git_tree_entry_free(tentry); - git_tree_free(tree); - - /* create a blob/tree collision */ - entry.path = "a/b"; - cl_git_pass(git_index_add(g_index, &entry)); - - /* a/b should now be a tree and a/b/c a blob */ - cl_git_pass(git_index_write_tree(&tree_id, g_index)); - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b")); - cl_git_fail(git_tree_entry_bypath(&tentry, tree, "a/b/c")); - git_tree_entry_free(tentry); - git_tree_free(tree); -} - -void test_index_collision__add_with_highstage_1(void) -{ - git_index_entry entry; - - memset(&entry, 0, sizeof(entry)); - entry.ctime.seconds = 12346789; - entry.mtime.seconds = 12346789; - entry.mode = 0100644; - entry.file_size = 0; - git_oid_cpy(&entry.id, &g_empty_id); - - entry.path = "a/b"; - GIT_INDEX_ENTRY_STAGE_SET(&entry, 2); - cl_git_pass(git_index_add(g_index, &entry)); - - /* create a blob beneath the previous tree entry */ - entry.path = "a/b/c"; - entry.flags = 0; - cl_git_pass(git_index_add(g_index, &entry)); - - /* create another tree entry above the blob */ - entry.path = "a/b"; - GIT_INDEX_ENTRY_STAGE_SET(&entry, 1); - cl_git_pass(git_index_add(g_index, &entry)); -} - -void test_index_collision__add_with_highstage_2(void) -{ - git_index_entry entry; - - memset(&entry, 0, sizeof(entry)); - entry.ctime.seconds = 12346789; - entry.mtime.seconds = 12346789; - entry.mode = 0100644; - entry.file_size = 0; - git_oid_cpy(&entry.id, &g_empty_id); - - entry.path = "a/b/c"; - GIT_INDEX_ENTRY_STAGE_SET(&entry, 1); - cl_git_pass(git_index_add(g_index, &entry)); - - /* create a blob beneath the previous tree entry */ - entry.path = "a/b/c"; - GIT_INDEX_ENTRY_STAGE_SET(&entry, 2); - cl_git_pass(git_index_add(g_index, &entry)); - - /* create another tree entry above the blob */ - entry.path = "a/b"; - GIT_INDEX_ENTRY_STAGE_SET(&entry, 3); - cl_git_pass(git_index_add(g_index, &entry)); -} diff --git a/tests/index/conflicts.c b/tests/index/conflicts.c deleted file mode 100644 index 41d0e219c..000000000 --- a/tests/index/conflicts.c +++ /dev/null @@ -1,462 +0,0 @@ -#include "clar_libgit2.h" -#include "index.h" -#include "git2/repository.h" -#include "conflicts.h" - -static git_repository *repo; -static git_index *repo_index; - -#define TEST_REPO_PATH "mergedrepo" -#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" - -/* Fixture setup and teardown */ -void test_index_conflicts__initialize(void) -{ - repo = cl_git_sandbox_init("mergedrepo"); - git_repository_index(&repo_index, repo); -} - -void test_index_conflicts__cleanup(void) -{ - git_index_free(repo_index); - repo_index = NULL; - - cl_git_sandbox_cleanup(); -} - -void test_index_conflicts__add(void) -{ - git_index_entry ancestor_entry, our_entry, their_entry; - - cl_assert(git_index_entrycount(repo_index) == 8); - - memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); - memset(&our_entry, 0x0, sizeof(git_index_entry)); - memset(&their_entry, 0x0, sizeof(git_index_entry)); - - ancestor_entry.path = "test-one.txt"; - ancestor_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 1); - git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); - - our_entry.path = "test-one.txt"; - our_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&our_entry, 2); - git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); - - their_entry.path = "test-one.txt"; - their_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 2); - git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); - - cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); - - cl_assert(git_index_entrycount(repo_index) == 11); -} - -void test_index_conflicts__add_fixes_incorrect_stage(void) -{ - git_index_entry ancestor_entry, our_entry, their_entry; - const git_index_entry *conflict_entry[3]; - - cl_assert(git_index_entrycount(repo_index) == 8); - - memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); - memset(&our_entry, 0x0, sizeof(git_index_entry)); - memset(&their_entry, 0x0, sizeof(git_index_entry)); - - ancestor_entry.path = "test-one.txt"; - ancestor_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 3); - git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); - - our_entry.path = "test-one.txt"; - our_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&our_entry, 1); - git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); - - their_entry.path = "test-one.txt"; - their_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&their_entry, 2); - git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); - - cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); - - cl_assert(git_index_entrycount(repo_index) == 11); - - cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], repo_index, "test-one.txt")); - - cl_assert(git_index_entry_stage(conflict_entry[0]) == 1); - cl_assert(git_index_entry_stage(conflict_entry[1]) == 2); - cl_assert(git_index_entry_stage(conflict_entry[2]) == 3); -} - -void test_index_conflicts__add_detects_invalid_filemode(void) -{ - git_index_entry ancestor_entry, our_entry, their_entry; - git_index_entry *conflict_entry[3]; - int i; - - cl_assert(git_index_entrycount(repo_index) == 8); - - memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); - memset(&our_entry, 0x0, sizeof(git_index_entry)); - memset(&their_entry, 0x0, sizeof(git_index_entry)); - - conflict_entry[0] = &ancestor_entry; - conflict_entry[1] = &our_entry; - conflict_entry[2] = &their_entry; - - for (i = 0; i < 3; i++) { - ancestor_entry.path = "test-one.txt"; - ancestor_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 3); - git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); - - our_entry.path = "test-one.txt"; - our_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&our_entry, 1); - git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); - - their_entry.path = "test-one.txt"; - their_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&their_entry, 2); - git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); - - /* Corrupt the conflict entry's mode */ - conflict_entry[i]->mode = 027431745; - - cl_git_fail(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); - } - - cl_assert(git_index_entrycount(repo_index) == 8); -} - - -void test_index_conflicts__add_removes_stage_zero(void) -{ - git_index_entry ancestor_entry, our_entry, their_entry; - const git_index_entry *conflict_entry[3]; - - cl_assert(git_index_entrycount(repo_index) == 8); - - memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); - memset(&our_entry, 0x0, sizeof(git_index_entry)); - memset(&their_entry, 0x0, sizeof(git_index_entry)); - - cl_git_mkfile("./mergedrepo/test-one.txt", "new-file\n"); - cl_git_pass(git_index_add_bypath(repo_index, "test-one.txt")); - cl_assert(git_index_entrycount(repo_index) == 9); - - ancestor_entry.path = "test-one.txt"; - ancestor_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 3); - git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); - - our_entry.path = "test-one.txt"; - our_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&our_entry, 1); - git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); - - their_entry.path = "test-one.txt"; - their_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&their_entry, 2); - git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); - - cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); - - cl_assert(git_index_entrycount(repo_index) == 11); - - cl_assert_equal_p(NULL, git_index_get_bypath(repo_index, "test-one.txt", 0)); - - cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], repo_index, "test-one.txt")); - - cl_assert_equal_oid(&ancestor_entry.id, &conflict_entry[0]->id); - cl_assert_equal_i(1, git_index_entry_stage(conflict_entry[0])); - cl_assert_equal_oid(&our_entry.id, &conflict_entry[1]->id); - cl_assert_equal_i(2, git_index_entry_stage(conflict_entry[1])); - cl_assert_equal_oid(&their_entry.id, &conflict_entry[2]->id); - cl_assert_equal_i(3, git_index_entry_stage(conflict_entry[2])); -} - -void test_index_conflicts__get(void) -{ - const git_index_entry *conflict_entry[3]; - git_oid oid; - - cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], - &conflict_entry[2], repo_index, "conflicts-one.txt")); - - cl_assert_equal_s("conflicts-one.txt", conflict_entry[0]->path); - - git_oid_fromstr(&oid, CONFLICTS_ONE_ANCESTOR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[0]->id); - - git_oid_fromstr(&oid, CONFLICTS_ONE_OUR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[1]->id); - - git_oid_fromstr(&oid, CONFLICTS_ONE_THEIR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[2]->id); - - cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], - &conflict_entry[2], repo_index, "conflicts-two.txt")); - - cl_assert_equal_s("conflicts-two.txt", conflict_entry[0]->path); - - git_oid_fromstr(&oid, CONFLICTS_TWO_ANCESTOR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[0]->id); - - git_oid_fromstr(&oid, CONFLICTS_TWO_OUR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[1]->id); - - git_oid_fromstr(&oid, CONFLICTS_TWO_THEIR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[2]->id); -} - -void test_index_conflicts__iterate(void) -{ - git_index_conflict_iterator *iterator; - const git_index_entry *conflict_entry[3]; - git_oid oid; - - cl_git_pass(git_index_conflict_iterator_new(&iterator, repo_index)); - - cl_git_pass(git_index_conflict_next(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], iterator)); - - git_oid_fromstr(&oid, CONFLICTS_ONE_ANCESTOR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[0]->id); - cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-one.txt") == 0); - - git_oid_fromstr(&oid, CONFLICTS_ONE_OUR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[1]->id); - cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-one.txt") == 0); - - git_oid_fromstr(&oid, CONFLICTS_ONE_THEIR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[2]->id); - cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-one.txt") == 0); - - cl_git_pass(git_index_conflict_next(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], iterator)); - - git_oid_fromstr(&oid, CONFLICTS_TWO_ANCESTOR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[0]->id); - cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-two.txt") == 0); - - git_oid_fromstr(&oid, CONFLICTS_TWO_OUR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[1]->id); - cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-two.txt") == 0); - - git_oid_fromstr(&oid, CONFLICTS_TWO_THEIR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[2]->id); - cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-two.txt") == 0); - - cl_assert(git_index_conflict_next(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], iterator) == GIT_ITEROVER); - - cl_assert(conflict_entry[0] == NULL); - cl_assert(conflict_entry[2] == NULL); - cl_assert(conflict_entry[2] == NULL); - - git_index_conflict_iterator_free(iterator); -} - -void test_index_conflicts__remove(void) -{ - const git_index_entry *entry; - size_t i; - - cl_assert(git_index_entrycount(repo_index) == 8); - - cl_git_pass(git_index_conflict_remove(repo_index, "conflicts-one.txt")); - cl_assert(git_index_entrycount(repo_index) == 5); - - for (i = 0; i < git_index_entrycount(repo_index); i++) { - cl_assert(entry = git_index_get_byindex(repo_index, i)); - cl_assert(strcmp(entry->path, "conflicts-one.txt") != 0); - } - - cl_git_pass(git_index_conflict_remove(repo_index, "conflicts-two.txt")); - cl_assert(git_index_entrycount(repo_index) == 2); - - for (i = 0; i < git_index_entrycount(repo_index); i++) { - cl_assert(entry = git_index_get_byindex(repo_index, i)); - cl_assert(strcmp(entry->path, "conflicts-two.txt") != 0); - } -} - -void test_index_conflicts__moved_to_reuc_on_add(void) -{ - const git_index_entry *entry; - size_t i; - - cl_assert(git_index_entrycount(repo_index) == 8); - - cl_git_mkfile("./mergedrepo/conflicts-one.txt", "new-file\n"); - - cl_git_pass(git_index_add_bypath(repo_index, "conflicts-one.txt")); - - cl_assert(git_index_entrycount(repo_index) == 6); - - for (i = 0; i < git_index_entrycount(repo_index); i++) { - cl_assert(entry = git_index_get_byindex(repo_index, i)); - - if (strcmp(entry->path, "conflicts-one.txt") == 0) - cl_assert(!git_index_entry_is_conflict(entry)); - } -} - -void test_index_conflicts__moved_to_reuc_on_remove(void) -{ - const git_index_entry *entry; - size_t i; - - cl_assert(git_index_entrycount(repo_index) == 8); - - cl_git_pass(p_unlink("./mergedrepo/conflicts-one.txt")); - - cl_git_pass(git_index_remove_bypath(repo_index, "conflicts-one.txt")); - - cl_assert(git_index_entrycount(repo_index) == 5); - - for (i = 0; i < git_index_entrycount(repo_index); i++) { - cl_assert(entry = git_index_get_byindex(repo_index, i)); - cl_assert(strcmp(entry->path, "conflicts-one.txt") != 0); - } -} - -void test_index_conflicts__remove_all_conflicts(void) -{ - size_t i; - const git_index_entry *entry; - - cl_assert(git_index_entrycount(repo_index) == 8); - - cl_assert_equal_i(true, git_index_has_conflicts(repo_index)); - - git_index_conflict_cleanup(repo_index); - - cl_assert_equal_i(false, git_index_has_conflicts(repo_index)); - - cl_assert(git_index_entrycount(repo_index) == 2); - - for (i = 0; i < git_index_entrycount(repo_index); i++) { - cl_assert(entry = git_index_get_byindex(repo_index, i)); - cl_assert(!git_index_entry_is_conflict(entry)); - } -} - -void test_index_conflicts__partial(void) -{ - git_index_entry ancestor_entry, our_entry, their_entry; - const git_index_entry *conflict_entry[3]; - - cl_assert(git_index_entrycount(repo_index) == 8); - - memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); - memset(&our_entry, 0x0, sizeof(git_index_entry)); - memset(&their_entry, 0x0, sizeof(git_index_entry)); - - ancestor_entry.path = "test-one.txt"; - ancestor_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 1); - git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); - - cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, NULL, NULL)); - cl_assert(git_index_entrycount(repo_index) == 9); - - cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], - &conflict_entry[2], repo_index, "test-one.txt")); - - cl_assert_equal_oid(&ancestor_entry.id, &conflict_entry[0]->id); - cl_assert(conflict_entry[1] == NULL); - cl_assert(conflict_entry[2] == NULL); -} - -void test_index_conflicts__case_matters(void) -{ - const git_index_entry *conflict_entry[3]; - git_oid oid; - const char *upper_case = "DIFFERS-IN-CASE.TXT"; - const char *mixed_case = "Differs-In-Case.txt"; - const char *correct_case; - bool ignorecase = cl_repo_get_bool(repo, "core.ignorecase"); - - git_index_entry ancestor_entry, our_entry, their_entry; - - memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); - memset(&our_entry, 0x0, sizeof(git_index_entry)); - memset(&their_entry, 0x0, sizeof(git_index_entry)); - - ancestor_entry.path = upper_case; - GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, GIT_INDEX_STAGE_ANCESTOR); - git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); - ancestor_entry.mode = GIT_FILEMODE_BLOB; - - our_entry.path = upper_case; - GIT_INDEX_ENTRY_STAGE_SET(&our_entry, GIT_INDEX_STAGE_OURS); - git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); - our_entry.mode = GIT_FILEMODE_BLOB; - - their_entry.path = upper_case; - GIT_INDEX_ENTRY_STAGE_SET(&their_entry, GIT_INDEX_STAGE_THEIRS); - git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); - their_entry.mode = GIT_FILEMODE_BLOB; - - cl_git_pass(git_index_conflict_add(repo_index, - &ancestor_entry, &our_entry, &their_entry)); - - ancestor_entry.path = mixed_case; - GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, GIT_INDEX_STAGE_ANCESTOR); - git_oid_fromstr(&ancestor_entry.id, CONFLICTS_TWO_ANCESTOR_OID); - ancestor_entry.mode = GIT_FILEMODE_BLOB; - - our_entry.path = mixed_case; - GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, GIT_INDEX_STAGE_ANCESTOR); - git_oid_fromstr(&our_entry.id, CONFLICTS_TWO_OUR_OID); - ancestor_entry.mode = GIT_FILEMODE_BLOB; - - their_entry.path = mixed_case; - GIT_INDEX_ENTRY_STAGE_SET(&their_entry, GIT_INDEX_STAGE_THEIRS); - git_oid_fromstr(&their_entry.id, CONFLICTS_TWO_THEIR_OID); - their_entry.mode = GIT_FILEMODE_BLOB; - - cl_git_pass(git_index_conflict_add(repo_index, - &ancestor_entry, &our_entry, &their_entry)); - - cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], - &conflict_entry[2], repo_index, upper_case)); - - /* - * We inserted with mixed case last, so on a case-insensitive - * fs we should get the mixed case. - */ - if (ignorecase) - correct_case = mixed_case; - else - correct_case = upper_case; - - cl_assert_equal_s(correct_case, conflict_entry[0]->path); - git_oid_fromstr(&oid, ignorecase ? CONFLICTS_TWO_ANCESTOR_OID : CONFLICTS_ONE_ANCESTOR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[0]->id); - - cl_assert_equal_s(correct_case, conflict_entry[1]->path); - git_oid_fromstr(&oid, ignorecase ? CONFLICTS_TWO_OUR_OID : CONFLICTS_ONE_OUR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[1]->id); - - cl_assert_equal_s(correct_case, conflict_entry[2]->path); - git_oid_fromstr(&oid, ignorecase ? CONFLICTS_TWO_THEIR_OID : CONFLICTS_ONE_THEIR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[2]->id); - - cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], - &conflict_entry[2], repo_index, mixed_case)); - - cl_assert_equal_s(mixed_case, conflict_entry[0]->path); - git_oid_fromstr(&oid, CONFLICTS_TWO_ANCESTOR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[0]->id); - - cl_assert_equal_s(mixed_case, conflict_entry[1]->path); - git_oid_fromstr(&oid, CONFLICTS_TWO_OUR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[1]->id); - - cl_assert_equal_s(mixed_case, conflict_entry[2]->path); - git_oid_fromstr(&oid, CONFLICTS_TWO_THEIR_OID); - cl_assert_equal_oid(&oid, &conflict_entry[2]->id); -} diff --git a/tests/index/conflicts.h b/tests/index/conflicts.h deleted file mode 100644 index 3e26e6d5b..000000000 --- a/tests/index/conflicts.h +++ /dev/null @@ -1,7 +0,0 @@ -#define CONFLICTS_ONE_ANCESTOR_OID "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81" -#define CONFLICTS_ONE_OUR_OID "6aea5f295304c36144ad6e9247a291b7f8112399" -#define CONFLICTS_ONE_THEIR_OID "516bd85f78061e09ccc714561d7b504672cb52da" - -#define CONFLICTS_TWO_ANCESTOR_OID "84af62840be1b1c47b778a8a249f3ff45155038c" -#define CONFLICTS_TWO_OUR_OID "8b3f43d2402825c200f835ca1762413e386fd0b2" -#define CONFLICTS_TWO_THEIR_OID "220bd62631c8cf7a83ef39c6b94595f00517211e" diff --git a/tests/index/crlf.c b/tests/index/crlf.c deleted file mode 100644 index 7520c23a3..000000000 --- a/tests/index/crlf.c +++ /dev/null @@ -1,402 +0,0 @@ -#include "clar_libgit2.h" -#include "../filter/crlf.h" - -#include "git2/checkout.h" -#include "repository.h" -#include "posix.h" - -#define FILE_CONTENTS_LF "one\ntwo\nthree\nfour\n" -#define FILE_CONTENTS_CRLF "one\r\ntwo\r\nthree\r\nfour\r\n" - -#define FILE_OID_LF "f384549cbeb481e437091320de6d1f2e15e11b4a" -#define FILE_OID_CRLF "7fbf4d847b191141d80f30c8ab03d2ad4cd543a9" - -static git_repository *g_repo; -static git_index *g_index; - -static git_str expected_fixture = GIT_STR_INIT; - -void test_index_crlf__initialize(void) -{ - g_repo = cl_git_sandbox_init_new("crlf"); - cl_git_pass(git_repository_index(&g_index, g_repo)); -} - -void test_index_crlf__cleanup(void) -{ - git_index_free(g_index); - cl_git_sandbox_cleanup(); - - if (expected_fixture.size) { - cl_fixture_cleanup(expected_fixture.ptr); - git_str_dispose(&expected_fixture); - } -} - -struct compare_data -{ - const char *systype; - const char *dirname; - const char *safecrlf; - const char *autocrlf; - const char *attrs; -}; - -static int add_and_check_file(void *payload, git_str *actual_path) -{ - git_str expected_path = GIT_STR_INIT; - git_str expected_path_fail = GIT_STR_INIT; - git_str expected_contents = GIT_STR_INIT; - struct compare_data *cd = payload; - char *basename; - const git_index_entry *entry; - git_blob *blob; - bool failed = true; - - basename = git_fs_path_basename(actual_path->ptr); - - if (!strcmp(basename, ".git") || !strcmp(basename, ".gitattributes")) { - failed = false; - goto done; - } - - cl_git_pass(git_str_joinpath(&expected_path, cd->dirname, basename)); - - cl_git_pass(git_str_puts(&expected_path_fail, expected_path.ptr)); - cl_git_pass(git_str_puts(&expected_path_fail, ".fail")); - - if (git_fs_path_isfile(expected_path.ptr)) { - cl_git_pass(git_index_add_bypath(g_index, basename)); - - cl_assert(entry = git_index_get_bypath(g_index, basename, 0)); - cl_git_pass(git_blob_lookup(&blob, g_repo, &entry->id)); - - cl_git_pass(git_futils_readbuffer(&expected_contents, expected_path.ptr)); - - if (strcmp(expected_contents.ptr, git_blob_rawcontent(blob)) != 0) - goto done; - - git_blob_free(blob); - } else if (git_fs_path_isfile(expected_path_fail.ptr)) { - cl_git_pass(git_futils_readbuffer(&expected_contents, expected_path_fail.ptr)); - git_str_rtrim(&expected_contents); - - if (git_index_add_bypath(g_index, basename) == 0 || - git_error_last()->klass != GIT_ERROR_FILTER || - strcmp(expected_contents.ptr, git_error_last()->message) != 0) - goto done; - } else { - cl_fail("unexpected index failure"); - } - - failed = false; - -done: - if (failed) { - git_str details = GIT_STR_INIT; - git_str_printf(&details, "filename=%s, system=%s, autocrlf=%s, safecrlf=%s, attrs={%s}", - basename, cd->systype, cd->autocrlf, cd->safecrlf, cd->attrs); - clar__fail(__FILE__, __func__, __LINE__, - "index contents did not match expected", details.ptr, 0); - git_str_dispose(&details); - } - - git__free(basename); - git_str_dispose(&expected_contents); - git_str_dispose(&expected_path); - git_str_dispose(&expected_path_fail); - return 0; -} - -static const char *system_type(void) -{ - if (GIT_EOL_NATIVE == GIT_EOL_CRLF) - return "windows"; - else - return "posix"; -} - -static void test_add_index(const char *safecrlf, const char *autocrlf, const char *attrs) -{ - git_str attrbuf = GIT_STR_INIT; - git_str expected_dirname = GIT_STR_INIT; - git_str sandboxname = GIT_STR_INIT; - git_str reponame = GIT_STR_INIT; - struct compare_data compare_data = { system_type(), NULL, safecrlf, autocrlf, attrs }; - const char *c; - - git_str_puts(&reponame, "crlf"); - - git_str_puts(&sandboxname, "autocrlf_"); - git_str_puts(&sandboxname, autocrlf); - - git_str_puts(&sandboxname, ",safecrlf_"); - git_str_puts(&sandboxname, safecrlf); - - if (*attrs) { - git_str_puts(&sandboxname, ","); - - for (c = attrs; *c; c++) { - if (*c == ' ') - git_str_putc(&sandboxname, ','); - else if (*c == '=') - git_str_putc(&sandboxname, '_'); - else - git_str_putc(&sandboxname, *c); - } - - git_str_printf(&attrbuf, "* %s\n", attrs); - cl_git_mkfile("crlf/.gitattributes", attrbuf.ptr); - } - - cl_repo_set_string(g_repo, "core.safecrlf", safecrlf); - cl_repo_set_string(g_repo, "core.autocrlf", autocrlf); - - cl_git_pass(git_index_clear(g_index)); - - git_str_joinpath(&expected_dirname, "crlf_data", system_type()); - git_str_puts(&expected_dirname, "_to_odb"); - - git_str_joinpath(&expected_fixture, expected_dirname.ptr, sandboxname.ptr); - cl_fixture_sandbox(expected_fixture.ptr); - - compare_data.dirname = sandboxname.ptr; - cl_git_pass(git_fs_path_direach(&reponame, 0, add_and_check_file, &compare_data)); - - cl_fixture_cleanup(expected_fixture.ptr); - git_str_dispose(&expected_fixture); - - git_str_dispose(&attrbuf); - git_str_dispose(&expected_fixture); - git_str_dispose(&expected_dirname); - git_str_dispose(&sandboxname); - git_str_dispose(&reponame); -} - -static void set_up_workingdir(const char *name) -{ - git_vector contents = GIT_VECTOR_INIT; - size_t i; - const char *fn; - - git_fs_path_dirload(&contents, name, 0, 0); - git_vector_foreach(&contents, i, fn) { - char *basename = git_fs_path_basename(fn); - bool skip = strncasecmp(basename, ".git", 4) == 0 && strlen(basename) == 4; - - git__free(basename); - - if (skip) - continue; - p_unlink(fn); - } - git_vector_free_deep(&contents); - - /* copy input files */ - git_fs_path_dirload(&contents, cl_fixture("crlf"), 0, 0); - git_vector_foreach(&contents, i, fn) { - char *basename = git_fs_path_basename(fn); - git_str dest_filename = GIT_STR_INIT; - - if (strcmp(basename, ".gitted") && - strcmp(basename, ".gitattributes")) { - git_str_joinpath(&dest_filename, name, basename); - cl_git_pass(git_futils_cp(fn, dest_filename.ptr, 0644)); - } - - git__free(basename); - git_str_dispose(&dest_filename); - } - git_vector_free_deep(&contents); -} - -void test_index_crlf__matches_core_git(void) -{ - const char *safecrlf[] = { "true", "false", "warn", NULL }; - const char *autocrlf[] = { "true", "false", "input", NULL }; - const char *attrs[] = { "", "-crlf", "-text", "eol=crlf", "eol=lf", - "text", "text eol=crlf", "text eol=lf", - "text=auto", "text=auto eol=crlf", "text=auto eol=lf", - NULL }; - const char **a, **b, **c; - - for (a = safecrlf; *a; a++) { - for (b = autocrlf; *b; b++) { - for (c = attrs; *c; c++) { - set_up_workingdir("crlf"); - test_add_index(*a, *b, *c); - } - } - } -} - -void test_index_crlf__autocrlf_false_no_attrs(void) -{ - const git_index_entry *entry; - git_oid oid; - - cl_repo_set_bool(g_repo, "core.autocrlf", false); - - cl_git_mkfile("./crlf/newfile.txt", - (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF); - - cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); - entry = git_index_get_bypath(g_index, "newfile.txt", 0); - - cl_git_pass(git_oid_fromstr(&oid, - (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_OID_CRLF : FILE_OID_LF)); - cl_assert_equal_oid(&oid, &entry->id); -} - -void test_index_crlf__autocrlf_true_no_attrs(void) -{ - const git_index_entry *entry; - git_oid oid; - - cl_repo_set_bool(g_repo, "core.autocrlf", true); - - cl_git_mkfile("./crlf/newfile.txt", - (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF); - - cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); - entry = git_index_get_bypath(g_index, "newfile.txt", 0); - - cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); - cl_assert_equal_oid(&oid, &entry->id); -} - -void test_index_crlf__autocrlf_input_no_attrs(void) -{ - const git_index_entry *entry; - git_oid oid; - - cl_repo_set_string(g_repo, "core.autocrlf", "input"); - - cl_git_mkfile("./crlf/newfile.txt", - (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF); - - cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); - entry = git_index_get_bypath(g_index, "newfile.txt", 0); - - cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); - cl_assert_equal_oid(&oid, &entry->id); -} - -void test_index_crlf__autocrlf_false_text_auto_attr(void) -{ - const git_index_entry *entry; - git_oid oid; - - cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); - - cl_repo_set_bool(g_repo, "core.autocrlf", false); - - cl_git_mkfile("./crlf/newfile.txt", - (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF); - - cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); - entry = git_index_get_bypath(g_index, "newfile.txt", 0); - - cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); - cl_assert_equal_oid(&oid, &entry->id); -} - -void test_index_crlf__autocrlf_true_text_auto_attr(void) -{ - const git_index_entry *entry; - git_oid oid; - - cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); - - cl_repo_set_bool(g_repo, "core.autocrlf", false); - - cl_git_mkfile("./crlf/newfile.txt", - (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF); - - cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); - entry = git_index_get_bypath(g_index, "newfile.txt", 0); - - cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); - cl_assert_equal_oid(&oid, &entry->id); -} - -void test_index_crlf__autocrlf_input_text_auto_attr(void) -{ - const git_index_entry *entry; - git_oid oid; - - cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); - - cl_repo_set_string(g_repo, "core.autocrlf", "input"); - - cl_git_mkfile("./crlf/newfile.txt", - (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF); - - cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); - entry = git_index_get_bypath(g_index, "newfile.txt", 0); - - cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); - cl_assert_equal_oid(&oid, &entry->id); -} - -void test_index_crlf__safecrlf_true_autocrlf_input_text_auto_attr(void) -{ - const git_index_entry *entry; - git_oid oid; - - cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); - - cl_repo_set_string(g_repo, "core.autocrlf", "input"); - cl_repo_set_bool(g_repo, "core.safecrlf", true); - - cl_git_mkfile("./crlf/newfile.txt", FILE_CONTENTS_LF); - - cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); - entry = git_index_get_bypath(g_index, "newfile.txt", 0); - cl_assert(entry); - - cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); - cl_assert_equal_oid(&oid, &entry->id); - - cl_git_mkfile("./crlf/newfile2.txt", FILE_CONTENTS_CRLF); - cl_git_fail(git_index_add_bypath(g_index, "newfile2.txt")); -} - -void test_index_crlf__safecrlf_true_autocrlf_input_text__no_attr(void) -{ - const git_index_entry *entry; - git_oid oid; - - cl_repo_set_string(g_repo, "core.autocrlf", "input"); - cl_repo_set_bool(g_repo, "core.safecrlf", true); - - cl_git_mkfile("./crlf/newfile.txt", FILE_CONTENTS_LF); - - cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); - entry = git_index_get_bypath(g_index, "newfile.txt", 0); - cl_assert(entry); - - cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); - cl_assert_equal_oid(&oid, &entry->id); - - cl_git_mkfile("./crlf/newfile2.txt", FILE_CONTENTS_CRLF); - cl_git_fail(git_index_add_bypath(g_index, "newfile2.txt")); -} - -void test_index_crlf__safecrlf_true_no_attrs(void) -{ - cl_repo_set_bool(g_repo, "core.autocrlf", true); - cl_repo_set_bool(g_repo, "core.safecrlf", true); - - cl_git_mkfile("crlf/newfile.txt", ALL_LF_TEXT_RAW); - cl_git_fail(git_index_add_bypath(g_index, "newfile.txt")); - - cl_git_mkfile("crlf/newfile.txt", ALL_CRLF_TEXT_RAW); - cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); - - cl_git_mkfile("crlf/newfile.txt", MORE_CRLF_TEXT_RAW); - cl_git_fail(git_index_add_bypath(g_index, "newfile.txt")); - - cl_git_mkfile("crlf/newfile.txt", MORE_LF_TEXT_RAW); - cl_git_fail(git_index_add_bypath(g_index, "newfile.txt")); -} diff --git a/tests/index/filemodes.c b/tests/index/filemodes.c deleted file mode 100644 index 1ab8a9a7f..000000000 --- a/tests/index/filemodes.c +++ /dev/null @@ -1,319 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "index.h" - -static git_repository *g_repo = NULL; - -void test_index_filemodes__initialize(void) -{ - g_repo = cl_git_sandbox_init("filemodes"); -} - -void test_index_filemodes__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_index_filemodes__read(void) -{ - git_index *index; - unsigned int i; - static bool expected[6] = { 0, 1, 0, 1, 0, 1 }; - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_assert_equal_i(6, (int)git_index_entrycount(index)); - - for (i = 0; i < 6; ++i) { - const git_index_entry *entry = git_index_get_byindex(index, i); - cl_assert(entry != NULL); - cl_assert(((entry->mode & 0100) ? 1 : 0) == expected[i]); - } - - git_index_free(index); -} - -static void replace_file_with_mode( - const char *filename, const char *backup, unsigned int create_mode) -{ - git_str path = GIT_STR_INIT, content = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&path, "filemodes", filename)); - cl_git_pass(git_str_printf(&content, "%s as %08u (%d)", - filename, create_mode, rand())); - - cl_git_pass(p_rename(path.ptr, backup)); - cl_git_write2file( - path.ptr, content.ptr, content.size, - O_WRONLY|O_CREAT|O_TRUNC, create_mode); - - git_str_dispose(&path); - git_str_dispose(&content); -} - -#define add_and_check_mode(I,F,X) add_and_check_mode_(I,F,X,__FILE__,__func__,__LINE__) - -static void add_and_check_mode_( - git_index *index, const char *filename, unsigned int expect_mode, - const char *file, const char *func, int line) -{ - size_t pos; - const git_index_entry *entry; - - cl_git_pass(git_index_add_bypath(index, filename)); - - clar__assert(!git_index_find(&pos, index, filename), - file, func, line, "Cannot find index entry", NULL, 1); - - entry = git_index_get_byindex(index, pos); - - clar__assert_equal(file, func, line, "Expected mode does not match index", - 1, "%07o", (unsigned int)entry->mode, (unsigned int)expect_mode); -} - -void test_index_filemodes__untrusted(void) -{ - git_index *index; - - cl_repo_set_bool(g_repo, "core.filemode", false); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_assert((git_index_caps(index) & GIT_INDEX_CAPABILITY_NO_FILEMODE) != 0); - - /* 1 - add 0644 over existing 0644 -> expect 0644 */ - replace_file_with_mode("exec_off", "filemodes/exec_off.0", 0644); - add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); - - /* 2 - add 0644 over existing 0755 -> expect 0755 */ - replace_file_with_mode("exec_on", "filemodes/exec_on.0", 0644); - add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 3 - add 0755 over existing 0644 -> expect 0644 */ - replace_file_with_mode("exec_off", "filemodes/exec_off.1", 0755); - add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); - - /* 4 - add 0755 over existing 0755 -> expect 0755 */ - replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755); - add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 5 - add new 0644 -> expect 0644 */ - cl_git_write2file("filemodes/new_off", "blah", 0, - O_WRONLY | O_CREAT | O_TRUNC, 0644); - add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB); - - /* 6 - add new 0755 -> expect 0644 if core.filemode == false */ - cl_git_write2file("filemodes/new_on", "blah", 0, - O_WRONLY | O_CREAT | O_TRUNC, 0755); - add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB); - - git_index_free(index); -} - -void test_index_filemodes__trusted(void) -{ - git_index *index; - - /* Only run these tests on platforms where I can actually - * chmod a file and get the stat results I expect! - */ - if (!cl_is_chmod_supported()) - return; - - cl_repo_set_bool(g_repo, "core.filemode", true); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_assert((git_index_caps(index) & GIT_INDEX_CAPABILITY_NO_FILEMODE) == 0); - - /* 1 - add 0644 over existing 0644 -> expect 0644 */ - replace_file_with_mode("exec_off", "filemodes/exec_off.0", 0644); - add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); - - /* 2 - add 0644 over existing 0755 -> expect 0644 */ - replace_file_with_mode("exec_on", "filemodes/exec_on.0", 0644); - add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB); - - /* 3 - add 0755 over existing 0644 -> expect 0755 */ - replace_file_with_mode("exec_off", "filemodes/exec_off.1", 0755); - add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 4 - add 0755 over existing 0755 -> expect 0755 */ - replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755); - add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 5 - add new 0644 -> expect 0644 */ - cl_git_write2file("filemodes/new_off", "blah", 0, - O_WRONLY | O_CREAT | O_TRUNC, 0644); - add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB); - - /* 6 - add 0755 -> expect 0755 */ - cl_git_write2file("filemodes/new_on", "blah", 0, - O_WRONLY | O_CREAT | O_TRUNC, 0755); - add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE); - - git_index_free(index); -} - -#define add_entry_and_check_mode(I,FF,X) add_entry_and_check_mode_(I,FF,X,__FILE__,__func__,__LINE__) - -static void add_entry_and_check_mode_( - git_index *index, bool from_file, git_filemode_t mode, - const char *file, const char *func, int line) -{ - size_t pos; - const git_index_entry* entry; - git_index_entry new_entry; - - /* If old_filename exists, we copy that to the new file, and test - * git_index_add(), otherwise create a new entry testing git_index_add_from_buffer - */ - if (from_file) - { - clar__assert(!git_index_find(&pos, index, "exec_off"), - file, func, line, "Cannot find original index entry", NULL, 1); - - entry = git_index_get_byindex(index, pos); - - memcpy(&new_entry, entry, sizeof(new_entry)); - } - else - memset(&new_entry, 0x0, sizeof(git_index_entry)); - - new_entry.path = "filemodes/explicit_test"; - new_entry.mode = mode; - - if (from_file) - { - clar__assert(!git_index_add(index, &new_entry), - file, func, line, "Cannot add index entry", NULL, 1); - } - else - { - const char *content = "hey there\n"; - clar__assert(!git_index_add_from_buffer(index, &new_entry, content, strlen(content)), - file, func, line, "Cannot add index entry from buffer", NULL, 1); - } - - clar__assert(!git_index_find(&pos, index, "filemodes/explicit_test"), - file, func, line, "Cannot find new index entry", NULL, 1); - - entry = git_index_get_byindex(index, pos); - - clar__assert_equal(file, func, line, "Expected mode does not match index", - 1, "%07o", (unsigned int)entry->mode, (unsigned int)mode); -} - -void test_index_filemodes__explicit(void) -{ - git_index *index; - - /* These tests should run and work everywhere, as the filemode is - * given explicitly to git_index_add or git_index_add_from_buffer - */ - cl_repo_set_bool(g_repo, "core.filemode", false); - - cl_git_pass(git_repository_index(&index, g_repo)); - - /* Each of these tests keeps overwriting the same file in the index. */ - /* 1 - add new 0644 entry */ - add_entry_and_check_mode(index, true, GIT_FILEMODE_BLOB); - - /* 2 - add 0755 entry over existing 0644 */ - add_entry_and_check_mode(index, true, GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 3 - add 0644 entry over existing 0755 */ - add_entry_and_check_mode(index, true, GIT_FILEMODE_BLOB); - - /* 4 - add 0755 buffer entry over existing 0644 */ - add_entry_and_check_mode(index, false, GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 5 - add 0644 buffer entry over existing 0755 */ - add_entry_and_check_mode(index, false, GIT_FILEMODE_BLOB); - - git_index_free(index); -} - -void test_index_filemodes__invalid(void) -{ - git_index *index; - git_index_entry entry; - const git_index_entry *dummy; - - cl_git_pass(git_repository_index(&index, g_repo)); - - /* add a dummy file so that we have a valid id */ - cl_git_mkfile("./filemodes/dummy-file.txt", "new-file\n"); - cl_git_pass(git_index_add_bypath(index, "dummy-file.txt")); - cl_assert((dummy = git_index_get_bypath(index, "dummy-file.txt", 0))); - - GIT_INDEX_ENTRY_STAGE_SET(&entry, 0); - entry.path = "foo"; - entry.mode = GIT_OBJECT_BLOB; - git_oid_cpy(&entry.id, &dummy->id); - cl_git_fail(git_index_add(index, &entry)); - - entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_index_add(index, &entry)); - - git_index_free(index); -} - -void test_index_filemodes__frombuffer_requires_files(void) -{ - git_index *index; - git_index_entry new_entry; - const git_index_entry *ret_entry; - const char *content = "hey there\n"; - - memset(&new_entry, 0, sizeof(new_entry)); - cl_git_pass(git_repository_index(&index, g_repo)); - - /* regular blob */ - new_entry.path = "dummy-file.txt"; - new_entry.mode = GIT_FILEMODE_BLOB; - - cl_git_pass(git_index_add_from_buffer(index, - &new_entry, content, strlen(content))); - - cl_assert((ret_entry = git_index_get_bypath(index, "dummy-file.txt", 0))); - cl_assert_equal_s("dummy-file.txt", ret_entry->path); - cl_assert_equal_i(GIT_FILEMODE_BLOB, ret_entry->mode); - - /* executable blob */ - new_entry.path = "dummy-file.txt"; - new_entry.mode = GIT_FILEMODE_BLOB_EXECUTABLE; - - cl_git_pass(git_index_add_from_buffer(index, - &new_entry, content, strlen(content))); - - cl_assert((ret_entry = git_index_get_bypath(index, "dummy-file.txt", 0))); - cl_assert_equal_s("dummy-file.txt", ret_entry->path); - cl_assert_equal_i(GIT_FILEMODE_BLOB_EXECUTABLE, ret_entry->mode); - - /* links are also acceptable */ - new_entry.path = "dummy-link.txt"; - new_entry.mode = GIT_FILEMODE_LINK; - - cl_git_pass(git_index_add_from_buffer(index, - &new_entry, content, strlen(content))); - - cl_assert((ret_entry = git_index_get_bypath(index, "dummy-link.txt", 0))); - cl_assert_equal_s("dummy-link.txt", ret_entry->path); - cl_assert_equal_i(GIT_FILEMODE_LINK, ret_entry->mode); - - /* trees are rejected */ - new_entry.path = "invalid_mode.txt"; - new_entry.mode = GIT_FILEMODE_TREE; - - cl_git_fail(git_index_add_from_buffer(index, - &new_entry, content, strlen(content))); - cl_assert_equal_p(NULL, git_index_get_bypath(index, "invalid_mode.txt", 0)); - - /* submodules are rejected */ - new_entry.path = "invalid_mode.txt"; - new_entry.mode = GIT_FILEMODE_COMMIT; - - cl_git_fail(git_index_add_from_buffer(index, - &new_entry, content, strlen(content))); - cl_assert_equal_p(NULL, git_index_get_bypath(index, "invalid_mode.txt", 0)); - - git_index_free(index); -} diff --git a/tests/index/inmemory.c b/tests/index/inmemory.c deleted file mode 100644 index 38e91e0fd..000000000 --- a/tests/index/inmemory.c +++ /dev/null @@ -1,22 +0,0 @@ -#include "clar_libgit2.h" - -void test_index_inmemory__can_create_an_inmemory_index(void) -{ - git_index *index; - - cl_git_pass(git_index_new(&index)); - cl_assert_equal_i(0, (int)git_index_entrycount(index)); - - git_index_free(index); -} - -void test_index_inmemory__cannot_add_bypath_to_an_inmemory_index(void) -{ - git_index *index; - - cl_git_pass(git_index_new(&index)); - - cl_assert_equal_i(GIT_ERROR, git_index_add_bypath(index, "test.txt")); - - git_index_free(index); -} diff --git a/tests/index/names.c b/tests/index/names.c deleted file mode 100644 index 369318b03..000000000 --- a/tests/index/names.c +++ /dev/null @@ -1,187 +0,0 @@ -#include "clar_libgit2.h" -#include "index.h" -#include "git2/sys/index.h" -#include "git2/repository.h" -#include "../reset/reset_helpers.h" - -static git_repository *repo; -static git_index *repo_index; - -#define TEST_REPO_PATH "mergedrepo" -#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" - -/* Fixture setup and teardown */ -void test_index_names__initialize(void) -{ - repo = cl_git_sandbox_init("mergedrepo"); - git_repository_index(&repo_index, repo); -} - -void test_index_names__cleanup(void) -{ - git_index_free(repo_index); - repo_index = NULL; - - cl_git_sandbox_cleanup(); -} - -static void index_add_conflicts(void) -{ - git_index_entry entry = {{0}}; - const char *paths[][3] = { - { "ancestor", "ours", "theirs" }, - { "ancestor2", "ours2", "theirs2" }, - { "ancestor3", "ours3", "theirs3" } }; - const char **conflict; - size_t i; - - for (i = 0; i < ARRAY_SIZE(paths); i++) { - conflict = paths[i]; - - /* ancestor */ - entry.path = conflict[0]; - entry.mode = GIT_FILEMODE_BLOB; - GIT_INDEX_ENTRY_STAGE_SET(&entry, GIT_INDEX_STAGE_ANCESTOR); - git_oid_fromstr(&entry.id, "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81"); - cl_git_pass(git_index_add(repo_index, &entry)); - - /* ours */ - entry.path = conflict[1]; - entry.mode = GIT_FILEMODE_BLOB; - GIT_INDEX_ENTRY_STAGE_SET(&entry, GIT_INDEX_STAGE_OURS); - git_oid_fromstr(&entry.id, "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81"); - cl_git_pass(git_index_add(repo_index, &entry)); - - /* theirs */ - entry.path = conflict[2]; - entry.mode = GIT_FILEMODE_BLOB; - GIT_INDEX_ENTRY_STAGE_SET(&entry, GIT_INDEX_STAGE_THEIRS); - git_oid_fromstr(&entry.id, "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81"); - cl_git_pass(git_index_add(repo_index, &entry)); - } -} - -void test_index_names__add(void) -{ - const git_index_name_entry *conflict_name; - - index_add_conflicts(); - cl_git_pass(git_index_name_add(repo_index, "ancestor", "ours", "theirs")); - cl_git_pass(git_index_name_add(repo_index, "ancestor2", "ours2", NULL)); - cl_git_pass(git_index_name_add(repo_index, "ancestor3", NULL, "theirs3")); - - cl_assert(git_index_name_entrycount(repo_index) == 3); - - conflict_name = git_index_name_get_byindex(repo_index, 0); - cl_assert(strcmp(conflict_name->ancestor, "ancestor") == 0); - cl_assert(strcmp(conflict_name->ours, "ours") == 0); - cl_assert(strcmp(conflict_name->theirs, "theirs") == 0); - - conflict_name = git_index_name_get_byindex(repo_index, 1); - cl_assert(strcmp(conflict_name->ancestor, "ancestor2") == 0); - cl_assert(strcmp(conflict_name->ours, "ours2") == 0); - cl_assert(conflict_name->theirs == NULL); - - conflict_name = git_index_name_get_byindex(repo_index, 2); - cl_assert(strcmp(conflict_name->ancestor, "ancestor3") == 0); - cl_assert(conflict_name->ours == NULL); - cl_assert(strcmp(conflict_name->theirs, "theirs3") == 0); - - cl_git_pass(git_index_write(repo_index)); -} - -void test_index_names__roundtrip(void) -{ - const git_index_name_entry *conflict_name; - - cl_git_pass(git_index_name_add(repo_index, "ancestor", "ours", "theirs")); - cl_git_pass(git_index_name_add(repo_index, "ancestor2", "ours2", NULL)); - cl_git_pass(git_index_name_add(repo_index, "ancestor3", NULL, "theirs3")); - - cl_git_pass(git_index_write(repo_index)); - git_index_clear(repo_index); - cl_assert(git_index_name_entrycount(repo_index) == 0); - - cl_git_pass(git_index_read(repo_index, true)); - cl_assert(git_index_name_entrycount(repo_index) == 3); - - conflict_name = git_index_name_get_byindex(repo_index, 0); - cl_assert(strcmp(conflict_name->ancestor, "ancestor") == 0); - cl_assert(strcmp(conflict_name->ours, "ours") == 0); - cl_assert(strcmp(conflict_name->theirs, "theirs") == 0); - - conflict_name = git_index_name_get_byindex(repo_index, 1); - cl_assert(strcmp(conflict_name->ancestor, "ancestor2") == 0); - cl_assert(strcmp(conflict_name->ours, "ours2") == 0); - cl_assert(conflict_name->theirs == NULL); - - conflict_name = git_index_name_get_byindex(repo_index, 2); - cl_assert(strcmp(conflict_name->ancestor, "ancestor3") == 0); - cl_assert(conflict_name->ours == NULL); - cl_assert(strcmp(conflict_name->theirs, "theirs3") == 0); -} - -void test_index_names__cleaned_on_reset_hard(void) -{ - git_object *target; - - cl_git_pass(git_revparse_single(&target, repo, "3a34580")); - - test_index_names__add(); - cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); - cl_assert(git_index_name_entrycount(repo_index) == 0); - - git_object_free(target); -} - -void test_index_names__cleaned_on_reset_mixed(void) -{ - git_object *target; - - cl_git_pass(git_revparse_single(&target, repo, "3a34580")); - - test_index_names__add(); - cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL)); - cl_assert(git_index_name_entrycount(repo_index) == 0); - - git_object_free(target); -} - -void test_index_names__cleaned_on_checkout_tree(void) -{ - git_oid oid; - git_object *obj; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_UPDATE_ONLY; - - test_index_names__add(); - cl_git_pass(git_reference_name_to_id(&oid, repo, "refs/heads/master")); - cl_git_pass(git_object_lookup(&obj, repo, &oid, GIT_OBJECT_ANY)); - cl_git_pass(git_checkout_tree(repo, obj, &opts)); - cl_assert_equal_sz(0, git_index_name_entrycount(repo_index)); - - git_object_free(obj); -} - -void test_index_names__cleaned_on_checkout_head(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_UPDATE_ONLY; - - test_index_names__add(); - cl_git_pass(git_checkout_head(repo, &opts)); - cl_assert_equal_sz(0, git_index_name_entrycount(repo_index)); -} - -void test_index_names__retained_on_checkout_index(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_UPDATE_ONLY; - - test_index_names__add(); - cl_git_pass(git_checkout_index(repo, repo_index, &opts)); - cl_assert(git_index_name_entrycount(repo_index) > 0); -} diff --git a/tests/index/nsec.c b/tests/index/nsec.c deleted file mode 100644 index 3efd855a7..000000000 --- a/tests/index/nsec.c +++ /dev/null @@ -1,129 +0,0 @@ -#include "clar_libgit2.h" -#include "index.h" -#include "git2/sys/index.h" -#include "git2/repository.h" -#include "../reset/reset_helpers.h" - -static git_repository *repo; -static git_index *repo_index; - -#define TEST_REPO_PATH "nsecs" - -/* Fixture setup and teardown */ -void test_index_nsec__initialize(void) -{ - repo = cl_git_sandbox_init("nsecs"); - git_repository_index(&repo_index, repo); -} - -void test_index_nsec__cleanup(void) -{ - git_index_free(repo_index); - repo_index = NULL; - - cl_git_sandbox_cleanup(); -} - -static bool try_create_file_with_nsec_timestamp(const char *path) -{ - struct stat st; - int try; - - /* retry a few times to avoid nanos *actually* equal 0 race condition */ - for (try = 0; try < 3; try++) { - cl_git_mkfile(path, "This is hopefully a file with nanoseconds!"); - - cl_must_pass(p_stat(path, &st)); - - if (st.st_ctime_nsec && st.st_mtime_nsec) - return true; - } - - return false; -} - -/* try to determine if the underlying filesystem supports a resolution - * higher than a single second. (i'm looking at you, hfs+) - */ -static bool should_expect_nsecs(void) -{ - git_str nsec_path = GIT_STR_INIT; - bool expect; - - git_str_joinpath(&nsec_path, clar_sandbox_path(), "nsec_test"); - - expect = try_create_file_with_nsec_timestamp(nsec_path.ptr); - - cl_must_pass(p_unlink(nsec_path.ptr)); - - git_str_dispose(&nsec_path); - - return expect; -} - -static bool has_nsecs(void) -{ - const git_index_entry *entry; - size_t i; - bool has_nsecs = false; - - for (i = 0; i < git_index_entrycount(repo_index); i++) { - entry = git_index_get_byindex(repo_index, i); - - if (entry->ctime.nanoseconds || entry->mtime.nanoseconds) { - has_nsecs = true; - break; - } - } - - return has_nsecs; -} - -void test_index_nsec__has_nanos(void) -{ - cl_assert_equal_b(true, has_nsecs()); -} - -void test_index_nsec__staging_maintains_other_nanos(void) -{ - const git_index_entry *entry; - bool expect_nsec, test_file_has_nsec; - - expect_nsec = should_expect_nsecs(); - test_file_has_nsec = try_create_file_with_nsec_timestamp("nsecs/a.txt"); - - cl_assert_equal_b(expect_nsec, test_file_has_nsec); - - cl_git_pass(git_index_add_bypath(repo_index, "a.txt")); - cl_git_pass(git_index_write(repo_index)); - - cl_git_pass(git_index_write(repo_index)); - - git_index_read(repo_index, 1); - cl_assert_equal_b(true, has_nsecs()); - - cl_assert((entry = git_index_get_bypath(repo_index, "a.txt", 0))); - - /* if we are writing nanoseconds to the index, expect them to be - * nonzero. - */ - if (expect_nsec) { - cl_assert(entry->ctime.nanoseconds != 0); - cl_assert(entry->mtime.nanoseconds != 0); - } else { - cl_assert_equal_i(0, entry->ctime.nanoseconds); - cl_assert_equal_i(0, entry->mtime.nanoseconds); - } -} - -void test_index_nsec__status_doesnt_clear_nsecs(void) -{ - git_status_list *statuslist; - - cl_git_pass(git_status_list_new(&statuslist, repo, NULL)); - - git_index_read(repo_index, 1); - cl_assert_equal_b(true, has_nsecs()); - - git_status_list_free(statuslist); -} diff --git a/tests/index/racy.c b/tests/index/racy.c deleted file mode 100644 index 07b3b73d4..000000000 --- a/tests/index/racy.c +++ /dev/null @@ -1,323 +0,0 @@ -#include "clar_libgit2.h" -#include "../checkout/checkout_helpers.h" - -#include "index.h" -#include "repository.h" - -static git_repository *g_repo; - -void test_index_racy__initialize(void) -{ - cl_git_pass(git_repository_init(&g_repo, "diff_racy", false)); -} - -void test_index_racy__cleanup(void) -{ - git_repository_free(g_repo); - g_repo = NULL; - - cl_fixture_cleanup("diff_racy"); -} - -void test_index_racy__diff(void) -{ - git_index *index; - git_diff *diff; - git_str path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "A")); - cl_git_mkfile(path.ptr, "A"); - - /* Put 'A' into the index */ - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_add_bypath(index, "A")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); - cl_assert_equal_i(0, git_diff_num_deltas(diff)); - git_diff_free(diff); - - /* Change its contents quickly, so we get the same timestamp */ - cl_git_mkfile(path.ptr, "B"); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); - cl_assert_equal_i(1, git_diff_num_deltas(diff)); - - git_index_free(index); - git_diff_free(diff); - git_str_dispose(&path); -} - -void test_index_racy__write_index_just_after_file(void) -{ - git_index *index; - git_diff *diff; - git_str path = GIT_STR_INIT; - struct p_timeval times[2]; - - /* Make sure we do have a timestamp */ - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "A")); - cl_git_mkfile(path.ptr, "A"); - /* Force the file's timestamp to be a second after we wrote the index */ - times[0].tv_sec = index->stamp.mtime.tv_sec + 1; - times[0].tv_usec = index->stamp.mtime.tv_nsec / 1000; - times[1].tv_sec = index->stamp.mtime.tv_sec + 1; - times[1].tv_usec = index->stamp.mtime.tv_nsec / 1000; - cl_git_pass(p_utimes(path.ptr, times)); - - /* - * Put 'A' into the index, the size field will be filled, - * because the index' on-disk timestamp does not match the - * file's timestamp. - */ - cl_git_pass(git_index_add_bypath(index, "A")); - cl_git_pass(git_index_write(index)); - - cl_git_mkfile(path.ptr, "B"); - /* - * Pretend this index' modification happened a second after the - * file update, and rewrite the file in that same second. - */ - times[0].tv_sec = index->stamp.mtime.tv_sec + 2; - times[0].tv_usec = index->stamp.mtime.tv_nsec / 1000; - times[1].tv_sec = index->stamp.mtime.tv_sec + 2; - times[0].tv_usec = index->stamp.mtime.tv_nsec / 1000; - - cl_git_pass(p_utimes(git_index_path(index), times)); - cl_git_pass(p_utimes(path.ptr, times)); - - cl_git_pass(git_index_read(index, true)); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); - cl_assert_equal_i(1, git_diff_num_deltas(diff)); - - git_str_dispose(&path); - git_diff_free(diff); - git_index_free(index); -} - - -static void setup_race(void) -{ - git_str path = GIT_STR_INIT; - git_index *index; - git_index_entry *entry; - struct stat st; - - /* Make sure we do have a timestamp */ - cl_git_pass(git_repository_index__weakptr(&index, g_repo)); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "A")); - - cl_git_mkfile(path.ptr, "A"); - cl_git_pass(git_index_add_bypath(index, "A")); - - cl_git_mkfile(path.ptr, "B"); - cl_git_pass(git_index_write(index)); - - cl_git_mkfile(path.ptr, ""); - - cl_git_pass(p_stat(path.ptr, &st)); - cl_assert(entry = (git_index_entry *)git_index_get_bypath(index, "A", 0)); - - /* force a race */ - entry->mtime.seconds = (int32_t)st.st_mtime; - entry->mtime.nanoseconds = (int32_t)st.st_mtime_nsec; - - git_str_dispose(&path); -} - -void test_index_racy__smudges_index_entry_on_save(void) -{ - git_index *index; - const git_index_entry *entry; - - setup_race(); - - /* write the index, which will smudge anything that had the same timestamp - * as the index when the index was loaded. that way future loads of the - * index (with the new timestamp) will know that these files were not - * clean. - */ - - cl_git_pass(git_repository_index__weakptr(&index, g_repo)); - cl_git_pass(git_index_write(index)); - - cl_assert(entry = git_index_get_bypath(index, "A", 0)); - cl_assert_equal_i(0, entry->file_size); -} - -void test_index_racy__detects_diff_of_change_in_identical_timestamp(void) -{ - git_index *index; - git_diff *diff; - - cl_git_pass(git_repository_index__weakptr(&index, g_repo)); - - setup_race(); - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); - cl_assert_equal_i(1, git_diff_num_deltas(diff)); - - git_diff_free(diff); -} - -static void setup_uptodate_files(void) -{ - git_str path = GIT_STR_INIT; - git_index *index; - const git_index_entry *a_entry; - git_index_entry new_entry = {{0}}; - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "A")); - cl_git_mkfile(path.ptr, "A"); - - /* Put 'A' into the index */ - cl_git_pass(git_index_add_bypath(index, "A")); - - cl_assert((a_entry = git_index_get_bypath(index, "A", 0))); - - /* Put 'B' into the index */ - new_entry.path = "B"; - new_entry.mode = GIT_FILEMODE_BLOB; - git_oid_cpy(&new_entry.id, &a_entry->id); - cl_git_pass(git_index_add(index, &new_entry)); - - /* Put 'C' into the index */ - new_entry.path = "C"; - new_entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_index_add_from_buffer(index, &new_entry, "hello!\n", 7)); - - git_index_free(index); - git_str_dispose(&path); -} - -void test_index_racy__adding_to_index_is_uptodate(void) -{ - git_index *index; - const git_index_entry *entry; - - setup_uptodate_files(); - - cl_git_pass(git_repository_index(&index, g_repo)); - - /* ensure that they're all uptodate */ - cl_assert((entry = git_index_get_bypath(index, "A", 0))); - cl_assert_equal_i(GIT_INDEX_ENTRY_UPTODATE, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); - - cl_assert((entry = git_index_get_bypath(index, "B", 0))); - cl_assert_equal_i(GIT_INDEX_ENTRY_UPTODATE, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); - - cl_assert((entry = git_index_get_bypath(index, "C", 0))); - cl_assert_equal_i(GIT_INDEX_ENTRY_UPTODATE, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); - - cl_git_pass(git_index_write(index)); - - git_index_free(index); -} - -void test_index_racy__reading_clears_uptodate_bit(void) -{ - git_index *index; - const git_index_entry *entry; - - setup_uptodate_files(); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_index_read(index, true)); - - /* ensure that no files are uptodate */ - cl_assert((entry = git_index_get_bypath(index, "A", 0))); - cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); - - cl_assert((entry = git_index_get_bypath(index, "B", 0))); - cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); - - cl_assert((entry = git_index_get_bypath(index, "C", 0))); - cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); - - git_index_free(index); -} - -void test_index_racy__read_tree_clears_uptodate_bit(void) -{ - git_index *index; - git_tree *tree; - const git_index_entry *entry; - git_oid id; - - setup_uptodate_files(); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_write_tree_to(&id, index, g_repo)); - cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - cl_git_pass(git_index_read_tree(index, tree)); - - /* ensure that no files are uptodate */ - cl_assert((entry = git_index_get_bypath(index, "A", 0))); - cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); - - cl_assert((entry = git_index_get_bypath(index, "B", 0))); - cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); - - cl_assert((entry = git_index_get_bypath(index, "C", 0))); - cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); - - git_tree_free(tree); - git_index_free(index); -} - -void test_index_racy__read_index_smudges(void) -{ - git_index *index, *newindex; - const git_index_entry *entry; - - /* if we are reading an index into our new index, ensure that any - * racy entries in the index that we're reading are smudged so that - * we don't propagate their timestamps without further investigation. - */ - setup_race(); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_new(&newindex)); - cl_git_pass(git_index_read_index(newindex, index)); - - cl_assert(entry = git_index_get_bypath(newindex, "A", 0)); - cl_assert_equal_i(0, entry->file_size); - - git_index_free(index); - git_index_free(newindex); -} - -void test_index_racy__read_index_clears_uptodate_bit(void) -{ - git_index *index, *newindex; - const git_index_entry *entry; - - setup_uptodate_files(); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_new(&newindex)); - cl_git_pass(git_index_read_index(newindex, index)); - - /* ensure that files brought in from the other index are not uptodate */ - cl_assert((entry = git_index_get_bypath(newindex, "A", 0))); - cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); - - cl_assert((entry = git_index_get_bypath(newindex, "B", 0))); - cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); - - cl_assert((entry = git_index_get_bypath(newindex, "C", 0))); - cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); - - git_index_free(index); - git_index_free(newindex); -} diff --git a/tests/index/read_index.c b/tests/index/read_index.c deleted file mode 100644 index 836c12b0e..000000000 --- a/tests/index/read_index.c +++ /dev/null @@ -1,232 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "index.h" -#include "conflicts.h" - -static git_repository *_repo; -static git_index *_index; - -void test_index_read_index__initialize(void) -{ - git_object *head; - git_reference *head_ref; - - _repo = cl_git_sandbox_init("testrepo"); - cl_git_pass(git_revparse_ext(&head, &head_ref, _repo, "HEAD")); - cl_git_pass(git_reset(_repo, head, GIT_RESET_HARD, NULL)); - cl_git_pass(git_repository_index(&_index, _repo)); - - git_reference_free(head_ref); - git_object_free(head); -} - -void test_index_read_index__cleanup(void) -{ - git_index_free(_index); - cl_git_sandbox_cleanup(); -} - -void test_index_read_index__maintains_stat_cache(void) -{ - git_index *new_index; - git_oid index_id; - git_index_entry new_entry; - const git_index_entry *e; - git_tree *tree; - size_t i; - - cl_assert_equal_i(4, git_index_entrycount(_index)); - - /* write-tree */ - cl_git_pass(git_index_write_tree(&index_id, _index)); - - /* read-tree, then read index */ - git_tree_lookup(&tree, _repo, &index_id); - cl_git_pass(git_index_new(&new_index)); - cl_git_pass(git_index_read_tree(new_index, tree)); - git_tree_free(tree); - - /* add a new entry that will not have stat data */ - memset(&new_entry, 0, sizeof(git_index_entry)); - new_entry.path = "Hello"; - git_oid_fromstr(&new_entry.id, "0123456789012345678901234567890123456789"); - new_entry.file_size = 1234; - new_entry.mode = 0100644; - cl_git_pass(git_index_add(new_index, &new_entry)); - cl_assert_equal_i(5, git_index_entrycount(new_index)); - - cl_git_pass(git_index_read_index(_index, new_index)); - git_index_free(new_index); - - cl_assert_equal_i(5, git_index_entrycount(_index)); - - for (i = 0; i < git_index_entrycount(_index); i++) { - e = git_index_get_byindex(_index, i); - - if (strcmp(e->path, "Hello") == 0) { - cl_assert_equal_i(0, e->ctime.seconds); - cl_assert_equal_i(0, e->mtime.seconds); - } else { - cl_assert(0 != e->ctime.seconds); - cl_assert(0 != e->mtime.seconds); - } - } -} - -static bool roundtrip_with_read_index(const char *tree_idstr) -{ - git_oid tree_id, new_tree_id; - git_tree *tree; - git_index *tree_index; - - cl_git_pass(git_oid_fromstr(&tree_id, tree_idstr)); - cl_git_pass(git_tree_lookup(&tree, _repo, &tree_id)); - cl_git_pass(git_index_new(&tree_index)); - cl_git_pass(git_index_read_tree(tree_index, tree)); - cl_git_pass(git_index_read_index(_index, tree_index)); - cl_git_pass(git_index_write_tree(&new_tree_id, _index)); - - git_tree_free(tree); - git_index_free(tree_index); - - return git_oid_equal(&tree_id, &new_tree_id); -} - -void test_index_read_index__produces_treesame_indexes(void) -{ - roundtrip_with_read_index("53fc32d17276939fc79ed05badaef2db09990016"); - roundtrip_with_read_index("944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); - roundtrip_with_read_index("1810dff58d8a660512d4832e740f692884338ccd"); - roundtrip_with_read_index("d52a8fe84ceedf260afe4f0287bbfca04a117e83"); - roundtrip_with_read_index("c36d8ea75da8cb510fcb0c408c1d7e53f9a99dbe"); - roundtrip_with_read_index("7b2417a23b63e1fdde88c80e14b33247c6e5785a"); - roundtrip_with_read_index("f82a8eb4cb20e88d1030fd10d89286215a715396"); - roundtrip_with_read_index("fd093bff70906175335656e6ce6ae05783708765"); - roundtrip_with_read_index("ae90f12eea699729ed24555e40b9fd669da12a12"); -} - -void test_index_read_index__read_and_writes(void) -{ - git_oid tree_id, new_tree_id; - git_tree *tree; - git_index *tree_index, *new_index; - - cl_git_pass(git_oid_fromstr(&tree_id, "ae90f12eea699729ed24555e40b9fd669da12a12")); - cl_git_pass(git_tree_lookup(&tree, _repo, &tree_id)); - cl_git_pass(git_index_new(&tree_index)); - cl_git_pass(git_index_read_tree(tree_index, tree)); - cl_git_pass(git_index_read_index(_index, tree_index)); - cl_git_pass(git_index_write(_index)); - - cl_git_pass(git_index_open(&new_index, git_index_path(_index))); - cl_git_pass(git_index_write_tree_to(&new_tree_id, new_index, _repo)); - - cl_assert_equal_oid(&tree_id, &new_tree_id); - - git_tree_free(tree); - git_index_free(tree_index); - git_index_free(new_index); -} - -static void add_conflicts(git_index *index, const char *filename) -{ - git_index_entry ancestor_entry, our_entry, their_entry; - static int conflict_idx = 0; - char *ancestor_ids[] = - { CONFLICTS_ONE_ANCESTOR_OID, CONFLICTS_TWO_ANCESTOR_OID }; - char *our_ids[] = - { CONFLICTS_ONE_OUR_OID, CONFLICTS_TWO_OUR_OID }; - char *their_ids[] = - { CONFLICTS_ONE_THEIR_OID, CONFLICTS_TWO_THEIR_OID }; - - conflict_idx = (conflict_idx + 1) % 2; - - memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); - memset(&our_entry, 0x0, sizeof(git_index_entry)); - memset(&their_entry, 0x0, sizeof(git_index_entry)); - - ancestor_entry.path = filename; - ancestor_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 1); - git_oid_fromstr(&ancestor_entry.id, ancestor_ids[conflict_idx]); - - our_entry.path = filename; - our_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&our_entry, 2); - git_oid_fromstr(&our_entry.id, our_ids[conflict_idx]); - - their_entry.path = filename; - their_entry.mode = 0100644; - GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 2); - git_oid_fromstr(&their_entry.id, their_ids[conflict_idx]); - - cl_git_pass(git_index_conflict_add(index, &ancestor_entry, - &our_entry, &their_entry)); -} - -void test_index_read_index__handles_conflicts(void) -{ - git_oid tree_id; - git_tree *tree; - git_index *index, *new_index; - git_index_conflict_iterator *conflict_iterator; - const git_index_entry *ancestor, *ours, *theirs; - - cl_git_pass(git_oid_fromstr(&tree_id, "ae90f12eea699729ed24555e40b9fd669da12a12")); - cl_git_pass(git_tree_lookup(&tree, _repo, &tree_id)); - cl_git_pass(git_index_new(&index)); - cl_git_pass(git_index_new(&new_index)); - cl_git_pass(git_index_read_tree(index, tree)); - cl_git_pass(git_index_read_tree(new_index, tree)); - - /* put some conflicts in only the old side, these should be removed */ - add_conflicts(index, "orig_side-1.txt"); - add_conflicts(index, "orig_side-2.txt"); - - /* put some conflicts in both indexes, these should be unchanged */ - add_conflicts(index, "both_sides-1.txt"); - add_conflicts(new_index, "both_sides-1.txt"); - add_conflicts(index, "both_sides-2.txt"); - add_conflicts(new_index, "both_sides-2.txt"); - - /* put some conflicts in the new index, these should be added */ - add_conflicts(new_index, "new_side-1.txt"); - add_conflicts(new_index, "new_side-2.txt"); - - cl_git_pass(git_index_read_index(index, new_index)); - cl_git_pass(git_index_conflict_iterator_new(&conflict_iterator, index)); - - cl_git_pass(git_index_conflict_next( - &ancestor, &ours, &theirs, conflict_iterator)); - cl_assert_equal_s("both_sides-1.txt", ancestor->path); - cl_assert_equal_s("both_sides-1.txt", ours->path); - cl_assert_equal_s("both_sides-1.txt", theirs->path); - - cl_git_pass(git_index_conflict_next( - &ancestor, &ours, &theirs, conflict_iterator)); - cl_assert_equal_s("both_sides-2.txt", ancestor->path); - cl_assert_equal_s("both_sides-2.txt", ours->path); - cl_assert_equal_s("both_sides-2.txt", theirs->path); - - cl_git_pass(git_index_conflict_next( - &ancestor, &ours, &theirs, conflict_iterator)); - cl_assert_equal_s("new_side-1.txt", ancestor->path); - cl_assert_equal_s("new_side-1.txt", ours->path); - cl_assert_equal_s("new_side-1.txt", theirs->path); - - cl_git_pass(git_index_conflict_next( - &ancestor, &ours, &theirs, conflict_iterator)); - cl_assert_equal_s("new_side-2.txt", ancestor->path); - cl_assert_equal_s("new_side-2.txt", ours->path); - cl_assert_equal_s("new_side-2.txt", theirs->path); - - - cl_git_fail_with(GIT_ITEROVER, git_index_conflict_next( - &ancestor, &ours, &theirs, conflict_iterator)); - - git_index_conflict_iterator_free(conflict_iterator); - - git_tree_free(tree); - git_index_free(new_index); - git_index_free(index); -} diff --git a/tests/index/read_tree.c b/tests/index/read_tree.c deleted file mode 100644 index 0e1882818..000000000 --- a/tests/index/read_tree.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" - -/* Test that reading and writing a tree is a no-op */ -void test_index_read_tree__read_write_involution(void) -{ - git_repository *repo; - git_index *index; - git_oid tree_oid; - git_tree *tree; - git_oid expected; - - p_mkdir("read_tree", 0700); - - cl_git_pass(git_repository_init(&repo, "./read_tree", 0)); - cl_git_pass(git_repository_index(&index, repo)); - - cl_assert(git_index_entrycount(index) == 0); - - p_mkdir("./read_tree/abc", 0700); - - /* Sort order: '-' < '/' < '_' */ - cl_git_mkfile("./read_tree/abc-d", NULL); - cl_git_mkfile("./read_tree/abc/d", NULL); - cl_git_mkfile("./read_tree/abc_d", NULL); - - cl_git_pass(git_index_add_bypath(index, "abc-d")); - cl_git_pass(git_index_add_bypath(index, "abc_d")); - cl_git_pass(git_index_add_bypath(index, "abc/d")); - - /* write-tree */ - cl_git_pass(git_index_write_tree(&expected, index)); - - /* read-tree */ - git_tree_lookup(&tree, repo, &expected); - cl_git_pass(git_index_read_tree(index, tree)); - git_tree_free(tree); - - cl_git_pass(git_index_write_tree(&tree_oid, index)); - cl_assert_equal_oid(&expected, &tree_oid); - - git_index_free(index); - git_repository_free(repo); - - cl_fixture_cleanup("read_tree"); -} diff --git a/tests/index/rename.c b/tests/index/rename.c deleted file mode 100644 index 86eaf0053..000000000 --- a/tests/index/rename.c +++ /dev/null @@ -1,86 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" - -void test_index_rename__single_file(void) -{ - git_repository *repo; - git_index *index; - size_t position; - git_oid expected; - const git_index_entry *entry; - - p_mkdir("rename", 0700); - - cl_git_pass(git_repository_init(&repo, "./rename", 0)); - cl_git_pass(git_repository_index(&index, repo)); - - cl_assert(git_index_entrycount(index) == 0); - - cl_git_mkfile("./rename/lame.name.txt", "new_file\n"); - - /* This should add a new blob to the object database in 'd4/fa8600b4f37d7516bef4816ae2c64dbf029e3a' */ - cl_git_pass(git_index_add_bypath(index, "lame.name.txt")); - cl_assert(git_index_entrycount(index) == 1); - - cl_git_pass(git_oid_fromstr(&expected, "d4fa8600b4f37d7516bef4816ae2c64dbf029e3a")); - - cl_assert(!git_index_find(&position, index, "lame.name.txt")); - - entry = git_index_get_byindex(index, position); - cl_assert_equal_oid(&expected, &entry->id); - - /* This removes the entry from the index, but not from the object database */ - cl_git_pass(git_index_remove(index, "lame.name.txt", 0)); - cl_assert(git_index_entrycount(index) == 0); - - p_rename("./rename/lame.name.txt", "./rename/fancy.name.txt"); - - cl_git_pass(git_index_add_bypath(index, "fancy.name.txt")); - cl_assert(git_index_entrycount(index) == 1); - - cl_assert(!git_index_find(&position, index, "fancy.name.txt")); - - entry = git_index_get_byindex(index, position); - cl_assert_equal_oid(&expected, &entry->id); - - git_index_free(index); - git_repository_free(repo); - - cl_fixture_cleanup("rename"); -} - -void test_index_rename__casechanging(void) -{ - git_repository *repo; - git_index *index; - const git_index_entry *entry; - git_index_entry new = {{0}}; - - p_mkdir("rename", 0700); - - cl_git_pass(git_repository_init(&repo, "./rename", 0)); - cl_git_pass(git_repository_index(&index, repo)); - - cl_git_mkfile("./rename/lame.name.txt", "new_file\n"); - - cl_git_pass(git_index_add_bypath(index, "lame.name.txt")); - cl_assert_equal_i(1, git_index_entrycount(index)); - cl_assert((entry = git_index_get_bypath(index, "lame.name.txt", 0))); - - memcpy(&new, entry, sizeof(git_index_entry)); - new.path = "LAME.name.TXT"; - - cl_git_pass(git_index_add(index, &new)); - cl_assert((entry = git_index_get_bypath(index, "LAME.name.TXT", 0))); - - if (cl_repo_get_bool(repo, "core.ignorecase")) - cl_assert_equal_i(1, git_index_entrycount(index)); - else - cl_assert_equal_i(2, git_index_entrycount(index)); - - git_index_free(index); - git_repository_free(repo); - - cl_fixture_cleanup("rename"); -} - diff --git a/tests/index/reuc.c b/tests/index/reuc.c deleted file mode 100644 index 1f95c4136..000000000 --- a/tests/index/reuc.c +++ /dev/null @@ -1,376 +0,0 @@ -#include "clar_libgit2.h" -#include "index.h" -#include "git2/sys/index.h" -#include "git2/repository.h" -#include "../reset/reset_helpers.h" - -static git_repository *repo; -static git_index *repo_index; - -#define TEST_REPO_PATH "mergedrepo" -#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" - -#define ONE_ANCESTOR_OID "478871385b9cd03908c5383acfd568bef023c6b3" -#define ONE_OUR_OID "4458b8bc9e72b6c8755ae456f60e9844d0538d8c" -#define ONE_THEIR_OID "8b72416545c7e761b64cecad4f1686eae4078aa8" - -#define TWO_ANCESTOR_OID "9d81f82fccc7dcd7de7a1ffead1815294c2e092c" -#define TWO_OUR_OID "8f3c06cff9a83757cec40c80bc9bf31a2582bde9" -#define TWO_THEIR_OID "887b153b165d32409c70163e0f734c090f12f673" - -/* Fixture setup and teardown */ -void test_index_reuc__initialize(void) -{ - repo = cl_git_sandbox_init("mergedrepo"); - git_repository_index(&repo_index, repo); -} - -void test_index_reuc__cleanup(void) -{ - git_index_free(repo_index); - repo_index = NULL; - - cl_git_sandbox_cleanup(); -} - -void test_index_reuc__add(void) -{ - git_oid ancestor_oid, our_oid, their_oid; - const git_index_reuc_entry *reuc; - - git_oid_fromstr(&ancestor_oid, ONE_ANCESTOR_OID); - git_oid_fromstr(&our_oid, ONE_OUR_OID); - git_oid_fromstr(&their_oid, ONE_THEIR_OID); - - cl_git_pass(git_index_reuc_add(repo_index, "newfile.txt", - 0100644, &ancestor_oid, - 0100644, &our_oid, - 0100644, &their_oid)); - - cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "newfile.txt")); - - cl_assert_equal_s("newfile.txt", reuc->path); - cl_assert(reuc->mode[0] == 0100644); - cl_assert(reuc->mode[1] == 0100644); - cl_assert(reuc->mode[2] == 0100644); - cl_assert_equal_oid(&reuc->oid[0], &ancestor_oid); - cl_assert_equal_oid(&reuc->oid[1], &our_oid); - cl_assert_equal_oid(&reuc->oid[2], &their_oid); - - cl_git_pass(git_index_write(repo_index)); -} - -void test_index_reuc__add_no_ancestor(void) -{ - git_oid ancestor_oid, our_oid, their_oid; - const git_index_reuc_entry *reuc; - - memset(&ancestor_oid, 0x0, sizeof(git_oid)); - git_oid_fromstr(&our_oid, ONE_OUR_OID); - git_oid_fromstr(&their_oid, ONE_THEIR_OID); - - cl_git_pass(git_index_reuc_add(repo_index, "newfile.txt", - 0, NULL, - 0100644, &our_oid, - 0100644, &their_oid)); - - cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "newfile.txt")); - - cl_assert_equal_s("newfile.txt", reuc->path); - cl_assert(reuc->mode[0] == 0); - cl_assert(reuc->mode[1] == 0100644); - cl_assert(reuc->mode[2] == 0100644); - cl_assert_equal_oid(&reuc->oid[0], &ancestor_oid); - cl_assert_equal_oid(&reuc->oid[1], &our_oid); - cl_assert_equal_oid(&reuc->oid[2], &their_oid); - - cl_git_pass(git_index_write(repo_index)); -} - -void test_index_reuc__read_bypath(void) -{ - const git_index_reuc_entry *reuc; - git_oid oid; - - cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); - - cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "two.txt")); - - cl_assert_equal_s("two.txt", reuc->path); - cl_assert(reuc->mode[0] == 0100644); - cl_assert(reuc->mode[1] == 0100644); - cl_assert(reuc->mode[2] == 0100644); - git_oid_fromstr(&oid, TWO_ANCESTOR_OID); - cl_assert_equal_oid(&reuc->oid[0], &oid); - git_oid_fromstr(&oid, TWO_OUR_OID); - cl_assert_equal_oid(&reuc->oid[1], &oid); - git_oid_fromstr(&oid, TWO_THEIR_OID); - cl_assert_equal_oid(&reuc->oid[2], &oid); - - cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "one.txt")); - - cl_assert_equal_s("one.txt", reuc->path); - cl_assert(reuc->mode[0] == 0100644); - cl_assert(reuc->mode[1] == 0100644); - cl_assert(reuc->mode[2] == 0100644); - git_oid_fromstr(&oid, ONE_ANCESTOR_OID); - cl_assert_equal_oid(&reuc->oid[0], &oid); - git_oid_fromstr(&oid, ONE_OUR_OID); - cl_assert_equal_oid(&reuc->oid[1], &oid); - git_oid_fromstr(&oid, ONE_THEIR_OID); - cl_assert_equal_oid(&reuc->oid[2], &oid); -} - -void test_index_reuc__ignore_case(void) -{ - const git_index_reuc_entry *reuc; - git_oid oid; - int index_caps; - - index_caps = git_index_caps(repo_index); - - index_caps &= ~GIT_INDEX_CAPABILITY_IGNORE_CASE; - cl_git_pass(git_index_set_caps(repo_index, index_caps)); - - cl_assert(!git_index_reuc_get_bypath(repo_index, "TWO.txt")); - - index_caps |= GIT_INDEX_CAPABILITY_IGNORE_CASE; - cl_git_pass(git_index_set_caps(repo_index, index_caps)); - - cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); - - cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "TWO.txt")); - - cl_assert_equal_s("two.txt", reuc->path); - cl_assert(reuc->mode[0] == 0100644); - cl_assert(reuc->mode[1] == 0100644); - cl_assert(reuc->mode[2] == 0100644); - git_oid_fromstr(&oid, TWO_ANCESTOR_OID); - cl_assert_equal_oid(&reuc->oid[0], &oid); - git_oid_fromstr(&oid, TWO_OUR_OID); - cl_assert_equal_oid(&reuc->oid[1], &oid); - git_oid_fromstr(&oid, TWO_THEIR_OID); - cl_assert_equal_oid(&reuc->oid[2], &oid); -} - -void test_index_reuc__read_byindex(void) -{ - const git_index_reuc_entry *reuc; - git_oid oid; - - cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); - - cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); - - cl_assert_equal_s("one.txt", reuc->path); - cl_assert(reuc->mode[0] == 0100644); - cl_assert(reuc->mode[1] == 0100644); - cl_assert(reuc->mode[2] == 0100644); - git_oid_fromstr(&oid, ONE_ANCESTOR_OID); - cl_assert_equal_oid(&reuc->oid[0], &oid); - git_oid_fromstr(&oid, ONE_OUR_OID); - cl_assert_equal_oid(&reuc->oid[1], &oid); - git_oid_fromstr(&oid, ONE_THEIR_OID); - cl_assert_equal_oid(&reuc->oid[2], &oid); - - cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 1)); - - cl_assert_equal_s("two.txt", reuc->path); - cl_assert(reuc->mode[0] == 0100644); - cl_assert(reuc->mode[1] == 0100644); - cl_assert(reuc->mode[2] == 0100644); - git_oid_fromstr(&oid, TWO_ANCESTOR_OID); - cl_assert_equal_oid(&reuc->oid[0], &oid); - git_oid_fromstr(&oid, TWO_OUR_OID); - cl_assert_equal_oid(&reuc->oid[1], &oid); - git_oid_fromstr(&oid, TWO_THEIR_OID); - cl_assert_equal_oid(&reuc->oid[2], &oid); -} - -void test_index_reuc__updates_existing(void) -{ - const git_index_reuc_entry *reuc; - git_oid ancestor_oid, our_oid, their_oid, oid; - int index_caps; - - git_index_clear(repo_index); - - index_caps = git_index_caps(repo_index); - - index_caps |= GIT_INDEX_CAPABILITY_IGNORE_CASE; - cl_git_pass(git_index_set_caps(repo_index, index_caps)); - - git_oid_fromstr(&ancestor_oid, TWO_ANCESTOR_OID); - git_oid_fromstr(&our_oid, TWO_OUR_OID); - git_oid_fromstr(&their_oid, TWO_THEIR_OID); - - cl_git_pass(git_index_reuc_add(repo_index, "two.txt", - 0100644, &ancestor_oid, - 0100644, &our_oid, - 0100644, &their_oid)); - - cl_git_pass(git_index_reuc_add(repo_index, "TWO.txt", - 0100644, &our_oid, - 0100644, &their_oid, - 0100644, &ancestor_oid)); - - cl_assert_equal_i(1, git_index_reuc_entrycount(repo_index)); - - cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); - - cl_assert_equal_s("TWO.txt", reuc->path); - git_oid_fromstr(&oid, TWO_OUR_OID); - cl_assert_equal_oid(&reuc->oid[0], &oid); - git_oid_fromstr(&oid, TWO_THEIR_OID); - cl_assert_equal_oid(&reuc->oid[1], &oid); - git_oid_fromstr(&oid, TWO_ANCESTOR_OID); - cl_assert_equal_oid(&reuc->oid[2], &oid); -} - -void test_index_reuc__remove(void) -{ - git_oid oid; - const git_index_reuc_entry *reuc; - - cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); - - cl_git_pass(git_index_reuc_remove(repo_index, 0)); - cl_git_fail(git_index_reuc_remove(repo_index, 1)); - - cl_assert_equal_i(1, git_index_reuc_entrycount(repo_index)); - - cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); - - cl_assert_equal_s("two.txt", reuc->path); - cl_assert(reuc->mode[0] == 0100644); - cl_assert(reuc->mode[1] == 0100644); - cl_assert(reuc->mode[2] == 0100644); - git_oid_fromstr(&oid, TWO_ANCESTOR_OID); - cl_assert_equal_oid(&reuc->oid[0], &oid); - git_oid_fromstr(&oid, TWO_OUR_OID); - cl_assert_equal_oid(&reuc->oid[1], &oid); - git_oid_fromstr(&oid, TWO_THEIR_OID); - cl_assert_equal_oid(&reuc->oid[2], &oid); -} - -void test_index_reuc__write(void) -{ - git_oid ancestor_oid, our_oid, their_oid; - const git_index_reuc_entry *reuc; - - git_index_clear(repo_index); - - /* Write out of order to ensure sorting is correct */ - git_oid_fromstr(&ancestor_oid, TWO_ANCESTOR_OID); - git_oid_fromstr(&our_oid, TWO_OUR_OID); - git_oid_fromstr(&their_oid, TWO_THEIR_OID); - - cl_git_pass(git_index_reuc_add(repo_index, "two.txt", - 0100644, &ancestor_oid, - 0100644, &our_oid, - 0100644, &their_oid)); - - git_oid_fromstr(&ancestor_oid, ONE_ANCESTOR_OID); - git_oid_fromstr(&our_oid, ONE_OUR_OID); - git_oid_fromstr(&their_oid, ONE_THEIR_OID); - - cl_git_pass(git_index_reuc_add(repo_index, "one.txt", - 0100644, &ancestor_oid, - 0100644, &our_oid, - 0100644, &their_oid)); - - cl_git_pass(git_index_write(repo_index)); - cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); - - /* ensure sort order was round-tripped correct */ - cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); - cl_assert_equal_s("one.txt", reuc->path); - - cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 1)); - cl_assert_equal_s("two.txt", reuc->path); -} - -static int reuc_entry_exists(void) -{ - return (git_index_reuc_get_bypath(repo_index, "newfile.txt") != NULL); -} - -void test_index_reuc__cleaned_on_reset_hard(void) -{ - git_object *target; - - cl_git_pass(git_revparse_single(&target, repo, "3a34580")); - - test_index_reuc__add(); - cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); - cl_assert(reuc_entry_exists() == false); - - git_object_free(target); -} - -void test_index_reuc__cleaned_on_reset_mixed(void) -{ - git_object *target; - - cl_git_pass(git_revparse_single(&target, repo, "3a34580")); - - test_index_reuc__add(); - cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL)); - cl_assert(reuc_entry_exists() == false); - - git_object_free(target); -} - -void test_index_reuc__retained_on_reset_soft(void) -{ - git_object *target; - - cl_git_pass(git_revparse_single(&target, repo, "3a34580")); - - cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); - - test_index_reuc__add(); - cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); - cl_assert(reuc_entry_exists() == true); - - git_object_free(target); -} - -void test_index_reuc__cleaned_on_checkout_tree(void) -{ - git_oid oid; - git_object *obj; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - test_index_reuc__add(); - cl_git_pass(git_reference_name_to_id(&oid, repo, "refs/heads/master")); - cl_git_pass(git_object_lookup(&obj, repo, &oid, GIT_OBJECT_ANY)); - cl_git_pass(git_checkout_tree(repo, obj, &opts)); - cl_assert(reuc_entry_exists() == false); - - git_object_free(obj); -} - -void test_index_reuc__cleaned_on_checkout_head(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - test_index_reuc__add(); - cl_git_pass(git_checkout_head(repo, &opts)); - cl_assert(reuc_entry_exists() == false); -} - -void test_index_reuc__retained_on_checkout_index(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - test_index_reuc__add(); - cl_git_pass(git_checkout_index(repo, repo_index, &opts)); - cl_assert(reuc_entry_exists() == true); -} diff --git a/tests/index/splitindex.c b/tests/index/splitindex.c deleted file mode 100644 index d32ed1022..000000000 --- a/tests/index/splitindex.c +++ /dev/null @@ -1,21 +0,0 @@ -#include "clar_libgit2.h" -#include "index.h" - -static git_repository *g_repo; - -void test_index_splitindex__initialize(void) -{ - g_repo = cl_git_sandbox_init("splitindex"); -} - -void test_index_splitindex__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_index_splitindex__fail_on_open(void) -{ - git_index *idx; - cl_git_fail_with(-1, git_repository_index(&idx, g_repo)); - cl_assert_equal_s(git_error_last()->message, "unsupported mandatory extension: 'link'"); -} diff --git a/tests/index/stage.c b/tests/index/stage.c deleted file mode 100644 index cdfe3a811..000000000 --- a/tests/index/stage.c +++ /dev/null @@ -1,62 +0,0 @@ -#include "clar_libgit2.h" -#include "index.h" -#include "git2/repository.h" - -static git_repository *repo; -static git_index *repo_index; - -#define TEST_REPO_PATH "mergedrepo" -#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" - -/* Fixture setup and teardown */ -void test_index_stage__initialize(void) -{ - repo = cl_git_sandbox_init("mergedrepo"); - git_repository_index(&repo_index, repo); -} - -void test_index_stage__cleanup(void) -{ - git_index_free(repo_index); - repo_index = NULL; - - cl_git_sandbox_cleanup(); -} - - -void test_index_stage__add_always_adds_stage_0(void) -{ - size_t entry_idx; - const git_index_entry *entry; - - cl_git_mkfile("./mergedrepo/new-file.txt", "new-file\n"); - - cl_git_pass(git_index_add_bypath(repo_index, "new-file.txt")); - - cl_assert(!git_index_find(&entry_idx, repo_index, "new-file.txt")); - cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); - cl_assert(git_index_entry_stage(entry) == 0); -} - -void test_index_stage__find_gets_first_stage(void) -{ - size_t entry_idx; - const git_index_entry *entry; - - cl_assert(!git_index_find(&entry_idx, repo_index, "one.txt")); - cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); - cl_assert(git_index_entry_stage(entry) == 0); - - cl_assert(!git_index_find(&entry_idx, repo_index, "two.txt")); - cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); - cl_assert(git_index_entry_stage(entry) == 0); - - cl_assert(!git_index_find(&entry_idx, repo_index, "conflicts-one.txt")); - cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); - cl_assert(git_index_entry_stage(entry) == 1); - - cl_assert(!git_index_find(&entry_idx, repo_index, "conflicts-two.txt")); - cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); - cl_assert(git_index_entry_stage(entry) == 1); -} - diff --git a/tests/index/tests.c b/tests/index/tests.c deleted file mode 100644 index 205d12e5b..000000000 --- a/tests/index/tests.c +++ /dev/null @@ -1,1173 +0,0 @@ -#include "clar_libgit2.h" -#include "index.h" - -static const size_t index_entry_count = 109; -static const size_t index_entry_count_2 = 1437; -#define TEST_INDEX_PATH cl_fixture("testrepo.git/index") -#define TEST_INDEX2_PATH cl_fixture("gitgit.index") -#define TEST_INDEXBIG_PATH cl_fixture("big.index") -#define TEST_INDEXBAD_PATH cl_fixture("bad.index") - - -/* Suite data */ -struct test_entry { - size_t index; - char path[128]; - off64_t file_size; - git_time_t mtime; -}; - -static struct test_entry test_entries[] = { - {4, "Makefile", 5064, 0x4C3F7F33}, - {6, "git.git-authors", 2709, 0x4C3F7F33}, - {36, "src/index.c", 10014, 0x4C43368D}, - {48, "src/revobject.h", 1448, 0x4C3F7FE2}, - {62, "tests/Makefile", 2631, 0x4C3F7F33} -}; - -/* Helpers */ -static void copy_file(const char *src, const char *dst) -{ - git_str source_buf = GIT_STR_INIT; - git_file dst_fd; - - cl_git_pass(git_futils_readbuffer(&source_buf, src)); - - dst_fd = git_futils_creat_withpath(dst, 0777, 0666); /* -V536 */ - if (dst_fd < 0) - goto cleanup; - - cl_git_pass(p_write(dst_fd, source_buf.ptr, source_buf.size)); - -cleanup: - git_str_dispose(&source_buf); - p_close(dst_fd); -} - -static void files_are_equal(const char *a, const char *b) -{ - git_str buf_a = GIT_STR_INIT; - git_str buf_b = GIT_STR_INIT; - int pass; - - if (git_futils_readbuffer(&buf_a, a) < 0) - cl_assert(0); - - if (git_futils_readbuffer(&buf_b, b) < 0) { - git_str_dispose(&buf_a); - cl_assert(0); - } - - pass = (buf_a.size == buf_b.size && !memcmp(buf_a.ptr, buf_b.ptr, buf_a.size)); - - git_str_dispose(&buf_a); - git_str_dispose(&buf_b); - - cl_assert(pass); -} - - -/* Fixture setup and teardown */ -void test_index_tests__initialize(void) -{ -} - -void test_index_tests__cleanup(void) -{ - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, 0)); -} - -void test_index_tests__empty_index(void) -{ - git_index *index; - - cl_git_pass(git_index_open(&index, "in-memory-index")); - cl_assert(index->on_disk == 0); - - cl_assert(git_index_entrycount(index) == 0); - cl_assert(git_vector_is_sorted(&index->entries)); - - git_index_free(index); -} - -void test_index_tests__default_test_index(void) -{ - git_index *index; - unsigned int i; - git_index_entry **entries; - - cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); - cl_assert(index->on_disk); - - cl_assert(git_index_entrycount(index) == index_entry_count); - cl_assert(git_vector_is_sorted(&index->entries)); - - entries = (git_index_entry **)index->entries.contents; - - for (i = 0; i < ARRAY_SIZE(test_entries); ++i) { - git_index_entry *e = entries[test_entries[i].index]; - - cl_assert_equal_s(e->path, test_entries[i].path); - cl_assert_equal_i(e->mtime.seconds, test_entries[i].mtime); - cl_assert_equal_i(e->file_size, test_entries[i].file_size); - } - - git_index_free(index); -} - -void test_index_tests__gitgit_index(void) -{ - git_index *index; - - cl_git_pass(git_index_open(&index, TEST_INDEX2_PATH)); - cl_assert(index->on_disk); - - cl_assert(git_index_entrycount(index) == index_entry_count_2); - cl_assert(git_vector_is_sorted(&index->entries)); - cl_assert(index->tree != NULL); - - git_index_free(index); -} - -void test_index_tests__find_in_existing(void) -{ - git_index *index; - unsigned int i; - - cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); - - for (i = 0; i < ARRAY_SIZE(test_entries); ++i) { - size_t idx; - - cl_assert(!git_index_find(&idx, index, test_entries[i].path)); - cl_assert(idx == test_entries[i].index); - } - - git_index_free(index); -} - -void test_index_tests__find_in_empty(void) -{ - git_index *index; - unsigned int i; - - cl_git_pass(git_index_open(&index, "fake-index")); - - for (i = 0; i < ARRAY_SIZE(test_entries); ++i) { - cl_assert(GIT_ENOTFOUND == git_index_find(NULL, index, test_entries[i].path)); - } - - git_index_free(index); -} - -void test_index_tests__find_prefix(void) -{ - git_index *index; - const git_index_entry *entry; - size_t pos; - - cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); - - cl_git_pass(git_index_find_prefix(&pos, index, "src")); - entry = git_index_get_byindex(index, pos); - cl_assert(git__strcmp(entry->path, "src/block-sha1/sha1.c") == 0); - - cl_git_pass(git_index_find_prefix(&pos, index, "src/co")); - entry = git_index_get_byindex(index, pos); - cl_assert(git__strcmp(entry->path, "src/commit.c") == 0); - - cl_assert(GIT_ENOTFOUND == git_index_find_prefix(NULL, index, "blah")); - - git_index_free(index); -} - -void test_index_tests__write(void) -{ - git_index *index; - - copy_file(TEST_INDEXBIG_PATH, "index_rewrite"); - - cl_git_pass(git_index_open(&index, "index_rewrite")); - cl_assert(index->on_disk); - - cl_git_pass(git_index_write(index)); - files_are_equal(TEST_INDEXBIG_PATH, "index_rewrite"); - - git_index_free(index); - - p_unlink("index_rewrite"); -} - -void test_index_tests__sort0(void) -{ - /* sort the entries in an index */ - - /* - * TODO: This no longer applies: - * index sorting in Git uses some specific changes to the way - * directories are sorted. - * - * We need to specifically check for this by creating a new - * index, adding entries in random order and then - * checking for consistency - */ -} - -void test_index_tests__sort1(void) -{ - /* sort the entries in an empty index */ - git_index *index; - - cl_git_pass(git_index_open(&index, "fake-index")); - - /* FIXME: this test is slightly dumb */ - cl_assert(git_vector_is_sorted(&index->entries)); - - git_index_free(index); -} - -static void cleanup_myrepo(void *opaque) -{ - GIT_UNUSED(opaque); - cl_fixture_cleanup("myrepo"); -} - -void test_index_tests__add(void) -{ - git_index *index; - git_filebuf file = GIT_FILEBUF_INIT; - git_repository *repo; - const git_index_entry *entry; - git_oid id1; - - cl_set_cleanup(&cleanup_myrepo, NULL); - - /* Initialize a new repository */ - cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); - - /* Ensure we're the only guy in the room */ - cl_git_pass(git_repository_index(&index, repo)); - cl_assert(git_index_entrycount(index) == 0); - - /* Create a new file in the working directory */ - cl_git_pass(git_futils_mkpath2file("myrepo/test.txt", 0777)); - cl_git_pass(git_filebuf_open(&file, "myrepo/test.txt", 0, 0666)); - cl_git_pass(git_filebuf_write(&file, "hey there\n", 10)); - cl_git_pass(git_filebuf_commit(&file)); - - /* Store the expected hash of the file/blob - * This has been generated by executing the following - * $ echo "hey there" | git hash-object --stdin - */ - cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); - - /* Add the new file to the index */ - cl_git_pass(git_index_add_bypath(index, "test.txt")); - - /* Wow... it worked! */ - cl_assert(git_index_entrycount(index) == 1); - entry = git_index_get_byindex(index, 0); - - /* And the built-in hashing mechanism worked as expected */ - cl_assert_equal_oid(&id1, &entry->id); - - /* Test access by path instead of index */ - cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); - cl_assert_equal_oid(&id1, &entry->id); - - git_index_free(index); - git_repository_free(repo); -} - -void test_index_tests__add_frombuffer(void) -{ - git_index *index; - git_repository *repo; - git_index_entry entry; - const git_index_entry *returned_entry; - - git_oid id1; - git_blob *blob; - - const char *content = "hey there\n"; - - cl_set_cleanup(&cleanup_myrepo, NULL); - - /* Initialize a new repository */ - cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); - - /* Ensure we're the only guy in the room */ - cl_git_pass(git_repository_index(&index, repo)); - cl_assert(git_index_entrycount(index) == 0); - - /* Store the expected hash of the file/blob - * This has been generated by executing the following - * $ echo "hey there" | git hash-object --stdin - */ - cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); - - /* Add the new file to the index */ - memset(&entry, 0x0, sizeof(git_index_entry)); - entry.mode = GIT_FILEMODE_BLOB; - entry.path = "test.txt"; - cl_git_pass(git_index_add_from_buffer(index, &entry, - content, strlen(content))); - - /* Wow... it worked! */ - cl_assert(git_index_entrycount(index) == 1); - returned_entry = git_index_get_byindex(index, 0); - - /* And the built-in hashing mechanism worked as expected */ - cl_assert_equal_oid(&id1, &returned_entry->id); - /* And mode is the one asked */ - cl_assert_equal_i(GIT_FILEMODE_BLOB, returned_entry->mode); - - /* Test access by path instead of index */ - cl_assert((returned_entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); - cl_assert_equal_oid(&id1, &returned_entry->id); - - /* Test the blob is in the repository */ - cl_git_pass(git_blob_lookup(&blob, repo, &id1)); - cl_assert_equal_s( - content, git_blob_rawcontent(blob)); - git_blob_free(blob); - - git_index_free(index); - git_repository_free(repo); -} - -void test_index_tests__dirty_and_clean(void) -{ - git_repository *repo; - git_index *index; - git_index_entry entry = {{0}}; - - /* Index is not dirty after opening */ - cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); - cl_git_pass(git_repository_index(&index, repo)); - - cl_assert(git_index_entrycount(index) == 0); - cl_assert(!git_index_is_dirty(index)); - - /* Index is dirty after adding an entry */ - entry.mode = GIT_FILEMODE_BLOB; - entry.path = "test.txt"; - cl_git_pass(git_index_add_from_buffer(index, &entry, "Hi.\n", 4)); - cl_assert(git_index_entrycount(index) == 1); - cl_assert(git_index_is_dirty(index)); - - /* Index is not dirty after write */ - cl_git_pass(git_index_write(index)); - cl_assert(!git_index_is_dirty(index)); - - /* Index is dirty after removing an entry */ - cl_git_pass(git_index_remove_bypath(index, "test.txt")); - cl_assert(git_index_entrycount(index) == 0); - cl_assert(git_index_is_dirty(index)); - - /* Index is not dirty after write */ - cl_git_pass(git_index_write(index)); - cl_assert(!git_index_is_dirty(index)); - - /* Index remains not dirty after read */ - cl_git_pass(git_index_read(index, 0)); - cl_assert(!git_index_is_dirty(index)); - - /* Index is dirty when we do an unforced read with dirty content */ - cl_git_pass(git_index_add_from_buffer(index, &entry, "Hi.\n", 4)); - cl_assert(git_index_entrycount(index) == 1); - cl_assert(git_index_is_dirty(index)); - - cl_git_pass(git_index_read(index, 0)); - cl_assert(git_index_is_dirty(index)); - - /* Index is clean when we force a read with dirty content */ - cl_git_pass(git_index_read(index, 1)); - cl_assert(!git_index_is_dirty(index)); - - git_index_free(index); - git_repository_free(repo); -} - -void test_index_tests__dirty_fails_optionally(void) -{ - git_repository *repo; - git_index *index; - git_index_entry entry = {{0}}; - - /* Index is not dirty after opening */ - repo = cl_git_sandbox_init("testrepo"); - cl_git_pass(git_repository_index(&index, repo)); - - /* Index is dirty after adding an entry */ - entry.mode = GIT_FILEMODE_BLOB; - entry.path = "test.txt"; - cl_git_pass(git_index_add_from_buffer(index, &entry, "Hi.\n", 4)); - cl_assert(git_index_is_dirty(index)); - - cl_git_pass(git_checkout_head(repo, NULL)); - - /* Index is dirty (again) after adding an entry */ - entry.mode = GIT_FILEMODE_BLOB; - entry.path = "test.txt"; - cl_git_pass(git_index_add_from_buffer(index, &entry, "Hi.\n", 4)); - cl_assert(git_index_is_dirty(index)); - - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, 1)); - cl_git_fail_with(GIT_EINDEXDIRTY, git_checkout_head(repo, NULL)); - - git_index_free(index); - cl_git_sandbox_cleanup(); -} - -void test_index_tests__add_frombuffer_reset_entry(void) -{ - git_index *index; - git_repository *repo; - git_index_entry entry; - const git_index_entry *returned_entry; - git_filebuf file = GIT_FILEBUF_INIT; - - git_oid id1; - git_blob *blob; - const char *old_content = "here\n"; - const char *content = "hey there\n"; - - cl_set_cleanup(&cleanup_myrepo, NULL); - - /* Initialize a new repository */ - cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_futils_mkpath2file("myrepo/test.txt", 0777)); - cl_git_pass(git_filebuf_open(&file, "myrepo/test.txt", 0, 0666)); - cl_git_pass(git_filebuf_write(&file, old_content, strlen(old_content))); - cl_git_pass(git_filebuf_commit(&file)); - - /* Store the expected hash of the file/blob - * This has been generated by executing the following - * $ echo "hey there" | git hash-object --stdin - */ - cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); - - cl_git_pass(git_index_add_bypath(index, "test.txt")); - - /* Add the new file to the index */ - memset(&entry, 0x0, sizeof(git_index_entry)); - entry.mode = GIT_FILEMODE_BLOB; - entry.path = "test.txt"; - cl_git_pass(git_index_add_from_buffer(index, &entry, - content, strlen(content))); - - /* Wow... it worked! */ - cl_assert(git_index_entrycount(index) == 1); - returned_entry = git_index_get_byindex(index, 0); - - /* And the built-in hashing mechanism worked as expected */ - cl_assert_equal_oid(&id1, &returned_entry->id); - /* And mode is the one asked */ - cl_assert_equal_i(GIT_FILEMODE_BLOB, returned_entry->mode); - - /* Test access by path instead of index */ - cl_assert((returned_entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); - cl_assert_equal_oid(&id1, &returned_entry->id); - cl_assert_equal_i(0, returned_entry->dev); - cl_assert_equal_i(0, returned_entry->ino); - cl_assert_equal_i(0, returned_entry->uid); - cl_assert_equal_i(0, returned_entry->uid); - cl_assert_equal_i(10, returned_entry->file_size); - - /* Test the blob is in the repository */ - cl_git_pass(git_blob_lookup(&blob, repo, &id1)); - cl_assert_equal_s(content, git_blob_rawcontent(blob)); - git_blob_free(blob); - - git_index_free(index); - git_repository_free(repo); -} - -static void cleanup_1397(void *opaque) -{ - GIT_UNUSED(opaque); - cl_git_sandbox_cleanup(); -} - -void test_index_tests__add_issue_1397(void) -{ - git_index *index; - git_repository *repo; - const git_index_entry *entry; - git_oid id1; - - cl_set_cleanup(&cleanup_1397, NULL); - - repo = cl_git_sandbox_init("issue_1397"); - - cl_repo_set_bool(repo, "core.autocrlf", true); - - /* Ensure we're the only guy in the room */ - cl_git_pass(git_repository_index(&index, repo)); - - /* Store the expected hash of the file/blob - * This has been generated by executing the following - * $ git hash-object crlf_file.txt - */ - cl_git_pass(git_oid_fromstr(&id1, "8312e0889a9cbab77c732b6bc39b51a683e3a318")); - - /* Make sure the initial SHA-1 is correct */ - cl_assert((entry = git_index_get_bypath(index, "crlf_file.txt", 0)) != NULL); - cl_assert_equal_oid(&id1, &entry->id); - - /* Update the index */ - cl_git_pass(git_index_add_bypath(index, "crlf_file.txt")); - - /* Check the new SHA-1 */ - cl_assert((entry = git_index_get_bypath(index, "crlf_file.txt", 0)) != NULL); - cl_assert_equal_oid(&id1, &entry->id); - - git_index_free(index); -} - -void test_index_tests__add_bypath_to_a_bare_repository_returns_EBAREPO(void) -{ - git_repository *bare_repo; - git_index *index; - - cl_git_pass(git_repository_open(&bare_repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_index(&index, bare_repo)); - - cl_assert_equal_i(GIT_EBAREREPO, git_index_add_bypath(index, "test.txt")); - - git_index_free(index); - git_repository_free(bare_repo); -} - -static void assert_add_bypath_fails(git_repository *repo, const char *fn) -{ - git_index *index; - git_str path = GIT_STR_INIT; - - cl_git_pass(git_repository_index(&index, repo)); - cl_assert(git_index_entrycount(index) == 0); - - git_str_joinpath(&path, "./invalid", fn); - - cl_git_mkfile(path.ptr, NULL); - cl_git_fail(git_index_add_bypath(index, fn)); - cl_must_pass(p_unlink(path.ptr)); - - cl_assert(git_index_entrycount(index) == 0); - - git_str_dispose(&path); - git_index_free(index); -} - -/* Test that writing an invalid filename fails */ -void test_index_tests__cannot_add_invalid_filename(void) -{ - git_repository *repo; - - cl_must_pass(p_mkdir("invalid", 0700)); - cl_git_pass(git_repository_init(&repo, "./invalid", 0)); - cl_must_pass(p_mkdir("./invalid/subdir", 0777)); - - /* cl_git_mkfile() needs the dir to exist */ - if (!git_fs_path_exists("./invalid/.GIT")) - cl_must_pass(p_mkdir("./invalid/.GIT", 0777)); - if (!git_fs_path_exists("./invalid/.GiT")) - cl_must_pass(p_mkdir("./invalid/.GiT", 0777)); - - assert_add_bypath_fails(repo, ".git/hello"); - assert_add_bypath_fails(repo, ".GIT/hello"); - assert_add_bypath_fails(repo, ".GiT/hello"); - assert_add_bypath_fails(repo, "./.git/hello"); - assert_add_bypath_fails(repo, "./foo"); - assert_add_bypath_fails(repo, "./bar"); - assert_add_bypath_fails(repo, "subdir/../bar"); - - git_repository_free(repo); - - cl_fixture_cleanup("invalid"); -} - -static void assert_add_fails(git_repository *repo, const char *fn) -{ - git_index *index; - git_str path = GIT_STR_INIT; - git_index_entry entry = {{0}}; - - cl_git_pass(git_repository_index(&index, repo)); - cl_assert(git_index_entrycount(index) == 0); - - entry.path = fn; - entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_oid_fromstr(&entry.id, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")); - - cl_git_fail(git_index_add(index, &entry)); - - cl_assert(git_index_entrycount(index) == 0); - - git_str_dispose(&path); - git_index_free(index); -} - -/* - * Test that writing an invalid filename fails on filesystem - * specific protected names - */ -void test_index_tests__cannot_add_protected_invalid_filename(void) -{ - git_repository *repo; - git_index *index; - - cl_must_pass(p_mkdir("invalid", 0700)); - - cl_git_pass(git_repository_init(&repo, "./invalid", 0)); - - /* add a file to the repository so we can reference it later */ - cl_git_pass(git_repository_index(&index, repo)); - cl_git_mkfile("invalid/dummy.txt", ""); - cl_git_pass(git_index_add_bypath(index, "dummy.txt")); - cl_must_pass(p_unlink("invalid/dummy.txt")); - cl_git_pass(git_index_remove_bypath(index, "dummy.txt")); - git_index_free(index); - - cl_repo_set_bool(repo, "core.protectHFS", true); - cl_repo_set_bool(repo, "core.protectNTFS", true); - - assert_add_fails(repo, ".git./hello"); - assert_add_fails(repo, ".git\xe2\x80\xad/hello"); - assert_add_fails(repo, "git~1/hello"); - assert_add_fails(repo, ".git\xe2\x81\xaf/hello"); - assert_add_fails(repo, ".git::$INDEX_ALLOCATION/dummy-file"); - - git_repository_free(repo); - - cl_fixture_cleanup("invalid"); -} - -static void replace_char(char *str, char in, char out) -{ - char *c = str; - - while (*c++) - if (*c == in) - *c = out; -} - -static void assert_write_fails(git_repository *repo, const char *fn_orig) -{ - git_index *index; - git_oid expected; - const git_index_entry *entry; - git_str path = GIT_STR_INIT; - char *fn; - - cl_git_pass(git_repository_index(&index, repo)); - cl_assert(git_index_entrycount(index) == 0); - - /* - * Sneak a valid path into the index, we'll update it - * to an invalid path when we try to write the index. - */ - fn = git__strdup(fn_orig); - replace_char(fn, '/', '_'); - replace_char(fn, ':', '!'); - - git_str_joinpath(&path, "./invalid", fn); - - cl_git_mkfile(path.ptr, NULL); - - cl_git_pass(git_index_add_bypath(index, fn)); - - cl_assert(entry = git_index_get_bypath(index, fn, 0)); - - /* kids, don't try this at home */ - replace_char((char *)entry->path, '_', '/'); - replace_char((char *)entry->path, '!', ':'); - - /* write-tree */ - cl_git_fail(git_index_write_tree(&expected, index)); - - p_unlink(path.ptr); - - cl_git_pass(git_index_remove_all(index, NULL, NULL, NULL)); - git_str_dispose(&path); - git_index_free(index); - git__free(fn); -} - -void test_index_tests__write_tree_invalid_unowned_index(void) -{ - git_index *idx; - git_repository *repo; - git_index_entry entry = {{0}}; - git_oid tree_id; - - cl_git_pass(git_index_new(&idx)); - - cl_git_pass(git_oid_fromstr(&entry.id, "8312e0a89a9cbab77c732b6bc39b51a783e3a318")); - entry.path = "foo"; - entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_index_add(idx, &entry)); - - cl_git_pass(git_repository_init(&repo, "./invalid-id", 0)); - - cl_git_fail(git_index_write_tree_to(&tree_id, idx, repo)); - - git_index_free(idx); - git_repository_free(repo); - - cl_fixture_cleanup("invalid-id"); -} - -/* Test that writing an invalid filename fails */ -void test_index_tests__write_invalid_filename(void) -{ - git_repository *repo; - - p_mkdir("invalid", 0700); - - cl_git_pass(git_repository_init(&repo, "./invalid", 0)); - - assert_write_fails(repo, ".git/hello"); - assert_write_fails(repo, ".GIT/hello"); - assert_write_fails(repo, ".GiT/hello"); - assert_write_fails(repo, "./.git/hello"); - assert_write_fails(repo, "./foo"); - assert_write_fails(repo, "./bar"); - assert_write_fails(repo, "foo/../bar"); - - git_repository_free(repo); - - cl_fixture_cleanup("invalid"); -} - -void test_index_tests__honors_protect_filesystems(void) -{ - git_repository *repo; - - p_mkdir("invalid", 0700); - - cl_git_pass(git_repository_init(&repo, "./invalid", 0)); - - cl_repo_set_bool(repo, "core.protectHFS", true); - cl_repo_set_bool(repo, "core.protectNTFS", true); - - assert_write_fails(repo, ".git./hello"); - assert_write_fails(repo, ".git\xe2\x80\xad/hello"); - assert_write_fails(repo, "git~1/hello"); - assert_write_fails(repo, ".git\xe2\x81\xaf/hello"); - assert_write_fails(repo, ".git::$INDEX_ALLOCATION/dummy-file"); - - git_repository_free(repo); - - cl_fixture_cleanup("invalid"); -} - -void test_index_tests__protectntfs_on_by_default(void) -{ - git_repository *repo; - - p_mkdir("invalid", 0700); - - cl_git_pass(git_repository_init(&repo, "./invalid", 0)); - assert_write_fails(repo, ".git./hello"); - assert_write_fails(repo, "git~1/hello"); - - git_repository_free(repo); - - cl_fixture_cleanup("invalid"); -} - -void test_index_tests__can_disable_protectntfs(void) -{ - git_repository *repo; - git_index *index; - - cl_must_pass(p_mkdir("valid", 0700)); - cl_git_rewritefile("valid/git~1", "steal the shortname"); - - cl_git_pass(git_repository_init(&repo, "./valid", 0)); - cl_git_pass(git_repository_index(&index, repo)); - cl_repo_set_bool(repo, "core.protectNTFS", false); - - cl_git_pass(git_index_add_bypath(index, "git~1")); - - git_index_free(index); - git_repository_free(repo); - - cl_fixture_cleanup("valid"); -} - -void test_index_tests__remove_entry(void) -{ - git_repository *repo; - git_index *index; - - p_mkdir("index_test", 0770); - - cl_git_pass(git_repository_init(&repo, "index_test", 0)); - cl_git_pass(git_repository_index(&index, repo)); - cl_assert(git_index_entrycount(index) == 0); - - cl_git_mkfile("index_test/hello", NULL); - cl_git_pass(git_index_add_bypath(index, "hello")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_index_read(index, true)); /* reload */ - cl_assert(git_index_entrycount(index) == 1); - cl_assert(git_index_get_bypath(index, "hello", 0) != NULL); - - cl_git_pass(git_index_remove(index, "hello", 0)); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_index_read(index, true)); /* reload */ - cl_assert(git_index_entrycount(index) == 0); - cl_assert(git_index_get_bypath(index, "hello", 0) == NULL); - - git_index_free(index); - git_repository_free(repo); - cl_fixture_cleanup("index_test"); -} - -void test_index_tests__remove_directory(void) -{ - git_repository *repo; - git_index *index; - - p_mkdir("index_test", 0770); - - cl_git_pass(git_repository_init(&repo, "index_test", 0)); - cl_git_pass(git_repository_index(&index, repo)); - cl_assert_equal_i(0, (int)git_index_entrycount(index)); - - p_mkdir("index_test/a", 0770); - cl_git_mkfile("index_test/a/1.txt", NULL); - cl_git_mkfile("index_test/a/2.txt", NULL); - cl_git_mkfile("index_test/a/3.txt", NULL); - cl_git_mkfile("index_test/b.txt", NULL); - - cl_git_pass(git_index_add_bypath(index, "a/1.txt")); - cl_git_pass(git_index_add_bypath(index, "a/2.txt")); - cl_git_pass(git_index_add_bypath(index, "a/3.txt")); - cl_git_pass(git_index_add_bypath(index, "b.txt")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_index_read(index, true)); /* reload */ - cl_assert_equal_i(4, (int)git_index_entrycount(index)); - cl_assert(git_index_get_bypath(index, "a/1.txt", 0) != NULL); - cl_assert(git_index_get_bypath(index, "a/2.txt", 0) != NULL); - cl_assert(git_index_get_bypath(index, "b.txt", 0) != NULL); - - cl_git_pass(git_index_remove(index, "a/1.txt", 0)); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_index_read(index, true)); /* reload */ - cl_assert_equal_i(3, (int)git_index_entrycount(index)); - cl_assert(git_index_get_bypath(index, "a/1.txt", 0) == NULL); - cl_assert(git_index_get_bypath(index, "a/2.txt", 0) != NULL); - cl_assert(git_index_get_bypath(index, "b.txt", 0) != NULL); - - cl_git_pass(git_index_remove_directory(index, "a", 0)); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_index_read(index, true)); /* reload */ - cl_assert_equal_i(1, (int)git_index_entrycount(index)); - cl_assert(git_index_get_bypath(index, "a/1.txt", 0) == NULL); - cl_assert(git_index_get_bypath(index, "a/2.txt", 0) == NULL); - cl_assert(git_index_get_bypath(index, "b.txt", 0) != NULL); - - git_index_free(index); - git_repository_free(repo); - cl_fixture_cleanup("index_test"); -} - -void test_index_tests__preserves_case(void) -{ - git_repository *repo; - git_index *index; - const git_index_entry *entry; - int index_caps; - - cl_set_cleanup(&cleanup_myrepo, NULL); - - cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); - cl_git_pass(git_repository_index(&index, repo)); - - index_caps = git_index_caps(index); - - cl_git_rewritefile("myrepo/test.txt", "hey there\n"); - cl_git_pass(git_index_add_bypath(index, "test.txt")); - - cl_git_pass(p_rename("myrepo/test.txt", "myrepo/TEST.txt")); - cl_git_rewritefile("myrepo/TEST.txt", "hello again\n"); - cl_git_pass(git_index_add_bypath(index, "TEST.txt")); - - if (index_caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) - cl_assert_equal_i(1, (int)git_index_entrycount(index)); - else - cl_assert_equal_i(2, (int)git_index_entrycount(index)); - - /* Test access by path instead of index */ - cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); - /* The path should *not* have changed without an explicit remove */ - cl_assert(git__strcmp(entry->path, "test.txt") == 0); - - cl_assert((entry = git_index_get_bypath(index, "TEST.txt", 0)) != NULL); - if (index_caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) - /* The path should *not* have changed without an explicit remove */ - cl_assert(git__strcmp(entry->path, "test.txt") == 0); - else - cl_assert(git__strcmp(entry->path, "TEST.txt") == 0); - - git_index_free(index); - git_repository_free(repo); -} - -void test_index_tests__elocked(void) -{ - git_repository *repo; - git_index *index; - git_filebuf file = GIT_FILEBUF_INIT; - const git_error *err; - int error; - - cl_set_cleanup(&cleanup_myrepo, NULL); - - cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); - cl_git_pass(git_repository_index(&index, repo)); - - /* Lock the index file so we fail to lock it */ - cl_git_pass(git_filebuf_open(&file, index->index_file_path, 0, 0666)); - error = git_index_write(index); - cl_assert_equal_i(GIT_ELOCKED, error); - - err = git_error_last(); - cl_assert_equal_i(err->klass, GIT_ERROR_INDEX); - - git_filebuf_cleanup(&file); - git_index_free(index); - git_repository_free(repo); -} - -void test_index_tests__reload_from_disk(void) -{ - git_repository *repo; - git_index *read_index; - git_index *write_index; - - cl_set_cleanup(&cleanup_myrepo, NULL); - - cl_git_pass(git_futils_mkdir("./myrepo", 0777, GIT_MKDIR_PATH)); - cl_git_mkfile("./myrepo/a.txt", "a\n"); - cl_git_mkfile("./myrepo/b.txt", "b\n"); - - cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); - cl_git_pass(git_repository_index(&write_index, repo)); - cl_assert_equal_i(false, write_index->on_disk); - - cl_git_pass(git_index_open(&read_index, write_index->index_file_path)); - cl_assert_equal_i(false, read_index->on_disk); - - /* Stage two new files against the write_index */ - cl_git_pass(git_index_add_bypath(write_index, "a.txt")); - cl_git_pass(git_index_add_bypath(write_index, "b.txt")); - - cl_assert_equal_sz(2, git_index_entrycount(write_index)); - - /* Persist the index changes to disk */ - cl_git_pass(git_index_write(write_index)); - cl_assert_equal_i(true, write_index->on_disk); - - /* Sync the changes back into the read_index */ - cl_assert_equal_sz(0, git_index_entrycount(read_index)); - - cl_git_pass(git_index_read(read_index, true)); - cl_assert_equal_i(true, read_index->on_disk); - - cl_assert_equal_sz(2, git_index_entrycount(read_index)); - - /* Remove the index file from the filesystem */ - cl_git_pass(p_unlink(write_index->index_file_path)); - - /* Sync the changes back into the read_index */ - cl_git_pass(git_index_read(read_index, true)); - cl_assert_equal_i(false, read_index->on_disk); - cl_assert_equal_sz(0, git_index_entrycount(read_index)); - - git_index_free(read_index); - git_index_free(write_index); - git_repository_free(repo); -} - -void test_index_tests__corrupted_extension(void) -{ - git_index *index; - - cl_git_fail_with(git_index_open(&index, TEST_INDEXBAD_PATH), GIT_ERROR); -} - -void test_index_tests__reload_while_ignoring_case(void) -{ - git_index *index; - unsigned int caps; - - cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); - cl_git_pass(git_vector_verify_sorted(&index->entries)); - - caps = git_index_caps(index); - cl_git_pass(git_index_set_caps(index, caps &= ~GIT_INDEX_CAPABILITY_IGNORE_CASE)); - cl_git_pass(git_index_read(index, true)); - cl_git_pass(git_vector_verify_sorted(&index->entries)); - cl_assert(git_index_get_bypath(index, ".HEADER", 0)); - cl_assert_equal_p(NULL, git_index_get_bypath(index, ".header", 0)); - - cl_git_pass(git_index_set_caps(index, caps | GIT_INDEX_CAPABILITY_IGNORE_CASE)); - cl_git_pass(git_index_read(index, true)); - cl_git_pass(git_vector_verify_sorted(&index->entries)); - cl_assert(git_index_get_bypath(index, ".HEADER", 0)); - cl_assert(git_index_get_bypath(index, ".header", 0)); - - git_index_free(index); -} - -void test_index_tests__change_icase_on_instance(void) -{ - git_index *index; - unsigned int caps; - const git_index_entry *e; - - cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); - cl_git_pass(git_vector_verify_sorted(&index->entries)); - - caps = git_index_caps(index); - cl_git_pass(git_index_set_caps(index, caps &= ~GIT_INDEX_CAPABILITY_IGNORE_CASE)); - cl_assert_equal_i(false, index->ignore_case); - cl_git_pass(git_vector_verify_sorted(&index->entries)); - cl_assert(e = git_index_get_bypath(index, "src/common.h", 0)); - cl_assert_equal_p(NULL, e = git_index_get_bypath(index, "SRC/Common.h", 0)); - cl_assert(e = git_index_get_bypath(index, "COPYING", 0)); - cl_assert_equal_p(NULL, e = git_index_get_bypath(index, "copying", 0)); - - cl_git_pass(git_index_set_caps(index, caps | GIT_INDEX_CAPABILITY_IGNORE_CASE)); - cl_assert_equal_i(true, index->ignore_case); - cl_git_pass(git_vector_verify_sorted(&index->entries)); - cl_assert(e = git_index_get_bypath(index, "COPYING", 0)); - cl_assert_equal_s("COPYING", e->path); - cl_assert(e = git_index_get_bypath(index, "copying", 0)); - cl_assert_equal_s("COPYING", e->path); - - git_index_free(index); -} - -void test_index_tests__can_lock_index(void) -{ - git_repository *repo; - git_index *index; - git_indexwriter one = GIT_INDEXWRITER_INIT, - two = GIT_INDEXWRITER_INIT; - - repo = cl_git_sandbox_init("testrepo.git"); - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_indexwriter_init(&one, index)); - - cl_git_fail_with(GIT_ELOCKED, git_indexwriter_init(&two, index)); - cl_git_fail_with(GIT_ELOCKED, git_index_write(index)); - - cl_git_pass(git_indexwriter_commit(&one)); - - cl_git_pass(git_index_write(index)); - - git_indexwriter_cleanup(&one); - git_indexwriter_cleanup(&two); - git_index_free(index); - cl_git_sandbox_cleanup(); -} - -void test_index_tests__can_iterate(void) -{ - git_index *index; - git_index_iterator *iterator; - const git_index_entry *entry; - size_t i, iterator_idx = 0, found = 0; - int ret; - - cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); - cl_git_pass(git_index_iterator_new(&iterator, index)); - - cl_assert(git_vector_is_sorted(&iterator->snap)); - - for (i = 0; i < ARRAY_SIZE(test_entries); i++) { - /* Advance iterator to next test entry index */ - do { - ret = git_index_iterator_next(&entry, iterator); - - if (ret == GIT_ITEROVER) - cl_fail("iterator did not contain all test entries"); - - cl_git_pass(ret); - } while (iterator_idx++ < test_entries[i].index); - - cl_assert_equal_s(entry->path, test_entries[i].path); - cl_assert_equal_i(entry->mtime.seconds, test_entries[i].mtime); - cl_assert_equal_i(entry->file_size, test_entries[i].file_size); - found++; - } - - while ((ret = git_index_iterator_next(&entry, iterator)) == 0) - ; - - if (ret != GIT_ITEROVER) - cl_git_fail(ret); - - cl_assert_equal_i(found, ARRAY_SIZE(test_entries)); - - git_index_iterator_free(iterator); - git_index_free(index); -} - -void test_index_tests__can_modify_while_iterating(void) -{ - git_index *index; - git_index_iterator *iterator; - const git_index_entry *entry; - git_index_entry new_entry = {{0}}; - size_t expected = 0, seen = 0; - int ret; - - cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); - cl_git_pass(git_index_iterator_new(&iterator, index)); - - expected = git_index_entrycount(index); - cl_assert(git_vector_is_sorted(&iterator->snap)); - - /* - * After we've counted the entries, add a new one and change another; - * ensure that our iterator is backed by a snapshot and thus returns - * the number of entries from when the iterator was created. - */ - cl_git_pass(git_oid_fromstr(&new_entry.id, "8312e0a89a9cbab77c732b6bc39b51a783e3a318")); - new_entry.path = "newfile"; - new_entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_index_add(index, &new_entry)); - - cl_git_pass(git_oid_fromstr(&new_entry.id, "4141414141414141414141414141414141414141")); - new_entry.path = "Makefile"; - new_entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_index_add(index, &new_entry)); - - while (true) { - ret = git_index_iterator_next(&entry, iterator); - - if (ret == GIT_ITEROVER) - break; - - seen++; - } - - cl_assert_equal_i(expected, seen); - - git_index_iterator_free(iterator); - git_index_free(index); -} diff --git a/tests/index/version.c b/tests/index/version.c deleted file mode 100644 index b6c0b7918..000000000 --- a/tests/index/version.c +++ /dev/null @@ -1,140 +0,0 @@ -#include "clar_libgit2.h" -#include "index.h" - -static git_repository *g_repo = NULL; - -void test_index_version__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -void test_index_version__can_read_v4(void) -{ - const char *paths[] = { - "file.tx", "file.txt", "file.txz", "foo", "zzz", - }; - git_index *index; - size_t i; - - g_repo = cl_git_sandbox_init("indexv4"); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_assert_equal_sz(git_index_entrycount(index), 5); - - for (i = 0; i < ARRAY_SIZE(paths); i++) { - const git_index_entry *entry = - git_index_get_bypath(index, paths[i], GIT_INDEX_STAGE_NORMAL); - - cl_assert(entry != NULL); - } - - git_index_free(index); -} - -void test_index_version__can_write_v4(void) -{ - const char *paths[] = { - "foo", - "foox", - "foobar", - "foobal", - "x", - "xz", - "xyzzyx" - }; - git_repository *repo; - git_index_entry entry; - git_index *index; - size_t i; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_set_version(index, 4)); - - for (i = 0; i < ARRAY_SIZE(paths); i++) { - memset(&entry, 0, sizeof(entry)); - entry.path = paths[i]; - entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_index_add_from_buffer(index, &entry, paths[i], - strlen(paths[i]) + 1)); - } - cl_assert_equal_sz(git_index_entrycount(index), ARRAY_SIZE(paths)); - - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_repository_open(&repo, git_repository_path(g_repo))); - cl_git_pass(git_repository_index(&index, repo)); - cl_assert(git_index_version(index) == 4); - - for (i = 0; i < ARRAY_SIZE(paths); i++) { - const git_index_entry *e; - - cl_assert(e = git_index_get_bypath(index, paths[i], 0)); - cl_assert_equal_s(paths[i], e->path); - } - - git_index_free(index); - git_repository_free(repo); -} - -void test_index_version__v4_uses_path_compression(void) -{ - git_index_entry entry; - git_index *index; - char path[250], buf[1]; - struct stat st; - char i, j; - - memset(path, 'a', sizeof(path)); - memset(buf, 'a', sizeof(buf)); - - memset(&entry, 0, sizeof(entry)); - entry.path = path; - entry.mode = GIT_FILEMODE_BLOB; - - g_repo = cl_git_sandbox_init("indexv4"); - cl_git_pass(git_repository_index(&index, g_repo)); - - /* write 676 paths of 250 bytes length */ - for (i = 'a'; i <= 'z'; i++) { - for (j = 'a'; j < 'z'; j++) { - path[ARRAY_SIZE(path) - 3] = i; - path[ARRAY_SIZE(path) - 2] = j; - path[ARRAY_SIZE(path) - 1] = '\0'; - cl_git_pass(git_index_add_from_buffer(index, &entry, buf, sizeof(buf))); - } - } - - cl_git_pass(git_index_write(index)); - cl_git_pass(p_stat(git_index_path(index), &st)); - - /* - * Without path compression, the written paths would at - * least take - * - * (entries * pathlen) = len - * (676 * 250) = 169000 - * - * bytes. As index v4 uses suffix-compression and our - * written paths only differ in the last two entries, - * this number will be much smaller, e.g. - * - * (1 * pathlen) + (675 * 2) = len - * 676 + 1350 = 2026 - * - * bytes. - * - * Note that the above calculations do not include - * additional metadata of the index, e.g. OIDs or - * index extensions. Including those we get an index - * of approx. 200kB without compression and 40kB with - * compression. As this is a lot smaller than without - * compression, we can verify that path compression is - * used. - */ - cl_assert_(st.st_size < 75000, "path compression not enabled"); - - git_index_free(index); -} diff --git a/tests/iterator/index.c b/tests/iterator/index.c deleted file mode 100644 index 7218b4f75..000000000 --- a/tests/iterator/index.c +++ /dev/null @@ -1,1385 +0,0 @@ -#include "clar_libgit2.h" -#include "iterator.h" -#include "repository.h" -#include "futils.h" -#include "iterator_helpers.h" -#include "../submodule/submodule_helpers.h" -#include - -static git_repository *g_repo; - -void test_iterator_index__initialize(void) -{ -} - -void test_iterator_index__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -static void index_iterator_test( - const char *sandbox, - const char *start, - const char *end, - git_iterator_flag_t flags, - int expected_count, - const char **expected_names, - const char **expected_oids) -{ - git_index *index; - git_iterator *i; - const git_index_entry *entry; - int error, count = 0, caps; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init(sandbox); - - cl_git_pass(git_repository_index(&index, g_repo)); - caps = git_index_caps(index); - - iter_opts.flags = flags; - iter_opts.start = start; - iter_opts.end = end; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &iter_opts)); - - while (!(error = git_iterator_advance(&entry, i))) { - cl_assert(entry); - - if (expected_names != NULL) - cl_assert_equal_s(expected_names[count], entry->path); - - if (expected_oids != NULL) { - git_oid oid; - cl_git_pass(git_oid_fromstr(&oid, expected_oids[count])); - cl_assert_equal_oid(&oid, &entry->id); - } - - count++; - } - - cl_assert_equal_i(GIT_ITEROVER, error); - cl_assert(!entry); - cl_assert_equal_i(expected_count, count); - - git_iterator_free(i); - - cl_assert(caps == git_index_caps(index)); - git_index_free(index); -} - -static const char *expected_index_0[] = { - "attr0", - "attr1", - "attr2", - "attr3", - "binfile", - "gitattributes", - "macro_bad", - "macro_test", - "root_test1", - "root_test2", - "root_test3", - "root_test4.txt", - "sub/abc", - "sub/file", - "sub/sub/file", - "sub/sub/subsub.txt", - "sub/subdir_test1", - "sub/subdir_test2.txt", - "subdir/.gitattributes", - "subdir/abc", - "subdir/subdir_test1", - "subdir/subdir_test2.txt", - "subdir2/subdir2_test1", -}; - -static const char *expected_index_oids_0[] = { - "556f8c827b8e4a02ad5cab77dca2bcb3e226b0b3", - "3b74db7ab381105dc0d28f8295a77f6a82989292", - "2c66e14f77196ea763fb1e41612c1aa2bc2d8ed2", - "c485abe35abd4aa6fd83b076a78bbea9e2e7e06c", - "d800886d9c86731ae5c4a62b0b77c437015e00d2", - "2b40c5aca159b04ea8d20ffe36cdf8b09369b14a", - "5819a185d77b03325aaf87cafc771db36f6ddca7", - "ff69f8639ce2e6010b3f33a74160aad98b48da2b", - "45141a79a77842c59a63229403220a4e4be74e3d", - "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d", - "108bb4e7fd7b16490dc33ff7d972151e73d7166e", - "a0f7217ae99f5ac3e88534f5cea267febc5fa85b", - "3e42ffc54a663f9401cc25843d6c0e71a33e4249", - "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", - "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", - "9e5bdc47d6a80f2be0ea3049ad74231b94609242", - "e563cf4758f0d646f1b14b76016aa17fa9e549a4", - "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78", - "99eae476896f4907224978b88e5ecaa6c5bb67a9", - "3e42ffc54a663f9401cc25843d6c0e71a33e4249", - "e563cf4758f0d646f1b14b76016aa17fa9e549a4", - "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78", - "dccada462d3df8ac6de596fb8c896aba9344f941" -}; - -void test_iterator_index__0(void) -{ - index_iterator_test( - "attr", NULL, NULL, 0, ARRAY_SIZE(expected_index_0), - expected_index_0, expected_index_oids_0); -} - -static const char *expected_index_1[] = { - "current_file", - "file_deleted", - "modified_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_new_file", - "staged_new_file_deleted_file", - "staged_new_file_modified_file", - "subdir.txt", - "subdir/current_file", - "subdir/deleted_file", - "subdir/modified_file", -}; - -static const char* expected_index_oids_1[] = { - "a0de7e0ac200c489c41c59dfa910154a70264e6e", - "5452d32f1dd538eb0405e8a83cc185f79e25e80f", - "452e4244b5d083ddf0460acf1ecc74db9dcfa11a", - "55d316c9ba708999f1918e9677d01dfcae69c6b9", - "a6be623522ce87a1d862128ac42672604f7b468b", - "906ee7711f4f4928ddcb2a5f8fbc500deba0d2a8", - "529a16e8e762d4acb7b9636ff540a00831f9155a", - "90b8c29d8ba39434d1c63e1b093daaa26e5bd972", - "ed062903b8f6f3dccb2fa81117ba6590944ef9bd", - "e8ee89e15bbe9b20137715232387b3de5b28972e", - "53ace0d1cc1145a5f4fe4f78a186a60263190733", - "1888c805345ba265b0ee9449b8877b6064592058", - "a6191982709b746d5650e93c2acf34ef74e11504" -}; - -void test_iterator_index__1(void) -{ - index_iterator_test( - "status", NULL, NULL, 0, ARRAY_SIZE(expected_index_1), - expected_index_1, expected_index_oids_1); -} - -static const char *expected_index_range[] = { - "root_test1", - "root_test2", - "root_test3", - "root_test4.txt", -}; - -static const char *expected_index_oids_range[] = { - "45141a79a77842c59a63229403220a4e4be74e3d", - "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d", - "108bb4e7fd7b16490dc33ff7d972151e73d7166e", - "a0f7217ae99f5ac3e88534f5cea267febc5fa85b", -}; - -void test_iterator_index__range(void) -{ - index_iterator_test( - "attr", "root", "root", 0, ARRAY_SIZE(expected_index_range), - expected_index_range, expected_index_oids_range); -} - -void test_iterator_index__range_empty_0(void) -{ - index_iterator_test( - "attr", "empty", "empty", 0, 0, NULL, NULL); -} - -void test_iterator_index__range_empty_1(void) -{ - index_iterator_test( - "attr", "z_empty_after", NULL, 0, 0, NULL, NULL); -} - -void test_iterator_index__range_empty_2(void) -{ - index_iterator_test( - "attr", NULL, ".aaa_empty_before", 0, 0, NULL, NULL); -} - -static void check_index_range( - git_repository *repo, - const char *start, - const char *end, - bool ignore_case, - int expected_count) -{ - git_index *index; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - int error, count, caps; - bool is_ignoring_case; - - cl_git_pass(git_repository_index(&index, repo)); - - caps = git_index_caps(index); - is_ignoring_case = ((caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0); - - if (ignore_case != is_ignoring_case) - cl_git_pass(git_index_set_caps(index, caps ^ GIT_INDEX_CAPABILITY_IGNORE_CASE)); - - i_opts.flags = 0; - i_opts.start = start; - i_opts.end = end; - - cl_git_pass(git_iterator_for_index(&i, repo, index, &i_opts)); - - cl_assert(git_iterator_ignore_case(i) == ignore_case); - - for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count) - /* count em up */; - - cl_assert_equal_i(GIT_ITEROVER, error); - cl_assert_equal_i(expected_count, count); - - git_iterator_free(i); - git_index_free(index); -} - -void test_iterator_index__range_icase(void) -{ - git_index *index; - git_tree *head; - - g_repo = cl_git_sandbox_init("testrepo"); - - /* reset index to match HEAD */ - cl_git_pass(git_repository_head_tree(&head, g_repo)); - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_read_tree(index, head)); - cl_git_pass(git_index_write(index)); - git_tree_free(head); - git_index_free(index); - - /* do some ranged iterator checks toggling case sensitivity */ - check_index_range(g_repo, "B", "C", false, 0); - check_index_range(g_repo, "B", "C", true, 1); - check_index_range(g_repo, "a", "z", false, 3); - check_index_range(g_repo, "a", "z", true, 4); -} - -static const char *expected_index_cs[] = { - "B", "D", "F", "H", "J", "L/1", "L/B", "L/D", "L/a", "L/c", - "a", "c", "e", "g", "i", "k/1", "k/B", "k/D", "k/a", "k/c", -}; - -static const char *expected_index_ci[] = { - "a", "B", "c", "D", "e", "F", "g", "H", "i", "J", - "k/1", "k/a", "k/B", "k/c", "k/D", "L/1", "L/a", "L/B", "L/c", "L/D", -}; - -void test_iterator_index__case_folding(void) -{ - git_str path = GIT_STR_INIT; - int fs_is_ci = 0; - - cl_git_pass(git_str_joinpath(&path, cl_fixture("icase"), ".gitted/CoNfIg")); - fs_is_ci = git_fs_path_exists(path.ptr); - git_str_dispose(&path); - - index_iterator_test( - "icase", NULL, NULL, 0, ARRAY_SIZE(expected_index_cs), - fs_is_ci ? expected_index_ci : expected_index_cs, NULL); - - cl_git_sandbox_cleanup(); - - index_iterator_test( - "icase", NULL, NULL, GIT_ITERATOR_IGNORE_CASE, - ARRAY_SIZE(expected_index_ci), expected_index_ci, NULL); - - cl_git_sandbox_cleanup(); - - index_iterator_test( - "icase", NULL, NULL, GIT_ITERATOR_DONT_IGNORE_CASE, - ARRAY_SIZE(expected_index_cs), expected_index_cs, NULL); -} - -/* Index contents (including pseudotrees): - * - * 0: a 5: F 10: k/ 16: L/ - * 1: B 6: g 11: k/1 17: L/1 - * 2: c 7: H 12: k/a 18: L/a - * 3: D 8: i 13: k/B 19: L/B - * 4: e 9: J 14: k/c 20: L/c - * 15: k/D 21: L/D - * - * 0: B 5: L/ 11: a 16: k/ - * 1: D 6: L/1 12: c 17: k/1 - * 2: F 7: L/B 13: e 18: k/B - * 3: H 8: L/D 14: g 19: k/D - * 4: J 9: L/a 15: i 20: k/a - * 10: L/c 21: k/c - */ - -void test_iterator_index__icase_0(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - /* autoexpand with no tree entries for index */ - cl_git_pass(git_iterator_for_index(&i, g_repo, index, NULL)); - expect_iterator_items(i, 20, NULL, 20, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 22, NULL, 22, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 12, NULL, 22, NULL); - git_iterator_free(i); - - git_index_free(index); -} - -void test_iterator_index__icase_1(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - int caps; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - caps = git_index_caps(index); - - /* force case sensitivity */ - cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEX_CAPABILITY_IGNORE_CASE)); - - /* autoexpand with no tree entries over range */ - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 7, NULL, 7, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 3, NULL, 3, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 8, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 4, NULL, 4, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 5, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 1, NULL, 4, NULL); - git_iterator_free(i); - - /* force case insensitivity */ - cl_git_pass(git_index_set_caps(index, caps | GIT_INDEX_CAPABILITY_IGNORE_CASE)); - - /* autoexpand with no tree entries over range */ - i_opts.flags = 0; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 13, NULL, 13, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 5, NULL, 5, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 14, NULL, 14, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 6, NULL, 6, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 9, NULL, 14, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 1, NULL, 6, NULL); - git_iterator_free(i); - - cl_git_pass(git_index_set_caps(index, caps)); - git_index_free(index); -} - -void test_iterator_index__pathlist(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - git_vector filelist; - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "a")); - cl_git_pass(git_vector_insert(&filelist, "B")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k/1")); - cl_git_pass(git_vector_insert(&filelist, "k/a")); - cl_git_pass(git_vector_insert(&filelist, "L/1")); - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - /* Case sensitive */ - { - const char *expected[] = { - "B", "D", "L/1", "a", "c", "e", "k/1", "k/a" }; - size_t expected_len = 8; - - i_opts.start = NULL; - i_opts.end = NULL; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Case INsensitive */ - { - const char *expected[] = { - "a", "B", "c", "D", "e", "k/1", "k/a", "L/1" }; - size_t expected_len = 8; - - i_opts.start = NULL; - i_opts.end = NULL; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Set a start, but no end. Case sensitive. */ - { - const char *expected[] = { "c", "e", "k/1", "k/a" }; - size_t expected_len = 4; - - i_opts.start = "c"; - i_opts.end = NULL; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Set a start, but no end. Case INsensitive. */ - { - const char *expected[] = { "c", "D", "e", "k/1", "k/a", "L/1" }; - size_t expected_len = 6; - - i_opts.start = "c"; - i_opts.end = NULL; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Set no start, but an end. Case sensitive. */ - { - const char *expected[] = { "B", "D", "L/1", "a", "c", "e" }; - size_t expected_len = 6; - - i_opts.start = NULL; - i_opts.end = "e"; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Set no start, but an end. Case INsensitive. */ - { - const char *expected[] = { "a", "B", "c", "D", "e" }; - size_t expected_len = 5; - - i_opts.start = NULL; - i_opts.end = "e"; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Start and an end, case sensitive */ - { - const char *expected[] = { "c", "e", "k/1" }; - size_t expected_len = 3; - - i_opts.start = "c"; - i_opts.end = "k/D"; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Start and an end, case sensitive */ - { - const char *expected[] = { "k/1" }; - size_t expected_len = 1; - - i_opts.start = "k"; - i_opts.end = "k/D"; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Start and an end, case INsensitive */ - { - const char *expected[] = { "c", "D", "e", "k/1", "k/a" }; - size_t expected_len = 5; - - i_opts.start = "c"; - i_opts.end = "k/D"; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Start and an end, case INsensitive */ - { - const char *expected[] = { "k/1", "k/a" }; - size_t expected_len = 2; - - i_opts.start = "k"; - i_opts.end = "k/D"; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - git_index_free(index); - git_vector_free(&filelist); -} - -void test_iterator_index__pathlist_with_dirs(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - git_vector filelist; - - cl_git_pass(git_vector_init(&filelist, 5, NULL)); - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - /* Test that a prefix `k` matches folders, even without trailing slash */ - { - const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" }; - size_t expected_len = 5; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "k")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Test that a `k/` matches a folder */ - { - const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" }; - size_t expected_len = 5; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "k/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* When the iterator is case sensitive, ensure we can't lookup the - * directory with the wrong case. - */ - { - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "K/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); - git_iterator_free(i); - } - - /* Test that case insensitive matching works. */ - { - const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" }; - size_t expected_len = 5; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "K/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Test that case insensitive matching works without trailing slash. */ - { - const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" }; - size_t expected_len = 5; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "K")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - git_index_free(index); - git_vector_free(&filelist); -} - -void test_iterator_index__pathlist_with_dirs_include_trees(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - git_vector filelist; - - const char *expected[] = { "k/", "k/1", "k/B", "k/D", "k/a", "k/c" }; - size_t expected_len = 6; - - cl_git_pass(git_vector_init(&filelist, 5, NULL)); - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "k")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - - git_index_free(index); - git_vector_free(&filelist); -} - -void test_iterator_index__pathlist_1(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - git_vector filelist = GIT_VECTOR_INIT; - int default_icase, expect; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "0")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k/1")); - cl_git_pass(git_vector_insert(&filelist, "k/a")); - - /* In this test we DO NOT force a case setting on the index. */ - default_icase = ((git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - i_opts.start = "b"; - i_opts.end = "k/D"; - - /* (c D e k/1 k/a ==> 5) vs (c e k/1 ==> 3) */ - expect = default_icase ? 5 : 3; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - git_index_free(index); - git_vector_free(&filelist); -} - -void test_iterator_index__pathlist_2(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - git_vector filelist = GIT_VECTOR_INIT; - int default_icase, expect; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "0")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k/")); - cl_git_pass(git_vector_insert(&filelist, "k.a")); - cl_git_pass(git_vector_insert(&filelist, "k.b")); - cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); - - /* In this test we DO NOT force a case setting on the index. */ - default_icase = ((git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - i_opts.start = "b"; - i_opts.end = "k/D"; - - /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */ - expect = default_icase ? 8 : 5; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - git_index_free(index); - git_vector_free(&filelist); -} - -void test_iterator_index__pathlist_four(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - git_vector filelist = GIT_VECTOR_INIT; - int default_icase, expect; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "0")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k")); - cl_git_pass(git_vector_insert(&filelist, "k.a")); - cl_git_pass(git_vector_insert(&filelist, "k.b")); - cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); - - /* In this test we DO NOT force a case setting on the index. */ - default_icase = ((git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - i_opts.start = "b"; - i_opts.end = "k/D"; - - /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */ - expect = default_icase ? 8 : 5; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - git_index_free(index); - git_vector_free(&filelist); -} - -void test_iterator_index__pathlist_icase(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - int caps; - git_vector filelist; - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "a")); - cl_git_pass(git_vector_insert(&filelist, "B")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k/1")); - cl_git_pass(git_vector_insert(&filelist, "k/a")); - cl_git_pass(git_vector_insert(&filelist, "L/1")); - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - caps = git_index_caps(index); - - /* force case sensitivity */ - cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEX_CAPABILITY_IGNORE_CASE)); - - /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 3, NULL, 3, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 1, NULL, 1, NULL); - git_iterator_free(i); - - /* force case insensitivity */ - cl_git_pass(git_index_set_caps(index, caps | GIT_INDEX_CAPABILITY_IGNORE_CASE)); - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 5, NULL, 5, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 2, NULL, 2, NULL); - git_iterator_free(i); - - cl_git_pass(git_index_set_caps(index, caps)); - git_index_free(index); - git_vector_free(&filelist); -} - -void test_iterator_index__pathlist_with_directory(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - git_tree *tree; - git_index *index; - - g_repo = cl_git_sandbox_init("testrepo2"); - git_repository_head_tree(&tree, g_repo); - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "subdir")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 4, NULL, 4, NULL); - git_iterator_free(i); - - git_index_free(index); - git_tree_free(tree); - git_vector_free(&filelist); -} - -static void create_paths(git_index *index, const char *root, int depth) -{ - git_str fullpath = GIT_STR_INIT; - git_index_entry entry; - size_t root_len; - int i; - - if (root) { - cl_git_pass(git_str_puts(&fullpath, root)); - cl_git_pass(git_str_putc(&fullpath, '/')); - } - - root_len = fullpath.size; - - for (i = 0; i < 8; i++) { - bool file = (depth == 0 || (i % 2) == 0); - git_str_truncate(&fullpath, root_len); - cl_git_pass(git_str_printf(&fullpath, "item%d", i)); - - if (file) { - memset(&entry, 0, sizeof(git_index_entry)); - entry.path = fullpath.ptr; - entry.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&entry.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6"); - - cl_git_pass(git_index_add(index, &entry)); - } else if (depth > 0) { - create_paths(index, fullpath.ptr, (depth - 1)); - } - } - - git_str_dispose(&fullpath); -} - -void test_iterator_index__pathlist_for_deeply_nested_item(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - git_vector filelist; - - cl_git_pass(git_vector_init(&filelist, 5, NULL)); - - g_repo = cl_git_sandbox_init("icase"); - cl_git_pass(git_repository_index(&index, g_repo)); - - create_paths(index, NULL, 3); - - /* Ensure that we find the single path we're interested in */ - { - const char *expected[] = { "item1/item3/item5/item7" }; - size_t expected_len = 1; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/item7")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - { - const char *expected[] = { - "item1/item3/item5/item0", "item1/item3/item5/item1", - "item1/item3/item5/item2", "item1/item3/item5/item3", - "item1/item3/item5/item4", "item1/item3/item5/item5", - "item1/item3/item5/item6", "item1/item3/item5/item7", - }; - size_t expected_len = 8; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - { - const char *expected[] = { - "item1/item3/item0", - "item1/item3/item1/item0", "item1/item3/item1/item1", - "item1/item3/item1/item2", "item1/item3/item1/item3", - "item1/item3/item1/item4", "item1/item3/item1/item5", - "item1/item3/item1/item6", "item1/item3/item1/item7", - "item1/item3/item2", - "item1/item3/item3/item0", "item1/item3/item3/item1", - "item1/item3/item3/item2", "item1/item3/item3/item3", - "item1/item3/item3/item4", "item1/item3/item3/item5", - "item1/item3/item3/item6", "item1/item3/item3/item7", - "item1/item3/item4", - "item1/item3/item5/item0", "item1/item3/item5/item1", - "item1/item3/item5/item2", "item1/item3/item5/item3", - "item1/item3/item5/item4", "item1/item3/item5/item5", - "item1/item3/item5/item6", "item1/item3/item5/item7", - "item1/item3/item6", - "item1/item3/item7/item0", "item1/item3/item7/item1", - "item1/item3/item7/item2", "item1/item3/item7/item3", - "item1/item3/item7/item4", "item1/item3/item7/item5", - "item1/item3/item7/item6", "item1/item3/item7/item7", - }; - size_t expected_len = 36; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "item1/item3/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Ensure that we find the single path we're interested in, and we find - * it efficiently, and don't stat the entire world to get there. - */ - { - const char *expected[] = { - "item0", "item1/item2", "item5/item7/item4", "item6", - "item7/item3/item1/item6" }; - size_t expected_len = 5; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "item7/item3/item1/item6")); - cl_git_pass(git_vector_insert(&filelist, "item6")); - cl_git_pass(git_vector_insert(&filelist, "item5/item7/item4")); - cl_git_pass(git_vector_insert(&filelist, "item1/item2")); - cl_git_pass(git_vector_insert(&filelist, "item0")); - - /* also add some things that don't exist or don't match the right type */ - cl_git_pass(git_vector_insert(&filelist, "item2/")); - cl_git_pass(git_vector_insert(&filelist, "itemN")); - cl_git_pass(git_vector_insert(&filelist, "item1/itemA")); - cl_git_pass(git_vector_insert(&filelist, "item5/item3/item4/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - git_index_free(index); - git_vector_free(&filelist); -} - -void test_iterator_index__advance_over(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - - i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_DONT_AUTOEXPAND; - - g_repo = cl_git_sandbox_init("icase"); - cl_git_pass(git_repository_index(&index, g_repo)); - - create_paths(index, NULL, 1); - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - - expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item0", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item1/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item2", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item3/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item4", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item5/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item6", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item7/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL); - - cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); - git_iterator_free(i); - git_index_free(index); -} - -void test_iterator_index__advance_into(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - - g_repo = cl_git_sandbox_init("icase"); - - i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_DONT_AUTOEXPAND; - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_advance_into(i, "B"); - expect_advance_into(i, "D"); - expect_advance_into(i, "F"); - expect_advance_into(i, "H"); - expect_advance_into(i, "J"); - expect_advance_into(i, "L/"); - expect_advance_into(i, "L/1"); - expect_advance_into(i, "L/B"); - expect_advance_into(i, "L/D"); - expect_advance_into(i, "L/a"); - expect_advance_into(i, "L/c"); - expect_advance_into(i, "a"); - expect_advance_into(i, "c"); - expect_advance_into(i, "e"); - expect_advance_into(i, "g"); - expect_advance_into(i, "i"); - expect_advance_into(i, "k/"); - expect_advance_into(i, "k/1"); - expect_advance_into(i, "k/B"); - expect_advance_into(i, "k/D"); - expect_advance_into(i, "k/a"); - expect_advance_into(i, "k/c"); - - cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); - git_iterator_free(i); - git_index_free(index); -} - -void test_iterator_index__advance_into_and_over(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - - g_repo = cl_git_sandbox_init("icase"); - - i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_DONT_AUTOEXPAND; - - cl_git_pass(git_repository_index(&index, g_repo)); - - create_paths(index, NULL, 2); - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_advance_into(i, "B"); - expect_advance_into(i, "D"); - expect_advance_into(i, "F"); - expect_advance_into(i, "H"); - expect_advance_into(i, "J"); - expect_advance_into(i, "L/"); - expect_advance_into(i, "L/1"); - expect_advance_into(i, "L/B"); - expect_advance_into(i, "L/D"); - expect_advance_into(i, "L/a"); - expect_advance_into(i, "L/c"); - expect_advance_into(i, "a"); - expect_advance_into(i, "c"); - expect_advance_into(i, "e"); - expect_advance_into(i, "g"); - expect_advance_into(i, "i"); - expect_advance_into(i, "item0"); - expect_advance_into(i, "item1/"); - expect_advance_into(i, "item1/item0"); - expect_advance_into(i, "item1/item1/"); - expect_advance_into(i, "item1/item1/item0"); - expect_advance_into(i, "item1/item1/item1"); - expect_advance_into(i, "item1/item1/item2"); - expect_advance_into(i, "item1/item1/item3"); - expect_advance_into(i, "item1/item1/item4"); - expect_advance_into(i, "item1/item1/item5"); - expect_advance_into(i, "item1/item1/item6"); - expect_advance_into(i, "item1/item1/item7"); - expect_advance_into(i, "item1/item2"); - expect_advance_over(i, "item1/item3/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item1/item4", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item1/item5/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item1/item6", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item1/item7/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_into(i, "item2"); - expect_advance_over(i, "item3/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item4", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item5/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item6", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "item7/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_into(i, "k/"); - expect_advance_into(i, "k/1"); - expect_advance_into(i, "k/B"); - expect_advance_into(i, "k/D"); - expect_advance_into(i, "k/a"); - expect_advance_into(i, "k/c"); - - cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); - git_iterator_free(i); - git_index_free(index); -} - -static void add_conflict( - git_index *index, - const char *ancestor_path, - const char *our_path, - const char *their_path) -{ - git_index_entry ancestor = {{0}}, ours = {{0}}, theirs = {{0}}; - - ancestor.path = ancestor_path; - ancestor.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&ancestor.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6"); - GIT_INDEX_ENTRY_STAGE_SET(&ancestor, 1); - - ours.path = our_path; - ours.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&ours.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6"); - GIT_INDEX_ENTRY_STAGE_SET(&ours, 2); - - theirs.path = their_path; - theirs.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&theirs.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6"); - GIT_INDEX_ENTRY_STAGE_SET(&theirs, 3); - - cl_git_pass(git_index_conflict_add(index, &ancestor, &ours, &theirs)); -} - -void test_iterator_index__include_conflicts(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - - i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_DONT_AUTOEXPAND; - - g_repo = cl_git_sandbox_init("icase"); - cl_git_pass(git_repository_index(&index, g_repo)); - - add_conflict(index, "CONFLICT1", "CONFLICT1" ,"CONFLICT1"); - add_conflict(index, "ZZZ-CONFLICT2.ancestor", "ZZZ-CONFLICT2.ours", "ZZZ-CONFLICT2.theirs"); - add_conflict(index, "ancestor.conflict3", "ours.conflict3", "theirs.conflict3"); - add_conflict(index, "zzz-conflict4", "zzz-conflict4", "zzz-conflict4"); - - /* Iterate the index, ensuring that conflicts are not included */ - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - - expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL); - - cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); - git_iterator_free(i); - - /* Try again, returning conflicts */ - i_opts.flags |= GIT_ITERATOR_INCLUDE_CONFLICTS; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - - expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "CONFLICT1", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "CONFLICT1", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "CONFLICT1", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "ZZZ-CONFLICT2.ancestor", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "ZZZ-CONFLICT2.ours", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "ZZZ-CONFLICT2.theirs", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "ancestor.conflict3", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "ours.conflict3", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "theirs.conflict3", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "zzz-conflict4", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "zzz-conflict4", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "zzz-conflict4", GIT_ITERATOR_STATUS_NORMAL); - - cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); - git_iterator_free(i); - - git_index_free(index); -} diff --git a/tests/iterator/iterator_helpers.c b/tests/iterator/iterator_helpers.c deleted file mode 100644 index b210dbb0c..000000000 --- a/tests/iterator/iterator_helpers.c +++ /dev/null @@ -1,143 +0,0 @@ -#include "clar_libgit2.h" -#include "iterator.h" -#include "repository.h" -#include "futils.h" -#include "iterator_helpers.h" -#include - -static void assert_at_end(git_iterator *i, bool verbose) -{ - const git_index_entry *end; - int error = git_iterator_advance(&end, i); - - if (verbose && error != GIT_ITEROVER) - fprintf(stderr, "Expected end of iterator, got '%s'\n", end->path); - - cl_git_fail_with(GIT_ITEROVER, error); -} - -void expect_iterator_items( - git_iterator *i, - size_t expected_flat, - const char **expected_flat_paths, - size_t expected_total, - const char **expected_total_paths) -{ - const git_index_entry *entry; - size_t count; - int no_trees = !(git_iterator_flags(i) & GIT_ITERATOR_INCLUDE_TREES); - bool v = false; - int error; - - if (v) fprintf(stderr, "== %s ==\n", no_trees ? "notrees" : "trees"); - - count = 0; - - while (!git_iterator_advance(&entry, i)) { - if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode); - - if (no_trees) - cl_assert(entry->mode != GIT_FILEMODE_TREE); - - if (expected_flat_paths) { - const char *expect_path = expected_flat_paths[count]; - size_t expect_len = strlen(expect_path); - - cl_assert_equal_s(expect_path, entry->path); - - if (expect_path[expect_len - 1] == '/') - cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode); - else - cl_assert(entry->mode != GIT_FILEMODE_TREE); - } - - cl_assert(++count <= expected_flat); - } - - assert_at_end(i, v); - cl_assert_equal_i(expected_flat, count); - - cl_git_pass(git_iterator_reset(i)); - - count = 0; - cl_git_pass(git_iterator_current(&entry, i)); - - if (v) fprintf(stderr, "-- %s --\n", no_trees ? "notrees" : "trees"); - - while (entry != NULL) { - if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode); - - if (no_trees) - cl_assert(entry->mode != GIT_FILEMODE_TREE); - - if (expected_total_paths) { - const char *expect_path = expected_total_paths[count]; - size_t expect_len = strlen(expect_path); - - cl_assert_equal_s(expect_path, entry->path); - - if (expect_path[expect_len - 1] == '/') - cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode); - else - cl_assert(entry->mode != GIT_FILEMODE_TREE); - } - - if (entry->mode == GIT_FILEMODE_TREE) { - error = git_iterator_advance_into(&entry, i); - - /* could return NOTFOUND if directory is empty */ - cl_assert(!error || error == GIT_ENOTFOUND); - - if (error == GIT_ENOTFOUND) { - error = git_iterator_advance(&entry, i); - cl_assert(!error || error == GIT_ITEROVER); - } - } else { - error = git_iterator_advance(&entry, i); - cl_assert(!error || error == GIT_ITEROVER); - } - - if (++count >= expected_total) - break; - } - - assert_at_end(i, v); - cl_assert_equal_i(expected_total, count); -} - - -void expect_advance_over( - git_iterator *i, - const char *expected_path, - git_iterator_status_t expected_status) -{ - const git_index_entry *entry; - git_iterator_status_t status; - int error; - - cl_git_pass(git_iterator_current(&entry, i)); - cl_assert_equal_s(expected_path, entry->path); - - error = git_iterator_advance_over(&entry, &status, i); - cl_assert(!error || error == GIT_ITEROVER); - cl_assert_equal_i(expected_status, status); -} - -void expect_advance_into( - git_iterator *i, - const char *expected_path) -{ - const git_index_entry *entry; - int error; - - cl_git_pass(git_iterator_current(&entry, i)); - cl_assert_equal_s(expected_path, entry->path); - - if (S_ISDIR(entry->mode)) - error = git_iterator_advance_into(&entry, i); - else - error = git_iterator_advance(&entry, i); - - cl_assert(!error || error == GIT_ITEROVER); -} - diff --git a/tests/iterator/iterator_helpers.h b/tests/iterator/iterator_helpers.h deleted file mode 100644 index 1884b41a1..000000000 --- a/tests/iterator/iterator_helpers.h +++ /dev/null @@ -1,16 +0,0 @@ - -extern void expect_iterator_items( - git_iterator *i, - size_t expected_flat, - const char **expected_flat_paths, - size_t expected_total, - const char **expected_total_paths); - -extern void expect_advance_over( - git_iterator *i, - const char *expected_path, - git_iterator_status_t expected_status); - -void expect_advance_into( - git_iterator *i, - const char *expected_path); diff --git a/tests/iterator/tree.c b/tests/iterator/tree.c deleted file mode 100644 index 4145c8dea..000000000 --- a/tests/iterator/tree.c +++ /dev/null @@ -1,1080 +0,0 @@ -#include "clar_libgit2.h" -#include "iterator.h" -#include "repository.h" -#include "futils.h" -#include "tree.h" -#include "../submodule/submodule_helpers.h" -#include "../diff/diff_helpers.h" -#include "iterator_helpers.h" -#include - -static git_repository *g_repo; - -void test_iterator_tree__initialize(void) -{ -} - -void test_iterator_tree__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -static void tree_iterator_test( - const char *sandbox, - const char *treeish, - const char *start, - const char *end, - int expected_count, - const char **expected_values) -{ - git_tree *t; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry; - int error, count = 0, count_post_reset = 0; - - g_repo = cl_git_sandbox_init(sandbox); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - i_opts.start = start; - i_opts.end = end; - - cl_assert(t = resolve_commit_oid_to_tree(g_repo, treeish)); - cl_git_pass(git_iterator_for_tree(&i, t, &i_opts)); - - /* test loop */ - while (!(error = git_iterator_advance(&entry, i))) { - cl_assert(entry); - if (expected_values != NULL) - cl_assert_equal_s(expected_values[count], entry->path); - count++; - } - cl_assert_equal_i(GIT_ITEROVER, error); - cl_assert(!entry); - cl_assert_equal_i(expected_count, count); - - /* test reset */ - cl_git_pass(git_iterator_reset(i)); - - while (!(error = git_iterator_advance(&entry, i))) { - cl_assert(entry); - if (expected_values != NULL) - cl_assert_equal_s(expected_values[count_post_reset], entry->path); - count_post_reset++; - } - cl_assert_equal_i(GIT_ITEROVER, error); - cl_assert(!entry); - cl_assert_equal_i(count, count_post_reset); - - git_iterator_free(i); - git_tree_free(t); -} - -/* results of: git ls-tree -r --name-only 605812a */ -const char *expected_tree_0[] = { - ".gitattributes", - "attr0", - "attr1", - "attr2", - "attr3", - "binfile", - "macro_test", - "root_test1", - "root_test2", - "root_test3", - "root_test4.txt", - "subdir/.gitattributes", - "subdir/abc", - "subdir/subdir_test1", - "subdir/subdir_test2.txt", - "subdir2/subdir2_test1", - NULL -}; - -void test_iterator_tree__0(void) -{ - tree_iterator_test("attr", "605812a", NULL, NULL, 16, expected_tree_0); -} - -/* results of: git ls-tree -r --name-only 6bab5c79 */ -const char *expected_tree_1[] = { - ".gitattributes", - "attr0", - "attr1", - "attr2", - "attr3", - "root_test1", - "root_test2", - "root_test3", - "root_test4.txt", - "subdir/.gitattributes", - "subdir/subdir_test1", - "subdir/subdir_test2.txt", - "subdir2/subdir2_test1", - NULL -}; - -void test_iterator_tree__1(void) -{ - tree_iterator_test("attr", "6bab5c79cd5", NULL, NULL, 13, expected_tree_1); -} - -/* results of: git ls-tree -r --name-only 26a125ee1 */ -const char *expected_tree_2[] = { - "current_file", - "file_deleted", - "modified_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "subdir.txt", - "subdir/current_file", - "subdir/deleted_file", - "subdir/modified_file", - NULL -}; - -void test_iterator_tree__2(void) -{ - tree_iterator_test("status", "26a125ee1", NULL, NULL, 12, expected_tree_2); -} - -/* $ git ls-tree -r --name-only 0017bd4ab1e */ -const char *expected_tree_3[] = { - "current_file", - "file_deleted", - "modified_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file" -}; - -void test_iterator_tree__3(void) -{ - tree_iterator_test("status", "0017bd4ab1e", NULL, NULL, 8, expected_tree_3); -} - -/* $ git ls-tree -r --name-only 24fa9a9fc4e202313e24b648087495441dab432b */ -const char *expected_tree_4[] = { - "attr0", - "attr1", - "attr2", - "attr3", - "binfile", - "gitattributes", - "macro_bad", - "macro_test", - "root_test1", - "root_test2", - "root_test3", - "root_test4.txt", - "sub/abc", - "sub/file", - "sub/sub/file", - "sub/sub/subsub.txt", - "sub/subdir_test1", - "sub/subdir_test2.txt", - "subdir/.gitattributes", - "subdir/abc", - "subdir/subdir_test1", - "subdir/subdir_test2.txt", - "subdir2/subdir2_test1", - NULL -}; - -void test_iterator_tree__4(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", NULL, NULL, - 23, expected_tree_4); -} - -void test_iterator_tree__4_ranged(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", - "sub", "sub", - 11, &expected_tree_4[12]); -} - -const char *expected_tree_ranged_0[] = { - "gitattributes", - "macro_bad", - "macro_test", - "root_test1", - "root_test2", - "root_test3", - "root_test4.txt", - NULL -}; - -void test_iterator_tree__ranged_0(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", - "git", "root", - 7, expected_tree_ranged_0); -} - -const char *expected_tree_ranged_1[] = { - "sub/subdir_test2.txt", - NULL -}; - -void test_iterator_tree__ranged_1(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", - "sub/subdir_test2.txt", "sub/subdir_test2.txt", - 1, expected_tree_ranged_1); -} - -void test_iterator_tree__range_empty_0(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", - "empty", "empty", 0, NULL); -} - -void test_iterator_tree__range_empty_1(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", - "z_empty_after", NULL, 0, NULL); -} - -void test_iterator_tree__range_empty_2(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", - NULL, ".aaa_empty_before", 0, NULL); -} - -static void check_tree_entry( - git_iterator *i, - const char *oid, - const char *oid_p, - const char *oid_pp, - const char *oid_ppp) -{ - const git_index_entry *ie; - const git_tree_entry *te; - const git_tree *tree; - - cl_git_pass(git_iterator_current_tree_entry(&te, i)); - cl_assert(te); - cl_assert(git_oid_streq(te->oid, oid) == 0); - - cl_git_pass(git_iterator_current(&ie, i)); - - if (oid_p) { - cl_git_pass(git_iterator_current_parent_tree(&tree, i, 0)); - cl_assert(tree); - cl_assert(git_oid_streq(git_tree_id(tree), oid_p) == 0); - } - - if (oid_pp) { - cl_git_pass(git_iterator_current_parent_tree(&tree, i, 1)); - cl_assert(tree); - cl_assert(git_oid_streq(git_tree_id(tree), oid_pp) == 0); - } - - if (oid_ppp) { - cl_git_pass(git_iterator_current_parent_tree(&tree, i, 2)); - cl_assert(tree); - cl_assert(git_oid_streq(git_tree_id(tree), oid_ppp) == 0); - } -} - -void test_iterator_tree__special_functions(void) -{ - git_tree *t; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry; - int error, cases = 0; - const char *rootoid = "ce39a97a7fb1fa90bcf5e711249c1e507476ae0e"; - - g_repo = cl_git_sandbox_init("attr"); - - t = resolve_commit_oid_to_tree( - g_repo, "24fa9a9fc4e202313e24b648087495441dab432b"); - cl_assert(t != NULL); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_tree(&i, t, &i_opts)); - - while (!(error = git_iterator_advance(&entry, i))) { - cl_assert(entry); - - if (strcmp(entry->path, "sub/file") == 0) { - cases++; - check_tree_entry( - i, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", - "ecb97df2a174987475ac816e3847fc8e9f6c596b", - rootoid, NULL); - } - else if (strcmp(entry->path, "sub/sub/subsub.txt") == 0) { - cases++; - check_tree_entry( - i, "9e5bdc47d6a80f2be0ea3049ad74231b94609242", - "4e49ba8c5b6c32ff28cd9dcb60be34df50fcc485", - "ecb97df2a174987475ac816e3847fc8e9f6c596b", rootoid); - } - else if (strcmp(entry->path, "subdir/.gitattributes") == 0) { - cases++; - check_tree_entry( - i, "99eae476896f4907224978b88e5ecaa6c5bb67a9", - "9fb40b6675dde60b5697afceae91b66d908c02d9", - rootoid, NULL); - } - else if (strcmp(entry->path, "subdir2/subdir2_test1") == 0) { - cases++; - check_tree_entry( - i, "dccada462d3df8ac6de596fb8c896aba9344f941", - "2929de282ce999e95183aedac6451d3384559c4b", - rootoid, NULL); - } - } - cl_assert_equal_i(GIT_ITEROVER, error); - cl_assert(!entry); - cl_assert_equal_i(4, cases); - - git_iterator_free(i); - git_tree_free(t); -} - -static void check_tree_range( - git_repository *repo, - const char *start, - const char *end, - bool ignore_case, - int expected_count) -{ - git_tree *head; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - int error, count; - - i_opts.flags = ignore_case ? GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; - i_opts.start = start; - i_opts.end = end; - - cl_git_pass(git_repository_head_tree(&head, repo)); - - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - - for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count) - /* count em up */; - - cl_assert_equal_i(GIT_ITEROVER, error); - cl_assert_equal_i(expected_count, count); - - git_iterator_free(i); - git_tree_free(head); -} - -void test_iterator_tree__range_icase(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); - - check_tree_range(g_repo, "B", "C", false, 0); - check_tree_range(g_repo, "B", "C", true, 1); - check_tree_range(g_repo, "b", "c", false, 1); - check_tree_range(g_repo, "b", "c", true, 1); - - check_tree_range(g_repo, "a", "z", false, 3); - check_tree_range(g_repo, "a", "z", true, 4); - check_tree_range(g_repo, "A", "Z", false, 1); - check_tree_range(g_repo, "A", "Z", true, 4); - check_tree_range(g_repo, "a", "Z", false, 0); - check_tree_range(g_repo, "a", "Z", true, 4); - check_tree_range(g_repo, "A", "z", false, 4); - check_tree_range(g_repo, "A", "z", true, 4); - - check_tree_range(g_repo, "new.txt", "new.txt", true, 1); - check_tree_range(g_repo, "new.txt", "new.txt", false, 1); - check_tree_range(g_repo, "README", "README", true, 1); - check_tree_range(g_repo, "README", "README", false, 1); -} - -void test_iterator_tree__icase_0(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_tree *head; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_head_tree(&head, g_repo)); - - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_tree(&i, head, NULL)); - expect_iterator_items(i, 20, NULL, 20, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 22, NULL, 22, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 12, NULL, 22, NULL); - git_iterator_free(i); - - git_tree_free(head); -} - -void test_iterator_tree__icase_1(void) -{ - git_iterator *i; - git_tree *head; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_head_tree(&head, g_repo)); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - /* auto expand with no tree entries */ - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 7, NULL, 7, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 3, NULL, 3, NULL); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - - /* auto expand with tree entries */ - i_opts.start = "c"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 8, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 4, NULL, 4, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 5, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 1, NULL, 4, NULL); - git_iterator_free(i); - - /* auto expand with no tree entries */ - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 13, NULL, 13, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 5, NULL, 5, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 14, NULL, 14, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 6, NULL, 6, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 9, NULL, 14, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 1, NULL, 6, NULL); - git_iterator_free(i); - - git_tree_free(head); -} - -void test_iterator_tree__icase_2(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_tree *head; - static const char *expect_basic[] = { - "current_file", - "file_deleted", - "modified_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "subdir.txt", - "subdir/current_file", - "subdir/deleted_file", - "subdir/modified_file", - NULL, - }; - static const char *expect_trees[] = { - "current_file", - "file_deleted", - "modified_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "subdir.txt", - "subdir/", - "subdir/current_file", - "subdir/deleted_file", - "subdir/modified_file", - NULL, - }; - static const char *expect_noauto[] = { - "current_file", - "file_deleted", - "modified_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "subdir.txt", - "subdir/", - NULL - }; - - g_repo = cl_git_sandbox_init("status"); - - cl_git_pass(git_repository_head_tree(&head, g_repo)); - - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_tree(&i, head, NULL)); - expect_iterator_items(i, 12, expect_basic, 12, expect_basic); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 13, expect_trees, 13, expect_trees); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 10, expect_noauto, 13, expect_trees); - git_iterator_free(i); - - git_tree_free(head); -} - -/* "b=name,t=name", blob_id, tree_id */ -static void build_test_tree( - git_oid *out, git_repository *repo, const char *fmt, ...) -{ - git_oid *id; - git_treebuilder *builder; - const char *scan = fmt, *next; - char type, delimiter; - git_filemode_t mode = GIT_FILEMODE_BLOB; - git_str name = GIT_STR_INIT; - va_list arglist; - - cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); /* start builder */ - - va_start(arglist, fmt); - while (*scan) { - switch (type = *scan++) { - case 't': case 'T': mode = GIT_FILEMODE_TREE; break; - case 'b': case 'B': mode = GIT_FILEMODE_BLOB; break; - default: - cl_assert(type == 't' || type == 'T' || type == 'b' || type == 'B'); - } - - delimiter = *scan++; /* read and skip delimiter */ - for (next = scan; *next && *next != delimiter; ++next) - /* seek end */; - cl_git_pass(git_str_set(&name, scan, (size_t)(next - scan))); - for (scan = next; *scan && (*scan == delimiter || *scan == ','); ++scan) - /* skip delimiter and optional comma */; - - id = va_arg(arglist, git_oid *); - - cl_git_pass(git_treebuilder_insert(NULL, builder, name.ptr, id, mode)); - } - va_end(arglist); - - cl_git_pass(git_treebuilder_write(out, builder)); - - git_treebuilder_free(builder); - git_str_dispose(&name); -} - -void test_iterator_tree__case_conflicts_0(void) -{ - const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6"; - git_tree *tree; - git_oid blob_id, biga_id, littlea_id, tree_id; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - const char *expect_cs[] = { - "A/1.file", "A/3.file", "a/2.file", "a/4.file" }; - const char *expect_ci[] = { - "A/1.file", "a/2.file", "A/3.file", "a/4.file" }; - const char *expect_cs_trees[] = { - "A/", "A/1.file", "A/3.file", "a/", "a/2.file", "a/4.file" }; - const char *expect_ci_trees[] = { - "A/", "A/1.file", "a/2.file", "A/3.file", "a/4.file" }; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */ - - /* create tree with: A/1.file, A/3.file, a/2.file, a/4.file */ - build_test_tree( - &biga_id, g_repo, "b|1.file|,b|3.file|", &blob_id, &blob_id); - build_test_tree( - &littlea_id, g_repo, "b|2.file|,b|4.file|", &blob_id, &blob_id); - build_test_tree( - &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 4, expect_cs, 4, expect_cs); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 4, expect_ci, 4, expect_ci); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 6, expect_cs_trees, 6, expect_cs_trees); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 5, expect_ci_trees, 5, expect_ci_trees); - git_iterator_free(i); - - git_tree_free(tree); -} - -void test_iterator_tree__case_conflicts_1(void) -{ - const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6"; - git_tree *tree; - git_oid blob_id, Ab_id, biga_id, littlea_id, tree_id; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - const char *expect_cs[] = { - "A/a", "A/b/1", "A/c", "a/C", "a/a", "a/b" }; - const char *expect_ci[] = { - "A/a", "a/b", "A/b/1", "A/c" }; - const char *expect_cs_trees[] = { - "A/", "A/a", "A/b/", "A/b/1", "A/c", "a/", "a/C", "a/a", "a/b" }; - const char *expect_ci_trees[] = { - "A/", "A/a", "a/b", "A/b/", "A/b/1", "A/c" }; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */ - - /* create: A/a A/b/1 A/c a/a a/b a/C */ - build_test_tree(&Ab_id, g_repo, "b|1|", &blob_id); - build_test_tree( - &biga_id, g_repo, "b|a|,t|b|,b|c|", &blob_id, &Ab_id, &blob_id); - build_test_tree( - &littlea_id, g_repo, "b|a|,b|b|,b|C|", &blob_id, &blob_id, &blob_id); - build_test_tree( - &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 6, expect_cs, 6, expect_cs); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 4, expect_ci, 4, expect_ci); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 9, expect_cs_trees, 9, expect_cs_trees); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 6, expect_ci_trees, 6, expect_ci_trees); - git_iterator_free(i); - - git_tree_free(tree); -} - -void test_iterator_tree__case_conflicts_2(void) -{ - const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6"; - git_tree *tree; - git_oid blob_id, d1, d2, c1, c2, b1, b2, a1, a2, tree_id; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - const char *expect_cs[] = { - "A/B/C/D/16", "A/B/C/D/foo", "A/B/C/d/15", "A/B/C/d/FOO", - "A/B/c/D/14", "A/B/c/D/foo", "A/B/c/d/13", "A/B/c/d/FOO", - "A/b/C/D/12", "A/b/C/D/foo", "A/b/C/d/11", "A/b/C/d/FOO", - "A/b/c/D/10", "A/b/c/D/foo", "A/b/c/d/09", "A/b/c/d/FOO", - "a/B/C/D/08", "a/B/C/D/foo", "a/B/C/d/07", "a/B/C/d/FOO", - "a/B/c/D/06", "a/B/c/D/foo", "a/B/c/d/05", "a/B/c/d/FOO", - "a/b/C/D/04", "a/b/C/D/foo", "a/b/C/d/03", "a/b/C/d/FOO", - "a/b/c/D/02", "a/b/c/D/foo", "a/b/c/d/01", "a/b/c/d/FOO", }; - const char *expect_ci[] = { - "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04", - "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08", - "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12", - "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16", - "A/B/C/D/foo", }; - const char *expect_ci_trees[] = { - "A/", "A/B/", "A/B/C/", "A/B/C/D/", - "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04", - "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08", - "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12", - "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16", - "A/B/C/D/foo", }; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */ - - build_test_tree(&d1, g_repo, "b|16|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|15|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&d1, g_repo, "b|14|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|13|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2); - - build_test_tree(&d1, g_repo, "b|12|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|11|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&d1, g_repo, "b|10|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|09|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2); - - build_test_tree(&a1, g_repo, "t|B|,t|b|", &b1, &b2); - - build_test_tree(&d1, g_repo, "b|08|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|07|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&d1, g_repo, "b|06|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|05|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2); - - build_test_tree(&d1, g_repo, "b|04|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|03|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&d1, g_repo, "b|02|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|01|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2); - - build_test_tree(&a2, g_repo, "t|B|,t|b|", &b1, &b2); - - build_test_tree(&tree_id, g_repo, "t/A/,t/a/", &a1, &a2); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 32, expect_cs, 32, expect_cs); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 17, expect_ci, 17, expect_ci); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 21, expect_ci_trees, 21, expect_ci_trees); - git_iterator_free(i); - - git_tree_free(tree); -} - -void test_iterator_tree__pathlist(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - git_tree *tree; - bool default_icase; - int expect; - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "a")); - cl_git_pass(git_vector_insert(&filelist, "B")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k.a")); - cl_git_pass(git_vector_insert(&filelist, "k.b")); - cl_git_pass(git_vector_insert(&filelist, "k/1")); - cl_git_pass(git_vector_insert(&filelist, "k/a")); - cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); - cl_git_pass(git_vector_insert(&filelist, "L/1")); - - g_repo = cl_git_sandbox_init("icase"); - git_repository_head_tree(&tree, g_repo); - - /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ - /* In this test we DO NOT force a case on the iterators and verify default behavior. */ - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 8, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "c"; - i_opts.end = NULL; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - default_icase = git_iterator_ignore_case(i); - /* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */ - expect = ((default_icase) ? 6 : 4); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - i_opts.start = NULL; - i_opts.end = "e"; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - default_icase = git_iterator_ignore_case(i); - /* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */ - expect = ((default_icase) ? 5 : 6); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - git_vector_free(&filelist); - git_tree_free(tree); -} - -void test_iterator_tree__pathlist_icase(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - git_tree *tree; - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "a")); - cl_git_pass(git_vector_insert(&filelist, "B")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k.a")); - cl_git_pass(git_vector_insert(&filelist, "k.b")); - cl_git_pass(git_vector_insert(&filelist, "k/1")); - cl_git_pass(git_vector_insert(&filelist, "k/a")); - cl_git_pass(git_vector_insert(&filelist, "kZZZZ")); - cl_git_pass(git_vector_insert(&filelist, "L/1")); - - g_repo = cl_git_sandbox_init("icase"); - git_repository_head_tree(&tree, g_repo); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 3, NULL, 3, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 1, NULL, 1, NULL); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 5, NULL, 5, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 2, NULL, 2, NULL); - git_iterator_free(i); - - git_vector_free(&filelist); - git_tree_free(tree); -} - -void test_iterator_tree__pathlist_with_directory(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - git_tree *tree; - - const char *expected[] = { "subdir/README", "subdir/new.txt", - "subdir/subdir2/README", "subdir/subdir2/new.txt" }; - size_t expected_len = 4; - - const char *expected2[] = { "subdir/subdir2/README", "subdir/subdir2/new.txt" }; - size_t expected_len2 = 2; - - g_repo = cl_git_sandbox_init("testrepo2"); - git_repository_head_tree(&tree, g_repo); - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "subdir")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "subdir/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "subdir/subdir2")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, expected_len2, expected2, expected_len2, expected2); - git_iterator_free(i); - - git_tree_free(tree); - git_vector_free(&filelist); -} - -void test_iterator_tree__pathlist_with_directory_include_tree_nodes(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - git_tree *tree; - - const char *expected[] = { "subdir/", "subdir/README", "subdir/new.txt", - "subdir/subdir2/", "subdir/subdir2/README", "subdir/subdir2/new.txt" }; - size_t expected_len = 6; - - g_repo = cl_git_sandbox_init("testrepo2"); - git_repository_head_tree(&tree, g_repo); - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "subdir")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - - git_tree_free(tree); - git_vector_free(&filelist); -} - -void test_iterator_tree__pathlist_no_match(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - git_tree *tree; - const git_index_entry *entry; - - g_repo = cl_git_sandbox_init("testrepo2"); - git_repository_head_tree(&tree, g_repo); - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "nonexistent/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - cl_assert_equal_i(GIT_ITEROVER, git_iterator_current(&entry, i)); - git_iterator_free(i); - - git_tree_free(tree); - git_vector_free(&filelist); -} - diff --git a/tests/iterator/workdir.c b/tests/iterator/workdir.c deleted file mode 100644 index 86b847cab..000000000 --- a/tests/iterator/workdir.c +++ /dev/null @@ -1,1523 +0,0 @@ -#include "clar_libgit2.h" -#include "iterator.h" -#include "repository.h" -#include "futils.h" -#include "../submodule/submodule_helpers.h" -#include "../merge/merge_helpers.h" -#include "iterator_helpers.h" -#include - -static git_repository *g_repo; - -void test_iterator_workdir__initialize(void) -{ -} - -void test_iterator_workdir__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -static void workdir_iterator_test( - const char *sandbox, - const char *start, - const char *end, - int expected_count, - int expected_ignores, - const char **expected_names, - const char *an_ignored_name) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry; - int error, count = 0, count_all = 0, count_all_post_reset = 0; - - g_repo = cl_git_sandbox_init(sandbox); - - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - i_opts.start = start; - i_opts.end = end; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - - error = git_iterator_current(&entry, i); - cl_assert((error == 0 && entry != NULL) || - (error == GIT_ITEROVER && entry == NULL)); - - while (entry != NULL) { - int ignored = git_iterator_current_is_ignored(i); - - if (S_ISDIR(entry->mode)) { - cl_git_pass(git_iterator_advance_into(&entry, i)); - continue; - } - - if (expected_names != NULL) - cl_assert_equal_s(expected_names[count_all], entry->path); - - if (an_ignored_name && strcmp(an_ignored_name,entry->path)==0) - cl_assert(ignored); - - if (!ignored) - count++; - count_all++; - - error = git_iterator_advance(&entry, i); - - cl_assert((error == 0 && entry != NULL) || - (error == GIT_ITEROVER && entry == NULL)); - } - - cl_assert_equal_i(expected_count, count); - cl_assert_equal_i(expected_count + expected_ignores, count_all); - - cl_git_pass(git_iterator_reset(i)); - - error = git_iterator_current(&entry, i); - cl_assert((error == 0 && entry != NULL) || - (error == GIT_ITEROVER && entry == NULL)); - - while (entry != NULL) { - if (S_ISDIR(entry->mode)) { - cl_git_pass(git_iterator_advance_into(&entry, i)); - continue; - } - - if (expected_names != NULL) - cl_assert_equal_s( - expected_names[count_all_post_reset], entry->path); - count_all_post_reset++; - - error = git_iterator_advance(&entry, i); - cl_assert(error == 0 || error == GIT_ITEROVER); - } - - cl_assert_equal_i(count_all, count_all_post_reset); - - git_iterator_free(i); -} - -void test_iterator_workdir__0(void) -{ - workdir_iterator_test("attr", NULL, NULL, 24, 5, NULL, "ign"); -} - -static const char *status_paths[] = { - "current_file", - "ignored_file", - "modified_file", - "new_file", - "staged_changes", - "staged_changes_modified_file", - "staged_delete_modified_file", - "staged_new_file", - "staged_new_file_modified_file", - "subdir.txt", - "subdir/current_file", - "subdir/modified_file", - "subdir/new_file", - "\xe8\xbf\x99", - NULL -}; - -void test_iterator_workdir__1(void) -{ - workdir_iterator_test( - "status", NULL, NULL, 13, 1, status_paths, "ignored_file"); -} - -static const char *status_paths_range_0[] = { - "staged_changes", - "staged_changes_modified_file", - "staged_delete_modified_file", - "staged_new_file", - "staged_new_file_modified_file", - NULL -}; - -void test_iterator_workdir__1_ranged_0(void) -{ - workdir_iterator_test( - "status", "staged", "staged", 5, 0, status_paths_range_0, NULL); -} - -static const char *status_paths_range_1[] = { - "modified_file", NULL -}; - -void test_iterator_workdir__1_ranged_1(void) -{ - workdir_iterator_test( - "status", "modified_file", "modified_file", - 1, 0, status_paths_range_1, NULL); -} - -static const char *status_paths_range_3[] = { - "subdir.txt", - "subdir/current_file", - "subdir/modified_file", - NULL -}; - -void test_iterator_workdir__1_ranged_3(void) -{ - workdir_iterator_test( - "status", "subdir", "subdir/modified_file", - 3, 0, status_paths_range_3, NULL); -} - -static const char *status_paths_range_4[] = { - "subdir/current_file", - "subdir/modified_file", - "subdir/new_file", - "\xe8\xbf\x99", - NULL -}; - -void test_iterator_workdir__1_ranged_4(void) -{ - workdir_iterator_test( - "status", "subdir/", NULL, 4, 0, status_paths_range_4, NULL); -} - -static const char *status_paths_range_5[] = { - "subdir/modified_file", - NULL -}; - -void test_iterator_workdir__1_ranged_5(void) -{ - workdir_iterator_test( - "status", "subdir/modified_file", "subdir/modified_file", - 1, 0, status_paths_range_5, NULL); -} - -void test_iterator_workdir__1_ranged_5_1_ranged_empty_0(void) -{ - workdir_iterator_test( - "status", "\xff_does_not_exist", NULL, - 0, 0, NULL, NULL); -} - -void test_iterator_workdir__1_ranged_empty_1(void) -{ - workdir_iterator_test( - "status", "empty", "empty", - 0, 0, NULL, NULL); -} - -void test_iterator_workdir__1_ranged_empty_2(void) -{ - workdir_iterator_test( - "status", NULL, "aaaa_empty_before", - 0, 0, NULL, NULL); -} - -void test_iterator_workdir__builtin_ignores(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry; - int idx; - static struct { - const char *path; - bool ignored; - } expected[] = { - { "dir/", true }, - { "file", false }, - { "ign", true }, - { "macro_bad", false }, - { "macro_test", false }, - { "root_test1", false }, - { "root_test2", false }, - { "root_test3", false }, - { "root_test4.txt", false }, - { "sub/", false }, - { "sub/.gitattributes", false }, - { "sub/abc", false }, - { "sub/dir/", true }, - { "sub/file", false }, - { "sub/ign/", true }, - { "sub/sub/", false }, - { "sub/sub/.gitattributes", false }, - { "sub/sub/dir", false }, /* file is not actually a dir */ - { "sub/sub/file", false }, - { NULL, false } - }; - - g_repo = cl_git_sandbox_init("attr"); - - cl_git_pass(p_mkdir("attr/sub/sub/.git", 0777)); - cl_git_mkfile("attr/sub/.git", "whatever"); - - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - i_opts.start = "dir"; - i_opts.end = "sub/sub/file"; - - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, &i_opts)); - cl_git_pass(git_iterator_current(&entry, i)); - - for (idx = 0; entry != NULL; ++idx) { - int ignored = git_iterator_current_is_ignored(i); - - cl_assert_equal_s(expected[idx].path, entry->path); - cl_assert_(ignored == expected[idx].ignored, expected[idx].path); - - if (!ignored && - (entry->mode == GIT_FILEMODE_TREE || - entry->mode == GIT_FILEMODE_COMMIT)) - { - /* it is possible to advance "into" a submodule */ - cl_git_pass(git_iterator_advance_into(&entry, i)); - } else { - int error = git_iterator_advance(&entry, i); - cl_assert(!error || error == GIT_ITEROVER); - } - } - - cl_assert(expected[idx].path == NULL); - - git_iterator_free(i); -} - -static void check_wd_first_through_third_range( - git_repository *repo, const char *start, const char *end) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry; - int error, idx; - static const char *expected[] = { "FIRST", "second", "THIRD", NULL }; - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - i_opts.start = start; - i_opts.end = end; - - cl_git_pass(git_iterator_for_workdir( - &i, repo, NULL, NULL, &i_opts)); - cl_git_pass(git_iterator_current(&entry, i)); - - for (idx = 0; entry != NULL; ++idx) { - cl_assert_equal_s(expected[idx], entry->path); - - error = git_iterator_advance(&entry, i); - cl_assert(!error || error == GIT_ITEROVER); - } - - cl_assert(expected[idx] == NULL); - - git_iterator_free(i); -} - -void test_iterator_workdir__handles_icase_range(void) -{ - g_repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_remove_placeholders(git_repository_path(g_repo), "dummy-marker.txt"); - - cl_git_mkfile("empty_standard_repo/before", "whatever\n"); - cl_git_mkfile("empty_standard_repo/FIRST", "whatever\n"); - cl_git_mkfile("empty_standard_repo/second", "whatever\n"); - cl_git_mkfile("empty_standard_repo/THIRD", "whatever\n"); - cl_git_mkfile("empty_standard_repo/zafter", "whatever\n"); - cl_git_mkfile("empty_standard_repo/Zlast", "whatever\n"); - - check_wd_first_through_third_range(g_repo, "first", "third"); - check_wd_first_through_third_range(g_repo, "FIRST", "THIRD"); - check_wd_first_through_third_range(g_repo, "first", "THIRD"); - check_wd_first_through_third_range(g_repo, "FIRST", "third"); - check_wd_first_through_third_range(g_repo, "FirSt", "tHiRd"); -} - -/* - * The workdir iterator is like the filesystem iterator, but honors - * special git type constructs (ignores, submodules, etc). - */ - -void test_iterator_workdir__icase(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("icase"); - - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 20, NULL, 20, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 22, NULL, 22, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 12, NULL, 22, NULL); - git_iterator_free(i); -} - -void test_iterator_workdir__icase_starts_and_ends(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("icase"); - - /* auto expand with no tree entries */ - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 7, NULL, 7, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 3, NULL, 3, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 8, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 4, NULL, 4, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 5, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 1, NULL, 4, NULL); - git_iterator_free(i); - - /* auto expand with no tree entries */ - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 13, NULL, 13, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 5, NULL, 5, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 14, NULL, 14, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 6, NULL, 6, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 9, NULL, 14, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 1, NULL, 6, NULL); - git_iterator_free(i); -} - -static void build_workdir_tree(const char *root, int dirs, int subs) -{ - int i, j; - char buf[64], sub[80]; - - for (i = 0; i < dirs; ++i) { - if (i % 2 == 0) { - p_snprintf(buf, sizeof(buf), "%s/dir%02d", root, i); - cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH)); - - p_snprintf(buf, sizeof(buf), "%s/dir%02d/file", root, i); - cl_git_mkfile(buf, buf); - buf[strlen(buf) - 5] = '\0'; - } else { - p_snprintf(buf, sizeof(buf), "%s/DIR%02d", root, i); - cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH)); - } - - for (j = 0; j < subs; ++j) { - switch (j % 4) { - case 0: p_snprintf(sub, sizeof(sub), "%s/sub%02d", buf, j); break; - case 1: p_snprintf(sub, sizeof(sub), "%s/sUB%02d", buf, j); break; - case 2: p_snprintf(sub, sizeof(sub), "%s/Sub%02d", buf, j); break; - case 3: p_snprintf(sub, sizeof(sub), "%s/SUB%02d", buf, j); break; - } - cl_git_pass(git_futils_mkdir(sub, 0775, GIT_MKDIR_PATH)); - - if (j % 2 == 0) { - size_t sublen = strlen(sub); - memcpy(&sub[sublen], "/file", sizeof("/file")); - cl_git_mkfile(sub, sub); - sub[sublen] = '\0'; - } - } - } -} - -void test_iterator_workdir__depth(void) -{ - git_iterator *iter; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("icase"); - - build_workdir_tree("icase", 10, 10); - build_workdir_tree("icase/DIR01/sUB01", 50, 0); - build_workdir_tree("icase/dir02/sUB01", 50, 0); - - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); - expect_iterator_items(iter, 125, NULL, 125, NULL); - git_iterator_free(iter); - - /* auto expand with tree entries (empty dirs silently skipped) */ - iter_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); - expect_iterator_items(iter, 337, NULL, 337, NULL); - git_iterator_free(iter); -} - -/* The filesystem iterator is a workdir iterator without any special - * workdir handling capabilities (ignores, submodules, etc). - */ -void test_iterator_workdir__filesystem(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - static const char *expect_base[] = { - "DIR01/Sub02/file", - "DIR01/sub00/file", - "current_file", - "dir00/Sub02/file", - "dir00/file", - "dir00/sub00/file", - "modified_file", - "new_file", - NULL, - }; - static const char *expect_trees[] = { - "DIR01/", - "DIR01/SUB03/", - "DIR01/Sub02/", - "DIR01/Sub02/file", - "DIR01/sUB01/", - "DIR01/sub00/", - "DIR01/sub00/file", - "current_file", - "dir00/", - "dir00/SUB03/", - "dir00/Sub02/", - "dir00/Sub02/file", - "dir00/file", - "dir00/sUB01/", - "dir00/sub00/", - "dir00/sub00/file", - "modified_file", - "new_file", - NULL, - }; - static const char *expect_noauto[] = { - "DIR01/", - "current_file", - "dir00/", - "modified_file", - "new_file", - NULL, - }; - - g_repo = cl_git_sandbox_init("status"); - - build_workdir_tree("status/subdir", 2, 4); - - cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", NULL)); - expect_iterator_items(i, 8, expect_base, 8, expect_base); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); - expect_iterator_items(i, 18, expect_trees, 18, expect_trees); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); - expect_iterator_items(i, 5, expect_noauto, 18, expect_trees); - git_iterator_free(i); - - git__tsort((void **)expect_base, 8, (git__tsort_cmp)git__strcasecmp); - git__tsort((void **)expect_trees, 18, (git__tsort_cmp)git__strcasecmp); - git__tsort((void **)expect_noauto, 5, (git__tsort_cmp)git__strcasecmp); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); - expect_iterator_items(i, 8, expect_base, 8, expect_base); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); - expect_iterator_items(i, 18, expect_trees, 18, expect_trees); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; - cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); - expect_iterator_items(i, 5, expect_noauto, 18, expect_trees); - git_iterator_free(i); -} - -void test_iterator_workdir__filesystem2(void) -{ - git_iterator *i; - static const char *expect_base[] = { - "heads/br2", - "heads/dir", - "heads/executable", - "heads/ident", - "heads/long-file-name", - "heads/master", - "heads/merge-conflict", - "heads/packed-test", - "heads/subtrees", - "heads/test", - "heads/testrepo-worktree", - "symref", - "tags/e90810b", - "tags/foo/bar", - "tags/foo/foo/bar", - "tags/point_to_blob", - "tags/test", - NULL, - }; - - g_repo = cl_git_sandbox_init("testrepo"); - - cl_git_pass(git_iterator_for_filesystem( - &i, "testrepo/.git/refs", NULL)); - expect_iterator_items(i, 17, expect_base, 17, expect_base); - git_iterator_free(i); -} - -/* - * Lots of empty dirs, or nearly empty ones, make the old workdir - * iterator cry. Also, segfault. - */ -void test_iterator_workdir__filesystem_gunk(void) -{ - git_str parent = GIT_STR_INIT; - git_iterator *i; - int n; - - if (!cl_is_env_set("GITTEST_INVASIVE_SPEED")) - cl_skip(); - - g_repo = cl_git_sandbox_init("testrepo"); - - for (n = 0; n < 100000; n++) { - git_str_clear(&parent); - cl_git_pass(git_str_printf(&parent, "%s/refs/heads/foo/%d/subdir", git_repository_path(g_repo), n)); - cl_git_pass(git_futils_mkdir(parent.ptr, 0775, GIT_MKDIR_PATH)); - } - - cl_git_pass(git_iterator_for_filesystem(&i, "testrepo/.git/refs", NULL)); - - /* - * Should only have 17 items, since we're not asking for trees to be - * returned. the goal of this test is simply to not crash. - */ - expect_iterator_items(i, 17, NULL, 16, NULL); - - git_iterator_free(i); - git_str_dispose(&parent); -} - -void test_iterator_workdir__skips_unreadable_dirs(void) -{ - git_iterator *i; - const git_index_entry *e; - - if (!cl_is_chmod_supported()) - return; - -#ifndef GIT_WIN32 - if (geteuid() == 0) - cl_skip(); -#endif - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_must_pass(p_mkdir("empty_standard_repo/r", 0777)); - cl_git_mkfile("empty_standard_repo/r/a", "hello"); - cl_must_pass(p_mkdir("empty_standard_repo/r/b", 0777)); - cl_git_mkfile("empty_standard_repo/r/b/problem", "not me"); - cl_must_pass(p_chmod("empty_standard_repo/r/b", 0000)); - cl_must_pass(p_mkdir("empty_standard_repo/r/c", 0777)); - cl_git_mkfile("empty_standard_repo/r/c/foo", "aloha"); - cl_git_mkfile("empty_standard_repo/r/d", "final"); - - cl_git_pass(git_iterator_for_filesystem( - &i, "empty_standard_repo/r", NULL)); - - cl_git_pass(git_iterator_advance(&e, i)); /* a */ - cl_assert_equal_s("a", e->path); - - cl_git_pass(git_iterator_advance(&e, i)); /* c/foo */ - cl_assert_equal_s("c/foo", e->path); - - cl_git_pass(git_iterator_advance(&e, i)); /* d */ - cl_assert_equal_s("d", e->path); - - cl_must_pass(p_chmod("empty_standard_repo/r/b", 0777)); - - cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i)); - git_iterator_free(i); -} - -void test_iterator_workdir__skips_fifos_and_special_files(void) -{ -#ifndef GIT_WIN32 - git_iterator *i; - const git_index_entry *e; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_must_pass(p_mkdir("empty_standard_repo/dir", 0777)); - cl_git_mkfile("empty_standard_repo/file", "not me"); - - cl_assert(!mkfifo("empty_standard_repo/fifo", 0777)); - cl_assert(!access("empty_standard_repo/fifo", F_OK)); - - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES | - GIT_ITERATOR_DONT_AUTOEXPAND; - - cl_git_pass(git_iterator_for_filesystem( - &i, "empty_standard_repo", &i_opts)); - - cl_git_pass(git_iterator_advance(&e, i)); /* .git */ - cl_assert(S_ISDIR(e->mode)); - cl_git_pass(git_iterator_advance(&e, i)); /* dir */ - cl_assert(S_ISDIR(e->mode)); - /* skips fifo */ - cl_git_pass(git_iterator_advance(&e, i)); /* file */ - cl_assert(S_ISREG(e->mode)); - - cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i)); - - git_iterator_free(i); -#else - cl_skip(); -#endif -} - -void test_iterator_workdir__pathlist(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - - cl_git_pass(git_vector_init(&filelist, 100, NULL)); - cl_git_pass(git_vector_insert(&filelist, "a")); - cl_git_pass(git_vector_insert(&filelist, "B")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k.a")); - cl_git_pass(git_vector_insert(&filelist, "k.b")); - cl_git_pass(git_vector_insert(&filelist, "k/1")); - cl_git_pass(git_vector_insert(&filelist, "k/a")); - cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); - cl_git_pass(git_vector_insert(&filelist, "L/1")); - - g_repo = cl_git_sandbox_init("icase"); - - /* Test iterators without returning tree entries (but autoexpanding.) */ - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - /* Case sensitive */ - { - const char *expected[] = { - "B", "D", "L/1", "a", "c", "e", "k/1", "k/a" }; - size_t expected_len = 8; - - i_opts.start = NULL; - i_opts.end = NULL; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Case INsensitive */ - { - const char *expected[] = { - "a", "B", "c", "D", "e", "k/1", "k/a", "L/1" }; - size_t expected_len = 8; - - i_opts.start = NULL; - i_opts.end = NULL; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Set a start, but no end. Case sensitive. */ - { - const char *expected[] = { "c", "e", "k/1", "k/a" }; - size_t expected_len = 4; - - i_opts.start = "c"; - i_opts.end = NULL; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Set a start, but no end. Case INsensitive. */ - { - const char *expected[] = { "c", "D", "e", "k/1", "k/a", "L/1" }; - size_t expected_len = 6; - - i_opts.start = "c"; - i_opts.end = NULL; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Set no start, but an end. Case sensitive. */ - { - const char *expected[] = { "B", "D", "L/1", "a", "c", "e" }; - size_t expected_len = 6; - - i_opts.start = NULL; - i_opts.end = "e"; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Set no start, but an end. Case INsensitive. */ - { - const char *expected[] = { "a", "B", "c", "D", "e" }; - size_t expected_len = 5; - - i_opts.start = NULL; - i_opts.end = "e"; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Start and an end, case sensitive */ - { - const char *expected[] = { "c", "e", "k/1" }; - size_t expected_len = 3; - - i_opts.start = "c"; - i_opts.end = "k/D"; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Start and an end, case sensitive */ - { - const char *expected[] = { "k/1" }; - size_t expected_len = 1; - - i_opts.start = "k"; - i_opts.end = "k/D"; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Start and an end, case INsensitive */ - { - const char *expected[] = { "c", "D", "e", "k/1", "k/a" }; - size_t expected_len = 5; - - i_opts.start = "c"; - i_opts.end = "k/D"; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Start and an end, case INsensitive */ - { - const char *expected[] = { "k/1", "k/a" }; - size_t expected_len = 2; - - i_opts.start = "k"; - i_opts.end = "k/D"; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - git_vector_free(&filelist); -} - -void test_iterator_workdir__pathlist_with_dirs(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - - cl_git_pass(git_vector_init(&filelist, 5, NULL)); - - g_repo = cl_git_sandbox_init("icase"); - - /* Test that a prefix `k` matches folders, even without trailing slash */ - { - const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" }; - size_t expected_len = 5; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "k")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Test that a `k/` matches a folder */ - { - const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" }; - size_t expected_len = 5; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "k/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* When the iterator is case sensitive, ensure we can't lookup the - * directory with the wrong case. - */ - { - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "K/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); - git_iterator_free(i); - } - - /* Test that case insensitive matching works. */ - { - const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" }; - size_t expected_len = 5; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "K/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Test that case insensitive matching works without trailing slash. */ - { - const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" }; - size_t expected_len = 5; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "K")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - git_vector_free(&filelist); -} - -static void create_paths(const char *root, int depth) -{ - git_str fullpath = GIT_STR_INIT; - size_t root_len; - int i; - - cl_git_pass(git_str_puts(&fullpath, root)); - cl_git_pass(git_fs_path_to_dir(&fullpath)); - - root_len = fullpath.size; - - for (i = 0; i < 8; i++) { - bool file = (depth == 0 || (i % 2) == 0); - git_str_truncate(&fullpath, root_len); - cl_git_pass(git_str_printf(&fullpath, "item%d", i)); - - if (file) { - cl_git_rewritefile(fullpath.ptr, "This is a file!\n"); - } else { - cl_must_pass(p_mkdir(fullpath.ptr, 0777)); - - if (depth > 0) - create_paths(fullpath.ptr, (depth - 1)); - } - } - - git_str_dispose(&fullpath); -} - -void test_iterator_workdir__pathlist_for_deeply_nested_item(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - - cl_git_pass(git_vector_init(&filelist, 5, NULL)); - - g_repo = cl_git_sandbox_init("icase"); - create_paths(git_repository_workdir(g_repo), 3); - - /* Ensure that we find the single path we're interested in, and we find - * it efficiently, and don't stat the entire world to get there. - */ - { - const char *expected[] = { "item1/item3/item5/item7" }; - size_t expected_len = 1; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/item7")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - cl_assert_equal_i(4, i->stat_calls); - git_iterator_free(i); - } - - /* Ensure that we find the single path we're interested in, and we find - * it efficiently, and don't stat the entire world to get there. - */ - { - const char *expected[] = { - "item1/item3/item5/item0", "item1/item3/item5/item1", - "item1/item3/item5/item2", "item1/item3/item5/item3", - "item1/item3/item5/item4", "item1/item3/item5/item5", - "item1/item3/item5/item6", "item1/item3/item5/item7", - }; - size_t expected_len = 8; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - cl_assert_equal_i(11, i->stat_calls); - git_iterator_free(i); - } - - /* Ensure that we find the single path we're interested in, and we find - * it efficiently, and don't stat the entire world to get there. - */ - { - const char *expected[] = { - "item1/item3/item0", - "item1/item3/item1/item0", "item1/item3/item1/item1", - "item1/item3/item1/item2", "item1/item3/item1/item3", - "item1/item3/item1/item4", "item1/item3/item1/item5", - "item1/item3/item1/item6", "item1/item3/item1/item7", - "item1/item3/item2", - "item1/item3/item3/item0", "item1/item3/item3/item1", - "item1/item3/item3/item2", "item1/item3/item3/item3", - "item1/item3/item3/item4", "item1/item3/item3/item5", - "item1/item3/item3/item6", "item1/item3/item3/item7", - "item1/item3/item4", - "item1/item3/item5/item0", "item1/item3/item5/item1", - "item1/item3/item5/item2", "item1/item3/item5/item3", - "item1/item3/item5/item4", "item1/item3/item5/item5", - "item1/item3/item5/item6", "item1/item3/item5/item7", - "item1/item3/item6", - "item1/item3/item7/item0", "item1/item3/item7/item1", - "item1/item3/item7/item2", "item1/item3/item7/item3", - "item1/item3/item7/item4", "item1/item3/item7/item5", - "item1/item3/item7/item6", "item1/item3/item7/item7", - }; - size_t expected_len = 36; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "item1/item3/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - cl_assert_equal_i(42, i->stat_calls); - git_iterator_free(i); - } - - /* Ensure that we find the single path we're interested in, and we find - * it efficiently, and don't stat the entire world to get there. - */ - { - const char *expected[] = { - "item0", "item1/item2", "item5/item7/item4", "item6", - "item7/item3/item1/item6" }; - size_t expected_len = 5; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "item7/item3/item1/item6")); - cl_git_pass(git_vector_insert(&filelist, "item6")); - cl_git_pass(git_vector_insert(&filelist, "item5/item7/item4")); - cl_git_pass(git_vector_insert(&filelist, "item1/item2")); - cl_git_pass(git_vector_insert(&filelist, "item0")); - - /* also add some things that don't exist or don't match the right type */ - cl_git_pass(git_vector_insert(&filelist, "item2/")); - cl_git_pass(git_vector_insert(&filelist, "itemN")); - cl_git_pass(git_vector_insert(&filelist, "item1/itemA")); - cl_git_pass(git_vector_insert(&filelist, "item5/item3/item4/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - cl_assert_equal_i(14, i->stat_calls); - git_iterator_free(i); - } - - git_vector_free(&filelist); -} - -void test_iterator_workdir__bounded_submodules(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - git_index *index; - git_tree *head; - - cl_git_pass(git_vector_init(&filelist, 5, NULL)); - - g_repo = setup_fixture_submod2(); - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_repository_head_tree(&head, g_repo)); - - /* Test that a submodule matches */ - { - const char *expected[] = { "sm_changed_head" }; - size_t expected_len = 1; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "sm_changed_head")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Test that a submodule still matches when suffixed with a '/' */ - { - const char *expected[] = { "sm_changed_head" }; - size_t expected_len = 1; - - git_vector_clear(&filelist); - cl_git_pass(git_vector_insert(&filelist, "sm_changed_head/")); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Test that start/end work with a submodule */ - { - const char *expected[] = { "sm_changed_head", "sm_changed_index" }; - size_t expected_len = 2; - - i_opts.start = "sm_changed_head"; - i_opts.end = "sm_changed_index"; - i_opts.pathlist.strings = NULL; - i_opts.pathlist.count = 0; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - /* Test that start and end allow '/' suffixes of submodules */ - { - const char *expected[] = { "sm_changed_head", "sm_changed_index" }; - size_t expected_len = 2; - - i_opts.start = "sm_changed_head"; - i_opts.end = "sm_changed_index"; - i_opts.pathlist.strings = NULL; - i_opts.pathlist.count = 0; - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - } - - git_vector_free(&filelist); - git_index_free(index); - git_tree_free(head); -} - -void test_iterator_workdir__advance_over(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_DONT_AUTOEXPAND; - - g_repo = cl_git_sandbox_init("icase"); - - /* create an empty directory */ - cl_must_pass(p_mkdir("icase/empty", 0777)); - - /* create a directory in which all contents are ignored */ - cl_must_pass(p_mkdir("icase/all_ignored", 0777)); - cl_git_rewritefile("icase/all_ignored/one", "This is ignored\n"); - cl_git_rewritefile("icase/all_ignored/two", "This, too, is ignored\n"); - cl_git_rewritefile("icase/all_ignored/.gitignore", ".gitignore\none\ntwo\n"); - - /* create a directory in which not all contents are ignored */ - cl_must_pass(p_mkdir("icase/some_ignored", 0777)); - cl_git_rewritefile("icase/some_ignored/one", "This is ignored\n"); - cl_git_rewritefile("icase/some_ignored/two", "This is not ignored\n"); - cl_git_rewritefile("icase/some_ignored/.gitignore", ".gitignore\none\n"); - - /* create a directory which has some empty children */ - cl_must_pass(p_mkdir("icase/empty_children", 0777)); - cl_must_pass(p_mkdir("icase/empty_children/empty1", 0777)); - cl_must_pass(p_mkdir("icase/empty_children/empty2", 0777)); - cl_must_pass(p_mkdir("icase/empty_children/empty3", 0777)); - - /* create a directory which will disappear! */ - cl_must_pass(p_mkdir("icase/missing_directory", 0777)); - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - - cl_must_pass(p_rmdir("icase/missing_directory")); - - expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "all_ignored/", GIT_ITERATOR_STATUS_IGNORED); - expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "empty/", GIT_ITERATOR_STATUS_EMPTY); - expect_advance_over(i, "empty_children/", GIT_ITERATOR_STATUS_EMPTY); - expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "missing_directory/", GIT_ITERATOR_STATUS_EMPTY); - expect_advance_over(i, "some_ignored/", GIT_ITERATOR_STATUS_NORMAL); - - cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); - git_iterator_free(i); -} - -void test_iterator_workdir__advance_over_with_pathlist(void) -{ - git_vector pathlist = GIT_VECTOR_INIT; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - git_vector_insert(&pathlist, "dirA/subdir1/subdir2/file"); - git_vector_insert(&pathlist, "dirB/subdir1/subdir2"); - git_vector_insert(&pathlist, "dirC/subdir1/nonexistent"); - git_vector_insert(&pathlist, "dirD/subdir1/nonexistent"); - git_vector_insert(&pathlist, "dirD/subdir1/subdir2"); - git_vector_insert(&pathlist, "dirD/nonexistent"); - - i_opts.pathlist.strings = (char **)pathlist.contents; - i_opts.pathlist.count = pathlist.length; - i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_DONT_AUTOEXPAND; - - g_repo = cl_git_sandbox_init("icase"); - - /* Create a directory that has a file that is included in our pathlist */ - cl_must_pass(p_mkdir("icase/dirA", 0777)); - cl_must_pass(p_mkdir("icase/dirA/subdir1", 0777)); - cl_must_pass(p_mkdir("icase/dirA/subdir1/subdir2", 0777)); - cl_git_rewritefile("icase/dirA/subdir1/subdir2/file", "foo!"); - - /* Create a directory that has a directory that is included in our pathlist */ - cl_must_pass(p_mkdir("icase/dirB", 0777)); - cl_must_pass(p_mkdir("icase/dirB/subdir1", 0777)); - cl_must_pass(p_mkdir("icase/dirB/subdir1/subdir2", 0777)); - cl_git_rewritefile("icase/dirB/subdir1/subdir2/file", "foo!"); - - /* Create a directory that would contain an entry in our pathlist, but - * that entry does not actually exist. We don't know this until we - * advance_over it. We want to distinguish this from an actually empty - * or ignored directory. - */ - cl_must_pass(p_mkdir("icase/dirC", 0777)); - cl_must_pass(p_mkdir("icase/dirC/subdir1", 0777)); - cl_must_pass(p_mkdir("icase/dirC/subdir1/subdir2", 0777)); - cl_git_rewritefile("icase/dirC/subdir1/subdir2/file", "foo!"); - - /* Create a directory that has a mix of actual and nonexistent paths */ - cl_must_pass(p_mkdir("icase/dirD", 0777)); - cl_must_pass(p_mkdir("icase/dirD/subdir1", 0777)); - cl_must_pass(p_mkdir("icase/dirD/subdir1/subdir2", 0777)); - cl_git_rewritefile("icase/dirD/subdir1/subdir2/file", "foo!"); - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - - expect_advance_over(i, "dirA/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "dirB/", GIT_ITERATOR_STATUS_NORMAL); - expect_advance_over(i, "dirC/", GIT_ITERATOR_STATUS_FILTERED); - expect_advance_over(i, "dirD/", GIT_ITERATOR_STATUS_NORMAL); - - cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); - git_iterator_free(i); - git_vector_free(&pathlist); -} - -void test_iterator_workdir__advance_into(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("icase"); - - i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_DONT_AUTOEXPAND; - - cl_must_pass(p_mkdir("icase/Empty", 0777)); - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_advance_into(i, "B"); - expect_advance_into(i, "D"); - expect_advance_into(i, "Empty/"); - expect_advance_into(i, "F"); - expect_advance_into(i, "H"); - expect_advance_into(i, "J"); - expect_advance_into(i, "L/"); - expect_advance_into(i, "L/1"); - expect_advance_into(i, "L/B"); - expect_advance_into(i, "L/D"); - expect_advance_into(i, "L/a"); - expect_advance_into(i, "L/c"); - expect_advance_into(i, "a"); - expect_advance_into(i, "c"); - expect_advance_into(i, "e"); - expect_advance_into(i, "g"); - expect_advance_into(i, "i"); - expect_advance_into(i, "k/"); - expect_advance_into(i, "k/1"); - expect_advance_into(i, "k/B"); - expect_advance_into(i, "k/D"); - expect_advance_into(i, "k/a"); - expect_advance_into(i, "k/c"); - - cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); - git_iterator_free(i); -} - -void test_iterator_workdir__pathlist_with_directory(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - - const char *expected[] = { "subdir/README", "subdir/new.txt", - "subdir/subdir2/README", "subdir/subdir2/new.txt" }; - size_t expected_len = 4; - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "subdir/")); - - g_repo = cl_git_sandbox_init("testrepo2"); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - - git_vector_free(&filelist); -} - -void test_iterator_workdir__pathlist_with_directory_include_trees(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - - const char *expected[] = { "subdir/", "subdir/README", "subdir/new.txt", - "subdir/subdir2/", "subdir/subdir2/README", "subdir/subdir2/new.txt", }; - size_t expected_len = 6; - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "subdir/")); - - g_repo = cl_git_sandbox_init("testrepo2"); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, expected_len, expected, expected_len, expected); - git_iterator_free(i); - - git_vector_free(&filelist); -} - -void test_iterator_workdir__hash_when_requested(void) -{ - git_iterator *iter; - const git_index_entry *entry; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - git_oid expected_id = {{0}}; - size_t i; - - struct merge_index_entry expected[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "7c7e08f9559d9e1551b91e1cf68f1d0066109add", 0, "oyster.txt" }, - { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "veal.txt" }, - }; - - g_repo = cl_git_sandbox_init("merge-recursive"); - - /* do the iteration normally, ensure there are no hashes */ - cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); - - for (i = 0; i < sizeof(expected) / sizeof(struct merge_index_entry); i++) { - cl_git_pass(git_iterator_advance(&entry, iter)); - - cl_assert_equal_oid(&expected_id, &entry->id); - cl_assert_equal_s(expected[i].path, entry->path); - } - cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&entry, iter)); - git_iterator_free(iter); - - /* do the iteration requesting hashes */ - iter_opts.flags |= GIT_ITERATOR_INCLUDE_HASH; - cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); - - for (i = 0; i < sizeof(expected) / sizeof(struct merge_index_entry); i++) { - cl_git_pass(git_iterator_advance(&entry, iter)); - - cl_git_pass(git_oid_fromstr(&expected_id, expected[i].oid_str)); - cl_assert_equal_oid(&expected_id, &entry->id); - cl_assert_equal_s(expected[i].path, entry->path); - } - cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&entry, iter)); - git_iterator_free(iter); -} diff --git a/tests/libgit2/CMakeLists.txt b/tests/libgit2/CMakeLists.txt new file mode 100644 index 000000000..8bf199ecf --- /dev/null +++ b/tests/libgit2/CMakeLists.txt @@ -0,0 +1,98 @@ +# tests: the unit and integration tests for libgit2 + +set(Python_ADDITIONAL_VERSIONS 3 2.7) +find_package(PythonInterp) + +if(NOT PYTHONINTERP_FOUND) + message(FATAL_ERROR "Could not find a python interpreter, which is needed to build the tests. " + "Make sure python is available, or pass -DBUILD_TESTS=OFF to skip building the tests") +ENDIF() + +set(CLAR_FIXTURES "${PROJECT_SOURCE_DIR}/tests/resources/") +set(CLAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}") +add_definitions(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\") +add_definitions(-DCLAR_TMPDIR=\"libgit2_tests\") +add_definitions(-DCLAR_WIN32_LONGPATHS) +add_definitions(-D_FILE_OFFSET_BITS=64) + +# Ensure that we do not use deprecated functions internally +add_definitions(-DGIT_DEPRECATE_HARD) + +set(TEST_INCLUDES "${CLAR_PATH}" "${CMAKE_CURRENT_BINARY_DIR}") +file(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c ${CLAR_PATH}/*/*.h) +set(SRC_CLAR "main.c" "clar_libgit2.c" "clar_libgit2_trace.c" "clar_libgit2_timer.c" "clar.c") + +if(MSVC_IDE) + list(APPEND SRC_CLAR "precompiled.c") +endif() + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/clar.suite ${CMAKE_CURRENT_BINARY_DIR}/clar_suite.h + COMMAND ${PYTHON_EXECUTABLE} generate.py -o "${CMAKE_CURRENT_BINARY_DIR}" -f -xonline -xstress -xperf . + DEPENDS ${SRC_TEST} + WORKING_DIRECTORY ${CLAR_PATH} +) + +set_source_files_properties( + ${CLAR_PATH}/clar.c + PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/clar.suite) + +add_executable(libgit2_tests ${SRC_CLAR} ${SRC_TEST} ${LIBGIT2_OBJECTS}) + +set_target_properties(libgit2_tests PROPERTIES C_STANDARD 90) +set_target_properties(libgit2_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) + +target_include_directories(libgit2_tests PRIVATE ${TEST_INCLUDES} ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES}) +target_include_directories(libgit2_tests SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) +target_link_libraries(libgit2_tests ${LIBGIT2_SYSTEM_LIBS}) + +ide_split_sources(libgit2_tests) + +# +# Old versions of gcc require us to declare our test functions; don't do +# this on newer compilers to avoid unnecessary recompilation. +# +if(CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) + target_compile_options(libgit2_tests PRIVATE -include "clar_suite.h") +endif() + +if(MSVC_IDE) + # Precompiled headers + set_target_properties(libgit2_tests PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") + set_source_files_properties("precompiled.c" COMPILE_FLAGS "/Ycprecompiled.h") +endif() + +function(ADD_CLAR_TEST name) + if(NOT USE_LEAK_CHECKER STREQUAL "OFF") + add_test(${name} "${PROJECT_SOURCE_DIR}/script/${USE_LEAK_CHECKER}.sh" "${PROJECT_BINARY_DIR}/libgit2_tests" ${ARGN}) + else() + add_test(${name} "${PROJECT_BINARY_DIR}/libgit2_tests" ${ARGN}) + endif() +endfunction(ADD_CLAR_TEST) + +add_clar_test(offline -v -xonline) +add_clar_test(invasive -v -score::ftruncate -sfilter::stream::bigfile -sodb::largefiles -siterator::workdir::filesystem_gunk -srepo::init -srepo::init::at_filesystem_root) +add_clar_test(online -v -sonline -xonline::customcert) +add_clar_test(online_customcert -v -sonline::customcert) +add_clar_test(gitdaemon -v -sonline::push) +add_clar_test(ssh -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths -sonline::clone::path_whitespace_ssh) +add_clar_test(proxy -v -sonline::clone::proxy) +add_clar_test(auth_clone -v -sonline::clone::cred) +add_clar_test(auth_clone_and_push -v -sonline::clone::push -sonline::push) + +# +# Header file validation project: ensure that we do not publish any sloppy +# definitions in our headers and that a consumer can include +# even when they have aggressive C90 warnings enabled. +# + +add_executable(headertest headertest.c) +set_target_properties(headertest PROPERTIES C_STANDARD 90) +set_target_properties(headertest PROPERTIES C_EXTENSIONS OFF) +target_include_directories(headertest PRIVATE ${LIBGIT2_INCLUDES}) + +if (MSVC) + target_compile_options(headertest PUBLIC /W4 /WX) +else() + target_compile_options(headertest PUBLIC -Wall -Wextra -pedantic -Werror) +endif() diff --git a/tests/libgit2/apply/apply_helpers.c b/tests/libgit2/apply/apply_helpers.c new file mode 100644 index 000000000..91cc51a71 --- /dev/null +++ b/tests/libgit2/apply/apply_helpers.c @@ -0,0 +1,135 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" + +struct iterator_compare_data { + struct merge_index_entry *expected; + size_t cnt; + size_t idx; +}; + +static int iterator_compare(const git_index_entry *entry, void *_data) +{ + git_oid expected_id; + + struct iterator_compare_data *data = (struct iterator_compare_data *)_data; + + cl_assert_equal_i(GIT_INDEX_ENTRY_STAGE(entry), data->expected[data->idx].stage); + cl_git_pass(git_oid_fromstr(&expected_id, data->expected[data->idx].oid_str)); + cl_assert_equal_oid(&entry->id, &expected_id); + cl_assert_equal_i(entry->mode, data->expected[data->idx].mode); + cl_assert_equal_s(entry->path, data->expected[data->idx].path); + + if (data->idx >= data->cnt) + return -1; + + data->idx++; + + return 0; +} + +void validate_apply_workdir( + git_repository *repo, + struct merge_index_entry *workdir_entries, + size_t workdir_cnt) +{ + git_index *index; + git_iterator *iterator; + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + struct iterator_compare_data data = { workdir_entries, workdir_cnt }; + + opts.flags |= GIT_ITERATOR_INCLUDE_HASH; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_iterator_for_workdir(&iterator, repo, index, NULL, &opts)); + + cl_git_pass(git_iterator_foreach(iterator, iterator_compare, &data)); + cl_assert_equal_i(data.idx, data.cnt); + + git_iterator_free(iterator); + git_index_free(index); +} + +void validate_apply_index( + git_repository *repo, + struct merge_index_entry *index_entries, + size_t index_cnt) +{ + git_index *index; + git_iterator *iterator; + struct iterator_compare_data data = { index_entries, index_cnt }; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_iterator_for_index(&iterator, repo, index, NULL)); + + cl_git_pass(git_iterator_foreach(iterator, iterator_compare, &data)); + cl_assert_equal_i(data.idx, data.cnt); + + git_iterator_free(iterator); + git_index_free(index); +} + +static int iterator_eq(const git_index_entry **entry, void *_data) +{ + GIT_UNUSED(_data); + + if (!entry[0] || !entry[1]) + return -1; + + cl_assert_equal_i(GIT_INDEX_ENTRY_STAGE(entry[0]), GIT_INDEX_ENTRY_STAGE(entry[1])); + cl_assert_equal_oid(&entry[0]->id, &entry[1]->id); + cl_assert_equal_i(entry[0]->mode, entry[1]->mode); + cl_assert_equal_s(entry[0]->path, entry[1]->path); + + return 0; +} + +void validate_index_unchanged(git_repository *repo) +{ + git_tree *head; + git_index *index; + git_iterator *head_iterator, *index_iterator, *iterators[2]; + + cl_git_pass(git_repository_head_tree(&head, repo)); + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_pass(git_iterator_for_tree(&head_iterator, head, NULL)); + cl_git_pass(git_iterator_for_index(&index_iterator, repo, index, NULL)); + + iterators[0] = head_iterator; + iterators[1] = index_iterator; + + cl_git_pass(git_iterator_walk(iterators, 2, iterator_eq, NULL)); + + git_iterator_free(head_iterator); + git_iterator_free(index_iterator); + + git_tree_free(head); + git_index_free(index); +} + +void validate_workdir_unchanged(git_repository *repo) +{ + git_tree *head; + git_index *index; + git_iterator *head_iterator, *workdir_iterator, *iterators[2]; + git_iterator_options workdir_opts = GIT_ITERATOR_OPTIONS_INIT; + + cl_git_pass(git_repository_head_tree(&head, repo)); + cl_git_pass(git_repository_index(&index, repo)); + + workdir_opts.flags |= GIT_ITERATOR_INCLUDE_HASH; + + cl_git_pass(git_iterator_for_tree(&head_iterator, head, NULL)); + cl_git_pass(git_iterator_for_workdir(&workdir_iterator, repo, index, NULL, &workdir_opts)); + + iterators[0] = head_iterator; + iterators[1] = workdir_iterator; + + cl_git_pass(git_iterator_walk(iterators, 2, iterator_eq, NULL)); + + git_iterator_free(head_iterator); + git_iterator_free(workdir_iterator); + + git_tree_free(head); + git_index_free(index); +} diff --git a/tests/libgit2/apply/apply_helpers.h b/tests/libgit2/apply/apply_helpers.h new file mode 100644 index 000000000..82094773e --- /dev/null +++ b/tests/libgit2/apply/apply_helpers.h @@ -0,0 +1,497 @@ +#include "../merge/merge_helpers.h" + +#define TEST_REPO_PATH "merge-recursive" + +#define DIFF_MODIFY_TWO_FILES \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "index f516580..ffb36e5 100644\n" \ + "--- a/asparagus.txt\n" \ + "+++ b/asparagus.txt\n" \ + "@@ -1 +1 @@\n" \ + "-ASPARAGUS SOUP!\n" \ + "+ASPARAGUS SOUP.\n" \ + "diff --git a/veal.txt b/veal.txt\n" \ + "index 94d2c01..a7b0665 100644\n" \ + "--- a/veal.txt\n" \ + "+++ b/veal.txt\n" \ + "@@ -1 +1 @@\n" \ + "-VEAL SOUP!\n" \ + "+VEAL SOUP.\n" \ + "@@ -7 +7 @@ occasionally, then put into it a shin of veal, let it boil two hours\n" \ + "-longer. take out the slices of ham, and skim off the grease if any\n" \ + "+longer; take out the slices of ham, and skim off the grease if any\n" + +/* This is the binary equivalent of DIFF_MODIFY_TWO_FILES */ +#define DIFF_MODIFY_TWO_FILES_BINARY \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "index f51658077d85f2264fa179b4d0848268cb3475c3..ffb36e513f5fdf8a6ba850a20142676a2ac4807d 100644\n" \ + "GIT binary patch\n" \ + "delta 24\n" \ + "fcmX@ja+-zTF*v|6$k9DCSRvRyG(c}7zYP-rT_OhP\n" \ + "\n" \ + "delta 24\n" \ + "fcmX@ja+-zTF*v|6$k9DCSRvRyG(d49zYP-rT;T@W\n" \ + "\n" \ + "diff --git a/veal.txt b/veal.txt\n" \ + "index 94d2c01087f48213bd157222d54edfefd77c9bba..a7b066537e6be7109abfe4ff97b675d4e077da20 100644\n" \ + "GIT binary patch\n" \ + "delta 26\n" \ + "hcmX@kah!uI%+=9HA=p1OKyM?L03)OIW@$zpW&mXg25bNT\n" \ + "\n" \ + "delta 26\n" \ + "hcmX@kah!uI%+=9HA=p1OKyf3N03)N`W@$zpW&mU#22ub3\n" \ + "\n" + +#define DIFF_DELETE_FILE \ + "diff --git a/gravy.txt b/gravy.txt\n" \ + "deleted file mode 100644\n" \ + "index c4e6cca..0000000\n" \ + "--- a/gravy.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,8 +0,0 @@\n" \ + "-GRAVY SOUP.\n" \ + "-\n" \ + "-Get eight pounds of coarse lean beef--wash it clean and lay it in your\n" \ + "-pot, put in the same ingredients as for the shin soup, with the same\n" \ + "-quantity of water, and follow the process directed for that. Strain the\n" \ + "-soup through a sieve, and serve it up clear, with nothing more than\n" \ + "-toasted bread in it; two table-spoonsful of mushroom catsup will add a\n" \ + "-fine flavour to the soup.\n" + +#define DIFF_ADD_FILE \ + "diff --git a/newfile.txt b/newfile.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..6370543\n" \ + "--- /dev/null\n" \ + "+++ b/newfile.txt\n" \ + "@@ -0,0 +1,2 @@\n" \ + "+This is a new file!\n" \ + "+Added by a patch.\n" + +#define DIFF_EXECUTABLE_FILE \ + "diff --git a/beef.txt b/beef.txt\n" \ + "old mode 100644\n" \ + "new mode 100755\n" + +#define DIFF_MANY_CHANGES_ONE \ + "diff --git a/veal.txt b/veal.txt\n" \ + "index 94d2c01..c9d7d5d 100644\n" \ + "--- a/veal.txt\n" \ + "+++ b/veal.txt\n" \ + "@@ -1,2 +1,2 @@\n" \ + "-VEAL SOUP!\n" \ + "+VEAL SOUP\n" \ + " \n" \ + "@@ -4,3 +4,2 @@\n" \ + " spoonful of black pepper pounded, and two of salt, with two or three\n" \ + "-slices of lean ham; let it boil steadily two hours; skim it\n" \ + " occasionally, then put into it a shin of veal, let it boil two hours\n" \ + "@@ -8,3 +7,3 @@\n" \ + " should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ + "-of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ + "+OF FLOUR very nicely, and the yelks of two eggs beaten well, strain this\n" \ + " mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ + "@@ -12,2 +11,3 @@\n" \ + " boiled two or three minutes to take off the raw taste of the eggs. If\n" \ + "+Inserted line.\n" \ + " the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ + "@@ -15,3 +15,3 @@\n" \ + " in, first taking off their skins, by letting them stand a few minutes in\n" \ + "-hot water, when they may be easily peeled. When made in this way you\n" \ + "+Changed line.\n" \ + " must thicken it with the flour only. Any part of the veal may be used,\n" + +#define DIFF_MANY_CHANGES_TWO \ + "diff --git a/veal.txt b/veal.txt\n" \ + "index 94d2c01..6b943d6 100644\n" \ + "--- a/veal.txt\n" \ + "+++ b/veal.txt\n" \ + "@@ -1,2 +1,2 @@\n" \ + "-VEAL SOUP!\n" \ + "+VEAL SOUP!!!\n" \ + " \n" \ + "@@ -4,3 +4,2 @@\n" \ + " spoonful of black pepper pounded, and two of salt, with two or three\n" \ + "-slices of lean ham; let it boil steadily two hours; skim it\n" \ + " occasionally, then put into it a shin of veal, let it boil two hours\n" \ + "@@ -8,3 +7,3 @@\n" \ + " should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ + "-of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ + "+of flour very nicely, AND the yelks of two eggs beaten well, strain this\n" \ + " mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ + "@@ -12,2 +11,3 @@\n" \ + " boiled two or three minutes to take off the raw taste of the eggs. If\n" \ + "+New line.\n" \ + " the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ + "@@ -15,4 +15,5 @@\n" \ + " in, first taking off their skins, by letting them stand a few minutes in\n" \ + "-hot water, when they may be easily peeled. When made in this way you\n" \ + "-must thicken it with the flour only. Any part of the veal may be used,\n" \ + "-but the shin or knuckle is the nicest.\n" \ + "+HOT water, when they may be easily peeled. When made in this way you\n" \ + "+must THICKEN it with the flour only. Any part of the veal may be used,\n" \ + "+but the shin OR knuckle is the nicest.\n" \ + "+Another new line.\n" \ + +#define DIFF_RENAME_FILE \ + "diff --git a/beef.txt b/notbeef.txt\n" \ + "similarity index 100%\n" \ + "rename from beef.txt\n" \ + "rename to notbeef.txt\n" + +#define DIFF_RENAME_AND_MODIFY_FILE \ + "diff --git a/beef.txt b/notbeef.txt\n" \ + "similarity index 97%\n" \ + "rename from beef.txt\n" \ + "rename to notbeef.txt\n" \ + "index 68f6182..6fa1014 100644\n" \ + "--- a/beef.txt\n" \ + "+++ b/notbeef.txt\n" \ + "@@ -1,4 +1,4 @@\n" \ + "-BEEF SOUP.\n" \ + "+THIS IS NOT BEEF SOUP, IT HAS A NEW NAME.\n" \ + "\n" \ + " Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \ + " which must be taken away entirely, or the soup will be greasy. Wash the\n" + +#define DIFF_RENAME_A_TO_B_TO_C \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "deleted file mode 100644\n" \ + "index f516580..0000000\n" \ + "--- a/asparagus.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,10 +0,0 @@\n" \ + "-ASPARAGUS SOUP!\n" \ + "-\n" \ + "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ + "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ + "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ + "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ + "-pulp them through a sieve, and strain the water to it, which must be put\n" \ + "-back in the pot; put into it a chicken cut up, with the tops of\n" \ + "-asparagus which had been laid by, boil it until these last articles are\n" \ + "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ + "diff --git a/beef.txt b/beef.txt\n" \ + "index 68f6182..f516580 100644\n" \ + "--- a/beef.txt\n" \ + "+++ b/beef.txt\n" \ + "@@ -1,22 +1,10 @@\n" \ + "-BEEF SOUP.\n" \ + "+ASPARAGUS SOUP!\n" \ + "\n" \ + "-Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \ + "-which must be taken away entirely, or the soup will be greasy. Wash the\n" \ + "-meat clean and lay it in a pot, sprinkle over it one small\n" \ + "-table-spoonful of pounded black pepper, and two of salt; three onions\n" \ + "-the size of a hen's egg, cut small, six small carrots scraped and cut\n" \ + "-up, two small turnips pared and cut into dice; pour on three quarts of\n" \ + "-water, cover the pot close, and keep it gently and steadily boiling five\n" \ + "-hours, which will leave about three pints of clear soup; do not let the\n" \ + "-pot boil over, but take off the scum carefully, as it rises. When it has\n" \ + "-boiled four hours, put in a small bundle of thyme and parsley, and a\n" \ + "-pint of celery cut small, or a tea-spoonful of celery seed pounded.\n" \ + "-These latter ingredients would lose their delicate flavour if boiled too\n" \ + "-much. Just before you take it up, brown it in the following manner: put\n" \ + "-a small table-spoonful of nice brown sugar into an iron skillet, set it\n" \ + "-on the fire and stir it till it melts and looks very dark, pour into it\n" \ + "-a ladle full of the soup, a little at a time; stirring it all the while.\n" \ + "-Strain this browning and mix it well with the soup; take out the bundle\n" \ + "-of thyme and parsley, put the nicest pieces of meat in your tureen, and\n" \ + "-pour on the soup and vegetables; put in some toasted bread cut in dice,\n" \ + "-and serve it up.\n" \ + "+Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ + "+of the tops, and lay them in water, chop the stalks and put them on the\n" \ + "+fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ + "+add two quarts of water, boil them till the stalks are quite soft, then\n" \ + "+pulp them through a sieve, and strain the water to it, which must be put\n" \ + "+back in the pot; put into it a chicken cut up, with the tops of\n" \ + "+asparagus which had been laid by, boil it until these last articles are\n" \ + "+sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ + "diff --git a/notbeef.txt b/notbeef.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..68f6182\n" \ + "--- /dev/null\n" \ + "+++ b/notbeef.txt\n" \ + "@@ -0,0 +1,22 @@\n" \ + "+BEEF SOUP.\n" \ + "+\n" \ + "+Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \ + "+which must be taken away entirely, or the soup will be greasy. Wash the\n" \ + "+meat clean and lay it in a pot, sprinkle over it one small\n" \ + "+table-spoonful of pounded black pepper, and two of salt; three onions\n" \ + "+the size of a hen's egg, cut small, six small carrots scraped and cut\n" \ + "+up, two small turnips pared and cut into dice; pour on three quarts of\n" \ + "+water, cover the pot close, and keep it gently and steadily boiling five\n" \ + "+hours, which will leave about three pints of clear soup; do not let the\n" \ + "+pot boil over, but take off the scum carefully, as it rises. When it has\n" \ + "+boiled four hours, put in a small bundle of thyme and parsley, and a\n" \ + "+pint of celery cut small, or a tea-spoonful of celery seed pounded.\n" \ + "+These latter ingredients would lose their delicate flavour if boiled too\n" \ + "+much. Just before you take it up, brown it in the following manner: put\n" \ + "+a small table-spoonful of nice brown sugar into an iron skillet, set it\n" \ + "+on the fire and stir it till it melts and looks very dark, pour into it\n" \ + "+a ladle full of the soup, a little at a time; stirring it all the while.\n" \ + "+Strain this browning and mix it well with the soup; take out the bundle\n" \ + "+of thyme and parsley, put the nicest pieces of meat in your tureen, and\n" \ + "+pour on the soup and vegetables; put in some toasted bread cut in dice,\n" \ + "+and serve it up.\n" + +#define DIFF_RENAME_A_TO_B_TO_C_EXACT \ + "diff --git a/asparagus.txt b/beef.txt\n" \ + "similarity index 100%\n" \ + "rename from asparagus.txt\n" \ + "rename to beef.txt\n" \ + "diff --git a/beef.txt b/notbeef.txt\n" \ + "similarity index 100%\n" \ + "rename from beef.txt\n" \ + "rename to notbeef.txt\n" + +#define DIFF_RENAME_CIRCULAR \ + "diff --git a/asparagus.txt b/beef.txt\n" \ + "similarity index 100%\n" \ + "rename from asparagus.txt\n" \ + "rename to beef.txt\n" \ + "diff --git a/beef.txt b/notbeef.txt\n" \ + "similarity index 100%\n" \ + "rename from beef.txt\n" \ + "rename to asparagus.txt\n" + +#define DIFF_RENAME_2_TO_1 \ + "diff --git a/asparagus.txt b/2.txt\n" \ + "similarity index 100%\n" \ + "rename from asparagus.txt\n" \ + "rename to 2.txt\n" \ + "diff --git a/beef.txt b/2.txt\n" \ + "similarity index 100%\n" \ + "rename from beef.txt\n" \ + "rename to 2.txt\n" + +#define DIFF_RENAME_1_TO_2 \ + "diff --git a/asparagus.txt b/2.txt\n" \ + "similarity index 100%\n" \ + "rename from asparagus.txt\n" \ + "rename to 1.txt\n" \ + "diff --git a/asparagus.txt b/2.txt\n" \ + "similarity index 100%\n" \ + "rename from asparagus.txt\n" \ + "rename to 2.txt\n" + +#define DIFF_TWO_DELTAS_ONE_FILE \ + "diff --git a/beef.txt b/beef.txt\n" \ + "index 68f6182..235069d 100644\n" \ + "--- a/beef.txt\n" \ + "+++ b/beef.txt\n" \ + "@@ -1,4 +1,4 @@\n" \ + "-BEEF SOUP.\n" \ + "+BEEF SOUP!\n" \ + "\n" \ + " Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \ + " which must be taken away entirely, or the soup will be greasy. Wash the\n" \ + "diff --git a/beef.txt b/beef.txt\n" \ + "index 68f6182..e059eb5 100644\n" \ + "--- a/beef.txt\n" \ + "+++ b/beef.txt\n" \ + "@@ -19,4 +19,4 @@ a ladle full of the soup, a little at a time; stirring it all the while.\n" \ + " Strain this browning and mix it well with the soup; take out the bundle\n" \ + " of thyme and parsley, put the nicest pieces of meat in your tureen, and\n" \ + " pour on the soup and vegetables; put in some toasted bread cut in dice,\n" \ + "-and serve it up.\n" \ + "+and serve it up!\n" + +#define DIFF_TWO_DELTAS_ONE_NEW_FILE \ + "diff --git a/newfile.txt b/newfile.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..6434b13\n" \ + "--- /dev/null\n" \ + "+++ b/newfile.txt\n" \ + "@@ -0,0 +1 @@\n" \ + "+This is a new file.\n" \ + "diff --git a/newfile.txt b/newfile.txt\n" \ + "index 6434b13..08d4c44 100644\n" \ + "--- a/newfile.txt\n" \ + "+++ b/newfile.txt\n" \ + "@@ -1 +1,3 @@\n" \ + " This is a new file.\n" \ + "+\n" \ + "+This is another change to a new file.\n" + +#define DIFF_RENAME_AND_MODIFY_DELTAS \ + "diff --git a/veal.txt b/asdf.txt\n" \ + "similarity index 96%\n" \ + "rename from veal.txt\n" \ + "rename to asdf.txt\n" \ + "index 94d2c01..292cb60 100644\n" \ + "--- a/veal.txt\n" \ + "+++ b/asdf.txt\n" \ + "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ + " in, first taking off their skins, by letting them stand a few minutes in\n" \ + " hot water, when they may be easily peeled. When made in this way you\n" \ + " must thicken it with the flour only. Any part of the veal may be used,\n" \ + "-but the shin or knuckle is the nicest.\n" \ + "+but the shin or knuckle is the nicest!\n" \ + "diff --git a/asdf.txt b/asdf.txt\n" \ + "index 292cb60..61c686b 100644\n" \ + "--- a/asdf.txt\n" \ + "+++ b/asdf.txt\n" \ + "@@ -1,4 +1,4 @@\n" \ + "-VEAL SOUP!\n" \ + "+VEAL SOUP\n" \ + "\n" \ + " Put into a pot three quarts of water, three onions cut small, one\n" \ + " spoonful of black pepper pounded, and two of salt, with two or three\n" + +#define DIFF_RENAME_AFTER_MODIFY \ + "diff --git a/veal.txt b/veal.txt\n" \ + "index 292cb60..61c686b 100644\n" \ + "--- a/veal.txt\n" \ + "+++ b/veal.txt\n" \ + "@@ -1,4 +1,4 @@\n" \ + "-VEAL SOUP!\n" \ + "+VEAL SOUP\n" \ + "\n" \ + " Put into a pot three quarts of water, three onions cut small, one\n" \ + " spoonful of black pepper pounded, and two of salt, with two or three\n" \ + "diff --git a/veal.txt b/other.txt\n" \ + "similarity index 96%\n" \ + "rename from veal.txt\n" \ + "rename to other.txt\n" \ + "index 94d2c01..292cb60 100644\n" \ + "--- a/veal.txt\n" \ + "+++ b/other.txt\n" \ + "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ + " in, first taking off their skins, by letting them stand a few minutes in\n" \ + " hot water, when they may be easily peeled. When made in this way you\n" \ + " must thicken it with the flour only. Any part of the veal may be used,\n" \ + "-but the shin or knuckle is the nicest.\n" \ + "+but the shin or knuckle is the nicest!\n" + +#define DIFF_RENAME_AFTER_MODIFY_TARGET_PATH \ + "diff --git a/beef.txt b/beef.txt\n" \ + "index 292cb60..61c686b 100644\n" \ + "--- a/beef.txt\n" \ + "+++ b/beef.txt\n" \ + "@@ -1,4 +1,4 @@\n" \ + "-VEAL SOUP!\n" \ + "+VEAL SOUP\n" \ + "\n" \ + " Put into a pot three quarts of water, three onions cut small, one\n" \ + " spoonful of black pepper pounded, and two of salt, with two or three\n" \ + "diff --git a/veal.txt b/beef.txt\n" \ + "similarity index 96%\n" \ + "rename from veal.txt\n" \ + "rename to beef.txt\n" \ + "index 94d2c01..292cb60 100644\n" \ + "--- a/veal.txt\n" \ + "+++ b/beef.txt\n" \ + "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ + " in, first taking off their skins, by letting them stand a few minutes in\n" \ + " hot water, when they may be easily peeled. When made in this way you\n" \ + " must thicken it with the flour only. Any part of the veal may be used,\n" \ + "-but the shin or knuckle is the nicest.\n" \ + "+but the shin or knuckle is the nicest!\n" + +#define DIFF_RENAME_AND_MODIFY_SOURCE_PATH \ + "diff --git a/veal.txt b/asdf.txt\n" \ + "similarity index 96%\n" \ + "rename from veal.txt\n" \ + "rename to asdf.txt\n" \ + "index 94d2c01..292cb60 100644\n" \ + "--- a/veal.txt\n" \ + "+++ b/asdf.txt\n" \ + "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ + " in, first taking off their skins, by letting them stand a few minutes in\n" \ + " hot water, when they may be easily peeled. When made in this way you\n" \ + " must thicken it with the flour only. Any part of the veal may be used,\n" \ + "-but the shin or knuckle is the nicest.\n" \ + "+but the shin or knuckle is the nicest!\n" \ + "diff --git a/veal.txt b/veal.txt\n" \ + "index 292cb60..61c686b 100644\n" \ + "--- a/veal.txt\n" \ + "+++ b/veal.txt\n" \ + "@@ -1,4 +1,4 @@\n" \ + "-VEAL SOUP!\n" \ + "+VEAL SOUP\n" \ + "\n" \ + " Put into a pot three quarts of water, three onions cut small, one\n" \ + " spoonful of black pepper pounded, and two of salt, with two or three\n" + +#define DIFF_DELETE_AND_READD_FILE \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "deleted file mode 100644\n" \ + "index f516580..0000000\n" \ + "--- a/asparagus.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,10 +0,0 @@\n" \ + "-ASPARAGUS SOUP!\n" \ + "-\n" \ + "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ + "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ + "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ + "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ + "-pulp them through a sieve, and strain the water to it, which must be put\n" \ + "-back in the pot; put into it a chicken cut up, with the tops of\n" \ + "-asparagus which had been laid by, boil it until these last articles are\n" \ + "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..2dc7f8b\n" \ + "--- /dev/null\n" \ + "+++ b/asparagus.txt\n" \ + "@@ -0,0 +1 @@\n" \ + "+New file.\n" \ + +#define DIFF_REMOVE_FILE_TWICE \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "deleted file mode 100644\n" \ + "index f516580..0000000\n" \ + "--- a/asparagus.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,10 +0,0 @@\n" \ + "-ASPARAGUS SOUP!\n" \ + "-\n" \ + "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ + "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ + "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ + "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ + "-pulp them through a sieve, and strain the water to it, which must be put\n" \ + "-back in the pot; put into it a chicken cut up, with the tops of\n" \ + "-asparagus which had been laid by, boil it until these last articles are\n" \ + "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "deleted file mode 100644\n" \ + "index f516580..0000000\n" \ + "--- a/asparagus.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,10 +0,0 @@\n" \ + "-ASPARAGUS SOUP!\n" \ + "-\n" \ + "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ + "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ + "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ + "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ + "-pulp them through a sieve, and strain the water to it, which must be put\n" \ + "-back in the pot; put into it a chicken cut up, with the tops of\n" \ + "-asparagus which had been laid by, boil it until these last articles are\n" \ + "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" + +#define DIFF_ADD_INVALID_FILENAME \ + "diff --git a/.git/hello_world.txt b/.git/hello_world.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..f75ba05\n" \ + "--- /dev/null\n" \ + "+++ b/.git/hello_world.txt\n" \ + "@@ -0,0 +1 @@\n" \ + "+Hello, world.\n" + +void validate_apply_workdir( + git_repository *repo, + struct merge_index_entry *workdir_entries, + size_t workdir_cnt); + +void validate_apply_index( + git_repository *repo, + struct merge_index_entry *index_entries, + size_t index_cnt); + +void validate_index_unchanged(git_repository *repo); +void validate_workdir_unchanged(git_repository *repo); diff --git a/tests/libgit2/apply/both.c b/tests/libgit2/apply/both.c new file mode 100644 index 000000000..108963270 --- /dev/null +++ b/tests/libgit2/apply/both.c @@ -0,0 +1,747 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_apply_both__initialize(void) +{ + git_oid oid; + git_commit *commit; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); +} + +void test_apply_both__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_apply_both__generated_diff(void) +{ + git_oid a_oid, b_oid; + git_commit *a_commit, *b_commit; + git_tree *a_tree, *b_tree; + git_diff *diff; + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + + struct merge_index_entry both_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f"); + cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + cl_git_pass(git_commit_tree(&b_tree, b_commit)); + + cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &diff_opts)); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); + git_tree_free(a_tree); + git_tree_free(b_tree); + git_commit_free(a_commit); + git_commit_free(b_commit); +} + +void test_apply_both__parsed_diff(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__removes_file(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_FILE, + strlen(DIFF_DELETE_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__adds_file(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__application_failure_leaves_index_unmodified(void) +{ + git_diff *diff; + git_index *index; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + /* mutate the index */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_remove(index, "veal.txt", 0)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_both__index_must_match_workdir(void) +{ + git_diff *diff; + git_index *index; + git_index_entry idx_entry; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + /* + * Append a line to the end of the file in both the index and the + * working directory. Although the appended line would allow for + * patch application in each, the line appended is different in + * each, so the application should not be allowed. + */ + cl_git_append2file("merge-recursive/asparagus.txt", + "This is a modification.\n"); + + cl_git_pass(git_repository_index(&index, repo)); + + memset(&idx_entry, 0, sizeof(git_index_entry)); + idx_entry.mode = 0100644; + idx_entry.path = "asparagus.txt"; + cl_git_pass(git_oid_fromstr(&idx_entry.id, "06d3fefb8726ab1099acc76e02dfb85e034b2538")); + cl_git_pass(git_index_add(index, &idx_entry)); + + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + git_diff_free(diff); +} + +void test_apply_both__index_mode_must_match_workdir(void) +{ + git_diff *diff; + + if (!cl_is_chmod_supported()) + clar__skip(); + + /* Set a file in the working directory executable. */ + cl_must_pass(p_chmod("merge-recursive/asparagus.txt", 0755)); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_MODIFY_TWO_FILES, + strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + git_diff_free(diff); +} + +void test_apply_both__application_failure_leaves_workdir_unmodified(void) +{ + git_diff *diff; + git_index *index; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "8684724651336001c5dbce74bed6736d2443958d", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + /* mutate the workdir */ + cl_git_rewritefile("merge-recursive/veal.txt", + "This is a modification.\n"); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, "veal.txt")); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__keeps_nonconflicting_changes(void) +{ + git_diff *diff; + git_index *index; + git_index_entry idx_entry; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "beef.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + struct merge_index_entry workdir_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "f75ba05f340c51065cbea2e1fdbfe5fe13144c97", 0, "gravy.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + /* mutate the index */ + cl_git_pass(git_repository_index(&index, repo)); + + memset(&idx_entry, 0, sizeof(git_index_entry)); + idx_entry.mode = 0100644; + idx_entry.path = "beef.txt"; + cl_git_pass(git_oid_fromstr(&idx_entry.id, "898d12687fb35be271c27c795a6b32c8b51da79e")); + cl_git_pass(git_index_add(index, &idx_entry)); + + cl_git_pass(git_index_remove(index, "bouilli.txt", 0)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + /* and mutate the working directory */ + cl_git_rmfile("merge-recursive/oyster.txt"); + cl_git_rewritefile("merge-recursive/gravy.txt", "Hello, world.\n"); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__can_apply_nonconflicting_file_changes(void) +{ + git_diff *diff; + git_index *index; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry both_expected[] = { + { 0100644, "f8a701c8a1a22c1729ee50faff1111f2d64f96fc", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + /* + * Replace the workdir file with a version that is different than + * HEAD but such that the patch still applies cleanly. This item + * has a new line appended. + */ + cl_git_append2file("merge-recursive/asparagus.txt", + "This line is added in the index and the workdir.\n"); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, "asparagus.txt")); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__honors_crlf_attributes(void) +{ + git_diff *diff; + git_oid oid; + git_commit *commit; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + struct merge_index_entry workdir_expected[] = { + { 0100644, "176a458f94e0ea5272ce67c36bf30b6be9caf623", 0, ".gitattributes" }, + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_mkfile("merge-recursive/.gitattributes", "* text=auto\n"); + + cl_git_rmfile("merge-recursive/asparagus.txt"); + cl_git_rmfile("merge-recursive/veal.txt"); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__rename(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "notbeef.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_FILE, + strlen(DIFF_RENAME_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__rename_and_modify(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "6fa10147f00fe1fab1d5e835529a9dad53db8552", 0, "notbeef.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AND_MODIFY_FILE, + strlen(DIFF_RENAME_AND_MODIFY_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__rename_a_to_b_to_c(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "notbeef.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_A_TO_B_TO_C, + strlen(DIFF_RENAME_A_TO_B_TO_C))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__rename_a_to_b_to_c_exact(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "notbeef.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_A_TO_B_TO_C_EXACT, + strlen(DIFF_RENAME_A_TO_B_TO_C_EXACT))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__rename_circular(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "asparagus.txt" }, + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_CIRCULAR, + strlen(DIFF_RENAME_CIRCULAR))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__rename_2_to_1(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "2.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_2_TO_1, + strlen(DIFF_RENAME_2_TO_1))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__rename_1_to_2(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "1.txt" }, + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "2.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_1_TO_2, + strlen(DIFF_RENAME_1_TO_2))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__two_deltas_one_file(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "0a9fd4415635e72573f0f6b5e68084cfe18f5075", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_TWO_DELTAS_ONE_FILE, + strlen(DIFF_TWO_DELTAS_ONE_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__two_deltas_one_new_file(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "08d4c445cf0078f3d9b604b82f32f4d87e083325", 0, "newfile.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_TWO_DELTAS_ONE_NEW_FILE, + strlen(DIFF_TWO_DELTAS_ONE_NEW_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__rename_and_modify_deltas(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "61c686bed39684eee8a2757ceb1291004a21333f", 0, "asdf.txt" }, + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AND_MODIFY_DELTAS, + strlen(DIFF_RENAME_AND_MODIFY_DELTAS))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__rename_delta_after_modify_delta(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "292cb60ce5e25c337c5b6e12957bbbfe1be4bf49", 0, "other.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "c8c120f466591bbe3b8867361d5ec3cdd9fda756", 0, "veal.txt" } + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AFTER_MODIFY, + strlen(DIFF_RENAME_AFTER_MODIFY))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__cant_rename_after_modify_nonexistent_target_path(void) +{ + git_diff *diff; + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AFTER_MODIFY_TARGET_PATH, + strlen(DIFF_RENAME_AFTER_MODIFY_TARGET_PATH))); + cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + git_diff_free(diff); +} + +void test_apply_both__cant_modify_source_path_after_rename(void) +{ + git_diff *diff; + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AND_MODIFY_SOURCE_PATH, + strlen(DIFF_RENAME_AND_MODIFY_SOURCE_PATH))); + cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + git_diff_free(diff); +} + +void test_apply_both__readd_deleted_file(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "2dc7f8b24ba27f3888368bd180df03ff4c6c6fab", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" } + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_AND_READD_FILE, + strlen(DIFF_DELETE_AND_READD_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, both_expected, both_expected_cnt); + validate_apply_workdir(repo, both_expected, both_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__cant_remove_file_twice(void) +{ + git_diff *diff; + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_REMOVE_FILE_TWICE, + strlen(DIFF_REMOVE_FILE_TWICE))); + cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + git_diff_free(diff); +} + +void test_apply_both__cant_add_invalid_filename(void) +{ + git_diff *diff; + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_ADD_INVALID_FILENAME, + strlen(DIFF_ADD_INVALID_FILENAME))); + cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + git_diff_free(diff); +} diff --git a/tests/libgit2/apply/callbacks.c b/tests/libgit2/apply/callbacks.c new file mode 100644 index 000000000..1b759dc9b --- /dev/null +++ b/tests/libgit2/apply/callbacks.c @@ -0,0 +1,128 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_apply_callbacks__initialize(void) +{ + git_oid oid; + git_commit *commit; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); +} + +void test_apply_callbacks__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int delta_abort_cb(const git_diff_delta *delta, void *payload) +{ + GIT_UNUSED(payload); + + if (!strcmp(delta->old_file.path, "veal.txt")) + return -99; + + return 0; +} + +void test_apply_callbacks__delta_aborts(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + opts.delta_cb = delta_abort_cb; + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_fail_with(-99, + git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); + + validate_index_unchanged(repo); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +static int delta_skip_cb(const git_diff_delta *delta, void *payload) +{ + GIT_UNUSED(payload); + + if (!strcmp(delta->old_file.path, "asparagus.txt")) + return 1; + + return 0; +} + +void test_apply_callbacks__delta_can_skip(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + opts.delta_cb = delta_skip_cb; + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +static int hunk_skip_odds_cb(const git_diff_hunk *hunk, void *payload) +{ + int *count = (int *)payload; + GIT_UNUSED(hunk); + + return ((*count)++ % 2 == 1); +} + +void test_apply_callbacks__hunk_can_skip(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + int count = 0; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "06f751b6ba4f017ddbf4248015768300268e092a", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + opts.hunk_cb = hunk_skip_odds_cb; + opts.payload = &count; + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MANY_CHANGES_ONE, strlen(DIFF_MANY_CHANGES_ONE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} diff --git a/tests/libgit2/apply/check.c b/tests/libgit2/apply/check.c new file mode 100644 index 000000000..9e42365ed --- /dev/null +++ b/tests/libgit2/apply/check.c @@ -0,0 +1,121 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_apply_check__initialize(void) +{ + git_oid oid; + git_commit *commit; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); +} + +void test_apply_check__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_apply_check__generate_diff(void) +{ + git_oid a_oid, b_oid; + git_commit *a_commit, *b_commit; + git_tree *a_tree, *b_tree; + git_diff *diff; + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + cl_git_pass(git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707")); + cl_git_pass(git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f")); + cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + cl_git_pass(git_commit_tree(&b_tree, b_commit)); + + opts.flags |= GIT_APPLY_CHECK; + cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &diff_opts)); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, &opts)); + + validate_index_unchanged(repo); + validate_workdir_unchanged(repo); + + git_diff_free(diff); + git_tree_free(a_tree); + git_tree_free(b_tree); + git_commit_free(a_commit); + git_commit_free(b_commit); +} + +void test_apply_check__parsed_diff(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + opts.flags |= GIT_APPLY_CHECK; + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); + + validate_index_unchanged(repo); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_check__binary(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + opts.flags |= GIT_APPLY_CHECK; + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES_BINARY, + strlen(DIFF_MODIFY_TWO_FILES_BINARY))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); + + validate_index_unchanged(repo); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_check__does_not_apply(void) +{ + git_diff *diff; + git_index *index; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + /* mutate the index */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_remove(index, "veal.txt", 0)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + opts.flags |= GIT_APPLY_CHECK; + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + + git_diff_free(diff); +} diff --git a/tests/libgit2/apply/fromdiff.c b/tests/libgit2/apply/fromdiff.c new file mode 100644 index 000000000..9da6ac923 --- /dev/null +++ b/tests/libgit2/apply/fromdiff.c @@ -0,0 +1,378 @@ +#include "clar_libgit2.h" +#include "git2/sys/repository.h" + +#include "apply.h" +#include "repository.h" + +#include "../patch/patch_common.h" + +static git_repository *repo = NULL; +static git_diff_options binary_opts = GIT_DIFF_OPTIONS_INIT; + +void test_apply_fromdiff__initialize(void) +{ + repo = cl_git_sandbox_init("renames"); + + binary_opts.flags |= GIT_DIFF_SHOW_BINARY; +} + +void test_apply_fromdiff__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int apply_gitbuf( + const git_str *old, + const char *oldname, + const git_str *new, + const char *newname, + const char *patch_expected, + const git_diff_options *diff_opts) +{ + git_patch *patch; + git_str result = GIT_STR_INIT; + git_buf patchbuf = GIT_BUF_INIT; + char *filename; + unsigned int mode; + int error; + + cl_git_pass(git_patch_from_buffers(&patch, + old ? old->ptr : NULL, old ? old->size : 0, + oldname, + new ? new->ptr : NULL, new ? new->size : 0, + newname, + diff_opts)); + + if (patch_expected) { + cl_git_pass(git_patch_to_buf(&patchbuf, patch)); + cl_assert_equal_s(patch_expected, patchbuf.ptr); + } + + error = git_apply__patch(&result, &filename, &mode, old ? old->ptr : NULL, old ? old->size : 0, patch, NULL); + + if (error == 0 && new == NULL) { + cl_assert_equal_i(0, result.size); + cl_assert_equal_p(NULL, filename); + cl_assert_equal_i(0, mode); + } + else if (error == 0) { + cl_assert_equal_s(new->ptr, result.ptr); + cl_assert_equal_s(newname ? newname : oldname, filename); + cl_assert_equal_i(0100644, mode); + } + + git__free(filename); + git_str_dispose(&result); + git_buf_dispose(&patchbuf); + git_patch_free(patch); + + return error; +} + +static int apply_buf( + const char *old, + const char *oldname, + const char *new, + const char *newname, + const char *patch_expected, + const git_diff_options *diff_opts) +{ + git_str o = GIT_STR_INIT, n = GIT_STR_INIT, + *optr = NULL, *nptr = NULL; + + if (old) { + o.ptr = (char *)old; + o.size = strlen(old); + optr = &o; + } + + if (new) { + n.ptr = (char *)new; + n.size = strlen(new); + nptr = &n; + } + + return apply_gitbuf(optr, oldname, nptr, newname, patch_expected, diff_opts); +} + +void test_apply_fromdiff__change_middle(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_MIDDLE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_MIDDLE, NULL)); +} + +void test_apply_fromdiff__change_middle_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_MIDDLE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__change_firstline(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_FIRSTLINE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, NULL)); +} + +void test_apply_fromdiff__lastline(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_LASTLINE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL)); +} + +void test_apply_fromdiff__change_middle_and_lastline_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_MIDDLE_AND_LASTLINE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_AND_LASTLINE_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__prepend(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND, "file.txt", + PATCH_ORIGINAL_TO_PREPEND, NULL)); +} + +void test_apply_fromdiff__prepend_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND, "file.txt", + PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__prepend_and_change(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE, NULL)); +} + +void test_apply_fromdiff__prepend_and_change_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__delete_and_change(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + PATCH_ORIGINAL_TO_DELETE_AND_CHANGE, NULL)); +} + +void test_apply_fromdiff__delete_and_change_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + PATCH_ORIGINAL_TO_DELETE_AND_CHANGE_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__delete_firstline(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_FIRSTLINE, "file.txt", + PATCH_ORIGINAL_TO_DELETE_FIRSTLINE, NULL)); +} + +void test_apply_fromdiff__append(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_APPEND, "file.txt", + PATCH_ORIGINAL_TO_APPEND, NULL)); +} + +void test_apply_fromdiff__append_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_APPEND, "file.txt", + PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__prepend_and_append(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_APPEND, "file.txt", + PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, NULL)); +} + +void test_apply_fromdiff__to_empty_file(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + "", NULL, + PATCH_ORIGINAL_TO_EMPTY_FILE, NULL)); +} + +void test_apply_fromdiff__from_empty_file(void) +{ + cl_git_pass(apply_buf( + "", NULL, + FILE_ORIGINAL, "file.txt", + PATCH_EMPTY_FILE_TO_ORIGINAL, NULL)); +} + +void test_apply_fromdiff__add(void) +{ + cl_git_pass(apply_buf( + NULL, NULL, + FILE_ORIGINAL, "file.txt", + PATCH_ADD_ORIGINAL, NULL)); +} + +void test_apply_fromdiff__delete(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + NULL, NULL, + PATCH_DELETE_ORIGINAL, NULL)); +} + +void test_apply_fromdiff__no_change(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_ORIGINAL, "file.txt", + "", NULL)); +} + +void test_apply_fromdiff__binary_add(void) +{ + git_str newfile = GIT_STR_INIT; + + newfile.ptr = FILE_BINARY_DELTA_MODIFIED; + newfile.size = FILE_BINARY_DELTA_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + NULL, NULL, + &newfile, "binary.bin", + NULL, &binary_opts)); +} + +void test_apply_fromdiff__binary_no_change(void) +{ + git_str original = GIT_STR_INIT; + + original.ptr = FILE_BINARY_DELTA_ORIGINAL; + original.size = FILE_BINARY_DELTA_ORIGINAL_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + &original, "binary.bin", + "", &binary_opts)); +} + +void test_apply_fromdiff__binary_change_delta(void) +{ + git_str original = GIT_STR_INIT, modified = GIT_STR_INIT; + + original.ptr = FILE_BINARY_DELTA_ORIGINAL; + original.size = FILE_BINARY_DELTA_ORIGINAL_LEN; + + modified.ptr = FILE_BINARY_DELTA_MODIFIED; + modified.size = FILE_BINARY_DELTA_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + &modified, "binary.bin", + NULL, &binary_opts)); +} + +void test_apply_fromdiff__binary_change_literal(void) +{ + git_str original = GIT_STR_INIT, modified = GIT_STR_INIT; + + original.ptr = FILE_BINARY_LITERAL_ORIGINAL; + original.size = FILE_BINARY_LITERAL_ORIGINAL_LEN; + + modified.ptr = FILE_BINARY_LITERAL_MODIFIED; + modified.size = FILE_BINARY_LITERAL_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + &modified, "binary.bin", + NULL, &binary_opts)); +} + +void test_apply_fromdiff__binary_delete(void) +{ + git_str original = GIT_STR_INIT; + + original.ptr = FILE_BINARY_DELTA_MODIFIED; + original.size = FILE_BINARY_DELTA_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + NULL, NULL, + NULL, &binary_opts)); +} + +void test_apply_fromdiff__patching_correctly_truncates_source(void) +{ + git_str original = GIT_STR_INIT, patched = GIT_STR_INIT; + git_patch *patch; + unsigned int mode; + char *path; + + cl_git_pass(git_patch_from_buffers(&patch, + "foo\nbar", 7, "file.txt", + "foo\nfoo", 7, "file.txt", NULL)); + + /* + * Previously, we would fail to correctly truncate the source buffer if + * the source has more than one line and ends with a non-newline + * character. In the following call, we thus truncate the source string + * in the middle of the second line. Without the bug fixed, we would + * successfully apply the patch to the source and return success. With + * the overflow being fixed, we should return an error. + */ + cl_git_fail_with(GIT_EAPPLYFAIL, + git_apply__patch(&patched, &path, &mode, + "foo\nbar\n", 5, patch, NULL)); + + /* Verify that the patch succeeds if we do not truncate */ + cl_git_pass(git_apply__patch(&patched, &path, &mode, + "foo\nbar\n", 7, patch, NULL)); + + git_str_dispose(&original); + git_str_dispose(&patched); + git_patch_free(patch); + git__free(path); +} diff --git a/tests/libgit2/apply/fromfile.c b/tests/libgit2/apply/fromfile.c new file mode 100644 index 000000000..8cde62392 --- /dev/null +++ b/tests/libgit2/apply/fromfile.c @@ -0,0 +1,449 @@ +#include "clar_libgit2.h" +#include "git2/sys/repository.h" + +#include "apply.h" +#include "patch.h" +#include "patch_parse.h" +#include "repository.h" + +#include "../patch/patch_common.h" + +static git_repository *repo = NULL; + +void test_apply_fromfile__initialize(void) +{ + repo = cl_git_sandbox_init("renames"); +} + +void test_apply_fromfile__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int apply_patchfile( + const char *old, + size_t old_len, + const char *new, + size_t new_len, + const char *patchfile, + const char *filename_expected, + unsigned int mode_expected) +{ + git_patch *patch; + git_str result = GIT_STR_INIT; + git_str patchbuf = GIT_STR_INIT; + char *filename; + unsigned int mode; + int error; + + cl_git_pass(git_patch_from_buffer(&patch, patchfile, strlen(patchfile), NULL)); + + error = git_apply__patch(&result, &filename, &mode, old, old_len, patch, NULL); + + if (error == 0) { + cl_assert_equal_i(new_len, result.size); + if (new_len) + cl_assert(memcmp(new, result.ptr, new_len) == 0); + + cl_assert_equal_s(filename_expected, filename); + cl_assert_equal_i(mode_expected, mode); + } + + git__free(filename); + git_str_dispose(&result); + git_str_dispose(&patchbuf); + git_patch_free(patch); + + return error; +} + +static int validate_and_apply_patchfile( + const char *old, + size_t old_len, + const char *new, + size_t new_len, + const char *patchfile, + const git_diff_options *diff_opts, + const char *filename_expected, + unsigned int mode_expected) +{ + git_patch *patch_fromdiff; + git_buf validated = GIT_BUF_INIT; + int error; + + cl_git_pass(git_patch_from_buffers(&patch_fromdiff, + old, old_len, "file.txt", + new, new_len, "file.txt", + diff_opts)); + cl_git_pass(git_patch_to_buf(&validated, patch_fromdiff)); + + cl_assert_equal_s(patchfile, validated.ptr); + + error = apply_patchfile(old, old_len, new, new_len, patchfile, filename_expected, mode_expected); + + git_buf_dispose(&validated); + git_patch_free(patch_fromdiff); + + return error; +} + +void test_apply_fromfile__change_middle(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_ORIGINAL_TO_CHANGE_MIDDLE, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, + &diff_opts, "file.txt", 0100644)); +} + + +void test_apply_fromfile__change_firstline(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_FIRSTLINE, strlen(FILE_CHANGE_FIRSTLINE), + PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__lastline(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_LASTLINE, strlen(FILE_CHANGE_LASTLINE), + PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_shrink(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE_SHRINK, strlen(FILE_CHANGE_MIDDLE_SHRINK), + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_SHRINK, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_shrink_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE_SHRINK, strlen(FILE_CHANGE_MIDDLE_SHRINK), + PATCH_ORIGINAL_TO_MIDDLE_SHRINK_NOCONTEXT, &diff_opts, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_grow(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE_GROW, strlen(FILE_CHANGE_MIDDLE_GROW), + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_GROW, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_grow_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE_GROW, strlen(FILE_CHANGE_MIDDLE_GROW), + PATCH_ORIGINAL_TO_MIDDLE_GROW_NOCONTEXT, &diff_opts, + "file.txt", 0100644)); +} + +void test_apply_fromfile__prepend(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_PREPEND, strlen(FILE_PREPEND), + PATCH_ORIGINAL_TO_PREPEND, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__prepend_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_PREPEND, strlen(FILE_PREPEND), + PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts, + "file.txt", 0100644)); +} + +void test_apply_fromfile__append(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_APPEND, strlen(FILE_APPEND), + PATCH_ORIGINAL_TO_APPEND, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__append_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_APPEND, strlen(FILE_APPEND), + PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, &diff_opts, + "file.txt", 0100644)); +} + +void test_apply_fromfile__prepend_and_append(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_PREPEND_AND_APPEND, strlen(FILE_PREPEND_AND_APPEND), + PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__to_empty_file(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + "", 0, + PATCH_ORIGINAL_TO_EMPTY_FILE, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__from_empty_file(void) +{ + cl_git_pass(validate_and_apply_patchfile( + "", 0, + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + PATCH_EMPTY_FILE_TO_ORIGINAL, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__add(void) +{ + cl_git_pass(validate_and_apply_patchfile( + NULL, 0, + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + PATCH_ADD_ORIGINAL, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__delete(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + NULL, 0, + PATCH_DELETE_ORIGINAL, NULL, NULL, 0)); +} + + +void test_apply_fromfile__rename_exact(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + PATCH_RENAME_EXACT, "newfile.txt", 0100644)); +} + +void test_apply_fromfile__rename_similar(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_RENAME_SIMILAR, "newfile.txt", 0100644)); +} + +void test_apply_fromfile__rename_similar_quotedname(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_RENAME_SIMILAR_QUOTEDNAME, "foo\"bar.txt", 0100644)); +} + +void test_apply_fromfile__modechange(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + PATCH_MODECHANGE_UNCHANGED, "file.txt", 0100755)); +} + +void test_apply_fromfile__modechange_with_modification(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_MODECHANGE_MODIFIED, "file.txt", 0100755)); +} + +void test_apply_fromfile__noisy(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_NOISY, "file.txt", 0100644)); +} + +void test_apply_fromfile__noisy_nocontext(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_NOISY_NOCONTEXT, "file.txt", 0100644)); +} + +void test_apply_fromfile__fail_truncated_1(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_1, + strlen(PATCH_TRUNCATED_1), NULL)); +} + +void test_apply_fromfile__fail_truncated_2(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_2, + strlen(PATCH_TRUNCATED_2), NULL)); +} + +void test_apply_fromfile__fail_truncated_3(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_3, + strlen(PATCH_TRUNCATED_3), NULL)); +} + +void test_apply_fromfile__fail_corrupt_githeader(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_CORRUPT_GIT_HEADER, + strlen(PATCH_CORRUPT_GIT_HEADER), NULL)); +} + +void test_apply_fromfile__empty_context(void) +{ + cl_git_pass(apply_patchfile( + FILE_EMPTY_CONTEXT_ORIGINAL, strlen(FILE_EMPTY_CONTEXT_ORIGINAL), + FILE_EMPTY_CONTEXT_MODIFIED, strlen(FILE_EMPTY_CONTEXT_MODIFIED), + PATCH_EMPTY_CONTEXT, + "file.txt", 0100644)); +} + +void test_apply_fromfile__append_no_nl(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_APPEND_NO_NL, strlen(FILE_APPEND_NO_NL), + PATCH_APPEND_NO_NL, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__fail_missing_new_file(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_NEW_FILE, + strlen(PATCH_CORRUPT_MISSING_NEW_FILE), NULL)); +} + +void test_apply_fromfile__fail_missing_old_file(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_OLD_FILE, + strlen(PATCH_CORRUPT_MISSING_OLD_FILE), NULL)); +} + +void test_apply_fromfile__fail_no_changes(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, + PATCH_CORRUPT_NO_CHANGES, + strlen(PATCH_CORRUPT_NO_CHANGES), NULL)); +} + +void test_apply_fromfile__fail_missing_hunk_header(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_HUNK_HEADER, + strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL)); +} + +void test_apply_fromfile__fail_not_a_patch(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_NOT_A_PATCH, + strlen(PATCH_NOT_A_PATCH), NULL)); +} + +void test_apply_fromfile__binary_add(void) +{ + cl_git_pass(apply_patchfile( + NULL, 0, + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + PATCH_BINARY_ADD, "binary.bin", 0100644)); +} + +void test_apply_fromfile__binary_change_delta(void) +{ + cl_git_pass(apply_patchfile( + FILE_BINARY_DELTA_ORIGINAL, FILE_BINARY_DELTA_ORIGINAL_LEN, + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + PATCH_BINARY_DELTA, "binary.bin", 0100644)); +} + +void test_apply_fromfile__binary_change_literal(void) +{ + cl_git_pass(apply_patchfile( + FILE_BINARY_LITERAL_ORIGINAL, FILE_BINARY_LITERAL_ORIGINAL_LEN, + FILE_BINARY_LITERAL_MODIFIED, FILE_BINARY_LITERAL_MODIFIED_LEN, + PATCH_BINARY_LITERAL, "binary.bin", 0100644)); +} + +void test_apply_fromfile__binary_delete(void) +{ + cl_git_pass(apply_patchfile( + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + NULL, 0, + PATCH_BINARY_DELETE, NULL, 0)); +} + +void test_apply_fromfile__binary_change_does_not_apply(void) +{ + /* try to apply patch backwards, ensure it does not apply */ + cl_git_fail(apply_patchfile( + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + FILE_BINARY_DELTA_ORIGINAL, FILE_BINARY_DELTA_ORIGINAL_LEN, + PATCH_BINARY_DELTA, "binary.bin", 0100644)); +} + +void test_apply_fromfile__binary_change_must_be_reversible(void) +{ + cl_git_fail(apply_patchfile( + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + NULL, 0, + PATCH_BINARY_NOT_REVERSIBLE, NULL, 0)); +} + +void test_apply_fromfile__empty_file_not_allowed(void) +{ + git_patch *patch; + + cl_git_fail(git_patch_from_buffer(&patch, "", 0, NULL)); + cl_git_fail(git_patch_from_buffer(&patch, NULL, 0, NULL)); +} diff --git a/tests/libgit2/apply/index.c b/tests/libgit2/apply/index.c new file mode 100644 index 000000000..9c9094cce --- /dev/null +++ b/tests/libgit2/apply/index.c @@ -0,0 +1,321 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_apply_index__initialize(void) +{ + git_oid oid; + git_commit *commit; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); +} + +void test_apply_index__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_apply_index__generate_diff(void) +{ + git_oid a_oid, b_oid; + git_commit *a_commit, *b_commit; + git_tree *a_tree, *b_tree; + git_diff *diff; + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + + struct merge_index_entry index_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f"); + cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + cl_git_pass(git_commit_tree(&b_tree, b_commit)); + + cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &diff_opts)); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); + git_tree_free(a_tree); + git_tree_free(b_tree); + git_commit_free(a_commit); + git_commit_free(b_commit); +} + +void test_apply_index__parsed_diff(void) +{ + git_diff *diff; + + struct merge_index_entry index_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_index__removes_file(void) +{ + git_diff *diff; + + struct merge_index_entry index_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_FILE, + strlen(DIFF_DELETE_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_index__adds_file(void) +{ + git_diff *diff; + + struct merge_index_entry index_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_index__modified_workdir_with_unmodified_index_is_ok(void) +{ + git_diff *diff; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + struct merge_index_entry workdir_expected[] = { + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "f75ba05f340c51065cbea2e1fdbfe5fe13144c97", 0, "veal.txt" } + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + /* mutate the workdir and leave the index matching HEAD */ + cl_git_rmfile("merge-recursive/asparagus.txt"); + cl_git_rewritefile("merge-recursive/veal.txt", "Hello, world.\n"); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_index__application_failure_leaves_index_unmodified(void) +{ + git_diff *diff; + git_index *index; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + /* mutate the index */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_remove(index, "veal.txt", 0)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_index__keeps_nonconflicting_changes(void) +{ + git_diff *diff; + git_index *index; + git_index_entry idx_entry; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "beef.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + /* mutate the index */ + cl_git_pass(git_repository_index(&index, repo)); + + memset(&idx_entry, 0, sizeof(git_index_entry)); + idx_entry.mode = 0100644; + idx_entry.path = "beef.txt"; + cl_git_pass(git_oid_fromstr(&idx_entry.id, "898d12687fb35be271c27c795a6b32c8b51da79e")); + cl_git_pass(git_index_add(index, &idx_entry)); + + cl_git_pass(git_index_remove(index, "bouilli.txt", 0)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_index__can_apply_nonconflicting_file_changes(void) +{ + git_diff *diff; + git_index *index; + git_index_entry idx_entry; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "4f2d1645dee99ced096877911de540c65ade2ef8", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + /* + * Replace the index entry with a version that is different than + * HEAD but such that the patch still applies cleanly. This item + * has a new line appended. + */ + + cl_git_pass(git_repository_index(&index, repo)); + + memset(&idx_entry, 0, sizeof(git_index_entry)); + idx_entry.mode = 0100644; + idx_entry.path = "asparagus.txt"; + cl_git_pass(git_oid_fromstr(&idx_entry.id, "06d3fefb8726ab1099acc76e02dfb85e034b2538")); + cl_git_pass(git_index_add(index, &idx_entry)); + + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_index__change_mode(void) +{ + git_diff *diff; + + const char *diff_file = DIFF_EXECUTABLE_FILE; + + struct merge_index_entry index_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100755, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} diff --git a/tests/libgit2/apply/partial.c b/tests/libgit2/apply/partial.c new file mode 100644 index 000000000..fd4908d4b --- /dev/null +++ b/tests/libgit2/apply/partial.c @@ -0,0 +1,231 @@ +#include "clar_libgit2.h" +#include "git2/sys/repository.h" + +#include "apply.h" +#include "repository.h" + +#include "../patch/patch_common.h" + +static git_repository *repo = NULL; + +void test_apply_partial__initialize(void) +{ + repo = cl_git_sandbox_init("renames"); +} + +void test_apply_partial__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int skip_addition( + const git_diff_hunk *hunk, + void *payload) +{ + GIT_UNUSED(payload); + + return (hunk->new_lines > hunk->old_lines) ? 1 : 0; +} + +static int skip_deletion( + const git_diff_hunk *hunk, + void *payload) +{ + GIT_UNUSED(payload); + + return (hunk->new_lines < hunk->old_lines) ? 1 : 0; +} + +static int skip_change( + const git_diff_hunk *hunk, + void *payload) +{ + GIT_UNUSED(payload); + + return (hunk->new_lines == hunk->old_lines) ? 1 : 0; +} + +static int abort_addition( + const git_diff_hunk *hunk, + void *payload) +{ + GIT_UNUSED(payload); + + return (hunk->new_lines > hunk->old_lines) ? GIT_EUSER : 0; +} + +static int abort_deletion( + const git_diff_hunk *hunk, + void *payload) +{ + GIT_UNUSED(payload); + + return (hunk->new_lines < hunk->old_lines) ? GIT_EUSER : 0; +} + +static int abort_change( + const git_diff_hunk *hunk, + void *payload) +{ + GIT_UNUSED(payload); + + return (hunk->new_lines == hunk->old_lines) ? GIT_EUSER : 0; +} + +static int apply_buf( + const char *old, + const char *oldname, + const char *new, + const char *newname, + const char *expected, + const git_diff_options *diff_opts, + git_apply_hunk_cb hunk_cb, + void *payload) +{ + git_patch *patch; + git_str result = GIT_STR_INIT; + git_str patchbuf = GIT_STR_INIT; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + char *filename; + unsigned int mode; + int error; + size_t oldsize = strlen(old); + size_t newsize = strlen(new); + + opts.hunk_cb = hunk_cb; + opts.payload = payload; + + cl_git_pass(git_patch_from_buffers(&patch, old, oldsize, oldname, new, newsize, newname, diff_opts)); + if ((error = git_apply__patch(&result, &filename, &mode, old, oldsize, patch, &opts)) == 0) { + cl_assert_equal_s(expected, result.ptr); + cl_assert_equal_s(newname, filename); + cl_assert_equal_i(0100644, mode); + } + + git__free(filename); + git_str_dispose(&result); + git_str_dispose(&patchbuf); + git_patch_free(patch); + + return error; +} + +void test_apply_partial__prepend_and_change_skip_addition(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + FILE_ORIGINAL, NULL, skip_addition, NULL)); +} + +void test_apply_partial__prepend_and_change_nocontext_skip_addition(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + FILE_CHANGE_MIDDLE, &diff_opts, skip_addition, NULL)); +} + +void test_apply_partial__prepend_and_change_nocontext_abort_addition(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_fail(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + FILE_ORIGINAL, &diff_opts, abort_addition, NULL)); +} + +void test_apply_partial__prepend_and_change_skip_change(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + FILE_PREPEND_AND_CHANGE, NULL, skip_change, NULL)); +} + +void test_apply_partial__prepend_and_change_nocontext_skip_change(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + FILE_PREPEND, &diff_opts, skip_change, NULL)); +} + +void test_apply_partial__prepend_and_change_nocontext_abort_change(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_fail(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + FILE_PREPEND, &diff_opts, abort_change, NULL)); +} + +void test_apply_partial__delete_and_change_skip_deletion(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + FILE_ORIGINAL, NULL, skip_deletion, NULL)); +} + +void test_apply_partial__delete_and_change_nocontext_skip_deletion(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + FILE_CHANGE_MIDDLE, &diff_opts, skip_deletion, NULL)); +} + +void test_apply_partial__delete_and_change_nocontext_abort_deletion(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_fail(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + FILE_ORIGINAL, &diff_opts, abort_deletion, NULL)); +} + +void test_apply_partial__delete_and_change_skip_change(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + FILE_DELETE_AND_CHANGE, NULL, skip_change, NULL)); +} + +void test_apply_partial__delete_and_change_nocontext_skip_change(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + FILE_DELETE_FIRSTLINE, &diff_opts, skip_change, NULL)); +} + +void test_apply_partial__delete_and_change_nocontext_abort_change(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_fail(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + FILE_DELETE_FIRSTLINE, &diff_opts, abort_change, NULL)); +} diff --git a/tests/libgit2/apply/tree.c b/tests/libgit2/apply/tree.c new file mode 100644 index 000000000..5154f134f --- /dev/null +++ b/tests/libgit2/apply/tree.c @@ -0,0 +1,94 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" +#include "../merge/merge_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + + +void test_apply_tree__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_apply_tree__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_apply_tree__one(void) +{ + git_oid a_oid, b_oid; + git_commit *a_commit, *b_commit; + git_tree *a_tree, *b_tree; + git_diff *diff; + git_index *index = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + + struct merge_index_entry expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + + git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f"); + + cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + cl_git_pass(git_commit_tree(&b_tree, b_commit)); + + cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &opts)); + + cl_git_pass(git_apply_to_tree(&index, repo, a_tree, diff, NULL)); + merge_test_index(index, expected, 6); + + git_index_free(index); + git_diff_free(diff); + git_tree_free(a_tree); + git_tree_free(b_tree); + git_commit_free(a_commit); + git_commit_free(b_commit); +} + +void test_apply_tree__adds_file(void) +{ + git_oid a_oid; + git_commit *a_commit; + git_tree *a_tree; + git_diff *diff; + git_index *index = NULL; + + struct merge_index_entry expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + + git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + + cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); + + cl_git_pass(git_apply_to_tree(&index, repo, a_tree, diff, NULL)); + merge_test_index(index, expected, 7); + + git_index_free(index); + git_diff_free(diff); + git_tree_free(a_tree); + git_commit_free(a_commit); +} diff --git a/tests/libgit2/apply/workdir.c b/tests/libgit2/apply/workdir.c new file mode 100644 index 000000000..d0d9c1aba --- /dev/null +++ b/tests/libgit2/apply/workdir.c @@ -0,0 +1,358 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_apply_workdir__initialize(void) +{ + git_oid oid; + git_commit *commit; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); +} + +void test_apply_workdir__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_apply_workdir__generated_diff(void) +{ + git_oid a_oid, b_oid; + git_commit *a_commit, *b_commit; + git_tree *a_tree, *b_tree; + git_diff *diff; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f"); cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + cl_git_pass(git_commit_tree(&b_tree, b_commit)); + + cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &opts)); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); + git_tree_free(a_tree); + git_tree_free(b_tree); + git_commit_free(a_commit); + git_commit_free(b_commit); +} + +void test_apply_workdir__parsed_diff(void) +{ + git_diff *diff; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__removes_file(void) +{ + git_diff *diff; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_FILE, + strlen(DIFF_DELETE_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__adds_file(void) +{ + git_diff *diff; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__modified_index_with_unmodified_workdir_is_ok(void) +{ + git_index *index; + git_index_entry idx_entry = {{0}}; + git_diff *diff; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "veal.txt" } + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + struct merge_index_entry workdir_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + /* mutate the index and leave the workdir matching HEAD */ + cl_git_pass(git_repository_index(&index, repo)); + + idx_entry.mode = 0100644; + idx_entry.path = "veal.txt"; + cl_git_pass(git_oid_fromstr(&idx_entry.id, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d")); + + cl_git_pass(git_index_add(index, &idx_entry)); + cl_git_pass(git_index_remove(index, "asparagus.txt", 0)); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_index_free(index); + git_diff_free(diff); +} + +void test_apply_workdir__application_failure_leaves_workdir_unmodified(void) +{ + git_diff *diff; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "8684724651336001c5dbce74bed6736d2443958d", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + /* mutate the workdir */ + cl_git_rewritefile("merge-recursive/veal.txt", + "This is a modification.\n"); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__keeps_nonconflicting_changes(void) +{ + git_diff *diff; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "f75ba05f340c51065cbea2e1fdbfe5fe13144c97", 0, "gravy.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_rmfile("merge-recursive/oyster.txt"); + cl_git_rewritefile("merge-recursive/gravy.txt", "Hello, world.\n"); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__can_apply_nonconflicting_file_changes(void) +{ + git_diff *diff; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "5db1a0fef164cb66cc0c00d35cc5af979ddc1a64", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + /* + * Replace the workdir file with a version that is different than + * HEAD but such that the patch still applies cleanly. This item + * has a new line appended. + */ + cl_git_append2file("merge-recursive/asparagus.txt", + "This line is added in the workdir.\n"); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__change_mode(void) +{ +#ifndef GIT_WIN32 + git_diff *diff; + + const char *diff_file = DIFF_EXECUTABLE_FILE; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100755, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +#endif +} + +void test_apply_workdir__apply_many_changes_one(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "c9d7d5d58088bc91f6e06f17ca3a205091568d3a", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MANY_CHANGES_ONE, strlen(DIFF_MANY_CHANGES_ONE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__apply_many_changes_two(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "6b943d65af6d8db74d747284fa4ca7d716ad5bbb", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MANY_CHANGES_TWO, strlen(DIFF_MANY_CHANGES_TWO))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} diff --git a/tests/libgit2/attr/attr_expect.h b/tests/libgit2/attr/attr_expect.h new file mode 100644 index 000000000..faf7a1181 --- /dev/null +++ b/tests/libgit2/attr/attr_expect.h @@ -0,0 +1,43 @@ +#ifndef __CLAR_TEST_ATTR_EXPECT__ +#define __CLAR_TEST_ATTR_EXPECT__ + +enum attr_expect_t { + EXPECT_FALSE, + EXPECT_TRUE, + EXPECT_UNDEFINED, + EXPECT_STRING +}; + +struct attr_expected { + const char *path; + const char *attr; + enum attr_expect_t expected; + const char *expected_str; +}; + +GIT_INLINE(void) attr_check_expected( + enum attr_expect_t expected, + const char *expected_str, + const char *name, + const char *value) +{ + switch (expected) { + case EXPECT_TRUE: + cl_assert_(GIT_ATTR_IS_TRUE(value), name); + break; + + case EXPECT_FALSE: + cl_assert_(GIT_ATTR_IS_FALSE(value), name); + break; + + case EXPECT_UNDEFINED: + cl_assert_(GIT_ATTR_IS_UNSPECIFIED(value), name); + break; + + case EXPECT_STRING: + cl_assert_equal_s(expected_str, value); + break; + } +} + +#endif diff --git a/tests/libgit2/attr/file.c b/tests/libgit2/attr/file.c new file mode 100644 index 000000000..1ac48c0fe --- /dev/null +++ b/tests/libgit2/attr/file.c @@ -0,0 +1,243 @@ +#include "clar_libgit2.h" +#include "attr_file.h" +#include "attr_expect.h" + +#define get_rule(X) ((git_attr_rule *)git_vector_get(&file->rules,(X))) +#define get_assign(R,Y) ((git_attr_assignment *)git_vector_get(&(R)->assigns,(Y))) + +void test_attr_file__simple_read(void) +{ + git_attr_file *file; + git_attr_assignment *assign; + git_attr_rule *rule; + + cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr0"))); + + cl_assert_equal_s(cl_fixture("attr/attr0"), file->entry->path); + cl_assert(file->rules.length == 1); + + rule = get_rule(0); + cl_assert(rule != NULL); + cl_assert_equal_s("*", rule->match.pattern); + cl_assert(rule->match.length == 1); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); + + cl_assert(rule->assigns.length == 1); + assign = get_assign(rule, 0); + cl_assert(assign != NULL); + cl_assert_equal_s("binary", assign->name); + cl_assert(GIT_ATTR_IS_TRUE(assign->value)); + + git_attr_file__free(file); +} + +void test_attr_file__match_variants(void) +{ + git_attr_file *file; + git_attr_rule *rule; + git_attr_assignment *assign; + + cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr1"))); + + cl_assert_equal_s(cl_fixture("attr/attr1"), file->entry->path); + cl_assert(file->rules.length == 10); + + /* let's do a thorough check of this rule, then just verify + * the things that are unique for the later rules + */ + rule = get_rule(0); + cl_assert(rule); + cl_assert_equal_s("pat0", rule->match.pattern); + cl_assert(rule->match.length == strlen("pat0")); + cl_assert(rule->assigns.length == 1); + assign = get_assign(rule,0); + cl_assert_equal_s("attr0", assign->name); + cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name)); + cl_assert(GIT_ATTR_IS_TRUE(assign->value)); + + rule = get_rule(1); + cl_assert_equal_s("pat1", rule->match.pattern); + cl_assert(rule->match.length == strlen("pat1")); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0); + + rule = get_rule(2); + cl_assert_equal_s("pat2", rule->match.pattern); + cl_assert(rule->match.length == strlen("pat2")); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_DIRECTORY) != 0); + + rule = get_rule(3); + cl_assert_equal_s("pat3dir/pat3file", rule->match.pattern); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_FULLPATH) != 0); + + rule = get_rule(4); + cl_assert_equal_s("pat4.*", rule->match.pattern); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); + + rule = get_rule(5); + cl_assert_equal_s("*.pat5", rule->match.pattern); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); + + rule = get_rule(7); + cl_assert_equal_s("pat7[a-e]??[xyz]", rule->match.pattern); + cl_assert(rule->assigns.length == 1); + cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); + assign = get_assign(rule,0); + cl_assert_equal_s("attr7", assign->name); + cl_assert(GIT_ATTR_IS_TRUE(assign->value)); + + rule = get_rule(8); + cl_assert_equal_s("pat8 with spaces", rule->match.pattern); + cl_assert(rule->match.length == strlen("pat8 with spaces")); + + rule = get_rule(9); + cl_assert_equal_s("pat9", rule->match.pattern); + + git_attr_file__free(file); +} + +static void check_one_assign( + git_attr_file *file, + int rule_idx, + int assign_idx, + const char *pattern, + const char *name, + enum attr_expect_t expected, + const char *expected_str) +{ + git_attr_rule *rule = get_rule(rule_idx); + git_attr_assignment *assign = get_assign(rule, assign_idx); + + cl_assert_equal_s(pattern, rule->match.pattern); + cl_assert(rule->assigns.length == 1); + cl_assert_equal_s(name, assign->name); + cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name)); + + attr_check_expected(expected, expected_str, assign->name, assign->value); +} + +void test_attr_file__assign_variants(void) +{ + git_attr_file *file; + git_attr_rule *rule; + git_attr_assignment *assign; + + cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr2"))); + + cl_assert_equal_s(cl_fixture("attr/attr2"), file->entry->path); + cl_assert(file->rules.length == 11); + + check_one_assign(file, 0, 0, "pat0", "simple", EXPECT_TRUE, NULL); + check_one_assign(file, 1, 0, "pat1", "neg", EXPECT_FALSE, NULL); + check_one_assign(file, 2, 0, "*", "notundef", EXPECT_TRUE, NULL); + check_one_assign(file, 3, 0, "pat2", "notundef", EXPECT_UNDEFINED, NULL); + check_one_assign(file, 4, 0, "pat3", "assigned", EXPECT_STRING, "test-value"); + check_one_assign(file, 5, 0, "pat4", "rule-with-more-chars", EXPECT_STRING, "value-with-more-chars"); + check_one_assign(file, 6, 0, "pat5", "empty", EXPECT_TRUE, NULL); + check_one_assign(file, 7, 0, "pat6", "negempty", EXPECT_FALSE, NULL); + + rule = get_rule(8); + cl_assert_equal_s("pat7", rule->match.pattern); + cl_assert(rule->assigns.length == 5); + /* assignments will be sorted by hash value, so we have to do + * lookups by search instead of by position + */ + assign = git_attr_rule__lookup_assignment(rule, "multiple"); + cl_assert(assign); + cl_assert_equal_s("multiple", assign->name); + cl_assert(GIT_ATTR_IS_TRUE(assign->value)); + assign = git_attr_rule__lookup_assignment(rule, "single"); + cl_assert(assign); + cl_assert_equal_s("single", assign->name); + cl_assert(GIT_ATTR_IS_FALSE(assign->value)); + assign = git_attr_rule__lookup_assignment(rule, "values"); + cl_assert(assign); + cl_assert_equal_s("values", assign->name); + cl_assert_equal_s("1", assign->value); + assign = git_attr_rule__lookup_assignment(rule, "also"); + cl_assert(assign); + cl_assert_equal_s("also", assign->name); + cl_assert_equal_s("a-really-long-value/*", assign->value); + assign = git_attr_rule__lookup_assignment(rule, "happy"); + cl_assert(assign); + cl_assert_equal_s("happy", assign->name); + cl_assert_equal_s("yes!", assign->value); + assign = git_attr_rule__lookup_assignment(rule, "other"); + cl_assert(!assign); + + rule = get_rule(9); + cl_assert_equal_s("pat8", rule->match.pattern); + cl_assert(rule->assigns.length == 2); + assign = git_attr_rule__lookup_assignment(rule, "again"); + cl_assert(assign); + cl_assert_equal_s("again", assign->name); + cl_assert(GIT_ATTR_IS_TRUE(assign->value)); + assign = git_attr_rule__lookup_assignment(rule, "another"); + cl_assert(assign); + cl_assert_equal_s("another", assign->name); + cl_assert_equal_s("12321", assign->value); + + check_one_assign(file, 10, 0, "pat9", "at-eof", EXPECT_FALSE, NULL); + + git_attr_file__free(file); +} + +static void assert_examples(git_attr_file *file) +{ + git_attr_rule *rule; + git_attr_assignment *assign; + + rule = get_rule(0); + cl_assert_equal_s("*.java", rule->match.pattern); + cl_assert(rule->assigns.length == 3); + assign = git_attr_rule__lookup_assignment(rule, "diff"); + cl_assert_equal_s("diff", assign->name); + cl_assert_equal_s("java", assign->value); + assign = git_attr_rule__lookup_assignment(rule, "crlf"); + cl_assert_equal_s("crlf", assign->name); + cl_assert(GIT_ATTR_IS_FALSE(assign->value)); + assign = git_attr_rule__lookup_assignment(rule, "myAttr"); + cl_assert_equal_s("myAttr", assign->name); + cl_assert(GIT_ATTR_IS_TRUE(assign->value)); + assign = git_attr_rule__lookup_assignment(rule, "missing"); + cl_assert(assign == NULL); + + rule = get_rule(1); + cl_assert_equal_s("NoMyAttr.java", rule->match.pattern); + cl_assert(rule->assigns.length == 1); + assign = get_assign(rule, 0); + cl_assert_equal_s("myAttr", assign->name); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(assign->value)); + + rule = get_rule(2); + cl_assert_equal_s("README", rule->match.pattern); + cl_assert(rule->assigns.length == 1); + assign = get_assign(rule, 0); + cl_assert_equal_s("caveat", assign->name); + cl_assert_equal_s("unspecified", assign->value); +} + +void test_attr_file__check_attr_examples(void) +{ + git_attr_file *file; + + cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr3"))); + cl_assert_equal_s(cl_fixture("attr/attr3"), file->entry->path); + cl_assert(file->rules.length == 3); + + assert_examples(file); + + git_attr_file__free(file); +} + +void test_attr_file__whitespace(void) +{ + git_attr_file *file; + + cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr4"))); + cl_assert_equal_s(cl_fixture("attr/attr4"), file->entry->path); + cl_assert(file->rules.length == 3); + + assert_examples(file); + + git_attr_file__free(file); +} diff --git a/tests/libgit2/attr/flags.c b/tests/libgit2/attr/flags.c new file mode 100644 index 000000000..6470ec732 --- /dev/null +++ b/tests/libgit2/attr/flags.c @@ -0,0 +1,108 @@ +#include "clar_libgit2.h" +#include "git2/attr.h" + +void test_attr_flags__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_attr_flags__bare(void) +{ + git_repository *repo = cl_git_sandbox_init("testrepo.git"); + const char *value; + + cl_assert(git_repository_is_bare(repo)); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM, "README.md", "diff")); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(value)); +} + +void test_attr_flags__index_vs_workdir(void) +{ + git_repository *repo = cl_git_sandbox_init("attr_index"); + const char *value; + + cl_assert(!git_repository_is_bare(repo)); + + /* wd then index */ + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "README.md", "bar")); + cl_assert(GIT_ATTR_IS_FALSE(value)); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "README.md", "blargh")); + cl_assert_equal_s(value, "goop"); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "README.txt", "foo")); + cl_assert(GIT_ATTR_IS_FALSE(value)); + + /* index then wd */ + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "README.md", "bar")); + cl_assert(GIT_ATTR_IS_TRUE(value)); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "README.md", "blargh")); + cl_assert_equal_s(value, "garble"); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "README.txt", "foo")); + cl_assert(GIT_ATTR_IS_TRUE(value)); +} + +void test_attr_flags__subdir(void) +{ + git_repository *repo = cl_git_sandbox_init("attr_index"); + const char *value; + + /* wd then index */ + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "sub/sub/README.md", "bar")); + cl_assert_equal_s(value, "1234"); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "sub/sub/README.txt", "another")); + cl_assert_equal_s(value, "one"); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "sub/sub/README.txt", "again")); + cl_assert(GIT_ATTR_IS_TRUE(value)); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, + "sub/sub/README.txt", "beep")); + cl_assert_equal_s(value, "10"); + + /* index then wd */ + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "sub/sub/README.md", "bar")); + cl_assert_equal_s(value, "1337"); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "sub/sub/README.txt", "another")); + cl_assert_equal_s(value, "one"); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "sub/sub/README.txt", "again")); + cl_assert(GIT_ATTR_IS_TRUE(value)); + + cl_git_pass(git_attr_get( + &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, + "sub/sub/README.txt", "beep")); + cl_assert_equal_s(value, "5"); +} + diff --git a/tests/libgit2/attr/lookup.c b/tests/libgit2/attr/lookup.c new file mode 100644 index 000000000..f19f38fbb --- /dev/null +++ b/tests/libgit2/attr/lookup.c @@ -0,0 +1,263 @@ +#include "clar_libgit2.h" +#include "attr_file.h" + +#include "attr_expect.h" + +void test_attr_lookup__simple(void) +{ + git_attr_file *file; + git_attr_path path; + const char *value = NULL; + + cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr0"))); + cl_assert_equal_s(cl_fixture("attr/attr0"), file->entry->path); + cl_assert(file->rules.length == 1); + + cl_git_pass(git_attr_path__init(&path, "test", NULL, GIT_DIR_FLAG_UNKNOWN)); + cl_assert_equal_s("test", path.path); + cl_assert_equal_s("test", path.basename); + cl_assert(!path.is_dir); + + cl_git_pass(git_attr_file__lookup_one(file,&path,"binary",&value)); + cl_assert(GIT_ATTR_IS_TRUE(value)); + + cl_git_pass(git_attr_file__lookup_one(file,&path,"missing",&value)); + cl_assert(!value); + + git_attr_path__free(&path); + git_attr_file__free(file); +} + +static void run_test_cases(git_attr_file *file, struct attr_expected *cases, int force_dir) +{ + git_attr_path path; + const char *value = NULL; + struct attr_expected *c; + int error; + + for (c = cases; c->path != NULL; c++) { + cl_git_pass(git_attr_path__init(&path, c->path, NULL, GIT_DIR_FLAG_UNKNOWN)); + + if (force_dir) + path.is_dir = 1; + + error = git_attr_file__lookup_one(file,&path,c->attr,&value); + cl_git_pass(error); + + attr_check_expected(c->expected, c->expected_str, c->attr, value); + + git_attr_path__free(&path); + } +} + +void test_attr_lookup__match_variants(void) +{ + git_attr_file *file; + git_attr_path path; + + struct attr_expected dir_cases[] = { + { "pat2", "attr2", EXPECT_TRUE, NULL }, + { "/testing/for/pat2", "attr2", EXPECT_TRUE, NULL }, + { "/not/pat2/yousee", "attr2", EXPECT_UNDEFINED, NULL }, + { "/fun/fun/fun/pat4.dir", "attr4", EXPECT_TRUE, NULL }, + { "foo.pat5", "attr5", EXPECT_TRUE, NULL }, + { NULL, NULL, 0, NULL } + }; + + struct attr_expected cases[] = { + /* pat0 -> simple match */ + { "pat0", "attr0", EXPECT_TRUE, NULL }, + { "/testing/for/pat0", "attr0", EXPECT_TRUE, NULL }, + { "relative/to/pat0", "attr0", EXPECT_TRUE, NULL }, + { "this-contains-pat0-inside", "attr0", EXPECT_UNDEFINED, NULL }, + { "this-aint-right", "attr0", EXPECT_UNDEFINED, NULL }, + { "/this/pat0/dont/match", "attr0", EXPECT_UNDEFINED, NULL }, + /* negative match */ + { "pat0", "attr1", EXPECT_TRUE, NULL }, + { "pat1", "attr1", EXPECT_UNDEFINED, NULL }, + { "/testing/for/pat1", "attr1", EXPECT_UNDEFINED, NULL }, + { "/testing/for/pat0", "attr1", EXPECT_TRUE, NULL }, + { "/testing/for/pat1/inside", "attr1", EXPECT_TRUE, NULL }, + { "misc", "attr1", EXPECT_TRUE, NULL }, + /* dir match */ + { "pat2", "attr2", EXPECT_UNDEFINED, NULL }, + { "/testing/for/pat2", "attr2", EXPECT_UNDEFINED, NULL }, + { "/not/pat2/yousee", "attr2", EXPECT_UNDEFINED, NULL }, + /* path match */ + { "pat3file", "attr3", EXPECT_UNDEFINED, NULL }, + { "/pat3dir/pat3file", "attr3", EXPECT_TRUE, NULL }, + { "pat3dir/pat3file", "attr3", EXPECT_TRUE, NULL }, + /* pattern* match */ + { "pat4.txt", "attr4", EXPECT_TRUE, NULL }, + { "/fun/fun/fun/pat4.c", "attr4", EXPECT_TRUE, NULL }, + { "pat4.", "attr4", EXPECT_TRUE, NULL }, + { "pat4", "attr4", EXPECT_UNDEFINED, NULL }, + /* *pattern match */ + { "foo.pat5", "attr5", EXPECT_TRUE, NULL }, + { "/this/is/ok.pat5", "attr5", EXPECT_TRUE, NULL }, + { "/this/is/bad.pat5/yousee.txt", "attr5", EXPECT_UNDEFINED, NULL }, + { "foo.pat5", "attr100", EXPECT_UNDEFINED, NULL }, + /* glob match with slashes */ + { "foo.pat6", "attr6", EXPECT_UNDEFINED, NULL }, + { "pat6/pat6/foobar.pat6", "attr6", EXPECT_TRUE, NULL }, + { "pat6/pat6/.pat6", "attr6", EXPECT_TRUE, NULL }, + { "pat6/pat6/extra/foobar.pat6", "attr6", EXPECT_UNDEFINED, NULL }, + { "/prefix/pat6/pat6/foobar.pat6", "attr6", EXPECT_UNDEFINED, NULL }, + { "/pat6/pat6/foobar.pat6", "attr6", EXPECT_TRUE, NULL }, + /* complex pattern */ + { "pat7a12z", "attr7", EXPECT_TRUE, NULL }, + { "pat7e__x", "attr7", EXPECT_TRUE, NULL }, + { "pat7b/1y", "attr7", EXPECT_UNDEFINED, NULL }, /* ? does not match / */ + { "pat7e_x", "attr7", EXPECT_UNDEFINED, NULL }, + { "pat7aaaa", "attr7", EXPECT_UNDEFINED, NULL }, + { "pat7zzzz", "attr7", EXPECT_UNDEFINED, NULL }, + { "/this/can/be/anything/pat7a12z", "attr7", EXPECT_TRUE, NULL }, + { "but/it/still/must/match/pat7aaaa", "attr7", EXPECT_UNDEFINED, NULL }, + { "pat7aaay.fail", "attr7", EXPECT_UNDEFINED, NULL }, + /* pattern with spaces */ + { "pat8 with spaces", "attr8", EXPECT_TRUE, NULL }, + { "/gotta love/pat8 with spaces", "attr8", EXPECT_TRUE, NULL }, + { "failing pat8 with spaces", "attr8", EXPECT_UNDEFINED, NULL }, + { "spaces", "attr8", EXPECT_UNDEFINED, NULL }, + /* pattern at eof */ + { "pat9", "attr9", EXPECT_TRUE, NULL }, + { "/eof/pat9", "attr9", EXPECT_TRUE, NULL }, + { "pat", "attr9", EXPECT_UNDEFINED, NULL }, + { "at9", "attr9", EXPECT_UNDEFINED, NULL }, + { "pat9.fail", "attr9", EXPECT_UNDEFINED, NULL }, + /* sentinel at end */ + { NULL, NULL, 0, NULL } + }; + + cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr1"))); + cl_assert_equal_s(cl_fixture("attr/attr1"), file->entry->path); + cl_assert(file->rules.length == 10); + + cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL, GIT_DIR_FLAG_UNKNOWN)); + cl_assert_equal_s("pat0", path.basename); + + run_test_cases(file, cases, 0); + run_test_cases(file, dir_cases, 1); + + git_attr_file__free(file); + git_attr_path__free(&path); +} + +void test_attr_lookup__assign_variants(void) +{ + git_attr_file *file; + + struct attr_expected cases[] = { + /* pat0 -> simple assign */ + { "pat0", "simple", EXPECT_TRUE, NULL }, + { "/testing/pat0", "simple", EXPECT_TRUE, NULL }, + { "pat0", "fail", EXPECT_UNDEFINED, NULL }, + { "/testing/pat0", "fail", EXPECT_UNDEFINED, NULL }, + /* negative assign */ + { "pat1", "neg", EXPECT_FALSE, NULL }, + { "/testing/pat1", "neg", EXPECT_FALSE, NULL }, + { "pat1", "fail", EXPECT_UNDEFINED, NULL }, + { "/testing/pat1", "fail", EXPECT_UNDEFINED, NULL }, + /* forced undef */ + { "pat1", "notundef", EXPECT_TRUE, NULL }, + { "pat2", "notundef", EXPECT_UNDEFINED, NULL }, + { "/lead/in/pat1", "notundef", EXPECT_TRUE, NULL }, + { "/lead/in/pat2", "notundef", EXPECT_UNDEFINED, NULL }, + /* assign value */ + { "pat3", "assigned", EXPECT_STRING, "test-value" }, + { "pat3", "notassigned", EXPECT_UNDEFINED, NULL }, + /* assign value */ + { "pat4", "rule-with-more-chars", EXPECT_STRING, "value-with-more-chars" }, + { "pat4", "notassigned-rule-with-more-chars", EXPECT_UNDEFINED, NULL }, + /* empty assignments */ + { "pat5", "empty", EXPECT_TRUE, NULL }, + { "pat6", "negempty", EXPECT_FALSE, NULL }, + /* multiple assignment */ + { "pat7", "multiple", EXPECT_TRUE, NULL }, + { "pat7", "single", EXPECT_FALSE, NULL }, + { "pat7", "values", EXPECT_STRING, "1" }, + { "pat7", "also", EXPECT_STRING, "a-really-long-value/*" }, + { "pat7", "happy", EXPECT_STRING, "yes!" }, + { "pat8", "again", EXPECT_TRUE, NULL }, + { "pat8", "another", EXPECT_STRING, "12321" }, + /* bad assignment */ + { "patbad0", "simple", EXPECT_UNDEFINED, NULL }, + { "patbad0", "notundef", EXPECT_TRUE, NULL }, + { "patbad1", "simple", EXPECT_UNDEFINED, NULL }, + /* eof assignment */ + { "pat9", "at-eof", EXPECT_FALSE, NULL }, + /* sentinel at end */ + { NULL, NULL, 0, NULL } + }; + + cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr2"))); + cl_assert(file->rules.length == 11); + + run_test_cases(file, cases, 0); + + git_attr_file__free(file); +} + +void test_attr_lookup__check_attr_examples(void) +{ + git_attr_file *file; + + struct attr_expected cases[] = { + { "foo.java", "diff", EXPECT_STRING, "java" }, + { "foo.java", "crlf", EXPECT_FALSE, NULL }, + { "foo.java", "myAttr", EXPECT_TRUE, NULL }, + { "foo.java", "other", EXPECT_UNDEFINED, NULL }, + { "/prefix/dir/foo.java", "diff", EXPECT_STRING, "java" }, + { "/prefix/dir/foo.java", "crlf", EXPECT_FALSE, NULL }, + { "/prefix/dir/foo.java", "myAttr", EXPECT_TRUE, NULL }, + { "/prefix/dir/foo.java", "other", EXPECT_UNDEFINED, NULL }, + { "NoMyAttr.java", "crlf", EXPECT_FALSE, NULL }, + { "NoMyAttr.java", "myAttr", EXPECT_UNDEFINED, NULL }, + { "NoMyAttr.java", "other", EXPECT_UNDEFINED, NULL }, + { "/prefix/dir/NoMyAttr.java", "crlf", EXPECT_FALSE, NULL }, + { "/prefix/dir/NoMyAttr.java", "myAttr", EXPECT_UNDEFINED, NULL }, + { "/prefix/dir/NoMyAttr.java", "other", EXPECT_UNDEFINED, NULL }, + { "README", "caveat", EXPECT_STRING, "unspecified" }, + { "/specific/path/README", "caveat", EXPECT_STRING, "unspecified" }, + { "README", "missing", EXPECT_UNDEFINED, NULL }, + { "/specific/path/README", "missing", EXPECT_UNDEFINED, NULL }, + /* sentinel at end */ + { NULL, NULL, 0, NULL } + }; + + cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr3"))); + cl_assert(file->rules.length == 3); + + run_test_cases(file, cases, 0); + + git_attr_file__free(file); +} + +void test_attr_lookup__from_buffer(void) +{ + git_attr_file *file; + git_attr_file_source source = {0}; + + struct attr_expected cases[] = { + { "abc", "foo", EXPECT_TRUE, NULL }, + { "abc", "bar", EXPECT_TRUE, NULL }, + { "abc", "baz", EXPECT_TRUE, NULL }, + { "aaa", "foo", EXPECT_TRUE, NULL }, + { "aaa", "bar", EXPECT_UNDEFINED, NULL }, + { "aaa", "baz", EXPECT_TRUE, NULL }, + { "qqq", "foo", EXPECT_UNDEFINED, NULL }, + { "qqq", "bar", EXPECT_UNDEFINED, NULL }, + { "qqq", "baz", EXPECT_TRUE, NULL }, + { NULL, NULL, 0, NULL } + }; + + cl_git_pass(git_attr_file__new(&file, NULL, &source)); + + cl_git_pass(git_attr_file__parse_buffer(NULL, file, "a* foo\nabc bar\n* baz", true)); + + cl_assert(file->rules.length == 3); + + run_test_cases(file, cases, 0); + + git_attr_file__free(file); +} diff --git a/tests/libgit2/attr/macro.c b/tests/libgit2/attr/macro.c new file mode 100644 index 000000000..1fbfd137f --- /dev/null +++ b/tests/libgit2/attr/macro.c @@ -0,0 +1,197 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "clar_libgit2.h" + +#include "git2/sys/repository.h" +#include "attr.h" + +static git_repository *g_repo = NULL; + +void test_attr_macro__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +void test_attr_macro__macros(void) +{ + const char *names[7] = { "rootattr", "binary", "diff", "crlf", "merge", "text", "frotz" }; + const char *names2[5] = { "mymacro", "positive", "negative", "rootattr", "another" }; + const char *names3[3] = { "macro2", "multi2", "multi3" }; + const char *values[7]; + + g_repo = cl_git_sandbox_init("attr"); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "binfile", 7, names)); + + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_TRUE(values[1])); + cl_assert(GIT_ATTR_IS_FALSE(values[2])); + cl_assert(GIT_ATTR_IS_FALSE(values[3])); + cl_assert(GIT_ATTR_IS_FALSE(values[4])); + cl_assert(GIT_ATTR_IS_FALSE(values[5])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[6])); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_test", 5, names2)); + + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_TRUE(values[1])); + cl_assert(GIT_ATTR_IS_FALSE(values[2])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); + cl_assert_equal_s("77", values[4]); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_test", 3, names3)); + + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_FALSE(values[1])); + cl_assert_equal_s("answer", values[2]); +} + +void test_attr_macro__bad_macros(void) +{ + const char *names[6] = { "rootattr", "positive", "negative", + "firstmacro", "secondmacro", "thirdmacro" }; + const char *values[6]; + + g_repo = cl_git_sandbox_init("attr"); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_bad", 6, names)); + + /* these three just confirm that the "mymacro" rule ran */ + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[0])); + cl_assert(GIT_ATTR_IS_TRUE(values[1])); + cl_assert(GIT_ATTR_IS_FALSE(values[2])); + + /* file contains: + * # let's try some malicious macro defs + * [attr]firstmacro -thirdmacro -secondmacro + * [attr]secondmacro firstmacro -firstmacro + * [attr]thirdmacro secondmacro=hahaha -firstmacro + * macro_bad firstmacro secondmacro thirdmacro + * + * firstmacro assignment list ends up with: + * -thirdmacro -secondmacro + * secondmacro assignment list expands "firstmacro" and ends up with: + * -thirdmacro -secondmacro -firstmacro + * thirdmacro assignment don't expand so list ends up with: + * secondmacro="hahaha" + * + * macro_bad assignment list ends up with: + * -thirdmacro -secondmacro firstmacro && + * -thirdmacro -secondmacro -firstmacro secondmacro && + * secondmacro="hahaha" thirdmacro + * + * so summary results should be: + * -firstmacro secondmacro="hahaha" thirdmacro + */ + cl_assert(GIT_ATTR_IS_FALSE(values[3])); + cl_assert_equal_s("hahaha", values[4]); + cl_assert(GIT_ATTR_IS_TRUE(values[5])); +} + +void test_attr_macro__macros_in_root_wd_apply(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(p_mkdir("empty_standard_repo/dir", 0777)); + cl_git_rewritefile("empty_standard_repo/.gitattributes", "[attr]customattr key=value\n"); + cl_git_rewritefile("empty_standard_repo/dir/.gitattributes", "file customattr\n"); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "dir/file", "key")); + cl_assert_equal_s(value, "value"); +} + +void test_attr_macro__changing_macro_in_root_wd_updates_attributes(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_rewritefile("empty_standard_repo/.gitattributes", + "[attr]customattr key=first\n" + "file customattr\n"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "key")); + cl_assert_equal_s(value, "first"); + + cl_git_rewritefile("empty_standard_repo/.gitattributes", + "[attr]customattr key=second\n" + "file customattr\n"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "key")); + cl_assert_equal_s(value, "second"); +} + +void test_attr_macro__macros_in_subdir_do_not_apply(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(p_mkdir("empty_standard_repo/dir", 0777)); + cl_git_rewritefile("empty_standard_repo/dir/.gitattributes", + "[attr]customattr key=value\n" + "file customattr\n"); + + /* This should _not_ pass, as macros in subdirectories shall be ignored */ + cl_git_pass(git_attr_get(&value, g_repo, 0, "dir/file", "key")); + cl_assert_equal_p(value, NULL); +} + +void test_attr_macro__adding_macro_succeeds(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_pass(git_attr_add_macro(g_repo, "macro", "key=value")); + cl_git_rewritefile("empty_standard_repo/.gitattributes", "file.txt macro\n"); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "key")); + cl_assert_equal_s(value, "value"); +} + +void test_attr_macro__adding_boolean_macros_succeeds(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_pass(git_attr_add_macro(g_repo, "macro-pos", "positive")); + cl_git_pass(git_attr_add_macro(g_repo, "macro-neg", "-negative")); + cl_git_rewritefile("empty_standard_repo/.gitattributes", "file.txt macro-pos macro-neg\n"); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "positive")); + cl_assert(GIT_ATTR_IS_TRUE(value)); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "negative")); + cl_assert(GIT_ATTR_IS_FALSE(value)); +} + +void test_attr_macro__redefining_macro_succeeds(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_pass(git_attr_add_macro(g_repo, "macro", "key=value1")); + cl_git_pass(git_attr_add_macro(g_repo, "macro", "key=value2")); + cl_git_rewritefile("empty_standard_repo/.gitattributes", "file.txt macro"); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "key")); + cl_assert_equal_s(value, "value2"); +} + +void test_attr_macro__recursive_macro_resolves(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_pass(git_attr_add_macro(g_repo, "expandme", "key=value")); + cl_git_pass(git_attr_add_macro(g_repo, "macro", "expandme")); + cl_git_rewritefile("empty_standard_repo/.gitattributes", "file.txt macro"); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "key")); + cl_assert_equal_s(value, "value"); +} diff --git a/tests/libgit2/attr/repo.c b/tests/libgit2/attr/repo.c new file mode 100644 index 000000000..abd238154 --- /dev/null +++ b/tests/libgit2/attr/repo.c @@ -0,0 +1,405 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "git2/attr.h" +#include "attr.h" +#include "sysdir.h" + +#include "attr_expect.h" +#include "git2/sys/repository.h" + +static git_repository *g_repo = NULL; + +void test_attr_repo__initialize(void) +{ + g_repo = cl_git_sandbox_init("attr"); +} + +void test_attr_repo__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; + cl_sandbox_set_search_path_defaults(); +} + +static struct attr_expected get_one_test_cases[] = { + { "root_test1", "repoattr", EXPECT_TRUE, NULL }, + { "root_test1", "rootattr", EXPECT_TRUE, NULL }, + { "root_test1", "missingattr", EXPECT_UNDEFINED, NULL }, + { "root_test1", "subattr", EXPECT_UNDEFINED, NULL }, + { "root_test1", "negattr", EXPECT_UNDEFINED, NULL }, + { "root_test2", "repoattr", EXPECT_TRUE, NULL }, + { "root_test2", "rootattr", EXPECT_FALSE, NULL }, + { "root_test2", "missingattr", EXPECT_UNDEFINED, NULL }, + { "root_test2", "multiattr", EXPECT_FALSE, NULL }, + { "root_test3", "repoattr", EXPECT_TRUE, NULL }, + { "root_test3", "rootattr", EXPECT_UNDEFINED, NULL }, + { "root_test3", "multiattr", EXPECT_STRING, "3" }, + { "root_test3", "multi2", EXPECT_UNDEFINED, NULL }, + { "sub/subdir_test1", "repoattr", EXPECT_TRUE, NULL }, + { "sub/subdir_test1", "rootattr", EXPECT_TRUE, NULL }, + { "sub/subdir_test1", "missingattr", EXPECT_UNDEFINED, NULL }, + { "sub/subdir_test1", "subattr", EXPECT_STRING, "yes" }, + { "sub/subdir_test1", "negattr", EXPECT_FALSE, NULL }, + { "sub/subdir_test1", "another", EXPECT_UNDEFINED, NULL }, + { "sub/subdir_test2.txt", "repoattr", EXPECT_TRUE, NULL }, + { "sub/subdir_test2.txt", "rootattr", EXPECT_TRUE, NULL }, + { "sub/subdir_test2.txt", "missingattr", EXPECT_UNDEFINED, NULL }, + { "sub/subdir_test2.txt", "subattr", EXPECT_STRING, "yes" }, + { "sub/subdir_test2.txt", "negattr", EXPECT_FALSE, NULL }, + { "sub/subdir_test2.txt", "another", EXPECT_STRING, "zero" }, + { "sub/subdir_test2.txt", "reposub", EXPECT_TRUE, NULL }, + { "sub/sub/subdir.txt", "another", EXPECT_STRING, "one" }, + { "sub/sub/subdir.txt", "reposubsub", EXPECT_TRUE, NULL }, + { "sub/sub/subdir.txt", "reposub", EXPECT_UNDEFINED, NULL }, + { "does-not-exist", "foo", EXPECT_STRING, "yes" }, + { "sub/deep/file", "deepdeep", EXPECT_TRUE, NULL }, + { "sub/sub/d/no", "test", EXPECT_STRING, "a/b/d/*" }, + { "sub/sub/d/yes", "test", EXPECT_UNDEFINED, NULL }, +}; + +void test_attr_repo__get_one(void) +{ + int i; + + for (i = 0; i < (int)ARRAY_SIZE(get_one_test_cases); ++i) { + struct attr_expected *scan = &get_one_test_cases[i]; + const char *value; + + cl_git_pass(git_attr_get(&value, g_repo, 0, scan->path, scan->attr)); + attr_check_expected( + scan->expected, scan->expected_str, scan->attr, value); + } + + cl_assert(git_attr_cache__is_cached( + g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".git/info/attributes")); + cl_assert(git_attr_cache__is_cached( + g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".gitattributes")); + cl_assert(git_attr_cache__is_cached( + g_repo, GIT_ATTR_FILE_SOURCE_FILE, "sub/.gitattributes")); +} + +void test_attr_repo__get_one_start_deep(void) +{ + int i; + + for (i = (int)ARRAY_SIZE(get_one_test_cases) - 1; i >= 0; --i) { + struct attr_expected *scan = &get_one_test_cases[i]; + const char *value; + + cl_git_pass(git_attr_get(&value, g_repo, 0, scan->path, scan->attr)); + attr_check_expected( + scan->expected, scan->expected_str, scan->attr, value); + } + + cl_assert(git_attr_cache__is_cached( + g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".git/info/attributes")); + cl_assert(git_attr_cache__is_cached( + g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".gitattributes")); + cl_assert(git_attr_cache__is_cached( + g_repo, GIT_ATTR_FILE_SOURCE_FILE, "sub/.gitattributes")); +} + +void test_attr_repo__get_many(void) +{ + const char *names[4] = { "repoattr", "rootattr", "missingattr", "subattr" }; + const char *values[4]; + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "root_test1", 4, names)); + + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_TRUE(values[1])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[2])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "root_test2", 4, names)); + + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_FALSE(values[1])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[2])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "sub/subdir_test1", 4, names)); + + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_TRUE(values[1])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[2])); + cl_assert_equal_s("yes", values[3]); +} + +void test_attr_repo__get_many_in_place(void) +{ + const char *vals[4] = { "repoattr", "rootattr", "missingattr", "subattr" }; + + /* it should be legal to look up values into the same array that has + * the attribute names, overwriting each name as the value is found. + */ + + cl_git_pass(git_attr_get_many(vals, g_repo, 0, "sub/subdir_test1", 4, vals)); + + cl_assert(GIT_ATTR_IS_TRUE(vals[0])); + cl_assert(GIT_ATTR_IS_TRUE(vals[1])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(vals[2])); + cl_assert_equal_s("yes", vals[3]); +} + +static int count_attrs( + const char *name, + const char *value, + void *payload) +{ + GIT_UNUSED(name); + GIT_UNUSED(value); + + *((int *)payload) += 1; + + return 0; +} + +#define CANCEL_VALUE 12345 + +static int cancel_iteration( + const char *name, + const char *value, + void *payload) +{ + GIT_UNUSED(name); + GIT_UNUSED(value); + + *((int *)payload) -= 1; + + if (*((int *)payload) < 0) + return CANCEL_VALUE; + + return 0; +} + +void test_attr_repo__foreach(void) +{ + int count; + + count = 0; + cl_git_pass(git_attr_foreach( + g_repo, 0, "root_test1", &count_attrs, &count)); + cl_assert(count == 2); + + count = 0; + cl_git_pass(git_attr_foreach(g_repo, 0, "sub/subdir_test1", + &count_attrs, &count)); + cl_assert(count == 4); /* repoattr, rootattr, subattr, negattr */ + + count = 0; + cl_git_pass(git_attr_foreach(g_repo, 0, "sub/subdir_test2.txt", + &count_attrs, &count)); + cl_assert(count == 6); /* repoattr, rootattr, subattr, reposub, negattr, another */ + + count = 2; + cl_assert_equal_i( + CANCEL_VALUE, git_attr_foreach( + g_repo, 0, "sub/subdir_test1", &cancel_iteration, &count) + ); +} + +void test_attr_repo__manpage_example(void) +{ + const char *value; + + cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "foo")); + cl_assert(GIT_ATTR_IS_TRUE(value)); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "bar")); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(value)); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "baz")); + cl_assert(GIT_ATTR_IS_FALSE(value)); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "merge")); + cl_assert_equal_s("filfre", value); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "frotz")); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(value)); +} + +#define CONTENT "I'm going to be dynamically processed\r\n" \ + "And my line endings...\r\n" \ + "...are going to be\n" \ + "normalized!\r\n" + +#define GITATTR "* text=auto\n" \ + "*.txt text\n" \ + "*.data binary\n" + +static void add_to_workdir(const char *filename, const char *content) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&buf, "attr", filename)); + cl_git_rewritefile(git_str_cstr(&buf), content); + + git_str_dispose(&buf); +} + +static void assert_proper_normalization(git_index *index, const char *filename, const char *expected_sha) +{ + size_t index_pos; + const git_index_entry *entry; + + add_to_workdir(filename, CONTENT); + cl_git_pass(git_index_add_bypath(index, filename)); + + cl_assert(!git_index_find(&index_pos, index, filename)); + + entry = git_index_get_byindex(index, index_pos); + cl_assert_equal_i(0, git_oid_streq(&entry->id, expected_sha)); +} + +void test_attr_repo__staging_properly_normalizes_line_endings_according_to_gitattributes_directives(void) +{ + git_index* index; + + cl_git_pass(git_repository_index(&index, g_repo)); + + add_to_workdir(".gitattributes", GITATTR); + + assert_proper_normalization(index, "text.txt", "22c74203bace3c2e950278c7ab08da0fca9f4e9b"); + assert_proper_normalization(index, "huh.dunno", "22c74203bace3c2e950278c7ab08da0fca9f4e9b"); + assert_proper_normalization(index, "binary.data", "66eeff1fcbacf589e6d70aa70edd3fce5be2b37c"); + + git_index_free(index); +} + +void test_attr_repo__bare_repo_with_index(void) +{ + const char *names[4] = { "test1", "test2", "test3", "test4" }; + const char *values[4]; + git_index *index; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_mkfile( + "attr/.gitattributes", + "*.txt test1 test2=foobar -test3\n" + "trial.txt -test1 test2=barfoo !test3 test4\n"); + cl_git_pass(git_index_add_bypath(index, ".gitattributes")); + git_index_free(index); + + cl_must_pass(p_unlink("attr/.gitattributes")); + cl_assert(!git_fs_path_exists("attr/.gitattributes")); + + cl_git_pass(git_repository_set_bare(g_repo)); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "file.txt", 4, names)); + + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert_equal_s("foobar", values[1]); + cl_assert(GIT_ATTR_IS_FALSE(values[2])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "trial.txt", 4, names)); + + cl_assert(GIT_ATTR_IS_FALSE(values[0])); + cl_assert_equal_s("barfoo", values[1]); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[2])); + cl_assert(GIT_ATTR_IS_TRUE(values[3])); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "sub/sub/subdir.txt", 4, names)); + + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert_equal_s("foobar", values[1]); + cl_assert(GIT_ATTR_IS_FALSE(values[2])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); +} + +void test_attr_repo__sysdir(void) +{ + git_str sysdir = GIT_STR_INIT; + const char *value; + + cl_git_pass(p_mkdir("system", 0777)); + cl_git_rewritefile("system/gitattributes", "file merge=foo"); + cl_git_pass(git_str_joinpath(&sysdir, clar_sandbox_path(), "system")); + cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, sysdir.ptr)); + g_repo = cl_git_sandbox_reopen(); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "merge")); + cl_assert_equal_s(value, "foo"); + + cl_git_pass(p_unlink("system/gitattributes")); + cl_git_pass(p_rmdir("system")); + git_str_dispose(&sysdir); +} + +void test_attr_repo__sysdir_with_session(void) +{ + const char *values[2], *attrs[2] = { "foo", "bar" }; + git_str sysdir = GIT_STR_INIT; + git_attr_session session; + + cl_git_pass(p_mkdir("system", 0777)); + cl_git_rewritefile("system/gitattributes", "file foo=1 bar=2"); + cl_git_pass(git_str_joinpath(&sysdir, clar_sandbox_path(), "system")); + cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, sysdir.ptr)); + g_repo = cl_git_sandbox_reopen(); + + cl_git_pass(git_attr_session__init(&session, g_repo)); + cl_git_pass(git_attr_get_many_with_session(values, g_repo, &session, NULL, "file", ARRAY_SIZE(attrs), attrs)); + + cl_assert_equal_s(values[0], "1"); + cl_assert_equal_s(values[1], "2"); + + cl_git_pass(p_unlink("system/gitattributes")); + cl_git_pass(p_rmdir("system")); + git_str_dispose(&sysdir); + git_attr_session__free(&session); +} + +void test_attr_repo__rewrite(void) +{ + const char *value; + + cl_git_rewritefile("attr/.gitattributes", "file.txt foo=first\n"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); + cl_assert_equal_s(value, "first"); + + cl_git_rewritefile("attr/.gitattributes", "file.txt foo=second\n"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); + cl_assert_equal_s(value, "second"); + + cl_git_rewritefile("attr/.gitattributes", "file.txt other=value\n"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); + cl_assert_equal_p(value, NULL); +} + +void test_attr_repo__rewrite_sysdir(void) +{ + git_str sysdir = GIT_STR_INIT; + const char *value; + + cl_git_pass(p_mkdir("system", 0777)); + cl_git_pass(git_str_joinpath(&sysdir, clar_sandbox_path(), "system")); + cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, sysdir.ptr)); + g_repo = cl_git_sandbox_reopen(); + + cl_git_rewritefile("system/gitattributes", "file foo=first"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "foo")); + cl_assert_equal_s(value, "first"); + + cl_git_rewritefile("system/gitattributes", "file foo=second"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "foo")); + cl_assert_equal_s(value, "second"); + + git_str_dispose(&sysdir); +} + +void test_attr_repo__unlink(void) +{ + const char *value; + + cl_git_rewritefile("attr/.gitattributes", "file.txt foo=value1\n"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); + cl_assert_equal_s(value, "value1"); + + cl_git_pass(p_unlink("attr/.gitattributes")); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); + cl_assert_equal_p(value, NULL); +} diff --git a/tests/libgit2/blame/blame_helpers.c b/tests/libgit2/blame/blame_helpers.c new file mode 100644 index 000000000..6b3ce677d --- /dev/null +++ b/tests/libgit2/blame/blame_helpers.c @@ -0,0 +1,67 @@ +#include "blame_helpers.h" + +void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...) +{ + va_list arglist; + + printf("Hunk %"PRIuZ" (line %"PRIuZ" +%"PRIuZ"): ", idx, + hunk->final_start_line_number, hunk->lines_in_hunk-1); + + va_start(arglist, fmt); + vprintf(fmt, arglist); + va_end(arglist); + + printf("\n"); +} + +void check_blame_hunk_index(git_repository *repo, git_blame *blame, int idx, + size_t start_line, size_t len, char boundary, const char *commit_id, const char *orig_path) +{ + char expected[GIT_OID_HEXSZ+1] = {0}, actual[GIT_OID_HEXSZ+1] = {0}; + const git_blame_hunk *hunk = git_blame_get_hunk_byindex(blame, idx); + cl_assert(hunk); + + if (!strncmp(commit_id, "0000", 4)) { + strcpy(expected, "0000000000000000000000000000000000000000"); + } else { + git_object *obj; + cl_git_pass(git_revparse_single(&obj, repo, commit_id)); + git_oid_fmt(expected, git_object_id(obj)); + git_object_free(obj); + } + + if (hunk->final_start_line_number != start_line) { + hunk_message(idx, hunk, "mismatched start line number: expected %"PRIuZ", got %"PRIuZ, + start_line, hunk->final_start_line_number); + } + cl_assert_equal_i(hunk->final_start_line_number, start_line); + + if (hunk->lines_in_hunk != len) { + hunk_message(idx, hunk, "mismatched line count: expected %"PRIuZ", got %"PRIuZ, + len, hunk->lines_in_hunk); + } + cl_assert_equal_i(hunk->lines_in_hunk, len); + + git_oid_fmt(actual, &hunk->final_commit_id); + if (strcmp(expected, actual)) { + hunk_message(idx, hunk, "has mismatched original id (got %s, expected %s)\n", + actual, expected); + } + cl_assert_equal_s(actual, expected); + cl_assert_equal_oid(&hunk->final_commit_id, &hunk->orig_commit_id); + + + if (strcmp(hunk->orig_path, orig_path)) { + hunk_message(idx, hunk, "has mismatched original path (got '%s', expected '%s')\n", + hunk->orig_path, orig_path); + } + cl_assert_equal_s(hunk->orig_path, orig_path); + + if (hunk->boundary != boundary) { + hunk_message(idx, hunk, "doesn't match boundary flag (got %d, expected %d)\n", + hunk->boundary, boundary); + } + cl_assert_equal_i(boundary, hunk->boundary); +} + + diff --git a/tests/libgit2/blame/blame_helpers.h b/tests/libgit2/blame/blame_helpers.h new file mode 100644 index 000000000..5b34b4aef --- /dev/null +++ b/tests/libgit2/blame/blame_helpers.h @@ -0,0 +1,14 @@ +#include "clar_libgit2.h" +#include "blame.h" + +void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...) GIT_FORMAT_PRINTF(3, 4); + +void check_blame_hunk_index( + git_repository *repo, + git_blame *blame, + int idx, + size_t start_line, + size_t len, + char boundary, + const char *commit_id, + const char *orig_path); diff --git a/tests/libgit2/blame/buffer.c b/tests/libgit2/blame/buffer.c new file mode 100644 index 000000000..06d5042dd --- /dev/null +++ b/tests/libgit2/blame/buffer.c @@ -0,0 +1,192 @@ +#include "blame_helpers.h" + +static git_repository *g_repo; +static git_blame *g_fileblame, *g_bufferblame; + +void test_blame_buffer__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + cl_git_pass(git_blame_file(&g_fileblame, g_repo, "b.txt", NULL)); + g_bufferblame = NULL; +} + +void test_blame_buffer__cleanup(void) +{ + git_blame_free(g_fileblame); + git_blame_free(g_bufferblame); + git_repository_free(g_repo); +} + +void test_blame_buffer__index(void) +{ + const git_blame_hunk *hunk; + const char *buffer = "Hello\nWorld!"; + + /* + * We need to open a different file from the ones used in other tests. Close + * the one opened in test_blame_buffer__initialize() to avoid a leak. + */ + git_blame_free(g_fileblame); + g_fileblame = NULL; + cl_git_pass(git_blame_file(&g_fileblame, g_repo, "file.txt", NULL)); + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + cl_assert_equal_i(2, git_blame_get_hunk_count(g_bufferblame)); + + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 1, 0, "836bc00b", "file.txt"); + hunk = git_blame_get_hunk_byline(g_bufferblame, 1); + cl_assert(hunk); + cl_assert_equal_s("lhchavez", hunk->final_signature->name); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 2, 1, 0, "00000000", "file.txt"); + hunk = git_blame_get_hunk_byline(g_bufferblame, 2); + cl_assert(hunk); + cl_assert(hunk->final_signature == NULL); +} + +void test_blame_buffer__added_line(void) +{ + const git_blame_hunk *hunk; + + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +abcdefg\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + cl_assert_equal_i(5, git_blame_get_hunk_count(g_bufferblame)); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "000000", "b.txt"); + + hunk = git_blame_get_hunk_byline(g_bufferblame, 16); + cl_assert(hunk); + cl_assert_equal_s("Ben Straub", hunk->final_signature->name); +} + +void test_blame_buffer__deleted_line(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 3, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 9, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 10, 5, 0, "aa06ecca", "b.txt"); +} + +void test_blame_buffer__add_splits_hunk(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +abc\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 2, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 8, 1, 0, "00000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 9, 3, 0, "63d671eb", "b.txt"); +} + +void test_blame_buffer__delete_crosses_hunk_boundary(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 7, 2, 0, "aa06ecca", "b.txt"); +} + +void test_blame_buffer__replace_line(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +abc\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n"; + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 7, 1, 0, "00000000", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 8, 3, 0, "63d671eb", "b.txt"); +} + +void test_blame_buffer__add_lines_at_end(void) +{ + const char *buffer = "\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\ +\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\ +\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\ +\n\ +abc\n\ +def\n"; + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + + cl_assert_equal_i(5, git_blame_get_hunk_count(g_bufferblame)); + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 4, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 5, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 3, 11, 5, 0, "aa06ecca", "b.txt"); + check_blame_hunk_index(g_repo, g_bufferblame, 4, 16, 2, 0, "00000000", "b.txt"); +} diff --git a/tests/libgit2/blame/getters.c b/tests/libgit2/blame/getters.c new file mode 100644 index 000000000..66eaeecf9 --- /dev/null +++ b/tests/libgit2/blame/getters.c @@ -0,0 +1,56 @@ +#include "clar_libgit2.h" + +#include "blame.h" + +git_blame *g_blame; + +void test_blame_getters__initialize(void) +{ + size_t i; + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + git_blame_hunk hunks[] = { + { 3, {{0}}, 1, NULL, {{0}}, "a", 0}, + { 3, {{0}}, 4, NULL, {{0}}, "b", 0}, + { 3, {{0}}, 7, NULL, {{0}}, "c", 0}, + { 3, {{0}}, 10, NULL, {{0}}, "d", 0}, + { 3, {{0}}, 13, NULL, {{0}}, "e", 0}, + }; + + g_blame = git_blame__alloc(NULL, opts, ""); + + for (i=0; i<5; i++) { + git_blame_hunk *h = git__calloc(1, sizeof(git_blame_hunk)); + h->final_start_line_number = hunks[i].final_start_line_number; + h->orig_path = git__strdup(hunks[i].orig_path); + h->lines_in_hunk = hunks[i].lines_in_hunk; + + git_vector_insert(&g_blame->hunks, h); + } +} + +void test_blame_getters__cleanup(void) +{ + git_blame_free(g_blame); +} + + +void test_blame_getters__byindex(void) +{ + const git_blame_hunk *h = git_blame_get_hunk_byindex(g_blame, 2); + cl_assert(h); + cl_assert_equal_s(h->orig_path, "c"); + + h = git_blame_get_hunk_byindex(g_blame, 95); + cl_assert_equal_p(h, NULL); +} + +void test_blame_getters__byline(void) +{ + const git_blame_hunk *h = git_blame_get_hunk_byline(g_blame, 5); + cl_assert(h); + cl_assert_equal_s(h->orig_path, "b"); + + h = git_blame_get_hunk_byline(g_blame, 95); + cl_assert_equal_p(h, NULL); +} diff --git a/tests/libgit2/blame/harder.c b/tests/libgit2/blame/harder.c new file mode 100644 index 000000000..e77741720 --- /dev/null +++ b/tests/libgit2/blame/harder.c @@ -0,0 +1,79 @@ +#include "clar_libgit2.h" + +#include "blame.h" + + +/** + * The test repo has a history that looks like this: + * + * * (A) bc7c5ac + * |\ + * | * (B) aa06ecc + * * | (C) 63d671e + * |/ + * * (D) da23739 + * * (E) b99f7ac + * + */ + +static git_repository *g_repo = NULL; + +void test_blame_harder__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); +} + +void test_blame_harder__cleanup(void) +{ + git_repository_free(g_repo); + g_repo = NULL; +} + + + +void test_blame_harder__m(void) +{ + /* TODO */ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + GIT_UNUSED(opts); + + opts.flags = GIT_BLAME_TRACK_COPIES_SAME_FILE; +} + + +void test_blame_harder__c(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + GIT_UNUSED(opts); + + /* Attribute the first hunk in b.txt to (E), since it was cut/pasted from + * a.txt in (D). + */ + opts.flags = GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; +} + +void test_blame_harder__cc(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + GIT_UNUSED(opts); + + /* Attribute the second hunk in b.txt to (E), since it was copy/pasted from + * a.txt in (C). + */ + opts.flags = GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; +} + +void test_blame_harder__ccc(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + GIT_UNUSED(opts); + + /* Attribute the third hunk in b.txt to (E). This hunk was deleted from + * a.txt in (D), but reintroduced in (B). + */ + opts.flags = GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES; +} diff --git a/tests/libgit2/blame/simple.c b/tests/libgit2/blame/simple.c new file mode 100644 index 000000000..6b13cccd4 --- /dev/null +++ b/tests/libgit2/blame/simple.c @@ -0,0 +1,362 @@ +#include "blame_helpers.h" + +static git_repository *g_repo; +static git_blame *g_blame; + +void test_blame_simple__initialize(void) +{ + g_repo = NULL; + g_blame = NULL; +} + +void test_blame_simple__cleanup(void) +{ + git_blame_free(g_blame); + git_repository_free(g_repo); +} + +/* + * $ git blame -s branch_file.txt + * orig line no final line no + * commit V author timestamp V + * c47800c7 1 (Scott Chacon 2010-05-25 11:58:14 -0700 1 + * a65fedf3 2 (Scott Chacon 2011-08-09 19:33:46 -0700 2 + */ +void test_blame_simple__trivial_testrepo(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo/.gitted"))); + cl_git_pass(git_blame_file(&g_blame, g_repo, "branch_file.txt", NULL)); + + cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 0, "c47800c7", "branch_file.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "a65fedf3", "branch_file.txt"); +} + +/* + * $ git blame -n b.txt + * orig line no final line no + * commit V author timestamp V + * da237394 1 (Ben Straub 2013-02-12 15:11:30 -0800 1 + * da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2 + * da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3 + * da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4 + * ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5 + * 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6 + * 63d671eb 7 (Ben Straub 2013-02-12 15:13:04 -0800 7 + * 63d671eb 8 (Ben Straub 2013-02-12 15:13:04 -0800 8 + * 63d671eb 9 (Ben Straub 2013-02-12 15:13:04 -0800 9 + * 63d671eb 10 (Ben Straub 2013-02-12 15:13:04 -0800 10 + * aa06ecca 6 (Ben Straub 2013-02-12 15:14:46 -0800 11 + * aa06ecca 7 (Ben Straub 2013-02-12 15:14:46 -0800 12 + * aa06ecca 8 (Ben Straub 2013-02-12 15:14:46 -0800 13 + * aa06ecca 9 (Ben Straub 2013-02-12 15:14:46 -0800 14 + * aa06ecca 10 (Ben Straub 2013-02-12 15:14:46 -0800 15 + */ +void test_blame_simple__trivial_blamerepo(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", NULL)); + + cl_assert_equal_i(4, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 2, 6, 5, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 3, 11, 5, 0, "aa06ecca", "b.txt"); +} + + +/* + * $ git blame -n 359fc2d -- include/git2.h + * orig line no final line no + * commit orig path V author timestamp V + * d12299fe src/git.h 1 (Vicent Martí 2010-12-03 22:22:10 +0200 1 + * 359fc2d2 include/git2.h 2 (Edward Thomson 2013-01-08 17:07:25 -0600 2 + * d12299fe src/git.h 5 (Vicent Martí 2010-12-03 22:22:10 +0200 3 + * bb742ede include/git2.h 4 (Vicent Martí 2011-09-19 01:54:32 +0300 4 + * bb742ede include/git2.h 5 (Vicent Martí 2011-09-19 01:54:32 +0300 5 + * d12299fe src/git.h 24 (Vicent Martí 2010-12-03 22:22:10 +0200 6 + * d12299fe src/git.h 25 (Vicent Martí 2010-12-03 22:22:10 +0200 7 + * d12299fe src/git.h 26 (Vicent Martí 2010-12-03 22:22:10 +0200 8 + * d12299fe src/git.h 27 (Vicent Martí 2010-12-03 22:22:10 +0200 9 + * d12299fe src/git.h 28 (Vicent Martí 2010-12-03 22:22:10 +0200 10 + * 96fab093 include/git2.h 11 (Sven Strickroth 2011-10-09 18:37:41 +0200 11 + * 9d1dcca2 src/git2.h 33 (Vicent Martí 2011-02-07 10:35:58 +0200 12 + * 44908fe7 src/git2.h 29 (Vicent Martí 2010-12-06 23:03:16 +0200 13 + * a15c550d include/git2.h 14 (Vicent Martí 2011-11-16 14:09:44 +0100 14 + * 44908fe7 src/git2.h 30 (Vicent Martí 2010-12-06 23:03:16 +0200 15 + * d12299fe src/git.h 32 (Vicent Martí 2010-12-03 22:22:10 +0200 16 + * 44908fe7 src/git2.h 33 (Vicent Martí 2010-12-06 23:03:16 +0200 17 + * d12299fe src/git.h 34 (Vicent Martí 2010-12-03 22:22:10 +0200 18 + * 44908fe7 src/git2.h 35 (Vicent Martí 2010-12-06 23:03:16 +0200 19 + * 638c2ca4 src/git2.h 36 (Vicent Martí 2010-12-18 02:10:25 +0200 20 + * 44908fe7 src/git2.h 36 (Vicent Martí 2010-12-06 23:03:16 +0200 21 + * d12299fe src/git.h 37 (Vicent Martí 2010-12-03 22:22:10 +0200 22 + * 44908fe7 src/git2.h 38 (Vicent Martí 2010-12-06 23:03:16 +0200 23 + * 44908fe7 src/git2.h 39 (Vicent Martí 2010-12-06 23:03:16 +0200 24 + * bf787bd8 include/git2.h 25 (Carlos Martín Nieto 2012-04-08 18:56:50 +0200 25 + * 0984c876 include/git2.h 26 (Scott J. Goldman 2012-11-28 18:27:43 -0800 26 + * 2f8a8ab2 src/git2.h 41 (Vicent Martí 2011-01-29 01:56:25 +0200 27 + * 27df4275 include/git2.h 47 (Michael Schubert 2011-06-28 14:13:12 +0200 28 + * a346992f include/git2.h 28 (Ben Straub 2012-05-10 09:47:14 -0700 29 + * d12299fe src/git.h 40 (Vicent Martí 2010-12-03 22:22:10 +0200 30 + * 44908fe7 src/git2.h 41 (Vicent Martí 2010-12-06 23:03:16 +0200 31 + * 44908fe7 src/git2.h 42 (Vicent Martí 2010-12-06 23:03:16 +0200 32 + * 44908fe7 src/git2.h 43 (Vicent Martí 2010-12-06 23:03:16 +0200 33 + * 44908fe7 src/git2.h 44 (Vicent Martí 2010-12-06 23:03:16 +0200 34 + * 44908fe7 src/git2.h 45 (Vicent Martí 2010-12-06 23:03:16 +0200 35 + * 65b09b1d include/git2.h 33 (Russell Belfer 2012-02-02 18:03:43 -0800 36 + * d12299fe src/git.h 46 (Vicent Martí 2010-12-03 22:22:10 +0200 37 + * 44908fe7 src/git2.h 47 (Vicent Martí 2010-12-06 23:03:16 +0200 38 + * 5d4cd003 include/git2.h 55 (Carlos Martín Nieto 2011-03-28 17:02:45 +0200 39 + * 41fb1ca0 include/git2.h 39 (Philip Kelley 2012-10-29 13:41:14 -0400 40 + * 2dc31040 include/git2.h 56 (Carlos Martín Nieto 2011-06-20 18:58:57 +0200 41 + * 764df57e include/git2.h 40 (Ben Straub 2012-06-15 13:14:43 -0700 42 + * 5280f4e6 include/git2.h 41 (Ben Straub 2012-07-31 19:39:06 -0700 43 + * 613d5eb9 include/git2.h 43 (Philip Kelley 2012-11-28 11:42:37 -0500 44 + * d12299fe src/git.h 48 (Vicent Martí 2010-12-03 22:22:10 +0200 45 + * 111ee3fe include/git2.h 41 (Vicent Martí 2012-07-11 14:37:26 +0200 46 + * f004c4a8 include/git2.h 44 (Russell Belfer 2012-08-21 17:26:39 -0700 47 + * 111ee3fe include/git2.h 42 (Vicent Martí 2012-07-11 14:37:26 +0200 48 + * 9c82357b include/git2.h 58 (Carlos Martín Nieto 2011-06-17 18:13:14 +0200 49 + * d6258deb include/git2.h 61 (Carlos Martín Nieto 2011-06-25 15:10:09 +0200 50 + * b311e313 include/git2.h 63 (Julien Miotte 2011-07-27 18:31:13 +0200 51 + * 3412391d include/git2.h 63 (Carlos Martín Nieto 2011-07-07 11:47:31 +0200 52 + * bfc9ca59 include/git2.h 43 (Russell Belfer 2012-03-28 16:45:36 -0700 53 + * bf477ed4 include/git2.h 44 (Michael Schubert 2012-02-15 00:33:38 +0100 54 + * edebceff include/git2.h 46 (nulltoken 2012-05-01 13:57:45 +0200 55 + * 743a4b3b include/git2.h 48 (nulltoken 2012-06-15 22:24:59 +0200 56 + * 0a32dca5 include/git2.h 54 (Michael Schubert 2012-08-19 22:26:32 +0200 57 + * 590fb68b include/git2.h 55 (nulltoken 2012-10-04 13:47:45 +0200 58 + * bf477ed4 include/git2.h 45 (Michael Schubert 2012-02-15 00:33:38 +0100 59 + * d12299fe src/git.h 49 (Vicent Martí 2010-12-03 22:22:10 +0200 60 + */ +void test_blame_simple__trivial_libgit2(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + git_object *obj; + + /* If we can't open the libgit2 repo or if it isn't a full repo + * with proper history, just skip this test */ + if (git_repository_open(&g_repo, cl_fixture("../..")) < 0) + cl_skip(); + + if (git_repository_is_shallow(g_repo)) + cl_skip(); + + if (git_revparse_single(&obj, g_repo, "359fc2d") < 0) + cl_skip(); + + git_oid_cpy(&opts.newest_commit, git_object_id(obj)); + git_object_free(obj); + + cl_git_pass(git_blame_file(&g_blame, g_repo, "include/git2.h", &opts)); + + check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "359fc2d2", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 2, 3, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 3, 4, 2, 0, "bb742ede", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 4, 6, 5, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 5, 11, 1, 0, "96fab093", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 6, 12, 1, 0, "9d1dcca2", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 7, 13, 1, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 8, 14, 1, 0, "a15c550d", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 9, 15, 1, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 10, 16, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 11, 17, 1, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 12, 18, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 13, 19, 1, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 14, 20, 1, 0, "638c2ca4", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 15, 21, 1, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 16, 22, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 17, 23, 2, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 18, 25, 1, 0, "bf787bd8", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 19, 26, 1, 0, "0984c876", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 20, 27, 1, 0, "2f8a8ab2", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 21, 28, 1, 0, "27df4275", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 22, 29, 1, 0, "a346992f", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 23, 30, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 24, 31, 5, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 25, 36, 1, 0, "65b09b1d", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 26, 37, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 27, 38, 1, 0, "44908fe7", "src/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 28, 39, 1, 0, "5d4cd003", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 29, 40, 1, 0, "41fb1ca0", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 30, 41, 1, 0, "2dc31040", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 31, 42, 1, 0, "764df57e", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 32, 43, 1, 0, "5280f4e6", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 33, 44, 1, 0, "613d5eb9", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 34, 45, 1, 0, "d12299fe", "src/git.h"); + check_blame_hunk_index(g_repo, g_blame, 35, 46, 1, 0, "111ee3fe", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 36, 47, 1, 0, "f004c4a8", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 37, 48, 1, 0, "111ee3fe", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 38, 49, 1, 0, "9c82357b", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 39, 50, 1, 0, "d6258deb", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 40, 51, 1, 0, "b311e313", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 41, 52, 1, 0, "3412391d", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 42, 53, 1, 0, "bfc9ca59", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 43, 54, 1, 0, "bf477ed4", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 44, 55, 1, 0, "edebceff", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 45, 56, 1, 0, "743a4b3b", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 46, 57, 1, 0, "0a32dca5", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 47, 58, 1, 0, "590fb68b", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 48, 59, 1, 0, "bf477ed4", "include/git2.h"); + check_blame_hunk_index(g_repo, g_blame, 49, 60, 1, 0, "d12299fe", "src/git.h"); +} + +/* This was leading to segfaults on some systems during cache eviction. */ +void test_blame_simple__trivial_libgit2_under_cache_pressure(void) +{ + ssize_t old_max_storage = git_cache__max_storage; + git_cache__max_storage = 1024 * 1024; + test_blame_simple__trivial_libgit2(); + git_cache__max_storage = old_max_storage; +} + +/* + * $ git blame -n b.txt -L 8 + * orig line no final line no + * commit V author timestamp V + * 63d671eb 8 (Ben Straub 2013-02-12 15:13:04 -0800 8 + * 63d671eb 9 (Ben Straub 2013-02-12 15:13:04 -0800 9 + * 63d671eb 10 (Ben Straub 2013-02-12 15:13:04 -0800 10 + * aa06ecca 6 (Ben Straub 2013-02-12 15:14:46 -0800 11 + * aa06ecca 7 (Ben Straub 2013-02-12 15:14:46 -0800 12 + * aa06ecca 8 (Ben Straub 2013-02-12 15:14:46 -0800 13 + * aa06ecca 9 (Ben Straub 2013-02-12 15:14:46 -0800 14 + * aa06ecca 10 (Ben Straub 2013-02-12 15:14:46 -0800 15 + */ +void test_blame_simple__can_restrict_lines_min(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + + opts.min_line = 8; + cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); + cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 8, 3, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 11, 5, 0, "aa06ecca", "b.txt"); +} + +/* + * $ git blame -n c.txt + * orig line no final line no + * commit V author timestamp V + * 702c7aa5 1 (Carl Schwan 2020-01-29 01:52:31 +0100 4 + */ +void test_blame_simple__can_ignore_whitespace_change(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + + opts.flags |= GIT_BLAME_IGNORE_WHITESPACE; + cl_git_pass(git_blame_file(&g_blame, g_repo, "c.txt", &opts)); + cl_assert_equal_i(1, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "702c7aa5", "c.txt"); +} + +/* + * $ git blame -n b.txt -L ,6 + * orig line no final line no + * commit V author timestamp V + * da237394 1 (Ben Straub 2013-02-12 15:11:30 -0800 1 + * da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2 + * da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3 + * da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4 + * ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5 + * 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6 + */ +void test_blame_simple__can_restrict_lines_max(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + + opts.max_line = 6; + cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); + cl_assert_equal_i(3, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 2, 6, 1, 0, "63d671eb", "b.txt"); +} + +/* + * $ git blame -n b.txt -L 2,7 + * orig line no final line no + * commit V author timestamp V + * da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2 + * da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3 + * da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4 + * ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5 + * 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6 + * 63d671eb 7 (Ben Straub 2013-02-12 15:13:04 -0800 7 + */ +void test_blame_simple__can_restrict_lines_both(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + + opts.min_line = 2; + opts.max_line = 7; + cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); + cl_assert_equal_i(3, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 2, 3, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 2, 6, 2, 0, "63d671eb", "b.txt"); +} + +void test_blame_simple__can_blame_huge_file(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + + cl_git_pass(git_blame_file(&g_blame, g_repo, "huge.txt", &opts)); + cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 1, 65536, 0, "4eecfea", "huge.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 65537, 1, 0, "6653ff4", "huge.txt"); +} + +/* + * $ git blame -n branch_file.txt be3563a..HEAD + * orig line no final line no + * commit V author timestamp V + * ^be3563a 1 (Scott Chacon 2010-05-25 11:58:27 -0700 1) hi + * a65fedf3 2 (Scott Chacon 2011-08-09 19:33:46 -0700 2) bye! + */ +void test_blame_simple__can_restrict_to_newish_commits(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); + + { + git_object *obj; + cl_git_pass(git_revparse_single(&obj, g_repo, "be3563a")); + git_oid_cpy(&opts.oldest_commit, git_object_id(obj)); + git_object_free(obj); + } + + cl_git_pass(git_blame_file(&g_blame, g_repo, "branch_file.txt", &opts)); + + cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 1, "be3563a", "branch_file.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "a65fedf", "branch_file.txt"); +} + +void test_blame_simple__can_restrict_to_first_parent_commits(void) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + opts.flags |= GIT_BLAME_FIRST_PARENT; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git"))); + + cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts)); + cl_assert_equal_i(4, git_blame_get_hunk_count(g_blame)); + check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 2, 6, 5, 0, "63d671eb", "b.txt"); + check_blame_hunk_index(g_repo, g_blame, 3, 11, 5, 0, "bc7c5ac2", "b.txt"); +} diff --git a/tests/libgit2/checkout/binaryunicode.c b/tests/libgit2/checkout/binaryunicode.c new file mode 100644 index 000000000..edb5cfaf5 --- /dev/null +++ b/tests/libgit2/checkout/binaryunicode.c @@ -0,0 +1,58 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "repo/repo_helpers.h" +#include "path.h" +#include "futils.h" + +static git_repository *g_repo; + +void test_checkout_binaryunicode__initialize(void) +{ + g_repo = cl_git_sandbox_init("binaryunicode"); +} + +void test_checkout_binaryunicode__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void execute_test(void) +{ + git_oid oid, check; + git_commit *commit; + git_tree *tree; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/branch1")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); + cl_git_pass(git_commit_tree(&tree, commit)); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_checkout_tree(g_repo, (git_object *)tree, &opts)); + + git_tree_free(tree); + git_commit_free(commit); + + /* Verify that the lenna.jpg file was checked out correctly */ + cl_git_pass(git_oid_fromstr(&check, "8ab005d890fe53f65eda14b23672f60d9f4ec5a1")); + cl_git_pass(git_odb_hashfile(&oid, "binaryunicode/lenna.jpg", GIT_OBJECT_BLOB)); + cl_assert_equal_oid(&oid, &check); + + /* Verify that the text file was checked out correctly */ + cl_git_pass(git_oid_fromstr(&check, "965b223880dd4249e2c66a0cc0b4cffe1dc40f5a")); + cl_git_pass(git_odb_hashfile(&oid, "binaryunicode/utf16_withbom_noeol_crlf.txt", GIT_OBJECT_BLOB)); + cl_assert_equal_oid(&oid, &check); +} + +void test_checkout_binaryunicode__noautocrlf(void) +{ + cl_repo_set_bool(g_repo, "core.autocrlf", false); + execute_test(); +} + +void test_checkout_binaryunicode__autocrlf(void) +{ + cl_repo_set_bool(g_repo, "core.autocrlf", true); + execute_test(); +} diff --git a/tests/libgit2/checkout/checkout_helpers.c b/tests/libgit2/checkout/checkout_helpers.c new file mode 100644 index 000000000..1e9c21bdc --- /dev/null +++ b/tests/libgit2/checkout/checkout_helpers.c @@ -0,0 +1,151 @@ +#include "clar_libgit2.h" +#include "checkout_helpers.h" +#include "refs.h" +#include "futils.h" +#include "index.h" + +void assert_on_branch(git_repository *repo, const char *branch) +{ + git_reference *head; + git_str bname = GIT_STR_INIT; + + cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); + cl_assert_(git_reference_type(head) == GIT_REFERENCE_SYMBOLIC, branch); + + cl_git_pass(git_str_joinpath(&bname, "refs/heads", branch)); + cl_assert_equal_s(bname.ptr, git_reference_symbolic_target(head)); + + git_reference_free(head); + git_str_dispose(&bname); +} + +void reset_index_to_treeish(git_object *treeish) +{ + git_object *tree; + git_index *index; + git_repository *repo = git_object_owner(treeish); + + cl_git_pass(git_object_peel(&tree, treeish, GIT_OBJECT_TREE)); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_read_tree(index, (git_tree *)tree)); + cl_git_pass(git_index_write(index)); + + git_object_free(tree); + git_index_free(index); +} + +int checkout_count_callback( + git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload) +{ + checkout_counts *ct = payload; + + GIT_UNUSED(baseline); GIT_UNUSED(target); GIT_UNUSED(workdir); + + if (why & GIT_CHECKOUT_NOTIFY_CONFLICT) { + ct->n_conflicts++; + + if (ct->debug) { + if (workdir) { + if (baseline) { + if (target) + fprintf(stderr, "M %s (conflicts with M %s)\n", + workdir->path, target->path); + else + fprintf(stderr, "M %s (conflicts with D %s)\n", + workdir->path, baseline->path); + } else { + if (target) + fprintf(stderr, "Existing %s (conflicts with A %s)\n", + workdir->path, target->path); + else + fprintf(stderr, "How can an untracked file be a conflict (%s)\n", workdir->path); + } + } else { + if (baseline) { + if (target) + fprintf(stderr, "D %s (conflicts with M %s)\n", + target->path, baseline->path); + else + fprintf(stderr, "D %s (conflicts with D %s)\n", + baseline->path, baseline->path); + } else { + if (target) + fprintf(stderr, "How can an added file with no workdir be a conflict (%s)\n", target->path); + else + fprintf(stderr, "How can a nonexistent file be a conflict (%s)\n", path); + } + } + } + } + + if (why & GIT_CHECKOUT_NOTIFY_DIRTY) { + ct->n_dirty++; + + if (ct->debug) { + if (workdir) + fprintf(stderr, "M %s\n", workdir->path); + else + fprintf(stderr, "D %s\n", baseline->path); + } + } + + if (why & GIT_CHECKOUT_NOTIFY_UPDATED) { + ct->n_updates++; + + if (ct->debug) { + if (baseline) { + if (target) + fprintf(stderr, "update: M %s\n", path); + else + fprintf(stderr, "update: D %s\n", path); + } else { + if (target) + fprintf(stderr, "update: A %s\n", path); + else + fprintf(stderr, "update: this makes no sense %s\n", path); + } + } + } + + if (why & GIT_CHECKOUT_NOTIFY_UNTRACKED) { + ct->n_untracked++; + + if (ct->debug) + fprintf(stderr, "? %s\n", path); + } + + if (why & GIT_CHECKOUT_NOTIFY_IGNORED) { + ct->n_ignored++; + + if (ct->debug) + fprintf(stderr, "I %s\n", path); + } + + return 0; +} + +void tick_index(git_index *index) +{ + struct timespec ts; + struct p_timeval times[2]; + + cl_assert(index->on_disk); + cl_assert(git_index_path(index)); + + cl_git_pass(git_index_read(index, true)); + ts = index->stamp.mtime; + + times[0].tv_sec = ts.tv_sec; + times[0].tv_usec = ts.tv_nsec / 1000; + times[1].tv_sec = ts.tv_sec + 5; + times[1].tv_usec = ts.tv_nsec / 1000; + + cl_git_pass(p_utimes(git_index_path(index), times)); + cl_git_pass(git_index_read(index, true)); +} diff --git a/tests/libgit2/checkout/checkout_helpers.h b/tests/libgit2/checkout/checkout_helpers.h new file mode 100644 index 000000000..879b48b06 --- /dev/null +++ b/tests/libgit2/checkout/checkout_helpers.h @@ -0,0 +1,30 @@ +#include "git2/object.h" +#include "git2/repository.h" + +extern void assert_on_branch(git_repository *repo, const char *branch); +extern void reset_index_to_treeish(git_object *treeish); + +#define check_file_contents(PATH,EXP) \ + cl_assert_equal_file(EXP,0,PATH) + +#define check_file_contents_nocr(PATH,EXP) \ + cl_assert_equal_file_ignore_cr(EXP,0,PATH) + +typedef struct { + int n_conflicts; + int n_dirty; + int n_updates; + int n_untracked; + int n_ignored; + int debug; +} checkout_counts; + +extern int checkout_count_callback( + git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload); + +extern void tick_index(git_index *index); diff --git a/tests/libgit2/checkout/conflict.c b/tests/libgit2/checkout/conflict.c new file mode 100644 index 000000000..4a9e4b9fa --- /dev/null +++ b/tests/libgit2/checkout/conflict.c @@ -0,0 +1,1145 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/sys/index.h" +#include "futils.h" +#include "repository.h" + +static git_repository *g_repo; +static git_index *g_index; + +#define TEST_REPO_PATH "merge-resolve" + +#define CONFLICTING_ANCESTOR_OID "d427e0b2e138501a3d15cc376077a3631e15bd46" +#define CONFLICTING_OURS_OID "4e886e602529caa9ab11d71f86634bd1b6e0de10" +#define CONFLICTING_THEIRS_OID "2bd0a343aeef7a2cf0d158478966a6e587ff3863" + +#define AUTOMERGEABLE_ANCESTOR_OID "6212c31dab5e482247d7977e4f0dd3601decf13b" +#define AUTOMERGEABLE_OURS_OID "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf" +#define AUTOMERGEABLE_THEIRS_OID "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe" + +#define LINK_ANCESTOR_OID "1a010b1c0f081b2e8901d55307a15c29ff30af0e" +#define LINK_OURS_OID "72ea499e108df5ff0a4a913e7655bbeeb1fb69f2" +#define LINK_THEIRS_OID "8bfb012a6d809e499bd8d3e194a3929bc8995b93" + +#define LINK_ANCESTOR_TARGET "file" +#define LINK_OURS_TARGET "other-file" +#define LINK_THEIRS_TARGET "still-another-file" + +#define CONFLICTING_OURS_FILE \ + "this file is changed in master and branch\n" +#define CONFLICTING_THEIRS_FILE \ + "this file is changed in branch and master\n" +#define CONFLICTING_DIFF3_FILE \ + "<<<<<<< ours\n" \ + "this file is changed in master and branch\n" \ + "=======\n" \ + "this file is changed in branch and master\n" \ + ">>>>>>> theirs\n" + +#define AUTOMERGEABLE_MERGED_FILE \ + "this file is changed in master\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is changed in branch\n" + +struct checkout_index_entry { + uint16_t mode; + char oid_str[GIT_OID_HEXSZ+1]; + int stage; + char path[128]; +}; + +struct checkout_name_entry { + char ancestor[64]; + char ours[64]; + char theirs[64]; +}; + +void test_checkout_conflict__initialize(void) +{ + git_config *cfg; + + g_repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&g_index, g_repo); + + cl_git_rewritefile( + TEST_REPO_PATH "/.gitattributes", + "* text eol=lf\n"); + + /* Ensure that the user's merge.conflictstyle doesn't interfere */ + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge")); + git_config_free(cfg); +} + +void test_checkout_conflict__cleanup(void) +{ + git_index_free(g_index); + cl_git_sandbox_cleanup(); +} + +static void create_index(struct checkout_index_entry *entries, size_t entries_len) +{ + git_str path = GIT_STR_INIT; + size_t i; + + for (i = 0; i < entries_len; i++) { + git_str_joinpath(&path, TEST_REPO_PATH, entries[i].path); + + if (entries[i].stage == 3 && (i == 0 || strcmp(entries[i-1].path, entries[i].path) != 0 || entries[i-1].stage != 2)) + p_unlink(git_str_cstr(&path)); + + cl_git_pass(git_index_remove_bypath(g_index, entries[i].path)); + } + + for (i = 0; i < entries_len; i++) { + git_index_entry entry; + + memset(&entry, 0x0, sizeof(git_index_entry)); + + entry.mode = entries[i].mode; + GIT_INDEX_ENTRY_STAGE_SET(&entry, entries[i].stage); + git_oid_fromstr(&entry.id, entries[i].oid_str); + entry.path = entries[i].path; + + cl_git_pass(git_index_add(g_index, &entry)); + } + + git_str_dispose(&path); +} + +static void create_index_names(struct checkout_name_entry *entries, size_t entries_len) +{ + size_t i; + + for (i = 0; i < entries_len; i++) { + cl_git_pass(git_index_name_add(g_index, + strlen(entries[i].ancestor) == 0 ? NULL : entries[i].ancestor, + strlen(entries[i].ours) == 0 ? NULL : entries[i].ours, + strlen(entries[i].theirs) == 0 ? NULL : entries[i].theirs)); + } +} + +static void create_conflicting_index(void) +{ + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting.txt" }, + { 0100644, CONFLICTING_OURS_OID, 2, "conflicting.txt" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting.txt" }, + }; + + create_index(checkout_index_entries, 3); + cl_git_pass(git_index_write(g_index)); +} + +static void ensure_workdir_contents(const char *path, const char *contents) +{ + git_str fullpath = GIT_STR_INIT, data_buf = GIT_STR_INIT; + + cl_git_pass( + git_str_joinpath(&fullpath, git_repository_workdir(g_repo), path)); + + cl_git_pass(git_futils_readbuffer(&data_buf, git_str_cstr(&fullpath))); + cl_assert(strcmp(git_str_cstr(&data_buf), contents) == 0); + + git_str_dispose(&fullpath); + git_str_dispose(&data_buf); +} + +static void ensure_workdir_oid(const char *path, const char *oid_str) +{ + git_oid expected, actual; + + cl_git_pass(git_oid_fromstr(&expected, oid_str)); + cl_git_pass(git_repository_hashfile(&actual, g_repo, path, GIT_OBJECT_BLOB, NULL)); + cl_assert_equal_oid(&expected, &actual); +} + +static void ensure_workdir_mode(const char *path, int mode) +{ +#ifdef GIT_WIN32 + GIT_UNUSED(path); + GIT_UNUSED(mode); +#else + git_str fullpath = GIT_STR_INIT; + struct stat st; + + cl_git_pass( + git_str_joinpath(&fullpath, git_repository_workdir(g_repo), path)); + + cl_git_pass(p_stat(git_str_cstr(&fullpath), &st)); + cl_assert_equal_i((mode & S_IRWXU), (st.st_mode & S_IRWXU)); + + git_str_dispose(&fullpath); +#endif +} + +static void ensure_workdir(const char *path, int mode, const char *oid_str) +{ + ensure_workdir_mode(path, mode); + ensure_workdir_oid(path, oid_str); +} + +static void ensure_workdir_link( + git_repository *repo, + const char *path, + const char *target) +{ + int symlinks; + + cl_git_pass(git_repository__configmap_lookup(&symlinks, repo, GIT_CONFIGMAP_SYMLINKS)); + + if (!symlinks) { + ensure_workdir_contents(path, target); + } else { + git_str fullpath = GIT_STR_INIT; + char actual[1024]; + struct stat st; + int len; + + cl_git_pass( + git_str_joinpath(&fullpath, git_repository_workdir(g_repo), path)); + + cl_git_pass(p_lstat(git_str_cstr(&fullpath), &st)); + cl_assert(S_ISLNK(st.st_mode)); + + cl_assert((len = p_readlink(git_str_cstr(&fullpath), actual, 1024)) > 0); + actual[len] = '\0'; + cl_assert(strcmp(actual, target) == 0); + + git_str_dispose(&fullpath); + } +} + +void test_checkout_conflict__ignored(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy |= GIT_CHECKOUT_SKIP_UNMERGED; + + create_conflicting_index(); + cl_git_pass(p_unlink(TEST_REPO_PATH "/conflicting.txt")); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/conflicting.txt")); +} + +void test_checkout_conflict__ours(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy |= GIT_CHECKOUT_USE_OURS; + + create_conflicting_index(); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_contents("conflicting.txt", CONFLICTING_OURS_FILE); +} + +void test_checkout_conflict__theirs(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy |= GIT_CHECKOUT_USE_THEIRS; + + create_conflicting_index(); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_contents("conflicting.txt", CONFLICTING_THEIRS_FILE); + +} + +void test_checkout_conflict__diff3(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + create_conflicting_index(); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_contents("conflicting.txt", CONFLICTING_DIFF3_FILE); +} + +void test_checkout_conflict__automerge(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "automergeable.txt" }, + { 0100644, AUTOMERGEABLE_OURS_OID, 2, "automergeable.txt" }, + { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "automergeable.txt" }, + }; + + create_index(checkout_index_entries, 3); + cl_git_pass(git_index_write(g_index)); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_contents("automergeable.txt", AUTOMERGEABLE_MERGED_FILE); +} + +void test_checkout_conflict__directory_file(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-1" }, + { 0100644, CONFLICTING_OURS_OID, 2, "df-1" }, + { 0100644, CONFLICTING_THEIRS_OID, 0, "df-1/file" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-2" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "df-2" }, + { 0100644, CONFLICTING_OURS_OID, 0, "df-2/file" }, + + { 0100644, CONFLICTING_THEIRS_OID, 3, "df-3" }, + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-3/file" }, + { 0100644, CONFLICTING_OURS_OID, 2, "df-3/file" }, + + { 0100644, CONFLICTING_OURS_OID, 2, "df-4" }, + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-4/file" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "df-4/file" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 12); + cl_git_pass(git_index_write(g_index)); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_oid("df-1/file", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("df-1~ours", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-2/file", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-2~theirs", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("df-3/file", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-3~theirs", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("df-4~ours", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-4/file", CONFLICTING_THEIRS_OID); +} + +void test_checkout_conflict__directory_file_with_custom_labels(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-1" }, + { 0100644, CONFLICTING_OURS_OID, 2, "df-1" }, + { 0100644, CONFLICTING_THEIRS_OID, 0, "df-1/file" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-2" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "df-2" }, + { 0100644, CONFLICTING_OURS_OID, 0, "df-2/file" }, + + { 0100644, CONFLICTING_THEIRS_OID, 3, "df-3" }, + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-3/file" }, + { 0100644, CONFLICTING_OURS_OID, 2, "df-3/file" }, + + { 0100644, CONFLICTING_OURS_OID, 2, "df-4" }, + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-4/file" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "df-4/file" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + opts.our_label = "HEAD"; + opts.their_label = "branch"; + + create_index(checkout_index_entries, 12); + cl_git_pass(git_index_write(g_index)); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_oid("df-1/file", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("df-1~HEAD", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-2/file", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-2~branch", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("df-3/file", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-3~branch", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("df-4~HEAD", CONFLICTING_OURS_OID); + ensure_workdir_oid("df-4/file", CONFLICTING_THEIRS_OID); +} + +void test_checkout_conflict__link_file(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "link-1" }, + { 0100644, CONFLICTING_OURS_OID, 2, "link-1" }, + { 0120000, LINK_THEIRS_OID, 3, "link-1" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "link-2" }, + { 0120000, LINK_OURS_OID, 2, "link-2" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "link-2" }, + + { 0120000, LINK_ANCESTOR_OID, 1, "link-3" }, + { 0100644, CONFLICTING_OURS_OID, 2, "link-3" }, + { 0120000, LINK_THEIRS_OID, 3, "link-3" }, + + { 0120000, LINK_ANCESTOR_OID, 1, "link-4" }, + { 0120000, LINK_OURS_OID, 2, "link-4" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "link-4" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 12); + cl_git_pass(git_index_write(g_index)); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + /* Typechange conflicts always keep the file in the workdir */ + ensure_workdir_oid("link-1", CONFLICTING_OURS_OID); + ensure_workdir_oid("link-2", CONFLICTING_THEIRS_OID); + ensure_workdir_oid("link-3", CONFLICTING_OURS_OID); + ensure_workdir_oid("link-4", CONFLICTING_THEIRS_OID); +} + +void test_checkout_conflict__links(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0120000, LINK_ANCESTOR_OID, 1, "link-1" }, + { 0120000, LINK_OURS_OID, 2, "link-1" }, + { 0120000, LINK_THEIRS_OID, 3, "link-1" }, + + { 0120000, LINK_OURS_OID, 2, "link-2" }, + { 0120000, LINK_THEIRS_OID, 3, "link-2" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 5); + cl_git_pass(git_index_write(g_index)); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + /* Conflicts with links always keep the ours side (even with -Xtheirs) */ + ensure_workdir_link(g_repo, "link-1", LINK_OURS_TARGET); + ensure_workdir_link(g_repo, "link-2", LINK_OURS_TARGET); +} + +void test_checkout_conflict__add_add(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_OURS_OID, 2, "conflicting.txt" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting.txt" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 2); + cl_git_pass(git_index_write(g_index)); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + /* Add/add writes diff3 files */ + ensure_workdir_contents("conflicting.txt", CONFLICTING_DIFF3_FILE); +} + +void test_checkout_conflict__mode_change(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-1" }, + { 0100755, CONFLICTING_ANCESTOR_OID, 2, "executable-1" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "executable-1" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-2" }, + { 0100644, CONFLICTING_OURS_OID, 2, "executable-2" }, + { 0100755, CONFLICTING_ANCESTOR_OID, 3, "executable-2" }, + + { 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-3" }, + { 0100644, CONFLICTING_ANCESTOR_OID, 2, "executable-3" }, + { 0100755, CONFLICTING_THEIRS_OID, 3, "executable-3" }, + + { 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-4" }, + { 0100755, CONFLICTING_OURS_OID, 2, "executable-4" }, + { 0100644, CONFLICTING_ANCESTOR_OID, 3, "executable-4" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-5" }, + { 0100755, CONFLICTING_OURS_OID, 2, "executable-5" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "executable-5" }, + + { 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-6" }, + { 0100644, CONFLICTING_OURS_OID, 2, "executable-6" }, + { 0100755, CONFLICTING_THEIRS_OID, 3, "executable-6" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 18); + cl_git_pass(git_index_write(g_index)); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + /* Keep the modified mode */ + ensure_workdir_oid("executable-1", CONFLICTING_THEIRS_OID); + ensure_workdir_mode("executable-1", 0100755); + + ensure_workdir_oid("executable-2", CONFLICTING_OURS_OID); + ensure_workdir_mode("executable-2", 0100755); + + ensure_workdir_oid("executable-3", CONFLICTING_THEIRS_OID); + ensure_workdir_mode("executable-3", 0100644); + + ensure_workdir_oid("executable-4", CONFLICTING_OURS_OID); + ensure_workdir_mode("executable-4", 0100644); + + ensure_workdir_contents("executable-5", CONFLICTING_DIFF3_FILE); + ensure_workdir_mode("executable-5", 0100755); + + ensure_workdir_contents("executable-6", CONFLICTING_DIFF3_FILE); + ensure_workdir_mode("executable-6", 0100644); +} + +void test_checkout_conflict__renames(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" }, + { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" }, + { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" }, + { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" }, + { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" }, + { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, + { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, + { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, + { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, + { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 2, "3a-newname-in-ours-deleted-in-theirs.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 1, "3a-renamed-in-ours-deleted-in-theirs.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 3, "3b-newname-in-theirs-deleted-in-ours.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 1, "3b-renamed-in-theirs-deleted-in-ours.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 1, "4a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 1, "4b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 1, "5a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 3, "5a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 1, "5b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 2, "5b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 2, "6-both-renamed-1-to-2-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 3, "6-both-renamed-1-to-2-theirs.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 1, "6-both-renamed-1-to-2.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "7-both-renamed-side-1.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "7-both-renamed-side-1.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "7-both-renamed-side-2.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "7-both-renamed-side-2.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" } + }; + + struct checkout_name_entry checkout_name_entries[] = { + { + "3a-renamed-in-ours-deleted-in-theirs.txt", + "3a-newname-in-ours-deleted-in-theirs.txt", + "" + }, + + { + "3b-renamed-in-theirs-deleted-in-ours.txt", + "", + "3b-newname-in-theirs-deleted-in-ours.txt" + }, + + { + "4a-renamed-in-ours-added-in-theirs.txt", + "4a-newname-in-ours-added-in-theirs.txt", + "" + }, + + { + "4b-renamed-in-theirs-added-in-ours.txt", + "", + "4b-newname-in-theirs-added-in-ours.txt" + }, + + { + "5a-renamed-in-ours-added-in-theirs.txt", + "5a-newname-in-ours-added-in-theirs.txt", + "5a-renamed-in-ours-added-in-theirs.txt" + }, + + { + "5b-renamed-in-theirs-added-in-ours.txt", + "5b-renamed-in-theirs-added-in-ours.txt", + "5b-newname-in-theirs-added-in-ours.txt" + }, + + { + "6-both-renamed-1-to-2.txt", + "6-both-renamed-1-to-2-ours.txt", + "6-both-renamed-1-to-2-theirs.txt" + }, + + { + "7-both-renamed-side-1.txt", + "7-both-renamed.txt", + "7-both-renamed-side-1.txt" + }, + + { + "7-both-renamed-side-2.txt", + "7-both-renamed-side-2.txt", + "7-both-renamed.txt" + } + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 41); + create_index_names(checkout_name_entries, 9); + cl_git_pass(git_index_write(g_index)); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir("0a-no-change.txt", + 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e"); + + ensure_workdir("0b-duplicated-in-ours.txt", + 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6"); + + ensure_workdir("0b-rewritten-in-ours.txt", + 0100644, "4c7e515d6d52d820496858f2f059ece69e99e2e3"); + + ensure_workdir("0c-duplicated-in-theirs.txt", + 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31"); + + ensure_workdir("0c-rewritten-in-theirs.txt", + 0100644, "4648d658682d1155c2a3db5b0c53305e26884ea5"); + + ensure_workdir("1a-newname-in-ours-edited-in-theirs.txt", + 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638"); + + ensure_workdir("1a-newname-in-ours.txt", + 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb"); + + ensure_workdir("1b-newname-in-theirs-edited-in-ours.txt", + 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a"); + + ensure_workdir("1b-newname-in-theirs.txt", + 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136"); + + ensure_workdir("2-newname-in-both.txt", + 0100644, "178940b450f238a56c0d75b7955cb57b38191982"); + + ensure_workdir("3a-newname-in-ours-deleted-in-theirs.txt", + 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9"); + + ensure_workdir("3b-newname-in-theirs-deleted-in-ours.txt", + 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495"); + + ensure_workdir("4a-newname-in-ours-added-in-theirs.txt~ours", + 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c"); + + ensure_workdir("4a-newname-in-ours-added-in-theirs.txt~theirs", + 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a"); + + ensure_workdir("4b-newname-in-theirs-added-in-ours.txt~ours", + 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9"); + + ensure_workdir("4b-newname-in-theirs-added-in-ours.txt~theirs", + 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db"); + + ensure_workdir("5a-newname-in-ours-added-in-theirs.txt~ours", + 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436"); + + ensure_workdir("5a-newname-in-ours-added-in-theirs.txt~theirs", + 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714"); + + ensure_workdir("5b-newname-in-theirs-added-in-ours.txt~ours", + 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced"); + + ensure_workdir("5b-newname-in-theirs-added-in-ours.txt~theirs", + 0100644, "63247125386de9ec90a27ad36169307bf8a11a38"); + + ensure_workdir("6-both-renamed-1-to-2-ours.txt", + 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450"); + + ensure_workdir("6-both-renamed-1-to-2-theirs.txt", + 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450"); + + ensure_workdir("7-both-renamed.txt~ours", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); + + ensure_workdir("7-both-renamed.txt~theirs", + 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); +} + +void test_checkout_conflict__rename_keep_ours(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" }, + { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" }, + { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" }, + { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" }, + { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" }, + { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, + { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, + { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, + { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, + { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 2, "3a-newname-in-ours-deleted-in-theirs.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 1, "3a-renamed-in-ours-deleted-in-theirs.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 3, "3b-newname-in-theirs-deleted-in-ours.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 1, "3b-renamed-in-theirs-deleted-in-ours.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 1, "4a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 1, "4b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 1, "5a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 3, "5a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 1, "5b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 2, "5b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 2, "6-both-renamed-1-to-2-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 3, "6-both-renamed-1-to-2-theirs.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 1, "6-both-renamed-1-to-2.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "7-both-renamed-side-1.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "7-both-renamed-side-1.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "7-both-renamed-side-2.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "7-both-renamed-side-2.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" } + }; + + struct checkout_name_entry checkout_name_entries[] = { + { + "3a-renamed-in-ours-deleted-in-theirs.txt", + "3a-newname-in-ours-deleted-in-theirs.txt", + "" + }, + + { + "3b-renamed-in-theirs-deleted-in-ours.txt", + "", + "3b-newname-in-theirs-deleted-in-ours.txt" + }, + + { + "4a-renamed-in-ours-added-in-theirs.txt", + "4a-newname-in-ours-added-in-theirs.txt", + "" + }, + + { + "4b-renamed-in-theirs-added-in-ours.txt", + "", + "4b-newname-in-theirs-added-in-ours.txt" + }, + + { + "5a-renamed-in-ours-added-in-theirs.txt", + "5a-newname-in-ours-added-in-theirs.txt", + "5a-renamed-in-ours-added-in-theirs.txt" + }, + + { + "5b-renamed-in-theirs-added-in-ours.txt", + "5b-renamed-in-theirs-added-in-ours.txt", + "5b-newname-in-theirs-added-in-ours.txt" + }, + + { + "6-both-renamed-1-to-2.txt", + "6-both-renamed-1-to-2-ours.txt", + "6-both-renamed-1-to-2-theirs.txt" + }, + + { + "7-both-renamed-side-1.txt", + "7-both-renamed.txt", + "7-both-renamed-side-1.txt" + }, + + { + "7-both-renamed-side-2.txt", + "7-both-renamed-side-2.txt", + "7-both-renamed.txt" + } + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; + + create_index(checkout_index_entries, 41); + create_index_names(checkout_name_entries, 9); + cl_git_pass(git_index_write(g_index)); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir("0a-no-change.txt", + 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e"); + + ensure_workdir("0b-duplicated-in-ours.txt", + 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6"); + + ensure_workdir("0b-rewritten-in-ours.txt", + 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e"); + + ensure_workdir("0c-duplicated-in-theirs.txt", + 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31"); + + ensure_workdir("0c-rewritten-in-theirs.txt", + 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09"); + + ensure_workdir("1a-newname-in-ours-edited-in-theirs.txt", + 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638"); + + ensure_workdir("1a-newname-in-ours.txt", + 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb"); + + ensure_workdir("1b-newname-in-theirs-edited-in-ours.txt", + 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a"); + + ensure_workdir("1b-newname-in-theirs.txt", + 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136"); + + ensure_workdir("2-newname-in-both.txt", + 0100644, "178940b450f238a56c0d75b7955cb57b38191982"); + + ensure_workdir("3a-newname-in-ours-deleted-in-theirs.txt", + 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9"); + + ensure_workdir("3b-newname-in-theirs-deleted-in-ours.txt", + 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495"); + + ensure_workdir("4a-newname-in-ours-added-in-theirs.txt", + 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c"); + + ensure_workdir("4b-newname-in-theirs-added-in-ours.txt", + 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9"); + + ensure_workdir("5a-newname-in-ours-added-in-theirs.txt", + 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436"); + + ensure_workdir("5b-newname-in-theirs-added-in-ours.txt", + 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced"); + + ensure_workdir("6-both-renamed-1-to-2-ours.txt", + 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450"); + + ensure_workdir("7-both-renamed.txt", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); +} + +void test_checkout_conflict__name_mangled_file_exists_in_workdir(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-one-side-one.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-one-side-one.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-one-side-two.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-one-side-two.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-one.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-one.txt" }, + + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-two-side-one.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-two-side-one.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-two-side-two.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-two-side-two.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-two.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-two.txt" }, + + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-three-side-one.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-three-side-one.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-three-side-two.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-three-side-two.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-three.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-three.txt" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-one" }, + { 0100644, CONFLICTING_OURS_OID, 2, "directory_file-one" }, + { 0100644, CONFLICTING_THEIRS_OID, 0, "directory_file-one/file" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-two" }, + { 0100644, CONFLICTING_OURS_OID, 0, "directory_file-two/file" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "directory_file-two" }, + }; + + struct checkout_name_entry checkout_name_entries[] = { + { + "test-one-side-one.txt", + "test-one.txt", + "test-one-side-one.txt" + }, + { + "test-one-side-two.txt", + "test-one-side-two.txt", + "test-one.txt" + }, + + { + "test-two-side-one.txt", + "test-two.txt", + "test-two-side-one.txt" + }, + { + "test-two-side-two.txt", + "test-two-side-two.txt", + "test-two.txt" + }, + + { + "test-three-side-one.txt", + "test-three.txt", + "test-three-side-one.txt" + }, + { + "test-three-side-two.txt", + "test-three-side-two.txt", + "test-three.txt" + } + }; + + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + + create_index(checkout_index_entries, 24); + create_index_names(checkout_name_entries, 6); + cl_git_pass(git_index_write(g_index)); + + /* Add some files on disk that conflict with the names that would be chosen + * for the files written for each side. */ + + cl_git_rewritefile("merge-resolve/test-one.txt~ours", + "Expect index contents to be written to ~ours_0"); + cl_git_rewritefile("merge-resolve/test-one.txt~theirs", + "Expect index contents to be written to ~theirs_0"); + + cl_git_rewritefile("merge-resolve/test-two.txt~ours", + "Expect index contents to be written to ~ours_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~theirs", + "Expect index contents to be written to ~theirs_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~ours_0", + "Expect index contents to be written to ~ours_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~theirs_0", + "Expect index contents to be written to ~theirs_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~ours_1", + "Expect index contents to be written to ~ours_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~theirs_1", + "Expect index contents to be written to ~theirs_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~ours_2", + "Expect index contents to be written to ~ours_3"); + cl_git_rewritefile("merge-resolve/test-two.txt~theirs_2", + "Expect index contents to be written to ~theirs_3"); + + cl_git_rewritefile("merge-resolve/test-three.txt~Ours", + "Expect case insensitive filesystems to create ~ours_0"); + cl_git_rewritefile("merge-resolve/test-three.txt~THEIRS", + "Expect case insensitive filesystems to create ~theirs_0"); + + cl_git_rewritefile("merge-resolve/directory_file-one~ours", + "Index contents written to ~ours_0 in this D/F conflict"); + cl_git_rewritefile("merge-resolve/directory_file-two~theirs", + "Index contents written to ~theirs_0 in this D/F conflict"); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir("test-one.txt~ours_0", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); + ensure_workdir("test-one.txt~theirs_0", + 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); + + ensure_workdir("test-two.txt~ours_3", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); + ensure_workdir("test-two.txt~theirs_3", + 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); + + /* Name is mangled on case insensitive only */ +#if defined(GIT_WIN32) || defined(__APPLE__) + ensure_workdir("test-three.txt~ours_0", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); + ensure_workdir("test-three.txt~theirs_0", + 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); +#else + ensure_workdir("test-three.txt~ours", + 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11"); + ensure_workdir("test-three.txt~theirs", + 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07"); +#endif + + ensure_workdir("directory_file-one~ours_0", 0100644, CONFLICTING_OURS_OID); + ensure_workdir("directory_file-two~theirs_0", 0100644, CONFLICTING_THEIRS_OID); +} + +void test_checkout_conflict__update_only(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "automergeable.txt" }, + { 0100644, AUTOMERGEABLE_OURS_OID, 2, "automergeable.txt" }, + { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "automergeable.txt" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "modify-delete" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "modify-delete" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-one" }, + { 0100644, CONFLICTING_OURS_OID, 2, "directory_file-one" }, + { 0100644, CONFLICTING_THEIRS_OID, 0, "directory_file-one/file" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-two" }, + { 0100644, CONFLICTING_OURS_OID, 0, "directory_file-two/file" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "directory_file-two" }, + }; + + opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_ONLY; + + create_index(checkout_index_entries, 3); + cl_git_pass(git_index_write(g_index)); + + cl_git_pass(p_mkdir("merge-resolve/directory_file-two", 0777)); + cl_git_rewritefile("merge-resolve/directory_file-two/file", CONFLICTING_OURS_FILE); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_contents("automergeable.txt", AUTOMERGEABLE_MERGED_FILE); + ensure_workdir("directory_file-two/file", 0100644, CONFLICTING_OURS_OID); + + cl_assert(!git_fs_path_exists("merge-resolve/modify-delete")); + cl_assert(!git_fs_path_exists("merge-resolve/test-one.txt")); + cl_assert(!git_fs_path_exists("merge-resolve/test-one-side-one.txt")); + cl_assert(!git_fs_path_exists("merge-resolve/test-one-side-two.txt")); + cl_assert(!git_fs_path_exists("merge-resolve/test-one.txt~ours")); + cl_assert(!git_fs_path_exists("merge-resolve/test-one.txt~theirs")); + cl_assert(!git_fs_path_exists("merge-resolve/directory_file-one/file")); + cl_assert(!git_fs_path_exists("merge-resolve/directory_file-one~ours")); + cl_assert(!git_fs_path_exists("merge-resolve/directory_file-two~theirs")); +} + +void test_checkout_conflict__path_filters(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + char *paths[] = { "conflicting-1.txt", "conflicting-3.txt" }; + git_strarray patharray = {0}; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-1.txt" }, + { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-1.txt" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-1.txt" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-2.txt" }, + { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-2.txt" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-2.txt" }, + + { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-3.txt" }, + { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-3.txt" }, + { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-3.txt" }, + + { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-4.txt" }, + { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-4.txt" }, + { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-4.txt" }, + }; + + patharray.count = 2; + patharray.strings = paths; + + opts.paths = patharray; + + create_index(checkout_index_entries, 12); + cl_git_pass(git_index_write(g_index)); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + ensure_workdir_contents("conflicting-1.txt", CONFLICTING_DIFF3_FILE); + cl_assert(!git_fs_path_exists("merge-resolve/conflicting-2.txt")); + ensure_workdir_contents("conflicting-3.txt", AUTOMERGEABLE_MERGED_FILE); + cl_assert(!git_fs_path_exists("merge-resolve/conflicting-4.txt")); +} + +static void collect_progress( + const char *path, + size_t completed_steps, + size_t total_steps, + void *payload) +{ + git_vector *paths = payload; + + GIT_UNUSED(completed_steps); + GIT_UNUSED(total_steps); + + if (path == NULL) + return; + + git_vector_insert(paths, strdup(path)); +} + +void test_checkout_conflict__report_progress(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_vector paths = GIT_VECTOR_INIT; + char *path; + size_t i; + + struct checkout_index_entry checkout_index_entries[] = { + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-1.txt" }, + { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-1.txt" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-1.txt" }, + + { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-2.txt" }, + { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-2.txt" }, + { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-2.txt" }, + + { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-3.txt" }, + { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-3.txt" }, + { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-3.txt" }, + + { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-4.txt" }, + { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-4.txt" }, + { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-4.txt" }, + }; + + opts.progress_cb = collect_progress; + opts.progress_payload = &paths; + + + create_index(checkout_index_entries, 12); + cl_git_pass(git_index_write(g_index)); + + cl_git_pass(git_checkout_index(g_repo, g_index, &opts)); + + cl_assert_equal_i(4, git_vector_length(&paths)); + cl_assert_equal_s("conflicting-1.txt", git_vector_get(&paths, 0)); + cl_assert_equal_s("conflicting-2.txt", git_vector_get(&paths, 1)); + cl_assert_equal_s("conflicting-3.txt", git_vector_get(&paths, 2)); + cl_assert_equal_s("conflicting-4.txt", git_vector_get(&paths, 3)); + + git_vector_foreach(&paths, i, path) + git__free(path); + + git_vector_free(&paths); +} diff --git a/tests/libgit2/checkout/crlf.c b/tests/libgit2/checkout/crlf.c new file mode 100644 index 000000000..21f8a852a --- /dev/null +++ b/tests/libgit2/checkout/crlf.c @@ -0,0 +1,496 @@ +#include "clar_libgit2.h" +#include "checkout_helpers.h" +#include "../filter/crlf.h" +#include "futils.h" + +#include "git2/checkout.h" +#include "repository.h" +#include "index.h" +#include "posix.h" + +static git_repository *g_repo; + +static const char *systype; +static git_str expected_fixture = GIT_STR_INIT; + +static int unlink_file(void *payload, git_str *path) +{ + char *fn; + + cl_assert(fn = git_fs_path_basename(path->ptr)); + + GIT_UNUSED(payload); + + if (strcmp(fn, ".git")) + cl_must_pass(p_unlink(path->ptr)); + + git__free(fn); + return 0; +} + +void test_checkout_crlf__initialize(void) +{ + git_str reponame = GIT_STR_INIT; + + g_repo = cl_git_sandbox_init("crlf"); + + /* + * remove the contents of the working directory so that we can + * check out over it. + */ + cl_git_pass(git_str_puts(&reponame, "crlf")); + cl_git_pass(git_fs_path_direach(&reponame, 0, unlink_file, NULL)); + + if (GIT_EOL_NATIVE == GIT_EOL_CRLF) + systype = "windows"; + else + systype = "posix"; + + git_str_dispose(&reponame); +} + +void test_checkout_crlf__cleanup(void) +{ + cl_git_sandbox_cleanup(); + + if (expected_fixture.size) { + cl_fixture_cleanup(expected_fixture.ptr); + git_str_dispose(&expected_fixture); + } +} + +struct compare_data +{ + const char *dirname; + const char *autocrlf; + const char *attrs; +}; + +static int compare_file(void *payload, git_str *actual_path) +{ + git_str expected_path = GIT_STR_INIT; + git_str actual_contents = GIT_STR_INIT; + git_str expected_contents = GIT_STR_INIT; + struct compare_data *cd = payload; + bool failed = true; + int cmp_git, cmp_gitattributes; + char *basename; + + basename = git_fs_path_basename(actual_path->ptr); + cmp_git = strcmp(basename, ".git"); + cmp_gitattributes = strcmp(basename, ".gitattributes"); + + if (cmp_git == 0 || cmp_gitattributes == 0) { + failed = false; + goto done; + } + + cl_git_pass(git_str_joinpath(&expected_path, cd->dirname, basename)); + + if (!git_fs_path_isfile(expected_path.ptr) || + !git_fs_path_isfile(actual_path->ptr)) + goto done; + + if (git_futils_readbuffer(&actual_contents, actual_path->ptr) < 0 || + git_futils_readbuffer(&expected_contents, expected_path.ptr) < 0) + goto done; + + if (actual_contents.size != expected_contents.size) + goto done; + + if (memcmp(actual_contents.ptr, expected_contents.ptr, expected_contents.size) != 0) + goto done; + + failed = false; + +done: + if (failed) { + git_str details = GIT_STR_INIT; + git_str_printf(&details, "filename=%s, system=%s, autocrlf=%s, attrs={%s}", + git_fs_path_basename(actual_path->ptr), systype, cd->autocrlf, cd->attrs); + clar__fail(__FILE__, __func__, __LINE__, + "checked out contents did not match expected", details.ptr, 0); + git_str_dispose(&details); + } + + git__free(basename); + git_str_dispose(&expected_contents); + git_str_dispose(&actual_contents); + git_str_dispose(&expected_path); + + return 0; +} + +static void test_checkout(const char *autocrlf, const char *attrs) +{ + git_str attrbuf = GIT_STR_INIT; + git_str expected_dirname = GIT_STR_INIT; + git_str systype_and_direction = GIT_STR_INIT; + git_str sandboxname = GIT_STR_INIT; + git_str reponame = GIT_STR_INIT; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + struct compare_data compare_data = { NULL, autocrlf, attrs }; + const char *c; + + cl_git_pass(git_str_puts(&reponame, "crlf")); + + cl_git_pass(git_str_puts(&systype_and_direction, systype)); + cl_git_pass(git_str_puts(&systype_and_direction, "_to_workdir")); + + cl_git_pass(git_str_puts(&sandboxname, "autocrlf_")); + cl_git_pass(git_str_puts(&sandboxname, autocrlf)); + + if (*attrs) { + cl_git_pass(git_str_puts(&sandboxname, ",")); + + for (c = attrs; *c; c++) { + if (*c == ' ') + cl_git_pass(git_str_putc(&sandboxname, ',')); + else if (*c == '=') + cl_git_pass(git_str_putc(&sandboxname, '_')); + else + cl_git_pass(git_str_putc(&sandboxname, *c)); + } + + cl_git_pass(git_str_printf(&attrbuf, "* %s\n", attrs)); + cl_git_mkfile("crlf/.gitattributes", attrbuf.ptr); + } + + cl_repo_set_string(g_repo, "core.autocrlf", autocrlf); + + cl_git_pass(git_str_joinpath(&expected_dirname, systype_and_direction.ptr, sandboxname.ptr)); + cl_git_pass(git_str_joinpath(&expected_fixture, "crlf_data", expected_dirname.ptr)); + cl_fixture_sandbox(expected_fixture.ptr); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_head(g_repo, &opts)); + + compare_data.dirname = sandboxname.ptr; + cl_git_pass(git_fs_path_direach(&reponame, 0, compare_file, &compare_data)); + + cl_fixture_cleanup(expected_fixture.ptr); + git_str_dispose(&expected_fixture); + + git_str_dispose(&attrbuf); + git_str_dispose(&expected_fixture); + git_str_dispose(&expected_dirname); + git_str_dispose(&sandboxname); + git_str_dispose(&systype_and_direction); + git_str_dispose(&reponame); +} + +static void empty_workdir(const char *name) +{ + git_vector contents = GIT_VECTOR_INIT; + char *basename; + int cmp; + size_t i; + const char *fn; + + cl_git_pass(git_fs_path_dirload(&contents, name, 0, 0)); + git_vector_foreach(&contents, i, fn) { + cl_assert(basename = git_fs_path_basename(fn)); + cmp = strncasecmp(basename, ".git", 4); + + git__free(basename); + + if (cmp) + cl_git_pass(p_unlink(fn)); + } + git_vector_free_deep(&contents); +} + +void test_checkout_crlf__matches_core_git(void) +{ + const char *autocrlf[] = { "true", "false", "input", NULL }; + const char *attrs[] = { "", "-crlf", "-text", "eol=crlf", "eol=lf", + "text", "text eol=crlf", "text eol=lf", + "text=auto", "text=auto eol=crlf", "text=auto eol=lf", + NULL }; + const char **a, **b; + + for (a = autocrlf; *a; a++) { + for (b = attrs; *b; b++) { + empty_workdir("crlf"); + test_checkout(*a, *b); + } + } +} + +void test_checkout_crlf__detect_crlf_autocrlf_false(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_repo_set_bool(g_repo, "core.autocrlf", false); + + git_checkout_head(g_repo, &opts); + + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); +} + +void test_checkout_crlf__autocrlf_false_index_size_is_unfiltered_size(void) +{ + git_index *index; + const git_index_entry *entry; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_repo_set_bool(g_repo, "core.autocrlf", false); + + cl_git_pass(git_repository_index(&index, g_repo)); + tick_index(index); + + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert((entry = git_index_get_bypath(index, "all-lf", 0)) != NULL); + cl_assert(entry->file_size == strlen(ALL_LF_TEXT_RAW)); + + cl_assert((entry = git_index_get_bypath(index, "all-crlf", 0)) != NULL); + cl_assert(entry->file_size == strlen(ALL_CRLF_TEXT_RAW)); + + git_index_free(index); +} + +void test_checkout_crlf__detect_crlf_autocrlf_true(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + git_checkout_head(g_repo, &opts); + + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF); + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); +} + +void test_checkout_crlf__detect_crlf_autocrlf_true_utf8(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + git_repository_set_head(g_repo, "refs/heads/master"); + git_checkout_head(g_repo, &opts); + + check_file_contents("./crlf/few-utf8-chars-lf", FEW_UTF8_CRLF_RAW); + check_file_contents("./crlf/many-utf8-chars-lf", MANY_UTF8_CRLF_RAW); + + check_file_contents("./crlf/few-utf8-chars-crlf", FEW_UTF8_CRLF_RAW); + check_file_contents("./crlf/many-utf8-chars-crlf", MANY_UTF8_CRLF_RAW); +} + +void test_checkout_crlf__autocrlf_true_index_size_is_filtered_size(void) +{ + git_index *index; + const git_index_entry *entry; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + git_repository_index(&index, g_repo); + tick_index(index); + + git_checkout_head(g_repo, &opts); + + cl_assert((entry = git_index_get_bypath(index, "all-lf", 0)) != NULL); + + cl_assert_equal_sz(strlen(ALL_LF_TEXT_AS_CRLF), entry->file_size); + + cl_assert((entry = git_index_get_bypath(index, "all-crlf", 0)) != NULL); + cl_assert_equal_sz(strlen(ALL_CRLF_TEXT_RAW), entry->file_size); + + git_index_free(index); +} + +void test_checkout_crlf__with_ident(void) +{ + git_index *index; + const git_index_entry *entry; + git_blob *blob; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_mkfile("crlf/.gitattributes", + "*.txt text\n*.bin binary\n" + "*.crlf text eol=crlf\n" + "*.lf text eol=lf\n" + "*.ident text ident\n" + "*.identcrlf ident text eol=crlf\n" + "*.identlf ident text eol=lf\n"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + /* add files with $Id$ */ + + cl_git_mkfile("crlf/lf.ident", ALL_LF_TEXT_RAW "\n$Id: initial content$\n"); + cl_git_mkfile("crlf/crlf.ident", ALL_CRLF_TEXT_RAW "\r\n$Id$\r\n\r\n"); + cl_git_mkfile("crlf/more1.identlf", "$Id$\n" MORE_LF_TEXT_RAW); + cl_git_mkfile("crlf/more2.identcrlf", "\r\n$Id: $\r\n" MORE_CRLF_TEXT_RAW); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "lf.ident")); + cl_git_pass(git_index_add_bypath(index, "crlf.ident")); + cl_git_pass(git_index_add_bypath(index, "more1.identlf")); + cl_git_pass(git_index_add_bypath(index, "more2.identcrlf")); + cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "Some ident files\n"); + + git_checkout_head(g_repo, &opts); + + /* check that blobs have $Id$ */ + + cl_assert((entry = git_index_get_bypath(index, "lf.ident", 0))); + cl_git_pass(git_blob_lookup(&blob, g_repo, &entry->id)); + cl_assert_equal_s( + ALL_LF_TEXT_RAW "\n$Id$\n", git_blob_rawcontent(blob)); + git_blob_free(blob); + + cl_assert((entry = git_index_get_bypath(index, "more2.identcrlf", 0))); + cl_git_pass(git_blob_lookup(&blob, g_repo, &entry->id)); + cl_assert_equal_s( + "\n$Id$\n" MORE_CRLF_TEXT_AS_LF, git_blob_rawcontent(blob)); + git_blob_free(blob); + + /* check that filesystem is initially untouched - matching core Git */ + + cl_assert_equal_file( + ALL_LF_TEXT_RAW "\n$Id: initial content$\n", 0, "crlf/lf.ident"); + + /* check that forced checkout rewrites correctly */ + + p_unlink("crlf/lf.ident"); + p_unlink("crlf/crlf.ident"); + p_unlink("crlf/more1.identlf"); + p_unlink("crlf/more2.identcrlf"); + + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert_equal_file( + ALL_LF_TEXT_AS_CRLF + "\r\n$Id: fcf6d4d9c212dc66563b1171b1cd99953c756467 $\r\n", + 0, "crlf/lf.ident"); + cl_assert_equal_file( + ALL_CRLF_TEXT_RAW + "\r\n$Id: f2c66ad9b2b5a734d9bf00d5000cc10a62b8a857 $\r\n\r\n", + 0, "crlf/crlf.ident"); + + cl_assert_equal_file( + "$Id: f7830382dac1f1583422be5530fdfbd26289431b $\n" + MORE_LF_TEXT_AS_LF, 0, "crlf/more1.identlf"); + + cl_assert_equal_file( + "\r\n$Id: 74677a68413012ce8d7e7cfc3f12603df3a3eac4 $\r\n" + MORE_CRLF_TEXT_AS_CRLF, 0, "crlf/more2.identcrlf"); + + git_index_free(index); +} + +void test_checkout_crlf__autocrlf_false_no_attrs(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_repo_set_bool(g_repo, "core.autocrlf", false); + + cl_git_pass(git_checkout_head(g_repo, &opts)); + + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); +} + +void test_checkout_crlf__autocrlf_true_no_attrs(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_git_pass(git_checkout_head(g_repo, &opts)); + + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF); + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_AS_CRLF); +} + +void test_checkout_crlf__autocrlf_input_no_attrs(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_repo_set_string(g_repo, "core.autocrlf", "input"); + + cl_git_pass(git_checkout_head(g_repo, &opts)); + + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); +} + +void test_checkout_crlf__autocrlf_false_text_auto_attr(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); + + cl_repo_set_bool(g_repo, "core.autocrlf", false); + + cl_git_pass(git_checkout_head(g_repo, &opts)); + + if (GIT_EOL_NATIVE == GIT_EOL_CRLF) { + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF); + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_AS_CRLF); + } else { + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); + } +} + +void test_checkout_crlf__autocrlf_true_text_auto_attr(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_git_pass(git_checkout_head(g_repo, &opts)); + + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF); + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_AS_CRLF); +} + +void test_checkout_crlf__autocrlf_input_text_auto_attr(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); + + cl_repo_set_string(g_repo, "core.autocrlf", "input"); + + cl_git_pass(git_checkout_head(g_repo, &opts)); + + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); +} + +void test_checkout_crlf__can_write_empty_file(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/empty-files")); + cl_git_pass(git_checkout_head(g_repo, &opts)); + + check_file_contents("./crlf/test1.txt", ""); + + check_file_contents("./crlf/test2.txt", "test2.txt's content\r\n"); + + check_file_contents("./crlf/test3.txt", ""); +} diff --git a/tests/libgit2/checkout/head.c b/tests/libgit2/checkout/head.c new file mode 100644 index 000000000..3b0bf4729 --- /dev/null +++ b/tests/libgit2/checkout/head.c @@ -0,0 +1,292 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "repo/repo_helpers.h" +#include "path.h" +#include "futils.h" + +static git_repository *g_repo; + +void test_checkout_head__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_checkout_head__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_checkout_head__unborn_head_returns_GIT_EUNBORNBRANCH(void) +{ + make_head_unborn(g_repo, NON_EXISTING_HEAD); + + cl_assert_equal_i(GIT_EUNBORNBRANCH, git_checkout_head(g_repo, NULL)); +} + +void test_checkout_head__with_index_only_tree(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_index *index; + + /* let's start by getting things into a known state */ + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_head(g_repo, &opts)); + + /* now let's stage some new stuff including a new directory */ + + cl_git_pass(git_repository_index(&index, g_repo)); + + p_mkdir("testrepo/newdir", 0777); + cl_git_mkfile("testrepo/newdir/newfile.txt", "new file\n"); + + cl_git_pass(git_index_add_bypath(index, "newdir/newfile.txt")); + cl_git_pass(git_index_write(index)); + + cl_assert(git_fs_path_isfile("testrepo/newdir/newfile.txt")); + cl_assert(git_index_get_bypath(index, "newdir/newfile.txt", 0) != NULL); + + git_index_free(index); + + /* okay, so now we have staged this new file; let's see if we can remove */ + + opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_assert(!git_fs_path_isfile("testrepo/newdir/newfile.txt")); + cl_assert(git_index_get_bypath(index, "newdir/newfile.txt", 0) == NULL); + + git_index_free(index); +} + +void test_checkout_head__do_not_remove_untracked_file(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_index *index; + + cl_git_pass(p_mkdir("testrepo/tracked", 0755)); + cl_git_mkfile("testrepo/tracked/tracked", "tracked\n"); + cl_git_mkfile("testrepo/tracked/untracked", "untracked\n"); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "tracked/tracked")); + cl_git_pass(git_index_write(index)); + + git_index_free(index); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert(!git_fs_path_isfile("testrepo/tracked/tracked")); + cl_assert(git_fs_path_isfile("testrepo/tracked/untracked")); +} + +void test_checkout_head__do_not_remove_untracked_file_in_subdir(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_index *index; + + cl_git_pass(p_mkdir("testrepo/tracked", 0755)); + cl_git_pass(p_mkdir("testrepo/tracked/subdir", 0755)); + cl_git_mkfile("testrepo/tracked/tracked", "tracked\n"); + cl_git_mkfile("testrepo/tracked/subdir/tracked", "tracked\n"); + cl_git_mkfile("testrepo/tracked/subdir/untracked", "untracked\n"); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "tracked/tracked")); + cl_git_pass(git_index_add_bypath(index, "tracked/subdir/tracked")); + cl_git_pass(git_index_write(index)); + + git_index_free(index); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert(!git_fs_path_isfile("testrepo/tracked/tracked")); + cl_assert(!git_fs_path_isfile("testrepo/tracked/subdir/tracked")); + cl_assert(git_fs_path_isfile("testrepo/tracked/subdir/untracked")); +} + +void test_checkout_head__do_remove_untracked_paths(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_index *index; + char *paths[] = {"tracked/untracked"}; + + cl_git_pass(p_mkdir("testrepo/tracked", 0755)); + cl_git_pass(p_mkdir("testrepo/tracked/subdir", 0755)); + cl_git_mkfile("testrepo/tracked/tracked", "tracked\n"); + cl_git_mkfile("testrepo/tracked/untracked", "untracked\n"); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "tracked/tracked")); + cl_git_pass(git_index_write(index)); + + git_index_free(index); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; + opts.paths.strings = paths; + opts.paths.count = 1; + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert(git_fs_path_isfile("testrepo/tracked/tracked")); + cl_assert(!git_fs_path_isfile("testrepo/tracked/untracked")); +} + +void test_checkout_head__do_remove_tracked_subdir(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_index *index; + + cl_git_pass(p_mkdir("testrepo/subdir", 0755)); + cl_git_pass(p_mkdir("testrepo/subdir/tracked", 0755)); + cl_git_mkfile("testrepo/subdir/tracked-file", "tracked\n"); + cl_git_mkfile("testrepo/subdir/untracked-file", "untracked\n"); + cl_git_mkfile("testrepo/subdir/tracked/tracked1", "tracked\n"); + cl_git_mkfile("testrepo/subdir/tracked/tracked2", "tracked\n"); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "subdir/tracked-file")); + cl_git_pass(git_index_add_bypath(index, "subdir/tracked/tracked1")); + cl_git_pass(git_index_add_bypath(index, "subdir/tracked/tracked2")); + cl_git_pass(git_index_write(index)); + + git_index_free(index); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert(!git_fs_path_isdir("testrepo/subdir/tracked")); + cl_assert(!git_fs_path_isfile("testrepo/subdir/tracked-file")); + cl_assert(git_fs_path_isfile("testrepo/subdir/untracked-file")); +} + +void test_checkout_head__typechange_workdir(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_object *target; + struct stat st; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_revparse_single(&target, g_repo, "HEAD")); + cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); + + cl_must_pass(p_chmod("testrepo/new.txt", 0755)); + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_git_pass(p_stat("testrepo/new.txt", &st)); + cl_assert(!GIT_PERMS_IS_EXEC(st.st_mode)); + + git_object_free(target); +} + +void test_checkout_head__typechange_index_and_workdir(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_object *target; + git_index *index; + struct stat st; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_revparse_single(&target, g_repo, "HEAD")); + cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); + + cl_must_pass(p_chmod("testrepo/new.txt", 0755)); + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "new.txt")); + cl_git_pass(git_index_write(index)); + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_git_pass(p_stat("testrepo/new.txt", &st)); + cl_assert(!GIT_PERMS_IS_EXEC(st.st_mode)); + + git_object_free(target); + git_index_free(index); +} + +void test_checkout_head__workdir_filemode_is_simplified(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_object *target, *branch; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_revparse_single(&target, g_repo, "a38d028f71eaa590febb7d716b1ca32350cf70da")); + cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); + + cl_must_pass(p_chmod("testrepo/branch_file.txt", 0666)); + + /* + * Checkout should not fail with a conflict; though the file mode + * on disk is literally different to the base (0666 vs 0644), Git + * ignores the actual mode and simply treats both as non-executable. + */ + cl_git_pass(git_revparse_single(&branch, g_repo, "099fabac3a9ea935598528c27f866e34089c2eff")); + + opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + cl_git_pass(git_checkout_tree(g_repo, branch, NULL)); + + git_object_free(branch); + git_object_free(target); +} + +void test_checkout_head__obeys_filemode_true(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_object *target, *branch; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + /* In this commit, `README` is executable */ + cl_git_pass(git_revparse_single(&target, g_repo, "f9ed4af42472941da45a3c")); + cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); + + cl_repo_set_bool(g_repo, "core.filemode", true); + cl_must_pass(p_chmod("testrepo/README", 0644)); + + /* + * Checkout will fail with a conflict; the file mode is updated in + * the checkout target, but the contents have changed in our branch. + */ + cl_git_pass(git_revparse_single(&branch, g_repo, "099fabac3a9ea935598528c27f866e34089c2eff")); + + opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + cl_git_fail_with(GIT_ECONFLICT, git_checkout_tree(g_repo, branch, NULL)); + + git_object_free(branch); + git_object_free(target); +} + +void test_checkout_head__obeys_filemode_false(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_object *target, *branch; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + /* In this commit, `README` is executable */ + cl_git_pass(git_revparse_single(&target, g_repo, "f9ed4af42472941da45a3c")); + cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL)); + + cl_repo_set_bool(g_repo, "core.filemode", false); + cl_must_pass(p_chmod("testrepo/README", 0644)); + + /* + * Checkout will fail with a conflict; the file contents are updated + * in the checkout target, but the filemode has changed in our branch. + */ + cl_git_pass(git_revparse_single(&branch, g_repo, "099fabac3a9ea935598528c27f866e34089c2eff")); + + opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; + opts.checkout_strategy |= GIT_CHECKOUT_SAFE; + cl_git_pass(git_checkout_tree(g_repo, branch, NULL)); + + git_object_free(branch); + git_object_free(target); +} diff --git a/tests/libgit2/checkout/icase.c b/tests/libgit2/checkout/icase.c new file mode 100644 index 000000000..d77c7abd5 --- /dev/null +++ b/tests/libgit2/checkout/icase.c @@ -0,0 +1,292 @@ +#include "clar_libgit2.h" + +#include "git2/checkout.h" +#include "refs.h" +#include "path.h" +#include "repository.h" + +#ifdef GIT_WIN32 +# include +#else +# include +#endif + +static git_repository *repo; +static git_object *obj; +static git_checkout_options checkout_opts; + +void test_checkout_icase__initialize(void) +{ + git_oid id; + git_config *cfg; + int icase = 0; + + repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass(git_repository_config_snapshot(&cfg, repo)); + git_config_get_bool(&icase, cfg, "core.ignorecase"); + git_config_free(cfg); + + if (!icase) + cl_skip(); + + cl_git_pass(git_reference_name_to_id(&id, repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, repo, &id, GIT_OBJECT_ANY)); + + git_checkout_options_init(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION); + checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE; +} + +void test_checkout_icase__cleanup(void) +{ + git_object_free(obj); + cl_git_sandbox_cleanup(); +} + +static char *get_filename(const char *in) +{ + char *search_dirname, *search_filename, *filename = NULL; + git_str out = GIT_STR_INIT; + DIR *dir; + struct dirent *de; + + cl_assert(search_dirname = git_fs_path_dirname(in)); + cl_assert(search_filename = git_fs_path_basename(in)); + + cl_assert(dir = opendir(search_dirname)); + + while ((de = readdir(dir))) { + if (strcasecmp(de->d_name, search_filename) == 0) { + git_str_join(&out, '/', search_dirname, de->d_name); + filename = git_str_detach(&out); + break; + } + } + + closedir(dir); + + git__free(search_dirname); + git__free(search_filename); + git_str_dispose(&out); + + return filename; +} + +static void assert_name_is(const char *expected) +{ + char *actual; + size_t actual_len, expected_len, start; + + cl_assert(actual = get_filename(expected)); + + expected_len = strlen(expected); + actual_len = strlen(actual); + cl_assert(actual_len >= expected_len); + + start = actual_len - expected_len; + cl_assert_equal_s(expected, actual + start); + + if (start) + cl_assert_equal_strn("/", actual + (start - 1), 1); + + free(actual); +} + +static int symlink_or_fake(git_repository *repo, const char *a, const char *b) +{ + int symlinks; + + cl_git_pass(git_repository__configmap_lookup(&symlinks, repo, GIT_CONFIGMAP_SYMLINKS)); + + if (symlinks) + return p_symlink(a, b); + else + return git_futils_fake_symlink(a, b); +} + +void test_checkout_icase__refuses_to_overwrite_files_for_files(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + + cl_git_write2file("testrepo/BRANCH_FILE.txt", "neue file\n", 10, \ + O_WRONLY | O_CREAT | O_TRUNC, 0644); + + cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); + assert_name_is("testrepo/BRANCH_FILE.txt"); +} + +void test_checkout_icase__overwrites_files_for_files_when_forced(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_write2file("testrepo/NEW.txt", "neue file\n", 10, \ + O_WRONLY | O_CREAT | O_TRUNC, 0644); + + cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); + assert_name_is("testrepo/new.txt"); +} + +void test_checkout_icase__refuses_to_overwrite_links_for_files(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + + cl_must_pass(symlink_or_fake(repo, "../tmp", "testrepo/BRANCH_FILE.txt")); + + cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); + + cl_assert(!git_fs_path_exists("tmp")); + assert_name_is("testrepo/BRANCH_FILE.txt"); +} + +void test_checkout_icase__overwrites_links_for_files_when_forced(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_must_pass(symlink_or_fake(repo, "../tmp", "testrepo/NEW.txt")); + + cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); + + cl_assert(!git_fs_path_exists("tmp")); + assert_name_is("testrepo/new.txt"); +} + +void test_checkout_icase__overwrites_empty_folders_for_files(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + + cl_must_pass(p_mkdir("testrepo/NEW.txt", 0777)); + + cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); + + assert_name_is("testrepo/new.txt"); + cl_assert(!git_fs_path_isdir("testrepo/new.txt")); +} + +void test_checkout_icase__refuses_to_overwrite_populated_folders_for_files(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + + cl_must_pass(p_mkdir("testrepo/BRANCH_FILE.txt", 0777)); + cl_git_write2file("testrepo/BRANCH_FILE.txt/foobar", "neue file\n", 10, \ + O_WRONLY | O_CREAT | O_TRUNC, 0644); + + cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); + + assert_name_is("testrepo/BRANCH_FILE.txt"); + cl_assert(git_fs_path_isdir("testrepo/BRANCH_FILE.txt")); +} + +void test_checkout_icase__overwrites_folders_for_files_when_forced(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_must_pass(p_mkdir("testrepo/NEW.txt", 0777)); + cl_git_write2file("testrepo/NEW.txt/foobar", "neue file\n", 10, \ + O_WRONLY | O_CREAT | O_TRUNC, 0644); + + cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); + + assert_name_is("testrepo/new.txt"); + cl_assert(!git_fs_path_isdir("testrepo/new.txt")); +} + +void test_checkout_icase__refuses_to_overwrite_files_for_folders(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + + cl_git_write2file("testrepo/A", "neue file\n", 10, \ + O_WRONLY | O_CREAT | O_TRUNC, 0644); + + cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); + assert_name_is("testrepo/A"); + cl_assert(!git_fs_path_isdir("testrepo/A")); +} + +void test_checkout_icase__overwrites_files_for_folders_when_forced(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_write2file("testrepo/A", "neue file\n", 10, \ + O_WRONLY | O_CREAT | O_TRUNC, 0644); + + cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); + assert_name_is("testrepo/a"); + cl_assert(git_fs_path_isdir("testrepo/a")); +} + +void test_checkout_icase__refuses_to_overwrite_links_for_folders(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + + cl_must_pass(symlink_or_fake(repo, "..", "testrepo/A")); + + cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); + + cl_assert(!git_fs_path_exists("b.txt")); + assert_name_is("testrepo/A"); +} + +void test_checkout_icase__overwrites_links_for_folders_when_forced(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_must_pass(symlink_or_fake(repo, "..", "testrepo/A")); + + cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); + + cl_assert(!git_fs_path_exists("b.txt")); + assert_name_is("testrepo/a"); +} + +void test_checkout_icase__ignores_unstaged_casechange(void) +{ + git_reference *orig_ref, *br2_ref; + git_commit *orig, *br2; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_lookup_resolved(&orig_ref, repo, "HEAD", 100)); + cl_git_pass(git_commit_lookup(&orig, repo, git_reference_target(orig_ref))); + cl_git_pass(git_reset(repo, (git_object *)orig, GIT_RESET_HARD, NULL)); + + cl_rename("testrepo/branch_file.txt", "testrepo/Branch_File.txt"); + + cl_git_pass(git_reference_lookup_resolved(&br2_ref, repo, "refs/heads/br2", 100)); + cl_git_pass(git_commit_lookup(&br2, repo, git_reference_target(br2_ref))); + + cl_git_pass(git_checkout_tree(repo, (const git_object *)br2, &checkout_opts)); + + git_commit_free(orig); + git_commit_free(br2); + git_reference_free(orig_ref); + git_reference_free(br2_ref); +} + +void test_checkout_icase__conflicts_with_casechanged_subtrees(void) +{ + git_reference *orig_ref; + git_object *orig, *subtrees; + git_oid oid; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_lookup_resolved(&orig_ref, repo, "HEAD", 100)); + cl_git_pass(git_object_lookup(&orig, repo, git_reference_target(orig_ref), GIT_OBJECT_COMMIT)); + cl_git_pass(git_reset(repo, (git_object *)orig, GIT_RESET_HARD, NULL)); + + cl_must_pass(p_mkdir("testrepo/AB", 0777)); + cl_must_pass(p_mkdir("testrepo/AB/C", 0777)); + cl_git_write2file("testrepo/AB/C/3.txt", "Foobar!\n", 8, O_RDWR|O_CREAT, 0666); + + cl_git_pass(git_reference_name_to_id(&oid, repo, "refs/heads/subtrees")); + cl_git_pass(git_object_lookup(&subtrees, repo, &oid, GIT_OBJECT_ANY)); + + cl_git_fail(git_checkout_tree(repo, subtrees, &checkout_opts)); + + git_object_free(orig); + git_object_free(subtrees); + git_reference_free(orig_ref); +} + diff --git a/tests/libgit2/checkout/index.c b/tests/libgit2/checkout/index.c new file mode 100644 index 000000000..6a80d22c5 --- /dev/null +++ b/tests/libgit2/checkout/index.c @@ -0,0 +1,881 @@ +#include "clar_libgit2.h" +#include "checkout_helpers.h" + +#include "git2/checkout.h" +#include "futils.h" +#include "repository.h" +#include "remote.h" +#include "repo/repo_helpers.h" + +static git_repository *g_repo; +static git_str g_global_path = GIT_STR_INIT; + +void test_checkout_index__initialize(void) +{ + git_tree *tree; + + g_repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass(git_repository_head_tree(&tree, g_repo)); + + reset_index_to_treeish((git_object *)tree); + git_tree_free(tree); + + cl_git_rewritefile( + "./testrepo/.gitattributes", + "* text eol=lf\n"); + + git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, + &g_global_path); +} + +void test_checkout_index__cleanup(void) +{ + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, + g_global_path.ptr); + git_str_dispose(&g_global_path); + + cl_git_sandbox_cleanup(); + + /* try to remove directories created by tests */ + cl_fixture_cleanup("alternative"); + cl_fixture_cleanup("symlink"); + cl_fixture_cleanup("symlink.git"); + cl_fixture_cleanup("tmp_global_path"); +} + +void test_checkout_index__cannot_checkout_a_bare_repository(void) +{ + cl_git_sandbox_cleanup(); + g_repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_fail(git_checkout_index(g_repo, NULL, NULL)); +} + +void test_checkout_index__can_create_missing_files(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/new.txt")); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/README", "hey there\n"); + check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + check_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__can_remove_untracked_files(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + git_futils_mkdir("./testrepo/dir/subdir/subsubdir", 0755, GIT_MKDIR_PATH); + cl_git_mkfile("./testrepo/dir/one", "one\n"); + cl_git_mkfile("./testrepo/dir/subdir/two", "two\n"); + + cl_assert_equal_i(true, git_fs_path_isdir("./testrepo/dir/subdir/subsubdir")); + + opts.checkout_strategy = + GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_RECREATE_MISSING | + GIT_CHECKOUT_REMOVE_UNTRACKED; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/dir")); +} + +void test_checkout_index__can_disable_pathspec_match(void) +{ + char *files_to_checkout[] = { "test10.txt", "test11.txt"}; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_object *objects; + git_index *index; + + /* reset to beginning of history (i.e. just a README file) */ + opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; + + cl_git_pass(git_revparse_single(&objects, g_repo, "8496071c1b46c854b31185ea97743be6a8774479")); + cl_git_pass(git_checkout_tree(g_repo, objects, &opts)); + cl_git_pass(git_repository_set_head_detached(g_repo, git_object_id(objects))); + git_object_free(objects); + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* We create 4 files and commit them */ + cl_git_mkfile("testrepo/test9.txt", "original\n"); + cl_git_mkfile("testrepo/test10.txt", "original\n"); + cl_git_mkfile("testrepo/test11.txt", "original\n"); + cl_git_mkfile("testrepo/test12.txt", "original\n"); + + cl_git_pass(git_index_add_bypath(index, "test9.txt")); + cl_git_pass(git_index_add_bypath(index, "test10.txt")); + cl_git_pass(git_index_add_bypath(index, "test11.txt")); + cl_git_pass(git_index_add_bypath(index, "test12.txt")); + cl_git_pass(git_index_write(index)); + + cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "commit our test files"); + + /* We modify the content of all 4 of our files */ + cl_git_rewritefile("testrepo/test9.txt", "modified\n"); + cl_git_rewritefile("testrepo/test10.txt", "modified\n"); + cl_git_rewritefile("testrepo/test11.txt", "modified\n"); + cl_git_rewritefile("testrepo/test12.txt", "modified\n"); + + /* We checkout only test10.txt and test11.txt */ + opts.checkout_strategy = + GIT_CHECKOUT_FORCE | + GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; + opts.paths.strings = files_to_checkout; + opts.paths.count = ARRAY_SIZE(files_to_checkout); + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + /* The only files that have been reverted to their original content + should be test10.txt and test11.txt */ + check_file_contents("testrepo/test9.txt", "modified\n"); + check_file_contents("testrepo/test10.txt", "original\n"); + check_file_contents("testrepo/test11.txt", "original\n"); + check_file_contents("testrepo/test12.txt", "modified\n"); + + git_index_free(index); +} + +void test_checkout_index__honor_the_specified_pathspecs(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + char *entries[] = { "*.txt" }; + + opts.paths.strings = entries; + opts.paths.count = 1; + + cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/new.txt")); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/README")); + check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + check_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__honor_the_gitattributes_directives(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + const char *attributes = + "branch_file.txt text eol=crlf\n" + "new.txt text eol=lf\n"; + + cl_git_mkfile("./testrepo/.gitattributes", attributes); + cl_repo_set_bool(g_repo, "core.autocrlf", false); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/README", "hey there\n"); + check_file_contents("./testrepo/new.txt", "my new file\n"); + check_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); +} + +void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) +{ +#ifdef GIT_WIN32 + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + const char *expected_readme_text = "hey there\r\n"; + + cl_git_pass(p_unlink("./testrepo/.gitattributes")); + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/README", expected_readme_text); +#endif +} + +static void populate_symlink_workdir(void) +{ + git_str path = GIT_STR_INIT; + git_repository *repo; + git_remote *origin; + git_object *target; + + const char *url = git_repository_path(g_repo); + + cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), "symlink.git")); + cl_git_pass(git_repository_init(&repo, path.ptr, true)); + cl_git_pass(git_repository_set_workdir(repo, "symlink", 1)); + + /* Delete the `origin` repo (if it exists) so we can recreate it. */ + git_remote_delete(repo, GIT_REMOTE_ORIGIN); + + cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); + git_remote_free(origin); + + cl_git_pass(git_revparse_single(&target, repo, "remotes/origin/master")); + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); + + git_object_free(target); + git_repository_free(repo); + git_str_dispose(&path); +} + +void test_checkout_index__honor_coresymlinks_default_true(void) +{ + char link_data[GIT_PATH_MAX]; + int link_size = GIT_PATH_MAX; + + cl_must_pass(p_mkdir("symlink", 0777)); + + if (!git_fs_path_supports_symlinks("symlink/test")) + cl_skip(); + +#ifdef GIT_WIN32 + /* + * Windows explicitly requires the global configuration to have + * core.symlinks=true in addition to actual filesystem support. + */ + create_tmp_global_config("tmp_global_path", "core.symlinks", "true"); +#endif + + populate_symlink_workdir(); + + link_size = p_readlink("./symlink/link_to_new.txt", link_data, link_size); + cl_assert(link_size >= 0); + + link_data[link_size] = '\0'; + cl_assert_equal_i(link_size, strlen("new.txt")); + cl_assert_equal_s(link_data, "new.txt"); + check_file_contents("./symlink/link_to_new.txt", "my new file\n"); +} + +void test_checkout_index__honor_coresymlinks_default_false(void) +{ + cl_must_pass(p_mkdir("symlink", 0777)); + +#ifndef GIT_WIN32 + /* + * This test is largely for Windows platforms to ensure that + * we respect an unset core.symlinks even when the platform + * supports symlinks. Bail entirely on POSIX platforms that + * do support symlinks. + */ + if (git_fs_path_supports_symlinks("symlink/test")) + cl_skip(); +#endif + + populate_symlink_workdir(); + check_file_contents("./symlink/link_to_new.txt", "new.txt"); +} + +void test_checkout_index__coresymlinks_set_to_true_fails_when_unsupported(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + if (git_fs_path_supports_symlinks("testrepo/test")) { + cl_skip(); + } + + cl_repo_set_bool(g_repo, "core.symlinks", true); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + cl_git_fail(git_checkout_index(g_repo, NULL, &opts)); +} + +void test_checkout_index__honor_coresymlinks_setting_set_to_true(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + char link_data[GIT_PATH_MAX]; + size_t link_size = GIT_PATH_MAX; + + if (!git_fs_path_supports_symlinks("testrepo/test")) { + cl_skip(); + } + + cl_repo_set_bool(g_repo, "core.symlinks", true); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size); + link_data[link_size] = '\0'; + cl_assert_equal_i(link_size, strlen("new.txt")); + cl_assert_equal_s(link_data, "new.txt"); + check_file_contents("./testrepo/link_to_new.txt", "my new file\n"); +} + +void test_checkout_index__honor_coresymlinks_setting_set_to_false(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_repo_set_bool(g_repo, "core.symlinks", false); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/link_to_new.txt", "new.txt"); +} + +void test_checkout_index__donot_overwrite_modified_file_by_default(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + + /* set this up to not return an error code on conflicts, but it + * still will not have permission to overwrite anything... + */ + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/new.txt", "This isn't what's stored!"); +} + +void test_checkout_index__can_overwrite_modified_file(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__options_disable_filters(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.disable_filters = false; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/new.txt", "my new file\r\n"); + + p_unlink("./testrepo/new.txt"); + + opts.disable_filters = true; + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__options_dir_modes(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + struct stat st; + git_oid oid; + git_commit *commit; + mode_t um; + + if (!cl_is_chmod_supported()) + return; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); + + reset_index_to_treeish((git_object *)commit); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.dir_mode = 0701; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + /* umask will influence actual directory creation mode */ + (void)p_umask(um = p_umask(022)); + + cl_git_pass(p_stat("./testrepo/a", &st)); + /* Haiku & Hurd use other mode bits, so we must mask them out */ + cl_assert_equal_i_fmt(st.st_mode & (S_IFMT | 07777), (GIT_FILEMODE_TREE | 0701) & ~um, "%07o"); + + /* File-mode test, since we're on the 'dir' branch */ + cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); + cl_assert_equal_i_fmt(st.st_mode & (S_IFMT | 07777), GIT_FILEMODE_BLOB_EXECUTABLE & ~um, "%07o"); + + git_commit_free(commit); +} + +void test_checkout_index__options_override_file_modes(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + struct stat st; + + if (!cl_is_chmod_supported()) + return; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.file_mode = 0700; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_git_pass(p_stat("./testrepo/new.txt", &st)); + cl_assert_equal_i_fmt(st.st_mode & GIT_MODE_PERMS_MASK, 0700, "%07o"); +} + +void test_checkout_index__options_open_flags(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_git_mkfile("./testrepo/new.txt", "hi\n"); + + opts.checkout_strategy = + GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_REMOVE_EXISTING; + opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); +} + +struct notify_data { + const char *file; + const char *sha; +}; + +static int test_checkout_notify_cb( + git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload) +{ + struct notify_data *expectations = (struct notify_data *)payload; + + GIT_UNUSED(workdir); + + cl_assert_equal_i(GIT_CHECKOUT_NOTIFY_CONFLICT, why); + cl_assert_equal_s(expectations->file, path); + cl_assert_equal_i(0, git_oid_streq(&baseline->id, expectations->sha)); + cl_assert_equal_i(0, git_oid_streq(&target->id, expectations->sha)); + + return 0; +} + +void test_checkout_index__can_notify_of_skipped_files(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + struct notify_data data; + + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + + /* + * $ git ls-tree HEAD + * 100644 blob a8233120f6ad708f843d861ce2b7228ec4e3dec6 README + * 100644 blob 3697d64be941a53d4ae8f6a271e4e3fa56b022cc branch_file.txt + * 100644 blob a71586c1dfe8a71c6cbf6c129f404c5642ff31bd new.txt + */ + data.file = "new.txt"; + data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_RECREATE_MISSING | + GIT_CHECKOUT_ALLOW_CONFLICTS; + opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT; + opts.notify_cb = test_checkout_notify_cb; + opts.notify_payload = &data; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); +} + +static int dont_notify_cb( + git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload) +{ + GIT_UNUSED(why); + GIT_UNUSED(path); + GIT_UNUSED(baseline); + GIT_UNUSED(target); + GIT_UNUSED(workdir); + GIT_UNUSED(payload); + + cl_assert(false); + + return 0; +} + +void test_checkout_index__wont_notify_of_expected_line_ending_changes(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_git_pass(p_unlink("./testrepo/.gitattributes")); + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_git_mkfile("./testrepo/new.txt", "my new file\r\n"); + + opts.checkout_strategy = + GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_RECREATE_MISSING | + GIT_CHECKOUT_ALLOW_CONFLICTS; + opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT; + opts.notify_cb = dont_notify_cb; + opts.notify_payload = NULL; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); +} + +static void checkout_progress_counter( + const char *path, size_t cur, size_t tot, void *payload) +{ + GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); + (*(int *)payload)++; +} + +void test_checkout_index__calls_progress_callback(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + int calls = 0; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + opts.progress_cb = checkout_progress_counter; + opts.progress_payload = &calls; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + cl_assert(calls > 0); +} + +void test_checkout_index__can_overcome_name_clashes(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_index *index; + + cl_git_pass(git_repository_index(&index, g_repo)); + git_index_clear(index); + + cl_git_mkfile("./testrepo/path0", "content\r\n"); + cl_git_pass(p_mkdir("./testrepo/path1", 0777)); + cl_git_mkfile("./testrepo/path1/file1", "content\r\n"); + + cl_git_pass(git_index_add_bypath(index, "path0")); + cl_git_pass(git_index_add_bypath(index, "path1/file1")); + + cl_git_pass(p_unlink("./testrepo/path0")); + cl_git_pass(git_futils_rmdir_r( + "./testrepo/path1", NULL, GIT_RMDIR_REMOVE_FILES)); + + cl_git_mkfile("./testrepo/path1", "content\r\n"); + cl_git_pass(p_mkdir("./testrepo/path0", 0777)); + cl_git_mkfile("./testrepo/path0/file0", "content\r\n"); + + cl_assert(git_fs_path_isfile("./testrepo/path1")); + cl_assert(git_fs_path_isfile("./testrepo/path0/file0")); + + opts.checkout_strategy = + GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_RECREATE_MISSING | + GIT_CHECKOUT_ALLOW_CONFLICTS; + cl_git_pass(git_checkout_index(g_repo, index, &opts)); + + cl_assert(git_fs_path_isfile("./testrepo/path1")); + cl_assert(git_fs_path_isfile("./testrepo/path0/file0")); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_index(g_repo, index, &opts)); + + cl_assert(git_fs_path_isfile("./testrepo/path0")); + cl_assert(git_fs_path_isfile("./testrepo/path1/file1")); + + git_index_free(index); +} + +void test_checkout_index__validates_struct_version(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + const git_error *err; + + opts.version = 1024; + cl_git_fail(git_checkout_index(g_repo, NULL, &opts)); + + err = git_error_last(); + cl_assert_equal_i(err->klass, GIT_ERROR_INVALID); + + opts.version = 0; + git_error_clear(); + cl_git_fail(git_checkout_index(g_repo, NULL, &opts)); + + err = git_error_last(); + cl_assert_equal_i(err->klass, GIT_ERROR_INVALID); +} + +void test_checkout_index__can_update_prefixed_files(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/new.txt")); + + cl_git_mkfile("./testrepo/READ", "content\n"); + cl_git_mkfile("./testrepo/README.after", "content\n"); + cl_git_pass(p_mkdir("./testrepo/branch_file", 0777)); + cl_git_pass(p_mkdir("./testrepo/branch_file/contained_dir", 0777)); + cl_git_mkfile("./testrepo/branch_file/contained_file", "content\n"); + cl_git_pass(p_mkdir("./testrepo/branch_file.txt.after", 0777)); + + opts.checkout_strategy = + GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_RECREATE_MISSING | + GIT_CHECKOUT_REMOVE_UNTRACKED; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + /* remove untracked will remove the .gitattributes file before the blobs + * were created, so they will have had crlf filtering applied on Windows + */ + check_file_contents_nocr("./testrepo/README", "hey there\n"); + check_file_contents_nocr("./testrepo/branch_file.txt", "hi\nbye!\n"); + check_file_contents_nocr("./testrepo/new.txt", "my new file\n"); + + cl_assert(!git_fs_path_exists("testrepo/READ")); + cl_assert(!git_fs_path_exists("testrepo/README.after")); + cl_assert(!git_fs_path_exists("testrepo/branch_file")); + cl_assert(!git_fs_path_exists("testrepo/branch_file.txt.after")); +} + +void test_checkout_index__can_checkout_a_newly_initialized_repository(void) +{ + cl_git_sandbox_cleanup(); + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_remove_placeholders(git_repository_path(g_repo), "dummy-marker.txt"); + + cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); +} + +void test_checkout_index__issue_1397(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_git_sandbox_cleanup(); + g_repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./issue_1397/crlf_file.txt", "first line\r\nsecond line\r\nboth with crlf"); +} + +void test_checkout_index__target_directory(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + checkout_counts cts; + memset(&cts, 0, sizeof(cts)); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_RECREATE_MISSING; + opts.target_directory = "alternative"; + cl_assert(!git_fs_path_isdir("alternative")); + + opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; + opts.notify_cb = checkout_count_callback; + opts.notify_payload = &cts; + + /* create some files that *would* conflict if we were using the wd */ + cl_git_mkfile("testrepo/README", "I'm in the way!\n"); + cl_git_mkfile("testrepo/new.txt", "my new file\n"); + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_assert_equal_i(0, cts.n_untracked); + cl_assert_equal_i(0, cts.n_ignored); + cl_assert_equal_i(4, cts.n_updates); + + check_file_contents("./alternative/README", "hey there\n"); + check_file_contents("./alternative/branch_file.txt", "hi\nbye!\n"); + check_file_contents("./alternative/new.txt", "my new file\n"); + + cl_git_pass(git_futils_rmdir_r( + "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_checkout_index__target_directory_from_bare(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_index *index; + git_object *head = NULL; + checkout_counts cts; + memset(&cts, 0, sizeof(cts)); + + cl_git_sandbox_cleanup(); + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_assert(git_repository_is_bare(g_repo)); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_revparse_single(&head, g_repo, "HEAD^{tree}")); + cl_git_pass(git_index_read_tree(index, (const git_tree *)head)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_RECREATE_MISSING; + + opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; + opts.notify_cb = checkout_count_callback; + opts.notify_payload = &cts; + + /* fail to checkout a bare repo */ + cl_git_fail(git_checkout_index(g_repo, NULL, &opts)); + + opts.target_directory = "alternative"; + cl_assert(!git_fs_path_isdir("alternative")); + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_assert_equal_i(0, cts.n_untracked); + cl_assert_equal_i(0, cts.n_ignored); + cl_assert_equal_i(3, cts.n_updates); + + /* files will have been filtered if needed, so strip CR */ + check_file_contents_nocr("./alternative/README", "hey there\n"); + check_file_contents_nocr("./alternative/branch_file.txt", "hi\nbye!\n"); + check_file_contents_nocr("./alternative/new.txt", "my new file\n"); + + cl_git_pass(git_futils_rmdir_r( + "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); + + git_object_free(head); +} + +void test_checkout_index__can_get_repo_from_index(void) +{ + git_index *index; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_fs_path_isfile("./testrepo/new.txt")); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_checkout_index(NULL, index, &opts)); + + check_file_contents("./testrepo/README", "hey there\n"); + check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + check_file_contents("./testrepo/new.txt", "my new file\n"); + + git_index_free(index); +} + +static void add_conflict(git_index *index, const char *path) +{ + git_index_entry entry; + + memset(&entry, 0, sizeof(git_index_entry)); + + entry.mode = 0100644; + entry.path = path; + + git_oid_fromstr(&entry.id, "d427e0b2e138501a3d15cc376077a3631e15bd46"); + GIT_INDEX_ENTRY_STAGE_SET(&entry, 1); + cl_git_pass(git_index_add(index, &entry)); + + git_oid_fromstr(&entry.id, "4e886e602529caa9ab11d71f86634bd1b6e0de10"); + GIT_INDEX_ENTRY_STAGE_SET(&entry, 2); + cl_git_pass(git_index_add(index, &entry)); + + git_oid_fromstr(&entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); + GIT_INDEX_ENTRY_STAGE_SET(&entry, 3); + cl_git_pass(git_index_add(index, &entry)); +} + +void test_checkout_index__writes_conflict_file(void) +{ + git_index *index; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_str conflicting_buf = GIT_STR_INIT; + + cl_git_pass(git_repository_index(&index, g_repo)); + + add_conflict(index, "conflicting.txt"); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, "testrepo/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, + "<<<<<<< ours\n" + "this file is changed in master and branch\n" + "=======\n" + "this file is changed in branch and master\n" + ">>>>>>> theirs\n") == 0); + git_str_dispose(&conflicting_buf); + + git_index_free(index); +} + +void test_checkout_index__adding_conflict_removes_stage_0(void) +{ + git_index *new_index, *index; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_git_pass(git_index_new(&new_index)); + + add_conflict(new_index, "new.txt"); + cl_git_pass(git_checkout_index(g_repo, new_index, &opts)); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_assert(git_index_get_bypath(index, "new.txt", 0) == NULL); + cl_assert(git_index_get_bypath(index, "new.txt", 1) != NULL); + cl_assert(git_index_get_bypath(index, "new.txt", 2) != NULL); + cl_assert(git_index_get_bypath(index, "new.txt", 3) != NULL); + + git_index_free(index); + git_index_free(new_index); +} + +void test_checkout_index__conflicts_honor_coreautocrlf(void) +{ +#ifdef GIT_WIN32 + git_index *index; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_str conflicting_buf = GIT_STR_INIT; + + cl_git_pass(p_unlink("./testrepo/.gitattributes")); + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_git_pass(git_repository_index(&index, g_repo)); + + add_conflict(index, "conflicting.txt"); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, "testrepo/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, + "<<<<<<< ours\r\n" + "this file is changed in master and branch\r\n" + "=======\r\n" + "this file is changed in branch and master\r\n" + ">>>>>>> theirs\r\n") == 0); + git_str_dispose(&conflicting_buf); + + git_index_free(index); +#endif +} diff --git a/tests/libgit2/checkout/nasty.c b/tests/libgit2/checkout/nasty.c new file mode 100644 index 000000000..732f1d5e8 --- /dev/null +++ b/tests/libgit2/checkout/nasty.c @@ -0,0 +1,386 @@ +#include "clar_libgit2.h" +#include "checkout_helpers.h" + +#include "git2/checkout.h" +#include "repository.h" +#include "futils.h" + +static const char *repo_name = "nasty"; +static git_repository *repo; +static git_checkout_options checkout_opts; + +void test_checkout_nasty__initialize(void) +{ + repo = cl_git_sandbox_init(repo_name); + + GIT_INIT_STRUCTURE(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION); + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; +} + +void test_checkout_nasty__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void test_checkout_passes(const char *refname, const char *filename) +{ + git_oid commit_id; + git_commit *commit; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&path, repo_name, filename)); + + cl_git_pass(git_reference_name_to_id(&commit_id, repo, refname)); + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE | + GIT_CHECKOUT_DONT_UPDATE_INDEX; + + cl_git_pass(git_checkout_tree(repo, (const git_object *)commit, &opts)); + cl_assert(!git_fs_path_exists(path.ptr)); + + git_commit_free(commit); + git_str_dispose(&path); +} + +static void test_checkout_fails(const char *refname, const char *filename) +{ + git_oid commit_id; + git_commit *commit; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&path, repo_name, filename)); + + cl_git_pass(git_reference_name_to_id(&commit_id, repo, refname)); + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_fail(git_checkout_tree(repo, (const git_object *)commit, &opts)); + cl_assert(!git_fs_path_exists(path.ptr)); + + git_commit_free(commit); + git_str_dispose(&path); +} + +/* A tree that contains ".git" as a tree, with a blob inside + * (".git/foobar"). + */ +void test_checkout_nasty__dotgit_tree(void) +{ + test_checkout_fails("refs/heads/dotgit_tree", ".git/foobar"); +} + +/* A tree that contains ".GIT" as a tree, with a blob inside + * (".GIT/foobar"). + */ +void test_checkout_nasty__dotcapitalgit_tree(void) +{ + test_checkout_fails("refs/heads/dotcapitalgit_tree", ".GIT/foobar"); +} + +/* A tree that contains a tree ".", with a blob inside ("./foobar"). + */ +void test_checkout_nasty__dot_tree(void) +{ + test_checkout_fails("refs/heads/dot_tree", "foobar"); +} + +/* A tree that contains a tree ".", with a tree ".git", with a blob + * inside ("./.git/foobar"). + */ +void test_checkout_nasty__dot_dotgit_tree(void) +{ + test_checkout_fails("refs/heads/dot_dotgit_tree", ".git/foobar"); +} + +/* A tree that contains a tree, with a tree "..", with a tree ".git", with a + * blob inside ("foo/../.git/foobar"). + */ +void test_checkout_nasty__dotdot_dotgit_tree(void) +{ + test_checkout_fails("refs/heads/dotdot_dotgit_tree", ".git/foobar"); +} + +/* A tree that contains a tree, with a tree "..", with a blob inside + * ("foo/../foobar"). + */ +void test_checkout_nasty__dotdot_tree(void) +{ + test_checkout_fails("refs/heads/dotdot_tree", "foobar"); +} + +/* A tree that contains a blob with the rogue name ".git/foobar" */ +void test_checkout_nasty__dotgit_path(void) +{ + test_checkout_fails("refs/heads/dotgit_path", ".git/foobar"); +} + +/* A tree that contains a blob with the rogue name ".GIT/foobar" */ +void test_checkout_nasty__dotcapitalgit_path(void) +{ + test_checkout_fails("refs/heads/dotcapitalgit_path", ".GIT/foobar"); +} + +/* A tree that contains a blob with the rogue name "./.git/foobar" */ +void test_checkout_nasty__dot_dotgit_path(void) +{ + test_checkout_fails("refs/heads/dot_dotgit_path", ".git/foobar"); +} + +/* A tree that contains a blob with the rogue name "./.GIT/foobar" */ +void test_checkout_nasty__dot_dotcapitalgit_path(void) +{ + test_checkout_fails("refs/heads/dot_dotcapitalgit_path", ".GIT/foobar"); +} + +/* A tree that contains a blob with the rogue name "foo/../.git/foobar" */ +void test_checkout_nasty__dotdot_dotgit_path(void) +{ + test_checkout_fails("refs/heads/dotdot_dotgit_path", ".git/foobar"); +} + +/* A tree that contains a blob with the rogue name "foo/../.GIT/foobar" */ +void test_checkout_nasty__dotdot_dotcapitalgit_path(void) +{ + test_checkout_fails("refs/heads/dotdot_dotcapitalgit_path", ".GIT/foobar"); +} + +/* A tree that contains a blob with the rogue name "foo/." */ +void test_checkout_nasty__dot_path(void) +{ + test_checkout_fails("refs/heads/dot_path", "./foobar"); +} + +/* A tree that contains a blob with the rogue name "foo/." */ +void test_checkout_nasty__dot_path_two(void) +{ + test_checkout_fails("refs/heads/dot_path_two", "foo/."); +} + +/* A tree that contains a blob with the rogue name "foo/../foobar" */ +void test_checkout_nasty__dotdot_path(void) +{ + test_checkout_fails("refs/heads/dotdot_path", "foobar"); +} + +/* A tree that contains an entry with a backslash ".git\foobar" */ +void test_checkout_nasty__dotgit_backslash_path(void) +{ +#ifdef GIT_WIN32 + test_checkout_fails("refs/heads/dotgit_backslash_path", ".git/foobar"); +#endif +} + +/* A tree that contains an entry with a backslash ".GIT\foobar" */ +void test_checkout_nasty__dotcapitalgit_backslash_path(void) +{ +#ifdef GIT_WIN32 + test_checkout_fails("refs/heads/dotcapitalgit_backslash_path", ".GIT/foobar"); +#endif +} + +/* A tree that contains an entry with a backslash ".\.GIT\foobar" */ +void test_checkout_nasty__dot_backslash_dotcapitalgit_path(void) +{ +#ifdef GIT_WIN32 + test_checkout_fails("refs/heads/dot_backslash_dotcapitalgit_path", ".GIT/foobar"); +#endif +} + +/* A tree that contains an entry ".git.", because Win32 APIs will drop the + * trailing slash. + */ +void test_checkout_nasty__dot_git_dot(void) +{ +#ifdef GIT_WIN32 + test_checkout_fails("refs/heads/dot_git_dot", ".git/foobar"); +#endif +} + +/* A tree that contains an entry "git~1", because that is typically the + * short name for ".git". + */ +void test_checkout_nasty__git_tilde1(void) +{ + test_checkout_fails("refs/heads/git_tilde1", ".git/foobar"); + test_checkout_fails("refs/heads/git_tilde1", "git~1/foobar"); +} + +/* A tree that contains an entry "git~2", when we have forced the short + * name for ".git" into "GIT~2". + */ +void test_checkout_nasty__git_custom_shortname(void) +{ +#ifdef GIT_WIN32 + if (!cl_sandbox_supports_8dot3()) + clar__skip(); + + cl_must_pass(p_rename("nasty/.git", "nasty/_temp")); + cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666); + cl_must_pass(p_rename("nasty/_temp", "nasty/.git")); + test_checkout_fails("refs/heads/git_tilde2", ".git/foobar"); +#endif +} + +/* A tree that contains an entry "git~3", which should be allowed, since + * it is not the typical short name ("GIT~1") or the actual short name + * ("GIT~2") for ".git". + */ +void test_checkout_nasty__only_looks_like_a_git_shortname(void) +{ +#ifdef GIT_WIN32 + git_oid commit_id; + git_commit *commit; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_must_pass(p_rename("nasty/.git", "nasty/_temp")); + cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666); + cl_must_pass(p_rename("nasty/_temp", "nasty/.git")); + + cl_git_pass(git_reference_name_to_id(&commit_id, repo, "refs/heads/git_tilde3")); + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_checkout_tree(repo, (const git_object *)commit, &opts)); + cl_assert(git_fs_path_exists("nasty/git~3/foobar")); + + git_commit_free(commit); +#endif +} + +/* A tree that contains an entry "git:", because Win32 APIs will reject + * that as looking too similar to a drive letter. + */ +void test_checkout_nasty__dot_git_colon(void) +{ +#ifdef GIT_WIN32 + test_checkout_fails("refs/heads/dot_git_colon", ".git/foobar"); +#endif +} + +/* A tree that contains an entry "git:foo", because Win32 APIs will turn + * that into ".git". + */ +void test_checkout_nasty__dot_git_colon_stuff(void) +{ +#ifdef GIT_WIN32 + test_checkout_fails("refs/heads/dot_git_colon_stuff", ".git/foobar"); +#endif +} + +/* A tree that contains an entry ".git::$INDEX_ALLOCATION" because NTFS + * will interpret that as a synonym to ".git", even when mounted via SMB + * on macOS. + */ +void test_checkout_nasty__dotgit_alternate_data_stream(void) +{ + test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git/dummy-file"); + test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git::$INDEX_ALLOCATION/dummy-file"); +} + +/* Trees that contains entries with a tree ".git" that contain + * byte sequences: + * { 0xe2, 0x80, 0x8c } + * { 0xe2, 0x80, 0x8d } + * { 0xe2, 0x80, 0x8e } + * { 0xe2, 0x80, 0x8f } + * { 0xe2, 0x80, 0xaa } + * { 0xe2, 0x80, 0xab } + * { 0xe2, 0x80, 0xac } + * { 0xe2, 0x80, 0xad } + * { 0xe2, 0x81, 0xae } + * { 0xe2, 0x81, 0xaa } + * { 0xe2, 0x81, 0xab } + * { 0xe2, 0x81, 0xac } + * { 0xe2, 0x81, 0xad } + * { 0xe2, 0x81, 0xae } + * { 0xe2, 0x81, 0xaf } + * { 0xef, 0xbb, 0xbf } + * Because these map to characters that HFS filesystems "ignore". Thus + * ".git" will map to ".git". + */ +void test_checkout_nasty__dot_git_hfs_ignorable(void) +{ +#ifdef __APPLE__ + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_1", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_2", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_3", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_4", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_5", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_6", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_7", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_8", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_9", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_10", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_11", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_12", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_13", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_14", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_15", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_16", ".git/foobar"); +#endif +} + +void test_checkout_nasty__honors_core_protecthfs(void) +{ + cl_repo_set_bool(repo, "core.protectHFS", true); + + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_1", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_2", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_3", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_4", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_5", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_6", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_7", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_8", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_9", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_10", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_11", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_12", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_13", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_14", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_15", ".git/foobar"); + test_checkout_fails("refs/heads/dotgit_hfs_ignorable_16", ".git/foobar"); +} + +void test_checkout_nasty__honors_core_protectntfs(void) +{ + cl_repo_set_bool(repo, "core.protectNTFS", true); + + test_checkout_fails("refs/heads/dotgit_backslash_path", ".git/foobar"); + test_checkout_fails("refs/heads/dotcapitalgit_backslash_path", ".GIT/foobar"); + test_checkout_fails("refs/heads/dot_git_dot", ".git/foobar"); + test_checkout_fails("refs/heads/git_tilde1", ".git/foobar"); +} + +void test_checkout_nasty__symlink1(void) +{ + test_checkout_passes("refs/heads/symlink1", ".git/foobar"); +} + +void test_checkout_nasty__symlink2(void) +{ + test_checkout_passes("refs/heads/symlink2", ".git/foobar"); +} + +void test_checkout_nasty__symlink3(void) +{ + test_checkout_passes("refs/heads/symlink3", ".git/foobar"); +} + +void test_checkout_nasty__gitmodules_symlink(void) +{ + cl_repo_set_bool(repo, "core.protectHFS", true); + test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules"); + cl_repo_set_bool(repo, "core.protectHFS", false); + + cl_repo_set_bool(repo, "core.protectNTFS", true); + test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules"); + cl_repo_set_bool(repo, "core.protectNTFS", false); + + test_checkout_fails("refs/heads/gitmodules-symlink", ".gitmodules"); +} diff --git a/tests/libgit2/checkout/tree.c b/tests/libgit2/checkout/tree.c new file mode 100644 index 000000000..d4b57f5d1 --- /dev/null +++ b/tests/libgit2/checkout/tree.c @@ -0,0 +1,1685 @@ +#include "clar_libgit2.h" +#include "checkout_helpers.h" + +#include "git2/checkout.h" +#include "repository.h" +#include "futils.h" + +static git_repository *g_repo; +static git_checkout_options g_opts; +static git_object *g_object; + +static void assert_status_entrycount(git_repository *repo, size_t count) +{ + git_status_list *status; + + cl_git_pass(git_status_list_new(&status, repo, NULL)); + cl_assert_equal_i(count, git_status_list_entrycount(status)); + + git_status_list_free(status); +} + +void test_checkout_tree__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); + + GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTIONS_VERSION); + g_opts.checkout_strategy = GIT_CHECKOUT_FORCE; +} + +void test_checkout_tree__cleanup(void) +{ + git_object_free(g_object); + g_object = NULL; + + cl_git_sandbox_cleanup(); + + if (git_fs_path_isdir("alternative")) + git_futils_rmdir_r("alternative", NULL, GIT_RMDIR_REMOVE_FILES); +} + +void test_checkout_tree__cannot_checkout_a_non_treeish(void) +{ + /* blob */ + cl_git_pass(git_revparse_single(&g_object, g_repo, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); + cl_git_fail(git_checkout_tree(g_repo, g_object, NULL)); +} + +void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) +{ + char *entries[] = { "ab/de/" }; + + g_opts.paths.strings = entries; + g_opts.paths.count = 1; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); + + cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/ab/")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/ab/de/2.txt")); + cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/ab/de/fgh/1.txt")); +} + +void test_checkout_tree__can_checkout_and_remove_directory(void) +{ + cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/ab/")); + + /* Checkout branch "subtrees" and update HEAD, so that HEAD matches the + * current working tree + */ + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees")); + + cl_assert_equal_i(true, git_fs_path_isdir("./testrepo/ab/")); + cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/ab/de/2.txt")); + cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/ab/de/fgh/1.txt")); + + git_object_free(g_object); + g_object = NULL; + + /* Checkout branch "master" and update HEAD, so that HEAD matches the + * current working tree + */ + cl_git_pass(git_revparse_single(&g_object, g_repo, "master")); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master")); + + /* This directory should no longer exist */ + cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/ab/")); +} + +void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void) +{ + char *entries[] = { "de/" }; + + g_opts.paths.strings = entries; + g_opts.paths.count = 1; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees:ab")); + + cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/de/")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/de/2.txt")); + cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/de/fgh/1.txt")); +} + +static void progress(const char *path, size_t cur, size_t tot, void *payload) +{ + bool *was_called = (bool*)payload; + GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); + *was_called = true; +} + +void test_checkout_tree__calls_progress_callback(void) +{ + bool was_called = 0; + + g_opts.progress_cb = progress; + g_opts.progress_payload = &was_called; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "master")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert_equal_i(was_called, true); +} + +void test_checkout_tree__doesnt_write_unrequested_files_to_worktree(void) +{ + git_oid master_oid; + git_oid chomped_oid; + git_commit* p_master_commit; + git_commit* p_chomped_commit; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + git_oid_fromstr(&master_oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + git_oid_fromstr(&chomped_oid, "e90810b8df3e80c413d903f631643c716887138d"); + cl_git_pass(git_commit_lookup(&p_master_commit, g_repo, &master_oid)); + cl_git_pass(git_commit_lookup(&p_chomped_commit, g_repo, &chomped_oid)); + + /* GIT_CHECKOUT_NONE should not add any file to the working tree from the + * index as it is supposed to be a dry run. + */ + opts.checkout_strategy = GIT_CHECKOUT_NONE; + git_checkout_tree(g_repo, (git_object*)p_chomped_commit, &opts); + cl_assert_equal_i(false, git_fs_path_isfile("testrepo/readme.txt")); + + git_commit_free(p_master_commit); + git_commit_free(p_chomped_commit); +} + +void test_checkout_tree__can_switch_branches(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + + assert_on_branch(g_repo, "master"); + + /* do first checkout with FORCE because we don't know if testrepo + * base data is clean for a checkout or not + */ + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir")); + + cl_assert(git_fs_path_isfile("testrepo/README")); + cl_assert(git_fs_path_isfile("testrepo/branch_file.txt")); + cl_assert(git_fs_path_isfile("testrepo/new.txt")); + cl_assert(git_fs_path_isfile("testrepo/a/b.txt")); + + cl_assert(!git_fs_path_isdir("testrepo/ab")); + + assert_on_branch(g_repo, "dir"); + + git_object_free(obj); + + /* do second checkout safe because we should be clean after first */ + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees")); + + cl_assert(git_fs_path_isfile("testrepo/README")); + cl_assert(git_fs_path_isfile("testrepo/branch_file.txt")); + cl_assert(git_fs_path_isfile("testrepo/new.txt")); + cl_assert(git_fs_path_isfile("testrepo/ab/4.txt")); + cl_assert(git_fs_path_isfile("testrepo/ab/c/3.txt")); + cl_assert(git_fs_path_isfile("testrepo/ab/de/2.txt")); + cl_assert(git_fs_path_isfile("testrepo/ab/de/fgh/1.txt")); + + cl_assert(!git_fs_path_isdir("testrepo/a")); + + assert_on_branch(g_repo, "subtrees"); + + git_object_free(obj); +} + +void test_checkout_tree__can_remove_untracked(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_UNTRACKED; + + cl_git_mkfile("testrepo/untracked_file", "as you wish"); + cl_assert(git_fs_path_isfile("testrepo/untracked_file")); + + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert(!git_fs_path_isfile("testrepo/untracked_file")); +} + +void test_checkout_tree__can_remove_ignored(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + int ignored = 0; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_IGNORED; + + cl_git_mkfile("testrepo/ignored_file", "as you wish"); + + cl_git_pass(git_ignore_add_rule(g_repo, "ignored_file\n")); + + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "ignored_file")); + cl_assert_equal_i(1, ignored); + + cl_assert(git_fs_path_isfile("testrepo/ignored_file")); + + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert(!git_fs_path_isfile("testrepo/ignored_file")); +} + +static int checkout_tree_with_blob_ignored_in_workdir(int strategy, bool isdir) +{ + git_oid oid; + git_object *obj = NULL; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + int ignored = 0, error; + + assert_on_branch(g_repo, "master"); + + /* do first checkout with FORCE because we don't know if testrepo + * base data is clean for a checkout or not + */ + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir")); + + cl_assert(git_fs_path_isfile("testrepo/README")); + cl_assert(git_fs_path_isfile("testrepo/branch_file.txt")); + cl_assert(git_fs_path_isfile("testrepo/new.txt")); + cl_assert(git_fs_path_isfile("testrepo/a/b.txt")); + + cl_assert(!git_fs_path_isdir("testrepo/ab")); + + assert_on_branch(g_repo, "dir"); + + git_object_free(obj); + + opts.checkout_strategy = strategy; + + if (isdir) { + cl_must_pass(p_mkdir("testrepo/ab", 0777)); + cl_must_pass(p_mkdir("testrepo/ab/4.txt", 0777)); + + cl_git_mkfile("testrepo/ab/4.txt/file1.txt", "as you wish"); + cl_git_mkfile("testrepo/ab/4.txt/file2.txt", "foo bar foo"); + cl_git_mkfile("testrepo/ab/4.txt/file3.txt", "inky blinky pinky clyde"); + + cl_assert(git_fs_path_isdir("testrepo/ab/4.txt")); + } else { + cl_must_pass(p_mkdir("testrepo/ab", 0777)); + cl_git_mkfile("testrepo/ab/4.txt", "as you wish"); + + cl_assert(git_fs_path_isfile("testrepo/ab/4.txt")); + } + + cl_git_pass(git_ignore_add_rule(g_repo, "ab/4.txt\n")); + + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "ab/4.txt")); + cl_assert_equal_i(1, ignored); + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + error = git_checkout_tree(g_repo, obj, &opts); + + git_object_free(obj); + + return error; +} + +void test_checkout_tree__conflict_on_ignored_when_not_overwriting(void) +{ + int error; + + cl_git_fail(error = checkout_tree_with_blob_ignored_in_workdir( + GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, false)); + + cl_assert_equal_i(GIT_ECONFLICT, error); +} + +void test_checkout_tree__can_overwrite_ignored_by_default(void) +{ + cl_git_pass(checkout_tree_with_blob_ignored_in_workdir(GIT_CHECKOUT_SAFE, false)); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees")); + + cl_assert(git_fs_path_isfile("testrepo/ab/4.txt")); + + assert_on_branch(g_repo, "subtrees"); +} + +void test_checkout_tree__conflict_on_ignored_folder_when_not_overwriting(void) +{ + int error; + + cl_git_fail(error = checkout_tree_with_blob_ignored_in_workdir( + GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, true)); + + cl_assert_equal_i(GIT_ECONFLICT, error); +} + +void test_checkout_tree__can_overwrite_ignored_folder_by_default(void) +{ + cl_git_pass(checkout_tree_with_blob_ignored_in_workdir(GIT_CHECKOUT_SAFE, true)); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees")); + + cl_assert(git_fs_path_isfile("testrepo/ab/4.txt")); + + assert_on_branch(g_repo, "subtrees"); + +} + +void test_checkout_tree__can_update_only(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + + /* first let's get things into a known state - by checkout out the HEAD */ + + assert_on_branch(g_repo, "master"); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert(!git_fs_path_isdir("testrepo/a")); + + check_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n"); + + /* now checkout branch but with update only */ + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir")); + + assert_on_branch(g_repo, "dir"); + + /* this normally would have been created (which was tested separately in + * the test_checkout_tree__can_switch_branches test), but with + * UPDATE_ONLY it will not have been created. + */ + cl_assert(!git_fs_path_isdir("testrepo/a")); + + /* but this file still should have been updated */ + check_file_contents_nocr("testrepo/branch_file.txt", "hi\n"); + + git_object_free(obj); +} + +void test_checkout_tree__can_checkout_with_pattern(void) +{ + char *entries[] = { "[l-z]*.txt" }; + + /* reset to beginning of history (i.e. just a README file) */ + + g_opts.checkout_strategy = + GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "8496071c1b46c854b31185ea97743be6a8774479")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + cl_git_pass( + git_repository_set_head_detached(g_repo, git_object_id(g_object))); + + git_object_free(g_object); + g_object = NULL; + + cl_assert(git_fs_path_exists("testrepo/README")); + cl_assert(!git_fs_path_exists("testrepo/branch_file.txt")); + cl_assert(!git_fs_path_exists("testrepo/link_to_new.txt")); + cl_assert(!git_fs_path_exists("testrepo/new.txt")); + + /* now to a narrow patterned checkout */ + + g_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + g_opts.paths.strings = entries; + g_opts.paths.count = 1; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "refs/heads/master")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert(git_fs_path_exists("testrepo/README")); + cl_assert(!git_fs_path_exists("testrepo/branch_file.txt")); + cl_assert(git_fs_path_exists("testrepo/link_to_new.txt")); + cl_assert(git_fs_path_exists("testrepo/new.txt")); +} + +void test_checkout_tree__pathlist_checkout_ignores_non_matches(void) +{ + char *entries[] = { "branch_file.txt", "link_to_new.txt" }; + + /* reset to beginning of history (i.e. just a README file) */ + + g_opts.checkout_strategy = + GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "refs/heads/master")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master")); + + cl_assert(git_fs_path_exists("testrepo/README")); + cl_assert(git_fs_path_exists("testrepo/branch_file.txt")); + cl_assert(git_fs_path_exists("testrepo/link_to_new.txt")); + cl_assert(git_fs_path_exists("testrepo/new.txt")); + + git_object_free(g_object); + cl_git_pass(git_revparse_single(&g_object, g_repo, "8496071c1b46c854b31185ea97743be6a8774479")); + + g_opts.checkout_strategy = + GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; + g_opts.paths.strings = entries; + g_opts.paths.count = 2; + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert(git_fs_path_exists("testrepo/README")); + cl_assert(!git_fs_path_exists("testrepo/branch_file.txt")); + cl_assert(!git_fs_path_exists("testrepo/link_to_new.txt")); + cl_assert(git_fs_path_exists("testrepo/new.txt")); +} + +void test_checkout_tree__can_disable_pattern_match(void) +{ + char *entries[] = { "b*.txt" }; + + /* reset to beginning of history (i.e. just a README file) */ + + g_opts.checkout_strategy = + GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "8496071c1b46c854b31185ea97743be6a8774479")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + cl_git_pass( + git_repository_set_head_detached(g_repo, git_object_id(g_object))); + + git_object_free(g_object); + g_object = NULL; + + cl_assert(!git_fs_path_isfile("testrepo/branch_file.txt")); + + /* now to a narrow patterned checkout, but disable pattern */ + + g_opts.checkout_strategy = + GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; + g_opts.paths.strings = entries; + g_opts.paths.count = 1; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "refs/heads/master")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert(!git_fs_path_isfile("testrepo/branch_file.txt")); + + /* let's try that again, but allow the pattern match */ + + g_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert(git_fs_path_isfile("testrepo/branch_file.txt")); +} + +static void assert_conflict( + const char *entry_path, + const char *new_content, + const char *parent_sha, + const char *commit_sha) +{ + git_index *index; + git_object *hack_tree; + git_reference *branch, *head; + git_str file_path = GIT_STR_INIT; + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* Create a branch pointing at the parent */ + cl_git_pass(git_revparse_single(&g_object, g_repo, parent_sha)); + cl_git_pass(git_branch_create(&branch, g_repo, + "potential_conflict", (git_commit *)g_object, 0)); + + /* Make HEAD point to this branch */ + cl_git_pass(git_reference_symbolic_create( + &head, g_repo, "HEAD", git_reference_name(branch), 1, NULL)); + git_reference_free(head); + git_reference_free(branch); + + /* Checkout the parent */ + g_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + /* Hack-ishy workaround to ensure *all* the index entries + * match the content of the tree + */ + cl_git_pass(git_object_peel(&hack_tree, g_object, GIT_OBJECT_TREE)); + cl_git_pass(git_index_read_tree(index, (git_tree *)hack_tree)); + cl_git_pass(git_index_write(index)); + git_object_free(hack_tree); + git_object_free(g_object); + g_object = NULL; + + /* Create a conflicting file */ + cl_git_pass(git_str_joinpath(&file_path, "./testrepo", entry_path)); + cl_git_mkfile(git_str_cstr(&file_path), new_content); + git_str_dispose(&file_path); + + /* Trying to checkout the original commit */ + cl_git_pass(git_revparse_single(&g_object, g_repo, commit_sha)); + + g_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + cl_assert_equal_i( + GIT_ECONFLICT, git_checkout_tree(g_repo, g_object, &g_opts)); + + /* Stage the conflicting change */ + cl_git_pass(git_index_add_bypath(index, entry_path)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_assert_equal_i( + GIT_ECONFLICT, git_checkout_tree(g_repo, g_object, &g_opts)); +} + +void test_checkout_tree__checking_out_a_conflicting_type_change_returns_ECONFLICT(void) +{ + /* + * 099faba adds a symlink named 'link_to_new.txt' + * a65fedf is the parent of 099faba + */ + + assert_conflict("link_to_new.txt", "old.txt", "a65fedf", "099faba"); +} + +void test_checkout_tree__checking_out_a_conflicting_type_change_returns_ECONFLICT_2(void) +{ + /* + * cf80f8d adds a directory named 'a/' + * a4a7dce is the parent of cf80f8d + */ + + assert_conflict("a", "hello\n", "a4a7dce", "cf80f8d"); +} + +void test_checkout_tree__checking_out_a_conflicting_content_change_returns_ECONFLICT(void) +{ + /* + * c47800c adds a symlink named 'branch_file.txt' + * 5b5b025 is the parent of 763d71a + */ + + assert_conflict("branch_file.txt", "hello\n", "5b5b025", "c47800c"); +} + +void test_checkout_tree__donot_update_deleted_file_by_default(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid old_id, new_id; + git_commit *old_commit = NULL, *new_commit = NULL; + git_index *index = NULL; + checkout_counts ct; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + memset(&ct, 0, sizeof(ct)); + opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; + opts.notify_cb = checkout_count_callback; + opts.notify_payload = &ct; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_oid_fromstr(&old_id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_commit_lookup(&old_commit, g_repo, &old_id)); + cl_git_pass(git_reset(g_repo, (git_object *)old_commit, GIT_RESET_HARD, NULL)); + + cl_git_pass(p_unlink("testrepo/branch_file.txt")); + cl_git_pass(git_index_remove_bypath(index ,"branch_file.txt")); + cl_git_pass(git_index_write(index)); + + cl_assert(!git_fs_path_exists("testrepo/branch_file.txt")); + + cl_git_pass(git_oid_fromstr(&new_id, "099fabac3a9ea935598528c27f866e34089c2eff")); + cl_git_pass(git_commit_lookup(&new_commit, g_repo, &new_id)); + + + cl_git_fail(git_checkout_tree(g_repo, (git_object *)new_commit, &opts)); + + cl_assert_equal_i(1, ct.n_conflicts); + cl_assert_equal_i(1, ct.n_updates); + + git_commit_free(old_commit); + git_commit_free(new_commit); + git_index_free(index); +} + +struct checkout_cancel_at { + const char *filename; + int error; + int count; +}; + +static int checkout_cancel_cb( + git_checkout_notify_t why, + const char *path, + const git_diff_file *b, + const git_diff_file *t, + const git_diff_file *w, + void *payload) +{ + struct checkout_cancel_at *ca = payload; + + GIT_UNUSED(why); GIT_UNUSED(b); GIT_UNUSED(t); GIT_UNUSED(w); + + ca->count++; + + if (!strcmp(path, ca->filename)) + return ca->error; + + return 0; +} + +void test_checkout_tree__can_cancel_checkout_from_notify(void) +{ + struct checkout_cancel_at ca; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + + assert_on_branch(g_repo, "master"); + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + ca.filename = "new.txt"; + ca.error = -5555; + ca.count = 0; + + opts.notify_flags = GIT_CHECKOUT_NOTIFY_UPDATED; + opts.notify_cb = checkout_cancel_cb; + opts.notify_payload = &ca; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_assert(!git_fs_path_exists("testrepo/new.txt")); + + cl_git_fail_with(git_checkout_tree(g_repo, obj, &opts), -5555); + + cl_assert(!git_fs_path_exists("testrepo/new.txt")); + + /* on case-insensitive FS = a/b.txt, branch_file.txt, new.txt */ + /* on case-sensitive FS = README, then above */ + + if (git_fs_path_exists("testrepo/.git/CoNfIg")) /* case insensitive */ + cl_assert_equal_i(3, ca.count); + else + cl_assert_equal_i(4, ca.count); + + /* and again with a different stopping point and return code */ + ca.filename = "README"; + ca.error = 123; + ca.count = 0; + + cl_git_fail_with(git_checkout_tree(g_repo, obj, &opts), 123); + + cl_assert(!git_fs_path_exists("testrepo/new.txt")); + + if (git_fs_path_exists("testrepo/.git/CoNfIg")) /* case insensitive */ + cl_assert_equal_i(4, ca.count); + else + cl_assert_equal_i(1, ca.count); + + git_object_free(obj); +} + +void test_checkout_tree__can_checkout_with_last_workdir_item_missing(void) +{ + git_index *index = NULL; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid tree_id, commit_id; + git_tree *tree = NULL; + git_commit *commit = NULL; + + git_repository_index(&index, g_repo); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_name_to_id(&commit_id, g_repo, "refs/heads/master")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id)); + + cl_git_pass(git_checkout_tree(g_repo, (git_object *)commit, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master")); + + cl_git_pass(p_mkdir("./testrepo/this-is-dir", 0777)); + cl_git_mkfile("./testrepo/this-is-dir/contained_file", "content\n"); + + cl_git_pass(git_index_add_bypath(index, "this-is-dir/contained_file")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + cl_git_pass(p_unlink("./testrepo/this-is-dir/contained_file")); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + opts.checkout_strategy = 1; + git_checkout_tree(g_repo, (git_object *)tree, &opts); + + git_tree_free(tree); + git_commit_free(commit); + git_index_free(index); +} + +void test_checkout_tree__issue_1397(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + const char *partial_oid = "8a7ef04"; + git_object *tree = NULL; + + test_checkout_tree__cleanup(); /* cleanup default checkout */ + + g_repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_git_pass(git_revparse_single(&tree, g_repo, partial_oid)); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_checkout_tree(g_repo, tree, &opts)); + + check_file_contents("./issue_1397/crlf_file.txt", "first line\r\nsecond line\r\nboth with crlf"); + + git_object_free(tree); +} + +void test_checkout_tree__can_write_to_empty_dirs(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + + assert_on_branch(g_repo, "master"); + + cl_git_pass(p_mkdir("testrepo/a", 0777)); + + /* do first checkout with FORCE because we don't know if testrepo + * base data is clean for a checkout or not + */ + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_assert(git_fs_path_isfile("testrepo/a/b.txt")); + + git_object_free(obj); +} + +void test_checkout_tree__fails_when_dir_in_use(void) +{ +#ifdef GIT_WIN32 + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_assert(git_fs_path_isfile("testrepo/a/b.txt")); + + git_object_free(obj); + + cl_git_pass(p_chdir("testrepo/a")); + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/master")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_fail(git_checkout_tree(g_repo, obj, &opts)); + + cl_git_pass(p_chdir("../..")); + + cl_assert(git_fs_path_is_empty_dir("testrepo/a")); + + git_object_free(obj); +#endif +} + +void test_checkout_tree__can_continue_when_dir_in_use(void) +{ +#ifdef GIT_WIN32 + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE | + GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_assert(git_fs_path_isfile("testrepo/a/b.txt")); + + git_object_free(obj); + + cl_git_pass(p_chdir("testrepo/a")); + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/master")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_git_pass(p_chdir("../..")); + + cl_assert(git_fs_path_is_empty_dir("testrepo/a")); + + git_object_free(obj); +#endif +} + +void test_checkout_tree__target_directory_from_bare(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + checkout_counts cts; + memset(&cts, 0, sizeof(cts)); + + test_checkout_tree__cleanup(); /* cleanup default checkout */ + + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_assert(git_repository_is_bare(g_repo)); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | + GIT_CHECKOUT_RECREATE_MISSING; + + opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; + opts.notify_cb = checkout_count_callback; + opts.notify_payload = &cts; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD")); + cl_git_pass(git_object_lookup(&g_object, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_fail(git_checkout_tree(g_repo, g_object, &opts)); + + opts.target_directory = "alternative"; + cl_assert(!git_fs_path_isdir("alternative")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &opts)); + + cl_assert_equal_i(0, cts.n_untracked); + cl_assert_equal_i(0, cts.n_ignored); + cl_assert_equal_i(3, cts.n_updates); + + check_file_contents_nocr("./alternative/README", "hey there\n"); + check_file_contents_nocr("./alternative/branch_file.txt", "hi\nbye!\n"); + check_file_contents_nocr("./alternative/new.txt", "my new file\n"); + + cl_git_pass(git_futils_rmdir_r( + "alternative", NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_checkout_tree__extremely_long_file_name(void) +{ + /* A utf-8 string with 83 characters, but 249 bytes. */ + const char *longname = "\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97"; + char path[1024] = {0}; + + g_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_revparse_single(&g_object, g_repo, "long-file-name")); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + sprintf(path, "testrepo/%s.txt", longname); + cl_assert(git_fs_path_exists(path)); + + git_object_free(g_object); + cl_git_pass(git_revparse_single(&g_object, g_repo, "master")); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + cl_assert(!git_fs_path_exists(path)); +} + +static void create_conflict(const char *path) +{ + git_index *index; + git_index_entry entry; + + cl_git_pass(git_repository_index(&index, g_repo)); + + memset(&entry, 0x0, sizeof(git_index_entry)); + entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&entry, 1); + git_oid_fromstr(&entry.id, "d427e0b2e138501a3d15cc376077a3631e15bd46"); + entry.path = path; + cl_git_pass(git_index_add(index, &entry)); + + GIT_INDEX_ENTRY_STAGE_SET(&entry, 2); + git_oid_fromstr(&entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); + cl_git_pass(git_index_add(index, &entry)); + + GIT_INDEX_ENTRY_STAGE_SET(&entry, 3); + git_oid_fromstr(&entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); + cl_git_pass(git_index_add(index, &entry)); + + cl_git_pass(git_index_write(index)); + git_index_free(index); +} + +void test_checkout_tree__fails_when_conflicts_exist_in_index(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + create_conflict("conflicts.txt"); + + cl_git_fail(git_checkout_tree(g_repo, obj, &opts)); + + git_object_free(obj); +} + +void test_checkout_tree__filemode_preserved_in_index(void) +{ + git_oid executable_oid; + git_commit *commit; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_index *index; + const git_index_entry *entry; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* test a freshly added executable */ + cl_git_pass(git_oid_fromstr(&executable_oid, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(entry = git_index_get_bypath(index, "executable.txt", 0)); + cl_assert(GIT_PERMS_IS_EXEC(entry->mode)); + + git_commit_free(commit); + + + /* Now start with a commit which has a text file */ + cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0)); + cl_assert(!GIT_PERMS_IS_EXEC(entry->mode)); + + git_commit_free(commit); + + + /* And then check out to a commit which converts the text file to an executable */ + cl_git_pass(git_oid_fromstr(&executable_oid, "144344043ba4d4a405da03de3844aa829ae8be0e")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0)); + cl_assert(GIT_PERMS_IS_EXEC(entry->mode)); + + git_commit_free(commit); + + + /* Finally, check out the text file again and check that the exec bit is cleared */ + cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0)); + cl_assert(!GIT_PERMS_IS_EXEC(entry->mode)); + + git_commit_free(commit); + + + git_index_free(index); +} + +#ifndef GIT_WIN32 +static mode_t read_filemode(const char *path) +{ + git_str fullpath = GIT_STR_INIT; + struct stat st; + mode_t result; + + git_str_joinpath(&fullpath, "testrepo", path); + cl_must_pass(p_stat(fullpath.ptr, &st)); + + result = GIT_PERMS_IS_EXEC(st.st_mode) ? + GIT_FILEMODE_BLOB_EXECUTABLE : GIT_FILEMODE_BLOB; + + git_str_dispose(&fullpath); + + return result; +} +#endif + +void test_checkout_tree__filemode_preserved_in_workdir(void) +{ +#ifndef GIT_WIN32 + git_oid executable_oid; + git_commit *commit; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + /* test a freshly added executable */ + cl_git_pass(git_oid_fromstr(&executable_oid, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(GIT_PERMS_IS_EXEC(read_filemode("executable.txt"))); + + git_commit_free(commit); + + + /* Now start with a commit which has a text file */ + cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); + + git_commit_free(commit); + + + /* And then check out to a commit which converts the text file to an executable */ + cl_git_pass(git_oid_fromstr(&executable_oid, "144344043ba4d4a405da03de3844aa829ae8be0e")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); + + git_commit_free(commit); + + + /* Finally, check out the text file again and check that the exec bit is cleared */ + cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); + + git_commit_free(commit); +#else + cl_skip(); +#endif +} + +void test_checkout_tree__removes_conflicts(void) +{ + git_oid commit_id; + git_commit *commit; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_index *index; + + cl_git_pass(git_oid_fromstr(&commit_id, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id)); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_remove(index, "executable.txt", 0)); + + create_conflict("executable.txt"); + cl_git_mkfile("testrepo/executable.txt", "This is the conflict file.\n"); + + create_conflict("other.txt"); + cl_git_mkfile("testrepo/other.txt", "This is another conflict file.\n"); + + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + + cl_assert_equal_p(NULL, git_index_get_bypath(index, "executable.txt", 1)); + cl_assert_equal_p(NULL, git_index_get_bypath(index, "executable.txt", 2)); + cl_assert_equal_p(NULL, git_index_get_bypath(index, "executable.txt", 3)); + + cl_assert_equal_p(NULL, git_index_get_bypath(index, "other.txt", 1)); + cl_assert_equal_p(NULL, git_index_get_bypath(index, "other.txt", 2)); + cl_assert_equal_p(NULL, git_index_get_bypath(index, "other.txt", 3)); + + cl_assert(!git_fs_path_exists("testrepo/other.txt")); + + git_commit_free(commit); + git_index_free(index); +} + + +void test_checkout_tree__removes_conflicts_only_by_pathscope(void) +{ + git_oid commit_id; + git_commit *commit; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_index *index; + const char *path = "executable.txt"; + + cl_git_pass(git_oid_fromstr(&commit_id, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id)); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + opts.paths.count = 1; + opts.paths.strings = (char **)&path; + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_remove(index, "executable.txt", 0)); + + create_conflict("executable.txt"); + cl_git_mkfile("testrepo/executable.txt", "This is the conflict file.\n"); + + create_conflict("other.txt"); + cl_git_mkfile("testrepo/other.txt", "This is another conflict file.\n"); + + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + + cl_assert_equal_p(NULL, git_index_get_bypath(index, "executable.txt", 1)); + cl_assert_equal_p(NULL, git_index_get_bypath(index, "executable.txt", 2)); + cl_assert_equal_p(NULL, git_index_get_bypath(index, "executable.txt", 3)); + + cl_assert(git_index_get_bypath(index, "other.txt", 1) != NULL); + cl_assert(git_index_get_bypath(index, "other.txt", 2) != NULL); + cl_assert(git_index_get_bypath(index, "other.txt", 3) != NULL); + + cl_assert(git_fs_path_exists("testrepo/other.txt")); + + git_commit_free(commit); + git_index_free(index); +} + +void test_checkout_tree__case_changing_rename(void) +{ + git_index *index; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid master_id, dir_commit_id, tree_id, commit_id; + git_commit *master_commit, *dir_commit; + git_tree *tree; + git_signature *signature; + const git_index_entry *index_entry; + bool case_sensitive; + + assert_on_branch(g_repo, "master"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* Switch branches and perform a case-changing rename */ + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_name_to_id(&dir_commit_id, g_repo, "refs/heads/dir")); + cl_git_pass(git_commit_lookup(&dir_commit, g_repo, &dir_commit_id)); + + cl_git_pass(git_checkout_tree(g_repo, (git_object *)dir_commit, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir")); + + cl_assert(git_fs_path_isfile("testrepo/README")); + case_sensitive = !git_fs_path_isfile("testrepo/readme"); + + cl_assert(index_entry = git_index_get_bypath(index, "README", 0)); + cl_assert_equal_s("README", index_entry->path); + + cl_git_pass(git_index_remove_bypath(index, "README")); + cl_git_pass(p_rename("testrepo/README", "testrepo/__readme__")); + cl_git_pass(p_rename("testrepo/__readme__", "testrepo/readme")); + cl_git_append2file("testrepo/readme", "An addendum..."); + cl_git_pass(git_index_add_bypath(index, "readme")); + + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + cl_git_pass(git_signature_new(&signature, "Renamer", "rename@contoso.com", time(NULL), 0)); + + cl_git_pass(git_commit_create(&commit_id, g_repo, "refs/heads/dir", signature, signature, NULL, "case-changing rename", tree, 1, (const git_commit **)&dir_commit)); + + cl_assert(git_fs_path_isfile("testrepo/readme")); + if (case_sensitive) + cl_assert(!git_fs_path_isfile("testrepo/README")); + + cl_assert(index_entry = git_index_get_bypath(index, "readme", 0)); + cl_assert_equal_s("readme", index_entry->path); + + /* Switching back to master should rename readme -> README */ + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_name_to_id(&master_id, g_repo, "refs/heads/master")); + cl_git_pass(git_commit_lookup(&master_commit, g_repo, &master_id)); + + cl_git_pass(git_checkout_tree(g_repo, (git_object *)master_commit, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master")); + + assert_on_branch(g_repo, "master"); + + cl_assert(git_fs_path_isfile("testrepo/README")); + if (case_sensitive) + cl_assert(!git_fs_path_isfile("testrepo/readme")); + + cl_assert(index_entry = git_index_get_bypath(index, "README", 0)); + cl_assert_equal_s("README", index_entry->path); + + git_index_free(index); + git_signature_free(signature); + git_tree_free(tree); + git_commit_free(dir_commit); + git_commit_free(master_commit); +} + +static void perfdata_cb(const git_checkout_perfdata *in, void *payload) +{ + memcpy(payload, in, sizeof(git_checkout_perfdata)); +} + +void test_checkout_tree__can_collect_perfdata(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + git_checkout_perfdata perfdata = {0}; + + opts.perfdata_cb = perfdata_cb; + opts.perfdata_payload = &perfdata; + + assert_on_branch(g_repo, "master"); + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_assert(perfdata.mkdir_calls > 0); + cl_assert(perfdata.stat_calls > 0); + + git_object_free(obj); +} + +static void update_attr_callback( + const char *path, + size_t completed_steps, + size_t total_steps, + void *payload) +{ + GIT_UNUSED(completed_steps); + GIT_UNUSED(total_steps); + GIT_UNUSED(payload); + + if (path && strcmp(path, "ident1.txt") == 0) + cl_git_write2file("testrepo/.gitattributes", + "*.txt ident\n", 12, O_RDWR|O_CREAT, 0666); +} + +void test_checkout_tree__caches_attributes_during_checkout(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + git_str ident1 = GIT_STR_INIT, ident2 = GIT_STR_INIT; + char *ident_paths[] = { "ident1.txt", "ident2.txt" }; + + opts.progress_cb = update_attr_callback; + + assert_on_branch(g_repo, "master"); + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + opts.paths.strings = ident_paths; + opts.paths.count = 2; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/ident")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_git_pass(git_futils_readbuffer(&ident1, "testrepo/ident1.txt")); + cl_git_pass(git_futils_readbuffer(&ident2, "testrepo/ident2.txt")); + + cl_assert_equal_strn(ident1.ptr, "# $Id$", 6); + cl_assert_equal_strn(ident2.ptr, "# $Id$", 6); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_git_pass(git_futils_readbuffer(&ident1, "testrepo/ident1.txt")); + cl_git_pass(git_futils_readbuffer(&ident2, "testrepo/ident2.txt")); + + cl_assert_equal_strn(ident1.ptr, "# $Id: ", 7); + cl_assert_equal_strn(ident2.ptr, "# $Id: ", 7); + + git_str_dispose(&ident1); + git_str_dispose(&ident2); + git_object_free(obj); +} + +void test_checkout_tree__can_not_update_index(void) +{ + git_oid oid; + git_object *head; + unsigned int status; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_index *index; + + opts.checkout_strategy |= + GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_UPDATE_INDEX; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD")); + cl_git_pass(git_object_lookup(&head, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_reset(g_repo, head, GIT_RESET_HARD, &g_opts)); + + cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/ab/")); + + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &opts)); + + cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/ab/de/2.txt")); + cl_git_pass(git_status_file(&status, g_repo, "ab/de/2.txt")); + cl_assert_equal_i(GIT_STATUS_WT_NEW, status); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_file(&status, g_repo, "ab/de/2.txt")); + cl_assert_equal_i(GIT_STATUS_WT_NEW, status); + + git_object_free(head); + git_index_free(index); +} + +void test_checkout_tree__can_update_but_not_write_index(void) +{ + git_oid oid; + git_object *head; + unsigned int status; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_index *index; + git_repository *other; + + opts.checkout_strategy |= + GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_WRITE_INDEX; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD")); + cl_git_pass(git_object_lookup(&head, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_reset(g_repo, head, GIT_RESET_HARD, &g_opts)); + + cl_assert_equal_i(false, git_fs_path_isdir("./testrepo/ab/")); + + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &opts)); + + cl_assert_equal_i(true, git_fs_path_isfile("./testrepo/ab/de/2.txt")); + cl_git_pass(git_status_file(&status, g_repo, "ab/de/2.txt")); + cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status); + + cl_git_pass(git_repository_open(&other, "testrepo")); + cl_git_pass(git_status_file(&status, other, "ab/de/2.txt")); + cl_assert_equal_i(GIT_STATUS_WT_NEW, status); + git_repository_free(other); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_repository_open(&other, "testrepo")); + cl_git_pass(git_status_file(&status, other, "ab/de/2.txt")); + cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status); + git_repository_free(other); + + git_object_free(head); + git_index_free(index); +} + +/* Emulate checking out in a repo created by clone --no-checkout, + * which would not have written an index. */ +void test_checkout_tree__safe_proceeds_if_no_index(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + + assert_on_branch(g_repo, "master"); + cl_must_pass(p_unlink("testrepo/.git/index")); + + /* do second checkout safe because we should be clean after first */ + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees")); + + cl_assert(git_fs_path_isfile("testrepo/README")); + cl_assert(git_fs_path_isfile("testrepo/branch_file.txt")); + cl_assert(git_fs_path_isfile("testrepo/new.txt")); + cl_assert(git_fs_path_isfile("testrepo/ab/4.txt")); + cl_assert(git_fs_path_isfile("testrepo/ab/c/3.txt")); + cl_assert(git_fs_path_isfile("testrepo/ab/de/2.txt")); + cl_assert(git_fs_path_isfile("testrepo/ab/de/fgh/1.txt")); + + cl_assert(!git_fs_path_isdir("testrepo/a")); + + assert_on_branch(g_repo, "subtrees"); + + git_object_free(obj); +} + +static int checkout_conflict_count_cb( + git_checkout_notify_t why, + const char *path, + const git_diff_file *b, + const git_diff_file *t, + const git_diff_file *w, + void *payload) +{ + size_t *n = payload; + + GIT_UNUSED(why); + GIT_UNUSED(path); + GIT_UNUSED(b); + GIT_UNUSED(t); + GIT_UNUSED(w); + + (*n)++; + + return 0; +} + +/* A repo that has a HEAD (even a properly born HEAD that peels to + * a commit) but no index should be treated as if it's an empty baseline + */ +void test_checkout_tree__baseline_is_empty_when_no_index(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_reference *head; + git_object *obj; + size_t conflicts = 0; + + assert_on_branch(g_repo, "master"); + + cl_git_pass(git_repository_head(&head, g_repo)); + cl_git_pass(git_reference_peel(&obj, head, GIT_OBJECT_COMMIT)); + + cl_git_pass(git_reset(g_repo, obj, GIT_RESET_HARD, NULL)); + + cl_must_pass(p_unlink("testrepo/.git/index")); + + /* for a safe checkout, we should have checkout conflicts with + * the existing untracked files. + */ + opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE; + opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT; + opts.notify_cb = checkout_conflict_count_cb; + opts.notify_payload = &conflicts; + + cl_git_fail_with(GIT_ECONFLICT, git_checkout_tree(g_repo, obj, &opts)); + cl_assert_equal_i(4, conflicts); + + /* but force should succeed and update the index */ + opts.checkout_strategy |= GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + assert_status_entrycount(g_repo, 0); + + git_object_free(obj); + git_reference_free(head); +} + +void test_checkout_tree__mode_change_is_force_updated(void) +{ + git_index *index; + git_reference *head; + git_object *obj; + + if (!cl_is_chmod_supported()) + clar__skip(); + + assert_on_branch(g_repo, "master"); + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_repository_head(&head, g_repo)); + cl_git_pass(git_reference_peel(&obj, head, GIT_OBJECT_COMMIT)); + + cl_git_pass(git_reset(g_repo, obj, GIT_RESET_HARD, NULL)); + assert_status_entrycount(g_repo, 0); + + /* update the mode on-disk */ + cl_must_pass(p_chmod("testrepo/README", 0755)); + + assert_status_entrycount(g_repo, 1); + cl_git_pass(git_checkout_tree(g_repo, obj, &g_opts)); + assert_status_entrycount(g_repo, 0); + + /* update the mode on-disk and in the index */ + cl_must_pass(p_chmod("testrepo/README", 0755)); + cl_must_pass(git_index_add_bypath(index, "README")); + + cl_git_pass(git_index_write(index)); + assert_status_entrycount(g_repo, 1); + + cl_git_pass(git_checkout_tree(g_repo, obj, &g_opts)); + cl_git_pass(git_index_write(index)); + + assert_status_entrycount(g_repo, 0); + + git_object_free(obj); + git_reference_free(head); + git_index_free(index); +} + +void test_checkout_tree__nullopts(void) +{ + cl_git_pass(git_checkout_tree(g_repo, NULL, NULL)); +} + +static void modify_index_ondisk(void) +{ + git_repository *other_repo; + git_index *other_index; + git_index_entry entry = {{0}}; + + cl_git_pass(git_repository_open(&other_repo, git_repository_workdir(g_repo))); + cl_git_pass(git_repository_index(&other_index, other_repo)); + + cl_git_pass(git_oid_fromstr(&entry.id, "1385f264afb75a56a5bec74243be9b367ba4ca08")); + entry.mode = 0100644; + entry.path = "README"; + + cl_git_pass(git_index_add(other_index, &entry)); + cl_git_pass(git_index_write(other_index)); + + git_index_free(other_index); + git_repository_free(other_repo); +} + +static void modify_index_and_checkout_tree(git_checkout_options *opts) +{ + git_index *index; + git_reference *head; + git_object *obj; + + /* External changes to the index are maintained by default */ + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_repository_head(&head, g_repo)); + cl_git_pass(git_reference_peel(&obj, head, GIT_OBJECT_COMMIT)); + + cl_git_pass(git_reset(g_repo, obj, GIT_RESET_HARD, NULL)); + assert_status_entrycount(g_repo, 0); + + modify_index_ondisk(); + + /* The file in the index remains modified */ + cl_git_pass(git_checkout_tree(g_repo, obj, opts)); + + git_object_free(obj); + git_reference_free(head); + git_index_free(index); +} + +void test_checkout_tree__retains_external_index_changes(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + modify_index_and_checkout_tree(&opts); + assert_status_entrycount(g_repo, 1); +} + +void test_checkout_tree__no_index_refresh(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_NO_REFRESH; + + modify_index_and_checkout_tree(&opts); + assert_status_entrycount(g_repo, 0); +} + +void test_checkout_tree__dry_run(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + checkout_counts ct; + + /* first let's get things into a known state - by checkout out the HEAD */ + + assert_on_branch(g_repo, "master"); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert(!git_fs_path_isdir("testrepo/a")); + + check_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n"); + + /* now checkout branch but with dry run enabled */ + + memset(&ct, 0, sizeof(ct)); + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DRY_RUN; + opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; + opts.notify_cb = checkout_count_callback; + opts.notify_payload = &ct; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir")); + + assert_on_branch(g_repo, "dir"); + + /* these normally would have been created and updated, but with + * DRY_RUN they will be unchanged. + */ + cl_assert(!git_fs_path_isdir("testrepo/a")); + check_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n"); + + /* check that notify callback was invoked */ + cl_assert_equal_i(ct.n_updates, 2); + + git_object_free(obj); +} diff --git a/tests/libgit2/checkout/typechange.c b/tests/libgit2/checkout/typechange.c new file mode 100644 index 000000000..b888843f0 --- /dev/null +++ b/tests/libgit2/checkout/typechange.c @@ -0,0 +1,335 @@ +#include "clar_libgit2.h" +#include "diff_generate.h" +#include "git2/checkout.h" +#include "path.h" +#include "posix.h" +#include "futils.h" + +static git_repository *g_repo = NULL; + +/* +From the test repo used for this test: +-------------------------------------- + +This is a test repo for libgit2 where tree entries have type changes + +The key types that could be found in tree entries are: + +1 - GIT_FILEMODE_NEW = 0000000 +2 - GIT_FILEMODE_TREE = 0040000 +3 - GIT_FILEMODE_BLOB = 0100644 +4 - GIT_FILEMODE_BLOB_EXECUTABLE = 0100755 +5 - GIT_FILEMODE_LINK = 0120000 +6 - GIT_FILEMODE_COMMIT = 0160000 + +I will try to have every type of transition somewhere in the history +of this repo. + +Commits +------- +Initial commit - a(1) b(1) c(1) d(1) e(1) +Create content - a(1->2) b(1->3) c(1->4) d(1->5) e(1->6) +Changes #1 - a(2->3) b(3->4) c(4->5) d(5->6) e(6->2) +Changes #2 - a(3->5) b(4->6) c(5->2) d(6->3) e(2->4) +Changes #3 - a(5->3) b(6->4) c(2->5) d(3->6) e(4->2) +Changes #4 - a(3->2) b(4->3) c(5->4) d(6->5) e(2->6) +Changes #5 - a(2->1) b(3->1) c(4->1) d(5->1) e(6->1) + +*/ + +static const char *g_typechange_oids[] = { + "79b9f23e85f55ea36a472a902e875bc1121a94cb", + "9bdb75b73836a99e3dbeea640a81de81031fdc29", + "0e7ed140b514b8cae23254cb8656fe1674403aff", + "9d0235c7a7edc0889a18f97a42ee6db9fe688447", + "9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a", + "1b63caae4a5ca96f78e8dfefc376c6a39a142475", + "6eae26c90e8ccc4d16208972119c40635489c6f0", + NULL +}; + +static bool g_typechange_empty[] = { + true, false, false, false, false, false, true, true +}; + +static const int g_typechange_expected_conflicts[] = { + 1, 2, 3, 3, 2, 3, 2 +}; + +static const int g_typechange_expected_untracked[] = { + 6, 4, 3, 2, 3, 2, 5 +}; + +void test_checkout_typechange__initialize(void) +{ + g_repo = cl_git_sandbox_init("typechanges"); + + cl_fixture_sandbox("submod2_target"); + p_rename("submod2_target/.gitted", "submod2_target/.git"); +} + +void test_checkout_typechange__cleanup(void) +{ + cl_git_sandbox_cleanup(); + cl_fixture_cleanup("submod2_target"); +} + +static void assert_file_exists(const char *path) +{ + cl_assert_(git_fs_path_isfile(path), path); +} + +static void assert_dir_exists(const char *path) +{ + cl_assert_(git_fs_path_isdir(path), path); +} + +static void assert_workdir_matches_tree( + git_repository *repo, const git_oid *id, const char *root, bool recurse) +{ + git_object *obj; + git_tree *tree; + size_t i, max_i; + git_str path = GIT_STR_INIT; + + if (!root) + root = git_repository_workdir(repo); + cl_assert(root); + + cl_git_pass(git_object_lookup(&obj, repo, id, GIT_OBJECT_ANY)); + cl_git_pass(git_object_peel((git_object **)&tree, obj, GIT_OBJECT_TREE)); + git_object_free(obj); + + max_i = git_tree_entrycount(tree); + + for (i = 0; i < max_i; ++i) { + const git_tree_entry *te = git_tree_entry_byindex(tree, i); + cl_assert(te); + + cl_git_pass(git_str_joinpath(&path, root, git_tree_entry_name(te))); + + switch (git_tree_entry_type(te)) { + case GIT_OBJECT_COMMIT: + assert_dir_exists(path.ptr); + break; + case GIT_OBJECT_TREE: + assert_dir_exists(path.ptr); + if (recurse) + assert_workdir_matches_tree( + repo, git_tree_entry_id(te), path.ptr, true); + break; + case GIT_OBJECT_BLOB: + switch (git_tree_entry_filemode(te)) { + case GIT_FILEMODE_BLOB: + case GIT_FILEMODE_BLOB_EXECUTABLE: + assert_file_exists(path.ptr); + /* because of cross-platform, don't confirm exec bit yet */ + break; + case GIT_FILEMODE_LINK: + cl_assert_(git_fs_path_exists(path.ptr), path.ptr); + /* because of cross-platform, don't confirm link yet */ + break; + default: + cl_assert(false); /* really?! */ + } + break; + default: + cl_assert(false); /* really?!! */ + } + } + + git_tree_free(tree); + git_str_dispose(&path); +} + +void test_checkout_typechange__checkout_typechanges_safe(void) +{ + int i; + git_object *obj; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + for (i = 0; g_typechange_oids[i] != NULL; ++i) { + cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i])); + + opts.checkout_strategy = !i ? GIT_CHECKOUT_FORCE : GIT_CHECKOUT_SAFE; + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_git_pass( + git_repository_set_head_detached(g_repo, git_object_id(obj))); + + assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true); + + git_object_free(obj); + + if (!g_typechange_empty[i]) { + cl_assert(git_fs_path_isdir("typechanges")); + cl_assert(git_fs_path_exists("typechanges/a")); + cl_assert(git_fs_path_exists("typechanges/b")); + cl_assert(git_fs_path_exists("typechanges/c")); + cl_assert(git_fs_path_exists("typechanges/d")); + cl_assert(git_fs_path_exists("typechanges/e")); + } else { + cl_assert(git_fs_path_isdir("typechanges")); + cl_assert(!git_fs_path_exists("typechanges/a")); + cl_assert(!git_fs_path_exists("typechanges/b")); + cl_assert(!git_fs_path_exists("typechanges/c")); + cl_assert(!git_fs_path_exists("typechanges/d")); + cl_assert(!git_fs_path_exists("typechanges/e")); + } + } +} + +typedef struct { + int conflicts; + int dirty; + int updates; + int untracked; + int ignored; +} notify_counts; + +static int notify_counter( + git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload) +{ + notify_counts *cts = payload; + + GIT_UNUSED(path); + GIT_UNUSED(baseline); + GIT_UNUSED(target); + GIT_UNUSED(workdir); + + switch (why) { + case GIT_CHECKOUT_NOTIFY_CONFLICT: cts->conflicts++; break; + case GIT_CHECKOUT_NOTIFY_DIRTY: cts->dirty++; break; + case GIT_CHECKOUT_NOTIFY_UPDATED: cts->updates++; break; + case GIT_CHECKOUT_NOTIFY_UNTRACKED: cts->untracked++; break; + case GIT_CHECKOUT_NOTIFY_IGNORED: cts->ignored++; break; + default: break; + } + + return 0; +} + +static void force_create_file(const char *file) +{ + int error = git_futils_rmdir_r(file, NULL, + GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS); + cl_assert(!error || error == GIT_ENOTFOUND); + cl_git_pass(git_futils_mkpath2file(file, 0777)); + cl_git_rewritefile(file, "yowza!!"); +} + +static int make_submodule_dirty(git_submodule *sm, const char *name, void *payload) +{ + git_str submodulepath = GIT_STR_INIT; + git_str dirtypath = GIT_STR_INIT; + git_repository *submodule_repo; + + GIT_UNUSED(name); + GIT_UNUSED(payload); + + /* remove submodule directory in preparation for init and repo_init */ + cl_git_pass(git_str_joinpath( + &submodulepath, + git_repository_workdir(g_repo), + git_submodule_path(sm) + )); + git_futils_rmdir_r(git_str_cstr(&submodulepath), NULL, GIT_RMDIR_REMOVE_FILES); + + /* initialize submodule's repository */ + cl_git_pass(git_submodule_repo_init(&submodule_repo, sm, 0)); + + /* create a file in the submodule workdir to make it dirty */ + cl_git_pass( + git_str_joinpath(&dirtypath, git_repository_workdir(submodule_repo), "dirty")); + force_create_file(git_str_cstr(&dirtypath)); + + git_str_dispose(&dirtypath); + git_str_dispose(&submodulepath); + git_repository_free(submodule_repo); + + return 0; +} + +void test_checkout_typechange__checkout_with_conflicts(void) +{ + int i; + git_object *obj; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + notify_counts cts = {0}; + + opts.notify_flags = + GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_UNTRACKED; + opts.notify_cb = notify_counter; + opts.notify_payload = &cts; + + for (i = 0; g_typechange_oids[i] != NULL; ++i) { + cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i])); + + force_create_file("typechanges/a/blocker"); + force_create_file("typechanges/b"); + force_create_file("typechanges/c/sub/sub/file"); + git_futils_rmdir_r("typechanges/d", NULL, GIT_RMDIR_REMOVE_FILES); + p_mkdir("typechanges/d", 0777); /* intentionally empty dir */ + force_create_file("typechanges/untracked"); + cl_git_pass(git_submodule_foreach(g_repo, make_submodule_dirty, NULL)); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + memset(&cts, 0, sizeof(cts)); + + cl_git_fail(git_checkout_tree(g_repo, obj, &opts)); + cl_assert_equal_i(cts.conflicts, g_typechange_expected_conflicts[i]); + cl_assert_equal_i(cts.untracked, g_typechange_expected_untracked[i]); + cl_assert_equal_i(cts.dirty, 0); + cl_assert_equal_i(cts.updates, 0); + cl_assert_equal_i(cts.ignored, 0); + + opts.checkout_strategy = + GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; + memset(&cts, 0, sizeof(cts)); + + cl_assert(git_fs_path_exists("typechanges/untracked")); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + cl_assert_equal_i(0, cts.conflicts); + + cl_assert(!git_fs_path_exists("typechanges/untracked")); + + cl_git_pass( + git_repository_set_head_detached(g_repo, git_object_id(obj))); + + assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true); + + git_object_free(obj); + } +} + +void test_checkout_typechange__status_char(void) +{ + size_t i; + git_oid oid; + git_commit *commit; + git_diff *diff; + const git_diff_delta *delta; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + char expected[8] = {'M', 'M', 'R', 'T', 'D', 'R', 'A', 'R'}; + + git_oid_fromstr(&oid, "9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a"); + cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); + diffopts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; + cl_git_pass(git_diff__commit(&diff, g_repo, commit, &diffopts)); + cl_git_pass(git_diff_find_similar(diff, NULL)); + + for (i = 0; i < git_diff_num_deltas(diff); i++) { + delta = git_diff_get_delta(diff, i); + cl_assert_equal_i(expected[i], git_diff_status_char(delta->status)); + } + + git_diff_free(diff); + git_commit_free(commit); +} diff --git a/tests/libgit2/cherrypick/bare.c b/tests/libgit2/cherrypick/bare.c new file mode 100644 index 000000000..f90ce0226 --- /dev/null +++ b/tests/libgit2/cherrypick/bare.c @@ -0,0 +1,105 @@ +#include "clar.h" +#include "clar_libgit2.h" + +#include "futils.h" +#include "git2/cherrypick.h" + +#include "../merge/merge_helpers.h" + +#define TEST_REPO_PATH "cherrypick" + +static git_repository *repo; + +void test_cherrypick_bare__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_cherrypick_bare__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_cherrypick_bare__automerge(void) +{ + git_commit *head = NULL, *commit = NULL; + git_index *index = NULL; + git_oid head_oid, cherry_oid; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "38c05a857e831a7e759d83778bfc85d003e21c45", 0, "file1.txt" }, + { 0100644, "a661b5dec1004e2c62654ded3762370c27cf266b", 0, "file2.txt" }, + { 0100644, "df6b290e0bd6a89b01d69f66687e8abf385283ca", 0, "file3.txt" }, + }; + + git_oid_fromstr(&head_oid, "d3d77487660ee3c0194ee01dc5eaf478782b1c7e"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + + git_oid_fromstr(&cherry_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); + cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); + + cl_git_pass(git_cherrypick_commit(&index, repo, commit, head, 0, NULL)); + cl_assert(merge_test_index(index, merge_index_entries, 3)); + + git_index_free(index); + git_commit_free(head); + git_commit_free(commit); +} + +void test_cherrypick_bare__conflicts(void) +{ + git_commit *head = NULL, *commit = NULL; + git_index *index = NULL; + git_oid head_oid, cherry_oid; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "242e7977ba73637822ffb265b46004b9b0e5153b", 0, "file1.txt" }, + { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 1, "file2.txt" }, + { 0100644, "bd6ffc8c6c41f0f85ff9e3d61c9479516bac0024", 2, "file2.txt" }, + { 0100644, "563f6473a3858f99b80e5f93c660512ed38e1e6f", 3, "file2.txt" }, + { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 1, "file3.txt" }, + { 0100644, "1124c2c1ae07b26fded662d6c3f3631d9dc16f88", 2, "file3.txt" }, + { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 3, "file3.txt" }, + }; + + git_oid_fromstr(&head_oid, "bafbf6912c09505ac60575cd43d3f2aba3bd84d8"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + + git_oid_fromstr(&cherry_oid, "e9b63f3655b2ad80c0ff587389b5a9589a3a7110"); + cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); + + cl_git_pass(git_cherrypick_commit(&index, repo, commit, head, 0, NULL)); + cl_assert(merge_test_index(index, merge_index_entries, 7)); + + git_index_free(index); + git_commit_free(head); + git_commit_free(commit); +} + +void test_cherrypick_bare__orphan(void) +{ + git_commit *head = NULL, *commit = NULL; + git_index *index = NULL; + git_oid head_oid, cherry_oid; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "38c05a857e831a7e759d83778bfc85d003e21c45", 0, "file1.txt" }, + { 0100644, "a661b5dec1004e2c62654ded3762370c27cf266b", 0, "file2.txt" }, + { 0100644, "85a4a1d791973644f24c72f5e89420d3064cc452", 0, "file3.txt" }, + { 0100644, "9ccb9bf50c011fd58dcbaa65df917bf79539717f", 0, "orphan.txt" }, + }; + + git_oid_fromstr(&head_oid, "d3d77487660ee3c0194ee01dc5eaf478782b1c7e"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + + git_oid_fromstr(&cherry_oid, "74f06b5bfec6d33d7264f73606b57a7c0b963819"); + cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); + + cl_git_pass(git_cherrypick_commit(&index, repo, commit, head, 0, NULL)); + cl_assert(merge_test_index(index, merge_index_entries, 4)); + + git_index_free(index); + git_commit_free(head); + git_commit_free(commit); +} + diff --git a/tests/libgit2/cherrypick/workdir.c b/tests/libgit2/cherrypick/workdir.c new file mode 100644 index 000000000..8fd1ecbdf --- /dev/null +++ b/tests/libgit2/cherrypick/workdir.c @@ -0,0 +1,469 @@ +#include "clar.h" +#include "clar_libgit2.h" + +#include "futils.h" +#include "git2/cherrypick.h" + +#include "../merge/merge_helpers.h" + +#define TEST_REPO_PATH "cherrypick" + +static git_repository *repo; +static git_index *repo_index; + +/* Fixture setup and teardown */ +void test_cherrypick_workdir__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); +} + +void test_cherrypick_workdir__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +/* git reset --hard d3d77487660ee3c0194ee01dc5eaf478782b1c7e + * git cherry-pick cfc4f0999a8367568e049af4f72e452d40828a15 + * git cherry-pick 964ea3da044d9083181a88ba6701de9e35778bf4 + * git cherry-pick a43a050c588d4e92f11a6b139680923e9728477d + */ +void test_cherrypick_workdir__automerge(void) +{ + git_oid head_oid; + git_signature *signature = NULL; + size_t i; + + const char *cherrypick_oids[] = { + "cfc4f0999a8367568e049af4f72e452d40828a15", + "964ea3da044d9083181a88ba6701de9e35778bf4", + "a43a050c588d4e92f11a6b139680923e9728477d", + }; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "38c05a857e831a7e759d83778bfc85d003e21c45", 0, "file1.txt" }, + { 0100644, "a661b5dec1004e2c62654ded3762370c27cf266b", 0, "file2.txt" }, + { 0100644, "df6b290e0bd6a89b01d69f66687e8abf385283ca", 0, "file3.txt" }, + + { 0100644, "38c05a857e831a7e759d83778bfc85d003e21c45", 0, "file1.txt" }, + { 0100644, "bd8fc3c59fb52d3c8b5907ace7defa5803f82419", 0, "file2.txt" }, + { 0100644, "df6b290e0bd6a89b01d69f66687e8abf385283ca", 0, "file3.txt" }, + + { 0100644, "f06427bee380364bc7e0cb26a9245158e4726ce0", 0, "file1.txt" }, + { 0100644, "bd8fc3c59fb52d3c8b5907ace7defa5803f82419", 0, "file2.txt" }, + { 0100644, "df6b290e0bd6a89b01d69f66687e8abf385283ca", 0, "file3.txt" }, + }; + + cl_git_pass(git_signature_new(&signature, "Picker", "picker@example.org", time(NULL), 0)); + + git_oid_fromstr(&head_oid, "d3d77487660ee3c0194ee01dc5eaf478782b1c7e"); + + for (i = 0; i < 3; ++i) { + git_commit *head = NULL, *commit = NULL; + git_oid cherry_oid, cherrypicked_oid, cherrypicked_tree_oid; + git_tree *cherrypicked_tree = NULL; + + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&cherry_oid, cherrypick_oids[i]); + cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); + cl_git_pass(git_cherrypick(repo, commit, NULL)); + + cl_assert(git_fs_path_exists(TEST_REPO_PATH "/.git/CHERRY_PICK_HEAD")); + cl_assert(git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); + + cl_git_pass(git_index_write_tree(&cherrypicked_tree_oid, repo_index)); + cl_git_pass(git_tree_lookup(&cherrypicked_tree, repo, &cherrypicked_tree_oid)); + cl_git_pass(git_commit_create(&cherrypicked_oid, repo, "HEAD", signature, signature, NULL, + "Cherry picked!", cherrypicked_tree, 1, (const git_commit **)&head)); + + cl_assert(merge_test_index(repo_index, merge_index_entries + i * 3, 3)); + + git_oid_cpy(&head_oid, &cherrypicked_oid); + + git_tree_free(cherrypicked_tree); + git_commit_free(head); + git_commit_free(commit); + } + + git_signature_free(signature); +} + +/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15 + * git cherry-pick a43a050c588d4e92f11a6b139680923e9728477d*/ +void test_cherrypick_workdir__empty_result(void) +{ + git_oid head_oid; + git_signature *signature = NULL; + git_commit *head = NULL, *commit = NULL; + git_oid cherry_oid; + + const char *cherrypick_oid = "a43a050c588d4e92f11a6b139680923e9728477d"; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "19c5c7207054604b69c84d08a7571ef9672bb5c2", 0, "file1.txt" }, + { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 0, "file2.txt" }, + { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 0, "file3.txt" }, + }; + + cl_git_pass(git_signature_new(&signature, "Picker", "picker@example.org", time(NULL), 0)); + + git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); + + /* Create an untracked file that should not conflict */ + cl_git_mkfile(TEST_REPO_PATH "/file4.txt", ""); + cl_assert(git_fs_path_exists(TEST_REPO_PATH "/file4.txt")); + + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&cherry_oid, cherrypick_oid); + cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); + cl_git_pass(git_cherrypick(repo, commit, NULL)); + + /* The resulting tree should not have changed, the change was already on HEAD */ + cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); + + git_commit_free(head); + git_commit_free(commit); + + git_signature_free(signature); +} + +/* git reset --hard bafbf6912c09505ac60575cd43d3f2aba3bd84d8 + * git cherry-pick e9b63f3655b2ad80c0ff587389b5a9589a3a7110 + */ +void test_cherrypick_workdir__conflicts(void) +{ + git_commit *head = NULL, *commit = NULL; + git_oid head_oid, cherry_oid; + git_str conflicting_buf = GIT_STR_INIT, mergemsg_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "242e7977ba73637822ffb265b46004b9b0e5153b", 0, "file1.txt" }, + { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 1, "file2.txt" }, + { 0100644, "bd6ffc8c6c41f0f85ff9e3d61c9479516bac0024", 2, "file2.txt" }, + { 0100644, "563f6473a3858f99b80e5f93c660512ed38e1e6f", 3, "file2.txt" }, + { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 1, "file3.txt" }, + { 0100644, "1124c2c1ae07b26fded662d6c3f3631d9dc16f88", 2, "file3.txt" }, + { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 3, "file3.txt" }, + }; + + git_oid_fromstr(&head_oid, "bafbf6912c09505ac60575cd43d3f2aba3bd84d8"); + + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&cherry_oid, "e9b63f3655b2ad80c0ff587389b5a9589a3a7110"); + cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); + cl_git_pass(git_cherrypick(repo, commit, NULL)); + + cl_assert(git_fs_path_exists(TEST_REPO_PATH "/.git/CHERRY_PICK_HEAD")); + cl_assert(git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 7)); + + cl_git_pass(git_futils_readbuffer(&mergemsg_buf, + TEST_REPO_PATH "/.git/MERGE_MSG")); + cl_assert(strcmp(git_str_cstr(&mergemsg_buf), + "Change all files\n" \ + "\n" \ + "#Conflicts:\n" \ + "#\tfile2.txt\n" \ + "#\tfile3.txt\n") == 0); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/file2.txt")); + + cl_assert(strcmp(git_str_cstr(&conflicting_buf), + "!File 2\n" \ + "File 2\n" \ + "File 2\n" \ + "File 2\n" \ + "File 2\n" \ + "File 2\n" \ + "File 2\n" \ + "File 2\n" \ + "File 2\n" \ + "File 2\n" \ + "File 2!!\n" \ + "File 2\n" \ + "File 2\n" \ + "File 2\n" \ + "<<<<<<< HEAD\n" \ + "File 2\n" \ + "=======\n" \ + "File 2!\n" \ + "File 2\n" \ + "File 2!\n" \ + ">>>>>>> e9b63f3... Change all files\n") == 0); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/file3.txt")); + + cl_assert(strcmp(git_str_cstr(&conflicting_buf), + "!File 3\n" \ + "File 3\n" \ + "File 3\n" \ + "File 3\n" \ + "File 3\n" \ + "File 3\n" \ + "File 3\n" \ + "File 3\n" \ + "File 3\n" \ + "File 3\n" \ + "File 3\n" \ + "File 3!!\n" \ + "File 3\n" \ + "File 3\n" \ + "File 3\n" \ + "<<<<<<< HEAD\n" \ + "=======\n" \ + "File 3!\n" \ + "File 3!\n" \ + ">>>>>>> e9b63f3... Change all files\n") == 0); + + git_commit_free(commit); + git_commit_free(head); + git_str_dispose(&mergemsg_buf); + git_str_dispose(&conflicting_buf); +} + +/* git reset --hard bafbf6912c09505ac60575cd43d3f2aba3bd84d8 + * git cherry-pick -X ours e9b63f3655b2ad80c0ff587389b5a9589a3a7110 + */ +void test_cherrypick_workdir__conflict_use_ours(void) +{ + git_commit *head = NULL, *commit = NULL; + git_oid head_oid, cherry_oid; + git_cherrypick_options opts = GIT_CHERRYPICK_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "242e7977ba73637822ffb265b46004b9b0e5153b", 0, "file1.txt" }, + { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 1, "file2.txt" }, + { 0100644, "bd6ffc8c6c41f0f85ff9e3d61c9479516bac0024", 2, "file2.txt" }, + { 0100644, "563f6473a3858f99b80e5f93c660512ed38e1e6f", 3, "file2.txt" }, + { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 1, "file3.txt" }, + { 0100644, "1124c2c1ae07b26fded662d6c3f3631d9dc16f88", 2, "file3.txt" }, + { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 3, "file3.txt" }, + }; + + struct merge_index_entry merge_filesystem_entries[] = { + { 0100644, "242e7977ba73637822ffb265b46004b9b0e5153b", 0, "file1.txt" }, + { 0100644, "bd6ffc8c6c41f0f85ff9e3d61c9479516bac0024", 0, "file2.txt" }, + { 0100644, "1124c2c1ae07b26fded662d6c3f3631d9dc16f88", 0, "file3.txt" }, + }; + + /* leave the index in a conflicted state, but checkout "ours" to the workdir */ + opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; + + git_oid_fromstr(&head_oid, "bafbf6912c09505ac60575cd43d3f2aba3bd84d8"); + + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&cherry_oid, "e9b63f3655b2ad80c0ff587389b5a9589a3a7110"); + cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); + cl_git_pass(git_cherrypick(repo, commit, &opts)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 7)); + cl_assert(merge_test_workdir(repo, merge_filesystem_entries, 3)); + + /* resolve conflicts in the index by taking "ours" */ + opts.merge_opts.file_favor = GIT_MERGE_FILE_FAVOR_OURS; + + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + cl_git_pass(git_cherrypick(repo, commit, &opts)); + + cl_assert(merge_test_index(repo_index, merge_filesystem_entries, 3)); + cl_assert(merge_test_workdir(repo, merge_filesystem_entries, 3)); + + git_commit_free(commit); + git_commit_free(head); +} + +/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15 + * git cherry-pick 2a26c7e88b285613b302ba76712bc998863f3cbc + */ +void test_cherrypick_workdir__rename(void) +{ + git_commit *head, *commit; + git_oid head_oid, cherry_oid; + git_cherrypick_options opts = GIT_CHERRYPICK_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "19c5c7207054604b69c84d08a7571ef9672bb5c2", 0, "file1.txt" }, + { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 0, "file2.txt" }, + { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 0, "file3.txt.renamed" }, + }; + + opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES; + opts.merge_opts.rename_threshold = 50; + + git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&cherry_oid, "2a26c7e88b285613b302ba76712bc998863f3cbc"); + cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); + cl_git_pass(git_cherrypick(repo, commit, &opts)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); + + git_commit_free(commit); + git_commit_free(head); +} + +/* git reset --hard 44cd2ed2052c9c68f9a439d208e9614dc2a55c70 + * git cherry-pick 2a26c7e88b285613b302ba76712bc998863f3cbc + */ +void test_cherrypick_workdir__both_renamed(void) +{ + git_commit *head, *commit; + git_oid head_oid, cherry_oid; + git_str mergemsg_buf = GIT_STR_INIT; + git_cherrypick_options opts = GIT_CHERRYPICK_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "19c5c7207054604b69c84d08a7571ef9672bb5c2", 0, "file1.txt" }, + { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 0, "file2.txt" }, + { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 1, "file3.txt" }, + { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 3, "file3.txt.renamed" }, + { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 2, "file3.txt.renamed_on_branch" }, + }; + + opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES; + opts.merge_opts.rename_threshold = 50; + + git_oid_fromstr(&head_oid, "44cd2ed2052c9c68f9a439d208e9614dc2a55c70"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&cherry_oid, "2a26c7e88b285613b302ba76712bc998863f3cbc"); + cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); + cl_git_pass(git_cherrypick(repo, commit, &opts)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 5)); + + cl_git_pass(git_futils_readbuffer(&mergemsg_buf, + TEST_REPO_PATH "/.git/MERGE_MSG")); + cl_assert(strcmp(git_str_cstr(&mergemsg_buf), + "Renamed file3.txt -> file3.txt.renamed\n" \ + "\n" \ + "#Conflicts:\n" \ + "#\tfile3.txt\n" \ + "#\tfile3.txt.renamed\n" \ + "#\tfile3.txt.renamed_on_branch\n") == 0); + + git_str_dispose(&mergemsg_buf); + git_commit_free(commit); + git_commit_free(head); +} + +void test_cherrypick_workdir__nonmerge_fails_mainline_specified(void) +{ + git_reference *head; + git_commit *commit; + git_cherrypick_options opts = GIT_CHERRYPICK_OPTIONS_INIT; + + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&commit, head, GIT_OBJECT_COMMIT)); + + opts.mainline = 1; + cl_must_fail(git_cherrypick(repo, commit, &opts)); + cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/CHERRY_PICK_HEAD")); + cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); + + git_reference_free(head); + git_commit_free(commit); +} + +/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15 + * git cherry-pick abe4603bc7cd5b8167a267e0e2418fd2348f8cff + */ +void test_cherrypick_workdir__merge_fails_without_mainline_specified(void) +{ + git_commit *head, *commit; + git_oid head_oid, cherry_oid; + + git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&cherry_oid, "abe4603bc7cd5b8167a267e0e2418fd2348f8cff"); + cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); + + cl_must_fail(git_cherrypick(repo, commit, NULL)); + cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/CHERRY_PICK_HEAD")); + cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); + + git_commit_free(commit); + git_commit_free(head); +} + +/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15 + * git cherry-pick -m1 abe4603bc7cd5b8167a267e0e2418fd2348f8cff + */ +void test_cherrypick_workdir__merge_first_parent(void) +{ + git_commit *head, *commit; + git_oid head_oid, cherry_oid; + git_cherrypick_options opts = GIT_CHERRYPICK_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "f90f9dcbdac2cce5cc166346160e19cb693ef4e8", 0, "file1.txt" }, + { 0100644, "563f6473a3858f99b80e5f93c660512ed38e1e6f", 0, "file2.txt" }, + { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 0, "file3.txt" }, + }; + + opts.mainline = 1; + + git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&cherry_oid, "abe4603bc7cd5b8167a267e0e2418fd2348f8cff"); + cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); + + cl_git_pass(git_cherrypick(repo, commit, &opts)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); + + git_commit_free(commit); + git_commit_free(head); +} + +/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15 + * git cherry-pick -m2 abe4603bc7cd5b8167a267e0e2418fd2348f8cff + */ +void test_cherrypick_workdir__merge_second_parent(void) +{ + git_commit *head, *commit; + git_oid head_oid, cherry_oid; + git_cherrypick_options opts = GIT_CHERRYPICK_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "487434cace79238a7091e2220611d4f20a765690", 0, "file1.txt" }, + { 0100644, "e5183bfd18e3a0a691fadde2f0d5610b73282d31", 0, "file2.txt" }, + { 0100644, "409a1bec58bf35348e8b62b72bb9c1f45cf5a587", 0, "file3.txt" }, + }; + + opts.mainline = 2; + + git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&cherry_oid, "abe4603bc7cd5b8167a267e0e2418fd2348f8cff"); + cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid)); + + cl_git_pass(git_cherrypick(repo, commit, &opts)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); + + git_commit_free(commit); + git_commit_free(head); +} + diff --git a/tests/libgit2/clar.c b/tests/libgit2/clar.c new file mode 100644 index 000000000..ca508d073 --- /dev/null +++ b/tests/libgit2/clar.c @@ -0,0 +1,788 @@ +/* + * Copyright (c) Vicent Marti. All rights reserved. + * + * This file is part of clar, distributed under the ISC license. + * For full terms see the included COPYING file. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* required for sandboxing */ +#include +#include + +#ifdef _WIN32 +# include +# include +# include +# include + +# define _MAIN_CC __cdecl + +# ifndef stat +# define stat(path, st) _stat(path, st) +# endif +# ifndef mkdir +# define mkdir(path, mode) _mkdir(path) +# endif +# ifndef chdir +# define chdir(path) _chdir(path) +# endif +# ifndef access +# define access(path, mode) _access(path, mode) +# endif +# ifndef strdup +# define strdup(str) _strdup(str) +# endif +# ifndef strcasecmp +# define strcasecmp(a,b) _stricmp(a,b) +# endif + +# ifndef __MINGW32__ +# pragma comment(lib, "shell32") +# ifndef strncpy +# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) +# endif +# ifndef W_OK +# define W_OK 02 +# endif +# ifndef S_ISDIR +# define S_ISDIR(x) ((x & _S_IFDIR) != 0) +# endif +# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__) +# else +# define p_snprintf snprintf +# endif + +# ifndef PRIuZ +# define PRIuZ "Iu" +# endif +# ifndef PRIxZ +# define PRIxZ "Ix" +# endif + +# if defined(_MSC_VER) || defined(__MINGW32__) + typedef struct stat STAT_T; +# else + typedef struct _stat STAT_T; +# endif +#else +# include /* waitpid(2) */ +# include +# define _MAIN_CC +# define p_snprintf snprintf +# ifndef PRIuZ +# define PRIuZ "zu" +# endif +# ifndef PRIxZ +# define PRIxZ "zx" +# endif + typedef struct stat STAT_T; +#endif + +#include "clar.h" + +static void fs_rm(const char *_source); +static void fs_copy(const char *_source, const char *dest); + +static const char * +fixture_path(const char *base, const char *fixture_name); + +struct clar_error { + const char *file; + const char *function; + size_t line_number; + const char *error_msg; + char *description; + + struct clar_error *next; +}; + +struct clar_explicit { + size_t suite_idx; + const char *filter; + + struct clar_explicit *next; +}; + +struct clar_report { + const char *test; + int test_number; + const char *suite; + + enum cl_test_status status; + + struct clar_error *errors; + struct clar_error *last_error; + + struct clar_report *next; +}; + +struct clar_summary { + const char *filename; + FILE *fp; +}; + +static struct { + enum cl_test_status test_status; + + const char *active_test; + const char *active_suite; + + int total_skipped; + int total_errors; + + int tests_ran; + int suites_ran; + + enum cl_output_format output_format; + + int report_errors_only; + int exit_on_error; + int report_suite_names; + + int write_summary; + char *summary_filename; + struct clar_summary *summary; + + struct clar_explicit *explicit; + struct clar_explicit *last_explicit; + + struct clar_report *reports; + struct clar_report *last_report; + + void (*local_cleanup)(void *); + void *local_cleanup_payload; + + jmp_buf trampoline; + int trampoline_enabled; + + cl_trace_cb *pfn_trace_cb; + void *trace_payload; + +} _clar; + +struct clar_func { + const char *name; + void (*ptr)(void); +}; + +struct clar_suite { + const char *name; + struct clar_func initialize; + struct clar_func cleanup; + const struct clar_func *tests; + size_t test_count; + int enabled; +}; + +/* From clar_print_*.c */ +static void clar_print_init(int test_count, int suite_count, const char *suite_names); +static void clar_print_shutdown(int test_count, int suite_count, int error_count); +static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error); +static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status failed); +static void clar_print_onsuite(const char *suite_name, int suite_index); +static void clar_print_onabort(const char *msg, ...); + +/* From clar_sandbox.c */ +static void clar_unsandbox(void); +static int clar_sandbox(void); + +/* From summary.h */ +static struct clar_summary *clar_summary_init(const char *filename); +static int clar_summary_shutdown(struct clar_summary *fp); + +/* Load the declarations for the test suite */ +#include "clar.suite" + + +#define CL_TRACE(ev) \ + do { \ + if (_clar.pfn_trace_cb) \ + _clar.pfn_trace_cb(ev, \ + _clar.active_suite, \ + _clar.active_test, \ + _clar.trace_payload); \ + } while (0) + +void cl_trace_register(cl_trace_cb *cb, void *payload) +{ + _clar.pfn_trace_cb = cb; + _clar.trace_payload = payload; +} + + +/* Core test functions */ +static void +clar_report_errors(struct clar_report *report) +{ + struct clar_error *error; + int i = 1; + + for (error = report->errors; error; error = error->next) + clar_print_error(i++, _clar.last_report, error); +} + +static void +clar_report_all(void) +{ + struct clar_report *report; + struct clar_error *error; + int i = 1; + + for (report = _clar.reports; report; report = report->next) { + if (report->status != CL_TEST_FAILURE) + continue; + + for (error = report->errors; error; error = error->next) + clar_print_error(i++, report, error); + } +} + +static void +clar_run_test( + const struct clar_func *test, + const struct clar_func *initialize, + const struct clar_func *cleanup) +{ + _clar.trampoline_enabled = 1; + + CL_TRACE(CL_TRACE__TEST__BEGIN); + + if (setjmp(_clar.trampoline) == 0) { + if (initialize->ptr != NULL) + initialize->ptr(); + + CL_TRACE(CL_TRACE__TEST__RUN_BEGIN); + test->ptr(); + CL_TRACE(CL_TRACE__TEST__RUN_END); + } + + _clar.trampoline_enabled = 0; + + if (_clar.last_report->status == CL_TEST_NOTRUN) + _clar.last_report->status = CL_TEST_OK; + + if (_clar.local_cleanup != NULL) + _clar.local_cleanup(_clar.local_cleanup_payload); + + if (cleanup->ptr != NULL) + cleanup->ptr(); + + CL_TRACE(CL_TRACE__TEST__END); + + _clar.tests_ran++; + + /* remove any local-set cleanup methods */ + _clar.local_cleanup = NULL; + _clar.local_cleanup_payload = NULL; + + if (_clar.report_errors_only) { + clar_report_errors(_clar.last_report); + } else { + clar_print_ontest(test->name, _clar.tests_ran, _clar.last_report->status); + } +} + +static void +clar_run_suite(const struct clar_suite *suite, const char *filter) +{ + const struct clar_func *test = suite->tests; + size_t i, matchlen; + struct clar_report *report; + int exact = 0; + + if (!suite->enabled) + return; + + if (_clar.exit_on_error && _clar.total_errors) + return; + + if (!_clar.report_errors_only) + clar_print_onsuite(suite->name, ++_clar.suites_ran); + + _clar.active_suite = suite->name; + _clar.active_test = NULL; + CL_TRACE(CL_TRACE__SUITE_BEGIN); + + if (filter) { + size_t suitelen = strlen(suite->name); + matchlen = strlen(filter); + if (matchlen <= suitelen) { + filter = NULL; + } else { + filter += suitelen; + while (*filter == ':') + ++filter; + matchlen = strlen(filter); + + if (matchlen && filter[matchlen - 1] == '$') { + exact = 1; + matchlen--; + } + } + } + + for (i = 0; i < suite->test_count; ++i) { + if (filter && strncmp(test[i].name, filter, matchlen)) + continue; + + if (exact && strlen(test[i].name) != matchlen) + continue; + + _clar.active_test = test[i].name; + + report = calloc(1, sizeof(struct clar_report)); + report->suite = _clar.active_suite; + report->test = _clar.active_test; + report->test_number = _clar.tests_ran; + report->status = CL_TEST_NOTRUN; + + if (_clar.reports == NULL) + _clar.reports = report; + + if (_clar.last_report != NULL) + _clar.last_report->next = report; + + _clar.last_report = report; + + clar_run_test(&test[i], &suite->initialize, &suite->cleanup); + + if (_clar.exit_on_error && _clar.total_errors) + return; + } + + _clar.active_test = NULL; + CL_TRACE(CL_TRACE__SUITE_END); +} + +static void +clar_usage(const char *arg) +{ + printf("Usage: %s [options]\n\n", arg); + printf("Options:\n"); + printf(" -sname Run only the suite with `name` (can go to individual test name)\n"); + printf(" -iname Include the suite with `name`\n"); + printf(" -xname Exclude the suite with `name`\n"); + printf(" -v Increase verbosity (show suite names)\n"); + printf(" -q Only report tests that had an error\n"); + printf(" -Q Quit as soon as a test fails\n"); + printf(" -t Display results in tap format\n"); + printf(" -l Print suite names\n"); + printf(" -r[filename] Write summary file (to the optional filename)\n"); + exit(-1); +} + +static void +clar_parse_args(int argc, char **argv) +{ + int i; + + /* Verify options before execute */ + for (i = 1; i < argc; ++i) { + char *argument = argv[i]; + + if (argument[0] != '-' || argument[1] == '\0' + || strchr("sixvqQtlr", argument[1]) == NULL) { + clar_usage(argv[0]); + } + } + + for (i = 1; i < argc; ++i) { + char *argument = argv[i]; + + switch (argument[1]) { + case 's': + case 'i': + case 'x': { /* given suite name */ + int offset = (argument[2] == '=') ? 3 : 2, found = 0; + char action = argument[1]; + size_t j, arglen, suitelen, cmplen; + + argument += offset; + arglen = strlen(argument); + + if (arglen == 0) + clar_usage(argv[0]); + + for (j = 0; j < _clar_suite_count; ++j) { + suitelen = strlen(_clar_suites[j].name); + cmplen = (arglen < suitelen) ? arglen : suitelen; + + if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) { + int exact = (arglen >= suitelen); + + /* Do we have a real suite prefix separated by a + * trailing '::' or just a matching substring? */ + if (arglen > suitelen && (argument[suitelen] != ':' + || argument[suitelen + 1] != ':')) + continue; + + ++found; + + if (!exact) + _clar.report_suite_names = 1; + + switch (action) { + case 's': { + struct clar_explicit *explicit = + calloc(1, sizeof(struct clar_explicit)); + assert(explicit); + + explicit->suite_idx = j; + explicit->filter = argument; + + if (_clar.explicit == NULL) + _clar.explicit = explicit; + + if (_clar.last_explicit != NULL) + _clar.last_explicit->next = explicit; + + _clar_suites[j].enabled = 1; + _clar.last_explicit = explicit; + break; + } + case 'i': _clar_suites[j].enabled = 1; break; + case 'x': _clar_suites[j].enabled = 0; break; + } + + if (exact) + break; + } + } + + if (!found) { + clar_print_onabort("No suite matching '%s' found.\n", argument); + exit(-1); + } + break; + } + + case 'q': + _clar.report_errors_only = 1; + break; + + case 'Q': + _clar.exit_on_error = 1; + break; + + case 't': + _clar.output_format = CL_OUTPUT_TAP; + break; + + case 'l': { + size_t j; + printf("Test suites (use -s to run just one):\n"); + for (j = 0; j < _clar_suite_count; ++j) + printf(" %3d: %s\n", (int)j, _clar_suites[j].name); + + exit(0); + } + + case 'v': + _clar.report_suite_names = 1; + break; + + case 'r': + _clar.write_summary = 1; + free(_clar.summary_filename); + _clar.summary_filename = strdup(*(argument + 2) ? (argument + 2) : "summary.xml"); + break; + + default: + assert(!"Unexpected commandline argument!"); + } + } +} + +void +clar_test_init(int argc, char **argv) +{ + if (argc > 1) + clar_parse_args(argc, argv); + + clar_print_init( + (int)_clar_callback_count, + (int)_clar_suite_count, + "" + ); + + if ((_clar.summary_filename = getenv("CLAR_SUMMARY")) != NULL) { + _clar.write_summary = 1; + _clar.summary_filename = strdup(_clar.summary_filename); + } + + if (_clar.write_summary && + !(_clar.summary = clar_summary_init(_clar.summary_filename))) { + clar_print_onabort("Failed to open the summary file\n"); + exit(-1); + } + + if (clar_sandbox() < 0) { + clar_print_onabort("Failed to sandbox the test runner.\n"); + exit(-1); + } +} + +int +clar_test_run(void) +{ + size_t i; + struct clar_explicit *explicit; + + if (_clar.explicit) { + for (explicit = _clar.explicit; explicit; explicit = explicit->next) + clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter); + } else { + for (i = 0; i < _clar_suite_count; ++i) + clar_run_suite(&_clar_suites[i], NULL); + } + + return _clar.total_errors; +} + +void +clar_test_shutdown(void) +{ + struct clar_explicit *explicit, *explicit_next; + struct clar_report *report, *report_next; + + clar_print_shutdown( + _clar.tests_ran, + (int)_clar_suite_count, + _clar.total_errors + ); + + clar_unsandbox(); + + if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) { + clar_print_onabort("Failed to write the summary file\n"); + exit(-1); + } + + for (explicit = _clar.explicit; explicit; explicit = explicit_next) { + explicit_next = explicit->next; + free(explicit); + } + + for (report = _clar.reports; report; report = report_next) { + report_next = report->next; + free(report); + } + + free(_clar.summary_filename); +} + +int +clar_test(int argc, char **argv) +{ + int errors; + + clar_test_init(argc, argv); + errors = clar_test_run(); + clar_test_shutdown(); + + return errors; +} + +static void abort_test(void) +{ + if (!_clar.trampoline_enabled) { + clar_print_onabort( + "Fatal error: a cleanup method raised an exception."); + clar_report_errors(_clar.last_report); + exit(-1); + } + + CL_TRACE(CL_TRACE__TEST__LONGJMP); + longjmp(_clar.trampoline, -1); +} + +void clar__skip(void) +{ + _clar.last_report->status = CL_TEST_SKIP; + _clar.total_skipped++; + abort_test(); +} + +void clar__fail( + const char *file, + const char *function, + size_t line, + const char *error_msg, + const char *description, + int should_abort) +{ + struct clar_error *error = calloc(1, sizeof(struct clar_error)); + + if (_clar.last_report->errors == NULL) + _clar.last_report->errors = error; + + if (_clar.last_report->last_error != NULL) + _clar.last_report->last_error->next = error; + + _clar.last_report->last_error = error; + + error->file = file; + error->function = function; + error->line_number = line; + error->error_msg = error_msg; + + if (description != NULL) + error->description = strdup(description); + + _clar.total_errors++; + _clar.last_report->status = CL_TEST_FAILURE; + + if (should_abort) + abort_test(); +} + +void clar__assert( + int condition, + const char *file, + const char *function, + size_t line, + const char *error_msg, + const char *description, + int should_abort) +{ + if (condition) + return; + + clar__fail(file, function, line, error_msg, description, should_abort); +} + +void clar__assert_equal( + const char *file, + const char *function, + size_t line, + const char *err, + int should_abort, + const char *fmt, + ...) +{ + va_list args; + char buf[4096]; + int is_equal = 1; + + va_start(args, fmt); + + if (!strcmp("%s", fmt)) { + const char *s1 = va_arg(args, const char *); + const char *s2 = va_arg(args, const char *); + is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2); + + if (!is_equal) { + if (s1 && s2) { + int pos; + for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) + /* find differing byte offset */; + p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", + s1, s2, pos); + } else { + p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2); + } + } + } + else if(!strcmp("%.*s", fmt)) { + const char *s1 = va_arg(args, const char *); + const char *s2 = va_arg(args, const char *); + int len = va_arg(args, int); + is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len); + + if (!is_equal) { + if (s1 && s2) { + int pos; + for (pos = 0; s1[pos] == s2[pos] && pos < len; ++pos) + /* find differing byte offset */; + p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s' (at byte %d)", + len, s1, len, s2, pos); + } else { + p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s'", len, s1, len, s2); + } + } + } + else if (!strcmp("%ls", fmt)) { + const wchar_t *wcs1 = va_arg(args, const wchar_t *); + const wchar_t *wcs2 = va_arg(args, const wchar_t *); + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2); + + if (!is_equal) { + if (wcs1 && wcs2) { + int pos; + for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos) + /* find differing byte offset */; + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)", + wcs1, wcs2, pos); + } else { + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2); + } + } + } + else if(!strcmp("%.*ls", fmt)) { + const wchar_t *wcs1 = va_arg(args, const wchar_t *); + const wchar_t *wcs2 = va_arg(args, const wchar_t *); + int len = va_arg(args, int); + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len); + + if (!is_equal) { + if (wcs1 && wcs2) { + int pos; + for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos) + /* find differing byte offset */; + p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls' (at byte %d)", + len, wcs1, len, wcs2, pos); + } else { + p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls'", len, wcs1, len, wcs2); + } + } + } + else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) { + size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t); + is_equal = (sz1 == sz2); + if (!is_equal) { + int offset = p_snprintf(buf, sizeof(buf), fmt, sz1); + strncat(buf, " != ", sizeof(buf) - offset); + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2); + } + } + else if (!strcmp("%p", fmt)) { + void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *); + is_equal = (p1 == p2); + if (!is_equal) + p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2); + } + else { + int i1 = va_arg(args, int), i2 = va_arg(args, int); + is_equal = (i1 == i2); + if (!is_equal) { + int offset = p_snprintf(buf, sizeof(buf), fmt, i1); + strncat(buf, " != ", sizeof(buf) - offset); + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2); + } + } + + va_end(args); + + if (!is_equal) + clar__fail(file, function, line, err, buf, should_abort); +} + +void cl_set_cleanup(void (*cleanup)(void *), void *opaque) +{ + _clar.local_cleanup = cleanup; + _clar.local_cleanup_payload = opaque; +} + +#include "clar/sandbox.h" +#include "clar/fixtures.h" +#include "clar/fs.h" +#include "clar/print.h" +#include "clar/summary.h" diff --git a/tests/libgit2/clar.h b/tests/libgit2/clar.h new file mode 100644 index 000000000..3f659c2f6 --- /dev/null +++ b/tests/libgit2/clar.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) Vicent Marti. All rights reserved. + * + * This file is part of clar, distributed under the ISC license. + * For full terms see the included COPYING file. + */ +#ifndef __CLAR_TEST_H__ +#define __CLAR_TEST_H__ + +#include + +enum cl_test_status { + CL_TEST_OK, + CL_TEST_FAILURE, + CL_TEST_SKIP, + CL_TEST_NOTRUN +}; + +enum cl_output_format { + CL_OUTPUT_CLAP, + CL_OUTPUT_TAP +}; + +/** Setup clar environment */ +void clar_test_init(int argc, char *argv[]); +int clar_test_run(void); +void clar_test_shutdown(void); + +/** One shot setup & run */ +int clar_test(int argc, char *argv[]); + +const char *clar_sandbox_path(void); + +void cl_set_cleanup(void (*cleanup)(void *), void *opaque); +void cl_fs_cleanup(void); + +/** + * cl_trace_* is a hook to provide a simple global tracing + * mechanism. + * + * The goal here is to let main() provide clar-proper + * with a callback to optionally write log info for + * test operations into the same stream used by their + * actual tests. This would let them print test names + * and maybe performance data as they choose. + * + * The goal is NOT to alter the flow of control or to + * override test selection/skipping. (So the callback + * does not return a value.) + * + * The goal is NOT to duplicate the existing + * pass/fail/skip reporting. (So the callback + * does not accept a status/errorcode argument.) + * + */ +typedef enum cl_trace_event { + CL_TRACE__SUITE_BEGIN, + CL_TRACE__SUITE_END, + CL_TRACE__TEST__BEGIN, + CL_TRACE__TEST__END, + CL_TRACE__TEST__RUN_BEGIN, + CL_TRACE__TEST__RUN_END, + CL_TRACE__TEST__LONGJMP +} cl_trace_event; + +typedef void (cl_trace_cb)( + cl_trace_event ev, + const char *suite_name, + const char *test_name, + void *payload); + +/** + * Register a callback into CLAR to send global trace events. + * Pass NULL to disable. + */ +void cl_trace_register(cl_trace_cb *cb, void *payload); + + +#ifdef CLAR_FIXTURE_PATH +const char *cl_fixture(const char *fixture_name); +void cl_fixture_sandbox(const char *fixture_name); +void cl_fixture_cleanup(const char *fixture_name); +const char *cl_fixture_basename(const char *fixture_name); +#endif + +/** + * Assertion macros with explicit error message + */ +#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 1) +#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 1) +#define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 1) + +/** + * Check macros with explicit error message + */ +#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 0) +#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 0) +#define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 0) + +/** + * Assertion macros with no error message + */ +#define cl_must_pass(expr) cl_must_pass_(expr, NULL) +#define cl_must_fail(expr) cl_must_fail_(expr, NULL) +#define cl_assert(expr) cl_assert_(expr, NULL) + +/** + * Check macros with no error message + */ +#define cl_check_pass(expr) cl_check_pass_(expr, NULL) +#define cl_check_fail(expr) cl_check_fail_(expr, NULL) +#define cl_check(expr) cl_check_(expr, NULL) + +/** + * Forced failure/warning + */ +#define cl_fail(desc) clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1) +#define cl_warning(desc) clar__fail(__FILE__, __func__, __LINE__, "Warning during test execution:", desc, 0) + +#define cl_skip() clar__skip() + +/** + * Typed assertion macros + */ +#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2)) +#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2)) + +#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2)) +#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2)) + +#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len)) +#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len)) + +#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len)) +#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len)) + +#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) +#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2)) +#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2)) + +#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__func__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0)) + +#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__func__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2)) + +void clar__skip(void); + +void clar__fail( + const char *file, + const char *func, + size_t line, + const char *error, + const char *description, + int should_abort); + +void clar__assert( + int condition, + const char *file, + const char *func, + size_t line, + const char *error, + const char *description, + int should_abort); + +void clar__assert_equal( + const char *file, + const char *func, + size_t line, + const char *err, + int should_abort, + const char *fmt, + ...); + +#endif diff --git a/tests/libgit2/clar/fixtures.h b/tests/libgit2/clar/fixtures.h new file mode 100644 index 000000000..77033d365 --- /dev/null +++ b/tests/libgit2/clar/fixtures.h @@ -0,0 +1,50 @@ +static const char * +fixture_path(const char *base, const char *fixture_name) +{ + static char _path[4096]; + size_t root_len; + + root_len = strlen(base); + strncpy(_path, base, sizeof(_path)); + + if (_path[root_len - 1] != '/') + _path[root_len++] = '/'; + + if (fixture_name[0] == '/') + fixture_name++; + + strncpy(_path + root_len, + fixture_name, + sizeof(_path) - root_len); + + return _path; +} + +#ifdef CLAR_FIXTURE_PATH +const char *cl_fixture(const char *fixture_name) +{ + return fixture_path(CLAR_FIXTURE_PATH, fixture_name); +} + +void cl_fixture_sandbox(const char *fixture_name) +{ + fs_copy(cl_fixture(fixture_name), _clar_path); +} + +const char *cl_fixture_basename(const char *fixture_name) +{ + const char *p; + + for (p = fixture_name; *p; p++) { + if (p[0] == '/' && p[1] && p[1] != '/') + fixture_name = p+1; + } + + return fixture_name; +} + +void cl_fixture_cleanup(const char *fixture_name) +{ + fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name))); +} +#endif diff --git a/tests/libgit2/clar/fs.h b/tests/libgit2/clar/fs.h new file mode 100644 index 000000000..44ede4572 --- /dev/null +++ b/tests/libgit2/clar/fs.h @@ -0,0 +1,520 @@ +/* + * By default, use a read/write loop to copy files on POSIX systems. + * On Linux, use sendfile by default as it's slightly faster. On + * macOS, we avoid fcopyfile by default because it's slightly slower. + */ +#undef USE_FCOPYFILE +#define USE_SENDFILE 1 + +#ifdef _WIN32 + +#ifdef CLAR_WIN32_LONGPATHS +# define CLAR_MAX_PATH 4096 +#else +# define CLAR_MAX_PATH MAX_PATH +#endif + +#define RM_RETRY_COUNT 5 +#define RM_RETRY_DELAY 10 + +#ifdef __MINGW32__ + +/* These security-enhanced functions are not available + * in MinGW, so just use the vanilla ones */ +#define wcscpy_s(a, b, c) wcscpy((a), (c)) +#define wcscat_s(a, b, c) wcscat((a), (c)) + +#endif /* __MINGW32__ */ + +static int +fs__dotordotdot(WCHAR *_tocheck) +{ + return _tocheck[0] == '.' && + (_tocheck[1] == '\0' || + (_tocheck[1] == '.' && _tocheck[2] == '\0')); +} + +static int +fs_rmdir_rmdir(WCHAR *_wpath) +{ + unsigned retries = 1; + + while (!RemoveDirectoryW(_wpath)) { + /* Only retry when we have retries remaining, and the + * error was ERROR_DIR_NOT_EMPTY. */ + if (retries++ > RM_RETRY_COUNT || + ERROR_DIR_NOT_EMPTY != GetLastError()) + return -1; + + /* Give whatever has a handle to a child item some time + * to release it before trying again */ + Sleep(RM_RETRY_DELAY * retries * retries); + } + + return 0; +} + +static void translate_path(WCHAR *path, size_t path_size) +{ + size_t path_len, i; + + if (wcsncmp(path, L"\\\\?\\", 4) == 0) + return; + + path_len = wcslen(path); + cl_assert(path_size > path_len + 4); + + for (i = path_len; i > 0; i--) { + WCHAR c = path[i - 1]; + + if (c == L'/') + path[i + 3] = L'\\'; + else + path[i + 3] = path[i - 1]; + } + + path[0] = L'\\'; + path[1] = L'\\'; + path[2] = L'?'; + path[3] = L'\\'; + path[path_len + 4] = L'\0'; +} + +static void +fs_rmdir_helper(WCHAR *_wsource) +{ + WCHAR buffer[CLAR_MAX_PATH]; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + size_t buffer_prefix_len; + + /* Set up the buffer and capture the length */ + wcscpy_s(buffer, CLAR_MAX_PATH, _wsource); + translate_path(buffer, CLAR_MAX_PATH); + wcscat_s(buffer, CLAR_MAX_PATH, L"\\"); + buffer_prefix_len = wcslen(buffer); + + /* FindFirstFile needs a wildcard to match multiple items */ + wcscat_s(buffer, CLAR_MAX_PATH, L"*"); + find_handle = FindFirstFileW(buffer, &find_data); + cl_assert(INVALID_HANDLE_VALUE != find_handle); + + do { + /* FindFirstFile/FindNextFile gives back . and .. + * entries at the beginning */ + if (fs__dotordotdot(find_data.cFileName)) + continue; + + wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName); + + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) + fs_rmdir_helper(buffer); + else { + /* If set, the +R bit must be cleared before deleting */ + if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes) + cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY)); + + cl_assert(DeleteFileW(buffer)); + } + } + while (FindNextFileW(find_handle, &find_data)); + + /* Ensure that we successfully completed the enumeration */ + cl_assert(ERROR_NO_MORE_FILES == GetLastError()); + + /* Close the find handle */ + FindClose(find_handle); + + /* Now that the directory is empty, remove it */ + cl_assert(0 == fs_rmdir_rmdir(_wsource)); +} + +static int +fs_rm_wait(WCHAR *_wpath) +{ + unsigned retries = 1; + DWORD last_error; + + do { + if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath)) + last_error = GetLastError(); + else + last_error = ERROR_SUCCESS; + + /* Is the item gone? */ + if (ERROR_FILE_NOT_FOUND == last_error || + ERROR_PATH_NOT_FOUND == last_error) + return 0; + + Sleep(RM_RETRY_DELAY * retries * retries); + } + while (retries++ <= RM_RETRY_COUNT); + + return -1; +} + +static void +fs_rm(const char *_source) +{ + WCHAR wsource[CLAR_MAX_PATH]; + DWORD attrs; + + /* The input path is UTF-8. Convert it to wide characters + * for use with the Windows API */ + cl_assert(MultiByteToWideChar(CP_UTF8, + MB_ERR_INVALID_CHARS, + _source, + -1, /* Indicates NULL termination */ + wsource, + CLAR_MAX_PATH)); + + translate_path(wsource, CLAR_MAX_PATH); + + /* Does the item exist? If not, we have no work to do */ + attrs = GetFileAttributesW(wsource); + + if (INVALID_FILE_ATTRIBUTES == attrs) + return; + + if (FILE_ATTRIBUTE_DIRECTORY & attrs) + fs_rmdir_helper(wsource); + else { + /* The item is a file. Strip the +R bit */ + if (FILE_ATTRIBUTE_READONLY & attrs) + cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY)); + + cl_assert(DeleteFileW(wsource)); + } + + /* Wait for the DeleteFile or RemoveDirectory call to complete */ + cl_assert(0 == fs_rm_wait(wsource)); +} + +static void +fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest) +{ + WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH]; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + size_t buf_source_prefix_len, buf_dest_prefix_len; + + wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource); + wcscat_s(buf_source, CLAR_MAX_PATH, L"\\"); + translate_path(buf_source, CLAR_MAX_PATH); + buf_source_prefix_len = wcslen(buf_source); + + wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest); + wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\"); + translate_path(buf_dest, CLAR_MAX_PATH); + buf_dest_prefix_len = wcslen(buf_dest); + + /* Get an enumerator for the items in the source. */ + wcscat_s(buf_source, CLAR_MAX_PATH, L"*"); + find_handle = FindFirstFileW(buf_source, &find_data); + cl_assert(INVALID_HANDLE_VALUE != find_handle); + + /* Create the target directory. */ + cl_assert(CreateDirectoryW(_wdest, NULL)); + + do { + /* FindFirstFile/FindNextFile gives back . and .. + * entries at the beginning */ + if (fs__dotordotdot(find_data.cFileName)) + continue; + + wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName); + wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName); + + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) + fs_copydir_helper(buf_source, buf_dest); + else + cl_assert(CopyFileW(buf_source, buf_dest, TRUE)); + } + while (FindNextFileW(find_handle, &find_data)); + + /* Ensure that we successfully completed the enumeration */ + cl_assert(ERROR_NO_MORE_FILES == GetLastError()); + + /* Close the find handle */ + FindClose(find_handle); +} + +static void +fs_copy(const char *_source, const char *_dest) +{ + WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH]; + DWORD source_attrs, dest_attrs; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + + /* The input paths are UTF-8. Convert them to wide characters + * for use with the Windows API. */ + cl_assert(MultiByteToWideChar(CP_UTF8, + MB_ERR_INVALID_CHARS, + _source, + -1, + wsource, + CLAR_MAX_PATH)); + + cl_assert(MultiByteToWideChar(CP_UTF8, + MB_ERR_INVALID_CHARS, + _dest, + -1, + wdest, + CLAR_MAX_PATH)); + + translate_path(wsource, CLAR_MAX_PATH); + translate_path(wdest, CLAR_MAX_PATH); + + /* Check the source for existence */ + source_attrs = GetFileAttributesW(wsource); + cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs); + + /* Check the target for existence */ + dest_attrs = GetFileAttributesW(wdest); + + if (INVALID_FILE_ATTRIBUTES != dest_attrs) { + /* Target exists; append last path part of source to target. + * Use FindFirstFile to parse the path */ + find_handle = FindFirstFileW(wsource, &find_data); + cl_assert(INVALID_HANDLE_VALUE != find_handle); + wcscat_s(wdest, CLAR_MAX_PATH, L"\\"); + wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName); + FindClose(find_handle); + + /* Check the new target for existence */ + cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest)); + } + + if (FILE_ATTRIBUTE_DIRECTORY & source_attrs) + fs_copydir_helper(wsource, wdest); + else + cl_assert(CopyFileW(wsource, wdest, TRUE)); +} + +void +cl_fs_cleanup(void) +{ + fs_rm(fixture_path(_clar_path, "*")); +} + +#else + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) +# include +#endif + +#if defined(__APPLE__) +# include +#endif + +static void basename_r(const char **out, int *out_len, const char *in) +{ + size_t in_len = strlen(in), start_pos; + + for (in_len = strlen(in); in_len; in_len--) { + if (in[in_len - 1] != '/') + break; + } + + for (start_pos = in_len; start_pos; start_pos--) { + if (in[start_pos - 1] == '/') + break; + } + + cl_assert(in_len - start_pos < INT_MAX); + + if (in_len - start_pos > 0) { + *out = &in[start_pos]; + *out_len = (in_len - start_pos); + } else { + *out = "/"; + *out_len = 1; + } +} + +static char *joinpath(const char *dir, const char *base, int base_len) +{ + char *out; + int len; + + if (base_len == -1) { + size_t bl = strlen(base); + + cl_assert(bl < INT_MAX); + base_len = (int)bl; + } + + len = strlen(dir) + base_len + 2; + cl_assert(len > 0); + + cl_assert(out = malloc(len)); + cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len); + + return out; +} + +static void +fs_copydir_helper(const char *source, const char *dest, int dest_mode) +{ + DIR *source_dir; + struct dirent *d; + + mkdir(dest, dest_mode); + + cl_assert_(source_dir = opendir(source), "Could not open source dir"); + while ((d = (errno = 0, readdir(source_dir))) != NULL) { + char *child; + + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + + child = joinpath(source, d->d_name, -1); + fs_copy(child, dest); + free(child); + } + + cl_assert_(errno == 0, "Failed to iterate source dir"); + + closedir(source_dir); +} + +static void +fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode) +{ + int in, out; + + cl_must_pass((in = open(source, O_RDONLY))); + cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode))); + +#if USE_FCOPYFILE && defined(__APPLE__) + ((void)(source_len)); /* unused */ + cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA)); +#elif USE_SENDFILE && defined(__linux__) + { + ssize_t ret = 0; + + while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) { + source_len -= (size_t)ret; + } + cl_assert(ret >= 0); + } +#else + { + char buf[131072]; + ssize_t ret; + + ((void)(source_len)); /* unused */ + + while ((ret = read(in, buf, sizeof(buf))) > 0) { + size_t len = (size_t)ret; + + while (len && (ret = write(out, buf, len)) > 0) { + cl_assert(ret <= (ssize_t)len); + len -= ret; + } + cl_assert(ret >= 0); + } + cl_assert(ret == 0); + } +#endif + + close(in); + close(out); +} + +static void +fs_copy(const char *source, const char *_dest) +{ + char *dbuf = NULL; + const char *dest = NULL; + struct stat source_st, dest_st; + + cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source"); + + if (lstat(_dest, &dest_st) == 0) { + const char *base; + int base_len; + + /* Target exists and is directory; append basename */ + cl_assert(S_ISDIR(dest_st.st_mode)); + + basename_r(&base, &base_len, source); + cl_assert(base_len < INT_MAX); + + dbuf = joinpath(_dest, base, base_len); + dest = dbuf; + } else if (errno != ENOENT) { + cl_fail("Cannot copy; cannot stat destination"); + } else { + dest = _dest; + } + + if (S_ISDIR(source_st.st_mode)) { + fs_copydir_helper(source, dest, source_st.st_mode); + } else { + fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode); + } + + free(dbuf); +} + +static void +fs_rmdir_helper(const char *path) +{ + DIR *dir; + struct dirent *d; + + cl_assert_(dir = opendir(path), "Could not open dir"); + while ((d = (errno = 0, readdir(dir))) != NULL) { + char *child; + + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + + child = joinpath(path, d->d_name, -1); + fs_rm(child); + free(child); + } + + cl_assert_(errno == 0, "Failed to iterate source dir"); + closedir(dir); + + cl_must_pass_(rmdir(path), "Could not remove directory"); +} + +static void +fs_rm(const char *path) +{ + struct stat st; + + if (lstat(path, &st)) { + if (errno == ENOENT) + return; + + cl_fail("Cannot copy; cannot stat destination"); + } + + if (S_ISDIR(st.st_mode)) { + fs_rmdir_helper(path); + } else { + cl_must_pass(unlink(path)); + } +} + +void +cl_fs_cleanup(void) +{ + clar_unsandbox(); + clar_sandbox(); +} +#endif diff --git a/tests/libgit2/clar/print.h b/tests/libgit2/clar/print.h new file mode 100644 index 000000000..dbfd27655 --- /dev/null +++ b/tests/libgit2/clar/print.h @@ -0,0 +1,200 @@ +/* clap: clar protocol, the traditional clar output format */ + +static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names) +{ + (void)test_count; + printf("Loaded %d suites: %s\n", (int)suite_count, suite_names); + printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n"); +} + +static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count) +{ + (void)test_count; + (void)suite_count; + (void)error_count; + + printf("\n\n"); + clar_report_all(); +} + +static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error) +{ + printf(" %d) Failure:\n", num); + + printf("%s::%s [%s:%"PRIuZ"]\n", + report->suite, + report->test, + error->file, + error->line_number); + + printf(" %s\n", error->error_msg); + + if (error->description != NULL) + printf(" %s\n", error->description); + + printf("\n"); + fflush(stdout); +} + +static void clar_print_clap_ontest(const char *test_name, int test_number, enum cl_test_status status) +{ + (void)test_name; + (void)test_number; + + switch(status) { + case CL_TEST_OK: printf("."); break; + case CL_TEST_FAILURE: printf("F"); break; + case CL_TEST_SKIP: printf("S"); break; + case CL_TEST_NOTRUN: printf("N"); break; + } + + fflush(stdout); +} + +static void clar_print_clap_onsuite(const char *suite_name, int suite_index) +{ + if (_clar.report_suite_names) + printf("\n%s", suite_name); + + (void)suite_index; +} + +static void clar_print_clap_onabort(const char *fmt, va_list arg) +{ + vfprintf(stderr, fmt, arg); +} + +/* tap: test anywhere protocol format */ + +static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names) +{ + (void)test_count; + (void)suite_count; + (void)suite_names; + printf("TAP version 13\n"); +} + +static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count) +{ + (void)suite_count; + (void)error_count; + + printf("1..%d\n", test_count); +} + +static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error) +{ + (void)num; + (void)report; + (void)error; +} + +static void print_escaped(const char *str) +{ + char *c; + + while ((c = strchr(str, '\'')) != NULL) { + printf("%.*s", (int)(c - str), str); + printf("''"); + str = c + 1; + } + + printf("%s", str); +} + +static void clar_print_tap_ontest(const char *test_name, int test_number, enum cl_test_status status) +{ + const struct clar_error *error = _clar.last_report->errors; + + (void)test_name; + (void)test_number; + + switch(status) { + case CL_TEST_OK: + printf("ok %d - %s::%s\n", test_number, _clar.active_suite, test_name); + break; + case CL_TEST_FAILURE: + printf("not ok %d - %s::%s\n", test_number, _clar.active_suite, test_name); + + printf(" ---\n"); + printf(" reason: |\n"); + printf(" %s\n", error->error_msg); + + if (error->description) + printf(" %s\n", error->description); + + printf(" at:\n"); + printf(" file: '"); print_escaped(error->file); printf("'\n"); + printf(" line: %" PRIuZ "\n", error->line_number); + printf(" function: '%s'\n", error->function); + printf(" ---\n"); + + break; + case CL_TEST_SKIP: + case CL_TEST_NOTRUN: + printf("ok %d - # SKIP %s::%s\n", test_number, _clar.active_suite, test_name); + break; + } + + fflush(stdout); +} + +static void clar_print_tap_onsuite(const char *suite_name, int suite_index) +{ + printf("# start of suite %d: %s\n", suite_index, suite_name); +} + +static void clar_print_tap_onabort(const char *fmt, va_list arg) +{ + printf("Bail out! "); + vprintf(fmt, arg); + fflush(stdout); +} + +/* indirection between protocol output selection */ + +#define PRINT(FN, ...) do { \ + switch (_clar.output_format) { \ + case CL_OUTPUT_CLAP: \ + clar_print_clap_##FN (__VA_ARGS__); \ + break; \ + case CL_OUTPUT_TAP: \ + clar_print_tap_##FN (__VA_ARGS__); \ + break; \ + default: \ + abort(); \ + } \ + } while (0) + +static void clar_print_init(int test_count, int suite_count, const char *suite_names) +{ + PRINT(init, test_count, suite_count, suite_names); +} + +static void clar_print_shutdown(int test_count, int suite_count, int error_count) +{ + PRINT(shutdown, test_count, suite_count, error_count); +} + +static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error) +{ + PRINT(error, num, report, error); +} + +static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status status) +{ + PRINT(ontest, test_name, test_number, status); +} + +static void clar_print_onsuite(const char *suite_name, int suite_index) +{ + PRINT(onsuite, suite_name, suite_index); +} + +static void clar_print_onabort(const char *msg, ...) +{ + va_list argp; + va_start(argp, msg); + PRINT(onabort, msg, argp); + va_end(argp); +} diff --git a/tests/libgit2/clar/sandbox.h b/tests/libgit2/clar/sandbox.h new file mode 100644 index 000000000..0ba147962 --- /dev/null +++ b/tests/libgit2/clar/sandbox.h @@ -0,0 +1,154 @@ +#ifdef __APPLE__ +#include +#endif + +static char _clar_path[4096 + 1]; + +static int +is_valid_tmp_path(const char *path) +{ + STAT_T st; + + if (stat(path, &st) != 0) + return 0; + + if (!S_ISDIR(st.st_mode)) + return 0; + + return (access(path, W_OK) == 0); +} + +static int +find_tmp_path(char *buffer, size_t length) +{ +#ifndef _WIN32 + static const size_t var_count = 5; + static const char *env_vars[] = { + "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE" + }; + + size_t i; + + for (i = 0; i < var_count; ++i) { + const char *env = getenv(env_vars[i]); + if (!env) + continue; + + if (is_valid_tmp_path(env)) { +#ifdef __APPLE__ + if (length >= PATH_MAX && realpath(env, buffer) != NULL) + return 0; +#endif + strncpy(buffer, env, length - 1); + buffer[length - 1] = '\0'; + return 0; + } + } + + /* If the environment doesn't say anything, try to use /tmp */ + if (is_valid_tmp_path("/tmp")) { +#ifdef __APPLE__ + if (length >= PATH_MAX && realpath("/tmp", buffer) != NULL) + return 0; +#endif + strncpy(buffer, "/tmp", length - 1); + buffer[length - 1] = '\0'; + return 0; + } + +#else + DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length); + if (env_len > 0 && env_len < (DWORD)length) + return 0; + + if (GetTempPath((DWORD)length, buffer)) + return 0; +#endif + + /* This system doesn't like us, try to use the current directory */ + if (is_valid_tmp_path(".")) { + strncpy(buffer, ".", length - 1); + buffer[length - 1] = '\0'; + return 0; + } + + return -1; +} + +static void clar_unsandbox(void) +{ + if (_clar_path[0] == '\0') + return; + + cl_must_pass(chdir("..")); + + fs_rm(_clar_path); +} + +static int build_sandbox_path(void) +{ +#ifdef CLAR_TMPDIR + const char path_tail[] = CLAR_TMPDIR "_XXXXXX"; +#else + const char path_tail[] = "clar_tmp_XXXXXX"; +#endif + + size_t len; + + if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0) + return -1; + + len = strlen(_clar_path); + +#ifdef _WIN32 + { /* normalize path to POSIX forward slashes */ + size_t i; + for (i = 0; i < len; ++i) { + if (_clar_path[i] == '\\') + _clar_path[i] = '/'; + } + } +#endif + + if (_clar_path[len - 1] != '/') { + _clar_path[len++] = '/'; + } + + strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len); + +#if defined(__MINGW32__) + if (_mktemp(_clar_path) == NULL) + return -1; + + if (mkdir(_clar_path, 0700) != 0) + return -1; +#elif defined(_WIN32) + if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0) + return -1; + + if (mkdir(_clar_path, 0700) != 0) + return -1; +#else + if (mkdtemp(_clar_path) == NULL) + return -1; +#endif + + return 0; +} + +static int clar_sandbox(void) +{ + if (_clar_path[0] == '\0' && build_sandbox_path() < 0) + return -1; + + if (chdir(_clar_path) != 0) + return -1; + + return 0; +} + +const char *clar_sandbox_path(void) +{ + return _clar_path; +} + diff --git a/tests/libgit2/clar/summary.h b/tests/libgit2/clar/summary.h new file mode 100644 index 000000000..6279f5057 --- /dev/null +++ b/tests/libgit2/clar/summary.h @@ -0,0 +1,134 @@ + +#include +#include + +static int clar_summary_close_tag( + struct clar_summary *summary, const char *tag, int indent) +{ + const char *indt; + + if (indent == 0) indt = ""; + else if (indent == 1) indt = "\t"; + else indt = "\t\t"; + + return fprintf(summary->fp, "%s\n", indt, tag); +} + +static int clar_summary_testsuites(struct clar_summary *summary) +{ + return fprintf(summary->fp, "\n"); +} + +static int clar_summary_testsuite(struct clar_summary *summary, + int idn, const char *name, const char *pkg, time_t timestamp, + double elapsed, int test_count, int fail_count, int error_count) +{ + struct tm *tm = localtime(×tamp); + char iso_dt[20]; + + if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0) + return -1; + + return fprintf(summary->fp, "\t\n", + idn, name, pkg, iso_dt, elapsed, test_count, fail_count, error_count); +} + +static int clar_summary_testcase(struct clar_summary *summary, + const char *name, const char *classname, double elapsed) +{ + return fprintf(summary->fp, + "\t\t\n", + name, classname, elapsed); +} + +static int clar_summary_failure(struct clar_summary *summary, + const char *type, const char *message, const char *desc) +{ + return fprintf(summary->fp, + "\t\t\t\n", + type, message, desc); +} + +struct clar_summary *clar_summary_init(const char *filename) +{ + struct clar_summary *summary; + FILE *fp; + + if ((fp = fopen(filename, "w")) == NULL) + return NULL; + + if ((summary = malloc(sizeof(struct clar_summary))) == NULL) { + fclose(fp); + return NULL; + } + + summary->filename = filename; + summary->fp = fp; + + return summary; +} + +int clar_summary_shutdown(struct clar_summary *summary) +{ + struct clar_report *report; + const char *last_suite = NULL; + + if (clar_summary_testsuites(summary) < 0) + goto on_error; + + report = _clar.reports; + while (report != NULL) { + struct clar_error *error = report->errors; + + if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) { + if (clar_summary_testsuite(summary, 0, report->suite, "", + time(NULL), 0, _clar.tests_ran, _clar.total_errors, 0) < 0) + goto on_error; + } + + last_suite = report->suite; + + clar_summary_testcase(summary, report->test, "what", 0); + + while (error != NULL) { + if (clar_summary_failure(summary, "assert", + error->error_msg, error->description) < 0) + goto on_error; + + error = error->next; + } + + if (clar_summary_close_tag(summary, "testcase", 2) < 0) + goto on_error; + + report = report->next; + + if (!report || strcmp(last_suite, report->suite) != 0) { + if (clar_summary_close_tag(summary, "testsuite", 1) < 0) + goto on_error; + } + } + + if (clar_summary_close_tag(summary, "testsuites", 0) < 0 || + fclose(summary->fp) != 0) + goto on_error; + + printf("written summary file to %s\n", summary->filename); + + free(summary); + return 0; + +on_error: + fclose(summary->fp); + free(summary); + return -1; +} diff --git a/tests/libgit2/clar_libgit2.c b/tests/libgit2/clar_libgit2.c new file mode 100644 index 000000000..55a09d111 --- /dev/null +++ b/tests/libgit2/clar_libgit2.c @@ -0,0 +1,623 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "fs_path.h" +#include "git2/sys/repository.h" + +void cl_git_report_failure( + int error, int expected, const char *file, const char *func, int line, const char *fncall) +{ + char msg[4096]; + const git_error *last = git_error_last(); + + if (expected) + p_snprintf(msg, 4096, "error %d (expected %d) - %s", + error, expected, last ? last->message : ""); + else if (error || last) + p_snprintf(msg, 4096, "error %d - %s", + error, last ? last->message : ""); + else + p_snprintf(msg, 4096, "no error, expected non-zero return"); + + clar__assert(0, file, func, line, fncall, msg, 1); +} + +void cl_git_mkfile(const char *filename, const char *content) +{ + int fd; + + fd = p_creat(filename, 0666); + cl_assert(fd != -1); + + if (content) { + cl_must_pass(p_write(fd, content, strlen(content))); + } else { + cl_must_pass(p_write(fd, filename, strlen(filename))); + cl_must_pass(p_write(fd, "\n", 1)); + } + + cl_must_pass(p_close(fd)); +} + +void cl_git_write2file( + const char *path, const char *content, size_t content_len, + int flags, unsigned int mode) +{ + int fd; + cl_assert(path && content); + cl_assert((fd = p_open(path, flags, mode)) >= 0); + if (!content_len) + content_len = strlen(content); + cl_must_pass(p_write(fd, content, content_len)); + cl_must_pass(p_close(fd)); +} + +void cl_git_append2file(const char *path, const char *content) +{ + cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_APPEND, 0644); +} + +void cl_git_rewritefile(const char *path, const char *content) +{ + cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_TRUNC, 0644); +} + +void cl_git_rmfile(const char *filename) +{ + cl_must_pass(p_unlink(filename)); +} + +char *cl_getenv(const char *name) +{ + git_str out = GIT_STR_INIT; + int error = git__getenv(&out, name); + + cl_assert(error >= 0 || error == GIT_ENOTFOUND); + + if (error == GIT_ENOTFOUND) + return NULL; + + if (out.size == 0) { + char *dup = git__strdup(""); + cl_assert(dup); + + return dup; + } + + return git_str_detach(&out); +} + +bool cl_is_env_set(const char *name) +{ + char *env = cl_getenv(name); + bool result = (env != NULL); + git__free(env); + return result; +} + +#ifdef GIT_WIN32 + +#include "win32/utf-conv.h" + +int cl_setenv(const char *name, const char *value) +{ + wchar_t *wide_name, *wide_value = NULL; + + cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0); + + if (value) { + cl_assert(git__utf8_to_16_alloc(&wide_value, value) >= 0); + cl_assert(SetEnvironmentVariableW(wide_name, wide_value)); + } else { + /* Windows XP returns 0 (failed) when passing NULL for lpValue when + * lpName does not exist in the environment block. This behavior + * seems to have changed in later versions. Don't check the return value + * of SetEnvironmentVariable when passing NULL for lpValue. */ + SetEnvironmentVariableW(wide_name, NULL); + } + + git__free(wide_name); + git__free(wide_value); + return 0; +} + +/* This function performs retries on calls to MoveFile in order + * to provide enhanced reliability in the face of antivirus + * agents that may be scanning the source (or in the case that + * the source is a directory, a child of the source). */ +int cl_rename(const char *source, const char *dest) +{ + git_win32_path source_utf16; + git_win32_path dest_utf16; + unsigned retries = 1; + + cl_assert(git_win32_path_from_utf8(source_utf16, source) >= 0); + cl_assert(git_win32_path_from_utf8(dest_utf16, dest) >= 0); + + while (!MoveFileW(source_utf16, dest_utf16)) { + /* Only retry if the error is ERROR_ACCESS_DENIED; + * this may indicate that an antivirus agent is + * preventing the rename from source to target */ + if (retries > 5 || + ERROR_ACCESS_DENIED != GetLastError()) + return -1; + + /* With 5 retries and a coefficient of 10ms, the maximum + * delay here is 550 ms */ + Sleep(10 * retries * retries); + retries++; + } + + return 0; +} + +#else + +#include + +int cl_setenv(const char *name, const char *value) +{ + return (value == NULL) ? unsetenv(name) : setenv(name, value, 1); +} + +int cl_rename(const char *source, const char *dest) +{ + return p_rename(source, dest); +} + +#endif + +static const char *_cl_sandbox = NULL; +static git_repository *_cl_repo = NULL; + +git_repository *cl_git_sandbox_init(const char *sandbox) +{ + /* Get the name of the sandbox folder which will be created */ + const char *basename = cl_fixture_basename(sandbox); + + /* Copy the whole sandbox folder from our fixtures to our test sandbox + * area. After this it can be accessed with `./sandbox` + */ + cl_fixture_sandbox(sandbox); + _cl_sandbox = sandbox; + + cl_git_pass(p_chdir(basename)); + + /* If this is not a bare repo, then rename `sandbox/.gitted` to + * `sandbox/.git` which must be done since we cannot store a folder + * named `.git` inside the fixtures folder of our libgit2 repo. + */ + if (p_access(".gitted", F_OK) == 0) + cl_git_pass(cl_rename(".gitted", ".git")); + + /* If we have `gitattributes`, rename to `.gitattributes`. This may + * be necessary if we don't want the attributes to be applied in the + * libgit2 repo, but just during testing. + */ + if (p_access("gitattributes", F_OK) == 0) + cl_git_pass(cl_rename("gitattributes", ".gitattributes")); + + /* As with `gitattributes`, we may need `gitignore` just for testing. */ + if (p_access("gitignore", F_OK) == 0) + cl_git_pass(cl_rename("gitignore", ".gitignore")); + + cl_git_pass(p_chdir("..")); + + /* Now open the sandbox repository and make it available for tests */ + cl_git_pass(git_repository_open(&_cl_repo, basename)); + + /* Adjust configs after copying to new filesystem */ + cl_git_pass(git_repository_reinit_filesystem(_cl_repo, 0)); + + return _cl_repo; +} + +git_repository *cl_git_sandbox_init_new(const char *sandbox) +{ + cl_git_pass(git_repository_init(&_cl_repo, sandbox, false)); + _cl_sandbox = sandbox; + + return _cl_repo; +} + +git_repository *cl_git_sandbox_reopen(void) +{ + if (_cl_repo) { + git_repository_free(_cl_repo); + _cl_repo = NULL; + + cl_git_pass(git_repository_open( + &_cl_repo, cl_fixture_basename(_cl_sandbox))); + } + + return _cl_repo; +} + +void cl_git_sandbox_cleanup(void) +{ + if (_cl_repo) { + git_repository_free(_cl_repo); + _cl_repo = NULL; + } + if (_cl_sandbox) { + cl_fixture_cleanup(_cl_sandbox); + _cl_sandbox = NULL; + } +} + +bool cl_toggle_filemode(const char *filename) +{ + struct stat st1, st2; + + cl_must_pass(p_stat(filename, &st1)); + cl_must_pass(p_chmod(filename, st1.st_mode ^ 0100)); + cl_must_pass(p_stat(filename, &st2)); + + return (st1.st_mode != st2.st_mode); +} + +bool cl_is_chmod_supported(void) +{ + static int _is_supported = -1; + + if (_is_supported < 0) { + cl_git_mkfile("filemode.t", "Test if filemode can be modified"); + _is_supported = cl_toggle_filemode("filemode.t"); + cl_must_pass(p_unlink("filemode.t")); + } + + return _is_supported; +} + +const char* cl_git_fixture_url(const char *fixturename) +{ + return cl_git_path_url(cl_fixture(fixturename)); +} + +const char* cl_git_path_url(const char *path) +{ + static char url[4096 + 1]; + + const char *in_buf; + git_str path_buf = GIT_STR_INIT; + git_str url_buf = GIT_STR_INIT; + + cl_git_pass(git_fs_path_prettify_dir(&path_buf, path, NULL)); + cl_git_pass(git_str_puts(&url_buf, "file://")); + +#ifdef GIT_WIN32 + /* + * A FILE uri matches the following format: file://[host]/path + * where "host" can be empty and "path" is an absolute path to the resource. + * + * In this test, no hostname is used, but we have to ensure the leading triple slashes: + * + * *nix: file:///usr/home/... + * Windows: file:///C:/Users/... + */ + cl_git_pass(git_str_putc(&url_buf, '/')); +#endif + + in_buf = git_str_cstr(&path_buf); + + /* + * A very hacky Url encoding that only takes care of escaping the spaces + */ + while (*in_buf) { + if (*in_buf == ' ') + cl_git_pass(git_str_puts(&url_buf, "%20")); + else + cl_git_pass(git_str_putc(&url_buf, *in_buf)); + + in_buf++; + } + + cl_assert(url_buf.size < sizeof(url) - 1); + + strncpy(url, git_str_cstr(&url_buf), sizeof(url) - 1); + url[sizeof(url) - 1] = '\0'; + git_str_dispose(&url_buf); + git_str_dispose(&path_buf); + return url; +} + +const char *cl_git_sandbox_path(int is_dir, ...) +{ + const char *path = NULL; + static char _temp[GIT_PATH_MAX]; + git_str buf = GIT_STR_INIT; + va_list arg; + + cl_git_pass(git_str_sets(&buf, clar_sandbox_path())); + + va_start(arg, is_dir); + + while ((path = va_arg(arg, const char *)) != NULL) { + cl_git_pass(git_str_joinpath(&buf, buf.ptr, path)); + } + va_end(arg); + + cl_git_pass(git_fs_path_prettify(&buf, buf.ptr, NULL)); + if (is_dir) + git_fs_path_to_dir(&buf); + + /* make sure we won't truncate */ + cl_assert(git_str_len(&buf) < sizeof(_temp)); + git_str_copy_cstr(_temp, sizeof(_temp), &buf); + + git_str_dispose(&buf); + + return _temp; +} + +typedef struct { + const char *filename; + size_t filename_len; +} remove_data; + +static int remove_placeholders_recurs(void *_data, git_str *path) +{ + remove_data *data = (remove_data *)_data; + size_t pathlen; + + if (git_fs_path_isdir(path->ptr) == true) + return git_fs_path_direach(path, 0, remove_placeholders_recurs, data); + + pathlen = path->size; + + if (pathlen < data->filename_len) + return 0; + + /* if path ends in '/'+filename (or equals filename) */ + if (!strcmp(data->filename, path->ptr + pathlen - data->filename_len) && + (pathlen == data->filename_len || + path->ptr[pathlen - data->filename_len - 1] == '/')) + return p_unlink(path->ptr); + + return 0; +} + +int cl_git_remove_placeholders(const char *directory_path, const char *filename) +{ + int error; + remove_data data; + git_str buffer = GIT_STR_INIT; + + if (git_fs_path_isdir(directory_path) == false) + return -1; + + if (git_str_sets(&buffer, directory_path) < 0) + return -1; + + data.filename = filename; + data.filename_len = strlen(filename); + + error = remove_placeholders_recurs(&data, &buffer); + + git_str_dispose(&buffer); + + return error; +} + +#define CL_COMMIT_NAME "Libgit2 Tester" +#define CL_COMMIT_EMAIL "libgit2-test@github.com" +#define CL_COMMIT_MSG "Test commit of tree " + +void cl_repo_commit_from_index( + git_oid *out, + git_repository *repo, + git_signature *sig, + git_time_t time, + const char *msg) +{ + git_index *index; + git_oid commit_id, tree_id; + git_object *parent = NULL; + git_reference *ref = NULL; + git_tree *tree = NULL; + char buf[128]; + int free_sig = (sig == NULL); + + /* it is fine if looking up HEAD fails - we make this the first commit */ + git_revparse_ext(&parent, &ref, repo, "HEAD"); + + /* write the index content as a tree */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_tree_lookup(&tree, repo, &tree_id)); + + if (sig) + cl_assert(sig->name && sig->email); + else if (!time) + cl_git_pass(git_signature_now(&sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL)); + else + cl_git_pass(git_signature_new( + &sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL, time, 0)); + + if (!msg) { + strcpy(buf, CL_COMMIT_MSG); + git_oid_tostr(buf + strlen(CL_COMMIT_MSG), + sizeof(buf) - strlen(CL_COMMIT_MSG), &tree_id); + msg = buf; + } + + cl_git_pass(git_commit_create_v( + &commit_id, repo, ref ? git_reference_name(ref) : "HEAD", + sig, sig, NULL, msg, tree, parent ? 1 : 0, parent)); + + if (out) + git_oid_cpy(out, &commit_id); + + git_object_free(parent); + git_reference_free(ref); + if (free_sig) + git_signature_free(sig); + git_tree_free(tree); +} + +void cl_repo_set_bool(git_repository *repo, const char *cfg, int value) +{ + git_config *config; + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, cfg, value != 0)); + git_config_free(config); +} + +int cl_repo_get_bool(git_repository *repo, const char *cfg) +{ + int val = 0; + git_config *config; + cl_git_pass(git_repository_config(&config, repo)); + if (git_config_get_bool(&val, config, cfg) < 0) + git_error_clear(); + git_config_free(config); + return val; +} + +void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value) +{ + git_config *config; + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_string(config, cfg, value)); + git_config_free(config); +} + +/* this is essentially the code from git__unescape modified slightly */ +static size_t strip_cr_from_buf(char *start, size_t len) +{ + char *scan, *trail, *end = start + len; + + for (scan = trail = start; scan < end; trail++, scan++) { + while (*scan == '\r') + scan++; /* skip '\r' */ + + if (trail != scan) + *trail = *scan; + } + + *trail = '\0'; + + return (trail - start); +} + +void 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) +{ + char buf[4000]; + ssize_t bytes, total_bytes = 0; + int fd = p_open(path, O_RDONLY | O_BINARY); + cl_assert(fd >= 0); + + if (expected_data && !expected_bytes) + expected_bytes = strlen(expected_data); + + while ((bytes = p_read(fd, buf, sizeof(buf))) != 0) { + clar__assert( + bytes > 0, file, func, line, "error reading from file", path, 1); + + if (ignore_cr) + bytes = strip_cr_from_buf(buf, bytes); + + if (memcmp(expected_data, buf, bytes) != 0) { + int pos; + for (pos = 0; pos < bytes && expected_data[pos] == buf[pos]; ++pos) + /* find differing byte offset */; + p_snprintf( + buf, sizeof(buf), "file content mismatch at byte %"PRIdZ, + (ssize_t)(total_bytes + pos)); + p_close(fd); + clar__fail(file, func, line, path, buf, 1); + } + + expected_data += bytes; + total_bytes += bytes; + } + + p_close(fd); + + clar__assert(!bytes, file, func, line, "error reading from file", path, 1); + clar__assert_equal(file, func, line, "mismatched file length", 1, "%"PRIuZ, + (size_t)expected_bytes, (size_t)total_bytes); +} + +static git_buf _cl_restore_home = GIT_BUF_INIT; + +void cl_fake_home_cleanup(void *payload) +{ + GIT_UNUSED(payload); + + if (_cl_restore_home.ptr) { + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, _cl_restore_home.ptr)); + git_buf_dispose(&_cl_restore_home); + } +} + +void cl_fake_home(void) +{ + git_str path = GIT_STR_INIT; + + cl_git_pass(git_libgit2_opts( + GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &_cl_restore_home)); + + cl_set_cleanup(cl_fake_home_cleanup, NULL); + + if (!git_fs_path_exists("home")) + cl_must_pass(p_mkdir("home", 0777)); + cl_git_pass(git_fs_path_prettify(&path, "home", NULL)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); + git_str_dispose(&path); +} + +void cl_sandbox_set_search_path_defaults(void) +{ + git_str path = GIT_STR_INIT; + + git_str_joinpath(&path, clar_sandbox_path(), "__config"); + + if (!git_fs_path_exists(path.ptr)) + cl_must_pass(p_mkdir(path.ptr, 0777)); + + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr); + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr); + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr); + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_PROGRAMDATA, path.ptr); + + git_str_dispose(&path); +} + +#ifdef GIT_WIN32 +bool cl_sandbox_supports_8dot3(void) +{ + git_str longpath = GIT_STR_INIT; + char *shortname; + bool supported; + + cl_git_pass( + git_str_joinpath(&longpath, clar_sandbox_path(), "longer_than_8dot3")); + + cl_git_write2file(longpath.ptr, "", 0, O_RDWR|O_CREAT, 0666); + shortname = git_win32_path_8dot3_name(longpath.ptr); + + supported = (shortname != NULL); + + git__free(shortname); + git_str_dispose(&longpath); + + return supported; +} +#endif + diff --git a/tests/libgit2/clar_libgit2.h b/tests/libgit2/clar_libgit2.h new file mode 100644 index 000000000..e3b7bd9f8 --- /dev/null +++ b/tests/libgit2/clar_libgit2.h @@ -0,0 +1,236 @@ +#ifndef __CLAR_LIBGIT2__ +#define __CLAR_LIBGIT2__ + +#include "clar.h" +#include +#include "common.h" +#include "posix.h" + +/** + * Replace for `clar_must_pass` that passes the last library error as the + * test failure message. + * + * Use this wrapper around all `git_` library calls that return error codes! + */ +#define cl_git_pass(expr) cl_git_expect((expr), 0, __FILE__, __func__, __LINE__) + +#define cl_git_fail_with(error, expr) cl_git_expect((expr), error, __FILE__, __func__, __LINE__) + +#define cl_git_expect(expr, expected, file, func, line) do { \ + int _lg2_error; \ + git_error_clear(); \ + if ((_lg2_error = (expr)) != expected) \ + cl_git_report_failure(_lg2_error, expected, file, func, line, "Function call failed: " #expr); \ + } while (0) + +/** + * Wrapper for `clar_must_fail` -- this one is + * just for consistency. Use with `git_` library + * calls that are supposed to fail! + */ +#define cl_git_fail(expr) do { \ + if ((expr) == 0) \ + git_error_clear(), \ + cl_git_report_failure(0, 0, __FILE__, __func__, __LINE__, "Function call succeeded: " #expr); \ + } while (0) + +/** + * Like cl_git_pass, only for Win32 error code conventions + */ +#define cl_win32_pass(expr) do { \ + int _win32_res; \ + if ((_win32_res = (expr)) == 0) { \ + git_error_set(GIT_ERROR_OS, "Returned: %d, system error code: %lu", _win32_res, GetLastError()); \ + cl_git_report_failure(_win32_res, 0, __FILE__, __func__, __LINE__, "System call failed: " #expr); \ + } \ + } while(0) + +/** + * Thread safe assertions; you cannot use `cl_git_report_failure` from a + * child thread since it will try to `longjmp` to abort and "the effect of + * a call to longjmp() where initialization of the jmp_buf structure was + * not performed in the calling thread is undefined." + * + * Instead, callers can provide a clar thread error context to a thread, + * which will populate and return it on failure. Callers can check the + * status with `cl_git_thread_check`. + */ +typedef struct { + int error; + const char *file; + const char *func; + int line; + const char *expr; + char error_msg[4096]; +} cl_git_thread_err; + +#ifdef GIT_THREADS +# define cl_git_thread_pass(threaderr, expr) cl_git_thread_pass_(threaderr, (expr), __FILE__, __func__, __LINE__) +#else +# define cl_git_thread_pass(threaderr, expr) cl_git_pass(expr) +#endif + +#define cl_git_thread_pass_(__threaderr, __expr, __file, __func, __line) do { \ + git_error_clear(); \ + if ((((cl_git_thread_err *)__threaderr)->error = (__expr)) != 0) { \ + const git_error *_last = git_error_last(); \ + ((cl_git_thread_err *)__threaderr)->file = __file; \ + ((cl_git_thread_err *)__threaderr)->func = __func; \ + ((cl_git_thread_err *)__threaderr)->line = __line; \ + ((cl_git_thread_err *)__threaderr)->expr = "Function call failed: " #__expr; \ + p_snprintf(((cl_git_thread_err *)__threaderr)->error_msg, 4096, "thread 0x%" PRIxZ " - error %d - %s", \ + git_thread_currentid(), ((cl_git_thread_err *)__threaderr)->error, \ + _last ? _last->message : ""); \ + git_thread_exit(__threaderr); \ + } \ + } while (0) + +GIT_INLINE(void) cl_git_thread_check(void *data) +{ + cl_git_thread_err *threaderr = (cl_git_thread_err *)data; + if (threaderr->error != 0) + clar__assert(0, threaderr->file, threaderr->func, threaderr->line, threaderr->expr, threaderr->error_msg, 1); +} + +void cl_git_report_failure(int, int, const char *, const char *, int, const char *); + +#define cl_assert_at_line(expr,file,func,line) \ + clar__assert((expr) != 0, file, func, line, "Expression is not true: " #expr, NULL, 1) + +GIT_INLINE(void) clar__assert_in_range( + int lo, int val, int hi, + const char *file, const char *func, int line, + const char *err, int should_abort) +{ + if (lo > val || hi < val) { + char buf[128]; + p_snprintf(buf, sizeof(buf), "%d not in [%d,%d]", val, lo, hi); + clar__fail(file, func, line, err, buf, should_abort); + } +} + +#define cl_assert_equal_sz(sz1,sz2) do { \ + size_t __sz1 = (size_t)(sz1), __sz2 = (size_t)(sz2); \ + clar__assert_equal(__FILE__,__func__,__LINE__,#sz1 " != " #sz2, 1, "%"PRIuZ, __sz1, __sz2); \ +} while (0) + +#define cl_assert_in_range(L,V,H) \ + clar__assert_in_range((L),(V),(H),__FILE__,__func__,__LINE__,"Range check: " #V " in [" #L "," #H "]", 1) + +#define cl_assert_equal_file(DATA,SIZE,PATH) \ + clar__assert_equal_file(DATA,SIZE,0,PATH,__FILE__,__func__,(int)__LINE__) + +#define cl_assert_equal_file_ignore_cr(DATA,SIZE,PATH) \ + clar__assert_equal_file(DATA,SIZE,1,PATH,__FILE__,__func__,(int)__LINE__) + +void clar__assert_equal_file( + const char *expected_data, + size_t expected_size, + int ignore_cr, + const char *path, + const char *file, + const char *func, + int line); + +GIT_INLINE(void) clar__assert_equal_oid( + const char *file, const char *func, int line, const char *desc, + const git_oid *one, const git_oid *two) +{ + if (git_oid_cmp(one, two)) { + char err[] = "\"........................................\" != \"........................................\""; + + git_oid_fmt(&err[1], one); + git_oid_fmt(&err[47], two); + + clar__fail(file, func, line, desc, err, 1); + } +} + +#define cl_assert_equal_oid(one, two) \ + clar__assert_equal_oid(__FILE__, __func__, __LINE__, \ + "OID mismatch: " #one " != " #two, (one), (two)) + +/* + * Some utility macros for building long strings + */ +#define REP4(STR) STR STR STR STR +#define REP15(STR) REP4(STR) REP4(STR) REP4(STR) STR STR STR +#define REP16(STR) REP4(REP4(STR)) +#define REP256(STR) REP16(REP16(STR)) +#define REP1024(STR) REP4(REP256(STR)) + +/* Write the contents of a buffer to disk */ +void cl_git_mkfile(const char *filename, const char *content); +void cl_git_append2file(const char *filename, const char *new_content); +void cl_git_rewritefile(const char *filename, const char *new_content); +void cl_git_write2file(const char *path, const char *data, + size_t datalen, int flags, unsigned int mode); +void cl_git_rmfile(const char *filename); + +bool cl_toggle_filemode(const char *filename); +bool cl_is_chmod_supported(void); + +/* Environment wrappers */ +char *cl_getenv(const char *name); +bool cl_is_env_set(const char *name); +int cl_setenv(const char *name, const char *value); + +/* Reliable rename */ +int cl_rename(const char *source, const char *dest); + +/* Git sandbox setup helpers */ + +git_repository *cl_git_sandbox_init(const char *sandbox); +git_repository *cl_git_sandbox_init_new(const char *name); +void cl_git_sandbox_cleanup(void); +git_repository *cl_git_sandbox_reopen(void); + +/* + * build a sandbox-relative from path segments + * is_dir will add a trailing slash + * vararg must be a NULL-terminated char * list + */ +const char *cl_git_sandbox_path(int is_dir, ...); + +/* Local-repo url helpers */ +const char* cl_git_fixture_url(const char *fixturename); +const char* cl_git_path_url(const char *path); + +/* Test repository cleaner */ +int cl_git_remove_placeholders(const char *directory_path, const char *filename); + +/* commit creation helpers */ +void cl_repo_commit_from_index( + git_oid *out, + git_repository *repo, + git_signature *sig, + git_time_t time, + const char *msg); + +/* config setting helpers */ +void cl_repo_set_bool(git_repository *repo, const char *cfg, int value); +int cl_repo_get_bool(git_repository *repo, const char *cfg); + +void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value); + +/* set up a fake "home" directory and set libgit2 GLOBAL search path. + * + * automatically configures cleanup function to restore the regular search + * path, although you can call it explicitly if you wish (with NULL). + */ +void cl_fake_home(void); +void cl_fake_home_cleanup(void *); + +void cl_sandbox_set_search_path_defaults(void); + +#ifdef GIT_WIN32 +# define cl_msleep(x) Sleep(x) +#else +# define cl_msleep(x) usleep(1000 * (x)) +#endif + +#ifdef GIT_WIN32 +bool cl_sandbox_supports_8dot3(void); +#endif + +#endif diff --git a/tests/libgit2/clar_libgit2_timer.c b/tests/libgit2/clar_libgit2_timer.c new file mode 100644 index 000000000..2330f9351 --- /dev/null +++ b/tests/libgit2/clar_libgit2_timer.c @@ -0,0 +1,30 @@ +#include "clar_libgit2.h" +#include "clar_libgit2_timer.h" + +void cl_perf_timer__init(cl_perf_timer *t) +{ + memset(t, 0, sizeof(cl_perf_timer)); +} + +void cl_perf_timer__start(cl_perf_timer *t) +{ + t->time_started = git__timer(); +} + +void cl_perf_timer__stop(cl_perf_timer *t) +{ + double time_now = git__timer(); + + t->last = time_now - t->time_started; + t->sum += t->last; +} + +double cl_perf_timer__last(const cl_perf_timer *t) +{ + return t->last; +} + +double cl_perf_timer__sum(const cl_perf_timer *t) +{ + return t->sum; +} diff --git a/tests/libgit2/clar_libgit2_timer.h b/tests/libgit2/clar_libgit2_timer.h new file mode 100644 index 000000000..7571a52e9 --- /dev/null +++ b/tests/libgit2/clar_libgit2_timer.h @@ -0,0 +1,35 @@ +#ifndef __CLAR_LIBGIT2_TIMER__ +#define __CLAR_LIBGIT2_TIMER__ + +struct cl_perf_timer +{ + /* cumulative running time across all start..stop intervals */ + double sum; + + /* value of last start..stop interval */ + double last; + + /* clock value at start */ + double time_started; +}; + +#define CL_PERF_TIMER_INIT {0} + +typedef struct cl_perf_timer cl_perf_timer; + +void cl_perf_timer__init(cl_perf_timer *t); +void cl_perf_timer__start(cl_perf_timer *t); +void cl_perf_timer__stop(cl_perf_timer *t); + +/** + * return value of last start..stop interval in seconds. + */ +double cl_perf_timer__last(const cl_perf_timer *t); + +/** + * return cumulative running time across all start..stop + * intervals in seconds. + */ +double cl_perf_timer__sum(const cl_perf_timer *t); + +#endif /* __CLAR_LIBGIT2_TIMER__ */ diff --git a/tests/libgit2/clar_libgit2_trace.c b/tests/libgit2/clar_libgit2_trace.c new file mode 100644 index 000000000..ebb0f41dd --- /dev/null +++ b/tests/libgit2/clar_libgit2_trace.c @@ -0,0 +1,263 @@ +#include "clar_libgit2_trace.h" +#include "clar_libgit2.h" +#include "clar_libgit2_timer.h" +#include "trace.h" + +struct method { + const char *name; + void (*git_trace_cb)(git_trace_level_t level, const char *msg); + void (*close)(void); +}; + +static const char *message_prefix(git_trace_level_t level) +{ + switch (level) { + case GIT_TRACE_NONE: + return "[NONE]: "; + case GIT_TRACE_FATAL: + return "[FATAL]: "; + case GIT_TRACE_ERROR: + return "[ERROR]: "; + case GIT_TRACE_WARN: + return "[WARN]: "; + case GIT_TRACE_INFO: + return "[INFO]: "; + case GIT_TRACE_DEBUG: + return "[DEBUG]: "; + case GIT_TRACE_TRACE: + return "[TRACE]: "; + default: + return "[?????]: "; + } +} + +static void _git_trace_cb__printf(git_trace_level_t level, const char *msg) +{ + printf("%s%s\n", message_prefix(level), msg); +} + +#if defined(GIT_WIN32) +static void _git_trace_cb__debug(git_trace_level_t level, const char *msg) +{ + OutputDebugString(message_prefix(level)); + OutputDebugString(msg); + OutputDebugString("\n"); + + printf("%s%s\n", message_prefix(level), msg); +} +#else +#define _git_trace_cb__debug _git_trace_cb__printf +#endif + + +static void _trace_printf_close(void) +{ + fflush(stdout); +} + +#define _trace_debug_close _trace_printf_close + + +static struct method s_methods[] = { + { "printf", _git_trace_cb__printf, _trace_printf_close }, + { "debug", _git_trace_cb__debug, _trace_debug_close }, + /* TODO add file method */ + {0}, +}; + + +static int s_trace_loaded = 0; +static int s_trace_level = GIT_TRACE_NONE; +static struct method *s_trace_method = NULL; +static int s_trace_tests = 0; + +static int set_method(const char *name) +{ + int k; + + if (!name || !*name) + name = "printf"; + + for (k=0; (s_methods[k].name); k++) { + if (strcmp(name, s_methods[k].name) == 0) { + s_trace_method = &s_methods[k]; + return 0; + } + } + fprintf(stderr, "Unknown CLAR_TRACE_METHOD: '%s'\n", name); + return -1; +} + + +/** + * Lookup CLAR_TRACE_LEVEL and CLAR_TRACE_METHOD from + * the environment and set the above s_trace_* fields. + * + * If CLAR_TRACE_LEVEL is not set, we disable tracing. + * + * TODO If set, we assume GIT_TRACE_TRACE level, which + * logs everything. Later, we may want to parse the + * value of the environment variable and set a specific + * level. + * + * We assume the "printf" method. This can be changed + * with the CLAR_TRACE_METHOD environment variable. + * Currently, this is only needed on Windows for a "debug" + * version which also writes to the debug output window + * in Visual Studio. + * + * TODO add a "file" method that would open and write + * to a well-known file. This would help keep trace + * output and clar output separate. + * + */ +static void _load_trace_params(void) +{ + char *sz_level; + char *sz_method; + char *sz_tests; + + s_trace_loaded = 1; + + sz_level = cl_getenv("CLAR_TRACE_LEVEL"); + if (!sz_level || !*sz_level) { + s_trace_level = GIT_TRACE_NONE; + s_trace_method = NULL; + return; + } + + /* TODO Parse sz_level and set s_trace_level. */ + s_trace_level = GIT_TRACE_TRACE; + + sz_method = cl_getenv("CLAR_TRACE_METHOD"); + if (set_method(sz_method) < 0) + set_method(NULL); + + sz_tests = cl_getenv("CLAR_TRACE_TESTS"); + if (sz_tests != NULL) + s_trace_tests = 1; +} + +#define HR "================================================================" + +/** + * Timer to report the take spend in a test's run() method. + */ +static cl_perf_timer s_timer_run = CL_PERF_TIMER_INIT; + +/** + * Timer to report total time in a test (init, run, cleanup). + */ +static cl_perf_timer s_timer_test = CL_PERF_TIMER_INIT; + +static void _cl_trace_cb__event_handler( + cl_trace_event ev, + const char *suite_name, + const char *test_name, + void *payload) +{ + GIT_UNUSED(payload); + + if (!s_trace_tests) + return; + + switch (ev) { + case CL_TRACE__SUITE_BEGIN: + git_trace(GIT_TRACE_TRACE, "\n\n%s\n%s: Begin Suite", HR, suite_name); +#if 0 && defined(GIT_WIN32_LEAKCHECK) + git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK, + suite_name); +#endif + break; + + case CL_TRACE__SUITE_END: +#if 0 && defined(GIT_WIN32_LEAKCHECK) + /* As an example of checkpointing, dump leaks within this suite. + * This may generate false positives for things like the global + * TLS error state and maybe the odb cache since they aren't + * freed until the global shutdown and outside the scope of this + * set of tests. + * + * This may under-report if the test itself uses a checkpoint. + * See tests/trace/windows/stacktrace.c + */ + git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK, + suite_name); +#endif + git_trace(GIT_TRACE_TRACE, "\n\n%s: End Suite\n%s", suite_name, HR); + break; + + case CL_TRACE__TEST__BEGIN: + git_trace(GIT_TRACE_TRACE, "\n%s::%s: Begin Test", suite_name, test_name); + cl_perf_timer__init(&s_timer_test); + cl_perf_timer__start(&s_timer_test); + break; + + case CL_TRACE__TEST__END: + cl_perf_timer__stop(&s_timer_test); + git_trace(GIT_TRACE_TRACE, "%s::%s: End Test (%.3f %.3f)", suite_name, test_name, + cl_perf_timer__last(&s_timer_run), + cl_perf_timer__last(&s_timer_test)); + break; + + case CL_TRACE__TEST__RUN_BEGIN: + git_trace(GIT_TRACE_TRACE, "%s::%s: Begin Run", suite_name, test_name); + cl_perf_timer__init(&s_timer_run); + cl_perf_timer__start(&s_timer_run); + break; + + case CL_TRACE__TEST__RUN_END: + cl_perf_timer__stop(&s_timer_run); + git_trace(GIT_TRACE_TRACE, "%s::%s: End Run", suite_name, test_name); + break; + + case CL_TRACE__TEST__LONGJMP: + cl_perf_timer__stop(&s_timer_run); + git_trace(GIT_TRACE_TRACE, "%s::%s: Aborted", suite_name, test_name); + break; + + default: + break; + } +} + +/** + * Setup/Enable git_trace() based upon settings user's environment. + */ +void cl_global_trace_register(void) +{ + if (!s_trace_loaded) + _load_trace_params(); + + if (s_trace_level == GIT_TRACE_NONE) + return; + if (s_trace_method == NULL) + return; + if (s_trace_method->git_trace_cb == NULL) + return; + + git_trace_set(s_trace_level, s_trace_method->git_trace_cb); + cl_trace_register(_cl_trace_cb__event_handler, NULL); +} + +/** + * If we turned on git_trace() earlier, turn it off. + * + * This is intended to let us close/flush any buffered + * IO if necessary. + * + */ +void cl_global_trace_disable(void) +{ + cl_trace_register(NULL, NULL); + git_trace_set(GIT_TRACE_NONE, NULL); + if (s_trace_method && s_trace_method->close) + s_trace_method->close(); + + /* Leave s_trace_ vars set so they can restart tracing + * since we only want to hit the environment variables + * once. + */ +} diff --git a/tests/libgit2/clar_libgit2_trace.h b/tests/libgit2/clar_libgit2_trace.h new file mode 100644 index 000000000..09d1e050f --- /dev/null +++ b/tests/libgit2/clar_libgit2_trace.h @@ -0,0 +1,7 @@ +#ifndef __CLAR_LIBGIT2_TRACE__ +#define __CLAR_LIBGIT2_TRACE__ + +void cl_global_trace_register(void); +void cl_global_trace_disable(void); + +#endif diff --git a/tests/libgit2/clone/empty.c b/tests/libgit2/clone/empty.c new file mode 100644 index 000000000..94847bc73 --- /dev/null +++ b/tests/libgit2/clone/empty.c @@ -0,0 +1,102 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" +#include "repository.h" +#include "repo/repo_helpers.h" + +static git_clone_options g_options; +static git_repository *g_repo; +static git_repository *g_repo_cloned; + +void test_clone_empty__initialize(void) +{ + git_repository *sandbox = cl_git_sandbox_init("empty_bare.git"); + git_fetch_options dummy_options = GIT_FETCH_OPTIONS_INIT; + cl_git_remove_placeholders(git_repository_path(sandbox), "dummy-marker.txt"); + + g_repo = NULL; + + memset(&g_options, 0, sizeof(git_clone_options)); + g_options.version = GIT_CLONE_OPTIONS_VERSION; + g_options.fetch_opts = dummy_options; +} + +void test_clone_empty__cleanup(void) +{ + cl_fixture_cleanup("tmp_global_path"); + cl_git_sandbox_cleanup(); +} + +static void cleanup_repository(void *path) +{ + cl_fixture_cleanup((const char *)path); + + git_repository_free(g_repo_cloned); + g_repo_cloned = NULL; +} + +void test_clone_empty__can_clone_an_empty_local_repo_barely(void) +{ + char *local_name = "refs/heads/master"; + const char *expected_tracked_branch_name = "refs/remotes/origin/master"; + const char *expected_remote_name = "origin"; + git_buf buf = GIT_BUF_INIT; + git_reference *ref; + + cl_set_cleanup(&cleanup_repository, "./empty"); + + g_options.bare = true; + cl_git_pass(git_clone(&g_repo_cloned, "./empty_bare.git", "./empty", &g_options)); + + /* Although the HEAD is unborn... */ + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo_cloned, local_name)); + + /* ...one can still retrieve the name of the remote tracking reference */ + cl_git_pass(git_branch_upstream_name(&buf, g_repo_cloned, local_name)); + + cl_assert_equal_s(expected_tracked_branch_name, buf.ptr); + git_buf_dispose(&buf); + + /* ...and the name of the remote... */ + cl_git_pass(git_branch_remote_name(&buf, g_repo_cloned, expected_tracked_branch_name)); + + cl_assert_equal_s(expected_remote_name, buf.ptr); + git_buf_dispose(&buf); + + /* ...even when the remote HEAD is unborn as well */ + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo_cloned, + expected_tracked_branch_name)); +} + +void test_clone_empty__respects_initialbranch_config(void) +{ + git_buf buf = GIT_BUF_INIT; + + create_tmp_global_config("tmp_global_path", "init.defaultbranch", "my_default_branch"); + + cl_set_cleanup(&cleanup_repository, "./empty"); + + g_options.bare = true; + cl_git_pass(git_clone(&g_repo_cloned, "./empty_bare.git", "./empty", &g_options)); + cl_git_pass(git_branch_upstream_name(&buf, g_repo_cloned, "refs/heads/my_default_branch")); + cl_assert_equal_s("refs/remotes/origin/my_default_branch", buf.ptr); + git_buf_dispose(&buf); +} + +void test_clone_empty__can_clone_an_empty_local_repo(void) +{ + cl_set_cleanup(&cleanup_repository, "./empty"); + + cl_git_pass(git_clone(&g_repo_cloned, "./empty_bare.git", "./empty", &g_options)); +} + +void test_clone_empty__can_clone_an_empty_standard_repo(void) +{ + cl_git_sandbox_cleanup(); + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_remove_placeholders(git_repository_path(g_repo), "dummy-marker.txt"); + + cl_set_cleanup(&cleanup_repository, "./empty"); + + cl_git_pass(git_clone(&g_repo_cloned, "./empty_standard_repo", "./empty", &g_options)); +} diff --git a/tests/libgit2/clone/local.c b/tests/libgit2/clone/local.c new file mode 100644 index 000000000..e0bd74df7 --- /dev/null +++ b/tests/libgit2/clone/local.c @@ -0,0 +1,212 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" +#include "clone.h" +#include "path.h" +#include "posix.h" +#include "futils.h" + +static int file_url(git_str *buf, const char *host, const char *path) +{ + if (path[0] == '/') + path++; + + git_str_clear(buf); + return git_str_printf(buf, "file://%s/%s", host, path); +} + +#ifdef GIT_WIN32 +static int git_style_unc_path(git_str *buf, const char *host, const char *path) +{ + git_str_clear(buf); + + if (host) + git_str_printf(buf, "//%s/", host); + + if (path[0] == '/') + path++; + + if (git__isalpha(path[0]) && path[1] == ':' && path[2] == '/') { + git_str_printf(buf, "%c$/", path[0]); + path += 3; + } + + git_str_puts(buf, path); + + return git_str_oom(buf) ? -1 : 0; +} + +static int unc_path(git_str *buf, const char *host, const char *path) +{ + char *c; + + if (git_style_unc_path(buf, host, path) < 0) + return -1; + + for (c = buf->ptr; *c; c++) + if (*c == '/') + *c = '\\'; + + return 0; +} +#endif + +void test_clone_local__should_clone_local(void) +{ + git_str buf = GIT_STR_INIT; + + /* we use a fixture path because it needs to exist for us to want to clone */ + const char *path = cl_fixture("testrepo.git"); + + cl_git_pass(file_url(&buf, "", path)); + cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); + cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); + cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); + + cl_git_pass(file_url(&buf, "localhost", path)); + cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); + cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); + cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); + + cl_git_pass(file_url(&buf, "other-host.mycompany.com", path)); + cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); + cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); + cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); + + /* Ensure that file:/// urls are percent decoded: .git == %2e%67%69%74 */ + cl_git_pass(file_url(&buf, "", path)); + git_str_shorten(&buf, 4); + cl_git_pass(git_str_puts(&buf, "%2e%67%69%74")); + cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); + cl_assert_equal_i(1, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); + cl_assert_equal_i(0, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); + + cl_assert_equal_i(1, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(1, git_clone__should_clone_local(path, GIT_CLONE_LOCAL)); + cl_assert_equal_i(1, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_NO_LINKS)); + cl_assert_equal_i(0, git_clone__should_clone_local(path, GIT_CLONE_NO_LOCAL)); + + git_str_dispose(&buf); +} + +void test_clone_local__hardlinks(void) +{ + git_repository *repo; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + git_str buf = GIT_STR_INIT; + struct stat st; + + /* + * In this first clone, we just copy over, since the temp dir + * will often be in a different filesystem, so we cannot + * link. It also allows us to control the number of links + */ + opts.bare = true; + opts.local = GIT_CLONE_LOCAL_NO_LINKS; + cl_git_pass(git_clone(&repo, cl_fixture("testrepo.git"), "./clone.git", &opts)); + git_repository_free(repo); + + /* This second clone is in the same filesystem, so we can hardlink */ + + opts.local = GIT_CLONE_LOCAL; + cl_git_pass(git_clone(&repo, cl_git_path_url("clone.git"), "./clone2.git", &opts)); + +#ifndef GIT_WIN32 + git_str_clear(&buf); + cl_git_pass(git_str_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); + + cl_git_pass(p_stat(buf.ptr, &st)); + cl_assert_equal_i(2, st.st_nlink); +#endif + + git_repository_free(repo); + git_str_clear(&buf); + + opts.local = GIT_CLONE_LOCAL_NO_LINKS; + cl_git_pass(git_clone(&repo, cl_git_path_url("clone.git"), "./clone3.git", &opts)); + + git_str_clear(&buf); + cl_git_pass(git_str_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); + + cl_git_pass(p_stat(buf.ptr, &st)); + cl_assert_equal_i(1, st.st_nlink); + + git_repository_free(repo); + + /* this one should automatically use links */ + cl_git_pass(git_clone(&repo, "./clone.git", "./clone4.git", NULL)); + +#ifndef GIT_WIN32 + git_str_clear(&buf); + cl_git_pass(git_str_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); + + cl_git_pass(p_stat(buf.ptr, &st)); + cl_assert_equal_i(3, st.st_nlink); +#endif + + git_str_dispose(&buf); + git_repository_free(repo); + + cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("./clone2.git", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("./clone3.git", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("./clone4.git", NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_clone_local__standard_unc_paths_are_written_git_style(void) +{ +#ifdef GIT_WIN32 + git_repository *repo; + git_remote *remote; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + git_str unc = GIT_STR_INIT, git_unc = GIT_STR_INIT; + + /* we use a fixture path because it needs to exist for us to want to clone */ + const char *path = cl_fixture("testrepo.git"); + + cl_git_pass(unc_path(&unc, "localhost", path)); + cl_git_pass(git_style_unc_path(&git_unc, "localhost", path)); + + cl_git_pass(git_clone(&repo, unc.ptr, "./clone.git", &opts)); + cl_git_pass(git_remote_lookup(&remote, repo, "origin")); + + cl_assert_equal_s(git_unc.ptr, git_remote_url(remote)); + + git_remote_free(remote); + git_repository_free(repo); + git_str_dispose(&unc); + git_str_dispose(&git_unc); + + cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); +#endif +} + +void test_clone_local__git_style_unc_paths(void) +{ +#ifdef GIT_WIN32 + git_repository *repo; + git_remote *remote; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + git_str git_unc = GIT_STR_INIT; + + /* we use a fixture path because it needs to exist for us to want to clone */ + const char *path = cl_fixture("testrepo.git"); + + cl_git_pass(git_style_unc_path(&git_unc, "localhost", path)); + + cl_git_pass(git_clone(&repo, git_unc.ptr, "./clone.git", &opts)); + cl_git_pass(git_remote_lookup(&remote, repo, "origin")); + + cl_assert_equal_s(git_unc.ptr, git_remote_url(remote)); + + git_remote_free(remote); + git_repository_free(repo); + git_str_dispose(&git_unc); + + cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); +#endif +} diff --git a/tests/libgit2/clone/nonetwork.c b/tests/libgit2/clone/nonetwork.c new file mode 100644 index 000000000..eab633635 --- /dev/null +++ b/tests/libgit2/clone/nonetwork.c @@ -0,0 +1,361 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" +#include "../submodule/submodule_helpers.h" +#include "remote.h" +#include "futils.h" +#include "repository.h" + +#define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository" + +static git_clone_options g_options; +static git_repository *g_repo; +static git_reference* g_ref; +static git_remote* g_remote; + +void test_clone_nonetwork__initialize(void) +{ + git_checkout_options dummy_opts = GIT_CHECKOUT_OPTIONS_INIT; + git_fetch_options dummy_fetch = GIT_FETCH_OPTIONS_INIT; + + g_repo = NULL; + + memset(&g_options, 0, sizeof(git_clone_options)); + g_options.version = GIT_CLONE_OPTIONS_VERSION; + g_options.checkout_opts = dummy_opts; + g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + g_options.fetch_opts = dummy_fetch; +} + +void test_clone_nonetwork__cleanup(void) +{ + if (g_repo) { + git_repository_free(g_repo); + g_repo = NULL; + } + + if (g_ref) { + git_reference_free(g_ref); + g_ref = NULL; + } + + if (g_remote) { + git_remote_free(g_remote); + g_remote = NULL; + } + + cl_fixture_cleanup("./foo"); +} + +void test_clone_nonetwork__bad_urls(void) +{ + /* Clone should clean up the mess if the URL isn't a git repository */ + cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options)); + cl_assert(!git_fs_path_exists("./foo")); + g_options.bare = true; + cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options)); + cl_assert(!git_fs_path_exists("./foo")); + + cl_git_fail(git_clone(&g_repo, "git://example.com:asdf", "./foo", &g_options)); + cl_git_fail(git_clone(&g_repo, "https://example.com:asdf/foo", "./foo", &g_options)); + cl_git_fail(git_clone(&g_repo, "git://github.com/git://github.com/foo/bar.git.git", + "./foo", &g_options)); + cl_git_fail(git_clone(&g_repo, "arrbee:my/bad:password@github.com:1111/strange:words.git", + "./foo", &g_options)); +} + +void test_clone_nonetwork__do_not_clean_existing_directory(void) +{ + /* Clone should not remove the directory if it already exists, but + * Should clean up entries it creates. */ + p_mkdir("./foo", GIT_DIR_MODE); + cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options)); + cl_assert(git_fs_path_is_empty_dir("./foo")); + + /* Try again with a bare repository. */ + g_options.bare = true; + cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options)); + cl_assert(git_fs_path_is_empty_dir("./foo")); +} + +void test_clone_nonetwork__local(void) +{ + cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); +} + +void test_clone_nonetwork__local_absolute_path(void) +{ + const char *local_src; + local_src = cl_fixture("testrepo.git"); + cl_git_pass(git_clone(&g_repo, local_src, "./foo", &g_options)); +} + +void test_clone_nonetwork__local_bare(void) +{ + g_options.bare = true; + cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); +} + +void test_clone_nonetwork__fail_when_the_target_is_a_file(void) +{ + cl_git_mkfile("./foo", "Bar!"); + cl_git_fail(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); +} + +void test_clone_nonetwork__fail_with_already_existing_but_non_empty_directory(void) +{ + p_mkdir("./foo", GIT_DIR_MODE); + cl_git_mkfile("./foo/bar", "Baz!"); + cl_git_fail(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); +} + +static int custom_origin_name_remote_create( + git_remote **out, + git_repository *repo, + const char *name, + const char *url, + void *payload) +{ + GIT_UNUSED(name); + GIT_UNUSED(payload); + + return git_remote_create(out, repo, "my_origin", url); +} + +void test_clone_nonetwork__custom_origin_name(void) +{ + g_options.remote_cb = custom_origin_name_remote_create; + cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); + + cl_git_pass(git_remote_lookup(&g_remote, g_repo, "my_origin")); +} + +void test_clone_nonetwork__defaults(void) +{ + cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", NULL)); + cl_assert(g_repo); + cl_git_pass(git_remote_lookup(&g_remote, g_repo, "origin")); +} + +void test_clone_nonetwork__cope_with_already_existing_directory(void) +{ + p_mkdir("./foo", GIT_DIR_MODE); + cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); +} + +void test_clone_nonetwork__can_prevent_the_checkout_of_a_standard_repo(void) +{ + git_str path = GIT_STR_INIT; + + g_options.checkout_opts.checkout_strategy = 0; + cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "master.txt")); + cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&path))); + + git_str_dispose(&path); +} + +void test_clone_nonetwork__can_checkout_given_branch(void) +{ + git_reference *remote_head; + + g_options.checkout_branch = "test"; + cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); + + cl_assert_equal_i(0, git_repository_head_unborn(g_repo)); + + cl_git_pass(git_repository_head(&g_ref, g_repo)); + cl_assert_equal_s(git_reference_name(g_ref), "refs/heads/test"); + + cl_assert(git_fs_path_exists("foo/readme.txt")); + + cl_git_pass(git_reference_lookup(&remote_head, g_repo, "refs/remotes/origin/HEAD")); + cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(remote_head)); + cl_assert_equal_s("refs/remotes/origin/master", git_reference_symbolic_target(remote_head)); + + git_reference_free(remote_head); +} + +static int clone_cancel_fetch_transfer_progress_cb( + const git_indexer_progress *stats, void *data) +{ + GIT_UNUSED(stats); GIT_UNUSED(data); + return -54321; +} + +void test_clone_nonetwork__can_cancel_clone_in_fetch(void) +{ + g_options.checkout_branch = "test"; + + g_options.fetch_opts.callbacks.transfer_progress = + clone_cancel_fetch_transfer_progress_cb; + + cl_git_fail_with(git_clone( + &g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options), + -54321); + + cl_assert(!g_repo); + cl_assert(!git_fs_path_exists("foo/readme.txt")); +} + +static int clone_cancel_checkout_cb( + git_checkout_notify_t why, + const char *path, + const git_diff_file *b, + const git_diff_file *t, + const git_diff_file *w, + void *payload) +{ + const char *at_file = payload; + GIT_UNUSED(why); GIT_UNUSED(b); GIT_UNUSED(t); GIT_UNUSED(w); + if (!strcmp(path, at_file)) + return -12345; + return 0; +} + +void test_clone_nonetwork__can_cancel_clone_in_checkout(void) +{ + g_options.checkout_branch = "test"; + + g_options.checkout_opts.notify_flags = GIT_CHECKOUT_NOTIFY_UPDATED; + g_options.checkout_opts.notify_cb = clone_cancel_checkout_cb; + g_options.checkout_opts.notify_payload = "readme.txt"; + + cl_git_fail_with(git_clone( + &g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options), + -12345); + + cl_assert(!g_repo); + cl_assert(!git_fs_path_exists("foo/readme.txt")); +} + +void test_clone_nonetwork__can_detached_head(void) +{ + git_object *obj; + git_repository *cloned; + git_reference *cloned_head; + + cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); + + cl_git_pass(git_revparse_single(&obj, g_repo, "master~1")); + cl_git_pass(git_repository_set_head_detached(g_repo, git_object_id(obj))); + + cl_git_pass(git_clone(&cloned, "./foo", "./foo1", &g_options)); + + cl_assert(git_repository_head_detached(cloned)); + + cl_git_pass(git_repository_head(&cloned_head, cloned)); + cl_assert_equal_oid(git_object_id(obj), git_reference_target(cloned_head)); + + git_object_free(obj); + git_reference_free(cloned_head); + git_repository_free(cloned); + + cl_fixture_cleanup("./foo1"); +} + +void test_clone_nonetwork__clone_tag_to_tree(void) +{ + git_repository *stage; + git_index_entry entry; + git_index *index; + git_odb *odb; + git_oid tree_id; + git_tree *tree; + git_reference *tag; + git_tree_entry *tentry; + const char *file_path = "some/deep/path.txt"; + const char *file_content = "some content\n"; + const char *tag_name = "refs/tags/tree-tag"; + + stage = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_odb(&odb, stage)); + cl_git_pass(git_index_new(&index)); + + memset(&entry, 0, sizeof(git_index_entry)); + entry.path = file_path; + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_odb_write(&entry.id, odb, file_content, strlen(file_content), GIT_OBJECT_BLOB)); + + cl_git_pass(git_index_add(index, &entry)); + cl_git_pass(git_index_write_tree_to(&tree_id, index, stage)); + cl_git_pass(git_reference_create(&tag, stage, tag_name, &tree_id, 0, NULL)); + git_reference_free(tag); + git_odb_free(odb); + git_index_free(index); + + g_options.local = GIT_CLONE_NO_LOCAL; + cl_git_pass(git_clone(&g_repo, cl_git_path_url(git_repository_path(stage)), "./foo", &g_options)); + git_repository_free(stage); + + cl_git_pass(git_reference_lookup(&tag, g_repo, tag_name)); + cl_git_pass(git_tree_lookup(&tree, g_repo, git_reference_target(tag))); + git_reference_free(tag); + + cl_git_pass(git_tree_entry_bypath(&tentry, tree, file_path)); + git_tree_entry_free(tentry); + git_tree_free(tree); + + cl_fixture_cleanup("testrepo.git"); +} + +static void assert_correct_reflog(const char *name) +{ + git_reflog *log; + const git_reflog_entry *entry; + git_str expected_message = GIT_STR_INIT; + + git_str_printf(&expected_message, + "clone: from %s", cl_git_fixture_url("testrepo.git")); + + cl_git_pass(git_reflog_read(&log, g_repo, name)); + cl_assert_equal_i(1, git_reflog_entrycount(log)); + entry = git_reflog_entry_byindex(log, 0); + cl_assert_equal_s(expected_message.ptr, git_reflog_entry_message(entry)); + + git_reflog_free(log); + + git_str_dispose(&expected_message); +} + +void test_clone_nonetwork__clone_updates_reflog_properly(void) +{ + cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); + assert_correct_reflog("HEAD"); + assert_correct_reflog("refs/heads/master"); +} + +static void cleanup_repository(void *path) +{ + if (g_repo) { + git_repository_free(g_repo); + g_repo = NULL; + } + + cl_fixture_cleanup((const char *)path); +} + +void test_clone_nonetwork__clone_from_empty_sets_upstream(void) +{ + git_config *config; + git_repository *repo; + const char *str; + + /* Create an empty repo to clone from */ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + cl_set_cleanup(&cleanup_repository, "./repowithunborn"); + cl_git_pass(git_clone(&repo, "./test1", "./repowithunborn", NULL)); + + cl_git_pass(git_repository_config_snapshot(&config, repo)); + + cl_git_pass(git_config_get_string(&str, config, "branch.master.remote")); + cl_assert_equal_s("origin", str); + cl_git_pass(git_config_get_string(&str, config, "branch.master.merge")); + cl_assert_equal_s("refs/heads/master", str); + + git_config_free(config); + git_repository_free(repo); + cl_fixture_cleanup("./repowithunborn"); +} diff --git a/tests/libgit2/clone/transport.c b/tests/libgit2/clone/transport.c new file mode 100644 index 000000000..fa4f65357 --- /dev/null +++ b/tests/libgit2/clone/transport.c @@ -0,0 +1,51 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" +#include "git2/transport.h" +#include "git2/sys/transport.h" +#include "futils.h" + +static int custom_transport( + git_transport **out, + git_remote *owner, + void *payload) +{ + *((int*)payload) = 1; + + return git_transport_local(out, owner, payload); +} + +static int custom_transport_remote_create( + git_remote **out, + git_repository *repo, + const char *name, + const char *url, + void *payload) +{ + int error; + + GIT_UNUSED(payload); + + if ((error = git_remote_create(out, repo, name, url)) < 0) + return error; + + return 0; +} + +void test_clone_transport__custom_transport(void) +{ + git_repository *repo; + git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; + int custom_transport_used = 0; + + clone_opts.remote_cb = custom_transport_remote_create; + clone_opts.fetch_opts.callbacks.transport = custom_transport; + clone_opts.fetch_opts.callbacks.payload = &custom_transport_used; + + cl_git_pass(git_clone(&repo, cl_fixture("testrepo.git"), "./custom_transport.git", &clone_opts)); + git_repository_free(repo); + + cl_git_pass(git_futils_rmdir_r("./custom_transport.git", NULL, GIT_RMDIR_REMOVE_FILES)); + + cl_assert(custom_transport_used == 1); +} diff --git a/tests/libgit2/commit/commit.c b/tests/libgit2/commit/commit.c new file mode 100644 index 000000000..fd574f7f2 --- /dev/null +++ b/tests/libgit2/commit/commit.c @@ -0,0 +1,189 @@ +#include "clar_libgit2.h" +#include "commit.h" +#include "git2/commit.h" + +static git_repository *_repo; + +void test_commit_commit__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&_repo, "testrepo.git")); +} + +void test_commit_commit__cleanup(void) +{ + git_repository_free(_repo); + _repo = NULL; + + cl_fixture_cleanup("testrepo.git"); +} + +void test_commit_commit__create_unexisting_update_ref(void) +{ + git_oid oid; + git_tree *tree; + git_commit *commit; + git_signature *s; + git_reference *ref; + + git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); + + git_oid_fromstr(&oid, "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); + cl_git_pass(git_tree_lookup(&tree, _repo, &oid)); + + cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); + + cl_git_fail(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); + cl_git_pass(git_commit_create(&oid, _repo, "refs/heads/foo/bar", s, s, + NULL, "some msg", tree, 1, (const git_commit **) &commit)); + + /* fail because the parent isn't the tip of the branch anymore */ + cl_git_fail(git_commit_create(&oid, _repo, "refs/heads/foo/bar", s, s, + NULL, "some msg", tree, 1, (const git_commit **) &commit)); + + cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); + cl_assert_equal_oid(&oid, git_reference_target(ref)); + + git_tree_free(tree); + git_commit_free(commit); + git_signature_free(s); + git_reference_free(ref); +} + +void test_commit_commit__create_initial_commit(void) +{ + git_oid oid; + git_tree *tree; + git_commit *commit; + git_signature *s; + git_reference *ref; + + git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); + + git_oid_fromstr(&oid, "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); + cl_git_pass(git_tree_lookup(&tree, _repo, &oid)); + + cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); + + cl_git_fail(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); + cl_git_pass(git_commit_create(&oid, _repo, "refs/heads/foo/bar", s, s, + NULL, "initial commit", tree, 0, NULL)); + + cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); + + cl_assert_equal_oid(&oid, git_reference_target(ref)); + + git_tree_free(tree); + git_commit_free(commit); + git_signature_free(s); + git_reference_free(ref); +} + +void test_commit_commit__create_initial_commit_parent_not_current(void) +{ + git_oid oid; + git_oid original_oid; + git_tree *tree; + git_commit *commit; + git_signature *s; + + git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); + + git_oid_fromstr(&oid, "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); + cl_git_pass(git_tree_lookup(&tree, _repo, &oid)); + + cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); + + cl_git_pass(git_reference_name_to_id(&original_oid, _repo, "HEAD")); + + cl_git_fail(git_commit_create(&oid, _repo, "HEAD", s, s, + NULL, "initial commit", tree, 0, NULL)); + + cl_git_pass(git_reference_name_to_id(&oid, _repo, "HEAD")); + + cl_assert_equal_oid(&oid, &original_oid); + + git_tree_free(tree); + git_commit_free(commit); + git_signature_free(s); +} + +static void assert_commit_summary(const char *expected, const char *given) +{ + git_commit *dummy; + + cl_assert(dummy = git__calloc(1, sizeof(struct git_commit))); + + dummy->raw_message = git__strdup(given); + cl_assert_equal_s(expected, git_commit_summary(dummy)); + + git_commit__free(dummy); +} + +static void assert_commit_body(const char *expected, const char *given) +{ + git_commit *dummy; + + cl_assert(dummy = git__calloc(1, sizeof(struct git_commit))); + + dummy->raw_message = git__strdup(given); + cl_assert_equal_s(expected, git_commit_body(dummy)); + + git_commit__free(dummy); +} + +void test_commit_commit__summary(void) +{ + assert_commit_summary("One-liner with no trailing newline", "One-liner with no trailing newline"); + assert_commit_summary("One-liner with trailing newline", "One-liner with trailing newline\n"); + assert_commit_summary("One-liner with trailing newline and space", "One-liner with trailing newline and space\n "); + assert_commit_summary("Trimmed leading&trailing newlines", "\n\nTrimmed leading&trailing newlines\n\n"); + assert_commit_summary("First paragraph only", "\nFirst paragraph only\n\n(There are more!)"); + assert_commit_summary("First paragraph only with space in the next line", "\nFirst paragraph only with space in the next line\n \n(There are more!)"); + assert_commit_summary("First paragraph only with spaces in the next line", "\nFirst paragraph only with spaces in the next line\n \n(There are more!)"); + assert_commit_summary("First paragraph with unwrapped trailing\tlines", "\nFirst paragraph\nwith unwrapped\ntrailing\tlines\n\n(Yes, unwrapped!)"); + assert_commit_summary("\tLeading tabs", "\tLeading\n\ttabs\n\nare preserved"); /* tabs around newlines are collapsed down to a single space */ + assert_commit_summary(" Leading Spaces", " Leading\n Spaces\n\nare preserved"); /* spaces around newlines are collapsed down to a single space */ + assert_commit_summary("Trailing tabs\tare removed", "Trailing tabs\tare removed\t\t"); + assert_commit_summary("Trailing spaces are removed", "Trailing spaces are removed "); + assert_commit_summary("Trailing tabs", "Trailing tabs\t\n\nare removed"); + assert_commit_summary("Trailing spaces", "Trailing spaces \n\nare removed"); + assert_commit_summary("Newlines are replaced by spaces", "Newlines\nare\nreplaced by spaces\n"); + assert_commit_summary(" Spaces after newlines are collapsed", "\n Spaces after newlines\n are\n collapsed\n "); /* newlines at the very beginning are ignored and not collapsed */ + assert_commit_summary(" Spaces before newlines are collapsed", " \nSpaces before newlines \nare \ncollapsed \n"); + assert_commit_summary(" Spaces around newlines are collapsed", " \n Spaces around newlines \n are \n collapsed \n "); + assert_commit_summary(" Trailing newlines are" , " \n Trailing newlines \n are \n\n collapsed \n "); + assert_commit_summary(" Trailing spaces are stripped", " \n Trailing spaces \n are stripped \n\n \n \t "); + assert_commit_summary("", ""); + assert_commit_summary("", " "); + assert_commit_summary("", "\n"); + assert_commit_summary("", "\n \n"); +} + +void test_commit_commit__body(void) +{ + assert_commit_body(NULL, "One-liner with no trailing newline"); + assert_commit_body(NULL, "One-liner with trailing newline\n"); + assert_commit_body(NULL, "\n\nTrimmed leading&trailing newlines\n\n"); + assert_commit_body("(There are more!)", "\nFirst paragraph only\n\n(There are more!)"); + assert_commit_body("(Yes, unwrapped!)", "\nFirst paragraph\nwith unwrapped\ntrailing\tlines\n\n(Yes, unwrapped!)"); + assert_commit_body("are preserved", "\tLeading\n\ttabs\n\nare preserved"); /* tabs around newlines are collapsed down to a single space */ + assert_commit_body("are preserved", " Leading\n Spaces\n\nare preserved"); /* spaces around newlines are collapsed down to a single space */ + assert_commit_body(NULL, "Trailing tabs\tare removed\t\t"); + assert_commit_body(NULL, "Trailing spaces are removed "); + assert_commit_body("are removed", "Trailing tabs\t\n\nare removed"); + assert_commit_body("are removed", "Trailing spaces \n\nare removed"); + assert_commit_body(NULL,"Newlines\nare\nreplaced by spaces\n"); + assert_commit_body(NULL , "\n Spaces after newlines\n are\n collapsed\n "); /* newlines at the very beginning are ignored and not collapsed */ + assert_commit_body(NULL , " \nSpaces before newlines \nare \ncollapsed \n"); + assert_commit_body(NULL , " \n Spaces around newlines \n are \n collapsed \n "); + assert_commit_body("collapsed" , " \n Trailing newlines \n are \n\n collapsed \n "); + assert_commit_body(NULL, " \n Trailing spaces \n are stripped \n\n \n \t "); + assert_commit_body(NULL , ""); + assert_commit_body(NULL , " "); + assert_commit_body(NULL , "\n"); + assert_commit_body(NULL , "\n \n"); +} diff --git a/tests/libgit2/commit/parent.c b/tests/libgit2/commit/parent.c new file mode 100644 index 000000000..18ce0bba6 --- /dev/null +++ b/tests/libgit2/commit/parent.c @@ -0,0 +1,60 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; +static git_commit *commit; + +void test_commit_parent__initialize(void) +{ + git_oid oid; + + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + + git_oid_fromstr(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); +} + +void test_commit_parent__cleanup(void) +{ + git_commit_free(commit); + commit = NULL; + + git_repository_free(_repo); + _repo = NULL; +} + +static void assert_nth_gen_parent(unsigned int gen, const char *expected_oid) +{ + git_commit *parent = NULL; + int error; + + error = git_commit_nth_gen_ancestor(&parent, commit, gen); + + if (expected_oid != NULL) { + cl_assert_equal_i(0, error); + cl_assert_equal_i(0, git_oid_streq(git_commit_id(parent), expected_oid)); + } else + cl_assert_equal_i(GIT_ENOTFOUND, error); + + git_commit_free(parent); +} + +/* + * $ git show be35~0 + * commit be3563ae3f795b2b4353bcce3a527ad0a4f7f644 + * + * $ git show be35~1 + * commit 9fd738e8f7967c078dceed8190330fc8648ee56a + * + * $ git show be35~3 + * commit 5b5b025afb0b4c913b4c338a42934a3863bf3644 + * + * $ git show be35~42 + * fatal: ambiguous argument 'be35~42': unknown revision or path not in the working tree. + */ +void test_commit_parent__can_retrieve_nth_generation_parent(void) +{ + assert_nth_gen_parent(0, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + assert_nth_gen_parent(1, "9fd738e8f7967c078dceed8190330fc8648ee56a"); + assert_nth_gen_parent(3, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + assert_nth_gen_parent(42, NULL); +} diff --git a/tests/libgit2/commit/parse.c b/tests/libgit2/commit/parse.c new file mode 100644 index 000000000..04366d7d2 --- /dev/null +++ b/tests/libgit2/commit/parse.c @@ -0,0 +1,551 @@ +#include "clar_libgit2.h" +#include +#include "commit.h" +#include "signature.h" + +/* Fixture setup */ +static git_repository *g_repo; +void test_commit_parse__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} +void test_commit_parse__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + +/* Header parsing */ +typedef struct { + const char *line; + const char *header; +} parse_test_case; + +static parse_test_case passing_header_cases[] = { + { "parent 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "parent " }, + { "tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " }, + { "random_heading 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "random_heading " }, + { "stuck_heading05452d6349abcd67aa396dfb28660d765d8b2a36\n", "stuck_heading" }, + { "tree 5F4BEFFC0759261D015AA63A3A85613FF2F235DE\n", "tree " }, + { "tree 1A669B8AB81B5EB7D9DB69562D34952A38A9B504\n", "tree " }, + { "tree 5B20DCC6110FCC75D31C6CEDEBD7F43ECA65B503\n", "tree " }, + { "tree 173E7BF00EA5C33447E99E6C1255954A13026BE4\n", "tree " }, + { NULL, NULL } +}; + +static parse_test_case failing_header_cases[] = { + { "parent 05452d6349abcd67aa396dfb28660d765d8b2a36", "parent " }, + { "05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " }, + { "parent05452d6349abcd67aa396dfb28660d765d8b2a6a\n", "parent " }, + { "parent 05452d6349abcd67aa396dfb280d765d8b2a6\n", "parent " }, + { "tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " }, + { "parent 0545xd6349abcd67aa396dfb28660d765d8b2a36\n", "parent " }, + { "parent 0545xd6349abcd67aa396dfb28660d765d8b2a36FF\n", "parent " }, + { "", "tree " }, + { "", "" }, + { NULL, NULL } +}; + +void test_commit_parse__header(void) +{ + git_oid oid; + + parse_test_case *testcase; + for (testcase = passing_header_cases; testcase->line != NULL; testcase++) + { + const char *line = testcase->line; + const char *line_end = line + strlen(line); + + cl_git_pass(git_oid__parse(&oid, &line, line_end, testcase->header)); + cl_assert(line == line_end); + } + + for (testcase = failing_header_cases; testcase->line != NULL; testcase++) + { + const char *line = testcase->line; + const char *line_end = line + strlen(line); + + cl_git_fail(git_oid__parse(&oid, &line, line_end, testcase->header)); + } +} + + +/* Signature parsing */ +typedef struct { + const char *string; + const char *header; + const char *name; + const char *email; + git_time_t time; + int offset; +} passing_signature_test_case; + +passing_signature_test_case passing_signature_cases[] = { + {"author Vicent Marti 12345 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 12345, 0}, + {"author Vicent Marti <> 12345 \n", "author ", "Vicent Marti", "", 12345, 0}, + {"author Vicent Marti 231301 +1020\n", "author ", "Vicent Marti", "tanoku@gmail.com", 231301, 620}, + {"author Vicent Marti with an outrageously long name which will probably overflow the buffer 12345 \n", "author ", "Vicent Marti with an outrageously long name which will probably overflow the buffer", "tanoku@gmail.com", 12345, 0}, + {"author Vicent Marti 12345 \n", "author ", "Vicent Marti", "tanokuwithaveryveryverylongemailwhichwillprobablyvoverflowtheemailbuffer@gmail.com", 12345, 0}, + {"committer Vicent Marti 123456 +0000 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 0}, + {"committer Vicent Marti 123456 +0100 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 60}, + {"committer Vicent Marti 123456 -0100 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, -60}, + /* Parse a signature without an author field */ + {"committer 123456 -0100 \n", "committer ", "", "tanoku@gmail.com", 123456, -60}, + /* Parse a signature without an author field */ + {"committer 123456 -0100 \n", "committer ", "", "tanoku@gmail.com", 123456, -60}, + /* Parse a signature with an empty author field */ + {"committer 123456 -0100 \n", "committer ", "", "tanoku@gmail.com", 123456, -60}, + /* Parse a signature with an empty email field */ + {"committer Vicent Marti <> 123456 -0100 \n", "committer ", "Vicent Marti", "", 123456, -60}, + /* Parse a signature with an empty email field */ + {"committer Vicent Marti < > 123456 -0100 \n", "committer ", "Vicent Marti", "", 123456, -60}, + /* Parse a signature with empty name and email */ + {"committer <> 123456 -0100 \n", "committer ", "", "", 123456, -60}, + /* Parse a signature with empty name and email */ + {"committer <> 123456 -0100 \n", "committer ", "", "", 123456, -60}, + /* Parse a signature with empty name and email */ + {"committer < > 123456 -0100 \n", "committer ", "", "", 123456, -60}, + /* Parse an obviously invalid signature */ + {"committer foo<@bar> 123456 -0100 \n", "committer ", "foo", "@bar", 123456, -60}, + /* Parse an obviously invalid signature */ + {"committer foo<@bar> 123456 -0100 \n", "committer ", "foo", "@bar", 123456, -60}, + /* Parse an obviously invalid signature */ + {"committer <>\n", "committer ", "", "", 0, 0}, + {"committer Vicent Marti 123456 -1500 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 0}, + {"committer Vicent Marti 123456 +0163 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 0}, + {"author Vicent Marti \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0, 0}, + /* a variety of dates */ + {"author Vicent Marti 0 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0, 0}, + {"author Vicent Marti 1234567890 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 1234567890, 0}, + {"author Vicent Marti 2147483647 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0x7fffffff, 0}, + {"author Vicent Marti 4294967295 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0xffffffff, 0}, + {"author Vicent Marti 4294967296 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 4294967296, 0}, + {"author Vicent Marti 8589934592 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 8589934592, 0}, + + {NULL,NULL,NULL,NULL,0,0} +}; + +typedef struct { + const char *string; + const char *header; +} failing_signature_test_case; + +failing_signature_test_case failing_signature_cases[] = { + {"committer Vicent Marti tanoku@gmail.com> 123456 -0100 \n", "committer "}, + {"author Vicent Marti 12345 \n", "author "}, + {"author Vicent Marti 12345 \n", "committer "}, + {"author Vicent Marti 12345 \n", "author "}, + {"author Vicent Marti <\n", "committer "}, + {"author ", "author "}, + {NULL, NULL,} +}; + +void test_commit_parse__signature(void) +{ + passing_signature_test_case *passcase; + failing_signature_test_case *failcase; + + for (passcase = passing_signature_cases; passcase->string != NULL; passcase++) + { + const char *str = passcase->string; + size_t len = strlen(passcase->string); + struct git_signature person = {0}; + + cl_git_pass(git_signature__parse(&person, &str, str + len, passcase->header, '\n')); + cl_assert_equal_s(passcase->name, person.name); + cl_assert_equal_s(passcase->email, person.email); + cl_assert_equal_i((int)passcase->time, (int)person.when.time); + cl_assert_equal_i(passcase->offset, person.when.offset); + git__free(person.name); git__free(person.email); + } + + for (failcase = failing_signature_cases; failcase->string != NULL; failcase++) + { + const char *str = failcase->string; + size_t len = strlen(failcase->string); + git_signature person = {0}; + cl_git_fail(git_signature__parse(&person, &str, str + len, failcase->header, '\n')); + git__free(person.name); git__free(person.email); + } +} + + + +static char *failing_commit_cases[] = { +/* empty commit */ +"", +/* random garbage */ +"asd97sa9du902e9a0jdsuusad09as9du098709aweu8987sd\n", +/* broken endlines 1 */ +"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\r\n\ +parent 05452d6349abcd67aa396dfb28660d765d8b2a36\r\n\ +author Vicent Marti 1273848544 +0200\r\n\ +committer Vicent Marti 1273848544 +0200\r\n\ +\r\n\ +a test commit with broken endlines\r\n", +/* broken endlines 2 */ +"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\ +parent 05452d6349abcd67aa396dfb28660d765d8b2a36\ +author Vicent Marti 1273848544 +0200\ +committer Vicent Marti 1273848544 +0200\ +\ +another test commit with broken endlines", +/* starting endlines */ +"\ntree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ +parent 05452d6349abcd67aa396dfb28660d765d8b2a36\n\ +author Vicent Marti 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n\ +a test commit with a starting endline\n", +/* corrupted commit 1 */ +"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ +parent 05452d6349abcd67aa396df", +/* corrupted commit 2 */ +"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ +parent ", +/* corrupted commit 3 */ +"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ +parent ", +/* corrupted commit 4 */ +"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\ +par", +}; + + +static char *passing_commit_cases[] = { +/* simple commit with no message */ +"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +author Vicent Marti 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n", +/* simple commit, no parent */ +"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +author Vicent Marti 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n\ +a simple commit which works\n", +/* simple commit, no parent, no newline in message */ +"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +author Vicent Marti 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n\ +a simple commit which works", +/* simple commit, 1 parent */ +"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +parent e90810b8df3e80c413d903f631643c716887138d\n\ +author Vicent Marti 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n\ +a simple commit which works\n", +/* simple commit with GPG signature */ +"tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ +parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +gpgsig -----BEGIN PGP SIGNATURE-----\n\ + Version: GnuPG v1.4.12 (Darwin)\n\ + \n\ + iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ + o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ + JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ + AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ + SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ + who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ + 6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ + cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ + c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ + ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ + 7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ + cpxtDQQMGYFpXK/71stq\n\ + =ozeK\n\ + -----END PGP SIGNATURE-----\n\ +\n\ +a simple commit which works\n", +/* some tools create two author entries */ +"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +author Vicent Marti 1273848544 +0200\n\ +author Helpful Coworker 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n\ +a simple commit which works", +}; + +static int parse_commit(git_commit **out, const char *buffer) +{ + git_commit *commit; + git_odb_object fake_odb_object; + int error; + + commit = (git_commit*)git__malloc(sizeof(git_commit)); + memset(commit, 0x0, sizeof(git_commit)); + commit->object.repo = g_repo; + + memset(&fake_odb_object, 0x0, sizeof(git_odb_object)); + fake_odb_object.buffer = (char *)buffer; + fake_odb_object.cached.size = strlen(fake_odb_object.buffer); + + error = git_commit__parse(commit, &fake_odb_object); + + *out = commit; + return error; +} + +void test_commit_parse__entire_commit(void) +{ + const int failing_commit_count = ARRAY_SIZE(failing_commit_cases); + const int passing_commit_count = ARRAY_SIZE(passing_commit_cases); + int i; + git_commit *commit; + + for (i = 0; i < failing_commit_count; ++i) { + cl_git_fail(parse_commit(&commit, failing_commit_cases[i])); + git_commit__free(commit); + } + + for (i = 0; i < passing_commit_count; ++i) { + cl_git_pass(parse_commit(&commit, passing_commit_cases[i])); + + if (!i) + cl_assert_equal_s("", git_commit_message(commit)); + else + cl_assert(git__prefixcmp( + git_commit_message(commit), "a simple commit which works") == 0); + + git_commit__free(commit); + } +} + + +/* query the details on a parsed commit */ +void test_commit_parse__details0(void) { + static const char *commit_ids[] = { + "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ + "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */ + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */ + "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ + "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */ + "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */ + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", /* 6 */ + }; + const size_t commit_count = sizeof(commit_ids) / sizeof(const char *); + unsigned int i; + + for (i = 0; i < commit_count; ++i) { + git_oid id; + git_commit *commit; + + const git_signature *author, *committer; + const char *message; + git_time_t commit_time; + unsigned int parents, p; + git_commit *parent = NULL, *old_parent = NULL; + + git_oid_fromstr(&id, commit_ids[i]); + + cl_git_pass(git_commit_lookup(&commit, g_repo, &id)); + + message = git_commit_message(commit); + author = git_commit_author(commit); + committer = git_commit_committer(commit); + commit_time = git_commit_time(commit); + parents = git_commit_parentcount(commit); + + cl_assert_equal_s("Scott Chacon", author->name); + cl_assert_equal_s("schacon@gmail.com", author->email); + cl_assert_equal_s("Scott Chacon", committer->name); + cl_assert_equal_s("schacon@gmail.com", committer->email); + cl_assert(message != NULL); + cl_assert(commit_time > 0); + cl_assert(parents <= 2); + for (p = 0;p < parents;p++) { + if (old_parent != NULL) + git_commit_free(old_parent); + + old_parent = parent; + cl_git_pass(git_commit_parent(&parent, commit, p)); + cl_assert(parent != NULL); + cl_assert(git_commit_author(parent) != NULL); /* is it really a commit? */ + } + git_commit_free(old_parent); + git_commit_free(parent); + + cl_git_fail(git_commit_parent(&parent, commit, parents)); + git_commit_free(commit); + } +} + +void test_commit_parse__leading_lf(void) +{ + git_commit *commit; + const char *buffer = +"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +parent e90810b8df3e80c413d903f631643c716887138d\n\ +author Vicent Marti 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n\ +\n\ +\n\ +This commit has a few LF at the start of the commit message"; + const char *message = +"This commit has a few LF at the start of the commit message"; + const char *raw_message = +"\n\ +\n\ +This commit has a few LF at the start of the commit message"; + cl_git_pass(parse_commit(&commit, buffer)); + cl_assert_equal_s(message, git_commit_message(commit)); + cl_assert_equal_s(raw_message, git_commit_message_raw(commit)); + git_commit__free(commit); +} + +void test_commit_parse__only_lf(void) +{ + git_commit *commit; + const char *buffer = +"tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +parent e90810b8df3e80c413d903f631643c716887138d\n\ +author Vicent Marti 1273848544 +0200\n\ +committer Vicent Marti 1273848544 +0200\n\ +\n\ +\n\ +\n"; + const char *message = ""; + const char *raw_message = "\n\n"; + + cl_git_pass(parse_commit(&commit, buffer)); + cl_assert_equal_s(message, git_commit_message(commit)); + cl_assert_equal_s(raw_message, git_commit_message_raw(commit)); + git_commit__free(commit); +} + +void test_commit_parse__arbitrary_field(void) +{ + git_commit *commit; + git_buf buf = GIT_BUF_INIT; + const char *gpgsig = "-----BEGIN PGP SIGNATURE-----\n\ +Version: GnuPG v1.4.12 (Darwin)\n\ +\n\ +iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ +o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ +JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ +AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ +SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ +who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ +6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ +cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ +c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ +ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ +7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ +cpxtDQQMGYFpXK/71stq\n\ +=ozeK\n\ +-----END PGP SIGNATURE-----"; + + cl_git_pass(parse_commit(&commit, passing_commit_cases[4])); + + cl_git_pass(git_commit_header_field(&buf, commit, "tree")); + cl_assert_equal_s("6b79e22d69bf46e289df0345a14ca059dfc9bdf6", buf.ptr); + git_buf_dispose(&buf); + + cl_git_pass(git_commit_header_field(&buf, commit, "parent")); + cl_assert_equal_s("34734e478d6cf50c27c9d69026d93974d052c454", buf.ptr); + git_buf_dispose(&buf); + + cl_git_pass(git_commit_header_field(&buf, commit, "gpgsig")); + cl_assert_equal_s(gpgsig, buf.ptr); + git_buf_dispose(&buf); + + cl_git_fail_with(GIT_ENOTFOUND, git_commit_header_field(&buf, commit, "awesomeness")); + cl_git_fail_with(GIT_ENOTFOUND, git_commit_header_field(&buf, commit, "par")); + + git_commit__free(commit); + cl_git_pass(parse_commit(&commit, passing_commit_cases[0])); + + cl_git_pass(git_commit_header_field(&buf, commit, "committer")); + cl_assert_equal_s("Vicent Marti 1273848544 +0200", buf.ptr); + + git_buf_dispose(&buf); + git_commit__free(commit); +} + +void test_commit_parse__extract_signature(void) +{ + git_odb *odb; + git_oid commit_id; + git_buf signature = GIT_BUF_INIT, signed_data = GIT_BUF_INIT; + const char *gpgsig = "-----BEGIN PGP SIGNATURE-----\n\ +Version: GnuPG v1.4.12 (Darwin)\n\ +\n\ +iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ +o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ +JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ +AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ +SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ +who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ +6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ +cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ +c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ +ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ +7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ +cpxtDQQMGYFpXK/71stq\n\ +=ozeK\n\ +-----END PGP SIGNATURE-----"; + + const char *data = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ +parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +\n\ +a simple commit which works\n"; + + const char *oneline_signature = "tree 51832e6397b30309c8bcad9c55fa6ae67778f378\n\ +parent a1b6decaaac768b5e01e1b5dbf5b2cc081bed1eb\n\ +author Some User 1454537944 -0700\n\ +committer Some User 1454537944 -0700\n\ +gpgsig bad\n\ +\n\ +corrupt signature\n"; + + const char *oneline_data = "tree 51832e6397b30309c8bcad9c55fa6ae67778f378\n\ +parent a1b6decaaac768b5e01e1b5dbf5b2cc081bed1eb\n\ +author Some User 1454537944 -0700\n\ +committer Some User 1454537944 -0700\n\ +\n\ +corrupt signature\n"; + + cl_git_pass(git_repository_odb__weakptr(&odb, g_repo)); + cl_git_pass(git_odb_write(&commit_id, odb, passing_commit_cases[4], strlen(passing_commit_cases[4]), GIT_OBJECT_COMMIT)); + + cl_git_pass(git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, NULL)); + cl_assert_equal_s(gpgsig, signature.ptr); + cl_assert_equal_s(data, signed_data.ptr); + + git_buf_dispose(&signature); + git_buf_dispose(&signed_data); + + cl_git_pass(git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, "gpgsig")); + cl_assert_equal_s(gpgsig, signature.ptr); + cl_assert_equal_s(data, signed_data.ptr); + + git_buf_dispose(&signature); + git_buf_dispose(&signed_data); + + /* Try to parse a tree */ + cl_git_pass(git_oid_fromstr(&commit_id, "45dd856fdd4d89b884c340ba0e047752d9b085d6")); + cl_git_fail_with(GIT_ENOTFOUND, git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, NULL)); + cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); + + /* Try to parse an unsigned commit */ + cl_git_pass(git_odb_write(&commit_id, odb, passing_commit_cases[1], strlen(passing_commit_cases[1]), GIT_OBJECT_COMMIT)); + cl_git_fail_with(GIT_ENOTFOUND, git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, NULL)); + cl_assert_equal_i(GIT_ERROR_OBJECT, git_error_last()->klass); + + /* Parse the commit with a single-line signature */ + cl_git_pass(git_odb_write(&commit_id, odb, oneline_signature, strlen(oneline_signature), GIT_OBJECT_COMMIT)); + cl_git_pass(git_commit_extract_signature(&signature, &signed_data, g_repo, &commit_id, NULL)); + cl_assert_equal_s("bad", signature.ptr); + cl_assert_equal_s(oneline_data, signed_data.ptr); + + git_buf_dispose(&signature); + git_buf_dispose(&signed_data); +} diff --git a/tests/libgit2/commit/signature.c b/tests/libgit2/commit/signature.c new file mode 100644 index 000000000..a91551415 --- /dev/null +++ b/tests/libgit2/commit/signature.c @@ -0,0 +1,148 @@ +#include "clar_libgit2.h" +#include "signature.h" + +static int try_build_signature(const char *name, const char *email, git_time_t time, int offset) +{ + git_signature *sign; + int error = 0; + + if ((error = git_signature_new(&sign, name, email, time, offset)) < 0) + return error; + + git_signature_free((git_signature *)sign); + + return error; +} + +static void assert_name_and_email( + const char *expected_name, + const char *expected_email, + const char *name, + const char *email) +{ + git_signature *sign; + + cl_git_pass(git_signature_new(&sign, name, email, 1234567890, 60)); + cl_assert_equal_s(expected_name, sign->name); + cl_assert_equal_s(expected_email, sign->email); + + git_signature_free(sign); +} + +void test_commit_signature__leading_and_trailing_spaces_are_trimmed(void) +{ + assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", " nulltoken ", " emeric.fermas@gmail.com "); + assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", " nulltoken ", " emeric.fermas@gmail.com \n"); + assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", " \t nulltoken \n", " \n emeric.fermas@gmail.com \n"); +} + +void test_commit_signature__leading_and_trailing_crud_is_trimmed(void) +{ + assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", "\"nulltoken\"", "\"emeric.fermas@gmail.com\""); + assert_name_and_email("nulltoken w", "emeric.fermas@gmail.com", "nulltoken w.", "emeric.fermas@gmail.com"); + assert_name_and_email("nulltoken \xe2\x98\xba", "emeric.fermas@gmail.com", "nulltoken \xe2\x98\xba", "emeric.fermas@gmail.com"); +} + +void test_commit_signature__timezone_does_not_read_oob(void) +{ + const char *header = "A 1461698487 +1234", *header_end; + git_signature *sig; + + /* Let the buffer end midway between the timezone offeset's "+12" and "34" */ + header_end = header + strlen(header) - 2; + + sig = git__calloc(1, sizeof(git_signature)); + cl_assert(sig); + + cl_git_pass(git_signature__parse(sig, &header, header_end, NULL, '\0')); + cl_assert_equal_s(sig->name, "A"); + cl_assert_equal_s(sig->email, "a@example.com"); + cl_assert_equal_i(sig->when.time, 1461698487); + cl_assert_equal_i(sig->when.offset, 12); + + git_signature_free(sig); +} + +void test_commit_signature__angle_brackets_in_names_are_not_supported(void) +{ + cl_git_fail(try_build_signature("Haack", "phil@haack", 1234567890, 60)); + cl_git_fail(try_build_signature("", "phil@haack", 1234567890, 60)); +} + +void test_commit_signature__angle_brackets_in_email_are_not_supported(void) +{ + cl_git_fail(try_build_signature("Phil Haack", ">phil@haack", 1234567890, 60)); + cl_git_fail(try_build_signature("Phil Haack", "phil@>haack", 1234567890, 60)); + cl_git_fail(try_build_signature("Phil Haack", "", 1234567890, 60)); +} + +void test_commit_signature__create_empties(void) +{ + /* can not create a signature with empty name or email */ + cl_git_pass(try_build_signature("nulltoken", "emeric.fermas@gmail.com", 1234567890, 60)); + + cl_git_fail(try_build_signature("", "emeric.fermas@gmail.com", 1234567890, 60)); + cl_git_fail(try_build_signature(" ", "emeric.fermas@gmail.com", 1234567890, 60)); + cl_git_fail(try_build_signature("nulltoken", "", 1234567890, 60)); + cl_git_fail(try_build_signature("nulltoken", " ", 1234567890, 60)); +} + +void test_commit_signature__create_one_char(void) +{ + /* creating a one character signature */ + assert_name_and_email("x", "foo@bar.baz", "x", "foo@bar.baz"); +} + +void test_commit_signature__create_two_char(void) +{ + /* creating a two character signature */ + assert_name_and_email("xx", "foo@bar.baz", "xx", "foo@bar.baz"); +} + +void test_commit_signature__create_zero_char(void) +{ + /* creating a zero character signature */ + git_signature *sign; + cl_git_fail(git_signature_new(&sign, "", "x@y.z", 1234567890, 60)); + cl_assert(sign == NULL); +} + +void test_commit_signature__from_buf(void) +{ + git_signature *sign; + + cl_git_pass(git_signature_from_buffer(&sign, "Test User 1461698487 +0200")); + cl_assert_equal_s("Test User", sign->name); + cl_assert_equal_s("test@test.tt", sign->email); + cl_assert_equal_i(1461698487, sign->when.time); + cl_assert_equal_i(120, sign->when.offset); + git_signature_free(sign); +} + +void test_commit_signature__from_buf_with_neg_zero_offset(void) +{ + git_signature *sign; + + cl_git_pass(git_signature_from_buffer(&sign, "Test User 1461698487 -0000")); + cl_assert_equal_s("Test User", sign->name); + cl_assert_equal_s("test@test.tt", sign->email); + cl_assert_equal_i(1461698487, sign->when.time); + cl_assert_equal_i(0, sign->when.offset); + cl_assert_equal_i('-', sign->when.sign); + git_signature_free(sign); +} + +void test_commit_signature__pos_and_neg_zero_offsets_dont_match(void) +{ + git_signature *with_neg_zero; + git_signature *with_pos_zero; + + cl_git_pass(git_signature_from_buffer(&with_neg_zero, "Test User 1461698487 -0000")); + cl_git_pass(git_signature_from_buffer(&with_pos_zero, "Test User 1461698487 +0000")); + + cl_assert(!git_signature__equal(with_neg_zero, with_pos_zero)); + + git_signature_free((git_signature *)with_neg_zero); + git_signature_free((git_signature *)with_pos_zero); +} diff --git a/tests/libgit2/commit/write.c b/tests/libgit2/commit/write.c new file mode 100644 index 000000000..5a9c9d5a5 --- /dev/null +++ b/tests/libgit2/commit/write.c @@ -0,0 +1,424 @@ +#include "clar_libgit2.h" +#include "git2/sys/commit.h" + +static const char *committer_name = "Vicent Marti"; +static const char *committer_email = "vicent@github.com"; +static const char *commit_message = "This commit has been created in memory\n\ + This is a commit created in memory and it will be written back to disk\n"; +static const char *tree_id_str = "1810dff58d8a660512d4832e740f692884338ccd"; +static const char *parent_id_str = "8496071c1b46c854b31185ea97743be6a8774479"; +static const char *root_commit_message = "This is a root commit\n\ + This is a root commit and should be the only one in this branch\n"; +static const char *root_reflog_message = "commit (initial): This is a root commit \ +This is a root commit and should be the only one in this branch"; +static char *head_old; +static git_reference *head, *branch; +static git_commit *commit; + +/* Fixture setup */ +static git_repository *g_repo; +void test_commit_write__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_commit_write__cleanup(void) +{ + git_reference_free(head); + head = NULL; + + git_reference_free(branch); + branch = NULL; + + git_commit_free(commit); + commit = NULL; + + git__free(head_old); + head_old = NULL; + + cl_git_sandbox_cleanup(); + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 1)); +} + + +/* write a new commit object from memory to disk */ +void test_commit_write__from_memory(void) +{ + git_oid tree_id, parent_id, commit_id; + git_signature *author, *committer; + const git_signature *author1, *committer1; + git_commit *parent; + git_tree *tree; + + git_oid_fromstr(&tree_id, tree_id_str); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + git_oid_fromstr(&parent_id, parent_id_str); + cl_git_pass(git_commit_lookup(&parent, g_repo, &parent_id)); + + /* create signatures */ + cl_git_pass(git_signature_new(&committer, committer_name, committer_email, 123456789, 60)); + cl_git_pass(git_signature_new(&author, committer_name, committer_email, 987654321, 90)); + + cl_git_pass(git_commit_create_v( + &commit_id, /* out id */ + g_repo, + NULL, /* do not update the HEAD */ + author, + committer, + NULL, + commit_message, + tree, + 1, parent)); + + git_object_free((git_object *)parent); + git_object_free((git_object *)tree); + + git_signature_free(committer); + git_signature_free(author); + + cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id)); + + /* Check attributes were set correctly */ + author1 = git_commit_author(commit); + cl_assert(author1 != NULL); + cl_assert_equal_s(committer_name, author1->name); + cl_assert_equal_s(committer_email, author1->email); + cl_assert(author1->when.time == 987654321); + cl_assert(author1->when.offset == 90); + + committer1 = git_commit_committer(commit); + cl_assert(committer1 != NULL); + cl_assert_equal_s(committer_name, committer1->name); + cl_assert_equal_s(committer_email, committer1->email); + cl_assert(committer1->when.time == 123456789); + cl_assert(committer1->when.offset == 60); + + cl_assert_equal_s(commit_message, git_commit_message(commit)); +} + +void test_commit_write__into_buf(void) +{ + git_oid tree_id; + git_signature *author, *committer; + git_tree *tree; + git_commit *parent; + git_oid parent_id; + git_buf commit = GIT_BUF_INIT; + + git_oid_fromstr(&tree_id, tree_id_str); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + /* create signatures */ + cl_git_pass(git_signature_new(&committer, committer_name, committer_email, 123456789, 60)); + cl_git_pass(git_signature_new(&author, committer_name, committer_email, 987654321, 90)); + + git_oid_fromstr(&parent_id, parent_id_str); + cl_git_pass(git_commit_lookup(&parent, g_repo, &parent_id)); + + cl_git_pass(git_commit_create_buffer(&commit, g_repo, author, committer, + NULL, root_commit_message, tree, 1, (const git_commit **) &parent)); + + cl_assert_equal_s(commit.ptr, + "tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +parent 8496071c1b46c854b31185ea97743be6a8774479\n\ +author Vicent Marti 987654321 +0130\n\ +committer Vicent Marti 123456789 +0100\n\ +\n\ +This is a root commit\n\ + This is a root commit and should be the only one in this branch\n\ +"); + + git_buf_dispose(&commit); + git_tree_free(tree); + git_commit_free(parent); + git_signature_free(author); + git_signature_free(committer); +} + +/* create a root commit */ +void test_commit_write__root(void) +{ + git_oid tree_id, commit_id; + const git_oid *branch_oid; + git_signature *author, *committer; + const char *branch_name = "refs/heads/root-commit-branch"; + git_tree *tree; + git_reflog *log; + const git_reflog_entry *entry; + + git_oid_fromstr(&tree_id, tree_id_str); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + /* create signatures */ + cl_git_pass(git_signature_new(&committer, committer_name, committer_email, 123456789, 60)); + cl_git_pass(git_signature_new(&author, committer_name, committer_email, 987654321, 90)); + + /* First we need to update HEAD so it points to our non-existent branch */ + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_assert(git_reference_type(head) == GIT_REFERENCE_SYMBOLIC); + head_old = git__strdup(git_reference_symbolic_target(head)); + cl_assert(head_old != NULL); + git_reference_free(head); + + cl_git_pass(git_reference_symbolic_create(&head, g_repo, "HEAD", branch_name, 1, NULL)); + + cl_git_pass(git_commit_create_v( + &commit_id, /* out id */ + g_repo, + "HEAD", + author, + committer, + NULL, + root_commit_message, + tree, + 0)); + + git_object_free((git_object *)tree); + git_signature_free(author); + + /* + * The fact that creating a commit works has already been + * tested. Here we just make sure it's our commit and that it was + * written as a root commit. + */ + cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id)); + cl_assert(git_commit_parentcount(commit) == 0); + cl_git_pass(git_reference_lookup(&branch, g_repo, branch_name)); + branch_oid = git_reference_target(branch); + cl_assert_equal_oid(branch_oid, &commit_id); + cl_assert_equal_s(root_commit_message, git_commit_message(commit)); + + cl_git_pass(git_reflog_read(&log, g_repo, branch_name)); + cl_assert_equal_i(1, git_reflog_entrycount(log)); + entry = git_reflog_entry_byindex(log, 0); + cl_assert_equal_s(committer->email, git_reflog_entry_committer(entry)->email); + cl_assert_equal_s(committer->name, git_reflog_entry_committer(entry)->name); + cl_assert_equal_s(root_reflog_message, git_reflog_entry_message(entry)); + + git_signature_free(committer); + git_reflog_free(log); +} + +static int create_commit_from_ids( + git_oid *result, + const git_oid *tree_id, + const git_oid *parent_id) +{ + git_signature *author, *committer; + const git_oid *parent_ids[1]; + int ret; + + cl_git_pass(git_signature_new( + &committer, committer_name, committer_email, 123456789, 60)); + cl_git_pass(git_signature_new( + &author, committer_name, committer_email, 987654321, 90)); + + parent_ids[0] = parent_id; + + ret = git_commit_create_from_ids( + result, + g_repo, + NULL, + author, + committer, + NULL, + root_commit_message, + tree_id, + 1, + parent_ids); + + git_signature_free(committer); + git_signature_free(author); + + return ret; +} + +void test_commit_write__can_write_invalid_objects(void) +{ + git_oid expected_id, tree_id, parent_id, commit_id; + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 0)); + + /* this is a valid tree and parent */ + git_oid_fromstr(&tree_id, tree_id_str); + git_oid_fromstr(&parent_id, parent_id_str); + + git_oid_fromstr(&expected_id, "c8571bbec3a72c4bcad31648902e5a453f1adece"); + cl_git_pass(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); + cl_assert_equal_oid(&expected_id, &commit_id); + + /* this is a wholly invented tree id */ + git_oid_fromstr(&tree_id, "1234567890123456789012345678901234567890"); + git_oid_fromstr(&parent_id, parent_id_str); + + git_oid_fromstr(&expected_id, "996008340b8e68d69bf3c28d7c57fb7ec3c8e202"); + cl_git_pass(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); + cl_assert_equal_oid(&expected_id, &commit_id); + + /* this is a wholly invented parent id */ + git_oid_fromstr(&tree_id, tree_id_str); + git_oid_fromstr(&parent_id, "1234567890123456789012345678901234567890"); + + git_oid_fromstr(&expected_id, "d78f660cab89d9791ca6714b57978bf2a7e709fd"); + cl_git_pass(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); + cl_assert_equal_oid(&expected_id, &commit_id); + + /* these are legitimate objects, but of the wrong type */ + git_oid_fromstr(&tree_id, parent_id_str); + git_oid_fromstr(&parent_id, tree_id_str); + + git_oid_fromstr(&expected_id, "5d80c07414e3f18792949699dfcacadf7748f361"); + cl_git_pass(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); + cl_assert_equal_oid(&expected_id, &commit_id); +} + +void test_commit_write__can_validate_objects(void) +{ + git_oid tree_id, parent_id, commit_id; + + /* this is a valid tree and parent */ + git_oid_fromstr(&tree_id, tree_id_str); + git_oid_fromstr(&parent_id, parent_id_str); + cl_git_pass(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); + + /* this is a wholly invented tree id */ + git_oid_fromstr(&tree_id, "1234567890123456789012345678901234567890"); + git_oid_fromstr(&parent_id, parent_id_str); + cl_git_fail(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); + + /* this is a wholly invented parent id */ + git_oid_fromstr(&tree_id, tree_id_str); + git_oid_fromstr(&parent_id, "1234567890123456789012345678901234567890"); + cl_git_fail(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); + + /* these are legitimate objects, but of the wrong type */ + git_oid_fromstr(&tree_id, parent_id_str); + git_oid_fromstr(&parent_id, tree_id_str); + cl_git_fail(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); +} + +void test_commit_write__attach_signature_checks_objects(void) +{ + const char *sig = "magic word: pretty please"; + const char *badtree = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ +parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +\n\ +a simple commit which does not work\n"; + + const char *badparent = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ +parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +\n\ +a simple commit which does not work\n"; + + git_oid id; + + cl_git_fail_with(-1, git_commit_create_with_signature(&id, g_repo, badtree, sig, "magicsig")); + cl_git_fail_with(-1, git_commit_create_with_signature(&id, g_repo, badparent, sig, "magicsig")); + +} + +void test_commit_write__attach_singleline_signature(void) +{ + const char *sig = "magic word: pretty please"; + + const char *data = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ +parent 8496071c1b46c854b31185ea97743be6a8774479\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +\n\ +a simple commit which works\n"; + + const char *complete = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ +parent 8496071c1b46c854b31185ea97743be6a8774479\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +magicsig magic word: pretty please\n\ +\n\ +a simple commit which works\n"; + + git_oid id; + git_odb *odb; + git_odb_object *obj; + + cl_git_pass(git_commit_create_with_signature(&id, g_repo, data, sig, "magicsig")); + + cl_git_pass(git_repository_odb(&odb, g_repo)); + cl_git_pass(git_odb_read(&obj, odb, &id)); + cl_assert_equal_s(complete, git_odb_object_data(obj)); + + git_odb_object_free(obj); + git_odb_free(odb); +} + +void test_commit_write__attach_multiline_signature(void) +{ + const char *gpgsig = "-----BEGIN PGP SIGNATURE-----\n\ +Version: GnuPG v1.4.12 (Darwin)\n\ +\n\ +iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ +o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ +JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ +AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ +SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ +who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ +6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ +cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ +c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ +ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ +7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ +cpxtDQQMGYFpXK/71stq\n\ +=ozeK\n\ +-----END PGP SIGNATURE-----"; + + const char *data = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ +parent 8496071c1b46c854b31185ea97743be6a8774479\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +\n\ +a simple commit which works\n"; + +const char *complete = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ +parent 8496071c1b46c854b31185ea97743be6a8774479\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +gpgsig -----BEGIN PGP SIGNATURE-----\n\ + Version: GnuPG v1.4.12 (Darwin)\n\ + \n\ + iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ + o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ + JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ + AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ + SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ + who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ + 6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ + cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ + c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ + ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ + 7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ + cpxtDQQMGYFpXK/71stq\n\ + =ozeK\n\ + -----END PGP SIGNATURE-----\n\ +\n\ +a simple commit which works\n"; + + git_oid one, two; + git_odb *odb; + git_odb_object *obj; + + cl_git_pass(git_commit_create_with_signature(&one, g_repo, data, gpgsig, "gpgsig")); + cl_git_pass(git_commit_create_with_signature(&two, g_repo, data, gpgsig, NULL)); + + cl_assert(!git_oid_cmp(&one, &two)); + cl_git_pass(git_repository_odb(&odb, g_repo)); + cl_git_pass(git_odb_read(&obj, odb, &one)); + cl_assert_equal_s(complete, git_odb_object_data(obj)); + + git_odb_object_free(obj); + git_odb_free(odb); +} diff --git a/tests/libgit2/config/add.c b/tests/libgit2/config/add.c new file mode 100644 index 000000000..405f1e2c9 --- /dev/null +++ b/tests/libgit2/config/add.c @@ -0,0 +1,37 @@ +#include "clar_libgit2.h" + +void test_config_add__initialize(void) +{ + cl_fixture_sandbox("config/config10"); +} + +void test_config_add__cleanup(void) +{ + cl_fixture_cleanup("config10"); +} + +void test_config_add__to_existing_section(void) +{ + git_config *cfg; + int32_t i; + + cl_git_pass(git_config_open_ondisk(&cfg, "config10")); + cl_git_pass(git_config_set_int32(cfg, "empty.tmp", 5)); + cl_git_pass(git_config_get_int32(&i, cfg, "empty.tmp")); + cl_assert(i == 5); + cl_git_pass(git_config_delete_entry(cfg, "empty.tmp")); + git_config_free(cfg); +} + +void test_config_add__to_new_section(void) +{ + git_config *cfg; + int32_t i; + + cl_git_pass(git_config_open_ondisk(&cfg, "config10")); + cl_git_pass(git_config_set_int32(cfg, "section.tmp", 5)); + cl_git_pass(git_config_get_int32(&i, cfg, "section.tmp")); + cl_assert(i == 5); + cl_git_pass(git_config_delete_entry(cfg, "section.tmp")); + git_config_free(cfg); +} diff --git a/tests/libgit2/config/backend.c b/tests/libgit2/config/backend.c new file mode 100644 index 000000000..96feecc4a --- /dev/null +++ b/tests/libgit2/config/backend.c @@ -0,0 +1,24 @@ +#include "clar_libgit2.h" +#include "git2/sys/config.h" + +void test_config_backend__checks_version(void) +{ + git_config *cfg; + git_config_backend backend = GIT_CONFIG_BACKEND_INIT; + const git_error *err; + + backend.version = 1024; + + cl_git_pass(git_config_new(&cfg)); + cl_git_fail(git_config_add_backend(cfg, &backend, 0, NULL, false)); + err = git_error_last(); + cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); + + git_error_clear(); + backend.version = 1024; + cl_git_fail(git_config_add_backend(cfg, &backend, 0, NULL, false)); + err = git_error_last(); + cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); + + git_config_free(cfg); +} diff --git a/tests/libgit2/config/conditionals.c b/tests/libgit2/config/conditionals.c new file mode 100644 index 000000000..564719dcb --- /dev/null +++ b/tests/libgit2/config/conditionals.c @@ -0,0 +1,175 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "repository.h" + +#ifdef GIT_WIN32 +# define ROOT_PREFIX "C:" +#else +# define ROOT_PREFIX +#endif + +static git_repository *_repo; + +void test_config_conditionals__initialize(void) +{ + _repo = cl_git_sandbox_init("empty_standard_repo"); +} + +void test_config_conditionals__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void assert_condition_includes(const char *keyword, const char *path, bool expected) +{ + git_buf value = GIT_BUF_INIT; + git_str buf = GIT_STR_INIT; + git_config *cfg; + + cl_git_pass(git_str_printf(&buf, "[includeIf \"%s:%s\"]\n", keyword, path)); + cl_git_pass(git_str_puts(&buf, "path = other\n")); + + cl_git_mkfile("empty_standard_repo/.git/config", buf.ptr); + cl_git_mkfile("empty_standard_repo/.git/other", "[foo]\nbar=baz\n"); + _repo = cl_git_sandbox_reopen(); + + git_str_dispose(&buf); + + cl_git_pass(git_repository_config(&cfg, _repo)); + + if (expected) { + cl_git_pass(git_config_get_string_buf(&value, cfg, "foo.bar")); + cl_assert_equal_s("baz", value.ptr); + } else { + cl_git_fail_with(GIT_ENOTFOUND, + git_config_get_string_buf(&value, cfg, "foo.bar")); + } + + git_str_dispose(&buf); + git_buf_dispose(&value); + git_config_free(cfg); +} + +static char *sandbox_path(git_str *buf, const char *suffix) +{ + char *path = p_realpath(clar_sandbox_path(), NULL); + cl_assert(path); + cl_git_pass(git_str_attach(buf, path, 0)); + cl_git_pass(git_str_joinpath(buf, buf->ptr, suffix)); + return buf->ptr; +} + +void test_config_conditionals__gitdir(void) +{ + git_str path = GIT_STR_INIT; + + assert_condition_includes("gitdir", ROOT_PREFIX "/", true); + assert_condition_includes("gitdir", "empty_stand", false); + assert_condition_includes("gitdir", "empty_stand/", false); + assert_condition_includes("gitdir", "empty_stand/.git", false); + assert_condition_includes("gitdir", "empty_stand/.git/", false); + assert_condition_includes("gitdir", "empty_stand*/", true); + assert_condition_includes("gitdir", "empty_stand*/.git", true); + assert_condition_includes("gitdir", "empty_stand*/.git/", false); + assert_condition_includes("gitdir", "empty_standard_repo", false); + assert_condition_includes("gitdir", "empty_standard_repo/", true); + assert_condition_includes("gitdir", "empty_standard_repo/.git", true); + assert_condition_includes("gitdir", "empty_standard_repo/.git/", false); + + assert_condition_includes("gitdir", "./", false); + + assert_condition_includes("gitdir", ROOT_PREFIX "/nonexistent", false); + assert_condition_includes("gitdir", ROOT_PREFIX "/empty_standard_repo", false); + assert_condition_includes("gitdir", "~/empty_standard_repo", false); + + assert_condition_includes("gitdir", sandbox_path(&path, "/"), true); + assert_condition_includes("gitdir", sandbox_path(&path, "/*"), false); + assert_condition_includes("gitdir", sandbox_path(&path, "/**"), true); + + assert_condition_includes("gitdir", sandbox_path(&path, "empty_standard_repo"), false); + assert_condition_includes("gitdir", sandbox_path(&path, "empty_standard_repo/"), true); + assert_condition_includes("gitdir", sandbox_path(&path, "empty_standard_repo/"), true); + assert_condition_includes("gitdir", sandbox_path(&path, "Empty_Standard_Repo"), false); + assert_condition_includes("gitdir", sandbox_path(&path, "Empty_Standard_Repo/"), false); + + git_str_dispose(&path); +} + +void test_config_conditionals__gitdir_i(void) +{ + git_str path = GIT_STR_INIT; + + assert_condition_includes("gitdir/i", sandbox_path(&path, "empty_standard_repo/"), true); + assert_condition_includes("gitdir/i", sandbox_path(&path, "EMPTY_STANDARD_REPO/"), true); + + git_str_dispose(&path); +} + +void test_config_conditionals__invalid_conditional_fails(void) +{ + assert_condition_includes("foobar", ".git", false); +} + +static void set_head(git_repository *repo, const char *name) +{ + cl_git_pass(git_repository_create_head(git_repository_path(repo), name)); +} + +void test_config_conditionals__onbranch(void) +{ + assert_condition_includes("onbranch", "master", true); + assert_condition_includes("onbranch", "m*", true); + assert_condition_includes("onbranch", "*", true); + assert_condition_includes("onbranch", "master/", false); + assert_condition_includes("onbranch", "foo", false); + + set_head(_repo, "foo"); + assert_condition_includes("onbranch", "master", false); + assert_condition_includes("onbranch", "foo", true); + assert_condition_includes("onbranch", "f*o", true); + + set_head(_repo, "dir/ref"); + assert_condition_includes("onbranch", "dir/ref", true); + assert_condition_includes("onbranch", "dir/", true); + assert_condition_includes("onbranch", "dir/*", true); + assert_condition_includes("onbranch", "dir/**", true); + assert_condition_includes("onbranch", "**", true); + assert_condition_includes("onbranch", "dir", false); + assert_condition_includes("onbranch", "dir*", false); + + set_head(_repo, "dir/subdir/ref"); + assert_condition_includes("onbranch", "dir/subdir/", true); + assert_condition_includes("onbranch", "dir/subdir/*", true); + assert_condition_includes("onbranch", "dir/subdir/ref", true); + assert_condition_includes("onbranch", "dir/", true); + assert_condition_includes("onbranch", "dir/**", true); + assert_condition_includes("onbranch", "**", true); + assert_condition_includes("onbranch", "dir", false); + assert_condition_includes("onbranch", "dir*", false); + assert_condition_includes("onbranch", "dir/*", false); +} + +void test_config_conditionals__empty(void) +{ + git_buf value = GIT_BUF_INIT; + git_str buf = GIT_STR_INIT; + git_config *cfg; + + cl_git_pass(git_str_puts(&buf, "[includeIf]\n")); + cl_git_pass(git_str_puts(&buf, "path = other\n")); + + cl_git_mkfile("empty_standard_repo/.git/config", buf.ptr); + cl_git_mkfile("empty_standard_repo/.git/other", "[foo]\nbar=baz\n"); + _repo = cl_git_sandbox_reopen(); + + git_str_dispose(&buf); + + cl_git_pass(git_repository_config(&cfg, _repo)); + + cl_git_fail_with(GIT_ENOTFOUND, + git_config_get_string_buf(&value, cfg, "foo.bar")); + + git_str_dispose(&buf); + git_buf_dispose(&value); + git_config_free(cfg); +} diff --git a/tests/libgit2/config/config_helpers.c b/tests/libgit2/config/config_helpers.c new file mode 100644 index 000000000..ecdab5bf6 --- /dev/null +++ b/tests/libgit2/config/config_helpers.c @@ -0,0 +1,67 @@ +#include "clar_libgit2.h" +#include "config_helpers.h" +#include "repository.h" + +void assert_config_entry_existence( + git_repository *repo, + const char *name, + bool is_supposed_to_exist) +{ + git_config *config; + git_config_entry *entry = NULL; + int result; + + cl_git_pass(git_repository_config__weakptr(&config, repo)); + + result = git_config_get_entry(&entry, config, name); + git_config_entry_free(entry); + + if (is_supposed_to_exist) + cl_git_pass(result); + else + cl_assert_equal_i(GIT_ENOTFOUND, result); +} + +void assert_config_entry_value( + git_repository *repo, + const char *name, + const char *expected_value) +{ + git_config *config; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_repository_config__weakptr(&config, repo)); + + cl_git_pass(git_config_get_string_buf(&buf, config, name)); + + cl_assert_equal_s(expected_value, buf.ptr); + git_buf_dispose(&buf); +} + +static int count_config_entries_cb( + const git_config_entry *entry, + void *payload) +{ + int *how_many = (int *)payload; + + GIT_UNUSED(entry); + + (*how_many)++; + + return 0; +} + +int count_config_entries_match(git_repository *repo, const char *pattern) +{ + git_config *config; + int how_many = 0; + + cl_git_pass(git_repository_config(&config, repo)); + + cl_assert_equal_i(0, git_config_foreach_match( + config, pattern, count_config_entries_cb, &how_many)); + + git_config_free(config); + + return how_many; +} diff --git a/tests/libgit2/config/config_helpers.h b/tests/libgit2/config/config_helpers.h new file mode 100644 index 000000000..440645730 --- /dev/null +++ b/tests/libgit2/config/config_helpers.h @@ -0,0 +1,13 @@ +extern void assert_config_entry_existence( + git_repository *repo, + const char *name, + bool is_supposed_to_exist); + +extern void assert_config_entry_value( + git_repository *repo, + const char *name, + const char *expected_value); + +extern int count_config_entries_match( + git_repository *repo, + const char *pattern); diff --git a/tests/libgit2/config/configlevel.c b/tests/libgit2/config/configlevel.c new file mode 100644 index 000000000..8422d32c9 --- /dev/null +++ b/tests/libgit2/config/configlevel.c @@ -0,0 +1,73 @@ +#include "clar_libgit2.h" + +void test_config_configlevel__adding_the_same_level_twice_returns_EEXISTS(void) +{ + int error; + git_config *cfg; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + error = git_config_add_file_ondisk(cfg, cl_fixture("config/config16"), + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0); + + cl_git_fail(error); + cl_assert_equal_i(GIT_EEXISTS, error); + + git_config_free(cfg); +} + +void test_config_configlevel__can_replace_a_config_file_at_an_existing_level(void) +{ + git_config *cfg; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), + GIT_CONFIG_LEVEL_LOCAL, NULL, 1)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), + GIT_CONFIG_LEVEL_LOCAL, NULL, 1)); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.stringglobal")); + cl_assert_equal_s("don't find me!", buf.ptr); + + git_buf_dispose(&buf); + git_config_free(cfg); +} + +void test_config_configlevel__can_read_from_a_single_level_focused_file_after_parent_config_has_been_freed(void) +{ + git_config *cfg; + git_config *single_level_cfg; + git_buf buf = {0}; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); + + cl_git_pass(git_config_open_level(&single_level_cfg, cfg, GIT_CONFIG_LEVEL_LOCAL)); + + git_config_free(cfg); + + cl_git_pass(git_config_get_string_buf(&buf, single_level_cfg, "core.stringglobal")); + cl_assert_equal_s("don't find me!", buf.ptr); + + git_buf_dispose(&buf); + git_config_free(single_level_cfg); +} + +void test_config_configlevel__fetching_a_level_from_an_empty_compound_config_returns_ENOTFOUND(void) +{ + git_config *cfg; + git_config *local_cfg; + + cl_git_pass(git_config_new(&cfg)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_config_open_level(&local_cfg, cfg, GIT_CONFIG_LEVEL_LOCAL)); + + git_config_free(cfg); +} diff --git a/tests/libgit2/config/global.c b/tests/libgit2/config/global.c new file mode 100644 index 000000000..5aba4eec6 --- /dev/null +++ b/tests/libgit2/config/global.c @@ -0,0 +1,172 @@ +#include "clar_libgit2.h" +#include "futils.h" + +void test_config_global__initialize(void) +{ + git_str path = GIT_STR_INIT; + + cl_git_pass(git_futils_mkdir_r("home", 0777)); + cl_git_pass(git_fs_path_prettify(&path, "home", NULL)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); + + cl_git_pass(git_futils_mkdir_r("xdg/git", 0777)); + cl_git_pass(git_fs_path_prettify(&path, "xdg/git", NULL)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); + + cl_git_pass(git_futils_mkdir_r("etc", 0777)); + cl_git_pass(git_fs_path_prettify(&path, "etc", NULL)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); + + git_str_dispose(&path); +} + +void test_config_global__cleanup(void) +{ + cl_sandbox_set_search_path_defaults(); + cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("xdg", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("etc", NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_config_global__open_global(void) +{ + git_config *cfg, *global, *selected, *dummy; + int32_t value; + + cl_git_mkfile("home/.gitconfig", "[global]\n test = 4567\n"); + + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_int32(&value, cfg, "global.test")); + cl_assert_equal_i(4567, value); + + cl_git_pass(git_config_open_level(&global, cfg, GIT_CONFIG_LEVEL_GLOBAL)); + cl_git_pass(git_config_get_int32(&value, global, "global.test")); + cl_assert_equal_i(4567, value); + + cl_git_fail(git_config_open_level(&dummy, cfg, GIT_CONFIG_LEVEL_XDG)); + + cl_git_pass(git_config_open_global(&selected, cfg)); + cl_git_pass(git_config_get_int32(&value, selected, "global.test")); + cl_assert_equal_i(4567, value); + + git_config_free(selected); + git_config_free(global); + git_config_free(cfg); +} + +void test_config_global__open_symlinked_global(void) +{ +#ifndef GIT_WIN32 + git_config *cfg; + int32_t value; + + cl_git_mkfile("home/.gitconfig.linked", "[global]\n test = 4567\n"); + cl_must_pass(symlink(".gitconfig.linked", "home/.gitconfig")); + + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_int32(&value, cfg, "global.test")); + cl_assert_equal_i(4567, value); + + git_config_free(cfg); +#endif +} + +void test_config_global__lock_missing_global_config(void) +{ + git_config *cfg; + git_config_entry *entry; + git_transaction *transaction; + + (void)p_unlink("home/.gitconfig"); /* No global config */ + + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_lock(&transaction, cfg)); + cl_git_pass(git_config_set_string(cfg, "assertion.fail", "boom")); + cl_git_pass(git_transaction_commit(transaction)); + git_transaction_free(transaction); + + /* cfg is updated */ + cl_git_pass(git_config_get_entry(&entry, cfg, "assertion.fail")); + cl_assert_equal_s("boom", entry->value); + + git_config_entry_free(entry); + git_config_free(cfg); + + /* We can reread the new value from the global config */ + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_entry(&entry, cfg, "assertion.fail")); + cl_assert_equal_s("boom", entry->value); + + git_config_entry_free(entry); + git_config_free(cfg); +} + +void test_config_global__open_xdg(void) +{ + git_config *cfg, *xdg, *selected; + const char *str = "teststring"; + const char *key = "this.variable"; + git_buf buf = {0}; + + cl_git_mkfile("xdg/git/config", "# XDG config\n[core]\n test = 1\n"); + + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_open_level(&xdg, cfg, GIT_CONFIG_LEVEL_XDG)); + cl_git_pass(git_config_open_global(&selected, cfg)); + + cl_git_pass(git_config_set_string(xdg, key, str)); + cl_git_pass(git_config_get_string_buf(&buf, selected, key)); + cl_assert_equal_s(str, buf.ptr); + + git_buf_dispose(&buf); + git_config_free(selected); + git_config_free(xdg); + git_config_free(cfg); +} + +void test_config_global__open_programdata(void) +{ + git_config *cfg; + git_repository *repo; + git_buf dir_path = GIT_BUF_INIT; + git_str config_path = GIT_STR_INIT; + git_buf var_contents = GIT_BUF_INIT; + + if (cl_is_env_set("GITTEST_INVASIVE_FS_STRUCTURE")) + cl_skip(); + + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, + GIT_CONFIG_LEVEL_PROGRAMDATA, &dir_path)); + + if (!git_fs_path_isdir(dir_path.ptr)) + cl_git_pass(p_mkdir(dir_path.ptr, 0777)); + + cl_git_pass(git_str_joinpath(&config_path, dir_path.ptr, "config")); + + cl_git_pass(git_config_open_ondisk(&cfg, config_path.ptr)); + cl_git_pass(git_config_set_string(cfg, "programdata.var", "even higher level")); + + git_str_dispose(&config_path); + git_config_free(cfg); + + git_config_open_default(&cfg); + cl_git_pass(git_config_get_string_buf(&var_contents, cfg, "programdata.var")); + cl_assert_equal_s("even higher level", var_contents.ptr); + + git_config_free(cfg); + git_buf_dispose(&var_contents); + + cl_git_pass(git_repository_init(&repo, "./foo.git", true)); + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_get_string_buf(&var_contents, cfg, "programdata.var")); + cl_assert_equal_s("even higher level", var_contents.ptr); + + git_config_free(cfg); + git_buf_dispose(&dir_path); + git_buf_dispose(&var_contents); + git_repository_free(repo); + cl_fixture_cleanup("./foo.git"); +} diff --git a/tests/libgit2/config/include.c b/tests/libgit2/config/include.c new file mode 100644 index 000000000..9328f3cf6 --- /dev/null +++ b/tests/libgit2/config/include.c @@ -0,0 +1,259 @@ +#include "clar_libgit2.h" +#include "futils.h" + +static git_config *cfg; +static git_buf buf; + +void test_config_include__initialize(void) +{ + cfg = NULL; + memset(&buf, 0, sizeof(git_buf)); +} + +void test_config_include__cleanup(void) +{ + git_config_free(cfg); + git_buf_dispose(&buf); +} + +void test_config_include__relative(void) +{ + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config-include"))); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); + cl_assert_equal_s("huzzah", buf.ptr); +} + +void test_config_include__absolute(void) +{ + git_str cfgdata = GIT_STR_INIT; + cl_git_pass(git_str_printf(&cfgdata, "[include]\npath = %s/config-included", cl_fixture("config"))); + + cl_git_mkfile("config-include-absolute", cfgdata.ptr); + git_str_dispose(&cfgdata); + + cl_git_pass(git_config_open_ondisk(&cfg, "config-include-absolute")); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); + cl_assert_equal_s("huzzah", buf.ptr); + + cl_git_pass(p_unlink("config-include-absolute")); +} + +void test_config_include__homedir(void) +{ + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, cl_fixture("config"))); + cl_git_mkfile("config-include-homedir", "[include]\npath = ~/config-included"); + + cl_git_pass(git_config_open_ondisk(&cfg, "config-include-homedir")); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); + cl_assert_equal_s("huzzah", buf.ptr); + + cl_sandbox_set_search_path_defaults(); + + cl_git_pass(p_unlink("config-include-homedir")); +} + +/* We need to pretend that the variables were defined where the file was included */ +void test_config_include__ordering(void) +{ + cl_git_mkfile("included", "[foo \"bar\"]\nbaz = hurrah\nfrotz = hiya"); + cl_git_mkfile("including", + "[foo \"bar\"]\nfrotz = hello\n" + "[include]\npath = included\n" + "[foo \"bar\"]\nbaz = huzzah\n"); + + cl_git_pass(git_config_open_ondisk(&cfg, "including")); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.frotz")); + cl_assert_equal_s("hiya", buf.ptr); + git_buf_dispose(&buf); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar.baz")); + cl_assert_equal_s("huzzah", buf.ptr); + + cl_git_pass(p_unlink("included")); + cl_git_pass(p_unlink("including")); +} + +/* We need to pretend that the variables were defined where the file was included */ +void test_config_include__depth(void) +{ + cl_git_mkfile("a", "[include]\npath = b"); + cl_git_mkfile("b", "[include]\npath = a"); + + cl_git_fail(git_config_open_ondisk(&cfg, "a")); + + cl_git_pass(p_unlink("a")); + cl_git_pass(p_unlink("b")); +} + +void test_config_include__empty_path_sanely_handled(void) +{ + cl_git_mkfile("a", "[include]\npath"); + cl_git_pass(git_config_open_ondisk(&cfg, "a")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "include.path")); + cl_assert_equal_s("", buf.ptr); + + cl_git_pass(p_unlink("a")); +} + +void test_config_include__missing(void) +{ + cl_git_mkfile("including", "[include]\npath = nonexistentfile\n[foo]\nbar = baz"); + + git_error_clear(); + cl_git_pass(git_config_open_ondisk(&cfg, "including")); + cl_assert(git_error_last() == NULL); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); + cl_assert_equal_s("baz", buf.ptr); + + cl_git_pass(p_unlink("including")); +} + +void test_config_include__missing_homedir(void) +{ + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, cl_fixture("config"))); + cl_git_mkfile("including", "[include]\npath = ~/.nonexistentfile\n[foo]\nbar = baz"); + + git_error_clear(); + cl_git_pass(git_config_open_ondisk(&cfg, "including")); + cl_assert(git_error_last() == NULL); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); + cl_assert_equal_s("baz", buf.ptr); + + cl_sandbox_set_search_path_defaults(); + cl_git_pass(p_unlink("including")); +} + +#define replicate10(s) s s s s s s s s s s +void test_config_include__depth2(void) +{ + const char *content = "[include]\n" replicate10(replicate10("path=bottom\n")); + + cl_git_mkfile("top-level", "[include]\npath = middle\n[foo]\nbar = baz"); + cl_git_mkfile("middle", content); + cl_git_mkfile("bottom", "[foo]\nbar2 = baz2"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); + cl_assert_equal_s("baz", buf.ptr); + + git_buf_dispose(&buf); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar2")); + cl_assert_equal_s("baz2", buf.ptr); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("middle")); + cl_git_pass(p_unlink("bottom")); +} + +void test_config_include__removing_include_removes_values(void) +{ + cl_git_mkfile("top-level", "[include]\npath = included"); + cl_git_mkfile("included", "[foo]\nbar = value"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + cl_git_mkfile("top-level", ""); + cl_git_fail(git_config_get_string_buf(&buf, cfg, "foo.bar")); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("included")); +} + +void test_config_include__rewriting_include_refreshes_values(void) +{ + cl_git_mkfile("top-level", "[include]\npath = first\n[include]\npath = second"); + cl_git_mkfile("first", "[first]\nfoo = bar"); + cl_git_mkfile("second", "[second]\nfoo = bar"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + cl_git_mkfile("first", "[first]\nother = value"); + cl_git_fail(git_config_get_string_buf(&buf, cfg, "foo.bar")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "first.other")); + cl_assert_equal_s(buf.ptr, "value"); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("first")); + cl_git_pass(p_unlink("second")); +} + +void test_config_include__rewriting_include_twice_refreshes_values(void) +{ + cl_git_mkfile("top-level", "[include]\npath = included"); + cl_git_mkfile("included", "[foo]\nbar = first-value"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar")); + + git_buf_dispose(&buf); + cl_git_mkfile("included", "[foo]\nother = value2"); + cl_git_fail(git_config_get_string_buf(&buf, cfg, "foo.bar")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.other")); + cl_assert_equal_s(buf.ptr, "value2"); + + git_buf_dispose(&buf); + cl_git_mkfile("included", "[foo]\nanother = bar"); + cl_git_fail(git_config_get_string_buf(&buf, cfg, "foo.other")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.another")); + cl_assert_equal_s(buf.ptr, "bar"); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("included")); +} + +void test_config_include__included_variables_cannot_be_deleted(void) +{ + cl_git_mkfile("top-level", "[include]\npath = included\n"); + cl_git_mkfile("included", "[foo]\nbar = value"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + cl_git_fail(git_config_delete_entry(cfg, "foo.bar")); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("included")); +} + +void test_config_include__included_variables_cannot_be_modified(void) +{ + cl_git_mkfile("top-level", "[include]\npath = included\n"); + + cl_git_mkfile("included", "[foo]\nbar = value"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + cl_git_fail(git_config_set_string(cfg, "foo.bar", "other-value")); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("included")); +} + +void test_config_include__variables_in_included_override_including(void) +{ + int i; + + cl_git_mkfile("top-level", "[foo]\nbar = 1\n[include]\npath = included"); + cl_git_mkfile("included", "[foo]\nbar = 2"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + cl_git_pass(git_config_get_int32(&i, cfg, "foo.bar")); + cl_assert_equal_i(i, 2); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("included")); +} + +void test_config_include__variables_in_including_override_included(void) +{ + int i; + + cl_git_mkfile("top-level", "[include]\npath = included\n[foo]\nbar = 1"); + cl_git_mkfile("included", "[foo]\nbar = 2"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + cl_git_pass(git_config_get_int32(&i, cfg, "foo.bar")); + cl_assert_equal_i(i, 1); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("included")); +} diff --git a/tests/libgit2/config/memory.c b/tests/libgit2/config/memory.c new file mode 100644 index 000000000..ae661899d --- /dev/null +++ b/tests/libgit2/config/memory.c @@ -0,0 +1,139 @@ +#include "clar_libgit2.h" + +#include "config_backend.h" + +static git_config_backend *backend; + +void test_config_memory__initialize(void) +{ + backend = NULL; +} + +void test_config_memory__cleanup(void) +{ + git_config_backend_free(backend); +} + +static void assert_config_contains(git_config_backend *backend, + const char *name, const char *value) +{ + git_config_entry *entry; + cl_git_pass(git_config_backend_get_string(&entry, backend, name)); + cl_assert_equal_s(entry->value, value); +} + +struct expected_entry { + const char *name; + const char *value; + int seen; +}; + +static int contains_all_cb(const git_config_entry *entry, void *payload) +{ + struct expected_entry *entries = (struct expected_entry *) payload; + int i; + + for (i = 0; entries[i].name; i++) { + if (strcmp(entries[i].name, entry->name) || + strcmp(entries[i].value , entry->value)) + continue; + + if (entries[i].seen) + cl_fail("Entry seen more than once"); + entries[i].seen = 1; + return 0; + } + + cl_fail("Unexpected entry"); + return -1; +} + +static void assert_config_contains_all(git_config_backend *backend, + struct expected_entry *entries) +{ + int i; + + cl_git_pass(git_config_backend_foreach(backend, contains_all_cb, entries)); + + for (i = 0; entries[i].name; i++) + cl_assert(entries[i].seen); +} + +static void setup_backend(const char *cfg) +{ + cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg))); + cl_git_pass(git_config_backend_open(backend, 0, NULL)); +} + +void test_config_memory__write_operations_fail(void) +{ + setup_backend(""); + cl_git_fail(git_config_backend_set_string(backend, "general.foo", "var")); + cl_git_fail(git_config_backend_delete(backend, "general.foo")); + cl_git_fail(git_config_backend_lock(backend)); + cl_git_fail(git_config_backend_unlock(backend, 0)); +} + +void test_config_memory__simple(void) +{ + setup_backend( + "[general]\n" + "foo=bar\n"); + + assert_config_contains(backend, "general.foo", "bar"); +} + +void test_config_memory__malformed_fails_to_open(void) +{ + const char *cfg = + "[general\n" + "foo=bar\n"; + cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg))); + cl_git_fail(git_config_backend_open(backend, 0, NULL)); +} + +void test_config_memory__multiple_vars(void) +{ + setup_backend( + "[general]\n" + "foo=bar\n" + "key=value\n"); + assert_config_contains(backend, "general.foo", "bar"); + assert_config_contains(backend, "general.key", "value"); +} + +void test_config_memory__multiple_sections(void) +{ + setup_backend( + "[general]\n" + "foo=bar\n" + "\n" + "[other]\n" + "key=value\n"); + assert_config_contains(backend, "general.foo", "bar"); + assert_config_contains(backend, "other.key", "value"); +} + +void test_config_memory__multivar_gets_correct_string(void) +{ + setup_backend( + "[general]\n" + "foo=bar1\n" + "foo=bar2\n"); + assert_config_contains(backend, "general.foo", "bar2"); +} + +void test_config_memory__foreach_sees_multivar(void) +{ + struct expected_entry entries[] = { + { "general.foo", "bar1", 0 }, + { "general.foo", "bar2", 0 }, + { NULL, NULL, 0 }, + }; + + setup_backend( + "[general]\n" + "foo=bar1\n" + "foo=bar2\n"); + assert_config_contains_all(backend, entries); +} diff --git a/tests/libgit2/config/multivar.c b/tests/libgit2/config/multivar.c new file mode 100644 index 000000000..244e37559 --- /dev/null +++ b/tests/libgit2/config/multivar.c @@ -0,0 +1,288 @@ +#include "clar_libgit2.h" + +static const char *_name = "remote.ab.url"; + +void test_config_multivar__initialize(void) +{ + cl_fixture_sandbox("config"); +} + +void test_config_multivar__cleanup(void) +{ + cl_fixture_cleanup("config"); +} + +static int mv_read_cb(const git_config_entry *entry, void *data) +{ + int *n = (int *) data; + + if (!strcmp(entry->name, _name)) + (*n)++; + + return 0; +} + +void test_config_multivar__foreach(void) +{ + git_config *cfg; + int n = 0; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config11"))); + + cl_git_pass(git_config_foreach(cfg, mv_read_cb, &n)); + cl_assert(n == 2); + + git_config_free(cfg); +} + +static int cb(const git_config_entry *entry, void *data) +{ + int *n = (int *) data; + + GIT_UNUSED(entry); + + (*n)++; + + return 0; +} + +static void check_get_multivar_foreach( + git_config *cfg, int expected, int expected_patterned) +{ + int n = 0; + + if (expected > 0) { + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert_equal_i(expected, n); + } else { + cl_assert_equal_i(GIT_ENOTFOUND, + git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + } + + n = 0; + + if (expected_patterned > 0) { + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "example", cb, &n)); + cl_assert_equal_i(expected_patterned, n); + } else { + cl_assert_equal_i(GIT_ENOTFOUND, + git_config_get_multivar_foreach(cfg, _name, "example", cb, &n)); + } +} + +static void check_get_multivar(git_config *cfg, int expected) +{ + git_config_iterator *iter; + git_config_entry *entry; + int n = 0; + + cl_git_pass(git_config_multivar_iterator_new(&iter, cfg, _name, NULL)); + + while (git_config_next(&entry, iter) == 0) + n++; + + cl_assert_equal_i(expected, n); + git_config_iterator_free(iter); + +} + +void test_config_multivar__get(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + check_get_multivar_foreach(cfg, 2, 1); + + /* add another that has the _name entry */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config9", GIT_CONFIG_LEVEL_SYSTEM, NULL, 1)); + check_get_multivar_foreach(cfg, 3, 2); + + /* add another that does not have the _name entry */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config0", GIT_CONFIG_LEVEL_GLOBAL, NULL, 1)); + check_get_multivar_foreach(cfg, 3, 2); + + /* add another that does not have the _name entry at the end */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config1", GIT_CONFIG_LEVEL_APP, NULL, 1)); + check_get_multivar_foreach(cfg, 3, 2); + + /* drop original file */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config2", GIT_CONFIG_LEVEL_LOCAL, NULL, 1)); + check_get_multivar_foreach(cfg, 1, 1); + + /* drop other file with match */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config3", GIT_CONFIG_LEVEL_SYSTEM, NULL, 1)); + check_get_multivar_foreach(cfg, 0, 0); + + /* reload original file (add different place in order) */ + cl_git_pass(git_config_add_file_ondisk(cfg, "config/config11", GIT_CONFIG_LEVEL_SYSTEM, NULL, 1)); + check_get_multivar_foreach(cfg, 2, 1); + + check_get_multivar(cfg, 2); + + git_config_free(cfg); +} + +void test_config_multivar__add(void) +{ + git_config *cfg; + int n; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + cl_git_pass(git_config_set_multivar(cfg, _name, "non-existent", "git://git.otherplace.org/libgit2")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert_equal_i(n, 3); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); + cl_assert_equal_i(n, 1); + + git_config_free(cfg); + + /* We know it works in memory, let's see if the file is written correctly */ + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert_equal_i(n, 3); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); + cl_assert_equal_i(n, 1); + + git_config_free(cfg); +} + +void test_config_multivar__add_new(void) +{ + const char *var = "a.brand.new"; + git_config *cfg; + int n; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + cl_git_pass(git_config_set_multivar(cfg, var, "$^", "variable")); + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, var, NULL, cb, &n)); + cl_assert_equal_i(n, 1); + + git_config_free(cfg); +} + +void test_config_multivar__replace(void) +{ + git_config *cfg; + int n; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert(n == 2); + + cl_git_pass(git_config_set_multivar(cfg, _name, "github", "git://git.otherplace.org/libgit2")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert(n == 2); + + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert(n == 2); + + git_config_free(cfg); +} + +void test_config_multivar__replace_multiple(void) +{ + git_config *cfg; + int n; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + cl_git_pass(git_config_set_multivar(cfg, _name, "git://", "git://git.otherplace.org/libgit2")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); + cl_assert_equal_i(n, 2); + + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n)); + cl_assert_equal_i(n, 2); + + git_config_free(cfg); +} + +void test_config_multivar__delete(void) +{ + git_config *cfg; + int n; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert_equal_i(2, n); + + cl_git_pass(git_config_delete_multivar(cfg, _name, "github")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert_equal_i(1, n); + + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert_equal_i(1, n); + + git_config_free(cfg); +} + +void test_config_multivar__delete_multiple(void) +{ + git_config *cfg; + int n; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n)); + cl_assert(n == 2); + + cl_git_pass(git_config_delete_multivar(cfg, _name, "git")); + + n = 0; + cl_git_fail_with(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n), GIT_ENOTFOUND); + + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + n = 0; + cl_git_fail_with(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n), GIT_ENOTFOUND); + + git_config_free(cfg); +} + +void test_config_multivar__delete_notfound(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "config/config11")); + + cl_git_fail_with(git_config_delete_multivar(cfg, "remote.ab.noturl", "git"), GIT_ENOTFOUND); + + git_config_free(cfg); +} diff --git a/tests/libgit2/config/new.c b/tests/libgit2/config/new.c new file mode 100644 index 000000000..baca05332 --- /dev/null +++ b/tests/libgit2/config/new.c @@ -0,0 +1,34 @@ +#include "clar_libgit2.h" + +#include "filebuf.h" +#include "futils.h" +#include "posix.h" + +#define TEST_CONFIG "git-new-config" + +void test_config_new__write_new_config(void) +{ + git_config *config; + git_buf buf = GIT_BUF_INIT; + + cl_git_mkfile(TEST_CONFIG, ""); + cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); + + cl_git_pass(git_config_set_string(config, "color.ui", "auto")); + cl_git_pass(git_config_set_string(config, "core.editor", "ed")); + + git_config_free(config); + + cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); + + cl_git_pass(git_config_get_string_buf(&buf, config, "color.ui")); + cl_assert_equal_s("auto", buf.ptr); + git_buf_dispose(&buf); + cl_git_pass(git_config_get_string_buf(&buf, config, "core.editor")); + cl_assert_equal_s("ed", buf.ptr); + + git_buf_dispose(&buf); + git_config_free(config); + + cl_must_pass(p_unlink(TEST_CONFIG)); +} diff --git a/tests/libgit2/config/read.c b/tests/libgit2/config/read.c new file mode 100644 index 000000000..a2e668c20 --- /dev/null +++ b/tests/libgit2/config/read.c @@ -0,0 +1,1040 @@ +#include "clar_libgit2.h" +#include "fs_path.h" + +static git_buf buf = GIT_BUF_INIT; + +void test_config_read__cleanup(void) +{ + git_buf_dispose(&buf); +} + +void test_config_read__simple_read(void) +{ + git_config *cfg; + int32_t i; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config0"))); + + cl_git_pass(git_config_get_int32(&i, cfg, "core.repositoryformatversion")); + cl_assert(i == 0); + cl_git_pass(git_config_get_bool(&i, cfg, "core.filemode")); + cl_assert(i == 1); + cl_git_pass(git_config_get_bool(&i, cfg, "core.bare")); + cl_assert(i == 0); + cl_git_pass(git_config_get_bool(&i, cfg, "core.logallrefupdates")); + cl_assert(i == 1); + + git_config_free(cfg); +} + +void test_config_read__case_sensitive(void) +{ + git_config *cfg; + int i; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config1"))); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "this.that.other")); + cl_assert_equal_s("true", buf.ptr); + git_buf_dispose(&buf); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "this.That.other")); + cl_assert_equal_s("yes", buf.ptr); + + cl_git_pass(git_config_get_bool(&i, cfg, "this.that.other")); + cl_assert(i == 1); + cl_git_pass(git_config_get_bool(&i, cfg, "this.That.other")); + cl_assert(i == 1); + + /* This one doesn't exist */ + cl_must_fail(git_config_get_bool(&i, cfg, "this.thaT.other")); + + git_config_free(cfg); +} + +/* + * If \ is the last non-space character on the line, we read the next + * one, separating each line with SP. + */ +void test_config_read__multiline_value(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config2"))); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "this.That.and")); + cl_assert_equal_s("one one one two two three three", buf.ptr); + + git_config_free(cfg); +} + +static void clean_test_config(void *unused) +{ + GIT_UNUSED(unused); + cl_fixture_cleanup("./testconfig"); +} + +void test_config_read__multiline_value_and_eof(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[header]\n key1 = foo\\\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "header.key1")); + cl_assert_equal_s("foo", buf.ptr); + + git_config_free(cfg); +} + +void test_config_read__multiline_eof(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[header]\n key1 = \\\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "header.key1")); + cl_assert_equal_s("", buf.ptr); + + git_config_free(cfg); +} + +/* + * This kind of subsection declaration is case-insensitive + */ +void test_config_read__subsection_header(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config3"))); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "section.subsection.var")); + cl_assert_equal_s("hello", buf.ptr); + + /* The subsection is transformed to lower-case */ + cl_must_fail(git_config_get_string_buf(&buf, cfg, "section.subSectIon.var")); + + git_config_free(cfg); +} + +void test_config_read__lone_variable(void) +{ + git_config *cfg; + int i; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config4"))); + + cl_git_fail(git_config_get_int32(&i, cfg, "some.section.variable")); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.section.variable")); + cl_assert_equal_s("", buf.ptr); + git_buf_dispose(&buf); + + cl_git_pass(git_config_get_bool(&i, cfg, "some.section.variable")); + cl_assert(i == 1); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.section.variableeq")); + cl_assert_equal_s("", buf.ptr); + + cl_git_pass(git_config_get_bool(&i, cfg, "some.section.variableeq")); + cl_assert(i == 0); + + git_config_free(cfg); +} + +void test_config_read__number_suffixes(void) +{ + git_config *cfg; + int64_t i; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config5"))); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.simple")); + cl_assert(i == 1); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.k")); + cl_assert(i == 1 * 1024); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.kk")); + cl_assert(i == 1 * 1024); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.m")); + cl_assert(i == 1 * 1024 * 1024); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.mm")); + cl_assert(i == 1 * 1024 * 1024); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.g")); + cl_assert(i == 1 * 1024 * 1024 * 1024); + + cl_git_pass(git_config_get_int64(&i, cfg, "number.gg")); + cl_assert(i == 1 * 1024 * 1024 * 1024); + + git_config_free(cfg); +} + +void test_config_read__blank_lines(void) +{ + git_config *cfg; + int i; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config6"))); + + cl_git_pass(git_config_get_bool(&i, cfg, "valid.subsection.something")); + cl_assert(i == 1); + + cl_git_pass(git_config_get_bool(&i, cfg, "something.else.something")); + cl_assert(i == 0); + + git_config_free(cfg); +} + +void test_config_read__invalid_ext_headers(void) +{ + git_config *cfg; + cl_must_fail(git_config_open_ondisk(&cfg, cl_fixture("config/config7"))); +} + +void test_config_read__empty_files(void) +{ + git_config *cfg; + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config8"))); + git_config_free(cfg); +} + +void test_config_read__symbol_headers(void) +{ + git_config *cfg; + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config20"))); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "valid.[subsection].something")); + cl_assert_equal_s("a", buf.ptr); + git_buf_dispose(&buf); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "sec.[subsec]/child.parent")); + cl_assert_equal_s("grand", buf.ptr); + git_buf_dispose(&buf); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "sec2.[subsec2]/child2.type")); + cl_assert_equal_s("dvcs", buf.ptr); + git_buf_dispose(&buf); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "sec3.escape\"quote.vcs")); + cl_assert_equal_s("git", buf.ptr); + git_buf_dispose(&buf); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "sec4.escaping\\slash.lib")); + cl_assert_equal_s("git2", buf.ptr); + git_buf_dispose(&buf); + git_config_free(cfg); +} + +void test_config_read__multiline_multiple_quoted_comment_chars(void) +{ + git_config *cfg; + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config21"))); + git_config_free(cfg); +} + +void test_config_read__multiline_multiple_quoted_quote_at_beginning_of_line(void) +{ + git_config* cfg; + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config22"))); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "alias.m")); + cl_assert_equal_s("cmd ;; ;; bar", buf.ptr); + git_buf_dispose(&buf); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "alias.m2")); + cl_assert_equal_s("'; ; something '", buf.ptr); + git_buf_dispose(&buf); + git_config_free(cfg); +} + +void test_config_read__header_in_last_line(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config10"))); + git_config_free(cfg); +} + +void test_config_read__prefixes(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "remote.ab.url")); + cl_assert_equal_s("http://example.com/git/ab", buf.ptr); + git_buf_dispose(&buf); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "remote.abba.url")); + cl_assert_equal_s("http://example.com/git/abba", buf.ptr); + + git_config_free(cfg); +} + +void test_config_read__escaping_quotes(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config13"))); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.editor")); + cl_assert_equal_s("\"C:/Program Files/Nonsense/bah.exe\" \"--some option\"", buf.ptr); + + git_config_free(cfg); +} + +void test_config_read__invalid_escape_sequence(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[header]\n key1 = \\\\\\;\n key2 = value2\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +static int count_cfg_entries_and_compare_levels( + const git_config_entry *entry, void *payload) +{ + int *count = payload; + + if (!strcmp(entry->value, "7") || !strcmp(entry->value, "17")) + cl_assert(entry->level == GIT_CONFIG_LEVEL_GLOBAL); + else + cl_assert(entry->level == GIT_CONFIG_LEVEL_SYSTEM); + + (*count)++; + return 0; +} + +static int cfg_callback_countdown(const git_config_entry *entry, void *payload) +{ + int *count = payload; + GIT_UNUSED(entry); + (*count)--; + if (*count == 0) + return -100; + return 0; +} + +void test_config_read__foreach(void) +{ + git_config *cfg; + int count, ret; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + + count = 0; + cl_git_pass(git_config_foreach(cfg, count_cfg_entries_and_compare_levels, &count)); + cl_assert_equal_i(7, count); + + count = 3; + cl_git_fail(ret = git_config_foreach(cfg, cfg_callback_countdown, &count)); + cl_assert_equal_i(-100, ret); + + git_config_free(cfg); +} + +void test_config_read__iterator(void) +{ + const char *keys[] = { + "core.dummy2", + "core.verylong", + "core.dummy", + "remote.ab.url", + "remote.abba.url", + "core.dummy2", + "core.global" + }; + git_config *cfg; + git_config_iterator *iter; + git_config_entry *entry; + int count, ret; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + + count = 0; + cl_git_pass(git_config_iterator_new(&iter, cfg)); + + while ((ret = git_config_next(&entry, iter)) == 0) { + cl_assert_equal_s(entry->name, keys[count]); + count++; + } + + git_config_iterator_free(iter); + cl_assert_equal_i(GIT_ITEROVER, ret); + cl_assert_equal_i(7, count); + + count = 3; + cl_git_pass(git_config_iterator_new(&iter, cfg)); + + git_config_iterator_free(iter); + git_config_free(cfg); +} + +static int count_cfg_entries(const git_config_entry *entry, void *payload) +{ + int *count = payload; + GIT_UNUSED(entry); + (*count)++; + return 0; +} + +void test_config_read__foreach_match(void) +{ + git_config *cfg; + int count; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, "core.*", count_cfg_entries, &count)); + cl_assert_equal_i(3, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, "remote\\.ab.*", count_cfg_entries, &count)); + cl_assert_equal_i(2, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, ".*url$", count_cfg_entries, &count)); + cl_assert_equal_i(2, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, ".*dummy.*", count_cfg_entries, &count)); + cl_assert_equal_i(2, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, ".*nomatch.*", count_cfg_entries, &count)); + cl_assert_equal_i(0, count); + + git_config_free(cfg); +} + +static void check_glob_iter(git_config *cfg, const char *regexp, int expected) +{ + git_config_iterator *iter; + git_config_entry *entry; + int count, error; + + cl_git_pass(git_config_iterator_glob_new(&iter, cfg, regexp)); + + count = 0; + while ((error = git_config_next(&entry, iter)) == 0) + count++; + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(expected, count); + git_config_iterator_free(iter); +} + +void test_config_read__iterator_invalid_glob(void) +{ + git_config *cfg; + git_config_iterator *iter; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + + cl_git_fail(git_config_iterator_glob_new(&iter, cfg, "*")); + + git_config_free(cfg); +} + +void test_config_read__iterator_glob(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + + check_glob_iter(cfg, "core.*", 3); + check_glob_iter(cfg, "remote\\.ab.*", 2); + check_glob_iter(cfg, ".*url$", 2); + check_glob_iter(cfg, ".*dummy.*", 2); + check_glob_iter(cfg, ".*nomatch.*", 0); + + git_config_free(cfg); +} + +void test_config_read__whitespace_not_required_around_assignment(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config14"))); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "a.b")); + cl_assert_equal_s("c", buf.ptr); + git_buf_dispose(&buf); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "d.e")); + cl_assert_equal_s("f", buf.ptr); + + git_config_free(cfg); +} + +void test_config_read__read_git_config_entry(void) +{ + git_config *cfg; + git_config_entry *entry; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); + + cl_git_pass(git_config_get_entry(&entry, cfg, "core.dummy2")); + cl_assert_equal_s("core.dummy2", entry->name); + cl_assert_equal_s("42", entry->value); + cl_assert_equal_i(GIT_CONFIG_LEVEL_SYSTEM, entry->level); + + git_config_entry_free(entry); + git_config_free(cfg); +} + +/* + * At the beginning of the test: + * - config9 has: core.dummy2=42 + * - config15 has: core.dummy2=7 + * - config16 has: core.dummy2=28 + */ +void test_config_read__local_config_overrides_global_config_overrides_system_config(void) +{ + git_config *cfg; + int32_t i; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config16"), + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); + + cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2")); + cl_assert_equal_i(28, i); + + git_config_free(cfg); + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + + cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2")); + cl_assert_equal_i(7, i); + + git_config_free(cfg); +} + +/* + * At the beginning of the test: + * - config9 has: core.global does not exist + * - config15 has: core.global=17 + * - config16 has: core.global=29 + * + * And also: + * - config9 has: core.system does not exist + * - config15 has: core.system does not exist + * - config16 has: core.system=11 + */ +void test_config_read__fallback_from_local_to_global_and_from_global_to_system(void) +{ + git_config *cfg; + int32_t i; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"), + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config16"), + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); + + cl_git_pass(git_config_get_int32(&i, cfg, "core.global")); + cl_assert_equal_i(17, i); + cl_git_pass(git_config_get_int32(&i, cfg, "core.system")); + cl_assert_equal_i(11, i); + + git_config_free(cfg); +} + +void test_config_read__parent_dir_is_file(void) +{ + git_config *cfg; + int count; + + cl_git_pass(git_config_new(&cfg)); + /* + * Verify we can add non-existing files when the parent directory is not + * a directory. + */ + cl_git_pass(git_config_add_file_ondisk(cfg, "/dev/null/.gitconfig", + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); + + count = 0; + cl_git_pass(git_config_foreach(cfg, count_cfg_entries_and_compare_levels, &count)); + cl_assert_equal_i(0, count); + + git_config_free(cfg); +} + +/* + * At the beginning of the test, config18 has: + * int32global = 28 + * int64global = 9223372036854775803 + * boolglobal = true + * stringglobal = I'm a global config value! + * + * And config19 has: + * int32global = -1 + * int64global = -2 + * boolglobal = false + * stringglobal = don't find me! + * + */ +void test_config_read__simple_read_from_specific_level(void) +{ + git_config *cfg, *cfg_specific; + int i; + int64_t l, expected = +9223372036854775803; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"), + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"), + GIT_CONFIG_LEVEL_SYSTEM, NULL, 0)); + + cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL)); + + cl_git_pass(git_config_get_int32(&i, cfg_specific, "core.int32global")); + cl_assert_equal_i(28, i); + cl_git_pass(git_config_get_int64(&l, cfg_specific, "core.int64global")); + cl_assert(l == expected); + cl_git_pass(git_config_get_bool(&i, cfg_specific, "core.boolglobal")); + cl_assert_equal_b(true, i); + cl_git_pass(git_config_get_string_buf(&buf, cfg_specific, "core.stringglobal")); + cl_assert_equal_s("I'm a global config value!", buf.ptr); + + git_config_free(cfg_specific); + git_config_free(cfg); +} + +void test_config_read__can_load_and_parse_an_empty_config_file(void) +{ + git_config *cfg; + int i; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", ""); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + cl_assert_equal_i(GIT_ENOTFOUND, git_config_get_int32(&i, cfg, "nope.neither")); + + git_config_free(cfg); +} + +void test_config_read__corrupt_header(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[sneaky ] \"quoted closing quote mark\\\""); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__corrupt_header2(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[unclosed \"bracket\"\n lib = git2\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__corrupt_header3(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[unclosed \"slash\\\"]\n lib = git2\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__invalid_key_chars(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[foo]\n has_underscore = git2\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + cl_git_rewritefile("./testconfig", "[foo]\n has/slash = git2\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + cl_git_rewritefile("./testconfig", "[foo]\n has+plus = git2\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + cl_git_rewritefile("./testconfig", "[no_key]\n = git2\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__lone_variable_with_trailing_whitespace(void) +{ + git_config *cfg; + int b; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[foo]\n lonevariable \n"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + + cl_git_pass(git_config_get_bool(&b, cfg, "foo.lonevariable")); + cl_assert_equal_b(true, b); + + git_config_free(cfg); +} + +void test_config_read__override_variable(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[some] var = one\nvar = two"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); + cl_assert_equal_s("two", buf.ptr); + + git_config_free(cfg); +} + +void test_config_read__path(void) +{ + git_config *cfg; + git_buf path = GIT_BUF_INIT; + git_buf old_path = GIT_BUF_INIT; + git_str home_path = GIT_STR_INIT; + git_str expected_path = GIT_STR_INIT; + + cl_git_pass(p_mkdir("fakehome", 0777)); + cl_git_pass(git_fs_path_prettify(&home_path, "fakehome", NULL)); + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &old_path)); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, home_path.ptr)); + cl_git_mkfile("./testconfig", "[some]\n path = ~/somefile"); + cl_git_pass(git_fs_path_join_unrooted(&expected_path, "somefile", home_path.ptr, NULL)); + + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + cl_git_pass(git_config_get_path(&path, cfg, "some.path")); + cl_assert_equal_s(expected_path.ptr, path.ptr); + git_buf_dispose(&path); + + cl_git_mkfile("./testconfig", "[some]\n path = ~/"); + cl_git_pass(git_fs_path_join_unrooted(&expected_path, "", home_path.ptr, NULL)); + + cl_git_pass(git_config_get_path(&path, cfg, "some.path")); + cl_assert_equal_s(expected_path.ptr, path.ptr); + git_buf_dispose(&path); + + cl_git_mkfile("./testconfig", "[some]\n path = ~"); + cl_git_pass(git_str_sets(&expected_path, home_path.ptr)); + + cl_git_pass(git_config_get_path(&path, cfg, "some.path")); + cl_assert_equal_s(expected_path.ptr, path.ptr); + git_buf_dispose(&path); + + cl_git_mkfile("./testconfig", "[some]\n path = ~user/foo"); + cl_git_fail(git_config_get_path(&path, cfg, "some.path")); + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, old_path.ptr)); + git_buf_dispose(&old_path); + git_str_dispose(&home_path); + git_str_dispose(&expected_path); + git_config_free(cfg); +} + +void test_config_read__crlf_style_line_endings(void) +{ + git_buf buf = GIT_BUF_INIT; + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[some]\r\n var = value\r\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); + cl_assert_equal_s(buf.ptr, "value"); + + git_config_free(cfg); + git_buf_dispose(&buf); +} + +void test_config_read__trailing_crlf(void) +{ + git_buf buf = GIT_BUF_INIT; + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[some]\r\n var = value\r\n\r\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); + cl_assert_equal_s(buf.ptr, "value"); + + git_config_free(cfg); + git_buf_dispose(&buf); +} + +void test_config_read__bom(void) +{ + git_buf buf = GIT_BUF_INIT; + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[some]\n var = value\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); + cl_assert_equal_s(buf.ptr, "value"); + + git_config_free(cfg); + git_buf_dispose(&buf); +} + +void test_config_read__arbitrary_whitespace_before_subsection(void) +{ + git_buf buf = GIT_BUF_INIT; + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[some \t \"subsection\"]\n var = value\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.subsection.var")); + cl_assert_equal_s(buf.ptr, "value"); + + git_config_free(cfg); + git_buf_dispose(&buf); +} + +void test_config_read__no_whitespace_after_subsection(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[some \"subsection\" ]\n var = value\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__invalid_space_section(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[some section]\n var = value\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__invalid_quoted_first_section(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[\"some\"]\n var = value\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__invalid_unquoted_subsection(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[some sub section]\n var = value\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__invalid_quoted_third_section(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[some sub \"section\"]\n var = value\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__unreadable_file_ignored(void) +{ + git_buf buf = GIT_BUF_INIT; + git_config *cfg; + int ret; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[some] var = value\n[some \"OtheR\"] var = value"); + cl_git_pass(p_chmod("./testconfig", 0)); + + ret = git_config_open_ondisk(&cfg, "./test/config"); + cl_assert(ret == 0 || ret == GIT_ENOTFOUND); + + git_config_free(cfg); + git_buf_dispose(&buf); +} + +void test_config_read__single_line(void) +{ + git_buf buf = GIT_BUF_INIT; + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[some] var = value\n[some \"OtheR\"] var = value"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); + cl_assert_equal_s(buf.ptr, "value"); + + git_buf_dispose(&buf); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.OtheR.var")); + cl_assert_equal_s(buf.ptr, "value"); + + git_config_free(cfg); + cl_git_mkfile("./testconfig", "[some] var = value\n[some \"OtheR\"]var = value"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + git_buf_dispose(&buf); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.var")); + cl_assert_equal_s(buf.ptr, "value"); + + git_buf_dispose(&buf); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.OtheR.var")); + cl_assert_equal_s(buf.ptr, "value"); + + git_config_free(cfg); + git_buf_dispose(&buf); +} + +static int read_nosection_cb(const git_config_entry *entry, void *payload) { + int *seen = (int*)payload; + if (strcmp(entry->name, "key") == 0) { + (*seen)++; + } + return 0; +} + +/* This would ideally issue a warning, if we had a way to do so. */ +void test_config_read__nosection(void) +{ + git_config *cfg; + git_buf buf = GIT_BUF_INIT; + int seen = 0; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config-nosection"))); + + /* + * Given a key with no section, we do not allow reading it, + * but we do include it in an iteration over the config + * store. This appears to match how git's own APIs (and + * git-config(1)) behave. + */ + + cl_git_fail_with(git_config_get_string_buf(&buf, cfg, "key"), GIT_EINVALIDSPEC); + + cl_git_pass(git_config_foreach(cfg, read_nosection_cb, &seen)); + cl_assert_equal_i(seen, 1); + + git_buf_dispose(&buf); + git_config_free(cfg); +} + +enum { + MAP_TRUE = 0, + MAP_FALSE = 1, + MAP_ALWAYS = 2 +}; + +static git_configmap _test_map1[] = { + {GIT_CONFIGMAP_STRING, "always", MAP_ALWAYS}, + {GIT_CONFIGMAP_FALSE, NULL, MAP_FALSE}, + {GIT_CONFIGMAP_TRUE, NULL, MAP_TRUE}, +}; + +static git_configmap _test_map2[] = { + {GIT_CONFIGMAP_INT32, NULL, 0}, +}; + +void test_config_read__get_mapped(void) +{ + git_config *cfg; + int val; + int known_good; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[header]\n" + " key1 = 1\n" + " key2 = true\n" + " key3\n" + " key4 = always\n" + " key5 = false\n" + " key6 = 0\n" + " key7 = never\n" + " key8 = On\n" + " key9 = off\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + + /* check parsing bool and string */ + cl_git_pass(git_config_get_mapped(&val, cfg, "header.key1", _test_map1, ARRAY_SIZE(_test_map1))); + cl_assert_equal_i(val, MAP_TRUE); + cl_git_pass(git_config_get_mapped(&val, cfg, "header.key2", _test_map1, ARRAY_SIZE(_test_map1))); + cl_assert_equal_i(val, MAP_TRUE); + cl_git_pass(git_config_get_mapped(&val, cfg, "header.key3", _test_map1, ARRAY_SIZE(_test_map1))); + cl_assert_equal_i(val, MAP_TRUE); + cl_git_pass(git_config_get_mapped(&val, cfg, "header.key8", _test_map1, ARRAY_SIZE(_test_map1))); + cl_assert_equal_i(val, MAP_TRUE); + + cl_git_pass(git_config_get_mapped(&val, cfg, "header.key4", _test_map1, ARRAY_SIZE(_test_map1))); + cl_assert_equal_i(val, MAP_ALWAYS); + + cl_git_pass(git_config_get_mapped(&val, cfg, "header.key5", _test_map1, ARRAY_SIZE(_test_map1))); + cl_assert_equal_i(val, MAP_FALSE); + cl_git_pass(git_config_get_mapped(&val, cfg, "header.key6", _test_map1, ARRAY_SIZE(_test_map1))); + cl_assert_equal_i(val, MAP_FALSE); + cl_git_pass(git_config_get_mapped(&val, cfg, "header.key9", _test_map1, ARRAY_SIZE(_test_map1))); + cl_assert_equal_i(val, MAP_FALSE); + + cl_git_fail(git_config_get_mapped(&val, cfg, "header.key7", _test_map1, ARRAY_SIZE(_test_map1))); + + /* check parsing int values */ + cl_git_pass(git_config_get_mapped(&val, cfg, "header.key1", _test_map2, ARRAY_SIZE(_test_map2))); + cl_git_pass(git_config_get_int32(&known_good, cfg, "header.key1")); + cl_assert_equal_i(val, known_good); + cl_git_pass(git_config_get_mapped(&val, cfg, "header.key6", _test_map2, ARRAY_SIZE(_test_map2))); + cl_git_pass(git_config_get_int32(&known_good, cfg, "header.key6")); + cl_assert_equal_i(val, known_good); + + cl_git_fail(git_config_get_mapped(&val, cfg, "header.key2", _test_map2, ARRAY_SIZE(_test_map2))); + cl_git_fail(git_config_get_mapped(&val, cfg, "header.key3", _test_map2, ARRAY_SIZE(_test_map2))); + cl_git_fail(git_config_get_mapped(&val, cfg, "header.key4", _test_map2, ARRAY_SIZE(_test_map2))); + cl_git_fail(git_config_get_mapped(&val, cfg, "header.key5", _test_map2, ARRAY_SIZE(_test_map2))); + cl_git_fail(git_config_get_mapped(&val, cfg, "header.key7", _test_map2, ARRAY_SIZE(_test_map2))); + cl_git_fail(git_config_get_mapped(&val, cfg, "header.key8", _test_map2, ARRAY_SIZE(_test_map2))); + cl_git_fail(git_config_get_mapped(&val, cfg, "header.key9", _test_map2, ARRAY_SIZE(_test_map2))); + + git_config_free(cfg); +} diff --git a/tests/libgit2/config/readonly.c b/tests/libgit2/config/readonly.c new file mode 100644 index 000000000..a8901e394 --- /dev/null +++ b/tests/libgit2/config/readonly.c @@ -0,0 +1,65 @@ +#include "clar_libgit2.h" +#include "config_backend.h" +#include "config.h" +#include "path.h" + +static git_config *cfg; + +void test_config_readonly__initialize(void) +{ + cl_git_pass(git_config_new(&cfg)); +} + +void test_config_readonly__cleanup(void) +{ + git_config_free(cfg); + cfg = NULL; +} + +void test_config_readonly__writing_to_readonly_fails(void) +{ + git_config_backend *backend; + + cl_git_pass(git_config_backend_from_file(&backend, "global")); + backend->readonly = 1; + cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + + cl_git_fail_with(GIT_ENOTFOUND, git_config_set_string(cfg, "foo.bar", "baz")); + cl_assert(!git_fs_path_exists("global")); +} + +void test_config_readonly__writing_to_cfg_with_rw_precedence_succeeds(void) +{ + git_config_backend *backend; + + cl_git_pass(git_config_backend_from_file(&backend, "global")); + backend->readonly = 1; + cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + + cl_git_pass(git_config_backend_from_file(&backend, "local")); + cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); + + cl_git_pass(git_config_set_string(cfg, "foo.bar", "baz")); + + cl_assert(git_fs_path_exists("local")); + cl_assert(!git_fs_path_exists("global")); + cl_git_pass(p_unlink("local")); +} + +void test_config_readonly__writing_to_cfg_with_ro_precedence_succeeds(void) +{ + git_config_backend *backend; + + cl_git_pass(git_config_backend_from_file(&backend, "local")); + backend->readonly = 1; + cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); + + cl_git_pass(git_config_backend_from_file(&backend, "global")); + cl_git_pass(git_config_add_backend(cfg, backend, GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + + cl_git_pass(git_config_set_string(cfg, "foo.bar", "baz")); + + cl_assert(!git_fs_path_exists("local")); + cl_assert(git_fs_path_exists("global")); + cl_git_pass(p_unlink("global")); +} diff --git a/tests/libgit2/config/rename.c b/tests/libgit2/config/rename.c new file mode 100644 index 000000000..be620c307 --- /dev/null +++ b/tests/libgit2/config/rename.c @@ -0,0 +1,89 @@ +#include "clar_libgit2.h" +#include "config.h" + +static git_repository *g_repo = NULL; +static git_config *g_config = NULL; + +void test_config_rename__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_config(&g_config, g_repo)); +} + +void test_config_rename__cleanup(void) +{ + git_config_free(g_config); + g_config = NULL; + + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +void test_config_rename__can_rename(void) +{ + git_config_entry *ce; + + cl_git_pass(git_config_get_entry( + &ce, g_config, "branch.track-local.remote")); + cl_assert_equal_s(".", ce->value); + git_config_entry_free(ce); + + cl_git_fail(git_config_get_entry( + &ce, g_config, "branch.local-track.remote")); + + cl_git_pass(git_config_rename_section( + g_repo, "branch.track-local", "branch.local-track")); + + cl_git_pass(git_config_get_entry( + &ce, g_config, "branch.local-track.remote")); + cl_assert_equal_s(".", ce->value); + git_config_entry_free(ce); + + cl_git_fail(git_config_get_entry( + &ce, g_config, "branch.track-local.remote")); +} + +void test_config_rename__prevent_overwrite(void) +{ + git_config_entry *ce; + + cl_git_pass(git_config_set_string( + g_config, "branch.local-track.remote", "yellow")); + + cl_git_pass(git_config_get_entry( + &ce, g_config, "branch.local-track.remote")); + cl_assert_equal_s("yellow", ce->value); + git_config_entry_free(ce); + + cl_git_pass(git_config_rename_section( + g_repo, "branch.track-local", "branch.local-track")); + + cl_git_pass(git_config_get_entry( + &ce, g_config, "branch.local-track.remote")); + cl_assert_equal_s(".", ce->value); + git_config_entry_free(ce); + + /* so, we don't currently prevent overwrite... */ + /* { + const git_error *err; + cl_assert((err = git_error_last()) != NULL); + cl_assert(err->message != NULL); + } */ +} + +static void assert_invalid_config_section_name( + git_repository *repo, const char *name) +{ + cl_git_fail_with( + git_config_rename_section(repo, "branch.remoteless", name), + GIT_EINVALIDSPEC); +} + +void test_config_rename__require_a_valid_new_name(void) +{ + assert_invalid_config_section_name(g_repo, ""); + assert_invalid_config_section_name(g_repo, "bra\nch"); + assert_invalid_config_section_name(g_repo, "branc#"); + assert_invalid_config_section_name(g_repo, "bra\nch.duh"); + assert_invalid_config_section_name(g_repo, "branc#.duh"); +} diff --git a/tests/libgit2/config/snapshot.c b/tests/libgit2/config/snapshot.c new file mode 100644 index 000000000..5cc08a721 --- /dev/null +++ b/tests/libgit2/config/snapshot.c @@ -0,0 +1,139 @@ +#include "clar_libgit2.h" + +#include "config_backend.h" + +static git_config *cfg; +static git_config *snapshot; + +void test_config_snapshot__cleanup(void) +{ + git_config_free(cfg); + cfg = NULL; + git_config_free(snapshot); + snapshot = NULL; +} + +void test_config_snapshot__create_snapshot(void) +{ + int32_t i; + + cl_git_mkfile("config", "[old]\nvalue = 5\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "config")); + cl_git_pass(git_config_get_int32(&i, cfg, "old.value")); + cl_assert_equal_i(5, i); + + cl_git_pass(git_config_snapshot(&snapshot, cfg)); + + /* Change the value on the file itself (simulate external process) */ + cl_git_mkfile("config", "[old]\nvalue = 56\n"); + + cl_git_pass(git_config_get_int32(&i, cfg, "old.value")); + cl_assert_equal_i(56, i); + cl_git_pass(git_config_get_int32(&i, snapshot, "old.value")); + cl_assert_equal_i(5, i); + + /* Change the value on the file itself (simulate external process) */ + cl_git_mkfile("config", "[old]\nvalue = 999\n"); + + /* Old snapshot should still have the old value */ + cl_git_pass(git_config_get_int32(&i, snapshot, "old.value")); + cl_assert_equal_i(5, i); + + /* New snapshot should see new value */ + git_config_free(snapshot); + cl_git_pass(git_config_snapshot(&snapshot, cfg)); + cl_git_pass(git_config_get_int32(&i, snapshot, "old.value")); + cl_assert_equal_i(999, i); + + cl_git_pass(p_unlink("config")); +} + +static int count_me(const git_config_entry *entry, void *payload) +{ + int *n = (int *) payload; + + GIT_UNUSED(entry); + + (*n)++; + + return 0; +} + +void test_config_snapshot__multivar(void) +{ + int count; + + count = 0; + cl_git_mkfile("config", "[old]\nvalue = 5\nvalue = 6\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "config")); + cl_git_pass(git_config_get_multivar_foreach(cfg, "old.value", NULL, count_me, &count)); + cl_assert_equal_i(2, count); + + count = 0; + cl_git_pass(git_config_snapshot(&snapshot, cfg)); + cl_git_pass(git_config_get_multivar_foreach(snapshot, "old.value", NULL, count_me, &count)); + cl_assert_equal_i(2, count); + + cl_git_pass(p_unlink("config")); +} + +void test_config_snapshot__includes(void) +{ + int i; + + cl_git_mkfile("including", "[include]\npath = included"); + cl_git_mkfile("included", "[section]\nkey = 1\n"); + + cl_git_pass(git_config_open_ondisk(&cfg, "including")); + cl_git_pass(git_config_snapshot(&snapshot, cfg)); + + cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); + cl_assert_equal_i(i, 1); + + /* Rewrite "included" config */ + cl_git_mkfile("included", "[section]\nkey = 11\n"); + + /* Assert that the live config changed, but snapshot remained the same */ + cl_git_pass(git_config_get_int32(&i, cfg, "section.key")); + cl_assert_equal_i(i, 11); + cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); + cl_assert_equal_i(i, 1); + + cl_git_pass(p_unlink("including")); + cl_git_pass(p_unlink("included")); +} + +void test_config_snapshot__snapshot(void) +{ + git_config *snapshot_snapshot; + int i; + + cl_git_mkfile("configfile", "[section]\nkey = 1\n"); + + cl_git_pass(git_config_open_ondisk(&cfg, "configfile")); + cl_git_pass(git_config_snapshot(&snapshot, cfg)); + + cl_git_pass(git_config_snapshot(&snapshot_snapshot, snapshot)); + + cl_git_pass(git_config_get_int32(&i, snapshot_snapshot, "section.key")); + cl_assert_equal_i(i, 1); + + git_config_free(snapshot_snapshot); + + cl_git_pass(p_unlink("configfile")); +} + +void test_config_snapshot__snapshot_from_in_memony(void) +{ + const char *configuration = "[section]\nkey = 1\n"; + git_config_backend *backend; + int i; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_backend_from_string(&backend, configuration, strlen(configuration))); + cl_git_pass(git_config_add_backend(cfg, backend, 0, NULL, 0)); + + cl_git_pass(git_config_snapshot(&snapshot, cfg)); + cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); + cl_assert_equal_i(i, 1); +} diff --git a/tests/libgit2/config/stress.c b/tests/libgit2/config/stress.c new file mode 100644 index 000000000..69e6810fa --- /dev/null +++ b/tests/libgit2/config/stress.c @@ -0,0 +1,206 @@ +#include "clar_libgit2.h" + +#include "filebuf.h" +#include "futils.h" +#include "posix.h" + +#define TEST_CONFIG "git-test-config" + +static git_buf buf = GIT_BUF_INIT; + +void test_config_stress__initialize(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + + cl_git_pass(git_filebuf_open(&file, TEST_CONFIG, 0, 0666)); + + git_filebuf_printf(&file, "[color]\n\tui = auto\n"); + git_filebuf_printf(&file, "[core]\n\teditor = \n"); + + cl_git_pass(git_filebuf_commit(&file)); +} + +void test_config_stress__cleanup(void) +{ + git_buf_dispose(&buf); + p_unlink(TEST_CONFIG); +} + +void test_config_stress__dont_break_on_invalid_input(void) +{ + git_config *config; + + cl_assert(git_fs_path_exists(TEST_CONFIG)); + cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); + + cl_git_pass(git_config_get_string_buf(&buf, config, "color.ui")); + cl_git_pass(git_config_get_string_buf(&buf, config, "core.editor")); + + git_config_free(config); +} + +static void assert_config_value(git_config *config, const char *key, const char *value) +{ + git_buf_dispose(&buf); + cl_git_pass(git_config_get_string_buf(&buf, config, key)); + cl_assert_equal_s(value, buf.ptr); +} + +void test_config_stress__comments(void) +{ + git_config *config; + + cl_git_pass(git_config_open_ondisk(&config, cl_fixture("config/config12"))); + + assert_config_value(config, "some.section.test2", "hello"); + assert_config_value(config, "some.section.test3", "welcome"); + assert_config_value(config, "some.section.other", "hello! \" ; ; ; "); + assert_config_value(config, "some.section.other2", "cool! \" # # # "); + assert_config_value(config, "some.section.multi", "hi, this is a ; multiline comment # with ;\n special chars and other stuff !@#"); + assert_config_value(config, "some.section.multi2", "good, this is a ; multiline comment # with ;\n special chars and other stuff !@#"); + assert_config_value(config, "some.section.back", "this is \ba phrase"); + assert_config_value(config, "some.section.dollar", "some $sign"); + assert_config_value(config, "some.section.multiquotes", "!ls x ls # comment2 $HOME"); + assert_config_value(config, "some.section.multiquotes2", "!ls x ls \"# comment2 $HOME\""); + assert_config_value(config, "some.section.multiquotes3", "hi # ho there are # more quotes"); + assert_config_value(config, "some.section.quotecomment", "hi # ho there are # more"); + + git_config_free(config); +} + +void test_config_stress__escape_subsection_names(void) +{ + git_config *config; + + cl_assert(git_fs_path_exists("git-test-config")); + cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); + + cl_git_pass(git_config_set_string(config, "some.sec\\tion.other", "foo")); + git_config_free(config); + + cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); + + assert_config_value(config, "some.sec\\tion.other", "foo"); + + git_config_free(config); +} + +void test_config_stress__trailing_backslash(void) +{ + git_config *config; + const char *path = "C:\\iam\\some\\windows\\path\\"; + + cl_assert(git_fs_path_exists("git-test-config")); + cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); + cl_git_pass(git_config_set_string(config, "windows.path", path)); + git_config_free(config); + + cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); + assert_config_value(config, "windows.path", path); + + git_config_free(config); +} + +void test_config_stress__complex(void) +{ + git_config *config; + const char *path = "./config-immediate-multiline"; + + cl_git_mkfile(path, "[imm]\n multi = \"\\\nfoo\""); + cl_git_pass(git_config_open_ondisk(&config, path)); + assert_config_value(config, "imm.multi", "foo"); + + git_config_free(config); +} + +void test_config_stress__quick_write(void) +{ + git_config *config_w, *config_r; + const char *path = "./config-quick-write"; + const char *key = "quick.write"; + int32_t i; + + /* Create an external writer for one instance with the other one */ + cl_git_pass(git_config_open_ondisk(&config_w, path)); + cl_git_pass(git_config_open_ondisk(&config_r, path)); + + /* Write and read in the same second (repeat to increase the chance of it happening) */ + for (i = 0; i < 10; i++) { + int32_t val; + cl_git_pass(git_config_set_int32(config_w, key, i)); + cl_msleep(1); + cl_git_pass(git_config_get_int32(&val, config_r, key)); + cl_assert_equal_i(i, val); + } + + git_config_free(config_r); + git_config_free(config_w); +} + +static int foreach_cb(const git_config_entry *entry, void *payload) +{ + if (!strcmp(entry->name, "key.value")) { + *(char **)payload = git__strdup(entry->value); + return 0; + } + return -1; +} + +void test_config_stress__foreach_refreshes(void) +{ + git_config *config_w, *config_r; + char *value = NULL; + + cl_git_pass(git_config_open_ondisk(&config_w, "./cfg")); + cl_git_pass(git_config_open_ondisk(&config_r, "./cfg")); + + cl_git_pass(git_config_set_string(config_w, "key.value", "1")); + cl_git_pass(git_config_foreach_match(config_r, "key.value", foreach_cb, &value)); + + cl_assert_equal_s(value, "1"); + + git_config_free(config_r); + git_config_free(config_w); + git__free(value); +} + +void test_config_stress__foreach_refreshes_snapshot(void) +{ + git_config *config, *snapshot; + char *value = NULL; + + cl_git_pass(git_config_open_ondisk(&config, "./cfg")); + + cl_git_pass(git_config_set_string(config, "key.value", "1")); + cl_git_pass(git_config_snapshot(&snapshot, config)); + cl_git_pass(git_config_foreach_match(snapshot, "key.value", foreach_cb, &value)); + + cl_assert_equal_s(value, "1"); + + git_config_free(snapshot); + git_config_free(config); + git__free(value); +} + +void test_config_stress__huge_section_with_many_values(void) +{ + git_config *config; + + if (!cl_is_env_set("GITTEST_INVASIVE_SPEED")) + cl_skip(); + + /* + * The config file is structured in such a way that is + * has a section header that is approximately 500kb of + * size followed by 40k entries. While the resulting + * configuration file itself is roughly 650kb in size and + * thus considered to be rather small, in the past we'd + * balloon to more than 20GB of memory (20000x500kb) + * while parsing the file. It thus was a trivial way to + * cause an out-of-memory situation and thus cause denial + * of service, e.g. via gitmodules. + */ + cl_git_pass(git_config_open_ondisk(&config, cl_fixture("config/config-oom"))); + + git_config_free(config); +} diff --git a/tests/libgit2/config/validkeyname.c b/tests/libgit2/config/validkeyname.c new file mode 100644 index 000000000..4b36509af --- /dev/null +++ b/tests/libgit2/config/validkeyname.c @@ -0,0 +1,49 @@ +#include "clar_libgit2.h" + +#include "config.h" + +static git_config *cfg; + +void test_config_validkeyname__initialize(void) +{ + cl_fixture_sandbox("config/config10"); + + cl_git_pass(git_config_open_ondisk(&cfg, "config10")); +} + +void test_config_validkeyname__cleanup(void) +{ + git_config_free(cfg); + cfg = NULL; + + cl_fixture_cleanup("config10"); +} + +static void assert_invalid_config_key_name(const char *name) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_fail_with(git_config_get_string_buf(&buf, cfg, name), + GIT_EINVALIDSPEC); + cl_git_fail_with(git_config_set_string(cfg, name, "42"), + GIT_EINVALIDSPEC); + cl_git_fail_with(git_config_delete_entry(cfg, name), + GIT_EINVALIDSPEC); + cl_git_fail_with(git_config_get_multivar_foreach(cfg, name, "*", NULL, NULL), + GIT_EINVALIDSPEC); + cl_git_fail_with(git_config_set_multivar(cfg, name, "*", "42"), + GIT_EINVALIDSPEC); +} + +void test_config_validkeyname__accessing_requires_a_valid_name(void) +{ + assert_invalid_config_key_name(""); + assert_invalid_config_key_name("."); + assert_invalid_config_key_name(".."); + assert_invalid_config_key_name("core."); + assert_invalid_config_key_name("d#ff.dirstat.lines"); + assert_invalid_config_key_name("diff.dirstat.lines#"); + assert_invalid_config_key_name("dif\nf.dirstat.lines"); + assert_invalid_config_key_name("dif.dir\nstat.lines"); + assert_invalid_config_key_name("dif.dirstat.li\nes"); +} diff --git a/tests/libgit2/config/write.c b/tests/libgit2/config/write.c new file mode 100644 index 000000000..9d8c3fe94 --- /dev/null +++ b/tests/libgit2/config/write.c @@ -0,0 +1,764 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "git2/sys/config.h" +#include "config.h" + +void test_config_write__initialize(void) +{ + cl_fixture_sandbox("config/config9"); + cl_fixture_sandbox("config/config15"); + cl_fixture_sandbox("config/config17"); + cl_fixture_sandbox("config/config22"); +} + +void test_config_write__cleanup(void) +{ + cl_fixture_cleanup("config9"); + cl_fixture_cleanup("config15"); + cl_fixture_cleanup("config17"); + cl_fixture_cleanup("config22"); +} + +void test_config_write__replace_value(void) +{ + git_config *cfg; + int i; + int64_t l, expected = +9223372036854775803; + + /* By freeing the config, we make sure we flush the values */ + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_int32(cfg, "core.dummy", 5)); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy")); + cl_assert(i == 5); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_int32(cfg, "core.dummy", 1)); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_int64(cfg, "core.verylong", expected)); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_get_int64(&l, cfg, "core.verylong")); + cl_assert(l == expected); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_must_fail(git_config_get_int32(&i, cfg, "core.verylong")); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_int64(cfg, "core.verylong", 1)); + git_config_free(cfg); +} + +void test_config_write__delete_value(void) +{ + git_config *cfg; + int32_t i; + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_int32(cfg, "core.dummy", 5)); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_delete_entry(cfg, "core.dummy")); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_assert(git_config_get_int32(&i, cfg, "core.dummy") == GIT_ENOTFOUND); + cl_git_pass(git_config_set_int32(cfg, "core.dummy", 1)); + git_config_free(cfg); +} + +/* + * At the beginning of the test: + * - config9 has: core.dummy2=42 + * - config15 has: core.dummy2=7 + */ +void test_config_write__delete_value_at_specific_level(void) +{ + git_config *cfg, *cfg_specific; + int32_t i; + + cl_git_pass(git_config_open_ondisk(&cfg, "config15")); + cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2")); + cl_assert(i == 7); + git_config_free(cfg); + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config9", + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config15", + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + + cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL)); + + cl_git_pass(git_config_delete_entry(cfg_specific, "core.dummy2")); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config15")); + cl_assert(git_config_get_int32(&i, cfg, "core.dummy2") == GIT_ENOTFOUND); + cl_git_pass(git_config_set_int32(cfg, "core.dummy2", 7)); + + git_config_free(cfg_specific); + git_config_free(cfg); +} + +/* + * This test exposes a bug where duplicate empty section headers could prevent + * deletion of config entries. + */ +void test_config_write__delete_value_with_duplicate_header(void) +{ + const char *file_name = "config-duplicate-header"; + const char *entry_name = "remote.origin.url"; + git_config *cfg; + git_config_entry *entry; + + /* This config can occur after removing and re-adding the origin remote */ + const char *file_content = + "[remote \"origin\"]\n" \ + "[branch \"master\"]\n" \ + " remote = \"origin\"\n" \ + "[remote \"origin\"]\n" \ + " url = \"foo\"\n"; + + /* Write the test config and make sure the expected entry exists */ + cl_git_mkfile(file_name, file_content); + cl_git_pass(git_config_open_ondisk(&cfg, file_name)); + cl_git_pass(git_config_get_entry(&entry, cfg, entry_name)); + + /* Delete that entry */ + cl_git_pass(git_config_delete_entry(cfg, entry_name)); + + /* Reopen the file and make sure the entry no longer exists */ + git_config_entry_free(entry); + git_config_free(cfg); + cl_git_pass(git_config_open_ondisk(&cfg, file_name)); + cl_git_fail(git_config_get_entry(&entry, cfg, entry_name)); + + /* Cleanup */ + git_config_entry_free(entry); + git_config_free(cfg); +} + +/* + * This test exposes a bug where duplicate section headers could cause + * config_write to add a new entry when one already exists. + */ +void test_config_write__add_value_with_duplicate_header(void) +{ + const char *file_name = "config-duplicate-insert"; + const char *entry_name = "foo.c"; + const char *old_val = "old"; + const char *new_val = "new"; + const char *str; + git_config *cfg, *snapshot; + + /* c = old should be replaced by c = new. + * The bug causes c = new to be inserted under the first 'foo' header. + */ + const char *file_content = + "[foo]\n" \ + " a = b\n" \ + "[other]\n" \ + " a = b\n" \ + "[foo]\n" \ + " c = old\n"; + + /* Write the test config */ + cl_git_mkfile(file_name, file_content); + cl_git_pass(git_config_open_ondisk(&cfg, file_name)); + + /* make sure the expected entry (foo.c) exists */ + cl_git_pass(git_config_snapshot(&snapshot, cfg)); + cl_git_pass(git_config_get_string(&str, snapshot, entry_name)); + cl_assert_equal_s(old_val, str); + git_config_free(snapshot); + + /* Try setting foo.c to something else */ + cl_git_pass(git_config_set_string(cfg, entry_name, new_val)); + git_config_free(cfg); + + /* Reopen the file and make sure the new value was set */ + cl_git_pass(git_config_open_ondisk(&cfg, file_name)); + cl_git_pass(git_config_snapshot(&snapshot, cfg)); + cl_git_pass(git_config_get_string(&str, snapshot, entry_name)); + cl_assert_equal_s(new_val, str); + + /* Cleanup */ + git_config_free(snapshot); + git_config_free(cfg); +} + +void test_config_write__overwrite_value_with_duplicate_header(void) +{ + const char *file_name = "config-duplicate-header"; + const char *entry_name = "remote.origin.url"; + git_config *cfg; + git_config_entry *entry; + + /* This config can occur after removing and re-adding the origin remote */ + const char *file_content = + "[remote \"origin\"]\n" \ + "[branch \"master\"]\n" \ + " remote = \"origin\"\n" \ + "[remote \"origin\"]\n" \ + " url = \"foo\"\n"; + + /* Write the test config and make sure the expected entry exists */ + cl_git_mkfile(file_name, file_content); + cl_git_pass(git_config_open_ondisk(&cfg, file_name)); + cl_git_pass(git_config_get_entry(&entry, cfg, entry_name)); + + /* Update that entry */ + cl_git_pass(git_config_set_string(cfg, entry_name, "newurl")); + + /* Reopen the file and make sure the entry was updated */ + git_config_entry_free(entry); + git_config_free(cfg); + cl_git_pass(git_config_open_ondisk(&cfg, file_name)); + cl_git_pass(git_config_get_entry(&entry, cfg, entry_name)); + + cl_assert_equal_s("newurl", entry->value); + + /* Cleanup */ + git_config_entry_free(entry); + git_config_free(cfg); +} + +static int multivar_cb(const git_config_entry *entry, void *data) +{ + int *n = (int *)data; + + cl_assert_equal_s(entry->value, "newurl"); + + (*n)++; + + return 0; +} + +void test_config_write__overwrite_multivar_within_duplicate_header(void) +{ + const char *file_name = "config-duplicate-header"; + const char *entry_name = "remote.origin.url"; + git_config *cfg; + git_config_entry *entry; + int n = 0; + + /* This config can occur after removing and re-adding the origin remote */ + const char *file_content = + "[remote \"origin\"]\n" \ + " url = \"bar\"\n" \ + "[branch \"master\"]\n" \ + " remote = \"origin\"\n" \ + "[remote \"origin\"]\n" \ + " url = \"foo\"\n"; + + /* Write the test config and make sure the expected entry exists */ + cl_git_mkfile(file_name, file_content); + cl_git_pass(git_config_open_ondisk(&cfg, file_name)); + cl_git_pass(git_config_get_entry(&entry, cfg, entry_name)); + + /* Update that entry */ + cl_git_pass(git_config_set_multivar(cfg, entry_name, ".*", "newurl")); + git_config_entry_free(entry); + git_config_free(cfg); + + /* Reopen the file and make sure the entry was updated */ + cl_git_pass(git_config_open_ondisk(&cfg, file_name)); + cl_git_pass(git_config_get_multivar_foreach(cfg, entry_name, NULL, multivar_cb, &n)); + cl_assert_equal_i(2, n); + + /* Cleanup */ + git_config_free(cfg); +} + +void test_config_write__write_subsection(void) +{ + git_config *cfg; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_string(cfg, "my.own.var", "works")); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "my.own.var")); + cl_assert_equal_s("works", buf.ptr); + + git_buf_dispose(&buf); + git_config_free(cfg); +} + +void test_config_write__delete_inexistent(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_assert(git_config_delete_entry(cfg, "core.imaginary") == GIT_ENOTFOUND); + git_config_free(cfg); +} + +void test_config_write__value_containing_quotes(void) +{ + git_config *cfg; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_string(cfg, "core.somevar", "this \"has\" quotes")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.somevar")); + cl_assert_equal_s("this \"has\" quotes", buf.ptr); + git_buf_dispose(&buf); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.somevar")); + cl_assert_equal_s("this \"has\" quotes", buf.ptr); + git_buf_dispose(&buf); + git_config_free(cfg); + + /* The code path for values that already exist is different, check that one as well */ + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_string(cfg, "core.somevar", "this also \"has\" quotes")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.somevar")); + cl_assert_equal_s("this also \"has\" quotes", buf.ptr); + git_buf_dispose(&buf); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.somevar")); + cl_assert_equal_s("this also \"has\" quotes", buf.ptr); + git_buf_dispose(&buf); + git_config_free(cfg); +} + +void test_config_write__escape_value(void) +{ + git_config *cfg; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_set_string(cfg, "core.somevar", "this \"has\" quotes and \t")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.somevar")); + cl_assert_equal_s("this \"has\" quotes and \t", buf.ptr); + git_buf_dispose(&buf); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.somevar")); + cl_assert_equal_s("this \"has\" quotes and \t", buf.ptr); + git_buf_dispose(&buf); + git_config_free(cfg); +} + +void test_config_write__add_value_at_specific_level(void) +{ + git_config *cfg, *cfg_specific; + int i; + int64_t l, expected = +9223372036854775803; + git_buf buf = GIT_BUF_INIT; + + /* open config15 as global level config file */ + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config9", + GIT_CONFIG_LEVEL_LOCAL, NULL, 0)); + cl_git_pass(git_config_add_file_ondisk(cfg, "config15", + GIT_CONFIG_LEVEL_GLOBAL, NULL, 0)); + + cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL)); + + cl_git_pass(git_config_set_int32(cfg_specific, "core.int32global", 28)); + cl_git_pass(git_config_set_int64(cfg_specific, "core.int64global", expected)); + cl_git_pass(git_config_set_bool(cfg_specific, "core.boolglobal", true)); + cl_git_pass(git_config_set_string(cfg_specific, "core.stringglobal", "I'm a global config value!")); + git_config_free(cfg_specific); + git_config_free(cfg); + + /* open config15 as local level config file */ + cl_git_pass(git_config_open_ondisk(&cfg, "config15")); + + cl_git_pass(git_config_get_int32(&i, cfg, "core.int32global")); + cl_assert_equal_i(28, i); + cl_git_pass(git_config_get_int64(&l, cfg, "core.int64global")); + cl_assert(l == expected); + cl_git_pass(git_config_get_bool(&i, cfg, "core.boolglobal")); + cl_assert_equal_b(true, i); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.stringglobal")); + cl_assert_equal_s("I'm a global config value!", buf.ptr); + + git_buf_dispose(&buf); + git_config_free(cfg); +} + +void test_config_write__add_value_at_file_with_no_clrf_at_the_end(void) +{ + git_config *cfg; + int i; + + cl_git_pass(git_config_open_ondisk(&cfg, "config17")); + cl_git_pass(git_config_set_int32(cfg, "core.newline", 7)); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config17")); + cl_git_pass(git_config_get_int32(&i, cfg, "core.newline")); + cl_assert_equal_i(7, i); + + git_config_free(cfg); +} + +void test_config_write__add_section_at_file_with_no_clrf_at_the_end(void) +{ + git_config *cfg; + int i; + + cl_git_pass(git_config_open_ondisk(&cfg, "config17")); + cl_git_pass(git_config_set_int32(cfg, "diff.context", 10)); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, "config17")); + cl_git_pass(git_config_get_int32(&i, cfg, "diff.context")); + cl_assert_equal_i(10, i); + + git_config_free(cfg); +} + +void test_config_write__add_value_which_needs_quotes(void) +{ + git_config *cfg, *base; + const char* str1; + const char* str2; + const char* str3; + const char* str4; + const char* str5; + + cl_git_pass(git_config_open_ondisk(&cfg, "config17")); + cl_git_pass(git_config_set_string(cfg, "core.startwithspace", " Something")); + cl_git_pass(git_config_set_string(cfg, "core.endwithspace", "Something ")); + cl_git_pass(git_config_set_string(cfg, "core.containscommentchar1", "some#thing")); + cl_git_pass(git_config_set_string(cfg, "core.containscommentchar2", "some;thing")); + cl_git_pass(git_config_set_string(cfg, "core.startwhithsapceandcontainsdoublequote", " some\"thing")); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&base, "config17")); + cl_git_pass(git_config_snapshot(&cfg, base)); + cl_git_pass(git_config_get_string(&str1, cfg, "core.startwithspace")); + cl_assert_equal_s(" Something", str1); + cl_git_pass(git_config_get_string(&str2, cfg, "core.endwithspace")); + cl_assert_equal_s("Something ", str2); + cl_git_pass(git_config_get_string(&str3, cfg, "core.containscommentchar1")); + cl_assert_equal_s("some#thing", str3); + cl_git_pass(git_config_get_string(&str4, cfg, "core.containscommentchar2")); + cl_assert_equal_s("some;thing", str4); + cl_git_pass(git_config_get_string(&str5, cfg, "core.startwhithsapceandcontainsdoublequote")); + cl_assert_equal_s(" some\"thing", str5); + git_config_free(cfg); + git_config_free(base); +} + +void test_config_write__can_set_a_value_to_NULL(void) +{ + git_repository *repository; + git_config *config; + + repository = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_repository_config(&config, repository)); + cl_git_fail(git_config_set_string(config, "a.b.c", NULL)); + git_config_free(config); + + cl_git_sandbox_cleanup(); +} + +void test_config_write__can_set_an_empty_value(void) +{ + git_repository *repository; + git_config *config; + git_buf buf = {0}; + + repository = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_config(&config, repository)); + + cl_git_pass(git_config_set_string(config, "core.somevar", "")); + cl_git_pass(git_config_get_string_buf(&buf, config, "core.somevar")); + cl_assert_equal_s("", buf.ptr); + + git_buf_dispose(&buf); + git_config_free(config); + cl_git_sandbox_cleanup(); +} + +void test_config_write__updating_a_locked_config_file_returns_ELOCKED(void) +{ + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "config9")); + + cl_git_mkfile("config9.lock", "[core]\n"); + + cl_git_fail_with(git_config_set_string(cfg, "core.dump", "boom"), GIT_ELOCKED); + + git_config_free(cfg); +} + +void test_config_write__outside_change(void) +{ + int32_t tmp; + git_config *cfg; + const char *filename = "config-ext-change"; + + cl_git_mkfile(filename, "[old]\nvalue = 5\n"); + + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + + cl_git_pass(git_config_get_int32(&tmp, cfg, "old.value")); + + /* Change the value on the file itself (simulate external process) */ + cl_git_mkfile(filename, "[old]\nvalue = 6\n"); + + cl_git_pass(git_config_set_int32(cfg, "new.value", 7)); + + cl_git_pass(git_config_get_int32(&tmp, cfg, "old.value")); + cl_assert_equal_i(6, tmp); + + git_config_free(cfg); +} + +#define FOO_COMMENT \ + "; another comment!\n" + +#define SECTION_FOO \ + "\n" \ + " \n" \ + " [section \"foo\"] \n" \ + " # here's a comment\n" \ + "\tname = \"value\"\n" \ + " name2 = \"value2\"\n" \ + +#define SECTION_FOO_WITH_COMMENT SECTION_FOO FOO_COMMENT + +#define SECTION_BAR \ + "[section \"bar\"]\t\n" \ + "\t \n" \ + " barname=\"value\"\n" + + +void test_config_write__preserves_whitespace_and_comments(void) +{ + const char *file_name = "config-duplicate-header"; + const char *n; + git_config *cfg; + git_str newfile = GIT_STR_INIT; + + /* This config can occur after removing and re-adding the origin remote */ + const char *file_content = SECTION_FOO_WITH_COMMENT SECTION_BAR; + + /* Write the test config and make sure the expected entry exists */ + cl_git_mkfile(file_name, file_content); + cl_git_pass(git_config_open_ondisk(&cfg, file_name)); + cl_git_pass(git_config_set_string(cfg, "section.foo.other", "otherval")); + cl_git_pass(git_config_set_string(cfg, "newsection.newname", "new_value")); + + /* Ensure that we didn't needlessly mangle the config file */ + cl_git_pass(git_futils_readbuffer(&newfile, file_name)); + n = newfile.ptr; + + cl_assert_equal_strn(SECTION_FOO, n, strlen(SECTION_FOO)); + n += strlen(SECTION_FOO); + cl_assert_equal_strn("\tother = otherval\n", n, strlen("\tother = otherval\n")); + n += strlen("\tother = otherval\n"); + cl_assert_equal_strn(FOO_COMMENT, n, strlen(FOO_COMMENT)); + n += strlen(FOO_COMMENT); + + cl_assert_equal_strn(SECTION_BAR, n, strlen(SECTION_BAR)); + n += strlen(SECTION_BAR); + + cl_assert_equal_s("[newsection]\n\tnewname = new_value\n", n); + + git_str_dispose(&newfile); + git_config_free(cfg); +} + +void test_config_write__preserves_entry_with_name_only(void) +{ + const char *file_name = "config-empty-value"; + git_config *cfg; + git_str newfile = GIT_STR_INIT; + + /* Write the test config and make sure the expected entry exists */ + cl_git_mkfile(file_name, "[section \"foo\"]\n\tname\n"); + cl_git_pass(git_config_open_ondisk(&cfg, file_name)); + cl_git_pass(git_config_set_string(cfg, "newsection.newname", "new_value")); + cl_git_pass(git_config_set_string(cfg, "section.foo.other", "otherval")); + + cl_git_pass(git_futils_readbuffer(&newfile, file_name)); + cl_assert_equal_s("[section \"foo\"]\n\tname\n\tother = otherval\n[newsection]\n\tnewname = new_value\n", newfile.ptr); + + git_str_dispose(&newfile); + git_config_free(cfg); +} + +void test_config_write__to_empty_file(void) +{ + git_config *cfg; + const char *filename = "config-file"; + git_str result = GIT_STR_INIT; + + cl_git_mkfile(filename, ""); + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + cl_git_pass(git_config_set_string(cfg, "section.name", "value")); + git_config_free(cfg); + + cl_git_pass(git_futils_readbuffer(&result, "config-file")); + cl_assert_equal_s("[section]\n\tname = value\n", result.ptr); + + git_str_dispose(&result); +} + +void test_config_write__to_file_with_only_comment(void) +{ + git_config *cfg; + const char *filename = "config-file"; + git_str result = GIT_STR_INIT; + + cl_git_mkfile(filename, "\n\n"); + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + cl_git_pass(git_config_set_string(cfg, "section.name", "value")); + git_config_free(cfg); + + cl_git_pass(git_futils_readbuffer(&result, "config-file")); + cl_assert_equal_s("\n\n[section]\n\tname = value\n", result.ptr); + + git_str_dispose(&result); +} + +void test_config_write__locking(void) +{ + git_config *cfg, *cfg2; + git_config_entry *entry; + git_transaction *tx; + const char *filename = "locked-file"; + + /* Open the config and lock it */ + cl_git_mkfile(filename, "[section]\n\tname = value\n"); + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); + cl_assert_equal_s("value", entry->value); + git_config_entry_free(entry); + cl_git_pass(git_config_lock(&tx, cfg)); + + /* Change entries in the locked backend */ + cl_git_pass(git_config_set_string(cfg, "section.name", "other value")); + cl_git_pass(git_config_set_string(cfg, "section2.name3", "more value")); + + /* We can see that the file we read from hasn't changed */ + cl_git_pass(git_config_open_ondisk(&cfg2, filename)); + cl_git_pass(git_config_get_entry(&entry, cfg2, "section.name")); + cl_assert_equal_s("value", entry->value); + git_config_entry_free(entry); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_entry(&entry, cfg2, "section2.name3")); + git_config_free(cfg2); + + /* And we also get the old view when we read from the locked config */ + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); + cl_assert_equal_s("value", entry->value); + git_config_entry_free(entry); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_entry(&entry, cfg, "section2.name3")); + + cl_git_pass(git_transaction_commit(tx)); + git_transaction_free(tx); + + /* Now that we've unlocked it, we should see both updates */ + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); + cl_assert_equal_s("other value", entry->value); + git_config_entry_free(entry); + cl_git_pass(git_config_get_entry(&entry, cfg, "section2.name3")); + cl_assert_equal_s("more value", entry->value); + git_config_entry_free(entry); + + git_config_free(cfg); + + /* We should also see the changes after reopening the config */ + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); + cl_assert_equal_s("other value", entry->value); + git_config_entry_free(entry); + cl_git_pass(git_config_get_entry(&entry, cfg, "section2.name3")); + cl_assert_equal_s("more value", entry->value); + git_config_entry_free(entry); + + git_config_free(cfg); +} + +void test_config_write__repeated(void) +{ + const char *filename = "config-repeated"; + git_config *cfg; + git_str result = GIT_STR_INIT; + const char *expected = "[sample \"prefix\"]\n\ +\tsetting1 = someValue1\n\ +\tsetting2 = someValue2\n\ +\tsetting3 = someValue3\n\ +\tsetting4 = someValue4\n\ +"; + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + cl_git_pass(git_config_set_string(cfg, "sample.prefix.setting1", "someValue1")); + cl_git_pass(git_config_set_string(cfg, "sample.prefix.setting2", "someValue2")); + cl_git_pass(git_config_set_string(cfg, "sample.prefix.setting3", "someValue3")); + cl_git_pass(git_config_set_string(cfg, "sample.prefix.setting4", "someValue4")); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + + cl_git_pass(git_futils_readbuffer(&result, filename)); + cl_assert_equal_s(expected, result.ptr); + git_str_dispose(&result); + + git_config_free(cfg); +} + +void test_config_write__preserve_case(void) +{ + const char *filename = "config-preserve-case"; + git_config *cfg; + git_str result = GIT_STR_INIT; + const char *expected = "[sOMe]\n" \ + "\tThInG = foo\n" \ + "\tOtheR = thing\n"; + + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + cl_git_pass(git_config_set_string(cfg, "sOMe.ThInG", "foo")); + cl_git_pass(git_config_set_string(cfg, "SomE.OtheR", "thing")); + git_config_free(cfg); + + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + + cl_git_pass(git_futils_readbuffer(&result, filename)); + cl_assert_equal_s(expected, result.ptr); + git_str_dispose(&result); + + git_config_free(cfg); +} + +void test_config_write__write_config_file_with_multi_line_value(void) +{ + git_config* cfg; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_config_open_ondisk(&cfg, "config22")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "alias.m")); + cl_assert_equal_s("cmd ;; ;; bar", buf.ptr); + cl_git_pass(git_config_set_string(cfg, "sOMe.ThInG", "foo")); + git_buf_dispose(&buf); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "alias.m")); + cl_assert_equal_s("cmd ;; ;; bar", buf.ptr); + git_buf_dispose(&buf); + + git_config_free(cfg); +} diff --git a/tests/libgit2/core/array.c b/tests/libgit2/core/array.c new file mode 100644 index 000000000..8e626a506 --- /dev/null +++ b/tests/libgit2/core/array.c @@ -0,0 +1,57 @@ +#include "clar_libgit2.h" +#include "array.h" + +static int int_lookup(const void *k, const void *a) +{ + const int *one = (const int *)k; + int *two = (int *)a; + + return *one - *two; +} + +#define expect_pos(k, n, ret) \ + key = (k); \ + cl_assert_equal_i((ret), \ + git_array_search(&p, integers, int_lookup, &key)); \ + cl_assert_equal_i((n), p); + +void test_core_array__bsearch2(void) +{ + git_array_t(int) integers = GIT_ARRAY_INIT; + int *i, key; + size_t p; + + i = git_array_alloc(integers); *i = 2; + i = git_array_alloc(integers); *i = 3; + i = git_array_alloc(integers); *i = 5; + i = git_array_alloc(integers); *i = 7; + i = git_array_alloc(integers); *i = 7; + i = git_array_alloc(integers); *i = 8; + i = git_array_alloc(integers); *i = 13; + i = git_array_alloc(integers); *i = 21; + i = git_array_alloc(integers); *i = 25; + i = git_array_alloc(integers); *i = 42; + i = git_array_alloc(integers); *i = 69; + i = git_array_alloc(integers); *i = 121; + i = git_array_alloc(integers); *i = 256; + i = git_array_alloc(integers); *i = 512; + i = git_array_alloc(integers); *i = 513; + i = git_array_alloc(integers); *i = 514; + i = git_array_alloc(integers); *i = 516; + i = git_array_alloc(integers); *i = 516; + i = git_array_alloc(integers); *i = 517; + + /* value to search for, expected position, return code */ + expect_pos(3, 1, GIT_OK); + expect_pos(2, 0, GIT_OK); + expect_pos(1, 0, GIT_ENOTFOUND); + expect_pos(25, 8, GIT_OK); + expect_pos(26, 9, GIT_ENOTFOUND); + expect_pos(42, 9, GIT_OK); + expect_pos(50, 10, GIT_ENOTFOUND); + expect_pos(68, 10, GIT_ENOTFOUND); + expect_pos(256, 12, GIT_OK); + + git_array_clear(integers); +} + diff --git a/tests/libgit2/core/assert.c b/tests/libgit2/core/assert.c new file mode 100644 index 000000000..ef75624b9 --- /dev/null +++ b/tests/libgit2/core/assert.c @@ -0,0 +1,94 @@ +#ifdef GIT_ASSERT_HARD +# undef GIT_ASSERT_HARD +#endif + +#define GIT_ASSERT_HARD 0 + +#include "clar_libgit2.h" + +static const char *hello_world = "hello, world"; +static const char *fail = "FAIL"; + +static int dummy_fn(const char *myarg) +{ + GIT_ASSERT_ARG(myarg); + GIT_ASSERT_ARG(myarg != hello_world); + return 0; +} + +static const char *fn_returns_string(const char *myarg) +{ + GIT_ASSERT_ARG_WITH_RETVAL(myarg, fail); + GIT_ASSERT_ARG_WITH_RETVAL(myarg != hello_world, fail); + + return myarg; +} + +static int bad_math(void) +{ + GIT_ASSERT(1 + 1 == 3); + return 42; +} + +static const char *bad_returns_string(void) +{ + GIT_ASSERT_WITH_RETVAL(1 + 1 == 3, NULL); + return hello_world; +} + +void test_core_assert__argument(void) +{ + cl_git_fail(dummy_fn(NULL)); + cl_assert(git_error_last()); + cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); + cl_assert_equal_s("invalid argument: 'myarg'", git_error_last()->message); + + cl_git_fail(dummy_fn(hello_world)); + cl_assert(git_error_last()); + cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); + cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message); + + cl_git_pass(dummy_fn("foo")); +} + +void test_core_assert__argument_with_non_int_return_type(void) +{ + const char *foo = "foo"; + + cl_assert_equal_p(fail, fn_returns_string(NULL)); + cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); + cl_assert_equal_s("invalid argument: 'myarg'", git_error_last()->message); + + cl_assert_equal_p(fail, fn_returns_string(hello_world)); + cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); + cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message); + + cl_assert_equal_p(foo, fn_returns_string(foo)); +} + +void test_core_assert__argument_with_void_return_type(void) +{ + const char *foo = "foo"; + + git_error_clear(); + fn_returns_string(hello_world); + cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); + cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message); + + git_error_clear(); + cl_assert_equal_p(foo, fn_returns_string(foo)); + cl_assert_equal_p(NULL, git_error_last()); +} + +void test_core_assert__internal(void) +{ + cl_git_fail(bad_math()); + cl_assert(git_error_last()); + cl_assert_equal_i(GIT_ERROR_INTERNAL, git_error_last()->klass); + cl_assert_equal_s("unrecoverable internal error: '1 + 1 == 3'", git_error_last()->message); + + cl_assert_equal_p(NULL, bad_returns_string()); + cl_assert(git_error_last()); + cl_assert_equal_i(GIT_ERROR_INTERNAL, git_error_last()->klass); + cl_assert_equal_s("unrecoverable internal error: '1 + 1 == 3'", git_error_last()->message); +} diff --git a/tests/libgit2/core/bitvec.c b/tests/libgit2/core/bitvec.c new file mode 100644 index 000000000..48d7b99f0 --- /dev/null +++ b/tests/libgit2/core/bitvec.c @@ -0,0 +1,64 @@ +#include "clar_libgit2.h" +#include "bitvec.h" + +#if 0 +static void print_bitvec(git_bitvec *bv) +{ + int b; + + if (!bv->length) { + for (b = 63; b >= 0; --b) + fprintf(stderr, "%d", (bv->u.bits & (1ul << b)) ? 1 : 0); + } else { + for (b = bv->length * 8; b >= 0; --b) + fprintf(stderr, "%d", (bv->u.ptr[b >> 3] & (b & 0x0ff)) ? 1 : 0); + } + fprintf(stderr, "\n"); +} +#endif + +static void set_some_bits(git_bitvec *bv, size_t length) +{ + size_t i; + + for (i = 0; i < length; ++i) { + if (i % 3 == 0 || i % 7 == 0) + git_bitvec_set(bv, i, true); + } +} + +static void check_some_bits(git_bitvec *bv, size_t length) +{ + size_t i; + + for (i = 0; i < length; ++i) + cl_assert_equal_b(i % 3 == 0 || i % 7 == 0, git_bitvec_get(bv, i)); +} + +void test_core_bitvec__0(void) +{ + git_bitvec bv; + + cl_git_pass(git_bitvec_init(&bv, 32)); + set_some_bits(&bv, 16); + check_some_bits(&bv, 16); + git_bitvec_clear(&bv); + set_some_bits(&bv, 32); + check_some_bits(&bv, 32); + git_bitvec_clear(&bv); + set_some_bits(&bv, 64); + check_some_bits(&bv, 64); + git_bitvec_free(&bv); + + cl_git_pass(git_bitvec_init(&bv, 128)); + set_some_bits(&bv, 32); + check_some_bits(&bv, 32); + set_some_bits(&bv, 128); + check_some_bits(&bv, 128); + git_bitvec_free(&bv); + + cl_git_pass(git_bitvec_init(&bv, 4000)); + set_some_bits(&bv, 4000); + check_some_bits(&bv, 4000); + git_bitvec_free(&bv); +} diff --git a/tests/libgit2/core/buf.c b/tests/libgit2/core/buf.c new file mode 100644 index 000000000..3959fa883 --- /dev/null +++ b/tests/libgit2/core/buf.c @@ -0,0 +1,54 @@ +#include "clar_libgit2.h" +#include "buf.h" + +void test_core_buf__sanitize(void) +{ + git_buf buf = { (char *)0x42, 0, 16 }; + + cl_git_pass(git_buf_sanitize(&buf)); + cl_assert_equal_s(buf.ptr, ""); + cl_assert_equal_i(buf.reserved, 0); + cl_assert_equal_i(buf.size, 0); + + git_buf_dispose(&buf); +} + +void test_core_buf__tostr(void) +{ + git_str str = GIT_STR_INIT; + git_buf buf = { (char *)0x42, 0, 16 }; + + cl_git_pass(git_buf_tostr(&str, &buf)); + + cl_assert_equal_s(buf.ptr, ""); + cl_assert_equal_i(buf.reserved, 0); + cl_assert_equal_i(buf.size, 0); + + cl_assert_equal_s(str.ptr, ""); + cl_assert_equal_i(str.asize, 0); + cl_assert_equal_i(str.size, 0); + + git_buf_dispose(&buf); + git_str_dispose(&str); +} + +void test_core_buf__fromstr(void) +{ + git_str str = GIT_STR_INIT; + git_buf buf = { (char *)0x42, 0, 16 }; + + cl_git_pass(git_buf_tostr(&str, &buf)); + cl_git_pass(git_str_puts(&str, "Hello, world.")); + cl_git_pass(git_buf_fromstr(&buf, &str)); + + cl_assert(buf.reserved > 14); + cl_assert_equal_i(buf.size, 13); + cl_assert_equal_s(buf.ptr, "Hello, world."); + + cl_assert_equal_s(str.ptr, ""); + cl_assert_equal_i(str.asize, 0); + cl_assert_equal_i(str.size, 0); + + git_buf_dispose(&buf); + git_str_dispose(&str); +} diff --git a/tests/libgit2/core/copy.c b/tests/libgit2/core/copy.c new file mode 100644 index 000000000..6d22b503d --- /dev/null +++ b/tests/libgit2/core/copy.c @@ -0,0 +1,153 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "posix.h" + +void test_core_copy__file(void) +{ + struct stat st; + const char *content = "This is some stuff to copy\n"; + + cl_git_mkfile("copy_me", content); + + cl_git_pass(git_futils_cp("copy_me", "copy_me_two", 0664)); + + cl_git_pass(git_fs_path_lstat("copy_me_two", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert(strlen(content) == (size_t)st.st_size); + + cl_git_pass(p_unlink("copy_me_two")); + cl_git_pass(p_unlink("copy_me")); +} + +void test_core_copy__file_in_dir(void) +{ + struct stat st; + const char *content = "This is some other stuff to copy\n"; + + cl_git_pass(git_futils_mkdir("an_dir/in_a_dir", 0775, GIT_MKDIR_PATH)); + cl_git_mkfile("an_dir/in_a_dir/copy_me", content); + cl_assert(git_fs_path_isdir("an_dir")); + + cl_git_pass(git_futils_mkpath2file + ("an_dir/second_dir/and_more/copy_me_two", 0775)); + + cl_git_pass(git_futils_cp + ("an_dir/in_a_dir/copy_me", + "an_dir/second_dir/and_more/copy_me_two", + 0664)); + + cl_git_pass(git_fs_path_lstat("an_dir/second_dir/and_more/copy_me_two", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert(strlen(content) == (size_t)st.st_size); + + cl_git_pass(git_futils_rmdir_r("an_dir", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_assert(!git_fs_path_isdir("an_dir")); +} + +#ifndef GIT_WIN32 +static void assert_hard_link(const char *path) +{ + /* we assert this by checking that there's more than one link to the file */ + struct stat st; + + cl_assert(git_fs_path_isfile(path)); + cl_git_pass(p_stat(path, &st)); + cl_assert(st.st_nlink > 1); +} +#endif + +void test_core_copy__tree(void) +{ + struct stat st; + const char *content = "File content\n"; + + cl_git_pass(git_futils_mkdir("src/b", 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("src/c/d", 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("src/c/e", 0775, GIT_MKDIR_PATH)); + + cl_git_mkfile("src/f1", content); + cl_git_mkfile("src/b/f2", content); + cl_git_mkfile("src/c/f3", content); + cl_git_mkfile("src/c/d/f4", content); + cl_git_mkfile("src/c/d/.f5", content); + +#ifndef GIT_WIN32 + cl_assert(p_symlink("../../b/f2", "src/c/d/l1") == 0); +#endif + + cl_assert(git_fs_path_isdir("src")); + cl_assert(git_fs_path_isdir("src/b")); + cl_assert(git_fs_path_isdir("src/c/d")); + cl_assert(git_fs_path_isfile("src/c/d/f4")); + + /* copy with no empty dirs, yes links, no dotfiles, no overwrite */ + + cl_git_pass( + git_futils_cp_r("src", "t1", GIT_CPDIR_COPY_SYMLINKS, 0) ); + + cl_assert(git_fs_path_isdir("t1")); + cl_assert(git_fs_path_isdir("t1/b")); + cl_assert(git_fs_path_isdir("t1/c")); + cl_assert(git_fs_path_isdir("t1/c/d")); + cl_assert(!git_fs_path_isdir("t1/c/e")); + + cl_assert(git_fs_path_isfile("t1/f1")); + cl_assert(git_fs_path_isfile("t1/b/f2")); + cl_assert(git_fs_path_isfile("t1/c/f3")); + cl_assert(git_fs_path_isfile("t1/c/d/f4")); + cl_assert(!git_fs_path_isfile("t1/c/d/.f5")); + + cl_git_pass(git_fs_path_lstat("t1/c/f3", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert(strlen(content) == (size_t)st.st_size); + +#ifndef GIT_WIN32 + cl_git_pass(git_fs_path_lstat("t1/c/d/l1", &st)); + cl_assert(S_ISLNK(st.st_mode)); +#endif + + cl_git_pass(git_futils_rmdir_r("t1", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_assert(!git_fs_path_isdir("t1")); + + /* copy with empty dirs, no links, yes dotfiles, no overwrite */ + + cl_git_pass( + git_futils_cp_r("src", "t2", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_COPY_DOTFILES, 0) ); + + cl_assert(git_fs_path_isdir("t2")); + cl_assert(git_fs_path_isdir("t2/b")); + cl_assert(git_fs_path_isdir("t2/c")); + cl_assert(git_fs_path_isdir("t2/c/d")); + cl_assert(git_fs_path_isdir("t2/c/e")); + + cl_assert(git_fs_path_isfile("t2/f1")); + cl_assert(git_fs_path_isfile("t2/b/f2")); + cl_assert(git_fs_path_isfile("t2/c/f3")); + cl_assert(git_fs_path_isfile("t2/c/d/f4")); + cl_assert(git_fs_path_isfile("t2/c/d/.f5")); + +#ifndef GIT_WIN32 + cl_git_fail(git_fs_path_lstat("t2/c/d/l1", &st)); +#endif + + cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_assert(!git_fs_path_isdir("t2")); + +#ifndef GIT_WIN32 + cl_git_pass(git_futils_cp_r("src", "t3", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_LINK_FILES, 0)); + cl_assert(git_fs_path_isdir("t3")); + + cl_assert(git_fs_path_isdir("t3")); + cl_assert(git_fs_path_isdir("t3/b")); + cl_assert(git_fs_path_isdir("t3/c")); + cl_assert(git_fs_path_isdir("t3/c/d")); + cl_assert(git_fs_path_isdir("t3/c/e")); + + assert_hard_link("t3/f1"); + assert_hard_link("t3/b/f2"); + assert_hard_link("t3/c/f3"); + assert_hard_link("t3/c/d/f4"); +#endif + + cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_RMDIR_REMOVE_FILES)); +} diff --git a/tests/libgit2/core/dirent.c b/tests/libgit2/core/dirent.c new file mode 100644 index 000000000..2419ec7ab --- /dev/null +++ b/tests/libgit2/core/dirent.c @@ -0,0 +1,306 @@ +#include "clar_libgit2.h" +#include "futils.h" + +typedef struct name_data { + int count; /* return count */ + char *name; /* filename */ +} name_data; + +typedef struct walk_data { + char *sub; /* sub-directory name */ + name_data *names; /* name state data */ + git_str path; +} walk_data; + + +static char *top_dir = "dir-walk"; +static walk_data *state_loc; + +static void setup(walk_data *d) +{ + name_data *n; + + cl_must_pass(p_mkdir(top_dir, 0777)); + + cl_must_pass(p_chdir(top_dir)); + + if (strcmp(d->sub, ".") != 0) + cl_must_pass(p_mkdir(d->sub, 0777)); + + cl_git_pass(git_str_sets(&d->path, d->sub)); + + state_loc = d; + + for (n = d->names; n->name; n++) { + git_file fd = p_creat(n->name, 0666); + cl_assert(fd >= 0); + p_close(fd); + n->count = 0; + } +} + +static void dirent_cleanup__cb(void *_d) +{ + walk_data *d = _d; + name_data *n; + + for (n = d->names; n->name; n++) { + cl_must_pass(p_unlink(n->name)); + } + + if (strcmp(d->sub, ".") != 0) + cl_must_pass(p_rmdir(d->sub)); + + cl_must_pass(p_chdir("..")); + + cl_must_pass(p_rmdir(top_dir)); + + git_str_dispose(&d->path); +} + +static void check_counts(walk_data *d) +{ + name_data *n; + + for (n = d->names; n->name; n++) { + cl_assert(n->count == 1); + } +} + +static int update_count(name_data *data, const char *name) +{ + name_data *n; + + for (n = data; n->name; n++) { + if (!strcmp(n->name, name)) { + n->count++; + return 0; + } + } + + return GIT_ERROR; +} + +static int one_entry(void *state, git_str *path) +{ + walk_data *d = (walk_data *) state; + + if (state != state_loc) + return GIT_ERROR; + + if (path != &d->path) + return GIT_ERROR; + + return update_count(d->names, path->ptr); +} + + +static name_data dot_names[] = { + { 0, "./a" }, + { 0, "./asdf" }, + { 0, "./pack-foo.pack" }, + { 0, NULL } +}; +static walk_data dot = { + ".", + dot_names, + GIT_STR_INIT +}; + +/* make sure that the '.' folder is not traversed */ +void test_core_dirent__dont_traverse_dot(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &dot); + setup(&dot); + + cl_git_pass(git_fs_path_direach(&dot.path, 0, one_entry, &dot)); + + check_counts(&dot); +} + + +static name_data sub_names[] = { + { 0, "sub/a" }, + { 0, "sub/asdf" }, + { 0, "sub/pack-foo.pack" }, + { 0, NULL } +}; +static walk_data sub = { + "sub", + sub_names, + GIT_STR_INIT +}; + +/* traverse a subfolder */ +void test_core_dirent__traverse_subfolder(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &sub); + setup(&sub); + + cl_git_pass(git_fs_path_direach(&sub.path, 0, one_entry, &sub)); + + check_counts(&sub); +} + + +static walk_data sub_slash = { + "sub/", + sub_names, + GIT_STR_INIT +}; + +/* traverse a slash-terminated subfolder */ +void test_core_dirent__traverse_slash_terminated_folder(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &sub_slash); + setup(&sub_slash); + + cl_git_pass(git_fs_path_direach(&sub_slash.path, 0, one_entry, &sub_slash)); + + check_counts(&sub_slash); +} + + +static name_data empty_names[] = { + { 0, NULL } +}; +static walk_data empty = { + "empty", + empty_names, + GIT_STR_INIT +}; + +/* make sure that empty folders are not traversed */ +void test_core_dirent__dont_traverse_empty_folders(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &empty); + setup(&empty); + + cl_git_pass(git_fs_path_direach(&empty.path, 0, one_entry, &empty)); + + check_counts(&empty); + + /* make sure callback not called */ + cl_assert(git_fs_path_is_empty_dir(empty.path.ptr)); +} + +static name_data odd_names[] = { + { 0, "odd/.a" }, + { 0, "odd/..c" }, + /* the following don't work on cygwin/win32 */ + /* { 0, "odd/.b." }, */ + /* { 0, "odd/..d.." }, */ + { 0, NULL } +}; +static walk_data odd = { + "odd", + odd_names, + GIT_STR_INIT +}; + +/* make sure that strange looking filenames ('..c') are traversed */ +void test_core_dirent__traverse_weird_filenames(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &odd); + setup(&odd); + + cl_git_pass(git_fs_path_direach(&odd.path, 0, one_entry, &odd)); + + check_counts(&odd); +} + +/* test filename length limits */ +void test_core_dirent__length_limits(void) +{ + char *big_filename = (char *)git__malloc(FILENAME_MAX + 1); + memset(big_filename, 'a', FILENAME_MAX + 1); + big_filename[FILENAME_MAX] = 0; + + cl_must_fail(p_creat(big_filename, 0666)); + + git__free(big_filename); +} + +void test_core_dirent__empty_dir(void) +{ + cl_must_pass(p_mkdir("empty_dir", 0777)); + cl_assert(git_fs_path_is_empty_dir("empty_dir")); + + cl_git_mkfile("empty_dir/content", "whatever\n"); + cl_assert(!git_fs_path_is_empty_dir("empty_dir")); + cl_assert(!git_fs_path_is_empty_dir("empty_dir/content")); + + cl_must_pass(p_unlink("empty_dir/content")); + + cl_must_pass(p_mkdir("empty_dir/content", 0777)); + cl_assert(!git_fs_path_is_empty_dir("empty_dir")); + cl_assert(git_fs_path_is_empty_dir("empty_dir/content")); + + cl_must_pass(p_rmdir("empty_dir/content")); + + cl_must_pass(p_rmdir("empty_dir")); +} + +static void handle_next(git_fs_path_diriter *diriter, walk_data *walk) +{ + const char *fullpath, *filename; + size_t fullpath_len, filename_len; + + cl_git_pass(git_fs_path_diriter_fullpath(&fullpath, &fullpath_len, diriter)); + cl_git_pass(git_fs_path_diriter_filename(&filename, &filename_len, diriter)); + + cl_assert_equal_strn(fullpath, "sub/", 4); + cl_assert_equal_s(fullpath+4, filename); + + update_count(walk->names, fullpath); +} + +/* test directory iterator */ +void test_core_dirent__diriter_with_fullname(void) +{ + git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT; + int error; + + cl_set_cleanup(&dirent_cleanup__cb, &sub); + setup(&sub); + + cl_git_pass(git_fs_path_diriter_init(&diriter, sub.path.ptr, 0)); + + while ((error = git_fs_path_diriter_next(&diriter)) == 0) + handle_next(&diriter, &sub); + + cl_assert_equal_i(error, GIT_ITEROVER); + + git_fs_path_diriter_free(&diriter); + + check_counts(&sub); +} + +void test_core_dirent__diriter_at_directory_root(void) +{ + git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT; + const char *sandbox_path, *path; + char *root_path; + size_t path_len; + int root_offset, error; + + sandbox_path = clar_sandbox_path(); + cl_assert((root_offset = git_fs_path_root(sandbox_path)) >= 0); + + cl_assert(root_path = git__calloc(1, root_offset + 2)); + strncpy(root_path, sandbox_path, root_offset + 1); + + cl_git_pass(git_fs_path_diriter_init(&diriter, root_path, 0)); + + while ((error = git_fs_path_diriter_next(&diriter)) == 0) { + cl_git_pass(git_fs_path_diriter_fullpath(&path, &path_len, &diriter)); + + cl_assert(path_len > (size_t)(root_offset + 1)); + cl_assert(path[root_offset+1] != '/'); + } + + cl_assert_equal_i(error, GIT_ITEROVER); + + git_fs_path_diriter_free(&diriter); + git__free(root_path); +} diff --git a/tests/libgit2/core/encoding.c b/tests/libgit2/core/encoding.c new file mode 100644 index 000000000..6cec24679 --- /dev/null +++ b/tests/libgit2/core/encoding.c @@ -0,0 +1,42 @@ +#include "clar_libgit2.h" +#include "varint.h" + +void test_core_encoding__decode(void) +{ + const unsigned char *buf = (unsigned char *)"AB"; + size_t size; + + cl_assert(git_decode_varint(buf, &size) == 65); + cl_assert(size == 1); + + buf = (unsigned char *)"\xfe\xdc\xbaXY"; + cl_assert(git_decode_varint(buf, &size) == 267869656); + cl_assert(size == 4); + + buf = (unsigned char *)"\xaa\xaa\xfe\xdc\xbaXY"; + cl_assert(git_decode_varint(buf, &size) == UINT64_C(1489279344088)); + cl_assert(size == 6); + + buf = (unsigned char *)"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xfe\xdc\xbaXY"; + cl_assert(git_decode_varint(buf, &size) == 0); + cl_assert(size == 0); + +} + +void test_core_encoding__encode(void) +{ + unsigned char buf[100]; + cl_assert(git_encode_varint(buf, 100, 65) == 1); + cl_assert(buf[0] == 'A'); + + cl_assert(git_encode_varint(buf, 1, 1) == 1); + cl_assert(!memcmp(buf, "\x01", 1)); + + cl_assert(git_encode_varint(buf, 100, 267869656) == 4); + cl_assert(!memcmp(buf, "\xfe\xdc\xbaX", 4)); + + cl_assert(git_encode_varint(buf, 100, UINT64_C(1489279344088)) == 6); + cl_assert(!memcmp(buf, "\xaa\xaa\xfe\xdc\xbaX", 6)); + + cl_assert(git_encode_varint(buf, 1, UINT64_C(1489279344088)) == -1); +} diff --git a/tests/libgit2/core/env.c b/tests/libgit2/core/env.c new file mode 100644 index 000000000..8ba9b9124 --- /dev/null +++ b/tests/libgit2/core/env.c @@ -0,0 +1,320 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "sysdir.h" + +#ifdef GIT_WIN32 +#define NUM_VARS 5 +static const char *env_vars[NUM_VARS] = { + "HOME", "HOMEDRIVE", "HOMEPATH", "USERPROFILE", "PROGRAMFILES" +}; +#else +#define NUM_VARS 1 +static const char *env_vars[NUM_VARS] = { "HOME" }; +#endif + +static char *env_save[NUM_VARS]; + +static char *home_values[] = { + "fake_home", + "f\xc3\xa1ke_h\xc3\xb5me", /* all in latin-1 supplement */ + "f\xc4\x80ke_\xc4\xa4ome", /* latin extended */ + "f\xce\xb1\xce\xba\xce\xb5_h\xce\xbfm\xce\xad", /* having fun with greek */ + "fa\xe0" "\xb8" "\x87" "e_\xe0" "\xb8" "\x99" "ome", /* thai characters */ + "f\xe1\x9c\x80ke_\xe1\x9c\x91ome", /* tagalog characters */ + "\xe1\xb8\x9f\xe1\xba\xa2" "ke_ho" "\xe1" "\xb9" "\x81" "e", /* latin extended additional */ + "\xf0\x9f\x98\x98\xf0\x9f\x98\x82", /* emoticons */ + NULL +}; + +void test_core_env__initialize(void) +{ + int i; + for (i = 0; i < NUM_VARS; ++i) + env_save[i] = cl_getenv(env_vars[i]); +} + +static void set_global_search_path_from_env(void) +{ + cl_git_pass(git_sysdir_set(GIT_SYSDIR_GLOBAL, NULL)); +} + +static void set_system_search_path_from_env(void) +{ + cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, NULL)); +} + +void test_core_env__cleanup(void) +{ + int i; + char **val; + + for (i = 0; i < NUM_VARS; ++i) { + cl_setenv(env_vars[i], env_save[i]); + git__free(env_save[i]); + env_save[i] = NULL; + } + + /* these will probably have already been cleaned up, but if a test + * fails, then it's probably good to try and clear out these dirs + */ + for (val = home_values; *val != NULL; val++) { + if (**val != '\0') + (void)p_rmdir(*val); + } + + cl_sandbox_set_search_path_defaults(); +} + +static void setenv_and_check(const char *name, const char *value) +{ + char *check; + + cl_git_pass(cl_setenv(name, value)); + check = cl_getenv(name); + + if (value) + cl_assert_equal_s(value, check); + else + cl_assert(check == NULL); + + git__free(check); +} + +void test_core_env__0(void) +{ + git_str path = GIT_STR_INIT, found = GIT_STR_INIT; + char testfile[16], tidx = '0'; + char **val; + const char *testname = "testfile"; + size_t testlen = strlen(testname); + + strncpy(testfile, testname, sizeof(testfile)); + cl_assert_equal_s(testname, testfile); + + for (val = home_values; *val != NULL; val++) { + + /* if we can't make the directory, let's just assume + * we are on a filesystem that doesn't support the + * characters in question and skip this test... + */ + if (p_mkdir(*val, 0777) != 0) { + *val = ""; /* mark as not created */ + continue; + } + + cl_git_pass(git_fs_path_prettify(&path, *val, NULL)); + + /* vary testfile name in each directory so accidentally leaving + * an environment variable set from a previous iteration won't + * accidentally make this test pass... + */ + testfile[testlen] = tidx++; + cl_git_pass(git_str_joinpath(&path, path.ptr, testfile)); + cl_git_mkfile(path.ptr, "find me"); + git_str_rtruncate_at_char(&path, '/'); + + cl_assert_equal_i( + GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); + + setenv_and_check("HOME", path.ptr); + set_global_search_path_from_env(); + + cl_git_pass(git_sysdir_find_global_file(&found, testfile)); + + cl_setenv("HOME", env_save[0]); + set_global_search_path_from_env(); + + cl_assert_equal_i( + GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); + +#ifdef GIT_WIN32 + setenv_and_check("HOMEDRIVE", NULL); + setenv_and_check("HOMEPATH", NULL); + setenv_and_check("USERPROFILE", path.ptr); + set_global_search_path_from_env(); + + cl_git_pass(git_sysdir_find_global_file(&found, testfile)); + + { + int root = git_fs_path_root(path.ptr); + char old; + + if (root >= 0) { + setenv_and_check("USERPROFILE", NULL); + set_global_search_path_from_env(); + + cl_assert_equal_i( + GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); + + old = path.ptr[root]; + path.ptr[root] = '\0'; + setenv_and_check("HOMEDRIVE", path.ptr); + path.ptr[root] = old; + setenv_and_check("HOMEPATH", &path.ptr[root]); + set_global_search_path_from_env(); + + cl_git_pass(git_sysdir_find_global_file(&found, testfile)); + } + } +#endif + + (void)p_rmdir(*val); + } + + git_str_dispose(&path); + git_str_dispose(&found); +} + + +void test_core_env__1(void) +{ + git_str path = GIT_STR_INIT; + + cl_assert_equal_i( + GIT_ENOTFOUND, git_sysdir_find_global_file(&path, "nonexistentfile")); + + cl_git_pass(cl_setenv("HOME", "doesnotexist")); +#ifdef GIT_WIN32 + cl_git_pass(cl_setenv("HOMEPATH", "doesnotexist")); + cl_git_pass(cl_setenv("USERPROFILE", "doesnotexist")); +#endif + set_global_search_path_from_env(); + + cl_assert_equal_i( + GIT_ENOTFOUND, git_sysdir_find_global_file(&path, "nonexistentfile")); + + cl_git_pass(cl_setenv("HOME", NULL)); +#ifdef GIT_WIN32 + cl_git_pass(cl_setenv("HOMEPATH", NULL)); + cl_git_pass(cl_setenv("USERPROFILE", NULL)); +#endif + set_global_search_path_from_env(); + set_system_search_path_from_env(); + + cl_assert_equal_i( + GIT_ENOTFOUND, git_sysdir_find_global_file(&path, "nonexistentfile")); + + cl_assert_equal_i( + GIT_ENOTFOUND, git_sysdir_find_system_file(&path, "nonexistentfile")); + +#ifdef GIT_WIN32 + cl_git_pass(cl_setenv("PROGRAMFILES", NULL)); + set_system_search_path_from_env(); + + cl_assert_equal_i( + GIT_ENOTFOUND, git_sysdir_find_system_file(&path, "nonexistentfile")); +#endif + + git_str_dispose(&path); +} + +static void check_global_searchpath( + const char *path, int position, const char *file, git_str *temp) +{ + git_str out = GIT_STR_INIT; + + /* build and set new path */ + if (position < 0) + cl_git_pass(git_str_join(temp, GIT_PATH_LIST_SEPARATOR, path, "$PATH")); + else if (position > 0) + cl_git_pass(git_str_join(temp, GIT_PATH_LIST_SEPARATOR, "$PATH", path)); + else + cl_git_pass(git_str_sets(temp, path)); + + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, temp->ptr)); + + /* get path and make sure $PATH expansion worked */ + cl_git_pass(git_libgit2_opts( + GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &out)); + + if (position < 0) + cl_assert(git__prefixcmp(out.ptr, path) == 0); + else if (position > 0) + cl_assert(git__suffixcmp(out.ptr, path) == 0); + else + cl_assert_equal_s(out.ptr, path); + + /* find file using new path */ + cl_git_pass(git_sysdir_find_global_file(temp, file)); + + /* reset path and confirm file not found */ + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, NULL)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sysdir_find_global_file(temp, file)); + + git_str_dispose(&out); +} + +void test_core_env__2(void) +{ + git_str path = GIT_STR_INIT, found = GIT_STR_INIT; + char testfile[16], tidx = '0'; + char **val; + const char *testname = "alternate"; + size_t testlen = strlen(testname); + + strncpy(testfile, testname, sizeof(testfile)); + cl_assert_equal_s(testname, testfile); + + for (val = home_values; *val != NULL; val++) { + + /* if we can't make the directory, let's just assume + * we are on a filesystem that doesn't support the + * characters in question and skip this test... + */ + if (p_mkdir(*val, 0777) != 0 && errno != EEXIST) { + *val = ""; /* mark as not created */ + continue; + } + + cl_git_pass(git_fs_path_prettify(&path, *val, NULL)); + + /* vary testfile name so any sloppiness is resetting variables or + * deleting files won't accidentally make a test pass. + */ + testfile[testlen] = tidx++; + cl_git_pass(git_str_joinpath(&path, path.ptr, testfile)); + cl_git_mkfile(path.ptr, "find me"); + git_str_rtruncate_at_char(&path, '/'); + + /* default should be NOTFOUND */ + cl_assert_equal_i( + GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); + + /* try plain, append $PATH, and prepend $PATH */ + check_global_searchpath(path.ptr, 0, testfile, &found); + check_global_searchpath(path.ptr, -1, testfile, &found); + check_global_searchpath(path.ptr, 1, testfile, &found); + + /* cleanup */ + cl_git_pass(git_str_joinpath(&path, path.ptr, testfile)); + (void)p_unlink(path.ptr); + (void)p_rmdir(*val); + } + + git_str_dispose(&path); + git_str_dispose(&found); +} + +void test_core_env__substitution(void) +{ + git_str buf = GIT_STR_INIT, expected = GIT_STR_INIT; + + /* Set it to something non-default so we have controllable values */ + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, "/tmp/a")); + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &buf)); + cl_assert_equal_s("/tmp/a", buf.ptr); + + git_str_clear(&buf); + cl_git_pass(git_str_join(&buf, GIT_PATH_LIST_SEPARATOR, "$PATH", "/tmp/b")); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, buf.ptr)); + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &buf)); + + cl_git_pass(git_str_join(&expected, GIT_PATH_LIST_SEPARATOR, "/tmp/a", "/tmp/b")); + cl_assert_equal_s(expected.ptr, buf.ptr); + + git_str_dispose(&expected); + git_str_dispose(&buf); +} diff --git a/tests/libgit2/core/errors.c b/tests/libgit2/core/errors.c new file mode 100644 index 000000000..386ecdc5f --- /dev/null +++ b/tests/libgit2/core/errors.c @@ -0,0 +1,222 @@ +#include "clar_libgit2.h" + +void test_core_errors__public_api(void) +{ + char *str_in_error; + + git_error_clear(); + cl_assert(git_error_last() == NULL); + + git_error_set_oom(); + + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); + str_in_error = strstr(git_error_last()->message, "memory"); + cl_assert(str_in_error != NULL); + + git_error_clear(); + + git_error_set_str(GIT_ERROR_REPOSITORY, "This is a test"); + + cl_assert(git_error_last() != NULL); + str_in_error = strstr(git_error_last()->message, "This is a test"); + cl_assert(str_in_error != NULL); + + git_error_clear(); + cl_assert(git_error_last() == NULL); +} + +#include "common.h" +#include "util.h" +#include "posix.h" + +void test_core_errors__new_school(void) +{ + char *str_in_error; + + git_error_clear(); + cl_assert(git_error_last() == NULL); + + git_error_set_oom(); /* internal fn */ + + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); + str_in_error = strstr(git_error_last()->message, "memory"); + cl_assert(str_in_error != NULL); + + git_error_clear(); + + git_error_set(GIT_ERROR_REPOSITORY, "This is a test"); /* internal fn */ + + cl_assert(git_error_last() != NULL); + str_in_error = strstr(git_error_last()->message, "This is a test"); + cl_assert(str_in_error != NULL); + + git_error_clear(); + cl_assert(git_error_last() == NULL); + + do { + struct stat st; + memset(&st, 0, sizeof(st)); + cl_assert(p_lstat("this_file_does_not_exist", &st) < 0); + GIT_UNUSED(st); + } while (false); + git_error_set(GIT_ERROR_OS, "stat failed"); /* internal fn */ + + cl_assert(git_error_last() != NULL); + str_in_error = strstr(git_error_last()->message, "stat failed"); + cl_assert(str_in_error != NULL); + cl_assert(git__prefixcmp(str_in_error, "stat failed: ") == 0); + cl_assert(strlen(str_in_error) > strlen("stat failed: ")); + +#ifdef GIT_WIN32 + git_error_clear(); + + /* The MSDN docs use this to generate a sample error */ + cl_assert(GetProcessId(NULL) == 0); + git_error_set(GIT_ERROR_OS, "GetProcessId failed"); /* internal fn */ + + cl_assert(git_error_last() != NULL); + str_in_error = strstr(git_error_last()->message, "GetProcessId failed"); + cl_assert(str_in_error != NULL); + cl_assert(git__prefixcmp(str_in_error, "GetProcessId failed: ") == 0); + cl_assert(strlen(str_in_error) > strlen("GetProcessId failed: ")); +#endif + + git_error_clear(); +} + +void test_core_errors__restore(void) +{ + git_error_state err_state = {0}; + + git_error_clear(); + cl_assert(git_error_last() == NULL); + + cl_assert_equal_i(0, git_error_state_capture(&err_state, 0)); + + memset(&err_state, 0x0, sizeof(git_error_state)); + + git_error_set(42, "Foo: %s", "bar"); + cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); + + cl_assert(git_error_last() == NULL); + + git_error_set(99, "Bar: %s", "foo"); + + git_error_state_restore(&err_state); + + cl_assert_equal_i(42, git_error_last()->klass); + cl_assert_equal_s("Foo: bar", git_error_last()->message); +} + +void test_core_errors__free_state(void) +{ + git_error_state err_state = {0}; + + git_error_clear(); + + git_error_set(42, "Foo: %s", "bar"); + cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); + + git_error_set(99, "Bar: %s", "foo"); + + git_error_state_free(&err_state); + + cl_assert_equal_i(99, git_error_last()->klass); + cl_assert_equal_s("Bar: foo", git_error_last()->message); + + git_error_state_restore(&err_state); + + cl_assert(git_error_last() == NULL); +} + +void test_core_errors__restore_oom(void) +{ + git_error_state err_state = {0}; + const git_error *oom_error = NULL; + + git_error_clear(); + + git_error_set_oom(); /* internal fn */ + oom_error = git_error_last(); + cl_assert(oom_error); + + cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); + + cl_assert(git_error_last() == NULL); + cl_assert_equal_i(GIT_ERROR_NOMEMORY, err_state.error_msg.klass); + cl_assert_equal_s("Out of memory", err_state.error_msg.message); + + git_error_state_restore(&err_state); + + cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); + cl_assert_(git_error_last() == oom_error, "static oom error not restored"); + + git_error_clear(); +} + +static int test_arraysize_multiply(size_t nelem, size_t size) +{ + size_t out; + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&out, nelem, size); + return 0; +} + +void test_core_errors__integer_overflow_alloc_multiply(void) +{ + cl_git_pass(test_arraysize_multiply(10, 10)); + cl_git_pass(test_arraysize_multiply(1000, 1000)); + cl_git_pass(test_arraysize_multiply(SIZE_MAX/sizeof(void *), sizeof(void *))); + cl_git_pass(test_arraysize_multiply(0, 10)); + cl_git_pass(test_arraysize_multiply(10, 0)); + + cl_git_fail(test_arraysize_multiply(SIZE_MAX-1, sizeof(void *))); + cl_git_fail(test_arraysize_multiply((SIZE_MAX/sizeof(void *))+1, sizeof(void *))); + + cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); + cl_assert_equal_s("Out of memory", git_error_last()->message); +} + +static int test_arraysize_add(size_t one, size_t two) +{ + size_t out; + GIT_ERROR_CHECK_ALLOC_ADD(&out, one, two); + return 0; +} + +void test_core_errors__integer_overflow_alloc_add(void) +{ + cl_git_pass(test_arraysize_add(10, 10)); + cl_git_pass(test_arraysize_add(1000, 1000)); + cl_git_pass(test_arraysize_add(SIZE_MAX-10, 10)); + + cl_git_fail(test_arraysize_multiply(SIZE_MAX-1, 2)); + cl_git_fail(test_arraysize_multiply(SIZE_MAX, SIZE_MAX)); + + cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); + cl_assert_equal_s("Out of memory", git_error_last()->message); +} + +void test_core_errors__integer_overflow_sets_oom(void) +{ + size_t out; + + git_error_clear(); + cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX-1, 1)); + cl_assert_equal_p(NULL, git_error_last()); + + git_error_clear(); + cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, 42, 69)); + cl_assert_equal_p(NULL, git_error_last()); + + git_error_clear(); + cl_assert(GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX, SIZE_MAX)); + cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); + cl_assert_equal_s("Out of memory", git_error_last()->message); + + git_error_clear(); + cl_assert(GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX, SIZE_MAX)); + cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); + cl_assert_equal_s("Out of memory", git_error_last()->message); +} diff --git a/tests/libgit2/core/features.c b/tests/libgit2/core/features.c new file mode 100644 index 000000000..7b28cc0cb --- /dev/null +++ b/tests/libgit2/core/features.c @@ -0,0 +1,35 @@ +#include "clar_libgit2.h" + +void test_core_features__0(void) +{ + int major, minor, rev, caps; + + git_libgit2_version(&major, &minor, &rev); + cl_assert_equal_i(LIBGIT2_VER_MAJOR, major); + cl_assert_equal_i(LIBGIT2_VER_MINOR, minor); + cl_assert_equal_i(LIBGIT2_VER_REVISION, rev); + + caps = git_libgit2_features(); + +#ifdef GIT_THREADS + cl_assert((caps & GIT_FEATURE_THREADS) != 0); +#else + cl_assert((caps & GIT_FEATURE_THREADS) == 0); +#endif + +#ifdef GIT_HTTPS + cl_assert((caps & GIT_FEATURE_HTTPS) != 0); +#endif + +#if defined(GIT_SSH) + cl_assert((caps & GIT_FEATURE_SSH) != 0); +#else + cl_assert((caps & GIT_FEATURE_SSH) == 0); +#endif + +#if defined(GIT_USE_NSEC) + cl_assert((caps & GIT_FEATURE_NSEC) != 0); +#else + cl_assert((caps & GIT_FEATURE_NSEC) == 0); +#endif +} diff --git a/tests/libgit2/core/filebuf.c b/tests/libgit2/core/filebuf.c new file mode 100644 index 000000000..6f40c2456 --- /dev/null +++ b/tests/libgit2/core/filebuf.c @@ -0,0 +1,267 @@ +#include "clar_libgit2.h" +#include "filebuf.h" + +/* make sure git_filebuf_open doesn't delete an existing lock */ +void test_core_filebuf__0(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + int fd; + char test[] = "test", testlock[] = "test.lock"; + + fd = p_creat(testlock, 0744); /* -V536 */ + + cl_must_pass(fd); + cl_must_pass(p_close(fd)); + + cl_git_fail(git_filebuf_open(&file, test, 0, 0666)); + cl_assert(git_fs_path_exists(testlock)); + + cl_must_pass(p_unlink(testlock)); +} + + +/* make sure GIT_FILEBUF_APPEND works as expected */ +void test_core_filebuf__1(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + + cl_git_mkfile(test, "libgit2 rocks\n"); + + cl_git_pass(git_filebuf_open(&file, test, GIT_FILEBUF_APPEND, 0666)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + cl_git_pass(git_filebuf_commit(&file)); + + cl_assert_equal_file("libgit2 rocks\nlibgit2 rocks\n", 0, test); + + cl_must_pass(p_unlink(test)); +} + + +/* make sure git_filebuf_write writes large buffer correctly */ +void test_core_filebuf__2(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + unsigned char buf[4096 * 4]; /* 2 * WRITE_BUFFER_SIZE */ + + memset(buf, 0xfe, sizeof(buf)); + + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + cl_git_pass(git_filebuf_write(&file, buf, sizeof(buf))); + cl_git_pass(git_filebuf_commit(&file)); + + cl_assert_equal_file((char *)buf, sizeof(buf), test); + + cl_must_pass(p_unlink(test)); +} + +/* make sure git_filebuf_cleanup clears the buffer */ +void test_core_filebuf__4(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + + cl_assert(file.buffer == NULL); + + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + cl_assert(file.buffer != NULL); + + git_filebuf_cleanup(&file); + cl_assert(file.buffer == NULL); +} + + +/* make sure git_filebuf_commit clears the buffer */ +void test_core_filebuf__5(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + + cl_assert(file.buffer == NULL); + + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + cl_assert(file.buffer != NULL); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + cl_assert(file.buffer != NULL); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert(file.buffer == NULL); + + cl_must_pass(p_unlink(test)); +} + + +/* make sure git_filebuf_commit takes umask into account */ +void test_core_filebuf__umask(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + struct stat statbuf; + mode_t mask, os_mask; + +#ifdef GIT_WIN32 + os_mask = 0600; +#else + os_mask = 0777; +#endif + + p_umask(mask = p_umask(0)); + + cl_assert(file.buffer == NULL); + + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + cl_assert(file.buffer != NULL); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + cl_assert(file.buffer != NULL); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert(file.buffer == NULL); + + cl_must_pass(p_stat("test", &statbuf)); + cl_assert_equal_i(statbuf.st_mode & os_mask, (0666 & ~mask) & os_mask); + + cl_must_pass(p_unlink(test)); +} + +void test_core_filebuf__rename_error(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char *dir = "subdir", *test = "subdir/test", *test_lock = "subdir/test.lock"; + int fd; + +#ifndef GIT_WIN32 + cl_skip(); +#endif + + cl_git_pass(p_mkdir(dir, 0666)); + cl_git_mkfile(test, "dummy content"); + fd = p_open(test, O_RDONLY); + cl_assert(fd > 0); + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_assert_equal_i(true, git_fs_path_exists(test_lock)); + + cl_git_fail(git_filebuf_commit(&file)); + p_close(fd); + + git_filebuf_cleanup(&file); + + cl_assert_equal_i(false, git_fs_path_exists(test_lock)); +} + +void test_core_filebuf__symlink_follow(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + const char *dir = "linkdir", *source = "linkdir/link"; + + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + cl_skip(); + + cl_git_pass(p_mkdir(dir, 0777)); + cl_git_pass(p_symlink("target", source)); + + cl_git_pass(git_filebuf_open(&file, source, 0, 0666)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock")); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert_equal_i(true, git_fs_path_exists("linkdir/target")); + + git_filebuf_cleanup(&file); + + /* The second time around, the target file does exist */ + cl_git_pass(git_filebuf_open(&file, source, 0, 0666)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock")); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert_equal_i(true, git_fs_path_exists("linkdir/target")); + + git_filebuf_cleanup(&file); + cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_core_filebuf__symlink_follow_absolute_paths(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str source = GIT_STR_INIT, target = GIT_STR_INIT; + + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + cl_skip(); + + cl_git_pass(git_str_joinpath(&source, clar_sandbox_path(), "linkdir/link")); + cl_git_pass(git_str_joinpath(&target, clar_sandbox_path(), "linkdir/target")); + cl_git_pass(p_mkdir("linkdir", 0777)); + cl_git_pass(p_symlink(target.ptr, source.ptr)); + + cl_git_pass(git_filebuf_open(&file, source.ptr, 0, 0666)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock")); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert_equal_i(true, git_fs_path_exists("linkdir/target")); + + git_filebuf_cleanup(&file); + git_str_dispose(&source); + git_str_dispose(&target); + + cl_git_pass(git_futils_rmdir_r("linkdir", NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_core_filebuf__symlink_depth(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + const char *dir = "linkdir", *source = "linkdir/link"; + + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + cl_skip(); + + cl_git_pass(p_mkdir(dir, 0777)); + /* Endless loop */ + cl_git_pass(p_symlink("link", source)); + + cl_git_fail(git_filebuf_open(&file, source, 0, 0666)); + + cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_core_filebuf__hidden_file(void) +{ +#ifndef GIT_WIN32 + cl_skip(); +#else + git_filebuf file = GIT_FILEBUF_INIT; + char *dir = "hidden", *test = "hidden/test"; + bool hidden; + + cl_git_pass(p_mkdir(dir, 0666)); + cl_git_mkfile(test, "dummy content"); + + cl_git_pass(git_win32__set_hidden(test, true)); + cl_git_pass(git_win32__hidden(&hidden, test)); + cl_assert(hidden); + + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_git_pass(git_filebuf_commit(&file)); + + git_filebuf_cleanup(&file); +#endif +} + +void test_core_filebuf__detects_directory(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + + cl_must_pass(p_mkdir("foo", 0777)); + cl_git_fail_with(GIT_EDIRECTORY, git_filebuf_open(&file, "foo", 0, 0666)); + cl_must_pass(p_rmdir("foo")); +} diff --git a/tests/libgit2/core/ftruncate.c b/tests/libgit2/core/ftruncate.c new file mode 100644 index 000000000..0c731cb1e --- /dev/null +++ b/tests/libgit2/core/ftruncate.c @@ -0,0 +1,48 @@ +/** + * Some tests for p_ftruncate() to ensure that + * properly handles large (2Gb+) files. + */ + +#include "clar_libgit2.h" + +static const char *filename = "core_ftruncate.txt"; +static int fd = -1; + +void test_core_ftruncate__initialize(void) +{ + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE")) + cl_skip(); + + cl_must_pass((fd = p_open(filename, O_CREAT | O_RDWR, 0644))); +} + +void test_core_ftruncate__cleanup(void) +{ + if (fd < 0) + return; + + p_close(fd); + fd = 0; + + p_unlink(filename); +} + +static void _extend(off64_t i64len) +{ + struct stat st; + int error; + + cl_assert((error = p_ftruncate(fd, i64len)) == 0); + cl_assert((error = p_fstat(fd, &st)) == 0); + cl_assert(st.st_size == i64len); +} + +void test_core_ftruncate__2gb(void) +{ + _extend(0x80000001); +} + +void test_core_ftruncate__4gb(void) +{ + _extend(0x100000001); +} diff --git a/tests/libgit2/core/futils.c b/tests/libgit2/core/futils.c new file mode 100644 index 000000000..3501765f6 --- /dev/null +++ b/tests/libgit2/core/futils.c @@ -0,0 +1,115 @@ +#include "clar_libgit2.h" +#include "futils.h" + +/* Fixture setup and teardown */ +void test_core_futils__initialize(void) +{ + cl_must_pass(p_mkdir("futils", 0777)); +} + +void test_core_futils__cleanup(void) +{ + cl_fixture_cleanup("futils"); +} + +void test_core_futils__writebuffer(void) +{ + git_str out = GIT_STR_INIT, + append = GIT_STR_INIT; + + /* create a new file */ + git_str_puts(&out, "hello!\n"); + git_str_printf(&out, "this is a %s\n", "test"); + + cl_git_pass(git_futils_writebuffer(&out, "futils/test-file", O_RDWR|O_CREAT, 0666)); + + cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); + + /* append some more data */ + git_str_puts(&append, "And some more!\n"); + git_str_put(&out, append.ptr, append.size); + + cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR|O_APPEND, 0666)); + + cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); + + git_str_dispose(&out); + git_str_dispose(&append); +} + +void test_core_futils__write_hidden_file(void) +{ +#ifndef GIT_WIN32 + cl_skip(); +#else + git_str out = GIT_STR_INIT, append = GIT_STR_INIT; + bool hidden; + + git_str_puts(&out, "hidden file.\n"); + git_futils_writebuffer(&out, "futils/test-file", O_RDWR | O_CREAT, 0666); + + cl_git_pass(git_win32__set_hidden("futils/test-file", true)); + + /* append some more data */ + git_str_puts(&append, "And some more!\n"); + git_str_put(&out, append.ptr, append.size); + + cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR | O_APPEND, 0666)); + + cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); + + cl_git_pass(git_win32__hidden(&hidden, "futils/test-file")); + cl_assert(hidden); + + git_str_dispose(&out); + git_str_dispose(&append); +#endif +} + +void test_core_futils__recursive_rmdir_keeps_symlink_targets(void) +{ + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + cl_skip(); + + cl_git_pass(git_futils_mkdir_r("a/b", 0777)); + cl_git_pass(git_futils_mkdir_r("dir-target", 0777)); + cl_git_mkfile("dir-target/file", "Contents"); + cl_git_mkfile("file-target", "Contents"); + cl_must_pass(p_symlink("dir-target", "a/symlink")); + cl_must_pass(p_symlink("file-target", "a/b/symlink")); + + cl_git_pass(git_futils_rmdir_r("a", NULL, GIT_RMDIR_REMOVE_FILES)); + + cl_assert(git_fs_path_exists("dir-target")); + cl_assert(git_fs_path_exists("file-target")); + + cl_must_pass(p_unlink("dir-target/file")); + cl_must_pass(p_rmdir("dir-target")); + cl_must_pass(p_unlink("file-target")); +} + +void test_core_futils__mktmp_umask(void) +{ +#ifdef GIT_WIN32 + cl_skip(); +#else + git_str path = GIT_STR_INIT; + struct stat st; + int fd; + + umask(0); + cl_assert((fd = git_futils_mktmp(&path, "foo", 0777)) >= 0); + cl_must_pass(p_fstat(fd, &st)); + cl_assert_equal_i(st.st_mode & 0777, 0777); + cl_must_pass(p_unlink(path.ptr)); + close(fd); + + umask(077); + cl_assert((fd = git_futils_mktmp(&path, "foo", 0777)) >= 0); + cl_must_pass(p_fstat(fd, &st)); + cl_assert_equal_i(st.st_mode & 0777, 0700); + cl_must_pass(p_unlink(path.ptr)); + close(fd); + git_str_dispose(&path); +#endif +} diff --git a/tests/libgit2/core/gitstr.c b/tests/libgit2/core/gitstr.c new file mode 100644 index 000000000..0a624e28c --- /dev/null +++ b/tests/libgit2/core/gitstr.c @@ -0,0 +1,1225 @@ +#include "clar_libgit2.h" +#include "git2/sys/hashsig.h" +#include "futils.h" + +#define TESTSTR "Have you seen that? Have you seeeen that??" +const char *test_string = TESTSTR; +const char *test_string_x2 = TESTSTR TESTSTR; + +#define TESTSTR_4096 REP1024("1234") +#define TESTSTR_8192 REP1024("12341234") +const char *test_4096 = TESTSTR_4096; +const char *test_8192 = TESTSTR_8192; + +/* test basic data concatenation */ +void test_core_gitstr__0(void) +{ + git_str buf = GIT_STR_INIT; + + cl_assert(buf.size == 0); + + git_str_puts(&buf, test_string); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_string, git_str_cstr(&buf)); + + git_str_puts(&buf, test_string); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_string_x2, git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +/* test git_str_printf */ +void test_core_gitstr__1(void) +{ + git_str buf = GIT_STR_INIT; + + git_str_printf(&buf, "%s %s %d ", "shoop", "da", 23); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s("shoop da 23 ", git_str_cstr(&buf)); + + git_str_printf(&buf, "%s %d", "woop", 42); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s("shoop da 23 woop 42", git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +/* more thorough test of concatenation options */ +void test_core_gitstr__2(void) +{ + git_str buf = GIT_STR_INIT; + int i; + char data[128]; + + cl_assert(buf.size == 0); + + /* this must be safe to do */ + git_str_dispose(&buf); + cl_assert(buf.size == 0); + cl_assert(buf.asize == 0); + + /* empty buffer should be empty string */ + cl_assert_equal_s("", git_str_cstr(&buf)); + cl_assert(buf.size == 0); + /* cl_assert(buf.asize == 0); -- should not assume what git_str does */ + + /* free should set us back to the beginning */ + git_str_dispose(&buf); + cl_assert(buf.size == 0); + cl_assert(buf.asize == 0); + + /* add letter */ + git_str_putc(&buf, '+'); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s("+", git_str_cstr(&buf)); + + /* add letter again */ + git_str_putc(&buf, '+'); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s("++", git_str_cstr(&buf)); + + /* let's try that a few times */ + for (i = 0; i < 16; ++i) { + git_str_putc(&buf, '+'); + cl_assert(git_str_oom(&buf) == 0); + } + cl_assert_equal_s("++++++++++++++++++", git_str_cstr(&buf)); + + git_str_dispose(&buf); + + /* add data */ + git_str_put(&buf, "xo", 2); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s("xo", git_str_cstr(&buf)); + + /* add letter again */ + git_str_put(&buf, "xo", 2); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s("xoxo", git_str_cstr(&buf)); + + /* let's try that a few times */ + for (i = 0; i < 16; ++i) { + git_str_put(&buf, "xo", 2); + cl_assert(git_str_oom(&buf) == 0); + } + cl_assert_equal_s("xoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxo", + git_str_cstr(&buf)); + + git_str_dispose(&buf); + + /* set to string */ + git_str_sets(&buf, test_string); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_string, git_str_cstr(&buf)); + + /* append string */ + git_str_puts(&buf, test_string); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_string_x2, git_str_cstr(&buf)); + + /* set to string again (should overwrite - not append) */ + git_str_sets(&buf, test_string); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_string, git_str_cstr(&buf)); + + /* test clear */ + git_str_clear(&buf); + cl_assert_equal_s("", git_str_cstr(&buf)); + + git_str_dispose(&buf); + + /* test extracting data into buffer */ + git_str_puts(&buf, REP4("0123456789")); + cl_assert(git_str_oom(&buf) == 0); + + git_str_copy_cstr(data, sizeof(data), &buf); + cl_assert_equal_s(REP4("0123456789"), data); + git_str_copy_cstr(data, 11, &buf); + cl_assert_equal_s("0123456789", data); + git_str_copy_cstr(data, 3, &buf); + cl_assert_equal_s("01", data); + git_str_copy_cstr(data, 1, &buf); + cl_assert_equal_s("", data); + + git_str_copy_cstr(data, sizeof(data), &buf); + cl_assert_equal_s(REP4("0123456789"), data); + + git_str_sets(&buf, REP256("x")); + git_str_copy_cstr(data, sizeof(data), &buf); + /* since sizeof(data) == 128, only 127 bytes should be copied */ + cl_assert_equal_s(REP4(REP16("x")) REP16("x") REP16("x") + REP16("x") "xxxxxxxxxxxxxxx", data); + + git_str_dispose(&buf); + + git_str_copy_cstr(data, sizeof(data), &buf); + cl_assert_equal_s("", data); +} + +/* let's do some tests with larger buffers to push our limits */ +void test_core_gitstr__3(void) +{ + git_str buf = GIT_STR_INIT; + + /* set to string */ + git_str_set(&buf, test_4096, 4096); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_4096, git_str_cstr(&buf)); + + /* append string */ + git_str_puts(&buf, test_4096); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_8192, git_str_cstr(&buf)); + + /* set to string again (should overwrite - not append) */ + git_str_set(&buf, test_4096, 4096); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_4096, git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +/* let's try some producer/consumer tests */ +void test_core_gitstr__4(void) +{ + git_str buf = GIT_STR_INIT; + int i; + + for (i = 0; i < 10; ++i) { + git_str_puts(&buf, "1234"); /* add 4 */ + cl_assert(git_str_oom(&buf) == 0); + git_str_consume(&buf, buf.ptr + 2); /* eat the first two */ + cl_assert(strlen(git_str_cstr(&buf)) == (size_t)((i + 1) * 2)); + } + /* we have appended 1234 10x and removed the first 20 letters */ + cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); + + git_str_consume(&buf, NULL); + cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); + + git_str_consume(&buf, "invalid pointer"); + cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); + + git_str_consume(&buf, buf.ptr); + cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); + + git_str_consume(&buf, buf.ptr + 1); + cl_assert_equal_s("2341234123412341234", git_str_cstr(&buf)); + + git_str_consume(&buf, buf.ptr + buf.size); + cl_assert_equal_s("", git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + + +static void +check_buf_append( + const char* data_a, + const char* data_b, + const char* expected_data, + size_t expected_size, + size_t expected_asize) +{ + git_str tgt = GIT_STR_INIT; + + git_str_sets(&tgt, data_a); + cl_assert(git_str_oom(&tgt) == 0); + git_str_puts(&tgt, data_b); + cl_assert(git_str_oom(&tgt) == 0); + cl_assert_equal_s(expected_data, git_str_cstr(&tgt)); + cl_assert_equal_i(tgt.size, expected_size); + if (expected_asize > 0) + cl_assert_equal_i(tgt.asize, expected_asize); + + git_str_dispose(&tgt); +} + +static void +check_buf_append_abc( + const char* buf_a, + const char* buf_b, + const char* buf_c, + const char* expected_ab, + const char* expected_abc, + const char* expected_abca, + const char* expected_abcab, + const char* expected_abcabc) +{ + git_str buf = GIT_STR_INIT; + + git_str_sets(&buf, buf_a); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(buf_a, git_str_cstr(&buf)); + + git_str_puts(&buf, buf_b); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected_ab, git_str_cstr(&buf)); + + git_str_puts(&buf, buf_c); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected_abc, git_str_cstr(&buf)); + + git_str_puts(&buf, buf_a); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected_abca, git_str_cstr(&buf)); + + git_str_puts(&buf, buf_b); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected_abcab, git_str_cstr(&buf)); + + git_str_puts(&buf, buf_c); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected_abcabc, git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +/* more variations on append tests */ +void test_core_gitstr__5(void) +{ + check_buf_append("", "", "", 0, 0); + check_buf_append("a", "", "a", 1, 0); + check_buf_append("", "a", "a", 1, 8); + check_buf_append("", "a", "a", 1, 8); + check_buf_append("a", "b", "ab", 2, 8); + check_buf_append("", "abcdefgh", "abcdefgh", 8, 16); + check_buf_append("abcdefgh", "", "abcdefgh", 8, 16); + + /* buffer with starting asize will grow to: + * 1 -> 2, 2 -> 3, 3 -> 5, 4 -> 6, 5 -> 8, 6 -> 9, + * 7 -> 11, 8 -> 12, 9 -> 14, 10 -> 15, 11 -> 17, 12 -> 18, + * 13 -> 20, 14 -> 21, 15 -> 23, 16 -> 24, 17 -> 26, 18 -> 27, + * 19 -> 29, 20 -> 30, 21 -> 32, 22 -> 33, 23 -> 35, 24 -> 36, + * ... + * follow sequence until value > target size, + * then round up to nearest multiple of 8. + */ + + check_buf_append("abcdefgh", "/", "abcdefgh/", 9, 16); + check_buf_append("abcdefgh", "ijklmno", "abcdefghijklmno", 15, 16); + check_buf_append("abcdefgh", "ijklmnop", "abcdefghijklmnop", 16, 24); + check_buf_append("0123456789", "0123456789", + "01234567890123456789", 20, 24); + check_buf_append(REP16("x"), REP16("o"), + REP16("x") REP16("o"), 32, 40); + + check_buf_append(test_4096, "", test_4096, 4096, 4104); + check_buf_append(test_4096, test_4096, test_8192, 8192, 8200); + + /* check sequences of appends */ + check_buf_append_abc("a", "b", "c", + "ab", "abc", "abca", "abcab", "abcabc"); + check_buf_append_abc("a1", "b2", "c3", + "a1b2", "a1b2c3", "a1b2c3a1", + "a1b2c3a1b2", "a1b2c3a1b2c3"); + check_buf_append_abc("a1/", "b2/", "c3/", + "a1/b2/", "a1/b2/c3/", "a1/b2/c3/a1/", + "a1/b2/c3/a1/b2/", "a1/b2/c3/a1/b2/c3/"); +} + +/* test swap */ +void test_core_gitstr__6(void) +{ + git_str a = GIT_STR_INIT; + git_str b = GIT_STR_INIT; + + git_str_sets(&a, "foo"); + cl_assert(git_str_oom(&a) == 0); + git_str_sets(&b, "bar"); + cl_assert(git_str_oom(&b) == 0); + + cl_assert_equal_s("foo", git_str_cstr(&a)); + cl_assert_equal_s("bar", git_str_cstr(&b)); + + git_str_swap(&a, &b); + + cl_assert_equal_s("bar", git_str_cstr(&a)); + cl_assert_equal_s("foo", git_str_cstr(&b)); + + git_str_dispose(&a); + git_str_dispose(&b); +} + + +/* test detach/attach data */ +void test_core_gitstr__7(void) +{ + const char *fun = "This is fun"; + git_str a = GIT_STR_INIT; + char *b = NULL; + + git_str_sets(&a, "foo"); + cl_assert(git_str_oom(&a) == 0); + cl_assert_equal_s("foo", git_str_cstr(&a)); + + b = git_str_detach(&a); + + cl_assert_equal_s("foo", b); + cl_assert_equal_s("", a.ptr); + git__free(b); + + b = git_str_detach(&a); + + cl_assert_equal_s(NULL, b); + cl_assert_equal_s("", a.ptr); + + git_str_dispose(&a); + + b = git__strdup(fun); + git_str_attach(&a, b, 0); + + cl_assert_equal_s(fun, a.ptr); + cl_assert(a.size == strlen(fun)); + cl_assert(a.asize == strlen(fun) + 1); + + git_str_dispose(&a); + + b = git__strdup(fun); + git_str_attach(&a, b, strlen(fun) + 1); + + cl_assert_equal_s(fun, a.ptr); + cl_assert(a.size == strlen(fun)); + cl_assert(a.asize == strlen(fun) + 1); + + git_str_dispose(&a); +} + + +static void +check_joinbuf_2( + const char *a, + const char *b, + const char *expected) +{ + char sep = '/'; + git_str buf = GIT_STR_INIT; + + git_str_join(&buf, sep, a, b); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected, git_str_cstr(&buf)); + git_str_dispose(&buf); +} + +static void +check_joinbuf_overlapped( + const char *oldval, + int ofs_a, + const char *b, + const char *expected) +{ + char sep = '/'; + git_str buf = GIT_STR_INIT; + + git_str_sets(&buf, oldval); + git_str_join(&buf, sep, buf.ptr + ofs_a, b); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected, git_str_cstr(&buf)); + git_str_dispose(&buf); +} + +static void +check_joinbuf_n_2( + const char *a, + const char *b, + const char *expected) +{ + char sep = '/'; + git_str buf = GIT_STR_INIT; + + git_str_sets(&buf, a); + cl_assert(git_str_oom(&buf) == 0); + + git_str_join_n(&buf, sep, 1, b); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected, git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +static void +check_joinbuf_n_4( + const char *a, + const char *b, + const char *c, + const char *d, + const char *expected) +{ + char sep = ';'; + git_str buf = GIT_STR_INIT; + git_str_join_n(&buf, sep, 4, a, b, c, d); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected, git_str_cstr(&buf)); + git_str_dispose(&buf); +} + +/* test join */ +void test_core_gitstr__8(void) +{ + git_str a = GIT_STR_INIT; + + git_str_join_n(&a, '/', 1, "foo"); + cl_assert(git_str_oom(&a) == 0); + cl_assert_equal_s("foo", git_str_cstr(&a)); + + git_str_join_n(&a, '/', 1, "bar"); + cl_assert(git_str_oom(&a) == 0); + cl_assert_equal_s("foo/bar", git_str_cstr(&a)); + + git_str_join_n(&a, '/', 1, "baz"); + cl_assert(git_str_oom(&a) == 0); + cl_assert_equal_s("foo/bar/baz", git_str_cstr(&a)); + + git_str_dispose(&a); + + check_joinbuf_2(NULL, "", ""); + check_joinbuf_2(NULL, "a", "a"); + check_joinbuf_2(NULL, "/a", "/a"); + check_joinbuf_2("", "", ""); + check_joinbuf_2("", "a", "a"); + check_joinbuf_2("", "/a", "/a"); + check_joinbuf_2("a", "", "a/"); + check_joinbuf_2("a", "/", "a/"); + check_joinbuf_2("a", "b", "a/b"); + check_joinbuf_2("/", "a", "/a"); + check_joinbuf_2("/", "", "/"); + check_joinbuf_2("/a", "/b", "/a/b"); + check_joinbuf_2("/a", "/b/", "/a/b/"); + check_joinbuf_2("/a/", "b/", "/a/b/"); + check_joinbuf_2("/a/", "/b/", "/a/b/"); + check_joinbuf_2("/a/", "//b/", "/a/b/"); + check_joinbuf_2("/abcd", "/defg", "/abcd/defg"); + check_joinbuf_2("/abcd", "/defg/", "/abcd/defg/"); + check_joinbuf_2("/abcd/", "defg/", "/abcd/defg/"); + check_joinbuf_2("/abcd/", "/defg/", "/abcd/defg/"); + + check_joinbuf_overlapped("abcd", 0, "efg", "abcd/efg"); + check_joinbuf_overlapped("abcd", 1, "efg", "bcd/efg"); + check_joinbuf_overlapped("abcd", 2, "efg", "cd/efg"); + check_joinbuf_overlapped("abcd", 3, "efg", "d/efg"); + check_joinbuf_overlapped("abcd", 4, "efg", "efg"); + check_joinbuf_overlapped("abc/", 2, "efg", "c/efg"); + check_joinbuf_overlapped("abc/", 3, "efg", "/efg"); + check_joinbuf_overlapped("abc/", 4, "efg", "efg"); + check_joinbuf_overlapped("abcd", 3, "", "d/"); + check_joinbuf_overlapped("abcd", 4, "", ""); + check_joinbuf_overlapped("abc/", 2, "", "c/"); + check_joinbuf_overlapped("abc/", 3, "", "/"); + check_joinbuf_overlapped("abc/", 4, "", ""); + + check_joinbuf_n_2("", "", ""); + check_joinbuf_n_2("", "a", "a"); + check_joinbuf_n_2("", "/a", "/a"); + check_joinbuf_n_2("a", "", "a/"); + check_joinbuf_n_2("a", "/", "a/"); + check_joinbuf_n_2("a", "b", "a/b"); + check_joinbuf_n_2("/", "a", "/a"); + check_joinbuf_n_2("/", "", "/"); + check_joinbuf_n_2("/a", "/b", "/a/b"); + check_joinbuf_n_2("/a", "/b/", "/a/b/"); + check_joinbuf_n_2("/a/", "b/", "/a/b/"); + check_joinbuf_n_2("/a/", "/b/", "/a/b/"); + check_joinbuf_n_2("/abcd", "/defg", "/abcd/defg"); + check_joinbuf_n_2("/abcd", "/defg/", "/abcd/defg/"); + check_joinbuf_n_2("/abcd/", "defg/", "/abcd/defg/"); + check_joinbuf_n_2("/abcd/", "/defg/", "/abcd/defg/"); + + check_joinbuf_n_4("", "", "", "", ""); + check_joinbuf_n_4("", "a", "", "", "a;"); + check_joinbuf_n_4("a", "", "", "", "a;"); + check_joinbuf_n_4("", "", "", "a", "a"); + check_joinbuf_n_4("a", "b", "", ";c;d;", "a;b;c;d;"); + check_joinbuf_n_4("a", "b", "", ";c;d", "a;b;c;d"); + check_joinbuf_n_4("abcd", "efgh", "ijkl", "mnop", "abcd;efgh;ijkl;mnop"); + check_joinbuf_n_4("abcd;", "efgh;", "ijkl;", "mnop;", "abcd;efgh;ijkl;mnop;"); + check_joinbuf_n_4(";abcd;", ";efgh;", ";ijkl;", ";mnop;", ";abcd;efgh;ijkl;mnop;"); +} + +void test_core_gitstr__9(void) +{ + git_str buf = GIT_STR_INIT; + + /* just some exhaustive tests of various separator placement */ + char *a[] = { "", "-", "a-", "-a", "-a-" }; + char *b[] = { "", "-", "b-", "-b", "-b-" }; + char sep[] = { 0, '-', '/' }; + char *expect_null[] = { "", "-", "a-", "-a", "-a-", + "-", "--", "a--", "-a-", "-a--", + "b-", "-b-", "a-b-", "-ab-", "-a-b-", + "-b", "--b", "a--b", "-a-b", "-a--b", + "-b-", "--b-", "a--b-", "-a-b-", "-a--b-" }; + char *expect_dash[] = { "", "-", "a-", "-a-", "-a-", + "-", "-", "a-", "-a-", "-a-", + "b-", "-b-", "a-b-", "-a-b-", "-a-b-", + "-b", "-b", "a-b", "-a-b", "-a-b", + "-b-", "-b-", "a-b-", "-a-b-", "-a-b-" }; + char *expect_slas[] = { "", "-/", "a-/", "-a/", "-a-/", + "-", "-/-", "a-/-", "-a/-", "-a-/-", + "b-", "-/b-", "a-/b-", "-a/b-", "-a-/b-", + "-b", "-/-b", "a-/-b", "-a/-b", "-a-/-b", + "-b-", "-/-b-", "a-/-b-", "-a/-b-", "-a-/-b-" }; + char **expect_values[] = { expect_null, expect_dash, expect_slas }; + char separator, **expect; + unsigned int s, i, j; + + for (s = 0; s < sizeof(sep) / sizeof(char); ++s) { + separator = sep[s]; + expect = expect_values[s]; + + for (j = 0; j < sizeof(b) / sizeof(char*); ++j) { + for (i = 0; i < sizeof(a) / sizeof(char*); ++i) { + git_str_join(&buf, separator, a[i], b[j]); + cl_assert_equal_s(*expect, buf.ptr); + expect++; + } + } + } + + git_str_dispose(&buf); +} + +void test_core_gitstr__10(void) +{ + git_str a = GIT_STR_INIT; + + cl_git_pass(git_str_join_n(&a, '/', 1, "test")); + cl_assert_equal_s(a.ptr, "test"); + cl_git_pass(git_str_join_n(&a, '/', 1, "string")); + cl_assert_equal_s(a.ptr, "test/string"); + git_str_clear(&a); + cl_git_pass(git_str_join_n(&a, '/', 3, "test", "string", "join")); + cl_assert_equal_s(a.ptr, "test/string/join"); + cl_git_pass(git_str_join_n(&a, '/', 2, a.ptr, "more")); + cl_assert_equal_s(a.ptr, "test/string/join/test/string/join/more"); + + git_str_dispose(&a); +} + +void test_core_gitstr__join3(void) +{ + git_str a = GIT_STR_INIT; + + cl_git_pass(git_str_join3(&a, '/', "test", "string", "join")); + cl_assert_equal_s("test/string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "test/", "string", "join")); + cl_assert_equal_s("test/string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "test/", "/string", "join")); + cl_assert_equal_s("test/string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "test/", "/string/", "join")); + cl_assert_equal_s("test/string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "test/", "/string/", "/join")); + cl_assert_equal_s("test/string/join", a.ptr); + + cl_git_pass(git_str_join3(&a, '/', "", "string", "join")); + cl_assert_equal_s("string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "", "string/", "join")); + cl_assert_equal_s("string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "", "string/", "/join")); + cl_assert_equal_s("string/join", a.ptr); + + cl_git_pass(git_str_join3(&a, '/', "string", "", "join")); + cl_assert_equal_s("string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "string/", "", "join")); + cl_assert_equal_s("string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "string/", "", "/join")); + cl_assert_equal_s("string/join", a.ptr); + + git_str_dispose(&a); +} + +void test_core_gitstr__11(void) +{ + git_str a = GIT_STR_INIT; + char *t1[] = { "nothing", "in", "common" }; + char *t2[] = { "something", "something else", "some other" }; + char *t3[] = { "something", "some fun", "no fun" }; + char *t4[] = { "happy", "happier", "happiest" }; + char *t5[] = { "happiest", "happier", "happy" }; + char *t6[] = { "no", "nope", "" }; + char *t7[] = { "", "doesn't matter" }; + + cl_git_pass(git_str_common_prefix(&a, t1, 3)); + cl_assert_equal_s(a.ptr, ""); + + cl_git_pass(git_str_common_prefix(&a, t2, 3)); + cl_assert_equal_s(a.ptr, "some"); + + cl_git_pass(git_str_common_prefix(&a, t3, 3)); + cl_assert_equal_s(a.ptr, ""); + + cl_git_pass(git_str_common_prefix(&a, t4, 3)); + cl_assert_equal_s(a.ptr, "happ"); + + cl_git_pass(git_str_common_prefix(&a, t5, 3)); + cl_assert_equal_s(a.ptr, "happ"); + + cl_git_pass(git_str_common_prefix(&a, t6, 3)); + cl_assert_equal_s(a.ptr, ""); + + cl_git_pass(git_str_common_prefix(&a, t7, 3)); + cl_assert_equal_s(a.ptr, ""); + + git_str_dispose(&a); +} + +void test_core_gitstr__rfind_variants(void) +{ + git_str a = GIT_STR_INIT; + ssize_t len; + + cl_git_pass(git_str_sets(&a, "/this/is/it/")); + + len = (ssize_t)git_str_len(&a); + + cl_assert(git_str_rfind(&a, '/') == len - 1); + cl_assert(git_str_rfind_next(&a, '/') == len - 4); + + cl_assert(git_str_rfind(&a, 'i') == len - 3); + cl_assert(git_str_rfind_next(&a, 'i') == len - 3); + + cl_assert(git_str_rfind(&a, 'h') == 2); + cl_assert(git_str_rfind_next(&a, 'h') == 2); + + cl_assert(git_str_rfind(&a, 'q') == -1); + cl_assert(git_str_rfind_next(&a, 'q') == -1); + + git_str_dispose(&a); +} + +void test_core_gitstr__puts_escaped(void) +{ + git_str a = GIT_STR_INIT; + + git_str_clear(&a); + cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", "")); + cl_assert_equal_s("this is a test", a.ptr); + + git_str_clear(&a); + cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\")); + cl_assert_equal_s("\\this is a \\tes\\t", a.ptr); + + git_str_clear(&a); + cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__")); + cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr); + + git_str_clear(&a); + cl_git_pass(git_str_puts_escape_regex(&a, "^match\\s*[A-Z]+.*")); + cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr); + + git_str_dispose(&a); +} + +static void assert_unescape(char *expected, char *to_unescape) { + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_sets(&buf, to_unescape)); + git_str_unescape(&buf); + cl_assert_equal_s(expected, buf.ptr); + cl_assert_equal_sz(strlen(expected), buf.size); + + git_str_dispose(&buf); +} + +void test_core_gitstr__unescape(void) +{ + assert_unescape("Escaped\\", "Es\\ca\\ped\\"); + assert_unescape("Es\\caped\\", "Es\\\\ca\\ped\\\\"); + assert_unescape("\\", "\\"); + assert_unescape("\\", "\\\\"); + assert_unescape("", ""); +} + +void test_core_gitstr__encode_base64(void) +{ + git_str buf = GIT_STR_INIT; + + /* t h i s + * 0x 74 68 69 73 + * 0b 01110100 01101000 01101001 01110011 + * 0b 011101 000110 100001 101001 011100 110000 + * 0x 1d 06 21 29 1c 30 + * d G h p c w + */ + cl_git_pass(git_str_encode_base64(&buf, "this", 4)); + cl_assert_equal_s("dGhpcw==", buf.ptr); + + git_str_clear(&buf); + cl_git_pass(git_str_encode_base64(&buf, "this!", 5)); + cl_assert_equal_s("dGhpcyE=", buf.ptr); + + git_str_clear(&buf); + cl_git_pass(git_str_encode_base64(&buf, "this!\n", 6)); + cl_assert_equal_s("dGhpcyEK", buf.ptr); + + git_str_dispose(&buf); +} + +void test_core_gitstr__decode_base64(void) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_decode_base64(&buf, "dGhpcw==", 8)); + cl_assert_equal_s("this", buf.ptr); + + git_str_clear(&buf); + cl_git_pass(git_str_decode_base64(&buf, "dGhpcyE=", 8)); + cl_assert_equal_s("this!", buf.ptr); + + git_str_clear(&buf); + cl_git_pass(git_str_decode_base64(&buf, "dGhpcyEK", 8)); + cl_assert_equal_s("this!\n", buf.ptr); + + cl_git_fail(git_str_decode_base64(&buf, "This is not a valid base64 string!!!", 36)); + cl_assert_equal_s("this!\n", buf.ptr); + + git_str_dispose(&buf); +} + +void test_core_gitstr__encode_base85(void) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_encode_base85(&buf, "this", 4)); + cl_assert_equal_s("bZBXF", buf.ptr); + git_str_clear(&buf); + + cl_git_pass(git_str_encode_base85(&buf, "two rnds", 8)); + cl_assert_equal_s("ba!tca&BaE", buf.ptr); + git_str_clear(&buf); + + cl_git_pass(git_str_encode_base85(&buf, "this is base 85 encoded", + strlen("this is base 85 encoded"))); + cl_assert_equal_s("bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", buf.ptr); + git_str_clear(&buf); + + git_str_dispose(&buf); +} + +void test_core_gitstr__decode_base85(void) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_decode_base85(&buf, "bZBXF", 5, 4)); + cl_assert_equal_sz(4, buf.size); + cl_assert_equal_s("this", buf.ptr); + git_str_clear(&buf); + + cl_git_pass(git_str_decode_base85(&buf, "ba!tca&BaE", 10, 8)); + cl_assert_equal_sz(8, buf.size); + cl_assert_equal_s("two rnds", buf.ptr); + git_str_clear(&buf); + + cl_git_pass(git_str_decode_base85(&buf, "bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", 30, 23)); + cl_assert_equal_sz(23, buf.size); + cl_assert_equal_s("this is base 85 encoded", buf.ptr); + git_str_clear(&buf); + + git_str_dispose(&buf); +} + +void test_core_gitstr__decode_base85_fails_gracefully(void) +{ + git_str buf = GIT_STR_INIT; + + git_str_puts(&buf, "foobar"); + + cl_git_fail(git_str_decode_base85(&buf, "invalid charsZZ", 15, 42)); + cl_git_fail(git_str_decode_base85(&buf, "invalidchars__ ", 15, 42)); + cl_git_fail(git_str_decode_base85(&buf, "overflowZZ~~~~~", 15, 42)); + cl_git_fail(git_str_decode_base85(&buf, "truncated", 9, 42)); + cl_assert_equal_sz(6, buf.size); + cl_assert_equal_s("foobar", buf.ptr); + + git_str_dispose(&buf); +} + +void test_core_gitstr__classify_with_utf8(void) +{ + char *data0 = "Simple text\n"; + size_t data0len = 12; + char *data1 = "Is that UTF-8 data I see…\nYep!\n"; + size_t data1len = 31; + char *data2 = "Internal NUL!!!\000\n\nI see you!\n"; + size_t data2len = 29; + char *data3 = "\xef\xbb\xbfThis is UTF-8 with a BOM.\n"; + size_t data3len = 20; + git_str b; + + b.ptr = data0; b.size = b.asize = data0len; + cl_assert(!git_str_is_binary(&b)); + cl_assert(!git_str_contains_nul(&b)); + + b.ptr = data1; b.size = b.asize = data1len; + cl_assert(!git_str_is_binary(&b)); + cl_assert(!git_str_contains_nul(&b)); + + b.ptr = data2; b.size = b.asize = data2len; + cl_assert(git_str_is_binary(&b)); + cl_assert(git_str_contains_nul(&b)); + + b.ptr = data3; b.size = b.asize = data3len; + cl_assert(!git_str_is_binary(&b)); + cl_assert(!git_str_contains_nul(&b)); +} + +#define SIMILARITY_TEST_DATA_1 \ + "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ + "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ + "020\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \ + "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \ + "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n" + +void test_core_gitstr__similarity_metric(void) +{ + git_hashsig *a, *b; + git_str buf = GIT_STR_INIT; + int sim; + + /* in the first case, we compare data to itself and expect 100% match */ + + cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); + cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + + cl_assert_equal_i(100, git_hashsig_compare(a, b)); + + git_hashsig_free(a); + git_hashsig_free(b); + + /* if we change just a single byte, how much does that change magnify? */ + + cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); + cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + cl_git_pass(git_str_sets(&buf, + "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ + "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ + "x020x\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \ + "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \ + "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n" + )); + cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + + sim = git_hashsig_compare(a, b); + + cl_assert_in_range(95, sim, 100); /* expect >95% similarity */ + + git_hashsig_free(a); + git_hashsig_free(b); + + /* let's try comparing data to a superset of itself */ + + cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); + cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1 + "050\n051\n052\n053\n054\n055\n056\n057\n058\n059\n")); + cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + + sim = git_hashsig_compare(a, b); + /* 20% lines added ~= 10% lines changed */ + + cl_assert_in_range(85, sim, 95); /* expect similarity around 90% */ + + git_hashsig_free(a); + git_hashsig_free(b); + + /* what if we keep about half the original data and add half new */ + + cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); + cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + cl_git_pass(git_str_sets(&buf, + "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ + "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ + "020x\n021\n022\n023\n024\n" \ + "x25\nx26\nx27\nx28\nx29\n" \ + "x30\nx31\nx32\nx33\nx34\nx35\nx36\nx37\nx38\nx39\n" \ + "x40\nx41\nx42\nx43\nx44\nx45\nx46\nx47\nx48\nx49\n" + )); + cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + + sim = git_hashsig_compare(a, b); + /* 50% lines changed */ + + cl_assert_in_range(40, sim, 60); /* expect in the 40-60% similarity range */ + + git_hashsig_free(a); + git_hashsig_free(b); + + /* lastly, let's check that we can hash file content as well */ + + cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); + cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + + cl_git_pass(git_futils_mkdir("scratch", 0755, GIT_MKDIR_PATH)); + cl_git_mkfile("scratch/testdata", SIMILARITY_TEST_DATA_1); + cl_git_pass(git_hashsig_create_fromfile( + &b, "scratch/testdata", GIT_HASHSIG_NORMAL)); + + cl_assert_equal_i(100, git_hashsig_compare(a, b)); + + git_hashsig_free(a); + git_hashsig_free(b); + + git_str_dispose(&buf); + git_futils_rmdir_r("scratch", NULL, GIT_RMDIR_REMOVE_FILES); +} + + +void test_core_gitstr__similarity_metric_whitespace(void) +{ + git_hashsig *a, *b; + git_str buf = GIT_STR_INIT; + int sim, i, j; + git_hashsig_option_t opt; + const char *tabbed = + " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\n" + " separator = sep[s];\n" + " expect = expect_values[s];\n" + "\n" + " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\n" + " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\n" + " git_str_join(&buf, separator, a[i], b[j]);\n" + " cl_assert_equal_s(*expect, buf.ptr);\n" + " expect++;\n" + " }\n" + " }\n" + " }\n"; + const char *spaced = + " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\n" + " separator = sep[s];\n" + " expect = expect_values[s];\n" + "\n" + " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\n" + " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\n" + " git_str_join(&buf, separator, a[i], b[j]);\n" + " cl_assert_equal_s(*expect, buf.ptr);\n" + " expect++;\n" + " }\n" + " }\n" + " }\n"; + const char *crlf_spaced2 = + " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\r\n" + " separator = sep[s];\r\n" + " expect = expect_values[s];\r\n" + "\r\n" + " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\r\n" + " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\r\n" + " git_str_join(&buf, separator, a[i], b[j]);\r\n" + " cl_assert_equal_s(*expect, buf.ptr);\r\n" + " expect++;\r\n" + " }\r\n" + " }\r\n" + " }\r\n"; + const char *text[3] = { tabbed, spaced, crlf_spaced2 }; + + /* let's try variations of our own code with whitespace changes */ + + for (opt = GIT_HASHSIG_NORMAL; opt <= GIT_HASHSIG_SMART_WHITESPACE; ++opt) { + for (i = 0; i < 3; ++i) { + for (j = 0; j < 3; ++j) { + cl_git_pass(git_str_sets(&buf, text[i])); + cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, opt)); + + cl_git_pass(git_str_sets(&buf, text[j])); + cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, opt)); + + sim = git_hashsig_compare(a, b); + + if (opt == GIT_HASHSIG_NORMAL) { + if (i == j) + cl_assert_equal_i(100, sim); + else + cl_assert_in_range(0, sim, 30); /* pretty different */ + } else { + cl_assert_equal_i(100, sim); + } + + git_hashsig_free(a); + git_hashsig_free(b); + } + } + } + + git_str_dispose(&buf); +} + +#include "../filter/crlf.h" + +#define check_buf(expected,buf) do { \ + cl_assert_equal_s(expected, buf.ptr); \ + cl_assert_equal_sz(strlen(expected), buf.size); } while (0) + +void test_core_gitstr__lf_and_crlf_conversions(void) +{ + git_str src = GIT_STR_INIT, tgt = GIT_STR_INIT; + + /* LF source */ + + git_str_sets(&src, "lf\nlf\nlf\nlf\n"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("lf\r\nlf\r\nlf\r\nlf\r\n", tgt); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf(src.ptr, tgt); + + git_str_sets(&src, "\nlf\nlf\nlf\nlf\nlf"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("\r\nlf\r\nlf\r\nlf\r\nlf\r\nlf", tgt); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf(src.ptr, tgt); + + /* CRLF source */ + + git_str_sets(&src, "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n", tgt); + + git_str_sets(&src, "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n"); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf("crlf\ncrlf\ncrlf\ncrlf\n", tgt); + + git_str_sets(&src, "\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf", tgt); + + git_str_sets(&src, "\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf"); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf("\ncrlf\ncrlf\ncrlf\ncrlf\ncrlf", tgt); + + /* CRLF in LF text */ + + git_str_sets(&src, "\nlf\nlf\ncrlf\r\nlf\nlf\ncrlf\r\n"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("\r\nlf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\ncrlf\r\n", tgt); + + git_str_sets(&src, "\nlf\nlf\ncrlf\r\nlf\nlf\ncrlf\r\n"); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf("\nlf\nlf\ncrlf\nlf\nlf\ncrlf\n", tgt); + + /* LF in CRLF text */ + + git_str_sets(&src, "\ncrlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("\r\ncrlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf", tgt); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf("\ncrlf\ncrlf\nlf\ncrlf\ncrlf", tgt); + + /* bare CR test */ + + git_str_sets(&src, "\rcrlf\r\nlf\nlf\ncr\rcrlf\r\nlf\ncr\r"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("\rcrlf\r\nlf\r\nlf\r\ncr\rcrlf\r\nlf\r\ncr\r", tgt); + + git_str_sets(&src, "\rcrlf\r\nlf\nlf\ncr\rcrlf\r\nlf\ncr\r"); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf("\rcrlf\nlf\nlf\ncr\rcrlf\nlf\ncr\r", tgt); + + git_str_sets(&src, "\rcr\r"); + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf(src.ptr, tgt); + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf("\rcr\r", tgt); + + git_str_dispose(&src); + git_str_dispose(&tgt); + + /* blob correspondence tests */ + + git_str_sets(&src, ALL_CRLF_TEXT_RAW); + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf(ALL_CRLF_TEXT_AS_CRLF, tgt); + git_str_sets(&src, ALL_CRLF_TEXT_RAW); + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf(ALL_CRLF_TEXT_AS_LF, tgt); + git_str_dispose(&src); + git_str_dispose(&tgt); + + git_str_sets(&src, ALL_LF_TEXT_RAW); + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf(ALL_LF_TEXT_AS_CRLF, tgt); + git_str_sets(&src, ALL_LF_TEXT_RAW); + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf(ALL_LF_TEXT_AS_LF, tgt); + git_str_dispose(&src); + git_str_dispose(&tgt); + + git_str_sets(&src, MORE_CRLF_TEXT_RAW); + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf(MORE_CRLF_TEXT_AS_CRLF, tgt); + git_str_sets(&src, MORE_CRLF_TEXT_RAW); + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf(MORE_CRLF_TEXT_AS_LF, tgt); + git_str_dispose(&src); + git_str_dispose(&tgt); + + git_str_sets(&src, MORE_LF_TEXT_RAW); + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf(MORE_LF_TEXT_AS_CRLF, tgt); + git_str_sets(&src, MORE_LF_TEXT_RAW); + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf(MORE_LF_TEXT_AS_LF, tgt); + git_str_dispose(&src); + git_str_dispose(&tgt); +} + +void test_core_gitstr__dont_grow_borrowed(void) +{ + const char *somestring = "blah blah"; + git_str buf = GIT_STR_INIT; + + git_str_attach_notowned(&buf, somestring, strlen(somestring) + 1); + cl_assert_equal_p(somestring, buf.ptr); + cl_assert_equal_i(0, buf.asize); + cl_assert_equal_i(strlen(somestring) + 1, buf.size); + + cl_git_fail_with(GIT_EINVALID, git_str_grow(&buf, 1024)); +} + +void test_core_gitstr__dont_hit_infinite_loop_when_resizing(void) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_puts(&buf, "foobar")); + /* + * We do not care whether this succeeds or fails, which + * would depend on platform-specific allocation + * semantics. We only want to know that the function + * actually returns. + */ + (void)git_str_try_grow(&buf, SIZE_MAX, true); + + git_str_dispose(&buf); +} + +void test_core_gitstr__avoid_printing_into_oom_buffer(void) +{ + git_str buf = GIT_STR_INIT; + + /* Emulate OOM situation with a previous allocation */ + buf.asize = 8; + buf.ptr = git_str__oom; + + /* + * Print the same string again. As the buffer still has + * an `asize` of 8 due to the previous print, + * `ENSURE_SIZE` would not try to reallocate the array at + * all. As it didn't explicitly check for `git_str__oom` + * in earlier versions, this would've resulted in it + * returning successfully and thus `git_str_puts` would + * just print into the `git_str__oom` array. + */ + cl_git_fail(git_str_puts(&buf, "foobar")); +} diff --git a/tests/libgit2/core/hex.c b/tests/libgit2/core/hex.c new file mode 100644 index 000000000..930af1670 --- /dev/null +++ b/tests/libgit2/core/hex.c @@ -0,0 +1,22 @@ +#include "clar_libgit2.h" +#include "util.h" + +void test_core_hex__fromhex(void) +{ + /* Passing cases */ + cl_assert(git__fromhex('0') == 0x0); + cl_assert(git__fromhex('1') == 0x1); + cl_assert(git__fromhex('3') == 0x3); + cl_assert(git__fromhex('9') == 0x9); + cl_assert(git__fromhex('A') == 0xa); + cl_assert(git__fromhex('C') == 0xc); + cl_assert(git__fromhex('F') == 0xf); + cl_assert(git__fromhex('a') == 0xa); + cl_assert(git__fromhex('c') == 0xc); + cl_assert(git__fromhex('f') == 0xf); + + /* Failing cases */ + cl_assert(git__fromhex('g') == -1); + cl_assert(git__fromhex('z') == -1); + cl_assert(git__fromhex('X') == -1); +} diff --git a/tests/libgit2/core/iconv.c b/tests/libgit2/core/iconv.c new file mode 100644 index 000000000..af1b4eabf --- /dev/null +++ b/tests/libgit2/core/iconv.c @@ -0,0 +1,78 @@ +#include "clar_libgit2.h" +#include "fs_path.h" + +#ifdef GIT_USE_ICONV +static git_fs_path_iconv_t ic; +static char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; +static char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; +#endif + +void test_core_iconv__initialize(void) +{ +#ifdef GIT_USE_ICONV + cl_git_pass(git_fs_path_iconv_init_precompose(&ic)); +#endif +} + +void test_core_iconv__cleanup(void) +{ +#ifdef GIT_USE_ICONV + git_fs_path_iconv_clear(&ic); +#endif +} + +void test_core_iconv__unchanged(void) +{ +#ifdef GIT_USE_ICONV + const char *data = "Ascii data", *original = data; + size_t datalen = strlen(data); + + cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); + GIT_UNUSED(datalen); + + /* There are no high bits set, so this should leave data untouched */ + cl_assert(data == original); +#endif +} + +void test_core_iconv__decomposed_to_precomposed(void) +{ +#ifdef GIT_USE_ICONV + const char *data = nfd; + size_t datalen, nfdlen = strlen(nfd); + + datalen = nfdlen; + cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); + GIT_UNUSED(datalen); + + /* The decomposed nfd string should be transformed to the nfc form + * (on platforms where iconv is enabled, of course). + */ + cl_assert_equal_s(nfc, data); + + /* should be able to do it multiple times with the same git_fs_path_iconv_t */ + data = nfd; datalen = nfdlen; + cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); + cl_assert_equal_s(nfc, data); + + data = nfd; datalen = nfdlen; + cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); + cl_assert_equal_s(nfc, data); +#endif +} + +void test_core_iconv__precomposed_is_unmodified(void) +{ +#ifdef GIT_USE_ICONV + const char *data = nfc; + size_t datalen = strlen(nfc); + + cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); + GIT_UNUSED(datalen); + + /* data is already in precomposed form, so even though some bytes have + * the high-bit set, the iconv transform should result in no change. + */ + cl_assert_equal_s(nfc, data); +#endif +} diff --git a/tests/libgit2/core/init.c b/tests/libgit2/core/init.c new file mode 100644 index 000000000..eba77ef52 --- /dev/null +++ b/tests/libgit2/core/init.c @@ -0,0 +1,54 @@ +#include "clar_libgit2.h" + +void test_core_init__returns_count(void) +{ + /* libgit2_tests initializes us first, so we have an existing + * initialization. + */ + cl_assert_equal_i(2, git_libgit2_init()); + cl_assert_equal_i(3, git_libgit2_init()); + + cl_assert_equal_i(2, git_libgit2_shutdown()); + cl_assert_equal_i(1, git_libgit2_shutdown()); +} + +void test_core_init__reinit_succeeds(void) +{ + cl_assert_equal_i(0, git_libgit2_shutdown()); + cl_assert_equal_i(1, git_libgit2_init()); + cl_sandbox_set_search_path_defaults(); +} + +#ifdef GIT_THREADS +static void *reinit(void *unused) +{ + unsigned i; + + for (i = 0; i < 20; i++) { + cl_assert(git_libgit2_init() > 0); + cl_assert(git_libgit2_shutdown() >= 0); + } + + return unused; +} +#endif + +void test_core_init__concurrent_init_succeeds(void) +{ +#ifdef GIT_THREADS + git_thread threads[10]; + unsigned i; + + cl_assert_equal_i(2, git_libgit2_init()); + + for (i = 0; i < ARRAY_SIZE(threads); i++) + git_thread_create(&threads[i], reinit, NULL); + for (i = 0; i < ARRAY_SIZE(threads); i++) + git_thread_join(&threads[i], NULL); + + cl_assert_equal_i(1, git_libgit2_shutdown()); + cl_sandbox_set_search_path_defaults(); +#else + cl_skip(); +#endif +} diff --git a/tests/libgit2/core/integer.c b/tests/libgit2/core/integer.c new file mode 100644 index 000000000..18364ba62 --- /dev/null +++ b/tests/libgit2/core/integer.c @@ -0,0 +1,253 @@ +#include "clar_libgit2.h" + +void test_core_integer__multiply_int64_no_overflow(void) +{ +#if !defined(git__multiply_int64_overflow) + int64_t result = 0; + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x800000000000000))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x800000000000000))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x7fffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x7fffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x8000000000000000))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(0x1)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(-0x1)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(-0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x800000000000000))); + cl_assert_equal_i(result, INT64_C(0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x800000000000000))); + cl_assert_equal_i(result, INT64_C(-0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x7fffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x7fffffffffffffff))); + cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(-0x1)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(0x1)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(-0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x800000000000000))); + cl_assert_equal_i(result, INT64_C(-0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x800000000000000))); + cl_assert_equal_i(result, INT64_C(0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x7fffffffffffffff))); + cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x7fffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(-0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(0x4)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(-0x4)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x800000000000000))); + cl_assert_equal_i(result, INT64_C(0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x800000000000000))); + cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(-0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(-0x4)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(0x4)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x800000000000000))); + cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x800000000000000))); + cl_assert_equal_i(result, INT64_C(0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x4000000000000000))); + cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(-0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(-0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x4000000000000000), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); +#endif +} + +void test_core_integer__multiply_int64_overflow(void) +{ +#if !defined(git__multiply_int64_overflow) + int64_t result = 0; + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x4000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x4000000000000000), INT64_C(0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x8000000000000000))); +#endif +} + +void test_core_integer__multiply_int64_edge_cases(void) +{ +#if !defined(git__multiply_int64_overflow) + int64_t result = 0; + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x8000000000000000))); + cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x1))); +#endif +} diff --git a/tests/libgit2/core/link.c b/tests/libgit2/core/link.c new file mode 100644 index 000000000..a1e2706b2 --- /dev/null +++ b/tests/libgit2/core/link.c @@ -0,0 +1,630 @@ +#include "clar_libgit2.h" +#include "posix.h" + +#ifdef GIT_WIN32 +# include "win32/reparse.h" +#endif + +void test_core_link__cleanup(void) +{ +#ifdef GIT_WIN32 + RemoveDirectory("lstat_junction"); + RemoveDirectory("lstat_dangling"); + RemoveDirectory("lstat_dangling_dir"); + RemoveDirectory("lstat_dangling_junction"); + + RemoveDirectory("stat_junction"); + RemoveDirectory("stat_dangling"); + RemoveDirectory("stat_dangling_dir"); + RemoveDirectory("stat_dangling_junction"); +#endif +} + +#ifdef GIT_WIN32 +static bool should_run(void) +{ + static SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY }; + PSID admin_sid; + BOOL is_admin; + + cl_win32_pass(AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &admin_sid)); + cl_win32_pass(CheckTokenMembership(NULL, admin_sid, &is_admin)); + FreeSid(admin_sid); + + return is_admin ? true : false; +} +#else +static bool should_run(void) +{ + return true; +} +#endif + +static void do_symlink(const char *old, const char *new, int is_dir) +{ +#ifndef GIT_WIN32 + GIT_UNUSED(is_dir); + + cl_must_pass(symlink(old, new)); +#else + typedef DWORD (WINAPI *create_symlink_func)(LPCTSTR, LPCTSTR, DWORD); + HMODULE module; + create_symlink_func pCreateSymbolicLink; + + cl_assert(module = GetModuleHandle("kernel32")); + cl_assert(pCreateSymbolicLink = (create_symlink_func)(void *)GetProcAddress(module, "CreateSymbolicLinkA")); + + cl_win32_pass(pCreateSymbolicLink(new, old, is_dir)); +#endif +} + +static void do_hardlink(const char *old, const char *new) +{ +#ifndef GIT_WIN32 + cl_must_pass(link(old, new)); +#else + typedef DWORD (WINAPI *create_hardlink_func)(LPCTSTR, LPCTSTR, LPSECURITY_ATTRIBUTES); + HMODULE module; + create_hardlink_func pCreateHardLink; + + cl_assert(module = GetModuleHandle("kernel32")); + cl_assert(pCreateHardLink = (create_hardlink_func)(void *)GetProcAddress(module, "CreateHardLinkA")); + + cl_win32_pass(pCreateHardLink(new, old, 0)); +#endif +} + +#ifdef GIT_WIN32 + +static void do_junction(const char *old, const char *new) +{ + GIT_REPARSE_DATA_BUFFER *reparse_buf; + HANDLE handle; + git_str unparsed_buf = GIT_STR_INIT; + wchar_t *subst_utf16, *print_utf16; + DWORD ioctl_ret; + int subst_utf16_len, subst_byte_len, print_utf16_len, print_byte_len, ret; + USHORT reparse_buflen; + size_t i; + + /* Junction targets must be the unparsed name, starting with \??\, using + * backslashes instead of forward, and end in a trailing backslash. + * eg: \??\C:\Foo\ + */ + git_str_puts(&unparsed_buf, "\\??\\"); + + for (i = 0; i < strlen(old); i++) + git_str_putc(&unparsed_buf, old[i] == '/' ? '\\' : old[i]); + + git_str_putc(&unparsed_buf, '\\'); + + subst_utf16_len = git__utf8_to_16(NULL, 0, git_str_cstr(&unparsed_buf)); + subst_byte_len = subst_utf16_len * sizeof(WCHAR); + + print_utf16_len = subst_utf16_len - 4; + print_byte_len = subst_byte_len - (4 * sizeof(WCHAR)); + + /* The junction must be an empty directory before the junction attribute + * can be added. + */ + cl_win32_pass(CreateDirectoryA(new, NULL)); + + handle = CreateFileA(new, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); + cl_win32_pass(handle != INVALID_HANDLE_VALUE); + + reparse_buflen = (USHORT)(REPARSE_DATA_HEADER_SIZE + + REPARSE_DATA_MOUNTPOINT_HEADER_SIZE + + subst_byte_len + sizeof(WCHAR) + + print_byte_len + sizeof(WCHAR)); + + reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen); + cl_assert(reparse_buf); + + subst_utf16 = reparse_buf->ReparseBuffer.MountPoint.PathBuffer; + print_utf16 = subst_utf16 + subst_utf16_len + 1; + + ret = git__utf8_to_16(subst_utf16, subst_utf16_len + 1, + git_str_cstr(&unparsed_buf)); + cl_assert_equal_i(subst_utf16_len, ret); + + ret = git__utf8_to_16(print_utf16, + print_utf16_len + 1, git_str_cstr(&unparsed_buf) + 4); + cl_assert_equal_i(print_utf16_len, ret); + + reparse_buf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset = 0; + reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength = subst_byte_len; + reparse_buf->ReparseBuffer.MountPoint.PrintNameOffset = (USHORT)(subst_byte_len + sizeof(WCHAR)); + reparse_buf->ReparseBuffer.MountPoint.PrintNameLength = print_byte_len; + reparse_buf->ReparseDataLength = reparse_buflen - REPARSE_DATA_HEADER_SIZE; + + cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, + reparse_buf, reparse_buflen, NULL, 0, &ioctl_ret, NULL)); + + CloseHandle(handle); + LocalFree(reparse_buf); + + git_str_dispose(&unparsed_buf); +} + +static void do_custom_reparse(const char *path) +{ + REPARSE_GUID_DATA_BUFFER *reparse_buf; + HANDLE handle; + DWORD ioctl_ret; + + const char *reparse_data = "Reparse points are silly."; + size_t reparse_buflen = REPARSE_GUID_DATA_BUFFER_HEADER_SIZE + + strlen(reparse_data) + 1; + + reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen); + cl_assert(reparse_buf); + + reparse_buf->ReparseTag = 42; + reparse_buf->ReparseDataLength = (WORD)(strlen(reparse_data) + 1); + + reparse_buf->ReparseGuid.Data1 = 0xdeadbeef; + reparse_buf->ReparseGuid.Data2 = 0xdead; + reparse_buf->ReparseGuid.Data3 = 0xbeef; + reparse_buf->ReparseGuid.Data4[0] = 42; + reparse_buf->ReparseGuid.Data4[1] = 42; + reparse_buf->ReparseGuid.Data4[2] = 42; + reparse_buf->ReparseGuid.Data4[3] = 42; + reparse_buf->ReparseGuid.Data4[4] = 42; + reparse_buf->ReparseGuid.Data4[5] = 42; + reparse_buf->ReparseGuid.Data4[6] = 42; + reparse_buf->ReparseGuid.Data4[7] = 42; + reparse_buf->ReparseGuid.Data4[8] = 42; + + memcpy(reparse_buf->GenericReparseBuffer.DataBuffer, + reparse_data, strlen(reparse_data) + 1); + + handle = CreateFileA(path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); + cl_win32_pass(handle != INVALID_HANDLE_VALUE); + + cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, + reparse_buf, + reparse_buf->ReparseDataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, + NULL, 0, &ioctl_ret, NULL)); + + CloseHandle(handle); + LocalFree(reparse_buf); +} + +#endif + +void test_core_link__stat_regular_file(void) +{ + struct stat st; + + cl_git_rewritefile("stat_regfile", "This is a regular file!\n"); + + cl_must_pass(p_stat("stat_regfile", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(24, st.st_size); +} + +void test_core_link__lstat_regular_file(void) +{ + struct stat st; + + cl_git_rewritefile("lstat_regfile", "This is a regular file!\n"); + + cl_must_pass(p_stat("lstat_regfile", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(24, st.st_size); +} + +void test_core_link__stat_symlink(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + cl_git_rewritefile("stat_target", "This is the target of a symbolic link.\n"); + do_symlink("stat_target", "stat_symlink", 0); + + cl_must_pass(p_stat("stat_target", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(39, st.st_size); + + cl_must_pass(p_stat("stat_symlink", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(39, st.st_size); +} + +void test_core_link__stat_symlink_directory(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + p_mkdir("stat_dirtarget", 0777); + do_symlink("stat_dirtarget", "stat_dirlink", 1); + + cl_must_pass(p_stat("stat_dirtarget", &st)); + cl_assert(S_ISDIR(st.st_mode)); + + cl_must_pass(p_stat("stat_dirlink", &st)); + cl_assert(S_ISDIR(st.st_mode)); +} + +void test_core_link__stat_symlink_chain(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + cl_git_rewritefile("stat_final_target", "Final target of some symbolic links...\n"); + do_symlink("stat_final_target", "stat_chain_3", 0); + do_symlink("stat_chain_3", "stat_chain_2", 0); + do_symlink("stat_chain_2", "stat_chain_1", 0); + + cl_must_pass(p_stat("stat_chain_1", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(39, st.st_size); +} + +void test_core_link__stat_dangling_symlink(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + do_symlink("stat_nonexistent", "stat_dangling", 0); + + cl_must_fail(p_stat("stat_nonexistent", &st)); + cl_must_fail(p_stat("stat_dangling", &st)); +} + +void test_core_link__stat_dangling_symlink_directory(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + do_symlink("stat_nonexistent", "stat_dangling_dir", 1); + + cl_must_fail(p_stat("stat_nonexistent_dir", &st)); + cl_must_fail(p_stat("stat_dangling", &st)); +} + +void test_core_link__lstat_symlink(void) +{ + git_str target_path = GIT_STR_INIT; + struct stat st; + + if (!should_run()) + clar__skip(); + + /* Windows always writes the canonical path as the link target, so + * write the full path on all platforms. + */ + git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_target"); + + cl_git_rewritefile("lstat_target", "This is the target of a symbolic link.\n"); + do_symlink(git_str_cstr(&target_path), "lstat_symlink", 0); + + cl_must_pass(p_lstat("lstat_target", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(39, st.st_size); + + cl_must_pass(p_lstat("lstat_symlink", &st)); + cl_assert(S_ISLNK(st.st_mode)); + cl_assert_equal_i(git_str_len(&target_path), st.st_size); + + git_str_dispose(&target_path); +} + +void test_core_link__lstat_symlink_directory(void) +{ + git_str target_path = GIT_STR_INIT; + struct stat st; + + if (!should_run()) + clar__skip(); + + git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_dirtarget"); + + p_mkdir("lstat_dirtarget", 0777); + do_symlink(git_str_cstr(&target_path), "lstat_dirlink", 1); + + cl_must_pass(p_lstat("lstat_dirtarget", &st)); + cl_assert(S_ISDIR(st.st_mode)); + + cl_must_pass(p_lstat("lstat_dirlink", &st)); + cl_assert(S_ISLNK(st.st_mode)); + cl_assert_equal_i(git_str_len(&target_path), st.st_size); + + git_str_dispose(&target_path); +} + +void test_core_link__lstat_dangling_symlink(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + do_symlink("lstat_nonexistent", "lstat_dangling", 0); + + cl_must_fail(p_lstat("lstat_nonexistent", &st)); + + cl_must_pass(p_lstat("lstat_dangling", &st)); + cl_assert(S_ISLNK(st.st_mode)); + cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size); +} + +void test_core_link__lstat_dangling_symlink_directory(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + do_symlink("lstat_nonexistent", "lstat_dangling_dir", 1); + + cl_must_fail(p_lstat("lstat_nonexistent", &st)); + + cl_must_pass(p_lstat("lstat_dangling_dir", &st)); + cl_assert(S_ISLNK(st.st_mode)); + cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size); +} + +void test_core_link__stat_junction(void) +{ +#ifdef GIT_WIN32 + git_str target_path = GIT_STR_INIT; + struct stat st; + + git_str_join(&target_path, '/', clar_sandbox_path(), "stat_junctarget"); + + p_mkdir("stat_junctarget", 0777); + do_junction(git_str_cstr(&target_path), "stat_junction"); + + cl_must_pass(p_stat("stat_junctarget", &st)); + cl_assert(S_ISDIR(st.st_mode)); + + cl_must_pass(p_stat("stat_junction", &st)); + cl_assert(S_ISDIR(st.st_mode)); + + git_str_dispose(&target_path); +#endif +} + +void test_core_link__stat_dangling_junction(void) +{ +#ifdef GIT_WIN32 + git_str target_path = GIT_STR_INIT; + struct stat st; + + git_str_join(&target_path, '/', clar_sandbox_path(), "stat_nonexistent_junctarget"); + + p_mkdir("stat_nonexistent_junctarget", 0777); + do_junction(git_str_cstr(&target_path), "stat_dangling_junction"); + + RemoveDirectory("stat_nonexistent_junctarget"); + + cl_must_fail(p_stat("stat_nonexistent_junctarget", &st)); + cl_must_fail(p_stat("stat_dangling_junction", &st)); + + git_str_dispose(&target_path); +#endif +} + +void test_core_link__lstat_junction(void) +{ +#ifdef GIT_WIN32 + git_str target_path = GIT_STR_INIT; + struct stat st; + + git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_junctarget"); + + p_mkdir("lstat_junctarget", 0777); + do_junction(git_str_cstr(&target_path), "lstat_junction"); + + cl_must_pass(p_lstat("lstat_junctarget", &st)); + cl_assert(S_ISDIR(st.st_mode)); + + cl_must_pass(p_lstat("lstat_junction", &st)); + cl_assert(S_ISLNK(st.st_mode)); + + git_str_dispose(&target_path); +#endif +} + +void test_core_link__lstat_dangling_junction(void) +{ +#ifdef GIT_WIN32 + git_str target_path = GIT_STR_INIT; + struct stat st; + + git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_nonexistent_junctarget"); + + p_mkdir("lstat_nonexistent_junctarget", 0777); + do_junction(git_str_cstr(&target_path), "lstat_dangling_junction"); + + RemoveDirectory("lstat_nonexistent_junctarget"); + + cl_must_fail(p_lstat("lstat_nonexistent_junctarget", &st)); + + cl_must_pass(p_lstat("lstat_dangling_junction", &st)); + cl_assert(S_ISLNK(st.st_mode)); + cl_assert_equal_i(git_str_len(&target_path), st.st_size); + + git_str_dispose(&target_path); +#endif +} + +void test_core_link__stat_hardlink(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + cl_git_rewritefile("stat_hardlink1", "This file has many names!\n"); + do_hardlink("stat_hardlink1", "stat_hardlink2"); + + cl_must_pass(p_stat("stat_hardlink1", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(26, st.st_size); + + cl_must_pass(p_stat("stat_hardlink2", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(26, st.st_size); +} + +void test_core_link__lstat_hardlink(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + cl_git_rewritefile("lstat_hardlink1", "This file has many names!\n"); + do_hardlink("lstat_hardlink1", "lstat_hardlink2"); + + cl_must_pass(p_lstat("lstat_hardlink1", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(26, st.st_size); + + cl_must_pass(p_lstat("lstat_hardlink2", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(26, st.st_size); +} + +void test_core_link__stat_reparse_point(void) +{ +#ifdef GIT_WIN32 + struct stat st; + + /* Generic reparse points should be treated as regular files, only + * symlinks and junctions should be treated as links. + */ + + cl_git_rewritefile("stat_reparse", "This is a reparse point!\n"); + do_custom_reparse("stat_reparse"); + + cl_must_pass(p_lstat("stat_reparse", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(25, st.st_size); +#endif +} + +void test_core_link__lstat_reparse_point(void) +{ +#ifdef GIT_WIN32 + struct stat st; + + cl_git_rewritefile("lstat_reparse", "This is a reparse point!\n"); + do_custom_reparse("lstat_reparse"); + + cl_must_pass(p_lstat("lstat_reparse", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(25, st.st_size); +#endif +} + +void test_core_link__readlink_nonexistent_file(void) +{ + char buf[2048]; + + cl_must_fail(p_readlink("readlink_nonexistent", buf, 2048)); + cl_assert_equal_i(ENOENT, errno); +} + +void test_core_link__readlink_normal_file(void) +{ + char buf[2048]; + + cl_git_rewritefile("readlink_regfile", "This is a regular file!\n"); + cl_must_fail(p_readlink("readlink_regfile", buf, 2048)); + cl_assert_equal_i(EINVAL, errno); +} + +void test_core_link__readlink_symlink(void) +{ + git_str target_path = GIT_STR_INIT; + int len; + char buf[2048]; + + if (!should_run()) + clar__skip(); + + git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_target"); + + cl_git_rewritefile("readlink_target", "This is the target of a symlink\n"); + do_symlink(git_str_cstr(&target_path), "readlink_link", 0); + + len = p_readlink("readlink_link", buf, 2048); + cl_must_pass(len); + + buf[len] = 0; + + cl_assert_equal_s(git_str_cstr(&target_path), buf); + + git_str_dispose(&target_path); +} + +void test_core_link__readlink_dangling(void) +{ + git_str target_path = GIT_STR_INIT; + int len; + char buf[2048]; + + if (!should_run()) + clar__skip(); + + git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_nonexistent"); + + do_symlink(git_str_cstr(&target_path), "readlink_dangling", 0); + + len = p_readlink("readlink_dangling", buf, 2048); + cl_must_pass(len); + + buf[len] = 0; + + cl_assert_equal_s(git_str_cstr(&target_path), buf); + + git_str_dispose(&target_path); +} + +void test_core_link__readlink_multiple(void) +{ + git_str target_path = GIT_STR_INIT, + path3 = GIT_STR_INIT, path2 = GIT_STR_INIT, path1 = GIT_STR_INIT; + int len; + char buf[2048]; + + if (!should_run()) + clar__skip(); + + git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_final"); + git_str_join(&path3, '/', clar_sandbox_path(), "readlink_3"); + git_str_join(&path2, '/', clar_sandbox_path(), "readlink_2"); + git_str_join(&path1, '/', clar_sandbox_path(), "readlink_1"); + + do_symlink(git_str_cstr(&target_path), git_str_cstr(&path3), 0); + do_symlink(git_str_cstr(&path3), git_str_cstr(&path2), 0); + do_symlink(git_str_cstr(&path2), git_str_cstr(&path1), 0); + + len = p_readlink("readlink_1", buf, 2048); + cl_must_pass(len); + + buf[len] = 0; + + cl_assert_equal_s(git_str_cstr(&path2), buf); + + git_str_dispose(&path1); + git_str_dispose(&path2); + git_str_dispose(&path3); + git_str_dispose(&target_path); +} diff --git a/tests/libgit2/core/memmem.c b/tests/libgit2/core/memmem.c new file mode 100644 index 000000000..fd9986d01 --- /dev/null +++ b/tests/libgit2/core/memmem.c @@ -0,0 +1,46 @@ +#include "clar_libgit2.h" + +static void assert_found(const char *haystack, const char *needle, size_t expected_pos) +{ + cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0, + needle, needle ? strlen(needle) : 0), + haystack + expected_pos); +} + +static void assert_absent(const char *haystack, const char *needle) +{ + cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0, + needle, needle ? strlen(needle) : 0), + NULL); +} + +void test_core_memmem__found(void) +{ + assert_found("a", "a", 0); + assert_found("ab", "a", 0); + assert_found("ba", "a", 1); + assert_found("aa", "a", 0); + assert_found("aab", "aa", 0); + assert_found("baa", "aa", 1); + assert_found("dabc", "abc", 1); + assert_found("abababc", "abc", 4); +} + +void test_core_memmem__absent(void) +{ + assert_absent("a", "b"); + assert_absent("a", "aa"); + assert_absent("ba", "ab"); + assert_absent("ba", "ab"); + assert_absent("abc", "abcd"); + assert_absent("abcabcabc", "bcac"); +} + +void test_core_memmem__edgecases(void) +{ + assert_absent(NULL, NULL); + assert_absent("a", NULL); + assert_absent(NULL, "a"); + assert_absent("", "a"); + assert_absent("a", ""); +} diff --git a/tests/libgit2/core/mkdir.c b/tests/libgit2/core/mkdir.c new file mode 100644 index 000000000..58a4cfcdb --- /dev/null +++ b/tests/libgit2/core/mkdir.c @@ -0,0 +1,291 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "posix.h" + +static void cleanup_basic_dirs(void *ref) +{ + GIT_UNUSED(ref); + git_futils_rmdir_r("d0", NULL, GIT_RMDIR_EMPTY_HIERARCHY); + git_futils_rmdir_r("d1", NULL, GIT_RMDIR_EMPTY_HIERARCHY); + git_futils_rmdir_r("d2", NULL, GIT_RMDIR_EMPTY_HIERARCHY); + git_futils_rmdir_r("d3", NULL, GIT_RMDIR_EMPTY_HIERARCHY); + git_futils_rmdir_r("d4", NULL, GIT_RMDIR_EMPTY_HIERARCHY); +} + +void test_core_mkdir__absolute(void) +{ + git_str path = GIT_STR_INIT; + + cl_set_cleanup(cleanup_basic_dirs, NULL); + + git_str_joinpath(&path, clar_sandbox_path(), "d0"); + + /* make a directory */ + cl_assert(!git_fs_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); + cl_assert(git_fs_path_isdir(path.ptr)); + + git_str_joinpath(&path, path.ptr, "subdir"); + cl_assert(!git_fs_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); + cl_assert(git_fs_path_isdir(path.ptr)); + + /* ensure mkdir_r works for a single subdir */ + git_str_joinpath(&path, path.ptr, "another"); + cl_assert(!git_fs_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); + cl_assert(git_fs_path_isdir(path.ptr)); + + /* ensure mkdir_r works */ + git_str_joinpath(&path, clar_sandbox_path(), "d1/foo/bar/asdf"); + cl_assert(!git_fs_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); + cl_assert(git_fs_path_isdir(path.ptr)); + + /* ensure we don't imply recursive */ + git_str_joinpath(&path, clar_sandbox_path(), "d2/foo/bar/asdf"); + cl_assert(!git_fs_path_isdir(path.ptr)); + cl_git_fail(git_futils_mkdir(path.ptr, 0755, 0)); + cl_assert(!git_fs_path_isdir(path.ptr)); + + git_str_dispose(&path); +} + +void test_core_mkdir__basic(void) +{ + cl_set_cleanup(cleanup_basic_dirs, NULL); + + /* make a directory */ + cl_assert(!git_fs_path_isdir("d0")); + cl_git_pass(git_futils_mkdir("d0", 0755, 0)); + cl_assert(git_fs_path_isdir("d0")); + + /* make a path */ + cl_assert(!git_fs_path_isdir("d1")); + cl_git_pass(git_futils_mkdir("d1/d1.1/d1.2", 0755, GIT_MKDIR_PATH)); + cl_assert(git_fs_path_isdir("d1")); + cl_assert(git_fs_path_isdir("d1/d1.1")); + cl_assert(git_fs_path_isdir("d1/d1.1/d1.2")); + + /* make a dir exclusively */ + cl_assert(!git_fs_path_isdir("d2")); + cl_git_pass(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL)); + cl_assert(git_fs_path_isdir("d2")); + + /* make exclusive failure */ + cl_git_fail(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL)); + + /* make a path exclusively */ + cl_assert(!git_fs_path_isdir("d3")); + cl_git_pass(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_assert(git_fs_path_isdir("d3")); + cl_assert(git_fs_path_isdir("d3/d3.1/d3.2")); + + /* make exclusive path failure */ + cl_git_fail(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + /* ??? Should EXCL only apply to the last item in the path? */ + + /* path with trailing slash? */ + cl_assert(!git_fs_path_isdir("d4")); + cl_git_pass(git_futils_mkdir("d4/d4.1/", 0755, GIT_MKDIR_PATH)); + cl_assert(git_fs_path_isdir("d4/d4.1")); +} + +static void cleanup_basedir(void *ref) +{ + GIT_UNUSED(ref); + git_futils_rmdir_r("base", NULL, GIT_RMDIR_EMPTY_HIERARCHY); +} + +void test_core_mkdir__with_base(void) +{ +#define BASEDIR "base/dir/here" + + cl_set_cleanup(cleanup_basedir, NULL); + + cl_git_pass(git_futils_mkdir(BASEDIR, 0755, GIT_MKDIR_PATH)); + + cl_git_pass(git_futils_mkdir_relative("a", BASEDIR, 0755, 0, NULL)); + cl_assert(git_fs_path_isdir(BASEDIR "/a")); + + cl_git_pass(git_futils_mkdir_relative("b/b1/b2", BASEDIR, 0755, GIT_MKDIR_PATH, NULL)); + cl_assert(git_fs_path_isdir(BASEDIR "/b/b1/b2")); + + /* exclusive with existing base */ + cl_git_pass(git_futils_mkdir_relative("c/c1/c2", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); + + /* fail: exclusive with duplicated suffix */ + cl_git_fail(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); + + /* fail: exclusive with any duplicated component */ + cl_git_fail(git_futils_mkdir_relative("c/cz/cz", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); + + /* success: exclusive without path */ + cl_git_pass(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_EXCL, NULL)); + + /* path with shorter base and existing dirs */ + cl_git_pass(git_futils_mkdir_relative("dir/here/d/", "base", 0755, GIT_MKDIR_PATH, NULL)); + cl_assert(git_fs_path_isdir("base/dir/here/d")); + + /* fail: path with shorter base and existing dirs */ + cl_git_fail(git_futils_mkdir_relative("dir/here/e/", "base", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); + + /* fail: base with missing components */ + cl_git_fail(git_futils_mkdir_relative("f/", "base/missing", 0755, GIT_MKDIR_PATH, NULL)); + + /* success: shift missing component to path */ + cl_git_pass(git_futils_mkdir_relative("missing/f/", "base/", 0755, GIT_MKDIR_PATH, NULL)); +} + +static void cleanup_chmod_root(void *ref) +{ + mode_t *mode = ref; + + if (mode != NULL) { + (void)p_umask(*mode); + git__free(mode); + } + + git_futils_rmdir_r("r", NULL, GIT_RMDIR_EMPTY_HIERARCHY); +} + +#define check_mode(X,A) check_mode_at_line((X), (A), __FILE__, __func__, __LINE__) + +static void check_mode_at_line( + mode_t expected, mode_t actual, + const char *file, const char *func, int line) +{ + /* FAT filesystems don't support exec bit, nor group/world bits */ + if (!cl_is_chmod_supported()) { + expected &= 0600; + actual &= 0600; + } + + clar__assert_equal( + file, func, line, "expected_mode != actual_mode", 1, + "%07o", (int)expected, (int)(actual & 0777)); +} + +void test_core_mkdir__chmods(void) +{ + struct stat st; + mode_t *old = git__malloc(sizeof(mode_t)); + *old = p_umask(022); + + cl_set_cleanup(cleanup_chmod_root, old); + + cl_git_pass(git_futils_mkdir("r", 0777, 0)); + + cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL)); + + cl_git_pass(git_fs_path_lstat("r/mode", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode/is", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode/is/important", &st)); + check_mode(0755, st.st_mode); + + cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL)); + + cl_git_pass(git_fs_path_lstat("r/mode2", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode2/is2", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode2/is2/important2", &st)); + check_mode(0777, st.st_mode); + + cl_git_pass(git_futils_mkdir_relative("mode3/is3/important3", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL)); + + cl_git_pass(git_fs_path_lstat("r/mode3", &st)); + check_mode(0777, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode3/is3", &st)); + check_mode(0777, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode3/is3/important3", &st)); + check_mode(0777, st.st_mode); + + /* test that we chmod existing dir */ + + cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL)); + + cl_git_pass(git_fs_path_lstat("r/mode", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode/is", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode/is/important", &st)); + check_mode(0777, st.st_mode); + + /* test that we chmod even existing dirs if CHMOD_PATH is set */ + + cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2.1", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL)); + + cl_git_pass(git_fs_path_lstat("r/mode2", &st)); + check_mode(0777, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode2/is2", &st)); + check_mode(0777, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode2/is2/important2.1", &st)); + check_mode(0777, st.st_mode); +} + +void test_core_mkdir__keeps_parent_symlinks(void) +{ +#ifndef GIT_WIN32 + git_str path = GIT_STR_INIT; + + cl_set_cleanup(cleanup_basic_dirs, NULL); + + /* make a directory */ + cl_assert(!git_fs_path_isdir("d0")); + cl_git_pass(git_futils_mkdir("d0", 0755, 0)); + cl_assert(git_fs_path_isdir("d0")); + + cl_must_pass(symlink("d0", "d1")); + cl_assert(git_fs_path_islink("d1")); + + cl_git_pass(git_futils_mkdir("d1/foo/bar", 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS)); + cl_assert(git_fs_path_islink("d1")); + cl_assert(git_fs_path_isdir("d1/foo/bar")); + cl_assert(git_fs_path_isdir("d0/foo/bar")); + + cl_must_pass(symlink("d0", "d2")); + cl_assert(git_fs_path_islink("d2")); + + git_str_joinpath(&path, clar_sandbox_path(), "d2/other/dir"); + + cl_git_pass(git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS)); + cl_assert(git_fs_path_islink("d2")); + cl_assert(git_fs_path_isdir("d2/other/dir")); + cl_assert(git_fs_path_isdir("d0/other/dir")); + + git_str_dispose(&path); +#endif +} + +void test_core_mkdir__mkdir_path_inside_unwriteable_parent(void) +{ + struct stat st; + mode_t *old; + + /* FAT filesystems don't support exec bit, nor group/world bits */ + if (!cl_is_chmod_supported()) + return; + + cl_assert((old = git__malloc(sizeof(mode_t))) != NULL); + *old = p_umask(022); + cl_set_cleanup(cleanup_chmod_root, old); + + cl_git_pass(git_futils_mkdir("r", 0777, 0)); + cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL)); + cl_git_pass(git_fs_path_lstat("r/mode", &st)); + check_mode(0755, st.st_mode); + + cl_must_pass(p_chmod("r/mode", 0111)); + cl_git_pass(git_fs_path_lstat("r/mode", &st)); + check_mode(0111, st.st_mode); + + cl_git_pass( + git_futils_mkdir_relative("mode/is/okay/inside", "r", 0777, GIT_MKDIR_PATH, NULL)); + cl_git_pass(git_fs_path_lstat("r/mode/is/okay/inside", &st)); + check_mode(0755, st.st_mode); + + cl_must_pass(p_chmod("r/mode", 0777)); +} diff --git a/tests/libgit2/core/oid.c b/tests/libgit2/core/oid.c new file mode 100644 index 000000000..894feadf6 --- /dev/null +++ b/tests/libgit2/core/oid.c @@ -0,0 +1,79 @@ +#include "clar_libgit2.h" +#include "oid.h" + +static git_oid id; +static git_oid idp; +static git_oid idm; +const char *str_oid = "ae90f12eea699729ed24555e40b9fd669da12a12"; +const char *str_oid_p = "ae90f12eea699729ed"; +const char *str_oid_m = "ae90f12eea699729ed24555e40b9fd669da12a12THIS IS EXTRA TEXT THAT SHOULD GET IGNORED"; + +void test_core_oid__initialize(void) +{ + cl_git_pass(git_oid_fromstr(&id, str_oid)); + cl_git_pass(git_oid_fromstrp(&idp, str_oid_p)); + cl_git_fail(git_oid_fromstrp(&idm, str_oid_m)); +} + +void test_core_oid__streq(void) +{ + cl_assert_equal_i(0, git_oid_streq(&id, str_oid)); + cl_assert_equal_i(-1, git_oid_streq(&id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); + + cl_assert_equal_i(-1, git_oid_streq(&id, "deadbeef")); + cl_assert_equal_i(-1, git_oid_streq(&id, "I'm not an oid.... :)")); + + cl_assert_equal_i(0, git_oid_streq(&idp, "ae90f12eea699729ed0000000000000000000000")); + cl_assert_equal_i(0, git_oid_streq(&idp, "ae90f12eea699729ed")); + cl_assert_equal_i(-1, git_oid_streq(&idp, "ae90f12eea699729ed1")); + cl_assert_equal_i(-1, git_oid_streq(&idp, "ae90f12eea699729ec")); + cl_assert_equal_i(-1, git_oid_streq(&idp, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); + + cl_assert_equal_i(-1, git_oid_streq(&idp, "deadbeef")); + cl_assert_equal_i(-1, git_oid_streq(&idp, "I'm not an oid.... :)")); +} + +void test_core_oid__strcmp(void) +{ + cl_assert_equal_i(0, git_oid_strcmp(&id, str_oid)); + cl_assert(git_oid_strcmp(&id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") < 0); + + cl_assert(git_oid_strcmp(&id, "deadbeef") < 0); + cl_assert_equal_i(-1, git_oid_strcmp(&id, "I'm not an oid.... :)")); + + cl_assert_equal_i(0, git_oid_strcmp(&idp, "ae90f12eea699729ed0000000000000000000000")); + cl_assert_equal_i(0, git_oid_strcmp(&idp, "ae90f12eea699729ed")); + cl_assert(git_oid_strcmp(&idp, "ae90f12eea699729ed1") < 0); + cl_assert(git_oid_strcmp(&idp, "ae90f12eea699729ec") > 0); + cl_assert(git_oid_strcmp(&idp, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") < 0); + + cl_assert(git_oid_strcmp(&idp, "deadbeef") < 0); + cl_assert_equal_i(-1, git_oid_strcmp(&idp, "I'm not an oid.... :)")); +} + +void test_core_oid__ncmp(void) +{ + cl_assert(!git_oid_ncmp(&id, &idp, 0)); + cl_assert(!git_oid_ncmp(&id, &idp, 1)); + cl_assert(!git_oid_ncmp(&id, &idp, 2)); + cl_assert(!git_oid_ncmp(&id, &idp, 17)); + cl_assert(!git_oid_ncmp(&id, &idp, 18)); + cl_assert(git_oid_ncmp(&id, &idp, 19)); + cl_assert(git_oid_ncmp(&id, &idp, 40)); + cl_assert(git_oid_ncmp(&id, &idp, 41)); + cl_assert(git_oid_ncmp(&id, &idp, 42)); + + cl_assert(!git_oid_ncmp(&id, &id, 0)); + cl_assert(!git_oid_ncmp(&id, &id, 1)); + cl_assert(!git_oid_ncmp(&id, &id, 39)); + cl_assert(!git_oid_ncmp(&id, &id, 40)); + cl_assert(!git_oid_ncmp(&id, &id, 41)); +} + +void test_core_oid__is_hexstr(void) +{ + cl_assert(git_oid__is_hexstr("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); + cl_assert(!git_oid__is_hexstr("deadbeefdeadbeef")); + cl_assert(!git_oid__is_hexstr("zeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); + cl_assert(!git_oid__is_hexstr("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef1")); +} diff --git a/tests/libgit2/core/oidmap.c b/tests/libgit2/core/oidmap.c new file mode 100644 index 000000000..7f98287a6 --- /dev/null +++ b/tests/libgit2/core/oidmap.c @@ -0,0 +1,127 @@ +#include "clar_libgit2.h" +#include "oidmap.h" + +static struct { + git_oid oid; + size_t extra; +} test_oids[0x0FFF]; + +static git_oidmap *g_map; + +void test_core_oidmap__initialize(void) +{ + uint32_t i, j; + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { + uint32_t segment = i / 8; + int modi = i - (segment * 8); + + test_oids[i].extra = i; + + for (j = 0; j < GIT_OID_RAWSZ / 4; ++j) { + test_oids[i].oid.id[j * 4 ] = (unsigned char)modi; + test_oids[i].oid.id[j * 4 + 1] = (unsigned char)(modi >> 8); + test_oids[i].oid.id[j * 4 + 2] = (unsigned char)(modi >> 16); + test_oids[i].oid.id[j * 4 + 3] = (unsigned char)(modi >> 24); + } + + test_oids[i].oid.id[ 8] = (unsigned char)i; + test_oids[i].oid.id[ 9] = (unsigned char)(i >> 8); + test_oids[i].oid.id[10] = (unsigned char)(i >> 16); + test_oids[i].oid.id[11] = (unsigned char)(i >> 24); + } + + cl_git_pass(git_oidmap_new(&g_map)); +} + +void test_core_oidmap__cleanup(void) +{ + git_oidmap_free(g_map); +} + +void test_core_oidmap__basic(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { + cl_assert(!git_oidmap_exists(g_map, &test_oids[i].oid)); + cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); + } + + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { + cl_assert(git_oidmap_exists(g_map, &test_oids[i].oid)); + cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[i].oid), &test_oids[i]); + } +} + +void test_core_oidmap__hash_collision(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { + cl_assert(!git_oidmap_exists(g_map, &test_oids[i].oid)); + cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); + } + + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { + cl_assert(git_oidmap_exists(g_map, &test_oids[i].oid)); + cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[i].oid), &test_oids[i]); + } +} + +void test_core_oidmap__get_succeeds_with_existing_keys(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) + cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); + + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) + cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[i].oid), &test_oids[i]); +} + +void test_core_oidmap__get_fails_with_nonexisting_key(void) +{ + size_t i; + + /* Do _not_ add last OID to verify that we cannot look it up */ + for (i = 0; i < ARRAY_SIZE(test_oids) - 1; ++i) + cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); + + cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[ARRAY_SIZE(test_oids) - 1].oid), NULL); +} + +void test_core_oidmap__setting_oid_persists(void) +{ + git_oid oids[] = { + {{ 0x01 }}, + {{ 0x02 }}, + {{ 0x03 }} + }; + + cl_git_pass(git_oidmap_set(g_map, &oids[0], "one")); + cl_git_pass(git_oidmap_set(g_map, &oids[1], "two")); + cl_git_pass(git_oidmap_set(g_map, &oids[2], "three")); + + cl_assert_equal_s(git_oidmap_get(g_map, &oids[0]), "one"); + cl_assert_equal_s(git_oidmap_get(g_map, &oids[1]), "two"); + cl_assert_equal_s(git_oidmap_get(g_map, &oids[2]), "three"); +} + +void test_core_oidmap__setting_existing_key_updates(void) +{ + git_oid oids[] = { + {{ 0x01 }}, + {{ 0x02 }}, + {{ 0x03 }} + }; + + cl_git_pass(git_oidmap_set(g_map, &oids[0], "one")); + cl_git_pass(git_oidmap_set(g_map, &oids[1], "two")); + cl_git_pass(git_oidmap_set(g_map, &oids[2], "three")); + cl_assert_equal_i(git_oidmap_size(g_map), 3); + + cl_git_pass(git_oidmap_set(g_map, &oids[1], "other")); + cl_assert_equal_i(git_oidmap_size(g_map), 3); + + cl_assert_equal_s(git_oidmap_get(g_map, &oids[1]), "other"); +} diff --git a/tests/libgit2/core/opts.c b/tests/libgit2/core/opts.c new file mode 100644 index 000000000..e8f65d510 --- /dev/null +++ b/tests/libgit2/core/opts.c @@ -0,0 +1,71 @@ +#include "clar_libgit2.h" +#include "cache.h" + +void test_core_opts__cleanup(void) +{ + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, NULL, 0)); +} + +void test_core_opts__readwrite(void) +{ + size_t old_val = 0; + size_t new_val = 0; + + git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &old_val); + git_libgit2_opts(GIT_OPT_SET_MWINDOW_SIZE, (size_t)1234); + git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &new_val); + + cl_assert(new_val == 1234); + + git_libgit2_opts(GIT_OPT_SET_MWINDOW_SIZE, old_val); + git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &new_val); + + cl_assert(new_val == old_val); +} + +void test_core_opts__invalid_option(void) +{ + cl_git_fail(git_libgit2_opts(-1, "foobar")); +} + +void test_core_opts__extensions_query(void) +{ + git_strarray out = { 0 }; + + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); + + cl_assert_equal_sz(out.count, 1); + cl_assert_equal_s("noop", out.strings[0]); + + git_strarray_dispose(&out); +} + +void test_core_opts__extensions_add(void) +{ + const char *in[] = { "foo" }; + git_strarray out = { 0 }; + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); + + cl_assert_equal_sz(out.count, 2); + cl_assert_equal_s("noop", out.strings[0]); + cl_assert_equal_s("foo", out.strings[1]); + + git_strarray_dispose(&out); +} + +void test_core_opts__extensions_remove(void) +{ + const char *in[] = { "bar", "!negate", "!noop", "baz" }; + git_strarray out = { 0 }; + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); + + cl_assert_equal_sz(out.count, 2); + cl_assert_equal_s("bar", out.strings[0]); + cl_assert_equal_s("baz", out.strings[1]); + + git_strarray_dispose(&out); +} diff --git a/tests/libgit2/core/path.c b/tests/libgit2/core/path.c new file mode 100644 index 000000000..a0ae77f1c --- /dev/null +++ b/tests/libgit2/core/path.c @@ -0,0 +1,739 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "fs_path.h" + +static char *path_save; + +void test_core_path__initialize(void) +{ + path_save = cl_getenv("PATH"); +} + +void test_core_path__cleanup(void) +{ + cl_setenv("PATH", path_save); + git__free(path_save); + path_save = NULL; +} + +static void +check_dirname(const char *A, const char *B) +{ + git_str dir = GIT_STR_INIT; + char *dir2; + + cl_assert(git_fs_path_dirname_r(&dir, A) >= 0); + cl_assert_equal_s(B, dir.ptr); + git_str_dispose(&dir); + + cl_assert((dir2 = git_fs_path_dirname(A)) != NULL); + cl_assert_equal_s(B, dir2); + git__free(dir2); +} + +static void +check_basename(const char *A, const char *B) +{ + git_str base = GIT_STR_INIT; + char *base2; + + cl_assert(git_fs_path_basename_r(&base, A) >= 0); + cl_assert_equal_s(B, base.ptr); + git_str_dispose(&base); + + cl_assert((base2 = git_fs_path_basename(A)) != NULL); + cl_assert_equal_s(B, base2); + git__free(base2); +} + +static void +check_joinpath(const char *path_a, const char *path_b, const char *expected_path) +{ + git_str joined_path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&joined_path, path_a, path_b)); + cl_assert_equal_s(expected_path, joined_path.ptr); + + git_str_dispose(&joined_path); +} + +static void +check_joinpath_n( + const char *path_a, + const char *path_b, + const char *path_c, + const char *path_d, + const char *expected_path) +{ + git_str joined_path = GIT_STR_INIT; + + cl_git_pass(git_str_join_n(&joined_path, '/', 4, + path_a, path_b, path_c, path_d)); + cl_assert_equal_s(expected_path, joined_path.ptr); + + git_str_dispose(&joined_path); +} + +static void check_setenv(const char* name, const char* value) +{ + char* check; + + cl_git_pass(cl_setenv(name, value)); + check = cl_getenv(name); + + if (value) + cl_assert_equal_s(value, check); + else + cl_assert(check == NULL); + + git__free(check); +} + +/* get the dirname of a path */ +void test_core_path__00_dirname(void) +{ + check_dirname(NULL, "."); + check_dirname("", "."); + check_dirname("a", "."); + check_dirname("/", "/"); + check_dirname("/usr", "/"); + check_dirname("/usr/", "/"); + check_dirname("/usr/lib", "/usr"); + check_dirname("/usr/lib/", "/usr"); + check_dirname("/usr/lib//", "/usr"); + check_dirname("usr/lib", "usr"); + check_dirname("usr/lib/", "usr"); + check_dirname("usr/lib//", "usr"); + check_dirname(".git/", "."); + + check_dirname(REP16("/abc"), REP15("/abc")); + +#ifdef GIT_WIN32 + check_dirname("C:/", "C:/"); + check_dirname("C:", "C:/"); + check_dirname("C:/path/", "C:/"); + check_dirname("C:/path", "C:/"); + check_dirname("//computername/", "//computername/"); + check_dirname("//computername", "//computername/"); + check_dirname("//computername/path/", "//computername/"); + check_dirname("//computername/path", "//computername/"); + check_dirname("//computername/sub/path/", "//computername/sub"); + check_dirname("//computername/sub/path", "//computername/sub"); +#endif +} + +/* get the base name of a path */ +void test_core_path__01_basename(void) +{ + check_basename(NULL, "."); + check_basename("", "."); + check_basename("a", "a"); + check_basename("/", "/"); + check_basename("/usr", "usr"); + check_basename("/usr/", "usr"); + check_basename("/usr/lib", "lib"); + check_basename("/usr/lib//", "lib"); + check_basename("usr/lib", "lib"); + + check_basename(REP16("/abc"), "abc"); + check_basename(REP1024("/abc"), "abc"); +} + +/* properly join path components */ +void test_core_path__05_joins(void) +{ + check_joinpath("", "", ""); + check_joinpath("", "a", "a"); + check_joinpath("", "/a", "/a"); + check_joinpath("a", "", "a/"); + check_joinpath("a", "/", "a/"); + check_joinpath("a", "b", "a/b"); + check_joinpath("/", "a", "/a"); + check_joinpath("/", "", "/"); + check_joinpath("/a", "/b", "/a/b"); + check_joinpath("/a", "/b/", "/a/b/"); + check_joinpath("/a/", "b/", "/a/b/"); + check_joinpath("/a/", "/b/", "/a/b/"); + + check_joinpath("/abcd", "/defg", "/abcd/defg"); + check_joinpath("/abcd", "/defg/", "/abcd/defg/"); + check_joinpath("/abcd/", "defg/", "/abcd/defg/"); + check_joinpath("/abcd/", "/defg/", "/abcd/defg/"); + + check_joinpath("/abcdefgh", "/12345678", "/abcdefgh/12345678"); + check_joinpath("/abcdefgh", "/12345678/", "/abcdefgh/12345678/"); + check_joinpath("/abcdefgh/", "12345678/", "/abcdefgh/12345678/"); + + check_joinpath(REP1024("aaaa"), "", REP1024("aaaa") "/"); + check_joinpath(REP1024("aaaa/"), "", REP1024("aaaa/")); + check_joinpath(REP1024("/aaaa"), "", REP1024("/aaaa") "/"); + + check_joinpath(REP1024("aaaa"), REP1024("bbbb"), + REP1024("aaaa") "/" REP1024("bbbb")); + check_joinpath(REP1024("/aaaa"), REP1024("/bbbb"), + REP1024("/aaaa") REP1024("/bbbb")); +} + +/* properly join path components for more than one path */ +void test_core_path__06_long_joins(void) +{ + check_joinpath_n("", "", "", "", ""); + check_joinpath_n("", "a", "", "", "a/"); + check_joinpath_n("a", "", "", "", "a/"); + check_joinpath_n("", "", "", "a", "a"); + check_joinpath_n("a", "b", "", "/c/d/", "a/b/c/d/"); + check_joinpath_n("a", "b", "", "/c/d", "a/b/c/d"); + check_joinpath_n("abcd", "efgh", "ijkl", "mnop", "abcd/efgh/ijkl/mnop"); + check_joinpath_n("abcd/", "efgh/", "ijkl/", "mnop/", "abcd/efgh/ijkl/mnop/"); + check_joinpath_n("/abcd/", "/efgh/", "/ijkl/", "/mnop/", "/abcd/efgh/ijkl/mnop/"); + + check_joinpath_n(REP1024("a"), REP1024("b"), REP1024("c"), REP1024("d"), + REP1024("a") "/" REP1024("b") "/" + REP1024("c") "/" REP1024("d")); + check_joinpath_n(REP1024("/a"), REP1024("/b"), REP1024("/c"), REP1024("/d"), + REP1024("/a") REP1024("/b") + REP1024("/c") REP1024("/d")); +} + + +static void +check_path_to_dir( + const char* path, + const char* expected) +{ + git_str tgt = GIT_STR_INIT; + + git_str_sets(&tgt, path); + cl_git_pass(git_fs_path_to_dir(&tgt)); + cl_assert_equal_s(expected, tgt.ptr); + + git_str_dispose(&tgt); +} + +static void +check_string_to_dir( + const char* path, + size_t maxlen, + const char* expected) +{ + size_t len = strlen(path); + char *buf = git__malloc(len + 2); + cl_assert(buf); + + strncpy(buf, path, len + 2); + + git_fs_path_string_to_dir(buf, maxlen); + + cl_assert_equal_s(expected, buf); + + git__free(buf); +} + +/* convert paths to dirs */ +void test_core_path__07_path_to_dir(void) +{ + check_path_to_dir("", ""); + check_path_to_dir(".", "./"); + check_path_to_dir("./", "./"); + check_path_to_dir("a/", "a/"); + check_path_to_dir("ab", "ab/"); + /* make sure we try just under and just over an expansion that will + * require a realloc + */ + check_path_to_dir("abcdef", "abcdef/"); + check_path_to_dir("abcdefg", "abcdefg/"); + check_path_to_dir("abcdefgh", "abcdefgh/"); + check_path_to_dir("abcdefghi", "abcdefghi/"); + check_path_to_dir(REP1024("abcd") "/", REP1024("abcd") "/"); + check_path_to_dir(REP1024("abcd"), REP1024("abcd") "/"); + + check_string_to_dir("", 1, ""); + check_string_to_dir(".", 1, "."); + check_string_to_dir(".", 2, "./"); + check_string_to_dir(".", 3, "./"); + check_string_to_dir("abcd", 3, "abcd"); + check_string_to_dir("abcd", 4, "abcd"); + check_string_to_dir("abcd", 5, "abcd/"); + check_string_to_dir("abcd", 6, "abcd/"); +} + +/* join path to itself */ +void test_core_path__08_self_join(void) +{ + git_str path = GIT_STR_INIT; + size_t asize = 0; + + asize = path.asize; + cl_git_pass(git_str_sets(&path, "/foo")); + cl_assert_equal_s(path.ptr, "/foo"); + cl_assert(asize < path.asize); + + asize = path.asize; + cl_git_pass(git_str_joinpath(&path, path.ptr, "this is a new string")); + cl_assert_equal_s(path.ptr, "/foo/this is a new string"); + cl_assert(asize < path.asize); + + asize = path.asize; + cl_git_pass(git_str_joinpath(&path, path.ptr, "/grow the buffer, grow the buffer, grow the buffer")); + cl_assert_equal_s(path.ptr, "/foo/this is a new string/grow the buffer, grow the buffer, grow the buffer"); + cl_assert(asize < path.asize); + + git_str_dispose(&path); + cl_git_pass(git_str_sets(&path, "/foo/bar")); + + cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "baz")); + cl_assert_equal_s(path.ptr, "/bar/baz"); + + asize = path.asize; + cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "somethinglongenoughtorealloc")); + cl_assert_equal_s(path.ptr, "/baz/somethinglongenoughtorealloc"); + cl_assert(asize < path.asize); + + git_str_dispose(&path); +} + +static void check_percent_decoding(const char *expected_result, const char *input) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git__percent_decode(&buf, input)); + cl_assert_equal_s(expected_result, git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +void test_core_path__09_percent_decode(void) +{ + check_percent_decoding("abcd", "abcd"); + check_percent_decoding("a2%", "a2%"); + check_percent_decoding("a2%3", "a2%3"); + check_percent_decoding("a2%%3", "a2%%3"); + check_percent_decoding("a2%3z", "a2%3z"); + check_percent_decoding("a,", "a%2c"); + check_percent_decoding("a21", "a2%31"); + check_percent_decoding("a2%1", "a2%%31"); + check_percent_decoding("a bc ", "a%20bc%20"); + check_percent_decoding("Vicent Mart" "\355", "Vicent%20Mart%ED"); +} + +static void check_fromurl(const char *expected_result, const char *input, int should_fail) +{ + git_str buf = GIT_STR_INIT; + + assert(should_fail || expected_result); + + if (!should_fail) { + cl_git_pass(git_fs_path_fromurl(&buf, input)); + cl_assert_equal_s(expected_result, git_str_cstr(&buf)); + } else + cl_git_fail(git_fs_path_fromurl(&buf, input)); + + git_str_dispose(&buf); +} + +#ifdef GIT_WIN32 +#define ABS_PATH_MARKER "" +#else +#define ABS_PATH_MARKER "/" +#endif + +void test_core_path__10_fromurl(void) +{ + /* Failing cases */ + check_fromurl(NULL, "a", 1); + check_fromurl(NULL, "http:///c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file://c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file:////c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file:///", 1); + check_fromurl(NULL, "file:////", 1); + check_fromurl(NULL, "file://servername/c:/Temp%20folder/note.txt", 1); + + /* Passing cases */ + check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file:///c:/Temp%20folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file://localhost/c:/Temp%20folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "c:/Temp+folder/note.txt", "file:///c:/Temp+folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "a", "file:///a", 0); +} + +typedef struct { + int expect_idx; + int cancel_after; + char **expect; +} check_walkup_info; + +#define CANCEL_VALUE 1234 + +static int check_one_walkup_step(void *ref, const char *path) +{ + check_walkup_info *info = (check_walkup_info *)ref; + + if (!info->cancel_after) { + cl_assert_equal_s(info->expect[info->expect_idx], "[CANCEL]"); + return CANCEL_VALUE; + } + info->cancel_after--; + + cl_assert(info->expect[info->expect_idx] != NULL); + cl_assert_equal_s(info->expect[info->expect_idx], path); + info->expect_idx++; + + return 0; +} + +void test_core_path__11_walkup(void) +{ + git_str p = GIT_STR_INIT; + + char *expect[] = { + /* 1 */ "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 2 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 3 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 4 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 5 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, + /* 6 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, + /* 7 */ "this_is_a_path", "", NULL, + /* 8 */ "this_is_a_path/", "", NULL, + /* 9 */ "///a///b///c///d///e///", "///a///b///c///d///", "///a///b///c///", "///a///b///", "///a///", "///", NULL, + /* 10 */ "a/b/c/", "a/b/", "a/", "", NULL, + /* 11 */ "a/b/c", "a/b/", "a/", "", NULL, + /* 12 */ "a/b/c/", "a/b/", "a/", NULL, + /* 13 */ "", NULL, + /* 14 */ "/", NULL, + /* 15 */ NULL + }; + + char *root[] = { + /* 1 */ NULL, + /* 2 */ NULL, + /* 3 */ "/", + /* 4 */ "", + /* 5 */ "/a/b", + /* 6 */ "/a/b/", + /* 7 */ NULL, + /* 8 */ NULL, + /* 9 */ NULL, + /* 10 */ NULL, + /* 11 */ NULL, + /* 12 */ "a/", + /* 13 */ NULL, + /* 14 */ NULL, + }; + + int i, j; + check_walkup_info info; + + info.expect = expect; + info.cancel_after = -1; + + for (i = 0, j = 0; expect[i] != NULL; i++, j++) { + + git_str_sets(&p, expect[i]); + + info.expect_idx = i; + cl_git_pass( + git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) + ); + + cl_assert_equal_s(p.ptr, expect[i]); + cl_assert(expect[info.expect_idx] == NULL); + i = info.expect_idx; + } + + git_str_dispose(&p); +} + +void test_core_path__11a_walkup_cancel(void) +{ + git_str p = GIT_STR_INIT; + int cancel[] = { 3, 2, 1, 0 }; + char *expect[] = { + "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "[CANCEL]", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "[CANCEL]", NULL, + "/a/b/c/d/e", "[CANCEL]", NULL, + "[CANCEL]", NULL, + NULL + }; + char *root[] = { NULL, NULL, "/", "", NULL }; + int i, j; + check_walkup_info info; + + info.expect = expect; + + for (i = 0, j = 0; expect[i] != NULL; i++, j++) { + + git_str_sets(&p, expect[i]); + + info.cancel_after = cancel[j]; + info.expect_idx = i; + + cl_assert_equal_i( + CANCEL_VALUE, + git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) + ); + + /* skip to next run of expectations */ + while (expect[i] != NULL) i++; + } + + git_str_dispose(&p); +} + +void test_core_path__12_offset_to_path_root(void) +{ + cl_assert(git_fs_path_root("non/rooted/path") == -1); + cl_assert(git_fs_path_root("/rooted/path") == 0); + +#ifdef GIT_WIN32 + /* Windows specific tests */ + cl_assert(git_fs_path_root("C:non/rooted/path") == -1); + cl_assert(git_fs_path_root("C:/rooted/path") == 2); + cl_assert(git_fs_path_root("//computername/sharefolder/resource") == 14); + cl_assert(git_fs_path_root("//computername/sharefolder") == 14); + cl_assert(git_fs_path_root("//computername") == -1); +#endif +} + +#define NON_EXISTING_FILEPATH "i_hope_i_do_not_exist" + +void test_core_path__13_cannot_prettify_a_non_existing_file(void) +{ + git_str p = GIT_STR_INIT; + + cl_assert_equal_b(git_fs_path_exists(NON_EXISTING_FILEPATH), false); + cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH, NULL)); + cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH "/so-do-i", NULL)); + + git_str_dispose(&p); +} + +void test_core_path__14_apply_relative(void) +{ + git_str p = GIT_STR_INIT; + + cl_git_pass(git_str_sets(&p, "/this/is/a/base")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../test")); + cl_assert_equal_s("/this/is/a/test", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../the/./end")); + cl_assert_equal_s("/this/is/the/end", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "./of/this/../the/string")); + cl_assert_equal_s("/this/is/the/end/of/the/string", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../../../../..")); + cl_assert_equal_s("/this/", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../")); + cl_assert_equal_s("/", p.ptr); + + cl_git_fail(git_fs_path_apply_relative(&p, "../../..")); + + + cl_git_pass(git_str_sets(&p, "d:/another/test")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../..")); + cl_assert_equal_s("d:/", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "from/here/to/../and/./back/.")); + cl_assert_equal_s("d:/from/here/and/back/", p.ptr); + + + cl_git_pass(git_str_sets(&p, "https://my.url.com/test.git")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../another.git")); + cl_assert_equal_s("https://my.url.com/another.git", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../full/path/url.patch")); + cl_assert_equal_s("https://my.url.com/full/path/url.patch", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "..")); + cl_assert_equal_s("https://my.url.com/full/path/", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../../")); + cl_assert_equal_s("https://", p.ptr); + + + cl_git_pass(git_str_sets(&p, "../../this/is/relative")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../preserves/the/prefix")); + cl_assert_equal_s("../../this/preserves/the/prefix", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../../../that")); + cl_assert_equal_s("../../that", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../there")); + cl_assert_equal_s("../../there", p.ptr); + git_str_dispose(&p); +} + +static void assert_resolve_relative( + git_str *buf, const char *expected, const char *path) +{ + cl_git_pass(git_str_sets(buf, path)); + cl_git_pass(git_fs_path_resolve_relative(buf, 0)); + cl_assert_equal_s(expected, buf->ptr); +} + +void test_core_path__15_resolve_relative(void) +{ + git_str buf = GIT_STR_INIT; + + assert_resolve_relative(&buf, "", ""); + assert_resolve_relative(&buf, "", "."); + assert_resolve_relative(&buf, "", "./"); + assert_resolve_relative(&buf, "..", ".."); + assert_resolve_relative(&buf, "../", "../"); + assert_resolve_relative(&buf, "..", "./.."); + assert_resolve_relative(&buf, "../", "./../"); + assert_resolve_relative(&buf, "../", "../."); + assert_resolve_relative(&buf, "../", ".././"); + assert_resolve_relative(&buf, "../..", "../.."); + assert_resolve_relative(&buf, "../../", "../../"); + + assert_resolve_relative(&buf, "/", "/"); + assert_resolve_relative(&buf, "/", "/."); + + assert_resolve_relative(&buf, "", "a/.."); + assert_resolve_relative(&buf, "", "a/../"); + assert_resolve_relative(&buf, "", "a/../."); + + assert_resolve_relative(&buf, "/a", "/a"); + assert_resolve_relative(&buf, "/a/", "/a/."); + assert_resolve_relative(&buf, "/", "/a/../"); + assert_resolve_relative(&buf, "/", "/a/../."); + assert_resolve_relative(&buf, "/", "/a/.././"); + + assert_resolve_relative(&buf, "a", "a"); + assert_resolve_relative(&buf, "a/", "a/"); + assert_resolve_relative(&buf, "a/", "a/."); + assert_resolve_relative(&buf, "a/", "a/./"); + + assert_resolve_relative(&buf, "a/b", "a//b"); + assert_resolve_relative(&buf, "a/b/c", "a/b/c"); + assert_resolve_relative(&buf, "b/c", "./b/c"); + assert_resolve_relative(&buf, "a/c", "a/./c"); + assert_resolve_relative(&buf, "a/b/", "a/b/."); + + assert_resolve_relative(&buf, "/a/b/c", "///a/b/c"); + assert_resolve_relative(&buf, "/", "////"); + assert_resolve_relative(&buf, "/a", "///a"); + assert_resolve_relative(&buf, "/", "///."); + assert_resolve_relative(&buf, "/", "///a/.."); + + assert_resolve_relative(&buf, "../../path", "../../test//../././path"); + assert_resolve_relative(&buf, "../d", "a/b/../../../c/../d"); + + cl_git_pass(git_str_sets(&buf, "/..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/./..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/.//..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/../.")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/../.././../a")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "////..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + /* things that start with Windows network paths */ +#ifdef GIT_WIN32 + assert_resolve_relative(&buf, "//a/b/c", "//a/b/c"); + assert_resolve_relative(&buf, "//a/", "//a/b/.."); + assert_resolve_relative(&buf, "//a/b/c", "//a/Q/../b/x/y/../../c"); + + cl_git_pass(git_str_sets(&buf, "//a/b/../..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); +#else + assert_resolve_relative(&buf, "/a/b/c", "//a/b/c"); + assert_resolve_relative(&buf, "/a/", "//a/b/.."); + assert_resolve_relative(&buf, "/a/b/c", "//a/Q/../b/x/y/../../c"); + assert_resolve_relative(&buf, "/", "//a/b/../.."); +#endif + + git_str_dispose(&buf); +} + +#define assert_common_dirlen(i, p, q) \ + cl_assert_equal_i((i), git_fs_path_common_dirlen((p), (q))); + +void test_core_path__16_resolve_relative(void) +{ + assert_common_dirlen(0, "", ""); + assert_common_dirlen(0, "", "bar.txt"); + assert_common_dirlen(0, "foo.txt", "bar.txt"); + assert_common_dirlen(0, "foo.txt", ""); + assert_common_dirlen(0, "foo/bar.txt", "bar/foo.txt"); + assert_common_dirlen(0, "foo/bar.txt", "../foo.txt"); + + assert_common_dirlen(1, "/one.txt", "/two.txt"); + assert_common_dirlen(4, "foo/one.txt", "foo/two.txt"); + assert_common_dirlen(5, "/foo/one.txt", "/foo/two.txt"); + + assert_common_dirlen(6, "a/b/c/foo.txt", "a/b/c/d/e/bar.txt"); + assert_common_dirlen(7, "/a/b/c/foo.txt", "/a/b/c/d/e/bar.txt"); +} + +static void fix_path(git_str *s) +{ +#ifndef GIT_WIN32 + GIT_UNUSED(s); +#else + char* c; + + for (c = s->ptr; *c; c++) { + if (*c == '/') + *c = '\\'; + } +#endif +} + +void test_core_path__find_exe_in_path(void) +{ + char *orig_path; + git_str sandbox_path = GIT_STR_INIT; + git_str new_path = GIT_STR_INIT, full_path = GIT_STR_INIT, + dummy_path = GIT_STR_INIT; + +#ifdef GIT_WIN32 + static const char *bogus_path_1 = "c:\\does\\not\\exist\\"; + static const char *bogus_path_2 = "e:\\non\\existent"; +#else + static const char *bogus_path_1 = "/this/path/does/not/exist/"; + static const char *bogus_path_2 = "/non/existent"; +#endif + + orig_path = cl_getenv("PATH"); + + git_str_puts(&sandbox_path, clar_sandbox_path()); + git_str_joinpath(&dummy_path, sandbox_path.ptr, "dummmmmmmy_libgit2_file"); + cl_git_rewritefile(dummy_path.ptr, "this is a dummy file"); + + fix_path(&sandbox_path); + fix_path(&dummy_path); + + cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s", + bogus_path_1, GIT_PATH_LIST_SEPARATOR, + orig_path, GIT_PATH_LIST_SEPARATOR, + sandbox_path.ptr, GIT_PATH_LIST_SEPARATOR, + bogus_path_2)); + + check_setenv("PATH", new_path.ptr); + + cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "this_file_does_not_exist")); + cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file")); + + cl_assert_equal_s(full_path.ptr, dummy_path.ptr); + + git_str_dispose(&full_path); + git_str_dispose(&new_path); + git_str_dispose(&dummy_path); + git_str_dispose(&sandbox_path); + git__free(orig_path); +} diff --git a/tests/libgit2/core/pool.c b/tests/libgit2/core/pool.c new file mode 100644 index 000000000..b07da0abd --- /dev/null +++ b/tests/libgit2/core/pool.c @@ -0,0 +1,92 @@ +#include "clar_libgit2.h" +#include "pool.h" +#include "git2/oid.h" + +void test_core_pool__0(void) +{ + int i; + git_pool p; + void *ptr; + + git_pool_init(&p, 1); + + for (i = 1; i < 10000; i *= 2) { + ptr = git_pool_malloc(&p, i); + cl_assert(ptr != NULL); + cl_assert(git_pool__ptr_in_pool(&p, ptr)); + cl_assert(!git_pool__ptr_in_pool(&p, &i)); + } + + git_pool_clear(&p); +} + +void test_core_pool__1(void) +{ + int i; + git_pool p; + + git_pool_init(&p, 1); + p.page_size = 4000; + + for (i = 2010; i > 0; i--) + cl_assert(git_pool_malloc(&p, i) != NULL); + +#ifndef GIT_DEBUG_POOL + /* with fixed page size, allocation must end up with these values */ + cl_assert_equal_i(591, git_pool__open_pages(&p)); +#endif + git_pool_clear(&p); + + git_pool_init(&p, 1); + p.page_size = 4120; + + for (i = 2010; i > 0; i--) + cl_assert(git_pool_malloc(&p, i) != NULL); + +#ifndef GIT_DEBUG_POOL + /* with fixed page size, allocation must end up with these values */ + cl_assert_equal_i(sizeof(void *) == 8 ? 575 : 573, git_pool__open_pages(&p)); +#endif + git_pool_clear(&p); +} + +static char to_hex[] = "0123456789abcdef"; + +void test_core_pool__2(void) +{ + git_pool p; + char oid_hex[GIT_OID_HEXSZ]; + git_oid *oid; + int i, j; + + memset(oid_hex, '0', sizeof(oid_hex)); + + git_pool_init(&p, sizeof(git_oid)); + p.page_size = 4000; + + for (i = 1000; i < 10000; i++) { + oid = git_pool_malloc(&p, 1); + cl_assert(oid != NULL); + + for (j = 0; j < 8; j++) + oid_hex[j] = to_hex[(i >> (4 * j)) & 0x0f]; + cl_git_pass(git_oid_fromstr(oid, oid_hex)); + } + +#ifndef GIT_DEBUG_POOL + /* with fixed page size, allocation must end up with these values */ + cl_assert_equal_i(sizeof(void *) == 8 ? 55 : 45, git_pool__open_pages(&p)); +#endif + git_pool_clear(&p); +} + +void test_core_pool__strndup_limit(void) +{ + git_pool p; + + git_pool_init(&p, 1); + /* ensure 64 bit doesn't overflow */ + cl_assert(git_pool_strndup(&p, "foo", (size_t)-1) == NULL); + git_pool_clear(&p); +} + diff --git a/tests/libgit2/core/posix.c b/tests/libgit2/core/posix.c new file mode 100644 index 000000000..cba312913 --- /dev/null +++ b/tests/libgit2/core/posix.c @@ -0,0 +1,238 @@ +#ifndef _WIN32 +# include +# include +# include +#else +# include +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32") +# endif +#endif + +#include "clar_libgit2.h" +#include "futils.h" +#include "posix.h" + +void test_core_posix__initialize(void) +{ +#ifdef GIT_WIN32 + /* on win32, the WSA context needs to be initialized + * before any socket calls can be performed */ + WSADATA wsd; + + cl_git_pass(WSAStartup(MAKEWORD(2,2), &wsd)); + cl_assert(LOBYTE(wsd.wVersion) == 2 && HIBYTE(wsd.wVersion) == 2); +#endif +} + +static bool supports_ipv6(void) +{ +#ifdef GIT_WIN32 + /* IPv6 is supported on Vista and newer */ + return git_has_win32_version(6, 0, 0); +#else + return 1; +#endif +} + +void test_core_posix__inet_pton(void) +{ + struct in_addr addr; + struct in6_addr addr6; + size_t i; + + struct in_addr_data { + const char *p; + const uint8_t n[4]; + }; + + struct in6_addr_data { + const char *p; + const uint8_t n[16]; + }; + + static struct in_addr_data in_addr_data[] = { + { "0.0.0.0", { 0, 0, 0, 0 } }, + { "10.42.101.8", { 10, 42, 101, 8 } }, + { "127.0.0.1", { 127, 0, 0, 1 } }, + { "140.177.10.12", { 140, 177, 10, 12 } }, + { "204.232.175.90", { 204, 232, 175, 90 } }, + { "255.255.255.255", { 255, 255, 255, 255 } }, + }; + + static struct in6_addr_data in6_addr_data[] = { + { "::", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { "::1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, + { "0:0:0:0:0:0:0:1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, + { "2001:db8:8714:3a90::12", { 0x20, 0x01, 0x0d, 0xb8, 0x87, 0x14, 0x3a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12 } }, + { "fe80::f8ba:c2d6:86be:3645", { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xba, 0xc2, 0xd6, 0x86, 0xbe, 0x36, 0x45 } }, + { "::ffff:204.152.189.116", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcc, 0x98, 0xbd, 0x74 } }, + }; + + /* Test some ipv4 addresses */ + for (i = 0; i < 6; i++) { + cl_assert(p_inet_pton(AF_INET, in_addr_data[i].p, &addr) == 1); + cl_assert(memcmp(&addr, in_addr_data[i].n, sizeof(struct in_addr)) == 0); + } + + /* Test some ipv6 addresses */ + if (supports_ipv6()) + { + for (i = 0; i < 6; i++) { + cl_assert(p_inet_pton(AF_INET6, in6_addr_data[i].p, &addr6) == 1); + cl_assert(memcmp(&addr6, in6_addr_data[i].n, sizeof(struct in6_addr)) == 0); + } + } + + /* Test some invalid strings */ + cl_assert(p_inet_pton(AF_INET, "", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, "foo", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, " 127.0.0.1", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, "bar", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, "10.foo.bar.1", &addr) == 0); + + /* Test unsupported address families */ + cl_git_fail(p_inet_pton(INT_MAX-1, "52.472", &addr)); + cl_assert_equal_i(EAFNOSUPPORT, errno); +} + +void test_core_posix__utimes(void) +{ + struct p_timeval times[2]; + struct stat st; + time_t curtime; + int fd; + + /* test p_utimes */ + times[0].tv_sec = 1234567890; + times[0].tv_usec = 0; + times[1].tv_sec = 1234567890; + times[1].tv_usec = 0; + + cl_git_mkfile("foo", "Dummy file."); + cl_must_pass(p_utimes("foo", times)); + + cl_must_pass(p_stat("foo", &st)); + cl_assert_equal_i(1234567890, st.st_atime); + cl_assert_equal_i(1234567890, st.st_mtime); + + + /* test p_futimes */ + times[0].tv_sec = 1414141414; + times[0].tv_usec = 0; + times[1].tv_sec = 1414141414; + times[1].tv_usec = 0; + + cl_must_pass(fd = p_open("foo", O_RDWR)); + cl_must_pass(p_futimes(fd, times)); + cl_must_pass(p_close(fd)); + + cl_must_pass(p_stat("foo", &st)); + cl_assert_equal_i(1414141414, st.st_atime); + cl_assert_equal_i(1414141414, st.st_mtime); + + + /* test p_utimes with current time, assume that + * it takes < 5 seconds to get the time...! + */ + cl_must_pass(p_utimes("foo", NULL)); + + curtime = time(NULL); + cl_must_pass(p_stat("foo", &st)); + cl_assert((st.st_atime - curtime) < 5); + cl_assert((st.st_mtime - curtime) < 5); + + cl_must_pass(p_unlink("foo")); +} + +void test_core_posix__unlink_removes_symlink(void) +{ + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + clar__skip(); + + cl_git_mkfile("file", "Dummy file."); + cl_git_pass(git_futils_mkdir("dir", 0777, 0)); + + cl_must_pass(p_symlink("file", "file-symlink")); + cl_must_pass(p_symlink("dir", "dir-symlink")); + + cl_must_pass(p_unlink("file-symlink")); + cl_must_pass(p_unlink("dir-symlink")); + + cl_assert(git_fs_path_exists("file")); + cl_assert(git_fs_path_exists("dir")); + + cl_must_pass(p_unlink("file")); + cl_must_pass(p_rmdir("dir")); +} + +void test_core_posix__symlink_resolves_to_correct_type(void) +{ + git_str contents = GIT_STR_INIT; + + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + clar__skip(); + + cl_must_pass(git_futils_mkdir("dir", 0777, 0)); + cl_must_pass(git_futils_mkdir("file", 0777, 0)); + cl_git_mkfile("dir/file", "symlink target"); + + cl_git_pass(p_symlink("file", "dir/link")); + + cl_git_pass(git_futils_readbuffer(&contents, "dir/file")); + cl_assert_equal_s(contents.ptr, "symlink target"); + + cl_must_pass(p_unlink("dir/link")); + cl_must_pass(p_unlink("dir/file")); + cl_must_pass(p_rmdir("dir")); + cl_must_pass(p_rmdir("file")); + + git_str_dispose(&contents); +} + +void test_core_posix__relative_symlink(void) +{ + git_str contents = GIT_STR_INIT; + + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + clar__skip(); + + cl_must_pass(git_futils_mkdir("dir", 0777, 0)); + cl_git_mkfile("file", "contents"); + cl_git_pass(p_symlink("../file", "dir/link")); + cl_git_pass(git_futils_readbuffer(&contents, "dir/link")); + cl_assert_equal_s(contents.ptr, "contents"); + + cl_must_pass(p_unlink("file")); + cl_must_pass(p_unlink("dir/link")); + cl_must_pass(p_rmdir("dir")); + + git_str_dispose(&contents); +} + +void test_core_posix__symlink_to_file_across_dirs(void) +{ + git_str contents = GIT_STR_INIT; + + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + clar__skip(); + + /* + * Create a relative symlink that points into another + * directory. This used to not work on Win32, where we + * forgot to convert directory separators to + * Windows-style ones. + */ + cl_must_pass(git_futils_mkdir("dir", 0777, 0)); + cl_git_mkfile("dir/target", "symlink target"); + cl_git_pass(p_symlink("dir/target", "link")); + + cl_git_pass(git_futils_readbuffer(&contents, "dir/target")); + cl_assert_equal_s(contents.ptr, "symlink target"); + + cl_must_pass(p_unlink("dir/target")); + cl_must_pass(p_unlink("link")); + cl_must_pass(p_rmdir("dir")); + + git_str_dispose(&contents); +} diff --git a/tests/libgit2/core/pqueue.c b/tests/libgit2/core/pqueue.c new file mode 100644 index 000000000..2b90f4172 --- /dev/null +++ b/tests/libgit2/core/pqueue.c @@ -0,0 +1,150 @@ +#include "clar_libgit2.h" +#include "pqueue.h" + +static int cmp_ints(const void *v1, const void *v2) +{ + int i1 = *(int *)v1, i2 = *(int *)v2; + return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0; +} + +void test_core_pqueue__items_are_put_in_order(void) +{ + git_pqueue pq; + int i, vals[20]; + + cl_git_pass(git_pqueue_init(&pq, 0, 20, cmp_ints)); + + for (i = 0; i < 20; ++i) { + if (i < 10) + vals[i] = 10 - i; /* 10 down to 1 */ + else + vals[i] = i + 1; /* 11 up to 20 */ + + cl_git_pass(git_pqueue_insert(&pq, &vals[i])); + } + + cl_assert_equal_i(20, git_pqueue_size(&pq)); + + for (i = 1; i <= 20; ++i) { + void *p = git_pqueue_pop(&pq); + cl_assert(p); + cl_assert_equal_i(i, *(int *)p); + } + + cl_assert_equal_i(0, git_pqueue_size(&pq)); + + git_pqueue_free(&pq); +} + +void test_core_pqueue__interleave_inserts_and_pops(void) +{ + git_pqueue pq; + int chunk, v, i, vals[200]; + + cl_git_pass(git_pqueue_init(&pq, 0, 20, cmp_ints)); + + for (v = 0, chunk = 20; chunk <= 200; chunk += 20) { + /* push the next 20 */ + for (; v < chunk; ++v) { + vals[v] = (v & 1) ? 200 - v : v; + cl_git_pass(git_pqueue_insert(&pq, &vals[v])); + } + + /* pop the lowest 10 */ + for (i = 0; i < 10; ++i) + (void)git_pqueue_pop(&pq); + } + + cl_assert_equal_i(100, git_pqueue_size(&pq)); + + /* at this point, we've popped 0-99 */ + + for (v = 100; v < 200; ++v) { + void *p = git_pqueue_pop(&pq); + cl_assert(p); + cl_assert_equal_i(v, *(int *)p); + } + + cl_assert_equal_i(0, git_pqueue_size(&pq)); + + git_pqueue_free(&pq); +} + +void test_core_pqueue__max_heap_size(void) +{ + git_pqueue pq; + int i, vals[100]; + + cl_git_pass(git_pqueue_init(&pq, GIT_PQUEUE_FIXED_SIZE, 50, cmp_ints)); + + for (i = 0; i < 100; ++i) { + vals[i] = (i & 1) ? 100 - i : i; + cl_git_pass(git_pqueue_insert(&pq, &vals[i])); + } + + cl_assert_equal_i(50, git_pqueue_size(&pq)); + + for (i = 50; i < 100; ++i) { + void *p = git_pqueue_pop(&pq); + cl_assert(p); + cl_assert_equal_i(i, *(int *)p); + } + + cl_assert_equal_i(0, git_pqueue_size(&pq)); + + git_pqueue_free(&pq); +} + +void test_core_pqueue__max_heap_size_without_comparison(void) +{ + git_pqueue pq; + int i, vals[100] = { 0 }; + + cl_git_pass(git_pqueue_init(&pq, GIT_PQUEUE_FIXED_SIZE, 50, NULL)); + + for (i = 0; i < 100; ++i) + cl_git_pass(git_pqueue_insert(&pq, &vals[i])); + + cl_assert_equal_i(50, git_pqueue_size(&pq)); + + /* As we have no comparison function, we cannot make any + * actual assumptions about which entries are part of the + * pqueue */ + for (i = 0; i < 50; ++i) + cl_assert(git_pqueue_pop(&pq)); + + cl_assert_equal_i(0, git_pqueue_size(&pq)); + + git_pqueue_free(&pq); +} + +static int cmp_ints_like_commit_time(const void *a, const void *b) +{ + return *((const int *)a) < *((const int *)b); +} + +void test_core_pqueue__interleaved_pushes_and_pops(void) +{ + git_pqueue pq; + int i, j, *val; + static int commands[] = + { 6, 9, 8, 0, 5, 0, 7, 0, 4, 3, 0, 0, 0, 4, 0, 2, 0, 1, 0, 0, -1 }; + static int expected[] = + { 9, 8, 7, 6, 5, 4, 4, 3, 2, 1, -1 }; + + cl_git_pass(git_pqueue_init(&pq, 0, 10, cmp_ints_like_commit_time)); + + for (i = 0, j = 0; commands[i] >= 0; ++i) { + if (!commands[i]) { + cl_assert((val = git_pqueue_pop(&pq)) != NULL); + cl_assert_equal_i(expected[j], *val); + ++j; + } else { + cl_git_pass(git_pqueue_insert(&pq, &commands[i])); + } + } + + cl_assert_equal_i(0, git_pqueue_size(&pq)); + git_pqueue_free(&pq); +} + diff --git a/tests/libgit2/core/qsort.c b/tests/libgit2/core/qsort.c new file mode 100644 index 000000000..efb79e6e9 --- /dev/null +++ b/tests/libgit2/core/qsort.c @@ -0,0 +1,90 @@ +#include "clar_libgit2.h" + +#define assert_sorted(a, cmp) \ + _assert_sorted(a, ARRAY_SIZE(a), sizeof(*a), cmp) + +struct big_entries { + char c[311]; +}; + +static void _assert_sorted(void *els, size_t n, size_t elsize, git__sort_r_cmp cmp) +{ + int8_t *p = els; + + git__qsort_r(p, n, elsize, cmp, NULL); + while (n-- > 1) { + cl_assert(cmp(p, p + elsize, NULL) <= 0); + p += elsize; + } +} + +static int cmp_big(const void *_a, const void *_b, void *payload) +{ + const struct big_entries *a = (const struct big_entries *)_a, *b = (const struct big_entries *)_b; + GIT_UNUSED(payload); + return (a->c[0] < b->c[0]) ? -1 : (a->c[0] > b->c[0]) ? +1 : 0; +} + +static int cmp_int(const void *_a, const void *_b, void *payload) +{ + int a = *(const int *)_a, b = *(const int *)_b; + GIT_UNUSED(payload); + return (a < b) ? -1 : (a > b) ? +1 : 0; +} + +static int cmp_str(const void *_a, const void *_b, void *payload) +{ + GIT_UNUSED(payload); + return strcmp((const char *) _a, (const char *) _b); +} + +void test_core_qsort__array_with_single_entry(void) +{ + int a[] = { 10 }; + assert_sorted(a, cmp_int); +} + +void test_core_qsort__array_with_equal_entries(void) +{ + int a[] = { 4, 4, 4, 4 }; + assert_sorted(a, cmp_int); +} + +void test_core_qsort__sorted_array(void) +{ + int a[] = { 1, 10 }; + assert_sorted(a, cmp_int); +} + +void test_core_qsort__unsorted_array(void) +{ + int a[] = { 123, 9, 412938, 10, 234, 89 }; + assert_sorted(a, cmp_int); +} + +void test_core_qsort__sorting_strings(void) +{ + char *a[] = { "foo", "bar", "baz" }; + assert_sorted(a, cmp_str); +} + +void test_core_qsort__sorting_big_entries(void) +{ + struct big_entries a[5]; + + memset(&a, 0, sizeof(a)); + + memset(a[0].c, 'w', sizeof(a[0].c) - 1); + memset(a[1].c, 'c', sizeof(a[1].c) - 1); + memset(a[2].c, 'w', sizeof(a[2].c) - 1); + memset(a[3].c, 'h', sizeof(a[3].c) - 1); + memset(a[4].c, 'a', sizeof(a[4].c) - 1); + + assert_sorted(a, cmp_big); + + cl_assert_equal_i(strspn(a[0].c, "a"), sizeof(a[0].c) - 1); + cl_assert_equal_i(strspn(a[1].c, "c"), sizeof(a[1].c) - 1); + cl_assert_equal_i(strspn(a[2].c, "h"), sizeof(a[2].c) - 1); + cl_assert_equal_i(strspn(a[3].c, "w"), sizeof(a[3].c) - 1); + cl_assert_equal_i(strspn(a[4].c, "w"), sizeof(a[4].c) - 1); +} diff --git a/tests/libgit2/core/regexp.c b/tests/libgit2/core/regexp.c new file mode 100644 index 000000000..8db5641e5 --- /dev/null +++ b/tests/libgit2/core/regexp.c @@ -0,0 +1,213 @@ +#include "clar_libgit2.h" + +#include + +#include "regexp.h" +#include "userdiff.h" + +#if LC_ALL > 0 +static const char *old_locales[LC_ALL]; +#endif + +static git_regexp regex; + +void test_core_regexp__initialize(void) +{ +#if LC_ALL > 0 + memset(&old_locales, 0, sizeof(old_locales)); +#endif +} + +void test_core_regexp__cleanup(void) +{ + git_regexp_dispose(®ex); +} + +static void try_set_locale(int category) +{ +#if LC_ALL > 0 + old_locales[category] = setlocale(category, NULL); +#endif + + if (!setlocale(category, "UTF-8") && + !setlocale(category, "c.utf8") && + !setlocale(category, "en_US.UTF-8")) + cl_skip(); + + if (MB_CUR_MAX == 1) + cl_fail("Expected locale to be switched to multibyte"); +} + + +void test_core_regexp__compile_ignores_global_locale_ctype(void) +{ + try_set_locale(LC_CTYPE); + cl_git_pass(git_regexp_compile(®ex, "[\xc0-\xff][\x80-\xbf]", 0)); +} + +void test_core_regexp__compile_ignores_global_locale_collate(void) +{ +#ifdef GIT_WIN32 + cl_skip(); +#endif + + try_set_locale(LC_COLLATE); + cl_git_pass(git_regexp_compile(®ex, "[\xc0-\xff][\x80-\xbf]", 0)); +} + +void test_core_regexp__regex_matches_digits_with_locale(void) +{ + char c, str[2]; + +#ifdef GIT_WIN32 + cl_skip(); +#endif + + try_set_locale(LC_COLLATE); + try_set_locale(LC_CTYPE); + + cl_git_pass(git_regexp_compile(®ex, "[[:digit:]]", 0)); + + str[1] = '\0'; + for (c = '0'; c <= '9'; c++) { + str[0] = c; + cl_git_pass(git_regexp_match(®ex, str)); + } +} + +void test_core_regexp__regex_matches_alphabet_with_locale(void) +{ + char c, str[2]; + +#ifdef GIT_WIN32 + cl_skip(); +#endif + + try_set_locale(LC_COLLATE); + try_set_locale(LC_CTYPE); + + cl_git_pass(git_regexp_compile(®ex, "[[:alpha:]]", 0)); + + str[1] = '\0'; + for (c = 'a'; c <= 'z'; c++) { + str[0] = c; + cl_git_pass(git_regexp_match(®ex, str)); + } + for (c = 'A'; c <= 'Z'; c++) { + str[0] = c; + cl_git_pass(git_regexp_match(®ex, str)); + } +} + +void test_core_regexp__compile_userdiff_regexps(void) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) { + git_diff_driver_definition ddef = builtin_defs[idx]; + + cl_git_pass(git_regexp_compile(®ex, ddef.fns, ddef.flags)); + git_regexp_dispose(®ex); + + cl_git_pass(git_regexp_compile(®ex, ddef.words, 0)); + git_regexp_dispose(®ex); + } +} + +void test_core_regexp__simple_search_matches(void) +{ + cl_git_pass(git_regexp_compile(®ex, "a", 0)); + cl_git_pass(git_regexp_search(®ex, "a", 0, NULL)); +} + +void test_core_regexp__case_insensitive_search_matches(void) +{ + cl_git_pass(git_regexp_compile(®ex, "a", GIT_REGEXP_ICASE)); + cl_git_pass(git_regexp_search(®ex, "A", 0, NULL)); +} + +void test_core_regexp__nonmatching_search_returns_error(void) +{ + cl_git_pass(git_regexp_compile(®ex, "a", 0)); + cl_git_fail(git_regexp_search(®ex, "b", 0, NULL)); +} + +void test_core_regexp__search_finds_complete_match(void) +{ + git_regmatch matches[1]; + + cl_git_pass(git_regexp_compile(®ex, "abc", 0)); + cl_git_pass(git_regexp_search(®ex, "abc", 1, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 3); +} + +void test_core_regexp__search_finds_correct_offsets(void) +{ + git_regmatch matches[3]; + + cl_git_pass(git_regexp_compile(®ex, "(a*)(b*)", 0)); + cl_git_pass(git_regexp_search(®ex, "ab", 3, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 2); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); + cl_assert_equal_i(matches[2].start, 1); + cl_assert_equal_i(matches[2].end, 2); +} + +void test_core_regexp__search_finds_empty_group(void) +{ + git_regmatch matches[3]; + + cl_git_pass(git_regexp_compile(®ex, "(a*)(b*)c", 0)); + cl_git_pass(git_regexp_search(®ex, "ac", 3, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 2); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); + cl_assert_equal_i(matches[2].start, 1); + cl_assert_equal_i(matches[2].end, 1); +} + +void test_core_regexp__search_fills_matches_with_first_matching_groups(void) +{ + git_regmatch matches[2]; + + cl_git_pass(git_regexp_compile(®ex, "(a)(b)(c)", 0)); + cl_git_pass(git_regexp_search(®ex, "abc", 2, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 3); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); +} + +void test_core_regexp__search_skips_nonmatching_group(void) +{ + git_regmatch matches[4]; + + cl_git_pass(git_regexp_compile(®ex, "(a)(b)?(c)", 0)); + cl_git_pass(git_regexp_search(®ex, "ac", 4, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 2); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); + cl_assert_equal_i(matches[2].start, -1); + cl_assert_equal_i(matches[2].end, -1); + cl_assert_equal_i(matches[3].start, 1); + cl_assert_equal_i(matches[3].end, 2); +} + +void test_core_regexp__search_initializes_trailing_nonmatching_groups(void) +{ + git_regmatch matches[3]; + + cl_git_pass(git_regexp_compile(®ex, "(a)bc", 0)); + cl_git_pass(git_regexp_search(®ex, "abc", 3, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 3); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); + cl_assert_equal_i(matches[2].start, -1); + cl_assert_equal_i(matches[2].end, -1); +} diff --git a/tests/libgit2/core/rmdir.c b/tests/libgit2/core/rmdir.c new file mode 100644 index 000000000..f6c66b3a4 --- /dev/null +++ b/tests/libgit2/core/rmdir.c @@ -0,0 +1,120 @@ +#include "clar_libgit2.h" +#include "futils.h" + +static const char *empty_tmp_dir = "test_gitfo_rmdir_recurs_test"; + +void test_core_rmdir__initialize(void) +{ + git_str path = GIT_STR_INIT; + + cl_must_pass(p_mkdir(empty_tmp_dir, 0777)); + + cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_one")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_two")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_two/three")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/two")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + git_str_dispose(&path); +} + +void test_core_rmdir__cleanup(void) +{ + if (git_fs_path_exists(empty_tmp_dir)) + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +/* make sure empty dir can be deleted recursively */ +void test_core_rmdir__delete_recursive(void) +{ + git_str path = GIT_STR_INIT; + cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one")); + cl_assert(git_fs_path_exists(git_str_cstr(&path))); + + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); + + cl_assert(!git_fs_path_exists(git_str_cstr(&path))); + + git_str_dispose(&path); +} + +/* make sure non-empty dir cannot be deleted recursively */ +void test_core_rmdir__fail_to_delete_non_empty_dir(void) +{ + git_str file = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&file, empty_tmp_dir, "/two/file.txt")); + + cl_git_mkfile(git_str_cstr(&file), "dummy"); + + cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); + + cl_must_pass(p_unlink(file.ptr)); + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); + + cl_assert(!git_fs_path_exists(empty_tmp_dir)); + + git_str_dispose(&file); +} + +void test_core_rmdir__keep_base(void) +{ + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_SKIP_ROOT)); + cl_assert(git_fs_path_exists(empty_tmp_dir)); +} + +void test_core_rmdir__can_skip_non_empty_dir(void) +{ + git_str file = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&file, empty_tmp_dir, "/two/file.txt")); + + cl_git_mkfile(git_str_cstr(&file), "dummy"); + + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_SKIP_NONEMPTY)); + cl_assert(git_fs_path_exists(git_str_cstr(&file)) == true); + + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_REMOVE_FILES)); + cl_assert(git_fs_path_exists(empty_tmp_dir) == false); + + git_str_dispose(&file); +} + +void test_core_rmdir__can_remove_empty_parents(void) +{ + git_str file = GIT_STR_INIT; + + cl_git_pass( + git_str_joinpath(&file, empty_tmp_dir, "/one/two_two/three/file.txt")); + cl_git_mkfile(git_str_cstr(&file), "dummy"); + cl_assert(git_fs_path_isfile(git_str_cstr(&file))); + + cl_git_pass(git_futils_rmdir_r("one/two_two/three/file.txt", empty_tmp_dir, + GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS)); + + cl_assert(!git_fs_path_exists(git_str_cstr(&file))); + + git_str_rtruncate_at_char(&file, '/'); /* three (only contained file.txt) */ + cl_assert(!git_fs_path_exists(git_str_cstr(&file))); + + git_str_rtruncate_at_char(&file, '/'); /* two_two (only contained three) */ + cl_assert(!git_fs_path_exists(git_str_cstr(&file))); + + git_str_rtruncate_at_char(&file, '/'); /* one (contained two_one also) */ + cl_assert(git_fs_path_exists(git_str_cstr(&file))); + + cl_assert(git_fs_path_exists(empty_tmp_dir) == true); + + git_str_dispose(&file); + + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); +} diff --git a/tests/libgit2/core/sha1.c b/tests/libgit2/core/sha1.c new file mode 100644 index 000000000..9ccdaab3c --- /dev/null +++ b/tests/libgit2/core/sha1.c @@ -0,0 +1,70 @@ +#include "clar_libgit2.h" +#include "hash.h" + +#define FIXTURE_DIR "sha1" + +void test_core_sha1__initialize(void) +{ + cl_fixture_sandbox(FIXTURE_DIR); +} + +void test_core_sha1__cleanup(void) +{ + cl_fixture_cleanup(FIXTURE_DIR); +} + +static int sha1_file(unsigned char *out, const char *filename) +{ + git_hash_ctx ctx; + char buf[2048]; + int fd, ret; + ssize_t read_len; + + fd = p_open(filename, O_RDONLY); + cl_assert(fd >= 0); + + cl_git_pass(git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)); + + while ((read_len = p_read(fd, buf, 2048)) > 0) + cl_git_pass(git_hash_update(&ctx, buf, (size_t)read_len)); + + cl_assert_equal_i(0, read_len); + p_close(fd); + + ret = git_hash_final(out, &ctx); + git_hash_ctx_cleanup(&ctx); + + return ret; +} + +void test_core_sha1__sum(void) +{ + unsigned char expected[GIT_HASH_SHA1_SIZE] = { + 0x4e, 0x72, 0x67, 0x9e, 0x3e, 0xa4, 0xd0, 0x4e, 0x0c, 0x64, + 0x2f, 0x02, 0x9e, 0x61, 0xeb, 0x80, 0x56, 0xc7, 0xed, 0x94 + }; + unsigned char actual[GIT_HASH_SHA1_SIZE]; + + cl_git_pass(sha1_file(actual, FIXTURE_DIR "/hello_c")); + cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA1_SIZE)); +} + +/* test that sha1 collision detection works when enabled */ +void test_core_sha1__detect_collision_attack(void) +{ + unsigned char actual[GIT_HASH_SHA1_SIZE]; + unsigned char expected[GIT_HASH_SHA1_SIZE] = { + 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, + 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a + }; + +#ifdef GIT_SHA1_COLLISIONDETECT + GIT_UNUSED(&expected); + cl_git_fail(sha1_file(actual, FIXTURE_DIR "/shattered-1.pdf")); + cl_assert_equal_s("SHA1 collision attack detected", git_error_last()->message); +#else + cl_git_pass(sha1_file(actual, FIXTURE_DIR "/shattered-1.pdf")); + cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA1_SIZE)); +#endif +} + diff --git a/tests/libgit2/core/sortedcache.c b/tests/libgit2/core/sortedcache.c new file mode 100644 index 000000000..cb4e34efa --- /dev/null +++ b/tests/libgit2/core/sortedcache.c @@ -0,0 +1,363 @@ +#include "clar_libgit2.h" +#include "sortedcache.h" + +static int name_only_cmp(const void *a, const void *b) +{ + return strcmp(a, b); +} + +void test_core_sortedcache__name_only(void) +{ + git_sortedcache *sc; + void *item; + size_t pos; + + cl_git_pass(git_sortedcache_new( + &sc, 0, NULL, NULL, name_only_cmp, NULL)); + + cl_git_pass(git_sortedcache_wlock(sc)); + cl_git_pass(git_sortedcache_upsert(&item, sc, "aaa")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "bbb")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "zzz")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "mmm")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "iii")); + git_sortedcache_wunlock(sc); + + cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL); + cl_assert_equal_s("aaa", item); + cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL); + cl_assert_equal_s("mmm", item); + cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL); + cl_assert_equal_s("zzz", item); + cl_assert(git_sortedcache_lookup(sc, "qqq") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("aaa", item); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("bbb", item); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("iii", item); + cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); + cl_assert_equal_s("mmm", item); + cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL); + cl_assert_equal_s("zzz", item); + cl_assert(git_sortedcache_entry(sc, 5) == NULL); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "iii")); + cl_assert_equal_sz(2, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz")); + cl_assert_equal_sz(4, pos); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "abc")); + + cl_git_pass(git_sortedcache_clear(sc, true)); + + cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + + git_sortedcache_free(sc); +} + +typedef struct { + int value; + char smaller_value; + char path[GIT_FLEX_ARRAY]; +} sortedcache_test_struct; + +static int sortedcache_test_struct_cmp(const void *a_, const void *b_) +{ + const sortedcache_test_struct *a = a_, *b = b_; + return strcmp(a->path, b->path); +} + +static void sortedcache_test_struct_free(void *payload, void *item_) +{ + sortedcache_test_struct *item = item_; + int *count = payload; + (*count)++; + item->smaller_value = 0; +} + +void test_core_sortedcache__in_memory(void) +{ + git_sortedcache *sc; + sortedcache_test_struct *item; + int free_count = 0; + + cl_git_pass(git_sortedcache_new( + &sc, offsetof(sortedcache_test_struct, path), + sortedcache_test_struct_free, &free_count, + sortedcache_test_struct_cmp, NULL)); + + cl_git_pass(git_sortedcache_wlock(sc)); + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "aaa")); + item->value = 10; + item->smaller_value = 1; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "bbb")); + item->value = 20; + item->smaller_value = 2; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "zzz")); + item->value = 30; + item->smaller_value = 26; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "mmm")); + item->value = 40; + item->smaller_value = 14; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "iii")); + item->value = 50; + item->smaller_value = 9; + git_sortedcache_wunlock(sc); + + cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); + + cl_git_pass(git_sortedcache_rlock(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL); + cl_assert_equal_s("aaa", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL); + cl_assert_equal_s("mmm", item->path); + cl_assert_equal_i(40, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL); + cl_assert_equal_s("zzz", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_lookup(sc, "abc") == NULL); + + /* not on Windows: + * cl_git_pass(git_sortedcache_rlock(sc)); -- grab more than one + */ + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("aaa", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("bbb", item->path); + cl_assert_equal_i(20, item->value); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("iii", item->path); + cl_assert_equal_i(50, item->value); + cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); + cl_assert_equal_s("mmm", item->path); + cl_assert_equal_i(40, item->value); + cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL); + cl_assert_equal_s("zzz", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_entry(sc, 5) == NULL); + + git_sortedcache_runlock(sc); + /* git_sortedcache_runlock(sc); */ + + cl_assert_equal_i(0, free_count); + + cl_git_pass(git_sortedcache_clear(sc, true)); + + cl_assert_equal_i(5, free_count); + + cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + + free_count = 0; + + cl_git_pass(git_sortedcache_wlock(sc)); + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "testing")); + item->value = 10; + item->smaller_value = 3; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "again")); + item->value = 20; + item->smaller_value = 1; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "final")); + item->value = 30; + item->smaller_value = 2; + git_sortedcache_wunlock(sc); + + cl_assert_equal_sz(3, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "testing")) != NULL); + cl_assert_equal_s("testing", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "again")) != NULL); + cl_assert_equal_s("again", item->path); + cl_assert_equal_i(20, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_lookup(sc, "zzz") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("again", item->path); + cl_assert_equal_i(20, item->value); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(30, item->value); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("testing", item->path); + cl_assert_equal_i(10, item->value); + cl_assert(git_sortedcache_entry(sc, 3) == NULL); + + { + size_t pos; + + cl_git_pass(git_sortedcache_wlock(sc)); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "again")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_remove(sc, pos)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "again")); + + cl_assert_equal_sz(2, git_sortedcache_entrycount(sc)); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "testing")); + cl_assert_equal_sz(1, pos); + cl_git_pass(git_sortedcache_remove(sc, pos)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "testing")); + + cl_assert_equal_sz(1, git_sortedcache_entrycount(sc)); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_remove(sc, pos)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "final")); + + cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); + + git_sortedcache_wunlock(sc); + } + + git_sortedcache_free(sc); + + cl_assert_equal_i(3, free_count); +} + +static void sortedcache_test_reload(git_sortedcache *sc) +{ + int count = 0; + git_str buf = GIT_STR_INIT; + char *scan, *after; + sortedcache_test_struct *item; + + cl_assert(git_sortedcache_lockandload(sc, &buf) > 0); + + cl_git_pass(git_sortedcache_clear(sc, false)); /* clear once we already have lock */ + + for (scan = buf.ptr; *scan; scan = after + 1) { + int val = strtol(scan, &after, 0); + cl_assert(after > scan); + scan = after; + + for (scan = after; git__isspace(*scan); ++scan) /* find start */; + for (after = scan; *after && *after != '\n'; ++after) /* find eol */; + *after = '\0'; + + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, scan)); + + item->value = val; + item->smaller_value = (char)(count++); + } + + git_sortedcache_wunlock(sc); + + git_str_dispose(&buf); +} + +void test_core_sortedcache__on_disk(void) +{ + git_sortedcache *sc; + sortedcache_test_struct *item; + int free_count = 0; + size_t pos; + + cl_git_mkfile("cacheitems.txt", "10 abc\n20 bcd\n30 cde\n"); + + cl_git_pass(git_sortedcache_new( + &sc, offsetof(sortedcache_test_struct, path), + sortedcache_test_struct_free, &free_count, + sortedcache_test_struct_cmp, "cacheitems.txt")); + + /* should need to reload the first time */ + + sortedcache_test_reload(sc); + + /* test what we loaded */ + + cl_assert_equal_sz(3, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL); + cl_assert_equal_s("abc", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "cde")) != NULL); + cl_assert_equal_s("cde", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("abc", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("bcd", item->path); + cl_assert_equal_i(20, item->value); + cl_assert(git_sortedcache_entry(sc, 3) == NULL); + + /* should not need to reload this time */ + + cl_assert_equal_i(0, git_sortedcache_lockandload(sc, NULL)); + + /* rewrite ondisk file and reload */ + + cl_assert_equal_i(0, free_count); + + cl_git_rewritefile( + "cacheitems.txt", "100 abc\n200 zzz\n500 aaa\n10 final\n"); + sortedcache_test_reload(sc); + + cl_assert_equal_i(3, free_count); + + /* test what we loaded */ + + cl_assert_equal_sz(4, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL); + cl_assert_equal_s("abc", item->path); + cl_assert_equal_i(100, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(10, item->value); + cl_assert(git_sortedcache_lookup(sc, "cde") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("aaa", item->path); + cl_assert_equal_i(500, item->value); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); + cl_assert_equal_s("zzz", item->path); + cl_assert_equal_i(200, item->value); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "abc")); + cl_assert_equal_sz(1, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final")); + cl_assert_equal_sz(2, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz")); + cl_assert_equal_sz(3, pos); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "missing")); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "cde")); + + git_sortedcache_free(sc); + + cl_assert_equal_i(7, free_count); +} + diff --git a/tests/libgit2/core/stat.c b/tests/libgit2/core/stat.c new file mode 100644 index 000000000..210072fe3 --- /dev/null +++ b/tests/libgit2/core/stat.c @@ -0,0 +1,113 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "posix.h" + +void test_core_stat__initialize(void) +{ + cl_git_pass(git_futils_mkdir("root/d1/d2", 0755, GIT_MKDIR_PATH)); + cl_git_mkfile("root/file", "whatever\n"); + cl_git_mkfile("root/d1/file", "whatever\n"); +} + +void test_core_stat__cleanup(void) +{ + git_futils_rmdir_r("root", NULL, GIT_RMDIR_REMOVE_FILES); +} + +#define cl_assert_error(val) \ + do { err = errno; cl_assert_equal_i((val), err); } while (0) + +void test_core_stat__0(void) +{ + struct stat st; + int err; + + cl_assert_equal_i(0, p_lstat("root", &st)); + cl_assert(S_ISDIR(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/", &st)); + cl_assert(S_ISDIR(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/file", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/d1", &st)); + cl_assert(S_ISDIR(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/d1/", &st)); + cl_assert(S_ISDIR(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/d1/file", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_error(0); + + cl_assert(p_lstat("root/missing", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat("root/missing/but/could/be/created", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat_posixly("root/missing/but/could/be/created", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat("root/d1/missing", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat("root/d1/missing/deeper/path", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat_posixly("root/d1/missing/deeper/path", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat_posixly("root/d1/file/deeper/path", &st) < 0); + cl_assert_error(ENOTDIR); + + cl_assert(p_lstat("root/file/invalid", &st) < 0); +#ifdef GIT_WIN32 + cl_assert_error(ENOENT); +#else + cl_assert_error(ENOTDIR); +#endif + + cl_assert(p_lstat_posixly("root/file/invalid", &st) < 0); + cl_assert_error(ENOTDIR); + + cl_assert(p_lstat("root/file/invalid/deeper_path", &st) < 0); +#ifdef GIT_WIN32 + cl_assert_error(ENOENT); +#else + cl_assert_error(ENOTDIR); +#endif + + cl_assert(p_lstat_posixly("root/file/invalid/deeper_path", &st) < 0); + cl_assert_error(ENOTDIR); + + cl_assert(p_lstat_posixly("root/d1/file/extra", &st) < 0); + cl_assert_error(ENOTDIR); + + cl_assert(p_lstat_posixly("root/d1/file/further/invalid/items", &st) < 0); + cl_assert_error(ENOTDIR); +} + +void test_core_stat__root(void) +{ + const char *sandbox = clar_sandbox_path(); + git_str root = GIT_STR_INIT; + int root_len; + struct stat st; + + root_len = git_fs_path_root(sandbox); + cl_assert(root_len >= 0); + + git_str_set(&root, sandbox, root_len+1); + + cl_must_pass(p_stat(root.ptr, &st)); + cl_assert(S_ISDIR(st.st_mode)); + + git_str_dispose(&root); +} diff --git a/tests/libgit2/core/string.c b/tests/libgit2/core/string.c new file mode 100644 index 000000000..928dfbcc1 --- /dev/null +++ b/tests/libgit2/core/string.c @@ -0,0 +1,136 @@ +#include "clar_libgit2.h" + +/* compare prefixes */ +void test_core_string__0(void) +{ + cl_assert(git__prefixcmp("", "") == 0); + cl_assert(git__prefixcmp("a", "") == 0); + cl_assert(git__prefixcmp("", "a") < 0); + cl_assert(git__prefixcmp("a", "b") < 0); + cl_assert(git__prefixcmp("b", "a") > 0); + cl_assert(git__prefixcmp("ab", "a") == 0); + cl_assert(git__prefixcmp("ab", "ac") < 0); + cl_assert(git__prefixcmp("ab", "aa") > 0); +} + +/* compare suffixes */ +void test_core_string__1(void) +{ + cl_assert(git__suffixcmp("", "") == 0); + cl_assert(git__suffixcmp("a", "") == 0); + cl_assert(git__suffixcmp("", "a") < 0); + cl_assert(git__suffixcmp("a", "b") < 0); + cl_assert(git__suffixcmp("b", "a") > 0); + cl_assert(git__suffixcmp("ba", "a") == 0); + cl_assert(git__suffixcmp("zaa", "ac") < 0); + cl_assert(git__suffixcmp("zaz", "ac") > 0); +} + +/* compare icase sorting with case equality */ +void test_core_string__2(void) +{ + cl_assert(git__strcasesort_cmp("", "") == 0); + cl_assert(git__strcasesort_cmp("foo", "foo") == 0); + cl_assert(git__strcasesort_cmp("foo", "bar") > 0); + cl_assert(git__strcasesort_cmp("bar", "foo") < 0); + cl_assert(git__strcasesort_cmp("foo", "FOO") > 0); + cl_assert(git__strcasesort_cmp("FOO", "foo") < 0); + cl_assert(git__strcasesort_cmp("foo", "BAR") > 0); + cl_assert(git__strcasesort_cmp("BAR", "foo") < 0); + cl_assert(git__strcasesort_cmp("fooBar", "foobar") < 0); +} + +/* compare prefixes with len */ +void test_core_string__prefixncmp(void) +{ + cl_assert(git__prefixncmp("", 0, "") == 0); + cl_assert(git__prefixncmp("a", 1, "") == 0); + cl_assert(git__prefixncmp("", 0, "a") < 0); + cl_assert(git__prefixncmp("a", 1, "b") < 0); + cl_assert(git__prefixncmp("b", 1, "a") > 0); + cl_assert(git__prefixncmp("ab", 2, "a") == 0); + cl_assert(git__prefixncmp("ab", 1, "a") == 0); + cl_assert(git__prefixncmp("ab", 2, "ac") < 0); + cl_assert(git__prefixncmp("a", 1, "ac") < 0); + cl_assert(git__prefixncmp("ab", 1, "ac") < 0); + cl_assert(git__prefixncmp("ab", 2, "aa") > 0); + cl_assert(git__prefixncmp("ab", 1, "aa") < 0); +} + +/* compare prefixes with len */ +void test_core_string__prefixncmp_icase(void) +{ + cl_assert(git__prefixncmp_icase("", 0, "") == 0); + cl_assert(git__prefixncmp_icase("a", 1, "") == 0); + cl_assert(git__prefixncmp_icase("", 0, "a") < 0); + cl_assert(git__prefixncmp_icase("a", 1, "b") < 0); + cl_assert(git__prefixncmp_icase("A", 1, "b") < 0); + cl_assert(git__prefixncmp_icase("a", 1, "B") < 0); + cl_assert(git__prefixncmp_icase("b", 1, "a") > 0); + cl_assert(git__prefixncmp_icase("B", 1, "a") > 0); + cl_assert(git__prefixncmp_icase("b", 1, "A") > 0); + cl_assert(git__prefixncmp_icase("ab", 2, "a") == 0); + cl_assert(git__prefixncmp_icase("Ab", 2, "a") == 0); + cl_assert(git__prefixncmp_icase("ab", 2, "A") == 0); + cl_assert(git__prefixncmp_icase("ab", 1, "a") == 0); + cl_assert(git__prefixncmp_icase("ab", 2, "ac") < 0); + cl_assert(git__prefixncmp_icase("Ab", 2, "ac") < 0); + cl_assert(git__prefixncmp_icase("ab", 2, "Ac") < 0); + cl_assert(git__prefixncmp_icase("a", 1, "ac") < 0); + cl_assert(git__prefixncmp_icase("ab", 1, "ac") < 0); + cl_assert(git__prefixncmp_icase("ab", 2, "aa") > 0); + cl_assert(git__prefixncmp_icase("ab", 1, "aa") < 0); +} + +void test_core_string__strcmp(void) +{ + cl_assert(git__strcmp("", "") == 0); + cl_assert(git__strcmp("foo", "foo") == 0); + cl_assert(git__strcmp("Foo", "foo") < 0); + cl_assert(git__strcmp("foo", "FOO") > 0); + cl_assert(git__strcmp("foo", "fOO") > 0); + + cl_assert(strcmp("rt\303\202of", "rt dev\302\266h") > 0); + cl_assert(strcmp("e\342\202\254ghi=", "et") > 0); + cl_assert(strcmp("rt dev\302\266h", "rt\303\202of") < 0); + cl_assert(strcmp("et", "e\342\202\254ghi=") < 0); + cl_assert(strcmp("\303\215", "\303\255") < 0); + + cl_assert(git__strcmp("rt\303\202of", "rt dev\302\266h") > 0); + cl_assert(git__strcmp("e\342\202\254ghi=", "et") > 0); + cl_assert(git__strcmp("rt dev\302\266h", "rt\303\202of") < 0); + cl_assert(git__strcmp("et", "e\342\202\254ghi=") < 0); + cl_assert(git__strcmp("\303\215", "\303\255") < 0); +} + +void test_core_string__strcasecmp(void) +{ + cl_assert(git__strcasecmp("", "") == 0); + cl_assert(git__strcasecmp("foo", "foo") == 0); + cl_assert(git__strcasecmp("foo", "Foo") == 0); + cl_assert(git__strcasecmp("foo", "FOO") == 0); + cl_assert(git__strcasecmp("foo", "fOO") == 0); + + cl_assert(strcasecmp("rt\303\202of", "rt dev\302\266h") > 0); + cl_assert(strcasecmp("e\342\202\254ghi=", "et") > 0); + cl_assert(strcasecmp("rt dev\302\266h", "rt\303\202of") < 0); + cl_assert(strcasecmp("et", "e\342\202\254ghi=") < 0); + cl_assert(strcasecmp("\303\215", "\303\255") < 0); + + cl_assert(git__strcasecmp("rt\303\202of", "rt dev\302\266h") > 0); + cl_assert(git__strcasecmp("e\342\202\254ghi=", "et") > 0); + cl_assert(git__strcasecmp("rt dev\302\266h", "rt\303\202of") < 0); + cl_assert(git__strcasecmp("et", "e\342\202\254ghi=") < 0); + cl_assert(git__strcasecmp("\303\215", "\303\255") < 0); +} + +void test_core_string__strlcmp(void) +{ + const char foo[3] = { 'f', 'o', 'o' }; + + cl_assert(git__strlcmp("foo", "foo", 3) == 0); + cl_assert(git__strlcmp("foo", foo, 3) == 0); + cl_assert(git__strlcmp("foo", "foobar", 3) == 0); + cl_assert(git__strlcmp("foobar", "foo", 3) > 0); + cl_assert(git__strlcmp("foo", "foobar", 6) < 0); +} diff --git a/tests/libgit2/core/strmap.c b/tests/libgit2/core/strmap.c new file mode 100644 index 000000000..ba118ae1e --- /dev/null +++ b/tests/libgit2/core/strmap.c @@ -0,0 +1,190 @@ +#include "clar_libgit2.h" +#include "strmap.h" + +static git_strmap *g_table; + +void test_core_strmap__initialize(void) +{ + cl_git_pass(git_strmap_new(&g_table)); + cl_assert(g_table != NULL); +} + +void test_core_strmap__cleanup(void) +{ + git_strmap_free(g_table); +} + +void test_core_strmap__0(void) +{ + cl_assert(git_strmap_size(g_table) == 0); +} + +static void insert_strings(git_strmap *table, size_t count) +{ + size_t i, j, over; + char *str; + + for (i = 0; i < count; ++i) { + str = malloc(10); + for (j = 0; j < 10; ++j) + str[j] = 'a' + (i % 26); + str[9] = '\0'; + + /* if > 26, then encode larger value in first letters */ + for (j = 0, over = i / 26; over > 0; j++, over = over / 26) + str[j] = 'A' + (over % 26); + + cl_git_pass(git_strmap_set(table, str, str)); + } + + cl_assert_equal_i(git_strmap_size(table), count); +} + +void test_core_strmap__inserted_strings_can_be_retrieved(void) +{ + char *str; + int i; + + insert_strings(g_table, 20); + + cl_assert(git_strmap_exists(g_table, "aaaaaaaaa")); + cl_assert(git_strmap_exists(g_table, "ggggggggg")); + cl_assert(!git_strmap_exists(g_table, "aaaaaaaab")); + cl_assert(!git_strmap_exists(g_table, "abcdefghi")); + + i = 0; + git_strmap_foreach_value(g_table, str, { i++; free(str); }); + cl_assert(i == 20); +} + +void test_core_strmap__deleted_entry_cannot_be_retrieved(void) +{ + char *str; + int i; + + insert_strings(g_table, 20); + + cl_assert(git_strmap_exists(g_table, "bbbbbbbbb")); + str = git_strmap_get(g_table, "bbbbbbbbb"); + cl_assert_equal_s(str, "bbbbbbbbb"); + cl_git_pass(git_strmap_delete(g_table, "bbbbbbbbb")); + free(str); + + cl_assert(!git_strmap_exists(g_table, "bbbbbbbbb")); + + i = 0; + git_strmap_foreach_value(g_table, str, { i++; free(str); }); + cl_assert_equal_i(i, 19); +} + +void test_core_strmap__inserting_many_keys_succeeds(void) +{ + char *str; + int i; + + insert_strings(g_table, 10000); + + i = 0; + git_strmap_foreach_value(g_table, str, { i++; free(str); }); + cl_assert_equal_i(i, 10000); +} + +void test_core_strmap__get_succeeds_with_existing_entries(void) +{ + const char *keys[] = { "foo", "bar", "gobble" }; + char *values[] = { "oof", "rab", "elbbog" }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(keys); i++) + cl_git_pass(git_strmap_set(g_table, keys[i], values[i])); + + cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); + cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab"); + cl_assert_equal_s(git_strmap_get(g_table, "gobble"), "elbbog"); +} + +void test_core_strmap__get_returns_null_on_nonexisting_key(void) +{ + const char *keys[] = { "foo", "bar", "gobble" }; + char *values[] = { "oof", "rab", "elbbog" }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(keys); i++) + cl_git_pass(git_strmap_set(g_table, keys[i], values[i])); + + cl_assert_equal_p(git_strmap_get(g_table, "other"), NULL); +} + +void test_core_strmap__set_persists_key(void) +{ + cl_git_pass(git_strmap_set(g_table, "foo", "oof")); + cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); +} + +void test_core_strmap__set_persists_multpile_keys(void) +{ + cl_git_pass(git_strmap_set(g_table, "foo", "oof")); + cl_git_pass(git_strmap_set(g_table, "bar", "rab")); + cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); + cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab"); +} + +void test_core_strmap__set_updates_existing_key(void) +{ + cl_git_pass(git_strmap_set(g_table, "foo", "oof")); + cl_git_pass(git_strmap_set(g_table, "bar", "rab")); + cl_git_pass(git_strmap_set(g_table, "gobble", "elbbog")); + cl_assert_equal_i(git_strmap_size(g_table), 3); + + cl_git_pass(git_strmap_set(g_table, "foo", "other")); + cl_assert_equal_i(git_strmap_size(g_table), 3); + + cl_assert_equal_s(git_strmap_get(g_table, "foo"), "other"); +} + +void test_core_strmap__iteration(void) +{ + struct { + char *key; + char *value; + int seen; + } entries[] = { + { "foo", "oof" }, + { "bar", "rab" }, + { "gobble", "elbbog" }, + }; + const char *key, *value; + size_t i, n; + + for (i = 0; i < ARRAY_SIZE(entries); i++) + cl_git_pass(git_strmap_set(g_table, entries[i].key, entries[i].value)); + + i = 0, n = 0; + while (git_strmap_iterate((void **) &value, g_table, &i, &key) == 0) { + size_t j; + + for (j = 0; j < ARRAY_SIZE(entries); j++) { + if (strcmp(entries[j].key, key)) + continue; + + cl_assert_equal_i(entries[j].seen, 0); + cl_assert_equal_s(entries[j].value, value); + entries[j].seen++; + break; + } + + n++; + } + + for (i = 0; i < ARRAY_SIZE(entries); i++) + cl_assert_equal_i(entries[i].seen, 1); + + cl_assert_equal_i(n, ARRAY_SIZE(entries)); +} + +void test_core_strmap__iterating_empty_map_stops_immediately(void) +{ + size_t i = 0; + + cl_git_fail_with(git_strmap_iterate(NULL, g_table, &i, NULL), GIT_ITEROVER); +} diff --git a/tests/libgit2/core/strtol.c b/tests/libgit2/core/strtol.c new file mode 100644 index 000000000..851b91b0a --- /dev/null +++ b/tests/libgit2/core/strtol.c @@ -0,0 +1,128 @@ +#include "clar_libgit2.h" + +static void assert_l32_parses(const char *string, int32_t expected, int base) +{ + int32_t i; + cl_git_pass(git__strntol32(&i, string, strlen(string), NULL, base)); + cl_assert_equal_i(i, expected); +} + +static void assert_l32_fails(const char *string, int base) +{ + int32_t i; + cl_git_fail(git__strntol32(&i, string, strlen(string), NULL, base)); +} + +static void assert_l64_parses(const char *string, int64_t expected, int base) +{ + int64_t i; + cl_git_pass(git__strntol64(&i, string, strlen(string), NULL, base)); + cl_assert_equal_i(i, expected); +} + +static void assert_l64_fails(const char *string, int base) +{ + int64_t i; + cl_git_fail(git__strntol64(&i, string, strlen(string), NULL, base)); +} + +void test_core_strtol__int32(void) +{ + assert_l32_parses("123", 123, 10); + assert_l32_parses(" +123 ", 123, 10); + assert_l32_parses(" -123 ", -123, 10); + assert_l32_parses(" +2147483647 ", 2147483647, 10); + assert_l32_parses(" -2147483648 ", INT64_C(-2147483648), 10); + assert_l32_parses("A", 10, 16); + assert_l32_parses("1x1", 1, 10); + + assert_l32_fails("", 10); + assert_l32_fails("a", 10); + assert_l32_fails("x10x", 10); + assert_l32_fails(" 2147483657 ", 10); + assert_l32_fails(" -2147483657 ", 10); +} + +void test_core_strtol__int64(void) +{ + assert_l64_parses("123", 123, 10); + assert_l64_parses(" +123 ", 123, 10); + assert_l64_parses(" -123 ", -123, 10); + assert_l64_parses(" +2147483647 ", 2147483647, 10); + assert_l64_parses(" -2147483648 ", INT64_C(-2147483648), 10); + assert_l64_parses(" 2147483657 ", INT64_C(2147483657), 10); + assert_l64_parses(" -2147483657 ", INT64_C(-2147483657), 10); + assert_l64_parses(" 9223372036854775807 ", INT64_MAX, 10); + assert_l64_parses(" -9223372036854775808 ", INT64_MIN, 10); + assert_l64_parses(" 0x7fffffffffffffff ", INT64_MAX, 16); + assert_l64_parses(" -0x8000000000000000 ", INT64_MIN, 16); + assert_l64_parses("1a", 26, 16); + assert_l64_parses("1A", 26, 16); + + assert_l64_fails("", 10); + assert_l64_fails("a", 10); + assert_l64_fails("x10x", 10); + assert_l64_fails("0x8000000000000000", 16); + assert_l64_fails("-0x8000000000000001", 16); +} + +void test_core_strtol__base_autodetection(void) +{ + assert_l64_parses("0", 0, 0); + assert_l64_parses("00", 0, 0); + assert_l64_parses("0x", 0, 0); + assert_l64_parses("0foobar", 0, 0); + assert_l64_parses("07", 7, 0); + assert_l64_parses("017", 15, 0); + assert_l64_parses("0x8", 8, 0); + assert_l64_parses("0x18", 24, 0); +} + +void test_core_strtol__buffer_length_with_autodetection_truncates(void) +{ + int64_t i64; + + cl_git_pass(git__strntol64(&i64, "011", 2, NULL, 0)); + cl_assert_equal_i(i64, 1); + cl_git_pass(git__strntol64(&i64, "0x11", 3, NULL, 0)); + cl_assert_equal_i(i64, 1); +} + +void test_core_strtol__buffer_length_truncates(void) +{ + int32_t i32; + int64_t i64; + + cl_git_pass(git__strntol32(&i32, "11", 1, NULL, 10)); + cl_assert_equal_i(i32, 1); + + cl_git_pass(git__strntol64(&i64, "11", 1, NULL, 10)); + cl_assert_equal_i(i64, 1); +} + +void test_core_strtol__buffer_length_with_leading_ws_truncates(void) +{ + int64_t i64; + + cl_git_fail(git__strntol64(&i64, " 1", 1, NULL, 10)); + + cl_git_pass(git__strntol64(&i64, " 11", 2, NULL, 10)); + cl_assert_equal_i(i64, 1); +} + +void test_core_strtol__buffer_length_with_leading_sign_truncates(void) +{ + int64_t i64; + + cl_git_fail(git__strntol64(&i64, "-1", 1, NULL, 10)); + + cl_git_pass(git__strntol64(&i64, "-11", 2, NULL, 10)); + cl_assert_equal_i(i64, -1); +} + +void test_core_strtol__error_message_cuts_off(void) +{ + assert_l32_fails("2147483657foobar", 10); + cl_assert(strstr(git_error_last()->message, "2147483657") != NULL); + cl_assert(strstr(git_error_last()->message, "foobar") == NULL); +} diff --git a/tests/libgit2/core/structinit.c b/tests/libgit2/core/structinit.c new file mode 100644 index 000000000..160e2f612 --- /dev/null +++ b/tests/libgit2/core/structinit.c @@ -0,0 +1,201 @@ +#include "clar_libgit2.h" +#include +#include +#include +#include +#include +#include + +#define STRINGIFY(s) #s + +/* Checks two conditions for the specified structure: + * 1. That the initializers for the latest version produces the same + * in-memory representation. + * 2. That the function-based initializer supports all versions from 1...n, + * where n is the latest version (often represented by GIT_*_VERSION). + * + * Parameters: + * structname: The name of the structure to test, e.g. git_blame_options. + * structver: The latest version of the specified structure. + * macroinit: The macro that initializes the latest version of the structure. + * funcinitname: The function that initializes the structure. Must have the + * signature "int (structname* instance, int version)". + */ +#define CHECK_MACRO_FUNC_INIT_EQUAL(structname, structver, macroinit, funcinitname) \ +do { \ + structname structname##_macro_latest = macroinit; \ + structname structname##_func_latest; \ + int structname##_curr_ver = structver - 1; \ + memset(&structname##_func_latest, 0, sizeof(structname##_func_latest)); \ + cl_git_pass(funcinitname(&structname##_func_latest, structver)); \ + options_cmp(&structname##_macro_latest, &structname##_func_latest, \ + sizeof(structname), STRINGIFY(structname)); \ + \ + while (structname##_curr_ver > 0) \ + { \ + structname macro; \ + cl_git_pass(funcinitname(¯o, structname##_curr_ver)); \ + structname##_curr_ver--; \ + }\ +} while(0) + +static void options_cmp(void *one, void *two, size_t size, const char *name) +{ + size_t i; + + for (i = 0; i < size; i++) { + if (((char *)one)[i] != ((char *)two)[i]) { + char desc[1024]; + + p_snprintf(desc, 1024, "Difference in %s at byte %" PRIuZ ": macro=%u / func=%u", + name, i, ((char *)one)[i], ((char *)two)[i]); + clar__fail(__FILE__, __func__, __LINE__, + "Difference between macro and function options initializer", + desc, 0); + return; + } + } +} + +void test_core_structinit__compare(void) +{ + /* These tests assume that they can memcmp() two structures that were + * initialized with the same static initializer. Eg, + * git_blame_options = GIT_BLAME_OPTIONS_INIT; + * + * This assumption fails when there is padding between structure members, + * which is not guaranteed to be initialized to anything sane at all. + * + * Assume most compilers, in a debug build, will clear that memory for + * us or set it to sentinel markers. Etc. + */ +#if !defined(DEBUG) && !defined(_DEBUG) + clar__skip(); +#endif + + /* apply */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_apply_options, GIT_APPLY_OPTIONS_VERSION, \ + GIT_APPLY_OPTIONS_INIT, git_apply_options_init); + + /* blame */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_blame_options, GIT_BLAME_OPTIONS_VERSION, \ + GIT_BLAME_OPTIONS_INIT, git_blame_options_init); + + /* blob_filter_options */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_blob_filter_options, GIT_BLOB_FILTER_OPTIONS_VERSION, \ + GIT_BLOB_FILTER_OPTIONS_INIT, git_blob_filter_options_init); + + /* checkout */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_checkout_options, GIT_CHECKOUT_OPTIONS_VERSION, \ + GIT_CHECKOUT_OPTIONS_INIT, git_checkout_options_init); + + /* clone */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_clone_options, GIT_CLONE_OPTIONS_VERSION, \ + GIT_CLONE_OPTIONS_INIT, git_clone_options_init); + + /* commit_graph_writer */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_commit_graph_writer_options, \ + GIT_COMMIT_GRAPH_WRITER_OPTIONS_VERSION, \ + GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT, \ + git_commit_graph_writer_options_init); + + /* diff */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_diff_options, GIT_DIFF_OPTIONS_VERSION, \ + GIT_DIFF_OPTIONS_INIT, git_diff_options_init); + + /* diff_find */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_diff_find_options, GIT_DIFF_FIND_OPTIONS_VERSION, \ + GIT_DIFF_FIND_OPTIONS_INIT, git_diff_find_options_init); + + /* filter */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_filter, GIT_FILTER_VERSION, \ + GIT_FILTER_INIT, git_filter_init); + + /* merge_file_input */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_merge_file_input, GIT_MERGE_FILE_INPUT_VERSION, \ + GIT_MERGE_FILE_INPUT_INIT, git_merge_file_input_init); + + /* merge_file */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_merge_file_options, GIT_MERGE_FILE_OPTIONS_VERSION, \ + GIT_MERGE_FILE_OPTIONS_INIT, git_merge_file_options_init); + + /* merge_tree */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_merge_options, GIT_MERGE_OPTIONS_VERSION, \ + GIT_MERGE_OPTIONS_INIT, git_merge_options_init); + + /* push */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_push_options, GIT_PUSH_OPTIONS_VERSION, \ + GIT_PUSH_OPTIONS_INIT, git_push_options_init); + + /* remote */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_remote_callbacks, GIT_REMOTE_CALLBACKS_VERSION, \ + GIT_REMOTE_CALLBACKS_INIT, git_remote_init_callbacks); + + /* repository_init */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_repository_init_options, GIT_REPOSITORY_INIT_OPTIONS_VERSION, \ + GIT_REPOSITORY_INIT_OPTIONS_INIT, git_repository_init_options_init); + + /* revert */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_revert_options, GIT_REVERT_OPTIONS_VERSION, \ + GIT_REVERT_OPTIONS_INIT, git_revert_options_init); + + /* stash apply */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_stash_apply_options, GIT_STASH_APPLY_OPTIONS_VERSION, \ + GIT_STASH_APPLY_OPTIONS_INIT, git_stash_apply_options_init); + + /* status */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_status_options, GIT_STATUS_OPTIONS_VERSION, \ + GIT_STATUS_OPTIONS_INIT, git_status_options_init); + + /* transport */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_transport, GIT_TRANSPORT_VERSION, \ + GIT_TRANSPORT_INIT, git_transport_init); + + /* config_backend */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_config_backend, GIT_CONFIG_BACKEND_VERSION, \ + GIT_CONFIG_BACKEND_INIT, git_config_init_backend); + + /* odb_backend */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_odb_backend, GIT_ODB_BACKEND_VERSION, \ + GIT_ODB_BACKEND_INIT, git_odb_init_backend); + + /* refdb_backend */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_refdb_backend, GIT_REFDB_BACKEND_VERSION, \ + GIT_REFDB_BACKEND_INIT, git_refdb_init_backend); + + /* submodule update */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_submodule_update_options, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, \ + GIT_SUBMODULE_UPDATE_OPTIONS_INIT, git_submodule_update_options_init); + + /* submodule update */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_proxy_options, GIT_PROXY_OPTIONS_VERSION, \ + GIT_PROXY_OPTIONS_INIT, git_proxy_options_init); + + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_diff_patchid_options, GIT_DIFF_PATCHID_OPTIONS_VERSION, \ + GIT_DIFF_PATCHID_OPTIONS_INIT, git_diff_patchid_options_init); +} diff --git a/tests/libgit2/core/useragent.c b/tests/libgit2/core/useragent.c new file mode 100644 index 000000000..a4ece902f --- /dev/null +++ b/tests/libgit2/core/useragent.c @@ -0,0 +1,17 @@ +#include "clar_libgit2.h" +#include "settings.h" + +void test_core_useragent__get(void) +{ + const char *custom_name = "super duper git"; + git_str buf = GIT_STR_INIT; + + cl_assert_equal_p(NULL, git_libgit2__user_agent()); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, custom_name)); + cl_assert_equal_s(custom_name, git_libgit2__user_agent()); + + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT, &buf)); + cl_assert_equal_s(custom_name, buf.ptr); + + git_str_dispose(&buf); +} diff --git a/tests/libgit2/core/utf8.c b/tests/libgit2/core/utf8.c new file mode 100644 index 000000000..e1987b8d6 --- /dev/null +++ b/tests/libgit2/core/utf8.c @@ -0,0 +1,20 @@ +#include "clar_libgit2.h" +#include "utf8.h" + +void test_core_utf8__char_length(void) +{ + cl_assert_equal_i(0, git_utf8_char_length("", 0)); + cl_assert_equal_i(1, git_utf8_char_length("$", 1)); + cl_assert_equal_i(5, git_utf8_char_length("abcde", 5)); + cl_assert_equal_i(1, git_utf8_char_length("\xc2\xa2", 2)); + cl_assert_equal_i(2, git_utf8_char_length("\x24\xc2\xa2", 3)); + cl_assert_equal_i(1, git_utf8_char_length("\xf0\x90\x8d\x88", 4)); + + /* uncontinued character counted as single characters */ + cl_assert_equal_i(2, git_utf8_char_length("\x24\xc2", 2)); + cl_assert_equal_i(3, git_utf8_char_length("\x24\xc2\xc2\xa2", 4)); + + /* invalid characters are counted as single characters */ + cl_assert_equal_i(4, git_utf8_char_length("\x24\xc0\xc0\x34", 4)); + cl_assert_equal_i(4, git_utf8_char_length("\x24\xf5\xfd\xc2", 4)); +} diff --git a/tests/libgit2/core/vector.c b/tests/libgit2/core/vector.c new file mode 100644 index 000000000..08cd2c19b --- /dev/null +++ b/tests/libgit2/core/vector.c @@ -0,0 +1,430 @@ +#include + +#include "clar_libgit2.h" +#include "vector.h" + +/* initial size of 1 would cause writing past array bounds */ +void test_core_vector__0(void) +{ + git_vector x; + int i; + cl_git_pass(git_vector_init(&x, 1, NULL)); + for (i = 0; i < 10; ++i) { + git_vector_insert(&x, (void*) 0xabc); + } + git_vector_free(&x); +} + + +/* don't read past array bounds on remove() */ +void test_core_vector__1(void) +{ + git_vector x; + /* make initial capacity exact for our insertions. */ + cl_git_pass(git_vector_init(&x, 3, NULL)); + git_vector_insert(&x, (void*) 0xabc); + git_vector_insert(&x, (void*) 0xdef); + git_vector_insert(&x, (void*) 0x123); + + git_vector_remove(&x, 0); /* used to read past array bounds. */ + git_vector_free(&x); +} + + +static int test_cmp(const void *a, const void *b) +{ + return *(const int *)a - *(const int *)b; +} + +/* remove duplicates */ +void test_core_vector__2(void) +{ + git_vector x; + int *ptrs[2]; + + ptrs[0] = git__malloc(sizeof(int)); + ptrs[1] = git__malloc(sizeof(int)); + + *ptrs[0] = 2; + *ptrs[1] = 1; + + cl_git_pass(git_vector_init(&x, 5, test_cmp)); + cl_git_pass(git_vector_insert(&x, ptrs[0])); + cl_git_pass(git_vector_insert(&x, ptrs[1])); + cl_git_pass(git_vector_insert(&x, ptrs[1])); + cl_git_pass(git_vector_insert(&x, ptrs[0])); + cl_git_pass(git_vector_insert(&x, ptrs[1])); + cl_assert(x.length == 5); + + git_vector_uniq(&x, NULL); + cl_assert(x.length == 2); + + git_vector_free(&x); + + git__free(ptrs[0]); + git__free(ptrs[1]); +} + + +static int compare_them(const void *a, const void *b) +{ + return (int)((intptr_t)a - (intptr_t)b); +} + +/* insert_sorted */ +void test_core_vector__3(void) +{ + git_vector x; + intptr_t i; + cl_git_pass(git_vector_init(&x, 1, &compare_them)); + + for (i = 0; i < 10; i += 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 9; i > 0; i -= 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + cl_assert(x.length == 10); + for (i = 0; i < 10; ++i) { + cl_assert(git_vector_get(&x, i) == (void*)(i + 1)); + } + + git_vector_free(&x); +} + +/* insert_sorted with duplicates */ +void test_core_vector__4(void) +{ + git_vector x; + intptr_t i; + cl_git_pass(git_vector_init(&x, 1, &compare_them)); + + for (i = 0; i < 10; i += 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 9; i > 0; i -= 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 0; i < 10; i += 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 9; i > 0; i -= 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + cl_assert(x.length == 20); + for (i = 0; i < 20; ++i) { + cl_assert(git_vector_get(&x, i) == (void*)(i / 2 + 1)); + } + + git_vector_free(&x); +} + +typedef struct { + int content; + int count; +} my_struct; + +static int _struct_count = 0; + +static int compare_structs(const void *a, const void *b) +{ + return ((const my_struct *)a)->content - + ((const my_struct *)b)->content; +} + +static int merge_structs(void **old_raw, void *new) +{ + my_struct *old = *(my_struct **)old_raw; + cl_assert(((my_struct *)old)->content == ((my_struct *)new)->content); + ((my_struct *)old)->count += 1; + git__free(new); + _struct_count--; + return GIT_EEXISTS; +} + +static my_struct *alloc_struct(int value) +{ + my_struct *st = git__malloc(sizeof(my_struct)); + st->content = value; + st->count = 0; + _struct_count++; + return st; +} + +/* insert_sorted with duplicates and special handling */ +void test_core_vector__5(void) +{ + git_vector x; + int i; + + cl_git_pass(git_vector_init(&x, 1, &compare_structs)); + + for (i = 0; i < 10; i += 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + for (i = 9; i > 0; i -= 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + cl_assert(x.length == 10); + cl_assert(_struct_count == 10); + + for (i = 0; i < 10; i += 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + for (i = 9; i > 0; i -= 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + cl_assert(x.length == 10); + cl_assert(_struct_count == 10); + + for (i = 0; i < 10; ++i) { + cl_assert(((my_struct *)git_vector_get(&x, i))->content == i); + git__free(git_vector_get(&x, i)); + _struct_count--; + } + + git_vector_free(&x); +} + +static int remove_ones(const git_vector *v, size_t idx, void *p) +{ + GIT_UNUSED(p); + return (git_vector_get(v, idx) == (void *)0x001); +} + +/* Test removal based on callback */ +void test_core_vector__remove_matching(void) +{ + git_vector x; + size_t i; + void *compare; + + cl_git_pass(git_vector_init(&x, 1, NULL)); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 1); + git_vector_remove_matching(&x, remove_ones, NULL); + cl_assert(x.length == 0); + + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 3); + git_vector_remove_matching(&x, remove_ones, NULL); + cl_assert(x.length == 0); + + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones, NULL); + cl_assert(x.length == 2); + + git_vector_foreach(&x, i, compare) { + cl_assert(compare != (void *)0x001); + } + + git_vector_clear(&x); + + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones, NULL); + cl_assert(x.length == 2); + + git_vector_foreach(&x, i, compare) { + cl_assert(compare != (void *)0x001); + } + + git_vector_clear(&x); + + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones, NULL); + cl_assert(x.length == 2); + + git_vector_foreach(&x, i, compare) { + cl_assert(compare != (void *)0x001); + } + + git_vector_clear(&x); + + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x003); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x003); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones, NULL); + cl_assert(x.length == 4); + + git_vector_free(&x); +} + +static void assert_vector(git_vector *x, void *expected[], size_t len) +{ + size_t i; + + cl_assert_equal_i(len, x->length); + + for (i = 0; i < len; i++) + cl_assert(expected[i] == x->contents[i]); +} + +void test_core_vector__grow_and_shrink(void) +{ + git_vector x = GIT_VECTOR_INIT; + void *expected1[] = { + (void *)0x02, (void *)0x03, (void *)0x04, (void *)0x05, + (void *)0x06, (void *)0x07, (void *)0x08, (void *)0x09, + (void *)0x0a + }; + void *expected2[] = { + (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06, + (void *)0x07, (void *)0x08, (void *)0x09, (void *)0x0a + }; + void *expected3[] = { + (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06, + (void *)0x0a + }; + void *expected4[] = { + (void *)0x02, (void *)0x04, (void *)0x05 + }; + void *expected5[] = { + (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, + (void *)0x05 + }; + void *expected6[] = { + (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, + (void *)0x05, (void *)0x00 + }; + void *expected7[] = { + (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, + (void *)0x00, (void *)0x00, (void *)0x00, (void *)0x05, + (void *)0x00 + }; + void *expected8[] = { + (void *)0x04, (void *)0x00, (void *)0x00, (void *)0x00, + (void *)0x05, (void *)0x00 + }; + void *expected9[] = { + (void *)0x04, (void *)0x00, (void *)0x05, (void *)0x00 + }; + void *expectedA[] = { (void *)0x04, (void *)0x00 }; + void *expectedB[] = { (void *)0x04 }; + + git_vector_insert(&x, (void *)0x01); + git_vector_insert(&x, (void *)0x02); + git_vector_insert(&x, (void *)0x03); + git_vector_insert(&x, (void *)0x04); + git_vector_insert(&x, (void *)0x05); + git_vector_insert(&x, (void *)0x06); + git_vector_insert(&x, (void *)0x07); + git_vector_insert(&x, (void *)0x08); + git_vector_insert(&x, (void *)0x09); + git_vector_insert(&x, (void *)0x0a); + + git_vector_remove_range(&x, 0, 1); + assert_vector(&x, expected1, ARRAY_SIZE(expected1)); + + git_vector_remove_range(&x, 1, 1); + assert_vector(&x, expected2, ARRAY_SIZE(expected2)); + + git_vector_remove_range(&x, 4, 3); + assert_vector(&x, expected3, ARRAY_SIZE(expected3)); + + git_vector_remove_range(&x, 3, 2); + assert_vector(&x, expected4, ARRAY_SIZE(expected4)); + + git_vector_insert_null(&x, 0, 2); + assert_vector(&x, expected5, ARRAY_SIZE(expected5)); + + git_vector_insert_null(&x, 5, 1); + assert_vector(&x, expected6, ARRAY_SIZE(expected6)); + + git_vector_insert_null(&x, 4, 3); + assert_vector(&x, expected7, ARRAY_SIZE(expected7)); + + git_vector_remove_range(&x, 0, 3); + assert_vector(&x, expected8, ARRAY_SIZE(expected8)); + + git_vector_remove_range(&x, 1, 2); + assert_vector(&x, expected9, ARRAY_SIZE(expected9)); + + git_vector_remove_range(&x, 2, 2); + assert_vector(&x, expectedA, ARRAY_SIZE(expectedA)); + + git_vector_remove_range(&x, 1, 1); + assert_vector(&x, expectedB, ARRAY_SIZE(expectedB)); + + git_vector_remove_range(&x, 0, 1); + assert_vector(&x, NULL, 0); + + git_vector_free(&x); +} + +void test_core_vector__reverse(void) +{ + git_vector v = GIT_VECTOR_INIT; + size_t i; + + void *in1[] = {(void *) 0x0, (void *) 0x1, (void *) 0x2, (void *) 0x3}; + void *out1[] = {(void *) 0x3, (void *) 0x2, (void *) 0x1, (void *) 0x0}; + + void *in2[] = {(void *) 0x0, (void *) 0x1, (void *) 0x2, (void *) 0x3, (void *) 0x4}; + void *out2[] = {(void *) 0x4, (void *) 0x3, (void *) 0x2, (void *) 0x1, (void *) 0x0}; + + for (i = 0; i < 4; i++) + cl_git_pass(git_vector_insert(&v, in1[i])); + + git_vector_reverse(&v); + + for (i = 0; i < 4; i++) + cl_assert_equal_p(out1[i], git_vector_get(&v, i)); + + git_vector_clear(&v); + for (i = 0; i < 5; i++) + cl_git_pass(git_vector_insert(&v, in2[i])); + + git_vector_reverse(&v); + + for (i = 0; i < 5; i++) + cl_assert_equal_p(out2[i], git_vector_get(&v, i)); + + git_vector_free(&v); +} + +void test_core_vector__dup_empty_vector(void) +{ + git_vector v = GIT_VECTOR_INIT; + git_vector dup = GIT_VECTOR_INIT; + int dummy; + + cl_assert_equal_i(0, v.length); + + cl_git_pass(git_vector_dup(&dup, &v, v._cmp)); + cl_assert_equal_i(0, dup._alloc_size); + cl_assert_equal_i(0, dup.length); + + cl_git_pass(git_vector_insert(&dup, &dummy)); + cl_assert_equal_i(8, dup._alloc_size); + cl_assert_equal_i(1, dup.length); + + git_vector_free(&dup); +} diff --git a/tests/libgit2/core/wildmatch.c b/tests/libgit2/core/wildmatch.c new file mode 100644 index 000000000..7c56ee7b8 --- /dev/null +++ b/tests/libgit2/core/wildmatch.c @@ -0,0 +1,248 @@ +#include "clar_libgit2.h" + +#include "wildmatch.h" + +#define assert_matches(string, pattern, wildmatch, iwildmatch, pathmatch, ipathmatch) \ + assert_matches_(string, pattern, wildmatch, iwildmatch, pathmatch, ipathmatch, __FILE__, __func__, __LINE__) + +static void assert_matches_(const char *string, const char *pattern, + char expected_wildmatch, char expected_iwildmatch, + char expected_pathmatch, char expected_ipathmatch, + const char *file, const char *func, size_t line) +{ + if (wildmatch(pattern, string, WM_PATHNAME) == expected_wildmatch) + clar__fail(file, func, line, "Test failed (wildmatch).", string, 1); + if (wildmatch(pattern, string, WM_PATHNAME|WM_CASEFOLD) == expected_iwildmatch) + clar__fail(file, func, line, "Test failed (iwildmatch).", string, 1); + if (wildmatch(pattern, string, 0) == expected_pathmatch) + clar__fail(file, func, line, "Test failed (pathmatch).", string, 1); + if (wildmatch(pattern, string, WM_CASEFOLD) == expected_ipathmatch) + clar__fail(file, func, line, "Test failed (ipathmatch).", string, 1); +} + +/* + * Below testcases are imported from git.git, t3070-wildmatch,sh at tag v2.22.0. + * Note that we've only imported the direct wildcard tests, but not the matching + * tests for git-ls-files. + */ + +void test_core_wildmatch__basic_wildmatch(void) +{ + assert_matches("foo", "foo", 1, 1, 1, 1); + assert_matches("foo", "bar", 0, 0, 0, 0); + assert_matches("", "", 1, 1, 1, 1); + assert_matches("foo", "???", 1, 1, 1, 1); + assert_matches("foo", "??", 0, 0, 0, 0); + assert_matches("foo", "*", 1, 1, 1, 1); + assert_matches("foo", "f*", 1, 1, 1, 1); + assert_matches("foo", "*f", 0, 0, 0, 0); + assert_matches("foo", "*foo*", 1, 1, 1, 1); + assert_matches("foobar", "*ob*a*r*", 1, 1, 1, 1); + assert_matches("aaaaaaabababab", "*ab", 1, 1, 1, 1); + assert_matches("foo*", "foo\\*", 1, 1, 1, 1); + assert_matches("foobar", "foo\\*bar", 0, 0, 0, 0); + assert_matches("f\\oo", "f\\\\oo", 1, 1, 1, 1); + assert_matches("ball", "*[al]?", 1, 1, 1, 1); + assert_matches("ten", "[ten]", 0, 0, 0, 0); + assert_matches("ten", "**[!te]", 1, 1, 1, 1); + assert_matches("ten", "**[!ten]", 0, 0, 0, 0); + assert_matches("ten", "t[a-g]n", 1, 1, 1, 1); + assert_matches("ten", "t[!a-g]n", 0, 0, 0, 0); + assert_matches("ton", "t[!a-g]n", 1, 1, 1, 1); + assert_matches("ton", "t[^a-g]n", 1, 1, 1, 1); + assert_matches("a]b", "a[]]b", 1, 1, 1, 1); + assert_matches("a-b", "a[]-]b", 1, 1, 1, 1); + assert_matches("a]b", "a[]-]b", 1, 1, 1, 1); + assert_matches("aab", "a[]-]b", 0, 0, 0, 0); + assert_matches("aab", "a[]a-]b", 1, 1, 1, 1); + assert_matches("]", "]", 1, 1, 1, 1); +} + +void test_core_wildmatch__slash_matching_features(void) +{ + assert_matches("foo/baz/bar", "foo*bar", 0, 0, 1, 1); + assert_matches("foo/baz/bar", "foo**bar", 0, 0, 1, 1); + assert_matches("foobazbar", "foo**bar", 1, 1, 1, 1); + assert_matches("foo/baz/bar", "foo/**/bar", 1, 1, 1, 1); + assert_matches("foo/baz/bar", "foo/**/**/bar", 1, 1, 0, 0); + assert_matches("foo/b/a/z/bar", "foo/**/bar", 1, 1, 1, 1); + assert_matches("foo/b/a/z/bar", "foo/**/**/bar", 1, 1, 1, 1); + assert_matches("foo/bar", "foo/**/bar", 1, 1, 0, 0); + assert_matches("foo/bar", "foo/**/**/bar", 1, 1, 0, 0); + assert_matches("foo/bar", "foo?bar", 0, 0, 1, 1); + assert_matches("foo/bar", "foo[/]bar", 0, 0, 1, 1); + assert_matches("foo/bar", "foo[^a-z]bar", 0, 0, 1, 1); + assert_matches("foo/bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r", 0, 0, 1, 1); + assert_matches("foo-bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r", 1, 1, 1, 1); + assert_matches("foo", "**/foo", 1, 1, 0, 0); + assert_matches("XXX/foo", "**/foo", 1, 1, 1, 1); + assert_matches("bar/baz/foo", "**/foo", 1, 1, 1, 1); + assert_matches("bar/baz/foo", "*/foo", 0, 0, 1, 1); + assert_matches("foo/bar/baz", "**/bar*", 0, 0, 1, 1); + assert_matches("deep/foo/bar/baz", "**/bar/*", 1, 1, 1, 1); + assert_matches("deep/foo/bar/baz/", "**/bar/*", 0, 0, 1, 1); + assert_matches("deep/foo/bar/baz/", "**/bar/**", 1, 1, 1, 1); + assert_matches("deep/foo/bar", "**/bar/*", 0, 0, 0, 0); + assert_matches("deep/foo/bar/", "**/bar/**", 1, 1, 1, 1); + assert_matches("foo/bar/baz", "**/bar**", 0, 0, 1, 1); + assert_matches("foo/bar/baz/x", "*/bar/**", 1, 1, 1, 1); + assert_matches("deep/foo/bar/baz/x", "*/bar/**", 0, 0, 1, 1); + assert_matches("deep/foo/bar/baz/x", "**/bar/*/*", 1, 1, 1, 1); +} + +void test_core_wildmatch__various_additional(void) +{ + assert_matches("acrt", "a[c-c]st", 0, 0, 0, 0); + assert_matches("acrt", "a[c-c]rt", 1, 1, 1, 1); + assert_matches("]", "[!]-]", 0, 0, 0, 0); + assert_matches("a", "[!]-]", 1, 1, 1, 1); + assert_matches("", "\\", 0, 0, 0, 0); + assert_matches("\\", "\\", 0, 0, 0, 0); + assert_matches("XXX/\\", "*/\\", 0, 0, 0, 0); + assert_matches("XXX/\\", "*/\\\\", 1, 1, 1, 1); + assert_matches("foo", "foo", 1, 1, 1, 1); + assert_matches("@foo", "@foo", 1, 1, 1, 1); + assert_matches("foo", "@foo", 0, 0, 0, 0); + assert_matches("[ab]", "\\[ab]", 1, 1, 1, 1); + assert_matches("[ab]", "[[]ab]", 1, 1, 1, 1); + assert_matches("[ab]", "[[:]ab]", 1, 1, 1, 1); + assert_matches("[ab]", "[[::]ab]", 0, 0, 0, 0); + assert_matches("[ab]", "[[:digit]ab]", 1, 1, 1, 1); + assert_matches("[ab]", "[\\[:]ab]", 1, 1, 1, 1); + assert_matches("?a?b", "\\??\\?b", 1, 1, 1, 1); + assert_matches("abc", "\\a\\b\\c", 1, 1, 1, 1); + assert_matches("foo", "", 0, 0, 0, 0); + assert_matches("foo/bar/baz/to", "**/t[o]", 1, 1, 1, 1); +} + +void test_core_wildmatch__character_classes(void) +{ + assert_matches("a1B", "[[:alpha:]][[:digit:]][[:upper:]]", 1, 1, 1, 1); + assert_matches("a", "[[:digit:][:upper:][:space:]]", 0, 1, 0, 1); + assert_matches("A", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); + assert_matches("1", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); + assert_matches("1", "[[:digit:][:upper:][:spaci:]]", 0, 0, 0, 0); + assert_matches(" ", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); + assert_matches(".", "[[:digit:][:upper:][:space:]]", 0, 0, 0, 0); + assert_matches(".", "[[:digit:][:punct:][:space:]]", 1, 1, 1, 1); + assert_matches("5", "[[:xdigit:]]", 1, 1, 1, 1); + assert_matches("f", "[[:xdigit:]]", 1, 1, 1, 1); + assert_matches("D", "[[:xdigit:]]", 1, 1, 1, 1); + assert_matches("_", "[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]", 1, 1, 1, 1); + assert_matches(".", "[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]", 1, 1, 1, 1); + assert_matches("5", "[a-c[:digit:]x-z]", 1, 1, 1, 1); + assert_matches("b", "[a-c[:digit:]x-z]", 1, 1, 1, 1); + assert_matches("y", "[a-c[:digit:]x-z]", 1, 1, 1, 1); + assert_matches("q", "[a-c[:digit:]x-z]", 0, 0, 0, 0); +} + +void test_core_wildmatch__additional_with_malformed(void) +{ + assert_matches("]", "[\\\\-^]", 1, 1, 1, 1); + assert_matches("[", "[\\\\-^]", 0, 0, 0, 0); + assert_matches("-", "[\\-_]", 1, 1, 1, 1); + assert_matches("]", "[\\]]", 1, 1, 1, 1); + assert_matches("\\]", "[\\]]", 0, 0, 0, 0); + assert_matches("\\", "[\\]]", 0, 0, 0, 0); + assert_matches("ab", "a[]b", 0, 0, 0, 0); + assert_matches("a[]b", "a[]b", 0, 0, 0, 0); + assert_matches("ab[", "ab[", 0, 0, 0, 0); + assert_matches("ab", "[!", 0, 0, 0, 0); + assert_matches("ab", "[-", 0, 0, 0, 0); + assert_matches("-", "[-]", 1, 1, 1, 1); + assert_matches("-", "[a-", 0, 0, 0, 0); + assert_matches("-", "[!a-", 0, 0, 0, 0); + assert_matches("-", "[--A]", 1, 1, 1, 1); + assert_matches("5", "[--A]", 1, 1, 1, 1); + assert_matches(" ", "[ --]", 1, 1, 1, 1); + assert_matches("$", "[ --]", 1, 1, 1, 1); + assert_matches("-", "[ --]", 1, 1, 1, 1); + assert_matches("0", "[ --]", 0, 0, 0, 0); + assert_matches("-", "[---]", 1, 1, 1, 1); + assert_matches("-", "[------]", 1, 1, 1, 1); + assert_matches("j", "[a-e-n]", 0, 0, 0, 0); + assert_matches("-", "[a-e-n]", 1, 1, 1, 1); + assert_matches("a", "[!------]", 1, 1, 1, 1); + assert_matches("[", "[]-a]", 0, 0, 0, 0); + assert_matches("^", "[]-a]", 1, 1, 1, 1); + assert_matches("^", "[!]-a]", 0, 0, 0, 0); + assert_matches("[", "[!]-a]", 1, 1, 1, 1); + assert_matches("^", "[a^bc]", 1, 1, 1, 1); + assert_matches("-b]", "[a-]b]", 1, 1, 1, 1); + assert_matches("\\", "[\\]", 0, 0, 0, 0); + assert_matches("\\", "[\\\\]", 1, 1, 1, 1); + assert_matches("\\", "[!\\\\]", 0, 0, 0, 0); + assert_matches("G", "[A-\\\\]", 1, 1, 1, 1); + assert_matches("aaabbb", "b*a", 0, 0, 0, 0); + assert_matches("aabcaa", "*ba*", 0, 0, 0, 0); + assert_matches(",", "[,]", 1, 1, 1, 1); + assert_matches(",", "[\\\\,]", 1, 1, 1, 1); + assert_matches("\\", "[\\\\,]", 1, 1, 1, 1); + assert_matches("-", "[,-.]", 1, 1, 1, 1); + assert_matches("+", "[,-.]", 0, 0, 0, 0); + assert_matches("-.]", "[,-.]", 0, 0, 0, 0); + assert_matches("2", "[\\1-\\3]", 1, 1, 1, 1); + assert_matches("3", "[\\1-\\3]", 1, 1, 1, 1); + assert_matches("4", "[\\1-\\3]", 0, 0, 0, 0); + assert_matches("\\", "[[-\\]]", 1, 1, 1, 1); + assert_matches("[", "[[-\\]]", 1, 1, 1, 1); + assert_matches("]", "[[-\\]]", 1, 1, 1, 1); + assert_matches("-", "[[-\\]]", 0, 0, 0, 0); +} + +void test_core_wildmatch__recursion(void) +{ + assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 1, 1, 1, 1); + assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 0, 0, 0, 0); + assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 0, 0, 0, 0); + assert_matches("XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*", 1, 1, 1, 1); + assert_matches("XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*", 0, 0, 0, 0); + assert_matches("abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt", "**/*a*b*g*n*t", 1, 1, 1, 1); + assert_matches("abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz", "**/*a*b*g*n*t", 0, 0, 0, 0); + assert_matches("foo", "*/*/*", 0, 0, 0, 0); + assert_matches("foo/bar", "*/*/*", 0, 0, 0, 0); + assert_matches("foo/bba/arr", "*/*/*", 1, 1, 1, 1); + assert_matches("foo/bb/aa/rr", "*/*/*", 0, 0, 1, 1); + assert_matches("foo/bb/aa/rr", "**/**/**", 1, 1, 1, 1); + assert_matches("abcXdefXghi", "*X*i", 1, 1, 1, 1); + assert_matches("ab/cXd/efXg/hi", "*X*i", 0, 0, 1, 1); + assert_matches("ab/cXd/efXg/hi", "*/*X*/*/*i", 1, 1, 1, 1); + assert_matches("ab/cXd/efXg/hi", "**/*X*/**/*i", 1, 1, 1, 1); +} + +void test_core_wildmatch__pathmatch(void) +{ + assert_matches("foo", "fo", 0, 0, 0, 0); + assert_matches("foo/bar", "foo/bar", 1, 1, 1, 1); + assert_matches("foo/bar", "foo/*", 1, 1, 1, 1); + assert_matches("foo/bba/arr", "foo/*", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo/**", 1, 1, 1, 1); + assert_matches("foo/bba/arr", "foo*", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo**", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo/*arr", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo/**arr", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo/*z", 0, 0, 0, 0); + assert_matches("foo/bba/arr", "foo/**z", 0, 0, 0, 0); + assert_matches("foo/bar", "foo?bar", 0, 0, 1, 1); + assert_matches("foo/bar", "foo[/]bar", 0, 0, 1, 1); + assert_matches("foo/bar", "foo[^a-z]bar", 0, 0, 1, 1); + assert_matches("ab/cXd/efXg/hi", "*Xg*i", 0, 0, 1, 1); +} + +void test_core_wildmatch__case_sensitivity(void) +{ + assert_matches("a", "[A-Z]", 0, 1, 0, 1); + assert_matches("A", "[A-Z]", 1, 1, 1, 1); + assert_matches("A", "[a-z]", 0, 1, 0, 1); + assert_matches("a", "[a-z]", 1, 1, 1, 1); + assert_matches("a", "[[:upper:]]", 0, 1, 0, 1); + assert_matches("A", "[[:upper:]]", 1, 1, 1, 1); + assert_matches("A", "[[:lower:]]", 0, 1, 0, 1); + assert_matches("a", "[[:lower:]]", 1, 1, 1, 1); + assert_matches("A", "[B-Za]", 0, 1, 0, 1); + assert_matches("a", "[B-Za]", 1, 1, 1, 1); + assert_matches("A", "[B-a]", 0, 1, 0, 1); + assert_matches("a", "[B-a]", 1, 1, 1, 1); + assert_matches("z", "[Z-y]", 0, 1, 0, 1); + assert_matches("Z", "[Z-y]", 1, 1, 1, 1); +} diff --git a/tests/libgit2/core/zstream.c b/tests/libgit2/core/zstream.c new file mode 100644 index 000000000..c22e81008 --- /dev/null +++ b/tests/libgit2/core/zstream.c @@ -0,0 +1,167 @@ +#include "clar_libgit2.h" +#include "zstream.h" + +static const char *data = "This is a test test test of This is a test"; + +#define INFLATE_EXTRA 2 + +static void assert_zlib_equal_( + const void *expected, size_t e_len, + const void *compressed, size_t c_len, + const char *msg, const char *file, const char *func, int line) +{ + z_stream stream; + char *expanded = git__calloc(1, e_len + INFLATE_EXTRA); + cl_assert(expanded); + + memset(&stream, 0, sizeof(stream)); + stream.next_out = (Bytef *)expanded; + stream.avail_out = (uInt)(e_len + INFLATE_EXTRA); + stream.next_in = (Bytef *)compressed; + stream.avail_in = (uInt)c_len; + + cl_assert(inflateInit(&stream) == Z_OK); + cl_assert(inflate(&stream, Z_FINISH)); + inflateEnd(&stream); + + clar__assert_equal( + file, func, line, msg, 1, + "%d", (int)stream.total_out, (int)e_len); + clar__assert_equal( + file, func, line, "Buffer len was not exact match", 1, + "%d", (int)stream.avail_out, (int)INFLATE_EXTRA); + + clar__assert( + memcmp(expanded, expected, e_len) == 0, + file, func, line, "uncompressed data did not match", NULL, 1); + + git__free(expanded); +} + +#define assert_zlib_equal(E,EL,C,CL) \ + assert_zlib_equal_(E, EL, C, CL, #EL " != " #CL, __FILE__, __func__, (int)__LINE__) + +void test_core_zstream__basic(void) +{ + git_zstream z = GIT_ZSTREAM_INIT; + char out[128]; + size_t outlen = sizeof(out); + + cl_git_pass(git_zstream_init(&z, GIT_ZSTREAM_DEFLATE)); + cl_git_pass(git_zstream_set_input(&z, data, strlen(data) + 1)); + cl_git_pass(git_zstream_get_output(out, &outlen, &z)); + cl_assert(git_zstream_done(&z)); + cl_assert(outlen > 0); + git_zstream_free(&z); + + assert_zlib_equal(data, strlen(data) + 1, out, outlen); +} + +void test_core_zstream__fails_on_trailing_garbage(void) +{ + git_str deflated = GIT_STR_INIT, inflated = GIT_STR_INIT; + char i = 0; + + /* compress a simple string */ + git_zstream_deflatebuf(&deflated, "foobar!!", 8); + + /* append some garbage */ + for (i = 0; i < 10; i++) { + git_str_putc(&deflated, i); + } + + cl_git_fail(git_zstream_inflatebuf(&inflated, deflated.ptr, deflated.size)); + + git_str_dispose(&deflated); + git_str_dispose(&inflated); +} + +void test_core_zstream__buffer(void) +{ + git_str out = GIT_STR_INIT; + cl_git_pass(git_zstream_deflatebuf(&out, data, strlen(data) + 1)); + assert_zlib_equal(data, strlen(data) + 1, out.ptr, out.size); + git_str_dispose(&out); +} + +#define BIG_STRING_PART "Big Data IS Big - Long Data IS Long - We need a buffer larger than 1024 x 1024 to make sure we trigger chunked compression - Big Big Data IS Bigger than Big - Long Long Data IS Longer than Long" + +static void compress_and_decompress_input_various_ways(git_str *input) +{ + git_str out1 = GIT_STR_INIT, out2 = GIT_STR_INIT; + git_str inflated = GIT_STR_INIT; + size_t i, fixed_size = max(input->size / 2, 256); + char *fixed = git__malloc(fixed_size); + cl_assert(fixed); + + /* compress with deflatebuf */ + + cl_git_pass(git_zstream_deflatebuf(&out1, input->ptr, input->size)); + assert_zlib_equal(input->ptr, input->size, out1.ptr, out1.size); + + /* compress with various fixed size buffer (accumulating the output) */ + + for (i = 0; i < 3; ++i) { + git_zstream zs = GIT_ZSTREAM_INIT; + size_t use_fixed_size; + + switch (i) { + case 0: use_fixed_size = 256; break; + case 1: use_fixed_size = fixed_size / 2; break; + case 2: use_fixed_size = fixed_size; break; + } + cl_assert(use_fixed_size <= fixed_size); + + cl_git_pass(git_zstream_init(&zs, GIT_ZSTREAM_DEFLATE)); + cl_git_pass(git_zstream_set_input(&zs, input->ptr, input->size)); + + while (!git_zstream_done(&zs)) { + size_t written = use_fixed_size; + cl_git_pass(git_zstream_get_output(fixed, &written, &zs)); + cl_git_pass(git_str_put(&out2, fixed, written)); + } + + git_zstream_free(&zs); + assert_zlib_equal(input->ptr, input->size, out2.ptr, out2.size); + + /* did both approaches give the same data? */ + cl_assert_equal_sz(out1.size, out2.size); + cl_assert(!memcmp(out1.ptr, out2.ptr, out1.size)); + + git_str_dispose(&out2); + } + + cl_git_pass(git_zstream_inflatebuf(&inflated, out1.ptr, out1.size)); + cl_assert_equal_i(input->size, inflated.size); + cl_assert(memcmp(input->ptr, inflated.ptr, inflated.size) == 0); + + git_str_dispose(&out1); + git_str_dispose(&inflated); + git__free(fixed); +} + +void test_core_zstream__big_data(void) +{ + git_str in = GIT_STR_INIT; + size_t scan, target; + + for (target = 1024; target <= 1024 * 1024 * 4; target *= 8) { + + /* make a big string that's easy to compress */ + git_str_clear(&in); + while (in.size < target) + cl_git_pass( + git_str_put(&in, BIG_STRING_PART, strlen(BIG_STRING_PART))); + + compress_and_decompress_input_various_ways(&in); + + /* make a big string that's hard to compress */ + srand(0xabad1dea); + for (scan = 0; scan < in.size; ++scan) + in.ptr[scan] = (char)rand(); + + compress_and_decompress_input_various_ways(&in); + } + + git_str_dispose(&in); +} diff --git a/tests/libgit2/date/date.c b/tests/libgit2/date/date.c new file mode 100644 index 000000000..82b5c6728 --- /dev/null +++ b/tests/libgit2/date/date.c @@ -0,0 +1,22 @@ +#include "clar_libgit2.h" + +#include "date.h" + +void test_date_date__overflow(void) +{ +#ifdef __LP64__ + git_time_t d2038, d2039; + + /* This is expected to fail on a 32-bit machine. */ + cl_git_pass(git_date_parse(&d2038, "2038-1-1")); + cl_git_pass(git_date_parse(&d2039, "2039-1-1")); + cl_assert(d2038 < d2039); +#endif +} + +void test_date_date__invalid_date(void) +{ + git_time_t d; + cl_git_fail(git_date_parse(&d, "")); + cl_git_fail(git_date_parse(&d, "NEITHER_INTEGER_NOR_DATETIME")); +} diff --git a/tests/libgit2/date/rfc2822.c b/tests/libgit2/date/rfc2822.c new file mode 100644 index 000000000..b0bbcfce5 --- /dev/null +++ b/tests/libgit2/date/rfc2822.c @@ -0,0 +1,37 @@ +#include "clar_libgit2.h" + +#include "date.h" + +void test_date_rfc2822__format_rfc2822_no_offset(void) +{ + git_time t = {1397031663, 0}; + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_date_rfc2822_fmt(&buf, t.time, t.offset)); + cl_assert_equal_s("Wed, 9 Apr 2014 08:21:03 +0000", buf.ptr); + + git_str_dispose(&buf); +} + +void test_date_rfc2822__format_rfc2822_positive_offset(void) +{ + git_time t = {1397031663, 120}; + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_date_rfc2822_fmt(&buf, t.time, t.offset)); + cl_assert_equal_s("Wed, 9 Apr 2014 10:21:03 +0200", buf.ptr); + + git_str_dispose(&buf); +} + +void test_date_rfc2822__format_rfc2822_negative_offset(void) +{ + git_time t = {1397031663, -120}; + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_date_rfc2822_fmt(&buf, t.time, t.offset)); + cl_assert_equal_s("Wed, 9 Apr 2014 06:21:03 -0200", buf.ptr); + + git_str_dispose(&buf); +} + diff --git a/tests/libgit2/delta/apply.c b/tests/libgit2/delta/apply.c new file mode 100644 index 000000000..5bb95a283 --- /dev/null +++ b/tests/libgit2/delta/apply.c @@ -0,0 +1,21 @@ +#include "clar_libgit2.h" + +#include "delta.h" + +void test_delta_apply__read_at_off(void) +{ + unsigned char base[16] = { 0 }, delta[] = { 0x10, 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00 }; + void *out; + size_t outlen; + + cl_git_fail(git_delta_apply(&out, &outlen, base, sizeof(base), delta, sizeof(delta))); +} + +void test_delta_apply__read_after_limit(void) +{ + unsigned char base[16] = { 0 }, delta[] = { 0x10, 0x70, 0xff }; + void *out; + size_t outlen; + + cl_git_fail(git_delta_apply(&out, &outlen, base, sizeof(base), delta, sizeof(delta))); +} diff --git a/tests/libgit2/describe/describe.c b/tests/libgit2/describe/describe.c new file mode 100644 index 000000000..ba67ca46b --- /dev/null +++ b/tests/libgit2/describe/describe.c @@ -0,0 +1,55 @@ +#include "clar_libgit2.h" +#include "describe_helpers.h" + +void test_describe_describe__can_describe_against_a_bare_repo(void) +{ + git_repository *repo; + git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; + git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + assert_describe("hard_tag", "HEAD", repo, &opts, &fmt_opts); + + opts.show_commit_oid_as_fallback = 1; + + assert_describe("be3563a*", "HEAD^", repo, &opts, &fmt_opts); + + git_repository_free(repo); +} + +static int delete_cb(git_reference *ref, void *payload) +{ + GIT_UNUSED(payload); + + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + return 0; +} + +void test_describe_describe__describe_a_repo_with_no_refs(void) +{ + git_repository *repo; + git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; + git_str buf = GIT_STR_INIT; + git_object *object; + git_describe_result *result = NULL; + + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_revparse_single(&object, repo, "HEAD")); + + cl_git_pass(git_reference_foreach(repo, delete_cb, NULL)); + + /* Impossible to describe without falling back to OIDs */ + cl_git_fail(git_describe_commit(&result, object, &opts)); + + /* Try again with OID fallbacks */ + opts.show_commit_oid_as_fallback = 1; + cl_git_pass(git_describe_commit(&result, object, &opts)); + + git_describe_result_free(result); + git_object_free(object); + git_str_dispose(&buf); + cl_git_sandbox_cleanup(); +} diff --git a/tests/libgit2/describe/describe_helpers.c b/tests/libgit2/describe/describe_helpers.c new file mode 100644 index 000000000..3df3b7c59 --- /dev/null +++ b/tests/libgit2/describe/describe_helpers.c @@ -0,0 +1,44 @@ +#include "describe_helpers.h" + +#include "wildmatch.h" + +void assert_describe( + const char *expected_output, + const char *revparse_spec, + git_repository *repo, + git_describe_options *opts, + git_describe_format_options *fmt_opts) +{ + git_object *object; + git_buf label = GIT_BUF_INIT; + git_describe_result *result; + + cl_git_pass(git_revparse_single(&object, repo, revparse_spec)); + + cl_git_pass(git_describe_commit(&result, object, opts)); + cl_git_pass(git_describe_format(&label, result, fmt_opts)); + + cl_must_pass(wildmatch(expected_output, label.ptr, 0)); + + git_describe_result_free(result); + git_object_free(object); + git_buf_dispose(&label); +} + +void assert_describe_workdir( + const char *expected_output, + git_repository *repo, + git_describe_options *opts, + git_describe_format_options *fmt_opts) +{ + git_buf label = GIT_BUF_INIT; + git_describe_result *result; + + cl_git_pass(git_describe_workdir(&result, repo, opts)); + cl_git_pass(git_describe_format(&label, result, fmt_opts)); + + cl_must_pass(wildmatch(expected_output, label.ptr, 0)); + + git_describe_result_free(result); + git_buf_dispose(&label); +} diff --git a/tests/libgit2/describe/describe_helpers.h b/tests/libgit2/describe/describe_helpers.h new file mode 100644 index 000000000..43e8c5e19 --- /dev/null +++ b/tests/libgit2/describe/describe_helpers.h @@ -0,0 +1,14 @@ +#include "clar_libgit2.h" + +extern void assert_describe( + const char *expected_output, + const char *revparse_spec, + git_repository *repo, + git_describe_options *opts, + git_describe_format_options *fmt_opts); + +extern void assert_describe_workdir( + const char *expected_output, + git_repository *repo, + git_describe_options *opts, + git_describe_format_options *fmt_opts); diff --git a/tests/libgit2/describe/t6120.c b/tests/libgit2/describe/t6120.c new file mode 100644 index 000000000..5ec176b87 --- /dev/null +++ b/tests/libgit2/describe/t6120.c @@ -0,0 +1,156 @@ +#include "clar_libgit2.h" +#include "describe_helpers.h" +#include "repository.h" + +/* Ported from https://github.com/git/git/blob/adfc1857bdb090786fd9d22c1acec39371c76048/t/t6120-describe.sh */ + +static git_repository *repo; + +void test_describe_t6120__initialize(void) +{ + repo = cl_git_sandbox_init("describe"); +} + +void test_describe_t6120__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_describe_t6120__default(void) +{ + git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; + git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; + + assert_describe("A-*", "HEAD", repo, &opts, &fmt_opts); + assert_describe("A-*", "HEAD^", repo, &opts, &fmt_opts); + assert_describe("R-*", "HEAD^^", repo, &opts, &fmt_opts); + assert_describe("A-*", "HEAD^^2", repo, &opts, &fmt_opts); + assert_describe("B", "HEAD^^2^", repo, &opts, &fmt_opts); + assert_describe("R-*", "HEAD^^^", repo, &opts, &fmt_opts); +} + +void test_describe_t6120__tags(void) +{ + git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; + git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; + opts.describe_strategy = GIT_DESCRIBE_TAGS; + + assert_describe("c-*", "HEAD", repo, &opts, &fmt_opts); + assert_describe("c-*", "HEAD^", repo, &opts, &fmt_opts); + assert_describe("e-*", "HEAD^^", repo, &opts, &fmt_opts); + assert_describe("c-*", "HEAD^^2", repo, &opts, &fmt_opts); + assert_describe("B", "HEAD^^2^", repo, &opts, &fmt_opts); + assert_describe("e", "HEAD^^^", repo, &opts, &fmt_opts); +} + +void test_describe_t6120__all(void) +{ + git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; + git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; + opts.describe_strategy = GIT_DESCRIBE_ALL; + + assert_describe("heads/master", "HEAD", repo, &opts, &fmt_opts); + assert_describe("tags/c-*", "HEAD^", repo, &opts, &fmt_opts); + assert_describe("tags/e", "HEAD^^^", repo, &opts, &fmt_opts); +} + +void test_describe_t6120__longformat(void) +{ + git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; + git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; + + fmt_opts.always_use_long_format = 1; + + assert_describe("B-0-*", "HEAD^^2^", repo, &opts, &fmt_opts); + assert_describe("A-3-*", "HEAD^^2", repo, &opts, &fmt_opts); +} + +void test_describe_t6120__firstparent(void) +{ + git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; + git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; + opts.describe_strategy = GIT_DESCRIBE_TAGS; + + assert_describe("c-7-*", "HEAD", repo, &opts, &fmt_opts); + + opts.only_follow_first_parent = 1; + assert_describe("e-3-*", "HEAD", repo, &opts, &fmt_opts); +} + +void test_describe_t6120__workdir(void) +{ + git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; + git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; + + assert_describe_workdir("A-*[0-9a-f]", repo, &opts, &fmt_opts); + cl_git_mkfile("describe/file", "something different"); + + fmt_opts.dirty_suffix = "-dirty"; + assert_describe_workdir("A-*[0-9a-f]-dirty", repo, &opts, &fmt_opts); + fmt_opts.dirty_suffix = ".mod"; + assert_describe_workdir("A-*[0-9a-f].mod", repo, &opts, &fmt_opts); +} + +static void commit_and_tag( + git_time_t *time, + const char *commit_msg, + const char *tag_name) +{ + git_index *index; + git_oid commit_id; + git_reference *ref; + + cl_git_pass(git_repository_index__weakptr(&index, repo)); + + cl_git_append2file("describe/file", "\n"); + + cl_git_pass(git_index_add_bypath(index, "file")); + cl_git_pass(git_index_write(index)); + + *time += 10; + cl_repo_commit_from_index(&commit_id, repo, NULL, *time, commit_msg); + + if (tag_name == NULL) + return; + + cl_git_pass(git_reference_create(&ref, repo, tag_name, &commit_id, 0, NULL)); + git_reference_free(ref); +} + +void test_describe_t6120__pattern(void) +{ + git_describe_options opts = GIT_DESCRIBE_OPTIONS_INIT; + git_describe_format_options fmt_opts = GIT_DESCRIBE_FORMAT_OPTIONS_INIT; + git_oid tag_id; + git_object *head; + git_signature *tagger; + git_time_t time; + + /* set-up matching pattern tests */ + cl_git_pass(git_revparse_single(&head, repo, "HEAD")); + + time = 1380553019; + cl_git_pass(git_signature_new(&tagger, "tagger", "tagger@libgit2.org", time, 0)); + cl_git_pass(git_tag_create(&tag_id, repo, "test-annotated", head, tagger, "test-annotated", 0)); + git_signature_free(tagger); + git_object_free(head); + + commit_and_tag(&time, "one more", "refs/tags/test1-lightweight"); + commit_and_tag(&time, "yet another", "refs/tags/test2-lightweight"); + commit_and_tag(&time, "even more", NULL); + + + /* Exercize */ + opts.pattern = "test-*"; + assert_describe("test-annotated-*", "HEAD", repo, &opts, &fmt_opts); + + opts.describe_strategy = GIT_DESCRIBE_TAGS; + opts.pattern = "test1-*"; + assert_describe("test1-lightweight-*", "HEAD", repo, &opts, &fmt_opts); + + opts.pattern = "test2-*"; + assert_describe("test2-lightweight-*", "HEAD", repo, &opts, &fmt_opts); + + fmt_opts.always_use_long_format = 1; + assert_describe("test2-lightweight-*", "HEAD^", repo, &opts, &fmt_opts); +} diff --git a/tests/libgit2/diff/binary.c b/tests/libgit2/diff/binary.c new file mode 100644 index 000000000..4e71f39c6 --- /dev/null +++ b/tests/libgit2/diff/binary.c @@ -0,0 +1,545 @@ +#include "clar_libgit2.h" + +#include "git2/sys/diff.h" + +#include "delta.h" +#include "filebuf.h" +#include "repository.h" + +static git_repository *repo; + +void test_diff_binary__initialize(void) +{ +} + +void test_diff_binary__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void test_patch( + const char *one, + const char *two, + const git_diff_options *opts, + const char *expected) +{ + git_oid id_one, id_two; + git_index *index = NULL; + git_commit *commit_one, *commit_two = NULL; + git_tree *tree_one, *tree_two; + git_diff *diff; + git_patch *patch; + git_buf actual = GIT_BUF_INIT; + + cl_git_pass(git_oid_fromstr(&id_one, one)); + cl_git_pass(git_commit_lookup(&commit_one, repo, &id_one)); + cl_git_pass(git_commit_tree(&tree_one, commit_one)); + + if (two) { + cl_git_pass(git_oid_fromstr(&id_two, two)); + cl_git_pass(git_commit_lookup(&commit_two, repo, &id_two)); + cl_git_pass(git_commit_tree(&tree_two, commit_two)); + } else { + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_write_tree(&id_two, index)); + cl_git_pass(git_tree_lookup(&tree_two, repo, &id_two)); + } + + cl_git_pass(git_diff_tree_to_tree(&diff, repo, tree_one, tree_two, opts)); + + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&actual, patch)); + + cl_assert_equal_s(expected, actual.ptr); + + git_buf_dispose(&actual); + cl_git_pass(git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, git_diff_print_callback__to_buf, &actual)); + + cl_assert_equal_s(expected, actual.ptr); + + git_buf_dispose(&actual); + git_patch_free(patch); + git_diff_free(diff); + git_tree_free(tree_one); + git_tree_free(tree_two); + git_commit_free(commit_one); + git_commit_free(commit_two); + git_index_free(index); +} + +void test_diff_binary__add_normal(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + const char *expected = + "diff --git a/binary.bin b/binary.bin\n" \ + "new file mode 100644\n" \ + "index 0000000..bd474b2\n" \ + "Binary files /dev/null and b/binary.bin differ\n"; + + repo = cl_git_sandbox_init("diff_format_email"); + test_patch( + "873806f6f27e631eb0b23e4b56bea2bfac14a373", + "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", + &opts, + expected); +} + +void test_diff_binary__add(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + const char *expected = + "diff --git a/binary.bin b/binary.bin\n" \ + "new file mode 100644\n" \ + "index 0000000000000000000000000000000000000000..bd474b2519cc15eab801ff851cc7d50f0dee49a1\n" \ + "GIT binary patch\n" \ + "literal 3\n" \ + "Kc${Nk-~s>u4FC%O\n" + "\n" \ + "literal 0\n" \ + "Hc$@u4FC%O\n" \ + "\n"; + + opts.flags = GIT_DIFF_SHOW_BINARY; + + repo = cl_git_sandbox_init("diff_format_email"); + test_patch( + "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", + "8d7523f6fcb2404257889abe0d96f093d9f524f9", + &opts, + expected); +} + +void test_diff_binary__delete_normal(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + const char *expected = + "diff --git a/binary.bin b/binary.bin\n" \ + "deleted file mode 100644\n" \ + "index bd474b2..0000000\n" \ + "Binary files a/binary.bin and /dev/null differ\n"; + + repo = cl_git_sandbox_init("diff_format_email"); + test_patch( + "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", + "873806f6f27e631eb0b23e4b56bea2bfac14a373", + &opts, + expected); +} + +void test_diff_binary__delete(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + const char *expected = + "diff --git a/binary.bin b/binary.bin\n" \ + "deleted file mode 100644\n" \ + "index bd474b2519cc15eab801ff851cc7d50f0dee49a1..0000000000000000000000000000000000000000\n" \ + "GIT binary patch\n" \ + "literal 0\n" \ + "Hc$@u4FC%O\n" \ + "\n"; + + opts.flags = GIT_DIFF_SHOW_BINARY; + opts.id_abbrev = GIT_OID_HEXSZ; + + repo = cl_git_sandbox_init("diff_format_email"); + test_patch( + "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", + "873806f6f27e631eb0b23e4b56bea2bfac14a373", + &opts, + expected); +} + +void test_diff_binary__delta(void) +{ + git_index *index; + git_str contents = GIT_STR_INIT; + size_t i; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + const char *expected = + "diff --git a/songof7cities.txt b/songof7cities.txt\n" \ + "index 4210ffd5c390b21dd5483375e75288dea9ede512..cc84ec183351c9944ed90a619ca08911924055b5 100644\n" \ + "GIT binary patch\n" \ + "delta 198\n" \ + "zc$}LmI8{(0BqLQJI6p64AwNwaIJGP_Pa)Ye#M3o+qJ$PQ;Y(X&QMK*C5^Br3bjG4d=XI^5@\n" \ + "JfH567LIG)KJdFSV\n" \ + "\n" \ + "delta 198\n" \ + "zc$}LmI8{(0BqLQJI6p64AwNwaIJGP_Pr*5}Br~;mqJ$PQ;Y(X&QMK*C5^Br3bjG4d=XI^5@\n" \ + "JfH567LIF3FM2!Fd\n" \ + "\n"; + + opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; + opts.id_abbrev = GIT_OID_HEXSZ; + + repo = cl_git_sandbox_init("renames"); + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_pass(git_futils_readbuffer(&contents, "renames/songof7cities.txt")); + + for (i = 0; i < contents.size - 6; i++) { + if (strncmp(&contents.ptr[i], "Cities", 6) == 0) + memcpy(&contents.ptr[i], "cITIES", 6); + } + + cl_git_rewritefile("renames/songof7cities.txt", contents.ptr); + cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_write(index)); + + test_patch( + "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13", + NULL, + &opts, + expected); + + git_index_free(index); + git_str_dispose(&contents); +} + +void test_diff_binary__delta_append(void) +{ + git_index *index; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + const char *expected = + "diff --git a/untimely.txt b/untimely.txt\n" \ + "index 9a69d960ae94b060f56c2a8702545e2bb1abb935..1111d4f11f4b35bf6759e0fb714fe09731ef0840 100644\n" \ + "GIT binary patch\n" \ + "delta 32\n" \ + "nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \ + "\n" \ + "delta 7\n" \ + "Oc%18D`@*{63ljhg(E~C7\n" \ + "\n"; + + opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; + opts.id_abbrev = GIT_OID_HEXSZ; + + repo = cl_git_sandbox_init("renames"); + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_append2file("renames/untimely.txt", "Oh that crazy Kipling!\r\n"); + cl_git_pass(git_index_add_bypath(index, "untimely.txt")); + cl_git_pass(git_index_write(index)); + + test_patch( + "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13", + NULL, + &opts, + expected); + + git_index_free(index); +} + +void test_diff_binary__empty_for_no_diff(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_oid id; + git_commit *commit; + git_tree *tree; + git_diff *diff; + git_str actual = GIT_STR_INIT; + + opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; + opts.id_abbrev = GIT_OID_HEXSZ; + + repo = cl_git_sandbox_init("renames"); + + cl_git_pass(git_oid_fromstr(&id, "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13")); + cl_git_pass(git_commit_lookup(&commit, repo, &id)); + cl_git_pass(git_commit_tree(&tree, commit)); + + cl_git_pass(git_diff_tree_to_tree(&diff, repo, tree, tree, &opts)); + cl_git_pass(git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, git_diff_print_callback__to_buf, &actual)); + + cl_assert_equal_s("", actual.ptr); + + git_str_dispose(&actual); + git_diff_free(diff); + git_commit_free(commit); + git_tree_free(tree); +} + +void test_diff_binary__index_to_workdir(void) +{ + git_index *index; + git_diff *diff; + git_patch *patch; + git_buf actual = GIT_BUF_INIT; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + const char *expected = + "diff --git a/untimely.txt b/untimely.txt\n" \ + "index 9a69d960ae94b060f56c2a8702545e2bb1abb935..1111d4f11f4b35bf6759e0fb714fe09731ef0840 100644\n" \ + "GIT binary patch\n" \ + "delta 32\n" \ + "nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \ + "\n" \ + "delta 7\n" \ + "Oc%18D`@*{63ljhg(E~C7\n" \ + "\n"; + + opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; + opts.id_abbrev = GIT_OID_HEXSZ; + + repo = cl_git_sandbox_init("renames"); + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_append2file("renames/untimely.txt", "Oh that crazy Kipling!\r\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, &opts)); + + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&actual, patch)); + + cl_assert_equal_s(expected, actual.ptr); + + cl_git_pass(git_index_add_bypath(index, "untimely.txt")); + cl_git_pass(git_index_write(index)); + + test_patch( + "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13", + NULL, + &opts, + expected); + + git_buf_dispose(&actual); + git_patch_free(patch); + git_diff_free(diff); + git_index_free(index); +} + +static int print_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + git_str *buf = (git_str *)payload; + + GIT_UNUSED(delta); + + if (hunk) + git_str_put(buf, hunk->header, hunk->header_len); + + if (line) + git_str_put(buf, line->content, line->content_len); + + return git_str_oom(buf) ? -1 : 0; +} + +void test_diff_binary__print_patch_from_diff(void) +{ + git_index *index; + git_diff *diff; + git_str actual = GIT_STR_INIT; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + const char *expected = + "diff --git a/untimely.txt b/untimely.txt\n" \ + "index 9a69d960ae94b060f56c2a8702545e2bb1abb935..1111d4f11f4b35bf6759e0fb714fe09731ef0840 100644\n" \ + "GIT binary patch\n" \ + "delta 32\n" \ + "nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \ + "\n" \ + "delta 7\n" \ + "Oc%18D`@*{63ljhg(E~C7\n" \ + "\n"; + + opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; + opts.id_abbrev = GIT_OID_HEXSZ; + + repo = cl_git_sandbox_init("renames"); + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_append2file("renames/untimely.txt", "Oh that crazy Kipling!\r\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, &opts)); + + cl_git_pass(git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, print_cb, &actual)); + + cl_assert_equal_s(expected, actual.ptr); + + git_str_dispose(&actual); + git_diff_free(diff); + git_index_free(index); +} + +struct diff_data { + char *old_path; + git_oid old_id; + git_str old_binary_base85; + size_t old_binary_inflatedlen; + git_diff_binary_t old_binary_type; + + char *new_path; + git_oid new_id; + git_str new_binary_base85; + size_t new_binary_inflatedlen; + git_diff_binary_t new_binary_type; +}; + +static int file_cb( + const git_diff_delta *delta, + float progress, + void *payload) +{ + struct diff_data *diff_data = payload; + + GIT_UNUSED(progress); + + if (delta->old_file.path) + diff_data->old_path = git__strdup(delta->old_file.path); + + if (delta->new_file.path) + diff_data->new_path = git__strdup(delta->new_file.path); + + git_oid_cpy(&diff_data->old_id, &delta->old_file.id); + git_oid_cpy(&diff_data->new_id, &delta->new_file.id); + + return 0; +} + +static int binary_cb( + const git_diff_delta *delta, + const git_diff_binary *binary, + void *payload) +{ + struct diff_data *diff_data = payload; + + GIT_UNUSED(delta); + + git_str_encode_base85(&diff_data->old_binary_base85, + binary->old_file.data, binary->old_file.datalen); + diff_data->old_binary_inflatedlen = binary->old_file.inflatedlen; + diff_data->old_binary_type = binary->old_file.type; + + git_str_encode_base85(&diff_data->new_binary_base85, + binary->new_file.data, binary->new_file.datalen); + diff_data->new_binary_inflatedlen = binary->new_file.inflatedlen; + diff_data->new_binary_type = binary->new_file.type; + + return 0; +} + +static int hunk_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + void *payload) +{ + GIT_UNUSED(delta); + GIT_UNUSED(hunk); + GIT_UNUSED(payload); + + cl_fail("did not expect hunk callback"); + return 0; +} + +static int line_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + GIT_UNUSED(delta); + GIT_UNUSED(hunk); + GIT_UNUSED(line); + GIT_UNUSED(payload); + + cl_fail("did not expect line callback"); + return 0; +} + +void test_diff_binary__blob_to_blob(void) +{ + git_index *index; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_blob *old_blob, *new_blob; + git_oid old_id, new_id; + struct diff_data diff_data = {0}; + + opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; + opts.id_abbrev = GIT_OID_HEXSZ; + + repo = cl_git_sandbox_init("renames"); + cl_git_pass(git_repository_index__weakptr(&index, repo)); + + cl_git_append2file("renames/untimely.txt", "Oh that crazy Kipling!\r\n"); + cl_git_pass(git_index_add_bypath(index, "untimely.txt")); + cl_git_pass(git_index_write(index)); + + git_oid_fromstr(&old_id, "9a69d960ae94b060f56c2a8702545e2bb1abb935"); + git_oid_fromstr(&new_id, "1111d4f11f4b35bf6759e0fb714fe09731ef0840"); + + cl_git_pass(git_blob_lookup(&old_blob, repo, &old_id)); + cl_git_pass(git_blob_lookup(&new_blob, repo, &new_id)); + + cl_git_pass(git_diff_blobs(old_blob, + "untimely.txt", new_blob, "untimely.txt", &opts, + file_cb, binary_cb, hunk_cb, line_cb, &diff_data)); + + cl_assert_equal_s("untimely.txt", diff_data.old_path); + cl_assert_equal_oid(&old_id, &diff_data.old_id); + cl_assert_equal_i(GIT_DIFF_BINARY_DELTA, diff_data.old_binary_type); + cl_assert_equal_i(7, diff_data.old_binary_inflatedlen); + cl_assert_equal_s("c%18D`@*{63ljhg(E~C7", + diff_data.old_binary_base85.ptr); + + cl_assert_equal_s("untimely.txt", diff_data.new_path); + cl_assert_equal_oid(&new_id, &diff_data.new_id); + cl_assert_equal_i(GIT_DIFF_BINARY_DELTA, diff_data.new_binary_type); + cl_assert_equal_i(32, diff_data.new_binary_inflatedlen); + cl_assert_equal_s("c%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW", + diff_data.new_binary_base85.ptr); + + git_blob_free(old_blob); + git_blob_free(new_blob); + + git__free(diff_data.old_path); + git__free(diff_data.new_path); + + git_str_dispose(&diff_data.old_binary_base85); + git_str_dispose(&diff_data.new_binary_base85); +} diff --git a/tests/libgit2/diff/blob.c b/tests/libgit2/diff/blob.c new file mode 100644 index 000000000..d2f42207d --- /dev/null +++ b/tests/libgit2/diff/blob.c @@ -0,0 +1,1063 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +#define BLOB_DIFF \ + "diff --git a/file b/file\n" \ + "index 45141a7..4d713dc 100644\n" \ + "--- a/file\n" \ + "+++ b/file\n" \ + "@@ -1 +1,6 @@\n" \ + " Hello from the root\n" \ + "+\n" \ + "+Some additional lines\n" \ + "+\n" \ + "+Down here below\n" \ + "+\n" + +static git_repository *g_repo = NULL; +static diff_expects expected; +static git_diff_options opts; +static git_blob *d, *alien; + +static void quick_diff_blob_to_str( + const git_blob *blob, const char *blob_path, + const char *str, size_t len, const char *str_path) +{ + memset(&expected, 0, sizeof(expected)); + + if (str && !len) + len = strlen(str); + + cl_git_pass(git_diff_blob_to_buffer( + blob, blob_path, str, len, str_path, + &opts, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); +} + +void test_diff_blob__initialize(void) +{ + git_oid oid; + + g_repo = cl_git_sandbox_init("attr"); + + cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION)); + opts.context_lines = 1; + + memset(&expected, 0, sizeof(expected)); + + /* tests/resources/attr/root_test4.txt */ + cl_git_pass(git_oid_fromstrn(&oid, "a0f7217a", 8)); + cl_git_pass(git_blob_lookup_prefix(&d, g_repo, &oid, 8)); + + /* alien.png */ + cl_git_pass(git_oid_fromstrn(&oid, "edf3dcee", 8)); + cl_git_pass(git_blob_lookup_prefix(&alien, g_repo, &oid, 8)); +} + +void test_diff_blob__cleanup(void) +{ + git_blob_free(d); + d = NULL; + + git_blob_free(alien); + alien = NULL; + + cl_git_sandbox_cleanup(); +} + +static void assert_one_modified( + int hunks, int lines, int ctxt, int adds, int dels, diff_expects *exp) +{ + cl_assert_equal_i(1, exp->files); + cl_assert_equal_i(1, exp->file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp->files_binary); + + cl_assert_equal_i(hunks, exp->hunks); + cl_assert_equal_i(lines, exp->lines); + cl_assert_equal_i(ctxt, exp->line_ctxt); + cl_assert_equal_i(adds, exp->line_adds); + cl_assert_equal_i(dels, exp->line_dels); +} + +void test_diff_blob__patch_with_freed_blobs(void) +{ + git_oid a_oid, b_oid; + git_blob *a, *b; + git_patch *p; + git_buf buf = GIT_BUF_INIT; + + /* tests/resources/attr/root_test1 */ + cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8)); + cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4)); + /* tests/resources/attr/root_test2 */ + cl_git_pass(git_oid_fromstrn(&b_oid, "4d713dc4", 8)); + cl_git_pass(git_blob_lookup_prefix(&b, g_repo, &b_oid, 4)); + + cl_git_pass(git_patch_from_blobs(&p, a, NULL, b, NULL, NULL)); + + git_blob_free(a); + git_blob_free(b); + + cl_git_pass(git_patch_to_buf(&buf, p)); + cl_assert_equal_s(buf.ptr, BLOB_DIFF); + + git_patch_free(p); + git_buf_dispose(&buf); +} + +void test_diff_blob__can_compare_text_blobs(void) +{ + git_blob *a, *b, *c; + git_oid a_oid, b_oid, c_oid; + + /* tests/resources/attr/root_test1 */ + cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8)); + cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4)); + + /* tests/resources/attr/root_test2 */ + cl_git_pass(git_oid_fromstrn(&b_oid, "4d713dc4", 8)); + cl_git_pass(git_blob_lookup_prefix(&b, g_repo, &b_oid, 4)); + + /* tests/resources/attr/root_test3 */ + cl_git_pass(git_oid_fromstrn(&c_oid, "c96bbb2c2557a832", 16)); + cl_git_pass(git_blob_lookup_prefix(&c, g_repo, &c_oid, 16)); + + /* Doing the equivalent of a `git diff -U1` on these files */ + + /* diff on tests/resources/attr/root_test1 */ + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + a, NULL, b, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + assert_one_modified(1, 6, 1, 5, 0, &expected); + + /* same diff but use direct buffers */ + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_buffers( + git_blob_rawcontent(a), (size_t)git_blob_rawsize(a), NULL, + git_blob_rawcontent(b), (size_t)git_blob_rawsize(b), NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + assert_one_modified(1, 6, 1, 5, 0, &expected); + + /* diff on tests/resources/attr/root_test2 */ + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + b, NULL, c, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + assert_one_modified(1, 15, 3, 9, 3, &expected); + + /* diff on tests/resources/attr/root_test3 */ + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + a, NULL, c, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + assert_one_modified(1, 13, 0, 12, 1, &expected); + + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + c, NULL, d, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + assert_one_modified(2, 14, 4, 6, 4, &expected); + + git_blob_free(a); + git_blob_free(b); + git_blob_free(c); +} + +static void assert_patch_matches_blobs( + git_patch *p, git_blob *a, git_blob *b, + int hunks, int l0, int l1, int ctxt, int adds, int dels) +{ + const git_diff_delta *delta; + size_t tc, ta, td; + + cl_assert(p != NULL); + + delta = git_patch_get_delta(p); + cl_assert(delta != NULL); + + cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status); + cl_assert_equal_oid(git_blob_id(a), &delta->old_file.id); + cl_assert_equal_sz(git_blob_rawsize(a), delta->old_file.size); + cl_assert_equal_oid(git_blob_id(b), &delta->new_file.id); + cl_assert_equal_sz(git_blob_rawsize(b), delta->new_file.size); + + cl_assert_equal_i(hunks, (int)git_patch_num_hunks(p)); + + if (hunks > 0) + cl_assert_equal_i(l0, git_patch_num_lines_in_hunk(p, 0)); + if (hunks > 1) + cl_assert_equal_i(l1, git_patch_num_lines_in_hunk(p, 1)); + + cl_git_pass(git_patch_line_stats(&tc, &ta, &td, p)); + cl_assert_equal_i(ctxt, (int)tc); + cl_assert_equal_i(adds, (int)ta); + cl_assert_equal_i(dels, (int)td); +} + +void test_diff_blob__can_compare_text_blobs_with_patch(void) +{ + git_blob *a, *b, *c; + git_oid a_oid, b_oid, c_oid; + git_patch *p; + + /* tests/resources/attr/root_test1 */ + cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8)); + cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 8)); + + /* tests/resources/attr/root_test2 */ + cl_git_pass(git_oid_fromstrn(&b_oid, "4d713dc4", 8)); + cl_git_pass(git_blob_lookup_prefix(&b, g_repo, &b_oid, 8)); + + /* tests/resources/attr/root_test3 */ + cl_git_pass(git_oid_fromstrn(&c_oid, "c96bbb2c2557a832", 16)); + cl_git_pass(git_blob_lookup_prefix(&c, g_repo, &c_oid, 16)); + + /* Doing the equivalent of a `git diff -U1` on these files */ + + /* diff on tests/resources/attr/root_test1 */ + cl_git_pass(git_patch_from_blobs(&p, a, NULL, b, NULL, &opts)); + assert_patch_matches_blobs(p, a, b, 1, 6, 0, 1, 5, 0); + git_patch_free(p); + + /* diff on tests/resources/attr/root_test2 */ + cl_git_pass(git_patch_from_blobs(&p, b, NULL, c, NULL, &opts)); + assert_patch_matches_blobs(p, b, c, 1, 15, 0, 3, 9, 3); + git_patch_free(p); + + /* diff on tests/resources/attr/root_test3 */ + cl_git_pass(git_patch_from_blobs(&p, a, NULL, c, NULL, &opts)); + assert_patch_matches_blobs(p, a, c, 1, 13, 0, 0, 12, 1); + git_patch_free(p); + + /* one more */ + cl_git_pass(git_patch_from_blobs(&p, c, NULL, d, NULL, &opts)); + assert_patch_matches_blobs(p, c, d, 2, 5, 9, 4, 6, 4); + git_patch_free(p); + + git_blob_free(a); + git_blob_free(b); + git_blob_free(c); +} + +void test_diff_blob__can_compare_against_null_blobs(void) +{ + git_blob *e = NULL; + + cl_git_pass(git_diff_blobs( + d, NULL, e, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, expected.files_binary); + + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(14, expected.hunk_old_lines); + cl_assert_equal_i(14, expected.lines); + cl_assert_equal_i(14, expected.line_dels); + + opts.flags |= GIT_DIFF_REVERSE; + memset(&expected, 0, sizeof(expected)); + + cl_git_pass(git_diff_blobs( + d, NULL, e, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, expected.files_binary); + + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(14, expected.hunk_new_lines); + cl_assert_equal_i(14, expected.lines); + cl_assert_equal_i(14, expected.line_adds); + + opts.flags ^= GIT_DIFF_REVERSE; + memset(&expected, 0, sizeof(expected)); + + cl_git_pass(git_diff_blobs( + alien, NULL, NULL, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.files_binary); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, expected.hunks); + cl_assert_equal_i(0, expected.lines); + + memset(&expected, 0, sizeof(expected)); + + cl_git_pass(git_diff_blobs( + NULL, NULL, alien, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.files_binary); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, expected.hunks); + cl_assert_equal_i(0, expected.lines); +} + +void test_diff_blob__can_compare_against_null_blobs_with_patch(void) +{ + git_blob *e = NULL; + git_patch *p; + const git_diff_delta *delta; + const git_diff_line *line; + int l, max_l; + + cl_git_pass(git_patch_from_blobs(&p, d, NULL, e, NULL, &opts)); + + cl_assert(p != NULL); + + delta = git_patch_get_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); + cl_assert_equal_oid(git_blob_id(d), &delta->old_file.id); + cl_assert_equal_sz(git_blob_rawsize(d), delta->old_file.size); + cl_assert(git_oid_is_zero(&delta->new_file.id)); + cl_assert_equal_sz(0, delta->new_file.size); + + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(14, git_patch_num_lines_in_hunk(p, 0)); + + max_l = git_patch_num_lines_in_hunk(p, 0); + for (l = 0; l < max_l; ++l) { + cl_git_pass(git_patch_get_line_in_hunk(&line, p, 0, l)); + cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin); + } + + git_patch_free(p); + + opts.flags |= GIT_DIFF_REVERSE; + + cl_git_pass(git_patch_from_blobs(&p, d, NULL, e, NULL, &opts)); + + cl_assert(p != NULL); + + delta = git_patch_get_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_ADDED, delta->status); + cl_assert(git_oid_is_zero(&delta->old_file.id)); + cl_assert_equal_sz(0, delta->old_file.size); + cl_assert_equal_oid(git_blob_id(d), &delta->new_file.id); + cl_assert_equal_sz(git_blob_rawsize(d), delta->new_file.size); + + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(14, git_patch_num_lines_in_hunk(p, 0)); + + max_l = git_patch_num_lines_in_hunk(p, 0); + for (l = 0; l < max_l; ++l) { + cl_git_pass(git_patch_get_line_in_hunk(&line, p, 0, l)); + cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin); + } + + git_patch_free(p); + + opts.flags ^= GIT_DIFF_REVERSE; + + cl_git_pass(git_patch_from_blobs(&p, alien, NULL, NULL, NULL, &opts)); + + cl_assert(p != NULL); + + delta = git_patch_get_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); + cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); + + cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); + + git_patch_free(p); + + cl_git_pass(git_patch_from_blobs(&p, NULL, NULL, alien, NULL, &opts)); + + cl_assert(p != NULL); + + delta = git_patch_get_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_ADDED, delta->status); + cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); + + cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); + + git_patch_free(p); +} + +static void assert_identical_blobs_comparison(diff_expects *expected) +{ + cl_assert_equal_i(1, expected->files); + cl_assert_equal_i(1, expected->file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(0, expected->hunks); + cl_assert_equal_i(0, expected->lines); +} + +void test_diff_blob__can_compare_identical_blobs(void) +{ + opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_diff_blobs( + d, NULL, d, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + assert_identical_blobs_comparison(&expected); + cl_assert_equal_i(0, expected.files_binary); + + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + NULL, NULL, NULL, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + assert_identical_blobs_comparison(&expected); + cl_assert_equal_i(0, expected.files_binary); + + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + alien, NULL, alien, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + assert_identical_blobs_comparison(&expected); + cl_assert(expected.files_binary > 0); +} + +void test_diff_blob__can_compare_identical_blobs_with_patch(void) +{ + git_patch *p; + const git_diff_delta *delta; + + cl_git_pass(git_patch_from_blobs(&p, d, NULL, d, NULL, &opts)); + cl_assert(p != NULL); + + delta = git_patch_get_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_UNMODIFIED, delta->status); + cl_assert_equal_sz(delta->old_file.size, git_blob_rawsize(d)); + cl_assert_equal_oid(git_blob_id(d), &delta->old_file.id); + cl_assert_equal_sz(delta->new_file.size, git_blob_rawsize(d)); + cl_assert_equal_oid(git_blob_id(d), &delta->new_file.id); + + cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); + git_patch_free(p); + + cl_git_pass(git_patch_from_blobs(&p, NULL, NULL, NULL, NULL, &opts)); + cl_assert(p != NULL); + + delta = git_patch_get_delta(p); + cl_assert(delta != NULL); + cl_assert_equal_i(GIT_DELTA_UNMODIFIED, delta->status); + cl_assert_equal_sz(0, delta->old_file.size); + cl_assert(git_oid_is_zero(&delta->old_file.id)); + cl_assert_equal_sz(0, delta->new_file.size); + cl_assert(git_oid_is_zero(&delta->new_file.id)); + + cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); + git_patch_free(p); + + cl_git_pass(git_patch_from_blobs(&p, alien, NULL, alien, NULL, &opts)); + cl_assert(p != NULL); + cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_patch_get_delta(p)->status); + cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); + git_patch_free(p); +} + +static void assert_binary_blobs_comparison(diff_expects *expected) +{ + cl_assert(expected->files_binary > 0); + + cl_assert_equal_i(1, expected->files); + cl_assert_equal_i(1, expected->file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expected->hunks); + cl_assert_equal_i(0, expected->lines); +} + +void test_diff_blob__can_compare_two_binary_blobs(void) +{ + git_blob *heart; + git_oid h_oid; + + /* heart.png */ + cl_git_pass(git_oid_fromstrn(&h_oid, "de863bff", 8)); + cl_git_pass(git_blob_lookup_prefix(&heart, g_repo, &h_oid, 8)); + + cl_git_pass(git_diff_blobs( + alien, NULL, heart, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + assert_binary_blobs_comparison(&expected); + + memset(&expected, 0, sizeof(expected)); + + cl_git_pass(git_diff_blobs( + heart, NULL, alien, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + assert_binary_blobs_comparison(&expected); + + git_blob_free(heart); +} + +void test_diff_blob__can_compare_a_binary_blob_and_a_text_blob(void) +{ + cl_git_pass(git_diff_blobs( + alien, NULL, d, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + assert_binary_blobs_comparison(&expected); + + memset(&expected, 0, sizeof(expected)); + + cl_git_pass(git_diff_blobs( + d, NULL, alien, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + assert_binary_blobs_comparison(&expected); +} + +/* + * $ git diff fe773770 a0f7217 + * diff --git a/fe773770 b/a0f7217 + * index fe77377..a0f7217 100644 + * --- a/fe773770 + * +++ b/a0f7217 + * @@ -1,6 +1,6 @@ + * Here is some stuff at the start + * + * -This should go in one hunk + * +This should go in one hunk (first) + * + * Some additional lines + * + * @@ -8,7 +8,7 @@ Down here below the other lines + * + * With even more at the end + * + * -Followed by a second hunk of stuff + * +Followed by a second hunk of stuff (second) + * + * That happens down here + */ +void test_diff_blob__comparing_two_text_blobs_honors_interhunkcontext(void) +{ + git_blob *old_d; + git_oid old_d_oid; + + opts.context_lines = 3; + + /* tests/resources/attr/root_test1 from commit f5b0af1 */ + cl_git_pass(git_oid_fromstrn(&old_d_oid, "fe773770", 8)); + cl_git_pass(git_blob_lookup_prefix(&old_d, g_repo, &old_d_oid, 8)); + + /* Test with default inter-hunk-context (not set) => default is 0 */ + cl_git_pass(git_diff_blobs( + old_d, NULL, d, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + cl_assert_equal_i(2, expected.hunks); + + /* Test with inter-hunk-context explicitly set to 0 */ + opts.interhunk_lines = 0; + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + old_d, NULL, d, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + cl_assert_equal_i(2, expected.hunks); + + /* Test with inter-hunk-context explicitly set to 1 */ + opts.interhunk_lines = 1; + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + old_d, NULL, d, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + + cl_assert_equal_i(1, expected.hunks); + + git_blob_free(old_d); +} + +void test_diff_blob__checks_options_version_too_low(void) +{ + const git_error *err; + + opts.version = 0; + cl_git_fail(git_diff_blobs( + d, NULL, alien, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + err = git_error_last(); + cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); +} + +void test_diff_blob__checks_options_version_too_high(void) +{ + const git_error *err; + + opts.version = 1024; + cl_git_fail(git_diff_blobs( + d, NULL, alien, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + err = git_error_last(); + cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); +} + +void test_diff_blob__can_correctly_detect_a_binary_blob_as_binary(void) +{ + /* alien.png */ + cl_assert_equal_i(true, git_blob_is_binary(alien)); +} + +void test_diff_blob__can_correctly_detect_binary_blob_data_as_binary(void) +{ + /* alien.png */ + const char *content = git_blob_rawcontent(alien); + size_t len = (size_t)git_blob_rawsize(alien); + cl_assert_equal_i(true, git_blob_data_is_binary(content, len)); +} + +void test_diff_blob__can_correctly_detect_a_textual_blob_as_non_binary(void) +{ + /* tests/resources/attr/root_test4.txt */ + cl_assert_equal_i(false, git_blob_is_binary(d)); +} + +void test_diff_blob__can_correctly_detect_textual_blob_data_as_non_binary(void) +{ + /* tests/resources/attr/root_test4.txt */ + const char *content = git_blob_rawcontent(d); + size_t len = (size_t)git_blob_rawsize(d); + cl_assert_equal_i(false, git_blob_data_is_binary(content, len)); +} + +/* + * git_diff_blob_to_buffer tests + */ + +static void assert_changed_single_one_line_file( + diff_expects *expected, git_delta_t mod) +{ + cl_assert_equal_i(1, expected->files); + cl_assert_equal_i(1, expected->file_status[mod]); + cl_assert_equal_i(1, expected->hunks); + cl_assert_equal_i(1, expected->lines); + + if (mod == GIT_DELTA_ADDED) + cl_assert_equal_i(1, expected->line_adds); + else if (mod == GIT_DELTA_DELETED) + cl_assert_equal_i(1, expected->line_dels); +} + +void test_diff_blob__can_compare_blob_to_buffer(void) +{ + git_blob *a; + git_oid a_oid; + const char *a_content = "Hello from the root\n"; + const char *b_content = "Hello from the root\n\nSome additional lines\n\nDown here below\n\n"; + + /* tests/resources/attr/root_test1 */ + cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8)); + cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 8)); + + /* diff from blob a to content of b */ + quick_diff_blob_to_str(a, NULL, b_content, 0, NULL); + assert_one_modified(1, 6, 1, 5, 0, &expected); + + /* diff from blob a to content of a */ + opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + quick_diff_blob_to_str(a, NULL, a_content, 0, NULL); + assert_identical_blobs_comparison(&expected); + + /* diff from NULL blob to content of a */ + memset(&expected, 0, sizeof(expected)); + quick_diff_blob_to_str(NULL, NULL, a_content, 0, NULL); + assert_changed_single_one_line_file(&expected, GIT_DELTA_ADDED); + + /* diff from blob a to NULL buffer */ + memset(&expected, 0, sizeof(expected)); + quick_diff_blob_to_str(a, NULL, NULL, 0, NULL); + assert_changed_single_one_line_file(&expected, GIT_DELTA_DELETED); + + /* diff with reverse */ + opts.flags ^= GIT_DIFF_REVERSE; + + memset(&expected, 0, sizeof(expected)); + quick_diff_blob_to_str(a, NULL, NULL, 0, NULL); + assert_changed_single_one_line_file(&expected, GIT_DELTA_ADDED); + + git_blob_free(a); +} + +void test_diff_blob__can_compare_blob_to_buffer_with_patch(void) +{ + git_patch *p; + git_blob *a; + git_oid a_oid; + const char *a_content = "Hello from the root\n"; + const char *b_content = "Hello from the root\n\nSome additional lines\n\nDown here below\n\n"; + size_t tc, ta, td; + + /* tests/resources/attr/root_test1 */ + cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8)); + cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 8)); + + /* diff from blob a to content of b */ + cl_git_pass(git_patch_from_blob_and_buffer( + &p, a, NULL, b_content, strlen(b_content), NULL, &opts)); + + cl_assert(p != NULL); + cl_assert_equal_i(GIT_DELTA_MODIFIED, git_patch_get_delta(p)->status); + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(6, git_patch_num_lines_in_hunk(p, 0)); + + cl_git_pass(git_patch_line_stats(&tc, &ta, &td, p)); + cl_assert_equal_i(1, (int)tc); + cl_assert_equal_i(5, (int)ta); + cl_assert_equal_i(0, (int)td); + + git_patch_free(p); + + /* diff from blob a to content of a */ + opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + cl_git_pass(git_patch_from_blob_and_buffer( + &p, a, NULL, a_content, strlen(a_content), NULL, &opts)); + cl_assert(p != NULL); + cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_patch_get_delta(p)->status); + cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); + git_patch_free(p); + + /* diff from NULL blob to content of a */ + cl_git_pass(git_patch_from_blob_and_buffer( + &p, NULL, NULL, a_content, strlen(a_content), NULL, &opts)); + cl_assert(p != NULL); + cl_assert_equal_i(GIT_DELTA_ADDED, git_patch_get_delta(p)->status); + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(1, git_patch_num_lines_in_hunk(p, 0)); + git_patch_free(p); + + /* diff from blob a to NULL buffer */ + cl_git_pass(git_patch_from_blob_and_buffer( + &p, a, NULL, NULL, 0, NULL, &opts)); + cl_assert(p != NULL); + cl_assert_equal_i(GIT_DELTA_DELETED, git_patch_get_delta(p)->status); + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(1, git_patch_num_lines_in_hunk(p, 0)); + git_patch_free(p); + + /* diff with reverse */ + opts.flags ^= GIT_DIFF_REVERSE; + + cl_git_pass(git_patch_from_blob_and_buffer( + &p, a, NULL, NULL, 0, NULL, &opts)); + cl_assert(p != NULL); + cl_assert_equal_i(GIT_DELTA_ADDED, git_patch_get_delta(p)->status); + cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); + cl_assert_equal_i(1, git_patch_num_lines_in_hunk(p, 0)); + git_patch_free(p); + + git_blob_free(a); +} + +static void assert_one_modified_with_lines(diff_expects *expected, int lines) +{ + cl_assert_equal_i(1, expected->files); + cl_assert_equal_i(1, expected->file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expected->files_binary); + cl_assert_equal_i(lines, expected->lines); +} + +void test_diff_blob__binary_data_comparisons(void) +{ + git_blob *bin, *nonbin; + git_oid oid; + const char *nonbin_content = "Hello from the root\n"; + size_t nonbin_len = 20; + const char *bin_content = "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n"; + size_t bin_len = 33; + + opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_oid_fromstrn(&oid, "45141a79", 8)); + cl_git_pass(git_blob_lookup_prefix(&nonbin, g_repo, &oid, 8)); + + cl_git_pass(git_oid_fromstrn(&oid, "b435cd56", 8)); + cl_git_pass(git_blob_lookup_prefix(&bin, g_repo, &oid, 8)); + + /* non-binary to reference content */ + + quick_diff_blob_to_str(nonbin, NULL, nonbin_content, nonbin_len, NULL); + assert_identical_blobs_comparison(&expected); + cl_assert_equal_i(0, expected.files_binary); + + /* binary to reference content */ + + quick_diff_blob_to_str(bin, NULL, bin_content, bin_len, NULL); + assert_identical_blobs_comparison(&expected); + + cl_assert_equal_i(1, expected.files_binary); + + /* non-binary to binary content */ + + quick_diff_blob_to_str(nonbin, NULL, bin_content, bin_len, NULL); + assert_binary_blobs_comparison(&expected); + + /* binary to non-binary content */ + + quick_diff_blob_to_str(bin, NULL, nonbin_content, nonbin_len, NULL); + assert_binary_blobs_comparison(&expected); + + /* non-binary to binary blob */ + + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + bin, NULL, nonbin, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + assert_binary_blobs_comparison(&expected); + + /* + * repeat with FORCE_TEXT + */ + + opts.flags |= GIT_DIFF_FORCE_TEXT; + + quick_diff_blob_to_str(bin, NULL, bin_content, bin_len, NULL); + assert_identical_blobs_comparison(&expected); + + quick_diff_blob_to_str(nonbin, NULL, bin_content, bin_len, NULL); + assert_one_modified_with_lines(&expected, 4); + + quick_diff_blob_to_str(bin, NULL, nonbin_content, nonbin_len, NULL); + assert_one_modified_with_lines(&expected, 4); + + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + bin, NULL, nonbin, NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + assert_one_modified_with_lines(&expected, 4); + + /* cleanup */ + git_blob_free(bin); + git_blob_free(nonbin); +} + +void test_diff_blob__using_path_and_attributes(void) +{ + git_config *cfg; + git_blob *bin, *nonbin; + git_oid oid; + const char *nonbin_content = "Hello from the root\n"; + const char *bin_content = + "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n"; + size_t bin_len = 33; + const char *changed; + git_patch *p; + git_buf buf = GIT_BUF_INIT; + + /* set up custom diff drivers and 'diff' attribute mappings for them */ + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, "diff.iam_binary.binary", 1)); + cl_git_pass(git_config_set_bool(cfg, "diff.iam_text.binary", 0)); + cl_git_pass(git_config_set_string( + cfg, "diff.iam_alphactx.xfuncname", "^[A-Za-z].*$")); + cl_git_pass(git_config_set_bool(cfg, "diff.iam_textalpha.binary", 0)); + cl_git_pass(git_config_set_string( + cfg, "diff.iam_textalpha.xfuncname", "^[A-Za-z].*$")); + cl_git_pass(git_config_set_string( + cfg, "diff.iam_numctx.funcname", "^[0-9][0-9]*")); + cl_git_pass(git_config_set_bool(cfg, "diff.iam_textnum.binary", 0)); + cl_git_pass(git_config_set_string( + cfg, "diff.iam_textnum.funcname", "^[0-9][0-9]*")); + git_config_free(cfg); + + cl_git_append2file( + "attr/.gitattributes", + "\n\n# test_diff_blob__using_path_and_attributes extra\n\n" + "*.binary diff=iam_binary\n" + "*.textary diff=iam_text\n" + "*.alphary diff=iam_alphactx\n" + "*.textalphary diff=iam_textalpha\n" + "*.textnumary diff=iam_textnum\n" + "*.numary diff=iam_numctx\n\n"); + + opts.context_lines = 0; + opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_oid_fromstrn(&oid, "45141a79", 8)); + cl_git_pass(git_blob_lookup_prefix(&nonbin, g_repo, &oid, 8)); + /* 20b: "Hello from the root\n" */ + + cl_git_pass(git_oid_fromstrn(&oid, "b435cd56", 8)); + cl_git_pass(git_blob_lookup_prefix(&bin, g_repo, &oid, 8)); + /* 33b: "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\n0123456789\n" */ + + /* non-binary to reference content */ + + quick_diff_blob_to_str(nonbin, NULL, nonbin_content, 0, NULL); + assert_identical_blobs_comparison(&expected); + cl_assert_equal_i(0, expected.files_binary); + + /* binary to reference content */ + + quick_diff_blob_to_str(bin, NULL, bin_content, bin_len, NULL); + assert_identical_blobs_comparison(&expected); + cl_assert_equal_i(1, expected.files_binary); + + /* add some text */ + + changed = "Hello from the root\nMore lines\nAnd more\nGo here\n"; + + quick_diff_blob_to_str(nonbin, NULL, changed, 0, NULL); + assert_one_modified(1, 3, 0, 3, 0, &expected); + + quick_diff_blob_to_str(nonbin, "foo/bar.binary", changed, 0, NULL); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, expected.files_binary); + cl_assert_equal_i(0, expected.hunks); + cl_assert_equal_i(0, expected.lines); + + quick_diff_blob_to_str(nonbin, "foo/bar.textary", changed, 0, NULL); + assert_one_modified(1, 3, 0, 3, 0, &expected); + + quick_diff_blob_to_str(nonbin, "foo/bar.alphary", changed, 0, NULL); + assert_one_modified(1, 3, 0, 3, 0, &expected); + + cl_git_pass(git_patch_from_blob_and_buffer( + &p, nonbin, "zzz.normal", changed, strlen(changed), NULL, &opts)); + cl_git_pass(git_patch_to_buf(&buf, p)); + cl_assert_equal_s( + "diff --git a/zzz.normal b/zzz.normal\n" + "index 45141a7..75b0dbb 100644\n" + "--- a/zzz.normal\n" + "+++ b/zzz.normal\n" + "@@ -1,0 +2,3 @@ Hello from the root\n" + "+More lines\n" + "+And more\n" + "+Go here\n", buf.ptr); + git_buf_dispose(&buf); + git_patch_free(p); + + cl_git_pass(git_patch_from_blob_and_buffer( + &p, nonbin, "zzz.binary", changed, strlen(changed), NULL, &opts)); + cl_git_pass(git_patch_to_buf(&buf, p)); + cl_assert_equal_s( + "diff --git a/zzz.binary b/zzz.binary\n" + "index 45141a7..75b0dbb 100644\n" + "Binary files a/zzz.binary and b/zzz.binary differ\n", buf.ptr); + git_buf_dispose(&buf); + git_patch_free(p); + + cl_git_pass(git_patch_from_blob_and_buffer( + &p, nonbin, "zzz.alphary", changed, strlen(changed), NULL, &opts)); + cl_git_pass(git_patch_to_buf(&buf, p)); + cl_assert_equal_s( + "diff --git a/zzz.alphary b/zzz.alphary\n" + "index 45141a7..75b0dbb 100644\n" + "--- a/zzz.alphary\n" + "+++ b/zzz.alphary\n" + "@@ -1,0 +2,3 @@ Hello from the root\n" + "+More lines\n" + "+And more\n" + "+Go here\n", buf.ptr); + git_buf_dispose(&buf); + git_patch_free(p); + + cl_git_pass(git_patch_from_blob_and_buffer( + &p, nonbin, "zzz.numary", changed, strlen(changed), NULL, &opts)); + cl_git_pass(git_patch_to_buf(&buf, p)); + cl_assert_equal_s( + "diff --git a/zzz.numary b/zzz.numary\n" + "index 45141a7..75b0dbb 100644\n" + "--- a/zzz.numary\n" + "+++ b/zzz.numary\n" + "@@ -1,0 +2,3 @@\n" + "+More lines\n" + "+And more\n" + "+Go here\n", buf.ptr); + git_buf_dispose(&buf); + git_patch_free(p); + + /* "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n" + * 33 bytes + */ + + changed = "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\nreplace a line\n"; + + cl_git_pass(git_patch_from_blob_and_buffer( + &p, bin, "zzz.normal", changed, 37, NULL, &opts)); + cl_git_pass(git_patch_to_buf(&buf, p)); + cl_assert_equal_s( + "diff --git a/zzz.normal b/zzz.normal\n" + "index b435cd5..1604519 100644\n" + "Binary files a/zzz.normal and b/zzz.normal differ\n", buf.ptr); + git_buf_dispose(&buf); + git_patch_free(p); + + cl_git_pass(git_patch_from_blob_and_buffer( + &p, bin, "zzz.textary", changed, 37, NULL, &opts)); + cl_git_pass(git_patch_to_buf(&buf, p)); + cl_assert_equal_s( + "diff --git a/zzz.textary b/zzz.textary\n" + "index b435cd5..1604519 100644\n" + "--- a/zzz.textary\n" + "+++ b/zzz.textary\n" + "@@ -3 +3 @@\n" + "-0123456789\n" + "+replace a line\n", buf.ptr); + git_buf_dispose(&buf); + git_patch_free(p); + + cl_git_pass(git_patch_from_blob_and_buffer( + &p, bin, "zzz.textalphary", changed, 37, NULL, &opts)); + cl_git_pass(git_patch_to_buf(&buf, p)); + cl_assert_equal_s( + "diff --git a/zzz.textalphary b/zzz.textalphary\n" + "index b435cd5..1604519 100644\n" + "--- a/zzz.textalphary\n" + "+++ b/zzz.textalphary\n" + "@@ -3 +3 @@\n" + "-0123456789\n" + "+replace a line\n", buf.ptr); + git_buf_dispose(&buf); + git_patch_free(p); + + cl_git_pass(git_patch_from_blob_and_buffer( + &p, bin, "zzz.textnumary", changed, 37, NULL, &opts)); + cl_git_pass(git_patch_to_buf(&buf, p)); + cl_assert_equal_s( + "diff --git a/zzz.textnumary b/zzz.textnumary\n" + "index b435cd5..1604519 100644\n" + "--- a/zzz.textnumary\n" + "+++ b/zzz.textnumary\n" + "@@ -3 +3 @@ 0123456789\n" + "-0123456789\n" + "+replace a line\n", buf.ptr); + git_buf_dispose(&buf); + git_patch_free(p); + + git_buf_dispose(&buf); + git_blob_free(nonbin); + git_blob_free(bin); +} + +void test_diff_blob__can_compare_buffer_to_buffer(void) +{ + const char *a = "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n"; + const char *b = "a\nB\nc\nd\nE\nF\nh\nj\nk\n"; + + opts.interhunk_lines = 0; + opts.context_lines = 0; + + memset(&expected, 0, sizeof(expected)); + + cl_git_pass(git_diff_buffers( + a, strlen(a), NULL, b, strlen(b), NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + assert_one_modified(4, 9, 0, 4, 5, &expected); + + opts.flags ^= GIT_DIFF_REVERSE; + + memset(&expected, 0, sizeof(expected)); + + cl_git_pass(git_diff_buffers( + a, strlen(a), NULL, b, strlen(b), NULL, &opts, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expected)); + assert_one_modified(4, 9, 0, 5, 4, &expected); +} diff --git a/tests/libgit2/diff/diff_helpers.c b/tests/libgit2/diff/diff_helpers.c new file mode 100644 index 000000000..e9900339f --- /dev/null +++ b/tests/libgit2/diff/diff_helpers.c @@ -0,0 +1,316 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" +#include "git2/sys/diff.h" + +git_tree *resolve_commit_oid_to_tree( + git_repository *repo, + const char *partial_oid) +{ + size_t len = strlen(partial_oid); + git_oid oid; + git_object *obj = NULL; + git_tree *tree = NULL; + + if (git_oid_fromstrn(&oid, partial_oid, len) == 0) + cl_git_pass(git_object_lookup_prefix(&obj, repo, &oid, len, GIT_OBJECT_ANY)); + + cl_git_pass(git_object_peel((git_object **) &tree, obj, GIT_OBJECT_TREE)); + git_object_free(obj); + return tree; +} + +static char diff_pick_suffix(int mode) +{ + if (S_ISDIR(mode)) + return '/'; + else if (GIT_PERMS_IS_EXEC(mode)) + return '*'; + else + return ' '; +} + +static void fprintf_delta(FILE *fp, const git_diff_delta *delta, float progress) +{ + char code = git_diff_status_char(delta->status); + char old_suffix = diff_pick_suffix(delta->old_file.mode); + char new_suffix = diff_pick_suffix(delta->new_file.mode); + + fprintf(fp, "%c\t%s", code, delta->old_file.path); + + if ((delta->old_file.path != delta->new_file.path && + strcmp(delta->old_file.path, delta->new_file.path) != 0) || + (delta->old_file.mode != delta->new_file.mode && + delta->old_file.mode != 0 && delta->new_file.mode != 0)) + fprintf(fp, "%c %s%c", old_suffix, delta->new_file.path, new_suffix); + else if (old_suffix != ' ') + fprintf(fp, "%c", old_suffix); + + fprintf(fp, "\t[%.2f]\n", progress); +} + +int diff_file_cb( + const git_diff_delta *delta, + float progress, + void *payload) +{ + diff_expects *e = payload; + + if (e->debug) + fprintf_delta(stderr, delta, progress); + + if (e->names) + cl_assert_equal_s(e->names[e->files], delta->old_file.path); + if (e->statuses) + cl_assert_equal_i(e->statuses[e->files], (int)delta->status); + + e->files++; + + if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0) + e->files_binary++; + + cl_assert(delta->status <= GIT_DELTA_CONFLICTED); + + e->file_status[delta->status] += 1; + + return 0; +} + +int diff_print_file_cb( + const git_diff_delta *delta, + float progress, + void *payload) +{ + if (!payload) { + fprintf_delta(stderr, delta, progress); + return 0; + } + + if (!((diff_expects *)payload)->debug) + fprintf_delta(stderr, delta, progress); + + return diff_file_cb(delta, progress, payload); +} + +int diff_binary_cb( + const git_diff_delta *delta, + const git_diff_binary *binary, + void *payload) +{ + GIT_UNUSED(delta); + GIT_UNUSED(binary); + GIT_UNUSED(payload); + + return 0; +} + +int diff_hunk_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + void *payload) +{ + diff_expects *e = payload; + const char *scan = hunk->header, *scan_end = scan + hunk->header_len; + + GIT_UNUSED(delta); + + /* confirm no NUL bytes in header text */ + while (scan < scan_end) + cl_assert('\0' != *scan++); + + e->hunks++; + e->hunk_old_lines += hunk->old_lines; + e->hunk_new_lines += hunk->new_lines; + return 0; +} + +int diff_line_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + diff_expects *e = payload; + + GIT_UNUSED(delta); + GIT_UNUSED(hunk); + + e->lines++; + switch (line->origin) { + case GIT_DIFF_LINE_CONTEXT: + case GIT_DIFF_LINE_CONTEXT_EOFNL: /* techically not a line */ + e->line_ctxt++; + break; + case GIT_DIFF_LINE_ADDITION: + case GIT_DIFF_LINE_ADD_EOFNL: /* technically not a line add */ + e->line_adds++; + break; + case GIT_DIFF_LINE_DELETION: + case GIT_DIFF_LINE_DEL_EOFNL: /* technically not a line delete */ + e->line_dels++; + break; + default: + break; + } + return 0; +} + +int diff_foreach_via_iterator( + git_diff *diff, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb line_cb, + void *data) +{ + size_t d, num_d = git_diff_num_deltas(diff); + + GIT_UNUSED(binary_cb); + + for (d = 0; d < num_d; ++d) { + git_patch *patch; + const git_diff_delta *delta; + size_t h, num_h; + + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + cl_assert((delta = git_patch_get_delta(patch)) != NULL); + + /* call file_cb for this file */ + if (file_cb != NULL && file_cb(delta, (float)d / num_d, data) != 0) { + git_patch_free(patch); + goto abort; + } + + /* if there are no changes, then the patch will be NULL */ + if (!patch) { + cl_assert(delta->status == GIT_DELTA_UNMODIFIED || + (delta->flags & GIT_DIFF_FLAG_BINARY) != 0); + continue; + } + + if (!hunk_cb && !line_cb) { + git_patch_free(patch); + continue; + } + + num_h = git_patch_num_hunks(patch); + + for (h = 0; h < num_h; h++) { + const git_diff_hunk *hunk; + size_t l, num_l; + + cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h)); + + if (hunk_cb && hunk_cb(delta, hunk, data) != 0) { + git_patch_free(patch); + goto abort; + } + + for (l = 0; l < num_l; ++l) { + const git_diff_line *line; + + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l)); + + if (line_cb && + line_cb(delta, hunk, line, data) != 0) { + git_patch_free(patch); + goto abort; + } + } + } + + git_patch_free(patch); + } + + return 0; + +abort: + git_error_clear(); + return GIT_EUSER; +} + +void diff_print(FILE *fp, git_diff *diff) +{ + cl_git_pass( + git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, + git_diff_print_callback__to_file_handle, fp ? fp : stderr)); +} + +void diff_print_raw(FILE *fp, git_diff *diff) +{ + cl_git_pass( + git_diff_print(diff, GIT_DIFF_FORMAT_RAW, + git_diff_print_callback__to_file_handle, fp ? fp : stderr)); +} + +static size_t num_modified_deltas(git_diff *diff) +{ + const git_diff_delta *delta; + size_t i, cnt = 0; + + for (i = 0; i < git_diff_num_deltas(diff); i++) { + delta = git_diff_get_delta(diff, i); + + if (delta->status != GIT_DELTA_UNMODIFIED) + cnt++; + } + + return cnt; +} + +void diff_assert_equal(git_diff *a, git_diff *b) +{ + const git_diff_delta *ad, *bd; + size_t i, j; + + assert(a && b); + + cl_assert_equal_i(num_modified_deltas(a), num_modified_deltas(b)); + + for (i = 0, j = 0; + i < git_diff_num_deltas(a) && j < git_diff_num_deltas(b); ) { + + ad = git_diff_get_delta(a, i); + bd = git_diff_get_delta(b, j); + + if (ad->status == GIT_DELTA_UNMODIFIED) { + i++; + continue; + } + if (bd->status == GIT_DELTA_UNMODIFIED) { + j++; + continue; + } + + cl_assert_equal_i(ad->status, bd->status); + cl_assert_equal_i(ad->flags, bd->flags); + cl_assert_equal_i(ad->similarity, bd->similarity); + cl_assert_equal_i(ad->nfiles, bd->nfiles); + + /* Don't examine the size or the flags of the deltas; + * computed deltas have sizes (parsed deltas do not) and + * computed deltas will have flags of `VALID_ID` and + * `EXISTS` (parsed deltas will not query the ODB.) + */ + + /* an empty id indicates that it wasn't presented, because + * the diff was identical. (eg, pure rename, mode change only, etc) + */ + if (ad->old_file.id_abbrev && bd->old_file.id_abbrev) { + cl_assert_equal_i(ad->old_file.id_abbrev, bd->old_file.id_abbrev); + cl_assert_equal_oid(&ad->old_file.id, &bd->old_file.id); + cl_assert_equal_i(ad->old_file.mode, bd->old_file.mode); + } + cl_assert_equal_s(ad->old_file.path, bd->old_file.path); + + if (ad->new_file.id_abbrev && bd->new_file.id_abbrev) { + cl_assert_equal_oid(&ad->new_file.id, &bd->new_file.id); + cl_assert_equal_i(ad->new_file.id_abbrev, bd->new_file.id_abbrev); + cl_assert_equal_i(ad->new_file.mode, bd->new_file.mode); + } + cl_assert_equal_s(ad->new_file.path, bd->new_file.path); + + i++; + j++; + } +} + diff --git a/tests/libgit2/diff/diff_helpers.h b/tests/libgit2/diff/diff_helpers.h new file mode 100644 index 000000000..af855ce68 --- /dev/null +++ b/tests/libgit2/diff/diff_helpers.h @@ -0,0 +1,73 @@ +#include "futils.h" +#include "git2/diff.h" + +extern git_tree *resolve_commit_oid_to_tree( + git_repository *repo, const char *partial_oid); + +typedef struct { + int files; + int files_binary; + + int file_status[11]; /* indexed by git_delta_t value */ + + int hunks; + int hunk_new_lines; + int hunk_old_lines; + + int lines; + int line_ctxt; + int line_adds; + int line_dels; + + /* optional arrays of expected specific values */ + const char **names; + int *statuses; + + int debug; + +} diff_expects; + +typedef struct { + const char *path; + const char *matched_pathspec; +} notify_expected; + +extern int diff_file_cb( + const git_diff_delta *delta, + float progress, + void *cb_data); + +extern int diff_print_file_cb( + const git_diff_delta *delta, + float progress, + void *cb_data); + +extern int diff_binary_cb( + const git_diff_delta *delta, + const git_diff_binary *binary, + void *cb_data); + +extern int diff_hunk_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + void *cb_data); + +extern int diff_line_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *cb_data); + +extern int diff_foreach_via_iterator( + git_diff *diff, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb line_cb, + void *data); + +extern void diff_print(FILE *fp, git_diff *diff); +extern void diff_print_raw(FILE *fp, git_diff *diff); + +extern void diff_assert_equal(git_diff *a, git_diff *b); + diff --git a/tests/libgit2/diff/diffiter.c b/tests/libgit2/diff/diffiter.c new file mode 100644 index 000000000..991c73bf4 --- /dev/null +++ b/tests/libgit2/diff/diffiter.c @@ -0,0 +1,453 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +void test_diff_diffiter__initialize(void) +{ +} + +void test_diff_diffiter__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_diffiter__create(void) +{ + git_repository *repo = cl_git_sandbox_init("attr"); + git_diff *diff; + size_t d, num_d; + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL)); + + num_d = git_diff_num_deltas(diff); + for (d = 0; d < num_d; ++d) { + const git_diff_delta *delta = git_diff_get_delta(diff, d); + cl_assert(delta != NULL); + } + + cl_assert(!git_diff_get_delta(diff, num_d)); + + git_diff_free(diff); +} + +void test_diff_diffiter__iterate_files_1(void) +{ + git_repository *repo = cl_git_sandbox_init("attr"); + git_diff *diff; + size_t d, num_d; + diff_expects exp = { 0 }; + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL)); + + num_d = git_diff_num_deltas(diff); + + for (d = 0; d < num_d; ++d) { + const git_diff_delta *delta = git_diff_get_delta(diff, d); + cl_assert(delta != NULL); + + diff_file_cb(delta, (float)d / (float)num_d, &exp); + } + cl_assert_equal_sz(6, exp.files); + + git_diff_free(diff); +} + +void test_diff_diffiter__iterate_files_2(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff *diff; + size_t d, num_d; + int count = 0; + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL)); + + num_d = git_diff_num_deltas(diff); + cl_assert_equal_i(8, (int)num_d); + + for (d = 0; d < num_d; ++d) { + const git_diff_delta *delta = git_diff_get_delta(diff, d); + cl_assert(delta != NULL); + count++; + } + cl_assert_equal_i(8, count); + + git_diff_free(diff); +} + +void test_diff_diffiter__iterate_files_and_hunks(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + size_t d, num_d; + int file_count = 0, hunk_count = 0; + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); + + num_d = git_diff_num_deltas(diff); + + for (d = 0; d < num_d; ++d) { + git_patch *patch; + size_t h, num_h; + + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + cl_assert(patch); + + file_count++; + + num_h = git_patch_num_hunks(patch); + + for (h = 0; h < num_h; h++) { + const git_diff_hunk *hunk; + + cl_git_pass(git_patch_get_hunk(&hunk, NULL, patch, h)); + cl_assert(hunk); + + hunk_count++; + } + + git_patch_free(patch); + } + + cl_assert_equal_i(13, file_count); + cl_assert_equal_i(8, hunk_count); + + git_diff_free(diff); +} + +void test_diff_diffiter__max_size_threshold(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + int file_count = 0, binary_count = 0, hunk_count = 0; + size_t d, num_d; + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); + num_d = git_diff_num_deltas(diff); + + for (d = 0; d < num_d; ++d) { + git_patch *patch; + const git_diff_delta *delta; + + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + cl_assert(patch); + delta = git_patch_get_delta(patch); + cl_assert(delta); + + file_count++; + hunk_count += (int)git_patch_num_hunks(patch); + + assert((delta->flags & (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)) != 0); + binary_count += ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); + + git_patch_free(patch); + } + + cl_assert_equal_i(13, file_count); + cl_assert_equal_i(0, binary_count); + cl_assert_equal_i(8, hunk_count); + + git_diff_free(diff); + + /* try again with low file size threshold */ + + file_count = binary_count = hunk_count = 0; + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + opts.max_size = 50; /* treat anything over 50 bytes as binary! */ + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); + num_d = git_diff_num_deltas(diff); + + for (d = 0; d < num_d; ++d) { + git_patch *patch; + const git_diff_delta *delta; + + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + delta = git_patch_get_delta(patch); + + file_count++; + hunk_count += (int)git_patch_num_hunks(patch); + + assert((delta->flags & (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)) != 0); + binary_count += ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); + + git_patch_free(patch); + } + + cl_assert_equal_i(13, file_count); + /* Three files are over the 50 byte threshold: + * - staged_changes_file_deleted + * - staged_changes_modified_file + * - staged_new_file_modified_file + */ + cl_assert_equal_i(3, binary_count); + cl_assert_equal_i(5, hunk_count); + + git_diff_free(diff); +} + + +void test_diff_diffiter__iterate_all(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp = {0}; + size_t d, num_d; + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); + + num_d = git_diff_num_deltas(diff); + for (d = 0; d < num_d; ++d) { + git_patch *patch; + size_t h, num_h; + + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + cl_assert(patch); + exp.files++; + + num_h = git_patch_num_hunks(patch); + for (h = 0; h < num_h; h++) { + const git_diff_hunk *range; + size_t l, num_l; + + cl_git_pass(git_patch_get_hunk(&range, &num_l, patch, h)); + cl_assert(range); + exp.hunks++; + + for (l = 0; l < num_l; ++l) { + const git_diff_line *line; + + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l)); + cl_assert(line && line->content); + exp.lines++; + } + } + + git_patch_free(patch); + } + + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(8, exp.hunks); + cl_assert_equal_i(14, exp.lines); + + git_diff_free(diff); +} + +static void iterate_over_patch(git_patch *patch, diff_expects *exp) +{ + size_t h, num_h = git_patch_num_hunks(patch), num_l; + + exp->files++; + exp->hunks += (int)num_h; + + /* let's iterate in reverse, just because we can! */ + for (h = 1, num_l = 0; h <= num_h; ++h) + num_l += git_patch_num_lines_in_hunk(patch, num_h - h); + + exp->lines += (int)num_l; +} + +#define PATCH_CACHE 5 + +void test_diff_diffiter__iterate_randomly_while_saving_state(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp = {0}; + git_patch *patches[PATCH_CACHE]; + size_t p, d, num_d; + + memset(patches, 0, sizeof(patches)); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); + + num_d = git_diff_num_deltas(diff); + + /* To make sure that references counts work for diff and patch objects, + * this generates patches and randomly caches them. Only when the patch + * is removed from the cache are hunks and lines counted. At the end, + * there are still patches in the cache, so free the diff and try to + * process remaining patches after the diff is freed. + */ + + srand(121212); + p = rand() % PATCH_CACHE; + + for (d = 0; d < num_d; ++d) { + /* take old patch */ + git_patch *patch = patches[p]; + patches[p] = NULL; + + /* cache new patch */ + cl_git_pass(git_patch_from_diff(&patches[p], diff, d)); + cl_assert(patches[p] != NULL); + + /* process old patch if non-NULL */ + if (patch != NULL) { + iterate_over_patch(patch, &exp); + git_patch_free(patch); + } + + p = rand() % PATCH_CACHE; + } + + /* free diff list now - refcounts should keep things safe */ + git_diff_free(diff); + + /* process remaining unprocessed patches */ + for (p = 0; p < PATCH_CACHE; p++) { + git_patch *patch = patches[p]; + + if (patch != NULL) { + iterate_over_patch(patch, &exp); + git_patch_free(patch); + } + } + + /* hopefully it all still added up right */ + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(8, exp.hunks); + cl_assert_equal_i(14, exp.lines); +} + +/* This output is taken directly from `git diff` on the status test data */ +static const char *expected_patch_text[8] = { + /* 0 */ + "diff --git a/file_deleted b/file_deleted\n" + "deleted file mode 100644\n" + "index 5452d32..0000000\n" + "--- a/file_deleted\n" + "+++ /dev/null\n" + "@@ -1 +0,0 @@\n" + "-file_deleted\n", + /* 1 */ + "diff --git a/modified_file b/modified_file\n" + "index 452e424..0a53963 100644\n" + "--- a/modified_file\n" + "+++ b/modified_file\n" + "@@ -1 +1,2 @@\n" + " modified_file\n" + "+modified_file\n", + /* 2 */ + "diff --git a/staged_changes_file_deleted b/staged_changes_file_deleted\n" + "deleted file mode 100644\n" + "index a6be623..0000000\n" + "--- a/staged_changes_file_deleted\n" + "+++ /dev/null\n" + "@@ -1,2 +0,0 @@\n" + "-staged_changes_file_deleted\n" + "-staged_changes_file_deleted\n", + /* 3 */ + "diff --git a/staged_changes_modified_file b/staged_changes_modified_file\n" + "index 906ee77..011c344 100644\n" + "--- a/staged_changes_modified_file\n" + "+++ b/staged_changes_modified_file\n" + "@@ -1,2 +1,3 @@\n" + " staged_changes_modified_file\n" + " staged_changes_modified_file\n" + "+staged_changes_modified_file\n", + /* 4 */ + "diff --git a/staged_new_file_deleted_file b/staged_new_file_deleted_file\n" + "deleted file mode 100644\n" + "index 90b8c29..0000000\n" + "--- a/staged_new_file_deleted_file\n" + "+++ /dev/null\n" + "@@ -1 +0,0 @@\n" + "-staged_new_file_deleted_file\n", + /* 5 */ + "diff --git a/staged_new_file_modified_file b/staged_new_file_modified_file\n" + "index ed06290..8b090c0 100644\n" + "--- a/staged_new_file_modified_file\n" + "+++ b/staged_new_file_modified_file\n" + "@@ -1 +1,2 @@\n" + " staged_new_file_modified_file\n" + "+staged_new_file_modified_file\n", + /* 6 */ + "diff --git a/subdir/deleted_file b/subdir/deleted_file\n" + "deleted file mode 100644\n" + "index 1888c80..0000000\n" + "--- a/subdir/deleted_file\n" + "+++ /dev/null\n" + "@@ -1 +0,0 @@\n" + "-subdir/deleted_file\n", + /* 7 */ + "diff --git a/subdir/modified_file b/subdir/modified_file\n" + "index a619198..57274b7 100644\n" + "--- a/subdir/modified_file\n" + "+++ b/subdir/modified_file\n" + "@@ -1 +1,2 @@\n" + " subdir/modified_file\n" + "+subdir/modified_file\n" +}; + +void test_diff_diffiter__iterate_and_generate_patch_text(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff *diff; + size_t d, num_d; + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL)); + + num_d = git_diff_num_deltas(diff); + cl_assert_equal_i(8, (int)num_d); + + for (d = 0; d < num_d; ++d) { + git_patch *patch; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + cl_assert(patch != NULL); + + cl_git_pass(git_patch_to_buf(&buf, patch)); + + cl_assert_equal_s(expected_patch_text[d], buf.ptr); + + git_buf_dispose(&buf); + git_patch_free(patch); + } + + git_diff_free(diff); +} + +void test_diff_diffiter__checks_options_version(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + const git_error *err; + + opts.version = 0; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + cl_git_fail(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); + err = git_error_last(); + cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); + + git_error_clear(); + opts.version = 1024; + cl_git_fail(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); + err = git_error_last(); + cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); +} + diff --git a/tests/libgit2/diff/drivers.c b/tests/libgit2/diff/drivers.c new file mode 100644 index 000000000..304a54b56 --- /dev/null +++ b/tests/libgit2/diff/drivers.c @@ -0,0 +1,279 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" +#include "repository.h" +#include "diff_driver.h" + +static git_repository *g_repo = NULL; + +void test_diff_drivers__initialize(void) +{ +} + +void test_diff_drivers__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static void overwrite_filemode(const char *expected, git_buf *actual) +{ + size_t offset; + char *found; + + found = strstr(expected, "100644"); + if (!found) + return; + + offset = ((const char *)found) - expected; + if (actual->size < offset + 6) + return; + + if (memcmp(&actual->ptr[offset], "100644", 6) != 0) + memcpy(&actual->ptr[offset], "100644", 6); +} + +void test_diff_drivers__patterns(void) +{ + git_config *cfg; + const char *one_sha = "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13"; + git_tree *one; + git_diff *diff; + git_patch *patch; + git_buf actual = GIT_BUF_INIT; + const char *expected0 = "diff --git a/untimely.txt b/untimely.txt\nindex 9a69d96..57fd0cf 100644\n--- a/untimely.txt\n+++ b/untimely.txt\n@@ -22,3 +22,5 @@ Comes through the blood of the vanguards who\n dreamed--too soon--it had sounded.\r\n \r\n -- Rudyard Kipling\r\n+\r\n+Some new stuff\r\n"; + const char *expected1 = "diff --git a/untimely.txt b/untimely.txt\nindex 9a69d96..57fd0cf 100644\nBinary files a/untimely.txt and b/untimely.txt differ\n"; + const char *expected2 = "diff --git a/untimely.txt b/untimely.txt\nindex 9a69d96..57fd0cf 100644\n--- a/untimely.txt\n+++ b/untimely.txt\n@@ -22,3 +22,5 @@ Heaven delivers on earth the Hour that cannot be\n dreamed--too soon--it had sounded.\r\n \r\n -- Rudyard Kipling\r\n+\r\n+Some new stuff\r\n"; + + g_repo = cl_git_sandbox_init("renames"); + + one = resolve_commit_oid_to_tree(g_repo, one_sha); + + /* no diff */ + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); + cl_assert_equal_i(0, (int)git_diff_num_deltas(diff)); + git_diff_free(diff); + + /* default diff */ + + cl_git_append2file("renames/untimely.txt", "\r\nSome new stuff\r\n"); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); + cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); + + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&actual, patch)); + cl_assert_equal_s(expected0, actual.ptr); + + git_buf_dispose(&actual); + git_patch_free(patch); + git_diff_free(diff); + + /* attribute diff set to false */ + + cl_git_rewritefile("renames/.gitattributes", "untimely.txt -diff\n"); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); + cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); + + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&actual, patch)); + cl_assert_equal_s(expected1, actual.ptr); + + git_buf_dispose(&actual); + git_patch_free(patch); + git_diff_free(diff); + + /* attribute diff set to unconfigured value (should use default) */ + + cl_git_rewritefile("renames/.gitattributes", "untimely.txt diff=kipling0\n"); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); + cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); + + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&actual, patch)); + cl_assert_equal_s(expected0, actual.ptr); + + git_buf_dispose(&actual); + git_patch_free(patch); + git_diff_free(diff); + + /* let's define that driver */ + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, "diff.kipling0.binary", 1)); + git_config_free(cfg); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); + cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); + + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&actual, patch)); + cl_assert_equal_s(expected1, actual.ptr); + + git_buf_dispose(&actual); + git_patch_free(patch); + git_diff_free(diff); + + /* let's use a real driver with some regular expressions */ + + git_diff_driver_registry_free(g_repo->diff_drivers); + g_repo->diff_drivers = NULL; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, "diff.kipling0.binary", 0)); + cl_git_pass(git_config_set_string(cfg, "diff.kipling0.xfuncname", "^H.*$")); + git_config_free(cfg); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL)); + cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); + + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&actual, patch)); + cl_assert_equal_s(expected2, actual.ptr); + + git_buf_dispose(&actual); + git_patch_free(patch); + git_diff_free(diff); + + git_tree_free(one); +} + +void test_diff_drivers__long_lines(void) +{ + const char *base = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non nisi ligula. Ut viverra enim sed lobortis suscipit.\nPhasellus eget erat odio. Praesent at est iaculis, ultricies augue vel, dignissim risus. Suspendisse at nisi quis turpis fringilla rutrum id sit amet nulla.\nNam eget dolor fermentum, aliquet nisl at, convallis tellus. Pellentesque rhoncus erat enim, id porttitor elit euismod quis.\nMauris sollicitudin magna odio, non egestas libero vehicula ut. Etiam et quam velit. Fusce eget libero rhoncus, ultricies felis sit amet, egestas purus.\nAliquam in semper tellus. Pellentesque adipiscing rutrum velit, quis malesuada lacus consequat eget.\n"; + git_index *idx; + git_diff *diff; + git_patch *patch; + git_buf actual = GIT_BUF_INIT; + const char *expected = "diff --git a/longlines.txt b/longlines.txt\nindex c1ce6ef..0134431 100644\n--- a/longlines.txt\n+++ b/longlines.txt\n@@ -3,3 +3,5 @@ Phasellus eget erat odio. Praesent at est iaculis, ultricies augue vel, dignissi\n Nam eget dolor fermentum, aliquet nisl at, convallis tellus. Pellentesque rhoncus erat enim, id porttitor elit euismod quis.\n Mauris sollicitudin magna odio, non egestas libero vehicula ut. Etiam et quam velit. Fusce eget libero rhoncus, ultricies felis sit amet, egestas purus.\n Aliquam in semper tellus. Pellentesque adipiscing rutrum velit, quis malesuada lacus consequat eget.\n+newline\n+newline\n"; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile("empty_standard_repo/longlines.txt", base); + cl_git_pass(git_repository_index(&idx, g_repo)); + cl_git_pass(git_index_add_bypath(idx, "longlines.txt")); + cl_git_pass(git_index_write(idx)); + git_index_free(idx); + + cl_git_append2file("empty_standard_repo/longlines.txt", "newline\nnewline\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); + cl_assert_equal_sz(1, git_diff_num_deltas(diff)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&actual, patch)); + + /* if chmod not supported, overwrite mode bits since anything is possible */ + overwrite_filemode(expected, &actual); + + cl_assert_equal_s(expected, actual.ptr); + + git_buf_dispose(&actual); + git_patch_free(patch); + git_diff_free(diff); +} + +void test_diff_drivers__builtins(void) +{ + git_diff *diff; + git_patch *patch; + git_str file = GIT_STR_INIT, expected = GIT_STR_INIT; + git_buf actual = GIT_BUF_INIT; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_vector files = GIT_VECTOR_INIT; + size_t i; + char *path, *extension; + + g_repo = cl_git_sandbox_init("userdiff"); + + cl_git_pass(git_fs_path_dirload(&files, "userdiff/files", 9, 0)); + + opts.interhunk_lines = 1; + opts.context_lines = 1; + opts.pathspec.count = 1; + + git_vector_foreach(&files, i, path) { + if (git__prefixcmp(path, "files/file.")) + continue; + extension = path + strlen("files/file."); + opts.pathspec.strings = &path; + + /* do diff with no special driver */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_assert_equal_sz(1, git_diff_num_deltas(diff)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&actual, patch)); + + git_str_sets(&expected, "userdiff/expected/nodriver/diff."); + git_str_puts(&expected, extension); + cl_git_pass(git_futils_readbuffer(&expected, expected.ptr)); + + overwrite_filemode(expected.ptr, &actual); + + cl_assert_equal_s(expected.ptr, actual.ptr); + + git_buf_dispose(&actual); + git_patch_free(patch); + git_diff_free(diff); + + /* do diff with driver */ + + { + FILE *fp = fopen("userdiff/.gitattributes", "w"); + fprintf(fp, "*.%s diff=%s\n", extension, extension); + fclose(fp); + } + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_assert_equal_sz(1, git_diff_num_deltas(diff)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&actual, patch)); + + git_str_sets(&expected, "userdiff/expected/driver/diff."); + git_str_puts(&expected, extension); + cl_git_pass(git_futils_readbuffer(&expected, expected.ptr)); + + overwrite_filemode(expected.ptr, &actual); + + cl_assert_equal_s(expected.ptr, actual.ptr); + + git_buf_dispose(&actual); + git_patch_free(patch); + git_diff_free(diff); + + git__free(path); + } + + git_buf_dispose(&actual); + git_str_dispose(&file); + git_str_dispose(&expected); + git_vector_free(&files); +} + +void test_diff_drivers__invalid_pattern(void) +{ + git_config *cfg; + git_index *idx; + git_diff *diff; + git_patch *patch; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("userdiff"); + cl_git_mkfile("userdiff/.gitattributes", "*.storyboard diff=storyboard\n"); + + cl_git_pass(git_repository_config__weakptr(&cfg, g_repo)); + cl_git_pass(git_config_set_string(cfg, "diff.storyboard.xfuncname", "")); + + cl_git_mkfile("userdiff/dummy.storyboard", ""); + cl_git_pass(git_repository_index__weakptr(&idx, g_repo)); + cl_git_pass(git_index_add_bypath(idx, "dummy.storyboard")); + cl_git_mkfile("userdiff/dummy.storyboard", "some content\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + + git_patch_free(patch); + git_diff_free(diff); +} diff --git a/tests/libgit2/diff/externalmodifications.c b/tests/libgit2/diff/externalmodifications.c new file mode 100644 index 000000000..df62c3316 --- /dev/null +++ b/tests/libgit2/diff/externalmodifications.c @@ -0,0 +1,133 @@ +#include "clar_libgit2.h" +#include "../checkout/checkout_helpers.h" + +#include "index.h" +#include "repository.h" + +static git_repository *g_repo; + +void test_diff_externalmodifications__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo2"); +} + +void test_diff_externalmodifications__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +void test_diff_externalmodifications__file_becomes_smaller(void) +{ + git_index *index; + git_diff *diff; + git_patch* patch; + git_str path = GIT_STR_INIT; + char big_string[500001]; + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "README")); + + /* Modify the file with a large string */ + memset(big_string, '\n', sizeof(big_string) - 1); + big_string[sizeof(big_string) - 1] = '\0'; + cl_git_mkfile(path.ptr, big_string); + + /* Get a diff */ + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + cl_assert_equal_i(500000, git_diff_get_delta(diff, 0)->new_file.size); + + /* Simulate file modification after we've gotten the diff. + * Write a shorter string to ensure that we don't mmap 500KB from + * the previous revision, which would most likely crash. */ + cl_git_mkfile(path.ptr, "hello"); + + /* Attempt to get a patch */ + cl_git_fail(git_patch_from_diff(&patch, diff, 0)); + + git_index_free(index); + git_diff_free(diff); + git_str_dispose(&path); +} + +void test_diff_externalmodifications__file_becomes_empty(void) +{ + git_index *index; + git_diff *diff; + git_patch* patch; + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "README")); + + /* Modify the file */ + cl_git_mkfile(path.ptr, "hello"); + + /* Get a diff */ + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + cl_assert_equal_i(5, git_diff_get_delta(diff, 0)->new_file.size); + + /* Empty out the file after we've gotten the diff */ + cl_git_mkfile(path.ptr, ""); + + /* Attempt to get a patch */ + cl_git_fail(git_patch_from_diff(&patch, diff, 0)); + + git_index_free(index); + git_diff_free(diff); + git_str_dispose(&path); +} + +void test_diff_externalmodifications__file_deleted(void) +{ + git_index *index; + git_diff *diff; + git_patch* patch; + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "README")); + + /* Get a diff */ + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + + /* Delete the file */ + cl_git_rmfile(path.ptr); + + /* Attempt to get a patch */ + cl_git_fail(git_patch_from_diff(&patch, diff, 0)); + + git_index_free(index); + git_diff_free(diff); + git_str_dispose(&path); +} + +void test_diff_externalmodifications__empty_file_becomes_non_empty(void) +{ + git_index *index; + git_diff *diff; + git_patch* patch; + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "README")); + + /* Empty out the file */ + cl_git_mkfile(path.ptr, ""); + + /* Get a diff */ + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + cl_assert_equal_i(0, git_diff_get_delta(diff, 0)->new_file.size); + + /* Simulate file modification after we've gotten the diff */ + cl_git_mkfile(path.ptr, "hello"); + cl_git_fail(git_patch_from_diff(&patch, diff, 0)); + + git_index_free(index); + git_diff_free(diff); + git_str_dispose(&path); +} diff --git a/tests/libgit2/diff/format_email.c b/tests/libgit2/diff/format_email.c new file mode 100644 index 000000000..612804c42 --- /dev/null +++ b/tests/libgit2/diff/format_email.c @@ -0,0 +1,523 @@ +#include "clar.h" +#include "clar_libgit2.h" + +#include "commit.h" +#include "diff.h" +#include "diff_generate.h" + +static git_repository *repo; + +void test_diff_format_email__initialize(void) +{ + repo = cl_git_sandbox_init("diff_format_email"); +} + +void test_diff_format_email__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +#ifndef GIT_DEPRECATE_HARD +static void assert_email_match( + const char *expected, + const char *oidstr, + git_diff_format_email_options *opts) +{ + git_oid oid; + git_commit *commit = NULL; + git_diff *diff = NULL; + git_buf buf = GIT_BUF_INIT; + + git_oid_fromstr(&oid, oidstr); + + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + + opts->id = git_commit_id(commit); + opts->author = git_commit_author(commit); + if (!opts->summary) + opts->summary = git_commit_summary(commit); + + cl_git_pass(git_diff__commit(&diff, repo, commit, NULL)); + cl_git_pass(git_diff_format_email(&buf, diff, opts)); + + cl_assert_equal_s(expected, buf.ptr); + git_buf_dispose(&buf); + + cl_git_pass(git_diff_commit_as_email( + &buf, repo, commit, 1, 1, opts->flags, NULL)); + cl_assert_equal_s(expected, buf.ptr); + + git_diff_free(diff); + git_commit_free(commit); + git_buf_dispose(&buf); +} +#endif + +void test_diff_format_email__simple(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; + const char *email = + "From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \ + "Subject: [PATCH] Modify some content\n" \ + "\n" \ + "---\n" \ + " file1.txt | 8 +++++---\n" \ + " 1 file changed, 5 insertions(+), 3 deletions(-)\n" \ + "\n" \ + "diff --git a/file1.txt b/file1.txt\n" \ + "index 94aaae8..af8f41d 100644\n" \ + "--- a/file1.txt\n" \ + "+++ b/file1.txt\n" \ + "@@ -1,15 +1,17 @@\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "+_file1.txt_\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "+\n" \ + "+\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "+_file1.txt_\n" \ + "+_file1.txt_\n" \ + " file1.txt\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + assert_email_match( + email, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); +#endif +} + +void test_diff_format_email__with_message(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; + const char *email = "From 627e7e12d87e07a83fad5b6bfa25e86ead4a5270 Mon Sep 17 00:00:00 2001\n" \ + "From: Patrick Steinhardt \n" \ + "Date: Tue, 24 Nov 2015 13:34:39 +0100\n" \ + "Subject: [PATCH] Modify content with message\n" \ + "\n" \ + "Modify content of file3.txt by appending a new line. Make this\n" \ + "commit message somewhat longer to test behavior with newlines\n" \ + "embedded in the message body.\n" \ + "\n" \ + "Also test if new paragraphs are included correctly.\n" \ + "---\n" \ + " file3.txt | 1 +\n" \ + " 1 file changed, 1 insertion(+)\n" \ + "\n" \ + "diff --git a/file3.txt b/file3.txt\n" \ + "index 9a2d780..7309653 100644\n" \ + "--- a/file3.txt\n" \ + "+++ b/file3.txt\n" \ + "@@ -3,3 +3,4 @@ file3!\n" \ + " file3\n" \ + " file3\n" \ + " file3\n" \ + "+file3\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + opts.body = "Modify content of file3.txt by appending a new line. Make this\n" \ + "commit message somewhat longer to test behavior with newlines\n" \ + "embedded in the message body.\n" \ + "\n" \ + "Also test if new paragraphs are included correctly."; + + assert_email_match( + email, "627e7e12d87e07a83fad5b6bfa25e86ead4a5270", &opts); +#endif +} + + +void test_diff_format_email__multiple(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_oid oid; + git_commit *commit = NULL; + git_diff *diff = NULL; + git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; + git_buf buf = GIT_BUF_INIT; + + const char *email = + "From 10808fe9c9be5a190c0ba68d1a002233fb363508 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Thu, 10 Apr 2014 19:37:05 +0200\n" \ + "Subject: [PATCH 1/2] Added file2.txt file3.txt\n" \ + "\n" \ + "---\n" \ + " file2.txt | 5 +++++\n" \ + " file3.txt | 5 +++++\n" \ + " 2 files changed, 10 insertions(+)\n" \ + " create mode 100644 file2.txt\n" \ + " create mode 100644 file3.txt\n" \ + "\n" \ + "diff --git a/file2.txt b/file2.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..e909123\n" \ + "--- /dev/null\n" \ + "+++ b/file2.txt\n" \ + "@@ -0,0 +1,5 @@\n" \ + "+file2\n" \ + "+file2\n" \ + "+file2\n" \ + "+file2\n" \ + "+file2\n" \ + "diff --git a/file3.txt b/file3.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..9435022\n" \ + "--- /dev/null\n" \ + "+++ b/file3.txt\n" \ + "@@ -0,0 +1,5 @@\n" \ + "+file3\n" \ + "+file3\n" \ + "+file3\n" \ + "+file3\n" \ + "+file3\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n" \ + "From 873806f6f27e631eb0b23e4b56bea2bfac14a373 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Thu, 10 Apr 2014 19:37:36 +0200\n" \ + "Subject: [PATCH 2/2] Modified file2.txt, file3.txt\n" \ + "\n" \ + "---\n" \ + " file2.txt | 2 +-\n" \ + " file3.txt | 2 +-\n" \ + " 2 files changed, 2 insertions(+), 2 deletions(-)\n" \ + "\n" \ + "diff --git a/file2.txt b/file2.txt\n" \ + "index e909123..7aff11d 100644\n" \ + "--- a/file2.txt\n" \ + "+++ b/file2.txt\n" \ + "@@ -1,5 +1,5 @@\n" \ + " file2\n" \ + " file2\n" \ + " file2\n" \ + "-file2\n" \ + "+file2!\n" \ + " file2\n" \ + "diff --git a/file3.txt b/file3.txt\n" \ + "index 9435022..9a2d780 100644\n" \ + "--- a/file3.txt\n" \ + "+++ b/file3.txt\n" \ + "@@ -1,5 +1,5 @@\n" \ + " file3\n" \ + "-file3\n" \ + "+file3!\n" \ + " file3\n" \ + " file3\n" \ + " file3\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + + git_oid_fromstr(&oid, "10808fe9c9be5a190c0ba68d1a002233fb363508"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + + opts.id = git_commit_id(commit); + opts.author = git_commit_author(commit); + opts.summary = git_commit_summary(commit); + opts.patch_no = 1; + opts.total_patches = 2; + + cl_git_pass(git_diff__commit(&diff, repo, commit, NULL)); + cl_git_pass(git_diff_format_email(&buf, diff, &opts)); + + git_diff_free(diff); + git_commit_free(commit); + diff = NULL; + commit = NULL; + + git_oid_fromstr(&oid, "873806f6f27e631eb0b23e4b56bea2bfac14a373"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + + opts.id = git_commit_id(commit); + opts.author = git_commit_author(commit); + opts.summary = git_commit_summary(commit); + opts.patch_no = 2; + opts.total_patches = 2; + + cl_git_pass(git_diff__commit(&diff, repo, commit, NULL)); + cl_git_pass(git_diff_format_email(&buf, diff, &opts)); + + cl_assert_equal_s(email, buf.ptr); + + git_diff_free(diff); + git_commit_free(commit); + git_buf_dispose(&buf); +#endif +} + +void test_diff_format_email__exclude_marker(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; + const char *email = + "From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \ + "Subject: Modify some content\n" \ + "\n" \ + "---\n" \ + " file1.txt | 8 +++++---\n" \ + " 1 file changed, 5 insertions(+), 3 deletions(-)\n" \ + "\n" \ + "diff --git a/file1.txt b/file1.txt\n" \ + "index 94aaae8..af8f41d 100644\n" \ + "--- a/file1.txt\n" \ + "+++ b/file1.txt\n" \ + "@@ -1,15 +1,17 @@\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "+_file1.txt_\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "+\n" \ + "+\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "+_file1.txt_\n" \ + "+_file1.txt_\n" \ + " file1.txt\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + opts.flags |= GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER; + + assert_email_match( + email, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); +#endif +} + +void test_diff_format_email__invalid_no(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_oid oid; + git_commit *commit = NULL; + git_diff *diff = NULL; + git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; + git_buf buf = GIT_BUF_INIT; + + git_oid_fromstr(&oid, "9264b96c6d104d0e07ae33d3007b6a48246c6f92"); + + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + + opts.id = git_commit_id(commit); + opts.author = git_commit_author(commit); + opts.summary = git_commit_summary(commit); + opts.patch_no = 2; + opts.total_patches = 1; + + cl_git_pass(git_diff__commit(&diff, repo, commit, NULL)); + cl_git_fail(git_diff_format_email(&buf, diff, &opts)); + cl_git_fail(git_diff_commit_as_email(&buf, repo, commit, 2, 1, 0, NULL)); + + git_diff_free(diff); + git_commit_free(commit); + git_buf_dispose(&buf); +#endif +} + +void test_diff_format_email__mode_change(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; + const char *email = + "From 7ade76dd34bba4733cf9878079f9fd4a456a9189 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Thu, 10 Apr 2014 10:05:03 +0200\n" \ + "Subject: [PATCH] Update permissions\n" \ + "\n" \ + "---\n" \ + " file1.txt.renamed | 0\n" \ + " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ + " mode change 100644 => 100755 file1.txt.renamed\n" \ + "\n" \ + "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \ + "old mode 100644\n" \ + "new mode 100755\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + assert_email_match( + email, "7ade76dd34bba4733cf9878079f9fd4a456a9189", &opts); +#endif +} + +void test_diff_format_email__rename_add_remove(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; + const char *email = + "From 6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Wed, 9 Apr 2014 21:15:56 +0200\n" \ + "Subject: [PATCH] Renamed file1.txt -> file1.txt.renamed\n" \ + "\n" \ + "---\n" \ + " file1.txt | 17 -----------------\n" \ + " file1.txt.renamed | 17 +++++++++++++++++\n" \ + " 2 files changed, 17 insertions(+), 17 deletions(-)\n" \ + " delete mode 100644 file1.txt\n" \ + " create mode 100644 file1.txt.renamed\n" \ + "\n" \ + "diff --git a/file1.txt b/file1.txt\n" \ + "deleted file mode 100644\n" \ + "index af8f41d..0000000\n" \ + "--- a/file1.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,17 +0,0 @@\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-_file1.txt_\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-\n" \ + "-\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-_file1.txt_\n" \ + "-_file1.txt_\n" \ + "-file1.txt\n" \ + "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \ + "new file mode 100644\n" \ + "index 0000000..a97157a\n" \ + "--- /dev/null\n" \ + "+++ b/file1.txt.renamed\n" \ + "@@ -0,0 +1,17 @@\n" \ + "+file1.txt\n" \ + "+file1.txt\n" \ + "+_file1.txt_\n" \ + "+file1.txt\n" \ + "+file1.txt\n" \ + "+file1.txt_renamed\n" \ + "+file1.txt\n" \ + "+\n" \ + "+\n" \ + "+file1.txt\n" \ + "+file1.txt\n" \ + "+file1.txt_renamed\n" \ + "+file1.txt\n" \ + "+file1.txt\n" \ + "+_file1.txt_\n" \ + "+_file1.txt_\n" \ + "+file1.txt\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + assert_email_match( + email, "6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d", &opts); +#endif +} + +void test_diff_format_email__multiline_summary(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; + const char *email = + "From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \ + "Subject: [PATCH] Modify some content\n" \ + "\n" \ + "---\n" \ + " file1.txt | 8 +++++---\n" \ + " 1 file changed, 5 insertions(+), 3 deletions(-)\n" \ + "\n" \ + "diff --git a/file1.txt b/file1.txt\n" \ + "index 94aaae8..af8f41d 100644\n" \ + "--- a/file1.txt\n" \ + "+++ b/file1.txt\n" \ + "@@ -1,15 +1,17 @@\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "+_file1.txt_\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "+\n" \ + "+\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "+_file1.txt_\n" \ + "+_file1.txt_\n" \ + " file1.txt\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + opts.summary = "Modify some content\nSome extra stuff here"; + + assert_email_match( + email, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); +#endif +} + +void test_diff_format_email__binary(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; + const char *email = + "From 8d7523f6fcb2404257889abe0d96f093d9f524f9 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Sun, 13 Apr 2014 18:10:18 +0200\n" \ + "Subject: [PATCH] Modified binary file\n" \ + "\n" \ + "---\n" \ + " binary.bin | Bin 3 -> 5 bytes\n" \ + " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ + "\n" \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index bd474b2..9ac35ff 100644\n" \ + "Binary files a/binary.bin and b/binary.bin differ\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + opts.summary = "Modified binary file"; + + assert_email_match( + email, "8d7523f6fcb2404257889abe0d96f093d9f524f9", &opts); +#endif +} + diff --git a/tests/libgit2/diff/index.c b/tests/libgit2/diff/index.c new file mode 100644 index 000000000..b616a372b --- /dev/null +++ b/tests/libgit2/diff/index.c @@ -0,0 +1,302 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_index__initialize(void) +{ + g_repo = cl_git_sandbox_init("status"); +} + +void test_diff_index__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_index__0(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + const char *b_commit = "0017bd4ab1ec3"; /* the start */ + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp; + + cl_assert(a); + cl_assert(b); + + opts.context_lines = 1; + opts.interhunk_lines = 1; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + /* to generate these values: + * - cd to tests/resources/status, + * - mv .gitted .git + * - git diff --name-status --cached 26a125ee1bf + * - git diff -U1 --cached 26a125ee1bf + * - mv .git .gitted + */ + cl_assert_equal_i(8, exp.files); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + + cl_assert_equal_i(8, exp.hunks); + + cl_assert_equal_i(11, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(6, exp.line_adds); + cl_assert_equal_i(2, exp.line_dels); + + git_diff_free(diff); + diff = NULL; + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + /* to generate these values: + * - cd to tests/resources/status, + * - mv .gitted .git + * - git diff --name-status --cached 0017bd4ab1ec3 + * - git diff -U1 --cached 0017bd4ab1ec3 + * - mv .git .gitted + */ + cl_assert_equal_i(12, exp.files); + cl_assert_equal_i(7, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + + cl_assert_equal_i(12, exp.hunks); + + cl_assert_equal_i(16, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(11, exp.line_adds); + cl_assert_equal_i(2, exp.line_dels); + + git_diff_free(diff); + diff = NULL; + + git_tree_free(a); + git_tree_free(b); +} + +static int diff_stop_after_2_files( + const git_diff_delta *delta, + float progress, + void *payload) +{ + diff_expects *e = payload; + + GIT_UNUSED(progress); + GIT_UNUSED(delta); + + e->files++; + + return (e->files == 2); +} + +void test_diff_index__1(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + const char *b_commit = "0017bd4ab1ec3"; /* the start */ + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp; + + cl_assert(a); + cl_assert(b); + + opts.context_lines = 1; + opts.interhunk_lines = 1; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); + + cl_assert_equal_i(1, git_diff_foreach( + diff, diff_stop_after_2_files, NULL, NULL, NULL, &exp) ); + + cl_assert_equal_i(2, exp.files); + + git_diff_free(diff); + diff = NULL; + + git_tree_free(a); + git_tree_free(b); +} + +void test_diff_index__checks_options_version(void) +{ + const char *a_commit = "26a125ee1bf"; + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + const git_error *err; + + opts.version = 0; + cl_git_fail(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); + err = git_error_last(); + cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); + cl_assert_equal_p(diff, NULL); + + git_error_clear(); + opts.version = 1024; + cl_git_fail(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); + err = git_error_last(); + cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); + cl_assert_equal_p(diff, NULL); + + git_tree_free(a); +} + +static void do_conflicted_diff(diff_expects *exp, unsigned long flags) +{ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_index_entry ancestor = {{0}}, ours = {{0}}, theirs = {{0}}; + git_diff *diff = NULL; + git_index *index; + + cl_assert(a); + + opts.context_lines = 1; + opts.interhunk_lines = 1; + opts.flags |= flags; + + memset(exp, 0, sizeof(diff_expects)); + + cl_git_pass(git_repository_index(&index, g_repo)); + + ancestor.path = ours.path = theirs.path = "staged_changes"; + ancestor.mode = ours.mode = theirs.mode = GIT_FILEMODE_BLOB; + + git_oid_fromstr(&ancestor.id, "d427e0b2e138501a3d15cc376077a3631e15bd46"); + git_oid_fromstr(&ours.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); + git_oid_fromstr(&theirs.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); + + cl_git_pass(git_index_conflict_add(index, &ancestor, &ours, &theirs)); + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, index, &opts)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, exp)); + + git_diff_free(diff); + git_tree_free(a); + git_index_free(index); +} + +void test_diff_index__reports_conflicts(void) +{ + diff_expects exp; + + do_conflicted_diff(&exp, 0); + + cl_assert_equal_i(8, exp.files); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_CONFLICTED]); + + cl_assert_equal_i(7, exp.hunks); + + cl_assert_equal_i(9, exp.lines); + cl_assert_equal_i(2, exp.line_ctxt); + cl_assert_equal_i(5, exp.line_adds); + cl_assert_equal_i(2, exp.line_dels); +} + +void test_diff_index__reports_conflicts_when_reversed(void) +{ + diff_expects exp; + + do_conflicted_diff(&exp, GIT_DIFF_REVERSE); + + cl_assert_equal_i(8, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_CONFLICTED]); + + cl_assert_equal_i(7, exp.hunks); + + cl_assert_equal_i(9, exp.lines); + cl_assert_equal_i(2, exp.line_ctxt); + cl_assert_equal_i(2, exp.line_adds); + cl_assert_equal_i(5, exp.line_dels); +} + +void test_diff_index__not_in_head_conflicted(void) +{ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + git_index_entry theirs = {{0}}; + git_index *index; + git_diff *diff; + const git_diff_delta *delta; + + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read_tree(index, a)); + + theirs.path = "file_not_in_head"; + theirs.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&theirs.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); + cl_git_pass(git_index_conflict_add(index, NULL, NULL, &theirs)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, index, NULL)); + + cl_assert_equal_i(git_diff_num_deltas(diff), 1); + delta = git_diff_get_delta(diff, 0); + cl_assert_equal_i(delta->status, GIT_DELTA_CONFLICTED); + + git_diff_free(diff); + git_index_free(index); + git_tree_free(a); +} + +void test_diff_index__to_index(void) +{ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + git_tree *old_tree; + git_index *old_index; + git_index *new_index; + git_diff *diff; + diff_expects exp; + + cl_git_pass(git_index_new(&old_index)); + old_tree = resolve_commit_oid_to_tree(g_repo, a_commit); + cl_git_pass(git_index_read_tree(old_index, old_tree)); + + cl_git_pass(git_repository_index(&new_index, g_repo)); + + cl_git_pass(git_diff_index_to_index(&diff, g_repo, old_index, new_index, NULL)); + + memset(&exp, 0, sizeof(diff_expects)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(8, exp.files); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_CONFLICTED]); + + git_diff_free(diff); + git_index_free(new_index); + git_index_free(old_index); + git_tree_free(old_tree); +} diff --git a/tests/libgit2/diff/notify.c b/tests/libgit2/diff/notify.c new file mode 100644 index 000000000..653512795 --- /dev/null +++ b/tests/libgit2/diff/notify.c @@ -0,0 +1,258 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_notify__initialize(void) +{ +} + +void test_diff_notify__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int assert_called_notifications( + const git_diff *diff_so_far, + const git_diff_delta *delta_to_add, + const char *matched_pathspec, + void *payload) +{ + bool found = false; + notify_expected *exp = (notify_expected*)payload; + notify_expected *e; + + GIT_UNUSED(diff_so_far); + + for (e = exp; e->path != NULL; e++) { + if (strcmp(e->path, delta_to_add->new_file.path)) + continue; + + cl_assert_equal_s(e->matched_pathspec, matched_pathspec); + + found = true; + break; + } + + cl_assert(found); + return 0; +} + +static void test_notify( + char **searched_pathspecs, + int pathspecs_count, + notify_expected *expected_matched_pathspecs, + int expected_diffed_files_count) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp; + + g_repo = cl_git_sandbox_init("status"); + + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + opts.notify_cb = assert_called_notifications; + opts.pathspec.strings = searched_pathspecs; + opts.pathspec.count = pathspecs_count; + + opts.payload = expected_matched_pathspecs; + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(expected_diffed_files_count, exp.files); + + git_diff_free(diff); +} + +void test_diff_notify__notify_single_pathspec(void) +{ + char *searched_pathspecs[] = { + "*_deleted", + }; + notify_expected expected_matched_pathspecs[] = { + { "file_deleted", "*_deleted" }, + { "staged_changes_file_deleted", "*_deleted" }, + { NULL, NULL } + }; + + test_notify(searched_pathspecs, 1, expected_matched_pathspecs, 2); +} + +void test_diff_notify__notify_multiple_pathspec(void) +{ + char *searched_pathspecs[] = { + "staged_changes_cant_find_me", + "subdir/modified_cant_find_me", + "subdir/*", + "staged*" + }; + notify_expected expected_matched_pathspecs[] = { + { "staged_changes_file_deleted", "staged*" }, + { "staged_changes_modified_file", "staged*" }, + { "staged_delete_modified_file", "staged*" }, + { "staged_new_file_deleted_file", "staged*" }, + { "staged_new_file_modified_file", "staged*" }, + { "subdir/deleted_file", "subdir/*" }, + { "subdir/modified_file", "subdir/*" }, + { "subdir/new_file", "subdir/*" }, + { NULL, NULL } + }; + + test_notify(searched_pathspecs, 4, expected_matched_pathspecs, 8); +} + +void test_diff_notify__notify_catchall_with_empty_pathspecs(void) +{ + char *searched_pathspecs[] = { + "", + "" + }; + notify_expected expected_matched_pathspecs[] = { + { "file_deleted", NULL }, + { "ignored_file", NULL }, + { "modified_file", NULL }, + { "new_file", NULL }, + { "\xe8\xbf\x99", NULL }, + { "staged_changes_file_deleted", NULL }, + { "staged_changes_modified_file", NULL }, + { "staged_delete_modified_file", NULL }, + { "staged_new_file_deleted_file", NULL }, + { "staged_new_file_modified_file", NULL }, + { "subdir/deleted_file", NULL }, + { "subdir/modified_file", NULL }, + { "subdir/new_file", NULL }, + { NULL, NULL } + }; + + test_notify(searched_pathspecs, 1, expected_matched_pathspecs, 13); +} + +void test_diff_notify__notify_catchall(void) +{ + char *searched_pathspecs[] = { + "*", + }; + notify_expected expected_matched_pathspecs[] = { + { "file_deleted", "*" }, + { "ignored_file", "*" }, + { "modified_file", "*" }, + { "new_file", "*" }, + { "\xe8\xbf\x99", "*" }, + { "staged_changes_file_deleted", "*" }, + { "staged_changes_modified_file", "*" }, + { "staged_delete_modified_file", "*" }, + { "staged_new_file_deleted_file", "*" }, + { "staged_new_file_modified_file", "*" }, + { "subdir/deleted_file", "*" }, + { "subdir/modified_file", "*" }, + { "subdir/new_file", "*" }, + { NULL, NULL } + }; + + test_notify(searched_pathspecs, 1, expected_matched_pathspecs, 13); +} + +static int abort_diff( + const git_diff *diff_so_far, + const git_diff_delta *delta_to_add, + const char *matched_pathspec, + void *payload) +{ + GIT_UNUSED(diff_so_far); + GIT_UNUSED(delta_to_add); + GIT_UNUSED(matched_pathspec); + GIT_UNUSED(payload); + + return -42; +} + +void test_diff_notify__notify_cb_can_abort_diff(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + char *pathspec = NULL; + + g_repo = cl_git_sandbox_init("status"); + + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + opts.notify_cb = abort_diff; + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 1; + + pathspec = "file_deleted"; + cl_git_fail_with( + git_diff_index_to_workdir(&diff, g_repo, NULL, &opts), -42); + + pathspec = "staged_changes_modified_file"; + cl_git_fail_with( + git_diff_index_to_workdir(&diff, g_repo, NULL, &opts), -42); +} + +static int filter_all( + const git_diff *diff_so_far, + const git_diff_delta *delta_to_add, + const char *matched_pathspec, + void *payload) +{ + GIT_UNUSED(diff_so_far); + GIT_UNUSED(delta_to_add); + GIT_UNUSED(matched_pathspec); + GIT_UNUSED(payload); + + return 42; +} + +void test_diff_notify__notify_cb_can_be_used_as_filtering_function(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + char *pathspec = NULL; + diff_expects exp; + + g_repo = cl_git_sandbox_init("status"); + + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + opts.notify_cb = filter_all; + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 1; + + pathspec = "*_deleted"; + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(0, exp.files); + + git_diff_free(diff); +} + +static int progress_abort_diff( + const git_diff *diff_so_far, + const char *old_path, + const char *new_path, + void *payload) +{ + GIT_UNUSED(diff_so_far); + GIT_UNUSED(old_path); + GIT_UNUSED(new_path); + GIT_UNUSED(payload); + + return -42; +} + +void test_diff_notify__progress_cb_can_abort_diff(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + + g_repo = cl_git_sandbox_init("status"); + + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + opts.progress_cb = progress_abort_diff; + + cl_git_fail_with( + git_diff_index_to_workdir(&diff, g_repo, NULL, &opts), -42); +} diff --git a/tests/libgit2/diff/parse.c b/tests/libgit2/diff/parse.c new file mode 100644 index 000000000..d3a0c8de6 --- /dev/null +++ b/tests/libgit2/diff/parse.c @@ -0,0 +1,450 @@ +#include "clar_libgit2.h" +#include "patch.h" +#include "patch_parse.h" +#include "diff_helpers.h" + +#include "../patch/patch_common.h" + +void test_diff_parse__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_parse__nonpatches_fail_with_notfound(void) +{ + git_diff *diff; + const char *not = PATCH_NOT_A_PATCH; + const char *not_with_leading = "Leading text.\n" PATCH_NOT_A_PATCH; + const char *not_with_trailing = PATCH_NOT_A_PATCH "Trailing text.\n"; + const char *not_with_both = "Lead.\n" PATCH_NOT_A_PATCH "Trail.\n"; + + cl_git_fail_with(GIT_ENOTFOUND, + git_diff_from_buffer(&diff, + not, + strlen(not))); + cl_git_fail_with(GIT_ENOTFOUND, + git_diff_from_buffer(&diff, + not_with_leading, + strlen(not_with_leading))); + cl_git_fail_with(GIT_ENOTFOUND, + git_diff_from_buffer(&diff, + not_with_trailing, + strlen(not_with_trailing))); + cl_git_fail_with(GIT_ENOTFOUND, + git_diff_from_buffer(&diff, + not_with_both, + strlen(not_with_both))); +} + +static void test_parse_invalid_diff(const char *invalid_diff) +{ + git_diff *diff; + git_str buf = GIT_STR_INIT; + + /* throw some random (legitimate) diffs in with the given invalid + * one. + */ + git_str_puts(&buf, PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE); + git_str_puts(&buf, PATCH_BINARY_DELTA); + git_str_puts(&buf, invalid_diff); + git_str_puts(&buf, PATCH_ORIGINAL_TO_CHANGE_MIDDLE); + git_str_puts(&buf, PATCH_BINARY_LITERAL); + + cl_git_fail_with(GIT_ERROR, + git_diff_from_buffer(&diff, buf.ptr, buf.size)); + + git_str_dispose(&buf); +} + +void test_diff_parse__exact_rename(void) +{ + const char *content = + "---\n" + " old_name.c => new_name.c | 0\n" + " 1 file changed, 0 insertions(+), 0 deletions(-)\n" + " rename old_name.c => new_name.c (100%)\n" + "\n" + "diff --git a/old_name.c b/new_name.c\n" + "similarity index 100%\n" + "rename from old_name.c\n" + "rename to new_name.c\n" + "-- \n" + "2.9.3\n"; + git_diff *diff; + + cl_git_pass(git_diff_from_buffer( + &diff, content, strlen(content))); + git_diff_free(diff); +} + +void test_diff_parse__empty_file(void) +{ + const char *content = + "---\n" + " file | 0\n" + " 1 file changed, 0 insertions(+), 0 deletions(-)\n" + " created mode 100644 file\n" + "\n" + "diff --git a/file b/file\n" + "new file mode 100644\n" + "index 0000000..e69de29\n" + "-- \n" + "2.20.1\n"; + git_diff *diff; + + cl_git_pass(git_diff_from_buffer( + &diff, content, strlen(content))); + git_diff_free(diff); +} + +void test_diff_parse__no_extended_headers(void) +{ + const char *content = PATCH_NO_EXTENDED_HEADERS; + git_diff *diff; + + cl_git_pass(git_diff_from_buffer( + &diff, content, strlen(content))); + git_diff_free(diff); +} + +void test_diff_parse__add_delete_no_index(void) +{ + const char *content = + "diff --git a/file.txt b/file.txt\n" + "new file mode 100644\n" + "--- /dev/null\n" + "+++ b/file.txt\n" + "@@ -0,0 +1,2 @@\n" + "+one\n" + "+two\n" + "diff --git a/otherfile.txt b/otherfile.txt\n" + "deleted file mode 100644\n" + "--- a/otherfile.txt\n" + "+++ /dev/null\n" + "@@ -1,1 +0,0 @@\n" + "-three\n"; + git_diff *diff; + + cl_git_pass(git_diff_from_buffer( + &diff, content, strlen(content))); + git_diff_free(diff); +} + +void test_diff_parse__invalid_patches_fails(void) +{ + test_parse_invalid_diff(PATCH_CORRUPT_MISSING_NEW_FILE); + test_parse_invalid_diff(PATCH_CORRUPT_MISSING_OLD_FILE); + test_parse_invalid_diff(PATCH_CORRUPT_NO_CHANGES); + test_parse_invalid_diff(PATCH_CORRUPT_MISSING_HUNK_HEADER); +} + +static void test_tree_to_tree_computed_to_parsed( + const char *sandbox, const char *a_id, const char *b_id, + uint32_t diff_flags, uint32_t find_flags) +{ + git_repository *repo; + git_diff *computed, *parsed; + git_tree *a, *b; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + git_buf computed_buf = GIT_BUF_INIT; + + repo = cl_git_sandbox_init(sandbox); + + opts.id_abbrev = GIT_OID_HEXSZ; + opts.flags = GIT_DIFF_SHOW_BINARY | diff_flags; + findopts.flags = find_flags; + + cl_assert((a = resolve_commit_oid_to_tree(repo, a_id)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(repo, b_id)) != NULL); + + cl_git_pass(git_diff_tree_to_tree(&computed, repo, a, b, &opts)); + + if (find_flags) + cl_git_pass(git_diff_find_similar(computed, &findopts)); + + cl_git_pass(git_diff_to_buf(&computed_buf, + computed, GIT_DIFF_FORMAT_PATCH)); + + cl_git_pass(git_diff_from_buffer(&parsed, + computed_buf.ptr, computed_buf.size)); + + diff_assert_equal(computed, parsed); + + git_tree_free(a); + git_tree_free(b); + + git_diff_free(computed); + git_diff_free(parsed); + + git_buf_dispose(&computed_buf); + + cl_git_sandbox_cleanup(); +} + +void test_diff_parse__can_parse_generated_diff(void) +{ + test_tree_to_tree_computed_to_parsed( + "diff", "d70d245e", "7a9e0b02", 0, 0); + test_tree_to_tree_computed_to_parsed( + "unsymlinked.git", "806999", "a8595c", 0, 0); + test_tree_to_tree_computed_to_parsed("diff", + "d70d245ed97ed2aa596dd1af6536e4bfdb047b69", + "7a9e0b02e63179929fed24f0a3e0f19168114d10", 0, 0); + test_tree_to_tree_computed_to_parsed( + "unsymlinked.git", "7fccd7", "806999", 0, 0); + test_tree_to_tree_computed_to_parsed( + "unsymlinked.git", "7fccd7", "a8595c", 0, 0); + test_tree_to_tree_computed_to_parsed( + "attr", "605812a", "370fe9ec22", 0, 0); + test_tree_to_tree_computed_to_parsed( + "attr", "f5b0af1fb4f5c", "370fe9ec22", 0, 0); + test_tree_to_tree_computed_to_parsed( + "diff", "d70d245e", "d70d245e", 0, 0); + test_tree_to_tree_computed_to_parsed("diff_format_email", + "873806f6f27e631eb0b23e4b56bea2bfac14a373", + "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", + GIT_DIFF_SHOW_BINARY, 0); + test_tree_to_tree_computed_to_parsed("diff_format_email", + "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", + "873806f6f27e631eb0b23e4b56bea2bfac14a373", + GIT_DIFF_SHOW_BINARY, 0); + test_tree_to_tree_computed_to_parsed("renames", + "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2", + "2bc7f351d20b53f1c72c16c4b036e491c478c49a", + 0, GIT_DIFF_FIND_RENAMES); + test_tree_to_tree_computed_to_parsed("renames", + "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2", + "2bc7f351d20b53f1c72c16c4b036e491c478c49a", + GIT_DIFF_INCLUDE_UNMODIFIED, + 0); + test_tree_to_tree_computed_to_parsed("renames", + "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2", + "2bc7f351d20b53f1c72c16c4b036e491c478c49a", + GIT_DIFF_INCLUDE_UNMODIFIED, + GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED | GIT_DIFF_FIND_EXACT_MATCH_ONLY); +} + +void test_diff_parse__get_patch_from_diff(void) +{ + git_repository *repo; + git_diff *computed, *parsed; + git_tree *a, *b; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_buf computed_buf = GIT_BUF_INIT; + git_patch *patch_computed, *patch_parsed; + + repo = cl_git_sandbox_init("diff"); + + opts.flags = GIT_DIFF_SHOW_BINARY; + + cl_assert((a = resolve_commit_oid_to_tree(repo, + "d70d245ed97ed2aa596dd1af6536e4bfdb047b69")) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(repo, + "7a9e0b02e63179929fed24f0a3e0f19168114d10")) != NULL); + + cl_git_pass(git_diff_tree_to_tree(&computed, repo, a, b, &opts)); + cl_git_pass(git_diff_to_buf(&computed_buf, + computed, GIT_DIFF_FORMAT_PATCH)); + cl_git_pass(git_patch_from_diff(&patch_computed, computed, 0)); + + cl_git_pass(git_diff_from_buffer(&parsed, + computed_buf.ptr, computed_buf.size)); + cl_git_pass(git_patch_from_diff(&patch_parsed, parsed, 0)); + + cl_assert_equal_i( + git_patch_num_hunks(patch_computed), + git_patch_num_hunks(patch_parsed)); + + git_patch_free(patch_computed); + git_patch_free(patch_parsed); + + git_tree_free(a); + git_tree_free(b); + + git_diff_free(computed); + git_diff_free(parsed); + + git_buf_dispose(&computed_buf); + + cl_git_sandbox_cleanup(); +} + +static int file_cb(const git_diff_delta *delta, float progress, void *payload) +{ + int *called = (int *) payload; + GIT_UNUSED(delta); + GIT_UNUSED(progress); + (*called)++; + return 0; +} + +void test_diff_parse__foreach_works_with_parsed_patch(void) +{ + const char patch[] = + "diff --git a/obj1 b/obj2\n" + "index 1234567..7654321 10644\n" + "--- a/obj1\n" + "+++ b/obj2\n" + "@@ -1 +1 @@\n" + "-abcde\n" + "+12345\n"; + int called = 0; + git_diff *diff; + + cl_git_pass(git_diff_from_buffer(&diff, patch, strlen(patch))); + cl_git_pass(git_diff_foreach(diff, file_cb, NULL, NULL, NULL, &called)); + cl_assert_equal_i(called, 1); + + git_diff_free(diff); +} + +void test_diff_parse__parsing_minimal_patch_succeeds(void) +{ + const char patch[] = + "diff --git a/obj1 b/obj2\n" + "index 1234567..7654321 10644\n" + "--- a/obj1\n" + "+++ b/obj2\n" + "@@ -1 +1 @@\n" + "-a\n" + "+\n"; + git_buf buf = GIT_BUF_INIT; + git_diff *diff; + + cl_git_pass(git_diff_from_buffer(&diff, patch, strlen(patch))); + cl_git_pass(git_diff_to_buf(&buf, diff, GIT_DIFF_FORMAT_PATCH)); + cl_assert_equal_s(patch, buf.ptr); + + git_diff_free(diff); + git_buf_dispose(&buf); +} + +void test_diff_parse__patch_roundtrip_succeeds(void) +{ + const char buf1[] = "a\n", buf2[] = "b\n"; + git_buf patchbuf = GIT_BUF_INIT, diffbuf = GIT_BUF_INIT; + git_patch *patch; + git_diff *diff; + + cl_git_pass(git_patch_from_buffers(&patch, buf1, strlen(buf1), "obj1", buf2, strlen(buf2), "obj2", NULL)); + cl_git_pass(git_patch_to_buf(&patchbuf, patch)); + + cl_git_pass(git_diff_from_buffer(&diff, patchbuf.ptr, patchbuf.size)); + cl_git_pass(git_diff_to_buf(&diffbuf, diff, GIT_DIFF_FORMAT_PATCH)); + + cl_assert_equal_s(patchbuf.ptr, diffbuf.ptr); + + git_patch_free(patch); + git_diff_free(diff); + git_buf_dispose(&patchbuf); + git_buf_dispose(&diffbuf); +} + +#define cl_assert_equal_i_src(i1,i2,file,func,line) clar__assert_equal(file,func,line,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) + +static void cl_git_assert_lineinfo_(int old_lineno, int new_lineno, int num_lines, git_patch *patch, size_t hunk_idx, size_t line_idx, const char *file, const char *func, int lineno) +{ + const git_diff_line *line; + + cl_git_expect(git_patch_get_line_in_hunk(&line, patch, hunk_idx, line_idx), 0, file, func, lineno); + cl_assert_equal_i_src(old_lineno, line->old_lineno, file, func, lineno); + cl_assert_equal_i_src(new_lineno, line->new_lineno, file, func, lineno); + cl_assert_equal_i_src(num_lines, line->num_lines, file, func, lineno); +} + +#define cl_git_assert_lineinfo(old, new, num, p, h, l) \ + cl_git_assert_lineinfo_(old,new,num,p,h,l,__FILE__,__func__,__LINE__) + + +void test_diff_parse__issue4672(void) +{ + const char *text = "diff --git a/a b/a\n" + "index 7f129fd..af431f2 100644\n" + "--- a/a\n" + "+++ b/a\n" + "@@ -3 +3 @@\n" + "-a contents 2\n" + "+a contents\n"; + + git_diff *diff; + git_patch *patch; + const git_diff_hunk *hunk; + size_t n, l = 0; + + cl_git_pass(git_diff_from_buffer(&diff, text, strlen(text))); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_get_hunk(&hunk, &n, patch, 0)); + + cl_git_assert_lineinfo(3, -1, 1, patch, 0, l++); + cl_git_assert_lineinfo(-1, 3, 1, patch, 0, l++); + + cl_assert_equal_i(n, l); + + git_patch_free(patch); + git_diff_free(diff); +} + +void test_diff_parse__lineinfo(void) +{ + const char *text = PATCH_ORIGINAL_TO_CHANGE_MIDDLE; + git_diff *diff; + git_patch *patch; + const git_diff_hunk *hunk; + size_t n, l = 0; + + cl_git_pass(git_diff_from_buffer(&diff, text, strlen(text))); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_get_hunk(&hunk, &n, patch, 0)); + + cl_git_assert_lineinfo(3, 3, 1, patch, 0, l++); + cl_git_assert_lineinfo(4, 4, 1, patch, 0, l++); + cl_git_assert_lineinfo(5, 5, 1, patch, 0, l++); + cl_git_assert_lineinfo(6, -1, 1, patch, 0, l++); + cl_git_assert_lineinfo(-1, 6, 1, patch, 0, l++); + cl_git_assert_lineinfo(7, 7, 1, patch, 0, l++); + cl_git_assert_lineinfo(8, 8, 1, patch, 0, l++); + cl_git_assert_lineinfo(9, 9, 1, patch, 0, l++); + + cl_assert_equal_i(n, l); + + git_patch_free(patch); + git_diff_free(diff); +} + + +void test_diff_parse__new_file_with_space(void) +{ + const char *content = PATCH_ORIGINAL_NEW_FILE_WITH_SPACE; + git_patch *patch; + git_diff *diff; + + cl_git_pass(git_diff_from_buffer(&diff, content, strlen(content))); + cl_git_pass(git_patch_from_diff((git_patch **) &patch, diff, 0)); + + cl_assert_equal_p(patch->diff_opts.old_prefix, NULL); + cl_assert_equal_p(patch->delta->old_file.path, NULL); + cl_assert_equal_s(patch->diff_opts.new_prefix, "b/"); + cl_assert_equal_s(patch->delta->new_file.path, "sp ace.txt"); + + git_patch_free(patch); + git_diff_free(diff); +} + +void test_diff_parse__crlf(void) +{ + const char *text = PATCH_CRLF; + git_diff *diff; + git_patch *patch; + const git_diff_delta *delta; + + cl_git_pass(git_diff_from_buffer(&diff, text, strlen(text))); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + delta = git_patch_get_delta(patch); + + cl_assert_equal_s(delta->old_file.path, "test-file"); + cl_assert_equal_s(delta->new_file.path, "test-file"); + + git_patch_free(patch); + git_diff_free(diff); +} diff --git a/tests/libgit2/diff/patch.c b/tests/libgit2/diff/patch.c new file mode 100644 index 000000000..8945afc26 --- /dev/null +++ b/tests/libgit2/diff/patch.c @@ -0,0 +1,703 @@ +#include "clar_libgit2.h" +#include "git2/sys/repository.h" + +#include "diff_helpers.h" +#include "diff.h" +#include "repository.h" + +static git_repository *g_repo = NULL; + +void test_diff_patch__initialize(void) +{ +} + +void test_diff_patch__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +#define EXPECTED_HEADER "diff --git a/subdir.txt b/subdir.txt\n" \ + "deleted file mode 100644\n" \ + "index e8ee89e..0000000\n" \ + "--- a/subdir.txt\n" \ + "+++ /dev/null\n" + +#define EXPECTED_HUNK "@@ -1,2 +0,0 @@\n" + +#define UTF8_HUNK_HEADER "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\n" + +#define UTF8_TRUNCATED_A_HUNK_HEADER "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\n" + +#define UTF8_TRUNCATED_L_HUNK_HEADER "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\xE6\x97\xA5\n" + +static int check_removal_cb( + const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload) +{ + switch (line->origin) { + case GIT_DIFF_LINE_FILE_HDR: + cl_assert_equal_s(EXPECTED_HEADER, line->content); + cl_assert(hunk == NULL); + goto check_delta; + + case GIT_DIFF_LINE_HUNK_HDR: + cl_assert_equal_s(EXPECTED_HUNK, line->content); + goto check_hunk; + + case GIT_DIFF_LINE_CONTEXT: + case GIT_DIFF_LINE_DELETION: + if (payload != NULL) + return *(int *)payload; + goto check_hunk; + + default: + /* unexpected code path */ + return -1; + } + +check_hunk: + cl_assert(hunk != NULL); + cl_assert_equal_i(1, hunk->old_start); + cl_assert_equal_i(2, hunk->old_lines); + cl_assert_equal_i(0, hunk->new_start); + cl_assert_equal_i(0, hunk->new_lines); + +check_delta: + cl_assert_equal_s("subdir.txt", delta->old_file.path); + cl_assert_equal_s("subdir.txt", delta->new_file.path); + cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); + + return 0; +} + +void test_diff_patch__can_properly_display_the_removal_of_a_file(void) +{ + /* + * $ git diff 26a125e..735b6a2 + * diff --git a/subdir.txt b/subdir.txt + * deleted file mode 100644 + * index e8ee89e..0000000 + * --- a/subdir.txt + * +++ /dev/null + * @@ -1,2 +0,0 @@ + * -Is it a bird? + * -Is it a plane? + */ + + const char *one_sha = "26a125e"; + const char *another_sha = "735b6a2"; + git_tree *one, *another; + git_diff *diff; + + g_repo = cl_git_sandbox_init("status"); + + one = resolve_commit_oid_to_tree(g_repo, one_sha); + another = resolve_commit_oid_to_tree(g_repo, another_sha); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL)); + + cl_git_pass(git_diff_print( + diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, NULL)); + + git_diff_free(diff); + + git_tree_free(another); + git_tree_free(one); +} + +void test_diff_patch__can_cancel_diff_print(void) +{ + const char *one_sha = "26a125e"; + const char *another_sha = "735b6a2"; + git_tree *one, *another; + git_diff *diff; + int fail_with; + + g_repo = cl_git_sandbox_init("status"); + + one = resolve_commit_oid_to_tree(g_repo, one_sha); + another = resolve_commit_oid_to_tree(g_repo, another_sha); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL)); + + fail_with = -2323; + + cl_git_fail_with(git_diff_print( + diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, &fail_with), + fail_with); + + fail_with = 45; + + cl_git_fail_with(git_diff_print( + diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, &fail_with), + fail_with); + + git_diff_free(diff); + + git_tree_free(another); + git_tree_free(one); +} + +void test_diff_patch__to_string(void) +{ + const char *one_sha = "26a125e"; + const char *another_sha = "735b6a2"; + git_tree *one, *another; + git_diff *diff; + git_patch *patch; + git_buf buf = GIT_BUF_INIT; + const char *expected = "diff --git a/subdir.txt b/subdir.txt\ndeleted file mode 100644\nindex e8ee89e..0000000\n--- a/subdir.txt\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-Is it a bird?\n-Is it a plane?\n"; + + g_repo = cl_git_sandbox_init("status"); + + one = resolve_commit_oid_to_tree(g_repo, one_sha); + another = resolve_commit_oid_to_tree(g_repo, another_sha); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL)); + + cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); + + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + + cl_git_pass(git_patch_to_buf(&buf, patch)); + + cl_assert_equal_s(expected, buf.ptr); + + cl_assert_equal_sz(31, git_patch_size(patch, 0, 0, 0)); + cl_assert_equal_sz(31, git_patch_size(patch, 1, 0, 0)); + cl_assert_equal_sz(31 + 16, git_patch_size(patch, 1, 1, 0)); + cl_assert_equal_sz(strlen(expected), git_patch_size(patch, 1, 1, 1)); + + git_buf_dispose(&buf); + git_patch_free(patch); + git_diff_free(diff); + git_tree_free(another); + git_tree_free(one); +} + +void test_diff_patch__config_options(void) +{ + const char *one_sha = "26a125e"; /* current HEAD */ + git_tree *one; + git_config *cfg; + git_diff *diff; + git_patch *patch; + git_buf buf = GIT_BUF_INIT; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + char *onefile = "staged_changes_modified_file"; + const char *expected1 = "diff --git c/staged_changes_modified_file i/staged_changes_modified_file\nindex 70bd944..906ee77 100644\n--- c/staged_changes_modified_file\n+++ i/staged_changes_modified_file\n@@ -1 +1,2 @@\n staged_changes_modified_file\n+staged_changes_modified_file\n"; + const char *expected2 = "diff --git i/staged_changes_modified_file w/staged_changes_modified_file\nindex 906ee77..011c344 100644\n--- i/staged_changes_modified_file\n+++ w/staged_changes_modified_file\n@@ -1,2 +1,3 @@\n staged_changes_modified_file\n staged_changes_modified_file\n+staged_changes_modified_file\n"; + const char *expected3 = "diff --git staged_changes_modified_file staged_changes_modified_file\nindex 906ee77..011c344 100644\n--- staged_changes_modified_file\n+++ staged_changes_modified_file\n@@ -1,2 +1,3 @@\n staged_changes_modified_file\n staged_changes_modified_file\n+staged_changes_modified_file\n"; + const char *expected4 = "diff --git staged_changes_modified_file staged_changes_modified_file\nindex 70bd9443ada0..906ee7711f4f 100644\n--- staged_changes_modified_file\n+++ staged_changes_modified_file\n@@ -1 +1,2 @@\n staged_changes_modified_file\n+staged_changes_modified_file\n"; + + g_repo = cl_git_sandbox_init("status"); + cl_git_pass(git_repository_config(&cfg, g_repo)); + one = resolve_commit_oid_to_tree(g_repo, one_sha); + opts.pathspec.count = 1; + opts.pathspec.strings = &onefile; + + + cl_git_pass(git_config_set_string(cfg, "diff.mnemonicprefix", "true")); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, one, NULL, &opts)); + + cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + cl_assert_equal_s(expected1, buf.ptr); + + git_buf_dispose(&buf); + git_patch_free(patch); + git_diff_free(diff); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + cl_assert_equal_s(expected2, buf.ptr); + + git_buf_dispose(&buf); + git_patch_free(patch); + git_diff_free(diff); + + + cl_git_pass(git_config_set_string(cfg, "diff.noprefix", "true")); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + cl_assert_equal_s(expected3, buf.ptr); + + git_buf_dispose(&buf); + git_patch_free(patch); + git_diff_free(diff); + + + cl_git_pass(git_config_set_int32(cfg, "core.abbrev", 12)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, one, NULL, &opts)); + + cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + cl_assert_equal_s(expected4, buf.ptr); + + git_buf_dispose(&buf); + git_patch_free(patch); + git_diff_free(diff); + + git_buf_dispose(&buf); + git_tree_free(one); + git_config_free(cfg); +} + +void test_diff_patch__hunks_have_correct_line_numbers(void) +{ + git_config *cfg; + git_tree *head; + git_diff_options opt = GIT_DIFF_OPTIONS_INIT; + git_diff *diff; + git_patch *patch; + const git_diff_delta *delta; + const git_diff_hunk *hunk; + const git_diff_line *line; + size_t hunklen; + git_str old_content = GIT_STR_INIT, actual = GIT_STR_INIT; + const char *new_content = "The Song of Seven Cities\n------------------------\n\nI WAS Lord of Cities very sumptuously builded.\nSeven roaring Cities paid me tribute from afar.\nIvory their outposts were--the guardrooms of them gilded,\nAnd garrisoned with Amazons invincible in war.\n\nThis is some new text;\nNot as good as the old text;\nBut here it is.\n\nSo they warred and trafficked only yesterday, my Cities.\nTo-day there is no mark or mound of where my Cities stood.\nFor the River rose at midnight and it washed away my Cities.\nThey are evened with Atlantis and the towns before the Flood.\n\nRain on rain-gorged channels raised the water-levels round them,\nFreshet backed on freshet swelled and swept their world from sight,\nTill the emboldened floods linked arms and, flashing forward, drowned them--\nDrowned my Seven Cities and their peoples in one night!\n\nLow among the alders lie their derelict foundations,\nThe beams wherein they trusted and the plinths whereon they built--\nMy rulers and their treasure and their unborn populations,\nDead, destroyed, aborted, and defiled with mud and silt!\n\nAnother replacement;\nBreaking up the poem;\nGenerating some hunks.\n\nTo the sound of trumpets shall their seed restore my Cities\nWealthy and well-weaponed, that once more may I behold\nAll the world go softly when it walks before my Cities,\nAnd the horses and the chariots fleeing from them as of old!\n\n -- Rudyard Kipling\n"; + + g_repo = cl_git_sandbox_init("renames"); + + cl_git_pass(git_config_new(&cfg)); + git_repository_set_config(g_repo, cfg); + git_config_free(cfg); + + git_repository_reinit_filesystem(g_repo, false); + + cl_git_pass( + git_futils_readbuffer(&old_content, "renames/songof7cities.txt")); + + cl_git_rewritefile("renames/songof7cities.txt", new_content); + + cl_git_pass(git_repository_head_tree(&head, g_repo)); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, head, &opt)); + + cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); + + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_assert((delta = git_patch_get_delta(patch)) != NULL); + + cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); + cl_assert_equal_i(2, (int)git_patch_num_hunks(patch)); + + /* check hunk 0 */ + + cl_git_pass( + git_patch_get_hunk(&hunk, &hunklen, patch, 0)); + + cl_assert_equal_i(18, (int)hunklen); + + cl_assert_equal_i(6, (int)hunk->old_start); + cl_assert_equal_i(15, (int)hunk->old_lines); + cl_assert_equal_i(6, (int)hunk->new_start); + cl_assert_equal_i(9, (int)hunk->new_lines); + + cl_assert_equal_i(18, (int)git_patch_num_lines_in_hunk(patch, 0)); + + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 0)); + cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin); + cl_git_pass(git_str_set(&actual, line->content, line->content_len)); + cl_assert_equal_s("Ivory their outposts were--the guardrooms of them gilded,\n", actual.ptr); + cl_assert_equal_i(6, line->old_lineno); + cl_assert_equal_i(6, line->new_lineno); + cl_assert_equal_i(-1, line->content_offset); + + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 3)); + cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin); + cl_git_pass(git_str_set(&actual, line->content, line->content_len)); + cl_assert_equal_s("All the world went softly when it walked before my Cities--\n", actual.ptr); + cl_assert_equal_i(9, line->old_lineno); + cl_assert_equal_i(-1, line->new_lineno); + cl_assert_equal_i(252, line->content_offset); + + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 12)); + cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin); + cl_git_pass(git_str_set(&actual, line->content, line->content_len)); + cl_assert_equal_s("This is some new text;\n", actual.ptr); + cl_assert_equal_i(-1, line->old_lineno); + cl_assert_equal_i(9, line->new_lineno); + cl_assert_equal_i(252, line->content_offset); + + /* check hunk 1 */ + + cl_git_pass(git_patch_get_hunk(&hunk, &hunklen, patch, 1)); + + cl_assert_equal_i(18, (int)hunklen); + + cl_assert_equal_i(31, (int)hunk->old_start); + cl_assert_equal_i(15, (int)hunk->old_lines); + cl_assert_equal_i(25, (int)hunk->new_start); + cl_assert_equal_i(9, (int)hunk->new_lines); + + cl_assert_equal_i(18, (int)git_patch_num_lines_in_hunk(patch, 1)); + + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 0)); + cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin); + cl_git_pass(git_str_set(&actual, line->content, line->content_len)); + cl_assert_equal_s("My rulers and their treasure and their unborn populations,\n", actual.ptr); + cl_assert_equal_i(31, line->old_lineno); + cl_assert_equal_i(25, line->new_lineno); + cl_assert_equal_i(-1, line->content_offset); + + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 3)); + cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin); + cl_git_pass(git_str_set(&actual, line->content, line->content_len)); + cl_assert_equal_s("The Daughters of the Palace whom they cherished in my Cities,\n", actual.ptr); + cl_assert_equal_i(34, line->old_lineno); + cl_assert_equal_i(-1, line->new_lineno); + cl_assert_equal_i(1468, line->content_offset); + + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 12)); + cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin); + cl_git_pass(git_str_set(&actual, line->content, line->content_len)); + cl_assert_equal_s("Another replacement;\n", actual.ptr); + cl_assert_equal_i(-1, line->old_lineno); + cl_assert_equal_i(28, line->new_lineno); + cl_assert_equal_i(1066, line->content_offset); + + git_patch_free(patch); + git_diff_free(diff); + + /* Let's check line numbers when there is no newline */ + + git_str_rtrim(&old_content); + cl_git_rewritefile("renames/songof7cities.txt", old_content.ptr); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, head, &opt)); + + cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); + + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_assert((delta = git_patch_get_delta(patch)) != NULL); + + cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); + cl_assert_equal_i(1, (int)git_patch_num_hunks(patch)); + + /* check hunk 0 */ + + cl_git_pass(git_patch_get_hunk(&hunk, &hunklen, patch, 0)); + + cl_assert_equal_i(6, (int)hunklen); + + cl_assert_equal_i(46, (int)hunk->old_start); + cl_assert_equal_i(4, (int)hunk->old_lines); + cl_assert_equal_i(46, (int)hunk->new_start); + cl_assert_equal_i(4, (int)hunk->new_lines); + + cl_assert_equal_i(6, (int)git_patch_num_lines_in_hunk(patch, 0)); + + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 1)); + cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin); + cl_git_pass(git_str_set(&actual, line->content, line->content_len)); + cl_assert_equal_s("And the horses and the chariots fleeing from them as of old!\n", actual.ptr); + cl_assert_equal_i(47, line->old_lineno); + cl_assert_equal_i(47, line->new_lineno); + + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 2)); + cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin); + cl_git_pass(git_str_set(&actual, line->content, line->content_len)); + cl_assert_equal_s("\n", actual.ptr); + cl_assert_equal_i(48, line->old_lineno); + cl_assert_equal_i(48, line->new_lineno); + + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 3)); + cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin); + cl_git_pass(git_str_set(&actual, line->content, line->content_len)); + cl_assert_equal_s(" -- Rudyard Kipling\n", actual.ptr); + cl_assert_equal_i(49, line->old_lineno); + cl_assert_equal_i(-1, line->new_lineno); + + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 4)); + cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin); + cl_git_pass(git_str_set(&actual, line->content, line->content_len)); + cl_assert_equal_s(" -- Rudyard Kipling", actual.ptr); + cl_assert_equal_i(-1, line->old_lineno); + cl_assert_equal_i(49, line->new_lineno); + + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 5)); + cl_assert_equal_i(GIT_DIFF_LINE_DEL_EOFNL, (int)line->origin); + cl_git_pass(git_str_set(&actual, line->content, line->content_len)); + cl_assert_equal_s("\n\\ No newline at end of file\n", actual.ptr); + cl_assert_equal_i(-1, line->old_lineno); + cl_assert_equal_i(49, line->new_lineno); + + git_patch_free(patch); + git_diff_free(diff); + + git_str_dispose(&actual); + git_str_dispose(&old_content); + git_tree_free(head); +} + +static void check_single_patch_stats( + git_repository *repo, size_t hunks, + size_t adds, size_t dels, size_t ctxt, size_t *sizes, + const char *expected) +{ + git_diff *diff; + git_patch *patch; + const git_diff_delta *delta; + size_t actual_ctxt, actual_adds, actual_dels; + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL)); + + cl_assert_equal_i(1, (int)git_diff_num_deltas(diff)); + + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_assert((delta = git_patch_get_delta(patch)) != NULL); + cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); + + cl_assert_equal_i((int)hunks, (int)git_patch_num_hunks(patch)); + + cl_git_pass( git_patch_line_stats( + &actual_ctxt, &actual_adds, &actual_dels, patch) ); + + cl_assert_equal_sz(ctxt, actual_ctxt); + cl_assert_equal_sz(adds, actual_adds); + cl_assert_equal_sz(dels, actual_dels); + + if (expected != NULL) { + git_buf buf = GIT_BUF_INIT; + cl_git_pass(git_patch_to_buf(&buf, patch)); + cl_assert_equal_s(expected, buf.ptr); + git_buf_dispose(&buf); + + cl_assert_equal_sz( + strlen(expected), git_patch_size(patch, 1, 1, 1)); + } + + if (sizes) { + if (sizes[0]) + cl_assert_equal_sz(sizes[0], git_patch_size(patch, 0, 0, 0)); + if (sizes[1]) + cl_assert_equal_sz(sizes[1], git_patch_size(patch, 1, 0, 0)); + if (sizes[2]) + cl_assert_equal_sz(sizes[2], git_patch_size(patch, 1, 1, 0)); + } + + /* walk lines in hunk with basic sanity checks */ + for (; hunks > 0; --hunks) { + size_t i, max_i; + const git_diff_line *line; + int last_new_lineno = -1, last_old_lineno = -1; + + max_i = git_patch_num_lines_in_hunk(patch, hunks - 1); + + for (i = 0; i < max_i; ++i) { + int expected = 1; + + cl_git_pass( + git_patch_get_line_in_hunk(&line, patch, hunks - 1, i)); + + if (line->origin == GIT_DIFF_LINE_ADD_EOFNL || + line->origin == GIT_DIFF_LINE_DEL_EOFNL || + line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) + expected = 0; + + if (line->old_lineno >= 0) { + if (last_old_lineno >= 0) + cl_assert_equal_i( + expected, line->old_lineno - last_old_lineno); + last_old_lineno = line->old_lineno; + } + + if (line->new_lineno >= 0) { + if (last_new_lineno >= 0) + cl_assert_equal_i( + expected, line->new_lineno - last_new_lineno); + last_new_lineno = line->new_lineno; + } + } + } + + git_patch_free(patch); + git_diff_free(diff); +} + +void test_diff_patch__line_counts_with_eofnl(void) +{ + git_config *cfg; + git_str content = GIT_STR_INIT; + const char *end; + git_index *index; + const char *expected = + /* below is pasted output of 'git diff' with fn context removed */ + "diff --git a/songof7cities.txt b/songof7cities.txt\n" + "index 378a7d9..3d0154e 100644\n" + "--- a/songof7cities.txt\n" + "+++ b/songof7cities.txt\n" + "@@ -42,7 +42,7 @@ With peoples undefeated of the dark, enduring blood.\n" + " \n" + " To the sound of trumpets shall their seed restore my Cities\n" + " Wealthy and well-weaponed, that once more may I behold\n" + "-All the world go softly when it walks before my Cities,\n" + "+#All the world go softly when it walks before my Cities,\n" + " And the horses and the chariots fleeing from them as of old!\n" + " \n" + " -- Rudyard Kipling\n" + "\\ No newline at end of file\n"; + size_t expected_sizes[3] = { 115, 119 + 115 + 114, 119 + 115 + 114 + 71 }; + + g_repo = cl_git_sandbox_init("renames"); + + cl_git_pass(git_config_new(&cfg)); + git_repository_set_config(g_repo, cfg); + git_config_free(cfg); + + git_repository_reinit_filesystem(g_repo, false); + + cl_git_pass(git_futils_readbuffer(&content, "renames/songof7cities.txt")); + + /* remove first line */ + + end = git_str_cstr(&content) + git_str_find(&content, '\n') + 1; + git_str_consume(&content, end); + cl_git_rewritefile("renames/songof7cities.txt", content.ptr); + + check_single_patch_stats(g_repo, 1, 0, 1, 3, NULL, NULL); + + /* remove trailing whitespace */ + + git_str_rtrim(&content); + cl_git_rewritefile("renames/songof7cities.txt", content.ptr); + + check_single_patch_stats(g_repo, 2, 1, 2, 6, NULL, NULL); + + /* add trailing whitespace */ + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_str_putc(&content, '\n')); + cl_git_rewritefile("renames/songof7cities.txt", content.ptr); + + check_single_patch_stats(g_repo, 1, 1, 1, 3, NULL, NULL); + + /* no trailing whitespace as context line */ + + { + /* walk back a couple lines, make space and insert char */ + char *scan = content.ptr + content.size; + int i; + + for (i = 0; i < 5; ++i) { + for (--scan; scan > content.ptr && *scan != '\n'; --scan) + /* seek to prev \n */; + } + cl_assert(scan > content.ptr); + + /* overwrite trailing \n with right-shifted content */ + memmove(scan + 1, scan, content.size - (scan - content.ptr) - 1); + /* insert '#' char into space we created */ + scan[1] = '#'; + } + cl_git_rewritefile("renames/songof7cities.txt", content.ptr); + + check_single_patch_stats( + g_repo, 1, 1, 1, 6, expected_sizes, expected); + + git_str_dispose(&content); +} + +void test_diff_patch__can_strip_bad_utf8(void) +{ + const char *a = "A " UTF8_HUNK_HEADER + " B\n" + " C\n" + " D\n" + " E\n" + " F\n" + " G\n" + " H\n" + " I\n" + " J\n" + " K\n" + "L " UTF8_HUNK_HEADER + " M\n" + " N\n" + " O\n" + " P\n" + " Q\n" + " R\n" + " S\n" + " T\n" + " U\n" + " V\n"; + + const char *b = "A " UTF8_HUNK_HEADER + " B\n" + " C\n" + " D\n" + " E modified\n" + " F\n" + " G\n" + " H\n" + " I\n" + " J\n" + " K\n" + "L " UTF8_HUNK_HEADER + " M\n" + " N\n" + " O\n" + " P modified\n" + " Q\n" + " R\n" + " S\n" + " T\n" + " U\n" + " V\n"; + + const char *expected = "diff --git a/file b/file\n" + "index d0647c4..7827ce5 100644\n" + "--- a/file\n" + "+++ b/file\n" + "@@ -2,7 +2,7 @@ A " UTF8_TRUNCATED_A_HUNK_HEADER + " B\n" + " C\n" + " D\n" + "- E\n" + "+ E modified\n" + " F\n" + " G\n" + " H\n" + "@@ -13,7 +13,7 @@ L " UTF8_TRUNCATED_L_HUNK_HEADER + " M\n" + " N\n" + " O\n" + "- P\n" + "+ P modified\n" + " Q\n" + " R\n" + " S\n"; + + git_diff_options opts; + git_patch *patch; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION)); + + cl_git_pass(git_patch_from_buffers(&patch, a, strlen(a), NULL, b, strlen(b), NULL, &opts)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + + cl_assert_equal_s(expected, buf.ptr); + + git_patch_free(patch); + git_buf_dispose(&buf); +} diff --git a/tests/libgit2/diff/patchid.c b/tests/libgit2/diff/patchid.c new file mode 100644 index 000000000..621a720f7 --- /dev/null +++ b/tests/libgit2/diff/patchid.c @@ -0,0 +1,93 @@ +#include "clar_libgit2.h" +#include "patch/patch_common.h" + +static void verify_patch_id(const char *diff_content, const char *expected_id) +{ + git_oid expected_oid, actual_oid; + git_diff *diff; + + cl_git_pass(git_oid_fromstr(&expected_oid, expected_id)); + cl_git_pass(git_diff_from_buffer(&diff, diff_content, strlen(diff_content))); + cl_git_pass(git_diff_patchid(&actual_oid, diff, NULL)); + + cl_assert_equal_oid(&expected_oid, &actual_oid); + + git_diff_free(diff); +} + +void test_diff_patchid__simple_commit(void) +{ + verify_patch_id(PATCH_SIMPLE_COMMIT, "06094b1948b878b7d9ff7560b4eae672a014b0ec"); +} + +void test_diff_patchid__deleted_file(void) +{ + verify_patch_id(PATCH_DELETE_ORIGINAL, "d18507fe189f49c028b32c8c34e1ad98dd6a1aad"); + verify_patch_id(PATCH_DELETED_FILE_2_HUNKS, "f31412498a17e6c3fbc635f2c5f9aa3ef4c1a9b7"); +} + +void test_diff_patchid__created_file(void) +{ + verify_patch_id(PATCH_ADD_ORIGINAL, "a7d39379308021465ae2ce65e338c048a3110db6"); +} + +void test_diff_patchid__binary_file(void) +{ + verify_patch_id(PATCH_ADD_BINARY_NOT_PRINTED, "2b31236b485faa30cf4dd33e4d6539829996739f"); +} + +void test_diff_patchid__renamed_file(void) +{ + verify_patch_id(PATCH_RENAME_EXACT, "4666d50cea4976f6f727448046d43461912058fd"); + verify_patch_id(PATCH_RENAME_SIMILAR, "a795087575fcb940227be524488bedd6b3d3f438"); +} + +void test_diff_patchid__modechange(void) +{ + verify_patch_id(PATCH_MODECHANGE_UNCHANGED, "dbf3423ee98375ef1c72a79fbd29a049a2bae771"); + verify_patch_id(PATCH_MODECHANGE_MODIFIED, "93aba696e1bbd2bbb73e3e3e62ed71f232137657"); +} + +void test_diff_patchid__shuffle_hunks(void) +{ + verify_patch_id(PATCH_DELETED_FILE_2_HUNKS_SHUFFLED, "f31412498a17e6c3fbc635f2c5f9aa3ef4c1a9b7"); +} + +void test_diff_patchid__filename_with_spaces(void) +{ + verify_patch_id(PATCH_APPEND_NO_NL, "f0ba05413beaef743b630e796153839462ee477a"); +} + +void test_diff_patchid__multiple_hunks(void) +{ + verify_patch_id(PATCH_MULTIPLE_HUNKS, "81e26c34643d17f521e57c483a6a637e18ba1f57"); +} + +void test_diff_patchid__multiple_files(void) +{ + verify_patch_id(PATCH_MULTIPLE_FILES, "192d1f49d23f2004517963aecd3f8a6c467f50ff"); +} + +void test_diff_patchid__same_diff_with_differing_whitespace_has_same_id(void) +{ + const char *tabs = + "diff --git a/file.txt b/file.txt\n" + "index 8fecc09..1d43a92 100644\n" + "--- a/file.txt\n" + "+++ b/file.txt\n" + "@@ -1 +1 @@\n" + "-old text\n" + "+ new text\n"; + const char *spaces = + "diff --git a/file.txt b/file.txt\n" + "index 8fecc09..1d43a92 100644\n" + "--- a/file.txt\n" + "+++ b/file.txt\n" + "@@ -1 +1 @@\n" + "-old text\n" + "+ new text\n"; + const char *id = "11efdd13c30f7a1056eac2ae2fb952da475e2c23"; + + verify_patch_id(tabs, id); + verify_patch_id(spaces, id); +} diff --git a/tests/libgit2/diff/pathspec.c b/tests/libgit2/diff/pathspec.c new file mode 100644 index 000000000..5761d2d2b --- /dev/null +++ b/tests/libgit2/diff/pathspec.c @@ -0,0 +1,93 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_pathspec__initialize(void) +{ + g_repo = cl_git_sandbox_init("status"); +} + +void test_diff_pathspec__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_pathspec__0(void) +{ + const char *a_commit = "26a125ee"; /* the current HEAD */ + const char *b_commit = "0017bd4a"; /* the start */ + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_strarray paths = { NULL, 1 }; + char *path; + git_pathspec *ps; + git_pathspec_match_list *matches; + + cl_assert(a); + cl_assert(b); + + path = "*_file"; + paths.strings = &path; + cl_git_pass(git_pathspec_new(&ps, &paths)); + + cl_git_pass(git_pathspec_match_tree(&matches, a, GIT_PATHSPEC_DEFAULT, ps)); + cl_assert_equal_i(7, (int)git_pathspec_match_list_entrycount(matches)); + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(matches,0)); + cl_assert(git_pathspec_match_list_diff_entry(matches,0) == NULL); + git_pathspec_match_list_free(matches); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, NULL, a, &opts)); + + cl_git_pass(git_pathspec_match_diff( + &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); + cl_assert_equal_i(7, (int)git_pathspec_match_list_entrycount(matches)); + cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); + cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); + cl_assert_equal_s("current_file", + git_pathspec_match_list_diff_entry(matches,0)->new_file.path); + cl_assert_equal_i(GIT_DELTA_ADDED, + (int)git_pathspec_match_list_diff_entry(matches,0)->status); + git_pathspec_match_list_free(matches); + + git_diff_free(diff); + diff = NULL; + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); + + cl_git_pass(git_pathspec_match_diff( + &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); + cl_assert_equal_i(3, (int)git_pathspec_match_list_entrycount(matches)); + cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); + cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); + cl_assert_equal_s("subdir/current_file", + git_pathspec_match_list_diff_entry(matches,0)->new_file.path); + cl_assert_equal_i(GIT_DELTA_DELETED, + (int)git_pathspec_match_list_diff_entry(matches,0)->status); + git_pathspec_match_list_free(matches); + + git_diff_free(diff); + diff = NULL; + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); + + cl_git_pass(git_pathspec_match_diff( + &matches, diff, GIT_PATHSPEC_DEFAULT, ps)); + cl_assert_equal_i(4, (int)git_pathspec_match_list_entrycount(matches)); + cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL); + cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL); + cl_assert_equal_s("modified_file", + git_pathspec_match_list_diff_entry(matches,0)->new_file.path); + cl_assert_equal_i(GIT_DELTA_MODIFIED, + (int)git_pathspec_match_list_diff_entry(matches,0)->status); + git_pathspec_match_list_free(matches); + + git_diff_free(diff); + diff = NULL; + + git_tree_free(a); + git_tree_free(b); + git_pathspec_free(ps); +} diff --git a/tests/libgit2/diff/racediffiter.c b/tests/libgit2/diff/racediffiter.c new file mode 100644 index 000000000..d364d6b21 --- /dev/null +++ b/tests/libgit2/diff/racediffiter.c @@ -0,0 +1,129 @@ +/* This test exercises the problem described in +** https://github.com/libgit2/libgit2/pull/3568 +** where deleting a directory during a diff/status +** operation can cause an access violation. +** +** The "test_diff_racediffiter__basic() test confirms +** the normal operation of diff on the given repo. +** +** The "test_diff_racediffiter__racy_rmdir() test +** uses the new diff progress callback to delete +** a directory (after the initial readdir() and +** before the directory itself is visited) causing +** the recursion and iteration to fail. +*/ + +#include "clar_libgit2.h" +#include "diff_helpers.h" + +#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) + +void test_diff_racediffiter__initialize(void) +{ +} + +void test_diff_racediffiter__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +typedef struct +{ + const char *path; + git_delta_t t; + +} basic_payload; + +static int notify_cb__basic( + const git_diff *diff_so_far, + const git_diff_delta *delta_to_add, + const char *matched_pathspec, + void *payload) +{ + basic_payload *exp = (basic_payload *)payload; + basic_payload *e; + + GIT_UNUSED(diff_so_far); + GIT_UNUSED(matched_pathspec); + + for (e = exp; e->path; e++) { + if (strcmp(e->path, delta_to_add->new_file.path) == 0) { + cl_assert_equal_i(e->t, delta_to_add->status); + return 0; + } + } + cl_assert(0); + return GIT_ENOTFOUND; +} + +void test_diff_racediffiter__basic(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_repository *repo = cl_git_sandbox_init("diff"); + git_diff *diff; + + basic_payload exp_a[] = { + { "another.txt", GIT_DELTA_MODIFIED }, + { "readme.txt", GIT_DELTA_MODIFIED }, + { "zzzzz/", GIT_DELTA_IGNORED }, + { NULL, 0 } + }; + + cl_must_pass(p_mkdir("diff/zzzzz", 0777)); + + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + opts.notify_cb = notify_cb__basic; + opts.payload = exp_a; + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); + + git_diff_free(diff); +} + + +typedef struct { + bool first_time; + const char *dir; + basic_payload *basic_payload; +} racy_payload; + +static int notify_cb__racy_rmdir( + const git_diff *diff_so_far, + const git_diff_delta *delta_to_add, + const char *matched_pathspec, + void *payload) +{ + racy_payload *pay = (racy_payload *)payload; + + if (pay->first_time) { + cl_must_pass(p_rmdir(pay->dir)); + pay->first_time = false; + } + + return notify_cb__basic(diff_so_far, delta_to_add, matched_pathspec, pay->basic_payload); +} + +void test_diff_racediffiter__racy(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_repository *repo = cl_git_sandbox_init("diff"); + git_diff *diff; + + basic_payload exp_a[] = { + { "another.txt", GIT_DELTA_MODIFIED }, + { "readme.txt", GIT_DELTA_MODIFIED }, + { NULL, 0 } + }; + + racy_payload pay = { true, "diff/zzzzz", exp_a }; + + cl_must_pass(p_mkdir("diff/zzzzz", 0777)); + + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + opts.notify_cb = notify_cb__racy_rmdir; + opts.payload = &pay; + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); + + git_diff_free(diff); +} diff --git a/tests/libgit2/diff/rename.c b/tests/libgit2/diff/rename.c new file mode 100644 index 000000000..30e9ea432 --- /dev/null +++ b/tests/libgit2/diff/rename.c @@ -0,0 +1,2034 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_rename__initialize(void) +{ + g_repo = cl_git_sandbox_init("renames"); + + cl_repo_set_bool(g_repo, "core.autocrlf", false); +} + +void test_diff_rename__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +#define INITIAL_COMMIT "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2" +#define COPY_RENAME_COMMIT "2bc7f351d20b53f1c72c16c4b036e491c478c49a" +#define REWRITE_COPY_COMMIT "1c068dee5790ef1580cfc4cd670915b48d790084" +#define RENAME_MODIFICATION_COMMIT "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13" +#define REWRITE_DELETE_COMMIT "84d8efa38af7ace2b302de0adbda16b1f1cd2e1b" +#define DELETE_RENAME_COMMIT "be053a189b0bbde545e0a3f59ce00b46ad29ce0d" +#define BREAK_REWRITE_BASE_COMMIT "db98035f715427eef1f5e17f03e1801c05301e9e" +#define BREAK_REWRITE_COMMIT "7e7bfb88ba9bc65fd700fee1819cf1c317aafa56" + +/* + * Renames repo has: + * + * commit 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 - + * serving.txt (25 lines) + * sevencities.txt (50 lines) + * commit 2bc7f351d20b53f1c72c16c4b036e491c478c49a - + * serving.txt -> sixserving.txt (rename, no change, 100% match) + * sevencities.txt -> sevencities.txt (no change) + * sevencities.txt -> songofseven.txt (copy, no change, 100% match) + * commit 1c068dee5790ef1580cfc4cd670915b48d790084 + * songofseven.txt -> songofseven.txt (major rewrite, <20% match - split) + * sixserving.txt -> sixserving.txt (indentation change) + * sixserving.txt -> ikeepsix.txt (copy, add title, >80% match) + * sevencities.txt (no change) + * commit 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 + * songofseven.txt -> untimely.txt (rename, convert to crlf) + * ikeepsix.txt -> ikeepsix.txt (reorder sections in file) + * sixserving.txt -> sixserving.txt (whitespace change - not just indent) + * sevencities.txt -> songof7cities.txt (rename, small text changes) + * commit 84d8efa38af7ace2b302de0adbda16b1f1cd2e1b + * songof7cities.txt -> songof7citie.txt (major rewrite, <20% match) + * ikeepsix.txt -> (deleted) + * untimely.txt (no change) + * sixserving.txt (no change) + * commit be053a189b0bbde545e0a3f59ce00b46ad29ce0d + * ikeepsix.txt -> (deleted) + * songof7cities.txt -> ikeepsix.txt (rename, 100% match) + * untimely.txt (no change) + * sixserving.txt (no change) + */ + +void test_diff_rename__match_oid(void) +{ + const char *old_sha = INITIAL_COMMIT; + const char *new_sha = COPY_RENAME_COMMIT; + git_tree *old_tree, *new_tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); + new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); + + /* Must pass GIT_DIFF_INCLUDE_UNMODIFIED if you expect to emulate + * --find-copies-harder during rename transformion... + */ + diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + /* git diff --no-renames \ + * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ + * 2bc7f351d20b53f1c72c16c4b036e491c478c49a + */ + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + + /* git diff 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ + * 2bc7f351d20b53f1c72c16c4b036e491c478c49a + * don't use NULL opts to avoid config `diff.renames` contamination + */ + opts.flags = GIT_DIFF_FIND_RENAMES; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_free(diff); + + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + /* git diff --find-copies-harder \ + * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ + * 2bc7f351d20b53f1c72c16c4b036e491c478c49a + */ + opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_free(diff); + + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + /* git diff --find-copies-harder -M100 -B100 \ + * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ + * 2bc7f351d20b53f1c72c16c4b036e491c478c49a + */ + opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED | + GIT_DIFF_FIND_EXACT_MATCH_ONLY; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_free(diff); + + git_tree_free(old_tree); + git_tree_free(new_tree); +} + +void test_diff_rename__checks_options_version(void) +{ + const char *old_sha = INITIAL_COMMIT; + const char *new_sha = COPY_RENAME_COMMIT; + git_tree *old_tree, *new_tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + const git_error *err; + + old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); + new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); + diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + opts.version = 0; + cl_git_fail(git_diff_find_similar(diff, &opts)); + err = git_error_last(); + cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); + + git_error_clear(); + opts.version = 1024; + cl_git_fail(git_diff_find_similar(diff, &opts)); + err = git_error_last(); + cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); + + git_diff_free(diff); + git_tree_free(old_tree); + git_tree_free(new_tree); +} + +void test_diff_rename__not_exact_match(void) +{ + const char *sha0 = COPY_RENAME_COMMIT; + const char *sha1 = REWRITE_COPY_COMMIT; + const char *sha2 = RENAME_MODIFICATION_COMMIT; + git_tree *old_tree, *new_tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + /* == Changes ===================================================== + * songofseven.txt -> songofseven.txt (major rewrite, <20% match - split) + * sixserving.txt -> sixserving.txt (indentation change) + * sixserving.txt -> ikeepsix.txt (copy, add title, >80% match) + * sevencities.txt (no change) + */ + + old_tree = resolve_commit_oid_to_tree(g_repo, sha0); + new_tree = resolve_commit_oid_to_tree(g_repo, sha1); + + /* Must pass GIT_DIFF_INCLUDE_UNMODIFIED if you expect to emulate + * --find-copies-harder during rename transformion... + */ + diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + /* git diff --no-renames \ + * 2bc7f351d20b53f1c72c16c4b036e491c478c49a \ + * 1c068dee5790ef1580cfc4cd670915b48d790084 + */ + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + + /* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a \ + * 1c068dee5790ef1580cfc4cd670915b48d790084 + * + * must not pass NULL for opts because it will pick up environment + * values for "diff.renames" and test won't be consistent. + */ + opts.flags = GIT_DIFF_FIND_RENAMES; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + + git_diff_free(diff); + + /* git diff -M -C \ + * 2bc7f351d20b53f1c72c16c4b036e491c478c49a \ + * 1c068dee5790ef1580cfc4cd670915b48d790084 + */ + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); + + git_diff_free(diff); + + /* git diff -M -C --find-copies-harder --break-rewrites \ + * 2bc7f351d20b53f1c72c16c4b036e491c478c49a \ + * 1c068dee5790ef1580cfc4cd670915b48d790084 + */ + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + opts.flags = GIT_DIFF_FIND_ALL; + opts.break_rewrite_threshold = 70; + + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(5, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); + + git_diff_free(diff); + + /* == Changes ===================================================== + * songofseven.txt -> untimely.txt (rename, convert to crlf) + * ikeepsix.txt -> ikeepsix.txt (reorder sections in file) + * sixserving.txt -> sixserving.txt (whitespace - not just indent) + * sevencities.txt -> songof7cities.txt (rename, small text changes) + */ + + git_tree_free(old_tree); + old_tree = new_tree; + new_tree = resolve_commit_oid_to_tree(g_repo, sha2); + + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + /* git diff --no-renames \ + * 1c068dee5790ef1580cfc4cd670915b48d790084 \ + * 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 + */ + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(6, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + git_diff_free(diff); + + /* git diff -M -C \ + * 1c068dee5790ef1580cfc4cd670915b48d790084 \ + * 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 + */ + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_free(diff); + + /* git diff -M -C --find-copies-harder --break-rewrites \ + * 1c068dee5790ef1580cfc4cd670915b48d790084 \ + * 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 + * with libgit2 default similarity comparison... + */ + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + /* the default match algorithm is going to find the internal + * whitespace differences in the lines of sixserving.txt to be + * significant enough that this will decide to split it into an ADD + * and a DELETE + */ + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(5, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_free(diff); + + /* git diff -M -C --find-copies-harder --break-rewrites \ + * 1c068dee5790ef1580cfc4cd670915b48d790084 \ + * 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 + * with ignore_space whitespace comparison + */ + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_IGNORE_WHITESPACE; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + /* Ignoring whitespace, this should no longer split sixserver.txt */ + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_free(diff); + + git_tree_free(old_tree); + git_tree_free(new_tree); +} + +void test_diff_rename__test_small_files(void) +{ + git_index *index; + git_reference *head_reference; + git_commit *head_commit; + git_tree *head_tree; + git_tree *commit_tree; + git_signature *signature; + git_diff *diff; + git_oid oid; + const git_diff_delta *delta; + git_diff_options diff_options = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options find_options = GIT_DIFF_FIND_OPTIONS_INIT; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_mkfile("renames/small.txt", "Hello World!\n"); + cl_git_pass(git_index_add_bypath(index, "small.txt")); + + cl_git_pass(git_repository_head(&head_reference, g_repo)); + cl_git_pass(git_reference_peel((git_object**)&head_commit, head_reference, GIT_OBJECT_COMMIT)); + cl_git_pass(git_commit_tree(&head_tree, head_commit)); + cl_git_pass(git_index_write_tree(&oid, index)); + cl_git_pass(git_tree_lookup(&commit_tree, g_repo, &oid)); + cl_git_pass(git_signature_new(&signature, "Rename", "rename@example.com", 1404157834, 0)); + cl_git_pass(git_commit_create(&oid, g_repo, "HEAD", signature, signature, NULL, "Test commit", commit_tree, 1, (const git_commit**)&head_commit)); + + cl_git_mkfile("renames/copy.txt", "Hello World!\n"); + cl_git_rmfile("renames/small.txt"); + + diff_options.flags = GIT_DIFF_INCLUDE_UNTRACKED; + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, commit_tree, &diff_options)); + find_options.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_FOR_UNTRACKED; + cl_git_pass(git_diff_find_similar(diff, &find_options)); + + cl_assert_equal_i(git_diff_num_deltas(diff), 1); + delta = git_diff_get_delta(diff, 0); + cl_assert_equal_i(delta->status, GIT_DELTA_RENAMED); + cl_assert_equal_s(delta->old_file.path, "small.txt"); + cl_assert_equal_s(delta->new_file.path, "copy.txt"); + + git_diff_free(diff); + git_signature_free(signature); + git_tree_free(commit_tree); + git_tree_free(head_tree); + git_commit_free(head_commit); + git_reference_free(head_reference); + git_index_free(index); +} + +void test_diff_rename__working_directory_changes(void) +{ + const char *sha0 = COPY_RENAME_COMMIT; + const char *blobsha = "66311f5cfbe7836c27510a3ba2f43e282e2c8bba"; + git_oid id; + git_tree *tree; + git_blob *blob; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + git_str old_content = GIT_STR_INIT, content = GIT_STR_INIT;; + + tree = resolve_commit_oid_to_tree(g_repo, sha0); + diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED | GIT_DIFF_INCLUDE_UNTRACKED; + + /* + $ git cat-file -p 2bc7f351d20b53f1c72c16c4b036e491c478c49a^{tree} + + 100644 blob 66311f5cfbe7836c27510a3ba2f43e282e2c8bba sevencities.txt + 100644 blob ad0a8e55a104ac54a8a29ed4b84b49e76837a113 sixserving.txt + 100644 blob 66311f5cfbe7836c27510a3ba2f43e282e2c8bba songofseven.txt + + $ for f in *.txt; do + echo `git hash-object -t blob $f` $f + done + + eaf4a3e3bfe68585e90cada20736ace491cd100b ikeepsix.txt + f90d4fc20ecddf21eebe6a37e9225d244339d2b5 sixserving.txt + 4210ffd5c390b21dd5483375e75288dea9ede512 songof7cities.txt + 9a69d960ae94b060f56c2a8702545e2bb1abb935 untimely.txt + */ + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts)); + + /* git diff --no-renames 2bc7f351d20b53f1c72c16c4b036e491c478c49a */ + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(6, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); + + /* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a */ + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(5, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + + /* rewrite files in the working directory with / without CRLF changes */ + + cl_git_pass( + git_futils_readbuffer(&old_content, "renames/songof7cities.txt")); + cl_git_pass( + git_str_lf_to_crlf(&content, &old_content)); + cl_git_pass( + git_futils_writebuffer(&content, "renames/songof7cities.txt", 0, 0)); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts)); + + /* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a */ + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(5, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + + /* try a different whitespace option */ + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts)); + + opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE; + opts.rename_threshold = 70; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(6, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + + /* try a different matching option */ + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts)); + + opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_EXACT_MATCH_ONLY; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(6, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + + git_diff_free(diff); + + /* again with exact match blob */ + + cl_git_pass(git_oid_fromstr(&id, blobsha)); + cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); + cl_git_pass(git_str_set( + &content, git_blob_rawcontent(blob), (size_t)git_blob_rawsize(blob))); + cl_git_rewritefile("renames/songof7cities.txt", content.ptr); + git_blob_free(blob); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts)); + + opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_EXACT_MATCH_ONLY; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + /* + fprintf(stderr, "\n\n"); + diff_print_raw(stderr, diff); + */ + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(5, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + + git_tree_free(tree); + git_str_dispose(&content); + git_str_dispose(&old_content); +} + +void test_diff_rename__patch(void) +{ + const char *sha0 = COPY_RENAME_COMMIT; + const char *sha1 = REWRITE_COPY_COMMIT; + git_tree *old_tree, *new_tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + git_patch *patch; + const git_diff_delta *delta; + git_buf buf = GIT_BUF_INIT; + const char *expected = "diff --git a/sixserving.txt b/ikeepsix.txt\nindex ad0a8e5..36020db 100644\n--- a/sixserving.txt\n+++ b/ikeepsix.txt\n@@ -1,3 +1,6 @@\n+I Keep Six Honest Serving-Men\n+=============================\n+\n I KEEP six honest serving-men\n (They taught me all I knew);\n Their names are What and Why and When\n@@ -21,4 +24,4 @@ She sends'em abroad on her own affairs,\n One million Hows, two million Wheres,\n And seven million Whys!\n \n- -- Rudyard Kipling\n+ -- Rudyard Kipling\n"; + + old_tree = resolve_commit_oid_to_tree(g_repo, sha0); + new_tree = resolve_commit_oid_to_tree(g_repo, sha1); + + diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + /* == Changes ===================================================== + * sixserving.txt -> ikeepsix.txt (copy, add title, >80% match) + * sevencities.txt (no change) + * sixserving.txt -> sixserving.txt (indentation change) + * songofseven.txt -> songofseven.txt (major rewrite, <20% match - split) + */ + + cl_assert_equal_i(4, (int)git_diff_num_deltas(diff)); + + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_assert((delta = git_patch_get_delta(patch)) != NULL); + cl_assert_equal_i(GIT_DELTA_COPIED, (int)delta->status); + + cl_git_pass(git_patch_to_buf(&buf, patch)); + cl_assert_equal_s(expected, buf.ptr); + git_buf_dispose(&buf); + + git_patch_free(patch); + + cl_assert((delta = git_diff_get_delta(diff, 1)) != NULL); + cl_assert_equal_i(GIT_DELTA_UNMODIFIED, (int)delta->status); + + cl_assert((delta = git_diff_get_delta(diff, 2)) != NULL); + cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); + + cl_assert((delta = git_diff_get_delta(diff, 3)) != NULL); + cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status); + + git_diff_free(diff); + git_tree_free(old_tree); + git_tree_free(new_tree); +} + +void test_diff_rename__file_exchange(void) +{ + git_str c1 = GIT_STR_INIT, c2 = GIT_STR_INIT; + git_index *index; + git_tree *tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt")); + cl_git_pass(git_futils_readbuffer(&c2, "renames/songof7cities.txt")); + cl_git_pass(git_futils_writebuffer(&c1, "renames/songof7cities.txt", 0, 0)); + cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_add_bypath(index, "untimely.txt")); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); + + git_str_dispose(&c1); + git_str_dispose(&c2); +} + +void test_diff_rename__file_exchange_three(void) +{ + git_str c1 = GIT_STR_INIT, c2 = GIT_STR_INIT, c3 = GIT_STR_INIT; + git_index *index; + git_tree *tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt")); + cl_git_pass(git_futils_readbuffer(&c2, "renames/songof7cities.txt")); + cl_git_pass(git_futils_readbuffer(&c3, "renames/ikeepsix.txt")); + + cl_git_pass(git_futils_writebuffer(&c1, "renames/ikeepsix.txt", 0, 0)); + cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0)); + cl_git_pass(git_futils_writebuffer(&c3, "renames/songof7cities.txt", 0, 0)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_add_bypath(index, "untimely.txt")); + cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); + + git_str_dispose(&c1); + git_str_dispose(&c2); + git_str_dispose(&c3); +} + +void test_diff_rename__file_partial_exchange(void) +{ + git_str c1 = GIT_STR_INIT, c2 = GIT_STR_INIT; + git_index *index; + git_tree *tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + int i; + + cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt")); + cl_git_pass(git_futils_writebuffer(&c1, "renames/songof7cities.txt", 0, 0)); + for (i = 0; i < 100; ++i) + cl_git_pass(git_str_puts(&c2, "this is not the content you are looking for\n")); + cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_add_bypath(index, "untimely.txt")); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); + + git_str_dispose(&c1); + git_str_dispose(&c2); +} + +void test_diff_rename__rename_and_copy_from_same_source(void) +{ + git_str c1 = GIT_STR_INIT, c2 = GIT_STR_INIT; + git_index *index; + git_tree *tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + /* put the first 2/3 of file into one new place + * and the second 2/3 of file into another new place + */ + cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt")); + cl_git_pass(git_str_set(&c2, c1.ptr, c1.size)); + git_str_truncate(&c1, c1.size * 2 / 3); + git_str_consume(&c2, ((char *)c2.ptr) + (c2.size / 3)); + cl_git_pass(git_futils_writebuffer(&c1, "renames/song_a.txt", 0, 0)); + cl_git_pass(git_futils_writebuffer(&c2, "renames/song_b.txt", 0, 0)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_add_bypath(index, "song_a.txt")); + cl_git_pass(git_index_add_bypath(index, "song_b.txt")); + + diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(6, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNMODIFIED]); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(6, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_COPIED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNMODIFIED]); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); + + git_str_dispose(&c1); + git_str_dispose(&c2); +} + +void test_diff_rename__from_deleted_to_split(void) +{ + git_str c1 = GIT_STR_INIT; + git_index *index; + git_tree *tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + /* old file is missing, new file is actually old file renamed */ + + cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt")); + cl_git_pass(git_futils_writebuffer(&c1, "renames/untimely.txt", 0, 0)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_add_bypath(index, "untimely.txt")); + + diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNMODIFIED]); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNMODIFIED]); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); + + git_str_dispose(&c1); +} + +struct rename_expected +{ + size_t len; + + unsigned int *status; + const char **sources; + const char **targets; + + size_t idx; +}; + +static int test_names_expected(const git_diff_delta *delta, float progress, void *p) +{ + struct rename_expected *expected = p; + + GIT_UNUSED(progress); + + cl_assert(expected->idx < expected->len); + + cl_assert_equal_i(delta->status, expected->status[expected->idx]); + + cl_assert(git__strcmp(expected->sources[expected->idx], + delta->old_file.path) == 0); + cl_assert(git__strcmp(expected->targets[expected->idx], + delta->new_file.path) == 0); + + expected->idx++; + + return 0; +} + +void test_diff_rename__rejected_match_can_match_others(void) +{ + git_reference *head, *selfsimilar; + git_index *index; + git_tree *tree; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + git_str one = GIT_STR_INIT, two = GIT_STR_INIT; + unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED }; + const char *sources[] = { "Class1.cs", "Class2.cs" }; + const char *targets[] = { "ClassA.cs", "ClassB.cs" }; + struct rename_expected expect = { 2, status, sources, targets }; + char *ptr; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + findopts.flags = GIT_DIFF_FIND_RENAMES; + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_git_pass(git_reference_symbolic_set_target( + &selfsimilar, head, "refs/heads/renames_similar", NULL)); + cl_git_pass(git_checkout_head(g_repo, &opts)); + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_futils_readbuffer(&one, "renames/Class1.cs")); + cl_git_pass(git_futils_readbuffer(&two, "renames/Class2.cs")); + + cl_git_pass(p_unlink("renames/Class1.cs")); + cl_git_pass(p_unlink("renames/Class2.cs")); + + cl_git_pass(git_index_remove_bypath(index, "Class1.cs")); + cl_git_pass(git_index_remove_bypath(index, "Class2.cs")); + + cl_assert(ptr = strstr(one.ptr, "Class1")); + ptr[5] = 'A'; + + cl_assert(ptr = strstr(two.ptr, "Class2")); + ptr[5] = 'B'; + + cl_git_pass( + git_futils_writebuffer(&one, "renames/ClassA.cs", O_RDWR|O_CREAT, 0777)); + cl_git_pass( + git_futils_writebuffer(&two, "renames/ClassB.cs", O_RDWR|O_CREAT, 0777)); + + cl_git_pass(git_index_add_bypath(index, "ClassA.cs")); + cl_git_pass(git_index_add_bypath(index, "ClassB.cs")); + + cl_git_pass(git_index_write(index)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass( + git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + cl_git_pass(git_diff_foreach( + diff, test_names_expected, NULL, NULL, NULL, &expect)); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); + git_reference_free(head); + git_reference_free(selfsimilar); + git_str_dispose(&one); + git_str_dispose(&two); +} + +static void write_similarity_file_two(const char *filename, size_t b_lines) +{ + git_str contents = GIT_STR_INIT; + size_t i; + + for (i = 0; i < b_lines; i++) + git_str_printf(&contents, "%02d - bbbbb\r\n", (int)(i+1)); + + for (i = b_lines; i < 50; i++) + git_str_printf(&contents, "%02d - aaaaa%s", (int)(i+1), (i == 49 ? "" : "\r\n")); + + cl_git_pass( + git_futils_writebuffer(&contents, filename, O_RDWR|O_CREAT, 0777)); + + git_str_dispose(&contents); +} + +void test_diff_rename__rejected_match_can_match_others_two(void) +{ + git_reference *head, *selfsimilar; + git_index *index; + git_tree *tree; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED }; + const char *sources[] = { "a.txt", "b.txt" }; + const char *targets[] = { "c.txt", "d.txt" }; + struct rename_expected expect = { 2, status, sources, targets }; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + findopts.flags = GIT_DIFF_FIND_RENAMES; + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_git_pass(git_reference_symbolic_set_target( + &selfsimilar, head, "refs/heads/renames_similar_two", NULL)); + cl_git_pass(git_checkout_head(g_repo, &opts)); + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(p_unlink("renames/a.txt")); + cl_git_pass(p_unlink("renames/b.txt")); + + cl_git_pass(git_index_remove_bypath(index, "a.txt")); + cl_git_pass(git_index_remove_bypath(index, "b.txt")); + + write_similarity_file_two("renames/c.txt", 7); + write_similarity_file_two("renames/d.txt", 8); + + cl_git_pass(git_index_add_bypath(index, "c.txt")); + cl_git_pass(git_index_add_bypath(index, "d.txt")); + + cl_git_pass(git_index_write(index)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass( + git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + cl_git_pass(git_diff_foreach( + diff, test_names_expected, NULL, NULL, NULL, &expect)); + cl_assert(expect.idx > 0); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); + git_reference_free(head); + git_reference_free(selfsimilar); +} + +void test_diff_rename__rejected_match_can_match_others_three(void) +{ + git_reference *head, *selfsimilar; + git_index *index; + git_tree *tree; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + + /* Both cannot be renames from a.txt */ + unsigned int status[] = { GIT_DELTA_ADDED, GIT_DELTA_RENAMED }; + const char *sources[] = { "0001.txt", "a.txt" }; + const char *targets[] = { "0001.txt", "0002.txt" }; + struct rename_expected expect = { 2, status, sources, targets }; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + findopts.flags = GIT_DIFF_FIND_RENAMES; + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_git_pass(git_reference_symbolic_set_target( + &selfsimilar, head, "refs/heads/renames_similar_two", NULL)); + cl_git_pass(git_checkout_head(g_repo, &opts)); + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(p_unlink("renames/a.txt")); + + cl_git_pass(git_index_remove_bypath(index, "a.txt")); + + write_similarity_file_two("renames/0001.txt", 7); + write_similarity_file_two("renames/0002.txt", 0); + + cl_git_pass(git_index_add_bypath(index, "0001.txt")); + cl_git_pass(git_index_add_bypath(index, "0002.txt")); + + cl_git_pass(git_index_write(index)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass( + git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + cl_git_pass(git_diff_foreach( + diff, test_names_expected, NULL, NULL, NULL, &expect)); + + cl_assert(expect.idx == expect.len); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); + git_reference_free(head); + git_reference_free(selfsimilar); +} + +void test_diff_rename__can_rename_from_rewrite(void) +{ + git_index *index; + git_tree *tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + + unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED }; + const char *sources[] = { "ikeepsix.txt", "songof7cities.txt" }; + const char *targets[] = { "songof7cities.txt", "this-is-a-rename.txt" }; + struct rename_expected expect = { 2, status, sources, targets }; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(p_rename("renames/songof7cities.txt", "renames/this-is-a-rename.txt")); + cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/songof7cities.txt")); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + + cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_add_bypath(index, "this-is-a-rename.txt")); + + cl_git_pass(git_index_write(index)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass( + git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + findopts.flags |= GIT_DIFF_FIND_AND_BREAK_REWRITES | + GIT_DIFF_FIND_REWRITES | + GIT_DIFF_FIND_RENAMES_FROM_REWRITES; + + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + cl_git_pass(git_diff_foreach( + diff, test_names_expected, NULL, NULL, NULL, &expect)); + + cl_assert(expect.idx == expect.len); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); +} + +void test_diff_rename__case_changes_are_split(void) +{ + git_index *index; + git_tree *tree; + git_diff *diff = NULL; + diff_expects exp; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/IKEEPSIX.txt")); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "IKEEPSIX.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, NULL)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_free(diff); + git_index_free(index); + git_tree_free(tree); +} + +void test_diff_rename__unmodified_can_be_renamed(void) +{ + git_index *index; + git_tree *tree; + git_diff *diff = NULL; + diff_expects exp; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt")); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_free(diff); + git_index_free(index); + git_tree_free(tree); +} + +void test_diff_rename__rewrite_on_single_file(void) +{ + git_index *index; + git_diff *diff = NULL; + diff_expects exp; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + + diffopts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + + findopts.flags = GIT_DIFF_FIND_FOR_UNTRACKED | + GIT_DIFF_FIND_AND_BREAK_REWRITES | + GIT_DIFF_FIND_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_rewritefile("renames/ikeepsix.txt", + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &diffopts)); + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + git_index_free(index); +} + +void test_diff_rename__can_find_copy_to_split(void) +{ + git_str c1 = GIT_STR_INIT; + git_index *index; + git_tree *tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt")); + cl_git_pass(git_futils_writebuffer(&c1, "renames/untimely.txt", 0, 0)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_add_bypath(index, "untimely.txt")); + + diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNMODIFIED]); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(5, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNMODIFIED]); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); + + git_str_dispose(&c1); +} + +void test_diff_rename__can_delete_unmodified_deltas(void) +{ + git_str c1 = GIT_STR_INIT; + git_index *index; + git_tree *tree; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt")); + cl_git_pass(git_futils_writebuffer(&c1, "renames/untimely.txt", 0, 0)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_add_bypath(index, "untimely.txt")); + + diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNMODIFIED]); + + opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_REMOVE_UNMODIFIED; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); + + git_str_dispose(&c1); +} + +void test_diff_rename__matches_config_behavior(void) +{ + const char *sha0 = INITIAL_COMMIT; + const char *sha1 = COPY_RENAME_COMMIT; + const char *sha2 = REWRITE_COPY_COMMIT; + + git_tree *tree0, *tree1, *tree2; + git_config *cfg; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + opts.flags = GIT_DIFF_FIND_BY_CONFIG; + tree0 = resolve_commit_oid_to_tree(g_repo, sha0); + tree1 = resolve_commit_oid_to_tree(g_repo, sha1); + tree2 = resolve_commit_oid_to_tree(g_repo, sha2); + + diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + cl_git_pass(git_repository_config(&cfg, g_repo)); + + /* diff.renames = false; no rename detection should happen */ + cl_git_pass(git_config_set_bool(cfg, "diff.renames", false)); + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree0, tree1, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, &opts)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + git_diff_free(diff); + + /* diff.renames = true; should act like -M */ + cl_git_pass(git_config_set_bool(cfg, "diff.renames", true)); + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree0, tree1, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, &opts)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + git_diff_free(diff); + + /* diff.renames = copies; should act like -M -C */ + cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies")); + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree1, tree2, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, &opts)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); + git_diff_free(diff); + + /* NULL find options is the same as GIT_DIFF_FIND_BY_CONFIG */ + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree1, tree2, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, NULL)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); + git_diff_free(diff); + + /* Cleanup */ + git_tree_free(tree0); + git_tree_free(tree1); + git_tree_free(tree2); + git_config_free(cfg); +} + +void test_diff_rename__can_override_thresholds_when_obeying_config(void) +{ + const char *sha1 = COPY_RENAME_COMMIT; + const char *sha2 = REWRITE_COPY_COMMIT; + + git_tree *tree1, *tree2; + git_config *cfg; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + tree1 = resolve_commit_oid_to_tree(g_repo, sha1); + tree2 = resolve_commit_oid_to_tree(g_repo, sha2); + + diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + opts.flags = GIT_DIFF_FIND_BY_CONFIG; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies")); + git_config_free(cfg); + + /* copy threshold = 96%, should see creation of ikeepsix.txt */ + opts.copy_threshold = 96; + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree1, tree2, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, &opts)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + git_diff_free(diff); + + /* copy threshold = 20%, should see sixserving.txt => ikeepsix.txt */ + opts.copy_threshold = 20; + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree1, tree2, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, &opts)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); + git_diff_free(diff); + + /* Cleanup */ + git_tree_free(tree1); + git_tree_free(tree2); +} + +void test_diff_rename__by_config_doesnt_mess_with_whitespace_settings(void) +{ + const char *sha1 = REWRITE_COPY_COMMIT; + const char *sha2 = RENAME_MODIFICATION_COMMIT; + + git_tree *tree1, *tree2; + git_config *cfg; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + tree1 = resolve_commit_oid_to_tree(g_repo, sha1); + tree2 = resolve_commit_oid_to_tree(g_repo, sha2); + + diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + opts.flags = GIT_DIFF_FIND_BY_CONFIG; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies")); + git_config_free(cfg); + + /* Don't ignore whitespace; this should find a change in sixserving.txt */ + opts.flags |= 0 | GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE; + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree1, tree2, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, &opts)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(5, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + git_diff_free(diff); + + /* Cleanup */ + git_tree_free(tree1); + git_tree_free(tree2); +} + +static void expect_files_renamed(const char *one, const char *two, uint32_t whitespace_flags) +{ + git_index *index; + git_diff *diff = NULL; + diff_expects exp; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + + diffopts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + findopts.flags = GIT_DIFF_FIND_FOR_UNTRACKED | + GIT_DIFF_FIND_AND_BREAK_REWRITES | + GIT_DIFF_FIND_RENAMES_FROM_REWRITES | + whitespace_flags; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_rewritefile("renames/ikeepsix.txt", one); + cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); + + cl_git_rmfile("renames/ikeepsix.txt"); + cl_git_rewritefile("renames/ikeepsix2.txt", two); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &diffopts)); + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_free(diff); + git_index_free(index); +} + +/* test some variations on empty and blank files */ +void test_diff_rename__empty_files_renamed(void) +{ + /* empty files are identical when ignoring whitespace or not */ + expect_files_renamed("", "", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE); + expect_files_renamed("", "", GIT_DIFF_FIND_IGNORE_WHITESPACE); +} + +/* test that blank files are similar when ignoring whitespace */ +void test_diff_rename__blank_files_renamed_when_ignoring_whitespace(void) +{ + expect_files_renamed("", "\n\n", GIT_DIFF_FIND_IGNORE_WHITESPACE); + expect_files_renamed("", "\r\n\r\n", GIT_DIFF_FIND_IGNORE_WHITESPACE); + expect_files_renamed("\r\n\r\n", "\n\n\n", GIT_DIFF_FIND_IGNORE_WHITESPACE); + + expect_files_renamed(" ", "\n\n", GIT_DIFF_FIND_IGNORE_WHITESPACE); + expect_files_renamed(" \n \n", "\n\n", GIT_DIFF_FIND_IGNORE_WHITESPACE); +} + +/* blank files are not similar when whitespace is not ignored */ +static void expect_files_not_renamed(const char *one, const char *two, uint32_t whitespace_flags) +{ + git_index *index; + git_diff *diff = NULL; + diff_expects exp; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + + diffopts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + + findopts.flags = GIT_DIFF_FIND_FOR_UNTRACKED | + whitespace_flags; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_rewritefile("renames/ikeepsix.txt", one); + cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); + + cl_git_rmfile("renames/ikeepsix.txt"); + cl_git_rewritefile("renames/ikeepsix2.txt", two); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &diffopts)); + cl_git_pass(git_diff_find_similar(diff, &findopts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + git_index_free(index); +} + +/* test that blank files are similar when ignoring renames */ +void test_diff_rename__blank_files_not_renamed_when_not_ignoring_whitespace(void) +{ + expect_files_not_renamed("", "\r\n\r\n\r\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE); + expect_files_not_renamed("", "\n\n\n\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE); + expect_files_not_renamed("\n\n\n\n", "\r\n\r\n\r\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE); +} + +/* test that 100% renames and copies emit the correct patch file + * git diff --find-copies-harder -M100 -B100 \ + * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ + * 2bc7f351d20b53f1c72c16c4b036e491c478c49a + */ +void test_diff_rename__identical(void) +{ + const char *old_sha = INITIAL_COMMIT; + const char *new_sha = COPY_RENAME_COMMIT; + git_tree *old_tree, *new_tree; + git_diff *diff; + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT; + git_buf diff_buf = GIT_BUF_INIT; + const char *expected = + "diff --git a/serving.txt b/sixserving.txt\n" + "similarity index 100%\n" + "rename from serving.txt\n" + "rename to sixserving.txt\n" + "diff --git a/sevencities.txt b/songofseven.txt\n" + "similarity index 100%\n" + "copy from sevencities.txt\n" + "copy to songofseven.txt\n"; + + old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); + new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); + + diff_opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + find_opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED | + GIT_DIFF_FIND_EXACT_MATCH_ONLY; + + cl_git_pass(git_diff_tree_to_tree(&diff, + g_repo, old_tree, new_tree, &diff_opts)); + cl_git_pass(git_diff_find_similar(diff, &find_opts)); + + cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH)); + + cl_assert_equal_s(expected, diff_buf.ptr); + + git_buf_dispose(&diff_buf); + git_diff_free(diff); + git_tree_free(old_tree); + git_tree_free(new_tree); +} + +void test_diff_rename__rewrite_and_delete(void) +{ + const char *old_sha = RENAME_MODIFICATION_COMMIT; + const char *new_sha = REWRITE_DELETE_COMMIT; + git_tree *old_tree, *new_tree; + git_diff *diff; + git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT; + git_buf diff_buf = GIT_BUF_INIT; + const char *expected = + "diff --git a/ikeepsix.txt b/ikeepsix.txt\n" + "deleted file mode 100644\n" + "index eaf4a3e..0000000\n" + "--- a/ikeepsix.txt\n" + "+++ /dev/null\n" + "@@ -1,27 +0,0 @@\n" + "-I Keep Six Honest Serving-Men\n" + "-=============================\n" + "-\n" + "-She sends'em abroad on her own affairs,\n" + "- From the second she opens her eyes—\n" + "-One million Hows, two million Wheres,\n" + "-And seven million Whys!\n" + "-\n" + "-I let them rest from nine till five,\n" + "- For I am busy then,\n" + "-As well as breakfast, lunch, and tea,\n" + "- For they are hungry men.\n" + "-But different folk have different views;\n" + "-I know a person small—\n" + "-She keeps ten million serving-men,\n" + "-Who get no rest at all!\n" + "-\n" + "- -- Rudyard Kipling\n" + "-\n" + "-I KEEP six honest serving-men\n" + "- (They taught me all I knew);\n" + "-Their names are What and Why and When\n" + "- And How and Where and Who.\n" + "-I send them over land and sea,\n" + "- I send them east and west;\n" + "-But after they have worked for me,\n" + "- I give them all a rest.\n" + "diff --git a/songof7cities.txt b/songof7cities.txt\n" + "index 4210ffd..95ceb12 100644\n" + "--- a/songof7cities.txt\n" + "+++ b/songof7cities.txt\n" + "@@ -1,45 +1,45 @@\n" + "-The Song of Seven Cities\n" + "+THE SONG OF SEVEN CITIES\n" + " ------------------------\n" + " \n" + "-I WAS Lord of Cities very sumptuously builded.\n" + "-Seven roaring Cities paid me tribute from afar.\n" + "-Ivory their outposts were--the guardrooms of them gilded,\n" + "-And garrisoned with Amazons invincible in war.\n" + "-\n" + "-All the world went softly when it walked before my Cities--\n" + "-Neither King nor Army vexed my peoples at their toil,\n" + "-Never horse nor chariot irked or overbore my Cities,\n" + "-Never Mob nor Ruler questioned whence they drew their spoil.\n" + "-\n" + "-Banded, mailed and arrogant from sunrise unto sunset;\n" + "-Singing while they sacked it, they possessed the land at large.\n" + "-Yet when men would rob them, they resisted, they made onset\n" + "-And pierced the smoke of battle with a thousand-sabred charge.\n" + "-\n" + "-So they warred and trafficked only yesterday, my Cities.\n" + "-To-day there is no mark or mound of where my Cities stood.\n" + "-For the River rose at midnight and it washed away my Cities.\n" + "-They are evened with Atlantis and the towns before the Flood.\n" + "-\n" + "-Rain on rain-gorged channels raised the water-levels round them,\n" + "-Freshet backed on freshet swelled and swept their world from sight,\n" + "-Till the emboldened floods linked arms and, flashing forward, drowned them--\n" + "-Drowned my Seven Cities and their peoples in one night!\n" + "-\n" + "-Low among the alders lie their derelict foundations,\n" + "-The beams wherein they trusted and the plinths whereon they built--\n" + "-My rulers and their treasure and their unborn populations,\n" + "-Dead, destroyed, aborted, and defiled with mud and silt!\n" + "-\n" + "-The Daughters of the Palace whom they cherished in my Cities,\n" + "-My silver-tongued Princesses, and the promise of their May--\n" + "-Their bridegrooms of the June-tide--all have perished in my Cities,\n" + "-With the harsh envenomed virgins that can neither love nor play.\n" + "-\n" + "-I was Lord of Cities--I will build anew my Cities,\n" + "-Seven, set on rocks, above the wrath of any flood.\n" + "-Nor will I rest from search till I have filled anew my Cities\n" + "-With peoples undefeated of the dark, enduring blood.\n" + "+I WAS LORD OF CITIES VERY SUMPTUOUSLY BUILDED.\n" + "+SEVEN ROARING CITIES PAID ME TRIBUTE FROM AFAR.\n" + "+IVORY THEIR OUTPOSTS WERE--THE GUARDROOMS OF THEM GILDED,\n" + "+AND GARRISONED WITH AMAZONS INVINCIBLE IN WAR.\n" + "+\n" + "+ALL THE WORLD WENT SOFTLY WHEN IT WALKED BEFORE MY CITIES--\n" + "+NEITHER KING NOR ARMY VEXED MY PEOPLES AT THEIR TOIL,\n" + "+NEVER HORSE NOR CHARIOT IRKED OR OVERBORE MY CITIES,\n" + "+NEVER MOB NOR RULER QUESTIONED WHENCE THEY DREW THEIR SPOIL.\n" + "+\n" + "+BANDED, MAILED AND ARROGANT FROM SUNRISE UNTO SUNSET;\n" + "+SINGING WHILE THEY SACKED IT, THEY POSSESSED THE LAND AT LARGE.\n" + "+YET WHEN MEN WOULD ROB THEM, THEY RESISTED, THEY MADE ONSET\n" + "+AND PIERCED THE SMOKE OF BATTLE WITH A THOUSAND-SABRED CHARGE.\n" + "+\n" + "+SO THEY WARRED AND TRAFFICKED ONLY YESTERDAY, MY CITIES.\n" + "+TO-DAY THERE IS NO MARK OR MOUND OF WHERE MY CITIES STOOD.\n" + "+FOR THE RIVER ROSE AT MIDNIGHT AND IT WASHED AWAY MY CITIES.\n" + "+THEY ARE EVENED WITH ATLANTIS AND THE TOWNS BEFORE THE FLOOD.\n" + "+\n" + "+RAIN ON RAIN-GORGED CHANNELS RAISED THE WATER-LEVELS ROUND THEM,\n" + "+FRESHET BACKED ON FRESHET SWELLED AND SWEPT THEIR WORLD FROM SIGHT,\n" + "+TILL THE EMBOLDENED FLOODS LINKED ARMS AND, FLASHING FORWARD, DROWNED THEM--\n" + "+DROWNED MY SEVEN CITIES AND THEIR PEOPLES IN ONE NIGHT!\n" + "+\n" + "+LOW AMONG THE ALDERS LIE THEIR DERELICT FOUNDATIONS,\n" + "+THE BEAMS WHEREIN THEY TRUSTED AND THE PLINTHS WHEREON THEY BUILT--\n" + "+MY RULERS AND THEIR TREASURE AND THEIR UNBORN POPULATIONS,\n" + "+DEAD, DESTROYED, ABORTED, AND DEFILED WITH MUD AND SILT!\n" + "+\n" + "+THE DAUGHTERS OF THE PALACE WHOM THEY CHERISHED IN MY CITIES,\n" + "+MY SILVER-TONGUED PRINCESSES, AND THE PROMISE OF THEIR MAY--\n" + "+THEIR BRIDEGROOMS OF THE JUNE-TIDE--ALL HAVE PERISHED IN MY CITIES,\n" + "+WITH THE HARSH ENVENOMED VIRGINS THAT CAN NEITHER LOVE NOR PLAY.\n" + "+\n" + "+I WAS LORD OF CITIES--I WILL BUILD ANEW MY CITIES,\n" + "+SEVEN, SET ON ROCKS, ABOVE THE WRATH OF ANY FLOOD.\n" + "+NOR WILL I REST FROM SEARCH TILL I HAVE FILLED ANEW MY CITIES\n" + "+WITH PEOPLES UNDEFEATED OF THE DARK, ENDURING BLOOD.\n" + " \n" + " To the sound of trumpets shall their seed restore my Cities\n" + " Wealthy and well-weaponed, that once more may I behold\n"; + + old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); + new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); + + find_opts.flags = GIT_DIFF_FIND_RENAMES_FROM_REWRITES; + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, old_tree, new_tree, NULL)); + cl_git_pass(git_diff_find_similar(diff, &find_opts)); + + cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH)); + + cl_assert_equal_s(expected, diff_buf.ptr); + + git_buf_dispose(&diff_buf); + git_diff_free(diff); + git_tree_free(old_tree); + git_tree_free(new_tree); +} + +void test_diff_rename__delete_and_rename(void) +{ + const char *old_sha = RENAME_MODIFICATION_COMMIT; + const char *new_sha = DELETE_RENAME_COMMIT; + git_tree *old_tree, *new_tree; + git_diff *diff; + git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT; + git_buf diff_buf = GIT_BUF_INIT; + const char *expected = + "diff --git a/sixserving.txt b/sixserving.txt\n" + "deleted file mode 100644\n" + "index f90d4fc..0000000\n" + "--- a/sixserving.txt\n" + "+++ /dev/null\n" + "@@ -1,25 +0,0 @@\n" + "-I KEEP six honest serving-men\n" + "- (They taught me all I knew);\n" + "-Their names are What and Why and When\n" + "- And How and Where and Who.\n" + "-I send them over land and sea,\n" + "- I send them east and west;\n" + "-But after they have worked for me,\n" + "- I give them all a rest.\n" + "-\n" + "-I let them rest from nine till five,\n" + "- For I am busy then,\n" + "-As well as breakfast, lunch, and tea,\n" + "- For they are hungry men.\n" + "-But different folk have different views;\n" + "-I know a person small—\n" + "-She keeps ten million serving-men,\n" + "-Who get no rest at all!\n" + "-\n" + "-She sends'em abroad on her own affairs,\n" + "- From the second she opens her eyes—\n" + "-One million Hows, two million Wheres,\n" + "-And seven million Whys!\n" + "-\n" + "- -- Rudyard Kipling\n" + "-\n" + "diff --git a/songof7cities.txt b/sixserving.txt\n" + "similarity index 100%\n" + "rename from songof7cities.txt\n" + "rename to sixserving.txt\n"; + + old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); + new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); + + find_opts.flags = GIT_DIFF_FIND_RENAMES_FROM_REWRITES; + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, old_tree, new_tree, NULL)); + cl_git_pass(git_diff_find_similar(diff, &find_opts)); + + cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH)); + + cl_assert_equal_s(expected, diff_buf.ptr); + + git_buf_dispose(&diff_buf); + git_diff_free(diff); + git_tree_free(old_tree); + git_tree_free(new_tree); +} + +/* + * The break_rewrite branch contains a testcase reduced from + * a real-world scenario, rather than being "constructed" like + * the above tests seem to be. There are two commits layered + * on top of the repo's initial commit; the base commit which + * clears out the files from the initial commit and installs + * four files. And then there's the modification commit which + * mutates the files in such a way as to trigger the bug in + * libgit2. + * commit db98035f715427eef1f5e17f03e1801c05301e9e + * serving.txt (deleted) + * sevencities.txt (deleted) + * AAA (313 lines) + * BBB (314 lines) + * CCC (704 lines) + * DDD (314 lines, identical to BBB) + * commit 7e7bfb88ba9bc65fd700fee1819cf1c317aafa56 + * This deletes CCC and makes slight modifications + * to AAA, BBB, and DDD. The find_best_matches loop + * for git_diff_find_similar computes the following: + * CCC moved to AAA (similarity 91) + * CCC copied to AAA (similarity 91) + * DDD moved to BBB (similarity 52) + * CCC copied to BBB (similarity 90) + * BBB moved to DDD (similarity 52) + * CCC copied to DDD (similarity 90) + * The code to rewrite the diffs by resolving these + * copies/renames would resolve the BBB <-> DDD moves + * but then still leave BBB as a rename target for + * the deleted file CCC. Since the split flag on BBB + * was cleared, this would trigger an error. + */ +void test_diff_rename__break_rewrite(void) +{ + const char *old_sha = BREAK_REWRITE_BASE_COMMIT; + const char *new_sha = BREAK_REWRITE_COMMIT; + git_tree *old_tree, *new_tree; + git_diff *diff; + git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT; + + old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); + new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); + + find_opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES | GIT_DIFF_BREAK_REWRITES | GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY; + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, old_tree, new_tree, NULL)); + cl_git_pass(git_diff_find_similar(diff, &find_opts)); + + git_diff_free(diff); + git_tree_free(old_tree); + git_tree_free(new_tree); +} diff --git a/tests/libgit2/diff/stats.c b/tests/libgit2/diff/stats.c new file mode 100644 index 000000000..f69dba9b3 --- /dev/null +++ b/tests/libgit2/diff/stats.c @@ -0,0 +1,378 @@ +#include "clar.h" +#include "clar_libgit2.h" + +#include "commit.h" +#include "diff.h" +#include "diff_generate.h" + +static git_repository *_repo; +static git_diff_stats *_stats; + +void test_diff_stats__initialize(void) +{ + _repo = cl_git_sandbox_init("diff_format_email"); +} + +void test_diff_stats__cleanup(void) +{ + git_diff_stats_free(_stats); _stats = NULL; + cl_git_sandbox_cleanup(); +} + +static void diff_stats_from_commit_oid( + git_diff_stats **stats, const char *oidstr, bool rename) +{ + git_oid oid; + git_commit *commit; + git_diff *diff; + + git_oid_fromstr(&oid, oidstr); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); + cl_git_pass(git_diff__commit(&diff, _repo, commit, NULL)); + if (rename) + cl_git_pass(git_diff_find_similar(diff, NULL)); + cl_git_pass(git_diff_get_stats(stats, diff)); + + git_diff_free(diff); + git_commit_free(commit); +} + +void test_diff_stats__stat(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " file1.txt | 8 +++++---\n" \ + " 1 file changed, 5 insertions(+), 3 deletions(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", false); + + cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(5, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(3, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); + cl_assert(strcmp(buf.ptr, stat) == 0); + git_buf_dispose(&buf); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 80)); + cl_assert(strcmp(buf.ptr, stat) == 0); + git_buf_dispose(&buf); +} + +void test_diff_stats__multiple_hunks(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " file2.txt | 5 +++--\n" \ + " file3.txt | 6 ++++--\n" \ + " 2 files changed, 7 insertions(+), 4 deletions(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "cd471f0d8770371e1bc78bcbb38db4c7e4106bd2", false); + + cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(7, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(4, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__numstat(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + "3 2 file2.txt\n" + "4 2 file3.txt\n"; + + diff_stats_from_commit_oid( + &_stats, "cd471f0d8770371e1bc78bcbb38db4c7e4106bd2", false); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_NUMBER, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__shortstat(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " 1 file changed, 5 insertions(+), 3 deletions(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", false); + + cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(5, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(3, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_SHORT, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__shortstat_noinsertions(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " 1 file changed, 2 deletions(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "06b7b69a62cbd1e53c6c4e0c3f16473dcfdb4af6", false); + + cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(0, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(2, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_SHORT, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__shortstat_nodeletions(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " 1 file changed, 3 insertions(+)\n"; + + diff_stats_from_commit_oid( + &_stats, "5219b9784f9a92d7bd7cb567a6d6a21bfb86697e", false); + + cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(3, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(0, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_SHORT, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__rename(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " file2.txt => file2.txt.renamed | 1 +\n" + " file3.txt => file3.txt.renamed | 4 +++-\n" + " 2 files changed, 4 insertions(+), 1 deletion(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "8947a46e2097638ca6040ad4877246f4186ec3bd", true); + + cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(4, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(1, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__rename_nochanges(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " file2.txt.renamed => file2.txt.renamed2 | 0\n" + " file3.txt.renamed => file3.txt.renamed2 | 0\n" + " 2 files changed, 0 insertions(+), 0 deletions(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "3991dce9e71a0641ca49a6a4eea6c9e7ff402ed4", true); + + cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(0, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(0, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__rename_and_modifiy(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " file2.txt.renamed2 | 2 +-\n" + " file3.txt.renamed2 => file3.txt.renamed | 0\n" + " 2 files changed, 1 insertion(+), 1 deletion(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "4ca10087e696d2ba78d07b146a118e9a7096ed4f", true); + + cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(1, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(1, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__rename_in_subdirectory(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " dir/{orig.txt => renamed.txt} | 0\n" + " 1 file changed, 0 insertions(+), 0 deletions(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "0db2a262bc8c5c3cba55254730045a8258da7a37", true); + + cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(0, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(0, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__rename_no_find(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " file2.txt | 5 -----\n" + " file2.txt.renamed | 6 ++++++\n" + " file3.txt | 5 -----\n" + " file3.txt.renamed | 7 +++++++\n" + " 4 files changed, 13 insertions(+), 10 deletions(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "8947a46e2097638ca6040ad4877246f4186ec3bd", false); + + cl_assert_equal_sz(4, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(13, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(10, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__rename_nochanges_no_find(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " file2.txt.renamed | 6 ------\n" + " file2.txt.renamed2 | 6 ++++++\n" + " file3.txt.renamed | 7 -------\n" + " file3.txt.renamed2 | 7 +++++++\n" + " 4 files changed, 13 insertions(+), 13 deletions(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "3991dce9e71a0641ca49a6a4eea6c9e7ff402ed4", false); + + cl_assert_equal_sz(4, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(13, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(13, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__rename_and_modify_no_find(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " file2.txt.renamed2 | 2 +-\n" + " file3.txt.renamed | 7 +++++++\n" + " file3.txt.renamed2 | 7 -------\n" + " 3 files changed, 8 insertions(+), 8 deletions(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "4ca10087e696d2ba78d07b146a118e9a7096ed4f", false); + + cl_assert_equal_sz(3, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(8, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(8, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__binary(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " binary.bin | Bin 3 -> 5 bytes\n" + " 1 file changed, 0 insertions(+), 0 deletions(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "8d7523f6fcb2404257889abe0d96f093d9f524f9", false); + + cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(0, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(0, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__binary_numstat(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + "- - binary.bin\n"; + + diff_stats_from_commit_oid( + &_stats, "8d7523f6fcb2404257889abe0d96f093d9f524f9", false); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_NUMBER, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__mode_change(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " file1.txt.renamed | 0\n" \ + " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ + " mode change 100644 => 100755 file1.txt.renamed\n"; + + diff_stats_from_commit_oid( + &_stats, "7ade76dd34bba4733cf9878079f9fd4a456a9189", false); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY, 0)); + cl_assert_equal_s(stat, buf.ptr); + git_buf_dispose(&buf); +} + +void test_diff_stats__new_file(void) +{ + git_diff *diff; + git_buf buf = GIT_BUF_INIT; + + const char *input = + "---\n" + " Gurjeet Singh | 1 +\n" + " 1 file changed, 1 insertion(+)\n" + " create mode 100644 Gurjeet Singh\n" + "\n" + "diff --git a/Gurjeet Singh b/Gurjeet Singh\n" + "new file mode 100644\n" + "index 0000000..6d0ecfd\n" + "--- /dev/null\n" + "+++ b/Gurjeet Singh \n" + "@@ -0,0 +1 @@\n" + "+I'm about to try git send-email\n" + "-- \n" + "2.21.0\n"; + + const char *stat = + " Gurjeet Singh | 1 +\n" + " 1 file changed, 1 insertion(+)\n" + " create mode 100644 Gurjeet Singh\n"; + + cl_git_pass(git_diff_from_buffer(&diff, input, strlen(input))); + cl_git_pass(git_diff_get_stats(&_stats, diff)); + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY, 0)); + cl_assert_equal_s(stat, buf.ptr); + + git_buf_dispose(&buf); + git_diff_free(diff); +} diff --git a/tests/libgit2/diff/submodules.c b/tests/libgit2/diff/submodules.c new file mode 100644 index 000000000..0436ca5c8 --- /dev/null +++ b/tests/libgit2/diff/submodules.c @@ -0,0 +1,543 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "posix.h" +#include "diff_helpers.h" +#include "../submodule/submodule_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_submodules__initialize(void) +{ +} + +void test_diff_submodules__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +#define get_buf_ptr(buf) ((buf)->size ? (buf)->ptr : NULL) + +static void check_diff_patches_at_line( + git_diff *diff, const char **expected, + const char *file, const char *func, int line) +{ + const git_diff_delta *delta; + git_patch *patch = NULL; + size_t d, num_d = git_diff_num_deltas(diff); + git_buf buf = GIT_BUF_INIT; + + for (d = 0; d < num_d; ++d, git_patch_free(patch)) { + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + cl_assert((delta = git_patch_get_delta(patch)) != NULL); + + if (delta->status == GIT_DELTA_UNMODIFIED) { + cl_assert_at_line(expected[d] == NULL, file, func, line); + continue; + } + + if (expected[d] && !strcmp(expected[d], "")) + continue; + if (expected[d] && !strcmp(expected[d], "")) { + cl_assert_at_line(delta->status == GIT_DELTA_UNTRACKED, file, func, line); + continue; + } + if (expected[d] && !strcmp(expected[d], "")) { + cl_git_pass(git_patch_to_buf(&buf, patch)); + cl_assert_at_line(!strcmp(expected[d], ""), file, func, line); + } + + cl_git_pass(git_patch_to_buf(&buf, patch)); + + clar__assert_equal( + file, func, line, "expected diff did not match actual diff", 1, + "%s", expected[d], get_buf_ptr(&buf)); + git_buf_dispose(&buf); + } + + cl_assert_at_line(expected[d] && !strcmp(expected[d], ""), file, func, line); +} + +#define check_diff_patches(diff, exp) \ + check_diff_patches_at_line(diff, exp, __FILE__, __func__, __LINE__) + +void test_diff_submodules__unmodified_submodule(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + static const char *expected[] = { + "", /* .gitmodules */ + NULL, /* added */ + NULL, /* ignored */ + "diff --git a/modified b/modified\nindex 092bfb9..452216e 100644\n--- a/modified\n+++ b/modified\n@@ -1 +1,2 @@\n-yo\n+changed\n+\n", /* modified */ + NULL, /* testrepo.git */ + NULL, /* unmodified */ + NULL, /* untracked */ + "" + }; + + g_repo = setup_fixture_submodules(); + + opts.flags = GIT_DIFF_INCLUDE_IGNORED | + GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_INCLUDE_UNMODIFIED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected); + git_diff_free(diff); +} + +void test_diff_submodules__dirty_submodule(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + static const char *expected[] = { + "", /* .gitmodules */ + NULL, /* added */ + NULL, /* ignored */ + "diff --git a/modified b/modified\nindex 092bfb9..452216e 100644\n--- a/modified\n+++ b/modified\n@@ -1 +1,2 @@\n-yo\n+changed\n+\n", /* modified */ + "diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */ + NULL, /* unmodified */ + NULL, /* untracked */ + "" + }; + + g_repo = setup_fixture_submodules(); + + cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); + cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before"); + + opts.flags = GIT_DIFF_INCLUDE_IGNORED | + GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_INCLUDE_UNMODIFIED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected); + git_diff_free(diff); +} + +void test_diff_submodules__dirty_submodule_2(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL, *diff2 = NULL; + char *smpath = "testrepo"; + static const char *expected_none[] = { + "" + }; + static const char *expected_dirty[] = { + "diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */ + "" + }; + + g_repo = setup_fixture_submodules(); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_SHOW_UNTRACKED_CONTENT | + GIT_DIFF_RECURSE_UNTRACKED_DIRS | + GIT_DIFF_DISABLE_PATHSPEC_MATCH; + opts.old_prefix = "a"; opts.new_prefix = "b"; + opts.pathspec.count = 1; + opts.pathspec.strings = &smpath; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_none); + git_diff_free(diff); + + cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); + cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + + { + git_tree *head; + + cl_git_pass(git_repository_head_tree(&head, g_repo)); + cl_git_pass(git_diff_tree_to_index(&diff2, g_repo, head, NULL, &opts)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_free(diff2); + git_tree_free(head); + + check_diff_patches(diff, expected_dirty); + } + + git_diff_free(diff); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_free(diff); +} + +void test_diff_submodules__submod2_index_to_wd(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + static const char *expected[] = { + "", /* .gitmodules */ + "", /* not-submodule */ + "", /* not */ + "diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */ + "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ + "", /* sm_changed_head- */ + "", /* sm_changed_head_ */ + "diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */ + "diff --git a/sm_changed_untracked_file b/sm_changed_untracked_file\nindex 4800958..4800958 160000\n--- a/sm_changed_untracked_file\n+++ b/sm_changed_untracked_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_untracked_file */ + "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */ + "" + }; + + g_repo = setup_fixture_submod2(); + + /* bracket existing submodule with similarly named items */ + cl_git_mkfile("submod2/sm_changed_head-", "hello"); + cl_git_mkfile("submod2/sm_changed_head_", "hello"); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected); + git_diff_free(diff); +} + +void test_diff_submodules__submod2_head_to_index(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_tree *head; + git_diff *diff = NULL; + static const char *expected[] = { + "", /* .gitmodules */ + "diff --git a/sm_added_and_uncommited b/sm_added_and_uncommited\nnew file mode 160000\nindex 0000000..4800958\n--- /dev/null\n+++ b/sm_added_and_uncommited\n@@ -0,0 +1 @@\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n", /* sm_added_and_uncommited */ + "" + }; + + g_repo = setup_fixture_submod2(); + + cl_git_pass(git_repository_head_tree(&head, g_repo)); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, head, NULL, &opts)); + check_diff_patches(diff, expected); + git_diff_free(diff); + + git_tree_free(head); +} + +void test_diff_submodules__invalid_cache(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_submodule *sm; + char *smpath = "sm_changed_head"; + git_repository *smrepo; + git_index *smindex; + static const char *expected_baseline[] = { + "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ + "" + }; + static const char *expected_unchanged[] = { "" }; + static const char *expected_dirty[] = { + "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247-dirty\n", + "" + }; + static const char *expected_moved[] = { + "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..7002348 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 700234833f6ccc20d744b238612646be071acaae\n", + "" + }; + static const char *expected_moved_dirty[] = { + "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..7002348 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 700234833f6ccc20d744b238612646be071acaae-dirty\n", + "" + }; + + g_repo = setup_fixture_submod2(); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + opts.pathspec.count = 1; + opts.pathspec.strings = &smpath; + + /* baseline */ + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_baseline); + git_diff_free(diff); + + /* update index with new HEAD */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath)); + cl_git_pass(git_submodule_add_to_index(sm, 1)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_free(diff); + + /* create untracked file in submodule working directory */ + cl_git_mkfile("submod2/sm_changed_head/new_around_here", "hello"); + git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_NONE); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_free(diff); + + git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_UNTRACKED); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_free(diff); + + /* modify tracked file in submodule working directory */ + cl_git_append2file( + "submod2/sm_changed_head/file_to_modify", "\nmore stuff\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_free(diff); + + git_submodule_free(sm); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_free(diff); + + git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_DIRTY); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_free(diff); + + /* add file to index in submodule */ + cl_git_pass(git_submodule_open(&smrepo, sm)); + cl_git_pass(git_repository_index(&smindex, smrepo)); + cl_git_pass(git_index_add_bypath(smindex, "file_to_modify")); + + git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_UNTRACKED); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_dirty); + git_diff_free(diff); + + git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_DIRTY); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_free(diff); + + /* commit changed index of submodule */ + cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Move it"); + + git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_DIRTY); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_moved); + git_diff_free(diff); + + git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_ALL); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_unchanged); + git_diff_free(diff); + + git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_NONE); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_moved_dirty); + git_diff_free(diff); + + p_unlink("submod2/sm_changed_head/new_around_here"); + + git_submodule_free(sm); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_moved); + git_diff_free(diff); + + git_index_free(smindex); + git_repository_free(smrepo); +} + +void test_diff_submodules__diff_ignore_options(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_config *cfg; + static const char *expected_normal[] = { + "", /* .gitmodules */ + "", /* not-submodule */ + "", /* not */ + "diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */ + "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ + "diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */ + "diff --git a/sm_changed_untracked_file b/sm_changed_untracked_file\nindex 4800958..4800958 160000\n--- a/sm_changed_untracked_file\n+++ b/sm_changed_untracked_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_untracked_file */ + "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */ + "" + }; + static const char *expected_ignore_all[] = { + "", /* .gitmodules */ + "", /* not-submodule */ + "", /* not */ + "" + }; + static const char *expected_ignore_dirty[] = { + "", /* .gitmodules */ + "", /* not-submodule */ + "", /* not */ + "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */ + "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */ + "" + }; + + g_repo = setup_fixture_submod2(); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + opts.old_prefix = "a"; opts.new_prefix = "b"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_normal); + git_diff_free(diff); + + opts.flags |= GIT_DIFF_IGNORE_SUBMODULES; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_all); + git_diff_free(diff); + + opts.flags &= ~GIT_DIFF_IGNORE_SUBMODULES; + opts.ignore_submodules = GIT_SUBMODULE_IGNORE_ALL; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_all); + git_diff_free(diff); + + opts.ignore_submodules = GIT_SUBMODULE_IGNORE_DIRTY; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_dirty); + git_diff_free(diff); + + opts.ignore_submodules = 0; + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, "diff.ignoreSubmodules", false)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_normal); + git_diff_free(diff); + + cl_git_pass(git_config_set_bool(cfg, "diff.ignoreSubmodules", true)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_all); + git_diff_free(diff); + + cl_git_pass(git_config_set_string(cfg, "diff.ignoreSubmodules", "none")); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_normal); + git_diff_free(diff); + + cl_git_pass(git_config_set_string(cfg, "diff.ignoreSubmodules", "dirty")); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + check_diff_patches(diff, expected_ignore_dirty); + git_diff_free(diff); + + git_config_free(cfg); +} + +void test_diff_submodules__skips_empty_includes_used(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp; + + /* A side effect of of Git's handling of untracked directories and + * auto-ignoring of ".git" entries is that a newly initialized Git + * repo inside another repo will be skipped by diff, but one that + * actually has a commit it in will show as an untracked directory. + * Let's make sure that works. + */ + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(0, exp.files); + git_diff_free(diff); + + { + git_repository *r2; + cl_git_pass(git_repository_init(&r2, "empty_standard_repo/subrepo", 0)); + git_repository_free(r2); + } + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + git_diff_free(diff); + + cl_git_mkfile("empty_standard_repo/subrepo/README.txt", "hello\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + git_diff_free(diff); +} + +static void ensure_submodules_found( + git_repository *repo, + const char **paths, + size_t cnt) +{ + git_diff *diff = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + const git_diff_delta *delta; + size_t i, pathlen; + + opts.pathspec.strings = (char **)paths; + opts.pathspec.count = cnt; + + git_diff_index_to_workdir(&diff, repo, NULL, &opts); + + cl_assert_equal_i(cnt, git_diff_num_deltas(diff)); + + for (i = 0; i < cnt; i++) { + delta = git_diff_get_delta(diff, i); + + /* ensure that the given path is returned w/o trailing slashes. */ + pathlen = strlen(opts.pathspec.strings[i]); + + while (pathlen && opts.pathspec.strings[i][pathlen - 1] == '/') + pathlen--; + + cl_assert_equal_strn(opts.pathspec.strings[i], delta->new_file.path, pathlen); + } + + git_diff_free(diff); +} + +void test_diff_submodules__can_be_identified_by_trailing_slash_in_pathspec(void) +{ + const char *one_path_without_slash[] = { "sm_changed_head" }; + const char *one_path_with_slash[] = { "sm_changed_head/" }; + const char *many_paths_without_slashes[] = { "sm_changed_head", "sm_changed_index" }; + const char *many_paths_with_slashes[] = { "sm_changed_head/", "sm_changed_index/" }; + + g_repo = setup_fixture_submod2(); + + ensure_submodules_found(g_repo, one_path_without_slash, ARRAY_SIZE(one_path_without_slash)); + ensure_submodules_found(g_repo, one_path_with_slash, ARRAY_SIZE(one_path_with_slash)); + ensure_submodules_found(g_repo, many_paths_without_slashes, ARRAY_SIZE(many_paths_without_slashes)); + ensure_submodules_found(g_repo, many_paths_with_slashes, ARRAY_SIZE(many_paths_with_slashes)); +} diff --git a/tests/libgit2/diff/tree.c b/tests/libgit2/diff/tree.c new file mode 100644 index 000000000..e03ee7b22 --- /dev/null +++ b/tests/libgit2/diff/tree.c @@ -0,0 +1,575 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; +static git_diff_options opts; +static git_diff *diff; +static git_tree *a, *b; +static diff_expects expect; + +void test_diff_tree__initialize(void) +{ + cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION)); + + memset(&expect, 0, sizeof(expect)); + + diff = NULL; + a = NULL; + b = NULL; +} + +void test_diff_tree__cleanup(void) +{ + git_diff_free(diff); + git_tree_free(a); + git_tree_free(b); + + cl_git_sandbox_cleanup(); + +} + +void test_diff_tree__0(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "605812a"; + const char *b_commit = "370fe9ec22"; + const char *c_commit = "f5b0af1fb4f5c"; + git_tree *c; + + g_repo = cl_git_sandbox_init("attr"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + cl_assert((c = resolve_commit_oid_to_tree(g_repo, c_commit)) != NULL); + + opts.context_lines = 1; + opts.interhunk_lines = 1; + + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); + + cl_assert_equal_i(5, expect.files); + cl_assert_equal_i(2, expect.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, expect.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]); + + cl_assert_equal_i(5, expect.hunks); + + cl_assert_equal_i(7 + 24 + 1 + 6 + 6, expect.lines); + cl_assert_equal_i(1, expect.line_ctxt); + cl_assert_equal_i(24 + 1 + 5 + 5, expect.line_adds); + cl_assert_equal_i(7 + 1, expect.line_dels); + + git_diff_free(diff); + diff = NULL; + + memset(&expect, 0, sizeof(expect)); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, c, b, &opts)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); + + cl_assert_equal_i(2, expect.files); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]); + + cl_assert_equal_i(2, expect.hunks); + + cl_assert_equal_i(8 + 15, expect.lines); + cl_assert_equal_i(1, expect.line_ctxt); + cl_assert_equal_i(1, expect.line_adds); + cl_assert_equal_i(7 + 14, expect.line_dels); + + git_tree_free(c); +} + +#define DIFF_OPTS(FLAGS, CTXT) \ + {GIT_DIFF_OPTIONS_VERSION, (FLAGS), GIT_SUBMODULE_IGNORE_UNSPECIFIED, \ + {NULL,0}, NULL, NULL, NULL, (CTXT), 1} + +void test_diff_tree__options(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "6bab5c79cd5140d0"; + const char *b_commit = "605812ab7fe421fdd"; + const char *c_commit = "f5b0af1fb4f5"; + const char *d_commit = "a97cc019851"; + git_tree *c, *d; + diff_expects actual; + int test_ab_or_cd[] = { 0, 0, 0, 0, 1, 1, 1, 1, 1 }; + git_diff_options test_options[] = { + /* a vs b tests */ + DIFF_OPTS(GIT_DIFF_NORMAL, 1), + DIFF_OPTS(GIT_DIFF_NORMAL, 3), + DIFF_OPTS(GIT_DIFF_REVERSE, 2), + DIFF_OPTS(GIT_DIFF_FORCE_TEXT, 2), + /* c vs d tests */ + DIFF_OPTS(GIT_DIFF_NORMAL, 3), + DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE, 3), + DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE_CHANGE, 3), + DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE_EOL, 3), + DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE | GIT_DIFF_REVERSE, 1), + }; + + /* to generate these values: + * - cd to tests/resources/attr, + * - mv .gitted .git + * - git diff [options] 6bab5c79cd5140d0 605812ab7fe421fdd + * - mv .git .gitted + */ +#define EXPECT_STATUS_ADM(ADDS,DELS,MODS) { 0, ADDS, DELS, MODS, 0, 0, 0, 0, 0 } + + diff_expects test_expects[] = { + /* a vs b tests */ + { 5, 0, EXPECT_STATUS_ADM(3, 0, 2), 4, 0, 0, 51, 2, 46, 3 }, + { 5, 0, EXPECT_STATUS_ADM(3, 0, 2), 4, 0, 0, 53, 4, 46, 3 }, + { 5, 0, EXPECT_STATUS_ADM(0, 3, 2), 4, 0, 0, 52, 3, 3, 46 }, + { 5, 0, EXPECT_STATUS_ADM(3, 0, 2), 5, 0, 0, 54, 3, 47, 4 }, + /* c vs d tests */ + { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 22, 9, 10, 3 }, + { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 19, 12, 7, 0 }, + { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 20, 11, 8, 1 }, + { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 20, 11, 8, 1 }, + { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 18, 11, 0, 7 }, + { 0 }, + }; + diff_expects *expected; + int i, j; + + g_repo = cl_git_sandbox_init("attr"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + cl_assert((c = resolve_commit_oid_to_tree(g_repo, c_commit)) != NULL); + cl_assert((d = resolve_commit_oid_to_tree(g_repo, d_commit)) != NULL); + + for (i = 0; test_expects[i].files > 0; i++) { + memset(&actual, 0, sizeof(actual)); /* clear accumulator */ + opts = test_options[i]; + + if (test_ab_or_cd[i] == 0) + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); + else + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, c, d, &opts)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &actual)); + + expected = &test_expects[i]; + cl_assert_equal_i(actual.files, expected->files); + for (j = GIT_DELTA_UNMODIFIED; j <= GIT_DELTA_TYPECHANGE; ++j) + cl_assert_equal_i(expected->file_status[j], actual.file_status[j]); + cl_assert_equal_i(actual.hunks, expected->hunks); + cl_assert_equal_i(actual.lines, expected->lines); + cl_assert_equal_i(actual.line_ctxt, expected->line_ctxt); + cl_assert_equal_i(actual.line_adds, expected->line_adds); + cl_assert_equal_i(actual.line_dels, expected->line_dels); + + git_diff_free(diff); + diff = NULL; + } + + git_tree_free(c); + git_tree_free(d); +} + +void test_diff_tree__bare(void) +{ + const char *a_commit = "8496071c1b46c85"; + const char *b_commit = "be3563ae3f79"; + + g_repo = cl_git_sandbox_init("testrepo.git"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + + opts.context_lines = 1; + opts.interhunk_lines = 1; + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); + + cl_assert_equal_i(3, expect.files); + cl_assert_equal_i(2, expect.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, expect.file_status[GIT_DELTA_MODIFIED]); + + cl_assert_equal_i(3, expect.hunks); + + cl_assert_equal_i(4, expect.lines); + cl_assert_equal_i(0, expect.line_ctxt); + cl_assert_equal_i(3, expect.line_adds); + cl_assert_equal_i(1, expect.line_dels); +} + +void test_diff_tree__merge(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "605812a"; + const char *b_commit = "370fe9ec22"; + const char *c_commit = "f5b0af1fb4f5c"; + git_tree *c; + git_diff *diff1 = NULL, *diff2 = NULL; + + g_repo = cl_git_sandbox_init("attr"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + cl_assert((c = resolve_commit_oid_to_tree(g_repo, c_commit)) != NULL); + + cl_git_pass(git_diff_tree_to_tree(&diff1, g_repo, a, b, NULL)); + + cl_git_pass(git_diff_tree_to_tree(&diff2, g_repo, c, b, NULL)); + + git_tree_free(c); + + cl_git_pass(git_diff_merge(diff1, diff2)); + + git_diff_free(diff2); + + cl_git_pass(git_diff_foreach( + diff1, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); + + cl_assert_equal_i(6, expect.files); + cl_assert_equal_i(2, expect.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, expect.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, expect.file_status[GIT_DELTA_MODIFIED]); + + cl_assert_equal_i(6, expect.hunks); + + cl_assert_equal_i(59, expect.lines); + cl_assert_equal_i(1, expect.line_ctxt); + cl_assert_equal_i(36, expect.line_adds); + cl_assert_equal_i(22, expect.line_dels); + + git_diff_free(diff1); +} + +void test_diff_tree__larger_hunks(void) +{ + const char *a_commit = "d70d245ed97ed2aa596dd1af6536e4bfdb047b69"; + const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10"; + size_t d, num_d, h, num_h, l, num_l; + git_patch *patch; + const git_diff_hunk *hunk; + const git_diff_line *line; + + g_repo = cl_git_sandbox_init("diff"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + + opts.context_lines = 1; + opts.interhunk_lines = 0; + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); + + num_d = git_diff_num_deltas(diff); + for (d = 0; d < num_d; ++d) { + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + cl_assert(patch); + + num_h = git_patch_num_hunks(patch); + for (h = 0; h < num_h; h++) { + cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h)); + + for (l = 0; l < num_l; ++l) { + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l)); + cl_assert(line); + } + + cl_git_fail(git_patch_get_line_in_hunk(&line, patch, h, num_l)); + } + + cl_git_fail(git_patch_get_hunk(&hunk, &num_l, patch, num_h)); + + git_patch_free(patch); + } + + cl_git_fail(git_patch_from_diff(&patch, diff, num_d)); + + cl_assert_equal_i(2, (int)num_d); +} + +void test_diff_tree__checks_options_version(void) +{ + const char *a_commit = "8496071c1b46c85"; + const char *b_commit = "be3563ae3f79"; + const git_error *err; + + g_repo = cl_git_sandbox_init("testrepo.git"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + + opts.version = 0; + cl_git_fail(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); + err = git_error_last(); + cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); + + git_error_clear(); + opts.version = 1024; + cl_git_fail(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); + err = git_error_last(); +} + +static void process_tree_to_tree_diffing( + const char *old_commit, + const char *new_commit) +{ + g_repo = cl_git_sandbox_init("unsymlinked.git"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, old_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, new_commit)) != NULL); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, NULL, &expect)); +} + +void test_diff_tree__symlink_blob_mode_changed_to_regular_file(void) +{ + /* + * $ git diff 7fccd7..806999 + * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h + * deleted file mode 120000 + * index 19bf568..0000000 + * --- a/include/Nu/Nu.h + * +++ /dev/null + * @@ -1 +0,0 @@ + * -../../objc/Nu.h + * \ No newline at end of file + * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h + * new file mode 100644 + * index 0000000..f9e6561 + * --- /dev/null + * +++ b/include/Nu/Nu.h + * @@ -0,0 +1 @@ + * +awesome content + * diff --git a/objc/Nu.h b/objc/Nu.h + * deleted file mode 100644 + * index f9e6561..0000000 + * --- a/objc/Nu.h + * +++ /dev/null + * @@ -1 +0,0 @@ + * -awesome content + */ + + process_tree_to_tree_diffing("7fccd7", "806999"); + + cl_assert_equal_i(3, expect.files); + cl_assert_equal_i(2, expect.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, expect.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_TYPECHANGE]); +} + +void test_diff_tree__symlink_blob_mode_changed_to_regular_file_as_typechange(void) +{ + /* + * $ git diff 7fccd7..a8595c + * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h + * deleted file mode 120000 + * index 19bf568..0000000 + * --- a/include/Nu/Nu.h + * +++ /dev/null + * @@ -1 +0,0 @@ + * -../../objc/Nu.h + * \ No newline at end of file + * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h + * new file mode 100755 + * index 0000000..f9e6561 + * --- /dev/null + * +++ b/include/Nu/Nu.h + * @@ -0,0 +1 @@ + * +awesome content + * diff --git a/objc/Nu.h b/objc/Nu.h + * deleted file mode 100644 + * index f9e6561..0000000 + * --- a/objc/Nu.h + * +++ /dev/null + * @@ -1 +0,0 @@ + * -awesome content + */ + + opts.flags = GIT_DIFF_INCLUDE_TYPECHANGE; + process_tree_to_tree_diffing("7fccd7", "a8595c"); + + cl_assert_equal_i(2, expect.files); + cl_assert_equal_i(1, expect.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, expect.file_status[GIT_DELTA_TYPECHANGE]); +} + +void test_diff_tree__regular_blob_mode_changed_to_executable_file(void) +{ + /* + * $ git diff 806999..a8595c + * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h + * old mode 100644 + * new mode 100755 + */ + + process_tree_to_tree_diffing("806999", "a8595c"); + + cl_assert_equal_i(1, expect.files); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, expect.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_TYPECHANGE]); +} + +void test_diff_tree__issue_1397(void) +{ + /* this test shows that it is not needed */ + + g_repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, "8a7ef04")) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, "7f483a7")) != NULL); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); + + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); + + cl_assert_equal_i(1, expect.files); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, expect.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_TYPECHANGE]); +} + +static void set_config_int(git_repository *repo, const char *name, int value) +{ + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_int32(cfg, name, value)); + git_config_free(cfg); +} + +void test_diff_tree__diff_configs(void) +{ + const char *a_commit = "d70d245e"; + const char *b_commit = "7a9e0b02"; + + g_repo = cl_git_sandbox_init("diff"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL)); + + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); + + cl_assert_equal_i(2, expect.files); + cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(6, expect.hunks); + cl_assert_equal_i(55, expect.lines); + cl_assert_equal_i(33, expect.line_ctxt); + cl_assert_equal_i(7, expect.line_adds); + cl_assert_equal_i(15, expect.line_dels); + + git_diff_free(diff); + diff = NULL; + + set_config_int(g_repo, "diff.context", 1); + + memset(&expect, 0, sizeof(expect)); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL)); + + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); + + cl_assert_equal_i(2, expect.files); + cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(7, expect.hunks); + cl_assert_equal_i(34, expect.lines); + cl_assert_equal_i(12, expect.line_ctxt); + cl_assert_equal_i(7, expect.line_adds); + cl_assert_equal_i(15, expect.line_dels); + + git_diff_free(diff); + diff = NULL; + + set_config_int(g_repo, "diff.context", 0); + set_config_int(g_repo, "diff.noprefix", 1); + + memset(&expect, 0, sizeof(expect)); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL)); + + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); + + cl_assert_equal_i(2, expect.files); + cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(7, expect.hunks); + cl_assert_equal_i(22, expect.lines); + cl_assert_equal_i(0, expect.line_ctxt); + cl_assert_equal_i(7, expect.line_adds); + cl_assert_equal_i(15, expect.line_dels); +} + +void test_diff_tree__diff_tree_with_empty_dir_entry_succeeds(void) +{ + const char *content = "This is a blob\n"; + const git_diff_delta *delta; + git_oid empty_tree, invalid_tree, blob; + git_buf patch = GIT_BUF_INIT; + git_treebuilder *builder; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_blob_create_from_buffer(&blob, g_repo, content, strlen(content))); + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + cl_git_pass(git_treebuilder_write(&empty_tree, builder)); + cl_git_pass(git_treebuilder_insert(NULL, builder, "empty_tree", &empty_tree, GIT_FILEMODE_TREE)); + cl_git_pass(git_treebuilder_insert(NULL, builder, "blob", &blob, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_write(&invalid_tree, builder)); + + cl_git_pass(git_tree_lookup(&a, g_repo, &empty_tree)); + cl_git_pass(git_tree_lookup(&b, g_repo, &invalid_tree)); + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL)); + + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); + cl_assert_equal_i(1, expect.files); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, expect.hunks); + cl_assert_equal_i(1, expect.lines); + cl_assert_equal_i(0, expect.line_ctxt); + cl_assert_equal_i(1, expect.line_adds); + cl_assert_equal_i(0, expect.line_dels); + + cl_git_pass(git_diff_to_buf(&patch, diff, GIT_DIFF_FORMAT_PATCH)); + cl_assert_equal_s(patch.ptr, + "diff --git a/blob b/blob\n" + "new file mode 100644\n" + "index 0000000..bbf2e80\n" + "--- /dev/null\n" + "+++ b/blob\n" + "@@ -0,0 +1 @@\n" + "+This is a blob\n"); + + cl_assert_equal_i(git_diff_num_deltas(diff), 1); + delta = git_diff_get_delta(diff, 0); + cl_assert_equal_s(delta->new_file.path, "blob"); + + git_treebuilder_free(builder); + git_buf_dispose(&patch); +} diff --git a/tests/libgit2/diff/workdir.c b/tests/libgit2/diff/workdir.c new file mode 100644 index 000000000..cdf53faa1 --- /dev/null +++ b/tests/libgit2/diff/workdir.c @@ -0,0 +1,2242 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" +#include "repository.h" +#include "index.h" +#include "git2/sys/diff.h" +#include "../checkout/checkout_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_workdir__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_workdir__to_index(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp; + int use_iterator; + + g_repo = cl_git_sandbox_init("status"); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + else + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + /* to generate these values: + * - cd to tests/resources/status, + * - mv .gitted .git + * - git diff --name-status + * - git diff + * - mv .git .gitted + */ + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); + + cl_assert_equal_i(8, exp.hunks); + + cl_assert_equal_i(14, exp.lines); + cl_assert_equal_i(5, exp.line_ctxt); + cl_assert_equal_i(4, exp.line_adds); + cl_assert_equal_i(5, exp.line_dels); + } + + { + git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT; + cl_git_pass(git_diff_get_perfdata(&perf, diff)); + cl_assert_equal_sz( + 13 /* in root */ + 3 /* in subdir */, perf.stat_calls); + cl_assert_equal_sz(5, perf.oid_calculations); + } + + git_diff_free(diff); +} + +void test_diff_workdir__to_index_with_conflicts(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_index *index; + git_index_entry our_entry = {{0}}, their_entry = {{0}}; + diff_expects exp = {0}; + + g_repo = cl_git_sandbox_init("status"); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + + /* Adding an entry that represents a rename gets two files in conflict */ + our_entry.path = "subdir/modified_file"; + our_entry.mode = 0100644; + git_oid_fromstr(&our_entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); + + their_entry.path = "subdir/rename_conflict"; + their_entry.mode = 0100644; + git_oid_fromstr(&their_entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_conflict_add(index, NULL, &our_entry, &their_entry)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts)); + + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(9, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_CONFLICTED]); + + cl_assert_equal_i(7, exp.hunks); + + cl_assert_equal_i(12, exp.lines); + cl_assert_equal_i(4, exp.line_ctxt); + cl_assert_equal_i(3, exp.line_adds); + cl_assert_equal_i(5, exp.line_dels); + + git_diff_free(diff); + git_index_free(index); +} + +void test_diff_workdir__to_index_with_assume_unchanged(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_index *idx = NULL; + diff_expects exp; + const git_index_entry *iep; + git_index_entry ie; + + g_repo = cl_git_sandbox_init("status"); + + /* do initial diff */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(8, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + git_diff_free(diff); + + /* mark a couple of entries with ASSUME_UNCHANGED */ + + cl_git_pass(git_repository_index(&idx, g_repo)); + + cl_assert((iep = git_index_get_bypath(idx, "modified_file", 0)) != NULL); + memcpy(&ie, iep, sizeof(ie)); + ie.flags |= GIT_INDEX_ENTRY_VALID; + cl_git_pass(git_index_add(idx, &ie)); + + cl_assert((iep = git_index_get_bypath(idx, "file_deleted", 0)) != NULL); + memcpy(&ie, iep, sizeof(ie)); + ie.flags |= GIT_INDEX_ENTRY_VALID; + cl_git_pass(git_index_add(idx, &ie)); + + cl_git_pass(git_index_write(idx)); + git_index_free(idx); + + /* redo diff and see that entries are skipped */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(6, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + git_diff_free(diff); + +} + +void test_diff_workdir__to_tree(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + const char *b_commit = "0017bd4ab1ec3"; /* the start */ + git_tree *a, *b; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_diff *diff2 = NULL; + diff_expects exp; + int use_iterator; + + g_repo = cl_git_sandbox_init("status"); + + a = resolve_commit_oid_to_tree(g_repo, a_commit); + b = resolve_commit_oid_to_tree(g_repo, b_commit); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + /* You can't really generate the equivalent of git_diff_tree_to_workdir() + * using C git. It really wants to interpose the index into the diff. + * + * To validate the following results with command line git, I ran the + * following: + * - git ls-tree 26a125 + * - find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths + * The results are documented at the bottom of this file in the + * long comment entitled "PREPARATION OF TEST DATA". + */ + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + else + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(14, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + /* Since there is no git diff equivalent, let's just assume that the + * text diffs produced by git_diff_foreach are accurate here. We will + * do more apples-to-apples test comparison below. + */ + + git_diff_free(diff); + diff = NULL; + memset(&exp, 0, sizeof(exp)); + + /* This is a compatible emulation of "git diff " which looks like + * a workdir to tree diff (even though it is not really). This is what + * you would get from "git diff --name-status 26a125ee1bf" + */ + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); + cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_free(diff2); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + else + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(15, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); + + cl_assert_equal_i(11, exp.hunks); + + cl_assert_equal_i(17, exp.lines); + cl_assert_equal_i(4, exp.line_ctxt); + cl_assert_equal_i(8, exp.line_adds); + cl_assert_equal_i(5, exp.line_dels); + } + + git_diff_free(diff); + diff = NULL; + memset(&exp, 0, sizeof(exp)); + + /* Again, emulating "git diff " for testing purposes using + * "git diff --name-status 0017bd4ab1ec3" instead. + */ + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts)); + cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_free(diff2); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + else + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(16, exp.files); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); + + cl_assert_equal_i(12, exp.hunks); + + cl_assert_equal_i(19, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(12, exp.line_adds); + cl_assert_equal_i(4, exp.line_dels); + } + + git_diff_free(diff); + + /* Let's try that once more with a reversed diff */ + + opts.flags |= GIT_DIFF_REVERSE; + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts)); + cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_free(diff2); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(16, exp.files); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); + + cl_assert_equal_i(12, exp.hunks); + + cl_assert_equal_i(19, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(12, exp.line_dels); + cl_assert_equal_i(4, exp.line_adds); + + git_diff_free(diff); + + /* all done now */ + + git_tree_free(a); + git_tree_free(b); +} + +void test_diff_workdir__to_index_with_pathspec(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp; + char *pathspec = NULL; + int use_iterator; + + g_repo = cl_git_sandbox_init("status"); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + pathspec = "modified_file"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + pathspec = "subdir"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + pathspec = "*_deleted"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); +} + +void test_diff_workdir__to_index_with_pathlist_disabling_fnmatch(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp; + char *pathspec = NULL; + int use_iterator; + + g_repo = cl_git_sandbox_init("status"); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_DISABLE_PATHSPEC_MATCH; + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 0; + + /* ensure that an empty pathspec list is ignored */ + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that a single NULL pathspec is filtered out (like when using + * fnmatch filtering) + */ + + opts.pathspec.count = 1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + pathspec = "modified_file"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that subdirs can be specified */ + pathspec = "subdir"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that subdirs can be specified with a trailing slash */ + pathspec = "subdir/"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that fnmatching is completely disabled */ + pathspec = "subdir/*"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that the prefix matching isn't completely braindead */ + pathspec = "subdi"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that fnmatching isn't working at all */ + pathspec = "*_deleted"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); +} + +void test_diff_workdir__filemode_changes(void) +{ + git_diff *diff = NULL; + diff_expects exp; + int use_iterator; + + if (!cl_is_chmod_supported()) + return; + + g_repo = cl_git_sandbox_init("issue_592"); + + cl_repo_set_bool(g_repo, "core.filemode", true); + + /* test once with no mods */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + else + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.hunks); + } + + git_diff_free(diff); + + /* chmod file and test again */ + + cl_assert(cl_toggle_filemode("issue_592/a.txt")); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + else + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.hunks); + } + + git_diff_free(diff); + + cl_assert(cl_toggle_filemode("issue_592/a.txt")); +} + +void test_diff_workdir__filemode_changes_with_filemode_false(void) +{ + git_diff *diff = NULL; + diff_expects exp; + + if (!cl_is_chmod_supported()) + return; + + g_repo = cl_git_sandbox_init("issue_592"); + + cl_repo_set_bool(g_repo, "core.filemode", false); + + /* test once with no mods */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.hunks); + + git_diff_free(diff); + + /* chmod file and test again */ + + cl_assert(cl_toggle_filemode("issue_592/a.txt")); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.hunks); + + git_diff_free(diff); + + cl_assert(cl_toggle_filemode("issue_592/a.txt")); +} + +void test_diff_workdir__head_index_and_workdir_all_differ(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff_i2t = NULL, *diff_w2i = NULL; + diff_expects exp; + char *pathspec = "staged_changes_modified_file"; + git_tree *tree; + int use_iterator; + + /* For this file, + * - head->index diff has 1 line of context, 1 line of diff + * - index->workdir diff has 2 lines of context, 1 line of diff + * but + * - head->workdir diff has 1 line of context, 2 lines of diff + * Let's make sure the right one is returned from each fn. + */ + + g_repo = cl_git_sandbox_init("status"); + + tree = resolve_commit_oid_to_tree(g_repo, "26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f"); + + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 1; + + cl_git_pass(git_diff_tree_to_index(&diff_i2t, g_repo, tree, NULL, &opts)); + cl_git_pass(git_diff_index_to_workdir(&diff_w2i, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff_i2t, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + else + cl_git_pass(git_diff_foreach( + diff_i2t, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.hunks); + cl_assert_equal_i(2, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff_w2i, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + else + cl_git_pass(git_diff_foreach( + diff_w2i, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.hunks); + cl_assert_equal_i(3, exp.lines); + cl_assert_equal_i(2, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } + + cl_git_pass(git_diff_merge(diff_i2t, diff_w2i)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff_i2t, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + else + cl_git_pass(git_diff_foreach( + diff_i2t, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.hunks); + cl_assert_equal_i(3, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(2, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } + + git_diff_free(diff_i2t); + git_diff_free(diff_w2i); + + git_tree_free(tree); +} + +void test_diff_workdir__eof_newline_changes(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp; + char *pathspec = "current_file"; + int use_iterator; + + g_repo = cl_git_sandbox_init("status"); + + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + else + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.hunks); + cl_assert_equal_i(0, exp.lines); + cl_assert_equal_i(0, exp.line_ctxt); + cl_assert_equal_i(0, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } + + git_diff_free(diff); + + cl_git_append2file("status/current_file", "\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + else + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.hunks); + cl_assert_equal_i(2, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } + + git_diff_free(diff); + + cl_git_rewritefile("status/current_file", "current_file"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + else + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.hunks); + cl_assert_equal_i(3, exp.lines); + cl_assert_equal_i(0, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(2, exp.line_dels); + } + + git_diff_free(diff); +} + +/* PREPARATION OF TEST DATA + * + * Since there is no command line equivalent of git_diff_tree_to_workdir, + * it was a bit of a pain to confirm that I was getting the expected + * results in the first part of this tests. Here is what I ended up + * doing to set my expectation for the file counts and results: + * + * Running "git ls-tree 26a125" and "git ls-tree aa27a6" shows: + * + * A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file + * B 5452d32f1dd538eb0405e8a83cc185f79e25e80f file_deleted + * C 452e4244b5d083ddf0460acf1ecc74db9dcfa11a modified_file + * D 32504b727382542f9f089e24fddac5e78533e96c staged_changes + * E 061d42a44cacde5726057b67558821d95db96f19 staged_changes_file_deleted + * F 70bd9443ada07063e7fbf0b3ff5c13f7494d89c2 staged_changes_modified_file + * G e9b9107f290627c04d097733a10055af941f6bca staged_delete_file_deleted + * H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file + * I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file + * J 1888c805345ba265b0ee9449b8877b6064592058 subdir/deleted_file + * K a6191982709b746d5650e93c2acf34ef74e11504 subdir/modified_file + * L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt + * + * -------- + * + * find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths + * + * A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file + * M 6a79f808a9c6bc9531ac726c184bbcd9351ccf11 ignored_file + * C 0a539630525aca2e7bc84975958f92f10a64c9b6 modified_file + * N d4fa8600b4f37d7516bef4816ae2c64dbf029e3a new_file + * D 55d316c9ba708999f1918e9677d01dfcae69c6b9 staged_changes + * F 011c3440d5c596e21d836aa6d7b10eb581f68c49 staged_changes_modified_file + * H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file + * O 529a16e8e762d4acb7b9636ff540a00831f9155a staged_new_file + * P 8b090c06d14ffa09c4e880088ebad33893f921d1 staged_new_file_modified_file + * I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file + * K 57274b75eeb5f36fd55527806d567b2240a20c57 subdir/modified_file + * Q 80a86a6931b91bc01c2dbf5ca55bdd24ad1ef466 subdir/new_file + * L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt + * + * -------- + * + * A - current_file (UNMODIFIED) -> not in results + * B D file_deleted + * M I ignored_file (IGNORED) + * C M modified_file + * N U new_file (UNTRACKED) + * D M staged_changes + * E D staged_changes_file_deleted + * F M staged_changes_modified_file + * G D staged_delete_file_deleted + * H - staged_delete_modified_file (UNMODIFIED) -> not in results + * O U staged_new_file + * P U staged_new_file_modified_file + * I - subdir/current_file (UNMODIFIED) -> not in results + * J D subdir/deleted_file + * K M subdir/modified_file + * Q U subdir/new_file + * L - subdir.txt (UNMODIFIED) -> not in results + * + * Expect 13 files, 0 ADD, 4 DEL, 4 MOD, 1 IGN, 4 UNTR + */ + + +void test_diff_workdir__larger_hunks(void) +{ + const char *a_commit = "d70d245ed97ed2aa596dd1af6536e4bfdb047b69"; + const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10"; + git_tree *a, *b; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + size_t i, d, num_d, h, num_h, l, num_l; + + g_repo = cl_git_sandbox_init("diff"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + + opts.context_lines = 1; + opts.interhunk_lines = 0; + + for (i = 0; i <= 2; ++i) { + git_diff *diff = NULL; + git_patch *patch; + const git_diff_hunk *hunk; + const git_diff_line *line; + + /* okay, this is a bit silly, but oh well */ + switch (i) { + case 0: + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + break; + case 1: + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); + break; + case 2: + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, b, &opts)); + break; + } + + num_d = git_diff_num_deltas(diff); + cl_assert_equal_i(2, (int)num_d); + + for (d = 0; d < num_d; ++d) { + cl_git_pass(git_patch_from_diff(&patch, diff, d)); + cl_assert(patch); + + num_h = git_patch_num_hunks(patch); + for (h = 0; h < num_h; h++) { + cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h)); + + for (l = 0; l < num_l; ++l) { + cl_git_pass( + git_patch_get_line_in_hunk(&line, patch, h, l)); + cl_assert(line); + } + + /* confirm fail after the last item */ + cl_git_fail( + git_patch_get_line_in_hunk(&line, patch, h, num_l)); + } + + /* confirm fail after the last item */ + cl_git_fail(git_patch_get_hunk(&hunk, &num_l, patch, num_h)); + + git_patch_free(patch); + } + + git_diff_free(diff); + } + + git_tree_free(a); + git_tree_free(b); +} + +/* Set up a test that exercises this code. The easiest test using existing + * test data is probably to create a sandbox of submod2 and then run a + * git_diff_tree_to_workdir against tree + * 873585b94bdeabccea991ea5e3ec1a277895b698. As for what you should actually + * test, you can start by just checking that the number of lines of diff + * content matches the actual output of git diff. That will at least + * demonstrate that the submodule content is being used to generate somewhat + * comparable outputs. It is a test that would fail without this code and + * will succeed with it. + */ + +#include "../submodule/submodule_helpers.h" + +void test_diff_workdir__submodules(void) +{ + const char *a_commit = "873585b94bdeabccea991ea5e3ec1a277895b698"; + git_tree *a; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp; + + g_repo = setup_fixture_submod2(); + + a = resolve_commit_oid_to_tree(g_repo, a_commit); + + opts.flags = + GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_INCLUDE_IGNORED | + GIT_DIFF_RECURSE_UNTRACKED_DIRS | + GIT_DIFF_SHOW_UNTRACKED_CONTENT; + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); + + /* diff_print(stderr, diff); */ + + /* essentially doing: git diff 873585b94bdeabccea991ea5e3ec1a277895b698 */ + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + /* so "git diff 873585" returns: + * M .gitmodules + * A just_a_dir/contents + * A just_a_file + * A sm_added_and_uncommited + * A sm_changed_file + * A sm_changed_head + * A sm_changed_index + * A sm_changed_untracked_file + * M sm_missing_commits + * A sm_unchanged + * which is a little deceptive because of the difference between the + * "git diff " results from "git_diff_tree_to_workdir". The + * only significant difference is that those Added items will show up + * as Untracked items in the pure libgit2 diff. + * + * Then add in the two extra untracked items "not" and "not-submodule" + * to get the 12 files reported here. + */ + + cl_assert_equal_i(12, exp.files); + + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(10, exp.file_status[GIT_DELTA_UNTRACKED]); + + /* the following numbers match "git diff 873585" exactly */ + + cl_assert_equal_i(9, exp.hunks); + + cl_assert_equal_i(33, exp.lines); + cl_assert_equal_i(2, exp.line_ctxt); + cl_assert_equal_i(30, exp.line_adds); + cl_assert_equal_i(1, exp.line_dels); + + git_diff_free(diff); + git_tree_free(a); +} + +void test_diff_workdir__cannot_diff_against_a_bare_repository(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_tree *tree; + + g_repo = cl_git_sandbox_init("testrepo.git"); + + cl_assert_equal_i( + GIT_EBAREREPO, git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_git_pass(git_repository_head_tree(&tree, g_repo)); + + cl_assert_equal_i( + GIT_EBAREREPO, git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); + + git_tree_free(tree); +} + +void test_diff_workdir__to_null_tree(void) +{ + git_diff *diff; + diff_expects exp; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_RECURSE_UNTRACKED_DIRS; + + g_repo = cl_git_sandbox_init("status"); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(exp.files, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); +} + +void test_diff_workdir__checks_options_version(void) +{ + git_diff *diff; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + const git_error *err; + + g_repo = cl_git_sandbox_init("status"); + + opts.version = 0; + cl_git_fail(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts)); + err = git_error_last(); + cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); + + git_error_clear(); + opts.version = 1024; + cl_git_fail(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts)); + err = git_error_last(); + cl_assert_equal_i(GIT_ERROR_INVALID, err->klass); +} + +void test_diff_workdir__can_diff_empty_file(void) +{ + git_diff *diff; + git_tree *tree; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + struct stat st; + git_patch *patch; + + g_repo = cl_git_sandbox_init("attr_index"); + + tree = resolve_commit_oid_to_tree(g_repo, "3812cfef3661"); /* HEAD */ + + /* baseline - make sure there are no outstanding diffs */ + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); + cl_assert_equal_i(2, (int)git_diff_num_deltas(diff)); + git_diff_free(diff); + + /* empty contents of file */ + + cl_git_rewritefile("attr_index/README.txt", ""); + cl_git_pass(git_fs_path_lstat("attr_index/README.txt", &st)); + cl_assert_equal_i(0, (int)st.st_size); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); + cl_assert_equal_i(3, (int)git_diff_num_deltas(diff)); + /* diffs are: .gitattributes, README.txt, sub/sub/.gitattributes */ + cl_git_pass(git_patch_from_diff(&patch, diff, 1)); + git_patch_free(patch); + git_diff_free(diff); + + /* remove a file altogether */ + + cl_git_pass(p_unlink("attr_index/README.txt")); + cl_assert(!git_fs_path_exists("attr_index/README.txt")); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); + cl_assert_equal_i(3, (int)git_diff_num_deltas(diff)); + cl_git_pass(git_patch_from_diff(&patch, diff, 1)); + git_patch_free(patch); + git_diff_free(diff); + + git_tree_free(tree); +} + +void test_diff_workdir__to_index_issue_1397(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp; + + g_repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.hunks); + cl_assert_equal_i(0, exp.lines); + + git_diff_free(diff); + diff = NULL; + + cl_git_rewritefile("issue_1397/crlf_file.txt", + "first line\r\nsecond line modified\r\nboth with crlf"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + + cl_assert_equal_i(1, exp.hunks); + + cl_assert_equal_i(5, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(1, exp.line_dels); + + git_diff_free(diff); +} + +void test_diff_workdir__to_tree_issue_1397(void) +{ + const char *a_commit = "7f483a738"; /* the current HEAD */ + git_tree *a; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_diff *diff2 = NULL; + diff_expects exp; + + g_repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + a = resolve_commit_oid_to_tree(g_repo, a_commit); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.hunks); + cl_assert_equal_i(0, exp.lines); + + git_diff_free(diff); + diff = NULL; + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); + cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_free(diff2); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.hunks); + cl_assert_equal_i(0, exp.lines); + + git_diff_free(diff); + git_tree_free(a); +} + +void test_diff_workdir__untracked_directory_scenarios(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp; + char *pathspec = NULL; + static const char *files0[] = { + "subdir/deleted_file", + "subdir/modified_file", + "subdir/new_file", + NULL + }; + static const char *files1[] = { + "subdir/deleted_file", + "subdir/directory/", + "subdir/modified_file", + "subdir/new_file", + NULL + }; + static const char *files2[] = { + "subdir/deleted_file", + "subdir/directory/more/notignored", + "subdir/modified_file", + "subdir/new_file", + NULL + }; + + g_repo = cl_git_sandbox_init("status"); + cl_git_mkfile("status/.gitignore", "ignored\n"); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 1; + pathspec = "subdir"; + + /* baseline for "subdir" pathspec */ + + memset(&exp, 0, sizeof(exp)); + exp.names = files0; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + + /* empty directory */ + + cl_git_pass(p_mkdir("status/subdir/directory", 0777)); + + memset(&exp, 0, sizeof(exp)); + exp.names = files1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + + /* empty directory in empty directory */ + + cl_git_pass(p_mkdir("status/subdir/directory/empty", 0777)); + + memset(&exp, 0, sizeof(exp)); + exp.names = files1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + + /* directory with only ignored files */ + + cl_git_pass(p_mkdir("status/subdir/directory/deeper", 0777)); + cl_git_mkfile("status/subdir/directory/deeper/ignored", "ignore me\n"); + + cl_git_pass(p_mkdir("status/subdir/directory/another", 0777)); + cl_git_mkfile("status/subdir/directory/another/ignored", "ignore me\n"); + + memset(&exp, 0, sizeof(exp)); + exp.names = files1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + + /* directory with ignored directory (contents irrelevant) */ + + cl_git_pass(p_mkdir("status/subdir/directory/more", 0777)); + cl_git_pass(p_mkdir("status/subdir/directory/more/ignored", 0777)); + cl_git_mkfile("status/subdir/directory/more/ignored/notignored", + "inside ignored dir\n"); + + memset(&exp, 0, sizeof(exp)); + exp.names = files1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + + /* quick version avoids directory scan */ + + opts.flags = opts.flags | GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS; + + memset(&exp, 0, sizeof(exp)); + exp.names = files1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + + /* directory with nested non-ignored content */ + + opts.flags = opts.flags & ~GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS; + + cl_git_mkfile("status/subdir/directory/more/notignored", + "not ignored deep under untracked\n"); + + memset(&exp, 0, sizeof(exp)); + exp.names = files1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); + + /* use RECURSE_UNTRACKED_DIRS to get actual untracked files (no ignores) */ + + opts.flags = opts.flags & ~GIT_DIFF_INCLUDE_IGNORED; + opts.flags = opts.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + + memset(&exp, 0, sizeof(exp)); + exp.names = files2; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]); + + git_diff_free(diff); +} + + +void test_diff_workdir__untracked_directory_comes_last(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + + g_repo = cl_git_sandbox_init("renames"); + + cl_git_mkfile("renames/.gitignore", "*.ign\n"); + cl_git_pass(p_mkdir("renames/zzz_untracked", 0777)); + cl_git_mkfile("renames/zzz_untracked/an.ign", "ignore me please"); + cl_git_mkfile("renames/zzz_untracked/skip.ign", "ignore me really"); + cl_git_mkfile("renames/zzz_untracked/test.ign", "ignore me now"); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_assert(diff != NULL); + + git_diff_free(diff); +} + +void test_diff_workdir__untracked_with_bom(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + const git_diff_delta *delta; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_git_write2file("empty_standard_repo/bom.txt", + "\xFF\xFE\x31\x00\x32\x00\x33\x00\x34\x00", 10, O_WRONLY|O_CREAT, 0664); + + opts.flags = + GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_SHOW_UNTRACKED_CONTENT; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + cl_assert((delta = git_diff_get_delta(diff, 0)) != NULL); + cl_assert_equal_i(GIT_DELTA_UNTRACKED, delta->status); + + /* not known at this point + * cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0); + */ + + git_diff_free(diff); +} + +void test_diff_workdir__patience_diff(void) +{ + git_index *index; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_patch *patch = NULL; + git_buf buf = GIT_BUF_INIT; + const char *expected_normal = "diff --git a/test.txt b/test.txt\nindex 34a5acc..d52725f 100644\n--- a/test.txt\n+++ b/test.txt\n@@ -1,10 +1,7 @@\n When I wrote this\n I did not know\n-how to create\n-a patience diff\n I did not know\n how to create\n+a patience diff\n another problem\n-I did not know\n-how to create\n a minimal diff\n"; + const char *expected_patience = "diff --git a/test.txt b/test.txt\nindex 34a5acc..d52725f 100644\n--- a/test.txt\n+++ b/test.txt\n@@ -1,10 +1,7 @@\n When I wrote this\n I did not know\n+I did not know\n how to create\n a patience diff\n-I did not know\n-how to create\n another problem\n-I did not know\n-how to create\n a minimal diff\n"; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_repo_set_bool(g_repo, "core.autocrlf", true); + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_mkfile( + "empty_standard_repo/test.txt", + "When I wrote this\nI did not know\nhow to create\na patience diff\nI did not know\nhow to create\nanother problem\nI did not know\nhow to create\na minimal diff\n"); + cl_git_pass(git_index_add_bypath(index, "test.txt")); + cl_repo_commit_from_index(NULL, g_repo, NULL, 1372350000, "Base"); + git_index_free(index); + + cl_git_rewritefile( + "empty_standard_repo/test.txt", + "When I wrote this\nI did not know\nI did not know\nhow to create\na patience diff\nanother problem\na minimal diff\n"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + + cl_assert_equal_s(expected_normal, buf.ptr); + git_buf_dispose(&buf); + git_patch_free(patch); + git_diff_free(diff); + + opts.flags |= GIT_DIFF_PATIENCE; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + + cl_assert_equal_s(expected_patience, buf.ptr); + git_buf_dispose(&buf); + + git_patch_free(patch); + git_diff_free(diff); +} + +void test_diff_workdir__with_stale_index(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_index *idx = NULL; + diff_expects exp; + + g_repo = cl_git_sandbox_init("status"); + cl_git_pass(git_repository_index(&idx, g_repo)); + + /* make the in-memory index invalid */ + { + git_repository *r2; + git_index *idx2; + cl_git_pass(git_repository_open(&r2, "status")); + cl_git_pass(git_repository_index(&idx2, r2)); + cl_git_pass(git_index_add_bypath(idx2, "new_file")); + cl_git_pass(git_index_add_bypath(idx2, "subdir/new_file")); + cl_git_pass(git_index_remove_bypath(idx2, "staged_new_file")); + cl_git_pass(git_index_remove_bypath(idx2, "staged_changes_file_deleted")); + cl_git_pass(git_index_write(idx2)); + git_index_free(idx2); + git_repository_free(r2); + } + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_INCLUDE_UNMODIFIED; + + /* first try with index pointer which should prevent reload */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, idx, &opts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(17, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_UNMODIFIED]); + + git_diff_free(diff); + + /* now let's try without the index pointer which should trigger reload */ + + /* two files that were UNTRACKED should have become UNMODIFIED */ + /* one file that was UNMODIFIED should now have become UNTRACKED */ + /* one file that was DELETED should now be gone completely */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + git_diff_free(diff); + + cl_assert_equal_i(16, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); + cl_assert_equal_i(6, exp.file_status[GIT_DELTA_UNMODIFIED]); + + git_index_free(idx); +} + +static int touch_file(void *payload, git_str *path) +{ + struct stat st; + struct p_timeval times[2]; + + GIT_UNUSED(payload); + if (git_fs_path_isdir(path->ptr)) + return 0; + + cl_must_pass(p_stat(path->ptr, &st)); + + times[0].tv_sec = st.st_mtime + 3; + times[0].tv_usec = 0; + times[1].tv_sec = st.st_mtime + 3; + times[1].tv_usec = 0; + + cl_must_pass(p_utimes(path->ptr, times)); + return 0; +} + +static void basic_diff_status(git_diff **out, const git_diff_options *opts) +{ + diff_expects exp; + + cl_git_pass(git_diff_index_to_workdir(out, g_repo, NULL, opts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + *out, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); +} + +void test_diff_workdir__can_update_index(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT; + git_index *index; + + g_repo = cl_git_sandbox_init("status"); + + /* touch all the files so stat times are different */ + { + git_str path = GIT_STR_INIT; + cl_git_pass(git_str_sets(&path, "status")); + cl_git_pass(git_fs_path_direach(&path, 0, touch_file, NULL)); + git_str_dispose(&path); + } + + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + basic_diff_status(&diff, &opts); + + cl_git_pass(git_diff_get_perfdata(&perf, diff)); + cl_assert_equal_sz(13 + 3, perf.stat_calls); + cl_assert_equal_sz(5, perf.oid_calculations); + + git_diff_free(diff); + + /* now allow diff to update stat cache */ + opts.flags |= GIT_DIFF_UPDATE_INDEX; + + /* advance a tick for the index so we don't re-calculate racily-clean entries */ + cl_git_pass(git_repository_index__weakptr(&index, g_repo)); + tick_index(index); + + basic_diff_status(&diff, &opts); + + cl_git_pass(git_diff_get_perfdata(&perf, diff)); + cl_assert_equal_sz(13 + 3, perf.stat_calls); + cl_assert_equal_sz(5, perf.oid_calculations); + + git_diff_free(diff); + + /* now if we do it again, we should see fewer OID calculations */ + + /* tick again as the index updating from the previous diff might have reset the timestamp */ + tick_index(index); + basic_diff_status(&diff, &opts); + + cl_git_pass(git_diff_get_perfdata(&perf, diff)); + cl_assert_equal_sz(13 + 3, perf.stat_calls); + cl_assert_equal_sz(0, perf.oid_calculations); + + git_diff_free(diff); +} + +#define STR7 "0123456" +#define STR8 "01234567" +#define STR40 STR8 STR8 STR8 STR8 STR8 +#define STR200 STR40 STR40 STR40 STR40 STR40 +#define STR999Z STR200 STR200 STR200 STR200 STR40 STR40 STR40 STR40 \ + STR8 STR8 STR8 STR8 STR7 "\0" +#define STR1000 STR200 STR200 STR200 STR200 STR200 +#define STR3999Z STR1000 STR1000 STR1000 STR999Z +#define STR4000 STR1000 STR1000 STR1000 STR1000 + +static void assert_delta_binary(git_diff *diff, size_t idx, int is_binary) +{ + git_patch *patch; + const git_diff_delta *delta; + + cl_git_pass(git_patch_from_diff(&patch, diff, idx)); + delta = git_patch_get_delta(patch); + cl_assert_equal_b((delta->flags & GIT_DIFF_FLAG_BINARY), is_binary); + git_patch_free(patch); +} + +void test_diff_workdir__binary_detection(void) +{ + git_index *idx; + git_diff *diff = NULL; + git_str b = GIT_STR_INIT; + int i; + git_str data[10] = { + { "1234567890", 0, 10 }, /* 0 - all ascii text control */ + { "\xC3\x85\xC3\xBC\xE2\x80\xA0\x48\xC3\xB8\xCF\x80\xCE\xA9", 0, 14 }, /* 1 - UTF-8 multibyte text */ + { "\xEF\xBB\xBF\xC3\x9C\xE2\xA4\x92\xC6\x92\x38\xC2\xA3\xE2\x82\xAC", 0, 16 }, /* 2 - UTF-8 with BOM */ + { STR999Z, 0, 1000 }, /* 3 - ASCII with NUL at 1000 */ + { STR3999Z, 0, 4000 }, /* 4 - ASCII with NUL at 4000 */ + { STR4000 STR3999Z "x", 0, 8001 }, /* 5 - ASCII with NUL at 8000 */ + { STR4000 STR4000 "\0", 0, 8001 }, /* 6 - ASCII with NUL at 8001 */ + { "\x00\xDC\x00\x6E\x21\x39\xFE\x0E\x00\x63\x00\xF8" + "\x00\x64\x00\x65\x20\x48", 0, 18 }, /* 7 - UTF-16 text */ + { "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d", + 0, 26 }, /* 8 - All non-printable characters (no NUL) */ + { "Hello \x01\x02\x03\x04\x05\x06 World!\x01\x02\x03\x04" + "\x05\x06\x07", 0, 26 }, /* 9 - 50-50 non-printable (no NUL) */ + }; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_pass(git_repository_index(&idx, g_repo)); + + /* We start with ASCII in index and test data in workdir, + * then we will try with test data in index and ASCII in workdir. + */ + + cl_git_pass(git_str_sets(&b, "empty_standard_repo/0")); + for (i = 0; i < 10; ++i) { + b.ptr[b.size - 1] = '0' + i; + cl_git_mkfile(b.ptr, "baseline"); + cl_git_pass(git_index_add_bypath(idx, &b.ptr[b.size - 1])); + + if (data[i].size == 0) + data[i].size = strlen(data[i].ptr); + cl_git_write2file( + b.ptr, data[i].ptr, data[i].size, O_WRONLY|O_TRUNC, 0664); + } + cl_git_pass(git_index_write(idx)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); + + cl_assert_equal_i(10, git_diff_num_deltas(diff)); + + /* using diff binary detection (i.e. looking for NUL byte) */ + assert_delta_binary(diff, 0, false); + assert_delta_binary(diff, 1, false); + assert_delta_binary(diff, 2, false); + assert_delta_binary(diff, 3, true); + assert_delta_binary(diff, 4, true); + assert_delta_binary(diff, 5, true); + assert_delta_binary(diff, 6, false); + assert_delta_binary(diff, 7, true); + assert_delta_binary(diff, 8, false); + assert_delta_binary(diff, 9, false); + /* The above have been checked to match command-line Git */ + + git_diff_free(diff); + + cl_git_pass(git_str_sets(&b, "empty_standard_repo/0")); + for (i = 0; i < 10; ++i) { + b.ptr[b.size - 1] = '0' + i; + cl_git_pass(git_index_add_bypath(idx, &b.ptr[b.size - 1])); + + cl_git_write2file(b.ptr, "baseline\n", 9, O_WRONLY|O_TRUNC, 0664); + } + cl_git_pass(git_index_write(idx)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); + + cl_assert_equal_i(10, git_diff_num_deltas(diff)); + + /* using diff binary detection (i.e. looking for NUL byte) */ + assert_delta_binary(diff, 0, false); + assert_delta_binary(diff, 1, false); + assert_delta_binary(diff, 2, false); + assert_delta_binary(diff, 3, true); + assert_delta_binary(diff, 4, true); + assert_delta_binary(diff, 5, true); + assert_delta_binary(diff, 6, false); + assert_delta_binary(diff, 7, true); + assert_delta_binary(diff, 8, false); + assert_delta_binary(diff, 9, false); + + git_diff_free(diff); + + git_index_free(idx); + git_str_dispose(&b); +} + +void test_diff_workdir__to_index_conflicted(void) { + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + git_index_entry ancestor = {{0}}, ours = {{0}}, theirs = {{0}}; + git_tree *a; + git_index *index; + git_diff *diff1, *diff2; + const git_diff_delta *delta; + + g_repo = cl_git_sandbox_init("status"); + a = resolve_commit_oid_to_tree(g_repo, a_commit); + + cl_git_pass(git_repository_index(&index, g_repo)); + + ancestor.path = ours.path = theirs.path = "_file"; + ancestor.mode = ours.mode = theirs.mode = 0100644; + git_oid_fromstr(&ancestor.id, "d427e0b2e138501a3d15cc376077a3631e15bd46"); + git_oid_fromstr(&ours.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); + git_oid_fromstr(&theirs.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); + cl_git_pass(git_index_conflict_add(index, &ancestor, &ours, &theirs)); + + cl_git_pass(git_diff_tree_to_index(&diff1, g_repo, a, index, NULL)); + cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, index, NULL)); + cl_git_pass(git_diff_merge(diff1, diff2)); + + cl_assert_equal_i(git_diff_num_deltas(diff1), 12); + delta = git_diff_get_delta(diff1, 0); + cl_assert_equal_s(delta->old_file.path, "_file"); + cl_assert_equal_i(delta->nfiles, 1); + cl_assert_equal_i(delta->status, GIT_DELTA_CONFLICTED); + + git_diff_free(diff2); + git_diff_free(diff1); + git_index_free(index); + git_tree_free(a); +} + +void test_diff_workdir__only_writes_index_when_necessary(void) +{ + git_index *index; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_reference *head; + git_object *head_object; + unsigned char initial[GIT_HASH_SHA1_SIZE], + first[GIT_HASH_SHA1_SIZE], + second[GIT_HASH_SHA1_SIZE]; + git_str path = GIT_STR_INIT; + struct stat st; + struct p_timeval times[2]; + + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_UPDATE_INDEX; + + g_repo = cl_git_sandbox_init("status"); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_repository_head(&head, g_repo)); + cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT)); + + cl_git_pass(git_reset(g_repo, head_object, GIT_RESET_HARD, NULL)); + + memcpy(initial, git_index__checksum(index), GIT_HASH_SHA1_SIZE); + + /* update the index timestamp to avoid raciness */ + cl_must_pass(p_stat("status/.git/index", &st)); + + times[0].tv_sec = st.st_mtime + 5; + times[0].tv_usec = 0; + times[1].tv_sec = st.st_mtime + 5; + times[1].tv_usec = 0; + + cl_must_pass(p_utimes("status/.git/index", times)); + + /* ensure diff doesn't touch the index */ + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + git_diff_free(diff); + + memcpy(first, git_index__checksum(index), GIT_HASH_SHA1_SIZE); + cl_assert(memcmp(initial, first, GIT_HASH_SHA1_SIZE) != 0); + + /* touch all the files so stat times are different */ + cl_git_pass(git_str_sets(&path, "status")); + cl_git_pass(git_fs_path_direach(&path, 0, touch_file, NULL)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + git_diff_free(diff); + + /* ensure the second diff did update the index */ + memcpy(second, git_index__checksum(index), GIT_HASH_SHA1_SIZE); + cl_assert(memcmp(first, second, GIT_HASH_SHA1_SIZE) != 0); + + git_str_dispose(&path); + git_object_free(head_object); + git_reference_free(head); + git_index_free(index); +} + +void test_diff_workdir__to_index_pathlist(void) +{ + git_index *index; + git_diff *diff; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_vector pathlist = GIT_VECTOR_INIT; + + git_vector_insert(&pathlist, "foobar/asdf"); + git_vector_insert(&pathlist, "subdir/asdf"); + git_vector_insert(&pathlist, "ignored/asdf"); + + g_repo = cl_git_sandbox_init("status"); + + cl_git_mkfile("status/.gitignore", ".gitignore\n" "ignored/\n"); + + cl_must_pass(p_mkdir("status/foobar", 0777)); + cl_git_mkfile("status/foobar/one", "one\n"); + + cl_must_pass(p_mkdir("status/ignored", 0777)); + cl_git_mkfile("status/ignored/one", "one\n"); + cl_git_mkfile("status/ignored/two", "two\n"); + cl_git_mkfile("status/ignored/three", "three\n"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + opts.flags = GIT_DIFF_INCLUDE_IGNORED; + opts.pathspec.strings = (char **)pathlist.contents; + opts.pathspec.count = pathlist.length; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + + opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + + git_index_free(index); + git_vector_free(&pathlist); +} + +void test_diff_workdir__symlink_changed_on_non_symlink_platform(void) +{ + git_tree *tree; + git_diff *diff; + diff_expects exp = {0}; + const git_diff_delta *delta; + const char *commit = "7fccd7"; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_vector pathlist = GIT_VECTOR_INIT; + int symlinks; + + g_repo = cl_git_sandbox_init("unsymlinked.git"); + + cl_git_pass(git_repository__configmap_lookup(&symlinks, g_repo, GIT_CONFIGMAP_SYMLINKS)); + + if (symlinks) + cl_skip(); + + cl_git_pass(git_vector_insert(&pathlist, "include/Nu/Nu.h")); + + opts.pathspec.strings = (char **)pathlist.contents; + opts.pathspec.count = pathlist.length; + + cl_must_pass(p_mkdir("symlink", 0777)); + cl_git_pass(git_repository_set_workdir(g_repo, "symlink", false)); + + cl_assert((tree = resolve_commit_oid_to_tree(g_repo, commit)) != NULL); + + /* first, do the diff with the original contents */ + + cl_git_pass(git_futils_mkpath2file("symlink/include/Nu/Nu.h", 0755)); + cl_git_mkfile("symlink/include/Nu/Nu.h", "../../objc/Nu.h"); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + + /* now update the contents and expect a difference, but that the file + * mode has persisted as a symbolic link. + */ + + cl_git_rewritefile("symlink/include/Nu/Nu.h", "awesome content\n"); + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(1, exp.files); + + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + delta = git_diff_get_delta(diff, 0); + cl_assert_equal_i(GIT_FILEMODE_LINK, delta->old_file.mode); + cl_assert_equal_i(GIT_FILEMODE_LINK, delta->new_file.mode); + + git_diff_free(diff); + + cl_git_pass(git_futils_rmdir_r("symlink", NULL, GIT_RMDIR_REMOVE_FILES)); + + git_tree_free(tree); + git_vector_free(&pathlist); +} + +void test_diff_workdir__order(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_buf patch = GIT_BUF_INIT; + git_oid tree_oid, blob_oid; + git_treebuilder *builder; + git_tree *tree; + git_diff *diff; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + /* Build tree with a single file "abc.txt" */ + cl_git_pass(git_blob_create_from_buffer(&blob_oid, g_repo, "foo\n", 4)); + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + cl_git_pass(git_treebuilder_insert(NULL, builder, "abc.txt", &blob_oid, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_write(&tree_oid, builder)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid)); + + /* Create a directory that sorts before and one that sorts after "abc.txt" */ + cl_git_mkfile("empty_standard_repo/abc.txt", "bar\n"); + cl_must_pass(p_mkdir("empty_standard_repo/abb", 0777)); + cl_must_pass(p_mkdir("empty_standard_repo/abd", 0777)); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); + + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + cl_git_pass(git_diff_to_buf(&patch, diff, GIT_DIFF_FORMAT_PATCH)); + cl_assert_equal_s(patch.ptr, + "diff --git a/abc.txt b/abc.txt\n" + "index 257cc56..5716ca5 100644\n" + "--- a/abc.txt\n" + "+++ b/abc.txt\n" + "@@ -1 +1 @@\n" + "-foo\n" + "+bar\n"); + + git_treebuilder_free(builder); + git_buf_dispose(&patch); + git_diff_free(diff); + git_tree_free(tree); +} + +void test_diff_workdir__ignore_blank_lines(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff; + git_patch *patch; + git_buf buf = GIT_BUF_INIT; + + g_repo = cl_git_sandbox_init("rebase"); + cl_git_rewritefile("rebase/gravy.txt", "GRAVY SOUP.\n\n\nGet eight pounds of coarse lean beef--wash it clean and lay it in your\n\npot, put in the same ingredients as for the shin soup, with the same\nquantity of water, and follow the process directed for that. Strain the\nsoup through a sieve, and serve it up clear, with nothing more than\ntoasted bread in it; two table-spoonsful of mushroom catsup will add a\nfine flavour to the soup!\n"); + + /* Perform the diff normally */ + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + + cl_assert_equal_s("diff --git a/gravy.txt b/gravy.txt\nindex c4e6cca..3c617e6 100644\n--- a/gravy.txt\n+++ b/gravy.txt\n@@ -1,8 +1,10 @@\n GRAVY SOUP.\n \n+\n Get eight pounds of coarse lean beef--wash it clean and lay it in your\n+\n pot, put in the same ingredients as for the shin soup, with the same\n quantity of water, and follow the process directed for that. Strain the\n soup through a sieve, and serve it up clear, with nothing more than\n toasted bread in it; two table-spoonsful of mushroom catsup will add a\n-fine flavour to the soup.\n+fine flavour to the soup!\n", buf.ptr); + + git_buf_dispose(&buf); + git_patch_free(patch); + git_diff_free(diff); + + /* Perform the diff ignoring blank lines */ + opts.flags |= GIT_DIFF_IGNORE_BLANK_LINES; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + cl_git_pass(git_patch_from_diff(&patch, diff, 0)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + + cl_assert_equal_s("diff --git a/gravy.txt b/gravy.txt\nindex c4e6cca..3c617e6 100644\n--- a/gravy.txt\n+++ b/gravy.txt\n@@ -5,4 +7,4 @@ pot, put in the same ingredients as for the shin soup, with the same\n quantity of water, and follow the process directed for that. Strain the\n soup through a sieve, and serve it up clear, with nothing more than\n toasted bread in it; two table-spoonsful of mushroom catsup will add a\n-fine flavour to the soup.\n+fine flavour to the soup!\n", buf.ptr); + + git_buf_dispose(&buf); + git_patch_free(patch); + git_diff_free(diff); +} diff --git a/tests/libgit2/email/create.c b/tests/libgit2/email/create.c new file mode 100644 index 000000000..27a665582 --- /dev/null +++ b/tests/libgit2/email/create.c @@ -0,0 +1,364 @@ +#include "clar.h" +#include "clar_libgit2.h" + +#include "diff_generate.h" + +static git_repository *repo; + +void test_email_create__initialize(void) +{ + repo = cl_git_sandbox_init("diff_format_email"); +} + +void test_email_create__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void email_for_commit( + git_buf *out, + const char *commit_id, + git_email_create_options *opts) +{ + git_oid oid; + git_commit *commit = NULL; + git_diff *diff = NULL; + + git_oid_fromstr(&oid, commit_id); + + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + + cl_git_pass(git_email_create_from_commit(out, commit, opts)); + + git_diff_free(diff); + git_commit_free(commit); +} + +static void assert_email_match( + const char *expected, + const char *commit_id, + git_email_create_options *opts) +{ + git_buf buf = GIT_BUF_INIT; + + email_for_commit(&buf, commit_id, opts); + cl_assert_equal_s(expected, buf.ptr); + + git_buf_dispose(&buf); +} + +static void assert_subject_match( + const char *expected, + const char *commit_id, + git_email_create_options *opts) +{ + git_buf buf = GIT_BUF_INIT; + char *subject, *nl; + + email_for_commit(&buf, commit_id, opts); + + cl_assert((subject = strstr(buf.ptr, "\nSubject: ")) != NULL); + subject += 10; + + if ((nl = strchr(subject, '\n')) != NULL) + *nl = '\0'; + + cl_assert_equal_s(expected, subject); + + git_buf_dispose(&buf); +} + +void test_email_create__commit(void) +{ + const char *expected = + "From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \ + "Subject: [PATCH] Modify some content\n" \ + "\n" \ + "---\n" \ + " file1.txt | 8 +++++---\n" \ + " 1 file changed, 5 insertions(+), 3 deletions(-)\n" \ + "\n" \ + "diff --git a/file1.txt b/file1.txt\n" \ + "index 94aaae8..af8f41d 100644\n" \ + "--- a/file1.txt\n" \ + "+++ b/file1.txt\n" \ + "@@ -1,15 +1,17 @@\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "+_file1.txt_\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "+\n" \ + "+\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "+_file1.txt_\n" \ + "+_file1.txt_\n" \ + " file1.txt\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + assert_email_match( + expected, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", NULL); +} + +void test_email_create__rename(void) +{ + const char *expected = + "From 6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Wed, 9 Apr 2014 21:15:56 +0200\n" \ + "Subject: [PATCH] Renamed file1.txt -> file1.txt.renamed\n" \ + "\n" \ + "---\n" \ + " file1.txt => file1.txt.renamed | 4 ++--\n" \ + " 1 file changed, 2 insertions(+), 2 deletions(-)\n" \ + "\n" \ + "diff --git a/file1.txt b/file1.txt.renamed\n" \ + "similarity index 86%\n" \ + "rename from file1.txt\n" \ + "rename to file1.txt.renamed\n" \ + "index af8f41d..a97157a 100644\n" \ + "--- a/file1.txt\n" \ + "+++ b/file1.txt.renamed\n" \ + "@@ -3,13 +3,13 @@ file1.txt\n" \ + " _file1.txt_\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "-file1.txt\n" \ + "+file1.txt_renamed\n" \ + " file1.txt\n" \ + " \n" \ + " \n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "-file1.txt\n" \ + "+file1.txt_renamed\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " _file1.txt_\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + assert_email_match(expected, "6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d", NULL); +} + +void test_email_create__rename_as_add_delete(void) +{ + const char *expected = + "From 6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Wed, 9 Apr 2014 21:15:56 +0200\n" \ + "Subject: [PATCH] Renamed file1.txt -> file1.txt.renamed\n" \ + "\n" \ + "---\n" \ + " file1.txt | 17 -----------------\n" \ + " file1.txt.renamed | 17 +++++++++++++++++\n" \ + " 2 files changed, 17 insertions(+), 17 deletions(-)\n" \ + " delete mode 100644 file1.txt\n" \ + " create mode 100644 file1.txt.renamed\n" \ + "\n" \ + "diff --git a/file1.txt b/file1.txt\n" \ + "deleted file mode 100644\n" \ + "index af8f41d..0000000\n" \ + "--- a/file1.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,17 +0,0 @@\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-_file1.txt_\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-\n" \ + "-\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-_file1.txt_\n" \ + "-_file1.txt_\n" \ + "-file1.txt\n" \ + "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \ + "new file mode 100644\n" \ + "index 0000000..a97157a\n" \ + "--- /dev/null\n" \ + "+++ b/file1.txt.renamed\n" \ + "@@ -0,0 +1,17 @@\n" \ + "+file1.txt\n" \ + "+file1.txt\n" \ + "+_file1.txt_\n" \ + "+file1.txt\n" \ + "+file1.txt\n" \ + "+file1.txt_renamed\n" \ + "+file1.txt\n" \ + "+\n" \ + "+\n" \ + "+file1.txt\n" \ + "+file1.txt\n" \ + "+file1.txt_renamed\n" \ + "+file1.txt\n" \ + "+file1.txt\n" \ + "+_file1.txt_\n" \ + "+_file1.txt_\n" \ + "+file1.txt\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + opts.flags |= GIT_EMAIL_CREATE_NO_RENAMES; + + assert_email_match(expected, "6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d", &opts); +} + +void test_email_create__binary(void) +{ + const char *expected = + "From 8d7523f6fcb2404257889abe0d96f093d9f524f9 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Sun, 13 Apr 2014 18:10:18 +0200\n" \ + "Subject: [PATCH] Modified binary file\n" \ + "\n" \ + "---\n" \ + " binary.bin | Bin 3 -> 5 bytes\n" \ + " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ + "\n" \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index bd474b2519cc15eab801ff851cc7d50f0dee49a1..9ac35ff15cd8864aeafd889e4826a3150f0b06c4 100644\n" \ + "GIT binary patch\n" \ + "literal 5\n" \ + "Mc${NkU}WL~000&M4gdfE\n" \ + "\n" \ + "literal 3\n" \ + "Kc${Nk-~s>u4FC%O\n" \ + "\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + assert_email_match(expected, "8d7523f6fcb2404257889abe0d96f093d9f524f9", NULL); +} + +void test_email_create__binary_not_included(void) +{ + const char *expected = + "From 8d7523f6fcb2404257889abe0d96f093d9f524f9 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Sun, 13 Apr 2014 18:10:18 +0200\n" \ + "Subject: [PATCH] Modified binary file\n" \ + "\n" \ + "---\n" \ + " binary.bin | Bin 3 -> 5 bytes\n" \ + " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ + "\n" \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index bd474b2..9ac35ff 100644\n" \ + "Binary files a/binary.bin and b/binary.bin differ\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + opts.diff_opts.flags &= ~GIT_DIFF_SHOW_BINARY; + + assert_email_match(expected, "8d7523f6fcb2404257889abe0d96f093d9f524f9", &opts); +} + +void test_email_create__custom_summary_and_body(void) +{ + const char *expected = "From 627e7e12d87e07a83fad5b6bfa25e86ead4a5270 Mon Sep 17 00:00:00 2001\n" \ + "From: Patrick Steinhardt \n" \ + "Date: Tue, 24 Nov 2015 13:34:39 +0100\n" \ + "Subject: [PPPPPATCH 2/4] This is a subject\n" \ + "\n" \ + "Modify content of file3.txt by appending a new line. Make this\n" \ + "commit message somewhat longer to test behavior with newlines\n" \ + "embedded in the message body.\n" \ + "\n" \ + "Also test if new paragraphs are included correctly.\n" \ + "---\n" \ + " file3.txt | 1 +\n" \ + " 1 file changed, 1 insertion(+)\n" \ + "\n" \ + "diff --git a/file3.txt b/file3.txt\n" \ + "index 9a2d780..7309653 100644\n" \ + "--- a/file3.txt\n" \ + "+++ b/file3.txt\n" \ + "@@ -3,3 +3,4 @@ file3!\n" \ + " file3\n" \ + " file3\n" \ + " file3\n" \ + "+file3\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + const char *summary = "This is a subject\nwith\nnewlines"; + const char *body = "Modify content of file3.txt by appending a new line. Make this\n" \ + "commit message somewhat longer to test behavior with newlines\n" \ + "embedded in the message body.\n" \ + "\n" \ + "Also test if new paragraphs are included correctly."; + + git_oid oid; + git_commit *commit = NULL; + git_diff *diff = NULL; + git_buf buf = GIT_BUF_INIT; + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + + opts.subject_prefix = "PPPPPATCH"; + + git_oid_fromstr(&oid, "627e7e12d87e07a83fad5b6bfa25e86ead4a5270"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_diff__commit(&diff, repo, commit, NULL)); + cl_git_pass(git_email_create_from_diff(&buf, diff, 2, 4, &oid, summary, body, git_commit_author(commit), &opts)); + + cl_assert_equal_s(expected, buf.ptr); + + git_diff_free(diff); + git_commit_free(commit); + git_buf_dispose(&buf); +} + +void test_email_create__commit_subjects(void) +{ + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + + assert_subject_match("[PATCH] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.reroll_number = 42; + assert_subject_match("[PATCH v42] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.flags |= GIT_EMAIL_CREATE_ALWAYS_NUMBER; + assert_subject_match("[PATCH v42 1/1] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.start_number = 9; + assert_subject_match("[PATCH v42 9/9] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.subject_prefix = ""; + assert_subject_match("[v42 9/9] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.reroll_number = 0; + assert_subject_match("[9/9] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.start_number = 0; + assert_subject_match("[1/1] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.flags = GIT_EMAIL_CREATE_OMIT_NUMBERS; + assert_subject_match("Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); +} diff --git a/tests/libgit2/email/create.c.bak b/tests/libgit2/email/create.c.bak new file mode 100644 index 000000000..3bb95a6f6 --- /dev/null +++ b/tests/libgit2/email/create.c.bak @@ -0,0 +1,386 @@ +#include "clar.h" +#include "clar_libgit2.h" + +#include "buffer.h" +#include "diff_generate.h" + +static git_repository *repo; + +void test_email_create__initialize(void) +{ + repo = cl_git_sandbox_init("diff_format_email"); +} + +void test_email_create__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void email_for_commit( + git_buf *out, + const char *commit_id, + git_email_create_options *opts) +{ + git_oid oid; + git_commit *commit = NULL; + git_diff *diff = NULL; + + git_oid_fromstr(&oid, commit_id); + + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + + cl_git_pass(git_email_create_from_commit(out, commit, opts)); + + git_diff_free(diff); + git_commit_free(commit); +} + +static void assert_email_match( + const char *expected, + const char *commit_id, + git_email_create_options *opts) +{ + git_buf buf = GIT_BUF_INIT; + + email_for_commit(&buf, commit_id, opts); + cl_assert_equal_s(expected, git_buf_cstr(&buf)); + + git_buf_dispose(&buf); +} + +static void assert_subject_match( + const char *expected, + const char *commit_id, + git_email_create_options *opts) +{ + git_buf buf = GIT_BUF_INIT; + const char *loc; + + email_for_commit(&buf, commit_id, opts); + + cl_assert((loc = strstr(buf.ptr, "\nSubject: ")) != NULL); + git_buf_consume(&buf, (loc + 10)); + git_buf_truncate_at_char(&buf, '\n'); + + cl_assert_equal_s(expected, git_buf_cstr(&buf)); + + git_buf_dispose(&buf); +} + +void test_email_create__commit(void) +{ + const char *expected = + "From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \ + "Subject: [PATCH] Modify some content\n" \ + "\n" \ + "---\n" \ + " file1.txt | 8 +++++---\n" \ + " 1 file changed, 5 insertions(+), 3 deletions(-)\n" \ + "\n" \ + "diff --git a/file1.txt b/file1.txt\n" \ + "index 94aaae8..af8f41d 100644\n" \ + "--- a/file1.txt\n" \ + "+++ b/file1.txt\n" \ + "@@ -1,15 +1,17 @@\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "+_file1.txt_\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "+\n" \ + "+\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "+_file1.txt_\n" \ + "+_file1.txt_\n" \ + " file1.txt\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + assert_email_match( + expected, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", NULL); +} + +void test_email_create__custom_summary_and_body(void) +{ + const char *expected = "From 627e7e12d87e07a83fad5b6bfa25e86ead4a5270 Mon Sep 17 00:00:00 2001\n" \ + "From: Patrick Steinhardt \n" \ + "Date: Tue, 24 Nov 2015 13:34:39 +0100\n" \ + "Subject: [PPPPPATCH 2/4] This is a subject\n" \ + "\n" \ + "Modify content of file3.txt by appending a new line. Make this\n" \ + "commit message somewhat longer to test behavior with newlines\n" \ + "embedded in the message body.\n" \ + "\n" \ + "Also test if new paragraphs are included correctly.\n" \ + "---\n" \ + " file3.txt | 1 +\n" \ + " 1 file changed, 1 insertion(+)\n" \ + "\n" \ + "diff --git a/file3.txt b/file3.txt\n" \ + "index 9a2d780..7309653 100644\n" \ + "--- a/file3.txt\n" \ + "+++ b/file3.txt\n" \ + "@@ -3,3 +3,4 @@ file3!\n" \ + " file3\n" \ + " file3\n" \ + " file3\n" \ + "+file3\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + const char *summary = "This is a subject\nwith\nnewlines"; + const char *body = "Modify content of file3.txt by appending a new line. Make this\n" \ + "commit message somewhat longer to test behavior with newlines\n" \ + "embedded in the message body.\n" \ + "\n" \ + "Also test if new paragraphs are included correctly."; + + git_oid oid; + git_commit *commit = NULL; + git_diff *diff = NULL; + git_buf buf = GIT_BUF_INIT; + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + + opts.subject_prefix = "PPPPPATCH"; + + git_oid_fromstr(&oid, "627e7e12d87e07a83fad5b6bfa25e86ead4a5270"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_diff__commit(&diff, repo, commit, NULL)); + cl_git_pass(git_email_create_from_diff(&buf, diff, 2, 4, &oid, summary, body, git_commit_author(commit), &opts)); + + cl_assert_equal_s(expected, git_buf_cstr(&buf)); + + git_diff_free(diff); + git_commit_free(commit); + git_buf_dispose(&buf); +} + +void test_email_create__mode_change(void) +{ + const char *expected = + "From 7ade76dd34bba4733cf9878079f9fd4a456a9189 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Thu, 10 Apr 2014 10:05:03 +0200\n" \ + "Subject: [PATCH] Update permissions\n" \ + "\n" \ + "---\n" \ + " file1.txt.renamed | 0\n" \ + " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ + " mode change 100644 => 100755 file1.txt.renamed\n" \ + "\n" \ + "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \ + "old mode 100644\n" \ + "new mode 100755\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + assert_email_match(expected, "7ade76dd34bba4733cf9878079f9fd4a456a9189", NULL); +} + +void test_email_create__rename(void) +{ + const char *expected = + "From 6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Wed, 9 Apr 2014 21:15:56 +0200\n" \ + "Subject: [PATCH] Renamed file1.txt -> file1.txt.renamed\n" \ + "\n" \ + "---\n" \ + " file1.txt => file1.txt.renamed | 4 ++--\n" \ + " 1 file changed, 2 insertions(+), 2 deletions(-)\n" \ + "\n" \ + "diff --git a/file1.txt b/file1.txt.renamed\n" \ + "similarity index 86%\n" \ + "rename from file1.txt\n" \ + "rename to file1.txt.renamed\n" \ + "index af8f41d..a97157a 100644\n" \ + "--- a/file1.txt\n" \ + "+++ b/file1.txt.renamed\n" \ + "@@ -3,13 +3,13 @@ file1.txt\n" \ + " _file1.txt_\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "-file1.txt\n" \ + "+file1.txt_renamed\n" \ + " file1.txt\n" \ + " \n" \ + " \n" \ + " file1.txt\n" \ + " file1.txt\n" \ + "-file1.txt\n" \ + "+file1.txt_renamed\n" \ + " file1.txt\n" \ + " file1.txt\n" \ + " _file1.txt_\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + assert_email_match(expected, "6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d", NULL); +} + +void test_email_create__rename_as_add_delete(void) +{ + const char *expected = + "From 6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Wed, 9 Apr 2014 21:15:56 +0200\n" \ + "Subject: [PATCH] Renamed file1.txt -> file1.txt.renamed\n" \ + "\n" \ + "---\n" \ + " file1.txt | 17 -----------------\n" \ + " file1.txt.renamed | 17 +++++++++++++++++\n" \ + " 2 files changed, 17 insertions(+), 17 deletions(-)\n" \ + " delete mode 100644 file1.txt\n" \ + " create mode 100644 file1.txt.renamed\n" \ + "\n" \ + "diff --git a/file1.txt b/file1.txt\n" \ + "deleted file mode 100644\n" \ + "index af8f41d..0000000\n" \ + "--- a/file1.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,17 +0,0 @@\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-_file1.txt_\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-\n" \ + "-\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-file1.txt\n" \ + "-_file1.txt_\n" \ + "-_file1.txt_\n" \ + "-file1.txt\n" \ + "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \ + "new file mode 100644\n" \ + "index 0000000..a97157a\n" \ + "--- /dev/null\n" \ + "+++ b/file1.txt.renamed\n" \ + "@@ -0,0 +1,17 @@\n" \ + "+file1.txt\n" \ + "+file1.txt\n" \ + "+_file1.txt_\n" \ + "+file1.txt\n" \ + "+file1.txt\n" \ + "+file1.txt_renamed\n" \ + "+file1.txt\n" \ + "+\n" \ + "+\n" \ + "+file1.txt\n" \ + "+file1.txt\n" \ + "+file1.txt_renamed\n" \ + "+file1.txt\n" \ + "+file1.txt\n" \ + "+_file1.txt_\n" \ + "+_file1.txt_\n" \ + "+file1.txt\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + opts.flags |= GIT_EMAIL_CREATE_NO_RENAMES; + + assert_email_match(expected, "6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d", &opts); +} + +void test_email_create__binary(void) +{ + const char *expected = + "From 8d7523f6fcb2404257889abe0d96f093d9f524f9 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Sun, 13 Apr 2014 18:10:18 +0200\n" \ + "Subject: [PATCH] Modified binary file\n" \ + "\n" \ + "---\n" \ + " binary.bin | Bin 3 -> 5 bytes\n" \ + " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ + "\n" \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index bd474b2519cc15eab801ff851cc7d50f0dee49a1..9ac35ff15cd8864aeafd889e4826a3150f0b06c4 100644\n" \ + "GIT binary patch\n" \ + "literal 5\n" \ + "Mc${NkU}WL~000&M4gdfE\n" \ + "\n" \ + "literal 3\n" \ + "Kc${Nk-~s>u4FC%O\n" \ + "\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + assert_email_match(expected, "8d7523f6fcb2404257889abe0d96f093d9f524f9", NULL); +} + +void test_email_create__binary_not_included(void) +{ + const char *expected = + "From 8d7523f6fcb2404257889abe0d96f093d9f524f9 Mon Sep 17 00:00:00 2001\n" \ + "From: Jacques Germishuys \n" \ + "Date: Sun, 13 Apr 2014 18:10:18 +0200\n" \ + "Subject: [PATCH] Modified binary file\n" \ + "\n" \ + "---\n" \ + " binary.bin | Bin 3 -> 5 bytes\n" \ + " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \ + "\n" \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index bd474b2..9ac35ff 100644\n" \ + "Binary files a/binary.bin and b/binary.bin differ\n" \ + "--\n" \ + "libgit2 " LIBGIT2_VERSION "\n" \ + "\n"; + + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + opts.diff_opts.flags &= ~GIT_DIFF_SHOW_BINARY; + + assert_email_match(expected, "8d7523f6fcb2404257889abe0d96f093d9f524f9", &opts); +} + +void test_email_create__commit_subjects(void) +{ + git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT; + + assert_subject_match("[PATCH] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.reroll_number = 42; + assert_subject_match("[PATCH v42] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.flags |= GIT_EMAIL_CREATE_ALWAYS_NUMBER; + assert_subject_match("[PATCH v42 1/1] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.start_number = 9; + assert_subject_match("[PATCH v42 9/9] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.subject_prefix = ""; + assert_subject_match("[v42 9/9] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.reroll_number = 0; + assert_subject_match("[9/9] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.start_number = 0; + assert_subject_match("[1/1] Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); + + opts.flags = GIT_EMAIL_CREATE_OMIT_NUMBERS; + assert_subject_match("Modify some content", "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts); +} diff --git a/tests/libgit2/fetch/local.c b/tests/libgit2/fetch/local.c new file mode 100644 index 000000000..20bd7adf4 --- /dev/null +++ b/tests/libgit2/fetch/local.c @@ -0,0 +1,67 @@ +#include "clar_libgit2.h" +#include "futils.h" + +static git_repository *repo; + +void test_fetch_local__initialize(void) +{ + cl_git_pass(git_repository_init(&repo, "./fetch", 0)); +} + +void test_fetch_local__cleanup(void) +{ + git_repository_free(repo); + repo = NULL; + + cl_fixture_cleanup("./fetch"); +} + +void test_fetch_local__defaults(void) +{ + git_remote *remote; + git_object *obj; + git_oid expected_id; + + cl_git_pass(git_remote_create(&remote, repo, "test", + cl_fixture("testrepo.git"))); + cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); + + git_oid_fromstr(&expected_id, "258f0e2a959a364e40ed6603d5d44fbb24765b10"); + + cl_git_pass(git_revparse_single(&obj, repo, "refs/remotes/test/haacked")); + cl_assert_equal_oid(&expected_id, git_object_id(obj)); + + git_object_free(obj); + git_remote_free(remote); +} + +void test_fetch_local__reachable_commit(void) +{ + git_remote *remote; + git_strarray refspecs; + git_object *obj; + git_oid expected_id; + git_str fetchhead = GIT_STR_INIT; + char *refspec = "+5b5b025afb0b4c913b4c338a42934a3863bf3644:refs/success"; + + refspecs.strings = &refspec; + refspecs.count = 1; + + git_oid_fromstr(&expected_id, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + + cl_git_pass(git_remote_create(&remote, repo, "test", + cl_fixture("testrepo.git"))); + cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, NULL)); + + cl_git_pass(git_revparse_single(&obj, repo, "refs/success")); + cl_assert_equal_oid(&expected_id, git_object_id(obj)); + + cl_git_pass(git_futils_readbuffer(&fetchhead, "./fetch/.git/FETCH_HEAD")); + cl_assert_equal_strn(fetchhead.ptr, + "5b5b025afb0b4c913b4c338a42934a3863bf3644\t\t'5b5b025afb0b4c913b4c338a42934a3863bf3644' of ", + strlen("5b5b025afb0b4c913b4c338a42934a3863bf3644\t\t'5b5b025afb0b4c913b4c338a42934a3863bf3644' of ")); + + git_str_dispose(&fetchhead); + git_object_free(obj); + git_remote_free(remote); +} diff --git a/tests/libgit2/fetchhead/fetchhead_data.h b/tests/libgit2/fetchhead/fetchhead_data.h new file mode 100644 index 000000000..77c322001 --- /dev/null +++ b/tests/libgit2/fetchhead/fetchhead_data.h @@ -0,0 +1,48 @@ + +#define FETCH_HEAD_WILDCARD_DATA_LOCAL \ + "49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \ + "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \ + "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \ + "d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of https://github.com/libgit2/TestGitRepository\n" \ + "55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of https://github.com/libgit2/TestGitRepository\n" \ + "8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of https://github.com/libgit2/TestGitRepository\n" + +#define FETCH_HEAD_WILDCARD_DATA \ + "49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \ + "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \ + "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \ + "d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of https://github.com/libgit2/TestGitRepository\n" \ + "55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of https://github.com/libgit2/TestGitRepository\n" \ + "8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of https://github.com/libgit2/TestGitRepository\n" \ + "6e0c7bdb9b4ed93212491ee778ca1c65047cab4e\tnot-for-merge\ttag 'nearly-dangling' of https://github.com/libgit2/TestGitRepository\n" + +#define FETCH_HEAD_WILDCARD_DATA2 \ + "49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \ + "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \ + "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \ + +#define FETCH_HEAD_NO_MERGE_DATA \ + "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \ + "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \ + "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \ + "d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of https://github.com/libgit2/TestGitRepository\n" \ + "55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of https://github.com/libgit2/TestGitRepository\n" \ + "8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of https://github.com/libgit2/TestGitRepository\n" \ + "6e0c7bdb9b4ed93212491ee778ca1c65047cab4e\tnot-for-merge\ttag 'nearly-dangling' of https://github.com/libgit2/TestGitRepository\n" + +#define FETCH_HEAD_NO_MERGE_DATA2 \ + "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \ + "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \ + "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \ + +#define FETCH_HEAD_NO_MERGE_DATA3 \ + "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" \ + "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of https://github.com/libgit2/TestGitRepository\n" \ + "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of https://github.com/libgit2/TestGitRepository\n" \ + "8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of https://github.com/libgit2/TestGitRepository\n" \ + +#define FETCH_HEAD_EXPLICIT_DATA \ + "0966a434eb1a025db6b71485ab63a3bfbea520b6\t\tbranch 'first-merge' of https://github.com/libgit2/TestGitRepository\n" + +#define FETCH_HEAD_QUOTE_DATA \ + "0966a434eb1a025db6b71485ab63a3bfbea520b6\t\tbranch 'first's-merge' of https://github.com/libgit2/TestGitRepository\n" diff --git a/tests/libgit2/fetchhead/nonetwork.c b/tests/libgit2/fetchhead/nonetwork.c new file mode 100644 index 000000000..aadcc7880 --- /dev/null +++ b/tests/libgit2/fetchhead/nonetwork.c @@ -0,0 +1,542 @@ +#include "clar_libgit2.h" + +#include "futils.h" +#include "fetchhead.h" + +#include "fetchhead_data.h" + +#define DO_LOCAL_TEST 0 + +static git_repository *g_repo; + +void test_fetchhead_nonetwork__initialize(void) +{ + g_repo = NULL; +} + +static void cleanup_repository(void *path) +{ + if (g_repo) { + git_repository_free(g_repo); + g_repo = NULL; + } + + cl_fixture_cleanup((const char *)path); +} + +static void populate_fetchhead(git_vector *out, git_repository *repo) +{ + git_fetchhead_ref *fetchhead_ref; + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, + "49322bb17d3acc9146f98c97d078513228bbf3c0")); + cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 1, + "refs/heads/master", + "https://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_vector_insert(out, fetchhead_ref)); + + cl_git_pass(git_oid_fromstr(&oid, + "0966a434eb1a025db6b71485ab63a3bfbea520b6")); + cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, + "refs/heads/first-merge", + "https://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_vector_insert(out, fetchhead_ref)); + + cl_git_pass(git_oid_fromstr(&oid, + "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1")); + cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, + "refs/heads/no-parent", + "https://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_vector_insert(out, fetchhead_ref)); + + cl_git_pass(git_oid_fromstr(&oid, + "d96c4e80345534eccee5ac7b07fc7603b56124cb")); + cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, + "refs/tags/annotated_tag", + "https://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_vector_insert(out, fetchhead_ref)); + + cl_git_pass(git_oid_fromstr(&oid, + "55a1a760df4b86a02094a904dfa511deb5655905")); + cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, + "refs/tags/blob", + "https://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_vector_insert(out, fetchhead_ref)); + + cl_git_pass(git_oid_fromstr(&oid, + "8f50ba15d49353813cc6e20298002c0d17b0a9ee")); + cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0, + "refs/tags/commit_tree", + "https://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_vector_insert(out, fetchhead_ref)); + + cl_git_pass(git_fetchhead_write(repo, out)); +} + +void test_fetchhead_nonetwork__write(void) +{ + git_vector fetchhead_vector = GIT_VECTOR_INIT; + git_fetchhead_ref *fetchhead_ref; + git_str fetchhead_buf = GIT_STR_INIT; + int equals = 0; + size_t i; + + cl_git_pass(git_vector_init(&fetchhead_vector, 6, NULL)); + + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + populate_fetchhead(&fetchhead_vector, g_repo); + + cl_git_pass(git_futils_readbuffer(&fetchhead_buf, + "./test1/.git/FETCH_HEAD")); + + equals = (strcmp(fetchhead_buf.ptr, FETCH_HEAD_WILDCARD_DATA_LOCAL) == 0); + + git_str_dispose(&fetchhead_buf); + + git_vector_foreach(&fetchhead_vector, i, fetchhead_ref) { + git_fetchhead_ref_free(fetchhead_ref); + } + + git_vector_free(&fetchhead_vector); + + cl_assert(equals); +} + +typedef struct { + git_vector *fetchhead_vector; + size_t idx; +} fetchhead_ref_cb_data; + +static int fetchhead_ref_cb(const char *name, const char *url, + const git_oid *oid, unsigned int is_merge, void *payload) +{ + fetchhead_ref_cb_data *cb_data = payload; + git_fetchhead_ref *expected; + + cl_assert(payload); + + expected = git_vector_get(cb_data->fetchhead_vector, cb_data->idx); + + cl_assert_equal_oid(&expected->oid, oid); + cl_assert(expected->is_merge == is_merge); + + if (expected->ref_name) + cl_assert_equal_s(expected->ref_name, name); + else + cl_assert(name == NULL); + + if (expected->remote_url) + cl_assert_equal_s(expected->remote_url, url); + else + cl_assert(url == NULL); + + cb_data->idx++; + + return 0; +} + +void test_fetchhead_nonetwork__read(void) +{ + git_vector fetchhead_vector = GIT_VECTOR_INIT; + git_fetchhead_ref *fetchhead_ref; + fetchhead_ref_cb_data cb_data; + size_t i; + + memset(&cb_data, 0x0, sizeof(fetchhead_ref_cb_data)); + + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + populate_fetchhead(&fetchhead_vector, g_repo); + + cb_data.fetchhead_vector = &fetchhead_vector; + + cl_git_pass(git_repository_fetchhead_foreach(g_repo, fetchhead_ref_cb, &cb_data)); + + git_vector_foreach(&fetchhead_vector, i, fetchhead_ref) { + git_fetchhead_ref_free(fetchhead_ref); + } + + git_vector_free(&fetchhead_vector); +} + +static int read_old_style_cb(const char *name, const char *url, + const git_oid *oid, unsigned int is_merge, void *payload) +{ + git_oid expected; + + GIT_UNUSED(payload); + + git_oid_fromstr(&expected, "49322bb17d3acc9146f98c97d078513228bbf3c0"); + + cl_assert(name == NULL); + cl_assert(url == NULL); + cl_assert_equal_oid(&expected, oid); + cl_assert(is_merge == 1); + + return 0; +} + +void test_fetchhead_nonetwork__read_old_style(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\n"); + + cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_old_style_cb, NULL)); +} + +static int read_type_missing(const char *ref_name, const char *remote_url, + const git_oid *oid, unsigned int is_merge, void *payload) +{ + git_oid expected; + + GIT_UNUSED(payload); + + git_oid_fromstr(&expected, "49322bb17d3acc9146f98c97d078513228bbf3c0"); + + cl_assert_equal_s("name", ref_name); + cl_assert_equal_s("remote_url", remote_url); + cl_assert_equal_oid(&expected, oid); + cl_assert(is_merge == 0); + + return 0; +} + +void test_fetchhead_nonetwork__type_missing(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\t'name' of remote_url\n"); + + cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_type_missing, NULL)); +} + +static int read_name_missing(const char *ref_name, const char *remote_url, + const git_oid *oid, unsigned int is_merge, void *payload) +{ + git_oid expected; + + GIT_UNUSED(payload); + + git_oid_fromstr(&expected, "49322bb17d3acc9146f98c97d078513228bbf3c0"); + + cl_assert(ref_name == NULL); + cl_assert_equal_s("remote_url", remote_url); + cl_assert_equal_oid(&expected, oid); + cl_assert(is_merge == 0); + + return 0; +} + +void test_fetchhead_nonetwork__name_missing(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tremote_url\n"); + + cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_name_missing, NULL)); +} + +static int read_noop(const char *ref_name, const char *remote_url, + const git_oid *oid, unsigned int is_merge, void *payload) +{ + GIT_UNUSED(ref_name); + GIT_UNUSED(remote_url); + GIT_UNUSED(oid); + GIT_UNUSED(is_merge); + GIT_UNUSED(payload); + + return 0; +} + +void test_fetchhead_nonetwork__nonexistent(void) +{ + int error; + + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_fail((error = git_repository_fetchhead_foreach(g_repo, read_noop, NULL))); + cl_assert(error == GIT_ENOTFOUND); +} + +void test_fetchhead_nonetwork__invalid_unterminated_last_line(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "unterminated"); + cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); +} + +void test_fetchhead_nonetwork__invalid_oid(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "shortoid\n"); + cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); +} + +void test_fetchhead_nonetwork__invalid_for_merge(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tinvalid-merge\t\n"); + cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); + + cl_assert(git__prefixcmp(git_error_last()->message, "invalid for-merge") == 0); +} + +void test_fetchhead_nonetwork__invalid_description(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\n"); + cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); + + cl_assert(git__prefixcmp(git_error_last()->message, "invalid description") == 0); +} + +static int assert_master_for_merge(const char *ref, const char *url, const git_oid *id, unsigned int is_merge, void *data) +{ + GIT_UNUSED(url); + GIT_UNUSED(id); + GIT_UNUSED(data); + + if (!strcmp("refs/heads/master", ref) && !is_merge) + return -1; + + return 0; +} + +static int assert_none_for_merge(const char *ref, const char *url, const git_oid *id, unsigned int is_merge, void *data) +{ + GIT_UNUSED(ref); + GIT_UNUSED(url); + GIT_UNUSED(id); + GIT_UNUSED(data); + + return is_merge ? -1 : 0; +} + +void test_fetchhead_nonetwork__unborn_with_upstream(void) +{ + git_repository *repo; + git_remote *remote; + + /* Create an empty repo to clone from */ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + cl_set_cleanup(&cleanup_repository, "./repowithunborn"); + cl_git_pass(git_clone(&repo, "./test1", "./repowithunborn", NULL)); + + /* Simulate someone pushing to it by changing to one that has stuff */ + cl_git_pass(git_remote_set_url(repo, "origin", cl_fixture("testrepo.git"))); + cl_git_pass(git_remote_lookup(&remote, repo, "origin")); + + cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); + git_remote_free(remote); + + cl_git_pass(git_repository_fetchhead_foreach(repo, assert_master_for_merge, NULL)); + + git_repository_free(repo); + cl_fixture_cleanup("./repowithunborn"); +} + +void test_fetchhead_nonetwork__fetch_into_repo_with_symrefs(void) +{ + git_repository *repo; + git_remote *remote; + git_reference *symref; + + repo = cl_git_sandbox_init("empty_standard_repo"); + + /* + * Testing for a specific constellation where the repository has at + * least one symbolic reference in its refdb. + */ + cl_git_pass(git_reference_symbolic_create(&symref, repo, "refs/heads/symref", "refs/heads/master", 0, NULL)); + + cl_git_pass(git_remote_set_url(repo, "origin", cl_fixture("testrepo.git"))); + cl_git_pass(git_remote_lookup(&remote, repo, "origin")); + cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); + + git_remote_free(remote); + git_reference_free(symref); + cl_git_sandbox_cleanup(); +} + +void test_fetchhead_nonetwork__fetch_into_repo_with_invalid_head(void) +{ + git_remote *remote; + char *strings[] = { "refs/heads/*:refs/remotes/origin/*" }; + git_strarray refspecs = { strings, 1 }; + + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + /* HEAD pointing to nonexistent branch */ + cl_git_rewritefile("./test1/.git/HEAD", "ref: refs/heads/\n"); + + cl_git_pass(git_remote_create_anonymous(&remote, g_repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, NULL)); + cl_git_pass(git_repository_fetchhead_foreach(g_repo, assert_none_for_merge, NULL)); + + git_remote_free(remote); +} + +void test_fetchhead_nonetwork__quote_in_branch_name(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_rewritefile("./test1/.git/FETCH_HEAD", FETCH_HEAD_QUOTE_DATA); + cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_noop, NULL)); +} + +static bool found_master; +static bool found_haacked; +static bool find_master_haacked_called; + +static int find_master_haacked(const char *ref_name, const char *remote_url, const git_oid *oid, unsigned int is_merge, void *payload) +{ + GIT_UNUSED(remote_url); + GIT_UNUSED(oid); + GIT_UNUSED(payload); + + find_master_haacked_called = true; + + if (!strcmp("refs/heads/master", ref_name)) { + cl_assert(is_merge); + found_master = true; + } + if (!strcmp("refs/heads/haacked", ref_name)) { + cl_assert(is_merge); + found_haacked = true; + } + + return 0; +} + +void test_fetchhead_nonetwork__create_when_refpecs_given(void) +{ + git_remote *remote; + git_str path = GIT_STR_INIT; + char *refspec1 = "refs/heads/master"; + char *refspec2 = "refs/heads/haacked"; + char *refspecs[] = { refspec1, refspec2 }; + git_strarray specs = { + refspecs, + 2, + }; + + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_pass(git_str_joinpath(&path, git_repository_path(g_repo), "FETCH_HEAD")); + cl_git_pass(git_remote_create(&remote, g_repo, "origin", cl_fixture("testrepo.git"))); + + cl_assert(!git_fs_path_exists(path.ptr)); + cl_git_pass(git_remote_fetch(remote, &specs, NULL, NULL)); + cl_assert(git_fs_path_exists(path.ptr)); + + cl_git_pass(git_repository_fetchhead_foreach(g_repo, find_master_haacked, NULL)); + cl_assert(find_master_haacked_called); + cl_assert(found_master); + cl_assert(found_haacked); + + git_remote_free(remote); + git_str_dispose(&path); +} + +static bool count_refs_called; +struct prefix_count { + const char *prefix; + int count; + int expected; +}; + +static int count_refs(const char *ref_name, const char *remote_url, const git_oid *oid, unsigned int is_merge, void *payload) +{ + int i; + struct prefix_count *prefix_counts = (struct prefix_count *) payload; + + GIT_UNUSED(remote_url); + GIT_UNUSED(oid); + GIT_UNUSED(is_merge); + + count_refs_called = true; + + for (i = 0; prefix_counts[i].prefix; i++) { + if (!git__prefixcmp(ref_name, prefix_counts[i].prefix)) + prefix_counts[i].count++; + } + + return 0; +} + +void test_fetchhead_nonetwork__create_with_multiple_refspecs(void) +{ + git_remote *remote; + git_str path = GIT_STR_INIT; + + cl_set_cleanup(&cleanup_repository, "./test1"); + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_pass(git_remote_create(&remote, g_repo, "origin", cl_fixture("testrepo.git"))); + git_remote_free(remote); + cl_git_pass(git_remote_add_fetch(g_repo, "origin", "+refs/notes/*:refs/origin/notes/*")); + /* Pick up the new refspec */ + cl_git_pass(git_remote_lookup(&remote, g_repo, "origin")); + + cl_git_pass(git_str_joinpath(&path, git_repository_path(g_repo), "FETCH_HEAD")); + cl_assert(!git_fs_path_exists(path.ptr)); + cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); + cl_assert(git_fs_path_exists(path.ptr)); + + { + int i; + struct prefix_count prefix_counts[] = { + {"refs/notes/", 0, 1}, + {"refs/heads/", 0, 13}, + {"refs/tags/", 0, 7}, + {NULL, 0, 0}, + }; + + cl_git_pass(git_repository_fetchhead_foreach(g_repo, count_refs, &prefix_counts)); + cl_assert(count_refs_called); + for (i = 0; prefix_counts[i].prefix; i++) + cl_assert_equal_i(prefix_counts[i].expected, prefix_counts[i].count); + } + + git_remote_free(remote); + git_str_dispose(&path); +} + +void test_fetchhead_nonetwork__credentials_are_stripped(void) +{ + git_fetchhead_ref *ref; + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, "49322bb17d3acc9146f98c97d078513228bbf3c0")); + cl_git_pass(git_fetchhead_ref_create(&ref, &oid, 0, + "refs/tags/commit_tree", "http://foo:bar@github.com/libgit2/TestGitRepository")); + cl_assert_equal_s(ref->remote_url, "http://github.com/libgit2/TestGitRepository"); + git_fetchhead_ref_free(ref); + + cl_git_pass(git_oid_fromstr(&oid, "49322bb17d3acc9146f98c97d078513228bbf3c0")); + cl_git_pass(git_fetchhead_ref_create(&ref, &oid, 0, + "refs/tags/commit_tree", "https://foo:bar@github.com/libgit2/TestGitRepository")); + cl_assert_equal_s(ref->remote_url, "https://github.com/libgit2/TestGitRepository"); + git_fetchhead_ref_free(ref); +} diff --git a/tests/libgit2/filter/bare.c b/tests/libgit2/filter/bare.c new file mode 100644 index 000000000..8402638ce --- /dev/null +++ b/tests/libgit2/filter/bare.c @@ -0,0 +1,188 @@ +#include "clar_libgit2.h" +#include "crlf.h" + +static git_repository *g_repo = NULL; +static git_blob_filter_options filter_opts = GIT_BLOB_FILTER_OPTIONS_INIT; + +void test_filter_bare__initialize(void) +{ + cl_fixture_sandbox("crlf.git"); + cl_git_pass(git_repository_open(&g_repo, "crlf.git")); + + filter_opts.flags |= GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES; + filter_opts.flags |= GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD; +} + +void test_filter_bare__cleanup(void) +{ + git_repository_free(g_repo); + cl_fixture_cleanup("crlf.git"); +} + +void test_filter_bare__all_crlf(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "a9a2e89")); /* all-crlf */ + + cl_assert_equal_s(ALL_CRLF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &filter_opts)); + + cl_assert_equal_s(ALL_CRLF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &filter_opts)); + + /* in this case, raw content has crlf in it already */ + cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &filter_opts)); + + /* we never convert CRLF -> LF on platforms that have LF */ + cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.txt", &filter_opts)); + + /* in this case, raw content has crlf in it already */ + cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} + +void test_filter_bare__from_lf(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "799770d")); /* all-lf */ + + cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &filter_opts)); + + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &filter_opts)); + + /* in this case, raw content has crlf in it already */ + cl_assert_equal_s(ALL_LF_TEXT_AS_CRLF, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &filter_opts)); + + /* we never convert CRLF -> LF on platforms that have LF */ + cl_assert_equal_s(ALL_LF_TEXT_AS_LF, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} + +void test_filter_bare__nested_attributes(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "799770d")); /* all-lf */ + + cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "raw/file.bin", &filter_opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "raw/file.crlf", &filter_opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "raw/file.lf", &filter_opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} + +void test_filter_bare__sanitizes(void) +{ + git_blob *blob; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "e69de29")); /* zero-byte */ + + cl_assert_equal_i(0, git_blob_rawsize(blob)); + cl_assert_equal_s("", git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &filter_opts)); + cl_assert_equal_sz(0, buf.size); + cl_assert_equal_s("", buf.ptr); + git_buf_dispose(&buf); + + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &filter_opts)); + cl_assert_equal_sz(0, buf.size); + cl_assert_equal_s("", buf.ptr); + git_buf_dispose(&buf); + + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &filter_opts)); + cl_assert_equal_sz(0, buf.size); + cl_assert_equal_s("", buf.ptr); + git_buf_dispose(&buf); + + git_blob_free(blob); +} + +void test_filter_bare__from_specific_commit_one(void) +{ + git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; + git_blob *blob; + git_buf buf = { 0 }; + + opts.flags |= GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES; + opts.flags |= GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT; + cl_git_pass(git_oid_fromstr(&opts.attr_commit_id, "b8986fec0f7bde90f78ac72706e782d82f24f2f0")); + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "055c872")); /* ident */ + + cl_assert_equal_s("$Id$\n", git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "ident.bin", &opts)); + cl_assert_equal_s("$Id$\n", buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "ident.identlf", &opts)); + cl_assert_equal_s("$Id: 055c8729cdcc372500a08db659c045e16c4409fb $\n", buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} + +void test_filter_bare__from_specific_commit_with_no_attributes_file(void) +{ + git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; + git_blob *blob; + git_buf buf = { 0 }; + + opts.flags |= GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES; + opts.flags |= GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT; + cl_git_pass(git_oid_fromstr(&opts.attr_commit_id, "5afb6a14a864e30787857dd92af837e8cdd2cb1b")); + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "799770d")); /* all-lf */ + + cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + /* we never convert CRLF -> LF on platforms that have LF */ + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + /* we never convert CRLF -> LF on platforms that have LF */ + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} diff --git a/tests/libgit2/filter/blob.c b/tests/libgit2/filter/blob.c new file mode 100644 index 000000000..1cc33d42f --- /dev/null +++ b/tests/libgit2/filter/blob.c @@ -0,0 +1,145 @@ +#include "clar_libgit2.h" +#include "crlf.h" + +static git_repository *g_repo = NULL; + +void test_filter_blob__initialize(void) +{ + g_repo = cl_git_sandbox_init("crlf"); + cl_git_mkfile("crlf/.gitattributes", + "*.txt text\n*.bin binary\n" + "*.crlf text eol=crlf\n" + "*.lf text eol=lf\n" + "*.ident text ident\n" + "*.identcrlf ident text eol=crlf\n" + "*.identlf ident text eol=lf\n"); +} + +void test_filter_blob__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_filter_blob__all_crlf(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "a9a2e891")); /* all-crlf */ + + cl_assert_equal_s(ALL_CRLF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", NULL)); + + cl_assert_equal_s(ALL_CRLF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", NULL)); + + /* in this case, raw content has crlf in it already */ + cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", NULL)); + + /* we never convert CRLF -> LF on platforms that have LF */ + cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} + +void test_filter_blob__from_lf(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "799770d")); /* all-lf */ + + cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", NULL)); + + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", NULL)); + + /* in this case, raw content has crlf in it already */ + cl_assert_equal_s(ALL_LF_TEXT_AS_CRLF, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", NULL)); + + /* we never convert CRLF -> LF on platforms that have LF */ + cl_assert_equal_s(ALL_LF_TEXT_AS_LF, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} + +void test_filter_blob__sanitizes(void) +{ + git_blob *blob; + git_buf buf; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "e69de29")); /* zero-byte */ + + cl_assert_equal_i(0, git_blob_rawsize(blob)); + cl_assert_equal_s("", git_blob_rawcontent(blob)); + + memset(&buf, 0, sizeof(git_buf)); + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", NULL)); + cl_assert_equal_sz(0, buf.size); + cl_assert_equal_s("", buf.ptr); + git_buf_dispose(&buf); + + memset(&buf, 0, sizeof(git_buf)); + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", NULL)); + cl_assert_equal_sz(0, buf.size); + cl_assert_equal_s("", buf.ptr); + git_buf_dispose(&buf); + + memset(&buf, 0, sizeof(git_buf)); + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", NULL)); + cl_assert_equal_sz(0, buf.size); + cl_assert_equal_s("", buf.ptr); + git_buf_dispose(&buf); + + git_blob_free(blob); +} + +void test_filter_blob__ident(void) +{ + git_oid id; + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_mkfile("crlf/test.ident", "Some text\n$Id$\nGoes there\n"); + cl_git_pass(git_blob_create_from_workdir(&id, g_repo, "test.ident")); + cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); + cl_assert_equal_s( + "Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob)); + git_blob_free(blob); + + cl_git_mkfile("crlf/test.ident", "Some text\n$Id: Any old just you want$\nGoes there\n"); + cl_git_pass(git_blob_create_from_workdir(&id, g_repo, "test.ident")); + cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); + cl_assert_equal_s( + "Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "filter.bin", NULL)); + cl_assert_equal_s( + "Some text\n$Id$\nGoes there\n", buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "filter.identcrlf", NULL)); + cl_assert_equal_s( + "Some text\r\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845 $\r\nGoes there\r\n", buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "filter.identlf", NULL)); + cl_assert_equal_s( + "Some text\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845 $\nGoes there\n", buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); + +} diff --git a/tests/libgit2/filter/crlf.c b/tests/libgit2/filter/crlf.c new file mode 100644 index 000000000..925ea58d2 --- /dev/null +++ b/tests/libgit2/filter/crlf.c @@ -0,0 +1,253 @@ +#include "clar_libgit2.h" +#include "git2/sys/filter.h" + +static git_repository *g_repo = NULL; + +void test_filter_crlf__initialize(void) +{ + g_repo = cl_git_sandbox_init("crlf"); + + cl_git_mkfile("crlf/.gitattributes", + "*.txt text\n*.bin binary\n*.crlf text eol=crlf\n*.lf text eol=lf\n"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); +} + +void test_filter_crlf__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_filter_crlf__to_worktree(void) +{ + git_filter_list *fl; + git_filter *crlf; + git_buf out = GIT_BUF_INIT; + const char *in; + size_t in_len; + + cl_git_pass(git_filter_list_new( + &fl, g_repo, GIT_FILTER_TO_WORKTREE, 0)); + + crlf = git_filter_lookup(GIT_FILTER_CRLF); + cl_assert(crlf != NULL); + + cl_git_pass(git_filter_list_push(fl, crlf, NULL)); + + in = "Some text\nRight here\n"; + in_len = strlen(in); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + + cl_assert_equal_s("Some text\r\nRight here\r\n", out.ptr); + + git_filter_list_free(fl); + git_buf_dispose(&out); +} + +void test_filter_crlf__to_odb(void) +{ + git_filter_list *fl; + git_filter *crlf; + git_buf out = GIT_BUF_INIT; + const char *in; + size_t in_len; + + cl_git_pass(git_filter_list_new( + &fl, g_repo, GIT_FILTER_TO_ODB, 0)); + + crlf = git_filter_lookup(GIT_FILTER_CRLF); + cl_assert(crlf != NULL); + + cl_git_pass(git_filter_list_push(fl, crlf, NULL)); + + in = "Some text\r\nRight here\r\n"; + in_len = strlen(in); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + + cl_assert_equal_s("Some text\nRight here\n", out.ptr); + + git_filter_list_free(fl); + git_buf_dispose(&out); +} + +void test_filter_crlf__with_safecrlf(void) +{ + git_filter_list *fl; + git_filter *crlf; + git_buf out = GIT_BUF_INIT; + const char *in; + size_t in_len; + + cl_repo_set_bool(g_repo, "core.safecrlf", true); + + cl_git_pass(git_filter_list_new( + &fl, g_repo, GIT_FILTER_TO_ODB, 0)); + + crlf = git_filter_lookup(GIT_FILTER_CRLF); + cl_assert(crlf != NULL); + + cl_git_pass(git_filter_list_push(fl, crlf, NULL)); + + /* Normalized \r\n succeeds with safecrlf */ + in = "Normal\r\nCRLF\r\nline-endings.\r\n"; + in_len = strlen(in); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); + + /* Mix of line endings fails with safecrlf */ + in = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; + in_len = strlen(in); + + cl_git_fail(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_assert_equal_i(git_error_last()->klass, GIT_ERROR_FILTER); + + /* Normalized \n fails for autocrlf=true when safecrlf=true */ + in = "Normal\nLF\nonly\nline-endings.\n"; + in_len = strlen(in); + + cl_git_fail(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_assert_equal_i(git_error_last()->klass, GIT_ERROR_FILTER); + + /* String with \r but without \r\n does not fail with safecrlf */ + in = "Normal\nCR only\rand some more\nline-endings.\n"; + in_len = strlen(in); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_assert_equal_s("Normal\nCR only\rand some more\nline-endings.\n", out.ptr); + + git_filter_list_free(fl); + git_buf_dispose(&out); +} + +void test_filter_crlf__with_safecrlf_and_unsafe_allowed(void) +{ + git_filter_list *fl; + git_filter *crlf; + git_buf out = GIT_BUF_INIT; + const char *in; + size_t in_len; + + cl_repo_set_bool(g_repo, "core.safecrlf", true); + + cl_git_pass(git_filter_list_new( + &fl, g_repo, GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)); + + crlf = git_filter_lookup(GIT_FILTER_CRLF); + cl_assert(crlf != NULL); + + cl_git_pass(git_filter_list_push(fl, crlf, NULL)); + + /* Normalized \r\n succeeds with safecrlf */ + in = "Normal\r\nCRLF\r\nline-endings.\r\n"; + in_len = strlen(in); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); + + /* Mix of line endings fails with safecrlf, but allowed to pass */ + in = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; + in_len = strlen(in); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + /* TODO: check for warning */ + cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr); + + /* Normalized \n fails with safecrlf, but allowed to pass */ + in = "Normal\nLF\nonly\nline-endings.\n"; + in_len = strlen(in); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + /* TODO: check for warning */ + cl_assert_equal_s("Normal\nLF\nonly\nline-endings.\n", out.ptr); + + git_filter_list_free(fl); + git_buf_dispose(&out); +} + +void test_filter_crlf__no_safecrlf(void) +{ + git_filter_list *fl; + git_filter *crlf; + git_buf out = GIT_BUF_INIT; + const char *in; + size_t in_len; + + cl_git_pass(git_filter_list_new( + &fl, g_repo, GIT_FILTER_TO_ODB, 0)); + + crlf = git_filter_lookup(GIT_FILTER_CRLF); + cl_assert(crlf != NULL); + + cl_git_pass(git_filter_list_push(fl, crlf, NULL)); + + /* Normalized \r\n succeeds with safecrlf */ + in = "Normal\r\nCRLF\r\nline-endings.\r\n"; + in_len = strlen(in); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); + + /* Mix of line endings fails with safecrlf */ + in = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; + in_len = strlen(in); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr); + + /* Normalized \n fails with safecrlf */ + in = "Normal\nLF\nonly\nline-endings.\n"; + in_len = strlen(in); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_assert_equal_s("Normal\nLF\nonly\nline-endings.\n", out.ptr); + + git_filter_list_free(fl); + git_buf_dispose(&out); +} + +void test_filter_crlf__safecrlf_warn(void) +{ + git_filter_list *fl; + git_filter *crlf; + git_buf out = GIT_BUF_INIT; + const char *in; + size_t in_len; + + cl_repo_set_string(g_repo, "core.safecrlf", "warn"); + + cl_git_pass(git_filter_list_new( + &fl, g_repo, GIT_FILTER_TO_ODB, 0)); + + crlf = git_filter_lookup(GIT_FILTER_CRLF); + cl_assert(crlf != NULL); + + cl_git_pass(git_filter_list_push(fl, crlf, NULL)); + + /* Normalized \r\n succeeds with safecrlf=warn */ + in = "Normal\r\nCRLF\r\nline-endings.\r\n"; + in_len = strlen(in); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); + + /* Mix of line endings succeeds with safecrlf=warn */ + in = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; + in_len = strlen(in); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + /* TODO: check for warning */ + cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr); + + /* Normalized \n is reversible, so does not fail with safecrlf=warn */ + in = "Normal\nLF\nonly\nline-endings.\n"; + in_len = strlen(in); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + cl_assert_equal_s(in, out.ptr); + + git_filter_list_free(fl); + git_buf_dispose(&out); +} diff --git a/tests/libgit2/filter/crlf.h b/tests/libgit2/filter/crlf.h new file mode 100644 index 000000000..786edfc96 --- /dev/null +++ b/tests/libgit2/filter/crlf.h @@ -0,0 +1,30 @@ +#ifndef INCLUDE_filter_crlf_h__ +#define INCLUDE_filter_crlf_h__ + +/* + * file content for files in the resources/crlf repository + */ + +#define UTF8_BOM "\xEF\xBB\xBF" + +#define ALL_CRLF_TEXT_RAW "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n" +#define ALL_LF_TEXT_RAW "lf\nlf\nlf\nlf\nlf\n" +#define MORE_CRLF_TEXT_RAW "crlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf\r\n" +#define MORE_LF_TEXT_RAW "lf\nlf\ncrlf\r\nlf\nlf\n" + +#define ALL_CRLF_TEXT_AS_CRLF ALL_CRLF_TEXT_RAW +#define ALL_LF_TEXT_AS_CRLF "lf\r\nlf\r\nlf\r\nlf\r\nlf\r\n" +#define MORE_CRLF_TEXT_AS_CRLF "crlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf\r\n" +#define MORE_LF_TEXT_AS_CRLF "lf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\n" + +#define ALL_CRLF_TEXT_AS_LF "crlf\ncrlf\ncrlf\ncrlf\n" +#define ALL_LF_TEXT_AS_LF ALL_LF_TEXT_RAW +#define MORE_CRLF_TEXT_AS_LF "crlf\ncrlf\nlf\ncrlf\ncrlf\n" +#define MORE_LF_TEXT_AS_LF "lf\nlf\ncrlf\nlf\nlf\n" + +#define FEW_UTF8_CRLF_RAW "\xe2\x9a\xbdThe rest is ASCII01.\r\nThe rest is ASCII02.\r\nThe rest is ASCII03.\r\nThe rest is ASCII04.\r\nThe rest is ASCII05.\r\nThe rest is ASCII06.\r\nThe rest is ASCII07.\r\nThe rest is ASCII08.\r\nThe rest is ASCII09.\r\nThe rest is ASCII10.\r\nThe rest is ASCII11.\r\nThe rest is ASCII12.\r\nThe rest is ASCII13.\r\nThe rest is ASCII14.\r\nThe rest is ASCII15.\r\nThe rest is ASCII16.\r\nThe rest is ASCII17.\r\nThe rest is ASCII18.\r\nThe rest is ASCII19.\r\nThe rest is ASCII20.\r\nThe rest is ASCII21.\r\nThe rest is ASCII22.\r\n" +#define FEW_UTF8_LF_RAW "\xe2\x9a\xbdThe rest is ASCII01.\nThe rest is ASCII02.\nThe rest is ASCII03.\nThe rest is ASCII04.\nThe rest is ASCII05.\nThe rest is ASCII06.\nThe rest is ASCII07.\nThe rest is ASCII08.\nThe rest is ASCII09.\nThe rest is ASCII10.\nThe rest is ASCII11.\nThe rest is ASCII12.\nThe rest is ASCII13.\nThe rest is ASCII14.\nThe rest is ASCII15.\nThe rest is ASCII16.\nThe rest is ASCII17.\nThe rest is ASCII18.\nThe rest is ASCII19.\nThe rest is ASCII20.\nThe rest is ASCII21.\nThe rest is ASCII22.\n" +#define MANY_UTF8_CRLF_RAW "Lets sing!\r\n\xe2\x99\xab\xe2\x99\xaa\xe2\x99\xac\xe2\x99\xa9\r\nEat food\r\n\xf0\x9f\x8d\x85\xf0\x9f\x8d\x95\r\n" +#define MANY_UTF8_LF_RAW "Lets sing!\n\xe2\x99\xab\xe2\x99\xaa\xe2\x99\xac\xe2\x99\xa9\nEat food\n\xf0\x9f\x8d\x85\xf0\x9f\x8d\x95\n" + +#endif diff --git a/tests/libgit2/filter/custom.c b/tests/libgit2/filter/custom.c new file mode 100644 index 000000000..57cef33ce --- /dev/null +++ b/tests/libgit2/filter/custom.c @@ -0,0 +1,268 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "blob.h" +#include "filter.h" +#include "git2/sys/filter.h" +#include "git2/sys/repository.h" +#include "custom_helpers.h" + +/* going TO_WORKDIR, filters are executed low to high + * going TO_ODB, filters are executed high to low + */ +#define BITFLIP_FILTER_PRIORITY -1 +#define REVERSE_FILTER_PRIORITY -2 + +#ifdef GIT_WIN32 +# define NEWLINE "\r\n" +#else +# define NEWLINE "\n" +#endif + +static char workdir_data[] = + "some simple" NEWLINE + "data" NEWLINE + "that will be" NEWLINE + "trivially" NEWLINE + "scrambled." NEWLINE; + +#define REVERSED_DATA_LEN 51 + +/* Represents the data above scrambled (bits flipped) after \r\n -> \n + * conversion, then bytewise reversed + */ +static unsigned char bitflipped_and_reversed_data[] = + { 0xf5, 0xd1, 0x9b, 0x9a, 0x93, 0x9d, 0x92, 0x9e, 0x8d, 0x9c, 0x8c, + 0xf5, 0x86, 0x93, 0x93, 0x9e, 0x96, 0x89, 0x96, 0x8d, 0x8b, 0xf5, + 0x9a, 0x9d, 0xdf, 0x93, 0x93, 0x96, 0x88, 0xdf, 0x8b, 0x9e, 0x97, + 0x8b, 0xf5, 0x9e, 0x8b, 0x9e, 0x9b, 0xf5, 0x9a, 0x93, 0x8f, 0x92, + 0x96, 0x8c, 0xdf, 0x9a, 0x92, 0x90, 0x8c }; + +#define BITFLIPPED_AND_REVERSED_DATA_LEN 51 + +static git_repository *g_repo = NULL; + +static void register_custom_filters(void); + +void test_filter_custom__initialize(void) +{ + register_custom_filters(); + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile( + "empty_standard_repo/.gitattributes", + "hero* bitflip reverse\n" + "herofile text\n" + "heroflip -reverse binary\n" + "villain erroneous\n" + "*.bin binary\n"); +} + +void test_filter_custom__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static void register_custom_filters(void) +{ + static int filters_registered = 0; + + if (!filters_registered) { + cl_git_pass(git_filter_register( + "bitflip", create_bitflip_filter(), BITFLIP_FILTER_PRIORITY)); + + cl_git_pass(git_filter_register( + "reverse", create_reverse_filter("+reverse"), + REVERSE_FILTER_PRIORITY)); + + /* re-register reverse filter with standard filter=xyz priority */ + cl_git_pass(git_filter_register( + "pre-reverse", + create_reverse_filter("+prereverse"), + GIT_FILTER_DRIVER_PRIORITY)); + + cl_git_pass(git_filter_register( + "erroneous", + create_erroneous_filter("+erroneous"), + GIT_FILTER_DRIVER_PRIORITY)); + + filters_registered = 1; + } +} + +void test_filter_custom__to_odb(void) +{ + git_filter_list *fl; + git_buf out = GIT_BUF_INIT; + const char *in; + size_t in_len; + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_ODB, 0)); + + in = workdir_data; + in_len = strlen(workdir_data); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + + cl_assert_equal_i(BITFLIPPED_AND_REVERSED_DATA_LEN, out.size); + + cl_assert_equal_i( + 0, memcmp(bitflipped_and_reversed_data, out.ptr, out.size)); + + git_filter_list_free(fl); + git_buf_dispose(&out); +} + +void test_filter_custom__to_workdir(void) +{ + git_filter_list *fl; + git_buf out = GIT_BUF_INIT; + const char *in; + size_t in_len; + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_WORKTREE, 0)); + + in = (char *)bitflipped_and_reversed_data; + in_len = BITFLIPPED_AND_REVERSED_DATA_LEN; + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, in, in_len)); + + cl_assert_equal_i(strlen(workdir_data), out.size); + + cl_assert_equal_i( + 0, memcmp(workdir_data, out.ptr, out.size)); + + git_filter_list_free(fl); + git_buf_dispose(&out); +} + +void test_filter_custom__can_register_a_custom_filter_in_the_repository(void) +{ + git_filter_list *fl; + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_WORKTREE, 0)); + /* expect: bitflip, reverse, crlf */ + cl_assert_equal_sz(3, git_filter_list_length(fl)); + git_filter_list_free(fl); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "herocorp", GIT_FILTER_TO_WORKTREE, 0)); + /* expect: bitflip, reverse - possibly crlf depending on global config */ + { + size_t flen = git_filter_list_length(fl); + cl_assert(flen == 2 || flen == 3); + } + git_filter_list_free(fl); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "hero.bin", GIT_FILTER_TO_WORKTREE, 0)); + /* expect: bitflip, reverse */ + cl_assert_equal_sz(2, git_filter_list_length(fl)); + git_filter_list_free(fl); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "heroflip", GIT_FILTER_TO_WORKTREE, 0)); + /* expect: bitflip (because of -reverse) */ + cl_assert_equal_sz(1, git_filter_list_length(fl)); + git_filter_list_free(fl); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "doesntapplytome.bin", + GIT_FILTER_TO_WORKTREE, 0)); + /* expect: none */ + cl_assert_equal_sz(0, git_filter_list_length(fl)); + git_filter_list_free(fl); +} + +void test_filter_custom__order_dependency(void) +{ + git_index *index; + git_blob *blob; + git_buf buf = { 0 }; + + /* so if ident and reverse are used together, an interesting thing + * happens - a reversed "$Id$" string is no longer going to trigger + * ident correctly. When checking out, the filters should be applied + * in order CLRF, then ident, then reverse, so ident expansion should + * work correctly. On check in, the content should be reversed, then + * ident, then CRLF filtered. Let's make sure that works... + */ + + cl_git_mkfile( + "empty_standard_repo/.gitattributes", + "hero.*.rev-ident text ident prereverse eol=lf\n"); + + cl_git_mkfile( + "empty_standard_repo/hero.1.rev-ident", + "This is a test\n$Id$\nHave fun!\n"); + + cl_git_mkfile( + "empty_standard_repo/hero.2.rev-ident", + "Another test\n$dI$\nCrazy!\n"); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "hero.1.rev-ident")); + cl_git_pass(git_index_add_bypath(index, "hero.2.rev-ident")); + cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "Filter chains\n"); + git_index_free(index); + + cl_git_pass(git_blob_lookup(&blob, g_repo, + & git_index_get_bypath(index, "hero.1.rev-ident", 0)->id)); + cl_assert_equal_s( + "\n!nuf evaH\n$dI$\ntset a si sihT", git_blob_rawcontent(blob)); + cl_git_pass(git_blob_filter(&buf, blob, "hero.1.rev-ident", NULL)); + /* no expansion because id was reversed at checkin and now at ident + * time, reverse is not applied yet */ + cl_assert_equal_s( + "This is a test\n$Id$\nHave fun!\n", buf.ptr); + git_blob_free(blob); + + cl_git_pass(git_blob_lookup(&blob, g_repo, + & git_index_get_bypath(index, "hero.2.rev-ident", 0)->id)); + cl_assert_equal_s( + "\n!yzarC\n$Id$\ntset rehtonA", git_blob_rawcontent(blob)); + cl_git_pass(git_blob_filter(&buf, blob, "hero.2.rev-ident", NULL)); + /* expansion because reverse was applied at checkin and at ident time, + * reverse is not applied yet */ + cl_assert_equal_s( + "Another test\n$ 59001fe193103b1016b27027c0c827d036fd0ac8 :dI$\nCrazy!\n", buf.ptr); + cl_assert_equal_i(0, git_oid_strcmp( + git_blob_id(blob), "8ca0df630d728c0c72072b6101b301391ef10095")); + git_blob_free(blob); + + git_buf_dispose(&buf); +} + +void test_filter_custom__filter_registry_failure_cases(void) +{ + git_filter fake = { GIT_FILTER_VERSION, 0 }; + + cl_assert_equal_i(GIT_EEXISTS, git_filter_register("bitflip", &fake, 0)); + + cl_git_fail(git_filter_unregister(GIT_FILTER_CRLF)); + cl_git_fail(git_filter_unregister(GIT_FILTER_IDENT)); + cl_assert_equal_i(GIT_ENOTFOUND, git_filter_unregister("not-a-filter")); +} + +void test_filter_custom__erroneous_filter_fails(void) +{ + git_filter_list *filters; + git_buf out = GIT_BUF_INIT; + const char *in; + size_t in_len; + + cl_git_pass(git_filter_list_load( + &filters, g_repo, NULL, "villain", GIT_FILTER_TO_WORKTREE, 0)); + + in = workdir_data; + in_len = strlen(workdir_data); + + cl_git_fail(git_filter_list_apply_to_buffer(&out, filters, in, in_len)); + + git_filter_list_free(filters); + git_buf_dispose(&out); +} diff --git a/tests/libgit2/filter/custom_helpers.c b/tests/libgit2/filter/custom_helpers.c new file mode 100644 index 000000000..95a9f978e --- /dev/null +++ b/tests/libgit2/filter/custom_helpers.c @@ -0,0 +1,163 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "filter.h" +#include "git2/sys/filter.h" +#include "custom_helpers.h" + +#define VERY_SECURE_ENCRYPTION(b) ((b) ^ 0xff) + +int bitflip_filter_apply( + git_filter *self, + void **payload, + git_str *to, + const git_str *from, + const git_filter_source *source) +{ + const unsigned char *src = (const unsigned char *)from->ptr; + unsigned char *dst; + size_t i; + + GIT_UNUSED(self); GIT_UNUSED(payload); + + /* verify that attribute path match worked as expected */ + cl_assert_equal_i( + 0, git__strncmp("hero", git_filter_source_path(source), 4)); + + if (!from->size) + return 0; + + cl_git_pass(git_str_grow(to, from->size)); + + dst = (unsigned char *)to->ptr; + + for (i = 0; i < from->size; i++) + dst[i] = VERY_SECURE_ENCRYPTION(src[i]); + + to->size = from->size; + + return 0; +} + +static int bitflip_filter_stream( + git_writestream **out, + git_filter *self, + void **payload, + const git_filter_source *src, + git_writestream *next) +{ + return git_filter_buffered_stream_new(out, + self, bitflip_filter_apply, NULL, payload, src, next); +} + +static void bitflip_filter_free(git_filter *f) +{ + git__free(f); +} + +git_filter *create_bitflip_filter(void) +{ + git_filter *filter = git__calloc(1, sizeof(git_filter)); + cl_assert(filter); + + filter->version = GIT_FILTER_VERSION; + filter->attributes = "+bitflip"; + filter->shutdown = bitflip_filter_free; + filter->stream = bitflip_filter_stream; + + return filter; +} + + +int reverse_filter_apply( + git_filter *self, + void **payload, + git_str *to, + const git_str *from, + const git_filter_source *source) +{ + const unsigned char *src = (const unsigned char *)from->ptr; + const unsigned char *end = src + from->size; + unsigned char *dst; + + GIT_UNUSED(self); GIT_UNUSED(payload); GIT_UNUSED(source); + + /* verify that attribute path match worked as expected */ + cl_assert_equal_i( + 0, git__strncmp("hero", git_filter_source_path(source), 4)); + + if (!from->size) + return 0; + + cl_git_pass(git_str_grow(to, from->size)); + + dst = (unsigned char *)to->ptr + from->size - 1; + + while (src < end) + *dst-- = *src++; + + to->size = from->size; + + return 0; +} + +static int reverse_filter_stream( + git_writestream **out, + git_filter *self, + void **payload, + const git_filter_source *src, + git_writestream *next) +{ + return git_filter_buffered_stream_new(out, + self, reverse_filter_apply, NULL, payload, src, next); +} + +static void reverse_filter_free(git_filter *f) +{ + git__free(f); +} + +git_filter *create_reverse_filter(const char *attrs) +{ + git_filter *filter = git__calloc(1, sizeof(git_filter)); + cl_assert(filter); + + filter->version = GIT_FILTER_VERSION; + filter->attributes = attrs; + filter->shutdown = reverse_filter_free; + filter->stream = reverse_filter_stream; + + return filter; +} + +static int erroneous_filter_stream( + git_writestream **out, + git_filter *self, + void **payload, + const git_filter_source *src, + git_writestream *next) +{ + GIT_UNUSED(out); + GIT_UNUSED(self); + GIT_UNUSED(payload); + GIT_UNUSED(src); + GIT_UNUSED(next); + return -1; +} + +static void erroneous_filter_free(git_filter *f) +{ + git__free(f); +} + +git_filter *create_erroneous_filter(const char *attrs) +{ + git_filter *filter = git__calloc(1, sizeof(git_filter)); + cl_assert(filter); + + filter->version = GIT_FILTER_VERSION; + filter->attributes = attrs; + filter->stream = erroneous_filter_stream; + filter->shutdown = erroneous_filter_free; + + return filter; +} diff --git a/tests/libgit2/filter/custom_helpers.h b/tests/libgit2/filter/custom_helpers.h new file mode 100644 index 000000000..0936c581b --- /dev/null +++ b/tests/libgit2/filter/custom_helpers.h @@ -0,0 +1,19 @@ +#include "git2/sys/filter.h" + +extern git_filter *create_bitflip_filter(void); +extern git_filter *create_reverse_filter(const char *attr); +extern git_filter *create_erroneous_filter(const char *attr); + +extern int bitflip_filter_apply( + git_filter *self, + void **payload, + git_str *to, + const git_str *from, + const git_filter_source *source); + +extern int reverse_filter_apply( + git_filter *self, + void **payload, + git_str *to, + const git_str *from, + const git_filter_source *source); diff --git a/tests/libgit2/filter/file.c b/tests/libgit2/filter/file.c new file mode 100644 index 000000000..14b33bab9 --- /dev/null +++ b/tests/libgit2/filter/file.c @@ -0,0 +1,98 @@ +#include "clar_libgit2.h" +#include "git2/sys/filter.h" +#include "crlf.h" + +static git_repository *g_repo = NULL; + +void test_filter_file__initialize(void) +{ + git_reference *head_ref; + git_commit *head; + + g_repo = cl_git_sandbox_init("crlf"); + + cl_git_mkfile("crlf/.gitattributes", + "*.txt text\n*.bin binary\n*.crlf text eol=crlf\n*.lf text eol=lf\n"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_git_pass(git_repository_head(&head_ref, g_repo)); + cl_git_pass(git_reference_peel((git_object **)&head, head_ref, GIT_OBJECT_COMMIT)); + cl_git_pass(git_reset(g_repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_commit_free(head); + git_reference_free(head_ref); +} + +void test_filter_file__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_filter_file__apply(void) +{ + git_filter_list *fl; + git_filter *crlf; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_filter_list_new( + &fl, g_repo, GIT_FILTER_TO_ODB, 0)); + + crlf = git_filter_lookup(GIT_FILTER_CRLF); + cl_assert(crlf != NULL); + + cl_git_pass(git_filter_list_push(fl, crlf, NULL)); + + cl_git_pass(git_filter_list_apply_to_file(&buf, fl, g_repo, "all-crlf")); + cl_assert_equal_s("crlf\ncrlf\ncrlf\ncrlf\n", buf.ptr); + + git_buf_dispose(&buf); + git_filter_list_free(fl); +} + +struct buf_writestream { + git_writestream base; + git_str buf; +}; + +static int buf_writestream_write(git_writestream *s, const char *buf, size_t len) +{ + struct buf_writestream *stream = (struct buf_writestream *)s; + return git_str_put(&stream->buf, buf, len); +} + +static int buf_writestream_close(git_writestream *s) +{ + GIT_UNUSED(s); + return 0; +} + +static void buf_writestream_free(git_writestream *s) +{ + struct buf_writestream *stream = (struct buf_writestream *)s; + git_str_dispose(&stream->buf); +} + +void test_filter_file__apply_stream(void) +{ + git_filter_list *fl; + git_filter *crlf; + struct buf_writestream write_target = { { + buf_writestream_write, + buf_writestream_close, + buf_writestream_free } }; + + cl_git_pass(git_filter_list_new( + &fl, g_repo, GIT_FILTER_TO_ODB, 0)); + + crlf = git_filter_lookup(GIT_FILTER_CRLF); + cl_assert(crlf != NULL); + + cl_git_pass(git_filter_list_push(fl, crlf, NULL)); + + cl_git_pass(git_filter_list_stream_file(fl, g_repo, "all-crlf", &write_target.base)); + cl_assert_equal_s("crlf\ncrlf\ncrlf\ncrlf\n", write_target.buf.ptr); + + git_filter_list_free(fl); + write_target.base.free(&write_target.base); +} diff --git a/tests/libgit2/filter/ident.c b/tests/libgit2/filter/ident.c new file mode 100644 index 000000000..ed2e4677f --- /dev/null +++ b/tests/libgit2/filter/ident.c @@ -0,0 +1,133 @@ +#include "clar_libgit2.h" +#include "git2/sys/filter.h" + +static git_repository *g_repo = NULL; + +void test_filter_ident__initialize(void) +{ + g_repo = cl_git_sandbox_init("crlf"); +} + +void test_filter_ident__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void add_blob_and_filter( + const char *data, + git_filter_list *fl, + const char *expected) +{ + git_oid id; + git_blob *blob; + git_buf out = { 0 }; + + cl_git_mkfile("crlf/identtest", data); + cl_git_pass(git_blob_create_from_workdir(&id, g_repo, "identtest")); + cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); + + cl_git_pass(git_filter_list_apply_to_blob(&out, fl, blob)); + + cl_assert_equal_s(expected, out.ptr); + + git_blob_free(blob); + git_buf_dispose(&out); +} + +void test_filter_ident__to_worktree(void) +{ + git_filter_list *fl; + git_filter *ident; + + cl_git_pass(git_filter_list_new( + &fl, g_repo, GIT_FILTER_TO_WORKTREE, 0)); + + ident = git_filter_lookup(GIT_FILTER_IDENT); + cl_assert(ident != NULL); + + cl_git_pass(git_filter_list_push(fl, ident, NULL)); + + add_blob_and_filter( + "Hello\n$Id$\nFun stuff\n", fl, + "Hello\n$Id: b69e2387aafcaf73c4de5b9ab59abe27fdadee30 $\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id: Junky$\nFun stuff\n", fl, + "Hello\n$Id: 45cd107a7102911cb2a7df08404674327fa050b9 $\nFun stuff\n"); + add_blob_and_filter( + "$Id$\nAt the start\n", fl, + "$Id: b13415c767abc196fb95bd17070e8c1113e32160 $\nAt the start\n"); + add_blob_and_filter( + "At the end\n$Id$", fl, + "At the end\n$Id: 1344925c6bc65b34c5a7b50f86bf688e48e9a272 $"); + add_blob_and_filter( + "$Id$", fl, + "$Id: b3f5ebfb5843bc43ceecff6d4f26bb37c615beb1 $"); + add_blob_and_filter( + "$Id: Some sort of junk goes here$", fl, + "$Id: ab2dd3853c7c9a4bff55aca2bea077a73c32ac06 $"); + + add_blob_and_filter("$Id: ", fl, "$Id: "); + add_blob_and_filter("$Id", fl, "$Id"); + add_blob_and_filter("$I", fl, "$I"); + add_blob_and_filter("Id$", fl, "Id$"); + + git_filter_list_free(fl); +} + +void test_filter_ident__to_odb(void) +{ + git_filter_list *fl; + git_filter *ident; + + cl_git_pass(git_filter_list_new( + &fl, g_repo, GIT_FILTER_TO_ODB, 0)); + + ident = git_filter_lookup(GIT_FILTER_IDENT); + cl_assert(ident != NULL); + + cl_git_pass(git_filter_list_push(fl, ident, NULL)); + + add_blob_and_filter( + "Hello\n$Id$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id: b69e2387aafcaf73c4de5b9ab59abe27fdadee30$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id: Any junk you may have left here$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id:$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + add_blob_and_filter( + "Hello\n$Id:x$\nFun stuff\n", + fl, "Hello\n$Id$\nFun stuff\n"); + + add_blob_and_filter( + "$Id$\nAt the start\n", fl, "$Id$\nAt the start\n"); + add_blob_and_filter( + "$Id: lots of random text that should be removed from here$\nAt the start\n", fl, "$Id$\nAt the start\n"); + add_blob_and_filter( + "$Id: lots of random text that should not be removed without a terminator\nAt the start\n", fl, "$Id: lots of random text that should not be removed without a terminator\nAt the start\n"); + + add_blob_and_filter( + "At the end\n$Id$", fl, "At the end\n$Id$"); + add_blob_and_filter( + "At the end\n$Id:$", fl, "At the end\n$Id$"); + add_blob_and_filter( + "At the end\n$Id:asdfasdf$", fl, "At the end\n$Id$"); + add_blob_and_filter( + "At the end\n$Id", fl, "At the end\n$Id"); + add_blob_and_filter( + "At the end\n$IddI", fl, "At the end\n$IddI"); + + add_blob_and_filter("$Id$", fl, "$Id$"); + add_blob_and_filter("$Id: any$", fl, "$Id$"); + add_blob_and_filter("$Id: any long stuff goes here you see$", fl, "$Id$"); + add_blob_and_filter("$Id: ", fl, "$Id: "); + add_blob_and_filter("$Id", fl, "$Id"); + add_blob_and_filter("$I", fl, "$I"); + add_blob_and_filter("Id$", fl, "Id$"); + + git_filter_list_free(fl); +} diff --git a/tests/libgit2/filter/query.c b/tests/libgit2/filter/query.c new file mode 100644 index 000000000..429c10443 --- /dev/null +++ b/tests/libgit2/filter/query.c @@ -0,0 +1,90 @@ +#include "clar_libgit2.h" +#include "git2/sys/filter.h" +#include "crlf.h" + +static git_repository *g_repo = NULL; + +void test_filter_query__initialize(void) +{ + g_repo = cl_git_sandbox_init("crlf"); + + cl_git_mkfile("crlf/.gitattributes", + "*.txt text\n" + "*.bin binary\n" + "*.crlf text eol=crlf\n" + "*.lf text eol=lf\n" + "*.binident binary ident\n" + "*.ident text ident\n" + "*.identcrlf ident text eol=crlf\n" + "*.identlf ident text eol=lf\n" + "*.custom custom ident text\n"); +} + +void test_filter_query__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int filter_for(const char *filename, const char *filter) +{ + git_filter_list *fl; + int filtered; + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, filename, GIT_FILTER_TO_WORKTREE, 0)); + filtered = git_filter_list_contains(fl, filter); + git_filter_list_free(fl); + + return filtered; +} + +void test_filter_query__filters(void) +{ + cl_assert_equal_i(1, filter_for("text.txt", "crlf")); + cl_assert_equal_i(0, filter_for("binary.bin", "crlf")); + + cl_assert_equal_i(1, filter_for("foo.lf", "crlf")); + cl_assert_equal_i(0, filter_for("foo.lf", "ident")); + + cl_assert_equal_i(1, filter_for("id.ident", "crlf")); + cl_assert_equal_i(1, filter_for("id.ident", "ident")); + + cl_assert_equal_i(0, filter_for("id.binident", "crlf")); + cl_assert_equal_i(1, filter_for("id.binident", "ident")); +} + +void test_filter_query__autocrlf_true_implies_crlf(void) +{ + cl_repo_set_bool(g_repo, "core.autocrlf", true); + cl_assert_equal_i(1, filter_for("not_in_gitattributes", "crlf")); + cl_assert_equal_i(1, filter_for("foo.txt", "crlf")); + cl_assert_equal_i(0, filter_for("foo.bin", "crlf")); + cl_assert_equal_i(1, filter_for("foo.lf", "crlf")); + + cl_repo_set_bool(g_repo, "core.autocrlf", false); + cl_assert_equal_i(0, filter_for("not_in_gitattributes", "crlf")); + cl_assert_equal_i(1, filter_for("foo.txt", "crlf")); + cl_assert_equal_i(0, filter_for("foo.bin", "crlf")); + cl_assert_equal_i(1, filter_for("foo.lf", "crlf")); +} + +void test_filter_query__unknown(void) +{ + cl_assert_equal_i(1, filter_for("foo.custom", "crlf")); + cl_assert_equal_i(1, filter_for("foo.custom", "ident")); + cl_assert_equal_i(0, filter_for("foo.custom", "custom")); +} + +void test_filter_query__custom(void) +{ + git_filter custom = { GIT_FILTER_VERSION }; + + cl_git_pass(git_filter_register( + "custom", &custom, 42)); + + cl_assert_equal_i(1, filter_for("foo.custom", "crlf")); + cl_assert_equal_i(1, filter_for("foo.custom", "ident")); + cl_assert_equal_i(1, filter_for("foo.custom", "custom")); + + git_filter_unregister("custom"); +} diff --git a/tests/libgit2/filter/stream.c b/tests/libgit2/filter/stream.c new file mode 100644 index 000000000..0f85f9c47 --- /dev/null +++ b/tests/libgit2/filter/stream.c @@ -0,0 +1,215 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "blob.h" +#include "filter.h" +#include "git2/sys/filter.h" +#include "git2/sys/repository.h" + +static git_repository *g_repo = NULL; + +static git_filter *create_compress_filter(void); +static git_filter *compress_filter; + +void test_filter_stream__initialize(void) +{ + compress_filter = create_compress_filter(); + + cl_git_pass(git_filter_register("compress", compress_filter, 50)); + g_repo = cl_git_sandbox_init("empty_standard_repo"); +} + +void test_filter_stream__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; + + git_filter_unregister("compress"); + git__free(compress_filter); +} + +#define CHUNKSIZE 10240 + +struct compress_stream { + git_writestream parent; + git_writestream *next; + git_filter_mode_t mode; + char current; + size_t current_chunk; +}; + +static int compress_stream_write__deflated(struct compress_stream *stream, const char *buffer, size_t len) +{ + size_t idx = 0; + + while (len > 0) { + size_t chunkremain, chunksize; + + if (stream->current_chunk == 0) + stream->current = buffer[idx]; + + chunkremain = CHUNKSIZE - stream->current_chunk; + chunksize = min(chunkremain, len); + + stream->current_chunk += chunksize; + len -= chunksize; + idx += chunksize; + + if (stream->current_chunk == CHUNKSIZE) { + cl_git_pass(stream->next->write(stream->next, &stream->current, 1)); + stream->current_chunk = 0; + } + } + + return 0; +} + +static int compress_stream_write__inflated(struct compress_stream *stream, const char *buffer, size_t len) +{ + char inflated[CHUNKSIZE]; + size_t i, j; + + for (i = 0; i < len; i++) { + for (j = 0; j < CHUNKSIZE; j++) + inflated[j] = buffer[i]; + + cl_git_pass(stream->next->write(stream->next, inflated, CHUNKSIZE)); + } + + return 0; +} + +static int compress_stream_write(git_writestream *s, const char *buffer, size_t len) +{ + struct compress_stream *stream = (struct compress_stream *)s; + + return (stream->mode == GIT_FILTER_TO_ODB) ? + compress_stream_write__deflated(stream, buffer, len) : + compress_stream_write__inflated(stream, buffer, len); +} + +static int compress_stream_close(git_writestream *s) +{ + struct compress_stream *stream = (struct compress_stream *)s; + cl_assert_equal_i(0, stream->current_chunk); + stream->next->close(stream->next); + return 0; +} + +static void compress_stream_free(git_writestream *stream) +{ + git__free(stream); +} + +static int compress_filter_stream_init( + git_writestream **out, + git_filter *self, + void **payload, + const git_filter_source *src, + git_writestream *next) +{ + struct compress_stream *stream = git__calloc(1, sizeof(struct compress_stream)); + cl_assert(stream); + + GIT_UNUSED(self); + GIT_UNUSED(payload); + + stream->parent.write = compress_stream_write; + stream->parent.close = compress_stream_close; + stream->parent.free = compress_stream_free; + stream->next = next; + stream->mode = git_filter_source_mode(src); + + *out = (git_writestream *)stream; + return 0; +} + +git_filter *create_compress_filter(void) +{ + git_filter *filter = git__calloc(1, sizeof(git_filter)); + cl_assert(filter); + + filter->version = GIT_FILTER_VERSION; + filter->attributes = "+compress"; + filter->stream = compress_filter_stream_init; + + return filter; +} + +static void writefile(const char *filename, size_t numchunks) +{ + git_str path = GIT_STR_INIT; + char buf[CHUNKSIZE]; + size_t i = 0, j = 0; + int fd; + + cl_git_pass(git_str_joinpath(&path, "empty_standard_repo", filename)); + + fd = p_open(path.ptr, O_RDWR|O_CREAT, 0666); + cl_assert(fd >= 0); + + for (i = 0; i < numchunks; i++) { + for (j = 0; j < CHUNKSIZE; j++) { + buf[j] = i % 256; + } + + cl_git_pass(p_write(fd, buf, CHUNKSIZE)); + } + p_close(fd); + + git_str_dispose(&path); +} + +static void test_stream(size_t numchunks) +{ + git_index *index; + const git_index_entry *entry; + git_blob *blob; + struct stat st; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_mkfile( + "empty_standard_repo/.gitattributes", + "* compress\n"); + + /* write a file to disk */ + writefile("streamed_file", numchunks); + + /* place it in the index */ + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "streamed_file")); + cl_git_pass(git_index_write(index)); + + /* ensure it was appropriately compressed */ + cl_assert(entry = git_index_get_bypath(index, "streamed_file", 0)); + + cl_git_pass(git_blob_lookup(&blob, g_repo, &entry->id)); + cl_assert_equal_i(numchunks, git_blob_rawsize(blob)); + + /* check the file back out */ + cl_must_pass(p_unlink("empty_standard_repo/streamed_file")); + cl_git_pass(git_checkout_index(g_repo, index, &checkout_opts)); + + /* ensure it was decompressed */ + cl_must_pass(p_stat("empty_standard_repo/streamed_file", &st)); + cl_assert_equal_sz((numchunks * CHUNKSIZE), st.st_size); + + git_index_free(index); + git_blob_free(blob); +} + +/* write a 50KB file through the "compression" stream */ +void test_filter_stream__smallfile(void) +{ + test_stream(5); +} + +/* optionally write a 500 MB file through the compression stream */ +void test_filter_stream__bigfile(void) +{ + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE")) + cl_skip(); + + test_stream(51200); +} diff --git a/tests/libgit2/filter/systemattrs.c b/tests/libgit2/filter/systemattrs.c new file mode 100644 index 000000000..b687b4b44 --- /dev/null +++ b/tests/libgit2/filter/systemattrs.c @@ -0,0 +1,85 @@ +#include "clar_libgit2.h" +#include "crlf.h" +#include "path.h" + +static git_repository *g_repo = NULL; +static git_str system_attr_path = GIT_STR_INIT; + +void test_filter_systemattrs__initialize(void) +{ + git_buf system_path = GIT_BUF_INIT; + + g_repo = cl_git_sandbox_init("crlf"); + cl_must_pass(p_unlink("crlf/.gitattributes")); + + cl_git_pass(git_libgit2_opts( + GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, &system_path)); + cl_git_pass(git_str_joinpath(&system_attr_path, + system_path.ptr, "gitattributes")); + + cl_git_mkfile(system_attr_path.ptr, + "*.txt text\n" + "*.bin binary\n" + "*.crlf text eol=crlf\n" + "*.lf text eol=lf\n"); + + git_buf_dispose(&system_path); +} + +void test_filter_systemattrs__cleanup(void) +{ + cl_must_pass(p_unlink(system_attr_path.ptr)); + git_str_dispose(&system_attr_path); + + cl_git_sandbox_cleanup(); +} + +void test_filter_systemattrs__reads_system_attributes(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "799770d")); /* all-lf */ + + cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", NULL)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", NULL)); + cl_assert_equal_s(ALL_LF_TEXT_AS_CRLF, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", NULL)); + cl_assert_equal_s(ALL_LF_TEXT_AS_LF, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} + +void test_filter_systemattrs__disables_system_attributes(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; + + opts.flags |= GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "799770d")); /* all-lf */ + + cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + /* No attributes mean these are all treated literally */ + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} diff --git a/tests/libgit2/filter/wildcard.c b/tests/libgit2/filter/wildcard.c new file mode 100644 index 000000000..ee61a8dba --- /dev/null +++ b/tests/libgit2/filter/wildcard.c @@ -0,0 +1,188 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "blob.h" +#include "filter.h" +#include "git2/sys/filter.h" +#include "git2/sys/repository.h" +#include "custom_helpers.h" + +static git_repository *g_repo = NULL; + +static git_filter *create_wildcard_filter(void); + +#define DATA_LEN 32 + +static unsigned char input[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, +}; + +static unsigned char reversed[] = { + 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, +}; + +static unsigned char flipped[] = { + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, + 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, +}; + +void test_filter_wildcard__initialize(void) +{ + cl_git_pass(git_filter_register( + "wildcard", create_wildcard_filter(), GIT_FILTER_DRIVER_PRIORITY)); + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_rewritefile( + "empty_standard_repo/.gitattributes", + "* binary\n" + "hero-flip-* filter=wcflip\n" + "hero-reverse-* filter=wcreverse\n" + "none-* filter=unregistered\n"); +} + +void test_filter_wildcard__cleanup(void) +{ + cl_git_pass(git_filter_unregister("wildcard")); + + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static int wildcard_filter_check( + git_filter *self, + void **payload, + const git_filter_source *src, + const char **attr_values) +{ + GIT_UNUSED(self); + GIT_UNUSED(src); + + if (strcmp(attr_values[0], "wcflip") == 0 || + strcmp(attr_values[0], "wcreverse") == 0) { + *payload = git__strdup(attr_values[0]); + GIT_ERROR_CHECK_ALLOC(*payload); + return 0; + } + + return GIT_PASSTHROUGH; +} + +static int wildcard_filter_apply( + git_filter *self, + void **payload, + git_str *to, + const git_str *from, + const git_filter_source *source) +{ + const char *filtername = *payload; + + if (filtername && strcmp(filtername, "wcflip") == 0) + return bitflip_filter_apply(self, payload, to, from, source); + else if (filtername && strcmp(filtername, "wcreverse") == 0) + return reverse_filter_apply(self, payload, to, from, source); + + cl_fail("Unexpected attribute"); + return GIT_PASSTHROUGH; +} + +static int wildcard_filter_stream( + git_writestream **out, + git_filter *self, + void **payload, + const git_filter_source *src, + git_writestream *next) +{ + return git_filter_buffered_stream_new(out, + self, wildcard_filter_apply, NULL, payload, src, next); +} + +static void wildcard_filter_cleanup(git_filter *self, void *payload) +{ + GIT_UNUSED(self); + git__free(payload); +} + +static void wildcard_filter_free(git_filter *f) +{ + git__free(f); +} + +static git_filter *create_wildcard_filter(void) +{ + git_filter *filter = git__calloc(1, sizeof(git_filter)); + cl_assert(filter); + + filter->version = GIT_FILTER_VERSION; + filter->attributes = "filter=*"; + filter->check = wildcard_filter_check; + filter->stream = wildcard_filter_stream; + filter->cleanup = wildcard_filter_cleanup; + filter->shutdown = wildcard_filter_free; + + return filter; +} + +void test_filter_wildcard__reverse(void) +{ + git_filter_list *fl; + git_buf out = GIT_BUF_INIT; + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "hero-reverse-foo", GIT_FILTER_TO_ODB, 0)); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, (char *)input, DATA_LEN)); + + cl_assert_equal_i(DATA_LEN, out.size); + + cl_assert_equal_i( + 0, memcmp(reversed, out.ptr, out.size)); + + git_filter_list_free(fl); + git_buf_dispose(&out); +} + +void test_filter_wildcard__flip(void) +{ + git_filter_list *fl; + git_buf out = GIT_BUF_INIT; + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "hero-flip-foo", GIT_FILTER_TO_ODB, 0)); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, (char *)input, DATA_LEN)); + + cl_assert_equal_i(DATA_LEN, out.size); + + cl_assert_equal_i( + 0, memcmp(flipped, out.ptr, out.size)); + + git_filter_list_free(fl); + git_buf_dispose(&out); +} + +void test_filter_wildcard__none(void) +{ + git_filter_list *fl; + git_buf out = GIT_BUF_INIT; + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "none-foo", GIT_FILTER_TO_ODB, 0)); + + cl_git_pass(git_filter_list_apply_to_buffer(&out, fl, (char *)input, DATA_LEN)); + + cl_assert_equal_i(DATA_LEN, out.size); + + cl_assert_equal_i( + 0, memcmp(input, out.ptr, out.size)); + + git_filter_list_free(fl); + git_buf_dispose(&out); +} diff --git a/tests/libgit2/generate.py b/tests/libgit2/generate.py new file mode 100644 index 000000000..d2cdb684a --- /dev/null +++ b/tests/libgit2/generate.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python +# +# Copyright (c) Vicent Marti. All rights reserved. +# +# This file is part of clar, distributed under the ISC license. +# For full terms see the included COPYING file. +# + +from __future__ import with_statement +from string import Template +import re, fnmatch, os, sys, codecs, pickle, io + +class Module(object): + class Template(object): + def __init__(self, module): + self.module = module + + def _render_callback(self, cb): + if not cb: + return ' { NULL, NULL }' + return ' { "%s", &%s }' % (cb['short_name'], cb['symbol']) + + class DeclarationTemplate(Template): + def render(self): + out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n" + + for initializer in self.module.initializers: + out += "extern %s;\n" % initializer['declaration'] + + if self.module.cleanup: + out += "extern %s;\n" % self.module.cleanup['declaration'] + + return out + + class CallbacksTemplate(Template): + def render(self): + out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name + out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks) + out += "\n};\n" + return out + + class InfoTemplate(Template): + def render(self): + templates = [] + + initializers = self.module.initializers + if len(initializers) == 0: + initializers = [ None ] + + for initializer in initializers: + name = self.module.clean_name() + if initializer and initializer['short_name'].startswith('initialize_'): + variant = initializer['short_name'][len('initialize_'):] + name += " (%s)" % variant.replace('_', ' ') + + template = Template( + r""" + { + "${clean_name}", + ${initialize}, + ${cleanup}, + ${cb_ptr}, ${cb_count}, ${enabled} + }""" + ).substitute( + clean_name = name, + initialize = self._render_callback(initializer), + cleanup = self._render_callback(self.module.cleanup), + cb_ptr = "_clar_cb_%s" % self.module.name, + cb_count = len(self.module.callbacks), + enabled = int(self.module.enabled) + ) + templates.append(template) + + return ','.join(templates) + + def __init__(self, name): + self.name = name + + self.mtime = 0 + self.enabled = True + self.modified = False + + def clean_name(self): + return self.name.replace("_", "::") + + def _skip_comments(self, text): + SKIP_COMMENTS_REGEX = re.compile( + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', + re.DOTALL | re.MULTILINE) + + def _replacer(match): + s = match.group(0) + return "" if s.startswith('/') else s + + return re.sub(SKIP_COMMENTS_REGEX, _replacer, text) + + def parse(self, contents): + TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{" + + contents = self._skip_comments(contents) + regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE) + + self.callbacks = [] + self.initializers = [] + self.cleanup = None + + for (declaration, symbol, short_name) in regex.findall(contents): + data = { + "short_name" : short_name, + "declaration" : declaration, + "symbol" : symbol + } + + if short_name.startswith('initialize'): + self.initializers.append(data) + elif short_name == 'cleanup': + self.cleanup = data + else: + self.callbacks.append(data) + + return self.callbacks != [] + + def refresh(self, path): + self.modified = False + + try: + st = os.stat(path) + + # Not modified + if st.st_mtime == self.mtime: + return True + + self.modified = True + self.mtime = st.st_mtime + + with codecs.open(path, encoding='utf-8') as fp: + raw_content = fp.read() + + except IOError: + return False + + return self.parse(raw_content) + +class TestSuite(object): + + def __init__(self, path, output): + self.path = path + self.output = output + + def maybe_generate(self, path): + if not os.path.isfile(path): + return True + + if any(module.modified for module in self.modules.values()): + return True + + return False + + def find_modules(self): + modules = [] + for root, _, files in os.walk(self.path): + module_root = root[len(self.path):] + module_root = [c for c in module_root.split(os.sep) if c] + + tests_in_module = fnmatch.filter(files, "*.c") + + for test_file in tests_in_module: + full_path = os.path.join(root, test_file) + module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_") + + modules.append((full_path, module_name)) + + return modules + + def load_cache(self): + path = os.path.join(self.output, '.clarcache') + cache = {} + + try: + fp = open(path, 'rb') + cache = pickle.load(fp) + fp.close() + except (IOError, ValueError): + pass + + return cache + + def save_cache(self): + path = os.path.join(self.output, '.clarcache') + with open(path, 'wb') as cache: + pickle.dump(self.modules, cache) + + def load(self, force = False): + module_data = self.find_modules() + self.modules = {} if force else self.load_cache() + + for path, name in module_data: + if name not in self.modules: + self.modules[name] = Module(name) + + if not self.modules[name].refresh(path): + del self.modules[name] + + def disable(self, excluded): + for exclude in excluded: + for module in self.modules.values(): + name = module.clean_name() + if name.startswith(exclude): + module.enabled = False + module.modified = True + + def suite_count(self): + return sum(max(1, len(m.initializers)) for m in self.modules.values()) + + def callback_count(self): + return sum(len(module.callbacks) for module in self.modules.values()) + + def write(self): + wrote_suite = self.write_suite() + wrote_header = self.write_header() + + if wrote_suite or wrote_header: + self.save_cache() + return True + + return False + + def write_output(self, fn, data): + if not self.maybe_generate(fn): + return False + + current = None + + try: + with open(fn, 'r') as input: + current = input.read() + except OSError: + pass + except IOError: + pass + + if current == data: + return False + + with open(fn, 'w') as output: + output.write(data) + + return True + + def write_suite(self): + suite_fn = os.path.join(self.output, 'clar.suite') + + with io.StringIO() as suite_file: + modules = sorted(self.modules.values(), key=lambda module: module.name) + + for module in modules: + t = Module.DeclarationTemplate(module) + suite_file.write(t.render()) + + for module in modules: + t = Module.CallbacksTemplate(module) + suite_file.write(t.render()) + + suites = "static struct clar_suite _clar_suites[] = {" + ','.join( + Module.InfoTemplate(module).render() for module in modules + ) + "\n};\n" + + suite_file.write(suites) + + suite_file.write(u"static const size_t _clar_suite_count = %d;\n" % self.suite_count()) + suite_file.write(u"static const size_t _clar_callback_count = %d;\n" % self.callback_count()) + + return self.write_output(suite_fn, suite_file.getvalue()) + + return False + + def write_header(self): + header_fn = os.path.join(self.output, 'clar_suite.h') + + with io.StringIO() as header_file: + header_file.write(u"#ifndef _____clar_suite_h_____\n") + header_file.write(u"#define _____clar_suite_h_____\n") + + modules = sorted(self.modules.values(), key=lambda module: module.name) + + for module in modules: + t = Module.DeclarationTemplate(module) + header_file.write(t.render()) + + header_file.write(u"#endif\n") + + return self.write_output(header_fn, header_file.getvalue()) + + return False + +if __name__ == '__main__': + from optparse import OptionParser + + parser = OptionParser() + parser.add_option('-f', '--force', action="store_true", dest='force', default=False) + parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[]) + parser.add_option('-o', '--output', dest='output') + + options, args = parser.parse_args() + if len(args) > 1: + print("More than one path given") + sys.exit(1) + + path = args.pop() if args else '.' + output = options.output or path + suite = TestSuite(path, output) + suite.load(options.force) + suite.disable(options.excluded) + if suite.write(): + print("Written `clar.suite`, `clar_suite.h` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count())) + diff --git a/tests/libgit2/graph/ahead_behind.c b/tests/libgit2/graph/ahead_behind.c new file mode 100644 index 000000000..77d7768af --- /dev/null +++ b/tests/libgit2/graph/ahead_behind.c @@ -0,0 +1,58 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; +static git_commit *commit; +static size_t ahead; +static size_t behind; + +void test_graph_ahead_behind__initialize(void) +{ + git_oid oid; + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + + cl_git_pass(git_oid_fromstr(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); +} + +void test_graph_ahead_behind__cleanup(void) +{ + git_commit_free(commit); + commit = NULL; + + git_repository_free(_repo); + _repo = NULL; +} + +void test_graph_ahead_behind__returns_correct_result(void) +{ + git_oid oid; + git_oid oid2; + git_commit *other; + + cl_git_pass(git_oid_fromstr(&oid, "e90810b8df3e80c413d903f631643c716887138d")); + cl_git_pass(git_oid_fromstr(&oid2, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &oid, &oid2)); + cl_assert_equal_sz(2, ahead); + cl_assert_equal_sz(6, behind); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(commit), git_commit_id(commit))); + cl_assert_equal_sz(ahead, behind); + cl_git_pass(git_commit_nth_gen_ancestor(&other, commit, 1)); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(commit), git_commit_id(other))); + cl_assert_equal_sz(ahead, behind + 2); + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(other), git_commit_id(commit))); + cl_assert_equal_sz(ahead + 2, behind); + + git_commit_free(other); + + cl_git_pass(git_commit_nth_gen_ancestor(&other, commit, 3)); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(commit), git_commit_id(other))); + cl_assert_equal_sz(ahead, behind + 4); + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(other), git_commit_id(commit))); + cl_assert_equal_sz(ahead + 4, behind); + + git_commit_free(other); +} diff --git a/tests/libgit2/graph/commitgraph.c b/tests/libgit2/graph/commitgraph.c new file mode 100644 index 000000000..7607c35a3 --- /dev/null +++ b/tests/libgit2/graph/commitgraph.c @@ -0,0 +1,126 @@ +#include "clar_libgit2.h" + +#include +#include + +#include "commit_graph.h" +#include "futils.h" + +void test_graph_commitgraph__parse(void) +{ + git_repository *repo; + struct git_commit_graph_file *file; + struct git_commit_graph_entry e, parent; + git_oid id; + git_str commit_graph_path = GIT_STR_INIT; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_str_joinpath(&commit_graph_path, git_repository_path(repo), "objects/info/commit-graph")); + cl_git_pass(git_commit_graph_file_open(&file, git_str_cstr(&commit_graph_path))); + cl_assert_equal_i(git_commit_graph_file_needs_refresh(file, git_str_cstr(&commit_graph_path)), 0); + + cl_git_pass(git_oid_fromstr(&id, "5001298e0c09ad9c34e4249bc5801c75e9754fa5")); + cl_git_pass(git_commit_graph_entry_find(&e, file, &id, GIT_OID_HEXSZ)); + cl_assert_equal_oid(&e.sha1, &id); + cl_git_pass(git_oid_fromstr(&id, "418382dff1ffb8bdfba833f4d8bbcde58b1e7f47")); + cl_assert_equal_oid(&e.tree_oid, &id); + cl_assert_equal_i(e.generation, 1); + cl_assert_equal_i(e.commit_time, UINT64_C(1273610423)); + cl_assert_equal_i(e.parent_count, 0); + + cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_commit_graph_entry_find(&e, file, &id, GIT_OID_HEXSZ)); + cl_assert_equal_oid(&e.sha1, &id); + cl_assert_equal_i(e.generation, 5); + cl_assert_equal_i(e.commit_time, UINT64_C(1274813907)); + cl_assert_equal_i(e.parent_count, 2); + + cl_git_pass(git_oid_fromstr(&id, "9fd738e8f7967c078dceed8190330fc8648ee56a")); + cl_git_pass(git_commit_graph_entry_parent(&parent, file, &e, 0)); + cl_assert_equal_oid(&parent.sha1, &id); + cl_assert_equal_i(parent.generation, 4); + + cl_git_pass(git_oid_fromstr(&id, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + cl_git_pass(git_commit_graph_entry_parent(&parent, file, &e, 1)); + cl_assert_equal_oid(&parent.sha1, &id); + cl_assert_equal_i(parent.generation, 3); + + git_commit_graph_file_free(file); + git_repository_free(repo); + git_str_dispose(&commit_graph_path); +} + +void test_graph_commitgraph__parse_octopus_merge(void) +{ + git_repository *repo; + struct git_commit_graph_file *file; + struct git_commit_graph_entry e, parent; + git_oid id; + git_str commit_graph_path = GIT_STR_INIT; + + cl_git_pass(git_repository_open(&repo, cl_fixture("merge-recursive/.gitted"))); + cl_git_pass(git_str_joinpath(&commit_graph_path, git_repository_path(repo), "objects/info/commit-graph")); + cl_git_pass(git_commit_graph_file_open(&file, git_str_cstr(&commit_graph_path))); + + cl_git_pass(git_oid_fromstr(&id, "d71c24b3b113fd1d1909998c5bfe33b86a65ee03")); + cl_git_pass(git_commit_graph_entry_find(&e, file, &id, GIT_OID_HEXSZ)); + cl_assert_equal_oid(&e.sha1, &id); + cl_git_pass(git_oid_fromstr(&id, "348f16ffaeb73f319a75cec5b16a0a47d2d5e27c")); + cl_assert_equal_oid(&e.tree_oid, &id); + cl_assert_equal_i(e.generation, 7); + cl_assert_equal_i(e.commit_time, UINT64_C(1447083009)); + cl_assert_equal_i(e.parent_count, 3); + + cl_git_pass(git_oid_fromstr(&id, "ad2ace9e15f66b3d1138922e6ffdc3ea3f967fa6")); + cl_git_pass(git_commit_graph_entry_parent(&parent, file, &e, 0)); + cl_assert_equal_oid(&parent.sha1, &id); + cl_assert_equal_i(parent.generation, 6); + + cl_git_pass(git_oid_fromstr(&id, "483065df53c0f4a02cdc6b2910b05d388fc17ffb")); + cl_git_pass(git_commit_graph_entry_parent(&parent, file, &e, 1)); + cl_assert_equal_oid(&parent.sha1, &id); + cl_assert_equal_i(parent.generation, 2); + + cl_git_pass(git_oid_fromstr(&id, "815b5a1c80ca749d705c7aa0cb294a00cbedd340")); + cl_git_pass(git_commit_graph_entry_parent(&parent, file, &e, 2)); + cl_assert_equal_oid(&parent.sha1, &id); + cl_assert_equal_i(parent.generation, 6); + + git_commit_graph_file_free(file); + git_repository_free(repo); + git_str_dispose(&commit_graph_path); +} + +void test_graph_commitgraph__writer(void) +{ + git_repository *repo; + git_commit_graph_writer *w = NULL; + git_revwalk *walk; + git_commit_graph_writer_options opts = GIT_COMMIT_GRAPH_WRITER_OPTIONS_INIT; + git_buf cgraph = GIT_BUF_INIT; + git_str expected_cgraph = GIT_STR_INIT, path = GIT_STR_INIT; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "objects/info")); + cl_git_pass(git_commit_graph_writer_new(&w, git_str_cstr(&path))); + + /* This is equivalent to `git commit-graph write --reachable`. */ + cl_git_pass(git_revwalk_new(&walk, repo)); + cl_git_pass(git_revwalk_push_glob(walk, "refs/*")); + cl_git_pass(git_commit_graph_writer_add_revwalk(w, walk)); + git_revwalk_free(walk); + + cl_git_pass(git_commit_graph_writer_dump(&cgraph, w, &opts)); + cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "objects/info/commit-graph")); + cl_git_pass(git_futils_readbuffer(&expected_cgraph, git_str_cstr(&path))); + + cl_assert_equal_i(cgraph.size, git_str_len(&expected_cgraph)); + cl_assert_equal_i(memcmp(cgraph.ptr, git_str_cstr(&expected_cgraph), cgraph.size), 0); + + git_buf_dispose(&cgraph); + git_str_dispose(&expected_cgraph); + git_str_dispose(&path); + git_commit_graph_writer_free(w); + git_repository_free(repo); +} diff --git a/tests/libgit2/graph/descendant_of.c b/tests/libgit2/graph/descendant_of.c new file mode 100644 index 000000000..8e9952a09 --- /dev/null +++ b/tests/libgit2/graph/descendant_of.c @@ -0,0 +1,55 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; +static git_commit *commit; + +void test_graph_descendant_of__initialize(void) +{ + git_oid oid; + + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + + git_oid_fromstr(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); +} + +void test_graph_descendant_of__cleanup(void) +{ + git_commit_free(commit); + commit = NULL; + + git_repository_free(_repo); + _repo = NULL; +} + +void test_graph_descendant_of__returns_correct_result(void) +{ + git_commit *other; + + cl_assert_equal_i(0, git_graph_descendant_of(_repo, git_commit_id(commit), git_commit_id(commit))); + + + cl_git_pass(git_commit_nth_gen_ancestor(&other, commit, 1)); + + cl_assert_equal_i(1, git_graph_descendant_of(_repo, git_commit_id(commit), git_commit_id(other))); + cl_assert_equal_i(0, git_graph_descendant_of(_repo, git_commit_id(other), git_commit_id(commit))); + + git_commit_free(other); + + + cl_git_pass(git_commit_nth_gen_ancestor(&other, commit, 3)); + + cl_assert_equal_i(1, git_graph_descendant_of(_repo, git_commit_id(commit), git_commit_id(other))); + cl_assert_equal_i(0, git_graph_descendant_of(_repo, git_commit_id(other), git_commit_id(commit))); + + git_commit_free(other); + +} + +void test_graph_descendant_of__nopath(void) +{ + git_oid oid; + + git_oid_fromstr(&oid, "e90810b8df3e80c413d903f631643c716887138d"); + cl_assert_equal_i(0, git_graph_descendant_of(_repo, git_commit_id(commit), &oid)); +} diff --git a/tests/libgit2/graph/reachable_from_any.c b/tests/libgit2/graph/reachable_from_any.c new file mode 100644 index 000000000..9693d7d67 --- /dev/null +++ b/tests/libgit2/graph/reachable_from_any.c @@ -0,0 +1,236 @@ +#include "clar_libgit2.h" + +#include + +#include "commit_graph.h" +#include "bitvec.h" +#include "vector.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_graph_reachable_from_any__initialize(void) +{ + git_oid oid; + git_commit *commit; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); +} + +void test_graph_reachable_from_any__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_graph_reachable_from_any__returns_correct_result(void) +{ + git_object *branchA1, *branchA2, *branchB1, *branchB2, *branchC1, *branchC2, *branchH1, + *branchH2; + git_oid descendants[7]; + + cl_git_pass(git_revparse_single(&branchA1, repo, "branchA-1")); + cl_git_pass(git_revparse_single(&branchA2, repo, "branchA-2")); + cl_git_pass(git_revparse_single(&branchB1, repo, "branchB-1")); + cl_git_pass(git_revparse_single(&branchB2, repo, "branchB-2")); + cl_git_pass(git_revparse_single(&branchC1, repo, "branchC-1")); + cl_git_pass(git_revparse_single(&branchC2, repo, "branchC-2")); + cl_git_pass(git_revparse_single(&branchH1, repo, "branchH-1")); + cl_git_pass(git_revparse_single(&branchH2, repo, "branchH-2")); + + cl_assert_equal_i( + git_graph_reachable_from_any( + repo, git_object_id(branchH1), git_object_id(branchA1), 1), + 0); + cl_assert_equal_i( + git_graph_reachable_from_any( + repo, git_object_id(branchH1), git_object_id(branchA2), 1), + 0); + + cl_git_pass(git_oid_cpy(&descendants[0], git_object_id(branchA1))); + cl_git_pass(git_oid_cpy(&descendants[1], git_object_id(branchA2))); + cl_git_pass(git_oid_cpy(&descendants[2], git_object_id(branchB1))); + cl_git_pass(git_oid_cpy(&descendants[3], git_object_id(branchB2))); + cl_git_pass(git_oid_cpy(&descendants[4], git_object_id(branchC1))); + cl_git_pass(git_oid_cpy(&descendants[5], git_object_id(branchC2))); + cl_git_pass(git_oid_cpy(&descendants[6], git_object_id(branchH2))); + cl_assert_equal_i( + git_graph_reachable_from_any(repo, git_object_id(branchH2), descendants, 6), + 0); + cl_assert_equal_i( + git_graph_reachable_from_any(repo, git_object_id(branchH2), descendants, 7), + 1); + + git_object_free(branchA1); + git_object_free(branchA2); + git_object_free(branchB1); + git_object_free(branchB2); + git_object_free(branchC1); + git_object_free(branchC2); + git_object_free(branchH1); + git_object_free(branchH2); +} + +struct exhaustive_state { + git_odb *db; + git_vector commits; +}; + +/** Get all commits from the repository. */ +static int exhaustive_commits(const git_oid *id, void *payload) +{ + struct exhaustive_state *mc = (struct exhaustive_state *)payload; + size_t header_len; + git_object_t header_type; + int error = 0; + + error = git_odb_read_header(&header_len, &header_type, mc->db, id); + if (error < 0) + return error; + + if (header_type == GIT_OBJECT_COMMIT) { + git_commit *commit = NULL; + + cl_git_pass(git_commit_lookup(&commit, repo, id)); + cl_git_pass(git_vector_insert(&mc->commits, commit)); + } + + return 0; +} + +/** Compare the `git_oid`s of two `git_commit` objects. */ +static int commit_id_cmp(const void *a, const void *b) +{ + return git_oid_cmp( + git_commit_id((const git_commit *)a), git_commit_id((const git_commit *)b)); +} + +/** Find a `git_commit` whose ID matches the provided `git_oid` key. */ +static int id_commit_id_cmp(const void *key, const void *commit) +{ + return git_oid_cmp((const git_oid *)key, git_commit_id((const git_commit *)commit)); +} + +void test_graph_reachable_from_any__exhaustive(void) +{ + struct exhaustive_state mc = { + .db = NULL, + .commits = GIT_VECTOR_INIT, + }; + size_t child_idx, commit_count; + size_t n_descendants; + git_commit *child_commit; + git_bitvec reachable; + + cl_git_pass(git_repository_odb(&mc.db, repo)); + cl_git_pass(git_odb_foreach(mc.db, &exhaustive_commits, &mc)); + git_vector_set_cmp(&mc.commits, commit_id_cmp); + git_vector_sort(&mc.commits); + cl_git_pass(git_bitvec_init( + &reachable, + git_vector_length(&mc.commits) * git_vector_length(&mc.commits))); + + commit_count = git_vector_length(&mc.commits); + git_vector_foreach (&mc.commits, child_idx, child_commit) { + unsigned int parent_i; + + /* We treat each commit as being able to reach itself. */ + git_bitvec_set(&reachable, child_idx * commit_count + child_idx, true); + + for (parent_i = 0; parent_i < git_commit_parentcount(child_commit); ++parent_i) { + size_t parent_idx = -1; + cl_git_pass(git_vector_bsearch2( + &parent_idx, + &mc.commits, + id_commit_id_cmp, + git_commit_parent_id(child_commit, parent_i))); + + /* We have established that parent_idx is reachable from child_idx */ + git_bitvec_set(&reachable, parent_idx * commit_count + child_idx, true); + } + } + + /* Floyd-Warshall */ + { + size_t i, j, k; + for (k = 0; k < commit_count; ++k) { + for (i = 0; i < commit_count; ++i) { + if (!git_bitvec_get(&reachable, i * commit_count + k)) + continue; + for (j = 0; j < commit_count; ++j) { + if (!git_bitvec_get(&reachable, k * commit_count + j)) + continue; + git_bitvec_set(&reachable, i * commit_count + j, true); + } + } + } + } + + /* Try 1000 subsets of 1 through 10 entries each. */ + srand(0x223ddc4b); + for (n_descendants = 1; n_descendants < 10; ++n_descendants) { + size_t test_iteration; + git_oid descendants[10]; + + for (test_iteration = 0; test_iteration < 1000; ++test_iteration) { + size_t descendant_i; + size_t child_idx, parent_idx; + int expected_reachable = false, actual_reachable; + git_commit *child_commit, *parent_commit; + + parent_idx = rand() % commit_count; + parent_commit = (git_commit *)git_vector_get(&mc.commits, parent_idx); + for (descendant_i = 0; descendant_i < n_descendants; ++descendant_i) { + child_idx = rand() % commit_count; + child_commit = (git_commit *)git_vector_get(&mc.commits, child_idx); + expected_reachable |= git_bitvec_get( + &reachable, parent_idx * commit_count + child_idx); + git_oid_cpy(&descendants[descendant_i], + git_commit_id(child_commit)); + } + + actual_reachable = git_graph_reachable_from_any( + repo, + git_commit_id(parent_commit), + descendants, + n_descendants); + if (actual_reachable != expected_reachable) { + git_str error_message_buf = GIT_STR_INIT; + char parent_oidbuf[9] = {0}, child_oidbuf[9] = {0}; + + cl_git_pass(git_oid_nfmt( + parent_oidbuf, 8, git_commit_id(parent_commit))); + git_str_printf(&error_message_buf, + "git_graph_reachable_from_any(\"%s\", %zu, " + "{", + parent_oidbuf, + n_descendants); + for (descendant_i = 0; descendant_i < n_descendants; + ++descendant_i) { + cl_git_pass( + git_oid_nfmt(child_oidbuf, + 8, + &descendants[descendant_i])); + git_str_printf(&error_message_buf, " \"%s\"", child_oidbuf); + } + git_str_printf(&error_message_buf, + " }) = %d, expected = %d", + actual_reachable, + expected_reachable); + cl_check_(actual_reachable == expected_reachable, + git_str_cstr(&error_message_buf)); + } + } + } + + git_vector_foreach (&mc.commits, child_idx, child_commit) + git_commit_free(child_commit); + git_bitvec_free(&reachable); + git_vector_free(&mc.commits); + git_odb_free(mc.db); +} diff --git a/tests/libgit2/headertest.c b/tests/libgit2/headertest.c new file mode 100644 index 000000000..2af8a14ec --- /dev/null +++ b/tests/libgit2/headertest.c @@ -0,0 +1,13 @@ +/* + * Dummy project to validate header files + * + * This project is not intended to be executed, it should only include all + * header files to make sure that they can be used with stricter compiler + * settings than the libgit2 source files generally supports. + */ +#include "git2.h" + +int main(void) +{ + return 0; +} diff --git a/tests/libgit2/ignore/path.c b/tests/libgit2/ignore/path.c new file mode 100644 index 000000000..a574d1d79 --- /dev/null +++ b/tests/libgit2/ignore/path.c @@ -0,0 +1,585 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "futils.h" + +static git_repository *g_repo = NULL; + +void test_ignore_path__initialize(void) +{ + g_repo = cl_git_sandbox_init("attr"); +} + +void test_ignore_path__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static void assert_is_ignored_( + bool expected, const char *filepath, + const char *file, const char *func, int line) +{ + int is_ignored = 0; + + cl_git_expect( + git_ignore_path_is_ignored(&is_ignored, g_repo, filepath), 0, file, func, line); + + clar__assert_equal( + file, func, line, "expected != is_ignored", 1, "%d", + (int)(expected != 0), (int)(is_ignored != 0)); +} +#define assert_is_ignored(expected, filepath) \ + assert_is_ignored_(expected, filepath, __FILE__, __func__, __LINE__) + +void test_ignore_path__honor_temporary_rules(void) +{ + cl_git_rewritefile("attr/.gitignore", "/NewFolder\n/NewFolder/NewFolder"); + + assert_is_ignored(false, "File.txt"); + assert_is_ignored(true, "NewFolder"); + assert_is_ignored(true, "NewFolder/NewFolder"); + assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); +} + +void test_ignore_path__allow_root(void) +{ + cl_git_rewritefile("attr/.gitignore", "/"); + + assert_is_ignored(false, "File.txt"); + assert_is_ignored(false, "NewFolder"); + assert_is_ignored(false, "NewFolder/NewFolder"); + assert_is_ignored(false, "NewFolder/NewFolder/File.txt"); +} + +void test_ignore_path__ignore_space(void) +{ + cl_git_rewritefile("attr/.gitignore", "/\n\n/NewFolder \n/NewFolder/NewFolder"); + + assert_is_ignored(false, "File.txt"); + assert_is_ignored(true, "NewFolder"); + assert_is_ignored(true, "NewFolder/NewFolder"); + assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); +} + +void test_ignore_path__intermittent_space(void) +{ + cl_git_rewritefile("attr/.gitignore", "foo bar\n"); + + assert_is_ignored(false, "foo"); + assert_is_ignored(false, "bar"); + assert_is_ignored(true, "foo bar"); +} + +void test_ignore_path__trailing_space(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "foo \n" + "bar \n" + ); + + assert_is_ignored(true, "foo"); + assert_is_ignored(false, "foo "); + assert_is_ignored(true, "bar"); + assert_is_ignored(false, "bar "); + assert_is_ignored(false, "bar "); +} + +void test_ignore_path__escaped_trailing_spaces(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "foo\\ \n" + "bar\\ \\ \n" + "baz \\ \n" + "qux\\ \n" + ); + + assert_is_ignored(false, "foo"); + assert_is_ignored(true, "foo "); + assert_is_ignored(false, "bar"); + assert_is_ignored(false, "bar "); + assert_is_ignored(true, "bar "); + assert_is_ignored(true, "baz "); + assert_is_ignored(false, "baz "); + assert_is_ignored(true, "qux "); + assert_is_ignored(false, "qux"); + assert_is_ignored(false, "qux "); +} + +void test_ignore_path__ignore_dir(void) +{ + cl_git_rewritefile("attr/.gitignore", "dir/\n"); + + assert_is_ignored(true, "dir"); + assert_is_ignored(true, "dir/file"); +} + +void test_ignore_path__ignore_dir_with_trailing_space(void) +{ + cl_git_rewritefile("attr/.gitignore", "dir/ \n"); + + assert_is_ignored(true, "dir"); + assert_is_ignored(true, "dir/file"); +} + +void test_ignore_path__ignore_root(void) +{ + cl_git_rewritefile("attr/.gitignore", "/\n\n/NewFolder\n/NewFolder/NewFolder"); + + assert_is_ignored(false, "File.txt"); + assert_is_ignored(true, "NewFolder"); + assert_is_ignored(true, "NewFolder/NewFolder"); + assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); +} + +void test_ignore_path__full_paths(void) +{ + cl_git_rewritefile("attr/.gitignore", "Folder/*/Contained"); + + assert_is_ignored(true, "Folder/Middle/Contained"); + assert_is_ignored(false, "Folder/Middle/More/More/Contained"); + + cl_git_rewritefile("attr/.gitignore", "Folder/**/Contained"); + + assert_is_ignored(true, "Folder/Middle/Contained"); + assert_is_ignored(true, "Folder/Middle/More/More/Contained"); + + cl_git_rewritefile("attr/.gitignore", "Folder/**/Contained/*/Child"); + + assert_is_ignored(true, "Folder/Middle/Contained/Happy/Child"); + assert_is_ignored(false, "Folder/Middle/Contained/Not/Happy/Child"); + assert_is_ignored(true, "Folder/Middle/More/More/Contained/Happy/Child"); + assert_is_ignored(false, "Folder/Middle/More/More/Contained/Not/Happy/Child"); +} + +void test_ignore_path__more_starstar_cases(void) +{ + cl_must_pass(p_unlink("attr/.gitignore")); + cl_git_mkfile( + "attr/dir/.gitignore", + "sub/**/*.html\n"); + + assert_is_ignored(false, "aaa.html"); + assert_is_ignored(false, "dir"); + assert_is_ignored(false, "dir/sub"); + assert_is_ignored(true, "dir/sub/sub2/aaa.html"); + assert_is_ignored(true, "dir/sub/aaa.html"); + assert_is_ignored(false, "dir/aaa.html"); + assert_is_ignored(false, "sub"); + assert_is_ignored(false, "sub/aaa.html"); + assert_is_ignored(false, "sub/sub2/aaa.html"); +} + +void test_ignore_path__leading_stars(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "*/onestar\n" + "**/twostars\n" + "*/parent1/kid1/*\n" + "**/parent2/kid2/*\n"); + + assert_is_ignored(true, "dir1/onestar"); + assert_is_ignored(true, "dir1/onestar/child"); /* in ignored dir */ + assert_is_ignored(false, "dir1/dir2/onestar"); + + assert_is_ignored(true, "dir1/twostars"); + assert_is_ignored(true, "dir1/twostars/child"); /* in ignored dir */ + assert_is_ignored(true, "dir1/dir2/twostars"); + assert_is_ignored(true, "dir1/dir2/twostars/child"); /* in ignored dir */ + assert_is_ignored(true, "dir1/dir2/dir3/twostars"); + + assert_is_ignored(true, "dir1/parent1/kid1/file"); + assert_is_ignored(true, "dir1/parent1/kid1/file/inside/parent"); + assert_is_ignored(false, "dir1/dir2/parent1/kid1/file"); + assert_is_ignored(false, "dir1/parent1/file"); + assert_is_ignored(false, "dir1/kid1/file"); + + assert_is_ignored(true, "dir1/parent2/kid2/file"); + assert_is_ignored(true, "dir1/parent2/kid2/file/inside/parent"); + assert_is_ignored(true, "dir1/dir2/parent2/kid2/file"); + assert_is_ignored(true, "dir1/dir2/dir3/parent2/kid2/file"); + assert_is_ignored(false, "dir1/parent2/file"); + assert_is_ignored(false, "dir1/kid2/file"); +} + +void test_ignore_path__globs_and_path_delimiters(void) +{ + cl_git_rewritefile("attr/.gitignore", "foo/bar/**"); + assert_is_ignored(true, "foo/bar/baz"); + assert_is_ignored(true, "foo/bar/baz/quux"); + + cl_git_rewritefile("attr/.gitignore", "_*/"); + assert_is_ignored(true, "sub/_test/a/file"); + assert_is_ignored(false, "test_folder/file"); + assert_is_ignored(true, "_test/file"); + assert_is_ignored(true, "_test/a/file"); + + cl_git_rewritefile("attr/.gitignore", "**/_*/"); + assert_is_ignored(true, "sub/_test/a/file"); + assert_is_ignored(false, "test_folder/file"); + assert_is_ignored(true, "_test/file"); + assert_is_ignored(true, "_test/a/file"); + + cl_git_rewritefile("attr/.gitignore", "**/_*/foo/bar/*ux"); + + assert_is_ignored(true, "sub/_test/foo/bar/qux/file"); + assert_is_ignored(true, "_test/foo/bar/qux/file"); + assert_is_ignored(true, "_test/foo/bar/crux/file"); + assert_is_ignored(false, "_test/foo/bar/code/file"); +} + +void test_ignore_path__globs_without_star(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "*.foo\n" + "**.bar\n" + ); + + assert_is_ignored(true, ".foo"); + assert_is_ignored(true, "xyz.foo"); + assert_is_ignored(true, ".bar"); + assert_is_ignored(true, "x.bar"); + assert_is_ignored(true, "xyz.bar"); + + assert_is_ignored(true, "test/.foo"); + assert_is_ignored(true, "test/x.foo"); + assert_is_ignored(true, "test/xyz.foo"); + assert_is_ignored(true, "test/.bar"); + assert_is_ignored(true, "test/x.bar"); + assert_is_ignored(true, "test/xyz.bar"); +} + +void test_ignore_path__skip_gitignore_directory(void) +{ + cl_git_rewritefile("attr/.git/info/exclude", "/NewFolder\n/NewFolder/NewFolder"); + cl_must_pass(p_unlink("attr/.gitignore")); + cl_assert(!git_fs_path_exists("attr/.gitignore")); + p_mkdir("attr/.gitignore", 0777); + cl_git_mkfile("attr/.gitignore/garbage.txt", "new_file\n"); + + assert_is_ignored(false, "File.txt"); + assert_is_ignored(true, "NewFolder"); + assert_is_ignored(true, "NewFolder/NewFolder"); + assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); +} + +void test_ignore_path__subdirectory_gitignore(void) +{ + cl_must_pass(p_unlink("attr/.gitignore")); + cl_assert(!git_fs_path_exists("attr/.gitignore")); + cl_git_mkfile( + "attr/.gitignore", + "file1\n"); + cl_git_mkfile( + "attr/dir/.gitignore", + "file2/\n"); + + assert_is_ignored(true, "file1"); + assert_is_ignored(true, "dir/file1"); + assert_is_ignored(true, "dir/file2/actual_file"); /* in ignored dir */ + assert_is_ignored(false, "dir/file3"); +} + +void test_ignore_path__expand_tilde_to_homedir(void) +{ + git_config *cfg; + + assert_is_ignored(false, "example.global_with_tilde"); + + cl_fake_home(); + + /* construct fake home with fake global excludes */ + cl_git_mkfile("home/globalexclude", "# found me\n*.global_with_tilde\n"); + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_string(cfg, "core.excludesfile", "~/globalexclude")); + git_config_free(cfg); + + git_attr_cache_flush(g_repo); /* must reset to pick up change */ + + assert_is_ignored(true, "example.global_with_tilde"); + + cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES)); + + cl_fake_home_cleanup(NULL); + + git_attr_cache_flush(g_repo); /* must reset to pick up change */ + + assert_is_ignored(false, "example.global_with_tilde"); +} + +/* Ensure that the .gitignore in the subdirectory only affects + * items in the subdirectory. */ +void test_ignore_path__gitignore_in_subdir(void) +{ + cl_git_rmfile("attr/.gitignore"); + + cl_must_pass(p_mkdir("attr/dir1", 0777)); + cl_must_pass(p_mkdir("attr/dir1/dir2", 0777)); + cl_must_pass(p_mkdir("attr/dir1/dir2/dir3", 0777)); + + cl_git_mkfile("attr/dir1/dir2/dir3/.gitignore", "dir1/\ndir1/subdir/"); + + assert_is_ignored(false, "dir1/file"); + assert_is_ignored(false, "dir1/dir2/file"); + assert_is_ignored(false, "dir1/dir2/dir3/file"); + assert_is_ignored(true, "dir1/dir2/dir3/dir1/file"); + assert_is_ignored(true, "dir1/dir2/dir3/dir1/subdir/foo"); + + if (cl_repo_get_bool(g_repo, "core.ignorecase")) { + cl_git_mkfile("attr/dir1/dir2/dir3/.gitignore", "DiR1/\nDiR1/subdir/\n"); + + assert_is_ignored(false, "dir1/file"); + assert_is_ignored(false, "dir1/dir2/file"); + assert_is_ignored(false, "dir1/dir2/dir3/file"); + assert_is_ignored(true, "dir1/dir2/dir3/dir1/file"); + assert_is_ignored(true, "dir1/dir2/dir3/dir1/subdir/foo"); + } +} + +/* Ensure that files do not match folder cases */ +void test_ignore_path__dont_ignore_files_for_folder(void) +{ + cl_git_rmfile("attr/.gitignore"); + + cl_git_mkfile("attr/dir/.gitignore", "test/\n"); + + /* Create "test" as a file; ensure it is not ignored. */ + cl_git_mkfile("attr/dir/test", "This is a file."); + + assert_is_ignored(false, "dir/test"); + if (cl_repo_get_bool(g_repo, "core.ignorecase")) + assert_is_ignored(false, "dir/TeSt"); + + /* Create "test" as a directory; ensure it is ignored. */ + cl_git_rmfile("attr/dir/test"); + cl_must_pass(p_mkdir("attr/dir/test", 0777)); + + assert_is_ignored(true, "dir/test"); + if (cl_repo_get_bool(g_repo, "core.ignorecase")) + assert_is_ignored(true, "dir/TeSt"); + + /* Remove "test" entirely; ensure it is not ignored. + * (As it doesn't exist, it is not a directory.) + */ + cl_must_pass(p_rmdir("attr/dir/test")); + + assert_is_ignored(false, "dir/test"); + if (cl_repo_get_bool(g_repo, "core.ignorecase")) + assert_is_ignored(false, "dir/TeSt"); +} + +void test_ignore_path__symlink_to_outside(void) +{ +#ifdef GIT_WIN32 + cl_skip(); +#endif + + cl_git_rewritefile("attr/.gitignore", "symlink\n"); + cl_git_mkfile("target", "target"); + cl_git_pass(p_symlink("../target", "attr/symlink")); + assert_is_ignored(true, "symlink"); + assert_is_ignored(true, "lala/../symlink"); +} + +void test_ignore_path__test(void) +{ + cl_git_rewritefile("attr/.gitignore", + "/*/\n" + "!/src\n"); + assert_is_ignored(false, "src/foo.c"); + assert_is_ignored(false, "src/foo/foo.c"); + assert_is_ignored(false, "README.md"); + assert_is_ignored(true, "dist/foo.o"); + assert_is_ignored(true, "bin/foo"); +} + +void test_ignore_path__unignore_dir_succeeds(void) +{ + cl_git_rewritefile("attr/.gitignore", + "*.c\n" + "!src/*.c\n"); + assert_is_ignored(false, "src/foo.c"); + assert_is_ignored(true, "src/foo/foo.c"); +} + +void test_ignore_path__case_insensitive_unignores_previous_rule(void) +{ + git_config *cfg; + + cl_git_rewritefile("attr/.gitignore", + "/case\n" + "!/Case/\n"); + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, "core.ignorecase", true)); + + cl_must_pass(p_mkdir("attr/case", 0755)); + cl_git_mkfile("attr/case/file", "content"); + + assert_is_ignored(false, "case/file"); +} + +void test_ignore_path__case_sensitive_unignore_does_nothing(void) +{ + git_config *cfg; + + cl_git_rewritefile("attr/.gitignore", + "/case\n" + "!/Case/\n"); + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, "core.ignorecase", false)); + + cl_must_pass(p_mkdir("attr/case", 0755)); + cl_git_mkfile("attr/case/file", "content"); + + assert_is_ignored(true, "case/file"); +} + +void test_ignore_path__ignored_subdirfiles_with_subdir_rule(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "dir/*\n" + "!dir/sub1/sub2/**\n"); + + assert_is_ignored(true, "dir/a.test"); + assert_is_ignored(true, "dir/sub1/a.test"); + assert_is_ignored(true, "dir/sub1/sub2"); +} + +void test_ignore_path__ignored_subdirfiles_with_negations(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "dir/*\n" + "!dir/a.test\n"); + + assert_is_ignored(false, "dir/a.test"); + assert_is_ignored(true, "dir/b.test"); + assert_is_ignored(true, "dir/sub1/c.test"); +} + +void test_ignore_path__negative_directory_rules_only_match_directories(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "*\n" + "!/**/\n" + "!*.keep\n" + "!.gitignore\n" + ); + + assert_is_ignored(true, "src"); + assert_is_ignored(true, "src/A"); + assert_is_ignored(false, "src/"); + assert_is_ignored(false, "src/A.keep"); + assert_is_ignored(false, ".gitignore"); +} + +void test_ignore_path__escaped_character(void) +{ + cl_git_rewritefile("attr/.gitignore", "\\c\n"); + assert_is_ignored(true, "c"); + assert_is_ignored(false, "\\c"); +} + +void test_ignore_path__escaped_newline(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "\\\nnewline\n" + ); + + assert_is_ignored(true, "\nnewline"); +} + +void test_ignore_path__escaped_glob(void) +{ + cl_git_rewritefile("attr/.gitignore", "\\*\n"); + assert_is_ignored(true, "*"); + assert_is_ignored(false, "foo"); +} + +void test_ignore_path__escaped_comments(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "#foo\n" + "\\#bar\n" + "\\##baz\n" + "\\#\\\\#qux\n" + ); + + assert_is_ignored(false, "#foo"); + assert_is_ignored(true, "#bar"); + assert_is_ignored(false, "\\#bar"); + assert_is_ignored(true, "##baz"); + assert_is_ignored(false, "\\##baz"); + assert_is_ignored(true, "#\\#qux"); + assert_is_ignored(false, "##qux"); + assert_is_ignored(false, "\\##qux"); +} + +void test_ignore_path__escaped_slash(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "\\\\\n" + "\\\\preceding\n" + "inter\\\\mittent\n" + "trailing\\\\\n" + ); + +#ifndef GIT_WIN32 + assert_is_ignored(true, "\\"); + assert_is_ignored(true, "\\preceding"); +#endif + assert_is_ignored(true, "inter\\mittent"); + assert_is_ignored(true, "trailing\\"); +} + +void test_ignore_path__escaped_space(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "foo\\\\ \n" + "bar\\\\\\ \n"); + assert_is_ignored(true, "foo\\"); + assert_is_ignored(false, "foo\\ "); + assert_is_ignored(false, "foo\\\\ "); + assert_is_ignored(false, "foo\\\\"); + assert_is_ignored(true, "bar\\ "); + assert_is_ignored(false, "bar\\\\"); + assert_is_ignored(false, "bar\\\\ "); + assert_is_ignored(false, "bar\\\\\\"); + assert_is_ignored(false, "bar\\\\\\ "); +} + +void test_ignore_path__invalid_pattern(void) +{ + cl_git_rewritefile("attr/.gitignore", "["); + assert_is_ignored(false, "[f"); + assert_is_ignored(false, "f"); +} + +void test_ignore_path__negative_prefix_rule(void) +{ + cl_git_rewritefile("attr/.gitignore", "ff*\n!f\n"); + assert_is_ignored(true, "fff"); + assert_is_ignored(true, "ff"); + assert_is_ignored(false, "f"); +} + +void test_ignore_path__negative_more_specific(void) +{ + cl_git_rewritefile("attr/.gitignore", "*.txt\n!/dir/test.txt\n"); + assert_is_ignored(true, "test.txt"); + assert_is_ignored(false, "dir/test.txt"); + assert_is_ignored(true, "outer/dir/test.txt"); +} diff --git a/tests/libgit2/ignore/status.c b/tests/libgit2/ignore/status.c new file mode 100644 index 000000000..deb717590 --- /dev/null +++ b/tests/libgit2/ignore/status.c @@ -0,0 +1,1337 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "git2/attr.h" +#include "ignore.h" +#include "attr.h" +#include "status/status_helpers.h" + +static git_repository *g_repo = NULL; + +void test_ignore_status__initialize(void) +{ +} + +void test_ignore_status__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void assert_ignored_( + bool expected, const char *filepath, + const char *file, const char *func, int line) +{ + int is_ignored = 0; + cl_git_expect( + git_status_should_ignore(&is_ignored, g_repo, filepath), 0, file, func, line); + clar__assert( + (expected != 0) == (is_ignored != 0), + file, func, line, "expected != is_ignored", filepath, 1); +} +#define assert_ignored(expected, filepath) \ + assert_ignored_(expected, filepath, __FILE__, __func__, __LINE__) +#define assert_is_ignored(filepath) \ + assert_ignored_(true, filepath, __FILE__, __func__, __LINE__) +#define refute_is_ignored(filepath) \ + assert_ignored_(false, filepath, __FILE__, __func__, __LINE__) + +void test_ignore_status__0(void) +{ + struct { + const char *path; + int expected; + } test_cases[] = { + /* pattern "ign" from .gitignore */ + { "file", 0 }, + { "ign", 1 }, + { "sub", 0 }, + { "sub/file", 0 }, + { "sub/ign", 1 }, + { "sub/ign/file", 1 }, + { "sub/ign/sub", 1 }, + { "sub/ign/sub/file", 1 }, + { "sub/sub", 0 }, + { "sub/sub/file", 0 }, + { "sub/sub/ign", 1 }, + { "sub/sub/sub", 0 }, + /* pattern "dir/" from .gitignore */ + { "dir", 1 }, + { "dir/", 1 }, + { "sub/dir", 1 }, + { "sub/dir/", 1 }, + { "sub/dir/file", 1 }, /* contained in ignored parent */ + { "sub/sub/dir", 0 }, /* dir is not actually a dir, but a file */ + { NULL, 0 } + }, *one_test; + + g_repo = cl_git_sandbox_init("attr"); + + for (one_test = test_cases; one_test->path != NULL; one_test++) + assert_ignored(one_test->expected, one_test->path); + + /* confirm that ignore files were cached */ + cl_assert(git_attr_cache__is_cached( + g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".git/info/exclude")); + cl_assert(git_attr_cache__is_cached( + g_repo, GIT_ATTR_FILE_SOURCE_FILE, ".gitignore")); +} + + +void test_ignore_status__1(void) +{ + g_repo = cl_git_sandbox_init("attr"); + + cl_git_rewritefile("attr/.gitignore", "/*.txt\n/dir/\n"); + git_attr_cache_flush(g_repo); + + assert_is_ignored("root_test4.txt"); + refute_is_ignored("sub/subdir_test2.txt"); + assert_is_ignored("dir"); + assert_is_ignored("dir/"); + refute_is_ignored("sub/dir"); + refute_is_ignored("sub/dir/"); +} + +void test_ignore_status__empty_repo_with_gitignore_rewrite(void) +{ + status_entry_single st; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile( + "empty_standard_repo/look-ma.txt", "I'm going to be ignored!"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); + cl_assert(st.count == 1); + cl_assert(st.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt")); + cl_assert(st.status == GIT_STATUS_WT_NEW); + + refute_is_ignored("look-ma.txt"); + + cl_git_rewritefile("empty_standard_repo/.gitignore", "*.nomatch\n"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); + cl_assert(st.count == 2); + cl_assert(st.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt")); + cl_assert(st.status == GIT_STATUS_WT_NEW); + + refute_is_ignored("look-ma.txt"); + + cl_git_rewritefile("empty_standard_repo/.gitignore", "*.txt\n"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); + cl_assert(st.count == 2); + cl_assert(st.status == GIT_STATUS_IGNORED); + + cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt")); + cl_assert(st.status == GIT_STATUS_IGNORED); + + assert_is_ignored("look-ma.txt"); +} + +void test_ignore_status__ignore_pattern_contains_space(void) +{ + unsigned int flags; + const mode_t mode = 0777; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_rewritefile("empty_standard_repo/.gitignore", "foo bar.txt\n"); + + cl_git_mkfile( + "empty_standard_repo/foo bar.txt", "I'm going to be ignored!"); + + cl_git_pass(git_status_file(&flags, g_repo, "foo bar.txt")); + cl_assert(flags == GIT_STATUS_IGNORED); + + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/foo", mode)); + cl_git_mkfile("empty_standard_repo/foo/look-ma.txt", "I'm not going to be ignored!"); + + cl_git_pass(git_status_file(&flags, g_repo, "foo/look-ma.txt")); + cl_assert(flags == GIT_STATUS_WT_NEW); +} + +void test_ignore_status__ignore_pattern_ignorecase(void) +{ + unsigned int flags; + bool ignore_case; + git_index *index; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_rewritefile("empty_standard_repo/.gitignore", "a.txt\n"); + + cl_git_mkfile("empty_standard_repo/A.txt", "Differs in case"); + + cl_git_pass(git_repository_index(&index, g_repo)); + ignore_case = (git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0; + git_index_free(index); + + cl_git_pass(git_status_file(&flags, g_repo, "A.txt")); + cl_assert(flags == ignore_case ? GIT_STATUS_IGNORED : GIT_STATUS_WT_NEW); +} + +void test_ignore_status__subdirectories(void) +{ + status_entry_single st; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile( + "empty_standard_repo/ignore_me", "I'm going to be ignored!"); + + cl_git_rewritefile("empty_standard_repo/.gitignore", "ignore_me\n"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); + cl_assert_equal_i(2, st.count); + cl_assert(st.status == GIT_STATUS_IGNORED); + + cl_git_pass(git_status_file(&st.status, g_repo, "ignore_me")); + cl_assert(st.status == GIT_STATUS_IGNORED); + + assert_is_ignored("ignore_me"); + + /* I've changed libgit2 so that the behavior here now differs from + * core git but seems to make more sense. In core git, the following + * items are skipped completed, even if --ignored is passed to status. + * It you mirror these steps and run "git status -uall --ignored" then + * you will not see "test/ignore_me/" in the results. + * + * However, we had a couple reports of this as a bug, plus there is a + * similar circumstance where we were differing for core git when you + * used a rooted path for an ignore, so I changed this behavior. + */ + cl_git_pass(git_futils_mkdir_r( + "empty_standard_repo/test/ignore_me", 0775)); + cl_git_mkfile( + "empty_standard_repo/test/ignore_me/file", "I'm going to be ignored!"); + cl_git_mkfile( + "empty_standard_repo/test/ignore_me/file2", "Me, too!"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st)); + cl_assert_equal_i(3, st.count); + + cl_git_pass(git_status_file(&st.status, g_repo, "test/ignore_me/file")); + cl_assert(st.status == GIT_STATUS_IGNORED); + + assert_is_ignored("test/ignore_me/file"); +} + +static void make_test_data(const char *reponame, const char **files) +{ + const char **scan; + size_t repolen = strlen(reponame) + 1; + + g_repo = cl_git_sandbox_init(reponame); + + for (scan = files; *scan != NULL; ++scan) { + cl_git_pass(git_futils_mkdir_relative( + *scan + repolen, reponame, + 0777, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST, NULL)); + cl_git_mkfile(*scan, "contents"); + } +} + +static const char *test_repo_1 = "empty_standard_repo"; +static const char *test_files_1[] = { + "empty_standard_repo/dir/a/ignore_me", + "empty_standard_repo/dir/b/ignore_me", + "empty_standard_repo/dir/ignore_me", + "empty_standard_repo/ignore_also/file", + "empty_standard_repo/ignore_me", + "empty_standard_repo/test/ignore_me/file", + "empty_standard_repo/test/ignore_me/file2", + "empty_standard_repo/test/ignore_me/and_me/file", + NULL +}; + +void test_ignore_status__subdirectories_recursion(void) +{ + /* Let's try again with recursing into ignored dirs turned on */ + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *paths_r[] = { + ".gitignore", + "dir/a/ignore_me", + "dir/b/ignore_me", + "dir/ignore_me", + "ignore_also/file", + "ignore_me", + "test/ignore_me/and_me/file", + "test/ignore_me/file", + "test/ignore_me/file2", + }; + static const unsigned int statuses_r[] = { + GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, + GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, + GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, + }; + static const char *paths_nr[] = { + ".gitignore", + "dir/a/ignore_me", + "dir/b/ignore_me", + "dir/ignore_me", + "ignore_also/", + "ignore_me", + "test/ignore_me/", + }; + static const unsigned int statuses_nr[] = { + GIT_STATUS_WT_NEW, + GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, + GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, + }; + + make_test_data(test_repo_1, test_files_1); + cl_git_rewritefile("empty_standard_repo/.gitignore", "ignore_me\n/ignore_also\n"); + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 9; + counts.expected_paths = paths_r; + counts.expected_statuses = statuses_r; + + opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 7; + counts.expected_paths = paths_nr; + counts.expected_statuses = statuses_nr; + + opts.flags = GIT_STATUS_OPT_DEFAULTS; + + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +void test_ignore_status__subdirectories_not_at_root(void) +{ + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *paths_1[] = { + "dir/.gitignore", + "dir/a/ignore_me", + "dir/b/ignore_me", + "dir/ignore_me", + "ignore_also/file", + "ignore_me", + "test/.gitignore", + "test/ignore_me/and_me/file", + "test/ignore_me/file", + "test/ignore_me/file2", + }; + static const unsigned int statuses_1[] = { + GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, + GIT_STATUS_IGNORED, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, + }; + + make_test_data(test_repo_1, test_files_1); + cl_git_rewritefile("empty_standard_repo/dir/.gitignore", "ignore_me\n/ignore_also\n"); + cl_git_rewritefile("empty_standard_repo/test/.gitignore", "and_me\n"); + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 10; + counts.expected_paths = paths_1; + counts.expected_statuses = statuses_1; + + opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +void test_ignore_status__leading_slash_ignores(void) +{ + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *paths_2[] = { + "dir/.gitignore", + "dir/a/ignore_me", + "dir/b/ignore_me", + "dir/ignore_me", + "ignore_also/file", + "ignore_me", + "test/.gitignore", + "test/ignore_me/and_me/file", + "test/ignore_me/file", + "test/ignore_me/file2", + }; + static const unsigned int statuses_2[] = { + GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, + GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, + GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, + }; + + make_test_data(test_repo_1, test_files_1); + + cl_fake_home(); + cl_git_mkfile("home/.gitignore", "/ignore_me\n"); + { + git_config *cfg; + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_string( + cfg, "core.excludesfile", "~/.gitignore")); + git_config_free(cfg); + } + + cl_git_rewritefile("empty_standard_repo/.git/info/exclude", "/ignore_also\n"); + cl_git_rewritefile("empty_standard_repo/dir/.gitignore", "/ignore_me\n"); + cl_git_rewritefile("empty_standard_repo/test/.gitignore", "/and_me\n"); + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 10; + counts.expected_paths = paths_2; + counts.expected_statuses = statuses_2; + + opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +void test_ignore_status__multiple_leading_slash(void) +{ + static const char *test_files[] = { + "empty_standard_repo/a.test", + "empty_standard_repo/b.test", + "empty_standard_repo/c.test", + "empty_standard_repo/d.test", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "a.test\n" + "/b.test\n" + "//c.test\n" + "///d.test\n"); + + assert_is_ignored("a.test"); + assert_is_ignored("b.test"); + refute_is_ignored("c.test"); + refute_is_ignored("d.test"); +} + +void test_ignore_status__contained_dir_with_matching_name(void) +{ + static const char *test_files[] = { + "empty_standard_repo/subdir_match/aaa/subdir_match/file", + "empty_standard_repo/subdir_match/zzz_ignoreme", + NULL + }; + static const char *expected_paths[] = { + "subdir_match/.gitignore", + "subdir_match/aaa/subdir_match/file", + "subdir_match/zzz_ignoreme", + }; + static const unsigned int expected_statuses[] = { + GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED + }; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/subdir_match/.gitignore", "*_ignoreme\n"); + + refute_is_ignored("subdir_match/aaa/subdir_match/file"); + assert_is_ignored("subdir_match/zzz_ignoreme"); + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 3; + counts.expected_paths = expected_paths; + counts.expected_statuses = expected_statuses; + + opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +void test_ignore_status__trailing_slash_star(void) +{ + static const char *test_files[] = { + "empty_standard_repo/file", + "empty_standard_repo/subdir/file", + "empty_standard_repo/subdir/sub2/sub3/file", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/subdir/.gitignore", "/**/*\n"); + + refute_is_ignored("file"); + assert_is_ignored("subdir/sub2/sub3/file"); + assert_is_ignored("subdir/file"); +} + +void test_ignore_status__adding_internal_ignores(void) +{ + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + refute_is_ignored("one.txt"); + refute_is_ignored("two.bar"); + + cl_git_pass(git_ignore_add_rule(g_repo, "*.nomatch\n")); + + refute_is_ignored("one.txt"); + refute_is_ignored("two.bar"); + + cl_git_pass(git_ignore_add_rule(g_repo, "*.txt\n")); + + assert_is_ignored("one.txt"); + refute_is_ignored("two.bar"); + + cl_git_pass(git_ignore_add_rule(g_repo, "*.bar\n")); + + assert_is_ignored("one.txt"); + assert_is_ignored("two.bar"); + + cl_git_pass(git_ignore_clear_internal_rules(g_repo)); + + refute_is_ignored("one.txt"); + refute_is_ignored("two.bar"); + + cl_git_pass(git_ignore_add_rule( + g_repo, "multiple\n*.rules\n# comment line\n*.bar\n")); + + refute_is_ignored("one.txt"); + assert_is_ignored("two.bar"); +} + +void test_ignore_status__add_internal_as_first_thing(void) +{ + const char *add_me = "\n#################\n## Eclipse\n#################\n\n*.pydevproject\n.project\n.metadata\nbin/\ntmp/\n*.tmp\n\n"; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_ignore_add_rule(g_repo, add_me)); + + assert_is_ignored("one.tmp"); + refute_is_ignored("two.bar"); +} + +void test_ignore_status__internal_ignores_inside_deep_paths(void) +{ + const char *add_me = "Debug\nthis/is/deep\npatterned*/dir\n"; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_ignore_add_rule(g_repo, add_me)); + + assert_is_ignored("Debug"); + assert_is_ignored("and/Debug"); + assert_is_ignored("really/Debug/this/file"); + assert_is_ignored("Debug/what/I/say"); + + refute_is_ignored("and/NoDebug"); + refute_is_ignored("NoDebug/this"); + refute_is_ignored("please/NoDebug/this"); + + assert_is_ignored("this/is/deep"); + /* pattern containing slash gets FNM_PATHNAME so all slashes must match */ + refute_is_ignored("and/this/is/deep"); + assert_is_ignored("this/is/deep/too"); + /* pattern containing slash gets FNM_PATHNAME so all slashes must match */ + refute_is_ignored("but/this/is/deep/and/ignored"); + + refute_is_ignored("this/is/not/deep"); + refute_is_ignored("is/this/not/as/deep"); + refute_is_ignored("this/is/deepish"); + refute_is_ignored("xthis/is/deep"); +} + +void test_ignore_status__automatically_ignore_bad_files(void) +{ + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + assert_is_ignored(".git"); + assert_is_ignored("this/file/."); + assert_is_ignored("path/../funky"); + refute_is_ignored("path/whatever.c"); + + cl_git_pass(git_ignore_add_rule(g_repo, "*.c\n")); + + assert_is_ignored(".git"); + assert_is_ignored("this/file/."); + assert_is_ignored("path/../funky"); + assert_is_ignored("path/whatever.c"); + + cl_git_pass(git_ignore_clear_internal_rules(g_repo)); + + assert_is_ignored(".git"); + assert_is_ignored("this/file/."); + assert_is_ignored("path/../funky"); + refute_is_ignored("path/whatever.c"); +} + +void test_ignore_status__filenames_with_special_prefixes_do_not_interfere_with_status_retrieval(void) +{ + status_entry_single st; + char *test_cases[] = { + "!file", + "#blah", + "[blah]", + "[attr]", + "[attr]blah", + NULL + }; + int i; + + for (i = 0; *(test_cases + i) != NULL; i++) { + git_str file = GIT_STR_INIT; + char *file_name = *(test_cases + i); + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_str_joinpath(&file, "empty_standard_repo", file_name)); + cl_git_mkfile(git_str_cstr(&file), "Please don't ignore me!"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &st)); + cl_assert(st.count == 1); + cl_assert(st.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&st.status, repo, file_name)); + cl_assert(st.status == GIT_STATUS_WT_NEW); + + cl_git_sandbox_cleanup(); + git_str_dispose(&file); + } +} + +void test_ignore_status__issue_1766_negated_ignores(void) +{ + unsigned int status; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_futils_mkdir_r( + "empty_standard_repo/a", 0775)); + cl_git_mkfile( + "empty_standard_repo/a/.gitignore", "*\n!.gitignore\n"); + cl_git_mkfile( + "empty_standard_repo/a/ignoreme", "I should be ignored\n"); + + refute_is_ignored("a/.gitignore"); + assert_is_ignored("a/ignoreme"); + + cl_git_pass(git_futils_mkdir_r( + "empty_standard_repo/b", 0775)); + cl_git_mkfile( + "empty_standard_repo/b/.gitignore", "*\n!.gitignore\n"); + cl_git_mkfile( + "empty_standard_repo/b/ignoreme", "I should be ignored\n"); + + refute_is_ignored("b/.gitignore"); + assert_is_ignored("b/ignoreme"); + + /* shouldn't have changed results from first couple either */ + refute_is_ignored("a/.gitignore"); + assert_is_ignored("a/ignoreme"); + + /* status should find the two ignore files and nothing else */ + + cl_git_pass(git_status_file(&status, g_repo, "a/.gitignore")); + cl_assert_equal_i(GIT_STATUS_WT_NEW, (int)status); + + cl_git_pass(git_status_file(&status, g_repo, "a/ignoreme")); + cl_assert_equal_i(GIT_STATUS_IGNORED, (int)status); + + cl_git_pass(git_status_file(&status, g_repo, "b/.gitignore")); + cl_assert_equal_i(GIT_STATUS_WT_NEW, (int)status); + + cl_git_pass(git_status_file(&status, g_repo, "b/ignoreme")); + cl_assert_equal_i(GIT_STATUS_IGNORED, (int)status); + + { + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *paths[] = { + "a/.gitignore", + "a/ignoreme", + "b/.gitignore", + "b/ignoreme", + }; + static const unsigned int statuses[] = { + GIT_STATUS_WT_NEW, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_NEW, + GIT_STATUS_IGNORED, + }; + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 4; + counts.expected_paths = paths; + counts.expected_statuses = statuses; + + opts.flags = GIT_STATUS_OPT_DEFAULTS; + + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + } +} + +static void add_one_to_index(const char *file) +{ + git_index *index; + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, file)); + git_index_free(index); +} + +/* Some further broken scenarios that have been reported */ +void test_ignore_status__more_breakage(void) +{ + static const char *test_files[] = { + "empty_standard_repo/d1/pfx-d2/d3/d4/d5/tracked", + "empty_standard_repo/d1/pfx-d2/d3/d4/d5/untracked", + "empty_standard_repo/d1/pfx-d2/d3/d4/untracked", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "/d1/pfx-*\n" + "!/d1/pfx-d2/\n" + "/d1/pfx-d2/*\n" + "!/d1/pfx-d2/d3/\n" + "/d1/pfx-d2/d3/*\n" + "!/d1/pfx-d2/d3/d4/\n"); + add_one_to_index("d1/pfx-d2/d3/d4/d5/tracked"); + + { + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *files[] = { + ".gitignore", + "d1/pfx-d2/d3/d4/d5/tracked", + "d1/pfx-d2/d3/d4/d5/untracked", + "d1/pfx-d2/d3/d4/untracked", + }; + static const unsigned int statuses[] = { + GIT_STATUS_WT_NEW, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + }; + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 4; + counts.expected_paths = files; + counts.expected_statuses = statuses; + opts.flags = GIT_STATUS_OPT_DEFAULTS | + GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + } + + refute_is_ignored("d1/pfx-d2/d3/d4/d5/tracked"); + refute_is_ignored("d1/pfx-d2/d3/d4/d5/untracked"); + refute_is_ignored("d1/pfx-d2/d3/d4/untracked"); +} + +void test_ignore_status__negative_ignores_inside_ignores(void) +{ + static const char *test_files[] = { + "empty_standard_repo/top/mid/btm/tracked", + "empty_standard_repo/top/mid/btm/untracked", + "empty_standard_repo/zoo/bar", + "empty_standard_repo/zoo/foo/bar", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "top\n" + "!top/mid/btm\n" + "zoo/*\n" + "!zoo/bar\n" + "!zoo/foo/bar\n"); + add_one_to_index("top/mid/btm/tracked"); + + { + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *files[] = { + ".gitignore", "top/mid/btm/tracked", "top/mid/btm/untracked", + "zoo/bar", "zoo/foo/bar", + }; + static const unsigned int statuses[] = { + GIT_STATUS_WT_NEW, GIT_STATUS_INDEX_NEW, GIT_STATUS_IGNORED, + GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, + }; + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 5; + counts.expected_paths = files; + counts.expected_statuses = statuses; + opts.flags = GIT_STATUS_OPT_DEFAULTS | + GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + } + + assert_is_ignored("top/mid/btm/tracked"); + assert_is_ignored("top/mid/btm/untracked"); + refute_is_ignored("foo/bar"); +} + +void test_ignore_status__negative_ignores_in_slash_star(void) +{ + git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; + git_status_list *list; + int found_look_ma = 0, found_what_about = 0; + size_t i; + static const char *test_files[] = { + "empty_standard_repo/bin/look-ma.txt", + "empty_standard_repo/bin/what-about-me.txt", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "bin/*\n" + "!bin/w*\n"); + + assert_is_ignored("bin/look-ma.txt"); + refute_is_ignored("bin/what-about-me.txt"); + + status_opts.flags = GIT_STATUS_OPT_DEFAULTS; + cl_git_pass(git_status_list_new(&list, g_repo, &status_opts)); + for (i = 0; i < git_status_list_entrycount(list); i++) { + const git_status_entry *entry = git_status_byindex(list, i); + + if (!strcmp("bin/look-ma.txt", entry->index_to_workdir->new_file.path)) + found_look_ma = 1; + + if (!strcmp("bin/what-about-me.txt", entry->index_to_workdir->new_file.path)) + found_what_about = 1; + } + git_status_list_free(list); + + cl_assert(found_look_ma); + cl_assert(found_what_about); +} + +void test_ignore_status__negative_ignores_without_trailing_slash_inside_ignores(void) +{ + git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; + git_status_list *list; + int found_parent_file = 0, found_parent_child1_file = 0, found_parent_child2_file = 0; + size_t i; + static const char *test_files[] = { + "empty_standard_repo/parent/file.txt", + "empty_standard_repo/parent/force.txt", + "empty_standard_repo/parent/child1/file.txt", + "empty_standard_repo/parent/child2/file.txt", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "parent/*\n" + "!parent/force.txt\n" + "!parent/child1\n" + "!parent/child2/\n"); + + add_one_to_index("parent/force.txt"); + + assert_is_ignored("parent/file.txt"); + refute_is_ignored("parent/force.txt"); + refute_is_ignored("parent/child1/file.txt"); + refute_is_ignored("parent/child2/file.txt"); + + status_opts.flags = GIT_STATUS_OPT_DEFAULTS; + cl_git_pass(git_status_list_new(&list, g_repo, &status_opts)); + for (i = 0; i < git_status_list_entrycount(list); i++) { + const git_status_entry *entry = git_status_byindex(list, i); + + if (!entry->index_to_workdir) + continue; + + if (!strcmp("parent/file.txt", entry->index_to_workdir->new_file.path)) + found_parent_file = 1; + + if (!strcmp("parent/force.txt", entry->index_to_workdir->new_file.path)) + found_parent_file = 1; + + if (!strcmp("parent/child1/file.txt", entry->index_to_workdir->new_file.path)) + found_parent_child1_file = 1; + + if (!strcmp("parent/child2/file.txt", entry->index_to_workdir->new_file.path)) + found_parent_child2_file = 1; + } + git_status_list_free(list); + + cl_assert(found_parent_file); + cl_assert(found_parent_child1_file); + cl_assert(found_parent_child2_file); +} + +void test_ignore_status__negative_directory_ignores(void) +{ + static const char *test_files[] = { + "empty_standard_repo/parent/child1/bar.txt", + "empty_standard_repo/parent/child2/bar.txt", + "empty_standard_repo/parent/child3/foo.txt", + "empty_standard_repo/parent/child4/bar.txt", + "empty_standard_repo/parent/nested/child5/bar.txt", + "empty_standard_repo/parent/nested/child6/bar.txt", + "empty_standard_repo/parent/nested/child7/bar.txt", + "empty_standard_repo/padded_parent/child8/bar.txt", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "foo.txt\n" + "parent/child1\n" + "parent/child2\n" + "parent/child4\n" + "parent/nested/child5\n" + "nested/child6\n" + "nested/child7\n" + "padded_parent/child8\n" + /* test simple exact match */ + "!parent/child1\n" + /* test negating file without negating dir */ + "!parent/child2/bar.txt\n" + /* test negative pattern on dir with its content + * being ignored */ + "!parent/child3\n" + /* test with partial match at end */ + "!child4\n" + /* test with partial match with '/' at end */ + "!nested/child5\n" + /* test with complete match */ + "!nested/child6\n" + /* test with trailing '/' */ + "!child7/\n" + /* test with partial dir match */ + "!_parent/child8\n"); + + refute_is_ignored("parent/child1/bar.txt"); + assert_is_ignored("parent/child2/bar.txt"); + assert_is_ignored("parent/child3/foo.txt"); + refute_is_ignored("parent/child4/bar.txt"); + assert_is_ignored("parent/nested/child5/bar.txt"); + refute_is_ignored("parent/nested/child6/bar.txt"); + refute_is_ignored("parent/nested/child7/bar.txt"); + assert_is_ignored("padded_parent/child8/bar.txt"); +} + +void test_ignore_status__unignore_entry_in_ignored_dir(void) +{ + static const char *test_files[] = { + "empty_standard_repo/bar.txt", + "empty_standard_repo/parent/bar.txt", + "empty_standard_repo/parent/child/bar.txt", + "empty_standard_repo/nested/parent/child/bar.txt", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "bar.txt\n" + "!parent/child/bar.txt\n"); + + assert_is_ignored("bar.txt"); + assert_is_ignored("parent/bar.txt"); + refute_is_ignored("parent/child/bar.txt"); + assert_is_ignored("nested/parent/child/bar.txt"); +} + +void test_ignore_status__do_not_unignore_basename_prefix(void) +{ + static const char *test_files[] = { + "empty_standard_repo/foo_bar.txt", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "foo_bar.txt\n" + "!bar.txt\n"); + + assert_is_ignored("foo_bar.txt"); +} + +void test_ignore_status__filename_with_cr(void) +{ + int ignored; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_mkfile("empty_standard_repo/.gitignore", "Icon\r\r\n"); + + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Icon\r")); + cl_assert_equal_i(1, ignored); + + cl_git_mkfile("empty_standard_repo/.gitignore", "Ico\rn\n"); + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Ico\rn")); + cl_assert_equal_i(1, ignored); + + cl_git_mkfile("empty_standard_repo/.gitignore", "Ico\rn\r\n"); + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Ico\rn")); + cl_assert_equal_i(1, ignored); + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Ico\rn\r")); + cl_assert_equal_i(0, ignored); + + cl_git_mkfile("empty_standard_repo/.gitignore", "Ico\rn\r\r\n"); + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Ico\rn\r")); + cl_assert_equal_i(1, ignored); + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Icon\r")); + cl_assert_equal_i(0, ignored); + + cl_git_mkfile("empty_standard_repo/.gitignore", "Icon\r\n"); + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Icon\r")); + cl_assert_equal_i(0, ignored); + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "Icon")); + cl_assert_equal_i(1, ignored); +} + +void test_ignore_status__subdir_doesnt_match_above(void) +{ + int ignored, icase = 0, error; + git_config *cfg; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); + error = git_config_get_bool(&icase, cfg, "core.ignorecase"); + git_config_free(cfg); + if (error == GIT_ENOTFOUND) + error = 0; + + cl_git_pass(error); + + cl_git_pass(p_mkdir("empty_standard_repo/src", 0777)); + cl_git_pass(p_mkdir("empty_standard_repo/src/src", 0777)); + cl_git_mkfile("empty_standard_repo/src/.gitignore", "src\n"); + cl_git_mkfile("empty_standard_repo/.gitignore", ""); + + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "src/test.txt")); + cl_assert_equal_i(0, ignored); + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "src/src/test.txt")); + cl_assert_equal_i(1, ignored); + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "src/foo/test.txt")); + cl_assert_equal_i(0, ignored); + + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "SRC/src/test.txt")); + cl_assert_equal_i(icase, ignored); + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "src/SRC/test.txt")); + cl_assert_equal_i(icase, ignored); +} + +void test_ignore_status__negate_exact_previous(void) +{ + int ignored; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile("empty_standard_repo/.gitignore", "*.com\ntags\n!tags/\n.buildpath"); + cl_git_mkfile("empty_standard_repo/.buildpath", ""); + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, ".buildpath")); + cl_assert_equal_i(1, ignored); +} + +void test_ignore_status__negate_starstar(void) +{ + int ignored; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile("empty_standard_repo/.gitignore", + "code/projects/**/packages/*\n" + "!code/projects/**/packages/repositories.config"); + + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/code/projects/foo/bar/packages", 0777)); + cl_git_mkfile("empty_standard_repo/code/projects/foo/bar/packages/repositories.config", ""); + + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "code/projects/foo/bar/packages/repositories.config")); + cl_assert_equal_i(0, ignored); +} + +void test_ignore_status__ignore_all_toplevel_dirs_include_files(void) +{ + static const char *test_files[] = { + "empty_standard_repo/README.md", + "empty_standard_repo/src/main.c", + "empty_standard_repo/src/foo/foo.c", + "empty_standard_repo/dist/foo.o", + "empty_standard_repo/dist/main.o", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "/*/\n" + "!/src\n"); + + assert_is_ignored("dist/foo.o"); + assert_is_ignored("dist/main.o"); + + refute_is_ignored("README.md"); + refute_is_ignored("src/foo.c"); + refute_is_ignored("src/foo/foo.c"); +} + +void test_ignore_status__subdir_ignore_all_toplevel_dirs_include_files(void) +{ + static const char *test_files[] = { + "empty_standard_repo/project/README.md", + "empty_standard_repo/project/src/main.c", + "empty_standard_repo/project/src/foo/foo.c", + "empty_standard_repo/project/dist/foo.o", + "empty_standard_repo/project/dist/main.o", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/project/.gitignore", + "/*/\n" + "!/src\n"); + + assert_is_ignored("project/dist/foo.o"); + assert_is_ignored("project/dist/main.o"); + + refute_is_ignored("project/src/foo.c"); + refute_is_ignored("project/src/foo/foo.c"); + refute_is_ignored("project/README.md"); +} + +void test_ignore_status__subdir_ignore_everything_except_certain_files(void) +{ + static const char *test_files[] = { + "empty_standard_repo/project/README.md", + "empty_standard_repo/project/some_file", + "empty_standard_repo/project/src/main.c", + "empty_standard_repo/project/src/foo/foo.c", + "empty_standard_repo/project/dist/foo.o", + "empty_standard_repo/project/dist/main.o", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/project/.gitignore", + "/*\n" + "!/src\n" + "!README.md\n"); + + assert_is_ignored("project/some_file"); + assert_is_ignored("project/dist/foo.o"); + assert_is_ignored("project/dist/main.o"); + + refute_is_ignored("project/README.md"); + refute_is_ignored("project/src/foo.c"); + refute_is_ignored("project/src/foo/foo.c"); +} + +void test_ignore_status__deeper(void) +{ + const char *test_files[] = { + "empty_standard_repo/foo.data", + "empty_standard_repo/bar.data", + "empty_standard_repo/dont_ignore/foo.data", + "empty_standard_repo/dont_ignore/bar.data", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile("empty_standard_repo/.gitignore", + "*.data\n" + "!dont_ignore/*.data\n"); + + assert_is_ignored("foo.data"); + assert_is_ignored("bar.data"); + + refute_is_ignored("dont_ignore/foo.data"); + refute_is_ignored("dont_ignore/bar.data"); +} + +void test_ignore_status__unignored_dir_with_ignored_contents(void) +{ + static const char *test_files[] = { + "empty_standard_repo/dir/a.test", + "empty_standard_repo/dir/subdir/a.test", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "*.test\n" + "!dir/*\n"); + + refute_is_ignored("dir/a.test"); + assert_is_ignored("dir/subdir/a.test"); +} + +void test_ignore_status__unignored_subdirs(void) +{ + static const char *test_files[] = { + "empty_standard_repo/dir/a.test", + "empty_standard_repo/dir/subdir/a.test", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "dir/*\n" + "!dir/*/\n"); + + assert_is_ignored("dir/a.test"); + refute_is_ignored("dir/subdir/a.test"); +} + +void test_ignore_status__skips_bom(void) +{ + static const char *test_files[] = { + "empty_standard_repo/a.test", + "empty_standard_repo/b.test", + "empty_standard_repo/c.test", + "empty_standard_repo/foo.txt", + "empty_standard_repo/bar.txt", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "\xEF\xBB\xBF*.test\n"); + + assert_is_ignored("a.test"); + assert_is_ignored("b.test"); + assert_is_ignored("c.test"); + refute_is_ignored("foo.txt"); + refute_is_ignored("bar.txt"); +} + +void test_ignore_status__leading_spaces_are_significant(void) +{ + static const char *test_files[] = { + "empty_standard_repo/a.test", + "empty_standard_repo/b.test", + "empty_standard_repo/c.test", + "empty_standard_repo/d.test", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + " a.test\n" + "# this is a comment\n" + "b.test\n" + "\tc.test\n" + " # not a comment\n" + "d.test\n"); + + refute_is_ignored("a.test"); + assert_is_ignored(" a.test"); + refute_is_ignored("# this is a comment"); + assert_is_ignored("b.test"); + refute_is_ignored("c.test"); + assert_is_ignored("\tc.test"); + assert_is_ignored(" # not a comment"); + assert_is_ignored("d.test"); +} + +void test_ignore_status__override_nested_wildcard_unignore(void) +{ + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + const git_status_entry *status; + + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/dir", 0777)); + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/dir/subdir", 0777)); + cl_git_mkfile("empty_standard_repo/.gitignore", "a.test\n"); + cl_git_mkfile("empty_standard_repo/dir/.gitignore", "!*.test\n"); + cl_git_mkfile("empty_standard_repo/dir/subdir/.gitignore", "a.test\n"); + cl_git_mkfile("empty_standard_repo/dir/a.test", "pong"); + cl_git_mkfile("empty_standard_repo/dir/subdir/a.test", "pong"); + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + cl_git_pass(git_status_list_new(&statuslist, repo, &opts)); + cl_assert_equal_sz(4, git_status_list_entrycount(statuslist)); + + status = git_status_byindex(statuslist, 0); + cl_assert(status != NULL); + cl_assert_equal_s(".gitignore", status->index_to_workdir->old_file.path); + cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status); + + status = git_status_byindex(statuslist, 1); + cl_assert(status != NULL); + cl_assert_equal_s("dir/.gitignore", status->index_to_workdir->old_file.path); + cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status); + + status = git_status_byindex(statuslist, 2); + cl_assert(status != NULL); + cl_assert_equal_s("dir/a.test", status->index_to_workdir->old_file.path); + cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status); + + status = git_status_byindex(statuslist, 3); + cl_assert(status != NULL); + cl_assert_equal_s("dir/subdir/.gitignore", status->index_to_workdir->old_file.path); + cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status); + + git_status_list_free(statuslist); +} diff --git a/tests/libgit2/index/add.c b/tests/libgit2/index/add.c new file mode 100644 index 000000000..f101ea266 --- /dev/null +++ b/tests/libgit2/index/add.c @@ -0,0 +1,84 @@ +#include "clar_libgit2.h" + +static git_repository *g_repo = NULL; +static git_index *g_index = NULL; + +static const char *valid_blob_id = "fa49b077972391ad58037050f2a75f74e3671e92"; +static const char *valid_tree_id = "181037049a54a1eb5fab404658a3a250b44335d7"; +static const char *valid_commit_id = "763d71aadf09a7951596c9746c024e7eece7c7af"; +static const char *invalid_id = "1234567890123456789012345678901234567890"; + +void test_index_add__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_index(&g_index, g_repo)); +} + +void test_index_add__cleanup(void) +{ + git_index_free(g_index); + cl_git_sandbox_cleanup(); + g_repo = NULL; + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 1)); +} + +static void test_add_entry( + bool should_succeed, const char *idstr, git_filemode_t mode) +{ + git_index_entry entry = {{0}}; + + cl_git_pass(git_oid_fromstr(&entry.id, idstr)); + + entry.path = mode == GIT_FILEMODE_TREE ? "test_folder" : "test_file"; + entry.mode = mode; + + if (should_succeed) + cl_git_pass(git_index_add(g_index, &entry)); + else + cl_git_fail(git_index_add(g_index, &entry)); +} + +void test_index_add__invalid_entries_succeeds_by_default(void) +{ + /* + * Ensure that there is validation on object ids by default + */ + + /* ensure that we can add some actually good entries */ + test_add_entry(true, valid_blob_id, GIT_FILEMODE_BLOB); + test_add_entry(true, valid_blob_id, GIT_FILEMODE_BLOB_EXECUTABLE); + test_add_entry(true, valid_blob_id, GIT_FILEMODE_LINK); + + /* test that we fail to add some invalid (missing) blobs and trees */ + test_add_entry(false, invalid_id, GIT_FILEMODE_BLOB); + test_add_entry(false, invalid_id, GIT_FILEMODE_BLOB_EXECUTABLE); + test_add_entry(false, invalid_id, GIT_FILEMODE_LINK); + + /* test that we validate the types of objects */ + test_add_entry(false, valid_commit_id, GIT_FILEMODE_BLOB); + test_add_entry(false, valid_tree_id, GIT_FILEMODE_BLOB_EXECUTABLE); + test_add_entry(false, valid_commit_id, GIT_FILEMODE_LINK); + + /* + * Ensure that there we can disable validation + */ + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 0)); + + /* ensure that we can add some actually good entries */ + test_add_entry(true, valid_blob_id, GIT_FILEMODE_BLOB); + test_add_entry(true, valid_blob_id, GIT_FILEMODE_BLOB_EXECUTABLE); + test_add_entry(true, valid_blob_id, GIT_FILEMODE_LINK); + + /* test that we can now add some invalid (missing) blobs and trees */ + test_add_entry(true, invalid_id, GIT_FILEMODE_BLOB); + test_add_entry(true, invalid_id, GIT_FILEMODE_BLOB_EXECUTABLE); + test_add_entry(true, invalid_id, GIT_FILEMODE_LINK); + + /* test that we do not validate the types of objects */ + test_add_entry(true, valid_commit_id, GIT_FILEMODE_BLOB); + test_add_entry(true, valid_tree_id, GIT_FILEMODE_BLOB_EXECUTABLE); + test_add_entry(true, valid_commit_id, GIT_FILEMODE_LINK); +} + diff --git a/tests/libgit2/index/addall.c b/tests/libgit2/index/addall.c new file mode 100644 index 000000000..6f95f6386 --- /dev/null +++ b/tests/libgit2/index/addall.c @@ -0,0 +1,496 @@ +#include "clar_libgit2.h" +#include "../status/status_helpers.h" +#include "posix.h" +#include "futils.h" + +static git_repository *g_repo = NULL; +#define TEST_DIR "addall" + +void test_index_addall__initialize(void) +{ +} + +void test_index_addall__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +#define STATUS_INDEX_FLAGS \ + (GIT_STATUS_INDEX_NEW | GIT_STATUS_INDEX_MODIFIED | \ + GIT_STATUS_INDEX_DELETED | GIT_STATUS_INDEX_RENAMED | \ + GIT_STATUS_INDEX_TYPECHANGE) + +#define STATUS_WT_FLAGS \ + (GIT_STATUS_WT_NEW | GIT_STATUS_WT_MODIFIED | \ + GIT_STATUS_WT_DELETED | GIT_STATUS_WT_TYPECHANGE | \ + GIT_STATUS_WT_RENAMED) + +typedef struct { + size_t index_adds; + size_t index_dels; + size_t index_mods; + size_t wt_adds; + size_t wt_dels; + size_t wt_mods; + size_t ignores; + size_t conflicts; +} index_status_counts; + +static int index_status_cb( + const char *path, unsigned int status_flags, void *payload) +{ + index_status_counts *vals = payload; + + /* cb_status__print(path, status_flags, NULL); */ + + GIT_UNUSED(path); + + if (status_flags & GIT_STATUS_INDEX_NEW) + vals->index_adds++; + if (status_flags & GIT_STATUS_INDEX_MODIFIED) + vals->index_mods++; + if (status_flags & GIT_STATUS_INDEX_DELETED) + vals->index_dels++; + if (status_flags & GIT_STATUS_INDEX_TYPECHANGE) + vals->index_mods++; + + if (status_flags & GIT_STATUS_WT_NEW) + vals->wt_adds++; + if (status_flags & GIT_STATUS_WT_MODIFIED) + vals->wt_mods++; + if (status_flags & GIT_STATUS_WT_DELETED) + vals->wt_dels++; + if (status_flags & GIT_STATUS_WT_TYPECHANGE) + vals->wt_mods++; + + if (status_flags & GIT_STATUS_IGNORED) + vals->ignores++; + if (status_flags & GIT_STATUS_CONFLICTED) + vals->conflicts++; + + return 0; +} + +static void check_status_at_line( + git_repository *repo, + size_t index_adds, size_t index_dels, size_t index_mods, + size_t wt_adds, size_t wt_dels, size_t wt_mods, size_t ignores, + size_t conflicts, const char *file, const char *func, int line) +{ + index_status_counts vals; + + memset(&vals, 0, sizeof(vals)); + + cl_git_pass(git_status_foreach(repo, index_status_cb, &vals)); + + clar__assert_equal( + file,func,line,"wrong index adds", 1, "%"PRIuZ, index_adds, vals.index_adds); + clar__assert_equal( + file,func,line,"wrong index dels", 1, "%"PRIuZ, index_dels, vals.index_dels); + clar__assert_equal( + file,func,line,"wrong index mods", 1, "%"PRIuZ, index_mods, vals.index_mods); + clar__assert_equal( + file,func,line,"wrong workdir adds", 1, "%"PRIuZ, wt_adds, vals.wt_adds); + clar__assert_equal( + file,func,line,"wrong workdir dels", 1, "%"PRIuZ, wt_dels, vals.wt_dels); + clar__assert_equal( + file,func,line,"wrong workdir mods", 1, "%"PRIuZ, wt_mods, vals.wt_mods); + clar__assert_equal( + file,func,line,"wrong ignores", 1, "%"PRIuZ, ignores, vals.ignores); + clar__assert_equal( + file,func,line,"wrong conflicts", 1, "%"PRIuZ, conflicts, vals.conflicts); +} + +#define check_status(R,IA,ID,IM,WA,WD,WM,IG,C) \ + check_status_at_line(R,IA,ID,IM,WA,WD,WM,IG,C,__FILE__,__func__,__LINE__) + +static void check_stat_data(git_index *index, const char *path, bool match) +{ + const git_index_entry *entry; + struct stat st; + + cl_must_pass(p_lstat(path, &st)); + + /* skip repo base dir name */ + while (*path != '/') + ++path; + ++path; + + entry = git_index_get_bypath(index, path, 0); + cl_assert(entry); + + if (match) { + cl_assert(st.st_ctime == entry->ctime.seconds); + cl_assert(st.st_mtime == entry->mtime.seconds); + cl_assert(st.st_size == entry->file_size); + cl_assert((uint32_t)st.st_uid == entry->uid); + cl_assert((uint32_t)st.st_gid == entry->gid); + cl_assert_equal_i_fmt( + GIT_MODE_TYPE(st.st_mode), GIT_MODE_TYPE(entry->mode), "%07o"); + if (cl_is_chmod_supported()) + cl_assert_equal_b( + GIT_PERMS_IS_EXEC(st.st_mode), GIT_PERMS_IS_EXEC(entry->mode)); + } else { + /* most things will still match */ + cl_assert(st.st_size != entry->file_size); + /* would check mtime, but with second resolution it won't work :( */ + } +} + +static void addall_create_test_repo(bool check_every_step) +{ + g_repo = cl_git_sandbox_init_new(TEST_DIR); + + if (check_every_step) + check_status(g_repo, 0, 0, 0, 0, 0, 0, 0, 0); + + cl_git_mkfile(TEST_DIR "/file.foo", "a file"); + if (check_every_step) + check_status(g_repo, 0, 0, 0, 1, 0, 0, 0, 0); + + cl_git_mkfile(TEST_DIR "/.gitignore", "*.foo\n"); + if (check_every_step) + check_status(g_repo, 0, 0, 0, 1, 0, 0, 1, 0); + + cl_git_mkfile(TEST_DIR "/file.bar", "another file"); + if (check_every_step) + check_status(g_repo, 0, 0, 0, 2, 0, 0, 1, 0); +} + +void test_index_addall__repo_lifecycle(void) +{ + int error; + git_index *index; + git_strarray paths = { NULL, 0 }; + char *strs[1]; + + addall_create_test_repo(true); + + cl_git_pass(git_repository_index(&index, g_repo)); + + strs[0] = "file.*"; + paths.strings = strs; + paths.count = 1; + + cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); + cl_git_pass(git_index_write(index)); + check_stat_data(index, TEST_DIR "/file.bar", true); + check_status(g_repo, 1, 0, 0, 1, 0, 0, 1, 0); + + cl_git_rewritefile(TEST_DIR "/file.bar", "new content for file"); + check_stat_data(index, TEST_DIR "/file.bar", false); + check_status(g_repo, 1, 0, 0, 1, 0, 1, 1, 0); + + cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one"); + cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one"); + cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one"); + check_status(g_repo, 1, 0, 0, 4, 0, 1, 1, 0); + + cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); + check_stat_data(index, TEST_DIR "/file.bar", true); + check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0); + + cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); + cl_git_pass(git_index_write(index)); + check_stat_data(index, TEST_DIR "/file.zzz", true); + check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); + + cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "first commit"); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); + + if (cl_repo_get_bool(g_repo, "core.filemode")) { + cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); + cl_must_pass(p_chmod(TEST_DIR "/file.zzz", 0777)); + cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); + check_status(g_repo, 0, 0, 1, 3, 0, 0, 1, 0); + + /* go back to what we had before */ + cl_must_pass(p_chmod(TEST_DIR "/file.zzz", 0666)); + cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); + } + + + /* attempt to add an ignored file - does nothing */ + strs[0] = "file.foo"; + cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); + cl_git_pass(git_index_write(index)); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); + + /* add with check - should generate error */ + error = git_index_add_all( + index, &paths, GIT_INDEX_ADD_CHECK_PATHSPEC, NULL, NULL); + cl_assert_equal_i(GIT_EINVALIDSPEC, error); + cl_git_pass(git_index_write(index)); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); + + /* add with force - should allow */ + cl_git_pass(git_index_add_all( + index, &paths, GIT_INDEX_ADD_FORCE, NULL, NULL)); + cl_git_pass(git_index_write(index)); + check_stat_data(index, TEST_DIR "/file.foo", true); + check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0); + + /* now it's in the index, so regular add should work */ + cl_git_rewritefile(TEST_DIR "/file.foo", "new content for file"); + check_stat_data(index, TEST_DIR "/file.foo", false); + check_status(g_repo, 1, 0, 0, 3, 0, 1, 0, 0); + + cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); + cl_git_pass(git_index_write(index)); + check_stat_data(index, TEST_DIR "/file.foo", true); + check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0); + + cl_git_pass(git_index_add_bypath(index, "more.zzz")); + check_stat_data(index, TEST_DIR "/more.zzz", true); + check_status(g_repo, 2, 0, 0, 2, 0, 0, 0, 0); + + cl_git_rewritefile(TEST_DIR "/file.zzz", "new content for file"); + check_status(g_repo, 2, 0, 0, 2, 0, 1, 0, 0); + + cl_git_pass(git_index_add_bypath(index, "file.zzz")); + check_stat_data(index, TEST_DIR "/file.zzz", true); + check_status(g_repo, 2, 0, 1, 2, 0, 0, 0, 0); + + strs[0] = "*.zzz"; + cl_git_pass(git_index_remove_all(index, &paths, NULL, NULL)); + check_status(g_repo, 1, 1, 0, 4, 0, 0, 0, 0); + + cl_git_pass(git_index_add_bypath(index, "file.zzz")); + check_status(g_repo, 1, 0, 1, 3, 0, 0, 0, 0); + + cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "second commit"); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 0, 0); + + cl_must_pass(p_unlink(TEST_DIR "/file.zzz")); + check_status(g_repo, 0, 0, 0, 3, 1, 0, 0, 0); + + /* update_all should be able to remove entries */ + cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); + check_status(g_repo, 0, 1, 0, 3, 0, 0, 0, 0); + + strs[0] = "*"; + cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); + cl_git_pass(git_index_write(index)); + check_status(g_repo, 3, 1, 0, 0, 0, 0, 0, 0); + + /* must be able to remove at any position while still updating other files */ + cl_must_pass(p_unlink(TEST_DIR "/.gitignore")); + cl_git_rewritefile(TEST_DIR "/file.zzz", "reconstructed file"); + cl_git_rewritefile(TEST_DIR "/more.zzz", "altered file reality"); + check_status(g_repo, 3, 1, 0, 1, 1, 1, 0, 0); + + cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); + check_status(g_repo, 2, 1, 0, 1, 0, 0, 0, 0); + /* this behavior actually matches 'git add -u' where "file.zzz" has + * been removed from the index, so when you go to update, even though + * it exists in the HEAD, it is not re-added to the index, leaving it + * as a DELETE when comparing HEAD to index and as an ADD comparing + * index to worktree + */ + + git_index_free(index); +} + +void test_index_addall__files_in_folders(void) +{ + git_index *index; + + addall_create_test_repo(true); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); + cl_git_pass(git_index_write(index)); + check_stat_data(index, TEST_DIR "/file.bar", true); + check_status(g_repo, 2, 0, 0, 0, 0, 0, 1, 0); + + cl_must_pass(p_mkdir(TEST_DIR "/subdir", 0777)); + cl_git_mkfile(TEST_DIR "/subdir/file", "hello!\n"); + check_status(g_repo, 2, 0, 0, 1, 0, 0, 1, 0); + + cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); + cl_git_pass(git_index_write(index)); + check_status(g_repo, 3, 0, 0, 0, 0, 0, 1, 0); + + git_index_free(index); +} + +void test_index_addall__hidden_files(void) +{ +#ifdef GIT_WIN32 + git_index *index; + + addall_create_test_repo(true); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); + cl_git_pass(git_index_write(index)); + check_stat_data(index, TEST_DIR "/file.bar", true); + check_status(g_repo, 2, 0, 0, 0, 0, 0, 1, 0); + + cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one"); + cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one"); + cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one"); + + check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); + + cl_git_pass(git_win32__set_hidden(TEST_DIR "/file.zzz", true)); + cl_git_pass(git_win32__set_hidden(TEST_DIR "/more.zzz", true)); + cl_git_pass(git_win32__set_hidden(TEST_DIR "/other.zzz", true)); + + check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); + + cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); + cl_git_pass(git_index_write(index)); + check_stat_data(index, TEST_DIR "/file.bar", true); + check_status(g_repo, 5, 0, 0, 0, 0, 0, 1, 0); + + git_index_free(index); +#endif +} + +static int addall_match_prefix( + const char *path, const char *matched_pathspec, void *payload) +{ + GIT_UNUSED(matched_pathspec); + return !git__prefixcmp(path, payload) ? 0 : 1; +} + +static int addall_match_suffix( + const char *path, const char *matched_pathspec, void *payload) +{ + GIT_UNUSED(matched_pathspec); + return !git__suffixcmp(path, payload) ? 0 : 1; +} + +static int addall_cancel_at( + const char *path, const char *matched_pathspec, void *payload) +{ + GIT_UNUSED(matched_pathspec); + return !strcmp(path, payload) ? -123 : 0; +} + +void test_index_addall__callback_filtering(void) +{ + git_index *index; + + addall_create_test_repo(false); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass( + git_index_add_all(index, NULL, 0, addall_match_prefix, "file.")); + cl_git_pass(git_index_write(index)); + check_stat_data(index, TEST_DIR "/file.bar", true); + check_status(g_repo, 1, 0, 0, 1, 0, 0, 1, 0); + + cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one"); + cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one"); + cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one"); + + cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); + check_stat_data(index, TEST_DIR "/file.bar", true); + check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0); + + cl_git_pass( + git_index_add_all(index, NULL, 0, addall_match_prefix, "other")); + cl_git_pass(git_index_write(index)); + check_stat_data(index, TEST_DIR "/other.zzz", true); + check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); + + cl_git_pass( + git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz")); + cl_git_pass(git_index_write(index)); + check_status(g_repo, 4, 0, 0, 1, 0, 0, 1, 0); + + cl_git_pass( + git_index_remove_all(index, NULL, addall_match_suffix, ".zzz")); + check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0); + + cl_git_fail_with( + git_index_add_all(index, NULL, 0, addall_cancel_at, "more.zzz"), -123); + check_status(g_repo, 3, 0, 0, 2, 0, 0, 1, 0); + + cl_git_fail_with( + git_index_add_all(index, NULL, 0, addall_cancel_at, "other.zzz"), -123); + check_status(g_repo, 4, 0, 0, 1, 0, 0, 1, 0); + + cl_git_pass( + git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz")); + cl_git_pass(git_index_write(index)); + check_status(g_repo, 5, 0, 0, 0, 0, 0, 1, 0); + + cl_must_pass(p_unlink(TEST_DIR "/file.zzz")); + cl_must_pass(p_unlink(TEST_DIR "/more.zzz")); + cl_must_pass(p_unlink(TEST_DIR "/other.zzz")); + + cl_git_fail_with( + git_index_update_all(index, NULL, addall_cancel_at, "more.zzz"), -123); + /* file.zzz removed from index (so Index Adds 5 -> 4) and + * more.zzz + other.zzz removed (so Worktree Dels 0 -> 2) */ + check_status(g_repo, 4, 0, 0, 0, 2, 0, 1, 0); + + cl_git_fail_with( + git_index_update_all(index, NULL, addall_cancel_at, "other.zzz"), -123); + /* more.zzz removed from index (so Index Adds 4 -> 3) and + * Just other.zzz removed (so Worktree Dels 2 -> 1) */ + check_status(g_repo, 3, 0, 0, 0, 1, 0, 1, 0); + + git_index_free(index); +} + +void test_index_addall__adds_conflicts(void) +{ + git_index *index; + git_reference *ref; + git_annotated_commit *annotated; + + g_repo = cl_git_sandbox_init("merge-resolve"); + cl_git_pass(git_repository_index(&index, g_repo)); + + check_status(g_repo, 0, 0, 0, 0, 0, 0, 0, 0); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/branch")); + cl_git_pass(git_annotated_commit_from_ref(&annotated, g_repo, ref)); + + cl_git_pass(git_merge(g_repo, (const git_annotated_commit**)&annotated, 1, NULL, NULL)); + check_status(g_repo, 0, 1, 2, 0, 0, 0, 0, 1); + + cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); + cl_git_pass(git_index_write(index)); + check_status(g_repo, 0, 1, 3, 0, 0, 0, 0, 0); + + git_annotated_commit_free(annotated); + git_reference_free(ref); + git_index_free(index); +} + +void test_index_addall__removes_deleted_conflicted_files(void) +{ + git_index *index; + git_reference *ref; + git_annotated_commit *annotated; + + g_repo = cl_git_sandbox_init("merge-resolve"); + cl_git_pass(git_repository_index(&index, g_repo)); + + check_status(g_repo, 0, 0, 0, 0, 0, 0, 0, 0); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/branch")); + cl_git_pass(git_annotated_commit_from_ref(&annotated, g_repo, ref)); + + cl_git_pass(git_merge(g_repo, (const git_annotated_commit**)&annotated, 1, NULL, NULL)); + check_status(g_repo, 0, 1, 2, 0, 0, 0, 0, 1); + + cl_git_rmfile("merge-resolve/conflicting.txt"); + + cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); + cl_git_pass(git_index_write(index)); + check_status(g_repo, 0, 2, 2, 0, 0, 0, 0, 0); + + git_annotated_commit_free(annotated); + git_reference_free(ref); + git_index_free(index); +} diff --git a/tests/libgit2/index/bypath.c b/tests/libgit2/index/bypath.c new file mode 100644 index 000000000..b32a0a789 --- /dev/null +++ b/tests/libgit2/index/bypath.c @@ -0,0 +1,359 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "../submodule/submodule_helpers.h" + +static git_repository *g_repo; +static git_index *g_idx; + +void test_index_bypath__initialize(void) +{ + g_repo = setup_fixture_submod2(); + cl_git_pass(git_repository_index__weakptr(&g_idx, g_repo)); +} + +void test_index_bypath__cleanup(void) +{ + g_repo = NULL; + g_idx = NULL; +} + +void test_index_bypath__add_directory(void) +{ + cl_git_fail_with(GIT_EDIRECTORY, git_index_add_bypath(g_idx, "just_a_dir")); +} + +void test_index_bypath__add_submodule(void) +{ + unsigned int status; + const char *sm_name = "sm_changed_head"; + + cl_git_pass(git_submodule_status(&status, g_repo, sm_name, 0)); + cl_assert_equal_i(GIT_SUBMODULE_STATUS_WD_MODIFIED, status & GIT_SUBMODULE_STATUS_WD_MODIFIED); + cl_git_pass(git_index_add_bypath(g_idx, sm_name)); + cl_git_pass(git_submodule_status(&status, g_repo, sm_name, 0)); + cl_assert_equal_i(0, status & GIT_SUBMODULE_STATUS_WD_MODIFIED); +} + +void test_index_bypath__add_submodule_unregistered(void) +{ + const char *sm_name = "not-submodule"; + const char *sm_head = "68e92c611b80ee1ed8f38314ff9577f0d15b2444"; + const git_index_entry *entry; + + cl_git_pass(git_index_add_bypath(g_idx, sm_name)); + + cl_assert(entry = git_index_get_bypath(g_idx, sm_name, 0)); + cl_assert_equal_s(sm_head, git_oid_tostr_s(&entry->id)); + cl_assert_equal_s(sm_name, entry->path); +} + +void test_index_bypath__add_hidden(void) +{ +#ifdef GIT_WIN32 + const git_index_entry *entry; + bool hidden; + + cl_git_mkfile("submod2/hidden_file", "you can't see me"); + + cl_git_pass(git_win32__hidden(&hidden, "submod2/hidden_file")); + cl_assert(!hidden); + + cl_git_pass(git_win32__set_hidden("submod2/hidden_file", true)); + + cl_git_pass(git_win32__hidden(&hidden, "submod2/hidden_file")); + cl_assert(hidden); + + cl_git_pass(git_index_add_bypath(g_idx, "hidden_file")); + + cl_assert(entry = git_index_get_bypath(g_idx, "hidden_file", 0)); + cl_assert_equal_i(GIT_FILEMODE_BLOB, entry->mode); +#endif +} + +void test_index_bypath__add_keeps_existing_case(void) +{ + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file"); + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/file1.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/file1.txt", 0)); + cl_assert_equal_s("just_a_dir/file1.txt", entry->path); + + cl_git_rewritefile("submod2/just_a_dir/file1.txt", "Updated!"); + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/FILE1.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/FILE1.txt", 0)); + cl_assert_equal_s("just_a_dir/file1.txt", entry->path); +} + +void test_index_bypath__add_honors_existing_case(void) +{ + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file"); + cl_git_mkfile("submod2/just_a_dir/file2.txt", "This is another file"); + cl_git_mkfile("submod2/just_a_dir/file3.txt", "This is another file"); + cl_git_mkfile("submod2/just_a_dir/file4.txt", "And another file"); + + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/File1.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "JUST_A_DIR/file2.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/FILE3.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/File1.txt", 0)); + cl_assert_equal_s("just_a_dir/File1.txt", entry->path); + + cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/file2.txt", 0)); + cl_assert_equal_s("just_a_dir/file2.txt", entry->path); + + cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/FILE3.txt", 0)); + cl_assert_equal_s("just_a_dir/FILE3.txt", entry->path); + + cl_git_rewritefile("submod2/just_a_dir/file3.txt", "Rewritten"); + cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/file3.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/file3.txt", 0)); + cl_assert_equal_s("just_a_dir/FILE3.txt", entry->path); +} + +void test_index_bypath__add_honors_existing_case_2(void) +{ + git_index_entry dummy = { { 0 } }; + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + dummy.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&dummy.id, "f990a25a74d1a8281ce2ab018ea8df66795cd60b")); + + /* note that `git_index_add` does no checking to canonical directories */ + dummy.path = "Just_a_dir/file0.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "just_a_dir/fileA.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "Just_A_Dir/fileB.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "JUST_A_DIR/fileC.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "just_A_dir/fileD.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "JUST_a_DIR/fileE.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file"); + cl_git_mkfile("submod2/just_a_dir/file2.txt", "This is another file"); + cl_git_mkfile("submod2/just_a_dir/file3.txt", "This is another file"); + cl_git_mkfile("submod2/just_a_dir/file4.txt", "And another file"); + + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/File1.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "JUST_A_DIR/file2.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/FILE3.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "JusT_A_DIR/FILE4.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/File1.txt", 0)); + cl_assert_equal_s("just_a_dir/File1.txt", entry->path); + + cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/file2.txt", 0)); + cl_assert_equal_s("JUST_A_DIR/file2.txt", entry->path); + + cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/FILE3.txt", 0)); + cl_assert_equal_s("Just_A_Dir/FILE3.txt", entry->path); + + cl_git_rewritefile("submod2/just_a_dir/file3.txt", "Rewritten"); + cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/file3.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/file3.txt", 0)); + cl_assert_equal_s("Just_A_Dir/FILE3.txt", entry->path); +} + +void test_index_bypath__add_honors_existing_case_3(void) +{ + git_index_entry dummy = { { 0 } }; + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + dummy.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&dummy.id, "f990a25a74d1a8281ce2ab018ea8df66795cd60b")); + + dummy.path = "just_a_dir/filea.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "Just_A_Dir/fileB.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "just_A_DIR/FILEC.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "Just_a_DIR/FileD.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + cl_git_mkfile("submod2/JuSt_A_DiR/fILEE.txt", "This is a file"); + + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/fILEE.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/fILEE.txt", 0)); + cl_assert_equal_s("just_a_dir/fILEE.txt", entry->path); +} + +void test_index_bypath__add_honors_existing_case_4(void) +{ + git_index_entry dummy = { { 0 } }; + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + dummy.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&dummy.id, "f990a25a74d1a8281ce2ab018ea8df66795cd60b")); + + dummy.path = "just_a_dir/a/b/c/d/e/file1.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "just_a_dir/a/B/C/D/E/file2.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + cl_must_pass(p_mkdir("submod2/just_a_dir/a", 0777)); + cl_must_pass(p_mkdir("submod2/just_a_dir/a/b", 0777)); + cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z", 0777)); + cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z/y", 0777)); + cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z/y/x", 0777)); + + cl_git_mkfile("submod2/just_a_dir/a/b/z/y/x/FOO.txt", "This is a file"); + + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/A/b/Z/y/X/foo.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/A/b/Z/y/X/foo.txt", 0)); + cl_assert_equal_s("just_a_dir/a/b/Z/y/X/foo.txt", entry->path); +} + +void test_index_bypath__add_honors_mode(void) +{ + const git_index_entry *entry; + git_index_entry new_entry; + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + + memcpy(&new_entry, entry, sizeof(git_index_entry)); + new_entry.path = "README.txt"; + new_entry.mode = GIT_FILEMODE_BLOB_EXECUTABLE; + + cl_must_pass(p_chmod("submod2/README.txt", GIT_FILEMODE_BLOB_EXECUTABLE)); + + cl_git_pass(git_index_add(g_idx, &new_entry)); + cl_git_pass(git_index_write(g_idx)); + + cl_git_rewritefile("submod2/README.txt", "Modified but still executable"); + + cl_git_pass(git_index_add_bypath(g_idx, "README.txt")); + cl_git_pass(git_index_write(g_idx)); + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + cl_assert_equal_i(GIT_FILEMODE_BLOB_EXECUTABLE, entry->mode); +} + +void test_index_bypath__add_honors_conflict_mode(void) +{ + const git_index_entry *entry; + git_index_entry new_entry; + int stage = 0; + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + + memcpy(&new_entry, entry, sizeof(git_index_entry)); + new_entry.path = "README.txt"; + new_entry.mode = GIT_FILEMODE_BLOB_EXECUTABLE; + + cl_must_pass(p_chmod("submod2/README.txt", GIT_FILEMODE_BLOB_EXECUTABLE)); + + cl_git_pass(git_index_remove_bypath(g_idx, "README.txt")); + + for (stage = 1; stage <= 3; stage++) { + new_entry.flags = stage << GIT_INDEX_ENTRY_STAGESHIFT; + cl_git_pass(git_index_add(g_idx, &new_entry)); + } + + cl_git_pass(git_index_write(g_idx)); + + cl_git_rewritefile("submod2/README.txt", "Modified but still executable"); + + cl_git_pass(git_index_add_bypath(g_idx, "README.txt")); + cl_git_pass(git_index_write(g_idx)); + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + cl_assert_equal_i(GIT_FILEMODE_BLOB_EXECUTABLE, entry->mode); +} + +void test_index_bypath__add_honors_conflict_case(void) +{ + const git_index_entry *entry; + git_index_entry new_entry; + int stage = 0; + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + + memcpy(&new_entry, entry, sizeof(git_index_entry)); + new_entry.path = "README.txt"; + new_entry.mode = GIT_FILEMODE_BLOB_EXECUTABLE; + + cl_must_pass(p_chmod("submod2/README.txt", GIT_FILEMODE_BLOB_EXECUTABLE)); + + cl_git_pass(git_index_remove_bypath(g_idx, "README.txt")); + + for (stage = 1; stage <= 3; stage++) { + new_entry.flags = stage << GIT_INDEX_ENTRY_STAGESHIFT; + cl_git_pass(git_index_add(g_idx, &new_entry)); + } + + cl_git_pass(git_index_write(g_idx)); + + cl_git_rewritefile("submod2/README.txt", "Modified but still executable"); + + cl_git_pass(git_index_add_bypath(g_idx, "README.txt")); + cl_git_pass(git_index_write(g_idx)); + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + cl_assert_equal_i(GIT_FILEMODE_BLOB_EXECUTABLE, entry->mode); +} + +void test_index_bypath__add_honors_symlink(void) +{ + const git_index_entry *entry; + git_index_entry new_entry; + int symlinks; + + cl_git_pass(git_repository__configmap_lookup(&symlinks, g_repo, GIT_CONFIGMAP_SYMLINKS)); + + if (symlinks) + cl_skip(); + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + + memcpy(&new_entry, entry, sizeof(git_index_entry)); + new_entry.path = "README.txt"; + new_entry.mode = GIT_FILEMODE_LINK; + + cl_git_pass(git_index_add(g_idx, &new_entry)); + cl_git_pass(git_index_write(g_idx)); + + cl_git_rewritefile("submod2/README.txt", "Modified but still a (fake) symlink"); + + cl_git_pass(git_index_add_bypath(g_idx, "README.txt")); + cl_git_pass(git_index_write(g_idx)); + + cl_assert((entry = git_index_get_bypath(g_idx, "README.txt", 0)) != NULL); + cl_assert_equal_i(GIT_FILEMODE_LINK, entry->mode); +} diff --git a/tests/libgit2/index/cache.c b/tests/libgit2/index/cache.c new file mode 100644 index 000000000..56885aff7 --- /dev/null +++ b/tests/libgit2/index/cache.c @@ -0,0 +1,238 @@ +#include "clar_libgit2.h" +#include "git2.h" +#include "index.h" +#include "tree-cache.h" + +static git_repository *g_repo; + +void test_index_cache__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_index_cache__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +void test_index_cache__write_extension_at_root(void) +{ + git_index *index; + git_tree *tree; + git_oid id; + const char *tree_id_str = "45dd856fdd4d89b884c340ba0e047752d9b085d6"; + const char *index_file = "index-tree"; + + cl_git_pass(git_index_open(&index, index_file)); + cl_assert(index->tree == NULL); + cl_git_pass(git_oid_fromstr(&id, tree_id_str)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + cl_git_pass(git_index_read_tree(index, tree)); + git_tree_free(tree); + + cl_assert(index->tree); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_index_open(&index, index_file)); + cl_assert(index->tree); + + cl_assert_equal_i(git_index_entrycount(index), index->tree->entry_count); + cl_assert_equal_i(0, index->tree->children_count); + + cl_assert(git_oid_equal(&id, &index->tree->oid)); + + cl_git_pass(p_unlink(index_file)); + git_index_free(index); +} + +void test_index_cache__write_extension_invalidated_root(void) +{ + git_index *index; + git_tree *tree; + git_oid id; + const char *tree_id_str = "45dd856fdd4d89b884c340ba0e047752d9b085d6"; + const char *index_file = "index-tree-invalidated"; + git_index_entry entry; + + cl_git_pass(git_index_open(&index, index_file)); + cl_assert(index->tree == NULL); + cl_git_pass(git_oid_fromstr(&id, tree_id_str)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + cl_git_pass(git_index_read_tree(index, tree)); + git_tree_free(tree); + + cl_assert(index->tree); + + memset(&entry, 0x0, sizeof(git_index_entry)); + git_oid_cpy(&entry.id, &git_index_get_byindex(index, 0)->id); + entry.mode = GIT_FILEMODE_BLOB; + entry.path = "some-new-file.txt"; + + cl_git_pass(git_index_add(index, &entry)); + + cl_assert_equal_i(-1, index->tree->entry_count); + + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_index_open(&index, index_file)); + cl_assert(index->tree); + + cl_assert_equal_i(-1, index->tree->entry_count); + cl_assert_equal_i(0, index->tree->children_count); + + cl_assert(git_oid_cmp(&id, &index->tree->oid)); + + cl_git_pass(p_unlink(index_file)); + git_index_free(index); +} + +void test_index_cache__read_tree_no_children(void) +{ + git_index *index; + git_index_entry entry; + git_tree *tree; + git_oid id; + + cl_git_pass(git_index_new(&index)); + cl_assert(index->tree == NULL); + cl_git_pass(git_oid_fromstr(&id, "45dd856fdd4d89b884c340ba0e047752d9b085d6")); + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + cl_git_pass(git_index_read_tree(index, tree)); + git_tree_free(tree); + + cl_assert(index->tree); + cl_assert(git_oid_equal(&id, &index->tree->oid)); + cl_assert_equal_i(0, index->tree->children_count); + cl_assert_equal_i(git_index_entrycount(index), index->tree->entry_count); + + memset(&entry, 0x0, sizeof(git_index_entry)); + entry.path = "new.txt"; + entry.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&entry.id, "d4bcc68acd4410bf836a39f20afb2c2ece09584e"); + + cl_git_pass(git_index_add(index, &entry)); + cl_assert_equal_i(-1, index->tree->entry_count); + + git_index_free(index); +} + +void test_index_cache__two_levels(void) +{ + git_tree *tree; + git_oid tree_id; + git_index *index; + git_index_entry entry; + const git_tree_cache *tree_cache; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_clear(index)); + + memset(&entry, 0x0, sizeof(entry)); + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&entry.id, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); + entry.path = "top-level.txt"; + cl_git_pass(git_index_add(index, &entry)); + + entry.path = "subdir/file.txt"; + cl_git_pass(git_index_add(index, &entry)); + + /* the read-tree fills the tree cache */ + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + cl_git_pass(git_index_read_tree(index, tree)); + git_tree_free(tree); + cl_git_pass(git_index_write(index)); + + /* we now must have cache entries for "" and "subdir" */ + cl_assert(index->tree); + cl_assert(git_tree_cache_get(index->tree, "subdir")); + + cl_git_pass(git_index_read(index, true)); + /* we must still have cache entries for "" and "subdir", since we wrote it out */ + cl_assert(index->tree); + cl_assert(git_tree_cache_get(index->tree, "subdir")); + + entry.path = "top-level.txt"; + cl_git_pass(git_oid_fromstr(&entry.id, "3697d64be941a53d4ae8f6a271e4e3fa56b022cc")); + cl_git_pass(git_index_add(index, &entry)); + + /* writ out the index after we invalidate the root */ + cl_git_pass(git_index_write(index)); + cl_git_pass(git_index_read(index, true)); + + /* the cache for the subtree must still be valid, even if the root isn't */ + cl_assert(index->tree); + cl_assert_equal_i(-1, index->tree->entry_count); + cl_assert_equal_i(1, index->tree->children_count); + tree_cache = git_tree_cache_get(index->tree, "subdir"); + cl_assert(tree_cache); + cl_assert_equal_i(1, tree_cache->entry_count); + + git_index_free(index); +} + +void test_index_cache__read_tree_children(void) +{ + git_index *index; + git_index_entry entry; + git_tree *tree; + const git_tree_cache *cache; + git_oid tree_id; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_clear(index)); + cl_assert(index->tree == NULL); + + + /* add a bunch of entries at different levels */ + memset(&entry, 0x0, sizeof(git_index_entry)); + entry.path = "top-level"; + entry.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); + cl_git_pass(git_index_add(index, &entry)); + + + entry.path = "subdir/some-file"; + cl_git_pass(git_index_add(index, &entry)); + + entry.path = "subdir/even-deeper/some-file"; + cl_git_pass(git_index_add(index, &entry)); + + entry.path = "subdir2/some-file"; + cl_git_pass(git_index_add(index, &entry)); + + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_index_clear(index)); + cl_assert(index->tree == NULL); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + cl_git_pass(git_index_read_tree(index, tree)); + git_tree_free(tree); + + cl_assert(index->tree); + cl_assert_equal_i(2, index->tree->children_count); + + /* override with a slightly different id, also dummy */ + entry.path = "subdir/some-file"; + git_oid_fromstr(&entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); + cl_git_pass(git_index_add(index, &entry)); + + cl_assert_equal_i(-1, index->tree->entry_count); + + cache = git_tree_cache_get(index->tree, "subdir"); + cl_assert(cache); + cl_assert_equal_i(-1, cache->entry_count); + + cache = git_tree_cache_get(index->tree, "subdir/even-deeper"); + cl_assert(cache); + cl_assert_equal_i(1, cache->entry_count); + + cache = git_tree_cache_get(index->tree, "subdir2"); + cl_assert(cache); + cl_assert_equal_i(1, cache->entry_count); + + git_index_free(index); +} diff --git a/tests/libgit2/index/collision.c b/tests/libgit2/index/collision.c new file mode 100644 index 000000000..18d2664f0 --- /dev/null +++ b/tests/libgit2/index/collision.c @@ -0,0 +1,149 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/index.h" + +static git_repository *g_repo; +static git_odb *g_odb; +static git_index *g_index; +static git_oid g_empty_id; + +void test_index_collision__initialize(void) +{ + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_pass(git_repository_odb(&g_odb, g_repo)); + cl_git_pass(git_repository_index(&g_index, g_repo)); + + cl_git_pass(git_odb_write(&g_empty_id, g_odb, "", 0, GIT_OBJECT_BLOB)); +} + +void test_index_collision__cleanup(void) +{ + git_index_free(g_index); + git_odb_free(g_odb); + cl_git_sandbox_cleanup(); +} + +void test_index_collision__add_blob_with_conflicting_file(void) +{ + git_index_entry entry; + git_tree_entry *tentry; + git_oid tree_id; + git_tree *tree; + + memset(&entry, 0, sizeof(entry)); + entry.ctime.seconds = 12346789; + entry.mtime.seconds = 12346789; + entry.mode = 0100644; + entry.file_size = 0; + git_oid_cpy(&entry.id, &g_empty_id); + + entry.path = "a/b"; + cl_git_pass(git_index_add(g_index, &entry)); + + /* Check a/b exists here */ + cl_git_pass(git_index_write_tree(&tree_id, g_index)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b")); + git_tree_entry_free(tentry); + git_tree_free(tree); + + /* create a tree/blob collision */ + entry.path = "a/b/c"; + cl_git_pass(git_index_add(g_index, &entry)); + + /* a/b should now be a tree and a/b/c a blob */ + cl_git_pass(git_index_write_tree(&tree_id, g_index)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b/c")); + git_tree_entry_free(tentry); + git_tree_free(tree); +} + +void test_index_collision__add_blob_with_conflicting_dir(void) +{ + git_index_entry entry; + git_tree_entry *tentry; + git_oid tree_id; + git_tree *tree; + + memset(&entry, 0, sizeof(entry)); + entry.ctime.seconds = 12346789; + entry.mtime.seconds = 12346789; + entry.mode = 0100644; + entry.file_size = 0; + git_oid_cpy(&entry.id, &g_empty_id); + + entry.path = "a/b/c"; + cl_git_pass(git_index_add(g_index, &entry)); + + /* Check a/b/c exists here */ + cl_git_pass(git_index_write_tree(&tree_id, g_index)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b/c")); + git_tree_entry_free(tentry); + git_tree_free(tree); + + /* create a blob/tree collision */ + entry.path = "a/b"; + cl_git_pass(git_index_add(g_index, &entry)); + + /* a/b should now be a tree and a/b/c a blob */ + cl_git_pass(git_index_write_tree(&tree_id, g_index)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b")); + cl_git_fail(git_tree_entry_bypath(&tentry, tree, "a/b/c")); + git_tree_entry_free(tentry); + git_tree_free(tree); +} + +void test_index_collision__add_with_highstage_1(void) +{ + git_index_entry entry; + + memset(&entry, 0, sizeof(entry)); + entry.ctime.seconds = 12346789; + entry.mtime.seconds = 12346789; + entry.mode = 0100644; + entry.file_size = 0; + git_oid_cpy(&entry.id, &g_empty_id); + + entry.path = "a/b"; + GIT_INDEX_ENTRY_STAGE_SET(&entry, 2); + cl_git_pass(git_index_add(g_index, &entry)); + + /* create a blob beneath the previous tree entry */ + entry.path = "a/b/c"; + entry.flags = 0; + cl_git_pass(git_index_add(g_index, &entry)); + + /* create another tree entry above the blob */ + entry.path = "a/b"; + GIT_INDEX_ENTRY_STAGE_SET(&entry, 1); + cl_git_pass(git_index_add(g_index, &entry)); +} + +void test_index_collision__add_with_highstage_2(void) +{ + git_index_entry entry; + + memset(&entry, 0, sizeof(entry)); + entry.ctime.seconds = 12346789; + entry.mtime.seconds = 12346789; + entry.mode = 0100644; + entry.file_size = 0; + git_oid_cpy(&entry.id, &g_empty_id); + + entry.path = "a/b/c"; + GIT_INDEX_ENTRY_STAGE_SET(&entry, 1); + cl_git_pass(git_index_add(g_index, &entry)); + + /* create a blob beneath the previous tree entry */ + entry.path = "a/b/c"; + GIT_INDEX_ENTRY_STAGE_SET(&entry, 2); + cl_git_pass(git_index_add(g_index, &entry)); + + /* create another tree entry above the blob */ + entry.path = "a/b"; + GIT_INDEX_ENTRY_STAGE_SET(&entry, 3); + cl_git_pass(git_index_add(g_index, &entry)); +} diff --git a/tests/libgit2/index/conflicts.c b/tests/libgit2/index/conflicts.c new file mode 100644 index 000000000..41d0e219c --- /dev/null +++ b/tests/libgit2/index/conflicts.c @@ -0,0 +1,462 @@ +#include "clar_libgit2.h" +#include "index.h" +#include "git2/repository.h" +#include "conflicts.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "mergedrepo" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +/* Fixture setup and teardown */ +void test_index_conflicts__initialize(void) +{ + repo = cl_git_sandbox_init("mergedrepo"); + git_repository_index(&repo_index, repo); +} + +void test_index_conflicts__cleanup(void) +{ + git_index_free(repo_index); + repo_index = NULL; + + cl_git_sandbox_cleanup(); +} + +void test_index_conflicts__add(void) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + + cl_assert(git_index_entrycount(repo_index) == 8); + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = "test-one.txt"; + ancestor_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 1); + git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); + + our_entry.path = "test-one.txt"; + our_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&our_entry, 2); + git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); + + their_entry.path = "test-one.txt"; + their_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 2); + git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); + + cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); + + cl_assert(git_index_entrycount(repo_index) == 11); +} + +void test_index_conflicts__add_fixes_incorrect_stage(void) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + const git_index_entry *conflict_entry[3]; + + cl_assert(git_index_entrycount(repo_index) == 8); + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = "test-one.txt"; + ancestor_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 3); + git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); + + our_entry.path = "test-one.txt"; + our_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&our_entry, 1); + git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); + + their_entry.path = "test-one.txt"; + their_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&their_entry, 2); + git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); + + cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); + + cl_assert(git_index_entrycount(repo_index) == 11); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], repo_index, "test-one.txt")); + + cl_assert(git_index_entry_stage(conflict_entry[0]) == 1); + cl_assert(git_index_entry_stage(conflict_entry[1]) == 2); + cl_assert(git_index_entry_stage(conflict_entry[2]) == 3); +} + +void test_index_conflicts__add_detects_invalid_filemode(void) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + git_index_entry *conflict_entry[3]; + int i; + + cl_assert(git_index_entrycount(repo_index) == 8); + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + conflict_entry[0] = &ancestor_entry; + conflict_entry[1] = &our_entry; + conflict_entry[2] = &their_entry; + + for (i = 0; i < 3; i++) { + ancestor_entry.path = "test-one.txt"; + ancestor_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 3); + git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); + + our_entry.path = "test-one.txt"; + our_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&our_entry, 1); + git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); + + their_entry.path = "test-one.txt"; + their_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&their_entry, 2); + git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); + + /* Corrupt the conflict entry's mode */ + conflict_entry[i]->mode = 027431745; + + cl_git_fail(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); + } + + cl_assert(git_index_entrycount(repo_index) == 8); +} + + +void test_index_conflicts__add_removes_stage_zero(void) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + const git_index_entry *conflict_entry[3]; + + cl_assert(git_index_entrycount(repo_index) == 8); + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + cl_git_mkfile("./mergedrepo/test-one.txt", "new-file\n"); + cl_git_pass(git_index_add_bypath(repo_index, "test-one.txt")); + cl_assert(git_index_entrycount(repo_index) == 9); + + ancestor_entry.path = "test-one.txt"; + ancestor_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 3); + git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); + + our_entry.path = "test-one.txt"; + our_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&our_entry, 1); + git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); + + their_entry.path = "test-one.txt"; + their_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&their_entry, 2); + git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); + + cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); + + cl_assert(git_index_entrycount(repo_index) == 11); + + cl_assert_equal_p(NULL, git_index_get_bypath(repo_index, "test-one.txt", 0)); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], repo_index, "test-one.txt")); + + cl_assert_equal_oid(&ancestor_entry.id, &conflict_entry[0]->id); + cl_assert_equal_i(1, git_index_entry_stage(conflict_entry[0])); + cl_assert_equal_oid(&our_entry.id, &conflict_entry[1]->id); + cl_assert_equal_i(2, git_index_entry_stage(conflict_entry[1])); + cl_assert_equal_oid(&their_entry.id, &conflict_entry[2]->id); + cl_assert_equal_i(3, git_index_entry_stage(conflict_entry[2])); +} + +void test_index_conflicts__get(void) +{ + const git_index_entry *conflict_entry[3]; + git_oid oid; + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], repo_index, "conflicts-one.txt")); + + cl_assert_equal_s("conflicts-one.txt", conflict_entry[0]->path); + + git_oid_fromstr(&oid, CONFLICTS_ONE_ANCESTOR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[0]->id); + + git_oid_fromstr(&oid, CONFLICTS_ONE_OUR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[1]->id); + + git_oid_fromstr(&oid, CONFLICTS_ONE_THEIR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[2]->id); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], repo_index, "conflicts-two.txt")); + + cl_assert_equal_s("conflicts-two.txt", conflict_entry[0]->path); + + git_oid_fromstr(&oid, CONFLICTS_TWO_ANCESTOR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[0]->id); + + git_oid_fromstr(&oid, CONFLICTS_TWO_OUR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[1]->id); + + git_oid_fromstr(&oid, CONFLICTS_TWO_THEIR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[2]->id); +} + +void test_index_conflicts__iterate(void) +{ + git_index_conflict_iterator *iterator; + const git_index_entry *conflict_entry[3]; + git_oid oid; + + cl_git_pass(git_index_conflict_iterator_new(&iterator, repo_index)); + + cl_git_pass(git_index_conflict_next(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], iterator)); + + git_oid_fromstr(&oid, CONFLICTS_ONE_ANCESTOR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[0]->id); + cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-one.txt") == 0); + + git_oid_fromstr(&oid, CONFLICTS_ONE_OUR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[1]->id); + cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-one.txt") == 0); + + git_oid_fromstr(&oid, CONFLICTS_ONE_THEIR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[2]->id); + cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-one.txt") == 0); + + cl_git_pass(git_index_conflict_next(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], iterator)); + + git_oid_fromstr(&oid, CONFLICTS_TWO_ANCESTOR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[0]->id); + cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-two.txt") == 0); + + git_oid_fromstr(&oid, CONFLICTS_TWO_OUR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[1]->id); + cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-two.txt") == 0); + + git_oid_fromstr(&oid, CONFLICTS_TWO_THEIR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[2]->id); + cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-two.txt") == 0); + + cl_assert(git_index_conflict_next(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], iterator) == GIT_ITEROVER); + + cl_assert(conflict_entry[0] == NULL); + cl_assert(conflict_entry[2] == NULL); + cl_assert(conflict_entry[2] == NULL); + + git_index_conflict_iterator_free(iterator); +} + +void test_index_conflicts__remove(void) +{ + const git_index_entry *entry; + size_t i; + + cl_assert(git_index_entrycount(repo_index) == 8); + + cl_git_pass(git_index_conflict_remove(repo_index, "conflicts-one.txt")); + cl_assert(git_index_entrycount(repo_index) == 5); + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + cl_assert(strcmp(entry->path, "conflicts-one.txt") != 0); + } + + cl_git_pass(git_index_conflict_remove(repo_index, "conflicts-two.txt")); + cl_assert(git_index_entrycount(repo_index) == 2); + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + cl_assert(strcmp(entry->path, "conflicts-two.txt") != 0); + } +} + +void test_index_conflicts__moved_to_reuc_on_add(void) +{ + const git_index_entry *entry; + size_t i; + + cl_assert(git_index_entrycount(repo_index) == 8); + + cl_git_mkfile("./mergedrepo/conflicts-one.txt", "new-file\n"); + + cl_git_pass(git_index_add_bypath(repo_index, "conflicts-one.txt")); + + cl_assert(git_index_entrycount(repo_index) == 6); + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + + if (strcmp(entry->path, "conflicts-one.txt") == 0) + cl_assert(!git_index_entry_is_conflict(entry)); + } +} + +void test_index_conflicts__moved_to_reuc_on_remove(void) +{ + const git_index_entry *entry; + size_t i; + + cl_assert(git_index_entrycount(repo_index) == 8); + + cl_git_pass(p_unlink("./mergedrepo/conflicts-one.txt")); + + cl_git_pass(git_index_remove_bypath(repo_index, "conflicts-one.txt")); + + cl_assert(git_index_entrycount(repo_index) == 5); + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + cl_assert(strcmp(entry->path, "conflicts-one.txt") != 0); + } +} + +void test_index_conflicts__remove_all_conflicts(void) +{ + size_t i; + const git_index_entry *entry; + + cl_assert(git_index_entrycount(repo_index) == 8); + + cl_assert_equal_i(true, git_index_has_conflicts(repo_index)); + + git_index_conflict_cleanup(repo_index); + + cl_assert_equal_i(false, git_index_has_conflicts(repo_index)); + + cl_assert(git_index_entrycount(repo_index) == 2); + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + cl_assert(!git_index_entry_is_conflict(entry)); + } +} + +void test_index_conflicts__partial(void) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + const git_index_entry *conflict_entry[3]; + + cl_assert(git_index_entrycount(repo_index) == 8); + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = "test-one.txt"; + ancestor_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 1); + git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); + + cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, NULL, NULL)); + cl_assert(git_index_entrycount(repo_index) == 9); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], repo_index, "test-one.txt")); + + cl_assert_equal_oid(&ancestor_entry.id, &conflict_entry[0]->id); + cl_assert(conflict_entry[1] == NULL); + cl_assert(conflict_entry[2] == NULL); +} + +void test_index_conflicts__case_matters(void) +{ + const git_index_entry *conflict_entry[3]; + git_oid oid; + const char *upper_case = "DIFFERS-IN-CASE.TXT"; + const char *mixed_case = "Differs-In-Case.txt"; + const char *correct_case; + bool ignorecase = cl_repo_get_bool(repo, "core.ignorecase"); + + git_index_entry ancestor_entry, our_entry, their_entry; + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = upper_case; + GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, GIT_INDEX_STAGE_ANCESTOR); + git_oid_fromstr(&ancestor_entry.id, CONFLICTS_ONE_ANCESTOR_OID); + ancestor_entry.mode = GIT_FILEMODE_BLOB; + + our_entry.path = upper_case; + GIT_INDEX_ENTRY_STAGE_SET(&our_entry, GIT_INDEX_STAGE_OURS); + git_oid_fromstr(&our_entry.id, CONFLICTS_ONE_OUR_OID); + our_entry.mode = GIT_FILEMODE_BLOB; + + their_entry.path = upper_case; + GIT_INDEX_ENTRY_STAGE_SET(&their_entry, GIT_INDEX_STAGE_THEIRS); + git_oid_fromstr(&their_entry.id, CONFLICTS_ONE_THEIR_OID); + their_entry.mode = GIT_FILEMODE_BLOB; + + cl_git_pass(git_index_conflict_add(repo_index, + &ancestor_entry, &our_entry, &their_entry)); + + ancestor_entry.path = mixed_case; + GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, GIT_INDEX_STAGE_ANCESTOR); + git_oid_fromstr(&ancestor_entry.id, CONFLICTS_TWO_ANCESTOR_OID); + ancestor_entry.mode = GIT_FILEMODE_BLOB; + + our_entry.path = mixed_case; + GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, GIT_INDEX_STAGE_ANCESTOR); + git_oid_fromstr(&our_entry.id, CONFLICTS_TWO_OUR_OID); + ancestor_entry.mode = GIT_FILEMODE_BLOB; + + their_entry.path = mixed_case; + GIT_INDEX_ENTRY_STAGE_SET(&their_entry, GIT_INDEX_STAGE_THEIRS); + git_oid_fromstr(&their_entry.id, CONFLICTS_TWO_THEIR_OID); + their_entry.mode = GIT_FILEMODE_BLOB; + + cl_git_pass(git_index_conflict_add(repo_index, + &ancestor_entry, &our_entry, &their_entry)); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], repo_index, upper_case)); + + /* + * We inserted with mixed case last, so on a case-insensitive + * fs we should get the mixed case. + */ + if (ignorecase) + correct_case = mixed_case; + else + correct_case = upper_case; + + cl_assert_equal_s(correct_case, conflict_entry[0]->path); + git_oid_fromstr(&oid, ignorecase ? CONFLICTS_TWO_ANCESTOR_OID : CONFLICTS_ONE_ANCESTOR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[0]->id); + + cl_assert_equal_s(correct_case, conflict_entry[1]->path); + git_oid_fromstr(&oid, ignorecase ? CONFLICTS_TWO_OUR_OID : CONFLICTS_ONE_OUR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[1]->id); + + cl_assert_equal_s(correct_case, conflict_entry[2]->path); + git_oid_fromstr(&oid, ignorecase ? CONFLICTS_TWO_THEIR_OID : CONFLICTS_ONE_THEIR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[2]->id); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], repo_index, mixed_case)); + + cl_assert_equal_s(mixed_case, conflict_entry[0]->path); + git_oid_fromstr(&oid, CONFLICTS_TWO_ANCESTOR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[0]->id); + + cl_assert_equal_s(mixed_case, conflict_entry[1]->path); + git_oid_fromstr(&oid, CONFLICTS_TWO_OUR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[1]->id); + + cl_assert_equal_s(mixed_case, conflict_entry[2]->path); + git_oid_fromstr(&oid, CONFLICTS_TWO_THEIR_OID); + cl_assert_equal_oid(&oid, &conflict_entry[2]->id); +} diff --git a/tests/libgit2/index/conflicts.h b/tests/libgit2/index/conflicts.h new file mode 100644 index 000000000..3e26e6d5b --- /dev/null +++ b/tests/libgit2/index/conflicts.h @@ -0,0 +1,7 @@ +#define CONFLICTS_ONE_ANCESTOR_OID "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81" +#define CONFLICTS_ONE_OUR_OID "6aea5f295304c36144ad6e9247a291b7f8112399" +#define CONFLICTS_ONE_THEIR_OID "516bd85f78061e09ccc714561d7b504672cb52da" + +#define CONFLICTS_TWO_ANCESTOR_OID "84af62840be1b1c47b778a8a249f3ff45155038c" +#define CONFLICTS_TWO_OUR_OID "8b3f43d2402825c200f835ca1762413e386fd0b2" +#define CONFLICTS_TWO_THEIR_OID "220bd62631c8cf7a83ef39c6b94595f00517211e" diff --git a/tests/libgit2/index/crlf.c b/tests/libgit2/index/crlf.c new file mode 100644 index 000000000..7520c23a3 --- /dev/null +++ b/tests/libgit2/index/crlf.c @@ -0,0 +1,402 @@ +#include "clar_libgit2.h" +#include "../filter/crlf.h" + +#include "git2/checkout.h" +#include "repository.h" +#include "posix.h" + +#define FILE_CONTENTS_LF "one\ntwo\nthree\nfour\n" +#define FILE_CONTENTS_CRLF "one\r\ntwo\r\nthree\r\nfour\r\n" + +#define FILE_OID_LF "f384549cbeb481e437091320de6d1f2e15e11b4a" +#define FILE_OID_CRLF "7fbf4d847b191141d80f30c8ab03d2ad4cd543a9" + +static git_repository *g_repo; +static git_index *g_index; + +static git_str expected_fixture = GIT_STR_INIT; + +void test_index_crlf__initialize(void) +{ + g_repo = cl_git_sandbox_init_new("crlf"); + cl_git_pass(git_repository_index(&g_index, g_repo)); +} + +void test_index_crlf__cleanup(void) +{ + git_index_free(g_index); + cl_git_sandbox_cleanup(); + + if (expected_fixture.size) { + cl_fixture_cleanup(expected_fixture.ptr); + git_str_dispose(&expected_fixture); + } +} + +struct compare_data +{ + const char *systype; + const char *dirname; + const char *safecrlf; + const char *autocrlf; + const char *attrs; +}; + +static int add_and_check_file(void *payload, git_str *actual_path) +{ + git_str expected_path = GIT_STR_INIT; + git_str expected_path_fail = GIT_STR_INIT; + git_str expected_contents = GIT_STR_INIT; + struct compare_data *cd = payload; + char *basename; + const git_index_entry *entry; + git_blob *blob; + bool failed = true; + + basename = git_fs_path_basename(actual_path->ptr); + + if (!strcmp(basename, ".git") || !strcmp(basename, ".gitattributes")) { + failed = false; + goto done; + } + + cl_git_pass(git_str_joinpath(&expected_path, cd->dirname, basename)); + + cl_git_pass(git_str_puts(&expected_path_fail, expected_path.ptr)); + cl_git_pass(git_str_puts(&expected_path_fail, ".fail")); + + if (git_fs_path_isfile(expected_path.ptr)) { + cl_git_pass(git_index_add_bypath(g_index, basename)); + + cl_assert(entry = git_index_get_bypath(g_index, basename, 0)); + cl_git_pass(git_blob_lookup(&blob, g_repo, &entry->id)); + + cl_git_pass(git_futils_readbuffer(&expected_contents, expected_path.ptr)); + + if (strcmp(expected_contents.ptr, git_blob_rawcontent(blob)) != 0) + goto done; + + git_blob_free(blob); + } else if (git_fs_path_isfile(expected_path_fail.ptr)) { + cl_git_pass(git_futils_readbuffer(&expected_contents, expected_path_fail.ptr)); + git_str_rtrim(&expected_contents); + + if (git_index_add_bypath(g_index, basename) == 0 || + git_error_last()->klass != GIT_ERROR_FILTER || + strcmp(expected_contents.ptr, git_error_last()->message) != 0) + goto done; + } else { + cl_fail("unexpected index failure"); + } + + failed = false; + +done: + if (failed) { + git_str details = GIT_STR_INIT; + git_str_printf(&details, "filename=%s, system=%s, autocrlf=%s, safecrlf=%s, attrs={%s}", + basename, cd->systype, cd->autocrlf, cd->safecrlf, cd->attrs); + clar__fail(__FILE__, __func__, __LINE__, + "index contents did not match expected", details.ptr, 0); + git_str_dispose(&details); + } + + git__free(basename); + git_str_dispose(&expected_contents); + git_str_dispose(&expected_path); + git_str_dispose(&expected_path_fail); + return 0; +} + +static const char *system_type(void) +{ + if (GIT_EOL_NATIVE == GIT_EOL_CRLF) + return "windows"; + else + return "posix"; +} + +static void test_add_index(const char *safecrlf, const char *autocrlf, const char *attrs) +{ + git_str attrbuf = GIT_STR_INIT; + git_str expected_dirname = GIT_STR_INIT; + git_str sandboxname = GIT_STR_INIT; + git_str reponame = GIT_STR_INIT; + struct compare_data compare_data = { system_type(), NULL, safecrlf, autocrlf, attrs }; + const char *c; + + git_str_puts(&reponame, "crlf"); + + git_str_puts(&sandboxname, "autocrlf_"); + git_str_puts(&sandboxname, autocrlf); + + git_str_puts(&sandboxname, ",safecrlf_"); + git_str_puts(&sandboxname, safecrlf); + + if (*attrs) { + git_str_puts(&sandboxname, ","); + + for (c = attrs; *c; c++) { + if (*c == ' ') + git_str_putc(&sandboxname, ','); + else if (*c == '=') + git_str_putc(&sandboxname, '_'); + else + git_str_putc(&sandboxname, *c); + } + + git_str_printf(&attrbuf, "* %s\n", attrs); + cl_git_mkfile("crlf/.gitattributes", attrbuf.ptr); + } + + cl_repo_set_string(g_repo, "core.safecrlf", safecrlf); + cl_repo_set_string(g_repo, "core.autocrlf", autocrlf); + + cl_git_pass(git_index_clear(g_index)); + + git_str_joinpath(&expected_dirname, "crlf_data", system_type()); + git_str_puts(&expected_dirname, "_to_odb"); + + git_str_joinpath(&expected_fixture, expected_dirname.ptr, sandboxname.ptr); + cl_fixture_sandbox(expected_fixture.ptr); + + compare_data.dirname = sandboxname.ptr; + cl_git_pass(git_fs_path_direach(&reponame, 0, add_and_check_file, &compare_data)); + + cl_fixture_cleanup(expected_fixture.ptr); + git_str_dispose(&expected_fixture); + + git_str_dispose(&attrbuf); + git_str_dispose(&expected_fixture); + git_str_dispose(&expected_dirname); + git_str_dispose(&sandboxname); + git_str_dispose(&reponame); +} + +static void set_up_workingdir(const char *name) +{ + git_vector contents = GIT_VECTOR_INIT; + size_t i; + const char *fn; + + git_fs_path_dirload(&contents, name, 0, 0); + git_vector_foreach(&contents, i, fn) { + char *basename = git_fs_path_basename(fn); + bool skip = strncasecmp(basename, ".git", 4) == 0 && strlen(basename) == 4; + + git__free(basename); + + if (skip) + continue; + p_unlink(fn); + } + git_vector_free_deep(&contents); + + /* copy input files */ + git_fs_path_dirload(&contents, cl_fixture("crlf"), 0, 0); + git_vector_foreach(&contents, i, fn) { + char *basename = git_fs_path_basename(fn); + git_str dest_filename = GIT_STR_INIT; + + if (strcmp(basename, ".gitted") && + strcmp(basename, ".gitattributes")) { + git_str_joinpath(&dest_filename, name, basename); + cl_git_pass(git_futils_cp(fn, dest_filename.ptr, 0644)); + } + + git__free(basename); + git_str_dispose(&dest_filename); + } + git_vector_free_deep(&contents); +} + +void test_index_crlf__matches_core_git(void) +{ + const char *safecrlf[] = { "true", "false", "warn", NULL }; + const char *autocrlf[] = { "true", "false", "input", NULL }; + const char *attrs[] = { "", "-crlf", "-text", "eol=crlf", "eol=lf", + "text", "text eol=crlf", "text eol=lf", + "text=auto", "text=auto eol=crlf", "text=auto eol=lf", + NULL }; + const char **a, **b, **c; + + for (a = safecrlf; *a; a++) { + for (b = autocrlf; *b; b++) { + for (c = attrs; *c; c++) { + set_up_workingdir("crlf"); + test_add_index(*a, *b, *c); + } + } + } +} + +void test_index_crlf__autocrlf_false_no_attrs(void) +{ + const git_index_entry *entry; + git_oid oid; + + cl_repo_set_bool(g_repo, "core.autocrlf", false); + + cl_git_mkfile("./crlf/newfile.txt", + (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF); + + cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); + entry = git_index_get_bypath(g_index, "newfile.txt", 0); + + cl_git_pass(git_oid_fromstr(&oid, + (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_OID_CRLF : FILE_OID_LF)); + cl_assert_equal_oid(&oid, &entry->id); +} + +void test_index_crlf__autocrlf_true_no_attrs(void) +{ + const git_index_entry *entry; + git_oid oid; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_git_mkfile("./crlf/newfile.txt", + (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF); + + cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); + entry = git_index_get_bypath(g_index, "newfile.txt", 0); + + cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); + cl_assert_equal_oid(&oid, &entry->id); +} + +void test_index_crlf__autocrlf_input_no_attrs(void) +{ + const git_index_entry *entry; + git_oid oid; + + cl_repo_set_string(g_repo, "core.autocrlf", "input"); + + cl_git_mkfile("./crlf/newfile.txt", + (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF); + + cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); + entry = git_index_get_bypath(g_index, "newfile.txt", 0); + + cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); + cl_assert_equal_oid(&oid, &entry->id); +} + +void test_index_crlf__autocrlf_false_text_auto_attr(void) +{ + const git_index_entry *entry; + git_oid oid; + + cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); + + cl_repo_set_bool(g_repo, "core.autocrlf", false); + + cl_git_mkfile("./crlf/newfile.txt", + (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF); + + cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); + entry = git_index_get_bypath(g_index, "newfile.txt", 0); + + cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); + cl_assert_equal_oid(&oid, &entry->id); +} + +void test_index_crlf__autocrlf_true_text_auto_attr(void) +{ + const git_index_entry *entry; + git_oid oid; + + cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); + + cl_repo_set_bool(g_repo, "core.autocrlf", false); + + cl_git_mkfile("./crlf/newfile.txt", + (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF); + + cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); + entry = git_index_get_bypath(g_index, "newfile.txt", 0); + + cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); + cl_assert_equal_oid(&oid, &entry->id); +} + +void test_index_crlf__autocrlf_input_text_auto_attr(void) +{ + const git_index_entry *entry; + git_oid oid; + + cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); + + cl_repo_set_string(g_repo, "core.autocrlf", "input"); + + cl_git_mkfile("./crlf/newfile.txt", + (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF); + + cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); + entry = git_index_get_bypath(g_index, "newfile.txt", 0); + + cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); + cl_assert_equal_oid(&oid, &entry->id); +} + +void test_index_crlf__safecrlf_true_autocrlf_input_text_auto_attr(void) +{ + const git_index_entry *entry; + git_oid oid; + + cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n"); + + cl_repo_set_string(g_repo, "core.autocrlf", "input"); + cl_repo_set_bool(g_repo, "core.safecrlf", true); + + cl_git_mkfile("./crlf/newfile.txt", FILE_CONTENTS_LF); + + cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); + entry = git_index_get_bypath(g_index, "newfile.txt", 0); + cl_assert(entry); + + cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); + cl_assert_equal_oid(&oid, &entry->id); + + cl_git_mkfile("./crlf/newfile2.txt", FILE_CONTENTS_CRLF); + cl_git_fail(git_index_add_bypath(g_index, "newfile2.txt")); +} + +void test_index_crlf__safecrlf_true_autocrlf_input_text__no_attr(void) +{ + const git_index_entry *entry; + git_oid oid; + + cl_repo_set_string(g_repo, "core.autocrlf", "input"); + cl_repo_set_bool(g_repo, "core.safecrlf", true); + + cl_git_mkfile("./crlf/newfile.txt", FILE_CONTENTS_LF); + + cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); + entry = git_index_get_bypath(g_index, "newfile.txt", 0); + cl_assert(entry); + + cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); + cl_assert_equal_oid(&oid, &entry->id); + + cl_git_mkfile("./crlf/newfile2.txt", FILE_CONTENTS_CRLF); + cl_git_fail(git_index_add_bypath(g_index, "newfile2.txt")); +} + +void test_index_crlf__safecrlf_true_no_attrs(void) +{ + cl_repo_set_bool(g_repo, "core.autocrlf", true); + cl_repo_set_bool(g_repo, "core.safecrlf", true); + + cl_git_mkfile("crlf/newfile.txt", ALL_LF_TEXT_RAW); + cl_git_fail(git_index_add_bypath(g_index, "newfile.txt")); + + cl_git_mkfile("crlf/newfile.txt", ALL_CRLF_TEXT_RAW); + cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); + + cl_git_mkfile("crlf/newfile.txt", MORE_CRLF_TEXT_RAW); + cl_git_fail(git_index_add_bypath(g_index, "newfile.txt")); + + cl_git_mkfile("crlf/newfile.txt", MORE_LF_TEXT_RAW); + cl_git_fail(git_index_add_bypath(g_index, "newfile.txt")); +} diff --git a/tests/libgit2/index/filemodes.c b/tests/libgit2/index/filemodes.c new file mode 100644 index 000000000..1ab8a9a7f --- /dev/null +++ b/tests/libgit2/index/filemodes.c @@ -0,0 +1,319 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "index.h" + +static git_repository *g_repo = NULL; + +void test_index_filemodes__initialize(void) +{ + g_repo = cl_git_sandbox_init("filemodes"); +} + +void test_index_filemodes__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_index_filemodes__read(void) +{ + git_index *index; + unsigned int i; + static bool expected[6] = { 0, 1, 0, 1, 0, 1 }; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_assert_equal_i(6, (int)git_index_entrycount(index)); + + for (i = 0; i < 6; ++i) { + const git_index_entry *entry = git_index_get_byindex(index, i); + cl_assert(entry != NULL); + cl_assert(((entry->mode & 0100) ? 1 : 0) == expected[i]); + } + + git_index_free(index); +} + +static void replace_file_with_mode( + const char *filename, const char *backup, unsigned int create_mode) +{ + git_str path = GIT_STR_INIT, content = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&path, "filemodes", filename)); + cl_git_pass(git_str_printf(&content, "%s as %08u (%d)", + filename, create_mode, rand())); + + cl_git_pass(p_rename(path.ptr, backup)); + cl_git_write2file( + path.ptr, content.ptr, content.size, + O_WRONLY|O_CREAT|O_TRUNC, create_mode); + + git_str_dispose(&path); + git_str_dispose(&content); +} + +#define add_and_check_mode(I,F,X) add_and_check_mode_(I,F,X,__FILE__,__func__,__LINE__) + +static void add_and_check_mode_( + git_index *index, const char *filename, unsigned int expect_mode, + const char *file, const char *func, int line) +{ + size_t pos; + const git_index_entry *entry; + + cl_git_pass(git_index_add_bypath(index, filename)); + + clar__assert(!git_index_find(&pos, index, filename), + file, func, line, "Cannot find index entry", NULL, 1); + + entry = git_index_get_byindex(index, pos); + + clar__assert_equal(file, func, line, "Expected mode does not match index", + 1, "%07o", (unsigned int)entry->mode, (unsigned int)expect_mode); +} + +void test_index_filemodes__untrusted(void) +{ + git_index *index; + + cl_repo_set_bool(g_repo, "core.filemode", false); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_assert((git_index_caps(index) & GIT_INDEX_CAPABILITY_NO_FILEMODE) != 0); + + /* 1 - add 0644 over existing 0644 -> expect 0644 */ + replace_file_with_mode("exec_off", "filemodes/exec_off.0", 0644); + add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); + + /* 2 - add 0644 over existing 0755 -> expect 0755 */ + replace_file_with_mode("exec_on", "filemodes/exec_on.0", 0644); + add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); + + /* 3 - add 0755 over existing 0644 -> expect 0644 */ + replace_file_with_mode("exec_off", "filemodes/exec_off.1", 0755); + add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); + + /* 4 - add 0755 over existing 0755 -> expect 0755 */ + replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755); + add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); + + /* 5 - add new 0644 -> expect 0644 */ + cl_git_write2file("filemodes/new_off", "blah", 0, + O_WRONLY | O_CREAT | O_TRUNC, 0644); + add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB); + + /* 6 - add new 0755 -> expect 0644 if core.filemode == false */ + cl_git_write2file("filemodes/new_on", "blah", 0, + O_WRONLY | O_CREAT | O_TRUNC, 0755); + add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB); + + git_index_free(index); +} + +void test_index_filemodes__trusted(void) +{ + git_index *index; + + /* Only run these tests on platforms where I can actually + * chmod a file and get the stat results I expect! + */ + if (!cl_is_chmod_supported()) + return; + + cl_repo_set_bool(g_repo, "core.filemode", true); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_assert((git_index_caps(index) & GIT_INDEX_CAPABILITY_NO_FILEMODE) == 0); + + /* 1 - add 0644 over existing 0644 -> expect 0644 */ + replace_file_with_mode("exec_off", "filemodes/exec_off.0", 0644); + add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); + + /* 2 - add 0644 over existing 0755 -> expect 0644 */ + replace_file_with_mode("exec_on", "filemodes/exec_on.0", 0644); + add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB); + + /* 3 - add 0755 over existing 0644 -> expect 0755 */ + replace_file_with_mode("exec_off", "filemodes/exec_off.1", 0755); + add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB_EXECUTABLE); + + /* 4 - add 0755 over existing 0755 -> expect 0755 */ + replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755); + add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); + + /* 5 - add new 0644 -> expect 0644 */ + cl_git_write2file("filemodes/new_off", "blah", 0, + O_WRONLY | O_CREAT | O_TRUNC, 0644); + add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB); + + /* 6 - add 0755 -> expect 0755 */ + cl_git_write2file("filemodes/new_on", "blah", 0, + O_WRONLY | O_CREAT | O_TRUNC, 0755); + add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE); + + git_index_free(index); +} + +#define add_entry_and_check_mode(I,FF,X) add_entry_and_check_mode_(I,FF,X,__FILE__,__func__,__LINE__) + +static void add_entry_and_check_mode_( + git_index *index, bool from_file, git_filemode_t mode, + const char *file, const char *func, int line) +{ + size_t pos; + const git_index_entry* entry; + git_index_entry new_entry; + + /* If old_filename exists, we copy that to the new file, and test + * git_index_add(), otherwise create a new entry testing git_index_add_from_buffer + */ + if (from_file) + { + clar__assert(!git_index_find(&pos, index, "exec_off"), + file, func, line, "Cannot find original index entry", NULL, 1); + + entry = git_index_get_byindex(index, pos); + + memcpy(&new_entry, entry, sizeof(new_entry)); + } + else + memset(&new_entry, 0x0, sizeof(git_index_entry)); + + new_entry.path = "filemodes/explicit_test"; + new_entry.mode = mode; + + if (from_file) + { + clar__assert(!git_index_add(index, &new_entry), + file, func, line, "Cannot add index entry", NULL, 1); + } + else + { + const char *content = "hey there\n"; + clar__assert(!git_index_add_from_buffer(index, &new_entry, content, strlen(content)), + file, func, line, "Cannot add index entry from buffer", NULL, 1); + } + + clar__assert(!git_index_find(&pos, index, "filemodes/explicit_test"), + file, func, line, "Cannot find new index entry", NULL, 1); + + entry = git_index_get_byindex(index, pos); + + clar__assert_equal(file, func, line, "Expected mode does not match index", + 1, "%07o", (unsigned int)entry->mode, (unsigned int)mode); +} + +void test_index_filemodes__explicit(void) +{ + git_index *index; + + /* These tests should run and work everywhere, as the filemode is + * given explicitly to git_index_add or git_index_add_from_buffer + */ + cl_repo_set_bool(g_repo, "core.filemode", false); + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* Each of these tests keeps overwriting the same file in the index. */ + /* 1 - add new 0644 entry */ + add_entry_and_check_mode(index, true, GIT_FILEMODE_BLOB); + + /* 2 - add 0755 entry over existing 0644 */ + add_entry_and_check_mode(index, true, GIT_FILEMODE_BLOB_EXECUTABLE); + + /* 3 - add 0644 entry over existing 0755 */ + add_entry_and_check_mode(index, true, GIT_FILEMODE_BLOB); + + /* 4 - add 0755 buffer entry over existing 0644 */ + add_entry_and_check_mode(index, false, GIT_FILEMODE_BLOB_EXECUTABLE); + + /* 5 - add 0644 buffer entry over existing 0755 */ + add_entry_and_check_mode(index, false, GIT_FILEMODE_BLOB); + + git_index_free(index); +} + +void test_index_filemodes__invalid(void) +{ + git_index *index; + git_index_entry entry; + const git_index_entry *dummy; + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* add a dummy file so that we have a valid id */ + cl_git_mkfile("./filemodes/dummy-file.txt", "new-file\n"); + cl_git_pass(git_index_add_bypath(index, "dummy-file.txt")); + cl_assert((dummy = git_index_get_bypath(index, "dummy-file.txt", 0))); + + GIT_INDEX_ENTRY_STAGE_SET(&entry, 0); + entry.path = "foo"; + entry.mode = GIT_OBJECT_BLOB; + git_oid_cpy(&entry.id, &dummy->id); + cl_git_fail(git_index_add(index, &entry)); + + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_index_add(index, &entry)); + + git_index_free(index); +} + +void test_index_filemodes__frombuffer_requires_files(void) +{ + git_index *index; + git_index_entry new_entry; + const git_index_entry *ret_entry; + const char *content = "hey there\n"; + + memset(&new_entry, 0, sizeof(new_entry)); + cl_git_pass(git_repository_index(&index, g_repo)); + + /* regular blob */ + new_entry.path = "dummy-file.txt"; + new_entry.mode = GIT_FILEMODE_BLOB; + + cl_git_pass(git_index_add_from_buffer(index, + &new_entry, content, strlen(content))); + + cl_assert((ret_entry = git_index_get_bypath(index, "dummy-file.txt", 0))); + cl_assert_equal_s("dummy-file.txt", ret_entry->path); + cl_assert_equal_i(GIT_FILEMODE_BLOB, ret_entry->mode); + + /* executable blob */ + new_entry.path = "dummy-file.txt"; + new_entry.mode = GIT_FILEMODE_BLOB_EXECUTABLE; + + cl_git_pass(git_index_add_from_buffer(index, + &new_entry, content, strlen(content))); + + cl_assert((ret_entry = git_index_get_bypath(index, "dummy-file.txt", 0))); + cl_assert_equal_s("dummy-file.txt", ret_entry->path); + cl_assert_equal_i(GIT_FILEMODE_BLOB_EXECUTABLE, ret_entry->mode); + + /* links are also acceptable */ + new_entry.path = "dummy-link.txt"; + new_entry.mode = GIT_FILEMODE_LINK; + + cl_git_pass(git_index_add_from_buffer(index, + &new_entry, content, strlen(content))); + + cl_assert((ret_entry = git_index_get_bypath(index, "dummy-link.txt", 0))); + cl_assert_equal_s("dummy-link.txt", ret_entry->path); + cl_assert_equal_i(GIT_FILEMODE_LINK, ret_entry->mode); + + /* trees are rejected */ + new_entry.path = "invalid_mode.txt"; + new_entry.mode = GIT_FILEMODE_TREE; + + cl_git_fail(git_index_add_from_buffer(index, + &new_entry, content, strlen(content))); + cl_assert_equal_p(NULL, git_index_get_bypath(index, "invalid_mode.txt", 0)); + + /* submodules are rejected */ + new_entry.path = "invalid_mode.txt"; + new_entry.mode = GIT_FILEMODE_COMMIT; + + cl_git_fail(git_index_add_from_buffer(index, + &new_entry, content, strlen(content))); + cl_assert_equal_p(NULL, git_index_get_bypath(index, "invalid_mode.txt", 0)); + + git_index_free(index); +} diff --git a/tests/libgit2/index/inmemory.c b/tests/libgit2/index/inmemory.c new file mode 100644 index 000000000..38e91e0fd --- /dev/null +++ b/tests/libgit2/index/inmemory.c @@ -0,0 +1,22 @@ +#include "clar_libgit2.h" + +void test_index_inmemory__can_create_an_inmemory_index(void) +{ + git_index *index; + + cl_git_pass(git_index_new(&index)); + cl_assert_equal_i(0, (int)git_index_entrycount(index)); + + git_index_free(index); +} + +void test_index_inmemory__cannot_add_bypath_to_an_inmemory_index(void) +{ + git_index *index; + + cl_git_pass(git_index_new(&index)); + + cl_assert_equal_i(GIT_ERROR, git_index_add_bypath(index, "test.txt")); + + git_index_free(index); +} diff --git a/tests/libgit2/index/names.c b/tests/libgit2/index/names.c new file mode 100644 index 000000000..369318b03 --- /dev/null +++ b/tests/libgit2/index/names.c @@ -0,0 +1,187 @@ +#include "clar_libgit2.h" +#include "index.h" +#include "git2/sys/index.h" +#include "git2/repository.h" +#include "../reset/reset_helpers.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "mergedrepo" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +/* Fixture setup and teardown */ +void test_index_names__initialize(void) +{ + repo = cl_git_sandbox_init("mergedrepo"); + git_repository_index(&repo_index, repo); +} + +void test_index_names__cleanup(void) +{ + git_index_free(repo_index); + repo_index = NULL; + + cl_git_sandbox_cleanup(); +} + +static void index_add_conflicts(void) +{ + git_index_entry entry = {{0}}; + const char *paths[][3] = { + { "ancestor", "ours", "theirs" }, + { "ancestor2", "ours2", "theirs2" }, + { "ancestor3", "ours3", "theirs3" } }; + const char **conflict; + size_t i; + + for (i = 0; i < ARRAY_SIZE(paths); i++) { + conflict = paths[i]; + + /* ancestor */ + entry.path = conflict[0]; + entry.mode = GIT_FILEMODE_BLOB; + GIT_INDEX_ENTRY_STAGE_SET(&entry, GIT_INDEX_STAGE_ANCESTOR); + git_oid_fromstr(&entry.id, "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81"); + cl_git_pass(git_index_add(repo_index, &entry)); + + /* ours */ + entry.path = conflict[1]; + entry.mode = GIT_FILEMODE_BLOB; + GIT_INDEX_ENTRY_STAGE_SET(&entry, GIT_INDEX_STAGE_OURS); + git_oid_fromstr(&entry.id, "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81"); + cl_git_pass(git_index_add(repo_index, &entry)); + + /* theirs */ + entry.path = conflict[2]; + entry.mode = GIT_FILEMODE_BLOB; + GIT_INDEX_ENTRY_STAGE_SET(&entry, GIT_INDEX_STAGE_THEIRS); + git_oid_fromstr(&entry.id, "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81"); + cl_git_pass(git_index_add(repo_index, &entry)); + } +} + +void test_index_names__add(void) +{ + const git_index_name_entry *conflict_name; + + index_add_conflicts(); + cl_git_pass(git_index_name_add(repo_index, "ancestor", "ours", "theirs")); + cl_git_pass(git_index_name_add(repo_index, "ancestor2", "ours2", NULL)); + cl_git_pass(git_index_name_add(repo_index, "ancestor3", NULL, "theirs3")); + + cl_assert(git_index_name_entrycount(repo_index) == 3); + + conflict_name = git_index_name_get_byindex(repo_index, 0); + cl_assert(strcmp(conflict_name->ancestor, "ancestor") == 0); + cl_assert(strcmp(conflict_name->ours, "ours") == 0); + cl_assert(strcmp(conflict_name->theirs, "theirs") == 0); + + conflict_name = git_index_name_get_byindex(repo_index, 1); + cl_assert(strcmp(conflict_name->ancestor, "ancestor2") == 0); + cl_assert(strcmp(conflict_name->ours, "ours2") == 0); + cl_assert(conflict_name->theirs == NULL); + + conflict_name = git_index_name_get_byindex(repo_index, 2); + cl_assert(strcmp(conflict_name->ancestor, "ancestor3") == 0); + cl_assert(conflict_name->ours == NULL); + cl_assert(strcmp(conflict_name->theirs, "theirs3") == 0); + + cl_git_pass(git_index_write(repo_index)); +} + +void test_index_names__roundtrip(void) +{ + const git_index_name_entry *conflict_name; + + cl_git_pass(git_index_name_add(repo_index, "ancestor", "ours", "theirs")); + cl_git_pass(git_index_name_add(repo_index, "ancestor2", "ours2", NULL)); + cl_git_pass(git_index_name_add(repo_index, "ancestor3", NULL, "theirs3")); + + cl_git_pass(git_index_write(repo_index)); + git_index_clear(repo_index); + cl_assert(git_index_name_entrycount(repo_index) == 0); + + cl_git_pass(git_index_read(repo_index, true)); + cl_assert(git_index_name_entrycount(repo_index) == 3); + + conflict_name = git_index_name_get_byindex(repo_index, 0); + cl_assert(strcmp(conflict_name->ancestor, "ancestor") == 0); + cl_assert(strcmp(conflict_name->ours, "ours") == 0); + cl_assert(strcmp(conflict_name->theirs, "theirs") == 0); + + conflict_name = git_index_name_get_byindex(repo_index, 1); + cl_assert(strcmp(conflict_name->ancestor, "ancestor2") == 0); + cl_assert(strcmp(conflict_name->ours, "ours2") == 0); + cl_assert(conflict_name->theirs == NULL); + + conflict_name = git_index_name_get_byindex(repo_index, 2); + cl_assert(strcmp(conflict_name->ancestor, "ancestor3") == 0); + cl_assert(conflict_name->ours == NULL); + cl_assert(strcmp(conflict_name->theirs, "theirs3") == 0); +} + +void test_index_names__cleaned_on_reset_hard(void) +{ + git_object *target; + + cl_git_pass(git_revparse_single(&target, repo, "3a34580")); + + test_index_names__add(); + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); + cl_assert(git_index_name_entrycount(repo_index) == 0); + + git_object_free(target); +} + +void test_index_names__cleaned_on_reset_mixed(void) +{ + git_object *target; + + cl_git_pass(git_revparse_single(&target, repo, "3a34580")); + + test_index_names__add(); + cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL)); + cl_assert(git_index_name_entrycount(repo_index) == 0); + + git_object_free(target); +} + +void test_index_names__cleaned_on_checkout_tree(void) +{ + git_oid oid; + git_object *obj; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_UPDATE_ONLY; + + test_index_names__add(); + cl_git_pass(git_reference_name_to_id(&oid, repo, "refs/heads/master")); + cl_git_pass(git_object_lookup(&obj, repo, &oid, GIT_OBJECT_ANY)); + cl_git_pass(git_checkout_tree(repo, obj, &opts)); + cl_assert_equal_sz(0, git_index_name_entrycount(repo_index)); + + git_object_free(obj); +} + +void test_index_names__cleaned_on_checkout_head(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_UPDATE_ONLY; + + test_index_names__add(); + cl_git_pass(git_checkout_head(repo, &opts)); + cl_assert_equal_sz(0, git_index_name_entrycount(repo_index)); +} + +void test_index_names__retained_on_checkout_index(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_UPDATE_ONLY; + + test_index_names__add(); + cl_git_pass(git_checkout_index(repo, repo_index, &opts)); + cl_assert(git_index_name_entrycount(repo_index) > 0); +} diff --git a/tests/libgit2/index/nsec.c b/tests/libgit2/index/nsec.c new file mode 100644 index 000000000..3efd855a7 --- /dev/null +++ b/tests/libgit2/index/nsec.c @@ -0,0 +1,129 @@ +#include "clar_libgit2.h" +#include "index.h" +#include "git2/sys/index.h" +#include "git2/repository.h" +#include "../reset/reset_helpers.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "nsecs" + +/* Fixture setup and teardown */ +void test_index_nsec__initialize(void) +{ + repo = cl_git_sandbox_init("nsecs"); + git_repository_index(&repo_index, repo); +} + +void test_index_nsec__cleanup(void) +{ + git_index_free(repo_index); + repo_index = NULL; + + cl_git_sandbox_cleanup(); +} + +static bool try_create_file_with_nsec_timestamp(const char *path) +{ + struct stat st; + int try; + + /* retry a few times to avoid nanos *actually* equal 0 race condition */ + for (try = 0; try < 3; try++) { + cl_git_mkfile(path, "This is hopefully a file with nanoseconds!"); + + cl_must_pass(p_stat(path, &st)); + + if (st.st_ctime_nsec && st.st_mtime_nsec) + return true; + } + + return false; +} + +/* try to determine if the underlying filesystem supports a resolution + * higher than a single second. (i'm looking at you, hfs+) + */ +static bool should_expect_nsecs(void) +{ + git_str nsec_path = GIT_STR_INIT; + bool expect; + + git_str_joinpath(&nsec_path, clar_sandbox_path(), "nsec_test"); + + expect = try_create_file_with_nsec_timestamp(nsec_path.ptr); + + cl_must_pass(p_unlink(nsec_path.ptr)); + + git_str_dispose(&nsec_path); + + return expect; +} + +static bool has_nsecs(void) +{ + const git_index_entry *entry; + size_t i; + bool has_nsecs = false; + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + entry = git_index_get_byindex(repo_index, i); + + if (entry->ctime.nanoseconds || entry->mtime.nanoseconds) { + has_nsecs = true; + break; + } + } + + return has_nsecs; +} + +void test_index_nsec__has_nanos(void) +{ + cl_assert_equal_b(true, has_nsecs()); +} + +void test_index_nsec__staging_maintains_other_nanos(void) +{ + const git_index_entry *entry; + bool expect_nsec, test_file_has_nsec; + + expect_nsec = should_expect_nsecs(); + test_file_has_nsec = try_create_file_with_nsec_timestamp("nsecs/a.txt"); + + cl_assert_equal_b(expect_nsec, test_file_has_nsec); + + cl_git_pass(git_index_add_bypath(repo_index, "a.txt")); + cl_git_pass(git_index_write(repo_index)); + + cl_git_pass(git_index_write(repo_index)); + + git_index_read(repo_index, 1); + cl_assert_equal_b(true, has_nsecs()); + + cl_assert((entry = git_index_get_bypath(repo_index, "a.txt", 0))); + + /* if we are writing nanoseconds to the index, expect them to be + * nonzero. + */ + if (expect_nsec) { + cl_assert(entry->ctime.nanoseconds != 0); + cl_assert(entry->mtime.nanoseconds != 0); + } else { + cl_assert_equal_i(0, entry->ctime.nanoseconds); + cl_assert_equal_i(0, entry->mtime.nanoseconds); + } +} + +void test_index_nsec__status_doesnt_clear_nsecs(void) +{ + git_status_list *statuslist; + + cl_git_pass(git_status_list_new(&statuslist, repo, NULL)); + + git_index_read(repo_index, 1); + cl_assert_equal_b(true, has_nsecs()); + + git_status_list_free(statuslist); +} diff --git a/tests/libgit2/index/racy.c b/tests/libgit2/index/racy.c new file mode 100644 index 000000000..07b3b73d4 --- /dev/null +++ b/tests/libgit2/index/racy.c @@ -0,0 +1,323 @@ +#include "clar_libgit2.h" +#include "../checkout/checkout_helpers.h" + +#include "index.h" +#include "repository.h" + +static git_repository *g_repo; + +void test_index_racy__initialize(void) +{ + cl_git_pass(git_repository_init(&g_repo, "diff_racy", false)); +} + +void test_index_racy__cleanup(void) +{ + git_repository_free(g_repo); + g_repo = NULL; + + cl_fixture_cleanup("diff_racy"); +} + +void test_index_racy__diff(void) +{ + git_index *index; + git_diff *diff; + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "A")); + cl_git_mkfile(path.ptr, "A"); + + /* Put 'A' into the index */ + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, "A")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + + /* Change its contents quickly, so we get the same timestamp */ + cl_git_mkfile(path.ptr, "B"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + + git_index_free(index); + git_diff_free(diff); + git_str_dispose(&path); +} + +void test_index_racy__write_index_just_after_file(void) +{ + git_index *index; + git_diff *diff; + git_str path = GIT_STR_INIT; + struct p_timeval times[2]; + + /* Make sure we do have a timestamp */ + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "A")); + cl_git_mkfile(path.ptr, "A"); + /* Force the file's timestamp to be a second after we wrote the index */ + times[0].tv_sec = index->stamp.mtime.tv_sec + 1; + times[0].tv_usec = index->stamp.mtime.tv_nsec / 1000; + times[1].tv_sec = index->stamp.mtime.tv_sec + 1; + times[1].tv_usec = index->stamp.mtime.tv_nsec / 1000; + cl_git_pass(p_utimes(path.ptr, times)); + + /* + * Put 'A' into the index, the size field will be filled, + * because the index' on-disk timestamp does not match the + * file's timestamp. + */ + cl_git_pass(git_index_add_bypath(index, "A")); + cl_git_pass(git_index_write(index)); + + cl_git_mkfile(path.ptr, "B"); + /* + * Pretend this index' modification happened a second after the + * file update, and rewrite the file in that same second. + */ + times[0].tv_sec = index->stamp.mtime.tv_sec + 2; + times[0].tv_usec = index->stamp.mtime.tv_nsec / 1000; + times[1].tv_sec = index->stamp.mtime.tv_sec + 2; + times[0].tv_usec = index->stamp.mtime.tv_nsec / 1000; + + cl_git_pass(p_utimes(git_index_path(index), times)); + cl_git_pass(p_utimes(path.ptr, times)); + + cl_git_pass(git_index_read(index, true)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + + git_str_dispose(&path); + git_diff_free(diff); + git_index_free(index); +} + + +static void setup_race(void) +{ + git_str path = GIT_STR_INIT; + git_index *index; + git_index_entry *entry; + struct stat st; + + /* Make sure we do have a timestamp */ + cl_git_pass(git_repository_index__weakptr(&index, g_repo)); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "A")); + + cl_git_mkfile(path.ptr, "A"); + cl_git_pass(git_index_add_bypath(index, "A")); + + cl_git_mkfile(path.ptr, "B"); + cl_git_pass(git_index_write(index)); + + cl_git_mkfile(path.ptr, ""); + + cl_git_pass(p_stat(path.ptr, &st)); + cl_assert(entry = (git_index_entry *)git_index_get_bypath(index, "A", 0)); + + /* force a race */ + entry->mtime.seconds = (int32_t)st.st_mtime; + entry->mtime.nanoseconds = (int32_t)st.st_mtime_nsec; + + git_str_dispose(&path); +} + +void test_index_racy__smudges_index_entry_on_save(void) +{ + git_index *index; + const git_index_entry *entry; + + setup_race(); + + /* write the index, which will smudge anything that had the same timestamp + * as the index when the index was loaded. that way future loads of the + * index (with the new timestamp) will know that these files were not + * clean. + */ + + cl_git_pass(git_repository_index__weakptr(&index, g_repo)); + cl_git_pass(git_index_write(index)); + + cl_assert(entry = git_index_get_bypath(index, "A", 0)); + cl_assert_equal_i(0, entry->file_size); +} + +void test_index_racy__detects_diff_of_change_in_identical_timestamp(void) +{ + git_index *index; + git_diff *diff; + + cl_git_pass(git_repository_index__weakptr(&index, g_repo)); + + setup_race(); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL)); + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + + git_diff_free(diff); +} + +static void setup_uptodate_files(void) +{ + git_str path = GIT_STR_INIT; + git_index *index; + const git_index_entry *a_entry; + git_index_entry new_entry = {{0}}; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "A")); + cl_git_mkfile(path.ptr, "A"); + + /* Put 'A' into the index */ + cl_git_pass(git_index_add_bypath(index, "A")); + + cl_assert((a_entry = git_index_get_bypath(index, "A", 0))); + + /* Put 'B' into the index */ + new_entry.path = "B"; + new_entry.mode = GIT_FILEMODE_BLOB; + git_oid_cpy(&new_entry.id, &a_entry->id); + cl_git_pass(git_index_add(index, &new_entry)); + + /* Put 'C' into the index */ + new_entry.path = "C"; + new_entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_index_add_from_buffer(index, &new_entry, "hello!\n", 7)); + + git_index_free(index); + git_str_dispose(&path); +} + +void test_index_racy__adding_to_index_is_uptodate(void) +{ + git_index *index; + const git_index_entry *entry; + + setup_uptodate_files(); + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* ensure that they're all uptodate */ + cl_assert((entry = git_index_get_bypath(index, "A", 0))); + cl_assert_equal_i(GIT_INDEX_ENTRY_UPTODATE, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(index, "B", 0))); + cl_assert_equal_i(GIT_INDEX_ENTRY_UPTODATE, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(index, "C", 0))); + cl_assert_equal_i(GIT_INDEX_ENTRY_UPTODATE, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); + + cl_git_pass(git_index_write(index)); + + git_index_free(index); +} + +void test_index_racy__reading_clears_uptodate_bit(void) +{ + git_index *index; + const git_index_entry *entry; + + setup_uptodate_files(); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_index_read(index, true)); + + /* ensure that no files are uptodate */ + cl_assert((entry = git_index_get_bypath(index, "A", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(index, "B", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(index, "C", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); + + git_index_free(index); +} + +void test_index_racy__read_tree_clears_uptodate_bit(void) +{ + git_index *index; + git_tree *tree; + const git_index_entry *entry; + git_oid id; + + setup_uptodate_files(); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_write_tree_to(&id, index, g_repo)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + cl_git_pass(git_index_read_tree(index, tree)); + + /* ensure that no files are uptodate */ + cl_assert((entry = git_index_get_bypath(index, "A", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(index, "B", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(index, "C", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); + + git_tree_free(tree); + git_index_free(index); +} + +void test_index_racy__read_index_smudges(void) +{ + git_index *index, *newindex; + const git_index_entry *entry; + + /* if we are reading an index into our new index, ensure that any + * racy entries in the index that we're reading are smudged so that + * we don't propagate their timestamps without further investigation. + */ + setup_race(); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_new(&newindex)); + cl_git_pass(git_index_read_index(newindex, index)); + + cl_assert(entry = git_index_get_bypath(newindex, "A", 0)); + cl_assert_equal_i(0, entry->file_size); + + git_index_free(index); + git_index_free(newindex); +} + +void test_index_racy__read_index_clears_uptodate_bit(void) +{ + git_index *index, *newindex; + const git_index_entry *entry; + + setup_uptodate_files(); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_new(&newindex)); + cl_git_pass(git_index_read_index(newindex, index)); + + /* ensure that files brought in from the other index are not uptodate */ + cl_assert((entry = git_index_get_bypath(newindex, "A", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(newindex, "B", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); + + cl_assert((entry = git_index_get_bypath(newindex, "C", 0))); + cl_assert_equal_i(0, (entry->flags_extended & GIT_INDEX_ENTRY_UPTODATE)); + + git_index_free(index); + git_index_free(newindex); +} diff --git a/tests/libgit2/index/read_index.c b/tests/libgit2/index/read_index.c new file mode 100644 index 000000000..836c12b0e --- /dev/null +++ b/tests/libgit2/index/read_index.c @@ -0,0 +1,232 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "index.h" +#include "conflicts.h" + +static git_repository *_repo; +static git_index *_index; + +void test_index_read_index__initialize(void) +{ + git_object *head; + git_reference *head_ref; + + _repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_revparse_ext(&head, &head_ref, _repo, "HEAD")); + cl_git_pass(git_reset(_repo, head, GIT_RESET_HARD, NULL)); + cl_git_pass(git_repository_index(&_index, _repo)); + + git_reference_free(head_ref); + git_object_free(head); +} + +void test_index_read_index__cleanup(void) +{ + git_index_free(_index); + cl_git_sandbox_cleanup(); +} + +void test_index_read_index__maintains_stat_cache(void) +{ + git_index *new_index; + git_oid index_id; + git_index_entry new_entry; + const git_index_entry *e; + git_tree *tree; + size_t i; + + cl_assert_equal_i(4, git_index_entrycount(_index)); + + /* write-tree */ + cl_git_pass(git_index_write_tree(&index_id, _index)); + + /* read-tree, then read index */ + git_tree_lookup(&tree, _repo, &index_id); + cl_git_pass(git_index_new(&new_index)); + cl_git_pass(git_index_read_tree(new_index, tree)); + git_tree_free(tree); + + /* add a new entry that will not have stat data */ + memset(&new_entry, 0, sizeof(git_index_entry)); + new_entry.path = "Hello"; + git_oid_fromstr(&new_entry.id, "0123456789012345678901234567890123456789"); + new_entry.file_size = 1234; + new_entry.mode = 0100644; + cl_git_pass(git_index_add(new_index, &new_entry)); + cl_assert_equal_i(5, git_index_entrycount(new_index)); + + cl_git_pass(git_index_read_index(_index, new_index)); + git_index_free(new_index); + + cl_assert_equal_i(5, git_index_entrycount(_index)); + + for (i = 0; i < git_index_entrycount(_index); i++) { + e = git_index_get_byindex(_index, i); + + if (strcmp(e->path, "Hello") == 0) { + cl_assert_equal_i(0, e->ctime.seconds); + cl_assert_equal_i(0, e->mtime.seconds); + } else { + cl_assert(0 != e->ctime.seconds); + cl_assert(0 != e->mtime.seconds); + } + } +} + +static bool roundtrip_with_read_index(const char *tree_idstr) +{ + git_oid tree_id, new_tree_id; + git_tree *tree; + git_index *tree_index; + + cl_git_pass(git_oid_fromstr(&tree_id, tree_idstr)); + cl_git_pass(git_tree_lookup(&tree, _repo, &tree_id)); + cl_git_pass(git_index_new(&tree_index)); + cl_git_pass(git_index_read_tree(tree_index, tree)); + cl_git_pass(git_index_read_index(_index, tree_index)); + cl_git_pass(git_index_write_tree(&new_tree_id, _index)); + + git_tree_free(tree); + git_index_free(tree_index); + + return git_oid_equal(&tree_id, &new_tree_id); +} + +void test_index_read_index__produces_treesame_indexes(void) +{ + roundtrip_with_read_index("53fc32d17276939fc79ed05badaef2db09990016"); + roundtrip_with_read_index("944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); + roundtrip_with_read_index("1810dff58d8a660512d4832e740f692884338ccd"); + roundtrip_with_read_index("d52a8fe84ceedf260afe4f0287bbfca04a117e83"); + roundtrip_with_read_index("c36d8ea75da8cb510fcb0c408c1d7e53f9a99dbe"); + roundtrip_with_read_index("7b2417a23b63e1fdde88c80e14b33247c6e5785a"); + roundtrip_with_read_index("f82a8eb4cb20e88d1030fd10d89286215a715396"); + roundtrip_with_read_index("fd093bff70906175335656e6ce6ae05783708765"); + roundtrip_with_read_index("ae90f12eea699729ed24555e40b9fd669da12a12"); +} + +void test_index_read_index__read_and_writes(void) +{ + git_oid tree_id, new_tree_id; + git_tree *tree; + git_index *tree_index, *new_index; + + cl_git_pass(git_oid_fromstr(&tree_id, "ae90f12eea699729ed24555e40b9fd669da12a12")); + cl_git_pass(git_tree_lookup(&tree, _repo, &tree_id)); + cl_git_pass(git_index_new(&tree_index)); + cl_git_pass(git_index_read_tree(tree_index, tree)); + cl_git_pass(git_index_read_index(_index, tree_index)); + cl_git_pass(git_index_write(_index)); + + cl_git_pass(git_index_open(&new_index, git_index_path(_index))); + cl_git_pass(git_index_write_tree_to(&new_tree_id, new_index, _repo)); + + cl_assert_equal_oid(&tree_id, &new_tree_id); + + git_tree_free(tree); + git_index_free(tree_index); + git_index_free(new_index); +} + +static void add_conflicts(git_index *index, const char *filename) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + static int conflict_idx = 0; + char *ancestor_ids[] = + { CONFLICTS_ONE_ANCESTOR_OID, CONFLICTS_TWO_ANCESTOR_OID }; + char *our_ids[] = + { CONFLICTS_ONE_OUR_OID, CONFLICTS_TWO_OUR_OID }; + char *their_ids[] = + { CONFLICTS_ONE_THEIR_OID, CONFLICTS_TWO_THEIR_OID }; + + conflict_idx = (conflict_idx + 1) % 2; + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = filename; + ancestor_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 1); + git_oid_fromstr(&ancestor_entry.id, ancestor_ids[conflict_idx]); + + our_entry.path = filename; + our_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&our_entry, 2); + git_oid_fromstr(&our_entry.id, our_ids[conflict_idx]); + + their_entry.path = filename; + their_entry.mode = 0100644; + GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 2); + git_oid_fromstr(&their_entry.id, their_ids[conflict_idx]); + + cl_git_pass(git_index_conflict_add(index, &ancestor_entry, + &our_entry, &their_entry)); +} + +void test_index_read_index__handles_conflicts(void) +{ + git_oid tree_id; + git_tree *tree; + git_index *index, *new_index; + git_index_conflict_iterator *conflict_iterator; + const git_index_entry *ancestor, *ours, *theirs; + + cl_git_pass(git_oid_fromstr(&tree_id, "ae90f12eea699729ed24555e40b9fd669da12a12")); + cl_git_pass(git_tree_lookup(&tree, _repo, &tree_id)); + cl_git_pass(git_index_new(&index)); + cl_git_pass(git_index_new(&new_index)); + cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_read_tree(new_index, tree)); + + /* put some conflicts in only the old side, these should be removed */ + add_conflicts(index, "orig_side-1.txt"); + add_conflicts(index, "orig_side-2.txt"); + + /* put some conflicts in both indexes, these should be unchanged */ + add_conflicts(index, "both_sides-1.txt"); + add_conflicts(new_index, "both_sides-1.txt"); + add_conflicts(index, "both_sides-2.txt"); + add_conflicts(new_index, "both_sides-2.txt"); + + /* put some conflicts in the new index, these should be added */ + add_conflicts(new_index, "new_side-1.txt"); + add_conflicts(new_index, "new_side-2.txt"); + + cl_git_pass(git_index_read_index(index, new_index)); + cl_git_pass(git_index_conflict_iterator_new(&conflict_iterator, index)); + + cl_git_pass(git_index_conflict_next( + &ancestor, &ours, &theirs, conflict_iterator)); + cl_assert_equal_s("both_sides-1.txt", ancestor->path); + cl_assert_equal_s("both_sides-1.txt", ours->path); + cl_assert_equal_s("both_sides-1.txt", theirs->path); + + cl_git_pass(git_index_conflict_next( + &ancestor, &ours, &theirs, conflict_iterator)); + cl_assert_equal_s("both_sides-2.txt", ancestor->path); + cl_assert_equal_s("both_sides-2.txt", ours->path); + cl_assert_equal_s("both_sides-2.txt", theirs->path); + + cl_git_pass(git_index_conflict_next( + &ancestor, &ours, &theirs, conflict_iterator)); + cl_assert_equal_s("new_side-1.txt", ancestor->path); + cl_assert_equal_s("new_side-1.txt", ours->path); + cl_assert_equal_s("new_side-1.txt", theirs->path); + + cl_git_pass(git_index_conflict_next( + &ancestor, &ours, &theirs, conflict_iterator)); + cl_assert_equal_s("new_side-2.txt", ancestor->path); + cl_assert_equal_s("new_side-2.txt", ours->path); + cl_assert_equal_s("new_side-2.txt", theirs->path); + + + cl_git_fail_with(GIT_ITEROVER, git_index_conflict_next( + &ancestor, &ours, &theirs, conflict_iterator)); + + git_index_conflict_iterator_free(conflict_iterator); + + git_tree_free(tree); + git_index_free(new_index); + git_index_free(index); +} diff --git a/tests/libgit2/index/read_tree.c b/tests/libgit2/index/read_tree.c new file mode 100644 index 000000000..0e1882818 --- /dev/null +++ b/tests/libgit2/index/read_tree.c @@ -0,0 +1,46 @@ +#include "clar_libgit2.h" +#include "posix.h" + +/* Test that reading and writing a tree is a no-op */ +void test_index_read_tree__read_write_involution(void) +{ + git_repository *repo; + git_index *index; + git_oid tree_oid; + git_tree *tree; + git_oid expected; + + p_mkdir("read_tree", 0700); + + cl_git_pass(git_repository_init(&repo, "./read_tree", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + cl_assert(git_index_entrycount(index) == 0); + + p_mkdir("./read_tree/abc", 0700); + + /* Sort order: '-' < '/' < '_' */ + cl_git_mkfile("./read_tree/abc-d", NULL); + cl_git_mkfile("./read_tree/abc/d", NULL); + cl_git_mkfile("./read_tree/abc_d", NULL); + + cl_git_pass(git_index_add_bypath(index, "abc-d")); + cl_git_pass(git_index_add_bypath(index, "abc_d")); + cl_git_pass(git_index_add_bypath(index, "abc/d")); + + /* write-tree */ + cl_git_pass(git_index_write_tree(&expected, index)); + + /* read-tree */ + git_tree_lookup(&tree, repo, &expected); + cl_git_pass(git_index_read_tree(index, tree)); + git_tree_free(tree); + + cl_git_pass(git_index_write_tree(&tree_oid, index)); + cl_assert_equal_oid(&expected, &tree_oid); + + git_index_free(index); + git_repository_free(repo); + + cl_fixture_cleanup("read_tree"); +} diff --git a/tests/libgit2/index/rename.c b/tests/libgit2/index/rename.c new file mode 100644 index 000000000..86eaf0053 --- /dev/null +++ b/tests/libgit2/index/rename.c @@ -0,0 +1,86 @@ +#include "clar_libgit2.h" +#include "posix.h" + +void test_index_rename__single_file(void) +{ + git_repository *repo; + git_index *index; + size_t position; + git_oid expected; + const git_index_entry *entry; + + p_mkdir("rename", 0700); + + cl_git_pass(git_repository_init(&repo, "./rename", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + cl_assert(git_index_entrycount(index) == 0); + + cl_git_mkfile("./rename/lame.name.txt", "new_file\n"); + + /* This should add a new blob to the object database in 'd4/fa8600b4f37d7516bef4816ae2c64dbf029e3a' */ + cl_git_pass(git_index_add_bypath(index, "lame.name.txt")); + cl_assert(git_index_entrycount(index) == 1); + + cl_git_pass(git_oid_fromstr(&expected, "d4fa8600b4f37d7516bef4816ae2c64dbf029e3a")); + + cl_assert(!git_index_find(&position, index, "lame.name.txt")); + + entry = git_index_get_byindex(index, position); + cl_assert_equal_oid(&expected, &entry->id); + + /* This removes the entry from the index, but not from the object database */ + cl_git_pass(git_index_remove(index, "lame.name.txt", 0)); + cl_assert(git_index_entrycount(index) == 0); + + p_rename("./rename/lame.name.txt", "./rename/fancy.name.txt"); + + cl_git_pass(git_index_add_bypath(index, "fancy.name.txt")); + cl_assert(git_index_entrycount(index) == 1); + + cl_assert(!git_index_find(&position, index, "fancy.name.txt")); + + entry = git_index_get_byindex(index, position); + cl_assert_equal_oid(&expected, &entry->id); + + git_index_free(index); + git_repository_free(repo); + + cl_fixture_cleanup("rename"); +} + +void test_index_rename__casechanging(void) +{ + git_repository *repo; + git_index *index; + const git_index_entry *entry; + git_index_entry new = {{0}}; + + p_mkdir("rename", 0700); + + cl_git_pass(git_repository_init(&repo, "./rename", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_mkfile("./rename/lame.name.txt", "new_file\n"); + + cl_git_pass(git_index_add_bypath(index, "lame.name.txt")); + cl_assert_equal_i(1, git_index_entrycount(index)); + cl_assert((entry = git_index_get_bypath(index, "lame.name.txt", 0))); + + memcpy(&new, entry, sizeof(git_index_entry)); + new.path = "LAME.name.TXT"; + + cl_git_pass(git_index_add(index, &new)); + cl_assert((entry = git_index_get_bypath(index, "LAME.name.TXT", 0))); + + if (cl_repo_get_bool(repo, "core.ignorecase")) + cl_assert_equal_i(1, git_index_entrycount(index)); + else + cl_assert_equal_i(2, git_index_entrycount(index)); + + git_index_free(index); + git_repository_free(repo); + + cl_fixture_cleanup("rename"); +} + diff --git a/tests/libgit2/index/reuc.c b/tests/libgit2/index/reuc.c new file mode 100644 index 000000000..1f95c4136 --- /dev/null +++ b/tests/libgit2/index/reuc.c @@ -0,0 +1,376 @@ +#include "clar_libgit2.h" +#include "index.h" +#include "git2/sys/index.h" +#include "git2/repository.h" +#include "../reset/reset_helpers.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "mergedrepo" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +#define ONE_ANCESTOR_OID "478871385b9cd03908c5383acfd568bef023c6b3" +#define ONE_OUR_OID "4458b8bc9e72b6c8755ae456f60e9844d0538d8c" +#define ONE_THEIR_OID "8b72416545c7e761b64cecad4f1686eae4078aa8" + +#define TWO_ANCESTOR_OID "9d81f82fccc7dcd7de7a1ffead1815294c2e092c" +#define TWO_OUR_OID "8f3c06cff9a83757cec40c80bc9bf31a2582bde9" +#define TWO_THEIR_OID "887b153b165d32409c70163e0f734c090f12f673" + +/* Fixture setup and teardown */ +void test_index_reuc__initialize(void) +{ + repo = cl_git_sandbox_init("mergedrepo"); + git_repository_index(&repo_index, repo); +} + +void test_index_reuc__cleanup(void) +{ + git_index_free(repo_index); + repo_index = NULL; + + cl_git_sandbox_cleanup(); +} + +void test_index_reuc__add(void) +{ + git_oid ancestor_oid, our_oid, their_oid; + const git_index_reuc_entry *reuc; + + git_oid_fromstr(&ancestor_oid, ONE_ANCESTOR_OID); + git_oid_fromstr(&our_oid, ONE_OUR_OID); + git_oid_fromstr(&their_oid, ONE_THEIR_OID); + + cl_git_pass(git_index_reuc_add(repo_index, "newfile.txt", + 0100644, &ancestor_oid, + 0100644, &our_oid, + 0100644, &their_oid)); + + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "newfile.txt")); + + cl_assert_equal_s("newfile.txt", reuc->path); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + cl_assert_equal_oid(&reuc->oid[0], &ancestor_oid); + cl_assert_equal_oid(&reuc->oid[1], &our_oid); + cl_assert_equal_oid(&reuc->oid[2], &their_oid); + + cl_git_pass(git_index_write(repo_index)); +} + +void test_index_reuc__add_no_ancestor(void) +{ + git_oid ancestor_oid, our_oid, their_oid; + const git_index_reuc_entry *reuc; + + memset(&ancestor_oid, 0x0, sizeof(git_oid)); + git_oid_fromstr(&our_oid, ONE_OUR_OID); + git_oid_fromstr(&their_oid, ONE_THEIR_OID); + + cl_git_pass(git_index_reuc_add(repo_index, "newfile.txt", + 0, NULL, + 0100644, &our_oid, + 0100644, &their_oid)); + + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "newfile.txt")); + + cl_assert_equal_s("newfile.txt", reuc->path); + cl_assert(reuc->mode[0] == 0); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + cl_assert_equal_oid(&reuc->oid[0], &ancestor_oid); + cl_assert_equal_oid(&reuc->oid[1], &our_oid); + cl_assert_equal_oid(&reuc->oid[2], &their_oid); + + cl_git_pass(git_index_write(repo_index)); +} + +void test_index_reuc__read_bypath(void) +{ + const git_index_reuc_entry *reuc; + git_oid oid; + + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "two.txt")); + + cl_assert_equal_s("two.txt", reuc->path); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert_equal_oid(&reuc->oid[0], &oid); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert_equal_oid(&reuc->oid[1], &oid); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert_equal_oid(&reuc->oid[2], &oid); + + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "one.txt")); + + cl_assert_equal_s("one.txt", reuc->path); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, ONE_ANCESTOR_OID); + cl_assert_equal_oid(&reuc->oid[0], &oid); + git_oid_fromstr(&oid, ONE_OUR_OID); + cl_assert_equal_oid(&reuc->oid[1], &oid); + git_oid_fromstr(&oid, ONE_THEIR_OID); + cl_assert_equal_oid(&reuc->oid[2], &oid); +} + +void test_index_reuc__ignore_case(void) +{ + const git_index_reuc_entry *reuc; + git_oid oid; + int index_caps; + + index_caps = git_index_caps(repo_index); + + index_caps &= ~GIT_INDEX_CAPABILITY_IGNORE_CASE; + cl_git_pass(git_index_set_caps(repo_index, index_caps)); + + cl_assert(!git_index_reuc_get_bypath(repo_index, "TWO.txt")); + + index_caps |= GIT_INDEX_CAPABILITY_IGNORE_CASE; + cl_git_pass(git_index_set_caps(repo_index, index_caps)); + + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "TWO.txt")); + + cl_assert_equal_s("two.txt", reuc->path); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert_equal_oid(&reuc->oid[0], &oid); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert_equal_oid(&reuc->oid[1], &oid); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert_equal_oid(&reuc->oid[2], &oid); +} + +void test_index_reuc__read_byindex(void) +{ + const git_index_reuc_entry *reuc; + git_oid oid; + + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); + + cl_assert_equal_s("one.txt", reuc->path); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, ONE_ANCESTOR_OID); + cl_assert_equal_oid(&reuc->oid[0], &oid); + git_oid_fromstr(&oid, ONE_OUR_OID); + cl_assert_equal_oid(&reuc->oid[1], &oid); + git_oid_fromstr(&oid, ONE_THEIR_OID); + cl_assert_equal_oid(&reuc->oid[2], &oid); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 1)); + + cl_assert_equal_s("two.txt", reuc->path); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert_equal_oid(&reuc->oid[0], &oid); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert_equal_oid(&reuc->oid[1], &oid); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert_equal_oid(&reuc->oid[2], &oid); +} + +void test_index_reuc__updates_existing(void) +{ + const git_index_reuc_entry *reuc; + git_oid ancestor_oid, our_oid, their_oid, oid; + int index_caps; + + git_index_clear(repo_index); + + index_caps = git_index_caps(repo_index); + + index_caps |= GIT_INDEX_CAPABILITY_IGNORE_CASE; + cl_git_pass(git_index_set_caps(repo_index, index_caps)); + + git_oid_fromstr(&ancestor_oid, TWO_ANCESTOR_OID); + git_oid_fromstr(&our_oid, TWO_OUR_OID); + git_oid_fromstr(&their_oid, TWO_THEIR_OID); + + cl_git_pass(git_index_reuc_add(repo_index, "two.txt", + 0100644, &ancestor_oid, + 0100644, &our_oid, + 0100644, &their_oid)); + + cl_git_pass(git_index_reuc_add(repo_index, "TWO.txt", + 0100644, &our_oid, + 0100644, &their_oid, + 0100644, &ancestor_oid)); + + cl_assert_equal_i(1, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); + + cl_assert_equal_s("TWO.txt", reuc->path); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert_equal_oid(&reuc->oid[0], &oid); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert_equal_oid(&reuc->oid[1], &oid); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert_equal_oid(&reuc->oid[2], &oid); +} + +void test_index_reuc__remove(void) +{ + git_oid oid; + const git_index_reuc_entry *reuc; + + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + cl_git_pass(git_index_reuc_remove(repo_index, 0)); + cl_git_fail(git_index_reuc_remove(repo_index, 1)); + + cl_assert_equal_i(1, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); + + cl_assert_equal_s("two.txt", reuc->path); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert_equal_oid(&reuc->oid[0], &oid); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert_equal_oid(&reuc->oid[1], &oid); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert_equal_oid(&reuc->oid[2], &oid); +} + +void test_index_reuc__write(void) +{ + git_oid ancestor_oid, our_oid, their_oid; + const git_index_reuc_entry *reuc; + + git_index_clear(repo_index); + + /* Write out of order to ensure sorting is correct */ + git_oid_fromstr(&ancestor_oid, TWO_ANCESTOR_OID); + git_oid_fromstr(&our_oid, TWO_OUR_OID); + git_oid_fromstr(&their_oid, TWO_THEIR_OID); + + cl_git_pass(git_index_reuc_add(repo_index, "two.txt", + 0100644, &ancestor_oid, + 0100644, &our_oid, + 0100644, &their_oid)); + + git_oid_fromstr(&ancestor_oid, ONE_ANCESTOR_OID); + git_oid_fromstr(&our_oid, ONE_OUR_OID); + git_oid_fromstr(&their_oid, ONE_THEIR_OID); + + cl_git_pass(git_index_reuc_add(repo_index, "one.txt", + 0100644, &ancestor_oid, + 0100644, &our_oid, + 0100644, &their_oid)); + + cl_git_pass(git_index_write(repo_index)); + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + /* ensure sort order was round-tripped correct */ + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); + cl_assert_equal_s("one.txt", reuc->path); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 1)); + cl_assert_equal_s("two.txt", reuc->path); +} + +static int reuc_entry_exists(void) +{ + return (git_index_reuc_get_bypath(repo_index, "newfile.txt") != NULL); +} + +void test_index_reuc__cleaned_on_reset_hard(void) +{ + git_object *target; + + cl_git_pass(git_revparse_single(&target, repo, "3a34580")); + + test_index_reuc__add(); + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); + cl_assert(reuc_entry_exists() == false); + + git_object_free(target); +} + +void test_index_reuc__cleaned_on_reset_mixed(void) +{ + git_object *target; + + cl_git_pass(git_revparse_single(&target, repo, "3a34580")); + + test_index_reuc__add(); + cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL)); + cl_assert(reuc_entry_exists() == false); + + git_object_free(target); +} + +void test_index_reuc__retained_on_reset_soft(void) +{ + git_object *target; + + cl_git_pass(git_revparse_single(&target, repo, "3a34580")); + + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); + + test_index_reuc__add(); + cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); + cl_assert(reuc_entry_exists() == true); + + git_object_free(target); +} + +void test_index_reuc__cleaned_on_checkout_tree(void) +{ + git_oid oid; + git_object *obj; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + test_index_reuc__add(); + cl_git_pass(git_reference_name_to_id(&oid, repo, "refs/heads/master")); + cl_git_pass(git_object_lookup(&obj, repo, &oid, GIT_OBJECT_ANY)); + cl_git_pass(git_checkout_tree(repo, obj, &opts)); + cl_assert(reuc_entry_exists() == false); + + git_object_free(obj); +} + +void test_index_reuc__cleaned_on_checkout_head(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + test_index_reuc__add(); + cl_git_pass(git_checkout_head(repo, &opts)); + cl_assert(reuc_entry_exists() == false); +} + +void test_index_reuc__retained_on_checkout_index(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + test_index_reuc__add(); + cl_git_pass(git_checkout_index(repo, repo_index, &opts)); + cl_assert(reuc_entry_exists() == true); +} diff --git a/tests/libgit2/index/splitindex.c b/tests/libgit2/index/splitindex.c new file mode 100644 index 000000000..d32ed1022 --- /dev/null +++ b/tests/libgit2/index/splitindex.c @@ -0,0 +1,21 @@ +#include "clar_libgit2.h" +#include "index.h" + +static git_repository *g_repo; + +void test_index_splitindex__initialize(void) +{ + g_repo = cl_git_sandbox_init("splitindex"); +} + +void test_index_splitindex__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_index_splitindex__fail_on_open(void) +{ + git_index *idx; + cl_git_fail_with(-1, git_repository_index(&idx, g_repo)); + cl_assert_equal_s(git_error_last()->message, "unsupported mandatory extension: 'link'"); +} diff --git a/tests/libgit2/index/stage.c b/tests/libgit2/index/stage.c new file mode 100644 index 000000000..cdfe3a811 --- /dev/null +++ b/tests/libgit2/index/stage.c @@ -0,0 +1,62 @@ +#include "clar_libgit2.h" +#include "index.h" +#include "git2/repository.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "mergedrepo" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +/* Fixture setup and teardown */ +void test_index_stage__initialize(void) +{ + repo = cl_git_sandbox_init("mergedrepo"); + git_repository_index(&repo_index, repo); +} + +void test_index_stage__cleanup(void) +{ + git_index_free(repo_index); + repo_index = NULL; + + cl_git_sandbox_cleanup(); +} + + +void test_index_stage__add_always_adds_stage_0(void) +{ + size_t entry_idx; + const git_index_entry *entry; + + cl_git_mkfile("./mergedrepo/new-file.txt", "new-file\n"); + + cl_git_pass(git_index_add_bypath(repo_index, "new-file.txt")); + + cl_assert(!git_index_find(&entry_idx, repo_index, "new-file.txt")); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 0); +} + +void test_index_stage__find_gets_first_stage(void) +{ + size_t entry_idx; + const git_index_entry *entry; + + cl_assert(!git_index_find(&entry_idx, repo_index, "one.txt")); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 0); + + cl_assert(!git_index_find(&entry_idx, repo_index, "two.txt")); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 0); + + cl_assert(!git_index_find(&entry_idx, repo_index, "conflicts-one.txt")); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 1); + + cl_assert(!git_index_find(&entry_idx, repo_index, "conflicts-two.txt")); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 1); +} + diff --git a/tests/libgit2/index/tests.c b/tests/libgit2/index/tests.c new file mode 100644 index 000000000..205d12e5b --- /dev/null +++ b/tests/libgit2/index/tests.c @@ -0,0 +1,1173 @@ +#include "clar_libgit2.h" +#include "index.h" + +static const size_t index_entry_count = 109; +static const size_t index_entry_count_2 = 1437; +#define TEST_INDEX_PATH cl_fixture("testrepo.git/index") +#define TEST_INDEX2_PATH cl_fixture("gitgit.index") +#define TEST_INDEXBIG_PATH cl_fixture("big.index") +#define TEST_INDEXBAD_PATH cl_fixture("bad.index") + + +/* Suite data */ +struct test_entry { + size_t index; + char path[128]; + off64_t file_size; + git_time_t mtime; +}; + +static struct test_entry test_entries[] = { + {4, "Makefile", 5064, 0x4C3F7F33}, + {6, "git.git-authors", 2709, 0x4C3F7F33}, + {36, "src/index.c", 10014, 0x4C43368D}, + {48, "src/revobject.h", 1448, 0x4C3F7FE2}, + {62, "tests/Makefile", 2631, 0x4C3F7F33} +}; + +/* Helpers */ +static void copy_file(const char *src, const char *dst) +{ + git_str source_buf = GIT_STR_INIT; + git_file dst_fd; + + cl_git_pass(git_futils_readbuffer(&source_buf, src)); + + dst_fd = git_futils_creat_withpath(dst, 0777, 0666); /* -V536 */ + if (dst_fd < 0) + goto cleanup; + + cl_git_pass(p_write(dst_fd, source_buf.ptr, source_buf.size)); + +cleanup: + git_str_dispose(&source_buf); + p_close(dst_fd); +} + +static void files_are_equal(const char *a, const char *b) +{ + git_str buf_a = GIT_STR_INIT; + git_str buf_b = GIT_STR_INIT; + int pass; + + if (git_futils_readbuffer(&buf_a, a) < 0) + cl_assert(0); + + if (git_futils_readbuffer(&buf_b, b) < 0) { + git_str_dispose(&buf_a); + cl_assert(0); + } + + pass = (buf_a.size == buf_b.size && !memcmp(buf_a.ptr, buf_b.ptr, buf_a.size)); + + git_str_dispose(&buf_a); + git_str_dispose(&buf_b); + + cl_assert(pass); +} + + +/* Fixture setup and teardown */ +void test_index_tests__initialize(void) +{ +} + +void test_index_tests__cleanup(void) +{ + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, 0)); +} + +void test_index_tests__empty_index(void) +{ + git_index *index; + + cl_git_pass(git_index_open(&index, "in-memory-index")); + cl_assert(index->on_disk == 0); + + cl_assert(git_index_entrycount(index) == 0); + cl_assert(git_vector_is_sorted(&index->entries)); + + git_index_free(index); +} + +void test_index_tests__default_test_index(void) +{ + git_index *index; + unsigned int i; + git_index_entry **entries; + + cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + cl_assert(index->on_disk); + + cl_assert(git_index_entrycount(index) == index_entry_count); + cl_assert(git_vector_is_sorted(&index->entries)); + + entries = (git_index_entry **)index->entries.contents; + + for (i = 0; i < ARRAY_SIZE(test_entries); ++i) { + git_index_entry *e = entries[test_entries[i].index]; + + cl_assert_equal_s(e->path, test_entries[i].path); + cl_assert_equal_i(e->mtime.seconds, test_entries[i].mtime); + cl_assert_equal_i(e->file_size, test_entries[i].file_size); + } + + git_index_free(index); +} + +void test_index_tests__gitgit_index(void) +{ + git_index *index; + + cl_git_pass(git_index_open(&index, TEST_INDEX2_PATH)); + cl_assert(index->on_disk); + + cl_assert(git_index_entrycount(index) == index_entry_count_2); + cl_assert(git_vector_is_sorted(&index->entries)); + cl_assert(index->tree != NULL); + + git_index_free(index); +} + +void test_index_tests__find_in_existing(void) +{ + git_index *index; + unsigned int i; + + cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + + for (i = 0; i < ARRAY_SIZE(test_entries); ++i) { + size_t idx; + + cl_assert(!git_index_find(&idx, index, test_entries[i].path)); + cl_assert(idx == test_entries[i].index); + } + + git_index_free(index); +} + +void test_index_tests__find_in_empty(void) +{ + git_index *index; + unsigned int i; + + cl_git_pass(git_index_open(&index, "fake-index")); + + for (i = 0; i < ARRAY_SIZE(test_entries); ++i) { + cl_assert(GIT_ENOTFOUND == git_index_find(NULL, index, test_entries[i].path)); + } + + git_index_free(index); +} + +void test_index_tests__find_prefix(void) +{ + git_index *index; + const git_index_entry *entry; + size_t pos; + + cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + + cl_git_pass(git_index_find_prefix(&pos, index, "src")); + entry = git_index_get_byindex(index, pos); + cl_assert(git__strcmp(entry->path, "src/block-sha1/sha1.c") == 0); + + cl_git_pass(git_index_find_prefix(&pos, index, "src/co")); + entry = git_index_get_byindex(index, pos); + cl_assert(git__strcmp(entry->path, "src/commit.c") == 0); + + cl_assert(GIT_ENOTFOUND == git_index_find_prefix(NULL, index, "blah")); + + git_index_free(index); +} + +void test_index_tests__write(void) +{ + git_index *index; + + copy_file(TEST_INDEXBIG_PATH, "index_rewrite"); + + cl_git_pass(git_index_open(&index, "index_rewrite")); + cl_assert(index->on_disk); + + cl_git_pass(git_index_write(index)); + files_are_equal(TEST_INDEXBIG_PATH, "index_rewrite"); + + git_index_free(index); + + p_unlink("index_rewrite"); +} + +void test_index_tests__sort0(void) +{ + /* sort the entries in an index */ + + /* + * TODO: This no longer applies: + * index sorting in Git uses some specific changes to the way + * directories are sorted. + * + * We need to specifically check for this by creating a new + * index, adding entries in random order and then + * checking for consistency + */ +} + +void test_index_tests__sort1(void) +{ + /* sort the entries in an empty index */ + git_index *index; + + cl_git_pass(git_index_open(&index, "fake-index")); + + /* FIXME: this test is slightly dumb */ + cl_assert(git_vector_is_sorted(&index->entries)); + + git_index_free(index); +} + +static void cleanup_myrepo(void *opaque) +{ + GIT_UNUSED(opaque); + cl_fixture_cleanup("myrepo"); +} + +void test_index_tests__add(void) +{ + git_index *index; + git_filebuf file = GIT_FILEBUF_INIT; + git_repository *repo; + const git_index_entry *entry; + git_oid id1; + + cl_set_cleanup(&cleanup_myrepo, NULL); + + /* Initialize a new repository */ + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + + /* Ensure we're the only guy in the room */ + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(git_index_entrycount(index) == 0); + + /* Create a new file in the working directory */ + cl_git_pass(git_futils_mkpath2file("myrepo/test.txt", 0777)); + cl_git_pass(git_filebuf_open(&file, "myrepo/test.txt", 0, 0666)); + cl_git_pass(git_filebuf_write(&file, "hey there\n", 10)); + cl_git_pass(git_filebuf_commit(&file)); + + /* Store the expected hash of the file/blob + * This has been generated by executing the following + * $ echo "hey there" | git hash-object --stdin + */ + cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); + + /* Add the new file to the index */ + cl_git_pass(git_index_add_bypath(index, "test.txt")); + + /* Wow... it worked! */ + cl_assert(git_index_entrycount(index) == 1); + entry = git_index_get_byindex(index, 0); + + /* And the built-in hashing mechanism worked as expected */ + cl_assert_equal_oid(&id1, &entry->id); + + /* Test access by path instead of index */ + cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); + cl_assert_equal_oid(&id1, &entry->id); + + git_index_free(index); + git_repository_free(repo); +} + +void test_index_tests__add_frombuffer(void) +{ + git_index *index; + git_repository *repo; + git_index_entry entry; + const git_index_entry *returned_entry; + + git_oid id1; + git_blob *blob; + + const char *content = "hey there\n"; + + cl_set_cleanup(&cleanup_myrepo, NULL); + + /* Initialize a new repository */ + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + + /* Ensure we're the only guy in the room */ + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(git_index_entrycount(index) == 0); + + /* Store the expected hash of the file/blob + * This has been generated by executing the following + * $ echo "hey there" | git hash-object --stdin + */ + cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); + + /* Add the new file to the index */ + memset(&entry, 0x0, sizeof(git_index_entry)); + entry.mode = GIT_FILEMODE_BLOB; + entry.path = "test.txt"; + cl_git_pass(git_index_add_from_buffer(index, &entry, + content, strlen(content))); + + /* Wow... it worked! */ + cl_assert(git_index_entrycount(index) == 1); + returned_entry = git_index_get_byindex(index, 0); + + /* And the built-in hashing mechanism worked as expected */ + cl_assert_equal_oid(&id1, &returned_entry->id); + /* And mode is the one asked */ + cl_assert_equal_i(GIT_FILEMODE_BLOB, returned_entry->mode); + + /* Test access by path instead of index */ + cl_assert((returned_entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); + cl_assert_equal_oid(&id1, &returned_entry->id); + + /* Test the blob is in the repository */ + cl_git_pass(git_blob_lookup(&blob, repo, &id1)); + cl_assert_equal_s( + content, git_blob_rawcontent(blob)); + git_blob_free(blob); + + git_index_free(index); + git_repository_free(repo); +} + +void test_index_tests__dirty_and_clean(void) +{ + git_repository *repo; + git_index *index; + git_index_entry entry = {{0}}; + + /* Index is not dirty after opening */ + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + cl_assert(git_index_entrycount(index) == 0); + cl_assert(!git_index_is_dirty(index)); + + /* Index is dirty after adding an entry */ + entry.mode = GIT_FILEMODE_BLOB; + entry.path = "test.txt"; + cl_git_pass(git_index_add_from_buffer(index, &entry, "Hi.\n", 4)); + cl_assert(git_index_entrycount(index) == 1); + cl_assert(git_index_is_dirty(index)); + + /* Index is not dirty after write */ + cl_git_pass(git_index_write(index)); + cl_assert(!git_index_is_dirty(index)); + + /* Index is dirty after removing an entry */ + cl_git_pass(git_index_remove_bypath(index, "test.txt")); + cl_assert(git_index_entrycount(index) == 0); + cl_assert(git_index_is_dirty(index)); + + /* Index is not dirty after write */ + cl_git_pass(git_index_write(index)); + cl_assert(!git_index_is_dirty(index)); + + /* Index remains not dirty after read */ + cl_git_pass(git_index_read(index, 0)); + cl_assert(!git_index_is_dirty(index)); + + /* Index is dirty when we do an unforced read with dirty content */ + cl_git_pass(git_index_add_from_buffer(index, &entry, "Hi.\n", 4)); + cl_assert(git_index_entrycount(index) == 1); + cl_assert(git_index_is_dirty(index)); + + cl_git_pass(git_index_read(index, 0)); + cl_assert(git_index_is_dirty(index)); + + /* Index is clean when we force a read with dirty content */ + cl_git_pass(git_index_read(index, 1)); + cl_assert(!git_index_is_dirty(index)); + + git_index_free(index); + git_repository_free(repo); +} + +void test_index_tests__dirty_fails_optionally(void) +{ + git_repository *repo; + git_index *index; + git_index_entry entry = {{0}}; + + /* Index is not dirty after opening */ + repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_index(&index, repo)); + + /* Index is dirty after adding an entry */ + entry.mode = GIT_FILEMODE_BLOB; + entry.path = "test.txt"; + cl_git_pass(git_index_add_from_buffer(index, &entry, "Hi.\n", 4)); + cl_assert(git_index_is_dirty(index)); + + cl_git_pass(git_checkout_head(repo, NULL)); + + /* Index is dirty (again) after adding an entry */ + entry.mode = GIT_FILEMODE_BLOB; + entry.path = "test.txt"; + cl_git_pass(git_index_add_from_buffer(index, &entry, "Hi.\n", 4)); + cl_assert(git_index_is_dirty(index)); + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, 1)); + cl_git_fail_with(GIT_EINDEXDIRTY, git_checkout_head(repo, NULL)); + + git_index_free(index); + cl_git_sandbox_cleanup(); +} + +void test_index_tests__add_frombuffer_reset_entry(void) +{ + git_index *index; + git_repository *repo; + git_index_entry entry; + const git_index_entry *returned_entry; + git_filebuf file = GIT_FILEBUF_INIT; + + git_oid id1; + git_blob *blob; + const char *old_content = "here\n"; + const char *content = "hey there\n"; + + cl_set_cleanup(&cleanup_myrepo, NULL); + + /* Initialize a new repository */ + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_futils_mkpath2file("myrepo/test.txt", 0777)); + cl_git_pass(git_filebuf_open(&file, "myrepo/test.txt", 0, 0666)); + cl_git_pass(git_filebuf_write(&file, old_content, strlen(old_content))); + cl_git_pass(git_filebuf_commit(&file)); + + /* Store the expected hash of the file/blob + * This has been generated by executing the following + * $ echo "hey there" | git hash-object --stdin + */ + cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); + + cl_git_pass(git_index_add_bypath(index, "test.txt")); + + /* Add the new file to the index */ + memset(&entry, 0x0, sizeof(git_index_entry)); + entry.mode = GIT_FILEMODE_BLOB; + entry.path = "test.txt"; + cl_git_pass(git_index_add_from_buffer(index, &entry, + content, strlen(content))); + + /* Wow... it worked! */ + cl_assert(git_index_entrycount(index) == 1); + returned_entry = git_index_get_byindex(index, 0); + + /* And the built-in hashing mechanism worked as expected */ + cl_assert_equal_oid(&id1, &returned_entry->id); + /* And mode is the one asked */ + cl_assert_equal_i(GIT_FILEMODE_BLOB, returned_entry->mode); + + /* Test access by path instead of index */ + cl_assert((returned_entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); + cl_assert_equal_oid(&id1, &returned_entry->id); + cl_assert_equal_i(0, returned_entry->dev); + cl_assert_equal_i(0, returned_entry->ino); + cl_assert_equal_i(0, returned_entry->uid); + cl_assert_equal_i(0, returned_entry->uid); + cl_assert_equal_i(10, returned_entry->file_size); + + /* Test the blob is in the repository */ + cl_git_pass(git_blob_lookup(&blob, repo, &id1)); + cl_assert_equal_s(content, git_blob_rawcontent(blob)); + git_blob_free(blob); + + git_index_free(index); + git_repository_free(repo); +} + +static void cleanup_1397(void *opaque) +{ + GIT_UNUSED(opaque); + cl_git_sandbox_cleanup(); +} + +void test_index_tests__add_issue_1397(void) +{ + git_index *index; + git_repository *repo; + const git_index_entry *entry; + git_oid id1; + + cl_set_cleanup(&cleanup_1397, NULL); + + repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(repo, "core.autocrlf", true); + + /* Ensure we're the only guy in the room */ + cl_git_pass(git_repository_index(&index, repo)); + + /* Store the expected hash of the file/blob + * This has been generated by executing the following + * $ git hash-object crlf_file.txt + */ + cl_git_pass(git_oid_fromstr(&id1, "8312e0889a9cbab77c732b6bc39b51a683e3a318")); + + /* Make sure the initial SHA-1 is correct */ + cl_assert((entry = git_index_get_bypath(index, "crlf_file.txt", 0)) != NULL); + cl_assert_equal_oid(&id1, &entry->id); + + /* Update the index */ + cl_git_pass(git_index_add_bypath(index, "crlf_file.txt")); + + /* Check the new SHA-1 */ + cl_assert((entry = git_index_get_bypath(index, "crlf_file.txt", 0)) != NULL); + cl_assert_equal_oid(&id1, &entry->id); + + git_index_free(index); +} + +void test_index_tests__add_bypath_to_a_bare_repository_returns_EBAREPO(void) +{ + git_repository *bare_repo; + git_index *index; + + cl_git_pass(git_repository_open(&bare_repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_repository_index(&index, bare_repo)); + + cl_assert_equal_i(GIT_EBAREREPO, git_index_add_bypath(index, "test.txt")); + + git_index_free(index); + git_repository_free(bare_repo); +} + +static void assert_add_bypath_fails(git_repository *repo, const char *fn) +{ + git_index *index; + git_str path = GIT_STR_INIT; + + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(git_index_entrycount(index) == 0); + + git_str_joinpath(&path, "./invalid", fn); + + cl_git_mkfile(path.ptr, NULL); + cl_git_fail(git_index_add_bypath(index, fn)); + cl_must_pass(p_unlink(path.ptr)); + + cl_assert(git_index_entrycount(index) == 0); + + git_str_dispose(&path); + git_index_free(index); +} + +/* Test that writing an invalid filename fails */ +void test_index_tests__cannot_add_invalid_filename(void) +{ + git_repository *repo; + + cl_must_pass(p_mkdir("invalid", 0700)); + cl_git_pass(git_repository_init(&repo, "./invalid", 0)); + cl_must_pass(p_mkdir("./invalid/subdir", 0777)); + + /* cl_git_mkfile() needs the dir to exist */ + if (!git_fs_path_exists("./invalid/.GIT")) + cl_must_pass(p_mkdir("./invalid/.GIT", 0777)); + if (!git_fs_path_exists("./invalid/.GiT")) + cl_must_pass(p_mkdir("./invalid/.GiT", 0777)); + + assert_add_bypath_fails(repo, ".git/hello"); + assert_add_bypath_fails(repo, ".GIT/hello"); + assert_add_bypath_fails(repo, ".GiT/hello"); + assert_add_bypath_fails(repo, "./.git/hello"); + assert_add_bypath_fails(repo, "./foo"); + assert_add_bypath_fails(repo, "./bar"); + assert_add_bypath_fails(repo, "subdir/../bar"); + + git_repository_free(repo); + + cl_fixture_cleanup("invalid"); +} + +static void assert_add_fails(git_repository *repo, const char *fn) +{ + git_index *index; + git_str path = GIT_STR_INIT; + git_index_entry entry = {{0}}; + + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(git_index_entrycount(index) == 0); + + entry.path = fn; + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&entry.id, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")); + + cl_git_fail(git_index_add(index, &entry)); + + cl_assert(git_index_entrycount(index) == 0); + + git_str_dispose(&path); + git_index_free(index); +} + +/* + * Test that writing an invalid filename fails on filesystem + * specific protected names + */ +void test_index_tests__cannot_add_protected_invalid_filename(void) +{ + git_repository *repo; + git_index *index; + + cl_must_pass(p_mkdir("invalid", 0700)); + + cl_git_pass(git_repository_init(&repo, "./invalid", 0)); + + /* add a file to the repository so we can reference it later */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_mkfile("invalid/dummy.txt", ""); + cl_git_pass(git_index_add_bypath(index, "dummy.txt")); + cl_must_pass(p_unlink("invalid/dummy.txt")); + cl_git_pass(git_index_remove_bypath(index, "dummy.txt")); + git_index_free(index); + + cl_repo_set_bool(repo, "core.protectHFS", true); + cl_repo_set_bool(repo, "core.protectNTFS", true); + + assert_add_fails(repo, ".git./hello"); + assert_add_fails(repo, ".git\xe2\x80\xad/hello"); + assert_add_fails(repo, "git~1/hello"); + assert_add_fails(repo, ".git\xe2\x81\xaf/hello"); + assert_add_fails(repo, ".git::$INDEX_ALLOCATION/dummy-file"); + + git_repository_free(repo); + + cl_fixture_cleanup("invalid"); +} + +static void replace_char(char *str, char in, char out) +{ + char *c = str; + + while (*c++) + if (*c == in) + *c = out; +} + +static void assert_write_fails(git_repository *repo, const char *fn_orig) +{ + git_index *index; + git_oid expected; + const git_index_entry *entry; + git_str path = GIT_STR_INIT; + char *fn; + + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(git_index_entrycount(index) == 0); + + /* + * Sneak a valid path into the index, we'll update it + * to an invalid path when we try to write the index. + */ + fn = git__strdup(fn_orig); + replace_char(fn, '/', '_'); + replace_char(fn, ':', '!'); + + git_str_joinpath(&path, "./invalid", fn); + + cl_git_mkfile(path.ptr, NULL); + + cl_git_pass(git_index_add_bypath(index, fn)); + + cl_assert(entry = git_index_get_bypath(index, fn, 0)); + + /* kids, don't try this at home */ + replace_char((char *)entry->path, '_', '/'); + replace_char((char *)entry->path, '!', ':'); + + /* write-tree */ + cl_git_fail(git_index_write_tree(&expected, index)); + + p_unlink(path.ptr); + + cl_git_pass(git_index_remove_all(index, NULL, NULL, NULL)); + git_str_dispose(&path); + git_index_free(index); + git__free(fn); +} + +void test_index_tests__write_tree_invalid_unowned_index(void) +{ + git_index *idx; + git_repository *repo; + git_index_entry entry = {{0}}; + git_oid tree_id; + + cl_git_pass(git_index_new(&idx)); + + cl_git_pass(git_oid_fromstr(&entry.id, "8312e0a89a9cbab77c732b6bc39b51a783e3a318")); + entry.path = "foo"; + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_index_add(idx, &entry)); + + cl_git_pass(git_repository_init(&repo, "./invalid-id", 0)); + + cl_git_fail(git_index_write_tree_to(&tree_id, idx, repo)); + + git_index_free(idx); + git_repository_free(repo); + + cl_fixture_cleanup("invalid-id"); +} + +/* Test that writing an invalid filename fails */ +void test_index_tests__write_invalid_filename(void) +{ + git_repository *repo; + + p_mkdir("invalid", 0700); + + cl_git_pass(git_repository_init(&repo, "./invalid", 0)); + + assert_write_fails(repo, ".git/hello"); + assert_write_fails(repo, ".GIT/hello"); + assert_write_fails(repo, ".GiT/hello"); + assert_write_fails(repo, "./.git/hello"); + assert_write_fails(repo, "./foo"); + assert_write_fails(repo, "./bar"); + assert_write_fails(repo, "foo/../bar"); + + git_repository_free(repo); + + cl_fixture_cleanup("invalid"); +} + +void test_index_tests__honors_protect_filesystems(void) +{ + git_repository *repo; + + p_mkdir("invalid", 0700); + + cl_git_pass(git_repository_init(&repo, "./invalid", 0)); + + cl_repo_set_bool(repo, "core.protectHFS", true); + cl_repo_set_bool(repo, "core.protectNTFS", true); + + assert_write_fails(repo, ".git./hello"); + assert_write_fails(repo, ".git\xe2\x80\xad/hello"); + assert_write_fails(repo, "git~1/hello"); + assert_write_fails(repo, ".git\xe2\x81\xaf/hello"); + assert_write_fails(repo, ".git::$INDEX_ALLOCATION/dummy-file"); + + git_repository_free(repo); + + cl_fixture_cleanup("invalid"); +} + +void test_index_tests__protectntfs_on_by_default(void) +{ + git_repository *repo; + + p_mkdir("invalid", 0700); + + cl_git_pass(git_repository_init(&repo, "./invalid", 0)); + assert_write_fails(repo, ".git./hello"); + assert_write_fails(repo, "git~1/hello"); + + git_repository_free(repo); + + cl_fixture_cleanup("invalid"); +} + +void test_index_tests__can_disable_protectntfs(void) +{ + git_repository *repo; + git_index *index; + + cl_must_pass(p_mkdir("valid", 0700)); + cl_git_rewritefile("valid/git~1", "steal the shortname"); + + cl_git_pass(git_repository_init(&repo, "./valid", 0)); + cl_git_pass(git_repository_index(&index, repo)); + cl_repo_set_bool(repo, "core.protectNTFS", false); + + cl_git_pass(git_index_add_bypath(index, "git~1")); + + git_index_free(index); + git_repository_free(repo); + + cl_fixture_cleanup("valid"); +} + +void test_index_tests__remove_entry(void) +{ + git_repository *repo; + git_index *index; + + p_mkdir("index_test", 0770); + + cl_git_pass(git_repository_init(&repo, "index_test", 0)); + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(git_index_entrycount(index) == 0); + + cl_git_mkfile("index_test/hello", NULL); + cl_git_pass(git_index_add_bypath(index, "hello")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_index_read(index, true)); /* reload */ + cl_assert(git_index_entrycount(index) == 1); + cl_assert(git_index_get_bypath(index, "hello", 0) != NULL); + + cl_git_pass(git_index_remove(index, "hello", 0)); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_index_read(index, true)); /* reload */ + cl_assert(git_index_entrycount(index) == 0); + cl_assert(git_index_get_bypath(index, "hello", 0) == NULL); + + git_index_free(index); + git_repository_free(repo); + cl_fixture_cleanup("index_test"); +} + +void test_index_tests__remove_directory(void) +{ + git_repository *repo; + git_index *index; + + p_mkdir("index_test", 0770); + + cl_git_pass(git_repository_init(&repo, "index_test", 0)); + cl_git_pass(git_repository_index(&index, repo)); + cl_assert_equal_i(0, (int)git_index_entrycount(index)); + + p_mkdir("index_test/a", 0770); + cl_git_mkfile("index_test/a/1.txt", NULL); + cl_git_mkfile("index_test/a/2.txt", NULL); + cl_git_mkfile("index_test/a/3.txt", NULL); + cl_git_mkfile("index_test/b.txt", NULL); + + cl_git_pass(git_index_add_bypath(index, "a/1.txt")); + cl_git_pass(git_index_add_bypath(index, "a/2.txt")); + cl_git_pass(git_index_add_bypath(index, "a/3.txt")); + cl_git_pass(git_index_add_bypath(index, "b.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_index_read(index, true)); /* reload */ + cl_assert_equal_i(4, (int)git_index_entrycount(index)); + cl_assert(git_index_get_bypath(index, "a/1.txt", 0) != NULL); + cl_assert(git_index_get_bypath(index, "a/2.txt", 0) != NULL); + cl_assert(git_index_get_bypath(index, "b.txt", 0) != NULL); + + cl_git_pass(git_index_remove(index, "a/1.txt", 0)); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_index_read(index, true)); /* reload */ + cl_assert_equal_i(3, (int)git_index_entrycount(index)); + cl_assert(git_index_get_bypath(index, "a/1.txt", 0) == NULL); + cl_assert(git_index_get_bypath(index, "a/2.txt", 0) != NULL); + cl_assert(git_index_get_bypath(index, "b.txt", 0) != NULL); + + cl_git_pass(git_index_remove_directory(index, "a", 0)); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_index_read(index, true)); /* reload */ + cl_assert_equal_i(1, (int)git_index_entrycount(index)); + cl_assert(git_index_get_bypath(index, "a/1.txt", 0) == NULL); + cl_assert(git_index_get_bypath(index, "a/2.txt", 0) == NULL); + cl_assert(git_index_get_bypath(index, "b.txt", 0) != NULL); + + git_index_free(index); + git_repository_free(repo); + cl_fixture_cleanup("index_test"); +} + +void test_index_tests__preserves_case(void) +{ + git_repository *repo; + git_index *index; + const git_index_entry *entry; + int index_caps; + + cl_set_cleanup(&cleanup_myrepo, NULL); + + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + index_caps = git_index_caps(index); + + cl_git_rewritefile("myrepo/test.txt", "hey there\n"); + cl_git_pass(git_index_add_bypath(index, "test.txt")); + + cl_git_pass(p_rename("myrepo/test.txt", "myrepo/TEST.txt")); + cl_git_rewritefile("myrepo/TEST.txt", "hello again\n"); + cl_git_pass(git_index_add_bypath(index, "TEST.txt")); + + if (index_caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) + cl_assert_equal_i(1, (int)git_index_entrycount(index)); + else + cl_assert_equal_i(2, (int)git_index_entrycount(index)); + + /* Test access by path instead of index */ + cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); + /* The path should *not* have changed without an explicit remove */ + cl_assert(git__strcmp(entry->path, "test.txt") == 0); + + cl_assert((entry = git_index_get_bypath(index, "TEST.txt", 0)) != NULL); + if (index_caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) + /* The path should *not* have changed without an explicit remove */ + cl_assert(git__strcmp(entry->path, "test.txt") == 0); + else + cl_assert(git__strcmp(entry->path, "TEST.txt") == 0); + + git_index_free(index); + git_repository_free(repo); +} + +void test_index_tests__elocked(void) +{ + git_repository *repo; + git_index *index; + git_filebuf file = GIT_FILEBUF_INIT; + const git_error *err; + int error; + + cl_set_cleanup(&cleanup_myrepo, NULL); + + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + /* Lock the index file so we fail to lock it */ + cl_git_pass(git_filebuf_open(&file, index->index_file_path, 0, 0666)); + error = git_index_write(index); + cl_assert_equal_i(GIT_ELOCKED, error); + + err = git_error_last(); + cl_assert_equal_i(err->klass, GIT_ERROR_INDEX); + + git_filebuf_cleanup(&file); + git_index_free(index); + git_repository_free(repo); +} + +void test_index_tests__reload_from_disk(void) +{ + git_repository *repo; + git_index *read_index; + git_index *write_index; + + cl_set_cleanup(&cleanup_myrepo, NULL); + + cl_git_pass(git_futils_mkdir("./myrepo", 0777, GIT_MKDIR_PATH)); + cl_git_mkfile("./myrepo/a.txt", "a\n"); + cl_git_mkfile("./myrepo/b.txt", "b\n"); + + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + cl_git_pass(git_repository_index(&write_index, repo)); + cl_assert_equal_i(false, write_index->on_disk); + + cl_git_pass(git_index_open(&read_index, write_index->index_file_path)); + cl_assert_equal_i(false, read_index->on_disk); + + /* Stage two new files against the write_index */ + cl_git_pass(git_index_add_bypath(write_index, "a.txt")); + cl_git_pass(git_index_add_bypath(write_index, "b.txt")); + + cl_assert_equal_sz(2, git_index_entrycount(write_index)); + + /* Persist the index changes to disk */ + cl_git_pass(git_index_write(write_index)); + cl_assert_equal_i(true, write_index->on_disk); + + /* Sync the changes back into the read_index */ + cl_assert_equal_sz(0, git_index_entrycount(read_index)); + + cl_git_pass(git_index_read(read_index, true)); + cl_assert_equal_i(true, read_index->on_disk); + + cl_assert_equal_sz(2, git_index_entrycount(read_index)); + + /* Remove the index file from the filesystem */ + cl_git_pass(p_unlink(write_index->index_file_path)); + + /* Sync the changes back into the read_index */ + cl_git_pass(git_index_read(read_index, true)); + cl_assert_equal_i(false, read_index->on_disk); + cl_assert_equal_sz(0, git_index_entrycount(read_index)); + + git_index_free(read_index); + git_index_free(write_index); + git_repository_free(repo); +} + +void test_index_tests__corrupted_extension(void) +{ + git_index *index; + + cl_git_fail_with(git_index_open(&index, TEST_INDEXBAD_PATH), GIT_ERROR); +} + +void test_index_tests__reload_while_ignoring_case(void) +{ + git_index *index; + unsigned int caps; + + cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + cl_git_pass(git_vector_verify_sorted(&index->entries)); + + caps = git_index_caps(index); + cl_git_pass(git_index_set_caps(index, caps &= ~GIT_INDEX_CAPABILITY_IGNORE_CASE)); + cl_git_pass(git_index_read(index, true)); + cl_git_pass(git_vector_verify_sorted(&index->entries)); + cl_assert(git_index_get_bypath(index, ".HEADER", 0)); + cl_assert_equal_p(NULL, git_index_get_bypath(index, ".header", 0)); + + cl_git_pass(git_index_set_caps(index, caps | GIT_INDEX_CAPABILITY_IGNORE_CASE)); + cl_git_pass(git_index_read(index, true)); + cl_git_pass(git_vector_verify_sorted(&index->entries)); + cl_assert(git_index_get_bypath(index, ".HEADER", 0)); + cl_assert(git_index_get_bypath(index, ".header", 0)); + + git_index_free(index); +} + +void test_index_tests__change_icase_on_instance(void) +{ + git_index *index; + unsigned int caps; + const git_index_entry *e; + + cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + cl_git_pass(git_vector_verify_sorted(&index->entries)); + + caps = git_index_caps(index); + cl_git_pass(git_index_set_caps(index, caps &= ~GIT_INDEX_CAPABILITY_IGNORE_CASE)); + cl_assert_equal_i(false, index->ignore_case); + cl_git_pass(git_vector_verify_sorted(&index->entries)); + cl_assert(e = git_index_get_bypath(index, "src/common.h", 0)); + cl_assert_equal_p(NULL, e = git_index_get_bypath(index, "SRC/Common.h", 0)); + cl_assert(e = git_index_get_bypath(index, "COPYING", 0)); + cl_assert_equal_p(NULL, e = git_index_get_bypath(index, "copying", 0)); + + cl_git_pass(git_index_set_caps(index, caps | GIT_INDEX_CAPABILITY_IGNORE_CASE)); + cl_assert_equal_i(true, index->ignore_case); + cl_git_pass(git_vector_verify_sorted(&index->entries)); + cl_assert(e = git_index_get_bypath(index, "COPYING", 0)); + cl_assert_equal_s("COPYING", e->path); + cl_assert(e = git_index_get_bypath(index, "copying", 0)); + cl_assert_equal_s("COPYING", e->path); + + git_index_free(index); +} + +void test_index_tests__can_lock_index(void) +{ + git_repository *repo; + git_index *index; + git_indexwriter one = GIT_INDEXWRITER_INIT, + two = GIT_INDEXWRITER_INIT; + + repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_indexwriter_init(&one, index)); + + cl_git_fail_with(GIT_ELOCKED, git_indexwriter_init(&two, index)); + cl_git_fail_with(GIT_ELOCKED, git_index_write(index)); + + cl_git_pass(git_indexwriter_commit(&one)); + + cl_git_pass(git_index_write(index)); + + git_indexwriter_cleanup(&one); + git_indexwriter_cleanup(&two); + git_index_free(index); + cl_git_sandbox_cleanup(); +} + +void test_index_tests__can_iterate(void) +{ + git_index *index; + git_index_iterator *iterator; + const git_index_entry *entry; + size_t i, iterator_idx = 0, found = 0; + int ret; + + cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + cl_git_pass(git_index_iterator_new(&iterator, index)); + + cl_assert(git_vector_is_sorted(&iterator->snap)); + + for (i = 0; i < ARRAY_SIZE(test_entries); i++) { + /* Advance iterator to next test entry index */ + do { + ret = git_index_iterator_next(&entry, iterator); + + if (ret == GIT_ITEROVER) + cl_fail("iterator did not contain all test entries"); + + cl_git_pass(ret); + } while (iterator_idx++ < test_entries[i].index); + + cl_assert_equal_s(entry->path, test_entries[i].path); + cl_assert_equal_i(entry->mtime.seconds, test_entries[i].mtime); + cl_assert_equal_i(entry->file_size, test_entries[i].file_size); + found++; + } + + while ((ret = git_index_iterator_next(&entry, iterator)) == 0) + ; + + if (ret != GIT_ITEROVER) + cl_git_fail(ret); + + cl_assert_equal_i(found, ARRAY_SIZE(test_entries)); + + git_index_iterator_free(iterator); + git_index_free(index); +} + +void test_index_tests__can_modify_while_iterating(void) +{ + git_index *index; + git_index_iterator *iterator; + const git_index_entry *entry; + git_index_entry new_entry = {{0}}; + size_t expected = 0, seen = 0; + int ret; + + cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + cl_git_pass(git_index_iterator_new(&iterator, index)); + + expected = git_index_entrycount(index); + cl_assert(git_vector_is_sorted(&iterator->snap)); + + /* + * After we've counted the entries, add a new one and change another; + * ensure that our iterator is backed by a snapshot and thus returns + * the number of entries from when the iterator was created. + */ + cl_git_pass(git_oid_fromstr(&new_entry.id, "8312e0a89a9cbab77c732b6bc39b51a783e3a318")); + new_entry.path = "newfile"; + new_entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_index_add(index, &new_entry)); + + cl_git_pass(git_oid_fromstr(&new_entry.id, "4141414141414141414141414141414141414141")); + new_entry.path = "Makefile"; + new_entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_index_add(index, &new_entry)); + + while (true) { + ret = git_index_iterator_next(&entry, iterator); + + if (ret == GIT_ITEROVER) + break; + + seen++; + } + + cl_assert_equal_i(expected, seen); + + git_index_iterator_free(iterator); + git_index_free(index); +} diff --git a/tests/libgit2/index/version.c b/tests/libgit2/index/version.c new file mode 100644 index 000000000..b6c0b7918 --- /dev/null +++ b/tests/libgit2/index/version.c @@ -0,0 +1,140 @@ +#include "clar_libgit2.h" +#include "index.h" + +static git_repository *g_repo = NULL; + +void test_index_version__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +void test_index_version__can_read_v4(void) +{ + const char *paths[] = { + "file.tx", "file.txt", "file.txz", "foo", "zzz", + }; + git_index *index; + size_t i; + + g_repo = cl_git_sandbox_init("indexv4"); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_assert_equal_sz(git_index_entrycount(index), 5); + + for (i = 0; i < ARRAY_SIZE(paths); i++) { + const git_index_entry *entry = + git_index_get_bypath(index, paths[i], GIT_INDEX_STAGE_NORMAL); + + cl_assert(entry != NULL); + } + + git_index_free(index); +} + +void test_index_version__can_write_v4(void) +{ + const char *paths[] = { + "foo", + "foox", + "foobar", + "foobal", + "x", + "xz", + "xyzzyx" + }; + git_repository *repo; + git_index_entry entry; + git_index *index; + size_t i; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_set_version(index, 4)); + + for (i = 0; i < ARRAY_SIZE(paths); i++) { + memset(&entry, 0, sizeof(entry)); + entry.path = paths[i]; + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_index_add_from_buffer(index, &entry, paths[i], + strlen(paths[i]) + 1)); + } + cl_assert_equal_sz(git_index_entrycount(index), ARRAY_SIZE(paths)); + + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_repository_open(&repo, git_repository_path(g_repo))); + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(git_index_version(index) == 4); + + for (i = 0; i < ARRAY_SIZE(paths); i++) { + const git_index_entry *e; + + cl_assert(e = git_index_get_bypath(index, paths[i], 0)); + cl_assert_equal_s(paths[i], e->path); + } + + git_index_free(index); + git_repository_free(repo); +} + +void test_index_version__v4_uses_path_compression(void) +{ + git_index_entry entry; + git_index *index; + char path[250], buf[1]; + struct stat st; + char i, j; + + memset(path, 'a', sizeof(path)); + memset(buf, 'a', sizeof(buf)); + + memset(&entry, 0, sizeof(entry)); + entry.path = path; + entry.mode = GIT_FILEMODE_BLOB; + + g_repo = cl_git_sandbox_init("indexv4"); + cl_git_pass(git_repository_index(&index, g_repo)); + + /* write 676 paths of 250 bytes length */ + for (i = 'a'; i <= 'z'; i++) { + for (j = 'a'; j < 'z'; j++) { + path[ARRAY_SIZE(path) - 3] = i; + path[ARRAY_SIZE(path) - 2] = j; + path[ARRAY_SIZE(path) - 1] = '\0'; + cl_git_pass(git_index_add_from_buffer(index, &entry, buf, sizeof(buf))); + } + } + + cl_git_pass(git_index_write(index)); + cl_git_pass(p_stat(git_index_path(index), &st)); + + /* + * Without path compression, the written paths would at + * least take + * + * (entries * pathlen) = len + * (676 * 250) = 169000 + * + * bytes. As index v4 uses suffix-compression and our + * written paths only differ in the last two entries, + * this number will be much smaller, e.g. + * + * (1 * pathlen) + (675 * 2) = len + * 676 + 1350 = 2026 + * + * bytes. + * + * Note that the above calculations do not include + * additional metadata of the index, e.g. OIDs or + * index extensions. Including those we get an index + * of approx. 200kB without compression and 40kB with + * compression. As this is a lot smaller than without + * compression, we can verify that path compression is + * used. + */ + cl_assert_(st.st_size < 75000, "path compression not enabled"); + + git_index_free(index); +} diff --git a/tests/libgit2/iterator/index.c b/tests/libgit2/iterator/index.c new file mode 100644 index 000000000..7218b4f75 --- /dev/null +++ b/tests/libgit2/iterator/index.c @@ -0,0 +1,1385 @@ +#include "clar_libgit2.h" +#include "iterator.h" +#include "repository.h" +#include "futils.h" +#include "iterator_helpers.h" +#include "../submodule/submodule_helpers.h" +#include + +static git_repository *g_repo; + +void test_iterator_index__initialize(void) +{ +} + +void test_iterator_index__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static void index_iterator_test( + const char *sandbox, + const char *start, + const char *end, + git_iterator_flag_t flags, + int expected_count, + const char **expected_names, + const char **expected_oids) +{ + git_index *index; + git_iterator *i; + const git_index_entry *entry; + int error, count = 0, caps; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init(sandbox); + + cl_git_pass(git_repository_index(&index, g_repo)); + caps = git_index_caps(index); + + iter_opts.flags = flags; + iter_opts.start = start; + iter_opts.end = end; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &iter_opts)); + + while (!(error = git_iterator_advance(&entry, i))) { + cl_assert(entry); + + if (expected_names != NULL) + cl_assert_equal_s(expected_names[count], entry->path); + + if (expected_oids != NULL) { + git_oid oid; + cl_git_pass(git_oid_fromstr(&oid, expected_oids[count])); + cl_assert_equal_oid(&oid, &entry->id); + } + + count++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(!entry); + cl_assert_equal_i(expected_count, count); + + git_iterator_free(i); + + cl_assert(caps == git_index_caps(index)); + git_index_free(index); +} + +static const char *expected_index_0[] = { + "attr0", + "attr1", + "attr2", + "attr3", + "binfile", + "gitattributes", + "macro_bad", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "sub/abc", + "sub/file", + "sub/sub/file", + "sub/sub/subsub.txt", + "sub/subdir_test1", + "sub/subdir_test2.txt", + "subdir/.gitattributes", + "subdir/abc", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", +}; + +static const char *expected_index_oids_0[] = { + "556f8c827b8e4a02ad5cab77dca2bcb3e226b0b3", + "3b74db7ab381105dc0d28f8295a77f6a82989292", + "2c66e14f77196ea763fb1e41612c1aa2bc2d8ed2", + "c485abe35abd4aa6fd83b076a78bbea9e2e7e06c", + "d800886d9c86731ae5c4a62b0b77c437015e00d2", + "2b40c5aca159b04ea8d20ffe36cdf8b09369b14a", + "5819a185d77b03325aaf87cafc771db36f6ddca7", + "ff69f8639ce2e6010b3f33a74160aad98b48da2b", + "45141a79a77842c59a63229403220a4e4be74e3d", + "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d", + "108bb4e7fd7b16490dc33ff7d972151e73d7166e", + "a0f7217ae99f5ac3e88534f5cea267febc5fa85b", + "3e42ffc54a663f9401cc25843d6c0e71a33e4249", + "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", + "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", + "9e5bdc47d6a80f2be0ea3049ad74231b94609242", + "e563cf4758f0d646f1b14b76016aa17fa9e549a4", + "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78", + "99eae476896f4907224978b88e5ecaa6c5bb67a9", + "3e42ffc54a663f9401cc25843d6c0e71a33e4249", + "e563cf4758f0d646f1b14b76016aa17fa9e549a4", + "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78", + "dccada462d3df8ac6de596fb8c896aba9344f941" +}; + +void test_iterator_index__0(void) +{ + index_iterator_test( + "attr", NULL, NULL, 0, ARRAY_SIZE(expected_index_0), + expected_index_0, expected_index_oids_0); +} + +static const char *expected_index_1[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_new_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", +}; + +static const char* expected_index_oids_1[] = { + "a0de7e0ac200c489c41c59dfa910154a70264e6e", + "5452d32f1dd538eb0405e8a83cc185f79e25e80f", + "452e4244b5d083ddf0460acf1ecc74db9dcfa11a", + "55d316c9ba708999f1918e9677d01dfcae69c6b9", + "a6be623522ce87a1d862128ac42672604f7b468b", + "906ee7711f4f4928ddcb2a5f8fbc500deba0d2a8", + "529a16e8e762d4acb7b9636ff540a00831f9155a", + "90b8c29d8ba39434d1c63e1b093daaa26e5bd972", + "ed062903b8f6f3dccb2fa81117ba6590944ef9bd", + "e8ee89e15bbe9b20137715232387b3de5b28972e", + "53ace0d1cc1145a5f4fe4f78a186a60263190733", + "1888c805345ba265b0ee9449b8877b6064592058", + "a6191982709b746d5650e93c2acf34ef74e11504" +}; + +void test_iterator_index__1(void) +{ + index_iterator_test( + "status", NULL, NULL, 0, ARRAY_SIZE(expected_index_1), + expected_index_1, expected_index_oids_1); +} + +static const char *expected_index_range[] = { + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", +}; + +static const char *expected_index_oids_range[] = { + "45141a79a77842c59a63229403220a4e4be74e3d", + "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d", + "108bb4e7fd7b16490dc33ff7d972151e73d7166e", + "a0f7217ae99f5ac3e88534f5cea267febc5fa85b", +}; + +void test_iterator_index__range(void) +{ + index_iterator_test( + "attr", "root", "root", 0, ARRAY_SIZE(expected_index_range), + expected_index_range, expected_index_oids_range); +} + +void test_iterator_index__range_empty_0(void) +{ + index_iterator_test( + "attr", "empty", "empty", 0, 0, NULL, NULL); +} + +void test_iterator_index__range_empty_1(void) +{ + index_iterator_test( + "attr", "z_empty_after", NULL, 0, 0, NULL, NULL); +} + +void test_iterator_index__range_empty_2(void) +{ + index_iterator_test( + "attr", NULL, ".aaa_empty_before", 0, 0, NULL, NULL); +} + +static void check_index_range( + git_repository *repo, + const char *start, + const char *end, + bool ignore_case, + int expected_count) +{ + git_index *index; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + int error, count, caps; + bool is_ignoring_case; + + cl_git_pass(git_repository_index(&index, repo)); + + caps = git_index_caps(index); + is_ignoring_case = ((caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0); + + if (ignore_case != is_ignoring_case) + cl_git_pass(git_index_set_caps(index, caps ^ GIT_INDEX_CAPABILITY_IGNORE_CASE)); + + i_opts.flags = 0; + i_opts.start = start; + i_opts.end = end; + + cl_git_pass(git_iterator_for_index(&i, repo, index, &i_opts)); + + cl_assert(git_iterator_ignore_case(i) == ignore_case); + + for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count) + /* count em up */; + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(expected_count, count); + + git_iterator_free(i); + git_index_free(index); +} + +void test_iterator_index__range_icase(void) +{ + git_index *index; + git_tree *head; + + g_repo = cl_git_sandbox_init("testrepo"); + + /* reset index to match HEAD */ + cl_git_pass(git_repository_head_tree(&head, g_repo)); + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read_tree(index, head)); + cl_git_pass(git_index_write(index)); + git_tree_free(head); + git_index_free(index); + + /* do some ranged iterator checks toggling case sensitivity */ + check_index_range(g_repo, "B", "C", false, 0); + check_index_range(g_repo, "B", "C", true, 1); + check_index_range(g_repo, "a", "z", false, 3); + check_index_range(g_repo, "a", "z", true, 4); +} + +static const char *expected_index_cs[] = { + "B", "D", "F", "H", "J", "L/1", "L/B", "L/D", "L/a", "L/c", + "a", "c", "e", "g", "i", "k/1", "k/B", "k/D", "k/a", "k/c", +}; + +static const char *expected_index_ci[] = { + "a", "B", "c", "D", "e", "F", "g", "H", "i", "J", + "k/1", "k/a", "k/B", "k/c", "k/D", "L/1", "L/a", "L/B", "L/c", "L/D", +}; + +void test_iterator_index__case_folding(void) +{ + git_str path = GIT_STR_INIT; + int fs_is_ci = 0; + + cl_git_pass(git_str_joinpath(&path, cl_fixture("icase"), ".gitted/CoNfIg")); + fs_is_ci = git_fs_path_exists(path.ptr); + git_str_dispose(&path); + + index_iterator_test( + "icase", NULL, NULL, 0, ARRAY_SIZE(expected_index_cs), + fs_is_ci ? expected_index_ci : expected_index_cs, NULL); + + cl_git_sandbox_cleanup(); + + index_iterator_test( + "icase", NULL, NULL, GIT_ITERATOR_IGNORE_CASE, + ARRAY_SIZE(expected_index_ci), expected_index_ci, NULL); + + cl_git_sandbox_cleanup(); + + index_iterator_test( + "icase", NULL, NULL, GIT_ITERATOR_DONT_IGNORE_CASE, + ARRAY_SIZE(expected_index_cs), expected_index_cs, NULL); +} + +/* Index contents (including pseudotrees): + * + * 0: a 5: F 10: k/ 16: L/ + * 1: B 6: g 11: k/1 17: L/1 + * 2: c 7: H 12: k/a 18: L/a + * 3: D 8: i 13: k/B 19: L/B + * 4: e 9: J 14: k/c 20: L/c + * 15: k/D 21: L/D + * + * 0: B 5: L/ 11: a 16: k/ + * 1: D 6: L/1 12: c 17: k/1 + * 2: F 7: L/B 13: e 18: k/B + * 3: H 8: L/D 14: g 19: k/D + * 4: J 9: L/a 15: i 20: k/a + * 10: L/c 21: k/c + */ + +void test_iterator_index__icase_0(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* autoexpand with no tree entries for index */ + cl_git_pass(git_iterator_for_index(&i, g_repo, index, NULL)); + expect_iterator_items(i, 20, NULL, 20, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 22, NULL, 22, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 12, NULL, 22, NULL); + git_iterator_free(i); + + git_index_free(index); +} + +void test_iterator_index__icase_1(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + int caps; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + caps = git_index_caps(index); + + /* force case sensitivity */ + cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEX_CAPABILITY_IGNORE_CASE)); + + /* autoexpand with no tree entries over range */ + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 7, NULL, 7, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 3, NULL, 3, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 8, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 4, NULL, 4, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 5, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 1, NULL, 4, NULL); + git_iterator_free(i); + + /* force case insensitivity */ + cl_git_pass(git_index_set_caps(index, caps | GIT_INDEX_CAPABILITY_IGNORE_CASE)); + + /* autoexpand with no tree entries over range */ + i_opts.flags = 0; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 13, NULL, 13, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 5, NULL, 5, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 14, NULL, 14, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 6, NULL, 6, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 9, NULL, 14, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 1, NULL, 6, NULL); + git_iterator_free(i); + + cl_git_pass(git_index_set_caps(index, caps)); + git_index_free(index); +} + +void test_iterator_index__pathlist(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + /* Case sensitive */ + { + const char *expected[] = { + "B", "D", "L/1", "a", "c", "e", "k/1", "k/a" }; + size_t expected_len = 8; + + i_opts.start = NULL; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Case INsensitive */ + { + const char *expected[] = { + "a", "B", "c", "D", "e", "k/1", "k/a", "L/1" }; + size_t expected_len = 8; + + i_opts.start = NULL; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set a start, but no end. Case sensitive. */ + { + const char *expected[] = { "c", "e", "k/1", "k/a" }; + size_t expected_len = 4; + + i_opts.start = "c"; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set a start, but no end. Case INsensitive. */ + { + const char *expected[] = { "c", "D", "e", "k/1", "k/a", "L/1" }; + size_t expected_len = 6; + + i_opts.start = "c"; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set no start, but an end. Case sensitive. */ + { + const char *expected[] = { "B", "D", "L/1", "a", "c", "e" }; + size_t expected_len = 6; + + i_opts.start = NULL; + i_opts.end = "e"; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set no start, but an end. Case INsensitive. */ + { + const char *expected[] = { "a", "B", "c", "D", "e" }; + size_t expected_len = 5; + + i_opts.start = NULL; + i_opts.end = "e"; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case sensitive */ + { + const char *expected[] = { "c", "e", "k/1" }; + size_t expected_len = 3; + + i_opts.start = "c"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case sensitive */ + { + const char *expected[] = { "k/1" }; + size_t expected_len = 1; + + i_opts.start = "k"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case INsensitive */ + { + const char *expected[] = { "c", "D", "e", "k/1", "k/a" }; + size_t expected_len = 5; + + i_opts.start = "c"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case INsensitive */ + { + const char *expected[] = { "k/1", "k/a" }; + size_t expected_len = 2; + + i_opts.start = "k"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_with_dirs(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 5, NULL)); + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* Test that a prefix `k` matches folders, even without trailing slash */ + { + const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "k")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that a `k/` matches a folder */ + { + const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "k/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* When the iterator is case sensitive, ensure we can't lookup the + * directory with the wrong case. + */ + { + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "K/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + } + + /* Test that case insensitive matching works. */ + { + const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "K/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that case insensitive matching works without trailing slash. */ + { + const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "K")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_with_dirs_include_trees(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist; + + const char *expected[] = { "k/", "k/1", "k/B", "k/D", "k/a", "k/c" }; + size_t expected_len = 6; + + cl_git_pass(git_vector_init(&filelist, 5, NULL)); + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "k")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_1(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist = GIT_VECTOR_INIT; + int default_icase, expect; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "0")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + + /* In this test we DO NOT force a case setting on the index. */ + default_icase = ((git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "b"; + i_opts.end = "k/D"; + + /* (c D e k/1 k/a ==> 5) vs (c e k/1 ==> 3) */ + expect = default_icase ? 5 : 3; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_2(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist = GIT_VECTOR_INIT; + int default_icase, expect; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "0")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k/")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); + + /* In this test we DO NOT force a case setting on the index. */ + default_icase = ((git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "b"; + i_opts.end = "k/D"; + + /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */ + expect = default_icase ? 8 : 5; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_four(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist = GIT_VECTOR_INIT; + int default_icase, expect; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "0")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); + + /* In this test we DO NOT force a case setting on the index. */ + default_icase = ((git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "b"; + i_opts.end = "k/D"; + + /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */ + expect = default_icase ? 8 : 5; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_icase(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + int caps; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + caps = git_index_caps(index); + + /* force case sensitivity */ + cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEX_CAPABILITY_IGNORE_CASE)); + + /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 3, NULL, 3, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 1, NULL, 1, NULL); + git_iterator_free(i); + + /* force case insensitivity */ + cl_git_pass(git_index_set_caps(index, caps | GIT_INDEX_CAPABILITY_IGNORE_CASE)); + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 5, NULL, 5, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 2, NULL, 2, NULL); + git_iterator_free(i); + + cl_git_pass(git_index_set_caps(index, caps)); + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_with_directory(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + git_index *index; + + g_repo = cl_git_sandbox_init("testrepo2"); + git_repository_head_tree(&tree, g_repo); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "subdir")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 4, NULL, 4, NULL); + git_iterator_free(i); + + git_index_free(index); + git_tree_free(tree); + git_vector_free(&filelist); +} + +static void create_paths(git_index *index, const char *root, int depth) +{ + git_str fullpath = GIT_STR_INIT; + git_index_entry entry; + size_t root_len; + int i; + + if (root) { + cl_git_pass(git_str_puts(&fullpath, root)); + cl_git_pass(git_str_putc(&fullpath, '/')); + } + + root_len = fullpath.size; + + for (i = 0; i < 8; i++) { + bool file = (depth == 0 || (i % 2) == 0); + git_str_truncate(&fullpath, root_len); + cl_git_pass(git_str_printf(&fullpath, "item%d", i)); + + if (file) { + memset(&entry, 0, sizeof(git_index_entry)); + entry.path = fullpath.ptr; + entry.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&entry.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6"); + + cl_git_pass(git_index_add(index, &entry)); + } else if (depth > 0) { + create_paths(index, fullpath.ptr, (depth - 1)); + } + } + + git_str_dispose(&fullpath); +} + +void test_iterator_index__pathlist_for_deeply_nested_item(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 5, NULL)); + + g_repo = cl_git_sandbox_init("icase"); + cl_git_pass(git_repository_index(&index, g_repo)); + + create_paths(index, NULL, 3); + + /* Ensure that we find the single path we're interested in */ + { + const char *expected[] = { "item1/item3/item5/item7" }; + size_t expected_len = 1; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/item7")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + { + const char *expected[] = { + "item1/item3/item5/item0", "item1/item3/item5/item1", + "item1/item3/item5/item2", "item1/item3/item5/item3", + "item1/item3/item5/item4", "item1/item3/item5/item5", + "item1/item3/item5/item6", "item1/item3/item5/item7", + }; + size_t expected_len = 8; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + { + const char *expected[] = { + "item1/item3/item0", + "item1/item3/item1/item0", "item1/item3/item1/item1", + "item1/item3/item1/item2", "item1/item3/item1/item3", + "item1/item3/item1/item4", "item1/item3/item1/item5", + "item1/item3/item1/item6", "item1/item3/item1/item7", + "item1/item3/item2", + "item1/item3/item3/item0", "item1/item3/item3/item1", + "item1/item3/item3/item2", "item1/item3/item3/item3", + "item1/item3/item3/item4", "item1/item3/item3/item5", + "item1/item3/item3/item6", "item1/item3/item3/item7", + "item1/item3/item4", + "item1/item3/item5/item0", "item1/item3/item5/item1", + "item1/item3/item5/item2", "item1/item3/item5/item3", + "item1/item3/item5/item4", "item1/item3/item5/item5", + "item1/item3/item5/item6", "item1/item3/item5/item7", + "item1/item3/item6", + "item1/item3/item7/item0", "item1/item3/item7/item1", + "item1/item3/item7/item2", "item1/item3/item7/item3", + "item1/item3/item7/item4", "item1/item3/item7/item5", + "item1/item3/item7/item6", "item1/item3/item7/item7", + }; + size_t expected_len = 36; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item1/item3/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Ensure that we find the single path we're interested in, and we find + * it efficiently, and don't stat the entire world to get there. + */ + { + const char *expected[] = { + "item0", "item1/item2", "item5/item7/item4", "item6", + "item7/item3/item1/item6" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item7/item3/item1/item6")); + cl_git_pass(git_vector_insert(&filelist, "item6")); + cl_git_pass(git_vector_insert(&filelist, "item5/item7/item4")); + cl_git_pass(git_vector_insert(&filelist, "item1/item2")); + cl_git_pass(git_vector_insert(&filelist, "item0")); + + /* also add some things that don't exist or don't match the right type */ + cl_git_pass(git_vector_insert(&filelist, "item2/")); + cl_git_pass(git_vector_insert(&filelist, "itemN")); + cl_git_pass(git_vector_insert(&filelist, "item1/itemA")); + cl_git_pass(git_vector_insert(&filelist, "item5/item3/item4/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__advance_over(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + g_repo = cl_git_sandbox_init("icase"); + cl_git_pass(git_repository_index(&index, g_repo)); + + create_paths(index, NULL, 1); + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + + expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item0", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item1/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item2", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item3/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item4", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item5/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item6", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item7/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + git_index_free(index); +} + +void test_iterator_index__advance_into(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + + g_repo = cl_git_sandbox_init("icase"); + + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_advance_into(i, "B"); + expect_advance_into(i, "D"); + expect_advance_into(i, "F"); + expect_advance_into(i, "H"); + expect_advance_into(i, "J"); + expect_advance_into(i, "L/"); + expect_advance_into(i, "L/1"); + expect_advance_into(i, "L/B"); + expect_advance_into(i, "L/D"); + expect_advance_into(i, "L/a"); + expect_advance_into(i, "L/c"); + expect_advance_into(i, "a"); + expect_advance_into(i, "c"); + expect_advance_into(i, "e"); + expect_advance_into(i, "g"); + expect_advance_into(i, "i"); + expect_advance_into(i, "k/"); + expect_advance_into(i, "k/1"); + expect_advance_into(i, "k/B"); + expect_advance_into(i, "k/D"); + expect_advance_into(i, "k/a"); + expect_advance_into(i, "k/c"); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + git_index_free(index); +} + +void test_iterator_index__advance_into_and_over(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + + g_repo = cl_git_sandbox_init("icase"); + + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_git_pass(git_repository_index(&index, g_repo)); + + create_paths(index, NULL, 2); + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_advance_into(i, "B"); + expect_advance_into(i, "D"); + expect_advance_into(i, "F"); + expect_advance_into(i, "H"); + expect_advance_into(i, "J"); + expect_advance_into(i, "L/"); + expect_advance_into(i, "L/1"); + expect_advance_into(i, "L/B"); + expect_advance_into(i, "L/D"); + expect_advance_into(i, "L/a"); + expect_advance_into(i, "L/c"); + expect_advance_into(i, "a"); + expect_advance_into(i, "c"); + expect_advance_into(i, "e"); + expect_advance_into(i, "g"); + expect_advance_into(i, "i"); + expect_advance_into(i, "item0"); + expect_advance_into(i, "item1/"); + expect_advance_into(i, "item1/item0"); + expect_advance_into(i, "item1/item1/"); + expect_advance_into(i, "item1/item1/item0"); + expect_advance_into(i, "item1/item1/item1"); + expect_advance_into(i, "item1/item1/item2"); + expect_advance_into(i, "item1/item1/item3"); + expect_advance_into(i, "item1/item1/item4"); + expect_advance_into(i, "item1/item1/item5"); + expect_advance_into(i, "item1/item1/item6"); + expect_advance_into(i, "item1/item1/item7"); + expect_advance_into(i, "item1/item2"); + expect_advance_over(i, "item1/item3/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item1/item4", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item1/item5/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item1/item6", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item1/item7/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_into(i, "item2"); + expect_advance_over(i, "item3/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item4", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item5/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item6", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item7/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_into(i, "k/"); + expect_advance_into(i, "k/1"); + expect_advance_into(i, "k/B"); + expect_advance_into(i, "k/D"); + expect_advance_into(i, "k/a"); + expect_advance_into(i, "k/c"); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + git_index_free(index); +} + +static void add_conflict( + git_index *index, + const char *ancestor_path, + const char *our_path, + const char *their_path) +{ + git_index_entry ancestor = {{0}}, ours = {{0}}, theirs = {{0}}; + + ancestor.path = ancestor_path; + ancestor.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&ancestor.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6"); + GIT_INDEX_ENTRY_STAGE_SET(&ancestor, 1); + + ours.path = our_path; + ours.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&ours.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6"); + GIT_INDEX_ENTRY_STAGE_SET(&ours, 2); + + theirs.path = their_path; + theirs.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&theirs.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6"); + GIT_INDEX_ENTRY_STAGE_SET(&theirs, 3); + + cl_git_pass(git_index_conflict_add(index, &ancestor, &ours, &theirs)); +} + +void test_iterator_index__include_conflicts(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + g_repo = cl_git_sandbox_init("icase"); + cl_git_pass(git_repository_index(&index, g_repo)); + + add_conflict(index, "CONFLICT1", "CONFLICT1" ,"CONFLICT1"); + add_conflict(index, "ZZZ-CONFLICT2.ancestor", "ZZZ-CONFLICT2.ours", "ZZZ-CONFLICT2.theirs"); + add_conflict(index, "ancestor.conflict3", "ours.conflict3", "theirs.conflict3"); + add_conflict(index, "zzz-conflict4", "zzz-conflict4", "zzz-conflict4"); + + /* Iterate the index, ensuring that conflicts are not included */ + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + + expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + + /* Try again, returning conflicts */ + i_opts.flags |= GIT_ITERATOR_INCLUDE_CONFLICTS; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + + expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "CONFLICT1", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "CONFLICT1", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "CONFLICT1", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "ZZZ-CONFLICT2.ancestor", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "ZZZ-CONFLICT2.ours", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "ZZZ-CONFLICT2.theirs", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "ancestor.conflict3", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "ours.conflict3", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "theirs.conflict3", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "zzz-conflict4", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "zzz-conflict4", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "zzz-conflict4", GIT_ITERATOR_STATUS_NORMAL); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + + git_index_free(index); +} diff --git a/tests/libgit2/iterator/iterator_helpers.c b/tests/libgit2/iterator/iterator_helpers.c new file mode 100644 index 000000000..b210dbb0c --- /dev/null +++ b/tests/libgit2/iterator/iterator_helpers.c @@ -0,0 +1,143 @@ +#include "clar_libgit2.h" +#include "iterator.h" +#include "repository.h" +#include "futils.h" +#include "iterator_helpers.h" +#include + +static void assert_at_end(git_iterator *i, bool verbose) +{ + const git_index_entry *end; + int error = git_iterator_advance(&end, i); + + if (verbose && error != GIT_ITEROVER) + fprintf(stderr, "Expected end of iterator, got '%s'\n", end->path); + + cl_git_fail_with(GIT_ITEROVER, error); +} + +void expect_iterator_items( + git_iterator *i, + size_t expected_flat, + const char **expected_flat_paths, + size_t expected_total, + const char **expected_total_paths) +{ + const git_index_entry *entry; + size_t count; + int no_trees = !(git_iterator_flags(i) & GIT_ITERATOR_INCLUDE_TREES); + bool v = false; + int error; + + if (v) fprintf(stderr, "== %s ==\n", no_trees ? "notrees" : "trees"); + + count = 0; + + while (!git_iterator_advance(&entry, i)) { + if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode); + + if (no_trees) + cl_assert(entry->mode != GIT_FILEMODE_TREE); + + if (expected_flat_paths) { + const char *expect_path = expected_flat_paths[count]; + size_t expect_len = strlen(expect_path); + + cl_assert_equal_s(expect_path, entry->path); + + if (expect_path[expect_len - 1] == '/') + cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode); + else + cl_assert(entry->mode != GIT_FILEMODE_TREE); + } + + cl_assert(++count <= expected_flat); + } + + assert_at_end(i, v); + cl_assert_equal_i(expected_flat, count); + + cl_git_pass(git_iterator_reset(i)); + + count = 0; + cl_git_pass(git_iterator_current(&entry, i)); + + if (v) fprintf(stderr, "-- %s --\n", no_trees ? "notrees" : "trees"); + + while (entry != NULL) { + if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode); + + if (no_trees) + cl_assert(entry->mode != GIT_FILEMODE_TREE); + + if (expected_total_paths) { + const char *expect_path = expected_total_paths[count]; + size_t expect_len = strlen(expect_path); + + cl_assert_equal_s(expect_path, entry->path); + + if (expect_path[expect_len - 1] == '/') + cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode); + else + cl_assert(entry->mode != GIT_FILEMODE_TREE); + } + + if (entry->mode == GIT_FILEMODE_TREE) { + error = git_iterator_advance_into(&entry, i); + + /* could return NOTFOUND if directory is empty */ + cl_assert(!error || error == GIT_ENOTFOUND); + + if (error == GIT_ENOTFOUND) { + error = git_iterator_advance(&entry, i); + cl_assert(!error || error == GIT_ITEROVER); + } + } else { + error = git_iterator_advance(&entry, i); + cl_assert(!error || error == GIT_ITEROVER); + } + + if (++count >= expected_total) + break; + } + + assert_at_end(i, v); + cl_assert_equal_i(expected_total, count); +} + + +void expect_advance_over( + git_iterator *i, + const char *expected_path, + git_iterator_status_t expected_status) +{ + const git_index_entry *entry; + git_iterator_status_t status; + int error; + + cl_git_pass(git_iterator_current(&entry, i)); + cl_assert_equal_s(expected_path, entry->path); + + error = git_iterator_advance_over(&entry, &status, i); + cl_assert(!error || error == GIT_ITEROVER); + cl_assert_equal_i(expected_status, status); +} + +void expect_advance_into( + git_iterator *i, + const char *expected_path) +{ + const git_index_entry *entry; + int error; + + cl_git_pass(git_iterator_current(&entry, i)); + cl_assert_equal_s(expected_path, entry->path); + + if (S_ISDIR(entry->mode)) + error = git_iterator_advance_into(&entry, i); + else + error = git_iterator_advance(&entry, i); + + cl_assert(!error || error == GIT_ITEROVER); +} + diff --git a/tests/libgit2/iterator/iterator_helpers.h b/tests/libgit2/iterator/iterator_helpers.h new file mode 100644 index 000000000..1884b41a1 --- /dev/null +++ b/tests/libgit2/iterator/iterator_helpers.h @@ -0,0 +1,16 @@ + +extern void expect_iterator_items( + git_iterator *i, + size_t expected_flat, + const char **expected_flat_paths, + size_t expected_total, + const char **expected_total_paths); + +extern void expect_advance_over( + git_iterator *i, + const char *expected_path, + git_iterator_status_t expected_status); + +void expect_advance_into( + git_iterator *i, + const char *expected_path); diff --git a/tests/libgit2/iterator/tree.c b/tests/libgit2/iterator/tree.c new file mode 100644 index 000000000..4145c8dea --- /dev/null +++ b/tests/libgit2/iterator/tree.c @@ -0,0 +1,1080 @@ +#include "clar_libgit2.h" +#include "iterator.h" +#include "repository.h" +#include "futils.h" +#include "tree.h" +#include "../submodule/submodule_helpers.h" +#include "../diff/diff_helpers.h" +#include "iterator_helpers.h" +#include + +static git_repository *g_repo; + +void test_iterator_tree__initialize(void) +{ +} + +void test_iterator_tree__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static void tree_iterator_test( + const char *sandbox, + const char *treeish, + const char *start, + const char *end, + int expected_count, + const char **expected_values) +{ + git_tree *t; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + int error, count = 0, count_post_reset = 0; + + g_repo = cl_git_sandbox_init(sandbox); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + i_opts.start = start; + i_opts.end = end; + + cl_assert(t = resolve_commit_oid_to_tree(g_repo, treeish)); + cl_git_pass(git_iterator_for_tree(&i, t, &i_opts)); + + /* test loop */ + while (!(error = git_iterator_advance(&entry, i))) { + cl_assert(entry); + if (expected_values != NULL) + cl_assert_equal_s(expected_values[count], entry->path); + count++; + } + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(!entry); + cl_assert_equal_i(expected_count, count); + + /* test reset */ + cl_git_pass(git_iterator_reset(i)); + + while (!(error = git_iterator_advance(&entry, i))) { + cl_assert(entry); + if (expected_values != NULL) + cl_assert_equal_s(expected_values[count_post_reset], entry->path); + count_post_reset++; + } + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(!entry); + cl_assert_equal_i(count, count_post_reset); + + git_iterator_free(i); + git_tree_free(t); +} + +/* results of: git ls-tree -r --name-only 605812a */ +const char *expected_tree_0[] = { + ".gitattributes", + "attr0", + "attr1", + "attr2", + "attr3", + "binfile", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "subdir/.gitattributes", + "subdir/abc", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", + NULL +}; + +void test_iterator_tree__0(void) +{ + tree_iterator_test("attr", "605812a", NULL, NULL, 16, expected_tree_0); +} + +/* results of: git ls-tree -r --name-only 6bab5c79 */ +const char *expected_tree_1[] = { + ".gitattributes", + "attr0", + "attr1", + "attr2", + "attr3", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "subdir/.gitattributes", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", + NULL +}; + +void test_iterator_tree__1(void) +{ + tree_iterator_test("attr", "6bab5c79cd5", NULL, NULL, 13, expected_tree_1); +} + +/* results of: git ls-tree -r --name-only 26a125ee1 */ +const char *expected_tree_2[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", + NULL +}; + +void test_iterator_tree__2(void) +{ + tree_iterator_test("status", "26a125ee1", NULL, NULL, 12, expected_tree_2); +} + +/* $ git ls-tree -r --name-only 0017bd4ab1e */ +const char *expected_tree_3[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file" +}; + +void test_iterator_tree__3(void) +{ + tree_iterator_test("status", "0017bd4ab1e", NULL, NULL, 8, expected_tree_3); +} + +/* $ git ls-tree -r --name-only 24fa9a9fc4e202313e24b648087495441dab432b */ +const char *expected_tree_4[] = { + "attr0", + "attr1", + "attr2", + "attr3", + "binfile", + "gitattributes", + "macro_bad", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "sub/abc", + "sub/file", + "sub/sub/file", + "sub/sub/subsub.txt", + "sub/subdir_test1", + "sub/subdir_test2.txt", + "subdir/.gitattributes", + "subdir/abc", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", + NULL +}; + +void test_iterator_tree__4(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", NULL, NULL, + 23, expected_tree_4); +} + +void test_iterator_tree__4_ranged(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "sub", "sub", + 11, &expected_tree_4[12]); +} + +const char *expected_tree_ranged_0[] = { + "gitattributes", + "macro_bad", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + NULL +}; + +void test_iterator_tree__ranged_0(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "git", "root", + 7, expected_tree_ranged_0); +} + +const char *expected_tree_ranged_1[] = { + "sub/subdir_test2.txt", + NULL +}; + +void test_iterator_tree__ranged_1(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "sub/subdir_test2.txt", "sub/subdir_test2.txt", + 1, expected_tree_ranged_1); +} + +void test_iterator_tree__range_empty_0(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "empty", "empty", 0, NULL); +} + +void test_iterator_tree__range_empty_1(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "z_empty_after", NULL, 0, NULL); +} + +void test_iterator_tree__range_empty_2(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + NULL, ".aaa_empty_before", 0, NULL); +} + +static void check_tree_entry( + git_iterator *i, + const char *oid, + const char *oid_p, + const char *oid_pp, + const char *oid_ppp) +{ + const git_index_entry *ie; + const git_tree_entry *te; + const git_tree *tree; + + cl_git_pass(git_iterator_current_tree_entry(&te, i)); + cl_assert(te); + cl_assert(git_oid_streq(te->oid, oid) == 0); + + cl_git_pass(git_iterator_current(&ie, i)); + + if (oid_p) { + cl_git_pass(git_iterator_current_parent_tree(&tree, i, 0)); + cl_assert(tree); + cl_assert(git_oid_streq(git_tree_id(tree), oid_p) == 0); + } + + if (oid_pp) { + cl_git_pass(git_iterator_current_parent_tree(&tree, i, 1)); + cl_assert(tree); + cl_assert(git_oid_streq(git_tree_id(tree), oid_pp) == 0); + } + + if (oid_ppp) { + cl_git_pass(git_iterator_current_parent_tree(&tree, i, 2)); + cl_assert(tree); + cl_assert(git_oid_streq(git_tree_id(tree), oid_ppp) == 0); + } +} + +void test_iterator_tree__special_functions(void) +{ + git_tree *t; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + int error, cases = 0; + const char *rootoid = "ce39a97a7fb1fa90bcf5e711249c1e507476ae0e"; + + g_repo = cl_git_sandbox_init("attr"); + + t = resolve_commit_oid_to_tree( + g_repo, "24fa9a9fc4e202313e24b648087495441dab432b"); + cl_assert(t != NULL); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_tree(&i, t, &i_opts)); + + while (!(error = git_iterator_advance(&entry, i))) { + cl_assert(entry); + + if (strcmp(entry->path, "sub/file") == 0) { + cases++; + check_tree_entry( + i, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", + "ecb97df2a174987475ac816e3847fc8e9f6c596b", + rootoid, NULL); + } + else if (strcmp(entry->path, "sub/sub/subsub.txt") == 0) { + cases++; + check_tree_entry( + i, "9e5bdc47d6a80f2be0ea3049ad74231b94609242", + "4e49ba8c5b6c32ff28cd9dcb60be34df50fcc485", + "ecb97df2a174987475ac816e3847fc8e9f6c596b", rootoid); + } + else if (strcmp(entry->path, "subdir/.gitattributes") == 0) { + cases++; + check_tree_entry( + i, "99eae476896f4907224978b88e5ecaa6c5bb67a9", + "9fb40b6675dde60b5697afceae91b66d908c02d9", + rootoid, NULL); + } + else if (strcmp(entry->path, "subdir2/subdir2_test1") == 0) { + cases++; + check_tree_entry( + i, "dccada462d3df8ac6de596fb8c896aba9344f941", + "2929de282ce999e95183aedac6451d3384559c4b", + rootoid, NULL); + } + } + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(!entry); + cl_assert_equal_i(4, cases); + + git_iterator_free(i); + git_tree_free(t); +} + +static void check_tree_range( + git_repository *repo, + const char *start, + const char *end, + bool ignore_case, + int expected_count) +{ + git_tree *head; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + int error, count; + + i_opts.flags = ignore_case ? GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; + i_opts.start = start; + i_opts.end = end; + + cl_git_pass(git_repository_head_tree(&head, repo)); + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + + for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count) + /* count em up */; + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(expected_count, count); + + git_iterator_free(i); + git_tree_free(head); +} + +void test_iterator_tree__range_icase(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); + + check_tree_range(g_repo, "B", "C", false, 0); + check_tree_range(g_repo, "B", "C", true, 1); + check_tree_range(g_repo, "b", "c", false, 1); + check_tree_range(g_repo, "b", "c", true, 1); + + check_tree_range(g_repo, "a", "z", false, 3); + check_tree_range(g_repo, "a", "z", true, 4); + check_tree_range(g_repo, "A", "Z", false, 1); + check_tree_range(g_repo, "A", "Z", true, 4); + check_tree_range(g_repo, "a", "Z", false, 0); + check_tree_range(g_repo, "a", "Z", true, 4); + check_tree_range(g_repo, "A", "z", false, 4); + check_tree_range(g_repo, "A", "z", true, 4); + + check_tree_range(g_repo, "new.txt", "new.txt", true, 1); + check_tree_range(g_repo, "new.txt", "new.txt", false, 1); + check_tree_range(g_repo, "README", "README", true, 1); + check_tree_range(g_repo, "README", "README", false, 1); +} + +void test_iterator_tree__icase_0(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_tree *head; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_head_tree(&head, g_repo)); + + /* auto expand with no tree entries */ + cl_git_pass(git_iterator_for_tree(&i, head, NULL)); + expect_iterator_items(i, 20, NULL, 20, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 22, NULL, 22, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 12, NULL, 22, NULL); + git_iterator_free(i); + + git_tree_free(head); +} + +void test_iterator_tree__icase_1(void) +{ + git_iterator *i; + git_tree *head; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_head_tree(&head, g_repo)); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + /* auto expand with no tree entries */ + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 7, NULL, 7, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 3, NULL, 3, NULL); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + /* auto expand with tree entries */ + i_opts.start = "c"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 8, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 4, NULL, 4, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 5, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 1, NULL, 4, NULL); + git_iterator_free(i); + + /* auto expand with no tree entries */ + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 13, NULL, 13, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 5, NULL, 5, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 14, NULL, 14, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 6, NULL, 6, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 9, NULL, 14, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 1, NULL, 6, NULL); + git_iterator_free(i); + + git_tree_free(head); +} + +void test_iterator_tree__icase_2(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_tree *head; + static const char *expect_basic[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", + NULL, + }; + static const char *expect_trees[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "subdir.txt", + "subdir/", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", + NULL, + }; + static const char *expect_noauto[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "subdir.txt", + "subdir/", + NULL + }; + + g_repo = cl_git_sandbox_init("status"); + + cl_git_pass(git_repository_head_tree(&head, g_repo)); + + /* auto expand with no tree entries */ + cl_git_pass(git_iterator_for_tree(&i, head, NULL)); + expect_iterator_items(i, 12, expect_basic, 12, expect_basic); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 13, expect_trees, 13, expect_trees); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 10, expect_noauto, 13, expect_trees); + git_iterator_free(i); + + git_tree_free(head); +} + +/* "b=name,t=name", blob_id, tree_id */ +static void build_test_tree( + git_oid *out, git_repository *repo, const char *fmt, ...) +{ + git_oid *id; + git_treebuilder *builder; + const char *scan = fmt, *next; + char type, delimiter; + git_filemode_t mode = GIT_FILEMODE_BLOB; + git_str name = GIT_STR_INIT; + va_list arglist; + + cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); /* start builder */ + + va_start(arglist, fmt); + while (*scan) { + switch (type = *scan++) { + case 't': case 'T': mode = GIT_FILEMODE_TREE; break; + case 'b': case 'B': mode = GIT_FILEMODE_BLOB; break; + default: + cl_assert(type == 't' || type == 'T' || type == 'b' || type == 'B'); + } + + delimiter = *scan++; /* read and skip delimiter */ + for (next = scan; *next && *next != delimiter; ++next) + /* seek end */; + cl_git_pass(git_str_set(&name, scan, (size_t)(next - scan))); + for (scan = next; *scan && (*scan == delimiter || *scan == ','); ++scan) + /* skip delimiter and optional comma */; + + id = va_arg(arglist, git_oid *); + + cl_git_pass(git_treebuilder_insert(NULL, builder, name.ptr, id, mode)); + } + va_end(arglist); + + cl_git_pass(git_treebuilder_write(out, builder)); + + git_treebuilder_free(builder); + git_str_dispose(&name); +} + +void test_iterator_tree__case_conflicts_0(void) +{ + const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6"; + git_tree *tree; + git_oid blob_id, biga_id, littlea_id, tree_id; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + const char *expect_cs[] = { + "A/1.file", "A/3.file", "a/2.file", "a/4.file" }; + const char *expect_ci[] = { + "A/1.file", "a/2.file", "A/3.file", "a/4.file" }; + const char *expect_cs_trees[] = { + "A/", "A/1.file", "A/3.file", "a/", "a/2.file", "a/4.file" }; + const char *expect_ci_trees[] = { + "A/", "A/1.file", "a/2.file", "A/3.file", "a/4.file" }; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */ + + /* create tree with: A/1.file, A/3.file, a/2.file, a/4.file */ + build_test_tree( + &biga_id, g_repo, "b|1.file|,b|3.file|", &blob_id, &blob_id); + build_test_tree( + &littlea_id, g_repo, "b|2.file|,b|4.file|", &blob_id, &blob_id); + build_test_tree( + &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 4, expect_cs, 4, expect_cs); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 4, expect_ci, 4, expect_ci); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 6, expect_cs_trees, 6, expect_cs_trees); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 5, expect_ci_trees, 5, expect_ci_trees); + git_iterator_free(i); + + git_tree_free(tree); +} + +void test_iterator_tree__case_conflicts_1(void) +{ + const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6"; + git_tree *tree; + git_oid blob_id, Ab_id, biga_id, littlea_id, tree_id; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + const char *expect_cs[] = { + "A/a", "A/b/1", "A/c", "a/C", "a/a", "a/b" }; + const char *expect_ci[] = { + "A/a", "a/b", "A/b/1", "A/c" }; + const char *expect_cs_trees[] = { + "A/", "A/a", "A/b/", "A/b/1", "A/c", "a/", "a/C", "a/a", "a/b" }; + const char *expect_ci_trees[] = { + "A/", "A/a", "a/b", "A/b/", "A/b/1", "A/c" }; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */ + + /* create: A/a A/b/1 A/c a/a a/b a/C */ + build_test_tree(&Ab_id, g_repo, "b|1|", &blob_id); + build_test_tree( + &biga_id, g_repo, "b|a|,t|b|,b|c|", &blob_id, &Ab_id, &blob_id); + build_test_tree( + &littlea_id, g_repo, "b|a|,b|b|,b|C|", &blob_id, &blob_id, &blob_id); + build_test_tree( + &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 6, expect_cs, 6, expect_cs); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 4, expect_ci, 4, expect_ci); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 9, expect_cs_trees, 9, expect_cs_trees); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 6, expect_ci_trees, 6, expect_ci_trees); + git_iterator_free(i); + + git_tree_free(tree); +} + +void test_iterator_tree__case_conflicts_2(void) +{ + const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6"; + git_tree *tree; + git_oid blob_id, d1, d2, c1, c2, b1, b2, a1, a2, tree_id; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + const char *expect_cs[] = { + "A/B/C/D/16", "A/B/C/D/foo", "A/B/C/d/15", "A/B/C/d/FOO", + "A/B/c/D/14", "A/B/c/D/foo", "A/B/c/d/13", "A/B/c/d/FOO", + "A/b/C/D/12", "A/b/C/D/foo", "A/b/C/d/11", "A/b/C/d/FOO", + "A/b/c/D/10", "A/b/c/D/foo", "A/b/c/d/09", "A/b/c/d/FOO", + "a/B/C/D/08", "a/B/C/D/foo", "a/B/C/d/07", "a/B/C/d/FOO", + "a/B/c/D/06", "a/B/c/D/foo", "a/B/c/d/05", "a/B/c/d/FOO", + "a/b/C/D/04", "a/b/C/D/foo", "a/b/C/d/03", "a/b/C/d/FOO", + "a/b/c/D/02", "a/b/c/D/foo", "a/b/c/d/01", "a/b/c/d/FOO", }; + const char *expect_ci[] = { + "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04", + "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08", + "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12", + "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16", + "A/B/C/D/foo", }; + const char *expect_ci_trees[] = { + "A/", "A/B/", "A/B/C/", "A/B/C/D/", + "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04", + "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08", + "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12", + "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16", + "A/B/C/D/foo", }; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */ + + build_test_tree(&d1, g_repo, "b|16|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|15|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&d1, g_repo, "b|14|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|13|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2); + + build_test_tree(&d1, g_repo, "b|12|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|11|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&d1, g_repo, "b|10|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|09|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2); + + build_test_tree(&a1, g_repo, "t|B|,t|b|", &b1, &b2); + + build_test_tree(&d1, g_repo, "b|08|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|07|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&d1, g_repo, "b|06|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|05|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2); + + build_test_tree(&d1, g_repo, "b|04|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|03|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&d1, g_repo, "b|02|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|01|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2); + + build_test_tree(&a2, g_repo, "t|B|,t|b|", &b1, &b2); + + build_test_tree(&tree_id, g_repo, "t/A/,t/a/", &a1, &a2); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 32, expect_cs, 32, expect_cs); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 17, expect_ci, 17, expect_ci); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 21, expect_ci_trees, 21, expect_ci_trees); + git_iterator_free(i); + + git_tree_free(tree); +} + +void test_iterator_tree__pathlist(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + bool default_icase; + int expect; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + git_repository_head_tree(&tree, g_repo); + + /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ + /* In this test we DO NOT force a case on the iterators and verify default behavior. */ + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 8, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "c"; + i_opts.end = NULL; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + default_icase = git_iterator_ignore_case(i); + /* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */ + expect = ((default_icase) ? 6 : 4); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + i_opts.start = NULL; + i_opts.end = "e"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + default_icase = git_iterator_ignore_case(i); + /* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */ + expect = ((default_icase) ? 5 : 6); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_vector_free(&filelist); + git_tree_free(tree); +} + +void test_iterator_tree__pathlist_icase(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZ")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + git_repository_head_tree(&tree, g_repo); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 3, NULL, 3, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 1, NULL, 1, NULL); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 5, NULL, 5, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 2, NULL, 2, NULL); + git_iterator_free(i); + + git_vector_free(&filelist); + git_tree_free(tree); +} + +void test_iterator_tree__pathlist_with_directory(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + + const char *expected[] = { "subdir/README", "subdir/new.txt", + "subdir/subdir2/README", "subdir/subdir2/new.txt" }; + size_t expected_len = 4; + + const char *expected2[] = { "subdir/subdir2/README", "subdir/subdir2/new.txt" }; + size_t expected_len2 = 2; + + g_repo = cl_git_sandbox_init("testrepo2"); + git_repository_head_tree(&tree, g_repo); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "subdir")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "subdir/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "subdir/subdir2")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, expected_len2, expected2, expected_len2, expected2); + git_iterator_free(i); + + git_tree_free(tree); + git_vector_free(&filelist); +} + +void test_iterator_tree__pathlist_with_directory_include_tree_nodes(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + + const char *expected[] = { "subdir/", "subdir/README", "subdir/new.txt", + "subdir/subdir2/", "subdir/subdir2/README", "subdir/subdir2/new.txt" }; + size_t expected_len = 6; + + g_repo = cl_git_sandbox_init("testrepo2"); + git_repository_head_tree(&tree, g_repo); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "subdir")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + + git_tree_free(tree); + git_vector_free(&filelist); +} + +void test_iterator_tree__pathlist_no_match(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + const git_index_entry *entry; + + g_repo = cl_git_sandbox_init("testrepo2"); + git_repository_head_tree(&tree, g_repo); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "nonexistent/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + cl_assert_equal_i(GIT_ITEROVER, git_iterator_current(&entry, i)); + git_iterator_free(i); + + git_tree_free(tree); + git_vector_free(&filelist); +} + diff --git a/tests/libgit2/iterator/workdir.c b/tests/libgit2/iterator/workdir.c new file mode 100644 index 000000000..86b847cab --- /dev/null +++ b/tests/libgit2/iterator/workdir.c @@ -0,0 +1,1523 @@ +#include "clar_libgit2.h" +#include "iterator.h" +#include "repository.h" +#include "futils.h" +#include "../submodule/submodule_helpers.h" +#include "../merge/merge_helpers.h" +#include "iterator_helpers.h" +#include + +static git_repository *g_repo; + +void test_iterator_workdir__initialize(void) +{ +} + +void test_iterator_workdir__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static void workdir_iterator_test( + const char *sandbox, + const char *start, + const char *end, + int expected_count, + int expected_ignores, + const char **expected_names, + const char *an_ignored_name) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + int error, count = 0, count_all = 0, count_all_post_reset = 0; + + g_repo = cl_git_sandbox_init(sandbox); + + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + i_opts.start = start; + i_opts.end = end; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + + error = git_iterator_current(&entry, i); + cl_assert((error == 0 && entry != NULL) || + (error == GIT_ITEROVER && entry == NULL)); + + while (entry != NULL) { + int ignored = git_iterator_current_is_ignored(i); + + if (S_ISDIR(entry->mode)) { + cl_git_pass(git_iterator_advance_into(&entry, i)); + continue; + } + + if (expected_names != NULL) + cl_assert_equal_s(expected_names[count_all], entry->path); + + if (an_ignored_name && strcmp(an_ignored_name,entry->path)==0) + cl_assert(ignored); + + if (!ignored) + count++; + count_all++; + + error = git_iterator_advance(&entry, i); + + cl_assert((error == 0 && entry != NULL) || + (error == GIT_ITEROVER && entry == NULL)); + } + + cl_assert_equal_i(expected_count, count); + cl_assert_equal_i(expected_count + expected_ignores, count_all); + + cl_git_pass(git_iterator_reset(i)); + + error = git_iterator_current(&entry, i); + cl_assert((error == 0 && entry != NULL) || + (error == GIT_ITEROVER && entry == NULL)); + + while (entry != NULL) { + if (S_ISDIR(entry->mode)) { + cl_git_pass(git_iterator_advance_into(&entry, i)); + continue; + } + + if (expected_names != NULL) + cl_assert_equal_s( + expected_names[count_all_post_reset], entry->path); + count_all_post_reset++; + + error = git_iterator_advance(&entry, i); + cl_assert(error == 0 || error == GIT_ITEROVER); + } + + cl_assert_equal_i(count_all, count_all_post_reset); + + git_iterator_free(i); +} + +void test_iterator_workdir__0(void) +{ + workdir_iterator_test("attr", NULL, NULL, 24, 5, NULL, "ign"); +} + +static const char *status_paths[] = { + "current_file", + "ignored_file", + "modified_file", + "new_file", + "staged_changes", + "staged_changes_modified_file", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/modified_file", + "subdir/new_file", + "\xe8\xbf\x99", + NULL +}; + +void test_iterator_workdir__1(void) +{ + workdir_iterator_test( + "status", NULL, NULL, 13, 1, status_paths, "ignored_file"); +} + +static const char *status_paths_range_0[] = { + "staged_changes", + "staged_changes_modified_file", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_modified_file", + NULL +}; + +void test_iterator_workdir__1_ranged_0(void) +{ + workdir_iterator_test( + "status", "staged", "staged", 5, 0, status_paths_range_0, NULL); +} + +static const char *status_paths_range_1[] = { + "modified_file", NULL +}; + +void test_iterator_workdir__1_ranged_1(void) +{ + workdir_iterator_test( + "status", "modified_file", "modified_file", + 1, 0, status_paths_range_1, NULL); +} + +static const char *status_paths_range_3[] = { + "subdir.txt", + "subdir/current_file", + "subdir/modified_file", + NULL +}; + +void test_iterator_workdir__1_ranged_3(void) +{ + workdir_iterator_test( + "status", "subdir", "subdir/modified_file", + 3, 0, status_paths_range_3, NULL); +} + +static const char *status_paths_range_4[] = { + "subdir/current_file", + "subdir/modified_file", + "subdir/new_file", + "\xe8\xbf\x99", + NULL +}; + +void test_iterator_workdir__1_ranged_4(void) +{ + workdir_iterator_test( + "status", "subdir/", NULL, 4, 0, status_paths_range_4, NULL); +} + +static const char *status_paths_range_5[] = { + "subdir/modified_file", + NULL +}; + +void test_iterator_workdir__1_ranged_5(void) +{ + workdir_iterator_test( + "status", "subdir/modified_file", "subdir/modified_file", + 1, 0, status_paths_range_5, NULL); +} + +void test_iterator_workdir__1_ranged_5_1_ranged_empty_0(void) +{ + workdir_iterator_test( + "status", "\xff_does_not_exist", NULL, + 0, 0, NULL, NULL); +} + +void test_iterator_workdir__1_ranged_empty_1(void) +{ + workdir_iterator_test( + "status", "empty", "empty", + 0, 0, NULL, NULL); +} + +void test_iterator_workdir__1_ranged_empty_2(void) +{ + workdir_iterator_test( + "status", NULL, "aaaa_empty_before", + 0, 0, NULL, NULL); +} + +void test_iterator_workdir__builtin_ignores(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + int idx; + static struct { + const char *path; + bool ignored; + } expected[] = { + { "dir/", true }, + { "file", false }, + { "ign", true }, + { "macro_bad", false }, + { "macro_test", false }, + { "root_test1", false }, + { "root_test2", false }, + { "root_test3", false }, + { "root_test4.txt", false }, + { "sub/", false }, + { "sub/.gitattributes", false }, + { "sub/abc", false }, + { "sub/dir/", true }, + { "sub/file", false }, + { "sub/ign/", true }, + { "sub/sub/", false }, + { "sub/sub/.gitattributes", false }, + { "sub/sub/dir", false }, /* file is not actually a dir */ + { "sub/sub/file", false }, + { NULL, false } + }; + + g_repo = cl_git_sandbox_init("attr"); + + cl_git_pass(p_mkdir("attr/sub/sub/.git", 0777)); + cl_git_mkfile("attr/sub/.git", "whatever"); + + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + i_opts.start = "dir"; + i_opts.end = "sub/sub/file"; + + cl_git_pass(git_iterator_for_workdir( + &i, g_repo, NULL, NULL, &i_opts)); + cl_git_pass(git_iterator_current(&entry, i)); + + for (idx = 0; entry != NULL; ++idx) { + int ignored = git_iterator_current_is_ignored(i); + + cl_assert_equal_s(expected[idx].path, entry->path); + cl_assert_(ignored == expected[idx].ignored, expected[idx].path); + + if (!ignored && + (entry->mode == GIT_FILEMODE_TREE || + entry->mode == GIT_FILEMODE_COMMIT)) + { + /* it is possible to advance "into" a submodule */ + cl_git_pass(git_iterator_advance_into(&entry, i)); + } else { + int error = git_iterator_advance(&entry, i); + cl_assert(!error || error == GIT_ITEROVER); + } + } + + cl_assert(expected[idx].path == NULL); + + git_iterator_free(i); +} + +static void check_wd_first_through_third_range( + git_repository *repo, const char *start, const char *end) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + int error, idx; + static const char *expected[] = { "FIRST", "second", "THIRD", NULL }; + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + i_opts.start = start; + i_opts.end = end; + + cl_git_pass(git_iterator_for_workdir( + &i, repo, NULL, NULL, &i_opts)); + cl_git_pass(git_iterator_current(&entry, i)); + + for (idx = 0; entry != NULL; ++idx) { + cl_assert_equal_s(expected[idx], entry->path); + + error = git_iterator_advance(&entry, i); + cl_assert(!error || error == GIT_ITEROVER); + } + + cl_assert(expected[idx] == NULL); + + git_iterator_free(i); +} + +void test_iterator_workdir__handles_icase_range(void) +{ + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_remove_placeholders(git_repository_path(g_repo), "dummy-marker.txt"); + + cl_git_mkfile("empty_standard_repo/before", "whatever\n"); + cl_git_mkfile("empty_standard_repo/FIRST", "whatever\n"); + cl_git_mkfile("empty_standard_repo/second", "whatever\n"); + cl_git_mkfile("empty_standard_repo/THIRD", "whatever\n"); + cl_git_mkfile("empty_standard_repo/zafter", "whatever\n"); + cl_git_mkfile("empty_standard_repo/Zlast", "whatever\n"); + + check_wd_first_through_third_range(g_repo, "first", "third"); + check_wd_first_through_third_range(g_repo, "FIRST", "THIRD"); + check_wd_first_through_third_range(g_repo, "first", "THIRD"); + check_wd_first_through_third_range(g_repo, "FIRST", "third"); + check_wd_first_through_third_range(g_repo, "FirSt", "tHiRd"); +} + +/* + * The workdir iterator is like the filesystem iterator, but honors + * special git type constructs (ignores, submodules, etc). + */ + +void test_iterator_workdir__icase(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("icase"); + + /* auto expand with no tree entries */ + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 20, NULL, 20, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 22, NULL, 22, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 12, NULL, 22, NULL); + git_iterator_free(i); +} + +void test_iterator_workdir__icase_starts_and_ends(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("icase"); + + /* auto expand with no tree entries */ + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 7, NULL, 7, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 3, NULL, 3, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 8, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 4, NULL, 4, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 5, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 1, NULL, 4, NULL); + git_iterator_free(i); + + /* auto expand with no tree entries */ + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 13, NULL, 13, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 5, NULL, 5, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 14, NULL, 14, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 6, NULL, 6, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 9, NULL, 14, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 1, NULL, 6, NULL); + git_iterator_free(i); +} + +static void build_workdir_tree(const char *root, int dirs, int subs) +{ + int i, j; + char buf[64], sub[80]; + + for (i = 0; i < dirs; ++i) { + if (i % 2 == 0) { + p_snprintf(buf, sizeof(buf), "%s/dir%02d", root, i); + cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH)); + + p_snprintf(buf, sizeof(buf), "%s/dir%02d/file", root, i); + cl_git_mkfile(buf, buf); + buf[strlen(buf) - 5] = '\0'; + } else { + p_snprintf(buf, sizeof(buf), "%s/DIR%02d", root, i); + cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH)); + } + + for (j = 0; j < subs; ++j) { + switch (j % 4) { + case 0: p_snprintf(sub, sizeof(sub), "%s/sub%02d", buf, j); break; + case 1: p_snprintf(sub, sizeof(sub), "%s/sUB%02d", buf, j); break; + case 2: p_snprintf(sub, sizeof(sub), "%s/Sub%02d", buf, j); break; + case 3: p_snprintf(sub, sizeof(sub), "%s/SUB%02d", buf, j); break; + } + cl_git_pass(git_futils_mkdir(sub, 0775, GIT_MKDIR_PATH)); + + if (j % 2 == 0) { + size_t sublen = strlen(sub); + memcpy(&sub[sublen], "/file", sizeof("/file")); + cl_git_mkfile(sub, sub); + sub[sublen] = '\0'; + } + } + } +} + +void test_iterator_workdir__depth(void) +{ + git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("icase"); + + build_workdir_tree("icase", 10, 10); + build_workdir_tree("icase/DIR01/sUB01", 50, 0); + build_workdir_tree("icase/dir02/sUB01", 50, 0); + + /* auto expand with no tree entries */ + cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); + expect_iterator_items(iter, 125, NULL, 125, NULL); + git_iterator_free(iter); + + /* auto expand with tree entries (empty dirs silently skipped) */ + iter_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); + expect_iterator_items(iter, 337, NULL, 337, NULL); + git_iterator_free(iter); +} + +/* The filesystem iterator is a workdir iterator without any special + * workdir handling capabilities (ignores, submodules, etc). + */ +void test_iterator_workdir__filesystem(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + static const char *expect_base[] = { + "DIR01/Sub02/file", + "DIR01/sub00/file", + "current_file", + "dir00/Sub02/file", + "dir00/file", + "dir00/sub00/file", + "modified_file", + "new_file", + NULL, + }; + static const char *expect_trees[] = { + "DIR01/", + "DIR01/SUB03/", + "DIR01/Sub02/", + "DIR01/Sub02/file", + "DIR01/sUB01/", + "DIR01/sub00/", + "DIR01/sub00/file", + "current_file", + "dir00/", + "dir00/SUB03/", + "dir00/Sub02/", + "dir00/Sub02/file", + "dir00/file", + "dir00/sUB01/", + "dir00/sub00/", + "dir00/sub00/file", + "modified_file", + "new_file", + NULL, + }; + static const char *expect_noauto[] = { + "DIR01/", + "current_file", + "dir00/", + "modified_file", + "new_file", + NULL, + }; + + g_repo = cl_git_sandbox_init("status"); + + build_workdir_tree("status/subdir", 2, 4); + + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", NULL)); + expect_iterator_items(i, 8, expect_base, 8, expect_base); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); + expect_iterator_items(i, 18, expect_trees, 18, expect_trees); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); + expect_iterator_items(i, 5, expect_noauto, 18, expect_trees); + git_iterator_free(i); + + git__tsort((void **)expect_base, 8, (git__tsort_cmp)git__strcasecmp); + git__tsort((void **)expect_trees, 18, (git__tsort_cmp)git__strcasecmp); + git__tsort((void **)expect_noauto, 5, (git__tsort_cmp)git__strcasecmp); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); + expect_iterator_items(i, 8, expect_base, 8, expect_base); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); + expect_iterator_items(i, 18, expect_trees, 18, expect_trees); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); + expect_iterator_items(i, 5, expect_noauto, 18, expect_trees); + git_iterator_free(i); +} + +void test_iterator_workdir__filesystem2(void) +{ + git_iterator *i; + static const char *expect_base[] = { + "heads/br2", + "heads/dir", + "heads/executable", + "heads/ident", + "heads/long-file-name", + "heads/master", + "heads/merge-conflict", + "heads/packed-test", + "heads/subtrees", + "heads/test", + "heads/testrepo-worktree", + "symref", + "tags/e90810b", + "tags/foo/bar", + "tags/foo/foo/bar", + "tags/point_to_blob", + "tags/test", + NULL, + }; + + g_repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass(git_iterator_for_filesystem( + &i, "testrepo/.git/refs", NULL)); + expect_iterator_items(i, 17, expect_base, 17, expect_base); + git_iterator_free(i); +} + +/* + * Lots of empty dirs, or nearly empty ones, make the old workdir + * iterator cry. Also, segfault. + */ +void test_iterator_workdir__filesystem_gunk(void) +{ + git_str parent = GIT_STR_INIT; + git_iterator *i; + int n; + + if (!cl_is_env_set("GITTEST_INVASIVE_SPEED")) + cl_skip(); + + g_repo = cl_git_sandbox_init("testrepo"); + + for (n = 0; n < 100000; n++) { + git_str_clear(&parent); + cl_git_pass(git_str_printf(&parent, "%s/refs/heads/foo/%d/subdir", git_repository_path(g_repo), n)); + cl_git_pass(git_futils_mkdir(parent.ptr, 0775, GIT_MKDIR_PATH)); + } + + cl_git_pass(git_iterator_for_filesystem(&i, "testrepo/.git/refs", NULL)); + + /* + * Should only have 17 items, since we're not asking for trees to be + * returned. the goal of this test is simply to not crash. + */ + expect_iterator_items(i, 17, NULL, 16, NULL); + + git_iterator_free(i); + git_str_dispose(&parent); +} + +void test_iterator_workdir__skips_unreadable_dirs(void) +{ + git_iterator *i; + const git_index_entry *e; + + if (!cl_is_chmod_supported()) + return; + +#ifndef GIT_WIN32 + if (geteuid() == 0) + cl_skip(); +#endif + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_must_pass(p_mkdir("empty_standard_repo/r", 0777)); + cl_git_mkfile("empty_standard_repo/r/a", "hello"); + cl_must_pass(p_mkdir("empty_standard_repo/r/b", 0777)); + cl_git_mkfile("empty_standard_repo/r/b/problem", "not me"); + cl_must_pass(p_chmod("empty_standard_repo/r/b", 0000)); + cl_must_pass(p_mkdir("empty_standard_repo/r/c", 0777)); + cl_git_mkfile("empty_standard_repo/r/c/foo", "aloha"); + cl_git_mkfile("empty_standard_repo/r/d", "final"); + + cl_git_pass(git_iterator_for_filesystem( + &i, "empty_standard_repo/r", NULL)); + + cl_git_pass(git_iterator_advance(&e, i)); /* a */ + cl_assert_equal_s("a", e->path); + + cl_git_pass(git_iterator_advance(&e, i)); /* c/foo */ + cl_assert_equal_s("c/foo", e->path); + + cl_git_pass(git_iterator_advance(&e, i)); /* d */ + cl_assert_equal_s("d", e->path); + + cl_must_pass(p_chmod("empty_standard_repo/r/b", 0777)); + + cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i)); + git_iterator_free(i); +} + +void test_iterator_workdir__skips_fifos_and_special_files(void) +{ +#ifndef GIT_WIN32 + git_iterator *i; + const git_index_entry *e; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_must_pass(p_mkdir("empty_standard_repo/dir", 0777)); + cl_git_mkfile("empty_standard_repo/file", "not me"); + + cl_assert(!mkfifo("empty_standard_repo/fifo", 0777)); + cl_assert(!access("empty_standard_repo/fifo", F_OK)); + + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES | + GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_git_pass(git_iterator_for_filesystem( + &i, "empty_standard_repo", &i_opts)); + + cl_git_pass(git_iterator_advance(&e, i)); /* .git */ + cl_assert(S_ISDIR(e->mode)); + cl_git_pass(git_iterator_advance(&e, i)); /* dir */ + cl_assert(S_ISDIR(e->mode)); + /* skips fifo */ + cl_git_pass(git_iterator_advance(&e, i)); /* file */ + cl_assert(S_ISREG(e->mode)); + + cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i)); + + git_iterator_free(i); +#else + cl_skip(); +#endif +} + +void test_iterator_workdir__pathlist(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 100, NULL)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + + /* Test iterators without returning tree entries (but autoexpanding.) */ + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + /* Case sensitive */ + { + const char *expected[] = { + "B", "D", "L/1", "a", "c", "e", "k/1", "k/a" }; + size_t expected_len = 8; + + i_opts.start = NULL; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Case INsensitive */ + { + const char *expected[] = { + "a", "B", "c", "D", "e", "k/1", "k/a", "L/1" }; + size_t expected_len = 8; + + i_opts.start = NULL; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set a start, but no end. Case sensitive. */ + { + const char *expected[] = { "c", "e", "k/1", "k/a" }; + size_t expected_len = 4; + + i_opts.start = "c"; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set a start, but no end. Case INsensitive. */ + { + const char *expected[] = { "c", "D", "e", "k/1", "k/a", "L/1" }; + size_t expected_len = 6; + + i_opts.start = "c"; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set no start, but an end. Case sensitive. */ + { + const char *expected[] = { "B", "D", "L/1", "a", "c", "e" }; + size_t expected_len = 6; + + i_opts.start = NULL; + i_opts.end = "e"; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set no start, but an end. Case INsensitive. */ + { + const char *expected[] = { "a", "B", "c", "D", "e" }; + size_t expected_len = 5; + + i_opts.start = NULL; + i_opts.end = "e"; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case sensitive */ + { + const char *expected[] = { "c", "e", "k/1" }; + size_t expected_len = 3; + + i_opts.start = "c"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case sensitive */ + { + const char *expected[] = { "k/1" }; + size_t expected_len = 1; + + i_opts.start = "k"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case INsensitive */ + { + const char *expected[] = { "c", "D", "e", "k/1", "k/a" }; + size_t expected_len = 5; + + i_opts.start = "c"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case INsensitive */ + { + const char *expected[] = { "k/1", "k/a" }; + size_t expected_len = 2; + + i_opts.start = "k"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + git_vector_free(&filelist); +} + +void test_iterator_workdir__pathlist_with_dirs(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 5, NULL)); + + g_repo = cl_git_sandbox_init("icase"); + + /* Test that a prefix `k` matches folders, even without trailing slash */ + { + const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "k")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that a `k/` matches a folder */ + { + const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "k/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* When the iterator is case sensitive, ensure we can't lookup the + * directory with the wrong case. + */ + { + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "K/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + } + + /* Test that case insensitive matching works. */ + { + const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "K/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that case insensitive matching works without trailing slash. */ + { + const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "K")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + git_vector_free(&filelist); +} + +static void create_paths(const char *root, int depth) +{ + git_str fullpath = GIT_STR_INIT; + size_t root_len; + int i; + + cl_git_pass(git_str_puts(&fullpath, root)); + cl_git_pass(git_fs_path_to_dir(&fullpath)); + + root_len = fullpath.size; + + for (i = 0; i < 8; i++) { + bool file = (depth == 0 || (i % 2) == 0); + git_str_truncate(&fullpath, root_len); + cl_git_pass(git_str_printf(&fullpath, "item%d", i)); + + if (file) { + cl_git_rewritefile(fullpath.ptr, "This is a file!\n"); + } else { + cl_must_pass(p_mkdir(fullpath.ptr, 0777)); + + if (depth > 0) + create_paths(fullpath.ptr, (depth - 1)); + } + } + + git_str_dispose(&fullpath); +} + +void test_iterator_workdir__pathlist_for_deeply_nested_item(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 5, NULL)); + + g_repo = cl_git_sandbox_init("icase"); + create_paths(git_repository_workdir(g_repo), 3); + + /* Ensure that we find the single path we're interested in, and we find + * it efficiently, and don't stat the entire world to get there. + */ + { + const char *expected[] = { "item1/item3/item5/item7" }; + size_t expected_len = 1; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/item7")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + cl_assert_equal_i(4, i->stat_calls); + git_iterator_free(i); + } + + /* Ensure that we find the single path we're interested in, and we find + * it efficiently, and don't stat the entire world to get there. + */ + { + const char *expected[] = { + "item1/item3/item5/item0", "item1/item3/item5/item1", + "item1/item3/item5/item2", "item1/item3/item5/item3", + "item1/item3/item5/item4", "item1/item3/item5/item5", + "item1/item3/item5/item6", "item1/item3/item5/item7", + }; + size_t expected_len = 8; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + cl_assert_equal_i(11, i->stat_calls); + git_iterator_free(i); + } + + /* Ensure that we find the single path we're interested in, and we find + * it efficiently, and don't stat the entire world to get there. + */ + { + const char *expected[] = { + "item1/item3/item0", + "item1/item3/item1/item0", "item1/item3/item1/item1", + "item1/item3/item1/item2", "item1/item3/item1/item3", + "item1/item3/item1/item4", "item1/item3/item1/item5", + "item1/item3/item1/item6", "item1/item3/item1/item7", + "item1/item3/item2", + "item1/item3/item3/item0", "item1/item3/item3/item1", + "item1/item3/item3/item2", "item1/item3/item3/item3", + "item1/item3/item3/item4", "item1/item3/item3/item5", + "item1/item3/item3/item6", "item1/item3/item3/item7", + "item1/item3/item4", + "item1/item3/item5/item0", "item1/item3/item5/item1", + "item1/item3/item5/item2", "item1/item3/item5/item3", + "item1/item3/item5/item4", "item1/item3/item5/item5", + "item1/item3/item5/item6", "item1/item3/item5/item7", + "item1/item3/item6", + "item1/item3/item7/item0", "item1/item3/item7/item1", + "item1/item3/item7/item2", "item1/item3/item7/item3", + "item1/item3/item7/item4", "item1/item3/item7/item5", + "item1/item3/item7/item6", "item1/item3/item7/item7", + }; + size_t expected_len = 36; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item1/item3/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + cl_assert_equal_i(42, i->stat_calls); + git_iterator_free(i); + } + + /* Ensure that we find the single path we're interested in, and we find + * it efficiently, and don't stat the entire world to get there. + */ + { + const char *expected[] = { + "item0", "item1/item2", "item5/item7/item4", "item6", + "item7/item3/item1/item6" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item7/item3/item1/item6")); + cl_git_pass(git_vector_insert(&filelist, "item6")); + cl_git_pass(git_vector_insert(&filelist, "item5/item7/item4")); + cl_git_pass(git_vector_insert(&filelist, "item1/item2")); + cl_git_pass(git_vector_insert(&filelist, "item0")); + + /* also add some things that don't exist or don't match the right type */ + cl_git_pass(git_vector_insert(&filelist, "item2/")); + cl_git_pass(git_vector_insert(&filelist, "itemN")); + cl_git_pass(git_vector_insert(&filelist, "item1/itemA")); + cl_git_pass(git_vector_insert(&filelist, "item5/item3/item4/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + cl_assert_equal_i(14, i->stat_calls); + git_iterator_free(i); + } + + git_vector_free(&filelist); +} + +void test_iterator_workdir__bounded_submodules(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_index *index; + git_tree *head; + + cl_git_pass(git_vector_init(&filelist, 5, NULL)); + + g_repo = setup_fixture_submod2(); + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_repository_head_tree(&head, g_repo)); + + /* Test that a submodule matches */ + { + const char *expected[] = { "sm_changed_head" }; + size_t expected_len = 1; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "sm_changed_head")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that a submodule still matches when suffixed with a '/' */ + { + const char *expected[] = { "sm_changed_head" }; + size_t expected_len = 1; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "sm_changed_head/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that start/end work with a submodule */ + { + const char *expected[] = { "sm_changed_head", "sm_changed_index" }; + size_t expected_len = 2; + + i_opts.start = "sm_changed_head"; + i_opts.end = "sm_changed_index"; + i_opts.pathlist.strings = NULL; + i_opts.pathlist.count = 0; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that start and end allow '/' suffixes of submodules */ + { + const char *expected[] = { "sm_changed_head", "sm_changed_index" }; + size_t expected_len = 2; + + i_opts.start = "sm_changed_head"; + i_opts.end = "sm_changed_index"; + i_opts.pathlist.strings = NULL; + i_opts.pathlist.count = 0; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + git_vector_free(&filelist); + git_index_free(index); + git_tree_free(head); +} + +void test_iterator_workdir__advance_over(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + g_repo = cl_git_sandbox_init("icase"); + + /* create an empty directory */ + cl_must_pass(p_mkdir("icase/empty", 0777)); + + /* create a directory in which all contents are ignored */ + cl_must_pass(p_mkdir("icase/all_ignored", 0777)); + cl_git_rewritefile("icase/all_ignored/one", "This is ignored\n"); + cl_git_rewritefile("icase/all_ignored/two", "This, too, is ignored\n"); + cl_git_rewritefile("icase/all_ignored/.gitignore", ".gitignore\none\ntwo\n"); + + /* create a directory in which not all contents are ignored */ + cl_must_pass(p_mkdir("icase/some_ignored", 0777)); + cl_git_rewritefile("icase/some_ignored/one", "This is ignored\n"); + cl_git_rewritefile("icase/some_ignored/two", "This is not ignored\n"); + cl_git_rewritefile("icase/some_ignored/.gitignore", ".gitignore\none\n"); + + /* create a directory which has some empty children */ + cl_must_pass(p_mkdir("icase/empty_children", 0777)); + cl_must_pass(p_mkdir("icase/empty_children/empty1", 0777)); + cl_must_pass(p_mkdir("icase/empty_children/empty2", 0777)); + cl_must_pass(p_mkdir("icase/empty_children/empty3", 0777)); + + /* create a directory which will disappear! */ + cl_must_pass(p_mkdir("icase/missing_directory", 0777)); + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + + cl_must_pass(p_rmdir("icase/missing_directory")); + + expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "all_ignored/", GIT_ITERATOR_STATUS_IGNORED); + expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "empty/", GIT_ITERATOR_STATUS_EMPTY); + expect_advance_over(i, "empty_children/", GIT_ITERATOR_STATUS_EMPTY); + expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "missing_directory/", GIT_ITERATOR_STATUS_EMPTY); + expect_advance_over(i, "some_ignored/", GIT_ITERATOR_STATUS_NORMAL); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); +} + +void test_iterator_workdir__advance_over_with_pathlist(void) +{ + git_vector pathlist = GIT_VECTOR_INIT; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + git_vector_insert(&pathlist, "dirA/subdir1/subdir2/file"); + git_vector_insert(&pathlist, "dirB/subdir1/subdir2"); + git_vector_insert(&pathlist, "dirC/subdir1/nonexistent"); + git_vector_insert(&pathlist, "dirD/subdir1/nonexistent"); + git_vector_insert(&pathlist, "dirD/subdir1/subdir2"); + git_vector_insert(&pathlist, "dirD/nonexistent"); + + i_opts.pathlist.strings = (char **)pathlist.contents; + i_opts.pathlist.count = pathlist.length; + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + g_repo = cl_git_sandbox_init("icase"); + + /* Create a directory that has a file that is included in our pathlist */ + cl_must_pass(p_mkdir("icase/dirA", 0777)); + cl_must_pass(p_mkdir("icase/dirA/subdir1", 0777)); + cl_must_pass(p_mkdir("icase/dirA/subdir1/subdir2", 0777)); + cl_git_rewritefile("icase/dirA/subdir1/subdir2/file", "foo!"); + + /* Create a directory that has a directory that is included in our pathlist */ + cl_must_pass(p_mkdir("icase/dirB", 0777)); + cl_must_pass(p_mkdir("icase/dirB/subdir1", 0777)); + cl_must_pass(p_mkdir("icase/dirB/subdir1/subdir2", 0777)); + cl_git_rewritefile("icase/dirB/subdir1/subdir2/file", "foo!"); + + /* Create a directory that would contain an entry in our pathlist, but + * that entry does not actually exist. We don't know this until we + * advance_over it. We want to distinguish this from an actually empty + * or ignored directory. + */ + cl_must_pass(p_mkdir("icase/dirC", 0777)); + cl_must_pass(p_mkdir("icase/dirC/subdir1", 0777)); + cl_must_pass(p_mkdir("icase/dirC/subdir1/subdir2", 0777)); + cl_git_rewritefile("icase/dirC/subdir1/subdir2/file", "foo!"); + + /* Create a directory that has a mix of actual and nonexistent paths */ + cl_must_pass(p_mkdir("icase/dirD", 0777)); + cl_must_pass(p_mkdir("icase/dirD/subdir1", 0777)); + cl_must_pass(p_mkdir("icase/dirD/subdir1/subdir2", 0777)); + cl_git_rewritefile("icase/dirD/subdir1/subdir2/file", "foo!"); + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + + expect_advance_over(i, "dirA/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "dirB/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "dirC/", GIT_ITERATOR_STATUS_FILTERED); + expect_advance_over(i, "dirD/", GIT_ITERATOR_STATUS_NORMAL); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + git_vector_free(&pathlist); +} + +void test_iterator_workdir__advance_into(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("icase"); + + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_must_pass(p_mkdir("icase/Empty", 0777)); + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_advance_into(i, "B"); + expect_advance_into(i, "D"); + expect_advance_into(i, "Empty/"); + expect_advance_into(i, "F"); + expect_advance_into(i, "H"); + expect_advance_into(i, "J"); + expect_advance_into(i, "L/"); + expect_advance_into(i, "L/1"); + expect_advance_into(i, "L/B"); + expect_advance_into(i, "L/D"); + expect_advance_into(i, "L/a"); + expect_advance_into(i, "L/c"); + expect_advance_into(i, "a"); + expect_advance_into(i, "c"); + expect_advance_into(i, "e"); + expect_advance_into(i, "g"); + expect_advance_into(i, "i"); + expect_advance_into(i, "k/"); + expect_advance_into(i, "k/1"); + expect_advance_into(i, "k/B"); + expect_advance_into(i, "k/D"); + expect_advance_into(i, "k/a"); + expect_advance_into(i, "k/c"); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); +} + +void test_iterator_workdir__pathlist_with_directory(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + + const char *expected[] = { "subdir/README", "subdir/new.txt", + "subdir/subdir2/README", "subdir/subdir2/new.txt" }; + size_t expected_len = 4; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "subdir/")); + + g_repo = cl_git_sandbox_init("testrepo2"); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + + git_vector_free(&filelist); +} + +void test_iterator_workdir__pathlist_with_directory_include_trees(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + + const char *expected[] = { "subdir/", "subdir/README", "subdir/new.txt", + "subdir/subdir2/", "subdir/subdir2/README", "subdir/subdir2/new.txt", }; + size_t expected_len = 6; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "subdir/")); + + g_repo = cl_git_sandbox_init("testrepo2"); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + + git_vector_free(&filelist); +} + +void test_iterator_workdir__hash_when_requested(void) +{ + git_iterator *iter; + const git_index_entry *entry; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + git_oid expected_id = {{0}}; + size_t i; + + struct merge_index_entry expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "7c7e08f9559d9e1551b91e1cf68f1d0066109add", 0, "oyster.txt" }, + { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "veal.txt" }, + }; + + g_repo = cl_git_sandbox_init("merge-recursive"); + + /* do the iteration normally, ensure there are no hashes */ + cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); + + for (i = 0; i < sizeof(expected) / sizeof(struct merge_index_entry); i++) { + cl_git_pass(git_iterator_advance(&entry, iter)); + + cl_assert_equal_oid(&expected_id, &entry->id); + cl_assert_equal_s(expected[i].path, entry->path); + } + cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&entry, iter)); + git_iterator_free(iter); + + /* do the iteration requesting hashes */ + iter_opts.flags |= GIT_ITERATOR_INCLUDE_HASH; + cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); + + for (i = 0; i < sizeof(expected) / sizeof(struct merge_index_entry); i++) { + cl_git_pass(git_iterator_advance(&entry, iter)); + + cl_git_pass(git_oid_fromstr(&expected_id, expected[i].oid_str)); + cl_assert_equal_oid(&expected_id, &entry->id); + cl_assert_equal_s(expected[i].path, entry->path); + } + cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&entry, iter)); + git_iterator_free(iter); +} diff --git a/tests/libgit2/mailmap/basic.c b/tests/libgit2/mailmap/basic.c new file mode 100644 index 000000000..1f8ca56c2 --- /dev/null +++ b/tests/libgit2/mailmap/basic.c @@ -0,0 +1,101 @@ +#include "clar.h" +#include "clar_libgit2.h" + +#include "common.h" +#include "mailmap.h" + +static git_mailmap *mailmap = NULL; + +const char TEST_MAILMAP[] = + "Foo bar \n" + "Blatantly invalid line\n" + "Foo bar \n" + " \n" + " Other Name \n"; + +struct { + const char *real_name; + const char *real_email; + const char *replace_name; + const char *replace_email; +} expected[] = { + { "Foo bar", "foo@bar.com", NULL, "foo@baz.com" }, + { "Foo bar", "foo@bar.com", NULL, "foo@bal.com" }, + { NULL, "email@foo.com", NULL, "otheremail@foo.com" }, + { NULL, "email@foo.com", "Other Name", "yetanotheremail@foo.com" } +}; + +void test_mailmap_basic__initialize(void) +{ + cl_git_pass(git_mailmap_from_buffer( + &mailmap, TEST_MAILMAP, strlen(TEST_MAILMAP))); +} + +void test_mailmap_basic__cleanup(void) +{ + git_mailmap_free(mailmap); + mailmap = NULL; +} + +void test_mailmap_basic__entry(void) +{ + size_t idx; + const git_mailmap_entry *entry; + + /* Check that we have the expected # of entries */ + cl_assert_equal_sz(ARRAY_SIZE(expected), git_vector_length(&mailmap->entries)); + + for (idx = 0; idx < ARRAY_SIZE(expected); ++idx) { + /* Try to look up each entry and make sure they match */ + entry = git_mailmap_entry_lookup( + mailmap, expected[idx].replace_name, expected[idx].replace_email); + + cl_assert(entry); + cl_assert_equal_s(entry->real_name, expected[idx].real_name); + cl_assert_equal_s(entry->real_email, expected[idx].real_email); + cl_assert_equal_s(entry->replace_name, expected[idx].replace_name); + cl_assert_equal_s(entry->replace_email, expected[idx].replace_email); + } +} + +void test_mailmap_basic__lookup_not_found(void) +{ + const git_mailmap_entry *entry = git_mailmap_entry_lookup( + mailmap, "Whoever", "doesnotexist@fo.com"); + cl_assert(!entry); +} + +void test_mailmap_basic__lookup(void) +{ + const git_mailmap_entry *entry = git_mailmap_entry_lookup( + mailmap, "Typoed the name once", "foo@baz.com"); + cl_assert(entry); + cl_assert_equal_s(entry->real_name, "Foo bar"); +} + +void test_mailmap_basic__empty_email_query(void) +{ + const char *name; + const char *email; + cl_git_pass(git_mailmap_resolve( + &name, &email, mailmap, "Author name", "otheremail@foo.com")); + cl_assert_equal_s(name, "Author name"); + cl_assert_equal_s(email, "email@foo.com"); +} + +void test_mailmap_basic__name_matching(void) +{ + const char *name; + const char *email; + cl_git_pass(git_mailmap_resolve( + &name, &email, mailmap, "Other Name", "yetanotheremail@foo.com")); + + cl_assert_equal_s(name, "Other Name"); + cl_assert_equal_s(email, "email@foo.com"); + + cl_git_pass(git_mailmap_resolve( + &name, &email, mailmap, + "Other Name That Doesn't Match", "yetanotheremail@foo.com")); + cl_assert_equal_s(name, "Other Name That Doesn't Match"); + cl_assert_equal_s(email, "yetanotheremail@foo.com"); +} diff --git a/tests/libgit2/mailmap/blame.c b/tests/libgit2/mailmap/blame.c new file mode 100644 index 000000000..e6bc1a41f --- /dev/null +++ b/tests/libgit2/mailmap/blame.c @@ -0,0 +1,64 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/blame.h" +#include "mailmap.h" +#include "mailmap_testdata.h" + +static git_repository *g_repo; +static git_blame *g_blame; + +void test_mailmap_blame__initialize(void) +{ + g_repo = NULL; + g_blame = NULL; +} + +void test_mailmap_blame__cleanup(void) +{ + git_blame_free(g_blame); + cl_git_sandbox_cleanup(); +} + +void test_mailmap_blame__hunks(void) +{ + size_t idx = 0; + const git_blame_hunk *hunk = NULL; + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("mailmap"); + + opts.flags |= GIT_BLAME_USE_MAILMAP; + + cl_git_pass(git_blame_file(&g_blame, g_repo, "file.txt", &opts)); + cl_assert(g_blame); + + for (idx = 0; idx < ARRAY_SIZE(resolved); ++idx) { + hunk = git_blame_get_hunk_byline(g_blame, idx + 1); + + cl_assert(hunk->final_signature != NULL); + cl_assert(hunk->orig_signature != NULL); + cl_assert_equal_s(hunk->final_signature->name, resolved[idx].real_name); + cl_assert_equal_s(hunk->final_signature->email, resolved[idx].real_email); + } +} + +void test_mailmap_blame__hunks_no_mailmap(void) +{ + size_t idx = 0; + const git_blame_hunk *hunk = NULL; + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("mailmap"); + + cl_git_pass(git_blame_file(&g_blame, g_repo, "file.txt", &opts)); + cl_assert(g_blame); + + for (idx = 0; idx < ARRAY_SIZE(resolved); ++idx) { + hunk = git_blame_get_hunk_byline(g_blame, idx + 1); + + cl_assert(hunk->final_signature != NULL); + cl_assert(hunk->orig_signature != NULL); + cl_assert_equal_s(hunk->final_signature->name, resolved[idx].replace_name); + cl_assert_equal_s(hunk->final_signature->email, resolved[idx].replace_email); + } +} diff --git a/tests/libgit2/mailmap/mailmap_testdata.h b/tests/libgit2/mailmap/mailmap_testdata.h new file mode 100644 index 000000000..a06606b4b --- /dev/null +++ b/tests/libgit2/mailmap/mailmap_testdata.h @@ -0,0 +1,21 @@ +#include "mailmap.h" + +typedef struct mailmap_entry { + const char *real_name; + const char *real_email; + const char *replace_name; + const char *replace_email; +} mailmap_entry; + +static const mailmap_entry resolved[] = { + { "Brad", "cto@company.xx", "Brad", "cto@coompany.xx" }, + { "Brad L", "cto@company.xx", "Brad L", "cto@coompany.xx" }, + { "Some Dude", "some@dude.xx", "nick1", "bugs@company.xx" }, + { "Other Author", "other@author.xx", "nick2", "bugs@company.xx" }, + { "nick3", "bugs@company.xx", "nick3", "bugs@company.xx" }, + { "Other Author", "other@author.xx", "Some Garbage", "nick2@company.xx" }, + { "Phil Hill", "phil@company.xx", "unknown", "phil@company.xx" }, + { "Joseph", "joseph@company.xx", "Joseph", "bugs@company.xx" }, + { "Santa Claus", "santa.claus@northpole.xx", "Clause", "me@company.xx" }, + { "Charles", "charles@charles.xx", "Charles", "charles@charles.xx" } +}; diff --git a/tests/libgit2/mailmap/parsing.c b/tests/libgit2/mailmap/parsing.c new file mode 100644 index 000000000..5ea470f34 --- /dev/null +++ b/tests/libgit2/mailmap/parsing.c @@ -0,0 +1,269 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "git2/sys/repository.h" +#include "mailmap_testdata.h" + +static git_repository *g_repo; +static git_mailmap *g_mailmap; +static git_config *g_config; + +static const char string_mailmap[] = + "# Simple Comment line\n" + " \n" + "Some Dude nick1 \n" + "Other Author nick2 \n" + "Other Author \n" + "Phil Hill # Comment at end of line\n" + " Joseph \n" + "Santa Claus \n" + "Untracked "; + +static const mailmap_entry entries[] = { + { NULL, "cto@company.xx", NULL, "cto@coompany.xx" }, + { "Some Dude", "some@dude.xx", "nick1", "bugs@company.xx" }, + { "Other Author", "other@author.xx", "nick2", "bugs@company.xx" }, + { "Other Author", "other@author.xx", NULL, "nick2@company.xx" }, + { "Phil Hill", NULL, NULL, "phil@company.xx" }, + { NULL, "joseph@company.xx", "Joseph", "bugs@company.xx" }, + { "Santa Claus", "santa.claus@northpole.xx", NULL, "me@company.xx" }, + /* This entry isn't in the bare repository */ + { "Untracked", NULL, NULL, "untracked@company.xx" } +}; + +void test_mailmap_parsing__initialize(void) +{ + g_repo = NULL; + g_mailmap = NULL; + g_config = NULL; +} + +void test_mailmap_parsing__cleanup(void) +{ + git_mailmap_free(g_mailmap); + git_config_free(g_config); + cl_git_sandbox_cleanup(); +} + +static void check_mailmap_entries( + const git_mailmap *mailmap, const mailmap_entry *entries, size_t entries_size) +{ + const git_mailmap_entry *parsed; + size_t idx; + + /* Check the correct # of entries were parsed */ + cl_assert_equal_sz(entries_size, git_vector_length(&mailmap->entries)); + + /* Make sure looking up each entry succeeds */ + for (idx = 0; idx < entries_size; ++idx) { + parsed = git_mailmap_entry_lookup( + mailmap, entries[idx].replace_name, entries[idx].replace_email); + + cl_assert(parsed); + cl_assert_equal_s(parsed->real_name, entries[idx].real_name); + cl_assert_equal_s(parsed->real_email, entries[idx].real_email); + cl_assert_equal_s(parsed->replace_name, entries[idx].replace_name); + cl_assert_equal_s(parsed->replace_email, entries[idx].replace_email); + } +} + +static void check_mailmap_resolve( + const git_mailmap *mailmap, const mailmap_entry *resolved, size_t resolved_size) +{ + const char *resolved_name = NULL; + const char *resolved_email = NULL; + size_t idx; + + /* Check that the resolver behaves correctly */ + for (idx = 0; idx < resolved_size; ++idx) { + cl_git_pass(git_mailmap_resolve( + &resolved_name, &resolved_email, mailmap, + resolved[idx].replace_name, resolved[idx].replace_email)); + cl_assert_equal_s(resolved_name, resolved[idx].real_name); + cl_assert_equal_s(resolved_email, resolved[idx].real_email); + } +} + +static const mailmap_entry resolved_untracked[] = { + { "Untracked", "untracked@company.xx", "xx", "untracked@company.xx" } +}; + +void test_mailmap_parsing__string(void) +{ + cl_git_pass(git_mailmap_from_buffer( + &g_mailmap, string_mailmap, strlen(string_mailmap))); + + /* We should have parsed all of the entries */ + check_mailmap_entries(g_mailmap, entries, ARRAY_SIZE(entries)); + + /* Check that resolving the entries works */ + check_mailmap_resolve(g_mailmap, resolved, ARRAY_SIZE(resolved)); + check_mailmap_resolve( + g_mailmap, resolved_untracked, ARRAY_SIZE(resolved_untracked)); +} + +void test_mailmap_parsing__windows_string(void) +{ + git_str unixbuf = GIT_STR_INIT; + git_str winbuf = GIT_STR_INIT; + + /* Parse with windows-style line endings */ + git_str_attach_notowned(&unixbuf, string_mailmap, strlen(string_mailmap)); + cl_git_pass(git_str_lf_to_crlf(&winbuf, &unixbuf)); + + cl_git_pass(git_mailmap_from_buffer(&g_mailmap, winbuf.ptr, winbuf.size)); + git_str_dispose(&winbuf); + + /* We should have parsed all of the entries */ + check_mailmap_entries(g_mailmap, entries, ARRAY_SIZE(entries)); + + /* Check that resolving the entries works */ + check_mailmap_resolve(g_mailmap, resolved, ARRAY_SIZE(resolved)); + check_mailmap_resolve( + g_mailmap, resolved_untracked, ARRAY_SIZE(resolved_untracked)); +} + +void test_mailmap_parsing__fromrepo(void) +{ + g_repo = cl_git_sandbox_init("mailmap"); + cl_check(!git_repository_is_bare(g_repo)); + + cl_git_pass(git_mailmap_from_repository(&g_mailmap, g_repo)); + + /* We should have parsed all of the entries */ + check_mailmap_entries(g_mailmap, entries, ARRAY_SIZE(entries)); + + /* Check that resolving the entries works */ + check_mailmap_resolve(g_mailmap, resolved, ARRAY_SIZE(resolved)); + check_mailmap_resolve( + g_mailmap, resolved_untracked, ARRAY_SIZE(resolved_untracked)); +} + +static const mailmap_entry resolved_bare[] = { + { "xx", "untracked@company.xx", "xx", "untracked@company.xx" } +}; + +void test_mailmap_parsing__frombare(void) +{ + g_repo = cl_git_sandbox_init("mailmap/.gitted"); + cl_git_pass(git_repository_set_bare(g_repo)); + cl_check(git_repository_is_bare(g_repo)); + + cl_git_pass(git_mailmap_from_repository(&g_mailmap, g_repo)); + + /* We should have parsed all of the entries, except for the untracked one */ + check_mailmap_entries(g_mailmap, entries, ARRAY_SIZE(entries) - 1); + + /* Check that resolving the entries works */ + check_mailmap_resolve(g_mailmap, resolved, ARRAY_SIZE(resolved)); + check_mailmap_resolve( + g_mailmap, resolved_bare, ARRAY_SIZE(resolved_bare)); +} + +static const mailmap_entry resolved_with_file_override[] = { + { "Brad", "cto@company.xx", "Brad", "cto@coompany.xx" }, + { "Brad L", "cto@company.xx", "Brad L", "cto@coompany.xx" }, + { "Some Dude", "some@dude.xx", "nick1", "bugs@company.xx" }, + { "Other Author", "other@author.xx", "nick2", "bugs@company.xx" }, + { "nick3", "bugs@company.xx", "nick3", "bugs@company.xx" }, + { "Other Author", "other@author.xx", "Some Garbage", "nick2@company.xx" }, + { "Joseph", "joseph@company.xx", "Joseph", "bugs@company.xx" }, + { "Santa Claus", "santa.claus@northpole.xx", "Clause", "me@company.xx" }, + { "Charles", "charles@charles.xx", "Charles", "charles@charles.xx" }, + + /* This name is overridden by file_override */ + { "File Override", "phil@company.xx", "unknown", "phil@company.xx" }, + { "Other Name", "fileoverridename@company.xx", "override", "fileoverridename@company.xx" } +}; + +void test_mailmap_parsing__file_config(void) +{ + g_repo = cl_git_sandbox_init("mailmap"); + cl_git_pass(git_repository_config(&g_config, g_repo)); + + cl_git_pass(git_config_set_string( + g_config, "mailmap.file", cl_fixture("mailmap/file_override"))); + + cl_git_pass(git_mailmap_from_repository(&g_mailmap, g_repo)); + + /* Check we don't have duplicate entries */ + cl_assert_equal_sz(git_vector_length(&g_mailmap->entries), 9); + + /* Check that resolving the entries works */ + check_mailmap_resolve( + g_mailmap, resolved_with_file_override, + ARRAY_SIZE(resolved_with_file_override)); +} + +static const mailmap_entry resolved_with_blob_override[] = { + { "Brad", "cto@company.xx", "Brad", "cto@coompany.xx" }, + { "Brad L", "cto@company.xx", "Brad L", "cto@coompany.xx" }, + { "Some Dude", "some@dude.xx", "nick1", "bugs@company.xx" }, + { "Other Author", "other@author.xx", "nick2", "bugs@company.xx" }, + { "nick3", "bugs@company.xx", "nick3", "bugs@company.xx" }, + { "Other Author", "other@author.xx", "Some Garbage", "nick2@company.xx" }, + { "Joseph", "joseph@company.xx", "Joseph", "bugs@company.xx" }, + { "Santa Claus", "santa.claus@northpole.xx", "Clause", "me@company.xx" }, + { "Charles", "charles@charles.xx", "Charles", "charles@charles.xx" }, + + /* This name is overridden by blob_override */ + { "Blob Override", "phil@company.xx", "unknown", "phil@company.xx" }, + { "Other Name", "bloboverridename@company.xx", "override", "bloboverridename@company.xx" } +}; + +void test_mailmap_parsing__blob_config(void) +{ + g_repo = cl_git_sandbox_init("mailmap"); + cl_git_pass(git_repository_config(&g_config, g_repo)); + + cl_git_pass(git_config_set_string( + g_config, "mailmap.blob", "HEAD:blob_override")); + + cl_git_pass(git_mailmap_from_repository(&g_mailmap, g_repo)); + + /* Check we don't have duplicate entries */ + cl_assert_equal_sz(git_vector_length(&g_mailmap->entries), 9); + + /* Check that resolving the entries works */ + check_mailmap_resolve( + g_mailmap, resolved_with_blob_override, + ARRAY_SIZE(resolved_with_blob_override)); +} + +static const mailmap_entry bare_resolved_with_blob_override[] = { + /* As mailmap.blob is set, we won't load HEAD:.mailmap */ + { "Brad", "cto@coompany.xx", "Brad", "cto@coompany.xx" }, + { "Brad L", "cto@coompany.xx", "Brad L", "cto@coompany.xx" }, + { "nick1", "bugs@company.xx", "nick1", "bugs@company.xx" }, + { "nick2", "bugs@company.xx", "nick2", "bugs@company.xx" }, + { "nick3", "bugs@company.xx", "nick3", "bugs@company.xx" }, + { "Some Garbage", "nick2@company.xx", "Some Garbage", "nick2@company.xx" }, + { "Joseph", "bugs@company.xx", "Joseph", "bugs@company.xx" }, + { "Clause", "me@company.xx", "Clause", "me@company.xx" }, + { "Charles", "charles@charles.xx", "Charles", "charles@charles.xx" }, + + /* This name is overridden by blob_override */ + { "Blob Override", "phil@company.xx", "unknown", "phil@company.xx" }, + { "Other Name", "bloboverridename@company.xx", "override", "bloboverridename@company.xx" } +}; + +void test_mailmap_parsing__bare_blob_config(void) +{ + g_repo = cl_git_sandbox_init("mailmap/.gitted"); + cl_git_pass(git_repository_set_bare(g_repo)); + cl_check(git_repository_is_bare(g_repo)); + + cl_git_pass(git_repository_config(&g_config, g_repo)); + + cl_git_pass(git_config_set_string( + g_config, "mailmap.blob", "HEAD:blob_override")); + + cl_git_pass(git_mailmap_from_repository(&g_mailmap, g_repo)); + + /* Check that we only have the 2 entries */ + cl_assert_equal_sz(git_vector_length(&g_mailmap->entries), 2); + + /* Check that resolving the entries works */ + check_mailmap_resolve( + g_mailmap, bare_resolved_with_blob_override, + ARRAY_SIZE(bare_resolved_with_blob_override)); +} diff --git a/tests/libgit2/main.c b/tests/libgit2/main.c new file mode 100644 index 000000000..56751c288 --- /dev/null +++ b/tests/libgit2/main.c @@ -0,0 +1,50 @@ +#include "clar_libgit2.h" +#include "clar_libgit2_trace.h" + +#ifdef GIT_WIN32_LEAKCHECK +# include "win32/w32_leakcheck.h" +#endif + +#ifdef _WIN32 +int __cdecl main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + int res; + char *at_exit_cmd; + + clar_test_init(argc, argv); + + res = git_libgit2_init(); + if (res < 0) { + const git_error *err = git_error_last(); + const char *msg = err ? err->message : "unknown failure"; + fprintf(stderr, "failed to init libgit2: %s\n", msg); + return res; + } + + cl_global_trace_register(); + cl_sandbox_set_search_path_defaults(); + + /* Run the test suite */ + res = clar_test_run(); + + clar_test_shutdown(); + + cl_global_trace_disable(); + git_libgit2_shutdown(); + +#ifdef GIT_WIN32_LEAKCHECK + if (git_win32_leakcheck_has_leaks()) + res = res || 1; +#endif + + at_exit_cmd = getenv("CLAR_AT_EXIT"); + if (at_exit_cmd != NULL) { + int at_exit = system(at_exit_cmd); + return res || at_exit; + } + + return res; +} diff --git a/tests/libgit2/merge/analysis.c b/tests/libgit2/merge/analysis.c new file mode 100644 index 000000000..8c61303e3 --- /dev/null +++ b/tests/libgit2/merge/analysis.c @@ -0,0 +1,184 @@ +/* +NOTE: this is the implementation for both merge/trees/analysis.c and merge/workdir/analysis.c +You probably want to make changes to both files. +*/ + +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "git2/annotated_commit.h" +#include "git2/sys/index.h" +#include "merge.h" +#include "merge_helpers.h" +#include "refs.h" +#include "posix.h" + +#define TEST_REPO_PATH "merge-resolve" + +#define UPTODATE_BRANCH "master" +#define PREVIOUS_BRANCH "previous" + +#define FASTFORWARD_BRANCH "ff_branch" +#define FASTFORWARD_ID "fd89f8cffb663ac89095a0f9764902e93ceaca6a" + +#define NOFASTFORWARD_BRANCH "branch" +#define NOFASTFORWARD_ID "7cb63eed597130ba4abb87b3e544b85021905520" + +static git_repository *sandbox; +static git_repository *repo; + +void test_merge_analysis__initialize_with_bare_repository(void) +{ + sandbox = cl_git_sandbox_init(TEST_REPO_PATH); + cl_git_pass(git_repository_open_ext(&repo, git_repository_path(sandbox), + GIT_REPOSITORY_OPEN_BARE, NULL)); +} + +void test_merge_analysis__initialize_with_nonbare_repository(void) +{ + sandbox = cl_git_sandbox_init(TEST_REPO_PATH); + cl_git_pass(git_repository_open_ext(&repo, git_repository_workdir(sandbox), + 0, NULL)); +} + +void test_merge_analysis__cleanup(void) +{ + git_repository_free(repo); + cl_git_sandbox_cleanup(); +} + +static void analysis_from_branch( + git_merge_analysis_t *merge_analysis, + git_merge_preference_t *merge_pref, + const char *our_branchname, + const char *their_branchname) +{ + git_str our_refname = GIT_STR_INIT; + git_str their_refname = GIT_STR_INIT; + git_reference *our_ref; + git_reference *their_ref; + git_annotated_commit *their_head; + + if (our_branchname != NULL) { + cl_git_pass(git_str_printf(&our_refname, "%s%s", GIT_REFS_HEADS_DIR, our_branchname)); + cl_git_pass(git_reference_lookup(&our_ref, repo, git_str_cstr(&our_refname))); + } else { + cl_git_pass(git_reference_lookup(&our_ref, repo, GIT_HEAD_FILE)); + } + + cl_git_pass(git_str_printf(&their_refname, "%s%s", GIT_REFS_HEADS_DIR, their_branchname)); + + cl_git_pass(git_reference_lookup(&their_ref, repo, git_str_cstr(&their_refname))); + cl_git_pass(git_annotated_commit_from_ref(&their_head, repo, their_ref)); + + cl_git_pass(git_merge_analysis_for_ref(merge_analysis, merge_pref, repo, our_ref, (const git_annotated_commit **)&their_head, 1)); + + git_str_dispose(&our_refname); + git_str_dispose(&their_refname); + git_annotated_commit_free(their_head); + git_reference_free(our_ref); + git_reference_free(their_ref); +} + +void test_merge_analysis__fastforward(void) +{ + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; + + analysis_from_branch(&merge_analysis, &merge_pref, NULL, FASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL|GIT_MERGE_ANALYSIS_FASTFORWARD, merge_analysis); +} + +void test_merge_analysis__no_fastforward(void) +{ + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; + + analysis_from_branch(&merge_analysis, &merge_pref, NULL, NOFASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis); +} + +void test_merge_analysis__uptodate(void) +{ + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; + + analysis_from_branch(&merge_analysis, &merge_pref, NULL, UPTODATE_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); +} + +void test_merge_analysis__uptodate_merging_prev_commit(void) +{ + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; + + analysis_from_branch(&merge_analysis, &merge_pref, NULL, PREVIOUS_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); +} + +void test_merge_analysis__unborn(void) +{ + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; + git_str master = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&master, git_repository_path(repo), "refs/heads/master")); + cl_must_pass(p_unlink(git_str_cstr(&master))); + + analysis_from_branch(&merge_analysis, &merge_pref, NULL, NOFASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD|GIT_MERGE_ANALYSIS_UNBORN, merge_analysis); + + git_str_dispose(&master); +} + +void test_merge_analysis__fastforward_with_config_noff(void) +{ + git_config *config; + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_string(config, "merge.ff", "false")); + + analysis_from_branch(&merge_analysis, &merge_pref, NULL, FASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL|GIT_MERGE_ANALYSIS_FASTFORWARD, merge_analysis); + + cl_assert_equal_i(GIT_MERGE_PREFERENCE_NO_FASTFORWARD, (merge_pref & GIT_MERGE_PREFERENCE_NO_FASTFORWARD)); + + git_config_free(config); +} + +void test_merge_analysis__no_fastforward_with_config_ffonly(void) +{ + git_config *config; + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_string(config, "merge.ff", "only")); + + analysis_from_branch(&merge_analysis, &merge_pref, NULL, NOFASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis); + + cl_assert_equal_i(GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY, (merge_pref & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY)); + + git_config_free(config); +} + +void test_merge_analysis__between_uptodate_refs(void) +{ + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; + + analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH, PREVIOUS_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); +} + +void test_merge_analysis__between_noff_refs(void) +{ + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; + + analysis_from_branch(&merge_analysis, &merge_pref, "branch", FASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis); +} diff --git a/tests/libgit2/merge/annotated_commit.c b/tests/libgit2/merge/annotated_commit.c new file mode 100644 index 000000000..cfdf849e5 --- /dev/null +++ b/tests/libgit2/merge/annotated_commit.c @@ -0,0 +1,26 @@ +#include "clar_libgit2.h" + + +static git_repository *g_repo; + +void test_merge_annotated_commit__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_merge_annotated_commit__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_merge_annotated_commit__lookup_annotated_tag(void) +{ + git_annotated_commit *commit; + git_reference *ref; + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/tags/test")); + cl_git_pass(git_annotated_commit_from_ref(&commit, g_repo, ref)); + + git_annotated_commit_free(commit); + git_reference_free(ref); +} diff --git a/tests/libgit2/merge/conflict_data.h b/tests/libgit2/merge/conflict_data.h new file mode 100644 index 000000000..0b1e7ee03 --- /dev/null +++ b/tests/libgit2/merge/conflict_data.h @@ -0,0 +1,112 @@ +#define AUTOMERGEABLE_MERGED_FILE \ + "this file is changed in master\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is changed in branch\n" + +#define AUTOMERGEABLE_MERGED_FILE_CRLF \ + "this file is changed in master\r\n" \ + "this file is automergeable\r\n" \ + "this file is automergeable\r\n" \ + "this file is automergeable\r\n" \ + "this file is automergeable\r\n" \ + "this file is automergeable\r\n" \ + "this file is automergeable\r\n" \ + "this file is automergeable\r\n" \ + "this file is changed in branch\r\n" + +#define CONFLICTING_MERGE_FILE \ + "<<<<<<< HEAD\n" \ + "this file is changed in master and branch\n" \ + "=======\n" \ + "this file is changed in branch and master\n" \ + ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" + +#define CONFLICTING_DIFF3_FILE \ + "<<<<<<< HEAD\n" \ + "this file is changed in master and branch\n" \ + "||||||| initial\n" \ + "this file is a conflict\n" \ + "=======\n" \ + "this file is changed in branch and master\n" \ + ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" + +#define CONFLICTING_ZDIFF3_FILE \ + "<<<<<<< HEAD\n" \ + "this file is changed in master and branch\n" \ + "||||||| initial\n" \ + "this file is a conflict\n" \ + "=======\n" \ + "this file is changed in branch and master\n" \ + ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" + +#define CONFLICTING_UNION_FILE \ + "this file is changed in master and branch\n" \ + "this file is changed in branch and master\n" + +#define CONFLICTING_RECURSIVE_F1_TO_F2 \ + "VEAL SOUP.\n" \ + "\n" \ + "<<<<<<< HEAD\n" \ + "PUT INTO A POT THREE QUARTS OF WATER, three onions cut small, ONE\n" \ + "=======\n" \ + "PUT INTO A POT THREE QUARTS OF WATER, three onions cut not too small, one\n" \ + ">>>>>>> branchF-2\n" \ + "spoonful of black pepper pounded, and two of salt, with two or three\n" \ + "slices of lean ham; let it boil steadily two hours; skim it\n" \ + "occasionally, then put into it a shin of veal, let it boil two hours\n" \ + "longer; take out the slices of ham, and skim off the grease if any\n" \ + "should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ + "of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ + "mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ + "stir it well, and pour it into the pot, continuing to stir until it has\n" \ + "boiled two or three minutes to take off the raw taste of the eggs. If\n" \ + "the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ + "will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ + "in, first taking off their skins, by letting them stand a few minutes in\n" \ + "hot water, when they may be easily peeled. When made in this way you\n" \ + "must thicken it with the flour only. Any part of the veal may be used,\n" \ + "but the shin or knuckle is the nicest.\n" \ + "\n" \ + "<<<<<<< HEAD\n" \ + "This certainly is a mighty fine recipe.\n" \ + "=======\n" \ + "This is a mighty fine recipe!\n" \ + ">>>>>>> branchF-2\n" + +#define CONFLICTING_RECURSIVE_H2_TO_H1_WITH_DIFF3 \ + "VEAL SOUP.\n" \ + "\n" \ + "<<<<<<< HEAD\n" \ + "Put Into A Pot Three Quarts of Water, Three Onions Cut Small, One\n" \ + "||||||| merged common ancestors\n" \ + "<<<<<<<<< Temporary merge branch 1\n" \ + "PUT INTO A POT three quarts of water, three onions cut small, one\n" \ + "||||||||| merged common ancestors\n" \ + "Put into a pot three quarts of water, three onions cut small, one\n" \ + "=========\n" \ + "Put into a pot three quarts of water, THREE ONIONS CUT SMALL, one\n" \ + ">>>>>>>>> Temporary merge branch 2\n" \ + "=======\n" \ + "put into a pot three quarts of water, three onions cut small, one\n" \ + ">>>>>>> branchH-1\n" \ + "spoonful of black pepper pounded, and two of salt, with two or three\n" \ + "slices of lean ham; let it boil steadily two hours; skim it\n" \ + "occasionally, then put into it a shin of veal, let it boil two hours\n" \ + "longer; take out the slices of ham, and skim off the grease if any\n" \ + "should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ + "of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ + "mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ + "stir it well, and pour it into the pot, continuing to stir until it has\n" \ + "boiled two or three minutes to take off the raw taste of the eggs. If\n" \ + "the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ + "will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ + "in, first taking off their skins, by letting them stand a few minutes in\n" \ + "hot water, when they may be easily peeled. When made in this way you\n" \ + "must thicken it with the flour only. Any part of the veal may be used,\n" \ + "but the shin or knuckle is the nicest.\n" diff --git a/tests/libgit2/merge/driver.c b/tests/libgit2/merge/driver.c new file mode 100644 index 000000000..b7d320cbb --- /dev/null +++ b/tests/libgit2/merge/driver.c @@ -0,0 +1,396 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" + +#define TEST_REPO_PATH "merge-resolve" +#define BRANCH_ID "7cb63eed597130ba4abb87b3e544b85021905520" + +#define AUTOMERGEABLE_IDSTR "f2e1550a0c9e53d5811175864a29536642ae3821" + +static git_repository *repo; +static git_index *repo_index; +static git_oid automergeable_id; + +static void test_drivers_register(void); +static void test_drivers_unregister(void); + +void test_merge_driver__initialize(void) +{ + git_config *cfg; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); + + git_oid_fromstr(&automergeable_id, AUTOMERGEABLE_IDSTR); + + /* Ensure that the user's merge.conflictstyle doesn't interfere */ + cl_git_pass(git_repository_config(&cfg, repo)); + + cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge")); + cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", false)); + + test_drivers_register(); + + git_config_free(cfg); +} + +void test_merge_driver__cleanup(void) +{ + test_drivers_unregister(); + + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +struct test_merge_driver { + git_merge_driver base; + int initialized; + int shutdown; +}; + +static int test_driver_init(git_merge_driver *s) +{ + struct test_merge_driver *self = (struct test_merge_driver *)s; + self->initialized = 1; + return 0; +} + +static void test_driver_shutdown(git_merge_driver *s) +{ + struct test_merge_driver *self = (struct test_merge_driver *)s; + self->shutdown = 1; +} + +static int test_driver_apply( + git_merge_driver *s, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + git_str str = GIT_STR_INIT; + int error; + + GIT_UNUSED(s); + GIT_UNUSED(src); + + *path_out = "applied.txt"; + *mode_out = GIT_FILEMODE_BLOB; + + error = git_str_printf(&str, "This is the `%s` driver.\n", + filter_name); + + merged_out->ptr = str.ptr; + merged_out->size = str.size; + merged_out->reserved = 0; + + return error; +} + +static struct test_merge_driver test_driver_custom = { + { + GIT_MERGE_DRIVER_VERSION, + test_driver_init, + test_driver_shutdown, + test_driver_apply, + }, + 0, + 0, +}; + +static struct test_merge_driver test_driver_wildcard = { + { + GIT_MERGE_DRIVER_VERSION, + test_driver_init, + test_driver_shutdown, + test_driver_apply, + }, + 0, + 0, +}; + +static void test_drivers_register(void) +{ + cl_git_pass(git_merge_driver_register("custom", &test_driver_custom.base)); + cl_git_pass(git_merge_driver_register("*", &test_driver_wildcard.base)); +} + +static void test_drivers_unregister(void) +{ + cl_git_pass(git_merge_driver_unregister("custom")); + cl_git_pass(git_merge_driver_unregister("*")); +} + +static void set_gitattributes_to(const char *driver) +{ + git_str line = GIT_STR_INIT; + + if (driver && strcmp(driver, "")) + git_str_printf(&line, "automergeable.txt merge=%s\n", driver); + else if (driver) + git_str_printf(&line, "automergeable.txt merge\n"); + else + git_str_printf(&line, "automergeable.txt -merge\n"); + + cl_assert(!git_str_oom(&line)); + + cl_git_mkfile(TEST_REPO_PATH "/.gitattributes", line.ptr); + git_str_dispose(&line); +} + +static void merge_branch(void) +{ + git_oid their_id; + git_annotated_commit *their_head; + + cl_git_pass(git_oid_fromstr(&their_id, BRANCH_ID)); + cl_git_pass(git_annotated_commit_lookup(&their_head, repo, &their_id)); + + cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, + 1, NULL, NULL)); + + git_annotated_commit_free(their_head); +} + +void test_merge_driver__custom(void) +{ + const char *expected = "This is the `custom` driver.\n"; + set_gitattributes_to("custom"); + merge_branch(); + + cl_assert_equal_file(expected, strlen(expected), + TEST_REPO_PATH "/applied.txt"); +} + +void test_merge_driver__wildcard(void) +{ + const char *expected = "This is the `foobar` driver.\n"; + set_gitattributes_to("foobar"); + merge_branch(); + + cl_assert_equal_file(expected, strlen(expected), + TEST_REPO_PATH "/applied.txt"); +} + +void test_merge_driver__shutdown_is_called(void) +{ + test_driver_custom.initialized = 0; + test_driver_custom.shutdown = 0; + test_driver_wildcard.initialized = 0; + test_driver_wildcard.shutdown = 0; + + /* run the merge with the custom driver */ + set_gitattributes_to("custom"); + merge_branch(); + + /* unregister the drivers, ensure their shutdown function is called */ + test_drivers_unregister(); + + /* since the `custom` driver was used, it should have been initialized and + * shutdown, but the wildcard driver was not used at all and should not + * have been initialized or shutdown. + */ + cl_assert(test_driver_custom.initialized); + cl_assert(test_driver_custom.shutdown); + cl_assert(!test_driver_wildcard.initialized); + cl_assert(!test_driver_wildcard.shutdown); + + test_drivers_register(); +} + +static int defer_driver_apply( + git_merge_driver *s, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + GIT_UNUSED(s); + GIT_UNUSED(path_out); + GIT_UNUSED(mode_out); + GIT_UNUSED(merged_out); + GIT_UNUSED(filter_name); + GIT_UNUSED(src); + + return GIT_PASSTHROUGH; +} + +static struct test_merge_driver test_driver_defer_apply = { + { + GIT_MERGE_DRIVER_VERSION, + test_driver_init, + test_driver_shutdown, + defer_driver_apply, + }, + 0, + 0, +}; + +void test_merge_driver__apply_can_defer(void) +{ + const git_index_entry *idx; + + cl_git_pass(git_merge_driver_register("defer", + &test_driver_defer_apply.base)); + + set_gitattributes_to("defer"); + merge_branch(); + + cl_assert((idx = git_index_get_bypath(repo_index, "automergeable.txt", 0))); + cl_assert_equal_oid(&automergeable_id, &idx->id); + + git_merge_driver_unregister("defer"); +} + +static int conflict_driver_apply( + git_merge_driver *s, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + GIT_UNUSED(s); + GIT_UNUSED(path_out); + GIT_UNUSED(mode_out); + GIT_UNUSED(merged_out); + GIT_UNUSED(filter_name); + GIT_UNUSED(src); + + return GIT_EMERGECONFLICT; +} + +static struct test_merge_driver test_driver_conflict_apply = { + { + GIT_MERGE_DRIVER_VERSION, + test_driver_init, + test_driver_shutdown, + conflict_driver_apply, + }, + 0, + 0, +}; + +void test_merge_driver__apply_can_conflict(void) +{ + const git_index_entry *ancestor, *ours, *theirs; + + cl_git_pass(git_merge_driver_register("conflict", + &test_driver_conflict_apply.base)); + + set_gitattributes_to("conflict"); + merge_branch(); + + cl_git_pass(git_index_conflict_get(&ancestor, &ours, &theirs, + repo_index, "automergeable.txt")); + + git_merge_driver_unregister("conflict"); +} + +void test_merge_driver__default_can_be_specified(void) +{ + git_oid their_id; + git_annotated_commit *their_head; + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + const char *expected = "This is the `custom` driver.\n"; + + merge_opts.default_driver = "custom"; + + cl_git_pass(git_oid_fromstr(&their_id, BRANCH_ID)); + cl_git_pass(git_annotated_commit_lookup(&their_head, repo, &their_id)); + + cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, + 1, &merge_opts, NULL)); + + git_annotated_commit_free(their_head); + + cl_assert_equal_file(expected, strlen(expected), + TEST_REPO_PATH "/applied.txt"); +} + +void test_merge_driver__honors_builtin_mergedefault(void) +{ + const git_index_entry *ancestor, *ours, *theirs; + + cl_repo_set_string(repo, "merge.default", "binary"); + merge_branch(); + + cl_git_pass(git_index_conflict_get(&ancestor, &ours, &theirs, + repo_index, "automergeable.txt")); +} + +void test_merge_driver__honors_custom_mergedefault(void) +{ + const char *expected = "This is the `custom` driver.\n"; + + cl_repo_set_string(repo, "merge.default", "custom"); + merge_branch(); + + cl_assert_equal_file(expected, strlen(expected), + TEST_REPO_PATH "/applied.txt"); +} + +void test_merge_driver__mergedefault_deferring_falls_back_to_text(void) +{ + const git_index_entry *idx; + + cl_git_pass(git_merge_driver_register("defer", + &test_driver_defer_apply.base)); + + cl_repo_set_string(repo, "merge.default", "defer"); + merge_branch(); + + cl_assert((idx = git_index_get_bypath(repo_index, "automergeable.txt", 0))); + cl_assert_equal_oid(&automergeable_id, &idx->id); + + git_merge_driver_unregister("defer"); +} + +void test_merge_driver__set_forces_text(void) +{ + const git_index_entry *idx; + + /* `merge` without specifying a driver indicates `text` */ + set_gitattributes_to(""); + cl_repo_set_string(repo, "merge.default", "custom"); + + merge_branch(); + + cl_assert((idx = git_index_get_bypath(repo_index, "automergeable.txt", 0))); + cl_assert_equal_oid(&automergeable_id, &idx->id); +} + +void test_merge_driver__unset_forces_binary(void) +{ + const git_index_entry *ancestor, *ours, *theirs; + + /* `-merge` without specifying a driver indicates `binary` */ + set_gitattributes_to(NULL); + cl_repo_set_string(repo, "merge.default", "custom"); + + merge_branch(); + + cl_git_pass(git_index_conflict_get(&ancestor, &ours, &theirs, + repo_index, "automergeable.txt")); +} + +void test_merge_driver__not_configured_driver_falls_back(void) +{ + const git_index_entry *idx; + + test_drivers_unregister(); + + /* `merge` without specifying a driver indicates `text` */ + set_gitattributes_to("notfound"); + + merge_branch(); + + cl_assert((idx = git_index_get_bypath(repo_index, "automergeable.txt", 0))); + cl_assert_equal_oid(&automergeable_id, &idx->id); + + test_drivers_register(); +} + diff --git a/tests/libgit2/merge/files.c b/tests/libgit2/merge/files.c new file mode 100644 index 000000000..6296f3b7b --- /dev/null +++ b/tests/libgit2/merge/files.c @@ -0,0 +1,465 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "merge_helpers.h" +#include "conflict_data.h" +#include "refs.h" +#include "futils.h" +#include "diff_xdiff.h" + +#define TEST_REPO_PATH "merge-resolve" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +static git_repository *repo; +static git_index *repo_index; + +/* Fixture setup and teardown */ +void test_merge_files__initialize(void) +{ + git_config *cfg; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); + + /* Ensure that the user's merge.conflictstyle doesn't interfere */ + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge")); + git_config_free(cfg); +} + +void test_merge_files__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +void test_merge_files__automerge_from_bufs(void) +{ + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, + ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_result result = {0}; + const char *expected = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n"; + + ancestor.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"; + ancestor.size = strlen(ancestor.ptr); + ancestor.path = "testfile.txt"; + ancestor.mode = 0100755; + + ours.ptr = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"; + ours.size = strlen(ours.ptr); + ours.path = "testfile.txt"; + ours.mode = 0100755; + + theirs.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n"; + theirs.size = strlen(theirs.ptr); + theirs.path = "testfile.txt"; + theirs.mode = 0100755; + + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, 0)); + + cl_assert_equal_i(1, result.automergeable); + + cl_assert_equal_s("testfile.txt", result.path); + cl_assert_equal_i(0100755, result.mode); + + cl_assert_equal_i(strlen(expected), result.len); + cl_assert_equal_strn(expected, result.ptr, result.len); + + git_merge_file_result_free(&result); +} + +void test_merge_files__automerge_use_best_path_and_mode(void) +{ + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, + ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_result result = {0}; + const char *expected = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n"; + + ancestor.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"; + ancestor.size = strlen(ancestor.ptr); + ancestor.path = "testfile.txt"; + ancestor.mode = 0100755; + + ours.ptr = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"; + ours.size = strlen(ours.ptr); + ours.path = "testfile.txt"; + ours.mode = 0100644; + + theirs.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n"; + theirs.size = strlen(theirs.ptr); + theirs.path = "theirs.txt"; + theirs.mode = 0100755; + + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, 0)); + + cl_assert_equal_i(1, result.automergeable); + + cl_assert_equal_s("theirs.txt", result.path); + cl_assert_equal_i(0100644, result.mode); + + cl_assert_equal_i(strlen(expected), result.len); + cl_assert_equal_strn(expected, result.ptr, result.len); + + git_merge_file_result_free(&result); +} + +void test_merge_files__conflict_from_bufs(void) +{ + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, + ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_result result = {0}; + + const char *expected = "<<<<<<< testfile.txt\nAloha!\nOurs.\n=======\nHi!\nTheirs.\n>>>>>>> theirs.txt\n"; + size_t expected_len = strlen(expected); + + ancestor.ptr = "Hello!\nAncestor!\n"; + ancestor.size = strlen(ancestor.ptr); + ancestor.path = "testfile.txt"; + ancestor.mode = 0100755; + + ours.ptr = "Aloha!\nOurs.\n"; + ours.size = strlen(ours.ptr); + ours.path = "testfile.txt"; + ours.mode = 0100644; + + theirs.ptr = "Hi!\nTheirs.\n"; + theirs.size = strlen(theirs.ptr); + theirs.path = "theirs.txt"; + theirs.mode = 0100755; + + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, NULL)); + + cl_assert_equal_i(0, result.automergeable); + + cl_assert_equal_s("theirs.txt", result.path); + cl_assert_equal_i(0100644, result.mode); + + cl_assert_equal_i(expected_len, result.len); + cl_assert_equal_strn(expected, result.ptr, expected_len); + + git_merge_file_result_free(&result); +} + +void test_merge_files__automerge_from_index(void) +{ + git_merge_file_result result = {0}; + git_index_entry ancestor, ours, theirs; + + git_oid_fromstr(&ancestor.id, "6212c31dab5e482247d7977e4f0dd3601decf13b"); + ancestor.path = "automergeable.txt"; + ancestor.mode = 0100644; + + git_oid_fromstr(&ours.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); + ours.path = "automergeable.txt"; + ours.mode = 0100755; + + git_oid_fromstr(&theirs.id, "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe"); + theirs.path = "newname.txt"; + theirs.mode = 0100644; + + cl_git_pass(git_merge_file_from_index(&result, repo, + &ancestor, &ours, &theirs, 0)); + + cl_assert_equal_i(1, result.automergeable); + + cl_assert_equal_s("newname.txt", result.path); + cl_assert_equal_i(0100755, result.mode); + + cl_assert_equal_i(strlen(AUTOMERGEABLE_MERGED_FILE), result.len); + cl_assert_equal_strn(AUTOMERGEABLE_MERGED_FILE, result.ptr, result.len); + + git_merge_file_result_free(&result); +} + +void test_merge_files__automerge_whitespace_eol(void) +{ + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, + ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + const char *expected = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n"; + + ancestor.ptr = "0 \n1\n2\n3\n4\n5\n6\n7\n8\n9\n10 \n"; + ancestor.size = strlen(ancestor.ptr); + ancestor.path = "testfile.txt"; + ancestor.mode = 0100755; + + ours.ptr = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"; + ours.size = strlen(ours.ptr); + ours.path = "testfile.txt"; + ours.mode = 0100755; + + theirs.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n"; + theirs.size = strlen(theirs.ptr); + theirs.path = "testfile.txt"; + theirs.mode = 0100755; + + opts.flags |= GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL; + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); + + cl_assert_equal_i(1, result.automergeable); + + cl_assert_equal_s("testfile.txt", result.path); + cl_assert_equal_i(0100755, result.mode); + + cl_assert_equal_i(strlen(expected), result.len); + cl_assert_equal_strn(expected, result.ptr, result.len); + + git_merge_file_result_free(&result); +} + +void test_merge_files__automerge_whitespace_change(void) +{ + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, + ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + const char *expected = "Zero\n1\n2\n3\n4\n5 XXX\n6 YYY\n7\n8\n9\nTen\n"; + + ancestor.ptr = "0\n1\n2\n3\n4\n5 XXX\n6YYY\n7\n8\n9\n10\n"; + ancestor.size = strlen(ancestor.ptr); + ancestor.path = "testfile.txt"; + ancestor.mode = 0100755; + + ours.ptr = "Zero\n1\n2\n3\n4\n5 XXX\n6 YYY\n7\n8\n9\n10\n"; + ours.size = strlen(ours.ptr); + ours.path = "testfile.txt"; + ours.mode = 0100755; + + theirs.ptr = "0\n1\n2\n3\n4\n5 XXX\n6 YYY\n7\n8\n9\nTen\n"; + theirs.size = strlen(theirs.ptr); + theirs.path = "testfile.txt"; + theirs.mode = 0100755; + + opts.flags |= GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE; + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); + + cl_assert_equal_i(1, result.automergeable); + + cl_assert_equal_s("testfile.txt", result.path); + cl_assert_equal_i(0100755, result.mode); + + cl_assert_equal_i(strlen(expected), result.len); + cl_assert_equal_strn(expected, result.ptr, result.len); + + git_merge_file_result_free(&result); +} + +void test_merge_files__doesnt_add_newline(void) +{ + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, + ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + const char *expected = "Zero\n1\n2\n3\n4\n5 XXX\n6 YYY\n7\n8\n9\nTen"; + + ancestor.ptr = "0\n1\n2\n3\n4\n5 XXX\n6YYY\n7\n8\n9\n10"; + ancestor.size = strlen(ancestor.ptr); + ancestor.path = "testfile.txt"; + ancestor.mode = 0100755; + + ours.ptr = "Zero\n1\n2\n3\n4\n5 XXX\n6 YYY\n7\n8\n9\n10"; + ours.size = strlen(ours.ptr); + ours.path = "testfile.txt"; + ours.mode = 0100755; + + theirs.ptr = "0\n1\n2\n3\n4\n5 XXX\n6 YYY\n7\n8\n9\nTen"; + theirs.size = strlen(theirs.ptr); + theirs.path = "testfile.txt"; + theirs.mode = 0100755; + + opts.flags |= GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE; + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); + + cl_assert_equal_i(1, result.automergeable); + + cl_assert_equal_s("testfile.txt", result.path); + cl_assert_equal_i(0100755, result.mode); + + cl_assert_equal_i(strlen(expected), result.len); + cl_assert_equal_strn(expected, result.ptr, result.len); + + git_merge_file_result_free(&result); +} + +void test_merge_files__skips_large_files(void) +{ + git_merge_file_input ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + + ours.size = GIT_XDIFF_MAX_SIZE + 1; + ours.path = "testfile.txt"; + ours.mode = 0100755; + + theirs.size = GIT_XDIFF_MAX_SIZE + 1; + theirs.path = "testfile.txt"; + theirs.mode = 0100755; + + cl_git_pass(git_merge_file(&result, NULL, &ours, &theirs, &opts)); + + cl_assert_equal_i(0, result.automergeable); + + git_merge_file_result_free(&result); +} + +void test_merge_files__skips_binaries(void) +{ + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, + ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_result result = {0}; + + ancestor.ptr = "ance\0stor\0"; + ancestor.size = 10; + ancestor.path = "ancestor.txt"; + ancestor.mode = 0100755; + + ours.ptr = "foo\0bar\0"; + ours.size = 8; + ours.path = "ours.txt"; + ours.mode = 0100755; + + theirs.ptr = "bar\0foo\0"; + theirs.size = 8; + theirs.path = "theirs.txt"; + theirs.mode = 0100644; + + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, NULL)); + + cl_assert_equal_i(0, result.automergeable); + + git_merge_file_result_free(&result); +} + +void test_merge_files__handles_binaries_when_favored(void) +{ + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, + ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + + ancestor.ptr = "ance\0stor\0"; + ancestor.size = 10; + ancestor.path = "ancestor.txt"; + ancestor.mode = 0100755; + + ours.ptr = "foo\0bar\0"; + ours.size = 8; + ours.path = "ours.txt"; + ours.mode = 0100755; + + theirs.ptr = "bar\0foo\0"; + theirs.size = 8; + theirs.path = "theirs.txt"; + theirs.mode = 0100644; + + opts.favor = GIT_MERGE_FILE_FAVOR_OURS; + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); + + cl_assert_equal_i(1, result.automergeable); + + cl_assert_equal_s("ours.txt", result.path); + cl_assert_equal_i(0100755, result.mode); + + cl_assert_equal_i(ours.size, result.len); + cl_assert(memcmp(result.ptr, ours.ptr, ours.size) == 0); + + git_merge_file_result_free(&result); +} + +void test_merge_files__crlf_conflict_markers_for_crlf_files(void) +{ + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, + ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + + const char *expected = + "<<<<<<< file.txt\r\nThis file\r\ndoes, too.\r\n" + "=======\r\nAnd so does\r\nthis one.\r\n>>>>>>> file.txt\r\n"; + size_t expected_len = strlen(expected); + + const char *expected_diff3 = + "<<<<<<< file.txt\r\nThis file\r\ndoes, too.\r\n" + "||||||| file.txt\r\nThis file has\r\nCRLF line endings.\r\n" + "=======\r\nAnd so does\r\nthis one.\r\n>>>>>>> file.txt\r\n"; + size_t expected_diff3_len = strlen(expected_diff3); + + ancestor.ptr = "This file has\r\nCRLF line endings.\r\n"; + ancestor.size = 35; + ancestor.path = "file.txt"; + ancestor.mode = 0100644; + + ours.ptr = "This file\r\ndoes, too.\r\n"; + ours.size = 23; + ours.path = "file.txt"; + ours.mode = 0100644; + + theirs.ptr = "And so does\r\nthis one.\r\n"; + theirs.size = 24; + theirs.path = "file.txt"; + theirs.mode = 0100644; + + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); + cl_assert_equal_i(0, result.automergeable); + cl_assert_equal_i(expected_len, result.len); + cl_assert(memcmp(expected, result.ptr, expected_len) == 0); + git_merge_file_result_free(&result); + + opts.flags |= GIT_MERGE_FILE_STYLE_DIFF3; + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); + cl_assert_equal_i(0, result.automergeable); + cl_assert_equal_i(expected_diff3_len, result.len); + cl_assert(memcmp(expected_diff3, result.ptr, expected_len) == 0); + git_merge_file_result_free(&result); +} + +void test_merge_files__conflicts_in_zdiff3(void) +{ + git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, + ours = GIT_MERGE_FILE_INPUT_INIT, + theirs = GIT_MERGE_FILE_INPUT_INIT; + git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + + const char *expected_zdiff3 = + "1,\nfoo,\nbar,\n" \ + "<<<<<<< file.txt\n" \ + "||||||| file.txt\n# add more here\n" \ + "=======\nquux,\nwoot,\n" \ + ">>>>>>> file.txt\nbaz,\n3,\n"; + size_t expected_zdiff3_len = strlen(expected_zdiff3); + + ancestor.ptr = "1,\n# add more here\n3,\n"; + ancestor.size = strlen(ancestor.ptr); + ancestor.path = "file.txt"; + ancestor.mode = 0100644; + + ours.ptr = "1,\nfoo,\nbar,\nbaz,\n3,\n"; + ours.size = strlen(ours.ptr); + ours.path = "file.txt"; + ours.mode = 0100644; + + theirs.ptr = "1,\nfoo,\nbar,\nquux,\nwoot,\nbaz,\n3,\n"; + theirs.size = strlen(theirs.ptr); + theirs.path = "file.txt"; + theirs.mode = 0100644; + + opts.flags |= GIT_MERGE_FILE_STYLE_ZDIFF3; + cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); + cl_assert_equal_i(0, result.automergeable); + cl_assert_equal_i(expected_zdiff3_len, result.len); + cl_assert(memcmp(expected_zdiff3, result.ptr, expected_zdiff3_len) == 0); + git_merge_file_result_free(&result); +} diff --git a/tests/libgit2/merge/merge_helpers.c b/tests/libgit2/merge/merge_helpers.c new file mode 100644 index 000000000..ce3cd229b --- /dev/null +++ b/tests/libgit2/merge/merge_helpers.c @@ -0,0 +1,365 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "refs.h" +#include "tree.h" +#include "merge_helpers.h" +#include "merge.h" +#include "index.h" +#include "git2/merge.h" +#include "git2/sys/index.h" +#include "git2/annotated_commit.h" + +int merge_trees_from_branches( + git_index **index, git_repository *repo, + const char *ours_name, const char *theirs_name, + git_merge_options *opts) +{ + git_commit *our_commit, *their_commit, *ancestor_commit = NULL; + git_tree *our_tree, *their_tree, *ancestor_tree = NULL; + git_oid our_oid, their_oid, ancestor_oid; + git_str branch_buf = GIT_STR_INIT; + int error; + + git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours_name); + cl_git_pass(git_reference_name_to_id(&our_oid, repo, branch_buf.ptr)); + cl_git_pass(git_commit_lookup(&our_commit, repo, &our_oid)); + + git_str_clear(&branch_buf); + git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, theirs_name); + cl_git_pass(git_reference_name_to_id(&their_oid, repo, branch_buf.ptr)); + cl_git_pass(git_commit_lookup(&their_commit, repo, &their_oid)); + + error = git_merge_base(&ancestor_oid, repo, git_commit_id(our_commit), git_commit_id(their_commit)); + + if (error != GIT_ENOTFOUND) { + cl_git_pass(error); + + cl_git_pass(git_commit_lookup(&ancestor_commit, repo, &ancestor_oid)); + cl_git_pass(git_commit_tree(&ancestor_tree, ancestor_commit)); + } + + cl_git_pass(git_commit_tree(&our_tree, our_commit)); + cl_git_pass(git_commit_tree(&their_tree, their_commit)); + + error = git_merge_trees(index, repo, ancestor_tree, our_tree, their_tree, opts); + + git_str_dispose(&branch_buf); + git_tree_free(our_tree); + git_tree_free(their_tree); + git_tree_free(ancestor_tree); + git_commit_free(our_commit); + git_commit_free(their_commit); + git_commit_free(ancestor_commit); + + return error; +} + +int merge_commits_from_branches( + git_index **index, git_repository *repo, + const char *ours_name, const char *theirs_name, + git_merge_options *opts) +{ + git_commit *our_commit, *their_commit; + git_oid our_oid, their_oid; + git_str branch_buf = GIT_STR_INIT; + int error; + + git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours_name); + cl_git_pass(git_reference_name_to_id(&our_oid, repo, branch_buf.ptr)); + cl_git_pass(git_commit_lookup(&our_commit, repo, &our_oid)); + + git_str_clear(&branch_buf); + git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, theirs_name); + cl_git_pass(git_reference_name_to_id(&their_oid, repo, branch_buf.ptr)); + cl_git_pass(git_commit_lookup(&their_commit, repo, &their_oid)); + + error = git_merge_commits(index, repo, our_commit, their_commit, opts); + + git_str_dispose(&branch_buf); + git_commit_free(our_commit); + git_commit_free(their_commit); + + return error; +} + +int merge_branches(git_repository *repo, + const char *ours_branch, const char *theirs_branch, + git_merge_options *merge_opts, git_checkout_options *checkout_opts) +{ + git_reference *head_ref, *theirs_ref; + git_annotated_commit *theirs_head; + git_checkout_options head_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + head_checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_symbolic_create(&head_ref, repo, "HEAD", ours_branch, 1, NULL)); + cl_git_pass(git_checkout_head(repo, &head_checkout_opts)); + + cl_git_pass(git_reference_lookup(&theirs_ref, repo, theirs_branch)); + cl_git_pass(git_annotated_commit_from_ref(&theirs_head, repo, theirs_ref)); + + cl_git_pass(git_merge(repo, (const git_annotated_commit **)&theirs_head, 1, merge_opts, checkout_opts)); + + git_reference_free(head_ref); + git_reference_free(theirs_ref); + git_annotated_commit_free(theirs_head); + + return 0; +} + +void merge__dump_index_entries(git_vector *index_entries) +{ + size_t i; + const git_index_entry *index_entry; + + printf ("\nINDEX [%"PRIuZ"]:\n", index_entries->length); + for (i = 0; i < index_entries->length; i++) { + index_entry = index_entries->contents[i]; + + printf("%o ", index_entry->mode); + printf("%s ", git_oid_allocfmt(&index_entry->id)); + printf("%d ", git_index_entry_stage(index_entry)); + printf("%s ", index_entry->path); + printf("\n"); + } + printf("\n"); +} + +void merge__dump_names(git_index *index) +{ + size_t i; + const git_index_name_entry *conflict_name; + + for (i = 0; i < git_index_name_entrycount(index); i++) { + conflict_name = git_index_name_get_byindex(index, i); + + printf("%s %s %s\n", conflict_name->ancestor, conflict_name->ours, conflict_name->theirs); + } + printf("\n"); +} + +void merge__dump_reuc(git_index *index) +{ + size_t i; + const git_index_reuc_entry *reuc; + + printf ("\nREUC:\n"); + for (i = 0; i < git_index_reuc_entrycount(index); i++) { + reuc = git_index_reuc_get_byindex(index, i); + + printf("%s ", reuc->path); + printf("%o ", reuc->mode[0]); + printf("%s\n", git_oid_allocfmt(&reuc->oid[0])); + printf(" %o ", reuc->mode[1]); + printf(" %s\n", git_oid_allocfmt(&reuc->oid[1])); + printf(" %o ", reuc->mode[2]); + printf(" %s ", git_oid_allocfmt(&reuc->oid[2])); + printf("\n"); + } + printf("\n"); +} + +static int index_entry_eq_merge_index_entry(const struct merge_index_entry *expected, const git_index_entry *actual) +{ + git_oid expected_oid; + bool test_oid; + + if (strlen(expected->oid_str) != 0) { + cl_git_pass(git_oid_fromstr(&expected_oid, expected->oid_str)); + test_oid = 1; + } else + test_oid = 0; + + if (actual->mode != expected->mode || + (test_oid && git_oid_cmp(&actual->id, &expected_oid) != 0) || + git_index_entry_stage(actual) != expected->stage) + return 0; + + if (actual->mode == 0 && (actual->path != NULL || strlen(expected->path) > 0)) + return 0; + + if (actual->mode != 0 && (strcmp(actual->path, expected->path) != 0)) + return 0; + + return 1; +} + +static int name_entry_eq(const char *expected, const char *actual) +{ + if (strlen(expected) == 0) + return (actual == NULL) ? 1 : 0; + + return (strcmp(expected, actual) == 0) ? 1 : 0; +} + +static int name_entry_eq_merge_name_entry(const struct merge_name_entry *expected, const git_index_name_entry *actual) +{ + if (name_entry_eq(expected->ancestor_path, actual->ancestor) == 0 || + name_entry_eq(expected->our_path, actual->ours) == 0 || + name_entry_eq(expected->their_path, actual->theirs) == 0) + return 0; + + return 1; +} + +static int index_conflict_data_eq_merge_diff(const struct merge_index_conflict_data *expected, git_merge_diff *actual) +{ + if (!index_entry_eq_merge_index_entry(&expected->ancestor.entry, &actual->ancestor_entry) || + !index_entry_eq_merge_index_entry(&expected->ours.entry, &actual->our_entry) || + !index_entry_eq_merge_index_entry(&expected->theirs.entry, &actual->their_entry)) + return 0; + + if (expected->ours.status != actual->our_status || + expected->theirs.status != actual->their_status) + return 0; + + return 1; +} + +int merge_test_merge_conflicts(git_vector *conflicts, const struct merge_index_conflict_data expected[], size_t expected_len) +{ + git_merge_diff *actual; + size_t i; + + if (conflicts->length != expected_len) + return 0; + + for (i = 0; i < expected_len; i++) { + actual = conflicts->contents[i]; + + if (!index_conflict_data_eq_merge_diff(&expected[i], actual)) + return 0; + } + + return 1; +} + +int merge_test_index(git_index *index, const struct merge_index_entry expected[], size_t expected_len) +{ + size_t i; + const git_index_entry *index_entry; + + /* + merge__dump_index_entries(&index->entries); + */ + + if (git_index_entrycount(index) != expected_len) + return 0; + + for (i = 0; i < expected_len; i++) { + if ((index_entry = git_index_get_byindex(index, i)) == NULL) + return 0; + + if (!index_entry_eq_merge_index_entry(&expected[i], index_entry)) + return 0; + } + + return 1; +} + +int merge_test_names(git_index *index, const struct merge_name_entry expected[], size_t expected_len) +{ + size_t i; + const git_index_name_entry *name_entry; + + /* + dump_names(index); + */ + + if (git_index_name_entrycount(index) != expected_len) + return 0; + + for (i = 0; i < expected_len; i++) { + if ((name_entry = git_index_name_get_byindex(index, i)) == NULL) + return 0; + + if (! name_entry_eq_merge_name_entry(&expected[i], name_entry)) + return 0; + } + + return 1; +} + +int merge_test_reuc(git_index *index, const struct merge_reuc_entry expected[], size_t expected_len) +{ + size_t i; + const git_index_reuc_entry *reuc_entry; + git_oid expected_oid; + + /* + dump_reuc(index); + */ + + if (git_index_reuc_entrycount(index) != expected_len) + return 0; + + for (i = 0; i < expected_len; i++) { + if ((reuc_entry = git_index_reuc_get_byindex(index, i)) == NULL) + return 0; + + if (strcmp(reuc_entry->path, expected[i].path) != 0 || + reuc_entry->mode[0] != expected[i].ancestor_mode || + reuc_entry->mode[1] != expected[i].our_mode || + reuc_entry->mode[2] != expected[i].their_mode) + return 0; + + if (expected[i].ancestor_mode > 0) { + cl_git_pass(git_oid_fromstr(&expected_oid, expected[i].ancestor_oid_str)); + + if (git_oid_cmp(&reuc_entry->oid[0], &expected_oid) != 0) + return 0; + } + + if (expected[i].our_mode > 0) { + cl_git_pass(git_oid_fromstr(&expected_oid, expected[i].our_oid_str)); + + if (git_oid_cmp(&reuc_entry->oid[1], &expected_oid) != 0) + return 0; + } + + if (expected[i].their_mode > 0) { + cl_git_pass(git_oid_fromstr(&expected_oid, expected[i].their_oid_str)); + + if (git_oid_cmp(&reuc_entry->oid[2], &expected_oid) != 0) + return 0; + } + } + + return 1; +} + +static int dircount(void *payload, git_str *pathbuf) +{ + size_t *entries = payload; + size_t len = git_str_len(pathbuf); + + if (len < 5 || strcmp(pathbuf->ptr + (git_str_len(pathbuf) - 5), "/.git") != 0) + (*entries)++; + + return 0; +} + +int merge_test_workdir(git_repository *repo, const struct merge_index_entry expected[], size_t expected_len) +{ + size_t actual_len = 0, i; + git_oid actual_oid, expected_oid; + git_str wd = GIT_STR_INIT; + + git_str_puts(&wd, repo->workdir); + git_fs_path_direach(&wd, 0, dircount, &actual_len); + + if (actual_len != expected_len) + return 0; + + for (i = 0; i < expected_len; i++) { + git_blob_create_from_workdir(&actual_oid, repo, expected[i].path); + git_oid_fromstr(&expected_oid, expected[i].oid_str); + + if (git_oid_cmp(&actual_oid, &expected_oid) != 0) + return 0; + } + + git_str_dispose(&wd); + + return 1; +} diff --git a/tests/libgit2/merge/merge_helpers.h b/tests/libgit2/merge/merge_helpers.h new file mode 100644 index 000000000..339812ba5 --- /dev/null +++ b/tests/libgit2/merge/merge_helpers.h @@ -0,0 +1,72 @@ +#ifndef INCLUDE_cl_merge_helpers_h__ +#define INCLUDE_cl_merge_helpers_h__ + +#include "merge.h" +#include "git2/merge.h" + +struct merge_index_entry { + uint16_t mode; + char oid_str[GIT_OID_HEXSZ+1]; + int stage; + char path[128]; +}; + +struct merge_name_entry { + char ancestor_path[128]; + char our_path[128]; + char their_path[128]; +}; + +struct merge_index_with_status { + struct merge_index_entry entry; + unsigned int status; +}; + +struct merge_reuc_entry { + char path[128]; + unsigned int ancestor_mode; + unsigned int our_mode; + unsigned int their_mode; + char ancestor_oid_str[GIT_OID_HEXSZ+1]; + char our_oid_str[GIT_OID_HEXSZ+1]; + char their_oid_str[GIT_OID_HEXSZ+1]; +}; + +struct merge_index_conflict_data { + struct merge_index_with_status ancestor; + struct merge_index_with_status ours; + struct merge_index_with_status theirs; + git_merge_diff_t change_type; +}; + +int merge_trees_from_branches( + git_index **index, git_repository *repo, + const char *ours_name, const char *theirs_name, + git_merge_options *opts); + +int merge_commits_from_branches( + git_index **index, git_repository *repo, + const char *ours_name, const char *theirs_name, + git_merge_options *opts); + +int merge_branches(git_repository *repo, + const char *ours_branch, const char *theirs_branch, + git_merge_options *merge_opts, git_checkout_options *checkout_opts); + +int merge_test_diff_list(git_merge_diff_list *diff_list, const struct merge_index_entry expected[], size_t expected_len); + +int merge_test_merge_conflicts(git_vector *conflicts, const struct merge_index_conflict_data expected[], size_t expected_len); + +int merge_test_index(git_index *index, const struct merge_index_entry expected[], size_t expected_len); + +int merge_test_names(git_index *index, const struct merge_name_entry expected[], size_t expected_len); + +int merge_test_reuc(git_index *index, const struct merge_reuc_entry expected[], size_t expected_len); + +int merge_test_workdir(git_repository *repo, const struct merge_index_entry expected[], size_t expected_len); + +void merge__dump_names(git_index *index); +void merge__dump_index_entries(git_vector *index_entries); +void merge__dump_reuc(git_index *index); + +#endif diff --git a/tests/libgit2/merge/trees/automerge.c b/tests/libgit2/merge/trees/automerge.c new file mode 100644 index 000000000..3bf6c5208 --- /dev/null +++ b/tests/libgit2/merge/trees/automerge.c @@ -0,0 +1,195 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "futils.h" +#include "../merge_helpers.h" +#include "../conflict_data.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-resolve" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +#define THEIRS_AUTOMERGE_BRANCH "branch" + +#define THEIRS_UNRELATED_BRANCH "unrelated" +#define THEIRS_UNRELATED_OID "55b4e4687e7a0d9ca367016ed930f385d4022e6f" +#define THEIRS_UNRELATED_PARENT "d6cf6c7741b3316826af1314042550c97ded1d50" + +#define OURS_DIRECTORY_FILE "df_side1" +#define THEIRS_DIRECTORY_FILE "df_side2" + +/* Non-conflicting files, index entries are common to every merge operation */ +#define ADDED_IN_MASTER_INDEX_ENTRY \ + { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" } +#define AUTOMERGEABLE_INDEX_ENTRY \ + { 0100644, "f2e1550a0c9e53d5811175864a29536642ae3821", 0, "automergeable.txt" } +#define CHANGED_IN_BRANCH_INDEX_ENTRY \ + { 0100644, "4eb04c9e79e88f6640d01ff5b25ca2a60764f216", 0, "changed-in-branch.txt" } +#define CHANGED_IN_MASTER_INDEX_ENTRY \ + { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" } +#define UNCHANGED_INDEX_ENTRY \ + { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" } + +/* Expected REUC entries */ +#define AUTOMERGEABLE_REUC_ENTRY \ + { "automergeable.txt", 0100644, 0100644, 0100644, \ + "6212c31dab5e482247d7977e4f0dd3601decf13b", \ + "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", \ + "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe" } +#define CONFLICTING_REUC_ENTRY \ + { "conflicting.txt", 0100644, 0100644, 0100644, \ + "d427e0b2e138501a3d15cc376077a3631e15bd46", \ + "4e886e602529caa9ab11d71f86634bd1b6e0de10", \ + "2bd0a343aeef7a2cf0d158478966a6e587ff3863" } +#define REMOVED_IN_BRANCH_REUC_ENTRY \ + { "removed-in-branch.txt", 0100644, 0100644, 0, \ + "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ + "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ + "" } +#define REMOVED_IN_MASTER_REUC_ENTRY \ + { "removed-in-master.txt", 0100644, 0, 0100644, \ + "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", \ + "", \ + "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5" } + +/* Fixture setup and teardown */ +void test_merge_trees_automerge__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_merge_trees_automerge__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_merge_trees_automerge__automerge(void) +{ + git_index *index; + const git_index_entry *entry; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + git_blob *blob; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + cl_git_pass(merge_trees_from_branches(&index, repo, "master", THEIRS_AUTOMERGE_BRANCH, &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(index, merge_reuc_entries, 3)); + + cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL); + cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE)); + + cl_git_pass(git_object_lookup((git_object **)&blob, repo, &entry->id, GIT_OBJECT_BLOB)); + cl_assert(memcmp(git_blob_rawcontent(blob), AUTOMERGEABLE_MERGED_FILE, (size_t)entry->file_size) == 0); + + git_index_free(index); + git_blob_free(blob); +} + +void test_merge_trees_automerge__favor_ours(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + CONFLICTING_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY, + }; + + opts.file_favor = GIT_MERGE_FILE_FAVOR_OURS; + + cl_git_pass(merge_trees_from_branches(&index, repo, "master", THEIRS_AUTOMERGE_BRANCH, &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + cl_assert(merge_test_reuc(index, merge_reuc_entries, 4)); + + git_index_free(index); +} + +void test_merge_trees_automerge__favor_theirs(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 0, "conflicting.txt" }, + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + CONFLICTING_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY, + }; + + opts.file_favor = GIT_MERGE_FILE_FAVOR_THEIRS; + + cl_git_pass(merge_trees_from_branches(&index, repo, "master", THEIRS_AUTOMERGE_BRANCH, &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + cl_assert(merge_test_reuc(index, merge_reuc_entries, 4)); + + git_index_free(index); +} + +void test_merge_trees_automerge__unrelated(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, + { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 2, "automergeable.txt" }, + { 0100644, "d07ec190c306ec690bac349e87d01c4358e49bb2", 3, "automergeable.txt" }, + { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, + { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "4b253da36a0ae8bfce63aeabd8c5b58429925594", 3, "conflicting.txt" }, + { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" }, + { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" }, + { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, + { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, + }; + + cl_git_pass(merge_trees_from_branches(&index, repo, "master", THEIRS_UNRELATED_BRANCH, &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 11)); + + git_index_free(index); +} diff --git a/tests/libgit2/merge/trees/commits.c b/tests/libgit2/merge/trees/commits.c new file mode 100644 index 000000000..79fba8ac5 --- /dev/null +++ b/tests/libgit2/merge/trees/commits.c @@ -0,0 +1,148 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "../conflict_data.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-resolve" + +void test_merge_trees_commits__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_merge_trees_commits__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_merge_trees_commits__automerge(void) +{ + git_index *index; + const git_index_entry *entry; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + git_blob *blob; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, + { 0100644, "f2e1550a0c9e53d5811175864a29536642ae3821", 0, "automergeable.txt" }, + { 0100644, "4eb04c9e79e88f6640d01ff5b25ca2a60764f216", 0, "changed-in-branch.txt" }, + { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + { "automergeable.txt", 0100644, 0100644, 0100644, \ + "6212c31dab5e482247d7977e4f0dd3601decf13b", \ + "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", \ + "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe" }, + { "removed-in-branch.txt", 0100644, 0100644, 0, \ + "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ + "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ + "" }, + { "removed-in-master.txt", 0100644, 0, 0100644, \ + "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", \ + "", \ + "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "master", "branch", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(index, merge_reuc_entries, 3)); + + cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL); + cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE)); + + cl_git_pass(git_object_lookup((git_object **)&blob, repo, &entry->id, GIT_OBJECT_BLOB)); + cl_assert(memcmp(git_blob_rawcontent(blob), AUTOMERGEABLE_MERGED_FILE, (size_t)entry->file_size) == 0); + + git_index_free(index); + git_blob_free(blob); +} + +void test_merge_trees_commits__no_ancestor(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, + { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 2, "automergeable.txt" }, + { 0100644, "d07ec190c306ec690bac349e87d01c4358e49bb2", 3, "automergeable.txt" }, + { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, + { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "4b253da36a0ae8bfce63aeabd8c5b58429925594", 3, "conflicting.txt" }, + { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" }, + { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" }, + { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, + { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "master", "unrelated", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 11)); + + git_index_free(index); +} + +void test_merge_trees_commits__df_conflict(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "49130a28ef567af9a6a6104c38773fedfa5f9742", 2, "dir-10" }, + { 0100644, "6c06dcd163587c2cc18be44857e0b71116382aeb", 3, "dir-10" }, + { 0100644, "43aafd43bea779ec74317dc361f45ae3f532a505", 0, "dir-6" }, + { 0100644, "a031a28ae70e33a641ce4b8a8f6317f1ab79dee4", 3, "dir-7" }, + { 0100644, "5012fd565b1393bdfda1805d4ec38ce6619e1fd1", 1, "dir-7/file.txt" }, + { 0100644, "a5563304ddf6caba25cb50323a2ea6f7dbfcadca", 2, "dir-7/file.txt" }, + { 0100644, "e9ad6ec3e38364a3d07feda7c4197d4d845c53b5", 0, "dir-8" }, + { 0100644, "3ef4d30382ca33fdeba9fda895a99e0891ba37aa", 2, "dir-9" }, + { 0100644, "fc4c636d6515e9e261f9260dbcf3cc6eca97ea08", 1, "dir-9/file.txt" }, + { 0100644, "76ab0e2868197ec158ddd6c78d8a0d2fd73d38f9", 3, "dir-9/file.txt" }, + { 0100644, "5c2411f8075f48a6b2fdb85ebc0d371747c4df15", 0, "file-1/new" }, + { 0100644, "a39a620dae5bc8b4e771cd4d251b7d080401a21e", 1, "file-2" }, + { 0100644, "d963979c237d08b6ba39062ee7bf64c7d34a27f8", 2, "file-2" }, + { 0100644, "5c341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d", 0, "file-2/new" }, + { 0100644, "9efe7723802d4305142eee177e018fee1572c4f4", 0, "file-3/new" }, + { 0100644, "bacac9b3493509aa15e1730e1545fc0919d1dae0", 1, "file-4" }, + { 0100644, "7663fce0130db092936b137cabd693ec234eb060", 3, "file-4" }, + { 0100644, "e49f917b448d1340b31d76e54ba388268fd4c922", 0, "file-4/new" }, + { 0100644, "cab2cf23998b40f1af2d9d9a756dc9e285a8df4b", 2, "file-5/new" }, + { 0100644, "f5504f36e6f4eb797a56fc5bac6c6c7f32969bf2", 3, "file-5/new" }, + }; + + cl_git_pass(merge_trees_from_branches(&index, repo, "df_side1", "df_side2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 20)); + + git_index_free(index); +} + +void test_merge_trees_commits__fail_on_conflict(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + opts.flags |= GIT_MERGE_FAIL_ON_CONFLICT; + + cl_git_fail_with(GIT_EMERGECONFLICT, + merge_trees_from_branches(&index, repo, "df_side1", "df_side2", &opts)); + + cl_git_fail_with(GIT_EMERGECONFLICT, + merge_commits_from_branches(&index, repo, "master", "unrelated", &opts)); + cl_git_fail_with(GIT_EMERGECONFLICT, + merge_commits_from_branches(&index, repo, "master", "branch", &opts)); +} + diff --git a/tests/libgit2/merge/trees/modeconflict.c b/tests/libgit2/merge/trees/modeconflict.c new file mode 100644 index 000000000..a0521c4e8 --- /dev/null +++ b/tests/libgit2/merge/trees/modeconflict.c @@ -0,0 +1,58 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "futils.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-resolve" + +#define DF_SIDE1_BRANCH "df_side1" +#define DF_SIDE2_BRANCH "df_side2" + +/* Fixture setup and teardown */ +void test_merge_trees_modeconflict__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_merge_trees_modeconflict__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_merge_trees_modeconflict__df_conflict(void) +{ + git_index *index; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "49130a28ef567af9a6a6104c38773fedfa5f9742", 2, "dir-10" }, + { 0100644, "6c06dcd163587c2cc18be44857e0b71116382aeb", 3, "dir-10" }, + { 0100644, "43aafd43bea779ec74317dc361f45ae3f532a505", 0, "dir-6" }, + { 0100644, "a031a28ae70e33a641ce4b8a8f6317f1ab79dee4", 3, "dir-7" }, + { 0100644, "5012fd565b1393bdfda1805d4ec38ce6619e1fd1", 1, "dir-7/file.txt" }, + { 0100644, "a5563304ddf6caba25cb50323a2ea6f7dbfcadca", 2, "dir-7/file.txt" }, + { 0100644, "e9ad6ec3e38364a3d07feda7c4197d4d845c53b5", 0, "dir-8" }, + { 0100644, "3ef4d30382ca33fdeba9fda895a99e0891ba37aa", 2, "dir-9" }, + { 0100644, "fc4c636d6515e9e261f9260dbcf3cc6eca97ea08", 1, "dir-9/file.txt" }, + { 0100644, "76ab0e2868197ec158ddd6c78d8a0d2fd73d38f9", 3, "dir-9/file.txt" }, + { 0100644, "5c2411f8075f48a6b2fdb85ebc0d371747c4df15", 0, "file-1/new" }, + { 0100644, "a39a620dae5bc8b4e771cd4d251b7d080401a21e", 1, "file-2" }, + { 0100644, "d963979c237d08b6ba39062ee7bf64c7d34a27f8", 2, "file-2" }, + { 0100644, "5c341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d", 0, "file-2/new" }, + { 0100644, "9efe7723802d4305142eee177e018fee1572c4f4", 0, "file-3/new" }, + { 0100644, "bacac9b3493509aa15e1730e1545fc0919d1dae0", 1, "file-4" }, + { 0100644, "7663fce0130db092936b137cabd693ec234eb060", 3, "file-4" }, + { 0100644, "e49f917b448d1340b31d76e54ba388268fd4c922", 0, "file-4/new" }, + { 0100644, "cab2cf23998b40f1af2d9d9a756dc9e285a8df4b", 2, "file-5/new" }, + { 0100644, "f5504f36e6f4eb797a56fc5bac6c6c7f32969bf2", 3, "file-5/new" }, + }; + + cl_git_pass(merge_trees_from_branches(&index, repo, DF_SIDE1_BRANCH, DF_SIDE2_BRANCH, NULL)); + + cl_assert(merge_test_index(index, merge_index_entries, 20)); + + git_index_free(index); +} diff --git a/tests/libgit2/merge/trees/recursive.c b/tests/libgit2/merge/trees/recursive.c new file mode 100644 index 000000000..71f5af150 --- /dev/null +++ b/tests/libgit2/merge/trees/recursive.c @@ -0,0 +1,458 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "../merge_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_merge_trees_recursive__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_merge_trees_recursive__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_merge_trees_recursive__one_base_commit(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "dea7215f259b2cced87d1bda6c72f8b4ce37a2ff", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchA-1", "branchA-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +void test_merge_trees_recursive__one_base_commit_norecursive(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "dea7215f259b2cced87d1bda6c72f8b4ce37a2ff", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + + opts.flags |= GIT_MERGE_NO_RECURSIVE; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchA-1", "branchA-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +void test_merge_trees_recursive__two_base_commits(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "666ffdfcf1eaa5641fa31064bf2607327e843c09", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchB-1", "branchB-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +void test_merge_trees_recursive__two_base_commits_norecursive(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "cb49ad76147f5f9439cbd6133708b76142660660", 1, "veal.txt" }, + { 0100644, "b2a81ead9e722af0099fccfb478cea88eea749a2", 2, "veal.txt" }, + { 0100644, "4e21d2d63357bde5027d1625f5ec6b430cdeb143", 3, "veal.txt" }, + }; + + opts.flags |= GIT_MERGE_NO_RECURSIVE; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchB-1", "branchB-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +void test_merge_trees_recursive__two_levels_of_multiple_bases(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "15faa0c9991f2d65686e844651faa2ff9827887b", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchC-1", "branchC-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +void test_merge_trees_recursive__two_levels_of_multiple_bases_norecursive(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "b2a81ead9e722af0099fccfb478cea88eea749a2", 1, "veal.txt" }, + { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 2, "veal.txt" }, + { 0100644, "68a2e1ee61a23a4728fe6b35580fbbbf729df370", 3, "veal.txt" }, + }; + + opts.flags |= GIT_MERGE_NO_RECURSIVE; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchC-1", "branchC-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +void test_merge_trees_recursive__three_levels_of_multiple_bases(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "d55e5dc038c52f1a36548625bcb666cbc06db9e6", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchD-2", "branchD-1", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +void test_merge_trees_recursive__three_levels_of_multiple_bases_norecursive(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 1, "veal.txt" }, + { 0100644, "f1b44c04989a3a1c14b036cfadfa328d53a7bc5e", 2, "veal.txt" }, + { 0100644, "5e8747f5200fac0f945a07daf6163ca9cb1a8da9", 3, "veal.txt" }, + }; + + opts.flags |= GIT_MERGE_NO_RECURSIVE; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchD-2", "branchD-1", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +void test_merge_trees_recursive__three_base_commits(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4f7269b07c76d02755d75ccaf05c0b4c36cdc6c", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchE-1", "branchE-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +void test_merge_trees_recursive__three_base_commits_norecursive(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "9e12bce04446d097ae1782967a5888c2e2a0d35b", 1, "gravy.txt" }, + { 0100644, "d8dd349b78f19a4ebe3357bacb8138f00bf5ed41", 2, "gravy.txt" }, + { 0100644, "e50fbbd701458757bdfe9815f58ed717c588d1b5", 3, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + + opts.flags |= GIT_MERGE_NO_RECURSIVE; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchE-1", "branchE-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +void test_merge_trees_recursive__conflict(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "fa567f568ed72157c0c617438d077695b99d9aac", 1, "veal.txt" }, + { 0100644, "21950d5e4e4d1a871b4dfcf72ecb6b9c162c434e", 2, "veal.txt" }, + { 0100644, "3855170cef875708da06ab9ad7fc6a73b531cda1", 3, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchF-1", "branchF-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +/* + * Branch G-1 and G-2 have three common ancestors (815b5a1, ad2ace9, 483065d). + * The merge-base of the first two has two common ancestors (723181f, a34e5a1) + * which themselves have two common ancestors (8f35f30, 3a3f5a6), which + * finally has a common ancestor of 7c7bf85. This virtual merge base will + * be computed and merged with 483065d which also has a common ancestor of + * 7c7bf85. + */ +void test_merge_trees_recursive__oh_so_many_levels_of_recursion(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "7c7e08f9559d9e1551b91e1cf68f1d0066109add", 0, "oyster.txt" }, + { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchG-1", "branchG-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +/* Branch H-1 and H-2 have two common ancestors (aa9e263, 6ef31d3). The two + * ancestors themselves conflict. + */ +void test_merge_trees_recursive__conflicting_merge_base(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "cfc01b0976122eae42a82064440bbf534eddd7a0", 1, "veal.txt" }, + { 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 2, "veal.txt" }, + { 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 3, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchH-1", "branchH-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +/* Branch H-1 and H-2 have two common ancestors (aa9e263, 6ef31d3). The two + * ancestors themselves conflict. The generated common ancestor file will + * have diff3 style conflicts inside it. + */ +void test_merge_trees_recursive__conflicting_merge_base_with_diff3(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "0b01d2f70a1c6b9ab60c382f3f9cdc8173da6736", 1, "veal.txt" }, + { 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 2, "veal.txt" }, + { 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 3, "veal.txt" }, + }; + + opts.file_flags |= GIT_MERGE_FILE_STYLE_DIFF3; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchH-2", "branchH-1", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +/* Branch I-1 and I-2 have two common ancestors (aa9e263, 6ef31d3). The two + * ancestors themselves conflict, but when each was merged, the conflicts were + * resolved identically, thus merging I-1 into I-2 does not conflict. + */ +void test_merge_trees_recursive__conflicting_merge_base_since_resolved(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a02d4fd126e0cc8fb46ee48cf38bad36d44f2dbc", 0, "veal.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchI-1", "branchI-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); +} + +/* There are multiple levels of criss-cross merges, and multiple recursive + * merges would create a common ancestor that allows the merge to complete + * successfully. Test that we can build a single virtual base, then stop, + * which will produce a conflicting merge. + */ +void test_merge_trees_recursive__recursionlimit(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "53217e8ac3f52bccf7603b8fff0ed0f4817f9bb7", 1, "veal.txt" }, + { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 2, "veal.txt" }, + { 0100644, "68a2e1ee61a23a4728fe6b35580fbbbf729df370", 3, "veal.txt" }, + }; + + opts.recursion_limit = 1; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchC-1", "branchC-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + git_index_free(index); +} + +/* There are multiple levels of criss-cross merges. This ensures + * that the virtual merge base parents are compared in the same + * order as git. If the base parents are created in the order as + * git does, then the file `targetfile.txt` is automerged. If not, + * `targetfile.txt` will be in conflict due to the virtual merge + * base. + */ +void test_merge_trees_recursive__merge_base_for_virtual_commit(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "1bde1883de4977ea3e664b315da951d1f614c3b1", 0, "targetfile.txt" }, + { 0100644, "b7de2b52ba055688061355fad1599a5d214ce8f8", 1, "version.txt" }, + { 0100644, "358efd6f589384fa8baf92234db9c7899a53916e", 2, "version.txt" }, + { 0100644, "a664873b1c0b9a1ed300f8644dde536fdaa3a34f", 3, "version.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchJ-1", "branchJ-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 4)); + + git_index_free(index); +} + +/* This test is the same as above, but the graph is constructed such + * that the 1st-recursion merge bases of the two heads are + * in a different order. + */ +void test_merge_trees_recursive__merge_base_for_virtual_commit_2(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "4a06b258fed8a4d15967ec4253ae7366b70f727d", 0, "targetfile.txt" }, + { 0100644, "b6bd0f9952f396e757d3f91e08c59a7e91707201", 1, "version.txt" }, + { 0100644, "f0856993e005c0d8ed2dc7cdc222cc1d89fb3c77", 2, "version.txt" }, + { 0100644, "2cba583804a4a6fad1baf97c959be447238d1489", 3, "version.txt" }, + }; + + cl_git_pass(merge_commits_from_branches(&index, repo, "branchK-1", "branchK-2", &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 4)); + + git_index_free(index); +} diff --git a/tests/libgit2/merge/trees/renames.c b/tests/libgit2/merge/trees/renames.c new file mode 100644 index 000000000..26f6d3306 --- /dev/null +++ b/tests/libgit2/merge/trees/renames.c @@ -0,0 +1,352 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "futils.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-resolve" + +#define BRANCH_RENAME_OURS "rename_conflict_ours" +#define BRANCH_RENAME_THEIRS "rename_conflict_theirs" + +/* Fixture setup and teardown */ +void test_merge_trees_renames__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_merge_trees_renames__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_merge_trees_renames__index(void) +{ + git_index *index; + git_merge_options *opts = NULL; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" }, + { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" }, + { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" }, + { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" }, + { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" }, + { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, + { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, + { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, + { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, + { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 2, "3a-newname-in-ours-deleted-in-theirs.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 1, "3a-renamed-in-ours-deleted-in-theirs.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 3, "3b-newname-in-theirs-deleted-in-ours.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 1, "3b-renamed-in-theirs-deleted-in-ours.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 1, "4a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 1, "4b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 1, "5a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 3, "5a-renamed-in-ours-added-in-theirs.txt" }, + { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 1, "5b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 2, "5b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 2, "6-both-renamed-1-to-2-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 3, "6-both-renamed-1-to-2-theirs.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 1, "6-both-renamed-1-to-2.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "7-both-renamed-side-1.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "7-both-renamed-side-1.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "7-both-renamed-side-2.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "7-both-renamed-side-2.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" }, + }; + + struct merge_name_entry merge_name_entries[] = { + { + "3a-renamed-in-ours-deleted-in-theirs.txt", + "3a-newname-in-ours-deleted-in-theirs.txt", + "" + }, + + { + "3b-renamed-in-theirs-deleted-in-ours.txt", + "", + "3b-newname-in-theirs-deleted-in-ours.txt", + }, + + { + "4a-renamed-in-ours-added-in-theirs.txt", + "4a-newname-in-ours-added-in-theirs.txt", + "", + }, + + { + "4b-renamed-in-theirs-added-in-ours.txt", + "", + "4b-newname-in-theirs-added-in-ours.txt", + }, + + { + "5a-renamed-in-ours-added-in-theirs.txt", + "5a-newname-in-ours-added-in-theirs.txt", + "5a-renamed-in-ours-added-in-theirs.txt", + }, + + { + "5b-renamed-in-theirs-added-in-ours.txt", + "5b-renamed-in-theirs-added-in-ours.txt", + "5b-newname-in-theirs-added-in-ours.txt", + }, + + { + "6-both-renamed-1-to-2.txt", + "6-both-renamed-1-to-2-ours.txt", + "6-both-renamed-1-to-2-theirs.txt", + }, + + { + "7-both-renamed-side-1.txt", + "7-both-renamed.txt", + "7-both-renamed-side-1.txt", + }, + + { + "7-both-renamed-side-2.txt", + "7-both-renamed-side-2.txt", + "7-both-renamed.txt", + }, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + { "1a-newname-in-ours-edited-in-theirs.txt", + 0, 0100644, 0, + "", + "c3d02eeef75183df7584d8d13ac03053910c1301", + "" }, + + { "1a-newname-in-ours.txt", + 0, 0100644, 0, + "", + "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", + "" }, + + { "1a-renamed-in-ours-edited-in-theirs.txt", + 0100644, 0, 0100644, + "c3d02eeef75183df7584d8d13ac03053910c1301", + "", + "0d872f8e871a30208305978ecbf9e66d864f1638" }, + + { "1a-renamed-in-ours.txt", + 0100644, 0, 0100644, + "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", + "", + "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb" }, + + { "1b-newname-in-theirs-edited-in-ours.txt", + 0, 0, 0100644, + "", + "", + "241a1005cd9b980732741b74385b891142bcba28" }, + + { "1b-newname-in-theirs.txt", + 0, 0, 0100644, + "", + "", + "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136" }, + + { "1b-renamed-in-theirs-edited-in-ours.txt", + 0100644, 0100644, 0, + "241a1005cd9b980732741b74385b891142bcba28", + "ed9523e62e453e50dd9be1606af19399b96e397a", + "" }, + + { "1b-renamed-in-theirs.txt", + 0100644, 0100644, 0, + "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", + "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", + "" }, + + { "2-newname-in-both.txt", + 0, 0100644, 0100644, + "", + "178940b450f238a56c0d75b7955cb57b38191982", + "178940b450f238a56c0d75b7955cb57b38191982" }, + + { "2-renamed-in-both.txt", + 0100644, 0, 0, + "178940b450f238a56c0d75b7955cb57b38191982", + "", + "" }, + }; + + cl_git_pass(merge_trees_from_branches(&index, repo, + BRANCH_RENAME_OURS, BRANCH_RENAME_THEIRS, + opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 41)); + cl_assert(merge_test_names(index, merge_name_entries, 9)); + cl_assert(merge_test_reuc(index, merge_reuc_entries, 10)); + + git_index_free(index); +} + +void test_merge_trees_renames__no_rename_index(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" }, + { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" }, + { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" }, + { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" }, + { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" }, + { 0100644, "c3d02eeef75183df7584d8d13ac03053910c1301", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, + { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, + { 0100644, "c3d02eeef75183df7584d8d13ac03053910c1301", 1, "1a-renamed-in-ours-edited-in-theirs.txt" }, + { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 3, "1a-renamed-in-ours-edited-in-theirs.txt" }, + { 0100644, "241a1005cd9b980732741b74385b891142bcba28", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, + { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, + { 0100644, "241a1005cd9b980732741b74385b891142bcba28", 1, "1b-renamed-in-theirs-edited-in-ours.txt" }, + { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 2, "1b-renamed-in-theirs-edited-in-ours.txt" }, + { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-theirs.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" }, + }; + + opts.flags &= ~GIT_MERGE_FIND_RENAMES; + + cl_git_pass(merge_trees_from_branches(&index, repo, + BRANCH_RENAME_OURS, BRANCH_RENAME_THEIRS, + &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 32)); + + git_index_free(index); +} + +void test_merge_trees_renames__submodules(void) +{ + git_index *index; + git_merge_options *opts = NULL; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "cd3e8d4aa06bdc781f264171030bc28f2b370fee", 0, ".gitmodules" }, + { 0100644, "4dd1ef7569b18d92d93c0a35bb6b93049137b355", 1, "file.txt" }, + { 0100644, "a2d8d1824c68541cca94ffb90f79291eba495921", 2, "file.txt" }, + { 0100644, "63ec604d491161ddafdae4179843c26d54bd999a", 3, "file.txt" }, + { 0160000, "0000000000000000000000000000000000000001", 1, "submodule1" }, + { 0160000, "0000000000000000000000000000000000000002", 3, "submodule1" }, + { 0160000, "0000000000000000000000000000000000000003", 0, "submodule2" }, + }; + + cl_git_pass(merge_trees_from_branches(&index, repo, + "submodule_rename1", "submodule_rename2", + opts)); + cl_assert(merge_test_index(index, merge_index_entries, 7)); + git_index_free(index); +} + +void test_merge_trees_renames__cache_recomputation(void) +{ + git_oid blob, binary, ancestor_oid, theirs_oid, ours_oid; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + git_str path = GIT_STR_INIT; + git_treebuilder *builder; + git_tree *ancestor_tree, *their_tree, *our_tree; + git_index *index; + size_t blob_size; + void *data; + size_t i; + + cl_git_pass(git_oid_fromstr(&blob, "a2d8d1824c68541cca94ffb90f79291eba495921")); + + /* + * Create a 50MB blob that consists of NUL bytes only. It is important + * that this blob is of a special format, most importantly it cannot + * contain more than four non-consecutive newlines or NUL bytes. This + * is because of git_hashsig's inner workings where all files with less + * than four "lines" are deemed to small. + */ + blob_size = 50 * 1024 * 1024; + cl_assert(data = git__calloc(blob_size, 1)); + cl_git_pass(git_blob_create_from_buffer(&binary, repo, data, blob_size)); + + /* + * Create the common ancestor, which has 1000 dummy blobs and the binary + * blob. The dummy blobs serve as potential rename targets for the + * dummy blob. + */ + cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); + for (i = 0; i < 1000; i++) { + cl_git_pass(git_str_printf(&path, "%"PRIuZ".txt", i)); + cl_git_pass(git_treebuilder_insert(NULL, builder, path.ptr, &blob, GIT_FILEMODE_BLOB)); + git_str_clear(&path); + } + cl_git_pass(git_treebuilder_insert(NULL, builder, "original.bin", &binary, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_write(&ancestor_oid, builder)); + + /* We now the binary blob in our tree. */ + cl_git_pass(git_treebuilder_remove(builder, "original.bin")); + cl_git_pass(git_treebuilder_insert(NULL, builder, "renamed.bin", &binary, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_write(&ours_oid, builder)); + + git_treebuilder_free(builder); + + /* And move everything into a subdirectory in their tree. */ + cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); + cl_git_pass(git_treebuilder_insert(NULL, builder, "subdir", &ancestor_oid, GIT_FILEMODE_TREE)); + cl_git_pass(git_treebuilder_write(&theirs_oid, builder)); + + /* + * Now merge ancestor, ours and theirs. As `git_hashsig` refuses to + * create a hash signature for the 50MB binary file, we historically + * didn't cache the hashsig computation for it. As a result, we now + * started looking up the 50MB blob and scanning it at least 1000 + * times, which takes a long time. + * + * The number of 1000 blobs is chosen in such a way that it's + * noticeable when the bug creeps in again, as it takes around 12 + * minutes on my machine to compute the following merge. + */ + opts.target_limit = 5000; + cl_git_pass(git_tree_lookup(&ancestor_tree, repo, &ancestor_oid)); + cl_git_pass(git_tree_lookup(&their_tree, repo, &theirs_oid)); + cl_git_pass(git_tree_lookup(&our_tree, repo, &ours_oid)); + cl_git_pass(git_merge_trees(&index, repo, ancestor_tree, our_tree, their_tree, &opts)); + + git_treebuilder_free(builder); + git_str_dispose(&path); + git_index_free(index); + git_tree_free(ancestor_tree); + git_tree_free(their_tree); + git_tree_free(our_tree); + git__free(data); +} diff --git a/tests/libgit2/merge/trees/treediff.c b/tests/libgit2/merge/trees/treediff.c new file mode 100644 index 000000000..cd2cf7827 --- /dev/null +++ b/tests/libgit2/merge/trees/treediff.c @@ -0,0 +1,555 @@ +#include "clar_libgit2.h" +#include "git2/tree.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "diff.h" +#include "diff_tform.h" +#include "git2/sys/hashsig.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-resolve" + +#define TREE_OID_ANCESTOR "0d52e3a556e189ba0948ae56780918011c1b167d" +#define TREE_OID_MASTER "1f81433e3161efbf250576c58fede7f6b836f3d3" +#define TREE_OID_BRANCH "eea9286df54245fea72c5b557291470eb825f38f" +#define TREE_OID_RENAMES1 "f5f9dd5886a6ee20272be0aafc790cba43b31931" +#define TREE_OID_RENAMES2 "5fbfbdc04b4eca46f54f4853a3c5a1dce28f5165" + +#define TREE_OID_DF_ANCESTOR "b8a3a806d3950e8c0a03a34f234a92eff0e2c68d" +#define TREE_OID_DF_SIDE1 "ee1d6f164893c1866a323f072eeed36b855656be" +#define TREE_OID_DF_SIDE2 "6178885b38fe96e825ac0f492c0a941f288b37f6" + +#define TREE_OID_RENAME_CONFLICT_ANCESTOR "476dbb3e207313d1d8aaa120c6ad204bf1295e53" +#define TREE_OID_RENAME_CONFLICT_OURS "c4efe31e9decccc8b2b4d3df9aac2cdfe2995618" +#define TREE_OID_RENAME_CONFLICT_THEIRS "9e7f4359c469f309b6057febf4c6e80742cbed5b" + +void test_merge_trees_treediff__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_merge_trees_treediff__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void test_find_differences( + const char *ancestor_oidstr, + const char *ours_oidstr, + const char *theirs_oidstr, + struct merge_index_conflict_data *treediff_conflict_data, + size_t treediff_conflict_data_len) +{ + git_merge_diff_list *merge_diff_list = git_merge_diff_list__alloc(repo); + git_oid ancestor_oid, ours_oid, theirs_oid; + git_tree *ancestor_tree, *ours_tree, *theirs_tree; + git_iterator *ancestor_iter, *ours_iter, *theirs_iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + opts.flags |= GIT_MERGE_FIND_RENAMES; + opts.target_limit = 1000; + opts.rename_threshold = 50; + + opts.metric = git__malloc(sizeof(git_diff_similarity_metric)); + cl_assert(opts.metric != NULL); + + opts.metric->file_signature = git_diff_find_similar__hashsig_for_file; + opts.metric->buffer_signature = git_diff_find_similar__hashsig_for_buf; + opts.metric->free_signature = git_diff_find_similar__hashsig_free; + opts.metric->similarity = git_diff_find_similar__calc_similarity; + opts.metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE; + + cl_git_pass(git_oid_fromstr(&ancestor_oid, ancestor_oidstr)); + cl_git_pass(git_oid_fromstr(&ours_oid, ours_oidstr)); + cl_git_pass(git_oid_fromstr(&theirs_oid, theirs_oidstr)); + + cl_git_pass(git_tree_lookup(&ancestor_tree, repo, &ancestor_oid)); + cl_git_pass(git_tree_lookup(&ours_tree, repo, &ours_oid)); + cl_git_pass(git_tree_lookup(&theirs_tree, repo, &theirs_oid)); + + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_tree(&ancestor_iter, ancestor_tree, &iter_opts)); + cl_git_pass(git_iterator_for_tree(&ours_iter, ours_tree, &iter_opts)); + cl_git_pass(git_iterator_for_tree(&theirs_iter, theirs_tree, &iter_opts)); + + cl_git_pass(git_merge_diff_list__find_differences(merge_diff_list, ancestor_iter, ours_iter, theirs_iter)); + cl_git_pass(git_merge_diff_list__find_renames(repo, merge_diff_list, &opts)); + + /* + dump_merge_index(merge_index); + */ + + cl_assert(treediff_conflict_data_len == merge_diff_list->conflicts.length); + + cl_assert(merge_test_merge_conflicts(&merge_diff_list->conflicts, treediff_conflict_data, treediff_conflict_data_len)); + + git_iterator_free(ancestor_iter); + git_iterator_free(ours_iter); + git_iterator_free(theirs_iter); + + git_tree_free(ancestor_tree); + git_tree_free(ours_tree); + git_tree_free(theirs_tree); + + git_merge_diff_list__free(merge_diff_list); + + git__free(opts.metric); +} + +void test_merge_trees_treediff__simple(void) +{ + struct merge_index_conflict_data treediff_conflict_data[] = { + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, GIT_DELTA_ADDED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE + }, + + { + { { 0100644, "6212c31dab5e482247d7977e4f0dd3601decf13b", 0, "automergeable.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" }, GIT_DELTA_MODIFIED }, + { { 0100644, "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe", 0, "automergeable.txt" }, GIT_DELTA_MODIFIED }, + GIT_MERGE_DIFF_BOTH_MODIFIED + }, + + { + { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "4eb04c9e79e88f6640d01ff5b25ca2a60764f216", 0, "changed-in-branch.txt" }, GIT_DELTA_MODIFIED }, + GIT_MERGE_DIFF_NONE + }, + + { + { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, GIT_DELTA_MODIFIED }, + { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE + }, + + { + { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, GIT_DELTA_MODIFIED }, + { { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 0, "conflicting.txt" }, GIT_DELTA_MODIFIED }, + GIT_MERGE_DIFF_BOTH_MODIFIED + }, + + { + { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + GIT_MERGE_DIFF_NONE + }, + + { + { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE + }, + }; + + test_find_differences(TREE_OID_ANCESTOR, TREE_OID_MASTER, TREE_OID_BRANCH, treediff_conflict_data, 7); +} + +void test_merge_trees_treediff__df_conflicts(void) +{ + struct merge_index_conflict_data treediff_conflict_data[] = { + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "49130a28ef567af9a6a6104c38773fedfa5f9742", 0, "dir-10" }, GIT_DELTA_ADDED }, + { { 0100644, "6c06dcd163587c2cc18be44857e0b71116382aeb", 0, "dir-10" }, GIT_DELTA_ADDED }, + GIT_MERGE_DIFF_BOTH_ADDED, + }, + + { + { { 0100644, "242591eb280ee9eeb2ce63524b9a8b9bc4cb515d", 0, "dir-10/file.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + GIT_MERGE_DIFF_BOTH_DELETED, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "43aafd43bea779ec74317dc361f45ae3f532a505", 0, "dir-6" }, GIT_DELTA_ADDED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "cf8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d", 0, "dir-6/file.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "cf8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d", 0, "dir-6/file.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "a031a28ae70e33a641ce4b8a8f6317f1ab79dee4", 0, "dir-7" }, GIT_DELTA_ADDED }, + GIT_MERGE_DIFF_DIRECTORY_FILE, + }, + + { + { { 0100644, "5012fd565b1393bdfda1805d4ec38ce6619e1fd1", 0, "dir-7/file.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "a5563304ddf6caba25cb50323a2ea6f7dbfcadca", 0, "dir-7/file.txt" }, GIT_DELTA_MODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + GIT_MERGE_DIFF_DF_CHILD, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "e9ad6ec3e38364a3d07feda7c4197d4d845c53b5", 0, "dir-8" }, GIT_DELTA_ADDED }, + { {0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "f20c9063fa0bda9a397c96947a7b687305c49753", 0, "dir-8/file.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + { { 0100644, "f20c9063fa0bda9a397c96947a7b687305c49753", 0, "dir-8/file.txt" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "3ef4d30382ca33fdeba9fda895a99e0891ba37aa", 0, "dir-9" }, GIT_DELTA_ADDED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_DIRECTORY_FILE, + }, + + { + { { 0100644, "fc4c636d6515e9e261f9260dbcf3cc6eca97ea08", 0, "dir-9/file.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + { { 0100644, "76ab0e2868197ec158ddd6c78d8a0d2fd73d38f9", 0, "dir-9/file.txt" }, GIT_DELTA_MODIFIED }, + GIT_MERGE_DIFF_DF_CHILD, + }, + + { + { { 0100644, "1e4ff029aee68d0d69ef9eb6efa6cbf1ec732f99", 0, "file-1" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "1e4ff029aee68d0d69ef9eb6efa6cbf1ec732f99", 0, "file-1" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "5c2411f8075f48a6b2fdb85ebc0d371747c4df15", 0, "file-1/new" }, GIT_DELTA_ADDED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "a39a620dae5bc8b4e771cd4d251b7d080401a21e", 0, "file-2" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "d963979c237d08b6ba39062ee7bf64c7d34a27f8", 0, "file-2" }, GIT_DELTA_MODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + GIT_MERGE_DIFF_DIRECTORY_FILE, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "5c341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d", 0, "file-2/new" }, GIT_DELTA_ADDED }, + GIT_MERGE_DIFF_DF_CHILD, + }, + + { + { { 0100644, "032ebc5ab85d9553bb187d3cd40875ff23a63ed0", 0, "file-3" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + { { 0100644, "032ebc5ab85d9553bb187d3cd40875ff23a63ed0", 0, "file-3" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "9efe7723802d4305142eee177e018fee1572c4f4", 0, "file-3/new" }, GIT_DELTA_ADDED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "bacac9b3493509aa15e1730e1545fc0919d1dae0", 0, "file-4" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + { { 0100644, "7663fce0130db092936b137cabd693ec234eb060", 0, "file-4" }, GIT_DELTA_MODIFIED }, + GIT_MERGE_DIFF_DIRECTORY_FILE, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "e49f917b448d1340b31d76e54ba388268fd4c922", 0, "file-4/new" }, GIT_DELTA_ADDED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_DF_CHILD, + }, + + { + { { 0100644, "ac4045f965119e6998f4340ed0f411decfb3ec05", 0, "file-5" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + GIT_MERGE_DIFF_BOTH_DELETED, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "cab2cf23998b40f1af2d9d9a756dc9e285a8df4b", 0, "file-5/new" }, GIT_DELTA_ADDED }, + { { 0100644, "f5504f36e6f4eb797a56fc5bac6c6c7f32969bf2", 0, "file-5/new" }, GIT_DELTA_ADDED }, + GIT_MERGE_DIFF_BOTH_ADDED, + }, + }; + + test_find_differences(TREE_OID_DF_ANCESTOR, TREE_OID_DF_SIDE1, TREE_OID_DF_SIDE2, treediff_conflict_data, 20); +} + +void test_merge_trees_treediff__strict_renames(void) +{ + struct merge_index_conflict_data treediff_conflict_data[] = { + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, GIT_DELTA_ADDED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "6212c31dab5e482247d7977e4f0dd3601decf13b", 0, "automergeable.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" }, GIT_DELTA_MODIFIED }, + { { 0100644, "6212c31dab5e482247d7977e4f0dd3601decf13b", 0, "automergeable.txt" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, GIT_DELTA_MODIFIED }, + { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, GIT_DELTA_MODIFIED }, + { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "renamed-in-branch.txt" }, GIT_DELTA_RENAMED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "renamed.txt" }, GIT_DELTA_ADDED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "copied.txt" }, GIT_DELTA_RENAMED }, + GIT_MERGE_DIFF_NONE, + }, + }; + + test_find_differences(TREE_OID_ANCESTOR, TREE_OID_MASTER, TREE_OID_RENAMES1, treediff_conflict_data, 8); +} + +void test_merge_trees_treediff__rename_conflicts(void) +{ + struct merge_index_conflict_data treediff_conflict_data[] = { + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, GIT_DELTA_ADDED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-rewritten-in-ours.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 0, "0b-rewritten-in-ours.txt" }, GIT_DELTA_MODIFIED }, + { { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 0, "0b-rewritten-in-ours.txt" }, GIT_DELTA_MODIFIED }, + GIT_MERGE_DIFF_BOTH_MODIFIED, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, GIT_DELTA_ADDED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-rewritten-in-theirs.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 0, "0c-rewritten-in-theirs.txt" }, GIT_DELTA_MODIFIED }, + { { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 0, "0c-rewritten-in-theirs.txt" }, GIT_DELTA_MODIFIED }, + GIT_MERGE_DIFF_BOTH_MODIFIED, + }, + + { + { { 0100644, "c3d02eeef75183df7584d8d13ac03053910c1301", 0, "1a-renamed-in-ours-edited-in-theirs.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "c3d02eeef75183df7584d8d13ac03053910c1301", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, GIT_DELTA_RENAMED }, + { { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-renamed-in-ours-edited-in-theirs.txt" }, GIT_DELTA_MODIFIED }, + GIT_MERGE_DIFF_RENAMED_MODIFIED, + }, + + { + { { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-renamed-in-ours.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, GIT_DELTA_RENAMED }, + { { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-renamed-in-ours.txt" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "241a1005cd9b980732741b74385b891142bcba28", 0, "1b-renamed-in-theirs-edited-in-ours.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-renamed-in-theirs-edited-in-ours.txt" }, GIT_DELTA_MODIFIED }, + { { 0100644, "241a1005cd9b980732741b74385b891142bcba28", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, GIT_DELTA_RENAMED }, + GIT_MERGE_DIFF_RENAMED_MODIFIED, + }, + + { + { { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-renamed-in-theirs.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-renamed-in-theirs.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, GIT_DELTA_RENAMED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-renamed-in-both.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, GIT_DELTA_RENAMED }, + { { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, GIT_DELTA_RENAMED }, + GIT_MERGE_DIFF_BOTH_RENAMED, + }, + + { + { { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-renamed-in-ours-deleted-in-theirs.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, GIT_DELTA_RENAMED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + GIT_MERGE_DIFF_RENAMED_DELETED, + }, + + { + { { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-renamed-in-theirs-deleted-in-ours.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + { { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, GIT_DELTA_RENAMED }, + GIT_MERGE_DIFF_RENAMED_DELETED, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 0, "4a-newname-in-ours-added-in-theirs.txt" }, GIT_DELTA_ADDED }, + GIT_MERGE_DIFF_RENAMED_ADDED, + }, + + { + { { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-renamed-in-ours-added-in-theirs.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt" }, GIT_DELTA_RENAMED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + GIT_MERGE_DIFF_RENAMED_ADDED, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt" }, GIT_DELTA_ADDED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_RENAMED_ADDED, + }, + + { + { { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-renamed-in-theirs-added-in-ours.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + { { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-newname-in-theirs-added-in-ours.txt" }, GIT_DELTA_RENAMED }, + GIT_MERGE_DIFF_RENAMED_ADDED, + }, + + { + { { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "5-both-renamed-1-to-2.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "5-both-renamed-1-to-2-ours.txt" }, GIT_DELTA_RENAMED }, + { { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "5-both-renamed-1-to-2-theirs.txt" }, GIT_DELTA_RENAMED }, + GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2, + }, + + { + { { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "6-both-renamed-side-1.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "6-both-renamed.txt" }, GIT_DELTA_RENAMED }, + { { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "6-both-renamed-side-1.txt" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1, + }, + + { + { { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "6-both-renamed-side-2.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "6-both-renamed-side-2.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "6-both-renamed.txt" }, GIT_DELTA_RENAMED }, + GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1, + }, + }; + test_find_differences(TREE_OID_RENAME_CONFLICT_ANCESTOR, + TREE_OID_RENAME_CONFLICT_OURS, TREE_OID_RENAME_CONFLICT_THEIRS, treediff_conflict_data, 18); +} + +void test_merge_trees_treediff__best_renames(void) +{ + struct merge_index_conflict_data treediff_conflict_data[] = { + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, GIT_DELTA_ADDED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "6212c31dab5e482247d7977e4f0dd3601decf13b", 0, "automergeable.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" }, GIT_DELTA_MODIFIED }, + { { 0100644, "45299c1ca5e07bba1fd90843056fb559f96b1f5a", 0, "renamed-90.txt" }, GIT_DELTA_RENAMED }, + GIT_MERGE_DIFF_RENAMED_MODIFIED, + }, + + { + { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, GIT_DELTA_MODIFIED }, + { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, GIT_DELTA_MODIFIED }, + { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" },GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_DELETED }, + { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, + GIT_MERGE_DIFF_MODIFIED_DELETED, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "5843febcb23480df0b5edb22a21c59c772bb8e29", 0, "renamed-50.txt" }, GIT_DELTA_ADDED }, + GIT_MERGE_DIFF_NONE, + }, + + { + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, + { { 0100644, "a77a56a49f8f3ae242e02717f18ebbc60c5cc543", 0, "renamed-75.txt" }, GIT_DELTA_ADDED }, + GIT_MERGE_DIFF_NONE, + }, + }; + + test_find_differences(TREE_OID_ANCESTOR, TREE_OID_MASTER, TREE_OID_RENAMES2, treediff_conflict_data, 7); +} diff --git a/tests/libgit2/merge/trees/trivial.c b/tests/libgit2/merge/trees/trivial.c new file mode 100644 index 000000000..dce392c86 --- /dev/null +++ b/tests/libgit2/merge/trees/trivial.c @@ -0,0 +1,306 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "refs.h" +#include "futils.h" +#include "git2/sys/index.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-resolve" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + + +/* Fixture setup and teardown */ +void test_merge_trees_trivial__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_merge_trees_trivial__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + +static int merge_trivial(git_index **index, const char *ours, const char *theirs) +{ + git_commit *our_commit, *their_commit, *ancestor_commit; + git_tree *our_tree, *their_tree, *ancestor_tree; + git_oid our_oid, their_oid, ancestor_oid; + git_str branch_buf = GIT_STR_INIT; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours); + cl_git_pass(git_reference_name_to_id(&our_oid, repo, branch_buf.ptr)); + cl_git_pass(git_commit_lookup(&our_commit, repo, &our_oid)); + + git_str_clear(&branch_buf); + git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, theirs); + cl_git_pass(git_reference_name_to_id(&their_oid, repo, branch_buf.ptr)); + cl_git_pass(git_commit_lookup(&their_commit, repo, &their_oid)); + + cl_git_pass(git_merge_base(&ancestor_oid, repo, git_commit_id(our_commit), git_commit_id(their_commit))); + cl_git_pass(git_commit_lookup(&ancestor_commit, repo, &ancestor_oid)); + + cl_git_pass(git_commit_tree(&ancestor_tree, ancestor_commit)); + cl_git_pass(git_commit_tree(&our_tree, our_commit)); + cl_git_pass(git_commit_tree(&their_tree, their_commit)); + + cl_git_pass(git_merge_trees(index, repo, ancestor_tree, our_tree, their_tree, &opts)); + + git_str_dispose(&branch_buf); + git_tree_free(our_tree); + git_tree_free(their_tree); + git_tree_free(ancestor_tree); + git_commit_free(our_commit); + git_commit_free(their_commit); + git_commit_free(ancestor_commit); + + return 0; +} + +static int merge_trivial_conflict_entrycount(git_index *index) +{ + const git_index_entry *entry; + int count = 0; + size_t i; + + for (i = 0; i < git_index_entrycount(index); i++) { + cl_assert(entry = git_index_get_byindex(index, i)); + + if (git_index_entry_is_conflict(entry)) + count++; + } + + return count; +} + +/* 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote */ +void test_merge_trees_trivial__2alt(void) +{ + git_index *result; + const git_index_entry *entry; + + cl_git_pass(merge_trivial(&result, "trivial-2alt", "trivial-2alt-branch")); + + cl_assert(entry = git_index_get_bypath(result, "new-in-branch.txt", 0)); + cl_assert(git_index_reuc_entrycount(result) == 0); + cl_assert(merge_trivial_conflict_entrycount(result) == 0); + + git_index_free(result); +} + +/* 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head */ +void test_merge_trees_trivial__3alt(void) +{ + git_index *result; + const git_index_entry *entry; + + cl_git_pass(merge_trivial(&result, "trivial-3alt", "trivial-3alt-branch")); + + cl_assert(entry = git_index_get_bypath(result, "new-in-3alt.txt", 0)); + cl_assert(git_index_reuc_entrycount(result) == 0); + cl_assert(merge_trivial_conflict_entrycount(result) == 0); + + git_index_free(result); +} + +/* 4: ancest:(empty)^, head:head, remote:remote = result:no merge */ +void test_merge_trees_trivial__4(void) +{ + git_index *result; + const git_index_entry *entry; + + cl_git_pass(merge_trivial(&result, "trivial-4", "trivial-4-branch")); + + cl_assert((entry = git_index_get_bypath(result, "new-and-different.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(result) == 0); + + cl_assert(merge_trivial_conflict_entrycount(result) == 2); + cl_assert(entry = git_index_get_bypath(result, "new-and-different.txt", 2)); + cl_assert(entry = git_index_get_bypath(result, "new-and-different.txt", 3)); + + git_index_free(result); +} + +/* 5ALT: ancest:*, head:head, remote:head = result:head */ +void test_merge_trees_trivial__5alt_1(void) +{ + git_index *result; + const git_index_entry *entry; + + cl_git_pass(merge_trivial(&result, "trivial-5alt-1", "trivial-5alt-1-branch")); + + cl_assert(entry = git_index_get_bypath(result, "new-and-same.txt", 0)); + cl_assert(git_index_reuc_entrycount(result) == 0); + cl_assert(merge_trivial_conflict_entrycount(result) == 0); + + git_index_free(result); +} + +/* 5ALT: ancest:*, head:head, remote:head = result:head */ +void test_merge_trees_trivial__5alt_2(void) +{ + git_index *result; + const git_index_entry *entry; + + cl_git_pass(merge_trivial(&result, "trivial-5alt-2", "trivial-5alt-2-branch")); + + cl_assert(entry = git_index_get_bypath(result, "modified-to-same.txt", 0)); + cl_assert(git_index_reuc_entrycount(result) == 0); + cl_assert(merge_trivial_conflict_entrycount(result) == 0); + + git_index_free(result); +} + +/* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */ +void test_merge_trees_trivial__6(void) +{ + git_index *result; + const git_index_entry *entry; + const git_index_reuc_entry *reuc; + + cl_git_pass(merge_trivial(&result, "trivial-6", "trivial-6-branch")); + + cl_assert((entry = git_index_get_bypath(result, "removed-in-both.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(result) == 1); + cl_assert(reuc = git_index_reuc_get_bypath(result, "removed-in-both.txt")); + + cl_assert(merge_trivial_conflict_entrycount(result) == 0); + + git_index_free(result); +} + +/* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */ +void test_merge_trees_trivial__8(void) +{ + git_index *result; + const git_index_entry *entry; + const git_index_reuc_entry *reuc; + + cl_git_pass(merge_trivial(&result, "trivial-8", "trivial-8-branch")); + + cl_assert((entry = git_index_get_bypath(result, "removed-in-8.txt", 0)) == NULL); + + cl_assert(git_index_reuc_entrycount(result) == 1); + cl_assert(reuc = git_index_reuc_get_bypath(result, "removed-in-8.txt")); + + cl_assert(merge_trivial_conflict_entrycount(result) == 0); + + git_index_free(result); +} + +/* 7: ancest:ancest+, head:(empty), remote:remote = result:no merge */ +void test_merge_trees_trivial__7(void) +{ + git_index *result; + const git_index_entry *entry; + + cl_git_pass(merge_trivial(&result, "trivial-7", "trivial-7-branch")); + + cl_assert((entry = git_index_get_bypath(result, "removed-in-7.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(result) == 0); + + cl_assert(merge_trivial_conflict_entrycount(result) == 2); + cl_assert(entry = git_index_get_bypath(result, "removed-in-7.txt", 1)); + cl_assert(entry = git_index_get_bypath(result, "removed-in-7.txt", 3)); + + git_index_free(result); +} + +/* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */ +void test_merge_trees_trivial__10(void) +{ + git_index *result; + const git_index_entry *entry; + const git_index_reuc_entry *reuc; + + cl_git_pass(merge_trivial(&result, "trivial-10", "trivial-10-branch")); + + cl_assert((entry = git_index_get_bypath(result, "removed-in-10-branch.txt", 0)) == NULL); + + cl_assert(git_index_reuc_entrycount(result) == 1); + cl_assert(reuc = git_index_reuc_get_bypath(result, "removed-in-10-branch.txt")); + + cl_assert(merge_trivial_conflict_entrycount(result) == 0); + + git_index_free(result); +} + +/* 9: ancest:ancest+, head:head, remote:(empty) = result:no merge */ +void test_merge_trees_trivial__9(void) +{ + git_index *result; + const git_index_entry *entry; + + cl_git_pass(merge_trivial(&result, "trivial-9", "trivial-9-branch")); + + cl_assert((entry = git_index_get_bypath(result, "removed-in-9-branch.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(result) == 0); + + cl_assert(merge_trivial_conflict_entrycount(result) == 2); + cl_assert(entry = git_index_get_bypath(result, "removed-in-9-branch.txt", 1)); + cl_assert(entry = git_index_get_bypath(result, "removed-in-9-branch.txt", 2)); + + git_index_free(result); +} + +/* 13: ancest:ancest+, head:head, remote:ancest = result:head */ +void test_merge_trees_trivial__13(void) +{ + git_index *result; + const git_index_entry *entry; + git_oid expected_oid; + + cl_git_pass(merge_trivial(&result, "trivial-13", "trivial-13-branch")); + + cl_assert(entry = git_index_get_bypath(result, "modified-in-13.txt", 0)); + cl_git_pass(git_oid_fromstr(&expected_oid, "1cff9ec6a47a537380dedfdd17c9e76d74259a2b")); + cl_assert_equal_oid(&expected_oid, &entry->id); + + cl_assert(git_index_reuc_entrycount(result) == 0); + cl_assert(merge_trivial_conflict_entrycount(result) == 0); + + git_index_free(result); +} + +/* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */ +void test_merge_trees_trivial__14(void) +{ + git_index *result; + const git_index_entry *entry; + git_oid expected_oid; + + cl_git_pass(merge_trivial(&result, "trivial-14", "trivial-14-branch")); + + cl_assert(entry = git_index_get_bypath(result, "modified-in-14-branch.txt", 0)); + cl_git_pass(git_oid_fromstr(&expected_oid, "26153a3ff3649b6c2bb652d3f06878c6e0a172f9")); + cl_assert(git_oid_cmp(&entry->id, &expected_oid) == 0); + + cl_assert(git_index_reuc_entrycount(result) == 0); + cl_assert(merge_trivial_conflict_entrycount(result) == 0); + + git_index_free(result); +} + +/* 11: ancest:ancest+, head:head, remote:remote = result:no merge */ +void test_merge_trees_trivial__11(void) +{ + git_index *result; + const git_index_entry *entry; + + cl_git_pass(merge_trivial(&result, "trivial-11", "trivial-11-branch")); + + cl_assert((entry = git_index_get_bypath(result, "modified-in-both.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(result) == 0); + + cl_assert(merge_trivial_conflict_entrycount(result) == 3); + cl_assert(entry = git_index_get_bypath(result, "modified-in-both.txt", 1)); + cl_assert(entry = git_index_get_bypath(result, "modified-in-both.txt", 2)); + cl_assert(entry = git_index_get_bypath(result, "modified-in-both.txt", 3)); + + git_index_free(result); +} diff --git a/tests/libgit2/merge/trees/whitespace.c b/tests/libgit2/merge/trees/whitespace.c new file mode 100644 index 000000000..9917df506 --- /dev/null +++ b/tests/libgit2/merge/trees/whitespace.c @@ -0,0 +1,81 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "futils.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-whitespace" + +#define BRANCH_A_EOL "branch_a_eol" +#define BRANCH_B_EOL "branch_b_eol" + +#define BRANCH_A_CHANGE "branch_a_change" +#define BRANCH_B_CHANGE "branch_b_change" + +/* Fixture setup and teardown */ +void test_merge_trees_whitespace__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_merge_trees_whitespace__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_merge_trees_whitespace__conflict(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "4026a6c83f39c56881c9ac62e7582db9e3d33a4f", 1, "test.txt" }, + { 0100644, "c3b1fb31424c98072542cc8e42b48c92e52f494a", 2, "test.txt" }, + { 0100644, "262f67de0de2e535a59ae1bc3c739601e98c354d", 3, "test.txt" }, + }; + + cl_git_pass(merge_trees_from_branches(&index, repo, BRANCH_A_EOL, BRANCH_B_EOL, &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 3)); + + git_index_free(index); +} + +void test_merge_trees_whitespace__eol(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ee3c2aac8e03224c323b58ecb1f9eef616745467", 0, "test.txt" }, + }; + + opts.file_flags |= GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL; + + cl_git_pass(merge_trees_from_branches(&index, repo, BRANCH_A_EOL, BRANCH_B_EOL, &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 1)); + + git_index_free(index); +} + +void test_merge_trees_whitespace__change(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "a827eab4fd66ab37a6ebcfaa7b7e341abfd55947", 0, "test.txt" }, + }; + + opts.file_flags |= GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE; + + cl_git_pass(merge_trees_from_branches(&index, repo, BRANCH_A_CHANGE, BRANCH_B_CHANGE, &opts)); + + cl_assert(merge_test_index(index, merge_index_entries, 1)); + + git_index_free(index); +} diff --git a/tests/libgit2/merge/workdir/dirty.c b/tests/libgit2/merge/workdir/dirty.c new file mode 100644 index 000000000..b9c2ad033 --- /dev/null +++ b/tests/libgit2/merge/workdir/dirty.c @@ -0,0 +1,351 @@ +#include "clar_libgit2.h" +#include "git2/merge.h" +#include "merge.h" +#include "index.h" +#include "../merge_helpers.h" +#include "posix.h" + +#define TEST_REPO_PATH "merge-resolve" +#define MERGE_BRANCH_OID "7cb63eed597130ba4abb87b3e544b85021905520" + +#define AUTOMERGEABLE_MERGED_FILE \ + "this file is changed in master\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is changed in branch\n" + +#define CHANGED_IN_BRANCH_FILE \ + "changed in branch\n" + +static git_repository *repo; +static git_index *repo_index; + +static char *unaffected[][4] = { + { "added-in-master.txt", NULL }, + { "changed-in-master.txt", NULL }, + { "unchanged.txt", NULL }, + { "added-in-master.txt", "changed-in-master.txt", NULL }, + { "added-in-master.txt", "unchanged.txt", NULL }, + { "changed-in-master.txt", "unchanged.txt", NULL }, + { "added-in-master.txt", "changed-in-master.txt", "unchanged.txt", NULL }, + { "new_file.txt", NULL }, + { "new_file.txt", "unchanged.txt", NULL }, + { NULL }, +}; + +static char *affected[][5] = { + { "automergeable.txt", NULL }, + { "changed-in-branch.txt", NULL }, + { "conflicting.txt", NULL }, + { "removed-in-branch.txt", NULL }, + { "automergeable.txt", "changed-in-branch.txt", NULL }, + { "automergeable.txt", "conflicting.txt", NULL }, + { "automergeable.txt", "removed-in-branch.txt", NULL }, + { "changed-in-branch.txt", "conflicting.txt", NULL }, + { "changed-in-branch.txt", "removed-in-branch.txt", NULL }, + { "conflicting.txt", "removed-in-branch.txt", NULL }, + { "automergeable.txt", "changed-in-branch.txt", "conflicting.txt", NULL }, + { "automergeable.txt", "changed-in-branch.txt", "removed-in-branch.txt", NULL }, + { "automergeable.txt", "conflicting.txt", "removed-in-branch.txt", NULL }, + { "changed-in-branch.txt", "conflicting.txt", "removed-in-branch.txt", NULL }, + { "automergeable.txt", "changed-in-branch.txt", "conflicting.txt", "removed-in-branch.txt", NULL }, + { NULL }, +}; + +static char *result_contents[4][6] = { + { "automergeable.txt", AUTOMERGEABLE_MERGED_FILE, NULL, NULL }, + { "changed-in-branch.txt", CHANGED_IN_BRANCH_FILE, NULL, NULL }, + { "automergeable.txt", AUTOMERGEABLE_MERGED_FILE, "changed-in-branch.txt", CHANGED_IN_BRANCH_FILE, NULL, NULL }, + { NULL } +}; + +void test_merge_workdir_dirty__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); +} + +void test_merge_workdir_dirty__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +static void set_core_autocrlf_to(git_repository *repo, bool value) +{ + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", value)); + + git_config_free(cfg); +} + +static int merge_branch(void) +{ + git_oid their_oids[1]; + git_annotated_commit *their_head; + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + int error; + + cl_git_pass(git_oid_fromstr(&their_oids[0], MERGE_BRANCH_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_head, repo, &their_oids[0])); + + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + error = git_merge(repo, (const git_annotated_commit **)&their_head, 1, &merge_opts, &checkout_opts); + + git_annotated_commit_free(their_head); + + return error; +} + +static void write_files(char *files[]) +{ + char *filename; + git_str path = GIT_STR_INIT, content = GIT_STR_INIT; + size_t i; + + for (i = 0, filename = files[i]; filename; filename = files[++i]) { + git_str_clear(&path); + git_str_clear(&content); + + git_str_printf(&path, "%s/%s", TEST_REPO_PATH, filename); + git_str_printf(&content, "This is a dirty file in the working directory!\n\n" + "It will not be staged! Its filename is %s.\n", filename); + + cl_git_mkfile(path.ptr, content.ptr); + } + + git_str_dispose(&path); + git_str_dispose(&content); +} + +static void hack_index(char *files[]) +{ + char *filename; + struct stat statbuf; + git_str path = GIT_STR_INIT; + git_index_entry *entry; + struct p_timeval times[2]; + time_t now; + size_t i; + + /* Update the index to suggest that checkout placed these files on + * disk, keeping the object id but updating the cache, which will + * emulate a Git implementation's different filter. + * + * We set the file's timestamp to before now to pretend that + * it was an old checkout so we don't trigger the racy + * protections would would check the content. + */ + + now = time(NULL); + times[0].tv_sec = now - 5; + times[0].tv_usec = 0; + times[1].tv_sec = now - 5; + times[1].tv_usec = 0; + + for (i = 0, filename = files[i]; filename; filename = files[++i]) { + git_str_clear(&path); + + cl_assert(entry = (git_index_entry *) + git_index_get_bypath(repo_index, filename, 0)); + + cl_git_pass(git_str_printf(&path, "%s/%s", TEST_REPO_PATH, filename)); + cl_git_pass(p_utimes(path.ptr, times)); + cl_git_pass(p_stat(path.ptr, &statbuf)); + + entry->ctime.seconds = (int32_t)statbuf.st_ctime; + entry->mtime.seconds = (int32_t)statbuf.st_mtime; +#if defined(GIT_USE_NSEC) + entry->ctime.nanoseconds = statbuf.st_ctime_nsec; + entry->mtime.nanoseconds = statbuf.st_mtime_nsec; +#else + entry->ctime.nanoseconds = 0; + entry->mtime.nanoseconds = 0; +#endif + entry->dev = statbuf.st_dev; + entry->ino = statbuf.st_ino; + entry->uid = statbuf.st_uid; + entry->gid = statbuf.st_gid; + entry->file_size = (uint32_t)statbuf.st_size; + } + + git_str_dispose(&path); +} + +static void stage_random_files(char *files[]) +{ + char *filename; + size_t i; + + write_files(files); + + for (i = 0, filename = files[i]; filename; filename = files[++i]) + cl_git_pass(git_index_add_bypath(repo_index, filename)); +} + +static void stage_content(char *content[]) +{ + git_reference *head; + git_object *head_object; + git_str path = GIT_STR_INIT; + char *filename, *text; + size_t i; + + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT)); + cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); + + for (i = 0, filename = content[i], text = content[++i]; + filename && text; + filename = content[++i], text = content[++i]) { + + git_str_clear(&path); + + cl_git_pass(git_str_printf(&path, "%s/%s", TEST_REPO_PATH, filename)); + + cl_git_mkfile(path.ptr, text); + cl_git_pass(git_index_add_bypath(repo_index, filename)); + } + + git_object_free(head_object); + git_reference_free(head); + git_str_dispose(&path); +} + +static int merge_dirty_files(char *dirty_files[]) +{ + git_reference *head; + git_object *head_object; + int error; + + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT)); + cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); + + write_files(dirty_files); + + error = merge_branch(); + + git_object_free(head_object); + git_reference_free(head); + + return error; +} + +static int merge_differently_filtered_files(char *files[]) +{ + git_reference *head; + git_object *head_object; + int error; + + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT)); + cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); + + /* Emulate checkout with a broken or misconfigured filter: modify some + * files on-disk and then update the index with the updated file size + * and time, as if some filter applied them. These files should not be + * treated as dirty since we created them. + * + * (Make sure to update the index stamp to defeat racy-git protections + * trying to sanity check the files in the index; those would rehash the + * files, showing them as dirty, the exact mechanism we're trying to avoid.) + */ + + write_files(files); + hack_index(files); + + cl_git_pass(git_index_write(repo_index)); + + error = merge_branch(); + + git_object_free(head_object); + git_reference_free(head); + + return error; +} + +static int merge_staged_files(char *staged_files[]) +{ + stage_random_files(staged_files); + return merge_branch(); +} + +void test_merge_workdir_dirty__unaffected_dirty_files_allowed(void) +{ + char **files; + size_t i; + + for (i = 0, files = unaffected[i]; files[0]; files = unaffected[++i]) + cl_git_pass(merge_dirty_files(files)); +} + +void test_merge_workdir_dirty__unstaged_deletes_maintained(void) +{ + git_reference *head; + git_object *head_object; + + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT)); + cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); + + cl_git_pass(p_unlink("merge-resolve/unchanged.txt")); + + cl_git_pass(merge_branch()); + + git_object_free(head_object); + git_reference_free(head); +} + +void test_merge_workdir_dirty__affected_dirty_files_disallowed(void) +{ + char **files; + size_t i; + + for (i = 0, files = affected[i]; files[0]; files = affected[++i]) + cl_git_fail(merge_dirty_files(files)); +} + +void test_merge_workdir_dirty__staged_files_in_index_disallowed(void) +{ + char **files; + size_t i; + + for (i = 0, files = unaffected[i]; files[0]; files = unaffected[++i]) + cl_git_fail(merge_staged_files(files)); + + for (i = 0, files = affected[i]; files[0]; files = affected[++i]) + cl_git_fail(merge_staged_files(files)); +} + +void test_merge_workdir_dirty__identical_staged_files_allowed(void) +{ + char **content; + size_t i; + + set_core_autocrlf_to(repo, false); + + for (i = 0, content = result_contents[i]; content[0]; content = result_contents[++i]) { + stage_content(content); + + cl_git_pass(git_index_write(repo_index)); + cl_git_pass(merge_branch()); + } +} + +void test_merge_workdir_dirty__honors_cache(void) +{ + char **files; + size_t i; + + for (i = 0, files = affected[i]; files[0]; files = affected[++i]) + cl_git_pass(merge_differently_filtered_files(files)); +} diff --git a/tests/libgit2/merge/workdir/recursive.c b/tests/libgit2/merge/workdir/recursive.c new file mode 100644 index 000000000..7669e1b1a --- /dev/null +++ b/tests/libgit2/merge/workdir/recursive.c @@ -0,0 +1,84 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "../conflict_data.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_merge_workdir_recursive__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_merge_workdir_recursive__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_merge_workdir_recursive__writes_conflict_with_virtual_base(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + git_str conflicting_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "fa567f568ed72157c0c617438d077695b99d9aac", 1, "veal.txt" }, + { 0100644, "21950d5e4e4d1a871b4dfcf72ecb6b9c162c434e", 2, "veal.txt" }, + { 0100644, "3855170cef875708da06ab9ad7fc6a73b531cda1", 3, "veal.txt" }, + }; + + cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR "branchF-1", GIT_REFS_HEADS_DIR "branchF-2", &opts, NULL)); + + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, "merge-recursive/veal.txt")); + + cl_assert_equal_s(CONFLICTING_RECURSIVE_F1_TO_F2, conflicting_buf.ptr); + + git_index_free(index); + git_str_dispose(&conflicting_buf); +} + +void test_merge_workdir_recursive__conflicting_merge_base_with_diff3(void) +{ + git_index *index; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + git_str conflicting_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "0b01d2f70a1c6b9ab60c382f3f9cdc8173da6736", 1, "veal.txt" }, + { 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 2, "veal.txt" }, + { 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 3, "veal.txt" }, + }; + + opts.file_flags |= GIT_MERGE_FILE_STYLE_DIFF3; + checkout_opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3; + + cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR "branchH-2", GIT_REFS_HEADS_DIR "branchH-1", &opts, &checkout_opts)); + + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(merge_test_index(index, merge_index_entries, 8)); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, "merge-recursive/veal.txt")); + + cl_assert_equal_s(CONFLICTING_RECURSIVE_H2_TO_H1_WITH_DIFF3, conflicting_buf.ptr); + + git_index_free(index); + git_str_dispose(&conflicting_buf); +} diff --git a/tests/libgit2/merge/workdir/renames.c b/tests/libgit2/merge/workdir/renames.c new file mode 100644 index 000000000..1b5128cf1 --- /dev/null +++ b/tests/libgit2/merge/workdir/renames.c @@ -0,0 +1,155 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "futils.h" +#include "refs.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-resolve" + +#define BRANCH_RENAME_OURS "rename_conflict_ours" +#define BRANCH_RENAME_THEIRS "rename_conflict_theirs" + +/* Fixture setup and teardown */ +void test_merge_workdir_renames__initialize(void) +{ + git_config *cfg; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + /* Ensure that the user's merge.conflictstyle doesn't interfere */ + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge")); + git_config_free(cfg); +} + +void test_merge_workdir_renames__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_merge_workdir_renames__renames(void) +{ + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, + { 0100644, "8aac75de2a34b4d340bf62a6e58197269cb55797", 0, "0b-rewritten-in-ours.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, + { 0100644, "7edc726325da726751a4195e434e4377b0f67f9a", 0, "0c-rewritten-in-theirs.txt" }, + { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, + { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, + { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, + { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, + { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt~HEAD" }, + { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 0, "4a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" }, + { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt~HEAD" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 0, "5a-newname-in-ours-added-in-theirs.txt~HEAD" }, + { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 0, "5a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" }, + { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 0, "5b-newname-in-theirs-added-in-ours.txt~HEAD" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 0, "5b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-theirs.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt~HEAD" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" }, + }; + + merge_opts.flags |= GIT_MERGE_FIND_RENAMES; + merge_opts.rename_threshold = 50; + + cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, NULL)); + cl_assert(merge_test_workdir(repo, merge_index_entries, 24)); +} + +void test_merge_workdir_renames__ours(void) +{ + git_index *index; + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, + { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 0, "0b-rewritten-in-ours.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, + { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 0, "0c-rewritten-in-theirs.txt" }, + { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, + { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, + { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, + { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, + { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 0, "5a-newname-in-ours-added-in-theirs.txt" }, + { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 0, "5b-newname-in-theirs-added-in-ours.txt" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 0, "5b-renamed-in-theirs-added-in-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed-side-2.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt" }, + }; + + merge_opts.flags |= GIT_MERGE_FIND_RENAMES; + merge_opts.rename_threshold = 50; + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; + + cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, &checkout_opts)); + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_write(index)); + cl_assert(merge_test_workdir(repo, merge_index_entries, 20)); + + git_index_free(index); +} + +void test_merge_workdir_renames__similar(void) +{ + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + + /* + * Note: this differs slightly from the core git merge result - there, 4a is + * tracked as a rename/delete instead of a rename/add and the theirs side + * is not placed in workdir in any form. + */ + struct merge_index_entry merge_index_entries[] = { + { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, + { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, + { 0100644, "8aac75de2a34b4d340bf62a6e58197269cb55797", 0, "0b-rewritten-in-ours.txt" }, + { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, + { 0100644, "7edc726325da726751a4195e434e4377b0f67f9a", 0, "0c-rewritten-in-theirs.txt" }, + { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, + { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, + { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, + { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, + { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, + { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, + { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, + { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt~HEAD" }, + { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 0, "4a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" }, + { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt~HEAD" }, + { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" }, + { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 0, "5a-newname-in-ours-added-in-theirs.txt~HEAD" }, + { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 0, "5a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" }, + { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 0, "5b-newname-in-theirs-added-in-ours.txt~HEAD" }, + { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 0, "5b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" }, + { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-theirs.txt" }, + { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt~HEAD" }, + { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" }, + }; + + merge_opts.flags |= GIT_MERGE_FIND_RENAMES; + merge_opts.rename_threshold = 50; + + cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, NULL)); + cl_assert(merge_test_workdir(repo, merge_index_entries, 24)); +} + diff --git a/tests/libgit2/merge/workdir/setup.c b/tests/libgit2/merge/workdir/setup.c new file mode 100644 index 000000000..3db2d074f --- /dev/null +++ b/tests/libgit2/merge/workdir/setup.c @@ -0,0 +1,1096 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "refs.h" +#include "futils.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "merge-resolve" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +#define ORIG_HEAD "bd593285fc7fe4ca18ccdbabf027f5d689101452" + +#define THEIRS_SIMPLE_BRANCH "branch" +#define THEIRS_SIMPLE_OID "7cb63eed597130ba4abb87b3e544b85021905520" + +#define OCTO1_BRANCH "octo1" +#define OCTO1_OID "16f825815cfd20a07a75c71554e82d8eede0b061" + +#define OCTO2_BRANCH "octo2" +#define OCTO2_OID "158dc7bedb202f5b26502bf3574faa7f4238d56c" + +#define OCTO3_BRANCH "octo3" +#define OCTO3_OID "50ce7d7d01217679e26c55939eef119e0c93e272" + +#define OCTO4_BRANCH "octo4" +#define OCTO4_OID "54269b3f6ec3d7d4ede24dd350dd5d605495c3ae" + +#define OCTO5_BRANCH "octo5" +#define OCTO5_OID "e4f618a2c3ed0669308735727df5ebf2447f022f" + +/* Fixture setup and teardown */ +void test_merge_workdir_setup__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); +} + +void test_merge_workdir_setup__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +static bool test_file_contents(const char *filename, const char *expected) +{ + git_str file_path_buf = GIT_STR_INIT, file_buf = GIT_STR_INIT; + bool equals; + + git_str_joinpath(&file_path_buf, git_repository_path(repo), filename); + + cl_git_pass(git_futils_readbuffer(&file_buf, file_path_buf.ptr)); + equals = (strcmp(file_buf.ptr, expected) == 0); + + git_str_dispose(&file_path_buf); + git_str_dispose(&file_buf); + + return equals; +} + +static void write_file_contents(const char *filename, const char *output) +{ + git_str file_path_buf = GIT_STR_INIT; + + git_str_joinpath(&file_path_buf, git_repository_path(repo), + filename); + cl_git_rewritefile(file_path_buf.ptr, output); + + git_str_dispose(&file_path_buf); +} + +/* git merge --no-ff octo1 */ +void test_merge_workdir_setup__one_branch(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_annotated_commit *our_head, *their_heads[1]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 1)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'\n")); + + git_reference_free(octo1_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); +} + +/* git merge --no-ff 16f825815cfd20a07a75c71554e82d8eede0b061 */ +void test_merge_workdir_setup__one_oid(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_annotated_commit *our_head, *their_heads[1]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &octo1_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 1)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'\n")); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); +} + +/* git merge octo1 octo2 */ +void test_merge_workdir_setup__two_branches(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_annotated_commit *our_head, *their_heads[2]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 2)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO2_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); +} + +/* git merge octo1 octo2 octo3 */ +void test_merge_workdir_setup__three_branches(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_reference *octo3_ref; + git_annotated_commit *our_head, *their_heads[3]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[2], repo, octo3_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO2_BRANCH "' and '" OCTO3_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + git_reference_free(octo3_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); + git_annotated_commit_free(their_heads[2]); +} + +/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 158dc7bedb202f5b26502bf3574faa7f4238d56c 50ce7d7d01217679e26c55939eef119e0c93e272 */ +void test_merge_workdir_setup__three_oids(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_oid octo2_oid; + git_oid octo3_oid; + git_annotated_commit *our_head, *their_heads[3]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &octo1_oid)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[1], repo, &octo2_oid)); + + cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[2], repo, &octo3_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; commit '" OCTO2_OID "'; commit '" OCTO3_OID "'\n")); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); + git_annotated_commit_free(their_heads[2]); +} + +/* git merge octo1 158dc7bedb202f5b26502bf3574faa7f4238d56c */ +void test_merge_workdir_setup__branches_and_oids_1(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_oid octo2_oid; + git_annotated_commit *our_head, *their_heads[2]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[1], repo, &octo2_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 2)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'; commit '" OCTO2_OID "'\n")); + + git_reference_free(octo1_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); +} + +/* git merge octo1 158dc7bedb202f5b26502bf3574faa7f4238d56c octo3 54269b3f6ec3d7d4ede24dd350dd5d605495c3ae */ +void test_merge_workdir_setup__branches_and_oids_2(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_oid octo2_oid; + git_reference *octo3_ref; + git_oid octo4_oid; + git_annotated_commit *our_head, *their_heads[4]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[1], repo, &octo2_oid)); + + cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[2], repo, octo3_ref)); + + cl_git_pass(git_oid_fromstr(&octo4_oid, OCTO4_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[3], repo, &octo4_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 4)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "'; commit '" OCTO2_OID "'; commit '" OCTO4_OID "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo3_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); + git_annotated_commit_free(their_heads[2]); + git_annotated_commit_free(their_heads[3]); +} + +/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 octo2 50ce7d7d01217679e26c55939eef119e0c93e272 octo4 */ +void test_merge_workdir_setup__branches_and_oids_3(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_reference *octo2_ref; + git_oid octo3_oid; + git_reference *octo4_ref; + git_annotated_commit *our_head, *their_heads[4]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &octo1_oid)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[2], repo, &octo3_oid)); + + cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[3], repo, octo4_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 4)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; branches '" OCTO2_BRANCH "' and '" OCTO4_BRANCH "'; commit '" OCTO3_OID "'\n")); + + git_reference_free(octo2_ref); + git_reference_free(octo4_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); + git_annotated_commit_free(their_heads[2]); + git_annotated_commit_free(their_heads[3]); +} + +/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 octo2 50ce7d7d01217679e26c55939eef119e0c93e272 octo4 octo5 */ +void test_merge_workdir_setup__branches_and_oids_4(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_reference *octo2_ref; + git_oid octo3_oid; + git_reference *octo4_ref; + git_reference *octo5_ref; + git_annotated_commit *our_head, *their_heads[5]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &octo1_oid)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[2], repo, &octo3_oid)); + + cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[3], repo, octo4_ref)); + + cl_git_pass(git_reference_lookup(&octo5_ref, repo, GIT_REFS_HEADS_DIR OCTO5_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[4], repo, octo5_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 5)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n" OCTO5_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; branches '" OCTO2_BRANCH "', '" OCTO4_BRANCH "' and '" OCTO5_BRANCH "'; commit '" OCTO3_OID "'\n")); + + git_reference_free(octo2_ref); + git_reference_free(octo4_ref); + git_reference_free(octo5_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); + git_annotated_commit_free(their_heads[2]); + git_annotated_commit_free(their_heads[3]); + git_annotated_commit_free(their_heads[4]); +} + +/* git merge octo1 octo1 octo1 */ +void test_merge_workdir_setup__three_same_branches(void) +{ + git_oid our_oid; + git_reference *octo1_1_ref; + git_reference *octo1_2_ref; + git_reference *octo1_3_ref; + git_annotated_commit *our_head, *their_heads[3]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_1_ref)); + + cl_git_pass(git_reference_lookup(&octo1_2_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo1_2_ref)); + + cl_git_pass(git_reference_lookup(&octo1_3_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[2], repo, octo1_3_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO1_OID "\n" OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO1_BRANCH "' and '" OCTO1_BRANCH "'\n")); + + git_reference_free(octo1_1_ref); + git_reference_free(octo1_2_ref); + git_reference_free(octo1_3_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); + git_annotated_commit_free(their_heads[2]); +} + +/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 16f825815cfd20a07a75c71554e82d8eede0b061 16f825815cfd20a07a75c71554e82d8eede0b061 */ +void test_merge_workdir_setup__three_same_oids(void) +{ + git_oid our_oid; + git_oid octo1_1_oid; + git_oid octo1_2_oid; + git_oid octo1_3_oid; + git_annotated_commit *our_head, *their_heads[3]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_1_oid, OCTO1_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &octo1_1_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_2_oid, OCTO1_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[1], repo, &octo1_2_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_3_oid, OCTO1_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[2], repo, &octo1_3_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO1_OID "\n" OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; commit '" OCTO1_OID "'; commit '" OCTO1_OID "'\n")); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); + git_annotated_commit_free(their_heads[2]); +} + +static int create_remote_tracking_branch(const char *branch_name, const char *oid_str) +{ + int error = 0; + + git_str remotes_path = GIT_STR_INIT, + origin_path = GIT_STR_INIT, + filename = GIT_STR_INIT, + data = GIT_STR_INIT; + + if ((error = git_str_puts(&remotes_path, git_repository_path(repo))) < 0 || + (error = git_str_puts(&remotes_path, GIT_REFS_REMOTES_DIR)) < 0) + goto done; + + if (!git_fs_path_exists(git_str_cstr(&remotes_path)) && + (error = p_mkdir(git_str_cstr(&remotes_path), 0777)) < 0) + goto done; + + if ((error = git_str_puts(&origin_path, git_str_cstr(&remotes_path))) < 0 || + (error = git_str_puts(&origin_path, "origin")) < 0) + goto done; + + if (!git_fs_path_exists(git_str_cstr(&origin_path)) && + (error = p_mkdir(git_str_cstr(&origin_path), 0777)) < 0) + goto done; + + if ((error = git_str_puts(&filename, git_str_cstr(&origin_path))) < 0 || + (error = git_str_puts(&filename, "/")) < 0 || + (error = git_str_puts(&filename, branch_name)) < 0 || + (error = git_str_puts(&data, oid_str)) < 0 || + (error = git_str_puts(&data, "\n")) < 0) + goto done; + + cl_git_rewritefile(git_str_cstr(&filename), git_str_cstr(&data)); + +done: + git_str_dispose(&remotes_path); + git_str_dispose(&origin_path); + git_str_dispose(&filename); + git_str_dispose(&data); + + return error; +} + +/* git merge refs/remotes/origin/octo1 */ +void test_merge_workdir_setup__remote_tracking_one_branch(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_annotated_commit *our_head, *their_heads[1]; + + cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID)); + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 1)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branch 'refs/remotes/origin/" OCTO1_BRANCH "'\n")); + + git_reference_free(octo1_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); +} + +/* git merge refs/remotes/origin/octo1 refs/remotes/origin/octo2 */ +void test_merge_workdir_setup__remote_tracking_two_branches(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_annotated_commit *our_head, *their_heads[2]; + + cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID)); + cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID)); + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 2)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branches 'refs/remotes/origin/" OCTO1_BRANCH "' and 'refs/remotes/origin/" OCTO2_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); +} + +/* git merge refs/remotes/origin/octo1 refs/remotes/origin/octo2 refs/remotes/origin/octo3 */ +void test_merge_workdir_setup__remote_tracking_three_branches(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_reference *octo3_ref; + git_annotated_commit *our_head, *their_heads[3]; + + cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID)); + cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID)); + cl_git_pass(create_remote_tracking_branch(OCTO3_BRANCH, OCTO3_OID)); + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO3_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[2], repo, octo3_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branches 'refs/remotes/origin/" OCTO1_BRANCH "', 'refs/remotes/origin/" OCTO2_BRANCH "' and 'refs/remotes/origin/" OCTO3_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + git_reference_free(octo3_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); + git_annotated_commit_free(their_heads[2]); +} + +/* git merge octo1 refs/remotes/origin/octo2 */ +void test_merge_workdir_setup__normal_branch_and_remote_tracking_branch(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_annotated_commit *our_head, *their_heads[2]; + + cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID)); + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 2)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "', remote-tracking branch 'refs/remotes/origin/" OCTO2_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); +} + +/* git merge refs/remotes/origin/octo1 octo2 */ +void test_merge_workdir_setup__remote_tracking_branch_and_normal_branch(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_annotated_commit *our_head, *their_heads[2]; + + cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID)); + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 2)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO2_BRANCH "', remote-tracking branch 'refs/remotes/origin/" OCTO1_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); +} + +/* git merge octo1 refs/remotes/origin/octo2 octo3 refs/remotes/origin/octo4 */ +void test_merge_workdir_setup__two_remote_tracking_branch_and_two_normal_branches(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_reference *octo2_ref; + git_reference *octo3_ref; + git_reference *octo4_ref; + git_annotated_commit *our_head, *their_heads[4]; + + cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID)); + cl_git_pass(create_remote_tracking_branch(OCTO4_BRANCH, OCTO4_OID)); + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); + + cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[2], repo, octo3_ref)); + + cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO4_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[3], repo, octo4_ref)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 4)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "', remote-tracking branches 'refs/remotes/origin/" OCTO2_BRANCH "' and 'refs/remotes/origin/" OCTO4_BRANCH "'\n")); + + git_reference_free(octo1_ref); + git_reference_free(octo2_ref); + git_reference_free(octo3_ref); + git_reference_free(octo4_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); + git_annotated_commit_free(their_heads[2]); + git_annotated_commit_free(their_heads[3]); +} + +/* git pull origin branch octo1 */ +void test_merge_workdir_setup__pull_one(void) +{ + git_oid our_oid; + git_oid octo1_1_oid; + git_annotated_commit *our_head, *their_heads[1]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_1_oid, OCTO1_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_1_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 1)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch 'octo1' of http://remote.url/repo.git\n")); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); +} + +/* git pull origin octo1 octo2 */ +void test_merge_workdir_setup__pull_two(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_oid octo2_oid; + git_annotated_commit *our_head, *their_heads[2]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_oid)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.url/repo.git", &octo2_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 2)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO2_BRANCH "' of http://remote.url/repo.git\n")); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); +} + +/* git pull origin octo1 octo2 octo3 */ +void test_merge_workdir_setup__pull_three(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_oid octo2_oid; + git_oid octo3_oid; + git_annotated_commit *our_head, *their_heads[3]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_oid)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.url/repo.git", &octo2_oid)); + + cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.url/repo.git", &octo3_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO2_BRANCH "' and '" OCTO3_BRANCH "' of http://remote.url/repo.git\n")); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); + git_annotated_commit_free(their_heads[2]); +} + +void test_merge_workdir_setup__three_remotes(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_oid octo2_oid; + git_oid octo3_oid; + git_annotated_commit *our_head, *their_heads[3]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.first/repo.git", &octo1_oid)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.second/repo.git", &octo2_oid)); + + cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.third/repo.git", &octo3_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "' of http://remote.first/repo.git, branch '" OCTO2_BRANCH "' of http://remote.second/repo.git, branch '" OCTO3_BRANCH "' of http://remote.third/repo.git\n")); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); + git_annotated_commit_free(their_heads[2]); +} + +void test_merge_workdir_setup__two_remotes(void) +{ + git_oid our_oid; + git_oid octo1_oid; + git_oid octo2_oid; + git_oid octo3_oid; + git_oid octo4_oid; + git_annotated_commit *our_head, *their_heads[4]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.first/repo.git", &octo1_oid)); + + cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.second/repo.git", &octo2_oid)); + + cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.first/repo.git", &octo3_oid)); + + cl_git_pass(git_oid_fromstr(&octo4_oid, OCTO4_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[3], repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH, "http://remote.second/repo.git", &octo4_oid)); + + cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 4)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "' of http://remote.first/repo.git, branches '" OCTO2_BRANCH "' and '" OCTO4_BRANCH "' of http://remote.second/repo.git\n")); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); + git_annotated_commit_free(their_heads[1]); + git_annotated_commit_free(their_heads[2]); + git_annotated_commit_free(their_heads[3]); +} + +void test_merge_workdir_setup__id_from_head(void) +{ + git_oid expected_id; + const git_oid *id; + git_reference *ref; + git_annotated_commit *heads[3]; + + cl_git_pass(git_oid_fromstr(&expected_id, OCTO1_OID)); + cl_git_pass(git_annotated_commit_from_fetchhead(&heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &expected_id)); + id = git_annotated_commit_id(heads[0]); + cl_assert_equal_i(1, git_oid_equal(id, &expected_id)); + + cl_git_pass(git_annotated_commit_lookup(&heads[1], repo, &expected_id)); + id = git_annotated_commit_id(heads[1]); + cl_assert_equal_i(1, git_oid_equal(id, &expected_id)); + + cl_git_pass(git_reference_lookup(&ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&heads[2], repo, ref)); + id = git_annotated_commit_id(heads[2]); + cl_assert_equal_i(1, git_oid_equal(id, &expected_id)); + + git_reference_free(ref); + git_annotated_commit_free(heads[0]); + git_annotated_commit_free(heads[1]); + git_annotated_commit_free(heads[2]); +} + +struct annotated_commit_cb_data { + const char **oid_str; + unsigned int len; + + unsigned int i; +}; + +static int annotated_commit_foreach_cb(const git_oid *oid, void *payload) +{ + git_oid expected_oid; + struct annotated_commit_cb_data *cb_data = payload; + + git_oid_fromstr(&expected_oid, cb_data->oid_str[cb_data->i]); + cl_assert(git_oid_cmp(&expected_oid, oid) == 0); + cb_data->i++; + return 0; +} + +void test_merge_workdir_setup__head_notfound(void) +{ + int error; + + cl_git_fail((error = git_repository_mergehead_foreach(repo, + annotated_commit_foreach_cb, NULL))); + cl_assert(error == GIT_ENOTFOUND); +} + +void test_merge_workdir_setup__head_invalid_oid(void) +{ + int error; + + write_file_contents(GIT_MERGE_HEAD_FILE, "invalid-oid\n"); + + cl_git_fail((error = git_repository_mergehead_foreach(repo, + annotated_commit_foreach_cb, NULL))); + cl_assert(error == -1); +} + +void test_merge_workdir_setup__head_foreach_nonewline(void) +{ + int error; + + write_file_contents(GIT_MERGE_HEAD_FILE, THEIRS_SIMPLE_OID); + + cl_git_fail((error = git_repository_mergehead_foreach(repo, + annotated_commit_foreach_cb, NULL))); + cl_assert(error == -1); +} + +void test_merge_workdir_setup__head_foreach_one(void) +{ + const char *expected = THEIRS_SIMPLE_OID; + + struct annotated_commit_cb_data cb_data = { &expected, 1 }; + + write_file_contents(GIT_MERGE_HEAD_FILE, THEIRS_SIMPLE_OID "\n"); + + cl_git_pass(git_repository_mergehead_foreach(repo, + annotated_commit_foreach_cb, &cb_data)); + + cl_assert(cb_data.i == cb_data.len); +} + +void test_merge_workdir_setup__head_foreach_octopus(void) +{ + const char *expected[] = { THEIRS_SIMPLE_OID, + OCTO1_OID, OCTO2_OID, OCTO3_OID, OCTO4_OID, OCTO5_OID }; + + struct annotated_commit_cb_data cb_data = { expected, 6 }; + + write_file_contents(GIT_MERGE_HEAD_FILE, + THEIRS_SIMPLE_OID "\n" + OCTO1_OID "\n" + OCTO2_OID "\n" + OCTO3_OID "\n" + OCTO4_OID "\n" + OCTO5_OID "\n"); + + cl_git_pass(git_repository_mergehead_foreach(repo, + annotated_commit_foreach_cb, &cb_data)); + + cl_assert(cb_data.i == cb_data.len); +} + +void test_merge_workdir_setup__retained_after_success(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_annotated_commit *our_head, *their_heads[1]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_heads[0], 1, NULL, NULL)); + + cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); + cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); + cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); + cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'\n")); + + git_reference_free(octo1_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); +} + + +void test_merge_workdir_setup__removed_after_failure(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_annotated_commit *our_head, *their_heads[1]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_write2file("merge-resolve/.git/index.lock", "foo\n", 4, O_RDWR|O_CREAT, 0666); + + cl_git_fail(git_merge( + repo, (const git_annotated_commit **)&their_heads[0], 1, NULL, NULL)); + + cl_assert(!git_fs_path_exists("merge-resolve/.git/" GIT_MERGE_HEAD_FILE)); + cl_assert(!git_fs_path_exists("merge-resolve/.git/" GIT_MERGE_MODE_FILE)); + cl_assert(!git_fs_path_exists("merge-resolve/.git/" GIT_MERGE_MSG_FILE)); + + git_reference_free(octo1_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); +} + +void test_merge_workdir_setup__unlocked_after_success(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_annotated_commit *our_head, *their_heads[1]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_pass(git_merge( + repo, (const git_annotated_commit **)&their_heads[0], 1, NULL, NULL)); + + cl_assert(!git_fs_path_exists("merge-resolve/.git/index.lock")); + + git_reference_free(octo1_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); +} + +void test_merge_workdir_setup__unlocked_after_conflict(void) +{ + git_oid our_oid; + git_reference *octo1_ref; + git_annotated_commit *our_head, *their_heads[1]; + + cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); + cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); + + cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); + + cl_git_rewritefile("merge-resolve/new-in-octo1.txt", + "Conflicting file!\n\nMerge will fail!\n"); + + cl_git_fail(git_merge( + repo, (const git_annotated_commit **)&their_heads[0], 1, NULL, NULL)); + + cl_assert(!git_fs_path_exists("merge-resolve/.git/index.lock")); + + git_reference_free(octo1_ref); + + git_annotated_commit_free(our_head); + git_annotated_commit_free(their_heads[0]); +} diff --git a/tests/libgit2/merge/workdir/simple.c b/tests/libgit2/merge/workdir/simple.c new file mode 100644 index 000000000..b9d3fc24c --- /dev/null +++ b/tests/libgit2/merge/workdir/simple.c @@ -0,0 +1,778 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "../conflict_data.h" +#include "refs.h" +#include "futils.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "merge-resolve" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +#define THEIRS_SIMPLE_BRANCH "branch" +#define THEIRS_SIMPLE_OID "7cb63eed597130ba4abb87b3e544b85021905520" + +#define THEIRS_UNRELATED_BRANCH "unrelated" +#define THEIRS_UNRELATED_OID "55b4e4687e7a0d9ca367016ed930f385d4022e6f" +#define THEIRS_UNRELATED_PARENT "d6cf6c7741b3316826af1314042550c97ded1d50" + +#define OURS_DIRECTORY_FILE "df_side1" +#define THEIRS_DIRECTORY_FILE "fc90237dc4891fa6c69827fc465632225e391618" + + +/* Non-conflicting files, index entries are common to every merge operation */ +#define ADDED_IN_MASTER_INDEX_ENTRY \ + { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, \ + "added-in-master.txt" } +#define AUTOMERGEABLE_INDEX_ENTRY \ + { 0100644, "f2e1550a0c9e53d5811175864a29536642ae3821", 0, \ + "automergeable.txt" } +#define CHANGED_IN_BRANCH_INDEX_ENTRY \ + { 0100644, "4eb04c9e79e88f6640d01ff5b25ca2a60764f216", 0, \ + "changed-in-branch.txt" } +#define CHANGED_IN_MASTER_INDEX_ENTRY \ + { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, \ + "changed-in-master.txt" } +#define UNCHANGED_INDEX_ENTRY \ + { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, \ + "unchanged.txt" } + +/* Unrelated files */ +#define UNRELATED_NEW1 \ + { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, \ + "new-in-unrelated1.txt" } +#define UNRELATED_NEW2 \ + { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, \ + "new-in-unrelated2.txt" } + +/* Expected REUC entries */ +#define AUTOMERGEABLE_REUC_ENTRY \ + { "automergeable.txt", 0100644, 0100644, 0100644, \ + "6212c31dab5e482247d7977e4f0dd3601decf13b", \ + "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", \ + "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe" } +#define CONFLICTING_REUC_ENTRY \ + { "conflicting.txt", 0100644, 0100644, 0100644, \ + "d427e0b2e138501a3d15cc376077a3631e15bd46", \ + "4e886e602529caa9ab11d71f86634bd1b6e0de10", \ + "2bd0a343aeef7a2cf0d158478966a6e587ff3863" } +#define REMOVED_IN_BRANCH_REUC_ENTRY \ + { "removed-in-branch.txt", 0100644, 0100644, 0, \ + "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ + "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ + "" } +#define REMOVED_IN_MASTER_REUC_ENTRY \ + { "removed-in-master.txt", 0100644, 0, 0100644, \ + "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", \ + "", \ + "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5" } + + +/* Fixture setup and teardown */ +void test_merge_workdir_simple__initialize(void) +{ + git_config *cfg; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); + + /* Ensure that the user's merge.conflictstyle doesn't interfere */ + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge")); + git_config_free(cfg); +} + +void test_merge_workdir_simple__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +static void merge_simple_branch(int merge_file_favor, int addl_checkout_strategy) +{ + git_oid their_oids[1]; + git_annotated_commit *their_heads[1]; + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_SIMPLE_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &their_oids[0])); + + merge_opts.file_favor = merge_file_favor; + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS | + addl_checkout_strategy; + + cl_git_pass(git_merge(repo, (const git_annotated_commit **)their_heads, 1, &merge_opts, &checkout_opts)); + + git_annotated_commit_free(their_heads[0]); +} + +static void set_core_autocrlf_to(git_repository *repo, bool value) +{ + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", value)); + + git_config_free(cfg); +} + +void test_merge_workdir_simple__automerge(void) +{ + git_index *index; + const git_index_entry *entry; + git_str automergeable_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + + set_core_autocrlf_to(repo, false); + + merge_simple_branch(0, 0); + + cl_git_pass(git_futils_readbuffer(&automergeable_buf, + TEST_REPO_PATH "/automergeable.txt")); + cl_assert(strcmp(automergeable_buf.ptr, AUTOMERGEABLE_MERGED_FILE) == 0); + git_str_dispose(&automergeable_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); + + git_repository_index(&index, repo); + + cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL); + cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE)); + + git_index_free(index); +} + +void test_merge_workdir_simple__index_reload(void) +{ + git_repository *tmp_repo; + git_annotated_commit *their_heads[1]; + git_oid their_oid; + git_index_entry entry = {{0}}; + git_index *tmp_index; + + cl_git_pass(git_repository_open(&tmp_repo, git_repository_workdir(repo))); + cl_git_pass(git_repository_index(&tmp_index, tmp_repo)); + cl_git_pass(git_index_read(repo_index, 0)); + + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&entry.id, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2")); + entry.path = "automergeable.txt"; + cl_git_pass(git_index_add(repo_index, &entry)); + + cl_git_pass(git_index_add_bypath(tmp_index, "automergeable.txt")); + cl_git_pass(git_index_write(tmp_index)); + + cl_git_pass(git_oid_fromstr(&their_oid, THEIRS_SIMPLE_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &their_oid)); + cl_git_pass(git_merge(repo, (const git_annotated_commit **)their_heads, 1, NULL, NULL)); + + git_index_free(tmp_index); + git_repository_free(tmp_repo); + git_annotated_commit_free(their_heads[0]); +} + +void test_merge_workdir_simple__automerge_crlf(void) +{ +#ifdef GIT_WIN32 + git_index *index; + const git_index_entry *entry; + git_str automergeable_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + set_core_autocrlf_to(repo, true); + + merge_simple_branch(0, 0); + + cl_git_pass(git_futils_readbuffer(&automergeable_buf, + TEST_REPO_PATH "/automergeable.txt")); + cl_assert(strcmp(automergeable_buf.ptr, AUTOMERGEABLE_MERGED_FILE_CRLF) == 0); + git_str_dispose(&automergeable_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); + + git_repository_index(&index, repo); + + cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL); + cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE_CRLF)); + + git_index_free(index); +#endif /* GIT_WIN32 */ +} + +void test_merge_workdir_simple__mergefile(void) +{ + git_str conflicting_buf = GIT_STR_INIT, mergemsg_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + set_core_autocrlf_to(repo, false); + + merge_simple_branch(0, 0); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_MERGE_FILE) == 0); + cl_git_pass(git_futils_readbuffer(&mergemsg_buf, + TEST_REPO_PATH "/.git/MERGE_MSG")); + cl_assert(strcmp(git_str_cstr(&mergemsg_buf), + "Merge commit '7cb63eed597130ba4abb87b3e544b85021905520'\n" \ + "\n" \ + "#Conflicts:\n" \ + "#\tconflicting.txt\n") == 0); + git_str_dispose(&conflicting_buf); + git_str_dispose(&mergemsg_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); +} + +void test_merge_workdir_simple__diff3(void) +{ + git_str conflicting_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + set_core_autocrlf_to(repo, false); + + merge_simple_branch(0, GIT_CHECKOUT_CONFLICT_STYLE_DIFF3); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_DIFF3_FILE) == 0); + git_str_dispose(&conflicting_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); +} + +void test_merge_workdir_simple__zdiff3(void) +{ + git_str conflicting_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + set_core_autocrlf_to(repo, false); + + merge_simple_branch(0, GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/conflicting.txt")); + cl_assert_equal_s(CONFLICTING_ZDIFF3_FILE, conflicting_buf.ptr); + git_str_dispose(&conflicting_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); +} + +void test_merge_workdir_simple__union(void) +{ + git_str conflicting_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "72cdb057b340205164478565e91eb71647e66891", 0, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + CONFLICTING_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + set_core_autocrlf_to(repo, false); + + merge_simple_branch(GIT_MERGE_FILE_FAVOR_UNION, 0); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_UNION_FILE) == 0); + git_str_dispose(&conflicting_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4)); +} + +void test_merge_workdir_simple__gitattributes_union(void) +{ + git_str conflicting_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "72cdb057b340205164478565e91eb71647e66891", 0, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + CONFLICTING_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + set_core_autocrlf_to(repo, false); + cl_git_mkfile(TEST_REPO_PATH "/.gitattributes", "conflicting.txt merge=union\n"); + + merge_simple_branch(GIT_MERGE_FILE_FAVOR_NORMAL, 0); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_UNION_FILE) == 0); + git_str_dispose(&conflicting_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4)); +} + +void test_merge_workdir_simple__diff3_from_config(void) +{ + git_config *config; + git_str conflicting_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_string(config, "merge.conflictstyle", "diff3")); + + set_core_autocrlf_to(repo, false); + + merge_simple_branch(0, 0); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_DIFF3_FILE) == 0); + git_str_dispose(&conflicting_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); + + git_config_free(config); +} + +void test_merge_workdir_simple__zdiff3_from_config(void) +{ + git_config *config; + git_str conflicting_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_string(config, "merge.conflictstyle", "zdiff3")); + + set_core_autocrlf_to(repo, false); + + merge_simple_branch(0, 0); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_ZDIFF3_FILE) == 0); + git_str_dispose(&conflicting_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); + + git_config_free(config); +} + +void test_merge_workdir_simple__merge_overrides_config(void) +{ + git_config *config; + git_str conflicting_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_string(config, "merge.conflictstyle", "diff3")); + + set_core_autocrlf_to(repo, false); + + merge_simple_branch(0, GIT_CHECKOUT_CONFLICT_STYLE_MERGE); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_MERGE_FILE) == 0); + git_str_dispose(&conflicting_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); + + git_config_free(config); +} + +void test_merge_workdir_simple__checkout_ours(void) +{ + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + merge_simple_branch(0, GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); + + cl_assert(git_fs_path_exists(TEST_REPO_PATH "/conflicting.txt")); +} + +void test_merge_workdir_simple__favor_ours(void) +{ + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + CONFLICTING_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY, + }; + + merge_simple_branch(GIT_MERGE_FILE_FAVOR_OURS, 0); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4)); +} + +void test_merge_workdir_simple__favor_theirs(void) +{ + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 0, "conflicting.txt" }, + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + CONFLICTING_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY, + }; + + merge_simple_branch(GIT_MERGE_FILE_FAVOR_THEIRS, 0); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4)); +} + +void test_merge_workdir_simple__directory_file(void) +{ + git_reference *head; + git_oid their_oids[1], head_commit_id; + git_annotated_commit *their_heads[1]; + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + git_commit *head_commit; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "49130a28ef567af9a6a6104c38773fedfa5f9742", 2, "dir-10" }, + { 0100644, "6c06dcd163587c2cc18be44857e0b71116382aeb", 3, "dir-10" }, + { 0100644, "43aafd43bea779ec74317dc361f45ae3f532a505", 0, "dir-6" }, + { 0100644, "a031a28ae70e33a641ce4b8a8f6317f1ab79dee4", 3, "dir-7" }, + { 0100644, "5012fd565b1393bdfda1805d4ec38ce6619e1fd1", 1, "dir-7/file.txt" }, + { 0100644, "a5563304ddf6caba25cb50323a2ea6f7dbfcadca", 2, "dir-7/file.txt" }, + { 0100644, "e9ad6ec3e38364a3d07feda7c4197d4d845c53b5", 0, "dir-8" }, + { 0100644, "3ef4d30382ca33fdeba9fda895a99e0891ba37aa", 2, "dir-9" }, + { 0100644, "fc4c636d6515e9e261f9260dbcf3cc6eca97ea08", 1, "dir-9/file.txt" }, + { 0100644, "76ab0e2868197ec158ddd6c78d8a0d2fd73d38f9", 3, "dir-9/file.txt" }, + { 0100644, "5c2411f8075f48a6b2fdb85ebc0d371747c4df15", 0, "file-1/new" }, + { 0100644, "a39a620dae5bc8b4e771cd4d251b7d080401a21e", 1, "file-2" }, + { 0100644, "d963979c237d08b6ba39062ee7bf64c7d34a27f8", 2, "file-2" }, + { 0100644, "5c341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d", 0, "file-2/new" }, + { 0100644, "9efe7723802d4305142eee177e018fee1572c4f4", 0, "file-3/new" }, + { 0100644, "bacac9b3493509aa15e1730e1545fc0919d1dae0", 1, "file-4" }, + { 0100644, "7663fce0130db092936b137cabd693ec234eb060", 3, "file-4" }, + { 0100644, "e49f917b448d1340b31d76e54ba388268fd4c922", 0, "file-4/new" }, + { 0100644, "cab2cf23998b40f1af2d9d9a756dc9e285a8df4b", 2, "file-5/new" }, + { 0100644, "f5504f36e6f4eb797a56fc5bac6c6c7f32969bf2", 3, "file-5/new" }, + }; + + cl_git_pass(git_reference_symbolic_create(&head, repo, GIT_HEAD_FILE, GIT_REFS_HEADS_DIR OURS_DIRECTORY_FILE, 1, NULL)); + cl_git_pass(git_reference_name_to_id(&head_commit_id, repo, GIT_HEAD_FILE)); + cl_git_pass(git_commit_lookup(&head_commit, repo, &head_commit_id)); + cl_git_pass(git_reset(repo, (git_object *)head_commit, GIT_RESET_HARD, NULL)); + + cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_DIRECTORY_FILE)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &their_oids[0])); + + merge_opts.file_favor = 0; + cl_git_pass(git_merge(repo, (const git_annotated_commit **)their_heads, 1, &merge_opts, NULL)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 20)); + + git_reference_free(head); + git_commit_free(head_commit); + git_annotated_commit_free(their_heads[0]); +} + +void test_merge_workdir_simple__unrelated(void) +{ + git_oid their_oids[1]; + git_annotated_commit *their_heads[1]; + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, + { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" }, + { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, + { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, + { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" }, + { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" }, + { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, + { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, + }; + + cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_UNRELATED_PARENT)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &their_oids[0])); + + merge_opts.file_favor = 0; + cl_git_pass(git_merge(repo, (const git_annotated_commit **)their_heads, 1, &merge_opts, NULL)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 9)); + + git_annotated_commit_free(their_heads[0]); +} + +void test_merge_workdir_simple__unrelated_with_conflicts(void) +{ + git_oid their_oids[1]; + git_annotated_commit *their_heads[1]; + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, + { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 2, "automergeable.txt" }, + { 0100644, "d07ec190c306ec690bac349e87d01c4358e49bb2", 3, "automergeable.txt" }, + { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, + { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, + { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, + { 0100644, "4b253da36a0ae8bfce63aeabd8c5b58429925594", 3, "conflicting.txt" }, + { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" }, + { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" }, + { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, + { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, + }; + + cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_UNRELATED_OID)); + cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &their_oids[0])); + + merge_opts.file_favor = 0; + cl_git_pass(git_merge(repo, (const git_annotated_commit **)their_heads, 1, &merge_opts, NULL)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 11)); + + git_annotated_commit_free(their_heads[0]); +} + +void test_merge_workdir_simple__binary(void) +{ + git_oid our_oid, their_oid, our_file_oid; + git_commit *our_commit; + git_annotated_commit *their_head; + const git_index_entry *binary_entry; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "1c51d885170f57a0c4e8c69ff6363d91a5b51f85", 1, "binary" }, + { 0100644, "23ed141a6ae1e798b2f721afedbe947c119111ba", 2, "binary" }, + { 0100644, "836b8b82b26cab22eaaed8820877c76d6c8bca19", 3, "binary" }, + }; + + cl_git_pass(git_oid_fromstr(&our_oid, "cc338e4710c9b257106b8d16d82f86458d5beaf1")); + cl_git_pass(git_oid_fromstr(&their_oid, "ad01aebfdf2ac13145efafe3f9fcf798882f1730")); + + cl_git_pass(git_commit_lookup(&our_commit, repo, &our_oid)); + cl_git_pass(git_reset(repo, (git_object *)our_commit, GIT_RESET_HARD, NULL)); + + cl_git_pass(git_annotated_commit_lookup(&their_head, repo, &their_oid)); + + cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, 1, NULL, NULL)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); + + cl_git_pass(git_index_add_bypath(repo_index, "binary")); + cl_assert((binary_entry = git_index_get_bypath(repo_index, "binary", 0)) != NULL); + + cl_git_pass(git_oid_fromstr(&our_file_oid, "23ed141a6ae1e798b2f721afedbe947c119111ba")); + cl_assert(git_oid_cmp(&binary_entry->id, &our_file_oid) == 0); + + git_annotated_commit_free(their_head); + git_commit_free(our_commit); +} diff --git a/tests/libgit2/merge/workdir/submodules.c b/tests/libgit2/merge/workdir/submodules.c new file mode 100644 index 000000000..5117be789 --- /dev/null +++ b/tests/libgit2/merge/workdir/submodules.c @@ -0,0 +1,130 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "merge.h" +#include "../merge_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-resolve" + +#define SUBMODULE_MAIN_BRANCH "submodules" +#define SUBMODULE_OTHER_BRANCH "submodules-branch" +#define SUBMODULE_OTHER2_BRANCH "submodules-branch2" +#define SUBMODULE_DELETE_BRANCH "delete-submodule" + +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +/* Fixture setup and teardown */ +void test_merge_workdir_submodules__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_merge_workdir_submodules__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_merge_workdir_submodules__automerge(void) +{ + git_reference *our_ref, *their_ref; + git_commit *our_commit; + git_annotated_commit *their_head; + git_index *index; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "caff6b7d44973f53e3e0cf31d0d695188b19aec6", 0, ".gitmodules" }, + { 0100644, "950a663a6a7b2609eed1ed1ba9f41eb1a3192a9f", 0, "file1.txt" }, + { 0100644, "343e660b9cb4bee5f407c2e33fcb9df24d9407a4", 0, "file2.txt" }, + { 0160000, "d3d806a4bef96889117fd7ebac0e3cb5ec152932", 1, "submodule" }, + { 0160000, "297aa6cd028b3336c7802c7a6f49143da4e1602d", 2, "submodule" }, + { 0160000, "ae39c77c70cb6bad18bb471912460c4e1ba0f586", 3, "submodule" }, + }; + + cl_git_pass(git_reference_lookup(&our_ref, repo, "refs/heads/" SUBMODULE_MAIN_BRANCH)); + cl_git_pass(git_commit_lookup(&our_commit, repo, git_reference_target(our_ref))); + cl_git_pass(git_reset(repo, (git_object *)our_commit, GIT_RESET_HARD, NULL)); + + cl_git_pass(git_reference_lookup(&their_ref, repo, "refs/heads/" SUBMODULE_OTHER_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_head, repo, their_ref)); + + cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, 1, NULL, NULL)); + + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_index_free(index); + git_annotated_commit_free(their_head); + git_commit_free(our_commit); + git_reference_free(their_ref); + git_reference_free(our_ref); +} + +void test_merge_workdir_submodules__take_changed(void) +{ + git_reference *our_ref, *their_ref; + git_commit *our_commit; + git_annotated_commit *their_head; + git_index *index; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "caff6b7d44973f53e3e0cf31d0d695188b19aec6", 0, ".gitmodules" }, + { 0100644, "b438ff23300b2e0f80b84a6f30140dfa91e71423", 0, "file1.txt" }, + { 0100644, "f27fbafdfa6693f8f7a5128506fe3e338dbfcad2", 0, "file2.txt" }, + { 0160000, "297aa6cd028b3336c7802c7a6f49143da4e1602d", 0, "submodule" }, + }; + + cl_git_pass(git_reference_lookup(&our_ref, repo, "refs/heads/" SUBMODULE_MAIN_BRANCH)); + cl_git_pass(git_commit_lookup(&our_commit, repo, git_reference_target(our_ref))); + cl_git_pass(git_reset(repo, (git_object *)our_commit, GIT_RESET_HARD, NULL)); + + cl_git_pass(git_reference_lookup(&their_ref, repo, "refs/heads/" SUBMODULE_OTHER2_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_head, repo, their_ref)); + + cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, 1, NULL, NULL)); + + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(merge_test_index(index, merge_index_entries, 4)); + + git_index_free(index); + git_annotated_commit_free(their_head); + git_commit_free(our_commit); + git_reference_free(their_ref); + git_reference_free(our_ref); +} + + +void test_merge_workdir_submodules__update_delete_conflict(void) +{ + git_reference *our_ref, *their_ref; + git_commit *our_commit; + git_annotated_commit *their_head; + git_index *index; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", 0, ".gitmodules" }, + { 0100644, "5887a5e516c53bd58efb0f02ec6aa031b6fe9ad7", 0, "file1.txt" }, + { 0100644, "4218670ab81cc219a9f94befb5c5dad90ec52648", 0, "file2.txt" }, + { 0160000, "d3d806a4bef96889117fd7ebac0e3cb5ec152932", 1, "submodule"}, + { 0160000, "297aa6cd028b3336c7802c7a6f49143da4e1602d", 3, "submodule" }, + }; + + cl_git_pass(git_reference_lookup(&our_ref, repo, "refs/heads/" SUBMODULE_DELETE_BRANCH)); + cl_git_pass(git_commit_lookup(&our_commit, repo, git_reference_target(our_ref))); + cl_git_pass(git_reset(repo, (git_object *)our_commit, GIT_RESET_HARD, NULL)); + + cl_git_pass(git_reference_lookup(&their_ref, repo, "refs/heads/" SUBMODULE_MAIN_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&their_head, repo, their_ref)); + + cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, 1, NULL, NULL)); + + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(merge_test_index(index, merge_index_entries, 5)); + + git_index_free(index); + git_annotated_commit_free(their_head); + git_commit_free(our_commit); + git_reference_free(their_ref); + git_reference_free(our_ref); +} diff --git a/tests/libgit2/merge/workdir/trivial.c b/tests/libgit2/merge/workdir/trivial.c new file mode 100644 index 000000000..fe8374851 --- /dev/null +++ b/tests/libgit2/merge/workdir/trivial.c @@ -0,0 +1,262 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "git2/sys/index.h" +#include "merge.h" +#include "../merge_helpers.h" +#include "refs.h" +#include "futils.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "merge-resolve" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + + +/* Fixture setup and teardown */ +void test_merge_workdir_trivial__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); +} + +void test_merge_workdir_trivial__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + + +static int merge_trivial(const char *ours, const char *theirs) +{ + git_str branch_buf = GIT_STR_INIT; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + git_reference *our_ref, *their_ref; + git_annotated_commit *their_heads[1]; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours); + cl_git_pass(git_reference_symbolic_create(&our_ref, repo, "HEAD", branch_buf.ptr, 1, NULL)); + + cl_git_pass(git_checkout_head(repo, &checkout_opts)); + + git_str_clear(&branch_buf); + git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, theirs); + cl_git_pass(git_reference_lookup(&their_ref, repo, branch_buf.ptr)); + cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, their_ref)); + + cl_git_pass(git_merge(repo, (const git_annotated_commit **)their_heads, 1, NULL, NULL)); + + git_str_dispose(&branch_buf); + git_reference_free(our_ref); + git_reference_free(their_ref); + git_annotated_commit_free(their_heads[0]); + + return 0; +} + +static size_t merge_trivial_conflict_entrycount(void) +{ + const git_index_entry *entry; + size_t count = 0; + size_t i; + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + + if (git_index_entry_is_conflict(entry)) + count++; + } + + return count; +} + +/* 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote */ +void test_merge_workdir_trivial__2alt(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-2alt", "trivial-2alt-branch")); + + cl_assert(entry = git_index_get_bypath(repo_index, "new-in-branch.txt", 0)); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head */ +void test_merge_workdir_trivial__3alt(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-3alt", "trivial-3alt-branch")); + + cl_assert(entry = git_index_get_bypath(repo_index, "new-in-3alt.txt", 0)); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 4: ancest:(empty)^, head:head, remote:remote = result:no merge */ +void test_merge_workdir_trivial__4(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-4", "trivial-4-branch")); + + cl_assert((entry = git_index_get_bypath(repo_index, "new-and-different.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + cl_assert(merge_trivial_conflict_entrycount() == 2); + cl_assert(entry = git_index_get_bypath(repo_index, "new-and-different.txt", 2)); + cl_assert(entry = git_index_get_bypath(repo_index, "new-and-different.txt", 3)); +} + +/* 5ALT: ancest:*, head:head, remote:head = result:head */ +void test_merge_workdir_trivial__5alt_1(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-5alt-1", "trivial-5alt-1-branch")); + + cl_assert(entry = git_index_get_bypath(repo_index, "new-and-same.txt", 0)); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 5ALT: ancest:*, head:head, remote:head = result:head */ +void test_merge_workdir_trivial__5alt_2(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-5alt-2", "trivial-5alt-2-branch")); + + cl_assert(entry = git_index_get_bypath(repo_index, "modified-to-same.txt", 0)); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */ +void test_merge_workdir_trivial__6(void) +{ + const git_index_entry *entry; + const git_index_reuc_entry *reuc; + + cl_git_pass(merge_trivial("trivial-6", "trivial-6-branch")); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-both.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 1); + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "removed-in-both.txt")); + + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */ +void test_merge_workdir_trivial__8(void) +{ + const git_index_entry *entry; + const git_index_reuc_entry *reuc; + + cl_git_pass(merge_trivial("trivial-8", "trivial-8-branch")); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-8.txt", 0)) == NULL); + + cl_assert(git_index_reuc_entrycount(repo_index) == 1); + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "removed-in-8.txt")); + + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 7: ancest:ancest+, head:(empty), remote:remote = result:no merge */ +void test_merge_workdir_trivial__7(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-7", "trivial-7-branch")); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + cl_assert(merge_trivial_conflict_entrycount() == 2); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 1)); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 3)); +} + +/* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */ +void test_merge_workdir_trivial__10(void) +{ + const git_index_entry *entry; + const git_index_reuc_entry *reuc; + + cl_git_pass(merge_trivial("trivial-10", "trivial-10-branch")); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-10-branch.txt", 0)) == NULL); + + cl_assert(git_index_reuc_entrycount(repo_index) == 1); + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "removed-in-10-branch.txt")); + + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 9: ancest:ancest+, head:head, remote:(empty) = result:no merge */ +void test_merge_workdir_trivial__9(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-9", "trivial-9-branch")); + + cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + cl_assert(merge_trivial_conflict_entrycount() == 2); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 1)); + cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 2)); +} + +/* 13: ancest:ancest+, head:head, remote:ancest = result:head */ +void test_merge_workdir_trivial__13(void) +{ + const git_index_entry *entry; + git_oid expected_oid; + + cl_git_pass(merge_trivial("trivial-13", "trivial-13-branch")); + + cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-13.txt", 0)); + cl_git_pass(git_oid_fromstr(&expected_oid, "1cff9ec6a47a537380dedfdd17c9e76d74259a2b")); + cl_assert(git_oid_cmp(&entry->id, &expected_oid) == 0); + + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */ +void test_merge_workdir_trivial__14(void) +{ + const git_index_entry *entry; + git_oid expected_oid; + + cl_git_pass(merge_trivial("trivial-14", "trivial-14-branch")); + + cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-14-branch.txt", 0)); + cl_git_pass(git_oid_fromstr(&expected_oid, "26153a3ff3649b6c2bb652d3f06878c6e0a172f9")); + cl_assert(git_oid_cmp(&entry->id, &expected_oid) == 0); + + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + cl_assert(merge_trivial_conflict_entrycount() == 0); +} + +/* 11: ancest:ancest+, head:head, remote:remote = result:no merge */ +void test_merge_workdir_trivial__11(void) +{ + const git_index_entry *entry; + + cl_git_pass(merge_trivial("trivial-11", "trivial-11-branch")); + + cl_assert((entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 0)) == NULL); + cl_assert(git_index_reuc_entrycount(repo_index) == 0); + + cl_assert(merge_trivial_conflict_entrycount() == 3); + cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 1)); + cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 2)); + cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 3)); +} diff --git a/tests/libgit2/message/trailer.c b/tests/libgit2/message/trailer.c new file mode 100644 index 000000000..919e10a49 --- /dev/null +++ b/tests/libgit2/message/trailer.c @@ -0,0 +1,164 @@ +#include "clar_libgit2.h" + +static void assert_trailers(const char *message, git_message_trailer *trailers) +{ + git_message_trailer_array arr; + size_t i; + + int rc = git_message_trailers(&arr, message); + + cl_assert_equal_i(0, rc); + + for(i=0; i 0); + + git_strarray_dispose(&refnames); + git_remote_free(origin); + git_repository_free(repo); +} + +void test_network_fetchlocal__prune(void) +{ + git_repository *repo; + git_remote *origin; + int callcount = 0; + git_strarray refnames = {0}; + git_reference *ref; + git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); + const char *url = cl_git_path_url(git_repository_path(remote_repo)); + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + + options.callbacks.transfer_progress = transfer_cb; + options.callbacks.payload = &callcount; + + cl_set_cleanup(&cleanup_local_repo, "foo"); + cl_git_pass(git_repository_init(&repo, "foo", true)); + + cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(20, (int)refnames.count); + cl_assert(callcount > 0); + git_strarray_dispose(&refnames); + git_remote_free(origin); + + cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/br2")); + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); + cl_git_pass(git_remote_prune(origin, &options.callbacks)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(19, (int)refnames.count); + git_strarray_dispose(&refnames); + git_remote_free(origin); + + cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/packed")); + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); + cl_git_pass(git_remote_prune(origin, &options.callbacks)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(18, (int)refnames.count); + git_strarray_dispose(&refnames); + git_remote_free(origin); + + git_repository_free(repo); +} + +static int update_tips_fail_on_call(const char *ref, const git_oid *old, const git_oid *new, void *data) +{ + GIT_UNUSED(ref); + GIT_UNUSED(old); + GIT_UNUSED(new); + GIT_UNUSED(data); + + cl_fail("update tips called"); + return 0; +} + +static void assert_ref_exists(git_repository *repo, const char *name) +{ + git_reference *ref; + + cl_git_pass(git_reference_lookup(&ref, repo, name)); + git_reference_free(ref); +} + +void test_network_fetchlocal__prune_overlapping(void) +{ + git_repository *repo; + git_remote *origin; + int callcount = 0; + git_strarray refnames = {0}; + git_reference *ref; + git_config *config; + git_oid target; + + git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); + const char *url = cl_git_path_url(git_repository_path(remote_repo)); + + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + options.callbacks.transfer_progress = transfer_cb; + options.callbacks.payload = &callcount; + + cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/master")); + git_oid_cpy(&target, git_reference_target(ref)); + git_reference_free(ref); + cl_git_pass(git_reference_create(&ref, remote_repo, "refs/pull/42/head", &target, 1, NULL)); + git_reference_free(ref); + + cl_set_cleanup(&cleanup_local_repo, "foo"); + cl_git_pass(git_repository_init(&repo, "foo", true)); + + cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "remote.origin.prune", true)); + cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); + + git_remote_free(origin); + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); + + assert_ref_exists(repo, "refs/remotes/origin/master"); + assert_ref_exists(repo, "refs/remotes/origin/pr/42"); + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(21, (int)refnames.count); + git_strarray_dispose(&refnames); + + cl_git_pass(git_config_delete_multivar(config, "remote.origin.fetch", "refs")); + cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); + cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); + + git_remote_free(origin); + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); + options.callbacks.update_tips = update_tips_fail_on_call; + cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); + + assert_ref_exists(repo, "refs/remotes/origin/master"); + assert_ref_exists(repo, "refs/remotes/origin/pr/42"); + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(21, (int)refnames.count); + git_strarray_dispose(&refnames); + + cl_git_pass(git_config_delete_multivar(config, "remote.origin.fetch", "refs")); + cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); + cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); + + git_remote_free(origin); + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); + options.callbacks.update_tips = update_tips_fail_on_call; + cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); + + git_config_free(config); + git_strarray_dispose(&refnames); + git_remote_free(origin); + git_repository_free(repo); +} + +void test_network_fetchlocal__fetchprune(void) +{ + git_repository *repo; + git_remote *origin; + int callcount = 0; + git_strarray refnames = {0}; + git_reference *ref; + git_config *config; + git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); + const char *url = cl_git_path_url(git_repository_path(remote_repo)); + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + + options.callbacks.transfer_progress = transfer_cb; + options.callbacks.payload = &callcount; + + cl_set_cleanup(&cleanup_local_repo, "foo"); + cl_git_pass(git_repository_init(&repo, "foo", true)); + + cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(20, (int)refnames.count); + cl_assert(callcount > 0); + git_strarray_dispose(&refnames); + git_remote_free(origin); + + cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/br2")); + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); + cl_git_pass(git_remote_prune(origin, &options.callbacks)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(19, (int)refnames.count); + git_strarray_dispose(&refnames); + git_remote_free(origin); + + cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/packed")); + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "remote.origin.prune", 1)); + git_config_free(config); + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_assert_equal_i(1, git_remote_prune_refs(origin)); + cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(18, (int)refnames.count); + git_strarray_dispose(&refnames); + git_remote_free(origin); + + git_repository_free(repo); +} + +void test_network_fetchlocal__prune_tag(void) +{ + git_repository *repo; + git_remote *origin; + int callcount = 0; + git_reference *ref; + git_config *config; + git_oid tag_id; + git_signature *tagger; + git_object *obj; + + git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); + const char *url = cl_git_path_url(git_repository_path(remote_repo)); + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + + options.callbacks.transfer_progress = transfer_cb; + options.callbacks.payload = &callcount; + + cl_set_cleanup(&cleanup_local_repo, "foo"); + cl_git_pass(git_repository_init(&repo, "foo", true)); + + cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); + git_remote_free(origin); + + cl_git_pass(git_revparse_single(&obj, repo, "origin/master")); + + cl_git_pass(git_reference_create(&ref, repo, "refs/remotes/origin/fake-remote", git_object_id(obj), 1, NULL)); + git_reference_free(ref); + + /* create signature */ + cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); + + cl_git_pass( + git_tag_create(&tag_id, repo, + "some-tag", obj, tagger, tagger_message, 0) + ); + git_signature_free(tagger); + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "remote.origin.prune", 1)); + git_config_free(config); + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_assert_equal_i(1, git_remote_prune_refs(origin)); + cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); + + assert_ref_exists(repo, "refs/tags/some-tag"); + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, repo, "refs/remotes/origin/fake-remote")); + + git_object_free(obj); + git_remote_free(origin); + + git_repository_free(repo); +} + +void test_network_fetchlocal__partial(void) +{ + git_repository *repo = cl_git_sandbox_init("partial-testrepo"); + git_remote *origin; + int callcount = 0; + git_strarray refnames = {0}; + const char *url; + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + + options.callbacks.transfer_progress = transfer_cb; + options.callbacks.payload = &callcount; + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(1, (int)refnames.count); + + url = cl_git_fixture_url("testrepo.git"); + cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); + + git_strarray_dispose(&refnames); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(21, (int)refnames.count); /* 18 remote + 1 local */ + cl_assert(callcount > 0); + + git_strarray_dispose(&refnames); + git_remote_free(origin); +} + +static int remote_mirror_cb(git_remote **out, git_repository *repo, + const char *name, const char *url, void *payload) +{ + int error; + git_remote *remote; + + GIT_UNUSED(payload); + + if ((error = git_remote_create_with_fetchspec(&remote, repo, name, url, "+refs/*:refs/*")) < 0) + return error; + + *out = remote; + return 0; +} + +void test_network_fetchlocal__clone_into_mirror(void) +{ + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + git_repository *repo; + git_reference *ref; + + opts.bare = true; + opts.remote_cb = remote_mirror_cb; + cl_git_pass(git_clone(&repo, cl_git_fixture_url("testrepo.git"), "./foo.git", &opts)); + + cl_git_pass(git_reference_lookup(&ref, repo, "HEAD")); + cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(ref)); + cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(ref)); + + git_reference_free(ref); + cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/test/master")); + + git_reference_free(ref); + git_repository_free(repo); + cl_fixture_cleanup("./foo.git"); +} + +void test_network_fetchlocal__all_refs(void) +{ + git_repository *repo; + git_remote *remote; + git_reference *ref; + char *allrefs = "+refs/*:refs/*"; + git_strarray refspecs = { + &allrefs, + 1, + }; + + cl_git_pass(git_repository_init(&repo, "./foo.git", true)); + cl_git_pass(git_remote_create_anonymous(&remote, repo, cl_git_fixture_url("testrepo.git"))); + cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, NULL)); + + cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/test/master")); + git_reference_free(ref); + + cl_git_pass(git_reference_lookup(&ref, repo, "refs/tags/test")); + git_reference_free(ref); + + git_remote_free(remote); + git_repository_free(repo); + cl_fixture_cleanup("./foo.git"); +} + +void test_network_fetchlocal__multi_remotes(void) +{ + git_repository *repo = cl_git_sandbox_init("testrepo.git"); + git_remote *test, *test2; + git_strarray refnames = {0}; + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + + options.callbacks.transfer_progress = transfer_cb; + cl_git_pass(git_remote_set_url(repo, "test", cl_git_fixture_url("testrepo.git"))); + cl_git_pass(git_remote_lookup(&test, repo, "test")); + cl_git_pass(git_remote_fetch(test, NULL, &options, NULL)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(35, (int)refnames.count); + git_strarray_dispose(&refnames); + + cl_git_pass(git_remote_set_url(repo, "test_with_pushurl", cl_git_fixture_url("testrepo.git"))); + cl_git_pass(git_remote_lookup(&test2, repo, "test_with_pushurl")); + cl_git_pass(git_remote_fetch(test2, NULL, &options, NULL)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(48, (int)refnames.count); + + git_strarray_dispose(&refnames); + git_remote_free(test); + git_remote_free(test2); +} + +static int sideband_cb(const char *str, int len, void *payload) +{ + int *count = (int *) payload; + + GIT_UNUSED(str); + GIT_UNUSED(len); + + (*count)++; + return 0; +} + +void test_network_fetchlocal__call_progress(void) +{ + git_repository *repo; + git_remote *remote; + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + int callcount = 0; + + cl_git_pass(git_repository_init(&repo, "foo.git", true)); + cl_set_cleanup(cleanup_local_repo, "foo.git"); + + cl_git_pass(git_remote_create_with_fetchspec(&remote, repo, "origin", cl_git_fixture_url("testrepo.git"), "+refs/heads/*:refs/heads/*")); + + options.callbacks.sideband_progress = sideband_cb; + options.callbacks.payload = &callcount; + + cl_git_pass(git_remote_fetch(remote, NULL, &options, NULL)); + cl_assert(callcount != 0); + + git_remote_free(remote); + git_repository_free(repo); +} + +void test_network_fetchlocal__prune_load_remote_prune_config(void) +{ + git_repository *repo; + git_remote *origin; + git_config *config; + git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); + const char *url = cl_git_path_url(git_repository_path(remote_repo)); + + cl_set_cleanup(&cleanup_local_repo, "foo"); + cl_git_pass(git_repository_init(&repo, "foo", true)); + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "remote.origin.prune", 1)); + + cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + cl_assert_equal_i(1, git_remote_prune_refs(origin)); + + git_config_free(config); + git_remote_free(origin); + git_repository_free(repo); +} + +void test_network_fetchlocal__prune_load_fetch_prune_config(void) +{ + git_repository *repo; + git_remote *origin; + git_config *config; + git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); + const char *url = cl_git_path_url(git_repository_path(remote_repo)); + + cl_set_cleanup(&cleanup_local_repo, "foo"); + cl_git_pass(git_repository_init(&repo, "foo", true)); + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "fetch.prune", 1)); + + cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + cl_assert_equal_i(1, git_remote_prune_refs(origin)); + + git_config_free(config); + git_remote_free(origin); + git_repository_free(repo); +} + +static int update_tips_error(const char *ref, const git_oid *old, const git_oid *new, void *data) +{ + int *callcount = (int *) data; + + GIT_UNUSED(ref); + GIT_UNUSED(old); + GIT_UNUSED(new); + + (*callcount)++; + + return -1; +} + +void test_network_fetchlocal__update_tips_error_is_propagated(void) +{ + git_repository *repo; + git_reference_iterator *iterator; + git_reference *ref; + git_remote *remote; + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + int callcount = 0; + + cl_git_pass(git_repository_init(&repo, "foo.git", true)); + cl_set_cleanup(cleanup_local_repo, "foo.git"); + + cl_git_pass(git_remote_create_with_fetchspec(&remote, repo, "origin", cl_git_fixture_url("testrepo.git"), "+refs/heads/*:refs/remotes/update-tips/*")); + + options.callbacks.update_tips = update_tips_error; + options.callbacks.payload = &callcount; + + cl_git_fail(git_remote_fetch(remote, NULL, &options, NULL)); + cl_assert_equal_i(1, callcount); + + cl_git_pass(git_reference_iterator_glob_new(&iterator, repo, "refs/remotes/update-tips/**/")); + cl_assert_equal_i(GIT_ITEROVER, git_reference_next(&ref, iterator)); + + git_reference_iterator_free(iterator); + git_remote_free(remote); + git_repository_free(repo); +} diff --git a/tests/libgit2/network/matchhost.c b/tests/libgit2/network/matchhost.c new file mode 100644 index 000000000..3100dc21d --- /dev/null +++ b/tests/libgit2/network/matchhost.c @@ -0,0 +1,13 @@ +#include "clar_libgit2.h" +#include "netops.h" + +void test_network_matchhost__match(void) +{ + cl_git_pass(gitno__match_host("*.example.org", "www.example.org")); + cl_git_pass(gitno__match_host("*.foo.example.org", "www.foo.example.org")); + cl_git_fail(gitno__match_host("*.foo.example.org", "foo.example.org")); + cl_git_fail(gitno__match_host("*.foo.example.org", "www.example.org")); + cl_git_fail(gitno__match_host("*.example.org", "example.org")); + cl_git_fail(gitno__match_host("*.example.org", "www.foo.example.org")); + cl_git_fail(gitno__match_host("*.example.org", "blah.www.www.example.org")); +} diff --git a/tests/libgit2/network/refspecs.c b/tests/libgit2/network/refspecs.c new file mode 100644 index 000000000..d9e0d9e8d --- /dev/null +++ b/tests/libgit2/network/refspecs.c @@ -0,0 +1,191 @@ +#include "clar_libgit2.h" +#include "refspec.h" +#include "remote.h" + +static void assert_refspec(unsigned int direction, const char *input, bool is_expected_to_be_valid) +{ + git_refspec refspec; + int error; + + error = git_refspec__parse(&refspec, input, direction == GIT_DIRECTION_FETCH); + git_refspec__dispose(&refspec); + + if (is_expected_to_be_valid) + cl_assert_equal_i(0, error); + else + cl_assert_equal_i(GIT_EINVALIDSPEC, error); +} + +void test_network_refspecs__parsing(void) +{ + /* Ported from https://github.com/git/git/blob/abd2bde78bd994166900290434a2048e660dabed/t/t5511-refspec.sh */ + + assert_refspec(GIT_DIRECTION_PUSH, "", false); + assert_refspec(GIT_DIRECTION_PUSH, ":", true); + assert_refspec(GIT_DIRECTION_PUSH, "::", false); + assert_refspec(GIT_DIRECTION_PUSH, "+:", true); + + assert_refspec(GIT_DIRECTION_FETCH, "", true); + assert_refspec(GIT_DIRECTION_PUSH, ":", true); + assert_refspec(GIT_DIRECTION_FETCH, "::", false); + + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*:refs/remotes/frotz/*", true); + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*:refs/remotes/frotz", false); + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads:refs/remotes/frotz/*", false); + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/master:refs/remotes/frotz/xyzzy", true); + + /* + * These have invalid LHS, but we do not have a formal "valid sha-1 + * expression syntax checker" so they are not checked with the current + * code. They will be caught downstream anyway, but we may want to + * have tighter check later... + */ + /*assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/master::refs/remotes/frotz/xyzzy", false); */ + /*assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/maste :refs/remotes/frotz/xyzzy", false); */ + + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*:refs/remotes/frotz/*", true); + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*:refs/remotes/frotz", false); + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads:refs/remotes/frotz/*", false); + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/master:refs/remotes/frotz/xyzzy", true); + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/master::refs/remotes/frotz/xyzzy", false); + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/maste :refs/remotes/frotz/xyzzy", false); + + assert_refspec(GIT_DIRECTION_PUSH, "master~1:refs/remotes/frotz/backup", true); + assert_refspec(GIT_DIRECTION_FETCH, "master~1:refs/remotes/frotz/backup", false); + assert_refspec(GIT_DIRECTION_PUSH, "HEAD~4:refs/remotes/frotz/new", true); + assert_refspec(GIT_DIRECTION_FETCH, "HEAD~4:refs/remotes/frotz/new", false); + + assert_refspec(GIT_DIRECTION_PUSH, "HEAD", true); + assert_refspec(GIT_DIRECTION_FETCH, "HEAD", true); + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/ nitfol", false); + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/ nitfol", false); + + assert_refspec(GIT_DIRECTION_PUSH, "HEAD:", false); + assert_refspec(GIT_DIRECTION_FETCH, "HEAD:", true); + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/ nitfol:", false); + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/ nitfol:", false); + + assert_refspec(GIT_DIRECTION_PUSH, ":refs/remotes/frotz/deleteme", true); + assert_refspec(GIT_DIRECTION_FETCH, ":refs/remotes/frotz/HEAD-to-me", true); + assert_refspec(GIT_DIRECTION_PUSH, ":refs/remotes/frotz/delete me", false); + assert_refspec(GIT_DIRECTION_FETCH, ":refs/remotes/frotz/HEAD to me", false); + + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/for-linus:refs/remotes/mine/*-blah", true); + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/for-linus:refs/remotes/mine/*-blah", true); + + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads*/for-linus:refs/remotes/mine/*", true); + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads*/for-linus:refs/remotes/mine/*", true); + + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/*/for-linus:refs/remotes/mine/*", false); + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/*/for-linus:refs/remotes/mine/*", false); + + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*g*/for-linus:refs/remotes/mine/*", false); + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*g*/for-linus:refs/remotes/mine/*", false); + + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/for-linus:refs/remotes/mine/*", true); + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/for-linus:refs/remotes/mine/*", true); + + assert_refspec(GIT_DIRECTION_FETCH, "master", true); + assert_refspec(GIT_DIRECTION_PUSH, "master", true); + + assert_refspec(GIT_DIRECTION_FETCH, "refs/pull/*/head:refs/remotes/origin/pr/*", true); +} + +static void assert_valid_transform(const char *refspec, const char *name, const char *result) +{ + git_refspec spec; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_refspec__parse(&spec, refspec, true)); + cl_git_pass(git_refspec_transform(&buf, &spec, name)); + cl_assert_equal_s(result, buf.ptr); + + git_buf_dispose(&buf); + git_refspec__dispose(&spec); +} + +void test_network_refspecs__transform_mid_star(void) +{ + assert_valid_transform("refs/pull/*/head:refs/remotes/origin/pr/*", "refs/pull/23/head", "refs/remotes/origin/pr/23"); + assert_valid_transform("refs/heads/*:refs/remotes/origin/*", "refs/heads/master", "refs/remotes/origin/master"); + assert_valid_transform("refs/heads/*:refs/remotes/origin/*", "refs/heads/user/feature", "refs/remotes/origin/user/feature"); + assert_valid_transform("refs/heads/*:refs/heads/*", "refs/heads/master", "refs/heads/master"); + assert_valid_transform("refs/heads/*:refs/heads/*", "refs/heads/user/feature", "refs/heads/user/feature"); + assert_valid_transform("refs/*:refs/*", "refs/heads/master", "refs/heads/master"); +} + +void test_network_refspecs__transform_loosened_star(void) +{ + assert_valid_transform("refs/heads/branch-*:refs/remotes/origin/branch-*", "refs/heads/branch-a", "refs/remotes/origin/branch-a"); + assert_valid_transform("refs/heads/branch-*/head:refs/remotes/origin/branch-*/head", "refs/heads/branch-a/head", "refs/remotes/origin/branch-a/head"); +} + +void test_network_refspecs__transform_nested_star(void) +{ + assert_valid_transform("refs/heads/x*x/for-linus:refs/remotes/mine/*", "refs/heads/xbranchx/for-linus", "refs/remotes/mine/branch"); +} + +void test_network_refspecs__no_dst(void) +{ + assert_valid_transform("refs/heads/master:", "refs/heads/master", ""); +} + +static void assert_invalid_transform(const char *refspec, const char *name) +{ + git_refspec spec; + git_buf buf = GIT_BUF_INIT; + + git_refspec__parse(&spec, refspec, true); + cl_git_fail(git_refspec_transform(&buf, &spec, name)); + + git_buf_dispose(&buf); + git_refspec__dispose(&spec); +} + +void test_network_refspecs__invalid(void) +{ + assert_invalid_transform("refs/heads/*:refs/remotes/origin/*", "master"); + assert_invalid_transform("refs/heads/*:refs/remotes/origin/*", "refs/headz/master"); +} + +static void assert_invalid_rtransform(const char *refspec, const char *name) +{ + git_refspec spec; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_refspec__parse(&spec, refspec, true)); + cl_git_fail(git_refspec_rtransform(&buf, &spec, name)); + + git_buf_dispose(&buf); + git_refspec__dispose(&spec); +} + +void test_network_refspecs__invalid_reverse(void) +{ + assert_invalid_rtransform("refs/heads/*:refs/remotes/origin/*", "master"); + assert_invalid_rtransform("refs/heads/*:refs/remotes/origin/*", "refs/remotes/o/master"); +} + +void test_network_refspecs__matching(void) +{ + git_refspec spec; + + cl_git_pass(git_refspec__parse(&spec, ":", false)); + cl_assert_equal_s(":", spec.string); + cl_assert_equal_s("", spec.src); + cl_assert_equal_s("", spec.dst); + + git_refspec__dispose(&spec); +} + +void test_network_refspecs__parse_free(void) +{ + git_refspec *spec = NULL; + + cl_git_fail(git_refspec_parse(&spec, "", 0)); + cl_git_fail(git_refspec_parse(&spec, ":::", 0)); + cl_git_pass(git_refspec_parse(&spec, "HEAD:", 1)); + + cl_assert(spec != NULL); + git_refspec_free(spec); +} diff --git a/tests/libgit2/network/remote/defaultbranch.c b/tests/libgit2/network/remote/defaultbranch.c new file mode 100644 index 000000000..a7c0d81f6 --- /dev/null +++ b/tests/libgit2/network/remote/defaultbranch.c @@ -0,0 +1,107 @@ +#include "clar_libgit2.h" +#include "refspec.h" +#include "remote.h" + +static git_remote *g_remote; +static git_repository *g_repo_a, *g_repo_b; + +void test_network_remote_defaultbranch__initialize(void) +{ + g_repo_a = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_init(&g_repo_b, "repo-b.git", true)); + cl_git_pass(git_remote_create(&g_remote, g_repo_b, "origin", git_repository_path(g_repo_a))); +} + +void test_network_remote_defaultbranch__cleanup(void) +{ + git_remote_free(g_remote); + git_repository_free(g_repo_b); + + cl_git_sandbox_cleanup(); + cl_fixture_cleanup("repo-b.git"); +} + +static void assert_default_branch(const char *should) +{ + git_buf name = GIT_BUF_INIT; + + cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_pass(git_remote_default_branch(&name, g_remote)); + cl_assert_equal_s(should, name.ptr); + git_buf_dispose(&name); +} + +void test_network_remote_defaultbranch__master(void) +{ + assert_default_branch("refs/heads/master"); +} + +void test_network_remote_defaultbranch__master_does_not_win(void) +{ + cl_git_pass(git_repository_set_head(g_repo_a, "refs/heads/not-good")); + assert_default_branch("refs/heads/not-good"); +} + +void test_network_remote_defaultbranch__master_on_detached(void) +{ + cl_git_pass(git_repository_detach_head(g_repo_a)); + assert_default_branch("refs/heads/master"); +} + +void test_network_remote_defaultbranch__no_default_branch(void) +{ + git_remote *remote_b; + const git_remote_head **heads; + size_t len; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_remote_create(&remote_b, g_repo_b, "self", git_repository_path(g_repo_b))); + cl_git_pass(git_remote_connect(remote_b, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_pass(git_remote_ls(&heads, &len, remote_b)); + cl_assert_equal_i(0, len); + + cl_git_fail_with(GIT_ENOTFOUND, git_remote_default_branch(&buf, remote_b)); + + git_remote_free(remote_b); +} + +void test_network_remote_defaultbranch__detached_sharing_nonbranch_id(void) +{ + git_oid id, id_cloned; + git_reference *ref; + git_buf buf = GIT_BUF_INIT; + git_repository *cloned_repo; + + cl_git_pass(git_reference_name_to_id(&id, g_repo_a, "HEAD")); + cl_git_pass(git_repository_detach_head(g_repo_a)); + cl_git_pass(git_reference_remove(g_repo_a, "refs/heads/master")); + cl_git_pass(git_reference_remove(g_repo_a, "refs/heads/not-good")); + cl_git_pass(git_reference_create(&ref, g_repo_a, "refs/foo/bar", &id, 1, NULL)); + git_reference_free(ref); + + cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_fail_with(GIT_ENOTFOUND, git_remote_default_branch(&buf, g_remote)); + + cl_git_pass(git_clone(&cloned_repo, git_repository_path(g_repo_a), "./local-detached", NULL)); + + cl_assert(git_repository_head_detached(cloned_repo)); + cl_git_pass(git_reference_name_to_id(&id_cloned, g_repo_a, "HEAD")); + cl_assert(git_oid_equal(&id, &id_cloned)); + + git_repository_free(cloned_repo); +} + +void test_network_remote_defaultbranch__unborn_HEAD_with_branches(void) +{ + git_reference *ref; + git_repository *cloned_repo; + + cl_git_pass(git_reference_symbolic_create(&ref, g_repo_a, "HEAD", "refs/heads/i-dont-exist", 1, NULL)); + git_reference_free(ref); + + cl_git_pass(git_clone(&cloned_repo, git_repository_path(g_repo_a), "./semi-empty", NULL)); + + cl_assert(git_repository_head_unborn(cloned_repo)); + + git_repository_free(cloned_repo); +} diff --git a/tests/libgit2/network/remote/delete.c b/tests/libgit2/network/remote/delete.c new file mode 100644 index 000000000..f23a638aa --- /dev/null +++ b/tests/libgit2/network/remote/delete.c @@ -0,0 +1,46 @@ +#include "clar_libgit2.h" +#include "config/config_helpers.h" + +#include "repository.h" + +static git_repository *_repo; + +void test_network_remote_delete__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_network_remote_delete__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_network_remote_delete__remove_remote_tracking_branches(void) +{ + git_reference *ref; + + cl_git_pass(git_remote_delete(_repo, "test")); + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, _repo, "refs/remotes/test/master")); +} + +void test_network_remote_delete__remove_remote_configuration_settings(void) +{ + cl_assert(count_config_entries_match(_repo, "remote\\.test\\.+") > 0); + + cl_git_pass(git_remote_delete(_repo, "test")); + + cl_assert_equal_i(0, count_config_entries_match(_repo, "remote\\.test\\.+")); +} + +void test_network_remote_delete__remove_branch_upstream_configuration_settings(void) +{ + assert_config_entry_existence(_repo, "branch.mergeless.remote", true); + assert_config_entry_existence(_repo, "branch.master.remote", true); + + cl_git_pass(git_remote_delete(_repo, "test")); + + assert_config_entry_existence(_repo, "branch.mergeless.remote", false); + assert_config_entry_existence(_repo, "branch.mergeless.merge", false); + assert_config_entry_existence(_repo, "branch.master.remote", false); + assert_config_entry_existence(_repo, "branch.master.merge", false); +} diff --git a/tests/libgit2/network/remote/isvalidname.c b/tests/libgit2/network/remote/isvalidname.c new file mode 100644 index 000000000..a3080f67c --- /dev/null +++ b/tests/libgit2/network/remote/isvalidname.c @@ -0,0 +1,24 @@ +#include "clar_libgit2.h" + +static int is_valid_name(const char *name) +{ + int valid = 0; + cl_git_pass(git_remote_name_is_valid(&valid, name)); + return valid; +} + +void test_network_remote_isvalidname__can_detect_invalid_formats(void) +{ + cl_assert_equal_i(false, is_valid_name("/")); + cl_assert_equal_i(false, is_valid_name("//")); + cl_assert_equal_i(false, is_valid_name(".lock")); + cl_assert_equal_i(false, is_valid_name("a.lock")); + cl_assert_equal_i(false, is_valid_name("/no/leading/slash")); + cl_assert_equal_i(false, is_valid_name("no/trailing/slash/")); +} + +void test_network_remote_isvalidname__wont_hopefully_choke_on_valid_formats(void) +{ + cl_assert_equal_i(true, is_valid_name("webmatrix")); + cl_assert_equal_i(true, is_valid_name("yishaigalatzer/rules")); +} diff --git a/tests/libgit2/network/remote/local.c b/tests/libgit2/network/remote/local.c new file mode 100644 index 000000000..2007f3776 --- /dev/null +++ b/tests/libgit2/network/remote/local.c @@ -0,0 +1,483 @@ +#include "clar_libgit2.h" +#include "path.h" +#include "posix.h" +#include "git2/sys/repository.h" + +static git_repository *repo; +static git_str file_path_buf = GIT_STR_INIT; +static git_remote *remote; + +static char *push_refspec_strings[] = { + "refs/heads/master", +}; +static git_strarray push_array = { + push_refspec_strings, + 1, +}; + +void test_network_remote_local__initialize(void) +{ + cl_git_pass(git_repository_init(&repo, "remotelocal/", 0)); + cl_git_pass(git_repository_set_ident(repo, "Foo Bar", "foo@example.com")); + cl_assert(repo != NULL); +} + +void test_network_remote_local__cleanup(void) +{ + git_str_dispose(&file_path_buf); + + git_remote_free(remote); + remote = NULL; + + git_repository_free(repo); + repo = NULL; + + cl_fixture_cleanup("remotelocal"); +} + +static void connect_to_local_repository(const char *local_repository) +{ + git_str_sets(&file_path_buf, cl_git_path_url(local_repository)); + + cl_git_pass(git_remote_create_anonymous(&remote, repo, git_str_cstr(&file_path_buf))); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); +} + +void test_network_remote_local__connected(void) +{ + connect_to_local_repository(cl_fixture("testrepo.git")); + cl_assert(git_remote_connected(remote)); + + git_remote_disconnect(remote); + cl_assert(!git_remote_connected(remote)); +} + +void test_network_remote_local__retrieve_advertised_references(void) +{ + const git_remote_head **refs; + size_t refs_len; + + connect_to_local_repository(cl_fixture("testrepo.git")); + + cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); + + cl_assert_equal_i(refs_len, 30); +} + +void test_network_remote_local__retrieve_advertised_before_connect(void) +{ + const git_remote_head **refs; + size_t refs_len = 0; + + git_str_sets(&file_path_buf, cl_git_path_url(cl_fixture("testrepo.git"))); + + cl_git_pass(git_remote_create_anonymous(&remote, repo, git_str_cstr(&file_path_buf))); + cl_git_fail(git_remote_ls(&refs, &refs_len, remote)); +} + +void test_network_remote_local__retrieve_advertised_references_after_disconnect(void) +{ + const git_remote_head **refs; + size_t refs_len; + + connect_to_local_repository(cl_fixture("testrepo.git")); + git_remote_disconnect(remote); + + cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); + + cl_assert_equal_i(refs_len, 30); +} + +void test_network_remote_local__retrieve_advertised_references_from_spaced_repository(void) +{ + const git_remote_head **refs; + size_t refs_len; + + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(p_rename("testrepo.git", "spaced testrepo.git")); + + connect_to_local_repository("spaced testrepo.git"); + + cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); + + cl_assert_equal_i(refs_len, 30); + + git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */ + remote = NULL; + + cl_fixture_cleanup("spaced testrepo.git"); +} + +void test_network_remote_local__nested_tags_are_completely_peeled(void) +{ + const git_remote_head **refs; + size_t refs_len, i; + + connect_to_local_repository(cl_fixture("testrepo.git")); + + cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); + + for (i = 0; i < refs_len; i++) { + if (!strcmp(refs[i]->name, "refs/tags/test^{}")) + cl_git_pass(git_oid_streq(&refs[i]->oid, "e90810b8df3e80c413d903f631643c716887138d")); + } +} + +void test_network_remote_local__shorthand_fetch_refspec0(void) +{ + char *refspec_strings[] = { + "master:remotes/sloppy/master", + "master:boh/sloppy/master", + }; + git_strarray array = { + refspec_strings, + 2, + }; + + git_reference *ref; + + connect_to_local_repository(cl_fixture("testrepo.git")); + + cl_git_pass(git_remote_fetch(remote, &array, NULL, NULL)); + + cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/sloppy/master")); + git_reference_free(ref); + + cl_git_pass(git_reference_lookup(&ref, repo, "refs/heads/boh/sloppy/master")); + git_reference_free(ref); +} + +void test_network_remote_local__shorthand_fetch_refspec1(void) +{ + char *refspec_strings[] = { + "master", + "hard_tag", + }; + git_strarray array = { + refspec_strings, + 2, + }; + + git_reference *ref; + + connect_to_local_repository(cl_fixture("testrepo.git")); + + cl_git_pass(git_remote_fetch(remote, &array, NULL, NULL)); + + cl_git_fail(git_reference_lookup(&ref, repo, "refs/remotes/origin/master")); + cl_git_fail(git_reference_lookup(&ref, repo, "refs/tags/hard_tag")); +} + +void test_network_remote_local__tagopt(void) +{ + git_reference *ref; + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + + cl_git_pass(git_remote_create(&remote, repo, "tagopt", cl_git_path_url(cl_fixture("testrepo.git")))); + fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; + cl_git_pass(git_remote_fetch(remote, NULL, &fetch_opts, NULL)); + + cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/tagopt/master")); + git_reference_free(ref); + cl_git_pass(git_reference_lookup(&ref, repo, "refs/tags/hard_tag")); + git_reference_free(ref); + + fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; + cl_git_pass(git_remote_fetch(remote, NULL, &fetch_opts, NULL)); + cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/tagopt/master")); + git_reference_free(ref); +} + +void test_network_remote_local__push_to_bare_remote(void) +{ + char *refspec_strings[] = { + "master:master", + }; + git_strarray array = { + refspec_strings, + 1, + }; + + /* Should be able to push to a bare remote */ + git_remote *localremote; + + /* Get some commits */ + connect_to_local_repository(cl_fixture("testrepo.git")); + cl_git_pass(git_remote_fetch(remote, &array, NULL, NULL)); + + /* Set up an empty bare repo to push into */ + { + git_repository *localbarerepo; + cl_git_pass(git_repository_init(&localbarerepo, "./localbare.git", 1)); + git_repository_free(localbarerepo); + } + + /* Connect to the bare repo */ + cl_git_pass(git_remote_create_anonymous(&localremote, repo, "./localbare.git")); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL, NULL)); + + /* Try to push */ + cl_git_pass(git_remote_upload(localremote, &push_array, NULL)); + + /* Clean up */ + git_remote_free(localremote); + cl_fixture_cleanup("localbare.git"); +} + +void test_network_remote_local__push_to_bare_remote_with_file_url(void) +{ + char *refspec_strings[] = { + "master:master", + }; + git_strarray array = { + refspec_strings, + 1, + }; + /* Should be able to push to a bare remote */ + git_remote *localremote; + const char *url; + + /* Get some commits */ + connect_to_local_repository(cl_fixture("testrepo.git")); + cl_git_pass(git_remote_fetch(remote, &array, NULL, NULL)); + + /* Set up an empty bare repo to push into */ + { + git_repository *localbarerepo; + cl_git_pass(git_repository_init(&localbarerepo, "./localbare.git", 1)); + git_repository_free(localbarerepo); + } + + /* Create a file URL */ + url = cl_git_path_url("./localbare.git"); + + /* Connect to the bare repo */ + cl_git_pass(git_remote_create_anonymous(&localremote, repo, url)); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL, NULL)); + + /* Try to push */ + cl_git_pass(git_remote_upload(localremote, &push_array, NULL)); + + /* Clean up */ + git_remote_free(localremote); + cl_fixture_cleanup("localbare.git"); +} + + +void test_network_remote_local__push_to_non_bare_remote(void) +{ + char *refspec_strings[] = { + "master:master", + }; + git_strarray array = { + refspec_strings, + 1, + }; + /* Shouldn't be able to push to a non-bare remote */ + git_remote *localremote; + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + + /* Get some commits */ + connect_to_local_repository(cl_fixture("testrepo.git")); + cl_git_pass(git_remote_fetch(remote, &array, &fetch_opts, NULL)); + + /* Set up an empty non-bare repo to push into */ + { + git_repository *remoterepo = NULL; + cl_git_pass(git_repository_init(&remoterepo, "localnonbare", 0)); + git_repository_free(remoterepo); + } + + /* Connect to the bare repo */ + cl_git_pass(git_remote_create_anonymous(&localremote, repo, "./localnonbare")); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL, NULL)); + + /* Try to push */ + cl_git_fail_with(GIT_EBAREREPO, git_remote_upload(localremote, &push_array, NULL)); + + /* Clean up */ + git_remote_free(localremote); + cl_fixture_cleanup("localbare.git"); +} + +void test_network_remote_local__fetch(void) +{ + char *refspec_strings[] = { + "master:remotes/sloppy/master", + }; + git_strarray array = { + refspec_strings, + 1, + }; + + git_reflog *log; + const git_reflog_entry *entry; + git_reference *ref; + + connect_to_local_repository(cl_fixture("testrepo.git")); + + cl_git_pass(git_remote_fetch(remote, &array, NULL, "UPDAAAAAATE!!")); + + cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/sloppy/master")); + git_reference_free(ref); + + cl_git_pass(git_reflog_read(&log, repo, "refs/remotes/sloppy/master")); + cl_assert_equal_i(1, git_reflog_entrycount(log)); + entry = git_reflog_entry_byindex(log, 0); + cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email); + cl_assert_equal_s("UPDAAAAAATE!!", git_reflog_entry_message(entry)); + + git_reflog_free(log); +} + +void test_network_remote_local__reflog(void) +{ + char *refspec_strings[] = { + "master:remotes/sloppy/master", + }; + git_strarray array = { + refspec_strings, + 1, + }; + + git_reflog *log; + const git_reflog_entry *entry; + + connect_to_local_repository(cl_fixture("testrepo.git")); + + cl_git_pass(git_remote_fetch(remote, &array, NULL, "UPDAAAAAATE!!")); + + cl_git_pass(git_reflog_read(&log, repo, "refs/remotes/sloppy/master")); + cl_assert_equal_i(1, git_reflog_entrycount(log)); + entry = git_reflog_entry_byindex(log, 0); + cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email); + cl_assert_equal_s("UPDAAAAAATE!!", git_reflog_entry_message(entry)); + + git_reflog_free(log); +} + +void test_network_remote_local__fetch_default_reflog_message(void) +{ + char *refspec_strings[] = { + "master:remotes/sloppy/master", + }; + git_strarray array = { + refspec_strings, + 1, + }; + + git_reflog *log; + const git_reflog_entry *entry; + char expected_reflog_msg[1024]; + + connect_to_local_repository(cl_fixture("testrepo.git")); + + cl_git_pass(git_remote_fetch(remote, &array, NULL, NULL)); + + cl_git_pass(git_reflog_read(&log, repo, "refs/remotes/sloppy/master")); + cl_assert_equal_i(1, git_reflog_entrycount(log)); + entry = git_reflog_entry_byindex(log, 0); + cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email); + + sprintf(expected_reflog_msg, "fetch %s", git_remote_url(remote)); + cl_assert_equal_s(expected_reflog_msg, git_reflog_entry_message(entry)); + + git_reflog_free(log); +} + +void test_network_remote_local__opportunistic_update(void) +{ + git_reference *ref; + char *refspec_strings[] = { + "master", + }; + git_strarray array = { + refspec_strings, + 1, + }; + + /* this remote has a passive refspec of "refs/heads/:refs/remotes/origin/" */ + cl_git_pass(git_remote_create(&remote, repo, "origin", cl_git_fixture_url("testrepo.git"))); + /* and we pass the active refspec "master" */ + cl_git_pass(git_remote_fetch(remote, &array, NULL, NULL)); + + /* and we expect that to update our copy of origin's master */ + cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/origin/master")); + git_reference_free(ref); +} + +void test_network_remote_local__update_tips_for_new_remote(void) { + git_repository *src_repo; + git_repository *dst_repo; + git_remote *new_remote; + git_reference* branch; + + /* Copy test repo */ + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&src_repo, "testrepo.git")); + + /* Set up an empty bare repo to push into */ + cl_git_pass(git_repository_init(&dst_repo, "./localbare.git", 1)); + + /* Push to bare repo */ + cl_git_pass(git_remote_create(&new_remote, src_repo, "bare", "./localbare.git")); + cl_git_pass(git_remote_push(new_remote, &push_array, NULL)); + /* Make sure remote branch has been created */ + cl_git_pass(git_branch_lookup(&branch, src_repo, "bare/master", GIT_BRANCH_REMOTE)); + + git_reference_free(branch); + git_remote_free(new_remote); + git_repository_free(dst_repo); + cl_fixture_cleanup("localbare.git"); + git_repository_free(src_repo); + cl_fixture_cleanup("testrepo.git"); +} + +void test_network_remote_local__push_delete(void) +{ + git_repository *src_repo; + git_repository *dst_repo; + git_remote *remote; + git_reference *ref; + char *spec_push[] = { "refs/heads/master" }; + char *spec_delete[] = { ":refs/heads/master" }; + git_strarray specs = { + spec_push, + 1, + }; + + src_repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_init(&dst_repo, "target.git", 1)); + + cl_git_pass(git_remote_create(&remote, src_repo, "origin", "./target.git")); + + /* Push the master branch and verify it's there */ + cl_git_pass(git_remote_push(remote, &specs, NULL)); + cl_git_pass(git_reference_lookup(&ref, dst_repo, "refs/heads/master")); + git_reference_free(ref); + + specs.strings = spec_delete; + cl_git_pass(git_remote_push(remote, &specs, NULL)); + cl_git_fail(git_reference_lookup(&ref, dst_repo, "refs/heads/master")); + + git_remote_free(remote); + git_repository_free(dst_repo); + cl_fixture_cleanup("target.git"); + cl_git_sandbox_cleanup(); +} + +void test_network_remote_local__anonymous_remote_inmemory_repo(void) +{ + git_repository *inmemory; + git_remote *remote; + + git_str_sets(&file_path_buf, cl_git_path_url(cl_fixture("testrepo.git"))); + + cl_git_pass(git_repository_new(&inmemory)); + cl_git_pass(git_remote_create_anonymous(&remote, inmemory, git_str_cstr(&file_path_buf))); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_assert(git_remote_connected(remote)); + git_remote_disconnect(remote); + git_remote_free(remote); + git_repository_free(inmemory); +} diff --git a/tests/libgit2/network/remote/push.c b/tests/libgit2/network/remote/push.c new file mode 100644 index 000000000..3905debdf --- /dev/null +++ b/tests/libgit2/network/remote/push.c @@ -0,0 +1,114 @@ +#include "clar_libgit2.h" +#include "git2/sys/commit.h" + +static git_remote *_remote; +static git_repository *_repo, *_dummy; + +void test_network_remote_push__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + git_repository_open(&_repo, "testrepo.git"); + + /* We need a repository to have a remote */ + cl_git_pass(git_repository_init(&_dummy, "dummy.git", true)); + cl_git_pass(git_remote_create(&_remote, _dummy, "origin", cl_git_path_url("testrepo.git"))); +} + +void test_network_remote_push__cleanup(void) +{ + git_remote_free(_remote); + _remote = NULL; + + git_repository_free(_repo); + _repo = NULL; + + git_repository_free(_dummy); + _dummy = NULL; + + cl_fixture_cleanup("testrepo.git"); + cl_fixture_cleanup("dummy.git"); +} + +static int negotiation_cb(const git_push_update **updates, size_t len, void *payload) +{ + const git_push_update *expected = payload; + + cl_assert_equal_i(1, len); + cl_assert_equal_s(expected->src_refname, updates[0]->src_refname); + cl_assert_equal_s(expected->dst_refname, updates[0]->dst_refname); + cl_assert_equal_oid(&expected->src, &updates[0]->src); + cl_assert_equal_oid(&expected->dst, &updates[0]->dst); + + return 0; +} + +void test_network_remote_push__delete_notification(void) +{ + git_push_options opts = GIT_PUSH_OPTIONS_INIT; + git_reference *ref; + git_push_update expected; + char *refspec = ":refs/heads/master"; + const git_strarray refspecs = { + &refspec, + 1, + }; + + cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/master")); + + expected.src_refname = ""; + expected.dst_refname = "refs/heads/master"; + memset(&expected.dst, 0, sizeof(git_oid)); + git_oid_cpy(&expected.src, git_reference_target(ref)); + + opts.callbacks.push_negotiation = negotiation_cb; + opts.callbacks.payload = &expected; + cl_git_pass(git_remote_push(_remote, &refspecs, &opts)); + + git_reference_free(ref); + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, _repo, "refs/heads/master")); + +} + +static void create_dummy_commit(git_reference **out, git_repository *repo) +{ + git_index *index; + git_oid tree_id, commit_id; + git_signature *sig; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_write_tree(&tree_id, index)); + git_index_free(index); + + cl_git_pass(git_signature_now(&sig, "Pusher Joe", "pjoe")); + cl_git_pass(git_commit_create_from_ids(&commit_id, repo, NULL, sig, sig, + NULL, "Empty tree\n", &tree_id, 0, NULL)); + cl_git_pass(git_reference_create(out, repo, "refs/heads/empty-tree", &commit_id, true, "commit yo")); + git_signature_free(sig); +} + +void test_network_remote_push__create_notification(void) +{ + git_push_options opts = GIT_PUSH_OPTIONS_INIT; + git_reference *ref; + git_push_update expected; + char *refspec = "refs/heads/empty-tree"; + const git_strarray refspecs = { + &refspec, + 1, + }; + + create_dummy_commit(&ref, _dummy); + + expected.src_refname = "refs/heads/empty-tree"; + expected.dst_refname = "refs/heads/empty-tree"; + git_oid_cpy(&expected.dst, git_reference_target(ref)); + memset(&expected.src, 0, sizeof(git_oid)); + + opts.callbacks.push_negotiation = negotiation_cb; + opts.callbacks.payload = &expected; + cl_git_pass(git_remote_push(_remote, &refspecs, &opts)); + + git_reference_free(ref); + cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/empty-tree")); + git_reference_free(ref); +} diff --git a/tests/libgit2/network/remote/remotes.c b/tests/libgit2/network/remote/remotes.c new file mode 100644 index 000000000..79c4f39fa --- /dev/null +++ b/tests/libgit2/network/remote/remotes.c @@ -0,0 +1,575 @@ +#include "clar_libgit2.h" +#include "config/config_helpers.h" +#include "refspec.h" +#include "remote.h" + +static git_remote *_remote; +static git_repository *_repo; +static const git_refspec *_refspec; + +void test_network_remote_remotes__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_remote_lookup(&_remote, _repo, "test")); + + _refspec = git_remote_get_refspec(_remote, 0); + cl_assert(_refspec != NULL); +} + +void test_network_remote_remotes__cleanup(void) +{ + git_remote_free(_remote); + _remote = NULL; + + cl_git_sandbox_cleanup(); +} + +void test_network_remote_remotes__parsing(void) +{ + git_str url = GIT_STR_INIT; + git_remote *_remote2 = NULL; + + cl_assert_equal_s(git_remote_name(_remote), "test"); + cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); + cl_assert(git_remote_pushurl(_remote) == NULL); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, NULL)); + cl_assert_equal_s(url.ptr, "git://github.com/libgit2/libgit2"); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, NULL)); + cl_assert_equal_s(url.ptr, "git://github.com/libgit2/libgit2"); + + cl_git_pass(git_remote_lookup(&_remote2, _repo, "test_with_pushurl")); + cl_assert_equal_s(git_remote_name(_remote2), "test_with_pushurl"); + cl_assert_equal_s(git_remote_url(_remote2), "git://github.com/libgit2/fetchlibgit2"); + cl_assert_equal_s(git_remote_pushurl(_remote2), "git://github.com/libgit2/pushlibgit2"); + + cl_git_pass(git_remote__urlfordirection(&url, _remote2, GIT_DIRECTION_FETCH, NULL)); + cl_assert_equal_s(url.ptr, "git://github.com/libgit2/fetchlibgit2"); + + cl_git_pass(git_remote__urlfordirection(&url, _remote2, GIT_DIRECTION_PUSH, NULL)); + cl_assert_equal_s(url.ptr, "git://github.com/libgit2/pushlibgit2"); + + git_remote_free(_remote2); + git_str_dispose(&url); +} + +static int remote_ready_callback(git_remote *remote, int direction, void *payload) +{ + if (direction == GIT_DIRECTION_PUSH) { + const char *url = git_remote_pushurl(remote); + + cl_assert_equal_p(url, NULL);; + cl_assert_equal_s(payload, "payload"); + return git_remote_set_instance_pushurl(remote, "push_url"); + } + + if (direction == GIT_DIRECTION_FETCH) { + const char *url = git_remote_url(remote); + + cl_assert_equal_s(url, "git://github.com/libgit2/libgit2"); + cl_assert_equal_s(payload, "payload"); + return git_remote_set_instance_url(remote, "fetch_url"); + } + + return -1; +} + +void test_network_remote_remotes__remote_ready(void) +{ + git_str url = GIT_STR_INIT; + + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + callbacks.remote_ready = remote_ready_callback; + callbacks.payload = "payload"; + + cl_assert_equal_s(git_remote_name(_remote), "test"); + cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); + cl_assert(git_remote_pushurl(_remote) == NULL); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, &callbacks)); + cl_assert_equal_s(url.ptr, "fetch_url"); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, &callbacks)); + cl_assert_equal_s(url.ptr, "push_url"); + + git_str_dispose(&url); +} + +#ifndef GIT_DEPRECATE_HARD +static int urlresolve_callback(git_buf *url_resolved, const char *url, int direction, void *payload) +{ + int error = -1; + + cl_assert(strcmp(url, "git://github.com/libgit2/libgit2") == 0); + cl_assert(strcmp(payload, "payload") == 0); + cl_assert(url_resolved->size == 0); + + if (direction == GIT_DIRECTION_PUSH) + error = git_buf_set(url_resolved, "pushresolve", strlen("pushresolve") + 1); + if (direction == GIT_DIRECTION_FETCH) + error = git_buf_set(url_resolved, "fetchresolve", strlen("fetchresolve") + 1); + + return error; +} +#endif + +void test_network_remote_remotes__urlresolve(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_str url = GIT_STR_INIT; + + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + callbacks.resolve_url = urlresolve_callback; + callbacks.payload = "payload"; + + cl_assert_equal_s(git_remote_name(_remote), "test"); + cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); + cl_assert(git_remote_pushurl(_remote) == NULL); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, &callbacks)); + cl_assert_equal_s(url.ptr, "fetchresolve"); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, &callbacks)); + cl_assert_equal_s(url.ptr, "pushresolve"); + + git_str_dispose(&url); +#endif +} + +#ifndef GIT_DEPRECATE_HARD +static int urlresolve_passthrough_callback(git_buf *url_resolved, const char *url, int direction, void *payload) +{ + GIT_UNUSED(url_resolved); + GIT_UNUSED(url); + GIT_UNUSED(direction); + GIT_UNUSED(payload); + return GIT_PASSTHROUGH; +} +#endif + +void test_network_remote_remotes__urlresolve_passthrough(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_str url = GIT_STR_INIT; + const char *orig_url = "git://github.com/libgit2/libgit2"; + + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + callbacks.resolve_url = urlresolve_passthrough_callback; + + cl_assert_equal_s(git_remote_name(_remote), "test"); + cl_assert_equal_s(git_remote_url(_remote), orig_url); + cl_assert(git_remote_pushurl(_remote) == NULL); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, &callbacks)); + cl_assert_equal_s(url.ptr, orig_url); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, &callbacks)); + cl_assert_equal_s(url.ptr, orig_url); + + git_str_dispose(&url); +#endif +} + +void test_network_remote_remotes__instance_url(void) +{ + git_str url = GIT_STR_INIT; + const char *orig_url = "git://github.com/libgit2/libgit2"; + + cl_assert_equal_s(git_remote_name(_remote), "test"); + cl_assert_equal_s(git_remote_url(_remote), orig_url); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, NULL)); + cl_assert_equal_s(url.ptr, orig_url); + git_str_clear(&url); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, NULL)); + cl_assert_equal_s(url.ptr, orig_url); + git_str_clear(&url); + + /* Setting the instance url updates the fetch and push URLs */ + git_remote_set_instance_url(_remote, "https://github.com/new/remote/url"); + cl_assert_equal_s(git_remote_url(_remote), "https://github.com/new/remote/url"); + cl_assert_equal_p(git_remote_pushurl(_remote), NULL); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, NULL)); + cl_assert_equal_s(url.ptr, "https://github.com/new/remote/url"); + git_str_clear(&url); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, NULL)); + cl_assert_equal_s(url.ptr, "https://github.com/new/remote/url"); + git_str_clear(&url); + + /* Setting the instance push url updates only the push URL */ + git_remote_set_instance_pushurl(_remote, "https://github.com/new/push/url"); + cl_assert_equal_s(git_remote_url(_remote), "https://github.com/new/remote/url"); + cl_assert_equal_s(git_remote_pushurl(_remote), "https://github.com/new/push/url"); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, NULL)); + cl_assert_equal_s(url.ptr, "https://github.com/new/remote/url"); + git_str_clear(&url); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, NULL)); + cl_assert_equal_s(url.ptr, "https://github.com/new/push/url"); + git_str_clear(&url); + + git_str_dispose(&url); +} + +void test_network_remote_remotes__pushurl(void) +{ + const char *name = git_remote_name(_remote); + git_remote *mod; + + cl_git_pass(git_remote_set_pushurl(_repo, name, "git://github.com/libgit2/notlibgit2")); + cl_git_pass(git_remote_lookup(&mod, _repo, name)); + cl_assert_equal_s(git_remote_pushurl(mod), "git://github.com/libgit2/notlibgit2"); + git_remote_free(mod); + + cl_git_pass(git_remote_set_pushurl(_repo, name, NULL)); + cl_git_pass(git_remote_lookup(&mod, _repo, name)); + cl_assert(git_remote_pushurl(mod) == NULL); + git_remote_free(mod); +} + +void test_network_remote_remotes__error_when_not_found(void) +{ + git_remote *r; + cl_git_fail_with(git_remote_lookup(&r, _repo, "does-not-exist"), GIT_ENOTFOUND); + + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_CONFIG); +} + +void test_network_remote_remotes__error_when_no_push_available(void) +{ + git_remote *r; + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + char *specs = { + "refs/heads/master", + }; + git_strarray arr = { + &specs, + 1, + }; + + + cl_git_pass(git_remote_create_anonymous(&r, _repo, cl_fixture("testrepo.git"))); + + callbacks.transport = git_transport_local; + cl_git_pass(git_remote_connect(r, GIT_DIRECTION_PUSH, &callbacks, NULL, NULL)); + + /* Make sure that push is really not available */ + r->transport->push = NULL; + + cl_git_fail_with(-1, git_remote_upload(r, &arr, NULL)); + + git_remote_free(r); +} + +void test_network_remote_remotes__refspec_parsing(void) +{ + cl_assert_equal_s(git_refspec_src(_refspec), "refs/heads/*"); + cl_assert_equal_s(git_refspec_dst(_refspec), "refs/remotes/test/*"); +} + +void test_network_remote_remotes__add_fetchspec(void) +{ + size_t size; + + size = git_remote_refspec_count(_remote); + + cl_git_pass(git_remote_add_fetch(_repo, "test", "refs/*:refs/*")); + size++; + + git_remote_free(_remote); + cl_git_pass(git_remote_lookup(&_remote, _repo, "test")); + + cl_assert_equal_i((int)size, (int)git_remote_refspec_count(_remote)); + + _refspec = git_remote_get_refspec(_remote, size - 1); + cl_assert_equal_s(git_refspec_src(_refspec), "refs/*"); + cl_assert_equal_s(git_refspec_dst(_refspec), "refs/*"); + cl_assert_equal_s(git_refspec_string(_refspec), "refs/*:refs/*"); + cl_assert_equal_b(_refspec->push, false); + + cl_git_fail_with(GIT_EINVALIDSPEC, git_remote_add_fetch(_repo, "test", "refs/*/foo/*:refs/*")); +} + +void test_network_remote_remotes__dup(void) +{ + git_strarray array; + git_remote *dup; + + cl_git_pass(git_remote_dup(&dup, _remote)); + + cl_assert_equal_s(git_remote_name(dup), git_remote_name(_remote)); + cl_assert_equal_s(git_remote_url(dup), git_remote_url(_remote)); + cl_assert_equal_s(git_remote_pushurl(dup), git_remote_pushurl(_remote)); + + cl_git_pass(git_remote_get_fetch_refspecs(&array, _remote)); + cl_assert_equal_i(1, (int)array.count); + cl_assert_equal_s("+refs/heads/*:refs/remotes/test/*", array.strings[0]); + git_strarray_dispose(&array); + + cl_git_pass(git_remote_get_push_refspecs(&array, _remote)); + cl_assert_equal_i(0, (int)array.count); + git_strarray_dispose(&array); + + git_remote_free(dup); +} + +void test_network_remote_remotes__add_pushspec(void) +{ + size_t size; + + size = git_remote_refspec_count(_remote); + + cl_git_pass(git_remote_add_push(_repo, "test", "refs/*:refs/*")); + size++; + + git_remote_free(_remote); + cl_git_pass(git_remote_lookup(&_remote, _repo, "test")); + + cl_assert_equal_i((int)size, (int)git_remote_refspec_count(_remote)); + + _refspec = git_remote_get_refspec(_remote, size - 1); + cl_assert_equal_s(git_refspec_src(_refspec), "refs/*"); + cl_assert_equal_s(git_refspec_dst(_refspec), "refs/*"); + cl_assert_equal_s(git_refspec_string(_refspec), "refs/*:refs/*"); + + cl_assert_equal_b(_refspec->push, true); +} + +void test_network_remote_remotes__fnmatch(void) +{ + cl_assert(git_refspec_src_matches(_refspec, "refs/heads/master")); + cl_assert(git_refspec_src_matches(_refspec, "refs/heads/multi/level/branch")); +} + +void test_network_remote_remotes__transform(void) +{ + git_buf ref = GIT_BUF_INIT; + + cl_git_pass(git_refspec_transform(&ref, _refspec, "refs/heads/master")); + cl_assert_equal_s(ref.ptr, "refs/remotes/test/master"); + git_buf_dispose(&ref); +} + +void test_network_remote_remotes__transform_destination_to_source(void) +{ + git_buf ref = GIT_BUF_INIT; + + cl_git_pass(git_refspec_rtransform(&ref, _refspec, "refs/remotes/test/master")); + cl_assert_equal_s(ref.ptr, "refs/heads/master"); + git_buf_dispose(&ref); +} + +void test_network_remote_remotes__missing_refspecs(void) +{ + git_config *cfg; + + git_remote_free(_remote); + _remote = NULL; + + cl_git_pass(git_repository_config(&cfg, _repo)); + cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); + cl_git_pass(git_remote_lookup(&_remote, _repo, "specless")); + + git_config_free(cfg); +} + +void test_network_remote_remotes__nonmatch_upstream_refspec(void) +{ + git_config *config; + git_remote *remote; + char *specstr[] = { + "refs/tags/*:refs/tags/*", + }; + git_strarray specs = { + specstr, + 1, + }; + + cl_git_pass(git_remote_create(&remote, _repo, "taggy", git_repository_path(_repo))); + + /* + * Set the current branch's upstream remote to a dummy ref so we call into the code + * which tries to check for the current branch's upstream in the refspecs + */ + cl_git_pass(git_repository_config(&config, _repo)); + cl_git_pass(git_config_set_string(config, "branch.master.remote", "taggy")); + cl_git_pass(git_config_set_string(config, "branch.master.merge", "refs/heads/foo")); + + cl_git_pass(git_remote_fetch(remote, &specs, NULL, NULL)); + + git_remote_free(remote); +} + +void test_network_remote_remotes__list(void) +{ + git_strarray list; + git_config *cfg; + + cl_git_pass(git_remote_list(&list, _repo)); + cl_assert(list.count == 5); + git_strarray_dispose(&list); + + cl_git_pass(git_repository_config(&cfg, _repo)); + + /* Create a new remote */ + cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); + + /* Update a remote (previously without any url/pushurl entry) */ + cl_git_pass(git_config_set_string(cfg, "remote.no-remote-url.pushurl", "http://example.com")); + + cl_git_pass(git_remote_list(&list, _repo)); + cl_assert(list.count == 7); + git_strarray_dispose(&list); + + git_config_free(cfg); +} + +void test_network_remote_remotes__loading_a_missing_remote_returns_ENOTFOUND(void) +{ + git_remote_free(_remote); + _remote = NULL; + + cl_assert_equal_i(GIT_ENOTFOUND, git_remote_lookup(&_remote, _repo, "just-left-few-minutes-ago")); +} + +void test_network_remote_remotes__loading_with_an_invalid_name_returns_EINVALIDSPEC(void) +{ + git_remote_free(_remote); + _remote = NULL; + + cl_assert_equal_i(GIT_EINVALIDSPEC, git_remote_lookup(&_remote, _repo, "Inv@{id")); +} + +/* + * $ git remote add addtest http://github.com/libgit2/libgit2 + * + * $ cat .git/config + * [...] + * [remote "addtest"] + * url = http://github.com/libgit2/libgit2 + * fetch = +refs/heads/\*:refs/remotes/addtest/\* + */ +void test_network_remote_remotes__add(void) +{ + git_remote_free(_remote); + _remote = NULL; + + cl_git_pass(git_remote_create(&_remote, _repo, "addtest", "http://github.com/libgit2/libgit2")); + cl_assert_equal_i(GIT_REMOTE_DOWNLOAD_TAGS_AUTO, git_remote_autotag(_remote)); + + git_remote_free(_remote); + _remote = NULL; + + cl_git_pass(git_remote_lookup(&_remote, _repo, "addtest")); + cl_assert_equal_i(GIT_REMOTE_DOWNLOAD_TAGS_AUTO, git_remote_autotag(_remote)); + + _refspec = git_vector_get(&_remote->refspecs, 0); + cl_assert_equal_s("refs/heads/*", git_refspec_src(_refspec)); + cl_assert(git_refspec_force(_refspec) == 1); + cl_assert_equal_s("refs/remotes/addtest/*", git_refspec_dst(_refspec)); + cl_assert_equal_s(git_remote_url(_remote), "http://github.com/libgit2/libgit2"); +} + +void test_network_remote_remotes__tagopt(void) +{ + const char *name = git_remote_name(_remote); + + git_remote_set_autotag(_repo, name, GIT_REMOTE_DOWNLOAD_TAGS_ALL); + assert_config_entry_value(_repo, "remote.test.tagopt", "--tags"); + + git_remote_set_autotag(_repo, name, GIT_REMOTE_DOWNLOAD_TAGS_NONE); + assert_config_entry_value(_repo, "remote.test.tagopt", "--no-tags"); + + git_remote_set_autotag(_repo, name, GIT_REMOTE_DOWNLOAD_TAGS_AUTO); + assert_config_entry_existence(_repo, "remote.test.tagopt", false); +} + +void test_network_remote_remotes__can_load_with_an_empty_url(void) +{ + git_remote *remote = NULL; + + cl_git_pass(git_remote_lookup(&remote, _repo, "empty-remote-url")); + + cl_assert(remote->url == NULL); + cl_assert(remote->pushurl == NULL); + + cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_INVALID); + + git_remote_free(remote); +} + +void test_network_remote_remotes__can_load_with_only_an_empty_pushurl(void) +{ + git_remote *remote = NULL; + + cl_git_pass(git_remote_lookup(&remote, _repo, "empty-remote-pushurl")); + + cl_assert(remote->url == NULL); + cl_assert(remote->pushurl == NULL); + + cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + + git_remote_free(remote); +} + +void test_network_remote_remotes__returns_ENOTFOUND_when_neither_url_nor_pushurl(void) +{ + git_remote *remote = NULL; + + cl_git_fail_with( + git_remote_lookup(&remote, _repo, "no-remote-url"), GIT_ENOTFOUND); +} + +static const char *fetch_refspecs[] = { + "+refs/heads/*:refs/remotes/origin/*", + "refs/tags/*:refs/tags/*", + "+refs/pull/*:refs/pull/*", +}; + +static const char *push_refspecs[] = { + "refs/heads/*:refs/heads/*", + "refs/tags/*:refs/tags/*", + "refs/notes/*:refs/notes/*", +}; + +void test_network_remote_remotes__query_refspecs(void) +{ + git_remote *remote; + git_strarray array; + int i; + + cl_git_pass(git_remote_create_with_fetchspec(&remote, _repo, "query", "git://github.com/libgit2/libgit2", NULL)); + git_remote_free(remote); + + for (i = 0; i < 3; i++) { + cl_git_pass(git_remote_add_fetch(_repo, "query", fetch_refspecs[i])); + cl_git_pass(git_remote_add_push(_repo, "query", push_refspecs[i])); + } + + cl_git_pass(git_remote_lookup(&remote, _repo, "query")); + + cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); + for (i = 0; i < 3; i++) { + cl_assert_equal_s(fetch_refspecs[i], array.strings[i]); + } + git_strarray_dispose(&array); + + cl_git_pass(git_remote_get_push_refspecs(&array, remote)); + for (i = 0; i < 3; i++) { + cl_assert_equal_s(push_refspecs[i], array.strings[i]); + } + git_strarray_dispose(&array); + + git_remote_free(remote); + git_remote_delete(_repo, "test"); +} diff --git a/tests/libgit2/network/remote/rename.c b/tests/libgit2/network/remote/rename.c new file mode 100644 index 000000000..1fd2affba --- /dev/null +++ b/tests/libgit2/network/remote/rename.c @@ -0,0 +1,245 @@ +#include "clar_libgit2.h" +#include "config/config_helpers.h" + +#include "repository.h" + +static git_repository *_repo; +static const char *_remote_name = "test"; + +void test_network_remote_rename__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_network_remote_rename__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_network_remote_rename__renaming_a_remote_moves_related_configuration_section(void) +{ + git_strarray problems = {0}; + + assert_config_entry_existence(_repo, "remote.test.fetch", true); + assert_config_entry_existence(_repo, "remote.just/renamed.fetch", false); + + cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_dispose(&problems); + + assert_config_entry_existence(_repo, "remote.test.fetch", false); + assert_config_entry_existence(_repo, "remote.just/renamed.fetch", true); +} + +void test_network_remote_rename__renaming_a_remote_updates_branch_related_configuration_entries(void) +{ + git_strarray problems = {0}; + + assert_config_entry_value(_repo, "branch.master.remote", "test"); + + cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_dispose(&problems); + + assert_config_entry_value(_repo, "branch.master.remote", "just/renamed"); +} + +void test_network_remote_rename__renaming_a_remote_updates_default_fetchrefspec(void) +{ + git_strarray problems = {0}; + + cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_dispose(&problems); + + assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/heads/*:refs/remotes/just/renamed/*"); +} + +void test_network_remote_rename__renaming_a_remote_without_a_fetchrefspec_doesnt_create_one(void) +{ + git_config *config; + git_remote *remote; + git_strarray problems = {0}; + + cl_git_pass(git_repository_config__weakptr(&config, _repo)); + cl_git_pass(git_config_delete_entry(config, "remote.test.fetch")); + + cl_git_pass(git_remote_lookup(&remote, _repo, "test")); + git_remote_free(remote); + + assert_config_entry_existence(_repo, "remote.test.fetch", false); + + cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_dispose(&problems); + + assert_config_entry_existence(_repo, "remote.just/renamed.fetch", false); +} + +void test_network_remote_rename__renaming_a_remote_notifies_of_non_default_fetchrefspec(void) +{ + git_config *config; + git_remote *remote; + git_strarray problems = {0}; + + cl_git_pass(git_repository_config__weakptr(&config, _repo)); + cl_git_pass(git_config_set_string(config, "remote.test.fetch", "+refs/*:refs/*")); + cl_git_pass(git_remote_lookup(&remote, _repo, "test")); + git_remote_free(remote); + + cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); + cl_assert_equal_i(1, problems.count); + cl_assert_equal_s("+refs/*:refs/*", problems.strings[0]); + git_strarray_dispose(&problems); + + assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/*:refs/*"); + + git_strarray_dispose(&problems); +} + +void test_network_remote_rename__new_name_can_contain_dots(void) +{ + git_strarray problems = {0}; + + cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just.renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_dispose(&problems); + assert_config_entry_existence(_repo, "remote.just.renamed.fetch", true); +} + +void test_network_remote_rename__new_name_must_conform_to_reference_naming_conventions(void) +{ + git_strarray problems = {0}; + + cl_assert_equal_i( + GIT_EINVALIDSPEC, + git_remote_rename(&problems, _repo, _remote_name, "new@{name")); +} + +void test_network_remote_rename__renamed_name_is_persisted(void) +{ + git_remote *renamed; + git_repository *another_repo; + git_strarray problems = {0}; + + cl_git_fail(git_remote_lookup(&renamed, _repo, "just/renamed")); + + cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_dispose(&problems); + + cl_git_pass(git_repository_open(&another_repo, "testrepo.git")); + cl_git_pass(git_remote_lookup(&renamed, _repo, "just/renamed")); + + git_remote_free(renamed); + git_repository_free(another_repo); +} + +void test_network_remote_rename__cannot_overwrite_an_existing_remote(void) +{ + git_strarray problems = {0}; + + cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(&problems, _repo, _remote_name, "test")); + cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(&problems, _repo, _remote_name, "test_with_pushurl")); +} + +void test_network_remote_rename__renaming_a_remote_moves_the_underlying_reference(void) +{ + git_reference *underlying; + git_strarray problems = {0}; + + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&underlying, _repo, "refs/remotes/just/renamed")); + cl_git_pass(git_reference_lookup(&underlying, _repo, "refs/remotes/test/master")); + git_reference_free(underlying); + + cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_dispose(&problems); + + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&underlying, _repo, "refs/remotes/test/master")); + cl_git_pass(git_reference_lookup(&underlying, _repo, "refs/remotes/just/renamed/master")); + git_reference_free(underlying); +} + +void test_network_remote_rename__overwrite_ref_in_target(void) +{ + git_oid id; + char idstr[GIT_OID_HEXSZ + 1] = {0}; + git_reference *ref; + git_branch_t btype; + git_branch_iterator *iter; + git_strarray problems = {0}; + + cl_git_pass(git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); + cl_git_pass(git_reference_create(&ref, _repo, "refs/remotes/renamed/master", &id, 1, NULL)); + git_reference_free(ref); + + cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_dispose(&problems); + + /* make sure there's only one remote-tracking branch */ + cl_git_pass(git_branch_iterator_new(&iter, _repo, GIT_BRANCH_REMOTE)); + cl_git_pass(git_branch_next(&ref, &btype, iter)); + cl_assert_equal_s("refs/remotes/renamed/master", git_reference_name(ref)); + git_oid_fmt(idstr, git_reference_target(ref)); + cl_assert_equal_s("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", idstr); + git_reference_free(ref); + + cl_git_fail_with(GIT_ITEROVER, git_branch_next(&ref, &btype, iter)); + git_branch_iterator_free(iter); +} + +void test_network_remote_rename__nonexistent_returns_enotfound(void) +{ + git_strarray problems = {0}; + + int err = git_remote_rename(&problems, _repo, "nonexistent", "renamed"); + + cl_assert_equal_i(GIT_ENOTFOUND, err); +} + +void test_network_remote_rename__symref_head(void) +{ + int error; + git_reference *ref; + git_branch_t btype; + git_branch_iterator *iter; + git_strarray problems = {0}; + char idstr[GIT_OID_HEXSZ + 1] = {0}; + git_vector refs; + + cl_git_pass(git_reference_symbolic_create(&ref, _repo, "refs/remotes/test/HEAD", "refs/remotes/test/master", 0, NULL)); + git_reference_free(ref); + + cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_dispose(&problems); + + cl_git_pass(git_vector_init(&refs, 2, (git_vector_cmp) git_reference_cmp)); + cl_git_pass(git_branch_iterator_new(&iter, _repo, GIT_BRANCH_REMOTE)); + + while ((error = git_branch_next(&ref, &btype, iter)) == 0) { + cl_git_pass(git_vector_insert(&refs, ref)); + } + cl_assert_equal_i(GIT_ITEROVER, error); + git_vector_sort(&refs); + + cl_assert_equal_i(2, refs.length); + + ref = git_vector_get(&refs, 0); + cl_assert_equal_s("refs/remotes/renamed/HEAD", git_reference_name(ref)); + cl_assert_equal_s("refs/remotes/renamed/master", git_reference_symbolic_target(ref)); + git_reference_free(ref); + + ref = git_vector_get(&refs, 1); + cl_assert_equal_s("refs/remotes/renamed/master", git_reference_name(ref)); + git_oid_fmt(idstr, git_reference_target(ref)); + cl_assert_equal_s("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", idstr); + git_reference_free(ref); + + git_vector_free(&refs); + + cl_git_fail_with(GIT_ITEROVER, git_branch_next(&ref, &btype, iter)); + git_branch_iterator_free(iter); +} diff --git a/tests/libgit2/network/url/joinpath.c b/tests/libgit2/network/url/joinpath.c new file mode 100644 index 000000000..bf4557138 --- /dev/null +++ b/tests/libgit2/network/url/joinpath.c @@ -0,0 +1,194 @@ +#include "clar_libgit2.h" +#include "net.h" +#include "netops.h" + +static git_net_url source, target; + +void test_network_url_joinpath__initialize(void) +{ + memset(&source, 0, sizeof(source)); + memset(&target, 0, sizeof(target)); +} + +void test_network_url_joinpath__cleanup(void) +{ + git_net_url_dispose(&source); + git_net_url_dispose(&target); +} + +void test_network_url_joinpath__target_paths_and_queries(void) +{ + cl_git_pass(git_net_url_parse(&source, "http://example.com/a/b")); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d")); + cl_assert_equal_s(target.path, "/a/b/c/d"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d?foo")); + cl_assert_equal_s(target.path, "/a/b/c/d"); + cl_assert_equal_s(target.query, "foo"); + git_net_url_dispose(&target); +} + +void test_network_url_joinpath__source_query_removed(void) +{ + cl_git_pass(git_net_url_parse(&source, "http://example.com/a/b?query&one&two")); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d")); + cl_assert_equal_s(target.path, "/a/b/c/d"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d?foo")); + cl_assert_equal_s(target.path, "/a/b/c/d"); + cl_assert_equal_s(target.query, "foo"); + git_net_url_dispose(&target); +} + +void test_network_url_joinpath__source_lacks_path(void) +{ + cl_git_pass(git_net_url_parse(&source, "http://example.com")); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/")); + cl_assert_equal_s(target.path, "/"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "")); + cl_assert_equal_s(target.path, "/"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "asdf")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar")); + cl_assert_equal_s(target.path, "/foo/bar"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello")); + cl_assert_equal_s(target.path, "/foo/bar"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); +} + +void test_network_url_joinpath__source_is_slash(void) +{ + cl_git_pass(git_net_url_parse(&source, "http://example.com/")); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/")); + cl_assert_equal_s(target.path, "/"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "")); + cl_assert_equal_s(target.path, "/"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "asdf")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar")); + cl_assert_equal_s(target.path, "/foo/bar"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello")); + cl_assert_equal_s(target.path, "/foo/bar"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); +} + + +void test_network_url_joinpath__source_has_query(void) +{ + cl_git_pass(git_net_url_parse(&source, "http://example.com?query")); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/")); + cl_assert_equal_s(target.path, "/"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "")); + cl_assert_equal_s(target.path, "/"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "asdf")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar")); + cl_assert_equal_s(target.path, "/foo/bar"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello")); + cl_assert_equal_s(target.path, "/foo/bar"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); +} + + +void test_network_url_joinpath__empty_query_ignored(void) +{ + cl_git_pass(git_net_url_parse(&source, "http://example.com/foo")); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/bar/baz?")); + cl_assert_equal_s(target.path, "/foo/bar/baz"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); +} diff --git a/tests/libgit2/network/url/parse.c b/tests/libgit2/network/url/parse.c new file mode 100644 index 000000000..8149ba52c --- /dev/null +++ b/tests/libgit2/network/url/parse.c @@ -0,0 +1,557 @@ +#include "clar_libgit2.h" +#include "net.h" + +static git_net_url conndata; + +void test_network_url_parse__initialize(void) +{ + memset(&conndata, 0, sizeof(conndata)); +} + +void test_network_url_parse__cleanup(void) +{ + git_net_url_dispose(&conndata); +} + +/* Hostname */ + +void test_network_url_parse__hostname_trivial(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://example.com/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__hostname_root(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://example.com/")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__hostname_implied_root(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://example.com")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__hostname_implied_root_custom_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://example.com:42")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "42"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_parse__hostname_implied_root_empty_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://example.com:")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__hostname_encoded_password(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass%2fis%40bad@hostname.com:1234/")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "hostname.com"); + cl_assert_equal_s(conndata.port, "1234"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass/is@bad"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_parse__hostname_user(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user@example.com/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__hostname_user_pass(void) +{ + /* user:pass@hostname.tld/resource */ + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass@example.com/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__hostname_port(void) +{ + /* hostname.tld:port/resource */ + cl_git_pass(git_net_url_parse(&conndata, + "https://example.com:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_parse__hostname_empty_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://example.com:/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__hostname_user_port(void) +{ + /* user@hostname.tld:port/resource */ + cl_git_pass(git_net_url_parse(&conndata, + "https://user@example.com:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_parse__hostname_user_pass_port(void) +{ + /* user:pass@hostname.tld:port/resource */ + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass@example.com:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +/* IPv4 addresses */ + +void test_network_url_parse__ipv4_trivial(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv4_root(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1/")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv4_implied_root(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv4_implied_root_custom_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1:42")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "42"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_parse__ipv4_implied_root_empty_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1:")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv4_encoded_password(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass%2fis%40bad@192.168.1.1:1234/")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "1234"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass/is@bad"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_parse__ipv4_user(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user@192.168.1.1/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv4_user_pass(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass@192.168.1.1/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv4_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://192.168.1.1:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_parse__ipv4_empty_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1:/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv4_user_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user@192.168.1.1:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_parse__ipv4_user_pass_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass@192.168.1.1:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +/* IPv6 addresses */ + +void test_network_url_parse__ipv6_trivial(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv6_root(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]/")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv6_implied_root(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv6_implied_root_custom_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]:42")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "42"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_parse__ipv6_implied_root_empty_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]:")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv6_encoded_password(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass%2fis%40bad@[fe80::dcad:beff:fe00:0001]:1234/")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "1234"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass/is@bad"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_parse__ipv6_user(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user@[fe80::dcad:beff:fe00:0001]/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv6_user_pass(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass@[fe80::dcad:beff:fe00:0001]/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv6_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://[fe80::dcad:beff:fe00:0001]:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_parse__ipv6_empty_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]:/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_parse__ipv6_user_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user@[fe80::dcad:beff:fe00:0001]:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_parse__ipv6_user_pass_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass@[fe80::dcad:beff:fe00:0001]:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_parse__ipv6_invalid_addresses(void) +{ + /* Opening bracket missing */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://fe80::dcad:beff:fe00:0001]/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://fe80::dcad:beff:fe00:0001]/")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://fe80::dcad:beff:fe00:0001]")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://fe80::dcad:beff:fe00:0001]:42")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://fe80::dcad:beff:fe00:0001]:")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user:pass%2fis%40bad@fe80::dcad:beff:fe00:0001]:1234/")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user@fe80::dcad:beff:fe00:0001]/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user:pass@fe80::dcad:beff:fe00:0001]/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://fe80::dcad:beff:fe00:0001]:9191/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://fe80::dcad:beff:fe00:0001]:/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user@fe80::dcad:beff:fe00:0001]:9191/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user:pass@fe80::dcad:beff:fe00:0001]:9191/resource")); + + /* Closing bracket missing */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://[fe80::dcad:beff:fe00:0001/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://[fe80::dcad:beff:fe00:0001/")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://[fe80::dcad:beff:fe00:0001")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://[fe80::dcad:beff:fe00:0001:42")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://[fe80::dcad:beff:fe00:0001:")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user:pass%2fis%40bad@[fe80::dcad:beff:fe00:0001:1234/")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user@[fe80::dcad:beff:fe00:0001/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user:pass@[fe80::dcad:beff:fe00:0001/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://[fe80::dcad:beff:fe00:0001:9191/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://[fe80::dcad:beff:fe00:0001:/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user@[fe80::dcad:beff:fe00:0001:9191/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user:pass@[fe80::dcad:beff:fe00:0001:9191/resource")); + /* Both brackets missing */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://fe80::dcad:beff:fe00:0001/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://fe80::dcad:beff:fe00:0001/")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://fe80::dcad:beff:fe00:0001")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://fe80::dcad:beff:fe00:0001:42")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://fe80::dcad:beff:fe00:0001:")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user:pass%2fis%40bad@fe80::dcad:beff:fe00:0001:1234/")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user@fe80::dcad:beff:fe00:0001/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user:pass@fe80::dcad:beff:fe00:0001/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://fe80::dcad:beff:fe00:0001:9191/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "http://fe80::dcad:beff:fe00:0001:/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user@fe80::dcad:beff:fe00:0001:9191/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "https://user:pass@fe80::dcad:beff:fe00:0001:9191/resource")); + + /* Invalid character inside address */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, "http://[fe8o::dcad:beff:fe00:0001]/resource")); +} diff --git a/tests/libgit2/network/url/pattern.c b/tests/libgit2/network/url/pattern.c new file mode 100644 index 000000000..5e4495f70 --- /dev/null +++ b/tests/libgit2/network/url/pattern.c @@ -0,0 +1,103 @@ +#include "clar_libgit2.h" +#include "net.h" + +struct url_pattern { + const char *url; + const char *pattern; + bool matches; +}; + +void test_network_url_pattern__single(void) +{ + git_net_url url; + size_t i; + + struct url_pattern url_patterns[] = { + /* Wildcard matches */ + { "https://example.com/", "", false }, + { "https://example.com/", "*", true }, + + /* Literal and wildcard matches */ + { "https://example.com/", "example.com", true }, + { "https://example.com/", ".example.com", true }, + { "https://example.com/", "*.example.com", true }, + { "https://www.example.com/", "www.example.com", true }, + { "https://www.example.com/", ".example.com", true }, + { "https://www.example.com/", "*.example.com", true }, + + /* Literal and wildcard failures */ + { "https://example.com/", "example.org", false }, + { "https://example.com/", ".example.org", false }, + { "https://example.com/", "*.example.org", false }, + { "https://foo.example.com/", "www.example.com", false }, + + /* + * A port in the pattern is optional; if no port is + * present, it matches *all* ports. + */ + { "https://example.com/", "example.com:443", true }, + { "https://example.com/", "example.com:80", false }, + { "https://example.com:1443/", "example.com", true }, + + /* Failures with similar prefix/suffix */ + { "https://texample.com/", "example.com", false }, + { "https://example.com/", "mexample.com", false }, + { "https://example.com:44/", "example.com:443", false }, + { "https://example.com:443/", "example.com:44", false }, + }; + + for (i = 0; i < ARRAY_SIZE(url_patterns); i++) { + cl_git_pass(git_net_url_parse(&url, url_patterns[i].url)); + cl_assert_(git_net_url_matches_pattern(&url, url_patterns[i].pattern) == url_patterns[i].matches, url_patterns[i].pattern); + git_net_url_dispose(&url); + } +} + +void test_network_url_pattern__list(void) +{ + git_net_url url; + size_t i; + + struct url_pattern url_patterns[] = { + /* Wildcard matches */ + { "https://example.com/", "", false }, + { "https://example.com/", "*", true }, + { "https://example.com/", ",example.com,", true }, + { "https://example.com/", "foo,,example.com,,bar", true }, + { "https://example.com/", "foo,,zzz,,*,,bar", true }, + + /* Literals */ + { "https://example.com/", "example.com", true }, + { "https://example.com/", "foo.bar,example.com", true }, + { "https://example.com/", "foo.bar", false }, + { "https://example.com/", "foo.bar,example.org", false }, + { "https://www.example.com/", "foo.example.com,www.example.com,bar.example.com", true }, + { "https://www.example.com/", "foo.example.com,baz.example.com,bar.example.com", false }, + { "https://foo.example.com/", "www.example.com", false }, + { "https://foo.example.com/", "bar.example.com,www.example.com,", false }, + + /* Wildcards */ + { "https://example.com/", ".example.com", true }, + { "https://example.com/", "*.example.com", true }, + { "https://example.com/", "foo.com,bar.com,.example.com", true }, + { "https://example.com/", ".foo.com,.bar.com,.example.com", true }, + { "https://example.com/", ".foo.com,.bar.com,asdf.com", false }, + { "https://example.com/", "*.foo,*.bar,*.example.com,*.asdf", true }, + { "https://example.com/", "*.foo,*.bar,*.asdf", false }, + + + /* Ports! */ + { "https://example.com/", "example.com:443", true }, + { "https://example.com/", "example.com:42,example.com:443,example.com:99", true }, + { "https://example.com/", "example.com:42,example.com:80,example.org:443", false }, + { "https://example.com:1443/", "example.com", true }, + { "https://example.com:44/", "example.com:443", false }, + { "https://example.com:443/", "example.com:44", false }, + }; + + for (i = 0; i < ARRAY_SIZE(url_patterns); i++) { + cl_git_pass(git_net_url_parse(&url, url_patterns[i].url)); + cl_assert_(git_net_url_matches_pattern_list(&url, url_patterns[i].pattern) == url_patterns[i].matches, url_patterns[i].pattern); + git_net_url_dispose(&url); + } +} diff --git a/tests/libgit2/network/url/redirect.c b/tests/libgit2/network/url/redirect.c new file mode 100644 index 000000000..a94db7daf --- /dev/null +++ b/tests/libgit2/network/url/redirect.c @@ -0,0 +1,147 @@ +#include "clar_libgit2.h" +#include "net.h" +#include "netops.h" + +static git_net_url conndata; + +void test_network_url_redirect__initialize(void) +{ + memset(&conndata, 0, sizeof(conndata)); +} + +void test_network_url_redirect__cleanup(void) +{ + git_net_url_dispose(&conndata); +} + +void test_network_url_redirect__redirect_http(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "http://example.com/foo/bar/baz")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "http://example.com/foo/bar/baz", false, "bar/baz")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/foo/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_url_redirect__redirect_ssl(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://example.com/foo/bar/baz")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "https://example.com/foo/bar/baz", false, "bar/baz")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/foo/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_url_redirect__redirect_leaves_root_path(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://example.com/foo/bar/baz")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "https://example.com/foo/bar/baz", false, "/foo/bar/baz")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_url_redirect__redirect_encoded_username_password(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", false, "bar/baz")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/foo/"); + cl_assert_equal_s(conndata.username, "user/name"); + cl_assert_equal_s(conndata.password, "pass@word%zyx%v"); +} + +void test_network_url_redirect__redirect_cross_host_allowed(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://bar.com/bar/baz")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "https://foo.com/bar/baz", true, NULL)); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "foo.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/bar/baz"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_url_redirect__redirect_cross_host_denied(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://bar.com/bar/baz")); + cl_git_fail_with(git_net_url_apply_redirect(&conndata, + "https://foo.com/bar/baz", false, NULL), -1); +} + +void test_network_url_redirect__redirect_http_downgrade_denied(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://foo.com/bar/baz")); + cl_git_fail_with(git_net_url_apply_redirect(&conndata, + "http://foo.com/bar/baz", true, NULL), -1); +} + +void test_network_url_redirect__redirect_relative(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "http://foo.com/bar/baz/biff")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "/zap/baz/biff?bam", true, NULL)); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "foo.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_url_redirect__redirect_relative_ssl(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://foo.com/bar/baz/biff")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "/zap/baz/biff?bam", true, NULL)); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "foo.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_url_redirect__service_query_no_query_params_in_location(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://foo.com/bar/info/refs?service=git-upload-pack")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "/baz/info/refs", true, "/info/refs?service=git-upload-pack")); + cl_assert_equal_s(conndata.path, "/baz"); +} + +void test_network_url_redirect__service_query_with_query_params_in_location(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://foo.com/bar/info/refs?service=git-upload-pack")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "/baz/info/refs?service=git-upload-pack", true, "/info/refs?service=git-upload-pack")); + cl_assert_equal_s(conndata.path, "/baz"); +} diff --git a/tests/libgit2/network/url/scp.c b/tests/libgit2/network/url/scp.c new file mode 100644 index 000000000..8cdc832ae --- /dev/null +++ b/tests/libgit2/network/url/scp.c @@ -0,0 +1,321 @@ +#include "clar_libgit2.h" +#include "net.h" + +static git_net_url conndata; + +void test_network_url_scp__initialize(void) +{ + memset(&conndata, 0, sizeof(conndata)); +} + +void test_network_url_scp__cleanup(void) +{ + git_net_url_dispose(&conndata); +} + +/* Hostname */ + +void test_network_url_scp__hostname_trivial(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "example.com:/resource")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__hostname_bracketed(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "[example.com]:/resource")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__hostname_root(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "example.com:/")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__hostname_user(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "git@example.com:/resource")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "git"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__hostname_user_bracketed(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "[git@example.com]:/resource")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "git"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__hostname_port(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "[example.com:42]:/resource")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "42"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_scp__hostname_user_port(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "[git@example.com:42]:/resource")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "42"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "git"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_scp__ipv4_trivial(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "192.168.99.88:/resource/a/b/c")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "192.168.99.88"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "/resource/a/b/c"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__ipv4_bracketed(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "[192.168.99.88]:/resource/a/b/c")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "192.168.99.88"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "/resource/a/b/c"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__ipv4_user(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "git@192.168.99.88:/resource/a/b/c")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "192.168.99.88"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "/resource/a/b/c"); + cl_assert_equal_s(conndata.username, "git"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__ipv4_port(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "[192.168.99.88:1111]:/resource/a/b/c")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "192.168.99.88"); + cl_assert_equal_s(conndata.port, "1111"); + cl_assert_equal_s(conndata.path, "/resource/a/b/c"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_scp__ipv4_user_port(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "[git@192.168.99.88:1111]:/resource/a/b/c")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "192.168.99.88"); + cl_assert_equal_s(conndata.port, "1111"); + cl_assert_equal_s(conndata.path, "/resource/a/b/c"); + cl_assert_equal_s(conndata.username, "git"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_scp__ipv6_trivial(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "[fe80::dcad:beff:fe00:0001]:/resource/foo")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "/resource/foo"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__ipv6_user(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "git@[fe80::dcad:beff:fe00:0001]:/resource/foo")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "/resource/foo"); + cl_assert_equal_s(conndata.username, "git"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__ipv6_port(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "[[fe80::dcad:beff:fe00:0001]:99]:/resource/foo")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]"); + cl_assert_equal_s(conndata.port, "99"); + cl_assert_equal_s(conndata.path, "/resource/foo"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_scp__ipv6_user_port(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "[git@[fe80::dcad:beff:fe00:0001]:99]:/resource/foo")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]"); + cl_assert_equal_s(conndata.port, "99"); + cl_assert_equal_s(conndata.path, "/resource/foo"); + cl_assert_equal_s(conndata.username, "git"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_url_scp__hexhost_and_port(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "[fe:22]:/resource/foo")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "fe"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "/resource/foo"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__malformed_ipv6_one(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "fe80::dcad:beff:fe00:0001]:/resource")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "fe80"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, ":dcad:beff:fe00:0001]:/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__malformed_ipv6_two(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "[fe80::dcad:beff:fe00:0001]:42]:/resource")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "42]:/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__malformed_ipv6_with_user(void) +{ + cl_git_pass(git_net_url_parse_scp(&conndata, "git@[fe80::dcad:beff:fe00:0001]:42]:/resource")); + cl_assert_equal_s(conndata.scheme, "ssh"); + cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]"); + cl_assert_equal_s(conndata.port, "22"); + cl_assert_equal_s(conndata.path, "42]:/resource"); + cl_assert_equal_s(conndata.username, "git"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_url_scp__invalid_addresses(void) +{ + /* Path is required */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "example.com")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "example.com:")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[example.com:42]:")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[git@example.com:42]:")); + + /* Host is required */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + ":")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + ":foo")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "git@:foo")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[]:")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "git@[]:")); + + /* User is required if specified */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "@example.com:foo")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "@:foo")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[@localhost:22]:foo")); + + /* Port is required in brackets */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[example.com:]:foo")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[git@example.com:]:foo")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[fe:]:foo")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[@localhost]:foo")); + + /* Extra brackets are disallowed */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[git@[[fe80::dcad:beff:fe00:0001]]:42]:foo")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[[git@[fe80::dcad:beff:fe00:0001]]:42]:foo")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[[git@[fe80::dcad:beff:fe00:0001]:42]]:foo")); + + /* Closing bracket missing */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[fe80::dcad:beff:fe00:0001:/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[[fe80::dcad:beff:fe00:0001]:42:/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[git@[fe80::dcad:beff:fe00:0001]:42:/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, + "[git@[fe80::dcad:beff:fe00:0001:42]:/resource")); + + /* Invalid character inside address */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, + "[fe8o::dcad:beff:fe00:0001]:/resource")); +} diff --git a/tests/libgit2/network/url/valid.c b/tests/libgit2/network/url/valid.c new file mode 100644 index 000000000..2b2cb7ba4 --- /dev/null +++ b/tests/libgit2/network/url/valid.c @@ -0,0 +1,17 @@ +#include "clar_libgit2.h" +#include "net.h" + +void test_network_url_valid__test(void) +{ + cl_assert(git_net_str_is_url("http://example.com/")); + cl_assert(git_net_str_is_url("file://localhost/tmp/foo/")); + cl_assert(git_net_str_is_url("ssh://user@host:42/tmp")); + cl_assert(git_net_str_is_url("git+ssh://user@host:42/tmp")); + cl_assert(git_net_str_is_url("ssh+git://user@host:42/tmp")); + cl_assert(git_net_str_is_url("https://user:pass@example.com/foo/bar")); + + cl_assert(!git_net_str_is_url("host:foo.git")); + cl_assert(!git_net_str_is_url("host:/foo.git")); + cl_assert(!git_net_str_is_url("[host:42]:/foo.git")); + cl_assert(!git_net_str_is_url("[user@host:42]:/foo.git")); +} diff --git a/tests/libgit2/notes/notes.c b/tests/libgit2/notes/notes.c new file mode 100644 index 000000000..a36cddb8a --- /dev/null +++ b/tests/libgit2/notes/notes.c @@ -0,0 +1,658 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; +static git_signature *_sig; + +void test_notes_notes__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_signature_now(&_sig, "alice", "alice@example.com")); +} + +void test_notes_notes__cleanup(void) +{ + git_signature_free(_sig); + _sig = NULL; + + cl_git_sandbox_cleanup(); +} + +static void assert_note_equal(git_note *note, char *message, git_oid *note_oid) { + git_blob *blob; + + cl_assert_equal_s(git_note_message(note), message); + cl_assert_equal_oid(git_note_id(note), note_oid); + + cl_git_pass(git_blob_lookup(&blob, _repo, note_oid)); + cl_assert_equal_s(git_note_message(note), (const char *)git_blob_rawcontent(blob)); + + git_blob_free(blob); +} + +static void create_note(git_oid *note_oid, const char *canonical_namespace, const char *target_sha, const char *message) +{ + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, target_sha)); + cl_git_pass(git_note_create(note_oid, _repo, canonical_namespace, _sig, _sig, &oid, message, 0)); +} + +static struct { + const char *note_sha; + const char *annotated_object_sha; +} +list_expectations[] = { + { "1c73b1f51762155d357bcd1fd4f2c409ef80065b", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045" }, + { "1c73b1f51762155d357bcd1fd4f2c409ef80065b", "9fd738e8f7967c078dceed8190330fc8648ee56a" }, + { "257b43746b6b46caa4aa788376c647cce0a33e2b", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750" }, + { "1ec1c8e03f461f4f5d3f3702172483662e7223f3", "c47800c7266a2be04c571c04d5a6614691ea99bd" }, + { NULL, NULL } +}; + +#define EXPECTATIONS_COUNT (sizeof(list_expectations)/sizeof(list_expectations[0])) - 1 + +static int note_list_cb( + const git_oid *blob_id, const git_oid *annotated_obj_id, void *payload) +{ + git_oid expected_note_oid, expected_target_oid; + + unsigned int *count = (unsigned int *)payload; + + cl_assert(*count < EXPECTATIONS_COUNT); + + cl_git_pass(git_oid_fromstr(&expected_note_oid, list_expectations[*count].note_sha)); + cl_assert_equal_oid(&expected_note_oid, blob_id); + + cl_git_pass(git_oid_fromstr(&expected_target_oid, list_expectations[*count].annotated_object_sha)); + cl_assert_equal_oid(&expected_target_oid, annotated_obj_id); + + (*count)++; + + return 0; +} + +struct note_create_payload { + const char *note_oid; + const char *object_oid; + unsigned seen; +}; + +static int note_list_create_cb( + const git_oid *blob_oid, const git_oid *annotated_obj_id, void *payload) +{ + git_oid expected_note_oid, expected_target_oid; + struct note_create_payload *notes = payload; + size_t i; + + for (i = 0; notes[i].note_oid != NULL; i++) { + cl_git_pass(git_oid_fromstr(&expected_note_oid, notes[i].note_oid)); + + if (git_oid_cmp(&expected_note_oid, blob_oid) != 0) + continue; + + cl_git_pass(git_oid_fromstr(&expected_target_oid, notes[i].object_oid)); + + if (git_oid_cmp(&expected_target_oid, annotated_obj_id) != 0) + continue; + + notes[i].seen = 1; + return 0; + } + + cl_fail("Did not see expected note"); + return 0; +} + +static void assert_notes_seen(struct note_create_payload payload[], size_t n) +{ + size_t seen = 0, i; + + for (i = 0; payload[i].note_oid != NULL; i++) { + if (payload[i].seen) + seen++; + } + + cl_assert_equal_i(seen, n); +} + +void test_notes_notes__can_create_a_note(void) +{ + git_oid note_oid; + static struct note_create_payload can_create_a_note[] = { + { "1c9b1bc36730582a42d56eeee0dc58673d7ae869", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", 0 }, + { NULL, NULL, 0 } + }; + + create_note(¬e_oid, "refs/notes/i-can-see-dead-notes", can_create_a_note[0].object_oid, "I decorate 4a20\n"); + + cl_git_pass(git_note_foreach(_repo, "refs/notes/i-can-see-dead-notes", note_list_create_cb, &can_create_a_note)); + + assert_notes_seen(can_create_a_note, 1); +} + +void test_notes_notes__can_create_a_note_from_commit(void) +{ + git_oid oid; + git_oid notes_commit_out; + git_reference *ref; + static struct note_create_payload can_create_a_note_from_commit[] = { + { "1c9b1bc36730582a42d56eeee0dc58673d7ae869", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", 0 }, + { NULL, NULL, 0 } + }; + + cl_git_pass(git_oid_fromstr(&oid, can_create_a_note_from_commit[0].object_oid)); + + cl_git_pass(git_note_commit_create(¬es_commit_out, NULL, _repo, NULL, _sig, _sig, &oid, "I decorate 4a20\n", 1)); + + /* create_from_commit will not update any ref, + * so we must manually create the ref, that points to the commit */ + cl_git_pass(git_reference_create(&ref, _repo, "refs/notes/i-can-see-dead-notes", ¬es_commit_out, 0, NULL)); + + cl_git_pass(git_note_foreach(_repo, "refs/notes/i-can-see-dead-notes", note_list_create_cb, &can_create_a_note_from_commit)); + + assert_notes_seen(can_create_a_note_from_commit, 1); + + git_reference_free(ref); +} + + +/* Test that we can create a note from a commit, given an existing commit */ +void test_notes_notes__can_create_a_note_from_commit_given_an_existing_commit(void) +{ + git_oid oid; + git_oid notes_commit_out; + git_commit *existing_notes_commit = NULL; + git_reference *ref; + static struct note_create_payload can_create_a_note_from_commit_given_an_existing_commit[] = { + { "1c9b1bc36730582a42d56eeee0dc58673d7ae869", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", 0 }, + { "1aaf94147c21f981e0a20bf57b89137c5a6aae52", "9fd738e8f7967c078dceed8190330fc8648ee56a", 0 }, + { NULL, NULL, 0 } + }; + + cl_git_pass(git_oid_fromstr(&oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); + + cl_git_pass(git_note_commit_create(¬es_commit_out, NULL, _repo, NULL, _sig, _sig, &oid, "I decorate 4a20\n", 0)); + + cl_git_pass(git_oid_fromstr(&oid, "9fd738e8f7967c078dceed8190330fc8648ee56a")); + + git_commit_lookup(&existing_notes_commit, _repo, ¬es_commit_out); + + cl_assert(existing_notes_commit); + + cl_git_pass(git_note_commit_create(¬es_commit_out, NULL, _repo, existing_notes_commit, _sig, _sig, &oid, "I decorate 9fd7\n", 0)); + + /* create_from_commit will not update any ref, + * so we must manually create the ref, that points to the commit */ + cl_git_pass(git_reference_create(&ref, _repo, "refs/notes/i-can-see-dead-notes", ¬es_commit_out, 0, NULL)); + + cl_git_pass(git_note_foreach(_repo, "refs/notes/i-can-see-dead-notes", note_list_create_cb, &can_create_a_note_from_commit_given_an_existing_commit)); + + assert_notes_seen(can_create_a_note_from_commit_given_an_existing_commit, 2); + + git_commit_free(existing_notes_commit); + git_reference_free(ref); +} + +/* + * $ git notes --ref i-can-see-dead-notes add -m "I decorate a65f" a65fedf39aefe402d3bb6e24df4d4f5fe4547750 + * $ git notes --ref i-can-see-dead-notes add -m "I decorate c478" c47800c7266a2be04c571c04d5a6614691ea99bd + * $ git notes --ref i-can-see-dead-notes add -m "I decorate 9fd7 and 4a20" 9fd738e8f7967c078dceed8190330fc8648ee56a + * $ git notes --ref i-can-see-dead-notes add -m "I decorate 9fd7 and 4a20" 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + * + * $ git notes --ref i-can-see-dead-notes list + * 1c73b1f51762155d357bcd1fd4f2c409ef80065b 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + * 1c73b1f51762155d357bcd1fd4f2c409ef80065b 9fd738e8f7967c078dceed8190330fc8648ee56a + * 257b43746b6b46caa4aa788376c647cce0a33e2b a65fedf39aefe402d3bb6e24df4d4f5fe4547750 + * 1ec1c8e03f461f4f5d3f3702172483662e7223f3 c47800c7266a2be04c571c04d5a6614691ea99bd + * + * $ git ls-tree refs/notes/i-can-see-dead-notes + * 100644 blob 1c73b1f51762155d357bcd1fd4f2c409ef80065b 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + * 100644 blob 1c73b1f51762155d357bcd1fd4f2c409ef80065b 9fd738e8f7967c078dceed8190330fc8648ee56a + * 100644 blob 257b43746b6b46caa4aa788376c647cce0a33e2b a65fedf39aefe402d3bb6e24df4d4f5fe4547750 + * 100644 blob 1ec1c8e03f461f4f5d3f3702172483662e7223f3 c47800c7266a2be04c571c04d5a6614691ea99bd +*/ +void test_notes_notes__can_retrieve_a_list_of_notes_for_a_given_namespace(void) +{ + git_oid note_oid1, note_oid2, note_oid3, note_oid4; + unsigned int retrieved_notes = 0; + + create_note(¬e_oid1, "refs/notes/i-can-see-dead-notes", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "I decorate a65f\n"); + create_note(¬e_oid2, "refs/notes/i-can-see-dead-notes", "c47800c7266a2be04c571c04d5a6614691ea99bd", "I decorate c478\n"); + create_note(¬e_oid3, "refs/notes/i-can-see-dead-notes", "9fd738e8f7967c078dceed8190330fc8648ee56a", "I decorate 9fd7 and 4a20\n"); + create_note(¬e_oid4, "refs/notes/i-can-see-dead-notes", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", "I decorate 9fd7 and 4a20\n"); + + cl_git_pass(git_note_foreach +(_repo, "refs/notes/i-can-see-dead-notes", note_list_cb, &retrieved_notes)); + + cl_assert_equal_i(4, retrieved_notes); +} + +static int note_cancel_cb( + const git_oid *blob_id, const git_oid *annotated_obj_id, void *payload) +{ + unsigned int *count = (unsigned int *)payload; + + GIT_UNUSED(blob_id); + GIT_UNUSED(annotated_obj_id); + + (*count)++; + + return (*count > 2); +} + +void test_notes_notes__can_cancel_foreach(void) +{ + git_oid note_oid1, note_oid2, note_oid3, note_oid4; + unsigned int retrieved_notes = 0; + + create_note(¬e_oid1, "refs/notes/i-can-see-dead-notes", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "I decorate a65f\n"); + create_note(¬e_oid2, "refs/notes/i-can-see-dead-notes", "c47800c7266a2be04c571c04d5a6614691ea99bd", "I decorate c478\n"); + create_note(¬e_oid3, "refs/notes/i-can-see-dead-notes", "9fd738e8f7967c078dceed8190330fc8648ee56a", "I decorate 9fd7 and 4a20\n"); + create_note(¬e_oid4, "refs/notes/i-can-see-dead-notes", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", "I decorate 9fd7 and 4a20\n"); + + cl_assert_equal_i( + 1, + git_note_foreach(_repo, "refs/notes/i-can-see-dead-notes", + note_cancel_cb, &retrieved_notes)); +} + +void test_notes_notes__retrieving_a_list_of_notes_for_an_unknown_namespace_returns_ENOTFOUND(void) +{ + int error; + unsigned int retrieved_notes = 0; + + error = git_note_foreach(_repo, "refs/notes/i-am-not", note_list_cb, &retrieved_notes); + cl_git_fail(error); + cl_assert_equal_i(GIT_ENOTFOUND, error); + + cl_assert_equal_i(0, retrieved_notes); +} + +void test_notes_notes__inserting_a_note_without_passing_a_namespace_uses_the_default_namespace(void) +{ + git_oid note_oid, target_oid; + git_note *note, *default_namespace_note; + git_buf default_ref = GIT_BUF_INIT; + + cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125")); + cl_git_pass(git_note_default_ref(&default_ref, _repo)); + + create_note(¬e_oid, NULL, "08b041783f40edfe12bb406c9c9a8a040177c125", "hello world\n"); + + cl_git_pass(git_note_read(¬e, _repo, NULL, &target_oid)); + cl_git_pass(git_note_read(&default_namespace_note, _repo, default_ref.ptr, &target_oid)); + + assert_note_equal(note, "hello world\n", ¬e_oid); + assert_note_equal(default_namespace_note, "hello world\n", ¬e_oid); + + git_buf_dispose(&default_ref); + git_note_free(note); + git_note_free(default_namespace_note); +} + +void test_notes_notes__can_insert_a_note_with_a_custom_namespace(void) +{ + git_oid note_oid, target_oid; + git_note *note; + + cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125")); + + create_note(¬e_oid, "refs/notes/some/namespace", "08b041783f40edfe12bb406c9c9a8a040177c125", "hello world on a custom namespace\n"); + + cl_git_pass(git_note_read(¬e, _repo, "refs/notes/some/namespace", &target_oid)); + + assert_note_equal(note, "hello world on a custom namespace\n", ¬e_oid); + + git_note_free(note); +} + +/* + * $ git notes --ref fanout list 8496071c1b46c854b31185ea97743be6a8774479 + * 08b041783f40edfe12bb406c9c9a8a040177c125 + */ +void test_notes_notes__creating_a_note_on_a_target_which_already_has_one_returns_EEXISTS(void) +{ + int error; + git_oid note_oid, target_oid; + + cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125")); + + create_note(¬e_oid, NULL, "08b041783f40edfe12bb406c9c9a8a040177c125", "hello world\n"); + error = git_note_create(¬e_oid, _repo, NULL, _sig, _sig, &target_oid, "hello world\n", 0); + cl_git_fail(error); + cl_assert_equal_i(GIT_EEXISTS, error); + + create_note(¬e_oid, "refs/notes/some/namespace", "08b041783f40edfe12bb406c9c9a8a040177c125", "hello world\n"); + error = git_note_create(¬e_oid, _repo, "refs/notes/some/namespace", _sig, _sig, &target_oid, "hello world\n", 0); + cl_git_fail(error); + cl_assert_equal_i(GIT_EEXISTS, error); +} + + +void test_notes_notes__creating_a_note_on_a_target_can_overwrite_existing_note(void) +{ + git_oid note_oid, target_oid; + git_note *note, *namespace_note; + + cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125")); + + create_note(¬e_oid, NULL, "08b041783f40edfe12bb406c9c9a8a040177c125", "hello old world\n"); + cl_git_pass(git_note_create(¬e_oid, _repo, NULL, _sig, _sig, &target_oid, "hello new world\n", 1)); + + cl_git_pass(git_note_read(¬e, _repo, NULL, &target_oid)); + assert_note_equal(note, "hello new world\n", ¬e_oid); + + create_note(¬e_oid, "refs/notes/some/namespace", "08b041783f40edfe12bb406c9c9a8a040177c125", "hello old world\n"); + cl_git_pass(git_note_create(¬e_oid, _repo, "refs/notes/some/namespace", _sig, _sig, &target_oid, "hello new ref world\n", 1)); + + cl_git_pass(git_note_read(&namespace_note, _repo, "refs/notes/some/namespace", &target_oid)); + assert_note_equal(namespace_note, "hello new ref world\n", ¬e_oid); + + git_note_free(note); + git_note_free(namespace_note); +} + +static char *messages[] = { + "08c041783f40edfe12bb406c9c9a8a040177c125", + "96c45fbe09ab7445fc7c60fd8d17f32494399343", + "48cc7e38dcfc1ec87e70ec03e08c3e83d7a16aa1", + "24c3eaafb681c3df668f9df96f58e7b8c756eb04", + "96ca1b6ccc7858ae94684777f85ac0e7447f7040", + "7ac2db4378a08bb244a427c357e0082ee0d57ac6", + "e6cba23dbf4ef84fe35e884f017f4e24dc228572", + "c8cf3462c7d8feba716deeb2ebe6583bd54589e2", + "39c16b9834c2d665ac5f68ad91dc5b933bad8549", + "f3c582b1397df6a664224ebbaf9d4cc952706597", + "29cec67037fe8e89977474988219016ae7f342a6", + "36c4cd238bf8e82e27b740e0741b025f2e8c79ab", + "f1c45a47c02e01d5a9a326f1d9f7f756373387f8", + "4aca84406f5daee34ab513a60717c8d7b1763ead", + "84ce167da452552f63ed8407b55d5ece4901845f", + NULL +}; + +#define MESSAGES_COUNT (sizeof(messages)/sizeof(messages[0])) - 1 + +/* Test that we can read a note */ +void test_notes_notes__can_read_a_note(void) +{ + git_oid note_oid, target_oid; + git_note *note; + + create_note(¬e_oid, "refs/notes/i-can-see-dead-notes", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", "I decorate 4a20\n"); + + cl_git_pass(git_oid_fromstr(&target_oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); + + cl_git_pass(git_note_read(¬e, _repo, "refs/notes/i-can-see-dead-notes", &target_oid)); + + cl_assert_equal_s(git_note_message(note), "I decorate 4a20\n"); + + git_note_free(note); +} + +/* Test that we can read a note with from commit api */ +void test_notes_notes__can_read_a_note_from_a_commit(void) +{ + git_oid oid, notes_commit_oid; + git_commit *notes_commit; + git_note *note; + + cl_git_pass(git_oid_fromstr(&oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); + cl_git_pass(git_note_commit_create(¬es_commit_oid, NULL, _repo, NULL, _sig, _sig, &oid, "I decorate 4a20\n", 1)); + cl_git_pass(git_commit_lookup(¬es_commit, _repo, ¬es_commit_oid)); + cl_assert(notes_commit); + + cl_git_pass(git_note_commit_read(¬e, _repo, notes_commit, &oid)); + cl_assert_equal_s(git_note_message(note), "I decorate 4a20\n"); + + git_commit_free(notes_commit); + git_note_free(note); +} + +/* Test that we can read a commit with no note fails */ +void test_notes_notes__attempt_to_read_a_note_from_a_commit_with_no_note_fails(void) +{ + git_oid oid, notes_commit_oid; + git_commit *notes_commit; + git_note *note; + + cl_git_pass(git_oid_fromstr(&oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); + + cl_git_pass(git_note_commit_create(¬es_commit_oid, NULL, _repo, NULL, _sig, _sig, &oid, "I decorate 4a20\n", 1)); + + git_commit_lookup(¬es_commit, _repo, ¬es_commit_oid); + + cl_git_pass(git_note_commit_remove(¬es_commit_oid, _repo, notes_commit, _sig, _sig, &oid)); + git_commit_free(notes_commit); + + git_commit_lookup(¬es_commit, _repo, ¬es_commit_oid); + + cl_assert(notes_commit); + + cl_git_fail_with(GIT_ENOTFOUND, git_note_commit_read(¬e, _repo, notes_commit, &oid)); + + git_commit_free(notes_commit); +} + +/* + * $ git ls-tree refs/notes/fanout + * 040000 tree 4b22b35d44b5a4f589edf3dc89196399771796ea 84 + * + * $ git ls-tree 4b22b35 + * 040000 tree d71aab4f9b04b45ce09bcaa636a9be6231474759 96 + * + * $ git ls-tree d71aab4 + * 100644 blob 08b041783f40edfe12bb406c9c9a8a040177c125 071c1b46c854b31185ea97743be6a8774479 + */ +void test_notes_notes__can_insert_a_note_in_an_existing_fanout(void) +{ + size_t i; + git_oid note_oid, target_oid; + git_note *_note; + + cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125")); + + for (i = 0; i < MESSAGES_COUNT; i++) { + cl_git_pass(git_note_create(¬e_oid, _repo, "refs/notes/fanout", _sig, _sig, &target_oid, messages[i], 0)); + cl_git_pass(git_note_read(&_note, _repo, "refs/notes/fanout", &target_oid)); + git_note_free(_note); + + git_oid_cpy(&target_oid, ¬e_oid); + } +} + +/* + * $ git notes --ref fanout list 8496071c1b46c854b31185ea97743be6a8774479 + * 08b041783f40edfe12bb406c9c9a8a040177c125 + */ +void test_notes_notes__can_read_a_note_in_an_existing_fanout(void) +{ + git_oid note_oid, target_oid; + git_note *note; + + cl_git_pass(git_oid_fromstr(&target_oid, "8496071c1b46c854b31185ea97743be6a8774479")); + cl_git_pass(git_note_read(¬e, _repo, "refs/notes/fanout", &target_oid)); + + cl_git_pass(git_oid_fromstr(¬e_oid, "08b041783f40edfe12bb406c9c9a8a040177c125")); + cl_assert_equal_oid(git_note_id(note), ¬e_oid); + + git_note_free(note); +} + +/* Can remove a note */ +void test_notes_notes__can_remove_a_note(void) +{ + git_oid note_oid, target_oid; + git_note *note; + + create_note(¬e_oid, "refs/notes/i-can-see-dead-notes", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", "I decorate 4a20\n"); + + cl_git_pass(git_oid_fromstr(&target_oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); + cl_git_pass(git_note_remove(_repo, "refs/notes/i-can-see-dead-notes", _sig, _sig, &target_oid)); + + cl_git_fail(git_note_read(¬e, _repo, "refs/notes/i-can-see-dead-notes", &target_oid)); +} + +/* Can remove a note from a commit */ +void test_notes_notes__can_remove_a_note_from_commit(void) +{ + git_oid oid, notes_commit_oid; + git_note *note = NULL; + git_commit *existing_notes_commit; + git_reference *ref; + + cl_git_pass(git_oid_fromstr(&oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); + + cl_git_pass(git_note_commit_create(¬es_commit_oid, NULL, _repo, NULL, _sig, _sig, &oid, "I decorate 4a20\n", 0)); + + cl_git_pass(git_commit_lookup(&existing_notes_commit, _repo, ¬es_commit_oid)); + + cl_assert(existing_notes_commit); + + cl_git_pass(git_note_commit_remove(¬es_commit_oid, _repo, existing_notes_commit, _sig, _sig, &oid)); + + /* remove_from_commit will not update any ref, + * so we must manually create the ref, that points to the commit */ + cl_git_pass(git_reference_create(&ref, _repo, "refs/notes/i-can-see-dead-notes", ¬es_commit_oid, 0, NULL)); + + cl_git_fail(git_note_read(¬e, _repo, "refs/notes/i-can-see-dead-notes", &oid)); + + git_commit_free(existing_notes_commit); + git_reference_free(ref); + git_note_free(note); +} + + +void test_notes_notes__can_remove_a_note_in_an_existing_fanout(void) +{ + git_oid target_oid; + git_note *note; + + cl_git_pass(git_oid_fromstr(&target_oid, "8496071c1b46c854b31185ea97743be6a8774479")); + cl_git_pass(git_note_remove(_repo, "refs/notes/fanout", _sig, _sig, &target_oid)); + + cl_git_fail(git_note_read(¬e, _repo, "refs/notes/fanout", &target_oid)); +} + +void test_notes_notes__removing_a_note_which_doesnt_exists_returns_ENOTFOUND(void) +{ + int error; + git_oid target_oid; + + cl_git_pass(git_oid_fromstr(&target_oid, "8496071c1b46c854b31185ea97743be6a8774479")); + cl_git_pass(git_note_remove(_repo, "refs/notes/fanout", _sig, _sig, &target_oid)); + + error = git_note_remove(_repo, "refs/notes/fanout", _sig, _sig, &target_oid); + cl_git_fail(error); + cl_assert_equal_i(GIT_ENOTFOUND, error); +} + +void test_notes_notes__can_iterate_default_namespace(void) +{ + git_note_iterator *iter; + git_note *note; + git_oid note_id, annotated_id; + git_oid note_created[2]; + const char* note_message[] = { + "I decorate a65f\n", + "I decorate c478\n" + }; + int i, err; + + create_note(¬e_created[0], "refs/notes/commits", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", note_message[0]); + create_note(¬e_created[1], "refs/notes/commits", + "c47800c7266a2be04c571c04d5a6614691ea99bd", note_message[1]); + + cl_git_pass(git_note_iterator_new(&iter, _repo, NULL)); + + for (i = 0; (err = git_note_next(¬e_id, &annotated_id, iter)) >= 0; ++i) { + cl_git_pass(git_note_read(¬e, _repo, NULL, &annotated_id)); + cl_assert_equal_s(git_note_message(note), note_message[i]); + git_note_free(note); + } + + cl_assert_equal_i(GIT_ITEROVER, err); + cl_assert_equal_i(2, i); + git_note_iterator_free(iter); +} + +void test_notes_notes__can_iterate_custom_namespace(void) +{ + git_note_iterator *iter; + git_note *note; + git_oid note_id, annotated_id; + git_oid note_created[2]; + const char* note_message[] = { + "I decorate a65f\n", + "I decorate c478\n" + }; + int i, err; + + create_note(¬e_created[0], "refs/notes/beer", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", note_message[0]); + create_note(¬e_created[1], "refs/notes/beer", + "c47800c7266a2be04c571c04d5a6614691ea99bd", note_message[1]); + + cl_git_pass(git_note_iterator_new(&iter, _repo, "refs/notes/beer")); + + for (i = 0; (err = git_note_next(¬e_id, &annotated_id, iter)) >= 0; ++i) { + cl_git_pass(git_note_read(¬e, _repo, "refs/notes/beer", &annotated_id)); + cl_assert_equal_s(git_note_message(note), note_message[i]); + git_note_free(note); + } + + cl_assert_equal_i(GIT_ITEROVER, err); + cl_assert_equal_i(2, i); + git_note_iterator_free(iter); +} + +void test_notes_notes__empty_iterate(void) +{ + git_note_iterator *iter; + + cl_git_fail(git_note_iterator_new(&iter, _repo, "refs/notes/commits")); +} + +void test_notes_notes__iterate_from_commit(void) +{ + git_note_iterator *iter; + git_note *note; + git_oid note_id, annotated_id; + git_oid oids[2]; + git_oid notes_commit_oids[2]; + git_commit *notes_commits[2]; + const char* note_message[] = { + "I decorate a65f\n", + "I decorate c478\n" + }; + int i, err; + + cl_git_pass(git_oid_fromstr(&(oids[0]), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); + cl_git_pass(git_oid_fromstr(&(oids[1]), "c47800c7266a2be04c571c04d5a6614691ea99bd")); + + cl_git_pass(git_note_commit_create(¬es_commit_oids[0], NULL, _repo, NULL, _sig, _sig, &(oids[0]), note_message[0], 0)); + + git_commit_lookup(¬es_commits[0], _repo, ¬es_commit_oids[0]); + cl_assert(notes_commits[0]); + + cl_git_pass(git_note_commit_create(¬es_commit_oids[1], NULL, _repo, notes_commits[0], _sig, _sig, &(oids[1]), note_message[1], 0)); + + git_commit_lookup(¬es_commits[1], _repo, ¬es_commit_oids[1]); + cl_assert(notes_commits[1]); + + cl_git_pass(git_note_commit_iterator_new(&iter, notes_commits[1])); + + for (i = 0; (err = git_note_next(¬e_id, &annotated_id, iter)) >= 0; ++i) { + cl_git_pass(git_note_commit_read(¬e, _repo, notes_commits[1], &annotated_id)); + cl_assert_equal_s(git_note_message(note), note_message[i]); + git_note_free(note); + } + + cl_assert_equal_i(GIT_ITEROVER, err); + cl_assert_equal_i(2, i); + + git_note_iterator_free(iter); + git_commit_free(notes_commits[0]); + git_commit_free(notes_commits[1]); +} diff --git a/tests/libgit2/notes/notesref.c b/tests/libgit2/notes/notesref.c new file mode 100644 index 000000000..6ba324c76 --- /dev/null +++ b/tests/libgit2/notes/notesref.c @@ -0,0 +1,67 @@ +#include "clar_libgit2.h" + +#include "notes.h" + +static git_repository *_repo; +static git_note *_note; +static git_signature *_sig; +static git_config *_cfg; + +void test_notes_notesref__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&_repo, "testrepo.git")); +} + +void test_notes_notesref__cleanup(void) +{ + git_note_free(_note); + _note = NULL; + + git_signature_free(_sig); + _sig = NULL; + + git_config_free(_cfg); + _cfg = NULL; + + git_repository_free(_repo); + _repo = NULL; + + cl_fixture_cleanup("testrepo.git"); +} + +void test_notes_notesref__config_corenotesref(void) +{ + git_oid oid, note_oid; + git_buf default_ref = GIT_BUF_INIT; + + cl_git_pass(git_signature_now(&_sig, "alice", "alice@example.com")); + cl_git_pass(git_oid_fromstr(&oid, "8496071c1b46c854b31185ea97743be6a8774479")); + + cl_git_pass(git_repository_config(&_cfg, _repo)); + + cl_git_pass(git_config_set_string(_cfg, "core.notesRef", "refs/notes/mydefaultnotesref")); + + cl_git_pass(git_note_create(¬e_oid, _repo, NULL, _sig, _sig, &oid, "test123test\n", 0)); + + cl_git_pass(git_note_read(&_note, _repo, NULL, &oid)); + cl_assert_equal_s("test123test\n", git_note_message(_note)); + cl_assert_equal_oid(git_note_id(_note), ¬e_oid); + + git_note_free(_note); + + cl_git_pass(git_note_read(&_note, _repo, "refs/notes/mydefaultnotesref", &oid)); + cl_assert_equal_s("test123test\n", git_note_message(_note)); + cl_assert_equal_oid(git_note_id(_note), ¬e_oid); + + cl_git_pass(git_note_default_ref(&default_ref, _repo)); + cl_assert_equal_s("refs/notes/mydefaultnotesref", default_ref.ptr); + git_buf_dispose(&default_ref); + + cl_git_pass(git_config_delete_entry(_cfg, "core.notesRef")); + + cl_git_pass(git_note_default_ref(&default_ref, _repo)); + cl_assert_equal_s(GIT_NOTES_DEFAULT_REF, default_ref.ptr); + + git_buf_dispose(&default_ref); +} diff --git a/tests/libgit2/object/blob/filter.c b/tests/libgit2/object/blob/filter.c new file mode 100644 index 000000000..00b553e71 --- /dev/null +++ b/tests/libgit2/object/blob/filter.c @@ -0,0 +1,149 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "blob.h" + +static git_repository *g_repo = NULL; + +#define CRLF_NUM_TEST_OBJECTS 9 + +static const char *g_crlf_raw[CRLF_NUM_TEST_OBJECTS] = { + "", + "foo\nbar\n", + "foo\rbar\r", + "foo\r\nbar\r\n", + "foo\nbar\rboth\r\nreversed\n\ragain\nproblems\r", + "123\n\000\001\002\003\004abc\255\254\253\r\n", + "\xEF\xBB\xBFThis is UTF-8\n", + "\xEF\xBB\xBF\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n", + "\xFE\xFF\x00T\x00h\x00i\x00s\x00!" +}; + +static off64_t g_crlf_raw_len[CRLF_NUM_TEST_OBJECTS] = { + -1, -1, -1, -1, -1, 17, -1, -1, 12 +}; + +static git_oid g_crlf_oids[CRLF_NUM_TEST_OBJECTS]; + +static git_str g_crlf_filtered[CRLF_NUM_TEST_OBJECTS] = { + { "", 0, 0 }, + { "foo\nbar\n", 0, 8 }, + { "foo\rbar\r", 0, 8 }, + { "foo\nbar\n", 0, 8 }, + { "foo\nbar\rboth\nreversed\n\ragain\nproblems\r", 0, 38 }, + { "123\n\000\001\002\003\004abc\255\254\253\n", 0, 16 }, + { "\xEF\xBB\xBFThis is UTF-8\n", 0, 17 }, + { "\xEF\xBB\xBF\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\n\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\n", 0, 29 }, + { "\xFE\xFF\x00T\x00h\x00i\x00s\x00!", 0, 12 } +}; + +static git_str_text_stats g_crlf_filtered_stats[CRLF_NUM_TEST_OBJECTS] = { + { 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 2, 0, 6, 0 }, + { 0, 0, 2, 0, 0, 6, 0 }, + { 0, 0, 2, 2, 2, 6, 0 }, + { 0, 0, 4, 4, 1, 31, 0 }, + { 0, 1, 1, 2, 1, 9, 5 }, + { GIT_STR_BOM_UTF8, 0, 0, 1, 0, 16, 0 }, + { GIT_STR_BOM_UTF8, 0, 2, 2, 2, 27, 0 }, + { GIT_STR_BOM_UTF16_BE, 5, 0, 0, 0, 7, 5 }, +}; + +void test_object_blob_filter__initialize(void) +{ + int i; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { + if (g_crlf_raw_len[i] < 0) + g_crlf_raw_len[i] = strlen(g_crlf_raw[i]); + + cl_git_pass(git_blob_create_from_buffer( + &g_crlf_oids[i], g_repo, g_crlf_raw[i], (size_t)g_crlf_raw_len[i])); + } +} + +void test_object_blob_filter__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_object_blob_filter__unfiltered(void) +{ + int i; + git_blob *blob; + + for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { + size_t raw_len = (size_t)g_crlf_raw_len[i]; + + cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i])); + + cl_assert_equal_sz(raw_len, (size_t)git_blob_rawsize(blob)); + cl_assert_equal_i( + 0, memcmp(g_crlf_raw[i], git_blob_rawcontent(blob), raw_len)); + + git_blob_free(blob); + } +} + +void test_object_blob_filter__stats(void) +{ + int i; + git_blob *blob; + git_str buf = GIT_STR_INIT; + git_str_text_stats stats; + + for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { + cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i])); + cl_git_pass(git_blob__getbuf(&buf, blob)); + git_str_gather_text_stats(&stats, &buf, false); + cl_assert_equal_i( + 0, memcmp(&g_crlf_filtered_stats[i], &stats, sizeof(stats))); + git_blob_free(blob); + } + + git_str_dispose(&buf); +} + +void test_object_blob_filter__to_odb(void) +{ + git_filter_list *fl = NULL; + git_config *cfg; + int i; + git_blob *blob; + git_buf out = GIT_BUF_INIT, zeroed; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_assert(cfg); + + git_attr_cache_flush(g_repo); + cl_git_append2file("empty_standard_repo/.gitattributes", "*.txt text\n"); + + cl_git_pass(git_filter_list_load( + &fl, g_repo, NULL, "filename.txt", GIT_FILTER_TO_ODB, 0)); + cl_assert(fl != NULL); + + for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { + cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i])); + + /* try once with allocated blob */ + cl_git_pass(git_filter_list_apply_to_blob(&out, fl, blob)); + cl_assert_equal_sz(g_crlf_filtered[i].size, out.size); + cl_assert_equal_i( + 0, memcmp(out.ptr, g_crlf_filtered[i].ptr, out.size)); + + /* try again with zeroed blob */ + memset(&zeroed, 0, sizeof(zeroed)); + cl_git_pass(git_filter_list_apply_to_blob(&zeroed, fl, blob)); + cl_assert_equal_sz(g_crlf_filtered[i].size, zeroed.size); + cl_assert_equal_i( + 0, memcmp(zeroed.ptr, g_crlf_filtered[i].ptr, zeroed.size)); + git_buf_dispose(&zeroed); + + git_blob_free(blob); + } + + git_filter_list_free(fl); + git_buf_dispose(&out); + git_config_free(cfg); +} diff --git a/tests/libgit2/object/blob/fromstream.c b/tests/libgit2/object/blob/fromstream.c new file mode 100644 index 000000000..60ff3991b --- /dev/null +++ b/tests/libgit2/object/blob/fromstream.c @@ -0,0 +1,86 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "futils.h" + +static git_repository *repo; +static char textual_content[] = "libgit2\n\r\n\0"; + +void test_object_blob_fromstream__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_object_blob_fromstream__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_object_blob_fromstream__multiple_write(void) +{ + git_oid expected_id, id; + git_object *blob; + git_writestream *stream; + int i, howmany = 6; + + cl_git_pass(git_oid_fromstr(&expected_id, "321cbdf08803c744082332332838df6bd160f8f9")); + + cl_git_fail_with(GIT_ENOTFOUND, + git_object_lookup(&blob, repo, &expected_id, GIT_OBJECT_ANY)); + + cl_git_pass(git_blob_create_from_stream(&stream, repo, NULL)); + + for (i = 0; i < howmany; i++) + cl_git_pass(stream->write(stream, textual_content, strlen(textual_content))); + + cl_git_pass(git_blob_create_from_stream_commit(&id, stream)); + cl_assert_equal_oid(&expected_id, &id); + + cl_git_pass(git_object_lookup(&blob, repo, &expected_id, GIT_OBJECT_BLOB)); + + git_object_free(blob); +} + +#define GITATTR "* text=auto\n" \ + "*.txt text\n" \ + "*.data binary\n" + +static void write_attributes(git_repository *repo) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&buf, git_repository_path(repo), "info")); + cl_git_pass(git_str_joinpath(&buf, git_str_cstr(&buf), "attributes")); + + cl_git_pass(git_futils_mkpath2file(git_str_cstr(&buf), 0777)); + cl_git_rewritefile(git_str_cstr(&buf), GITATTR); + + git_str_dispose(&buf); +} + +static void assert_named_chunked_blob(const char *expected_sha, const char *fake_name) +{ + git_oid expected_id, id; + git_writestream *stream; + int i, howmany = 6; + + cl_git_pass(git_oid_fromstr(&expected_id, expected_sha)); + + cl_git_pass(git_blob_create_from_stream(&stream, repo, fake_name)); + + for (i = 0; i < howmany; i++) + cl_git_pass(stream->write(stream, textual_content, strlen(textual_content))); + + cl_git_pass(git_blob_create_from_stream_commit(&id, stream)); + + cl_assert_equal_oid(&expected_id, &id); +} + +void test_object_blob_fromstream__creating_a_blob_from_chunks_honors_the_attributes_directives(void) +{ + write_attributes(repo); + + assert_named_chunked_blob("321cbdf08803c744082332332838df6bd160f8f9", "dummy.data"); + assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.txt"); + assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.dunno"); +} diff --git a/tests/libgit2/object/blob/write.c b/tests/libgit2/object/blob/write.c new file mode 100644 index 000000000..422258d63 --- /dev/null +++ b/tests/libgit2/object/blob/write.c @@ -0,0 +1,68 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "futils.h" + +static git_repository *repo; + +#define WORKDIR "empty_standard_repo" +#define BARE_REPO "testrepo.git" +#define ELSEWHERE "elsewhere" + +typedef int (*blob_creator_fn)( + git_oid *, + git_repository *, + const char *); + +void test_object_blob_write__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void assert_blob_creation(const char *path_to_file, const char *blob_from_path, blob_creator_fn creator) +{ + git_oid oid; + cl_git_mkfile(path_to_file, "1..2...3... Can you hear me?\n"); + + cl_must_pass(creator(&oid, repo, blob_from_path)); + cl_assert(git_oid_streq(&oid, "da5e4f20c91c81b44a7e298f3d3fb3fe2f178e32") == 0); +} + +void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_file_located_in_the_working_directory(void) +{ + repo = cl_git_sandbox_init(WORKDIR); + + assert_blob_creation(WORKDIR "/test.txt", "test.txt", &git_blob_create_from_workdir); +} + +void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_absolute_filepath_pointing_outside_of_the_working_directory(void) +{ + git_str full_path = GIT_STR_INIT; + + repo = cl_git_sandbox_init(WORKDIR); + + cl_must_pass(p_mkdir(ELSEWHERE, 0777)); + cl_must_pass(git_fs_path_prettify_dir(&full_path, ELSEWHERE, NULL)); + cl_must_pass(git_str_puts(&full_path, "test.txt")); + + assert_blob_creation(ELSEWHERE "/test.txt", git_str_cstr(&full_path), &git_blob_create_from_disk); + + git_str_dispose(&full_path); + cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_object_blob_write__can_create_a_blob_in_a_bare_repo_from_a_absolute_filepath(void) +{ + git_str full_path = GIT_STR_INIT; + + repo = cl_git_sandbox_init(BARE_REPO); + + cl_must_pass(p_mkdir(ELSEWHERE, 0777)); + cl_must_pass(git_fs_path_prettify_dir(&full_path, ELSEWHERE, NULL)); + cl_must_pass(git_str_puts(&full_path, "test.txt")); + + assert_blob_creation(ELSEWHERE "/test.txt", git_str_cstr(&full_path), &git_blob_create_from_disk); + + git_str_dispose(&full_path); + cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_RMDIR_REMOVE_FILES)); +} diff --git a/tests/libgit2/object/cache.c b/tests/libgit2/object/cache.c new file mode 100644 index 000000000..08bf03648 --- /dev/null +++ b/tests/libgit2/object/cache.c @@ -0,0 +1,276 @@ +#include "clar_libgit2.h" +#include "repository.h" + +static git_repository *g_repo; +static size_t cache_limit; +static int object_type; + +void test_object_cache__initialize_cache_no_blobs(void) +{ + g_repo = NULL; + object_type = GIT_OBJECT_BLOB; + cache_limit = 0; +} + +void test_object_cache__initialize_cache_tiny_blobs(void) +{ + g_repo = NULL; + object_type = GIT_OBJECT_BLOB; + cache_limit = 10; +} + +void test_object_cache__initialize_cache_all_blobs(void) +{ + g_repo = NULL; + object_type = GIT_OBJECT_BLOB; + cache_limit = 32767; +} + +void test_object_cache__initialize_cache_no_trees(void) +{ + g_repo = NULL; + object_type = GIT_OBJECT_TREE; + cache_limit = 0; +} + +void test_object_cache__cleanup(void) +{ + git_repository_free(g_repo); + g_repo = NULL; + + git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJECT_BLOB, (size_t)0); + git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJECT_TREE, (size_t)4096); + git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJECT_COMMIT, (size_t)4096); +} + +static struct { + git_object_t type; + const char *sha; + size_t size; +} g_data[] = { + /* HEAD */ + { GIT_OBJECT_BLOB, "a8233120f6ad708f843d861ce2b7228ec4e3dec6", 10 }, /* README */ + { GIT_OBJECT_BLOB, "3697d64be941a53d4ae8f6a271e4e3fa56b022cc", 8 }, /* branch_file.txt */ + { GIT_OBJECT_BLOB, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd", 12 }, /* new.txt */ + + /* refs/heads/subtrees */ + { GIT_OBJECT_BLOB, "1385f264afb75a56a5bec74243be9b367ba4ca08", 4 }, /* README */ + { GIT_OBJECT_TREE, "f1425cef211cc08caa31e7b545ffb232acb098c3", 90 }, /* ab */ + { GIT_OBJECT_BLOB, "d6c93164c249c8000205dd4ec5cbca1b516d487f", 6 }, /* ab/4.txt */ + { GIT_OBJECT_TREE, "9a03079b8a8ee85a0bee58bf9be3da8b62414ed4", 33 }, /* ab/c */ + { GIT_OBJECT_BLOB, "270b8ea76056d5cad83af921837702d3e3c2924d", 6 }, /* ab/c/3.txt */ + { GIT_OBJECT_TREE, "b6361fc6a97178d8fc8639fdeed71c775ab52593", 63 }, /* ab/de */ + { GIT_OBJECT_BLOB, "e7b4ad382349ff96dd8199000580b9b1e2042eb0", 6 }, /* ab/de/2.txt */ + { GIT_OBJECT_TREE, "3259a6bd5b57fb9c1281bb7ed3167b50f224cb54", 33 }, /* ab/de/fgh */ + { GIT_OBJECT_BLOB, "1f67fc4386b2d171e0d21be1c447e12660561f9b", 6 }, /* ab/de/fgh/1.txt */ + { GIT_OBJECT_BLOB, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", 3 }, /* branch_file.txt */ + { GIT_OBJECT_BLOB, "fa49b077972391ad58037050f2a75f74e3671e92", 9 }, /* new.txt */ + + /* refs/heads/chomped */ + { GIT_OBJECT_BLOB, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", 51 }, /* readme.txt */ + + { 0, NULL, 0 }, + { 0, NULL, 0 } +}; + +void test_object_cache__cache_counts(void) +{ + int i, start, nonmatching = 0; + git_oid oid; + git_odb_object *odb_obj; + git_object *obj; + git_odb *odb; + + git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, object_type, cache_limit); + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_repository_odb(&odb, g_repo)); + + start = (int)git_cache_size(&g_repo->objects); + + for (i = 0; g_data[i].sha != NULL; ++i) { + int count = (int)git_cache_size(&g_repo->objects); + + cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha)); + + /* alternate between loading raw and parsed objects */ + if ((i & 1) == 0) { + cl_git_pass(git_odb_read(&odb_obj, odb, &oid)); + cl_assert(g_data[i].type == git_odb_object_type(odb_obj)); + git_odb_object_free(odb_obj); + } else { + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + cl_assert(g_data[i].type == git_object_type(obj)); + git_object_free(obj); + } + + if ((g_data[i].type == object_type && g_data[i].size >= cache_limit) || + (g_data[i].type != object_type && g_data[i].type == GIT_OBJECT_BLOB)) + cl_assert_equal_i(count, (int)git_cache_size(&g_repo->objects)); + else { + cl_assert_equal_i(count + 1, (int)git_cache_size(&g_repo->objects)); + nonmatching++; + } + } + + cl_assert_equal_i(nonmatching, (int)git_cache_size(&g_repo->objects) - start); + + for (i = 0; g_data[i].sha != NULL; ++i) { + int count = (int)git_cache_size(&g_repo->objects); + + cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha)); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + cl_assert(g_data[i].type == git_object_type(obj)); + git_object_free(obj); + + cl_assert_equal_i(count, (int)git_cache_size(&g_repo->objects)); + } + + git_odb_free(odb); +} + +static void *cache_parsed(void *arg) +{ + int i; + git_oid oid; + git_object *obj; + + for (i = ((int *)arg)[1]; g_data[i].sha != NULL; i += 2) { + cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha)); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + cl_assert(g_data[i].type == git_object_type(obj)); + git_object_free(obj); + } + + for (i = 0; i < ((int *)arg)[1]; i += 2) { + cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha)); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + cl_assert(g_data[i].type == git_object_type(obj)); + git_object_free(obj); + } + + return arg; +} + +static void *cache_raw(void *arg) +{ + int i; + git_oid oid; + git_odb *odb; + git_odb_object *odb_obj; + + cl_git_pass(git_repository_odb(&odb, g_repo)); + + for (i = ((int *)arg)[1]; g_data[i].sha != NULL; i += 2) { + cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha)); + cl_git_pass(git_odb_read(&odb_obj, odb, &oid)); + cl_assert(g_data[i].type == git_odb_object_type(odb_obj)); + git_odb_object_free(odb_obj); + } + + for (i = 0; i < ((int *)arg)[1]; i += 2) { + cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha)); + cl_git_pass(git_odb_read(&odb_obj, odb, &oid)); + cl_assert(g_data[i].type == git_odb_object_type(odb_obj)); + git_odb_object_free(odb_obj); + } + + git_odb_free(odb); + + return arg; +} + +#define REPEAT 20 +#define THREADCOUNT 50 + +void test_object_cache__threadmania(void) +{ + int try, th, max_i; + void *data; + void *(*fn)(void *); + +#ifdef GIT_THREADS + git_thread t[THREADCOUNT]; +#endif + + for (max_i = 0; g_data[max_i].sha != NULL; ++max_i) + /* count up */; + + for (try = 0; try < REPEAT; ++try) { + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); + + for (th = 0; th < THREADCOUNT; ++th) { + data = git__malloc(2 * sizeof(int)); + + ((int *)data)[0] = th; + ((int *)data)[1] = th % max_i; + + fn = (th & 1) ? cache_parsed : cache_raw; + +#ifdef GIT_THREADS + cl_git_pass(git_thread_create(&t[th], fn, data)); +#else + cl_assert(fn(data) == data); + git__free(data); +#endif + } + +#ifdef GIT_THREADS + for (th = 0; th < THREADCOUNT; ++th) { + cl_git_pass(git_thread_join(&t[th], &data)); + cl_assert_equal_i(th, ((int *)data)[0]); + git__free(data); + } +#endif + + git_repository_free(g_repo); + g_repo = NULL; + } +} + +static void *cache_quick(void *arg) +{ + git_oid oid; + git_object *obj; + + cl_git_pass(git_oid_fromstr(&oid, g_data[4].sha)); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + cl_assert(g_data[4].type == git_object_type(obj)); + git_object_free(obj); + + return arg; +} + +void test_object_cache__fast_thread_rush(void) +{ + int try, th, data[THREADCOUNT]; +#ifdef GIT_THREADS + git_thread t[THREADCOUNT]; +#endif + + for (try = 0; try < REPEAT; ++try) { + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); + + for (th = 0; th < THREADCOUNT; ++th) { + data[th] = th; +#ifdef GIT_THREADS + cl_git_pass( + git_thread_create(&t[th], cache_quick, &data[th])); +#else + cl_assert(cache_quick(&data[th]) == &data[th]); +#endif + } + +#ifdef GIT_THREADS + for (th = 0; th < THREADCOUNT; ++th) { + void *rval; + cl_git_pass(git_thread_join(&t[th], &rval)); + cl_assert_equal_i(th, *((int *)rval)); + } +#endif + + git_repository_free(g_repo); + g_repo = NULL; + } +} diff --git a/tests/libgit2/object/commit/commitstagedfile.c b/tests/libgit2/object/commit/commitstagedfile.c new file mode 100644 index 000000000..7322a4e86 --- /dev/null +++ b/tests/libgit2/object/commit/commitstagedfile.c @@ -0,0 +1,218 @@ +#include "clar_libgit2.h" +#include "posix.h" + +static git_repository *repo; + +void test_object_commit_commitstagedfile__initialize(void) +{ + cl_fixture("treebuilder"); + cl_git_pass(git_repository_init(&repo, "treebuilder/", 0)); + cl_assert(repo != NULL); +} + +void test_object_commit_commitstagedfile__cleanup(void) +{ + git_repository_free(repo); + repo = NULL; + + cl_fixture_cleanup("treebuilder"); +} + +void test_object_commit_commitstagedfile__generate_predictable_object_ids(void) +{ + git_index *index; + const git_index_entry *entry; + git_oid expected_blob_oid, tree_oid, expected_tree_oid, commit_oid, expected_commit_oid; + git_signature *signature; + git_tree *tree; + git_buf buffer = GIT_BUF_INIT; + + /* + * The test below replicates the following git scenario + * + * $ echo "test" > test.txt + * $ git hash-object test.txt + * 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 + * + * $ git add . + * $ git commit -m "Initial commit" + * + * $ git log + * commit 1fe3126578fc4eca68c193e4a3a0a14a0704624d + * Author: nulltoken + * Date: Wed Dec 14 08:29:03 2011 +0100 + * + * Initial commit + * + * $ git show 1fe3 --format=raw + * commit 1fe3126578fc4eca68c193e4a3a0a14a0704624d + * tree 2b297e643c551e76cfa1f93810c50811382f9117 + * author nulltoken 1323847743 +0100 + * committer nulltoken 1323847743 +0100 + * + * Initial commit + * + * diff --git a/test.txt b/test.txt + * new file mode 100644 + * index 0000000..9daeafb + * --- /dev/null + * +++ b/test.txt + * @@ -0,0 +1 @@ + * +test + * + * $ git ls-tree 2b297 + * 100644 blob 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 test.txt + */ + + cl_git_pass(git_oid_fromstr(&expected_commit_oid, "1fe3126578fc4eca68c193e4a3a0a14a0704624d")); + cl_git_pass(git_oid_fromstr(&expected_tree_oid, "2b297e643c551e76cfa1f93810c50811382f9117")); + cl_git_pass(git_oid_fromstr(&expected_blob_oid, "9daeafb9864cf43055ae93beb0afd6c7d144bfa4")); + + /* + * Add a new file to the index + */ + cl_git_mkfile("treebuilder/test.txt", "test\n"); + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, "test.txt")); + + entry = git_index_get_byindex(index, 0); + + cl_assert(git_oid_cmp(&expected_blob_oid, &entry->id) == 0); + + /* + * Information about index entry should match test file + */ + { + struct stat st; + cl_must_pass(p_lstat("treebuilder/test.txt", &st)); + cl_assert(entry->file_size == st.st_size); +#ifndef _WIN32 + /* + * Windows doesn't populate these fields, and the signage is + * wrong in the Windows version of the struct, so lets avoid + * the "comparing signed and unsigned" compilation warning in + * that case. + */ + cl_assert(entry->uid == st.st_uid); + cl_assert(entry->gid == st.st_gid); +#endif + } + + /* + * Build the tree from the index + */ + cl_git_pass(git_index_write_tree(&tree_oid, index)); + + cl_assert(git_oid_cmp(&expected_tree_oid, &tree_oid) == 0); + + /* + * Commit the staged file + */ + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); + cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); + + cl_git_pass(git_message_prettify(&buffer, "Initial commit", 0, '#')); + + cl_git_pass(git_commit_create_v( + &commit_oid, + repo, + "HEAD", + signature, + signature, + NULL, + buffer.ptr, + tree, + 0)); + + cl_assert(git_oid_cmp(&expected_commit_oid, &commit_oid) == 0); + + git_buf_dispose(&buffer); + git_signature_free(signature); + git_tree_free(tree); + git_index_free(index); +} + +static void assert_commit_tree_has_n_entries(git_commit *c, int count) +{ + git_tree *tree; + cl_git_pass(git_commit_tree(&tree, c)); + cl_assert_equal_i(count, git_tree_entrycount(tree)); + git_tree_free(tree); +} + +static void assert_commit_is_head_(git_commit *c, const char *file, const char *func, int line) +{ + git_commit *head; + cl_git_pass(git_revparse_single((git_object **)&head, repo, "HEAD")); + clar__assert(git_oid_equal(git_commit_id(c), git_commit_id(head)), file, func, line, "Commit is not the HEAD", NULL, 1); + git_commit_free(head); +} +#define assert_commit_is_head(C) assert_commit_is_head_((C),__FILE__,__func__,__LINE__) + +void test_object_commit_commitstagedfile__amend_commit(void) +{ + git_index *index; + git_oid old_oid, new_oid, tree_oid; + git_commit *old_commit, *new_commit; + git_tree *tree; + + /* make a commit */ + + cl_git_mkfile("treebuilder/myfile", "This is a file\n"); + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, "myfile")); + cl_repo_commit_from_index(&old_oid, repo, NULL, 0, "first commit"); + + cl_git_pass(git_commit_lookup(&old_commit, repo, &old_oid)); + + cl_assert_equal_i(0, git_commit_parentcount(old_commit)); + assert_commit_tree_has_n_entries(old_commit, 1); + assert_commit_is_head(old_commit); + + /* let's amend the message of the HEAD commit */ + + cl_git_pass(git_commit_amend( + &new_oid, old_commit, "HEAD", NULL, NULL, NULL, "Initial commit", NULL)); + + /* fail because the commit isn't the tip of the branch anymore */ + cl_git_fail(git_commit_amend( + &new_oid, old_commit, "HEAD", NULL, NULL, NULL, "Initial commit", NULL)); + + cl_git_pass(git_commit_lookup(&new_commit, repo, &new_oid)); + + cl_assert_equal_i(0, git_commit_parentcount(new_commit)); + assert_commit_tree_has_n_entries(new_commit, 1); + assert_commit_is_head(new_commit); + + git_commit_free(old_commit); + + old_commit = new_commit; + + /* let's amend the tree of that last commit */ + + cl_git_mkfile("treebuilder/anotherfile", "This is another file\n"); + cl_git_pass(git_index_add_bypath(index, "anotherfile")); + cl_git_pass(git_index_write_tree(&tree_oid, index)); + cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); + cl_assert_equal_i(2, git_tree_entrycount(tree)); + + /* fail to amend on a ref which does not exist */ + cl_git_fail_with(GIT_ENOTFOUND, git_commit_amend( + &new_oid, old_commit, "refs/heads/nope", NULL, NULL, NULL, "Initial commit", tree)); + + cl_git_pass(git_commit_amend( + &new_oid, old_commit, "HEAD", NULL, NULL, NULL, "Initial commit", tree)); + git_tree_free(tree); + + cl_git_pass(git_commit_lookup(&new_commit, repo, &new_oid)); + + cl_assert_equal_i(0, git_commit_parentcount(new_commit)); + assert_commit_tree_has_n_entries(new_commit, 2); + assert_commit_is_head(new_commit); + + /* cleanup */ + + git_commit_free(old_commit); + git_commit_free(new_commit); + git_index_free(index); +} diff --git a/tests/libgit2/object/commit/parse.c b/tests/libgit2/object/commit/parse.c new file mode 100644 index 000000000..4ff1ad62a --- /dev/null +++ b/tests/libgit2/object/commit/parse.c @@ -0,0 +1,232 @@ +#include "clar_libgit2.h" +#include "commit.h" +#include "object.h" +#include "signature.h" + +static void assert_commit_parses(const char *data, size_t datalen, + const char *expected_treeid, + const char *expected_author, + const char *expected_committer, + const char *expected_encoding, + const char *expected_message, + size_t expected_parents) +{ + git_commit *commit; + if (!datalen) + datalen = strlen(data); + cl_git_pass(git_object__from_raw((git_object **) &commit, data, datalen, GIT_OBJECT_COMMIT)); + + if (expected_author) { + git_signature *author; + cl_git_pass(git_signature_from_buffer(&author, expected_author)); + cl_assert(git_signature__equal(author, commit->author)); + cl_assert_equal_s(author->name, commit->author->name); + cl_assert_equal_s(author->email, commit->author->email); + cl_assert_equal_i(author->when.time, commit->author->when.time); + cl_assert_equal_i(author->when.offset, commit->author->when.offset); + cl_assert_equal_i(author->when.sign, commit->author->when.sign); + git_signature_free(author); + } + + if (expected_committer) { + git_signature *committer; + cl_git_pass(git_signature_from_buffer(&committer, expected_committer)); + cl_assert_equal_s(committer->name, commit->committer->name); + cl_assert_equal_s(committer->email, commit->committer->email); + cl_assert_equal_i(committer->when.time, commit->committer->when.time); + cl_assert_equal_i(committer->when.offset, commit->committer->when.offset); + cl_assert_equal_i(committer->when.sign, commit->committer->when.sign); + git_signature_free(committer); + } + + if (expected_encoding) + cl_assert_equal_s(commit->message_encoding, expected_encoding); + else + cl_assert_equal_p(commit->message_encoding, NULL); + + if (expected_message) + cl_assert_equal_s(commit->raw_message, expected_message); + else + cl_assert_equal_p(commit->message_encoding, NULL); + + if (expected_treeid) { + git_oid tree_oid; + cl_git_pass(git_oid_fromstr(&tree_oid, expected_treeid)); + cl_assert_equal_oid(&tree_oid, &commit->tree_id); + } + + cl_assert_equal_i(commit->parent_ids.size, expected_parents); + + git_object__free(&commit->object); +} + +static void assert_commit_fails(const char *data, size_t datalen) +{ + git_object *object; + if (!datalen) + datalen = strlen(data); + cl_git_fail(git_object__from_raw(&object, data, datalen, GIT_OBJECT_COMMIT)); +} + +void test_object_commit_parse__parsing_commit_succeeds(void) +{ + const char *commit = + "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" + "author Author \n" + "committer Committer \n" + "encoding Encoding\n" + "\n" + "Message"; + assert_commit_parses(commit, 0, + "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", + "Author ", + "Committer ", + "Encoding", + "Message", 0); +} + +void test_object_commit_parse__parsing_commit_without_encoding_succeeds(void) +{ + const char *commit = + "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" + "author Author \n" + "committer Committer \n" + "\n" + "Message"; + assert_commit_parses(commit, 0, + "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", + "Author ", + "Committer ", + NULL, + "Message", 0); +} + +void test_object_commit_parse__parsing_commit_with_multiple_authors_succeeds(void) +{ + const char *commit = + "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" + "author Author1 \n" + "author Author2 \n" + "author Author3 \n" + "author Author4 \n" + "committer Committer \n" + "\n" + "Message"; + assert_commit_parses(commit, 0, + "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", + "Author1 ", + "Committer ", + NULL, + "Message", 0); +} + +void test_object_commit_parse__parsing_commit_with_multiple_committers_succeeds(void) +{ + const char *commit = + "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" + "author Author \n" + "committer Committer1 \n" + "committer Committer2 \n" + "committer Committer3 \n" + "committer Committer4 \n" + "\n" + "Message"; + assert_commit_parses(commit, 0, + "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", + "Author ", + "Committer1 ", + NULL, + "Message", 0); +} + +void test_object_commit_parse__parsing_commit_without_message_succeeds(void) +{ + const char *commit = + "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" + "author Author \n" + "committer Committer \n"; + assert_commit_parses(commit, 0, + "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", + "Author ", + "Committer ", + NULL, + "", 0); +} + +void test_object_commit_parse__parsing_commit_with_unknown_fields_succeeds(void) +{ + const char *commit = + "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" + "author Author \n" + "committer Committer \n" + "foo bar\n" + "more garbage\n" + "\n" + "Message"; + assert_commit_parses(commit, 0, + "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", + "Author ", + "Committer ", + NULL, + "Message", 0); +} + +void test_object_commit_parse__parsing_commit_with_invalid_tree_fails(void) +{ + const char *commit = + "tree 3e7ac388cadacccdf1xxx5f3445895b71d9cb0f8\n" + "author Author \n" + "committer Committer \n" + "\n" + "Message"; + assert_commit_fails(commit, 0); +} + +void test_object_commit_parse__parsing_commit_without_tree_fails(void) +{ + const char *commit = + "author Author \n" + "committer Committer \n" + "\n" + "Message"; + assert_commit_fails(commit, 0); +} + +void test_object_commit_parse__parsing_commit_without_author_fails(void) +{ + const char *commit = + "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" + "committer Committer \n" + "\n" + "Message"; + assert_commit_fails(commit, 0); +} + +void test_object_commit_parse__parsing_commit_without_committer_fails(void) +{ + const char *commit = + "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" + "author Author \n" + "\n" + "Message"; + assert_commit_fails(commit, 0); +} + +void test_object_commit_parse__parsing_encoding_will_not_cause_oob_read(void) +{ + const char *commit = + "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" + "author <>\n" + "committer <>\n" + "encoding foo\n"; + /* + * As we ignore unknown fields, the cut-off encoding field will be + * parsed just fine. + */ + assert_commit_parses(commit, strlen(commit) - strlen("ncoding foo\n"), + "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", + "<>", + "<>", + NULL, + "", 0); +} diff --git a/tests/libgit2/object/lookup.c b/tests/libgit2/object/lookup.c new file mode 100644 index 000000000..a7b1ceeb4 --- /dev/null +++ b/tests/libgit2/object/lookup.c @@ -0,0 +1,122 @@ +#include "clar_libgit2.h" + +#include "repository.h" + +static git_repository *g_repo; + +void test_object_lookup__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_object_lookup__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_object_lookup__lookup_wrong_type_returns_enotfound(void) +{ + const char *commit = "e90810b8df3e80c413d903f631643c716887138d"; + git_oid oid; + git_object *object; + + cl_git_pass(git_oid_fromstr(&oid, commit)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_TAG)); +} + +void test_object_lookup__lookup_nonexisting_returns_enotfound(void) +{ + const char *unknown = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + git_oid oid; + git_object *object; + + cl_git_pass(git_oid_fromstr(&oid, unknown)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_ANY)); +} + +void test_object_lookup__lookup_wrong_type_by_abbreviated_id_returns_enotfound(void) +{ + const char *commit = "e90810b"; + git_oid oid; + git_object *object; + + cl_git_pass(git_oid_fromstrn(&oid, commit, strlen(commit))); + cl_assert_equal_i( + GIT_ENOTFOUND, git_object_lookup_prefix(&object, g_repo, &oid, strlen(commit), GIT_OBJECT_TAG)); +} + +void test_object_lookup__lookup_wrong_type_eventually_returns_enotfound(void) +{ + const char *commit = "e90810b8df3e80c413d903f631643c716887138d"; + git_oid oid; + git_object *object; + + cl_git_pass(git_oid_fromstr(&oid, commit)); + + cl_git_pass(git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_COMMIT)); + git_object_free(object); + + cl_assert_equal_i( + GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_TAG)); +} + +void test_object_lookup__lookup_corrupt_object_returns_error(void) +{ + const char *commit = "8e73b769e97678d684b809b163bebdae2911720f", + *file = "objects/8e/73b769e97678d684b809b163bebdae2911720f"; + git_str path = GIT_STR_INIT, contents = GIT_STR_INIT; + git_oid oid; + git_object *object; + size_t i; + + cl_git_pass(git_oid_fromstr(&oid, commit)); + cl_git_pass(git_str_joinpath(&path, git_repository_path(g_repo), file)); + cl_git_pass(git_futils_readbuffer(&contents, path.ptr)); + + /* Corrupt and try to read the object */ + for (i = 0; i < contents.size; i++) { + contents.ptr[i] ^= 0x1; + cl_git_pass(git_futils_writebuffer(&contents, path.ptr, O_RDWR, 0644)); + cl_git_fail(git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_COMMIT)); + contents.ptr[i] ^= 0x1; + } + + /* Restore original content and assert we can read the object */ + cl_git_pass(git_futils_writebuffer(&contents, path.ptr, O_RDWR, 0644)); + cl_git_pass(git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_COMMIT)); + + git_object_free(object); + git_str_dispose(&path); + git_str_dispose(&contents); +} + +void test_object_lookup__lookup_object_with_wrong_hash_returns_error(void) +{ + const char *oldloose = "objects/8e/73b769e97678d684b809b163bebdae2911720f", + *newloose = "objects/8e/73b769e97678d684b809b163bebdae2911720e", + *commit = "8e73b769e97678d684b809b163bebdae2911720e"; + git_str oldpath = GIT_STR_INIT, newpath = GIT_STR_INIT; + git_object *object; + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, commit)); + + /* Copy object to another location with wrong hash */ + cl_git_pass(git_str_joinpath(&oldpath, git_repository_path(g_repo), oldloose)); + cl_git_pass(git_str_joinpath(&newpath, git_repository_path(g_repo), newloose)); + cl_git_pass(git_futils_cp(oldpath.ptr, newpath.ptr, 0644)); + + /* Verify that lookup fails due to a hashsum mismatch */ + cl_git_fail_with(GIT_EMISMATCH, git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_COMMIT)); + + /* Disable verification and try again */ + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)); + cl_git_pass(git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_COMMIT)); + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1)); + + git_object_free(object); + git_str_dispose(&oldpath); + git_str_dispose(&newpath); +} diff --git a/tests/libgit2/object/lookupbypath.c b/tests/libgit2/object/lookupbypath.c new file mode 100644 index 000000000..f2257f556 --- /dev/null +++ b/tests/libgit2/object/lookupbypath.c @@ -0,0 +1,83 @@ +#include "clar_libgit2.h" + +#include "repository.h" + +static git_repository *g_repo; +static git_tree *g_root_tree; +static git_commit *g_head_commit; +static git_object *g_expectedobject, + *g_actualobject; + +void test_object_lookupbypath__initialize(void) +{ + git_reference *head; + git_tree_entry *tree_entry; + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("attr/.gitted"))); + + cl_git_pass(git_repository_head(&head, g_repo)); + cl_git_pass(git_reference_peel((git_object**)&g_head_commit, head, GIT_OBJECT_COMMIT)); + cl_git_pass(git_commit_tree(&g_root_tree, g_head_commit)); + cl_git_pass(git_tree_entry_bypath(&tree_entry, g_root_tree, "subdir/subdir_test2.txt")); + cl_git_pass(git_object_lookup(&g_expectedobject, g_repo, git_tree_entry_id(tree_entry), + GIT_OBJECT_ANY)); + + git_tree_entry_free(tree_entry); + git_reference_free(head); + + g_actualobject = NULL; +} +void test_object_lookupbypath__cleanup(void) +{ + git_object_free(g_actualobject); + git_object_free(g_expectedobject); + git_tree_free(g_root_tree); + git_commit_free(g_head_commit); + g_expectedobject = NULL; + git_repository_free(g_repo); + g_repo = NULL; +} + +void test_object_lookupbypath__errors(void) +{ + cl_assert_equal_i(GIT_EINVALIDSPEC, + git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree, + "subdir/subdir_test2.txt", GIT_OBJECT_TREE)); /* It's not a tree */ + cl_assert_equal_i(GIT_ENOTFOUND, + git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree, + "file/doesnt/exist", GIT_OBJECT_ANY)); +} + +void test_object_lookupbypath__from_root_tree(void) +{ + cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree, + "subdir/subdir_test2.txt", GIT_OBJECT_BLOB)); + cl_assert_equal_oid(git_object_id(g_expectedobject), + git_object_id(g_actualobject)); +} + +void test_object_lookupbypath__from_head_commit(void) +{ + cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)g_head_commit, + "subdir/subdir_test2.txt", GIT_OBJECT_BLOB)); + cl_assert_equal_oid(git_object_id(g_expectedobject), + git_object_id(g_actualobject)); +} + +void test_object_lookupbypath__from_subdir_tree(void) +{ + git_tree_entry *entry = NULL; + git_tree *tree = NULL; + + cl_git_pass(git_tree_entry_bypath(&entry, g_root_tree, "subdir")); + cl_git_pass(git_tree_lookup(&tree, g_repo, git_tree_entry_id(entry))); + + cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)tree, + "subdir_test2.txt", GIT_OBJECT_BLOB)); + cl_assert_equal_oid(git_object_id(g_expectedobject), + git_object_id(g_actualobject)); + + git_tree_entry_free(entry); + git_tree_free(tree); +} + diff --git a/tests/libgit2/object/message.c b/tests/libgit2/object/message.c new file mode 100644 index 000000000..d87c8ef70 --- /dev/null +++ b/tests/libgit2/object/message.c @@ -0,0 +1,197 @@ +#include "clar_libgit2.h" + +static void assert_message_prettifying(char *expected_output, char *input, int strip_comments) +{ + git_buf prettified_message = GIT_BUF_INIT; + + git_message_prettify(&prettified_message, input, strip_comments, '#'); + cl_assert_equal_s(expected_output, prettified_message.ptr); + + git_buf_dispose(&prettified_message); +} + +#define t40 "A quick brown fox jumps over the lazy do" +#define s40 " " +#define sss s40 s40 s40 s40 s40 s40 s40 s40 s40 s40 /* # 400 */ +#define ttt t40 t40 t40 t40 t40 t40 t40 t40 t40 t40 /* # 400 */ + +/* Ported from git.git */ +/* see https://github.com/git/git/blob/master/t/t0030-stripspace.sh */ +void test_object_message__long_lines_without_spaces_should_be_unchanged(void) +{ + assert_message_prettifying(ttt "\n", ttt, 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt, 0); + assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt, 0); + assert_message_prettifying(ttt ttt ttt ttt "\n", ttt ttt ttt ttt, 0); +} + +void test_object_message__lines_with_spaces_at_the_beginning_should_be_unchanged(void) +{ + assert_message_prettifying(sss ttt "\n", sss ttt, 0); + assert_message_prettifying(sss sss ttt "\n", sss sss ttt, 0); + assert_message_prettifying(sss sss sss ttt "\n", sss sss sss ttt, 0); +} + +void test_object_message__lines_with_intermediate_spaces_should_be_unchanged(void) +{ + assert_message_prettifying(ttt sss ttt "\n", ttt sss ttt, 0); + assert_message_prettifying(ttt sss sss ttt "\n", ttt sss sss ttt, 0); +} + +void test_object_message__consecutive_blank_lines_should_be_unified(void) +{ + assert_message_prettifying(ttt "\n\n" ttt "\n", ttt "\n\n\n\n\n" ttt "\n", 0); + assert_message_prettifying(ttt ttt "\n\n" ttt "\n", ttt ttt "\n\n\n\n\n" ttt "\n", 0); + assert_message_prettifying(ttt ttt ttt "\n\n" ttt "\n", ttt ttt ttt "\n\n\n\n\n" ttt "\n", 0); + + assert_message_prettifying(ttt "\n\n" ttt ttt "\n", ttt "\n\n\n\n\n" ttt ttt "\n", 0); + assert_message_prettifying(ttt "\n\n" ttt ttt ttt "\n", ttt "\n\n\n\n\n" ttt ttt ttt "\n", 0); + + assert_message_prettifying(ttt "\n\n" ttt "\n", ttt "\n\t\n \n\n \t\t\n" ttt "\n", 0); + assert_message_prettifying(ttt ttt "\n\n" ttt "\n", ttt ttt "\n\t\n \n\n \t\t\n" ttt "\n", 0); + assert_message_prettifying(ttt ttt ttt "\n\n" ttt "\n", ttt ttt ttt "\n\t\n \n\n \t\t\n" ttt "\n", 0); + + assert_message_prettifying(ttt "\n\n" ttt ttt "\n", ttt "\n\t\n \n\n \t\t\n" ttt ttt "\n", 0); + assert_message_prettifying(ttt "\n\n" ttt ttt ttt "\n", ttt "\n\t\n \n\n \t\t\n" ttt ttt ttt "\n", 0); +} + +void test_object_message__only_consecutive_blank_lines_should_be_completely_removed(void) +{ + assert_message_prettifying("", "\n", 0); + assert_message_prettifying("", "\n\n\n", 0); + assert_message_prettifying("", sss "\n" sss "\n" sss "\n", 0); + assert_message_prettifying("", sss sss "\n" sss "\n\n", 0); +} + +void test_object_message__consecutive_blank_lines_at_the_beginning_should_be_removed(void) +{ + assert_message_prettifying(ttt "\n", "\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n", "\n\n\n" ttt "\n", 0); + assert_message_prettifying(ttt ttt "\n", "\n\n\n" ttt ttt "\n", 0); + assert_message_prettifying(ttt ttt ttt "\n", "\n\n\n" ttt ttt ttt "\n", 0); + assert_message_prettifying(ttt ttt ttt ttt "\n", "\n\n\n" ttt ttt ttt ttt "\n", 0); + assert_message_prettifying(ttt "\n", sss "\n" sss "\n" sss "\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n", "\n" sss "\n" sss sss "\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n", sss sss "\n" sss "\n\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n", sss sss sss "\n\n\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n", "\n" sss sss sss "\n\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n", "\n\n" sss sss sss "\n" ttt "\n", 0); +} + +void test_object_message__consecutive_blank_lines_at_the_end_should_be_removed(void) +{ + assert_message_prettifying(ttt "\n", ttt "\n\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n\n\n\n", 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt "\n\n\n\n", 0); + assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt "\n\n\n\n", 0); + assert_message_prettifying(ttt ttt ttt ttt "\n", ttt ttt ttt ttt "\n\n\n\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n" sss "\n" sss "\n" sss "\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n\n" sss "\n" sss sss "\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n" sss sss "\n" sss "\n\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n" sss sss sss "\n\n\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n\n" sss sss sss "\n\n", 0); + assert_message_prettifying(ttt "\n", ttt "\n\n\n" sss sss sss "\n\n", 0); +} + +void test_object_message__text_without_newline_at_end_should_end_with_newline(void) +{ + assert_message_prettifying(ttt "\n", ttt, 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt, 0); + assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt, 0); + assert_message_prettifying(ttt ttt ttt ttt "\n", ttt ttt ttt ttt, 0); +} + +void test_object_message__text_plus_spaces_without_newline_should_not_show_spaces_and_end_with_newline(void) +{ + assert_message_prettifying(ttt "\n", ttt sss, 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt sss, 0); + assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt sss, 0); + assert_message_prettifying(ttt "\n", ttt sss sss, 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt sss sss, 0); + assert_message_prettifying(ttt "\n", ttt sss sss sss, 0); +} + +void test_object_message__text_plus_spaces_ending_with_newline_should_be_cleaned_and_newline_must_remain(void){ + assert_message_prettifying(ttt "\n", ttt sss "\n", 0); + assert_message_prettifying(ttt "\n", ttt sss sss "\n", 0); + assert_message_prettifying(ttt "\n", ttt sss sss sss "\n", 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt sss "\n", 0); + assert_message_prettifying(ttt ttt "\n", ttt ttt sss sss "\n", 0); + assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt sss "\n", 0); +} + +void test_object_message__spaces_with_newline_at_end_should_be_replaced_with_empty_string(void) +{ + assert_message_prettifying("", sss "\n", 0); + assert_message_prettifying("", sss sss "\n", 0); + assert_message_prettifying("", sss sss sss "\n", 0); + assert_message_prettifying("", sss sss sss sss "\n", 0); +} + +void test_object_message__spaces_without_newline_at_end_should_be_replaced_with_empty_string(void) +{ + assert_message_prettifying("", "", 0); + assert_message_prettifying("", sss sss, 0); + assert_message_prettifying("", sss sss sss, 0); + assert_message_prettifying("", sss sss sss sss, 0); +} + +void test_object_message__consecutive_text_lines_should_be_unchanged(void) +{ + assert_message_prettifying(ttt ttt "\n" ttt "\n", ttt ttt "\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n" ttt ttt "\n" ttt "\n", ttt "\n" ttt ttt "\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n" ttt "\n" ttt "\n" ttt ttt "\n", ttt "\n" ttt "\n" ttt "\n" ttt ttt "\n", 0); + assert_message_prettifying(ttt "\n" ttt "\n\n" ttt ttt "\n" ttt "\n", ttt "\n" ttt "\n\n" ttt ttt "\n" ttt "\n", 0); + assert_message_prettifying(ttt ttt "\n\n" ttt "\n" ttt ttt "\n", ttt ttt "\n\n" ttt "\n" ttt ttt "\n", 0); + assert_message_prettifying(ttt "\n" ttt ttt "\n\n" ttt "\n", ttt "\n" ttt ttt "\n\n" ttt "\n", 0); +} + +void test_object_message__strip_comments(void) +{ + assert_message_prettifying("", "# comment", 1); + assert_message_prettifying("", "# comment\n", 1); + assert_message_prettifying("", "# comment \n", 1); + + assert_message_prettifying(ttt "\n", ttt "\n" "# comment\n", 1); + assert_message_prettifying(ttt "\n", "# comment\n" ttt "\n", 1); + assert_message_prettifying(ttt "\n" ttt "\n", ttt "\n" "# comment\n" ttt "\n", 1); +} + +void test_object_message__keep_comments(void) +{ + assert_message_prettifying("# comment\n", "# comment", 0); + assert_message_prettifying("# comment\n", "# comment\n", 0); + assert_message_prettifying("# comment\n", "# comment \n", 0); + + assert_message_prettifying(ttt "\n" "# comment\n", ttt "\n" "# comment\n", 0); + assert_message_prettifying("# comment\n" ttt "\n", "# comment\n" ttt "\n", 0); + assert_message_prettifying(ttt "\n" "# comment\n" ttt "\n", ttt "\n" "# comment\n" ttt "\n", 0); +} + +void test_object_message__message_prettify(void) +{ + git_buf buffer; + + memset(&buffer, 0, sizeof(buffer)); + cl_git_pass(git_message_prettify(&buffer, "", 0, '#')); + cl_assert_equal_s(buffer.ptr, ""); + git_buf_dispose(&buffer); + cl_git_pass(git_message_prettify(&buffer, "", 1, '#')); + cl_assert_equal_s(buffer.ptr, ""); + git_buf_dispose(&buffer); + + cl_git_pass(git_message_prettify(&buffer, "Short", 0, '#')); + cl_assert_equal_s("Short\n", buffer.ptr); + git_buf_dispose(&buffer); + cl_git_pass(git_message_prettify(&buffer, "Short", 1, '#')); + cl_assert_equal_s("Short\n", buffer.ptr); + git_buf_dispose(&buffer); + + cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 0, '#')); + cl_assert_equal_s(buffer.ptr, "This is longer\nAnd multiline\n# with some comments still in\n"); + git_buf_dispose(&buffer); + + cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 1, '#')); + cl_assert_equal_s(buffer.ptr, "This is longer\nAnd multiline\n"); + git_buf_dispose(&buffer); +} diff --git a/tests/libgit2/object/peel.c b/tests/libgit2/object/peel.c new file mode 100644 index 000000000..8be6e42d4 --- /dev/null +++ b/tests/libgit2/object/peel.c @@ -0,0 +1,118 @@ +#include "clar_libgit2.h" + +static git_repository *g_repo; + +void test_object_peel__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); +} + +void test_object_peel__cleanup(void) +{ + git_repository_free(g_repo); + g_repo = NULL; +} + +static void assert_peel( + const char *sha, + git_object_t requested_type, + const char* expected_sha, + git_object_t expected_type) +{ + git_oid oid, expected_oid; + git_object *obj; + git_object *peeled; + + cl_git_pass(git_oid_fromstr(&oid, sha)); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_git_pass(git_object_peel(&peeled, obj, requested_type)); + + cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); + cl_assert_equal_oid(&expected_oid, git_object_id(peeled)); + + cl_assert_equal_i(expected_type, git_object_type(peeled)); + + git_object_free(peeled); + git_object_free(obj); +} + +static void assert_peel_error(int error, const char *sha, git_object_t requested_type) +{ + git_oid oid; + git_object *obj; + git_object *peeled; + + cl_git_pass(git_oid_fromstr(&oid, sha)); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); + + cl_assert_equal_i(error, git_object_peel(&peeled, obj, requested_type)); + + git_object_free(obj); +} + +void test_object_peel__peeling_an_object_into_its_own_type_returns_another_instance_of_it(void) +{ + assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_TAG, + "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_TAG); + assert_peel("53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); + assert_peel("0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_BLOB, + "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_BLOB); +} + +void test_object_peel__tag(void) +{ + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_COMMIT, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_TREE, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); + assert_peel_error(GIT_EPEEL, "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_BLOB); + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_ANY, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); +} + +void test_object_peel__commit(void) +{ + assert_peel_error(GIT_EINVALIDSPEC, "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_BLOB); + assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_TREE, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); + assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); + assert_peel_error(GIT_EINVALIDSPEC, "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_TAG); + assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_ANY, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); +} + +void test_object_peel__tree(void) +{ + assert_peel_error(GIT_EINVALIDSPEC, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_BLOB); + assert_peel("53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); + assert_peel_error(GIT_EINVALIDSPEC, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_COMMIT); + assert_peel_error(GIT_EINVALIDSPEC, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TAG); + assert_peel_error(GIT_EINVALIDSPEC, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_ANY); +} + +void test_object_peel__blob(void) +{ + assert_peel("0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_BLOB, + "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_BLOB); + assert_peel_error(GIT_EINVALIDSPEC, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_TREE); + assert_peel_error(GIT_EINVALIDSPEC, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_COMMIT); + assert_peel_error(GIT_EINVALIDSPEC, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_TAG); + assert_peel_error(GIT_EINVALIDSPEC, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_ANY); +} + +void test_object_peel__target_any_object_for_type_change(void) +{ + /* tag to commit */ + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_ANY, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); + + /* commit to tree */ + assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_ANY, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); +} diff --git a/tests/libgit2/object/raw/chars.c b/tests/libgit2/object/raw/chars.c new file mode 100644 index 000000000..cde0bdbf6 --- /dev/null +++ b/tests/libgit2/object/raw/chars.c @@ -0,0 +1,41 @@ + +#include "clar_libgit2.h" + +#include "odb.h" + +void test_object_raw_chars__find_invalid_chars_in_oid(void) +{ + git_oid out; + unsigned char exp[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + char in[] = "16a67770b7d8d72317c4b775213c23a8bd74f5e0"; + unsigned int i; + + for (i = 0; i < 256; i++) { + in[38] = (char)i; + if (git__fromhex(i) >= 0) { + exp[19] = (unsigned char)(git__fromhex(i) << 4); + cl_git_pass(git_oid_fromstr(&out, in)); + cl_assert(memcmp(out.id, exp, sizeof(out.id)) == 0); + } else { + cl_git_fail(git_oid_fromstr(&out, in)); + } + } +} + +void test_object_raw_chars__build_valid_oid_from_raw_bytes(void) +{ + git_oid out; + unsigned char exp[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + git_oid_fromraw(&out, exp); + cl_git_pass(memcmp(out.id, exp, sizeof(out.id))); +} diff --git a/tests/libgit2/object/raw/compare.c b/tests/libgit2/object/raw/compare.c new file mode 100644 index 000000000..56c016b72 --- /dev/null +++ b/tests/libgit2/object/raw/compare.c @@ -0,0 +1,123 @@ + +#include "clar_libgit2.h" + +#include "odb.h" + +void test_object_raw_compare__succeed_on_copy_oid(void) +{ + git_oid a, b; + unsigned char exp[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + memset(&b, 0, sizeof(b)); + git_oid_fromraw(&a, exp); + git_oid_cpy(&b, &a); + cl_git_pass(memcmp(a.id, exp, sizeof(a.id))); +} + +void test_object_raw_compare__succeed_on_oid_comparison_lesser(void) +{ + git_oid a, b; + unsigned char a_in[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + unsigned char b_in[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xf0, + }; + git_oid_fromraw(&a, a_in); + git_oid_fromraw(&b, b_in); + cl_assert(git_oid_cmp(&a, &b) < 0); +} + +void test_object_raw_compare__succeed_on_oid_comparison_equal(void) +{ + git_oid a, b; + unsigned char a_in[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + git_oid_fromraw(&a, a_in); + git_oid_fromraw(&b, a_in); + cl_assert(git_oid_cmp(&a, &b) == 0); +} + +void test_object_raw_compare__succeed_on_oid_comparison_greater(void) +{ + git_oid a, b; + unsigned char a_in[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + unsigned char b_in[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xd0, + }; + git_oid_fromraw(&a, a_in); + git_oid_fromraw(&b, b_in); + cl_assert(git_oid_cmp(&a, &b) > 0); +} + +void test_object_raw_compare__compare_fmt_oids(void) +{ + const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; + git_oid in; + char out[GIT_OID_HEXSZ + 1]; + + cl_git_pass(git_oid_fromstr(&in, exp)); + + /* Format doesn't touch the last byte */ + out[GIT_OID_HEXSZ] = 'Z'; + git_oid_fmt(out, &in); + cl_assert(out[GIT_OID_HEXSZ] == 'Z'); + + /* Format produced the right result */ + out[GIT_OID_HEXSZ] = '\0'; + cl_assert_equal_s(exp, out); +} + +void test_object_raw_compare__compare_static_oids(void) +{ + const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; + git_oid in; + char *out; + + cl_git_pass(git_oid_fromstr(&in, exp)); + + out = git_oid_tostr_s(&in); + cl_assert(out); + cl_assert_equal_s(exp, out); +} + +void test_object_raw_compare__compare_pathfmt_oids(void) +{ + const char *exp1 = "16a0123456789abcdef4b775213c23a8bd74f5e0"; + const char *exp2 = "16/a0123456789abcdef4b775213c23a8bd74f5e0"; + git_oid in; + char out[GIT_OID_HEXSZ + 2]; + + cl_git_pass(git_oid_fromstr(&in, exp1)); + + /* Format doesn't touch the last byte */ + out[GIT_OID_HEXSZ + 1] = 'Z'; + git_oid_pathfmt(out, &in); + cl_assert(out[GIT_OID_HEXSZ + 1] == 'Z'); + + /* Format produced the right result */ + out[GIT_OID_HEXSZ + 1] = '\0'; + cl_assert_equal_s(exp2, out); +} diff --git a/tests/libgit2/object/raw/convert.c b/tests/libgit2/object/raw/convert.c new file mode 100644 index 000000000..40a01ae09 --- /dev/null +++ b/tests/libgit2/object/raw/convert.c @@ -0,0 +1,112 @@ + +#include "clar_libgit2.h" + +#include "odb.h" + +void test_object_raw_convert__succeed_on_oid_to_string_conversion(void) +{ + const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; + git_oid in; + char out[GIT_OID_HEXSZ + 1]; + char *str; + int i; + + cl_git_pass(git_oid_fromstr(&in, exp)); + + /* NULL buffer pointer, returns static empty string */ + str = git_oid_tostr(NULL, sizeof(out), &in); + cl_assert(str && *str == '\0' && str != out); + + /* zero buffer size, returns static empty string */ + str = git_oid_tostr(out, 0, &in); + cl_assert(str && *str == '\0' && str != out); + + /* NULL oid pointer, sets existing buffer to empty string */ + str = git_oid_tostr(out, sizeof(out), NULL); + cl_assert(str && *str == '\0' && str == out); + + /* n == 1, returns out as an empty string */ + str = git_oid_tostr(out, 1, &in); + cl_assert(str && *str == '\0' && str == out); + + for (i = 1; i < GIT_OID_HEXSZ; i++) { + out[i+1] = 'Z'; + str = git_oid_tostr(out, i+1, &in); + /* returns out containing c-string */ + cl_assert(str && str == out); + /* must be '\0' terminated */ + cl_assert(*(str+i) == '\0'); + /* must not touch bytes past end of string */ + cl_assert(*(str+(i+1)) == 'Z'); + /* i == n-1 characters of string */ + cl_git_pass(strncmp(exp, out, i)); + } + + /* returns out as hex formatted c-string */ + str = git_oid_tostr(out, sizeof(out), &in); + cl_assert(str && str == out && *(str+GIT_OID_HEXSZ) == '\0'); + cl_assert_equal_s(exp, out); +} + +void test_object_raw_convert__succeed_on_oid_to_string_conversion_big(void) +{ + const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; + git_oid in; + char big[GIT_OID_HEXSZ + 1 + 3]; /* note + 4 => big buffer */ + char *str; + + cl_git_pass(git_oid_fromstr(&in, exp)); + + /* place some tail material */ + big[GIT_OID_HEXSZ+0] = 'W'; /* should be '\0' afterwards */ + big[GIT_OID_HEXSZ+1] = 'X'; /* should remain untouched */ + big[GIT_OID_HEXSZ+2] = 'Y'; /* ditto */ + big[GIT_OID_HEXSZ+3] = 'Z'; /* ditto */ + + /* returns big as hex formatted c-string */ + str = git_oid_tostr(big, sizeof(big), &in); + cl_assert(str && str == big && *(str+GIT_OID_HEXSZ) == '\0'); + cl_assert_equal_s(exp, big); + + /* check tail material is untouched */ + cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+1) == 'X'); + cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+2) == 'Y'); + cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+3) == 'Z'); +} + +static void check_partial_oid( + char *buffer, size_t count, const git_oid *oid, const char *expected) +{ + git_oid_nfmt(buffer, count, oid); + buffer[count] = '\0'; + cl_assert_equal_s(expected, buffer); +} + +void test_object_raw_convert__convert_oid_partially(void) +{ + const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; + git_oid in; + char big[GIT_OID_HEXSZ + 1 + 3]; /* note + 4 => big buffer */ + + cl_git_pass(git_oid_fromstr(&in, exp)); + + git_oid_nfmt(big, sizeof(big), &in); + cl_assert_equal_s(exp, big); + + git_oid_nfmt(big, GIT_OID_HEXSZ + 1, &in); + cl_assert_equal_s(exp, big); + + check_partial_oid(big, 1, &in, "1"); + check_partial_oid(big, 2, &in, "16"); + check_partial_oid(big, 3, &in, "16a"); + check_partial_oid(big, 4, &in, "16a0"); + check_partial_oid(big, 5, &in, "16a01"); + + check_partial_oid(big, GIT_OID_HEXSZ, &in, exp); + check_partial_oid( + big, GIT_OID_HEXSZ - 1, &in, "16a0123456789abcdef4b775213c23a8bd74f5e"); + check_partial_oid( + big, GIT_OID_HEXSZ - 2, &in, "16a0123456789abcdef4b775213c23a8bd74f5"); + check_partial_oid( + big, GIT_OID_HEXSZ - 3, &in, "16a0123456789abcdef4b775213c23a8bd74f"); +} diff --git a/tests/libgit2/object/raw/data.h b/tests/libgit2/object/raw/data.h new file mode 100644 index 000000000..57431e70e --- /dev/null +++ b/tests/libgit2/object/raw/data.h @@ -0,0 +1,323 @@ + +/* + * Raw data + */ +static unsigned char commit_data[] = { + 0x74, 0x72, 0x65, 0x65, 0x20, 0x64, 0x66, 0x66, + 0x32, 0x64, 0x61, 0x39, 0x30, 0x62, 0x32, 0x35, + 0x34, 0x65, 0x31, 0x62, 0x65, 0x62, 0x38, 0x38, + 0x39, 0x64, 0x31, 0x66, 0x31, 0x66, 0x31, 0x32, + 0x38, 0x38, 0x62, 0x65, 0x31, 0x38, 0x30, 0x33, + 0x37, 0x38, 0x32, 0x64, 0x66, 0x0a, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x20, 0x41, 0x20, 0x55, + 0x20, 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, + 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, + 0x31, 0x34, 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, + 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x74, 0x65, 0x72, 0x20, 0x43, 0x20, + 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, + 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, + 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, + 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, + 0x30, 0x0a, 0x0a, 0x41, 0x20, 0x6f, 0x6e, 0x65, + 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x73, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x0a, 0x0a, 0x54, 0x68, + 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, + 0x20, 0x66, 0x75, 0x72, 0x74, 0x68, 0x65, 0x72, + 0x20, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x70, 0x75, 0x72, 0x70, + 0x6f, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x62, 0x79, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x53, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x2d, 0x6f, 0x66, 0x2d, + 0x62, 0x79, 0x3a, 0x20, 0x41, 0x20, 0x55, 0x20, + 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + 0x3e, 0x0a, +}; + + +static unsigned char tree_data[] = { + 0x31, 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x6f, + 0x6e, 0x65, 0x00, 0x8b, 0x13, 0x78, 0x91, 0x79, + 0x1f, 0xe9, 0x69, 0x27, 0xad, 0x78, 0xe6, 0x4b, + 0x0a, 0xad, 0x7b, 0xde, 0xd0, 0x8b, 0xdc, 0x31, + 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x73, 0x6f, + 0x6d, 0x65, 0x00, 0xfd, 0x84, 0x30, 0xbc, 0x86, + 0x4c, 0xfc, 0xd5, 0xf1, 0x0e, 0x55, 0x90, 0xf8, + 0xa4, 0x47, 0xe0, 0x1b, 0x94, 0x2b, 0xfe, 0x31, + 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x74, 0x77, + 0x6f, 0x00, 0x78, 0x98, 0x19, 0x22, 0x61, 0x3b, + 0x2a, 0xfb, 0x60, 0x25, 0x04, 0x2f, 0xf6, 0xbd, + 0x87, 0x8a, 0xc1, 0x99, 0x4e, 0x85, 0x31, 0x30, + 0x30, 0x36, 0x34, 0x34, 0x20, 0x7a, 0x65, 0x72, + 0x6f, 0x00, 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, + 0xd6, 0x43, 0x4b, 0x8b, 0x29, 0xae, 0x77, 0x5a, + 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91, +}; + +static unsigned char tag_data[] = { + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x33, + 0x64, 0x37, 0x66, 0x38, 0x61, 0x36, 0x61, 0x66, + 0x30, 0x37, 0x36, 0x63, 0x38, 0x63, 0x33, 0x66, + 0x32, 0x30, 0x30, 0x37, 0x31, 0x61, 0x38, 0x39, + 0x33, 0x35, 0x63, 0x64, 0x62, 0x65, 0x38, 0x32, + 0x32, 0x38, 0x35, 0x39, 0x34, 0x64, 0x31, 0x0a, + 0x74, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x0a, 0x74, 0x61, 0x67, 0x20, + 0x76, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0a, 0x74, + 0x61, 0x67, 0x67, 0x65, 0x72, 0x20, 0x43, 0x20, + 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, + 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, + 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, + 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, + 0x30, 0x0a, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20, + 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, + 0x61, 0x67, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, + 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x76, 0x30, + 0x2e, 0x30, 0x2e, 0x31, 0x0a, +}; + +/* + * Dummy data + */ +static unsigned char zero_data[] = { + 0x00, +}; + +static unsigned char one_data[] = { + 0x0a, +}; + +static unsigned char two_data[] = { + 0x61, 0x0a, +}; + +static unsigned char some_data[] = { + 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, + 0x69, 0x73, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, + 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, + 0x3b, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, + 0x6e, 0x20, 0x72, 0x65, 0x64, 0x69, 0x73, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x69, + 0x74, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, + 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x0a, + 0x20, 0x2a, 0x20, 0x69, 0x74, 0x20, 0x75, 0x6e, + 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2c, + 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x20, 0x32, 0x2c, 0x0a, 0x20, 0x2a, 0x20, 0x61, + 0x73, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, + 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, + 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x2a, 0x0a, + 0x20, 0x2a, 0x20, 0x49, 0x6e, 0x20, 0x61, 0x64, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x47, 0x4e, 0x55, 0x20, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, 0x63, 0x65, + 0x6e, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x2a, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x73, 0x20, 0x67, 0x69, 0x76, 0x65, + 0x20, 0x79, 0x6f, 0x75, 0x20, 0x75, 0x6e, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x20, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x69, 0x6e, + 0x6b, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x0a, 0x20, + 0x2a, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, + 0x6e, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6d, 0x62, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x74, + 0x68, 0x65, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x67, + 0x72, 0x61, 0x6d, 0x73, 0x2c, 0x0a, 0x20, 0x2a, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20, + 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65, + 0x20, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x6e, + 0x79, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x2a, + 0x20, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x20, + 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x2e, 0x20, 0x20, 0x28, 0x54, 0x68, 0x65, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, + 0x20, 0x2a, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, + 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, + 0x64, 0x6f, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, + 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x74, 0x68, 0x65, + 0x72, 0x20, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, + 0x74, 0x73, 0x3b, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c, + 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x0a, 0x20, 0x2a, 0x20, 0x6d, + 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2c, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x73, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x6e, + 0x6f, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x65, + 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x0a, 0x20, + 0x2a, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x62, + 0x69, 0x6e, 0x65, 0x64, 0x20, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, + 0x29, 0x0a, 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, + 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, + 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x68, 0x6f, 0x70, 0x65, 0x20, 0x74, 0x68, 0x61, + 0x74, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c, + 0x6c, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, + 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x62, 0x75, 0x74, + 0x0a, 0x20, 0x2a, 0x20, 0x57, 0x49, 0x54, 0x48, + 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20, + 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, + 0x3b, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, + 0x74, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x69, + 0x65, 0x64, 0x20, 0x77, 0x61, 0x72, 0x72, 0x61, + 0x6e, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x0a, 0x20, + 0x2a, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, + 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, + 0x59, 0x20, 0x6f, 0x72, 0x20, 0x46, 0x49, 0x54, + 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, + 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 0x49, + 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, + 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2e, 0x20, 0x20, + 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x47, 0x4e, 0x55, 0x0a, 0x20, 0x2a, 0x20, 0x47, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, + 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x0a, + 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x59, 0x6f, + 0x75, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, + 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x20, 0x61, + 0x20, 0x63, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, + 0x20, 0x2a, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x61, 0x6d, 0x3b, 0x20, 0x73, 0x65, 0x65, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x20, 0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47, + 0x2e, 0x20, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, + 0x74, 0x2c, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, + 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x2a, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, + 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, + 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x35, 0x31, 0x20, + 0x46, 0x72, 0x61, 0x6e, 0x6b, 0x6c, 0x69, 0x6e, + 0x20, 0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x2c, + 0x20, 0x46, 0x69, 0x66, 0x74, 0x68, 0x20, 0x46, + 0x6c, 0x6f, 0x6f, 0x72, 0x2c, 0x0a, 0x20, 0x2a, + 0x20, 0x42, 0x6f, 0x73, 0x74, 0x6f, 0x6e, 0x2c, + 0x20, 0x4d, 0x41, 0x20, 0x30, 0x32, 0x31, 0x31, + 0x30, 0x2d, 0x31, 0x33, 0x30, 0x31, 0x2c, 0x20, + 0x55, 0x53, 0x41, 0x2e, 0x0a, 0x20, 0x2a, 0x2f, + 0x0a, +}; + +/* + * SHA1 Hashes + */ +static char *commit_id = "3d7f8a6af076c8c3f20071a8935cdbe8228594d1"; +static char *tree_id = "dff2da90b254e1beb889d1f1f1288be1803782df"; +static char *tag_id = "09d373e1dfdc16b129ceec6dd649739911541e05"; +static char *zero_id = "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"; +static char *one_id = "8b137891791fe96927ad78e64b0aad7bded08bdc"; +static char *two_id = "78981922613b2afb6025042ff6bd878ac1994e85"; +static char *some_id = "fd8430bc864cfcd5f10e5590f8a447e01b942bfe"; + +/* + * In-memory objects + */ +static git_rawobj tree_obj = { + tree_data, + sizeof(tree_data), + GIT_OBJECT_TREE +}; + +static git_rawobj tag_obj = { + tag_data, + sizeof(tag_data), + GIT_OBJECT_TAG +}; + +static git_rawobj zero_obj = { + zero_data, + 0, + GIT_OBJECT_BLOB +}; + +static git_rawobj one_obj = { + one_data, + sizeof(one_data), + GIT_OBJECT_BLOB +}; + +static git_rawobj two_obj = { + two_data, + sizeof(two_data), + GIT_OBJECT_BLOB +}; + +static git_rawobj commit_obj = { + commit_data, + sizeof(commit_data), + GIT_OBJECT_COMMIT +}; + +static git_rawobj some_obj = { + some_data, + sizeof(some_data), + GIT_OBJECT_BLOB +}; + +static git_rawobj junk_obj = { + NULL, + 0, + GIT_OBJECT_INVALID +}; diff --git a/tests/libgit2/object/raw/fromstr.c b/tests/libgit2/object/raw/fromstr.c new file mode 100644 index 000000000..8c11c105f --- /dev/null +++ b/tests/libgit2/object/raw/fromstr.c @@ -0,0 +1,30 @@ + +#include "clar_libgit2.h" + +#include "odb.h" + +void test_object_raw_fromstr__fail_on_invalid_oid_string(void) +{ + git_oid out; + cl_git_fail(git_oid_fromstr(&out, "")); + cl_git_fail(git_oid_fromstr(&out, "moo")); + cl_git_fail(git_oid_fromstr(&out, "16a67770b7d8d72317c4b775213c23a8bd74f5ez")); +} + +void test_object_raw_fromstr__succeed_on_valid_oid_string(void) +{ + git_oid out; + unsigned char exp[] = { + 0x16, 0xa6, 0x77, 0x70, 0xb7, + 0xd8, 0xd7, 0x23, 0x17, 0xc4, + 0xb7, 0x75, 0x21, 0x3c, 0x23, + 0xa8, 0xbd, 0x74, 0xf5, 0xe0, + }; + + cl_git_pass(git_oid_fromstr(&out, "16a67770b7d8d72317c4b775213c23a8bd74f5e0")); + cl_git_pass(memcmp(out.id, exp, sizeof(out.id))); + + cl_git_pass(git_oid_fromstr(&out, "16A67770B7D8D72317C4b775213C23A8BD74F5E0")); + cl_git_pass(memcmp(out.id, exp, sizeof(out.id))); + +} diff --git a/tests/libgit2/object/raw/hash.c b/tests/libgit2/object/raw/hash.c new file mode 100644 index 000000000..5a3e81855 --- /dev/null +++ b/tests/libgit2/object/raw/hash.c @@ -0,0 +1,169 @@ + +#include "clar_libgit2.h" + +#include "odb.h" +#include "hash.h" + +#include "data.h" + +static void hash_object_pass(git_oid *oid, git_rawobj *obj) +{ + cl_git_pass(git_odb_hash(oid, obj->data, obj->len, obj->type)); +} +static void hash_object_fail(git_oid *oid, git_rawobj *obj) +{ + cl_git_fail(git_odb_hash(oid, obj->data, obj->len, obj->type)); +} + +static char *hello_id = "22596363b3de40b06f981fb85d82312e8c0ed511"; +static char *hello_text = "hello world\n"; + +static char *bye_id = "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"; +static char *bye_text = "bye world\n"; + +void test_object_raw_hash__hash_by_blocks(void) +{ + git_hash_ctx ctx; + git_oid id1, id2; + + cl_git_pass(git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)); + + /* should already be init'd */ + cl_git_pass(git_hash_update(&ctx, hello_text, strlen(hello_text))); + cl_git_pass(git_hash_final(id2.id, &ctx)); + cl_git_pass(git_oid_fromstr(&id1, hello_id)); + cl_assert(git_oid_cmp(&id1, &id2) == 0); + + /* reinit should permit reuse */ + cl_git_pass(git_hash_init(&ctx)); + cl_git_pass(git_hash_update(&ctx, bye_text, strlen(bye_text))); + cl_git_pass(git_hash_final(id2.id, &ctx)); + cl_git_pass(git_oid_fromstr(&id1, bye_id)); + cl_assert(git_oid_cmp(&id1, &id2) == 0); + + git_hash_ctx_cleanup(&ctx); +} + +void test_object_raw_hash__hash_buffer_in_single_call(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, hello_id)); + git_hash_buf(id2.id, hello_text, strlen(hello_text), GIT_HASH_ALGORITHM_SHA1); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_vector(void) +{ + git_oid id1, id2; + git_str_vec vec[2]; + + cl_git_pass(git_oid_fromstr(&id1, hello_id)); + + vec[0].data = hello_text; + vec[0].len = 4; + vec[1].data = hello_text+4; + vec[1].len = strlen(hello_text)-4; + + git_hash_vec(id2.id, vec, 2, GIT_HASH_ALGORITHM_SHA1); + + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_junk_data(void) +{ + git_oid id, id_zero; + + cl_git_pass(git_oid_fromstr(&id_zero, zero_id)); + + /* invalid types: */ + junk_obj.data = some_data; + hash_object_fail(&id, &junk_obj); + + junk_obj.type = 0; /* EXT1 */ + hash_object_fail(&id, &junk_obj); + + junk_obj.type = 5; /* EXT2 */ + hash_object_fail(&id, &junk_obj); + + junk_obj.type = GIT_OBJECT_OFS_DELTA; + hash_object_fail(&id, &junk_obj); + + junk_obj.type = GIT_OBJECT_REF_DELTA; + hash_object_fail(&id, &junk_obj); + + junk_obj.type = 42; + hash_object_fail(&id, &junk_obj); + + /* data can be NULL only if len is zero: */ + junk_obj.type = GIT_OBJECT_BLOB; + junk_obj.data = NULL; + hash_object_pass(&id, &junk_obj); + cl_assert(git_oid_cmp(&id, &id_zero) == 0); + + junk_obj.len = 1; + hash_object_fail(&id, &junk_obj); +} + +void test_object_raw_hash__hash_commit_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, commit_id)); + hash_object_pass(&id2, &commit_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_tree_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, tree_id)); + hash_object_pass(&id2, &tree_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_tag_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, tag_id)); + hash_object_pass(&id2, &tag_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_zero_length_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, zero_id)); + hash_object_pass(&id2, &zero_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_one_byte_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, one_id)); + hash_object_pass(&id2, &one_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_two_byte_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, two_id)); + hash_object_pass(&id2, &two_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} + +void test_object_raw_hash__hash_multi_byte_object(void) +{ + git_oid id1, id2; + + cl_git_pass(git_oid_fromstr(&id1, some_id)); + hash_object_pass(&id2, &some_obj); + cl_assert(git_oid_cmp(&id1, &id2) == 0); +} diff --git a/tests/libgit2/object/raw/short.c b/tests/libgit2/object/raw/short.c new file mode 100644 index 000000000..e8d2cf5a5 --- /dev/null +++ b/tests/libgit2/object/raw/short.c @@ -0,0 +1,137 @@ + +#include "clar_libgit2.h" + +#include "odb.h" +#include "hash.h" + +void test_object_raw_short__oid_shortener_no_duplicates(void) +{ + git_oid_shorten *os; + int min_len; + + os = git_oid_shorten_new(0); + cl_assert(os != NULL); + + git_oid_shorten_add(os, "22596363b3de40b06f981fb85d82312e8c0ed511"); + git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); + git_oid_shorten_add(os, "16a0123456789abcdef4b775213c23a8bd74f5e0"); + min_len = git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); + + cl_assert(min_len == GIT_OID_HEXSZ + 1); + + git_oid_shorten_free(os); +} + +static int insert_sequential_oids( + char ***out, git_oid_shorten *os, int n, int fail) +{ + int i, min_len = 0; + char numbuf[16]; + git_oid oid; + char **oids = git__calloc(n, sizeof(char *)); + cl_assert(oids != NULL); + + for (i = 0; i < n; ++i) { + p_snprintf(numbuf, sizeof(numbuf), "%u", (unsigned int)i); + git_hash_buf(oid.id, numbuf, strlen(numbuf), GIT_HASH_ALGORITHM_SHA1); + + oids[i] = git__malloc(GIT_OID_HEXSZ + 1); + cl_assert(oids[i]); + git_oid_nfmt(oids[i], GIT_OID_HEXSZ + 1, &oid); + + min_len = git_oid_shorten_add(os, oids[i]); + + /* After "fail", we expect git_oid_shorten_add to fail */ + if (fail >= 0 && i >= fail) + cl_assert(min_len < 0); + else + cl_assert(min_len >= 0); + } + + *out = oids; + + return min_len; +} + +static void free_oids(int n, char **oids) +{ + int i; + + for (i = 0; i < n; ++i) { + git__free(oids[i]); + } + git__free(oids); +} + +void test_object_raw_short__oid_shortener_stresstest_git_oid_shorten(void) +{ +#define MAX_OIDS 1000 + + git_oid_shorten *os; + size_t i, j; + int min_len = 0, found_collision; + char **oids; + + os = git_oid_shorten_new(0); + cl_assert(os != NULL); + + /* + * Insert in the shortener 1000 unique SHA1 ids + */ + min_len = insert_sequential_oids(&oids, os, MAX_OIDS, MAX_OIDS); + cl_assert(min_len > 0); + + /* + * Compare the first `min_char - 1` characters of each + * SHA1 OID. If the minimizer worked, we should find at + * least one collision + */ + found_collision = 0; + for (i = 0; i < MAX_OIDS; ++i) { + for (j = i + 1; j < MAX_OIDS; ++j) { + if (memcmp(oids[i], oids[j], min_len - 1) == 0) + found_collision = 1; + } + } + cl_assert_equal_b(true, found_collision); + + /* + * Compare the first `min_char` characters of each + * SHA1 OID. If the minimizer worked, every single preffix + * should be unique. + */ + found_collision = 0; + for (i = 0; i < MAX_OIDS; ++i) { + for (j = i + 1; j < MAX_OIDS; ++j) { + if (memcmp(oids[i], oids[j], min_len) == 0) + found_collision = 1; + } + } + cl_assert_equal_b(false, found_collision); + + /* cleanup */ + free_oids(MAX_OIDS, oids); + git_oid_shorten_free(os); + +#undef MAX_OIDS +} + +void test_object_raw_short__oid_shortener_too_much_oids(void) +{ + /* The magic number of oids at which an oid_shortener will fail. + * This was experimentally established. */ +#define MAX_OIDS 24556 + + git_oid_shorten *os; + char **oids; + + os = git_oid_shorten_new(0); + cl_assert(os != NULL); + + cl_assert(insert_sequential_oids(&oids, os, MAX_OIDS, MAX_OIDS - 1) < 0); + + free_oids(MAX_OIDS, oids); + git_oid_shorten_free(os); + +#undef MAX_OIDS +} diff --git a/tests/libgit2/object/raw/size.c b/tests/libgit2/object/raw/size.c new file mode 100644 index 000000000..930c6de23 --- /dev/null +++ b/tests/libgit2/object/raw/size.c @@ -0,0 +1,13 @@ + +#include "clar_libgit2.h" + +#include "odb.h" + +void test_object_raw_size__validate_oid_size(void) +{ + git_oid out; + cl_assert(20 == GIT_OID_RAWSZ); + cl_assert(40 == GIT_OID_HEXSZ); + cl_assert(sizeof(out) == GIT_OID_RAWSZ); + cl_assert(sizeof(out.id) == GIT_OID_RAWSZ); +} diff --git a/tests/libgit2/object/raw/type2string.c b/tests/libgit2/object/raw/type2string.c new file mode 100644 index 000000000..ebd81f552 --- /dev/null +++ b/tests/libgit2/object/raw/type2string.c @@ -0,0 +1,54 @@ + +#include "clar_libgit2.h" + +#include "odb.h" +#include "hash.h" + +void test_object_raw_type2string__convert_type_to_string(void) +{ + cl_assert_equal_s(git_object_type2string(GIT_OBJECT_INVALID), ""); + cl_assert_equal_s(git_object_type2string(0), ""); /* EXT1 */ + cl_assert_equal_s(git_object_type2string(GIT_OBJECT_COMMIT), "commit"); + cl_assert_equal_s(git_object_type2string(GIT_OBJECT_TREE), "tree"); + cl_assert_equal_s(git_object_type2string(GIT_OBJECT_BLOB), "blob"); + cl_assert_equal_s(git_object_type2string(GIT_OBJECT_TAG), "tag"); + cl_assert_equal_s(git_object_type2string(5), ""); /* EXT2 */ + cl_assert_equal_s(git_object_type2string(GIT_OBJECT_OFS_DELTA), "OFS_DELTA"); + cl_assert_equal_s(git_object_type2string(GIT_OBJECT_REF_DELTA), "REF_DELTA"); + + cl_assert_equal_s(git_object_type2string(-2), ""); + cl_assert_equal_s(git_object_type2string(8), ""); + cl_assert_equal_s(git_object_type2string(1234), ""); +} + +void test_object_raw_type2string__convert_string_to_type(void) +{ + cl_assert(git_object_string2type(NULL) == GIT_OBJECT_INVALID); + cl_assert(git_object_string2type("") == GIT_OBJECT_INVALID); + cl_assert(git_object_string2type("commit") == GIT_OBJECT_COMMIT); + cl_assert(git_object_string2type("tree") == GIT_OBJECT_TREE); + cl_assert(git_object_string2type("blob") == GIT_OBJECT_BLOB); + cl_assert(git_object_string2type("tag") == GIT_OBJECT_TAG); + cl_assert(git_object_string2type("OFS_DELTA") == GIT_OBJECT_OFS_DELTA); + cl_assert(git_object_string2type("REF_DELTA") == GIT_OBJECT_REF_DELTA); + + cl_assert(git_object_string2type("CoMmIt") == GIT_OBJECT_INVALID); + cl_assert(git_object_string2type("hohoho") == GIT_OBJECT_INVALID); +} + +void test_object_raw_type2string__check_type_is_loose(void) +{ + cl_assert(git_object_typeisloose(GIT_OBJECT_INVALID) == 0); + cl_assert(git_object_typeisloose(0) == 0); /* EXT1 */ + cl_assert(git_object_typeisloose(GIT_OBJECT_COMMIT) == 1); + cl_assert(git_object_typeisloose(GIT_OBJECT_TREE) == 1); + cl_assert(git_object_typeisloose(GIT_OBJECT_BLOB) == 1); + cl_assert(git_object_typeisloose(GIT_OBJECT_TAG) == 1); + cl_assert(git_object_typeisloose(5) == 0); /* EXT2 */ + cl_assert(git_object_typeisloose(GIT_OBJECT_OFS_DELTA) == 0); + cl_assert(git_object_typeisloose(GIT_OBJECT_REF_DELTA) == 0); + + cl_assert(git_object_typeisloose(-2) == 0); + cl_assert(git_object_typeisloose(8) == 0); + cl_assert(git_object_typeisloose(1234) == 0); +} diff --git a/tests/libgit2/object/raw/write.c b/tests/libgit2/object/raw/write.c new file mode 100644 index 000000000..40e05f357 --- /dev/null +++ b/tests/libgit2/object/raw/write.c @@ -0,0 +1,462 @@ +#include "clar_libgit2.h" +#include "git2/odb_backend.h" + +#include "futils.h" +#include "odb.h" + +typedef struct object_data { + char *id; /* object id (sha1) */ + char *dir; /* object store (fan-out) directory name */ + char *file; /* object store filename */ +} object_data; + +static const char *odb_dir = "test-objects"; + +void test_body(object_data *d, git_rawobj *o); + + + +/* Helpers */ +static void remove_object_files(object_data *d) +{ + cl_git_pass(p_unlink(d->file)); + cl_git_pass(p_rmdir(d->dir)); + cl_assert(errno != ENOTEMPTY); + cl_git_pass(p_rmdir(odb_dir) < 0); +} + +static void streaming_write(git_oid *oid, git_odb *odb, git_rawobj *raw) +{ + git_odb_stream *stream; + int error; + + cl_git_pass(git_odb_open_wstream(&stream, odb, raw->len, raw->type)); + git_odb_stream_write(stream, raw->data, raw->len); + error = git_odb_stream_finalize_write(oid, stream); + git_odb_stream_free(stream); + cl_git_pass(error); +} + +static void check_object_files(object_data *d) +{ + cl_assert(git_fs_path_exists(d->dir)); + cl_assert(git_fs_path_exists(d->file)); +} + +static void cmp_objects(git_rawobj *o1, git_rawobj *o2) +{ + cl_assert(o1->type == o2->type); + cl_assert(o1->len == o2->len); + if (o1->len > 0) + cl_assert(memcmp(o1->data, o2->data, o1->len) == 0); +} + +static void make_odb_dir(void) +{ + cl_git_pass(p_mkdir(odb_dir, GIT_OBJECT_DIR_MODE)); +} + + +/* Standard test form */ +void test_body(object_data *d, git_rawobj *o) +{ + git_odb *db; + git_oid id1, id2; + git_odb_object *obj; + git_rawobj tmp; + + make_odb_dir(); + cl_git_pass(git_odb_open(&db, odb_dir)); + cl_git_pass(git_oid_fromstr(&id1, d->id)); + + streaming_write(&id2, db, o); + cl_assert(git_oid_cmp(&id1, &id2) == 0); + check_object_files(d); + + cl_git_pass(git_odb_read(&obj, db, &id1)); + + tmp.data = obj->buffer; + tmp.len = obj->cached.size; + tmp.type = obj->cached.type; + + cmp_objects(&tmp, o); + + git_odb_object_free(obj); + git_odb_free(db); + remove_object_files(d); +} + + +void test_object_raw_write__loose_object(void) +{ + object_data commit = { + "3d7f8a6af076c8c3f20071a8935cdbe8228594d1", + "test-objects/3d", + "test-objects/3d/7f8a6af076c8c3f20071a8935cdbe8228594d1", + }; + + unsigned char commit_data[] = { + 0x74, 0x72, 0x65, 0x65, 0x20, 0x64, 0x66, 0x66, + 0x32, 0x64, 0x61, 0x39, 0x30, 0x62, 0x32, 0x35, + 0x34, 0x65, 0x31, 0x62, 0x65, 0x62, 0x38, 0x38, + 0x39, 0x64, 0x31, 0x66, 0x31, 0x66, 0x31, 0x32, + 0x38, 0x38, 0x62, 0x65, 0x31, 0x38, 0x30, 0x33, + 0x37, 0x38, 0x32, 0x64, 0x66, 0x0a, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x20, 0x41, 0x20, 0x55, + 0x20, 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, + 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, + 0x31, 0x34, 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, + 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x74, 0x65, 0x72, 0x20, 0x43, 0x20, + 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, + 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, + 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, + 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, + 0x30, 0x0a, 0x0a, 0x41, 0x20, 0x6f, 0x6e, 0x65, + 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x73, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x0a, 0x0a, 0x54, 0x68, + 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, + 0x20, 0x66, 0x75, 0x72, 0x74, 0x68, 0x65, 0x72, + 0x20, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x70, 0x75, 0x72, 0x70, + 0x6f, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x62, 0x79, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x53, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x2d, 0x6f, 0x66, 0x2d, + 0x62, 0x79, 0x3a, 0x20, 0x41, 0x20, 0x55, 0x20, + 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + 0x3e, 0x0a, + }; + + git_rawobj commit_obj = { + commit_data, + sizeof(commit_data), + GIT_OBJECT_COMMIT + }; + + test_body(&commit, &commit_obj); +} + +void test_object_raw_write__loose_tree(void) +{ + static object_data tree = { + "dff2da90b254e1beb889d1f1f1288be1803782df", + "test-objects/df", + "test-objects/df/f2da90b254e1beb889d1f1f1288be1803782df", + }; + + static unsigned char tree_data[] = { + 0x31, 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x6f, + 0x6e, 0x65, 0x00, 0x8b, 0x13, 0x78, 0x91, 0x79, + 0x1f, 0xe9, 0x69, 0x27, 0xad, 0x78, 0xe6, 0x4b, + 0x0a, 0xad, 0x7b, 0xde, 0xd0, 0x8b, 0xdc, 0x31, + 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x73, 0x6f, + 0x6d, 0x65, 0x00, 0xfd, 0x84, 0x30, 0xbc, 0x86, + 0x4c, 0xfc, 0xd5, 0xf1, 0x0e, 0x55, 0x90, 0xf8, + 0xa4, 0x47, 0xe0, 0x1b, 0x94, 0x2b, 0xfe, 0x31, + 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x74, 0x77, + 0x6f, 0x00, 0x78, 0x98, 0x19, 0x22, 0x61, 0x3b, + 0x2a, 0xfb, 0x60, 0x25, 0x04, 0x2f, 0xf6, 0xbd, + 0x87, 0x8a, 0xc1, 0x99, 0x4e, 0x85, 0x31, 0x30, + 0x30, 0x36, 0x34, 0x34, 0x20, 0x7a, 0x65, 0x72, + 0x6f, 0x00, 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, + 0xd6, 0x43, 0x4b, 0x8b, 0x29, 0xae, 0x77, 0x5a, + 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91, + }; + + static git_rawobj tree_obj = { + tree_data, + sizeof(tree_data), + GIT_OBJECT_TREE + }; + + test_body(&tree, &tree_obj); +} + +void test_object_raw_write__loose_tag(void) +{ + static object_data tag = { + "09d373e1dfdc16b129ceec6dd649739911541e05", + "test-objects/09", + "test-objects/09/d373e1dfdc16b129ceec6dd649739911541e05", + }; + + static unsigned char tag_data[] = { + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x33, + 0x64, 0x37, 0x66, 0x38, 0x61, 0x36, 0x61, 0x66, + 0x30, 0x37, 0x36, 0x63, 0x38, 0x63, 0x33, 0x66, + 0x32, 0x30, 0x30, 0x37, 0x31, 0x61, 0x38, 0x39, + 0x33, 0x35, 0x63, 0x64, 0x62, 0x65, 0x38, 0x32, + 0x32, 0x38, 0x35, 0x39, 0x34, 0x64, 0x31, 0x0a, + 0x74, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x0a, 0x74, 0x61, 0x67, 0x20, + 0x76, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0a, 0x74, + 0x61, 0x67, 0x67, 0x65, 0x72, 0x20, 0x43, 0x20, + 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, + 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, + 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, + 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, + 0x30, 0x0a, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20, + 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, + 0x61, 0x67, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, + 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x76, 0x30, + 0x2e, 0x30, 0x2e, 0x31, 0x0a, + }; + + static git_rawobj tag_obj = { + tag_data, + sizeof(tag_data), + GIT_OBJECT_TAG + }; + + + test_body(&tag, &tag_obj); +} + +void test_object_raw_write__zero_length(void) +{ + static object_data zero = { + "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + "test-objects/e6", + "test-objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391", + }; + + static unsigned char zero_data[] = { + 0x00 /* dummy data */ + }; + + static git_rawobj zero_obj = { + zero_data, + 0, + GIT_OBJECT_BLOB + }; + + test_body(&zero, &zero_obj); +} + +void test_object_raw_write__one_byte(void) +{ + static object_data one = { + "8b137891791fe96927ad78e64b0aad7bded08bdc", + "test-objects/8b", + "test-objects/8b/137891791fe96927ad78e64b0aad7bded08bdc", + }; + + static unsigned char one_data[] = { + 0x0a, + }; + + static git_rawobj one_obj = { + one_data, + sizeof(one_data), + GIT_OBJECT_BLOB + }; + + test_body(&one, &one_obj); +} + +void test_object_raw_write__two_byte(void) +{ + static object_data two = { + "78981922613b2afb6025042ff6bd878ac1994e85", + "test-objects/78", + "test-objects/78/981922613b2afb6025042ff6bd878ac1994e85", + }; + + static unsigned char two_data[] = { + 0x61, 0x0a, + }; + + static git_rawobj two_obj = { + two_data, + sizeof(two_data), + GIT_OBJECT_BLOB + }; + + test_body(&two, &two_obj); +} + +void test_object_raw_write__several_bytes(void) +{ + static object_data some = { + "fd8430bc864cfcd5f10e5590f8a447e01b942bfe", + "test-objects/fd", + "test-objects/fd/8430bc864cfcd5f10e5590f8a447e01b942bfe", + }; + + static unsigned char some_data[] = { + 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, + 0x69, 0x73, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, + 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, + 0x3b, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, + 0x6e, 0x20, 0x72, 0x65, 0x64, 0x69, 0x73, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x69, + 0x74, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, + 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x0a, + 0x20, 0x2a, 0x20, 0x69, 0x74, 0x20, 0x75, 0x6e, + 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2c, + 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x20, 0x32, 0x2c, 0x0a, 0x20, 0x2a, 0x20, 0x61, + 0x73, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, + 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, + 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x2a, 0x0a, + 0x20, 0x2a, 0x20, 0x49, 0x6e, 0x20, 0x61, 0x64, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x47, 0x4e, 0x55, 0x20, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, 0x63, 0x65, + 0x6e, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x2a, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x73, 0x20, 0x67, 0x69, 0x76, 0x65, + 0x20, 0x79, 0x6f, 0x75, 0x20, 0x75, 0x6e, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x20, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x69, 0x6e, + 0x6b, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x0a, 0x20, + 0x2a, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, + 0x6e, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6d, 0x62, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x74, + 0x68, 0x65, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x67, + 0x72, 0x61, 0x6d, 0x73, 0x2c, 0x0a, 0x20, 0x2a, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20, + 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65, + 0x20, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x6e, + 0x79, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x2a, + 0x20, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x20, + 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x2e, 0x20, 0x20, 0x28, 0x54, 0x68, 0x65, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, + 0x20, 0x2a, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, + 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, + 0x64, 0x6f, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, + 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x74, 0x68, 0x65, + 0x72, 0x20, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, + 0x74, 0x73, 0x3b, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c, + 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x0a, 0x20, 0x2a, 0x20, 0x6d, + 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2c, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x73, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x6e, + 0x6f, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x65, + 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x0a, 0x20, + 0x2a, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x62, + 0x69, 0x6e, 0x65, 0x64, 0x20, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, + 0x29, 0x0a, 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, + 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, + 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x68, 0x6f, 0x70, 0x65, 0x20, 0x74, 0x68, 0x61, + 0x74, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c, + 0x6c, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, + 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x62, 0x75, 0x74, + 0x0a, 0x20, 0x2a, 0x20, 0x57, 0x49, 0x54, 0x48, + 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20, + 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, + 0x3b, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, + 0x74, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x69, + 0x65, 0x64, 0x20, 0x77, 0x61, 0x72, 0x72, 0x61, + 0x6e, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x0a, 0x20, + 0x2a, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, + 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, + 0x59, 0x20, 0x6f, 0x72, 0x20, 0x46, 0x49, 0x54, + 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, + 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 0x49, + 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, + 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2e, 0x20, 0x20, + 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x47, 0x4e, 0x55, 0x0a, 0x20, 0x2a, 0x20, 0x47, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, + 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x0a, + 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x59, 0x6f, + 0x75, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, + 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x20, 0x61, + 0x20, 0x63, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, + 0x20, 0x2a, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x61, 0x6d, 0x3b, 0x20, 0x73, 0x65, 0x65, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x20, 0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47, + 0x2e, 0x20, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, + 0x74, 0x2c, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, + 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x2a, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, + 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, + 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x35, 0x31, 0x20, + 0x46, 0x72, 0x61, 0x6e, 0x6b, 0x6c, 0x69, 0x6e, + 0x20, 0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x2c, + 0x20, 0x46, 0x69, 0x66, 0x74, 0x68, 0x20, 0x46, + 0x6c, 0x6f, 0x6f, 0x72, 0x2c, 0x0a, 0x20, 0x2a, + 0x20, 0x42, 0x6f, 0x73, 0x74, 0x6f, 0x6e, 0x2c, + 0x20, 0x4d, 0x41, 0x20, 0x30, 0x32, 0x31, 0x31, + 0x30, 0x2d, 0x31, 0x33, 0x30, 0x31, 0x2c, 0x20, + 0x55, 0x53, 0x41, 0x2e, 0x0a, 0x20, 0x2a, 0x2f, + 0x0a, + }; + + static git_rawobj some_obj = { + some_data, + sizeof(some_data), + GIT_OBJECT_BLOB + }; + + test_body(&some, &some_obj); +} diff --git a/tests/libgit2/object/shortid.c b/tests/libgit2/object/shortid.c new file mode 100644 index 000000000..9b673efeb --- /dev/null +++ b/tests/libgit2/object/shortid.c @@ -0,0 +1,51 @@ +#include "clar_libgit2.h" + +git_repository *_repo; + +void test_object_shortid__initialize(void) +{ + cl_git_pass(git_repository_open(&_repo, cl_fixture("duplicate.git"))); +} + +void test_object_shortid__cleanup(void) +{ + git_repository_free(_repo); + _repo = NULL; +} + +void test_object_shortid__select(void) +{ + git_oid full; + git_object *obj; + git_buf shorty = {0}; + + git_oid_fromstr(&full, "ce013625030ba8dba906f756967f9e9ca394464a"); + cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJECT_ANY)); + cl_git_pass(git_object_short_id(&shorty, obj)); + cl_assert_equal_i(7, shorty.size); + cl_assert_equal_s("ce01362", shorty.ptr); + git_object_free(obj); + + git_oid_fromstr(&full, "038d718da6a1ebbc6a7780a96ed75a70cc2ad6e2"); + cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJECT_ANY)); + cl_git_pass(git_object_short_id(&shorty, obj)); + cl_assert_equal_i(7, shorty.size); + cl_assert_equal_s("038d718", shorty.ptr); + git_object_free(obj); + + git_oid_fromstr(&full, "dea509d097ce692e167dfc6a48a7a280cc5e877e"); + cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJECT_ANY)); + cl_git_pass(git_object_short_id(&shorty, obj)); + cl_assert_equal_i(9, shorty.size); + cl_assert_equal_s("dea509d09", shorty.ptr); + git_object_free(obj); + + git_oid_fromstr(&full, "dea509d0b3cb8ee0650f6ca210bc83f4678851ba"); + cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJECT_ANY)); + cl_git_pass(git_object_short_id(&shorty, obj)); + cl_assert_equal_i(9, shorty.size); + cl_assert_equal_s("dea509d0b", shorty.ptr); + git_object_free(obj); + + git_buf_dispose(&shorty); +} diff --git a/tests/libgit2/object/tag/list.c b/tests/libgit2/object/tag/list.c new file mode 100644 index 000000000..d15f09205 --- /dev/null +++ b/tests/libgit2/object/tag/list.c @@ -0,0 +1,117 @@ +#include "clar_libgit2.h" + +#include "tag.h" + +static git_repository *g_repo; + +#define MAX_USED_TAGS 6 + +struct pattern_match_t +{ + const char* pattern; + const size_t expected_matches; + const char* expected_results[MAX_USED_TAGS]; +}; + +/* Helpers */ +static void ensure_tag_pattern_match(git_repository *repo, + const struct pattern_match_t* data) +{ + int already_found[MAX_USED_TAGS] = { 0 }; + git_strarray tag_list; + int error = 0; + size_t successfully_found = 0; + size_t i, j; + + cl_assert(data->expected_matches <= MAX_USED_TAGS); + + if ((error = git_tag_list_match(&tag_list, data->pattern, repo)) < 0) + goto exit; + + if (tag_list.count != data->expected_matches) + { + error = GIT_ERROR; + goto exit; + } + + /* we have to be prepared that tags come in any order. */ + for (i = 0; i < tag_list.count; i++) + { + for (j = 0; j < data->expected_matches; j++) + { + if (!already_found[j] && !strcmp(data->expected_results[j], tag_list.strings[i])) + { + already_found[j] = 1; + successfully_found++; + break; + } + } + } + cl_assert_equal_i((int)successfully_found, (int)data->expected_matches); + +exit: + git_strarray_dispose(&tag_list); + cl_git_pass(error); +} + +/* Fixture setup and teardown */ +void test_object_tag_list__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tag_list__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_object_tag_list__list_all(void) +{ + /* list all tag names from the repository */ + git_strarray tag_list; + + cl_git_pass(git_tag_list(&tag_list, g_repo)); + + cl_assert_equal_i((int)tag_list.count, 6); + + git_strarray_dispose(&tag_list); +} + +static const struct pattern_match_t matches[] = { + /* All tags, including a packed one and two namespaced ones. */ + { "", 6, { "e90810b", "point_to_blob", "test", "packed-tag", "foo/bar", "foo/foo/bar" } }, + + /* beginning with */ + { "t*", 1, { "test" } }, + + /* ending with */ + { "*b", 2, { "e90810b", "point_to_blob" } }, + + /* exact match */ + { "e", 0 }, + { "e90810b", 1, { "e90810b" } }, + + /* either or */ + { "e90810[ab]", 1, { "e90810b" } }, + + /* glob in the middle */ + { "foo/*/bar", 1, { "foo/foo/bar" } }, + + /* + * The matching of '*' is based on plain string matching analog to the regular expression ".*" + * => a '/' in the tag name has no special meaning. + * Compare to `git tag -l "*bar"` + */ + { "*bar", 2, { "foo/bar", "foo/foo/bar" } }, + + /* End of list */ + { NULL } +}; + +void test_object_tag_list__list_by_pattern(void) +{ + /* list all tag names from the repository matching a specified pattern */ + size_t i = 0; + while (matches[i].pattern) + ensure_tag_pattern_match(g_repo, &matches[i++]); +} diff --git a/tests/libgit2/object/tag/parse.c b/tests/libgit2/object/tag/parse.c new file mode 100644 index 000000000..2c0635ae4 --- /dev/null +++ b/tests/libgit2/object/tag/parse.c @@ -0,0 +1,218 @@ +#include "clar_libgit2.h" +#include "object.h" +#include "signature.h" +#include "tag.h" + +static void assert_tag_parses(const char *data, size_t datalen, + const char *expected_oid, + const char *expected_name, + const char *expected_tagger, + const char *expected_message) +{ + git_tag *tag; + + if (!datalen) + datalen = strlen(data); + + cl_git_pass(git_object__from_raw((git_object **) &tag, data, datalen, GIT_OBJECT_TAG)); + cl_assert_equal_i(tag->type, GIT_OBJECT_TAG); + + if (expected_oid) { + git_oid oid; + cl_git_pass(git_oid_fromstr(&oid, expected_oid)); + cl_assert_equal_oid(&oid, &tag->target); + } + + if (expected_name) + cl_assert_equal_s(expected_name, tag->tag_name); + else + cl_assert_equal_s(tag->message, NULL); + + if (expected_tagger) { + git_signature *tagger; + cl_git_pass(git_signature_from_buffer(&tagger, expected_tagger)); + cl_assert_equal_s(tagger->name, tag->tagger->name); + cl_assert_equal_s(tagger->email, tag->tagger->email); + cl_assert_equal_i(tagger->when.time, tag->tagger->when.time); + cl_assert_equal_i(tagger->when.offset, tag->tagger->when.offset); + cl_assert_equal_i(tagger->when.sign, tag->tagger->when.sign); + git_signature_free(tagger); + } else { + cl_assert_equal_s(tag->tagger, NULL); + } + + if (expected_message) + cl_assert_equal_s(expected_message, tag->message); + else + cl_assert_equal_s(tag->message, NULL); + + git_object__free(&tag->object); +} + +static void assert_tag_fails(const char *data, size_t datalen) +{ + git_object *object; + if (!datalen) + datalen = strlen(data); + cl_git_fail(git_object__from_raw(&object, data, datalen, GIT_OBJECT_TAG)); +} + +void test_object_tag_parse__valid_tag_parses(void) +{ + const char *tag = + "object a8d447f68076d1520f69649bb52629941be7031f\n" + "type tag\n" + "tag tagname\n" + "tagger Taggy Mr. Taggart \n" + "\n" + "Message"; + assert_tag_parses(tag, 0, + "a8d447f68076d1520f69649bb52629941be7031f", + "tagname", + "Taggy Mr. Taggart ", + "Message"); +} + +void test_object_tag_parse__missing_tagger_parses(void) +{ + const char *tag = + "object a8d447f68076d1520f69649bb52629941be7031f\n" + "type tag\n" + "tag tagname\n" + "\n" + "Message"; + assert_tag_parses(tag, 0, + "a8d447f68076d1520f69649bb52629941be7031f", + "tagname", + NULL, + "Message"); +} + +void test_object_tag_parse__missing_message_parses(void) +{ + const char *tag = + "object a8d447f68076d1520f69649bb52629941be7031f\n" + "type tag\n" + "tag tagname\n" + "tagger Taggy Mr. Taggart \n"; + assert_tag_parses(tag, 0, + "a8d447f68076d1520f69649bb52629941be7031f", + "tagname", + "Taggy Mr. Taggart ", + NULL); +} + +void test_object_tag_parse__unknown_field_parses(void) +{ + const char *tag = + "object a8d447f68076d1520f69649bb52629941be7031f\n" + "type tag\n" + "tag tagname\n" + "tagger Taggy Mr. Taggart \n" + "foo bar\n" + "frubble frabble\n" + "\n" + "Message"; + assert_tag_parses(tag, 0, + "a8d447f68076d1520f69649bb52629941be7031f", + "tagname", + "Taggy Mr. Taggart ", + "Message"); +} + +void test_object_tag_parse__missing_object_fails(void) +{ + const char *tag = + "type tag\n" + "tag tagname\n" + "tagger Taggy Mr. Taggart \n" + "\n" + "Message"; + assert_tag_fails(tag, 0); +} + +void test_object_tag_parse__malformatted_object_fails(void) +{ + const char *tag = + "object a8d447f68076d15xxxxxxxxxxxxxxxx41be7031f\n" + "type tag\n" + "tag tagname\n" + "tagger Taggy Mr. Taggart \n" + "\n" + "Message"; + assert_tag_fails(tag, 0); +} + +void test_object_tag_parse__missing_type_fails(void) +{ + const char *tag = + "object a8d447f68076d1520f69649bb52629941be7031f\n" + "tag tagname\n" + "tagger Taggy Mr. Taggart \n" + "\n" + "Message"; + assert_tag_fails(tag, 0); +} + +void test_object_tag_parse__invalid_type_fails(void) +{ + const char *tag = + "object a8d447f68076d1520f69649bb52629941be7031f\n" + "type garbage\n" + "tag tagname\n" + "tagger Taggy Mr. Taggart \n" + "\n" + "Message"; + assert_tag_fails(tag, 0); +} + +void test_object_tag_parse__missing_tagname_fails(void) +{ + const char *tag = + "object a8d447f68076d1520f69649bb52629941be7031f\n" + "type tag\n" + "tagger Taggy Mr. Taggart \n" + "\n" + "Message"; + assert_tag_fails(tag, 0); +} + +void test_object_tag_parse__misformatted_tagger_fails(void) +{ + const char *tag = + "object a8d447f68076d1520f69649bb52629941be7031f\n" + "type tag\n" + "tag Tag\n" + "tagger taggy@taggart.com>\n" + "\n" + "Message"; + assert_tag_fails(tag, 0); +} + +void test_object_tag_parse__missing_message_fails(void) +{ + const char *tag = + "object a8d447f68076d1520f69649bb52629941be7031f\n" + "type tag\n" + "tag Tag\n" + "tagger taggy@taggart.com>\n"; + assert_tag_fails(tag, 0); +} + +void test_object_tag_parse__no_oob_read_when_searching_message(void) +{ + const char *tag = + "object a8d447f68076d1520f69649bb52629941be7031f\n" + "type tag\n" + "tag \n" + "tagger <>\n" + " \n\n" + "Message"; + /* + * The OOB read previously resulted in an OOM error. We + * thus want to make sure that the resulting error is the + * expected one. + */ + assert_tag_fails(tag, strlen(tag) - strlen("\n\nMessage")); + cl_assert(strstr(git_error_last()->message, "tag contains no message")); +} diff --git a/tests/libgit2/object/tag/peel.c b/tests/libgit2/object/tag/peel.c new file mode 100644 index 000000000..7456a8e17 --- /dev/null +++ b/tests/libgit2/object/tag/peel.c @@ -0,0 +1,61 @@ +#include "clar_libgit2.h" +#include "tag.h" + +static git_repository *repo; +static git_tag *tag; +static git_object *target; + +void test_object_tag_peel__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&repo, "testrepo.git")); +} + +void test_object_tag_peel__cleanup(void) +{ + git_tag_free(tag); + tag = NULL; + + git_object_free(target); + target = NULL; + + git_repository_free(repo); + repo = NULL; + + cl_fixture_cleanup("testrepo.git"); +} + +static void retrieve_tag_from_oid(git_tag **tag_out, git_repository *repo, const char *sha) +{ + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, sha)); + cl_git_pass(git_tag_lookup(tag_out, repo, &oid)); +} + +void test_object_tag_peel__can_peel_to_a_commit(void) +{ + retrieve_tag_from_oid(&tag, repo, "7b4384978d2493e851f9cca7858815fac9b10980"); + + cl_git_pass(git_tag_peel(&target, tag)); + cl_assert(git_object_type(target) == GIT_OBJECT_COMMIT); + cl_git_pass(git_oid_streq(git_object_id(target), "e90810b8df3e80c413d903f631643c716887138d")); +} + +void test_object_tag_peel__can_peel_several_nested_tags_to_a_commit(void) +{ + retrieve_tag_from_oid(&tag, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); + + cl_git_pass(git_tag_peel(&target, tag)); + cl_assert(git_object_type(target) == GIT_OBJECT_COMMIT); + cl_git_pass(git_oid_streq(git_object_id(target), "e90810b8df3e80c413d903f631643c716887138d")); +} + +void test_object_tag_peel__can_peel_to_a_non_commit(void) +{ + retrieve_tag_from_oid(&tag, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91"); + + cl_git_pass(git_tag_peel(&target, tag)); + cl_assert(git_object_type(target) == GIT_OBJECT_BLOB); + cl_git_pass(git_oid_streq(git_object_id(target), "1385f264afb75a56a5bec74243be9b367ba4ca08")); +} diff --git a/tests/libgit2/object/tag/read.c b/tests/libgit2/object/tag/read.c new file mode 100644 index 000000000..90ba58029 --- /dev/null +++ b/tests/libgit2/object/tag/read.c @@ -0,0 +1,179 @@ +#include "clar_libgit2.h" + +#include "tag.h" + +static const char *tag1_id = "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"; +static const char *tag2_id = "7b4384978d2493e851f9cca7858815fac9b10980"; +static const char *tagged_commit = "e90810b8df3e80c413d903f631643c716887138d"; +static const char *bad_tag_id = "eda9f45a2a98d4c17a09d681d88569fa4ea91755"; +static const char *badly_tagged_commit = "e90810b8df3e80c413d903f631643c716887138d"; +static const char *short_tag_id = "5da7760512a953e3c7c4e47e4392c7a4338fb729"; +static const char *short_tagged_commit = "4a5ed60bafcf4638b7c8356bd4ce1916bfede93c"; +static const char *taggerless = "4a23e2e65ad4e31c4c9db7dc746650bfad082679"; + +static git_repository *g_repo; + +/* Fixture setup and teardown */ +void test_object_tag_read__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tag_read__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + +void test_object_tag_read__parse(void) +{ + /* read and parse a tag from the repository */ + git_tag *tag1, *tag2; + git_commit *commit; + git_oid id1, id2, id_commit; + + git_oid_fromstr(&id1, tag1_id); + git_oid_fromstr(&id2, tag2_id); + git_oid_fromstr(&id_commit, tagged_commit); + + cl_git_pass(git_tag_lookup(&tag1, g_repo, &id1)); + + cl_assert_equal_s(git_tag_name(tag1), "test"); + cl_assert(git_tag_target_type(tag1) == GIT_OBJECT_TAG); + + cl_git_pass(git_tag_target((git_object **)&tag2, tag1)); + cl_assert(tag2 != NULL); + + cl_assert(git_oid_cmp(&id2, git_tag_id(tag2)) == 0); + + cl_git_pass(git_tag_target((git_object **)&commit, tag2)); + cl_assert(commit != NULL); + + cl_assert(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0); + + git_tag_free(tag1); + git_tag_free(tag2); + git_commit_free(commit); +} + +void test_object_tag_read__parse_without_tagger(void) +{ + /* read and parse a tag without a tagger field */ + git_repository *bad_tag_repo; + git_tag *bad_tag; + git_commit *commit; + git_oid id, id_commit; + + /* TODO: This is a little messy */ + cl_git_pass(git_repository_open(&bad_tag_repo, cl_fixture("bad_tag.git"))); + + git_oid_fromstr(&id, bad_tag_id); + git_oid_fromstr(&id_commit, badly_tagged_commit); + + cl_git_pass(git_tag_lookup(&bad_tag, bad_tag_repo, &id)); + cl_assert(bad_tag != NULL); + + cl_assert_equal_s(git_tag_name(bad_tag), "e90810b"); + cl_assert(git_oid_cmp(&id, git_tag_id(bad_tag)) == 0); + cl_assert(bad_tag->tagger == NULL); + + cl_git_pass(git_tag_target((git_object **)&commit, bad_tag)); + cl_assert(commit != NULL); + + cl_assert(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0); + + + git_tag_free(bad_tag); + git_commit_free(commit); + git_repository_free(bad_tag_repo); +} + +void test_object_tag_read__parse_without_message(void) +{ + /* read and parse a tag without a message field */ + git_repository *short_tag_repo; + git_tag *short_tag; + git_commit *commit; + git_oid id, id_commit; + + /* TODO: This is a little messy */ + cl_git_pass(git_repository_open(&short_tag_repo, cl_fixture("short_tag.git"))); + + git_oid_fromstr(&id, short_tag_id); + git_oid_fromstr(&id_commit, short_tagged_commit); + + cl_git_pass(git_tag_lookup(&short_tag, short_tag_repo, &id)); + cl_assert(short_tag != NULL); + + cl_assert_equal_s(git_tag_name(short_tag), "no_description"); + cl_assert(git_oid_cmp(&id, git_tag_id(short_tag)) == 0); + cl_assert(short_tag->message == NULL); + + cl_git_pass(git_tag_target((git_object **)&commit, short_tag)); + cl_assert(commit != NULL); + + cl_assert(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0); + + git_tag_free(short_tag); + git_commit_free(commit); + git_repository_free(short_tag_repo); +} + +void test_object_tag_read__without_tagger_nor_message(void) +{ + git_tag *tag; + git_oid id; + git_repository *repo; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + cl_git_pass(git_oid_fromstr(&id, taggerless)); + + cl_git_pass(git_tag_lookup(&tag, repo, &id)); + + cl_assert_equal_s(git_tag_name(tag), "taggerless"); + cl_assert(git_tag_target_type(tag) == GIT_OBJECT_COMMIT); + + cl_assert(tag->message == NULL); + cl_assert(tag->tagger == NULL); + + git_tag_free(tag); + git_repository_free(repo); +} + +static const char *silly_tag = "object c054ccaefbf2da31c3b19178f9e3ef20a3867924\n\ +type commit\n\ +tag v1_0_1\n\ +tagger Jamis Buck 1107717917\n\ +diff --git a/lib/sqlite3/version.rb b/lib/sqlite3/version.rb\n\ +index 0b3bf69..4ee8fc2 100644\n\ +--- a/lib/sqlite3/version.rb\n\ ++++ b/lib/sqlite3/version.rb\n\ +@@ -36,7 +36,7 @@ module SQLite3\n\ + \n\ + MAJOR = 1\n\ + MINOR = 0\n\ +- TINY = 0\n\ ++ TINY = 1\n\ + \n\ + STRING = [ MAJOR, MINOR, TINY ].join( \".\" )\n\ + \n\ + -0600\n\ +\n\ +v1_0_1 release\n"; + +void test_object_tag_read__extra_header_fields(void) +{ + git_tag *tag; + git_odb *odb; + git_oid id; + + cl_git_pass(git_repository_odb__weakptr(&odb, g_repo)); + + cl_git_pass(git_odb_write(&id, odb, silly_tag, strlen(silly_tag), GIT_OBJECT_TAG)); + cl_git_pass(git_tag_lookup(&tag, g_repo, &id)); + + cl_assert_equal_s("v1_0_1 release\n", git_tag_message(tag)); + + git_tag_free(tag); +} diff --git a/tests/libgit2/object/tag/write.c b/tests/libgit2/object/tag/write.c new file mode 100644 index 000000000..3c1a98956 --- /dev/null +++ b/tests/libgit2/object/tag/write.c @@ -0,0 +1,260 @@ +#include "clar_libgit2.h" + +static const char* tagger_name = "Vicent Marti"; +static const char* tagger_email = "vicent@github.com"; +static const char* tagger_message = "This is my tag.\n\nThere are many tags, but this one is mine\n"; + +static const char *tag2_id = "7b4384978d2493e851f9cca7858815fac9b10980"; +static const char *tagged_commit = "e90810b8df3e80c413d903f631643c716887138d"; + +static git_repository *g_repo; + +/* Fixture setup and teardown */ +void test_object_tag_write__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tag_write__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_object_tag_write__basic(void) +{ + /* write a tag to the repository and read it again */ + git_tag *tag; + git_oid target_id, tag_id; + git_signature *tagger; + const git_signature *tagger1; + git_reference *ref_tag; + git_object *target; + + git_oid_fromstr(&target_id, tagged_commit); + cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); + + /* create signature */ + cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); + + cl_git_pass( + git_tag_create(&tag_id, g_repo, + "the-tag", target, tagger, tagger_message, 0) + ); + + git_object_free(target); + git_signature_free(tagger); + + cl_git_pass(git_tag_lookup(&tag, g_repo, &tag_id)); + cl_assert(git_oid_cmp(git_tag_target_id(tag), &target_id) == 0); + + /* Check attributes were set correctly */ + tagger1 = git_tag_tagger(tag); + cl_assert(tagger1 != NULL); + cl_assert_equal_s(tagger1->name, tagger_name); + cl_assert_equal_s(tagger1->email, tagger_email); + cl_assert(tagger1->when.time == 123456789); + cl_assert(tagger1->when.offset == 60); + + cl_assert_equal_s(git_tag_message(tag), tagger_message); + + cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/the-tag")); + cl_assert(git_oid_cmp(git_reference_target(ref_tag), &tag_id) == 0); + cl_git_pass(git_reference_delete(ref_tag)); + git_reference_free(ref_tag); + + git_tag_free(tag); +} + +void test_object_tag_write__overwrite(void) +{ + /* Attempt to write a tag bearing the same name than an already existing tag */ + git_oid target_id, tag_id; + git_signature *tagger; + git_object *target; + + git_oid_fromstr(&target_id, tagged_commit); + cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); + + /* create signature */ + cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); + + cl_assert_equal_i(GIT_EEXISTS, git_tag_create( + &tag_id, /* out id */ + g_repo, + "e90810b", + target, + tagger, + tagger_message, + 0)); + + git_object_free(target); + git_signature_free(tagger); +} + +void test_object_tag_write__replace(void) +{ + /* Replace an already existing tag */ + git_oid target_id, tag_id, old_tag_id; + git_signature *tagger; + git_reference *ref_tag; + git_object *target; + + git_oid_fromstr(&target_id, tagged_commit); + cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); + + cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/e90810b")); + git_oid_cpy(&old_tag_id, git_reference_target(ref_tag)); + git_reference_free(ref_tag); + + /* create signature */ + cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); + + cl_git_pass(git_tag_create( + &tag_id, /* out id */ + g_repo, + "e90810b", + target, + tagger, + tagger_message, + 1)); + + git_object_free(target); + git_signature_free(tagger); + + cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/e90810b")); + cl_assert(git_oid_cmp(git_reference_target(ref_tag), &tag_id) == 0); + cl_assert(git_oid_cmp(git_reference_target(ref_tag), &old_tag_id) != 0); + + git_reference_free(ref_tag); +} + +void test_object_tag_write__lightweight(void) +{ + /* write a lightweight tag to the repository and read it again */ + git_oid target_id, object_id; + git_reference *ref_tag; + git_object *target; + + git_oid_fromstr(&target_id, tagged_commit); + cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); + + cl_git_pass(git_tag_create_lightweight( + &object_id, + g_repo, + "light-tag", + target, + 0)); + + git_object_free(target); + + cl_assert(git_oid_cmp(&object_id, &target_id) == 0); + + cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/light-tag")); + cl_assert(git_oid_cmp(git_reference_target(ref_tag), &target_id) == 0); + + cl_git_pass(git_tag_delete(g_repo, "light-tag")); + + git_reference_free(ref_tag); +} + +void test_object_tag_write__lightweight_over_existing(void) +{ + /* Attempt to write a lightweight tag bearing the same name than an already existing tag */ + git_oid target_id, object_id, existing_object_id; + git_object *target; + + git_oid_fromstr(&target_id, tagged_commit); + cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); + + cl_assert_equal_i(GIT_EEXISTS, git_tag_create_lightweight( + &object_id, + g_repo, + "e90810b", + target, + 0)); + + git_oid_fromstr(&existing_object_id, tag2_id); + cl_assert(git_oid_cmp(&object_id, &existing_object_id) == 0); + + git_object_free(target); +} + +void test_object_tag_write__delete(void) +{ + /* Delete an already existing tag */ + git_reference *ref_tag; + + cl_git_pass(git_tag_delete(g_repo, "e90810b")); + + cl_git_fail(git_reference_lookup(&ref_tag, g_repo, "refs/tags/e90810b")); + + git_reference_free(ref_tag); +} + +void test_object_tag_write__creating_with_an_invalid_name_returns_EINVALIDSPEC(void) +{ + git_oid target_id, tag_id; + git_signature *tagger; + git_object *target; + + git_oid_fromstr(&target_id, tagged_commit); + cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); + + cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); + + cl_assert_equal_i(GIT_EINVALIDSPEC, + git_tag_create(&tag_id, g_repo, + "Inv@{id", target, tagger, tagger_message, 0) + ); + + cl_assert_equal_i(GIT_EINVALIDSPEC, + git_tag_create_lightweight(&tag_id, g_repo, + "Inv@{id", target, 0) + ); + + git_object_free(target); + git_signature_free(tagger); +} + +void test_object_tag_write__deleting_with_an_invalid_name_returns_EINVALIDSPEC(void) +{ + cl_assert_equal_i(GIT_EINVALIDSPEC, git_tag_delete(g_repo, "Inv@{id")); +} + +static void create_annotation(git_oid *tag_id, const char *name) +{ + git_object *target; + git_oid target_id; + git_signature *tagger; + + cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); + + git_oid_fromstr(&target_id, tagged_commit); + cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); + + cl_git_pass(git_tag_annotation_create(tag_id, g_repo, name, target, tagger, "boom!")); + git_object_free(target); + git_signature_free(tagger); +} + +void test_object_tag_write__creating_an_annotation_stores_the_new_object_in_the_odb(void) +{ + git_oid tag_id; + git_tag *tag; + + create_annotation(&tag_id, "new_tag"); + + cl_git_pass(git_tag_lookup(&tag, g_repo, &tag_id)); + cl_assert_equal_s("new_tag", git_tag_name(tag)); + + git_tag_free(tag); +} + +void test_object_tag_write__creating_an_annotation_does_not_create_a_reference(void) +{ + git_oid tag_id; + git_reference *tag_ref; + + create_annotation(&tag_id, "new_tag"); + cl_git_fail_with(git_reference_lookup(&tag_ref, g_repo, "refs/tags/new_tag"), GIT_ENOTFOUND); +} diff --git a/tests/libgit2/object/tree/attributes.c b/tests/libgit2/object/tree/attributes.c new file mode 100644 index 000000000..8654dfa31 --- /dev/null +++ b/tests/libgit2/object/tree/attributes.c @@ -0,0 +1,118 @@ +#include "clar_libgit2.h" +#include "tree.h" + +static git_repository *repo; + +static const char *blob_oid = "3d0970ec547fc41ef8a5882dde99c6adce65b021"; +static const char *tree_oid = "1b05fdaa881ee45b48cbaa5e9b037d667a47745e"; + +void test_object_tree_attributes__initialize(void) +{ + repo = cl_git_sandbox_init("deprecated-mode.git"); +} + +void test_object_tree_attributes__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_object_tree_attributes__ensure_correctness_of_attributes_on_insertion(void) +{ + git_treebuilder *builder; + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, blob_oid)); + + cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); + + cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0777777)); + cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0100666)); + cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0000001)); + + git_treebuilder_free(builder); +} + +void test_object_tree_attributes__group_writable_tree_entries_created_with_an_antique_git_version_can_still_be_accessed(void) +{ + git_oid tid; + git_tree *tree; + const git_tree_entry *entry; + + + cl_git_pass(git_oid_fromstr(&tid, tree_oid)); + cl_git_pass(git_tree_lookup(&tree, repo, &tid)); + + entry = git_tree_entry_byname(tree, "old_mode.txt"); + cl_assert_equal_i( + GIT_FILEMODE_BLOB, + git_tree_entry_filemode(entry)); + + git_tree_free(tree); +} + +void test_object_tree_attributes__treebuilder_reject_invalid_filemode(void) +{ + git_treebuilder *builder; + git_oid bid; + const git_tree_entry *entry; + + cl_git_pass(git_oid_fromstr(&bid, blob_oid)); + cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); + + cl_git_fail(git_treebuilder_insert( + &entry, + builder, + "normalized.txt", + &bid, + GIT_FILEMODE_BLOB_GROUP_WRITABLE)); + + git_treebuilder_free(builder); +} + +void test_object_tree_attributes__normalize_attributes_when_creating_a_tree_from_an_existing_one(void) +{ + git_treebuilder *builder; + git_oid tid, tid2; + git_tree *tree; + const git_tree_entry *entry; + + cl_git_pass(git_oid_fromstr(&tid, tree_oid)); + cl_git_pass(git_tree_lookup(&tree, repo, &tid)); + + cl_git_pass(git_treebuilder_new(&builder, repo, tree)); + + entry = git_treebuilder_get(builder, "old_mode.txt"); + cl_assert(entry != NULL); + cl_assert_equal_i( + GIT_FILEMODE_BLOB, + git_tree_entry_filemode(entry)); + + cl_git_pass(git_treebuilder_write(&tid2, builder)); + git_treebuilder_free(builder); + git_tree_free(tree); + + cl_git_pass(git_tree_lookup(&tree, repo, &tid2)); + entry = git_tree_entry_byname(tree, "old_mode.txt"); + cl_assert(entry != NULL); + cl_assert_equal_i( + GIT_FILEMODE_BLOB, + git_tree_entry_filemode(entry)); + + git_tree_free(tree); +} + +void test_object_tree_attributes__normalize_600(void) +{ + git_oid id; + git_tree *tree; + const git_tree_entry *entry; + + git_oid_fromstr(&id, "0810fb7818088ff5ac41ee49199b51473b1bd6c7"); + cl_git_pass(git_tree_lookup(&tree, repo, &id)); + + entry = git_tree_entry_byname(tree, "ListaTeste.xml"); + cl_assert_equal_i(git_tree_entry_filemode(entry), GIT_FILEMODE_BLOB); + cl_assert_equal_i(git_tree_entry_filemode_raw(entry), 0100600); + + git_tree_free(tree); +} diff --git a/tests/libgit2/object/tree/duplicateentries.c b/tests/libgit2/object/tree/duplicateentries.c new file mode 100644 index 000000000..c11ae0d3d --- /dev/null +++ b/tests/libgit2/object/tree/duplicateentries.c @@ -0,0 +1,157 @@ +#include "clar_libgit2.h" +#include "tree.h" + +static git_repository *_repo; + +void test_object_tree_duplicateentries__initialize(void) { + _repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tree_duplicateentries__cleanup(void) { + cl_git_sandbox_cleanup(); +} + +/* + * $ git show --format=raw refs/heads/dir + * commit 144344043ba4d4a405da03de3844aa829ae8be0e + * tree d52a8fe84ceedf260afe4f0287bbfca04a117e83 + * parent cf80f8de9f1185bf3a05f993f6121880dd0cfbc9 + * author Ben Straub 1343755506 -0700 + * committer Ben Straub 1343755506 -0700 + * + * Change a file mode + * + * diff --git a/a/b.txt b/a/b.txt + * old mode 100644 + * new mode 100755 + * + * $ git ls-tree d52a8fe84ceedf260afe4f0287bbfca04a117e83 + * 100644 blob a8233120f6ad708f843d861ce2b7228ec4e3dec6 README + * 040000 tree 4e0883eeeeebc1fb1735161cea82f7cb5fab7e63 a + * 100644 blob 45b983be36b73c0788dc9cbcb76cbb80fc7bb057 branch_file.txt + * 100644 blob a71586c1dfe8a71c6cbf6c129f404c5642ff31bd new.txt + */ + +static void tree_checker( + git_oid *tid, + const char *expected_sha, + git_filemode_t expected_filemode) +{ + git_tree *tree; + const git_tree_entry *entry; + git_oid oid; + + cl_git_pass(git_tree_lookup(&tree, _repo, tid)); + cl_assert_equal_i(1, (int)git_tree_entrycount(tree)); + entry = git_tree_entry_byindex(tree, 0); + + cl_git_pass(git_oid_fromstr(&oid, expected_sha)); + + cl_assert_equal_i(0, git_oid_cmp(&oid, git_tree_entry_id(entry))); + cl_assert_equal_i(expected_filemode, git_tree_entry_filemode(entry)); + + git_tree_free(tree); +} + +static void tree_creator(git_oid *out, void (*fn)(git_treebuilder *)) +{ + git_treebuilder *builder; + + cl_git_pass(git_treebuilder_new(&builder, _repo, NULL)); + + fn(builder); + + cl_git_pass(git_treebuilder_write(out, builder)); + git_treebuilder_free(builder); +} + +static void two_blobs(git_treebuilder *bld) +{ + git_oid oid; + const git_tree_entry *entry; + + cl_git_pass(git_oid_fromstr(&oid, + "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); /* blob oid (README) */ + + cl_git_pass(git_treebuilder_insert( + &entry, bld, "duplicate", &oid, + GIT_FILEMODE_BLOB)); + + cl_git_pass(git_oid_fromstr(&oid, + "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); /* blob oid (new.txt) */ + + cl_git_pass(git_treebuilder_insert( + &entry, bld, "duplicate", &oid, + GIT_FILEMODE_BLOB)); +} + +static void one_blob_and_one_tree(git_treebuilder *bld) +{ + git_oid oid; + const git_tree_entry *entry; + + cl_git_pass(git_oid_fromstr(&oid, + "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); /* blob oid (README) */ + + cl_git_pass(git_treebuilder_insert( + &entry, bld, "duplicate", &oid, + GIT_FILEMODE_BLOB)); + + cl_git_pass(git_oid_fromstr(&oid, + "4e0883eeeeebc1fb1735161cea82f7cb5fab7e63")); /* tree oid (a) */ + + cl_git_pass(git_treebuilder_insert( + &entry, bld, "duplicate", &oid, + GIT_FILEMODE_TREE)); +} + +void test_object_tree_duplicateentries__cannot_create_a_duplicate_entry_through_the_treebuilder(void) +{ + git_oid tid; + + tree_creator(&tid, two_blobs); + tree_checker(&tid, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd", GIT_FILEMODE_BLOB); + + tree_creator(&tid, one_blob_and_one_tree); + tree_checker(&tid, "4e0883eeeeebc1fb1735161cea82f7cb5fab7e63", GIT_FILEMODE_TREE); +} + +static void add_fake_conflicts(git_index *index) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = "duplicate"; + ancestor_entry.mode = GIT_FILEMODE_BLOB; + GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 1); + git_oid_fromstr(&ancestor_entry.id, "a8233120f6ad708f843d861ce2b7228ec4e3dec6"); + + our_entry.path = "duplicate"; + our_entry.mode = GIT_FILEMODE_BLOB; + GIT_INDEX_ENTRY_STAGE_SET(&our_entry, 2); + git_oid_fromstr(&our_entry.id, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057"); + + their_entry.path = "duplicate"; + their_entry.mode = GIT_FILEMODE_BLOB; + GIT_INDEX_ENTRY_STAGE_SET(&their_entry, 3); + git_oid_fromstr(&their_entry.id, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"); + + cl_git_pass(git_index_conflict_add(index, &ancestor_entry, &our_entry, &their_entry)); +} + +void test_object_tree_duplicateentries__cannot_create_a_duplicate_entry_building_a_tree_from_a_index_with_conflicts(void) +{ + git_index *index; + git_oid tid; + + cl_git_pass(git_repository_index(&index, _repo)); + + add_fake_conflicts(index); + + cl_assert_equal_i(GIT_EUNMERGED, git_index_write_tree(&tid, index)); + + git_index_free(index); +} diff --git a/tests/libgit2/object/tree/frompath.c b/tests/libgit2/object/tree/frompath.c new file mode 100644 index 000000000..86ca47e94 --- /dev/null +++ b/tests/libgit2/object/tree/frompath.c @@ -0,0 +1,68 @@ +#include "clar_libgit2.h" + +static git_repository *repo; +static git_tree *tree; + +void test_object_tree_frompath__initialize(void) +{ + git_oid id; + const char *tree_with_subtrees_oid = "ae90f12eea699729ed24555e40b9fd669da12a12"; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + cl_assert(repo != NULL); + + cl_git_pass(git_oid_fromstr(&id, tree_with_subtrees_oid)); + cl_git_pass(git_tree_lookup(&tree, repo, &id)); + cl_assert(tree != NULL); +} + +void test_object_tree_frompath__cleanup(void) +{ + git_tree_free(tree); + tree = NULL; + + git_repository_free(repo); + repo = NULL; +} + +static void assert_tree_from_path( + git_tree *root, + const char *path, + const char *expected_entry_name) +{ + git_tree_entry *entry; + + cl_git_pass(git_tree_entry_bypath(&entry, root, path)); + cl_assert_equal_s(git_tree_entry_name(entry), expected_entry_name); + git_tree_entry_free(entry); +} + +void test_object_tree_frompath__retrieve_tree_from_path_to_treeentry(void) +{ + git_tree_entry *e; + + assert_tree_from_path(tree, "README", "README"); + assert_tree_from_path(tree, "ab/de/fgh/1.txt", "1.txt"); + assert_tree_from_path(tree, "ab/de/fgh", "fgh"); + assert_tree_from_path(tree, "ab/de/fgh/", "fgh"); + assert_tree_from_path(tree, "ab/de", "de"); + assert_tree_from_path(tree, "ab/", "ab"); + assert_tree_from_path(tree, "ab/de/", "de"); + + cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "i-do-not-exist.txt")); + cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "README/")); + cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "ab/de/fgh/i-do-not-exist.txt")); + cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "nope/de/fgh/1.txt")); + cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "ab/me-neither/fgh/2.txt")); + cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "ab/me-neither/fgh/2.txt/")); +} + +void test_object_tree_frompath__fail_when_processing_an_invalid_path(void) +{ + git_tree_entry *e; + + cl_must_fail(git_tree_entry_bypath(&e, tree, "/")); + cl_must_fail(git_tree_entry_bypath(&e, tree, "/ab")); + cl_must_fail(git_tree_entry_bypath(&e, tree, "/ab/de")); + cl_must_fail(git_tree_entry_bypath(&e, tree, "ab//de")); +} diff --git a/tests/libgit2/object/tree/parse.c b/tests/libgit2/object/tree/parse.c new file mode 100644 index 000000000..9d76a74f0 --- /dev/null +++ b/tests/libgit2/object/tree/parse.c @@ -0,0 +1,164 @@ +#include "clar_libgit2.h" +#include "tree.h" +#include "object.h" + +#define OID1_HEX \ + "\xae\x90\xf1\x2e\xea\x69\x97\x29\xed\x24" \ + "\x55\x5e\x40\xb9\xfd\x66\x9d\xa1\x2a\x12" +#define OID1_STR "ae90f12eea699729ed24555e40b9fd669da12a12" + +#define OID2_HEX \ + "\xe8\xbf\xe5\xaf\x39\x57\x9a\x7e\x48\x98" \ + "\xbb\x23\xf3\xa7\x6a\x72\xc3\x68\xce\xe6" +#define OID2_STR "e8bfe5af39579a7e4898bb23f3a76a72c368cee6" + +typedef struct { + const char *filename; + uint16_t attr; + const char *oid; +} expected_entry; + +static void assert_tree_parses(const char *data, size_t datalen, + expected_entry *expected_entries, size_t expected_nentries) +{ + git_tree *tree; + size_t n; + + if (!datalen) + datalen = strlen(data); + cl_git_pass(git_object__from_raw((git_object **) &tree, data, datalen, GIT_OBJECT_TREE)); + + cl_assert_equal_i(git_tree_entrycount(tree), expected_nentries); + + for (n = 0; n < expected_nentries; n++) { + expected_entry *expected = expected_entries + n; + const git_tree_entry *entry; + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, expected->oid)); + + cl_assert(entry = git_tree_entry_byname(tree, expected->filename)); + cl_assert_equal_s(expected->filename, entry->filename); + cl_assert_equal_i(expected->attr, entry->attr); + cl_assert_equal_oid(&oid, entry->oid); + } + + git_object_free(&tree->object); +} + +static void assert_tree_fails(const char *data, size_t datalen) +{ + git_object *object; + if (!datalen) + datalen = strlen(data); + cl_git_fail(git_object__from_raw(&object, data, datalen, GIT_OBJECT_TREE)); +} + +void test_object_tree_parse__single_blob_parses(void) +{ + expected_entry entries[] = { + { "foo", 0100644, OID1_STR }, + }; + const char data[] = "100644 foo\x00" OID1_HEX; + + assert_tree_parses(data, ARRAY_SIZE(data) - 1, entries, ARRAY_SIZE(entries)); +} + +void test_object_tree_parse__single_tree_parses(void) +{ + expected_entry entries[] = { + { "foo", 040000, OID1_STR }, + }; + const char data[] = "040000 foo\x00" OID1_HEX; + + assert_tree_parses(data, ARRAY_SIZE(data) - 1, entries, ARRAY_SIZE(entries)); +} + +void test_object_tree_parse__leading_filename_spaces_parse(void) +{ + expected_entry entries[] = { + { " bar", 0100644, OID1_STR }, + }; + const char data[] = "100644 bar\x00" OID1_HEX; + + assert_tree_parses(data, ARRAY_SIZE(data) - 1, entries, ARRAY_SIZE(entries)); +} + +void test_object_tree_parse__multiple_entries_parse(void) +{ + expected_entry entries[] = { + { "bar", 0100644, OID1_STR }, + { "foo", 040000, OID2_STR }, + }; + const char data[] = + "100644 bar\x00" OID1_HEX + "040000 foo\x00" OID2_HEX; + + assert_tree_parses(data, ARRAY_SIZE(data) - 1, entries, ARRAY_SIZE(entries)); +} + +void test_object_tree_parse__invalid_mode_fails(void) +{ + const char data[] = "10x644 bar\x00" OID1_HEX; + assert_tree_fails(data, ARRAY_SIZE(data) - 1); +} + +void test_object_tree_parse__missing_mode_fails(void) +{ + const char data[] = " bar\x00" OID1_HEX; + assert_tree_fails(data, ARRAY_SIZE(data) - 1); +} + +void test_object_tree_parse__mode_doesnt_cause_oob_read(void) +{ + const char data[] = "100644 bar\x00" OID1_HEX; + assert_tree_fails(data, 2); + /* + * An oob-read would correctly parse the filename and + * later fail to parse the OID with a different error + * message + */ + cl_assert_equal_s(git_error_last()->message, "failed to parse tree: missing space after filemode"); +} + +void test_object_tree_parse__unreasonably_large_mode_fails(void) +{ + const char data[] = "10000000000000000000000000 bar\x00" OID1_HEX; + assert_tree_fails(data, ARRAY_SIZE(data) - 1); +} + +void test_object_tree_parse__missing_filename_separator_fails(void) +{ + const char data[] = "100644bar\x00" OID1_HEX; + assert_tree_fails(data, ARRAY_SIZE(data) - 1); +} + +void test_object_tree_parse__missing_filename_terminator_fails(void) +{ + const char data[] = "100644 bar" OID1_HEX; + assert_tree_fails(data, ARRAY_SIZE(data) - 1); +} + +void test_object_tree_parse__empty_filename_fails(void) +{ + const char data[] = "100644 \x00" OID1_HEX; + assert_tree_fails(data, ARRAY_SIZE(data) - 1); +} + +void test_object_tree_parse__trailing_garbage_fails(void) +{ + const char data[] = "100644 bar\x00" OID1_HEX "x"; + assert_tree_fails(data, ARRAY_SIZE(data) - 1); +} + +void test_object_tree_parse__leading_space_fails(void) +{ + const char data[] = " 100644 bar\x00" OID1_HEX; + assert_tree_fails(data, ARRAY_SIZE(data) - 1); +} + +void test_object_tree_parse__truncated_oid_fails(void) +{ + const char data[] = " 100644 bar\x00" OID1_HEX; + assert_tree_fails(data, ARRAY_SIZE(data) - 2); +} diff --git a/tests/libgit2/object/tree/read.c b/tests/libgit2/object/tree/read.c new file mode 100644 index 000000000..95a2e70fb --- /dev/null +++ b/tests/libgit2/object/tree/read.c @@ -0,0 +1,119 @@ +#include "clar_libgit2.h" + +#include "tree.h" + +static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd"; + +static git_repository *g_repo; + +/* Fixture setup and teardown */ +void test_object_tree_read__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tree_read__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + + +void test_object_tree_read__loaded(void) +{ + /* access randomly the entries on a loaded tree */ + git_oid id; + git_tree *tree; + + git_oid_fromstr(&id, tree_oid); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + + cl_assert(git_tree_entry_byname(tree, "README") != NULL); + cl_assert(git_tree_entry_byname(tree, "NOTEXISTS") == NULL); + cl_assert(git_tree_entry_byname(tree, "") == NULL); + cl_assert(git_tree_entry_byindex(tree, 0) != NULL); + cl_assert(git_tree_entry_byindex(tree, 2) != NULL); + cl_assert(git_tree_entry_byindex(tree, 3) == NULL); + cl_assert(git_tree_entry_byindex(tree, (unsigned int)-1) == NULL); + + git_tree_free(tree); +} + +void test_object_tree_read__two(void) +{ + /* read a tree from the repository */ + git_oid id; + git_tree *tree; + const git_tree_entry *entry; + git_object *obj; + + git_oid_fromstr(&id, tree_oid); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + + cl_assert(git_tree_entrycount(tree) == 3); + + /* GH-86: git_object_lookup() should also check the type if the object comes from the cache */ + cl_assert(git_object_lookup(&obj, g_repo, &id, GIT_OBJECT_TREE) == 0); + cl_assert(obj != NULL); + git_object_free(obj); + obj = NULL; + cl_git_fail(git_object_lookup(&obj, g_repo, &id, GIT_OBJECT_BLOB)); + cl_assert(obj == NULL); + + entry = git_tree_entry_byname(tree, "README"); + cl_assert(entry != NULL); + + cl_assert_equal_s(git_tree_entry_name(entry), "README"); + + cl_git_pass(git_tree_entry_to_object(&obj, g_repo, entry)); + cl_assert(obj != NULL); + + git_object_free(obj); + git_tree_free(tree); +} + +#define BIGFILE "bigfile" + +#ifdef GIT_ARCH_64 +#define BIGFILE_SIZE (off_t)4294967296 +#else +# define BIGFILE_SIZE SIZE_MAX +#endif + +void test_object_tree_read__largefile(void) +{ + const git_tree_entry *entry; + git_index_entry ie; + git_commit *commit; + git_object *object; + git_index *index; + git_tree *tree; + git_oid oid; + char *buf; + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE")) + cl_skip(); + + cl_assert(buf = git__calloc(1, BIGFILE_SIZE)); + + memset(&ie, 0, sizeof(ie)); + ie.mode = GIT_FILEMODE_BLOB; + ie.path = BIGFILE; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_from_buffer(index, &ie, buf, BIGFILE_SIZE)); + cl_repo_commit_from_index(&oid, g_repo, NULL, 0, BIGFILE); + + cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); + cl_git_pass(git_commit_tree(&tree, commit)); + cl_assert(entry = git_tree_entry_byname(tree, BIGFILE)); + cl_git_pass(git_tree_entry_to_object(&object, g_repo, entry)); + + git_object_free(object); + git_tree_free(tree); + git_index_free(index); + git_commit_free(commit); + git__free(buf); +} diff --git a/tests/libgit2/object/tree/update.c b/tests/libgit2/object/tree/update.c new file mode 100644 index 000000000..41b50f3e9 --- /dev/null +++ b/tests/libgit2/object/tree/update.c @@ -0,0 +1,302 @@ +#include "clar_libgit2.h" +#include "tree.h" + +static git_repository *g_repo; + +void test_object_tree_update__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo2"); +} + +void test_object_tree_update__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_object_tree_update__remove_blob(void) +{ + git_oid tree_index_id, tree_updater_id, base_id; + git_tree *base_tree; + git_index *idx; + const char *path = "README"; + + git_tree_update updates[] = { + { GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB /* ignored */, path}, + }; + + cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); + cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); + + /* Create it with an index */ + cl_git_pass(git_index_new(&idx)); + cl_git_pass(git_index_read_tree(idx, base_tree)); + cl_git_pass(git_index_remove(idx, path, 0)); + cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); + git_index_free(idx); + + /* Perform the same operation via the tree updater */ + cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 1, updates)); + + cl_assert_equal_oid(&tree_index_id, &tree_updater_id); + + git_tree_free(base_tree); +} + +void test_object_tree_update__remove_blob_deeper(void) +{ + git_oid tree_index_id, tree_updater_id, base_id; + git_tree *base_tree; + git_index *idx; + const char *path = "subdir/README"; + + git_tree_update updates[] = { + { GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB /* ignored */, path}, + }; + + cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); + cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); + + /* Create it with an index */ + cl_git_pass(git_index_new(&idx)); + cl_git_pass(git_index_read_tree(idx, base_tree)); + cl_git_pass(git_index_remove(idx, path, 0)); + cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); + git_index_free(idx); + + /* Perform the same operation via the tree updater */ + cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 1, updates)); + + cl_assert_equal_oid(&tree_index_id, &tree_updater_id); + + git_tree_free(base_tree); +} + +void test_object_tree_update__remove_all_entries(void) +{ + git_oid tree_index_id, tree_updater_id, base_id; + git_tree *base_tree; + git_index *idx; + const char *path1 = "subdir/subdir2/README"; + const char *path2 = "subdir/subdir2/new.txt"; + + git_tree_update updates[] = { + { GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB /* ignored */, path1}, + { GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB /* ignored */, path2}, + }; + + cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); + cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); + + /* Create it with an index */ + cl_git_pass(git_index_new(&idx)); + cl_git_pass(git_index_read_tree(idx, base_tree)); + cl_git_pass(git_index_remove(idx, path1, 0)); + cl_git_pass(git_index_remove(idx, path2, 0)); + cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); + git_index_free(idx); + + /* Perform the same operation via the tree updater */ + cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 2, updates)); + + cl_assert_equal_oid(&tree_index_id, &tree_updater_id); + + git_tree_free(base_tree); +} + +void test_object_tree_update__replace_blob(void) +{ + git_oid tree_index_id, tree_updater_id, base_id; + git_tree *base_tree; + git_index *idx; + const char *path = "README"; + git_index_entry entry = { {0} }; + + git_tree_update updates[] = { + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, path}, + }; + + cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); + cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); + + /* Create it with an index */ + cl_git_pass(git_index_new(&idx)); + cl_git_pass(git_index_read_tree(idx, base_tree)); + + entry.path = path; + cl_git_pass(git_oid_fromstr(&entry.id, "fa49b077972391ad58037050f2a75f74e3671e92")); + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_index_add(idx, &entry)); + + cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); + git_index_free(idx); + + /* Perform the same operation via the tree updater */ + cl_git_pass(git_oid_fromstr(&updates[0].id, "fa49b077972391ad58037050f2a75f74e3671e92")); + cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 1, updates)); + + cl_assert_equal_oid(&tree_index_id, &tree_updater_id); + + git_tree_free(base_tree); +} + +void test_object_tree_update__add_blobs(void) +{ + git_oid tree_index_id, tree_updater_id, base_id; + git_tree *base_tree; + git_index *idx; + git_index_entry entry = { {0} }; + int i; + const char *paths[] = { + "some/deep/path", + "some/other/path", + "a/path/elsewhere", + }; + + git_tree_update updates[] = { + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[0]}, + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[1]}, + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[2]}, + }; + + cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); + + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&entry.id, "fa49b077972391ad58037050f2a75f74e3671e92")); + + for (i = 0; i < 3; i++) { + cl_git_pass(git_oid_fromstr(&updates[i].id, "fa49b077972391ad58037050f2a75f74e3671e92")); + } + + for (i = 0; i < 2; i++) { + int j; + + /* Create it with an index */ + cl_git_pass(git_index_new(&idx)); + + base_tree = NULL; + if (i == 1) { + cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); + cl_git_pass(git_index_read_tree(idx, base_tree)); + } + + for (j = 0; j < 3; j++) { + entry.path = paths[j]; + cl_git_pass(git_index_add(idx, &entry)); + } + + cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); + git_index_free(idx); + + /* Perform the same operations via the tree updater */ + cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 3, updates)); + + cl_assert_equal_oid(&tree_index_id, &tree_updater_id); + } + + git_tree_free(base_tree); +} + +void test_object_tree_update__add_blobs_unsorted(void) +{ + git_oid tree_index_id, tree_updater_id, base_id; + git_tree *base_tree; + git_index *idx; + git_index_entry entry = { {0} }; + int i; + const char *paths[] = { + "some/deep/path", + "a/path/elsewhere", + "some/other/path", + }; + + git_tree_update updates[] = { + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[0]}, + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[1]}, + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[2]}, + }; + + cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); + + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&entry.id, "fa49b077972391ad58037050f2a75f74e3671e92")); + + for (i = 0; i < 3; i++) { + cl_git_pass(git_oid_fromstr(&updates[i].id, "fa49b077972391ad58037050f2a75f74e3671e92")); + } + + for (i = 0; i < 2; i++) { + int j; + + /* Create it with an index */ + cl_git_pass(git_index_new(&idx)); + + base_tree = NULL; + if (i == 1) { + cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); + cl_git_pass(git_index_read_tree(idx, base_tree)); + } + + for (j = 0; j < 3; j++) { + entry.path = paths[j]; + cl_git_pass(git_index_add(idx, &entry)); + } + + cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); + git_index_free(idx); + + /* Perform the same operations via the tree updater */ + cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 3, updates)); + + cl_assert_equal_oid(&tree_index_id, &tree_updater_id); + } + + git_tree_free(base_tree); +} + +void test_object_tree_update__add_conflict(void) +{ + int i; + git_oid tree_updater_id; + git_tree_update updates[] = { + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, "a/dir/blob"}, + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, "a/dir"}, + }; + + for (i = 0; i < 2; i++) { + cl_git_pass(git_oid_fromstr(&updates[i].id, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); + } + + cl_git_fail(git_tree_create_updated(&tree_updater_id, g_repo, NULL, 2, updates)); +} + +void test_object_tree_update__add_conflict2(void) +{ + int i; + git_oid tree_updater_id; + git_tree_update updates[] = { + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, "a/dir/blob"}, + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_TREE, "a/dir/blob"}, + }; + + for (i = 0; i < 2; i++) { + cl_git_pass(git_oid_fromstr(&updates[i].id, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); + } + + cl_git_fail(git_tree_create_updated(&tree_updater_id, g_repo, NULL, 2, updates)); +} + +void test_object_tree_update__remove_invalid_submodule(void) +{ + git_tree *baseline; + git_oid updated_tree_id, baseline_id; + git_tree_update updates[] = { + {GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB, "submodule"}, + }; + + /* This tree contains a submodule with an all-zero commit for a submodule named 'submodule' */ + cl_git_pass(git_oid_fromstr(&baseline_id, "396c7f1adb7925f51ba13a75f48252f44c5a14a2")); + cl_git_pass(git_tree_lookup(&baseline, g_repo, &baseline_id)); + cl_git_pass(git_tree_create_updated(&updated_tree_id, g_repo, baseline, 1, updates)); + + git_tree_free(baseline); +} diff --git a/tests/libgit2/object/tree/walk.c b/tests/libgit2/object/tree/walk.c new file mode 100644 index 000000000..d1fdaa3a0 --- /dev/null +++ b/tests/libgit2/object/tree/walk.c @@ -0,0 +1,177 @@ +#include "clar_libgit2.h" +#include "tree.h" + +static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd"; +static git_repository *g_repo; + +void test_object_tree_walk__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tree_walk__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int treewalk_count_cb( + const char *root, const git_tree_entry *entry, void *payload) +{ + int *count = payload; + + GIT_UNUSED(root); + GIT_UNUSED(entry); + + (*count) += 1; + + return 0; +} + +void test_object_tree_walk__0(void) +{ + git_oid id; + git_tree *tree; + int ct; + + git_oid_fromstr(&id, tree_oid); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + + ct = 0; + cl_git_pass(git_tree_walk(tree, GIT_TREEWALK_PRE, treewalk_count_cb, &ct)); + cl_assert_equal_i(3, ct); + + ct = 0; + cl_git_pass(git_tree_walk(tree, GIT_TREEWALK_POST, treewalk_count_cb, &ct)); + cl_assert_equal_i(3, ct); + + git_tree_free(tree); +} + + +static int treewalk_stop_cb( + const char *root, const git_tree_entry *entry, void *payload) +{ + int *count = payload; + + GIT_UNUSED(root); + GIT_UNUSED(entry); + + (*count) += 1; + + return (*count == 2) ? -123 : 0; +} + +static int treewalk_stop_immediately_cb( + const char *root, const git_tree_entry *entry, void *payload) +{ + GIT_UNUSED(root); + GIT_UNUSED(entry); + GIT_UNUSED(payload); + return -100; +} + +void test_object_tree_walk__1(void) +{ + git_oid id; + git_tree *tree; + int ct; + + git_oid_fromstr(&id, tree_oid); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + + ct = 0; + cl_assert_equal_i( + -123, git_tree_walk(tree, GIT_TREEWALK_PRE, treewalk_stop_cb, &ct)); + cl_assert_equal_i(2, ct); + + ct = 0; + cl_assert_equal_i( + -123, git_tree_walk(tree, GIT_TREEWALK_POST, treewalk_stop_cb, &ct)); + cl_assert_equal_i(2, ct); + + cl_assert_equal_i( + -100, git_tree_walk( + tree, GIT_TREEWALK_PRE, treewalk_stop_immediately_cb, NULL)); + + cl_assert_equal_i( + -100, git_tree_walk( + tree, GIT_TREEWALK_POST, treewalk_stop_immediately_cb, NULL)); + + git_tree_free(tree); +} + + +struct treewalk_skip_data { + int files; + int dirs; + const char *skip; + const char *stop; +}; + +static int treewalk_skip_de_cb( + const char *root, const git_tree_entry *entry, void *payload) +{ + struct treewalk_skip_data *data = payload; + const char *name = git_tree_entry_name(entry); + + GIT_UNUSED(root); + + if (git_tree_entry_type(entry) == GIT_OBJECT_TREE) + data->dirs++; + else + data->files++; + + if (data->skip && !strcmp(name, data->skip)) + return 1; + else if (data->stop && !strcmp(name, data->stop)) + return -1; + else + return 0; +} + +void test_object_tree_walk__2(void) +{ + git_oid id; + git_tree *tree; + struct treewalk_skip_data data; + + /* look up a deep tree */ + git_oid_fromstr(&id, "ae90f12eea699729ed24555e40b9fd669da12a12"); + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + + memset(&data, 0, sizeof(data)); + data.skip = "de"; + + cl_assert_equal_i(0, git_tree_walk( + tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); + cl_assert_equal_i(5, data.files); + cl_assert_equal_i(3, data.dirs); + + memset(&data, 0, sizeof(data)); + data.stop = "3.txt"; + + cl_assert_equal_i(-1, git_tree_walk( + tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); + cl_assert_equal_i(3, data.files); + cl_assert_equal_i(2, data.dirs); + + memset(&data, 0, sizeof(data)); + data.skip = "new.txt"; + + cl_assert_equal_i(0, git_tree_walk( + tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); + cl_assert_equal_i(7, data.files); + cl_assert_equal_i(4, data.dirs); + + memset(&data, 0, sizeof(data)); + data.stop = "new.txt"; + + cl_assert_equal_i(-1, git_tree_walk( + tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); + cl_assert_equal_i(7, data.files); + cl_assert_equal_i(4, data.dirs); + + git_tree_free(tree); +} diff --git a/tests/libgit2/object/tree/write.c b/tests/libgit2/object/tree/write.c new file mode 100644 index 000000000..a4ceb35b6 --- /dev/null +++ b/tests/libgit2/object/tree/write.c @@ -0,0 +1,526 @@ +#include "clar_libgit2.h" + +#include "tree.h" + +static const char *blob_oid = "fa49b077972391ad58037050f2a75f74e3671e92"; +static const char *first_tree = "181037049a54a1eb5fab404658a3a250b44335d7"; +static const char *second_tree = "f60079018b664e4e79329a7ef9559c8d9e0378d1"; +static const char *third_tree = "eb86d8b81d6adbd5290a935d6c9976882de98488"; + +static git_repository *g_repo; + +/* Fixture setup and teardown */ +void test_object_tree_write__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tree_write__cleanup(void) +{ + cl_git_sandbox_cleanup(); + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 1)); +} + +void test_object_tree_write__from_memory(void) +{ + /* write a tree from a memory */ + git_treebuilder *builder; + git_tree *tree; + git_oid id, bid, rid, id2; + + git_oid_fromstr(&id, first_tree); + git_oid_fromstr(&id2, second_tree); + git_oid_fromstr(&bid, blob_oid); + + /* create a second tree from first tree using `git_treebuilder_insert` + * on REPOSITORY_FOLDER. + */ + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + cl_git_pass(git_treebuilder_new(&builder, g_repo, tree)); + + cl_git_fail(git_treebuilder_insert(NULL, builder, "", + &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, "/", + &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, ".git", + &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, "..", + &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, ".", + &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, "folder/new.txt", + &bid, GIT_FILEMODE_BLOB)); + + cl_git_pass(git_treebuilder_insert( + NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB)); + + cl_git_pass(git_treebuilder_write(&rid, builder)); + + cl_assert(git_oid_cmp(&rid, &id2) == 0); + + git_treebuilder_free(builder); + git_tree_free(tree); +} + +void test_object_tree_write__subtree(void) +{ + /* write a hierarchical tree from a memory */ + git_treebuilder *builder; + git_tree *tree; + git_oid id, bid, subtree_id, id2, id3; + git_oid id_hiearar; + + git_oid_fromstr(&id, first_tree); + git_oid_fromstr(&id2, second_tree); + git_oid_fromstr(&id3, third_tree); + git_oid_fromstr(&bid, blob_oid); + + /* create subtree */ + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + cl_git_pass(git_treebuilder_insert( + NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB)); /* -V536 */ + cl_git_pass(git_treebuilder_write(&subtree_id, builder)); + git_treebuilder_free(builder); + + /* create parent tree */ + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + cl_git_pass(git_treebuilder_new(&builder, g_repo, tree)); + cl_git_pass(git_treebuilder_insert( + NULL, builder, "new", &subtree_id, GIT_FILEMODE_TREE)); /* -V536 */ + cl_git_pass(git_treebuilder_write(&id_hiearar, builder)); + git_treebuilder_free(builder); + git_tree_free(tree); + + cl_assert(git_oid_cmp(&id_hiearar, &id3) == 0); + + /* check data is correct */ + cl_git_pass(git_tree_lookup(&tree, g_repo, &id_hiearar)); + cl_assert(2 == git_tree_entrycount(tree)); + git_tree_free(tree); +} + +/* + * And the Lord said: Is this tree properly sorted? + */ +void test_object_tree_write__sorted_subtrees(void) +{ + git_treebuilder *builder; + git_tree *tree; + unsigned int i; + int position_c = -1, position_cake = -1, position_config = -1; + + struct { + unsigned int attr; + const char *filename; + } entries[] = { + { GIT_FILEMODE_BLOB, ".gitattributes" }, + { GIT_FILEMODE_BLOB, ".gitignore" }, + { GIT_FILEMODE_BLOB, ".htaccess" }, + { GIT_FILEMODE_BLOB, "Capfile" }, + { GIT_FILEMODE_BLOB, "Makefile"}, + { GIT_FILEMODE_BLOB, "README"}, + { GIT_FILEMODE_TREE, "app"}, + { GIT_FILEMODE_TREE, "cake"}, + { GIT_FILEMODE_TREE, "config"}, + { GIT_FILEMODE_BLOB, "c"}, + { GIT_FILEMODE_BLOB, "git_test.txt"}, + { GIT_FILEMODE_BLOB, "htaccess.htaccess"}, + { GIT_FILEMODE_BLOB, "index.php"}, + { GIT_FILEMODE_TREE, "plugins"}, + { GIT_FILEMODE_TREE, "schemas"}, + { GIT_FILEMODE_TREE, "ssl-certs"}, + { GIT_FILEMODE_TREE, "vendors"} + }; + + git_oid bid, tid, tree_oid; + + cl_git_pass(git_oid_fromstr(&bid, blob_oid)); + cl_git_pass(git_oid_fromstr(&tid, first_tree)); + + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + git_oid *id = entries[i].attr == GIT_FILEMODE_TREE ? &tid : &bid; + + cl_git_pass(git_treebuilder_insert(NULL, + builder, entries[i].filename, id, entries[i].attr)); + } + + cl_git_pass(git_treebuilder_write(&tree_oid, builder)); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid)); + for (i = 0; i < git_tree_entrycount(tree); i++) { + const git_tree_entry *entry = git_tree_entry_byindex(tree, i); + + if (strcmp(entry->filename, "c") == 0) + position_c = i; + + if (strcmp(entry->filename, "cake") == 0) + position_cake = i; + + if (strcmp(entry->filename, "config") == 0) + position_config = i; + } + + git_tree_free(tree); + + cl_assert(position_c != -1); + cl_assert(position_cake != -1); + cl_assert(position_config != -1); + + cl_assert(position_c < position_cake); + cl_assert(position_cake < position_config); + + git_treebuilder_free(builder); +} + +static struct { + unsigned int attr; + const char *filename; +} _entries[] = { + { GIT_FILEMODE_BLOB, "aardvark" }, + { GIT_FILEMODE_BLOB, ".first" }, + { GIT_FILEMODE_BLOB, "apple" }, + { GIT_FILEMODE_BLOB, "last"}, + { GIT_FILEMODE_BLOB, "apple_after"}, + { GIT_FILEMODE_BLOB, "after_aardvark"}, + { 0, NULL }, +}; + +void test_object_tree_write__removing_and_re_adding_in_treebuilder(void) +{ + git_treebuilder *builder; + int i, aardvark_i, apple_i, apple_after_i, apple_extra_i, last_i; + git_oid entry_oid, tree_oid; + git_tree *tree; + + cl_git_pass(git_oid_fromstr(&entry_oid, blob_oid)); + + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + + cl_assert_equal_i(0, (int)git_treebuilder_entrycount(builder)); + + for (i = 0; _entries[i].filename; ++i) + cl_git_pass(git_treebuilder_insert(NULL, + builder, _entries[i].filename, &entry_oid, _entries[i].attr)); + + cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder)); + + cl_git_pass(git_treebuilder_remove(builder, "apple")); + cl_assert_equal_i(5, (int)git_treebuilder_entrycount(builder)); + + cl_git_pass(git_treebuilder_remove(builder, "apple_after")); + cl_assert_equal_i(4, (int)git_treebuilder_entrycount(builder)); + + cl_git_pass(git_treebuilder_insert( + NULL, builder, "before_last", &entry_oid, GIT_FILEMODE_BLOB)); + cl_assert_equal_i(5, (int)git_treebuilder_entrycount(builder)); + + /* reinsert apple_after */ + cl_git_pass(git_treebuilder_insert( + NULL, builder, "apple_after", &entry_oid, GIT_FILEMODE_BLOB)); + cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder)); + + cl_git_pass(git_treebuilder_remove(builder, "last")); + cl_assert_equal_i(5, (int)git_treebuilder_entrycount(builder)); + + /* reinsert last */ + cl_git_pass(git_treebuilder_insert( + NULL, builder, "last", &entry_oid, GIT_FILEMODE_BLOB)); + cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder)); + + cl_git_pass(git_treebuilder_insert( + NULL, builder, "apple_extra", &entry_oid, GIT_FILEMODE_BLOB)); + cl_assert_equal_i(7, (int)git_treebuilder_entrycount(builder)); + + cl_git_pass(git_treebuilder_write(&tree_oid, builder)); + + git_treebuilder_free(builder); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid)); + + cl_assert_equal_i(7, (int)git_tree_entrycount(tree)); + + cl_assert(git_tree_entry_byname(tree, ".first") != NULL); + cl_assert(git_tree_entry_byname(tree, "apple") == NULL); + cl_assert(git_tree_entry_byname(tree, "apple_after") != NULL); + cl_assert(git_tree_entry_byname(tree, "apple_extra") != NULL); + cl_assert(git_tree_entry_byname(tree, "last") != NULL); + + aardvark_i = apple_i = apple_after_i = apple_extra_i = last_i = -1; + + for (i = 0; i < 7; ++i) { + const git_tree_entry *entry = git_tree_entry_byindex(tree, i); + + if (!strcmp(entry->filename, "aardvark")) + aardvark_i = i; + else if (!strcmp(entry->filename, "apple")) + apple_i = i; + else if (!strcmp(entry->filename, "apple_after")) + apple_after_i = i; + else if (!strcmp(entry->filename, "apple_extra")) + apple_extra_i = i; + else if (!strcmp(entry->filename, "last")) + last_i = i; + } + + cl_assert_equal_i(-1, apple_i); + cl_assert_equal_i(6, last_i); + cl_assert(aardvark_i < apple_after_i); + cl_assert(apple_after_i < apple_extra_i); + + git_tree_free(tree); +} + +static int treebuilder_filter_prefixed( + const git_tree_entry *entry, void *payload) +{ + return !git__prefixcmp(git_tree_entry_name(entry), payload); +} + +void test_object_tree_write__filtering(void) +{ + git_treebuilder *builder; + int i; + git_oid entry_oid, tree_oid; + git_tree *tree; + + git_oid_fromstr(&entry_oid, blob_oid); + + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + + for (i = 0; _entries[i].filename; ++i) + cl_git_pass(git_treebuilder_insert(NULL, + builder, _entries[i].filename, &entry_oid, _entries[i].attr)); + + cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder)); + + cl_assert(git_treebuilder_get(builder, "apple") != NULL); + cl_assert(git_treebuilder_get(builder, "aardvark") != NULL); + cl_assert(git_treebuilder_get(builder, "last") != NULL); + + git_treebuilder_filter(builder, treebuilder_filter_prefixed, "apple"); + + cl_assert_equal_i(4, (int)git_treebuilder_entrycount(builder)); + + cl_assert(git_treebuilder_get(builder, "apple") == NULL); + cl_assert(git_treebuilder_get(builder, "aardvark") != NULL); + cl_assert(git_treebuilder_get(builder, "last") != NULL); + + git_treebuilder_filter(builder, treebuilder_filter_prefixed, "a"); + + cl_assert_equal_i(2, (int)git_treebuilder_entrycount(builder)); + + cl_assert(git_treebuilder_get(builder, "aardvark") == NULL); + cl_assert(git_treebuilder_get(builder, "last") != NULL); + + cl_git_pass(git_treebuilder_write(&tree_oid, builder)); + + git_treebuilder_free(builder); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid)); + + cl_assert_equal_i(2, (int)git_tree_entrycount(tree)); + + git_tree_free(tree); +} + +void test_object_tree_write__cruel_paths(void) +{ + static const char *the_paths[] = { + "C:\\", + " : * ? \" \n < > |", + "a\\b", + "\\\\b\a", + ":\\", + "COM1", + "foo.aux", + REP1024("1234"), /* 4096 char string */ + REP1024("12345678"), /* 8192 char string */ + "\xC5\xAA\x6E\xC4\xAD\x63\xC5\x8D\x64\x65\xCC\xBD", /* Ūnĭcōde̽ */ + NULL + }; + git_treebuilder *builder; + git_tree *tree; + git_oid id, bid, subid; + const char **scan; + int count = 0, i, j; + git_tree_entry *te; + + git_oid_fromstr(&bid, blob_oid); + + /* create tree */ + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + for (scan = the_paths; *scan; ++scan) { + cl_git_pass(git_treebuilder_insert( + NULL, builder, *scan, &bid, GIT_FILEMODE_BLOB)); + count++; + } + cl_git_pass(git_treebuilder_write(&id, builder)); + git_treebuilder_free(builder); + + /* check data is correct */ + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + + cl_assert_equal_i(count, git_tree_entrycount(tree)); + + for (scan = the_paths; *scan; ++scan) { + const git_tree_entry *cte = git_tree_entry_byname(tree, *scan); + cl_assert(cte != NULL); + cl_assert_equal_s(*scan, git_tree_entry_name(cte)); + } + for (scan = the_paths; *scan; ++scan) { + cl_git_pass(git_tree_entry_bypath(&te, tree, *scan)); + cl_assert_equal_s(*scan, git_tree_entry_name(te)); + git_tree_entry_free(te); + } + + git_tree_free(tree); + + /* let's try longer paths */ + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + for (scan = the_paths; *scan; ++scan) { + cl_git_pass(git_treebuilder_insert( + NULL, builder, *scan, &id, GIT_FILEMODE_TREE)); + } + cl_git_pass(git_treebuilder_write(&subid, builder)); + git_treebuilder_free(builder); + + /* check data is correct */ + cl_git_pass(git_tree_lookup(&tree, g_repo, &subid)); + + cl_assert_equal_i(count, git_tree_entrycount(tree)); + + for (i = 0; i < count; ++i) { + for (j = 0; j < count; ++j) { + git_str b = GIT_STR_INIT; + cl_git_pass(git_str_joinpath(&b, the_paths[i], the_paths[j])); + cl_git_pass(git_tree_entry_bypath(&te, tree, b.ptr)); + cl_assert_equal_s(the_paths[j], git_tree_entry_name(te)); + git_tree_entry_free(te); + git_str_dispose(&b); + } + } + + git_tree_free(tree); +} + +void test_object_tree_write__protect_filesystems(void) +{ + git_treebuilder *builder; + git_oid bid; + + cl_git_pass(git_oid_fromstr(&bid, "fa49b077972391ad58037050f2a75f74e3671e92")); + + /* Ensure that (by default) we can write objects with funny names on + * platforms that are not affected. + */ + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + + cl_git_fail(git_treebuilder_insert(NULL, builder, ".git.", &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, "git~1", &bid, GIT_FILEMODE_BLOB)); + +#ifndef __APPLE__ + cl_git_pass(git_treebuilder_insert(NULL, builder, ".git\xef\xbb\xbf", &bid, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_insert(NULL, builder, ".git\xe2\x80\xad", &bid, GIT_FILEMODE_BLOB)); +#endif + + git_treebuilder_free(builder); + + /* Now turn on core.protectHFS and core.protectNTFS and validate that these + * paths are rejected. + */ + + cl_repo_set_bool(g_repo, "core.protectHFS", true); + cl_repo_set_bool(g_repo, "core.protectNTFS", true); + + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + + cl_git_fail(git_treebuilder_insert(NULL, builder, ".git.", &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, "git~1", &bid, GIT_FILEMODE_BLOB)); + + cl_git_fail(git_treebuilder_insert(NULL, builder, ".git\xef\xbb\xbf", &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, ".git\xe2\x80\xad", &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, ".git::$INDEX_ALLOCATION/dummy-file", &bid, GIT_FILEMODE_BLOB)); + + git_treebuilder_free(builder); +} + +static void test_invalid_objects(bool should_allow_invalid) +{ + git_treebuilder *builder; + git_oid valid_blob_id, invalid_blob_id, valid_tree_id, invalid_tree_id; + +#define assert_allowed(expr) \ + clar__assert(!(expr) == should_allow_invalid, \ + __FILE__, __func__, __LINE__, \ + (should_allow_invalid ? \ + "Expected function call to succeed: " #expr : \ + "Expected function call to fail: " #expr), \ + NULL, 1) + + cl_git_pass(git_oid_fromstr(&valid_blob_id, blob_oid)); + cl_git_pass(git_oid_fromstr(&invalid_blob_id, + "1234567890123456789012345678901234567890")); + cl_git_pass(git_oid_fromstr(&valid_tree_id, first_tree)); + cl_git_pass(git_oid_fromstr(&invalid_tree_id, + "0000000000111111111122222222223333333333")); + + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + + /* test valid blobs and trees (these should always pass) */ + cl_git_pass(git_treebuilder_insert(NULL, builder, "file.txt", &valid_blob_id, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_insert(NULL, builder, "folder", &valid_tree_id, GIT_FILEMODE_TREE)); + + /* replace valid files and folders with invalid ones */ + assert_allowed(git_treebuilder_insert(NULL, builder, "file.txt", &invalid_blob_id, GIT_FILEMODE_BLOB)); + assert_allowed(git_treebuilder_insert(NULL, builder, "folder", &invalid_blob_id, GIT_FILEMODE_BLOB)); + + /* insert new invalid files and folders */ + assert_allowed(git_treebuilder_insert(NULL, builder, "invalid_file.txt", &invalid_blob_id, GIT_FILEMODE_BLOB)); + assert_allowed(git_treebuilder_insert(NULL, builder, "invalid_folder", &invalid_blob_id, GIT_FILEMODE_BLOB)); + + /* insert valid blobs as trees and trees as blobs */ + assert_allowed(git_treebuilder_insert(NULL, builder, "file_as_folder", &valid_blob_id, GIT_FILEMODE_TREE)); + assert_allowed(git_treebuilder_insert(NULL, builder, "folder_as_file.txt", &valid_tree_id, GIT_FILEMODE_BLOB)); + +#undef assert_allowed + + git_treebuilder_free(builder); +} + +static void test_inserting_submodule(void) +{ + git_treebuilder *bld; + git_oid sm_id; + + cl_git_pass(git_oid_fromstr(&sm_id, "da39a3ee5e6b4b0d3255bfef95601890afd80709")); + cl_git_pass(git_treebuilder_new(&bld, g_repo, NULL)); + cl_git_pass(git_treebuilder_insert(NULL, bld, "sm", &sm_id, GIT_FILEMODE_COMMIT)); + git_treebuilder_free(bld); +} + +void test_object_tree_write__object_validity(void) +{ + /* Ensure that we cannot add invalid objects by default */ + test_invalid_objects(false); + test_inserting_submodule(); + + /* Ensure that we can turn off validation */ + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 0)); + test_invalid_objects(true); + test_inserting_submodule(); +} + +void test_object_tree_write__invalid_null_oid(void) +{ + git_treebuilder *bld; + git_oid null_oid = {{0}}; + + cl_git_pass(git_treebuilder_new(&bld, g_repo, NULL)); + cl_git_fail(git_treebuilder_insert(NULL, bld, "null_oid_file", &null_oid, GIT_FILEMODE_BLOB)); + cl_assert(git_error_last() && strstr(git_error_last()->message, "null OID") != NULL); + + git_treebuilder_free(bld); +} diff --git a/tests/libgit2/object/validate.c b/tests/libgit2/object/validate.c new file mode 100644 index 000000000..87193deb6 --- /dev/null +++ b/tests/libgit2/object/validate.c @@ -0,0 +1,50 @@ +#include "clar_libgit2.h" + +#define VALID_COMMIT "tree bdd24e358576f1baa275df98cdcaf3ac9a3f4233\n" \ + "parent d6d956f1d66210bfcd0484166befab33b5987a39\n" \ + "author Edward Thomson 1638286404 -0500\n" \ + "committer Edward Thomson 1638324642 -0500\n" \ + "\n" \ + "commit go here.\n" +#define VALID_TREE "100644 HEADER\0\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42" + +#define INVALID_COMMIT "tree bdd24e358576f1baa275df98cdcaf3ac9a3f4233\n" \ + "parent d6d956f1d66210bfcd0484166befab33b5987a39\n" \ + "committer Edward Thomson 1638324642 -0500\n" \ + "\n" \ + "commit go here.\n" +#define INVALID_TREE "100644 HEADER \x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42" + +void test_object_validate__valid(void) +{ + int valid; + + cl_git_pass(git_object_rawcontent_is_valid(&valid, "", 0, GIT_OBJECT_BLOB)); + cl_assert_equal_i(1, valid); + + cl_git_pass(git_object_rawcontent_is_valid(&valid, "foobar", 0, GIT_OBJECT_BLOB)); + cl_assert_equal_i(1, valid); + + cl_git_pass(git_object_rawcontent_is_valid(&valid, VALID_COMMIT, CONST_STRLEN(VALID_COMMIT), GIT_OBJECT_COMMIT)); + cl_assert_equal_i(1, valid); + + cl_git_pass(git_object_rawcontent_is_valid(&valid, VALID_TREE, CONST_STRLEN(VALID_TREE), GIT_OBJECT_TREE)); + cl_assert_equal_i(1, valid); +} + +void test_object_validate__invalid(void) +{ + int valid; + + cl_git_pass(git_object_rawcontent_is_valid(&valid, "", 0, GIT_OBJECT_COMMIT)); + cl_assert_equal_i(0, valid); + + cl_git_pass(git_object_rawcontent_is_valid(&valid, "foobar", 0, GIT_OBJECT_COMMIT)); + cl_assert_equal_i(0, valid); + + cl_git_pass(git_object_rawcontent_is_valid(&valid, INVALID_COMMIT, CONST_STRLEN(INVALID_COMMIT), GIT_OBJECT_COMMIT)); + cl_assert_equal_i(0, valid); + + cl_git_pass(git_object_rawcontent_is_valid(&valid, INVALID_TREE, CONST_STRLEN(INVALID_TREE), GIT_OBJECT_TREE)); + cl_assert_equal_i(0, valid); +} diff --git a/tests/libgit2/odb/alternates.c b/tests/libgit2/odb/alternates.c new file mode 100644 index 000000000..6c00fda2f --- /dev/null +++ b/tests/libgit2/odb/alternates.c @@ -0,0 +1,80 @@ +#include "clar_libgit2.h" +#include "odb.h" +#include "filebuf.h" + +static git_str destpath, filepath; +static const char *paths[] = { + "A.git", "B.git", "C.git", "D.git", "E.git", "F.git", "G.git" +}; +static git_filebuf file; +static git_repository *repo; + +void test_odb_alternates__cleanup(void) +{ + size_t i; + + git_str_dispose(&destpath); + git_str_dispose(&filepath); + + for (i = 0; i < ARRAY_SIZE(paths); i++) + cl_fixture_cleanup(paths[i]); +} + +static void init_linked_repo(const char *path, const char *alternate) +{ + git_str_clear(&destpath); + git_str_clear(&filepath); + + cl_git_pass(git_repository_init(&repo, path, 1)); + cl_git_pass(git_fs_path_prettify(&destpath, alternate, NULL)); + cl_git_pass(git_str_joinpath(&destpath, destpath.ptr, "objects")); + cl_git_pass(git_str_joinpath(&filepath, git_repository_path(repo), "objects/info")); + cl_git_pass(git_futils_mkdir(filepath.ptr, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_str_joinpath(&filepath, filepath.ptr , "alternates")); + + cl_git_pass(git_filebuf_open(&file, git_str_cstr(&filepath), 0, 0666)); + git_filebuf_printf(&file, "%s\n", git_str_cstr(&destpath)); + cl_git_pass(git_filebuf_commit(&file)); + + git_repository_free(repo); +} + +void test_odb_alternates__chained(void) +{ + git_commit *commit; + git_oid oid; + + /* Set the alternate A -> testrepo.git */ + init_linked_repo(paths[0], cl_fixture("testrepo.git")); + + /* Set the alternate B -> A */ + init_linked_repo(paths[1], paths[0]); + + /* Now load B and see if we can find an object from testrepo.git */ + cl_git_pass(git_repository_open(&repo, paths[1])); + git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + git_commit_free(commit); + git_repository_free(repo); +} + +void test_odb_alternates__long_chain(void) +{ + git_commit *commit; + git_oid oid; + size_t i; + + /* Set the alternate A -> testrepo.git */ + init_linked_repo(paths[0], cl_fixture("testrepo.git")); + + /* Set up the five-element chain */ + for (i = 1; i < ARRAY_SIZE(paths); i++) { + init_linked_repo(paths[i], paths[i-1]); + } + + /* Now load the last one and see if we can find an object from testrepo.git */ + cl_git_pass(git_repository_open(&repo, paths[ARRAY_SIZE(paths)-1])); + git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_fail(git_commit_lookup(&commit, repo, &oid)); + git_repository_free(repo); +} diff --git a/tests/libgit2/odb/backend/backend_helpers.c b/tests/libgit2/odb/backend/backend_helpers.c new file mode 100644 index 000000000..542799242 --- /dev/null +++ b/tests/libgit2/odb/backend/backend_helpers.c @@ -0,0 +1,172 @@ +#include "clar_libgit2.h" +#include "git2/sys/odb_backend.h" +#include "backend_helpers.h" + +static int search_object(const fake_object **out, fake_backend *fake, const git_oid *oid, size_t len) +{ + const fake_object *obj = fake->objects, *found = NULL; + + while (obj && obj->oid) { + git_oid current_oid; + + git_oid_fromstr(¤t_oid, obj->oid); + + if (git_oid_ncmp(¤t_oid, oid, len) == 0) { + if (found) + return GIT_EAMBIGUOUS; + found = obj; + } + + obj++; + } + + if (found && out) + *out = found; + + return found ? GIT_OK : GIT_ENOTFOUND; +} + +static int fake_backend__exists(git_odb_backend *backend, const git_oid *oid) +{ + fake_backend *fake; + + fake = (fake_backend *)backend; + + fake->exists_calls++; + + return search_object(NULL, fake, oid, GIT_OID_HEXSZ) == GIT_OK; +} + +static int fake_backend__exists_prefix( + git_oid *out, git_odb_backend *backend, const git_oid *oid, size_t len) +{ + const fake_object *obj; + fake_backend *fake; + int error; + + fake = (fake_backend *)backend; + + fake->exists_prefix_calls++; + + if ((error = search_object(&obj, fake, oid, len)) < 0) + return error; + + if (out) + git_oid_fromstr(out, obj->oid); + + return 0; +} + +static int fake_backend__read( + void **buffer_p, size_t *len_p, git_object_t *type_p, + git_odb_backend *backend, const git_oid *oid) +{ + const fake_object *obj; + fake_backend *fake; + int error; + + fake = (fake_backend *)backend; + + fake->read_calls++; + + if ((error = search_object(&obj, fake, oid, GIT_OID_HEXSZ)) < 0) + return error; + + *len_p = strlen(obj->content); + *buffer_p = git__strdup(obj->content); + *type_p = GIT_OBJECT_BLOB; + + return 0; +} + +static int fake_backend__read_header( + size_t *len_p, git_object_t *type_p, + git_odb_backend *backend, const git_oid *oid) +{ + const fake_object *obj; + fake_backend *fake; + int error; + + fake = (fake_backend *)backend; + + fake->read_header_calls++; + + if ((error = search_object(&obj, fake, oid, GIT_OID_HEXSZ)) < 0) + return error; + + *len_p = strlen(obj->content); + *type_p = GIT_OBJECT_BLOB; + + return 0; +} + +static int fake_backend__read_prefix( + git_oid *out_oid, void **buffer_p, size_t *len_p, git_object_t *type_p, + git_odb_backend *backend, const git_oid *short_oid, size_t len) +{ + const fake_object *obj; + fake_backend *fake; + int error; + + fake = (fake_backend *)backend; + + fake->read_prefix_calls++; + + if ((error = search_object(&obj, fake, short_oid, len)) < 0) + return error; + + git_oid_fromstr(out_oid, obj->oid); + *len_p = strlen(obj->content); + *buffer_p = git__strdup(obj->content); + *type_p = GIT_OBJECT_BLOB; + + return 0; +} + +static int fake_backend__refresh(git_odb_backend *backend) +{ + fake_backend *fake; + + fake = (fake_backend *)backend; + + fake->refresh_calls++; + + return 0; +} + + +static void fake_backend__free(git_odb_backend *_backend) +{ + fake_backend *backend; + + backend = (fake_backend *)_backend; + + git__free(backend); +} + +int build_fake_backend( + git_odb_backend **out, + const fake_object *objects, + bool support_refresh) +{ + fake_backend *backend; + + backend = git__calloc(1, sizeof(fake_backend)); + GIT_ERROR_CHECK_ALLOC(backend); + + backend->parent.version = GIT_ODB_BACKEND_VERSION; + + backend->objects = objects; + + backend->parent.read = fake_backend__read; + backend->parent.read_prefix = fake_backend__read_prefix; + backend->parent.read_header = fake_backend__read_header; + backend->parent.refresh = support_refresh ? fake_backend__refresh : NULL; + backend->parent.exists = fake_backend__exists; + backend->parent.exists_prefix = fake_backend__exists_prefix; + backend->parent.free = &fake_backend__free; + + *out = (git_odb_backend *)backend; + + return 0; +} diff --git a/tests/libgit2/odb/backend/backend_helpers.h b/tests/libgit2/odb/backend/backend_helpers.h new file mode 100644 index 000000000..32d7a8bf0 --- /dev/null +++ b/tests/libgit2/odb/backend/backend_helpers.h @@ -0,0 +1,24 @@ +#include "git2/sys/odb_backend.h" + +typedef struct { + const char *oid; + const char *content; +} fake_object; + +typedef struct { + git_odb_backend parent; + + int exists_calls; + int exists_prefix_calls; + int read_calls; + int read_header_calls; + int read_prefix_calls; + int refresh_calls; + + const fake_object *objects; +} fake_backend; + +int build_fake_backend( + git_odb_backend **out, + const fake_object *objects, + bool support_refresh); diff --git a/tests/libgit2/odb/backend/mempack.c b/tests/libgit2/odb/backend/mempack.c new file mode 100644 index 000000000..2eeed51aa --- /dev/null +++ b/tests/libgit2/odb/backend/mempack.c @@ -0,0 +1,60 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "backend_helpers.h" +#include "git2/sys/mempack.h" + +static git_odb *_odb; +static git_oid _oid; +static git_odb_object *_obj; +static git_repository *_repo; + +void test_odb_backend_mempack__initialize(void) +{ + git_odb_backend *backend; + + cl_git_pass(git_mempack_new(&backend)); + cl_git_pass(git_odb_new(&_odb)); + cl_git_pass(git_odb_add_backend(_odb, backend, 10)); + cl_git_pass(git_repository_wrap_odb(&_repo, _odb)); +} + +void test_odb_backend_mempack__cleanup(void) +{ + git_odb_object_free(_obj); + git_odb_free(_odb); + git_repository_free(_repo); +} + +void test_odb_backend_mempack__write_succeeds(void) +{ + const char *data = "data"; + cl_git_pass(git_odb_write(&_oid, _odb, data, strlen(data) + 1, GIT_OBJECT_BLOB)); + cl_git_pass(git_odb_read(&_obj, _odb, &_oid)); +} + +void test_odb_backend_mempack__read_of_missing_object_fails(void) +{ + cl_git_pass(git_oid_fromstr(&_oid, "f6ea0495187600e7b2288c8ac19c5886383a4633")); + cl_git_fail_with(GIT_ENOTFOUND, git_odb_read(&_obj, _odb, &_oid)); +} + +void test_odb_backend_mempack__exists_of_missing_object_fails(void) +{ + cl_git_pass(git_oid_fromstr(&_oid, "f6ea0495187600e7b2288c8ac19c5886383a4633")); + cl_assert(git_odb_exists(_odb, &_oid) == 0); +} + +void test_odb_backend_mempack__exists_with_existing_objects_succeeds(void) +{ + const char *data = "data"; + cl_git_pass(git_odb_write(&_oid, _odb, data, strlen(data) + 1, GIT_OBJECT_BLOB)); + cl_assert(git_odb_exists(_odb, &_oid) == 1); +} + +void test_odb_backend_mempack__blob_create_from_buffer_succeeds(void) +{ + const char *data = "data"; + + cl_git_pass(git_blob_create_from_buffer(&_oid, _repo, data, strlen(data) + 1)); + cl_assert(git_odb_exists(_odb, &_oid) == 1); +} diff --git a/tests/libgit2/odb/backend/multiple.c b/tests/libgit2/odb/backend/multiple.c new file mode 100644 index 000000000..5f1eacd52 --- /dev/null +++ b/tests/libgit2/odb/backend/multiple.c @@ -0,0 +1,121 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "backend_helpers.h" + +#define EXISTING_HASH "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" + +static git_repository *_repo; +static git_odb_object *_obj; +static fake_backend *_fake_empty; +static fake_backend *_fake_filled; + +static git_oid _existing_oid; + +static const fake_object _objects_filled[] = { + { EXISTING_HASH, "" }, + { NULL, NULL } +}; + +static const fake_object _objects_empty[] = { + { NULL, NULL } +}; + +void test_odb_backend_multiple__initialize(void) +{ + git_odb_backend *backend; + + git_oid_fromstr(&_existing_oid, EXISTING_HASH); + + _obj = NULL; + _repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(build_fake_backend(&backend, _objects_filled, false)); + _fake_filled = (fake_backend *)backend; + + cl_git_pass(build_fake_backend(&backend, _objects_empty, false)); + _fake_empty = (fake_backend *)backend; +} + +void test_odb_backend_multiple__cleanup(void) +{ + git_odb_object_free(_obj); + cl_git_sandbox_cleanup(); +} + +void test_odb_backend_multiple__read_with_empty_first_succeeds(void) +{ + git_odb *odb; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_filled, 10)); + cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_empty, 50)); + + cl_git_pass(git_odb_read(&_obj, odb, &_existing_oid)); + + cl_assert_equal_i(1, _fake_filled->read_calls); + cl_assert_equal_i(1, _fake_empty->read_calls); +} + +void test_odb_backend_multiple__read_with_first_matching_stops(void) +{ + git_odb *odb; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_empty, 10)); + cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_filled, 50)); + + cl_git_pass(git_odb_read(&_obj, odb, &_existing_oid)); + + cl_assert_equal_i(1, _fake_filled->read_calls); + cl_assert_equal_i(0, _fake_empty->read_calls); +} + +void test_odb_backend_multiple__read_prefix_with_first_empty_succeeds(void) +{ + git_odb *odb; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_filled, 10)); + cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_empty, 50)); + + cl_git_pass(git_odb_read_prefix(&_obj, odb, &_existing_oid, 7)); + + cl_assert_equal_i(1, _fake_filled->read_prefix_calls); + cl_assert_equal_i(1, _fake_empty->read_prefix_calls); +} + +void test_odb_backend_multiple__read_prefix_with_first_matching_reads_both(void) +{ + git_odb *odb; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_empty, -10)); + cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_filled, 50)); + + cl_git_pass(git_odb_read_prefix(&_obj, odb, &_existing_oid, 7)); + + cl_assert_equal_i(1, _fake_filled->read_prefix_calls); + cl_assert_equal_i(1, _fake_empty->read_prefix_calls); +} + +void test_odb_backend_multiple__read_prefix_with_first_matching_succeeds_without_hash_verification(void) +{ + git_odb *odb; + + git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0); + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_empty, -10)); + cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_filled, 50)); + + cl_git_pass(git_odb_read_prefix(&_obj, odb, &_existing_oid, 7)); + + /* + * Both backends should be checked as we have to check + * for collisions + */ + cl_assert_equal_i(1, _fake_filled->read_prefix_calls); + cl_assert_equal_i(1, _fake_empty->read_prefix_calls); + + git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1); +} diff --git a/tests/libgit2/odb/backend/nobackend.c b/tests/libgit2/odb/backend/nobackend.c new file mode 100644 index 000000000..7484d423b --- /dev/null +++ b/tests/libgit2/odb/backend/nobackend.c @@ -0,0 +1,46 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "git2/sys/repository.h" + +static git_repository *_repo; + +void test_odb_backend_nobackend__initialize(void) +{ + git_config *config; + git_odb *odb; + git_refdb *refdb; + + cl_git_pass(git_repository_new(&_repo)); + cl_git_pass(git_config_new(&config)); + cl_git_pass(git_odb_new(&odb)); + cl_git_pass(git_refdb_new(&refdb, _repo)); + + git_repository_set_config(_repo, config); + git_repository_set_odb(_repo, odb); + git_repository_set_refdb(_repo, refdb); + + /* The set increases the refcount and we don't want them anymore */ + git_config_free(config); + git_odb_free(odb); + git_refdb_free(refdb); +} + +void test_odb_backend_nobackend__cleanup(void) +{ + git_repository_free(_repo); +} + +void test_odb_backend_nobackend__write_fails_gracefully(void) +{ + git_oid id; + git_odb *odb; + const git_error *err; + + git_repository_odb(&odb, _repo); + cl_git_fail(git_odb_write(&id, odb, "Hello world!\n", 13, GIT_OBJECT_BLOB)); + + err = git_error_last(); + cl_assert_equal_s(err->message, "cannot write object - unsupported in the loaded odb backends"); + + git_odb_free(odb); +} diff --git a/tests/libgit2/odb/backend/nonrefreshing.c b/tests/libgit2/odb/backend/nonrefreshing.c new file mode 100644 index 000000000..2db10efbc --- /dev/null +++ b/tests/libgit2/odb/backend/nonrefreshing.c @@ -0,0 +1,147 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "backend_helpers.h" + +static git_repository *_repo; +static fake_backend *_fake; + +#define NONEXISTING_HASH "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" +#define EXISTING_HASH "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" + +static const fake_object _objects[] = { + { EXISTING_HASH, "" }, + { NULL, NULL } +}; + +static git_oid _nonexisting_oid; +static git_oid _existing_oid; + +static void setup_repository_and_backend(void) +{ + git_odb *odb = NULL; + git_odb_backend *backend = NULL; + + _repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(build_fake_backend(&backend, _objects, false)); + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_git_pass(git_odb_add_backend(odb, backend, 10)); + + _fake = (fake_backend *)backend; +} + +void test_odb_backend_nonrefreshing__initialize(void) +{ + git_oid_fromstr(&_nonexisting_oid, NONEXISTING_HASH); + git_oid_fromstr(&_existing_oid, EXISTING_HASH); + setup_repository_and_backend(); +} + +void test_odb_backend_nonrefreshing__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_odb_backend_nonrefreshing__exists_is_invoked_once_on_failure(void) +{ + git_odb *odb; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_assert_equal_b(false, git_odb_exists(odb, &_nonexisting_oid)); + + cl_assert_equal_i(1, _fake->exists_calls); +} + +void test_odb_backend_nonrefreshing__read_is_invoked_once_on_failure(void) +{ + git_object *obj; + + cl_git_fail_with( + git_object_lookup(&obj, _repo, &_nonexisting_oid, GIT_OBJECT_ANY), + GIT_ENOTFOUND); + + cl_assert_equal_i(1, _fake->read_calls); +} + +void test_odb_backend_nonrefreshing__readprefix_is_invoked_once_on_failure(void) +{ + git_object *obj; + + cl_git_fail_with( + git_object_lookup_prefix(&obj, _repo, &_nonexisting_oid, 7, GIT_OBJECT_ANY), + GIT_ENOTFOUND); + + cl_assert_equal_i(1, _fake->read_prefix_calls); +} + +void test_odb_backend_nonrefreshing__readheader_is_invoked_once_on_failure(void) +{ + git_odb *odb; + size_t len; + git_object_t type; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + + cl_git_fail_with( + git_odb_read_header(&len, &type, odb, &_nonexisting_oid), + GIT_ENOTFOUND); + + cl_assert_equal_i(1, _fake->read_header_calls); +} + +void test_odb_backend_nonrefreshing__exists_is_invoked_once_on_success(void) +{ + git_odb *odb; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_assert_equal_b(true, git_odb_exists(odb, &_existing_oid)); + + cl_assert_equal_i(1, _fake->exists_calls); +} + +void test_odb_backend_nonrefreshing__read_is_invoked_once_on_success(void) +{ + git_object *obj; + + cl_git_pass(git_object_lookup(&obj, _repo, &_existing_oid, GIT_OBJECT_ANY)); + + cl_assert_equal_i(1, _fake->read_calls); + + git_object_free(obj); +} + +void test_odb_backend_nonrefreshing__readprefix_is_invoked_once_on_success(void) +{ + git_object *obj; + + cl_git_pass(git_object_lookup_prefix(&obj, _repo, &_existing_oid, 7, GIT_OBJECT_ANY)); + + cl_assert_equal_i(1, _fake->read_prefix_calls); + + git_object_free(obj); +} + +void test_odb_backend_nonrefreshing__readheader_is_invoked_once_on_success(void) +{ + git_odb *odb; + size_t len; + git_object_t type; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + + cl_git_pass(git_odb_read_header(&len, &type, odb, &_existing_oid)); + + cl_assert_equal_i(1, _fake->read_header_calls); +} + +void test_odb_backend_nonrefreshing__read_is_invoked_once_when_revparsing_a_full_oid(void) +{ + git_object *obj; + + cl_git_fail_with( + git_revparse_single(&obj, _repo, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + GIT_ENOTFOUND); + + cl_assert_equal_i(1, _fake->read_calls); +} diff --git a/tests/libgit2/odb/backend/refreshing.c b/tests/libgit2/odb/backend/refreshing.c new file mode 100644 index 000000000..9e49298a8 --- /dev/null +++ b/tests/libgit2/odb/backend/refreshing.c @@ -0,0 +1,176 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "backend_helpers.h" + +static git_repository *_repo; +static fake_backend *_fake; + +#define NONEXISTING_HASH "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" +#define EXISTING_HASH "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" + +static const fake_object _objects[] = { + { EXISTING_HASH, "" }, + { NULL, NULL } +}; + +static git_oid _nonexisting_oid; +static git_oid _existing_oid; + +static void setup_repository_and_backend(void) +{ + git_odb *odb = NULL; + git_odb_backend *backend = NULL; + + _repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(build_fake_backend(&backend, _objects, true)); + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_git_pass(git_odb_add_backend(odb, backend, 10)); + + _fake = (fake_backend *)backend; +} + +void test_odb_backend_refreshing__initialize(void) +{ + git_oid_fromstr(&_nonexisting_oid, NONEXISTING_HASH); + git_oid_fromstr(&_existing_oid, EXISTING_HASH); + setup_repository_and_backend(); +} + +void test_odb_backend_refreshing__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_odb_backend_refreshing__exists_is_invoked_twice_on_failure(void) +{ + git_odb *odb; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_assert_equal_b(false, git_odb_exists(odb, &_nonexisting_oid)); + + cl_assert_equal_i(2, _fake->exists_calls); + cl_assert_equal_i(1, _fake->refresh_calls); +} + +void test_odb_backend_refreshing__read_is_invoked_twice_on_failure(void) +{ + git_object *obj; + + cl_git_fail_with( + git_object_lookup(&obj, _repo, &_nonexisting_oid, GIT_OBJECT_ANY), + GIT_ENOTFOUND); + + cl_assert_equal_i(2, _fake->read_calls); + cl_assert_equal_i(1, _fake->refresh_calls); +} + +void test_odb_backend_refreshing__readprefix_is_invoked_twice_on_failure(void) +{ + git_object *obj; + + cl_git_fail_with( + git_object_lookup_prefix(&obj, _repo, &_nonexisting_oid, 7, GIT_OBJECT_ANY), + GIT_ENOTFOUND); + + cl_assert_equal_i(2, _fake->read_prefix_calls); + cl_assert_equal_i(1, _fake->refresh_calls); +} + +void test_odb_backend_refreshing__readheader_is_invoked_twice_on_failure(void) +{ + git_odb *odb; + size_t len; + git_object_t type; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + + cl_git_fail_with( + git_odb_read_header(&len, &type, odb, &_nonexisting_oid), + GIT_ENOTFOUND); + + cl_assert_equal_i(2, _fake->read_header_calls); + cl_assert_equal_i(1, _fake->refresh_calls); +} + +void test_odb_backend_refreshing__exists_is_invoked_once_on_success(void) +{ + git_odb *odb; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_assert_equal_b(true, git_odb_exists(odb, &_existing_oid)); + + cl_assert_equal_i(1, _fake->exists_calls); + cl_assert_equal_i(0, _fake->refresh_calls); +} + +void test_odb_backend_refreshing__read_is_invoked_once_on_success(void) +{ + git_object *obj; + + cl_git_pass(git_object_lookup(&obj, _repo, &_existing_oid, GIT_OBJECT_ANY)); + + cl_assert_equal_i(1, _fake->read_calls); + cl_assert_equal_i(0, _fake->refresh_calls); + + git_object_free(obj); +} + +void test_odb_backend_refreshing__readprefix_is_invoked_once_on_success(void) +{ + git_object *obj; + + cl_git_pass(git_object_lookup_prefix(&obj, _repo, &_existing_oid, 7, GIT_OBJECT_ANY)); + + cl_assert_equal_i(1, _fake->read_prefix_calls); + cl_assert_equal_i(0, _fake->refresh_calls); + + git_object_free(obj); +} + +void test_odb_backend_refreshing__readheader_is_invoked_once_on_success(void) +{ + git_odb *odb; + size_t len; + git_object_t type; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + + cl_git_pass(git_odb_read_header(&len, &type, odb, &_existing_oid)); + + cl_assert_equal_i(1, _fake->read_header_calls); + cl_assert_equal_i(0, _fake->refresh_calls); +} + +void test_odb_backend_refreshing__read_is_invoked_twice_when_revparsing_a_full_oid(void) +{ + git_object *obj; + + cl_git_fail_with( + git_revparse_single(&obj, _repo, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + GIT_ENOTFOUND); + + cl_assert_equal_i(2, _fake->read_calls); + cl_assert_equal_i(1, _fake->refresh_calls); +} + +void test_odb_backend_refreshing__refresh_is_invoked(void) +{ + git_odb *odb; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_assert_equal_i(0, git_odb_refresh(odb)); + + cl_assert_equal_i(1, _fake->refresh_calls); +} + +void test_odb_backend_refreshing__refresh_suppressed_with_no_refresh(void) +{ + git_odb *odb; + + cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); + cl_assert_equal_b(false, git_odb_exists_ext(odb, &_nonexisting_oid, GIT_ODB_LOOKUP_NO_REFRESH)); + + cl_assert_equal_i(0, _fake->refresh_calls); +} diff --git a/tests/libgit2/odb/backend/simple.c b/tests/libgit2/odb/backend/simple.c new file mode 100644 index 000000000..6c9293ac0 --- /dev/null +++ b/tests/libgit2/odb/backend/simple.c @@ -0,0 +1,250 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "backend_helpers.h" + +#define EMPTY_HASH "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" + +static git_repository *_repo; +static git_odb *_odb; +static git_odb_object *_obj; +static git_oid _oid; + +static void setup_backend(const fake_object *objs) +{ + git_odb_backend *backend; + + cl_git_pass(build_fake_backend(&backend, objs, false)); + + cl_git_pass(git_repository_odb__weakptr(&_odb, _repo)); + cl_git_pass(git_odb_add_backend(_odb, backend, 10)); +} + +static void assert_object_contains(git_odb_object *obj, const char *expected) +{ + const char *actual = (const char *) git_odb_object_data(obj); + + cl_assert_equal_s(actual, expected); +} + +void test_odb_backend_simple__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); + _odb = NULL; + _obj = NULL; +} + +void test_odb_backend_simple__cleanup(void) +{ + git_odb_object_free(_obj); + cl_git_sandbox_cleanup(); + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1)); +} + +void test_odb_backend_simple__read_of_object_succeeds(void) +{ + const fake_object objs[] = { + { "f6ea0495187600e7b2288c8ac19c5886383a4632", "foobar" }, + { NULL, NULL } + }; + + setup_backend(objs); + + cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); + cl_git_pass(git_odb_read(&_obj, _odb, &_oid)); + + assert_object_contains(_obj, objs[0].content); +} + +void test_odb_backend_simple__read_of_nonexisting_object_fails(void) +{ + const fake_object objs[] = { + { "f6ea0495187600e7b2288c8ac19c5886383a4632", "foobar" }, + { NULL, NULL } + }; + + setup_backend(objs); + + cl_git_pass(git_oid_fromstr(&_oid, "f6ea0495187600e7b2288c8ac19c5886383a4633")); + cl_git_fail_with(GIT_ENOTFOUND, git_odb_read(&_obj, _odb, &_oid)); +} + +void test_odb_backend_simple__read_with_hash_mismatch_fails(void) +{ + const fake_object objs[] = { + { "1234567890123456789012345678901234567890", "nonmatching content" }, + { NULL, NULL } + }; + + setup_backend(objs); + + cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); + cl_git_fail_with(GIT_EMISMATCH, git_odb_read(&_obj, _odb, &_oid)); +} + +void test_odb_backend_simple__read_with_hash_mismatch_succeeds_without_verification(void) +{ + const fake_object objs[] = { + { "1234567890123456789012345678901234567890", "nonmatching content" }, + { NULL, NULL } + }; + + setup_backend(objs); + cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)); + cl_git_pass(git_odb_read(&_obj, _odb, &_oid)); + + assert_object_contains(_obj, objs[0].content); +} + +void test_odb_backend_simple__read_prefix_succeeds(void) +{ + const fake_object objs[] = { + { "f6ea0495187600e7b2288c8ac19c5886383a4632", "foobar" }, + { NULL, NULL } + }; + + setup_backend(objs); + + cl_git_pass(git_oid_fromstr(&_oid, "f6ea0495187600e7b2288c8ac19c5886383a4632")); + cl_git_pass(git_odb_read(&_obj, _odb, &_oid)); + + assert_object_contains(_obj, objs[0].content); +} + +void test_odb_backend_simple__read_prefix_of_nonexisting_object_fails(void) +{ + const fake_object objs[] = { + { "f6ea0495187600e7b2288c8ac19c5886383a4632", "foobar" }, + { NULL, NULL } + }; + char *hash = "f6ea0495187600e8"; + + setup_backend(objs); + + cl_git_pass(git_oid_fromstrn(&_oid, hash, strlen(hash))); + cl_git_fail_with(GIT_ENOTFOUND, git_odb_read(&_obj, _odb, &_oid)); +} + +void test_odb_backend_simple__read_with_ambiguous_prefix_fails(void) +{ + const fake_object objs[] = { + { "1234567890111111111111111111111111111111", "first content" }, + { "1234567890222222222222222222222222222222", "second content" }, + { NULL, NULL } + }; + + setup_backend(objs); + + cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); + cl_git_fail_with(GIT_EAMBIGUOUS, git_odb_read_prefix(&_obj, _odb, &_oid, 7)); +} + +void test_odb_backend_simple__read_with_highly_ambiguous_prefix(void) +{ + const fake_object objs[] = { + { "1234567890111111111111111111111111111111", "first content" }, + { "1234567890111111111111111111111111111112", "second content" }, + { NULL, NULL } + }; + + setup_backend(objs); + + cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)); + cl_git_fail_with(GIT_EAMBIGUOUS, git_odb_read_prefix(&_obj, _odb, &_oid, 39)); + cl_git_pass(git_odb_read_prefix(&_obj, _odb, &_oid, 40)); + assert_object_contains(_obj, objs[0].content); +} + +void test_odb_backend_simple__exists_succeeds(void) +{ + const fake_object objs[] = { + { "f6ea0495187600e7b2288c8ac19c5886383a4632", "foobar" }, + { NULL, NULL } + }; + + setup_backend(objs); + + cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); + cl_assert(git_odb_exists(_odb, &_oid)); +} + +void test_odb_backend_simple__exists_fails_for_nonexisting_object(void) +{ + const fake_object objs[] = { + { "f6ea0495187600e7b2288c8ac19c5886383a4632", "foobar" }, + { NULL, NULL } + }; + + setup_backend(objs); + + cl_git_pass(git_oid_fromstr(&_oid, "f6ea0495187600e7b2288c8ac19c5886383a4633")); + cl_assert(git_odb_exists(_odb, &_oid) == 0); +} + +void test_odb_backend_simple__exists_prefix_succeeds(void) +{ + const fake_object objs[] = { + { "1234567890111111111111111111111111111111", "first content" }, + { "1234567890222222222222222222222222222222", "second content" }, + { NULL, NULL } + }; + git_oid found; + + setup_backend(objs); + + cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); + cl_git_pass(git_odb_exists_prefix(&found, _odb, &_oid, 12)); + cl_assert(git_oid_equal(&found, &_oid)); +} + +void test_odb_backend_simple__exists_with_ambiguous_prefix_fails(void) +{ + const fake_object objs[] = { + { "1234567890111111111111111111111111111111", "first content" }, + { "1234567890222222222222222222222222222222", "second content" }, + { NULL, NULL } + }; + + setup_backend(objs); + + cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); + cl_git_fail_with(GIT_EAMBIGUOUS, git_odb_exists_prefix(NULL, _odb, &_oid, 7)); +} + +void test_odb_backend_simple__exists_with_highly_ambiguous_prefix(void) +{ + const fake_object objs[] = { + { "1234567890111111111111111111111111111111", "first content" }, + { "1234567890111111111111111111111111111112", "second content" }, + { NULL, NULL } + }; + git_oid found; + + setup_backend(objs); + + cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)); + cl_git_fail_with(GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &_oid, 39)); + cl_git_pass(git_odb_exists_prefix(&found, _odb, &_oid, 40)); + cl_assert(git_oid_equal(&found, &_oid)); +} + +void test_odb_backend_simple__null_oid_is_ignored(void) +{ + const fake_object objs[] = { + { "0000000000000000000000000000000000000000", "null oid content" }, + { NULL, NULL } + }; + git_oid null_oid = {{0}}; + git_odb_object *obj; + + setup_backend(objs); + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)); + cl_assert(!git_odb_exists(_odb, &null_oid)); + + cl_git_fail_with(GIT_ENOTFOUND, git_odb_read(&obj, _odb, &null_oid)); + cl_assert(git_error_last() && strstr(git_error_last()->message, "null OID")); +} diff --git a/tests/libgit2/odb/emptyobjects.c b/tests/libgit2/odb/emptyobjects.c new file mode 100644 index 000000000..e3ec62d3f --- /dev/null +++ b/tests/libgit2/odb/emptyobjects.c @@ -0,0 +1,58 @@ +#include "clar_libgit2.h" +#include "odb.h" +#include "filebuf.h" + +#define TEST_REPO_PATH "redundant.git" + +git_repository *g_repo; +git_odb *g_odb; + +void test_odb_emptyobjects__initialize(void) +{ + g_repo = cl_git_sandbox_init(TEST_REPO_PATH); + cl_git_pass(git_repository_odb(&g_odb, g_repo)); +} + +void test_odb_emptyobjects__cleanup(void) +{ + git_odb_free(g_odb); + cl_git_sandbox_cleanup(); +} + +void test_odb_emptyobjects__blob_notfound(void) +{ + git_oid id, written_id; + git_blob *blob; + + cl_git_pass(git_oid_fromstr(&id, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")); + cl_git_fail_with(GIT_ENOTFOUND, git_blob_lookup(&blob, g_repo, &id)); + + cl_git_pass(git_odb_write(&written_id, g_odb, "", 0, GIT_OBJECT_BLOB)); + cl_assert(git_fs_path_exists(TEST_REPO_PATH "/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391")); +} + +void test_odb_emptyobjects__read_tree(void) +{ + git_oid id; + git_tree *tree; + + cl_git_pass(git_oid_fromstr(&id, "4b825dc642cb6eb9a060e54bf8d69288fbee4904")); + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + cl_assert_equal_i(GIT_OBJECT_TREE, git_object_type((git_object *) tree)); + cl_assert_equal_i(0, git_tree_entrycount(tree)); + cl_assert_equal_p(NULL, git_tree_entry_byname(tree, "foo")); + git_tree_free(tree); +} + +void test_odb_emptyobjects__read_tree_odb(void) +{ + git_oid id; + git_odb_object *tree_odb; + + cl_git_pass(git_oid_fromstr(&id, "4b825dc642cb6eb9a060e54bf8d69288fbee4904")); + cl_git_pass(git_odb_read(&tree_odb, g_odb, &id)); + cl_assert(git_odb_object_data(tree_odb)); + cl_assert_equal_s("", git_odb_object_data(tree_odb)); + cl_assert_equal_i(0, git_odb_object_size(tree_odb)); + git_odb_object_free(tree_odb); +} diff --git a/tests/libgit2/odb/foreach.c b/tests/libgit2/odb/foreach.c new file mode 100644 index 000000000..c2a448363 --- /dev/null +++ b/tests/libgit2/odb/foreach.c @@ -0,0 +1,140 @@ +#include "clar_libgit2.h" +#include "odb.h" +#include "git2/odb_backend.h" +#include "pack.h" + +static git_odb *_odb; +static git_repository *_repo; + +void test_odb_foreach__cleanup(void) +{ + git_odb_free(_odb); + git_repository_free(_repo); + + _odb = NULL; + _repo = NULL; +} + +static int foreach_cb(const git_oid *oid, void *data) +{ + int *nobj = data; + (*nobj)++; + + GIT_UNUSED(oid); + + return 0; +} + +/* + * $ git --git-dir tests/resources/testrepo.git count-objects --verbose + * count: 60 + * size: 240 + * in-pack: 1640 + * packs: 3 + * size-pack: 425 + * prune-packable: 0 + * garbage: 0 + */ +void test_odb_foreach__foreach(void) +{ + int nobj = 0; + + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + git_repository_odb(&_odb, _repo); + + cl_git_pass(git_odb_foreach(_odb, foreach_cb, &nobj)); + cl_assert_equal_i(60 + 1640, nobj); /* count + in-pack */ +} + +void test_odb_foreach__one_pack(void) +{ + git_odb_backend *backend = NULL; + int nobj = 0; + + cl_git_pass(git_odb_new(&_odb)); + cl_git_pass(git_odb_backend_one_pack(&backend, cl_fixture("testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx"))); + cl_git_pass(git_odb_add_backend(_odb, backend, 1)); + _repo = NULL; + + cl_git_pass(git_odb_foreach(_odb, foreach_cb, &nobj)); + cl_assert(nobj == 1628); +} + +static int foreach_stop_cb(const git_oid *oid, void *data) +{ + int *nobj = data; + (*nobj)++; + + GIT_UNUSED(oid); + + return (*nobj == 1000) ? -321 : 0; +} + +static int foreach_stop_first_cb(const git_oid *oid, void *data) +{ + int *nobj = data; + (*nobj)++; + + GIT_UNUSED(oid); + + return -123; +} + +static int foreach_stop_cb_positive_ret(const git_oid *oid, void *data) +{ + int *nobj = data; + (*nobj)++; + + GIT_UNUSED(oid); + + return (*nobj == 1000) ? 321 : 0; +} + +void test_odb_foreach__interrupt_foreach(void) +{ + int nobj = 0; + git_oid id; + + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + git_repository_odb(&_odb, _repo); + + cl_assert_equal_i(-321, git_odb_foreach(_odb, foreach_stop_cb, &nobj)); + cl_assert(nobj == 1000); + + nobj = 0; + + cl_assert_equal_i(321, git_odb_foreach(_odb, foreach_stop_cb_positive_ret, &nobj)); + cl_assert(nobj == 1000); + + git_odb_free(_odb); + git_repository_free(_repo); + + cl_git_pass(git_repository_init(&_repo, "onlyloose.git", true)); + git_repository_odb(&_odb, _repo); + + cl_git_pass(git_odb_write(&id, _odb, "", 0, GIT_OBJECT_BLOB)); + cl_assert_equal_i(-123, git_odb_foreach(_odb, foreach_stop_first_cb, &nobj)); +} + +void test_odb_foreach__files_in_objects_dir(void) +{ + git_repository *repo; + git_odb *odb; + git_str buf = GIT_STR_INIT; + int nobj = 0; + + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&repo, "testrepo.git")); + + cl_git_pass(git_str_joinpath(&buf, git_repository_path(repo), "objects/somefile")); + cl_git_mkfile(buf.ptr, ""); + git_str_dispose(&buf); + + cl_git_pass(git_repository_odb(&odb, repo)); + cl_git_pass(git_odb_foreach(odb, foreach_cb, &nobj)); + cl_assert_equal_i(60 + 1640, nobj); /* count + in-pack */ + + git_odb_free(odb); + git_repository_free(repo); + cl_fixture_cleanup("testrepo.git"); +} diff --git a/tests/libgit2/odb/freshen.c b/tests/libgit2/odb/freshen.c new file mode 100644 index 000000000..2396e3774 --- /dev/null +++ b/tests/libgit2/odb/freshen.c @@ -0,0 +1,183 @@ +#include "clar_libgit2.h" +#include "odb.h" +#include "posix.h" + +static git_repository *repo; +static git_odb *odb; + +void test_odb_freshen__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_odb(&odb, repo)); +} + +void test_odb_freshen__cleanup(void) +{ + git_odb_free(odb); + cl_git_sandbox_cleanup(); +} + +static void set_time_wayback(struct stat *out, const char *fn) +{ + git_str fullpath = GIT_STR_INIT; + struct p_timeval old[2]; + + old[0].tv_sec = 1234567890; + old[0].tv_usec = 0; + old[1].tv_sec = 1234567890; + old[1].tv_usec = 0; + + git_str_joinpath(&fullpath, "testrepo.git/objects", fn); + + cl_must_pass(p_utimes(git_str_cstr(&fullpath), old)); + cl_must_pass(p_lstat(git_str_cstr(&fullpath), out)); + git_str_dispose(&fullpath); +} + +#define LOOSE_STR "my new file\n" +#define LOOSE_BLOB_ID "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd" +#define LOOSE_BLOB_FN "a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd" + +void test_odb_freshen__loose_blob(void) +{ + git_oid expected_id, id; + struct stat before, after; + + cl_git_pass(git_oid_fromstr(&expected_id, LOOSE_BLOB_ID)); + set_time_wayback(&before, LOOSE_BLOB_FN); + + /* make sure we freshen a blob */ + cl_git_pass(git_blob_create_from_buffer(&id, repo, LOOSE_STR, CONST_STRLEN(LOOSE_STR))); + cl_assert_equal_oid(&expected_id, &id); + cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_BLOB_FN, &after)); + + cl_assert(before.st_atime < after.st_atime); + cl_assert(before.st_mtime < after.st_mtime); +} + +#define UNIQUE_STR "doesnt exist in the odb yet\n" +#define UNIQUE_BLOB_ID "78a87d0b8878c5953b9a63015ff4e22a3d898826" +#define UNIQUE_BLOB_FN "78/a87d0b8878c5953b9a63015ff4e22a3d898826" + +void test_odb_freshen__readonly_object(void) +{ + git_oid expected_id, id; + struct stat before, after; + + cl_git_pass(git_oid_fromstr(&expected_id, UNIQUE_BLOB_ID)); + + cl_git_pass(git_blob_create_from_buffer(&id, repo, UNIQUE_STR, CONST_STRLEN(UNIQUE_STR))); + cl_assert_equal_oid(&expected_id, &id); + + set_time_wayback(&before, UNIQUE_BLOB_FN); + cl_assert((before.st_mode & S_IWUSR) == 0); + + cl_git_pass(git_blob_create_from_buffer(&id, repo, UNIQUE_STR, CONST_STRLEN(UNIQUE_STR))); + cl_assert_equal_oid(&expected_id, &id); + cl_must_pass(p_lstat("testrepo.git/objects/" UNIQUE_BLOB_FN, &after)); + + cl_assert(before.st_atime < after.st_atime); + cl_assert(before.st_mtime < after.st_mtime); +} + +#define LOOSE_TREE_ID "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162" +#define LOOSE_TREE_FN "94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162" + +void test_odb_freshen__loose_tree(void) +{ + git_oid expected_id, id; + git_tree *tree; + struct stat before, after; + + cl_git_pass(git_oid_fromstr(&expected_id, LOOSE_TREE_ID)); + set_time_wayback(&before, LOOSE_TREE_FN); + + cl_git_pass(git_tree_lookup(&tree, repo, &expected_id)); + cl_git_pass(git_tree_create_updated(&id, repo, tree, 0, NULL)); + + /* make sure we freshen a tree */ + cl_assert_equal_oid(&expected_id, &id); + cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_TREE_FN, &after)); + + cl_assert(before.st_atime < after.st_atime); + cl_assert(before.st_mtime < after.st_mtime); + + git_tree_free(tree); +} + +void test_odb_freshen__tree_during_commit(void) +{ + git_oid tree_id, parent_id, commit_id; + git_tree *tree; + git_commit *parent; + git_signature *signature; + struct stat before, after; + + cl_git_pass(git_oid_fromstr(&tree_id, LOOSE_TREE_ID)); + cl_git_pass(git_tree_lookup(&tree, repo, &tree_id)); + set_time_wayback(&before, LOOSE_TREE_FN); + + cl_git_pass(git_oid_fromstr(&parent_id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); + cl_git_pass(git_commit_lookup(&parent, repo, &parent_id)); + + cl_git_pass(git_signature_new(&signature, + "Refresher", "refresher@example.com", 1488547083, 0)); + + cl_git_pass(git_commit_create(&commit_id, repo, NULL, + signature, signature, NULL, "New commit pointing to old tree", + tree, 1, (const git_commit **)&parent)); + + /* make sure we freshen the tree the commit points to */ + cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_TREE_FN, &after)); + cl_assert(before.st_atime < after.st_atime); + cl_assert(before.st_mtime < after.st_mtime); + + git_signature_free(signature); + git_commit_free(parent); + git_tree_free(tree); +} + +#define PACKED_STR "Testing a readme.txt\n" +#define PACKED_ID "6336846bd5c88d32f93ae57d846683e61ab5c530" +#define PACKED_FN "pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack" + +void test_odb_freshen__packed_object(void) +{ + git_oid expected_id, id; + struct stat before, after; + struct p_timeval old_times[2]; + + cl_git_pass(git_oid_fromstr(&expected_id, PACKED_ID)); + + old_times[0].tv_sec = 1234567890; + old_times[0].tv_usec = 0; + old_times[1].tv_sec = 1234567890; + old_times[1].tv_usec = 0; + + /* set time to way back */ + cl_must_pass(p_utimes("testrepo.git/objects/pack/" PACKED_FN, old_times)); + cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &before)); + + /* ensure that packfile is freshened */ + cl_git_pass(git_odb_write(&id, odb, PACKED_STR, + CONST_STRLEN(PACKED_STR), GIT_OBJECT_BLOB)); + cl_assert_equal_oid(&expected_id, &id); + cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &after)); + + cl_assert(before.st_atime < after.st_atime); + cl_assert(before.st_mtime < after.st_mtime); + + memcpy(&before, &after, sizeof(struct stat)); + + /* ensure that the pack file is not freshened again immediately */ + cl_git_pass(git_odb_write(&id, odb, PACKED_STR, + CONST_STRLEN(PACKED_STR), GIT_OBJECT_BLOB)); + cl_assert_equal_oid(&expected_id, &id); + cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &after)); + + cl_assert(before.st_atime == after.st_atime); + cl_assert(before.st_atime_nsec == after.st_atime_nsec); + cl_assert(before.st_mtime == after.st_mtime); + cl_assert(before.st_mtime_nsec == after.st_mtime_nsec); +} + diff --git a/tests/libgit2/odb/largefiles.c b/tests/libgit2/odb/largefiles.c new file mode 100644 index 000000000..acc786ee4 --- /dev/null +++ b/tests/libgit2/odb/largefiles.c @@ -0,0 +1,189 @@ +#include "clar_libgit2.h" +#include "git2/odb_backend.h" +#include "hash.h" +#include "odb.h" + +#define LARGEFILE_SIZE 5368709122 + +static git_repository *repo; +static git_odb *odb; + +void test_odb_largefiles__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_odb(&odb, repo)); +} + +void test_odb_largefiles__cleanup(void) +{ + git_odb_free(odb); + cl_git_sandbox_cleanup(); +} + +static void writefile(git_oid *oid) +{ + static git_odb_stream *stream; + git_str buf = GIT_STR_INIT; + size_t i; + + for (i = 0; i < 3041; i++) + cl_git_pass(git_str_puts(&buf, "Hello, world.\n")); + + cl_git_pass(git_odb_open_wstream(&stream, odb, LARGEFILE_SIZE, GIT_OBJECT_BLOB)); + for (i = 0; i < 126103; i++) + cl_git_pass(git_odb_stream_write(stream, buf.ptr, buf.size)); + + cl_git_pass(git_odb_stream_finalize_write(oid, stream)); + + git_odb_stream_free(stream); + git_str_dispose(&buf); +} + +void test_odb_largefiles__write_from_memory(void) +{ + git_oid expected, oid; + git_str buf = GIT_STR_INIT; + size_t i; + +#ifndef GIT_ARCH_64 + cl_skip(); +#endif + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || + !cl_is_env_set("GITTEST_INVASIVE_MEMORY") || + !cl_is_env_set("GITTEST_SLOW")) + cl_skip(); + + for (i = 0; i < (3041*126103); i++) + cl_git_pass(git_str_puts(&buf, "Hello, world.\n")); + + git_oid_fromstr(&expected, "3fb56989cca483b21ba7cb0a6edb229d10e1c26c"); + cl_git_pass(git_odb_write(&oid, odb, buf.ptr, buf.size, GIT_OBJECT_BLOB)); + + cl_assert_equal_oid(&expected, &oid); +} + +void test_odb_largefiles__streamwrite(void) +{ + git_oid expected, oid; + +#ifndef GIT_ARCH_64 + cl_skip(); +#endif + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || + !cl_is_env_set("GITTEST_SLOW")) + cl_skip(); + + git_oid_fromstr(&expected, "3fb56989cca483b21ba7cb0a6edb229d10e1c26c"); + writefile(&oid); + + cl_assert_equal_oid(&expected, &oid); +} + +void test_odb_largefiles__streamread(void) +{ + git_oid oid, read_oid; + git_odb_stream *stream; + char buf[10240]; + char hdr[64]; + size_t len, hdr_len, total = 0; + git_hash_ctx hash; + git_object_t type; + int ret; + +#ifndef GIT_ARCH_64 + cl_skip(); +#endif + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || + !cl_is_env_set("GITTEST_SLOW")) + cl_skip(); + + writefile(&oid); + + cl_git_pass(git_odb_open_rstream(&stream, &len, &type, odb, &oid)); + + cl_assert_equal_sz(LARGEFILE_SIZE, len); + cl_assert_equal_i(GIT_OBJECT_BLOB, type); + + cl_git_pass(git_hash_ctx_init(&hash, GIT_HASH_ALGORITHM_SHA1)); + cl_git_pass(git_odb__format_object_header(&hdr_len, hdr, sizeof(hdr), len, type)); + + cl_git_pass(git_hash_update(&hash, hdr, hdr_len)); + + while ((ret = git_odb_stream_read(stream, buf, 10240)) > 0) { + cl_git_pass(git_hash_update(&hash, buf, ret)); + total += ret; + } + + cl_assert_equal_sz(LARGEFILE_SIZE, total); + + git_hash_final(read_oid.id, &hash); + + cl_assert_equal_oid(&oid, &read_oid); + + git_hash_ctx_cleanup(&hash); + git_odb_stream_free(stream); +} + +void test_odb_largefiles__read_into_memory(void) +{ + git_oid oid; + git_odb_object *obj; + +#ifndef GIT_ARCH_64 + cl_skip(); +#endif + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || + !cl_is_env_set("GITTEST_INVASIVE_MEMORY") || + !cl_is_env_set("GITTEST_SLOW")) + cl_skip(); + + writefile(&oid); + cl_git_pass(git_odb_read(&obj, odb, &oid)); + + git_odb_object_free(obj); +} + +void test_odb_largefiles__read_into_memory_rejected_on_32bit(void) +{ + git_oid oid; + git_odb_object *obj = NULL; + +#ifdef GIT_ARCH_64 + cl_skip(); +#endif + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || + !cl_is_env_set("GITTEST_INVASIVE_MEMORY") || + !cl_is_env_set("GITTEST_SLOW")) + cl_skip(); + + writefile(&oid); + cl_git_fail(git_odb_read(&obj, odb, &oid)); + + git_odb_object_free(obj); +} + +void test_odb_largefiles__read_header(void) +{ + git_oid oid; + size_t len; + git_object_t type; + +#ifndef GIT_ARCH_64 + cl_skip(); +#endif + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || + !cl_is_env_set("GITTEST_SLOW")) + cl_skip(); + + writefile(&oid); + cl_git_pass(git_odb_read_header(&len, &type, odb, &oid)); + + cl_assert_equal_sz(LARGEFILE_SIZE, len); + cl_assert_equal_i(GIT_OBJECT_BLOB, type); +} diff --git a/tests/libgit2/odb/loose.c b/tests/libgit2/odb/loose.c new file mode 100644 index 000000000..fe013a78c --- /dev/null +++ b/tests/libgit2/odb/loose.c @@ -0,0 +1,291 @@ +#include "clar_libgit2.h" +#include "odb.h" +#include "git2/odb_backend.h" +#include "posix.h" +#include "loose_data.h" +#include "repository.h" + +#ifdef __ANDROID_API__ +# define S_IREAD S_IRUSR +# define S_IWRITE S_IWUSR +#endif + +static void write_object_files(object_data *d) +{ + int fd; + + if (p_mkdir(d->dir, GIT_OBJECT_DIR_MODE) < 0) + cl_assert(errno == EEXIST); + + cl_assert((fd = p_creat(d->file, S_IREAD | S_IWRITE)) >= 0); + cl_must_pass(p_write(fd, d->bytes, d->blen)); + + p_close(fd); +} + +static void cmp_objects(git_rawobj *o, object_data *d) +{ + cl_assert(o->type == git_object_string2type(d->type)); + cl_assert(o->len == d->dlen); + + if (o->len > 0) + cl_assert(memcmp(o->data, d->data, o->len) == 0); +} + +static void test_read_object(object_data *data) +{ + git_oid id; + git_odb_object *obj; + git_odb *odb; + git_rawobj tmp; + + write_object_files(data); + + cl_git_pass(git_odb_open(&odb, "test-objects")); + cl_git_pass(git_oid_fromstr(&id, data->id)); + cl_git_pass(git_odb_read(&obj, odb, &id)); + + tmp.data = obj->buffer; + tmp.len = obj->cached.size; + tmp.type = obj->cached.type; + + cmp_objects(&tmp, data); + + git_odb_object_free(obj); + git_odb_free(odb); +} + +static void test_read_header(object_data *data) +{ + git_oid id; + git_odb *odb; + size_t len; + git_object_t type; + + write_object_files(data); + + cl_git_pass(git_odb_open(&odb, "test-objects")); + cl_git_pass(git_oid_fromstr(&id, data->id)); + cl_git_pass(git_odb_read_header(&len, &type, odb, &id)); + + cl_assert_equal_sz(data->dlen, len); + cl_assert_equal_i(git_object_string2type(data->type), type); + + git_odb_free(odb); +} + +static void test_readstream_object(object_data *data, size_t blocksize) +{ + git_oid id; + git_odb *odb; + git_odb_stream *stream; + git_rawobj tmp; + char buf[2048], *ptr = buf; + size_t remain; + int ret; + + write_object_files(data); + + cl_git_pass(git_odb_open(&odb, "test-objects")); + cl_git_pass(git_oid_fromstr(&id, data->id)); + cl_git_pass(git_odb_open_rstream(&stream, &tmp.len, &tmp.type, odb, &id)); + + remain = tmp.len; + + while (remain) { + cl_assert((ret = git_odb_stream_read(stream, ptr, blocksize)) >= 0); + if (ret == 0) + break; + + cl_assert(remain >= (size_t)ret); + remain -= ret; + ptr += ret; + } + + cl_assert(remain == 0); + + tmp.data = buf; + + cmp_objects(&tmp, data); + + git_odb_stream_free(stream); + git_odb_free(odb); +} + +void test_odb_loose__initialize(void) +{ + p_fsync__cnt = 0; + cl_must_pass(p_mkdir("test-objects", GIT_OBJECT_DIR_MODE)); +} + +void test_odb_loose__cleanup(void) +{ + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 0)); + cl_fixture_cleanup("test-objects"); +} + +void test_odb_loose__exists(void) +{ + git_oid id, id2; + git_odb *odb; + + write_object_files(&one); + cl_git_pass(git_odb_open(&odb, "test-objects")); + + cl_git_pass(git_oid_fromstr(&id, one.id)); + cl_assert(git_odb_exists(odb, &id)); + + cl_git_pass(git_oid_fromstrp(&id, "8b137891")); + cl_git_pass(git_odb_exists_prefix(&id2, odb, &id, 8)); + cl_assert_equal_i(0, git_oid_streq(&id2, one.id)); + + /* Test for a missing object */ + cl_git_pass(git_oid_fromstr(&id, "8b137891791fe96927ad78e64b0aad7bded08baa")); + cl_assert(!git_odb_exists(odb, &id)); + + cl_git_pass(git_oid_fromstrp(&id, "8b13789a")); + cl_assert_equal_i(GIT_ENOTFOUND, git_odb_exists_prefix(&id2, odb, &id, 8)); + + git_odb_free(odb); +} + +void test_odb_loose__simple_reads(void) +{ + test_read_object(&commit); + test_read_object(&tree); + test_read_object(&tag); + test_read_object(&zero); + test_read_object(&one); + test_read_object(&two); + test_read_object(&some); +} + +void test_odb_loose__streaming_reads(void) +{ + size_t blocksizes[] = { 1, 2, 4, 16, 99, 1024, 123456789 }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(blocksizes); i++) { + test_readstream_object(&commit, blocksizes[i]); + test_readstream_object(&tree, blocksizes[i]); + test_readstream_object(&tag, blocksizes[i]); + test_readstream_object(&zero, blocksizes[i]); + test_readstream_object(&one, blocksizes[i]); + test_readstream_object(&two, blocksizes[i]); + test_readstream_object(&some, blocksizes[i]); + } +} + +void test_odb_loose__read_header(void) +{ + test_read_header(&commit); + test_read_header(&tree); + test_read_header(&tag); + test_read_header(&zero); + test_read_header(&one); + test_read_header(&two); + test_read_header(&some); +} + +static void test_write_object_permission( + mode_t dir_mode, mode_t file_mode, + mode_t expected_dir_mode, mode_t expected_file_mode) +{ + git_odb *odb; + git_odb_backend *backend; + git_oid oid; + struct stat statbuf; + mode_t mask, os_mask; + + /* Windows does not return group/user bits from stat, + * files are never executable. + */ +#ifdef GIT_WIN32 + os_mask = 0600; +#else + os_mask = 0777; +#endif + + mask = p_umask(0); + p_umask(mask); + + cl_git_pass(git_odb_new(&odb)); + cl_git_pass(git_odb_backend_loose(&backend, "test-objects", -1, 0, dir_mode, file_mode)); + cl_git_pass(git_odb_add_backend(odb, backend, 1)); + cl_git_pass(git_odb_write(&oid, odb, "Test data\n", 10, GIT_OBJECT_BLOB)); + + cl_git_pass(p_stat("test-objects/67", &statbuf)); + cl_assert_equal_i(statbuf.st_mode & os_mask, (expected_dir_mode & ~mask) & os_mask); + + cl_git_pass(p_stat("test-objects/67/b808feb36201507a77f85e6d898f0a2836e4a5", &statbuf)); + cl_assert_equal_i(statbuf.st_mode & os_mask, (expected_file_mode & ~mask) & os_mask); + + git_odb_free(odb); +} + +void test_odb_loose__permissions_standard(void) +{ + test_write_object_permission(0, 0, GIT_OBJECT_DIR_MODE, GIT_OBJECT_FILE_MODE); +} + +void test_odb_loose__permissions_readonly(void) +{ + test_write_object_permission(0777, 0444, 0777, 0444); +} + +void test_odb_loose__permissions_readwrite(void) +{ + test_write_object_permission(0777, 0666, 0777, 0666); +} + +static void write_object_to_loose_odb(int fsync) +{ + git_odb *odb; + git_odb_backend *backend; + git_oid oid; + + cl_git_pass(git_odb_new(&odb)); + cl_git_pass(git_odb_backend_loose(&backend, "test-objects", -1, fsync, 0777, 0666)); + cl_git_pass(git_odb_add_backend(odb, backend, 1)); + cl_git_pass(git_odb_write(&oid, odb, "Test data\n", 10, GIT_OBJECT_BLOB)); + git_odb_free(odb); +} + +void test_odb_loose__does_not_fsync_by_default(void) +{ + write_object_to_loose_odb(0); + cl_assert_equal_sz(0, p_fsync__cnt); +} + +void test_odb_loose__fsync_obeys_odb_option(void) +{ + write_object_to_loose_odb(1); + cl_assert(p_fsync__cnt > 0); +} + +void test_odb_loose__fsync_obeys_global_setting(void) +{ + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 1)); + write_object_to_loose_odb(0); + cl_assert(p_fsync__cnt > 0); +} + +void test_odb_loose__fsync_obeys_repo_setting(void) +{ + git_repository *repo; + git_odb *odb; + git_oid oid; + + cl_git_pass(git_repository_init(&repo, "test-objects", 1)); + cl_git_pass(git_repository_odb__weakptr(&odb, repo)); + cl_git_pass(git_odb_write(&oid, odb, "No fsync here\n", 14, GIT_OBJECT_BLOB)); + cl_assert(p_fsync__cnt == 0); + git_repository_free(repo); + + cl_git_pass(git_repository_open(&repo, "test-objects")); + cl_repo_set_bool(repo, "core.fsyncObjectFiles", true); + cl_git_pass(git_repository_odb__weakptr(&odb, repo)); + cl_git_pass(git_odb_write(&oid, odb, "Now fsync\n", 10, GIT_OBJECT_BLOB)); + cl_assert(p_fsync__cnt > 0); + git_repository_free(repo); +} diff --git a/tests/libgit2/odb/loose_data.h b/tests/libgit2/odb/loose_data.h new file mode 100644 index 000000000..c10c9bc7f --- /dev/null +++ b/tests/libgit2/odb/loose_data.h @@ -0,0 +1,522 @@ +typedef struct object_data { + unsigned char *bytes; /* (compressed) bytes stored in object store */ + size_t blen; /* length of data in object store */ + char *id; /* object id (sha1) */ + char *type; /* object type */ + char *dir; /* object store (fan-out) directory name */ + char *file; /* object store filename */ + unsigned char *data; /* (uncompressed) object data */ + size_t dlen; /* length of (uncompressed) object data */ +} object_data; + +/* one == 8b137891791fe96927ad78e64b0aad7bded08bdc */ +static unsigned char one_bytes[] = { + 0x31, 0x78, 0x9c, 0xe3, 0x02, 0x00, 0x00, 0x0b, + 0x00, 0x0b, +}; + +static unsigned char one_data[] = { + 0x0a, +}; + +static object_data one = { + one_bytes, + sizeof(one_bytes), + "8b137891791fe96927ad78e64b0aad7bded08bdc", + "blob", + "test-objects/8b", + "test-objects/8b/137891791fe96927ad78e64b0aad7bded08bdc", + one_data, + sizeof(one_data), +}; + + +/* commit == 3d7f8a6af076c8c3f20071a8935cdbe8228594d1 */ +static unsigned char commit_bytes[] = { + 0x78, 0x01, 0x85, 0x50, 0xc1, 0x6a, 0xc3, 0x30, + 0x0c, 0xdd, 0xd9, 0x5f, 0xa1, 0xfb, 0x96, 0x12, + 0xbb, 0x29, 0x71, 0x46, 0x19, 0x2b, 0x3d, 0x97, + 0x1d, 0xd6, 0x7d, 0x80, 0x1d, 0xcb, 0x89, 0x21, + 0xb6, 0x82, 0xed, 0x40, 0xf3, 0xf7, 0xf3, 0x48, + 0x29, 0x3b, 0x6d, 0xd2, 0xe5, 0xbd, 0x27, 0xbd, + 0x27, 0x50, 0x4f, 0xde, 0xbb, 0x0c, 0xfb, 0x43, + 0xf3, 0x94, 0x23, 0x22, 0x18, 0x6b, 0x85, 0x51, + 0x5d, 0xad, 0xc5, 0xa1, 0x41, 0xae, 0x51, 0x4b, + 0xd9, 0x19, 0x6e, 0x4b, 0x0b, 0x29, 0x35, 0x72, + 0x59, 0xef, 0x5b, 0x29, 0x8c, 0x65, 0x6a, 0xc9, + 0x23, 0x45, 0x38, 0xc1, 0x17, 0x5c, 0x7f, 0xc0, + 0x71, 0x13, 0xde, 0xf1, 0xa6, 0xfc, 0x3c, 0xe1, + 0xae, 0x27, 0xff, 0x06, 0x5c, 0x88, 0x56, 0xf2, + 0x46, 0x74, 0x2d, 0x3c, 0xd7, 0xa5, 0x58, 0x51, + 0xcb, 0xb9, 0x8c, 0x11, 0xce, 0xf0, 0x01, 0x97, + 0x0d, 0x1e, 0x1f, 0xea, 0x3f, 0x6e, 0x76, 0x02, + 0x0a, 0x58, 0x4d, 0x2e, 0x20, 0x6c, 0x1e, 0x48, + 0x8b, 0xf7, 0x2a, 0xae, 0x8c, 0x5d, 0x47, 0x04, + 0x4d, 0x66, 0x05, 0xb2, 0x90, 0x0b, 0xbe, 0xcf, + 0x3d, 0xa6, 0xa4, 0x06, 0x7c, 0x29, 0x3c, 0x64, + 0xe5, 0x82, 0x0b, 0x03, 0xd8, 0x25, 0x96, 0x8d, + 0x08, 0x78, 0x9b, 0x27, 0x15, 0x54, 0x76, 0x14, + 0xd8, 0xdd, 0x35, 0x2f, 0x71, 0xa6, 0x84, 0x8f, + 0x90, 0x51, 0x85, 0x01, 0x13, 0xb8, 0x90, 0x23, + 0x99, 0xa5, 0x47, 0x03, 0x7a, 0xfd, 0x15, 0xbf, + 0x63, 0xec, 0xd3, 0x0d, 0x01, 0x4d, 0x45, 0xb6, + 0xd2, 0xeb, 0xeb, 0xdf, 0xef, 0x60, 0xdf, 0xef, + 0x1f, 0x78, 0x35, +}; + +static unsigned char commit_data[] = { + 0x74, 0x72, 0x65, 0x65, 0x20, 0x64, 0x66, 0x66, + 0x32, 0x64, 0x61, 0x39, 0x30, 0x62, 0x32, 0x35, + 0x34, 0x65, 0x31, 0x62, 0x65, 0x62, 0x38, 0x38, + 0x39, 0x64, 0x31, 0x66, 0x31, 0x66, 0x31, 0x32, + 0x38, 0x38, 0x62, 0x65, 0x31, 0x38, 0x30, 0x33, + 0x37, 0x38, 0x32, 0x64, 0x66, 0x0a, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x20, 0x41, 0x20, 0x55, + 0x20, 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, + 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, + 0x31, 0x34, 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, + 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x74, 0x65, 0x72, 0x20, 0x43, 0x20, + 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, + 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, + 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, + 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, + 0x30, 0x0a, 0x0a, 0x41, 0x20, 0x6f, 0x6e, 0x65, + 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x73, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x0a, 0x0a, 0x54, 0x68, + 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, + 0x20, 0x66, 0x75, 0x72, 0x74, 0x68, 0x65, 0x72, + 0x20, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x70, 0x75, 0x72, 0x70, + 0x6f, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x62, 0x79, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x53, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x2d, 0x6f, 0x66, 0x2d, + 0x62, 0x79, 0x3a, 0x20, 0x41, 0x20, 0x55, 0x20, + 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + 0x3e, 0x0a, +}; + +static object_data commit = { + commit_bytes, + sizeof(commit_bytes), + "3d7f8a6af076c8c3f20071a8935cdbe8228594d1", + "commit", + "test-objects/3d", + "test-objects/3d/7f8a6af076c8c3f20071a8935cdbe8228594d1", + commit_data, + sizeof(commit_data), +}; + +/* tree == dff2da90b254e1beb889d1f1f1288be1803782df */ +static unsigned char tree_bytes[] = { + 0x78, 0x01, 0x2b, 0x29, 0x4a, 0x4d, 0x55, 0x30, + 0x34, 0x32, 0x63, 0x30, 0x34, 0x30, 0x30, 0x33, + 0x31, 0x51, 0xc8, 0xcf, 0x4b, 0x65, 0xe8, 0x16, + 0xae, 0x98, 0x58, 0x29, 0xff, 0x32, 0x53, 0x7d, + 0x6d, 0xc5, 0x33, 0x6f, 0xae, 0xb5, 0xd5, 0xf7, + 0x2e, 0x74, 0xdf, 0x81, 0x4a, 0x17, 0xe7, 0xe7, + 0xa6, 0x32, 0xfc, 0x6d, 0x31, 0xd8, 0xd3, 0xe6, + 0xf3, 0xe7, 0xea, 0x47, 0xbe, 0xd0, 0x09, 0x3f, + 0x96, 0xb8, 0x3f, 0x90, 0x9e, 0xa2, 0xfd, 0x0f, + 0x2a, 0x5f, 0x52, 0x9e, 0xcf, 0x50, 0x31, 0x43, + 0x52, 0x29, 0xd1, 0x5a, 0xeb, 0x77, 0x82, 0x2a, + 0x8b, 0xfe, 0xb7, 0xbd, 0xed, 0x5d, 0x07, 0x67, + 0xfa, 0xb5, 0x42, 0xa5, 0xab, 0x52, 0x8b, 0xf2, + 0x19, 0x9e, 0xcd, 0x7d, 0x34, 0x7b, 0xd3, 0xc5, + 0x6b, 0xce, 0xde, 0xdd, 0x9a, 0xeb, 0xca, 0xa3, + 0x6e, 0x1c, 0x7a, 0xd2, 0x13, 0x3c, 0x11, 0x00, + 0xe2, 0xaa, 0x38, 0x57, +}; + +static unsigned char tree_data[] = { + 0x31, 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x6f, + 0x6e, 0x65, 0x00, 0x8b, 0x13, 0x78, 0x91, 0x79, + 0x1f, 0xe9, 0x69, 0x27, 0xad, 0x78, 0xe6, 0x4b, + 0x0a, 0xad, 0x7b, 0xde, 0xd0, 0x8b, 0xdc, 0x31, + 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x73, 0x6f, + 0x6d, 0x65, 0x00, 0xfd, 0x84, 0x30, 0xbc, 0x86, + 0x4c, 0xfc, 0xd5, 0xf1, 0x0e, 0x55, 0x90, 0xf8, + 0xa4, 0x47, 0xe0, 0x1b, 0x94, 0x2b, 0xfe, 0x31, + 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x74, 0x77, + 0x6f, 0x00, 0x78, 0x98, 0x19, 0x22, 0x61, 0x3b, + 0x2a, 0xfb, 0x60, 0x25, 0x04, 0x2f, 0xf6, 0xbd, + 0x87, 0x8a, 0xc1, 0x99, 0x4e, 0x85, 0x31, 0x30, + 0x30, 0x36, 0x34, 0x34, 0x20, 0x7a, 0x65, 0x72, + 0x6f, 0x00, 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, + 0xd6, 0x43, 0x4b, 0x8b, 0x29, 0xae, 0x77, 0x5a, + 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91, +}; + +static object_data tree = { + tree_bytes, + sizeof(tree_bytes), + "dff2da90b254e1beb889d1f1f1288be1803782df", + "tree", + "test-objects/df", + "test-objects/df/f2da90b254e1beb889d1f1f1288be1803782df", + tree_data, + sizeof(tree_data), +}; + +/* tag == 09d373e1dfdc16b129ceec6dd649739911541e05 */ +static unsigned char tag_bytes[] = { + 0x78, 0x01, 0x35, 0x4e, 0xcb, 0x0a, 0xc2, 0x40, + 0x10, 0xf3, 0xbc, 0x5f, 0x31, 0x77, 0xa1, 0xec, + 0xa3, 0xed, 0x6e, 0x41, 0x44, 0xf0, 0x2c, 0x5e, + 0xfc, 0x81, 0xe9, 0x76, 0xb6, 0xad, 0xb4, 0xb4, + 0x6c, 0x07, 0xd1, 0xbf, 0x77, 0x44, 0x0d, 0x39, + 0x84, 0x10, 0x92, 0x30, 0xf6, 0x60, 0xbc, 0xdb, + 0x2d, 0xed, 0x9d, 0x22, 0x83, 0xeb, 0x7c, 0x0a, + 0x58, 0x63, 0xd2, 0xbe, 0x8e, 0x21, 0xba, 0x64, + 0xb5, 0xf6, 0x06, 0x43, 0xe3, 0xaa, 0xd8, 0xb5, + 0x14, 0xac, 0x0d, 0x55, 0x53, 0x76, 0x46, 0xf1, + 0x6b, 0x25, 0x88, 0xcb, 0x3c, 0x8f, 0xac, 0x58, + 0x3a, 0x1e, 0xba, 0xd0, 0x85, 0xd8, 0xd8, 0xf7, + 0x94, 0xe1, 0x0c, 0x57, 0xb8, 0x8c, 0xcc, 0x22, + 0x0f, 0xdf, 0x90, 0xc8, 0x13, 0x3d, 0x71, 0x5e, + 0x27, 0x2a, 0xc4, 0x39, 0x82, 0xb1, 0xd6, 0x07, + 0x53, 0xda, 0xc6, 0xc3, 0x5e, 0x0b, 0x94, 0xba, + 0x0d, 0xe3, 0x06, 0x42, 0x1e, 0x08, 0x3e, 0x95, + 0xbf, 0x4b, 0x69, 0xc9, 0x90, 0x69, 0x22, 0xdc, + 0xe8, 0xbf, 0xf2, 0x06, 0x42, 0x9a, 0x36, 0xb1, +}; + +static unsigned char tag_data[] = { + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x33, + 0x64, 0x37, 0x66, 0x38, 0x61, 0x36, 0x61, 0x66, + 0x30, 0x37, 0x36, 0x63, 0x38, 0x63, 0x33, 0x66, + 0x32, 0x30, 0x30, 0x37, 0x31, 0x61, 0x38, 0x39, + 0x33, 0x35, 0x63, 0x64, 0x62, 0x65, 0x38, 0x32, + 0x32, 0x38, 0x35, 0x39, 0x34, 0x64, 0x31, 0x0a, + 0x74, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x0a, 0x74, 0x61, 0x67, 0x20, + 0x76, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0a, 0x74, + 0x61, 0x67, 0x67, 0x65, 0x72, 0x20, 0x43, 0x20, + 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, + 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, + 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, + 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, + 0x30, 0x0a, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20, + 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, + 0x61, 0x67, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, + 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x76, 0x30, + 0x2e, 0x30, 0x2e, 0x31, 0x0a, +}; + +static object_data tag = { + tag_bytes, + sizeof(tag_bytes), + "09d373e1dfdc16b129ceec6dd649739911541e05", + "tag", + "test-objects/09", + "test-objects/09/d373e1dfdc16b129ceec6dd649739911541e05", + tag_data, + sizeof(tag_data), +}; + +/* zero == e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 */ +static unsigned char zero_bytes[] = { + 0x78, 0x01, 0x4b, 0xca, 0xc9, 0x4f, 0x52, 0x30, + 0x60, 0x00, 0x00, 0x09, 0xb0, 0x01, 0xf0, +}; + +static unsigned char zero_data[] = { + 0x00 /* dummy data */ +}; + +static object_data zero = { + zero_bytes, + sizeof(zero_bytes), + "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + "blob", + "test-objects/e6", + "test-objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391", + zero_data, + 0, +}; + +/* two == 78981922613b2afb6025042ff6bd878ac1994e85 */ +static unsigned char two_bytes[] = { + 0x78, 0x01, 0x4b, 0xca, 0xc9, 0x4f, 0x52, 0x30, + 0x62, 0x48, 0xe4, 0x02, 0x00, 0x0e, 0x64, 0x02, + 0x5d, +}; + +static unsigned char two_data[] = { + 0x61, 0x0a, +}; + +static object_data two = { + two_bytes, + sizeof(two_bytes), + "78981922613b2afb6025042ff6bd878ac1994e85", + "blob", + "test-objects/78", + "test-objects/78/981922613b2afb6025042ff6bd878ac1994e85", + two_data, + sizeof(two_data), +}; + +/* some == fd8430bc864cfcd5f10e5590f8a447e01b942bfe */ +static unsigned char some_bytes[] = { + 0x78, 0x01, 0x7d, 0x54, 0xc1, 0x4e, 0xe3, 0x30, + 0x10, 0xdd, 0x33, 0x5f, 0x31, 0xc7, 0x5d, 0x94, + 0xa5, 0x84, 0xd5, 0x22, 0xad, 0x7a, 0x0a, 0x15, + 0x85, 0x48, 0xd0, 0x56, 0x49, 0x2a, 0xd4, 0xa3, + 0x13, 0x4f, 0x88, 0x85, 0x63, 0x47, 0xb6, 0x43, + 0xc9, 0xdf, 0xef, 0x8c, 0x69, 0x17, 0x56, 0x0b, + 0x7b, 0xaa, 0x62, 0x7b, 0xde, 0xbc, 0xf7, 0xe6, + 0x4d, 0x6b, 0x6d, 0x6b, 0x48, 0xd3, 0xcb, 0x5f, + 0x5f, 0x66, 0xa7, 0x27, 0x70, 0x0a, 0x55, 0xa7, + 0x3c, 0xb4, 0x4a, 0x23, 0xf0, 0xaf, 0x43, 0x04, + 0x6f, 0xdb, 0xb0, 0x17, 0x0e, 0xe7, 0x30, 0xd9, + 0x11, 0x1a, 0x61, 0xc0, 0xa1, 0x54, 0x3e, 0x38, + 0x55, 0x8f, 0x81, 0x9e, 0x05, 0x10, 0x46, 0xce, + 0xac, 0x83, 0xde, 0x4a, 0xd5, 0x4e, 0x0c, 0x42, + 0x67, 0xa3, 0x91, 0xe8, 0x20, 0x74, 0x08, 0x01, + 0x5d, 0xef, 0xc1, 0xb6, 0xf1, 0xe3, 0x66, 0xb5, + 0x85, 0x1b, 0x34, 0xe8, 0x84, 0x86, 0xcd, 0x58, + 0x6b, 0xd5, 0xc0, 0x9d, 0x6a, 0xd0, 0x78, 0x4c, + 0xe0, 0x19, 0x9d, 0x57, 0xd6, 0xc0, 0x45, 0xc2, + 0x18, 0xc2, 0xc3, 0xc0, 0x0f, 0x7c, 0x87, 0x12, + 0xea, 0x29, 0x56, 0x2f, 0x99, 0x4f, 0x79, 0xe0, + 0x03, 0x4b, 0x4b, 0x4d, 0x44, 0xa0, 0x92, 0x33, + 0x2a, 0xe0, 0x9a, 0xdc, 0x80, 0x90, 0x52, 0xf1, + 0x11, 0x04, 0x1b, 0x4b, 0x06, 0xea, 0xae, 0x3c, + 0xe3, 0x7a, 0x50, 0x74, 0x4a, 0x84, 0xfe, 0xc3, + 0x81, 0x41, 0xf8, 0x89, 0x18, 0x43, 0x67, 0x9d, + 0x87, 0x47, 0xf5, 0x8c, 0x51, 0xf6, 0x68, 0xb4, + 0xea, 0x55, 0x20, 0x2a, 0x6f, 0x80, 0xdc, 0x42, + 0x2b, 0xf3, 0x14, 0x2b, 0x1a, 0xdb, 0x0f, 0xe4, + 0x9a, 0x64, 0x84, 0xa3, 0x90, 0xa8, 0xf9, 0x8f, + 0x9d, 0x86, 0x9e, 0xd3, 0xab, 0x5a, 0x99, 0xc8, + 0xd9, 0xc3, 0x5e, 0x85, 0x0e, 0x2c, 0xb5, 0x73, + 0x30, 0x38, 0xfb, 0xe8, 0x44, 0xef, 0x5f, 0x95, + 0x1b, 0xc9, 0xd0, 0xef, 0x3c, 0x26, 0x32, 0x1e, + 0xff, 0x2d, 0xb6, 0x23, 0x7b, 0x3f, 0xd1, 0x3c, + 0x78, 0x1a, 0x0d, 0xcb, 0xe6, 0xf6, 0xd4, 0x44, + 0x99, 0x47, 0x1a, 0x9e, 0xed, 0x23, 0xb5, 0x91, + 0x6a, 0xdf, 0x53, 0x39, 0x03, 0xf8, 0x5a, 0xb1, + 0x0f, 0x1f, 0xce, 0x81, 0x11, 0xde, 0x01, 0x7a, + 0x90, 0x16, 0xc4, 0x30, 0xe8, 0x89, 0xed, 0x7b, + 0x65, 0x4b, 0xd7, 0x03, 0x36, 0xc1, 0xcf, 0xa1, + 0xa5, 0xb1, 0xe3, 0x8b, 0xe8, 0x07, 0x4d, 0xf3, + 0x23, 0x25, 0x13, 0x35, 0x27, 0xf5, 0x8c, 0x11, + 0xd3, 0xa0, 0x9a, 0xa8, 0xf5, 0x38, 0x7d, 0xce, + 0x55, 0xc2, 0x71, 0x79, 0x13, 0xc7, 0xa3, 0xda, + 0x77, 0x68, 0xc0, 0xd8, 0x10, 0xdd, 0x24, 0x8b, + 0x15, 0x59, 0xc5, 0x10, 0xe2, 0x20, 0x99, 0x8e, + 0xf0, 0x05, 0x9b, 0x31, 0x88, 0x5a, 0xe3, 0xd9, + 0x37, 0xba, 0xe2, 0xdb, 0xbf, 0x92, 0xfa, 0x66, + 0x16, 0x97, 0x47, 0xd9, 0x9d, 0x1d, 0x28, 0x7c, + 0x9d, 0x08, 0x1c, 0xc7, 0xbd, 0xd2, 0x1a, 0x6a, + 0x04, 0xf2, 0xa2, 0x1d, 0x75, 0x02, 0x14, 0x5d, + 0xc6, 0x78, 0xc8, 0xab, 0xdb, 0xf5, 0xb6, 0x82, + 0x6c, 0xb5, 0x83, 0x87, 0xac, 0x28, 0xb2, 0x55, + 0xb5, 0x9b, 0xc7, 0xc1, 0xb0, 0xb7, 0xf8, 0x4c, + 0xbc, 0x38, 0x0e, 0x8a, 0x04, 0x2a, 0x62, 0x41, + 0x6b, 0xe0, 0x84, 0x09, 0x13, 0xe9, 0xe1, 0xea, + 0xfb, 0xeb, 0x62, 0x71, 0x4b, 0x25, 0xd9, 0x55, + 0x7e, 0x97, 0x57, 0x3b, 0x20, 0x33, 0x96, 0x79, + 0xb5, 0xba, 0x2e, 0x4b, 0x58, 0xae, 0x0b, 0xc8, + 0x60, 0x93, 0x15, 0x55, 0xbe, 0xd8, 0xde, 0x65, + 0x05, 0x6c, 0xb6, 0xc5, 0x66, 0x5d, 0x5e, 0x93, + 0xf7, 0x25, 0x65, 0x98, 0x41, 0x29, 0x86, 0x0c, + 0xf2, 0xf1, 0x14, 0xa2, 0xb3, 0xbd, 0x75, 0x08, + 0x12, 0x83, 0x50, 0xda, 0x1f, 0x23, 0xbe, 0xa3, + 0x1d, 0xf4, 0x9d, 0x1d, 0xb5, 0x84, 0x4e, 0x50, + 0x38, 0x1d, 0x36, 0x48, 0x21, 0x95, 0xd1, 0xac, + 0x81, 0x99, 0x1d, 0xc1, 0x3f, 0x41, 0xe6, 0x9e, + 0x42, 0x5b, 0x0a, 0x48, 0xcc, 0x5f, 0xe0, 0x7d, + 0x3f, 0xc4, 0x6f, 0x0e, 0xfe, 0xc0, 0x2d, 0xfe, + 0x01, 0x2c, 0xd6, 0x9b, 0x5d, 0xbe, 0xba, 0x21, + 0xca, 0x79, 0xcb, 0xe3, 0x49, 0x60, 0xef, 0x68, + 0x05, 0x28, 0x9b, 0x8c, 0xc1, 0x12, 0x3e, 0xdb, + 0xc7, 0x04, 0x7e, 0xa6, 0x74, 0x29, 0xcc, 0x13, + 0xed, 0x07, 0x94, 0x81, 0xd6, 0x96, 0xaa, 0x97, + 0xaa, 0xa5, 0xc0, 0x2f, 0xb5, 0xb5, 0x2e, 0xe6, + 0xfc, 0xca, 0xfa, 0x60, 0x4d, 0x02, 0xf7, 0x19, + 0x9c, 0x5f, 0xa4, 0xe9, 0xf9, 0xf7, 0xf4, 0xc7, + 0x79, 0x9a, 0xc0, 0xb6, 0xcc, 0x58, 0xec, 0xec, + 0xe4, 0x37, 0x22, 0xfa, 0x8b, 0x53, +}; + +static unsigned char some_data[] = { + 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, + 0x69, 0x73, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, + 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, + 0x3b, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, + 0x6e, 0x20, 0x72, 0x65, 0x64, 0x69, 0x73, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x69, + 0x74, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, + 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x0a, + 0x20, 0x2a, 0x20, 0x69, 0x74, 0x20, 0x75, 0x6e, + 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2c, + 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x20, 0x32, 0x2c, 0x0a, 0x20, 0x2a, 0x20, 0x61, + 0x73, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, + 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, + 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x2a, 0x0a, + 0x20, 0x2a, 0x20, 0x49, 0x6e, 0x20, 0x61, 0x64, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x47, 0x4e, 0x55, 0x20, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, 0x63, 0x65, + 0x6e, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x2a, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x73, 0x20, 0x67, 0x69, 0x76, 0x65, + 0x20, 0x79, 0x6f, 0x75, 0x20, 0x75, 0x6e, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x20, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x69, 0x6e, + 0x6b, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x0a, 0x20, + 0x2a, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, + 0x6e, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6d, 0x62, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x74, + 0x68, 0x65, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x67, + 0x72, 0x61, 0x6d, 0x73, 0x2c, 0x0a, 0x20, 0x2a, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20, + 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65, + 0x20, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x6e, + 0x79, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x2a, + 0x20, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x20, + 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x2e, 0x20, 0x20, 0x28, 0x54, 0x68, 0x65, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, + 0x20, 0x2a, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, + 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, + 0x64, 0x6f, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, + 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x74, 0x68, 0x65, + 0x72, 0x20, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, + 0x74, 0x73, 0x3b, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c, + 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x0a, 0x20, 0x2a, 0x20, 0x6d, + 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2c, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x73, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x6e, + 0x6f, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x65, + 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x0a, 0x20, + 0x2a, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x62, + 0x69, 0x6e, 0x65, 0x64, 0x20, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, + 0x29, 0x0a, 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, + 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, + 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x68, 0x6f, 0x70, 0x65, 0x20, 0x74, 0x68, 0x61, + 0x74, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c, + 0x6c, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, + 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x62, 0x75, 0x74, + 0x0a, 0x20, 0x2a, 0x20, 0x57, 0x49, 0x54, 0x48, + 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20, + 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, + 0x3b, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, + 0x74, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x69, + 0x65, 0x64, 0x20, 0x77, 0x61, 0x72, 0x72, 0x61, + 0x6e, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x0a, 0x20, + 0x2a, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, + 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, + 0x59, 0x20, 0x6f, 0x72, 0x20, 0x46, 0x49, 0x54, + 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, + 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 0x49, + 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, + 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2e, 0x20, 0x20, + 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x47, 0x4e, 0x55, 0x0a, 0x20, 0x2a, 0x20, 0x47, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, + 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x0a, + 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x59, 0x6f, + 0x75, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, + 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x20, 0x61, + 0x20, 0x63, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, + 0x20, 0x2a, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x61, 0x6d, 0x3b, 0x20, 0x73, 0x65, 0x65, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x20, 0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47, + 0x2e, 0x20, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, + 0x74, 0x2c, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, + 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x2a, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, + 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, + 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x35, 0x31, 0x20, + 0x46, 0x72, 0x61, 0x6e, 0x6b, 0x6c, 0x69, 0x6e, + 0x20, 0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x2c, + 0x20, 0x46, 0x69, 0x66, 0x74, 0x68, 0x20, 0x46, + 0x6c, 0x6f, 0x6f, 0x72, 0x2c, 0x0a, 0x20, 0x2a, + 0x20, 0x42, 0x6f, 0x73, 0x74, 0x6f, 0x6e, 0x2c, + 0x20, 0x4d, 0x41, 0x20, 0x30, 0x32, 0x31, 0x31, + 0x30, 0x2d, 0x31, 0x33, 0x30, 0x31, 0x2c, 0x20, + 0x55, 0x53, 0x41, 0x2e, 0x0a, 0x20, 0x2a, 0x2f, + 0x0a, +}; + +static object_data some = { + some_bytes, + sizeof(some_bytes), + "fd8430bc864cfcd5f10e5590f8a447e01b942bfe", + "blob", + "test-objects/fd", + "test-objects/fd/8430bc864cfcd5f10e5590f8a447e01b942bfe", + some_data, + sizeof(some_data), +}; diff --git a/tests/libgit2/odb/mixed.c b/tests/libgit2/odb/mixed.c new file mode 100644 index 000000000..87e945c83 --- /dev/null +++ b/tests/libgit2/odb/mixed.c @@ -0,0 +1,286 @@ +#include "clar_libgit2.h" +#include "odb.h" + +static git_odb *_odb; + +void test_odb_mixed__initialize(void) +{ + cl_git_pass(git_odb_open(&_odb, cl_fixture("duplicate.git/objects"))); +} + +void test_odb_mixed__cleanup(void) +{ + git_odb_free(_odb); + _odb = NULL; +} + +void test_odb_mixed__dup_oid(void) { + const char hex[] = "ce013625030ba8dba906f756967f9e9ca394464a"; + const char short_hex[] = "ce01362"; + git_oid oid; + git_odb_object *obj; + + cl_git_pass(git_oid_fromstr(&oid, hex)); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, GIT_OID_HEXSZ)); + git_odb_object_free(obj); + + cl_git_pass(git_odb_exists_prefix(NULL, _odb, &oid, GIT_OID_HEXSZ)); + + cl_git_pass(git_oid_fromstrn(&oid, short_hex, sizeof(short_hex) - 1)); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, sizeof(short_hex) - 1)); + git_odb_object_free(obj); + + cl_git_pass(git_odb_exists_prefix(NULL, _odb, &oid, sizeof(short_hex) - 1)); +} + +/* some known sha collisions of file content: + * 'aabqhq' and 'aaazvc' with prefix 'dea509d0' (+ '9' and + 'b') + * 'aaeufo' and 'aaaohs' with prefix '81b5bff5' (+ 'f' and + 'b') + * 'aafewy' and 'aaepta' with prefix '739e3c4c' + * 'aahsyn' and 'aadrjg' with prefix '0ddeaded' (+ '9' and + 'e') + */ + +void test_odb_mixed__dup_oid_prefix_0(void) { + char hex[10]; + git_oid oid, found; + git_odb_object *obj; + + /* ambiguous in the same pack file */ + + strncpy(hex, "dea509d0", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_assert_equal_i( + GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + cl_assert_equal_i( + GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &oid, strlen(hex))); + + strncpy(hex, "dea509d09", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + cl_git_pass(git_odb_exists_prefix(&found, _odb, &oid, strlen(hex))); + cl_assert_equal_oid(&found, git_odb_object_id(obj)); + git_odb_object_free(obj); + + strncpy(hex, "dea509d0b", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); + + /* ambiguous in different pack files */ + + strncpy(hex, "81b5bff5", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_assert_equal_i( + GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + cl_assert_equal_i( + GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &oid, strlen(hex))); + + strncpy(hex, "81b5bff5b", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + cl_git_pass(git_odb_exists_prefix(&found, _odb, &oid, strlen(hex))); + cl_assert_equal_oid(&found, git_odb_object_id(obj)); + git_odb_object_free(obj); + + strncpy(hex, "81b5bff5f", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); + + /* ambiguous in pack file and loose */ + + strncpy(hex, "0ddeaded", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_assert_equal_i( + GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + cl_assert_equal_i( + GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &oid, strlen(hex))); + + strncpy(hex, "0ddeaded9", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + cl_git_pass(git_odb_exists_prefix(&found, _odb, &oid, strlen(hex))); + cl_assert_equal_oid(&found, git_odb_object_id(obj)); + git_odb_object_free(obj); + + strncpy(hex, "0ddeadede", sizeof(hex)); + cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); + cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); + git_odb_object_free(obj); +} + +struct expand_id_test_data { + char *lookup_id; + char *expected_id; + git_object_t expected_type; +}; + +struct expand_id_test_data expand_id_test_data[] = { + /* some prefixes and their expected values */ + { "dea509d0", NULL, GIT_OBJECT_ANY }, + { "00000000", NULL, GIT_OBJECT_ANY }, + { "dea509d0", NULL, GIT_OBJECT_ANY }, + { "dea509d09", "dea509d097ce692e167dfc6a48a7a280cc5e877e", GIT_OBJECT_BLOB }, + { "dea509d0b", "dea509d0b3cb8ee0650f6ca210bc83f4678851ba", GIT_OBJECT_BLOB }, + { "ce0136250", "ce013625030ba8dba906f756967f9e9ca394464a", GIT_OBJECT_BLOB }, + { "0ddeaded", NULL, GIT_OBJECT_ANY }, + { "4d5979b", "4d5979b468252190cb572ae758aca36928e8a91e", GIT_OBJECT_TREE }, + { "0ddeaded", NULL, GIT_OBJECT_ANY }, + { "0ddeadede", "0ddeadede9e6d6ccddce0ee1e5749eed0485e5ea", GIT_OBJECT_BLOB }, + { "0ddeaded9", "0ddeaded9502971eefe1e41e34d0e536853ae20f", GIT_OBJECT_BLOB }, + { "f00b4e", NULL, GIT_OBJECT_ANY }, + + /* this OID is too short and should be ambiguous! */ + { "f00", NULL, GIT_OBJECT_ANY }, + + /* some full-length object ids */ + { "0000000000000000000000000000000000000000", NULL, GIT_OBJECT_ANY }, + { + "dea509d097ce692e167dfc6a48a7a280cc5e877e", + "dea509d097ce692e167dfc6a48a7a280cc5e877e", + GIT_OBJECT_BLOB + }, + { "f00f00f00f00f00f00f00f00f00f00f00f00f00f", NULL, GIT_OBJECT_ANY }, + { + "4d5979b468252190cb572ae758aca36928e8a91e", + "4d5979b468252190cb572ae758aca36928e8a91e", + GIT_OBJECT_TREE + }, + + /* + * ensure we're not leaking the return error code for the + * last lookup if the last object is invalid + */ + { "0ddeadedfff", NULL, GIT_OBJECT_ANY }, +}; + +static void setup_prefix_query( + git_odb_expand_id **out_ids, + size_t *out_num) +{ + git_odb_expand_id *ids; + size_t num, i; + + num = ARRAY_SIZE(expand_id_test_data); + + cl_assert((ids = git__calloc(num, sizeof(git_odb_expand_id)))); + + for (i = 0; i < num; i++) { + git_odb_expand_id *id = &ids[i]; + + size_t len = strlen(expand_id_test_data[i].lookup_id); + + git_oid_fromstrn(&id->id, expand_id_test_data[i].lookup_id, len); + id->length = (unsigned short)len; + id->type = expand_id_test_data[i].expected_type; + } + + *out_ids = ids; + *out_num = num; +} + +static void assert_found_objects(git_odb_expand_id *ids) +{ + size_t num, i; + + num = ARRAY_SIZE(expand_id_test_data); + + for (i = 0; i < num; i++) { + git_oid expected_id = {{0}}; + size_t expected_len = 0; + git_object_t expected_type = 0; + + if (expand_id_test_data[i].expected_id) { + git_oid_fromstr(&expected_id, expand_id_test_data[i].expected_id); + expected_len = GIT_OID_HEXSZ; + expected_type = expand_id_test_data[i].expected_type; + } + + cl_assert_equal_oid(&expected_id, &ids[i].id); + cl_assert_equal_i(expected_len, ids[i].length); + cl_assert_equal_i(expected_type, ids[i].type); + } +} + +static void assert_notfound_objects(git_odb_expand_id *ids) +{ + git_oid expected_id = {{0}}; + size_t num, i; + + num = ARRAY_SIZE(expand_id_test_data); + + for (i = 0; i < num; i++) { + cl_assert_equal_oid(&expected_id, &ids[i].id); + cl_assert_equal_i(0, ids[i].length); + cl_assert_equal_i(0, ids[i].type); + } +} + +void test_odb_mixed__expand_ids(void) +{ + git_odb_expand_id *ids; + size_t i, num; + + /* test looking for the actual (correct) types */ + + setup_prefix_query(&ids, &num); + cl_git_pass(git_odb_expand_ids(_odb, ids, num)); + assert_found_objects(ids); + git__free(ids); + + /* test looking for an explicit `type == 0` */ + + setup_prefix_query(&ids, &num); + + for (i = 0; i < num; i++) + ids[i].type = 0; + + cl_git_pass(git_odb_expand_ids(_odb, ids, num)); + assert_found_objects(ids); + git__free(ids); + + /* test looking for an explicit GIT_OBJECT_ANY */ + + setup_prefix_query(&ids, &num); + + for (i = 0; i < num; i++) + ids[i].type = GIT_OBJECT_ANY; + + cl_git_pass(git_odb_expand_ids(_odb, ids, num)); + assert_found_objects(ids); + git__free(ids); + + /* test looking for the completely wrong type */ + + setup_prefix_query(&ids, &num); + + for (i = 0; i < num; i++) + ids[i].type = (ids[i].type == GIT_OBJECT_BLOB) ? + GIT_OBJECT_TREE : GIT_OBJECT_BLOB; + + cl_git_pass(git_odb_expand_ids(_odb, ids, num)); + assert_notfound_objects(ids); + git__free(ids); +} + +void test_odb_mixed__expand_ids_cached(void) +{ + git_odb_expand_id *ids; + size_t i, num; + + /* test looking for the actual (correct) types after accessing the object */ + + setup_prefix_query(&ids, &num); + + for (i = 0; i < num; i++) { + git_odb_object *obj; + if (ids[i].type == GIT_OBJECT_ANY) + continue; + cl_git_pass(git_odb_read_prefix(&obj, _odb, &ids[i].id, ids[i].length)); + git_odb_object_free(obj); + } + + cl_git_pass(git_odb_expand_ids(_odb, ids, num)); + assert_found_objects(ids); + git__free(ids); +} diff --git a/tests/libgit2/odb/pack_data.h b/tests/libgit2/odb/pack_data.h new file mode 100644 index 000000000..e6371beb1 --- /dev/null +++ b/tests/libgit2/odb/pack_data.h @@ -0,0 +1,151 @@ + +static const char *packed_objects[] = { + "0266163a49e280c4f5ed1e08facd36a2bd716bcf", + "53fc32d17276939fc79ed05badaef2db09990016", + "6336846bd5c88d32f93ae57d846683e61ab5c530", + "6dcf9bf7541ee10456529833502442f385010c3d", + "bed08a0b30b72a9d4aed7f1af8c8ca124e8d64b9", + "e90810b8df3e80c413d903f631643c716887138d", + "fc3c3a2083e9f6f89e6bd53e9420e70d1e357c9b", + "fc58168adf502d0c0ef614c3111a7038fc8c09c8", + "fd0ec0333948dfe23265ac46be0205a436a8c3a5", + "fd8430bc864cfcd5f10e5590f8a447e01b942bfe", + "fd899f45951c15c1c5f7c34b1c864e91bd6556c6", + "fda23b974899e7e1f938619099280bfda13bdca9", + "fdbec189efb657c8325962b494875987881a356b", + "fe1ca6bd22b5d8353ce6c2f3aba80805c438a7a5", + "fe3a6a42c87ff1239370c741a265f3997add87c1", + "deb106bfd2d36ecf9f0079224c12022201a39ad1", + "dec93efc79e60f2680de3e666755d335967eec30", + "def425bf8568b9c1e20879bf5be6f9c52b7361c4", + "df48000ac4f48570054e3a71a81916357997b680", + "dfae6ed8f6dd8acc3b40a31811ea316239223559", + "dff79e27d3d2cdc09790ded80fe2ea8ff5d61034", + "e00e46abe4c542e17c8bc83d72cf5be8018d7b0e", + "e01b107b4f77f8f98645adac0206a504f2d29d7c", + "e032d863f512c47b479bd984f8b6c8061f66b7d4", + "e044baa468a1c74f9f9da36805445f6888358b49", + "e04529998989ba8ae3419538dd57969af819b241", + "e0637ddfbea67c8d7f557c709e095af8906e9176", + "e0743ad4031231e71700abdc6fdbe94f189d20e5", + "cf33ac7a3d8b2b8f6bb266518aadbf59de397608", + "cf5f7235b9c9689b133f6ea12015720b411329bd", + "cf6cccf1297284833a9a03138a1f5738fa1c6c94", + "cf7992bde17ce7a79cab5f0c1fcbe8a0108721ed", + "cfe3a027ab12506d4144ee8a35669ae8fc4b7ab1", + "cfe96f31dfad7bab49977aa1df7302f7fafcb025", + "cff54d138945ef4de384e9d2759291d0c13ea90a", + "d01f7573ac34c2f502bd1cf18cde73480c741151", + "d03f567593f346a1ca96a57f8191def098d126e3", + "d047b47aadf88501238f36f5c17dd0a50dc62087", + "d0a0d63086fae3b0682af7261df21f7d0f7f066d", + "d0a44bd6ed0be21b725a96c0891bbc79bc1a540c", + "d0d7e736e536a41bcb885005f8bf258c61cad682", + "d0e7959d4b95ffec6198df6f5a7ae259b23a5f50", + "bf2fe2acca17d13356ce802ba9dc8343f710dfb7", + "bf55f407d6d9418e51f42ea7a3a6aadf17388349", + "bf92206f8b633b88a66dca4a911777630b06fbac", + "bfaf8c42eb8842abe206179fee864cfba87e3ca9", + "bfe05675d4e8f6b59d50932add8790f1a06b10ee", + "bff8618112330763327cfa6ce6e914db84f51ddf", + "bff873e9853ed99fed52c25f7ad29f78b27dcec2", + "c01c3fae7251098d7af1b459bcd0786e81d4616d", + "c0220fca67f48b8a5d4163d53b1486224be3a198", + "c02d0b160b82ee72469c269f13de4c26a7ea09cb", + "c059510ad1b45ab58390e042d7dee1ac46703854", + "c07204a1897aeeaa3c248d29dbfa9b033baf9755", + "c073337a4dd7276931b4b3fdbc3f0040e9441793", + "0fd7e4bfba5b3a82be88d1057757ca8b2c5e6d26", + "100746511cc45c9f1ad6721c4ef5be49222fee4d", + "1088490171d9b984d68b8b9be9ca003f4eafff59", + "1093c8ff4cb78fcf5f79dbbeedcb6e824bd4e253", + "10aa3fa72afab7ee31e116ae06442fe0f7b79df2", + "10b759e734e8299aa0dca08be935d95d886127b6", + "111d5ccf0bb010c4e8d7af3eedfa12ef4c5e265b", + "11261fbff21758444d426356ff6327ee01e90752", + "112998d425717bb922ce74e8f6f0f831d8dc4510", + "2ef4e5d838b6507bd61d457cf6466662b791c5c0", + "2ef4faa0f82efa00eeac6cae9e8b2abccc8566ee", + "2f06098183b0d7be350acbe39cdbaccff2df0c4a", + "2f1c5d509ac5bffb3c62f710a1c2c542e126dfd1", + "2f205b20fc16423c42b3ba51b2ea78d7b9ff3578", + "2f9b6b6e3d9250ba09360734aa47973a993b59d1", + "30c62a2d5a8d644f1311d4f7fe3f6a788e4c8188", + "31438e245492d85fd6da4d1406eba0fbde8332a4", + "3184a3abdfea231992254929ff4e275898e5bbf6", + "3188ffdbb3a3d52e0f78f30c484533899224436e", + "32581d0093429770d044a60eb0e9cc0462bedb13", + "32679a9544d83e5403202c4d5efb61ad02492847", + "4e7e9f60b7e2049b7f5697daf133161a18ef688f", + "4e8cda27ddc8be7db875ceb0f360c37734724c6d", + "4ea481c61c59ab55169b7cbaae536ad50b49d6f0", + "4f0adcd0e61eabe06fe32be66b16559537124b7a", + "4f1355c91100d12f9e7202f91b245df0c110867c", + "4f6eadeb08b9d0d1e8b1b3eac8a34940adf29a2d", + "4f9339df943c53117a5fc8e86e2f38716ff3a668", + "4fc3874b118752e40de556b1c3e7b4a9f1737d00", + "4ff1dd0992dd6baafdb5e166be6f9f23b59bdf87", + "5018a35e0b7e2eec7ce5050baf9c7343f3f74164", + "50298f44a45eda3a29dae82dbe911b5aa176ac07", + "502acd164fb115768d723144da2e7bb5a24891bb", + "50330c02bd4fd95c9db1fcf2f97f4218e42b7226", + "5052bf355d9f8c52446561a39733a8767bf31e37", + "6f2cd729ae42988c1dd43588d3a6661ba48ad7a0", + "6f4e2c42d9138bfbf3e0f908f1308828cc6f2178", + "6f6a17db05a83620cef4572761831c20a70ba9b9", + "6faad60901e36538634f0d8b8ff3f21f83503c71", + "6fc72e46de3df0c3842dab302bbacf697a63abab", + "6fdccd49f442a7204399ca9b418f017322dbded8", + "6fe7568fc3861c334cb008fd85d57d9647249ef5", + "700f55d91d7b55665594676a4bada1f1457a0598", + "702bd70595a7b19afc48a1f784a6505be68469d4", + "7033f9ee0e52b08cb5679cd49b7b7999eaf9eaf8", + "70957110ce446c4e250f865760fb3da513cdcc92", + "8ec696a4734f16479d091bc70574d23dd9fe7443", + "8ed341c55ed4d6f4cdc8bf4f0ca18a08c93f6962", + "8edc2805f1f11b63e44bf81f4557f8b473612b69", + "8ef9060a954118a698fc10e20acdc430566a100f", + "8f0c4b543f4bb6eb1518ecfc3d4699e43108d393", + "8fac94df3035405c2e60b3799153ce7c428af6b9", + "904c0ac12b23548de524adae712241b423d765a3", + "90bbaa9a809c3a768d873a9cc7d52b4f3bf3d1b9", + "90d4d2f0fc362beabbbf76b4ffda0828229c198d", + "90f9ff6755330b685feff6c3d81782ee3592ab04", + "91822c50ebe4f9bf5bbb8308ecf9f6557062775c", + "91d973263a55708fa8255867b3202d81ef9c2868", + "af292c99c6148d772af3315a1c74e83330e7ead7", + "af3b99d5be330dbbce0b9250c3a5fb05911908cc", + "af55d0cdeb280af2db8697e5afa506e081012719", + "af795e498d411142ddb073e8ca2c5447c3295a4c", + "afadc73a392f8cc8e2cc77dd62a7433dd3bafa8c", + "affd84ed8ec7ce67612fe3c12a80f8164b101f6a", + "b0941f9c70ffe67f0387a827b338e64ecf3190f0", + "b0a3077f9ef6e093f8d9869bdb0c07095bd722cb", + "b0a8568a7614806378a54db5706ee3b06ae58693", + "b0fb7372f242233d1d35ce7d8e74d3990cbc5841", + "b10489944b9ead17427551759d180d10203e06ba", + "b196a807b323f2748ffc6b1d42cd0812d04c9a40", + "b1bb1d888f0c5e19278536d49fa77db035fac7ae" +}; + +static const char *loose_objects[] = { + "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", + "a8233120f6ad708f843d861ce2b7228ec4e3dec6", + "fd093bff70906175335656e6ce6ae05783708765", + "c47800c7266a2be04c571c04d5a6614691ea99bd", + "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd", + "8496071c1b46c854b31185ea97743be6a8774479", + "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + "814889a078c031f61ed08ab5fa863aea9314344d", + "5b5b025afb0b4c913b4c338a42934a3863bf3644", + "1385f264afb75a56a5bec74243be9b367ba4ca08", + "f60079018b664e4e79329a7ef9559c8d9e0378d1", + "be3563ae3f795b2b4353bcce3a527ad0a4f7f644", + "75057dd4114e74cca1d750d0aee1647c903cb60a", + "fa49b077972391ad58037050f2a75f74e3671e92", + "9fd738e8f7967c078dceed8190330fc8648ee56a", + "1810dff58d8a660512d4832e740f692884338ccd", + "181037049a54a1eb5fab404658a3a250b44335d7", + "a4a7dce85cf63874e984719f4fdd239f5145052f", + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045" +}; diff --git a/tests/libgit2/odb/pack_data_one.h b/tests/libgit2/odb/pack_data_one.h new file mode 100644 index 000000000..13570ba78 --- /dev/null +++ b/tests/libgit2/odb/pack_data_one.h @@ -0,0 +1,19 @@ +/* Just a few to make sure it's working, the rest is tested already */ +static const char *packed_objects_one[] = { + "9fcf811e00fa469688943a9152c16d4ee90fb9a9", + "a93f42a5b5e9de40fa645a9ff1e276a021c9542b", + "12bf5f3e3470d90db177ccf1b5e8126409377fc6", + "ed1ea164cdbe3c4b200fb4fa19861ea90eaee222", + "dfae6ed8f6dd8acc3b40a31811ea316239223559", + "aefe66d192771201e369fde830530f4475beec30", + "775e4b4c1296e9e3104f2a36ca9cf9356a130959", + "412ec4e4a6a7419bc1be00561fe474e54cb499fe", + "236e7579fed7763be77209efb8708960982f3cb3", + "09fe9364461cf60dd1c46b0e9545b1e47bb1a297", + "d76d8a6390d1cf32138d98a91b1eb7e0275a12f5", + "d0fdf2dcff2f548952eec536ccc6d266550041bc", + "a20d733a9fa79fa5b4cbb9639864f93325ec27a6", + "785d3fe8e7db5ade2c2242fecd46c32a7f4dc59f", + "4d8d0fd9cb6045075385701c3f933ec13345e9c4", + "0cfd861bd547b6520d1fc2e190e8359e0a9c9b90" +}; diff --git a/tests/libgit2/odb/packed.c b/tests/libgit2/odb/packed.c new file mode 100644 index 000000000..3d502ed6d --- /dev/null +++ b/tests/libgit2/odb/packed.c @@ -0,0 +1,79 @@ +#include "clar_libgit2.h" +#include "odb.h" +#include "pack_data.h" + +static git_odb *_odb; + +void test_odb_packed__initialize(void) +{ + cl_git_pass(git_odb_open(&_odb, cl_fixture("testrepo.git/objects"))); +} + +void test_odb_packed__cleanup(void) +{ + git_odb_free(_odb); + _odb = NULL; +} + +void test_odb_packed__mass_read(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(packed_objects); ++i) { + git_oid id; + git_odb_object *obj; + + cl_git_pass(git_oid_fromstr(&id, packed_objects[i])); + cl_assert(git_odb_exists(_odb, &id) == 1); + cl_git_pass(git_odb_read(&obj, _odb, &id)); + + git_odb_object_free(obj); + } +} + +void test_odb_packed__read_header_0(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(packed_objects); ++i) { + git_oid id; + git_odb_object *obj; + size_t len; + git_object_t type; + + cl_git_pass(git_oid_fromstr(&id, packed_objects[i])); + + cl_git_pass(git_odb_read(&obj, _odb, &id)); + cl_git_pass(git_odb_read_header(&len, &type, _odb, &id)); + + cl_assert(obj->cached.size == len); + cl_assert(obj->cached.type == type); + + git_odb_object_free(obj); + } +} + +void test_odb_packed__read_header_1(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(loose_objects); ++i) { + git_oid id; + git_odb_object *obj; + size_t len; + git_object_t type; + + cl_git_pass(git_oid_fromstr(&id, loose_objects[i])); + + cl_assert(git_odb_exists(_odb, &id) == 1); + + cl_git_pass(git_odb_read(&obj, _odb, &id)); + cl_git_pass(git_odb_read_header(&len, &type, _odb, &id)); + + cl_assert(obj->cached.size == len); + cl_assert(obj->cached.type == type); + + git_odb_object_free(obj); + } +} + diff --git a/tests/libgit2/odb/packed_one.c b/tests/libgit2/odb/packed_one.c new file mode 100644 index 000000000..17cd4f760 --- /dev/null +++ b/tests/libgit2/odb/packed_one.c @@ -0,0 +1,60 @@ +#include "clar_libgit2.h" +#include "git2/odb_backend.h" + +#include "pack_data_one.h" +#include "pack.h" + +static git_odb *_odb; + +void test_odb_packed_one__initialize(void) +{ + git_odb_backend *backend = NULL; + + cl_git_pass(git_odb_new(&_odb)); + cl_git_pass(git_odb_backend_one_pack(&backend, cl_fixture("testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx"))); + cl_git_pass(git_odb_add_backend(_odb, backend, 1)); +} + +void test_odb_packed_one__cleanup(void) +{ + git_odb_free(_odb); + _odb = NULL; +} + +void test_odb_packed_one__mass_read(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(packed_objects_one); ++i) { + git_oid id; + git_odb_object *obj; + + cl_git_pass(git_oid_fromstr(&id, packed_objects_one[i])); + cl_assert(git_odb_exists(_odb, &id) == 1); + cl_git_pass(git_odb_read(&obj, _odb, &id)); + + git_odb_object_free(obj); + } +} + +void test_odb_packed_one__read_header_0(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(packed_objects_one); ++i) { + git_oid id; + git_odb_object *obj; + size_t len; + git_object_t type; + + cl_git_pass(git_oid_fromstr(&id, packed_objects_one[i])); + + cl_git_pass(git_odb_read(&obj, _odb, &id)); + cl_git_pass(git_odb_read_header(&len, &type, _odb, &id)); + + cl_assert(obj->cached.size == len); + cl_assert(obj->cached.type == type); + + git_odb_object_free(obj); + } +} diff --git a/tests/libgit2/odb/sorting.c b/tests/libgit2/odb/sorting.c new file mode 100644 index 000000000..e027230fa --- /dev/null +++ b/tests/libgit2/odb/sorting.c @@ -0,0 +1,99 @@ +#include "clar_libgit2.h" +#include "git2/sys/odb_backend.h" +#include "odb.h" + +typedef struct { + git_odb_backend base; + size_t position; +} fake_backend; + +static git_odb_backend *new_backend(size_t position) +{ + fake_backend *b; + + b = git__calloc(1, sizeof(fake_backend)); + if (b == NULL) + return NULL; + + b->base.free = (void (*)(git_odb_backend *)) git__free; + b->base.version = GIT_ODB_BACKEND_VERSION; + b->position = position; + return (git_odb_backend *)b; +} + +static void check_backend_sorting(git_odb *odb) +{ + size_t i, max_i = git_odb_num_backends(odb); + fake_backend *internal; + + for (i = 0; i < max_i; ++i) { + cl_git_pass(git_odb_get_backend((git_odb_backend **)&internal, odb, i)); + cl_assert(internal != NULL); + cl_assert_equal_sz(i, internal->position); + } +} + +static git_odb *_odb; + +void test_odb_sorting__initialize(void) +{ + cl_git_pass(git_odb_new(&_odb)); +} + +void test_odb_sorting__cleanup(void) +{ + git_odb_free(_odb); + _odb = NULL; + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ODB_LOOSE_PRIORITY, + GIT_ODB_DEFAULT_LOOSE_PRIORITY)); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ODB_PACKED_PRIORITY, + GIT_ODB_DEFAULT_PACKED_PRIORITY)); +} + +void test_odb_sorting__basic_backends_sorting(void) +{ + cl_git_pass(git_odb_add_backend(_odb, new_backend(0), 5)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(2), 3)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(1), 4)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(3), 1)); + + check_backend_sorting(_odb); +} + +void test_odb_sorting__alternate_backends_sorting(void) +{ + cl_git_pass(git_odb_add_backend(_odb, new_backend(1), 5)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(5), 3)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(3), 4)); + cl_git_pass(git_odb_add_backend(_odb, new_backend(7), 1)); + cl_git_pass(git_odb_add_alternate(_odb, new_backend(0), 5)); + cl_git_pass(git_odb_add_alternate(_odb, new_backend(4), 3)); + cl_git_pass(git_odb_add_alternate(_odb, new_backend(2), 4)); + cl_git_pass(git_odb_add_alternate(_odb, new_backend(6), 1)); + + check_backend_sorting(_odb); +} + +void test_odb_sorting__override_default_backend_priority(void) +{ + git_odb *new_odb; + git_odb_backend *loose, *packed, *backend; + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ODB_LOOSE_PRIORITY, 5)); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ODB_PACKED_PRIORITY, 3)); + git_odb_backend_pack(&packed, "./testrepo.git/objects"); + git_odb_backend_loose(&loose, "./testrepo.git/objects", -1, 0, 0, 0); + + cl_git_pass(git_odb_open(&new_odb, cl_fixture("testrepo.git/objects"))); + cl_assert_equal_sz(2, git_odb_num_backends(new_odb)); + + cl_git_pass(git_odb_get_backend(&backend, new_odb, 0)); + cl_assert_equal_p(loose->read, backend->read); + + cl_git_pass(git_odb_get_backend(&backend, new_odb, 1)); + cl_assert_equal_p(packed->read, backend->read); + + git_odb_free(new_odb); + loose->free(loose); + packed->free(packed); +} diff --git a/tests/libgit2/odb/streamwrite.c b/tests/libgit2/odb/streamwrite.c new file mode 100644 index 000000000..c174f6974 --- /dev/null +++ b/tests/libgit2/odb/streamwrite.c @@ -0,0 +1,56 @@ +#include "clar_libgit2.h" +#include "git2/odb_backend.h" + +static git_repository *repo; +static git_odb *odb; +static git_odb_stream *stream; + +void test_odb_streamwrite__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_odb(&odb, repo)); + + cl_git_pass(git_odb_open_wstream(&stream, odb, 14, GIT_OBJECT_BLOB)); + cl_assert_equal_sz(14, stream->declared_size); +} + +void test_odb_streamwrite__cleanup(void) +{ + git_odb_stream_free(stream); + git_odb_free(odb); + cl_git_sandbox_cleanup(); +} + +void test_odb_streamwrite__can_accept_chunks(void) +{ + git_oid oid; + + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8)); + cl_assert_equal_sz(8, stream->received_bytes); + + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 6)); + cl_assert_equal_sz(8 + 6, stream->received_bytes); + + cl_git_pass(git_odb_stream_finalize_write(&oid, stream)); +} + +void test_odb_streamwrite__can_detect_missing_bytes(void) +{ + git_oid oid; + + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8)); + cl_assert_equal_sz(8, stream->received_bytes); + + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 4)); + cl_assert_equal_sz(8 + 4, stream->received_bytes); + + cl_git_fail(git_odb_stream_finalize_write(&oid, stream)); +} + +void test_odb_streamwrite__can_detect_additional_bytes(void) +{ + cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8)); + cl_assert_equal_sz(8, stream->received_bytes); + + cl_git_fail(git_odb_stream_write(stream, "deadbeef", 7)); +} diff --git a/tests/libgit2/online/badssl.c b/tests/libgit2/online/badssl.c new file mode 100644 index 000000000..6735e9cdb --- /dev/null +++ b/tests/libgit2/online/badssl.c @@ -0,0 +1,75 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" + +static git_repository *g_repo; + +#ifdef GIT_HTTPS +static bool g_has_ssl = true; +#else +static bool g_has_ssl = false; +#endif + +static int cert_check_assert_invalid(git_cert *cert, int valid, const char* host, void *payload) +{ + GIT_UNUSED(cert); GIT_UNUSED(host); GIT_UNUSED(payload); + + cl_assert_equal_i(0, valid); + + return GIT_ECERTIFICATE; +} + +void test_online_badssl__expired(void) +{ + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid; + + if (!g_has_ssl) + cl_skip(); + + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://expired.badssl.com/fake.git", "./fake", NULL)); + + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://expired.badssl.com/fake.git", "./fake", &opts)); +} + +void test_online_badssl__wrong_host(void) +{ + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid; + + if (!g_has_ssl) + cl_skip(); + + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", NULL)); + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", &opts)); +} + +void test_online_badssl__self_signed(void) +{ + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid; + + if (!g_has_ssl) + cl_skip(); + + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://self-signed.badssl.com/fake.git", "./fake", NULL)); + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://self-signed.badssl.com/fake.git", "./fake", &opts)); +} + +void test_online_badssl__old_cipher(void) +{ + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid; + + if (!g_has_ssl) + cl_skip(); + + cl_git_fail(git_clone(&g_repo, "https://rc4.badssl.com/fake.git", "./fake", NULL)); + cl_git_fail(git_clone(&g_repo, "https://rc4.badssl.com/fake.git", "./fake", &opts)); +} diff --git a/tests/libgit2/online/clone.c b/tests/libgit2/online/clone.c new file mode 100644 index 000000000..9f2580bb3 --- /dev/null +++ b/tests/libgit2/online/clone.c @@ -0,0 +1,1004 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" +#include "git2/cred_helpers.h" +#include "remote.h" +#include "futils.h" +#include "refs.h" + +#define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository" +#define LIVE_EMPTYREPO_URL "http://github.com/libgit2/TestEmptyRepository" +#define BB_REPO_URL "https://libgit3@bitbucket.org/libgit2/testgitrepository.git" +#define BB_REPO_URL_WITH_PASS "https://libgit3:libgit3@bitbucket.org/libgit2/testgitrepository.git" +#define BB_REPO_URL_WITH_WRONG_PASS "https://libgit3:wrong@bitbucket.org/libgit2/testgitrepository.git" +#define GOOGLESOURCE_REPO_URL "https://chromium.googlesource.com/external/github.com/sergi/go-diff" + +#define SSH_REPO_URL "ssh://github.com/libgit2/TestGitRepository" + +static git_repository *g_repo; +static git_clone_options g_options; + +static char *_remote_url = NULL; +static char *_remote_user = NULL; +static char *_remote_pass = NULL; +static char *_remote_sslnoverify = NULL; +static char *_remote_ssh_pubkey = NULL; +static char *_remote_ssh_privkey = NULL; +static char *_remote_ssh_passphrase = NULL; +static char *_remote_ssh_fingerprint = NULL; +static char *_remote_proxy_scheme = NULL; +static char *_remote_proxy_host = NULL; +static char *_remote_proxy_user = NULL; +static char *_remote_proxy_pass = NULL; +static char *_remote_proxy_selfsigned = NULL; +static char *_remote_expectcontinue = NULL; +static char *_remote_redirect_initial = NULL; +static char *_remote_redirect_subsequent = NULL; + +static int _orig_proxies_need_reset = 0; +static char *_orig_http_proxy = NULL; +static char *_orig_https_proxy = NULL; +static char *_orig_no_proxy = NULL; + +static int ssl_cert(git_cert *cert, int valid, const char *host, void *payload) +{ + GIT_UNUSED(cert); + GIT_UNUSED(host); + GIT_UNUSED(payload); + + if (_remote_sslnoverify != NULL) + valid = 1; + + return valid ? 0 : GIT_ECERTIFICATE; +} + +void test_online_clone__initialize(void) +{ + git_checkout_options dummy_opts = GIT_CHECKOUT_OPTIONS_INIT; + git_fetch_options dummy_fetch = GIT_FETCH_OPTIONS_INIT; + + g_repo = NULL; + + memset(&g_options, 0, sizeof(git_clone_options)); + g_options.version = GIT_CLONE_OPTIONS_VERSION; + g_options.checkout_opts = dummy_opts; + g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + g_options.fetch_opts = dummy_fetch; + g_options.fetch_opts.callbacks.certificate_check = ssl_cert; + + _remote_url = cl_getenv("GITTEST_REMOTE_URL"); + _remote_user = cl_getenv("GITTEST_REMOTE_USER"); + _remote_pass = cl_getenv("GITTEST_REMOTE_PASS"); + _remote_sslnoverify = cl_getenv("GITTEST_REMOTE_SSL_NOVERIFY"); + _remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY"); + _remote_ssh_privkey = cl_getenv("GITTEST_REMOTE_SSH_KEY"); + _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); + _remote_ssh_fingerprint = cl_getenv("GITTEST_REMOTE_SSH_FINGERPRINT"); + _remote_proxy_scheme = cl_getenv("GITTEST_REMOTE_PROXY_SCHEME"); + _remote_proxy_host = cl_getenv("GITTEST_REMOTE_PROXY_HOST"); + _remote_proxy_user = cl_getenv("GITTEST_REMOTE_PROXY_USER"); + _remote_proxy_pass = cl_getenv("GITTEST_REMOTE_PROXY_PASS"); + _remote_proxy_selfsigned = cl_getenv("GITTEST_REMOTE_PROXY_SELFSIGNED"); + _remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE"); + _remote_redirect_initial = cl_getenv("GITTEST_REMOTE_REDIRECT_INITIAL"); + _remote_redirect_subsequent = cl_getenv("GITTEST_REMOTE_REDIRECT_SUBSEQUENT"); + + if (_remote_expectcontinue) + git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1); + + _orig_proxies_need_reset = 0; +} + +void test_online_clone__cleanup(void) +{ + if (g_repo) { + git_repository_free(g_repo); + g_repo = NULL; + } + cl_fixture_cleanup("./foo"); + cl_fixture_cleanup("./initial"); + cl_fixture_cleanup("./subsequent"); + + git__free(_remote_url); + git__free(_remote_user); + git__free(_remote_pass); + git__free(_remote_sslnoverify); + git__free(_remote_ssh_pubkey); + git__free(_remote_ssh_privkey); + git__free(_remote_ssh_passphrase); + git__free(_remote_ssh_fingerprint); + git__free(_remote_proxy_scheme); + git__free(_remote_proxy_host); + git__free(_remote_proxy_user); + git__free(_remote_proxy_pass); + git__free(_remote_proxy_selfsigned); + git__free(_remote_expectcontinue); + git__free(_remote_redirect_initial); + git__free(_remote_redirect_subsequent); + + if (_orig_proxies_need_reset) { + cl_setenv("HTTP_PROXY", _orig_http_proxy); + cl_setenv("HTTPS_PROXY", _orig_https_proxy); + cl_setenv("NO_PROXY", _orig_no_proxy); + + git__free(_orig_http_proxy); + git__free(_orig_https_proxy); + git__free(_orig_no_proxy); + } + + git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, NULL); +} + +void test_online_clone__network_full(void) +{ + git_remote *origin; + + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + cl_assert(!git_repository_is_bare(g_repo)); + cl_git_pass(git_remote_lookup(&origin, g_repo, "origin")); + + cl_assert_equal_i(GIT_REMOTE_DOWNLOAD_TAGS_AUTO, origin->download_tags); + + git_remote_free(origin); +} + +void test_online_clone__network_bare(void) +{ + git_remote *origin; + + g_options.bare = true; + + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + cl_assert(git_repository_is_bare(g_repo)); + cl_git_pass(git_remote_lookup(&origin, g_repo, "origin")); + + git_remote_free(origin); +} + +void test_online_clone__empty_repository(void) +{ + git_reference *head; + + cl_git_pass(git_clone(&g_repo, LIVE_EMPTYREPO_URL, "./foo", &g_options)); + + cl_assert_equal_i(true, git_repository_is_empty(g_repo)); + cl_assert_equal_i(true, git_repository_head_unborn(g_repo)); + + cl_git_pass(git_reference_lookup(&head, g_repo, GIT_HEAD_FILE)); + cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head)); + cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); + + git_reference_free(head); +} + +static void checkout_progress(const char *path, size_t cur, size_t tot, void *payload) +{ + bool *was_called = (bool*)payload; + GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); + (*was_called) = true; +} + +static int fetch_progress(const git_indexer_progress *stats, void *payload) +{ + bool *was_called = (bool*)payload; + GIT_UNUSED(stats); + (*was_called) = true; + return 0; +} + +void test_online_clone__can_checkout_a_cloned_repo(void) +{ + git_str path = GIT_STR_INIT; + git_reference *head, *remote_head; + bool checkout_progress_cb_was_called = false, + fetch_progress_cb_was_called = false; + + g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + g_options.checkout_opts.progress_cb = &checkout_progress; + g_options.checkout_opts.progress_payload = &checkout_progress_cb_was_called; + g_options.fetch_opts.callbacks.transfer_progress = &fetch_progress; + g_options.fetch_opts.callbacks.payload = &fetch_progress_cb_was_called; + + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "master.txt")); + cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&path))); + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head)); + cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); + + cl_git_pass(git_reference_lookup(&remote_head, g_repo, "refs/remotes/origin/HEAD")); + cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(remote_head)); + cl_assert_equal_s("refs/remotes/origin/master", git_reference_symbolic_target(remote_head)); + + cl_assert_equal_i(true, checkout_progress_cb_was_called); + cl_assert_equal_i(true, fetch_progress_cb_was_called); + + git_reference_free(remote_head); + git_reference_free(head); + git_str_dispose(&path); +} + +static int remote_mirror_cb(git_remote **out, git_repository *repo, + const char *name, const char *url, void *payload) +{ + int error; + git_remote *remote; + + GIT_UNUSED(payload); + + if ((error = git_remote_create_with_fetchspec(&remote, repo, name, url, "+refs/*:refs/*")) < 0) + return error; + + *out = remote; + return 0; +} + +void test_online_clone__clone_mirror(void) +{ + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + git_reference *head; + + bool fetch_progress_cb_was_called = false; + + opts.fetch_opts.callbacks.transfer_progress = &fetch_progress; + opts.fetch_opts.callbacks.payload = &fetch_progress_cb_was_called; + + opts.bare = true; + opts.remote_cb = remote_mirror_cb; + + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo.git", &opts)); + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head)); + cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); + + cl_assert_equal_i(true, fetch_progress_cb_was_called); + + git_reference_free(head); + git_repository_free(g_repo); + g_repo = NULL; + + cl_fixture_cleanup("./foo.git"); +} + +static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *payload) +{ + int *callcount = (int*)payload; + GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b); + *callcount = *callcount + 1; + return 0; +} + +void test_online_clone__custom_remote_callbacks(void) +{ + int callcount = 0; + + g_options.fetch_opts.callbacks.update_tips = update_tips; + g_options.fetch_opts.callbacks.payload = &callcount; + + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + cl_assert(callcount > 0); +} + +void test_online_clone__custom_headers(void) +{ + char *empty_header = ""; + char *unnamed_header = "this is a header about nothing"; + char *newlines = "X-Custom: almost OK\n"; + char *conflict = "Accept: defined-by-git"; + char *ok = "X-Custom: this should be ok"; + + g_options.fetch_opts.custom_headers.count = 1; + + g_options.fetch_opts.custom_headers.strings = &empty_header; + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + + g_options.fetch_opts.custom_headers.strings = &unnamed_header; + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + + g_options.fetch_opts.custom_headers.strings = &newlines; + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + + g_options.fetch_opts.custom_headers.strings = &conflict; + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + + /* Finally, we got it right! */ + g_options.fetch_opts.custom_headers.strings = &ok; + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); +} + +static int cred_failure_cb( + git_credential **cred, + const char *url, + const char *username_from_url, + unsigned int allowed_types, + void *data) +{ + GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url); + GIT_UNUSED(allowed_types); GIT_UNUSED(data); + return -172; +} + +void test_online_clone__cred_callback_failure_return_code_is_tunnelled(void) +{ + git__free(_remote_url); + git__free(_remote_user); + + _remote_url = git__strdup("https://github.com/libgit2/non-existent"); + _remote_user = git__strdup("libgit2test"); + + g_options.fetch_opts.callbacks.credentials = cred_failure_cb; + + cl_git_fail_with(-172, git_clone(&g_repo, _remote_url, "./foo", &g_options)); +} + +static int cred_count_calls_cb(git_credential **cred, const char *url, const char *user, + unsigned int allowed_types, void *data) +{ + size_t *counter = (size_t *) data; + + GIT_UNUSED(url); GIT_UNUSED(user); GIT_UNUSED(allowed_types); + + if (allowed_types == GIT_CREDENTIAL_USERNAME) + return git_credential_username_new(cred, "foo"); + + (*counter)++; + + if (*counter == 3) + return GIT_EUSER; + + return git_credential_userpass_plaintext_new(cred, "foo", "bar"); +} + +void test_online_clone__cred_callback_called_again_on_auth_failure(void) +{ + size_t counter = 0; + + git__free(_remote_url); + git__free(_remote_user); + + _remote_url = git__strdup("https://gitlab.com/libgit2/non-existent"); + _remote_user = git__strdup("libgit2test"); + + g_options.fetch_opts.callbacks.credentials = cred_count_calls_cb; + g_options.fetch_opts.callbacks.payload = &counter; + + cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, _remote_url, "./foo", &g_options)); + cl_assert_equal_i(3, counter); +} + +static int cred_default( + git_credential **cred, + const char *url, + const char *user_from_url, + unsigned int allowed_types, + void *payload) +{ + GIT_UNUSED(url); + GIT_UNUSED(user_from_url); + GIT_UNUSED(payload); + + if (!(allowed_types & GIT_CREDENTIAL_DEFAULT)) + return 0; + + return git_credential_default_new(cred); +} + +void test_online_clone__credentials(void) +{ + /* Remote URL environment variable must be set. + * User and password are optional. + */ + git_credential_userpass_payload user_pass = { + _remote_user, + _remote_pass + }; + + if (!_remote_url) + clar__skip(); + + if (cl_is_env_set("GITTEST_REMOTE_DEFAULT")) { + g_options.fetch_opts.callbacks.credentials = cred_default; + } else { + g_options.fetch_opts.callbacks.credentials = git_credential_userpass; + g_options.fetch_opts.callbacks.payload = &user_pass; + } + + cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options)); + git_repository_free(g_repo); g_repo = NULL; + cl_fixture_cleanup("./foo"); +} + +void test_online_clone__credentials_via_custom_headers(void) +{ + const char *creds = "libgit3:libgit3"; + git_str auth = GIT_STR_INIT; + + cl_git_pass(git_str_puts(&auth, "Authorization: Basic ")); + cl_git_pass(git_str_encode_base64(&auth, creds, strlen(creds))); + g_options.fetch_opts.custom_headers.count = 1; + g_options.fetch_opts.custom_headers.strings = &auth.ptr; + + cl_git_pass(git_clone(&g_repo, "https://bitbucket.org/libgit2/testgitrepository.git", "./foo", &g_options)); + + git_str_dispose(&auth); +} + +void test_online_clone__bitbucket_style(void) +{ + git_credential_userpass_payload user_pass = { + "libgit3", "libgit3" + }; + + g_options.fetch_opts.callbacks.credentials = git_credential_userpass; + g_options.fetch_opts.callbacks.payload = &user_pass; + + cl_git_pass(git_clone(&g_repo, BB_REPO_URL, "./foo", &g_options)); + git_repository_free(g_repo); g_repo = NULL; + cl_fixture_cleanup("./foo"); +} + +void test_online_clone__bitbucket_uses_creds_in_url(void) +{ + git_credential_userpass_payload user_pass = { + "libgit2", "wrong" + }; + + g_options.fetch_opts.callbacks.credentials = git_credential_userpass; + g_options.fetch_opts.callbacks.payload = &user_pass; + + /* + * Correct user and pass are in the URL; the (incorrect) creds in + * the `git_credential_userpass_payload` should be ignored. + */ + cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_PASS, "./foo", &g_options)); + git_repository_free(g_repo); g_repo = NULL; + cl_fixture_cleanup("./foo"); +} + +void test_online_clone__bitbucket_falls_back_to_specified_creds(void) +{ + git_credential_userpass_payload user_pass = { + "libgit2", "libgit2" + }; + + g_options.fetch_opts.callbacks.credentials = git_credential_userpass; + g_options.fetch_opts.callbacks.payload = &user_pass; + + /* + * TODO: as of March 2018, bitbucket sporadically fails with + * 403s instead of replying with a 401 - but only sometimes. + */ + cl_skip(); + + /* + * Incorrect user and pass are in the URL; the (correct) creds in + * the `git_credential_userpass_payload` should be used as a fallback. + */ + cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_WRONG_PASS, "./foo", &g_options)); + git_repository_free(g_repo); g_repo = NULL; + cl_fixture_cleanup("./foo"); +} + +void test_online_clone__googlesource(void) +{ + cl_git_pass(git_clone(&g_repo, GOOGLESOURCE_REPO_URL, "./foo", &g_options)); + git_repository_free(g_repo); g_repo = NULL; + cl_fixture_cleanup("./foo"); +} + +static int cancel_at_half(const git_indexer_progress *stats, void *payload) +{ + GIT_UNUSED(payload); + + if (stats->received_objects > (stats->total_objects/2)) + return 4321; + return 0; +} + +void test_online_clone__can_cancel(void) +{ + g_options.fetch_opts.callbacks.transfer_progress = cancel_at_half; + + cl_git_fail_with(4321, + git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); +} + +static int cred_cb(git_credential **cred, const char *url, const char *user_from_url, + unsigned int allowed_types, void *payload) +{ + GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload); + + if (allowed_types & GIT_CREDENTIAL_USERNAME) + return git_credential_username_new(cred, _remote_user); + + if (allowed_types & GIT_CREDENTIAL_SSH_KEY) + return git_credential_ssh_key_new(cred, + _remote_user, _remote_ssh_pubkey, + _remote_ssh_privkey, _remote_ssh_passphrase); + + git_error_set(GIT_ERROR_NET, "unexpected cred type"); + return -1; +} + +static int check_ssh_auth_methods(git_credential **cred, const char *url, const char *username_from_url, + unsigned int allowed_types, void *data) +{ + int *with_user = (int *) data; + GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(data); + + if (!*with_user) + cl_assert_equal_i(GIT_CREDENTIAL_USERNAME, allowed_types); + else + cl_assert(!(allowed_types & GIT_CREDENTIAL_USERNAME)); + + return GIT_EUSER; +} + +void test_online_clone__ssh_auth_methods(void) +{ + int with_user; + +#ifndef GIT_SSH + clar__skip(); +#endif + g_options.fetch_opts.callbacks.credentials = check_ssh_auth_methods; + g_options.fetch_opts.callbacks.payload = &with_user; + g_options.fetch_opts.callbacks.certificate_check = NULL; + + with_user = 0; + cl_git_fail_with(GIT_EUSER, + git_clone(&g_repo, SSH_REPO_URL, "./foo", &g_options)); + + with_user = 1; + cl_git_fail_with(GIT_EUSER, + git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options)); +} + +static int custom_remote_ssh_with_paths( + git_remote **out, + git_repository *repo, + const char *name, + const char *url, + void *payload) +{ + int error; + + GIT_UNUSED(payload); + + if ((error = git_remote_create(out, repo, name, url)) < 0) + return error; + + return 0; +} + +void test_online_clone__ssh_with_paths(void) +{ + char *bad_paths[] = { + "/bin/yes", + "/bin/false", + }; + char *good_paths[] = { + "/usr/bin/git-upload-pack", + "/usr/bin/git-receive-pack", + }; + git_strarray arr = { + bad_paths, + 2, + }; + +#ifndef GIT_SSH + clar__skip(); +#endif + if (!_remote_url || !_remote_user || strncmp(_remote_url, "ssh://", 5) != 0) + clar__skip(); + + g_options.remote_cb = custom_remote_ssh_with_paths; + g_options.fetch_opts.callbacks.transport = git_transport_ssh_with_paths; + g_options.fetch_opts.callbacks.credentials = cred_cb; + g_options.fetch_opts.callbacks.payload = &arr; + g_options.fetch_opts.callbacks.certificate_check = NULL; + + cl_git_fail(git_clone(&g_repo, _remote_url, "./foo", &g_options)); + + arr.strings = good_paths; + cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options)); +} + +static int cred_foo_bar(git_credential **cred, const char *url, const char *username_from_url, + unsigned int allowed_types, void *data) + +{ + GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(allowed_types); GIT_UNUSED(data); + + return git_credential_userpass_plaintext_new(cred, "foo", "bar"); +} + +void test_online_clone__ssh_cannot_change_username(void) +{ +#ifndef GIT_SSH + clar__skip(); +#endif + g_options.fetch_opts.callbacks.credentials = cred_foo_bar; + + cl_git_fail(git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options)); +} + +static int ssh_certificate_check(git_cert *cert, int valid, const char *host, void *payload) +{ + git_cert_hostkey *key; + git_oid expected = {{0}}, actual = {{0}}; + + GIT_UNUSED(valid); + GIT_UNUSED(payload); + + cl_assert(_remote_ssh_fingerprint); + + cl_git_pass(git_oid_fromstrp(&expected, _remote_ssh_fingerprint)); + cl_assert_equal_i(GIT_CERT_HOSTKEY_LIBSSH2, cert->cert_type); + key = (git_cert_hostkey *) cert; + + /* + * We need to figure out how long our input was to check for + * the type. Here we abuse the fact that both hashes fit into + * our git_oid type. + */ + if (strlen(_remote_ssh_fingerprint) == 32 && key->type & GIT_CERT_SSH_MD5) { + memcpy(&actual.id, key->hash_md5, 16); + } else if (strlen(_remote_ssh_fingerprint) == 40 && key->type & GIT_CERT_SSH_SHA1) { + memcpy(&actual, key->hash_sha1, 20); + } else { + cl_fail("Cannot find a usable SSH hash"); + } + + cl_assert(!memcmp(&expected, &actual, 20)); + + cl_assert_equal_s("localhost", host); + + return GIT_EUSER; +} + +void test_online_clone__ssh_cert(void) +{ + g_options.fetch_opts.callbacks.certificate_check = ssh_certificate_check; + + if (!_remote_ssh_fingerprint) + cl_skip(); + + cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, _remote_url, "./foo", &g_options)); +} + +static char *read_key_file(const char *path) +{ + FILE *f; + char *buf; + long key_length; + + if (!path || !*path) + return NULL; + + cl_assert((f = fopen(path, "r")) != NULL); + cl_assert(fseek(f, 0, SEEK_END) != -1); + cl_assert((key_length = ftell(f)) != -1); + cl_assert(fseek(f, 0, SEEK_SET) != -1); + cl_assert((buf = malloc(key_length)) != NULL); + cl_assert(fread(buf, key_length, 1, f) == 1); + fclose(f); + + return buf; +} + +static int ssh_memory_cred_cb(git_credential **cred, const char *url, const char *user_from_url, + unsigned int allowed_types, void *payload) +{ + GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload); + + if (allowed_types & GIT_CREDENTIAL_USERNAME) + return git_credential_username_new(cred, _remote_user); + + if (allowed_types & GIT_CREDENTIAL_SSH_KEY) + { + char *pubkey = read_key_file(_remote_ssh_pubkey); + char *privkey = read_key_file(_remote_ssh_privkey); + + int ret = git_credential_ssh_key_memory_new(cred, _remote_user, pubkey, privkey, _remote_ssh_passphrase); + + if (privkey) + free(privkey); + if (pubkey) + free(pubkey); + return ret; + } + + git_error_set(GIT_ERROR_NET, "unexpected cred type"); + return -1; +} + +void test_online_clone__ssh_memory_auth(void) +{ +#ifndef GIT_SSH_MEMORY_CREDENTIALS + clar__skip(); +#endif + if (!_remote_url || !_remote_user || !_remote_ssh_privkey || strncmp(_remote_url, "ssh://", 5) != 0) + clar__skip(); + + g_options.fetch_opts.callbacks.credentials = ssh_memory_cred_cb; + + cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options)); +} + +static int fail_certificate_check(git_cert *cert, int valid, const char *host, void *payload) +{ + GIT_UNUSED(cert); + GIT_UNUSED(valid); + GIT_UNUSED(host); + GIT_UNUSED(payload); + + return GIT_ECERTIFICATE; +} + +void test_online_clone__certificate_invalid(void) +{ + g_options.fetch_opts.callbacks.certificate_check = fail_certificate_check; + + cl_git_fail_with(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options), + GIT_ECERTIFICATE); + +#ifdef GIT_SSH + cl_git_fail_with(git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options), + GIT_ECERTIFICATE); +#endif +} + +static int succeed_certificate_check(git_cert *cert, int valid, const char *host, void *payload) +{ + GIT_UNUSED(cert); + GIT_UNUSED(valid); + GIT_UNUSED(payload); + + cl_assert_equal_s("github.com", host); + + return 0; +} + +void test_online_clone__certificate_valid(void) +{ + g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check; + + cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options)); +} + +void test_online_clone__start_with_http(void) +{ + g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check; + + cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); +} + +static int called_proxy_creds; +static int proxy_cred_cb(git_credential **out, const char *url, const char *username, unsigned int allowed, void *payload) +{ + GIT_UNUSED(url); + GIT_UNUSED(username); + GIT_UNUSED(allowed); + GIT_UNUSED(payload); + + called_proxy_creds = 1; + return git_credential_userpass_plaintext_new(out, _remote_proxy_user, _remote_proxy_pass); +} + +static int proxy_cert_cb(git_cert *cert, int valid, const char *host, void *payload) +{ + char *colon; + size_t host_len; + + GIT_UNUSED(cert); + GIT_UNUSED(valid); + GIT_UNUSED(payload); + + cl_assert(_remote_proxy_host); + + if ((colon = strchr(_remote_proxy_host, ':')) != NULL) + host_len = (colon - _remote_proxy_host); + else + host_len = strlen(_remote_proxy_host); + + if (_remote_proxy_selfsigned != NULL && + strlen(host) == host_len && + strncmp(_remote_proxy_host, host, host_len) == 0) + valid = 1; + + return valid ? 0 : GIT_ECERTIFICATE; +} + +void test_online_clone__proxy_credentials_request(void) +{ + git_str url = GIT_STR_INIT; + + if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) + cl_skip(); + + cl_git_pass(git_str_printf(&url, "%s://%s/", + _remote_proxy_scheme ? _remote_proxy_scheme : "http", + _remote_proxy_host)); + + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; + g_options.fetch_opts.proxy_opts.url = url.ptr; + g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb; + g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; + called_proxy_creds = 0; + cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + cl_assert(called_proxy_creds); + + git_str_dispose(&url); +} + +void test_online_clone__proxy_credentials_in_url(void) +{ + git_str url = GIT_STR_INIT; + + if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) + cl_skip(); + + cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/", + _remote_proxy_scheme ? _remote_proxy_scheme : "http", + _remote_proxy_user, _remote_proxy_pass, _remote_proxy_host)); + + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; + g_options.fetch_opts.proxy_opts.url = url.ptr; + g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; + called_proxy_creds = 0; + cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + cl_assert(called_proxy_creds == 0); + + git_str_dispose(&url); +} + +void test_online_clone__proxy_credentials_in_environment(void) +{ + git_str url = GIT_STR_INIT; + + if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) + cl_skip(); + + _orig_http_proxy = cl_getenv("HTTP_PROXY"); + _orig_https_proxy = cl_getenv("HTTPS_PROXY"); + _orig_no_proxy = cl_getenv("NO_PROXY"); + _orig_proxies_need_reset = 1; + + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO; + g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; + + cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/", + _remote_proxy_scheme ? _remote_proxy_scheme : "http", + _remote_proxy_user, _remote_proxy_pass, _remote_proxy_host)); + + cl_setenv("HTTP_PROXY", url.ptr); + cl_setenv("HTTPS_PROXY", url.ptr); + cl_setenv("NO_PROXY", NULL); + + cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + + git_str_dispose(&url); +} + +void test_online_clone__proxy_credentials_in_url_https(void) +{ + git_str url = GIT_STR_INIT; + + if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) + cl_skip(); + + cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/", + _remote_proxy_scheme ? _remote_proxy_scheme : "http", + _remote_proxy_user, _remote_proxy_pass, _remote_proxy_host)); + + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; + g_options.fetch_opts.proxy_opts.url = url.ptr; + g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; + g_options.fetch_opts.callbacks.certificate_check = ssl_cert; + called_proxy_creds = 0; + cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + cl_assert(called_proxy_creds == 0); + + git_str_dispose(&url); +} + +void test_online_clone__proxy_auto_not_detected(void) +{ + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO; + + cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); +} + +void test_online_clone__proxy_cred_callback_after_failed_url_creds(void) +{ + git_str url = GIT_STR_INIT; + + if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) + cl_skip(); + + cl_git_pass(git_str_printf(&url, "%s://invalid_user_name:INVALID_pass_WORD@%s/", + _remote_proxy_scheme ? _remote_proxy_scheme : "http", + _remote_proxy_host)); + + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; + g_options.fetch_opts.proxy_opts.url = url.ptr; + g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb; + g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; + called_proxy_creds = 0; + cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + cl_assert(called_proxy_creds); + + git_str_dispose(&url); +} + +void test_online_clone__azurerepos(void) +{ + cl_git_pass(git_clone(&g_repo, "https://libgit2@dev.azure.com/libgit2/test/_git/test", "./foo", &g_options)); + cl_assert(git_fs_path_exists("./foo/master.txt")); +} + +void test_online_clone__path_whitespace(void) +{ + cl_git_pass(git_clone(&g_repo, "https://libgit2@dev.azure.com/libgit2/test/_git/spaces%20in%20the%20name", "./foo", &g_options)); + cl_assert(git_fs_path_exists("./foo/master.txt")); +} + +void test_online_clone__redirect_default_succeeds_for_initial(void) +{ + git_clone_options options = GIT_CLONE_OPTIONS_INIT; + + if (!_remote_redirect_initial || !_remote_redirect_subsequent) + cl_skip(); + + cl_git_pass(git_clone(&g_repo, _remote_redirect_initial, "./initial", &options)); +} + +void test_online_clone__redirect_default_fails_for_subsequent(void) +{ + git_clone_options options = GIT_CLONE_OPTIONS_INIT; + + if (!_remote_redirect_initial || !_remote_redirect_subsequent) + cl_skip(); + + cl_git_fail(git_clone(&g_repo, _remote_redirect_subsequent, "./fail", &options)); +} + +void test_online_clone__redirect_none(void) +{ + git_clone_options options = GIT_CLONE_OPTIONS_INIT; + + if (!_remote_redirect_initial) + cl_skip(); + + options.fetch_opts.follow_redirects = GIT_REMOTE_REDIRECT_NONE; + + cl_git_fail(git_clone(&g_repo, _remote_redirect_initial, "./fail", &options)); +} + +void test_online_clone__redirect_initial_succeeds_for_initial(void) +{ + git_clone_options options = GIT_CLONE_OPTIONS_INIT; + + if (!_remote_redirect_initial || !_remote_redirect_subsequent) + cl_skip(); + + options.fetch_opts.follow_redirects = GIT_REMOTE_REDIRECT_INITIAL; + + cl_git_pass(git_clone(&g_repo, _remote_redirect_initial, "./initial", &options)); +} + +void test_online_clone__redirect_initial_fails_for_subsequent(void) +{ + git_clone_options options = GIT_CLONE_OPTIONS_INIT; + + if (!_remote_redirect_initial || !_remote_redirect_subsequent) + cl_skip(); + + options.fetch_opts.follow_redirects = GIT_REMOTE_REDIRECT_INITIAL; + + cl_git_fail(git_clone(&g_repo, _remote_redirect_subsequent, "./fail", &options)); +} diff --git a/tests/libgit2/online/customcert.c b/tests/libgit2/online/customcert.c new file mode 100644 index 000000000..7932a9e68 --- /dev/null +++ b/tests/libgit2/online/customcert.c @@ -0,0 +1,79 @@ +#include "clar_libgit2.h" + +#include "path.h" +#include "git2/clone.h" +#include "git2/cred_helpers.h" +#include "remote.h" +#include "futils.h" +#include "refs.h" + +/* + * Certificate one is in the `certs` folder; certificate two is in the + * `self-signed.pem` file. + */ +#define CUSTOM_CERT_ONE_URL "https://test.libgit2.org:1443/anonymous/test.git" +#define CUSTOM_CERT_ONE_PATH "certs" + +#define CUSTOM_CERT_TWO_URL "https://test.libgit2.org:2443/anonymous/test.git" +#define CUSTOM_CERT_TWO_FILE "self-signed.pem" + +#if (GIT_OPENSSL || GIT_MBEDTLS) +static git_repository *g_repo; +static int initialized = false; +#endif + +void test_online_customcert__initialize(void) +{ +#if (GIT_OPENSSL || GIT_MBEDTLS) + g_repo = NULL; + + if (!initialized) { + git_str path = GIT_STR_INIT, file = GIT_STR_INIT; + char cwd[GIT_PATH_MAX]; + + cl_fixture_sandbox(CUSTOM_CERT_ONE_PATH); + cl_fixture_sandbox(CUSTOM_CERT_TWO_FILE); + + cl_must_pass(p_getcwd(cwd, GIT_PATH_MAX)); + cl_git_pass(git_str_joinpath(&path, cwd, CUSTOM_CERT_ONE_PATH)); + cl_git_pass(git_str_joinpath(&file, cwd, CUSTOM_CERT_TWO_FILE)); + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, + file.ptr, path.ptr)); + initialized = true; + + git_str_dispose(&file); + git_str_dispose(&path); + } +#endif +} + +void test_online_customcert__cleanup(void) +{ +#if (GIT_OPENSSL || GIT_MBEDTLS) + if (g_repo) { + git_repository_free(g_repo); + g_repo = NULL; + } + + cl_fixture_cleanup("./cloned"); + cl_fixture_cleanup(CUSTOM_CERT_ONE_PATH); + cl_fixture_cleanup(CUSTOM_CERT_TWO_FILE); +#endif +} + +void test_online_customcert__file(void) +{ +#if (GIT_OPENSSL || GIT_MBEDTLS) + cl_git_pass(git_clone(&g_repo, CUSTOM_CERT_ONE_URL, "./cloned", NULL)); + cl_assert(git_fs_path_exists("./cloned/master.txt")); +#endif +} + +void test_online_customcert__path(void) +{ +#if (GIT_OPENSSL || GIT_MBEDTLS) + cl_git_pass(git_clone(&g_repo, CUSTOM_CERT_TWO_URL, "./cloned", NULL)); + cl_assert(git_fs_path_exists("./cloned/master.txt")); +#endif +} diff --git a/tests/libgit2/online/fetch.c b/tests/libgit2/online/fetch.c new file mode 100644 index 000000000..7334f7e8b --- /dev/null +++ b/tests/libgit2/online/fetch.c @@ -0,0 +1,323 @@ +#include "clar_libgit2.h" +#include "futils.h" + +static git_repository *_repo; +static int counter; + +static char *_remote_proxy_scheme = NULL; +static char *_remote_proxy_host = NULL; +static char *_remote_proxy_user = NULL; +static char *_remote_proxy_pass = NULL; +static char *_remote_redirect_initial = NULL; +static char *_remote_redirect_subsequent = NULL; + +void test_online_fetch__initialize(void) +{ + cl_git_pass(git_repository_init(&_repo, "./fetch", 0)); + + _remote_proxy_scheme = cl_getenv("GITTEST_REMOTE_PROXY_SCHEME"); + _remote_proxy_host = cl_getenv("GITTEST_REMOTE_PROXY_HOST"); + _remote_proxy_user = cl_getenv("GITTEST_REMOTE_PROXY_USER"); + _remote_proxy_pass = cl_getenv("GITTEST_REMOTE_PROXY_PASS"); + _remote_redirect_initial = cl_getenv("GITTEST_REMOTE_REDIRECT_INITIAL"); + _remote_redirect_subsequent = cl_getenv("GITTEST_REMOTE_REDIRECT_SUBSEQUENT"); +} + +void test_online_fetch__cleanup(void) +{ + git_repository_free(_repo); + _repo = NULL; + + cl_fixture_cleanup("./fetch"); + cl_fixture_cleanup("./redirected"); + + git__free(_remote_proxy_scheme); + git__free(_remote_proxy_host); + git__free(_remote_proxy_user); + git__free(_remote_proxy_pass); + git__free(_remote_redirect_initial); + git__free(_remote_redirect_subsequent); +} + +static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *data) +{ + GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b); GIT_UNUSED(data); + + ++counter; + + return 0; +} + +static int progress(const git_indexer_progress *stats, void *payload) +{ + size_t *bytes_received = (size_t *)payload; + *bytes_received = stats->received_bytes; + return 0; +} + +static void do_fetch(const char *url, git_remote_autotag_option_t flag, int n) +{ + git_remote *remote; + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + size_t bytes_received = 0; + + options.callbacks.transfer_progress = progress; + options.callbacks.update_tips = update_tips; + options.callbacks.payload = &bytes_received; + options.download_tags = flag; + counter = 0; + + cl_git_pass(git_remote_create(&remote, _repo, "test", url)); + cl_git_pass(git_remote_fetch(remote, NULL, &options, NULL)); + cl_assert_equal_i(counter, n); + cl_assert(bytes_received > 0); + + git_remote_free(remote); +} + +void test_online_fetch__default_http(void) +{ + do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6); +} + +void test_online_fetch__default_https(void) +{ + do_fetch("https://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6); +} + +void test_online_fetch__no_tags_git(void) +{ + do_fetch("https://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3); +} + +void test_online_fetch__no_tags_http(void) +{ + do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3); +} + +void test_online_fetch__fetch_twice(void) +{ + git_remote *remote; + cl_git_pass(git_remote_create(&remote, _repo, "test", "https://github.com/libgit2/TestGitRepository.git")); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_pass(git_remote_download(remote, NULL, NULL)); + git_remote_disconnect(remote); + + git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL); + cl_git_pass(git_remote_download(remote, NULL, NULL)); + git_remote_disconnect(remote); + + git_remote_free(remote); +} + +static int transferProgressCallback(const git_indexer_progress *stats, void *payload) +{ + bool *invoked = (bool *)payload; + + GIT_UNUSED(stats); + *invoked = true; + return 0; +} + +void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date(void) +{ + git_repository *_repository; + bool invoked = false; + git_remote *remote; + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + opts.bare = true; + + cl_git_pass(git_clone(&_repository, "https://github.com/libgit2/TestGitRepository.git", + "./fetch/lg2", &opts)); + git_repository_free(_repository); + + cl_git_pass(git_repository_open(&_repository, "./fetch/lg2")); + + cl_git_pass(git_remote_lookup(&remote, _repository, "origin")); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + + cl_assert_equal_i(false, invoked); + + options.callbacks.transfer_progress = &transferProgressCallback; + options.callbacks.payload = &invoked; + cl_git_pass(git_remote_download(remote, NULL, &options)); + + cl_assert_equal_i(false, invoked); + + cl_git_pass(git_remote_update_tips(remote, &options.callbacks, 1, options.download_tags, NULL)); + git_remote_disconnect(remote); + + git_remote_free(remote); + git_repository_free(_repository); +} + +static int cancel_at_half(const git_indexer_progress *stats, void *payload) +{ + GIT_UNUSED(payload); + + if (stats->received_objects > (stats->total_objects/2)) + return -4321; + return 0; +} + +void test_online_fetch__can_cancel(void) +{ + git_remote *remote; + size_t bytes_received = 0; + git_fetch_options options = GIT_FETCH_OPTIONS_INIT; + + cl_git_pass(git_remote_create(&remote, _repo, "test", + "http://github.com/libgit2/TestGitRepository.git")); + + options.callbacks.transfer_progress = cancel_at_half; + options.callbacks.payload = &bytes_received; + + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_fail_with(git_remote_download(remote, NULL, &options), -4321); + git_remote_disconnect(remote); + git_remote_free(remote); +} + +void test_online_fetch__ls_disconnected(void) +{ + const git_remote_head **refs; + size_t refs_len_before, refs_len_after; + git_remote *remote; + + cl_git_pass(git_remote_create(&remote, _repo, "test", + "http://github.com/libgit2/TestGitRepository.git")); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_pass(git_remote_ls(&refs, &refs_len_before, remote)); + git_remote_disconnect(remote); + cl_git_pass(git_remote_ls(&refs, &refs_len_after, remote)); + + cl_assert_equal_i(refs_len_before, refs_len_after); + + git_remote_free(remote); +} + +void test_online_fetch__remote_symrefs(void) +{ + const git_remote_head **refs; + size_t refs_len; + git_remote *remote; + + cl_git_pass(git_remote_create(&remote, _repo, "test", + "http://github.com/libgit2/TestGitRepository.git")); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + git_remote_disconnect(remote); + cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); + + cl_assert_equal_s("HEAD", refs[0]->name); + cl_assert_equal_s("refs/heads/master", refs[0]->symref_target); + + git_remote_free(remote); +} + +void test_online_fetch__twice(void) +{ + git_remote *remote; + + cl_git_pass(git_remote_create(&remote, _repo, "test", "http://github.com/libgit2/TestGitRepository.git")); + cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); + cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); + + git_remote_free(remote); +} + +void test_online_fetch__proxy(void) +{ + git_remote *remote; + git_str url = GIT_STR_INIT; + git_fetch_options fetch_opts; + + if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) + cl_skip(); + + cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/", + _remote_proxy_scheme ? _remote_proxy_scheme : "http", + _remote_proxy_user, _remote_proxy_pass, _remote_proxy_host)); + + cl_git_pass(git_fetch_options_init(&fetch_opts, GIT_FETCH_OPTIONS_VERSION)); + fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; + fetch_opts.proxy_opts.url = url.ptr; + + cl_git_pass(git_remote_create(&remote, _repo, "test", "https://github.com/libgit2/TestGitRepository.git")); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, &fetch_opts.proxy_opts, NULL)); + cl_git_pass(git_remote_fetch(remote, NULL, &fetch_opts, NULL)); + + git_remote_free(remote); + git_str_dispose(&url); +} + +static int do_redirected_fetch(const char *url, const char *name, const char *config) +{ + git_repository *repo; + git_remote *remote; + int error; + + cl_git_pass(git_repository_init(&repo, "./redirected", 0)); + cl_fixture_cleanup(name); + + if (config) + cl_repo_set_string(repo, "http.followRedirects", config); + + cl_git_pass(git_remote_create(&remote, repo, name, url)); + error = git_remote_fetch(remote, NULL, NULL, NULL); + + git_remote_free(remote); + git_repository_free(repo); + + cl_fixture_cleanup("./redirected"); + + return error; +} + +void test_online_fetch__redirect_config(void) +{ + if (!_remote_redirect_initial || !_remote_redirect_subsequent) + cl_skip(); + + /* config defaults */ + cl_git_pass(do_redirected_fetch(_remote_redirect_initial, "initial", NULL)); + cl_git_fail(do_redirected_fetch(_remote_redirect_subsequent, "subsequent", NULL)); + + /* redirect=initial */ + cl_git_pass(do_redirected_fetch(_remote_redirect_initial, "initial", "initial")); + cl_git_fail(do_redirected_fetch(_remote_redirect_subsequent, "subsequent", "initial")); + + /* redirect=false */ + cl_git_fail(do_redirected_fetch(_remote_redirect_initial, "initial", "false")); + cl_git_fail(do_redirected_fetch(_remote_redirect_subsequent, "subsequent", "false")); +} + +void test_online_fetch__reachable_commit(void) +{ + git_remote *remote; + git_strarray refspecs; + git_object *obj; + git_oid expected_id; + git_str fetchhead = GIT_STR_INIT; + char *refspec = "+2c349335b7f797072cf729c4f3bb0914ecb6dec9:refs/success"; + + refspecs.strings = &refspec; + refspecs.count = 1; + + git_oid_fromstr(&expected_id, "2c349335b7f797072cf729c4f3bb0914ecb6dec9"); + + cl_git_pass(git_remote_create(&remote, _repo, "test", + "https://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, NULL)); + + cl_git_pass(git_revparse_single(&obj, _repo, "refs/success")); + cl_assert_equal_oid(&expected_id, git_object_id(obj)); + + cl_git_pass(git_futils_readbuffer(&fetchhead, "./fetch/.git/FETCH_HEAD")); + cl_assert_equal_s(fetchhead.ptr, + "2c349335b7f797072cf729c4f3bb0914ecb6dec9\t\t'2c349335b7f797072cf729c4f3bb0914ecb6dec9' of https://github.com/libgit2/TestGitRepository\n"); + + git_str_dispose(&fetchhead); + git_object_free(obj); + git_remote_free(remote); +} diff --git a/tests/libgit2/online/fetchhead.c b/tests/libgit2/online/fetchhead.c new file mode 100644 index 000000000..1b66c528e --- /dev/null +++ b/tests/libgit2/online/fetchhead.c @@ -0,0 +1,169 @@ +#include "clar_libgit2.h" + +#include "futils.h" +#include "fetchhead.h" +#include "../fetchhead/fetchhead_data.h" +#include "git2/clone.h" + +#define LIVE_REPO_URL "https://github.com/libgit2/TestGitRepository" + +static git_repository *g_repo; +static git_clone_options g_options; + +void test_online_fetchhead__initialize(void) +{ + git_fetch_options dummy_fetch = GIT_FETCH_OPTIONS_INIT; + g_repo = NULL; + + memset(&g_options, 0, sizeof(git_clone_options)); + g_options.version = GIT_CLONE_OPTIONS_VERSION; + g_options.fetch_opts = dummy_fetch; +} + +void test_online_fetchhead__cleanup(void) +{ + if (g_repo) { + git_repository_free(g_repo); + g_repo = NULL; + } + + cl_fixture_cleanup("./foo"); +} + +static void fetchhead_test_clone(void) +{ + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); +} + +static size_t count_references(void) +{ + git_strarray array; + size_t refs; + + cl_git_pass(git_reference_list(&array, g_repo)); + refs = array.count; + + git_strarray_dispose(&array); + + return refs; +} + +static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fetchhead) +{ + git_remote *remote; + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + git_str fetchhead_buf = GIT_STR_INIT; + git_strarray array, *active_refs = NULL; + + cl_git_pass(git_remote_lookup(&remote, g_repo, "origin")); + fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; + + if(fetchspec != NULL) { + array.count = 1; + array.strings = (char **) &fetchspec; + active_refs = &array; + } + + cl_git_pass(git_remote_fetch(remote, active_refs, &fetch_opts, NULL)); + git_remote_free(remote); + + cl_git_pass(git_futils_readbuffer(&fetchhead_buf, "./foo/.git/FETCH_HEAD")); + + cl_assert_equal_s(fetchhead_buf.ptr, expected_fetchhead); + git_str_dispose(&fetchhead_buf); +} + +void test_online_fetchhead__wildcard_spec(void) +{ + fetchhead_test_clone(); + fetchhead_test_fetch(NULL, FETCH_HEAD_WILDCARD_DATA2); + cl_git_pass(git_tag_delete(g_repo, "annotated_tag")); + cl_git_pass(git_tag_delete(g_repo, "blob")); + cl_git_pass(git_tag_delete(g_repo, "commit_tree")); + cl_git_pass(git_tag_delete(g_repo, "nearly-dangling")); + fetchhead_test_fetch(NULL, FETCH_HEAD_WILDCARD_DATA); +} + +void test_online_fetchhead__explicit_spec(void) +{ + fetchhead_test_clone(); + fetchhead_test_fetch("refs/heads/first-merge:refs/remotes/origin/first-merge", FETCH_HEAD_EXPLICIT_DATA); +} + +void test_online_fetchhead__no_merges(void) +{ + git_config *config; + + fetchhead_test_clone(); + + cl_git_pass(git_repository_config(&config, g_repo)); + cl_git_pass(git_config_delete_entry(config, "branch.master.remote")); + cl_git_pass(git_config_delete_entry(config, "branch.master.merge")); + git_config_free(config); + + fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA2); + cl_git_pass(git_tag_delete(g_repo, "annotated_tag")); + cl_git_pass(git_tag_delete(g_repo, "blob")); + cl_git_pass(git_tag_delete(g_repo, "commit_tree")); + cl_git_pass(git_tag_delete(g_repo, "nearly-dangling")); + fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA); + cl_git_pass(git_tag_delete(g_repo, "commit_tree")); + fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA3); +} + +void test_online_fetchhead__explicit_dst_refspec_creates_branch(void) +{ + git_reference *ref; + size_t refs; + + fetchhead_test_clone(); + refs = count_references(); + fetchhead_test_fetch("refs/heads/first-merge:refs/heads/explicit-refspec", FETCH_HEAD_EXPLICIT_DATA); + + cl_git_pass(git_branch_lookup(&ref, g_repo, "explicit-refspec", GIT_BRANCH_ALL)); + cl_assert_equal_i(refs + 1, count_references()); + + git_reference_free(ref); +} + +void test_online_fetchhead__empty_dst_refspec_creates_no_branch(void) +{ + git_reference *ref; + size_t refs; + + fetchhead_test_clone(); + refs = count_references(); + + fetchhead_test_fetch("refs/heads/first-merge", FETCH_HEAD_EXPLICIT_DATA); + cl_git_fail(git_branch_lookup(&ref, g_repo, "first-merge", GIT_BRANCH_ALL)); + + cl_assert_equal_i(refs, count_references()); +} + +void test_online_fetchhead__colon_only_dst_refspec_creates_no_branch(void) +{ + size_t refs; + + fetchhead_test_clone(); + refs = count_references(); + fetchhead_test_fetch("refs/heads/first-merge:", FETCH_HEAD_EXPLICIT_DATA); + + cl_assert_equal_i(refs, count_references()); +} + +void test_online_fetchhead__creds_get_stripped(void) +{ + git_str buf = GIT_STR_INIT; + git_remote *remote; + + cl_git_pass(git_repository_init(&g_repo, "./foo", 0)); + cl_git_pass(git_remote_create_anonymous(&remote, g_repo, "https://foo:bar@github.com/libgit2/TestGitRepository")); + cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); + + cl_git_pass(git_futils_readbuffer(&buf, "./foo/.git/FETCH_HEAD")); + cl_assert_equal_s(buf.ptr, + "49322bb17d3acc9146f98c97d078513228bbf3c0\t\thttps://github.com/libgit2/TestGitRepository\n"); + + git_remote_free(remote); + git_str_dispose(&buf); +} diff --git a/tests/libgit2/online/push.c b/tests/libgit2/online/push.c new file mode 100644 index 000000000..51adc3930 --- /dev/null +++ b/tests/libgit2/online/push.c @@ -0,0 +1,917 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "vector.h" +#include "../submodule/submodule_helpers.h" +#include "push_util.h" +#include "refspec.h" +#include "remote.h" + +static git_repository *_repo; + +static char *_remote_url = NULL; + +static char *_remote_user = NULL; +static char *_remote_pass = NULL; + +static char *_remote_ssh_key = NULL; +static char *_remote_ssh_pubkey = NULL; +static char *_remote_ssh_passphrase = NULL; + +static char *_remote_default = NULL; +static char *_remote_expectcontinue = NULL; + +static int cred_acquire_cb(git_credential **, const char *, const char *, unsigned int, void *); + +static git_remote *_remote; +static record_callbacks_data _record_cbs_data = {{ 0 }}; +static git_remote_callbacks _record_cbs = RECORD_CALLBACKS_INIT(&_record_cbs_data); + +static git_oid _oid_b6; +static git_oid _oid_b5; +static git_oid _oid_b4; +static git_oid _oid_b3; +static git_oid _oid_b2; +static git_oid _oid_b1; + +static git_oid _tag_commit; +static git_oid _tag_tree; +static git_oid _tag_blob; +static git_oid _tag_lightweight; +static git_oid _tag_tag; + +static int cred_acquire_cb( + git_credential **cred, + const char *url, + const char *user_from_url, + unsigned int allowed_types, + void *payload) +{ + GIT_UNUSED(url); + GIT_UNUSED(user_from_url); + GIT_UNUSED(payload); + + if (GIT_CREDENTIAL_USERNAME & allowed_types) { + if (!_remote_user) { + printf("GITTEST_REMOTE_USER must be set\n"); + return -1; + } + + return git_credential_username_new(cred, _remote_user); + } + + if (GIT_CREDENTIAL_DEFAULT & allowed_types) { + if (!_remote_default) { + printf("GITTEST_REMOTE_DEFAULT must be set to use NTLM/Negotiate credentials\n"); + return -1; + } + + return git_credential_default_new(cred); + } + + if (GIT_CREDENTIAL_SSH_KEY & allowed_types) { + if (!_remote_user || !_remote_ssh_pubkey || !_remote_ssh_key || !_remote_ssh_passphrase) { + printf("GITTEST_REMOTE_USER, GITTEST_REMOTE_SSH_PUBKEY, GITTEST_REMOTE_SSH_KEY and GITTEST_REMOTE_SSH_PASSPHRASE must be set\n"); + return -1; + } + + return git_credential_ssh_key_new(cred, _remote_user, _remote_ssh_pubkey, _remote_ssh_key, _remote_ssh_passphrase); + } + + if (GIT_CREDENTIAL_USERPASS_PLAINTEXT & allowed_types) { + if (!_remote_user || !_remote_pass) { + printf("GITTEST_REMOTE_USER and GITTEST_REMOTE_PASS must be set\n"); + return -1; + } + + return git_credential_userpass_plaintext_new(cred, _remote_user, _remote_pass); + } + + return -1; +} + +/** + * git_push_status_foreach callback that records status entries. + */ +static int record_push_status_cb(const char *ref, const char *msg, void *payload) +{ + record_callbacks_data *data = (record_callbacks_data *) payload; + push_status *s; + + cl_assert(s = git__calloc(1, sizeof(*s))); + if (ref) + cl_assert(s->ref = git__strdup(ref)); + s->success = (msg == NULL); + if (msg) + cl_assert(s->msg = git__strdup(msg)); + + git_vector_insert(&data->statuses, s); + + return 0; +} + +static void do_verify_push_status(record_callbacks_data *data, const push_status expected[], const size_t expected_len) +{ + git_vector *actual = &data->statuses; + push_status *iter; + bool failed = false; + size_t i; + + if (expected_len != actual->length) + failed = true; + else + git_vector_foreach(actual, i, iter) + if (strcmp(expected[i].ref, iter->ref) || + (expected[i].success != iter->success) || + (expected[i].msg && (!iter->msg || strcmp(expected[i].msg, iter->msg)))) { + failed = true; + break; + } + + if (failed) { + git_str msg = GIT_STR_INIT; + + git_str_puts(&msg, "Expected and actual push statuses differ:\nEXPECTED:\n"); + + for(i = 0; i < expected_len; i++) { + git_str_printf(&msg, "%s: %s\n", + expected[i].ref, + expected[i].success ? "success" : "failed"); + } + + git_str_puts(&msg, "\nACTUAL:\n"); + + git_vector_foreach(actual, i, iter) { + if (iter->success) + git_str_printf(&msg, "%s: success\n", iter->ref); + else + git_str_printf(&msg, "%s: failed with message: %s", iter->ref, iter->msg); + } + + cl_fail(git_str_cstr(&msg)); + + git_str_dispose(&msg); + } + + git_vector_foreach(actual, i, iter) { + push_status *s = (push_status *)iter; + git__free(s->ref); + git__free(s->msg); + git__free(s); + } + + git_vector_free(actual); +} + +/** + * Verifies that after git_push_finish(), refs on a remote have the expected + * names, oids, and order. + * + * @param remote remote to verify + * @param expected_refs expected remote refs after push + * @param expected_refs_len length of expected_refs + */ +static void verify_refs(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len) +{ + const git_remote_head **actual_refs; + size_t actual_refs_len; + + git_remote_ls(&actual_refs, &actual_refs_len, remote); + verify_remote_refs(actual_refs, actual_refs_len, expected_refs, expected_refs_len); +} + +/** + * Verifies that after git_push_update_tips(), remote tracking branches have the expected + * names and oids. + * + * @param remote remote to verify + * @param expected_refs expected remote refs after push + * @param expected_refs_len length of expected_refs + */ +static void verify_tracking_branches(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len) +{ + git_refspec *fetch_spec; + size_t i, j; + git_str msg = GIT_STR_INIT; + git_buf ref_name = GIT_BUF_INIT; + git_vector actual_refs = GIT_VECTOR_INIT; + git_branch_iterator *iter; + char *actual_ref; + git_oid oid; + int failed = 0, error; + git_branch_t branch_type; + git_reference *ref; + + /* Get current remote-tracking branches */ + cl_git_pass(git_branch_iterator_new(&iter, remote->repo, GIT_BRANCH_REMOTE)); + + while ((error = git_branch_next(&ref, &branch_type, iter)) == 0) { + cl_assert_equal_i(branch_type, GIT_BRANCH_REMOTE); + + cl_git_pass(git_vector_insert(&actual_refs, git__strdup(git_reference_name(ref)))); + + git_reference_free(ref); + } + + cl_assert_equal_i(error, GIT_ITEROVER); + git_branch_iterator_free(iter); + + /* Loop through expected refs, make sure they exist */ + for (i = 0; i < expected_refs_len; i++) { + + /* Convert remote reference name into remote-tracking branch name. + * If the spec is not under refs/heads/, then skip. + */ + fetch_spec = git_remote__matching_refspec(remote, expected_refs[i].name); + if (!fetch_spec) + continue; + + cl_git_pass(git_refspec_transform(&ref_name, fetch_spec, expected_refs[i].name)); + + /* Find matching remote branch */ + git_vector_foreach(&actual_refs, j, actual_ref) { + if (!strcmp(ref_name.ptr, actual_ref)) + break; + } + + if (j == actual_refs.length) { + git_str_printf(&msg, "Did not find expected tracking branch '%s'.", ref_name.ptr); + failed = 1; + goto failed; + } + + /* Make sure tracking branch is at expected commit ID */ + cl_git_pass(git_reference_name_to_id(&oid, remote->repo, actual_ref)); + + if (git_oid_cmp(expected_refs[i].oid, &oid) != 0) { + git_str_puts(&msg, "Tracking branch commit does not match expected ID."); + failed = 1; + goto failed; + } + + git__free(actual_ref); + cl_git_pass(git_vector_remove(&actual_refs, j)); + } + + /* Make sure there are no extra branches */ + if (actual_refs.length > 0) { + git_str_puts(&msg, "Unexpected remote tracking branches exist."); + failed = 1; + goto failed; + } + +failed: + if (failed) + cl_fail(git_str_cstr(&msg)); + + git_vector_foreach(&actual_refs, i, actual_ref) + git__free(actual_ref); + + git_vector_free(&actual_refs); + git_str_dispose(&msg); + git_buf_dispose(&ref_name); +} + +static void verify_update_tips_callback(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len) +{ + git_refspec *fetch_spec; + git_str msg = GIT_STR_INIT; + git_buf ref_name = GIT_BUF_INIT; + updated_tip *tip = NULL; + size_t i, j; + int failed = 0; + + for (i = 0; i < expected_refs_len; ++i) { + /* Convert remote reference name into tracking branch name. + * If the spec is not under refs/heads/, then skip. + */ + fetch_spec = git_remote__matching_refspec(remote, expected_refs[i].name); + if (!fetch_spec) + continue; + + cl_git_pass(git_refspec_transform(&ref_name, fetch_spec, expected_refs[i].name)); + + /* Find matching update_tip entry */ + git_vector_foreach(&_record_cbs_data.updated_tips, j, tip) { + if (!strcmp(ref_name.ptr, tip->name)) + break; + } + + if (j == _record_cbs_data.updated_tips.length) { + git_str_printf(&msg, "Did not find expected updated tip entry for branch '%s'.", ref_name.ptr); + failed = 1; + goto failed; + } + + if (git_oid_cmp(expected_refs[i].oid, &tip->new_oid) != 0) { + git_str_printf(&msg, "Updated tip ID does not match expected ID"); + failed = 1; + goto failed; + } + } + +failed: + if (failed) + cl_fail(git_str_cstr(&msg)); + + git_buf_dispose(&ref_name); + git_str_dispose(&msg); +} + +void test_online_push__initialize(void) +{ + git_vector delete_specs = GIT_VECTOR_INIT; + const git_remote_head **heads; + size_t heads_len; + git_push_options push_opts = GIT_PUSH_OPTIONS_INIT; + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + + _repo = cl_git_sandbox_init("push_src"); + + cl_git_pass(git_repository_set_ident(_repo, "Random J. Hacker", "foo@example.com")); + cl_fixture_sandbox("testrepo.git"); + cl_rename("push_src/submodule/.gitted", "push_src/submodule/.git"); + + rewrite_gitmodules(git_repository_workdir(_repo)); + + /* git log --format=oneline --decorate --graph + * *-. 951bbbb90e2259a4c8950db78946784fb53fcbce (HEAD, b6) merge b3, b4, and b5 to b6 + * |\ \ + * | | * fa38b91f199934685819bea316186d8b008c52a2 (b5) added submodule named 'submodule' pointing to '../testrepo.git' + * | * | 27b7ce66243eb1403862d05f958c002312df173d (b4) edited fold\b.txt + * | |/ + * * | d9b63a88223d8367516f50bd131a5f7349b7f3e4 (b3) edited a.txt + * |/ + * * a78705c3b2725f931d3ee05348d83cc26700f247 (b2, b1) added fold and fold/b.txt + * * 5c0bb3d1b9449d1cc69d7519fd05166f01840915 added a.txt + */ + git_oid_fromstr(&_oid_b6, "951bbbb90e2259a4c8950db78946784fb53fcbce"); + git_oid_fromstr(&_oid_b5, "fa38b91f199934685819bea316186d8b008c52a2"); + git_oid_fromstr(&_oid_b4, "27b7ce66243eb1403862d05f958c002312df173d"); + git_oid_fromstr(&_oid_b3, "d9b63a88223d8367516f50bd131a5f7349b7f3e4"); + git_oid_fromstr(&_oid_b2, "a78705c3b2725f931d3ee05348d83cc26700f247"); + git_oid_fromstr(&_oid_b1, "a78705c3b2725f931d3ee05348d83cc26700f247"); + + git_oid_fromstr(&_tag_commit, "805c54522e614f29f70d2413a0470247d8b424ac"); + git_oid_fromstr(&_tag_tree, "ff83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e"); + git_oid_fromstr(&_tag_blob, "b483ae7ba66decee9aee971f501221dea84b1498"); + git_oid_fromstr(&_tag_lightweight, "951bbbb90e2259a4c8950db78946784fb53fcbce"); + git_oid_fromstr(&_tag_tag, "eea4f2705eeec2db3813f2430829afce99cd00b5"); + + /* Remote URL environment variable must be set. User and password are optional. */ + + _remote_url = cl_getenv("GITTEST_REMOTE_URL"); + _remote_user = cl_getenv("GITTEST_REMOTE_USER"); + _remote_pass = cl_getenv("GITTEST_REMOTE_PASS"); + _remote_ssh_key = cl_getenv("GITTEST_REMOTE_SSH_KEY"); + _remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY"); + _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); + _remote_default = cl_getenv("GITTEST_REMOTE_DEFAULT"); + _remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE"); + _remote = NULL; + + /* Skip the test if we're missing the remote URL */ + if (!_remote_url) + cl_skip(); + + if (_remote_expectcontinue) + git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1); + + cl_git_pass(git_remote_create(&_remote, _repo, "test", _remote_url)); + + record_callbacks_data_clear(&_record_cbs_data); + + cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH, &_record_cbs, NULL, NULL)); + + /* Clean up previously pushed branches. Fails if receive.denyDeletes is + * set on the remote. Also, on Git 1.7.0 and newer, you must run + * 'git config receive.denyDeleteCurrent ignore' in the remote repo in + * order to delete the remote branch pointed to by HEAD (usually master). + * See: https://raw.github.com/git/git/master/Documentation/RelNotes/1.7.0.txt + */ + cl_git_pass(git_remote_ls(&heads, &heads_len, _remote)); + cl_git_pass(create_deletion_refspecs(&delete_specs, heads, heads_len)); + if (delete_specs.length) { + git_strarray arr = { + (char **) delete_specs.contents, + delete_specs.length, + }; + + memcpy(&push_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks)); + cl_git_pass(git_remote_upload(_remote, &arr, &push_opts)); + } + + git_remote_disconnect(_remote); + git_vector_free_deep(&delete_specs); + + /* Now that we've deleted everything, fetch from the remote */ + memcpy(&fetch_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks)); + cl_git_pass(git_remote_fetch(_remote, NULL, &fetch_opts, NULL)); +} + +void test_online_push__cleanup(void) +{ + if (_remote) + git_remote_free(_remote); + _remote = NULL; + + git__free(_remote_url); + git__free(_remote_user); + git__free(_remote_pass); + git__free(_remote_ssh_key); + git__free(_remote_ssh_pubkey); + git__free(_remote_ssh_passphrase); + git__free(_remote_default); + git__free(_remote_expectcontinue); + + /* Freed by cl_git_sandbox_cleanup */ + _repo = NULL; + + git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 0); + + record_callbacks_data_clear(&_record_cbs_data); + + cl_fixture_cleanup("testrepo.git"); + cl_git_sandbox_cleanup(); +} + +static int push_pack_progress_cb( + int stage, unsigned int current, unsigned int total, void* payload) +{ + record_callbacks_data *data = (record_callbacks_data *) payload; + GIT_UNUSED(stage); GIT_UNUSED(current); GIT_UNUSED(total); + if (data->pack_progress_calls < 0) + return data->pack_progress_calls; + + data->pack_progress_calls++; + return 0; +} + +static int push_transfer_progress_cb( + unsigned int current, unsigned int total, size_t bytes, void* payload) +{ + record_callbacks_data *data = (record_callbacks_data *) payload; + GIT_UNUSED(current); GIT_UNUSED(total); GIT_UNUSED(bytes); + if (data->transfer_progress_calls < 0) + return data->transfer_progress_calls; + + data->transfer_progress_calls++; + return 0; +} + +/** + * Calls push and relists refs on remote to verify success. + * + * @param refspecs refspecs to push + * @param refspecs_len length of refspecs + * @param expected_refs expected remote refs after push + * @param expected_refs_len length of expected_refs + * @param expected_ret expected return value from git_push_finish() + * @param check_progress_cb Check that the push progress callbacks are called + */ +static void do_push( + const char *refspecs[], size_t refspecs_len, + push_status expected_statuses[], size_t expected_statuses_len, + expected_ref expected_refs[], size_t expected_refs_len, + int expected_ret, int check_progress_cb, int check_update_tips_cb) +{ + git_push_options opts = GIT_PUSH_OPTIONS_INIT; + size_t i; + int error; + git_strarray specs = {0}; + record_callbacks_data *data; + + if (_remote) { + /* Auto-detect the number of threads to use */ + opts.pb_parallelism = 0; + + memcpy(&opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks)); + data = opts.callbacks.payload; + + opts.callbacks.pack_progress = push_pack_progress_cb; + opts.callbacks.push_transfer_progress = push_transfer_progress_cb; + opts.callbacks.push_update_reference = record_push_status_cb; + + if (refspecs_len) { + specs.count = refspecs_len; + specs.strings = git__calloc(refspecs_len, sizeof(char *)); + cl_assert(specs.strings); + } + + for (i = 0; i < refspecs_len; i++) + specs.strings[i] = (char *) refspecs[i]; + + /* if EUSER, then abort in transfer */ + if (check_progress_cb && expected_ret == GIT_EUSER) + data->transfer_progress_calls = GIT_EUSER; + + error = git_remote_push(_remote, &specs, &opts); + git__free(specs.strings); + + if (expected_ret < 0) { + cl_git_fail_with(expected_ret, error); + } else { + cl_git_pass(error); + } + + if (check_progress_cb && expected_ret == 0) { + cl_assert(data->pack_progress_calls > 0); + cl_assert(data->transfer_progress_calls > 0); + } + + do_verify_push_status(data, expected_statuses, expected_statuses_len); + + verify_refs(_remote, expected_refs, expected_refs_len); + verify_tracking_branches(_remote, expected_refs, expected_refs_len); + + if (check_update_tips_cb) + verify_update_tips_callback(_remote, expected_refs, expected_refs_len); + + } + +} + +/* Call push_finish() without ever calling git_push_add_refspec() */ +void test_online_push__noop(void) +{ + do_push(NULL, 0, NULL, 0, NULL, 0, 0, 0, 1); +} + +void test_online_push__b1(void) +{ + const char *specs[] = { "refs/heads/b1:refs/heads/b1" }; + push_status exp_stats[] = { { "refs/heads/b1", 1 } }; + expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); +} + +void test_online_push__b2(void) +{ + const char *specs[] = { "refs/heads/b2:refs/heads/b2" }; + push_status exp_stats[] = { { "refs/heads/b2", 1 } }; + expected_ref exp_refs[] = { { "refs/heads/b2", &_oid_b2 } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); +} + +void test_online_push__b3(void) +{ + const char *specs[] = { "refs/heads/b3:refs/heads/b3" }; + push_status exp_stats[] = { { "refs/heads/b3", 1 } }; + expected_ref exp_refs[] = { { "refs/heads/b3", &_oid_b3 } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); +} + +void test_online_push__b4(void) +{ + const char *specs[] = { "refs/heads/b4:refs/heads/b4" }; + push_status exp_stats[] = { { "refs/heads/b4", 1 } }; + expected_ref exp_refs[] = { { "refs/heads/b4", &_oid_b4 } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); +} + +void test_online_push__b5(void) +{ + const char *specs[] = { "refs/heads/b5:refs/heads/b5" }; + push_status exp_stats[] = { { "refs/heads/b5", 1 } }; + expected_ref exp_refs[] = { { "refs/heads/b5", &_oid_b5 } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); +} + +void test_online_push__b5_cancel(void) +{ + const char *specs[] = { "refs/heads/b5:refs/heads/b5" }; + do_push(specs, ARRAY_SIZE(specs), NULL, 0, NULL, 0, GIT_EUSER, 1, 1); +} + +void test_online_push__multi(void) +{ + git_reflog *log; + const git_reflog_entry *entry; + + const char *specs[] = { + "refs/heads/b1:refs/heads/b1", + "refs/heads/b2:refs/heads/b2", + "refs/heads/b3:refs/heads/b3", + "refs/heads/b4:refs/heads/b4", + "refs/heads/b5:refs/heads/b5" + }; + push_status exp_stats[] = { + { "refs/heads/b1", 1 }, + { "refs/heads/b2", 1 }, + { "refs/heads/b3", 1 }, + { "refs/heads/b4", 1 }, + { "refs/heads/b5", 1 } + }; + expected_ref exp_refs[] = { + { "refs/heads/b1", &_oid_b1 }, + { "refs/heads/b2", &_oid_b2 }, + { "refs/heads/b3", &_oid_b3 }, + { "refs/heads/b4", &_oid_b4 }, + { "refs/heads/b5", &_oid_b5 } + }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + + cl_git_pass(git_reflog_read(&log, _repo, "refs/remotes/test/b1")); + entry = git_reflog_entry_byindex(log, 0); + if (entry) { + cl_assert_equal_s("update by push", git_reflog_entry_message(entry)); + cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email); + } + + git_reflog_free(log); +} + +void test_online_push__implicit_tgt(void) +{ + const char *specs1[] = { "refs/heads/b1" }; + push_status exp_stats1[] = { { "refs/heads/b1", 1 } }; + expected_ref exp_refs1[] = { { "refs/heads/b1", &_oid_b1 } }; + + const char *specs2[] = { "refs/heads/b2" }; + push_status exp_stats2[] = { { "refs/heads/b2", 1 } }; + expected_ref exp_refs2[] = { + { "refs/heads/b1", &_oid_b1 }, + { "refs/heads/b2", &_oid_b2 } + }; + + do_push(specs1, ARRAY_SIZE(specs1), + exp_stats1, ARRAY_SIZE(exp_stats1), + exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1); + do_push(specs2, ARRAY_SIZE(specs2), + exp_stats2, ARRAY_SIZE(exp_stats2), + exp_refs2, ARRAY_SIZE(exp_refs2), 0, 0, 0); +} + +void test_online_push__fast_fwd(void) +{ + /* Fast forward b1 in tgt from _oid_b1 to _oid_b6. */ + + const char *specs_init[] = { "refs/heads/b1:refs/heads/b1" }; + push_status exp_stats_init[] = { { "refs/heads/b1", 1 } }; + expected_ref exp_refs_init[] = { { "refs/heads/b1", &_oid_b1 } }; + + const char *specs_ff[] = { "refs/heads/b6:refs/heads/b1" }; + push_status exp_stats_ff[] = { { "refs/heads/b1", 1 } }; + expected_ref exp_refs_ff[] = { { "refs/heads/b1", &_oid_b6 } }; + + /* Do a force push to reset b1 in target back to _oid_b1 */ + const char *specs_reset[] = { "+refs/heads/b1:refs/heads/b1" }; + /* Force should have no effect on a fast forward push */ + const char *specs_ff_force[] = { "+refs/heads/b6:refs/heads/b1" }; + + do_push(specs_init, ARRAY_SIZE(specs_init), + exp_stats_init, ARRAY_SIZE(exp_stats_init), + exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 1, 1); + + do_push(specs_ff, ARRAY_SIZE(specs_ff), + exp_stats_ff, ARRAY_SIZE(exp_stats_ff), + exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0); + + do_push(specs_reset, ARRAY_SIZE(specs_reset), + exp_stats_init, ARRAY_SIZE(exp_stats_init), + exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 0, 0); + + do_push(specs_ff_force, ARRAY_SIZE(specs_ff_force), + exp_stats_ff, ARRAY_SIZE(exp_stats_ff), + exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0); +} + +void test_online_push__tag_commit(void) +{ + const char *specs[] = { "refs/tags/tag-commit:refs/tags/tag-commit" }; + push_status exp_stats[] = { { "refs/tags/tag-commit", 1 } }; + expected_ref exp_refs[] = { { "refs/tags/tag-commit", &_tag_commit } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); +} + +void test_online_push__tag_tree(void) +{ + const char *specs[] = { "refs/tags/tag-tree:refs/tags/tag-tree" }; + push_status exp_stats[] = { { "refs/tags/tag-tree", 1 } }; + expected_ref exp_refs[] = { { "refs/tags/tag-tree", &_tag_tree } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); +} + +void test_online_push__tag_blob(void) +{ + const char *specs[] = { "refs/tags/tag-blob:refs/tags/tag-blob" }; + push_status exp_stats[] = { { "refs/tags/tag-blob", 1 } }; + expected_ref exp_refs[] = { { "refs/tags/tag-blob", &_tag_blob } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); +} + +void test_online_push__tag_lightweight(void) +{ + const char *specs[] = { "refs/tags/tag-lightweight:refs/tags/tag-lightweight" }; + push_status exp_stats[] = { { "refs/tags/tag-lightweight", 1 } }; + expected_ref exp_refs[] = { { "refs/tags/tag-lightweight", &_tag_lightweight } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); +} + +void test_online_push__tag_to_tag(void) +{ + const char *specs[] = { "refs/tags/tag-tag:refs/tags/tag-tag" }; + push_status exp_stats[] = { { "refs/tags/tag-tag", 1 } }; + expected_ref exp_refs[] = { { "refs/tags/tag-tag", &_tag_tag } }; + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 0, 0); +} + +void test_online_push__force(void) +{ + const char *specs1[] = {"refs/heads/b3:refs/heads/tgt"}; + push_status exp_stats1[] = { { "refs/heads/tgt", 1 } }; + expected_ref exp_refs1[] = { { "refs/heads/tgt", &_oid_b3 } }; + + const char *specs2[] = {"refs/heads/b4:refs/heads/tgt"}; + + const char *specs2_force[] = {"+refs/heads/b4:refs/heads/tgt"}; + push_status exp_stats2_force[] = { { "refs/heads/tgt", 1 } }; + expected_ref exp_refs2_force[] = { { "refs/heads/tgt", &_oid_b4 } }; + + do_push(specs1, ARRAY_SIZE(specs1), + exp_stats1, ARRAY_SIZE(exp_stats1), + exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1); + + do_push(specs2, ARRAY_SIZE(specs2), + NULL, 0, + exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD, 0, 0); + + /* Non-fast-forward update with force should pass. */ + record_callbacks_data_clear(&_record_cbs_data); + do_push(specs2_force, ARRAY_SIZE(specs2_force), + exp_stats2_force, ARRAY_SIZE(exp_stats2_force), + exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0, 1, 1); +} + +void test_online_push__delete(void) +{ + const char *specs1[] = { + "refs/heads/b1:refs/heads/tgt1", + "refs/heads/b1:refs/heads/tgt2" + }; + push_status exp_stats1[] = { + { "refs/heads/tgt1", 1 }, + { "refs/heads/tgt2", 1 } + }; + expected_ref exp_refs1[] = { + { "refs/heads/tgt1", &_oid_b1 }, + { "refs/heads/tgt2", &_oid_b1 } + }; + + const char *specs_del_fake[] = { ":refs/heads/fake" }; + /* Force has no effect for delete. */ + const char *specs_del_fake_force[] = { "+:refs/heads/fake" }; + push_status exp_stats_fake[] = { { "refs/heads/fake", 1 } }; + + const char *specs_delete[] = { ":refs/heads/tgt1" }; + push_status exp_stats_delete[] = { { "refs/heads/tgt1", 1 } }; + expected_ref exp_refs_delete[] = { { "refs/heads/tgt2", &_oid_b1 } }; + /* Force has no effect for delete. */ + const char *specs_delete_force[] = { "+:refs/heads/tgt1" }; + + do_push(specs1, ARRAY_SIZE(specs1), + exp_stats1, ARRAY_SIZE(exp_stats1), + exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1); + + /* When deleting a non-existent branch, the git client sends zero for both + * the old and new commit id. This should succeed on the server with the + * same status report as if the branch were actually deleted. The server + * returns a warning on the side-band iff the side-band is supported. + * Since libgit2 doesn't support the side-band yet, there are no warnings. + */ + do_push(specs_del_fake, ARRAY_SIZE(specs_del_fake), + exp_stats_fake, 1, + exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0); + do_push(specs_del_fake_force, ARRAY_SIZE(specs_del_fake_force), + exp_stats_fake, 1, + exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0); + + /* Delete one of the pushed branches. */ + do_push(specs_delete, ARRAY_SIZE(specs_delete), + exp_stats_delete, ARRAY_SIZE(exp_stats_delete), + exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0); + + /* Re-push branches and retry delete with force. */ + do_push(specs1, ARRAY_SIZE(specs1), + exp_stats1, ARRAY_SIZE(exp_stats1), + exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0); + do_push(specs_delete_force, ARRAY_SIZE(specs_delete_force), + exp_stats_delete, ARRAY_SIZE(exp_stats_delete), + exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0); +} + +void test_online_push__bad_refspecs(void) +{ + /* All classes of refspecs that should be rejected by + * git_push_add_refspec() should go in this test. + */ + char *specs = { + "b6:b6", + }; + git_strarray arr = { + &specs, + 1, + }; + + if (_remote) { + cl_git_fail(git_remote_upload(_remote, &arr, NULL)); + } +} + +void test_online_push__expressions(void) +{ + /* TODO: Expressions in refspecs doesn't actually work yet */ + const char *specs_left_expr[] = { "refs/heads/b2~1:refs/heads/b2" }; + + /* TODO: Find a more precise way of checking errors than a exit code of -1. */ + do_push(specs_left_expr, ARRAY_SIZE(specs_left_expr), + NULL, 0, + NULL, 0, -1, 0, 0); +} + +void test_online_push__notes(void) +{ + git_oid note_oid, *target_oid, expected_oid; + git_signature *signature; + const char *specs[] = { "refs/notes/commits:refs/notes/commits" }; + push_status exp_stats[] = { { "refs/notes/commits", 1 } }; + expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } }; + const char *specs_del[] = { ":refs/notes/commits" }; + + git_oid_fromstr(&expected_oid, "8461a99b27b7043e58ff6e1f5d2cf07d282534fb"); + + target_oid = &_oid_b6; + + /* Create note to push */ + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ + cl_git_pass(git_note_create(¬e_oid, _repo, NULL, signature, signature, target_oid, "hello world\n", 0)); + + do_push(specs, ARRAY_SIZE(specs), + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + + /* And make sure to delete the note */ + + do_push(specs_del, ARRAY_SIZE(specs_del), + exp_stats, 1, + NULL, 0, 0, 0, 0); + + git_signature_free(signature); +} + +void test_online_push__configured(void) +{ + git_oid note_oid, *target_oid, expected_oid; + git_signature *signature; + git_remote *old_remote; + const char *specs[] = { "refs/notes/commits:refs/notes/commits" }; + push_status exp_stats[] = { { "refs/notes/commits", 1 } }; + expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } }; + const char *specs_del[] = { ":refs/notes/commits" }; + + git_oid_fromstr(&expected_oid, "8461a99b27b7043e58ff6e1f5d2cf07d282534fb"); + + target_oid = &_oid_b6; + + cl_git_pass(git_remote_add_push(_repo, git_remote_name(_remote), specs[0])); + old_remote = _remote; + cl_git_pass(git_remote_lookup(&_remote, _repo, git_remote_name(_remote))); + git_remote_free(old_remote); + + /* Create note to push */ + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ + cl_git_pass(git_note_create(¬e_oid, _repo, NULL, signature, signature, target_oid, "hello world\n", 0)); + + do_push(NULL, 0, + exp_stats, ARRAY_SIZE(exp_stats), + exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); + + /* And make sure to delete the note */ + + do_push(specs_del, ARRAY_SIZE(specs_del), + exp_stats, 1, + NULL, 0, 0, 0, 0); + + git_signature_free(signature); +} diff --git a/tests/libgit2/online/push_util.c b/tests/libgit2/online/push_util.c new file mode 100644 index 000000000..cd1831d4c --- /dev/null +++ b/tests/libgit2/online/push_util.c @@ -0,0 +1,141 @@ +#include "clar_libgit2.h" +#include "vector.h" +#include "push_util.h" + +const git_oid OID_ZERO = {{ 0 }}; + +void updated_tip_free(updated_tip *t) +{ + git__free(t->name); + git__free(t); +} + +static void push_status_free(push_status *s) +{ + git__free(s->ref); + git__free(s->msg); + git__free(s); +} + +void record_callbacks_data_clear(record_callbacks_data *data) +{ + size_t i; + updated_tip *tip; + push_status *status; + + git_vector_foreach(&data->updated_tips, i, tip) + updated_tip_free(tip); + + git_vector_free(&data->updated_tips); + + git_vector_foreach(&data->statuses, i, status) + push_status_free(status); + + git_vector_free(&data->statuses); + + data->pack_progress_calls = 0; + data->transfer_progress_calls = 0; +} + +int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data) +{ + updated_tip *t; + record_callbacks_data *record_data = (record_callbacks_data *)data; + + cl_assert(t = git__calloc(1, sizeof(*t))); + + cl_assert(t->name = git__strdup(refname)); + git_oid_cpy(&t->old_oid, a); + git_oid_cpy(&t->new_oid, b); + + git_vector_insert(&record_data->updated_tips, t); + + return 0; +} + +int create_deletion_refspecs(git_vector *out, const git_remote_head **heads, size_t heads_len) +{ + git_str del_spec = GIT_STR_INIT; + int valid; + size_t i; + + for (i = 0; i < heads_len; i++) { + const git_remote_head *head = heads[i]; + /* Ignore malformed ref names (which also saves us from tag^{} */ + cl_git_pass(git_reference_name_is_valid(&valid, head->name)); + if (!valid) + return 0; + + /* Create a refspec that deletes a branch in the remote */ + if (strcmp(head->name, "refs/heads/master")) { + cl_git_pass(git_str_putc(&del_spec, ':')); + cl_git_pass(git_str_puts(&del_spec, head->name)); + cl_git_pass(git_vector_insert(out, git_str_detach(&del_spec))); + } + } + + return 0; +} + +int record_ref_cb(git_remote_head *head, void *payload) +{ + git_vector *refs = (git_vector *) payload; + return git_vector_insert(refs, head); +} + +void verify_remote_refs(const git_remote_head *actual_refs[], size_t actual_refs_len, const expected_ref expected_refs[], size_t expected_refs_len) +{ + size_t i, j = 0; + git_str msg = GIT_STR_INIT; + const git_remote_head *actual; + char *oid_str; + bool master_present = false; + + /* We don't care whether "master" is present on the other end or not */ + for (i = 0; i < actual_refs_len; i++) { + actual = actual_refs[i]; + if (!strcmp(actual->name, "refs/heads/master")) { + master_present = true; + break; + } + } + + if (expected_refs_len + (master_present ? 1 : 0) != actual_refs_len) + goto failed; + + for (i = 0; i < actual_refs_len; i++) { + actual = actual_refs[i]; + if (master_present && !strcmp(actual->name, "refs/heads/master")) + continue; + + if (strcmp(expected_refs[j].name, actual->name) || + git_oid_cmp(expected_refs[j].oid, &actual->oid)) + goto failed; + + j++; + } + + return; + +failed: + git_str_puts(&msg, "Expected and actual refs differ:\nEXPECTED:\n"); + + for(i = 0; i < expected_refs_len; i++) { + oid_str = git_oid_tostr_s(expected_refs[i].oid); + cl_git_pass(git_str_printf(&msg, "%s = %s\n", expected_refs[i].name, oid_str)); + } + + git_str_puts(&msg, "\nACTUAL:\n"); + for (i = 0; i < actual_refs_len; i++) { + actual = actual_refs[i]; + if (master_present && !strcmp(actual->name, "refs/heads/master")) + continue; + + oid_str = git_oid_tostr_s(&actual->oid); + cl_git_pass(git_str_printf(&msg, "%s = %s\n", actual->name, oid_str)); + } + + cl_fail(git_str_cstr(&msg)); + + git_str_dispose(&msg); +} diff --git a/tests/libgit2/online/push_util.h b/tests/libgit2/online/push_util.h new file mode 100644 index 000000000..5f669feaf --- /dev/null +++ b/tests/libgit2/online/push_util.h @@ -0,0 +1,83 @@ +#ifndef INCLUDE_cl_push_util_h__ +#define INCLUDE_cl_push_util_h__ + +#include "git2/oid.h" + +/* Constant for zero oid */ +extern const git_oid OID_ZERO; + +/** + * Macro for initializing git_remote_callbacks to use test helpers that + * record data in a record_callbacks_data instance. + * @param data pointer to a record_callbacks_data instance + */ +#define RECORD_CALLBACKS_INIT(data) \ + { GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, NULL, record_update_tips_cb, NULL, NULL, NULL, NULL, NULL, NULL, data, NULL } + +typedef struct { + char *name; + git_oid old_oid; + git_oid new_oid; +} updated_tip; + +typedef struct { + git_vector updated_tips; + git_vector statuses; + int pack_progress_calls; + int transfer_progress_calls; +} record_callbacks_data; + +typedef struct { + const char *name; + const git_oid *oid; +} expected_ref; + +/* the results of a push status. when used for expected values, msg may be NULL + * to indicate that it should not be matched. */ +typedef struct { + char *ref; + int success; + char *msg; +} push_status; + + +void updated_tip_free(updated_tip *t); + +void record_callbacks_data_clear(record_callbacks_data *data); + +/** + * Callback for git_remote_update_tips that records updates + * + * @param data (git_vector *) of updated_tip instances + */ +int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data); + +/** + * Create a set of refspecs that deletes each of the inputs + * + * @param out the vector in which to store the refspecs + * @param heads the remote heads + * @param heads_len the size of the array + */ +int create_deletion_refspecs(git_vector *out, const git_remote_head **heads, size_t heads_len); + +/** + * Callback for git_remote_list that adds refspecs to vector + * + * @param head a ref on the remote + * @param payload (git_vector *) of git_remote_head instances + */ +int record_ref_cb(git_remote_head *head, void *payload); + +/** + * Verifies that refs on remote stored by record_ref_cb match the expected + * names, oids, and order. + * + * @param actual_refs actual refs in the remote + * @param actual_refs_len length of actual_refs + * @param expected_refs expected remote refs + * @param expected_refs_len length of expected_refs + */ +void verify_remote_refs(const git_remote_head *actual_refs[], size_t actual_refs_len, const expected_ref expected_refs[], size_t expected_refs_len); + +#endif /* INCLUDE_cl_push_util_h__ */ diff --git a/tests/libgit2/online/remotes.c b/tests/libgit2/online/remotes.c new file mode 100644 index 000000000..887874d92 --- /dev/null +++ b/tests/libgit2/online/remotes.c @@ -0,0 +1,127 @@ +#include "clar_libgit2.h" + +#define URL "https://github.com/libgit2/TestGitRepository" +#define REFSPEC "refs/heads/first-merge:refs/remotes/origin/first-merge" + +static int remote_single_branch(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload) +{ + GIT_UNUSED(payload); + + cl_git_pass(git_remote_create_with_fetchspec(out, repo, name, url, REFSPEC)); + + return 0; +} + +void test_online_remotes__single_branch(void) +{ + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + git_repository *repo; + git_remote *remote; + git_strarray refs; + size_t i, count = 0; + + opts.remote_cb = remote_single_branch; + opts.checkout_branch = "first-merge"; + + cl_git_pass(git_clone(&repo, URL, "./single-branch", &opts)); + cl_git_pass(git_reference_list(&refs, repo)); + + for (i = 0; i < refs.count; i++) { + if (!git__prefixcmp(refs.strings[i], "refs/heads/")) + count++; + } + cl_assert_equal_i(1, count); + + git_strarray_dispose(&refs); + + cl_git_pass(git_remote_lookup(&remote, repo, "origin")); + cl_git_pass(git_remote_get_fetch_refspecs(&refs, remote)); + + cl_assert_equal_i(1, refs.count); + cl_assert_equal_s(REFSPEC, refs.strings[0]); + + git_strarray_dispose(&refs); + git_remote_free(remote); + git_repository_free(repo); +} + +void test_online_remotes__restricted_refspecs(void) +{ + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + git_repository *repo; + + opts.remote_cb = remote_single_branch; + + cl_git_fail_with(GIT_EINVALIDSPEC, git_clone(&repo, URL, "./restrict-refspec", &opts)); +} + +void test_online_remotes__detached_remote_fails_downloading(void) +{ + git_remote *remote; + + cl_git_pass(git_remote_create_detached(&remote, URL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_fail(git_remote_download(remote, NULL, NULL)); + + git_remote_free(remote); +} + +void test_online_remotes__detached_remote_fails_uploading(void) +{ + git_remote *remote; + + cl_git_pass(git_remote_create_detached(&remote, URL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_fail(git_remote_upload(remote, NULL, NULL)); + + git_remote_free(remote); +} + +void test_online_remotes__detached_remote_fails_pushing(void) +{ + git_remote *remote; + + cl_git_pass(git_remote_create_detached(&remote, URL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_fail(git_remote_push(remote, NULL, NULL)); + + git_remote_free(remote); +} + +void test_online_remotes__detached_remote_succeeds_ls(void) +{ + const char *refs[] = { + "HEAD", + "refs/heads/first-merge", + "refs/heads/master", + "refs/heads/no-parent", + "refs/tags/annotated_tag", + "refs/tags/annotated_tag^{}", + "refs/tags/blob", + "refs/tags/commit_tree", + "refs/tags/nearly-dangling", + }; + const git_remote_head **heads; + git_remote *remote; + size_t i, j, n; + + cl_git_pass(git_remote_create_detached(&remote, URL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); + cl_git_pass(git_remote_ls(&heads, &n, remote)); + + cl_assert_equal_sz(n, 9); + for (i = 0; i < n; i++) { + char found = false; + + for (j = 0; j < ARRAY_SIZE(refs); j++) { + if (!strcmp(heads[i]->name, refs[j])) { + found = true; + break; + } + } + + cl_assert_(found, heads[i]->name); + } + + git_remote_free(remote); +} diff --git a/tests/libgit2/pack/filelimit.c b/tests/libgit2/pack/filelimit.c new file mode 100644 index 000000000..fa08485fb --- /dev/null +++ b/tests/libgit2/pack/filelimit.c @@ -0,0 +1,136 @@ +#include "clar_libgit2.h" +#include "mwindow.h" + +#include +#include "git2/sys/commit.h" +#include "git2/sys/mempack.h" + +static size_t expected_open_mwindow_files = 0; +static size_t original_mwindow_file_limit = 0; + +extern git_mutex git__mwindow_mutex; +extern git_mwindow_ctl git_mwindow__mem_ctl; + +void test_pack_filelimit__initialize_tiny(void) +{ + expected_open_mwindow_files = 1; + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_MWINDOW_FILE_LIMIT, &original_mwindow_file_limit)); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_MWINDOW_FILE_LIMIT, expected_open_mwindow_files)); +} + +void test_pack_filelimit__initialize_medium(void) +{ + expected_open_mwindow_files = 10; + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_MWINDOW_FILE_LIMIT, &original_mwindow_file_limit)); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_MWINDOW_FILE_LIMIT, expected_open_mwindow_files)); +} + +void test_pack_filelimit__initialize_unlimited(void) +{ + expected_open_mwindow_files = 15; + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_MWINDOW_FILE_LIMIT, &original_mwindow_file_limit)); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_MWINDOW_FILE_LIMIT, 0)); +} + +void test_pack_filelimit__cleanup(void) +{ + git_str path = GIT_STR_INIT; + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_MWINDOW_FILE_LIMIT, original_mwindow_file_limit)); + + cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), "repo.git")); + cl_fixture_cleanup(path.ptr); + git_str_dispose(&path); +} + +/* + * Create a packfile with one commit, one tree, and two blobs. The first blob + * (README.md) has the same content in all commits, but the second one + * (file.txt) has a different content in each commit. + */ +static void create_packfile_commit( + git_repository *repo, + git_oid *out_commit_id, + git_oid *parent_id, + size_t commit_index, + size_t commit_count) +{ + git_str file_contents = GIT_STR_INIT; + git_treebuilder *treebuilder; + git_packbuilder *packbuilder; + git_signature *s; + git_oid oid, tree_id, commit_id; + const git_oid *parents[] = { parent_id }; + size_t parent_count = parent_id ? 1 : 0; + + cl_git_pass(git_treebuilder_new(&treebuilder, repo, NULL)); + + cl_git_pass(git_blob_create_from_buffer(&oid, repo, "", 0)); + cl_git_pass(git_treebuilder_insert(NULL, treebuilder, "README.md", &oid, 0100644)); + + cl_git_pass(git_str_printf(&file_contents, "Commit %zd/%zd", commit_index, commit_count)); + cl_git_pass(git_blob_create_from_buffer(&oid, repo, file_contents.ptr, file_contents.size)); + cl_git_pass(git_treebuilder_insert(NULL, treebuilder, "file.txt", &oid, 0100644)); + + cl_git_pass(git_treebuilder_write(&tree_id, treebuilder)); + cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); + cl_git_pass(git_commit_create_from_ids(&commit_id, repo, "refs/heads/master", s, s, + NULL, file_contents.ptr, &tree_id, parent_count, parents)); + + cl_git_pass(git_packbuilder_new(&packbuilder, repo)); + cl_git_pass(git_packbuilder_insert_commit(packbuilder, &commit_id)); + cl_git_pass(git_packbuilder_write(packbuilder, NULL, 0, NULL, NULL)); + + cl_git_pass(git_oid_cpy(out_commit_id, &commit_id)); + + git_str_dispose(&file_contents); + git_treebuilder_free(treebuilder); + git_packbuilder_free(packbuilder); + git_signature_free(s); +} + +void test_pack_filelimit__open_repo_with_multiple_packfiles(void) +{ + git_str path = GIT_STR_INIT; + git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; + git_repository *repo; + git_revwalk *walk; + git_oid id, *parent_id = NULL; + size_t i; + const size_t commit_count = 16; + unsigned int open_windows; + + /* + * Create a repository and populate it with 16 commits, each in its own + * packfile. + */ + cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), "repo.git")); + cl_git_pass(git_repository_init(&repo, path.ptr, true)); + for (i = 0; i < commit_count; ++i) { + create_packfile_commit(repo, &id, parent_id, i + 1, commit_count); + parent_id = &id; + } + + cl_git_pass(git_revwalk_new(&walk, repo)); + cl_git_pass(git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL)); + cl_git_pass(git_revwalk_push_ref(walk, "refs/heads/master")); + + /* Walking the repository requires eventually opening each of the packfiles. */ + i = 0; + while (git_revwalk_next(&id, walk) == 0) + ++i; + cl_assert_equal_i(commit_count, i); + + cl_git_pass(git_mutex_lock(&git__mwindow_mutex)); + /* + * Adding an assert while holding a lock will cause the whole process to + * deadlock. Copy the value and do the assert after releasing the lock. + */ + open_windows = ctl->open_windows; + cl_git_pass(git_mutex_unlock(&git__mwindow_mutex)); + + cl_assert_equal_i(expected_open_mwindow_files, open_windows); + + git_str_dispose(&path); + git_revwalk_free(walk); + git_repository_free(repo); +} diff --git a/tests/libgit2/pack/indexer.c b/tests/libgit2/pack/indexer.c new file mode 100644 index 000000000..ec48ffd98 --- /dev/null +++ b/tests/libgit2/pack/indexer.c @@ -0,0 +1,320 @@ +#include "clar_libgit2.h" +#include +#include "futils.h" +#include "hash.h" +#include "iterator.h" +#include "vector.h" +#include "posix.h" + + +/* + * This is a packfile with three objects. The second is a delta which + * depends on the third, which is also a delta. + */ +static const unsigned char out_of_order_pack[] = { + 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, + 0x32, 0x78, 0x9c, 0x63, 0x67, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x76, + 0xe6, 0x8f, 0xe8, 0x12, 0x9b, 0x54, 0x6b, 0x10, 0x1a, 0xee, 0x95, 0x10, + 0xc5, 0x32, 0x8e, 0x7f, 0x21, 0xca, 0x1d, 0x18, 0x78, 0x9c, 0x63, 0x62, + 0x66, 0x4e, 0xcb, 0xcf, 0x07, 0x00, 0x02, 0xac, 0x01, 0x4d, 0x75, 0x01, + 0xd7, 0x71, 0x36, 0x66, 0xf4, 0xde, 0x82, 0x27, 0x76, 0xc7, 0x62, 0x2c, + 0x10, 0xf1, 0xb0, 0x7d, 0xe2, 0x80, 0xdc, 0x78, 0x9c, 0x63, 0x62, 0x62, + 0x62, 0xb7, 0x03, 0x00, 0x00, 0x69, 0x00, 0x4c, 0xde, 0x7d, 0xaa, 0xe4, + 0x19, 0x87, 0x58, 0x80, 0x61, 0x09, 0x9a, 0x33, 0xca, 0x7a, 0x31, 0x92, + 0x6f, 0xae, 0x66, 0x75 +}; +static const unsigned int out_of_order_pack_len = 112; + +/* + * Packfile with two objects. The second is a delta against an object + * which is not in the packfile + */ +static const unsigned char thin_pack[] = { + 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, + 0x32, 0x78, 0x9c, 0x63, 0x67, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x76, + 0xe6, 0x8f, 0xe8, 0x12, 0x9b, 0x54, 0x6b, 0x10, 0x1a, 0xee, 0x95, 0x10, + 0xc5, 0x32, 0x8e, 0x7f, 0x21, 0xca, 0x1d, 0x18, 0x78, 0x9c, 0x63, 0x62, + 0x66, 0x4e, 0xcb, 0xcf, 0x07, 0x00, 0x02, 0xac, 0x01, 0x4d, 0x42, 0x52, + 0x3a, 0x6f, 0x39, 0xd1, 0xfe, 0x66, 0x68, 0x6b, 0xa5, 0xe5, 0xe2, 0x97, + 0xac, 0x94, 0x6c, 0x76, 0x0b, 0x04 +}; +static const unsigned int thin_pack_len = 78; + +/* + * Packfile with one object. It references an object which is not in the + * packfile and has a corrupt length (states the deltified stream is 1 byte + * long, where it is actually 6). + */ +static const unsigned char corrupt_thin_pack[] = { + 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, + 0x71, 0xe6, 0x8f, 0xe8, 0x12, 0x9b, 0x54, 0x6b, 0x10, 0x1a, 0xee, 0x95, + 0x10, 0xc5, 0x32, 0x8e, 0x7f, 0x21, 0xca, 0x1d, 0x18, 0x78, 0x9c, 0x63, + 0x62, 0x66, 0x4e, 0xcb, 0xcf, 0x07, 0x00, 0x02, 0xac, 0x01, 0x4d, 0x07, + 0x67, 0x03, 0xc5, 0x40, 0x99, 0x49, 0xb1, 0x3b, 0x7d, 0xae, 0x9b, 0x0e, + 0xdd, 0xde, 0xc6, 0x76, 0x43, 0x24, 0x64 +}; +static const unsigned int corrupt_thin_pack_len = 67; + +/* + * Packfile with a missing trailer. + */ +static const unsigned char missing_trailer_pack[] = { + 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x50, 0xf4, 0x3b, +}; +static const unsigned int missing_trailer_pack_len = 12; + +/* + * Packfile that causes the packfile stream to open in a way in which it leaks + * the stream reader. + */ +static const unsigned char leaky_pack[] = { + 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, + 0xf4, 0xbd, 0x51, 0x51, 0x51, 0x51, 0x51, 0x72, 0x65, 0x41, 0x4b, 0x63, + 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0xbd, 0x41, 0x4b +}; +static const unsigned int leaky_pack_len = 33; + +/* + * Packfile with a three objects. The first one is a tree referencing two blobs, + * the second object is one of those blobs. The second blob is missing. + */ +unsigned char incomplete_pack[] = { + 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, + 0xae, 0x03, 0x78, 0x9c, 0x33, 0x34, 0x30, 0x30, 0x33, 0x31, 0x51, 0x48, + 0x4a, 0x2c, 0x62, 0x08, 0x17, 0x3b, 0x15, 0xd9, 0x7e, 0xfa, 0x67, 0x6d, + 0xf6, 0x56, 0x4f, 0x85, 0x7d, 0xcb, 0xd6, 0xde, 0x53, 0xd1, 0x6d, 0x7f, + 0x66, 0x08, 0x91, 0x4e, 0xcb, 0xcf, 0x67, 0x50, 0xad, 0x39, 0x9a, 0xa2, + 0xb3, 0x71, 0x41, 0xc8, 0x87, 0x9e, 0x13, 0xf6, 0xba, 0x53, 0xec, 0xc2, + 0xfe, 0xda, 0xed, 0x9b, 0x09, 0x00, 0xe8, 0xc8, 0x19, 0xab, 0x34, 0x78, + 0x9c, 0x4b, 0x4a, 0x2c, 0xe2, 0x02, 0x00, 0x03, 0x9d, 0x01, 0x40, 0x4b, + 0x72, 0xa2, 0x6f, 0xb6, 0x88, 0x2d, 0x6c, 0xa5, 0x07, 0xb2, 0xa5, 0x45, + 0xe8, 0xdb, 0xe6, 0x53, 0xb3, 0x52, 0xe2 +}; +unsigned int incomplete_pack_len = 115; + +static const unsigned char base_obj[] = { 07, 076 }; +static const unsigned int base_obj_len = 2; + +void test_pack_indexer__out_of_order(void) +{ + git_indexer *idx = 0; + git_indexer_progress stats = { 0 }; + + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); + cl_git_pass(git_indexer_append( + idx, out_of_order_pack, out_of_order_pack_len, &stats)); + cl_git_pass(git_indexer_commit(idx, &stats)); + + cl_assert_equal_i(stats.total_objects, 3); + cl_assert_equal_i(stats.received_objects, 3); + cl_assert_equal_i(stats.indexed_objects, 3); + + git_indexer_free(idx); +} + +void test_pack_indexer__missing_trailer(void) +{ + git_indexer *idx = 0; + git_indexer_progress stats = { 0 }; + + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); + cl_git_pass(git_indexer_append( + idx, missing_trailer_pack, missing_trailer_pack_len, &stats)); + cl_git_fail(git_indexer_commit(idx, &stats)); + + cl_assert(git_error_last() != NULL); + cl_assert_equal_i(git_error_last()->klass, GIT_ERROR_INDEXER); + + git_indexer_free(idx); +} + +void test_pack_indexer__leaky(void) +{ + git_indexer *idx = 0; + git_indexer_progress stats = { 0 }; + + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); + cl_git_pass(git_indexer_append( + idx, leaky_pack, leaky_pack_len, &stats)); + cl_git_fail(git_indexer_commit(idx, &stats)); + + cl_assert(git_error_last() != NULL); + cl_assert_equal_i(git_error_last()->klass, GIT_ERROR_INDEXER); + + git_indexer_free(idx); +} + +void test_pack_indexer__fix_thin(void) +{ + git_indexer *idx = NULL; + git_indexer_progress stats = { 0 }; + git_repository *repo; + git_odb *odb; + git_oid id, should_id; + + cl_git_pass(git_repository_init(&repo, "thin.git", true)); + cl_git_pass(git_repository_odb(&odb, repo)); + + /* Store the missing base into your ODB so the indexer can fix the pack */ + cl_git_pass(git_odb_write(&id, odb, base_obj, base_obj_len, GIT_OBJECT_BLOB)); + git_oid_fromstr(&should_id, "e68fe8129b546b101aee9510c5328e7f21ca1d18"); + cl_assert_equal_oid(&should_id, &id); + + cl_git_pass(git_indexer_new(&idx, ".", 0, odb, NULL)); + cl_git_pass(git_indexer_append(idx, thin_pack, thin_pack_len, &stats)); + cl_git_pass(git_indexer_commit(idx, &stats)); + + cl_assert_equal_i(stats.total_objects, 2); + cl_assert_equal_i(stats.received_objects, 2); + cl_assert_equal_i(stats.indexed_objects, 2); + cl_assert_equal_i(stats.local_objects, 1); + + cl_assert_equal_s("fefdb2d740a3a6b6c03a0c7d6ce431c6d5810e13", git_indexer_name(idx)); + + git_indexer_free(idx); + git_odb_free(odb); + git_repository_free(repo); + + /* + * The pack's name/hash only tells us what objects there are, + * so we need to go through the packfile again in order to + * figure out whether we calculated the trailer correctly. + */ + { + unsigned char buffer[128]; + int fd; + ssize_t read; + struct stat st; + const char *name = "pack-fefdb2d740a3a6b6c03a0c7d6ce431c6d5810e13.pack"; + + fd = p_open(name, O_RDONLY); + cl_assert(fd != -1); + + cl_git_pass(p_stat(name, &st)); + + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); + read = p_read(fd, buffer, sizeof(buffer)); + cl_assert(read != -1); + p_close(fd); + + cl_git_pass(git_indexer_append(idx, buffer, read, &stats)); + cl_git_pass(git_indexer_commit(idx, &stats)); + + cl_assert_equal_i(stats.total_objects, 3); + cl_assert_equal_i(stats.received_objects, 3); + cl_assert_equal_i(stats.indexed_objects, 3); + cl_assert_equal_i(stats.local_objects, 0); + + git_indexer_free(idx); + } +} + +void test_pack_indexer__corrupt_length(void) +{ + git_indexer *idx = NULL; + git_indexer_progress stats = { 0 }; + git_repository *repo; + git_odb *odb; + git_oid id, should_id; + + cl_git_pass(git_repository_init(&repo, "thin.git", true)); + cl_git_pass(git_repository_odb(&odb, repo)); + + /* Store the missing base into your ODB so the indexer can fix the pack */ + cl_git_pass(git_odb_write(&id, odb, base_obj, base_obj_len, GIT_OBJECT_BLOB)); + git_oid_fromstr(&should_id, "e68fe8129b546b101aee9510c5328e7f21ca1d18"); + cl_assert_equal_oid(&should_id, &id); + + cl_git_pass(git_indexer_new(&idx, ".", 0, odb, NULL)); + cl_git_pass(git_indexer_append( + idx, corrupt_thin_pack, corrupt_thin_pack_len, &stats)); + cl_git_fail(git_indexer_commit(idx, &stats)); + + cl_assert(git_error_last() != NULL); + cl_assert_equal_i(git_error_last()->klass, GIT_ERROR_ZLIB); + + git_indexer_free(idx); + git_odb_free(odb); + git_repository_free(repo); +} + +void test_pack_indexer__incomplete_pack_fails_with_strict(void) +{ + git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; + git_indexer *idx = 0; + git_indexer_progress stats = { 0 }; + + opts.verify = 1; + + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, &opts)); + cl_git_pass(git_indexer_append( + idx, incomplete_pack, incomplete_pack_len, &stats)); + cl_git_fail(git_indexer_commit(idx, &stats)); + + cl_assert_equal_i(stats.total_objects, 2); + cl_assert_equal_i(stats.received_objects, 2); + cl_assert_equal_i(stats.indexed_objects, 2); + + git_indexer_free(idx); +} + +void test_pack_indexer__out_of_order_with_connectivity_checks(void) +{ + git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; + git_indexer *idx = 0; + git_indexer_progress stats = { 0 }; + + opts.verify = 1; + + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, &opts)); + cl_git_pass(git_indexer_append( + idx, out_of_order_pack, out_of_order_pack_len, &stats)); + cl_git_pass(git_indexer_commit(idx, &stats)); + + cl_assert_equal_i(stats.total_objects, 3); + cl_assert_equal_i(stats.received_objects, 3); + cl_assert_equal_i(stats.indexed_objects, 3); + + git_indexer_free(idx); +} + +static int find_tmp_file_recurs(void *opaque, git_str *path) +{ + int error = 0; + git_str *first_tmp_file = opaque; + struct stat st; + + if ((error = p_lstat_posixly(path->ptr, &st)) < 0) + return error; + + if (S_ISDIR(st.st_mode)) + return git_fs_path_direach(path, 0, find_tmp_file_recurs, opaque); + + /* This is the template that's used in git_futils_mktmp. */ + if (strstr(git_str_cstr(path), "_git2_") != NULL) + return git_str_sets(first_tmp_file, git_str_cstr(path)); + + return 0; +} + +void test_pack_indexer__no_tmp_files(void) +{ + git_indexer *idx = NULL; + git_str path = GIT_STR_INIT; + git_str first_tmp_file = GIT_STR_INIT; + + /* Precondition: there are no temporary files. */ + cl_git_pass(git_str_sets(&path, clar_sandbox_path())); + cl_git_pass(find_tmp_file_recurs(&first_tmp_file, &path)); + git_str_dispose(&path); + cl_assert(git_str_len(&first_tmp_file) == 0); + + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); + git_indexer_free(idx); + + cl_git_pass(git_str_sets(&path, clar_sandbox_path())); + cl_git_pass(find_tmp_file_recurs(&first_tmp_file, &path)); + git_str_dispose(&path); + cl_assert(git_str_len(&first_tmp_file) == 0); + git_str_dispose(&first_tmp_file); +} diff --git a/tests/libgit2/pack/midx.c b/tests/libgit2/pack/midx.c new file mode 100644 index 000000000..9dd949363 --- /dev/null +++ b/tests/libgit2/pack/midx.c @@ -0,0 +1,111 @@ +#include "clar_libgit2.h" + +#include +#include + +#include "futils.h" +#include "midx.h" + +void test_pack_midx__parse(void) +{ + git_repository *repo; + struct git_midx_file *idx; + struct git_midx_entry e; + git_oid id; + git_str midx_path = GIT_STR_INIT; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_str_joinpath(&midx_path, git_repository_path(repo), "objects/pack/multi-pack-index")); + cl_git_pass(git_midx_open(&idx, git_str_cstr(&midx_path))); + cl_assert_equal_i(git_midx_needs_refresh(idx, git_str_cstr(&midx_path)), 0); + + cl_git_pass(git_oid_fromstr(&id, "5001298e0c09ad9c34e4249bc5801c75e9754fa5")); + cl_git_pass(git_midx_entry_find(&e, idx, &id, GIT_OID_HEXSZ)); + cl_assert_equal_oid(&e.sha1, &id); + cl_assert_equal_s( + (const char *)git_vector_get(&idx->packfile_names, e.pack_index), + "pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx"); + + git_midx_free(idx); + git_repository_free(repo); + git_str_dispose(&midx_path); +} + +void test_pack_midx__lookup(void) +{ + git_repository *repo; + git_commit *commit; + git_oid id; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + cl_git_pass(git_oid_fromstr(&id, "5001298e0c09ad9c34e4249bc5801c75e9754fa5")); + cl_git_pass(git_commit_lookup_prefix(&commit, repo, &id, GIT_OID_HEXSZ)); + cl_assert_equal_s(git_commit_message(commit), "packed commit one\n"); + + git_commit_free(commit); + git_repository_free(repo); +} + +void test_pack_midx__writer(void) +{ + git_repository *repo; + git_midx_writer *w = NULL; + git_buf midx = GIT_BUF_INIT; + git_str expected_midx = GIT_STR_INIT, path = GIT_STR_INIT; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "objects/pack")); + cl_git_pass(git_midx_writer_new(&w, git_str_cstr(&path))); + + cl_git_pass(git_midx_writer_add(w, "pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx")); + cl_git_pass(git_midx_writer_add(w, "pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx")); + cl_git_pass(git_midx_writer_add(w, "pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx")); + + cl_git_pass(git_midx_writer_dump(&midx, w)); + cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "objects/pack/multi-pack-index")); + cl_git_pass(git_futils_readbuffer(&expected_midx, git_str_cstr(&path))); + + cl_assert_equal_i(midx.size, git_str_len(&expected_midx)); + cl_assert_equal_strn(midx.ptr, git_str_cstr(&expected_midx), midx.size); + + git_buf_dispose(&midx); + git_str_dispose(&expected_midx); + git_str_dispose(&path); + git_midx_writer_free(w); + git_repository_free(repo); +} + +void test_pack_midx__odb_create(void) +{ + git_repository *repo; + git_odb *odb; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + git_str midx = GIT_STR_INIT, expected_midx = GIT_STR_INIT, midx_path = GIT_STR_INIT; + struct stat st; + + opts.bare = true; + opts.local = GIT_CLONE_LOCAL; + cl_git_pass(git_clone(&repo, cl_fixture("testrepo/.gitted"), "./clone.git", &opts)); + cl_git_pass(git_str_joinpath(&midx_path, git_repository_path(repo), "objects/pack/multi-pack-index")); + cl_git_fail(p_stat(git_str_cstr(&midx_path), &st)); + + cl_git_pass(git_repository_odb(&odb, repo)); + cl_git_pass(git_odb_write_multi_pack_index(odb)); + git_odb_free(odb); + + cl_git_pass(p_stat(git_str_cstr(&midx_path), &st)); + + cl_git_pass(git_futils_readbuffer(&expected_midx, cl_fixture("testrepo.git/objects/pack/multi-pack-index"))); + cl_git_pass(git_futils_readbuffer(&midx, git_str_cstr(&midx_path))); + cl_assert_equal_i(git_str_len(&midx), git_str_len(&expected_midx)); + cl_assert_equal_strn(git_str_cstr(&midx), git_str_cstr(&expected_midx), git_str_len(&midx)); + + git_repository_free(repo); + git_str_dispose(&midx); + git_str_dispose(&midx_path); + git_str_dispose(&expected_midx); + + cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); +} diff --git a/tests/libgit2/pack/packbuilder.c b/tests/libgit2/pack/packbuilder.c new file mode 100644 index 000000000..f23579817 --- /dev/null +++ b/tests/libgit2/pack/packbuilder.c @@ -0,0 +1,276 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "pack.h" +#include "hash.h" +#include "iterator.h" +#include "vector.h" +#include "posix.h" +#include "hash.h" + +static git_repository *_repo; +static git_revwalk *_revwalker; +static git_packbuilder *_packbuilder; +static git_indexer *_indexer; +static git_vector _commits; +static int _commits_is_initialized; +static git_indexer_progress _stats; + +extern bool git_disable_pack_keep_file_checks; + +void test_pack_packbuilder__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(p_chdir("testrepo.git")); + cl_git_pass(git_revwalk_new(&_revwalker, _repo)); + cl_git_pass(git_packbuilder_new(&_packbuilder, _repo)); + cl_git_pass(git_vector_init(&_commits, 0, NULL)); + _commits_is_initialized = 1; + memset(&_stats, 0, sizeof(_stats)); + p_fsync__cnt = 0; +} + +void test_pack_packbuilder__cleanup(void) +{ + git_oid *o; + unsigned int i; + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 0)); + cl_git_pass(git_libgit2_opts(GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, false)); + + if (_commits_is_initialized) { + _commits_is_initialized = 0; + git_vector_foreach(&_commits, i, o) { + git__free(o); + } + git_vector_free(&_commits); + } + + git_packbuilder_free(_packbuilder); + _packbuilder = NULL; + + git_revwalk_free(_revwalker); + _revwalker = NULL; + + git_indexer_free(_indexer); + _indexer = NULL; + + cl_git_pass(p_chdir("..")); + cl_git_sandbox_cleanup(); + _repo = NULL; +} + +static void seed_packbuilder(void) +{ + git_oid oid, *o; + unsigned int i; + + git_revwalk_sorting(_revwalker, GIT_SORT_TIME); + cl_git_pass(git_revwalk_push_ref(_revwalker, "HEAD")); + + while (git_revwalk_next(&oid, _revwalker) == 0) { + o = git__malloc(GIT_OID_RAWSZ); + cl_assert(o != NULL); + git_oid_cpy(o, &oid); + cl_git_pass(git_vector_insert(&_commits, o)); + } + + git_vector_foreach(&_commits, i, o) { + cl_git_pass(git_packbuilder_insert(_packbuilder, o, NULL)); + } + + git_vector_foreach(&_commits, i, o) { + git_object *obj; + cl_git_pass(git_object_lookup(&obj, _repo, o, GIT_OBJECT_COMMIT)); + cl_git_pass(git_packbuilder_insert_tree(_packbuilder, + git_commit_tree_id((git_commit *)obj))); + git_object_free(obj); + } +} + +static int feed_indexer(void *ptr, size_t len, void *payload) +{ + git_indexer_progress *stats = (git_indexer_progress *)payload; + + return git_indexer_append(_indexer, ptr, len, stats); +} + +void test_pack_packbuilder__create_pack(void) +{ + git_indexer_progress stats; + git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; + git_hash_ctx ctx; + unsigned char hash[GIT_HASH_SHA1_SIZE]; + char hex[(GIT_HASH_SHA1_SIZE * 2) + 1]; + + seed_packbuilder(); + + cl_git_pass(git_indexer_new(&_indexer, ".", 0, NULL, NULL)); + cl_git_pass(git_packbuilder_foreach(_packbuilder, feed_indexer, &stats)); + cl_git_pass(git_indexer_commit(_indexer, &stats)); + + git_str_printf(&path, "pack-%s.pack", git_indexer_name(_indexer)); + + /* + * By default, packfiles are created with only one thread. + * Therefore we can predict the object ordering and make sure + * we create exactly the same pack as git.git does when *not* + * reusing existing deltas (as libgit2). + * + * $ cd tests/resources/testrepo.git + * $ git rev-list --objects HEAD | \ + * git pack-objects -q --no-reuse-delta --threads=1 pack + * $ sha1sum pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.pack + * 5d410bdf97cf896f9007681b92868471d636954b + * + */ + + cl_git_pass(git_futils_readbuffer(&buf, git_str_cstr(&path))); + + cl_git_pass(git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)); + cl_git_pass(git_hash_update(&ctx, buf.ptr, buf.size)); + cl_git_pass(git_hash_final(hash, &ctx)); + git_hash_ctx_cleanup(&ctx); + + git_str_dispose(&path); + git_str_dispose(&buf); + + git_hash_fmt(hex, hash, GIT_HASH_SHA1_SIZE); + cl_assert_equal_s(hex, "5d410bdf97cf896f9007681b92868471d636954b"); +} + +void test_pack_packbuilder__get_name(void) +{ + seed_packbuilder(); + + cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0, NULL, NULL)); + cl_assert_equal_s("7f5fa362c664d68ba7221259be1cbd187434b2f0", git_packbuilder_name(_packbuilder)); +} + +void test_pack_packbuilder__write_default_path(void) +{ + seed_packbuilder(); + + cl_git_pass(git_packbuilder_write(_packbuilder, NULL, 0, NULL, NULL)); + cl_assert(git_fs_path_exists("objects/pack/pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.idx")); + cl_assert(git_fs_path_exists("objects/pack/pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.pack")); +} + +static void test_write_pack_permission(mode_t given, mode_t expected) +{ + struct stat statbuf; + mode_t mask, os_mask; + + seed_packbuilder(); + + cl_git_pass(git_packbuilder_write(_packbuilder, ".", given, NULL, NULL)); + + /* Windows does not return group/user bits from stat, + * files are never executable. + */ +#ifdef GIT_WIN32 + os_mask = 0600; +#else + os_mask = 0777; +#endif + + mask = p_umask(0); + p_umask(mask); + + cl_git_pass(p_stat("pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.idx", &statbuf)); + cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask); + + cl_git_pass(p_stat("pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.pack", &statbuf)); + cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask); +} + +void test_pack_packbuilder__permissions_standard(void) +{ + test_write_pack_permission(0, GIT_PACK_FILE_MODE); +} + +void test_pack_packbuilder__permissions_readonly(void) +{ + test_write_pack_permission(0444, 0444); +} + +void test_pack_packbuilder__permissions_readwrite(void) +{ + test_write_pack_permission(0666, 0666); +} + +void test_pack_packbuilder__does_not_fsync_by_default(void) +{ + seed_packbuilder(); + cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0666, NULL, NULL)); + cl_assert_equal_sz(0, p_fsync__cnt); +} + +/* We fsync the packfile and index. On non-Windows, we also fsync + * the parent directories. + */ +#ifdef GIT_WIN32 +static int expected_fsyncs = 2; +#else +static int expected_fsyncs = 4; +#endif + +void test_pack_packbuilder__fsync_global_setting(void) +{ + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 1)); + p_fsync__cnt = 0; + seed_packbuilder(); + cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0666, NULL, NULL)); + cl_assert_equal_sz(expected_fsyncs, p_fsync__cnt); +} + +void test_pack_packbuilder__fsync_repo_setting(void) +{ + cl_repo_set_bool(_repo, "core.fsyncObjectFiles", true); + p_fsync__cnt = 0; + seed_packbuilder(); + cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0666, NULL, NULL)); + cl_assert_equal_sz(expected_fsyncs, p_fsync__cnt); +} + +static int foreach_cb(void *buf, size_t len, void *payload) +{ + git_indexer *idx = (git_indexer *) payload; + cl_git_pass(git_indexer_append(idx, buf, len, &_stats)); + return 0; +} + +void test_pack_packbuilder__foreach(void) +{ + git_indexer *idx; + + seed_packbuilder(); + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); + cl_git_pass(git_packbuilder_foreach(_packbuilder, foreach_cb, idx)); + cl_git_pass(git_indexer_commit(idx, &_stats)); + git_indexer_free(idx); +} + +static int foreach_cancel_cb(void *buf, size_t len, void *payload) +{ + git_indexer *idx = (git_indexer *)payload; + cl_git_pass(git_indexer_append(idx, buf, len, &_stats)); + return (_stats.total_objects > 2) ? -1111 : 0; +} + +void test_pack_packbuilder__foreach_with_cancel(void) +{ + git_indexer *idx; + + seed_packbuilder(); + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); + cl_git_fail_with( + git_packbuilder_foreach(_packbuilder, foreach_cancel_cb, idx), -1111); + git_indexer_free(idx); +} + +void test_pack_packbuilder__keep_file_check(void) +{ + assert(!git_disable_pack_keep_file_checks); + cl_git_pass(git_libgit2_opts(GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, true)); + assert(git_disable_pack_keep_file_checks); +} diff --git a/tests/libgit2/pack/sharing.c b/tests/libgit2/pack/sharing.c new file mode 100644 index 000000000..eaf7686b7 --- /dev/null +++ b/tests/libgit2/pack/sharing.c @@ -0,0 +1,42 @@ +#include "clar_libgit2.h" +#include +#include "strmap.h" +#include "mwindow.h" +#include "pack.h" + +extern git_strmap *git__pack_cache; + +void test_pack_sharing__open_two_repos(void) +{ + git_repository *repo1, *repo2; + git_object *obj1, *obj2; + git_oid id; + size_t pos; + void *data; + int error; + + cl_git_pass(git_repository_open(&repo1, cl_fixture("testrepo.git"))); + cl_git_pass(git_repository_open(&repo2, cl_fixture("testrepo.git"))); + + git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + cl_git_pass(git_object_lookup(&obj1, repo1, &id, GIT_OBJECT_ANY)); + cl_git_pass(git_object_lookup(&obj2, repo2, &id, GIT_OBJECT_ANY)); + + pos = 0; + while ((error = git_strmap_iterate(&data, git__pack_cache, &pos, NULL)) == 0) { + struct git_pack_file *pack = (struct git_pack_file *) data; + + cl_assert_equal_i(2, pack->refcount.val); + } + + cl_assert_equal_i(3, git_strmap_size(git__pack_cache)); + + git_object_free(obj1); + git_object_free(obj2); + git_repository_free(repo1); + git_repository_free(repo2); + + /* we don't want to keep the packs open after the repos go away */ + cl_assert_equal_i(0, git_strmap_size(git__pack_cache)); +} diff --git a/tests/libgit2/pack/threadsafety.c b/tests/libgit2/pack/threadsafety.c new file mode 100644 index 000000000..fd6a61fbd --- /dev/null +++ b/tests/libgit2/pack/threadsafety.c @@ -0,0 +1,62 @@ +#include "clar_libgit2.h" +#include "pool.h" + +#include +#include "git2/sys/commit.h" +#include "git2/sys/mempack.h" + +static size_t original_mwindow_file_limit = 0; + +void test_pack_threadsafety__initialize(void) +{ + size_t open_mwindow_files = 1; + + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_MWINDOW_FILE_LIMIT, &original_mwindow_file_limit)); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_MWINDOW_FILE_LIMIT, open_mwindow_files)); +} + +void test_pack_threadsafety__cleanup(void) +{ + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_MWINDOW_FILE_LIMIT, original_mwindow_file_limit)); +} + +#ifdef GIT_THREADS +static void *get_status(void *arg) +{ + const char *repo_path = (const char *)arg; + git_repository *repo; + git_status_list *status; + + cl_git_pass(git_repository_open(&repo, repo_path)); + cl_git_pass(git_status_list_new(&status, repo, NULL)); + git_status_list_free(status); + git_repository_free(repo); + + return NULL; +} +#endif + +void test_pack_threadsafety__open_repo_in_multiple_threads(void) +{ +#ifdef GIT_THREADS + const char *repo_path = cl_fixture("../.."); + git_repository *repo; + git_thread threads[8]; + size_t i; + + /* If we can't open the libgit2 repo or if it isn't a full repo + * with proper history, just skip this test */ + if (git_repository_open(&repo, repo_path) < 0) + cl_skip(); + if (git_repository_is_shallow(repo)) + cl_skip(); + git_repository_free(repo); + + for (i = 0; i < ARRAY_SIZE(threads); i++) + git_thread_create(&threads[i], get_status, (void *)repo_path); + for (i = 0; i < ARRAY_SIZE(threads); i++) + git_thread_join(&threads[i], NULL); +#else + cl_skip(); +#endif +} diff --git a/tests/libgit2/patch/parse.c b/tests/libgit2/patch/parse.c new file mode 100644 index 000000000..a3c4c6730 --- /dev/null +++ b/tests/libgit2/patch/parse.c @@ -0,0 +1,221 @@ +#include "clar_libgit2.h" +#include "patch.h" +#include "patch_parse.h" + +#include "patch_common.h" + +static void ensure_patch_validity(git_patch *patch) +{ + const git_diff_delta *delta; + char idstr[GIT_OID_HEXSZ+1] = {0}; + + cl_assert((delta = git_patch_get_delta(patch)) != NULL); + cl_assert_equal_i(2, delta->nfiles); + + cl_assert_equal_s(delta->old_file.path, "file.txt"); + cl_assert(delta->old_file.mode == GIT_FILEMODE_BLOB); + cl_assert_equal_i(7, delta->old_file.id_abbrev); + git_oid_nfmt(idstr, delta->old_file.id_abbrev, &delta->old_file.id); + cl_assert_equal_s(idstr, "9432026"); + cl_assert_equal_i(0, delta->old_file.size); + + cl_assert_equal_s(delta->new_file.path, "file.txt"); + cl_assert(delta->new_file.mode == GIT_FILEMODE_BLOB); + cl_assert_equal_i(7, delta->new_file.id_abbrev); + git_oid_nfmt(idstr, delta->new_file.id_abbrev, &delta->new_file.id); + cl_assert_equal_s(idstr, "cd8fd12"); + cl_assert_equal_i(0, delta->new_file.size); +} + +static void ensure_identical_patch_inout(const char *content) +{ + git_buf buf = GIT_BUF_INIT; + git_patch *patch; + + cl_git_pass(git_patch_from_buffer(&patch, content, strlen(content), NULL)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + cl_assert_equal_strn(buf.ptr, content, strlen(content)); + + git_patch_free(patch); + git_buf_dispose(&buf); +} + +void test_patch_parse__original_to_change_middle(void) +{ + git_patch *patch; + + cl_git_pass(git_patch_from_buffer( + &patch, PATCH_ORIGINAL_TO_CHANGE_MIDDLE, + strlen(PATCH_ORIGINAL_TO_CHANGE_MIDDLE), NULL)); + ensure_patch_validity(patch); + git_patch_free(patch); +} + +void test_patch_parse__leading_and_trailing_garbage(void) +{ + git_patch *patch; + const char *leading = "This is some leading garbage.\n" + "Maybe it's email headers?\n" + "\n" + PATCH_ORIGINAL_TO_CHANGE_MIDDLE; + const char *trailing = PATCH_ORIGINAL_TO_CHANGE_MIDDLE + "\n" + "This is some trailing garbage.\n" + "Maybe it's an email signature?\n"; + const char *both = "Here's some leading garbage\n" + PATCH_ORIGINAL_TO_CHANGE_MIDDLE + "And here's some trailing.\n"; + + cl_git_pass(git_patch_from_buffer(&patch, leading, strlen(leading), + NULL)); + ensure_patch_validity(patch); + git_patch_free(patch); + + cl_git_pass(git_patch_from_buffer(&patch, trailing, strlen(trailing), + NULL)); + ensure_patch_validity(patch); + git_patch_free(patch); + + cl_git_pass(git_patch_from_buffer(&patch, both, strlen(both), + NULL)); + ensure_patch_validity(patch); + git_patch_free(patch); +} + +void test_patch_parse__nonpatches_fail_with_notfound(void) +{ + git_patch *patch; + + cl_git_fail_with(GIT_ENOTFOUND, + git_patch_from_buffer(&patch, PATCH_NOT_A_PATCH, + strlen(PATCH_NOT_A_PATCH), NULL)); +} + +void test_patch_parse__invalid_patches_fails(void) +{ + git_patch *patch; + + cl_git_fail_with(GIT_ERROR, + git_patch_from_buffer(&patch, PATCH_CORRUPT_GIT_HEADER, + strlen(PATCH_CORRUPT_GIT_HEADER), NULL)); + cl_git_fail_with(GIT_ERROR, + git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_NEW_FILE, + strlen(PATCH_CORRUPT_MISSING_NEW_FILE), NULL)); + cl_git_fail_with(GIT_ERROR, + git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_OLD_FILE, + strlen(PATCH_CORRUPT_MISSING_OLD_FILE), NULL)); + cl_git_fail_with(GIT_ERROR, + git_patch_from_buffer(&patch, PATCH_CORRUPT_NO_CHANGES, + strlen(PATCH_CORRUPT_NO_CHANGES), NULL)); + cl_git_fail_with(GIT_ERROR, + git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_HUNK_HEADER, + strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL)); +} + +void test_patch_parse__no_newline_at_end_of_new_file(void) +{ + ensure_identical_patch_inout(PATCH_APPEND_NO_NL); +} + +void test_patch_parse__no_newline_at_end_of_old_file(void) +{ + ensure_identical_patch_inout(PATCH_APPEND_NO_NL_IN_OLD_FILE); +} + +void test_patch_parse__files_with_whitespaces_succeeds(void) +{ + ensure_identical_patch_inout(PATCH_NAME_WHITESPACE); +} + +void test_patch_parse__lifetime_of_patch_does_not_depend_on_buffer(void) +{ + git_str diff = GIT_STR_INIT; + git_buf rendered = GIT_BUF_INIT; + git_patch *patch; + + cl_git_pass(git_str_sets(&diff, PATCH_ORIGINAL_TO_CHANGE_MIDDLE)); + cl_git_pass(git_patch_from_buffer(&patch, diff.ptr, diff.size, NULL)); + git_str_dispose(&diff); + + cl_git_pass(git_patch_to_buf(&rendered, patch)); + cl_assert_equal_s(PATCH_ORIGINAL_TO_CHANGE_MIDDLE, rendered.ptr); + git_buf_dispose(&rendered); + + cl_git_pass(git_patch_to_buf(&rendered, patch)); + cl_assert_equal_s(PATCH_ORIGINAL_TO_CHANGE_MIDDLE, rendered.ptr); + git_buf_dispose(&rendered); + + git_patch_free(patch); +} + +void test_patch_parse__binary_file_with_missing_paths(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_MISSING_PATHS, + strlen(PATCH_BINARY_FILE_WITH_MISSING_PATHS), NULL)); +} + +void test_patch_parse__binary_file_with_whitespace_paths(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_WHITESPACE_PATHS, + strlen(PATCH_BINARY_FILE_WITH_WHITESPACE_PATHS), NULL)); +} + +void test_patch_parse__binary_file_with_empty_quoted_paths(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_QUOTED_EMPTY_PATHS, + strlen(PATCH_BINARY_FILE_WITH_QUOTED_EMPTY_PATHS), NULL)); +} + +void test_patch_parse__binary_file_path_with_spaces(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_PATH_WITH_SPACES, + strlen(PATCH_BINARY_FILE_PATH_WITH_SPACES), NULL)); +} + +void test_patch_parse__binary_file_path_without_body_paths(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_PATH_WITHOUT_BODY_PATHS, + strlen(PATCH_BINARY_FILE_PATH_WITHOUT_BODY_PATHS), NULL)); +} + +void test_patch_parse__binary_file_with_truncated_delta(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_TRUNCATED_DELTA, + strlen(PATCH_BINARY_FILE_WITH_TRUNCATED_DELTA), NULL)); + cl_assert_equal_s(git_error_last()->message, "truncated binary data at line 5"); +} + +void test_patch_parse__memory_leak_on_multiple_paths(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_MULTIPLE_OLD_PATHS, strlen(PATCH_MULTIPLE_OLD_PATHS), NULL)); +} + +void test_patch_parse__truncated_no_newline_at_end_of_file(void) +{ + size_t len = strlen(PATCH_APPEND_NO_NL) - strlen("at end of file\n"); + const git_diff_line *line; + git_patch *patch; + + cl_git_pass(git_patch_from_buffer(&patch, PATCH_APPEND_NO_NL, len, NULL)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 4)); + cl_assert_equal_s(line->content, "\\ No newline "); + + git_patch_free(patch); +} + +void test_patch_parse__line_number_overflow(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_INTMAX_NEW_LINES, strlen(PATCH_INTMAX_NEW_LINES), NULL)); + git_patch_free(patch); +} diff --git a/tests/libgit2/patch/patch_common.h b/tests/libgit2/patch/patch_common.h new file mode 100644 index 000000000..1e03889fc --- /dev/null +++ b/tests/libgit2/patch/patch_common.h @@ -0,0 +1,1005 @@ +/* The original file contents */ + +#define FILE_ORIGINAL \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" + +/* A change in the middle of the file (and the resultant patch) */ + +#define FILE_CHANGE_MIDDLE \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(THIS line is changed!)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" + +#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -6 +6 @@ yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" + +/* A change of the first line (and the resultant patch) */ + +#define FILE_CHANGE_FIRSTLINE \ + "hey, change in head!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" + +#define PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..c81df1d 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,4 +1,4 @@\n" \ + "-hey!\n" \ + "+hey, change in head!\n" \ + " this is some context!\n" \ + " around some lines\n" \ + " that will change\n" + +/* A change of the last line (and the resultant patch) */ + +#define FILE_CHANGE_LASTLINE \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "change to the last line.\n" + +#define PATCH_ORIGINAL_TO_CHANGE_LASTLINE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..f70db1c 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -6,4 +6,4 @@ yes it is!\n" \ + " (this line is changed)\n" \ + " and this\n" \ + " is additional context\n" \ + "-below it!\n" \ + "+change to the last line.\n" + +/* A change of the middle where we remove many lines */ + +#define FILE_CHANGE_MIDDLE_SHRINK \ + "hey!\n" \ + "i've changed a lot, but left the line\n" \ + "below it!\n" + +#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_SHRINK \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..629cd35 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,9 +1,3 @@\n" \ + " hey!\n" \ + "-this is some context!\n" \ + "-around some lines\n" \ + "-that will change\n" \ + "-yes it is!\n" \ + "-(this line is changed)\n" \ + "-and this\n" \ + "-is additional context\n" \ + "+i've changed a lot, but left the line\n" \ + " below it!\n" + +#define PATCH_ORIGINAL_TO_MIDDLE_SHRINK_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..629cd35 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -2,7 +2 @@ hey!\n" \ + "-this is some context!\n" \ + "-around some lines\n" \ + "-that will change\n" \ + "-yes it is!\n" \ + "-(this line is changed)\n" \ + "-and this\n" \ + "-is additional context\n" \ + "+i've changed a lot, but left the line\n" + +/* A change to the middle where we grow many lines */ + +#define FILE_CHANGE_MIDDLE_GROW \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "this line is changed\n" \ + "and this line is added\n" \ + "so is this\n" \ + "(this too)\n" \ + "whee...\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" + +#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_GROW \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..207ebca 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -3,7 +3,11 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+this line is changed\n" \ + "+and this line is added\n" \ + "+so is this\n" \ + "+(this too)\n" \ + "+whee...\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + + +#define PATCH_ORIGINAL_TO_MIDDLE_GROW_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..207ebca 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -6 +6,5 @@ yes it is!\n" \ + "-(this line is changed)\n" \ + "+this line is changed\n" \ + "+and this line is added\n" \ + "+so is this\n" \ + "+(this too)\n" \ + "+whee...\n" + +/* An insertion at the beginning of the file (and the resultant patch) */ + +#define FILE_PREPEND \ + "insert at front\n" \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" + +#define PATCH_ORIGINAL_TO_PREPEND \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..0f39b9a 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,3 +1,4 @@\n" \ + "+insert at front\n" \ + " hey!\n" \ + " this is some context!\n" \ + " around some lines\n" + +#define PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..0f39b9a 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -0,0 +1 @@\n" \ + "+insert at front\n" + +/* An insertion at the beginning of the file and change in the middle */ + +#define FILE_PREPEND_AND_CHANGE \ + "insert at front\n" \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(THIS line is changed!)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" + +#define PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..f73c8bb 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,9 +1,10 @@\n" \ + "+insert at front\n" \ + " hey!\n" \ + " this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..f73c8bb 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -0,0 +1 @@\n" \ + "+insert at front\n" \ + "@@ -6 +7 @@ yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" + +/* A change in the middle and a deletion of the newline at the end of the file */ + +#define FILE_CHANGE_MIDDLE_AND_LASTLINE \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(THIS line is changed!)\n" \ + "and this\n" \ + "is additional context\n" \ + "BELOW it! - (THIS line is changed!)" + +#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_AND_LASTLINE_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..e05d36c 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -6 +6 @@ yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + "@@ -9 +9 @@ is additional context\n" \ + "-below it!\n" \ + "+BELOW it! - (THIS line is changed!)\n" \ + "\\ No newline at end of file\n" + +/* A deletion at the beginning of the file and a change in the middle */ + +#define FILE_DELETE_AND_CHANGE \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(THIS line is changed!)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" + +#define PATCH_ORIGINAL_TO_DELETE_AND_CHANGE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..1e2dfa6 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,9 +1,8 @@\n" \ + "-hey!\n" \ + " this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define PATCH_ORIGINAL_TO_DELETE_AND_CHANGE_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..1e2dfa6 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1 +0,0 @@\n" \ + "-hey!\n" \ + "@@ -6 +5 @@ yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" + +/* A deletion at the beginning of the file */ + +#define FILE_DELETE_FIRSTLINE \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" + +#define PATCH_ORIGINAL_TO_DELETE_FIRSTLINE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..f31fa13 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,4 +1,3 @@\n" \ + "-hey!\n" \ + " this is some context!\n" \ + " around some lines\n" \ + " that will change\n" + +/* An insertion at the end of the file (and the resultant patch) */ + +#define FILE_APPEND \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" \ + "insert at end\n" + +#define PATCH_ORIGINAL_TO_APPEND \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..72788bb 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -7,3 +7,4 @@ yes it is!\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" \ + "+insert at end\n" + +#define PATCH_ORIGINAL_TO_APPEND_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..72788bb 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -9,0 +10 @@ below it!\n" \ + "+insert at end\n" + +#define PATCH_DELETED_FILE_2_HUNKS \ + "diff --git a/a b/a\n" \ + "index 7f129fd..af431f2 100644\n" \ + "--- a/a\n" \ + "+++ b/a\n" \ + "@@ -1 +1 @@\n" \ + "-a contents 2\n" \ + "+a contents\n" \ + "diff --git a/c/d b/c/d\n" \ + "deleted file mode 100644\n" \ + "index 297efb8..0000000\n" \ + "--- a/c/d\n" \ + "+++ /dev/null\n" \ + "@@ -1 +0,0 @@\n" \ + "-c/d contents\n" + +#define PATCH_DELETED_FILE_2_HUNKS_SHUFFLED \ + "diff --git a/c/d b/c/d\n" \ + "deleted file mode 100644\n" \ + "index 297efb8..0000000\n" \ + "--- a/c/d\n" \ + "+++ /dev/null\n" \ + "@@ -1 +0,0 @@\n" \ + "-c/d contents\n" \ + "diff --git a/a b/a\n" \ + "index 7f129fd..af431f2 100644\n" \ + "--- a/a\n" \ + "+++ b/a\n" \ + "@@ -1 +1 @@\n" \ + "-a contents 2\n" \ + "+a contents\n" + +#define PATCH_SIMPLE_COMMIT \ + "commit 15e119375018fba121cf58e02a9f17fe22df0df8\n" \ + "Author: Edward Thomson \n" \ + "Date: Wed Jun 14 13:31:20 2017 +0200\n" \ + "\n" \ + " CHANGELOG: document git_filter_init and GIT_FILTER_INIT\n" \ + "\n" \ + "diff --git a/CHANGELOG.md b/CHANGELOG.md\n" \ + "index 1b9e0c90a..24ecba426 100644\n" \ + "--- a/CHANGELOG.md\n" \ + "+++ b/CHANGELOG.md\n" \ + "@@ -96,6 +96,9 @@ v0.26\n" \ + " * `git_transport_smart_proxy_options()' enables you to get the proxy options for\n" \ + " smart transports.\n" \ + "\n" \ + "+* The `GIT_FILTER_INIT` macro and the `git_filter_init` function are provided\n" \ + "+ to initialize a `git_filter` structure.\n" \ + "+\n" \ + " ### Breaking API changes\n" \ + "\n" \ + " * `clone_checkout_strategy` has been removed from\n" + +#define PATCH_MULTIPLE_HUNKS \ + "diff --git a/x b/x\n" \ + "index 0719398..fa0350c 100644\n" \ + "--- a/x\n" \ + "+++ b/x\n" \ + "@@ -1,5 +1,4 @@\n" \ + " 1\n" \ + "-2\n" \ + " 3\n" \ + " 4\n" \ + " 5\n" \ + "@@ -7,3 +6,4 @@\n" \ + " 7\n" \ + " 8\n" \ + " 9\n" \ + "+10\n" + +#define PATCH_MULTIPLE_FILES \ + "diff --git a/x b/x\n" \ + "index 8a1218a..7059ba5 100644\n" \ + "--- a/x\n" \ + "+++ b/x\n" \ + "@@ -1,5 +1,4 @@\n" \ + " 1\n" \ + " 2\n" \ + "-3\n" \ + " 4\n" \ + " 5\n" \ + "diff --git a/y b/y\n" \ + "index e006065..9405325 100644\n" \ + "--- a/y\n" \ + "+++ b/y\n" \ + "@@ -1,4 +1,5 @@\n" \ + " a\n" \ + " b\n" \ + "+c\n" \ + " d\n" \ + " e\n" + +#define FILE_PREPEND_AND_APPEND \ + "first and\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "last lines\n" + +#define PATCH_ORIGINAL_TO_PREPEND_AND_APPEND \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..f282430 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,4 +1,4 @@\n" \ + "-hey!\n" \ + "+first and\n" \ + " this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + "@@ -6,4 +6,4 @@ yes it is!\n" \ + " (this line is changed)\n" \ + " and this\n" \ + " is additional context\n" \ + "-below it!\n" \ + "+last lines\n" + +#define PATCH_ORIGINAL_TO_EMPTY_FILE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..e69de29 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,9 +0,0 @@\n" \ + "-hey!\n" \ + "-this is some context!\n" \ + "-around some lines\n" \ + "-that will change\n" \ + "-yes it is!\n" \ + "-(this line is changed)\n" \ + "-and this\n" \ + "-is additional context\n" \ + "-below it!\n" + +#define PATCH_EMPTY_FILE_TO_ORIGINAL \ + "diff --git a/file.txt b/file.txt\n" \ + "index e69de29..9432026 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -0,0 +1,9 @@\n" \ + "+hey!\n" \ + "+this is some context!\n" \ + "+around some lines\n" \ + "+that will change\n" \ + "+yes it is!\n" \ + "+(this line is changed)\n" \ + "+and this\n" \ + "+is additional context\n" \ + "+below it!\n" + +#define PATCH_ADD_ORIGINAL \ + "diff --git a/file.txt b/file.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..9432026\n" \ + "--- /dev/null\n" \ + "+++ b/file.txt\n" \ + "@@ -0,0 +1,9 @@\n" \ + "+hey!\n" \ + "+this is some context!\n" \ + "+around some lines\n" \ + "+that will change\n" \ + "+yes it is!\n" \ + "+(this line is changed)\n" \ + "+and this\n" \ + "+is additional context\n" \ + "+below it!\n" + +#define PATCH_DELETE_ORIGINAL \ + "diff --git a/file.txt b/file.txt\n" \ + "deleted file mode 100644\n" \ + "index 9432026..0000000\n" \ + "--- a/file.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,9 +0,0 @@\n" \ + "-hey!\n" \ + "-this is some context!\n" \ + "-around some lines\n" \ + "-that will change\n" \ + "-yes it is!\n" \ + "-(this line is changed)\n" \ + "-and this\n" \ + "-is additional context\n" \ + "-below it!\n" + +#define PATCH_RENAME_EXACT \ + "diff --git a/file.txt b/newfile.txt\n" \ + "similarity index 100%\n" \ + "rename from file.txt\n" \ + "rename to newfile.txt\n" + +#define PATCH_RENAME_EXACT_WITH_MODE \ + "diff --git a/RENAMED.md b/README.md\n" \ + "old mode 100644\n" \ + "new mode 100755\n" \ + "similarity index 100%\n" \ + "rename from RENAMED.md\n" \ + "rename to README.md\n" + +#define PATCH_RENAME_SIMILAR \ + "diff --git a/file.txt b/newfile.txt\n" \ + "similarity index 77%\n" \ + "rename from file.txt\n" \ + "rename to newfile.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/newfile.txt\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define PATCH_RENAME_EXACT_QUOTEDNAME \ + "diff --git a/file.txt \"b/foo\\\"bar.txt\"\n" \ + "similarity index 100%\n" \ + "rename from file.txt\n" \ + "rename to \"foo\\\"bar.txt\"\n" + +#define PATCH_RENAME_SIMILAR_QUOTEDNAME \ + "diff --git a/file.txt \"b/foo\\\"bar.txt\"\n" \ + "similarity index 77%\n" \ + "rename from file.txt\n" \ + "rename to \"foo\\\"bar.txt\"\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ \"b/foo\\\"bar.txt\"\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define PATCH_MODECHANGE_UNCHANGED \ + "diff --git a/file.txt b/file.txt\n" \ + "old mode 100644\n" \ + "new mode 100755\n" + +#define PATCH_MODECHANGE_MODIFIED \ + "diff --git a/file.txt b/file.txt\n" \ + "old mode 100644\n" \ + "new mode 100755\n" \ + "index 9432026..cd8fd12\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define PATCH_NOISY \ + "This is some\nleading noise\n@@ - that\nlooks like a hunk header\n" \ + "but actually isn't and should parse ok\n" \ + PATCH_ORIGINAL_TO_CHANGE_MIDDLE \ + "plus some trailing garbage for good measure\n" + +#define PATCH_NOISY_NOCONTEXT \ + "This is some\nleading noise\n@@ - that\nlooks like a hunk header\n" \ + "but actually isn't and should parse ok\n" \ + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT \ + "plus some trailing garbage for good measure\n" + +#define PATCH_TRUNCATED_1 \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" + +#define PATCH_TRUNCATED_2 \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define PATCH_TRUNCATED_3 \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define FILE_EMPTY_CONTEXT_ORIGINAL \ + "this\nhas\nan\n\nempty\ncontext\nline\n" + +#define FILE_EMPTY_CONTEXT_MODIFIED \ + "this\nhas\nan\n\nempty...\ncontext\nline\n" + +#define PATCH_EMPTY_CONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 398d2df..bb15234 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -2,6 +2,6 @@ this\n" \ + " has\n" \ + " an\n" \ + "\n" \ + "-empty\n" \ + "+empty...\n" \ + " context\n" \ + " line\n" + +#define FILE_APPEND_NO_NL \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" \ + "added line with no nl" + +#define PATCH_APPEND_NO_NL \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..83759c0 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -7,3 +7,4 @@ yes it is!\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" \ + "+added line with no nl\n" \ + "\\ No newline at end of file\n" + +#define PATCH_APPEND_NO_NL_IN_OLD_FILE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..83759c0 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,1 +1,1 @@\n" \ + "-foo\n" \ + "\\ No newline at end of file\n" \ + "+foo\n" + +#define PATCH_NAME_WHITESPACE \ + "diff --git a/file with spaces.txt b/file with spaces.txt\n" \ + "index 9432026..83759c0 100644\n" \ + "--- a/file with spaces.txt\n" \ + "+++ b/file with spaces.txt\n" \ + "@@ -0,3 +0,2 @@\n" \ + " and this\n" \ + "-is additional context\n" \ + " below it!\n" \ + +#define PATCH_CORRUPT_GIT_HEADER \ + "diff --git a/file.txt\n" \ + "index 9432026..0f39b9a 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -0,0 +1 @@\n" \ + "+insert at front\n" + +#define PATCH_CORRUPT_MISSING_NEW_FILE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "@@ -6 +6 @@ yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" + +#define PATCH_CORRUPT_MISSING_OLD_FILE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "+++ b/file.txt\n" \ + "@@ -6 +6 @@ yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" + +#define PATCH_CORRUPT_NO_CHANGES \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -0,0 +0,0 @@ yes it is!\n" + +#define PATCH_CORRUPT_MISSING_HUNK_HEADER \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" + +#define PATCH_NOT_A_PATCH \ + "+++this is not\n" \ + "--actually even\n" \ + " a legitimate \n" \ + "+patch file\n" \ + "-it's something else\n" \ + " entirely!" + +/* binary contents */ + +#define FILE_BINARY_LITERAL_ORIGINAL "\x00\x00\x0a" +#define FILE_BINARY_LITERAL_ORIGINAL_LEN 3 + +#define FILE_BINARY_LITERAL_MODIFIED "\x00\x00\x01\x02\x0a" +#define FILE_BINARY_LITERAL_MODIFIED_LEN 5 + +#define PATCH_BINARY_LITERAL \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index bd474b2519cc15eab801ff851cc7d50f0dee49a1..9ac35ff15cd8864aeafd889e4826a3150f0b06c4 100644\n" \ + "GIT binary patch\n" \ + "literal 5\n" \ + "Mc${NkU}WL~000&M4gdfE\n" \ + "\n" \ + "literal 3\n" \ + "Kc${Nk-~s>u4FC%O\n\n" + +#define FILE_BINARY_DELTA_ORIGINAL \ + "\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02\x0a\x54\x68\x69" \ + "\x73\x20\x69\x73\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x66\x69" \ + "\x6c\x65\x2c\x20\x62\x79\x20\x76\x69\x72\x74\x75\x65\x20\x6f\x66" \ + "\x20\x68\x61\x76\x69\x6e\x67\x20\x73\x6f\x6d\x65\x20\x6e\x75\x6c" \ + "\x6c\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ + "\x0a\x57\x65\x27\x72\x65\x20\x67\x6f\x69\x6e\x67\x20\x74\x6f\x20" \ + "\x63\x68\x61\x6e\x67\x65\x20\x70\x6f\x72\x74\x69\x6f\x6e\x73\x20" \ + "\x6f\x66\x20\x69\x74\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00" \ + "\x00\x01\x02\x0a\x53\x6f\x20\x74\x68\x61\x74\x20\x77\x65\x20\x67" \ + "\x69\x74\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x64\x65\x6c\x74" \ + "\x61\x20\x69\x6e\x73\x74\x65\x61\x64\x20\x6f\x66\x20\x74\x68\x65" \ + "\x20\x64\x65\x66\x6c\x61\x74\x65\x64\x20\x63\x6f\x6e\x74\x65\x6e" \ + "\x74\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ + "\x0a" +#define FILE_BINARY_DELTA_ORIGINAL_LEN 209 + +#define FILE_BINARY_DELTA_MODIFIED \ + "\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02\x0a\x5a\x5a\x5a" \ + "\x5a\x20\x69\x73\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x66\x69" \ + "\x6c\x65\x2c\x20\x62\x79\x20\x76\x69\x72\x74\x75\x65\x20\x6f\x66" \ + "\x20\x68\x61\x76\x69\x6e\x67\x20\x73\x6f\x6d\x65\x20\x6e\x75\x6c" \ + "\x6c\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ + "\x0a\x57\x65\x27\x72\x65\x20\x67\x6f\x69\x6e\x67\x20\x74\x6f\x20" \ + "\x63\x68\x61\x6e\x67\x65\x20\x70\x6f\x72\x74\x69\x6f\x6e\x73\x20" \ + "\x6f\x66\x20\x49\x54\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00" \ + "\x00\x01\x02\x0a\x53\x4f\x20\x74\x68\x61\x74\x20\x77\x65\x20\x67" \ + "\x69\x74\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x64\x65\x6c\x74" \ + "\x61\x20\x69\x6e\x73\x74\x65\x61\x64\x20\x6f\x66\x20\x74\x68\x65" \ + "\x20\x64\x65\x66\x6c\x61\x74\x65\x64\x20\x63\x6f\x6e\x74\x65\x6e" \ + "\x74\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ + "\x0a" +#define FILE_BINARY_DELTA_MODIFIED_LEN 209 + +#define PATCH_BINARY_DELTA \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index 27184d9883b12c4c9c54b4a31137603586169f51..7c94f9e60bf366033d98e0d551ae37d30faef74a 100644\n" \ + "GIT binary patch\n" \ + "delta 48\n" \ + "kc$~Y)c#%<%fq{_;hPk4EV4`4>uxE%K7m7r%|HL+L0In7XGynhq\n" \ + "\n" \ + "delta 48\n" \ + "mc$~Y)c#%<%fq{_;hPgsAGK(h)CJASj=y9P)1m{m|^9BI99|yz$\n\n" + +#define PATCH_BINARY_ADD \ + "diff --git a/binary.bin b/binary.bin\n" \ + "new file mode 100644\n" \ + "index 0000000000000000000000000000000000000000..7c94f9e60bf366033d98e0d551ae37d30faef74a\n" \ + "GIT binary patch\n" \ + "literal 209\n" \ + "zc${60u?oUK5JXSQe8qG&;(u6KCC_\n" \ + "zAhe=XX7rNzh<3&##YcwqNHmEKsP<&&m~%Zf;eX@Khr$?aExDmfqyyt+#l^I)3+LMg\n" \ + "kxnAIj9Pfn_|Gh`fP7tlm6j#y{FJYg_IifRlR^R@A08f862mk;8\n" \ + "\n" \ + "literal 0\n" \ + "Hc$@C_\n" \ + "zAhe=XX7rNzh<3&##YcwqNHmEKsP<&&m~%Zf;eX@Khr$?aExDmfqyyt+#l^I)3+LMg\n" \ + "kxnAIj9Pfn_|Gh`fP7tlm6j#y{FJYg_IifRlR^R@A08f862mk;8\n\n" + +/* contains an old side that does not match the expected source */ +#define PATCH_BINARY_NOT_REVERSIBLE \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index 27184d9883b12c4c9c54b4a31137603586169f51..7c94f9e60bf366033d98e0d551ae37d30faef74a 100644\n" \ + "GIT binary patch\n" \ + "literal 5\n" \ + "Mc${NkU}WL~000&M4gdfE\n" \ + "\n" \ + "delta 48\n" \ + "mc$~Y)c#%<%fq{_;hPgsAGK(h)CJASj=y9P)1m{m|^9BI99|yz$\n\n" + +#define PATCH_BINARY_NOT_PRINTED \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index 27184d9..7c94f9e 100644\n" \ + "Binary files a/binary.bin and b/binary.bin differ\n" + +#define PATCH_ADD_BINARY_NOT_PRINTED \ + "diff --git a/test.bin b/test.bin\n" \ + "new file mode 100644\n" \ + "index 0000000..9e0f96a\n" \ + "Binary files /dev/null and b/test.bin differ\n" + +#define PATCH_ORIGINAL_NEW_FILE_WITH_SPACE \ + "diff --git a/sp ace.txt b/sp ace.txt\n" \ + "new file mode 100644\n" \ + "index 000000000..789819226\n" \ + "--- /dev/null\n" \ + "+++ b/sp ace.txt\n" \ + "@@ -0,0 +1 @@\n" \ + "+a\n" + +#define PATCH_CRLF \ + "diff --git a/test-file b/test-file\r\n" \ + "new file mode 100644\r\n" \ + "index 0000000..af431f2 100644\r\n" \ + "--- /dev/null\r\n" \ + "+++ b/test-file\r\n" \ + "@@ -0,0 +1 @@\r\n" \ + "+a contents\r\n" + +#define PATCH_NO_EXTENDED_HEADERS \ + "diff --git a/file b/file\n" \ + "--- a/file\n" \ + "+++ b/file\n" \ + "@@ -1,3 +1,3 @@\n" \ + " a\n" \ + "-b\n" \ + "+bb\n" \ + " c\n" + +#define PATCH_BINARY_FILE_WITH_MISSING_PATHS \ + "diff --git \n" \ + "--- \n" \ + "+++ \n" \ + "Binary files " + +#define PATCH_BINARY_FILE_WITH_WHITESPACE_PATHS \ + "diff --git a/file b/file\n" \ + "--- \n" \ + "+++ \n" \ + "Binary files " + +#define PATCH_BINARY_FILE_WITH_QUOTED_EMPTY_PATHS \ + "diff --git a/file b/file\n" \ + "--- \"\"\n" \ + "+++ \"\"\n" \ + "Binary files " + +#define PATCH_BINARY_FILE_PATH_WITH_SPACES \ + "diff --git a b c d e f\n" \ + "--- a b c\n" \ + "+++ d e f\n" \ + "Binary files a b c and d e f differ" + +#define PATCH_BINARY_FILE_PATH_WITHOUT_BODY_PATHS \ + "diff --git a b c d e f\n" \ + "--- \n" \ + "+++ \n" \ + "Binary files a b c and d e f differ" + +#define PATCH_BINARY_FILE_WITH_TRUNCATED_DELTA \ + "diff --git a/file b/file\n" \ + "index 1420..b71f\n" \ + "GIT binary patch\n" \ + "delta 7\n" \ + "d" + +#define PATCH_MULTIPLE_OLD_PATHS \ + "diff --git \n" \ + "--- \n" \ + "+++ \n" \ + "index 0000..7DDb\n" \ + "--- \n" + +#define PATCH_INTMAX_NEW_LINES \ + "diff --git a/file b/file\n" \ + "--- a/file\n" \ + "+++ b/file\n" \ + "@@ -0 +2147483647 @@\n" \ + "\n" \ + " " diff --git a/tests/libgit2/patch/print.c b/tests/libgit2/patch/print.c new file mode 100644 index 000000000..33cf27ddb --- /dev/null +++ b/tests/libgit2/patch/print.c @@ -0,0 +1,186 @@ +#include "clar_libgit2.h" +#include "patch.h" +#include "patch_parse.h" + +#include "patch_common.h" + + +/* sanity check the round-trip of patch parsing: ensure that we can parse + * and then print a variety of patch files. + */ + +static void patch_print_from_patchfile(const char *data, size_t len) +{ + git_patch *patch; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_patch_from_buffer(&patch, data, len, NULL)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + + cl_assert_equal_s(data, buf.ptr); + + git_patch_free(patch); + git_buf_dispose(&buf); +} + +void test_patch_print__change_middle(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_MIDDLE, + strlen(PATCH_ORIGINAL_TO_CHANGE_MIDDLE)); +} + +void test_patch_print__change_middle_nocontext(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, + strlen(PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT)); +} + +void test_patch_print__change_firstline(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, + strlen(PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE)); +} + +void test_patch_print__change_lastline(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_LASTLINE, + strlen(PATCH_ORIGINAL_TO_CHANGE_LASTLINE)); +} + +void test_patch_print__prepend(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_PREPEND, + strlen(PATCH_ORIGINAL_TO_PREPEND)); +} + +void test_patch_print__prepend_nocontext(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, + strlen(PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT)); +} + +void test_patch_print__append(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_APPEND, + strlen(PATCH_ORIGINAL_TO_APPEND)); +} + +void test_patch_print__append_nocontext(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, + strlen(PATCH_ORIGINAL_TO_APPEND_NOCONTEXT)); +} + +void test_patch_print__prepend_and_append(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, + strlen(PATCH_ORIGINAL_TO_PREPEND_AND_APPEND)); +} + +void test_patch_print__to_empty_file(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_EMPTY_FILE, + strlen(PATCH_ORIGINAL_TO_EMPTY_FILE)); +} + +void test_patch_print__from_empty_file(void) +{ + patch_print_from_patchfile(PATCH_EMPTY_FILE_TO_ORIGINAL, + strlen(PATCH_EMPTY_FILE_TO_ORIGINAL)); +} + +void test_patch_print__add(void) +{ + patch_print_from_patchfile(PATCH_ADD_ORIGINAL, + strlen(PATCH_ADD_ORIGINAL)); +} + +void test_patch_print__delete(void) +{ + patch_print_from_patchfile(PATCH_DELETE_ORIGINAL, + strlen(PATCH_DELETE_ORIGINAL)); +} + +void test_patch_print__rename_exact(void) +{ + patch_print_from_patchfile(PATCH_RENAME_EXACT, + strlen(PATCH_RENAME_EXACT)); +} + +void test_patch_print__rename_exact_with_mode(void) +{ + patch_print_from_patchfile(PATCH_RENAME_EXACT_WITH_MODE, + strlen(PATCH_RENAME_EXACT_WITH_MODE)); +} + +void test_patch_print__rename_similar(void) +{ + patch_print_from_patchfile(PATCH_RENAME_SIMILAR, + strlen(PATCH_RENAME_SIMILAR)); +} + +void test_patch_print__rename_exact_quotedname(void) +{ + patch_print_from_patchfile(PATCH_RENAME_EXACT_QUOTEDNAME, + strlen(PATCH_RENAME_EXACT_QUOTEDNAME)); +} + +void test_patch_print__rename_similar_quotedname(void) +{ + patch_print_from_patchfile(PATCH_RENAME_SIMILAR_QUOTEDNAME, + strlen(PATCH_RENAME_SIMILAR_QUOTEDNAME)); +} + +void test_patch_print__modechange_unchanged(void) +{ + patch_print_from_patchfile(PATCH_MODECHANGE_UNCHANGED, + strlen(PATCH_MODECHANGE_UNCHANGED)); +} + +void test_patch_print__modechange_modified(void) +{ + patch_print_from_patchfile(PATCH_MODECHANGE_MODIFIED, + strlen(PATCH_MODECHANGE_MODIFIED)); +} + +void test_patch_print__binary_literal(void) +{ + patch_print_from_patchfile(PATCH_BINARY_LITERAL, + strlen(PATCH_BINARY_LITERAL)); +} + +void test_patch_print__binary_delta(void) +{ + patch_print_from_patchfile(PATCH_BINARY_DELTA, + strlen(PATCH_BINARY_DELTA)); +} + +void test_patch_print__binary_add(void) +{ + patch_print_from_patchfile(PATCH_BINARY_ADD, + strlen(PATCH_BINARY_ADD)); +} + +void test_patch_print__binary_delete(void) +{ + patch_print_from_patchfile(PATCH_BINARY_DELETE, + strlen(PATCH_BINARY_DELETE)); +} + +void test_patch_print__not_reversible(void) +{ + patch_print_from_patchfile(PATCH_BINARY_NOT_REVERSIBLE, + strlen(PATCH_BINARY_NOT_REVERSIBLE)); +} + +void test_patch_print__binary_not_shown(void) +{ + patch_print_from_patchfile(PATCH_BINARY_NOT_PRINTED, + strlen(PATCH_BINARY_NOT_PRINTED)); +} + +void test_patch_print__binary_add_not_shown(void) +{ + patch_print_from_patchfile(PATCH_ADD_BINARY_NOT_PRINTED, + strlen(PATCH_ADD_BINARY_NOT_PRINTED)); +} diff --git a/tests/libgit2/path/core.c b/tests/libgit2/path/core.c new file mode 100644 index 000000000..db5359af8 --- /dev/null +++ b/tests/libgit2/path/core.c @@ -0,0 +1,405 @@ +#include "clar_libgit2.h" +#include "fs_path.h" +#include "path.h" + +void test_path_core__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void test_make_relative( + const char *expected_path, + const char *path, + const char *parent, + int expected_status) +{ + git_str buf = GIT_STR_INIT; + git_str_puts(&buf, path); + cl_assert_equal_i(expected_status, git_fs_path_make_relative(&buf, parent)); + cl_assert_equal_s(expected_path, buf.ptr); + git_str_dispose(&buf); +} + +void test_path_core__make_relative(void) +{ + test_make_relative("foo.c", "/path/to/foo.c", "/path/to", 0); + test_make_relative("bar/foo.c", "/path/to/bar/foo.c", "/path/to", 0); + test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0); + + test_make_relative("", "/path/to", "/path/to", 0); + test_make_relative("", "/path/to", "/path/to/", 0); + + test_make_relative("../", "/path/to", "/path/to/foo", 0); + + test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar", 0); + test_make_relative("../bar/foo.c", "/path/to/bar/foo.c", "/path/to/baz", 0); + + test_make_relative("../../foo.c", "/path/to/foo.c", "/path/to/foo/bar", 0); + test_make_relative("../../foo/bar.c", "/path/to/foo/bar.c", "/path/to/bar/foo", 0); + + test_make_relative("../../foo.c", "/foo.c", "/bar/foo", 0); + + test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0); + test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar/", 0); + + test_make_relative("foo.c", "d:/path/to/foo.c", "d:/path/to", 0); + + test_make_relative("../foo", "/foo", "/bar", 0); + test_make_relative("path/to/foo.c", "/path/to/foo.c", "/", 0); + test_make_relative("../foo", "path/to/foo", "path/to/bar", 0); + + test_make_relative("/path/to/foo.c", "/path/to/foo.c", "d:/path/to", GIT_ENOTFOUND); + test_make_relative("d:/path/to/foo.c", "d:/path/to/foo.c", "/path/to", GIT_ENOTFOUND); + + test_make_relative("/path/to/foo.c", "/path/to/foo.c", "not-a-rooted-path", GIT_ENOTFOUND); + test_make_relative("not-a-rooted-path", "not-a-rooted-path", "/path/to", GIT_ENOTFOUND); + + test_make_relative("/path", "/path", "pathtofoo", GIT_ENOTFOUND); + test_make_relative("path", "path", "pathtofoo", GIT_ENOTFOUND); +} + +void test_path_core__isvalid_standard(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/file.txt", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/.file", 0)); +} + +/* Ensure that `is_valid_str` only reads str->size bytes */ +void test_path_core__isvalid_standard_str(void) +{ + git_str str = GIT_STR_INIT_CONST("foo/bar//zap", 0); + unsigned int flags = GIT_FS_PATH_REJECT_EMPTY_COMPONENT; + + str.size = 0; + cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); + + str.size = 3; + cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags)); + + str.size = 4; + cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); + + str.size = 5; + cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags)); + + str.size = 7; + cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags)); + + str.size = 8; + cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); + + str.size = strlen(str.ptr); + cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); +} + +void test_path_core__isvalid_empty_dir_component(void) +{ + unsigned int flags = GIT_FS_PATH_REJECT_EMPTY_COMPONENT; + + /* empty component */ + cl_assert_equal_b(true, git_fs_path_is_valid("foo//bar", 0)); + + /* leading slash */ + cl_assert_equal_b(true, git_fs_path_is_valid("/", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("/foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("/foo/bar", 0)); + + /* trailing slash */ + cl_assert_equal_b(true, git_fs_path_is_valid("foo/", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/", 0)); + + + /* empty component */ + cl_assert_equal_b(false, git_fs_path_is_valid("foo//bar", flags)); + + /* leading slash */ + cl_assert_equal_b(false, git_fs_path_is_valid("/", flags)); + cl_assert_equal_b(false, git_fs_path_is_valid("/foo", flags)); + cl_assert_equal_b(false, git_fs_path_is_valid("/foo/bar", flags)); + + /* trailing slash */ + cl_assert_equal_b(false, git_fs_path_is_valid("foo/", flags)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar/", flags)); +} + +void test_path_core__isvalid_dot_and_dotdot(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid(".", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("./foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/.", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("./foo", 0)); + + cl_assert_equal_b(true, git_fs_path_is_valid("..", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("../foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/..", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("../foo", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid(".", GIT_FS_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_fs_path_is_valid("./foo", GIT_FS_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/.", GIT_FS_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_fs_path_is_valid("./foo", GIT_FS_PATH_REJECT_TRAVERSAL)); + + cl_assert_equal_b(false, git_fs_path_is_valid("..", GIT_FS_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_fs_path_is_valid("../foo", GIT_FS_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/..", GIT_FS_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_fs_path_is_valid("../foo", GIT_FS_PATH_REJECT_TRAVERSAL)); +} + +void test_path_core__isvalid_backslash(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("foo\\file.txt", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar\\file.txt", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar\\", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("foo\\file.txt", GIT_FS_PATH_REJECT_BACKSLASH)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar\\file.txt", GIT_FS_PATH_REJECT_BACKSLASH)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar\\", GIT_FS_PATH_REJECT_BACKSLASH)); +} + +void test_path_core__isvalid_trailing_dot(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("foo.", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo...", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar.", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo./bar", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("foo.", GIT_FS_PATH_REJECT_TRAILING_DOT)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo...", GIT_FS_PATH_REJECT_TRAILING_DOT)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar.", GIT_FS_PATH_REJECT_TRAILING_DOT)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo./bar", GIT_FS_PATH_REJECT_TRAILING_DOT)); +} + +void test_path_core__isvalid_trailing_space(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("foo ", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo ", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar ", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid(" ", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo /bar", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("foo ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); + cl_assert_equal_b(false, git_fs_path_is_valid(" ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo /bar", GIT_FS_PATH_REJECT_TRAILING_SPACE)); +} + +void test_path_core__isvalid_trailing_colon(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("foo:", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar:", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid(":", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo:/bar", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("foo:", GIT_FS_PATH_REJECT_TRAILING_COLON)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar:", GIT_FS_PATH_REJECT_TRAILING_COLON)); + cl_assert_equal_b(false, git_fs_path_is_valid(":", GIT_FS_PATH_REJECT_TRAILING_COLON)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo:/bar", GIT_FS_PATH_REJECT_TRAILING_COLON)); +} + +void test_path_core__isvalid_dos_paths(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("aux", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux.", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux:", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux.asdf", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux.asdf\\zippy", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux:asdf\\foobar", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("con", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("prn", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("nul", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("aux", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("aux.", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("aux:", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("aux.asdf", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("aux.asdf\\zippy", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("aux:asdf\\foobar", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("con", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("prn", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("nul", GIT_FS_PATH_REJECT_DOS_PATHS)); + + cl_assert_equal_b(true, git_fs_path_is_valid("aux1", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux1", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("auxn", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux\\foo", GIT_FS_PATH_REJECT_DOS_PATHS)); +} + +void test_path_core__isvalid_dos_paths_withnum(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("com1", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1.", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1:", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1.asdf", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1.asdf\\zippy", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1:asdf\\foobar", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1\\foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("lpt1", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("com1", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("com1.", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("com1:", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("com1.asdf", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("com1.asdf\\zippy", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("com1:asdf\\foobar", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("com1/foo", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("lpt1", GIT_FS_PATH_REJECT_DOS_PATHS)); + + cl_assert_equal_b(true, git_fs_path_is_valid("com0", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com0", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("com10", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com10", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("comn", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1\\foo", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("lpt0", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("lpt10", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("lptn", GIT_FS_PATH_REJECT_DOS_PATHS)); +} + +void test_path_core__isvalid_nt_chars(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("asdf\001foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdf\037bar", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdffoo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdf:foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdf\"bar", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdf|foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdf?bar", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdf*bar", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("asdf\001foo", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdf\037bar", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdffoo", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdf:foo", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdf\"bar", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdf|foo", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdf?bar", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdf*bar", GIT_FS_PATH_REJECT_NT_CHARS)); +} + +void test_path_core__validate_workdir(void) +{ + cl_must_pass(git_path_validate_length(NULL, "/foo/bar")); + cl_must_pass(git_path_validate_length(NULL, "C:\\Foo\\Bar")); + cl_must_pass(git_path_validate_length(NULL, "\\\\?\\C:\\Foo\\Bar")); + cl_must_pass(git_path_validate_length(NULL, "\\\\?\\C:\\Foo\\Bar")); + cl_must_pass(git_path_validate_length(NULL, "\\\\?\\UNC\\server\\C$\\folder")); + +#ifdef GIT_WIN32 + /* + * In the absence of a repo configuration, 259 character paths + * succeed. >= 260 character paths fail. + */ + cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\ok.txt")); + cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\260.txt")); + cl_must_fail(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\longer_than_260.txt")); + + /* count characters, not bytes */ + cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\260.txt")); + cl_must_fail(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\long.txt")); +#else + cl_must_pass(git_path_validate_length(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/ok.txt")); + cl_must_pass(git_path_validate_length(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/260.txt")); + cl_must_pass(git_path_validate_length(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); + cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\260.txt")); + cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\long.txt")); +#endif +} + +void test_path_core__validate_workdir_with_core_longpath(void) +{ +#ifdef GIT_WIN32 + git_repository *repo; + git_config *config; + + repo = cl_git_sandbox_init("empty_bare.git"); + + cl_git_pass(git_repository_open(&repo, "empty_bare.git")); + cl_git_pass(git_repository_config(&config, repo)); + + /* fail by default */ + cl_must_fail(git_path_validate_length(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); + + /* set core.longpaths explicitly on */ + cl_git_pass(git_config_set_bool(config, "core.longpaths", 1)); + cl_must_pass(git_path_validate_length(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); + + /* set core.longpaths explicitly off */ + cl_git_pass(git_config_set_bool(config, "core.longpaths", 0)); + cl_must_fail(git_path_validate_length(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); + + git_config_free(config); + git_repository_free(repo); +#endif +} + +static void test_join_unrooted( + const char *expected_result, + ssize_t expected_rootlen, + const char *path, + const char *base) +{ + git_str result = GIT_STR_INIT; + ssize_t root_at; + + cl_git_pass(git_fs_path_join_unrooted(&result, path, base, &root_at)); + cl_assert_equal_s(expected_result, result.ptr); + cl_assert_equal_i(expected_rootlen, root_at); + + git_str_dispose(&result); +} + +void test_path_core__join_unrooted(void) +{ + git_str out = GIT_STR_INIT; + + test_join_unrooted("foo", 0, "foo", NULL); + test_join_unrooted("foo/bar", 0, "foo/bar", NULL); + + /* Relative paths have base prepended */ + test_join_unrooted("/foo/bar", 4, "bar", "/foo"); + test_join_unrooted("/foo/bar/foobar", 4, "bar/foobar", "/foo"); + test_join_unrooted("c:/foo/bar/foobar", 6, "bar/foobar", "c:/foo"); + test_join_unrooted("c:/foo/bar/foobar", 10, "foobar", "c:/foo/bar"); + + /* Absolute paths are not prepended with base */ + test_join_unrooted("/foo", 0, "/foo", "/asdf"); + test_join_unrooted("/foo/bar", 0, "/foo/bar", "/asdf"); + + /* Drive letter is given as root length on Windows */ + test_join_unrooted("c:/foo", 2, "c:/foo", "c:/asdf"); + test_join_unrooted("c:/foo/bar", 2, "c:/foo/bar", "c:/asdf"); + +#ifdef GIT_WIN32 + /* Paths starting with '\\' are absolute */ + test_join_unrooted("\\bar", 0, "\\bar", "c:/foo/"); + test_join_unrooted("\\\\network\\bar", 9, "\\\\network\\bar", "c:/foo/"); +#else + /* Paths starting with '\\' are not absolute on non-Windows systems */ + test_join_unrooted("/foo/\\bar", 4, "\\bar", "/foo"); + test_join_unrooted("c:/foo/\\bar", 7, "\\bar", "c:/foo/"); +#endif + + /* Base is returned when it's provided and is the prefix */ + test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo"); + test_join_unrooted("c:/foo/bar/foobar", 10, "c:/foo/bar/foobar", "c:/foo/bar"); + + /* Trailing slash in the base is ignored */ + test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo/"); + + git_str_dispose(&out); +} + +void test_path_core__join_unrooted_respects_funny_windows_roots(void) +{ + test_join_unrooted("💩:/foo/bar/foobar", 9, "bar/foobar", "💩:/foo"); + test_join_unrooted("💩:/foo/bar/foobar", 13, "foobar", "💩:/foo/bar"); + test_join_unrooted("💩:/foo", 5, "💩:/foo", "💩:/asdf"); + test_join_unrooted("💩:/foo/bar", 5, "💩:/foo/bar", "💩:/asdf"); + test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo"); + test_join_unrooted("💩:/foo/bar/foobar", 13, "💩:/foo/bar/foobar", "💩:/foo/bar"); + test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo/"); +} diff --git a/tests/libgit2/path/dotgit.c b/tests/libgit2/path/dotgit.c new file mode 100644 index 000000000..855145f42 --- /dev/null +++ b/tests/libgit2/path/dotgit.c @@ -0,0 +1,206 @@ +#include "clar_libgit2.h" + +#include "path.h" + +static char *gitmodules_altnames[] = { + ".gitmodules", + + /* + * Equivalent to the ".git\u200cmodules" string from git but hard-coded + * as a UTF-8 sequence + */ + ".git\xe2\x80\x8cmodules", + + ".Gitmodules", + ".gitmoduleS", + + ".gitmodules ", + ".gitmodules.", + ".gitmodules ", + ".gitmodules. ", + ".gitmodules .", + ".gitmodules..", + ".gitmodules ", + ".gitmodules. ", + ".gitmodules . ", + ".gitmodules .", + + ".Gitmodules ", + ".Gitmodules.", + ".Gitmodules ", + ".Gitmodules. ", + ".Gitmodules .", + ".Gitmodules..", + ".Gitmodules ", + ".Gitmodules. ", + ".Gitmodules . ", + ".Gitmodules .", + + "GITMOD~1", + "gitmod~1", + "GITMOD~2", + "gitmod~3", + "GITMOD~4", + + "GITMOD~1 ", + "gitmod~2.", + "GITMOD~3 ", + "gitmod~4. ", + "GITMOD~1 .", + "gitmod~2 ", + "GITMOD~3. ", + "gitmod~4 . ", + + "GI7EBA~1", + "gi7eba~9", + + "GI7EB~10", + "GI7EB~11", + "GI7EB~99", + "GI7EB~10", + "GI7E~100", + "GI7E~101", + "GI7E~999", + "~1000000", + "~9999999", +}; + +static char *gitmodules_not_altnames[] = { + ".gitmodules x", + ".gitmodules .x", + + " .gitmodules", + + "..gitmodules", + + "gitmodules", + + ".gitmodule", + + ".gitmodules x ", + ".gitmodules .x", + + "GI7EBA~", + "GI7EBA~0", + "GI7EBA~~1", + "GI7EBA~X", + "Gx7EBA~1", + "GI7EBX~1", + + "GI7EB~1", + "GI7EB~01", + "GI7EB~1", +}; + +void test_path_dotgit__dotgit_modules(void) +{ + size_t i; + + cl_assert_equal_i(1, git_path_is_gitfile(".gitmodules", strlen(".gitmodules"), GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_GENERIC)); + cl_assert_equal_i(1, git_path_is_gitfile(".git\xe2\x80\x8cmodules", strlen(".git\xe2\x80\x8cmodules"), GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_GENERIC)); + + for (i = 0; i < ARRAY_SIZE(gitmodules_altnames); i++) { + const char *name = gitmodules_altnames[i]; + if (!git_path_is_gitfile(name, strlen(name), GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_GENERIC)) + cl_fail(name); + } + + for (i = 0; i < ARRAY_SIZE(gitmodules_not_altnames); i++) { + const char *name = gitmodules_not_altnames[i]; + if (git_path_is_gitfile(name, strlen(name), GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_GENERIC)) + cl_fail(name); + } +} + +void test_path_dotgit__dotgit_modules_symlink(void) +{ + cl_assert_equal_b(true, git_path_is_valid(NULL, ".gitmodules", 0, GIT_PATH_REJECT_DOT_GIT_HFS|GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, ".gitmodules", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, ".gitmodules", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, ".gitmodules . .::$DATA", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_NTFS)); +} + +void test_path_dotgit__git_fs_path_is_file(void) +{ + cl_git_fail(git_path_is_gitfile("blob", 4, -1, GIT_PATH_FS_HFS)); + cl_git_pass(git_path_is_gitfile("blob", 4, GIT_PATH_GITFILE_GITIGNORE, GIT_PATH_FS_HFS)); + cl_git_pass(git_path_is_gitfile("blob", 4, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_HFS)); + cl_git_pass(git_path_is_gitfile("blob", 4, GIT_PATH_GITFILE_GITATTRIBUTES, GIT_PATH_FS_HFS)); + cl_git_fail(git_path_is_gitfile("blob", 4, 3, GIT_PATH_FS_HFS)); +} + +void test_path_dotgit__isvalid_dot_git(void) +{ + cl_assert_equal_b(true, git_path_is_valid(NULL, ".git", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".git/foo", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/.git", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/.git/bar", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/.GIT/bar", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/bar/.Git", 0, 0)); + + cl_assert_equal_b(false, git_path_is_valid(NULL, ".git", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_is_valid(NULL, ".git/foo", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_is_valid(NULL, "foo/.git", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_is_valid(NULL, "foo/.git/bar", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_is_valid(NULL, "foo/.GIT/bar", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_is_valid(NULL, "foo/bar/.Git", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); + + cl_assert_equal_b(true, git_path_is_valid(NULL, "!git", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/!git", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, "!git/bar", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".tig", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/.tig", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".tig/bar", 0, 0)); +} + +void test_path_dotgit__isvalid_dotgit_ntfs(void) +{ + cl_assert_equal_b(true, git_path_is_valid(NULL, ".git", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".git ", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".git.", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".git.. .", 0, 0)); + + cl_assert_equal_b(true, git_path_is_valid(NULL, "git~1", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, "git~1 ", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, "git~1.", 0, 0)); + cl_assert_equal_b(true, git_path_is_valid(NULL, "git~1.. .", 0, 0)); + + cl_assert_equal_b(false, git_path_is_valid(NULL, ".git", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, ".git ", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, ".git.", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, ".git.. .", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); + + cl_assert_equal_b(false, git_path_is_valid(NULL, "git~1", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, "git~1 ", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, "git~1.", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, "git~1.. .", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); +} + +void test_path_dotgit__isvalid_dotgit_with_hfs_ignorables(void) +{ + cl_assert_equal_b(false, git_path_is_valid(NULL, ".git", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, ".git\xe2\x80\x8c", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, ".gi\xe2\x80\x8dT", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, ".g\xe2\x80\x8eIt", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, ".\xe2\x80\x8fgIt", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, "\xe2\x80\xaa.gIt", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + + cl_assert_equal_b(false, git_path_is_valid(NULL, "\xe2\x80\xab.\xe2\x80\xacG\xe2\x80\xadI\xe2\x80\xaet", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, "\xe2\x81\xab.\xe2\x80\xaaG\xe2\x81\xabI\xe2\x80\xact", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(false, git_path_is_valid(NULL, "\xe2\x81\xad.\xe2\x80\xaeG\xef\xbb\xbfIT", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + + cl_assert_equal_b(true, git_path_is_valid(NULL, ".", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".g", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".gi", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_is_valid(NULL, " .git", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_is_valid(NULL, "..git\xe2\x80\x8c", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".gi\xe2\x80\x8dT.", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".g\xe2\x80It", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".\xe2gIt", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_is_valid(NULL, "\xe2\x80\xaa.gi", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".gi\x80\x8dT", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".gi\x8dT", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".g\xe2i\x80T\x8e", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".git\xe2\x80\xbf", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); + cl_assert_equal_b(true, git_path_is_valid(NULL, ".git\xe2\xab\x81", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); +} diff --git a/tests/libgit2/path/win32.c b/tests/libgit2/path/win32.c new file mode 100644 index 000000000..1aaf6867a --- /dev/null +++ b/tests/libgit2/path/win32.c @@ -0,0 +1,282 @@ + +#include "clar_libgit2.h" + +#ifdef GIT_WIN32 +#include "win32/path_w32.h" +#endif + +#ifdef GIT_WIN32 +static void test_utf8_to_utf16(const char *utf8_in, const wchar_t *utf16_expected) +{ + git_win32_path path_utf16; + int path_utf16len; + + cl_assert((path_utf16len = git_win32_path_from_utf8(path_utf16, utf8_in)) >= 0); + cl_assert_equal_wcs(utf16_expected, path_utf16); + cl_assert_equal_i(wcslen(utf16_expected), path_utf16len); +} + +static void test_utf8_to_utf16_relative(const char* utf8_in, const wchar_t* utf16_expected) +{ + git_win32_path path_utf16; + int path_utf16len; + + cl_assert((path_utf16len = git_win32_path_relative_from_utf8(path_utf16, utf8_in)) >= 0); + cl_assert_equal_wcs(utf16_expected, path_utf16); + cl_assert_equal_i(wcslen(utf16_expected), path_utf16len); +} +#endif + +void test_path_win32__utf8_to_utf16(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("C:\\", L"\\\\?\\C:\\"); + test_utf8_to_utf16("c:\\", L"\\\\?\\c:\\"); + test_utf8_to_utf16("C:/", L"\\\\?\\C:\\"); + test_utf8_to_utf16("c:/", L"\\\\?\\c:\\"); +#endif +} + +void test_path_win32__removes_trailing_slash(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("C:\\Foo\\", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:/Foo/", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:/Foo///", L"\\\\?\\C:\\Foo"); +#endif +} + +void test_path_win32__squashes_multiple_slashes(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("C:\\\\Foo\\Bar\\\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); + test_utf8_to_utf16("C://Foo/Bar///Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); +#endif +} + +void test_path_win32__unc(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path"); + test_utf8_to_utf16("//server/git/style/unc/path", L"\\\\?\\UNC\\server\\git\\style\\unc\\path"); +#endif +} + +void test_path_win32__honors_max_path(void) +{ +#ifdef GIT_WIN32 + git_win32_path path_utf16; + + test_utf8_to_utf16("C:\\This path is 261 characters which is fine for our path handling functions which cope with paths longer than MAX_PATH\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghijk", + L"\\\\?\\C:\\This path is 261 characters which is fine for our path handling functions which cope with paths longer than MAX_PATH\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghijk"); + + cl_check_fail(git_win32_path_from_utf8(path_utf16, "C:\\This path is 4097 chars and exceeds our maximum path length on Windows which is limited to 4096 characters\\alas\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij01")); + +#endif +} + +void test_path_win32__dot_and_dotdot(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("C:\\Foo\\..\\Foobar", L"\\\\?\\C:\\Foobar"); + test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar", L"\\\\?\\C:\\Foo\\Foobar"); + test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar\\..", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:\\Foobar\\..", L"\\\\?\\C:\\"); + test_utf8_to_utf16("C:/Foo/Bar/../Foobar", L"\\\\?\\C:\\Foo\\Foobar"); + test_utf8_to_utf16("C:/Foo/Bar/../Foobar/../Asdf/", L"\\\\?\\C:\\Foo\\Asdf"); + test_utf8_to_utf16("C:/Foo/Bar/../Foobar/..", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:/Foo/..", L"\\\\?\\C:\\"); + + test_utf8_to_utf16("C:\\Foo\\Bar\\.\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); + test_utf8_to_utf16("C:\\.\\Foo\\.\\Bar\\.\\Foobar\\.\\", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); + test_utf8_to_utf16("C:/Foo/Bar/./Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); + test_utf8_to_utf16("C:/Foo/../Bar/./Foobar/../", L"\\\\?\\C:\\Bar"); + + test_utf8_to_utf16("C:\\Foo\\..\\..\\Bar", L"\\\\?\\C:\\Bar"); +#endif +} + +void test_path_win32__absolute_from_no_drive_letter(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("\\Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar"); + test_utf8_to_utf16("/Foo/Bar", L"\\\\?\\C:\\Foo\\Bar"); +#endif +} + +void test_path_win32__absolute_from_relative(void) +{ +#ifdef GIT_WIN32 + char cwd_backup[MAX_PATH]; + + cl_must_pass(p_getcwd(cwd_backup, MAX_PATH)); + cl_must_pass(p_chdir("C:/")); + + test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("..\\..\\Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("Foo\\..", L"\\\\?\\C:\\"); + test_utf8_to_utf16("Foo\\..\\..", L"\\\\?\\C:\\"); + test_utf8_to_utf16("", L"\\\\?\\C:\\"); + + cl_must_pass(p_chdir("C:/Windows")); + + test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Windows\\Foo"); + test_utf8_to_utf16("Foo\\Bar", L"\\\\?\\C:\\Windows\\Foo\\Bar"); + test_utf8_to_utf16("..\\Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("Foo\\..\\Bar", L"\\\\?\\C:\\Windows\\Bar"); + test_utf8_to_utf16("", L"\\\\?\\C:\\Windows"); + + cl_must_pass(p_chdir(cwd_backup)); +#endif +} + +void test_path_win32__keeps_relative(void) +{ +#ifdef GIT_WIN32 + /* Relative paths stay relative */ + test_utf8_to_utf16_relative("Foo", L"Foo"); + test_utf8_to_utf16_relative("..\\..\\Foo", L"..\\..\\Foo"); + test_utf8_to_utf16_relative("Foo\\..", L"Foo\\.."); + test_utf8_to_utf16_relative("Foo\\..\\..", L"Foo\\..\\.."); + test_utf8_to_utf16_relative("Foo\\Bar", L"Foo\\Bar"); + test_utf8_to_utf16_relative("Foo\\..\\Bar", L"Foo\\..\\Bar"); + test_utf8_to_utf16_relative("../../Foo", L"..\\..\\Foo"); + test_utf8_to_utf16_relative("Foo/..", L"Foo\\.."); + test_utf8_to_utf16_relative("Foo/../..", L"Foo\\..\\.."); + test_utf8_to_utf16_relative("Foo/Bar", L"Foo\\Bar"); + test_utf8_to_utf16_relative("Foo/../Bar", L"Foo\\..\\Bar"); + test_utf8_to_utf16_relative("Foo/../Bar/", L"Foo\\..\\Bar\\"); + test_utf8_to_utf16_relative("", L""); + + /* Absolute paths are canonicalized */ + test_utf8_to_utf16_relative("\\Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16_relative("/Foo/Bar/", L"\\\\?\\C:\\Foo\\Bar"); + test_utf8_to_utf16_relative("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path"); +#endif +} + +#ifdef GIT_WIN32 +static void test_canonicalize(const wchar_t *in, const wchar_t *expected) +{ + git_win32_path canonical; + + cl_assert(wcslen(in) < MAX_PATH); + wcscpy(canonical, in); + + cl_must_pass(git_win32_path_canonicalize(canonical)); + cl_assert_equal_wcs(expected, canonical); +} +#endif + +static void test_remove_namespace(const wchar_t *in, const wchar_t *expected) +{ +#ifdef GIT_WIN32 + git_win32_path canonical; + + cl_assert(wcslen(in) < MAX_PATH); + wcscpy(canonical, in); + + git_win32_path_remove_namespace(canonical, wcslen(in)); + cl_assert_equal_wcs(expected, canonical); +#else + GIT_UNUSED(in); + GIT_UNUSED(expected); +#endif +} + +void test_path_win32__remove_namespace(void) +{ + test_remove_namespace(L"\\\\?\\C:\\Temp\\Foo", L"C:\\Temp\\Foo"); + test_remove_namespace(L"\\\\?\\C:\\", L"C:\\"); + test_remove_namespace(L"\\\\?\\", L""); + + test_remove_namespace(L"\\??\\C:\\Temp\\Foo", L"C:\\Temp\\Foo"); + test_remove_namespace(L"\\??\\C:\\", L"C:\\"); + test_remove_namespace(L"\\??\\", L""); + + test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\\\?\\UNC\\server\\C$", L"\\\\server\\C$"); + test_remove_namespace(L"\\\\?\\UNC\\server\\", L"\\\\server"); + test_remove_namespace(L"\\\\?\\UNC\\server", L"\\\\server"); + + test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\??\\UNC\\server\\C$", L"\\\\server\\C$"); + test_remove_namespace(L"\\??\\UNC\\server\\", L"\\\\server"); + test_remove_namespace(L"\\??\\UNC\\server", L"\\\\server"); + + test_remove_namespace(L"\\\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\\\server\\C$", L"\\\\server\\C$"); + test_remove_namespace(L"\\\\server\\", L"\\\\server"); + test_remove_namespace(L"\\\\server", L"\\\\server"); + + test_remove_namespace(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar"); + test_remove_namespace(L"C:\\", L"C:\\"); + test_remove_namespace(L"", L""); + +} + +void test_path_win32__canonicalize(void) +{ +#ifdef GIT_WIN32 + test_canonicalize(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar"); + test_canonicalize(L"C:\\Foo\\", L"C:\\Foo"); + test_canonicalize(L"C:\\Foo\\\\", L"C:\\Foo"); + test_canonicalize(L"C:\\Foo\\..\\Bar", L"C:\\Bar"); + test_canonicalize(L"C:\\Foo\\..\\..\\Bar", L"C:\\Bar"); + test_canonicalize(L"C:\\Foo\\..\\..\\..\\..\\", L"C:\\"); + test_canonicalize(L"C:/Foo/Bar", L"C:\\Foo\\Bar"); + test_canonicalize(L"C:/", L"C:\\"); + + test_canonicalize(L"\\\\?\\C:\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar"); + test_canonicalize(L"\\\\?\\C:\\Foo\\Bar\\", L"\\\\?\\C:\\Foo\\Bar"); + test_canonicalize(L"\\\\?\\C:\\\\Foo\\.\\Bar\\\\..\\", L"\\\\?\\C:\\Foo"); + test_canonicalize(L"\\\\?\\C:\\\\", L"\\\\?\\C:\\"); + test_canonicalize(L"//?/C:/", L"\\\\?\\C:\\"); + test_canonicalize(L"//?/C:/../../Foo/", L"\\\\?\\C:\\Foo"); + test_canonicalize(L"//?/C:/Foo/../../", L"\\\\?\\C:\\"); + + test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\?\\UNC\\server\\C$\\folder"); + test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder"); + test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder"); + test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\..\\..\\..\\..\\share\\", L"\\\\?\\UNC\\server\\share"); + + test_canonicalize(L"\\\\server\\share", L"\\\\server\\share"); + test_canonicalize(L"\\\\server\\share\\", L"\\\\server\\share"); + test_canonicalize(L"\\\\server\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar"); + test_canonicalize(L"\\\\server\\\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar"); + test_canonicalize(L"\\\\server\\share\\..\\foo", L"\\\\server\\foo"); + test_canonicalize(L"\\\\server\\..\\..\\share\\.\\foo", L"\\\\server\\share\\foo"); +#endif +} + +void test_path_win32__8dot3_name(void) +{ +#ifdef GIT_WIN32 + char *shortname; + + if (!cl_sandbox_supports_8dot3()) + clar__skip(); + + /* Some guaranteed short names */ + cl_assert_equal_s("PROGRA~1", (shortname = git_win32_path_8dot3_name("C:\\Program Files"))); + git__free(shortname); + + cl_assert_equal_s("WINDOWS", (shortname = git_win32_path_8dot3_name("C:\\WINDOWS"))); + git__free(shortname); + + /* Create some predictable short names */ + cl_must_pass(p_mkdir(".foo", 0777)); + cl_assert_equal_s("FOO~1", (shortname = git_win32_path_8dot3_name(".foo"))); + git__free(shortname); + + cl_git_write2file("bar~1", "foobar\n", 7, O_RDWR|O_CREAT, 0666); + cl_must_pass(p_mkdir(".bar", 0777)); + cl_assert_equal_s("BAR~2", (shortname = git_win32_path_8dot3_name(".bar"))); + git__free(shortname); +#endif +} diff --git a/tests/libgit2/perf/helper__perf__do_merge.c b/tests/libgit2/perf/helper__perf__do_merge.c new file mode 100644 index 000000000..c77b46a1f --- /dev/null +++ b/tests/libgit2/perf/helper__perf__do_merge.c @@ -0,0 +1,75 @@ +#include "clar_libgit2.h" +#include "helper__perf__do_merge.h" +#include "helper__perf__timer.h" + +static git_repository * g_repo; + +void perf__do_merge(const char *fixture, + const char *test_name, + const char *id_a, + const char *id_b) +{ + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + git_oid oid_a; + git_oid oid_b; + git_reference *ref_branch_a = NULL; + git_reference *ref_branch_b = NULL; + git_commit *commit_a = NULL; + git_commit *commit_b = NULL; + git_annotated_commit *annotated_commits[1] = { NULL }; + perf_timer t_total = PERF_TIMER_INIT; + perf_timer t_clone = PERF_TIMER_INIT; + perf_timer t_checkout = PERF_TIMER_INIT; + perf_timer t_merge = PERF_TIMER_INIT; + + perf__timer__start(&t_total); + + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + clone_opts.checkout_opts = checkout_opts; + + perf__timer__start(&t_clone); + cl_git_pass(git_clone(&g_repo, fixture, test_name, &clone_opts)); + perf__timer__stop(&t_clone); + + git_oid_fromstr(&oid_a, id_a); + cl_git_pass(git_commit_lookup(&commit_a, g_repo, &oid_a)); + cl_git_pass(git_branch_create(&ref_branch_a, g_repo, + "A", commit_a, + 0)); + + perf__timer__start(&t_checkout); + cl_git_pass(git_checkout_tree(g_repo, (git_object*)commit_a, &checkout_opts)); + perf__timer__stop(&t_checkout); + + cl_git_pass(git_repository_set_head(g_repo, git_reference_name(ref_branch_a))); + + git_oid_fromstr(&oid_b, id_b); + cl_git_pass(git_commit_lookup(&commit_b, g_repo, &oid_b)); + cl_git_pass(git_branch_create(&ref_branch_b, g_repo, + "B", commit_b, + 0)); + + cl_git_pass(git_annotated_commit_lookup(&annotated_commits[0], g_repo, &oid_b)); + + perf__timer__start(&t_merge); + cl_git_pass(git_merge(g_repo, + (const git_annotated_commit **)annotated_commits, 1, + &merge_opts, &checkout_opts)); + perf__timer__stop(&t_merge); + + git_reference_free(ref_branch_a); + git_reference_free(ref_branch_b); + git_commit_free(commit_a); + git_commit_free(commit_b); + git_annotated_commit_free(annotated_commits[0]); + git_repository_free(g_repo); + + perf__timer__stop(&t_total); + + perf__timer__report(&t_clone, "%s: clone", test_name); + perf__timer__report(&t_checkout, "%s: checkout", test_name); + perf__timer__report(&t_merge, "%s: merge", test_name); + perf__timer__report(&t_total, "%s: total", test_name); +} diff --git a/tests/libgit2/perf/helper__perf__do_merge.h b/tests/libgit2/perf/helper__perf__do_merge.h new file mode 100644 index 000000000..4a4723da5 --- /dev/null +++ b/tests/libgit2/perf/helper__perf__do_merge.h @@ -0,0 +1,4 @@ +void perf__do_merge(const char *fixture, + const char *test_name, + const char *id_a, + const char *id_b); diff --git a/tests/libgit2/perf/helper__perf__timer.c b/tests/libgit2/perf/helper__perf__timer.c new file mode 100644 index 000000000..8a7ed09e8 --- /dev/null +++ b/tests/libgit2/perf/helper__perf__timer.c @@ -0,0 +1,73 @@ +#include "clar_libgit2.h" +#include "helper__perf__timer.h" + +#if defined(GIT_WIN32) + +void perf__timer__start(perf_timer *t) +{ + QueryPerformanceCounter(&t->time_started); +} + +void perf__timer__stop(perf_timer *t) +{ + LARGE_INTEGER time_now; + QueryPerformanceCounter(&time_now); + + t->sum.QuadPart += (time_now.QuadPart - t->time_started.QuadPart); +} + +void perf__timer__report(perf_timer *t, const char *fmt, ...) +{ + va_list arglist; + LARGE_INTEGER freq; + double fraction; + + QueryPerformanceFrequency(&freq); + + fraction = ((double)t->sum.QuadPart) / ((double)freq.QuadPart); + + printf("%10.3f: ", fraction); + + va_start(arglist, fmt); + vprintf(fmt, arglist); + va_end(arglist); + + printf("\n"); +} + +#else + +#include + +static uint32_t now_in_ms(void) +{ + struct timeval now; + gettimeofday(&now, NULL); + return (uint32_t)((now.tv_sec * 1000) + (now.tv_usec / 1000)); +} + +void perf__timer__start(perf_timer *t) +{ + t->time_started = now_in_ms(); +} + +void perf__timer__stop(perf_timer *t) +{ + uint32_t now = now_in_ms(); + t->sum += (now - t->time_started); +} + +void perf__timer__report(perf_timer *t, const char *fmt, ...) +{ + va_list arglist; + + printf("%10.3f: ", ((double)t->sum) / 1000); + + va_start(arglist, fmt); + vprintf(fmt, arglist); + va_end(arglist); + + printf("\n"); +} + +#endif diff --git a/tests/libgit2/perf/helper__perf__timer.h b/tests/libgit2/perf/helper__perf__timer.h new file mode 100644 index 000000000..5aff4b136 --- /dev/null +++ b/tests/libgit2/perf/helper__perf__timer.h @@ -0,0 +1,27 @@ +#if defined(GIT_WIN32) + +struct perf__timer +{ + LARGE_INTEGER sum; + LARGE_INTEGER time_started; +}; + +#define PERF_TIMER_INIT {0} + +#else + +struct perf__timer +{ + uint32_t sum; + uint32_t time_started; +}; + +#define PERF_TIMER_INIT {0} + +#endif + +typedef struct perf__timer perf_timer; + +void perf__timer__start(perf_timer *t); +void perf__timer__stop(perf_timer *t); +void perf__timer__report(perf_timer *t, const char *fmt, ...); diff --git a/tests/libgit2/perf/merge.c b/tests/libgit2/perf/merge.c new file mode 100644 index 000000000..721902d63 --- /dev/null +++ b/tests/libgit2/perf/merge.c @@ -0,0 +1,31 @@ +#include "clar_libgit2.h" +#include "helper__perf__do_merge.h" + +/* This test requires a large repo with many files. + * It doesn't care about the contents, just the size. + * + * For now, we use the LibGit2 repo containing the + * source tree because it is already here. + * + * `find . | wc -l` reports 5128. + * + */ +#define SRC_REPO (cl_fixture("../..")) + +/* We need 2 arbitrary commits within that repo + * that have a large number of changed files. + * Again, we don't care about the actual contents, + * just the size. + * + * For now, we use these public branches: + * maint/v0.21 d853fb9f24e0fe63b3dce9fbc04fd9cfe17a030b Always checkout with case sensitive iterator + * maint/v0.22 1ce9ea3ba9b4fa666602d52a5281d41a482cc58b checkout tests: cleanup realpath impl on Win32 + * + */ +#define ID_BRANCH_A "d853fb9f24e0fe63b3dce9fbc04fd9cfe17a030b" +#define ID_BRANCH_B "1ce9ea3ba9b4fa666602d52a5281d41a482cc58b" + +void test_perf_merge__m1(void) +{ + perf__do_merge(SRC_REPO, "m1", ID_BRANCH_A, ID_BRANCH_B); +} diff --git a/tests/libgit2/precompiled.c b/tests/libgit2/precompiled.c new file mode 100644 index 000000000..5f656a45d --- /dev/null +++ b/tests/libgit2/precompiled.c @@ -0,0 +1 @@ +#include "precompiled.h" diff --git a/tests/libgit2/precompiled.h b/tests/libgit2/precompiled.h new file mode 100644 index 000000000..ea53a60e9 --- /dev/null +++ b/tests/libgit2/precompiled.h @@ -0,0 +1,4 @@ +#include "common.h" +#include "git2.h" +#include "clar.h" +#include "clar_libgit2.h" diff --git a/tests/libgit2/rebase/abort.c b/tests/libgit2/rebase/abort.c new file mode 100644 index 000000000..a83c529ce --- /dev/null +++ b/tests/libgit2/rebase/abort.c @@ -0,0 +1,250 @@ +#include "clar_libgit2.h" +#include "git2/rebase.h" +#include "merge.h" +#include "posix.h" +#include "annotated_commit.h" + +#include + +static git_repository *repo; + +/* Fixture setup and teardown */ +void test_rebase_abort__initialize(void) +{ + repo = cl_git_sandbox_init("rebase"); +} + +void test_rebase_abort__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void ensure_aborted( + git_annotated_commit *branch, + git_annotated_commit *onto) +{ + git_reference *head_ref, *branch_ref = NULL; + git_status_list *statuslist; + git_reflog *reflog; + const git_reflog_entry *reflog_entry; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + /* Make sure the refs are updated appropriately */ + cl_git_pass(git_reference_lookup(&head_ref, repo, "HEAD")); + + if (branch->ref_name == NULL) + cl_assert_equal_oid(git_annotated_commit_id(branch), git_reference_target(head_ref)); + else { + cl_assert_equal_s("refs/heads/beef", git_reference_symbolic_target(head_ref)); + cl_git_pass(git_reference_lookup(&branch_ref, repo, git_reference_symbolic_target(head_ref))); + cl_assert_equal_oid(git_annotated_commit_id(branch), git_reference_target(branch_ref)); + } + + git_status_list_new(&statuslist, repo, NULL); + cl_assert_equal_i(0, git_status_list_entrycount(statuslist)); + git_status_list_free(statuslist); + + /* Make sure the reflogs are updated appropriately */ + cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); + + cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_oid(git_annotated_commit_id(onto), git_reflog_entry_id_old(reflog_entry)); + cl_assert_equal_oid(git_annotated_commit_id(branch), git_reflog_entry_id_new(reflog_entry)); + cl_assert_equal_s("rebase: aborting", git_reflog_entry_message(reflog_entry)); + + git_reflog_free(reflog); + git_reference_free(head_ref); + git_reference_free(branch_ref); +} + +static void test_abort( + git_annotated_commit *branch, git_annotated_commit *onto) +{ + git_rebase *rebase; + + cl_git_pass(git_rebase_open(&rebase, repo, NULL)); + cl_git_pass(git_rebase_abort(rebase)); + + ensure_aborted(branch, onto); + + git_rebase_free(rebase); +} + +void test_rebase_abort__merge(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *onto_ref; + git_annotated_commit *branch_head, *onto_head; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + test_abort(branch_head, onto_head); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + + git_reference_free(branch_ref); + git_reference_free(onto_ref); + git_rebase_free(rebase); +} + +void test_rebase_abort__merge_immediately_after_init(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *onto_ref; + git_annotated_commit *branch_head, *onto_head; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + cl_git_pass(git_rebase_abort(rebase)); + ensure_aborted(branch_head, onto_head); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + + git_reference_free(branch_ref); + git_reference_free(onto_ref); + git_rebase_free(rebase); +} + +void test_rebase_abort__merge_by_id(void) +{ + git_rebase *rebase; + git_oid branch_id, onto_id; + git_annotated_commit *branch_head, *onto_head; + + cl_git_pass(git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64")); + cl_git_pass(git_oid_fromstr(&onto_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); + + cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); + cl_git_pass(git_annotated_commit_lookup(&onto_head, repo, &onto_id)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + test_abort(branch_head, onto_head); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + + git_rebase_free(rebase); +} + +void test_rebase_abort__merge_by_revspec(void) +{ + git_rebase *rebase; + git_annotated_commit *branch_head, *onto_head; + + cl_git_pass(git_annotated_commit_from_revspec(&branch_head, repo, "b146bd7")); + cl_git_pass(git_annotated_commit_from_revspec(&onto_head, repo, "efad0b1")); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + test_abort(branch_head, onto_head); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + + git_rebase_free(rebase); +} + +void test_rebase_abort__merge_by_id_immediately_after_init(void) +{ + git_rebase *rebase; + git_oid branch_id, onto_id; + git_annotated_commit *branch_head, *onto_head; + + cl_git_pass(git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64")); + cl_git_pass(git_oid_fromstr(&onto_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); + + cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); + cl_git_pass(git_annotated_commit_lookup(&onto_head, repo, &onto_id)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + cl_git_pass(git_rebase_abort(rebase)); + ensure_aborted(branch_head, onto_head); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + + git_rebase_free(rebase); +} + +void test_rebase_abort__detached_head(void) +{ + git_rebase *rebase; + git_oid branch_id, onto_id; + git_signature *signature; + git_annotated_commit *branch_head, *onto_head; + + git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64"); + git_oid_fromstr(&onto_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + + cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); + cl_git_pass(git_annotated_commit_lookup(&onto_head, repo, &onto_id)); + + cl_git_pass(git_signature_new(&signature, "Rebaser", "rebaser@example.com", 1404157834, -400)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + test_abort(branch_head, onto_head); + + git_signature_free(signature); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + + git_rebase_free(rebase); +} + +void test_rebase_abort__old_style_head_file(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *onto_ref; + git_signature *signature; + git_annotated_commit *branch_head, *onto_head; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); + + cl_git_pass(git_signature_new(&signature, "Rebaser", "rebaser@example.com", 1404157834, -400)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + p_rename("rebase-merge/.git/rebase-merge/orig-head", + "rebase-merge/.git/rebase-merge/head"); + + test_abort(branch_head, onto_head); + + git_signature_free(signature); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + + git_reference_free(branch_ref); + git_reference_free(onto_ref); + git_rebase_free(rebase); +} diff --git a/tests/libgit2/rebase/inmemory.c b/tests/libgit2/rebase/inmemory.c new file mode 100644 index 000000000..040a81b1b --- /dev/null +++ b/tests/libgit2/rebase/inmemory.c @@ -0,0 +1,210 @@ +#include "clar_libgit2.h" +#include "git2/rebase.h" +#include "posix.h" + +#include + +static git_repository *repo; +static git_signature *signature; + +/* Fixture setup and teardown */ +void test_rebase_inmemory__initialize(void) +{ + repo = cl_git_sandbox_init("rebase"); + + cl_git_pass(git_signature_new(&signature, + "Rebaser", "rebaser@rebaser.rb", 1405694510, 0)); +} + +void test_rebase_inmemory__cleanup(void) +{ + git_signature_free(signature); + cl_git_sandbox_cleanup(); +} + +void test_rebase_inmemory__not_in_rebase_state(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; + + opts.inmemory = true; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + git_rebase_free(rebase); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + + git_reference_free(branch_ref); + git_reference_free(upstream_ref); +} + +void test_rebase_inmemory__can_resolve_conflicts(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_status_list *status_list; + git_oid pick_id, commit_id, expected_commit_id; + git_index *rebase_index, *repo_index; + git_index_entry resolution = {{0}}; + git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; + + opts.inmemory = true; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + + git_oid_fromstr(&pick_id, "33f915f9e4dbd9f4b24430e48731a59b45b15500"); + + cl_assert_equal_i(GIT_REBASE_OPERATION_PICK, rebase_operation->type); + cl_assert_equal_oid(&pick_id, &rebase_operation->id); + + /* ensure that we did not do anything stupid to the workdir or repo index */ + cl_git_pass(git_repository_index(&repo_index, repo)); + cl_assert(!git_index_has_conflicts(repo_index)); + + cl_git_pass(git_status_list_new(&status_list, repo, NULL)); + cl_assert_equal_i(0, git_status_list_entrycount(status_list)); + + /* but that the index returned from rebase does have conflicts */ + cl_git_pass(git_rebase_inmemory_index(&rebase_index, rebase)); + cl_assert(git_index_has_conflicts(rebase_index)); + + cl_git_fail_with(GIT_EUNMERGED, git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + /* ensure that we can work with the in-memory index to resolve the conflict */ + resolution.path = "asparagus.txt"; + resolution.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&resolution.id, "414dfc71ead79c07acd4ea47fecf91f289afc4b9"); + cl_git_pass(git_index_conflict_remove(rebase_index, "asparagus.txt")); + cl_git_pass(git_index_add(rebase_index, &resolution)); + + /* and finally create a commit for the resolved rebase operation */ + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + cl_git_pass(git_oid_fromstr(&expected_commit_id, "db7af47222181e548810da2ab5fec0e9357c5637")); + cl_assert_equal_oid(&commit_id, &expected_commit_id); + + git_status_list_free(status_list); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_index_free(repo_index); + git_index_free(rebase_index); + git_rebase_free(rebase); +} + +void test_rebase_inmemory__no_common_ancestor(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, expected_final_id; + git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; + + opts.inmemory = true; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/barley")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_finish(rebase, signature)); + + git_oid_fromstr(&expected_final_id, "71e7ee8d4fe7d8bf0d107355197e0a953dfdb7f3"); + cl_assert_equal_oid(&expected_final_id, &commit_id); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_inmemory__with_directories(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, tree_id; + git_commit *commit; + git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; + + opts.inmemory = true; + + git_oid_fromstr(&tree_id, "a4d6d9c3d57308fd8e320cf2525bae8f1adafa57"); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/deep_gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + cl_assert_equal_oid(&tree_id, git_commit_tree_id(commit)); + + git_commit_free(commit); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} diff --git a/tests/libgit2/rebase/iterator.c b/tests/libgit2/rebase/iterator.c new file mode 100644 index 000000000..a120f28ac --- /dev/null +++ b/tests/libgit2/rebase/iterator.c @@ -0,0 +1,140 @@ +#include "clar_libgit2.h" +#include "git2/rebase.h" +#include "posix.h" + +#include + +static git_repository *repo; +static git_index *_index; +static git_signature *signature; + +/* Fixture setup and teardown */ +void test_rebase_iterator__initialize(void) +{ + repo = cl_git_sandbox_init("rebase"); + cl_git_pass(git_repository_index(&_index, repo)); + cl_git_pass(git_signature_new(&signature, "Rebaser", + "rebaser@rebaser.rb", 1405694510, 0)); +} + +void test_rebase_iterator__cleanup(void) +{ + git_signature_free(signature); + git_index_free(_index); + cl_git_sandbox_cleanup(); +} + +static void test_operations(git_rebase *rebase, size_t expected_current) +{ + size_t i, expected_count = 5; + git_oid expected_oid[5]; + git_rebase_operation *operation; + + git_oid_fromstr(&expected_oid[0], "da9c51a23d02d931a486f45ad18cda05cf5d2b94"); + git_oid_fromstr(&expected_oid[1], "8d1f13f93c4995760ac07d129246ac1ff64c0be9"); + git_oid_fromstr(&expected_oid[2], "3069cc907e6294623e5917ef6de663928c1febfb"); + git_oid_fromstr(&expected_oid[3], "588e5d2f04d49707fe4aab865e1deacaf7ef6787"); + git_oid_fromstr(&expected_oid[4], "b146bd7608eac53d9bf9e1a6963543588b555c64"); + + cl_assert_equal_i(expected_count, git_rebase_operation_entrycount(rebase)); + cl_assert_equal_i(expected_current, git_rebase_operation_current(rebase)); + + for (i = 0; i < expected_count; i++) { + operation = git_rebase_operation_byindex(rebase, i); + cl_assert_equal_i(GIT_REBASE_OPERATION_PICK, operation->type); + cl_assert_equal_oid(&expected_oid[i], &operation->id); + cl_assert_equal_p(NULL, operation->exec); + } +} + +static void test_iterator(bool inmemory) +{ + git_rebase *rebase; + git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, expected_id; + int error; + + opts.inmemory = inmemory; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); + test_operations(rebase, GIT_REBASE_NO_OPERATION); + + if (!inmemory) { + git_rebase_free(rebase); + cl_git_pass(git_rebase_open(&rebase, repo, NULL)); + } + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + test_operations(rebase, 0); + + git_oid_fromstr(&expected_id, "776e4c48922799f903f03f5f6e51da8b01e4cce0"); + cl_assert_equal_oid(&expected_id, &commit_id); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + test_operations(rebase, 1); + + git_oid_fromstr(&expected_id, "ba1f9b4fd5cf8151f7818be2111cc0869f1eb95a"); + cl_assert_equal_oid(&expected_id, &commit_id); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + test_operations(rebase, 2); + + git_oid_fromstr(&expected_id, "948b12fe18b84f756223a61bece4c307787cd5d4"); + cl_assert_equal_oid(&expected_id, &commit_id); + + if (!inmemory) { + git_rebase_free(rebase); + cl_git_pass(git_rebase_open(&rebase, repo, NULL)); + } + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + test_operations(rebase, 3); + + git_oid_fromstr(&expected_id, "d9d5d59d72c9968687f9462578d79878cd80e781"); + cl_assert_equal_oid(&expected_id, &commit_id); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + test_operations(rebase, 4); + + git_oid_fromstr(&expected_id, "9cf383c0a125d89e742c5dec58ed277dd07588b3"); + cl_assert_equal_oid(&expected_id, &commit_id); + + cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); + cl_assert_equal_i(GIT_ITEROVER, error); + test_operations(rebase, 4); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_iterator__iterates(void) +{ + test_iterator(false); +} + +void test_rebase_iterator__iterates_inmemory(void) +{ + test_iterator(true); +} diff --git a/tests/libgit2/rebase/merge.c b/tests/libgit2/rebase/merge.c new file mode 100644 index 000000000..5f730f750 --- /dev/null +++ b/tests/libgit2/rebase/merge.c @@ -0,0 +1,855 @@ +#include "clar_libgit2.h" +#include "git2/checkout.h" +#include "git2/rebase.h" +#include "posix.h" +#include "signature.h" + +#include + +static git_repository *repo; +static git_signature *signature; + +static void set_core_autocrlf_to(git_repository *repo, bool value) +{ + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", value)); + + git_config_free(cfg); +} + +/* Fixture setup and teardown */ +void test_rebase_merge__initialize(void) +{ + repo = cl_git_sandbox_init("rebase"); + cl_git_pass(git_signature_new(&signature, + "Rebaser", "rebaser@rebaser.rb", 1405694510, 0)); + + set_core_autocrlf_to(repo, false); +} + +void test_rebase_merge__cleanup(void) +{ + git_signature_free(signature); + cl_git_sandbox_cleanup(); +} + +void test_rebase_merge__next(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_status_list *status_list; + const git_status_entry *status_entry; + git_oid pick_id, file1_id; + git_oid master_id, beef_id; + + git_oid_fromstr(&master_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + git_oid_fromstr(&beef_id, "b146bd7608eac53d9bf9e1a6963543588b555c64"); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_assert_equal_s("refs/heads/beef", git_rebase_orig_head_name(rebase)); + cl_assert_equal_oid(&beef_id, git_rebase_orig_head_id(rebase)); + + cl_assert_equal_s("master", git_rebase_onto_name(rebase)); + cl_assert_equal_oid(&master_id, git_rebase_onto_id(rebase)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + + git_oid_fromstr(&pick_id, "da9c51a23d02d931a486f45ad18cda05cf5d2b94"); + + cl_assert_equal_i(GIT_REBASE_OPERATION_PICK, rebase_operation->type); + cl_assert_equal_oid(&pick_id, &rebase_operation->id); + cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/current"); + cl_assert_equal_file("1\n", 2, "rebase/.git/rebase-merge/msgnum"); + + cl_git_pass(git_status_list_new(&status_list, repo, NULL)); + cl_assert_equal_i(1, git_status_list_entrycount(status_list)); + cl_assert(status_entry = git_status_byindex(status_list, 0)); + + cl_assert_equal_s("beef.txt", status_entry->head_to_index->new_file.path); + + git_oid_fromstr(&file1_id, "8d95ea62e621f1d38d230d9e7d206e41096d76af"); + cl_assert_equal_oid(&file1_id, &status_entry->head_to_index->new_file.id); + + git_status_list_free(status_list); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__next_with_conflicts(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_status_list *status_list; + const git_status_entry *status_entry; + git_oid pick_id, commit_id; + + const char *expected_merge = +"ASPARAGUS SOUP.\n" +"\n" +"<<<<<<< master\n" +"TAKE FOUR LARGE BUNCHES of asparagus, scrape it nicely, cut off one inch\n" +"OF THE TOPS, and lay them in water, chop the stalks and put them on the\n" +"FIRE WITH A PIECE OF BACON, a large onion cut up, and pepper and salt;\n" +"ADD TWO QUARTS OF WATER, boil them till the stalks are quite soft, then\n" +"PULP THEM THROUGH A SIEVE, and strain the water to it, which must be put\n" +"=======\n" +"Take four large bunches of asparagus, scrape it nicely, CUT OFF ONE INCH\n" +"of the tops, and lay them in water, chop the stalks and PUT THEM ON THE\n" +"fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" +"add two quarts of water, boil them till the stalks are quite soft, then\n" +"pulp them through a sieve, and strain the water to it, which must be put\n" +">>>>>>> Conflicting modification 1 to asparagus\n" +"back in the pot; put into it a chicken cut up, with the tops of\n" +"asparagus which had been laid by, boil it until these last articles are\n" +"sufficiently done, thicken with flour, butter and milk, and serve it up.\n"; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + + git_oid_fromstr(&pick_id, "33f915f9e4dbd9f4b24430e48731a59b45b15500"); + + cl_assert_equal_i(GIT_REBASE_OPERATION_PICK, rebase_operation->type); + cl_assert_equal_oid(&pick_id, &rebase_operation->id); + cl_assert_equal_file("33f915f9e4dbd9f4b24430e48731a59b45b15500\n", 41, "rebase/.git/rebase-merge/current"); + cl_assert_equal_file("1\n", 2, "rebase/.git/rebase-merge/msgnum"); + + cl_git_pass(git_status_list_new(&status_list, repo, NULL)); + cl_assert_equal_i(1, git_status_list_entrycount(status_list)); + cl_assert(status_entry = git_status_byindex(status_list, 0)); + + cl_assert_equal_s("asparagus.txt", status_entry->head_to_index->new_file.path); + + cl_assert_equal_file(expected_merge, strlen(expected_merge), "rebase/asparagus.txt"); + + cl_git_fail_with(GIT_EUNMERGED, git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + git_status_list_free(status_list); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__next_stops_with_iterover(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id; + int error; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); + cl_assert_equal_i(GIT_ITEROVER, error); + + cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/msgnum"); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__commit(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, tree_id, parent_id; + git_signature *author; + git_commit *commit; + git_reflog *reflog; + const git_reflog_entry *reflog_entry; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + + git_oid_fromstr(&parent_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_assert_equal_i(1, git_commit_parentcount(commit)); + cl_assert_equal_oid(&parent_id, git_commit_parent_id(commit, 0)); + + git_oid_fromstr(&tree_id, "4461379789c777d2a6c1f2ee0e9d6c86731b9992"); + cl_assert_equal_oid(&tree_id, git_commit_tree_id(commit)); + + cl_assert_equal_s(NULL, git_commit_message_encoding(commit)); + cl_assert_equal_s("Modification 1 to beef\n", git_commit_message(commit)); + + cl_git_pass(git_signature_new(&author, + "Edward Thomson", "ethomson@edwardthomson.com", 1405621769, 0-(4*60))); + cl_assert(git_signature__equal(author, git_commit_author(commit))); + + cl_assert(git_signature__equal(signature, git_commit_committer(commit))); + + /* Make sure the reflogs are updated appropriately */ + cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); + cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_oid(&parent_id, git_reflog_entry_id_old(reflog_entry)); + cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); + cl_assert_equal_s("rebase: Modification 1 to beef", git_reflog_entry_message(reflog_entry)); + + git_reflog_free(reflog); + git_signature_free(author); + git_commit_free(commit); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__commit_with_id(void) +{ + git_rebase *rebase; + git_oid branch_id, upstream_id; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, tree_id, parent_id; + git_signature *author; + git_commit *commit; + git_reflog *reflog; + const git_reflog_entry *reflog_entry; + + cl_git_pass(git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64")); + cl_git_pass(git_oid_fromstr(&upstream_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); + + cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); + cl_git_pass(git_annotated_commit_lookup(&upstream_head, repo, &upstream_id)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + + git_oid_fromstr(&parent_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_assert_equal_i(1, git_commit_parentcount(commit)); + cl_assert_equal_oid(&parent_id, git_commit_parent_id(commit, 0)); + + git_oid_fromstr(&tree_id, "4461379789c777d2a6c1f2ee0e9d6c86731b9992"); + cl_assert_equal_oid(&tree_id, git_commit_tree_id(commit)); + + cl_assert_equal_s(NULL, git_commit_message_encoding(commit)); + cl_assert_equal_s("Modification 1 to beef\n", git_commit_message(commit)); + + cl_git_pass(git_signature_new(&author, + "Edward Thomson", "ethomson@edwardthomson.com", 1405621769, 0-(4*60))); + cl_assert(git_signature__equal(author, git_commit_author(commit))); + + cl_assert(git_signature__equal(signature, git_commit_committer(commit))); + + /* Make sure the reflogs are updated appropriately */ + cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); + cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_oid(&parent_id, git_reflog_entry_id_old(reflog_entry)); + cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); + cl_assert_equal_s("rebase: Modification 1 to beef", git_reflog_entry_message(reflog_entry)); + + git_reflog_free(reflog); + git_signature_free(author); + git_commit_free(commit); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_rebase_free(rebase); +} + +void test_rebase_merge__blocked_when_dirty(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + /* Allow untracked files */ + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_mkfile("rebase/untracked_file.txt", "This is untracked\n"); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + /* Do not allow unstaged */ + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_mkfile("rebase/veal.txt", "This is an unstaged change\n"); + cl_git_fail_with(GIT_EUNMERGED, git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__commit_updates_rewritten(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_assert_equal_file( + "da9c51a23d02d931a486f45ad18cda05cf5d2b94 776e4c48922799f903f03f5f6e51da8b01e4cce0\n" + "8d1f13f93c4995760ac07d129246ac1ff64c0be9 ba1f9b4fd5cf8151f7818be2111cc0869f1eb95a\n", + 164, "rebase/.git/rebase-merge/rewritten"); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__commit_drops_already_applied(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id; + int error; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/green_pea")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_fail(error = git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_assert_equal_i(GIT_EAPPLIED, error); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_assert_equal_file( + "8d1f13f93c4995760ac07d129246ac1ff64c0be9 2ac4fb7b74c1287f6c792acad759e1ec01e18dae\n", + 82, "rebase/.git/rebase-merge/rewritten"); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__finish(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref, *head_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id; + git_reflog *reflog; + const git_reflog_entry *reflog_entry; + int error; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); + cl_assert_equal_i(GIT_ITEROVER, error); + + cl_git_pass(git_rebase_finish(rebase, signature)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&head_ref, repo, "HEAD")); + cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head_ref)); + cl_assert_equal_s("refs/heads/gravy", git_reference_symbolic_target(head_ref)); + + /* Make sure the reflogs are updated appropriately */ + cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); + cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_oid(&commit_id, git_reflog_entry_id_old(reflog_entry)); + cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); + cl_assert_equal_s("rebase finished: returning to refs/heads/gravy", git_reflog_entry_message(reflog_entry)); + git_reflog_free(reflog); + + cl_git_pass(git_reflog_read(&reflog, repo, "refs/heads/gravy")); + cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_oid(git_annotated_commit_id(branch_head), git_reflog_entry_id_old(reflog_entry)); + cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); + cl_assert_equal_s("rebase finished: refs/heads/gravy onto f87d14a4a236582a0278a916340a793714256864", git_reflog_entry_message(reflog_entry)); + + git_reflog_free(reflog); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(head_ref); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__detached_finish(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref, *head_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id; + git_reflog *reflog; + const git_reflog_entry *reflog_entry; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + int error; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_repository_set_head_detached_from_annotated(repo, branch_head)); + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + git_checkout_head(repo, &opts); + + cl_git_pass(git_rebase_init(&rebase, repo, NULL, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); + cl_assert_equal_i(GIT_ITEROVER, error); + + cl_git_pass(git_rebase_finish(rebase, signature)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&head_ref, repo, "HEAD")); + cl_assert_equal_i(GIT_REFERENCE_DIRECT, git_reference_type(head_ref)); + + /* Make sure the reflogs are updated appropriately */ + cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); + cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_oid(git_annotated_commit_id(upstream_head), git_reflog_entry_id_old(reflog_entry)); + cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); + + git_reflog_free(reflog); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(head_ref); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__finish_with_ids(void) +{ + git_rebase *rebase; + git_reference *head_ref; + git_oid branch_id, upstream_id; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id; + git_reflog *reflog; + const git_reflog_entry *reflog_entry; + int error; + + cl_git_pass(git_oid_fromstr(&branch_id, "d616d97082eb7bb2dc6f180a7cca940993b7a56f")); + cl_git_pass(git_oid_fromstr(&upstream_id, "f87d14a4a236582a0278a916340a793714256864")); + + cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); + cl_git_pass(git_annotated_commit_lookup(&upstream_head, repo, &upstream_id)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); + cl_assert_equal_i(GIT_ITEROVER, error); + + cl_git_pass(git_rebase_finish(rebase, signature)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&head_ref, repo, "HEAD")); + cl_assert_equal_i(GIT_REFERENCE_DIRECT, git_reference_type(head_ref)); + cl_assert_equal_oid(&commit_id, git_reference_target(head_ref)); + + /* reflogs are not updated as if we were operating on proper + * branches. check that the last reflog entry is the rebase. + */ + cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); + cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); + cl_assert_equal_s("rebase: Modification 3 to gravy", git_reflog_entry_message(reflog_entry)); + git_reflog_free(reflog); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(head_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__no_common_ancestor(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, expected_final_id; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/barley")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_finish(rebase, signature)); + + git_oid_fromstr(&expected_final_id, "71e7ee8d4fe7d8bf0d107355197e0a953dfdb7f3"); + cl_assert_equal_oid(&expected_final_id, &commit_id); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +static void test_copy_note( + const git_rebase_options *opts, + bool should_exist) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_commit *branch_commit; + git_rebase_operation *rebase_operation; + git_oid note_id, commit_id; + git_note *note = NULL; + int error; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_reference_peel((git_object **)&branch_commit, + branch_ref, GIT_OBJECT_COMMIT)); + + /* Add a note to a commit */ + cl_git_pass(git_note_create(¬e_id, repo, "refs/notes/test", + git_commit_author(branch_commit), git_commit_committer(branch_commit), + git_commit_id(branch_commit), + "This is a commit note.", 0)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_finish(rebase, signature)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + if (should_exist) { + cl_git_pass(git_note_read(¬e, repo, "refs/notes/test", &commit_id)); + cl_assert_equal_s("This is a commit note.", git_note_message(note)); + } else { + cl_git_fail(error = + git_note_read(¬e, repo, "refs/notes/test", &commit_id)); + cl_assert_equal_i(GIT_ENOTFOUND, error); + } + + git_note_free(note); + git_commit_free(branch_commit); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__copy_notes_off_by_default(void) +{ + test_copy_note(NULL, 0); +} + +void test_rebase_merge__copy_notes_specified_in_options(void) +{ + git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; + opts.rewrite_notes_ref = "refs/notes/test"; + + test_copy_note(&opts, 1); +} + +void test_rebase_merge__copy_notes_specified_in_config(void) +{ + git_config *config; + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_string(config, + "notes.rewriteRef", "refs/notes/test")); + + test_copy_note(NULL, 1); +} + +void test_rebase_merge__copy_notes_disabled_in_config(void) +{ + git_config *config; + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "notes.rewrite.rebase", 0)); + cl_git_pass(git_config_set_string(config, + "notes.rewriteRef", "refs/notes/test")); + + test_copy_note(NULL, 0); +} + +static void rebase_checkout_progress_cb( + const char *path, + size_t completed_steps, + size_t total_steps, + void *payload) +{ + int *called = payload; + + GIT_UNUSED(path); + GIT_UNUSED(completed_steps); + GIT_UNUSED(total_steps); + + *called = 1; +} + +void test_rebase_merge__custom_checkout_options(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_options rebase_options = GIT_REBASE_OPTIONS_INIT; + git_checkout_options checkout_options = GIT_CHECKOUT_OPTIONS_INIT; + git_rebase_operation *rebase_operation; + int called = 0; + + checkout_options.progress_cb = rebase_checkout_progress_cb; + checkout_options.progress_payload = &called; + + memcpy(&rebase_options.checkout_options, &checkout_options, + sizeof(git_checkout_options)); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + called = 0; + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_options)); + cl_assert_equal_i(1, called); + + called = 0; + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_assert_equal_i(1, called); + + called = 0; + cl_git_pass(git_rebase_abort(rebase)); + cl_assert_equal_i(1, called); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__custom_merge_options(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_options rebase_options = GIT_REBASE_OPTIONS_INIT; + git_rebase_operation *rebase_operation; + + rebase_options.merge_options.flags |= + GIT_MERGE_FAIL_ON_CONFLICT | + GIT_MERGE_SKIP_REUC; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_options)); + + cl_git_fail_with(GIT_EMERGECONFLICT, git_rebase_next(&rebase_operation, rebase)); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__with_directories(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, tree_id; + git_commit *commit; + + git_oid_fromstr(&tree_id, "a4d6d9c3d57308fd8e320cf2525bae8f1adafa57"); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/deep_gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + cl_assert_equal_oid(&tree_id, git_commit_tree_id(commit)); + + git_commit_free(commit); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} diff --git a/tests/libgit2/rebase/setup.c b/tests/libgit2/rebase/setup.c new file mode 100644 index 000000000..34d5edbf6 --- /dev/null +++ b/tests/libgit2/rebase/setup.c @@ -0,0 +1,596 @@ +#include "clar_libgit2.h" +#include "git2/rebase.h" +#include "posix.h" + +#include + +static git_repository *repo; +static git_index *_index; +static git_signature *signature; + +/* Fixture setup and teardown */ +void test_rebase_setup__initialize(void) +{ + repo = cl_git_sandbox_init("rebase"); + cl_git_pass(git_repository_index(&_index, repo)); + cl_git_pass(git_signature_now(&signature, "Rebaser", "rebaser@rebaser.rb")); +} + +void test_rebase_setup__cleanup(void) +{ + git_signature_free(signature); + git_index_free(_index); + cl_git_sandbox_cleanup(); +} + +/* git checkout beef ; git rebase --merge master + * git checkout beef ; git rebase --merge master */ +void test_rebase_setup__blocked_when_in_progress(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + git_rebase_free(rebase); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + cl_git_fail(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); +} + +/* git checkout beef ; git rebase --merge master */ +void test_rebase_setup__merge(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_reference *head; + git_commit *head_commit; + git_oid head_id; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("8d1f13f93c4995760ac07d129246ac1ff64c0be9\n", 41, "rebase/.git/rebase-merge/cmt.2"); + cl_assert_equal_file("3069cc907e6294623e5917ef6de663928c1febfb\n", 41, "rebase/.git/rebase-merge/cmt.3"); + cl_assert_equal_file("588e5d2f04d49707fe4aab865e1deacaf7ef6787\n", 41, "rebase/.git/rebase-merge/cmt.4"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/cmt.5"); + cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +/* git checkout beef && git rebase --merge --root --onto master */ +void test_rebase_setup__merge_root(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *onto_ref; + git_annotated_commit *branch_head, *onto_head; + git_reference *head; + git_commit *head_commit; + git_oid head_id; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("8d1f13f93c4995760ac07d129246ac1ff64c0be9\n", 41, "rebase/.git/rebase-merge/cmt.2"); + cl_assert_equal_file("3069cc907e6294623e5917ef6de663928c1febfb\n", 41, "rebase/.git/rebase-merge/cmt.3"); + cl_assert_equal_file("588e5d2f04d49707fe4aab865e1deacaf7ef6787\n", 41, "rebase/.git/rebase-merge/cmt.4"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/cmt.5"); + cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + git_reference_free(branch_ref); + git_reference_free(onto_ref); + git_rebase_free(rebase); +} + +/* git checkout gravy && git rebase --merge --onto master veal */ +void test_rebase_setup__merge_onto_and_upstream(void) +{ + git_rebase *rebase; + git_reference *branch1_ref, *branch2_ref, *onto_ref; + git_annotated_commit *branch1_head, *branch2_head, *onto_head; + git_reference *head; + git_commit *head_commit; + git_oid head_id; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&branch1_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&branch2_ref, repo, "refs/heads/veal")); + cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch1_head, repo, branch1_ref)); + cl_git_pass(git_annotated_commit_from_ref(&branch2_head, repo, branch2_ref)); + cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch1_head, branch2_head, onto_head, NULL)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("1\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(branch1_head); + git_annotated_commit_free(branch2_head); + git_annotated_commit_free(onto_head); + git_reference_free(branch1_ref); + git_reference_free(branch2_ref); + git_reference_free(onto_ref); + git_rebase_free(rebase); +} + +/* git checkout beef && git rebase --merge --onto master gravy veal */ +void test_rebase_setup__merge_onto_upstream_and_branch(void) +{ + git_rebase *rebase; + git_reference *upstream_ref, *branch_ref, *onto_ref; + git_annotated_commit *upstream_head, *branch_head, *onto_head; + git_reference *head; + git_commit *head_commit; + git_oid head_id; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_repository_set_head(repo, "refs/heads/beef")); + cl_git_pass(git_checkout_head(repo, &checkout_opts)); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/veal")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, onto_head, NULL)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + cl_assert_equal_file("3e8989b5a16d5258c935d998ef0e6bb139cc4757\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("4cacc6f6e740a5bc64faa33e04b8ef0733d8a127\n", 41, "rebase/.git/rebase-merge/cmt.2"); + cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/rebase-merge/cmt.3"); + cl_assert_equal_file("3\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(upstream_head); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + git_reference_free(upstream_ref); + git_reference_free(branch_ref); + git_reference_free(onto_ref); + git_rebase_free(rebase); +} + +/* git checkout beef && git rebase --merge --onto `git rev-parse master` + * `git rev-parse veal` `git rev-parse gravy` + */ +void test_rebase_setup__merge_onto_upstream_and_branch_by_id(void) +{ + git_rebase *rebase; + git_oid upstream_id, branch_id, onto_id; + git_annotated_commit *upstream_head, *branch_head, *onto_head; + git_reference *head; + git_commit *head_commit; + git_oid head_id; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_repository_set_head(repo, "refs/heads/beef")); + cl_git_pass(git_checkout_head(repo, &checkout_opts)); + + cl_git_pass(git_oid_fromstr(&upstream_id, "f87d14a4a236582a0278a916340a793714256864")); + cl_git_pass(git_oid_fromstr(&branch_id, "d616d97082eb7bb2dc6f180a7cca940993b7a56f")); + cl_git_pass(git_oid_fromstr(&onto_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); + + cl_git_pass(git_annotated_commit_lookup(&upstream_head, repo, &upstream_id)); + cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); + cl_git_pass(git_annotated_commit_lookup(&onto_head, repo, &onto_id)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, onto_head, NULL)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("1\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(upstream_head); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + git_rebase_free(rebase); +} + +/* Ensure merge commits are dropped in a rebase */ +/* git checkout veal && git rebase --merge master */ +void test_rebase_setup__branch_with_merges(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_reference *head; + git_commit *head_commit; + git_oid head_id; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/veal")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_file("4bed71df7017283cac61bbf726197ad6a5a18b84\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("2aa3ce842094e08ebac152b3d6d5b0fff39f9c6e\n", 41, "rebase/.git/rebase-merge/cmt.2"); + cl_assert_equal_file("3e8989b5a16d5258c935d998ef0e6bb139cc4757\n", 41, "rebase/.git/rebase-merge/cmt.3"); + cl_assert_equal_file("4cacc6f6e740a5bc64faa33e04b8ef0733d8a127\n", 41, "rebase/.git/rebase-merge/cmt.4"); + cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/rebase-merge/cmt.5"); + cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +/* git checkout barley && git rebase --merge master */ +void test_rebase_setup__orphan_branch(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_reference *head; + git_commit *head_commit; + git_oid head_id; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/barley")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("12c084412b952396962eb420716df01022b847cc\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_file("aa4c42aecdfc7cd989bbc3209934ea7cda3f4d88\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("e4f809f826c1a9fc929874bc0e4644dd2f2a1af4\n", 41, "rebase/.git/rebase-merge/cmt.2"); + cl_assert_equal_file("9539b2cc291d6a6b1b266df8474d31fdd344dd79\n", 41, "rebase/.git/rebase-merge/cmt.3"); + cl_assert_equal_file("013cc32d341bab0e6f039f50f153c18986f16c58\n", 41, "rebase/.git/rebase-merge/cmt.4"); + cl_assert_equal_file("12c084412b952396962eb420716df01022b847cc\n", 41, "rebase/.git/rebase-merge/cmt.5"); + cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("12c084412b952396962eb420716df01022b847cc\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +/* git checkout beef && git rebase --merge master */ +void test_rebase_setup__merge_null_branch_uses_HEAD(void) +{ + git_rebase *rebase; + git_reference *upstream_ref; + git_annotated_commit *upstream_head; + git_reference *head; + git_commit *head_commit; + git_oid head_id; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_repository_set_head(repo, "refs/heads/beef")); + cl_git_pass(git_checkout_head(repo, &checkout_opts)); + + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, NULL, upstream_head, NULL, NULL)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("8d1f13f93c4995760ac07d129246ac1ff64c0be9\n", 41, "rebase/.git/rebase-merge/cmt.2"); + cl_assert_equal_file("3069cc907e6294623e5917ef6de663928c1febfb\n", 41, "rebase/.git/rebase-merge/cmt.3"); + cl_assert_equal_file("588e5d2f04d49707fe4aab865e1deacaf7ef6787\n", 41, "rebase/.git/rebase-merge/cmt.4"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/cmt.5"); + cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(upstream_head); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +/* git checkout b146bd7608eac53d9bf9e1a6963543588b555c64 && git rebase --merge master */ +void test_rebase_setup__merge_from_detached(void) +{ + git_rebase *rebase; + git_reference *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_reference *head; + git_commit *head_commit; + git_oid branch_id, head_id; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64")); + + cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("8d1f13f93c4995760ac07d129246ac1ff64c0be9\n", 41, "rebase/.git/rebase-merge/cmt.2"); + cl_assert_equal_file("3069cc907e6294623e5917ef6de663928c1febfb\n", 41, "rebase/.git/rebase-merge/cmt.3"); + cl_assert_equal_file("588e5d2f04d49707fe4aab865e1deacaf7ef6787\n", 41, "rebase/.git/rebase-merge/cmt.4"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/cmt.5"); + cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +/* git checkout beef && git rebase --merge efad0b11c47cb2f0220cbd6f5b0f93bb99064b00 */ +void test_rebase_setup__merge_branch_by_id(void) +{ + git_rebase *rebase; + git_reference *branch_ref; + git_annotated_commit *branch_head, *upstream_head; + git_reference *head; + git_commit *head_commit; + git_oid head_id, upstream_id; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + + cl_git_pass(git_oid_fromstr(&upstream_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_lookup(&upstream_head, repo, &upstream_id)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("8d1f13f93c4995760ac07d129246ac1ff64c0be9\n", 41, "rebase/.git/rebase-merge/cmt.2"); + cl_assert_equal_file("3069cc907e6294623e5917ef6de663928c1febfb\n", 41, "rebase/.git/rebase-merge/cmt.3"); + cl_assert_equal_file("588e5d2f04d49707fe4aab865e1deacaf7ef6787\n", 41, "rebase/.git/rebase-merge/cmt.4"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/cmt.5"); + cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_rebase_free(rebase); +} + +static int rebase_is_blocked(void) +{ + git_rebase *rebase = NULL; + int error; + + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + error = git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); + + return error; +} + +void test_rebase_setup__blocked_for_staged_change(void) +{ + cl_git_rewritefile("rebase/newfile.txt", "Stage an add"); + git_index_add_bypath(_index, "newfile.txt"); + cl_git_fail(rebase_is_blocked()); +} + +void test_rebase_setup__blocked_for_unstaged_change(void) +{ + cl_git_rewritefile("rebase/asparagus.txt", "Unstaged change"); + cl_git_fail(rebase_is_blocked()); +} + +void test_rebase_setup__not_blocked_for_untracked_add(void) +{ + cl_git_rewritefile("rebase/newfile.txt", "Untracked file"); + cl_git_pass(rebase_is_blocked()); +} + diff --git a/tests/libgit2/rebase/sign.c b/tests/libgit2/rebase/sign.c new file mode 100644 index 000000000..4064cf79b --- /dev/null +++ b/tests/libgit2/rebase/sign.c @@ -0,0 +1,491 @@ +#include "clar_libgit2.h" +#include "git2/rebase.h" + +static git_repository *repo; +static git_signature *signature; + +/* Fixture setup and teardown */ +void test_rebase_sign__initialize(void) +{ + repo = cl_git_sandbox_init("rebase"); + cl_git_pass(git_signature_new(&signature, "Rebaser", + "rebaser@rebaser.rb", 1405694510, 0)); +} + +void test_rebase_sign__cleanup(void) +{ + git_signature_free(signature); + cl_git_sandbox_cleanup(); +} + +static int create_cb_passthrough( + git_oid *out, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[], + void *payload) +{ + GIT_UNUSED(out); + GIT_UNUSED(author); + GIT_UNUSED(committer); + GIT_UNUSED(message_encoding); + GIT_UNUSED(message); + GIT_UNUSED(tree); + GIT_UNUSED(parent_count); + GIT_UNUSED(parents); + GIT_UNUSED(payload); + + return GIT_PASSTHROUGH; +} + +/* git checkout gravy ; git rebase --merge veal */ +void test_rebase_sign__passthrough_create_cb(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, expected_id; + git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; + git_commit *commit; + const char *expected_commit_raw_header = "tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ +parent f87d14a4a236582a0278a916340a793714256864\n\ +author Edward Thomson 1405625055 -0400\n\ +committer Rebaser 1405694510 +0000\n"; + + rebase_opts.commit_create_cb = create_cb_passthrough; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + git_oid_fromstr(&expected_id, "129183968a65abd6c52da35bff43325001bfc630"); + cl_assert_equal_oid(&expected_id, &commit_id); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit)); + + cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); + + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_commit_free(commit); + git_rebase_free(rebase); +} + +static int create_cb_signed_gpg( + git_oid *out, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[], + void *payload) +{ + git_buf commit_content = GIT_BUF_INIT; + const char *gpg_signature = "-----BEGIN PGP SIGNATURE-----\n\ +\n\ +iQIzBAEBCgAdFiEEgVlDEfSlmKn0fvGgK++h5T2/ctIFAlwZcrAACgkQK++h5T2/\n\ +ctIPVhAA42RyZhMdKl5Bm0KtQco2scsukIg2y7tjSwhti91zDu3HQgpusjjo0fQx\n\ +ZzB+OrmlvQ9CDcGpZ0THIzXD8GRJoDMPqdrvZVrBWkGcHvw7/YPA8skzsjkauJ8W\n\ +7lzF5LCuHSS6OUmPT/+5hEHPin5PB3zhfszyC+Q7aujnIuPJMrKiMnUa+w1HWifM\n\ +km49OOygQ9S6NQoVuEQede22+c76DlDL7yFghGoo1f0sKCE/9LW6SEnwI/bWv9eo\n\ +nom5vOPrvQeJiYCQk+2DyWo8RdSxINtY+G9bPE4RXm+6ZgcXECPm9TYDIWpL36fC\n\ +jvtGLs98woWFElOziBMp5Tb630GMcSI+q5ivHfJ3WS5NKLYLHBNK4iSFN0/dgAnB\n\ +dj6GcKXKWnIBWn6ZM4o40pcM5KSRUUCLtA0ZmjJH4c4zx3X5fUxd+enwkf3e9VZO\n\ +fNKC/+xfq6NfoPUPK9+UnchHpJaJw7RG5tZS+sWCz2xpQ1y3/o49xImNyM3wnpvB\n\ +cRAZabqIHpZa9/DIUkELOtCzln6niqkjRgg3M/YCCNznwV+0RNgz87VtyTPerdef\n\ +xrqn0+ROMF6ebVqIs6PPtuPkxnAJu7TMKXVB5rFnAewS24e6cIGFzeIA7810py3l\n\ +cttVRsdOoego+fiy08eFE+aJIeYiINRGhqOBTsuqG4jIdpdKxPE=\n\ +=KbsY\n\ +-----END PGP SIGNATURE-----"; + + git_repository *repo = (git_repository *)payload; + int error; + + if ((error = git_commit_create_buffer(&commit_content, + repo, author, committer, message_encoding, message, + tree, parent_count, parents)) < 0) + goto done; + + error = git_commit_create_with_signature(out, repo, + commit_content.ptr, + gpg_signature, + NULL); + +done: + git_buf_dispose(&commit_content); + return error; +} + +/* git checkout gravy ; git rebase --merge veal */ +void test_rebase_sign__create_gpg_signed(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, expected_id; + git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; + git_commit *commit; + const char *expected_commit_raw_header = "tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ +parent f87d14a4a236582a0278a916340a793714256864\n\ +author Edward Thomson 1405625055 -0400\n\ +committer Rebaser 1405694510 +0000\n\ +gpgsig -----BEGIN PGP SIGNATURE-----\n\ + \n\ + iQIzBAEBCgAdFiEEgVlDEfSlmKn0fvGgK++h5T2/ctIFAlwZcrAACgkQK++h5T2/\n\ + ctIPVhAA42RyZhMdKl5Bm0KtQco2scsukIg2y7tjSwhti91zDu3HQgpusjjo0fQx\n\ + ZzB+OrmlvQ9CDcGpZ0THIzXD8GRJoDMPqdrvZVrBWkGcHvw7/YPA8skzsjkauJ8W\n\ + 7lzF5LCuHSS6OUmPT/+5hEHPin5PB3zhfszyC+Q7aujnIuPJMrKiMnUa+w1HWifM\n\ + km49OOygQ9S6NQoVuEQede22+c76DlDL7yFghGoo1f0sKCE/9LW6SEnwI/bWv9eo\n\ + nom5vOPrvQeJiYCQk+2DyWo8RdSxINtY+G9bPE4RXm+6ZgcXECPm9TYDIWpL36fC\n\ + jvtGLs98woWFElOziBMp5Tb630GMcSI+q5ivHfJ3WS5NKLYLHBNK4iSFN0/dgAnB\n\ + dj6GcKXKWnIBWn6ZM4o40pcM5KSRUUCLtA0ZmjJH4c4zx3X5fUxd+enwkf3e9VZO\n\ + fNKC/+xfq6NfoPUPK9+UnchHpJaJw7RG5tZS+sWCz2xpQ1y3/o49xImNyM3wnpvB\n\ + cRAZabqIHpZa9/DIUkELOtCzln6niqkjRgg3M/YCCNznwV+0RNgz87VtyTPerdef\n\ + xrqn0+ROMF6ebVqIs6PPtuPkxnAJu7TMKXVB5rFnAewS24e6cIGFzeIA7810py3l\n\ + cttVRsdOoego+fiy08eFE+aJIeYiINRGhqOBTsuqG4jIdpdKxPE=\n\ + =KbsY\n\ + -----END PGP SIGNATURE-----\n"; + + rebase_opts.commit_create_cb = create_cb_signed_gpg; + rebase_opts.payload = repo; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + git_oid_fromstr(&expected_id, "bf78348e45c8286f52b760f1db15cb6da030f2ef"); + cl_assert_equal_oid(&expected_id, &commit_id); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit)); + + cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); + + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_commit_free(commit); + git_rebase_free(rebase); +} + +static int create_cb_error( + git_oid *out, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[], + void *payload) +{ + GIT_UNUSED(out); + GIT_UNUSED(author); + GIT_UNUSED(committer); + GIT_UNUSED(message_encoding); + GIT_UNUSED(message); + GIT_UNUSED(tree); + GIT_UNUSED(parent_count); + GIT_UNUSED(parents); + GIT_UNUSED(payload); + + return GIT_EUSER; +} + +/* git checkout gravy ; git rebase --merge veal */ +void test_rebase_sign__create_propagates_error(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_oid commit_id; + git_rebase_operation *rebase_operation; + git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; + + rebase_opts.commit_create_cb = create_cb_error; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_fail_with(GIT_EUSER, git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_rebase_free(rebase); +} + +#ifndef GIT_DEPRECATE_HARD +static const char *expected_commit_content = "\ +tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ +parent f87d14a4a236582a0278a916340a793714256864\n\ +author Edward Thomson 1405625055 -0400\n\ +committer Rebaser 1405694510 +0000\n\ +\n\ +Modification 3 to gravy\n"; + +int signing_cb_passthrough( + git_buf *signature, + git_buf *signature_field, + const char *commit_content, + void *payload) +{ + cl_assert_equal_i(0, signature->size); + cl_assert_equal_i(0, signature_field->size); + cl_assert_equal_s(expected_commit_content, commit_content); + cl_assert_equal_p(NULL, payload); + return GIT_PASSTHROUGH; +} +#endif /* !GIT_DEPRECATE_HARD */ + +/* git checkout gravy ; git rebase --merge veal */ +void test_rebase_sign__passthrough_signing_cb(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, expected_id; + git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; + git_commit *commit; + const char *expected_commit_raw_header = "tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ +parent f87d14a4a236582a0278a916340a793714256864\n\ +author Edward Thomson 1405625055 -0400\n\ +committer Rebaser 1405694510 +0000\n"; + + rebase_opts.signing_cb = signing_cb_passthrough; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + git_oid_fromstr(&expected_id, "129183968a65abd6c52da35bff43325001bfc630"); + cl_assert_equal_oid(&expected_id, &commit_id); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit)); + + cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); + + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_commit_free(commit); + git_rebase_free(rebase); +#endif /* !GIT_DEPRECATE_HARD */ +} + +#ifndef GIT_DEPRECATE_HARD +int signing_cb_gpg( + git_buf *signature, + git_buf *signature_field, + const char *commit_content, + void *payload) +{ + const char *gpg_signature = "\ +-----BEGIN PGP SIGNATURE-----\n\ +\n\ +iQIzBAEBCgAdFiEEgVlDEfSlmKn0fvGgK++h5T2/ctIFAlwZcrAACgkQK++h5T2/\n\ +ctIPVhAA42RyZhMdKl5Bm0KtQco2scsukIg2y7tjSwhti91zDu3HQgpusjjo0fQx\n\ +ZzB+OrmlvQ9CDcGpZ0THIzXD8GRJoDMPqdrvZVrBWkGcHvw7/YPA8skzsjkauJ8W\n\ +7lzF5LCuHSS6OUmPT/+5hEHPin5PB3zhfszyC+Q7aujnIuPJMrKiMnUa+w1HWifM\n\ +km49OOygQ9S6NQoVuEQede22+c76DlDL7yFghGoo1f0sKCE/9LW6SEnwI/bWv9eo\n\ +nom5vOPrvQeJiYCQk+2DyWo8RdSxINtY+G9bPE4RXm+6ZgcXECPm9TYDIWpL36fC\n\ +jvtGLs98woWFElOziBMp5Tb630GMcSI+q5ivHfJ3WS5NKLYLHBNK4iSFN0/dgAnB\n\ +dj6GcKXKWnIBWn6ZM4o40pcM5KSRUUCLtA0ZmjJH4c4zx3X5fUxd+enwkf3e9VZO\n\ +fNKC/+xfq6NfoPUPK9+UnchHpJaJw7RG5tZS+sWCz2xpQ1y3/o49xImNyM3wnpvB\n\ +cRAZabqIHpZa9/DIUkELOtCzln6niqkjRgg3M/YCCNznwV+0RNgz87VtyTPerdef\n\ +xrqn0+ROMF6ebVqIs6PPtuPkxnAJu7TMKXVB5rFnAewS24e6cIGFzeIA7810py3l\n\ +cttVRsdOoego+fiy08eFE+aJIeYiINRGhqOBTsuqG4jIdpdKxPE=\n\ +=KbsY\n\ +-----END PGP SIGNATURE-----"; + + cl_assert_equal_i(0, signature->size); + cl_assert_equal_i(0, signature_field->size); + cl_assert_equal_s(expected_commit_content, commit_content); + cl_assert_equal_p(NULL, payload); + + cl_git_pass(git_buf_set(signature, gpg_signature, strlen(gpg_signature) + 1)); + return GIT_OK; +} +#endif /* !GIT_DEPRECATE_HARD */ + +/* git checkout gravy ; git rebase --merge veal */ +void test_rebase_sign__gpg_with_no_field(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, expected_id; + git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; + git_commit *commit; + const char *expected_commit_raw_header = "\ +tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ +parent f87d14a4a236582a0278a916340a793714256864\n\ +author Edward Thomson 1405625055 -0400\n\ +committer Rebaser 1405694510 +0000\n\ +gpgsig -----BEGIN PGP SIGNATURE-----\n\ + \n\ + iQIzBAEBCgAdFiEEgVlDEfSlmKn0fvGgK++h5T2/ctIFAlwZcrAACgkQK++h5T2/\n\ + ctIPVhAA42RyZhMdKl5Bm0KtQco2scsukIg2y7tjSwhti91zDu3HQgpusjjo0fQx\n\ + ZzB+OrmlvQ9CDcGpZ0THIzXD8GRJoDMPqdrvZVrBWkGcHvw7/YPA8skzsjkauJ8W\n\ + 7lzF5LCuHSS6OUmPT/+5hEHPin5PB3zhfszyC+Q7aujnIuPJMrKiMnUa+w1HWifM\n\ + km49OOygQ9S6NQoVuEQede22+c76DlDL7yFghGoo1f0sKCE/9LW6SEnwI/bWv9eo\n\ + nom5vOPrvQeJiYCQk+2DyWo8RdSxINtY+G9bPE4RXm+6ZgcXECPm9TYDIWpL36fC\n\ + jvtGLs98woWFElOziBMp5Tb630GMcSI+q5ivHfJ3WS5NKLYLHBNK4iSFN0/dgAnB\n\ + dj6GcKXKWnIBWn6ZM4o40pcM5KSRUUCLtA0ZmjJH4c4zx3X5fUxd+enwkf3e9VZO\n\ + fNKC/+xfq6NfoPUPK9+UnchHpJaJw7RG5tZS+sWCz2xpQ1y3/o49xImNyM3wnpvB\n\ + cRAZabqIHpZa9/DIUkELOtCzln6niqkjRgg3M/YCCNznwV+0RNgz87VtyTPerdef\n\ + xrqn0+ROMF6ebVqIs6PPtuPkxnAJu7TMKXVB5rFnAewS24e6cIGFzeIA7810py3l\n\ + cttVRsdOoego+fiy08eFE+aJIeYiINRGhqOBTsuqG4jIdpdKxPE=\n\ + =KbsY\n\ + -----END PGP SIGNATURE-----\n"; + + rebase_opts.signing_cb = signing_cb_gpg; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + git_oid_fromstr(&expected_id, "bf78348e45c8286f52b760f1db15cb6da030f2ef"); + cl_assert_equal_oid(&expected_id, &commit_id); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit)); + + cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); + + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_commit_free(commit); + git_rebase_free(rebase); +#endif /* !GIT_DEPRECATE_HARD */ +} + + +#ifndef GIT_DEPRECATE_HARD +int signing_cb_magic_field( + git_buf *signature, + git_buf *signature_field, + const char *commit_content, + void *payload) +{ + const char *signature_content = "magic word: pretty please"; + const char *signature_field_content = "magicsig"; + + cl_assert_equal_p(NULL, signature->ptr); + cl_assert_equal_i(0, signature->size); + cl_assert_equal_p(NULL, signature_field->ptr); + cl_assert_equal_i(0, signature_field->size); + cl_assert_equal_s(expected_commit_content, commit_content); + cl_assert_equal_p(NULL, payload); + + cl_git_pass(git_buf_set(signature, signature_content, + strlen(signature_content) + 1)); + cl_git_pass(git_buf_set(signature_field, signature_field_content, + strlen(signature_field_content) + 1)); + + return GIT_OK; +} +#endif /* !GIT_DEPRECATE_HARD */ + +/* git checkout gravy ; git rebase --merge veal */ +void test_rebase_sign__custom_signature_field(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, expected_id; + git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; + git_commit *commit; + const char *expected_commit_raw_header = "\ +tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ +parent f87d14a4a236582a0278a916340a793714256864\n\ +author Edward Thomson 1405625055 -0400\n\ +committer Rebaser 1405694510 +0000\n\ +magicsig magic word: pretty please\n"; + + rebase_opts.signing_cb = signing_cb_magic_field; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + git_oid_fromstr(&expected_id, "f46a4a8d26ae411b02aa61b7d69576627f4a1e1c"); + cl_assert_equal_oid(&expected_id, &commit_id); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit)); + + cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); + + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_commit_free(commit); + git_rebase_free(rebase); +#endif /* !GIT_DEPRECATE_HARD */ +} diff --git a/tests/libgit2/rebase/submodule.c b/tests/libgit2/rebase/submodule.c new file mode 100644 index 000000000..0b3c2d5b5 --- /dev/null +++ b/tests/libgit2/rebase/submodule.c @@ -0,0 +1,95 @@ +#include "clar_libgit2.h" +#include "git2/checkout.h" +#include "git2/rebase.h" +#include "posix.h" +#include "signature.h" +#include "../submodule/submodule_helpers.h" + +#include + +static git_repository *repo; +static git_signature *signature; + +/* Fixture setup and teardown */ +void test_rebase_submodule__initialize(void) +{ + git_index *index; + git_oid tree_oid, commit_id; + git_tree *tree; + git_commit *parent; + git_object *obj; + git_reference *master_ref; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + repo = cl_git_sandbox_init("rebase-submodule"); + cl_git_pass(git_signature_new(&signature, + "Rebaser", "rebaser@rebaser.rb", 1405694510, 0)); + + rewrite_gitmodules(git_repository_workdir(repo)); + + cl_git_pass(git_submodule_set_url(repo, "my-submodule", git_repository_path(repo))); + + /* We have to commit the rewritten .gitmodules file */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, ".gitmodules")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_index_write_tree(&tree_oid, index)); + cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); + + cl_git_pass(git_repository_head(&master_ref, repo)); + cl_git_pass(git_commit_lookup(&parent, repo, git_reference_target(master_ref))); + + cl_git_pass(git_commit_create_v(&commit_id, repo, git_reference_name(master_ref), signature, signature, NULL, "Fixup .gitmodules", tree, 1, parent)); + + /* And a final reset, for good measure */ + cl_git_pass(git_object_lookup(&obj, repo, &commit_id, GIT_OBJECT_COMMIT)); + cl_git_pass(git_reset(repo, obj, GIT_RESET_HARD, &opts)); + + git_index_free(index); + git_object_free(obj); + git_commit_free(parent); + git_reference_free(master_ref); + git_tree_free(tree); +} + +void test_rebase_submodule__cleanup(void) +{ + git_signature_free(signature); + cl_git_sandbox_cleanup(); +} + +void test_rebase_submodule__init_untracked(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_str untracked_path = GIT_STR_INIT; + FILE *fp; + git_submodule *submodule; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_submodule_lookup(&submodule, repo, "my-submodule")); + cl_git_pass(git_submodule_update(submodule, 1, NULL)); + + git_str_printf(&untracked_path, "%s/my-submodule/untracked", git_repository_workdir(repo)); + fp = fopen(git_str_cstr(&untracked_path), "w"); + fprintf(fp, "An untracked file in a submodule should not block a rebase\n"); + fclose(fp); + git_str_dispose(&untracked_path); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + git_submodule_free(submodule); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} diff --git a/tests/libgit2/refs/basic.c b/tests/libgit2/refs/basic.c new file mode 100644 index 000000000..32742f9cc --- /dev/null +++ b/tests/libgit2/refs/basic.c @@ -0,0 +1,85 @@ +#include "clar_libgit2.h" + +#include "futils.h" +#include "refs.h" +#include "ref_helpers.h" + +static git_repository *g_repo; + +static const char *loose_tag_ref_name = "refs/tags/e90810b"; + +void test_refs_basic__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_set_ident(g_repo, "me", "foo@example.com")); +} + +void test_refs_basic__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_basic__reference_realloc(void) +{ + git_reference *ref; + git_reference *new_ref; + const char *new_name = "refs/tags/awful/name-which-is/clearly/really-that-much/longer-than/the-old-one"; + + /* Retrieval of the reference to rename */ + cl_git_pass(git_reference_lookup(&ref, g_repo, loose_tag_ref_name)); + + new_ref = git_reference__realloc(&ref, new_name); + cl_assert(new_ref != NULL); + git_reference_free(new_ref); + git_reference_free(ref); + + /* Reload, so we restore the value */ + cl_git_pass(git_reference_lookup(&ref, g_repo, loose_tag_ref_name)); + + cl_git_pass(git_reference_rename(&new_ref, ref, new_name, 1, "log message")); + cl_assert(ref != NULL); + cl_assert(new_ref != NULL); + git_reference_free(new_ref); + git_reference_free(ref); +} + +void test_refs_basic__longpaths(void) +{ +#ifdef GIT_WIN32 + const char *base; + size_t base_len, extra_len; + ssize_t remain_len, i; + git_str refname = GIT_STR_INIT; + git_reference *one = NULL, *two = NULL; + git_oid id; + + cl_git_pass(git_oid_fromstr(&id, "099fabac3a9ea935598528c27f866e34089c2eff")); + + base = git_repository_path(g_repo); + base_len = git_utf8_char_length(base, strlen(base)); + extra_len = CONST_STRLEN("logs/refs/heads/") + CONST_STRLEN(".lock"); + + remain_len = (ssize_t)MAX_PATH - (base_len + extra_len); + cl_assert(remain_len > 0); + + cl_git_pass(git_str_puts(&refname, "refs/heads/")); + + for (i = 0; i < remain_len; i++) { + cl_git_pass(git_str_putc(&refname, 'a')); + } + + /* + * The full path to the reflog lockfile is 260 characters, + * this is permitted. + */ + cl_git_pass(git_reference_create(&one, g_repo, refname.ptr, &id, 0, NULL)); + + /* Adding one more character gives us a path that is too long. */ + cl_git_pass(git_str_putc(&refname, 'z')); + cl_git_fail(git_reference_create(&two, g_repo, refname.ptr, &id, 0, NULL)); + + git_reference_free(one); + git_reference_free(two); + git_str_dispose(&refname); +#endif +} diff --git a/tests/libgit2/refs/branches/checkedout.c b/tests/libgit2/refs/branches/checkedout.c new file mode 100644 index 000000000..d6dab2c0e --- /dev/null +++ b/tests/libgit2/refs/branches/checkedout.c @@ -0,0 +1,53 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "worktree/worktree_helpers.h" + +static git_repository *repo; + +static void assert_checked_out(git_repository *repo, const char *branch, int checked_out) +{ + git_reference *ref; + + cl_git_pass(git_reference_lookup(&ref, repo, branch)); + cl_assert(git_branch_is_checked_out(ref) == checked_out); + + git_reference_free(ref); +} + +void test_refs_branches_checkedout__simple_repo(void) +{ + repo = cl_git_sandbox_init("testrepo"); + assert_checked_out(repo, "refs/heads/master", 1); + assert_checked_out(repo, "refs/heads/executable", 0); + cl_git_sandbox_cleanup(); +} + +void test_refs_branches_checkedout__worktree(void) +{ + static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT("testrepo", "testrepo-worktree"); + + setup_fixture_worktree(&fixture); + + assert_checked_out(fixture.repo, "refs/heads/master", 1); + assert_checked_out(fixture.repo, "refs/heads/testrepo-worktree", 1); + + assert_checked_out(fixture.worktree, "refs/heads/master", 1); + assert_checked_out(fixture.worktree, "refs/heads/testrepo-worktree", 1); + + cleanup_fixture_worktree(&fixture); +} + +void test_refs_branches_checkedout__head_is_not_checked_out(void) +{ + repo = cl_git_sandbox_init("testrepo"); + assert_checked_out(repo, "HEAD", 0); + cl_git_sandbox_cleanup(); +} + +void test_refs_branches_checkedout__master_in_bare_repo_is_not_checked_out(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); + assert_checked_out(repo, "refs/heads/master", 0); + cl_git_sandbox_cleanup(); +} diff --git a/tests/libgit2/refs/branches/create.c b/tests/libgit2/refs/branches/create.c new file mode 100644 index 000000000..2fb11668b --- /dev/null +++ b/tests/libgit2/refs/branches/create.c @@ -0,0 +1,279 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "path.h" + +static git_repository *repo; +static git_commit *target; +static git_reference *branch; + +void test_refs_branches_create__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); + branch = NULL; + target = NULL; +} + +void test_refs_branches_create__cleanup(void) +{ + git_reference_free(branch); + branch = NULL; + + git_commit_free(target); + target = NULL; + + cl_git_sandbox_cleanup(); + repo = NULL; +} + +static void retrieve_target_from_oid(git_commit **out, git_repository *repo, const char *sha) +{ + git_object *obj; + + cl_git_pass(git_revparse_single(&obj, repo, sha)); + cl_git_pass(git_commit_lookup(out, repo, git_object_id(obj))); + git_object_free(obj); +} + +static void retrieve_known_commit(git_commit **commit, git_repository *repo) +{ + retrieve_target_from_oid(commit, repo, "e90810b8df3"); +} + +#define NEW_BRANCH_NAME "new-branch-on-the-block" + +void test_refs_branches_create__can_create_a_local_branch(void) +{ + retrieve_known_commit(&target, repo); + + cl_git_pass(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0)); + cl_git_pass(git_oid_cmp(git_reference_target(branch), git_commit_id(target))); +} + +void test_refs_branches_create__can_not_create_a_branch_if_its_name_collide_with_an_existing_one(void) +{ + retrieve_known_commit(&target, repo); + + cl_assert_equal_i(GIT_EEXISTS, git_branch_create(&branch, repo, "br2", target, 0)); +} + +void test_refs_branches_create__can_force_create_over_an_existing_branch(void) +{ + retrieve_known_commit(&target, repo); + + cl_git_pass(git_branch_create(&branch, repo, "br2", target, 1)); + cl_git_pass(git_oid_cmp(git_reference_target(branch), git_commit_id(target))); + cl_assert_equal_s("refs/heads/br2", git_reference_name(branch)); +} + +void test_refs_branches_create__cannot_force_create_over_current_branch_in_nonbare_repo(void) +{ + const git_oid *oid; + git_reference *branch2; + + /* Default repo for these tests is a bare repo, but this test requires a non-bare one */ + cl_git_sandbox_cleanup(); + repo = cl_git_sandbox_init("testrepo"); + retrieve_known_commit(&target, repo); + + cl_git_pass(git_branch_lookup(&branch2, repo, "master", GIT_BRANCH_LOCAL)); + cl_assert_equal_s("refs/heads/master", git_reference_name(branch2)); + cl_assert_equal_i(true, git_branch_is_head(branch2)); + oid = git_reference_target(branch2); + + cl_git_fail_with(-1, git_branch_create(&branch, repo, "master", target, 1)); + branch = NULL; + cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL)); + cl_assert_equal_s("refs/heads/master", git_reference_name(branch)); + cl_git_pass(git_oid_cmp(git_reference_target(branch), oid)); + git_reference_free(branch2); +} + +void test_refs_branches_create__can_force_create_over_current_branch_in_bare_repo(void) +{ + const git_oid *oid; + git_reference *branch2; + retrieve_known_commit(&target, repo); + + cl_git_pass(git_branch_lookup(&branch2, repo, "master", GIT_BRANCH_LOCAL)); + cl_assert_equal_s("refs/heads/master", git_reference_name(branch2)); + cl_assert_equal_i(true, git_branch_is_head(branch2)); + oid = git_commit_id(target); + + cl_git_pass(git_branch_create(&branch, repo, "master", target, 1)); + git_reference_free(branch); + branch = NULL; + cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL)); + cl_assert_equal_s("refs/heads/master", git_reference_name(branch)); + cl_git_pass(git_oid_cmp(git_reference_target(branch), oid)); + git_reference_free(branch2); +} + +void test_refs_branches_create__creating_a_branch_with_an_invalid_name_returns_EINVALIDSPEC(void) +{ + retrieve_known_commit(&target, repo); + + cl_assert_equal_i(GIT_EINVALIDSPEC, + git_branch_create(&branch, repo, "inv@{id", target, 0)); +} + +static void assert_branch_matches_name( + const char *expected, const char *lookup_as) +{ + git_reference *ref; + git_str b = GIT_STR_INIT; + + cl_git_pass(git_branch_lookup(&ref, repo, lookup_as, GIT_BRANCH_LOCAL)); + + cl_git_pass(git_str_sets(&b, "refs/heads/")); + cl_git_pass(git_str_puts(&b, expected)); + cl_assert_equal_s(b.ptr, git_reference_name(ref)); + + cl_git_pass( + git_oid_cmp(git_reference_target(ref), git_commit_id(target))); + + git_reference_free(ref); + git_str_dispose(&b); +} + +void test_refs_branches_create__can_create_branch_with_unicode(void) +{ + const char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; + const char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; + const char *emoji = "\xF0\x9F\x8D\xB7"; + const char *names[] = { nfc, nfd, emoji }; + const char *alt[] = { nfd, nfc, NULL }; + const char *expected[] = { nfc, nfd, emoji }; + unsigned int i; + bool fs_decompose_unicode = + git_fs_path_does_decompose_unicode(git_repository_path(repo)); + + retrieve_known_commit(&target, repo); + + if (cl_repo_get_bool(repo, "core.precomposeunicode")) + expected[1] = nfc; + /* test decomp. because not all Mac filesystems decompose unicode */ + else if (fs_decompose_unicode) + expected[0] = nfd; + + for (i = 0; i < ARRAY_SIZE(names); ++i) { + const char *name; + cl_git_pass(git_branch_create( + &branch, repo, names[i], target, 0)); + cl_git_pass(git_oid_cmp( + git_reference_target(branch), git_commit_id(target))); + + cl_git_pass(git_branch_name(&name, branch)); + cl_assert_equal_s(expected[i], name); + assert_branch_matches_name(expected[i], names[i]); + if (fs_decompose_unicode && alt[i]) + assert_branch_matches_name(expected[i], alt[i]); + + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); + branch = NULL; + } +} + +/** + * Verify that we can create a branch with a name that matches the + * namespace of a previously delete branch. + * + * git branch level_one/level_two + * git branch -D level_one/level_two + * git branch level_one + * + * We expect the delete to have deleted the files: + * ".git/refs/heads/level_one/level_two" + * ".git/logs/refs/heads/level_one/level_two" + * It may or may not have deleted the (now empty) + * containing directories. To match git.git behavior, + * the second create needs to implicilty delete the + * directories and create the new files. + * "refs/heads/level_one" + * "logs/refs/heads/level_one" + * + * We should not fail to create the branch or its + * reflog because of an obsolete namespace container + * directory. + */ +void test_refs_branches_create__name_vs_namespace(void) +{ + const char * name; + struct item { + const char *first; + const char *second; + }; + static const struct item item[] = { + { "level_one/level_two", "level_one" }, + { "a/b/c/d/e", "a/b/c/d" }, + { "ss/tt/uu/vv/ww", "ss" }, + /* And one test case that is deeper. */ + { "xx1/xx2/xx3/xx4", "xx1/xx2/xx3/xx4/xx5/xx6" }, + { NULL, NULL }, + }; + const struct item *p; + + retrieve_known_commit(&target, repo); + + for (p=item; p->first; p++) { + cl_git_pass(git_branch_create(&branch, repo, p->first, target, 0)); + cl_git_pass(git_oid_cmp(git_reference_target(branch), git_commit_id(target))); + cl_git_pass(git_branch_name(&name, branch)); + cl_assert_equal_s(name, p->first); + + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); + branch = NULL; + + cl_git_pass(git_branch_create(&branch, repo, p->second, target, 0)); + git_reference_free(branch); + branch = NULL; + } +} + +/** + * We still need to fail if part of the namespace is + * still in use. + */ +void test_refs_branches_create__name_vs_namespace_fail(void) +{ + const char * name; + struct item { + const char *first; + const char *first_alternate; + const char *second; + }; + static const struct item item[] = { + { "level_one/level_two", "level_one/alternate", "level_one" }, + { "a/b/c/d/e", "a/b/c/d/alternate", "a/b/c/d" }, + { "ss/tt/uu/vv/ww", "ss/alternate", "ss" }, + { NULL, NULL, NULL }, + }; + const struct item *p; + + retrieve_known_commit(&target, repo); + + for (p=item; p->first; p++) { + cl_git_pass(git_branch_create(&branch, repo, p->first, target, 0)); + cl_git_pass(git_oid_cmp(git_reference_target(branch), git_commit_id(target))); + cl_git_pass(git_branch_name(&name, branch)); + cl_assert_equal_s(name, p->first); + + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); + branch = NULL; + + cl_git_pass(git_branch_create(&branch, repo, p->first_alternate, target, 0)); + cl_git_pass(git_oid_cmp(git_reference_target(branch), git_commit_id(target))); + cl_git_pass(git_branch_name(&name, branch)); + cl_assert_equal_s(name, p->first_alternate); + + /* we do not delete the alternate. */ + git_reference_free(branch); + branch = NULL; + + cl_git_fail(git_branch_create(&branch, repo, p->second, target, 0)); + git_reference_free(branch); + branch = NULL; + } +} diff --git a/tests/libgit2/refs/branches/delete.c b/tests/libgit2/refs/branches/delete.c new file mode 100644 index 000000000..27995f9e2 --- /dev/null +++ b/tests/libgit2/refs/branches/delete.c @@ -0,0 +1,188 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "repo/repo_helpers.h" +#include "config/config_helpers.h" +#include "futils.h" +#include "reflog.h" + +static git_repository *repo; +static git_reference *fake_remote; + +void test_refs_branches_delete__initialize(void) +{ + git_oid id; + + repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_reference_create(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0, NULL)); +} + +void test_refs_branches_delete__cleanup(void) +{ + git_reference_free(fake_remote); + fake_remote = NULL; + + cl_git_sandbox_cleanup(); + repo = NULL; +} + +void test_refs_branches_delete__can_not_delete_a_branch_pointed_at_by_HEAD(void) +{ + git_reference *head; + git_reference *branch; + + /* Ensure HEAD targets the local master branch */ + cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); + cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); + git_reference_free(head); + + cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL)); + cl_git_fail(git_branch_delete(branch)); + git_reference_free(branch); +} + +void test_refs_branches_delete__can_delete_a_branch_even_if_HEAD_is_missing(void) +{ + git_reference *head; + git_reference *branch; + + cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); + git_reference_delete(head); + git_reference_free(head); + + cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); +} + +void test_refs_branches_delete__can_delete_a_branch_when_HEAD_is_unborn(void) +{ + git_reference *branch; + + make_head_unborn(repo, NON_EXISTING_HEAD); + + cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); +} + +void test_refs_branches_delete__can_delete_a_branch_pointed_at_by_detached_HEAD(void) +{ + git_reference *head, *branch; + + cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); + cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head)); + cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); + git_reference_free(head); + + /* Detach HEAD and make it target the commit that "master" points to */ + git_repository_detach_head(repo); + + cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); +} + +void test_refs_branches_delete__can_delete_a_local_branch(void) +{ + git_reference *branch; + cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); +} + +void test_refs_branches_delete__can_delete_a_remote_branch(void) +{ + git_reference *branch; + cl_git_pass(git_branch_lookup(&branch, repo, "nulltoken/master", GIT_BRANCH_REMOTE)); + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); +} + +void test_refs_branches_delete__deleting_a_branch_removes_related_configuration_data(void) +{ + git_reference *branch; + + assert_config_entry_existence(repo, "branch.track-local.remote", true); + assert_config_entry_existence(repo, "branch.track-local.merge", true); + + cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); + + assert_config_entry_existence(repo, "branch.track-local.remote", false); + assert_config_entry_existence(repo, "branch.track-local.merge", false); +} + +void test_refs_branches_delete__removes_reflog(void) +{ + git_reference *branch; + git_reflog *log; + git_oid oidzero = {{0}}; + git_signature *sig; + + /* Ensure the reflog has at least one entry */ + cl_git_pass(git_signature_now(&sig, "Me", "user@example.com")); + cl_git_pass(git_reflog_read(&log, repo, "refs/heads/track-local")); + cl_git_pass(git_reflog_append(log, &oidzero, sig, "message")); + cl_assert(git_reflog_entrycount(log) > 0); + git_signature_free(sig); + git_reflog_free(log); + + cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); + + cl_assert_equal_i(false, git_reference_has_log(repo, "refs/heads/track-local")); + + /* Reading a non-existent reflog creates it, but it should be empty */ + cl_git_pass(git_reflog_read(&log, repo, "refs/heads/track-local")); + cl_assert_equal_i(0, git_reflog_entrycount(log)); + git_reflog_free(log); +} + +void test_refs_branches_delete__removes_empty_folders(void) +{ + const char *commondir = git_repository_commondir(repo); + git_oid commit_id; + git_commit *commit; + git_reference *branch; + + git_reflog *log; + git_oid oidzero = {{0}}; + git_signature *sig; + + git_str ref_folder = GIT_STR_INIT; + git_str reflog_folder = GIT_STR_INIT; + + /* Create a new branch with a nested name */ + cl_git_pass(git_oid_fromstr(&commit_id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + cl_git_pass(git_branch_create(&branch, repo, "some/deep/ref", commit, 0)); + git_commit_free(commit); + + /* Ensure the reflog has at least one entry */ + cl_git_pass(git_signature_now(&sig, "Me", "user@example.com")); + cl_git_pass(git_reflog_read(&log, repo, "refs/heads/some/deep/ref")); + cl_git_pass(git_reflog_append(log, &oidzero, sig, "message")); + cl_assert(git_reflog_entrycount(log) > 0); + git_signature_free(sig); + git_reflog_free(log); + + cl_git_pass(git_str_joinpath(&ref_folder, commondir, "refs/heads/some/deep")); + cl_git_pass(git_str_join3(&reflog_folder, '/', commondir, GIT_REFLOG_DIR, "refs/heads/some/deep")); + + cl_assert(git_fs_path_exists(git_str_cstr(&ref_folder)) == true); + cl_assert(git_fs_path_exists(git_str_cstr(&reflog_folder)) == true); + + cl_git_pass(git_branch_delete(branch)); + + cl_assert(git_fs_path_exists(git_str_cstr(&ref_folder)) == false); + cl_assert(git_fs_path_exists(git_str_cstr(&reflog_folder)) == false); + + git_reference_free(branch); + git_str_dispose(&ref_folder); + git_str_dispose(&reflog_folder); +} + diff --git a/tests/libgit2/refs/branches/ishead.c b/tests/libgit2/refs/branches/ishead.c new file mode 100644 index 000000000..1df70b789 --- /dev/null +++ b/tests/libgit2/refs/branches/ishead.c @@ -0,0 +1,98 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "repo/repo_helpers.h" + +static git_repository *repo; +static git_reference *branch; + +void test_refs_branches_ishead__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); + branch = NULL; +} + +void test_refs_branches_ishead__cleanup(void) +{ + git_reference_free(branch); + branch = NULL; + + cl_git_sandbox_cleanup(); + repo = NULL; +} + +void test_refs_branches_ishead__can_tell_if_a_branch_is_pointed_at_by_HEAD(void) +{ + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); + + cl_assert_equal_i(true, git_branch_is_head(branch)); +} + +void test_refs_branches_ishead__can_properly_handle_unborn_HEAD(void) +{ + make_head_unborn(repo, NON_EXISTING_HEAD); + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); + + cl_assert_equal_i(false, git_branch_is_head(branch)); +} + +void test_refs_branches_ishead__can_properly_handle_missing_HEAD(void) +{ + delete_head(repo); + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); + + cl_assert_equal_i(false, git_branch_is_head(branch)); +} + +void test_refs_branches_ishead__can_tell_if_a_branch_is_not_pointed_at_by_HEAD(void) +{ + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/br2")); + + cl_assert_equal_i(false, git_branch_is_head(branch)); +} + +void test_refs_branches_ishead__wont_be_fooled_by_a_non_branch(void) +{ + cl_git_pass(git_reference_lookup(&branch, repo, "refs/tags/e90810b")); + + cl_assert_equal_i(false, git_branch_is_head(branch)); +} + +/* + * $ git init . + * Initialized empty Git repository in d:/temp/tempee/.git/ + * + * $ touch a && git add a + * $ git commit -m" boom" + * [master (root-commit) b47b758] boom + * 0 files changed + * create mode 100644 a + * + * $ echo "ref: refs/heads/master" > .git/refs/heads/linked + * $ echo "ref: refs/heads/linked" > .git/refs/heads/super + * $ echo "ref: refs/heads/super" > .git/HEAD + * + * $ git branch + * linked -> master + * * master + * super -> master + */ +void test_refs_branches_ishead__only_direct_references_are_considered(void) +{ + git_reference *linked, *super, *head; + + cl_git_pass(git_reference_symbolic_create(&linked, repo, "refs/heads/linked", "refs/heads/master", 0, NULL)); + cl_git_pass(git_reference_symbolic_create(&super, repo, "refs/heads/super", "refs/heads/linked", 0, NULL)); + cl_git_pass(git_reference_symbolic_create(&head, repo, GIT_HEAD_FILE, "refs/heads/super", 1, NULL)); + + cl_assert_equal_i(false, git_branch_is_head(linked)); + cl_assert_equal_i(false, git_branch_is_head(super)); + + cl_git_pass(git_repository_head(&branch, repo)); + cl_assert_equal_s("refs/heads/master", git_reference_name(branch)); + + git_reference_free(linked); + git_reference_free(super); + git_reference_free(head); +} diff --git a/tests/libgit2/refs/branches/iterator.c b/tests/libgit2/refs/branches/iterator.c new file mode 100644 index 000000000..e086681e5 --- /dev/null +++ b/tests/libgit2/refs/branches/iterator.c @@ -0,0 +1,151 @@ +#include "clar_libgit2.h" +#include "refs.h" + +static git_repository *repo; +static git_reference *fake_remote; + +void test_refs_branches_iterator__initialize(void) +{ + git_oid id; + + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&repo, "testrepo.git")); + + cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_reference_create(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0, NULL)); +} + +void test_refs_branches_iterator__cleanup(void) +{ + git_reference_free(fake_remote); + fake_remote = NULL; + + git_repository_free(repo); + repo = NULL; + + cl_fixture_cleanup("testrepo.git"); + + cl_git_sandbox_cleanup(); +} + +static void assert_retrieval(unsigned int flags, unsigned int expected_count) +{ + git_branch_iterator *iter; + git_reference *ref; + int count = 0, error; + git_branch_t type; + + cl_git_pass(git_branch_iterator_new(&iter, repo, flags)); + while ((error = git_branch_next(&ref, &type, iter)) == 0) { + count++; + git_reference_free(ref); + } + + git_branch_iterator_free(iter); + cl_assert_equal_i(error, GIT_ITEROVER); + cl_assert_equal_i(expected_count, count); +} + +void test_refs_branches_iterator__retrieve_all_branches(void) +{ + assert_retrieval(GIT_BRANCH_ALL, 15); +} + +void test_refs_branches_iterator__retrieve_remote_branches(void) +{ + assert_retrieval(GIT_BRANCH_REMOTE, 2); +} + +void test_refs_branches_iterator__retrieve_local_branches(void) +{ + assert_retrieval(GIT_BRANCH_LOCAL, 13); +} + +struct expectations { + const char *branch_name; + int encounters; +}; + +static void assert_branch_has_been_found(struct expectations *findings, const char* expected_branch_name) +{ + int pos = 0; + + for (pos = 0; findings[pos].branch_name; ++pos) { + if (strcmp(expected_branch_name, findings[pos].branch_name) == 0) { + cl_assert_equal_i(1, findings[pos].encounters); + return; + } + } + + cl_fail("expected branch not found in list."); +} + +static void contains_branches(struct expectations exp[], git_branch_iterator *iter) +{ + git_reference *ref; + git_branch_t type; + int error, pos = 0; + + while ((error = git_branch_next(&ref, &type, iter)) == 0) { + for (pos = 0; exp[pos].branch_name; ++pos) { + if (strcmp(git_reference_shorthand(ref), exp[pos].branch_name) == 0) + exp[pos].encounters++; + } + + git_reference_free(ref); + } + + cl_assert_equal_i(error, GIT_ITEROVER); +} + +/* + * $ git branch -r + * nulltoken/HEAD -> nulltoken/master + * nulltoken/master + */ +void test_refs_branches_iterator__retrieve_remote_symbolic_HEAD_when_present(void) +{ + git_branch_iterator *iter; + struct expectations exp[] = { + { "nulltoken/HEAD", 0 }, + { "nulltoken/master", 0 }, + { NULL, 0 } + }; + + git_reference_free(fake_remote); + cl_git_pass(git_reference_symbolic_create(&fake_remote, repo, "refs/remotes/nulltoken/HEAD", "refs/remotes/nulltoken/master", 0, NULL)); + + assert_retrieval(GIT_BRANCH_REMOTE, 3); + + cl_git_pass(git_branch_iterator_new(&iter, repo, GIT_BRANCH_REMOTE)); + contains_branches(exp, iter); + git_branch_iterator_free(iter); + + assert_branch_has_been_found(exp, "nulltoken/HEAD"); + assert_branch_has_been_found(exp, "nulltoken/master"); +} + +void test_refs_branches_iterator__mix_of_packed_and_loose(void) +{ + git_branch_iterator *iter; + struct expectations exp[] = { + { "master", 0 }, + { "origin/HEAD", 0 }, + { "origin/master", 0 }, + { "origin/packed", 0 }, + { NULL, 0 } + }; + git_repository *r2; + + r2 = cl_git_sandbox_init("testrepo2"); + + cl_git_pass(git_branch_iterator_new(&iter, r2, GIT_BRANCH_ALL)); + contains_branches(exp, iter); + + git_branch_iterator_free(iter); + + assert_branch_has_been_found(exp, "master"); + assert_branch_has_been_found(exp, "origin/HEAD"); + assert_branch_has_been_found(exp, "origin/master"); + assert_branch_has_been_found(exp, "origin/packed"); +} diff --git a/tests/libgit2/refs/branches/lookup.c b/tests/libgit2/refs/branches/lookup.c new file mode 100644 index 000000000..ef0c1f97d --- /dev/null +++ b/tests/libgit2/refs/branches/lookup.c @@ -0,0 +1,68 @@ +#include "clar_libgit2.h" +#include "refs.h" + +static git_repository *repo; +static git_reference *branch; + +void test_refs_branches_lookup__initialize(void) +{ + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + branch = NULL; +} + +void test_refs_branches_lookup__cleanup(void) +{ + git_reference_free(branch); + branch = NULL; + + git_repository_free(repo); + repo = NULL; +} + +void test_refs_branches_lookup__can_retrieve_a_local_branch_local(void) +{ + cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); +} + +void test_refs_branches_lookup__can_retrieve_a_local_branch_all(void) +{ + cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_ALL)); +} + +void test_refs_branches_lookup__trying_to_retrieve_a_local_branch_remote(void) +{ + cl_git_fail(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_REMOTE)); +} + +void test_refs_branches_lookup__can_retrieve_a_remote_tracking_branch_remote(void) +{ + cl_git_pass(git_branch_lookup(&branch, repo, "test/master", GIT_BRANCH_REMOTE)); +} + +void test_refs_branches_lookup__can_retrieve_a_remote_tracking_branch_all(void) +{ + cl_git_pass(git_branch_lookup(&branch, repo, "test/master", GIT_BRANCH_ALL)); +} + +void test_refs_branches_lookup__trying_to_retrieve_a_remote_tracking_branch_local(void) +{ + cl_git_fail(git_branch_lookup(&branch, repo, "test/master", GIT_BRANCH_LOCAL)); +} + +void test_refs_branches_lookup__trying_to_retrieve_an_unknown_branch_returns_ENOTFOUND(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "where/are/you", GIT_BRANCH_LOCAL)); + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "over/here", GIT_BRANCH_REMOTE)); + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "maybe/here", GIT_BRANCH_ALL)); +} + +void test_refs_branches_lookup__trying_to_retrieve_a_branch_with_an_invalid_name_returns_EINVALIDSPEC(void) +{ + cl_assert_equal_i(GIT_EINVALIDSPEC, + git_branch_lookup(&branch, repo, "are/you/inv@{id", GIT_BRANCH_LOCAL)); + cl_assert_equal_i(GIT_EINVALIDSPEC, + git_branch_lookup(&branch, repo, "yes/i am", GIT_BRANCH_REMOTE)); + cl_assert_equal_i(GIT_EINVALIDSPEC, + git_branch_lookup(&branch, repo, "inv al/id", GIT_BRANCH_ALL)); +} diff --git a/tests/libgit2/refs/branches/move.c b/tests/libgit2/refs/branches/move.c new file mode 100644 index 000000000..46a5082d2 --- /dev/null +++ b/tests/libgit2/refs/branches/move.c @@ -0,0 +1,212 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "config/config_helpers.h" + +static git_repository *repo; + +void test_refs_branches_move__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_refs_branches_move__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +#define NEW_BRANCH_NAME "new-branch-on-the-block" + +void test_refs_branches_move__can_move_a_local_branch(void) +{ + git_reference *original_ref, *new_ref; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + + cl_git_pass(git_branch_move(&new_ref, original_ref, NEW_BRANCH_NAME, 0)); + cl_assert_equal_s(GIT_REFS_HEADS_DIR NEW_BRANCH_NAME, git_reference_name(new_ref)); + + git_reference_free(original_ref); + git_reference_free(new_ref); +} + +void test_refs_branches_move__can_move_a_local_branch_to_a_different_namespace(void) +{ + git_reference *original_ref, *new_ref, *newer_ref; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + + /* Downward */ + cl_git_pass(git_branch_move(&new_ref, original_ref, "somewhere/" NEW_BRANCH_NAME, 0)); + git_reference_free(original_ref); + + /* Upward */ + cl_git_pass(git_branch_move(&newer_ref, new_ref, "br2", 0)); + git_reference_free(new_ref); + + git_reference_free(newer_ref); +} + +void test_refs_branches_move__can_move_a_local_branch_to_a_partially_colliding_namespace(void) +{ + git_reference *original_ref, *new_ref, *newer_ref; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + + /* Downward */ + cl_git_pass(git_branch_move(&new_ref, original_ref, "br2/" NEW_BRANCH_NAME, 0)); + git_reference_free(original_ref); + + /* Upward */ + cl_git_pass(git_branch_move(&newer_ref, new_ref, "br2", 0)); + git_reference_free(new_ref); + + git_reference_free(newer_ref); +} + +void test_refs_branches_move__can_not_move_a_branch_if_its_destination_name_collide_with_an_existing_one(void) +{ + git_reference *original_ref, *new_ref; + git_config *config; + git_buf original_remote = GIT_BUF_INIT, + original_merge = GIT_BUF_INIT; + const char *str; + + cl_git_pass(git_repository_config_snapshot(&config, repo)); + + cl_git_pass(git_config_get_string_buf(&original_remote, config, "branch.master.remote")); + cl_git_pass(git_config_get_string_buf(&original_merge, config, "branch.master.merge")); + git_config_free(config); + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + + cl_assert_equal_i(GIT_EEXISTS, + git_branch_move(&new_ref, original_ref, "master", 0)); + + cl_assert(git_error_last()->message != NULL); + + cl_git_pass(git_repository_config_snapshot(&config, repo)); + cl_git_pass(git_config_get_string(&str, config, "branch.master.remote")); + cl_assert_equal_s(original_remote.ptr, str); + cl_git_pass(git_config_get_string(&str, config, "branch.master.merge")); + cl_assert_equal_s(original_merge.ptr, str); + git_config_free(config); + + cl_assert_equal_i(GIT_EEXISTS, + git_branch_move(&new_ref, original_ref, "cannot-fetch", 0)); + + cl_assert(git_error_last()->message != NULL); + + cl_git_pass(git_repository_config_snapshot(&config, repo)); + cl_git_pass(git_config_get_string(&str, config, "branch.master.remote")); + cl_assert_equal_s(original_remote.ptr, str); + cl_git_pass(git_config_get_string(&str, config, "branch.master.merge")); + cl_assert_equal_s(original_merge.ptr, str); + git_config_free(config); + + git_reference_free(original_ref); + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/track-local")); + + cl_assert_equal_i(GIT_EEXISTS, + git_branch_move(&new_ref, original_ref, "master", 0)); + + cl_assert(git_error_last()->message != NULL); + + cl_git_pass(git_repository_config_snapshot(&config, repo)); + cl_git_pass(git_config_get_string(&str, config, "branch.master.remote")); + cl_assert_equal_s(original_remote.ptr, str); + cl_git_pass(git_config_get_string(&str, config, "branch.master.merge")); + cl_assert_equal_s(original_merge.ptr, str); + + git_buf_dispose(&original_remote); + git_buf_dispose(&original_merge); + git_reference_free(original_ref); + git_config_free(config); +} + +void test_refs_branches_move__moving_a_branch_with_an_invalid_name_returns_EINVALIDSPEC(void) +{ + git_reference *original_ref, *new_ref; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + + cl_assert_equal_i(GIT_EINVALIDSPEC, git_branch_move(&new_ref, original_ref, "Inv@{id", 0)); + + git_reference_free(original_ref); +} + +void test_refs_branches_move__can_not_move_a_non_branch(void) +{ + git_reference *tag, *new_ref; + + cl_git_pass(git_reference_lookup(&tag, repo, "refs/tags/e90810b")); + cl_git_fail(git_branch_move(&new_ref, tag, NEW_BRANCH_NAME, 0)); + + git_reference_free(tag); +} + +void test_refs_branches_move__can_force_move_over_an_existing_branch(void) +{ + git_reference *original_ref, *new_ref; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + + cl_git_pass(git_branch_move(&new_ref, original_ref, "master", 1)); + + git_reference_free(original_ref); + git_reference_free(new_ref); +} + +void test_refs_branches_move__moving_a_branch_moves_related_configuration_data(void) +{ + git_reference *branch; + git_reference *new_branch; + + cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL)); + + assert_config_entry_existence(repo, "branch.track-local.remote", true); + assert_config_entry_existence(repo, "branch.track-local.merge", true); + assert_config_entry_existence(repo, "branch.moved.remote", false); + assert_config_entry_existence(repo, "branch.moved.merge", false); + + cl_git_pass(git_branch_move(&new_branch, branch, "moved", 0)); + git_reference_free(branch); + + assert_config_entry_existence(repo, "branch.track-local.remote", false); + assert_config_entry_existence(repo, "branch.track-local.merge", false); + assert_config_entry_existence(repo, "branch.moved.remote", true); + assert_config_entry_existence(repo, "branch.moved.merge", true); + + git_reference_free(new_branch); +} + +void test_refs_branches_move__moving_the_branch_pointed_at_by_HEAD_updates_HEAD(void) +{ + git_reference *branch; + git_reference *new_branch; + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); + cl_git_pass(git_branch_move(&new_branch, branch, "master2", 0)); + git_reference_free(branch); + git_reference_free(new_branch); + + cl_git_pass(git_repository_head(&branch, repo)); + cl_assert_equal_s("refs/heads/master2", git_reference_name(branch)); + git_reference_free(branch); +} + +void test_refs_branches_move__can_move_with_unicode(void) +{ + git_reference *original_ref, *new_ref; + const char *new_branch_name = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + cl_git_pass(git_branch_move(&new_ref, original_ref, new_branch_name, 0)); + + if (cl_repo_get_bool(repo, "core.precomposeunicode")) + cl_assert_equal_s(GIT_REFS_HEADS_DIR "\xC3\x85\x73\x74\x72\xC3\xB6\x6D", git_reference_name(new_ref)); + else + cl_assert_equal_s(GIT_REFS_HEADS_DIR "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D", git_reference_name(new_ref)); + + git_reference_free(original_ref); + git_reference_free(new_ref); +} diff --git a/tests/libgit2/refs/branches/name.c b/tests/libgit2/refs/branches/name.c new file mode 100644 index 000000000..efa68e32b --- /dev/null +++ b/tests/libgit2/refs/branches/name.c @@ -0,0 +1,62 @@ +#include "clar_libgit2.h" +#include "branch.h" + +static git_repository *repo; +static git_reference *ref; + +void test_refs_branches_name__initialize(void) +{ + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); +} + +void test_refs_branches_name__cleanup(void) +{ + git_reference_free(ref); + ref = NULL; + + git_repository_free(repo); + repo = NULL; +} + +void test_refs_branches_name__can_get_local_branch_name(void) +{ + const char *name; + + cl_git_pass(git_branch_lookup(&ref,repo,"master",GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_name(&name,ref)); + cl_assert_equal_s("master",name); +} + +void test_refs_branches_name__can_get_remote_branch_name(void) +{ + const char *name; + + cl_git_pass(git_branch_lookup(&ref,repo,"test/master",GIT_BRANCH_REMOTE)); + cl_git_pass(git_branch_name(&name,ref)); + cl_assert_equal_s("test/master",name); +} + +void test_refs_branches_name__error_when_ref_is_no_branch(void) +{ + const char *name; + + cl_git_pass(git_reference_lookup(&ref,repo,"refs/notes/fanout")); + cl_git_fail(git_branch_name(&name,ref)); +} + +static int name_is_valid(const char *name) +{ + int valid; + cl_git_pass(git_branch_name_is_valid(&valid, name)); + return valid; +} + +void test_refs_branches_name__is_name_valid(void) +{ + cl_assert_equal_i(true, name_is_valid("master")); + cl_assert_equal_i(true, name_is_valid("test/master")); + + cl_assert_equal_i(false, name_is_valid("")); + cl_assert_equal_i(false, name_is_valid("HEAD")); + cl_assert_equal_i(false, name_is_valid("-dash")); +} diff --git a/tests/libgit2/refs/branches/remote.c b/tests/libgit2/refs/branches/remote.c new file mode 100644 index 000000000..e2bd3485a --- /dev/null +++ b/tests/libgit2/refs/branches/remote.c @@ -0,0 +1,65 @@ +#include "clar_libgit2.h" +#include "branch.h" +#include "remote.h" + +static git_repository *g_repo; +static const char *remote_tracking_branch_name = "refs/remotes/test/master"; +static const char *expected_remote_name = "test"; +static int expected_remote_name_length; + +void test_refs_branches_remote__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); + + expected_remote_name_length = (int)strlen(expected_remote_name) + 1; +} + +void test_refs_branches_remote__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_branches_remote__can_get_remote_for_branch(void) +{ + git_buf remotename = {0}; + + cl_git_pass(git_branch_remote_name(&remotename, g_repo, remote_tracking_branch_name)); + + cl_assert_equal_s("test", remotename.ptr); + git_buf_dispose(&remotename); +} + +void test_refs_branches_remote__no_matching_remote_returns_error(void) +{ + const char *unknown = "refs/remotes/nonexistent/master"; + git_buf buf = GIT_BUF_INIT; + + git_error_clear(); + cl_git_fail_with(git_branch_remote_name(&buf, g_repo, unknown), GIT_ENOTFOUND); + cl_assert(git_error_last() != NULL); +} + +void test_refs_branches_remote__local_remote_returns_error(void) +{ + const char *local = "refs/heads/master"; + git_buf buf = GIT_BUF_INIT; + + git_error_clear(); + cl_git_fail_with(git_branch_remote_name(&buf, g_repo, local), GIT_ERROR); + cl_assert(git_error_last() != NULL); +} + +void test_refs_branches_remote__ambiguous_remote_returns_error(void) +{ + git_remote *remote; + git_buf buf = GIT_BUF_INIT; + + /* Create the remote */ + cl_git_pass(git_remote_create_with_fetchspec(&remote, g_repo, "addtest", "http://github.com/libgit2/libgit2", "refs/heads/*:refs/remotes/test/*")); + + git_remote_free(remote); + + git_error_clear(); + cl_git_fail_with(git_branch_remote_name(&buf, g_repo, remote_tracking_branch_name), GIT_EAMBIGUOUS); + cl_assert(git_error_last() != NULL); +} diff --git a/tests/libgit2/refs/branches/upstream.c b/tests/libgit2/refs/branches/upstream.c new file mode 100644 index 000000000..919705e07 --- /dev/null +++ b/tests/libgit2/refs/branches/upstream.c @@ -0,0 +1,218 @@ +#include "clar_libgit2.h" +#include "config/config_helpers.h" +#include "refs.h" + +static git_repository *repo; +static git_reference *branch, *upstream; + +void test_refs_branches_upstream__initialize(void) +{ + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + branch = NULL; + upstream = NULL; +} + +void test_refs_branches_upstream__cleanup(void) +{ + git_reference_free(upstream); + git_reference_free(branch); + branch = NULL; + + git_repository_free(repo); + repo = NULL; +} + +void test_refs_branches_upstream__can_retrieve_the_remote_tracking_reference_of_a_local_branch(void) +{ + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); + + cl_git_pass(git_branch_upstream(&upstream, branch)); + + cl_assert_equal_s("refs/remotes/test/master", git_reference_name(upstream)); +} + +void test_refs_branches_upstream__can_retrieve_the_local_upstream_reference_of_a_local_branch(void) +{ + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/track-local")); + + cl_git_pass(git_branch_upstream(&upstream, branch)); + + cl_assert_equal_s("refs/heads/master", git_reference_name(upstream)); +} + +void test_refs_branches_upstream__cannot_retrieve_a_remote_upstream_reference_from_a_non_branch(void) +{ + cl_git_pass(git_reference_lookup(&branch, repo, "refs/tags/e90810b")); + + cl_git_fail(git_branch_upstream(&upstream, branch)); +} + +void test_refs_branches_upstream__trying_to_retrieve_a_remote_tracking_reference_from_a_plain_local_branch_returns_GIT_ENOTFOUND(void) +{ + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/subtrees")); + + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_upstream(&upstream, branch)); +} + +void test_refs_branches_upstream__trying_to_retrieve_a_remote_tracking_reference_from_a_branch_with_no_fetchspec_returns_GIT_ENOTFOUND(void) +{ + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/cannot-fetch")); + + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_upstream(&upstream, branch)); +} + +void test_refs_branches_upstream__upstream_remote(void) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_branch_upstream_remote(&buf, repo, "refs/heads/master")); + cl_assert_equal_s("test", buf.ptr); + git_buf_dispose(&buf); +} + +void test_refs_branches_upstream__upstream_merge(void) +{ + git_reference *branch; + git_repository *repository; + git_buf buf = GIT_BUF_INIT; + + repository = cl_git_sandbox_init("testrepo.git"); + + /* check repository */ + cl_git_pass(git_reference_lookup(&branch, repository, "refs/heads/test")); + cl_git_pass(git_branch_set_upstream(branch, "test/master")); + + assert_config_entry_value(repository, "branch.test.remote", "test"); + assert_config_entry_value(repository, "branch.test.merge", "refs/heads/master"); + + git_reference_free(branch); + + /* check merge branch */ + cl_git_pass(git_branch_upstream_merge(&buf, repository, "refs/heads/test")); + cl_assert_equal_s("refs/heads/master", buf.ptr); + git_buf_dispose(&buf); + + cl_git_sandbox_cleanup(); +} + +void test_refs_branches_upstream__upstream_remote_empty_value(void) +{ + git_repository *repository; + git_config *cfg; + git_buf buf = GIT_BUF_INIT; + + repository = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_config(&cfg, repository)); + cl_git_pass(git_config_set_string(cfg, "branch.master.remote", "")); + cl_git_fail_with(GIT_ENOTFOUND, git_branch_upstream_remote(&buf, repository, "refs/heads/master")); + + cl_git_pass(git_config_delete_entry(cfg, "branch.master.remote")); + cl_git_fail_with(GIT_ENOTFOUND, git_branch_upstream_remote(&buf, repository, "refs/heads/master")); + cl_git_sandbox_cleanup(); +} + +static void assert_merge_and_or_remote_key_missing(git_repository *repository, const git_commit *target, const char *entry_name) +{ + git_reference *branch; + + cl_assert_equal_i(GIT_OBJECT_COMMIT, git_object_type((git_object*)target)); + cl_git_pass(git_branch_create(&branch, repository, entry_name, (git_commit*)target, 0)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_upstream(&upstream, branch)); + + git_reference_free(branch); +} + +void test_refs_branches_upstream__retrieve_a_remote_tracking_reference_from_a_branch_with_no_remote_returns_GIT_ENOTFOUND(void) +{ + git_reference *head; + git_repository *repository; + git_commit *target; + + repository = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_repository_head(&head, repository)); + cl_git_pass(git_reference_peel(((git_object **)&target), head, GIT_OBJECT_COMMIT)); + git_reference_free(head); + + assert_merge_and_or_remote_key_missing(repository, target, "remoteless"); + assert_merge_and_or_remote_key_missing(repository, target, "mergeless"); + assert_merge_and_or_remote_key_missing(repository, target, "mergeandremoteless"); + + git_commit_free(target); + + cl_git_sandbox_cleanup(); +} + +void test_refs_branches_upstream__set_unset_upstream(void) +{ + git_reference *branch; + git_repository *repository; + + repository = cl_git_sandbox_init("testrepo.git"); + + /* remote */ + cl_git_pass(git_reference_lookup(&branch, repository, "refs/heads/test")); + cl_git_pass(git_branch_set_upstream(branch, "test/master")); + + assert_config_entry_value(repository, "branch.test.remote", "test"); + assert_config_entry_value(repository, "branch.test.merge", "refs/heads/master"); + + git_reference_free(branch); + + /* local */ + cl_git_pass(git_reference_lookup(&branch, repository, "refs/heads/test")); + cl_git_pass(git_branch_set_upstream(branch, "master")); + + assert_config_entry_value(repository, "branch.test.remote", "."); + assert_config_entry_value(repository, "branch.test.merge", "refs/heads/master"); + + /* unset */ + cl_git_pass(git_branch_set_upstream(branch, NULL)); + assert_config_entry_existence(repository, "branch.test.remote", false); + assert_config_entry_existence(repository, "branch.test.merge", false); + + git_reference_free(branch); + + cl_git_pass(git_reference_lookup(&branch, repository, "refs/heads/master")); + cl_git_pass(git_branch_set_upstream(branch, NULL)); + assert_config_entry_existence(repository, "branch.test.remote", false); + assert_config_entry_existence(repository, "branch.test.merge", false); + + git_reference_free(branch); + + cl_git_sandbox_cleanup(); +} + +void test_refs_branches_upstream__no_fetch_refspec(void) +{ + git_reference *ref, *branch; + git_repository *repo; + git_remote *remote; + git_config *cfg; + + repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_remote_create_with_fetchspec(&remote, repo, "matching", ".", NULL)); + cl_git_pass(git_remote_add_push(repo, "matching", ":")); + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/test")); + cl_git_pass(git_reference_create(&ref, repo, "refs/remotes/matching/master", git_reference_target(branch), 1, "fetch")); + cl_git_fail(git_branch_set_upstream(branch, "matching/master")); + cl_assert_equal_s("could not determine remote for 'refs/remotes/matching/master'", + git_error_last()->message); + + /* we can't set it automatically, so let's test the user setting it by hand */ + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_string(cfg, "branch.test.remote", "matching")); + cl_git_pass(git_config_set_string(cfg, "branch.test.merge", "refs/heads/master")); + /* we still can't find it because there is no rule for that reference */ + cl_git_fail_with(GIT_ENOTFOUND, git_branch_upstream(&ref, branch)); + + git_reference_free(ref); + git_reference_free(branch); + git_remote_free(remote); + + cl_git_sandbox_cleanup(); +} diff --git a/tests/libgit2/refs/branches/upstreamname.c b/tests/libgit2/refs/branches/upstreamname.c new file mode 100644 index 000000000..5bae154d2 --- /dev/null +++ b/tests/libgit2/refs/branches/upstreamname.c @@ -0,0 +1,35 @@ +#include "clar_libgit2.h" +#include "branch.h" + +static git_repository *repo; +static git_buf upstream_name; + +void test_refs_branches_upstreamname__initialize(void) +{ + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + +} + +void test_refs_branches_upstreamname__cleanup(void) +{ + git_buf_dispose(&upstream_name); + + git_repository_free(repo); + repo = NULL; +} + +void test_refs_branches_upstreamname__can_retrieve_the_remote_tracking_reference_name_of_a_local_branch(void) +{ + cl_git_pass(git_branch_upstream_name( + &upstream_name, repo, "refs/heads/master")); + + cl_assert_equal_s("refs/remotes/test/master", upstream_name.ptr); +} + +void test_refs_branches_upstreamname__can_retrieve_the_local_upstream_reference_name_of_a_local_branch(void) +{ + cl_git_pass(git_branch_upstream_name( + &upstream_name, repo, "refs/heads/track-local")); + + cl_assert_equal_s("refs/heads/master", upstream_name.ptr); +} diff --git a/tests/libgit2/refs/crashes.c b/tests/libgit2/refs/crashes.c new file mode 100644 index 000000000..4f508aed2 --- /dev/null +++ b/tests/libgit2/refs/crashes.c @@ -0,0 +1,44 @@ +#include "clar_libgit2.h" +#include "refs.h" + +void test_refs_crashes__double_free(void) +{ + git_repository *repo; + git_reference *ref, *ref2; + const char *REFNAME = "refs/heads/xxx"; + + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_reference_symbolic_create(&ref, repo, REFNAME, "refs/heads/master", 0, NULL)); + cl_git_pass(git_reference_lookup(&ref2, repo, REFNAME)); + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + git_reference_free(ref2); + + /* reference is gone from disk, so reloading it will fail */ + cl_git_fail(git_reference_lookup(&ref2, repo, REFNAME)); + + cl_git_sandbox_cleanup(); +} + +void test_refs_crashes__empty_packedrefs(void) +{ + git_repository *repo; + git_reference *ref; + const char *REFNAME = "refs/heads/xxx"; + git_str temp_path = GIT_STR_INIT; + int fd = 0; + + repo = cl_git_sandbox_init("empty_bare.git"); + + /* create zero-length packed-refs file */ + cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(repo), GIT_PACKEDREFS_FILE)); + cl_git_pass(((fd = p_creat(temp_path.ptr, 0644)) < 0)); + cl_git_pass(p_close(fd)); + + /* should fail gracefully */ + cl_git_fail_with( + GIT_ENOTFOUND, git_reference_lookup(&ref, repo, REFNAME)); + + cl_git_sandbox_cleanup(); + git_str_dispose(&temp_path); +} diff --git a/tests/libgit2/refs/create.c b/tests/libgit2/refs/create.c new file mode 100644 index 000000000..01eb62a52 --- /dev/null +++ b/tests/libgit2/refs/create.c @@ -0,0 +1,362 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" +#include "ref_helpers.h" + +static const char *current_master_tip = "099fabac3a9ea935598528c27f866e34089c2eff"; +static const char *current_head_target = "refs/heads/master"; + +static git_repository *g_repo; + +void test_refs_create__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); + p_fsync__cnt = 0; +} + +void test_refs_create__cleanup(void) +{ + cl_git_sandbox_cleanup(); + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 1)); + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION, 1)); + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 0)); +} + +void test_refs_create__symbolic(void) +{ + /* create a new symbolic reference */ + git_reference *new_reference, *looked_up_ref, *resolved_ref; + git_repository *repo2; + git_oid id; + + const char *new_head_tracker = "ANOTHER_HEAD_TRACKER"; + + git_oid_fromstr(&id, current_master_tip); + + /* Create and write the new symbolic reference */ + cl_git_pass(git_reference_symbolic_create(&new_reference, g_repo, new_head_tracker, current_head_target, 0, NULL)); + + /* Ensure the reference can be looked-up... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head_tracker)); + cl_assert(git_reference_type(looked_up_ref) & GIT_REFERENCE_SYMBOLIC); + cl_assert(reference_is_packed(looked_up_ref) == 0); + cl_assert_equal_s(looked_up_ref->name, new_head_tracker); + + /* ...peeled.. */ + cl_git_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); + cl_assert(git_reference_type(resolved_ref) == GIT_REFERENCE_DIRECT); + + /* ...and that it points to the current master tip */ + cl_assert_equal_oid(&id, git_reference_target(resolved_ref)); + git_reference_free(looked_up_ref); + git_reference_free(resolved_ref); + + /* Similar test with a fresh new repository */ + cl_git_pass(git_repository_open(&repo2, "testrepo")); + + cl_git_pass(git_reference_lookup(&looked_up_ref, repo2, new_head_tracker)); + cl_git_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); + cl_assert_equal_oid(&id, git_reference_target(resolved_ref)); + + git_repository_free(repo2); + + git_reference_free(new_reference); + git_reference_free(looked_up_ref); + git_reference_free(resolved_ref); +} + +void test_refs_create__symbolic_with_arbitrary_content(void) +{ + git_reference *new_reference, *looked_up_ref; + git_repository *repo2; + git_oid id; + + const char *new_head_tracker = "ANOTHER_HEAD_TRACKER"; + const char *arbitrary_target = "ARBITRARY DATA"; + + git_oid_fromstr(&id, current_master_tip); + + /* Attempt to create symbolic ref with arbitrary data in target + * fails by default + */ + cl_git_fail(git_reference_symbolic_create(&new_reference, g_repo, new_head_tracker, arbitrary_target, 0, NULL)); + + git_libgit2_opts(GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION, 0); + + /* With strict target validation disabled, ref creation succeeds */ + cl_git_pass(git_reference_symbolic_create(&new_reference, g_repo, new_head_tracker, arbitrary_target, 0, NULL)); + + /* Ensure the reference can be looked-up... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head_tracker)); + cl_assert(git_reference_type(looked_up_ref) & GIT_REFERENCE_SYMBOLIC); + cl_assert(reference_is_packed(looked_up_ref) == 0); + cl_assert_equal_s(looked_up_ref->name, new_head_tracker); + git_reference_free(looked_up_ref); + + /* Ensure the target is what we expect it to be */ + cl_assert_equal_s(git_reference_symbolic_target(new_reference), arbitrary_target); + + /* Similar test with a fresh new repository object */ + cl_git_pass(git_repository_open(&repo2, "testrepo")); + + /* Ensure the reference can be looked-up... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, repo2, new_head_tracker)); + cl_assert(git_reference_type(looked_up_ref) & GIT_REFERENCE_SYMBOLIC); + cl_assert(reference_is_packed(looked_up_ref) == 0); + cl_assert_equal_s(looked_up_ref->name, new_head_tracker); + + /* Ensure the target is what we expect it to be */ + cl_assert_equal_s(git_reference_symbolic_target(new_reference), arbitrary_target); + + git_repository_free(repo2); + git_reference_free(new_reference); + git_reference_free(looked_up_ref); +} + +void test_refs_create__deep_symbolic(void) +{ + /* create a deep symbolic reference */ + git_reference *new_reference, *looked_up_ref, *resolved_ref; + git_oid id; + + const char *new_head_tracker = "deep/rooted/tracker"; + + git_oid_fromstr(&id, current_master_tip); + + cl_git_pass(git_reference_symbolic_create(&new_reference, g_repo, new_head_tracker, current_head_target, 0, NULL)); + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head_tracker)); + cl_git_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); + cl_assert_equal_oid(&id, git_reference_target(resolved_ref)); + + git_reference_free(new_reference); + git_reference_free(looked_up_ref); + git_reference_free(resolved_ref); +} + +void test_refs_create__oid(void) +{ + /* create a new OID reference */ + git_reference *new_reference, *looked_up_ref; + git_repository *repo2; + git_oid id; + + const char *new_head = "refs/heads/new-head"; + + git_oid_fromstr(&id, current_master_tip); + + /* Create and write the new object id reference */ + cl_git_pass(git_reference_create(&new_reference, g_repo, new_head, &id, 0, NULL)); + + /* Ensure the reference can be looked-up... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head)); + cl_assert(git_reference_type(looked_up_ref) & GIT_REFERENCE_DIRECT); + cl_assert(reference_is_packed(looked_up_ref) == 0); + cl_assert_equal_s(looked_up_ref->name, new_head); + + /* ...and that it points to the current master tip */ + cl_assert_equal_oid(&id, git_reference_target(looked_up_ref)); + git_reference_free(looked_up_ref); + + /* Similar test with a fresh new repository */ + cl_git_pass(git_repository_open(&repo2, "testrepo")); + + cl_git_pass(git_reference_lookup(&looked_up_ref, repo2, new_head)); + cl_assert_equal_oid(&id, git_reference_target(looked_up_ref)); + + git_repository_free(repo2); + + git_reference_free(new_reference); + git_reference_free(looked_up_ref); +} + +/* Can by default create a reference that targets at an unknown id */ +void test_refs_create__oid_unknown_succeeds_without_strict(void) +{ + git_reference *new_reference, *looked_up_ref; + git_oid id; + + const char *new_head = "refs/heads/new-head"; + + git_oid_fromstr(&id, "deadbeef3f795b2b4353bcce3a527ad0a4f7f644"); + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 0)); + + /* Create and write the new object id reference */ + cl_git_pass(git_reference_create(&new_reference, g_repo, new_head, &id, 0, NULL)); + git_reference_free(new_reference); + + /* Ensure the reference can't be looked-up... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head)); + git_reference_free(looked_up_ref); +} + +/* Strict object enforcement enforces valid object id */ +void test_refs_create__oid_unknown_fails_by_default(void) +{ + git_reference *new_reference, *looked_up_ref; + git_oid id; + + const char *new_head = "refs/heads/new-head"; + + git_oid_fromstr(&id, "deadbeef3f795b2b4353bcce3a527ad0a4f7f644"); + + /* Create and write the new object id reference */ + cl_git_fail(git_reference_create(&new_reference, g_repo, new_head, &id, 0, NULL)); + + /* Ensure the reference can't be looked-up... */ + cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, new_head)); +} + +void test_refs_create__propagate_eexists(void) +{ + git_oid oid; + + /* Make sure it works for oid and for symbolic both */ + cl_git_pass(git_oid_fromstr(&oid, current_master_tip)); + cl_git_fail_with(GIT_EEXISTS, git_reference_create(NULL, g_repo, current_head_target, &oid, false, NULL)); + cl_git_fail_with(GIT_EEXISTS, git_reference_symbolic_create(NULL, g_repo, "HEAD", current_head_target, false, NULL)); +} + +void test_refs_create__existing_dir_propagates_edirectory(void) +{ + git_reference *new_reference, *fail_reference; + git_oid id; + const char *dir_head = "refs/heads/new-dir/new-head", + *fail_head = "refs/heads/new-dir"; + + git_oid_fromstr(&id, current_master_tip); + + /* Create and write the new object id reference */ + cl_git_pass(git_reference_create(&new_reference, g_repo, dir_head, &id, 1, NULL)); + cl_git_fail_with(GIT_EDIRECTORY, + git_reference_create(&fail_reference, g_repo, fail_head, &id, false, NULL)); + + git_reference_free(new_reference); +} + +static void test_invalid_name(const char *name) +{ + git_reference *new_reference; + git_oid id; + + git_oid_fromstr(&id, current_master_tip); + + cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_create( + &new_reference, g_repo, name, &id, 0, NULL)); + + cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_symbolic_create( + &new_reference, g_repo, name, current_head_target, 0, NULL)); +} + +void test_refs_create__creating_a_reference_with_an_invalid_name_returns_EINVALIDSPEC(void) +{ + test_invalid_name("refs/heads/inv@{id"); + test_invalid_name("refs/heads/back\\slash"); + + test_invalid_name("refs/heads/foo "); + test_invalid_name("refs/heads/foo /bar"); + test_invalid_name("refs/heads/com1:bar/foo"); + + test_invalid_name("refs/heads/e:"); + test_invalid_name("refs/heads/c:/foo"); + + test_invalid_name("refs/heads/foo."); +} + +static void test_win32_name(const char *name) +{ + git_reference *new_reference = NULL; + git_oid id; + int ret; + + git_oid_fromstr(&id, current_master_tip); + + ret = git_reference_create(&new_reference, g_repo, name, &id, 0, NULL); + +#ifdef GIT_WIN32 + cl_assert_equal_i(GIT_EINVALIDSPEC, ret); +#else + cl_git_pass(ret); +#endif + + git_reference_free(new_reference); +} + +void test_refs_create__creating_a_loose_ref_with_invalid_windows_name(void) +{ + test_win32_name("refs/heads/foo./bar"); + + test_win32_name("refs/heads/aux"); + test_win32_name("refs/heads/aux.foo/bar"); + + test_win32_name("refs/heads/com1"); +} + +/* Creating a loose ref involves fsync'ing the reference, the + * reflog and (on non-Windows) the containing directories. + * Creating a packed ref involves fsync'ing the packed ref file + * and (on non-Windows) the containing directory. + */ +#ifdef GIT_WIN32 +static int expected_fsyncs_create = 2, expected_fsyncs_compress = 1; +#else +static int expected_fsyncs_create = 4, expected_fsyncs_compress = 2; +#endif + +static void count_fsyncs(size_t *create_count, size_t *compress_count) +{ + git_reference *ref = NULL; + git_refdb *refdb; + git_oid id; + + p_fsync__cnt = 0; + + git_oid_fromstr(&id, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/fsync_test", &id, 0, "log message")); + git_reference_free(ref); + + *create_count = p_fsync__cnt; + p_fsync__cnt = 0; + + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); + git_refdb_free(refdb); + + *compress_count = p_fsync__cnt; + p_fsync__cnt = 0; +} + +void test_refs_create__does_not_fsync_by_default(void) +{ + size_t create_count, compress_count; + count_fsyncs(&create_count, &compress_count); + + cl_assert_equal_i(0, create_count); + cl_assert_equal_i(0, compress_count); +} + +void test_refs_create__fsyncs_when_global_opt_set(void) +{ + size_t create_count, compress_count; + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 1)); + count_fsyncs(&create_count, &compress_count); + + cl_assert_equal_i(expected_fsyncs_create, create_count); + cl_assert_equal_i(expected_fsyncs_compress, compress_count); +} + +void test_refs_create__fsyncs_when_repo_config_set(void) +{ + size_t create_count, compress_count; + + cl_repo_set_bool(g_repo, "core.fsyncObjectFiles", true); + + count_fsyncs(&create_count, &compress_count); + + cl_assert_equal_i(expected_fsyncs_create, create_count); + cl_assert_equal_i(expected_fsyncs_compress, compress_count); +} diff --git a/tests/libgit2/refs/delete.c b/tests/libgit2/refs/delete.c new file mode 100644 index 000000000..42cc534b5 --- /dev/null +++ b/tests/libgit2/refs/delete.c @@ -0,0 +1,118 @@ +#include "clar_libgit2.h" + +#include "futils.h" +#include "git2/reflog.h" +#include "git2/refdb.h" +#include "reflog.h" +#include "ref_helpers.h" + +static const char *packed_test_head_name = "refs/heads/packed-test"; +static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; + +static git_repository *g_repo; + + + +void test_refs_delete__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_delete__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + + +void test_refs_delete__packed_loose(void) +{ + /* deleting a ref which is both packed and loose should remove both tracks in the filesystem */ + git_reference *looked_up_ref, *another_looked_up_ref; + git_str temp_path = GIT_STR_INIT; + + /* Ensure the loose reference exists on the file system */ + cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), packed_test_head_name)); + cl_assert(git_fs_path_exists(temp_path.ptr)); + + /* Lookup the reference */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); + + /* Ensure it's the loose version that has been found */ + cl_assert(reference_is_packed(looked_up_ref) == 0); + + /* Now that the reference is deleted... */ + cl_git_pass(git_reference_delete(looked_up_ref)); + git_reference_free(looked_up_ref); + + /* Looking up the reference once again should not retrieve it */ + cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name)); + + /* Ensure the loose reference doesn't exist any longer on the file system */ + cl_assert(!git_fs_path_exists(temp_path.ptr)); + + git_reference_free(another_looked_up_ref); + git_str_dispose(&temp_path); +} + +void test_refs_delete__packed_only(void) +{ + /* can delete a just packed reference */ + git_reference *ref; + git_refdb *refdb; + git_oid id; + const char *new_ref = "refs/heads/new_ref"; + + git_oid_fromstr(&id, current_master_tip); + + /* Create and write the new object id reference */ + cl_git_pass(git_reference_create(&ref, g_repo, new_ref, &id, 0, NULL)); + git_reference_free(ref); + + /* Lookup the reference */ + cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref)); + + /* Ensure it's a loose reference */ + cl_assert(reference_is_packed(ref) == 0); + + /* Pack all existing references */ + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); + + /* Reload the reference from disk */ + git_reference_free(ref); + cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref)); + + /* Ensure it's a packed reference */ + cl_assert(reference_is_packed(ref) == 1); + + /* This should pass */ + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + git_refdb_free(refdb); +} + +void test_refs_delete__remove(void) +{ + git_reference *ref; + + /* Check that passing no old values lets us delete */ + + cl_git_pass(git_reference_lookup(&ref, g_repo, packed_test_head_name)); + git_reference_free(ref); + + cl_git_pass(git_reference_remove(g_repo, packed_test_head_name)); + + cl_git_fail(git_reference_lookup(&ref, g_repo, packed_test_head_name)); +} + +void test_refs_delete__head(void) +{ + git_reference *ref; + + /* Check that it is not possible to delete HEAD */ + + cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); + cl_git_fail(git_reference_delete(ref)); + git_reference_free(ref); +} diff --git a/tests/libgit2/refs/dup.c b/tests/libgit2/refs/dup.c new file mode 100644 index 000000000..8a89cd95c --- /dev/null +++ b/tests/libgit2/refs/dup.c @@ -0,0 +1,42 @@ +#include "clar_libgit2.h" +#include "refs.h" + +static git_repository *g_repo; + +void test_refs_dup__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_refs_dup__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_dup__direct(void) +{ + git_reference *a, *b; + + cl_git_pass(git_reference_lookup(&a, g_repo, "refs/heads/master")); + cl_git_pass(git_reference_dup(&b, a)); + + cl_assert(git_reference_cmp(a, b) == 0); + cl_assert(git_reference_owner(b) == g_repo); + + git_reference_free(b); + git_reference_free(a); +} + +void test_refs_dup__symbolic(void) +{ + git_reference *a, *b; + + cl_git_pass(git_reference_lookup(&a, g_repo, "HEAD")); + cl_git_pass(git_reference_dup(&b, a)); + + cl_assert(git_reference_cmp(a, b) == 0); + cl_assert(git_reference_owner(b) == g_repo); + + git_reference_free(b); + git_reference_free(a); +} diff --git a/tests/libgit2/refs/foreachglob.c b/tests/libgit2/refs/foreachglob.c new file mode 100644 index 000000000..b208a95a2 --- /dev/null +++ b/tests/libgit2/refs/foreachglob.c @@ -0,0 +1,100 @@ +#include "clar_libgit2.h" +#include "refs.h" + +static git_repository *repo; +static git_reference *fake_remote; + +void test_refs_foreachglob__initialize(void) +{ + git_oid id; + + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&repo, "testrepo.git")); + + cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_reference_create(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0, NULL)); +} + +void test_refs_foreachglob__cleanup(void) +{ + git_reference_free(fake_remote); + fake_remote = NULL; + + git_repository_free(repo); + repo = NULL; + + cl_fixture_cleanup("testrepo.git"); +} + +static int count_cb(const char *reference_name, void *payload) +{ + int *count = (int *)payload; + + GIT_UNUSED(reference_name); + + (*count)++; + + return 0; +} + +static void assert_retrieval(const char *glob, int expected_count) +{ + int count = 0; + + cl_git_pass(git_reference_foreach_glob(repo, glob, count_cb, &count)); + + cl_assert_equal_i(expected_count, count); +} + +void test_refs_foreachglob__retrieve_all_refs(void) +{ + /* 13 heads (including one packed head) + 1 note + 2 remotes + 7 tags + 1 blob */ + assert_retrieval("*", 24); +} + +void test_refs_foreachglob__retrieve_remote_branches(void) +{ + assert_retrieval("refs/remotes/*", 2); +} + +void test_refs_foreachglob__retrieve_local_branches(void) +{ + assert_retrieval("refs/heads/*", 13); +} + +void test_refs_foreachglob__retrieve_nonexistant(void) +{ + assert_retrieval("refs/nonexistent/*", 0); +} + +void test_refs_foreachglob__retrieve_partially_named_references(void) +{ + /* + * refs/heads/packed-test, refs/heads/test + * refs/remotes/test/master, refs/tags/test + */ + + assert_retrieval("*test*", 4); +} + + +static int interrupt_cb(const char *reference_name, void *payload) +{ + int *count = (int *)payload; + + GIT_UNUSED(reference_name); + + (*count)++; + + return (*count == 11) ? -1000 : 0; +} + +void test_refs_foreachglob__can_cancel(void) +{ + int count = 0; + + cl_assert_equal_i(-1000, git_reference_foreach_glob( + repo, "*", interrupt_cb, &count) ); + + cl_assert_equal_i(11, count); +} diff --git a/tests/libgit2/refs/isvalidname.c b/tests/libgit2/refs/isvalidname.c new file mode 100644 index 000000000..063f0f798 --- /dev/null +++ b/tests/libgit2/refs/isvalidname.c @@ -0,0 +1,38 @@ +#include "clar_libgit2.h" + +static bool is_valid_name(const char *name) +{ + int valid; + cl_git_pass(git_reference_name_is_valid(&valid, name)); + return valid; +} + +void test_refs_isvalidname__can_detect_invalid_formats(void) +{ + cl_assert_equal_i(false, is_valid_name("refs/tags/0.17.0^{}")); + cl_assert_equal_i(false, is_valid_name("TWO/LEVELS")); + cl_assert_equal_i(false, is_valid_name("ONE.LEVEL")); + cl_assert_equal_i(false, is_valid_name("HEAD/")); + cl_assert_equal_i(false, is_valid_name("NO_TRAILING_UNDERSCORE_")); + cl_assert_equal_i(false, is_valid_name("_NO_LEADING_UNDERSCORE")); + cl_assert_equal_i(false, is_valid_name("HEAD/aa")); + cl_assert_equal_i(false, is_valid_name("lower_case")); + cl_assert_equal_i(false, is_valid_name("/stupid/name/master")); + cl_assert_equal_i(false, is_valid_name("/")); + cl_assert_equal_i(false, is_valid_name("//")); + cl_assert_equal_i(false, is_valid_name("")); + cl_assert_equal_i(false, is_valid_name("refs/heads/sub.lock/webmatrix")); +} + +void test_refs_isvalidname__wont_hopefully_choke_on_valid_formats(void) +{ + cl_assert_equal_i(true, is_valid_name("refs/tags/0.17.0")); + cl_assert_equal_i(true, is_valid_name("refs/LEVELS")); + cl_assert_equal_i(true, is_valid_name("HEAD")); + cl_assert_equal_i(true, is_valid_name("ONE_LEVEL")); + cl_assert_equal_i(true, is_valid_name("refs/stash")); + cl_assert_equal_i(true, is_valid_name("refs/remotes/origin/bim_with_3d@11296")); + cl_assert_equal_i(true, is_valid_name("refs/master{yesterday")); + cl_assert_equal_i(true, is_valid_name("refs/master}yesterday")); + cl_assert_equal_i(true, is_valid_name("refs/master{yesterday}")); +} diff --git a/tests/libgit2/refs/iterator.c b/tests/libgit2/refs/iterator.c new file mode 100644 index 000000000..a4f9e62ec --- /dev/null +++ b/tests/libgit2/refs/iterator.c @@ -0,0 +1,274 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "vector.h" + +static git_repository *repo; + +void test_refs_iterator__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_refs_iterator__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static const char *refnames[] = { + "refs/blobs/annotated_tag_to_blob", + "refs/heads/br2", + "refs/heads/cannot-fetch", + "refs/heads/chomped", + "refs/heads/haacked", + "refs/heads/master", + "refs/heads/not-good", + "refs/heads/packed", + "refs/heads/packed-test", + "refs/heads/subtrees", + "refs/heads/test", + "refs/heads/track-local", + "refs/heads/trailing", + "refs/heads/with-empty-log", + "refs/notes/fanout", + "refs/remotes/test/master", + "refs/tags/annotated_tag_to_blob", + "refs/tags/e90810b", + "refs/tags/hard_tag", + "refs/tags/point_to_blob", + "refs/tags/taggerless", + "refs/tags/test", + "refs/tags/wrapped_tag", + NULL +}; + +static const char *refnames_with_symlink[] = { + "refs/blobs/annotated_tag_to_blob", + "refs/heads/br2", + "refs/heads/cannot-fetch", + "refs/heads/chomped", + "refs/heads/haacked", + "refs/heads/link/a", + "refs/heads/link/b", + "refs/heads/link/c", + "refs/heads/link/d", + "refs/heads/master", + "refs/heads/not-good", + "refs/heads/packed", + "refs/heads/packed-test", + "refs/heads/subtrees", + "refs/heads/test", + "refs/heads/track-local", + "refs/heads/trailing", + "refs/heads/with-empty-log", + "refs/notes/fanout", + "refs/remotes/test/master", + "refs/tags/annotated_tag_to_blob", + "refs/tags/e90810b", + "refs/tags/hard_tag", + "refs/tags/point_to_blob", + "refs/tags/taggerless", + "refs/tags/test", + "refs/tags/wrapped_tag", + NULL +}; + +static int refcmp_cb(const void *a, const void *b) +{ + const git_reference *refa = (const git_reference *)a; + const git_reference *refb = (const git_reference *)b; + + return strcmp(refa->name, refb->name); +} + +static void assert_all_refnames_match(const char **expected, git_vector *names) +{ + size_t i; + git_reference *ref; + + git_vector_sort(names); + + git_vector_foreach(names, i, ref) { + cl_assert(expected[i] != NULL); + cl_assert_equal_s(expected[i], ref->name); + git_reference_free(ref); + } + cl_assert(expected[i] == NULL); + + git_vector_free(names); +} + +void test_refs_iterator__list(void) +{ + git_reference_iterator *iter; + git_vector output; + git_reference *ref; + + cl_git_pass(git_vector_init(&output, 33, &refcmp_cb)); + cl_git_pass(git_reference_iterator_new(&iter, repo)); + + while (1) { + int error = git_reference_next(&ref, iter); + if (error == GIT_ITEROVER) + break; + cl_git_pass(error); + cl_git_pass(git_vector_insert(&output, ref)); + } + + git_reference_iterator_free(iter); + + assert_all_refnames_match(refnames, &output); +} + +void test_refs_iterator__empty(void) +{ + git_reference_iterator *iter; + git_odb *odb; + git_reference *ref; + git_repository *empty; + + cl_git_pass(git_odb_new(&odb)); + cl_git_pass(git_repository_wrap_odb(&empty, odb)); + + cl_git_pass(git_reference_iterator_new(&iter, empty)); + cl_assert_equal_i(GIT_ITEROVER, git_reference_next(&ref, iter)); + + git_reference_iterator_free(iter); + git_odb_free(odb); + git_repository_free(empty); +} + +static int refs_foreach_cb(git_reference *reference, void *payload) +{ + git_vector *output = payload; + cl_git_pass(git_vector_insert(output, reference)); + return 0; +} + +void test_refs_iterator__foreach(void) +{ + git_vector output; + cl_git_pass(git_vector_init(&output, 33, &refcmp_cb)); + cl_git_pass(git_reference_foreach(repo, refs_foreach_cb, &output)); + assert_all_refnames_match(refnames, &output); +} + +void test_refs_iterator__foreach_through_symlink(void) +{ + git_vector output; + +#ifdef GIT_WIN32 + cl_skip(); +#endif + + cl_git_pass(git_vector_init(&output, 32, &refcmp_cb)); + + cl_git_pass(p_mkdir("refs", 0777)); + cl_git_mkfile("refs/a", "1234567890123456789012345678901234567890"); + cl_git_mkfile("refs/b", "1234567890123456789012345678901234567890"); + cl_git_mkfile("refs/c", "1234567890123456789012345678901234567890"); + cl_git_mkfile("refs/d", "1234567890123456789012345678901234567890"); + + cl_git_pass(p_symlink("../../../refs", "testrepo.git/refs/heads/link")); + + cl_git_pass(git_reference_foreach(repo, refs_foreach_cb, &output)); + assert_all_refnames_match(refnames_with_symlink, &output); +} + +static int refs_foreach_cancel_cb(git_reference *reference, void *payload) +{ + int *cancel_after = payload; + + git_reference_free(reference); + + if (!*cancel_after) + return -333; + (*cancel_after)--; + return 0; +} + +void test_refs_iterator__foreach_can_cancel(void) +{ + int cancel_after = 3; + cl_git_fail_with( + git_reference_foreach(repo, refs_foreach_cancel_cb, &cancel_after), + -333); + cl_assert_equal_i(0, cancel_after); +} + +static int refs_foreach_name_cb(const char *name, void *payload) +{ + git_vector *output = payload; + cl_git_pass(git_vector_insert(output, git__strdup(name))); + return 0; +} + +void test_refs_iterator__foreach_name(void) +{ + git_vector output; + size_t i; + char *name; + + cl_git_pass(git_vector_init(&output, 32, &git__strcmp_cb)); + cl_git_pass( + git_reference_foreach_name(repo, refs_foreach_name_cb, &output)); + + git_vector_sort(&output); + + git_vector_foreach(&output, i, name) { + cl_assert(refnames[i] != NULL); + cl_assert_equal_s(refnames[i], name); + git__free(name); + } + + git_vector_free(&output); +} + +static int refs_foreach_name_cancel_cb(const char *name, void *payload) +{ + int *cancel_after = payload; + if (!*cancel_after) + return -333; + GIT_UNUSED(name); + (*cancel_after)--; + return 0; +} + +void test_refs_iterator__foreach_name_can_cancel(void) +{ + int cancel_after = 5; + cl_git_fail_with( + git_reference_foreach_name( + repo, refs_foreach_name_cancel_cb, &cancel_after), + -333); + cl_assert_equal_i(0, cancel_after); +} + +void test_refs_iterator__concurrent_delete(void) +{ + git_reference_iterator *iter; + size_t full_count = 0, concurrent_count = 0; + const char *name; + int error; + + cl_git_sandbox_cleanup(); + repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass(git_reference_iterator_new(&iter, repo)); + while ((error = git_reference_next_name(&name, iter)) == 0) { + full_count++; + } + + git_reference_iterator_free(iter); + cl_assert_equal_i(GIT_ITEROVER, error); + + cl_git_pass(git_reference_iterator_new(&iter, repo)); + while ((error = git_reference_next_name(&name, iter)) == 0) { + cl_git_pass(git_reference_remove(repo, name)); + concurrent_count++; + } + + git_reference_iterator_free(iter); + cl_assert_equal_i(GIT_ITEROVER, error); + + cl_assert_equal_i(full_count, concurrent_count); +} diff --git a/tests/libgit2/refs/list.c b/tests/libgit2/refs/list.c new file mode 100644 index 000000000..8085ff84b --- /dev/null +++ b/tests/libgit2/refs/list.c @@ -0,0 +1,57 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" + +static git_repository *g_repo; + + + +void test_refs_list__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_list__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + + +void test_refs_list__all(void) +{ + /* try to list all the references in our test repo */ + git_strarray ref_list; + + cl_git_pass(git_reference_list(&ref_list, g_repo)); + + /*{ + unsigned short i; + for (i = 0; i < ref_list.count; ++i) + printf("# %s\n", ref_list.strings[i]); + }*/ + + /* We have exactly 12 refs in total if we include the packed ones: + * there is a reference that exists both in the packfile and as + * loose, but we only list it once */ + cl_assert_equal_i((int)ref_list.count, 19); + + git_strarray_dispose(&ref_list); +} + +void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_extension(void) +{ + git_strarray ref_list; + + /* Create a fake locked reference */ + cl_git_mkfile( + "./testrepo/.git/refs/heads/hanwen.lock", + "144344043ba4d4a405da03de3844aa829ae8be0e\n"); + + cl_git_pass(git_reference_list(&ref_list, g_repo)); + cl_assert_equal_i((int)ref_list.count, 19); + + git_strarray_dispose(&ref_list); +} diff --git a/tests/libgit2/refs/listall.c b/tests/libgit2/refs/listall.c new file mode 100644 index 000000000..9da8d1ac3 --- /dev/null +++ b/tests/libgit2/refs/listall.c @@ -0,0 +1,47 @@ +#include "clar_libgit2.h" +#include "posix.h" + +static git_repository *repo; +static git_strarray ref_list; + +static void ensure_no_refname_starts_with_a_forward_slash(const char *path) +{ + size_t i; + + cl_git_pass(git_repository_open(&repo, path)); + cl_git_pass(git_reference_list(&ref_list, repo)); + + cl_assert(ref_list.count > 0); + + for (i = 0; i < ref_list.count; i++) + cl_assert(git__prefixcmp(ref_list.strings[i], "/") != 0); + + git_strarray_dispose(&ref_list); + git_repository_free(repo); +} + +void test_refs_listall__from_repository_opened_through_workdir_path(void) +{ + cl_fixture_sandbox("status"); + cl_git_pass(p_rename("status/.gitted", "status/.git")); + + ensure_no_refname_starts_with_a_forward_slash("status"); + + cl_fixture_cleanup("status"); +} + +void test_refs_listall__from_repository_opened_through_gitdir_path(void) +{ + ensure_no_refname_starts_with_a_forward_slash(cl_fixture("testrepo.git")); +} + +void test_refs_listall__from_repository_with_no_trailing_newline(void) +{ + cl_git_pass(git_repository_open(&repo, cl_fixture("bad_tag.git"))); + cl_git_pass(git_reference_list(&ref_list, repo)); + + cl_assert(ref_list.count > 0); + + git_strarray_dispose(&ref_list); + git_repository_free(repo); +} diff --git a/tests/libgit2/refs/lookup.c b/tests/libgit2/refs/lookup.c new file mode 100644 index 000000000..01e956de2 --- /dev/null +++ b/tests/libgit2/refs/lookup.c @@ -0,0 +1,68 @@ +#include "clar_libgit2.h" +#include "refs.h" + +static git_repository *g_repo; + +void test_refs_lookup__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_refs_lookup__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_lookup__with_resolve(void) +{ + git_reference *a, *b, *temp; + + cl_git_pass(git_reference_lookup(&temp, g_repo, "HEAD")); + cl_git_pass(git_reference_resolve(&a, temp)); + git_reference_free(temp); + + cl_git_pass(git_reference_lookup_resolved(&b, g_repo, "HEAD", 5)); + cl_assert(git_reference_cmp(a, b) == 0); + git_reference_free(b); + + cl_git_pass(git_reference_lookup_resolved(&b, g_repo, "HEAD_TRACKER", 5)); + cl_assert(git_reference_cmp(a, b) == 0); + git_reference_free(b); + + git_reference_free(a); +} + +void test_refs_lookup__invalid_name(void) +{ + git_oid oid; + cl_git_fail(git_reference_name_to_id(&oid, g_repo, "/refs/tags/point_to_blob")); +} + +void test_refs_lookup__oid(void) +{ + git_oid tag, expected; + + cl_git_pass(git_reference_name_to_id(&tag, g_repo, "refs/tags/point_to_blob")); + cl_git_pass(git_oid_fromstr(&expected, "1385f264afb75a56a5bec74243be9b367ba4ca08")); + cl_assert_equal_oid(&expected, &tag); +} + +void test_refs_lookup__namespace(void) +{ + int error; + git_reference *ref; + + error = git_reference_lookup(&ref, g_repo, "refs/heads"); + cl_assert_equal_i(error, GIT_ENOTFOUND); + + error = git_reference_lookup(&ref, g_repo, "refs/heads/"); + cl_assert_equal_i(error, GIT_EINVALIDSPEC); +} + +void test_refs_lookup__dwim_notfound(void) +{ + git_reference *ref; + + cl_git_fail_with(GIT_ENOTFOUND, git_reference_dwim(&ref, g_repo, "idontexist")); + cl_assert_equal_s("no reference found for shorthand 'idontexist'", git_error_last()->message); +} diff --git a/tests/libgit2/refs/namespaces.c b/tests/libgit2/refs/namespaces.c new file mode 100644 index 000000000..19456b5a4 --- /dev/null +++ b/tests/libgit2/refs/namespaces.c @@ -0,0 +1,36 @@ +#include "clar_libgit2.h" + +#include "repository.h" + +static git_repository *g_repo; + +void test_refs_namespaces__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_namespaces__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_namespaces__get_and_set(void) +{ + cl_assert_equal_s(NULL, git_repository_get_namespace(g_repo)); + + cl_git_pass(git_repository_set_namespace(g_repo, "namespace")); + cl_assert_equal_s("namespace", git_repository_get_namespace(g_repo)); + + cl_git_pass(git_repository_set_namespace(g_repo, NULL)); + cl_assert_equal_s(NULL, git_repository_get_namespace(g_repo)); +} + +void test_refs_namespaces__namespace_doesnt_show_normal_refs(void) +{ + static git_strarray ref_list; + + cl_git_pass(git_repository_set_namespace(g_repo, "namespace")); + cl_git_pass(git_reference_list(&ref_list, g_repo)); + cl_assert_equal_i(0, ref_list.count); + git_strarray_dispose(&ref_list); +} diff --git a/tests/libgit2/refs/normalize.c b/tests/libgit2/refs/normalize.c new file mode 100644 index 000000000..ff815002c --- /dev/null +++ b/tests/libgit2/refs/normalize.c @@ -0,0 +1,401 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" + +/* Helpers */ +static void ensure_refname_normalized( + unsigned int flags, + const char *input_refname, + const char *expected_refname) +{ + char buffer_out[GIT_REFNAME_MAX]; + + cl_git_pass(git_reference_normalize_name(buffer_out, sizeof(buffer_out), input_refname, flags)); + + cl_assert_equal_s(expected_refname, buffer_out); +} + +static void ensure_refname_invalid(unsigned int flags, const char *input_refname) +{ + char buffer_out[GIT_REFNAME_MAX]; + + cl_assert_equal_i( + GIT_EINVALIDSPEC, + git_reference_normalize_name(buffer_out, sizeof(buffer_out), input_refname, flags)); +} + +void test_refs_normalize__can_normalize_a_direct_reference_name(void) +{ + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_NORMAL, "refs/dummy/a", "refs/dummy/a"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_NORMAL, "refs/stash", "refs/stash"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_NORMAL, "refs/tags/a", "refs/tags/a"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/a/b", "refs/heads/a/b"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/a./b", "refs/heads/a./b"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/v@ation", "refs/heads/v@ation"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_NORMAL, "refs///heads///a", "refs/heads/a"); +} + +void test_refs_normalize__cannot_normalize_any_direct_reference_name(void) +{ + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "a"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "/a"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "//a"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, ""); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "/refs/heads/a/"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/a/"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/a."); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/a.lock"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/foo?bar"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads\foo"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/v@ation", "refs/heads/v@ation"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_NORMAL, "refs///heads///a", "refs/heads/a"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/.a/b"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/foo/../bar"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/foo..bar"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/./foo"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/v@{ation"); +} + +void test_refs_normalize__symbolic(void) +{ + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, ""); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "heads\foo"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "/"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "///"); + + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "ALL_CAPS_AND_UNDERSCORES", "ALL_CAPS_AND_UNDERSCORES"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/MixedCasing", "refs/MixedCasing"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs///heads///a", "refs/heads/a"); + + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "HEAD", "HEAD"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "MERGE_HEAD", "MERGE_HEAD"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "FETCH_HEAD", "FETCH_HEAD"); +} + +/* Ported from JGit, BSD licence. + * See https://github.com/spearce/JGit/commit/e4bf8f6957bbb29362575d641d1e77a02d906739 + * + * Copyright (C) 2009, Google Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Git Development Community nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +void test_refs_normalize__jgit_suite(void) +{ + /* tests borrowed from JGit */ + +/* EmptyString */ + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, ""); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "/"); + +/* MustHaveTwoComponents */ + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_NORMAL, "master"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_NORMAL, "heads/master", "heads/master"); + +/* ValidHead */ + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master", "refs/heads/master"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/pu", "refs/heads/pu"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/z", "refs/heads/z"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/FoO", "refs/heads/FoO"); + +/* ValidTag */ + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/tags/v1.0", "refs/tags/v1.0"); + +/* NoLockSuffix */ + ensure_refname_invalid(GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master.lock"); + +/* NoDirectorySuffix */ + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master/"); + +/* NoSpace */ + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/i haz space"); + +/* NoAsciiControlCharacters */ + { + char c; + char buffer[GIT_REFNAME_MAX]; + for (c = '\1'; c < ' '; c++) { + p_snprintf(buffer, sizeof(buffer), "refs/heads/mast%cer", c); + ensure_refname_invalid(GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, buffer); + } + } + +/* NoBareDot */ + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/."); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/.."); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/./master"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/../master"); + +/* NoLeadingOrTrailingDot */ + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "."); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/.bar"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/..bar"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/bar."); + +/* ContainsDot */ + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/m.a.s.t.e.r", "refs/heads/m.a.s.t.e.r"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master..pu"); + +/* NoMagicRefCharacters */ + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master^"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/^master"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "^refs/heads/master"); + + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master~"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/~master"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "~refs/heads/master"); + + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master:"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/:master"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, ":refs/heads/master"); + +/* ShellGlob */ + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master?"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/?master"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "?refs/heads/master"); + + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master["); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/[master"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "[refs/heads/master"); + + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master*"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/*master"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "*refs/heads/master"); + +/* ValidSpecialCharacters */ + ensure_refname_normalized + (GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/!", "refs/heads/!"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/\"", "refs/heads/\""); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/#", "refs/heads/#"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/$", "refs/heads/$"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/%", "refs/heads/%"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/&", "refs/heads/&"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/'", "refs/heads/'"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/(", "refs/heads/("); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/)", "refs/heads/)"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/+", "refs/heads/+"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/,", "refs/heads/,"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/-", "refs/heads/-"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/;", "refs/heads/;"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/<", "refs/heads/<"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/=", "refs/heads/="); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/>", "refs/heads/>"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/@", "refs/heads/@"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/]", "refs/heads/]"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/_", "refs/heads/_"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/`", "refs/heads/`"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/{", "refs/heads/{"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/|", "refs/heads/|"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/}", "refs/heads/}"); + + /* + * This is valid on UNIX, but not on Windows + * hence we make in invalid due to non-portability + */ + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/\\"); + +/* UnicodeNames */ + /* + * Currently this fails. + * ensure_refname_normalized(GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/\u00e5ngstr\u00f6m", "refs/heads/\u00e5ngstr\u00f6m"); + */ + +/* RefLogQueryIsValidRef */ + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master@{1}"); + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master@{1.hour.ago}"); +} + +void test_refs_normalize__buffer_has_to_be_big_enough_to_hold_the_normalized_version(void) +{ + char buffer_out[21]; + + cl_git_pass(git_reference_normalize_name( + buffer_out, 21, "refs//heads///long///name", GIT_REFERENCE_FORMAT_NORMAL)); + cl_git_fail(git_reference_normalize_name( + buffer_out, 20, "refs//heads///long///name", GIT_REFERENCE_FORMAT_NORMAL)); +} + +#define ONE_LEVEL_AND_REFSPEC \ + GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL \ + | GIT_REFERENCE_FORMAT_REFSPEC_PATTERN + +void test_refs_normalize__refspec_pattern(void) +{ + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "heads/*foo/bar", "heads/*foo/bar"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "heads/foo*/bar", "heads/foo*/bar"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "heads/f*o/bar", "heads/f*o/bar"); + + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "foo"); + ensure_refname_normalized( + ONE_LEVEL_AND_REFSPEC, "FOO", "FOO"); + + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "foo/bar", "foo/bar"); + ensure_refname_normalized( + ONE_LEVEL_AND_REFSPEC, "foo/bar", "foo/bar"); + + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "*/foo", "*/foo"); + ensure_refname_normalized( + ONE_LEVEL_AND_REFSPEC, "*/foo", "*/foo"); + + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "foo/*/bar", "foo/*/bar"); + ensure_refname_normalized( + ONE_LEVEL_AND_REFSPEC, "foo/*/bar", "foo/*/bar"); + + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "*"); + ensure_refname_normalized( + ONE_LEVEL_AND_REFSPEC, "*", "*"); + + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "foo/*/*"); + ensure_refname_invalid( + ONE_LEVEL_AND_REFSPEC, "foo/*/*"); + + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "*/foo/*"); + ensure_refname_invalid( + ONE_LEVEL_AND_REFSPEC, "*/foo/*"); + + ensure_refname_invalid( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "*/*/foo"); + ensure_refname_invalid( + ONE_LEVEL_AND_REFSPEC, "*/*/foo"); +} diff --git a/tests/libgit2/refs/overwrite.c b/tests/libgit2/refs/overwrite.c new file mode 100644 index 000000000..1826b1257 --- /dev/null +++ b/tests/libgit2/refs/overwrite.c @@ -0,0 +1,136 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" + +static const char *ref_name = "refs/heads/other"; +static const char *ref_master_name = "refs/heads/master"; +static const char *ref_branch_name = "refs/heads/branch"; +static const char *ref_test_name = "refs/heads/test"; + +static git_repository *g_repo; + +void test_refs_overwrite__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_overwrite__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_overwrite__symbolic(void) +{ + /* Overwrite an existing symbolic reference */ + git_reference *ref, *branch_ref; + + /* The target needds to exist and we need to check the name has changed */ + cl_git_pass(git_reference_symbolic_create(&branch_ref, g_repo, ref_branch_name, ref_master_name, 0, NULL)); + cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_branch_name, 0, NULL)); + git_reference_free(ref); + + /* Ensure it points to the right place*/ + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + cl_assert(git_reference_type(ref) & GIT_REFERENCE_SYMBOLIC); + cl_assert_equal_s(git_reference_symbolic_target(ref), ref_branch_name); + git_reference_free(ref); + + /* Ensure we can't create it unless we force it to */ + cl_git_fail(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 0, NULL)); + cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 1, NULL)); + git_reference_free(ref); + + /* Ensure it points to the right place */ + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + cl_assert(git_reference_type(ref) & GIT_REFERENCE_SYMBOLIC); + cl_assert_equal_s(git_reference_symbolic_target(ref), ref_master_name); + + git_reference_free(ref); + git_reference_free(branch_ref); +} + +void test_refs_overwrite__object_id(void) +{ + /* Overwrite an existing object id reference */ + git_reference *ref; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); + git_oid_cpy(&id, git_reference_target(ref)); + git_reference_free(ref); + + /* Create it */ + cl_git_pass(git_reference_create(&ref, g_repo, ref_name, &id, 0, NULL)); + git_reference_free(ref); + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_test_name)); + cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); + git_oid_cpy(&id, git_reference_target(ref)); + git_reference_free(ref); + + /* Ensure we can't overwrite unless we force it */ + cl_git_fail(git_reference_create(&ref, g_repo, ref_name, &id, 0, NULL)); + cl_git_pass(git_reference_create(&ref, g_repo, ref_name, &id, 1, NULL)); + git_reference_free(ref); + + /* Ensure it has been overwritten */ + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + cl_assert_equal_oid(&id, git_reference_target(ref)); + + git_reference_free(ref); +} + +void test_refs_overwrite__object_id_with_symbolic(void) +{ + /* Overwrite an existing object id reference with a symbolic one */ + git_reference *ref; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); + git_oid_cpy(&id, git_reference_target(ref)); + git_reference_free(ref); + + cl_git_pass(git_reference_create(&ref, g_repo, ref_name, &id, 0, NULL)); + git_reference_free(ref); + cl_git_fail(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 0, NULL)); + cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 1, NULL)); + git_reference_free(ref); + + /* Ensure it points to the right place */ + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + cl_assert(git_reference_type(ref) & GIT_REFERENCE_SYMBOLIC); + cl_assert_equal_s(git_reference_symbolic_target(ref), ref_master_name); + + git_reference_free(ref); +} + +void test_refs_overwrite__symbolic_with_object_id(void) +{ + /* Overwrite an existing symbolic reference with an object id one */ + git_reference *ref; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); + git_oid_cpy(&id, git_reference_target(ref)); + git_reference_free(ref); + + /* Create the symbolic ref */ + cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 0, NULL)); + git_reference_free(ref); + /* It shouldn't overwrite unless we tell it to */ + cl_git_fail(git_reference_create(&ref, g_repo, ref_name, &id, 0, NULL)); + cl_git_pass(git_reference_create(&ref, g_repo, ref_name, &id, 1, NULL)); + git_reference_free(ref); + + /* Ensure it points to the right place */ + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); + cl_assert_equal_oid(&id, git_reference_target(ref)); + + git_reference_free(ref); +} diff --git a/tests/libgit2/refs/pack.c b/tests/libgit2/refs/pack.c new file mode 100644 index 000000000..1c1cd51cb --- /dev/null +++ b/tests/libgit2/refs/pack.c @@ -0,0 +1,105 @@ +#include "clar_libgit2.h" + +#include "futils.h" +#include "git2/reflog.h" +#include "git2/refdb.h" +#include "reflog.h" +#include "refs.h" +#include "ref_helpers.h" + +static const char *loose_tag_ref_name = "refs/tags/e90810b"; + +static git_repository *g_repo; + +void test_refs_pack__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_pack__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void packall(void) +{ + git_refdb *refdb; + + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); + git_refdb_free(refdb); +} + +void test_refs_pack__empty(void) +{ + /* create a packfile for an empty folder */ + git_str temp_path = GIT_STR_INIT; + + cl_git_pass(git_str_join_n(&temp_path, '/', 3, git_repository_path(g_repo), GIT_REFS_HEADS_DIR, "empty_dir")); + cl_git_pass(git_futils_mkdir_r(temp_path.ptr, GIT_REFS_DIR_MODE)); + git_str_dispose(&temp_path); + + packall(); +} + +void test_refs_pack__loose(void) +{ + /* create a packfile from all the loose refs in a repo */ + git_reference *reference; + git_str temp_path = GIT_STR_INIT; + + /* Ensure a known loose ref can be looked up */ + cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name)); + cl_assert(reference_is_packed(reference) == 0); + cl_assert_equal_s(reference->name, loose_tag_ref_name); + git_reference_free(reference); + + /* + * We are now trying to pack also a loose reference + * called `points_to_blob`, to make sure we can properly + * pack weak tags + */ + packall(); + + /* Ensure the packed-refs file exists */ + cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), GIT_PACKEDREFS_FILE)); + cl_assert(git_fs_path_exists(temp_path.ptr)); + + /* Ensure the known ref can still be looked up but is now packed */ + cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name)); + cl_assert(reference_is_packed(reference)); + cl_assert_equal_s(reference->name, loose_tag_ref_name); + + /* Ensure the known ref has been removed from the loose folder structure */ + cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), loose_tag_ref_name)); + cl_assert(!git_fs_path_exists(temp_path.ptr)); + + git_reference_free(reference); + git_str_dispose(&temp_path); +} + +void test_refs_pack__symbolic(void) +{ + /* create a packfile from loose refs skipping symbolic refs */ + int i; + git_oid head; + git_reference *ref; + char name[128]; + + cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); + + /* make a bunch of references */ + + for (i = 0; i < 100; ++i) { + p_snprintf(name, sizeof(name), "refs/heads/symbolic-%03d", i); + cl_git_pass(git_reference_symbolic_create( + &ref, g_repo, name, "refs/heads/master", 0, NULL)); + git_reference_free(ref); + + p_snprintf(name, sizeof(name), "refs/heads/direct-%03d", i); + cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0, NULL)); + git_reference_free(ref); + } + + packall(); +} diff --git a/tests/libgit2/refs/peel.c b/tests/libgit2/refs/peel.c new file mode 100644 index 000000000..38f3465a0 --- /dev/null +++ b/tests/libgit2/refs/peel.c @@ -0,0 +1,131 @@ +#include "clar_libgit2.h" + +static git_repository *g_repo; +static git_repository *g_peel_repo; + +void test_refs_peel__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_repository_open(&g_peel_repo, cl_fixture("peeled.git"))); +} + +void test_refs_peel__cleanup(void) +{ + git_repository_free(g_repo); + g_repo = NULL; + git_repository_free(g_peel_repo); + g_peel_repo = NULL; +} + +static void assert_peel_generic( + git_repository *repo, + const char *ref_name, + git_object_t requested_type, + const char* expected_sha, + git_object_t expected_type) +{ + git_oid expected_oid; + git_reference *ref; + git_object *peeled; + + cl_git_pass(git_reference_lookup(&ref, repo, ref_name)); + + cl_git_pass(git_reference_peel(&peeled, ref, requested_type)); + + cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); + cl_assert_equal_oid(&expected_oid, git_object_id(peeled)); + + cl_assert_equal_i(expected_type, git_object_type(peeled)); + + git_object_free(peeled); + git_reference_free(ref); +} + +static void assert_peel( + const char *ref_name, + git_object_t requested_type, + const char* expected_sha, + git_object_t expected_type) +{ + assert_peel_generic(g_repo, ref_name, requested_type, + expected_sha, expected_type); +} + +static void assert_peel_error(int error, const char *ref_name, git_object_t requested_type) +{ + git_reference *ref; + git_object *peeled; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + + cl_assert_equal_i(error, git_reference_peel(&peeled, ref, requested_type)); + + git_reference_free(ref); +} + +void test_refs_peel__can_peel_a_tag(void) +{ + assert_peel("refs/tags/test", GIT_OBJECT_TAG, + "b25fa35b38051e4ae45d4222e795f9df2e43f1d1", GIT_OBJECT_TAG); + assert_peel("refs/tags/test", GIT_OBJECT_COMMIT, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); + assert_peel("refs/tags/test", GIT_OBJECT_TREE, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); + assert_peel("refs/tags/point_to_blob", GIT_OBJECT_BLOB, + "1385f264afb75a56a5bec74243be9b367ba4ca08", GIT_OBJECT_BLOB); +} + +void test_refs_peel__can_peel_a_branch(void) +{ + assert_peel("refs/heads/master", GIT_OBJECT_COMMIT, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJECT_COMMIT); + assert_peel("refs/heads/master", GIT_OBJECT_TREE, + "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162", GIT_OBJECT_TREE); +} + +void test_refs_peel__can_peel_a_symbolic_reference(void) +{ + assert_peel("HEAD", GIT_OBJECT_COMMIT, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJECT_COMMIT); + assert_peel("HEAD", GIT_OBJECT_TREE, + "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162", GIT_OBJECT_TREE); +} + +void test_refs_peel__cannot_peel_into_a_non_existing_target(void) +{ + assert_peel_error(GIT_EINVALIDSPEC, "refs/tags/point_to_blob", GIT_OBJECT_TAG); +} + +void test_refs_peel__can_peel_into_any_non_tag_object(void) +{ + assert_peel("refs/heads/master", GIT_OBJECT_ANY, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJECT_COMMIT); + assert_peel("refs/tags/point_to_blob", GIT_OBJECT_ANY, + "1385f264afb75a56a5bec74243be9b367ba4ca08", GIT_OBJECT_BLOB); + assert_peel("refs/tags/test", GIT_OBJECT_ANY, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); +} + +void test_refs_peel__can_peel_fully_peeled_packed_refs(void) +{ + assert_peel_generic(g_peel_repo, + "refs/tags/tag-inside-tags", GIT_OBJECT_ANY, + "0df1a5865c8abfc09f1f2182e6a31be550e99f07", + GIT_OBJECT_COMMIT); + assert_peel_generic(g_peel_repo, + "refs/foo/tag-outside-tags", GIT_OBJECT_ANY, + "0df1a5865c8abfc09f1f2182e6a31be550e99f07", + GIT_OBJECT_COMMIT); +} + +void test_refs_peel__can_peel_fully_peeled_tag_to_tag(void) +{ + assert_peel_generic(g_peel_repo, + "refs/tags/tag-inside-tags", GIT_OBJECT_TAG, + "c2596aa0151888587ec5c0187f261e63412d9e11", + GIT_OBJECT_TAG); + assert_peel_generic(g_peel_repo, + "refs/foo/tag-outside-tags", GIT_OBJECT_TAG, + "c2596aa0151888587ec5c0187f261e63412d9e11", + GIT_OBJECT_TAG); +} diff --git a/tests/libgit2/refs/races.c b/tests/libgit2/refs/races.c new file mode 100644 index 000000000..476789358 --- /dev/null +++ b/tests/libgit2/refs/races.c @@ -0,0 +1,170 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" +#include "ref_helpers.h" + +static const char *commit_id = "099fabac3a9ea935598528c27f866e34089c2eff"; +static const char *refname = "refs/heads/master"; +static const char *other_refname = "refs/heads/foo"; +static const char *other_commit_id = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; + +static git_repository *g_repo; + +void test_refs_races__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_races__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_races__create_matching_zero_old(void) +{ + git_reference *ref; + git_oid id, zero_id; + + git_oid_fromstr(&id, commit_id); + git_oid_fromstr(&zero_id, "0000000000000000000000000000000000000000"); + + cl_git_fail(git_reference_create_matching(&ref, g_repo, refname, &id, 1, &zero_id, NULL)); + git_reference_free(ref); + + cl_git_pass(git_reference_create_matching(&ref, g_repo, other_refname, &id, 1, &zero_id, NULL)); + git_reference_free(ref); + + cl_git_fail(git_reference_create_matching(&ref, g_repo, other_refname, &id, 1, &zero_id, NULL)); + git_reference_free(ref); +} + +void test_refs_races__create_matching(void) +{ + git_reference *ref, *ref2, *ref3; + git_oid id, other_id; + + git_oid_fromstr(&id, commit_id); + git_oid_fromstr(&other_id, other_commit_id); + + cl_git_fail_with(GIT_EMODIFIED, git_reference_create_matching(&ref, g_repo, refname, &other_id, 1, &other_id, NULL)); + + cl_git_pass(git_reference_lookup(&ref, g_repo, refname)); + cl_git_pass(git_reference_create_matching(&ref2, g_repo, refname, &other_id, 1, &id, NULL)); + cl_git_fail_with(GIT_EMODIFIED, git_reference_set_target(&ref3, ref, &other_id, NULL)); + + git_reference_free(ref); + git_reference_free(ref2); + git_reference_free(ref3); +} + +void test_refs_races__symbolic_create_matching(void) +{ + git_reference *ref, *ref2, *ref3; + git_oid id, other_id; + + git_oid_fromstr(&id, commit_id); + git_oid_fromstr(&other_id, other_commit_id); + + cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_create_matching(&ref, g_repo, "HEAD", other_refname, 1, other_refname, NULL)); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); + cl_git_pass(git_reference_symbolic_create_matching(&ref2, g_repo, "HEAD", other_refname, 1, NULL, refname)); + cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_set_target(&ref3, ref, other_refname, NULL)); + + git_reference_free(ref); + git_reference_free(ref2); + git_reference_free(ref3); +} + +void test_refs_races__delete(void) +{ + git_reference *ref, *ref2; + git_oid id, other_id; + + git_oid_fromstr(&id, commit_id); + git_oid_fromstr(&other_id, other_commit_id); + + /* We can delete a value that matches */ + cl_git_pass(git_reference_lookup(&ref, g_repo, refname)); + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + /* We cannot delete a symbolic value that doesn't match */ + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref")); + cl_git_pass(git_reference_symbolic_create_matching(&ref2, g_repo, "refs/symref", other_refname, 1, NULL, refname)); + cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref)); + + git_reference_free(ref); + git_reference_free(ref2); + + cl_git_pass(git_reference_create(&ref, g_repo, refname, &id, 1, NULL)); + git_reference_free(ref); + + /* We cannot delete an oid value that doesn't match */ + cl_git_pass(git_reference_lookup(&ref, g_repo, refname)); + cl_git_pass(git_reference_create_matching(&ref2, g_repo, refname, &other_id, 1, &id, NULL)); + cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref)); + + git_reference_free(ref); + git_reference_free(ref2); +} + +void test_refs_races__switch_oid_to_symbolic(void) +{ + git_reference *ref, *ref2, *ref3; + git_oid id, other_id; + + git_oid_fromstr(&id, commit_id); + git_oid_fromstr(&other_id, other_commit_id); + + /* Removing a direct ref when it's currently symbolic should fail */ + cl_git_pass(git_reference_lookup(&ref, g_repo, refname)); + cl_git_pass(git_reference_symbolic_create(&ref2, g_repo, refname, other_refname, 1, NULL)); + cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref)); + + git_reference_free(ref); + git_reference_free(ref2); + + cl_git_pass(git_reference_create(&ref, g_repo, refname, &id, 1, NULL)); + git_reference_free(ref); + + /* Updating a direct ref when it's currently symbolic should fail */ + cl_git_pass(git_reference_lookup(&ref, g_repo, refname)); + cl_git_pass(git_reference_symbolic_create(&ref2, g_repo, refname, other_refname, 1, NULL)); + cl_git_fail_with(GIT_EMODIFIED, git_reference_set_target(&ref3, ref, &other_id, NULL)); + + git_reference_free(ref); + git_reference_free(ref2); + git_reference_free(ref3); +} + +void test_refs_races__switch_symbolic_to_oid(void) +{ + git_reference *ref, *ref2, *ref3; + git_oid id, other_id; + + git_oid_fromstr(&id, commit_id); + git_oid_fromstr(&other_id, other_commit_id); + + /* Removing a symbolic ref when it's currently direct should fail */ + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref")); + cl_git_pass(git_reference_create(&ref2, g_repo, "refs/symref", &id, 1, NULL)); + cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref)); + + git_reference_free(ref); + git_reference_free(ref2); + + cl_git_pass(git_reference_symbolic_create(&ref, g_repo, "refs/symref", refname, 1, NULL)); + git_reference_free(ref); + + /* Updating a symbolic ref when it's currently direct should fail */ + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref")); + cl_git_pass(git_reference_create(&ref2, g_repo, "refs/symref", &id, 1, NULL)); + cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_set_target(&ref3, ref, other_refname, NULL)); + + git_reference_free(ref); + git_reference_free(ref2); + git_reference_free(ref3); +} diff --git a/tests/libgit2/refs/read.c b/tests/libgit2/refs/read.c new file mode 100644 index 000000000..a622c770b --- /dev/null +++ b/tests/libgit2/refs/read.c @@ -0,0 +1,299 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" +#include "ref_helpers.h" + +static const char *loose_tag_ref_name = "refs/tags/e90810b"; +static const char *non_existing_tag_ref_name = "refs/tags/i-do-not-exist"; +static const char *head_tracker_sym_ref_name = "HEAD_TRACKER"; +static const char *current_head_target = "refs/heads/master"; +static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; +static const char *packed_head_name = "refs/heads/packed"; +static const char *packed_test_head_name = "refs/heads/packed-test"; + +static git_repository *g_repo; + +void test_refs_read__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); +} + +void test_refs_read__cleanup(void) +{ + git_repository_free(g_repo); + g_repo = NULL; +} + +void test_refs_read__loose_tag(void) +{ + /* lookup a loose tag reference */ + git_reference *reference; + git_object *object; + git_str ref_name_from_tag_name = GIT_STR_INIT; + + cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name)); + cl_assert(git_reference_type(reference) & GIT_REFERENCE_DIRECT); + cl_assert(reference_is_packed(reference) == 0); + cl_assert_equal_s(reference->name, loose_tag_ref_name); + + cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(reference), GIT_OBJECT_ANY)); + cl_assert(object != NULL); + cl_assert(git_object_type(object) == GIT_OBJECT_TAG); + + /* Ensure the name of the tag matches the name of the reference */ + cl_git_pass(git_str_joinpath(&ref_name_from_tag_name, GIT_REFS_TAGS_DIR, git_tag_name((git_tag *)object))); + cl_assert_equal_s(ref_name_from_tag_name.ptr, loose_tag_ref_name); + git_str_dispose(&ref_name_from_tag_name); + + git_object_free(object); + + git_reference_free(reference); +} + +void test_refs_read__nonexisting_tag(void) +{ + /* lookup a loose tag reference that doesn't exist */ + git_reference *reference; + + cl_git_fail(git_reference_lookup(&reference, g_repo, non_existing_tag_ref_name)); + + git_reference_free(reference); +} + + +void test_refs_read__symbolic(void) +{ + /* lookup a symbolic reference */ + git_reference *reference, *resolved_ref; + git_object *object; + git_oid id; + + cl_git_pass(git_reference_lookup(&reference, g_repo, GIT_HEAD_FILE)); + cl_assert(git_reference_type(reference) & GIT_REFERENCE_SYMBOLIC); + cl_assert(reference_is_packed(reference) == 0); + cl_assert_equal_s(reference->name, GIT_HEAD_FILE); + + cl_git_pass(git_reference_resolve(&resolved_ref, reference)); + cl_assert(git_reference_type(resolved_ref) == GIT_REFERENCE_DIRECT); + + cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(resolved_ref), GIT_OBJECT_ANY)); + cl_assert(object != NULL); + cl_assert(git_object_type(object) == GIT_OBJECT_COMMIT); + + git_oid_fromstr(&id, current_master_tip); + cl_assert_equal_oid(&id, git_object_id(object)); + + git_object_free(object); + + git_reference_free(reference); + git_reference_free(resolved_ref); +} + +void test_refs_read__nested_symbolic(void) +{ + /* lookup a nested symbolic reference */ + git_reference *reference, *resolved_ref; + git_object *object; + git_oid id; + + cl_git_pass(git_reference_lookup(&reference, g_repo, head_tracker_sym_ref_name)); + cl_assert(git_reference_type(reference) & GIT_REFERENCE_SYMBOLIC); + cl_assert(reference_is_packed(reference) == 0); + cl_assert_equal_s(reference->name, head_tracker_sym_ref_name); + + cl_git_pass(git_reference_resolve(&resolved_ref, reference)); + cl_assert(git_reference_type(resolved_ref) == GIT_REFERENCE_DIRECT); + + cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(resolved_ref), GIT_OBJECT_ANY)); + cl_assert(object != NULL); + cl_assert(git_object_type(object) == GIT_OBJECT_COMMIT); + + git_oid_fromstr(&id, current_master_tip); + cl_assert_equal_oid(&id, git_object_id(object)); + + git_object_free(object); + + git_reference_free(reference); + git_reference_free(resolved_ref); +} + +void test_refs_read__head_then_master(void) +{ + /* lookup the HEAD and resolve the master branch */ + git_reference *reference, *resolved_ref, *comp_base_ref; + + cl_git_pass(git_reference_lookup(&reference, g_repo, head_tracker_sym_ref_name)); + cl_git_pass(git_reference_resolve(&comp_base_ref, reference)); + git_reference_free(reference); + + cl_git_pass(git_reference_lookup(&reference, g_repo, GIT_HEAD_FILE)); + cl_git_pass(git_reference_resolve(&resolved_ref, reference)); + cl_assert_equal_oid(git_reference_target(comp_base_ref), git_reference_target(resolved_ref)); + git_reference_free(reference); + git_reference_free(resolved_ref); + + cl_git_pass(git_reference_lookup(&reference, g_repo, current_head_target)); + cl_git_pass(git_reference_resolve(&resolved_ref, reference)); + cl_assert_equal_oid(git_reference_target(comp_base_ref), git_reference_target(resolved_ref)); + git_reference_free(reference); + git_reference_free(resolved_ref); + + git_reference_free(comp_base_ref); +} + +void test_refs_read__master_then_head(void) +{ + /* lookup the master branch and then the HEAD */ + git_reference *reference, *master_ref, *resolved_ref; + + cl_git_pass(git_reference_lookup(&master_ref, g_repo, current_head_target)); + cl_git_pass(git_reference_lookup(&reference, g_repo, GIT_HEAD_FILE)); + + cl_git_pass(git_reference_resolve(&resolved_ref, reference)); + cl_assert_equal_oid(git_reference_target(master_ref), git_reference_target(resolved_ref)); + + git_reference_free(reference); + git_reference_free(resolved_ref); + git_reference_free(master_ref); +} + + +void test_refs_read__packed(void) +{ + /* lookup a packed reference */ + git_reference *reference; + git_object *object; + + cl_git_pass(git_reference_lookup(&reference, g_repo, packed_head_name)); + cl_assert(git_reference_type(reference) & GIT_REFERENCE_DIRECT); + cl_assert(reference_is_packed(reference)); + cl_assert_equal_s(reference->name, packed_head_name); + + cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(reference), GIT_OBJECT_ANY)); + cl_assert(object != NULL); + cl_assert(git_object_type(object) == GIT_OBJECT_COMMIT); + + git_object_free(object); + + git_reference_free(reference); +} + +void test_refs_read__loose_first(void) +{ + /* assure that a loose reference is looked up before a packed reference */ + git_reference *reference; + + cl_git_pass(git_reference_lookup(&reference, g_repo, packed_head_name)); + git_reference_free(reference); + cl_git_pass(git_reference_lookup(&reference, g_repo, packed_test_head_name)); + cl_assert(git_reference_type(reference) & GIT_REFERENCE_DIRECT); + cl_assert(reference_is_packed(reference) == 0); + cl_assert_equal_s(reference->name, packed_test_head_name); + + git_reference_free(reference); +} + +void test_refs_read__chomped(void) +{ + git_reference *test, *chomped; + + cl_git_pass(git_reference_lookup(&test, g_repo, "refs/heads/test")); + cl_git_pass(git_reference_lookup(&chomped, g_repo, "refs/heads/chomped")); + cl_assert_equal_oid(git_reference_target(test), git_reference_target(chomped)); + + git_reference_free(test); + git_reference_free(chomped); +} + +void test_refs_read__trailing(void) +{ + git_reference *test, *trailing; + + cl_git_pass(git_reference_lookup(&test, g_repo, "refs/heads/test")); + cl_git_pass(git_reference_lookup(&trailing, g_repo, "refs/heads/trailing")); + cl_assert_equal_oid(git_reference_target(test), git_reference_target(trailing)); + git_reference_free(trailing); + cl_git_pass(git_reference_lookup(&trailing, g_repo, "FETCH_HEAD")); + + git_reference_free(test); + git_reference_free(trailing); +} + +void test_refs_read__unfound_return_ENOTFOUND(void) +{ + git_reference *reference; + git_oid id; + + cl_assert_equal_i(GIT_ENOTFOUND, + git_reference_lookup(&reference, g_repo, "TEST_MASTER")); + cl_assert_equal_i(GIT_ENOTFOUND, + git_reference_lookup(&reference, g_repo, "refs/test/master")); + cl_assert_equal_i(GIT_ENOTFOUND, + git_reference_lookup(&reference, g_repo, "refs/tags/test/master")); + cl_assert_equal_i(GIT_ENOTFOUND, + git_reference_lookup(&reference, g_repo, "refs/tags/test/farther/master")); + + cl_assert_equal_i(GIT_ENOTFOUND, + git_reference_name_to_id(&id, g_repo, "refs/tags/test/farther/master")); +} + +static void assert_is_branch(const char *name, bool expected_branchness) +{ + git_reference *reference; + cl_git_pass(git_reference_lookup(&reference, g_repo, name)); + cl_assert_equal_i(expected_branchness, git_reference_is_branch(reference)); + git_reference_free(reference); +} + +void test_refs_read__can_determine_if_a_reference_is_a_local_branch(void) +{ + assert_is_branch("refs/heads/master", true); + assert_is_branch("refs/heads/packed", true); + assert_is_branch("refs/remotes/test/master", false); + assert_is_branch("refs/tags/e90810b", false); +} + +static void assert_is_tag(const char *name, bool expected_tagness) +{ + git_reference *reference; + cl_git_pass(git_reference_lookup(&reference, g_repo, name)); + cl_assert_equal_i(expected_tagness, git_reference_is_tag(reference)); + git_reference_free(reference); +} + +void test_refs_read__can_determine_if_a_reference_is_a_tag(void) +{ + assert_is_tag("refs/tags/e90810b", true); + assert_is_tag("refs/tags/test", true); + assert_is_tag("refs/heads/packed", false); + assert_is_tag("refs/remotes/test/master", false); +} + +static void assert_is_note(const char *name, bool expected_noteness) +{ + git_reference *reference; + cl_git_pass(git_reference_lookup(&reference, g_repo, name)); + cl_assert_equal_i(expected_noteness, git_reference_is_note(reference)); + git_reference_free(reference); +} + +void test_refs_read__can_determine_if_a_reference_is_a_note(void) +{ + assert_is_note("refs/notes/fanout", true); + assert_is_note("refs/heads/packed", false); + assert_is_note("refs/remotes/test/master", false); +} + +void test_refs_read__invalid_name_returns_EINVALIDSPEC(void) +{ + git_reference *reference; + git_oid id; + + cl_assert_equal_i(GIT_EINVALIDSPEC, + git_reference_lookup(&reference, g_repo, "refs/heads/Inv@{id")); + + cl_assert_equal_i(GIT_EINVALIDSPEC, + git_reference_name_to_id(&id, g_repo, "refs/heads/Inv@{id")); +} diff --git a/tests/libgit2/refs/ref_helpers.c b/tests/libgit2/refs/ref_helpers.c new file mode 100644 index 000000000..70d5d36d5 --- /dev/null +++ b/tests/libgit2/refs/ref_helpers.c @@ -0,0 +1,25 @@ +#include "git2/repository.h" +#include "git2/refs.h" +#include "common.h" +#include "util.h" +#include "path.h" +#include "ref_helpers.h" + +int reference_is_packed(git_reference *ref) +{ + git_str ref_path = GIT_STR_INIT; + int packed; + + assert(ref); + + if (git_str_joinpath(&ref_path, + git_repository_path(git_reference_owner(ref)), + git_reference_name(ref)) < 0) + return -1; + + packed = !git_fs_path_isfile(ref_path.ptr); + + git_str_dispose(&ref_path); + + return packed; +} diff --git a/tests/libgit2/refs/ref_helpers.h b/tests/libgit2/refs/ref_helpers.h new file mode 100644 index 000000000..0ef55bfce --- /dev/null +++ b/tests/libgit2/refs/ref_helpers.h @@ -0,0 +1 @@ +int reference_is_packed(git_reference *ref); diff --git a/tests/libgit2/refs/reflog/drop.c b/tests/libgit2/refs/reflog/drop.c new file mode 100644 index 000000000..916bd9933 --- /dev/null +++ b/tests/libgit2/refs/reflog/drop.c @@ -0,0 +1,115 @@ +#include "clar_libgit2.h" + +#include "reflog.h" + +static git_repository *g_repo; +static git_reflog *g_reflog; +static size_t entrycount; + +void test_refs_reflog_drop__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); + + git_reflog_read(&g_reflog, g_repo, "HEAD"); + entrycount = git_reflog_entrycount(g_reflog); +} + +void test_refs_reflog_drop__cleanup(void) +{ + git_reflog_free(g_reflog); + g_reflog = NULL; + + cl_git_sandbox_cleanup(); +} + +void test_refs_reflog_drop__dropping_a_non_exisiting_entry_from_the_log_returns_ENOTFOUND(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_reflog_drop(g_reflog, entrycount, 0)); + + cl_assert_equal_sz(entrycount, git_reflog_entrycount(g_reflog)); +} + +void test_refs_reflog_drop__can_drop_an_entry(void) +{ + cl_assert(entrycount > 4); + + cl_git_pass(git_reflog_drop(g_reflog, 2, 0)); + cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); +} + +void test_refs_reflog_drop__can_drop_an_entry_and_rewrite_the_log_history(void) +{ + const git_reflog_entry *before_current; + const git_reflog_entry *after_current; + git_oid before_current_old_oid, before_current_cur_oid; + + cl_assert(entrycount > 4); + + before_current = git_reflog_entry_byindex(g_reflog, 1); + + git_oid_cpy(&before_current_old_oid, &before_current->oid_old); + git_oid_cpy(&before_current_cur_oid, &before_current->oid_cur); + + cl_git_pass(git_reflog_drop(g_reflog, 1, 1)); + + cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); + + after_current = git_reflog_entry_byindex(g_reflog, 0); + + cl_assert_equal_i(0, git_oid_cmp(&before_current_old_oid, &after_current->oid_old)); + cl_assert(0 != git_oid_cmp(&before_current_cur_oid, &after_current->oid_cur)); +} + +void test_refs_reflog_drop__can_drop_the_oldest_entry(void) +{ + const git_reflog_entry *entry; + + cl_assert(entrycount > 2); + + cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 0)); + cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); + + entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); + cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) != 0); +} + +void test_refs_reflog_drop__can_drop_the_oldest_entry_and_rewrite_the_log_history(void) +{ + const git_reflog_entry *entry; + + cl_assert(entrycount > 2); + + cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 1)); + cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); + + entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); + cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); +} + +void test_refs_reflog_drop__can_drop_all_the_entries(void) +{ + cl_assert(--entrycount > 0); + + do { + cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); + } while (--entrycount > 0); + + cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); + + cl_assert_equal_i(0, (int)git_reflog_entrycount(g_reflog)); +} + +void test_refs_reflog_drop__can_persist_deletion_on_disk(void) +{ + cl_assert(entrycount > 2); + + cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); + cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); + cl_git_pass(git_reflog_write(g_reflog)); + + git_reflog_free(g_reflog); + + git_reflog_read(&g_reflog, g_repo, "HEAD"); + + cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); +} diff --git a/tests/libgit2/refs/reflog/messages.c b/tests/libgit2/refs/reflog/messages.c new file mode 100644 index 000000000..ed183d2f2 --- /dev/null +++ b/tests/libgit2/refs/reflog/messages.c @@ -0,0 +1,421 @@ +#include "clar_libgit2.h" + +#include "futils.h" +#include "git2/reflog.h" +#include "reflog.h" +#include "refs.h" +#include "reflog_helpers.h" + +static const char *g_email = "foo@example.com"; +static git_repository *g_repo; + +/* Fixture setup and teardown */ +void test_refs_reflog_messages__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_set_ident(g_repo, "Foo Bar", g_email)); +} + +void test_refs_reflog_messages__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_reflog_messages__setting_head_updates_reflog(void) +{ + git_object *tag; + git_annotated_commit *annotated; + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); /* 4 */ + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/unborn")); + cl_git_pass(git_revparse_single(&tag, g_repo, "tags/test")); + cl_git_pass(git_repository_set_head_detached(g_repo, git_object_id(tag))); /* 3 */ + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); /* 2 */ + cl_git_pass(git_repository_set_head(g_repo, "refs/tags/test")); /* 1 */ + cl_git_pass(git_repository_set_head(g_repo, "refs/remotes/test/master")); /* 0 */ + + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 4, + NULL, "refs/heads/haacked", + "foo@example.com", + "checkout: moving from master to haacked"); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 3, + NULL, "tags/test^{commit}", + "foo@example.com", + "checkout: moving from unborn to e90810b8df3e80c413d903f631643c716887138d"); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 2, + "tags/test^{commit}", "refs/heads/haacked", + "foo@example.com", + "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to haacked"); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 1, + "refs/heads/haacked", "tags/test^{commit}", + "foo@example.com", + "checkout: moving from haacked to test"); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "tags/test^{commit}", "refs/remotes/test/master", + "foo@example.com", + "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to test/master"); + + cl_git_pass(git_annotated_commit_from_revspec(&annotated, g_repo, "haacked~0")); + cl_git_pass(git_repository_set_head_detached_from_annotated(g_repo, annotated)); + + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + NULL, "refs/heads/haacked", + "foo@example.com", + "checkout: moving from be3563ae3f795b2b4353bcce3a527ad0a4f7f644 to haacked~0"); + + git_annotated_commit_free(annotated); + git_object_free(tag); +} + +void test_refs_reflog_messages__setting_head_to_same_target_ignores_reflog(void) +{ + size_t nentries, nentries_after; + + nentries = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); + + nentries_after = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_assert_equal_i(nentries + 1, nentries_after); +} + +void test_refs_reflog_messages__detaching_writes_reflog(void) +{ + git_oid id; + const char *msg; + + msg = "checkout: moving from master to e90810b8df3e80c413d903f631643c716887138d"; + git_oid_fromstr(&id, "e90810b8df3e80c413d903f631643c716887138d"); + cl_git_pass(git_repository_set_head_detached(g_repo, &id)); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "e90810b8df3e80c413d903f631643c716887138d", + NULL, msg); + + msg = "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to haacked"; + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "e90810b8df3e80c413d903f631643c716887138d", + "258f0e2a959a364e40ed6603d5d44fbb24765b10", + NULL, msg); +} + +void test_refs_reflog_messages__orphan_branch_does_not_count(void) +{ + git_oid id; + const char *msg; + + /* Have something known */ + msg = "checkout: moving from master to e90810b8df3e80c413d903f631643c716887138d"; + git_oid_fromstr(&id, "e90810b8df3e80c413d903f631643c716887138d"); + cl_git_pass(git_repository_set_head_detached(g_repo, &id)); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "e90810b8df3e80c413d903f631643c716887138d", + NULL, msg); + + /* Switching to an orphan branch does not write to the reflog */ + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/orphan")); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "e90810b8df3e80c413d903f631643c716887138d", + NULL, msg); + + /* And coming back, we set the source to zero */ + msg = "checkout: moving from orphan to haacked"; + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "0000000000000000000000000000000000000000", + "258f0e2a959a364e40ed6603d5d44fbb24765b10", + NULL, msg); +} + +void test_refs_reflog_messages__branch_birth(void) +{ + git_signature *sig; + git_oid id; + git_tree *tree; + git_reference *ref; + const char *msg; + size_t nentries, nentries_after; + + nentries = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); + + cl_git_pass(git_repository_head(&ref, g_repo)); + cl_git_pass(git_reference_peel((git_object **) &tree, ref, GIT_OBJECT_TREE)); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/orphan")); + + nentries_after = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_assert_equal_i(nentries, nentries_after); + + msg = "message 2"; + cl_git_pass(git_commit_create(&id, g_repo, "HEAD", sig, sig, NULL, msg, tree, 0, NULL)); + + cl_assert_equal_i(1, reflog_entrycount(g_repo, "refs/heads/orphan")); + + nentries_after = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_assert_equal_i(nentries + 1, nentries_after); + + git_signature_free(sig); + git_tree_free(tree); + git_reference_free(ref); +} + +void test_refs_reflog_messages__commit_on_symbolic_ref_updates_head_reflog(void) +{ + git_signature *sig; + git_oid id; + git_tree *tree; + git_reference *ref1, *ref2; + const char *msg; + size_t nentries_head, nentries_master; + + nentries_head = reflog_entrycount(g_repo, GIT_HEAD_FILE); + + cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); + + cl_git_pass(git_repository_head(&ref1, g_repo)); + cl_git_pass(git_reference_peel((git_object **) &tree, ref1, GIT_OBJECT_TREE)); + + nentries_master = reflog_entrycount(g_repo, "refs/heads/master"); + + msg = "message 1"; + cl_git_pass(git_reference_symbolic_create(&ref2, g_repo, "refs/heads/master", "refs/heads/foo", 1, msg)); + + cl_assert_equal_i(0, reflog_entrycount(g_repo, "refs/heads/foo")); + cl_assert_equal_i(nentries_head, reflog_entrycount(g_repo, GIT_HEAD_FILE)); + cl_assert_equal_i(nentries_master, reflog_entrycount(g_repo, "refs/heads/master")); + + msg = "message 2"; + cl_git_pass(git_commit_create(&id, g_repo, "HEAD", sig, sig, NULL, msg, tree, 0, NULL)); + + cl_assert_equal_i(1, reflog_entrycount(g_repo, "refs/heads/foo")); + cl_assert_equal_i(nentries_head + 1, reflog_entrycount(g_repo, GIT_HEAD_FILE)); + cl_assert_equal_i(nentries_master, reflog_entrycount(g_repo, "refs/heads/master")); + + git_signature_free(sig); + git_reference_free(ref1); + git_reference_free(ref2); + git_tree_free(tree); +} + +void test_refs_reflog_messages__show_merge_for_merge_commits(void) +{ + git_oid b1_oid; + git_oid b2_oid; + git_oid merge_commit_oid; + git_commit *b1_commit; + git_commit *b2_commit; + git_signature *s; + git_commit *parent_commits[2]; + git_tree *tree; + + cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); + + cl_git_pass(git_reference_name_to_id(&b1_oid, g_repo, "HEAD")); + cl_git_pass(git_reference_name_to_id(&b2_oid, g_repo, "refs/heads/test")); + + cl_git_pass(git_commit_lookup(&b1_commit, g_repo, &b1_oid)); + cl_git_pass(git_commit_lookup(&b2_commit, g_repo, &b2_oid)); + + parent_commits[0] = b1_commit; + parent_commits[1] = b2_commit; + + cl_git_pass(git_commit_tree(&tree, b1_commit)); + + cl_git_pass(git_commit_create(&merge_commit_oid, + g_repo, "HEAD", s, s, NULL, + "Merge commit", tree, + 2, (const struct git_commit **) parent_commits)); + + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + NULL, + git_oid_tostr_s(&merge_commit_oid), + NULL, "commit (merge): Merge commit"); + + git_tree_free(tree); + git_commit_free(b1_commit); + git_commit_free(b2_commit); + git_signature_free(s); +} + +void test_refs_reflog_messages__creating_a_direct_reference(void) +{ + git_reference *reference; + git_oid id; + git_reflog *reflog; + const git_reflog_entry *entry; + + const char *name = "refs/heads/new-head"; + const char *message = "You've been logged, mate!"; + + cl_git_pass(git_reference_name_to_id(&id, g_repo, "HEAD")); + + cl_git_pass(git_reference_create(&reference, g_repo, name, &id, 0, message)); + + cl_git_pass(git_reflog_read(&reflog, g_repo, name)); + cl_assert_equal_sz(1, git_reflog_entrycount(reflog)); + + entry = git_reflog_entry_byindex(reflog, 0); + cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); + cl_assert_equal_oid(&id, &entry->oid_cur); + cl_assert_equal_s(message, entry->msg); + + git_reflog_free(reflog); + git_reference_free(reference); +} + +void test_refs_reflog_messages__newline_gets_replaced(void) +{ + const git_reflog_entry *entry; + git_signature *signature; + git_reflog *reflog; + git_oid oid; + + cl_git_pass(git_signature_now(&signature, "me", "foo@example.com")); + cl_git_pass(git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); + + cl_git_pass(git_reflog_read(&reflog, g_repo, "HEAD")); + cl_assert_equal_sz(7, git_reflog_entrycount(reflog)); + cl_git_pass(git_reflog_append(reflog, &oid, signature, "inner\nnewline")); + cl_assert_equal_sz(8, git_reflog_entrycount(reflog)); + + cl_assert(entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_s(git_reflog_entry_message(entry), "inner newline"); + + git_signature_free(signature); + git_reflog_free(reflog); +} + +void test_refs_reflog_messages__renaming_ref(void) +{ + git_reference *ref, *new_ref; + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); + cl_git_pass(git_reference_rename(&new_ref, ref, "refs/heads/renamed", false, + "message")); + + cl_reflog_check_entry(g_repo, git_reference_name(new_ref), 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "foo@example.com", "message"); + + git_reference_free(ref); + git_reference_free(new_ref); +} + +void test_refs_reflog_messages__updating_a_direct_reference(void) +{ + git_reference *ref, *ref_out, *target_ref; + git_oid target_id; + const char *message = "You've been logged, mate!"; + + git_reference_name_to_id(&target_id, g_repo, "refs/heads/haacked"); + cl_git_pass(git_reference_lookup(&target_ref, g_repo, "refs/heads/haacked")); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); + + cl_git_pass(git_reference_set_target(&ref_out, ref, &target_id, message)); + + cl_reflog_check_entry(g_repo, "refs/heads/master", 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "258f0e2a959a364e40ed6603d5d44fbb24765b10", + NULL, message); + + git_reference_free(target_ref); + git_reference_free(ref); + git_reference_free(ref_out); +} + +#define NEW_BRANCH_NAME "new-branch-on-the-block" + +void test_refs_reflog_messages__creating_branches_default_messages(void) +{ + git_str buf = GIT_STR_INIT; + git_annotated_commit *annotated; + git_object *obj; + git_commit *target; + git_reference *branch1, *branch2; + + cl_git_pass(git_revparse_single(&obj, g_repo, "e90810b8df3")); + cl_git_pass(git_commit_lookup(&target, g_repo, git_object_id(obj))); + git_object_free(obj); + + cl_git_pass(git_branch_create(&branch1, g_repo, NEW_BRANCH_NAME, target, false)); + + cl_git_pass(git_str_printf(&buf, "branch: Created from %s", git_oid_tostr_s(git_commit_id(target)))); + cl_reflog_check_entry(g_repo, "refs/heads/" NEW_BRANCH_NAME, 0, + GIT_OID_HEX_ZERO, + git_oid_tostr_s(git_commit_id(target)), + g_email, git_str_cstr(&buf)); + + cl_git_pass(git_reference_remove(g_repo, "refs/heads/" NEW_BRANCH_NAME)); + + cl_git_pass(git_annotated_commit_from_revspec(&annotated, g_repo, "e90810b8df3")); + cl_git_pass(git_branch_create_from_annotated(&branch2, g_repo, NEW_BRANCH_NAME, annotated, true)); + + cl_reflog_check_entry(g_repo, "refs/heads/" NEW_BRANCH_NAME, 0, + GIT_OID_HEX_ZERO, + git_oid_tostr_s(git_commit_id(target)), + g_email, "branch: Created from e90810b8df3"); + + git_annotated_commit_free(annotated); + git_str_dispose(&buf); + git_commit_free(target); + git_reference_free(branch1); + git_reference_free(branch2); +} + +void test_refs_reflog_messages__moving_branch_default_message(void) +{ + git_reference *branch; + git_reference *new_branch; + git_oid id; + + cl_git_pass(git_reference_lookup(&branch, g_repo, "refs/heads/master")); + git_oid_cpy(&id, git_reference_target(branch)); + cl_git_pass(git_branch_move(&new_branch, branch, "master2", 0)); + + cl_reflog_check_entry(g_repo, git_reference_name(new_branch), 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + g_email, + "branch: renamed refs/heads/master to refs/heads/master2"); + + git_reference_free(branch); + git_reference_free(new_branch); +} + +void test_refs_reflog_messages__detaching_head_default_message(void) +{ + git_reference *ref; + + cl_assert_equal_i(false, git_repository_head_detached(g_repo)); + + cl_git_pass(git_repository_detach_head(g_repo)); + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + NULL, "checkout: moving from master to a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_assert_equal_i(true, git_repository_head_detached(g_repo)); + + /* take the repo back to its original state */ + cl_git_pass(git_reference_symbolic_create(&ref, g_repo, "HEAD", "refs/heads/master", + true, "REATTACH")); + + cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + NULL, "REATTACH"); + + cl_assert_equal_i(false, git_repository_head_detached(g_repo)); + + git_reference_free(ref); +} diff --git a/tests/libgit2/refs/reflog/reflog.c b/tests/libgit2/refs/reflog/reflog.c new file mode 100644 index 000000000..32ce7ffb7 --- /dev/null +++ b/tests/libgit2/refs/reflog/reflog.c @@ -0,0 +1,469 @@ +#include "clar_libgit2.h" + +#include "futils.h" +#include "git2/reflog.h" +#include "reflog.h" + +static const char *new_ref = "refs/heads/test-reflog"; +static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; +#define commit_msg "commit: bla bla" + +static git_repository *g_repo; + + +/* helpers */ +static void assert_signature(const git_signature *expected, const git_signature *actual) +{ + cl_assert(actual); + cl_assert_equal_s(expected->name, actual->name); + cl_assert_equal_s(expected->email, actual->email); + cl_assert(expected->when.offset == actual->when.offset); + cl_assert(expected->when.time == actual->when.time); +} + + +/* Fixture setup and teardown */ +void test_refs_reflog_reflog__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_refs_reflog_reflog__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void assert_appends(const git_signature *committer, const git_oid *oid) +{ + git_repository *repo2; + git_reference *lookedup_ref; + git_reflog *reflog; + const git_reflog_entry *entry; + + /* Reopen a new instance of the repository */ + cl_git_pass(git_repository_open(&repo2, "testrepo.git")); + + /* Lookup the previously created branch */ + cl_git_pass(git_reference_lookup(&lookedup_ref, repo2, new_ref)); + + /* Read and parse the reflog for this branch */ + cl_git_pass(git_reflog_read(&reflog, repo2, new_ref)); + cl_assert_equal_i(3, (int)git_reflog_entrycount(reflog)); + + /* The first one was the creation of the branch */ + entry = git_reflog_entry_byindex(reflog, 2); + cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); + + entry = git_reflog_entry_byindex(reflog, 1); + assert_signature(committer, entry->committer); + cl_assert(git_oid_cmp(oid, &entry->oid_old) == 0); + cl_assert(git_oid_cmp(oid, &entry->oid_cur) == 0); + cl_assert(entry->msg == NULL); + + entry = git_reflog_entry_byindex(reflog, 0); + assert_signature(committer, entry->committer); + cl_assert(git_oid_cmp(oid, &entry->oid_cur) == 0); + cl_assert_equal_s(commit_msg, entry->msg); + + git_reflog_free(reflog); + git_repository_free(repo2); + + git_reference_free(lookedup_ref); +} + +void test_refs_reflog_reflog__append_then_read(void) +{ + /* write a reflog for a given reference and ensure it can be read back */ + git_reference *ref; + git_oid oid; + git_signature *committer; + git_reflog *reflog; + + /* Create a new branch pointing at the HEAD */ + git_oid_fromstr(&oid, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, new_ref, &oid, 0, NULL)); + git_reference_free(ref); + + cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); + + cl_git_pass(git_reflog_read(&reflog, g_repo, new_ref)); + cl_git_pass(git_reflog_append(reflog, &oid, committer, NULL)); + cl_git_pass(git_reflog_append(reflog, &oid, committer, commit_msg "\n")); + cl_git_pass(git_reflog_write(reflog)); + + assert_appends(committer, &oid); + + git_reflog_free(reflog); + git_signature_free(committer); +} + +void test_refs_reflog_reflog__renaming_the_reference_moves_the_reflog(void) +{ + git_reference *master, *new_master; + git_str master_log_path = GIT_STR_INIT, moved_log_path = GIT_STR_INIT; + + git_str_joinpath(&master_log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); + git_str_puts(&moved_log_path, git_str_cstr(&master_log_path)); + git_str_joinpath(&master_log_path, git_str_cstr(&master_log_path), "refs/heads/master"); + git_str_joinpath(&moved_log_path, git_str_cstr(&moved_log_path), "refs/moved"); + + cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&master_log_path))); + cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&moved_log_path))); + + cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); + cl_git_pass(git_reference_rename(&new_master, master, "refs/moved", 0, NULL)); + git_reference_free(master); + + cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&master_log_path))); + cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&moved_log_path))); + + git_reference_free(new_master); + git_str_dispose(&moved_log_path); + git_str_dispose(&master_log_path); +} + +void test_refs_reflog_reflog__deleting_the_reference_deletes_the_reflog(void) +{ + git_reference *master; + git_str master_log_path = GIT_STR_INIT; + + git_str_joinpath(&master_log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); + git_str_joinpath(&master_log_path, git_str_cstr(&master_log_path), "refs/heads/master"); + + cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&master_log_path))); + + cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); + cl_git_pass(git_reference_delete(master)); + git_reference_free(master); + + cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&master_log_path))); + git_str_dispose(&master_log_path); +} + +void test_refs_reflog_reflog__removes_empty_reflog_dir(void) +{ + git_reference *ref; + git_str log_path = GIT_STR_INIT; + git_oid id; + + /* Create a new branch pointing at the HEAD */ + git_oid_fromstr(&id, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/new-dir/new-head", &id, 0, NULL)); + + git_str_joinpath(&log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); + git_str_joinpath(&log_path, git_str_cstr(&log_path), "refs/heads/new-dir/new-head"); + + cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&log_path))); + + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + /* new ref creation should succeed since new-dir is empty */ + git_oid_fromstr(&id, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/new-dir", &id, 0, NULL)); + git_reference_free(ref); + + git_str_dispose(&log_path); +} + +void test_refs_reflog_reflog__fails_gracefully_on_nonempty_reflog_dir(void) +{ + git_reference *ref; + git_str log_path = GIT_STR_INIT; + git_oid id; + + /* Create a new branch pointing at the HEAD */ + git_oid_fromstr(&id, current_master_tip); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/new-dir/new-head", &id, 0, NULL)); + git_reference_free(ref); + + git_str_joinpath(&log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); + git_str_joinpath(&log_path, git_str_cstr(&log_path), "refs/heads/new-dir/new-head"); + + cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&log_path))); + + /* delete the ref manually, leave the reflog */ + cl_must_pass(p_unlink("testrepo.git/refs/heads/new-dir/new-head")); + + /* new ref creation should fail since new-dir contains reflogs still */ + git_oid_fromstr(&id, current_master_tip); + cl_git_fail_with(GIT_EDIRECTORY, git_reference_create(&ref, g_repo, "refs/heads/new-dir", &id, 0, NULL)); + git_reference_free(ref); + + git_str_dispose(&log_path); +} + +static void assert_has_reflog(bool expected_result, const char *name) +{ + cl_assert_equal_i(expected_result, git_reference_has_log(g_repo, name)); +} + +void test_refs_reflog_reflog__reference_has_reflog(void) +{ + assert_has_reflog(true, "HEAD"); + assert_has_reflog(true, "refs/heads/master"); + assert_has_reflog(false, "refs/heads/subtrees"); +} + +void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_returns_an_empty_one(void) +{ + git_reflog *reflog; + const char *refname = "refs/heads/subtrees"; + git_str subtrees_log_path = GIT_STR_INIT; + + git_str_join_n(&subtrees_log_path, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname); + cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&subtrees_log_path))); + + cl_git_pass(git_reflog_read(&reflog, g_repo, refname)); + + cl_assert_equal_i(0, (int)git_reflog_entrycount(reflog)); + + git_reflog_free(reflog); + git_str_dispose(&subtrees_log_path); +} + +void test_refs_reflog_reflog__reading_a_reflog_with_invalid_format_succeeds(void) +{ + git_reflog *reflog; + const char *refname = "refs/heads/newline"; + const char *refmessage = + "Reflog*message with a newline and enough content after it to pass the GIT_REFLOG_SIZE_MIN check inside reflog_parse."; + const git_reflog_entry *entry; + git_reference *ref; + git_oid id; + git_str logpath = GIT_STR_INIT, logcontents = GIT_STR_INIT; + char *star; + + /* Create a new branch. */ + cl_git_pass(git_oid_fromstr(&id, current_master_tip)); + cl_git_pass(git_reference_create(&ref, g_repo, refname, &id, 1, refmessage)); + + /* + * Corrupt the branch reflog by introducing a newline inside the reflog message. + * We do this by replacing '*' with '\n' + */ + cl_git_pass(git_str_join_n(&logpath, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname)); + cl_git_pass(git_futils_readbuffer(&logcontents, git_str_cstr(&logpath))); + cl_assert((star = strchr(git_str_cstr(&logcontents), '*')) != NULL); + *star = '\n'; + cl_git_rewritefile(git_str_cstr(&logpath), git_str_cstr(&logcontents)); + + /* + * Confirm that the file was rewritten successfully + * and now contains a '\n' in the expected location + */ + cl_git_pass(git_futils_readbuffer(&logcontents, git_str_cstr(&logpath))); + cl_assert(strstr(git_str_cstr(&logcontents), "Reflog\nmessage") != NULL); + + cl_git_pass(git_reflog_read(&reflog, g_repo, refname)); + cl_assert(entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_s(git_reflog_entry_message(entry), "Reflog"); + + git_reference_free(ref); + git_reflog_free(reflog); + git_str_dispose(&logpath); + git_str_dispose(&logcontents); +} + +void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void) +{ + git_reference *master, *new_master; + git_str master_log_path = GIT_STR_INIT, moved_log_path = GIT_STR_INIT; + git_reflog *reflog; + + cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); + cl_git_pass(git_reflog_read(&reflog, g_repo, "refs/heads/master")); + + cl_git_pass(git_reflog_write(reflog)); + + cl_git_pass(git_reference_rename(&new_master, master, "refs/moved", 0, NULL)); + git_reference_free(master); + + cl_git_fail(git_reflog_write(reflog)); + + git_reflog_free(reflog); + git_reference_free(new_master); + git_str_dispose(&moved_log_path); + git_str_dispose(&master_log_path); +} + +void test_refs_reflog_reflog__renaming_with_an_invalid_name_returns_EINVALIDSPEC(void) +{ + cl_assert_equal_i(GIT_EINVALIDSPEC, + git_reflog_rename(g_repo, "refs/heads/master", "refs/heads/Inv@{id")); +} + +void test_refs_reflog_reflog__write_only_std_locations(void) +{ + git_reference *ref; + git_oid id; + + git_oid_fromstr(&id, current_master_tip); + + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/foo", &id, 1, NULL)); + git_reference_free(ref); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/tags/foo", &id, 1, NULL)); + git_reference_free(ref); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/notes/foo", &id, 1, NULL)); + git_reference_free(ref); + + assert_has_reflog(true, "refs/heads/foo"); + assert_has_reflog(false, "refs/tags/foo"); + assert_has_reflog(true, "refs/notes/foo"); + +} + +void test_refs_reflog_reflog__write_when_explicitly_active(void) +{ + git_reference *ref; + git_oid id; + + git_oid_fromstr(&id, current_master_tip); + git_reference_ensure_log(g_repo, "refs/tags/foo"); + + cl_git_pass(git_reference_create(&ref, g_repo, "refs/tags/foo", &id, 1, NULL)); + git_reference_free(ref); + assert_has_reflog(true, "refs/tags/foo"); +} + +void test_refs_reflog_reflog__append_to_HEAD_when_changing_current_branch(void) +{ + size_t nlogs, nlogs_after; + git_reference *ref; + git_reflog *log; + git_oid id; + + cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); + nlogs = git_reflog_entrycount(log); + git_reflog_free(log); + + /* Move it back */ + git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/master", &id, 1, NULL)); + git_reference_free(ref); + + cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); + nlogs_after = git_reflog_entrycount(log); + git_reflog_free(log); + + cl_assert_equal_i(nlogs_after, nlogs + 1); +} + +void test_refs_reflog_reflog__do_not_append_when_no_update(void) +{ + size_t nlogs, nlogs_after; + git_reference *ref, *ref2; + git_reflog *log; + + cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); + nlogs = git_reflog_entrycount(log); + git_reflog_free(log); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); + cl_git_pass(git_reference_create(&ref2, g_repo, "refs/heads/master", + git_reference_target(ref), 1, NULL)); + + git_reference_free(ref); + git_reference_free(ref2); + + cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); + nlogs_after = git_reflog_entrycount(log); + git_reflog_free(log); + + cl_assert_equal_i(nlogs_after, nlogs); +} + +static void assert_no_reflog_update(void) +{ + size_t nlogs, nlogs_after; + size_t nlogs_master, nlogs_master_after; + git_reference *ref; + git_reflog *log; + git_oid id; + + cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); + nlogs = git_reflog_entrycount(log); + git_reflog_free(log); + + cl_git_pass(git_reflog_read(&log, g_repo, "refs/heads/master")); + nlogs_master = git_reflog_entrycount(log); + git_reflog_free(log); + + /* Move it back */ + git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/master", &id, 1, NULL)); + git_reference_free(ref); + + cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); + nlogs_after = git_reflog_entrycount(log); + git_reflog_free(log); + + cl_assert_equal_i(nlogs_after, nlogs); + + cl_git_pass(git_reflog_read(&log, g_repo, "refs/heads/master")); + nlogs_master_after = git_reflog_entrycount(log); + git_reflog_free(log); + + cl_assert_equal_i(nlogs_after, nlogs); + cl_assert_equal_i(nlogs_master_after, nlogs_master); + +} + +void test_refs_reflog_reflog__logallrefupdates_bare_set_false(void) +{ + git_config *config; + + cl_git_pass(git_repository_config(&config, g_repo)); + cl_git_pass(git_config_set_bool(config, "core.logallrefupdates", false)); + git_config_free(config); + + assert_no_reflog_update(); +} + +void test_refs_reflog_reflog__logallrefupdates_bare_set_always(void) +{ + git_config *config; + git_reference *ref; + git_reflog *log; + git_oid id; + + cl_git_pass(git_repository_config(&config, g_repo)); + cl_git_pass(git_config_set_string(config, "core.logallrefupdates", "always")); + git_config_free(config); + + git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + cl_git_pass(git_reference_create(&ref, g_repo, "refs/bork", &id, 1, "message")); + + cl_git_pass(git_reflog_read(&log, g_repo, "refs/bork")); + cl_assert_equal_i(1, git_reflog_entrycount(log)); + cl_assert_equal_s("message", git_reflog_entry_byindex(log, 0)->msg); + + git_reflog_free(log); + git_reference_free(ref); +} + +void test_refs_reflog_reflog__logallrefupdates_bare_unset(void) +{ + git_config *config; + + cl_git_pass(git_repository_config(&config, g_repo)); + cl_git_pass(git_config_delete_entry(config, "core.logallrefupdates")); + git_config_free(config); + + assert_no_reflog_update(); +} + +void test_refs_reflog_reflog__logallrefupdates_nonbare_set_false(void) +{ + git_config *config; + + cl_git_sandbox_cleanup(); + g_repo = cl_git_sandbox_init("testrepo"); + + + cl_git_pass(git_repository_config(&config, g_repo)); + cl_git_pass(git_config_set_bool(config, "core.logallrefupdates", false)); + git_config_free(config); + + assert_no_reflog_update(); +} diff --git a/tests/libgit2/refs/reflog/reflog_helpers.c b/tests/libgit2/refs/reflog/reflog_helpers.c new file mode 100644 index 000000000..2ea41ee06 --- /dev/null +++ b/tests/libgit2/refs/reflog/reflog_helpers.c @@ -0,0 +1,120 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "reflog.h" +#include "reflog_helpers.h" + +int reflog_entry_tostr(git_str *out, const git_reflog_entry *entry) +{ + char old_oid[GIT_OID_HEXSZ], new_oid[GIT_OID_HEXSZ]; + + assert(out && entry); + + git_oid_tostr((char *)&old_oid, GIT_OID_HEXSZ, git_reflog_entry_id_old(entry)); + git_oid_tostr((char *)&new_oid, GIT_OID_HEXSZ, git_reflog_entry_id_new(entry)); + + return git_str_printf(out, "%s %s %s %s", old_oid, new_oid, "somesig", git_reflog_entry_message(entry)); +} + +size_t reflog_entrycount(git_repository *repo, const char *name) +{ + git_reflog *log; + size_t ret; + + cl_git_pass(git_reflog_read(&log, repo, name)); + ret = git_reflog_entrycount(log); + git_reflog_free(log); + + return ret; +} + +void cl_reflog_check_entry_(git_repository *repo, const char *reflog, size_t idx, + const char *old_spec, const char *new_spec, + const char *email, const char *message, + const char *file, const char *func, int line) +{ + git_reflog *log; + const git_reflog_entry *entry; + git_str result = GIT_STR_INIT; + + cl_git_pass(git_reflog_read(&log, repo, reflog)); + entry = git_reflog_entry_byindex(log, idx); + if (entry == NULL) + clar__fail(file, func, line, "Reflog has no such entry", NULL, 1); + + if (old_spec) { + git_object *obj = NULL; + if (git_revparse_single(&obj, repo, old_spec) == GIT_OK) { + if (git_oid_cmp(git_object_id(obj), git_reflog_entry_id_old(entry)) != 0) { + git_oid__writebuf(&result, "\tOld OID: \"", git_object_id(obj)); + git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_old(entry)); + git_str_puts(&result, "\"\n"); + } + git_object_free(obj); + } else { + git_oid *oid = git__calloc(1, sizeof(*oid)); + git_oid_fromstr(oid, old_spec); + if (git_oid_cmp(oid, git_reflog_entry_id_old(entry)) != 0) { + git_oid__writebuf(&result, "\tOld OID: \"", oid); + git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_old(entry)); + git_str_puts(&result, "\"\n"); + } + git__free(oid); + } + } + if (new_spec) { + git_object *obj = NULL; + if (git_revparse_single(&obj, repo, new_spec) == GIT_OK) { + if (git_oid_cmp(git_object_id(obj), git_reflog_entry_id_new(entry)) != 0) { + git_oid__writebuf(&result, "\tNew OID: \"", git_object_id(obj)); + git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_new(entry)); + git_str_puts(&result, "\"\n"); + } + git_object_free(obj); + } else { + git_oid *oid = git__calloc(1, sizeof(*oid)); + git_oid_fromstr(oid, new_spec); + if (git_oid_cmp(oid, git_reflog_entry_id_new(entry)) != 0) { + git_oid__writebuf(&result, "\tNew OID: \"", oid); + git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_new(entry)); + git_str_puts(&result, "\"\n"); + } + git__free(oid); + } + } + + if (email && strcmp(email, git_reflog_entry_committer(entry)->email) != 0) + git_str_printf(&result, "\tEmail: \"%s\" != \"%s\"\n", email, git_reflog_entry_committer(entry)->email); + + if (message) { + const char *entry_msg = git_reflog_entry_message(entry); + if (entry_msg == NULL) entry_msg = ""; + + if (entry_msg && strcmp(message, entry_msg) != 0) + git_str_printf(&result, "\tMessage: \"%s\" != \"%s\"\n", message, entry_msg); + } + if (git_str_len(&result) != 0) + clar__fail(file, func, line, "Reflog entry mismatch", git_str_cstr(&result), 1); + + git_str_dispose(&result); + git_reflog_free(log); +} + +void reflog_print(git_repository *repo, const char *reflog_name) +{ + git_reflog *reflog; + size_t idx; + git_str out = GIT_STR_INIT; + + git_reflog_read(&reflog, repo, reflog_name); + + for (idx = 0; idx < git_reflog_entrycount(reflog); idx++) { + const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, idx); + reflog_entry_tostr(&out, entry); + git_str_putc(&out, '\n'); + } + + fprintf(stderr, "%s", git_str_cstr(&out)); + git_str_dispose(&out); + git_reflog_free(reflog); +} diff --git a/tests/libgit2/refs/reflog/reflog_helpers.h b/tests/libgit2/refs/reflog/reflog_helpers.h new file mode 100644 index 000000000..4cd92cadc --- /dev/null +++ b/tests/libgit2/refs/reflog/reflog_helpers.h @@ -0,0 +1,12 @@ +size_t reflog_entrycount(git_repository *repo, const char *name); + +#define cl_reflog_check_entry(repo, reflog, idx, old_spec, new_spec, email, message) \ + cl_reflog_check_entry_(repo, reflog, idx, old_spec, new_spec, email, message, __FILE__, __FUNCTION__, __LINE__) + +void cl_reflog_check_entry_(git_repository *repo, const char *reflog, size_t idx, + const char *old_spec, const char *new_spec, + const char *email, const char *message, + const char *file, const char *func, int line); + +void reflog_print(git_repository *repo, const char *reflog_name); +int reflog_entry_tostr(git_str *out, const git_reflog_entry *entry); diff --git a/tests/libgit2/refs/rename.c b/tests/libgit2/refs/rename.c new file mode 100644 index 000000000..f71e65782 --- /dev/null +++ b/tests/libgit2/refs/rename.c @@ -0,0 +1,368 @@ +#include "clar_libgit2.h" + +#include "futils.h" +#include "git2/reflog.h" +#include "reflog.h" +#include "refs.h" +#include "ref_helpers.h" + +static const char *loose_tag_ref_name = "refs/tags/e90810b"; +static const char *packed_head_name = "refs/heads/packed"; +static const char *packed_test_head_name = "refs/heads/packed-test"; +static const char *ref_one_name = "refs/heads/one/branch"; +static const char *ref_one_name_new = "refs/heads/two/branch"; +static const char *ref_two_name = "refs/heads/two"; +static const char *ref_master_name = "refs/heads/master"; +static const char *ref_two_name_new = "refs/heads/two/two"; + +static git_repository *g_repo; + + + +void test_refs_rename__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_set_ident(g_repo, "me", "foo@example.com")); +} + +void test_refs_rename__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + + +void test_refs_rename__loose(void) +{ + /* rename a loose reference */ + git_reference *looked_up_ref, *new_ref, *another_looked_up_ref; + git_str temp_path = GIT_STR_INIT; + const char *new_name = "refs/tags/Nemo/knows/refs.kung-fu"; + + /* Ensure the ref doesn't exist on the file system */ + cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), new_name)); + cl_assert(!git_fs_path_exists(temp_path.ptr)); + + /* Retrieval of the reference to rename */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, loose_tag_ref_name)); + + /* ... which is indeed loose */ + cl_assert(reference_is_packed(looked_up_ref) == 0); + + /* Now that the reference is renamed... */ + cl_git_pass(git_reference_rename(&new_ref, looked_up_ref, new_name, 0, NULL)); + cl_assert_equal_s(new_ref->name, new_name); + git_reference_free(looked_up_ref); + + /* ...It can't be looked-up with the old name... */ + cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, loose_tag_ref_name)); + + /* ...but the new name works ok... */ + cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, new_name)); + cl_assert_equal_s(new_ref->name, new_name); + + /* .. the new ref is loose... */ + cl_assert(reference_is_packed(another_looked_up_ref) == 0); + cl_assert(reference_is_packed(new_ref) == 0); + + /* ...and the ref can be found in the file system */ + cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), new_name)); + cl_assert(git_fs_path_exists(temp_path.ptr)); + + git_reference_free(new_ref); + git_reference_free(another_looked_up_ref); + git_str_dispose(&temp_path); +} + +void test_refs_rename__packed(void) +{ + /* rename a packed reference (should make it loose) */ + git_reference *looked_up_ref, *new_ref, *another_looked_up_ref; + git_str temp_path = GIT_STR_INIT; + const char *brand_new_name = "refs/heads/brand_new_name"; + + /* Ensure the ref doesn't exist on the file system */ + cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), packed_head_name)); + cl_assert(!git_fs_path_exists(temp_path.ptr)); + + /* The reference can however be looked-up... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); + + /* .. and it's packed */ + cl_assert(reference_is_packed(looked_up_ref) != 0); + + /* Now that the reference is renamed... */ + cl_git_pass(git_reference_rename(&new_ref, looked_up_ref, brand_new_name, 0, NULL)); + cl_assert_equal_s(new_ref->name, brand_new_name); + git_reference_free(looked_up_ref); + + /* ...It can't be looked-up with the old name... */ + cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, packed_head_name)); + + /* ...but the new name works ok... */ + cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, brand_new_name)); + cl_assert_equal_s(another_looked_up_ref->name, brand_new_name); + + /* .. the ref is no longer packed... */ + cl_assert(reference_is_packed(another_looked_up_ref) == 0); + cl_assert(reference_is_packed(new_ref) == 0); + + /* ...and the ref now happily lives in the file system */ + cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), brand_new_name)); + cl_assert(git_fs_path_exists(temp_path.ptr)); + + git_reference_free(new_ref); + git_reference_free(another_looked_up_ref); + git_str_dispose(&temp_path); +} + +void test_refs_rename__packed_doesnt_pack_others(void) +{ + /* renaming a packed reference does not pack another reference which happens to be in both loose and pack state */ + git_reference *looked_up_ref, *another_looked_up_ref, *renamed_ref; + git_str temp_path = GIT_STR_INIT; + const char *brand_new_name = "refs/heads/brand_new_name"; + + /* Ensure the other reference exists on the file system */ + cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), packed_test_head_name)); + cl_assert(git_fs_path_exists(temp_path.ptr)); + + /* Lookup the other reference */ + cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name)); + + /* Ensure it's loose */ + cl_assert(reference_is_packed(another_looked_up_ref) == 0); + git_reference_free(another_looked_up_ref); + + /* Lookup the reference to rename */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); + + /* Ensure it's packed */ + cl_assert(reference_is_packed(looked_up_ref) != 0); + + /* Now that the reference is renamed... */ + cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, brand_new_name, 0, NULL)); + git_reference_free(looked_up_ref); + + /* Lookup the other reference */ + cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name)); + + /* Ensure it's loose */ + cl_assert(reference_is_packed(another_looked_up_ref) == 0); + + /* Ensure the other ref still exists on the file system */ + cl_assert(git_fs_path_exists(temp_path.ptr)); + + git_reference_free(renamed_ref); + git_reference_free(another_looked_up_ref); + git_str_dispose(&temp_path); +} + +void test_refs_rename__name_collision(void) +{ + /* can not rename a reference with the name of an existing reference */ + git_reference *looked_up_ref, *renamed_ref; + + /* An existing reference... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); + + /* Can not be renamed to the name of another existing reference. */ + cl_git_fail(git_reference_rename(&renamed_ref, looked_up_ref, packed_test_head_name, 0, NULL)); + git_reference_free(looked_up_ref); + + /* Failure to rename it hasn't corrupted its state */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); + cl_assert_equal_s(looked_up_ref->name, packed_head_name); + + git_reference_free(looked_up_ref); +} + +void test_refs_rename__invalid_name(void) +{ + /* can not rename a reference with an invalid name */ + git_reference *looked_up_ref, *renamed_ref; + + /* An existing oid reference... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); + + /* Can not be renamed with an invalid name. */ + cl_assert_equal_i( + GIT_EINVALIDSPEC, + git_reference_rename(&renamed_ref, looked_up_ref, "Hello! I'm a very invalid name.", 0, NULL)); + + /* Can not be renamed outside of the refs hierarchy + * unless it's ALL_CAPS_AND_UNDERSCORES. + */ + cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_rename(&renamed_ref, looked_up_ref, "i-will-sudo-you", 0, NULL)); + + /* Failure to rename it hasn't corrupted its state */ + git_reference_free(looked_up_ref); + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); + cl_assert_equal_s(looked_up_ref->name, packed_test_head_name); + + git_reference_free(looked_up_ref); +} + +void test_refs_rename__force_loose_packed(void) +{ + /* can force-rename a packed reference with the name of an existing loose and packed reference */ + git_reference *looked_up_ref, *renamed_ref; + git_oid oid; + + /* An existing reference... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); + git_oid_cpy(&oid, git_reference_target(looked_up_ref)); + + /* Can be force-renamed to the name of another existing reference. */ + cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, packed_test_head_name, 1, NULL)); + git_reference_free(looked_up_ref); + git_reference_free(renamed_ref); + + /* Check we actually renamed it */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); + cl_assert_equal_s(looked_up_ref->name, packed_test_head_name); + cl_assert_equal_oid(&oid, git_reference_target(looked_up_ref)); + git_reference_free(looked_up_ref); + + /* And that the previous one doesn't exist any longer */ + cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); +} + +void test_refs_rename__force_loose(void) +{ + /* can force-rename a loose reference with the name of an existing loose reference */ + git_reference *looked_up_ref, *renamed_ref; + git_oid oid; + + /* An existing reference... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, "refs/heads/br2")); + git_oid_cpy(&oid, git_reference_target(looked_up_ref)); + + /* Can be force-renamed to the name of another existing reference. */ + cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, "refs/heads/test", 1, NULL)); + git_reference_free(looked_up_ref); + git_reference_free(renamed_ref); + + /* Check we actually renamed it */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, "refs/heads/test")); + cl_assert_equal_s(looked_up_ref->name, "refs/heads/test"); + cl_assert_equal_oid(&oid, git_reference_target(looked_up_ref)); + git_reference_free(looked_up_ref); + + /* And that the previous one doesn't exist any longer */ + cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, "refs/heads/br2")); + + git_reference_free(looked_up_ref); +} + + +void test_refs_rename__overwrite(void) +{ + /* can not overwrite name of existing reference */ + git_reference *ref, *ref_one, *ref_one_new, *ref_two; + git_refdb *refdb; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); + + git_oid_cpy(&id, git_reference_target(ref)); + + /* Create loose references */ + cl_git_pass(git_reference_create(&ref_one, g_repo, ref_one_name, &id, 0, NULL)); + cl_git_pass(git_reference_create(&ref_two, g_repo, ref_two_name, &id, 0, NULL)); + + /* Pack everything */ + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); + + /* Attempt to create illegal reference */ + cl_git_fail(git_reference_create(&ref_one_new, g_repo, ref_one_name_new, &id, 0, NULL)); + + /* Illegal reference couldn't be created so this is supposed to fail */ + cl_git_fail(git_reference_lookup(&ref_one_new, g_repo, ref_one_name_new)); + + git_reference_free(ref); + git_reference_free(ref_one); + git_reference_free(ref_one_new); + git_reference_free(ref_two); + git_refdb_free(refdb); +} + + +void test_refs_rename__prefix(void) +{ + /* can be renamed to a new name prefixed with the old name */ + git_reference *ref, *ref_two, *looked_up_ref, *renamed_ref; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); + + git_oid_cpy(&id, git_reference_target(ref)); + + /* Create loose references */ + cl_git_pass(git_reference_create(&ref_two, g_repo, ref_two_name, &id, 0, NULL)); + + /* An existing reference... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name)); + + /* Can be rename to a new name starting with the old name. */ + cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, ref_two_name_new, 0, NULL)); + git_reference_free(looked_up_ref); + git_reference_free(renamed_ref); + + /* Check we actually renamed it */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new)); + cl_assert_equal_s(looked_up_ref->name, ref_two_name_new); + git_reference_free(looked_up_ref); + cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name)); + + git_reference_free(ref); + git_reference_free(ref_two); + git_reference_free(looked_up_ref); +} + +void test_refs_rename__move_up(void) +{ + /* can move a reference to a upper reference hierarchy */ + git_reference *ref, *ref_two, *looked_up_ref, *renamed_ref; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); + + git_oid_cpy(&id, git_reference_target(ref)); + + /* Create loose references */ + cl_git_pass(git_reference_create(&ref_two, g_repo, ref_two_name_new, &id, 0, NULL)); + git_reference_free(ref_two); + + /* An existing reference... */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new)); + + /* Can be renamed upward the reference tree. */ + cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, ref_two_name, 0, NULL)); + git_reference_free(looked_up_ref); + git_reference_free(renamed_ref); + + /* Check we actually renamed it */ + cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name)); + cl_assert_equal_s(looked_up_ref->name, ref_two_name); + git_reference_free(looked_up_ref); + + cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new)); + git_reference_free(ref); + git_reference_free(looked_up_ref); +} + +void test_refs_rename__propagate_eexists(void) +{ + git_reference *ref, *new_ref; + + cl_git_pass(git_reference_lookup(&ref, g_repo, packed_head_name)); + + cl_assert_equal_i(GIT_EEXISTS, git_reference_rename(&new_ref, ref, packed_test_head_name, 0, NULL)); + + git_reference_free(ref); +} diff --git a/tests/libgit2/refs/revparse.c b/tests/libgit2/refs/revparse.c new file mode 100644 index 000000000..0bd2ae5bc --- /dev/null +++ b/tests/libgit2/refs/revparse.c @@ -0,0 +1,890 @@ +#include "clar_libgit2.h" + +#include "git2/revparse.h" +#include "refs.h" +#include "path.h" + +static git_repository *g_repo; +static git_object *g_obj; + +/* Helpers */ +static void test_object_and_ref_inrepo( + const char *spec, + const char *expected_oid, + const char *expected_refname, + git_repository *repo, + bool assert_reference_retrieval) +{ + char objstr[64] = {0}; + git_object *obj = NULL; + git_reference *ref = NULL; + int error; + + error = git_revparse_ext(&obj, &ref, repo, spec); + + if (expected_oid != NULL) { + cl_git_pass(error); + git_oid_fmt(objstr, git_object_id(obj)); + cl_assert_equal_s(objstr, expected_oid); + } else + cl_git_fail(error); + + if (assert_reference_retrieval) { + if (expected_refname == NULL) + cl_assert(NULL == ref); + else + cl_assert_equal_s(expected_refname, git_reference_name(ref)); + } + + git_object_free(obj); + git_reference_free(ref); +} + +static void test_object_inrepo(const char *spec, const char *expected_oid, git_repository *repo) +{ + test_object_and_ref_inrepo(spec, expected_oid, NULL, repo, false); +} + +static void test_id_inrepo( + const char *spec, + const char *expected_left, + const char *expected_right, + git_revspec_t expected_flags, + git_repository *repo) +{ + git_revspec revspec; + int error = git_revparse(&revspec, repo, spec); + + if (expected_left) { + char str[64] = {0}; + cl_assert_equal_i(0, error); + git_oid_fmt(str, git_object_id(revspec.from)); + cl_assert_equal_s(str, expected_left); + git_object_free(revspec.from); + } else { + cl_assert_equal_i(GIT_ENOTFOUND, error); + } + + if (expected_right) { + char str[64] = {0}; + git_oid_fmt(str, git_object_id(revspec.to)); + cl_assert_equal_s(str, expected_right); + git_object_free(revspec.to); + } + + if (expected_flags) + cl_assert_equal_i(expected_flags, revspec.flags); +} + +static void test_object(const char *spec, const char *expected_oid) +{ + test_object_inrepo(spec, expected_oid, g_repo); +} + +static void test_object_and_ref(const char *spec, const char *expected_oid, const char *expected_refname) +{ + test_object_and_ref_inrepo(spec, expected_oid, expected_refname, g_repo, true); +} + +static void test_rangelike(const char *rangelike, + const char *expected_left, + const char *expected_right, + git_revspec_t expected_revparseflags) +{ + char objstr[64] = {0}; + git_revspec revspec; + int error; + + error = git_revparse(&revspec, g_repo, rangelike); + + if (expected_left != NULL) { + cl_assert_equal_i(0, error); + cl_assert_equal_i(revspec.flags, expected_revparseflags); + git_oid_fmt(objstr, git_object_id(revspec.from)); + cl_assert_equal_s(objstr, expected_left); + git_oid_fmt(objstr, git_object_id(revspec.to)); + cl_assert_equal_s(objstr, expected_right); + } else + cl_assert(error != 0); + + git_object_free(revspec.from); + git_object_free(revspec.to); +} + + +static void test_id( + const char *spec, + const char *expected_left, + const char *expected_right, + git_revspec_t expected_flags) +{ + test_id_inrepo(spec, expected_left, expected_right, expected_flags, g_repo); +} + +static void test_invalid_revspec(const char* invalid_spec) +{ + git_revspec revspec; + + cl_assert_equal_i( + GIT_EINVALIDSPEC, git_revparse(&revspec, g_repo, invalid_spec)); +} + +void test_refs_revparse__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); +} + +void test_refs_revparse__cleanup(void) +{ + git_repository_free(g_repo); +} + +void test_refs_revparse__nonexistant_object(void) +{ + test_object("this-does-not-exist", NULL); + test_object("this-does-not-exist^1", NULL); + test_object("this-does-not-exist~2", NULL); +} + +static void assert_invalid_single_spec(const char *invalid_spec) +{ + cl_assert_equal_i( + GIT_EINVALIDSPEC, git_revparse_single(&g_obj, g_repo, invalid_spec)); +} + +void test_refs_revparse__invalid_reference_name(void) +{ + assert_invalid_single_spec("this doesn't make sense"); + assert_invalid_single_spec("Inv@{id"); + assert_invalid_single_spec(""); +} + +void test_refs_revparse__shas(void) +{ + test_object("c47800c7266a2be04c571c04d5a6614691ea99bd", "c47800c7266a2be04c571c04d5a6614691ea99bd"); + test_object("c47800c", "c47800c7266a2be04c571c04d5a6614691ea99bd"); +} + +void test_refs_revparse__head(void) +{ + test_object("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("HEAD^0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("HEAD~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); +} + +void test_refs_revparse__full_refs(void) +{ + test_object("refs/heads/master", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("refs/heads/test", "e90810b8df3e80c413d903f631643c716887138d"); + test_object("refs/tags/test", "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); +} + +void test_refs_revparse__partial_refs(void) +{ + test_object("point_to_blob", "1385f264afb75a56a5bec74243be9b367ba4ca08"); + test_object("packed-test", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); + test_object("br2", "a4a7dce85cf63874e984719f4fdd239f5145052f"); +} + +void test_refs_revparse__describe_output(void) +{ + test_object("blah-7-gc47800c", "c47800c7266a2be04c571c04d5a6614691ea99bd"); + test_object("not-good", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); +} + +void test_refs_revparse__nth_parent(void) +{ + assert_invalid_single_spec("be3563a^-1"); + assert_invalid_single_spec("^"); + assert_invalid_single_spec("be3563a^{tree}^"); + assert_invalid_single_spec("point_to_blob^{blob}^"); + assert_invalid_single_spec("this doesn't make sense^1"); + + test_object("be3563a^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); + test_object("be3563a^", "9fd738e8f7967c078dceed8190330fc8648ee56a"); + test_object("be3563a^2", "c47800c7266a2be04c571c04d5a6614691ea99bd"); + test_object("be3563a^1^1", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); + test_object("be3563a^^", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); + test_object("be3563a^2^1", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + test_object("be3563a^0", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("be3563a^{commit}^", "9fd738e8f7967c078dceed8190330fc8648ee56a"); + + test_object("be3563a^42", NULL); +} + +void test_refs_revparse__not_tag(void) +{ + test_object("point_to_blob^{}", "1385f264afb75a56a5bec74243be9b367ba4ca08"); + test_object("wrapped_tag^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master^{tree}^{}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); + test_object("e90810b^{}", "e90810b8df3e80c413d903f631643c716887138d"); + test_object("tags/e90810b^{}", "e90810b8df3e80c413d903f631643c716887138d"); + test_object("e908^{}", "e90810b8df3e80c413d903f631643c716887138d"); +} + +void test_refs_revparse__to_type(void) +{ + assert_invalid_single_spec("wrapped_tag^{trip}"); + test_object("point_to_blob^{commit}", NULL); + cl_assert_equal_i( + GIT_EPEEL, git_revparse_single(&g_obj, g_repo, "wrapped_tag^{blob}")); + + test_object("wrapped_tag^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("wrapped_tag^{tree}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); + test_object("point_to_blob^{blob}", "1385f264afb75a56a5bec74243be9b367ba4ca08"); + test_object("master^{commit}^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); +} + +void test_refs_revparse__linear_history(void) +{ + assert_invalid_single_spec("~"); + test_object("foo~bar", NULL); + + assert_invalid_single_spec("master~bar"); + assert_invalid_single_spec("master~-1"); + assert_invalid_single_spec("master~0bar"); + assert_invalid_single_spec("this doesn't make sense~2"); + assert_invalid_single_spec("be3563a^{tree}~"); + assert_invalid_single_spec("point_to_blob^{blob}~"); + + test_object("master~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master~1", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("master~2", "9fd738e8f7967c078dceed8190330fc8648ee56a"); + test_object("master~1~1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); + test_object("master~~", "9fd738e8f7967c078dceed8190330fc8648ee56a"); +} + +void test_refs_revparse__chaining(void) +{ + assert_invalid_single_spec("master@{0}@{0}"); + assert_invalid_single_spec("@{u}@{-1}"); + assert_invalid_single_spec("@{-1}@{-1}"); + assert_invalid_single_spec("@{-3}@{0}"); + + test_object("master@{0}~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); + test_object("@{u}@{0}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("@{-1}@{0}", "a4a7dce85cf63874e984719f4fdd239f5145052f"); + test_object("@{-4}@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("master~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); + test_object("master~1^2", "c47800c7266a2be04c571c04d5a6614691ea99bd"); + test_object("master^1^2~1", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + test_object("master^^2^", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + test_object("master^1^1^1^1^1", "8496071c1b46c854b31185ea97743be6a8774479"); + test_object("master^^1^2^1", NULL); +} + +void test_refs_revparse__upstream(void) +{ + assert_invalid_single_spec("e90810b@{u}"); + assert_invalid_single_spec("refs/tags/e90810b@{u}"); + test_object("refs/heads/e90810b@{u}", NULL); + + test_object("master@{upstream}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("heads/master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("refs/heads/master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); +} + +void test_refs_revparse__ordinal(void) +{ + assert_invalid_single_spec("master@{-2}"); + + /* TODO: make the test below actually fail + * cl_git_fail(git_revparse_single(&g_obj, g_repo, "master@{1a}")); + */ + + test_object("nope@{0}", NULL); + test_object("master@{31415}", NULL); + test_object("@{1000}", NULL); + test_object("@{2}", NULL); + + test_object("@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + + test_object("master@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("heads/master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("refs/heads/master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); +} + +void test_refs_revparse__previous_head(void) +{ + assert_invalid_single_spec("@{-xyz}"); + assert_invalid_single_spec("@{-0}"); + assert_invalid_single_spec("@{-1b}"); + + test_object("@{-42}", NULL); + + test_object("@{-2}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("@{-1}", "a4a7dce85cf63874e984719f4fdd239f5145052f"); +} + +static void create_fake_stash_reference_and_reflog(git_repository *repo) +{ + git_reference *master, *new_master; + git_str log_path = GIT_STR_INIT; + + git_str_joinpath(&log_path, git_repository_path(repo), "logs/refs/fakestash"); + + cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&log_path))); + + cl_git_pass(git_reference_lookup(&master, repo, "refs/heads/master")); + cl_git_pass(git_reference_rename(&new_master, master, "refs/fakestash", 0, NULL)); + git_reference_free(master); + + cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&log_path))); + + git_str_dispose(&log_path); + git_reference_free(new_master); +} + +void test_refs_revparse__reflog_of_a_ref_under_refs(void) +{ + git_repository *repo = cl_git_sandbox_init("testrepo.git"); + + test_object_inrepo("refs/fakestash", NULL, repo); + + create_fake_stash_reference_and_reflog(repo); + + /* + * $ git reflog -1 refs/fakestash + * a65fedf refs/fakestash@{0}: commit: checking in + * + * $ git reflog -1 refs/fakestash@{0} + * a65fedf refs/fakestash@{0}: commit: checking in + * + * $ git reflog -1 fakestash + * a65fedf fakestash@{0}: commit: checking in + * + * $ git reflog -1 fakestash@{0} + * a65fedf fakestash@{0}: commit: checking in + */ + test_object_inrepo("refs/fakestash", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); + test_object_inrepo("refs/fakestash@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); + test_object_inrepo("fakestash", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); + test_object_inrepo("fakestash@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); + + cl_git_sandbox_cleanup(); +} + +void test_refs_revparse__revwalk(void) +{ + test_object("master^{/not found in any commit}", NULL); + test_object("master^{/merge}", NULL); + assert_invalid_single_spec("master^{/((}"); + + test_object("master^{/anoth}", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + test_object("master^{/Merge}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("br2^{/Merge}", "a4a7dce85cf63874e984719f4fdd239f5145052f"); + test_object("master^{/fo.rth}", "9fd738e8f7967c078dceed8190330fc8648ee56a"); +} + +void test_refs_revparse__date(void) +{ + /* + * $ git reflog HEAD --date=iso + * a65fedf HEAD@{2012-04-30 08:23:41 -0900}: checkout: moving from br2 to master + * a4a7dce HEAD@{2012-04-30 08:23:37 -0900}: commit: checking in + * c47800c HEAD@{2012-04-30 08:23:28 -0900}: checkout: moving from master to br2 + * a65fedf HEAD@{2012-04-30 08:23:23 -0900}: commit: + * be3563a HEAD@{2012-04-30 10:22:43 -0700}: clone: from /Users/ben/src/libgit2/tes + * + * $ git reflog HEAD --date=raw + * a65fedf HEAD@{1335806621 -0900}: checkout: moving from br2 to master + * a4a7dce HEAD@{1335806617 -0900}: commit: checking in + * c47800c HEAD@{1335806608 -0900}: checkout: moving from master to br2 + * a65fedf HEAD@{1335806603 -0900}: commit: + * be3563a HEAD@{1335806563 -0700}: clone: from /Users/ben/src/libgit2/tests/resour + */ + test_object("HEAD@{10 years ago}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + + test_object("HEAD@{1 second}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("HEAD@{1 second ago}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("HEAD@{2 days ago}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + /* + * $ git reflog master --date=iso + * a65fedf master@{2012-04-30 09:23:23 -0800}: commit: checking in + * be3563a master@{2012-04-30 09:22:43 -0800}: clone: from /Users/ben/src... + * + * $ git reflog master --date=raw + * a65fedf master@{1335806603 -0800}: commit: checking in + * be3563a master@{1335806563 -0800}: clone: from /Users/ben/src/libgit2/tests/reso + */ + + + /* + * $ git rev-parse "master@{2012-04-30 17:22:42 +0000}" + * warning: log for 'master' only goes back to Mon, 30 Apr 2012 09:22:43 -0800 + * be3563ae3f795b2b4353bcce3a527ad0a4f7f644 + */ + test_object("master@{2012-04-30 17:22:42 +0000}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("master@{2012-04-30 09:22:42 -0800}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + + /* + * $ git reflog -1 "master@{2012-04-30 17:22:43 +0000}" + * be3563a master@{Mon Apr 30 09:22:43 2012 -0800}: clone: from /Users/ben/src/libg + */ + test_object("master@{2012-04-30 17:22:43 +0000}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("master@{2012-04-30 09:22:43 -0800}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + + /* + * $ git reflog -1 "master@{2012-4-30 09:23:27 -0800}" + * a65fedf master@{Mon Apr 30 09:23:23 2012 -0800}: commit: checking in + */ + test_object("master@{2012-4-30 09:23:27 -0800}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + /* + * $ git reflog -1 master@{2012-05-03} + * a65fedf master@{Mon Apr 30 09:23:23 2012 -0800}: commit: checking in + */ + test_object("master@{2012-05-03}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + /* + * $ git reflog -1 "master@{1335806603}" + * a65fedf + * + * $ git reflog -1 "master@{1335806602}" + * be3563a + */ + test_object("master@{1335806603}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master@{1335806602}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + + /* + * $ git rev-parse "with-empty-log@{2 days ago}" -- + * fatal: log for refs/heads/with-empty-log is empty + */ + test_object("with-empty-log@{2 days ago}", NULL); +} + +void test_refs_revparse__invalid_date(void) +{ + /* + * $ git rev-parse HEAD@{} -- + * fatal: bad revision 'HEAD@{}' + * + * $ git rev-parse HEAD@{NEITHER_INTEGER_NOR_DATETIME} -- + * fatal: bad revision 'HEAD@{NEITHER_INTEGER_NOR_DATETIME}' + */ + test_object("HEAD@{}", NULL); + test_object("HEAD@{NEITHER_INTEGER_NOR_DATETIME}", NULL); +} + +void test_refs_revparse__colon(void) +{ + assert_invalid_single_spec(":/"); + assert_invalid_single_spec("point_to_blob:readme.txt"); + cl_git_fail(git_revparse_single(&g_obj, g_repo, ":2:README")); /* Not implemented */ + + test_object(":/not found in any commit", NULL); + test_object("subtrees:ab/42.txt", NULL); + test_object("subtrees:ab/4.txt/nope", NULL); + test_object("subtrees:nope", NULL); + test_object("test/master^1:branch_file.txt", NULL); + + /* From tags */ + test_object("test:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf"); + test_object("tags/test:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf"); + test_object("e90810b:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf"); + test_object("tags/e90810b:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf"); + + /* From commits */ + test_object("a65f:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc"); + + /* From trees */ + test_object("a65f^{tree}:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc"); + test_object("944c:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc"); + + /* Retrieving trees */ + test_object("master:", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); + test_object("subtrees:", "ae90f12eea699729ed24555e40b9fd669da12a12"); + test_object("subtrees:ab", "f1425cef211cc08caa31e7b545ffb232acb098c3"); + test_object("subtrees:ab/", "f1425cef211cc08caa31e7b545ffb232acb098c3"); + + /* Retrieving blobs */ + test_object("subtrees:ab/4.txt", "d6c93164c249c8000205dd4ec5cbca1b516d487f"); + test_object("subtrees:ab/de/fgh/1.txt", "1f67fc4386b2d171e0d21be1c447e12660561f9b"); + test_object("master:README", "a8233120f6ad708f843d861ce2b7228ec4e3dec6"); + test_object("master:new.txt", "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"); + test_object(":/Merge", "a4a7dce85cf63874e984719f4fdd239f5145052f"); + test_object(":/one", "c47800c7266a2be04c571c04d5a6614691ea99bd"); + test_object(":/packed commit t", "41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9"); + test_object("test/master^2:branch_file.txt", "45b983be36b73c0788dc9cbcb76cbb80fc7bb057"); + test_object("test/master@{1}:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc"); +} + +void test_refs_revparse__disambiguation(void) +{ + /* + * $ git show e90810b + * tag e90810b + * Tagger: Vicent Marti + * Date: Thu Aug 12 03:59:17 2010 +0200 + * + * This is a very simple tag. + * + * commit e90810b8df3e80c413d903f631643c716887138d + * Author: Vicent Marti + * Date: Thu Aug 5 18:42:20 2010 +0200 + * + * Test commit 2 + * + * diff --git a/readme.txt b/readme.txt + * index 6336846..0266163 100644 + * --- a/readme.txt + * +++ b/readme.txt + * @@ -1 +1,2 @@ + * Testing a readme.txt + * +Now we add a single line here + * + * $ git show-ref e90810b + * 7b4384978d2493e851f9cca7858815fac9b10980 refs/tags/e90810b + * + */ + test_object("e90810b", "7b4384978d2493e851f9cca7858815fac9b10980"); + + /* + * $ git show e90810 + * commit e90810b8df3e80c413d903f631643c716887138d + * Author: Vicent Marti + * Date: Thu Aug 5 18:42:20 2010 +0200 + * + * Test commit 2 + * + * diff --git a/readme.txt b/readme.txt + * index 6336846..0266163 100644 + * --- a/readme.txt + * +++ b/readme.txt + * @@ -1 +1,2 @@ + * Testing a readme.txt + * +Now we add a single line here + */ + test_object("e90810", "e90810b8df3e80c413d903f631643c716887138d"); +} + +void test_refs_revparse__a_too_short_objectid_returns_EAMBIGUOUS(void) +{ + cl_assert_equal_i( + GIT_EAMBIGUOUS, git_revparse_single(&g_obj, g_repo, "e90")); +} + +/* + * $ echo "aabqhq" | git hash-object -t blob --stdin + * dea509d0b3cb8ee0650f6ca210bc83f4678851ba + * + * $ echo "aaazvc" | git hash-object -t blob --stdin + * dea509d097ce692e167dfc6a48a7a280cc5e877e + */ +void test_refs_revparse__a_not_precise_enough_objectid_returns_EAMBIGUOUS(void) +{ + git_repository *repo; + git_index *index; + git_object *obj; + + repo = cl_git_sandbox_init("testrepo"); + + cl_git_mkfile("testrepo/one.txt", "aabqhq\n"); + cl_git_mkfile("testrepo/two.txt", "aaazvc\n"); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, "one.txt")); + cl_git_pass(git_index_add_bypath(index, "two.txt")); + + cl_git_fail_with(git_revparse_single(&obj, repo, "dea509d0"), GIT_EAMBIGUOUS); + + cl_git_pass(git_revparse_single(&obj, repo, "dea509d09")); + + git_object_free(obj); + git_index_free(index); + cl_git_sandbox_cleanup(); +} + +void test_refs_revparse__issue_994(void) +{ + git_repository *repo; + git_reference *head, *with_at; + git_object *target; + + repo = cl_git_sandbox_init("testrepo.git"); + + cl_assert_equal_i(GIT_ENOTFOUND, + git_revparse_single(&target, repo, "origin/bim_with_3d@11296")); + + cl_assert_equal_i(GIT_ENOTFOUND, + git_revparse_single(&target, repo, "refs/remotes/origin/bim_with_3d@11296")); + + + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_create( + &with_at, + repo, + "refs/remotes/origin/bim_with_3d@11296", + git_reference_target(head), + 0, + NULL)); + + cl_git_pass(git_revparse_single(&target, repo, "origin/bim_with_3d@11296")); + git_object_free(target); + + cl_git_pass(git_revparse_single(&target, repo, "refs/remotes/origin/bim_with_3d@11296")); + git_object_free(target); + + git_reference_free(with_at); + git_reference_free(head); + cl_git_sandbox_cleanup(); +} + +/** + * $ git rev-parse blah-7-gc47800c + * c47800c7266a2be04c571c04d5a6614691ea99bd + * + * $ git rev-parse HEAD~3 + * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + * + * $ git branch blah-7-gc47800c HEAD~3 + * + * $ git rev-parse blah-7-gc47800c + * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + */ +void test_refs_revparse__try_to_retrieve_branch_before_described_tag(void) +{ + git_repository *repo; + git_reference *branch; + git_object *target; + char sha[GIT_OID_HEXSZ + 1]; + + repo = cl_git_sandbox_init("testrepo.git"); + + test_object_inrepo("blah-7-gc47800c", "c47800c7266a2be04c571c04d5a6614691ea99bd", repo); + + cl_git_pass(git_revparse_single(&target, repo, "HEAD~3")); + cl_git_pass(git_branch_create(&branch, repo, "blah-7-gc47800c", (git_commit *)target, 0)); + + git_oid_tostr(sha, GIT_OID_HEXSZ + 1, git_object_id(target)); + + test_object_inrepo("blah-7-gc47800c", sha, repo); + + git_reference_free(branch); + git_object_free(target); + cl_git_sandbox_cleanup(); +} + +/** + * $ git rev-parse a65fedf39aefe402d3bb6e24df4d4f5fe4547750 + * a65fedf39aefe402d3bb6e24df4d4f5fe4547750 + * + * $ git rev-parse HEAD~3 + * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + * + * $ git branch a65fedf39aefe402d3bb6e24df4d4f5fe4547750 HEAD~3 + * + * $ git rev-parse a65fedf39aefe402d3bb6e24df4d4f5fe4547750 + * a65fedf39aefe402d3bb6e24df4d4f5fe4547750 + * + * $ git rev-parse heads/a65fedf39aefe402d3bb6e24df4d4f5fe4547750 + * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + */ +void test_refs_revparse__try_to_retrieve_sha_before_branch(void) +{ + git_repository *repo; + git_reference *branch; + git_object *target; + char sha[GIT_OID_HEXSZ + 1]; + + repo = cl_git_sandbox_init("testrepo.git"); + + test_object_inrepo("a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); + + cl_git_pass(git_revparse_single(&target, repo, "HEAD~3")); + cl_git_pass(git_branch_create(&branch, repo, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", (git_commit *)target, 0)); + + git_oid_tostr(sha, GIT_OID_HEXSZ + 1, git_object_id(target)); + + test_object_inrepo("a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); + test_object_inrepo("heads/a65fedf39aefe402d3bb6e24df4d4f5fe4547750", sha, repo); + + git_reference_free(branch); + git_object_free(target); + cl_git_sandbox_cleanup(); +} + +/** + * $ git rev-parse c47800 + * c47800c7266a2be04c571c04d5a6614691ea99bd + * + * $ git rev-parse HEAD~3 + * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + * + * $ git branch c47800 HEAD~3 + * + * $ git rev-parse c47800 + * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + */ +void test_refs_revparse__try_to_retrieve_branch_before_abbrev_sha(void) +{ + git_repository *repo; + git_reference *branch; + git_object *target; + char sha[GIT_OID_HEXSZ + 1]; + + repo = cl_git_sandbox_init("testrepo.git"); + + test_object_inrepo("c47800", "c47800c7266a2be04c571c04d5a6614691ea99bd", repo); + + cl_git_pass(git_revparse_single(&target, repo, "HEAD~3")); + cl_git_pass(git_branch_create(&branch, repo, "c47800", (git_commit *)target, 0)); + + git_oid_tostr(sha, GIT_OID_HEXSZ + 1, git_object_id(target)); + + test_object_inrepo("c47800", sha, repo); + + git_reference_free(branch); + git_object_free(target); + cl_git_sandbox_cleanup(); +} + + +void test_refs_revparse__range(void) +{ + assert_invalid_single_spec("be3563a^1..be3563a"); + + test_rangelike("be3563a^1..be3563a", + "9fd738e8f7967c078dceed8190330fc8648ee56a", + "be3563ae3f795b2b4353bcce3a527ad0a4f7f644", + GIT_REVSPEC_RANGE); + + test_rangelike("be3563a^1...be3563a", + "9fd738e8f7967c078dceed8190330fc8648ee56a", + "be3563ae3f795b2b4353bcce3a527ad0a4f7f644", + GIT_REVSPEC_RANGE | GIT_REVSPEC_MERGE_BASE); + + test_rangelike("be3563a^1.be3563a", NULL, NULL, 0); +} + +void test_refs_revparse__parses_range_operator(void) +{ + test_id("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", NULL, GIT_REVSPEC_SINGLE); + test_id("HEAD~3..HEAD", + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + GIT_REVSPEC_RANGE); + + test_id("HEAD~3...HEAD", + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + GIT_REVSPEC_RANGE | GIT_REVSPEC_MERGE_BASE); + + test_id("HEAD~3..", + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + GIT_REVSPEC_RANGE); + + test_id("HEAD~3...", + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + GIT_REVSPEC_RANGE | GIT_REVSPEC_MERGE_BASE); + + test_id("..HEAD~3", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", + GIT_REVSPEC_RANGE); + + test_id("...HEAD~3", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", + GIT_REVSPEC_RANGE | GIT_REVSPEC_MERGE_BASE); + + test_id("...", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + GIT_REVSPEC_RANGE | GIT_REVSPEC_MERGE_BASE); + + test_invalid_revspec(".."); +} + +void test_refs_revparse__ext_retrieves_both_the_reference_and_its_target(void) +{ + test_object_and_ref( + "master@{upstream}", + "be3563ae3f795b2b4353bcce3a527ad0a4f7f644", + "refs/remotes/test/master"); + + test_object_and_ref( + "@{-1}", + "a4a7dce85cf63874e984719f4fdd239f5145052f", + "refs/heads/br2"); +} + +void test_refs_revparse__ext_can_expand_short_reference_names(void) +{ + test_object_and_ref( + "master", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "refs/heads/master"); + + test_object_and_ref( + "HEAD", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + "refs/heads/master"); + + test_object_and_ref( + "tags/test", + "b25fa35b38051e4ae45d4222e795f9df2e43f1d1", + "refs/tags/test"); +} + +void test_refs_revparse__ext_returns_NULL_reference_when_expression_points_at_a_revision(void) +{ + test_object_and_ref( + "HEAD~3", + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", + NULL); + + test_object_and_ref( + "HEAD~0", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + NULL); + + test_object_and_ref( + "HEAD^0", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", + NULL); + + test_object_and_ref( + "@{-1}@{0}", + "a4a7dce85cf63874e984719f4fdd239f5145052f", + NULL); +} + +void test_refs_revparse__ext_returns_NULL_reference_when_expression_points_at_a_tree_content(void) +{ + test_object_and_ref( + "tags/test:readme.txt", + "0266163a49e280c4f5ed1e08facd36a2bd716bcf", + NULL); +} + +void test_refs_revparse__uneven_sizes(void) +{ + test_object("a65fedf39aefe402d3bb6e24df4d4f5fe454775", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + test_object("a65fedf39aefe402d3bb6e24df4d4f5fe45477", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + test_object("a65fedf39aefe402d3bb6e24df4d4f5fe4547", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + test_object("a65fedf39aefe402d3bb6e24df4d", + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); +} + +void test_refs_revparse__parses_at_head(void) +{ + test_id("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", NULL, GIT_REVSPEC_SINGLE); + test_id("@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", NULL, GIT_REVSPEC_SINGLE); + test_id("@", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", NULL, GIT_REVSPEC_SINGLE); +} diff --git a/tests/libgit2/refs/setter.c b/tests/libgit2/refs/setter.c new file mode 100644 index 000000000..b34c71eb5 --- /dev/null +++ b/tests/libgit2/refs/setter.c @@ -0,0 +1,99 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" +#include "git2/refs.h" + +static const char *ref_name = "refs/heads/other"; +static const char *ref_master_name = "refs/heads/master"; +static const char *ref_test_name = "refs/heads/test"; + +static git_repository *g_repo; + +void test_refs_setter__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_setter__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_setter__update_direct(void) +{ + git_reference *ref, *test_ref, *new_ref; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) == GIT_REFERENCE_DIRECT); + git_oid_cpy(&id, git_reference_target(ref)); + git_reference_free(ref); + + cl_git_pass(git_reference_lookup(&test_ref, g_repo, ref_test_name)); + cl_assert(git_reference_type(test_ref) == GIT_REFERENCE_DIRECT); + + cl_git_pass(git_reference_set_target(&new_ref, test_ref, &id, NULL)); + + git_reference_free(test_ref); + git_reference_free(new_ref); + + cl_git_pass(git_reference_lookup(&test_ref, g_repo, ref_test_name)); + cl_assert(git_reference_type(test_ref) == GIT_REFERENCE_DIRECT); + cl_assert_equal_oid(&id, git_reference_target(test_ref)); + git_reference_free(test_ref); +} + +void test_refs_setter__update_symbolic(void) +{ + git_reference *head, *new_head; + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_assert(git_reference_type(head) == GIT_REFERENCE_SYMBOLIC); + cl_assert(strcmp(git_reference_symbolic_target(head), ref_master_name) == 0); + + cl_git_pass(git_reference_symbolic_set_target(&new_head, head, ref_test_name, NULL)); + git_reference_free(new_head); + git_reference_free(head); + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_assert(git_reference_type(head) == GIT_REFERENCE_SYMBOLIC); + cl_assert(strcmp(git_reference_symbolic_target(head), ref_test_name) == 0); + git_reference_free(head); +} + +void test_refs_setter__cant_update_direct_with_symbolic(void) +{ + /* Overwrite an existing object id reference with a symbolic one */ + git_reference *ref, *new; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) == GIT_REFERENCE_DIRECT); + git_oid_cpy(&id, git_reference_target(ref)); + + cl_git_fail(git_reference_symbolic_set_target(&new, ref, ref_name, NULL)); + + git_reference_free(ref); +} + +void test_refs_setter__cant_update_symbolic_with_direct(void) +{ + /* Overwrite an existing symbolic reference with an object id one */ + git_reference *ref, *new; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) == GIT_REFERENCE_DIRECT); + git_oid_cpy(&id, git_reference_target(ref)); + git_reference_free(ref); + + /* Create the symbolic ref */ + cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 0, NULL)); + + /* Can't set an OID on a direct ref */ + cl_git_fail(git_reference_set_target(&new, ref, &id, NULL)); + + git_reference_free(ref); +} diff --git a/tests/libgit2/refs/shorthand.c b/tests/libgit2/refs/shorthand.c new file mode 100644 index 000000000..e008adc74 --- /dev/null +++ b/tests/libgit2/refs/shorthand.c @@ -0,0 +1,27 @@ +#include "clar_libgit2.h" + +#include "repository.h" + +static void assert_shorthand(git_repository *repo, const char *refname, const char *shorthand) +{ + git_reference *ref; + + cl_git_pass(git_reference_lookup(&ref, repo, refname)); + cl_assert_equal_s(git_reference_shorthand(ref), shorthand); + git_reference_free(ref); +} + +void test_refs_shorthand__0(void) +{ + git_repository *repo; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + + assert_shorthand(repo, "refs/heads/master", "master"); + assert_shorthand(repo, "refs/tags/test", "test"); + assert_shorthand(repo, "refs/remotes/test/master", "test/master"); + assert_shorthand(repo, "refs/notes/fanout", "notes/fanout"); + + git_repository_free(repo); +} diff --git a/tests/libgit2/refs/tags/name.c b/tests/libgit2/refs/tags/name.c new file mode 100644 index 000000000..1dd1760b9 --- /dev/null +++ b/tests/libgit2/refs/tags/name.c @@ -0,0 +1,17 @@ +#include "clar_libgit2.h" + +static int name_is_valid(const char *name) +{ + int valid; + cl_git_pass(git_tag_name_is_valid(&valid, name)); + return valid; +} + +void test_refs_tags_name__is_name_valid(void) +{ + cl_assert_equal_i(true, name_is_valid("sometag")); + cl_assert_equal_i(true, name_is_valid("test/sometag")); + + cl_assert_equal_i(false, name_is_valid("")); + cl_assert_equal_i(false, name_is_valid("-dash")); +} diff --git a/tests/libgit2/refs/transactions.c b/tests/libgit2/refs/transactions.c new file mode 100644 index 000000000..50c102ad0 --- /dev/null +++ b/tests/libgit2/refs/transactions.c @@ -0,0 +1,157 @@ +#include "clar_libgit2.h" +#include "git2/transaction.h" + +static git_repository *g_repo; +static git_transaction *g_tx; + +void test_refs_transactions__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_transaction_new(&g_tx, g_repo)); +} + +void test_refs_transactions__cleanup(void) +{ + git_transaction_free(g_tx); + cl_git_sandbox_cleanup(); +} + +void test_refs_transactions__single_ref_oid(void) +{ + git_reference *ref; + git_oid id; + + git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); + cl_git_pass(git_transaction_set_target(g_tx, "refs/heads/master", &id, NULL, NULL)); + cl_git_pass(git_transaction_commit(g_tx)); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); + + cl_assert(!git_oid_cmp(&id, git_reference_target(ref))); + git_reference_free(ref); +} + +void test_refs_transactions__single_ref_symbolic(void) +{ + git_reference *ref; + + cl_git_pass(git_transaction_lock_ref(g_tx, "HEAD")); + cl_git_pass(git_transaction_set_symbolic_target(g_tx, "HEAD", "refs/heads/foo", NULL, NULL)); + cl_git_pass(git_transaction_commit(g_tx)); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); + + cl_assert_equal_s("refs/heads/foo", git_reference_symbolic_target(ref)); + git_reference_free(ref); +} + +void test_refs_transactions__single_ref_mix_types(void) +{ + git_reference *ref; + git_oid id; + + git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); + cl_git_pass(git_transaction_lock_ref(g_tx, "HEAD")); + cl_git_pass(git_transaction_set_symbolic_target(g_tx, "refs/heads/master", "refs/heads/foo", NULL, NULL)); + cl_git_pass(git_transaction_set_target(g_tx, "HEAD", &id, NULL, NULL)); + cl_git_pass(git_transaction_commit(g_tx)); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); + cl_assert_equal_s("refs/heads/foo", git_reference_symbolic_target(ref)); + git_reference_free(ref); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); + cl_assert(!git_oid_cmp(&id, git_reference_target(ref))); + git_reference_free(ref); +} + +void test_refs_transactions__single_ref_delete(void) +{ + git_reference *ref; + + cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); + cl_git_pass(git_transaction_remove(g_tx, "refs/heads/master")); + cl_git_pass(git_transaction_commit(g_tx)); + + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo, "refs/heads/master")); +} + +void test_refs_transactions__single_create(void) +{ + git_reference *ref; + const char *name = "refs/heads/new-branch"; + git_oid id; + + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo, name)); + + cl_git_pass(git_transaction_lock_ref(g_tx, name)); + + git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_pass(git_transaction_set_target(g_tx, name, &id, NULL, NULL)); + cl_git_pass(git_transaction_commit(g_tx)); + + cl_git_pass(git_reference_lookup(&ref, g_repo, name)); + cl_assert(!git_oid_cmp(&id, git_reference_target(ref))); + git_reference_free(ref); +} + +void test_refs_transactions__unlocked_set(void) +{ + git_oid id; + + cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); + git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_fail_with(GIT_ENOTFOUND, git_transaction_set_target(g_tx, "refs/heads/foo", &id, NULL, NULL)); + cl_git_pass(git_transaction_commit(g_tx)); +} + +void test_refs_transactions__error_on_locking_locked_ref(void) +{ + git_oid id; + git_transaction *g_tx_with_lock; + git_repository *g_repo_with_locking_tx; + const char *g_repo_path = git_repository_path(g_repo); + + /* prepare a separate transaction in another instance of testrepo and lock master */ + cl_git_pass(git_repository_open(&g_repo_with_locking_tx, g_repo_path)); + cl_git_pass(git_transaction_new(&g_tx_with_lock, g_repo_with_locking_tx)); + cl_git_pass(git_transaction_lock_ref(g_tx_with_lock, "refs/heads/master")); + + /* lock reference for set_target */ + cl_git_pass(git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); + cl_git_fail_with(GIT_ELOCKED, git_transaction_lock_ref(g_tx, "refs/heads/master")); + cl_git_fail_with(GIT_ENOTFOUND, git_transaction_set_target(g_tx, "refs/heads/master", &id, NULL, NULL)); + + git_transaction_free(g_tx_with_lock); + git_repository_free(g_repo_with_locking_tx); +} + +void test_refs_transactions__commit_unlocks_unmodified_ref(void) +{ + git_transaction *second_tx; + + cl_git_pass(git_transaction_new(&second_tx, g_repo)); + cl_git_pass(git_transaction_lock_ref(second_tx, "refs/heads/master")); + cl_git_pass(git_transaction_commit(second_tx)); + + /* a transaction must now be able to get the lock */ + cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); + + git_transaction_free(second_tx); +} + +void test_refs_transactions__free_unlocks_unmodified_ref(void) +{ + git_transaction *second_tx; + + cl_git_pass(git_transaction_new(&second_tx, g_repo)); + cl_git_pass(git_transaction_lock_ref(second_tx, "refs/heads/master")); + git_transaction_free(second_tx); + + /* a transaction must now be able to get the lock */ + cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); +} diff --git a/tests/libgit2/refs/unicode.c b/tests/libgit2/refs/unicode.c new file mode 100644 index 000000000..a279d5006 --- /dev/null +++ b/tests/libgit2/refs/unicode.c @@ -0,0 +1,54 @@ +#include "clar_libgit2.h" + +static git_repository *repo; + +void test_refs_unicode__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_refs_unicode__cleanup(void) +{ + cl_git_sandbox_cleanup(); + repo = NULL; +} + +void test_refs_unicode__create_and_lookup(void) +{ + git_reference *ref0, *ref1, *ref2; + git_repository *repo2; + + const char *REFNAME = "refs/heads/" "\303\205" "ngstr" "\303\266" "m"; + const char *master = "refs/heads/master"; + + /* Create the reference */ + cl_git_pass(git_reference_lookup(&ref0, repo, master)); + cl_git_pass(git_reference_create( + &ref1, repo, REFNAME, git_reference_target(ref0), 0, NULL)); + cl_assert_equal_s(REFNAME, git_reference_name(ref1)); + git_reference_free(ref0); + + /* Lookup the reference in a different instance of the repository */ + cl_git_pass(git_repository_open(&repo2, "testrepo.git")); + + cl_git_pass(git_reference_lookup(&ref2, repo2, REFNAME)); + cl_assert_equal_oid(git_reference_target(ref1), git_reference_target(ref2)); + cl_assert_equal_s(REFNAME, git_reference_name(ref2)); + git_reference_free(ref2); + +#if GIT_USE_ICONV + /* Lookup reference by decomposed unicode name */ + +#define REFNAME_DECOMPOSED "refs/heads/" "A" "\314\212" "ngstro" "\314\210" "m" + + cl_git_pass(git_reference_lookup(&ref2, repo2, REFNAME_DECOMPOSED)); + cl_assert_equal_oid(git_reference_target(ref1), git_reference_target(ref2)); + cl_assert_equal_s(REFNAME, git_reference_name(ref2)); + git_reference_free(ref2); +#endif + + /* Cleanup */ + + git_reference_free(ref1); + git_repository_free(repo2); +} diff --git a/tests/libgit2/refs/update.c b/tests/libgit2/refs/update.c new file mode 100644 index 000000000..1c5127d9e --- /dev/null +++ b/tests/libgit2/refs/update.c @@ -0,0 +1,26 @@ +#include "clar_libgit2.h" + +#include "refs.h" + +static git_repository *g_repo; + +void test_refs_update__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_refs_update__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_update__updating_the_target_of_a_symref_with_an_invalid_name_returns_EINVALIDSPEC(void) +{ + git_reference *head; + + cl_git_pass(git_reference_lookup(&head, g_repo, GIT_HEAD_FILE)); + cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head)); + git_reference_free(head); + + cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_symbolic_create(&head, g_repo, GIT_HEAD_FILE, "refs/heads/inv@{id", 1, NULL)); +} diff --git a/tests/libgit2/remote/create.c b/tests/libgit2/remote/create.c new file mode 100644 index 000000000..f92be9dfc --- /dev/null +++ b/tests/libgit2/remote/create.c @@ -0,0 +1,388 @@ +#include "clar_libgit2.h" +#include "config/config_helpers.h" + +static git_repository *_repo; +static git_config *_config; + +#define TEST_URL "http://github.com/libgit2/libgit2.git" + +void test_remote_create__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + + cl_git_pass(git_repository_open(&_repo, "testrepo.git")); + + cl_git_pass(git_repository_config(&_config, _repo)); +} + +void test_remote_create__cleanup(void) +{ + git_config_free(_config); + + git_repository_free(_repo); + + cl_fixture_cleanup("testrepo.git"); +} + +void test_remote_create__manual(void) +{ + git_remote *remote; + cl_git_pass(git_config_set_string(_config, "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*")); + cl_git_pass(git_config_set_string(_config, "remote.origin.url", TEST_URL)); + + cl_git_pass(git_remote_lookup(&remote, _repo, "origin")); + cl_assert_equal_s(git_remote_name(remote), "origin"); + cl_assert_equal_s(git_remote_url(remote), TEST_URL); + + git_remote_free(remote); +} + +void test_remote_create__named(void) +{ + git_remote *remote; + git_config *cfg; + const char *cfg_val; + + size_t section_count = count_config_entries_match(_repo, "remote\\."); + + cl_git_pass(git_remote_create(&remote, _repo, "valid-name", TEST_URL)); + + cl_assert_equal_s(git_remote_name(remote), "valid-name"); + cl_assert_equal_s(git_remote_url(remote), TEST_URL); + cl_assert_equal_p(git_remote_owner(remote), _repo); + + cl_git_pass(git_repository_config_snapshot(&cfg, _repo)); + + cl_git_pass(git_config_get_string(&cfg_val, cfg, "remote.valid-name.fetch")); + cl_assert_equal_s(cfg_val, "+refs/heads/*:refs/remotes/valid-name/*"); + + cl_git_pass(git_config_get_string(&cfg_val, cfg, "remote.valid-name.url")); + cl_assert_equal_s(cfg_val, TEST_URL); + + cl_assert_equal_i(section_count + 2, count_config_entries_match(_repo, "remote\\.")); + + git_config_free(cfg); + git_remote_free(remote); +} + +void test_remote_create__named_fail_on_invalid_name(void) +{ + const char *names[] = { + NULL, + "Inv@{id", + "", + "/", + "//", + ".lock", + "a.lock", + }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(names); i++) { + git_remote *remote = NULL; + cl_git_fail_with(GIT_EINVALIDSPEC, git_remote_create(&remote, _repo, names[i], TEST_URL)); + cl_assert_equal_p(remote, NULL); + } +} + +void test_remote_create__named_fail_on_invalid_url(void) +{ + git_remote *remote = NULL; + + cl_git_fail_with(GIT_ERROR, git_remote_create(&remote, _repo, "bad-url", "")); + cl_assert_equal_p(remote, NULL); +} + +void test_remote_create__named_fail_on_conflicting_name(void) +{ + git_remote *remote = NULL; + + cl_git_fail_with(GIT_EEXISTS, git_remote_create(&remote, _repo, "test", TEST_URL)); + cl_assert_equal_p(remote, NULL); +} + +void test_remote_create__with_fetchspec(void) +{ + git_remote *remote; + git_strarray array; + size_t section_count = count_config_entries_match(_repo, "remote\\."); + + cl_git_pass(git_remote_create_with_fetchspec(&remote, _repo, "test-new", "git://github.com/libgit2/libgit2", "+refs/*:refs/*")); + cl_assert_equal_s(git_remote_name(remote), "test-new"); + cl_assert_equal_s(git_remote_url(remote), "git://github.com/libgit2/libgit2"); + cl_assert_equal_p(git_remote_owner(remote), _repo); + + cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); + cl_assert_equal_s("+refs/*:refs/*", array.strings[0]); + cl_assert_equal_i(1, array.count); + cl_assert_equal_i(section_count + 2, count_config_entries_match(_repo, "remote\\.")); + + git_strarray_dispose(&array); + git_remote_free(remote); +} + +void test_remote_create__with_empty_fetchspec(void) +{ + git_remote *remote; + git_strarray array; + size_t section_count = count_config_entries_match(_repo, "remote\\."); + + cl_git_pass(git_remote_create_with_fetchspec(&remote, _repo, "test-new", "git://github.com/libgit2/libgit2", NULL)); + cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); + cl_assert_equal_i(0, array.count); + cl_assert_equal_i(section_count + 1, count_config_entries_match(_repo, "remote\\.")); + + git_strarray_dispose(&array); + git_remote_free(remote); +} + +void test_remote_create__with_fetchspec_invalid_name(void) +{ + git_remote *remote = NULL; + + cl_git_fail_with(GIT_EINVALIDSPEC, git_remote_create_with_fetchspec(&remote, _repo, NULL, TEST_URL, NULL)); + cl_assert_equal_p(remote, NULL); +} + +void test_remote_create__with_fetchspec_invalid_url(void) +{ + git_remote *remote = NULL; + + cl_git_fail_with(GIT_EINVALIDSPEC, git_remote_create_with_fetchspec(&remote, _repo, NULL, "", NULL)); + cl_assert_equal_p(remote, NULL); +} + +void test_remote_create__anonymous(void) +{ + git_remote *remote; + git_strarray array; + size_t section_count = count_config_entries_match(_repo, "remote\\."); + + cl_git_pass(git_remote_create_anonymous(&remote, _repo, TEST_URL)); + cl_assert_equal_s(git_remote_name(remote), NULL); + cl_assert_equal_s(git_remote_url(remote), TEST_URL); + cl_assert_equal_p(git_remote_owner(remote), _repo); + + cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); + cl_assert_equal_i(0, array.count); + cl_assert_equal_i(section_count, count_config_entries_match(_repo, "remote\\.")); + + git_strarray_dispose(&array); + git_remote_free(remote); +} + +void test_remote_create__anonymous_invalid_url(void) +{ + git_remote *remote = NULL; + + cl_git_fail_with(GIT_EINVALIDSPEC, git_remote_create_anonymous(&remote, _repo, "")); + cl_assert_equal_p(remote, NULL); +} + +void test_remote_create__detached(void) +{ + git_remote *remote; + git_strarray array; + + size_t section_count = count_config_entries_match(_repo, "remote\\."); + + cl_git_pass(git_remote_create_detached(&remote, TEST_URL)); + cl_assert_equal_s(git_remote_name(remote), NULL); + cl_assert_equal_s(git_remote_url(remote), TEST_URL); + cl_assert_equal_p(git_remote_owner(remote), NULL); + + cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); + cl_assert_equal_i(0, array.count); + cl_assert_equal_i(section_count, count_config_entries_match(_repo, "remote\\.")); + + git_strarray_dispose(&array); + git_remote_free(remote); +} + +void test_remote_create__detached_invalid_url(void) +{ + git_remote *remote = NULL; + + cl_git_fail_with(GIT_EINVALIDSPEC, git_remote_create_detached(&remote, "")); + cl_assert_equal_p(remote, NULL); +} + +void test_remote_create__with_opts_named(void) +{ + git_remote *remote; + git_strarray array; + git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + + opts.name = "test-new"; + opts.repository = _repo; + + cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts)); + cl_assert_equal_s(git_remote_name(remote), "test-new"); + cl_assert_equal_s(git_remote_url(remote), TEST_URL); + cl_assert_equal_p(git_remote_owner(remote), _repo); + + cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); + cl_assert_equal_i(1, array.count); + cl_assert_equal_s("+refs/heads/*:refs/remotes/test-new/*", array.strings[0]); + + git_strarray_dispose(&array); + git_remote_free(remote); +} + +void test_remote_create__with_opts_named_and_fetchspec(void) +{ + git_remote *remote; + git_strarray array; + git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + + opts.name = "test-new"; + opts.repository = _repo; + opts.fetchspec = "+refs/*:refs/*"; + + cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts)); + cl_assert_equal_s(git_remote_name(remote), "test-new"); + cl_assert_equal_s(git_remote_url(remote), TEST_URL); + cl_assert_equal_p(git_remote_owner(remote), _repo); + + cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); + cl_assert_equal_i(1, array.count); + cl_assert_equal_s("+refs/*:refs/*", array.strings[0]); + + git_strarray_dispose(&array); + git_remote_free(remote); +} + +void test_remote_create__with_opts_named_no_fetchspec(void) +{ + git_remote *remote; + git_strarray array; + git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + + opts.name = "test-new"; + opts.repository = _repo; + opts.flags = GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC; + + cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts)); + cl_assert_equal_s(git_remote_name(remote), "test-new"); + cl_assert_equal_s(git_remote_url(remote), TEST_URL); + cl_assert_equal_p(git_remote_owner(remote), _repo); + + cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); + cl_assert_equal_i(0, array.count); + + git_strarray_dispose(&array); + git_remote_free(remote); +} + +void test_remote_create__with_opts_anonymous(void) +{ + git_remote *remote; + git_strarray array; + git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + + opts.repository = _repo; + + cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts)); + cl_assert_equal_s(git_remote_name(remote), NULL); + cl_assert_equal_s(git_remote_url(remote), TEST_URL); + cl_assert_equal_p(git_remote_owner(remote), _repo); + + cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); + cl_assert_equal_i(0, array.count); + + git_strarray_dispose(&array); + git_remote_free(remote); +} + +void test_remote_create__with_opts_detached(void) +{ + git_remote *remote; + git_strarray array; + git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + + cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts)); + cl_assert_equal_s(git_remote_name(remote), NULL); + cl_assert_equal_s(git_remote_url(remote), TEST_URL); + cl_assert_equal_p(git_remote_owner(remote), NULL); + + cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); + cl_assert_equal_i(0, array.count); + + git_strarray_dispose(&array); + + git_remote_free(remote); + + cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, NULL)); + cl_assert_equal_s(git_remote_name(remote), NULL); + cl_assert_equal_s(git_remote_url(remote), TEST_URL); + cl_assert_equal_p(git_remote_owner(remote), NULL); + + cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); + cl_assert_equal_i(0, array.count); + + git_strarray_dispose(&array); + + git_remote_free(remote); +} + + +void test_remote_create__with_opts_insteadof_disabled(void) +{ + git_remote *remote; + git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + + opts.repository = _repo; + opts.flags = GIT_REMOTE_CREATE_SKIP_INSTEADOF; + + cl_git_pass(git_remote_create_with_opts(&remote, "http://example.com/libgit2/libgit2", &opts)); + + cl_assert_equal_s(git_remote_url(remote), "http://example.com/libgit2/libgit2"); + cl_assert_equal_p(git_remote_pushurl(remote), NULL); + + git_remote_free(remote); +} + +static int create_with_name(git_remote **remote, git_repository *repo, const char *name, const char *url) +{ + git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; + + opts.repository = repo; + opts.name = name; + + return git_remote_create_with_opts(remote, url, &opts); +} + +void test_remote_create__with_opts_invalid_name(void) +{ + const char *names[] = { + "Inv@{id", + "", + "/", + "//", + ".lock", + "a.lock", + }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(names); i++) { + git_remote *remote = NULL; + cl_git_fail_with(GIT_EINVALIDSPEC, create_with_name(&remote, _repo, names[i], TEST_URL)); + cl_assert_equal_p(remote, NULL); + } +} + +void test_remote_create__with_opts_conflicting_name(void) +{ + git_remote *remote = NULL; + + cl_git_fail_with(GIT_EEXISTS, create_with_name(&remote, _repo, "test", TEST_URL)); + cl_assert_equal_p(remote, NULL); +} + +void test_remote_create__with_opts_invalid_url(void) +{ + git_remote *remote = NULL; + + cl_git_fail_with(GIT_EINVALIDSPEC, create_with_name(&remote, _repo, "test-new", "")); + cl_assert_equal_p(remote, NULL); +} diff --git a/tests/libgit2/remote/fetch.c b/tests/libgit2/remote/fetch.c new file mode 100644 index 000000000..370046267 --- /dev/null +++ b/tests/libgit2/remote/fetch.c @@ -0,0 +1,169 @@ +#include "../clar_libgit2.h" + +#include "remote.h" +#include "repository.h" + +static git_repository *repo1; +static git_repository *repo2; +static char* repo1_path; +static char* repo2_path; + +static const char *REPO1_REFNAME = "refs/heads/main"; +static const char *REPO2_REFNAME = "refs/remotes/repo1/main"; +static char *FORCE_FETCHSPEC = "+refs/heads/main:refs/remotes/repo1/main"; +static char *NON_FORCE_FETCHSPEC = "refs/heads/main:refs/remotes/repo1/main"; + +void test_remote_fetch__initialize(void) { + git_config *c; + git_str repo1_path_buf = GIT_STR_INIT; + git_str repo2_path_buf = GIT_STR_INIT; + const char *sandbox = clar_sandbox_path(); + + cl_git_pass(git_str_joinpath(&repo1_path_buf, sandbox, "fetchtest_repo1")); + repo1_path = git_str_detach(&repo1_path_buf); + cl_git_pass(git_repository_init(&repo1, repo1_path, true)); + + cl_git_pass(git_str_joinpath(&repo2_path_buf, sandbox, "fetchtest_repo2")); + repo2_path = git_str_detach(&repo2_path_buf); + cl_git_pass(git_repository_init(&repo2, repo2_path, true)); + + cl_git_pass(git_repository_config(&c, repo1)); + cl_git_pass(git_config_set_string(c, "user.email", "some@email")); + cl_git_pass(git_config_set_string(c, "user.name", "some@name")); + git_config_free(c); + git_str_dispose(&repo1_path_buf); + git_str_dispose(&repo2_path_buf); +} + +void test_remote_fetch__cleanup(void) { + git_repository_free(repo1); + git_repository_free(repo2); + + cl_git_pass(git_futils_rmdir_r(repo1_path, NULL, GIT_RMDIR_REMOVE_FILES)); + free(repo1_path); + + cl_git_pass(git_futils_rmdir_r(repo2_path, NULL, GIT_RMDIR_REMOVE_FILES)); + free(repo2_path); +} + + +/** + * This checks that the '+' flag on fetchspecs is respected. We create a + * repository that has a reference to two commits, one a child of the other. + * We fetch this repository into a second repository. Then we reset the + * reference in the first repository and run the fetch again. If the '+' flag + * is used then the reference in the second repository will change, but if it + * is not then it should stay the same. + * + * @param commit1id A pointer to an OID which will be populated with the first + * commit. + * @param commit2id A pointer to an OID which will be populated with the second + * commit, which is a descendant of the first. + * @param force Whether to use a spec with '+' prefixed to force the refs + * to update + */ +static void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, + bool force) { + char *refspec_strs = { + force ? FORCE_FETCHSPEC : NON_FORCE_FETCHSPEC, + }; + git_strarray refspecs = { + .count = 1, + .strings = &refspec_strs, + }; + + /* create two commits in repo 1 and a reference to them */ + { + git_oid empty_tree_id; + git_tree *empty_tree; + git_signature *sig; + git_treebuilder *tb; + cl_git_pass(git_treebuilder_new(&tb, repo1, NULL)); + cl_git_pass(git_treebuilder_write(&empty_tree_id, tb)); + cl_git_pass(git_tree_lookup(&empty_tree, repo1, &empty_tree_id)); + cl_git_pass(git_signature_default(&sig, repo1)); + cl_git_pass(git_commit_create(commit1id, repo1, REPO1_REFNAME, sig, + sig, NULL, "one", empty_tree, 0, NULL)); + cl_git_pass(git_commit_create_v(commit2id, repo1, REPO1_REFNAME, sig, + sig, NULL, "two", empty_tree, 1, commit1id)); + + git_tree_free(empty_tree); + git_signature_free(sig); + git_treebuilder_free(tb); + } + + /* fetch the reference via the remote */ + { + git_remote *remote; + + cl_git_pass(git_remote_create_anonymous(&remote, repo2, + git_repository_path(repo1))); + cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, "some message")); + + git_remote_free(remote); + } + + /* assert that repo2 references the second commit */ + { + const git_oid *target; + git_reference *ref; + cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME)); + target = git_reference_target(ref); + cl_assert_equal_b(git_oid_cmp(target, commit2id), 0); + git_reference_free(ref); + } + + /* set the reference in repo1 to point to the older commit */ + { + git_reference *ref; + git_reference *ref2; + cl_git_pass(git_reference_lookup(&ref, repo1, REPO1_REFNAME)); + cl_git_pass(git_reference_set_target(&ref2, ref, commit1id, + "rollback")); + git_reference_free(ref); + git_reference_free(ref2); + } + + /* fetch the reference again */ + { + git_remote *remote; + + cl_git_pass(git_remote_create_anonymous(&remote, repo2, + git_repository_path(repo1))); + cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, "some message")); + + git_remote_free(remote); + } +} + +void test_remote_fetch__dont_update_refs_if_not_descendant_and_not_force(void) { + const git_oid *target; + git_oid commit1id; + git_oid commit2id; + git_reference *ref; + + do_time_travelling_fetch(&commit1id, &commit2id, false); + + /* assert that the reference in repo2 has not changed */ + cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME)); + target = git_reference_target(ref); + cl_assert_equal_b(git_oid_cmp(target, &commit2id), 0); + + git_reference_free(ref); +} + +void test_remote_fetch__do_update_refs_if_not_descendant_and_force(void) { + const git_oid *target; + git_oid commit1id; + git_oid commit2id; + git_reference *ref; + + do_time_travelling_fetch(&commit1id, &commit2id, true); + + /* assert that the reference in repo2 has changed */ + cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME)); + target = git_reference_target(ref); + cl_assert_equal_b(git_oid_cmp(target, &commit1id), 0); + + git_reference_free(ref); +} diff --git a/tests/libgit2/remote/httpproxy.c b/tests/libgit2/remote/httpproxy.c new file mode 100644 index 000000000..f62a2545b --- /dev/null +++ b/tests/libgit2/remote/httpproxy.c @@ -0,0 +1,188 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "net.h" +#include "remote.h" + +static git_repository *repo; +static git_net_url url = GIT_NET_URL_INIT; + +static int orig_proxies_need_reset = 0; +static char *orig_http_proxy = NULL; +static char *orig_https_proxy = NULL; +static char *orig_no_proxy = NULL; + +void test_remote_httpproxy__initialize(void) +{ + git_remote *remote; + + repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_remote_create(&remote, repo, "lg2", "https://github.com/libgit2/libgit2")); + cl_git_pass(git_net_url_parse(&url, "https://github.com/libgit2/libgit2")); + + git_remote_free(remote); + + orig_proxies_need_reset = 0; +} + +void test_remote_httpproxy__cleanup(void) +{ + if (orig_proxies_need_reset) { + cl_setenv("HTTP_PROXY", orig_http_proxy); + cl_setenv("HTTPS_PROXY", orig_https_proxy); + cl_setenv("NO_PROXY", orig_no_proxy); + + git__free(orig_http_proxy); + git__free(orig_https_proxy); + git__free(orig_no_proxy); + } + + git_net_url_dispose(&url); + cl_git_sandbox_cleanup(); +} + +static void assert_proxy_is(const char *expected) +{ + git_remote *remote; + char *proxy; + + cl_git_pass(git_remote_lookup(&remote, repo, "lg2")); + cl_git_pass(git_remote__http_proxy(&proxy, remote, &url)); + + if (expected) + cl_assert_equal_s(proxy, expected); + else + cl_assert_equal_p(proxy, expected); + + git_remote_free(remote); + git__free(proxy); +} + +static void assert_config_match(const char *config, const char *expected) +{ + git_remote *remote; + char *proxy; + + if (config) + cl_repo_set_string(repo, config, expected); + + cl_git_pass(git_remote_lookup(&remote, repo, "lg2")); + cl_git_pass(git_remote__http_proxy(&proxy, remote, &url)); + + if (expected) + cl_assert_equal_s(proxy, expected); + else + cl_assert_equal_p(proxy, expected); + + git_remote_free(remote); + git__free(proxy); +} + +void test_remote_httpproxy__config_overrides(void) +{ + /* + * http.proxy should be honored, then http..proxy should + * be honored in increasing specificity of the url. finally, + * remote..proxy is the most specific. + */ + assert_config_match(NULL, NULL); + assert_config_match("http.proxy", "http://localhost:1/"); + assert_config_match("http.https://github.com.proxy", "http://localhost:2/"); + assert_config_match("http.https://github.com/.proxy", "http://localhost:3/"); + assert_config_match("http.https://github.com/libgit2.proxy", "http://localhost:4/"); + assert_config_match("http.https://github.com/libgit2/.proxy", "http://localhost:5/"); + assert_config_match("http.https://github.com/libgit2/libgit2.proxy", "http://localhost:6/"); + assert_config_match("remote.lg2.proxy", "http://localhost:7/"); +} + +void test_remote_httpproxy__config_empty_overrides(void) +{ + /* + * with greater specificity, an empty config entry overrides + * a set one + */ + assert_config_match("http.proxy", "http://localhost:1/"); + assert_config_match("http.https://github.com.proxy", ""); + assert_config_match("http.https://github.com/libgit2/libgit2.proxy", "http://localhost:2/"); + assert_config_match("remote.lg2.proxy", ""); +} + +static void assert_global_config_match(const char *config, const char *expected) +{ + git_remote *remote; + char *proxy; + git_config* cfg; + + if (config) { + cl_git_pass(git_config_open_default(&cfg)); + git_config_set_string(cfg, config, expected); + git_config_free(cfg); + } + + cl_git_pass(git_remote_create_detached(&remote, "https://github.com/libgit2/libgit2")); + cl_git_pass(git_remote__http_proxy(&proxy, remote, &url)); + + if (expected) + cl_assert_equal_s(proxy, expected); + else + cl_assert_equal_p(proxy, expected); + + git_remote_free(remote); + git__free(proxy); +} + +void test_remote_httpproxy__config_overrides_detached_remote(void) +{ + cl_fake_home(); + + assert_global_config_match(NULL, NULL); + assert_global_config_match("http.proxy", "http://localhost:1/"); + assert_global_config_match("http.https://github.com.proxy", "http://localhost:2/"); + assert_global_config_match("http.https://github.com/.proxy", "http://localhost:3/"); + assert_global_config_match("http.https://github.com/libgit2.proxy", "http://localhost:4/"); + assert_global_config_match("http.https://github.com/libgit2/.proxy", "http://localhost:5/"); + assert_global_config_match("http.https://github.com/libgit2/libgit2.proxy", "http://localhost:6/"); + + cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_remote_httpproxy__env(void) +{ + orig_http_proxy = cl_getenv("HTTP_PROXY"); + orig_https_proxy = cl_getenv("HTTPS_PROXY"); + orig_no_proxy = cl_getenv("NO_PROXY"); + orig_proxies_need_reset = 1; + + /* Clear everything for a fresh start */ + cl_setenv("HTTP_PROXY", NULL); + cl_setenv("HTTPS_PROXY", NULL); + cl_setenv("NO_PROXY", NULL); + + /* HTTP proxy is ignored for HTTPS */ + cl_setenv("HTTP_PROXY", "http://localhost:9/"); + assert_proxy_is(NULL); + + /* HTTPS proxy is honored for HTTPS */ + cl_setenv("HTTPS_PROXY", "http://localhost:10/"); + assert_proxy_is("http://localhost:10/"); + + /* NO_PROXY is honored */ + cl_setenv("NO_PROXY", "github.com:443"); + assert_proxy_is(NULL); + + cl_setenv("NO_PROXY", "github.com:80"); + assert_proxy_is("http://localhost:10/"); + + cl_setenv("NO_PROXY", "github.com"); + assert_proxy_is(NULL); + + cl_setenv("NO_PROXY", "github.dev,github.com,github.foo"); + assert_proxy_is(NULL); + + cl_setenv("HTTPS_PROXY", ""); + assert_proxy_is(NULL); + + /* configuration overrides environment variables */ + cl_setenv("HTTPS_PROXY", "http://localhost:10/"); + cl_setenv("NO_PROXY", "github.none"); + assert_config_match("http.https://github.com.proxy", "http://localhost:11/"); +} diff --git a/tests/libgit2/remote/insteadof.c b/tests/libgit2/remote/insteadof.c new file mode 100644 index 000000000..c39df4be7 --- /dev/null +++ b/tests/libgit2/remote/insteadof.c @@ -0,0 +1,154 @@ +#include "clar_libgit2.h" +#include "remote.h" +#include "repository.h" + +#define REPO_PATH "testrepo2/.gitted" +#define REMOTE_ORIGIN "origin" +#define REMOTE_INSTEADOF_URL_FETCH "insteadof-url-fetch" +#define REMOTE_INSTEADOF_URL_PUSH "insteadof-url-push" +#define REMOTE_INSTEADOF_URL_BOTH "insteadof-url-both" +#define REMOTE_INSTEADOF_PUSHURL_FETCH "insteadof-pushurl-fetch" +#define REMOTE_INSTEADOF_PUSHURL_PUSH "insteadof-pushurl-push" +#define REMOTE_INSTEADOF_PUSHURL_BOTH "insteadof-pushurl-both" + +static git_repository *g_repo; +static git_remote *g_remote; + +void test_remote_insteadof__initialize(void) +{ + g_repo = NULL; + g_remote = NULL; +} + +void test_remote_insteadof__cleanup(void) +{ + git_repository_free(g_repo); + git_remote_free(g_remote); +} + +void test_remote_insteadof__not_applicable(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_ORIGIN)); + + cl_assert_equal_s( + git_remote_url(g_remote), + "https://github.com/libgit2/false.git"); + cl_assert_equal_p(git_remote_pushurl(g_remote), NULL); +} + +void test_remote_insteadof__url_insteadof_fetch(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF_URL_FETCH)); + + cl_assert_equal_s( + git_remote_url(g_remote), + "http://github.com/url/fetch/libgit2"); + cl_assert_equal_p(git_remote_pushurl(g_remote), NULL); +} + +void test_remote_insteadof__url_insteadof_push(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF_URL_PUSH)); + + cl_assert_equal_s( + git_remote_url(g_remote), + "http://example.com/url/push/libgit2"); + cl_assert_equal_s( + git_remote_pushurl(g_remote), + "git@github.com:url/push/libgit2"); +} + +void test_remote_insteadof__url_insteadof_both(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF_URL_BOTH)); + + cl_assert_equal_s( + git_remote_url(g_remote), + "http://github.com/url/both/libgit2"); + cl_assert_equal_s( + git_remote_pushurl(g_remote), + "git@github.com:url/both/libgit2"); +} + +void test_remote_insteadof__pushurl_insteadof_fetch(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF_PUSHURL_FETCH)); + + cl_assert_equal_s( + git_remote_url(g_remote), + "http://github.com/url/fetch/libgit2"); + cl_assert_equal_s( + git_remote_pushurl(g_remote), + "http://github.com/url/fetch/libgit2-push"); +} + +void test_remote_insteadof__pushurl_insteadof_push(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF_PUSHURL_PUSH)); + + cl_assert_equal_s( + git_remote_url(g_remote), + "http://example.com/url/push/libgit2"); + cl_assert_equal_s( + git_remote_pushurl(g_remote), + "http://example.com/url/push/libgit2-push"); +} + +void test_remote_insteadof__pushurl_insteadof_both(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF_PUSHURL_BOTH)); + + cl_assert_equal_s( + git_remote_url(g_remote), + "http://github.com/url/both/libgit2"); + cl_assert_equal_s( + git_remote_pushurl(g_remote), + "http://github.com/url/both/libgit2-push"); +} + +void test_remote_insteadof__anonymous_remote_fetch(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_create_anonymous(&g_remote, g_repo, + "http://example.com/url/fetch/libgit2")); + + cl_assert_equal_s( + git_remote_url(g_remote), + "http://github.com/url/fetch/libgit2"); + cl_assert_equal_p(git_remote_pushurl(g_remote), NULL); +} + +void test_remote_insteadof__anonymous_remote_push(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_create_anonymous(&g_remote, g_repo, + "http://example.com/url/push/libgit2")); + + cl_assert_equal_s( + git_remote_url(g_remote), + "http://example.com/url/push/libgit2"); + cl_assert_equal_s( + git_remote_pushurl(g_remote), + "git@github.com:url/push/libgit2"); +} + +void test_remote_insteadof__anonymous_remote_both(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); + cl_git_pass(git_remote_create_anonymous(&g_remote, g_repo, + "http://example.com/url/both/libgit2")); + + cl_assert_equal_s( + git_remote_url(g_remote), + "http://github.com/url/both/libgit2"); + cl_assert_equal_s( + git_remote_pushurl(g_remote), + "git@github.com:url/both/libgit2"); +} diff --git a/tests/libgit2/remote/list.c b/tests/libgit2/remote/list.c new file mode 100644 index 000000000..4a6be3d1b --- /dev/null +++ b/tests/libgit2/remote/list.c @@ -0,0 +1,43 @@ +#include "clar_libgit2.h" +#include "config/config_helpers.h" + +static git_repository *_repo; + +#define TEST_URL "http://github.com/libgit2/libgit2.git" + +void test_remote_list__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo"); +} + +void test_remote_list__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_remote_list__always_checks_disk_config(void) +{ + git_repository *repo; + git_strarray remotes; + git_remote *remote; + + cl_git_pass(git_repository_open(&repo, git_repository_path(_repo))); + + cl_git_pass(git_remote_list(&remotes, _repo)); + cl_assert_equal_sz(remotes.count, 1); + git_strarray_dispose(&remotes); + + cl_git_pass(git_remote_create(&remote, _repo, "valid-name", TEST_URL)); + + cl_git_pass(git_remote_list(&remotes, _repo)); + cl_assert_equal_sz(remotes.count, 2); + git_strarray_dispose(&remotes); + + cl_git_pass(git_remote_list(&remotes, repo)); + cl_assert_equal_sz(remotes.count, 2); + git_strarray_dispose(&remotes); + + git_repository_free(repo); + git_remote_free(remote); +} + diff --git a/tests/libgit2/repo/config.c b/tests/libgit2/repo/config.c new file mode 100644 index 000000000..ee7e43dff --- /dev/null +++ b/tests/libgit2/repo/config.c @@ -0,0 +1,211 @@ +#include "clar_libgit2.h" +#include "sysdir.h" +#include "futils.h" +#include + +static git_str path = GIT_STR_INIT; + +void test_repo_config__initialize(void) +{ + cl_fixture_sandbox("empty_standard_repo"); + cl_git_pass(cl_rename( + "empty_standard_repo/.gitted", "empty_standard_repo/.git")); + + git_str_clear(&path); + + cl_must_pass(p_mkdir("alternate", 0777)); + cl_git_pass(git_fs_path_prettify(&path, "alternate", NULL)); +} + +void test_repo_config__cleanup(void) +{ + cl_sandbox_set_search_path_defaults(); + + git_str_dispose(&path); + + cl_git_pass( + git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_assert(!git_fs_path_isdir("alternate")); + + cl_fixture_cleanup("empty_standard_repo"); + +} + +void test_repo_config__can_open_global_when_there_is_no_file(void) +{ + git_repository *repo; + git_config *config, *global; + + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); + + cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_open_level( + &global, config, GIT_CONFIG_LEVEL_GLOBAL)); + + cl_git_pass(git_config_set_string(global, "test.set", "42")); + + git_config_free(global); + git_config_free(config); + git_repository_free(repo); +} + +void test_repo_config__can_open_missing_global_with_separators(void) +{ + git_repository *repo; + git_config *config, *global; + + cl_git_pass(git_str_printf( + &path, "%c%s", GIT_PATH_LIST_SEPARATOR, "dummy")); + + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); + + git_str_dispose(&path); + + cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_open_level( + &global, config, GIT_CONFIG_LEVEL_GLOBAL)); + + cl_git_pass(git_config_set_string(global, "test.set", "42")); + + git_config_free(global); + git_config_free(config); + git_repository_free(repo); +} + +#include "repository.h" + +void test_repo_config__read_with_no_configs_at_all(void) +{ + git_repository *repo; + int val; + + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); + + /* with none */ + + cl_must_pass(p_unlink("empty_standard_repo/.git/config")); + cl_assert(!git_fs_path_isfile("empty_standard_repo/.git/config")); + + cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + git_repository__configmap_lookup_cache_clear(repo); + val = -1; + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); + cl_assert_equal_i(GIT_ABBREV_DEFAULT, val); + git_repository_free(repo); + + /* with no local config, just system */ + + cl_sandbox_set_search_path_defaults(); + + cl_must_pass(p_mkdir("alternate/1", 0777)); + cl_git_pass(git_str_joinpath(&path, path.ptr, "1")); + cl_git_rewritefile("alternate/1/gitconfig", "[core]\n\tabbrev = 10\n"); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); + + cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + git_repository__configmap_lookup_cache_clear(repo); + val = -1; + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); + cl_assert_equal_i(10, val); + git_repository_free(repo); + + /* with just xdg + system */ + + cl_must_pass(p_mkdir("alternate/2", 0777)); + path.ptr[path.size - 1] = '2'; + cl_git_rewritefile("alternate/2/config", "[core]\n\tabbrev = 20\n"); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); + + cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + git_repository__configmap_lookup_cache_clear(repo); + val = -1; + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); + cl_assert_equal_i(20, val); + git_repository_free(repo); + + /* with global + xdg + system */ + + cl_must_pass(p_mkdir("alternate/3", 0777)); + path.ptr[path.size - 1] = '3'; + cl_git_rewritefile("alternate/3/.gitconfig", "[core]\n\tabbrev = 30\n"); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); + + cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + git_repository__configmap_lookup_cache_clear(repo); + val = -1; + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); + cl_assert_equal_i(30, val); + git_repository_free(repo); + + /* with all configs */ + + cl_git_rewritefile("empty_standard_repo/.git/config", "[core]\n\tabbrev = 40\n"); + + cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + git_repository__configmap_lookup_cache_clear(repo); + val = -1; + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); + cl_assert_equal_i(40, val); + git_repository_free(repo); + + /* with all configs but delete the files ? */ + + cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + git_repository__configmap_lookup_cache_clear(repo); + val = -1; + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); + cl_assert_equal_i(40, val); + + cl_must_pass(p_unlink("empty_standard_repo/.git/config")); + cl_assert(!git_fs_path_isfile("empty_standard_repo/.git/config")); + + cl_must_pass(p_unlink("alternate/1/gitconfig")); + cl_assert(!git_fs_path_isfile("alternate/1/gitconfig")); + + cl_must_pass(p_unlink("alternate/2/config")); + cl_assert(!git_fs_path_isfile("alternate/2/config")); + + cl_must_pass(p_unlink("alternate/3/.gitconfig")); + cl_assert(!git_fs_path_isfile("alternate/3/.gitconfig")); + + git_repository__configmap_lookup_cache_clear(repo); + val = -1; + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); + cl_assert_equal_i(40, val); + git_repository_free(repo); + + /* reopen */ + + cl_assert(!git_fs_path_isfile("empty_standard_repo/.git/config")); + cl_assert(!git_fs_path_isfile("alternate/3/.gitconfig")); + + cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + git_repository__configmap_lookup_cache_clear(repo); + val = -1; + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); + cl_assert_equal_i(7, val); + git_repository_free(repo); + + cl_assert(!git_fs_path_exists("empty_standard_repo/.git/config")); + cl_assert(!git_fs_path_exists("alternate/3/.gitconfig")); +} diff --git a/tests/libgit2/repo/discover.c b/tests/libgit2/repo/discover.c new file mode 100644 index 000000000..523fdf8e3 --- /dev/null +++ b/tests/libgit2/repo/discover.c @@ -0,0 +1,210 @@ +#include "clar_libgit2.h" + +#include "odb.h" +#include "futils.h" +#include "repository.h" + +#define TEMP_REPO_FOLDER "temprepo/" +#define DISCOVER_FOLDER TEMP_REPO_FOLDER "discover.git" + +#define SUB_REPOSITORY_FOLDER_NAME "sub_repo" +#define SUB_REPOSITORY_FOLDER DISCOVER_FOLDER "/" SUB_REPOSITORY_FOLDER_NAME +#define SUB_REPOSITORY_GITDIR SUB_REPOSITORY_FOLDER "/.git" +#define SUB_REPOSITORY_FOLDER_SUB SUB_REPOSITORY_FOLDER "/sub" +#define SUB_REPOSITORY_FOLDER_SUB_SUB SUB_REPOSITORY_FOLDER_SUB "/subsub" +#define SUB_REPOSITORY_FOLDER_SUB_SUB_SUB SUB_REPOSITORY_FOLDER_SUB_SUB "/subsubsub" + +#define REPOSITORY_ALTERNATE_FOLDER DISCOVER_FOLDER "/alternate_sub_repo" +#define REPOSITORY_ALTERNATE_FOLDER_SUB REPOSITORY_ALTERNATE_FOLDER "/sub" +#define REPOSITORY_ALTERNATE_FOLDER_SUB_SUB REPOSITORY_ALTERNATE_FOLDER_SUB "/subsub" +#define REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB REPOSITORY_ALTERNATE_FOLDER_SUB_SUB "/subsubsub" + +#define ALTERNATE_MALFORMED_FOLDER1 DISCOVER_FOLDER "/alternate_malformed_repo1" +#define ALTERNATE_MALFORMED_FOLDER2 DISCOVER_FOLDER "/alternate_malformed_repo2" +#define ALTERNATE_MALFORMED_FOLDER3 DISCOVER_FOLDER "/alternate_malformed_repo3" +#define ALTERNATE_NOT_FOUND_FOLDER DISCOVER_FOLDER "/alternate_not_found_repo" + +static void ensure_repository_discover(const char *start_path, + const char *ceiling_dirs, + const char *expected_path) +{ + git_buf found_path = GIT_BUF_INIT; + git_str resolved = GIT_STR_INIT; + + git_str_attach(&resolved, p_realpath(expected_path, NULL), 0); + cl_assert(resolved.size > 0); + cl_git_pass(git_fs_path_to_dir(&resolved)); + cl_git_pass(git_repository_discover(&found_path, start_path, 1, ceiling_dirs)); + + cl_assert_equal_s(found_path.ptr, resolved.ptr); + + git_str_dispose(&resolved); + git_buf_dispose(&found_path); +} + +static void write_file(const char *path, const char *content) +{ + git_file file; + int error; + + if (git_fs_path_exists(path)) { + cl_git_pass(p_unlink(path)); + } + + file = git_futils_creat_withpath(path, 0777, 0666); + cl_assert(file >= 0); + + error = p_write(file, content, strlen(content) * sizeof(char)); + p_close(file); + cl_git_pass(error); +} + +/*no check is performed on ceiling_dirs length, so be sure it's long enough */ +static void append_ceiling_dir(git_str *ceiling_dirs, const char *path) +{ + git_str pretty_path = GIT_STR_INIT; + char ceiling_separator[2] = { GIT_PATH_LIST_SEPARATOR, '\0' }; + + cl_git_pass(git_fs_path_prettify_dir(&pretty_path, path, NULL)); + + if (ceiling_dirs->size > 0) + git_str_puts(ceiling_dirs, ceiling_separator); + + git_str_puts(ceiling_dirs, pretty_path.ptr); + + git_str_dispose(&pretty_path); + cl_assert(git_str_oom(ceiling_dirs) == 0); +} + +static git_buf discovered; +static git_str ceiling_dirs; + +void test_repo_discover__initialize(void) +{ + git_repository *repo; + const mode_t mode = 0777; + git_futils_mkdir_r(DISCOVER_FOLDER, mode); + + git_str_init(&ceiling_dirs, 0); + append_ceiling_dir(&ceiling_dirs, TEMP_REPO_FOLDER); + + cl_git_pass(git_repository_init(&repo, DISCOVER_FOLDER, 1)); + git_repository_free(repo); + + cl_git_pass(git_repository_init(&repo, SUB_REPOSITORY_FOLDER, 0)); + cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, mode)); + cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, mode)); + + cl_git_pass(git_futils_mkdir_r(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, mode)); + write_file(REPOSITORY_ALTERNATE_FOLDER "/" DOT_GIT, "gitdir: ../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT); + write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB "/" DOT_GIT, "gitdir: ../../../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT); + write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB "/" DOT_GIT, "gitdir: ../../../../"); + + cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER1, mode)); + write_file(ALTERNATE_MALFORMED_FOLDER1 "/" DOT_GIT, "Anything but not gitdir:"); + cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER2, mode)); + write_file(ALTERNATE_MALFORMED_FOLDER2 "/" DOT_GIT, "gitdir:"); + cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER3, mode)); + write_file(ALTERNATE_MALFORMED_FOLDER3 "/" DOT_GIT, "gitdir: \n\n\n"); + cl_git_pass(git_futils_mkdir_r(ALTERNATE_NOT_FOUND_FOLDER, mode)); + write_file(ALTERNATE_NOT_FOUND_FOLDER "/" DOT_GIT, "gitdir: a_repository_that_surely_does_not_exist"); + + git_repository_free(repo); +} + +void test_repo_discover__cleanup(void) +{ + git_buf_dispose(&discovered); + git_str_dispose(&ceiling_dirs); + cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_repo_discover__discovering_repo_with_exact_path_succeeds(void) +{ + cl_git_pass(git_repository_discover(&discovered, DISCOVER_FOLDER, 0, ceiling_dirs.ptr)); + cl_git_pass(git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER, 0, ceiling_dirs.ptr)); +} + +void test_repo_discover__discovering_nonexistent_dir_fails(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, DISCOVER_FOLDER "-nonexistent", 0, NULL)); +} + +void test_repo_discover__discovering_repo_with_subdirectory_succeeds(void) +{ + ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); +} + +void test_repo_discover__discovering_repository_with_alternative_gitdir_succeeds(void) +{ + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs.ptr, DISCOVER_FOLDER); +} + +void test_repo_discover__discovering_repository_with_malformed_alternative_gitdir_fails(void) +{ + cl_git_fail(git_repository_discover(&discovered, ALTERNATE_MALFORMED_FOLDER1, 0, ceiling_dirs.ptr)); + cl_git_fail(git_repository_discover(&discovered, ALTERNATE_MALFORMED_FOLDER2, 0, ceiling_dirs.ptr)); + cl_git_fail(git_repository_discover(&discovered, ALTERNATE_MALFORMED_FOLDER3, 0, ceiling_dirs.ptr)); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, ALTERNATE_NOT_FOUND_FOLDER, 0, ceiling_dirs.ptr)); +} + +void test_repo_discover__discovering_repository_with_ceiling(void) +{ + append_ceiling_dir(&ceiling_dirs, SUB_REPOSITORY_FOLDER_SUB); + + /* this must pass as ceiling_directories cannot prevent the current + * working directory to be checked */ + ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + + ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs.ptr)); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs.ptr)); +} + +void test_repo_discover__other_ceiling(void) +{ + append_ceiling_dir(&ceiling_dirs, SUB_REPOSITORY_FOLDER); + + /* this must pass as ceiling_directories cannot predent the current + * working directory to be checked */ + ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB, 0, ceiling_dirs.ptr)); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs.ptr)); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs.ptr)); +} + +void test_repo_discover__ceiling_should_not_affect_gitdir_redirection(void) +{ + append_ceiling_dir(&ceiling_dirs, SUB_REPOSITORY_FOLDER); + + /* gitfile redirection should not be affected by ceiling directories */ + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs.ptr, DISCOVER_FOLDER); +} + +void test_repo_discover__discovery_starting_at_file_succeeds(void) +{ + int fd; + + cl_assert((fd = p_creat(SUB_REPOSITORY_FOLDER "/file", 0600)) >= 0); + cl_assert(p_close(fd) == 0); + + ensure_repository_discover(SUB_REPOSITORY_FOLDER "/file", ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); +} + +void test_repo_discover__discovery_starting_at_system_root_causes_no_hang(void) +{ +#ifdef GIT_WIN32 + git_buf out = GIT_BUF_INIT; + cl_git_fail(git_repository_discover(&out, "C:/", 0, NULL)); + cl_git_fail(git_repository_discover(&out, "//localhost/", 0, NULL)); +#endif +} diff --git a/tests/libgit2/repo/env.c b/tests/libgit2/repo/env.c new file mode 100644 index 000000000..e3e522480 --- /dev/null +++ b/tests/libgit2/repo/env.c @@ -0,0 +1,277 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "sysdir.h" +#include + +static void clear_git_env(void) +{ + cl_setenv("GIT_DIR", NULL); + cl_setenv("GIT_CEILING_DIRECTORIES", NULL); + cl_setenv("GIT_INDEX_FILE", NULL); + cl_setenv("GIT_NAMESPACE", NULL); + cl_setenv("GIT_OBJECT_DIRECTORY", NULL); + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); + cl_setenv("GIT_WORK_TREE", NULL); + cl_setenv("GIT_COMMON_DIR", NULL); +} + +void test_repo_env__initialize(void) +{ + clear_git_env(); +} + +void test_repo_env__cleanup(void) +{ + cl_git_sandbox_cleanup(); + + if (git_fs_path_isdir("attr")) + git_futils_rmdir_r("attr", NULL, GIT_RMDIR_REMOVE_FILES); + if (git_fs_path_isdir("testrepo.git")) + git_futils_rmdir_r("testrepo.git", NULL, GIT_RMDIR_REMOVE_FILES); + if (git_fs_path_isdir("peeled.git")) + git_futils_rmdir_r("peeled.git", NULL, GIT_RMDIR_REMOVE_FILES); + + clear_git_env(); +} + +static int GIT_FORMAT_PRINTF(2, 3) cl_setenv_printf(const char *name, const char *fmt, ...) +{ + int ret; + va_list args; + git_str buf = GIT_STR_INIT; + + va_start(args, fmt); + cl_git_pass(git_str_vprintf(&buf, fmt, args)); + va_end(args); + + ret = cl_setenv(name, git_str_cstr(&buf)); + git_str_dispose(&buf); + return ret; +} + +/* Helper functions for test_repo_open__env, passing through the file and line + * from the caller rather than those of the helper. The expression strings + * distinguish between the possible failures within the helper. */ + +static void env_pass_(const char *path, const char *file, const char *func, int line) +{ + git_repository *repo; + cl_git_expect(git_repository_open_ext(NULL, path, GIT_REPOSITORY_OPEN_FROM_ENV, NULL), 0, file, func, line); + cl_git_expect(git_repository_open_ext(&repo, path, GIT_REPOSITORY_OPEN_FROM_ENV, NULL), 0, file, func, line); + cl_assert_at_line(git__suffixcmp(git_repository_path(repo), "attr/.git/") == 0, file, func, line); + cl_assert_at_line(git__suffixcmp(git_repository_workdir(repo), "attr/") == 0, file, func, line); + cl_assert_at_line(!git_repository_is_bare(repo), file, func, line); + git_repository_free(repo); +} +#define env_pass(path) env_pass_((path), __FILE__, __func__, __LINE__) + +#define cl_git_fail_at_line(expr, file, func, line) clar__assert((expr) < 0, file, func, line, "Expected function call to fail: " #expr, NULL, 1) + +static void env_fail_(const char *path, const char *file, const char *func, int line) +{ + git_repository *repo; + cl_git_fail_at_line(git_repository_open_ext(NULL, path, GIT_REPOSITORY_OPEN_FROM_ENV, NULL), file, func, line); + cl_git_fail_at_line(git_repository_open_ext(&repo, path, GIT_REPOSITORY_OPEN_FROM_ENV, NULL), file, func, line); +} +#define env_fail(path) env_fail_((path), __FILE__, __func__, __LINE__) + +static void env_cd_( + const char *path, + void (*passfail_)(const char *, const char *, const char *, int), + const char *file, const char *func, int line) +{ + git_str cwd_buf = GIT_STR_INIT; + cl_git_pass(git_fs_path_prettify_dir(&cwd_buf, ".", NULL)); + cl_must_pass(p_chdir(path)); + passfail_(NULL, file, func, line); + cl_must_pass(p_chdir(git_str_cstr(&cwd_buf))); + git_str_dispose(&cwd_buf); +} +#define env_cd_pass(path) env_cd_((path), env_pass_, __FILE__, __func__, __LINE__) +#define env_cd_fail(path) env_cd_((path), env_fail_, __FILE__, __func__, __LINE__) + +static void env_check_objects_(bool a, bool t, bool p, const char *file, const char *func, int line) +{ + git_repository *repo; + git_oid oid_a, oid_t, oid_p; + git_object *object; + cl_git_pass(git_oid_fromstr(&oid_a, "45141a79a77842c59a63229403220a4e4be74e3d")); + cl_git_pass(git_oid_fromstr(&oid_t, "1385f264afb75a56a5bec74243be9b367ba4ca08")); + cl_git_pass(git_oid_fromstr(&oid_p, "0df1a5865c8abfc09f1f2182e6a31be550e99f07")); + cl_git_expect(git_repository_open_ext(&repo, "attr", GIT_REPOSITORY_OPEN_FROM_ENV, NULL), 0, file, func, line); + + if (a) { + cl_git_expect(git_object_lookup(&object, repo, &oid_a, GIT_OBJECT_BLOB), 0, file, func, line); + git_object_free(object); + } else { + cl_git_fail_at_line(git_object_lookup(&object, repo, &oid_a, GIT_OBJECT_BLOB), file, func, line); + } + + if (t) { + cl_git_expect(git_object_lookup(&object, repo, &oid_t, GIT_OBJECT_BLOB), 0, file, func, line); + git_object_free(object); + } else { + cl_git_fail_at_line(git_object_lookup(&object, repo, &oid_t, GIT_OBJECT_BLOB), file, func, line); + } + + if (p) { + cl_git_expect(git_object_lookup(&object, repo, &oid_p, GIT_OBJECT_COMMIT), 0, file, func, line); + git_object_free(object); + } else { + cl_git_fail_at_line(git_object_lookup(&object, repo, &oid_p, GIT_OBJECT_COMMIT), file, func, line); + } + + git_repository_free(repo); +} +#define env_check_objects(a, t, t2) env_check_objects_((a), (t), (t2), __FILE__, __func__, __LINE__) + +void test_repo_env__open(void) +{ + git_repository *repo = NULL; + git_str repo_dir_buf = GIT_STR_INIT; + const char *repo_dir = NULL; + git_index *index = NULL; + const char *t_obj = "testrepo.git/objects"; + const char *p_obj = "peeled.git/objects"; + + clear_git_env(); + + cl_fixture_sandbox("attr"); + cl_fixture_sandbox("testrepo.git"); + cl_fixture_sandbox("peeled.git"); + cl_git_pass(p_rename("attr/.gitted", "attr/.git")); + + cl_git_pass(git_fs_path_prettify_dir(&repo_dir_buf, "attr", NULL)); + repo_dir = git_str_cstr(&repo_dir_buf); + + /* GIT_DIR that doesn't exist */ + cl_setenv("GIT_DIR", "does-not-exist"); + env_fail(NULL); + /* Explicit start_path overrides GIT_DIR */ + env_pass("attr"); + env_pass("attr/.git"); + env_pass("attr/sub"); + env_pass("attr/sub/sub"); + + /* GIT_DIR with relative paths */ + cl_setenv("GIT_DIR", "attr/.git"); + env_pass(NULL); + cl_setenv("GIT_DIR", "attr"); + env_fail(NULL); + cl_setenv("GIT_DIR", "attr/sub"); + env_fail(NULL); + cl_setenv("GIT_DIR", "attr/sub/sub"); + env_fail(NULL); + + /* GIT_DIR with absolute paths */ + cl_setenv_printf("GIT_DIR", "%s/.git", repo_dir); + env_pass(NULL); + cl_setenv("GIT_DIR", repo_dir); + env_fail(NULL); + cl_setenv_printf("GIT_DIR", "%s/sub", repo_dir); + env_fail(NULL); + cl_setenv_printf("GIT_DIR", "%s/sub/sub", repo_dir); + env_fail(NULL); + cl_setenv("GIT_DIR", NULL); + + /* Searching from the current directory */ + env_cd_pass("attr"); + env_cd_pass("attr/.git"); + env_cd_pass("attr/sub"); + env_cd_pass("attr/sub/sub"); + + /* A ceiling directory blocks searches from ascending into that + * directory, but doesn't block the start_path itself. */ + cl_setenv("GIT_CEILING_DIRECTORIES", repo_dir); + env_cd_pass("attr"); + env_cd_fail("attr/sub"); + env_cd_fail("attr/sub/sub"); + + cl_setenv_printf("GIT_CEILING_DIRECTORIES", "%s/sub", repo_dir); + env_cd_pass("attr"); + env_cd_pass("attr/sub"); + env_cd_fail("attr/sub/sub"); + + /* Multiple ceiling directories */ + cl_setenv_printf("GIT_CEILING_DIRECTORIES", "123%c%s/sub%cabc", + GIT_PATH_LIST_SEPARATOR, repo_dir, GIT_PATH_LIST_SEPARATOR); + env_cd_pass("attr"); + env_cd_pass("attr/sub"); + env_cd_fail("attr/sub/sub"); + + cl_setenv_printf("GIT_CEILING_DIRECTORIES", "%s%c%s/sub", + repo_dir, GIT_PATH_LIST_SEPARATOR, repo_dir); + env_cd_pass("attr"); + env_cd_fail("attr/sub"); + env_cd_fail("attr/sub/sub"); + + cl_setenv_printf("GIT_CEILING_DIRECTORIES", "%s/sub%c%s", + repo_dir, GIT_PATH_LIST_SEPARATOR, repo_dir); + env_cd_pass("attr"); + env_cd_fail("attr/sub"); + env_cd_fail("attr/sub/sub"); + + cl_setenv_printf("GIT_CEILING_DIRECTORIES", "%s%c%s/sub/sub", + repo_dir, GIT_PATH_LIST_SEPARATOR, repo_dir); + env_cd_pass("attr"); + env_cd_fail("attr/sub"); + env_cd_fail("attr/sub/sub"); + + cl_setenv("GIT_CEILING_DIRECTORIES", NULL); + + /* Index files */ + cl_setenv("GIT_INDEX_FILE", cl_fixture("gitgit.index")); + cl_git_pass(git_repository_open_ext(&repo, "attr", GIT_REPOSITORY_OPEN_FROM_ENV, NULL)); + cl_git_pass(git_repository_index(&index, repo)); + cl_assert_equal_s(git_index_path(index), cl_fixture("gitgit.index")); + cl_assert_equal_i(git_index_entrycount(index), 1437); + git_index_free(index); + git_repository_free(repo); + cl_setenv("GIT_INDEX_FILE", NULL); + + /* Namespaces */ + cl_setenv("GIT_NAMESPACE", "some-namespace"); + cl_git_pass(git_repository_open_ext(&repo, "attr", GIT_REPOSITORY_OPEN_FROM_ENV, NULL)); + cl_assert_equal_s(git_repository_get_namespace(repo), "some-namespace"); + git_repository_free(repo); + cl_setenv("GIT_NAMESPACE", NULL); + + /* Object directories and alternates */ + env_check_objects(true, false, false); + + cl_setenv("GIT_OBJECT_DIRECTORY", t_obj); + env_check_objects(false, true, false); + cl_setenv("GIT_OBJECT_DIRECTORY", NULL); + + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", t_obj); + env_check_objects(true, true, false); + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); + + cl_setenv("GIT_OBJECT_DIRECTORY", p_obj); + env_check_objects(false, false, true); + cl_setenv("GIT_OBJECT_DIRECTORY", NULL); + + cl_setenv("GIT_OBJECT_DIRECTORY", t_obj); + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", p_obj); + env_check_objects(false, true, true); + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); + cl_setenv("GIT_OBJECT_DIRECTORY", NULL); + + cl_setenv_printf("GIT_ALTERNATE_OBJECT_DIRECTORIES", + "%s%c%s", t_obj, GIT_PATH_LIST_SEPARATOR, p_obj); + env_check_objects(true, true, true); + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); + + cl_setenv_printf("GIT_ALTERNATE_OBJECT_DIRECTORIES", + "%s%c%s", p_obj, GIT_PATH_LIST_SEPARATOR, t_obj); + env_check_objects(true, true, true); + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); + + cl_fixture_cleanup("peeled.git"); + cl_fixture_cleanup("testrepo.git"); + cl_fixture_cleanup("attr"); + + git_str_dispose(&repo_dir_buf); + + clear_git_env(); +} diff --git a/tests/libgit2/repo/extensions.c b/tests/libgit2/repo/extensions.c new file mode 100644 index 000000000..e7772acd5 --- /dev/null +++ b/tests/libgit2/repo/extensions.c @@ -0,0 +1,72 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "sysdir.h" +#include + +git_repository *repo; + +void test_repo_extensions__initialize(void) +{ + git_config *config; + + repo = cl_git_sandbox_init("empty_bare.git"); + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 1)); + git_config_free(config); +} + +void test_repo_extensions__cleanup(void) +{ + cl_git_sandbox_cleanup(); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, NULL, 0)); +} + +void test_repo_extensions__builtin(void) +{ + git_repository *extended; + + cl_repo_set_string(repo, "extensions.noop", "foobar"); + + cl_git_pass(git_repository_open(&extended, "empty_bare.git")); + cl_assert(git_repository_path(extended) != NULL); + cl_assert(git__suffixcmp(git_repository_path(extended), "/") == 0); + git_repository_free(extended); +} + +void test_repo_extensions__negate_builtin(void) +{ + const char *in[] = { "foo", "!noop", "baz" }; + git_repository *extended; + + cl_repo_set_string(repo, "extensions.noop", "foobar"); + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); + + cl_git_fail(git_repository_open(&extended, "empty_bare.git")); + git_repository_free(extended); +} + +void test_repo_extensions__unsupported(void) +{ + git_repository *extended = NULL; + + cl_repo_set_string(repo, "extensions.unknown", "foobar"); + + cl_git_fail(git_repository_open(&extended, "empty_bare.git")); + git_repository_free(extended); +} + +void test_repo_extensions__adds_extension(void) +{ + const char *in[] = { "foo", "!noop", "newextension", "baz" }; + git_repository *extended; + + cl_repo_set_string(repo, "extensions.newextension", "foobar"); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); + + cl_git_pass(git_repository_open(&extended, "empty_bare.git")); + cl_assert(git_repository_path(extended) != NULL); + cl_assert(git__suffixcmp(git_repository_path(extended), "/") == 0); + git_repository_free(extended); +} diff --git a/tests/libgit2/repo/getters.c b/tests/libgit2/repo/getters.c new file mode 100644 index 000000000..d401bb832 --- /dev/null +++ b/tests/libgit2/repo/getters.c @@ -0,0 +1,53 @@ +#include "clar_libgit2.h" +#include "repo/repo_helpers.h" + +void test_repo_getters__is_empty_correctly_deals_with_pristine_looking_repos(void) +{ + git_repository *repo; + + repo = cl_git_sandbox_init("empty_bare.git"); + cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt"); + + cl_assert_equal_i(true, git_repository_is_empty(repo)); + + cl_git_sandbox_cleanup(); +} + +void test_repo_getters__is_empty_can_detect_used_repositories(void) +{ + git_repository *repo; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + cl_assert_equal_i(false, git_repository_is_empty(repo)); + + git_repository_free(repo); +} + +void test_repo_getters__is_empty_can_detect_repositories_with_defaultbranch_config_empty(void) +{ + git_repository *repo; + + create_tmp_global_config("tmp_global_path", "init.defaultBranch", ""); + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + cl_assert_equal_i(false, git_repository_is_empty(repo)); + + git_repository_free(repo); +} + +void test_repo_getters__retrieving_the_odb_honors_the_refcount(void) +{ + git_odb *odb; + git_repository *repo; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + cl_git_pass(git_repository_odb(&odb, repo)); + cl_assert(((git_refcount *)odb)->refcount.val == 2); + + git_repository_free(repo); + cl_assert(((git_refcount *)odb)->refcount.val == 1); + + git_odb_free(odb); +} diff --git a/tests/libgit2/repo/hashfile.c b/tests/libgit2/repo/hashfile.c new file mode 100644 index 000000000..e23bb77f9 --- /dev/null +++ b/tests/libgit2/repo/hashfile.c @@ -0,0 +1,171 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; + +void test_repo_hashfile__initialize(void) +{ + _repo = cl_git_sandbox_init("status"); +} + +void test_repo_hashfile__cleanup(void) +{ + cl_fixture_cleanup("absolute"); + cl_git_sandbox_cleanup(); + _repo = NULL; +} + +void test_repo_hashfile__simple(void) +{ + git_oid a, b; + git_str full = GIT_STR_INIT; + + /* hash with repo relative path */ + cl_git_pass(git_odb_hashfile(&a, "status/current_file", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "current_file", GIT_OBJECT_BLOB, NULL)); + cl_assert_equal_oid(&a, &b); + + cl_git_pass(git_str_joinpath(&full, git_repository_workdir(_repo), "current_file")); + + /* hash with full path */ + cl_git_pass(git_odb_hashfile(&a, full.ptr, GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, full.ptr, GIT_OBJECT_BLOB, NULL)); + cl_assert_equal_oid(&a, &b); + + /* hash with invalid type */ + cl_git_fail(git_odb_hashfile(&a, full.ptr, GIT_OBJECT_ANY)); + cl_git_fail(git_repository_hashfile(&b, _repo, full.ptr, GIT_OBJECT_OFS_DELTA, NULL)); + + git_str_dispose(&full); +} + +void test_repo_hashfile__filtered_in_workdir(void) +{ + git_str root = GIT_STR_INIT, txt = GIT_STR_INIT, bin = GIT_STR_INIT; + char cwd[GIT_PATH_MAX]; + git_oid a, b; + + cl_must_pass(p_getcwd(cwd, GIT_PATH_MAX)); + cl_must_pass(p_mkdir("absolute", 0777)); + cl_git_pass(git_str_joinpath(&root, cwd, "status")); + cl_git_pass(git_str_joinpath(&txt, root.ptr, "testfile.txt")); + cl_git_pass(git_str_joinpath(&bin, root.ptr, "testfile.bin")); + + cl_repo_set_bool(_repo, "core.autocrlf", true); + + cl_git_append2file("status/.gitattributes", "*.txt text\n*.bin binary\n\n"); + + /* create some sample content with CRLF in it */ + cl_git_mkfile("status/testfile.txt", "content\r\n"); + cl_git_mkfile("status/testfile.bin", "other\r\nstuff\r\n"); + + /* not equal hashes because of filtering */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJECT_BLOB, NULL)); + cl_assert(git_oid_cmp(&a, &b)); + + /* not equal hashes because of filtering when specified by absolute path */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, txt.ptr, GIT_OBJECT_BLOB, NULL)); + cl_assert(git_oid_cmp(&a, &b)); + + /* equal hashes because filter is binary */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJECT_BLOB, NULL)); + cl_assert_equal_oid(&a, &b); + + /* equal hashes because filter is binary when specified by absolute path */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, bin.ptr, GIT_OBJECT_BLOB, NULL)); + cl_assert_equal_oid(&a, &b); + + /* equal hashes when 'as_file' points to binary filtering */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJECT_BLOB, "foo.bin")); + cl_assert_equal_oid(&a, &b); + + /* equal hashes when 'as_file' points to binary filtering (absolute path) */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, txt.ptr, GIT_OBJECT_BLOB, "foo.bin")); + cl_assert_equal_oid(&a, &b); + + /* not equal hashes when 'as_file' points to text filtering */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJECT_BLOB, "foo.txt")); + cl_assert(git_oid_cmp(&a, &b)); + + /* not equal hashes when 'as_file' points to text filtering */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, bin.ptr, GIT_OBJECT_BLOB, "foo.txt")); + cl_assert(git_oid_cmp(&a, &b)); + + /* equal hashes when 'as_file' is empty and turns off filtering */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJECT_BLOB, "")); + cl_assert_equal_oid(&a, &b); + + cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJECT_BLOB, "")); + cl_assert_equal_oid(&a, &b); + + cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, txt.ptr, GIT_OBJECT_BLOB, "")); + cl_assert_equal_oid(&a, &b); + + cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, bin.ptr, GIT_OBJECT_BLOB, "")); + cl_assert_equal_oid(&a, &b); + + /* some hash type failures */ + cl_git_fail(git_odb_hashfile(&a, "status/testfile.txt", 0)); + cl_git_fail(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJECT_ANY, NULL)); + + git_str_dispose(&txt); + git_str_dispose(&bin); + git_str_dispose(&root); +} + +void test_repo_hashfile__filtered_outside_workdir(void) +{ + git_str root = GIT_STR_INIT, txt = GIT_STR_INIT, bin = GIT_STR_INIT; + char cwd[GIT_PATH_MAX]; + git_oid a, b; + + cl_must_pass(p_getcwd(cwd, GIT_PATH_MAX)); + cl_must_pass(p_mkdir("absolute", 0777)); + cl_git_pass(git_str_joinpath(&root, cwd, "absolute")); + cl_git_pass(git_str_joinpath(&txt, root.ptr, "testfile.txt")); + cl_git_pass(git_str_joinpath(&bin, root.ptr, "testfile.bin")); + + cl_repo_set_bool(_repo, "core.autocrlf", true); + cl_git_append2file("status/.gitattributes", "*.txt text\n*.bin binary\n\n"); + + /* create some sample content with CRLF in it */ + cl_git_mkfile("absolute/testfile.txt", "content\r\n"); + cl_git_mkfile("absolute/testfile.bin", "other\r\nstuff\r\n"); + + /* not equal hashes because of filtering */ + cl_git_pass(git_odb_hashfile(&a, "absolute/testfile.txt", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, txt.ptr, GIT_OBJECT_BLOB, "testfile.txt")); + cl_assert(git_oid_cmp(&a, &b)); + + /* equal hashes because filter is binary */ + cl_git_pass(git_odb_hashfile(&a, "absolute/testfile.bin", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, bin.ptr, GIT_OBJECT_BLOB, "testfile.bin")); + cl_assert_equal_oid(&a, &b); + + /* + * equal hashes because no filtering occurs for absolute paths outside the working + * directory unless as_path is specified + */ + cl_git_pass(git_odb_hashfile(&a, "absolute/testfile.txt", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, txt.ptr, GIT_OBJECT_BLOB, NULL)); + cl_assert_equal_oid(&a, &b); + + cl_git_pass(git_odb_hashfile(&a, "absolute/testfile.bin", GIT_OBJECT_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, bin.ptr, GIT_OBJECT_BLOB, NULL)); + cl_assert_equal_oid(&a, &b); + + git_str_dispose(&txt); + git_str_dispose(&bin); + git_str_dispose(&root); +} diff --git a/tests/libgit2/repo/head.c b/tests/libgit2/repo/head.c new file mode 100644 index 000000000..822990555 --- /dev/null +++ b/tests/libgit2/repo/head.c @@ -0,0 +1,182 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "repo_helpers.h" +#include "posix.h" +#include "git2/annotated_commit.h" + +static const char *g_email = "foo@example.com"; +static git_repository *repo; + +void test_repo_head__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_set_ident(repo, "Foo Bar", g_email)); +} + +void test_repo_head__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_repo_head__unborn_head(void) +{ + git_reference *ref; + + cl_git_pass(git_repository_head_detached(repo)); + + make_head_unborn(repo, NON_EXISTING_HEAD); + + cl_assert(git_repository_head_unborn(repo) == 1); + + + /* take the repo back to it's original state */ + cl_git_pass(git_reference_symbolic_create(&ref, repo, "HEAD", "refs/heads/master", 1, NULL)); + cl_assert(git_repository_head_unborn(repo) == 0); + + git_reference_free(ref); +} + +void test_repo_head__set_head_Attaches_HEAD_to_un_unborn_branch_when_the_branch_doesnt_exist(void) +{ + git_reference *head; + + cl_git_pass(git_repository_set_head(repo, "refs/heads/doesnt/exist/yet")); + + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_head(&head, repo)); +} + +void test_repo_head__set_head_Returns_ENOTFOUND_when_the_reference_doesnt_exist(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_set_head(repo, "refs/tags/doesnt/exist/yet")); +} + +void test_repo_head__set_head_Fails_when_the_reference_points_to_a_non_commitish(void) +{ + cl_git_fail(git_repository_set_head(repo, "refs/tags/point_to_blob")); +} + +void test_repo_head__set_head_Attaches_HEAD_when_the_reference_points_to_a_branch(void) +{ + git_reference *head; + + cl_git_pass(git_repository_set_head(repo, "refs/heads/br2")); + + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_head(&head, repo)); + cl_assert_equal_s("refs/heads/br2", git_reference_name(head)); + + git_reference_free(head); +} + +static void assert_head_is_correctly_detached(void) +{ + git_reference *head; + git_object *commit; + + cl_assert_equal_i(true, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_head(&head, repo)); + + cl_git_pass(git_object_lookup(&commit, repo, git_reference_target(head), GIT_OBJECT_COMMIT)); + + git_object_free(commit); + git_reference_free(head); +} + +void test_repo_head__set_head_Detaches_HEAD_when_the_reference_doesnt_point_to_a_branch(void) +{ + cl_git_pass(git_repository_set_head(repo, "refs/tags/test")); + + cl_assert_equal_i(true, git_repository_head_detached(repo)); + + assert_head_is_correctly_detached(); +} + +void test_repo_head__set_head_detached_Return_ENOTFOUND_when_the_object_doesnt_exist(void) +{ + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); + + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_set_head_detached(repo, &oid)); +} + +void test_repo_head__set_head_detached_Fails_when_the_object_isnt_a_commitish(void) +{ + git_object *blob; + + cl_git_pass(git_revparse_single(&blob, repo, "point_to_blob")); + + cl_git_fail(git_repository_set_head_detached(repo, git_object_id(blob))); + + git_object_free(blob); +} + +void test_repo_head__set_head_detached_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void) +{ + git_object *tag; + + cl_git_pass(git_revparse_single(&tag, repo, "tags/test")); + cl_assert_equal_i(GIT_OBJECT_TAG, git_object_type(tag)); + + cl_git_pass(git_repository_set_head_detached(repo, git_object_id(tag))); + + assert_head_is_correctly_detached(); + + git_object_free(tag); +} + +void test_repo_head__detach_head_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void) +{ + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_detach_head(repo)); + + assert_head_is_correctly_detached(); +} + +void test_repo_head__detach_head_Fails_if_HEAD_and_point_to_a_non_commitish(void) +{ + git_reference *head; + + cl_git_pass(git_reference_symbolic_create(&head, repo, GIT_HEAD_FILE, "refs/tags/point_to_blob", 1, NULL)); + + cl_git_fail(git_repository_detach_head(repo)); + + git_reference_free(head); +} + +void test_repo_head__detaching_an_unborn_branch_returns_GIT_EUNBORNBRANCH(void) +{ + make_head_unborn(repo, NON_EXISTING_HEAD); + + cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_detach_head(repo)); +} + +void test_repo_head__retrieving_an_unborn_branch_returns_GIT_EUNBORNBRANCH(void) +{ + git_reference *head; + + make_head_unborn(repo, NON_EXISTING_HEAD); + + cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_head(&head, repo)); +} + +void test_repo_head__retrieving_a_missing_head_returns_GIT_ENOTFOUND(void) +{ + git_reference *head; + + delete_head(repo); + + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_head(&head, repo)); +} + +void test_repo_head__can_tell_if_an_unborn_head_is_detached(void) +{ + make_head_unborn(repo, NON_EXISTING_HEAD); + + cl_assert_equal_i(false, git_repository_head_detached(repo)); +} diff --git a/tests/libgit2/repo/headtree.c b/tests/libgit2/repo/headtree.c new file mode 100644 index 000000000..e899ac399 --- /dev/null +++ b/tests/libgit2/repo/headtree.c @@ -0,0 +1,53 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "repo_helpers.h" +#include "posix.h" + +static git_repository *repo; +static git_tree *tree; + +void test_repo_headtree__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); + tree = NULL; +} + +void test_repo_headtree__cleanup(void) +{ + git_tree_free(tree); + cl_git_sandbox_cleanup(); +} + +void test_repo_headtree__can_retrieve_the_root_tree_from_a_detached_head(void) +{ + cl_git_pass(git_repository_detach_head(repo)); + + cl_git_pass(git_repository_head_tree(&tree, repo)); + + cl_assert(git_oid_streq(git_tree_id(tree), "az")); +} + +void test_repo_headtree__can_retrieve_the_root_tree_from_a_non_detached_head(void) +{ + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_head_tree(&tree, repo)); + + cl_assert(git_oid_streq(git_tree_id(tree), "az")); +} + +void test_repo_headtree__when_head_is_unborn_returns_EUNBORNBRANCH(void) +{ + make_head_unborn(repo, NON_EXISTING_HEAD); + + cl_assert_equal_i(true, git_repository_head_unborn(repo)); + + cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_head_tree(&tree, repo)); +} + +void test_repo_headtree__when_head_is_missing_returns_ENOTFOUND(void) +{ + delete_head(repo); + + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_head_tree(&tree, repo)); +} diff --git a/tests/libgit2/repo/init.c b/tests/libgit2/repo/init.c new file mode 100644 index 000000000..7cf6742ca --- /dev/null +++ b/tests/libgit2/repo/init.c @@ -0,0 +1,738 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "repository.h" +#include "config.h" +#include "path.h" +#include "config/config_helpers.h" +#include "repo/repo_helpers.h" + +enum repo_mode { + STANDARD_REPOSITORY = 0, + BARE_REPOSITORY = 1 +}; + +static git_repository *g_repo = NULL; +static git_str g_global_path = GIT_STR_INIT; + +void test_repo_init__initialize(void) +{ + g_repo = NULL; + + git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, + &g_global_path); +} + +void test_repo_init__cleanup(void) +{ + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, + g_global_path.ptr); + git_str_dispose(&g_global_path); + + cl_fixture_cleanup("tmp_global_path"); +} + +static void cleanup_repository(void *path) +{ + git_repository_free(g_repo); + g_repo = NULL; + + cl_fixture_cleanup((const char *)path); +} + +static void ensure_repository_init( + const char *working_directory, + int is_bare, + const char *expected_path_repository, + const char *expected_working_directory) +{ + const char *workdir; + + cl_assert(!git_fs_path_isdir(working_directory)); + + cl_git_pass(git_repository_init(&g_repo, working_directory, is_bare)); + + workdir = git_repository_workdir(g_repo); + if (workdir != NULL || expected_working_directory != NULL) { + cl_assert( + git__suffixcmp(workdir, expected_working_directory) == 0 + ); + } + + cl_assert( + git__suffixcmp(git_repository_path(g_repo), expected_path_repository) == 0 + ); + + cl_assert(git_repository_is_bare(g_repo) == is_bare); + +#ifdef GIT_WIN32 + if (!is_bare) { + DWORD fattrs = GetFileAttributes(git_repository_path(g_repo)); + cl_assert((fattrs & FILE_ATTRIBUTE_HIDDEN) != 0); + } +#endif + + cl_assert(git_repository_is_empty(g_repo)); +} + +void test_repo_init__standard_repo(void) +{ + cl_set_cleanup(&cleanup_repository, "testrepo"); + ensure_repository_init("testrepo/", 0, "testrepo/.git/", "testrepo/"); +} + +void test_repo_init__standard_repo_noslash(void) +{ + cl_set_cleanup(&cleanup_repository, "testrepo"); + ensure_repository_init("testrepo", 0, "testrepo/.git/", "testrepo/"); +} + +void test_repo_init__bare_repo(void) +{ + cl_set_cleanup(&cleanup_repository, "testrepo.git"); + ensure_repository_init("testrepo.git/", 1, "testrepo.git/", NULL); +} + +void test_repo_init__bare_repo_noslash(void) +{ + cl_set_cleanup(&cleanup_repository, "testrepo.git"); + ensure_repository_init("testrepo.git", 1, "testrepo.git/", NULL); +} + +void test_repo_init__bare_repo_escaping_current_workdir(void) +{ + git_str path_repository = GIT_STR_INIT; + git_str path_current_workdir = GIT_STR_INIT; + + cl_git_pass(git_fs_path_prettify_dir(&path_current_workdir, ".", NULL)); + + cl_git_pass(git_str_joinpath(&path_repository, git_str_cstr(&path_current_workdir), "a/b/c")); + cl_git_pass(git_futils_mkdir_r(git_str_cstr(&path_repository), GIT_DIR_MODE)); + + /* Change the current working directory */ + cl_git_pass(chdir(git_str_cstr(&path_repository))); + + /* Initialize a bare repo with a relative path escaping out of the current working directory */ + cl_git_pass(git_repository_init(&g_repo, "../d/e.git", 1)); + cl_git_pass(git__suffixcmp(git_repository_path(g_repo), "/a/b/d/e.git/")); + + git_repository_free(g_repo); + g_repo = NULL; + + /* Open a bare repo with a relative path escaping out of the current working directory */ + cl_git_pass(git_repository_open(&g_repo, "../d/e.git")); + + cl_git_pass(chdir(git_str_cstr(&path_current_workdir))); + + git_str_dispose(&path_current_workdir); + git_str_dispose(&path_repository); + + cleanup_repository("a"); +} + +void test_repo_init__reinit_bare_repo(void) +{ + cl_set_cleanup(&cleanup_repository, "reinit.git"); + + /* Initialize the repository */ + cl_git_pass(git_repository_init(&g_repo, "reinit.git", 1)); + git_repository_free(g_repo); + g_repo = NULL; + + /* Reinitialize the repository */ + cl_git_pass(git_repository_init(&g_repo, "reinit.git", 1)); +} + +void test_repo_init__reinit_too_recent_bare_repo(void) +{ + git_config *config; + + /* Initialize the repository */ + cl_git_pass(git_repository_init(&g_repo, "reinit.git", 1)); + git_repository_config(&config, g_repo); + + /* + * Hack the config of the repository to make it look like it has + * been created by a recenter version of git/libgit2 + */ + cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 42)); + + git_config_free(config); + git_repository_free(g_repo); + g_repo = NULL; + + /* Try to reinitialize the repository */ + cl_git_fail(git_repository_init(&g_repo, "reinit.git", 1)); + + cl_fixture_cleanup("reinit.git"); +} + +void test_repo_init__additional_templates(void) +{ + git_str path = GIT_STR_INIT; + + cl_set_cleanup(&cleanup_repository, "tester"); + + ensure_repository_init("tester", 0, "tester/.git/", "tester/"); + + cl_git_pass( + git_str_joinpath(&path, git_repository_path(g_repo), "description")); + cl_assert(git_fs_path_isfile(git_str_cstr(&path))); + + cl_git_pass( + git_str_joinpath(&path, git_repository_path(g_repo), "info/exclude")); + cl_assert(git_fs_path_isfile(git_str_cstr(&path))); + + cl_git_pass( + git_str_joinpath(&path, git_repository_path(g_repo), "hooks")); + cl_assert(git_fs_path_isdir(git_str_cstr(&path))); + /* won't confirm specific contents of hooks dir since it may vary */ + + git_str_dispose(&path); +} + +static void assert_config_entry_on_init_bytype( + const char *config_key, int expected_value, bool is_bare) +{ + git_config *config; + int error, current_value; + const char *repo_path = is_bare ? + "config_entry/test.bare.git" : "config_entry/test.non.bare.git"; + + cl_set_cleanup(&cleanup_repository, "config_entry"); + + cl_git_pass(git_repository_init(&g_repo, repo_path, is_bare)); + + cl_git_pass(git_repository_config(&config, g_repo)); + error = git_config_get_bool(¤t_value, config, config_key); + git_config_free(config); + + if (expected_value >= 0) { + cl_assert_equal_i(0, error); + cl_assert_equal_i(expected_value, current_value); + } else { + cl_assert_equal_i(expected_value, error); + } +} + +static void assert_config_entry_on_init( + const char *config_key, int expected_value) +{ + assert_config_entry_on_init_bytype(config_key, expected_value, true); + git_repository_free(g_repo); + g_repo = NULL; + + assert_config_entry_on_init_bytype(config_key, expected_value, false); +} + +void test_repo_init__detect_filemode(void) +{ + assert_config_entry_on_init("core.filemode", cl_is_chmod_supported()); +} + +void test_repo_init__detect_ignorecase(void) +{ + struct stat st; + bool found_without_match; + + cl_git_write2file("testCAPS", "whatever\n", 0, O_CREAT | O_WRONLY, 0666); + found_without_match = (p_stat("Testcaps", &st) == 0); + cl_must_pass(p_unlink("testCAPS")); + + assert_config_entry_on_init( + "core.ignorecase", found_without_match ? true : GIT_ENOTFOUND); +} + +/* + * Windows: if the filesystem supports symlinks (because we're running + * as administrator, or because the user has opted into it for normal + * users) then we can also opt-in explicitly by settings `core.symlinks` + * in the global config. Symlinks remain off by default. + */ + +void test_repo_init__symlinks_win32_enabled_by_global_config(void) +{ +#ifndef GIT_WIN32 + cl_skip(); +#else + git_config *config, *repo_config; + int val; + + if (!git_fs_path_supports_symlinks("link")) + cl_skip(); + + create_tmp_global_config("tmp_global_config", "core.symlinks", "true"); + + /* + * Create a new repository (can't use `assert_config_on_init` since we + * want to examine configuration levels with more granularity.) + */ + cl_git_pass(git_repository_init(&g_repo, "config_entry/test.non.bare.git", false)); + + /* Ensure that core.symlinks remains set (via the global config). */ + cl_git_pass(git_repository_config(&config, g_repo)); + cl_git_pass(git_config_get_bool(&val, config, "core.symlinks")); + cl_assert_equal_i(1, val); + + /* + * Ensure that the repository config does not set core.symlinks. + * It should remain inherited. + */ + cl_git_pass(git_config_open_level(&repo_config, config, GIT_CONFIG_LEVEL_LOCAL)); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_bool(&val, repo_config, "core.symlinks")); + git_config_free(repo_config); + + git_config_free(config); + + git_repository_free(g_repo); + g_repo = NULL; +#endif +} + +void test_repo_init__symlinks_win32_off_by_default(void) +{ +#ifndef GIT_WIN32 + cl_skip(); +#else + assert_config_entry_on_init("core.symlinks", false); +#endif +} + +void test_repo_init__symlinks_posix_detected(void) +{ +#ifdef GIT_WIN32 + cl_skip(); +#else + assert_config_entry_on_init( + "core.symlinks", git_fs_path_supports_symlinks("link") ? GIT_ENOTFOUND : false); +#endif +} + +void test_repo_init__detect_precompose_unicode_required(void) +{ +#ifdef GIT_USE_ICONV + char *composed = "ḱṷṓn", *decomposed = "ḱṷṓn"; + struct stat st; + bool found_with_nfd; + + cl_git_write2file(composed, "whatever\n", 0, O_CREAT | O_WRONLY, 0666); + found_with_nfd = (p_stat(decomposed, &st) == 0); + cl_must_pass(p_unlink(composed)); + + assert_config_entry_on_init("core.precomposeunicode", found_with_nfd); +#else + assert_config_entry_on_init("core.precomposeunicode", GIT_ENOTFOUND); +#endif +} + +void test_repo_init__reinit_doesnot_overwrite_ignorecase(void) +{ + git_config *config; + int current_value; + + /* Init a new repo */ + cl_set_cleanup(&cleanup_repository, "not.overwrite.git"); + cl_git_pass(git_repository_init(&g_repo, "not.overwrite.git", 1)); + + /* Change the "core.ignorecase" config value to something unlikely */ + git_repository_config(&config, g_repo); + git_config_set_int32(config, "core.ignorecase", 42); + git_config_free(config); + git_repository_free(g_repo); + g_repo = NULL; + + /* Reinit the repository */ + cl_git_pass(git_repository_init(&g_repo, "not.overwrite.git", 1)); + git_repository_config(&config, g_repo); + + /* Ensure the "core.ignorecase" config value hasn't been updated */ + cl_git_pass(git_config_get_int32(¤t_value, config, "core.ignorecase")); + cl_assert_equal_i(42, current_value); + + git_config_free(config); +} + +void test_repo_init__reinit_overwrites_filemode(void) +{ + int expected = cl_is_chmod_supported(), current_value; + + /* Init a new repo */ + cl_set_cleanup(&cleanup_repository, "overwrite.git"); + cl_git_pass(git_repository_init(&g_repo, "overwrite.git", 1)); + + /* Change the "core.filemode" config value to something unlikely */ + cl_repo_set_bool(g_repo, "core.filemode", !expected); + + git_repository_free(g_repo); + g_repo = NULL; + + /* Reinit the repository */ + cl_git_pass(git_repository_init(&g_repo, "overwrite.git", 1)); + + /* Ensure the "core.filemode" config value has been reset */ + current_value = cl_repo_get_bool(g_repo, "core.filemode"); + cl_assert_equal_i(expected, current_value); +} + +void test_repo_init__sets_logAllRefUpdates_according_to_type_of_repository(void) +{ + assert_config_entry_on_init_bytype("core.logallrefupdates", GIT_ENOTFOUND, true); + git_repository_free(g_repo); + assert_config_entry_on_init_bytype("core.logallrefupdates", true, false); +} + +void test_repo_init__extended_0(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + /* without MKDIR this should fail */ + cl_git_fail(git_repository_init_ext(&g_repo, "extended", &opts)); + + /* make the directory first, then it should succeed */ + cl_git_pass(git_futils_mkdir("extended", 0775, 0)); + cl_git_pass(git_repository_init_ext(&g_repo, "extended", &opts)); + + cl_assert(!git__suffixcmp(git_repository_workdir(g_repo), "/extended/")); + cl_assert(!git__suffixcmp(git_repository_path(g_repo), "/extended/.git/")); + cl_assert(!git_repository_is_bare(g_repo)); + cl_assert(git_repository_is_empty(g_repo)); + + cleanup_repository("extended"); +} + +void test_repo_init__extended_1(void) +{ + git_reference *ref; + git_remote *remote; + struct stat st; + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; + opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP; + opts.workdir_path = "../c_wd"; + opts.description = "Awesomest test repository evah"; + opts.initial_head = "development"; + opts.origin_url = "https://github.com/libgit2/libgit2.git"; + + cl_git_pass(git_repository_init_ext(&g_repo, "root/b/c.git", &opts)); + + cl_assert(!git__suffixcmp(git_repository_workdir(g_repo), "/c_wd/")); + cl_assert(!git__suffixcmp(git_repository_path(g_repo), "/c.git/")); + cl_assert(git_fs_path_isfile("root/b/c_wd/.git")); + cl_assert(!git_repository_is_bare(g_repo)); + /* repo will not be counted as empty because we set head to "development" */ + cl_assert(!git_repository_is_empty(g_repo)); + + cl_git_pass(git_fs_path_lstat(git_repository_path(g_repo), &st)); + cl_assert(S_ISDIR(st.st_mode)); + if (cl_is_chmod_supported()) + cl_assert((S_ISGID & st.st_mode) == S_ISGID); + else + cl_assert((S_ISGID & st.st_mode) == 0); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); + cl_assert(git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC); + cl_assert_equal_s("refs/heads/development", git_reference_symbolic_target(ref)); + git_reference_free(ref); + + cl_git_pass(git_remote_lookup(&remote, g_repo, "origin")); + cl_assert_equal_s("origin", git_remote_name(remote)); + cl_assert_equal_s(opts.origin_url, git_remote_url(remote)); + git_remote_free(remote); + + git_repository_free(g_repo); + cl_fixture_cleanup("root"); +} + +void test_repo_init__relative_gitdir(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_str dot_git_content = GIT_STR_INIT; + + opts.workdir_path = "../c_wd"; + opts.flags = + GIT_REPOSITORY_INIT_MKPATH | + GIT_REPOSITORY_INIT_RELATIVE_GITLINK | + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; + + /* make the directory first, then it should succeed */ + cl_git_pass(git_repository_init_ext(&g_repo, "root/b/my_repository", &opts)); + + cl_assert(!git__suffixcmp(git_repository_workdir(g_repo), "root/b/c_wd/")); + cl_assert(!git__suffixcmp(git_repository_path(g_repo), "root/b/my_repository/")); + cl_assert(!git_repository_is_bare(g_repo)); + cl_assert(git_repository_is_empty(g_repo)); + + /* Verify that the gitlink and worktree entries are relative */ + + /* Verify worktree */ + assert_config_entry_value(g_repo, "core.worktree", "../c_wd/"); + + /* Verify gitlink */ + cl_git_pass(git_futils_readbuffer(&dot_git_content, "root/b/c_wd/.git")); + cl_assert_equal_s("gitdir: ../my_repository/", dot_git_content.ptr); + + git_str_dispose(&dot_git_content); + cleanup_repository("root"); +} + +void test_repo_init__relative_gitdir_2(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_str dot_git_content = GIT_STR_INIT; + git_str full_path = GIT_STR_INIT; + + cl_git_pass(git_fs_path_prettify(&full_path, ".", NULL)); + cl_git_pass(git_str_joinpath(&full_path, full_path.ptr, "root/b/c_wd")); + + opts.workdir_path = full_path.ptr; + opts.flags = + GIT_REPOSITORY_INIT_MKPATH | + GIT_REPOSITORY_INIT_RELATIVE_GITLINK | + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; + + /* make the directory first, then it should succeed */ + cl_git_pass(git_repository_init_ext(&g_repo, "root/b/my_repository", &opts)); + git_str_dispose(&full_path); + + cl_assert(!git__suffixcmp(git_repository_workdir(g_repo), "root/b/c_wd/")); + cl_assert(!git__suffixcmp(git_repository_path(g_repo), "root/b/my_repository/")); + cl_assert(!git_repository_is_bare(g_repo)); + cl_assert(git_repository_is_empty(g_repo)); + + /* Verify that the gitlink and worktree entries are relative */ + + /* Verify worktree */ + assert_config_entry_value(g_repo, "core.worktree", "../c_wd/"); + + /* Verify gitlink */ + cl_git_pass(git_futils_readbuffer(&dot_git_content, "root/b/c_wd/.git")); + cl_assert_equal_s("gitdir: ../my_repository/", dot_git_content.ptr); + + git_str_dispose(&dot_git_content); + cleanup_repository("root"); +} + +void test_repo_init__can_reinit_an_initialized_repository(void) +{ + git_repository *reinit; + + cl_set_cleanup(&cleanup_repository, "extended"); + + cl_git_pass(git_futils_mkdir("extended", 0775, 0)); + cl_git_pass(git_repository_init(&g_repo, "extended", false)); + + cl_git_pass(git_repository_init(&reinit, "extended", false)); + + cl_assert_equal_s(git_repository_path(g_repo), git_repository_path(reinit)); + + git_repository_free(reinit); +} + +void test_repo_init__init_with_initial_commit(void) +{ + git_index *index; + + cl_set_cleanup(&cleanup_repository, "committed"); + + /* Initialize the repository */ + cl_git_pass(git_repository_init(&g_repo, "committed", 0)); + + /* Index will be automatically created when requested for a new repo */ + cl_git_pass(git_repository_index(&index, g_repo)); + + /* Create a file so we can commit it + * + * If you are writing code outside the test suite, you can create this + * file any way that you like, such as: + * FILE *fp = fopen("committed/file.txt", "w"); + * fputs("some stuff\n", fp); + * fclose(fp); + * We like to use the help functions because they do error detection + * in a way that's easily compatible with our test suite. + */ + cl_git_mkfile("committed/file.txt", "some stuff\n"); + + /* Add file to the index */ + cl_git_pass(git_index_add_bypath(index, "file.txt")); + cl_git_pass(git_index_write(index)); + + /* Intentionally not using cl_repo_commit_from_index here so this code + * can be used as an example of how an initial commit is typically + * made to a repository... + */ + + /* Make sure we're ready to use git_signature_default :-) */ + { + git_config *cfg, *local; + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_open_level(&local, cfg, GIT_CONFIG_LEVEL_LOCAL)); + cl_git_pass(git_config_set_string(local, "user.name", "Test User")); + cl_git_pass(git_config_set_string(local, "user.email", "t@example.com")); + git_config_free(local); + git_config_free(cfg); + } + + /* Create a commit with the new contents of the index */ + { + git_signature *sig; + git_oid tree_id, commit_id; + git_tree *tree; + + cl_git_pass(git_signature_default(&sig, g_repo)); + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + cl_git_pass(git_commit_create_v( + &commit_id, g_repo, "HEAD", sig, sig, + NULL, "First", tree, 0)); + + git_tree_free(tree); + git_signature_free(sig); + } + + git_index_free(index); +} + +void test_repo_init__at_filesystem_root(void) +{ + git_repository *repo; + const char *sandbox = clar_sandbox_path(); + git_str root = GIT_STR_INIT; + int root_len; + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_STRUCTURE")) + cl_skip(); + + root_len = git_fs_path_root(sandbox); + cl_assert(root_len >= 0); + + git_str_put(&root, sandbox, root_len+1); + git_str_joinpath(&root, root.ptr, "libgit2_test_dir"); + + cl_assert(!git_fs_path_exists(root.ptr)); + + cl_git_pass(git_repository_init(&repo, root.ptr, 0)); + cl_assert(git_fs_path_isdir(root.ptr)); + cl_git_pass(git_futils_rmdir_r(root.ptr, NULL, GIT_RMDIR_REMOVE_FILES)); + + git_str_dispose(&root); + git_repository_free(repo); +} + +void test_repo_init__nonexisting_directory(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_repository *repo; + + /* + * If creating a repo with non-existing parent directories, then libgit2 + * will by default create the complete directory hierarchy if using + * `git_repository_init`. Thus, let's use the extended version and not + * set the `GIT_REPOSITORY_INIT_MKPATH` flag. + */ + cl_git_fail(git_repository_init_ext(&repo, "nonexisting/path", &opts)); +} + +void test_repo_init__nonexisting_root(void) +{ +#ifdef GIT_WIN32 + git_repository *repo; + + /* + * This really only depends on the nonexistence of the Q: drive. We + * cannot implement the equivalent test on Unix systems, as there is + * fundamentally no path that is disconnected from the root directory. + */ + cl_git_fail(git_repository_init(&repo, "Q:/non/existent/path", 0)); + cl_git_fail(git_repository_init(&repo, "Q:\\non\\existent\\path", 0)); +#else + clar__skip(); +#endif +} + +void test_repo_init__unwriteable_directory(void) +{ +#ifndef GIT_WIN32 + git_repository *repo; + + if (geteuid() == 0) + clar__skip(); + + /* + * Create a non-writeable directory so that we cannot create directories + * inside of it. The root user has CAP_DAC_OVERRIDE, so he doesn't care + * for the directory permissions and thus we need to skip the test if + * run as root user. + */ + cl_must_pass(p_mkdir("unwriteable", 0444)); + cl_git_fail(git_repository_init(&repo, "unwriteable/repo", 0)); + cl_must_pass(p_rmdir("unwriteable")); +#else + clar__skip(); +#endif +} + +void test_repo_init__defaultbranch_config(void) +{ + git_reference *head; + + cl_set_cleanup(&cleanup_repository, "repo"); + + create_tmp_global_config("tmp_global_path", "init.defaultbranch", "my_default_branch"); + + cl_git_pass(git_repository_init(&g_repo, "repo", 0)); + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + + cl_assert_equal_s("refs/heads/my_default_branch", git_reference_symbolic_target(head)); + + git_reference_free(head); +} + +void test_repo_init__defaultbranch_config_empty(void) +{ + git_reference *head; + + cl_set_cleanup(&cleanup_repository, "repo"); + + create_tmp_global_config("tmp_global_path", "init.defaultbranch", ""); + + cl_git_pass(git_repository_init(&g_repo, "repo", 0)); + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + + cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); + + git_reference_free(head); +} + +void test_repo_init__longpath(void) +{ +#ifdef GIT_WIN32 + size_t padding = CONST_STRLEN("objects/pack/pack-.pack.lock") + GIT_OID_HEXSZ; + size_t max, i; + git_str path = GIT_STR_INIT; + git_repository *one = NULL, *two = NULL; + + /* + * Files within repositories need to fit within MAX_PATH; + * that means a repo path must be at most (MAX_PATH - 18). + */ + cl_git_pass(git_str_puts(&path, clar_sandbox_path())); + cl_git_pass(git_str_putc(&path, '/')); + + max = ((MAX_PATH) - path.size) - padding; + + for (i = 0; i < max - 1; i++) + cl_git_pass(git_str_putc(&path, 'a')); + + cl_git_pass(git_repository_init(&one, path.ptr, 1)); + + /* Paths longer than this are rejected */ + cl_git_pass(git_str_putc(&path, 'z')); + cl_git_fail(git_repository_init(&two, path.ptr, 1)); + + git_repository_free(one); + git_repository_free(two); + git_str_dispose(&path); +#endif +} diff --git a/tests/libgit2/repo/message.c b/tests/libgit2/repo/message.c new file mode 100644 index 000000000..6241f48f9 --- /dev/null +++ b/tests/libgit2/repo/message.c @@ -0,0 +1,39 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "posix.h" + +static git_repository *_repo; + +void test_repo_message__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_repo_message__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_repo_message__none(void) +{ + git_buf actual = GIT_BUF_INIT; + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_message(&actual, _repo)); +} + +void test_repo_message__message(void) +{ + git_str path = GIT_STR_INIT; + git_buf actual = GIT_BUF_INIT; + const char expected[] = "Test\n\nThis is a test of the emergency broadcast system\n"; + + cl_git_pass(git_str_joinpath(&path, git_repository_path(_repo), "MERGE_MSG")); + cl_git_mkfile(git_str_cstr(&path), expected); + + cl_git_pass(git_repository_message(&actual, _repo)); + cl_assert_equal_s(expected, actual.ptr); + git_buf_dispose(&actual); + + cl_git_pass(p_unlink(git_str_cstr(&path))); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_message(&actual, _repo)); + git_str_dispose(&path); +} diff --git a/tests/libgit2/repo/new.c b/tests/libgit2/repo/new.c new file mode 100644 index 000000000..d77e903f6 --- /dev/null +++ b/tests/libgit2/repo/new.c @@ -0,0 +1,27 @@ +#include "clar_libgit2.h" +#include "git2/sys/repository.h" + +void test_repo_new__has_nothing(void) +{ + git_repository *repo; + + cl_git_pass(git_repository_new(&repo)); + cl_assert_equal_b(true, git_repository_is_bare(repo)); + cl_assert_equal_p(NULL, git_repository_path(repo)); + cl_assert_equal_p(NULL, git_repository_workdir(repo)); + git_repository_free(repo); +} + +void test_repo_new__is_bare_until_workdir_set(void) +{ + git_repository *repo; + + cl_git_pass(git_repository_new(&repo)); + cl_assert_equal_b(true, git_repository_is_bare(repo)); + + cl_git_pass(git_repository_set_workdir(repo, clar_sandbox_path(), 0)); + cl_assert_equal_b(false, git_repository_is_bare(repo)); + + git_repository_free(repo); +} + diff --git a/tests/libgit2/repo/open.c b/tests/libgit2/repo/open.c new file mode 100644 index 000000000..f7ed2c373 --- /dev/null +++ b/tests/libgit2/repo/open.c @@ -0,0 +1,455 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "sysdir.h" +#include + + +void test_repo_open__cleanup(void) +{ + cl_git_sandbox_cleanup(); + + if (git_fs_path_isdir("alternate")) + git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES); +} + +void test_repo_open__bare_empty_repo(void) +{ + git_repository *repo = cl_git_sandbox_init("empty_bare.git"); + + cl_assert(git_repository_path(repo) != NULL); + cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0); + cl_assert(git_repository_workdir(repo) == NULL); +} + +void test_repo_open__format_version_1(void) +{ + git_repository *repo; + git_config *config; + + repo = cl_git_sandbox_init("empty_bare.git"); + + cl_git_pass(git_repository_open(&repo, "empty_bare.git")); + cl_git_pass(git_repository_config(&config, repo)); + + cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 1)); + + git_config_free(config); + git_repository_free(repo); + + cl_git_pass(git_repository_open(&repo, "empty_bare.git")); + cl_assert(git_repository_path(repo) != NULL); + cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0); + git_repository_free(repo); +} + +void test_repo_open__standard_empty_repo_through_gitdir(void) +{ + git_repository *repo; + + cl_git_pass(git_repository_open(&repo, cl_fixture("empty_standard_repo/.gitted"))); + + cl_assert(git_repository_path(repo) != NULL); + cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0); + + cl_assert(git_repository_workdir(repo) != NULL); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "/") == 0); + + git_repository_free(repo); +} + +void test_repo_open__standard_empty_repo_through_workdir(void) +{ + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_assert(git_repository_path(repo) != NULL); + cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0); + + cl_assert(git_repository_workdir(repo) != NULL); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "/") == 0); +} + + +void test_repo_open__open_with_discover(void) +{ + static const char *variants[] = { + "attr", "attr/", "attr/.git", "attr/.git/", + "attr/sub", "attr/sub/", "attr/sub/sub", "attr/sub/sub/", + NULL + }; + git_repository *repo; + const char **scan; + + cl_fixture_sandbox("attr"); + cl_git_pass(p_rename("attr/.gitted", "attr/.git")); + + for (scan = variants; *scan != NULL; scan++) { + cl_git_pass(git_repository_open_ext(&repo, *scan, 0, NULL)); + cl_assert(git__suffixcmp(git_repository_path(repo), "attr/.git/") == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "attr/") == 0); + git_repository_free(repo); + } + + cl_fixture_cleanup("attr"); +} + +void test_repo_open__check_if_repository(void) +{ + cl_git_sandbox_init("empty_standard_repo"); + + /* Pass NULL for the output parameter to check for but not open the repo */ + cl_git_pass(git_repository_open_ext(NULL, "empty_standard_repo", 0, NULL)); + cl_git_fail(git_repository_open_ext(NULL, "repo_does_not_exist", 0, NULL)); + + cl_fixture_cleanup("empty_standard_repo"); +} + +static void make_gitlink_dir(const char *dir, const char *linktext) +{ + git_str path = GIT_STR_INIT; + + cl_git_pass(git_futils_mkdir(dir, 0777, GIT_MKDIR_VERIFY_DIR)); + cl_git_pass(git_str_joinpath(&path, dir, ".git")); + cl_git_rewritefile(path.ptr, linktext); + git_str_dispose(&path); +} + +void test_repo_open__gitlinked(void) +{ + /* need to have both repo dir and workdir set up correctly */ + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_repository *repo2; + + make_gitlink_dir("alternate", "gitdir: ../empty_standard_repo/.git"); + + cl_git_pass(git_repository_open(&repo2, "alternate")); + + cl_assert(git_repository_path(repo2) != NULL); + cl_assert_(git__suffixcmp(git_repository_path(repo2), "empty_standard_repo/.git/") == 0, git_repository_path(repo2)); + cl_assert_equal_s(git_repository_path(repo), git_repository_path(repo2)); + + cl_assert(git_repository_workdir(repo2) != NULL); + cl_assert_(git__suffixcmp(git_repository_workdir(repo2), "alternate/") == 0, git_repository_workdir(repo2)); + + git_repository_free(repo2); +} + +void test_repo_open__with_symlinked_config(void) +{ +#ifndef GIT_WIN32 + git_str path = GIT_STR_INIT; + git_repository *repo; + git_config *cfg; + int32_t value; + + cl_git_sandbox_init("empty_standard_repo"); + + /* Setup .gitconfig as symlink */ + cl_git_pass(git_futils_mkdir_r("home", 0777)); + cl_git_mkfile("home/.gitconfig.linked", "[global]\ntest = 4567\n"); + cl_must_pass(symlink(".gitconfig.linked", "home/.gitconfig")); + cl_git_pass(git_fs_path_prettify(&path, "home", NULL)); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); + + cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_int32(&value, cfg, "global.test")); + cl_assert_equal_i(4567, value); + + git_config_free(cfg); + git_repository_free(repo); + cl_git_pass(git_futils_rmdir_r(git_str_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES)); + cl_sandbox_set_search_path_defaults(); + git_str_dispose(&path); +#endif +} + +void test_repo_open__from_git_new_workdir(void) +{ +#ifndef GIT_WIN32 + /* The git-new-workdir script that ships with git sets up a bunch of + * symlinks to create a second workdir that shares the object db with + * another checkout. Libgit2 can open a repo that has been configured + * this way. + */ + + git_repository *repo2; + git_str link_tgt = GIT_STR_INIT, link = GIT_STR_INIT, body = GIT_STR_INIT; + const char **scan; + int link_fd; + static const char *links[] = { + "config", "refs", "logs/refs", "objects", "info", "hooks", + "packed-refs", "remotes", "rr-cache", "svn", NULL + }; + static const char *copies[] = { + "HEAD", NULL + }; + + cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(p_mkdir("alternate", 0777)); + cl_git_pass(p_mkdir("alternate/.git", 0777)); + + for (scan = links; *scan != NULL; scan++) { + git_str_joinpath(&link_tgt, "empty_standard_repo/.git", *scan); + if (git_fs_path_exists(link_tgt.ptr)) { + git_str_joinpath(&link_tgt, "../../empty_standard_repo/.git", *scan); + git_str_joinpath(&link, "alternate/.git", *scan); + if (strchr(*scan, '/')) + git_futils_mkpath2file(link.ptr, 0777); + cl_assert_(symlink(link_tgt.ptr, link.ptr) == 0, strerror(errno)); + } + } + for (scan = copies; *scan != NULL; scan++) { + git_str_joinpath(&link_tgt, "empty_standard_repo/.git", *scan); + if (git_fs_path_exists(link_tgt.ptr)) { + git_str_joinpath(&link, "alternate/.git", *scan); + cl_git_pass(git_futils_readbuffer(&body, link_tgt.ptr)); + + cl_assert((link_fd = git_futils_creat_withpath(link.ptr, 0777, 0666)) >= 0); + cl_must_pass(p_write(link_fd, body.ptr, body.size)); + p_close(link_fd); + } + } + + git_str_dispose(&link_tgt); + git_str_dispose(&link); + git_str_dispose(&body); + + + cl_git_pass(git_repository_open(&repo2, "alternate")); + + cl_assert(git_repository_path(repo2) != NULL); + cl_assert_(git__suffixcmp(git_repository_path(repo2), "alternate/.git/") == 0, git_repository_path(repo2)); + + cl_assert(git_repository_workdir(repo2) != NULL); + cl_assert_(git__suffixcmp(git_repository_workdir(repo2), "alternate/") == 0, git_repository_workdir(repo2)); + + git_repository_free(repo2); +#else + cl_skip(); +#endif +} + +void test_repo_open__failures(void) +{ + git_repository *base, *repo; + git_str ceiling = GIT_STR_INIT; + + base = cl_git_sandbox_init("attr"); + cl_git_pass(git_str_sets(&ceiling, git_repository_workdir(base))); + + /* fail with no searching */ + cl_git_fail(git_repository_open(&repo, "attr/sub")); + cl_git_fail(git_repository_open_ext( + &repo, "attr/sub", GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)); + + /* fail with ceiling too low */ + cl_git_fail(git_repository_open_ext(&repo, "attr/sub", 0, ceiling.ptr)); + cl_git_pass(git_str_joinpath(&ceiling, ceiling.ptr, "sub")); + cl_git_fail(git_repository_open_ext(&repo, "attr/sub/sub", 0, ceiling.ptr)); + + /* fail with no repo */ + cl_git_pass(p_mkdir("alternate", 0777)); + cl_git_pass(p_mkdir("alternate/.git", 0777)); + cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL)); + cl_git_fail(git_repository_open_ext(&repo, "alternate/.git", 0, NULL)); + + /* fail with no searching and no appending .git */ + cl_git_fail(git_repository_open_ext( + &repo, "attr", + GIT_REPOSITORY_OPEN_NO_SEARCH | GIT_REPOSITORY_OPEN_NO_DOTGIT, + NULL)); + + git_str_dispose(&ceiling); +} + +void test_repo_open__bad_gitlinks(void) +{ + git_repository *repo; + static const char *bad_links[] = { + "garbage\n", "gitdir", "gitdir:\n", "gitdir: foobar", + "gitdir: ../invalid", "gitdir: ../invalid2", + "gitdir: ../attr/.git with extra stuff", + NULL + }; + const char **scan; + + cl_git_sandbox_init("attr"); + + cl_git_pass(p_mkdir("invalid", 0777)); + cl_git_pass(git_futils_mkdir_r("invalid2/.git", 0777)); + + for (scan = bad_links; *scan != NULL; scan++) { + make_gitlink_dir("alternate", *scan); + repo = NULL; + cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL)); + cl_assert(repo == NULL); + } + + git_futils_rmdir_r("invalid", NULL, GIT_RMDIR_REMOVE_FILES); + git_futils_rmdir_r("invalid2", NULL, GIT_RMDIR_REMOVE_FILES); +} + +#ifdef GIT_WIN32 +static void unposix_path(git_str *path) +{ + char *src, *tgt; + + src = tgt = path->ptr; + + /* convert "/d/..." to "d:\..." */ + if (src[0] == '/' && isalpha(src[1]) && src[2] == '/') { + *tgt++ = src[1]; + *tgt++ = ':'; + *tgt++ = '\\'; + src += 3; + } + + while (*src) { + *tgt++ = (*src == '/') ? '\\' : *src; + src++; + } + + *tgt = '\0'; +} +#endif + +void test_repo_open__win32_path(void) +{ +#ifdef GIT_WIN32 + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"), *repo2; + git_str winpath = GIT_STR_INIT; + static const char *repo_path = "empty_standard_repo/.git/"; + static const char *repo_wd = "empty_standard_repo/"; + + cl_assert(git__suffixcmp(git_repository_path(repo), repo_path) == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo), repo_wd) == 0); + + cl_git_pass(git_str_sets(&winpath, git_repository_path(repo))); + unposix_path(&winpath); + cl_git_pass(git_repository_open(&repo2, winpath.ptr)); + cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0); + git_repository_free(repo2); + + cl_git_pass(git_str_sets(&winpath, git_repository_path(repo))); + git_str_truncate(&winpath, winpath.size - 1); /* remove trailing '/' */ + unposix_path(&winpath); + cl_git_pass(git_repository_open(&repo2, winpath.ptr)); + cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0); + git_repository_free(repo2); + + cl_git_pass(git_str_sets(&winpath, git_repository_workdir(repo))); + unposix_path(&winpath); + cl_git_pass(git_repository_open(&repo2, winpath.ptr)); + cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0); + git_repository_free(repo2); + + cl_git_pass(git_str_sets(&winpath, git_repository_workdir(repo))); + git_str_truncate(&winpath, winpath.size - 1); /* remove trailing '/' */ + unposix_path(&winpath); + cl_git_pass(git_repository_open(&repo2, winpath.ptr)); + cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0); + git_repository_free(repo2); + + git_str_dispose(&winpath); +#endif +} + +void test_repo_open__opening_a_non_existing_repository_returns_ENOTFOUND(void) +{ + git_repository *repo; + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_open(&repo, "i-do-not/exist")); +} + +void test_repo_open__no_config(void) +{ + git_str path = GIT_STR_INIT; + git_repository *repo; + git_config *config; + + cl_fixture_sandbox("empty_standard_repo"); + cl_git_pass(cl_rename( + "empty_standard_repo/.gitted", "empty_standard_repo/.git")); + + /* remove local config */ + cl_git_pass(git_futils_rmdir_r( + "empty_standard_repo/.git/config", NULL, GIT_RMDIR_REMOVE_FILES)); + + /* isolate from system level configs */ + cl_must_pass(p_mkdir("alternate", 0777)); + cl_git_pass(git_fs_path_prettify(&path, "alternate", NULL)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); + + git_str_dispose(&path); + + cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + cl_git_pass(git_repository_config(&config, repo)); + + cl_git_pass(git_config_set_string(config, "test.set", "42")); + + git_config_free(config); + git_repository_free(repo); + cl_fixture_cleanup("empty_standard_repo"); + + cl_sandbox_set_search_path_defaults(); +} + +void test_repo_open__force_bare(void) +{ + /* need to have both repo dir and workdir set up correctly */ + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_repository *barerepo; + + make_gitlink_dir("alternate", "gitdir: ../empty_standard_repo/.git"); + + cl_assert(!git_repository_is_bare(repo)); + + cl_git_pass(git_repository_open(&barerepo, "alternate")); + cl_assert(!git_repository_is_bare(barerepo)); + git_repository_free(barerepo); + + cl_git_pass(git_repository_open_bare( + &barerepo, "empty_standard_repo/.git")); + cl_assert(git_repository_is_bare(barerepo)); + git_repository_free(barerepo); + + cl_git_fail(git_repository_open_bare(&barerepo, "alternate/.git")); + + cl_git_pass(git_repository_open_ext( + &barerepo, "alternate/.git", GIT_REPOSITORY_OPEN_BARE, NULL)); + cl_assert(git_repository_is_bare(barerepo)); + git_repository_free(barerepo); + + cl_git_pass(p_mkdir("empty_standard_repo/subdir", 0777)); + cl_git_mkfile("empty_standard_repo/subdir/something.txt", "something"); + + cl_git_fail(git_repository_open_bare( + &barerepo, "empty_standard_repo/subdir")); + + cl_git_pass(git_repository_open_ext( + &barerepo, "empty_standard_repo/subdir", GIT_REPOSITORY_OPEN_BARE, NULL)); + cl_assert(git_repository_is_bare(barerepo)); + git_repository_free(barerepo); + + cl_git_pass(p_mkdir("alternate/subdir", 0777)); + cl_git_pass(p_mkdir("alternate/subdir/sub2", 0777)); + cl_git_mkfile("alternate/subdir/sub2/something.txt", "something"); + + cl_git_fail(git_repository_open_bare(&barerepo, "alternate/subdir/sub2")); + + cl_git_pass(git_repository_open_ext( + &barerepo, "alternate/subdir/sub2", + GIT_REPOSITORY_OPEN_BARE|GIT_REPOSITORY_OPEN_CROSS_FS, NULL)); + cl_assert(git_repository_is_bare(barerepo)); + git_repository_free(barerepo); +} + diff --git a/tests/libgit2/repo/pathspec.c b/tests/libgit2/repo/pathspec.c new file mode 100644 index 000000000..5b86662bc --- /dev/null +++ b/tests/libgit2/repo/pathspec.c @@ -0,0 +1,385 @@ +#include "clar_libgit2.h" +#include "git2/pathspec.h" + +static git_repository *g_repo; + +void test_repo_pathspec__initialize(void) +{ + g_repo = cl_git_sandbox_init("status"); +} + +void test_repo_pathspec__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static char *str0[] = { "*_file", "new_file", "garbage" }; +static char *str1[] = { "*_FILE", "NEW_FILE", "GARBAGE" }; +static char *str2[] = { "staged_*" }; +static char *str3[] = { "!subdir", "*_file", "new_file" }; +static char *str4[] = { "*" }; +static char *str5[] = { "S*" }; + +void test_repo_pathspec__workdir0(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "*_file", "new_file", "garbage" } */ + s.strings = str0; s.count = ARRAY_SIZE(str0); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 0)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_FIND_FAILURES | GIT_PATHSPEC_FAILURES_ONLY, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__workdir1(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "*_FILE", "NEW_FILE", "GARBAGE" } */ + s.strings = str1; s.count = ARRAY_SIZE(str1); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_IGNORE_CASE, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_USE_CASE, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_fail(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_NO_MATCH_ERROR, ps)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(3, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__workdir2(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "staged_*" } */ + s.strings = str2; s.count = ARRAY_SIZE(str2); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); + cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_fail(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_NO_GLOB | GIT_PATHSPEC_NO_MATCH_ERROR, ps)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_NO_GLOB | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__workdir3(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "!subdir", "*_file", "new_file" } */ + s.strings = str3; s.count = ARRAY_SIZE(str3); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); + cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); + cl_assert_equal_s("new_file", git_pathspec_match_list_entry(m, 2)); + cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 3)); + cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_s("staged_new_file", git_pathspec_match_list_entry(m, 5)); + cl_assert_equal_s("staged_new_file_modified_file", git_pathspec_match_list_entry(m, 6)); + cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 7)); + + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__workdir4(void) +{ + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "*" } */ + s.strings = str4; s.count = ARRAY_SIZE(str4); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); + cl_assert_equal_sz(13, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("\xE8\xBF\x99", git_pathspec_match_list_entry(m, 12)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); +} + + +void test_repo_pathspec__index0(void) +{ + git_index *idx; + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + cl_git_pass(git_repository_index(&idx, g_repo)); + + /* { "*_file", "new_file", "garbage" } */ + s.strings = str0; s.count = ARRAY_SIZE(str0); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_index(&m, idx, 0, ps)); + cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); + cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2)); + cl_assert_equal_s("staged_new_file", git_pathspec_match_list_entry(m, 3)); + cl_assert_equal_s("staged_new_file_deleted_file", git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_s("staged_new_file_modified_file", git_pathspec_match_list_entry(m, 5)); + cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 6)); + cl_assert_equal_s("subdir/deleted_file", git_pathspec_match_list_entry(m, 7)); + cl_assert_equal_s("subdir/modified_file", git_pathspec_match_list_entry(m, 8)); + cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 9)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_index(&m, idx, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0)); + cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1)); + cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); + git_index_free(idx); +} + +void test_repo_pathspec__index1(void) +{ + /* Currently the USE_CASE and IGNORE_CASE flags don't work on the + * index because the index sort order for the index iterator is + * set by the index itself. I think the correct fix is for the + * index not to embed a global sort order but to support traversal + * in either case sensitive or insensitive order in a stateless + * manner. + * + * Anyhow, as it is, there is no point in doing this test. + */ +#if 0 + git_index *idx; + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + cl_git_pass(git_repository_index(&idx, g_repo)); + + /* { "*_FILE", "NEW_FILE", "GARBAGE" } */ + s.strings = str1; s.count = ARRAY_SIZE(str1); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_pathspec_match_index(&m, idx, + GIT_PATHSPEC_USE_CASE, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_index(&m, idx, + GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(3, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_index(&m, idx, + GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_pathspec_free(ps); + git_index_free(idx); +#endif +} + +void test_repo_pathspec__tree0(void) +{ + git_object *tree; + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "*_file", "new_file", "garbage" } */ + s.strings = str0; s.count = ARRAY_SIZE(str0); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD~2^{tree}")); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(4, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); + cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2)); + cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 3)); + cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0)); + cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1)); + cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2)); + git_pathspec_match_list_free(m); + + git_object_free(tree); + + cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); + cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2)); + cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 3)); + cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_s("subdir/deleted_file", git_pathspec_match_list_entry(m, 5)); + cl_assert_equal_s("subdir/modified_file", git_pathspec_match_list_entry(m, 6)); + cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 7)); + cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); + cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0)); + cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1)); + cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2)); + git_pathspec_match_list_free(m); + + git_object_free(tree); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__tree5(void) +{ + git_object *tree; + git_strarray s; + git_pathspec *ps; + git_pathspec_match_list *m; + + /* { "S*" } */ + s.strings = str5; s.count = ARRAY_SIZE(str5); + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD~2^{tree}")); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("staged_changes", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 4)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_object_free(tree); + + cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, + GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); + cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m)); + cl_assert_equal_s("staged_changes", git_pathspec_match_list_entry(m, 0)); + cl_assert_equal_s("subdir.txt", git_pathspec_match_list_entry(m, 5)); + cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 6)); + cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); + git_pathspec_match_list_free(m); + + git_object_free(tree); + + git_pathspec_free(ps); +} + +void test_repo_pathspec__in_memory(void) +{ + static char *strings[] = { "one", "two*", "!three*", "*four" }; + git_strarray s = { strings, ARRAY_SIZE(strings) }; + git_pathspec *ps; + + cl_git_pass(git_pathspec_new(&ps, &s)); + + cl_assert(git_pathspec_matches_path(ps, 0, "one")); + cl_assert(!git_pathspec_matches_path(ps, 0, "ONE")); + cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_IGNORE_CASE, "ONE")); + cl_assert(git_pathspec_matches_path(ps, 0, "two")); + cl_assert(git_pathspec_matches_path(ps, 0, "two.txt")); + cl_assert(!git_pathspec_matches_path(ps, 0, "three.txt")); + cl_assert(git_pathspec_matches_path(ps, 0, "anything.four")); + cl_assert(!git_pathspec_matches_path(ps, 0, "three.four")); + cl_assert(!git_pathspec_matches_path(ps, 0, "nomatch")); + cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "two")); + cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "two*")); + cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "anyfour")); + cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "*four")); + + git_pathspec_free(ps); +} diff --git a/tests/libgit2/repo/repo_helpers.c b/tests/libgit2/repo/repo_helpers.c new file mode 100644 index 000000000..1efde70a5 --- /dev/null +++ b/tests/libgit2/repo/repo_helpers.c @@ -0,0 +1,37 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "repo_helpers.h" +#include "posix.h" + +void make_head_unborn(git_repository* repo, const char *target) +{ + git_reference *head; + + cl_git_pass(git_reference_symbolic_create(&head, repo, GIT_HEAD_FILE, target, 1, NULL)); + git_reference_free(head); +} + +void delete_head(git_repository* repo) +{ + git_str head_path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&head_path, git_repository_path(repo), GIT_HEAD_FILE)); + cl_git_pass(p_unlink(git_str_cstr(&head_path))); + + git_str_dispose(&head_path); +} + +void create_tmp_global_config(const char *dirname, const char *key, const char *val) +{ + git_str path = GIT_STR_INIT; + git_config *config; + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, + GIT_CONFIG_LEVEL_GLOBAL, dirname)); + cl_must_pass(p_mkdir(dirname, 0777)); + cl_git_pass(git_str_joinpath(&path, dirname, ".gitconfig")); + cl_git_pass(git_config_open_ondisk(&config, path.ptr)); + cl_git_pass(git_config_set_string(config, key, val)); + git_config_free(config); + git_str_dispose(&path); +} diff --git a/tests/libgit2/repo/repo_helpers.h b/tests/libgit2/repo/repo_helpers.h new file mode 100644 index 000000000..a93bf36ae --- /dev/null +++ b/tests/libgit2/repo/repo_helpers.h @@ -0,0 +1,7 @@ +#include "common.h" + +#define NON_EXISTING_HEAD "refs/heads/hide/and/seek" + +extern void make_head_unborn(git_repository* repo, const char *target); +extern void delete_head(git_repository* repo); +extern void create_tmp_global_config(const char *path, const char *key, const char *val); diff --git a/tests/libgit2/repo/reservedname.c b/tests/libgit2/repo/reservedname.c new file mode 100644 index 000000000..245d8625a --- /dev/null +++ b/tests/libgit2/repo/reservedname.c @@ -0,0 +1,132 @@ +#include "clar_libgit2.h" +#include "../submodule/submodule_helpers.h" +#include "repository.h" + +void test_repo_reservedname__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_repo_reservedname__includes_shortname_on_win32(void) +{ + git_repository *repo; + git_str *reserved; + size_t reserved_len; + + repo = cl_git_sandbox_init("nasty"); + cl_assert(git_repository__reserved_names(&reserved, &reserved_len, repo, false)); + +#ifdef GIT_WIN32 + cl_assert_equal_i(2, reserved_len); + cl_assert_equal_s(".git", reserved[0].ptr); + cl_assert_equal_s("GIT~1", reserved[1].ptr); +#else + cl_assert_equal_i(1, reserved_len); + cl_assert_equal_s(".git", reserved[0].ptr); +#endif +} + +void test_repo_reservedname__includes_shortname_when_requested(void) +{ + git_repository *repo; + git_str *reserved; + size_t reserved_len; + + repo = cl_git_sandbox_init("nasty"); + cl_assert(git_repository__reserved_names(&reserved, &reserved_len, repo, true)); + + cl_assert_equal_i(2, reserved_len); + cl_assert_equal_s(".git", reserved[0].ptr); + cl_assert_equal_s("GIT~1", reserved[1].ptr); +} + +/* Ensures that custom shortnames are included: creates a GIT~1 so that the + * .git folder itself will have to be named GIT~2 + */ +void test_repo_reservedname__custom_shortname_recognized(void) +{ +#ifdef GIT_WIN32 + git_repository *repo; + git_str *reserved; + size_t reserved_len; + + if (!cl_sandbox_supports_8dot3()) + clar__skip(); + + repo = cl_git_sandbox_init("nasty"); + + cl_must_pass(p_rename("nasty/.git", "nasty/_temp")); + cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666); + cl_must_pass(p_rename("nasty/_temp", "nasty/.git")); + + cl_assert(git_repository__reserved_names(&reserved, &reserved_len, repo, true)); + + cl_assert_equal_i(3, reserved_len); + cl_assert_equal_s(".git", reserved[0].ptr); + cl_assert_equal_s("GIT~1", reserved[1].ptr); + cl_assert_equal_s("GIT~2", reserved[2].ptr); +#endif +} + +/* When looking at the short name for a submodule, we need to prevent + * people from overwriting the `.git` file in the submodule working + * directory itself. We don't want to look at the actual repository + * path, since it will be in the super's repository above us, and + * typically named with the name of our subrepository. Consequently, + * preventing access to the short name of the actual repository path + * would prevent us from creating files with the same name as the + * subrepo. (Eg, a submodule named "libgit2" could not contain a file + * named "libgit2", which would be unfortunate.) + */ +void test_repo_reservedname__submodule_pointer(void) +{ +#ifdef GIT_WIN32 + git_repository *super_repo, *sub_repo; + git_submodule *sub; + git_str *sub_reserved; + size_t sub_reserved_len; + + if (!cl_sandbox_supports_8dot3()) + clar__skip(); + + super_repo = setup_fixture_submod2(); + + assert_submodule_exists(super_repo, "sm_unchanged"); + + cl_git_pass(git_submodule_lookup(&sub, super_repo, "sm_unchanged")); + cl_git_pass(git_submodule_open(&sub_repo, sub)); + + cl_assert(git_repository__reserved_names(&sub_reserved, &sub_reserved_len, sub_repo, true)); + + cl_assert_equal_i(2, sub_reserved_len); + cl_assert_equal_s(".git", sub_reserved[0].ptr); + cl_assert_equal_s("GIT~1", sub_reserved[1].ptr); + + git_submodule_free(sub); + git_repository_free(sub_repo); +#endif +} + +/* Like the `submodule_pointer` test (above), this ensures that we do not + * follow the gitlink to the submodule's repository location and treat that + * as a reserved name. This tests at an initial submodule update, where the + * submodule repo is being created. + */ +void test_repo_reservedname__submodule_pointer_during_create(void) +{ + git_repository *repo; + git_submodule *sm; + git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + git_str url = GIT_STR_INIT; + + repo = setup_fixture_super(); + + cl_git_pass(git_str_joinpath(&url, clar_sandbox_path(), "sub.git")); + cl_repo_set_string(repo, "submodule.sub.url", url.ptr); + + cl_git_pass(git_submodule_lookup(&sm, repo, "sub")); + cl_git_pass(git_submodule_update(sm, 1, &update_options)); + + git_submodule_free(sm); + git_str_dispose(&url); +} diff --git a/tests/libgit2/repo/setters.c b/tests/libgit2/repo/setters.c new file mode 100644 index 000000000..9a965dec6 --- /dev/null +++ b/tests/libgit2/repo/setters.c @@ -0,0 +1,108 @@ +#include "clar_libgit2.h" +#include "git2/sys/repository.h" + +#include "posix.h" +#include "util.h" +#include "path.h" +#include "futils.h" + +static git_repository *repo; + +void test_repo_setters__initialize(void) +{ + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(git_repository_open(&repo, "testrepo.git")); + cl_must_pass(p_mkdir("new_workdir", 0777)); +} + +void test_repo_setters__cleanup(void) +{ + git_repository_free(repo); + repo = NULL; + + cl_fixture_cleanup("testrepo.git"); + cl_fixture_cleanup("new_workdir"); +} + +void test_repo_setters__setting_a_workdir_turns_a_bare_repository_into_a_standard_one(void) +{ + cl_assert(git_repository_is_bare(repo) == 1); + + cl_assert(git_repository_workdir(repo) == NULL); + cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", false)); + + cl_assert(git_repository_workdir(repo) != NULL); + cl_assert(git_repository_is_bare(repo) == 0); +} + +void test_repo_setters__setting_a_workdir_prettifies_its_path(void) +{ + cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", false)); + + cl_assert(git__suffixcmp(git_repository_workdir(repo), "new_workdir/") == 0); +} + +void test_repo_setters__setting_a_workdir_creates_a_gitlink(void) +{ + git_config *cfg; + git_buf buf = GIT_BUF_INIT; + git_str content = GIT_STR_INIT; + + cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", true)); + + cl_assert(git_fs_path_isfile("./new_workdir/.git")); + + cl_git_pass(git_futils_readbuffer(&content, "./new_workdir/.git")); + cl_assert(git__prefixcmp(git_str_cstr(&content), "gitdir: ") == 0); + cl_assert(git__suffixcmp(git_str_cstr(&content), "testrepo.git/") == 0); + git_str_dispose(&content); + + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.worktree")); + cl_assert(git__suffixcmp(buf.ptr, "new_workdir/") == 0); + + git_buf_dispose(&buf); + git_config_free(cfg); +} + +void test_repo_setters__setting_a_new_index_on_a_repo_which_has_already_loaded_one_properly_honors_the_refcount(void) +{ + git_index *new_index; + + cl_git_pass(git_index_open(&new_index, "./my-index")); + cl_assert(((git_refcount *)new_index)->refcount.val == 1); + + git_repository_set_index(repo, new_index); + cl_assert(((git_refcount *)new_index)->refcount.val == 2); + + git_repository_free(repo); + cl_assert(((git_refcount *)new_index)->refcount.val == 1); + + git_index_free(new_index); + + /* + * Ensure the cleanup method won't try to free the repo as it's already been taken care of + */ + repo = NULL; +} + +void test_repo_setters__setting_a_new_odb_on_a_repo_which_already_loaded_one_properly_honors_the_refcount(void) +{ + git_odb *new_odb; + + cl_git_pass(git_odb_open(&new_odb, "./testrepo.git/objects")); + cl_assert(((git_refcount *)new_odb)->refcount.val == 1); + + git_repository_set_odb(repo, new_odb); + cl_assert(((git_refcount *)new_odb)->refcount.val == 2); + + git_repository_free(repo); + cl_assert(((git_refcount *)new_odb)->refcount.val == 1); + + git_odb_free(new_odb); + + /* + * Ensure the cleanup method won't try to free the repo as it's already been taken care of + */ + repo = NULL; +} diff --git a/tests/libgit2/repo/shallow.c b/tests/libgit2/repo/shallow.c new file mode 100644 index 000000000..adb7a9e44 --- /dev/null +++ b/tests/libgit2/repo/shallow.c @@ -0,0 +1,39 @@ +#include "clar_libgit2.h" +#include "futils.h" + +static git_repository *g_repo; + +void test_repo_shallow__initialize(void) +{ +} + +void test_repo_shallow__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_repo_shallow__no_shallow_file(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_assert_equal_i(0, git_repository_is_shallow(g_repo)); +} + +void test_repo_shallow__empty_shallow_file(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_git_mkfile("testrepo.git/shallow", ""); + cl_assert_equal_i(0, git_repository_is_shallow(g_repo)); +} + +void test_repo_shallow__shallow_repo(void) +{ + g_repo = cl_git_sandbox_init("shallow.git"); + cl_assert_equal_i(1, git_repository_is_shallow(g_repo)); +} + +void test_repo_shallow__clears_errors(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_assert_equal_i(0, git_repository_is_shallow(g_repo)); + cl_assert_equal_p(NULL, git_error_last()); +} diff --git a/tests/libgit2/repo/state.c b/tests/libgit2/repo/state.c new file mode 100644 index 000000000..92b272dce --- /dev/null +++ b/tests/libgit2/repo/state.c @@ -0,0 +1,131 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "posix.h" +#include "futils.h" + +static git_repository *_repo; +static git_str _path; + +void test_repo_state__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_repo_state__cleanup(void) +{ + cl_git_sandbox_cleanup(); + git_str_dispose(&_path); +} + +static void setup_simple_state(const char *filename) +{ + cl_git_pass(git_str_joinpath(&_path, git_repository_path(_repo), filename)); + git_futils_mkpath2file(git_str_cstr(&_path), 0777); + cl_git_mkfile(git_str_cstr(&_path), "dummy"); +} + +static void assert_repo_state(git_repository_state_t state) +{ + cl_assert_equal_i(state, git_repository_state(_repo)); +} + +void test_repo_state__none_with_HEAD_attached(void) +{ + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__none_with_HEAD_detached(void) +{ + cl_git_pass(git_repository_detach_head(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__merge(void) +{ + setup_simple_state(GIT_MERGE_HEAD_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_MERGE); + cl_git_pass(git_repository_state_cleanup(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__revert(void) +{ + setup_simple_state(GIT_REVERT_HEAD_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_REVERT); + cl_git_pass(git_repository_state_cleanup(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__revert_sequence(void) +{ + setup_simple_state(GIT_REVERT_HEAD_FILE); + setup_simple_state(GIT_SEQUENCER_TODO_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_REVERT_SEQUENCE); + cl_git_pass(git_repository_state_cleanup(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__cherry_pick(void) +{ + setup_simple_state(GIT_CHERRYPICK_HEAD_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_CHERRYPICK); + cl_git_pass(git_repository_state_cleanup(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__cherrypick_sequence(void) +{ + setup_simple_state(GIT_CHERRYPICK_HEAD_FILE); + setup_simple_state(GIT_SEQUENCER_TODO_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE); + cl_git_pass(git_repository_state_cleanup(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__bisect(void) +{ + setup_simple_state(GIT_BISECT_LOG_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_BISECT); + cl_git_pass(git_repository_state_cleanup(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__rebase_interactive(void) +{ + setup_simple_state(GIT_REBASE_MERGE_INTERACTIVE_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_REBASE_INTERACTIVE); + cl_git_pass(git_repository_state_cleanup(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__rebase_merge(void) +{ + setup_simple_state(GIT_REBASE_MERGE_DIR "whatever"); + assert_repo_state(GIT_REPOSITORY_STATE_REBASE_MERGE); + cl_git_pass(git_repository_state_cleanup(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__rebase(void) +{ + setup_simple_state(GIT_REBASE_APPLY_REBASING_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_REBASE); + cl_git_pass(git_repository_state_cleanup(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__apply_mailbox(void) +{ + setup_simple_state(GIT_REBASE_APPLY_APPLYING_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_APPLY_MAILBOX); + cl_git_pass(git_repository_state_cleanup(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__apply_mailbox_or_rebase(void) +{ + setup_simple_state(GIT_REBASE_APPLY_DIR "whatever"); + assert_repo_state(GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE); + cl_git_pass(git_repository_state_cleanup(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} diff --git a/tests/libgit2/repo/template.c b/tests/libgit2/repo/template.c new file mode 100644 index 000000000..e8fe266cf --- /dev/null +++ b/tests/libgit2/repo/template.c @@ -0,0 +1,305 @@ +#include "clar_libgit2.h" + +#include "futils.h" +#include "repo/repo_helpers.h" + +#define CLEAR_FOR_CORE_FILEMODE(M) ((M) &= ~0177) + +static git_repository *_repo = NULL; +static mode_t g_umask = 0; +static git_str _global_path = GIT_STR_INIT; + +static const char *fixture_repo; +static const char *fixture_templates; + +void test_repo_template__initialize(void) +{ + _repo = NULL; + + /* load umask if not already loaded */ + if (!g_umask) { + g_umask = p_umask(022); + (void)p_umask(g_umask); + } +} + +void test_repo_template__cleanup(void) +{ + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, + _global_path.ptr); + git_str_dispose(&_global_path); + + cl_fixture_cleanup("tmp_global_path"); + + if (fixture_repo) { + cl_fixture_cleanup(fixture_repo); + fixture_repo = NULL; + } + + if (fixture_templates) { + cl_fixture_cleanup(fixture_templates); + fixture_templates = NULL; + } + + git_repository_free(_repo); + _repo = NULL; +} + +static void assert_hooks_match( + const char *template_dir, + const char *repo_dir, + const char *hook_path, + bool core_filemode) +{ + git_str expected = GIT_STR_INIT; + git_str actual = GIT_STR_INIT; + struct stat expected_st, st; + + cl_git_pass(git_str_joinpath(&expected, template_dir, hook_path)); + cl_git_pass(git_fs_path_lstat(expected.ptr, &expected_st)); + + cl_git_pass(git_str_joinpath(&actual, repo_dir, hook_path)); + cl_git_pass(git_fs_path_lstat(actual.ptr, &st)); + + cl_assert(expected_st.st_size == st.st_size); + + if (GIT_MODE_TYPE(expected_st.st_mode) != GIT_FILEMODE_LINK) { + mode_t expected_mode = + GIT_MODE_TYPE(expected_st.st_mode) | + (GIT_PERMS_FOR_WRITE(expected_st.st_mode) & ~g_umask); + + if (!core_filemode) { + CLEAR_FOR_CORE_FILEMODE(expected_mode); + CLEAR_FOR_CORE_FILEMODE(st.st_mode); + } + + cl_assert_equal_i_fmt(expected_mode, st.st_mode, "%07o"); + } + + git_str_dispose(&expected); + git_str_dispose(&actual); +} + +static void assert_mode_seems_okay( + const char *base, const char *path, + git_filemode_t expect_mode, bool expect_setgid, bool core_filemode) +{ + git_str full = GIT_STR_INIT; + struct stat st; + + cl_git_pass(git_str_joinpath(&full, base, path)); + cl_git_pass(git_fs_path_lstat(full.ptr, &st)); + git_str_dispose(&full); + + if (!core_filemode) { + CLEAR_FOR_CORE_FILEMODE(expect_mode); + CLEAR_FOR_CORE_FILEMODE(st.st_mode); + expect_setgid = false; + } + + if (S_ISGID != 0) + cl_assert_equal_b(expect_setgid, (st.st_mode & S_ISGID) != 0); + + cl_assert_equal_b( + GIT_PERMS_IS_EXEC(expect_mode), GIT_PERMS_IS_EXEC(st.st_mode)); + + cl_assert_equal_i_fmt( + GIT_MODE_TYPE(expect_mode), GIT_MODE_TYPE(st.st_mode), "%07o"); +} + +static void setup_repo(const char *name, git_repository_init_options *opts) +{ + cl_git_pass(git_repository_init_ext(&_repo, name, opts)); + fixture_repo = name; +} + +static void setup_templates(const char *name, bool setup_globally) +{ + git_str path = GIT_STR_INIT; + + cl_fixture_sandbox("template"); + if (strcmp(name, "template")) + cl_must_pass(p_rename("template", name)); + + fixture_templates = name; + + /* + * Create a symlink from link.sample to update.sample if the filesystem + * supports it. + */ + cl_git_pass(git_str_join3(&path, '/', name, "hooks", "link.sample")); +#ifdef GIT_WIN32 + cl_git_mkfile(path.ptr, "#!/bin/sh\necho hello, world\n"); +#else + cl_must_pass(p_symlink("update.sample", path.ptr)); +#endif + + git_str_clear(&path); + + /* Create a file starting with a dot */ + cl_git_pass(git_str_join3(&path, '/', name, "hooks", ".dotfile")); + cl_git_mkfile(path.ptr, "something\n"); + + git_str_clear(&path); + + if (setup_globally) { + cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), name)); + create_tmp_global_config("tmp_global_path", "init.templatedir", path.ptr); + } + + git_str_dispose(&path); +} + +static void validate_templates(git_repository *repo, const char *template_path) +{ + git_str path = GIT_STR_INIT, expected = GIT_STR_INIT, actual = GIT_STR_INIT; + int filemode; + + cl_git_pass(git_str_joinpath(&path, template_path, "description")); + cl_git_pass(git_futils_readbuffer(&expected, path.ptr)); + + git_str_clear(&path); + + cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "description")); + cl_git_pass(git_futils_readbuffer(&actual, path.ptr)); + + cl_assert_equal_s(expected.ptr, actual.ptr); + + filemode = cl_repo_get_bool(repo, "core.filemode"); + + assert_hooks_match( + template_path, git_repository_path(repo), + "hooks/update.sample", filemode); + assert_hooks_match( + template_path, git_repository_path(repo), + "hooks/link.sample", filemode); + assert_hooks_match( + template_path, git_repository_path(repo), + "hooks/.dotfile", filemode); + + git_str_dispose(&expected); + git_str_dispose(&actual); + git_str_dispose(&path); +} + +void test_repo_template__external_templates_specified_in_options(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + opts.template_path = "template"; + + setup_templates("template", false); + setup_repo("templated.git", &opts); + + validate_templates(_repo, "template"); +} + +void test_repo_template__external_templates_specified_in_config(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + + setup_templates("template", true); + setup_repo("templated.git", &opts); + + validate_templates(_repo, "template"); +} + +void test_repo_template__external_templates_with_leading_dot(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + + setup_templates(".template_with_leading_dot", true); + setup_repo("templated.git", &opts); + + validate_templates(_repo, ".template_with_leading_dot"); +} + +void test_repo_template__extended_with_template_and_shared_mode(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + const char *repo_path; + int filemode; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + opts.template_path = "template"; + opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP; + + setup_templates("template", false); + setup_repo("init_shared_from_tpl", &opts); + + filemode = cl_repo_get_bool(_repo, "core.filemode"); + + repo_path = git_repository_path(_repo); + assert_mode_seems_okay(repo_path, "hooks", + GIT_FILEMODE_TREE | GIT_REPOSITORY_INIT_SHARED_GROUP, true, filemode); + assert_mode_seems_okay(repo_path, "info", + GIT_FILEMODE_TREE | GIT_REPOSITORY_INIT_SHARED_GROUP, true, filemode); + assert_mode_seems_okay(repo_path, "description", + GIT_FILEMODE_BLOB, false, filemode); + + validate_templates(_repo, "template"); +} + +void test_repo_template__templated_head_is_used(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_str head = GIT_STR_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + + setup_templates("template", true); + cl_git_mkfile("template/HEAD", "foobar\n"); + setup_repo("repo", &opts); + + cl_git_pass(git_futils_readbuffer(&head, "repo/.git/HEAD")); + cl_assert_equal_s("foobar\n", head.ptr); + + git_str_dispose(&head); +} + +void test_repo_template__initial_head_option_overrides_template_head(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_str head = GIT_STR_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + opts.initial_head = "manual"; + + setup_templates("template", true); + cl_git_mkfile("template/HEAD", "foobar\n"); + setup_repo("repo", &opts); + + cl_git_pass(git_futils_readbuffer(&head, "repo/.git/HEAD")); + cl_assert_equal_s("ref: refs/heads/manual\n", head.ptr); + + git_str_dispose(&head); +} + +void test_repo_template__empty_template_path(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + opts.template_path = ""; + + setup_repo("foo", &opts); +} + +void test_repo_template__nonexistent_template_path(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + opts.template_path = "/tmp/path/that/does/not/exist/for/libgit2/test"; + + setup_repo("bar", &opts); +} diff --git a/tests/libgit2/reset/default.c b/tests/libgit2/reset/default.c new file mode 100644 index 000000000..c76f14813 --- /dev/null +++ b/tests/libgit2/reset/default.c @@ -0,0 +1,212 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "reset_helpers.h" +#include "path.h" + +static git_repository *_repo; +static git_object *_target; +static git_strarray _pathspecs; +static git_index *_index; + +static void initialize(const char *repo_name) +{ + _repo = cl_git_sandbox_init(repo_name); + cl_git_pass(git_repository_index(&_index, _repo)); + + _target = NULL; + + _pathspecs.strings = NULL; + _pathspecs.count = 0; +} + +void test_reset_default__initialize(void) +{ +} + +void test_reset_default__cleanup(void) +{ + git_object_free(_target); + _target = NULL; + + git_index_free(_index); + _index = NULL; + + cl_git_sandbox_cleanup(); +} + +static void assert_content_in_index( + git_strarray *pathspecs, + bool should_exist, + git_strarray *expected_shas) +{ + size_t i, pos; + int error; + + for (i = 0; i < pathspecs->count; i++) { + error = git_index_find(&pos, _index, pathspecs->strings[i]); + + if (should_exist) { + const git_index_entry *entry; + + cl_assert(error != GIT_ENOTFOUND); + + entry = git_index_get_byindex(_index, pos); + cl_assert(entry != NULL); + + if (!expected_shas) + continue; + + cl_git_pass(git_oid_streq(&entry->id, expected_shas->strings[i])); + } else + cl_assert_equal_i(should_exist, error != GIT_ENOTFOUND); + } +} + +void test_reset_default__resetting_filepaths_against_a_null_target_removes_them_from_the_index(void) +{ + char *paths[] = { "staged_changes", "staged_new_file" }; + + initialize("status"); + + _pathspecs.strings = paths; + _pathspecs.count = 2; + + assert_content_in_index(&_pathspecs, true, NULL); + + cl_git_pass(git_reset_default(_repo, NULL, &_pathspecs)); + + assert_content_in_index(&_pathspecs, false, NULL); +} + +/* + * $ git ls-files --cached -s --abbrev=7 -- "staged*" + * 100644 55d316c 0 staged_changes + * 100644 a6be623 0 staged_changes_file_deleted + * ... + * + * $ git reset 0017bd4 -- staged_changes staged_changes_file_deleted + * Unstaged changes after reset: + * ... + * + * $ git ls-files --cached -s --abbrev=7 -- "staged*" + * 100644 32504b7 0 staged_changes + * 100644 061d42a 0 staged_changes_file_deleted + * ... + */ +void test_reset_default__resetting_filepaths_replaces_their_corresponding_index_entries(void) +{ + git_strarray before, after; + + char *paths[] = { "staged_changes", "staged_changes_file_deleted" }; + char *before_shas[] = { "55d316c9ba708999f1918e9677d01dfcae69c6b9", + "a6be623522ce87a1d862128ac42672604f7b468b" }; + char *after_shas[] = { "32504b727382542f9f089e24fddac5e78533e96c", + "061d42a44cacde5726057b67558821d95db96f19" }; + + initialize("status"); + + _pathspecs.strings = paths; + _pathspecs.count = 2; + before.strings = before_shas; + before.count = 2; + after.strings = after_shas; + after.count = 2; + + cl_git_pass(git_revparse_single(&_target, _repo, "0017bd4")); + assert_content_in_index(&_pathspecs, true, &before); + + cl_git_pass(git_reset_default(_repo, _target, &_pathspecs)); + + assert_content_in_index(&_pathspecs, true, &after); +} + +/* + * $ git ls-files --cached -s --abbrev=7 -- conflicts-one.txt + * 100644 1f85ca5 1 conflicts-one.txt + * 100644 6aea5f2 2 conflicts-one.txt + * 100644 516bd85 3 conflicts-one.txt + * + * $ git reset 9a05ccb -- conflicts-one.txt + * Unstaged changes after reset: + * ... + * + * $ git ls-files --cached -s --abbrev=7 -- conflicts-one.txt + * 100644 1f85ca5 0 conflicts-one.txt + * + */ +void test_reset_default__resetting_filepaths_clears_previous_conflicts(void) +{ + const git_index_entry *conflict_entry[3]; + git_strarray after; + + char *paths[] = { "conflicts-one.txt" }; + char *after_shas[] = { "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81" }; + + initialize("mergedrepo"); + + _pathspecs.strings = paths; + _pathspecs.count = 1; + after.strings = after_shas; + after.count = 1; + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], _index, "conflicts-one.txt")); + + cl_git_pass(git_revparse_single(&_target, _repo, "9a05ccb")); + cl_git_pass(git_reset_default(_repo, _target, &_pathspecs)); + + assert_content_in_index(&_pathspecs, true, &after); + + cl_assert_equal_i(GIT_ENOTFOUND, git_index_conflict_get(&conflict_entry[0], + &conflict_entry[1], &conflict_entry[2], _index, "conflicts-one.txt")); +} + +/* +$ git reset HEAD -- "I_am_not_there.txt" "me_neither.txt" +Unstaged changes after reset: +... +*/ +void test_reset_default__resetting_unknown_filepaths_does_not_fail(void) +{ + char *paths[] = { "I_am_not_there.txt", "me_neither.txt" }; + + initialize("status"); + + _pathspecs.strings = paths; + _pathspecs.count = 2; + + assert_content_in_index(&_pathspecs, false, NULL); + + cl_git_pass(git_revparse_single(&_target, _repo, "HEAD")); + cl_git_pass(git_reset_default(_repo, _target, &_pathspecs)); + + assert_content_in_index(&_pathspecs, false, NULL); +} + +void test_reset_default__staged_rename_reset_delete(void) +{ + git_index_entry entry; + const git_index_entry *existing; + char *paths[] = { "new.txt" }; + + initialize("testrepo2"); + + existing = git_index_get_bypath(_index, "new.txt", 0); + cl_assert(existing); + memcpy(&entry, existing, sizeof(entry)); + + cl_git_pass(git_index_remove_bypath(_index, "new.txt")); + + entry.path = "renamed.txt"; + cl_git_pass(git_index_add(_index, &entry)); + + _pathspecs.strings = paths; + _pathspecs.count = 1; + + assert_content_in_index(&_pathspecs, false, NULL); + + cl_git_pass(git_revparse_single(&_target, _repo, "HEAD")); + cl_git_pass(git_reset_default(_repo, _target, &_pathspecs)); + + assert_content_in_index(&_pathspecs, true, NULL); +} diff --git a/tests/libgit2/reset/hard.c b/tests/libgit2/reset/hard.c new file mode 100644 index 000000000..9d177c021 --- /dev/null +++ b/tests/libgit2/reset/hard.c @@ -0,0 +1,293 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "reset_helpers.h" +#include "path.h" +#include "futils.h" + +static git_repository *repo; +static git_object *target; + +void test_reset_hard__initialize(void) +{ + repo = cl_git_sandbox_init("status"); + target = NULL; +} + +void test_reset_hard__cleanup(void) +{ + if (target != NULL) { + git_object_free(target); + target = NULL; + } + + cl_git_sandbox_cleanup(); +} + +static int strequal_ignore_eol(const char *exp, const char *str) +{ + while (*exp && *str) { + if (*exp != *str) { + while (*exp == '\r' || *exp == '\n') ++exp; + while (*str == '\r' || *str == '\n') ++str; + if (*exp != *str) + return false; + } else { + exp++; str++; + } + } + return (!*exp && !*str); +} + +void test_reset_hard__resetting_reverts_modified_files(void) +{ + git_str path = GIT_STR_INIT, content = GIT_STR_INIT; + int i; + static const char *files[4] = { + "current_file", + "modified_file", + "staged_new_file", + "staged_changes_modified_file" }; + static const char *before[4] = { + "current_file\n", + "modified_file\nmodified_file\n", + "staged_new_file\n", + "staged_changes_modified_file\nstaged_changes_modified_file\nstaged_changes_modified_file\n" + }; + static const char *after[4] = { + "current_file\n", + "modified_file\n", + NULL, + "staged_changes_modified_file\n" + }; + const char *wd = git_repository_workdir(repo); + + cl_assert(wd); + + for (i = 0; i < 4; ++i) { + cl_git_pass(git_str_joinpath(&path, wd, files[i])); + cl_git_pass(git_futils_readbuffer(&content, path.ptr)); + cl_assert_equal_s(before[i], content.ptr); + } + + cl_git_pass(git_revparse_single(&target, repo, "26a125e")); + + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); + + for (i = 0; i < 4; ++i) { + cl_git_pass(git_str_joinpath(&path, wd, files[i])); + if (after[i]) { + cl_git_pass(git_futils_readbuffer(&content, path.ptr)); + cl_assert(strequal_ignore_eol(after[i], content.ptr)); + } else { + cl_assert(!git_fs_path_exists(path.ptr)); + } + } + + git_str_dispose(&content); + git_str_dispose(&path); +} + +void test_reset_hard__cannot_reset_in_a_bare_repository(void) +{ + git_repository *bare; + + cl_git_pass(git_repository_open(&bare, cl_fixture("testrepo.git"))); + cl_assert(git_repository_is_bare(bare) == true); + + cl_git_pass(git_revparse_single(&target, bare, KNOWN_COMMIT_IN_BARE_REPO)); + + cl_assert_equal_i(GIT_EBAREREPO, git_reset(bare, target, GIT_RESET_HARD, NULL)); + + git_repository_free(bare); +} + +static void index_entry_init(git_index *index, int side, git_oid *oid) +{ + git_index_entry entry; + + memset(&entry, 0x0, sizeof(git_index_entry)); + + entry.path = "conflicting_file"; + GIT_INDEX_ENTRY_STAGE_SET(&entry, side); + entry.mode = 0100644; + git_oid_cpy(&entry.id, oid); + + cl_git_pass(git_index_add(index, &entry)); +} + +static void unmerged_index_init(git_index *index, int entries) +{ + int write_ancestor = 1; + int write_ours = 2; + int write_theirs = 4; + git_oid ancestor, ours, theirs; + + git_oid_fromstr(&ancestor, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); + git_oid_fromstr(&ours, "32504b727382542f9f089e24fddac5e78533e96c"); + git_oid_fromstr(&theirs, "061d42a44cacde5726057b67558821d95db96f19"); + + cl_git_rewritefile("status/conflicting_file", "conflicting file\n"); + + if (entries & write_ancestor) + index_entry_init(index, 1, &ancestor); + + if (entries & write_ours) + index_entry_init(index, 2, &ours); + + if (entries & write_theirs) + index_entry_init(index, 3, &theirs); +} + +void test_reset_hard__resetting_reverts_unmerged(void) +{ + git_index *index; + int entries; + + /* Ensure every permutation of non-zero stage entries results in the + * path being cleaned up. */ + for (entries = 1; entries < 8; entries++) { + cl_git_pass(git_repository_index(&index, repo)); + + unmerged_index_init(index, entries); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_revparse_single(&target, repo, "26a125e")); + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); + + cl_assert(git_fs_path_exists("status/conflicting_file") == 0); + + git_object_free(target); + target = NULL; + + git_index_free(index); + } +} + +void test_reset_hard__cleans_up_merge(void) +{ + git_str merge_head_path = GIT_STR_INIT, + merge_msg_path = GIT_STR_INIT, + merge_mode_path = GIT_STR_INIT, + orig_head_path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD")); + cl_git_mkfile(git_str_cstr(&merge_head_path), "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef\n"); + + cl_git_pass(git_str_joinpath(&merge_msg_path, git_repository_path(repo), "MERGE_MSG")); + cl_git_mkfile(git_str_cstr(&merge_msg_path), "Merge commit 0017bd4ab1ec30440b17bae1680cff124ab5f1f6\n"); + + cl_git_pass(git_str_joinpath(&merge_mode_path, git_repository_path(repo), "MERGE_MODE")); + cl_git_mkfile(git_str_cstr(&merge_mode_path), ""); + + cl_git_pass(git_str_joinpath(&orig_head_path, git_repository_path(repo), "ORIG_HEAD")); + cl_git_mkfile(git_str_cstr(&orig_head_path), "0017bd4ab1ec30440b17bae1680cff124ab5f1f6"); + + cl_git_pass(git_revparse_single(&target, repo, "0017bd4")); + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); + + cl_assert(!git_fs_path_exists(git_str_cstr(&merge_head_path))); + cl_assert(!git_fs_path_exists(git_str_cstr(&merge_msg_path))); + cl_assert(!git_fs_path_exists(git_str_cstr(&merge_mode_path))); + + cl_assert(git_fs_path_exists(git_str_cstr(&orig_head_path))); + cl_git_pass(p_unlink(git_str_cstr(&orig_head_path))); + + git_str_dispose(&merge_head_path); + git_str_dispose(&merge_msg_path); + git_str_dispose(&merge_mode_path); + git_str_dispose(&orig_head_path); +} + +void test_reset_hard__reflog_is_correct(void) +{ + git_str buf = GIT_STR_INIT; + git_annotated_commit *annotated; + const char *exp_msg = "commit: Add a file which name should appear before the " + "\"subdir/\" folder while being dealt with by the treewalker"; + + reflog_check(repo, "HEAD", 3, "emeric.fermas@gmail.com", exp_msg); + reflog_check(repo, "refs/heads/master", 3, "emeric.fermas@gmail.com", exp_msg); + + /* Branch not moving, no reflog entry */ + cl_git_pass(git_revparse_single(&target, repo, "HEAD^{commit}")); + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); + reflog_check(repo, "HEAD", 3, "emeric.fermas@gmail.com", exp_msg); + reflog_check(repo, "refs/heads/master", 3, "emeric.fermas@gmail.com", exp_msg); + + git_object_free(target); + + /* Moved branch, expect id in message */ + cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}")); + cl_git_pass(git_str_printf(&buf, "reset: moving to %s", git_oid_tostr_s(git_object_id(target)))); + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); + reflog_check(repo, "HEAD", 4, NULL, git_str_cstr(&buf)); + reflog_check(repo, "refs/heads/master", 4, NULL, git_str_cstr(&buf)); + + git_str_dispose(&buf); + + /* Moved branch, expect revspec in message */ + exp_msg = "reset: moving to HEAD~^{commit}"; + cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "HEAD~^{commit}")); + cl_git_pass(git_reset_from_annotated(repo, annotated, GIT_RESET_HARD, NULL)); + reflog_check(repo, "HEAD", 5, NULL, exp_msg); + reflog_check(repo, "refs/heads/master", 5, NULL, exp_msg); + + git_annotated_commit_free(annotated); + +} + +void test_reset_hard__switch_file_to_dir(void) +{ + git_index_entry entry = {{ 0 }}; + git_index *idx; + git_odb *odb; + git_object *commit; + git_tree *tree; + git_signature *sig; + git_oid src_tree_id, tgt_tree_id; + git_oid src_id, tgt_id; + + cl_git_pass(git_repository_odb(&odb, repo)); + cl_git_pass(git_odb_write(&entry.id, odb, "", 0, GIT_OBJECT_BLOB)); + git_odb_free(odb); + + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_index_new(&idx)); + cl_git_pass(git_signature_now(&sig, "foo", "bar")); + + /* Create the old tree */ + entry.path = "README"; + cl_git_pass(git_index_add(idx, &entry)); + entry.path = "dir"; + cl_git_pass(git_index_add(idx, &entry)); + + cl_git_pass(git_index_write_tree_to(&src_tree_id, idx, repo)); + cl_git_pass(git_index_clear(idx)); + + cl_git_pass(git_tree_lookup(&tree, repo, &src_tree_id)); + cl_git_pass(git_commit_create(&src_id, repo, NULL, sig, sig, NULL, "foo", tree, 0, NULL)); + git_tree_free(tree); + + /* Create the new tree */ + entry.path = "README"; + cl_git_pass(git_index_add(idx, &entry)); + entry.path = "dir/FILE"; + cl_git_pass(git_index_add(idx, &entry)); + + cl_git_pass(git_index_write_tree_to(&tgt_tree_id, idx, repo)); + cl_git_pass(git_tree_lookup(&tree, repo, &tgt_tree_id)); + cl_git_pass(git_commit_create(&tgt_id, repo, NULL, sig, sig, NULL, "foo", tree, 0, NULL)); + git_tree_free(tree); + git_index_free(idx); + git_signature_free(sig); + + /* Let's go to a known state of the src commit with the file named 'dir' */ + cl_git_pass(git_object_lookup(&commit, repo, &src_id, GIT_OBJECT_COMMIT)); + cl_git_pass(git_reset(repo, commit, GIT_RESET_HARD, NULL)); + git_object_free(commit); + + /* And now we move over to the commit with the directory named 'dir' */ + cl_git_pass(git_object_lookup(&commit, repo, &tgt_id, GIT_OBJECT_COMMIT)); + cl_git_pass(git_reset(repo, commit, GIT_RESET_HARD, NULL)); + git_object_free(commit); +} diff --git a/tests/libgit2/reset/mixed.c b/tests/libgit2/reset/mixed.c new file mode 100644 index 000000000..4a78d4c37 --- /dev/null +++ b/tests/libgit2/reset/mixed.c @@ -0,0 +1,85 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "reset_helpers.h" +#include "path.h" + +static git_repository *repo; +static git_object *target; + +void test_reset_mixed__initialize(void) +{ + repo = cl_git_sandbox_init("attr"); + target = NULL; +} + +void test_reset_mixed__cleanup(void) +{ + git_object_free(target); + target = NULL; + + cl_git_sandbox_cleanup(); +} + +void test_reset_mixed__cannot_reset_in_a_bare_repository(void) +{ + git_repository *bare; + + cl_git_pass(git_repository_open(&bare, cl_fixture("testrepo.git"))); + cl_assert(git_repository_is_bare(bare) == true); + + cl_git_pass(git_revparse_single(&target, bare, KNOWN_COMMIT_IN_BARE_REPO)); + + cl_assert_equal_i(GIT_EBAREREPO, git_reset(bare, target, GIT_RESET_MIXED, NULL)); + + git_repository_free(bare); +} + +void test_reset_mixed__resetting_refreshes_the_index_to_the_commit_tree(void) +{ + unsigned int status; + + cl_git_pass(git_status_file(&status, repo, "macro_bad")); + cl_assert(status == GIT_STATUS_CURRENT); + cl_git_pass(git_revparse_single(&target, repo, "605812a")); + + cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL)); + + cl_git_pass(git_status_file(&status, repo, "macro_bad")); + cl_assert(status == GIT_STATUS_WT_NEW); +} + +void test_reset_mixed__reflog_is_correct(void) +{ + git_str buf = GIT_STR_INIT; + git_annotated_commit *annotated; + const char *exp_msg = "commit: Updating test data so we can test inter-hunk-context"; + + reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg); + reflog_check(repo, "refs/heads/master", 9, "yoram.harmelin@gmail.com", exp_msg); + + /* Branch not moving, no reflog entry */ + cl_git_pass(git_revparse_single(&target, repo, "HEAD^{commit}")); + cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL)); + reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg); + reflog_check(repo, "refs/heads/master", 9, "yoram.harmelin@gmail.com", exp_msg); + + git_object_free(target); + target = NULL; + + /* Moved branch, expect id in message */ + cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}")); + git_str_clear(&buf); + cl_git_pass(git_str_printf(&buf, "reset: moving to %s", git_oid_tostr_s(git_object_id(target)))); + cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL)); + reflog_check(repo, "HEAD", 10, NULL, git_str_cstr(&buf)); + reflog_check(repo, "refs/heads/master", 10, NULL, git_str_cstr(&buf)); + git_str_dispose(&buf); + + /* Moved branch, expect revspec in message */ + exp_msg = "reset: moving to HEAD~^{commit}"; + cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "HEAD~^{commit}")); + cl_git_pass(git_reset_from_annotated(repo, annotated, GIT_RESET_MIXED, NULL)); + reflog_check(repo, "HEAD", 11, NULL, exp_msg); + reflog_check(repo, "refs/heads/master", 11, NULL, exp_msg); + git_annotated_commit_free(annotated); +} diff --git a/tests/libgit2/reset/reset_helpers.c b/tests/libgit2/reset/reset_helpers.c new file mode 100644 index 000000000..e6acec9ef --- /dev/null +++ b/tests/libgit2/reset/reset_helpers.c @@ -0,0 +1,20 @@ +#include "clar_libgit2.h" +#include "reset_helpers.h" + +void reflog_check(git_repository *repo, const char *refname, + size_t exp_count, const char *exp_email, const char *exp_msg) +{ + git_reflog *log; + const git_reflog_entry *entry; + + GIT_UNUSED(exp_email); + + cl_git_pass(git_reflog_read(&log, repo, refname)); + cl_assert_equal_i(exp_count, git_reflog_entrycount(log)); + entry = git_reflog_entry_byindex(log, 0); + + if (exp_msg) + cl_assert_equal_s(exp_msg, git_reflog_entry_message(entry)); + + git_reflog_free(log); +} diff --git a/tests/libgit2/reset/reset_helpers.h b/tests/libgit2/reset/reset_helpers.h new file mode 100644 index 000000000..e7e048514 --- /dev/null +++ b/tests/libgit2/reset/reset_helpers.h @@ -0,0 +1,7 @@ +#include "common.h" + +#define KNOWN_COMMIT_IN_BARE_REPO "e90810b8df3e80c413d903f631643c716887138d" +#define KNOWN_COMMIT_IN_ATTR_REPO "217878ab49e1314388ea2e32dc6fdb58a1b969e0" + +void reflog_check(git_repository *repo, const char *refname, + size_t exp_count, const char *exp_email, const char *exp_msg); diff --git a/tests/libgit2/reset/soft.c b/tests/libgit2/reset/soft.c new file mode 100644 index 000000000..aca0834f2 --- /dev/null +++ b/tests/libgit2/reset/soft.c @@ -0,0 +1,189 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "reset_helpers.h" +#include "path.h" +#include "repo/repo_helpers.h" + +static git_repository *repo; +static git_object *target; + +void test_reset_soft__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_reset_soft__cleanup(void) +{ + git_object_free(target); + target = NULL; + + cl_git_sandbox_cleanup(); +} + +static void assert_reset_soft(bool should_be_detached) +{ + git_oid oid; + + cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); + cl_git_fail(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO)); + cl_git_pass(git_revparse_single(&target, repo, KNOWN_COMMIT_IN_BARE_REPO)); + + cl_assert(git_repository_head_detached(repo) == should_be_detached); + + cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); + + cl_assert(git_repository_head_detached(repo) == should_be_detached); + + cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); + cl_git_pass(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO)); +} + +void test_reset_soft__can_reset_the_non_detached_Head_to_the_specified_commit(void) +{ + assert_reset_soft(false); +} + +void test_reset_soft__can_reset_the_detached_Head_to_the_specified_commit(void) +{ + git_repository_detach_head(repo); + + assert_reset_soft(true); +} + +void test_reset_soft__resetting_to_the_commit_pointed_at_by_the_Head_does_not_change_the_target_of_the_Head(void) +{ + git_oid oid; + char raw_head_oid[GIT_OID_HEXSZ + 1]; + + cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); + git_oid_fmt(raw_head_oid, &oid); + raw_head_oid[GIT_OID_HEXSZ] = '\0'; + + cl_git_pass(git_revparse_single(&target, repo, raw_head_oid)); + + cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); + + cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); + cl_git_pass(git_oid_streq(&oid, raw_head_oid)); +} + +void test_reset_soft__resetting_to_a_tag_sets_the_Head_to_the_peeled_commit(void) +{ + git_oid oid; + + /* b25fa35 is a tag, pointing to another tag which points to commit e90810b */ + cl_git_pass(git_revparse_single(&target, repo, "b25fa35")); + + cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); + + cl_assert(git_repository_head_detached(repo) == false); + cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); + cl_git_pass(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO)); +} + +void test_reset_soft__cannot_reset_to_a_tag_not_pointing_at_a_commit(void) +{ + /* 53fc32d is the tree of commit e90810b */ + cl_git_pass(git_revparse_single(&target, repo, "53fc32d")); + + cl_git_fail(git_reset(repo, target, GIT_RESET_SOFT, NULL)); + git_object_free(target); + + /* 521d87c is an annotated tag pointing to a blob */ + cl_git_pass(git_revparse_single(&target, repo, "521d87c")); + cl_git_fail(git_reset(repo, target, GIT_RESET_SOFT, NULL)); +} + +void test_reset_soft__resetting_against_an_unborn_head_repo_makes_the_head_no_longer_unborn(void) +{ + git_reference *head; + + cl_git_pass(git_revparse_single(&target, repo, KNOWN_COMMIT_IN_BARE_REPO)); + + make_head_unborn(repo, NON_EXISTING_HEAD); + + cl_assert_equal_i(true, git_repository_head_unborn(repo)); + + cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); + + cl_assert_equal_i(false, git_repository_head_unborn(repo)); + + cl_git_pass(git_reference_lookup(&head, repo, NON_EXISTING_HEAD)); + cl_assert_equal_i(0, git_oid_streq(git_reference_target(head), KNOWN_COMMIT_IN_BARE_REPO)); + + git_reference_free(head); +} + +void test_reset_soft__fails_when_merging(void) +{ + git_str merge_head_path = GIT_STR_INIT; + + cl_git_pass(git_repository_detach_head(repo)); + cl_git_pass(git_str_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD")); + cl_git_mkfile(git_str_cstr(&merge_head_path), "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef\n"); + + cl_git_pass(git_revparse_single(&target, repo, KNOWN_COMMIT_IN_BARE_REPO)); + + cl_assert_equal_i(GIT_EUNMERGED, git_reset(repo, target, GIT_RESET_SOFT, NULL)); + cl_git_pass(p_unlink(git_str_cstr(&merge_head_path))); + + git_str_dispose(&merge_head_path); +} + +void test_reset_soft__fails_when_index_contains_conflicts_independently_of_MERGE_HEAD_file_existence(void) +{ + git_index *index; + git_reference *head; + git_str merge_head_path = GIT_STR_INIT; + + cl_git_sandbox_cleanup(); + + repo = cl_git_sandbox_init("mergedrepo"); + + cl_git_pass(git_str_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD")); + cl_git_pass(p_unlink(git_str_cstr(&merge_head_path))); + git_str_dispose(&merge_head_path); + + cl_git_pass(git_repository_index(&index, repo)); + cl_assert_equal_i(true, git_index_has_conflicts(index)); + git_index_free(index); + + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel(&target, head, GIT_OBJECT_COMMIT)); + git_reference_free(head); + + cl_assert_equal_i(GIT_EUNMERGED, git_reset(repo, target, GIT_RESET_SOFT, NULL)); +} + +void test_reset_soft__reflog_is_correct(void) +{ + git_annotated_commit *annotated; + const char *exp_msg = "checkout: moving from br2 to master"; + const char *master_msg = "commit: checking in"; + + reflog_check(repo, "HEAD", 7, "yoram.harmelin@gmail.com", exp_msg); + reflog_check(repo, "refs/heads/master", 2, "yoram.harmelin@gmail.com", master_msg); + + /* Branch not moving, no reflog entry */ + cl_git_pass(git_revparse_single(&target, repo, "HEAD^{commit}")); + cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); + reflog_check(repo, "HEAD", 7, "yoram.harmelin@gmail.com", exp_msg); + reflog_check(repo, "refs/heads/master", 2, "yoram.harmelin@gmail.com", master_msg); + git_object_free(target); + + /* Moved branch, expect id in message */ + exp_msg = "reset: moving to be3563ae3f795b2b4353bcce3a527ad0a4f7f644"; + cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}")); + cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); + reflog_check(repo, "HEAD", 8, "yoram.harmelin@gmail.com", exp_msg); + reflog_check(repo, "refs/heads/master", 3, NULL, exp_msg); + + /* Moved branch, expect message with annotated string */ + exp_msg = "reset: moving to HEAD~^{commit}"; + cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "HEAD~^{commit}")); + cl_git_pass(git_reset_from_annotated(repo, annotated, GIT_RESET_SOFT, NULL)); + reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg); + reflog_check(repo, "refs/heads/master", 4, NULL, exp_msg); + + git_annotated_commit_free(annotated); +} diff --git a/tests/libgit2/revert/bare.c b/tests/libgit2/revert/bare.c new file mode 100644 index 000000000..9261cfe86 --- /dev/null +++ b/tests/libgit2/revert/bare.c @@ -0,0 +1,106 @@ +#include "clar.h" +#include "clar_libgit2.h" + +#include "futils.h" +#include "git2/revert.h" + +#include "../merge/merge_helpers.h" + +#define TEST_REPO_PATH "revert" + +static git_repository *repo; + +/* Fixture setup and teardown */ +void test_revert_bare__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_revert_bare__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_revert_bare__automerge(void) +{ + git_commit *head_commit, *revert_commit; + git_oid head_oid, revert_oid; + git_index *index; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" }, + { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, + { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, + { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, + }; + + git_oid_fromstr(&head_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45"); + cl_git_pass(git_commit_lookup(&head_commit, repo, &head_oid)); + + git_oid_fromstr(&revert_oid, "d1d403d22cbe24592d725f442835cf46fe60c8ac"); + cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_oid)); + + cl_git_pass(git_revert_commit(&index, repo, revert_commit, head_commit, 0, NULL)); + cl_assert(merge_test_index(index, merge_index_entries, 4)); + + git_commit_free(revert_commit); + git_commit_free(head_commit); + git_index_free(index); +} + +void test_revert_bare__conflicts(void) +{ + git_reference *head_ref; + git_commit *head_commit, *revert_commit; + git_oid revert_oid; + git_index *index; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "7731926a337c4eaba1e2187d90ebfa0a93659382", 1, "file1.txt" }, + { 0100644, "4b8fcff56437e60f58e9a6bc630dd242ebf6ea2c", 2, "file1.txt" }, + { 0100644, "3a3ef367eaf3fe79effbfb0a56b269c04c2b59fe", 3, "file1.txt" }, + { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, + { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, + { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, + }; + + git_oid_fromstr(&revert_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45"); + + cl_git_pass(git_repository_head(&head_ref, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head_ref, GIT_OBJECT_COMMIT)); + + cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_oid)); + cl_git_pass(git_revert_commit(&index, repo, revert_commit, head_commit, 0, NULL)); + + cl_assert(git_index_has_conflicts(index)); + cl_assert(merge_test_index(index, merge_index_entries, 6)); + + git_commit_free(revert_commit); + git_commit_free(head_commit); + git_reference_free(head_ref); + git_index_free(index); +} + +void test_revert_bare__orphan(void) +{ + git_commit *head_commit, *revert_commit; + git_oid head_oid, revert_oid; + git_index *index; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "296a6d3be1dff05c5d1f631d2459389fa7b619eb", 0, "file-mainline.txt" }, + }; + + git_oid_fromstr(&head_oid, "39467716290f6df775a91cdb9a4eb39295018145"); + cl_git_pass(git_commit_lookup(&head_commit, repo, &head_oid)); + + git_oid_fromstr(&revert_oid, "ebb03002cee5d66c7732dd06241119fe72ab96a5"); + cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_oid)); + + cl_git_pass(git_revert_commit(&index, repo, revert_commit, head_commit, 0, NULL)); + cl_assert(merge_test_index(index, merge_index_entries, 1)); + + git_commit_free(revert_commit); + git_commit_free(head_commit); + git_index_free(index); +} diff --git a/tests/libgit2/revert/rename.c b/tests/libgit2/revert/rename.c new file mode 100644 index 000000000..0d713c60f --- /dev/null +++ b/tests/libgit2/revert/rename.c @@ -0,0 +1,49 @@ +#include "clar.h" +#include "clar_libgit2.h" + +#include "git2/revert.h" +#include "../merge/merge_helpers.h" + +#define TEST_REPO_PATH "revert-rename.git" + +static git_repository *repo; + +/* Fixture setup and teardown */ +void test_revert_rename__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_revert_rename__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +/* Attempt a revert when there is a file rename AND change of file mode, + * but the file contents remain the same. Check that the file mode doesn't + * change following the revert. + */ +void test_revert_rename__automerge(void) +{ + git_commit *head_commit, *revert_commit; + git_oid revert_oid; + git_index *index; + git_reference *head_ref; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "f0f64c618e1646d2948a456ed7c4bcfad5536d68", 0, "goodmode" }}; + + cl_git_pass(git_repository_head(&head_ref, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head_ref, GIT_OBJECT_COMMIT)); + + cl_git_pass(git_oid_fromstr(&revert_oid, "7b4d7c3789b3581973c04087cb774c3c3576de2f")); + cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_oid)); + + cl_git_pass(git_revert_commit(&index, repo, revert_commit, head_commit, 0, NULL)); + cl_assert(merge_test_index(index, merge_index_entries, 1)); + + git_commit_free(revert_commit); + git_commit_free(head_commit); + git_index_free(index); + git_reference_free(head_ref); +} diff --git a/tests/libgit2/revert/workdir.c b/tests/libgit2/revert/workdir.c new file mode 100644 index 000000000..3f54280ca --- /dev/null +++ b/tests/libgit2/revert/workdir.c @@ -0,0 +1,576 @@ +#include "clar.h" +#include "clar_libgit2.h" + +#include "futils.h" +#include "git2/revert.h" + +#include "../merge/merge_helpers.h" + +#define TEST_REPO_PATH "revert" + +static git_repository *repo; +static git_index *repo_index; + +/* Fixture setup and teardown */ +void test_revert_workdir__initialize(void) +{ + git_config *cfg; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); + + /* Ensure that the user's merge.conflictstyle doesn't interfere */ + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge")); + git_config_free(cfg); +} + +void test_revert_workdir__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +/* git reset --hard 72333f47d4e83616630ff3b0ffe4c0faebcc3c45 + * git revert --no-commit d1d403d22cbe24592d725f442835cf46fe60c8ac */ +void test_revert_workdir__automerge(void) +{ + git_commit *head, *commit; + git_oid head_oid, revert_oid; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" }, + { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, + { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, + { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, + }; + + git_oid_fromstr(&head_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&revert_oid, "d1d403d22cbe24592d725f442835cf46fe60c8ac"); + cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); + cl_git_pass(git_revert(repo, commit, NULL)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); + + git_commit_free(commit); + git_commit_free(head); +} + +/* git revert --no-commit 72333f47d4e83616630ff3b0ffe4c0faebcc3c45 */ +void test_revert_workdir__conflicts(void) +{ + git_reference *head_ref; + git_commit *head, *commit; + git_oid revert_oid; + git_str conflicting_buf = GIT_STR_INIT, mergemsg_buf = GIT_STR_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "7731926a337c4eaba1e2187d90ebfa0a93659382", 1, "file1.txt" }, + { 0100644, "4b8fcff56437e60f58e9a6bc630dd242ebf6ea2c", 2, "file1.txt" }, + { 0100644, "3a3ef367eaf3fe79effbfb0a56b269c04c2b59fe", 3, "file1.txt" }, + { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, + { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, + { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, + }; + + git_oid_fromstr(&revert_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45"); + + cl_git_pass(git_repository_head(&head_ref, repo)); + cl_git_pass(git_reference_peel((git_object **)&head, head_ref, GIT_OBJECT_COMMIT)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); + cl_git_pass(git_revert(repo, commit, NULL)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/file1.txt")); + cl_assert(strcmp(conflicting_buf.ptr, "!File one!\n" \ + "!File one!\n" \ + "File one!\n" \ + "File one\n" \ + "File one\n" \ + "File one\n" \ + "File one\n" \ + "File one\n" \ + "File one\n" \ + "File one\n" \ + "<<<<<<< HEAD\n" \ + "File one!\n" \ + "!File one!\n" \ + "!File one!\n" \ + "!File one!\n" \ + "=======\n" \ + "File one\n" \ + "File one\n" \ + "File one\n" \ + "File one\n" \ + ">>>>>>> parent of 72333f4... automergeable changes\n") == 0); + + cl_assert(git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); + cl_git_pass(git_futils_readbuffer(&mergemsg_buf, + TEST_REPO_PATH "/.git/MERGE_MSG")); + cl_assert(strcmp(mergemsg_buf.ptr, + "Revert \"automergeable changes\"\n" \ + "\n" \ + "This reverts commit 72333f47d4e83616630ff3b0ffe4c0faebcc3c45.\n" + "\n" \ + "#Conflicts:\n" \ + "#\tfile1.txt\n") == 0); + + git_commit_free(commit); + git_commit_free(head); + git_reference_free(head_ref); + git_str_dispose(&mergemsg_buf); + git_str_dispose(&conflicting_buf); +} + +/* git reset --hard 39467716290f6df775a91cdb9a4eb39295018145 + * git revert --no-commit ebb03002cee5d66c7732dd06241119fe72ab96a5 +*/ +void test_revert_workdir__orphan(void) +{ + git_commit *head, *commit; + git_oid head_oid, revert_oid; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "296a6d3be1dff05c5d1f631d2459389fa7b619eb", 0, "file-mainline.txt" }, + }; + + git_oid_fromstr(&head_oid, "39467716290f6df775a91cdb9a4eb39295018145"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&revert_oid, "ebb03002cee5d66c7732dd06241119fe72ab96a5"); + cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); + cl_git_pass(git_revert(repo, commit, NULL)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 1)); + + git_commit_free(commit); + git_commit_free(head); +} + +/* + * revert the same commit twice (when the first reverts cleanly): + * + * git revert 2d440f2 + * git revert 2d440f2 + */ +void test_revert_workdir__again(void) +{ + git_reference *head_ref; + git_commit *orig_head; + git_tree *reverted_tree; + git_oid reverted_tree_oid, reverted_commit_oid; + git_signature *signature; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "7731926a337c4eaba1e2187d90ebfa0a93659382", 0, "file1.txt" }, + { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, + { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, + { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, + }; + + cl_git_pass(git_repository_head(&head_ref, repo)); + cl_git_pass(git_reference_peel((git_object **)&orig_head, head_ref, GIT_OBJECT_COMMIT)); + cl_git_pass(git_reset(repo, (git_object *)orig_head, GIT_RESET_HARD, NULL)); + + cl_git_pass(git_revert(repo, orig_head, NULL)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); + + cl_git_pass(git_index_write_tree(&reverted_tree_oid, repo_index)); + cl_git_pass(git_tree_lookup(&reverted_tree, repo, &reverted_tree_oid)); + + cl_git_pass(git_signature_new(&signature, "Reverter", "reverter@example.org", time(NULL), 0)); + cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, (const git_commit **)&orig_head)); + + cl_git_pass(git_revert(repo, orig_head, NULL)); + cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); + + git_signature_free(signature); + git_tree_free(reverted_tree); + git_commit_free(orig_head); + git_reference_free(head_ref); +} + +/* git reset --hard 72333f47d4e83616630ff3b0ffe4c0faebcc3c45 + * git revert --no-commit d1d403d22cbe24592d725f442835cf46fe60c8ac */ +void test_revert_workdir__again_after_automerge(void) +{ + git_commit *head, *commit; + git_tree *reverted_tree; + git_oid head_oid, revert_oid, reverted_tree_oid, reverted_commit_oid; + git_signature *signature; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" }, + { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, + { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, + { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, + }; + + struct merge_index_entry second_revert_entries[] = { + { 0100644, "3a3ef367eaf3fe79effbfb0a56b269c04c2b59fe", 1, "file1.txt" }, + { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 2, "file1.txt" }, + { 0100644, "747726e021bc5f44b86de60e3032fd6f9f1b8383", 3, "file1.txt" }, + { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, + { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, + { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, + }; + + git_oid_fromstr(&head_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&revert_oid, "d1d403d22cbe24592d725f442835cf46fe60c8ac"); + cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); + cl_git_pass(git_revert(repo, commit, NULL)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); + + cl_git_pass(git_index_write_tree(&reverted_tree_oid, repo_index)); + cl_git_pass(git_tree_lookup(&reverted_tree, repo, &reverted_tree_oid)); + + cl_git_pass(git_signature_new(&signature, "Reverter", "reverter@example.org", time(NULL), 0)); + cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, (const git_commit **)&head)); + + cl_git_pass(git_revert(repo, commit, NULL)); + cl_assert(merge_test_index(repo_index, second_revert_entries, 6)); + + git_signature_free(signature); + git_tree_free(reverted_tree); + git_commit_free(commit); + git_commit_free(head); +} + +/* + * revert the same commit twice (when the first reverts cleanly): + * + * git revert 2d440f2 + * git revert 2d440f2 + */ +void test_revert_workdir__again_after_edit(void) +{ + git_reference *head_ref; + git_commit *orig_head, *commit; + git_tree *reverted_tree; + git_oid orig_head_oid, revert_oid, reverted_tree_oid, reverted_commit_oid; + git_signature *signature; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "3721552e06c4bdc7d478e0674e6304888545d5fd", 0, "file1.txt" }, + { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, + { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, + { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, + }; + + cl_git_pass(git_repository_head(&head_ref, repo)); + + cl_git_pass(git_oid_fromstr(&orig_head_oid, "399fb3aba3d9d13f7d40a9254ce4402067ef3149")); + cl_git_pass(git_commit_lookup(&orig_head, repo, &orig_head_oid)); + cl_git_pass(git_reset(repo, (git_object *)orig_head, GIT_RESET_HARD, NULL)); + + cl_git_pass(git_oid_fromstr(&revert_oid, "2d440f2b3147d3dc7ad1085813478d6d869d5a4d")); + cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); + + cl_git_pass(git_revert(repo, commit, NULL)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); + + cl_git_pass(git_index_write_tree(&reverted_tree_oid, repo_index)); + cl_git_pass(git_tree_lookup(&reverted_tree, repo, &reverted_tree_oid)); + + cl_git_pass(git_signature_new(&signature, "Reverter", "reverter@example.org", time(NULL), 0)); + cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, (const git_commit **)&orig_head)); + + cl_git_pass(git_revert(repo, commit, NULL)); + cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); + + git_signature_free(signature); + git_tree_free(reverted_tree); + git_commit_free(commit); + git_commit_free(orig_head); + git_reference_free(head_ref); +} + +/* + * revert the same commit twice (when the first reverts cleanly): + * + * git reset --hard 75ec9929465623f17ff3ad68c0438ea56faba815 + * git revert 97e52d5e81f541080cd6b92829fb85bc4d81d90b + */ +void test_revert_workdir__again_after_edit_two(void) +{ + git_str diff_buf = GIT_STR_INIT; + git_config *config; + git_oid head_commit_oid, revert_commit_oid; + git_commit *head_commit, *revert_commit; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "a8c86221b400b836010567cc3593db6e96c1a83a", 1, "file.txt" }, + { 0100644, "46ff0854663aeb2182b9838c8da68e33ac23bc1e", 2, "file.txt" }, + { 0100644, "21a96a98ed84d45866e1de6e266fd3a61a4ae9dc", 3, "file.txt" }, + }; + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "core.autocrlf", 0)); + + cl_git_pass(git_oid_fromstr(&head_commit_oid, "75ec9929465623f17ff3ad68c0438ea56faba815")); + cl_git_pass(git_commit_lookup(&head_commit, repo, &head_commit_oid)); + cl_git_pass(git_reset(repo, (git_object *)head_commit, GIT_RESET_HARD, NULL)); + + cl_git_pass(git_oid_fromstr(&revert_commit_oid, "97e52d5e81f541080cd6b92829fb85bc4d81d90b")); + cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_commit_oid)); + + cl_git_pass(git_revert(repo, revert_commit, NULL)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); + + cl_git_pass(git_futils_readbuffer(&diff_buf, "revert/file.txt")); + cl_assert_equal_s( + "a\n" \ + "<<<<<<< HEAD\n" \ + "=======\n" \ + "a\n" \ + ">>>>>>> parent of 97e52d5... Revert me\n" \ + "a\n" \ + "a\n" \ + "a\n" \ + "a\n" \ + "ab", + diff_buf.ptr); + + git_commit_free(revert_commit); + git_commit_free(head_commit); + git_config_free(config); + git_str_dispose(&diff_buf); +} + +/* git reset --hard 72333f47d4e83616630ff3b0ffe4c0faebcc3c45 + * git revert --no-commit d1d403d22cbe24592d725f442835cf46fe60c8ac */ +void test_revert_workdir__conflict_use_ours(void) +{ + git_commit *head, *commit; + git_oid head_oid, revert_oid; + git_revert_options opts = GIT_REVERT_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" }, + { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, + { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, + { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, + }; + + struct merge_index_entry merge_filesystem_entries[] = { + { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" }, + { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, + { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, + { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, + }; + + opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; + + git_oid_fromstr(&head_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&revert_oid, "d1d403d22cbe24592d725f442835cf46fe60c8ac"); + cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); + cl_git_pass(git_revert(repo, commit, &opts)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); + cl_assert(merge_test_workdir(repo, merge_filesystem_entries, 4)); + + git_commit_free(commit); + git_commit_free(head); +} + +/* git reset --hard cef56612d71a6af8d8015691e4865f7fece905b5 + * git revert --no-commit 55568c8de5322ff9a95d72747a239cdb64a19965 + */ +void test_revert_workdir__rename_1_of_2(void) +{ + git_commit *head, *commit; + git_oid head_oid, revert_oid; + git_revert_options opts = GIT_REVERT_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "747726e021bc5f44b86de60e3032fd6f9f1b8383", 0, "file1.txt" }, + { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, + { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, + { 0100644, "55acf326a69f0aab7a974ec53ffa55a50bcac14e", 3, "file4.txt" }, + { 0100644, "55acf326a69f0aab7a974ec53ffa55a50bcac14e", 1, "file5.txt" }, + { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 2, "file6.txt" }, + }; + + opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES; + opts.merge_opts.rename_threshold = 50; + + git_oid_fromstr(&head_oid, "cef56612d71a6af8d8015691e4865f7fece905b5"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&revert_oid, "55568c8de5322ff9a95d72747a239cdb64a19965"); + cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); + cl_git_pass(git_revert(repo, commit, &opts)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); + + git_commit_free(commit); + git_commit_free(head); +} + +/* git reset --hard 55568c8de5322ff9a95d72747a239cdb64a19965 + * git revert --no-commit HEAD~1 */ +void test_revert_workdir__rename(void) +{ + git_commit *head, *commit; + git_oid head_oid, revert_oid; + git_revert_options opts = GIT_REVERT_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "55acf326a69f0aab7a974ec53ffa55a50bcac14e", 1, "file4.txt" }, + { 0100644, "55acf326a69f0aab7a974ec53ffa55a50bcac14e", 2, "file5.txt" }, + }; + + struct merge_name_entry merge_name_entries[] = { + { "file4.txt", "file5.txt", "" }, + }; + + opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES; + opts.merge_opts.rename_threshold = 50; + + git_oid_fromstr(&head_oid, "55568c8de5322ff9a95d72747a239cdb64a19965"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + git_oid_fromstr(&revert_oid, "0aa8c7e40d342fff78d60b29a4ba8e993ed79c51"); + cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); + cl_git_pass(git_revert(repo, commit, &opts)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 2)); + cl_assert(merge_test_names(repo_index, merge_name_entries, 1)); + + git_commit_free(commit); + git_commit_free(head); +} + +/* git revert --no-commit HEAD */ +void test_revert_workdir__head(void) +{ + git_reference *head; + git_commit *commit; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "7731926a337c4eaba1e2187d90ebfa0a93659382", 0, "file1.txt" }, + { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, + { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, + { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, + }; + + /* HEAD is 2d440f2b3147d3dc7ad1085813478d6d869d5a4d */ + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&commit, head, GIT_OBJECT_COMMIT)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + cl_git_pass(git_revert(repo, commit, NULL)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); + cl_assert(merge_test_workdir(repo, merge_index_entries, 4)); + + git_reference_free(head); + git_commit_free(commit); +} + +void test_revert_workdir__nonmerge_fails_mainline_specified(void) +{ + git_reference *head; + git_commit *commit; + git_revert_options opts = GIT_REVERT_OPTIONS_INIT; + + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&commit, head, GIT_OBJECT_COMMIT)); + + opts.mainline = 1; + cl_must_fail(git_revert(repo, commit, &opts)); + cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); + cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/REVERT_HEAD")); + + git_reference_free(head); + git_commit_free(commit); +} + +/* git reset --hard 5acdc74af27172ec491d213ee36cea7eb9ef2579 + * git revert HEAD */ +void test_revert_workdir__merge_fails_without_mainline_specified(void) +{ + git_commit *head; + git_oid head_oid; + + git_oid_fromstr(&head_oid, "5acdc74af27172ec491d213ee36cea7eb9ef2579"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + cl_must_fail(git_revert(repo, head, NULL)); + cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); + cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/REVERT_HEAD")); + + git_commit_free(head); +} + +/* git reset --hard 5acdc74af27172ec491d213ee36cea7eb9ef2579 + * git revert HEAD -m1 --no-commit */ +void test_revert_workdir__merge_first_parent(void) +{ + git_commit *head; + git_oid head_oid; + git_revert_options opts = GIT_REVERT_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "296a6d3be1dff05c5d1f631d2459389fa7b619eb", 0, "file-mainline.txt" }, + { 0100644, "0cdb66192ee192f70f891f05a47636057420e871", 0, "file1.txt" }, + { 0100644, "73ec36fa120f8066963a0bc9105bb273dbd903d7", 0, "file2.txt" }, + }; + + opts.mainline = 1; + + git_oid_fromstr(&head_oid, "5acdc74af27172ec491d213ee36cea7eb9ef2579"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + cl_git_pass(git_revert(repo, head, &opts)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); + + git_commit_free(head); +} + +void test_revert_workdir__merge_second_parent(void) +{ + git_commit *head; + git_oid head_oid; + git_revert_options opts = GIT_REVERT_OPTIONS_INIT; + + struct merge_index_entry merge_index_entries[] = { + { 0100644, "33c6fd981c49a2abf2971482089350bfc5cda8ea", 0, "file-branch.txt" }, + { 0100644, "0cdb66192ee192f70f891f05a47636057420e871", 0, "file1.txt" }, + { 0100644, "73ec36fa120f8066963a0bc9105bb273dbd903d7", 0, "file2.txt" }, + }; + + opts.mainline = 2; + + git_oid_fromstr(&head_oid, "5acdc74af27172ec491d213ee36cea7eb9ef2579"); + cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); + cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); + + cl_git_pass(git_revert(repo, head, &opts)); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); + + git_commit_free(head); +} diff --git a/tests/libgit2/revwalk/basic.c b/tests/libgit2/revwalk/basic.c new file mode 100644 index 000000000..2c8d885e2 --- /dev/null +++ b/tests/libgit2/revwalk/basic.c @@ -0,0 +1,627 @@ +#include "clar_libgit2.h" + +/* + * a4a7dce [0] Merge branch 'master' into br2 + |\ + | * 9fd738e [1] a fourth commit + | * 4a202b3 [2] a third commit + * | c47800c [3] branch commit one + |/ + * 5b5b025 [5] another commit + * 8496071 [4] testing +*/ +static const char *commit_head = "a4a7dce85cf63874e984719f4fdd239f5145052f"; + +static const char *commit_ids[] = { + "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ + "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */ + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */ + "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ + "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */ + "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */ +}; + +/* Careful: there are two possible topological sorts */ +static const int commit_sorting_topo[][6] = { + {0, 1, 2, 3, 5, 4}, {0, 3, 1, 2, 5, 4} +}; + +static const int commit_sorting_time[][6] = { + {0, 3, 1, 2, 5, 4} +}; + +static const int commit_sorting_topo_reverse[][6] = { + {4, 5, 3, 2, 1, 0}, {4, 5, 2, 1, 3, 0} +}; + +static const int commit_sorting_time_reverse[][6] = { + {4, 5, 2, 1, 3, 0} +}; + +/* This is specified unsorted, so both combinations are possible */ +static const int commit_sorting_segment[][6] = { + {1, 2, -1, -1, -1, -1}, {2, 1, -1, -1, -1, -1} +}; + +#define commit_count 6 +static const int result_bytes = 24; + + +static int get_commit_index(git_oid *raw_oid) +{ + int i; + char oid[GIT_OID_HEXSZ]; + + git_oid_fmt(oid, raw_oid); + + for (i = 0; i < commit_count; ++i) + if (memcmp(oid, commit_ids[i], GIT_OID_HEXSZ) == 0) + return i; + + return -1; +} + +static int test_walk_only(git_revwalk *walk, + const int possible_results[][commit_count], int results_count) +{ + git_oid oid; + int i; + int result_array[commit_count]; + + for (i = 0; i < commit_count; ++i) + result_array[i] = -1; + + i = 0; + while (git_revwalk_next(&oid, walk) == 0) { + result_array[i++] = get_commit_index(&oid); + /*{ + char str[GIT_OID_HEXSZ+1]; + git_oid_fmt(str, &oid); + str[GIT_OID_HEXSZ] = 0; + printf(" %d) %s\n", i, str); + }*/ + } + + for (i = 0; i < results_count; ++i) + if (memcmp(possible_results[i], + result_array, result_bytes) == 0) + return 0; + + return GIT_ERROR; +} + +static int test_walk(git_revwalk *walk, const git_oid *root, + int flags, const int possible_results[][6], int results_count) +{ + git_revwalk_sorting(walk, flags); + git_revwalk_push(walk, root); + + return test_walk_only(walk, possible_results, results_count); +} + +static git_repository *_repo = NULL; +static git_revwalk *_walk = NULL; +static const char *_fixture = NULL; + +void test_revwalk_basic__initialize(void) +{ +} + +void test_revwalk_basic__cleanup(void) +{ + git_revwalk_free(_walk); + + if (_fixture) + cl_git_sandbox_cleanup(); + else + git_repository_free(_repo); + + _fixture = NULL; + _repo = NULL; + _walk = NULL; +} + +static void revwalk_basic_setup_walk(const char *fixture) +{ + if (fixture) { + _fixture = fixture; + _repo = cl_git_sandbox_init(fixture); + } else { + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + } + + cl_git_pass(git_revwalk_new(&_walk, _repo)); +} + +void test_revwalk_basic__sorting_modes(void) +{ + git_oid id; + + revwalk_basic_setup_walk(NULL); + + git_oid_fromstr(&id, commit_head); + + cl_git_pass(test_walk(_walk, &id, GIT_SORT_TIME, commit_sorting_time, 1)); + cl_git_pass(test_walk(_walk, &id, GIT_SORT_TOPOLOGICAL, commit_sorting_topo, 2)); + cl_git_pass(test_walk(_walk, &id, GIT_SORT_TIME | GIT_SORT_REVERSE, commit_sorting_time_reverse, 1)); + cl_git_pass(test_walk(_walk, &id, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE, commit_sorting_topo_reverse, 2)); +} + +void test_revwalk_basic__glob_heads(void) +{ + int i = 0; + git_oid oid; + + revwalk_basic_setup_walk(NULL); + + cl_git_pass(git_revwalk_push_glob(_walk, "heads")); + + while (git_revwalk_next(&oid, _walk) == 0) + i++; + + /* git log --branches --oneline | wc -l => 14 */ + cl_assert_equal_i(i, 14); +} + +void test_revwalk_basic__glob_heads_with_invalid(void) +{ + int i; + git_oid oid; + + revwalk_basic_setup_walk("testrepo"); + + cl_git_mkfile("testrepo/.git/refs/heads/garbage", "not-a-ref"); + cl_git_pass(git_revwalk_push_glob(_walk, "heads")); + + for (i = 0; !git_revwalk_next(&oid, _walk); ++i) + /* walking */; + + /* git log --branches --oneline | wc -l => 16 */ + cl_assert_equal_i(20, i); +} + +void test_revwalk_basic__push_head(void) +{ + int i = 0; + git_oid oid; + + revwalk_basic_setup_walk(NULL); + + cl_git_pass(git_revwalk_push_head(_walk)); + + while (git_revwalk_next(&oid, _walk) == 0) { + i++; + } + + /* git log HEAD --oneline | wc -l => 7 */ + cl_assert_equal_i(i, 7); +} + +void test_revwalk_basic__sorted_after_reset(void) +{ + int i = 0; + git_oid oid; + + revwalk_basic_setup_walk(NULL); + + git_oid_fromstr(&oid, commit_head); + + /* push, sort, and test the walk */ + cl_git_pass(git_revwalk_push(_walk, &oid)); + git_revwalk_sorting(_walk, GIT_SORT_TIME); + + cl_git_pass(test_walk_only(_walk, commit_sorting_time, 2)); + + /* reset, push, and test again - we should see all entries */ + git_revwalk_reset(_walk); + cl_git_pass(git_revwalk_push(_walk, &oid)); + + while (git_revwalk_next(&oid, _walk) == 0) + i++; + + cl_assert_equal_i(i, commit_count); +} + +void test_revwalk_basic__push_head_hide_ref(void) +{ + int i = 0; + git_oid oid; + + revwalk_basic_setup_walk(NULL); + + cl_git_pass(git_revwalk_push_head(_walk)); + cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed-test")); + + while (git_revwalk_next(&oid, _walk) == 0) { + i++; + } + + /* git log HEAD --oneline --not refs/heads/packed-test | wc -l => 4 */ + cl_assert_equal_i(i, 4); +} + +void test_revwalk_basic__push_head_hide_ref_nobase(void) +{ + int i = 0; + git_oid oid; + + revwalk_basic_setup_walk(NULL); + + cl_git_pass(git_revwalk_push_head(_walk)); + cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed")); + + while (git_revwalk_next(&oid, _walk) == 0) { + i++; + } + + /* git log HEAD --oneline --not refs/heads/packed | wc -l => 7 */ + cl_assert_equal_i(i, 7); +} + +/* +* $ git rev-list HEAD 5b5b02 ^refs/heads/packed-test +* a65fedf39aefe402d3bb6e24df4d4f5fe4547750 +* be3563ae3f795b2b4353bcce3a527ad0a4f7f644 +* c47800c7266a2be04c571c04d5a6614691ea99bd +* 9fd738e8f7967c078dceed8190330fc8648ee56a + +* $ git log HEAD 5b5b02 --oneline --not refs/heads/packed-test | wc -l => 4 +* a65fedf +* be3563a Merge branch 'br2' +* c47800c branch commit one +* 9fd738e a fourth commit +*/ +void test_revwalk_basic__multiple_push_1(void) +{ + int i = 0; + git_oid oid; + + revwalk_basic_setup_walk(NULL); + + cl_git_pass(git_revwalk_push_head(_walk)); + + cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed-test")); + + cl_git_pass(git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644")); + cl_git_pass(git_revwalk_push(_walk, &oid)); + + while (git_revwalk_next(&oid, _walk) == 0) + i++; + + cl_assert_equal_i(i, 4); +} + +/* +* Difference between test_revwalk_basic__multiple_push_1 and +* test_revwalk_basic__multiple_push_2 is in the order reference +* refs/heads/packed-test and commit 5b5b02 are pushed. +* revwalk should return same commits in both the tests. + +* $ git rev-list 5b5b02 HEAD ^refs/heads/packed-test +* a65fedf39aefe402d3bb6e24df4d4f5fe4547750 +* be3563ae3f795b2b4353bcce3a527ad0a4f7f644 +* c47800c7266a2be04c571c04d5a6614691ea99bd +* 9fd738e8f7967c078dceed8190330fc8648ee56a + +* $ git log 5b5b02 HEAD --oneline --not refs/heads/packed-test | wc -l => 4 +* a65fedf +* be3563a Merge branch 'br2' +* c47800c branch commit one +* 9fd738e a fourth commit +*/ +void test_revwalk_basic__multiple_push_2(void) +{ + int i = 0; + git_oid oid; + + revwalk_basic_setup_walk(NULL); + + cl_git_pass(git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644")); + cl_git_pass(git_revwalk_push(_walk, &oid)); + + cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed-test")); + + cl_git_pass(git_revwalk_push_head(_walk)); + + while (git_revwalk_next(&oid, _walk) == 0) + i++; + + cl_assert_equal_i(i, 4); +} + +void test_revwalk_basic__disallow_non_commit(void) +{ + git_oid oid; + + revwalk_basic_setup_walk(NULL); + + cl_git_pass(git_oid_fromstr(&oid, "521d87c1ec3aef9824daf6d96cc0ae3710766d91")); + cl_git_fail(git_revwalk_push(_walk, &oid)); +} + +void test_revwalk_basic__hide_then_push(void) +{ + git_oid oid; + int i = 0; + + revwalk_basic_setup_walk(NULL); + cl_git_pass(git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644")); + + cl_git_pass(git_revwalk_hide(_walk, &oid)); + cl_git_pass(git_revwalk_push(_walk, &oid)); + + while (git_revwalk_next(&oid, _walk) == 0) + i++; + + cl_assert_equal_i(i, 0); +} + +void test_revwalk_basic__topo_crash(void) +{ + git_oid oid; + git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + + revwalk_basic_setup_walk(NULL); + git_revwalk_sorting(_walk, GIT_SORT_TOPOLOGICAL); + + cl_git_pass(git_revwalk_push(_walk, &oid)); + cl_git_pass(git_revwalk_hide(_walk, &oid)); + + git_revwalk_next(&oid, _walk); +} + +void test_revwalk_basic__from_new_to_old(void) +{ + git_oid from_oid, to_oid, oid; + int i = 0; + + revwalk_basic_setup_walk(NULL); + git_revwalk_sorting(_walk, GIT_SORT_TIME); + + cl_git_pass(git_oid_fromstr(&to_oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644")); + cl_git_pass(git_oid_fromstr(&from_oid, "a4a7dce85cf63874e984719f4fdd239f5145052f")); + + cl_git_pass(git_revwalk_push(_walk, &to_oid)); + cl_git_pass(git_revwalk_hide(_walk, &from_oid)); + + while (git_revwalk_next(&oid, _walk) == 0) + i++; + + cl_assert_equal_i(i, 0); +} + +void test_revwalk_basic__push_range(void) +{ + revwalk_basic_setup_walk(NULL); + + git_revwalk_reset(_walk); + git_revwalk_sorting(_walk, 0); + cl_git_pass(git_revwalk_push_range(_walk, "9fd738e~2..9fd738e")); + cl_git_pass(test_walk_only(_walk, commit_sorting_segment, 2)); +} + +void test_revwalk_basic__push_range_merge_base(void) +{ + revwalk_basic_setup_walk(NULL); + + git_revwalk_reset(_walk); + git_revwalk_sorting(_walk, 0); + cl_git_fail_with(GIT_EINVALIDSPEC, git_revwalk_push_range(_walk, "HEAD...HEAD~2")); +} + +void test_revwalk_basic__push_range_no_range(void) +{ + revwalk_basic_setup_walk(NULL); + + git_revwalk_reset(_walk); + git_revwalk_sorting(_walk, 0); + cl_git_fail_with(GIT_EINVALIDSPEC, git_revwalk_push_range(_walk, "HEAD")); +} + +void test_revwalk_basic__push_mixed(void) +{ + git_oid oid; + int i = 0; + + revwalk_basic_setup_walk(NULL); + + git_revwalk_reset(_walk); + git_revwalk_sorting(_walk, 0); + cl_git_pass(git_revwalk_push_glob(_walk, "tags")); + + while (git_revwalk_next(&oid, _walk) == 0) { + i++; + } + + /* git rev-list --count --glob=tags #=> 9 */ + cl_assert_equal_i(9, i); +} + +void test_revwalk_basic__push_all(void) +{ + git_oid oid; + int i = 0; + + revwalk_basic_setup_walk(NULL); + + git_revwalk_reset(_walk); + git_revwalk_sorting(_walk, 0); + cl_git_pass(git_revwalk_push_glob(_walk, "*")); + + while (git_revwalk_next(&oid, _walk) == 0) { + i++; + } + + /* git rev-list --count --all #=> 15 */ + cl_assert_equal_i(15, i); +} + +/* +* $ git rev-list br2 master e908 +* a65fedf39aefe402d3bb6e24df4d4f5fe4547750 +* e90810b8df3e80c413d903f631643c716887138d +* 6dcf9bf7541ee10456529833502442f385010c3d +* a4a7dce85cf63874e984719f4fdd239f5145052f +* be3563ae3f795b2b4353bcce3a527ad0a4f7f644 +* c47800c7266a2be04c571c04d5a6614691ea99bd +* 9fd738e8f7967c078dceed8190330fc8648ee56a +* 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 +* 5b5b025afb0b4c913b4c338a42934a3863bf3644 +* 8496071c1b46c854b31185ea97743be6a8774479 +*/ + +void test_revwalk_basic__mimic_git_rev_list(void) +{ + git_oid oid; + + revwalk_basic_setup_walk(NULL); + git_revwalk_sorting(_walk, GIT_SORT_TIME); + + cl_git_pass(git_revwalk_push_ref(_walk, "refs/heads/br2")); + cl_git_pass(git_revwalk_push_ref(_walk, "refs/heads/master")); + cl_git_pass(git_oid_fromstr(&oid, "e90810b8df3e80c413d903f631643c716887138d")); + cl_git_pass(git_revwalk_push(_walk, &oid)); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "e90810b8df3e80c413d903f631643c716887138d")); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "6dcf9bf7541ee10456529833502442f385010c3d")); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "a4a7dce85cf63874e984719f4fdd239f5145052f")); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "9fd738e8f7967c078dceed8190330fc8648ee56a")); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644")); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "8496071c1b46c854b31185ea97743be6a8774479")); + + cl_git_fail_with(git_revwalk_next(&oid, _walk), GIT_ITEROVER); +} + +void test_revwalk_basic__big_timestamp(void) +{ + git_reference *head; + git_commit *tip; + git_signature *sig; + git_tree *tree; + git_oid id; + int error; + + revwalk_basic_setup_walk("testrepo.git"); + + cl_git_pass(git_repository_head(&head, _repo)); + cl_git_pass(git_reference_peel((git_object **) &tip, head, GIT_OBJECT_COMMIT)); + + /* Commit with a far-ahead timestamp, we should be able to parse it in the revwalk */ + cl_git_pass(git_signature_new(&sig, "Joe", "joe@example.com", INT64_C(2399662595), 0)); + cl_git_pass(git_commit_tree(&tree, tip)); + + cl_git_pass(git_commit_create(&id, _repo, "HEAD", sig, sig, NULL, "some message", tree, 1, + (const git_commit **)&tip)); + + cl_git_pass(git_revwalk_push_head(_walk)); + + while ((error = git_revwalk_next(&id, _walk)) == 0) { + /* nothing */ + } + + cl_assert_equal_i(GIT_ITEROVER, error); + + git_tree_free(tree); + git_commit_free(tip); + git_reference_free(head); + git_signature_free(sig); + +} + +/* Ensure that we correctly hide a commit that is (timewise) older + * than the commits that we are showing. + * + * % git rev-list 8e73b76..bd75801 + * bd758010071961f28336333bc41e9c64c9a64866 + */ +void test_revwalk_basic__old_hidden_commit_one(void) +{ + git_oid new_id, old_id, oid; + + revwalk_basic_setup_walk("testrepo.git"); + + cl_git_pass(git_oid_fromstr(&new_id, "bd758010071961f28336333bc41e9c64c9a64866")); + cl_git_pass(git_revwalk_push(_walk, &new_id)); + + cl_git_pass(git_oid_fromstr(&old_id, "8e73b769e97678d684b809b163bebdae2911720f")); + cl_git_pass(git_revwalk_hide(_walk, &old_id)); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "bd758010071961f28336333bc41e9c64c9a64866")); + + cl_git_fail_with(GIT_ITEROVER, git_revwalk_next(&oid, _walk)); +} + +/* Ensure that we correctly hide a commit that is (timewise) older + * than the commits that we are showing. + * + * % git rev-list bd75801 ^b91e763 + * bd758010071961f28336333bc41e9c64c9a64866 + */ +void test_revwalk_basic__old_hidden_commit_two(void) +{ + git_oid new_id, old_id, oid; + + revwalk_basic_setup_walk("testrepo.git"); + + cl_git_pass(git_oid_fromstr(&new_id, "bd758010071961f28336333bc41e9c64c9a64866")); + cl_git_pass(git_revwalk_push(_walk, &new_id)); + + cl_git_pass(git_oid_fromstr(&old_id, "b91e763008b10db366442469339f90a2b8400d0a")); + cl_git_pass(git_revwalk_hide(_walk, &old_id)); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "bd758010071961f28336333bc41e9c64c9a64866")); + + cl_git_fail_with(GIT_ITEROVER, git_revwalk_next(&oid, _walk)); +} + +/* + * Ensure that we correctly hide all parent commits of a newer + * commit when first hiding older commits. + * + * % git rev-list D ^B ^A ^E + * 790ba0facf6fd103699a5c40cd19dad277ff49cd + * b82cee5004151ae0c4f82b69fb71b87477664b6f + */ +void test_revwalk_basic__newer_hidden_commit_hides_old_commits(void) +{ + git_oid oid; + + revwalk_basic_setup_walk("revwalk.git"); + + cl_git_pass(git_revwalk_push_ref(_walk, "refs/heads/D")); + cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/B")); + cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/A")); + cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/E")); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(git_oid_streq(&oid, "b82cee5004151ae0c4f82b69fb71b87477664b6f")); + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(git_oid_streq(&oid, "790ba0facf6fd103699a5c40cd19dad277ff49cd")); + + cl_git_fail_with(GIT_ITEROVER, git_revwalk_next(&oid, _walk)); +} diff --git a/tests/libgit2/revwalk/hidecb.c b/tests/libgit2/revwalk/hidecb.c new file mode 100644 index 000000000..54315bc77 --- /dev/null +++ b/tests/libgit2/revwalk/hidecb.c @@ -0,0 +1,230 @@ +#include "clar_libgit2.h" +/* +* a4a7dce [0] Merge branch 'master' into br2 +|\ +| * 9fd738e [1] a fourth commit +| * 4a202b3 [2] a third commit +* | c47800c [3] branch commit one +|/ +* 5b5b025 [5] another commit +* 8496071 [4] testing +*/ +static const char *commit_head = "a4a7dce85cf63874e984719f4fdd239f5145052f"; + +static const char *commit_strs[] = { + "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ + "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */ + "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */ + "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ + "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */ + "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */ +}; + +#define commit_count 6 + +static git_oid commit_ids[commit_count]; +static git_oid _head_id; +static git_repository *_repo; + + +void test_revwalk_hidecb__initialize(void) +{ + int i; + + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_oid_fromstr(&_head_id, commit_head)); + + for (i = 0; i < commit_count; i++) + cl_git_pass(git_oid_fromstr(&commit_ids[i], commit_strs[i])); + +} + +void test_revwalk_hidecb__cleanup(void) +{ + git_repository_free(_repo); + _repo = NULL; +} + +/* Hide all commits */ +static int hide_every_commit_cb(const git_oid *commit_id, void *data) +{ + GIT_UNUSED(commit_id); + GIT_UNUSED(data); + + return 1; +} + +/* Do not hide anything */ +static int hide_none_cb(const git_oid *commit_id, void *data) +{ + GIT_UNUSED(commit_id); + GIT_UNUSED(data); + + return 0; +} + +/* Hide some commits */ +static int hide_commit_cb(const git_oid *commit_id, void *data) +{ + GIT_UNUSED(commit_id); + GIT_UNUSED(data); + + return (git_oid_cmp(commit_id, &commit_ids[5]) == 0); +} + +/* In payload data, pointer to a commit id is passed */ +static int hide_commit_use_payload_cb(const git_oid *commit_id, void *data) +{ + git_oid *hide_commit_id = data; + + return (git_oid_cmp(commit_id, hide_commit_id) == 0); +} + +void test_revwalk_hidecb__hide_all_cb(void) +{ + git_revwalk *walk; + git_oid id; + + cl_git_pass(git_revwalk_new(&walk, _repo)); + cl_git_pass(git_revwalk_add_hide_cb(walk, hide_every_commit_cb, NULL)); + cl_git_pass(git_revwalk_push(walk, &_head_id)); + + /* First call to git_revwalk_next should return GIT_ITEROVER */ + cl_assert_equal_i(GIT_ITEROVER, git_revwalk_next(&id, walk)); + + git_revwalk_free(walk); +} + + +void test_revwalk_hidecb__hide_none_cb(void) +{ + git_revwalk *walk; + int i, error; + git_oid id; + + cl_git_pass(git_revwalk_new(&walk, _repo)); + cl_git_pass(git_revwalk_add_hide_cb(walk, hide_none_cb, NULL)); + cl_git_pass(git_revwalk_push(walk, &_head_id)); + + /* It should return all 6 commits */ + i = 0; + while ((error = git_revwalk_next(&id, walk)) == 0) + i++; + + cl_assert_equal_i(i, 6); + cl_assert_equal_i(error, GIT_ITEROVER); + + git_revwalk_free(walk); +} + +void test_revwalk_hidecb__unset_cb_before_walk(void) +{ + git_revwalk *walk; + git_oid id; + int i, error; + + cl_git_pass(git_revwalk_new(&walk, _repo)); + cl_git_pass(git_revwalk_add_hide_cb(walk, hide_every_commit_cb, NULL)); + cl_git_pass(git_revwalk_add_hide_cb(walk, NULL, NULL)); + cl_git_pass(git_revwalk_push(walk, &_head_id)); + + /* It should return all 6 commits */ + i = 0; + while ((error = git_revwalk_next(&id, walk)) == 0) + i++; + + cl_assert_equal_i(i, 6); + cl_assert_equal_i(error, GIT_ITEROVER); + + git_revwalk_free(walk); +} + +void test_revwalk_hidecb__change_cb_before_walk(void) +{ + git_revwalk *walk; + git_oid id; + + cl_git_pass(git_revwalk_new(&walk, _repo)); + cl_git_pass(git_revwalk_add_hide_cb(walk, hide_none_cb, NULL)); + cl_git_pass(git_revwalk_add_hide_cb(walk, hide_every_commit_cb, NULL)); + cl_git_pass(git_revwalk_push(walk, &_head_id)); + + /* First call to git_revwalk_next should return GIT_ITEROVER */ + cl_assert_equal_i(GIT_ITEROVER, git_revwalk_next(&id, walk)); + + git_revwalk_free(walk); +} + +void test_revwalk_hidecb__add_hide_cb_during_walking(void) +{ + git_revwalk *walk; + git_oid id; + int error; + + cl_git_pass(git_revwalk_new(&walk, _repo)); + cl_git_pass(git_revwalk_push(walk, &_head_id)); + + /* Start walking without adding hide callback */ + cl_git_pass(git_revwalk_next(&id, walk)); + + /* Now add hide callback */ + cl_git_pass(git_revwalk_add_hide_cb(walk, hide_none_cb, NULL)); + + /* walk should be reset */ + error = git_revwalk_next(&id, walk); + cl_assert_equal_i(error, GIT_ITEROVER); + + git_revwalk_free(walk); +} + +void test_revwalk_hidecb__hide_some_commits(void) +{ + git_revwalk *walk; + git_oid id; + int i, error; + + cl_git_pass(git_revwalk_new(&walk, _repo)); + cl_git_pass(git_revwalk_push(walk, &_head_id)); + git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL); + + /* Add hide callback */ + cl_git_pass(git_revwalk_add_hide_cb(walk, hide_commit_cb, NULL)); + + i = 0; + while ((error = git_revwalk_next(&id, walk)) == 0) { + cl_assert_equal_oid(&commit_ids[i], &id); + i++; + } + + cl_assert_equal_i(i, 4); + cl_assert_equal_i(error, GIT_ITEROVER); + + git_revwalk_free(walk); +} + +void test_revwalk_hidecb__test_payload(void) +{ + git_revwalk *walk; + git_oid id; + int i, error; + + cl_git_pass(git_revwalk_new(&walk, _repo)); + cl_git_pass(git_revwalk_push(walk, &_head_id)); + git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL); + + /* Add hide callback, pass id of parent of initial commit as payload data */ + cl_git_pass(git_revwalk_add_hide_cb(walk, hide_commit_use_payload_cb, &commit_ids[5])); + + i = 0; + while ((error = git_revwalk_next(&id, walk)) == 0) { + cl_assert_equal_oid(&commit_ids[i], &id); + i++; + } + + /* walker should return four commits */ + cl_assert_equal_i(i, 4); + cl_assert_equal_i(error, GIT_ITEROVER); + + git_revwalk_free(walk); +} + diff --git a/tests/libgit2/revwalk/mergebase.c b/tests/libgit2/revwalk/mergebase.c new file mode 100644 index 000000000..0378c869b --- /dev/null +++ b/tests/libgit2/revwalk/mergebase.c @@ -0,0 +1,514 @@ +#include "clar_libgit2.h" +#include "vector.h" +#include + +static git_repository *_repo; +static git_repository *_repo2; + +void test_revwalk_mergebase__initialize(void) +{ + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_repository_open(&_repo2, cl_fixture("twowaymerge.git"))); +} + +void test_revwalk_mergebase__cleanup(void) +{ + git_repository_free(_repo); + _repo = NULL; + + git_repository_free(_repo2); + _repo2 = NULL; +} + +void test_revwalk_mergebase__single1(void) +{ + git_oid result, one, two, expected; + size_t ahead, behind; + + cl_git_pass(git_oid_fromstr(&one, "c47800c7266a2be04c571c04d5a6614691ea99bd ")); + cl_git_pass(git_oid_fromstr(&two, "9fd738e8f7967c078dceed8190330fc8648ee56a")); + cl_git_pass(git_oid_fromstr(&expected, "5b5b025afb0b4c913b4c338a42934a3863bf3644")); + + cl_git_pass(git_merge_base(&result, _repo, &one, &two)); + cl_assert_equal_oid(&expected, &result); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &one, &two)); + cl_assert_equal_sz(ahead, 1); + cl_assert_equal_sz(behind, 2); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &two, &one)); + cl_assert_equal_sz(ahead, 2); + cl_assert_equal_sz(behind, 1); +} + +void test_revwalk_mergebase__single2(void) +{ + git_oid result, one, two, expected; + size_t ahead, behind; + + cl_git_pass(git_oid_fromstr(&one, "763d71aadf09a7951596c9746c024e7eece7c7af")); + cl_git_pass(git_oid_fromstr(&two, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); + cl_git_pass(git_oid_fromstr(&expected, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + + cl_git_pass(git_merge_base(&result, _repo, &one, &two)); + cl_assert_equal_oid(&expected, &result); + + cl_git_pass(git_graph_ahead_behind( &ahead, &behind, _repo, &one, &two)); + cl_assert_equal_sz(ahead, 1); + cl_assert_equal_sz(behind, 4); + + cl_git_pass(git_graph_ahead_behind( &ahead, &behind, _repo, &two, &one)); + cl_assert_equal_sz(ahead, 4); + cl_assert_equal_sz(behind, 1); +} + +void test_revwalk_mergebase__merged_branch(void) +{ + git_oid result, one, two, expected; + size_t ahead, behind; + + cl_git_pass(git_oid_fromstr(&one, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); + cl_git_pass(git_oid_fromstr(&two, "9fd738e8f7967c078dceed8190330fc8648ee56a")); + cl_git_pass(git_oid_fromstr(&expected, "9fd738e8f7967c078dceed8190330fc8648ee56a")); + + cl_git_pass(git_merge_base(&result, _repo, &one, &two)); + cl_assert_equal_oid(&expected, &result); + + cl_git_pass(git_merge_base(&result, _repo, &two, &one)); + cl_assert_equal_oid(&expected, &result); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &one, &two)); + cl_assert_equal_sz(ahead, 3); + cl_assert_equal_sz(behind, 0); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &two, &one)); + cl_assert_equal_sz(ahead, 0); + cl_assert_equal_sz(behind, 3); +} + +void test_revwalk_mergebase__two_way_merge(void) +{ + git_oid one, two; + size_t ahead, behind; + + cl_git_pass(git_oid_fromstr(&one, "9b219343610c88a1187c996d0dc58330b55cee28")); + cl_git_pass(git_oid_fromstr(&two, "a953a018c5b10b20c86e69fef55ebc8ad4c5a417")); + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo2, &one, &two)); + + cl_assert_equal_sz(ahead, 8); + cl_assert_equal_sz(behind, 2); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo2, &two, &one)); + + cl_assert_equal_sz(ahead, 2); + cl_assert_equal_sz(behind, 8); +} + +void test_revwalk_mergebase__no_common_ancestor_returns_ENOTFOUND(void) +{ + git_oid result, one, two; + size_t ahead, behind; + int error; + + cl_git_pass(git_oid_fromstr(&one, "763d71aadf09a7951596c9746c024e7eece7c7af")); + cl_git_pass(git_oid_fromstr(&two, "e90810b8df3e80c413d903f631643c716887138d")); + + error = git_merge_base(&result, _repo, &one, &two); + cl_git_fail(error); + + cl_assert_equal_i(GIT_ENOTFOUND, error); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &one, &two)); + cl_assert_equal_sz(4, ahead); + cl_assert_equal_sz(2, behind); +} + +void test_revwalk_mergebase__prefer_youngest_merge_base(void) +{ + git_oid result, one, two, expected; + + cl_git_pass(git_oid_fromstr(&one, "a4a7dce85cf63874e984719f4fdd239f5145052f")); + cl_git_pass(git_oid_fromstr(&two, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_oid_fromstr(&expected, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + + cl_git_pass(git_merge_base(&result, _repo, &one, &two)); + cl_assert_equal_oid(&expected, &result); +} + +void test_revwalk_mergebase__multiple_merge_bases(void) +{ + git_oid one, two, expected1, expected2; + git_oidarray result = {NULL, 0}; + + cl_git_pass(git_oid_fromstr(&one, "a4a7dce85cf63874e984719f4fdd239f5145052f")); + cl_git_pass(git_oid_fromstr(&two, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_oid_fromstr(&expected1, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + cl_git_pass(git_oid_fromstr(&expected2, "9fd738e8f7967c078dceed8190330fc8648ee56a")); + + cl_git_pass(git_merge_bases(&result, _repo, &one, &two)); + cl_assert_equal_i(2, result.count); + cl_assert_equal_oid(&expected1, &result.ids[0]); + cl_assert_equal_oid(&expected2, &result.ids[1]); + + git_oidarray_dispose(&result); +} + +void test_revwalk_mergebase__multiple_merge_bases_many_commits(void) +{ + git_oid expected1, expected2; + git_oidarray result = {NULL, 0}; + + git_oid *input = git__malloc(sizeof(git_oid) * 2); + + cl_git_pass(git_oid_fromstr(&input[0], "a4a7dce85cf63874e984719f4fdd239f5145052f")); + cl_git_pass(git_oid_fromstr(&input[1], "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_oid_fromstr(&expected1, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + cl_git_pass(git_oid_fromstr(&expected2, "9fd738e8f7967c078dceed8190330fc8648ee56a")); + + cl_git_pass(git_merge_bases_many(&result, _repo, 2, input)); + cl_assert_equal_i(2, result.count); + cl_assert_equal_oid(&expected1, &result.ids[0]); + cl_assert_equal_oid(&expected2, &result.ids[1]); + + git_oidarray_dispose(&result); + git__free(input); +} + +void test_revwalk_mergebase__no_off_by_one_missing(void) +{ + git_oid result, one, two; + + cl_git_pass(git_oid_fromstr(&one, "1a443023183e3f2bfbef8ac923cd81c1018a18fd")); + cl_git_pass(git_oid_fromstr(&two, "9f13f7d0a9402c681f91dc590cf7b5470e6a77d2")); + cl_git_pass(git_merge_base(&result, _repo, &one, &two)); +} + +static void assert_mergebase_many(const char *expected_sha, int count, ...) +{ + va_list ap; + int i; + git_oid *oids; + git_oid oid, expected; + char *partial_oid; + git_object *object; + + oids = git__malloc(count * sizeof(git_oid)); + cl_assert(oids != NULL); + + memset(oids, 0x0, count * sizeof(git_oid)); + + va_start(ap, count); + + for (i = 0; i < count; ++i) { + partial_oid = va_arg(ap, char *); + cl_git_pass(git_oid_fromstrn(&oid, partial_oid, strlen(partial_oid))); + + cl_git_pass(git_object_lookup_prefix(&object, _repo, &oid, strlen(partial_oid), GIT_OBJECT_COMMIT)); + git_oid_cpy(&oids[i], git_object_id(object)); + git_object_free(object); + } + + va_end(ap); + + if (expected_sha == NULL) + cl_assert_equal_i(GIT_ENOTFOUND, git_merge_base_many(&oid, _repo, count, oids)); + else { + cl_git_pass(git_merge_base_many(&oid, _repo, count, oids)); + cl_git_pass(git_oid_fromstr(&expected, expected_sha)); + + cl_assert_equal_oid(&expected, &oid); + } + + git__free(oids); +} + +void test_revwalk_mergebase__many_no_common_ancestor_returns_ENOTFOUND(void) +{ + assert_mergebase_many(NULL, 3, "41bc8c", "e90810", "a65fed"); + assert_mergebase_many(NULL, 3, "e90810", "41bc8c", "a65fed"); + assert_mergebase_many(NULL, 3, "e90810", "a65fed", "41bc8c"); + assert_mergebase_many(NULL, 3, "a65fed", "e90810", "41bc8c"); + assert_mergebase_many(NULL, 3, "a65fed", "41bc8c", "e90810"); + + assert_mergebase_many(NULL, 3, "e90810", "763d71", "a65fed"); +} + +void test_revwalk_mergebase__many_merge_branch(void) +{ + assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "a65fed", "763d71", "849607"); + + assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "763d71", "e90810", "a65fed"); + assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "763d71", "a65fed", "e90810"); + + assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "a65fed", "763d71", "849607"); + assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "a65fed", "849607", "763d71"); + assert_mergebase_many("8496071c1b46c854b31185ea97743be6a8774479", 3, "849607", "a65fed", "763d71"); + + assert_mergebase_many("5b5b025afb0b4c913b4c338a42934a3863bf3644", 5, "5b5b02", "763d71", "a4a7dc", "a65fed", "41bc8c"); +} + +static void assert_mergebase_octopus(const char *expected_sha, int count, ...) +{ + va_list ap; + int i; + git_oid *oids; + git_oid oid, expected; + char *partial_oid; + git_object *object; + + oids = git__malloc(count * sizeof(git_oid)); + cl_assert(oids != NULL); + + memset(oids, 0x0, count * sizeof(git_oid)); + + va_start(ap, count); + + for (i = 0; i < count; ++i) { + partial_oid = va_arg(ap, char *); + cl_git_pass(git_oid_fromstrn(&oid, partial_oid, strlen(partial_oid))); + + cl_git_pass(git_object_lookup_prefix(&object, _repo, &oid, strlen(partial_oid), GIT_OBJECT_COMMIT)); + git_oid_cpy(&oids[i], git_object_id(object)); + git_object_free(object); + } + + va_end(ap); + + if (expected_sha == NULL) + cl_assert_equal_i(GIT_ENOTFOUND, git_merge_base_octopus(&oid, _repo, count, oids)); + else { + cl_git_pass(git_merge_base_octopus(&oid, _repo, count, oids)); + cl_git_pass(git_oid_fromstr(&expected, expected_sha)); + + cl_assert_equal_oid(&expected, &oid); + } + + git__free(oids); +} + +void test_revwalk_mergebase__octopus_no_common_ancestor_returns_ENOTFOUND(void) +{ + assert_mergebase_octopus(NULL, 3, "41bc8c", "e90810", "a65fed"); + assert_mergebase_octopus(NULL, 3, "e90810", "41bc8c", "a65fed"); + assert_mergebase_octopus(NULL, 3, "e90810", "a65fed", "41bc8c"); + assert_mergebase_octopus(NULL, 3, "a65fed", "e90810", "41bc8c"); + assert_mergebase_octopus(NULL, 3, "a65fed", "41bc8c", "e90810"); + + assert_mergebase_octopus(NULL, 3, "e90810", "763d71", "a65fed"); + + assert_mergebase_octopus(NULL, 3, "763d71", "e90810", "a65fed"); + assert_mergebase_octopus(NULL, 3, "763d71", "a65fed", "e90810"); + + assert_mergebase_octopus(NULL, 5, "5b5b02", "763d71", "a4a7dc", "a65fed", "41bc8c"); +} + +void test_revwalk_mergebase__octopus_merge_branch(void) +{ + assert_mergebase_octopus("8496071c1b46c854b31185ea97743be6a8774479", 3, "a65fed", "763d71", "849607"); + + assert_mergebase_octopus("8496071c1b46c854b31185ea97743be6a8774479", 3, "a65fed", "763d71", "849607"); + assert_mergebase_octopus("8496071c1b46c854b31185ea97743be6a8774479", 3, "a65fed", "849607", "763d71"); + assert_mergebase_octopus("8496071c1b46c854b31185ea97743be6a8774479", 3, "849607", "a65fed", "763d71"); +} + +/* + * testrepo.git $ git log --graph --all + * * commit 763d71aadf09a7951596c9746c024e7eece7c7af + * | Author: nulltoken + * | Date: Sun Oct 9 12:54:47 2011 +0200 + * | + * | Add some files into subdirectories + * | + * | * commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750 + * | | Author: Scott Chacon + * | | Date: Tue Aug 9 19:33:46 2011 -0700 + * | | + * | * commit be3563ae3f795b2b4353bcce3a527ad0a4f7f644 + * | |\ Merge: 9fd738e c47800c + * | |/ Author: Scott Chacon + * |/| Date: Tue May 25 11:58:27 2010 -0700 + * | | + * | | Merge branch 'br2' + * | | + * | | * commit e90810b8df3e80c413d903f631643c716887138d + * | | | Author: Vicent Marti + * | | | Date: Thu Aug 5 18:42:20 2010 +0200 + * | | | + * | | | Test commit 2 + * | | | + * | | * commit 6dcf9bf7541ee10456529833502442f385010c3d + * | | Author: Vicent Marti + * | | Date: Thu Aug 5 18:41:33 2010 +0200 + * | | + * | | Test commit 1 + * | | + * | | * commit a4a7dce85cf63874e984719f4fdd239f5145052f + * | | |\ Merge: c47800c 9fd738e + * | |/ / Author: Scott Chacon + * |/| / Date: Tue May 25 12:00:23 2010 -0700 + * | |/ + * | | Merge branch 'master' into br2 + * | | + * | * commit 9fd738e8f7967c078dceed8190330fc8648ee56a + * | | Author: Scott Chacon + * | | Date: Mon May 24 10:19:19 2010 -0700 + * | | + * | | a fourth commit + * | | + * | * commit 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 + * | | Author: Scott Chacon + * | | Date: Mon May 24 10:19:04 2010 -0700 + * | | + * | | a third commit + * | | + * * | commit c47800c7266a2be04c571c04d5a6614691ea99bd + * |/ Author: Scott Chacon + * | Date: Tue May 25 11:58:14 2010 -0700 + * | + * | branch commit one + * | + * * commit 5b5b025afb0b4c913b4c338a42934a3863bf3644 + * | Author: Scott Chacon + * | Date: Tue May 11 13:38:42 2010 -0700 + * | + * | another commit + * | + * * commit 8496071c1b46c854b31185ea97743be6a8774479 + * Author: Scott Chacon + * Date: Sat May 8 16:13:06 2010 -0700 + * + * testing + * + * * commit 41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9 + * | Author: Scott Chacon + * | Date: Tue May 11 13:40:41 2010 -0700 + * | + * | packed commit two + * | + * * commit 5001298e0c09ad9c34e4249bc5801c75e9754fa5 + * Author: Scott Chacon + * Date: Tue May 11 13:40:23 2010 -0700 + * + * packed commit one + */ + +/* + * twowaymerge.git $ git log --graph --all + * * commit 9b219343610c88a1187c996d0dc58330b55cee28 + * |\ Merge: c37a783 2224e19 + * | | Author: Scott J. Goldman + * | | Date: Tue Nov 27 20:31:04 2012 -0800 + * | | + * | | Merge branch 'first-branch' into second-branch + * | | + * | * commit 2224e191514cb4bd8c566d80dac22dfcb1e9bb83 + * | | Author: Scott J. Goldman + * | | Date: Tue Nov 27 20:28:51 2012 -0800 + * | | + * | | j + * | | + * | * commit a41a49f8f5cd9b6cb14a076bf8394881ed0b4d19 + * | | Author: Scott J. Goldman + * | | Date: Tue Nov 27 20:28:39 2012 -0800 + * | | + * | | i + * | | + * | * commit 82bf9a1a10a4b25c1f14c9607b60970705e92545 + * | | Author: Scott J. Goldman + * | | Date: Tue Nov 27 20:28:28 2012 -0800 + * | | + * | | h + * | | + * * | commit c37a783c20d92ac92362a78a32860f7eebf938ef + * | | Author: Scott J. Goldman + * | | Date: Tue Nov 27 20:30:57 2012 -0800 + * | | + * | | n + * | | + * * | commit 8b82fb1794cb1c8c7f172ec730a4c2db0ae3e650 + * | | Author: Scott J. Goldman + * | | Date: Tue Nov 27 20:30:43 2012 -0800 + * | | + * | | m + * | | + * * | commit 6ab5d28acbf3c3bdff276f7ccfdf29c1520e542f + * | | Author: Scott J. Goldman + * | | Date: Tue Nov 27 20:30:38 2012 -0800 + * | | + * | | l + * | | + * * | commit 7b8c336c45fc6895c1c60827260fe5d798e5d247 + * | | Author: Scott J. Goldman + * | | Date: Tue Nov 27 20:30:24 2012 -0800 + * | | + * | | k + * | | + * | | * commit 1c30b88f5f3ee66d78df6520a7de9e89b890818b + * | | | Author: Scott J. Goldman + * | | | Date: Tue Nov 27 20:28:10 2012 -0800 + * | | | + * | | | e + * | | | + * | | * commit 42b7311aa626e712891940c1ec5d5cba201946a4 + * | | | Author: Scott J. Goldman + * | | | Date: Tue Nov 27 20:28:06 2012 -0800 + * | | | + * | | | d + * | | | + * | | * commit a953a018c5b10b20c86e69fef55ebc8ad4c5a417 + * | | |\ Merge: bd1732c cdf97fd + * | | |/ Author: Scott J. Goldman + * | |/| Date: Tue Nov 27 20:26:43 2012 -0800 + * | | | + * | | | Merge branch 'first-branch' + * | | | + * | * | commit cdf97fd3bb48eb3827638bb33d208f5fd32d0aa6 + * | | | Author: Scott J. Goldman + * | | | Date: Tue Nov 27 20:24:46 2012 -0800 + * | | | + * | | | g + * | | | + * | * | commit ef0488f0b722f0be8bcb90a7730ac7efafd1d694 + * | | | Author: Scott J. Goldman + * | | | Date: Tue Nov 27 20:24:39 2012 -0800 + * | | | + * | | | f + * | | | + * | | * commit bd1732c43c68d712ad09e1d872b9be6d4b9efdc4 + * | |/ Author: Scott J. Goldman + * | | Date: Tue Nov 27 17:43:58 2012 -0800 + * | | + * | | c + * | | + * | * commit 0c8a3f1f3d5f421cf83048c7c73ee3b55a5e0f29 + * |/ Author: Scott J. Goldman + * | Date: Tue Nov 27 17:43:48 2012 -0800 + * | + * | b + * | + * * commit 1f4c0311a24b63f6fc209a59a1e404942d4a5006 + * Author: Scott J. Goldman + * Date: Tue Nov 27 17:43:41 2012 -0800 + * + * a + */ + +void test_revwalk_mergebase__remove_redundant(void) +{ + git_repository *repo; + git_oid one, two, base; + git_oidarray result = {NULL, 0}; + + cl_git_pass(git_repository_open(&repo, cl_fixture("redundant.git"))); + + cl_git_pass(git_oid_fromstr(&one, "d89137c93ba1ee749214ff4ce52ae9137bc833f9")); + cl_git_pass(git_oid_fromstr(&two, "91f4b95df4a59504a9813ba66912562931d990e3")); + cl_git_pass(git_oid_fromstr(&base, "6cb1f2352d974e1c5a776093017e8772416ac97a")); + + cl_git_pass(git_merge_bases(&result, repo, &one, &two)); + cl_assert_equal_i(1, result.count); + cl_assert_equal_oid(&base, &result.ids[0]); + + git_oidarray_dispose(&result); + git_repository_free(repo); +} diff --git a/tests/libgit2/revwalk/signatureparsing.c b/tests/libgit2/revwalk/signatureparsing.c new file mode 100644 index 000000000..b312bad09 --- /dev/null +++ b/tests/libgit2/revwalk/signatureparsing.c @@ -0,0 +1,47 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; +static git_revwalk *_walk; + +void test_revwalk_signatureparsing__initialize(void) +{ + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_revwalk_new(&_walk, _repo)); +} + +void test_revwalk_signatureparsing__cleanup(void) +{ + git_revwalk_free(_walk); + _walk = NULL; + + git_repository_free(_repo); + _repo = NULL; +} + +void test_revwalk_signatureparsing__do_not_choke_when_name_contains_angle_brackets(void) +{ + git_reference *ref; + git_oid commit_oid; + git_commit *commit; + const git_signature *signature; + + /* + * The branch below points at a commit with angle brackets in the committer/author name + * committer 1323847743 +0100 + */ + cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/haacked")); + + git_revwalk_push(_walk, git_reference_target(ref)); + cl_git_pass(git_revwalk_next(&commit_oid, _walk)); + + cl_git_pass(git_commit_lookup(&commit, _repo, git_reference_target(ref))); + + signature = git_commit_committer(commit); + cl_assert_equal_s("foo@example.com", signature->email); + cl_assert_equal_s("Yu V. Bin Haacked", signature->name); + cl_assert_equal_i(1323847743, (int)signature->when.time); + cl_assert_equal_i(60, signature->when.offset); + + git_commit_free(commit); + git_reference_free(ref); +} diff --git a/tests/libgit2/revwalk/simplify.c b/tests/libgit2/revwalk/simplify.c new file mode 100644 index 000000000..6dd068a42 --- /dev/null +++ b/tests/libgit2/revwalk/simplify.c @@ -0,0 +1,56 @@ +#include "clar_libgit2.h" + +void test_revwalk_simplify__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +/* + * a4a7dce [0] Merge branch 'master' into br2 + |\ + | * 9fd738e [1] a fourth commit + | * 4a202b3 [2] a third commit + * | c47800c [3] branch commit one + |/ + * 5b5b025 [5] another commit + * 8496071 [4] testing +*/ +static const char *commit_head = "a4a7dce85cf63874e984719f4fdd239f5145052f"; + +static const char *expected_str[] = { + "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ + "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ + "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 4 */ + "8496071c1b46c854b31185ea97743be6a8774479", /* 5 */ +}; + +void test_revwalk_simplify__first_parent(void) +{ + git_repository *repo; + git_revwalk *walk; + git_oid id, expected[4]; + int i, error; + + for (i = 0; i < 4; i++) { + git_oid_fromstr(&expected[i], expected_str[i]); + } + + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_revwalk_new(&walk, repo)); + + git_oid_fromstr(&id, commit_head); + cl_git_pass(git_revwalk_push(walk, &id)); + git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL); + git_revwalk_simplify_first_parent(walk); + + i = 0; + while ((error = git_revwalk_next(&id, walk)) == 0) { + cl_assert_equal_oid(&expected[i], &id); + i++; + } + + cl_assert_equal_i(i, 4); + cl_assert_equal_i(error, GIT_ITEROVER); + + git_revwalk_free(walk); +} diff --git a/tests/libgit2/stash/apply.c b/tests/libgit2/stash/apply.c new file mode 100644 index 000000000..5125ae639 --- /dev/null +++ b/tests/libgit2/stash/apply.c @@ -0,0 +1,449 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "stash_helpers.h" + +static git_signature *signature; +static git_repository *repo; +static git_index *repo_index; + +void test_stash_apply__initialize(void) +{ + git_oid oid; + + repo = cl_git_sandbox_init_new("stash"); + cl_git_pass(git_repository_index(&repo_index, repo)); + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ + + cl_git_mkfile("stash/what", "hello\n"); + cl_git_mkfile("stash/how", "small\n"); + cl_git_mkfile("stash/who", "world\n"); + cl_git_mkfile("stash/where", "meh\n"); + + cl_git_pass(git_index_add_bypath(repo_index, "what")); + cl_git_pass(git_index_add_bypath(repo_index, "how")); + cl_git_pass(git_index_add_bypath(repo_index, "who")); + + cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit"); + + cl_git_rewritefile("stash/what", "goodbye\n"); + cl_git_rewritefile("stash/who", "funky world\n"); + cl_git_mkfile("stash/when", "tomorrow\n"); + cl_git_mkfile("stash/why", "would anybody use stash?\n"); + cl_git_mkfile("stash/where", "????\n"); + + cl_git_pass(git_index_add_bypath(repo_index, "who")); + cl_git_pass(git_index_add_bypath(repo_index, "why")); + cl_git_pass(git_index_add_bypath(repo_index, "where")); + cl_git_pass(git_index_write(repo_index)); + + cl_git_rewritefile("stash/where", "....\n"); + + /* Pre-stash state */ + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); + assert_status(repo, "where", GIT_STATUS_INDEX_NEW|GIT_STATUS_WT_MODIFIED); + + cl_git_pass(git_stash_save(&oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + + /* Post-stash state */ + assert_status(repo, "what", GIT_STATUS_CURRENT); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_ENOTFOUND); + assert_status(repo, "why", GIT_ENOTFOUND); + assert_status(repo, "where", GIT_ENOTFOUND); +} + +void test_stash_apply__cleanup(void) +{ + git_signature_free(signature); + signature = NULL; + + git_index_free(repo_index); + repo_index = NULL; + + cl_git_sandbox_cleanup(); +} + +void test_stash_apply__with_default(void) +{ + git_str where = GIT_STR_INIT; + + cl_git_pass(git_stash_apply(repo, 0, NULL)); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); + assert_status(repo, "where", GIT_STATUS_INDEX_NEW); + + cl_git_pass(git_futils_readbuffer(&where, "stash/where")); + cl_assert_equal_s("....\n", where.ptr); + + git_str_dispose(&where); +} + +void test_stash_apply__with_existing_file(void) +{ + cl_git_mkfile("stash/where", "oops!\n"); + cl_git_fail(git_stash_apply(repo, 0, NULL)); +} + +void test_stash_apply__merges_new_file(void) +{ + const git_index_entry *ancestor, *our, *their; + + cl_git_mkfile("stash/where", "committed before stash\n"); + cl_git_pass(git_index_add_bypath(repo_index, "where")); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); + + cl_git_pass(git_stash_apply(repo, 0, NULL)); + + cl_assert_equal_i(1, git_index_has_conflicts(repo_index)); + assert_status(repo, "what", GIT_STATUS_INDEX_MODIFIED); + cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "where")); /* unmerged */ + assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); +} + +void test_stash_apply__with_reinstate_index(void) +{ + git_str where = GIT_STR_INIT; + git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; + + opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX; + + cl_git_pass(git_stash_apply(repo, 0, &opts)); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); + assert_status(repo, "where", GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_MODIFIED); + + cl_git_pass(git_futils_readbuffer(&where, "stash/where")); + cl_assert_equal_s("....\n", where.ptr); + + git_str_dispose(&where); +} + +void test_stash_apply__conflict_index_with_default(void) +{ + const git_index_entry *ancestor; + const git_index_entry *our; + const git_index_entry *their; + + cl_git_rewritefile("stash/who", "nothing\n"); + cl_git_pass(git_index_add_bypath(repo_index, "who")); + cl_git_pass(git_index_write(repo_index)); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); + + cl_git_pass(git_stash_apply(repo, 0, NULL)); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 1); + assert_status(repo, "what", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "how", GIT_STATUS_CURRENT); + cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "who")); /* unmerged */ + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); +} + +void test_stash_apply__conflict_index_with_reinstate_index(void) +{ + git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; + + opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX; + + cl_git_rewritefile("stash/who", "nothing\n"); + cl_git_pass(git_index_add_bypath(repo_index, "who")); + cl_git_pass(git_index_write(repo_index)); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); + + cl_git_fail_with(git_stash_apply(repo, 0, &opts), GIT_ECONFLICT); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_CURRENT); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_ENOTFOUND); + assert_status(repo, "why", GIT_ENOTFOUND); +} + +void test_stash_apply__conflict_untracked_with_default(void) +{ + git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; + + cl_git_mkfile("stash/when", "nothing\n"); + + cl_git_fail_with(git_stash_apply(repo, 0, &opts), GIT_ECONFLICT); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_CURRENT); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_ENOTFOUND); +} + +void test_stash_apply__conflict_untracked_with_reinstate_index(void) +{ + git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; + + opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX; + + cl_git_mkfile("stash/when", "nothing\n"); + + cl_git_fail_with(git_stash_apply(repo, 0, &opts), GIT_ECONFLICT); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_CURRENT); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_ENOTFOUND); +} + +void test_stash_apply__conflict_workdir_with_default(void) +{ + cl_git_rewritefile("stash/what", "ciao\n"); + + cl_git_fail_with(git_stash_apply(repo, 0, NULL), GIT_ECONFLICT); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_ENOTFOUND); +} + +void test_stash_apply__conflict_workdir_with_reinstate_index(void) +{ + git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; + + opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX; + + cl_git_rewritefile("stash/what", "ciao\n"); + + cl_git_fail_with(git_stash_apply(repo, 0, &opts), GIT_ECONFLICT); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_ENOTFOUND); +} + +void test_stash_apply__conflict_commit_with_default(void) +{ + const git_index_entry *ancestor; + const git_index_entry *our; + const git_index_entry *their; + + cl_git_rewritefile("stash/what", "ciao\n"); + cl_git_pass(git_index_add_bypath(repo_index, "what")); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); + + cl_git_pass(git_stash_apply(repo, 0, NULL)); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 1); + cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "what")); /* unmerged */ + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); +} + +void test_stash_apply__conflict_commit_with_reinstate_index(void) +{ + git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; + const git_index_entry *ancestor; + const git_index_entry *our; + const git_index_entry *their; + + opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX; + + cl_git_rewritefile("stash/what", "ciao\n"); + cl_git_pass(git_index_add_bypath(repo_index, "what")); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); + + cl_git_pass(git_stash_apply(repo, 0, &opts)); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 1); + cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "what")); /* unmerged */ + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); +} + +void test_stash_apply__fails_with_uncommitted_changes_in_index(void) +{ + cl_git_rewritefile("stash/who", "nothing\n"); + cl_git_pass(git_index_add_bypath(repo_index, "who")); + cl_git_pass(git_index_write(repo_index)); + + cl_git_fail_with(git_stash_apply(repo, 0, NULL), GIT_EUNCOMMITTED); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_CURRENT); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "when", GIT_ENOTFOUND); + assert_status(repo, "why", GIT_ENOTFOUND); +} + +void test_stash_apply__pop(void) +{ + cl_git_pass(git_stash_pop(repo, 0, NULL)); + + cl_git_fail_with(git_stash_pop(repo, 0, NULL), GIT_ENOTFOUND); +} + +struct seen_paths { + bool what; + bool how; + bool who; + bool when; +}; + +static int checkout_notify( + git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload) +{ + struct seen_paths *seen_paths = (struct seen_paths *)payload; + + GIT_UNUSED(why); + GIT_UNUSED(baseline); + GIT_UNUSED(target); + GIT_UNUSED(workdir); + + if (strcmp(path, "what") == 0) + seen_paths->what = 1; + else if (strcmp(path, "how") == 0) + seen_paths->how = 1; + else if (strcmp(path, "who") == 0) + seen_paths->who = 1; + else if (strcmp(path, "when") == 0) + seen_paths->when = 1; + + return 0; +} + +void test_stash_apply__executes_notify_cb(void) +{ + git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; + struct seen_paths seen_paths = {0}; + + opts.checkout_options.notify_cb = checkout_notify; + opts.checkout_options.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; + opts.checkout_options.notify_payload = &seen_paths; + + cl_git_pass(git_stash_apply(repo, 0, &opts)); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); + assert_status(repo, "where", GIT_STATUS_INDEX_NEW); + + cl_assert_equal_b(true, seen_paths.what); + cl_assert_equal_b(false, seen_paths.how); + cl_assert_equal_b(true, seen_paths.who); + cl_assert_equal_b(true, seen_paths.when); +} + +static int progress_cb( + git_stash_apply_progress_t progress, + void *payload) +{ + git_stash_apply_progress_t *p = (git_stash_apply_progress_t *)payload; + + cl_assert_equal_i((*p)+1, progress); + + *p = progress; + + return 0; +} + +void test_stash_apply__calls_progress_cb(void) +{ + git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; + git_stash_apply_progress_t progress = GIT_STASH_APPLY_PROGRESS_NONE; + + opts.progress_cb = progress_cb; + opts.progress_payload = &progress; + + cl_git_pass(git_stash_apply(repo, 0, &opts)); + cl_assert_equal_i(progress, GIT_STASH_APPLY_PROGRESS_DONE); +} + +static int aborting_progress_cb( + git_stash_apply_progress_t progress, + void *payload) +{ + GIT_UNUSED(payload); + + if (progress == GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED) + return -44; + + return 0; +} + +void test_stash_apply__progress_cb_can_abort(void) +{ + git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; + + opts.progress_cb = aborting_progress_cb; + + cl_git_fail_with(-44, git_stash_apply(repo, 0, &opts)); +} + +void test_stash_apply__uses_reflog_like_indices_1(void) +{ + git_oid oid; + + cl_git_mkfile("stash/untracked", "untracked\n"); + cl_git_pass(git_stash_save(&oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + assert_status(repo, "untracked", GIT_ENOTFOUND); + + /* stash@{1} is the oldest (first) stash we made */ + cl_git_pass(git_stash_apply(repo, 1, NULL)); + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); + assert_status(repo, "where", GIT_STATUS_INDEX_NEW); +} + +void test_stash_apply__uses_reflog_like_indices_2(void) +{ + git_oid oid; + + cl_git_mkfile("stash/untracked", "untracked\n"); + cl_git_pass(git_stash_save(&oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + assert_status(repo, "untracked", GIT_ENOTFOUND); + + /* stash@{0} is the newest stash we made immediately above */ + cl_git_pass(git_stash_apply(repo, 0, NULL)); + + cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); + assert_status(repo, "untracked", GIT_STATUS_WT_NEW); +} diff --git a/tests/libgit2/stash/drop.c b/tests/libgit2/stash/drop.c new file mode 100644 index 000000000..a57147172 --- /dev/null +++ b/tests/libgit2/stash/drop.c @@ -0,0 +1,174 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "stash_helpers.h" +#include "refs.h" + +static git_repository *repo; +static git_signature *signature; + +void test_stash_drop__initialize(void) +{ + cl_git_pass(git_repository_init(&repo, "stash", 0)); + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ +} + +void test_stash_drop__cleanup(void) +{ + git_signature_free(signature); + signature = NULL; + + git_repository_free(repo); + repo = NULL; + + cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_stash_drop__cannot_drop_from_an_empty_stash(void) +{ + cl_git_fail_with(git_stash_drop(repo, 0), GIT_ENOTFOUND); +} + +static void push_three_states(void) +{ + git_oid oid; + git_index *index; + + cl_git_mkfile("stash/zero.txt", "content\n"); + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, "zero.txt")); + cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit"); + cl_assert(git_fs_path_exists("stash/zero.txt")); + git_index_free(index); + + cl_git_mkfile("stash/one.txt", "content\n"); + cl_git_pass(git_stash_save( + &oid, repo, signature, "First", GIT_STASH_INCLUDE_UNTRACKED)); + cl_assert(!git_fs_path_exists("stash/one.txt")); + cl_assert(git_fs_path_exists("stash/zero.txt")); + + cl_git_mkfile("stash/two.txt", "content\n"); + cl_git_pass(git_stash_save( + &oid, repo, signature, "Second", GIT_STASH_INCLUDE_UNTRACKED)); + cl_assert(!git_fs_path_exists("stash/two.txt")); + cl_assert(git_fs_path_exists("stash/zero.txt")); + + cl_git_mkfile("stash/three.txt", "content\n"); + cl_git_pass(git_stash_save( + &oid, repo, signature, "Third", GIT_STASH_INCLUDE_UNTRACKED)); + cl_assert(!git_fs_path_exists("stash/three.txt")); + cl_assert(git_fs_path_exists("stash/zero.txt")); +} + +void test_stash_drop__cannot_drop_a_non_existing_stashed_state(void) +{ + push_three_states(); + + cl_git_fail_with(git_stash_drop(repo, 666), GIT_ENOTFOUND); + cl_git_fail_with(git_stash_drop(repo, 42), GIT_ENOTFOUND); + cl_git_fail_with(git_stash_drop(repo, 3), GIT_ENOTFOUND); +} + +void test_stash_drop__can_purge_the_stash_from_the_top(void) +{ + push_three_states(); + + cl_git_pass(git_stash_drop(repo, 0)); + cl_git_pass(git_stash_drop(repo, 0)); + cl_git_pass(git_stash_drop(repo, 0)); + + cl_git_fail_with(git_stash_drop(repo, 0), GIT_ENOTFOUND); +} + +void test_stash_drop__can_purge_the_stash_from_the_bottom(void) +{ + push_three_states(); + + cl_git_pass(git_stash_drop(repo, 2)); + cl_git_pass(git_stash_drop(repo, 1)); + cl_git_pass(git_stash_drop(repo, 0)); + + cl_git_fail_with(git_stash_drop(repo, 0), GIT_ENOTFOUND); +} + +void test_stash_drop__dropping_an_entry_rewrites_reflog_history(void) +{ + git_reference *stash; + git_reflog *reflog; + const git_reflog_entry *entry; + git_oid oid; + size_t count; + + push_three_states(); + + cl_git_pass(git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)); + + cl_git_pass(git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)); + entry = git_reflog_entry_byindex(reflog, 1); + + git_oid_cpy(&oid, git_reflog_entry_id_old(entry)); + count = git_reflog_entrycount(reflog); + + git_reflog_free(reflog); + + cl_git_pass(git_stash_drop(repo, 1)); + + cl_git_pass(git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)); + entry = git_reflog_entry_byindex(reflog, 0); + + cl_assert_equal_oid(&oid, git_reflog_entry_id_old(entry)); + cl_assert_equal_sz(count - 1, git_reflog_entrycount(reflog)); + + git_reflog_free(reflog); + + git_reference_free(stash); +} + +void test_stash_drop__dropping_the_last_entry_removes_the_stash(void) +{ + git_reference *stash; + + push_three_states(); + + cl_git_pass(git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)); + git_reference_free(stash); + + cl_git_pass(git_stash_drop(repo, 0)); + cl_git_pass(git_stash_drop(repo, 0)); + cl_git_pass(git_stash_drop(repo, 0)); + + cl_git_fail_with( + git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE), GIT_ENOTFOUND); +} + +static void retrieve_top_stash_id(git_oid *out) +{ + git_object *top_stash; + + cl_git_pass(git_revparse_single(&top_stash, repo, "stash@{0}")); + cl_git_pass(git_reference_name_to_id(out, repo, GIT_REFS_STASH_FILE)); + + cl_assert_equal_oid(out, git_object_id(top_stash)); + + git_object_free(top_stash); +} + +void test_stash_drop__dropping_the_top_stash_updates_the_stash_reference(void) +{ + git_object *next_top_stash; + git_oid oid; + + push_three_states(); + + retrieve_top_stash_id(&oid); + + cl_git_pass(git_revparse_single(&next_top_stash, repo, "stash@{1}")); + cl_assert(git_oid_cmp(&oid, git_object_id(next_top_stash))); + + cl_git_pass(git_stash_drop(repo, 0)); + + retrieve_top_stash_id(&oid); + + cl_assert_equal_oid(&oid, git_object_id(next_top_stash)); + + git_object_free(next_top_stash); +} diff --git a/tests/libgit2/stash/foreach.c b/tests/libgit2/stash/foreach.c new file mode 100644 index 000000000..fa3a9c906 --- /dev/null +++ b/tests/libgit2/stash/foreach.c @@ -0,0 +1,126 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "stash_helpers.h" + +struct callback_data +{ + char **oids; + int invokes; +}; + +static git_repository *repo; +static git_signature *signature; +static git_oid stash_tip_oid; +struct callback_data data; + +#define REPO_NAME "stash" + +void test_stash_foreach__initialize(void) +{ + cl_git_pass(git_signature_new( + &signature, + "nulltoken", + "emeric.fermas@gmail.com", + 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ + + memset(&data, 0, sizeof(struct callback_data)); +} + +void test_stash_foreach__cleanup(void) +{ + git_signature_free(signature); + signature = NULL; + + git_repository_free(repo); + repo = NULL; + + cl_git_pass(git_futils_rmdir_r(REPO_NAME, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +static int callback_cb( + size_t index, + const char* message, + const git_oid *stash_oid, + void *payload) +{ + struct callback_data *data = (struct callback_data *)payload; + + GIT_UNUSED(index); + GIT_UNUSED(message); + + cl_assert_equal_i(0, git_oid_streq(stash_oid, data->oids[data->invokes++])); + + return 0; +} + +void test_stash_foreach__enumerating_a_empty_repository_doesnt_fail(void) +{ + char *oids[] = { NULL }; + + data.oids = oids; + + cl_git_pass(git_repository_init(&repo, REPO_NAME, 0)); + + cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); + + cl_assert_equal_i(0, data.invokes); +} + +void test_stash_foreach__can_enumerate_a_repository(void) +{ + char *oids_default[] = { + "493568b7a2681187aaac8a58d3f1eab1527cba84", NULL }; + + char *oids_untracked[] = { + "7f89a8b15c878809c5c54d1ff8f8c9674154017b", + "493568b7a2681187aaac8a58d3f1eab1527cba84", NULL }; + + char *oids_ignored[] = { + "c95599a8fef20a7e57582c6727b1a0d02e0a5828", + "7f89a8b15c878809c5c54d1ff8f8c9674154017b", + "493568b7a2681187aaac8a58d3f1eab1527cba84", NULL }; + + cl_git_pass(git_repository_init(&repo, REPO_NAME, 0)); + + setup_stash(repo, signature); + + cl_git_pass(git_stash_save( + &stash_tip_oid, + repo, + signature, + NULL, + GIT_STASH_DEFAULT)); + + data.oids = oids_default; + + cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); + cl_assert_equal_i(1, data.invokes); + + /* ensure stash_foreach operates with INCLUDE_UNTRACKED */ + cl_git_pass(git_stash_save( + &stash_tip_oid, + repo, + signature, + NULL, + GIT_STASH_INCLUDE_UNTRACKED)); + + data.oids = oids_untracked; + data.invokes = 0; + + cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); + cl_assert_equal_i(2, data.invokes); + + /* ensure stash_foreach operates with INCLUDE_IGNORED */ + cl_git_pass(git_stash_save( + &stash_tip_oid, + repo, + signature, + NULL, + GIT_STASH_INCLUDE_IGNORED)); + + data.oids = oids_ignored; + data.invokes = 0; + + cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); + cl_assert_equal_i(3, data.invokes); +} diff --git a/tests/libgit2/stash/save.c b/tests/libgit2/stash/save.c new file mode 100644 index 000000000..f574211d7 --- /dev/null +++ b/tests/libgit2/stash/save.c @@ -0,0 +1,490 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "stash_helpers.h" + +static git_repository *repo; +static git_signature *signature; +static git_oid stash_tip_oid; + +/* + * Friendly reminder, in order to ease the reading of the following tests: + * + * "stash" points to the worktree commit + * "stash^1" points to the base commit (HEAD when the stash was created) + * "stash^2" points to the index commit + * "stash^3" points to the untracked commit + */ + +void test_stash_save__initialize(void) +{ + cl_git_pass(git_repository_init(&repo, "stash", 0)); + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ + + setup_stash(repo, signature); +} + +void test_stash_save__cleanup(void) +{ + git_signature_free(signature); + signature = NULL; + + git_repository_free(repo); + repo = NULL; + + cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_fixture_cleanup("sorry-it-is-a-non-bare-only-party"); +} + +static void assert_object_oid(const char* revision, const char* expected_oid, git_object_t type) +{ + int result; + git_object *obj; + + result = git_revparse_single(&obj, repo, revision); + + if (!expected_oid) { + cl_assert_equal_i(GIT_ENOTFOUND, result); + return; + } else + cl_assert_equal_i(0, result); + + cl_git_pass(git_oid_streq(git_object_id(obj), expected_oid)); + cl_assert_equal_i(type, git_object_type(obj)); + git_object_free(obj); +} + +static void assert_blob_oid(const char* revision, const char* expected_oid) +{ + assert_object_oid(revision, expected_oid, GIT_OBJECT_BLOB); +} + +void test_stash_save__does_not_keep_index_by_default(void) +{ +/* +$ git stash + +$ git show refs/stash:what +see you later + +$ git show refs/stash:how +not so small and + +$ git show refs/stash:who +funky world + +$ git show refs/stash:when +fatal: Path 'when' exists on disk, but not in 'stash'. + +$ git show refs/stash^2:what +goodbye + +$ git show refs/stash^2:how +not so small and + +$ git show refs/stash^2:who +world + +$ git show refs/stash^2:when +fatal: Path 'when' exists on disk, but not in 'stash^2'. + +$ git status --short +?? when + +*/ + unsigned int status; + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + cl_git_pass(git_status_file(&status, repo, "when")); + + assert_blob_oid("refs/stash:what", "bc99dc98b3eba0e9157e94769cd4d49cb49de449"); /* see you later */ + assert_blob_oid("refs/stash:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ + assert_blob_oid("refs/stash:who", "a0400d4954659306a976567af43125a0b1aa8595"); /* funky world */ + assert_blob_oid("refs/stash:when", NULL); + assert_blob_oid("refs/stash:why", "88c2533e21f098b89c91a431d8075cbdbe422a51"); /* would anybody use stash? */ + assert_blob_oid("refs/stash:where", "e3d6434ec12eb76af8dfa843a64ba6ab91014a0b"); /* .... */ + assert_blob_oid("refs/stash:.gitignore", "ac4d88de61733173d9959e4b77c69b9f17a00980"); + assert_blob_oid("refs/stash:just.ignore", NULL); + + assert_blob_oid("refs/stash^2:what", "dd7e1c6f0fefe118f0b63d9f10908c460aa317a6"); /* goodbye */ + assert_blob_oid("refs/stash^2:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ + assert_blob_oid("refs/stash^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ + assert_blob_oid("refs/stash^2:when", NULL); + assert_blob_oid("refs/stash^2:why", "88c2533e21f098b89c91a431d8075cbdbe422a51"); /* would anybody use stash? */ + assert_blob_oid("refs/stash^2:where", "e08f7fbb9a42a0c5367cf8b349f1f08c3d56bd72"); /* ???? */ + assert_blob_oid("refs/stash^2:.gitignore", "ac4d88de61733173d9959e4b77c69b9f17a00980"); + assert_blob_oid("refs/stash^2:just.ignore", NULL); + + assert_blob_oid("refs/stash^3", NULL); + + cl_assert_equal_i(GIT_STATUS_WT_NEW, status); +} + +void test_stash_save__can_keep_index(void) +{ + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_KEEP_INDEX)); + + assert_status(repo, "what", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); +} + +static void assert_commit_message_contains(const char *revision, const char *fragment) +{ + git_commit *commit; + + cl_git_pass(git_revparse_single((git_object**)&commit, repo, revision)); + + cl_assert(strstr(git_commit_message(commit), fragment) != NULL); + + git_commit_free(commit); +} + +void test_stash_save__can_include_untracked_files(void) +{ + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + + assert_commit_message_contains("refs/stash^3", "untracked files on master: "); + + assert_blob_oid("refs/stash^3:what", NULL); + assert_blob_oid("refs/stash^3:how", NULL); + assert_blob_oid("refs/stash^3:who", NULL); + assert_blob_oid("refs/stash^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b"); + assert_blob_oid("refs/stash^3:just.ignore", NULL); +} + +void test_stash_save__untracked_skips_ignored(void) +{ + cl_git_append2file("stash/.gitignore", "bundle/vendor/\n"); + cl_must_pass(p_mkdir("stash/bundle", 0777)); + cl_must_pass(p_mkdir("stash/bundle/vendor", 0777)); + cl_git_mkfile("stash/bundle/vendor/blah", "contents\n"); + + cl_assert(git_fs_path_exists("stash/when")); /* untracked */ + cl_assert(git_fs_path_exists("stash/just.ignore")); /* ignored */ + cl_assert(git_fs_path_exists("stash/bundle/vendor/blah")); /* ignored */ + + cl_git_pass(git_stash_save( + &stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + + cl_assert(!git_fs_path_exists("stash/when")); + cl_assert(git_fs_path_exists("stash/bundle/vendor/blah")); + cl_assert(git_fs_path_exists("stash/just.ignore")); +} + +void test_stash_save__can_include_untracked_and_ignored_files(void) +{ + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)); + + assert_commit_message_contains("refs/stash^3", "untracked files on master: "); + + assert_blob_oid("refs/stash^3:what", NULL); + assert_blob_oid("refs/stash^3:how", NULL); + assert_blob_oid("refs/stash^3:who", NULL); + assert_blob_oid("refs/stash^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b"); + assert_blob_oid("refs/stash^3:just.ignore", "78925fb1236b98b37a35e9723033e627f97aa88b"); + + cl_assert(!git_fs_path_exists("stash/just.ignore")); +} + +/* + * Note: this test was flaky prior to fixing #4101 -- run it several + * times to get a failure. The issues is that whether the fast + * (stat-only) codepath is used inside stash's diff operation depends + * on whether files are "racily clean", and there doesn't seem to be + * an easy way to force the exact required state. + */ +void test_stash_save__untracked_regression(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + const char *paths[] = {"what", "where", "how", "why"}; + git_reference *head; + git_commit *head_commit; + git_str untracked_dir; + + const char* workdir = git_repository_workdir(repo); + + git_str_init(&untracked_dir, 0); + git_str_printf(&untracked_dir, "%sz", workdir); + + cl_assert(!p_mkdir(untracked_dir.ptr, 0777)); + + cl_git_pass(git_repository_head(&head, repo)); + + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + opts.paths.strings = (char **)paths; + opts.paths.count = 4; + + cl_git_pass(git_checkout_tree(repo, (git_object*)head_commit, &opts)); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + assert_commit_message_contains("refs/stash", "WIP on master"); + + git_reference_free(head); + git_commit_free(head_commit); + git_str_dispose(&untracked_dir); +} + +#define MESSAGE "Look Ma! I'm on TV!" +void test_stash_save__can_accept_a_message(void) +{ + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, MESSAGE, GIT_STASH_DEFAULT)); + + assert_commit_message_contains("refs/stash^2", "index on master: "); + assert_commit_message_contains("refs/stash", "On master: " MESSAGE); +} + +void test_stash_save__cannot_stash_against_an_unborn_branch(void) +{ + git_reference *head; + + cl_git_pass(git_reference_symbolic_create(&head, repo, "HEAD", "refs/heads/unborn", 1, NULL)); + + cl_assert_equal_i(GIT_EUNBORNBRANCH, + git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + git_reference_free(head); +} + +void test_stash_save__cannot_stash_against_a_bare_repository(void) +{ + git_repository *local; + + cl_git_pass(git_repository_init(&local, "sorry-it-is-a-non-bare-only-party", 1)); + + cl_assert_equal_i(GIT_EBAREREPO, + git_stash_save(&stash_tip_oid, local, signature, NULL, GIT_STASH_DEFAULT)); + + git_repository_free(local); +} + +void test_stash_save__can_stash_against_a_detached_head(void) +{ + git_repository_detach_head(repo); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + assert_commit_message_contains("refs/stash^2", "index on (no branch): "); + assert_commit_message_contains("refs/stash", "WIP on (no branch): "); +} + +void test_stash_save__stashing_updates_the_reflog(void) +{ + assert_object_oid("refs/stash@{0}", NULL, GIT_OBJECT_COMMIT); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + assert_object_oid("refs/stash@{0}", git_oid_tostr_s(&stash_tip_oid), GIT_OBJECT_COMMIT); + assert_object_oid("refs/stash@{1}", NULL, GIT_OBJECT_COMMIT); +} + +void test_stash_save__multiline_message(void) +{ + const char *msg = "This\n\nis a multiline message\n"; + const git_reflog_entry *entry; + git_reflog *reflog; + + assert_object_oid("refs/stash@{0}", NULL, GIT_OBJECT_COMMIT); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, msg, GIT_STASH_DEFAULT)); + + cl_git_pass(git_reflog_read(&reflog, repo, "refs/stash")); + cl_assert(entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_s(git_reflog_entry_message(entry), "On master: This is a multiline message"); + + assert_object_oid("refs/stash@{0}", git_oid_tostr_s(&stash_tip_oid), GIT_OBJECT_COMMIT); + assert_commit_message_contains("refs/stash@{0}", msg); + + git_reflog_free(reflog); +} + +void test_stash_save__cannot_stash_when_there_are_no_local_change(void) +{ + git_index *index; + git_oid stash_tip_oid; + + cl_git_pass(git_repository_index(&index, repo)); + + /* + * 'what', 'where' and 'who' are being committed. + * 'when' remains untracked. + */ + cl_git_pass(git_index_add_bypath(index, "what")); + cl_git_pass(git_index_add_bypath(index, "where")); + cl_git_pass(git_index_add_bypath(index, "who")); + + cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit"); + git_index_free(index); + + cl_assert_equal_i(GIT_ENOTFOUND, + git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + p_unlink("stash/when"); + cl_assert_equal_i(GIT_ENOTFOUND, + git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); +} + +void test_stash_save__can_stage_normal_then_stage_untracked(void) +{ + /* + * $ git ls-tree stash@{1}^0 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob e6d64adb2c7f3eb8feb493b556cc8070dca379a3 how + * 100644 blob bc99dc98b3eba0e9157e94769cd4d49cb49de449 what + * 100644 blob a0400d4954659306a976567af43125a0b1aa8595 who + * + * $ git ls-tree stash@{1}^1 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how + * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{1}^2 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob e6d64adb2c7f3eb8feb493b556cc8070dca379a3 how + * 100644 blob dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{1}^3 + * fatal: Not a valid object name stash@{1}^3 + * + * $ git ls-tree stash@{0}^0 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how + * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{0}^1 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how + * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{0}^2 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how + * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{0}^3 + * 100644 blob b6ed15e81e2593d7bb6265eb4a991d29dc3e628b when + */ + + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + assert_status(repo, "what", GIT_STATUS_CURRENT); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + assert_status(repo, "what", GIT_STATUS_CURRENT); + assert_status(repo, "how", GIT_STATUS_CURRENT); + assert_status(repo, "who", GIT_STATUS_CURRENT); + assert_status(repo, "when", GIT_ENOTFOUND); + assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); + + + assert_blob_oid("stash@{1}^0:what", "bc99dc98b3eba0e9157e94769cd4d49cb49de449"); /* see you later */ + assert_blob_oid("stash@{1}^0:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ + assert_blob_oid("stash@{1}^0:who", "a0400d4954659306a976567af43125a0b1aa8595"); /* funky world */ + assert_blob_oid("stash@{1}^0:when", NULL); + + assert_blob_oid("stash@{1}^2:what", "dd7e1c6f0fefe118f0b63d9f10908c460aa317a6"); /* goodbye */ + assert_blob_oid("stash@{1}^2:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ + assert_blob_oid("stash@{1}^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ + assert_blob_oid("stash@{1}^2:when", NULL); + + assert_object_oid("stash@{1}^3", NULL, GIT_OBJECT_COMMIT); + + assert_blob_oid("stash@{0}^0:what", "ce013625030ba8dba906f756967f9e9ca394464a"); /* hello */ + assert_blob_oid("stash@{0}^0:how", "ac790413e2d7a26c3767e78c57bb28716686eebc"); /* small */ + assert_blob_oid("stash@{0}^0:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ + assert_blob_oid("stash@{0}^0:when", NULL); + + assert_blob_oid("stash@{0}^2:what", "ce013625030ba8dba906f756967f9e9ca394464a"); /* hello */ + assert_blob_oid("stash@{0}^2:how", "ac790413e2d7a26c3767e78c57bb28716686eebc"); /* small */ + assert_blob_oid("stash@{0}^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ + assert_blob_oid("stash@{0}^2:when", NULL); + + assert_blob_oid("stash@{0}^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b"); /* now */ +} + +#define EMPTY_TREE "4b825dc642cb6eb9a060e54bf8d69288fbee4904" + +void test_stash_save__including_untracked_without_any_untracked_file_creates_an_empty_tree(void) +{ + cl_must_pass(p_unlink("stash/when")); + + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "when", GIT_ENOTFOUND); + assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + + assert_object_oid("stash^3^{tree}", EMPTY_TREE, GIT_OBJECT_TREE); +} + +void test_stash_save__ignored_directory(void) +{ + cl_git_pass(p_mkdir("stash/ignored_directory", 0777)); + cl_git_pass(p_mkdir("stash/ignored_directory/sub", 0777)); + cl_git_mkfile("stash/ignored_directory/sub/some_file", "stuff"); + + assert_status(repo, "ignored_directory/sub/some_file", GIT_STATUS_WT_NEW); + cl_git_pass(git_ignore_add_rule(repo, "ignored_directory/")); + assert_status(repo, "ignored_directory/sub/some_file", GIT_STATUS_IGNORED); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)); + + cl_assert(!git_fs_path_exists("stash/ignored_directory/sub/some_file")); + cl_assert(!git_fs_path_exists("stash/ignored_directory/sub")); + cl_assert(!git_fs_path_exists("stash/ignored_directory")); +} + +void test_stash_save__skip_submodules(void) +{ + git_repository *untracked_repo; + cl_git_pass(git_repository_init(&untracked_repo, "stash/untracked_repo", false)); + cl_git_mkfile("stash/untracked_repo/content", "stuff"); + git_repository_free(untracked_repo); + + assert_status(repo, "untracked_repo/", GIT_STATUS_WT_NEW); + + cl_git_pass(git_stash_save( + &stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + + assert_status(repo, "untracked_repo/", GIT_STATUS_WT_NEW); +} + +void test_stash_save__deleted_in_index_modified_in_workdir(void) +{ + git_index *index; + + git_repository_index(&index, repo); + + cl_git_pass(git_index_remove_bypath(index, "who")); + cl_git_pass(git_index_write(index)); + + assert_status(repo, "who", GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + assert_blob_oid("stash@{0}^0:who", "a0400d4954659306a976567af43125a0b1aa8595"); + assert_blob_oid("stash@{0}^2:who", NULL); + + git_index_free(index); +} diff --git a/tests/libgit2/stash/stash_helpers.c b/tests/libgit2/stash/stash_helpers.c new file mode 100644 index 000000000..cd0cfbd0f --- /dev/null +++ b/tests/libgit2/stash/stash_helpers.c @@ -0,0 +1,57 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "stash_helpers.h" + +void setup_stash(git_repository *repo, git_signature *signature) +{ + git_index *index; + + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_mkfile("stash/what", "hello\n"); /* ce013625030ba8dba906f756967f9e9ca394464a */ + cl_git_mkfile("stash/how", "small\n"); /* ac790413e2d7a26c3767e78c57bb28716686eebc */ + cl_git_mkfile("stash/who", "world\n"); /* cc628ccd10742baea8241c5924df992b5c019f71 */ + cl_git_mkfile("stash/when", "now\n"); /* b6ed15e81e2593d7bb6265eb4a991d29dc3e628b */ + cl_git_mkfile("stash/just.ignore", "me\n"); /* 78925fb1236b98b37a35e9723033e627f97aa88b */ + + cl_git_mkfile("stash/.gitignore", "*.ignore\n"); + + cl_git_pass(git_index_add_bypath(index, "what")); + cl_git_pass(git_index_add_bypath(index, "how")); + cl_git_pass(git_index_add_bypath(index, "who")); + cl_git_pass(git_index_add_bypath(index, ".gitignore")); + + cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit"); + + cl_git_rewritefile("stash/what", "goodbye\n"); /* dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 */ + cl_git_rewritefile("stash/how", "not so small and\n"); /* e6d64adb2c7f3eb8feb493b556cc8070dca379a3 */ + cl_git_rewritefile("stash/who", "funky world\n"); /* a0400d4954659306a976567af43125a0b1aa8595 */ + cl_git_mkfile("stash/why", "would anybody use stash?\n"); /* 88c2533e21f098b89c91a431d8075cbde422a51 */ + cl_git_mkfile("stash/where", "????\n"); /* e08f7fbb9a42a0c5367cf8b349f1f08c3d56bd72 */ + + cl_git_pass(git_index_add_bypath(index, "what")); + cl_git_pass(git_index_add_bypath(index, "how")); + cl_git_pass(git_index_add_bypath(index, "why")); + cl_git_pass(git_index_add_bypath(index, "where")); + cl_git_pass(git_index_write(index)); + + cl_git_rewritefile("stash/what", "see you later\n"); /* bc99dc98b3eba0e9157e94769cd4d49cb49de449 */ + cl_git_mkfile("stash/where", "....\n"); /* e3d6434ec12eb76af8dfa843a64ba6ab91014a0b */ + + git_index_free(index); +} + +void assert_status( + git_repository *repo, + const char *path, + int status_flags) +{ + unsigned int status; + + if (status_flags < 0) + cl_assert_equal_i(status_flags, git_status_file(&status, repo, path)); + else { + cl_git_pass(git_status_file(&status, repo, path)); + cl_assert_equal_i((unsigned int)status_flags, status); + } +} diff --git a/tests/libgit2/stash/stash_helpers.h b/tests/libgit2/stash/stash_helpers.h new file mode 100644 index 000000000..66d758fe2 --- /dev/null +++ b/tests/libgit2/stash/stash_helpers.h @@ -0,0 +1,8 @@ +void setup_stash( + git_repository *repo, + git_signature *signature); + +void assert_status( + git_repository *repo, + const char *path, + int status_flags); diff --git a/tests/libgit2/stash/submodules.c b/tests/libgit2/stash/submodules.c new file mode 100644 index 000000000..8cadca0f2 --- /dev/null +++ b/tests/libgit2/stash/submodules.c @@ -0,0 +1,83 @@ +#include "clar_libgit2.h" +#include "stash_helpers.h" +#include "../submodule/submodule_helpers.h" + +static git_repository *repo; +static git_signature *signature; +static git_oid stash_tip_oid; + +static git_submodule *sm; + +void test_stash_submodules__initialize(void) +{ + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ + + repo = setup_fixture_submodules(); + + cl_git_pass(git_submodule_lookup(&sm, repo, "testrepo")); +} + +void test_stash_submodules__cleanup(void) +{ + git_submodule_free(sm); + sm = NULL; + + git_signature_free(signature); + signature = NULL; +} + +void test_stash_submodules__does_not_stash_modified_submodules(void) +{ + static git_index *smindex; + static git_repository *smrepo; + + assert_status(repo, "modified", GIT_STATUS_WT_MODIFIED); + + /* modify file in submodule */ + cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); + assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); + + /* add file to index in submodule */ + cl_git_pass(git_submodule_open(&smrepo, sm)); + cl_git_pass(git_repository_index(&smindex, smrepo)); + cl_git_pass(git_index_add_bypath(smindex, "README")); + + /* commit changed index of submodule */ + cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Modify it"); + assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "modified", GIT_STATUS_CURRENT); + + git_index_free(smindex); + git_repository_free(smrepo); +} + +void test_stash_submodules__stash_is_empty_with_modified_submodules(void) +{ + static git_index *smindex; + static git_repository *smrepo; + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + assert_status(repo, "modified", GIT_STATUS_CURRENT); + + /* modify file in submodule */ + cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); + assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); + + /* add file to index in submodule */ + cl_git_pass(git_submodule_open(&smrepo, sm)); + cl_git_pass(git_repository_index(&smindex, smrepo)); + cl_git_pass(git_index_add_bypath(smindex, "README")); + + /* commit changed index of submodule */ + cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Modify it"); + assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); + + cl_git_fail_with(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT), GIT_ENOTFOUND); + + git_index_free(smindex); + git_repository_free(smrepo); +} diff --git a/tests/libgit2/status/renames.c b/tests/libgit2/status/renames.c new file mode 100644 index 000000000..d5cf87d07 --- /dev/null +++ b/tests/libgit2/status/renames.c @@ -0,0 +1,844 @@ +#include "clar_libgit2.h" +#include "path.h" +#include "posix.h" +#include "status_helpers.h" +#include "util.h" +#include "status.h" + +static git_repository *g_repo = NULL; + +void test_status_renames__initialize(void) +{ + g_repo = cl_git_sandbox_init("renames"); + + cl_repo_set_bool(g_repo, "core.autocrlf", false); +} + +void test_status_renames__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void _rename_helper( + git_repository *repo, const char *from, const char *to, const char *extra) +{ + git_str oldpath = GIT_STR_INIT, newpath = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath( + &oldpath, git_repository_workdir(repo), from)); + cl_git_pass(git_str_joinpath( + &newpath, git_repository_workdir(repo), to)); + + cl_git_pass(p_rename(oldpath.ptr, newpath.ptr)); + + if (extra) + cl_git_append2file(newpath.ptr, extra); + + git_str_dispose(&oldpath); + git_str_dispose(&newpath); +} + +#define rename_file(R,O,N) _rename_helper((R), (O), (N), NULL) +#define rename_and_edit_file(R,O,N) \ + _rename_helper((R), (O), (N), "Added at the end to keep similarity!") + +struct status_entry { + git_status_t status; + const char *oldname; + const char *newname; +}; + +static void check_status( + git_status_list *status_list, + struct status_entry *expected_list, + size_t expected_len) +{ + const git_status_entry *actual; + const struct status_entry *expected; + const char *oldname, *newname; + size_t i, files_in_status = git_status_list_entrycount(status_list); + + cl_assert_equal_sz(expected_len, files_in_status); + + for (i = 0; i < expected_len; i++) { + actual = git_status_byindex(status_list, i); + expected = &expected_list[i]; + + oldname = actual->head_to_index ? actual->head_to_index->old_file.path : + actual->index_to_workdir ? actual->index_to_workdir->old_file.path : NULL; + + newname = actual->index_to_workdir ? actual->index_to_workdir->new_file.path : + actual->head_to_index ? actual->head_to_index->new_file.path : NULL; + + cl_assert_equal_i_fmt(expected->status, actual->status, "%04x"); + + if (expected->oldname) { + cl_assert(oldname != NULL); + cl_assert_equal_s(oldname, expected->oldname); + } else { + cl_assert(oldname == NULL); + } + + if (actual->status & (GIT_STATUS_INDEX_RENAMED|GIT_STATUS_WT_RENAMED)) { + if (expected->newname) { + cl_assert(newname != NULL); + cl_assert_equal_s(newname, expected->newname); + } else { + cl_assert(newname == NULL); + } + } + } +} + +void test_status_renames__head2index_one(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "newname.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "newname.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "newname.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 1); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__head2index_two(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED, + "sixserving.txt", "aaa.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED, + "untimely.txt", "bbb.txt" }, + { GIT_STATUS_INDEX_RENAMED, "songof7cities.txt", "ccc.txt" }, + { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "ddd.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "ddd.txt"); + rename_and_edit_file(g_repo, "sixserving.txt", "aaa.txt"); + rename_file(g_repo, "songof7cities.txt", "ccc.txt"); + rename_and_edit_file(g_repo, "untimely.txt", "bbb.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_remove_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_remove_bypath(index, "untimely.txt")); + cl_git_pass(git_index_add_bypath(index, "ddd.txt")); + cl_git_pass(git_index_add_bypath(index, "aaa.txt")); + cl_git_pass(git_index_add_bypath(index, "ccc.txt")); + cl_git_pass(git_index_add_bypath(index, "bbb.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 4); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__head2index_no_rename_from_rewrite(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_MODIFIED, "ikeepsix.txt", "ikeepsix.txt" }, + { GIT_STATUS_INDEX_MODIFIED, "sixserving.txt", "sixserving.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "_temp_.txt", "sixserving.txt"); + + cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 2); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__head2index_rename_from_rewrite(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "ikeepsix.txt" }, + { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "sixserving.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "_temp_.txt", "sixserving.txt"); + + cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 2); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__index2workdir_one(void) +{ + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "newname.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + rename_file(g_repo, "ikeepsix.txt", "newname.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 1); + git_status_list_free(statuslist); +} + +void test_status_renames__index2workdir_two(void) +{ + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "sixserving.txt", "aaa.txt" }, + { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "untimely.txt", "bbb.txt" }, + { GIT_STATUS_WT_RENAMED, "songof7cities.txt", "ccc.txt" }, + { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "ddd.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + rename_file(g_repo, "ikeepsix.txt", "ddd.txt"); + rename_and_edit_file(g_repo, "sixserving.txt", "aaa.txt"); + rename_file(g_repo, "songof7cities.txt", "ccc.txt"); + rename_and_edit_file(g_repo, "untimely.txt", "bbb.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 4); + git_status_list_free(statuslist); +} + +void test_status_renames__index2workdir_rename_from_rewrite(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_WT_RENAMED, "sixserving.txt", "ikeepsix.txt" }, + { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "sixserving.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "_temp_.txt", "sixserving.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 2); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__both_one(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "ikeepsix.txt", "newname-workdir.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "newname-index.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "newname-index.txt")); + cl_git_pass(git_index_write(index)); + + rename_file(g_repo, "newname-index.txt", "newname-workdir.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 1); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__both_two(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | + GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "ikeepsix.txt", "ikeepsix-both.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED, + "sixserving.txt", "sixserving-index.txt" }, + { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "songof7cities.txt", "songof7cities-workdir.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "untimely.txt", "untimely-both.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_and_edit_file(g_repo, "ikeepsix.txt", "ikeepsix-index.txt"); + rename_and_edit_file(g_repo, "sixserving.txt", "sixserving-index.txt"); + rename_file(g_repo, "untimely.txt", "untimely-index.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_remove_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_remove_bypath(index, "untimely.txt")); + cl_git_pass(git_index_add_bypath(index, "ikeepsix-index.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving-index.txt")); + cl_git_pass(git_index_add_bypath(index, "untimely-index.txt")); + cl_git_pass(git_index_write(index)); + + rename_and_edit_file(g_repo, "ikeepsix-index.txt", "ikeepsix-both.txt"); + rename_and_edit_file(g_repo, "songof7cities.txt", "songof7cities-workdir.txt"); + rename_file(g_repo, "untimely-index.txt", "untimely-both.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 4); + git_status_list_free(statuslist); + + git_index_free(index); +} + + +void test_status_renames__both_rename_from_rewrite(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "songof7cities.txt", "ikeepsix.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "ikeepsix.txt", "sixserving.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "sixserving.txt", "songof7cities.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "songof7cities.txt", "sixserving.txt"); + rename_file(g_repo, "_temp_.txt", "songof7cities.txt"); + + cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_write(index)); + + rename_file(g_repo, "songof7cities.txt", "_temp_.txt"); + rename_file(g_repo, "ikeepsix.txt", "songof7cities.txt"); + rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); + rename_file(g_repo, "_temp_.txt", "sixserving.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 3); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__rewrites_only_for_renames(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_WT_MODIFIED, "ikeepsix.txt", "ikeepsix.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_rewritefile("renames/ikeepsix.txt", + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n" \ + "This is enough content for the file to be rewritten.\n"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 1); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__both_casechange_one(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + int index_caps; + struct status_entry expected_icase[] = { + { GIT_STATUS_INDEX_RENAMED, + "ikeepsix.txt", "IKeepSix.txt" }, + }; + struct status_entry expected_case[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "ikeepsix.txt", "IKEEPSIX.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_repository_index(&index, g_repo)); + index_caps = git_index_caps(index); + + rename_file(g_repo, "ikeepsix.txt", "IKeepSix.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "IKeepSix.txt")); + cl_git_pass(git_index_write(index)); + + /* on a case-insensitive file system, this change won't matter. + * on a case-sensitive one, it will. + */ + rename_file(g_repo, "IKeepSix.txt", "IKEEPSIX.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + + check_status(statuslist, (index_caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) ? + expected_icase : expected_case, 1); + + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__both_casechange_two(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + int index_caps; + struct status_entry expected_icase[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | + GIT_STATUS_WT_MODIFIED, + "ikeepsix.txt", "IKeepSix.txt" }, + { GIT_STATUS_INDEX_MODIFIED, + "sixserving.txt", "sixserving.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_MODIFIED, + "songof7cities.txt", "songof7.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "untimely.txt", "untimeliest.txt" } + }; + struct status_entry expected_case[] = { + { GIT_STATUS_INDEX_RENAMED | + GIT_STATUS_WT_MODIFIED | GIT_STATUS_WT_RENAMED, + "songof7cities.txt", "SONGOF7.txt" }, + { GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_RENAMED, + "sixserving.txt", "SixServing.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | + GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "ikeepsix.txt", "ikeepsix.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "untimely.txt", "untimeliest.txt" } + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_repository_index(&index, g_repo)); + index_caps = git_index_caps(index); + + rename_and_edit_file(g_repo, "ikeepsix.txt", "IKeepSix.txt"); + rename_and_edit_file(g_repo, "sixserving.txt", "sixserving.txt"); + rename_file(g_repo, "songof7cities.txt", "songof7.txt"); + rename_file(g_repo, "untimely.txt", "untimelier.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_remove_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_remove_bypath(index, "untimely.txt")); + cl_git_pass(git_index_add_bypath(index, "IKeepSix.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_add_bypath(index, "songof7.txt")); + cl_git_pass(git_index_add_bypath(index, "untimelier.txt")); + cl_git_pass(git_index_write(index)); + + rename_and_edit_file(g_repo, "IKeepSix.txt", "ikeepsix.txt"); + rename_file(g_repo, "sixserving.txt", "SixServing.txt"); + rename_and_edit_file(g_repo, "songof7.txt", "SONGOF7.txt"); + rename_file(g_repo, "untimelier.txt", "untimeliest.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + + check_status(statuslist, (index_caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) ? + expected_icase : expected_case, 4); + + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__zero_byte_file_does_not_fail(void) +{ + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + + struct status_entry expected[] = { + { GIT_STATUS_WT_DELETED, "ikeepsix.txt", "ikeepsix.txt" }, + { GIT_STATUS_WT_NEW, "zerobyte.txt", "zerobyte.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES | + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR | + GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | + GIT_STATUS_SHOW_INDEX_AND_WORKDIR | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + + p_unlink("renames/ikeepsix.txt"); + cl_git_mkfile("renames/zerobyte.txt", ""); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 2); + git_status_list_free(statuslist); +} + +#ifdef GIT_USE_ICONV +static char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; +static char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; +#endif + +/* + * Create a file in NFD (canonically decomposed) format. Ensure + * that when core.precomposeunicode is false that we return paths + * in NFD, but when core.precomposeunicode is true, then we + * return paths precomposed (in NFC). + */ +void test_status_renames__precomposed_unicode_rename(void) +{ +#ifdef GIT_USE_ICONV + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected0[] = { + { GIT_STATUS_WT_NEW, nfd, NULL }, + { GIT_STATUS_WT_DELETED, "sixserving.txt", NULL }, + }; + struct status_entry expected1[] = { + { GIT_STATUS_WT_RENAMED, "sixserving.txt", nfd }, + }; + struct status_entry expected2[] = { + { GIT_STATUS_WT_DELETED, "sixserving.txt", NULL }, + { GIT_STATUS_WT_NEW, nfc, NULL }, + }; + struct status_entry expected3[] = { + { GIT_STATUS_WT_RENAMED, "sixserving.txt", nfc }, + }; + + rename_file(g_repo, "sixserving.txt", nfd); + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + + cl_repo_set_bool(g_repo, "core.precomposeunicode", false); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected0, ARRAY_SIZE(expected0)); + git_status_list_free(statuslist); + + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected1, ARRAY_SIZE(expected1)); + git_status_list_free(statuslist); + + cl_repo_set_bool(g_repo, "core.precomposeunicode", true); + + opts.flags &= ~GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected2, ARRAY_SIZE(expected2)); + git_status_list_free(statuslist); + + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected3, ARRAY_SIZE(expected3)); + git_status_list_free(statuslist); +#endif +} + +void test_status_renames__precomposed_unicode_toggle_is_rename(void) +{ +#ifdef GIT_USE_ICONV + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected0[] = { + { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", nfd }, + }; + struct status_entry expected1[] = { + { GIT_STATUS_WT_RENAMED, nfd, nfc }, + }; + struct status_entry expected2[] = { + { GIT_STATUS_INDEX_RENAMED, nfd, nfc }, + }; + struct status_entry expected3[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, nfd, nfd }, + }; + + cl_repo_set_bool(g_repo, "core.precomposeunicode", false); + rename_file(g_repo, "ikeepsix.txt", nfd); + + { + git_index *index; + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, nfd)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + } + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected0, ARRAY_SIZE(expected0)); + git_status_list_free(statuslist); + + cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "commit nfd"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + cl_assert_equal_sz(0, git_status_list_entrycount(statuslist)); + git_status_list_free(statuslist); + + cl_repo_set_bool(g_repo, "core.precomposeunicode", true); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected1, ARRAY_SIZE(expected1)); + git_status_list_free(statuslist); + + { + git_index *index; + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_remove_bypath(index, nfd)); + cl_git_pass(git_index_add_bypath(index, nfc)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + } + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected2, ARRAY_SIZE(expected2)); + git_status_list_free(statuslist); + + cl_repo_set_bool(g_repo, "core.precomposeunicode", false); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected3, ARRAY_SIZE(expected3)); + git_status_list_free(statuslist); +#endif +} + +void test_status_renames__rename_threshold(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + + _rename_helper(g_repo, "ikeepsix.txt", "newname.txt", + "Line 1\n" \ + "Line 2\n" \ + "Line 3\n" \ + "Line 4\n" \ + "Line 5\n" \ + "Line 6\n" \ + "Line 7\n" \ + "Line 8\n" \ + "Line 9\n" + ); + + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* Default threshold */ + { + struct status_entry expected[] = { + { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, "ikeepsix.txt", "newname.txt" }, + }; + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 1); + git_status_list_free(statuslist); + } + + /* Threshold set to 90 */ + { + struct status_entry expected[] = { + { GIT_STATUS_WT_DELETED, "ikeepsix.txt", NULL }, + { GIT_STATUS_WT_NEW, "newname.txt", NULL } + }; + + opts.rename_threshold = 90; + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 2); + git_status_list_free(statuslist); + } + + /* Threshold set to 25 */ + { + struct status_entry expected[] = { + { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, "ikeepsix.txt", "newname.txt" }, + }; + + opts.rename_threshold = 25; + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 1); + git_status_list_free(statuslist); + } + + git_index_free(index); +} + +void test_status_renames__case_insensitive_h2i_and_i2wc(void) +{ + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + git_reference *head, *test_branch; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + git_str path_to_delete = GIT_STR_INIT; + git_str path_to_edit = GIT_STR_INIT; + git_index *index; + git_strarray paths = { NULL, 0 }; + + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_MODIFIED, "sixserving.txt", "sixserving-renamed.txt" }, + { GIT_STATUS_INDEX_DELETED, "Wow.txt", "Wow.txt" } + }; + + + /* Checkout the correct branch */ + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_git_pass(git_reference_symbolic_set_target( + &test_branch, head, "refs/heads/case-insensitive-status", NULL)); + cl_git_pass(git_checkout_head(g_repo, &checkout_opts)); + + cl_git_pass(git_repository_index(&index, g_repo)); + + + /* Rename sixserving.txt, delete Wow.txt, and stage those changes */ + rename_file(g_repo, "sixserving.txt", "sixserving-renamed.txt"); + cl_git_pass(git_str_joinpath( + &path_to_delete, git_repository_workdir(g_repo), "Wow.txt")); + cl_git_rmfile(path_to_delete.ptr); + + cl_git_pass(git_index_add_all(index, &paths, GIT_INDEX_ADD_FORCE, NULL, NULL)); + cl_git_pass(git_index_write(index)); + + + /* Change content of sixserving-renamed.txt */ + cl_git_pass(git_str_joinpath( + &path_to_edit, git_repository_workdir(g_repo), "sixserving-renamed.txt")); + cl_git_append2file(path_to_edit.ptr, "New content\n"); + + /* Run status */ + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY; + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + check_status(statuslist, expected, 2); + git_status_list_free(statuslist); + + git_index_free(index); + + git_str_dispose(&path_to_delete); + git_str_dispose(&path_to_edit); + + git_reference_free(head); + git_reference_free(test_branch); +} diff --git a/tests/libgit2/status/single.c b/tests/libgit2/status/single.c new file mode 100644 index 000000000..e7f92097c --- /dev/null +++ b/tests/libgit2/status/single.c @@ -0,0 +1,45 @@ +#include "clar_libgit2.h" +#include "posix.h" + +static void +cleanup__remove_file(void *_file) +{ + cl_must_pass(p_unlink((char *)_file)); +} + +/* test retrieving OID from a file apart from the ODB */ +void test_status_single__hash_single_file(void) +{ + static const char file_name[] = "new_file"; + static const char file_contents[] = "new_file\n"; + static const char file_hash[] = "d4fa8600b4f37d7516bef4816ae2c64dbf029e3a"; + + git_oid expected_id, actual_id; + + /* initialization */ + git_oid_fromstr(&expected_id, file_hash); + cl_git_mkfile(file_name, file_contents); + cl_set_cleanup(&cleanup__remove_file, (void *)file_name); + + cl_git_pass(git_odb_hashfile(&actual_id, file_name, GIT_OBJECT_BLOB)); + cl_assert_equal_oid(&expected_id, &actual_id); +} + +/* test retrieving OID from an empty file apart from the ODB */ +void test_status_single__hash_single_empty_file(void) +{ + static const char file_name[] = "new_empty_file"; + static const char file_contents[] = ""; + static const char file_hash[] = "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"; + + git_oid expected_id, actual_id; + + /* initialization */ + git_oid_fromstr(&expected_id, file_hash); + cl_git_mkfile(file_name, file_contents); + cl_set_cleanup(&cleanup__remove_file, (void *)file_name); + + cl_git_pass(git_odb_hashfile(&actual_id, file_name, GIT_OBJECT_BLOB)); + cl_assert_equal_oid(&expected_id, &actual_id); +} + diff --git a/tests/libgit2/status/status_data.h b/tests/libgit2/status/status_data.h new file mode 100644 index 000000000..09b9827f2 --- /dev/null +++ b/tests/libgit2/status/status_data.h @@ -0,0 +1,326 @@ +#include "status_helpers.h" + +/* A utf-8 string with 83 characters, but 249 bytes. */ +static const char *longname = "\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97"; + + +/* entries for a plain copy of tests/resources/status */ + +static const char *entry_paths0[] = { + "file_deleted", + "ignored_file", + "modified_file", + "new_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + + "subdir/deleted_file", + "subdir/modified_file", + "subdir/new_file", + + "\xe8\xbf\x99", +}; + +static const unsigned int entry_statuses0[] = { + GIT_STATUS_WT_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_DELETED, + GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_NEW, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_DELETED, + GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_MODIFIED, + + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + + GIT_STATUS_WT_NEW, +}; + +static const int entry_count0 = 16; + +/* entries for a copy of tests/resources/status with all content + * deleted from the working directory + */ + +static const char *entry_paths2[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", +}; + +static const unsigned int entry_statuses2[] = { + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, +}; + +static const int entry_count2 = 15; + +/* entries for a copy of tests/resources/status with some mods */ + +static const char *entry_paths3_icase[] = { + ".HEADER", + "42-is-not-prime.sigh", + "current_file", + "current_file/", + "file_deleted", + "ignored_file", + "modified_file", + "new_file", + "README.md", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + "subdir", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", + "\xe8\xbf\x99", +}; + +static const unsigned int entry_statuses3_icase[] = { + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_NEW, +}; + +static const char *entry_paths3[] = { + ".HEADER", + "42-is-not-prime.sigh", + "README.md", + "current_file", + "current_file/", + "file_deleted", + "ignored_file", + "modified_file", + "new_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + "subdir", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", + "\xe8\xbf\x99", +}; + +static const unsigned int entry_statuses3[] = { + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_NEW, +}; + +static const int entry_count3 = 22; + + +/* entries for a copy of tests/resources/status with some mods + * and different options to the status call + */ + +static const char *entry_paths4[] = { + ".new_file", + "current_file", + "current_file/current_file", + "current_file/modified_file", + "current_file/new_file", + "file_deleted", + "modified_file", + "new_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + "subdir", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", + "zzz_new_dir/new_file", + "zzz_new_file", + "\xe8\xbf\x99", +}; + +static const unsigned int entry_statuses4[] = { + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, +}; + +static const int entry_count4 = 23; + + +/* entries for a copy of tests/resources/status with options + * passed to the status call in order to only get the differences + * between the HEAD and the index (changes to be committed) + */ + +static const char *entry_paths5[] = { + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", +}; + +static const unsigned int entry_statuses5[] = { + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_NEW, +}; + +static const int entry_count5 = 8; + + +/* entries for a copy of tests/resources/status with options + * passed to the status call in order to only get the differences + * between the workdir and the index (changes not staged, untracked files) + */ + +static const char *entry_paths6[] = { + "file_deleted", + "ignored_file", + "modified_file", + "new_file", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_modified_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + "subdir/deleted_file", + "subdir/modified_file", + "subdir/new_file", + "\xe8\xbf\x99", +}; + +static const unsigned int entry_statuses6[] = { + GIT_STATUS_WT_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, +}; + +static const int entry_count6 = 13; diff --git a/tests/libgit2/status/status_helpers.c b/tests/libgit2/status/status_helpers.c new file mode 100644 index 000000000..5d13caa9a --- /dev/null +++ b/tests/libgit2/status/status_helpers.c @@ -0,0 +1,97 @@ +#include "clar_libgit2.h" +#include "status_helpers.h" + +int cb_status__normal( + const char *path, unsigned int status_flags, void *payload) +{ + status_entry_counts *counts = payload; + + if (counts->debug) + cb_status__print(path, status_flags, NULL); + + if (counts->entry_count >= counts->expected_entry_count) + counts->wrong_status_flags_count++; + else if (strcmp(path, counts->expected_paths[counts->entry_count])) + counts->wrong_sorted_path++; + else if (status_flags != counts->expected_statuses[counts->entry_count]) + counts->wrong_status_flags_count++; + + counts->entry_count++; + return 0; +} + +int cb_status__count(const char *p, unsigned int s, void *payload) +{ + volatile int *count = (int *)payload; + + GIT_UNUSED(p); + GIT_UNUSED(s); + + (*count)++; + + return 0; +} + +int cb_status__single(const char *p, unsigned int s, void *payload) +{ + status_entry_single *data = (status_entry_single *)payload; + + if (data->debug) + fprintf(stderr, "%02d: %s (%04x)\n", data->count, p, s); + + data->count++; + data->status = s; + + return 0; +} + +int cb_status__print( + const char *path, unsigned int status_flags, void *payload) +{ + char istatus = ' ', wstatus = ' '; + int icount = 0, wcount = 0; + + if (status_flags & GIT_STATUS_INDEX_NEW) { + istatus = 'A'; icount++; + } + if (status_flags & GIT_STATUS_INDEX_MODIFIED) { + istatus = 'M'; icount++; + } + if (status_flags & GIT_STATUS_INDEX_DELETED) { + istatus = 'D'; icount++; + } + if (status_flags & GIT_STATUS_INDEX_RENAMED) { + istatus = 'R'; icount++; + } + if (status_flags & GIT_STATUS_INDEX_TYPECHANGE) { + istatus = 'T'; icount++; + } + + if (status_flags & GIT_STATUS_WT_NEW) { + wstatus = 'A'; wcount++; + } + if (status_flags & GIT_STATUS_WT_MODIFIED) { + wstatus = 'M'; wcount++; + } + if (status_flags & GIT_STATUS_WT_DELETED) { + wstatus = 'D'; wcount++; + } + if (status_flags & GIT_STATUS_WT_TYPECHANGE) { + wstatus = 'T'; wcount++; + } + if (status_flags & GIT_STATUS_IGNORED) { + wstatus = 'I'; wcount++; + } + if (status_flags & GIT_STATUS_WT_UNREADABLE) { + wstatus = 'X'; wcount++; + } + + fprintf(stderr, "%c%c %s (%d/%d%s)\n", + istatus, wstatus, path, icount, wcount, + (icount > 1 || wcount > 1) ? " INVALID COMBO" : ""); + + if (payload) + *((int *)payload) += 1; + + return 0; +} diff --git a/tests/libgit2/status/status_helpers.h b/tests/libgit2/status/status_helpers.h new file mode 100644 index 000000000..464266af3 --- /dev/null +++ b/tests/libgit2/status/status_helpers.h @@ -0,0 +1,51 @@ +#ifndef INCLUDE_cl_status_helpers_h__ +#define INCLUDE_cl_status_helpers_h__ + +typedef struct { + int wrong_status_flags_count; + int wrong_sorted_path; + int entry_count; + const unsigned int* expected_statuses; + const char** expected_paths; + int expected_entry_count; + const char *file; + const char *func; + int line; + bool debug; +} status_entry_counts; + +#define status_counts_init(counts, paths, statuses) do { \ + memset(&(counts), 0, sizeof(counts)); \ + (counts).expected_statuses = (statuses); \ + (counts).expected_paths = (paths); \ + (counts).file = __FILE__; \ + (counts).func = __func__; \ + (counts).line = __LINE__; \ + } while (0) + +/* cb_status__normal takes payload of "status_entry_counts *" */ + +extern int cb_status__normal( + const char *path, unsigned int status_flags, void *payload); + + +/* cb_status__count takes payload of "int *" */ + +extern int cb_status__count(const char *p, unsigned int s, void *payload); + + +typedef struct { + int count; + unsigned int status; + bool debug; +} status_entry_single; + +/* cb_status__single takes payload of "status_entry_single *" */ + +extern int cb_status__single(const char *p, unsigned int s, void *payload); + +/* cb_status__print takes optional payload of "int *" */ + +extern int cb_status__print(const char *p, unsigned int s, void *payload); + +#endif diff --git a/tests/libgit2/status/submodules.c b/tests/libgit2/status/submodules.c new file mode 100644 index 000000000..d223657b4 --- /dev/null +++ b/tests/libgit2/status/submodules.c @@ -0,0 +1,563 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "status_helpers.h" +#include "../submodule/submodule_helpers.h" + +static git_repository *g_repo = NULL; + +void test_status_submodules__initialize(void) +{ +} + +void test_status_submodules__cleanup(void) +{ +} + +void test_status_submodules__api(void) +{ + git_submodule *sm; + + g_repo = setup_fixture_submodules(); + + cl_assert(git_submodule_lookup(NULL, g_repo, "nonexistent") == GIT_ENOTFOUND); + + cl_assert(git_submodule_lookup(NULL, g_repo, "modified") == GIT_ENOTFOUND); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + cl_assert(sm != NULL); + cl_assert_equal_s("testrepo", git_submodule_name(sm)); + cl_assert_equal_s("testrepo", git_submodule_path(sm)); + git_submodule_free(sm); +} + +void test_status_submodules__0(void) +{ + int counts = 0; + + g_repo = setup_fixture_submodules(); + + cl_assert(git_fs_path_isdir("submodules/.git")); + cl_assert(git_fs_path_isdir("submodules/testrepo/.git")); + cl_assert(git_fs_path_isfile("submodules/.gitmodules")); + + cl_git_pass( + git_status_foreach(g_repo, cb_status__count, &counts) + ); + + cl_assert_equal_i(6, counts); +} + +static const char *expected_files[] = { + ".gitmodules", + "added", + "deleted", + "ignored", + "modified", + "untracked" +}; + +static unsigned int expected_status[] = { + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW +}; + +static int cb_status__match(const char *p, unsigned int s, void *payload) +{ + status_entry_counts *counts = payload; + int idx = counts->entry_count++; + + clar__assert_equal( + counts->file, counts->func, counts->line, + "Status path mismatch", 1, + "%s", counts->expected_paths[idx], p); + + clar__assert_equal( + counts->file, counts->func, counts->line, + "Status code mismatch", 1, + "%o", counts->expected_statuses[idx], s); + + return 0; +} + +void test_status_submodules__1(void) +{ + status_entry_counts counts; + + g_repo = setup_fixture_submodules(); + + cl_assert(git_fs_path_isdir("submodules/.git")); + cl_assert(git_fs_path_isdir("submodules/testrepo/.git")); + cl_assert(git_fs_path_isfile("submodules/.gitmodules")); + + status_counts_init(counts, expected_files, expected_status); + + cl_git_pass( git_status_foreach(g_repo, cb_status__match, &counts) ); + + cl_assert_equal_i(6, counts.entry_count); +} + +void test_status_submodules__single_file(void) +{ + unsigned int status = 0; + g_repo = setup_fixture_submodules(); + cl_git_pass( git_status_file(&status, g_repo, "testrepo") ); + cl_assert(!status); +} + +void test_status_submodules__moved_head(void) +{ + git_submodule *sm; + git_repository *smrepo; + git_oid oid; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *expected_files_with_sub[] = { + ".gitmodules", + "added", + "deleted", + "ignored", + "modified", + "testrepo", + "untracked" + }; + static unsigned int expected_status_with_sub[] = { + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW + }; + + g_repo = setup_fixture_submodules(); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + cl_git_pass(git_submodule_open(&smrepo, sm)); + git_submodule_free(sm); + + /* move submodule HEAD to c47800c7266a2be04c571c04d5a6614691ea99bd */ + cl_git_pass( + git_oid_fromstr(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + cl_git_pass(git_repository_set_head_detached(smrepo, &oid)); + + /* first do a normal status, which should now include the submodule */ + + opts.flags = GIT_STATUS_OPT_DEFAULTS; + + status_counts_init( + counts, expected_files_with_sub, expected_status_with_sub); + cl_git_pass( + git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(7, counts.entry_count); + + /* try again with EXCLUDE_SUBMODULES which should skip it */ + + opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_EXCLUDE_SUBMODULES; + + status_counts_init(counts, expected_files, expected_status); + cl_git_pass( + git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(6, counts.entry_count); + + git_repository_free(smrepo); +} + +void test_status_submodules__dirty_workdir_only(void) +{ + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *expected_files_with_sub[] = { + ".gitmodules", + "added", + "deleted", + "ignored", + "modified", + "testrepo", + "untracked" + }; + static unsigned int expected_status_with_sub[] = { + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW + }; + + g_repo = setup_fixture_submodules(); + + cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); + cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before"); + + /* first do a normal status, which should now include the submodule */ + + opts.flags = GIT_STATUS_OPT_DEFAULTS; + + status_counts_init( + counts, expected_files_with_sub, expected_status_with_sub); + cl_git_pass( + git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(7, counts.entry_count); + + /* try again with EXCLUDE_SUBMODULES which should skip it */ + + opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_EXCLUDE_SUBMODULES; + + status_counts_init(counts, expected_files, expected_status); + cl_git_pass( + git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(6, counts.entry_count); +} + +void test_status_submodules__uninitialized(void) +{ + git_repository *cloned_repo; + git_status_list *statuslist; + + g_repo = cl_git_sandbox_init("submod2"); + + cl_git_pass(git_clone(&cloned_repo, "submod2", "submod2-clone", NULL)); + + cl_git_pass(git_status_list_new(&statuslist, cloned_repo, NULL)); + cl_assert_equal_i(0, git_status_list_entrycount(statuslist)); + + git_status_list_free(statuslist); + git_repository_free(cloned_repo); + cl_git_sandbox_cleanup(); +} + +void test_status_submodules__contained_untracked_repo(void) +{ + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + git_repository *contained; + static const char *expected_files_not_ignored[] = { + ".gitmodules", + "added", + "deleted", + "modified", + "untracked" + }; + static unsigned int expected_status_not_ignored[] = { + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + }; + static const char *expected_files_with_untracked[] = { + ".gitmodules", + "added", + "deleted", + "dir/file.md", + "modified", + "untracked" + }; + static const char *expected_files_with_untracked_dir[] = { + ".gitmodules", + "added", + "deleted", + "dir/", + "modified", + "untracked" + }; + static unsigned int expected_status_with_untracked[] = { + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW + }; + + g_repo = setup_fixture_submodules(); + + /* skip empty directory */ + + cl_must_pass(p_mkdir("submodules/dir", 0777)); + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED; + + status_counts_init( + counts, expected_files_not_ignored, expected_status_not_ignored); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(5, counts.entry_count); + + /* still skipping because empty == ignored */ + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + status_counts_init( + counts, expected_files_not_ignored, expected_status_not_ignored); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(5, counts.entry_count); + + /* find non-ignored contents of directory */ + + cl_git_mkfile("submodules/dir/file.md", "hello"); + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + status_counts_init( + counts, expected_files_with_untracked, expected_status_with_untracked); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(6, counts.entry_count); + + /* but skip if all content is ignored */ + + cl_git_append2file("submodules/.git/info/exclude", "\n*.md\n\n"); + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + status_counts_init( + counts, expected_files_not_ignored, expected_status_not_ignored); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(5, counts.entry_count); + + /* same is true if it contains a git link */ + + cl_git_mkfile("submodules/dir/.git", "gitlink: ../.git"); + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + status_counts_init( + counts, expected_files_not_ignored, expected_status_not_ignored); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(5, counts.entry_count); + + /* but if it contains tracked files, it should just show up as a + * directory and exclude the files in it + */ + + cl_git_mkfile("submodules/dir/another_file", "hello"); + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + status_counts_init( + counts, expected_files_with_untracked_dir, + expected_status_with_untracked); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(6, counts.entry_count); + + /* that applies to a git repo with a .git directory too */ + + cl_must_pass(p_unlink("submodules/dir/.git")); + cl_git_pass(git_repository_init(&contained, "submodules/dir", false)); + git_repository_free(contained); + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + status_counts_init( + counts, expected_files_with_untracked_dir, + expected_status_with_untracked); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(6, counts.entry_count); + + /* same result even if we don't recurse into subdirectories */ + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED; + + status_counts_init( + counts, expected_files_with_untracked_dir, + expected_status_with_untracked); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(6, counts.entry_count); + + /* and if we remove the untracked file, it goes back to ignored */ + + cl_must_pass(p_unlink("submodules/dir/another_file")); + + status_counts_init( + counts, expected_files_not_ignored, expected_status_not_ignored); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(5, counts.entry_count); +} + +void test_status_submodules__broken_stuff_that_git_allows(void) +{ + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + git_repository *contained; + static const char *expected_files_with_broken[] = { + ".gitmodules", + "added", + "broken/tracked", + "deleted", + "ignored", + "modified", + "untracked" + }; + static unsigned int expected_status_with_broken[] = { + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_INDEX_DELETED, + GIT_STATUS_IGNORED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + }; + + g_repo = setup_fixture_submodules(); + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | + GIT_STATUS_OPT_INCLUDE_IGNORED; + + /* make a directory and stick a tracked item into the index */ + { + git_index *idx; + cl_must_pass(p_mkdir("submodules/broken", 0777)); + cl_git_mkfile("submodules/broken/tracked", "tracked content"); + cl_git_pass(git_repository_index(&idx, g_repo)); + cl_git_pass(git_index_add_bypath(idx, "broken/tracked")); + cl_git_pass(git_index_write(idx)); + git_index_free(idx); + } + + status_counts_init( + counts, expected_files_with_broken, expected_status_with_broken); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(7, counts.entry_count); + + /* directory with tracked items that looks a little bit like a repo */ + + cl_must_pass(p_mkdir("submodules/broken/.git", 0777)); + cl_must_pass(p_mkdir("submodules/broken/.git/info", 0777)); + cl_git_mkfile("submodules/broken/.git/info/exclude", "# bogus"); + + status_counts_init( + counts, expected_files_with_broken, expected_status_with_broken); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(7, counts.entry_count); + + /* directory with tracked items that is a repo */ + + cl_git_pass(git_futils_rmdir_r( + "submodules/broken/.git", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_repository_init(&contained, "submodules/broken", false)); + git_repository_free(contained); + + status_counts_init( + counts, expected_files_with_broken, expected_status_with_broken); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(7, counts.entry_count); + + /* directory with tracked items that claims to be a submodule but is not */ + + cl_git_pass(git_futils_rmdir_r( + "submodules/broken/.git", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_append2file("submodules/.gitmodules", + "\n[submodule \"broken\"]\n" + "\tpath = broken\n" + "\turl = https://github.com/not/used\n\n"); + + status_counts_init( + counts, expected_files_with_broken, expected_status_with_broken); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__match, &counts)); + cl_assert_equal_i(7, counts.entry_count); +} + +void test_status_submodules__entry_but_dir_tracked(void) +{ + git_repository *repo; + git_status_list *status; + git_diff *diff; + git_index *index; + git_tree *tree; + + cl_git_pass(git_repository_init(&repo, "mixed-submodule", 0)); + cl_git_mkfile("mixed-submodule/.gitmodules", "[submodule \"sub\"]\n path = sub\n url = ../foo\n"); + cl_git_pass(p_mkdir("mixed-submodule/sub", 0777)); + cl_git_mkfile("mixed-submodule/sub/file", ""); + + /* Create the commit with sub/file as a file, and an entry for sub in the modules list */ + { + git_oid tree_id, commit_id; + git_signature *sig; + git_reference *ref; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, ".gitmodules")); + cl_git_pass(git_index_add_bypath(index, "sub/file")); + cl_git_pass(git_index_write(index)); + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_signature_now(&sig, "Sloppy Submoduler", "sloppy@example.com")); + cl_git_pass(git_tree_lookup(&tree, repo, &tree_id)); + cl_git_pass(git_commit_create(&commit_id, repo, NULL, sig, sig, NULL, "message", tree, 0, NULL)); + cl_git_pass(git_reference_create(&ref, repo, "refs/heads/master", &commit_id, 1, "commit: foo")); + git_reference_free(ref); + git_signature_free(sig); + } + + cl_git_pass(git_diff_tree_to_index(&diff, repo, tree, index, NULL)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, NULL)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + + cl_git_pass(git_status_list_new(&status, repo, NULL)); + cl_assert_equal_i(0, git_status_list_entrycount(status)); + + git_status_list_free(status); + git_index_free(index); + git_tree_free(tree); + git_repository_free(repo); +} + +void test_status_submodules__mixed_case(void) +{ + git_status_list *status; + git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; + const git_status_entry *s; + size_t i; + + status_opts.flags = + GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS | + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR | + GIT_STATUS_OPT_RENAMES_FROM_REWRITES | + GIT_STATUS_OPT_INCLUDE_UNREADABLE | + GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED; + + g_repo = setup_fixture_submod3(); + + cl_git_pass(git_status_list_new(&status, g_repo, &status_opts)); + + for (i = 0; i < git_status_list_entrycount(status); i++) { + s = git_status_byindex(status, i); + + if (s->head_to_index && + strcmp(s->head_to_index->old_file.path, ".gitmodules") == 0) + continue; + + cl_assert_equal_i(0, s->status); + } + + git_status_list_free(status); +} + diff --git a/tests/libgit2/status/worktree.c b/tests/libgit2/status/worktree.c new file mode 100644 index 000000000..00c6ec2d5 --- /dev/null +++ b/tests/libgit2/status/worktree.c @@ -0,0 +1,1356 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "ignore.h" +#include "status_data.h" +#include "posix.h" +#include "util.h" +#include "path.h" +#include "../diff/diff_helpers.h" +#include "../checkout/checkout_helpers.h" +#include "git2/sys/diff.h" + +/** + * Cleanup + * + * This will be called once after each test finishes, even + * if the test failed + */ +void test_status_worktree__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +/** + * Tests - Status determination on a working tree + */ +/* this test is equivalent to t18-status.c:statuscb0 */ +void test_status_worktree__whole_repository(void) +{ + status_entry_counts counts; + git_repository *repo = cl_git_sandbox_init("status"); + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = entry_count0; + counts.expected_paths = entry_paths0; + counts.expected_statuses = entry_statuses0; + + cl_git_pass( + git_status_foreach(repo, cb_status__normal, &counts) + ); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +static void assert_show( + const int entry_counts, + const char *entry_paths[], + const unsigned int entry_statuses[], + git_repository *repo, + git_status_show_t show, + unsigned int extra_flags) +{ + status_entry_counts counts; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = entry_counts; + counts.expected_paths = entry_paths; + counts.expected_statuses = entry_statuses; + + opts.flags = GIT_STATUS_OPT_DEFAULTS | extra_flags; + opts.show = show; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) + ); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +void test_status_worktree__show_index_and_workdir(void) +{ + assert_show(entry_count0, entry_paths0, entry_statuses0, + cl_git_sandbox_init("status"), GIT_STATUS_SHOW_INDEX_AND_WORKDIR, 0); +} + +void test_status_worktree__show_index_only(void) +{ + assert_show(entry_count5, entry_paths5, entry_statuses5, + cl_git_sandbox_init("status"), GIT_STATUS_SHOW_INDEX_ONLY, 0); +} + +void test_status_worktree__show_workdir_only(void) +{ + assert_show(entry_count6, entry_paths6, entry_statuses6, + cl_git_sandbox_init("status"), GIT_STATUS_SHOW_WORKDIR_ONLY, 0); +} + +/* this test is equivalent to t18-status.c:statuscb1 */ +void test_status_worktree__empty_repository(void) +{ + int count = 0; + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_status_foreach(repo, cb_status__count, &count)); + + cl_assert_equal_i(0, count); +} + +static int remove_file_cb(void *data, git_str *file) +{ + const char *filename = git_str_cstr(file); + + GIT_UNUSED(data); + + if (git__suffixcmp(filename, ".git") == 0) + return 0; + + if (git_fs_path_isdir(filename)) + cl_git_pass(git_futils_rmdir_r(filename, NULL, GIT_RMDIR_REMOVE_FILES)); + else + cl_git_pass(p_unlink(git_str_cstr(file))); + + return 0; +} + +/* this test is equivalent to t18-status.c:statuscb2 */ +void test_status_worktree__purged_worktree(void) +{ + status_entry_counts counts; + git_repository *repo = cl_git_sandbox_init("status"); + git_str workdir = GIT_STR_INIT; + + /* first purge the contents of the worktree */ + cl_git_pass(git_str_sets(&workdir, git_repository_workdir(repo))); + cl_git_pass(git_fs_path_direach(&workdir, 0, remove_file_cb, NULL)); + git_str_dispose(&workdir); + + /* now get status */ + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = entry_count2; + counts.expected_paths = entry_paths2; + counts.expected_statuses = entry_statuses2; + + cl_git_pass( + git_status_foreach(repo, cb_status__normal, &counts) + ); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +/* this test is similar to t18-status.c:statuscb3 */ +void test_status_worktree__swap_subdir_and_file(void) +{ + status_entry_counts counts; + git_repository *repo = cl_git_sandbox_init("status"); + git_index *index; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + bool ignore_case; + + cl_git_pass(git_repository_index(&index, repo)); + ignore_case = (git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0; + git_index_free(index); + + /* first alter the contents of the worktree */ + cl_git_pass(p_rename("status/current_file", "status/swap")); + cl_git_pass(p_rename("status/subdir", "status/current_file")); + cl_git_pass(p_rename("status/swap", "status/subdir")); + + cl_git_mkfile("status/.HEADER", "dummy"); + cl_git_mkfile("status/42-is-not-prime.sigh", "dummy"); + cl_git_mkfile("status/README.md", "dummy"); + + /* now get status */ + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = entry_count3; + counts.expected_paths = ignore_case ? entry_paths3_icase : entry_paths3; + counts.expected_statuses = ignore_case ? entry_statuses3_icase : entry_statuses3; + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_INCLUDE_IGNORED; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) + ); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +void test_status_worktree__swap_subdir_with_recurse_and_pathspec(void) +{ + status_entry_counts counts; + git_repository *repo = cl_git_sandbox_init("status"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + + /* first alter the contents of the worktree */ + cl_git_pass(p_rename("status/current_file", "status/swap")); + cl_git_pass(p_rename("status/subdir", "status/current_file")); + cl_git_pass(p_rename("status/swap", "status/subdir")); + cl_git_mkfile("status/.new_file", "dummy"); + cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", 0777)); + cl_git_mkfile("status/zzz_new_dir/new_file", "dummy"); + cl_git_mkfile("status/zzz_new_file", "dummy"); + + /* now get status */ + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = entry_count4; + counts.expected_paths = entry_paths4; + counts.expected_statuses = entry_statuses4; + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + /* TODO: set pathspec to "current_file" eventually */ + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) + ); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +static void stage_and_commit(git_repository *repo, const char *path) +{ + git_index *index; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, path)); + cl_repo_commit_from_index(NULL, repo, NULL, 1323847743, "Initial commit\n"); + git_index_free(index); +} + +void test_status_worktree__within_subdir(void) +{ + status_entry_counts counts; + git_repository *repo = cl_git_sandbox_init("status"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + char *paths[] = { "zzz_new_dir" }; + git_strarray pathsArray; + + /* first alter the contents of the worktree */ + cl_git_mkfile("status/.new_file", "dummy"); + cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", 0777)); + cl_git_mkfile("status/zzz_new_dir/new_file", "dummy"); + cl_git_mkfile("status/zzz_new_file", "dummy"); + cl_git_mkfile("status/wut", "dummy"); + + stage_and_commit(repo, "zzz_new_dir/new_file"); + + /* now get status */ + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = entry_count4; + counts.expected_paths = entry_paths4; + counts.expected_statuses = entry_statuses4; + counts.debug = true; + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; + + pathsArray.count = 1; + pathsArray.strings = paths; + opts.pathspec = pathsArray; + + /* We committed zzz_new_dir/new_file above. It shouldn't be reported. */ + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) + ); + + cl_assert_equal_i(0, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +/* this test is equivalent to t18-status.c:singlestatus0 */ +void test_status_worktree__single_file(void) +{ + int i; + unsigned int status_flags; + git_repository *repo = cl_git_sandbox_init("status"); + + for (i = 0; i < (int)entry_count0; i++) { + cl_git_pass( + git_status_file(&status_flags, repo, entry_paths0[i]) + ); + cl_assert(entry_statuses0[i] == status_flags); + } +} + +/* this test is equivalent to t18-status.c:singlestatus1 */ +void test_status_worktree__single_nonexistent_file(void) +{ + int error; + unsigned int status_flags; + git_repository *repo = cl_git_sandbox_init("status"); + + error = git_status_file(&status_flags, repo, "nonexistent"); + cl_git_fail(error); + cl_assert(error == GIT_ENOTFOUND); +} + +/* this test is equivalent to t18-status.c:singlestatus2 */ +void test_status_worktree__single_nonexistent_file_empty_repo(void) +{ + int error; + unsigned int status_flags; + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + + error = git_status_file(&status_flags, repo, "nonexistent"); + cl_git_fail(error); + cl_assert(error == GIT_ENOTFOUND); +} + +/* this test is equivalent to t18-status.c:singlestatus3 */ +void test_status_worktree__single_file_empty_repo(void) +{ + unsigned int status_flags; + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile("empty_standard_repo/new_file", "new_file\n"); + + cl_git_pass(git_status_file(&status_flags, repo, "new_file")); + cl_assert(status_flags == GIT_STATUS_WT_NEW); +} + +/* this test is equivalent to t18-status.c:singlestatus4 */ +void test_status_worktree__single_folder(void) +{ + int error; + unsigned int status_flags; + git_repository *repo = cl_git_sandbox_init("status"); + + error = git_status_file(&status_flags, repo, "subdir"); + cl_git_fail(error); + cl_assert(error != GIT_ENOTFOUND); +} + + +void test_status_worktree__ignores(void) +{ + int i, ignored; + git_repository *repo = cl_git_sandbox_init("status"); + + for (i = 0; i < (int)entry_count0; i++) { + cl_git_pass( + git_status_should_ignore(&ignored, repo, entry_paths0[i]) + ); + cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_IGNORED)); + } + + cl_git_pass( + git_status_should_ignore(&ignored, repo, "nonexistent_file") + ); + cl_assert(!ignored); + + cl_git_pass( + git_status_should_ignore(&ignored, repo, "ignored_nonexistent_file") + ); + cl_assert(ignored); +} + +static int cb_status__check_592(const char *p, unsigned int s, void *payload) +{ + if (s != GIT_STATUS_WT_DELETED || + (payload != NULL && strcmp(p, (const char *)payload) != 0)) + return -1; + + return 0; +} + +void test_status_worktree__issue_592(void) +{ + git_repository *repo; + git_str path = GIT_STR_INIT; + + repo = cl_git_sandbox_init("issue_592"); + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "l.txt")); + cl_git_pass(p_unlink(git_str_cstr(&path))); + cl_assert(!git_fs_path_exists("issue_592/l.txt")); + + cl_git_pass(git_status_foreach(repo, cb_status__check_592, "l.txt")); + + git_str_dispose(&path); +} + +void test_status_worktree__issue_592_2(void) +{ + git_repository *repo; + git_str path = GIT_STR_INIT; + + repo = cl_git_sandbox_init("issue_592"); + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "c/a.txt")); + cl_git_pass(p_unlink(git_str_cstr(&path))); + cl_assert(!git_fs_path_exists("issue_592/c/a.txt")); + + cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt")); + + git_str_dispose(&path); +} + +void test_status_worktree__issue_592_3(void) +{ + git_repository *repo; + git_str path = GIT_STR_INIT; + + repo = cl_git_sandbox_init("issue_592"); + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "c")); + cl_git_pass(git_futils_rmdir_r(git_str_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES)); + cl_assert(!git_fs_path_exists("issue_592/c/a.txt")); + + cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt")); + + git_str_dispose(&path); +} + +void test_status_worktree__issue_592_4(void) +{ + git_repository *repo; + git_str path = GIT_STR_INIT; + + repo = cl_git_sandbox_init("issue_592"); + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "t/b.txt")); + cl_git_pass(p_unlink(git_str_cstr(&path))); + + cl_git_pass(git_status_foreach(repo, cb_status__check_592, "t/b.txt")); + + git_str_dispose(&path); +} + +void test_status_worktree__issue_592_5(void) +{ + git_repository *repo; + git_str path = GIT_STR_INIT; + + repo = cl_git_sandbox_init("issue_592"); + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "t")); + cl_git_pass(git_futils_rmdir_r(git_str_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(p_mkdir(git_str_cstr(&path), 0777)); + + cl_git_pass(git_status_foreach(repo, cb_status__check_592, NULL)); + + git_str_dispose(&path); +} + +void test_status_worktree__issue_592_ignores_0(void) +{ + int count = 0; + status_entry_single st; + git_repository *repo = cl_git_sandbox_init("issue_592"); + + cl_git_pass(git_status_foreach(repo, cb_status__count, &count)); + cl_assert_equal_i(0, count); + + cl_git_rewritefile("issue_592/.gitignore", + ".gitignore\n*.txt\nc/\n[tT]*/\n"); + + cl_git_pass(git_status_foreach(repo, cb_status__count, &count)); + cl_assert_equal_i(1, count); + + /* This is a situation where the behavior of libgit2 is + * different from core git. Core git will show ignored.txt + * in the list of ignored files, even though the directory + * "t" is ignored and the file is untracked because we have + * the explicit "*.txt" ignore rule. Libgit2 just excludes + * all untracked files that are contained within ignored + * directories without explicitly listing them. + */ + cl_git_rewritefile("issue_592/t/ignored.txt", "ping"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &st)); + cl_assert_equal_i(1, st.count); + cl_assert(st.status == GIT_STATUS_IGNORED); + + cl_git_rewritefile("issue_592/c/ignored_by_dir", "ping"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &st)); + cl_assert_equal_i(1, st.count); + cl_assert(st.status == GIT_STATUS_IGNORED); + + cl_git_rewritefile("issue_592/t/ignored_by_dir_pattern", "ping"); + + memset(&st, 0, sizeof(st)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &st)); + cl_assert_equal_i(1, st.count); + cl_assert(st.status == GIT_STATUS_IGNORED); +} + +void test_status_worktree__issue_592_ignored_dirs_with_tracked_content(void) +{ + int count = 0; + git_repository *repo = cl_git_sandbox_init("issue_592b"); + + cl_git_pass(git_status_foreach(repo, cb_status__count, &count)); + cl_assert_equal_i(1, count); + + /* if we are really mimicking core git, then only ignored1.txt + * at the top level will show up in the ignores list here. + * everything else will be unmodified or skipped completely. + */ +} + +void test_status_worktree__conflict_with_diff3(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_index *index; + unsigned int status; + git_index_entry ancestor_entry, our_entry, their_entry; + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = "modified_file"; + ancestor_entry.mode = 0100644; + git_oid_fromstr(&ancestor_entry.id, + "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); + + our_entry.path = "modified_file"; + our_entry.mode = 0100644; + git_oid_fromstr(&our_entry.id, + "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); + + their_entry.path = "modified_file"; + their_entry.mode = 0100644; + git_oid_fromstr(&their_entry.id, + "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); + + cl_git_pass(git_status_file(&status, repo, "modified_file")); + cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_remove(index, "modified_file", 0)); + cl_git_pass(git_index_conflict_add( + index, &ancestor_entry, &our_entry, &their_entry)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_status_file(&status, repo, "modified_file")); + + cl_assert_equal_i(GIT_STATUS_CONFLICTED, status); +} + +static const char *filemode_paths[] = { + "exec_off", + "exec_off2on_staged", + "exec_off2on_workdir", + "exec_off_untracked", + "exec_on", + "exec_on2off_staged", + "exec_on2off_workdir", + "exec_on_untracked", +}; + +static unsigned int filemode_statuses[] = { + GIT_STATUS_CURRENT, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW, + GIT_STATUS_CURRENT, + GIT_STATUS_INDEX_MODIFIED, + GIT_STATUS_WT_MODIFIED, + GIT_STATUS_WT_NEW +}; + +static const int filemode_count = 8; + +void test_status_worktree__filemode_changes(void) +{ + git_repository *repo = cl_git_sandbox_init("filemodes"); + status_entry_counts counts; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + + /* overwrite stored filemode with platform appropriate value */ + if (cl_is_chmod_supported()) + cl_repo_set_bool(repo, "core.filemode", true); + else { + int i; + + cl_repo_set_bool(repo, "core.filemode", false); + + /* won't trust filesystem mode diffs, so these will appear unchanged */ + for (i = 0; i < filemode_count; ++i) + if (filemode_statuses[i] == GIT_STATUS_WT_MODIFIED) + filemode_statuses[i] = GIT_STATUS_CURRENT; + } + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED; + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = filemode_count; + counts.expected_paths = filemode_paths; + counts.expected_statuses = filemode_statuses; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) + ); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +void test_status_worktree__filemode_non755(void) +{ + git_repository *repo = cl_git_sandbox_init("filemodes"); + status_entry_counts counts; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + git_str executable_path = GIT_STR_INIT; + git_str nonexecutable_path = GIT_STR_INIT; + + if (!cl_is_chmod_supported()) + return; + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED; + + git_str_joinpath(&executable_path, git_repository_workdir(repo), "exec_on"); + cl_must_pass(p_chmod(git_str_cstr(&executable_path), 0744)); + git_str_dispose(&executable_path); + + git_str_joinpath(&nonexecutable_path, git_repository_workdir(repo), "exec_off"); + + cl_must_pass(p_chmod(git_str_cstr(&nonexecutable_path), 0655)); + git_str_dispose(&nonexecutable_path); + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = filemode_count; + counts.expected_paths = filemode_paths; + counts.expected_statuses = filemode_statuses; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) + ); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + + +static int cb_status__interrupt(const char *p, unsigned int s, void *payload) +{ + volatile int *count = (int *)payload; + + GIT_UNUSED(p); + GIT_UNUSED(s); + + (*count)++; + + return (*count == 8) ? -111 : 0; +} + +void test_status_worktree__interruptable_foreach(void) +{ + int count = 0; + git_repository *repo = cl_git_sandbox_init("status"); + + cl_assert_equal_i( + -111, git_status_foreach(repo, cb_status__interrupt, &count) + ); + + cl_assert_equal_i(8, count); +} + +void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + unsigned int status; + + cl_repo_set_bool(repo, "core.autocrlf", true); + + cl_git_rewritefile("status/current_file", "current_file\r\n"); + + cl_git_pass(git_status_file(&status, repo, "current_file")); + + /* stat data on file should no longer match stat cache, even though + * file diff will be empty because of line-ending conversion - matches + * the Git command-line behavior here. + */ + cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status); +} + +void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf_issue_1397(void) +{ + git_repository *repo = cl_git_sandbox_init("issue_1397"); + unsigned int status; + + cl_repo_set_bool(repo, "core.autocrlf", true); + + cl_git_pass(git_status_file(&status, repo, "crlf_file.txt")); + + cl_assert_equal_i(GIT_STATUS_CURRENT, status); +} + +void test_status_worktree__conflicted_item(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_index *index; + unsigned int status; + git_index_entry ancestor_entry, our_entry, their_entry; + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.mode = 0100644; + ancestor_entry.path = "modified_file"; + git_oid_fromstr(&ancestor_entry.id, + "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); + + our_entry.mode = 0100644; + our_entry.path = "modified_file"; + git_oid_fromstr(&our_entry.id, + "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); + + their_entry.mode = 0100644; + their_entry.path = "modified_file"; + git_oid_fromstr(&their_entry.id, + "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); + + cl_git_pass(git_status_file(&status, repo, "modified_file")); + cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_conflict_add(index, &ancestor_entry, + &our_entry, &their_entry)); + + cl_git_pass(git_status_file(&status, repo, "modified_file")); + cl_assert_equal_i(GIT_STATUS_CONFLICTED, status); + + git_index_free(index); +} + +void test_status_worktree__conflict_has_no_oid(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_index *index; + git_index_entry entry = {{0}}; + git_status_list *statuslist; + const git_status_entry *status; + git_oid zero_id = {{0}}; + + entry.mode = 0100644; + entry.path = "modified_file"; + git_oid_fromstr(&entry.id, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_conflict_add(index, &entry, &entry, &entry)); + + git_status_list_new(&statuslist, repo, NULL); + + cl_assert_equal_i(16, git_status_list_entrycount(statuslist)); + + status = git_status_byindex(statuslist, 2); + + cl_assert_equal_i(GIT_STATUS_CONFLICTED, status->status); + cl_assert_equal_s("modified_file", status->head_to_index->old_file.path); + cl_assert(!git_oid_equal(&zero_id, &status->head_to_index->old_file.id)); + cl_assert(0 != status->head_to_index->old_file.mode); + cl_assert_equal_s("modified_file", status->head_to_index->new_file.path); + cl_assert_equal_oid(&zero_id, &status->head_to_index->new_file.id); + cl_assert_equal_i(0, status->head_to_index->new_file.mode); + cl_assert_equal_i(0, status->head_to_index->new_file.size); + + cl_assert_equal_s("modified_file", status->index_to_workdir->old_file.path); + cl_assert_equal_oid(&zero_id, &status->index_to_workdir->old_file.id); + cl_assert_equal_i(0, status->index_to_workdir->old_file.mode); + cl_assert_equal_i(0, status->index_to_workdir->old_file.size); + cl_assert_equal_s("modified_file", status->index_to_workdir->new_file.path); + cl_assert( + !git_oid_equal(&zero_id, &status->index_to_workdir->new_file.id) || + !(status->index_to_workdir->new_file.flags & GIT_DIFF_FLAG_VALID_ID)); + cl_assert(0 != status->index_to_workdir->new_file.mode); + cl_assert(0 != status->index_to_workdir->new_file.size); + + git_index_free(index); + git_status_list_free(statuslist); +} + +static void assert_ignore_case( + bool should_ignore_case, + int expected_lower_cased_file_status, + int expected_camel_cased_file_status) +{ + unsigned int status; + git_str lower_case_path = GIT_STR_INIT, camel_case_path = GIT_STR_INIT; + git_repository *repo, *repo2; + + repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt"); + + cl_repo_set_bool(repo, "core.ignorecase", should_ignore_case); + + cl_git_pass(git_str_joinpath(&lower_case_path, + git_repository_workdir(repo), "plop")); + + cl_git_mkfile(git_str_cstr(&lower_case_path), ""); + + stage_and_commit(repo, "plop"); + + cl_git_pass(git_repository_open(&repo2, "./empty_standard_repo")); + + cl_git_pass(git_status_file(&status, repo2, "plop")); + cl_assert_equal_i(GIT_STATUS_CURRENT, status); + + cl_git_pass(git_str_joinpath(&camel_case_path, + git_repository_workdir(repo), "Plop")); + + cl_git_pass(p_rename(git_str_cstr(&lower_case_path), git_str_cstr(&camel_case_path))); + + cl_git_pass(git_status_file(&status, repo2, "plop")); + cl_assert_equal_i(expected_lower_cased_file_status, status); + + cl_git_pass(git_status_file(&status, repo2, "Plop")); + cl_assert_equal_i(expected_camel_cased_file_status, status); + + git_repository_free(repo2); + git_str_dispose(&lower_case_path); + git_str_dispose(&camel_case_path); +} + +void test_status_worktree__file_status_honors_core_ignorecase_true(void) +{ + assert_ignore_case(true, GIT_STATUS_CURRENT, GIT_STATUS_CURRENT); +} + +void test_status_worktree__file_status_honors_core_ignorecase_false(void) +{ + assert_ignore_case(false, GIT_STATUS_WT_DELETED, GIT_STATUS_WT_NEW); +} + +void test_status_worktree__file_status_honors_case_ignorecase_regarding_untracked_files(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + unsigned int status; + git_index *index; + + cl_repo_set_bool(repo, "core.ignorecase", false); + + repo = cl_git_sandbox_reopen(); + + /* Actually returns GIT_STATUS_IGNORED on Windows */ + cl_git_fail_with(git_status_file(&status, repo, "NEW_FILE"), GIT_ENOTFOUND); + + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_pass(git_index_add_bypath(index, "new_file")); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + /* Actually returns GIT_STATUS_IGNORED on Windows */ + cl_git_fail_with(git_status_file(&status, repo, "NEW_FILE"), GIT_ENOTFOUND); +} + +void test_status_worktree__simple_delete(void) +{ + git_repository *repo = cl_git_sandbox_init("renames"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + int count; + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH | + GIT_STATUS_OPT_EXCLUDE_SUBMODULES | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + count = 0; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__count, &count) ); + cl_assert_equal_i(0, count); + + cl_must_pass(p_unlink("renames/untimely.txt")); + + count = 0; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__count, &count) ); + cl_assert_equal_i(1, count); +} + +void test_status_worktree__simple_delete_indexed(void) +{ + git_repository *repo = cl_git_sandbox_init("renames"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + git_status_list *status; + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH | + GIT_STATUS_OPT_EXCLUDE_SUBMODULES | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + cl_git_pass(git_status_list_new(&status, repo, &opts)); + cl_assert_equal_sz(0, git_status_list_entrycount(status)); + git_status_list_free(status); + + cl_must_pass(p_unlink("renames/untimely.txt")); + + cl_git_pass(git_status_list_new(&status, repo, &opts)); + cl_assert_equal_sz(1, git_status_list_entrycount(status)); + cl_assert_equal_i( + GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status); + git_status_list_free(status); +} + +static const char *icase_paths[] = { "B", "c", "g", "H" }; +static unsigned int icase_statuses[] = { + GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED, +}; + +static const char *case_paths[] = { "B", "H", "c", "g" }; +static unsigned int case_statuses[] = { + GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_DELETED, GIT_STATUS_WT_MODIFIED, +}; + +void test_status_worktree__sorting_by_case(void) +{ + git_repository *repo = cl_git_sandbox_init("icase"); + git_index *index; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + bool native_ignore_case; + status_entry_counts counts; + + cl_git_pass(git_repository_index(&index, repo)); + native_ignore_case = + (git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0; + git_index_free(index); + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = 0; + counts.expected_paths = NULL; + counts.expected_statuses = NULL; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + + cl_git_rewritefile("icase/B", "new stuff"); + cl_must_pass(p_unlink("icase/c")); + cl_git_rewritefile("icase/g", "new stuff"); + cl_must_pass(p_unlink("icase/H")); + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = 4; + if (native_ignore_case) { + counts.expected_paths = icase_paths; + counts.expected_statuses = icase_statuses; + } else { + counts.expected_paths = case_paths; + counts.expected_statuses = case_statuses; + } + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + + opts.flags = GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = 4; + counts.expected_paths = case_paths; + counts.expected_statuses = case_statuses; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + + opts.flags = GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY; + + memset(&counts, 0, sizeof(counts)); + counts.expected_entry_count = 4; + counts.expected_paths = icase_paths; + counts.expected_statuses = icase_statuses; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +void test_status_worktree__long_filenames(void) +{ + char path[260*4+1] = {0}; + const char *expected_paths[] = {path}; + const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; + + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts = {0}; + + /* Create directory with amazingly long filename */ + sprintf(path, "empty_standard_repo/%s", longname); + cl_git_pass(git_futils_mkdir_r(path, 0777)); + sprintf(path, "empty_standard_repo/%s/foo", longname); + cl_git_mkfile(path, "dummy"); + + sprintf(path, "%s/foo", longname); + counts.expected_entry_count = 1; + counts.expected_paths = expected_paths; + counts.expected_statuses = expected_statuses; + + opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; + opts.flags = GIT_STATUS_OPT_DEFAULTS; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +/* The update stat cache tests mostly just mirror other tests and try + * to make sure that updating the stat cache doesn't change the results + * while reducing the amount of work that needs to be done + */ + +static void check_status0(git_status_list *status) +{ + size_t i, max_i = git_status_list_entrycount(status); + cl_assert_equal_sz(entry_count0, max_i); + for (i = 0; i < max_i; ++i) { + const git_status_entry *entry = git_status_byindex(status, i); + cl_assert_equal_i(entry_statuses0[i], entry->status); + } +} + +void test_status_worktree__update_stat_cache_0(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + git_status_list *status; + git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT; + git_index *index; + + opts.flags = GIT_STATUS_OPT_DEFAULTS; + + cl_git_pass(git_status_list_new(&status, repo, &opts)); + check_status0(status); + cl_git_pass(git_status_list_get_perfdata(&perf, status)); + cl_assert_equal_sz(13 + 3, perf.stat_calls); + cl_assert_equal_sz(5, perf.oid_calculations); + + git_status_list_free(status); + + /* tick the index so we avoid recalculating racily-clean entries */ + cl_git_pass(git_repository_index__weakptr(&index, repo)); + tick_index(index); + + opts.flags |= GIT_STATUS_OPT_UPDATE_INDEX; + + cl_git_pass(git_status_list_new(&status, repo, &opts)); + check_status0(status); + cl_git_pass(git_status_list_get_perfdata(&perf, status)); + cl_assert_equal_sz(13 + 3, perf.stat_calls); + cl_assert_equal_sz(5, perf.oid_calculations); + + git_status_list_free(status); + + opts.flags &= ~GIT_STATUS_OPT_UPDATE_INDEX; + + /* tick again as the index updating from the previous diff might have reset the timestamp */ + tick_index(index); + cl_git_pass(git_status_list_new(&status, repo, &opts)); + check_status0(status); + cl_git_pass(git_status_list_get_perfdata(&perf, status)); + cl_assert_equal_sz(13 + 3, perf.stat_calls); + cl_assert_equal_sz(0, perf.oid_calculations); + + git_status_list_free(status); +} + +void test_status_worktree__unreadable(void) +{ +#ifndef GIT_WIN32 + const char *expected_paths[] = { "no_permission/foo" }; + const unsigned int expected_statuses[] = {GIT_STATUS_WT_UNREADABLE}; + + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts = {0}; + + if (geteuid() == 0) + cl_skip(); + + /* Create directory with no read permission */ + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777)); + cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); + p_chmod("empty_standard_repo/no_permission", 0644); + + counts.expected_entry_count = 1; + counts.expected_paths = expected_paths; + counts.expected_statuses = expected_statuses; + + opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; + opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_INCLUDE_UNREADABLE; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); + + /* Restore permissions so we can cleanup :) */ + p_chmod("empty_standard_repo/no_permission", 0777); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +#else + cl_skip(); +#endif +} + +void test_status_worktree__unreadable_not_included(void) +{ +#ifndef GIT_WIN32 + const char *expected_paths[] = { "no_permission/" }; + const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; + + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts = {0}; + + /* Create directory with no read permission */ + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777)); + cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); + p_chmod("empty_standard_repo/no_permission", 0644); + + counts.expected_entry_count = 1; + counts.expected_paths = expected_paths; + counts.expected_statuses = expected_statuses; + + opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; + opts.flags = (GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_INCLUDE_UNTRACKED); + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); + + /* Restore permissions so we can cleanup :) */ + p_chmod("empty_standard_repo/no_permission", 0777); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +#else + cl_skip(); +#endif +} + +void test_status_worktree__unreadable_as_untracked(void) +{ + const char *expected_paths[] = { "no_permission/foo" }; + const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; + + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts = {0}; + + /* Create directory with no read permission */ + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777)); + cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); + p_chmod("empty_standard_repo/no_permission", 0644); + + counts.expected_entry_count = 1; + counts.expected_paths = expected_paths; + counts.expected_statuses = expected_statuses; + + opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; + opts.flags = GIT_STATUS_OPT_DEFAULTS | + GIT_STATUS_OPT_INCLUDE_UNREADABLE | + GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); + + /* Restore permissions so we can cleanup :) */ + p_chmod("empty_standard_repo/no_permission", 0777); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + +void test_status_worktree__update_index_with_symlink_doesnt_change_mode(void) +{ + git_repository *repo = cl_git_sandbox_init("testrepo"); + git_reference *head; + git_object *head_object; + git_index *index; + const git_index_entry *idx_entry; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts = {0}; + const char *expected_paths[] = { "README" }; + const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_UPDATE_INDEX; + + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT)); + + cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); + + cl_git_rewritefile("testrepo/README", "This was rewritten."); + + /* this status rewrites the index because we have changed the + * contents of a tracked file + */ + counts.expected_entry_count = 1; + counts.expected_paths = expected_paths; + counts.expected_statuses = expected_statuses; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); + cl_assert_equal_i(1, counts.entry_count); + + /* now ensure that the status's rewrite of the index did not screw + * up the mode of the symlink `link_to_new.txt`, particularly + * on platforms that don't support symlinks + */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_read(index, true)); + + cl_assert(idx_entry = git_index_get_bypath(index, "link_to_new.txt", 0)); + cl_assert(S_ISLNK(idx_entry->mode)); + + git_index_free(index); + git_object_free(head_object); + git_reference_free(head); +} + +static const char *testrepo2_subdir_paths[] = { + "subdir/README", + "subdir/new.txt", + "subdir/subdir2/README", + "subdir/subdir2/new.txt", +}; + +static const char *testrepo2_subdir_paths_icase[] = { + "subdir/new.txt", + "subdir/README", + "subdir/subdir2/new.txt", + "subdir/subdir2/README" +}; + +void test_status_worktree__with_directory_in_pathlist(void) +{ + git_repository *repo = cl_git_sandbox_init("testrepo2"); + git_index *index; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + git_status_list *statuslist; + const git_status_entry *status; + size_t i, entrycount; + bool native_ignore_case; + char *subdir_path = "subdir"; + + cl_git_pass(git_repository_index(&index, repo)); + native_ignore_case = + (git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0; + git_index_free(index); + + opts.pathspec.strings = &subdir_path; + opts.pathspec.count = 1; + opts.flags = + GIT_STATUS_OPT_DEFAULTS | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; + + opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; + git_status_list_new(&statuslist, repo, &opts); + + entrycount = git_status_list_entrycount(statuslist); + cl_assert_equal_i(4, entrycount); + + for (i = 0; i < entrycount; i++) { + status = git_status_byindex(statuslist, i); + cl_assert_equal_i(0, status->status); + cl_assert_equal_s(native_ignore_case ? + testrepo2_subdir_paths_icase[i] : + testrepo2_subdir_paths[i], + status->index_to_workdir->old_file.path); + } + + git_status_list_free(statuslist); + + opts.show = GIT_STATUS_SHOW_INDEX_ONLY; + git_status_list_new(&statuslist, repo, &opts); + + entrycount = git_status_list_entrycount(statuslist); + cl_assert_equal_i(4, entrycount); + + for (i = 0; i < entrycount; i++) { + status = git_status_byindex(statuslist, i); + cl_assert_equal_i(0, status->status); + cl_assert_equal_s(native_ignore_case ? + testrepo2_subdir_paths_icase[i] : + testrepo2_subdir_paths[i], + status->head_to_index->old_file.path); + } + + git_status_list_free(statuslist); + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + git_status_list_new(&statuslist, repo, &opts); + + entrycount = git_status_list_entrycount(statuslist); + cl_assert_equal_i(4, entrycount); + + for (i = 0; i < entrycount; i++) { + status = git_status_byindex(statuslist, i); + cl_assert_equal_i(0, status->status); + cl_assert_equal_s(native_ignore_case ? + testrepo2_subdir_paths_icase[i] : + testrepo2_subdir_paths[i], + status->index_to_workdir->old_file.path); + } + + git_status_list_free(statuslist); +} + +void test_status_worktree__at_head_parent(void) +{ + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + git_status_list *statuslist; + git_tree *parent_tree; + const git_status_entry *status; + + cl_git_mkfile("empty_standard_repo/file1", "ping"); + stage_and_commit(repo, "file1"); + + cl_git_pass(git_repository_head_tree(&parent_tree, repo)); + + cl_git_mkfile("empty_standard_repo/file2", "pong"); + stage_and_commit(repo, "file2"); + + cl_git_rewritefile("empty_standard_repo/file2", "pyng"); + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.baseline = parent_tree; + cl_git_pass(git_status_list_new(&statuslist, repo, &opts)); + + cl_assert_equal_sz(1, git_status_list_entrycount(statuslist)); + status = git_status_byindex(statuslist, 0); + cl_assert(status != NULL); + cl_assert_equal_s("file2", status->index_to_workdir->old_file.path); + cl_assert_equal_i(GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW, status->status); + + git_tree_free(parent_tree); + git_status_list_free(statuslist); +} diff --git a/tests/libgit2/status/worktree_init.c b/tests/libgit2/status/worktree_init.c new file mode 100644 index 000000000..40f1b2a31 --- /dev/null +++ b/tests/libgit2/status/worktree_init.c @@ -0,0 +1,338 @@ +#include "clar_libgit2.h" +#include "git2/sys/repository.h" + +#include "futils.h" +#include "ignore.h" +#include "status_helpers.h" +#include "posix.h" +#include "util.h" +#include "path.h" + +static void cleanup_new_repo(void *path) +{ + cl_fixture_cleanup((char *)path); +} + +void test_status_worktree_init__cannot_retrieve_the_status_of_a_bare_repository(void) +{ + git_repository *repo; + unsigned int status = 0; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + cl_assert_equal_i(GIT_EBAREREPO, git_status_file(&status, repo, "dummy")); + git_repository_free(repo); +} + +void test_status_worktree_init__first_commit_in_progress(void) +{ + git_repository *repo; + git_index *index; + status_entry_single result; + + cl_set_cleanup(&cleanup_new_repo, "getting_started"); + + cl_git_pass(git_repository_init(&repo, "getting_started", 0)); + cl_git_mkfile("getting_started/testfile.txt", "content\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(1, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, "testfile.txt")); + cl_git_pass(git_index_write(index)); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(1, result.count); + cl_assert(result.status == GIT_STATUS_INDEX_NEW); + + git_index_free(index); + git_repository_free(repo); +} + + + +void test_status_worktree_init__status_file_without_index_or_workdir(void) +{ + git_repository *repo; + unsigned int status = 0; + git_index *index; + + cl_git_pass(p_mkdir("wd", 0777)); + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_repository_set_workdir(repo, "wd", false)); + + cl_git_pass(git_index_open(&index, "empty-index")); + cl_assert_equal_i(0, (int)git_index_entrycount(index)); + git_repository_set_index(repo, index); + + cl_git_pass(git_status_file(&status, repo, "branch_file.txt")); + + cl_assert_equal_i(GIT_STATUS_INDEX_DELETED, status); + + git_repository_free(repo); + git_index_free(index); + cl_git_pass(p_rmdir("wd")); +} + +static void fill_index_wth_head_entries(git_repository *repo, git_index *index) +{ + git_oid oid; + git_commit *commit; + git_tree *tree; + + cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_commit_tree(&tree, commit)); + + cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_write(index)); + + git_tree_free(tree); + git_commit_free(commit); +} + +void test_status_worktree_init__status_file_with_clean_index_and_empty_workdir(void) +{ + git_repository *repo; + unsigned int status = 0; + git_index *index; + + cl_git_pass(p_mkdir("wd", 0777)); + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_repository_set_workdir(repo, "wd", false)); + + cl_git_pass(git_index_open(&index, "my-index")); + fill_index_wth_head_entries(repo, index); + + git_repository_set_index(repo, index); + + cl_git_pass(git_status_file(&status, repo, "branch_file.txt")); + + cl_assert_equal_i(GIT_STATUS_WT_DELETED, status); + + git_repository_free(repo); + git_index_free(index); + cl_git_pass(p_rmdir("wd")); + cl_git_pass(p_unlink("my-index")); +} + +void test_status_worktree_init__bracket_in_filename(void) +{ + git_repository *repo; + git_index *index; + status_entry_single result; + unsigned int status_flags; + + #define FILE_WITH_BRACKET "LICENSE[1].md" + #define FILE_WITHOUT_BRACKET "LICENSE1.md" + + cl_set_cleanup(&cleanup_new_repo, "with_bracket"); + + cl_git_pass(git_repository_init(&repo, "with_bracket", 0)); + cl_git_mkfile("with_bracket/" FILE_WITH_BRACKET, "I have a bracket in my name\n"); + + /* file is new to working directory */ + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(1, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* ignore the file */ + + cl_git_rewritefile("with_bracket/.gitignore", "*.md\n.gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_IGNORED); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_IGNORED); + + /* don't ignore the file */ + + cl_git_rewritefile("with_bracket/.gitignore", ".gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* add the file to the index */ + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, FILE_WITH_BRACKET)); + cl_git_pass(git_index_write(index)); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_INDEX_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + + /* Create file without bracket */ + + cl_git_mkfile("with_bracket/" FILE_WITHOUT_BRACKET, "I have no bracket in my name!\n"); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITHOUT_BRACKET)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + cl_git_fail_with(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md"), GIT_ENOTFOUND); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + + git_index_free(index); + git_repository_free(repo); +} + +void test_status_worktree_init__space_in_filename(void) +{ + git_repository *repo; + git_index *index; + status_entry_single result; + unsigned int status_flags; + +#define FILE_WITH_SPACE "LICENSE - copy.md" + + cl_set_cleanup(&cleanup_new_repo, "with_space"); + cl_git_pass(git_repository_init(&repo, "with_space", 0)); + cl_git_mkfile("with_space/" FILE_WITH_SPACE, "I have a space in my name\n"); + + /* file is new to working directory */ + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(1, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* ignore the file */ + + cl_git_rewritefile("with_space/.gitignore", "*.md\n.gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_IGNORED); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); + cl_assert(status_flags == GIT_STATUS_IGNORED); + + /* don't ignore the file */ + + cl_git_rewritefile("with_space/.gitignore", ".gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* add the file to the index */ + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, FILE_WITH_SPACE)); + cl_git_pass(git_index_write(index)); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_INDEX_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); + cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + + git_index_free(index); + git_repository_free(repo); +} + +static int cb_status__expected_path(const char *p, unsigned int s, void *payload) +{ + const char *expected_path = (const char *)payload; + + GIT_UNUSED(s); + + if (payload == NULL) + cl_fail("Unexpected path"); + + cl_assert_equal_s(expected_path, p); + + return 0; +} + +void test_status_worktree_init__disable_pathspec_match(void) +{ + git_repository *repo; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + char *file_with_bracket = "LICENSE[1].md", + *imaginary_file_with_bracket = "LICENSE[1-2].md"; + + cl_set_cleanup(&cleanup_new_repo, "pathspec"); + cl_git_pass(git_repository_init(&repo, "pathspec", 0)); + cl_git_mkfile("pathspec/LICENSE[1].md", "screaming bracket\n"); + cl_git_mkfile("pathspec/LICENSE1.md", "no bracket\n"); + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; + opts.pathspec.count = 1; + opts.pathspec.strings = &file_with_bracket; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__expected_path, + file_with_bracket) + ); + + /* Test passing a pathspec matching files in the workdir. */ + /* Must not match because pathspecs are disabled. */ + opts.pathspec.strings = &imaginary_file_with_bracket; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__expected_path, NULL) + ); + + git_repository_free(repo); +} + +void test_status_worktree_init__new_staged_file_must_handle_crlf(void) +{ + git_repository *repo; + git_index *index; + unsigned int status; + + cl_set_cleanup(&cleanup_new_repo, "getting_started"); + cl_git_pass(git_repository_init(&repo, "getting_started", 0)); + + /* Ensure that repo has core.autocrlf=true */ + cl_repo_set_bool(repo, "core.autocrlf", true); + + cl_git_mkfile("getting_started/testfile.txt", "content\r\n"); /* Content with CRLF */ + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, "testfile.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_file(&status, repo, "testfile.txt")); + cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status); + + git_index_free(index); + git_repository_free(repo); +} + diff --git a/tests/libgit2/str/basic.c b/tests/libgit2/str/basic.c new file mode 100644 index 000000000..5d2556805 --- /dev/null +++ b/tests/libgit2/str/basic.c @@ -0,0 +1,50 @@ +#include "clar_libgit2.h" + +static const char *test_string = "Have you seen that? Have you seeeen that??"; + +void test_str_basic__resize(void) +{ + git_str buf1 = GIT_STR_INIT; + git_str_puts(&buf1, test_string); + cl_assert(git_str_oom(&buf1) == 0); + cl_assert_equal_s(git_str_cstr(&buf1), test_string); + + git_str_puts(&buf1, test_string); + cl_assert(strlen(git_str_cstr(&buf1)) == strlen(test_string) * 2); + git_str_dispose(&buf1); +} + +void test_str_basic__resize_incremental(void) +{ + git_str buf1 = GIT_STR_INIT; + + /* Presently, asking for 6 bytes will round up to 8. */ + cl_git_pass(git_str_puts(&buf1, "Hello")); + cl_assert_equal_i(5, buf1.size); + cl_assert_equal_i(8, buf1.asize); + + /* Ensure an additional byte does not realloc. */ + cl_git_pass(git_str_grow_by(&buf1, 1)); + cl_assert_equal_i(5, buf1.size); + cl_assert_equal_i(8, buf1.asize); + + /* But requesting many does. */ + cl_git_pass(git_str_grow_by(&buf1, 16)); + cl_assert_equal_i(5, buf1.size); + cl_assert(buf1.asize > 8); + + git_str_dispose(&buf1); +} + +void test_str_basic__printf(void) +{ + git_str buf2 = GIT_STR_INIT; + git_str_printf(&buf2, "%s %s %d ", "shoop", "da", 23); + cl_assert(git_str_oom(&buf2) == 0); + cl_assert_equal_s(git_str_cstr(&buf2), "shoop da 23 "); + + git_str_printf(&buf2, "%s %d", "woop", 42); + cl_assert(git_str_oom(&buf2) == 0); + cl_assert_equal_s(git_str_cstr(&buf2), "shoop da 23 woop 42"); + git_str_dispose(&buf2); +} diff --git a/tests/libgit2/str/oom.c b/tests/libgit2/str/oom.c new file mode 100644 index 000000000..3d59ead01 --- /dev/null +++ b/tests/libgit2/str/oom.c @@ -0,0 +1,58 @@ +#include "clar_libgit2.h" + +/* Override default allocators with ones that will fail predictably. */ + +static git_allocator std_alloc; +static git_allocator oom_alloc; + +static void *oom_malloc(size_t n, const char *file, int line) +{ + /* Reject any allocation of more than 100 bytes */ + return (n > 100) ? NULL : std_alloc.gmalloc(n, file, line); +} + +static void *oom_realloc(void *p, size_t n, const char *file, int line) +{ + /* Reject any allocation of more than 100 bytes */ + return (n > 100) ? NULL : std_alloc.grealloc(p, n, file, line); +} + +void test_str_oom__initialize(void) +{ + git_stdalloc_init_allocator(&std_alloc); + git_stdalloc_init_allocator(&oom_alloc); + + oom_alloc.gmalloc = oom_malloc; + oom_alloc.grealloc = oom_realloc; + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ALLOCATOR, &oom_alloc)); +} + +void test_str_oom__cleanup(void) +{ + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ALLOCATOR, NULL)); +} + +void test_str_oom__grow(void) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_grow(&buf, 42)); + cl_assert(!git_str_oom(&buf)); + + cl_assert(git_str_grow(&buf, 101) == -1); + cl_assert(git_str_oom(&buf)); + + git_str_dispose(&buf); +} + +void test_str_oom__grow_by(void) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_grow_by(&buf, 42)); + cl_assert(!git_str_oom(&buf)); + + cl_assert(git_str_grow_by(&buf, 101) == -1); + cl_assert(git_str_oom(&buf)); +} diff --git a/tests/libgit2/str/percent.c b/tests/libgit2/str/percent.c new file mode 100644 index 000000000..339389075 --- /dev/null +++ b/tests/libgit2/str/percent.c @@ -0,0 +1,48 @@ +#include "clar_libgit2.h" + +static void expect_decode_pass(const char *expected, const char *encoded) +{ + git_str in = GIT_STR_INIT, out = GIT_STR_INIT; + + /* + * ensure that we only read the given length of the input buffer + * by putting garbage at the end. this will ensure that we do + * not, eg, rely on nul-termination or walk off the end of the buf. + */ + cl_git_pass(git_str_puts(&in, encoded)); + cl_git_pass(git_str_PUTS(&in, "TRAILER")); + + cl_git_pass(git_str_decode_percent(&out, in.ptr, strlen(encoded))); + + cl_assert_equal_s(expected, git_str_cstr(&out)); + cl_assert_equal_i(strlen(expected), git_str_len(&out)); + + git_str_dispose(&in); + git_str_dispose(&out); +} + +void test_str_percent__decode_succeeds(void) +{ + expect_decode_pass("", ""); + expect_decode_pass(" ", "%20"); + expect_decode_pass("a", "a"); + expect_decode_pass(" a", "%20a"); + expect_decode_pass("a ", "a%20"); + expect_decode_pass("github.com", "github.com"); + expect_decode_pass("github.com", "githu%62.com"); + expect_decode_pass("github.com", "github%2ecom"); + expect_decode_pass("foo bar baz", "foo%20bar%20baz"); + expect_decode_pass("foo bar baz", "foo%20bar%20baz"); + expect_decode_pass("foo bar ", "foo%20bar%20"); +} + +void test_str_percent__ignores_invalid(void) +{ + expect_decode_pass("githu%%.com", "githu%%.com"); + expect_decode_pass("github.co%2", "github.co%2"); + expect_decode_pass("github%2.com", "github%2.com"); + expect_decode_pass("githu%2z.com", "githu%2z.com"); + expect_decode_pass("github.co%9z", "github.co%9z"); + expect_decode_pass("github.co%2", "github.co%2"); + expect_decode_pass("github.co%", "github.co%"); +} diff --git a/tests/libgit2/str/quote.c b/tests/libgit2/str/quote.c new file mode 100644 index 000000000..2c6546247 --- /dev/null +++ b/tests/libgit2/str/quote.c @@ -0,0 +1,87 @@ +#include "clar_libgit2.h" + +static void expect_quote_pass(const char *expected, const char *str) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_puts(&buf, str)); + cl_git_pass(git_str_quote(&buf)); + + cl_assert_equal_s(expected, git_str_cstr(&buf)); + cl_assert_equal_i(strlen(expected), git_str_len(&buf)); + + git_str_dispose(&buf); +} + +void test_str_quote__quote_succeeds(void) +{ + expect_quote_pass("", ""); + expect_quote_pass("foo", "foo"); + expect_quote_pass("foo/bar/baz.c", "foo/bar/baz.c"); + expect_quote_pass("foo bar", "foo bar"); + expect_quote_pass("\"\\\"leading quote\"", "\"leading quote"); + expect_quote_pass("\"slash\\\\y\"", "slash\\y"); + expect_quote_pass("\"foo\\r\\nbar\"", "foo\r\nbar"); + expect_quote_pass("\"foo\\177bar\"", "foo\177bar"); + expect_quote_pass("\"foo\\001bar\"", "foo\001bar"); + expect_quote_pass("\"foo\\377bar\"", "foo\377bar"); +} + +static void expect_unquote_pass(const char *expected, const char *quoted) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_puts(&buf, quoted)); + cl_git_pass(git_str_unquote(&buf)); + + cl_assert_equal_s(expected, git_str_cstr(&buf)); + cl_assert_equal_i(strlen(expected), git_str_len(&buf)); + + git_str_dispose(&buf); +} + +static void expect_unquote_fail(const char *quoted) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_puts(&buf, quoted)); + cl_git_fail(git_str_unquote(&buf)); + + git_str_dispose(&buf); +} + +void test_str_quote__unquote_succeeds(void) +{ + expect_unquote_pass("", "\"\""); + expect_unquote_pass(" ", "\" \""); + expect_unquote_pass("foo", "\"foo\""); + expect_unquote_pass("foo bar", "\"foo bar\""); + expect_unquote_pass("foo\"bar", "\"foo\\\"bar\""); + expect_unquote_pass("foo\\bar", "\"foo\\\\bar\""); + expect_unquote_pass("foo\tbar", "\"foo\\tbar\""); + expect_unquote_pass("\vfoo\tbar\n", "\"\\vfoo\\tbar\\n\""); + expect_unquote_pass("foo\nbar", "\"foo\\012bar\""); + expect_unquote_pass("foo\r\nbar", "\"foo\\015\\012bar\""); + expect_unquote_pass("foo\r\nbar", "\"\\146\\157\\157\\015\\012\\142\\141\\162\""); + expect_unquote_pass("newline: \n", "\"newline: \\012\""); + expect_unquote_pass("0xff: \377", "\"0xff: \\377\""); +} + +void test_str_quote__unquote_fails(void) +{ + expect_unquote_fail("no quotes at all"); + expect_unquote_fail("\"no trailing quote"); + expect_unquote_fail("no leading quote\""); + expect_unquote_fail("\"invalid \\z escape char\""); + expect_unquote_fail("\"\\q invalid escape char\""); + expect_unquote_fail("\"invalid escape char \\p\""); + expect_unquote_fail("\"invalid \\1 escape char \""); + expect_unquote_fail("\"invalid \\14 escape char \""); + expect_unquote_fail("\"invalid \\280 escape char\""); + expect_unquote_fail("\"invalid \\378 escape char\""); + expect_unquote_fail("\"invalid \\380 escape char\""); + expect_unquote_fail("\"invalid \\411 escape char\""); + expect_unquote_fail("\"truncated escape char \\\""); + expect_unquote_fail("\"truncated escape char \\0\""); + expect_unquote_fail("\"truncated escape char \\01\""); +} diff --git a/tests/libgit2/str/splice.c b/tests/libgit2/str/splice.c new file mode 100644 index 000000000..14e844e2f --- /dev/null +++ b/tests/libgit2/str/splice.c @@ -0,0 +1,92 @@ +#include "clar_libgit2.h" + +static git_str _buf; + +void test_str_splice__initialize(void) { + git_str_init(&_buf, 16); +} + +void test_str_splice__cleanup(void) { + git_str_dispose(&_buf); +} + +void test_str_splice__preprend(void) +{ + git_str_sets(&_buf, "world!"); + + cl_git_pass(git_str_splice(&_buf, 0, 0, "Hello Dolly", strlen("Hello "))); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__append(void) +{ + git_str_sets(&_buf, "Hello"); + + cl_git_pass(git_str_splice(&_buf, git_str_len(&_buf), 0, " world!", strlen(" world!"))); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__insert_at(void) +{ + git_str_sets(&_buf, "Hell world!"); + + cl_git_pass(git_str_splice(&_buf, strlen("Hell"), 0, "o", strlen("o"))); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__remove_at(void) +{ + git_str_sets(&_buf, "Hello world of warcraft!"); + + cl_git_pass(git_str_splice(&_buf, strlen("Hello world"), strlen(" of warcraft"), "", 0)); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__replace(void) +{ + git_str_sets(&_buf, "Hell0 w0rld!"); + + cl_git_pass(git_str_splice(&_buf, strlen("Hell"), strlen("0 w0"), "o wo", strlen("o wo"))); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__replace_with_longer(void) +{ + git_str_sets(&_buf, "Hello you!"); + + cl_git_pass(git_str_splice(&_buf, strlen("Hello "), strlen("you"), "world", strlen("world"))); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__replace_with_shorter(void) +{ + git_str_sets(&_buf, "Brave new world!"); + + cl_git_pass(git_str_splice(&_buf, 0, strlen("Brave new"), "Hello", strlen("Hello"))); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__truncate(void) +{ + git_str_sets(&_buf, "Hello world!!"); + + cl_git_pass(git_str_splice(&_buf, strlen("Hello world!"), strlen("!"), "", 0)); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__dont_do_anything(void) +{ + git_str_sets(&_buf, "Hello world!"); + + cl_git_pass(git_str_splice(&_buf, 3, 0, "Hello", 0)); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} diff --git a/tests/libgit2/stream/deprecated.c b/tests/libgit2/stream/deprecated.c new file mode 100644 index 000000000..fecd1be03 --- /dev/null +++ b/tests/libgit2/stream/deprecated.c @@ -0,0 +1,60 @@ +#include "clar_libgit2.h" +#include "git2/sys/stream.h" +#include "streams/tls.h" +#include "streams/socket.h" +#include "stream.h" + +void test_stream_deprecated__cleanup(void) +{ + cl_git_pass(git_stream_register(GIT_STREAM_TLS | GIT_STREAM_STANDARD, NULL)); +} + +#ifndef GIT_DEPRECATE_HARD +static git_stream test_stream; +static int ctor_called; + +static int test_stream_init(git_stream **out, const char *host, const char *port) +{ + GIT_UNUSED(host); + GIT_UNUSED(port); + + ctor_called = 1; + *out = &test_stream; + + return 0; +} +#endif + +void test_stream_deprecated__register_tls(void) +{ +#ifndef GIT_DEPRECATE_HARD + git_stream *stream; + int error; + + ctor_called = 0; + cl_git_pass(git_stream_register_tls(test_stream_init)); + cl_git_pass(git_tls_stream_new(&stream, "localhost", "443")); + cl_assert_equal_i(1, ctor_called); + cl_assert_equal_p(&test_stream, stream); + + ctor_called = 0; + stream = NULL; + cl_git_pass(git_stream_register_tls(NULL)); + error = git_tls_stream_new(&stream, "localhost", "443"); + + /* + * We don't have TLS support enabled, or we're on Windows, + * which has no arbitrary TLS stream support. + */ +#if defined(GIT_WIN32) || !defined(GIT_HTTPS) + cl_git_fail_with(-1, error); +#else + cl_git_pass(error); +#endif + + cl_assert_equal_i(0, ctor_called); + cl_assert(&test_stream != stream); + + git_stream_free(stream); +#endif +} diff --git a/tests/libgit2/stream/registration.c b/tests/libgit2/stream/registration.c new file mode 100644 index 000000000..bf3c20502 --- /dev/null +++ b/tests/libgit2/stream/registration.c @@ -0,0 +1,119 @@ +#include "clar_libgit2.h" +#include "git2/sys/stream.h" +#include "streams/tls.h" +#include "streams/socket.h" +#include "stream.h" + +static git_stream test_stream; +static int ctor_called; + +void test_stream_registration__cleanup(void) +{ + cl_git_pass(git_stream_register(GIT_STREAM_TLS | GIT_STREAM_STANDARD, NULL)); +} + +static int test_stream_init(git_stream **out, const char *host, const char *port) +{ + GIT_UNUSED(host); + GIT_UNUSED(port); + + ctor_called = 1; + *out = &test_stream; + + return 0; +} + +static int test_stream_wrap(git_stream **out, git_stream *in, const char *host) +{ + GIT_UNUSED(in); + GIT_UNUSED(host); + + ctor_called = 1; + *out = &test_stream; + + return 0; +} + +void test_stream_registration__insecure(void) +{ + git_stream *stream; + git_stream_registration registration = {0}; + + registration.version = 1; + registration.init = test_stream_init; + registration.wrap = test_stream_wrap; + + ctor_called = 0; + cl_git_pass(git_stream_register(GIT_STREAM_STANDARD, ®istration)); + cl_git_pass(git_socket_stream_new(&stream, "localhost", "80")); + cl_assert_equal_i(1, ctor_called); + cl_assert_equal_p(&test_stream, stream); + + ctor_called = 0; + stream = NULL; + cl_git_pass(git_stream_register(GIT_STREAM_STANDARD, NULL)); + cl_git_pass(git_socket_stream_new(&stream, "localhost", "80")); + + cl_assert_equal_i(0, ctor_called); + cl_assert(&test_stream != stream); + + git_stream_free(stream); +} + +void test_stream_registration__tls(void) +{ + git_stream *stream; + git_stream_registration registration = {0}; + int error; + + registration.version = 1; + registration.init = test_stream_init; + registration.wrap = test_stream_wrap; + + ctor_called = 0; + cl_git_pass(git_stream_register(GIT_STREAM_TLS, ®istration)); + cl_git_pass(git_tls_stream_new(&stream, "localhost", "443")); + cl_assert_equal_i(1, ctor_called); + cl_assert_equal_p(&test_stream, stream); + + ctor_called = 0; + stream = NULL; + cl_git_pass(git_stream_register(GIT_STREAM_TLS, NULL)); + error = git_tls_stream_new(&stream, "localhost", "443"); + + /* We don't have TLS support enabled, or we're on Windows, + * which has no arbitrary TLS stream support. + */ +#if defined(GIT_WIN32) || !defined(GIT_HTTPS) + cl_git_fail_with(-1, error); +#else + cl_git_pass(error); +#endif + + cl_assert_equal_i(0, ctor_called); + cl_assert(&test_stream != stream); + + git_stream_free(stream); +} + +void test_stream_registration__both(void) +{ + git_stream *stream; + git_stream_registration registration = {0}; + + registration.version = 1; + registration.init = test_stream_init; + registration.wrap = test_stream_wrap; + + cl_git_pass(git_stream_register(GIT_STREAM_STANDARD | GIT_STREAM_TLS, ®istration)); + + ctor_called = 0; + cl_git_pass(git_tls_stream_new(&stream, "localhost", "443")); + cl_assert_equal_i(1, ctor_called); + cl_assert_equal_p(&test_stream, stream); + + ctor_called = 0; + cl_git_pass(git_socket_stream_new(&stream, "localhost", "80")); + cl_assert_equal_i(1, ctor_called); + cl_assert_equal_p(&test_stream, stream); +} diff --git a/tests/libgit2/stress/diff.c b/tests/libgit2/stress/diff.c new file mode 100644 index 000000000..aecf08bbe --- /dev/null +++ b/tests/libgit2/stress/diff.c @@ -0,0 +1,146 @@ +#include "clar_libgit2.h" +#include "../diff/diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_stress_diff__initialize(void) +{ +} + +void test_stress_diff__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +#define ANOTHER_POEM \ +"OH, glorious are the guarded heights\nWhere guardian souls abide—\nSelf-exiled from our gross delights—\nAbove, beyond, outside:\nAn ampler arc their spirit swings—\nCommands a juster view—\nWe have their word for all these things,\nNo doubt their words are true.\n\nYet we, the bond slaves of our day,\nWhom dirt and danger press—\nCo-heirs of insolence, delay,\nAnd leagued unfaithfulness—\nSuch is our need must seek indeed\nAnd, having found, engage\nThe men who merely do the work\nFor which they draw the wage.\n\nFrom forge and farm and mine and bench,\nDeck, altar, outpost lone—\nMill, school, battalion, counter, trench,\nRail, senate, sheepfold, throne—\nCreation's cry goes up on high\nFrom age to cheated age:\n\"Send us the men who do the work\n\"For which they draw the wage!\"\n" + +static void test_with_many(int expected_new) +{ + git_index *index; + git_tree *tree, *new_tree; + git_diff *diff = NULL; + diff_expects exp; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt")); + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 2, exp.files); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 1, exp.files); + + git_diff_free(diff); + + cl_repo_commit_from_index(NULL, g_repo, NULL, 1372350000, "yoyoyo"); + cl_git_pass(git_revparse_single( + (git_object **)&new_tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree, new_tree, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 2, exp.files); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(expected_new + 1, exp.files); + + git_diff_free(diff); + + git_tree_free(new_tree); + git_tree_free(tree); + git_index_free(index); +} + +void test_stress_diff__rename_big_files(void) +{ + git_index *index; + char tmp[64]; + int i, j; + git_str b = GIT_STR_INIT; + + g_repo = cl_git_sandbox_init("renames"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + for (i = 0; i < 100; i += 1) { + p_snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + for (j = i * 256; j > 0; --j) + git_str_printf(&b, "more content %d\n", i); + cl_git_mkfile(tmp, b.ptr); + } + + for (i = 0; i < 100; i += 1) { + p_snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); + } + + git_str_dispose(&b); + git_index_free(index); + + test_with_many(100); +} + +void test_stress_diff__rename_many_files(void) +{ + git_index *index; + char tmp[64]; + int i; + git_str b = GIT_STR_INIT; + + g_repo = cl_git_sandbox_init("renames"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + git_str_printf(&b, "%08d\n" ANOTHER_POEM "%08d\n" ANOTHER_POEM ANOTHER_POEM, 0, 0); + + for (i = 0; i < 2500; i += 1) { + p_snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + p_snprintf(b.ptr, 9, "%08d", i); + b.ptr[8] = '\n'; + cl_git_mkfile(tmp, b.ptr); + } + git_str_dispose(&b); + + for (i = 0; i < 2500; i += 1) { + p_snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); + cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); + } + + git_index_free(index); + + test_with_many(2500); +} diff --git a/tests/libgit2/submodule/add.c b/tests/libgit2/submodule/add.c new file mode 100644 index 000000000..ae5507d7f --- /dev/null +++ b/tests/libgit2/submodule/add.c @@ -0,0 +1,251 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "submodule_helpers.h" +#include "config/config_helpers.h" +#include "futils.h" +#include "repository.h" +#include "git2/sys/commit.h" + +static git_repository *g_repo = NULL; +static const char *valid_blob_id = "fa49b077972391ad58037050f2a75f74e3671e92"; + +void test_submodule_add__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void assert_submodule_url(const char* name, const char *url) +{ + git_str key = GIT_STR_INIT; + + + cl_git_pass(git_str_printf(&key, "submodule.%s.url", name)); + assert_config_entry_value(g_repo, git_str_cstr(&key), url); + + git_str_dispose(&key); +} + +void test_submodule_add__url_absolute(void) +{ + git_submodule *sm; + git_repository *repo; + git_str dot_git_content = GIT_STR_INIT; + + g_repo = setup_fixture_submod2(); + + /* re-add existing submodule */ + cl_git_fail_with( + GIT_EEXISTS, + git_submodule_add_setup(NULL, g_repo, "whatever", "sm_unchanged", 1)); + + /* add a submodule using a gitlink */ + + cl_git_pass( + git_submodule_add_setup(&sm, g_repo, "https://github.com/libgit2/libgit2.git", "sm_libgit2", 1) + ); + git_submodule_free(sm); + + cl_assert(git_fs_path_isfile("submod2/" "sm_libgit2" "/.git")); + + cl_assert(git_fs_path_isdir("submod2/.git/modules")); + cl_assert(git_fs_path_isdir("submod2/.git/modules/" "sm_libgit2")); + cl_assert(git_fs_path_isfile("submod2/.git/modules/" "sm_libgit2" "/HEAD")); + assert_submodule_url("sm_libgit2", "https://github.com/libgit2/libgit2.git"); + + cl_git_pass(git_repository_open(&repo, "submod2/" "sm_libgit2")); + + /* Verify worktree path is relative */ + assert_config_entry_value(repo, "core.worktree", "../../../sm_libgit2/"); + + /* Verify gitdir path is relative */ + cl_git_pass(git_futils_readbuffer(&dot_git_content, "submod2/" "sm_libgit2" "/.git")); + cl_assert_equal_s("gitdir: ../.git/modules/sm_libgit2/", dot_git_content.ptr); + + git_repository_free(repo); + git_str_dispose(&dot_git_content); + + /* add a submodule not using a gitlink */ + + cl_git_pass( + git_submodule_add_setup(&sm, g_repo, "https://github.com/libgit2/libgit2.git", "sm_libgit2b", 0) + ); + git_submodule_free(sm); + + cl_assert(git_fs_path_isdir("submod2/" "sm_libgit2b" "/.git")); + cl_assert(git_fs_path_isfile("submod2/" "sm_libgit2b" "/.git/HEAD")); + cl_assert(!git_fs_path_exists("submod2/.git/modules/" "sm_libgit2b")); + assert_submodule_url("sm_libgit2b", "https://github.com/libgit2/libgit2.git"); +} + +void test_submodule_add__url_relative(void) +{ + git_submodule *sm; + git_remote *remote; + git_strarray problems = {0}; + + /* default remote url is https://github.com/libgit2/false.git */ + g_repo = cl_git_sandbox_init("testrepo2"); + + /* make sure we don't default to origin - rename origin -> test_remote */ + cl_git_pass(git_remote_rename(&problems, g_repo, "origin", "test_remote")); + cl_assert_equal_i(0, problems.count); + git_strarray_dispose(&problems); + cl_git_fail(git_remote_lookup(&remote, g_repo, "origin")); + + cl_git_pass( + git_submodule_add_setup(&sm, g_repo, "../TestGitRepository", "TestGitRepository", 1) + ); + git_submodule_free(sm); + + assert_submodule_url("TestGitRepository", "https://github.com/libgit2/TestGitRepository"); +} + +void test_submodule_add__url_relative_to_origin(void) +{ + git_submodule *sm; + + /* default remote url is https://github.com/libgit2/false.git */ + g_repo = cl_git_sandbox_init("testrepo2"); + + cl_git_pass( + git_submodule_add_setup(&sm, g_repo, "../TestGitRepository", "TestGitRepository", 1) + ); + git_submodule_free(sm); + + assert_submodule_url("TestGitRepository", "https://github.com/libgit2/TestGitRepository"); +} + +void test_submodule_add__url_relative_to_workdir(void) +{ + git_submodule *sm; + + /* In this repo, HEAD (master) has no remote tracking branc h*/ + g_repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass( + git_submodule_add_setup(&sm, g_repo, "./", "TestGitRepository", 1) + ); + git_submodule_free(sm); + + assert_submodule_url("TestGitRepository", git_repository_workdir(g_repo)); +} + +static void test_add_entry( + git_index *index, + const char *idstr, + const char *path, + git_filemode_t mode) +{ + git_index_entry entry = {{0}}; + + cl_git_pass(git_oid_fromstr(&entry.id, idstr)); + + entry.path = path; + entry.mode = mode; + + cl_git_pass(git_index_add(index, &entry)); +} + +void test_submodule_add__path_exists_in_index(void) +{ + git_index *index; + git_submodule *sm; + git_str filename = GIT_STR_INIT; + + g_repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass(git_str_joinpath(&filename, "subdirectory", "test.txt")); + + cl_git_pass(git_repository_index__weakptr(&index, g_repo)); + + test_add_entry(index, valid_blob_id, filename.ptr, GIT_FILEMODE_BLOB); + + cl_git_fail_with(git_submodule_add_setup(&sm, g_repo, "./", "subdirectory", 1), GIT_EEXISTS); + + git_submodule_free(sm); + git_str_dispose(&filename); +} + +void test_submodule_add__file_exists_in_index(void) +{ + git_index *index; + git_submodule *sm; + git_str name = GIT_STR_INIT; + + g_repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass(git_repository_index__weakptr(&index, g_repo)); + + test_add_entry(index, valid_blob_id, "subdirectory", GIT_FILEMODE_BLOB); + + cl_git_fail_with(git_submodule_add_setup(&sm, g_repo, "./", "subdirectory", 1), GIT_EEXISTS); + + git_submodule_free(sm); + git_str_dispose(&name); +} + +void test_submodule_add__submodule_clone(void) +{ + git_oid tree_id, commit_id; + git_signature *sig; + git_submodule *sm; + git_index *index; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + /* Create the submodule structure, clone into it and finalize */ + cl_git_pass(git_submodule_add_setup(&sm, g_repo, cl_fixture("testrepo.git"), "testrepo-add", true)); + cl_git_pass(git_submodule_clone(NULL, sm, NULL)); + cl_git_pass(git_submodule_add_finalize(sm)); + + /* Create the submodule commit */ + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_signature_now(&sig, "Submoduler", "submoduler@local")); + cl_git_pass(git_commit_create_from_ids(&commit_id, g_repo, "HEAD", sig, sig, NULL, "A submodule\n", + &tree_id, 0, NULL)); + + assert_submodule_exists(g_repo, "testrepo-add"); + + git_signature_free(sig); + git_submodule_free(sm); + git_index_free(index); +} + +void test_submodule_add__submodule_clone_into_nonempty_dir_succeeds(void) +{ + git_submodule *sm; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(p_mkdir("empty_standard_repo/sm", 0777)); + cl_git_mkfile("empty_standard_repo/sm/foobar", ""); + + /* Create the submodule structure, clone into it and finalize */ + cl_git_pass(git_submodule_add_setup(&sm, g_repo, cl_fixture("testrepo.git"), "sm", true)); + cl_git_pass(git_submodule_clone(NULL, sm, NULL)); + cl_git_pass(git_submodule_add_finalize(sm)); + + cl_assert(git_fs_path_exists("empty_standard_repo/sm/foobar")); + + assert_submodule_exists(g_repo, "sm"); + + git_submodule_free(sm); +} + +void test_submodule_add__submodule_clone_twice_fails(void) +{ + git_submodule *sm; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + /* Create the submodule structure, clone into it and finalize */ + cl_git_pass(git_submodule_add_setup(&sm, g_repo, cl_fixture("testrepo.git"), "sm", true)); + cl_git_pass(git_submodule_clone(NULL, sm, NULL)); + cl_git_pass(git_submodule_add_finalize(sm)); + + cl_git_fail(git_submodule_clone(NULL, sm, NULL)); + + git_submodule_free(sm); +} diff --git a/tests/libgit2/submodule/escape.c b/tests/libgit2/submodule/escape.c new file mode 100644 index 000000000..bcd52b510 --- /dev/null +++ b/tests/libgit2/submodule/escape.c @@ -0,0 +1,98 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "submodule_helpers.h" +#include "futils.h" +#include "repository.h" + +static git_repository *g_repo = NULL; + +void test_submodule_escape__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +#define EVIL_SM_NAME "../../modules/evil" +#define EVIL_SM_NAME_WINDOWS "..\\\\..\\\\modules\\\\evil" +#define EVIL_SM_NAME_WINDOWS_UNESC "..\\..\\modules\\evil" + +static int find_evil(git_submodule *sm, const char *name, void *payload) +{ + int *foundit = (int *) payload; + + GIT_UNUSED(sm); + + if (!git__strcmp(EVIL_SM_NAME, name) || + !git__strcmp(EVIL_SM_NAME_WINDOWS_UNESC, name)) + *foundit = true; + + return 0; +} + +void test_submodule_escape__from_gitdir(void) +{ + int foundit; + git_submodule *sm; + git_str buf = GIT_STR_INIT; + unsigned int sm_location; + + g_repo = setup_fixture_submodule_simple(); + + cl_git_pass(git_str_joinpath(&buf, git_repository_workdir(g_repo), ".gitmodules")); + cl_git_rewritefile(buf.ptr, + "[submodule \"" EVIL_SM_NAME "\"]\n" + " path = testrepo\n" + " url = ../testrepo.git\n"); + git_str_dispose(&buf); + + /* Find it all the different ways we know about it */ + foundit = 0; + cl_git_pass(git_submodule_foreach(g_repo, find_evil, &foundit)); + cl_assert_equal_i(0, foundit); + cl_git_fail_with(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, EVIL_SM_NAME)); + /* + * We do know about this as it's in the index and HEAD, but the data is + * incomplete as there is no configured data for it (we pretend it + * doesn't exist). This leaves us with an odd situation but it's + * consistent with what we would do if we did add a submodule with no + * configuration. + */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + cl_git_pass(git_submodule_location(&sm_location, sm)); + cl_assert_equal_i(GIT_SUBMODULE_STATUS_IN_INDEX | GIT_SUBMODULE_STATUS_IN_HEAD, sm_location); + git_submodule_free(sm); +} + +void test_submodule_escape__from_gitdir_windows(void) +{ + int foundit; + git_submodule *sm; + git_str buf = GIT_STR_INIT; + unsigned int sm_location; + + g_repo = setup_fixture_submodule_simple(); + + cl_git_pass(git_str_joinpath(&buf, git_repository_workdir(g_repo), ".gitmodules")); + cl_git_rewritefile(buf.ptr, + "[submodule \"" EVIL_SM_NAME_WINDOWS "\"]\n" + " path = testrepo\n" + " url = ../testrepo.git\n"); + git_str_dispose(&buf); + + /* Find it all the different ways we know about it */ + foundit = 0; + cl_git_pass(git_submodule_foreach(g_repo, find_evil, &foundit)); + cl_assert_equal_i(0, foundit); + cl_git_fail_with(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, EVIL_SM_NAME_WINDOWS_UNESC)); + /* + * We do know about this as it's in the index and HEAD, but the data is + * incomplete as there is no configured data for it (we pretend it + * doesn't exist). This leaves us with an odd situation but it's + * consistent with what we would do if we did add a submodule with no + * configuration. + */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + cl_git_pass(git_submodule_location(&sm_location, sm)); + cl_assert_equal_i(GIT_SUBMODULE_STATUS_IN_INDEX | GIT_SUBMODULE_STATUS_IN_HEAD, sm_location); + git_submodule_free(sm); +} diff --git a/tests/libgit2/submodule/init.c b/tests/libgit2/submodule/init.c new file mode 100644 index 000000000..a8e1291c4 --- /dev/null +++ b/tests/libgit2/submodule/init.c @@ -0,0 +1,115 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "submodule_helpers.h" +#include "futils.h" + +static git_repository *g_repo = NULL; + +void test_submodule_init__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_submodule_init__absolute_url(void) +{ + git_submodule *sm; + git_config *cfg; + git_str absolute_url = GIT_STR_INIT; + const char *config_url; + + g_repo = setup_fixture_submodule_simple(); + + cl_assert(git_fs_path_dirname_r(&absolute_url, git_repository_workdir(g_repo)) > 0); + cl_git_pass(git_str_joinpath(&absolute_url, absolute_url.ptr, "testrepo.git")); + + /* write the absolute url to the .gitmodules file*/ + cl_git_pass(git_submodule_set_url(g_repo, "testrepo", absolute_url.ptr)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + /* verify that the .gitmodules is set with an absolute path*/ + cl_assert_equal_s(absolute_url.ptr, git_submodule_url(sm)); + + /* init and verify that absolute path is written to .git/config */ + cl_git_pass(git_submodule_init(sm, false)); + + cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); + + cl_git_pass(git_config_get_string(&config_url, cfg, "submodule.testrepo.url")); + cl_assert_equal_s(absolute_url.ptr, config_url); + + git_str_dispose(&absolute_url); + git_config_free(cfg); + git_submodule_free(sm); +} + +void test_submodule_init__relative_url(void) +{ + git_submodule *sm; + git_config *cfg; + git_str absolute_url = GIT_STR_INIT; + const char *config_url; + + g_repo = setup_fixture_submodule_simple(); + + cl_assert(git_fs_path_dirname_r(&absolute_url, git_repository_workdir(g_repo)) > 0); + cl_git_pass(git_str_joinpath(&absolute_url, absolute_url.ptr, "testrepo.git")); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + /* verify that the .gitmodules is set with an absolute path*/ + cl_assert_equal_s("../testrepo.git", git_submodule_url(sm)); + + /* init and verify that absolute path is written to .git/config */ + cl_git_pass(git_submodule_init(sm, false)); + + cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); + + cl_git_pass(git_config_get_string(&config_url, cfg, "submodule.testrepo.url")); + cl_assert_equal_s(absolute_url.ptr, config_url); + + git_str_dispose(&absolute_url); + git_config_free(cfg); + git_submodule_free(sm); +} + +void test_submodule_init__relative_url_detached_head(void) +{ + git_submodule *sm; + git_config *cfg; + git_str absolute_url = GIT_STR_INIT; + const char *config_url; + git_reference *head_ref = NULL; + git_object *head_commit = NULL; + + g_repo = setup_fixture_submodule_simple(); + + /* Put the parent repository into a detached head state. */ + cl_git_pass(git_repository_head(&head_ref, g_repo)); + cl_git_pass(git_reference_peel(&head_commit, head_ref, GIT_OBJECT_COMMIT)); + + cl_git_pass(git_repository_set_head_detached(g_repo, git_commit_id((git_commit *)head_commit))); + + cl_assert(git_fs_path_dirname_r(&absolute_url, git_repository_workdir(g_repo)) > 0); + cl_git_pass(git_str_joinpath(&absolute_url, absolute_url.ptr, "testrepo.git")); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + /* verify that the .gitmodules is set with an absolute path*/ + cl_assert_equal_s("../testrepo.git", git_submodule_url(sm)); + + /* init and verify that absolute path is written to .git/config */ + cl_git_pass(git_submodule_init(sm, false)); + + cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); + + cl_git_pass(git_config_get_string(&config_url, cfg, "submodule.testrepo.url")); + cl_assert_equal_s(absolute_url.ptr, config_url); + + git_str_dispose(&absolute_url); + git_config_free(cfg); + git_object_free(head_commit); + git_reference_free(head_ref); + git_submodule_free(sm); +} diff --git a/tests/libgit2/submodule/inject_option.c b/tests/libgit2/submodule/inject_option.c new file mode 100644 index 000000000..e28ff8489 --- /dev/null +++ b/tests/libgit2/submodule/inject_option.c @@ -0,0 +1,80 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "submodule_helpers.h" +#include "futils.h" +#include "repository.h" + +static git_repository *g_repo = NULL; + +void test_submodule_inject_option__initialize(void) +{ + g_repo = setup_fixture_submodule_simple(); +} + +void test_submodule_inject_option__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int find_naughty(git_submodule *sm, const char *name, void *payload) +{ + int *foundit = (int *) payload; + + GIT_UNUSED(sm); + + if (!git__strcmp("naughty", name)) + *foundit = true; + + return 0; +} + +void test_submodule_inject_option__url(void) +{ + int foundit; + git_submodule *sm; + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&buf, git_repository_workdir(g_repo), ".gitmodules")); + cl_git_rewritefile(buf.ptr, + "[submodule \"naughty\"]\n" + " path = testrepo\n" + " url = -u./payload\n"); + git_str_dispose(&buf); + + /* We do want to find it, but with the appropriate field empty */ + foundit = 0; + cl_git_pass(git_submodule_foreach(g_repo, find_naughty, &foundit)); + cl_assert_equal_i(1, foundit); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "naughty")); + cl_assert_equal_s("testrepo", git_submodule_path(sm)); + cl_assert_equal_p(NULL, git_submodule_url(sm)); + + git_submodule_free(sm); +} + +void test_submodule_inject_option__path(void) +{ + int foundit; + git_submodule *sm; + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&buf, git_repository_workdir(g_repo), ".gitmodules")); + cl_git_rewritefile(buf.ptr, + "[submodule \"naughty\"]\n" + " path = --something\n" + " url = blah.git\n"); + git_str_dispose(&buf); + + /* We do want to find it, but with the appropriate field empty */ + foundit = 0; + cl_git_pass(git_submodule_foreach(g_repo, find_naughty, &foundit)); + cl_assert_equal_i(1, foundit); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "naughty")); + cl_assert_equal_s("naughty", git_submodule_path(sm)); + cl_assert_equal_s("blah.git", git_submodule_url(sm)); + + git_submodule_free(sm); +} diff --git a/tests/libgit2/submodule/lookup.c b/tests/libgit2/submodule/lookup.c new file mode 100644 index 000000000..acfdc838c --- /dev/null +++ b/tests/libgit2/submodule/lookup.c @@ -0,0 +1,516 @@ +#include "clar_libgit2.h" +#include "submodule_helpers.h" +#include "git2/sys/repository.h" +#include "repository.h" +#include "futils.h" + +static git_repository *g_repo = NULL; + +void test_submodule_lookup__initialize(void) +{ + g_repo = setup_fixture_submod2(); +} + +void test_submodule_lookup__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_submodule_lookup__simple_lookup(void) +{ + assert_submodule_exists(g_repo, "sm_unchanged"); + + /* lookup pending change in .gitmodules that is not in HEAD */ + assert_submodule_exists(g_repo, "sm_added_and_uncommited"); + + /* lookup pending change in .gitmodules that is not in HEAD nor index */ + assert_submodule_exists(g_repo, "sm_gitmodules_only"); + + /* lookup git repo subdir that is not added as submodule */ + refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS); + + /* lookup existing directory that is not a submodule */ + refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND); + + /* lookup existing file that is not a submodule */ + refute_submodule_exists(g_repo, "just_a_file", GIT_ENOTFOUND); + + /* lookup non-existent item */ + refute_submodule_exists(g_repo, "no_such_file", GIT_ENOTFOUND); + + /* lookup a submodule by path with a trailing slash */ + assert_submodule_exists(g_repo, "sm_added_and_uncommited/"); +} + +void test_submodule_lookup__can_be_dupped(void) +{ + git_submodule *sm; + git_submodule *sm_duplicate; + const char *oid = "480095882d281ed676fe5b863569520e54a7d5c0"; + + /* Check original */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_assert(git_submodule_owner(sm) == g_repo); + cl_assert_equal_s("sm_unchanged", git_submodule_name(sm)); + cl_assert(git__suffixcmp(git_submodule_path(sm), "sm_unchanged") == 0); + cl_assert(git__suffixcmp(git_submodule_url(sm), "/submod2_target") == 0); + + cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_head_id(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), oid) == 0); + + cl_assert(git_submodule_ignore(sm) == GIT_SUBMODULE_IGNORE_NONE); + cl_assert(git_submodule_update_strategy(sm) == GIT_SUBMODULE_UPDATE_CHECKOUT); + + /* Duplicate and free original */ + cl_assert(git_submodule_dup(&sm_duplicate, sm) == 0); + git_submodule_free(sm); + + /* Check duplicate */ + cl_assert(git_submodule_owner(sm_duplicate) == g_repo); + cl_assert_equal_s("sm_unchanged", git_submodule_name(sm_duplicate)); + cl_assert(git__suffixcmp(git_submodule_path(sm_duplicate), "sm_unchanged") == 0); + cl_assert(git__suffixcmp(git_submodule_url(sm_duplicate), "/submod2_target") == 0); + + cl_assert(git_oid_streq(git_submodule_index_id(sm_duplicate), oid) == 0); + cl_assert(git_oid_streq(git_submodule_head_id(sm_duplicate), oid) == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm_duplicate), oid) == 0); + + cl_assert(git_submodule_ignore(sm_duplicate) == GIT_SUBMODULE_IGNORE_NONE); + cl_assert(git_submodule_update_strategy(sm_duplicate) == GIT_SUBMODULE_UPDATE_CHECKOUT); + + git_submodule_free(sm_duplicate); +} + +void test_submodule_lookup__accessors(void) +{ + git_submodule *sm; + const char *oid = "480095882d281ed676fe5b863569520e54a7d5c0"; + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_assert(git_submodule_owner(sm) == g_repo); + cl_assert_equal_s("sm_unchanged", git_submodule_name(sm)); + cl_assert(git__suffixcmp(git_submodule_path(sm), "sm_unchanged") == 0); + cl_assert(git__suffixcmp(git_submodule_url(sm), "/submod2_target") == 0); + + cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_head_id(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), oid) == 0); + + cl_assert(git_submodule_ignore(sm) == GIT_SUBMODULE_IGNORE_NONE); + cl_assert(git_submodule_update_strategy(sm) == GIT_SUBMODULE_UPDATE_CHECKOUT); + + git_submodule_free(sm); + + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_assert_equal_s("sm_changed_head", git_submodule_name(sm)); + + cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_head_id(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), + "3d9386c507f6b093471a3e324085657a3c2b4247") == 0); + + git_submodule_free(sm); + + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_assert_equal_s("sm_added_and_uncommited", git_submodule_name(sm)); + + cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0); + cl_assert(git_submodule_head_id(sm) == NULL); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), oid) == 0); + + git_submodule_free(sm); + + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits")); + cl_assert_equal_s("sm_missing_commits", git_submodule_name(sm)); + + cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_head_id(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), + "5e4963595a9774b90524d35a807169049de8ccad") == 0); + + git_submodule_free(sm); +} + +typedef struct { + int count; +} sm_lookup_data; + +static int sm_lookup_cb(git_submodule *sm, const char *name, void *payload) +{ + sm_lookup_data *data = payload; + data->count += 1; + cl_assert_equal_s(git_submodule_name(sm), name); + return 0; +} + +void test_submodule_lookup__foreach(void) +{ + git_config *cfg; + sm_lookup_data data; + + memset(&data, 0, sizeof(data)); + cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); + cl_assert_equal_i(8, data.count); + + memset(&data, 0, sizeof(data)); + + /* Change the path for a submodule so it doesn't match the name */ + cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.gitmodules")); + + cl_git_pass(git_config_set_string(cfg, "submodule.smchangedindex.path", "sm_changed_index")); + cl_git_pass(git_config_set_string(cfg, "submodule.smchangedindex.url", "../submod2_target")); + cl_git_pass(git_config_delete_entry(cfg, "submodule.sm_changed_index.path")); + cl_git_pass(git_config_delete_entry(cfg, "submodule.sm_changed_index.url")); + + git_config_free(cfg); + + cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); + cl_assert_equal_i(8, data.count); +} + +static int foreach_cb(git_submodule *sm, const char *name, void *payload) +{ + GIT_UNUSED(sm); + GIT_UNUSED(name); + GIT_UNUSED(payload); + return 0; +} + +void test_submodule_lookup__duplicated_path(void) +{ + cl_git_rewritefile("submod2/.gitmodules", + "[submodule \"sm1\"]\n" + " path = duplicated-path\n" + " url = sm1\n" + "[submodule \"sm2\"]\n" + " path = duplicated-path\n" + " url = sm2\n"); + + cl_git_fail(git_submodule_foreach(g_repo, foreach_cb, NULL)); +} + +void test_submodule_lookup__lookup_even_with_unborn_head(void) +{ + git_reference *head; + + /* put us on an unborn branch */ + cl_git_pass(git_reference_symbolic_create( + &head, g_repo, "HEAD", "refs/heads/garbage", 1, NULL)); + git_reference_free(head); + + test_submodule_lookup__simple_lookup(); /* baseline should still pass */ +} + +void test_submodule_lookup__lookup_even_with_missing_index(void) +{ + git_index *idx; + + /* give the repo an empty index */ + cl_git_pass(git_index_new(&idx)); + git_repository_set_index(g_repo, idx); + git_index_free(idx); + + test_submodule_lookup__simple_lookup(); /* baseline should still pass */ +} + +void test_submodule_lookup__backslashes(void) +{ + git_config *cfg; + git_submodule *sm; + git_repository *subrepo; + git_buf buf = GIT_BUF_INIT; + const char *backslashed_path = "..\\submod2_target"; + + cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.gitmodules")); + cl_git_pass(git_config_set_string(cfg, "submodule.sm_unchanged.url", backslashed_path)); + git_config_free(cfg); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_assert_equal_s(backslashed_path, git_submodule_url(sm)); + cl_git_pass(git_submodule_open(&subrepo, sm)); + + cl_git_pass(git_submodule_resolve_url(&buf, g_repo, backslashed_path)); + + git_buf_dispose(&buf); + git_submodule_free(sm); + git_repository_free(subrepo); +} + +static void baseline_tests(void) +{ + /* small baseline that should work even if we change the index or make + * commits from the index + */ + assert_submodule_exists(g_repo, "sm_unchanged"); + assert_submodule_exists(g_repo, "sm_gitmodules_only"); + refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS); +} + +static void add_submodule_with_commit(const char *name) +{ + git_submodule *sm; + git_repository *smrepo; + git_index *idx; + git_str p = GIT_STR_INIT; + + cl_git_pass(git_submodule_add_setup(&sm, g_repo, + "https://github.com/libgit2/libgit2.git", name, 1)); + + assert_submodule_exists(g_repo, name); + + cl_git_pass(git_submodule_open(&smrepo, sm)); + cl_git_pass(git_repository_index(&idx, smrepo)); + + cl_git_pass(git_str_joinpath(&p, git_repository_workdir(smrepo), "file")); + cl_git_mkfile(p.ptr, "new file"); + git_str_dispose(&p); + + cl_git_pass(git_index_add_bypath(idx, "file")); + cl_git_pass(git_index_write(idx)); + git_index_free(idx); + + cl_repo_commit_from_index(NULL, smrepo, NULL, 0, "initial commit"); + git_repository_free(smrepo); + + cl_git_pass(git_submodule_add_finalize(sm)); + + git_submodule_free(sm); +} + +void test_submodule_lookup__just_added(void) +{ + git_submodule *sm; + git_str snap1 = GIT_STR_INIT, snap2 = GIT_STR_INIT; + git_reference *original_head = NULL; + + refute_submodule_exists(g_repo, "sm_just_added", GIT_ENOTFOUND); + refute_submodule_exists(g_repo, "sm_just_added_2", GIT_ENOTFOUND); + refute_submodule_exists(g_repo, "sm_just_added_idx", GIT_ENOTFOUND); + refute_submodule_exists(g_repo, "sm_just_added_head", GIT_ENOTFOUND); + refute_submodule_exists(g_repo, "mismatch_name", GIT_ENOTFOUND); + refute_submodule_exists(g_repo, "mismatch_path", GIT_ENOTFOUND); + baseline_tests(); + + cl_git_pass(git_futils_readbuffer(&snap1, "submod2/.gitmodules")); + cl_git_pass(git_repository_head(&original_head, g_repo)); + + cl_git_pass(git_submodule_add_setup(&sm, g_repo, + "https://github.com/libgit2/libgit2.git", "sm_just_added", 1)); + git_submodule_free(sm); + assert_submodule_exists(g_repo, "sm_just_added"); + + cl_git_pass(git_submodule_add_setup(&sm, g_repo, + "https://github.com/libgit2/libgit2.git", "sm_just_added_2", 1)); + assert_submodule_exists(g_repo, "sm_just_added_2"); + cl_git_fail(git_submodule_add_finalize(sm)); /* fails if no HEAD */ + git_submodule_free(sm); + + add_submodule_with_commit("sm_just_added_head"); + cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "commit new sm to head"); + assert_submodule_exists(g_repo, "sm_just_added_head"); + + add_submodule_with_commit("sm_just_added_idx"); + assert_submodule_exists(g_repo, "sm_just_added_idx"); + + cl_git_pass(git_futils_readbuffer(&snap2, "submod2/.gitmodules")); + + cl_git_append2file( + "submod2/.gitmodules", + "\n[submodule \"mismatch_name\"]\n" + "\tpath = mismatch_path\n" + "\turl = https://example.com/example.git\n\n"); + + assert_submodule_exists(g_repo, "mismatch_name"); + assert_submodule_exists(g_repo, "mismatch_path"); + assert_submodule_exists(g_repo, "sm_just_added"); + assert_submodule_exists(g_repo, "sm_just_added_2"); + assert_submodule_exists(g_repo, "sm_just_added_idx"); + assert_submodule_exists(g_repo, "sm_just_added_head"); + baseline_tests(); + + cl_git_rewritefile("submod2/.gitmodules", snap2.ptr); + git_str_dispose(&snap2); + + refute_submodule_exists(g_repo, "mismatch_name", GIT_ENOTFOUND); + refute_submodule_exists(g_repo, "mismatch_path", GIT_ENOTFOUND); + assert_submodule_exists(g_repo, "sm_just_added"); + assert_submodule_exists(g_repo, "sm_just_added_2"); + assert_submodule_exists(g_repo, "sm_just_added_idx"); + assert_submodule_exists(g_repo, "sm_just_added_head"); + baseline_tests(); + + cl_git_rewritefile("submod2/.gitmodules", snap1.ptr); + git_str_dispose(&snap1); + + refute_submodule_exists(g_repo, "mismatch_name", GIT_ENOTFOUND); + refute_submodule_exists(g_repo, "mismatch_path", GIT_ENOTFOUND); + /* note error code change, because add_setup made a repo in the workdir */ + refute_submodule_exists(g_repo, "sm_just_added", GIT_EEXISTS); + refute_submodule_exists(g_repo, "sm_just_added_2", GIT_EEXISTS); + /* these still exist in index and head respectively */ + assert_submodule_exists(g_repo, "sm_just_added_idx"); + assert_submodule_exists(g_repo, "sm_just_added_head"); + baseline_tests(); + + { + git_index *idx; + cl_git_pass(git_repository_index(&idx, g_repo)); + cl_git_pass(git_index_remove_bypath(idx, "sm_just_added_idx")); + cl_git_pass(git_index_remove_bypath(idx, "sm_just_added_head")); + cl_git_pass(git_index_write(idx)); + git_index_free(idx); + } + + refute_submodule_exists(g_repo, "sm_just_added_idx", GIT_EEXISTS); + assert_submodule_exists(g_repo, "sm_just_added_head"); + + { + cl_git_pass(git_reference_create(NULL, g_repo, "refs/heads/master", git_reference_target(original_head), 1, "move head back")); + git_reference_free(original_head); + } + + refute_submodule_exists(g_repo, "sm_just_added_head", GIT_EEXISTS); +} + +/* Test_App and Test_App2 are fairly similar names, make sure we load the right one */ +void test_submodule_lookup__prefix_name(void) +{ + git_submodule *sm; + + cl_git_rewritefile("submod2/.gitmodules", + "[submodule \"Test_App\"]\n" + " path = Test_App\n" + " url = ../Test_App\n" + "[submodule \"Test_App2\"]\n" + " path = Test_App2\n" + " url = ../Test_App\n"); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "Test_App")); + cl_assert_equal_s("Test_App", git_submodule_name(sm)); + + git_submodule_free(sm); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "Test_App2")); + cl_assert_equal_s("Test_App2", git_submodule_name(sm)); + + git_submodule_free(sm); +} + +void test_submodule_lookup__renamed(void) +{ + const char *newpath = "sm_actually_changed"; + git_index *idx; + sm_lookup_data data; + + cl_git_pass(git_repository_index__weakptr(&idx, g_repo)); + + /* We're replicating 'git mv sm_unchanged sm_actually_changed' in this test */ + + cl_git_pass(p_rename("submod2/sm_unchanged", "submod2/sm_actually_changed")); + + /* Change the path in .gitmodules and stage it*/ + { + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.gitmodules")); + cl_git_pass(git_config_set_string(cfg, "submodule.sm_unchanged.path", newpath)); + git_config_free(cfg); + + cl_git_pass(git_index_add_bypath(idx, ".gitmodules")); + } + + /* Change the worktree info in the submodule's config */ + { + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.git/modules/sm_unchanged/config")); + cl_git_pass(git_config_set_string(cfg, "core.worktree", "../../../sm_actually_changed")); + git_config_free(cfg); + } + + /* Rename the entry in the index */ + { + const git_index_entry *e; + git_index_entry entry = {{ 0 }}; + + e = git_index_get_bypath(idx, "sm_unchanged", 0); + cl_assert(e); + cl_assert_equal_i(GIT_FILEMODE_COMMIT, e->mode); + + entry.path = newpath; + entry.mode = GIT_FILEMODE_COMMIT; + git_oid_cpy(&entry.id, &e->id); + + cl_git_pass(git_index_remove(idx, "sm_unchanged", 0)); + cl_git_pass(git_index_add(idx, &entry)); + cl_git_pass(git_index_write(idx)); + } + + memset(&data, 0, sizeof(data)); + cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); + cl_assert_equal_i(8, data.count); +} + +void test_submodule_lookup__cached(void) +{ + git_submodule *sm; + git_submodule *sm2; + /* See that the simple tests still pass. */ + + git_repository_submodule_cache_all(g_repo); + test_submodule_lookup__simple_lookup(); + git_repository_submodule_cache_clear(g_repo); + test_submodule_lookup__simple_lookup(); + + /* Check that subsequent calls return different objects when cached. */ + git_repository_submodule_cache_all(g_repo); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_lookup(&sm2, g_repo, "sm_unchanged")); + cl_assert_equal_p(sm, sm2); + git_submodule_free(sm2); + + /* and that we get new objects again after clearing the cache. */ + git_repository_submodule_cache_clear(g_repo); + cl_git_pass(git_submodule_lookup(&sm2, g_repo, "sm_unchanged")); + cl_assert(sm != sm2); + git_submodule_free(sm); + git_submodule_free(sm2); +} + +void test_submodule_lookup__lookup_in_bare_repository_fails(void) +{ + git_submodule *sm; + + cl_git_sandbox_cleanup(); + g_repo = cl_git_sandbox_init("submodules.git"); + + cl_git_fail(git_submodule_lookup(&sm, g_repo, "nonexisting")); +} + +void test_submodule_lookup__foreach_in_bare_repository_fails(void) +{ + cl_git_sandbox_cleanup(); + g_repo = cl_git_sandbox_init("submodules.git"); + + cl_git_fail(git_submodule_foreach(g_repo, foreach_cb, NULL)); +} + +void test_submodule_lookup__fail_invalid_gitmodules(void) +{ + git_submodule *sm; + sm_lookup_data data; + memset(&data, 0, sizeof(data)); + + cl_git_rewritefile("submod2/.gitmodules", + "[submodule \"Test_App\"\n" + " path = Test_App\n" + " url = ../Test_App\n"); + + cl_git_fail(git_submodule_lookup(&sm, g_repo, "Test_App")); + + cl_git_fail(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); +} diff --git a/tests/libgit2/submodule/modify.c b/tests/libgit2/submodule/modify.c new file mode 100644 index 000000000..7e7f0ca15 --- /dev/null +++ b/tests/libgit2/submodule/modify.c @@ -0,0 +1,233 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "submodule_helpers.h" +#include "config/config_helpers.h" + +static git_repository *g_repo = NULL; + +#define SM_LIBGIT2_URL "https://github.com/libgit2/libgit2.git" +#define SM_LIBGIT2_BRANCH "github-branch" +#define SM_LIBGIT2 "sm_libgit2" + +void test_submodule_modify__initialize(void) +{ + g_repo = setup_fixture_submod2(); +} + +static int delete_one_config(const git_config_entry *entry, void *payload) +{ + git_config *cfg = payload; + return git_config_delete_entry(cfg, entry->name); +} + +static int init_one_submodule( + git_submodule *sm, const char *name, void *payload) +{ + GIT_UNUSED(name); + GIT_UNUSED(payload); + return git_submodule_init(sm, false); +} + +void test_submodule_modify__init(void) +{ + git_config *cfg; + const char *str; + + /* erase submodule data from .git/config */ + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass( + git_config_foreach_match(cfg, "submodule\\..*", delete_one_config, cfg)); + git_config_free(cfg); + + /* confirm no submodule data in config */ + cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_string(&str, cfg, "submodule.sm_unchanged.url")); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_string(&str, cfg, "submodule.sm_changed_head.url")); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_string(&str, cfg, "submodule.sm_added_and_uncommited.url")); + git_config_free(cfg); + + /* call init and see that settings are copied */ + cl_git_pass(git_submodule_foreach(g_repo, init_one_submodule, NULL)); + + /* confirm submodule data in config */ + cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); + cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_unchanged.url")); + cl_assert(git__suffixcmp(str, "/submod2_target") == 0); + cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_changed_head.url")); + cl_assert(git__suffixcmp(str, "/submod2_target") == 0); + cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_added_and_uncommited.url")); + cl_assert(git__suffixcmp(str, "/submod2_target") == 0); + git_config_free(cfg); +} + +static int sync_one_submodule( + git_submodule *sm, const char *name, void *payload) +{ + GIT_UNUSED(name); + GIT_UNUSED(payload); + return git_submodule_sync(sm); +} + +static void assert_submodule_url_is_synced( + git_submodule *sm, const char *parent_key, const char *child_key) +{ + git_repository *smrepo; + + assert_config_entry_value(g_repo, parent_key, git_submodule_url(sm)); + + cl_git_pass(git_submodule_open(&smrepo, sm)); + assert_config_entry_value(smrepo, child_key, git_submodule_url(sm)); + git_repository_free(smrepo); +} + +void test_submodule_modify__sync(void) +{ + git_submodule *sm1, *sm2, *sm3; + git_config *cfg; + const char *str; + +#define SM1 "sm_unchanged" +#define SM2 "sm_changed_head" +#define SM3 "sm_added_and_uncommited" + + /* look up some submodules */ + cl_git_pass(git_submodule_lookup(&sm1, g_repo, SM1)); + cl_git_pass(git_submodule_lookup(&sm2, g_repo, SM2)); + cl_git_pass(git_submodule_lookup(&sm3, g_repo, SM3)); + + /* At this point, the .git/config URLs for the submodules have + * not be rewritten with the absolute paths (although the + * .gitmodules have. Let's confirm that they DO NOT match + * yet, then we can do a sync to make them match... + */ + + /* check submodule info does not match before sync */ + cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); + cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM1".url")); + cl_assert(strcmp(git_submodule_url(sm1), str) != 0); + cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM2".url")); + cl_assert(strcmp(git_submodule_url(sm2), str) != 0); + cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM3".url")); + cl_assert(strcmp(git_submodule_url(sm3), str) != 0); + git_config_free(cfg); + + /* sync all the submodules */ + cl_git_pass(git_submodule_foreach(g_repo, sync_one_submodule, NULL)); + + /* check that submodule config is updated */ + assert_submodule_url_is_synced( + sm1, "submodule."SM1".url", "remote.origin.url"); + assert_submodule_url_is_synced( + sm2, "submodule."SM2".url", "remote.origin.url"); + assert_submodule_url_is_synced( + sm3, "submodule."SM3".url", "remote.origin.url"); + + git_submodule_free(sm1); + git_submodule_free(sm2); + git_submodule_free(sm3); +} + +static void assert_ignore_change(git_submodule_ignore_t ignore) +{ + git_submodule *sm; + + cl_git_pass(git_submodule_set_ignore(g_repo, "sm_changed_head", ignore)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_assert_equal_i(ignore, git_submodule_ignore(sm)); + git_submodule_free(sm); +} + +void test_submodule_modify__set_ignore(void) +{ + assert_ignore_change(GIT_SUBMODULE_IGNORE_UNTRACKED); + assert_ignore_change(GIT_SUBMODULE_IGNORE_NONE); + assert_ignore_change(GIT_SUBMODULE_IGNORE_ALL); +} + +static void assert_update_change(git_submodule_update_t update) +{ + git_submodule *sm; + + cl_git_pass(git_submodule_set_update(g_repo, "sm_changed_head", update)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_assert_equal_i(update, git_submodule_update_strategy(sm)); + git_submodule_free(sm); +} + +void test_submodule_modify__set_update(void) +{ + assert_update_change(GIT_SUBMODULE_UPDATE_REBASE); + assert_update_change(GIT_SUBMODULE_UPDATE_NONE); + assert_update_change(GIT_SUBMODULE_UPDATE_CHECKOUT); +} + +static void assert_recurse_change(git_submodule_recurse_t recurse) +{ + git_submodule *sm; + + cl_git_pass(git_submodule_set_fetch_recurse_submodules(g_repo, "sm_changed_head", recurse)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_assert_equal_i(recurse, git_submodule_fetch_recurse_submodules(sm)); + git_submodule_free(sm); +} + +void test_submodule_modify__set_fetch_recurse_submodules(void) +{ + assert_recurse_change(GIT_SUBMODULE_RECURSE_YES); + assert_recurse_change(GIT_SUBMODULE_RECURSE_NO); + assert_recurse_change(GIT_SUBMODULE_RECURSE_ONDEMAND); +} + +void test_submodule_modify__set_branch(void) +{ + git_submodule *sm; + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_assert(git_submodule_branch(sm) == NULL); + git_submodule_free(sm); + + cl_git_pass(git_submodule_set_branch(g_repo, "sm_changed_head", SM_LIBGIT2_BRANCH)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_assert_equal_s(SM_LIBGIT2_BRANCH, git_submodule_branch(sm)); + git_submodule_free(sm); + + cl_git_pass(git_submodule_set_branch(g_repo, "sm_changed_head", NULL)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_assert(git_submodule_branch(sm) == NULL); + git_submodule_free(sm); +} + +void test_submodule_modify__set_url(void) +{ + git_submodule *sm; + + cl_git_pass(git_submodule_set_url(g_repo, "sm_changed_head", SM_LIBGIT2_URL)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_assert_equal_s(SM_LIBGIT2_URL, git_submodule_url(sm)); + git_submodule_free(sm); +} + +void test_submodule_modify__set_relative_url(void) +{ + git_str path = GIT_STR_INIT; + git_repository *repo; + git_submodule *sm; + + cl_git_pass(git_submodule_set_url(g_repo, SM1, "../relative-url")); + cl_git_pass(git_submodule_lookup(&sm, g_repo, SM1)); + cl_git_pass(git_submodule_sync(sm)); + cl_git_pass(git_submodule_open(&repo, sm)); + + cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), "relative-url")); + + assert_config_entry_value(g_repo, "submodule."SM1".url", path.ptr); + assert_config_entry_value(repo, "remote.origin.url", path.ptr); + + git_repository_free(repo); + git_submodule_free(sm); + git_str_dispose(&path); +} diff --git a/tests/libgit2/submodule/nosubs.c b/tests/libgit2/submodule/nosubs.c new file mode 100644 index 000000000..e82230e87 --- /dev/null +++ b/tests/libgit2/submodule/nosubs.c @@ -0,0 +1,130 @@ +/* test the submodule APIs on repositories where there are no submodules */ + +#include "clar_libgit2.h" +#include "posix.h" +#include "futils.h" + +void test_submodule_nosubs__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_submodule_nosubs__lookup(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_submodule *sm = NULL; + + p_mkdir("status/subrepo", 0777); + cl_git_mkfile("status/subrepo/.git", "gitdir: ../.git"); + + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, repo, "subdir")); + + cl_assert_equal_i(GIT_EEXISTS, git_submodule_lookup(&sm, repo, "subrepo")); + + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, repo, "subdir")); + + cl_assert_equal_i(GIT_EEXISTS, git_submodule_lookup(&sm, repo, "subrepo")); +} + +static int fake_submod_cb(git_submodule *sm, const char *n, void *p) +{ + GIT_UNUSED(sm); GIT_UNUSED(n); GIT_UNUSED(p); + return 0; +} + +void test_submodule_nosubs__foreach(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + cl_git_pass(git_submodule_foreach(repo, fake_submod_cb, NULL)); +} + +void test_submodule_nosubs__add(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_submodule *sm, *sm2; + + cl_git_pass(git_submodule_add_setup(&sm, repo, "https://github.com/libgit2/libgit2.git", "submodules/libgit2", 1)); + + cl_git_pass(git_submodule_lookup(&sm2, repo, "submodules/libgit2")); + git_submodule_free(sm2); + + cl_git_pass(git_submodule_foreach(repo, fake_submod_cb, NULL)); + + git_submodule_free(sm); +} + +void test_submodule_nosubs__bad_gitmodules(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + + cl_git_mkfile("status/.gitmodules", "[submodule \"foobar\"]\tpath=blargle\n\turl=\n\tbranch=\n\tupdate=flooble\n\n"); + + cl_git_rewritefile("status/.gitmodules", "[submodule \"foobar\"]\tpath=blargle\n\turl=\n\tbranch=\n\tupdate=rebase\n\n"); + + cl_git_pass(git_submodule_lookup(NULL, repo, "foobar")); + cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(NULL, repo, "subdir")); +} + +void test_submodule_nosubs__add_and_delete(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_submodule *sm; + git_str buf = GIT_STR_INIT; + + cl_git_fail(git_submodule_lookup(NULL, repo, "libgit2")); + cl_git_fail(git_submodule_lookup(NULL, repo, "submodules/libgit2")); + + /* create */ + + cl_git_pass(git_submodule_add_setup( + &sm, repo, "https://github.com/libgit2/libgit2.git", "submodules/libgit2", 1)); + cl_assert_equal_s("submodules/libgit2", git_submodule_name(sm)); + cl_assert_equal_s("submodules/libgit2", git_submodule_path(sm)); + git_submodule_free(sm); + + cl_git_pass(git_futils_readbuffer(&buf, "status/.gitmodules")); + cl_assert(strstr(buf.ptr, "[submodule \"submodules/libgit2\"]") != NULL); + cl_assert(strstr(buf.ptr, "path = submodules/libgit2") != NULL); + git_str_dispose(&buf); + + /* lookup */ + + cl_git_fail(git_submodule_lookup(&sm, repo, "libgit2")); + cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2")); + cl_assert_equal_s("submodules/libgit2", git_submodule_name(sm)); + cl_assert_equal_s("submodules/libgit2", git_submodule_path(sm)); + git_submodule_free(sm); + + /* update name */ + + cl_git_rewritefile( + "status/.gitmodules", + "[submodule \"libgit2\"]\n" + " path = submodules/libgit2\n" + " url = https://github.com/libgit2/libgit2.git\n"); + + cl_git_pass(git_submodule_lookup(&sm, repo, "libgit2")); + cl_assert_equal_s("libgit2", git_submodule_name(sm)); + cl_assert_equal_s("submodules/libgit2", git_submodule_path(sm)); + git_submodule_free(sm); + cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2")); + git_submodule_free(sm); + + /* revert name update */ + + cl_git_rewritefile( + "status/.gitmodules", + "[submodule \"submodules/libgit2\"]\n" + " path = submodules/libgit2\n" + " url = https://github.com/libgit2/libgit2.git\n"); + + cl_git_fail(git_submodule_lookup(&sm, repo, "libgit2")); + cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2")); + git_submodule_free(sm); + + /* remove completely */ + + cl_must_pass(p_unlink("status/.gitmodules")); + cl_git_fail(git_submodule_lookup(&sm, repo, "libgit2")); + cl_git_fail(git_submodule_lookup(&sm, repo, "submodules/libgit2")); +} diff --git a/tests/libgit2/submodule/open.c b/tests/libgit2/submodule/open.c new file mode 100644 index 000000000..e6883d208 --- /dev/null +++ b/tests/libgit2/submodule/open.c @@ -0,0 +1,90 @@ +#include "clar_libgit2.h" +#include "submodule_helpers.h" +#include "path.h" + +static git_repository *g_parent; +static git_repository *g_child; +static git_submodule *g_module; + +void test_submodule_open__initialize(void) +{ + g_parent = setup_fixture_submod2(); +} + +void test_submodule_open__cleanup(void) +{ + git_submodule_free(g_module); + git_repository_free(g_child); + cl_git_sandbox_cleanup(); + g_parent = NULL; + g_child = NULL; + g_module = NULL; +} + +static void assert_sm_valid(git_repository *parent, git_repository *child, const char *sm_name) +{ + git_str expected = GIT_STR_INIT, actual = GIT_STR_INIT; + + /* assert working directory */ + cl_git_pass(git_str_joinpath(&expected, git_repository_workdir(parent), sm_name)); + cl_git_pass(git_fs_path_prettify_dir(&expected, expected.ptr, NULL)); + cl_git_pass(git_str_sets(&actual, git_repository_workdir(child))); + cl_git_pass(git_fs_path_prettify_dir(&actual, actual.ptr, NULL)); + cl_assert_equal_s(expected.ptr, actual.ptr); + + git_str_clear(&expected); + git_str_clear(&actual); + + /* assert common directory */ + cl_git_pass(git_str_joinpath(&expected, git_repository_commondir(parent), "modules")); + cl_git_pass(git_str_joinpath(&expected, expected.ptr, sm_name)); + cl_git_pass(git_fs_path_prettify_dir(&expected, expected.ptr, NULL)); + cl_git_pass(git_str_sets(&actual, git_repository_commondir(child))); + cl_git_pass(git_fs_path_prettify_dir(&actual, actual.ptr, NULL)); + cl_assert_equal_s(expected.ptr, actual.ptr); + + /* assert git directory */ + cl_git_pass(git_str_sets(&actual, git_repository_path(child))); + cl_git_pass(git_fs_path_prettify_dir(&actual, actual.ptr, NULL)); + cl_assert_equal_s(expected.ptr, actual.ptr); + + git_str_dispose(&expected); + git_str_dispose(&actual); +} + +void test_submodule_open__opening_via_lookup_succeeds(void) +{ + cl_git_pass(git_submodule_lookup(&g_module, g_parent, "sm_unchanged")); + cl_git_pass(git_submodule_open(&g_child, g_module)); + assert_sm_valid(g_parent, g_child, "sm_unchanged"); +} + +void test_submodule_open__direct_open_succeeds(void) +{ + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_parent), "sm_unchanged")); + cl_git_pass(git_repository_open(&g_child, path.ptr)); + assert_sm_valid(g_parent, g_child, "sm_unchanged"); + + git_str_dispose(&path); +} + +void test_submodule_open__direct_open_succeeds_for_broken_sm_with_gitdir(void) +{ + git_str path = GIT_STR_INIT; + + /* + * This is actually not a valid submodule, but we + * encountered at least one occasion where the gitdir + * file existed inside of a submodule's gitdir. As we are + * now able to open these submodules correctly, we still + * add a test for this. + */ + cl_git_mkfile("submod2/.git/modules/sm_unchanged/gitdir", ".git"); + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_parent), "sm_unchanged")); + cl_git_pass(git_repository_open(&g_child, path.ptr)); + assert_sm_valid(g_parent, g_child, "sm_unchanged"); + + git_str_dispose(&path); +} diff --git a/tests/libgit2/submodule/repository_init.c b/tests/libgit2/submodule/repository_init.c new file mode 100644 index 000000000..39b55c403 --- /dev/null +++ b/tests/libgit2/submodule/repository_init.c @@ -0,0 +1,38 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "submodule_helpers.h" +#include "config/config_helpers.h" +#include "futils.h" + +static git_repository *g_repo = NULL; + +void test_submodule_repository_init__basic(void) +{ + git_submodule *sm; + git_repository *repo; + git_str dot_git_content = GIT_STR_INIT; + + g_repo = setup_fixture_submod2(); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_gitmodules_only")); + cl_git_pass(git_submodule_init(sm, 0)); + cl_git_pass(git_submodule_repo_init(&repo, sm, 1)); + + /* Verify worktree */ + assert_config_entry_value(repo, "core.worktree", "../../../sm_gitmodules_only/"); + + /* Verify gitlink */ + cl_git_pass(git_futils_readbuffer(&dot_git_content, "submod2/" "sm_gitmodules_only" "/.git")); + cl_assert_equal_s("gitdir: ../.git/modules/sm_gitmodules_only/", dot_git_content.ptr); + + cl_assert(git_fs_path_isfile("submod2/" "sm_gitmodules_only" "/.git")); + + cl_assert(git_fs_path_isdir("submod2/.git/modules")); + cl_assert(git_fs_path_isdir("submod2/.git/modules/" "sm_gitmodules_only")); + cl_assert(git_fs_path_isfile("submod2/.git/modules/" "sm_gitmodules_only" "/HEAD")); + + git_submodule_free(sm); + git_repository_free(repo); + git_str_dispose(&dot_git_content); +} diff --git a/tests/libgit2/submodule/status.c b/tests/libgit2/submodule/status.c new file mode 100644 index 000000000..1d41337b7 --- /dev/null +++ b/tests/libgit2/submodule/status.c @@ -0,0 +1,354 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "submodule_helpers.h" +#include "futils.h" +#include "iterator.h" + +static git_repository *g_repo = NULL; + +void test_submodule_status__initialize(void) +{ + g_repo = setup_fixture_submod2(); +} + +void test_submodule_status__cleanup(void) +{ +} + +void test_submodule_status__unchanged(void) +{ + unsigned int status = get_submodule_status(g_repo, "sm_unchanged"); + unsigned int expected = + GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_IN_WD; + + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + cl_assert(expected == status); +} + +static void rm_submodule(const char *name) +{ + git_str path = GIT_STR_INIT; + cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), name)); + cl_git_pass(git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)); + git_str_dispose(&path); +} + +static void add_submodule_to_index(const char *name) +{ + git_submodule *sm; + cl_git_pass(git_submodule_lookup(&sm, g_repo, name)); + cl_git_pass(git_submodule_add_to_index(sm, true)); + git_submodule_free(sm); +} + +static void rm_submodule_from_index(const char *name) +{ + git_index *index; + size_t pos; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_assert(!git_index_find(&pos, index, name)); + cl_git_pass(git_index_remove(index, name, 0)); + cl_git_pass(git_index_write(index)); + git_index_free(index); +} + +/* 4 values of GIT_SUBMODULE_IGNORE to check */ + +void test_submodule_status__ignore_none(void) +{ + unsigned int status; + + rm_submodule("sm_unchanged"); + + refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND); + refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS); + refute_submodule_exists(g_repo, "not", GIT_EEXISTS); + + status = get_submodule_status(g_repo, "sm_changed_index"); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0); + + status = get_submodule_status(g_repo, "sm_changed_head"); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + status = get_submodule_status(g_repo, "sm_changed_file"); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0); + + status = get_submodule_status(g_repo, "sm_changed_untracked_file"); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNTRACKED) != 0); + + status = get_submodule_status(g_repo, "sm_missing_commits"); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + status = get_submodule_status(g_repo, "sm_added_and_uncommited"); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0); + + /* removed sm_unchanged for deleted workdir */ + status = get_submodule_status(g_repo, "sm_unchanged"); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); + + /* now mkdir sm_unchanged to test uninitialized */ + cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); + status = get_submodule_status(g_repo, "sm_unchanged"); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); + + /* update sm_changed_head in index */ + add_submodule_to_index("sm_changed_head"); + status = get_submodule_status(g_repo, "sm_changed_head"); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0); + + /* remove sm_changed_head from index */ + rm_submodule_from_index("sm_changed_head"); + status = get_submodule_status(g_repo, "sm_changed_head"); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_DELETED) != 0); +} + +void test_submodule_status__ignore_untracked(void) +{ + unsigned int status; + git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_UNTRACKED; + + rm_submodule("sm_unchanged"); + + refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND); + refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS); + refute_submodule_exists(g_repo, "not", GIT_EEXISTS); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_index", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_file", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_untracked_file", ign)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_missing_commits", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_added_and_uncommited", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0); + + /* removed sm_unchanged for deleted workdir */ + cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); + + /* now mkdir sm_unchanged to test uninitialized */ + cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); + cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); + + /* update sm_changed_head in index */ + add_submodule_to_index("sm_changed_head"); + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0); +} + +void test_submodule_status__ignore_dirty(void) +{ + unsigned int status; + git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_DIRTY; + + rm_submodule("sm_unchanged"); + + refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND); + refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS); + refute_submodule_exists(g_repo, "not", GIT_EEXISTS); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_index", ign)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_file", ign)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_untracked_file", ign)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_missing_commits", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_added_and_uncommited", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0); + + /* removed sm_unchanged for deleted workdir */ + cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); + + /* now mkdir sm_unchanged to test uninitialized */ + cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); + cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); + + /* update sm_changed_head in index */ + add_submodule_to_index("sm_changed_head"); + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0); +} + +void test_submodule_status__ignore_all(void) +{ + unsigned int status; + git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_ALL; + + rm_submodule("sm_unchanged"); + + refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND); + refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS); + refute_submodule_exists(g_repo, "not", GIT_EEXISTS); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_index", ign)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_file", ign)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_untracked_file", ign)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_missing_commits", ign)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_status(&status, g_repo,"sm_added_and_uncommited", ign)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + /* removed sm_unchanged for deleted workdir */ + cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + /* now mkdir sm_unchanged to test uninitialized */ + cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); + cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + /* update sm_changed_head in index */ + add_submodule_to_index("sm_changed_head"); + cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); +} + +typedef struct { + size_t counter; + const char **paths; + int *statuses; +} submodule_expectations; + +static int confirm_submodule_status( + const char *path, unsigned int status_flags, void *payload) +{ + submodule_expectations *exp = payload; + + while (exp->statuses[exp->counter] < 0) + exp->counter++; + + cl_assert_equal_i(exp->statuses[exp->counter], (int)status_flags); + cl_assert_equal_s(exp->paths[exp->counter++], path); + + GIT_UNUSED(status_flags); + + return 0; +} + +void test_submodule_status__iterator(void) +{ + git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + size_t i; + static const char *expected[] = { + ".gitmodules", + "just_a_dir/", + "just_a_dir/contents", + "just_a_file", + "not-submodule/", + "not-submodule/README.txt", + "not/", + "not/README.txt", + "README.txt", + "sm_added_and_uncommited", + "sm_changed_file", + "sm_changed_head", + "sm_changed_index", + "sm_changed_untracked_file", + "sm_missing_commits", + "sm_unchanged", + NULL + }; + static int expected_flags[] = { + GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED, /* ".gitmodules" */ + -1, /* "just_a_dir/" will be skipped */ + GIT_STATUS_CURRENT, /* "just_a_dir/contents" */ + GIT_STATUS_CURRENT, /* "just_a_file" */ + GIT_STATUS_WT_NEW, /* "not-submodule/" untracked item */ + -1, /* "not-submodule/README.txt" */ + GIT_STATUS_WT_NEW, /* "not/" untracked item */ + -1, /* "not/README.txt" */ + GIT_STATUS_CURRENT, /* "README.txt */ + GIT_STATUS_INDEX_NEW, /* "sm_added_and_uncommited" */ + GIT_STATUS_WT_MODIFIED, /* "sm_changed_file" */ + GIT_STATUS_WT_MODIFIED, /* "sm_changed_head" */ + GIT_STATUS_WT_MODIFIED, /* "sm_changed_index" */ + GIT_STATUS_WT_MODIFIED, /* "sm_changed_untracked_file" */ + GIT_STATUS_WT_MODIFIED, /* "sm_missing_commits" */ + GIT_STATUS_CURRENT, /* "sm_unchanged" */ + 0 + }; + submodule_expectations exp = { 0, expected, expected_flags }; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + git_index *index; + + iter_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_iterator_for_workdir(&iter, g_repo, index, NULL, &iter_opts)); + + for (i = 0; !git_iterator_advance(&entry, iter); ++i) + cl_assert_equal_s(expected[i], entry->path); + + git_iterator_free(iter); + git_index_free(index); + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED | + GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | + GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY; + + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, confirm_submodule_status, &exp)); +} + +void test_submodule_status__untracked_dirs_containing_ignored_files(void) +{ + unsigned int status, expected; + + cl_git_append2file( + "submod2/.git/modules/sm_unchanged/info/exclude", "\n*.ignored\n"); + + cl_git_pass( + git_futils_mkdir_relative("sm_unchanged/directory", "submod2", 0755, 0, NULL)); + cl_git_mkfile( + "submod2/sm_unchanged/directory/i_am.ignored", + "ignore this file, please\n"); + + status = get_submodule_status(g_repo, "sm_unchanged"); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + expected = GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_IN_WD; + cl_assert(status == expected); +} diff --git a/tests/libgit2/submodule/submodule_helpers.c b/tests/libgit2/submodule/submodule_helpers.c new file mode 100644 index 000000000..b8fc9f60d --- /dev/null +++ b/tests/libgit2/submodule/submodule_helpers.c @@ -0,0 +1,245 @@ +#include "clar_libgit2.h" +#include "path.h" +#include "util.h" +#include "posix.h" +#include "submodule_helpers.h" +#include "git2/sys/repository.h" + +/* rewrite gitmodules -> .gitmodules + * rewrite the empty or relative urls inside each module + * rename the .gitted directory inside any submodule to .git + */ +void rewrite_gitmodules(const char *workdir) +{ + git_str in_f = GIT_STR_INIT, out_f = GIT_STR_INIT, path = GIT_STR_INIT; + FILE *in, *out; + char line[256]; + + cl_git_pass(git_str_joinpath(&in_f, workdir, "gitmodules")); + cl_git_pass(git_str_joinpath(&out_f, workdir, ".gitmodules")); + + cl_assert((in = fopen(in_f.ptr, "rb")) != NULL); + cl_assert((out = fopen(out_f.ptr, "wb")) != NULL); + + while (fgets(line, sizeof(line), in) != NULL) { + char *scan = line; + + while (*scan == ' ' || *scan == '\t') scan++; + + /* rename .gitted -> .git in submodule directories */ + if (git__prefixcmp(scan, "path =") == 0) { + scan += strlen("path ="); + while (*scan == ' ') scan++; + + git_str_joinpath(&path, workdir, scan); + git_str_rtrim(&path); + git_str_joinpath(&path, path.ptr, ".gitted"); + + if (!git_str_oom(&path) && p_access(path.ptr, F_OK) == 0) { + git_str_joinpath(&out_f, workdir, scan); + git_str_rtrim(&out_f); + git_str_joinpath(&out_f, out_f.ptr, ".git"); + + if (!git_str_oom(&out_f)) + p_rename(path.ptr, out_f.ptr); + } + } + + /* copy non-"url =" lines verbatim */ + if (git__prefixcmp(scan, "url =") != 0) { + fputs(line, out); + continue; + } + + /* convert relative URLs in "url =" lines */ + scan += strlen("url ="); + while (*scan == ' ') scan++; + + if (*scan == '.') { + git_str_joinpath(&path, workdir, scan); + git_str_rtrim(&path); + } else if (!*scan || *scan == '\n') { + git_str_joinpath(&path, workdir, "../testrepo.git"); + } else { + fputs(line, out); + continue; + } + + git_fs_path_prettify(&path, path.ptr, NULL); + git_str_putc(&path, '\n'); + cl_assert(!git_str_oom(&path)); + + fwrite(line, scan - line, sizeof(char), out); + fputs(path.ptr, out); + } + + fclose(in); + fclose(out); + + cl_must_pass(p_unlink(in_f.ptr)); + + git_str_dispose(&in_f); + git_str_dispose(&out_f); + git_str_dispose(&path); +} + +static void cleanup_fixture_submodules(void *payload) +{ + cl_git_sandbox_cleanup(); /* either "submodules" or "submod2" */ + + if (payload) + cl_fixture_cleanup(payload); +} + +git_repository *setup_fixture_submodules(void) +{ + git_repository *repo = cl_git_sandbox_init("submodules"); + + cl_fixture_sandbox("testrepo.git"); + + rewrite_gitmodules(git_repository_workdir(repo)); + p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git"); + + cl_set_cleanup(cleanup_fixture_submodules, "testrepo.git"); + + cl_git_pass(git_repository_reinit_filesystem(repo, 1)); + + return repo; +} + +git_repository *setup_fixture_submod2(void) +{ + git_repository *repo = cl_git_sandbox_init("submod2"); + + cl_fixture_sandbox("submod2_target"); + p_rename("submod2_target/.gitted", "submod2_target/.git"); + + rewrite_gitmodules(git_repository_workdir(repo)); + p_rename("submod2/not-submodule/.gitted", "submod2/not-submodule/.git"); + p_rename("submod2/not/.gitted", "submod2/not/.git"); + + cl_set_cleanup(cleanup_fixture_submodules, "submod2_target"); + + cl_git_pass(git_repository_reinit_filesystem(repo, 1)); + + return repo; +} + +git_repository *setup_fixture_submod3(void) +{ + git_repository *repo = cl_git_sandbox_init("submod3"); + + cl_fixture_sandbox("submod2_target"); + p_rename("submod2_target/.gitted", "submod2_target/.git"); + + rewrite_gitmodules(git_repository_workdir(repo)); + p_rename("submod3/One/.gitted", "submod3/One/.git"); + p_rename("submod3/TWO/.gitted", "submod3/TWO/.git"); + p_rename("submod3/three/.gitted", "submod3/three/.git"); + p_rename("submod3/FoUr/.gitted", "submod3/FoUr/.git"); + p_rename("submod3/Five/.gitted", "submod3/Five/.git"); + p_rename("submod3/six/.gitted", "submod3/six/.git"); + p_rename("submod3/sEvEn/.gitted", "submod3/sEvEn/.git"); + p_rename("submod3/EIGHT/.gitted", "submod3/EIGHT/.git"); + p_rename("submod3/nine/.gitted", "submod3/nine/.git"); + p_rename("submod3/TEN/.gitted", "submod3/TEN/.git"); + + cl_set_cleanup(cleanup_fixture_submodules, "submod2_target"); + + cl_git_pass(git_repository_reinit_filesystem(repo, 1)); + + return repo; +} + +git_repository *setup_fixture_super(void) +{ + git_repository *repo = cl_git_sandbox_init("super"); + + cl_fixture_sandbox("sub.git"); + p_mkdir("super/sub", 0777); + + rewrite_gitmodules(git_repository_workdir(repo)); + + cl_set_cleanup(cleanup_fixture_submodules, "sub.git"); + + cl_git_pass(git_repository_reinit_filesystem(repo, 1)); + + return repo; +} + +git_repository *setup_fixture_submodule_simple(void) +{ + git_repository *repo = cl_git_sandbox_init("submodule_simple"); + + cl_fixture_sandbox("testrepo.git"); + p_mkdir("submodule_simple/testrepo", 0777); + + cl_set_cleanup(cleanup_fixture_submodules, "testrepo.git"); + + cl_git_pass(git_repository_reinit_filesystem(repo, 1)); + + return repo; +} + +git_repository *setup_fixture_submodule_with_path(void) +{ + git_repository *repo = cl_git_sandbox_init("submodule_with_path"); + + cl_fixture_sandbox("testrepo.git"); + p_mkdir("submodule_with_path/lib", 0777); + p_mkdir("submodule_with_path/lib/testrepo", 0777); + + cl_set_cleanup(cleanup_fixture_submodules, "testrepo.git"); + + cl_git_pass(git_repository_reinit_filesystem(repo, 1)); + + return repo; +} + +void assert__submodule_exists( + git_repository *repo, const char *name, + const char *msg, const char *file, const char *func, int line) +{ + git_submodule *sm; + int error = git_submodule_lookup(&sm, repo, name); + if (error) + cl_git_report_failure(error, 0, file, func, line, msg); + cl_assert_at_line(sm != NULL, file, func, line); + git_submodule_free(sm); +} + +void refute__submodule_exists( + git_repository *repo, const char *name, int expected_error, + const char *msg, const char *file, const char *func, int line) +{ + clar__assert_equal( + file, func, line, msg, 1, "%i", + expected_error, (int)(git_submodule_lookup(NULL, repo, name))); +} + +unsigned int get_submodule_status(git_repository *repo, const char *name) +{ + unsigned int status = 0; + + assert(repo && name); + + cl_git_pass(git_submodule_status(&status, repo, name, GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + + return status; +} + +static int print_submodules(git_submodule *sm, const char *name, void *p) +{ + unsigned int loc = 0; + GIT_UNUSED(p); + git_submodule_location(&loc, sm); + fprintf(stderr, "# submodule %s (at %s) flags %x\n", + name, git_submodule_path(sm), loc); + return 0; +} + +void dump_submodules(git_repository *repo) +{ + git_submodule_foreach(repo, print_submodules, NULL); +} + diff --git a/tests/libgit2/submodule/submodule_helpers.h b/tests/libgit2/submodule/submodule_helpers.h new file mode 100644 index 000000000..3c3f062ae --- /dev/null +++ b/tests/libgit2/submodule/submodule_helpers.h @@ -0,0 +1,25 @@ +extern void rewrite_gitmodules(const char *workdir); + +/* these will automatically set a cleanup callback */ +extern git_repository *setup_fixture_submodules(void); +extern git_repository *setup_fixture_submod2(void); +extern git_repository *setup_fixture_submod3(void); +extern git_repository *setup_fixture_submodule_simple(void); +extern git_repository *setup_fixture_super(void); +extern git_repository *setup_fixture_submodule_with_path(void); + +extern unsigned int get_submodule_status(git_repository *, const char *); + +extern void assert__submodule_exists(git_repository *, const char *, + const char *, const char *, const char *, int); + +#define assert_submodule_exists(repo,name) \ + assert__submodule_exists(repo, name, "git_submodule_lookup(" #name ") failed", __FILE__, __func__, __LINE__) + +extern void refute__submodule_exists(git_repository *, const char *, + int err, const char *, const char *, const char *, int); + +#define refute_submodule_exists(repo,name,code) \ + refute__submodule_exists(repo, name, code, "expected git_submodule_lookup(" #name ") to fail with error " #code, __FILE__, __func__, __LINE__) + +extern void dump_submodules(git_repository *repo); diff --git a/tests/libgit2/submodule/update.c b/tests/libgit2/submodule/update.c new file mode 100644 index 000000000..4aa959852 --- /dev/null +++ b/tests/libgit2/submodule/update.c @@ -0,0 +1,440 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "submodule_helpers.h" +#include "futils.h" + +static git_repository *g_repo = NULL; + +void test_submodule_update__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_submodule_update__uninitialized_submodule_no_init(void) +{ + git_submodule *sm; + git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + + g_repo = setup_fixture_submodule_simple(); + + /* get the submodule */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + /* updating an uninitialized repository throws */ + cl_git_fail_with( + GIT_ERROR, + git_submodule_update(sm, 0, &update_options)); + + git_submodule_free(sm); +} + +struct update_submodule_cb_payload { + int update_tips_called; + int checkout_progress_called; + int checkout_notify_called; +}; + +static void checkout_progress_cb( + const char *path, + size_t completed_steps, + size_t total_steps, + void *payload) +{ + struct update_submodule_cb_payload *update_payload = payload; + + GIT_UNUSED(path); + GIT_UNUSED(completed_steps); + GIT_UNUSED(total_steps); + + update_payload->checkout_progress_called = 1; +} + +static int checkout_notify_cb( + git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload) +{ + struct update_submodule_cb_payload *update_payload = payload; + + GIT_UNUSED(why); + GIT_UNUSED(path); + GIT_UNUSED(baseline); + GIT_UNUSED(target); + GIT_UNUSED(workdir); + + update_payload->checkout_notify_called = 1; + + return 0; +} + +static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *data) +{ + struct update_submodule_cb_payload *update_payload = data; + + GIT_UNUSED(refname); + GIT_UNUSED(a); + GIT_UNUSED(b); + + update_payload->update_tips_called = 1; + + return 1; +} + +void test_submodule_update__update_submodule(void) +{ + git_submodule *sm; + git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + unsigned int submodule_status = 0; + struct update_submodule_cb_payload update_payload = { 0 }; + + g_repo = setup_fixture_submodule_simple(); + + update_options.checkout_opts.progress_cb = checkout_progress_cb; + update_options.checkout_opts.progress_payload = &update_payload; + + update_options.fetch_opts.callbacks.update_tips = update_tips; + update_options.fetch_opts.callbacks.payload = &update_payload; + + /* get the submodule */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + /* verify the initial state of the submodule */ + cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); + + /* initialize and update the submodule */ + cl_git_pass(git_submodule_init(sm, 0)); + cl_git_pass(git_submodule_update(sm, 0, &update_options)); + + /* verify state */ + cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_IN_WD); + + cl_assert(git_oid_streq(git_submodule_head_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_index_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + + /* verify that the expected callbacks have been called. */ + cl_assert_equal_i(1, update_payload.checkout_progress_called); + cl_assert_equal_i(1, update_payload.update_tips_called); + + git_submodule_free(sm); +} + +void test_submodule_update__update_submodule_with_path(void) +{ + git_submodule *sm; + git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + unsigned int submodule_status = 0; + struct update_submodule_cb_payload update_payload = { 0 }; + + g_repo = setup_fixture_submodule_with_path(); + + update_options.checkout_opts.progress_cb = checkout_progress_cb; + update_options.checkout_opts.progress_payload = &update_payload; + + update_options.fetch_opts.callbacks.update_tips = update_tips; + update_options.fetch_opts.callbacks.payload = &update_payload; + + /* get the submodule */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + /* verify the initial state of the submodule */ + cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); + + /* initialize and update the submodule */ + cl_git_pass(git_submodule_init(sm, 0)); + cl_git_pass(git_submodule_update(sm, 0, &update_options)); + + /* verify state */ + cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_IN_WD); + + cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + + /* verify that the expected callbacks have been called. */ + cl_assert_equal_i(1, update_payload.checkout_progress_called); + cl_assert_equal_i(1, update_payload.update_tips_called); + + git_submodule_free(sm); +} + +void test_submodule_update__update_and_init_submodule(void) +{ + git_submodule *sm; + git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + unsigned int submodule_status = 0; + + g_repo = setup_fixture_submodule_simple(); + + /* get the submodule */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); + + /* update (with option to initialize sub repo) */ + cl_git_pass(git_submodule_update(sm, 1, &update_options)); + + /* verify expected state */ + cl_assert(git_oid_streq(git_submodule_head_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_index_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + + git_submodule_free(sm); +} + +void test_submodule_update__update_already_checked_out_submodule(void) +{ + git_submodule *sm = NULL; + git_checkout_options checkout_options = GIT_CHECKOUT_OPTIONS_INIT; + git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + unsigned int submodule_status = 0; + git_reference *branch_reference = NULL; + git_object *branch_commit = NULL; + struct update_submodule_cb_payload update_payload = { 0 }; + + g_repo = setup_fixture_submodule_simple(); + + update_options.checkout_opts.progress_cb = checkout_progress_cb; + update_options.checkout_opts.progress_payload = &update_payload; + + /* Initialize and update the sub repository */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); + + cl_git_pass(git_submodule_update(sm, 1, &update_options)); + + /* verify expected state */ + cl_assert(git_oid_streq(git_submodule_head_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_index_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + + /* checkout the alternate_1 branch */ + checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_lookup(&branch_reference, g_repo, "refs/heads/alternate_1")); + cl_git_pass(git_reference_peel(&branch_commit, branch_reference, GIT_OBJECT_COMMIT)); + cl_git_pass(git_checkout_tree(g_repo, branch_commit, &checkout_options)); + cl_git_pass(git_repository_set_head(g_repo, git_reference_name(branch_reference))); + + /* + * Verify state after checkout of parent repository. The submodule ID in the + * HEAD commit and index should be updated, but not the workdir. + */ + + cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + + git_submodule_free(sm); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS_WD_MODIFIED); + + cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + + /* + * Update the submodule and verify the state. + * Now, the HEAD, index, and Workdir commits should all be updated to + * the new commit. + */ + cl_git_pass(git_submodule_update(sm, 0, &update_options)); + cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + + /* verify that the expected callbacks have been called. */ + cl_assert_equal_i(1, update_payload.checkout_progress_called); + + git_submodule_free(sm); + git_object_free(branch_commit); + git_reference_free(branch_reference); +} + +void test_submodule_update__update_blocks_on_dirty_wd(void) +{ + git_submodule *sm = NULL; + git_checkout_options checkout_options = GIT_CHECKOUT_OPTIONS_INIT; + git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + unsigned int submodule_status = 0; + git_reference *branch_reference = NULL; + git_object *branch_commit = NULL; + struct update_submodule_cb_payload update_payload = { 0 }; + + g_repo = setup_fixture_submodule_simple(); + + update_options.checkout_opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT; + update_options.checkout_opts.notify_cb = checkout_notify_cb; + update_options.checkout_opts.notify_payload = &update_payload; + + /* Initialize and update the sub repository */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); + + cl_git_pass(git_submodule_update(sm, 1, &update_options)); + + /* verify expected state */ + cl_assert(git_oid_streq(git_submodule_head_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_index_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + + /* checkout the alternate_1 branch */ + checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_lookup(&branch_reference, g_repo, "refs/heads/alternate_1")); + cl_git_pass(git_reference_peel(&branch_commit, branch_reference, GIT_OBJECT_COMMIT)); + cl_git_pass(git_checkout_tree(g_repo, branch_commit, &checkout_options)); + cl_git_pass(git_repository_set_head(g_repo, git_reference_name(branch_reference))); + + /* + * Verify state after checkout of parent repository. The submodule ID in the + * HEAD commit and index should be updated, but not the workdir. + */ + + cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + + git_submodule_free(sm); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS_WD_MODIFIED); + + cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + + /* + * Create a conflicting edit in the subrepository to verify that + * the submodule update action is blocked. + */ + cl_git_write2file("submodule_simple/testrepo/branch_file.txt", "a conflicting edit", 0, + O_WRONLY | O_CREAT | O_TRUNC, 0755); + + cl_git_fail(git_submodule_update(sm, 0, &update_options)); + + /* verify that the expected callbacks have been called. */ + cl_assert_equal_i(1, update_payload.checkout_notify_called); + + /* verify that the submodule state has not changed. */ + cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + + git_submodule_free(sm); + git_object_free(branch_commit); + git_reference_free(branch_reference); +} + +void test_submodule_update__can_force_update(void) +{ + git_submodule *sm = NULL; + git_checkout_options checkout_options = GIT_CHECKOUT_OPTIONS_INIT; + git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + unsigned int submodule_status = 0; + git_reference *branch_reference = NULL; + git_object *branch_commit = NULL; + + g_repo = setup_fixture_submodule_simple(); + + /* Initialize and update the sub repository */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); + + cl_git_pass(git_submodule_update(sm, 1, &update_options)); + + /* verify expected state */ + cl_assert(git_oid_streq(git_submodule_head_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_index_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + + /* checkout the alternate_1 branch */ + checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_lookup(&branch_reference, g_repo, "refs/heads/alternate_1")); + cl_git_pass(git_reference_peel(&branch_commit, branch_reference, GIT_OBJECT_COMMIT)); + cl_git_pass(git_checkout_tree(g_repo, branch_commit, &checkout_options)); + cl_git_pass(git_repository_set_head(g_repo, git_reference_name(branch_reference))); + + /* + * Verify state after checkout of parent repository. The submodule ID in the + * HEAD commit and index should be updated, but not the workdir. + */ + cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); + + git_submodule_free(sm); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); + + cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS_WD_MODIFIED); + + cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); + cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + + /* + * Create a conflicting edit in the subrepository to verify that + * the submodule update action is blocked. + */ + cl_git_write2file("submodule_simple/testrepo/branch_file.txt", "a conflicting edit", 0, + O_WRONLY | O_CREAT | O_TRUNC, 0777); + + /* forcefully checkout and verify the submodule state was updated. */ + update_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_submodule_update(sm, 0, &update_options)); + cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + cl_assert(git_oid_streq(git_submodule_wd_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); + + git_submodule_free(sm); + git_object_free(branch_commit); + git_reference_free(branch_reference); +} + diff --git a/tests/libgit2/threads/atomic.c b/tests/libgit2/threads/atomic.c new file mode 100644 index 000000000..4d04a777a --- /dev/null +++ b/tests/libgit2/threads/atomic.c @@ -0,0 +1,125 @@ +#include "clar_libgit2.h" + +void test_threads_atomic__atomic32_set(void) +{ + git_atomic32 v = {0}; + git_atomic32_set(&v, 1); + cl_assert_equal_i(v.val, 1); +} + +void test_threads_atomic__atomic32_get(void) +{ + git_atomic32 v = {1}; + cl_assert_equal_i(git_atomic32_get(&v), 1); +} + +void test_threads_atomic__atomic32_inc(void) +{ + git_atomic32 v = {0}; + cl_assert_equal_i(git_atomic32_inc(&v), 1); + cl_assert_equal_i(v.val, 1); +} + +void test_threads_atomic__atomic32_add(void) +{ + git_atomic32 v = {0}; + cl_assert_equal_i(git_atomic32_add(&v, 1), 1); + cl_assert_equal_i(v.val, 1); +} + +void test_threads_atomic__atomic32_dec(void) +{ + git_atomic32 v = {1}; + cl_assert_equal_i(git_atomic32_dec(&v), 0); + cl_assert_equal_i(v.val, 0); +} + +void test_threads_atomic__atomic64_set(void) +{ +#ifndef GIT_ARCH_64 + cl_skip(); +#else + git_atomic64 v = {0}; + git_atomic64_set(&v, 1); + cl_assert_equal_i(v.val, 1); +#endif +} + +void test_threads_atomic__atomic64_get(void) +{ +#ifndef GIT_ARCH_64 + cl_skip(); +#else + git_atomic64 v = {1}; + cl_assert_equal_i(git_atomic64_get(&v), 1); +#endif +} + +void test_threads_atomic__atomic64_add(void) +{ +#ifndef GIT_ARCH_64 + cl_skip(); +#else + git_atomic64 v = {0}; + cl_assert_equal_i(git_atomic64_add(&v, 1), 1); + cl_assert_equal_i(v.val, 1); +#endif +} + +void test_threads_atomic__cas_pointer(void) +{ + int *value = NULL; + int newvalue1 = 1, newvalue2 = 2; + + /* value is updated */ + cl_assert_equal_p(git_atomic_compare_and_swap(&value, NULL, &newvalue1), NULL); + cl_assert_equal_p(value, &newvalue1); + + /* value is not updated */ + cl_assert_equal_p(git_atomic_compare_and_swap(&value, NULL, &newvalue2), &newvalue1); + cl_assert_equal_p(value, &newvalue1); +} + +void test_threads_atomic__cas_intptr(void) +{ + intptr_t value = 0; + intptr_t oldvalue; + intptr_t newvalue; + + /* value is updated */ + oldvalue = 0; + newvalue = 1; + cl_assert_equal_i((intptr_t)git_atomic_compare_and_swap(&value, (void *)oldvalue, (void *)newvalue), 0); + cl_assert_equal_i(value, 1); + + /* value is not updated */ + oldvalue = 0; + newvalue = 2; + cl_assert_equal_i((intptr_t)git_atomic_compare_and_swap(&value, (void *)oldvalue, (void *)newvalue), 1); + cl_assert_equal_i(value, 1); +} + +void test_threads_atomic__swap(void) +{ + int *value = NULL; + int newvalue = 1; + + cl_assert_equal_p(git_atomic_swap(value, &newvalue), NULL); + cl_assert_equal_p(value, &newvalue); + + cl_assert_equal_p(git_atomic_swap(value, NULL), &newvalue); + cl_assert_equal_p(value, NULL); +} + +void test_threads_atomic__load_ptr(void) +{ + int value = 1; + int *ptr = &value; + cl_assert_equal_p(git_atomic_load(ptr), &value); +} + +void test_threads_atomic__load_intptr(void) +{ + intptr_t value = 1; + cl_assert_equal_i((intptr_t)git_atomic_load(value), 1); +} diff --git a/tests/libgit2/threads/basic.c b/tests/libgit2/threads/basic.c new file mode 100644 index 000000000..2d7ddc26b --- /dev/null +++ b/tests/libgit2/threads/basic.c @@ -0,0 +1,83 @@ +#include "clar_libgit2.h" + +#include "thread_helpers.h" +#include "cache.h" + + +static git_repository *g_repo; + +void test_threads_basic__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_threads_basic__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + + +void test_threads_basic__cache(void) +{ + /* run several threads polling the cache at the same time */ + cl_assert(1 == 1); +} + +void test_threads_basic__multiple_init(void) +{ + git_repository *nested_repo; + + git_libgit2_init(); + cl_git_pass(git_repository_open(&nested_repo, cl_fixture("testrepo.git"))); + git_repository_free(nested_repo); + + git_libgit2_shutdown(); + cl_git_pass(git_repository_open(&nested_repo, cl_fixture("testrepo.git"))); + git_repository_free(nested_repo); +} + +static void *set_error(void *dummy) +{ + git_error_set(GIT_ERROR_INVALID, "oh no, something happened!\n"); + + return dummy; +} + +/* Set errors so we can check that we free it */ +void test_threads_basic__set_error(void) +{ + run_in_parallel(1, 4, set_error, NULL, NULL); +} + +#ifdef GIT_THREADS +static void *return_normally(void *param) +{ + return param; +} + +static void *exit_abruptly(void *param) +{ + git_thread_exit(param); + return NULL; +} +#endif + +void test_threads_basic__exit(void) +{ +#ifndef GIT_THREADS + clar__skip(); +#else + git_thread thread; + void *result; + + /* Ensure that the return value of the threadproc is returned. */ + cl_git_pass(git_thread_create(&thread, return_normally, (void *)424242)); + cl_git_pass(git_thread_join(&thread, &result)); + cl_assert_equal_sz(424242, (size_t)result); + + /* Ensure that the return value of `git_thread_exit` is returned. */ + cl_git_pass(git_thread_create(&thread, exit_abruptly, (void *)232323)); + cl_git_pass(git_thread_join(&thread, &result)); + cl_assert_equal_sz(232323, (size_t)result); +#endif +} diff --git a/tests/libgit2/threads/diff.c b/tests/libgit2/threads/diff.c new file mode 100644 index 000000000..04c8cb97f --- /dev/null +++ b/tests/libgit2/threads/diff.c @@ -0,0 +1,218 @@ +#include "clar_libgit2.h" +#include "thread_helpers.h" + +#ifdef GIT_THREADS + +# if defined(GIT_WIN32) +# define git_thread_yield() Sleep(0) +# elif defined(__FreeBSD__) || defined(__MidnightBSD__) || defined(__DragonFly__) +# define git_thread_yield() pthread_yield() +# else +# define git_thread_yield() sched_yield() +# endif + +#else +# define git_thread_yield() (void)0 +#endif + +static git_repository *_repo; +static git_tree *_a, *_b; +static git_atomic32 _counts[4]; +static int _check_counts; +#ifdef GIT_WIN32 +static int _retries; +#endif + +#define THREADS 20 + +void test_threads_diff__initialize(void) +{ +#ifdef GIT_WIN32 + _retries = git_win32__retries; + git_win32__retries = 1; +#endif +} + +void test_threads_diff__cleanup(void) +{ + cl_git_sandbox_cleanup(); + +#ifdef GIT_WIN32 + git_win32__retries = _retries; +#endif +} + +static void setup_trees(void) +{ + git_index *idx; + + _repo = cl_git_sandbox_reopen(); /* reopen sandbox to flush caches */ + + /* avoid competing to load initial index */ + cl_git_pass(git_repository_index(&idx, _repo)); + git_index_free(idx); + + cl_git_pass(git_revparse_single( + (git_object **)&_a, _repo, "0017bd4ab1^{tree}")); + cl_git_pass(git_revparse_single( + (git_object **)&_b, _repo, "26a125ee1b^{tree}")); + + memset(_counts, 0, sizeof(_counts)); +} + +static void free_trees(void) +{ + git_tree_free(_a); _a = NULL; + git_tree_free(_b); _b = NULL; + + if (_check_counts) { + cl_assert_equal_i(288, git_atomic32_get(&_counts[0])); + cl_assert_equal_i(112, git_atomic32_get(&_counts[1])); + cl_assert_equal_i( 80, git_atomic32_get(&_counts[2])); + cl_assert_equal_i( 96, git_atomic32_get(&_counts[3])); + } +} + +static void *run_index_diffs(void *arg) +{ + int thread = *(int *)arg; + git_repository *repo; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + size_t i; + int exp[4] = { 0, 0, 0, 0 }; + + cl_git_pass(git_repository_open(&repo, git_repository_path(_repo))); + + switch (thread & 0x03) { + case 0: /* diff index to workdir */; + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); + break; + case 1: /* diff tree 'a' to index */; + cl_git_pass(git_diff_tree_to_index(&diff, repo, _a, NULL, &opts)); + break; + case 2: /* diff tree 'b' to index */; + cl_git_pass(git_diff_tree_to_index(&diff, repo, _b, NULL, &opts)); + break; + case 3: /* diff index to workdir (explicit index) */; + { + git_index *idx; + cl_git_pass(git_repository_index(&idx, repo)); + cl_git_pass(git_diff_index_to_workdir(&diff, repo, idx, &opts)); + git_index_free(idx); + break; + } + } + + /* keep some diff stats to make sure results are as expected */ + + i = git_diff_num_deltas(diff); + git_atomic32_add(&_counts[0], (int32_t)i); + exp[0] = (int)i; + + while (i > 0) { + switch (git_diff_get_delta(diff, --i)->status) { + case GIT_DELTA_MODIFIED: exp[1]++; git_atomic32_inc(&_counts[1]); break; + case GIT_DELTA_ADDED: exp[2]++; git_atomic32_inc(&_counts[2]); break; + case GIT_DELTA_DELETED: exp[3]++; git_atomic32_inc(&_counts[3]); break; + default: break; + } + } + + switch (thread & 0x03) { + case 0: case 3: + cl_assert_equal_i(8, exp[0]); cl_assert_equal_i(4, exp[1]); + cl_assert_equal_i(0, exp[2]); cl_assert_equal_i(4, exp[3]); + break; + case 1: + cl_assert_equal_i(12, exp[0]); cl_assert_equal_i(3, exp[1]); + cl_assert_equal_i(7, exp[2]); cl_assert_equal_i(2, exp[3]); + break; + case 2: + cl_assert_equal_i(8, exp[0]); cl_assert_equal_i(3, exp[1]); + cl_assert_equal_i(3, exp[2]); cl_assert_equal_i(2, exp[3]); + break; + } + + git_diff_free(diff); + git_repository_free(repo); + git_error_clear(); + + return arg; +} + +void test_threads_diff__concurrent_diffs(void) +{ + _repo = cl_git_sandbox_init("status"); + _check_counts = 1; + + run_in_parallel( + 5, 32, run_index_diffs, setup_trees, free_trees); +} + +static void *run_index_diffs_with_modifier(void *arg) +{ + int thread = *(int *)arg; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_index *idx = NULL; + git_repository *repo; + + cl_git_pass(git_repository_open(&repo, git_repository_path(_repo))); + cl_git_pass(git_repository_index(&idx, repo)); + + /* have first thread altering the index as we go */ + if (thread == 0) { + int i; + + for (i = 0; i < 300; ++i) { + switch (i & 0x03) { + case 0: (void)git_index_add_bypath(idx, "new_file"); break; + case 1: (void)git_index_remove_bypath(idx, "modified_file"); break; + case 2: (void)git_index_remove_bypath(idx, "new_file"); break; + case 3: (void)git_index_add_bypath(idx, "modified_file"); break; + } + git_thread_yield(); + } + + goto done; + } + + /* only use explicit index in this test to prevent reloading */ + + switch (thread & 0x03) { + case 0: /* diff index to workdir */; + cl_git_pass(git_diff_index_to_workdir(&diff, repo, idx, &opts)); + break; + case 1: /* diff tree 'a' to index */; + cl_git_pass(git_diff_tree_to_index(&diff, repo, _a, idx, &opts)); + break; + case 2: /* diff tree 'b' to index */; + cl_git_pass(git_diff_tree_to_index(&diff, repo, _b, idx, &opts)); + break; + case 3: /* diff index to workdir reversed */; + opts.flags |= GIT_DIFF_REVERSE; + cl_git_pass(git_diff_index_to_workdir(&diff, repo, idx, &opts)); + break; + } + + /* results will be unpredictable with index modifier thread running */ + + git_diff_free(diff); + +done: + git_index_free(idx); + git_repository_free(repo); + git_error_clear(); + + return arg; +} + +void test_threads_diff__with_concurrent_index_modified(void) +{ + _repo = cl_git_sandbox_init("status"); + _check_counts = 0; + + run_in_parallel( + 5, 16, run_index_diffs_with_modifier, setup_trees, free_trees); +} diff --git a/tests/libgit2/threads/iterator.c b/tests/libgit2/threads/iterator.c new file mode 100644 index 000000000..33d1bda81 --- /dev/null +++ b/tests/libgit2/threads/iterator.c @@ -0,0 +1,55 @@ +#include "clar_libgit2.h" +#include "thread_helpers.h" +#include "iterator.h" + +static git_repository *_repo; + +void test_threads_iterator__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void *run_workdir_iterator(void *arg) +{ + int error = 0; + git_repository *repo; + git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry = NULL; + + iter_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_git_pass(git_repository_open(&repo, git_repository_path(_repo))); + cl_git_pass(git_iterator_for_workdir( + &iter, repo, NULL, NULL, &iter_opts)); + + while (!error) { + if (entry && entry->mode == GIT_FILEMODE_TREE) { + error = git_iterator_advance_into(&entry, iter); + + if (error == GIT_ENOTFOUND) + error = git_iterator_advance(&entry, iter); + } else { + error = git_iterator_advance(&entry, iter); + } + + if (!error) + (void)git_iterator_current_is_ignored(iter); + } + + cl_assert_equal_i(GIT_ITEROVER, error); + + git_iterator_free(iter); + git_repository_free(repo); + git_error_clear(); + return arg; +} + + +void test_threads_iterator__workdir(void) +{ + _repo = cl_git_sandbox_init("status"); + + run_in_parallel( + 1, 20, run_workdir_iterator, NULL, NULL); +} diff --git a/tests/libgit2/threads/refdb.c b/tests/libgit2/threads/refdb.c new file mode 100644 index 000000000..a4630df6a --- /dev/null +++ b/tests/libgit2/threads/refdb.c @@ -0,0 +1,220 @@ +#include "clar_libgit2.h" +#include "git2/refdb.h" +#include "refdb.h" + +static git_repository *g_repo; +static int g_expected = 0; + +#ifdef GIT_WIN32 +static bool concurrent_compress = false; +#else +static bool concurrent_compress = true; +#endif + +void test_threads_refdb__initialize(void) +{ + g_repo = NULL; +} + +void test_threads_refdb__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +#define REPEAT 20 +#define THREADS 20 +/* Number of references to create or delete in each thread */ +#define NREFS 10 + +struct th_data { + cl_git_thread_err error; + int id; + const char *path; +}; + +static void *iterate_refs(void *arg) +{ + struct th_data *data = (struct th_data *) arg; + git_reference_iterator *i; + git_reference *ref; + int count = 0, error; + git_repository *repo; + + cl_git_thread_pass(data, git_repository_open(&repo, data->path)); + do { + error = git_reference_iterator_new(&i, repo); + } while (error == GIT_ELOCKED); + cl_git_thread_pass(data, error); + + for (count = 0; !git_reference_next(&ref, i); ++count) { + cl_assert(ref != NULL); + git_reference_free(ref); + } + + if (g_expected > 0) + cl_assert_equal_i(g_expected, count); + + git_reference_iterator_free(i); + + git_repository_free(repo); + git_error_clear(); + return arg; +} + +static void *create_refs(void *arg) +{ + int i, error; + struct th_data *data = (struct th_data *) arg; + git_oid head; + char name[128]; + git_reference *ref[NREFS]; + git_repository *repo; + + cl_git_thread_pass(data, git_repository_open(&repo, data->path)); + + do { + error = git_reference_name_to_id(&head, repo, "HEAD"); + } while (error == GIT_ELOCKED); + cl_git_thread_pass(data, error); + + for (i = 0; i < NREFS; ++i) { + p_snprintf(name, sizeof(name), "refs/heads/thread-%03d-%02d", data->id, i); + do { + error = git_reference_create(&ref[i], repo, name, &head, 0, NULL); + } while (error == GIT_ELOCKED); + cl_git_thread_pass(data, error); + + if (concurrent_compress && i == NREFS/2) { + git_refdb *refdb; + cl_git_thread_pass(data, git_repository_refdb(&refdb, repo)); + do { + error = git_refdb_compress(refdb); + } while (error == GIT_ELOCKED); + cl_git_thread_pass(data, error); + git_refdb_free(refdb); + } + } + + for (i = 0; i < NREFS; ++i) + git_reference_free(ref[i]); + + git_repository_free(repo); + + git_error_clear(); + return arg; +} + +static void *delete_refs(void *arg) +{ + int i, error; + struct th_data *data = (struct th_data *) arg; + git_reference *ref; + char name[128]; + git_repository *repo; + + cl_git_thread_pass(data, git_repository_open(&repo, data->path)); + + for (i = 0; i < NREFS; ++i) { + p_snprintf( + name, sizeof(name), "refs/heads/thread-%03d-%02d", (data->id) & ~0x3, i); + + if (!git_reference_lookup(&ref, repo, name)) { + do { + error = git_reference_delete(ref); + } while (error == GIT_ELOCKED); + /* Sometimes we race with other deleter threads */ + if (error == GIT_ENOTFOUND) + error = 0; + + cl_git_thread_pass(data, error); + git_reference_free(ref); + } + + if (concurrent_compress && i == NREFS/2) { + git_refdb *refdb; + cl_git_thread_pass(data, git_repository_refdb(&refdb, repo)); + do { + error = git_refdb_compress(refdb); + } while (error == GIT_ELOCKED); + cl_git_thread_pass(data, error); + git_refdb_free(refdb); + } + } + + git_repository_free(repo); + git_error_clear(); + return arg; +} + +void test_threads_refdb__edit_while_iterate(void) +{ + int r, t; + struct th_data th_data[THREADS]; + git_oid head; + git_reference *ref; + char name[128]; + git_refdb *refdb; + +#ifdef GIT_THREADS + git_thread th[THREADS]; +#endif + + g_repo = cl_git_sandbox_init("testrepo2"); + + cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); + + /* make a bunch of references */ + + for (r = 0; r < 50; ++r) { + p_snprintf(name, sizeof(name), "refs/heads/starter-%03d", r); + cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0, NULL)); + git_reference_free(ref); + } + + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); + git_refdb_free(refdb); + + g_expected = -1; + + g_repo = cl_git_sandbox_reopen(); /* reopen to flush caches */ + + for (t = 0; t < THREADS; ++t) { + void *(*fn)(void *arg); + + switch (t & 0x3) { + case 0: fn = create_refs; break; + case 1: fn = delete_refs; break; + default: fn = iterate_refs; break; + } + + th_data[t].id = t; + th_data[t].path = git_repository_path(g_repo); + +#ifdef GIT_THREADS + cl_git_pass(git_thread_create(&th[t], fn, &th_data[t])); +#else + fn(&th_data[t]); +#endif + } + +#ifdef GIT_THREADS + for (t = 0; t < THREADS; ++t) { + cl_git_pass(git_thread_join(&th[t], NULL)); + cl_git_thread_check(&th_data[t]); + } + + memset(th, 0, sizeof(th)); + + for (t = 0; t < THREADS; ++t) { + th_data[t].id = t; + cl_git_pass(git_thread_create(&th[t], iterate_refs, &th_data[t])); + } + + for (t = 0; t < THREADS; ++t) { + cl_git_pass(git_thread_join(&th[t], NULL)); + cl_git_thread_check(&th_data[t]); + } +#endif +} diff --git a/tests/libgit2/threads/thread_helpers.c b/tests/libgit2/threads/thread_helpers.c new file mode 100644 index 000000000..54bf6097d --- /dev/null +++ b/tests/libgit2/threads/thread_helpers.c @@ -0,0 +1,44 @@ +#include "clar_libgit2.h" +#include "thread_helpers.h" + +void run_in_parallel( + int repeats, + int threads, + void *(*func)(void *), + void (*before_test)(void), + void (*after_test)(void)) +{ + int r, t, *id = git__calloc(threads, sizeof(int)); +#ifdef GIT_THREADS + git_thread *th = git__calloc(threads, sizeof(git_thread)); + cl_assert(th != NULL); +#else + void *th = NULL; +#endif + + cl_assert(id != NULL); + + for (r = 0; r < repeats; ++r) { + if (before_test) before_test(); + + for (t = 0; t < threads; ++t) { + id[t] = t; +#ifdef GIT_THREADS + cl_git_pass(git_thread_create(&th[t], func, &id[t])); +#else + cl_assert(func(&id[t]) == &id[t]); +#endif + } + +#ifdef GIT_THREADS + for (t = 0; t < threads; ++t) + cl_git_pass(git_thread_join(&th[t], NULL)); + memset(th, 0, threads * sizeof(git_thread)); +#endif + + if (after_test) after_test(); + } + + git__free(id); + git__free(th); +} diff --git a/tests/libgit2/threads/thread_helpers.h b/tests/libgit2/threads/thread_helpers.h new file mode 100644 index 000000000..0f23a4ce0 --- /dev/null +++ b/tests/libgit2/threads/thread_helpers.h @@ -0,0 +1,8 @@ +#include "thread.h" + +void run_in_parallel( + int repeats, + int threads, + void *(*func)(void *), + void (*before_test)(void), + void (*after_test)(void)); diff --git a/tests/libgit2/threads/tlsdata.c b/tests/libgit2/threads/tlsdata.c new file mode 100644 index 000000000..7c69b4444 --- /dev/null +++ b/tests/libgit2/threads/tlsdata.c @@ -0,0 +1,65 @@ +#include "clar_libgit2.h" + +#include "thread_helpers.h" + +void test_threads_tlsdata__can_set_and_get(void) +{ + git_tlsdata_key key_one, key_two, key_three; + + cl_git_pass(git_tlsdata_init(&key_one, NULL)); + cl_git_pass(git_tlsdata_init(&key_two, NULL)); + cl_git_pass(git_tlsdata_init(&key_three, NULL)); + + cl_git_pass(git_tlsdata_set(key_one, (void *)(size_t)42424242)); + cl_git_pass(git_tlsdata_set(key_two, (void *)(size_t)0xdeadbeef)); + cl_git_pass(git_tlsdata_set(key_three, (void *)(size_t)98761234)); + + cl_assert_equal_sz((size_t)42424242, git_tlsdata_get(key_one)); + cl_assert_equal_sz((size_t)0xdeadbeef, git_tlsdata_get(key_two)); + cl_assert_equal_sz((size_t)98761234, git_tlsdata_get(key_three)); + + cl_git_pass(git_tlsdata_dispose(key_one)); + cl_git_pass(git_tlsdata_dispose(key_two)); + cl_git_pass(git_tlsdata_dispose(key_three)); +} + +#ifdef GIT_THREADS + +static void *set_and_get(void *param) +{ + git_tlsdata_key *tlsdata_key = (git_tlsdata_key *)param; + int val; + + if (git_tlsdata_set(*tlsdata_key, &val) != 0 || + git_tlsdata_get(*tlsdata_key) != &val) + return (void *)0; + + return (void *)1; +} + +#endif + +#define THREAD_COUNT 10 + +void test_threads_tlsdata__threads(void) +{ +#ifdef GIT_THREADS + git_thread thread[THREAD_COUNT]; + git_tlsdata_key tlsdata; + int i; + + cl_git_pass(git_tlsdata_init(&tlsdata, NULL)); + + for (i = 0; i < THREAD_COUNT; i++) + cl_git_pass(git_thread_create(&thread[i], set_and_get, &tlsdata)); + + for (i = 0; i < THREAD_COUNT; i++) { + void *result; + + cl_git_pass(git_thread_join(&thread[i], &result)); + cl_assert_equal_sz(1, (size_t)result); + } + + cl_git_pass(git_tlsdata_dispose(tlsdata)); +#endif +} diff --git a/tests/libgit2/trace/trace.c b/tests/libgit2/trace/trace.c new file mode 100644 index 000000000..097208bff --- /dev/null +++ b/tests/libgit2/trace/trace.c @@ -0,0 +1,106 @@ +#include "clar_libgit2.h" +#include "clar_libgit2_trace.h" +#include "trace.h" + +static int written = 0; + +static void trace_callback(git_trace_level_t level, const char *message) +{ + GIT_UNUSED(level); + + cl_assert(strcmp(message, "Hello world!") == 0); + + written = 1; +} + +void test_trace_trace__initialize(void) +{ + /* If global tracing is enabled, disable for the duration of this test. */ + cl_global_trace_disable(); + + git_trace_set(GIT_TRACE_INFO, trace_callback); + written = 0; +} + +void test_trace_trace__cleanup(void) +{ + git_trace_set(GIT_TRACE_NONE, NULL); + + /* If global tracing was enabled, restart it. */ + cl_global_trace_register(); +} + +void test_trace_trace__sets(void) +{ +#ifdef GIT_TRACE + cl_assert(git_trace_level() == GIT_TRACE_INFO); +#else + cl_skip(); +#endif +} + +void test_trace_trace__can_reset(void) +{ +#ifdef GIT_TRACE + cl_assert(git_trace_level() == GIT_TRACE_INFO); + cl_git_pass(git_trace_set(GIT_TRACE_ERROR, trace_callback)); + + cl_assert(written == 0); + git_trace(GIT_TRACE_INFO, "Hello %s!", "world"); + cl_assert(written == 0); + + git_trace(GIT_TRACE_ERROR, "Hello %s!", "world"); + cl_assert(written == 1); +#else + cl_skip(); +#endif +} + +void test_trace_trace__can_unset(void) +{ +#ifdef GIT_TRACE + cl_assert(git_trace_level() == GIT_TRACE_INFO); + cl_git_pass(git_trace_set(GIT_TRACE_NONE, NULL)); + + cl_assert(git_trace_level() == GIT_TRACE_NONE); + + cl_assert(written == 0); + git_trace(GIT_TRACE_FATAL, "Hello %s!", "world"); + cl_assert(written == 0); +#else + cl_skip(); +#endif +} + +void test_trace_trace__skips_higher_level(void) +{ +#ifdef GIT_TRACE + cl_assert(written == 0); + git_trace(GIT_TRACE_DEBUG, "Hello %s!", "world"); + cl_assert(written == 0); +#else + cl_skip(); +#endif +} + +void test_trace_trace__writes(void) +{ +#ifdef GIT_TRACE + cl_assert(written == 0); + git_trace(GIT_TRACE_INFO, "Hello %s!", "world"); + cl_assert(written == 1); +#else + cl_skip(); +#endif +} + +void test_trace_trace__writes_lower_level(void) +{ +#ifdef GIT_TRACE + cl_assert(written == 0); + git_trace(GIT_TRACE_ERROR, "Hello %s!", "world"); + cl_assert(written == 1); +#else + cl_skip(); +#endif +} diff --git a/tests/libgit2/trace/windows/stacktrace.c b/tests/libgit2/trace/windows/stacktrace.c new file mode 100644 index 000000000..0a77ef99d --- /dev/null +++ b/tests/libgit2/trace/windows/stacktrace.c @@ -0,0 +1,152 @@ +#include "clar_libgit2.h" +#include "win32/w32_leakcheck.h" + +#if defined(GIT_WIN32_LEAKCHECK) +static void a(void) +{ + char buf[10000]; + + cl_assert(git_win32_leakcheck_stack(buf, sizeof(buf), 0, NULL, NULL) == 0); + +#if 0 + fprintf(stderr, "Stacktrace from [%s:%d]:\n%s\n", __FILE__, __LINE__, buf); +#endif +} + +static void b(void) +{ + a(); +} + +static void c(void) +{ + b(); +} +#endif + +void test_trace_windows_stacktrace__basic(void) +{ +#if defined(GIT_WIN32_LEAKCHECK) + c(); +#endif +} + + +void test_trace_windows_stacktrace__leaks(void) +{ +#if defined(GIT_WIN32_LEAKCHECK) + void * p1; + void * p2; + void * p3; + void * p4; + int before, after; + int leaks; + int error; + + /* remember outstanding leaks due to set setup + * and set mark/checkpoint. + */ + before = git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL | + GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK, + NULL); + + p1 = git__malloc(5); + leaks = git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, + "p1"); + cl_assert_equal_i(1, leaks); + + p2 = git__malloc(5); + leaks = git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, + "p1,p2"); + cl_assert_equal_i(2, leaks); + + p3 = git__malloc(5); + leaks = git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, + "p1,p2,p3"); + cl_assert_equal_i(3, leaks); + + git__free(p2); + leaks = git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, + "p1,p3"); + cl_assert_equal_i(2, leaks); + + /* move the mark. only new leaks should appear afterwards */ + error = git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK, + NULL); + /* cannot use cl_git_pass() since that may allocate memory. */ + cl_assert_equal_i(0, error); + + leaks = git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, + "not_p1,not_p3"); + cl_assert_equal_i(0, leaks); + + p4 = git__malloc(5); + leaks = git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, + "p4,not_p1,not_p3"); + cl_assert_equal_i(1, leaks); + + git__free(p1); + git__free(p3); + leaks = git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, + "p4"); + cl_assert_equal_i(1, leaks); + + git__free(p4); + leaks = git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, + "end"); + cl_assert_equal_i(0, leaks); + + /* confirm current absolute leaks count matches beginning value. */ + after = git_win32_leakcheck_stacktrace_dump( + GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | + GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL, + "total"); + cl_assert_equal_i(before, after); +#endif +} + +#if defined(GIT_WIN32_LEAKCHECK) +static void aux_cb_alloc__1(unsigned int *aux_id) +{ + static unsigned int aux_counter = 0; + + *aux_id = aux_counter++; +} + +static void aux_cb_lookup__1(unsigned int aux_id, char *aux_msg, size_t aux_msg_len) +{ + p_snprintf(aux_msg, aux_msg_len, "\tQQ%08x\n", aux_id); +} + +#endif + +void test_trace_windows_stacktrace__aux1(void) +{ +#if defined(GIT_WIN32_LEAKCHECK) + git_win32_leakcheck_stack_set_aux_cb(aux_cb_alloc__1, aux_cb_lookup__1); + c(); + c(); + c(); + c(); + git_win32_leakcheck_stack_set_aux_cb(NULL, NULL); +#endif +} diff --git a/tests/libgit2/transport/register.c b/tests/libgit2/transport/register.c new file mode 100644 index 000000000..88ba247de --- /dev/null +++ b/tests/libgit2/transport/register.c @@ -0,0 +1,79 @@ +#include "clar_libgit2.h" +#include "git2/sys/transport.h" + +static git_transport _transport = GIT_TRANSPORT_INIT; + +static int dummy_transport(git_transport **transport, git_remote *owner, void *param) +{ + *transport = &_transport; + GIT_UNUSED(owner); + GIT_UNUSED(param); + return 0; +} + +void test_transport_register__custom_transport(void) +{ + git_transport *transport; + + cl_git_pass(git_transport_register("something", dummy_transport, NULL)); + + cl_git_pass(git_transport_new(&transport, NULL, "something://somepath")); + + cl_assert(transport == &_transport); + + cl_git_pass(git_transport_unregister("something")); +} + +void test_transport_register__custom_transport_error_doubleregister(void) +{ + cl_git_pass(git_transport_register("something", dummy_transport, NULL)); + + cl_git_fail_with(git_transport_register("something", dummy_transport, NULL), GIT_EEXISTS); + + cl_git_pass(git_transport_unregister("something")); +} + +void test_transport_register__custom_transport_error_remove_non_existing(void) +{ + cl_git_fail_with(git_transport_unregister("something"), GIT_ENOTFOUND); +} + +void test_transport_register__custom_transport_ssh(void) +{ + const char *urls[] = { + "ssh://somehost:somepath", + "ssh+git://somehost:somepath", + "git+ssh://somehost:somepath", + "git@somehost:somepath", + "ssh://somehost:somepath%20with%20%spaces", + "ssh://somehost:somepath with spaces" + }; + git_transport *transport; + unsigned i; + + for (i = 0; i < ARRAY_SIZE(urls); i++) { +#ifndef GIT_SSH + cl_git_fail_with(git_transport_new(&transport, NULL, urls[i]), -1); +#else + cl_git_pass(git_transport_new(&transport, NULL, urls[i])); + transport->free(transport); +#endif + } + + cl_git_pass(git_transport_register("ssh", dummy_transport, NULL)); + + cl_git_pass(git_transport_new(&transport, NULL, "git@somehost:somepath")); + + cl_assert(transport == &_transport); + + cl_git_pass(git_transport_unregister("ssh")); + + for (i = 0; i < ARRAY_SIZE(urls); i++) { +#ifndef GIT_SSH + cl_git_fail_with(git_transport_new(&transport, NULL, urls[i]), -1); +#else + cl_git_pass(git_transport_new(&transport, NULL, urls[i])); + transport->free(transport); +#endif + } +} diff --git a/tests/libgit2/transports/smart/packet.c b/tests/libgit2/transports/smart/packet.c new file mode 100644 index 000000000..5b623a378 --- /dev/null +++ b/tests/libgit2/transports/smart/packet.c @@ -0,0 +1,340 @@ +#include "clar_libgit2.h" +#include "transports/smart.h" + +enum expected_status { + PARSE_SUCCESS, + PARSE_FAILURE +}; + +static void assert_flush_parses(const char *line) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_FLUSH); + cl_assert_equal_strn(endptr, line + 4, linelen - 4); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_data_pkt_parses(const char *line, const char *expected_data, size_t expected_len) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_data *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_DATA); + cl_assert_equal_i(pkt->len, expected_len); + cl_assert_equal_strn(pkt->data, expected_data, expected_len); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_sideband_progress_parses(const char *line, const char *expected_data, size_t expected_len) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_progress *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_PROGRESS); + cl_assert_equal_i(pkt->len, expected_len); + cl_assert_equal_strn(pkt->data, expected_data, expected_len); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_error_parses(const char *line, const char *expected_error, size_t expected_len) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_err *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_ERR); + cl_assert_equal_i(pkt->len, expected_len); + cl_assert_equal_strn(pkt->error, expected_error, expected_len); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_ack_parses(const char *line, const char *expected_oid, enum git_ack_status expected_status) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_ack *pkt; + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, expected_oid)); + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_ACK); + cl_assert_equal_oid(&pkt->oid, &oid); + cl_assert_equal_i(pkt->status, expected_status); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_nak_parses(const char *line) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_NAK); + cl_assert_equal_strn(endptr, line + 7, linelen - 7); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_comment_parses(const char *line, const char *expected_comment) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_comment *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_COMMENT); + cl_assert_equal_strn(pkt->comment, expected_comment, strlen(expected_comment)); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_ok_parses(const char *line, const char *expected_ref) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_ok *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_OK); + cl_assert_equal_strn(pkt->ref, expected_ref, strlen(expected_ref)); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_unpack_parses(const char *line, bool ok) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_unpack *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_UNPACK); + cl_assert_equal_i(pkt->unpack_ok, ok); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_ng_parses(const char *line, const char *expected_ref, const char *expected_msg) +{ + size_t linelen = strlen(line) + 1; + const char *endptr; + git_pkt_ng *pkt; + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_NG); + cl_assert_equal_strn(pkt->ref, expected_ref, strlen(expected_ref)); + cl_assert_equal_strn(pkt->msg, expected_msg, strlen(expected_msg)); + + git_pkt_free((git_pkt *) pkt); +} + +#define assert_ref_parses(line, expected_oid, expected_ref, expected_capabilities) \ + assert_ref_parses_(line, sizeof(line), expected_oid, expected_ref, expected_capabilities) + +static void assert_ref_parses_(const char *line, size_t linelen, const char *expected_oid, + const char *expected_ref, const char *expected_capabilities) +{ + const char *endptr; + git_pkt_ref *pkt; + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, expected_oid)); + + cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); + cl_assert_equal_i(pkt->type, GIT_PKT_REF); + cl_assert_equal_oid(&pkt->head.oid, &oid); + cl_assert_equal_strn(pkt->head.name, expected_ref, strlen(expected_ref)); + if (expected_capabilities) + cl_assert_equal_strn(pkt->capabilities, expected_capabilities, strlen(expected_capabilities)); + else + cl_assert_equal_p(NULL, pkt->capabilities); + + git_pkt_free((git_pkt *) pkt); +} + +static void assert_pkt_fails(const char *line) +{ + const char *endptr; + git_pkt *pkt; + cl_git_fail(git_pkt_parse_line(&pkt, &endptr, line, strlen(line) + 1)); +} + +void test_transports_smart_packet__parsing_garbage_fails(void) +{ + assert_pkt_fails("0foobar"); + assert_pkt_fails("00foobar"); + assert_pkt_fails("000foobar"); + assert_pkt_fails("0001"); + assert_pkt_fails(""); + assert_pkt_fails("0"); + assert_pkt_fails("0i00"); + assert_pkt_fails("f"); +} + +void test_transports_smart_packet__flush_parses(void) +{ + assert_flush_parses("0000"); + assert_flush_parses("0000foobar"); +} + +void test_transports_smart_packet__data_pkt(void) +{ + assert_pkt_fails("000foobar"); + assert_pkt_fails("0001o"); + assert_pkt_fails("0001\1"); + assert_data_pkt_parses("0005\1", "", 0); + assert_pkt_fails("0009\1o"); + assert_data_pkt_parses("0009\1data", "data", 4); + assert_data_pkt_parses("000a\1data", "data", 5); +} + +void test_transports_smart_packet__sideband_progress_pkt(void) +{ + assert_pkt_fails("0001\2"); + assert_sideband_progress_parses("0005\2", "", 0); + assert_pkt_fails("0009\2o"); + assert_sideband_progress_parses("0009\2data", "data", 4); + assert_sideband_progress_parses("000a\2data", "data", 5); +} + +void test_transports_smart_packet__sideband_err_pkt(void) +{ + assert_pkt_fails("0001\3"); + assert_error_parses("0005\3", "", 0); + assert_pkt_fails("0009\3o"); + assert_error_parses("0009\3data", "data", 4); + assert_error_parses("000a\3data", "data", 5); +} + +void test_transports_smart_packet__ack_pkt(void) +{ + assert_ack_parses("0030ACK 0000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000", 0); + assert_ack_parses("0039ACK 0000000000000000000000000000000000000000 continue", + "0000000000000000000000000000000000000000", + GIT_ACK_CONTINUE); + assert_ack_parses("0037ACK 0000000000000000000000000000000000000000 common", + "0000000000000000000000000000000000000000", + GIT_ACK_COMMON); + assert_ack_parses("0037ACK 0000000000000000000000000000000000000000 ready", + "0000000000000000000000000000000000000000", + GIT_ACK_READY); + + /* these should fail as they don't have OIDs */ + assert_pkt_fails("0007ACK"); + assert_pkt_fails("0008ACK "); + + /* this one is missing a space and should thus fail */ + assert_pkt_fails("0036ACK00000000000000000x0000000000000000000000 ready"); + + /* the following ones have invalid OIDs and should thus fail */ + assert_pkt_fails("0037ACK 00000000000000000x0000000000000000000000 ready"); + assert_pkt_fails("0036ACK 000000000000000000000000000000000000000 ready"); + assert_pkt_fails("0036ACK 00000000000000000x0000000000000000000000ready"); + + /* this one has an invalid status and should thus fail */ + assert_pkt_fails("0036ACK 0000000000000000000000000000000000000000 read"); +} + +void test_transports_smart_packet__nak_pkt(void) +{ + assert_nak_parses("0007NAK"); + assert_pkt_fails("0007NaK"); + assert_pkt_fails("0007nak"); + assert_nak_parses("0007NAKfoobar"); + assert_pkt_fails("0007nakfoobar"); + assert_pkt_fails("0007 NAK"); +} + +void test_transports_smart_packet__error_pkt(void) +{ + assert_pkt_fails("0007ERR"); + assert_pkt_fails("0008ERRx"); + assert_error_parses("0008ERR ", "", 0); + assert_error_parses("000EERR ERRMSG", "ERRMSG", 6); +} + +void test_transports_smart_packet__comment_pkt(void) +{ + assert_comment_parses("0005#", ""); + assert_comment_parses("000B#foobar", "#fooba"); + assert_comment_parses("000C#foobar", "#foobar"); + assert_comment_parses("001A#this is a comment\nfoo", "#this is a comment\nfoo"); +} + +void test_transports_smart_packet__ok_pkt(void) +{ + assert_pkt_fails("0007ok\n"); + assert_ok_parses("0007ok ", ""); + assert_ok_parses("0008ok \n", ""); + assert_ok_parses("0008ok x", "x"); + assert_ok_parses("0009ok x\n", "x"); + assert_pkt_fails("001OK ref/foo/bar"); + assert_ok_parses("0012ok ref/foo/bar", "ref/foo/bar"); + assert_pkt_fails("0013OK ref/foo/bar\n"); + assert_ok_parses("0013ok ref/foo/bar\n", "ref/foo/bar"); +} + +void test_transports_smart_packet__ng_pkt(void) +{ + /* TODO: same as for ok pkt */ + assert_pkt_fails("0007ng\n"); + assert_pkt_fails("0008ng \n"); + assert_pkt_fails("000Bng ref\n"); + assert_pkt_fails("000Bng ref\n"); + /* TODO: is this a valid packet line? Probably not. */ + assert_ng_parses("000Ang x\n", "", "x"); + assert_ng_parses("000Fng ref msg\n", "ref", "msg"); + assert_ng_parses("000Fng ref msg\n", "ref", "msg"); +} + +void test_transports_smart_packet__unpack_pkt(void) +{ + assert_unpack_parses("000Dunpack ok", 1); + assert_unpack_parses("000Dunpack ng error-msg", 0); + /* TODO: the following tests should fail */ + assert_unpack_parses("000Aunpack", 0); + assert_unpack_parses("0011unpack foobar", 0); + assert_unpack_parses("0010unpack ng ok", 0); + assert_unpack_parses("0010unpack okfoo", 1); +} + +void test_transports_smart_packet__ref_pkt(void) +{ + assert_pkt_fails("002C0000000000000000000000000000000000000000"); + assert_pkt_fails("002D0000000000000000000000000000000000000000\n"); + assert_pkt_fails("00300000000000000000000000000000000000000000HEAD"); + assert_pkt_fails("004800000000x0000000000000000000000000000000 refs/heads/master\0multi_ack"); + assert_ref_parses( + "003F0000000000000000000000000000000000000000 refs/heads/master\0", + "0000000000000000000000000000000000000000", "refs/heads/master", ""); + assert_ref_parses( + "00480000000000000000000000000000000000000000 refs/heads/master\0multi_ack", + "0000000000000000000000000000000000000000", "refs/heads/master", "multi_ack"); + assert_ref_parses( + "00460000000000000000000000000000000000000000 refs/heads/master\0one two", + "0000000000000000000000000000000000000000", "refs/heads/master", "one two"); + assert_ref_parses( + "00310000000000000000000000000000000000000000 HEAD", + "0000000000000000000000000000000000000000", "HEAD", NULL); + assert_pkt_fails("0031000000000000000000000000000000000000000 HEAD"); + assert_ref_parses( + "00360000000000000000000000000000000000000000 HEAD HEAD", + "0000000000000000000000000000000000000000", "HEAD HEAD", NULL); +} diff --git a/tests/libgit2/valgrind-supp-mac.txt b/tests/libgit2/valgrind-supp-mac.txt new file mode 100644 index 000000000..3298abee6 --- /dev/null +++ b/tests/libgit2/valgrind-supp-mac.txt @@ -0,0 +1,176 @@ +{ + libgit2-git-error-set-buffer + Memcheck:Leak + ... + fun:git__realloc + fun:git_str_try_grow + fun:git_str_grow + fun:git_str_vprintf + fun:git_error_set +} +{ + mac-setenv-leak-1 + Memcheck:Leak + fun:malloc_zone_malloc + fun:__setenv + fun:setenv +} +{ + mac-setenv-leak-2 + Memcheck:Leak + fun:malloc_zone_malloc + fun:malloc_set_zone_name + ... + fun:init__zone0 + fun:setenv +} +{ + mac-dyld-initializer-leak + Memcheck:Leak + fun:malloc + ... + fun:dyld_register_image_state_change_handler + fun:_dyld_initializer +} +{ + mac-tz-leak-1 + Memcheck:Leak + ... + fun:token_table_add + fun:notify_register_check + fun:notify_register_tz +} +{ + mac-tz-leak-2 + Memcheck:Leak + fun:malloc + fun:tzload +} +{ + mac-tz-leak-3 + Memcheck:Leak + fun:malloc + fun:tzsetwall_basic +} +{ + mac-tz-leak-4 + Memcheck:Leak + fun:malloc + fun:gmtsub +} +{ + mac-system-init-leak-1 + Memcheck:Leak + ... + fun:_libxpc_initializer + fun:libSystem_initializer +} +{ + mac-system-init-leak-2 + Memcheck:Leak + ... + fun:__keymgr_initializer + fun:libSystem_initializer +} +{ + mac-puts-leak + Memcheck:Leak + fun:malloc + fun:__smakebuf + ... + fun:puts +} +{ + mac-ssl-uninitialized-1 + Memcheck:Cond + obj:/usr/lib/libcrypto.0.9.8.dylib + ... + fun:ssl23_connect +} +{ + mac-ssl-uninitialized-2 + Memcheck:Cond + ... + obj:/usr/lib/libssl.0.9.8.dylib + ... + fun:ssl23_connect +} +{ + mac-ssl-uninitialized-3 + Memcheck:Value8 + obj:/usr/lib/libcrypto.0.9.8.dylib + ... + fun:ssl23_connect +} +{ + mac-ssl-leak-1 + Memcheck:Leak + ... + fun:ERR_load_strings +} +{ + mac-ssl-leak-2 + Memcheck:Leak + ... + fun:SSL_library_init +} +{ + mac-ssl-leak-3 + Memcheck:Leak + ... + fun:si_module_with_name + fun:getaddrinfo +} +{ + mac-ssl-leak-4 + Memcheck:Leak + fun:malloc + fun:CRYPTO_malloc + ... + fun:ssl3_get_server_certificate +} +{ + mac-ssl-leak-5 + Memcheck:Leak + fun:malloc + fun:CRYPTO_malloc + ... + fun:ERR_put_error +} +{ + clar-printf-buf + Memcheck:Leak + fun:malloc + fun:__smakebuf + ... + fun:printf + fun:clar_print_init +} +{ + molo-1 + Memcheck:Leak + fun:malloc_zone_malloc + ... + fun:_objc_init +} +{ + molo-2 + Memcheck:Leak + fun:malloc_zone_calloc + ... + fun:_objc_init +} +{ + molo-3 + Memcheck:Leak + fun:malloc + ... + fun:_objc_init +} +{ + molo-4 + Memcheck:Leak + fun:malloc + ... + fun:dyld_register_image_state_change_handler +} diff --git a/tests/libgit2/win32/forbidden.c b/tests/libgit2/win32/forbidden.c new file mode 100644 index 000000000..5c007987b --- /dev/null +++ b/tests/libgit2/win32/forbidden.c @@ -0,0 +1,182 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "submodule.h" + +static const char *repo_name = "win32-forbidden"; +static git_repository *repo; + +void test_win32_forbidden__initialize(void) +{ + repo = cl_git_sandbox_init(repo_name); +} + +void test_win32_forbidden__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_win32_forbidden__can_open_index(void) +{ + git_index *index; + cl_git_pass(git_repository_index(&index, repo)); + cl_assert_equal_i(7, git_index_entrycount(index)); + + /* ensure we can even write the unmodified index */ + cl_git_pass(git_index_write(index)); + + git_index_free(index); +} + +void test_win32_forbidden__can_add_forbidden_filename_with_entry(void) +{ + git_index *index; + git_index_entry entry = {{0}}; + + cl_git_pass(git_repository_index(&index, repo)); + + entry.path = "aux"; + entry.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&entry.id, "da623abd956bb2fd8052c708c7ed43f05d192d37"); + + cl_git_pass(git_index_add(index, &entry)); + + git_index_free(index); +} + +void test_win32_forbidden__cannot_add_dot_git_even_with_entry(void) +{ + git_index *index; + git_index_entry entry = {{0}}; + + cl_git_pass(git_repository_index(&index, repo)); + + entry.path = "foo/.git"; + entry.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&entry.id, "da623abd956bb2fd8052c708c7ed43f05d192d37"); + + cl_git_fail(git_index_add(index, &entry)); + + git_index_free(index); +} + +void test_win32_forbidden__cannot_add_forbidden_filename_from_filesystem(void) +{ + git_index *index; + + /* since our function calls are very low-level, we can create `aux.`, + * but we should not be able to add it to the index + */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_write2file("win32-forbidden/aux.", "foo\n", 4, O_RDWR | O_CREAT, 0666); + +#ifdef GIT_WIN32 + cl_git_fail(git_index_add_bypath(index, "aux.")); +#else + cl_git_pass(git_index_add_bypath(index, "aux.")); +#endif + + cl_must_pass(p_unlink("win32-forbidden/aux.")); + git_index_free(index); +} + +static int dummy_submodule_cb( + git_submodule *sm, const char *name, void *payload) +{ + GIT_UNUSED(sm); + GIT_UNUSED(name); + GIT_UNUSED(payload); + return 0; +} + +void test_win32_forbidden__can_diff_tree_to_index(void) +{ + git_diff *diff; + git_tree *tree; + + cl_git_pass(git_repository_head_tree(&tree, repo)); + cl_git_pass(git_diff_tree_to_index(&diff, repo, tree, NULL, NULL)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + git_tree_free(tree); +} + +void test_win32_forbidden__can_diff_tree_to_tree(void) +{ + git_diff *diff; + git_tree *tree; + + cl_git_pass(git_repository_head_tree(&tree, repo)); + cl_git_pass(git_diff_tree_to_tree(&diff, repo, tree, tree, NULL)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + git_tree_free(tree); +} + +void test_win32_forbidden__can_diff_index_to_workdir(void) +{ + git_index *index; + git_diff *diff; + const git_diff_delta *delta; + git_tree *tree; + size_t i; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_repository_head_tree(&tree, repo)); + cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, NULL)); + + for (i = 0; i < git_diff_num_deltas(diff); i++) { + delta = git_diff_get_delta(diff, i); + cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); + } + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); +} + +void test_win32_forbidden__checking_out_forbidden_index_fails(void) +{ +#ifdef GIT_WIN32 + git_index *index; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_diff *diff; + const git_diff_delta *delta; + git_tree *tree; + size_t num_deltas, i; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_fail(git_checkout_index(repo, index, &opts)); + + cl_git_pass(git_repository_head_tree(&tree, repo)); + cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, NULL)); + + num_deltas = git_diff_num_deltas(diff); + + cl_assert(num_deltas > 0); + + for (i = 0; i < num_deltas; i++) { + delta = git_diff_get_delta(diff, i); + cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); + } + + git_diff_free(diff); + git_tree_free(tree); + git_index_free(index); +#endif +} + +void test_win32_forbidden__can_query_submodules(void) +{ + cl_git_pass(git_submodule_foreach(repo, dummy_submodule_cb, NULL)); +} + +void test_win32_forbidden__can_blame_file(void) +{ + git_blame *blame; + + cl_git_pass(git_blame_file(&blame, repo, "aux", NULL)); + git_blame_free(blame); +} diff --git a/tests/libgit2/win32/longpath.c b/tests/libgit2/win32/longpath.c new file mode 100644 index 000000000..4be86db5a --- /dev/null +++ b/tests/libgit2/win32/longpath.c @@ -0,0 +1,130 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" +#include "clone.h" +#include "futils.h" +#include "repository.h" + +static git_str path = GIT_STR_INIT; + +#define LONG_FILENAME "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt" + +void test_win32_longpath__initialize(void) +{ +#ifdef GIT_WIN32 + const char *base = clar_sandbox_path(); + size_t base_len = strlen(base); + size_t remain = MAX_PATH - base_len; + size_t i; + + git_str_clear(&path); + git_str_puts(&path, base); + git_str_putc(&path, '/'); + + cl_assert(remain < (MAX_PATH - 5)); + + for (i = 0; i < (remain - 5); i++) + git_str_putc(&path, 'a'); +#endif +} + +void test_win32_longpath__cleanup(void) +{ + git_str_dispose(&path); + cl_git_sandbox_cleanup(); +} + +void test_win32_longpath__errmsg_on_checkout(void) +{ +#ifdef GIT_WIN32 + git_repository *repo; + + cl_git_fail(git_clone(&repo, cl_fixture("testrepo.git"), path.ptr, NULL)); + cl_assert(git__prefixcmp(git_error_last()->message, "path too long") == 0); +#endif +} + +void test_win32_longpath__workdir_path_validated(void) +{ +#ifdef GIT_WIN32 + git_repository *repo = cl_git_sandbox_init("testrepo"); + git_str out = GIT_STR_INIT; + + cl_git_pass(git_repository_workdir_path(&out, repo, "a.txt")); + + /* even if the repo path is a drive letter, this is too long */ + cl_git_fail(git_repository_workdir_path(&out, repo, LONG_FILENAME)); + cl_assert(git__prefixcmp(git_error_last()->message, "path too long") == 0); + + cl_repo_set_bool(repo, "core.longpaths", true); + cl_git_pass(git_repository_workdir_path(&out, repo, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt")); + cl_git_pass(git_repository_workdir_path(&out, repo, LONG_FILENAME)); + git_str_dispose(&out); +#endif +} + +#ifdef GIT_WIN32 +static void assert_longpath_status_and_add(git_repository *repo, const char *wddata, const char *repodata) { + git_index *index; + git_blob *blob; + git_str out = GIT_STR_INIT; + const git_index_entry *entry; + unsigned int status_flags; + + cl_git_pass(git_repository_workdir_path(&out, repo, LONG_FILENAME)); + + cl_git_rewritefile(out.ptr, wddata); + + cl_git_pass(git_status_file(&status_flags, repo, LONG_FILENAME)); + cl_assert_equal_i(GIT_STATUS_WT_NEW, status_flags); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, LONG_FILENAME)); + + cl_git_pass(git_status_file(&status_flags, repo, LONG_FILENAME)); + cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status_flags); + + cl_assert((entry = git_index_get_bypath(index, LONG_FILENAME, 0)) != NULL); + cl_git_pass(git_blob_lookup(&blob, repo, &entry->id)); + cl_assert_equal_s(repodata, git_blob_rawcontent(blob)); + + git_blob_free(blob); + git_index_free(index); + git_str_dispose(&out); +} +#endif + +void test_win32_longpath__status_and_add(void) +{ +#ifdef GIT_WIN32 + git_repository *repo = cl_git_sandbox_init("testrepo"); + + cl_repo_set_bool(repo, "core.longpaths", true); + + /* + * Doing no content filtering, we expect the data we add + * to be the data in the repository. + */ + assert_longpath_status_and_add(repo, + "This is a long path.\r\n", + "This is a long path.\r\n"); +#endif +} + +void test_win32_longpath__status_and_add_with_filter(void) +{ +#ifdef GIT_WIN32 + git_repository *repo = cl_git_sandbox_init("testrepo"); + + cl_repo_set_bool(repo, "core.longpaths", true); + cl_repo_set_bool(repo, "core.autocrlf", true); + + /* + * With `core.autocrlf`, we expect the data we add to have + * newline conversion performed. + */ + assert_longpath_status_and_add(repo, + "This is a long path.\r\n", + "This is a long path.\n"); +#endif +} diff --git a/tests/libgit2/win32/systemdir.c b/tests/libgit2/win32/systemdir.c new file mode 100644 index 000000000..52c1784a1 --- /dev/null +++ b/tests/libgit2/win32/systemdir.c @@ -0,0 +1,338 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "sysdir.h" +#include "win32/findfile.h" + +#ifdef GIT_WIN32 +static char *path_save; +static git_str gfw_path_root = GIT_STR_INIT; +static git_str gfw_registry_root = GIT_STR_INIT; +#endif + +void test_win32_systemdir__initialize(void) +{ +#ifdef GIT_WIN32 + git_str path_env = GIT_STR_INIT; + + path_save = cl_getenv("PATH"); + git_win32__set_registry_system_dir(L""); + + cl_git_pass(git_str_puts(&path_env, "C:\\GitTempTest\\Foo;\"c:\\program files\\doesnotexisttesttemp\";C:\\fakefakedoesnotexist")); + cl_setenv("PATH", path_env.ptr); + + cl_git_pass(git_str_puts(&gfw_path_root, clar_sandbox_path())); + cl_git_pass(git_str_puts(&gfw_path_root, "/fake_gfw_path_install")); + + cl_git_pass(git_str_puts(&gfw_registry_root, clar_sandbox_path())); + cl_git_pass(git_str_puts(&gfw_registry_root, "/fake_gfw_registry_install")); + + git_str_dispose(&path_env); +#endif +} + +void test_win32_systemdir__cleanup(void) +{ +#ifdef GIT_WIN32 + cl_fixture_cleanup("fake_gfw_path_install"); + cl_fixture_cleanup("fake_gfw_registry_install"); + git_str_dispose(&gfw_path_root); + git_str_dispose(&gfw_registry_root); + + cl_setenv("PATH", path_save); + git__free(path_save); + path_save = NULL; + + git_win32__set_registry_system_dir(NULL); + cl_sandbox_set_search_path_defaults(); +#endif +} + +#ifdef GIT_WIN32 +static void fix_path(git_str *s) +{ + char *c; + + for (c = s->ptr; *c; c++) { + if (*c == '/') + *c = '\\'; + } +} + +static void populate_fake_gfw( + git_str *expected_etc_dir, + const char *root, + const char *token, + bool create_gitconfig, + bool create_mingw64_gitconfig, + bool add_to_path, + bool add_to_registry) +{ + git_str bin_path = GIT_STR_INIT, exe_path = GIT_STR_INIT, + etc_path = GIT_STR_INIT, mingw64_path = GIT_STR_INIT, + config_path = GIT_STR_INIT, path_env = GIT_STR_INIT, + config_data = GIT_STR_INIT; + + cl_git_pass(git_str_puts(&bin_path, root)); + cl_git_pass(git_str_puts(&bin_path, "/cmd")); + cl_git_pass(git_futils_mkdir_r(bin_path.ptr, 0755)); + + cl_git_pass(git_str_puts(&exe_path, bin_path.ptr)); + cl_git_pass(git_str_puts(&exe_path, "/git.cmd")); + cl_git_mkfile(exe_path.ptr, "This is a fake executable."); + + cl_git_pass(git_str_puts(&etc_path, root)); + cl_git_pass(git_str_puts(&etc_path, "/etc")); + cl_git_pass(git_futils_mkdir_r(etc_path.ptr, 0755)); + + cl_git_pass(git_str_puts(&mingw64_path, root)); + cl_git_pass(git_str_puts(&mingw64_path, "/mingw64/etc")); + cl_git_pass(git_futils_mkdir_r(mingw64_path.ptr, 0755)); + + if (create_gitconfig) { + git_str_clear(&config_data); + git_str_printf(&config_data, "[gfw]\n\ttest = etc %s\n", token); + + cl_git_pass(git_str_puts(&config_path, etc_path.ptr)); + cl_git_pass(git_str_puts(&config_path, "/gitconfig")); + cl_git_mkfile(config_path.ptr, config_data.ptr); + } + + if (create_mingw64_gitconfig) { + git_str_clear(&config_data); + git_str_printf(&config_data, "[gfw]\n\ttest = mingw64 %s\n", token); + + git_str_clear(&config_path); + cl_git_pass(git_str_puts(&config_path, mingw64_path.ptr)); + cl_git_pass(git_str_puts(&config_path, "/gitconfig")); + cl_git_mkfile(config_path.ptr, config_data.ptr); + } + + if (add_to_path) { + fix_path(&bin_path); + cl_git_pass(git_str_puts(&path_env, "C:\\GitTempTest\\Foo;\"c:\\program files\\doesnotexisttesttemp\";")); + cl_git_pass(git_str_puts(&path_env, bin_path.ptr)); + cl_git_pass(git_str_puts(&path_env, ";C:\\fakefakedoesnotexist")); + cl_setenv("PATH", path_env.ptr); + } + + if (add_to_registry) { + git_win32_path registry_path; + size_t offset = 0; + + cl_assert(git_win32_path_from_utf8(registry_path, root) >= 0); + if (wcsncmp(registry_path, L"\\\\?\\", CONST_STRLEN("\\\\?\\")) == 0) + offset = CONST_STRLEN("\\\\?\\"); + git_win32__set_registry_system_dir(registry_path + offset); + } + + cl_git_pass(git_str_join(expected_etc_dir, GIT_PATH_LIST_SEPARATOR, expected_etc_dir->ptr, etc_path.ptr)); + cl_git_pass(git_str_join(expected_etc_dir, GIT_PATH_LIST_SEPARATOR, expected_etc_dir->ptr, mingw64_path.ptr)); + + git_str_dispose(&bin_path); + git_str_dispose(&exe_path); + git_str_dispose(&etc_path); + git_str_dispose(&mingw64_path); + git_str_dispose(&config_path); + git_str_dispose(&path_env); + git_str_dispose(&config_data); +} + +static void populate_fake_ecosystem( + git_str *expected_etc_dir, + bool create_gitconfig, + bool create_mingw64_gitconfig, + bool path, + bool registry) +{ + if (path) + populate_fake_gfw(expected_etc_dir, gfw_path_root.ptr, "path", create_gitconfig, create_mingw64_gitconfig, true, false); + + if (registry) + populate_fake_gfw(expected_etc_dir, gfw_registry_root.ptr, "registry", create_gitconfig, create_mingw64_gitconfig, false, true); +} +#endif + +void test_win32_systemdir__finds_etc_in_path(void) +{ +#ifdef GIT_WIN32 + git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; + git_config *cfg; + git_buf value = GIT_BUF_INIT; + + populate_fake_ecosystem(&expected, true, false, true, false); + + cl_git_pass(git_win32__find_system_dirs(&out, "etc")); + cl_assert_equal_s(out.ptr, expected.ptr); + + git_sysdir_reset(); + + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); + cl_assert_equal_s("etc path", value.ptr); + + git_buf_dispose(&value); + git_str_dispose(&expected); + git_str_dispose(&out); + git_config_free(cfg); +#endif +} + +void test_win32_systemdir__finds_mingw64_etc_in_path(void) +{ +#ifdef GIT_WIN32 + git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; + git_config* cfg; + git_buf value = GIT_BUF_INIT; + + populate_fake_ecosystem(&expected, false, true, true, false); + + cl_git_pass(git_win32__find_system_dirs(&out, "etc")); + cl_assert_equal_s(out.ptr, expected.ptr); + + git_sysdir_reset(); + + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); + cl_assert_equal_s("mingw64 path", value.ptr); + + git_buf_dispose(&value); + git_str_dispose(&expected); + git_str_dispose(&out); + git_config_free(cfg); +#endif +} + +void test_win32_systemdir__prefers_etc_to_mingw64_in_path(void) +{ +#ifdef GIT_WIN32 + git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; + git_config* cfg; + git_buf value = GIT_BUF_INIT; + + populate_fake_ecosystem(&expected, true, true, true, false); + + cl_git_pass(git_win32__find_system_dirs(&out, "etc")); + cl_assert_equal_s(out.ptr, expected.ptr); + + git_sysdir_reset(); + + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); + cl_assert_equal_s("etc path", value.ptr); + + git_buf_dispose(&value); + git_str_dispose(&expected); + git_str_dispose(&out); + git_config_free(cfg); +#endif +} + +void test_win32_systemdir__finds_etc_in_registry(void) +{ +#ifdef GIT_WIN32 + git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; + git_config* cfg; + git_buf value = GIT_BUF_INIT; + + populate_fake_ecosystem(&expected, true, false, false, true); + + cl_git_pass(git_win32__find_system_dirs(&out, "etc")); + cl_assert_equal_s(out.ptr, expected.ptr); + + git_sysdir_reset(); + + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); + cl_assert_equal_s("etc registry", value.ptr); + + git_buf_dispose(&value); + git_str_dispose(&expected); + git_str_dispose(&out); + git_config_free(cfg); +#endif +} + +void test_win32_systemdir__finds_mingw64_etc_in_registry(void) +{ +#ifdef GIT_WIN32 + git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; + git_config* cfg; + git_buf value = GIT_BUF_INIT; + + populate_fake_ecosystem(&expected, false, true, false, true); + + cl_git_pass(git_win32__find_system_dirs(&out, "etc")); + cl_assert_equal_s(out.ptr, expected.ptr); + + git_sysdir_reset(); + + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); + cl_assert_equal_s("mingw64 registry", value.ptr); + + git_buf_dispose(&value); + git_str_dispose(&expected); + git_str_dispose(&out); + git_config_free(cfg); +#endif +} + +void test_win32_systemdir__prefers_etc_to_mingw64_in_registry(void) +{ +#ifdef GIT_WIN32 + git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; + git_config* cfg; + git_buf value = GIT_BUF_INIT; + + populate_fake_ecosystem(&expected, true, true, false, true); + + cl_git_pass(git_win32__find_system_dirs(&out, "etc")); + cl_assert_equal_s(out.ptr, expected.ptr); + + git_sysdir_reset(); + + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); + cl_assert_equal_s("etc registry", value.ptr); + + git_buf_dispose(&value); + git_str_dispose(&expected); + git_str_dispose(&out); + git_config_free(cfg); +#endif +} + +void test_win32_systemdir__prefers_path_to_registry(void) +{ +#ifdef GIT_WIN32 + git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; + git_config* cfg; + git_buf value = GIT_BUF_INIT; + + populate_fake_ecosystem(&expected, true, true, true, true); + + cl_git_pass(git_win32__find_system_dirs(&out, "etc")); + cl_assert_equal_s(out.ptr, expected.ptr); + + git_sysdir_reset(); + + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); + cl_assert_equal_s("etc path", value.ptr); + + git_buf_dispose(&value); + git_str_dispose(&expected); + git_str_dispose(&out); + git_config_free(cfg); +#endif +} + +void test_win32_systemdir__no_git_installed(void) +{ +#ifdef GIT_WIN32 + git_str out = GIT_STR_INIT; + + cl_git_pass(git_win32__find_system_dirs(&out, "etc")); + cl_assert_equal_s(out.ptr, ""); +#endif +} diff --git a/tests/libgit2/worktree/bare.c b/tests/libgit2/worktree/bare.c new file mode 100644 index 000000000..7234dfffd --- /dev/null +++ b/tests/libgit2/worktree/bare.c @@ -0,0 +1,72 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" +#include "submodule/submodule_helpers.h" + +#define COMMON_REPO "testrepo.git" +#define WORKTREE_REPO "worktree" + +static git_repository *g_repo; + +void test_worktree_bare__initialize(void) +{ + g_repo = cl_git_sandbox_init(COMMON_REPO); + + cl_assert_equal_i(1, git_repository_is_bare(g_repo)); + cl_assert_equal_i(0, git_repository_is_worktree(g_repo)); +} + +void test_worktree_bare__cleanup(void) +{ + cl_fixture_cleanup(WORKTREE_REPO); + cl_git_sandbox_cleanup(); +} + +void test_worktree_bare__list(void) +{ + git_strarray wts; + + cl_git_pass(git_worktree_list(&wts, g_repo)); + cl_assert_equal_i(wts.count, 0); + + git_strarray_dispose(&wts); +} + +void test_worktree_bare__add(void) +{ + git_worktree *wt; + git_repository *wtrepo; + git_strarray wts; + + cl_git_pass(git_worktree_add(&wt, g_repo, "name", WORKTREE_REPO, NULL)); + + cl_git_pass(git_worktree_list(&wts, g_repo)); + cl_assert_equal_i(wts.count, 1); + + cl_git_pass(git_worktree_validate(wt)); + + cl_git_pass(git_repository_open(&wtrepo, WORKTREE_REPO)); + cl_assert_equal_i(0, git_repository_is_bare(wtrepo)); + cl_assert_equal_i(1, git_repository_is_worktree(wtrepo)); + + git_strarray_dispose(&wts); + git_worktree_free(wt); + git_repository_free(wtrepo); +} + +void test_worktree_bare__repository_path(void) +{ + git_worktree *wt; + git_repository *wtrepo; + + cl_git_pass(git_worktree_add(&wt, g_repo, "name", WORKTREE_REPO, NULL)); + cl_assert_equal_s(git_worktree_path(wt), cl_git_sandbox_path(0, WORKTREE_REPO, NULL)); + + cl_git_pass(git_repository_open(&wtrepo, WORKTREE_REPO)); + cl_assert_equal_s(git_repository_path(wtrepo), cl_git_sandbox_path(1, COMMON_REPO, "worktrees", "name", NULL)); + + cl_assert_equal_s(git_repository_commondir(g_repo), git_repository_commondir(wtrepo)); + cl_assert_equal_s(git_repository_workdir(wtrepo), cl_git_sandbox_path(1, WORKTREE_REPO, NULL)); + + git_repository_free(wtrepo); + git_worktree_free(wt); +} diff --git a/tests/libgit2/worktree/config.c b/tests/libgit2/worktree/config.c new file mode 100644 index 000000000..81dcfe1fa --- /dev/null +++ b/tests/libgit2/worktree/config.c @@ -0,0 +1,47 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +void test_worktree_config__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_config__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_config__open(void) +{ + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, fixture.worktree)); + cl_assert(cfg != NULL); + + git_config_free(cfg); +} + +void test_worktree_config__set(void) +{ + git_config *cfg; + int32_t val; + + cl_git_pass(git_repository_config(&cfg, fixture.worktree)); + cl_git_pass(git_config_set_int32(cfg, "core.dummy", 5)); + git_config_free(cfg); + + /* + * reopen to verify configuration has been set in the + * common dir + */ + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_config_get_int32(&val, cfg, "core.dummy")); + cl_assert_equal_i(val, 5); + git_config_free(cfg); +} diff --git a/tests/libgit2/worktree/merge.c b/tests/libgit2/worktree/merge.c new file mode 100644 index 000000000..5b7e2a837 --- /dev/null +++ b/tests/libgit2/worktree/merge.c @@ -0,0 +1,121 @@ +#include "clar_libgit2.h" + +#include "worktree_helpers.h" +#include "merge/merge_helpers.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +#define MASTER_BRANCH "refs/heads/master" +#define CONFLICT_BRANCH "refs/heads/merge-conflict" + +#define CONFLICT_BRANCH_FILE_TXT \ + "<<<<<<< HEAD\n" \ + "hi\n" \ + "bye!\n" \ + "=======\n" \ + "conflict\n" \ + ">>>>>>> merge-conflict\n" \ + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +static const char *merge_files[] = { + GIT_MERGE_HEAD_FILE, + GIT_ORIG_HEAD_FILE, + GIT_MERGE_MODE_FILE, + GIT_MERGE_MSG_FILE, +}; + +void test_worktree_merge__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_merge__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_merge__merge_head(void) +{ + git_reference *theirs_ref, *ref; + git_annotated_commit *theirs; + + cl_git_pass(git_reference_lookup(&theirs_ref, fixture.worktree, CONFLICT_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&theirs, fixture.worktree, theirs_ref)); + cl_git_pass(git_merge(fixture.worktree, (const git_annotated_commit **)&theirs, 1, NULL, NULL)); + + cl_git_pass(git_reference_lookup(&ref, fixture.worktree, GIT_MERGE_HEAD_FILE)); + + git_reference_free(ref); + git_reference_free(theirs_ref); + git_annotated_commit_free(theirs); +} + +void test_worktree_merge__merge_setup(void) +{ + git_reference *ours_ref, *theirs_ref; + git_annotated_commit *ours, *theirs; + git_str path = GIT_STR_INIT; + unsigned i; + + cl_git_pass(git_reference_lookup(&ours_ref, fixture.worktree, MASTER_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&ours, fixture.worktree, ours_ref)); + + cl_git_pass(git_reference_lookup(&theirs_ref, fixture.worktree, CONFLICT_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&theirs, fixture.worktree, theirs_ref)); + + cl_git_pass(git_merge__setup(fixture.worktree, + ours, (const git_annotated_commit **)&theirs, 1)); + + for (i = 0; i < ARRAY_SIZE(merge_files); i++) { + cl_git_pass(git_str_joinpath(&path, + fixture.worktree->gitdir, + merge_files[i])); + cl_assert(git_fs_path_exists(path.ptr)); + } + + git_str_dispose(&path); + git_reference_free(ours_ref); + git_reference_free(theirs_ref); + git_annotated_commit_free(ours); + git_annotated_commit_free(theirs); +} + +void test_worktree_merge__merge_conflict(void) +{ + git_str path = GIT_STR_INIT, buf = GIT_STR_INIT; + git_reference *theirs_ref; + git_annotated_commit *theirs; + git_index *index; + const git_index_entry *entry; + size_t i, conflicts = 0; + + cl_git_pass(git_reference_lookup(&theirs_ref, fixture.worktree, CONFLICT_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&theirs, fixture.worktree, theirs_ref)); + + cl_git_pass(git_merge(fixture.worktree, + (const git_annotated_commit **)&theirs, 1, NULL, NULL)); + + cl_git_pass(git_repository_index(&index, fixture.worktree)); + for (i = 0; i < git_index_entrycount(index); i++) { + cl_assert(entry = git_index_get_byindex(index, i)); + + if (git_index_entry_is_conflict(entry)) + conflicts++; + } + cl_assert_equal_sz(conflicts, 3); + + git_reference_free(theirs_ref); + git_annotated_commit_free(theirs); + git_index_free(index); + + cl_git_pass(git_str_joinpath(&path, fixture.worktree->workdir, "branch_file.txt")); + cl_git_pass(git_futils_readbuffer(&buf, path.ptr)); + cl_assert_equal_s(buf.ptr, CONFLICT_BRANCH_FILE_TXT); + + git_str_dispose(&path); + git_str_dispose(&buf); +} + diff --git a/tests/libgit2/worktree/open.c b/tests/libgit2/worktree/open.c new file mode 100644 index 000000000..0c3fdc173 --- /dev/null +++ b/tests/libgit2/worktree/open.c @@ -0,0 +1,126 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "worktree.h" +#include "worktree_helpers.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +static void assert_worktree_valid(git_repository *wt, const char *parentdir, const char *wtdir) +{ + cl_assert(wt->is_worktree); + + cl_assert_equal_s(wt->workdir, cl_git_sandbox_path(1, wtdir, NULL)); + cl_assert_equal_s(wt->gitlink, cl_git_sandbox_path(0, wtdir, ".git", NULL)); + cl_assert_equal_s(wt->gitdir, cl_git_sandbox_path(1, parentdir, ".git", "worktrees", wtdir, NULL)); +} + +void test_worktree_open__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_open__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_open__repository(void) +{ + assert_worktree_valid(fixture.worktree, COMMON_REPO, WORKTREE_REPO); +} + +void test_worktree_open__repository_through_workdir(void) +{ + git_repository *wt; + + cl_git_pass(git_repository_open(&wt, WORKTREE_REPO)); + assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO); + + git_repository_free(wt); +} + +void test_worktree_open__repository_through_gitlink(void) +{ + git_repository *wt; + + cl_git_pass(git_repository_open(&wt, WORKTREE_REPO "/.git")); + assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO); + + git_repository_free(wt); +} + +void test_worktree_open__repository_through_gitdir(void) +{ + git_str gitdir_path = GIT_STR_INIT; + git_repository *wt; + + cl_git_pass(git_str_joinpath(&gitdir_path, COMMON_REPO, ".git")); + cl_git_pass(git_str_joinpath(&gitdir_path, gitdir_path.ptr, "worktrees")); + cl_git_pass(git_str_joinpath(&gitdir_path, gitdir_path.ptr, "testrepo-worktree")); + + cl_git_pass(git_repository_open(&wt, gitdir_path.ptr)); + assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO); + + git_str_dispose(&gitdir_path); + git_repository_free(wt); +} + +void test_worktree_open__open_discovered_worktree(void) +{ + git_buf path = GIT_BUF_INIT; + git_repository *repo; + + cl_git_pass(git_repository_discover(&path, + git_repository_workdir(fixture.worktree), false, NULL)); + cl_git_pass(git_repository_open(&repo, path.ptr)); + cl_assert_equal_s(git_repository_workdir(fixture.worktree), + git_repository_workdir(repo)); + + git_buf_dispose(&path); + git_repository_free(repo); +} + +void test_worktree_open__repository_with_nonexistent_parent(void) +{ + git_repository *repo; + + cleanup_fixture_worktree(&fixture); + + cl_fixture_sandbox(WORKTREE_REPO); + cl_git_pass(p_chdir(WORKTREE_REPO)); + cl_git_pass(cl_rename(".gitted", ".git")); + cl_git_pass(p_chdir("..")); + + cl_git_fail(git_repository_open(&repo, WORKTREE_REPO)); + + cl_fixture_cleanup(WORKTREE_REPO); +} + +void test_worktree_open__open_from_repository(void) +{ + git_worktree *opened, *lookedup; + + cl_git_pass(git_worktree_open_from_repository(&opened, fixture.worktree)); + cl_git_pass(git_worktree_lookup(&lookedup, fixture.repo, WORKTREE_REPO)); + + cl_assert_equal_s(opened->name, lookedup->name); + cl_assert_equal_s(opened->gitdir_path, lookedup->gitdir_path); + cl_assert_equal_s(opened->gitlink_path, lookedup->gitlink_path); + cl_assert_equal_s(opened->parent_path, lookedup->parent_path); + cl_assert_equal_s(opened->commondir_path, lookedup->commondir_path); + cl_assert_equal_i(opened->locked, lookedup->locked); + + git_worktree_free(opened); + git_worktree_free(lookedup); +} + +void test_worktree_open__open_from_nonworktree_fails(void) +{ + git_worktree *wt; + + cl_git_fail(git_worktree_open_from_repository(&wt, fixture.repo)); +} diff --git a/tests/libgit2/worktree/reflog.c b/tests/libgit2/worktree/reflog.c new file mode 100644 index 000000000..a68e72dcf --- /dev/null +++ b/tests/libgit2/worktree/reflog.c @@ -0,0 +1,91 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" + +#include "reflog.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +#define REFLOG "refs/heads/testrepo-worktree" +#define REFLOG_MESSAGE "reflog message" + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +void test_worktree_reflog__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_reflog__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_reflog__read_worktree_HEAD(void) +{ + git_reflog *reflog; + const git_reflog_entry *entry; + + cl_git_pass(git_reflog_read(&reflog, fixture.worktree, "HEAD")); + cl_assert_equal_i(1, git_reflog_entrycount(reflog)); + + entry = git_reflog_entry_byindex(reflog, 0); + cl_assert(entry != NULL); + cl_assert_equal_s("checkout: moving from 099fabac3a9ea935598528c27f866e34089c2eff to testrepo-worktree", git_reflog_entry_message(entry)); + + git_reflog_free(reflog); +} + +void test_worktree_reflog__read_parent_HEAD(void) +{ + git_reflog *reflog; + + cl_git_pass(git_reflog_read(&reflog, fixture.repo, "HEAD")); + /* there is no logs/HEAD in the parent repo */ + cl_assert_equal_i(0, git_reflog_entrycount(reflog)); + + git_reflog_free(reflog); +} + +void test_worktree_reflog__read(void) +{ + git_reflog *reflog; + const git_reflog_entry *entry; + + cl_git_pass(git_reflog_read(&reflog, fixture.worktree, REFLOG)); + cl_assert_equal_i(git_reflog_entrycount(reflog), 1); + + entry = git_reflog_entry_byindex(reflog, 0); + cl_assert(entry != NULL); + cl_assert_equal_s(git_reflog_entry_message(entry), "branch: Created from HEAD"); + + git_reflog_free(reflog); +} + +void test_worktree_reflog__append_then_read(void) +{ + git_reflog *reflog, *parent_reflog; + const git_reflog_entry *entry; + git_reference *head; + git_signature *sig; + const git_oid *oid; + + cl_git_pass(git_repository_head(&head, fixture.worktree)); + cl_assert((oid = git_reference_target(head)) != NULL); + cl_git_pass(git_signature_now(&sig, "foo", "foo@bar")); + + cl_git_pass(git_reflog_read(&reflog, fixture.worktree, REFLOG)); + cl_git_pass(git_reflog_append(reflog, oid, sig, REFLOG_MESSAGE)); + git_reflog_write(reflog); + + cl_git_pass(git_reflog_read(&parent_reflog, fixture.repo, REFLOG)); + entry = git_reflog_entry_byindex(parent_reflog, 0); + cl_assert(git_oid_cmp(oid, &entry->oid_old) == 0); + cl_assert(git_oid_cmp(oid, &entry->oid_cur) == 0); + + git_reference_free(head); + git_signature_free(sig); + git_reflog_free(reflog); + git_reflog_free(parent_reflog); +} diff --git a/tests/libgit2/worktree/refs.c b/tests/libgit2/worktree/refs.c new file mode 100644 index 000000000..557726aaf --- /dev/null +++ b/tests/libgit2/worktree/refs.c @@ -0,0 +1,198 @@ +#include "clar_libgit2.h" +#include "path.h" +#include "refs.h" +#include "worktree.h" +#include "worktree_helpers.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +void test_worktree_refs__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_refs__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_refs__list(void) +{ + git_strarray refs, wtrefs; + unsigned i, j; + int error = 0; + + cl_git_pass(git_reference_list(&refs, fixture.repo)); + cl_git_pass(git_reference_list(&wtrefs, fixture.worktree)); + + if (refs.count != wtrefs.count) + { + error = GIT_ERROR; + goto exit; + } + + for (i = 0; i < refs.count; i++) + { + int found = 0; + + for (j = 0; j < wtrefs.count; j++) + { + if (!strcmp(refs.strings[i], wtrefs.strings[j])) + { + found = 1; + break; + } + } + + if (!found) + { + error = GIT_ERROR; + goto exit; + } + } + +exit: + git_strarray_dispose(&refs); + git_strarray_dispose(&wtrefs); + cl_git_pass(error); +} + +void test_worktree_refs__read_head(void) +{ + git_reference *head; + + cl_git_pass(git_repository_head(&head, fixture.worktree)); + + git_reference_free(head); +} + +void test_worktree_refs__set_head_fails_when_worktree_wants_linked_repos_HEAD(void) +{ + git_reference *head; + + cl_git_pass(git_repository_head(&head, fixture.repo)); + cl_git_fail(git_repository_set_head(fixture.worktree, git_reference_name(head))); + + git_reference_free(head); +} + +void test_worktree_refs__set_head_fails_when_main_repo_wants_worktree_head(void) +{ + git_reference *head; + + cl_git_pass(git_repository_head(&head, fixture.worktree)); + cl_git_fail(git_repository_set_head(fixture.repo, git_reference_name(head))); + + git_reference_free(head); +} + +void test_worktree_refs__set_head_works_for_current_HEAD(void) +{ + git_reference *head; + + cl_git_pass(git_repository_head(&head, fixture.repo)); + cl_git_pass(git_repository_set_head(fixture.repo, git_reference_name(head))); + + git_reference_free(head); +} + +void test_worktree_refs__set_head_fails_when_already_checked_out(void) +{ + cl_git_fail(git_repository_set_head(fixture.repo, "refs/heads/testrepo-worktree")); +} + +void test_worktree_refs__delete_fails_for_checked_out_branch(void) +{ + git_reference *branch; + + cl_git_pass(git_branch_lookup(&branch, fixture.repo, + "testrepo-worktree", GIT_BRANCH_LOCAL)); + cl_git_fail(git_branch_delete(branch)); + + git_reference_free(branch); +} + +void test_worktree_refs__delete_succeeds_after_pruning_worktree(void) +{ + git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + git_reference *branch; + git_worktree *worktree; + + opts.flags = GIT_WORKTREE_PRUNE_VALID; + + cl_git_pass(git_worktree_lookup(&worktree, fixture.repo, fixture.worktreename)); + cl_git_pass(git_worktree_prune(worktree, &opts)); + git_worktree_free(worktree); + + cl_git_pass(git_branch_lookup(&branch, fixture.repo, + "testrepo-worktree", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); +} + +void test_worktree_refs__delete_unrelated_branch_on_worktree(void) +{ + git_reference *branch; + + cl_git_pass(git_branch_lookup(&branch, fixture.worktree, + "merge-conflict", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + + git_reference_free(branch); +} + +void test_worktree_refs__delete_unrelated_branch_on_parent(void) +{ + git_reference *branch; + + cl_git_pass(git_branch_lookup(&branch, fixture.repo, + "merge-conflict", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + + git_reference_free(branch); +} + +void test_worktree_refs__renaming_reference_updates_worktree_heads(void) +{ + git_reference *head, *branch, *renamed; + + cl_git_pass(git_branch_lookup(&branch, fixture.repo, + "testrepo-worktree", GIT_BRANCH_LOCAL)); + cl_git_pass(git_reference_rename(&renamed, branch, "refs/heads/renamed", 0, NULL)); + + cl_git_pass(git_reference_lookup(&head, fixture.worktree, GIT_HEAD_FILE)); + cl_assert_equal_i(git_reference_type(head), GIT_REFERENCE_SYMBOLIC); + cl_assert_equal_s(git_reference_symbolic_target(head), "refs/heads/renamed"); + + git_reference_free(head); + git_reference_free(branch); + git_reference_free(renamed); +} + +void test_worktree_refs__creating_refs_uses_commondir(void) +{ + git_reference *head, *branch, *lookup; + git_commit *commit; + git_str refpath = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&refpath, + git_repository_commondir(fixture.worktree), "refs/heads/testbranch")); + cl_assert(!git_fs_path_exists(refpath.ptr)); + + cl_git_pass(git_repository_head(&head, fixture.worktree)); + cl_git_pass(git_commit_lookup(&commit, fixture.worktree, git_reference_target(head))); + cl_git_pass(git_branch_create(&branch, fixture.worktree, "testbranch", commit, 0)); + cl_git_pass(git_branch_lookup(&lookup, fixture.worktree, "testbranch", GIT_BRANCH_LOCAL)); + cl_assert(git_reference_cmp(branch, lookup) == 0); + cl_assert(git_fs_path_exists(refpath.ptr)); + + git_reference_free(lookup); + git_reference_free(branch); + git_reference_free(head); + git_commit_free(commit); + git_str_dispose(&refpath); +} diff --git a/tests/libgit2/worktree/repository.c b/tests/libgit2/worktree/repository.c new file mode 100644 index 000000000..c4eeadd35 --- /dev/null +++ b/tests/libgit2/worktree/repository.c @@ -0,0 +1,67 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" +#include "submodule/submodule_helpers.h" + +#include "repository.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +void test_worktree_repository__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_repository__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_repository__head(void) +{ + git_reference *ref, *head; + + cl_git_pass(git_reference_lookup(&ref, fixture.repo, "refs/heads/testrepo-worktree")); + cl_git_pass(git_repository_head_for_worktree(&head, fixture.repo, "testrepo-worktree")); + cl_assert(git_reference_cmp(ref, head) == 0); + cl_assert(git_reference_owner(ref) == fixture.repo); + + git_reference_free(ref); + git_reference_free(head); +} + +void test_worktree_repository__head_fails_for_invalid_worktree(void) +{ + git_reference *head = NULL; + + cl_git_fail(git_repository_head_for_worktree(&head, fixture.repo, "invalid")); + cl_assert(head == NULL); +} + +void test_worktree_repository__head_detached(void) +{ + git_reference *ref, *head; + + cl_git_pass(git_reference_lookup(&ref, fixture.repo, "refs/heads/testrepo-worktree")); + cl_git_pass(git_repository_set_head_detached(fixture.worktree, &ref->target.oid)); + + cl_assert(git_repository_head_detached(fixture.worktree)); + cl_assert(git_repository_head_detached_for_worktree(fixture.repo, "testrepo-worktree")); + cl_git_pass(git_repository_head_for_worktree(&head, fixture.repo, "testrepo-worktree")); + + cl_assert_equal_oid(&ref->target.oid, &head->target.oid); + + git_reference_free(ref); + git_reference_free(head); +} + +void test_worktree_repository__head_detached_fails_for_invalid_worktree(void) +{ + git_reference *head = NULL; + + cl_git_fail(git_repository_head_detached_for_worktree(fixture.repo, "invalid")); + cl_assert(head == NULL); +} diff --git a/tests/libgit2/worktree/submodule.c b/tests/libgit2/worktree/submodule.c new file mode 100644 index 000000000..6b0c07452 --- /dev/null +++ b/tests/libgit2/worktree/submodule.c @@ -0,0 +1,92 @@ +#include "clar_libgit2.h" +#include "repository.h" +#include "worktree.h" +#include "worktree_helpers.h" + +#define WORKTREE_PARENT "submodules-worktree-parent" +#define WORKTREE_CHILD "submodules-worktree-child" + +static worktree_fixture parent + = WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT); +static worktree_fixture child + = WORKTREE_FIXTURE_INIT(NULL, WORKTREE_CHILD); + +void test_worktree_submodule__initialize(void) +{ + setup_fixture_worktree(&parent); + + cl_git_pass(p_rename( + "submodules/testrepo/.gitted", + "submodules/testrepo/.git")); + + setup_fixture_worktree(&child); +} + +void test_worktree_submodule__cleanup(void) +{ + cleanup_fixture_worktree(&child); + cleanup_fixture_worktree(&parent); +} + +void test_worktree_submodule__submodule_worktree_parent(void) +{ + cl_assert(git_repository_path(parent.worktree) != NULL); + cl_assert(git_repository_workdir(parent.worktree) != NULL); + + cl_assert(!parent.repo->is_worktree); + cl_assert(parent.worktree->is_worktree); +} + +void test_worktree_submodule__submodule_worktree_child(void) +{ + cl_assert(!parent.repo->is_worktree); + cl_assert(parent.worktree->is_worktree); + cl_assert(child.worktree->is_worktree); +} + +void test_worktree_submodule__open_discovered_submodule_worktree(void) +{ + git_buf path = GIT_BUF_INIT; + git_repository *repo; + + cl_git_pass(git_repository_discover(&path, + git_repository_workdir(child.worktree), false, NULL)); + cl_git_pass(git_repository_open(&repo, path.ptr)); + cl_assert_equal_s(git_repository_workdir(child.worktree), + git_repository_workdir(repo)); + + git_buf_dispose(&path); + git_repository_free(repo); +} + +void test_worktree_submodule__resolve_relative_url(void) +{ + git_str wt_path = GIT_STR_INIT; + git_buf sm_relative_path = GIT_BUF_INIT, wt_relative_path = GIT_BUF_INIT; + git_repository *repo; + git_worktree *wt; + + cl_git_pass(git_futils_mkdir("subdir", 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_fs_path_prettify_dir(&wt_path, "subdir", NULL)); + cl_git_pass(git_str_joinpath(&wt_path, wt_path.ptr, "wt")); + + /* Open child repository, which is a submodule */ + cl_git_pass(git_repository_open(&child.repo, WORKTREE_CHILD)); + + /* Create worktree of submodule repository */ + cl_git_pass(git_worktree_add(&wt, child.repo, "subdir", wt_path.ptr, NULL)); + cl_git_pass(git_repository_open_from_worktree(&repo, wt)); + + cl_git_pass(git_submodule_resolve_url(&sm_relative_path, repo, + "../" WORKTREE_CHILD)); + cl_git_pass(git_submodule_resolve_url(&wt_relative_path, child.repo, + "../" WORKTREE_CHILD)); + + cl_assert_equal_s(sm_relative_path.ptr, wt_relative_path.ptr); + + git_worktree_free(wt); + git_repository_free(repo); + git_str_dispose(&wt_path); + git_buf_dispose(&sm_relative_path); + git_buf_dispose(&wt_relative_path); +} diff --git a/tests/libgit2/worktree/worktree.c b/tests/libgit2/worktree/worktree.c new file mode 100644 index 000000000..66273d1cb --- /dev/null +++ b/tests/libgit2/worktree/worktree.c @@ -0,0 +1,647 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" +#include "submodule/submodule_helpers.h" + +#include "checkout.h" +#include "repository.h" +#include "worktree.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +void test_worktree_worktree__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_worktree__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_worktree__list(void) +{ + git_strarray wts; + + cl_git_pass(git_worktree_list(&wts, fixture.repo)); + cl_assert_equal_i(wts.count, 1); + cl_assert_equal_s(wts.strings[0], "testrepo-worktree"); + + git_strarray_dispose(&wts); +} + +void test_worktree_worktree__list_with_invalid_worktree_dirs(void) +{ + const char *filesets[3][2] = { + { "gitdir", "commondir" }, + { "gitdir", "HEAD" }, + { "HEAD", "commondir" }, + }; + git_str path = GIT_STR_INIT; + git_strarray wts; + size_t i, j, len; + + cl_git_pass(git_str_joinpath(&path, + fixture.repo->commondir, + "worktrees/invalid")); + cl_git_pass(p_mkdir(path.ptr, 0755)); + + len = path.size; + + for (i = 0; i < ARRAY_SIZE(filesets); i++) { + + for (j = 0; j < ARRAY_SIZE(filesets[i]); j++) { + git_str_truncate(&path, len); + cl_git_pass(git_str_joinpath(&path, path.ptr, filesets[i][j])); + cl_git_pass(p_close(p_creat(path.ptr, 0644))); + } + + cl_git_pass(git_worktree_list(&wts, fixture.worktree)); + cl_assert_equal_i(wts.count, 1); + cl_assert_equal_s(wts.strings[0], "testrepo-worktree"); + git_strarray_dispose(&wts); + + for (j = 0; j < ARRAY_SIZE(filesets[i]); j++) { + git_str_truncate(&path, len); + cl_git_pass(git_str_joinpath(&path, path.ptr, filesets[i][j])); + p_unlink(path.ptr); + } + } + + git_str_dispose(&path); +} + +void test_worktree_worktree__list_in_worktree_repo(void) +{ + git_strarray wts; + + cl_git_pass(git_worktree_list(&wts, fixture.worktree)); + cl_assert_equal_i(wts.count, 1); + cl_assert_equal_s(wts.strings[0], "testrepo-worktree"); + + git_strarray_dispose(&wts); +} + +void test_worktree_worktree__list_without_worktrees(void) +{ + git_repository *repo; + git_strarray wts; + + repo = cl_git_sandbox_init("testrepo2"); + cl_git_pass(git_worktree_list(&wts, repo)); + cl_assert_equal_i(wts.count, 0); + + git_repository_free(repo); +} + +void test_worktree_worktree__lookup(void) +{ + git_worktree *wt; + git_str gitdir_path = GIT_STR_INIT; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + + cl_git_pass(git_str_joinpath(&gitdir_path, fixture.repo->commondir, "worktrees/testrepo-worktree/")); + + cl_assert_equal_s(wt->gitdir_path, gitdir_path.ptr); + cl_assert_equal_s(wt->parent_path, fixture.repo->workdir); + cl_assert_equal_s(wt->gitlink_path, fixture.worktree->gitlink); + cl_assert_equal_s(wt->commondir_path, fixture.repo->gitdir); + cl_assert_equal_s(wt->commondir_path, fixture.repo->commondir); + + git_str_dispose(&gitdir_path); + git_worktree_free(wt); +} + +void test_worktree_worktree__lookup_nonexistent_worktree(void) +{ + git_worktree *wt; + + cl_git_fail(git_worktree_lookup(&wt, fixture.repo, "nonexistent")); + cl_assert_equal_p(wt, NULL); +} + +void test_worktree_worktree__open(void) +{ + git_worktree *wt; + git_repository *repo; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + + cl_git_pass(git_repository_open_from_worktree(&repo, wt)); + cl_assert_equal_s(git_repository_workdir(repo), + git_repository_workdir(fixture.worktree)); + + git_repository_free(repo); + git_worktree_free(wt); +} + +void test_worktree_worktree__open_invalid_commondir(void) +{ + git_worktree *wt; + git_repository *repo; + git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; + + cl_git_pass(git_str_sets(&buf, "/path/to/nonexistent/commondir")); + cl_git_pass(git_str_joinpath(&path, + fixture.repo->commondir, + "worktrees/testrepo-worktree/commondir")); + cl_git_pass(git_futils_writebuffer(&buf, path.ptr, O_RDWR, 0644)); + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_fail(git_repository_open_from_worktree(&repo, wt)); + + git_str_dispose(&buf); + git_str_dispose(&path); + git_worktree_free(wt); +} + +void test_worktree_worktree__open_invalid_gitdir(void) +{ + git_worktree *wt; + git_repository *repo; + git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; + + cl_git_pass(git_str_sets(&buf, "/path/to/nonexistent/gitdir")); + cl_git_pass(git_str_joinpath(&path, + fixture.repo->commondir, + "worktrees/testrepo-worktree/gitdir")); + cl_git_pass(git_futils_writebuffer(&buf, path.ptr, O_RDWR, 0644)); + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_fail(git_repository_open_from_worktree(&repo, wt)); + + git_str_dispose(&buf); + git_str_dispose(&path); + git_worktree_free(wt); +} + +void test_worktree_worktree__open_invalid_parent(void) +{ + git_worktree *wt; + git_repository *repo; + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_sets(&buf, "/path/to/nonexistent/gitdir")); + cl_git_pass(git_futils_writebuffer(&buf, + fixture.worktree->gitlink, O_RDWR, 0644)); + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_fail(git_repository_open_from_worktree(&repo, wt)); + + git_str_dispose(&buf); + git_worktree_free(wt); +} + +void test_worktree_worktree__init(void) +{ + git_worktree *wt; + git_repository *repo; + git_reference *branch; + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-new")); + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr, NULL)); + + /* Open and verify created repo */ + cl_git_pass(git_repository_open(&repo, path.ptr)); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-new/") == 0); + cl_git_pass(git_branch_lookup(&branch, repo, "worktree-new", GIT_BRANCH_LOCAL)); + + git_str_dispose(&path); + git_worktree_free(wt); + git_reference_free(branch); + git_repository_free(repo); +} + +void test_worktree_worktree__add_locked(void) +{ + git_worktree *wt; + git_repository *repo; + git_reference *branch; + git_str path = GIT_STR_INIT; + git_worktree_add_options opts = GIT_WORKTREE_ADD_OPTIONS_INIT; + + opts.lock = 1; + + cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-locked")); + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-locked", path.ptr, &opts)); + + /* Open and verify created repo */ + cl_assert(git_worktree_is_locked(NULL, wt)); + cl_git_pass(git_repository_open(&repo, path.ptr)); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-locked/") == 0); + cl_git_pass(git_branch_lookup(&branch, repo, "worktree-locked", GIT_BRANCH_LOCAL)); + + git_str_dispose(&path); + git_worktree_free(wt); + git_reference_free(branch); + git_repository_free(repo); +} + +void test_worktree_worktree__init_existing_branch(void) +{ + git_reference *head, *branch; + git_commit *commit; + git_worktree *wt; + git_str path = GIT_STR_INIT; + + cl_git_pass(git_repository_head(&head, fixture.repo)); + cl_git_pass(git_commit_lookup(&commit, fixture.repo, &head->target.oid)); + cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-new", commit, false)); + + cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-new")); + cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr, NULL)); + + git_str_dispose(&path); + git_commit_free(commit); + git_reference_free(head); + git_reference_free(branch); +} + +void test_worktree_worktree__add_with_explicit_branch(void) +{ + git_reference *head, *branch, *wthead; + git_commit *commit; + git_worktree *wt; + git_repository *wtrepo; + git_str path = GIT_STR_INIT; + git_worktree_add_options opts = GIT_WORKTREE_ADD_OPTIONS_INIT; + + cl_git_pass(git_repository_head(&head, fixture.repo)); + cl_git_pass(git_commit_lookup(&commit, fixture.repo, &head->target.oid)); + cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-with-ref", commit, false)); + + opts.ref = branch; + + cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-with-different-name")); + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-with-different-name", path.ptr, &opts)); + cl_git_pass(git_repository_open_from_worktree(&wtrepo, wt)); + cl_git_pass(git_repository_head(&wthead, wtrepo)); + cl_assert_equal_s(git_reference_name(wthead), "refs/heads/worktree-with-ref"); + + git_str_dispose(&path); + git_commit_free(commit); + git_reference_free(head); + git_reference_free(branch); + git_reference_free(wthead); + git_repository_free(wtrepo); + git_worktree_free(wt); +} + +void test_worktree_worktree__add_no_checkout(void) +{ + git_worktree *wt; + git_repository *wtrepo; + git_index *index; + git_str path = GIT_STR_INIT; + git_worktree_add_options opts = GIT_WORKTREE_ADD_OPTIONS_INIT; + + opts.checkout_options.checkout_strategy = GIT_CHECKOUT_NONE; + + cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-no-checkout")); + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-no-checkout", path.ptr, &opts)); + + cl_git_pass(git_repository_open(&wtrepo, path.ptr)); + cl_git_pass(git_repository_index(&index, wtrepo)); + cl_assert_equal_i(git_index_entrycount(index), 0); + + git_str_dispose(&path); + git_worktree_free(wt); + git_index_free(index); + git_repository_free(wtrepo); +} + +void test_worktree_worktree__init_existing_worktree(void) +{ + git_worktree *wt; + git_str path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-new")); + cl_git_fail(git_worktree_add(&wt, fixture.repo, "testrepo-worktree", path.ptr, NULL)); + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_assert_equal_s(wt->gitlink_path, fixture.worktree->gitlink); + + git_str_dispose(&path); + git_worktree_free(wt); +} + +void test_worktree_worktree__init_existing_path(void) +{ + const char *wtfiles[] = { "HEAD", "commondir", "gitdir", "index" }; + git_worktree *wt; + git_str path = GIT_STR_INIT; + unsigned i; + + /* Delete files to verify they have not been created by + * the init call */ + for (i = 0; i < ARRAY_SIZE(wtfiles); i++) { + cl_git_pass(git_str_joinpath(&path, + fixture.worktree->gitdir, wtfiles[i])); + cl_git_pass(p_unlink(path.ptr)); + } + + cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../testrepo-worktree")); + cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr, NULL)); + + /* Verify files have not been re-created */ + for (i = 0; i < ARRAY_SIZE(wtfiles); i++) { + cl_git_pass(git_str_joinpath(&path, + fixture.worktree->gitdir, wtfiles[i])); + cl_assert(!git_fs_path_exists(path.ptr)); + } + + git_str_dispose(&path); +} + +void test_worktree_worktree__init_submodule(void) +{ + git_repository *repo, *sm, *wt; + git_worktree *worktree; + git_str path = GIT_STR_INIT; + + cleanup_fixture_worktree(&fixture); + repo = setup_fixture_submod2(); + + cl_git_pass(git_str_joinpath(&path, repo->workdir, "sm_unchanged")); + cl_git_pass(git_repository_open(&sm, path.ptr)); + cl_git_pass(git_str_joinpath(&path, repo->workdir, "../worktree/")); + cl_git_pass(git_worktree_add(&worktree, sm, "repo-worktree", path.ptr, NULL)); + cl_git_pass(git_repository_open_from_worktree(&wt, worktree)); + + cl_git_pass(git_fs_path_prettify_dir(&path, path.ptr, NULL)); + cl_assert_equal_s(path.ptr, wt->workdir); + cl_git_pass(git_fs_path_prettify_dir(&path, sm->commondir, NULL)); + cl_assert_equal_s(sm->commondir, wt->commondir); + + cl_git_pass(git_str_joinpath(&path, sm->gitdir, "worktrees/repo-worktree/")); + cl_assert_equal_s(path.ptr, wt->gitdir); + + git_str_dispose(&path); + git_worktree_free(worktree); + git_repository_free(sm); + git_repository_free(wt); +} + +void test_worktree_worktree__validate(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_pass(git_worktree_validate(wt)); + + git_worktree_free(wt); +} + +void test_worktree_worktree__name(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_assert_equal_s(git_worktree_name(wt), "testrepo-worktree"); + + git_worktree_free(wt); +} + +void test_worktree_worktree__path(void) +{ + git_worktree *wt; + git_str expected_path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&expected_path, clar_sandbox_path(), "testrepo-worktree")); + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_assert_equal_s(git_worktree_path(wt), expected_path.ptr); + + git_str_dispose(&expected_path); + git_worktree_free(wt); +} + +void test_worktree_worktree__validate_invalid_commondir(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + git__free(wt->commondir_path); + wt->commondir_path = "/path/to/invalid/commondir"; + + cl_git_fail(git_worktree_validate(wt)); + + wt->commondir_path = NULL; + git_worktree_free(wt); +} + +void test_worktree_worktree__validate_invalid_gitdir(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + git__free(wt->gitdir_path); + wt->gitdir_path = "/path/to/invalid/gitdir"; + cl_git_fail(git_worktree_validate(wt)); + + wt->gitdir_path = NULL; + git_worktree_free(wt); +} + +void test_worktree_worktree__validate_invalid_parent(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + git__free(wt->parent_path); + wt->parent_path = "/path/to/invalid/parent"; + cl_git_fail(git_worktree_validate(wt)); + + wt->parent_path = NULL; + git_worktree_free(wt); +} + +void test_worktree_worktree__lock_with_reason(void) +{ + git_worktree *wt; + git_buf reason = GIT_BUF_INIT; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + + cl_assert(!git_worktree_is_locked(NULL, wt)); + cl_git_pass(git_worktree_lock(wt, "because")); + cl_assert(git_worktree_is_locked(&reason, wt) > 0); + cl_assert_equal_s(reason.ptr, "because"); + cl_assert(wt->locked); + + git_buf_dispose(&reason); + git_worktree_free(wt); +} + +void test_worktree_worktree__lock_without_reason(void) +{ + git_worktree *wt; + git_buf reason = GIT_BUF_INIT; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + + cl_assert(!git_worktree_is_locked(NULL, wt)); + cl_git_pass(git_worktree_lock(wt, NULL)); + cl_assert(git_worktree_is_locked(&reason, wt) > 0); + cl_assert_equal_i(reason.size, 0); + cl_assert(wt->locked); + + git_buf_dispose(&reason); + git_worktree_free(wt); +} + +void test_worktree_worktree__unlock_unlocked_worktree(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_assert(!git_worktree_is_locked(NULL, wt)); + cl_assert_equal_i(1, git_worktree_unlock(wt)); + cl_assert(!wt->locked); + + git_worktree_free(wt); +} + +void test_worktree_worktree__unlock_locked_worktree(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_pass(git_worktree_lock(wt, NULL)); + cl_assert(git_worktree_is_locked(NULL, wt)); + cl_assert_equal_i(0, git_worktree_unlock(wt)); + cl_assert(!wt->locked); + + git_worktree_free(wt); +} + +void test_worktree_worktree__prune_without_opts_fails(void) +{ + git_worktree *wt; + git_repository *repo; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_fail(git_worktree_prune(wt, NULL)); + + /* Assert the repository is still valid */ + cl_git_pass(git_repository_open_from_worktree(&repo, wt)); + + git_worktree_free(wt); + git_repository_free(repo); +} + +void test_worktree_worktree__prune_valid(void) +{ + git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + git_worktree *wt; + git_repository *repo; + + opts.flags = GIT_WORKTREE_PRUNE_VALID; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_pass(git_worktree_prune(wt, &opts)); + + /* Assert the repository is not valid anymore */ + cl_git_fail(git_repository_open_from_worktree(&repo, wt)); + + git_worktree_free(wt); + git_repository_free(repo); +} + +void test_worktree_worktree__prune_locked(void) +{ + git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + git_worktree *wt; + git_repository *repo; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_pass(git_worktree_lock(wt, NULL)); + + opts.flags = GIT_WORKTREE_PRUNE_VALID; + cl_git_fail(git_worktree_prune(wt, &opts)); + /* Assert the repository is still valid */ + cl_git_pass(git_repository_open_from_worktree(&repo, wt)); + + opts.flags = GIT_WORKTREE_PRUNE_VALID|GIT_WORKTREE_PRUNE_LOCKED; + cl_git_pass(git_worktree_prune(wt, &opts)); + + git_worktree_free(wt); + git_repository_free(repo); +} + +void test_worktree_worktree__prune_gitdir_only(void) +{ + git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + git_worktree *wt; + + opts.flags = GIT_WORKTREE_PRUNE_VALID; + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_pass(git_worktree_prune(wt, &opts)); + + cl_assert(!git_fs_path_exists(wt->gitdir_path)); + cl_assert(git_fs_path_exists(wt->gitlink_path)); + + git_worktree_free(wt); +} + +void test_worktree_worktree__prune_worktree(void) +{ + git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; + git_worktree *wt; + + opts.flags = GIT_WORKTREE_PRUNE_VALID|GIT_WORKTREE_PRUNE_WORKING_TREE; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_pass(git_worktree_prune(wt, &opts)); + + cl_assert(!git_fs_path_exists(wt->gitdir_path)); + cl_assert(!git_fs_path_exists(wt->gitlink_path)); + + git_worktree_free(wt); +} + +static int foreach_worktree_cb(git_repository *worktree, void *payload) +{ + int *counter = (int *)payload; + + switch (*counter) { + case 0: + cl_assert_equal_s(git_repository_path(fixture.repo), + git_repository_path(worktree)); + cl_assert(!git_repository_is_worktree(worktree)); + break; + case 1: + cl_assert_equal_s(git_repository_path(fixture.worktree), + git_repository_path(worktree)); + cl_assert(git_repository_is_worktree(worktree)); + break; + default: + cl_fail("more worktrees found than expected"); + } + + (*counter)++; + + return 0; +} + +void test_worktree_worktree__foreach_worktree_lists_all_worktrees(void) +{ + int counter = 0; + cl_git_pass(git_repository_foreach_worktree(fixture.repo, foreach_worktree_cb, &counter)); +} + +void test_worktree_worktree__validate_invalid_worktreedir(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + p_rename("testrepo-worktree", "testrepo-worktree-tmp"); + cl_git_fail(git_worktree_validate(wt)); + p_rename("testrepo-worktree-tmp", "testrepo-worktree"); + + git_worktree_free(wt); +} diff --git a/tests/libgit2/worktree/worktree_helpers.c b/tests/libgit2/worktree/worktree_helpers.c new file mode 100644 index 000000000..6d4cdbaeb --- /dev/null +++ b/tests/libgit2/worktree/worktree_helpers.c @@ -0,0 +1,30 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" + +void cleanup_fixture_worktree(worktree_fixture *fixture) +{ + if (!fixture) + return; + + if (fixture->repo) { + git_repository_free(fixture->repo); + fixture->repo = NULL; + } + if (fixture->worktree) { + git_repository_free(fixture->worktree); + fixture->worktree = NULL; + } + + if (fixture->reponame) + cl_fixture_cleanup(fixture->reponame); + if (fixture->worktreename) + cl_fixture_cleanup(fixture->worktreename); +} + +void setup_fixture_worktree(worktree_fixture *fixture) +{ + if (fixture->reponame) + fixture->repo = cl_git_sandbox_init(fixture->reponame); + if (fixture->worktreename) + fixture->worktree = cl_git_sandbox_init(fixture->worktreename); +} diff --git a/tests/libgit2/worktree/worktree_helpers.h b/tests/libgit2/worktree/worktree_helpers.h new file mode 100644 index 000000000..35ea9ed4c --- /dev/null +++ b/tests/libgit2/worktree/worktree_helpers.h @@ -0,0 +1,11 @@ +typedef struct { + const char *reponame; + const char *worktreename; + git_repository *repo; + git_repository *worktree; +} worktree_fixture; + +#define WORKTREE_FIXTURE_INIT(repo, worktree) { (repo), (worktree), NULL, NULL } + +void cleanup_fixture_worktree(worktree_fixture *fixture); +void setup_fixture_worktree(worktree_fixture *fixture); diff --git a/tests/mailmap/basic.c b/tests/mailmap/basic.c deleted file mode 100644 index 1f8ca56c2..000000000 --- a/tests/mailmap/basic.c +++ /dev/null @@ -1,101 +0,0 @@ -#include "clar.h" -#include "clar_libgit2.h" - -#include "common.h" -#include "mailmap.h" - -static git_mailmap *mailmap = NULL; - -const char TEST_MAILMAP[] = - "Foo bar \n" - "Blatantly invalid line\n" - "Foo bar \n" - " \n" - " Other Name \n"; - -struct { - const char *real_name; - const char *real_email; - const char *replace_name; - const char *replace_email; -} expected[] = { - { "Foo bar", "foo@bar.com", NULL, "foo@baz.com" }, - { "Foo bar", "foo@bar.com", NULL, "foo@bal.com" }, - { NULL, "email@foo.com", NULL, "otheremail@foo.com" }, - { NULL, "email@foo.com", "Other Name", "yetanotheremail@foo.com" } -}; - -void test_mailmap_basic__initialize(void) -{ - cl_git_pass(git_mailmap_from_buffer( - &mailmap, TEST_MAILMAP, strlen(TEST_MAILMAP))); -} - -void test_mailmap_basic__cleanup(void) -{ - git_mailmap_free(mailmap); - mailmap = NULL; -} - -void test_mailmap_basic__entry(void) -{ - size_t idx; - const git_mailmap_entry *entry; - - /* Check that we have the expected # of entries */ - cl_assert_equal_sz(ARRAY_SIZE(expected), git_vector_length(&mailmap->entries)); - - for (idx = 0; idx < ARRAY_SIZE(expected); ++idx) { - /* Try to look up each entry and make sure they match */ - entry = git_mailmap_entry_lookup( - mailmap, expected[idx].replace_name, expected[idx].replace_email); - - cl_assert(entry); - cl_assert_equal_s(entry->real_name, expected[idx].real_name); - cl_assert_equal_s(entry->real_email, expected[idx].real_email); - cl_assert_equal_s(entry->replace_name, expected[idx].replace_name); - cl_assert_equal_s(entry->replace_email, expected[idx].replace_email); - } -} - -void test_mailmap_basic__lookup_not_found(void) -{ - const git_mailmap_entry *entry = git_mailmap_entry_lookup( - mailmap, "Whoever", "doesnotexist@fo.com"); - cl_assert(!entry); -} - -void test_mailmap_basic__lookup(void) -{ - const git_mailmap_entry *entry = git_mailmap_entry_lookup( - mailmap, "Typoed the name once", "foo@baz.com"); - cl_assert(entry); - cl_assert_equal_s(entry->real_name, "Foo bar"); -} - -void test_mailmap_basic__empty_email_query(void) -{ - const char *name; - const char *email; - cl_git_pass(git_mailmap_resolve( - &name, &email, mailmap, "Author name", "otheremail@foo.com")); - cl_assert_equal_s(name, "Author name"); - cl_assert_equal_s(email, "email@foo.com"); -} - -void test_mailmap_basic__name_matching(void) -{ - const char *name; - const char *email; - cl_git_pass(git_mailmap_resolve( - &name, &email, mailmap, "Other Name", "yetanotheremail@foo.com")); - - cl_assert_equal_s(name, "Other Name"); - cl_assert_equal_s(email, "email@foo.com"); - - cl_git_pass(git_mailmap_resolve( - &name, &email, mailmap, - "Other Name That Doesn't Match", "yetanotheremail@foo.com")); - cl_assert_equal_s(name, "Other Name That Doesn't Match"); - cl_assert_equal_s(email, "yetanotheremail@foo.com"); -} diff --git a/tests/mailmap/blame.c b/tests/mailmap/blame.c deleted file mode 100644 index e6bc1a41f..000000000 --- a/tests/mailmap/blame.c +++ /dev/null @@ -1,64 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/blame.h" -#include "mailmap.h" -#include "mailmap_testdata.h" - -static git_repository *g_repo; -static git_blame *g_blame; - -void test_mailmap_blame__initialize(void) -{ - g_repo = NULL; - g_blame = NULL; -} - -void test_mailmap_blame__cleanup(void) -{ - git_blame_free(g_blame); - cl_git_sandbox_cleanup(); -} - -void test_mailmap_blame__hunks(void) -{ - size_t idx = 0; - const git_blame_hunk *hunk = NULL; - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("mailmap"); - - opts.flags |= GIT_BLAME_USE_MAILMAP; - - cl_git_pass(git_blame_file(&g_blame, g_repo, "file.txt", &opts)); - cl_assert(g_blame); - - for (idx = 0; idx < ARRAY_SIZE(resolved); ++idx) { - hunk = git_blame_get_hunk_byline(g_blame, idx + 1); - - cl_assert(hunk->final_signature != NULL); - cl_assert(hunk->orig_signature != NULL); - cl_assert_equal_s(hunk->final_signature->name, resolved[idx].real_name); - cl_assert_equal_s(hunk->final_signature->email, resolved[idx].real_email); - } -} - -void test_mailmap_blame__hunks_no_mailmap(void) -{ - size_t idx = 0; - const git_blame_hunk *hunk = NULL; - git_blame_options opts = GIT_BLAME_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("mailmap"); - - cl_git_pass(git_blame_file(&g_blame, g_repo, "file.txt", &opts)); - cl_assert(g_blame); - - for (idx = 0; idx < ARRAY_SIZE(resolved); ++idx) { - hunk = git_blame_get_hunk_byline(g_blame, idx + 1); - - cl_assert(hunk->final_signature != NULL); - cl_assert(hunk->orig_signature != NULL); - cl_assert_equal_s(hunk->final_signature->name, resolved[idx].replace_name); - cl_assert_equal_s(hunk->final_signature->email, resolved[idx].replace_email); - } -} diff --git a/tests/mailmap/mailmap_testdata.h b/tests/mailmap/mailmap_testdata.h deleted file mode 100644 index a06606b4b..000000000 --- a/tests/mailmap/mailmap_testdata.h +++ /dev/null @@ -1,21 +0,0 @@ -#include "mailmap.h" - -typedef struct mailmap_entry { - const char *real_name; - const char *real_email; - const char *replace_name; - const char *replace_email; -} mailmap_entry; - -static const mailmap_entry resolved[] = { - { "Brad", "cto@company.xx", "Brad", "cto@coompany.xx" }, - { "Brad L", "cto@company.xx", "Brad L", "cto@coompany.xx" }, - { "Some Dude", "some@dude.xx", "nick1", "bugs@company.xx" }, - { "Other Author", "other@author.xx", "nick2", "bugs@company.xx" }, - { "nick3", "bugs@company.xx", "nick3", "bugs@company.xx" }, - { "Other Author", "other@author.xx", "Some Garbage", "nick2@company.xx" }, - { "Phil Hill", "phil@company.xx", "unknown", "phil@company.xx" }, - { "Joseph", "joseph@company.xx", "Joseph", "bugs@company.xx" }, - { "Santa Claus", "santa.claus@northpole.xx", "Clause", "me@company.xx" }, - { "Charles", "charles@charles.xx", "Charles", "charles@charles.xx" } -}; diff --git a/tests/mailmap/parsing.c b/tests/mailmap/parsing.c deleted file mode 100644 index 5ea470f34..000000000 --- a/tests/mailmap/parsing.c +++ /dev/null @@ -1,269 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" -#include "git2/sys/repository.h" -#include "mailmap_testdata.h" - -static git_repository *g_repo; -static git_mailmap *g_mailmap; -static git_config *g_config; - -static const char string_mailmap[] = - "# Simple Comment line\n" - " \n" - "Some Dude nick1 \n" - "Other Author nick2 \n" - "Other Author \n" - "Phil Hill # Comment at end of line\n" - " Joseph \n" - "Santa Claus \n" - "Untracked "; - -static const mailmap_entry entries[] = { - { NULL, "cto@company.xx", NULL, "cto@coompany.xx" }, - { "Some Dude", "some@dude.xx", "nick1", "bugs@company.xx" }, - { "Other Author", "other@author.xx", "nick2", "bugs@company.xx" }, - { "Other Author", "other@author.xx", NULL, "nick2@company.xx" }, - { "Phil Hill", NULL, NULL, "phil@company.xx" }, - { NULL, "joseph@company.xx", "Joseph", "bugs@company.xx" }, - { "Santa Claus", "santa.claus@northpole.xx", NULL, "me@company.xx" }, - /* This entry isn't in the bare repository */ - { "Untracked", NULL, NULL, "untracked@company.xx" } -}; - -void test_mailmap_parsing__initialize(void) -{ - g_repo = NULL; - g_mailmap = NULL; - g_config = NULL; -} - -void test_mailmap_parsing__cleanup(void) -{ - git_mailmap_free(g_mailmap); - git_config_free(g_config); - cl_git_sandbox_cleanup(); -} - -static void check_mailmap_entries( - const git_mailmap *mailmap, const mailmap_entry *entries, size_t entries_size) -{ - const git_mailmap_entry *parsed; - size_t idx; - - /* Check the correct # of entries were parsed */ - cl_assert_equal_sz(entries_size, git_vector_length(&mailmap->entries)); - - /* Make sure looking up each entry succeeds */ - for (idx = 0; idx < entries_size; ++idx) { - parsed = git_mailmap_entry_lookup( - mailmap, entries[idx].replace_name, entries[idx].replace_email); - - cl_assert(parsed); - cl_assert_equal_s(parsed->real_name, entries[idx].real_name); - cl_assert_equal_s(parsed->real_email, entries[idx].real_email); - cl_assert_equal_s(parsed->replace_name, entries[idx].replace_name); - cl_assert_equal_s(parsed->replace_email, entries[idx].replace_email); - } -} - -static void check_mailmap_resolve( - const git_mailmap *mailmap, const mailmap_entry *resolved, size_t resolved_size) -{ - const char *resolved_name = NULL; - const char *resolved_email = NULL; - size_t idx; - - /* Check that the resolver behaves correctly */ - for (idx = 0; idx < resolved_size; ++idx) { - cl_git_pass(git_mailmap_resolve( - &resolved_name, &resolved_email, mailmap, - resolved[idx].replace_name, resolved[idx].replace_email)); - cl_assert_equal_s(resolved_name, resolved[idx].real_name); - cl_assert_equal_s(resolved_email, resolved[idx].real_email); - } -} - -static const mailmap_entry resolved_untracked[] = { - { "Untracked", "untracked@company.xx", "xx", "untracked@company.xx" } -}; - -void test_mailmap_parsing__string(void) -{ - cl_git_pass(git_mailmap_from_buffer( - &g_mailmap, string_mailmap, strlen(string_mailmap))); - - /* We should have parsed all of the entries */ - check_mailmap_entries(g_mailmap, entries, ARRAY_SIZE(entries)); - - /* Check that resolving the entries works */ - check_mailmap_resolve(g_mailmap, resolved, ARRAY_SIZE(resolved)); - check_mailmap_resolve( - g_mailmap, resolved_untracked, ARRAY_SIZE(resolved_untracked)); -} - -void test_mailmap_parsing__windows_string(void) -{ - git_str unixbuf = GIT_STR_INIT; - git_str winbuf = GIT_STR_INIT; - - /* Parse with windows-style line endings */ - git_str_attach_notowned(&unixbuf, string_mailmap, strlen(string_mailmap)); - cl_git_pass(git_str_lf_to_crlf(&winbuf, &unixbuf)); - - cl_git_pass(git_mailmap_from_buffer(&g_mailmap, winbuf.ptr, winbuf.size)); - git_str_dispose(&winbuf); - - /* We should have parsed all of the entries */ - check_mailmap_entries(g_mailmap, entries, ARRAY_SIZE(entries)); - - /* Check that resolving the entries works */ - check_mailmap_resolve(g_mailmap, resolved, ARRAY_SIZE(resolved)); - check_mailmap_resolve( - g_mailmap, resolved_untracked, ARRAY_SIZE(resolved_untracked)); -} - -void test_mailmap_parsing__fromrepo(void) -{ - g_repo = cl_git_sandbox_init("mailmap"); - cl_check(!git_repository_is_bare(g_repo)); - - cl_git_pass(git_mailmap_from_repository(&g_mailmap, g_repo)); - - /* We should have parsed all of the entries */ - check_mailmap_entries(g_mailmap, entries, ARRAY_SIZE(entries)); - - /* Check that resolving the entries works */ - check_mailmap_resolve(g_mailmap, resolved, ARRAY_SIZE(resolved)); - check_mailmap_resolve( - g_mailmap, resolved_untracked, ARRAY_SIZE(resolved_untracked)); -} - -static const mailmap_entry resolved_bare[] = { - { "xx", "untracked@company.xx", "xx", "untracked@company.xx" } -}; - -void test_mailmap_parsing__frombare(void) -{ - g_repo = cl_git_sandbox_init("mailmap/.gitted"); - cl_git_pass(git_repository_set_bare(g_repo)); - cl_check(git_repository_is_bare(g_repo)); - - cl_git_pass(git_mailmap_from_repository(&g_mailmap, g_repo)); - - /* We should have parsed all of the entries, except for the untracked one */ - check_mailmap_entries(g_mailmap, entries, ARRAY_SIZE(entries) - 1); - - /* Check that resolving the entries works */ - check_mailmap_resolve(g_mailmap, resolved, ARRAY_SIZE(resolved)); - check_mailmap_resolve( - g_mailmap, resolved_bare, ARRAY_SIZE(resolved_bare)); -} - -static const mailmap_entry resolved_with_file_override[] = { - { "Brad", "cto@company.xx", "Brad", "cto@coompany.xx" }, - { "Brad L", "cto@company.xx", "Brad L", "cto@coompany.xx" }, - { "Some Dude", "some@dude.xx", "nick1", "bugs@company.xx" }, - { "Other Author", "other@author.xx", "nick2", "bugs@company.xx" }, - { "nick3", "bugs@company.xx", "nick3", "bugs@company.xx" }, - { "Other Author", "other@author.xx", "Some Garbage", "nick2@company.xx" }, - { "Joseph", "joseph@company.xx", "Joseph", "bugs@company.xx" }, - { "Santa Claus", "santa.claus@northpole.xx", "Clause", "me@company.xx" }, - { "Charles", "charles@charles.xx", "Charles", "charles@charles.xx" }, - - /* This name is overridden by file_override */ - { "File Override", "phil@company.xx", "unknown", "phil@company.xx" }, - { "Other Name", "fileoverridename@company.xx", "override", "fileoverridename@company.xx" } -}; - -void test_mailmap_parsing__file_config(void) -{ - g_repo = cl_git_sandbox_init("mailmap"); - cl_git_pass(git_repository_config(&g_config, g_repo)); - - cl_git_pass(git_config_set_string( - g_config, "mailmap.file", cl_fixture("mailmap/file_override"))); - - cl_git_pass(git_mailmap_from_repository(&g_mailmap, g_repo)); - - /* Check we don't have duplicate entries */ - cl_assert_equal_sz(git_vector_length(&g_mailmap->entries), 9); - - /* Check that resolving the entries works */ - check_mailmap_resolve( - g_mailmap, resolved_with_file_override, - ARRAY_SIZE(resolved_with_file_override)); -} - -static const mailmap_entry resolved_with_blob_override[] = { - { "Brad", "cto@company.xx", "Brad", "cto@coompany.xx" }, - { "Brad L", "cto@company.xx", "Brad L", "cto@coompany.xx" }, - { "Some Dude", "some@dude.xx", "nick1", "bugs@company.xx" }, - { "Other Author", "other@author.xx", "nick2", "bugs@company.xx" }, - { "nick3", "bugs@company.xx", "nick3", "bugs@company.xx" }, - { "Other Author", "other@author.xx", "Some Garbage", "nick2@company.xx" }, - { "Joseph", "joseph@company.xx", "Joseph", "bugs@company.xx" }, - { "Santa Claus", "santa.claus@northpole.xx", "Clause", "me@company.xx" }, - { "Charles", "charles@charles.xx", "Charles", "charles@charles.xx" }, - - /* This name is overridden by blob_override */ - { "Blob Override", "phil@company.xx", "unknown", "phil@company.xx" }, - { "Other Name", "bloboverridename@company.xx", "override", "bloboverridename@company.xx" } -}; - -void test_mailmap_parsing__blob_config(void) -{ - g_repo = cl_git_sandbox_init("mailmap"); - cl_git_pass(git_repository_config(&g_config, g_repo)); - - cl_git_pass(git_config_set_string( - g_config, "mailmap.blob", "HEAD:blob_override")); - - cl_git_pass(git_mailmap_from_repository(&g_mailmap, g_repo)); - - /* Check we don't have duplicate entries */ - cl_assert_equal_sz(git_vector_length(&g_mailmap->entries), 9); - - /* Check that resolving the entries works */ - check_mailmap_resolve( - g_mailmap, resolved_with_blob_override, - ARRAY_SIZE(resolved_with_blob_override)); -} - -static const mailmap_entry bare_resolved_with_blob_override[] = { - /* As mailmap.blob is set, we won't load HEAD:.mailmap */ - { "Brad", "cto@coompany.xx", "Brad", "cto@coompany.xx" }, - { "Brad L", "cto@coompany.xx", "Brad L", "cto@coompany.xx" }, - { "nick1", "bugs@company.xx", "nick1", "bugs@company.xx" }, - { "nick2", "bugs@company.xx", "nick2", "bugs@company.xx" }, - { "nick3", "bugs@company.xx", "nick3", "bugs@company.xx" }, - { "Some Garbage", "nick2@company.xx", "Some Garbage", "nick2@company.xx" }, - { "Joseph", "bugs@company.xx", "Joseph", "bugs@company.xx" }, - { "Clause", "me@company.xx", "Clause", "me@company.xx" }, - { "Charles", "charles@charles.xx", "Charles", "charles@charles.xx" }, - - /* This name is overridden by blob_override */ - { "Blob Override", "phil@company.xx", "unknown", "phil@company.xx" }, - { "Other Name", "bloboverridename@company.xx", "override", "bloboverridename@company.xx" } -}; - -void test_mailmap_parsing__bare_blob_config(void) -{ - g_repo = cl_git_sandbox_init("mailmap/.gitted"); - cl_git_pass(git_repository_set_bare(g_repo)); - cl_check(git_repository_is_bare(g_repo)); - - cl_git_pass(git_repository_config(&g_config, g_repo)); - - cl_git_pass(git_config_set_string( - g_config, "mailmap.blob", "HEAD:blob_override")); - - cl_git_pass(git_mailmap_from_repository(&g_mailmap, g_repo)); - - /* Check that we only have the 2 entries */ - cl_assert_equal_sz(git_vector_length(&g_mailmap->entries), 2); - - /* Check that resolving the entries works */ - check_mailmap_resolve( - g_mailmap, bare_resolved_with_blob_override, - ARRAY_SIZE(bare_resolved_with_blob_override)); -} diff --git a/tests/main.c b/tests/main.c deleted file mode 100644 index 56751c288..000000000 --- a/tests/main.c +++ /dev/null @@ -1,50 +0,0 @@ -#include "clar_libgit2.h" -#include "clar_libgit2_trace.h" - -#ifdef GIT_WIN32_LEAKCHECK -# include "win32/w32_leakcheck.h" -#endif - -#ifdef _WIN32 -int __cdecl main(int argc, char *argv[]) -#else -int main(int argc, char *argv[]) -#endif -{ - int res; - char *at_exit_cmd; - - clar_test_init(argc, argv); - - res = git_libgit2_init(); - if (res < 0) { - const git_error *err = git_error_last(); - const char *msg = err ? err->message : "unknown failure"; - fprintf(stderr, "failed to init libgit2: %s\n", msg); - return res; - } - - cl_global_trace_register(); - cl_sandbox_set_search_path_defaults(); - - /* Run the test suite */ - res = clar_test_run(); - - clar_test_shutdown(); - - cl_global_trace_disable(); - git_libgit2_shutdown(); - -#ifdef GIT_WIN32_LEAKCHECK - if (git_win32_leakcheck_has_leaks()) - res = res || 1; -#endif - - at_exit_cmd = getenv("CLAR_AT_EXIT"); - if (at_exit_cmd != NULL) { - int at_exit = system(at_exit_cmd); - return res || at_exit; - } - - return res; -} diff --git a/tests/merge/analysis.c b/tests/merge/analysis.c deleted file mode 100644 index 8c61303e3..000000000 --- a/tests/merge/analysis.c +++ /dev/null @@ -1,184 +0,0 @@ -/* -NOTE: this is the implementation for both merge/trees/analysis.c and merge/workdir/analysis.c -You probably want to make changes to both files. -*/ - -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "git2/annotated_commit.h" -#include "git2/sys/index.h" -#include "merge.h" -#include "merge_helpers.h" -#include "refs.h" -#include "posix.h" - -#define TEST_REPO_PATH "merge-resolve" - -#define UPTODATE_BRANCH "master" -#define PREVIOUS_BRANCH "previous" - -#define FASTFORWARD_BRANCH "ff_branch" -#define FASTFORWARD_ID "fd89f8cffb663ac89095a0f9764902e93ceaca6a" - -#define NOFASTFORWARD_BRANCH "branch" -#define NOFASTFORWARD_ID "7cb63eed597130ba4abb87b3e544b85021905520" - -static git_repository *sandbox; -static git_repository *repo; - -void test_merge_analysis__initialize_with_bare_repository(void) -{ - sandbox = cl_git_sandbox_init(TEST_REPO_PATH); - cl_git_pass(git_repository_open_ext(&repo, git_repository_path(sandbox), - GIT_REPOSITORY_OPEN_BARE, NULL)); -} - -void test_merge_analysis__initialize_with_nonbare_repository(void) -{ - sandbox = cl_git_sandbox_init(TEST_REPO_PATH); - cl_git_pass(git_repository_open_ext(&repo, git_repository_workdir(sandbox), - 0, NULL)); -} - -void test_merge_analysis__cleanup(void) -{ - git_repository_free(repo); - cl_git_sandbox_cleanup(); -} - -static void analysis_from_branch( - git_merge_analysis_t *merge_analysis, - git_merge_preference_t *merge_pref, - const char *our_branchname, - const char *their_branchname) -{ - git_str our_refname = GIT_STR_INIT; - git_str their_refname = GIT_STR_INIT; - git_reference *our_ref; - git_reference *their_ref; - git_annotated_commit *their_head; - - if (our_branchname != NULL) { - cl_git_pass(git_str_printf(&our_refname, "%s%s", GIT_REFS_HEADS_DIR, our_branchname)); - cl_git_pass(git_reference_lookup(&our_ref, repo, git_str_cstr(&our_refname))); - } else { - cl_git_pass(git_reference_lookup(&our_ref, repo, GIT_HEAD_FILE)); - } - - cl_git_pass(git_str_printf(&their_refname, "%s%s", GIT_REFS_HEADS_DIR, their_branchname)); - - cl_git_pass(git_reference_lookup(&their_ref, repo, git_str_cstr(&their_refname))); - cl_git_pass(git_annotated_commit_from_ref(&their_head, repo, their_ref)); - - cl_git_pass(git_merge_analysis_for_ref(merge_analysis, merge_pref, repo, our_ref, (const git_annotated_commit **)&their_head, 1)); - - git_str_dispose(&our_refname); - git_str_dispose(&their_refname); - git_annotated_commit_free(their_head); - git_reference_free(our_ref); - git_reference_free(their_ref); -} - -void test_merge_analysis__fastforward(void) -{ - git_merge_analysis_t merge_analysis; - git_merge_preference_t merge_pref; - - analysis_from_branch(&merge_analysis, &merge_pref, NULL, FASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL|GIT_MERGE_ANALYSIS_FASTFORWARD, merge_analysis); -} - -void test_merge_analysis__no_fastforward(void) -{ - git_merge_analysis_t merge_analysis; - git_merge_preference_t merge_pref; - - analysis_from_branch(&merge_analysis, &merge_pref, NULL, NOFASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis); -} - -void test_merge_analysis__uptodate(void) -{ - git_merge_analysis_t merge_analysis; - git_merge_preference_t merge_pref; - - analysis_from_branch(&merge_analysis, &merge_pref, NULL, UPTODATE_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); -} - -void test_merge_analysis__uptodate_merging_prev_commit(void) -{ - git_merge_analysis_t merge_analysis; - git_merge_preference_t merge_pref; - - analysis_from_branch(&merge_analysis, &merge_pref, NULL, PREVIOUS_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); -} - -void test_merge_analysis__unborn(void) -{ - git_merge_analysis_t merge_analysis; - git_merge_preference_t merge_pref; - git_str master = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&master, git_repository_path(repo), "refs/heads/master")); - cl_must_pass(p_unlink(git_str_cstr(&master))); - - analysis_from_branch(&merge_analysis, &merge_pref, NULL, NOFASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD|GIT_MERGE_ANALYSIS_UNBORN, merge_analysis); - - git_str_dispose(&master); -} - -void test_merge_analysis__fastforward_with_config_noff(void) -{ - git_config *config; - git_merge_analysis_t merge_analysis; - git_merge_preference_t merge_pref; - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_string(config, "merge.ff", "false")); - - analysis_from_branch(&merge_analysis, &merge_pref, NULL, FASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL|GIT_MERGE_ANALYSIS_FASTFORWARD, merge_analysis); - - cl_assert_equal_i(GIT_MERGE_PREFERENCE_NO_FASTFORWARD, (merge_pref & GIT_MERGE_PREFERENCE_NO_FASTFORWARD)); - - git_config_free(config); -} - -void test_merge_analysis__no_fastforward_with_config_ffonly(void) -{ - git_config *config; - git_merge_analysis_t merge_analysis; - git_merge_preference_t merge_pref; - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_string(config, "merge.ff", "only")); - - analysis_from_branch(&merge_analysis, &merge_pref, NULL, NOFASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis); - - cl_assert_equal_i(GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY, (merge_pref & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY)); - - git_config_free(config); -} - -void test_merge_analysis__between_uptodate_refs(void) -{ - git_merge_analysis_t merge_analysis; - git_merge_preference_t merge_pref; - - analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH, PREVIOUS_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); -} - -void test_merge_analysis__between_noff_refs(void) -{ - git_merge_analysis_t merge_analysis; - git_merge_preference_t merge_pref; - - analysis_from_branch(&merge_analysis, &merge_pref, "branch", FASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis); -} diff --git a/tests/merge/annotated_commit.c b/tests/merge/annotated_commit.c deleted file mode 100644 index cfdf849e5..000000000 --- a/tests/merge/annotated_commit.c +++ /dev/null @@ -1,26 +0,0 @@ -#include "clar_libgit2.h" - - -static git_repository *g_repo; - -void test_merge_annotated_commit__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_merge_annotated_commit__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_merge_annotated_commit__lookup_annotated_tag(void) -{ - git_annotated_commit *commit; - git_reference *ref; - - cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/tags/test")); - cl_git_pass(git_annotated_commit_from_ref(&commit, g_repo, ref)); - - git_annotated_commit_free(commit); - git_reference_free(ref); -} diff --git a/tests/merge/conflict_data.h b/tests/merge/conflict_data.h deleted file mode 100644 index 0b1e7ee03..000000000 --- a/tests/merge/conflict_data.h +++ /dev/null @@ -1,112 +0,0 @@ -#define AUTOMERGEABLE_MERGED_FILE \ - "this file is changed in master\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is changed in branch\n" - -#define AUTOMERGEABLE_MERGED_FILE_CRLF \ - "this file is changed in master\r\n" \ - "this file is automergeable\r\n" \ - "this file is automergeable\r\n" \ - "this file is automergeable\r\n" \ - "this file is automergeable\r\n" \ - "this file is automergeable\r\n" \ - "this file is automergeable\r\n" \ - "this file is automergeable\r\n" \ - "this file is changed in branch\r\n" - -#define CONFLICTING_MERGE_FILE \ - "<<<<<<< HEAD\n" \ - "this file is changed in master and branch\n" \ - "=======\n" \ - "this file is changed in branch and master\n" \ - ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" - -#define CONFLICTING_DIFF3_FILE \ - "<<<<<<< HEAD\n" \ - "this file is changed in master and branch\n" \ - "||||||| initial\n" \ - "this file is a conflict\n" \ - "=======\n" \ - "this file is changed in branch and master\n" \ - ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" - -#define CONFLICTING_ZDIFF3_FILE \ - "<<<<<<< HEAD\n" \ - "this file is changed in master and branch\n" \ - "||||||| initial\n" \ - "this file is a conflict\n" \ - "=======\n" \ - "this file is changed in branch and master\n" \ - ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n" - -#define CONFLICTING_UNION_FILE \ - "this file is changed in master and branch\n" \ - "this file is changed in branch and master\n" - -#define CONFLICTING_RECURSIVE_F1_TO_F2 \ - "VEAL SOUP.\n" \ - "\n" \ - "<<<<<<< HEAD\n" \ - "PUT INTO A POT THREE QUARTS OF WATER, three onions cut small, ONE\n" \ - "=======\n" \ - "PUT INTO A POT THREE QUARTS OF WATER, three onions cut not too small, one\n" \ - ">>>>>>> branchF-2\n" \ - "spoonful of black pepper pounded, and two of salt, with two or three\n" \ - "slices of lean ham; let it boil steadily two hours; skim it\n" \ - "occasionally, then put into it a shin of veal, let it boil two hours\n" \ - "longer; take out the slices of ham, and skim off the grease if any\n" \ - "should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ - "of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ - "mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ - "stir it well, and pour it into the pot, continuing to stir until it has\n" \ - "boiled two or three minutes to take off the raw taste of the eggs. If\n" \ - "the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ - "will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ - "in, first taking off their skins, by letting them stand a few minutes in\n" \ - "hot water, when they may be easily peeled. When made in this way you\n" \ - "must thicken it with the flour only. Any part of the veal may be used,\n" \ - "but the shin or knuckle is the nicest.\n" \ - "\n" \ - "<<<<<<< HEAD\n" \ - "This certainly is a mighty fine recipe.\n" \ - "=======\n" \ - "This is a mighty fine recipe!\n" \ - ">>>>>>> branchF-2\n" - -#define CONFLICTING_RECURSIVE_H2_TO_H1_WITH_DIFF3 \ - "VEAL SOUP.\n" \ - "\n" \ - "<<<<<<< HEAD\n" \ - "Put Into A Pot Three Quarts of Water, Three Onions Cut Small, One\n" \ - "||||||| merged common ancestors\n" \ - "<<<<<<<<< Temporary merge branch 1\n" \ - "PUT INTO A POT three quarts of water, three onions cut small, one\n" \ - "||||||||| merged common ancestors\n" \ - "Put into a pot three quarts of water, three onions cut small, one\n" \ - "=========\n" \ - "Put into a pot three quarts of water, THREE ONIONS CUT SMALL, one\n" \ - ">>>>>>>>> Temporary merge branch 2\n" \ - "=======\n" \ - "put into a pot three quarts of water, three onions cut small, one\n" \ - ">>>>>>> branchH-1\n" \ - "spoonful of black pepper pounded, and two of salt, with two or three\n" \ - "slices of lean ham; let it boil steadily two hours; skim it\n" \ - "occasionally, then put into it a shin of veal, let it boil two hours\n" \ - "longer; take out the slices of ham, and skim off the grease if any\n" \ - "should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ - "of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ - "mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ - "stir it well, and pour it into the pot, continuing to stir until it has\n" \ - "boiled two or three minutes to take off the raw taste of the eggs. If\n" \ - "the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ - "will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \ - "in, first taking off their skins, by letting them stand a few minutes in\n" \ - "hot water, when they may be easily peeled. When made in this way you\n" \ - "must thicken it with the flour only. Any part of the veal may be used,\n" \ - "but the shin or knuckle is the nicest.\n" diff --git a/tests/merge/driver.c b/tests/merge/driver.c deleted file mode 100644 index b7d320cbb..000000000 --- a/tests/merge/driver.c +++ /dev/null @@ -1,396 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" - -#define TEST_REPO_PATH "merge-resolve" -#define BRANCH_ID "7cb63eed597130ba4abb87b3e544b85021905520" - -#define AUTOMERGEABLE_IDSTR "f2e1550a0c9e53d5811175864a29536642ae3821" - -static git_repository *repo; -static git_index *repo_index; -static git_oid automergeable_id; - -static void test_drivers_register(void); -static void test_drivers_unregister(void); - -void test_merge_driver__initialize(void) -{ - git_config *cfg; - - repo = cl_git_sandbox_init(TEST_REPO_PATH); - git_repository_index(&repo_index, repo); - - git_oid_fromstr(&automergeable_id, AUTOMERGEABLE_IDSTR); - - /* Ensure that the user's merge.conflictstyle doesn't interfere */ - cl_git_pass(git_repository_config(&cfg, repo)); - - cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge")); - cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", false)); - - test_drivers_register(); - - git_config_free(cfg); -} - -void test_merge_driver__cleanup(void) -{ - test_drivers_unregister(); - - git_index_free(repo_index); - cl_git_sandbox_cleanup(); -} - -struct test_merge_driver { - git_merge_driver base; - int initialized; - int shutdown; -}; - -static int test_driver_init(git_merge_driver *s) -{ - struct test_merge_driver *self = (struct test_merge_driver *)s; - self->initialized = 1; - return 0; -} - -static void test_driver_shutdown(git_merge_driver *s) -{ - struct test_merge_driver *self = (struct test_merge_driver *)s; - self->shutdown = 1; -} - -static int test_driver_apply( - git_merge_driver *s, - const char **path_out, - uint32_t *mode_out, - git_buf *merged_out, - const char *filter_name, - const git_merge_driver_source *src) -{ - git_str str = GIT_STR_INIT; - int error; - - GIT_UNUSED(s); - GIT_UNUSED(src); - - *path_out = "applied.txt"; - *mode_out = GIT_FILEMODE_BLOB; - - error = git_str_printf(&str, "This is the `%s` driver.\n", - filter_name); - - merged_out->ptr = str.ptr; - merged_out->size = str.size; - merged_out->reserved = 0; - - return error; -} - -static struct test_merge_driver test_driver_custom = { - { - GIT_MERGE_DRIVER_VERSION, - test_driver_init, - test_driver_shutdown, - test_driver_apply, - }, - 0, - 0, -}; - -static struct test_merge_driver test_driver_wildcard = { - { - GIT_MERGE_DRIVER_VERSION, - test_driver_init, - test_driver_shutdown, - test_driver_apply, - }, - 0, - 0, -}; - -static void test_drivers_register(void) -{ - cl_git_pass(git_merge_driver_register("custom", &test_driver_custom.base)); - cl_git_pass(git_merge_driver_register("*", &test_driver_wildcard.base)); -} - -static void test_drivers_unregister(void) -{ - cl_git_pass(git_merge_driver_unregister("custom")); - cl_git_pass(git_merge_driver_unregister("*")); -} - -static void set_gitattributes_to(const char *driver) -{ - git_str line = GIT_STR_INIT; - - if (driver && strcmp(driver, "")) - git_str_printf(&line, "automergeable.txt merge=%s\n", driver); - else if (driver) - git_str_printf(&line, "automergeable.txt merge\n"); - else - git_str_printf(&line, "automergeable.txt -merge\n"); - - cl_assert(!git_str_oom(&line)); - - cl_git_mkfile(TEST_REPO_PATH "/.gitattributes", line.ptr); - git_str_dispose(&line); -} - -static void merge_branch(void) -{ - git_oid their_id; - git_annotated_commit *their_head; - - cl_git_pass(git_oid_fromstr(&their_id, BRANCH_ID)); - cl_git_pass(git_annotated_commit_lookup(&their_head, repo, &their_id)); - - cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, - 1, NULL, NULL)); - - git_annotated_commit_free(their_head); -} - -void test_merge_driver__custom(void) -{ - const char *expected = "This is the `custom` driver.\n"; - set_gitattributes_to("custom"); - merge_branch(); - - cl_assert_equal_file(expected, strlen(expected), - TEST_REPO_PATH "/applied.txt"); -} - -void test_merge_driver__wildcard(void) -{ - const char *expected = "This is the `foobar` driver.\n"; - set_gitattributes_to("foobar"); - merge_branch(); - - cl_assert_equal_file(expected, strlen(expected), - TEST_REPO_PATH "/applied.txt"); -} - -void test_merge_driver__shutdown_is_called(void) -{ - test_driver_custom.initialized = 0; - test_driver_custom.shutdown = 0; - test_driver_wildcard.initialized = 0; - test_driver_wildcard.shutdown = 0; - - /* run the merge with the custom driver */ - set_gitattributes_to("custom"); - merge_branch(); - - /* unregister the drivers, ensure their shutdown function is called */ - test_drivers_unregister(); - - /* since the `custom` driver was used, it should have been initialized and - * shutdown, but the wildcard driver was not used at all and should not - * have been initialized or shutdown. - */ - cl_assert(test_driver_custom.initialized); - cl_assert(test_driver_custom.shutdown); - cl_assert(!test_driver_wildcard.initialized); - cl_assert(!test_driver_wildcard.shutdown); - - test_drivers_register(); -} - -static int defer_driver_apply( - git_merge_driver *s, - const char **path_out, - uint32_t *mode_out, - git_buf *merged_out, - const char *filter_name, - const git_merge_driver_source *src) -{ - GIT_UNUSED(s); - GIT_UNUSED(path_out); - GIT_UNUSED(mode_out); - GIT_UNUSED(merged_out); - GIT_UNUSED(filter_name); - GIT_UNUSED(src); - - return GIT_PASSTHROUGH; -} - -static struct test_merge_driver test_driver_defer_apply = { - { - GIT_MERGE_DRIVER_VERSION, - test_driver_init, - test_driver_shutdown, - defer_driver_apply, - }, - 0, - 0, -}; - -void test_merge_driver__apply_can_defer(void) -{ - const git_index_entry *idx; - - cl_git_pass(git_merge_driver_register("defer", - &test_driver_defer_apply.base)); - - set_gitattributes_to("defer"); - merge_branch(); - - cl_assert((idx = git_index_get_bypath(repo_index, "automergeable.txt", 0))); - cl_assert_equal_oid(&automergeable_id, &idx->id); - - git_merge_driver_unregister("defer"); -} - -static int conflict_driver_apply( - git_merge_driver *s, - const char **path_out, - uint32_t *mode_out, - git_buf *merged_out, - const char *filter_name, - const git_merge_driver_source *src) -{ - GIT_UNUSED(s); - GIT_UNUSED(path_out); - GIT_UNUSED(mode_out); - GIT_UNUSED(merged_out); - GIT_UNUSED(filter_name); - GIT_UNUSED(src); - - return GIT_EMERGECONFLICT; -} - -static struct test_merge_driver test_driver_conflict_apply = { - { - GIT_MERGE_DRIVER_VERSION, - test_driver_init, - test_driver_shutdown, - conflict_driver_apply, - }, - 0, - 0, -}; - -void test_merge_driver__apply_can_conflict(void) -{ - const git_index_entry *ancestor, *ours, *theirs; - - cl_git_pass(git_merge_driver_register("conflict", - &test_driver_conflict_apply.base)); - - set_gitattributes_to("conflict"); - merge_branch(); - - cl_git_pass(git_index_conflict_get(&ancestor, &ours, &theirs, - repo_index, "automergeable.txt")); - - git_merge_driver_unregister("conflict"); -} - -void test_merge_driver__default_can_be_specified(void) -{ - git_oid their_id; - git_annotated_commit *their_head; - git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; - const char *expected = "This is the `custom` driver.\n"; - - merge_opts.default_driver = "custom"; - - cl_git_pass(git_oid_fromstr(&their_id, BRANCH_ID)); - cl_git_pass(git_annotated_commit_lookup(&their_head, repo, &their_id)); - - cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, - 1, &merge_opts, NULL)); - - git_annotated_commit_free(their_head); - - cl_assert_equal_file(expected, strlen(expected), - TEST_REPO_PATH "/applied.txt"); -} - -void test_merge_driver__honors_builtin_mergedefault(void) -{ - const git_index_entry *ancestor, *ours, *theirs; - - cl_repo_set_string(repo, "merge.default", "binary"); - merge_branch(); - - cl_git_pass(git_index_conflict_get(&ancestor, &ours, &theirs, - repo_index, "automergeable.txt")); -} - -void test_merge_driver__honors_custom_mergedefault(void) -{ - const char *expected = "This is the `custom` driver.\n"; - - cl_repo_set_string(repo, "merge.default", "custom"); - merge_branch(); - - cl_assert_equal_file(expected, strlen(expected), - TEST_REPO_PATH "/applied.txt"); -} - -void test_merge_driver__mergedefault_deferring_falls_back_to_text(void) -{ - const git_index_entry *idx; - - cl_git_pass(git_merge_driver_register("defer", - &test_driver_defer_apply.base)); - - cl_repo_set_string(repo, "merge.default", "defer"); - merge_branch(); - - cl_assert((idx = git_index_get_bypath(repo_index, "automergeable.txt", 0))); - cl_assert_equal_oid(&automergeable_id, &idx->id); - - git_merge_driver_unregister("defer"); -} - -void test_merge_driver__set_forces_text(void) -{ - const git_index_entry *idx; - - /* `merge` without specifying a driver indicates `text` */ - set_gitattributes_to(""); - cl_repo_set_string(repo, "merge.default", "custom"); - - merge_branch(); - - cl_assert((idx = git_index_get_bypath(repo_index, "automergeable.txt", 0))); - cl_assert_equal_oid(&automergeable_id, &idx->id); -} - -void test_merge_driver__unset_forces_binary(void) -{ - const git_index_entry *ancestor, *ours, *theirs; - - /* `-merge` without specifying a driver indicates `binary` */ - set_gitattributes_to(NULL); - cl_repo_set_string(repo, "merge.default", "custom"); - - merge_branch(); - - cl_git_pass(git_index_conflict_get(&ancestor, &ours, &theirs, - repo_index, "automergeable.txt")); -} - -void test_merge_driver__not_configured_driver_falls_back(void) -{ - const git_index_entry *idx; - - test_drivers_unregister(); - - /* `merge` without specifying a driver indicates `text` */ - set_gitattributes_to("notfound"); - - merge_branch(); - - cl_assert((idx = git_index_get_bypath(repo_index, "automergeable.txt", 0))); - cl_assert_equal_oid(&automergeable_id, &idx->id); - - test_drivers_register(); -} - diff --git a/tests/merge/files.c b/tests/merge/files.c deleted file mode 100644 index 6296f3b7b..000000000 --- a/tests/merge/files.c +++ /dev/null @@ -1,465 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" -#include "merge_helpers.h" -#include "conflict_data.h" -#include "refs.h" -#include "futils.h" -#include "diff_xdiff.h" - -#define TEST_REPO_PATH "merge-resolve" -#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" - -static git_repository *repo; -static git_index *repo_index; - -/* Fixture setup and teardown */ -void test_merge_files__initialize(void) -{ - git_config *cfg; - - repo = cl_git_sandbox_init(TEST_REPO_PATH); - git_repository_index(&repo_index, repo); - - /* Ensure that the user's merge.conflictstyle doesn't interfere */ - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge")); - git_config_free(cfg); -} - -void test_merge_files__cleanup(void) -{ - git_index_free(repo_index); - cl_git_sandbox_cleanup(); -} - -void test_merge_files__automerge_from_bufs(void) -{ - git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, - ours = GIT_MERGE_FILE_INPUT_INIT, - theirs = GIT_MERGE_FILE_INPUT_INIT; - git_merge_file_result result = {0}; - const char *expected = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n"; - - ancestor.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"; - ancestor.size = strlen(ancestor.ptr); - ancestor.path = "testfile.txt"; - ancestor.mode = 0100755; - - ours.ptr = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"; - ours.size = strlen(ours.ptr); - ours.path = "testfile.txt"; - ours.mode = 0100755; - - theirs.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n"; - theirs.size = strlen(theirs.ptr); - theirs.path = "testfile.txt"; - theirs.mode = 0100755; - - cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, 0)); - - cl_assert_equal_i(1, result.automergeable); - - cl_assert_equal_s("testfile.txt", result.path); - cl_assert_equal_i(0100755, result.mode); - - cl_assert_equal_i(strlen(expected), result.len); - cl_assert_equal_strn(expected, result.ptr, result.len); - - git_merge_file_result_free(&result); -} - -void test_merge_files__automerge_use_best_path_and_mode(void) -{ - git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, - ours = GIT_MERGE_FILE_INPUT_INIT, - theirs = GIT_MERGE_FILE_INPUT_INIT; - git_merge_file_result result = {0}; - const char *expected = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n"; - - ancestor.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"; - ancestor.size = strlen(ancestor.ptr); - ancestor.path = "testfile.txt"; - ancestor.mode = 0100755; - - ours.ptr = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"; - ours.size = strlen(ours.ptr); - ours.path = "testfile.txt"; - ours.mode = 0100644; - - theirs.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n"; - theirs.size = strlen(theirs.ptr); - theirs.path = "theirs.txt"; - theirs.mode = 0100755; - - cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, 0)); - - cl_assert_equal_i(1, result.automergeable); - - cl_assert_equal_s("theirs.txt", result.path); - cl_assert_equal_i(0100644, result.mode); - - cl_assert_equal_i(strlen(expected), result.len); - cl_assert_equal_strn(expected, result.ptr, result.len); - - git_merge_file_result_free(&result); -} - -void test_merge_files__conflict_from_bufs(void) -{ - git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, - ours = GIT_MERGE_FILE_INPUT_INIT, - theirs = GIT_MERGE_FILE_INPUT_INIT; - git_merge_file_result result = {0}; - - const char *expected = "<<<<<<< testfile.txt\nAloha!\nOurs.\n=======\nHi!\nTheirs.\n>>>>>>> theirs.txt\n"; - size_t expected_len = strlen(expected); - - ancestor.ptr = "Hello!\nAncestor!\n"; - ancestor.size = strlen(ancestor.ptr); - ancestor.path = "testfile.txt"; - ancestor.mode = 0100755; - - ours.ptr = "Aloha!\nOurs.\n"; - ours.size = strlen(ours.ptr); - ours.path = "testfile.txt"; - ours.mode = 0100644; - - theirs.ptr = "Hi!\nTheirs.\n"; - theirs.size = strlen(theirs.ptr); - theirs.path = "theirs.txt"; - theirs.mode = 0100755; - - cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, NULL)); - - cl_assert_equal_i(0, result.automergeable); - - cl_assert_equal_s("theirs.txt", result.path); - cl_assert_equal_i(0100644, result.mode); - - cl_assert_equal_i(expected_len, result.len); - cl_assert_equal_strn(expected, result.ptr, expected_len); - - git_merge_file_result_free(&result); -} - -void test_merge_files__automerge_from_index(void) -{ - git_merge_file_result result = {0}; - git_index_entry ancestor, ours, theirs; - - git_oid_fromstr(&ancestor.id, "6212c31dab5e482247d7977e4f0dd3601decf13b"); - ancestor.path = "automergeable.txt"; - ancestor.mode = 0100644; - - git_oid_fromstr(&ours.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); - ours.path = "automergeable.txt"; - ours.mode = 0100755; - - git_oid_fromstr(&theirs.id, "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe"); - theirs.path = "newname.txt"; - theirs.mode = 0100644; - - cl_git_pass(git_merge_file_from_index(&result, repo, - &ancestor, &ours, &theirs, 0)); - - cl_assert_equal_i(1, result.automergeable); - - cl_assert_equal_s("newname.txt", result.path); - cl_assert_equal_i(0100755, result.mode); - - cl_assert_equal_i(strlen(AUTOMERGEABLE_MERGED_FILE), result.len); - cl_assert_equal_strn(AUTOMERGEABLE_MERGED_FILE, result.ptr, result.len); - - git_merge_file_result_free(&result); -} - -void test_merge_files__automerge_whitespace_eol(void) -{ - git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, - ours = GIT_MERGE_FILE_INPUT_INIT, - theirs = GIT_MERGE_FILE_INPUT_INIT; - git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; - git_merge_file_result result = {0}; - const char *expected = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n"; - - ancestor.ptr = "0 \n1\n2\n3\n4\n5\n6\n7\n8\n9\n10 \n"; - ancestor.size = strlen(ancestor.ptr); - ancestor.path = "testfile.txt"; - ancestor.mode = 0100755; - - ours.ptr = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"; - ours.size = strlen(ours.ptr); - ours.path = "testfile.txt"; - ours.mode = 0100755; - - theirs.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n"; - theirs.size = strlen(theirs.ptr); - theirs.path = "testfile.txt"; - theirs.mode = 0100755; - - opts.flags |= GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL; - cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); - - cl_assert_equal_i(1, result.automergeable); - - cl_assert_equal_s("testfile.txt", result.path); - cl_assert_equal_i(0100755, result.mode); - - cl_assert_equal_i(strlen(expected), result.len); - cl_assert_equal_strn(expected, result.ptr, result.len); - - git_merge_file_result_free(&result); -} - -void test_merge_files__automerge_whitespace_change(void) -{ - git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, - ours = GIT_MERGE_FILE_INPUT_INIT, - theirs = GIT_MERGE_FILE_INPUT_INIT; - git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; - git_merge_file_result result = {0}; - const char *expected = "Zero\n1\n2\n3\n4\n5 XXX\n6 YYY\n7\n8\n9\nTen\n"; - - ancestor.ptr = "0\n1\n2\n3\n4\n5 XXX\n6YYY\n7\n8\n9\n10\n"; - ancestor.size = strlen(ancestor.ptr); - ancestor.path = "testfile.txt"; - ancestor.mode = 0100755; - - ours.ptr = "Zero\n1\n2\n3\n4\n5 XXX\n6 YYY\n7\n8\n9\n10\n"; - ours.size = strlen(ours.ptr); - ours.path = "testfile.txt"; - ours.mode = 0100755; - - theirs.ptr = "0\n1\n2\n3\n4\n5 XXX\n6 YYY\n7\n8\n9\nTen\n"; - theirs.size = strlen(theirs.ptr); - theirs.path = "testfile.txt"; - theirs.mode = 0100755; - - opts.flags |= GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE; - cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); - - cl_assert_equal_i(1, result.automergeable); - - cl_assert_equal_s("testfile.txt", result.path); - cl_assert_equal_i(0100755, result.mode); - - cl_assert_equal_i(strlen(expected), result.len); - cl_assert_equal_strn(expected, result.ptr, result.len); - - git_merge_file_result_free(&result); -} - -void test_merge_files__doesnt_add_newline(void) -{ - git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, - ours = GIT_MERGE_FILE_INPUT_INIT, - theirs = GIT_MERGE_FILE_INPUT_INIT; - git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; - git_merge_file_result result = {0}; - const char *expected = "Zero\n1\n2\n3\n4\n5 XXX\n6 YYY\n7\n8\n9\nTen"; - - ancestor.ptr = "0\n1\n2\n3\n4\n5 XXX\n6YYY\n7\n8\n9\n10"; - ancestor.size = strlen(ancestor.ptr); - ancestor.path = "testfile.txt"; - ancestor.mode = 0100755; - - ours.ptr = "Zero\n1\n2\n3\n4\n5 XXX\n6 YYY\n7\n8\n9\n10"; - ours.size = strlen(ours.ptr); - ours.path = "testfile.txt"; - ours.mode = 0100755; - - theirs.ptr = "0\n1\n2\n3\n4\n5 XXX\n6 YYY\n7\n8\n9\nTen"; - theirs.size = strlen(theirs.ptr); - theirs.path = "testfile.txt"; - theirs.mode = 0100755; - - opts.flags |= GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE; - cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); - - cl_assert_equal_i(1, result.automergeable); - - cl_assert_equal_s("testfile.txt", result.path); - cl_assert_equal_i(0100755, result.mode); - - cl_assert_equal_i(strlen(expected), result.len); - cl_assert_equal_strn(expected, result.ptr, result.len); - - git_merge_file_result_free(&result); -} - -void test_merge_files__skips_large_files(void) -{ - git_merge_file_input ours = GIT_MERGE_FILE_INPUT_INIT, - theirs = GIT_MERGE_FILE_INPUT_INIT; - git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; - git_merge_file_result result = {0}; - - ours.size = GIT_XDIFF_MAX_SIZE + 1; - ours.path = "testfile.txt"; - ours.mode = 0100755; - - theirs.size = GIT_XDIFF_MAX_SIZE + 1; - theirs.path = "testfile.txt"; - theirs.mode = 0100755; - - cl_git_pass(git_merge_file(&result, NULL, &ours, &theirs, &opts)); - - cl_assert_equal_i(0, result.automergeable); - - git_merge_file_result_free(&result); -} - -void test_merge_files__skips_binaries(void) -{ - git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, - ours = GIT_MERGE_FILE_INPUT_INIT, - theirs = GIT_MERGE_FILE_INPUT_INIT; - git_merge_file_result result = {0}; - - ancestor.ptr = "ance\0stor\0"; - ancestor.size = 10; - ancestor.path = "ancestor.txt"; - ancestor.mode = 0100755; - - ours.ptr = "foo\0bar\0"; - ours.size = 8; - ours.path = "ours.txt"; - ours.mode = 0100755; - - theirs.ptr = "bar\0foo\0"; - theirs.size = 8; - theirs.path = "theirs.txt"; - theirs.mode = 0100644; - - cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, NULL)); - - cl_assert_equal_i(0, result.automergeable); - - git_merge_file_result_free(&result); -} - -void test_merge_files__handles_binaries_when_favored(void) -{ - git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, - ours = GIT_MERGE_FILE_INPUT_INIT, - theirs = GIT_MERGE_FILE_INPUT_INIT; - git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; - git_merge_file_result result = {0}; - - ancestor.ptr = "ance\0stor\0"; - ancestor.size = 10; - ancestor.path = "ancestor.txt"; - ancestor.mode = 0100755; - - ours.ptr = "foo\0bar\0"; - ours.size = 8; - ours.path = "ours.txt"; - ours.mode = 0100755; - - theirs.ptr = "bar\0foo\0"; - theirs.size = 8; - theirs.path = "theirs.txt"; - theirs.mode = 0100644; - - opts.favor = GIT_MERGE_FILE_FAVOR_OURS; - cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); - - cl_assert_equal_i(1, result.automergeable); - - cl_assert_equal_s("ours.txt", result.path); - cl_assert_equal_i(0100755, result.mode); - - cl_assert_equal_i(ours.size, result.len); - cl_assert(memcmp(result.ptr, ours.ptr, ours.size) == 0); - - git_merge_file_result_free(&result); -} - -void test_merge_files__crlf_conflict_markers_for_crlf_files(void) -{ - git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, - ours = GIT_MERGE_FILE_INPUT_INIT, - theirs = GIT_MERGE_FILE_INPUT_INIT; - git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; - git_merge_file_result result = {0}; - - const char *expected = - "<<<<<<< file.txt\r\nThis file\r\ndoes, too.\r\n" - "=======\r\nAnd so does\r\nthis one.\r\n>>>>>>> file.txt\r\n"; - size_t expected_len = strlen(expected); - - const char *expected_diff3 = - "<<<<<<< file.txt\r\nThis file\r\ndoes, too.\r\n" - "||||||| file.txt\r\nThis file has\r\nCRLF line endings.\r\n" - "=======\r\nAnd so does\r\nthis one.\r\n>>>>>>> file.txt\r\n"; - size_t expected_diff3_len = strlen(expected_diff3); - - ancestor.ptr = "This file has\r\nCRLF line endings.\r\n"; - ancestor.size = 35; - ancestor.path = "file.txt"; - ancestor.mode = 0100644; - - ours.ptr = "This file\r\ndoes, too.\r\n"; - ours.size = 23; - ours.path = "file.txt"; - ours.mode = 0100644; - - theirs.ptr = "And so does\r\nthis one.\r\n"; - theirs.size = 24; - theirs.path = "file.txt"; - theirs.mode = 0100644; - - cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); - cl_assert_equal_i(0, result.automergeable); - cl_assert_equal_i(expected_len, result.len); - cl_assert(memcmp(expected, result.ptr, expected_len) == 0); - git_merge_file_result_free(&result); - - opts.flags |= GIT_MERGE_FILE_STYLE_DIFF3; - cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); - cl_assert_equal_i(0, result.automergeable); - cl_assert_equal_i(expected_diff3_len, result.len); - cl_assert(memcmp(expected_diff3, result.ptr, expected_len) == 0); - git_merge_file_result_free(&result); -} - -void test_merge_files__conflicts_in_zdiff3(void) -{ - git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT, - ours = GIT_MERGE_FILE_INPUT_INIT, - theirs = GIT_MERGE_FILE_INPUT_INIT; - git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; - git_merge_file_result result = {0}; - - const char *expected_zdiff3 = - "1,\nfoo,\nbar,\n" \ - "<<<<<<< file.txt\n" \ - "||||||| file.txt\n# add more here\n" \ - "=======\nquux,\nwoot,\n" \ - ">>>>>>> file.txt\nbaz,\n3,\n"; - size_t expected_zdiff3_len = strlen(expected_zdiff3); - - ancestor.ptr = "1,\n# add more here\n3,\n"; - ancestor.size = strlen(ancestor.ptr); - ancestor.path = "file.txt"; - ancestor.mode = 0100644; - - ours.ptr = "1,\nfoo,\nbar,\nbaz,\n3,\n"; - ours.size = strlen(ours.ptr); - ours.path = "file.txt"; - ours.mode = 0100644; - - theirs.ptr = "1,\nfoo,\nbar,\nquux,\nwoot,\nbaz,\n3,\n"; - theirs.size = strlen(theirs.ptr); - theirs.path = "file.txt"; - theirs.mode = 0100644; - - opts.flags |= GIT_MERGE_FILE_STYLE_ZDIFF3; - cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, &opts)); - cl_assert_equal_i(0, result.automergeable); - cl_assert_equal_i(expected_zdiff3_len, result.len); - cl_assert(memcmp(expected_zdiff3, result.ptr, expected_zdiff3_len) == 0); - git_merge_file_result_free(&result); -} diff --git a/tests/merge/merge_helpers.c b/tests/merge/merge_helpers.c deleted file mode 100644 index ce3cd229b..000000000 --- a/tests/merge/merge_helpers.c +++ /dev/null @@ -1,365 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "refs.h" -#include "tree.h" -#include "merge_helpers.h" -#include "merge.h" -#include "index.h" -#include "git2/merge.h" -#include "git2/sys/index.h" -#include "git2/annotated_commit.h" - -int merge_trees_from_branches( - git_index **index, git_repository *repo, - const char *ours_name, const char *theirs_name, - git_merge_options *opts) -{ - git_commit *our_commit, *their_commit, *ancestor_commit = NULL; - git_tree *our_tree, *their_tree, *ancestor_tree = NULL; - git_oid our_oid, their_oid, ancestor_oid; - git_str branch_buf = GIT_STR_INIT; - int error; - - git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours_name); - cl_git_pass(git_reference_name_to_id(&our_oid, repo, branch_buf.ptr)); - cl_git_pass(git_commit_lookup(&our_commit, repo, &our_oid)); - - git_str_clear(&branch_buf); - git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, theirs_name); - cl_git_pass(git_reference_name_to_id(&their_oid, repo, branch_buf.ptr)); - cl_git_pass(git_commit_lookup(&their_commit, repo, &their_oid)); - - error = git_merge_base(&ancestor_oid, repo, git_commit_id(our_commit), git_commit_id(their_commit)); - - if (error != GIT_ENOTFOUND) { - cl_git_pass(error); - - cl_git_pass(git_commit_lookup(&ancestor_commit, repo, &ancestor_oid)); - cl_git_pass(git_commit_tree(&ancestor_tree, ancestor_commit)); - } - - cl_git_pass(git_commit_tree(&our_tree, our_commit)); - cl_git_pass(git_commit_tree(&their_tree, their_commit)); - - error = git_merge_trees(index, repo, ancestor_tree, our_tree, their_tree, opts); - - git_str_dispose(&branch_buf); - git_tree_free(our_tree); - git_tree_free(their_tree); - git_tree_free(ancestor_tree); - git_commit_free(our_commit); - git_commit_free(their_commit); - git_commit_free(ancestor_commit); - - return error; -} - -int merge_commits_from_branches( - git_index **index, git_repository *repo, - const char *ours_name, const char *theirs_name, - git_merge_options *opts) -{ - git_commit *our_commit, *their_commit; - git_oid our_oid, their_oid; - git_str branch_buf = GIT_STR_INIT; - int error; - - git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours_name); - cl_git_pass(git_reference_name_to_id(&our_oid, repo, branch_buf.ptr)); - cl_git_pass(git_commit_lookup(&our_commit, repo, &our_oid)); - - git_str_clear(&branch_buf); - git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, theirs_name); - cl_git_pass(git_reference_name_to_id(&their_oid, repo, branch_buf.ptr)); - cl_git_pass(git_commit_lookup(&their_commit, repo, &their_oid)); - - error = git_merge_commits(index, repo, our_commit, their_commit, opts); - - git_str_dispose(&branch_buf); - git_commit_free(our_commit); - git_commit_free(their_commit); - - return error; -} - -int merge_branches(git_repository *repo, - const char *ours_branch, const char *theirs_branch, - git_merge_options *merge_opts, git_checkout_options *checkout_opts) -{ - git_reference *head_ref, *theirs_ref; - git_annotated_commit *theirs_head; - git_checkout_options head_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - - head_checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_reference_symbolic_create(&head_ref, repo, "HEAD", ours_branch, 1, NULL)); - cl_git_pass(git_checkout_head(repo, &head_checkout_opts)); - - cl_git_pass(git_reference_lookup(&theirs_ref, repo, theirs_branch)); - cl_git_pass(git_annotated_commit_from_ref(&theirs_head, repo, theirs_ref)); - - cl_git_pass(git_merge(repo, (const git_annotated_commit **)&theirs_head, 1, merge_opts, checkout_opts)); - - git_reference_free(head_ref); - git_reference_free(theirs_ref); - git_annotated_commit_free(theirs_head); - - return 0; -} - -void merge__dump_index_entries(git_vector *index_entries) -{ - size_t i; - const git_index_entry *index_entry; - - printf ("\nINDEX [%"PRIuZ"]:\n", index_entries->length); - for (i = 0; i < index_entries->length; i++) { - index_entry = index_entries->contents[i]; - - printf("%o ", index_entry->mode); - printf("%s ", git_oid_allocfmt(&index_entry->id)); - printf("%d ", git_index_entry_stage(index_entry)); - printf("%s ", index_entry->path); - printf("\n"); - } - printf("\n"); -} - -void merge__dump_names(git_index *index) -{ - size_t i; - const git_index_name_entry *conflict_name; - - for (i = 0; i < git_index_name_entrycount(index); i++) { - conflict_name = git_index_name_get_byindex(index, i); - - printf("%s %s %s\n", conflict_name->ancestor, conflict_name->ours, conflict_name->theirs); - } - printf("\n"); -} - -void merge__dump_reuc(git_index *index) -{ - size_t i; - const git_index_reuc_entry *reuc; - - printf ("\nREUC:\n"); - for (i = 0; i < git_index_reuc_entrycount(index); i++) { - reuc = git_index_reuc_get_byindex(index, i); - - printf("%s ", reuc->path); - printf("%o ", reuc->mode[0]); - printf("%s\n", git_oid_allocfmt(&reuc->oid[0])); - printf(" %o ", reuc->mode[1]); - printf(" %s\n", git_oid_allocfmt(&reuc->oid[1])); - printf(" %o ", reuc->mode[2]); - printf(" %s ", git_oid_allocfmt(&reuc->oid[2])); - printf("\n"); - } - printf("\n"); -} - -static int index_entry_eq_merge_index_entry(const struct merge_index_entry *expected, const git_index_entry *actual) -{ - git_oid expected_oid; - bool test_oid; - - if (strlen(expected->oid_str) != 0) { - cl_git_pass(git_oid_fromstr(&expected_oid, expected->oid_str)); - test_oid = 1; - } else - test_oid = 0; - - if (actual->mode != expected->mode || - (test_oid && git_oid_cmp(&actual->id, &expected_oid) != 0) || - git_index_entry_stage(actual) != expected->stage) - return 0; - - if (actual->mode == 0 && (actual->path != NULL || strlen(expected->path) > 0)) - return 0; - - if (actual->mode != 0 && (strcmp(actual->path, expected->path) != 0)) - return 0; - - return 1; -} - -static int name_entry_eq(const char *expected, const char *actual) -{ - if (strlen(expected) == 0) - return (actual == NULL) ? 1 : 0; - - return (strcmp(expected, actual) == 0) ? 1 : 0; -} - -static int name_entry_eq_merge_name_entry(const struct merge_name_entry *expected, const git_index_name_entry *actual) -{ - if (name_entry_eq(expected->ancestor_path, actual->ancestor) == 0 || - name_entry_eq(expected->our_path, actual->ours) == 0 || - name_entry_eq(expected->their_path, actual->theirs) == 0) - return 0; - - return 1; -} - -static int index_conflict_data_eq_merge_diff(const struct merge_index_conflict_data *expected, git_merge_diff *actual) -{ - if (!index_entry_eq_merge_index_entry(&expected->ancestor.entry, &actual->ancestor_entry) || - !index_entry_eq_merge_index_entry(&expected->ours.entry, &actual->our_entry) || - !index_entry_eq_merge_index_entry(&expected->theirs.entry, &actual->their_entry)) - return 0; - - if (expected->ours.status != actual->our_status || - expected->theirs.status != actual->their_status) - return 0; - - return 1; -} - -int merge_test_merge_conflicts(git_vector *conflicts, const struct merge_index_conflict_data expected[], size_t expected_len) -{ - git_merge_diff *actual; - size_t i; - - if (conflicts->length != expected_len) - return 0; - - for (i = 0; i < expected_len; i++) { - actual = conflicts->contents[i]; - - if (!index_conflict_data_eq_merge_diff(&expected[i], actual)) - return 0; - } - - return 1; -} - -int merge_test_index(git_index *index, const struct merge_index_entry expected[], size_t expected_len) -{ - size_t i; - const git_index_entry *index_entry; - - /* - merge__dump_index_entries(&index->entries); - */ - - if (git_index_entrycount(index) != expected_len) - return 0; - - for (i = 0; i < expected_len; i++) { - if ((index_entry = git_index_get_byindex(index, i)) == NULL) - return 0; - - if (!index_entry_eq_merge_index_entry(&expected[i], index_entry)) - return 0; - } - - return 1; -} - -int merge_test_names(git_index *index, const struct merge_name_entry expected[], size_t expected_len) -{ - size_t i; - const git_index_name_entry *name_entry; - - /* - dump_names(index); - */ - - if (git_index_name_entrycount(index) != expected_len) - return 0; - - for (i = 0; i < expected_len; i++) { - if ((name_entry = git_index_name_get_byindex(index, i)) == NULL) - return 0; - - if (! name_entry_eq_merge_name_entry(&expected[i], name_entry)) - return 0; - } - - return 1; -} - -int merge_test_reuc(git_index *index, const struct merge_reuc_entry expected[], size_t expected_len) -{ - size_t i; - const git_index_reuc_entry *reuc_entry; - git_oid expected_oid; - - /* - dump_reuc(index); - */ - - if (git_index_reuc_entrycount(index) != expected_len) - return 0; - - for (i = 0; i < expected_len; i++) { - if ((reuc_entry = git_index_reuc_get_byindex(index, i)) == NULL) - return 0; - - if (strcmp(reuc_entry->path, expected[i].path) != 0 || - reuc_entry->mode[0] != expected[i].ancestor_mode || - reuc_entry->mode[1] != expected[i].our_mode || - reuc_entry->mode[2] != expected[i].their_mode) - return 0; - - if (expected[i].ancestor_mode > 0) { - cl_git_pass(git_oid_fromstr(&expected_oid, expected[i].ancestor_oid_str)); - - if (git_oid_cmp(&reuc_entry->oid[0], &expected_oid) != 0) - return 0; - } - - if (expected[i].our_mode > 0) { - cl_git_pass(git_oid_fromstr(&expected_oid, expected[i].our_oid_str)); - - if (git_oid_cmp(&reuc_entry->oid[1], &expected_oid) != 0) - return 0; - } - - if (expected[i].their_mode > 0) { - cl_git_pass(git_oid_fromstr(&expected_oid, expected[i].their_oid_str)); - - if (git_oid_cmp(&reuc_entry->oid[2], &expected_oid) != 0) - return 0; - } - } - - return 1; -} - -static int dircount(void *payload, git_str *pathbuf) -{ - size_t *entries = payload; - size_t len = git_str_len(pathbuf); - - if (len < 5 || strcmp(pathbuf->ptr + (git_str_len(pathbuf) - 5), "/.git") != 0) - (*entries)++; - - return 0; -} - -int merge_test_workdir(git_repository *repo, const struct merge_index_entry expected[], size_t expected_len) -{ - size_t actual_len = 0, i; - git_oid actual_oid, expected_oid; - git_str wd = GIT_STR_INIT; - - git_str_puts(&wd, repo->workdir); - git_fs_path_direach(&wd, 0, dircount, &actual_len); - - if (actual_len != expected_len) - return 0; - - for (i = 0; i < expected_len; i++) { - git_blob_create_from_workdir(&actual_oid, repo, expected[i].path); - git_oid_fromstr(&expected_oid, expected[i].oid_str); - - if (git_oid_cmp(&actual_oid, &expected_oid) != 0) - return 0; - } - - git_str_dispose(&wd); - - return 1; -} diff --git a/tests/merge/merge_helpers.h b/tests/merge/merge_helpers.h deleted file mode 100644 index 339812ba5..000000000 --- a/tests/merge/merge_helpers.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef INCLUDE_cl_merge_helpers_h__ -#define INCLUDE_cl_merge_helpers_h__ - -#include "merge.h" -#include "git2/merge.h" - -struct merge_index_entry { - uint16_t mode; - char oid_str[GIT_OID_HEXSZ+1]; - int stage; - char path[128]; -}; - -struct merge_name_entry { - char ancestor_path[128]; - char our_path[128]; - char their_path[128]; -}; - -struct merge_index_with_status { - struct merge_index_entry entry; - unsigned int status; -}; - -struct merge_reuc_entry { - char path[128]; - unsigned int ancestor_mode; - unsigned int our_mode; - unsigned int their_mode; - char ancestor_oid_str[GIT_OID_HEXSZ+1]; - char our_oid_str[GIT_OID_HEXSZ+1]; - char their_oid_str[GIT_OID_HEXSZ+1]; -}; - -struct merge_index_conflict_data { - struct merge_index_with_status ancestor; - struct merge_index_with_status ours; - struct merge_index_with_status theirs; - git_merge_diff_t change_type; -}; - -int merge_trees_from_branches( - git_index **index, git_repository *repo, - const char *ours_name, const char *theirs_name, - git_merge_options *opts); - -int merge_commits_from_branches( - git_index **index, git_repository *repo, - const char *ours_name, const char *theirs_name, - git_merge_options *opts); - -int merge_branches(git_repository *repo, - const char *ours_branch, const char *theirs_branch, - git_merge_options *merge_opts, git_checkout_options *checkout_opts); - -int merge_test_diff_list(git_merge_diff_list *diff_list, const struct merge_index_entry expected[], size_t expected_len); - -int merge_test_merge_conflicts(git_vector *conflicts, const struct merge_index_conflict_data expected[], size_t expected_len); - -int merge_test_index(git_index *index, const struct merge_index_entry expected[], size_t expected_len); - -int merge_test_names(git_index *index, const struct merge_name_entry expected[], size_t expected_len); - -int merge_test_reuc(git_index *index, const struct merge_reuc_entry expected[], size_t expected_len); - -int merge_test_workdir(git_repository *repo, const struct merge_index_entry expected[], size_t expected_len); - -void merge__dump_names(git_index *index); -void merge__dump_index_entries(git_vector *index_entries); -void merge__dump_reuc(git_index *index); - -#endif diff --git a/tests/merge/trees/automerge.c b/tests/merge/trees/automerge.c deleted file mode 100644 index 3bf6c5208..000000000 --- a/tests/merge/trees/automerge.c +++ /dev/null @@ -1,195 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" -#include "futils.h" -#include "../merge_helpers.h" -#include "../conflict_data.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-resolve" -#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" - -#define THEIRS_AUTOMERGE_BRANCH "branch" - -#define THEIRS_UNRELATED_BRANCH "unrelated" -#define THEIRS_UNRELATED_OID "55b4e4687e7a0d9ca367016ed930f385d4022e6f" -#define THEIRS_UNRELATED_PARENT "d6cf6c7741b3316826af1314042550c97ded1d50" - -#define OURS_DIRECTORY_FILE "df_side1" -#define THEIRS_DIRECTORY_FILE "df_side2" - -/* Non-conflicting files, index entries are common to every merge operation */ -#define ADDED_IN_MASTER_INDEX_ENTRY \ - { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" } -#define AUTOMERGEABLE_INDEX_ENTRY \ - { 0100644, "f2e1550a0c9e53d5811175864a29536642ae3821", 0, "automergeable.txt" } -#define CHANGED_IN_BRANCH_INDEX_ENTRY \ - { 0100644, "4eb04c9e79e88f6640d01ff5b25ca2a60764f216", 0, "changed-in-branch.txt" } -#define CHANGED_IN_MASTER_INDEX_ENTRY \ - { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" } -#define UNCHANGED_INDEX_ENTRY \ - { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" } - -/* Expected REUC entries */ -#define AUTOMERGEABLE_REUC_ENTRY \ - { "automergeable.txt", 0100644, 0100644, 0100644, \ - "6212c31dab5e482247d7977e4f0dd3601decf13b", \ - "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", \ - "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe" } -#define CONFLICTING_REUC_ENTRY \ - { "conflicting.txt", 0100644, 0100644, 0100644, \ - "d427e0b2e138501a3d15cc376077a3631e15bd46", \ - "4e886e602529caa9ab11d71f86634bd1b6e0de10", \ - "2bd0a343aeef7a2cf0d158478966a6e587ff3863" } -#define REMOVED_IN_BRANCH_REUC_ENTRY \ - { "removed-in-branch.txt", 0100644, 0100644, 0, \ - "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ - "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ - "" } -#define REMOVED_IN_MASTER_REUC_ENTRY \ - { "removed-in-master.txt", 0100644, 0, 0100644, \ - "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", \ - "", \ - "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5" } - -/* Fixture setup and teardown */ -void test_merge_trees_automerge__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_merge_trees_automerge__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_merge_trees_automerge__automerge(void) -{ - git_index *index; - const git_index_entry *entry; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - git_blob *blob; - - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - - { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, - - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY - }; - - cl_git_pass(merge_trees_from_branches(&index, repo, "master", THEIRS_AUTOMERGE_BRANCH, &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 8)); - cl_assert(merge_test_reuc(index, merge_reuc_entries, 3)); - - cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL); - cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE)); - - cl_git_pass(git_object_lookup((git_object **)&blob, repo, &entry->id, GIT_OBJECT_BLOB)); - cl_assert(memcmp(git_blob_rawcontent(blob), AUTOMERGEABLE_MERGED_FILE, (size_t)entry->file_size) == 0); - - git_index_free(index); - git_blob_free(blob); -} - -void test_merge_trees_automerge__favor_ours(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - CONFLICTING_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY, - }; - - opts.file_favor = GIT_MERGE_FILE_FAVOR_OURS; - - cl_git_pass(merge_trees_from_branches(&index, repo, "master", THEIRS_AUTOMERGE_BRANCH, &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 6)); - cl_assert(merge_test_reuc(index, merge_reuc_entries, 4)); - - git_index_free(index); -} - -void test_merge_trees_automerge__favor_theirs(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 0, "conflicting.txt" }, - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - CONFLICTING_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY, - }; - - opts.file_favor = GIT_MERGE_FILE_FAVOR_THEIRS; - - cl_git_pass(merge_trees_from_branches(&index, repo, "master", THEIRS_AUTOMERGE_BRANCH, &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 6)); - cl_assert(merge_test_reuc(index, merge_reuc_entries, 4)); - - git_index_free(index); -} - -void test_merge_trees_automerge__unrelated(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, - { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 2, "automergeable.txt" }, - { 0100644, "d07ec190c306ec690bac349e87d01c4358e49bb2", 3, "automergeable.txt" }, - { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, - { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "4b253da36a0ae8bfce63aeabd8c5b58429925594", 3, "conflicting.txt" }, - { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" }, - { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" }, - { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, - { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, - }; - - cl_git_pass(merge_trees_from_branches(&index, repo, "master", THEIRS_UNRELATED_BRANCH, &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 11)); - - git_index_free(index); -} diff --git a/tests/merge/trees/commits.c b/tests/merge/trees/commits.c deleted file mode 100644 index 79fba8ac5..000000000 --- a/tests/merge/trees/commits.c +++ /dev/null @@ -1,148 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" -#include "../merge_helpers.h" -#include "../conflict_data.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-resolve" - -void test_merge_trees_commits__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_merge_trees_commits__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_merge_trees_commits__automerge(void) -{ - git_index *index; - const git_index_entry *entry; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - git_blob *blob; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, - { 0100644, "f2e1550a0c9e53d5811175864a29536642ae3821", 0, "automergeable.txt" }, - { 0100644, "4eb04c9e79e88f6640d01ff5b25ca2a60764f216", 0, "changed-in-branch.txt" }, - { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, - - { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, - - { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - { "automergeable.txt", 0100644, 0100644, 0100644, \ - "6212c31dab5e482247d7977e4f0dd3601decf13b", \ - "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", \ - "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe" }, - { "removed-in-branch.txt", 0100644, 0100644, 0, \ - "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ - "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ - "" }, - { "removed-in-master.txt", 0100644, 0, 0100644, \ - "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", \ - "", \ - "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5" }, - }; - - cl_git_pass(merge_commits_from_branches(&index, repo, "master", "branch", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 8)); - cl_assert(merge_test_reuc(index, merge_reuc_entries, 3)); - - cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL); - cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE)); - - cl_git_pass(git_object_lookup((git_object **)&blob, repo, &entry->id, GIT_OBJECT_BLOB)); - cl_assert(memcmp(git_blob_rawcontent(blob), AUTOMERGEABLE_MERGED_FILE, (size_t)entry->file_size) == 0); - - git_index_free(index); - git_blob_free(blob); -} - -void test_merge_trees_commits__no_ancestor(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, - { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 2, "automergeable.txt" }, - { 0100644, "d07ec190c306ec690bac349e87d01c4358e49bb2", 3, "automergeable.txt" }, - { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, - { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "4b253da36a0ae8bfce63aeabd8c5b58429925594", 3, "conflicting.txt" }, - { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" }, - { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" }, - { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, - { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, - }; - - cl_git_pass(merge_commits_from_branches(&index, repo, "master", "unrelated", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 11)); - - git_index_free(index); -} - -void test_merge_trees_commits__df_conflict(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "49130a28ef567af9a6a6104c38773fedfa5f9742", 2, "dir-10" }, - { 0100644, "6c06dcd163587c2cc18be44857e0b71116382aeb", 3, "dir-10" }, - { 0100644, "43aafd43bea779ec74317dc361f45ae3f532a505", 0, "dir-6" }, - { 0100644, "a031a28ae70e33a641ce4b8a8f6317f1ab79dee4", 3, "dir-7" }, - { 0100644, "5012fd565b1393bdfda1805d4ec38ce6619e1fd1", 1, "dir-7/file.txt" }, - { 0100644, "a5563304ddf6caba25cb50323a2ea6f7dbfcadca", 2, "dir-7/file.txt" }, - { 0100644, "e9ad6ec3e38364a3d07feda7c4197d4d845c53b5", 0, "dir-8" }, - { 0100644, "3ef4d30382ca33fdeba9fda895a99e0891ba37aa", 2, "dir-9" }, - { 0100644, "fc4c636d6515e9e261f9260dbcf3cc6eca97ea08", 1, "dir-9/file.txt" }, - { 0100644, "76ab0e2868197ec158ddd6c78d8a0d2fd73d38f9", 3, "dir-9/file.txt" }, - { 0100644, "5c2411f8075f48a6b2fdb85ebc0d371747c4df15", 0, "file-1/new" }, - { 0100644, "a39a620dae5bc8b4e771cd4d251b7d080401a21e", 1, "file-2" }, - { 0100644, "d963979c237d08b6ba39062ee7bf64c7d34a27f8", 2, "file-2" }, - { 0100644, "5c341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d", 0, "file-2/new" }, - { 0100644, "9efe7723802d4305142eee177e018fee1572c4f4", 0, "file-3/new" }, - { 0100644, "bacac9b3493509aa15e1730e1545fc0919d1dae0", 1, "file-4" }, - { 0100644, "7663fce0130db092936b137cabd693ec234eb060", 3, "file-4" }, - { 0100644, "e49f917b448d1340b31d76e54ba388268fd4c922", 0, "file-4/new" }, - { 0100644, "cab2cf23998b40f1af2d9d9a756dc9e285a8df4b", 2, "file-5/new" }, - { 0100644, "f5504f36e6f4eb797a56fc5bac6c6c7f32969bf2", 3, "file-5/new" }, - }; - - cl_git_pass(merge_trees_from_branches(&index, repo, "df_side1", "df_side2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 20)); - - git_index_free(index); -} - -void test_merge_trees_commits__fail_on_conflict(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - opts.flags |= GIT_MERGE_FAIL_ON_CONFLICT; - - cl_git_fail_with(GIT_EMERGECONFLICT, - merge_trees_from_branches(&index, repo, "df_side1", "df_side2", &opts)); - - cl_git_fail_with(GIT_EMERGECONFLICT, - merge_commits_from_branches(&index, repo, "master", "unrelated", &opts)); - cl_git_fail_with(GIT_EMERGECONFLICT, - merge_commits_from_branches(&index, repo, "master", "branch", &opts)); -} - diff --git a/tests/merge/trees/modeconflict.c b/tests/merge/trees/modeconflict.c deleted file mode 100644 index a0521c4e8..000000000 --- a/tests/merge/trees/modeconflict.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" -#include "../merge_helpers.h" -#include "futils.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-resolve" - -#define DF_SIDE1_BRANCH "df_side1" -#define DF_SIDE2_BRANCH "df_side2" - -/* Fixture setup and teardown */ -void test_merge_trees_modeconflict__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_merge_trees_modeconflict__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_merge_trees_modeconflict__df_conflict(void) -{ - git_index *index; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "49130a28ef567af9a6a6104c38773fedfa5f9742", 2, "dir-10" }, - { 0100644, "6c06dcd163587c2cc18be44857e0b71116382aeb", 3, "dir-10" }, - { 0100644, "43aafd43bea779ec74317dc361f45ae3f532a505", 0, "dir-6" }, - { 0100644, "a031a28ae70e33a641ce4b8a8f6317f1ab79dee4", 3, "dir-7" }, - { 0100644, "5012fd565b1393bdfda1805d4ec38ce6619e1fd1", 1, "dir-7/file.txt" }, - { 0100644, "a5563304ddf6caba25cb50323a2ea6f7dbfcadca", 2, "dir-7/file.txt" }, - { 0100644, "e9ad6ec3e38364a3d07feda7c4197d4d845c53b5", 0, "dir-8" }, - { 0100644, "3ef4d30382ca33fdeba9fda895a99e0891ba37aa", 2, "dir-9" }, - { 0100644, "fc4c636d6515e9e261f9260dbcf3cc6eca97ea08", 1, "dir-9/file.txt" }, - { 0100644, "76ab0e2868197ec158ddd6c78d8a0d2fd73d38f9", 3, "dir-9/file.txt" }, - { 0100644, "5c2411f8075f48a6b2fdb85ebc0d371747c4df15", 0, "file-1/new" }, - { 0100644, "a39a620dae5bc8b4e771cd4d251b7d080401a21e", 1, "file-2" }, - { 0100644, "d963979c237d08b6ba39062ee7bf64c7d34a27f8", 2, "file-2" }, - { 0100644, "5c341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d", 0, "file-2/new" }, - { 0100644, "9efe7723802d4305142eee177e018fee1572c4f4", 0, "file-3/new" }, - { 0100644, "bacac9b3493509aa15e1730e1545fc0919d1dae0", 1, "file-4" }, - { 0100644, "7663fce0130db092936b137cabd693ec234eb060", 3, "file-4" }, - { 0100644, "e49f917b448d1340b31d76e54ba388268fd4c922", 0, "file-4/new" }, - { 0100644, "cab2cf23998b40f1af2d9d9a756dc9e285a8df4b", 2, "file-5/new" }, - { 0100644, "f5504f36e6f4eb797a56fc5bac6c6c7f32969bf2", 3, "file-5/new" }, - }; - - cl_git_pass(merge_trees_from_branches(&index, repo, DF_SIDE1_BRANCH, DF_SIDE2_BRANCH, NULL)); - - cl_assert(merge_test_index(index, merge_index_entries, 20)); - - git_index_free(index); -} diff --git a/tests/merge/trees/recursive.c b/tests/merge/trees/recursive.c deleted file mode 100644 index 71f5af150..000000000 --- a/tests/merge/trees/recursive.c +++ /dev/null @@ -1,458 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" -#include "../merge_helpers.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - -void test_merge_trees_recursive__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_merge_trees_recursive__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_merge_trees_recursive__one_base_commit(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "dea7215f259b2cced87d1bda6c72f8b4ce37a2ff", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchA-1", "branchA-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 6)); - - git_index_free(index); -} - -void test_merge_trees_recursive__one_base_commit_norecursive(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "dea7215f259b2cced87d1bda6c72f8b4ce37a2ff", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, - }; - - opts.flags |= GIT_MERGE_NO_RECURSIVE; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchA-1", "branchA-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 6)); - - git_index_free(index); -} - -void test_merge_trees_recursive__two_base_commits(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "666ffdfcf1eaa5641fa31064bf2607327e843c09", 0, "veal.txt" }, - }; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchB-1", "branchB-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 6)); - - git_index_free(index); -} - -void test_merge_trees_recursive__two_base_commits_norecursive(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "cb49ad76147f5f9439cbd6133708b76142660660", 1, "veal.txt" }, - { 0100644, "b2a81ead9e722af0099fccfb478cea88eea749a2", 2, "veal.txt" }, - { 0100644, "4e21d2d63357bde5027d1625f5ec6b430cdeb143", 3, "veal.txt" }, - }; - - opts.flags |= GIT_MERGE_NO_RECURSIVE; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchB-1", "branchB-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 8)); - - git_index_free(index); -} - -void test_merge_trees_recursive__two_levels_of_multiple_bases(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "15faa0c9991f2d65686e844651faa2ff9827887b", 0, "veal.txt" }, - }; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchC-1", "branchC-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 6)); - - git_index_free(index); -} - -void test_merge_trees_recursive__two_levels_of_multiple_bases_norecursive(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "b2a81ead9e722af0099fccfb478cea88eea749a2", 1, "veal.txt" }, - { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 2, "veal.txt" }, - { 0100644, "68a2e1ee61a23a4728fe6b35580fbbbf729df370", 3, "veal.txt" }, - }; - - opts.flags |= GIT_MERGE_NO_RECURSIVE; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchC-1", "branchC-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 8)); - - git_index_free(index); -} - -void test_merge_trees_recursive__three_levels_of_multiple_bases(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "d55e5dc038c52f1a36548625bcb666cbc06db9e6", 0, "veal.txt" }, - }; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchD-2", "branchD-1", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 6)); - - git_index_free(index); -} - -void test_merge_trees_recursive__three_levels_of_multiple_bases_norecursive(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 1, "veal.txt" }, - { 0100644, "f1b44c04989a3a1c14b036cfadfa328d53a7bc5e", 2, "veal.txt" }, - { 0100644, "5e8747f5200fac0f945a07daf6163ca9cb1a8da9", 3, "veal.txt" }, - }; - - opts.flags |= GIT_MERGE_NO_RECURSIVE; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchD-2", "branchD-1", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 8)); - - git_index_free(index); -} - -void test_merge_trees_recursive__three_base_commits(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4f7269b07c76d02755d75ccaf05c0b4c36cdc6c", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchE-1", "branchE-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 6)); - - git_index_free(index); -} - -void test_merge_trees_recursive__three_base_commits_norecursive(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "9e12bce04446d097ae1782967a5888c2e2a0d35b", 1, "gravy.txt" }, - { 0100644, "d8dd349b78f19a4ebe3357bacb8138f00bf5ed41", 2, "gravy.txt" }, - { 0100644, "e50fbbd701458757bdfe9815f58ed717c588d1b5", 3, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, - }; - - opts.flags |= GIT_MERGE_NO_RECURSIVE; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchE-1", "branchE-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 8)); - - git_index_free(index); -} - -void test_merge_trees_recursive__conflict(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "fa567f568ed72157c0c617438d077695b99d9aac", 1, "veal.txt" }, - { 0100644, "21950d5e4e4d1a871b4dfcf72ecb6b9c162c434e", 2, "veal.txt" }, - { 0100644, "3855170cef875708da06ab9ad7fc6a73b531cda1", 3, "veal.txt" }, - }; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchF-1", "branchF-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 8)); - - git_index_free(index); -} - -/* - * Branch G-1 and G-2 have three common ancestors (815b5a1, ad2ace9, 483065d). - * The merge-base of the first two has two common ancestors (723181f, a34e5a1) - * which themselves have two common ancestors (8f35f30, 3a3f5a6), which - * finally has a common ancestor of 7c7bf85. This virtual merge base will - * be computed and merged with 483065d which also has a common ancestor of - * 7c7bf85. - */ -void test_merge_trees_recursive__oh_so_many_levels_of_recursion(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "7c7e08f9559d9e1551b91e1cf68f1d0066109add", 0, "oyster.txt" }, - { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "veal.txt" }, - }; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchG-1", "branchG-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 6)); - - git_index_free(index); -} - -/* Branch H-1 and H-2 have two common ancestors (aa9e263, 6ef31d3). The two - * ancestors themselves conflict. - */ -void test_merge_trees_recursive__conflicting_merge_base(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "cfc01b0976122eae42a82064440bbf534eddd7a0", 1, "veal.txt" }, - { 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 2, "veal.txt" }, - { 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 3, "veal.txt" }, - }; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchH-1", "branchH-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 8)); - - git_index_free(index); -} - -/* Branch H-1 and H-2 have two common ancestors (aa9e263, 6ef31d3). The two - * ancestors themselves conflict. The generated common ancestor file will - * have diff3 style conflicts inside it. - */ -void test_merge_trees_recursive__conflicting_merge_base_with_diff3(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "0b01d2f70a1c6b9ab60c382f3f9cdc8173da6736", 1, "veal.txt" }, - { 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 2, "veal.txt" }, - { 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 3, "veal.txt" }, - }; - - opts.file_flags |= GIT_MERGE_FILE_STYLE_DIFF3; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchH-2", "branchH-1", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 8)); - - git_index_free(index); -} - -/* Branch I-1 and I-2 have two common ancestors (aa9e263, 6ef31d3). The two - * ancestors themselves conflict, but when each was merged, the conflicts were - * resolved identically, thus merging I-1 into I-2 does not conflict. - */ -void test_merge_trees_recursive__conflicting_merge_base_since_resolved(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "a02d4fd126e0cc8fb46ee48cf38bad36d44f2dbc", 0, "veal.txt" }, - }; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchI-1", "branchI-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 6)); - - git_index_free(index); -} - -/* There are multiple levels of criss-cross merges, and multiple recursive - * merges would create a common ancestor that allows the merge to complete - * successfully. Test that we can build a single virtual base, then stop, - * which will produce a conflicting merge. - */ -void test_merge_trees_recursive__recursionlimit(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "53217e8ac3f52bccf7603b8fff0ed0f4817f9bb7", 1, "veal.txt" }, - { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 2, "veal.txt" }, - { 0100644, "68a2e1ee61a23a4728fe6b35580fbbbf729df370", 3, "veal.txt" }, - }; - - opts.recursion_limit = 1; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchC-1", "branchC-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 8)); - - git_index_free(index); -} - -/* There are multiple levels of criss-cross merges. This ensures - * that the virtual merge base parents are compared in the same - * order as git. If the base parents are created in the order as - * git does, then the file `targetfile.txt` is automerged. If not, - * `targetfile.txt` will be in conflict due to the virtual merge - * base. - */ -void test_merge_trees_recursive__merge_base_for_virtual_commit(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "1bde1883de4977ea3e664b315da951d1f614c3b1", 0, "targetfile.txt" }, - { 0100644, "b7de2b52ba055688061355fad1599a5d214ce8f8", 1, "version.txt" }, - { 0100644, "358efd6f589384fa8baf92234db9c7899a53916e", 2, "version.txt" }, - { 0100644, "a664873b1c0b9a1ed300f8644dde536fdaa3a34f", 3, "version.txt" }, - }; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchJ-1", "branchJ-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 4)); - - git_index_free(index); -} - -/* This test is the same as above, but the graph is constructed such - * that the 1st-recursion merge bases of the two heads are - * in a different order. - */ -void test_merge_trees_recursive__merge_base_for_virtual_commit_2(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "4a06b258fed8a4d15967ec4253ae7366b70f727d", 0, "targetfile.txt" }, - { 0100644, "b6bd0f9952f396e757d3f91e08c59a7e91707201", 1, "version.txt" }, - { 0100644, "f0856993e005c0d8ed2dc7cdc222cc1d89fb3c77", 2, "version.txt" }, - { 0100644, "2cba583804a4a6fad1baf97c959be447238d1489", 3, "version.txt" }, - }; - - cl_git_pass(merge_commits_from_branches(&index, repo, "branchK-1", "branchK-2", &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 4)); - - git_index_free(index); -} diff --git a/tests/merge/trees/renames.c b/tests/merge/trees/renames.c deleted file mode 100644 index 26f6d3306..000000000 --- a/tests/merge/trees/renames.c +++ /dev/null @@ -1,352 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" -#include "../merge_helpers.h" -#include "futils.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-resolve" - -#define BRANCH_RENAME_OURS "rename_conflict_ours" -#define BRANCH_RENAME_THEIRS "rename_conflict_theirs" - -/* Fixture setup and teardown */ -void test_merge_trees_renames__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_merge_trees_renames__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_merge_trees_renames__index(void) -{ - git_index *index; - git_merge_options *opts = NULL; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, - { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, - { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" }, - { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" }, - { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" }, - { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, - { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" }, - { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" }, - { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" }, - { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, - { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, - { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, - { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, - { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, - { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 2, "3a-newname-in-ours-deleted-in-theirs.txt" }, - { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 1, "3a-renamed-in-ours-deleted-in-theirs.txt" }, - { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 3, "3b-newname-in-theirs-deleted-in-ours.txt" }, - { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 1, "3b-renamed-in-theirs-deleted-in-ours.txt" }, - { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 1, "4a-renamed-in-ours-added-in-theirs.txt" }, - { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 1, "4b-renamed-in-theirs-added-in-ours.txt" }, - { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 1, "5a-renamed-in-ours-added-in-theirs.txt" }, - { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 3, "5a-renamed-in-ours-added-in-theirs.txt" }, - { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 1, "5b-renamed-in-theirs-added-in-ours.txt" }, - { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 2, "5b-renamed-in-theirs-added-in-ours.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 2, "6-both-renamed-1-to-2-ours.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 3, "6-both-renamed-1-to-2-theirs.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 1, "6-both-renamed-1-to-2.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "7-both-renamed-side-1.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "7-both-renamed-side-1.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "7-both-renamed-side-2.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "7-both-renamed-side-2.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" }, - }; - - struct merge_name_entry merge_name_entries[] = { - { - "3a-renamed-in-ours-deleted-in-theirs.txt", - "3a-newname-in-ours-deleted-in-theirs.txt", - "" - }, - - { - "3b-renamed-in-theirs-deleted-in-ours.txt", - "", - "3b-newname-in-theirs-deleted-in-ours.txt", - }, - - { - "4a-renamed-in-ours-added-in-theirs.txt", - "4a-newname-in-ours-added-in-theirs.txt", - "", - }, - - { - "4b-renamed-in-theirs-added-in-ours.txt", - "", - "4b-newname-in-theirs-added-in-ours.txt", - }, - - { - "5a-renamed-in-ours-added-in-theirs.txt", - "5a-newname-in-ours-added-in-theirs.txt", - "5a-renamed-in-ours-added-in-theirs.txt", - }, - - { - "5b-renamed-in-theirs-added-in-ours.txt", - "5b-renamed-in-theirs-added-in-ours.txt", - "5b-newname-in-theirs-added-in-ours.txt", - }, - - { - "6-both-renamed-1-to-2.txt", - "6-both-renamed-1-to-2-ours.txt", - "6-both-renamed-1-to-2-theirs.txt", - }, - - { - "7-both-renamed-side-1.txt", - "7-both-renamed.txt", - "7-both-renamed-side-1.txt", - }, - - { - "7-both-renamed-side-2.txt", - "7-both-renamed-side-2.txt", - "7-both-renamed.txt", - }, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - { "1a-newname-in-ours-edited-in-theirs.txt", - 0, 0100644, 0, - "", - "c3d02eeef75183df7584d8d13ac03053910c1301", - "" }, - - { "1a-newname-in-ours.txt", - 0, 0100644, 0, - "", - "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", - "" }, - - { "1a-renamed-in-ours-edited-in-theirs.txt", - 0100644, 0, 0100644, - "c3d02eeef75183df7584d8d13ac03053910c1301", - "", - "0d872f8e871a30208305978ecbf9e66d864f1638" }, - - { "1a-renamed-in-ours.txt", - 0100644, 0, 0100644, - "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", - "", - "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb" }, - - { "1b-newname-in-theirs-edited-in-ours.txt", - 0, 0, 0100644, - "", - "", - "241a1005cd9b980732741b74385b891142bcba28" }, - - { "1b-newname-in-theirs.txt", - 0, 0, 0100644, - "", - "", - "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136" }, - - { "1b-renamed-in-theirs-edited-in-ours.txt", - 0100644, 0100644, 0, - "241a1005cd9b980732741b74385b891142bcba28", - "ed9523e62e453e50dd9be1606af19399b96e397a", - "" }, - - { "1b-renamed-in-theirs.txt", - 0100644, 0100644, 0, - "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", - "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", - "" }, - - { "2-newname-in-both.txt", - 0, 0100644, 0100644, - "", - "178940b450f238a56c0d75b7955cb57b38191982", - "178940b450f238a56c0d75b7955cb57b38191982" }, - - { "2-renamed-in-both.txt", - 0100644, 0, 0, - "178940b450f238a56c0d75b7955cb57b38191982", - "", - "" }, - }; - - cl_git_pass(merge_trees_from_branches(&index, repo, - BRANCH_RENAME_OURS, BRANCH_RENAME_THEIRS, - opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 41)); - cl_assert(merge_test_names(index, merge_name_entries, 9)); - cl_assert(merge_test_reuc(index, merge_reuc_entries, 10)); - - git_index_free(index); -} - -void test_merge_trees_renames__no_rename_index(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, - { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, - { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" }, - { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" }, - { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" }, - { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, - { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" }, - { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" }, - { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" }, - { 0100644, "c3d02eeef75183df7584d8d13ac03053910c1301", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, - { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, - { 0100644, "c3d02eeef75183df7584d8d13ac03053910c1301", 1, "1a-renamed-in-ours-edited-in-theirs.txt" }, - { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 3, "1a-renamed-in-ours-edited-in-theirs.txt" }, - { 0100644, "241a1005cd9b980732741b74385b891142bcba28", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, - { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, - { 0100644, "241a1005cd9b980732741b74385b891142bcba28", 1, "1b-renamed-in-theirs-edited-in-ours.txt" }, - { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 2, "1b-renamed-in-theirs-edited-in-ours.txt" }, - { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, - { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, - { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, - { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-theirs.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" }, - }; - - opts.flags &= ~GIT_MERGE_FIND_RENAMES; - - cl_git_pass(merge_trees_from_branches(&index, repo, - BRANCH_RENAME_OURS, BRANCH_RENAME_THEIRS, - &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 32)); - - git_index_free(index); -} - -void test_merge_trees_renames__submodules(void) -{ - git_index *index; - git_merge_options *opts = NULL; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "cd3e8d4aa06bdc781f264171030bc28f2b370fee", 0, ".gitmodules" }, - { 0100644, "4dd1ef7569b18d92d93c0a35bb6b93049137b355", 1, "file.txt" }, - { 0100644, "a2d8d1824c68541cca94ffb90f79291eba495921", 2, "file.txt" }, - { 0100644, "63ec604d491161ddafdae4179843c26d54bd999a", 3, "file.txt" }, - { 0160000, "0000000000000000000000000000000000000001", 1, "submodule1" }, - { 0160000, "0000000000000000000000000000000000000002", 3, "submodule1" }, - { 0160000, "0000000000000000000000000000000000000003", 0, "submodule2" }, - }; - - cl_git_pass(merge_trees_from_branches(&index, repo, - "submodule_rename1", "submodule_rename2", - opts)); - cl_assert(merge_test_index(index, merge_index_entries, 7)); - git_index_free(index); -} - -void test_merge_trees_renames__cache_recomputation(void) -{ - git_oid blob, binary, ancestor_oid, theirs_oid, ours_oid; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - git_str path = GIT_STR_INIT; - git_treebuilder *builder; - git_tree *ancestor_tree, *their_tree, *our_tree; - git_index *index; - size_t blob_size; - void *data; - size_t i; - - cl_git_pass(git_oid_fromstr(&blob, "a2d8d1824c68541cca94ffb90f79291eba495921")); - - /* - * Create a 50MB blob that consists of NUL bytes only. It is important - * that this blob is of a special format, most importantly it cannot - * contain more than four non-consecutive newlines or NUL bytes. This - * is because of git_hashsig's inner workings where all files with less - * than four "lines" are deemed to small. - */ - blob_size = 50 * 1024 * 1024; - cl_assert(data = git__calloc(blob_size, 1)); - cl_git_pass(git_blob_create_from_buffer(&binary, repo, data, blob_size)); - - /* - * Create the common ancestor, which has 1000 dummy blobs and the binary - * blob. The dummy blobs serve as potential rename targets for the - * dummy blob. - */ - cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); - for (i = 0; i < 1000; i++) { - cl_git_pass(git_str_printf(&path, "%"PRIuZ".txt", i)); - cl_git_pass(git_treebuilder_insert(NULL, builder, path.ptr, &blob, GIT_FILEMODE_BLOB)); - git_str_clear(&path); - } - cl_git_pass(git_treebuilder_insert(NULL, builder, "original.bin", &binary, GIT_FILEMODE_BLOB)); - cl_git_pass(git_treebuilder_write(&ancestor_oid, builder)); - - /* We now the binary blob in our tree. */ - cl_git_pass(git_treebuilder_remove(builder, "original.bin")); - cl_git_pass(git_treebuilder_insert(NULL, builder, "renamed.bin", &binary, GIT_FILEMODE_BLOB)); - cl_git_pass(git_treebuilder_write(&ours_oid, builder)); - - git_treebuilder_free(builder); - - /* And move everything into a subdirectory in their tree. */ - cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); - cl_git_pass(git_treebuilder_insert(NULL, builder, "subdir", &ancestor_oid, GIT_FILEMODE_TREE)); - cl_git_pass(git_treebuilder_write(&theirs_oid, builder)); - - /* - * Now merge ancestor, ours and theirs. As `git_hashsig` refuses to - * create a hash signature for the 50MB binary file, we historically - * didn't cache the hashsig computation for it. As a result, we now - * started looking up the 50MB blob and scanning it at least 1000 - * times, which takes a long time. - * - * The number of 1000 blobs is chosen in such a way that it's - * noticeable when the bug creeps in again, as it takes around 12 - * minutes on my machine to compute the following merge. - */ - opts.target_limit = 5000; - cl_git_pass(git_tree_lookup(&ancestor_tree, repo, &ancestor_oid)); - cl_git_pass(git_tree_lookup(&their_tree, repo, &theirs_oid)); - cl_git_pass(git_tree_lookup(&our_tree, repo, &ours_oid)); - cl_git_pass(git_merge_trees(&index, repo, ancestor_tree, our_tree, their_tree, &opts)); - - git_treebuilder_free(builder); - git_str_dispose(&path); - git_index_free(index); - git_tree_free(ancestor_tree); - git_tree_free(their_tree); - git_tree_free(our_tree); - git__free(data); -} diff --git a/tests/merge/trees/treediff.c b/tests/merge/trees/treediff.c deleted file mode 100644 index cd2cf7827..000000000 --- a/tests/merge/trees/treediff.c +++ /dev/null @@ -1,555 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/tree.h" -#include "merge.h" -#include "../merge_helpers.h" -#include "diff.h" -#include "diff_tform.h" -#include "git2/sys/hashsig.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-resolve" - -#define TREE_OID_ANCESTOR "0d52e3a556e189ba0948ae56780918011c1b167d" -#define TREE_OID_MASTER "1f81433e3161efbf250576c58fede7f6b836f3d3" -#define TREE_OID_BRANCH "eea9286df54245fea72c5b557291470eb825f38f" -#define TREE_OID_RENAMES1 "f5f9dd5886a6ee20272be0aafc790cba43b31931" -#define TREE_OID_RENAMES2 "5fbfbdc04b4eca46f54f4853a3c5a1dce28f5165" - -#define TREE_OID_DF_ANCESTOR "b8a3a806d3950e8c0a03a34f234a92eff0e2c68d" -#define TREE_OID_DF_SIDE1 "ee1d6f164893c1866a323f072eeed36b855656be" -#define TREE_OID_DF_SIDE2 "6178885b38fe96e825ac0f492c0a941f288b37f6" - -#define TREE_OID_RENAME_CONFLICT_ANCESTOR "476dbb3e207313d1d8aaa120c6ad204bf1295e53" -#define TREE_OID_RENAME_CONFLICT_OURS "c4efe31e9decccc8b2b4d3df9aac2cdfe2995618" -#define TREE_OID_RENAME_CONFLICT_THEIRS "9e7f4359c469f309b6057febf4c6e80742cbed5b" - -void test_merge_trees_treediff__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_merge_trees_treediff__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void test_find_differences( - const char *ancestor_oidstr, - const char *ours_oidstr, - const char *theirs_oidstr, - struct merge_index_conflict_data *treediff_conflict_data, - size_t treediff_conflict_data_len) -{ - git_merge_diff_list *merge_diff_list = git_merge_diff_list__alloc(repo); - git_oid ancestor_oid, ours_oid, theirs_oid; - git_tree *ancestor_tree, *ours_tree, *theirs_tree; - git_iterator *ancestor_iter, *ours_iter, *theirs_iter; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - opts.flags |= GIT_MERGE_FIND_RENAMES; - opts.target_limit = 1000; - opts.rename_threshold = 50; - - opts.metric = git__malloc(sizeof(git_diff_similarity_metric)); - cl_assert(opts.metric != NULL); - - opts.metric->file_signature = git_diff_find_similar__hashsig_for_file; - opts.metric->buffer_signature = git_diff_find_similar__hashsig_for_buf; - opts.metric->free_signature = git_diff_find_similar__hashsig_free; - opts.metric->similarity = git_diff_find_similar__calc_similarity; - opts.metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE; - - cl_git_pass(git_oid_fromstr(&ancestor_oid, ancestor_oidstr)); - cl_git_pass(git_oid_fromstr(&ours_oid, ours_oidstr)); - cl_git_pass(git_oid_fromstr(&theirs_oid, theirs_oidstr)); - - cl_git_pass(git_tree_lookup(&ancestor_tree, repo, &ancestor_oid)); - cl_git_pass(git_tree_lookup(&ours_tree, repo, &ours_oid)); - cl_git_pass(git_tree_lookup(&theirs_tree, repo, &theirs_oid)); - - iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_tree(&ancestor_iter, ancestor_tree, &iter_opts)); - cl_git_pass(git_iterator_for_tree(&ours_iter, ours_tree, &iter_opts)); - cl_git_pass(git_iterator_for_tree(&theirs_iter, theirs_tree, &iter_opts)); - - cl_git_pass(git_merge_diff_list__find_differences(merge_diff_list, ancestor_iter, ours_iter, theirs_iter)); - cl_git_pass(git_merge_diff_list__find_renames(repo, merge_diff_list, &opts)); - - /* - dump_merge_index(merge_index); - */ - - cl_assert(treediff_conflict_data_len == merge_diff_list->conflicts.length); - - cl_assert(merge_test_merge_conflicts(&merge_diff_list->conflicts, treediff_conflict_data, treediff_conflict_data_len)); - - git_iterator_free(ancestor_iter); - git_iterator_free(ours_iter); - git_iterator_free(theirs_iter); - - git_tree_free(ancestor_tree); - git_tree_free(ours_tree); - git_tree_free(theirs_tree); - - git_merge_diff_list__free(merge_diff_list); - - git__free(opts.metric); -} - -void test_merge_trees_treediff__simple(void) -{ - struct merge_index_conflict_data treediff_conflict_data[] = { - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, GIT_DELTA_ADDED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE - }, - - { - { { 0100644, "6212c31dab5e482247d7977e4f0dd3601decf13b", 0, "automergeable.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" }, GIT_DELTA_MODIFIED }, - { { 0100644, "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe", 0, "automergeable.txt" }, GIT_DELTA_MODIFIED }, - GIT_MERGE_DIFF_BOTH_MODIFIED - }, - - { - { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "4eb04c9e79e88f6640d01ff5b25ca2a60764f216", 0, "changed-in-branch.txt" }, GIT_DELTA_MODIFIED }, - GIT_MERGE_DIFF_NONE - }, - - { - { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, GIT_DELTA_MODIFIED }, - { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE - }, - - { - { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, GIT_DELTA_MODIFIED }, - { { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 0, "conflicting.txt" }, GIT_DELTA_MODIFIED }, - GIT_MERGE_DIFF_BOTH_MODIFIED - }, - - { - { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - GIT_MERGE_DIFF_NONE - }, - - { - { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE - }, - }; - - test_find_differences(TREE_OID_ANCESTOR, TREE_OID_MASTER, TREE_OID_BRANCH, treediff_conflict_data, 7); -} - -void test_merge_trees_treediff__df_conflicts(void) -{ - struct merge_index_conflict_data treediff_conflict_data[] = { - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "49130a28ef567af9a6a6104c38773fedfa5f9742", 0, "dir-10" }, GIT_DELTA_ADDED }, - { { 0100644, "6c06dcd163587c2cc18be44857e0b71116382aeb", 0, "dir-10" }, GIT_DELTA_ADDED }, - GIT_MERGE_DIFF_BOTH_ADDED, - }, - - { - { { 0100644, "242591eb280ee9eeb2ce63524b9a8b9bc4cb515d", 0, "dir-10/file.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - GIT_MERGE_DIFF_BOTH_DELETED, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "43aafd43bea779ec74317dc361f45ae3f532a505", 0, "dir-6" }, GIT_DELTA_ADDED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "cf8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d", 0, "dir-6/file.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "cf8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d", 0, "dir-6/file.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "a031a28ae70e33a641ce4b8a8f6317f1ab79dee4", 0, "dir-7" }, GIT_DELTA_ADDED }, - GIT_MERGE_DIFF_DIRECTORY_FILE, - }, - - { - { { 0100644, "5012fd565b1393bdfda1805d4ec38ce6619e1fd1", 0, "dir-7/file.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "a5563304ddf6caba25cb50323a2ea6f7dbfcadca", 0, "dir-7/file.txt" }, GIT_DELTA_MODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - GIT_MERGE_DIFF_DF_CHILD, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "e9ad6ec3e38364a3d07feda7c4197d4d845c53b5", 0, "dir-8" }, GIT_DELTA_ADDED }, - { {0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "f20c9063fa0bda9a397c96947a7b687305c49753", 0, "dir-8/file.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - { { 0100644, "f20c9063fa0bda9a397c96947a7b687305c49753", 0, "dir-8/file.txt" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "3ef4d30382ca33fdeba9fda895a99e0891ba37aa", 0, "dir-9" }, GIT_DELTA_ADDED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_DIRECTORY_FILE, - }, - - { - { { 0100644, "fc4c636d6515e9e261f9260dbcf3cc6eca97ea08", 0, "dir-9/file.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - { { 0100644, "76ab0e2868197ec158ddd6c78d8a0d2fd73d38f9", 0, "dir-9/file.txt" }, GIT_DELTA_MODIFIED }, - GIT_MERGE_DIFF_DF_CHILD, - }, - - { - { { 0100644, "1e4ff029aee68d0d69ef9eb6efa6cbf1ec732f99", 0, "file-1" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "1e4ff029aee68d0d69ef9eb6efa6cbf1ec732f99", 0, "file-1" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "5c2411f8075f48a6b2fdb85ebc0d371747c4df15", 0, "file-1/new" }, GIT_DELTA_ADDED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "a39a620dae5bc8b4e771cd4d251b7d080401a21e", 0, "file-2" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "d963979c237d08b6ba39062ee7bf64c7d34a27f8", 0, "file-2" }, GIT_DELTA_MODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - GIT_MERGE_DIFF_DIRECTORY_FILE, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "5c341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d", 0, "file-2/new" }, GIT_DELTA_ADDED }, - GIT_MERGE_DIFF_DF_CHILD, - }, - - { - { { 0100644, "032ebc5ab85d9553bb187d3cd40875ff23a63ed0", 0, "file-3" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - { { 0100644, "032ebc5ab85d9553bb187d3cd40875ff23a63ed0", 0, "file-3" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "9efe7723802d4305142eee177e018fee1572c4f4", 0, "file-3/new" }, GIT_DELTA_ADDED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "bacac9b3493509aa15e1730e1545fc0919d1dae0", 0, "file-4" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - { { 0100644, "7663fce0130db092936b137cabd693ec234eb060", 0, "file-4" }, GIT_DELTA_MODIFIED }, - GIT_MERGE_DIFF_DIRECTORY_FILE, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "e49f917b448d1340b31d76e54ba388268fd4c922", 0, "file-4/new" }, GIT_DELTA_ADDED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_DF_CHILD, - }, - - { - { { 0100644, "ac4045f965119e6998f4340ed0f411decfb3ec05", 0, "file-5" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - GIT_MERGE_DIFF_BOTH_DELETED, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "cab2cf23998b40f1af2d9d9a756dc9e285a8df4b", 0, "file-5/new" }, GIT_DELTA_ADDED }, - { { 0100644, "f5504f36e6f4eb797a56fc5bac6c6c7f32969bf2", 0, "file-5/new" }, GIT_DELTA_ADDED }, - GIT_MERGE_DIFF_BOTH_ADDED, - }, - }; - - test_find_differences(TREE_OID_DF_ANCESTOR, TREE_OID_DF_SIDE1, TREE_OID_DF_SIDE2, treediff_conflict_data, 20); -} - -void test_merge_trees_treediff__strict_renames(void) -{ - struct merge_index_conflict_data treediff_conflict_data[] = { - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, GIT_DELTA_ADDED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "6212c31dab5e482247d7977e4f0dd3601decf13b", 0, "automergeable.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" }, GIT_DELTA_MODIFIED }, - { { 0100644, "6212c31dab5e482247d7977e4f0dd3601decf13b", 0, "automergeable.txt" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, GIT_DELTA_MODIFIED }, - { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, GIT_DELTA_MODIFIED }, - { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "renamed-in-branch.txt" }, GIT_DELTA_RENAMED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "renamed.txt" }, GIT_DELTA_ADDED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "copied.txt" }, GIT_DELTA_RENAMED }, - GIT_MERGE_DIFF_NONE, - }, - }; - - test_find_differences(TREE_OID_ANCESTOR, TREE_OID_MASTER, TREE_OID_RENAMES1, treediff_conflict_data, 8); -} - -void test_merge_trees_treediff__rename_conflicts(void) -{ - struct merge_index_conflict_data treediff_conflict_data[] = { - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, GIT_DELTA_ADDED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-rewritten-in-ours.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 0, "0b-rewritten-in-ours.txt" }, GIT_DELTA_MODIFIED }, - { { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 0, "0b-rewritten-in-ours.txt" }, GIT_DELTA_MODIFIED }, - GIT_MERGE_DIFF_BOTH_MODIFIED, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, GIT_DELTA_ADDED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-rewritten-in-theirs.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 0, "0c-rewritten-in-theirs.txt" }, GIT_DELTA_MODIFIED }, - { { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 0, "0c-rewritten-in-theirs.txt" }, GIT_DELTA_MODIFIED }, - GIT_MERGE_DIFF_BOTH_MODIFIED, - }, - - { - { { 0100644, "c3d02eeef75183df7584d8d13ac03053910c1301", 0, "1a-renamed-in-ours-edited-in-theirs.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "c3d02eeef75183df7584d8d13ac03053910c1301", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, GIT_DELTA_RENAMED }, - { { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-renamed-in-ours-edited-in-theirs.txt" }, GIT_DELTA_MODIFIED }, - GIT_MERGE_DIFF_RENAMED_MODIFIED, - }, - - { - { { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-renamed-in-ours.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, GIT_DELTA_RENAMED }, - { { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-renamed-in-ours.txt" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "241a1005cd9b980732741b74385b891142bcba28", 0, "1b-renamed-in-theirs-edited-in-ours.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-renamed-in-theirs-edited-in-ours.txt" }, GIT_DELTA_MODIFIED }, - { { 0100644, "241a1005cd9b980732741b74385b891142bcba28", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, GIT_DELTA_RENAMED }, - GIT_MERGE_DIFF_RENAMED_MODIFIED, - }, - - { - { { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-renamed-in-theirs.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-renamed-in-theirs.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, GIT_DELTA_RENAMED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-renamed-in-both.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, GIT_DELTA_RENAMED }, - { { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, GIT_DELTA_RENAMED }, - GIT_MERGE_DIFF_BOTH_RENAMED, - }, - - { - { { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-renamed-in-ours-deleted-in-theirs.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, GIT_DELTA_RENAMED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - GIT_MERGE_DIFF_RENAMED_DELETED, - }, - - { - { { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-renamed-in-theirs-deleted-in-ours.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - { { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, GIT_DELTA_RENAMED }, - GIT_MERGE_DIFF_RENAMED_DELETED, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 0, "4a-newname-in-ours-added-in-theirs.txt" }, GIT_DELTA_ADDED }, - GIT_MERGE_DIFF_RENAMED_ADDED, - }, - - { - { { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-renamed-in-ours-added-in-theirs.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt" }, GIT_DELTA_RENAMED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - GIT_MERGE_DIFF_RENAMED_ADDED, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt" }, GIT_DELTA_ADDED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_RENAMED_ADDED, - }, - - { - { { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-renamed-in-theirs-added-in-ours.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - { { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-newname-in-theirs-added-in-ours.txt" }, GIT_DELTA_RENAMED }, - GIT_MERGE_DIFF_RENAMED_ADDED, - }, - - { - { { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "5-both-renamed-1-to-2.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "5-both-renamed-1-to-2-ours.txt" }, GIT_DELTA_RENAMED }, - { { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "5-both-renamed-1-to-2-theirs.txt" }, GIT_DELTA_RENAMED }, - GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2, - }, - - { - { { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "6-both-renamed-side-1.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "6-both-renamed.txt" }, GIT_DELTA_RENAMED }, - { { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "6-both-renamed-side-1.txt" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1, - }, - - { - { { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "6-both-renamed-side-2.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "6-both-renamed-side-2.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "6-both-renamed.txt" }, GIT_DELTA_RENAMED }, - GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1, - }, - }; - test_find_differences(TREE_OID_RENAME_CONFLICT_ANCESTOR, - TREE_OID_RENAME_CONFLICT_OURS, TREE_OID_RENAME_CONFLICT_THEIRS, treediff_conflict_data, 18); -} - -void test_merge_trees_treediff__best_renames(void) -{ - struct merge_index_conflict_data treediff_conflict_data[] = { - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, GIT_DELTA_ADDED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "6212c31dab5e482247d7977e4f0dd3601decf13b", 0, "automergeable.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" }, GIT_DELTA_MODIFIED }, - { { 0100644, "45299c1ca5e07bba1fd90843056fb559f96b1f5a", 0, "renamed-90.txt" }, GIT_DELTA_RENAMED }, - GIT_MERGE_DIFF_RENAMED_MODIFIED, - }, - - { - { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, GIT_DELTA_MODIFIED }, - { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, GIT_DELTA_MODIFIED }, - { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" },GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_DELETED }, - { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED }, - GIT_MERGE_DIFF_MODIFIED_DELETED, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "5843febcb23480df0b5edb22a21c59c772bb8e29", 0, "renamed-50.txt" }, GIT_DELTA_ADDED }, - GIT_MERGE_DIFF_NONE, - }, - - { - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED }, - { { 0100644, "a77a56a49f8f3ae242e02717f18ebbc60c5cc543", 0, "renamed-75.txt" }, GIT_DELTA_ADDED }, - GIT_MERGE_DIFF_NONE, - }, - }; - - test_find_differences(TREE_OID_ANCESTOR, TREE_OID_MASTER, TREE_OID_RENAMES2, treediff_conflict_data, 7); -} diff --git a/tests/merge/trees/trivial.c b/tests/merge/trees/trivial.c deleted file mode 100644 index dce392c86..000000000 --- a/tests/merge/trees/trivial.c +++ /dev/null @@ -1,306 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" -#include "../merge_helpers.h" -#include "refs.h" -#include "futils.h" -#include "git2/sys/index.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-resolve" -#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" - - -/* Fixture setup and teardown */ -void test_merge_trees_trivial__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_merge_trees_trivial__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - - -static int merge_trivial(git_index **index, const char *ours, const char *theirs) -{ - git_commit *our_commit, *their_commit, *ancestor_commit; - git_tree *our_tree, *their_tree, *ancestor_tree; - git_oid our_oid, their_oid, ancestor_oid; - git_str branch_buf = GIT_STR_INIT; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours); - cl_git_pass(git_reference_name_to_id(&our_oid, repo, branch_buf.ptr)); - cl_git_pass(git_commit_lookup(&our_commit, repo, &our_oid)); - - git_str_clear(&branch_buf); - git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, theirs); - cl_git_pass(git_reference_name_to_id(&their_oid, repo, branch_buf.ptr)); - cl_git_pass(git_commit_lookup(&their_commit, repo, &their_oid)); - - cl_git_pass(git_merge_base(&ancestor_oid, repo, git_commit_id(our_commit), git_commit_id(their_commit))); - cl_git_pass(git_commit_lookup(&ancestor_commit, repo, &ancestor_oid)); - - cl_git_pass(git_commit_tree(&ancestor_tree, ancestor_commit)); - cl_git_pass(git_commit_tree(&our_tree, our_commit)); - cl_git_pass(git_commit_tree(&their_tree, their_commit)); - - cl_git_pass(git_merge_trees(index, repo, ancestor_tree, our_tree, their_tree, &opts)); - - git_str_dispose(&branch_buf); - git_tree_free(our_tree); - git_tree_free(their_tree); - git_tree_free(ancestor_tree); - git_commit_free(our_commit); - git_commit_free(their_commit); - git_commit_free(ancestor_commit); - - return 0; -} - -static int merge_trivial_conflict_entrycount(git_index *index) -{ - const git_index_entry *entry; - int count = 0; - size_t i; - - for (i = 0; i < git_index_entrycount(index); i++) { - cl_assert(entry = git_index_get_byindex(index, i)); - - if (git_index_entry_is_conflict(entry)) - count++; - } - - return count; -} - -/* 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote */ -void test_merge_trees_trivial__2alt(void) -{ - git_index *result; - const git_index_entry *entry; - - cl_git_pass(merge_trivial(&result, "trivial-2alt", "trivial-2alt-branch")); - - cl_assert(entry = git_index_get_bypath(result, "new-in-branch.txt", 0)); - cl_assert(git_index_reuc_entrycount(result) == 0); - cl_assert(merge_trivial_conflict_entrycount(result) == 0); - - git_index_free(result); -} - -/* 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head */ -void test_merge_trees_trivial__3alt(void) -{ - git_index *result; - const git_index_entry *entry; - - cl_git_pass(merge_trivial(&result, "trivial-3alt", "trivial-3alt-branch")); - - cl_assert(entry = git_index_get_bypath(result, "new-in-3alt.txt", 0)); - cl_assert(git_index_reuc_entrycount(result) == 0); - cl_assert(merge_trivial_conflict_entrycount(result) == 0); - - git_index_free(result); -} - -/* 4: ancest:(empty)^, head:head, remote:remote = result:no merge */ -void test_merge_trees_trivial__4(void) -{ - git_index *result; - const git_index_entry *entry; - - cl_git_pass(merge_trivial(&result, "trivial-4", "trivial-4-branch")); - - cl_assert((entry = git_index_get_bypath(result, "new-and-different.txt", 0)) == NULL); - cl_assert(git_index_reuc_entrycount(result) == 0); - - cl_assert(merge_trivial_conflict_entrycount(result) == 2); - cl_assert(entry = git_index_get_bypath(result, "new-and-different.txt", 2)); - cl_assert(entry = git_index_get_bypath(result, "new-and-different.txt", 3)); - - git_index_free(result); -} - -/* 5ALT: ancest:*, head:head, remote:head = result:head */ -void test_merge_trees_trivial__5alt_1(void) -{ - git_index *result; - const git_index_entry *entry; - - cl_git_pass(merge_trivial(&result, "trivial-5alt-1", "trivial-5alt-1-branch")); - - cl_assert(entry = git_index_get_bypath(result, "new-and-same.txt", 0)); - cl_assert(git_index_reuc_entrycount(result) == 0); - cl_assert(merge_trivial_conflict_entrycount(result) == 0); - - git_index_free(result); -} - -/* 5ALT: ancest:*, head:head, remote:head = result:head */ -void test_merge_trees_trivial__5alt_2(void) -{ - git_index *result; - const git_index_entry *entry; - - cl_git_pass(merge_trivial(&result, "trivial-5alt-2", "trivial-5alt-2-branch")); - - cl_assert(entry = git_index_get_bypath(result, "modified-to-same.txt", 0)); - cl_assert(git_index_reuc_entrycount(result) == 0); - cl_assert(merge_trivial_conflict_entrycount(result) == 0); - - git_index_free(result); -} - -/* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */ -void test_merge_trees_trivial__6(void) -{ - git_index *result; - const git_index_entry *entry; - const git_index_reuc_entry *reuc; - - cl_git_pass(merge_trivial(&result, "trivial-6", "trivial-6-branch")); - - cl_assert((entry = git_index_get_bypath(result, "removed-in-both.txt", 0)) == NULL); - cl_assert(git_index_reuc_entrycount(result) == 1); - cl_assert(reuc = git_index_reuc_get_bypath(result, "removed-in-both.txt")); - - cl_assert(merge_trivial_conflict_entrycount(result) == 0); - - git_index_free(result); -} - -/* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */ -void test_merge_trees_trivial__8(void) -{ - git_index *result; - const git_index_entry *entry; - const git_index_reuc_entry *reuc; - - cl_git_pass(merge_trivial(&result, "trivial-8", "trivial-8-branch")); - - cl_assert((entry = git_index_get_bypath(result, "removed-in-8.txt", 0)) == NULL); - - cl_assert(git_index_reuc_entrycount(result) == 1); - cl_assert(reuc = git_index_reuc_get_bypath(result, "removed-in-8.txt")); - - cl_assert(merge_trivial_conflict_entrycount(result) == 0); - - git_index_free(result); -} - -/* 7: ancest:ancest+, head:(empty), remote:remote = result:no merge */ -void test_merge_trees_trivial__7(void) -{ - git_index *result; - const git_index_entry *entry; - - cl_git_pass(merge_trivial(&result, "trivial-7", "trivial-7-branch")); - - cl_assert((entry = git_index_get_bypath(result, "removed-in-7.txt", 0)) == NULL); - cl_assert(git_index_reuc_entrycount(result) == 0); - - cl_assert(merge_trivial_conflict_entrycount(result) == 2); - cl_assert(entry = git_index_get_bypath(result, "removed-in-7.txt", 1)); - cl_assert(entry = git_index_get_bypath(result, "removed-in-7.txt", 3)); - - git_index_free(result); -} - -/* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */ -void test_merge_trees_trivial__10(void) -{ - git_index *result; - const git_index_entry *entry; - const git_index_reuc_entry *reuc; - - cl_git_pass(merge_trivial(&result, "trivial-10", "trivial-10-branch")); - - cl_assert((entry = git_index_get_bypath(result, "removed-in-10-branch.txt", 0)) == NULL); - - cl_assert(git_index_reuc_entrycount(result) == 1); - cl_assert(reuc = git_index_reuc_get_bypath(result, "removed-in-10-branch.txt")); - - cl_assert(merge_trivial_conflict_entrycount(result) == 0); - - git_index_free(result); -} - -/* 9: ancest:ancest+, head:head, remote:(empty) = result:no merge */ -void test_merge_trees_trivial__9(void) -{ - git_index *result; - const git_index_entry *entry; - - cl_git_pass(merge_trivial(&result, "trivial-9", "trivial-9-branch")); - - cl_assert((entry = git_index_get_bypath(result, "removed-in-9-branch.txt", 0)) == NULL); - cl_assert(git_index_reuc_entrycount(result) == 0); - - cl_assert(merge_trivial_conflict_entrycount(result) == 2); - cl_assert(entry = git_index_get_bypath(result, "removed-in-9-branch.txt", 1)); - cl_assert(entry = git_index_get_bypath(result, "removed-in-9-branch.txt", 2)); - - git_index_free(result); -} - -/* 13: ancest:ancest+, head:head, remote:ancest = result:head */ -void test_merge_trees_trivial__13(void) -{ - git_index *result; - const git_index_entry *entry; - git_oid expected_oid; - - cl_git_pass(merge_trivial(&result, "trivial-13", "trivial-13-branch")); - - cl_assert(entry = git_index_get_bypath(result, "modified-in-13.txt", 0)); - cl_git_pass(git_oid_fromstr(&expected_oid, "1cff9ec6a47a537380dedfdd17c9e76d74259a2b")); - cl_assert_equal_oid(&expected_oid, &entry->id); - - cl_assert(git_index_reuc_entrycount(result) == 0); - cl_assert(merge_trivial_conflict_entrycount(result) == 0); - - git_index_free(result); -} - -/* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */ -void test_merge_trees_trivial__14(void) -{ - git_index *result; - const git_index_entry *entry; - git_oid expected_oid; - - cl_git_pass(merge_trivial(&result, "trivial-14", "trivial-14-branch")); - - cl_assert(entry = git_index_get_bypath(result, "modified-in-14-branch.txt", 0)); - cl_git_pass(git_oid_fromstr(&expected_oid, "26153a3ff3649b6c2bb652d3f06878c6e0a172f9")); - cl_assert(git_oid_cmp(&entry->id, &expected_oid) == 0); - - cl_assert(git_index_reuc_entrycount(result) == 0); - cl_assert(merge_trivial_conflict_entrycount(result) == 0); - - git_index_free(result); -} - -/* 11: ancest:ancest+, head:head, remote:remote = result:no merge */ -void test_merge_trees_trivial__11(void) -{ - git_index *result; - const git_index_entry *entry; - - cl_git_pass(merge_trivial(&result, "trivial-11", "trivial-11-branch")); - - cl_assert((entry = git_index_get_bypath(result, "modified-in-both.txt", 0)) == NULL); - cl_assert(git_index_reuc_entrycount(result) == 0); - - cl_assert(merge_trivial_conflict_entrycount(result) == 3); - cl_assert(entry = git_index_get_bypath(result, "modified-in-both.txt", 1)); - cl_assert(entry = git_index_get_bypath(result, "modified-in-both.txt", 2)); - cl_assert(entry = git_index_get_bypath(result, "modified-in-both.txt", 3)); - - git_index_free(result); -} diff --git a/tests/merge/trees/whitespace.c b/tests/merge/trees/whitespace.c deleted file mode 100644 index 9917df506..000000000 --- a/tests/merge/trees/whitespace.c +++ /dev/null @@ -1,81 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" -#include "../merge_helpers.h" -#include "futils.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-whitespace" - -#define BRANCH_A_EOL "branch_a_eol" -#define BRANCH_B_EOL "branch_b_eol" - -#define BRANCH_A_CHANGE "branch_a_change" -#define BRANCH_B_CHANGE "branch_b_change" - -/* Fixture setup and teardown */ -void test_merge_trees_whitespace__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_merge_trees_whitespace__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_merge_trees_whitespace__conflict(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "4026a6c83f39c56881c9ac62e7582db9e3d33a4f", 1, "test.txt" }, - { 0100644, "c3b1fb31424c98072542cc8e42b48c92e52f494a", 2, "test.txt" }, - { 0100644, "262f67de0de2e535a59ae1bc3c739601e98c354d", 3, "test.txt" }, - }; - - cl_git_pass(merge_trees_from_branches(&index, repo, BRANCH_A_EOL, BRANCH_B_EOL, &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 3)); - - git_index_free(index); -} - -void test_merge_trees_whitespace__eol(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ee3c2aac8e03224c323b58ecb1f9eef616745467", 0, "test.txt" }, - }; - - opts.file_flags |= GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL; - - cl_git_pass(merge_trees_from_branches(&index, repo, BRANCH_A_EOL, BRANCH_B_EOL, &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 1)); - - git_index_free(index); -} - -void test_merge_trees_whitespace__change(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "a827eab4fd66ab37a6ebcfaa7b7e341abfd55947", 0, "test.txt" }, - }; - - opts.file_flags |= GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE; - - cl_git_pass(merge_trees_from_branches(&index, repo, BRANCH_A_CHANGE, BRANCH_B_CHANGE, &opts)); - - cl_assert(merge_test_index(index, merge_index_entries, 1)); - - git_index_free(index); -} diff --git a/tests/merge/workdir/dirty.c b/tests/merge/workdir/dirty.c deleted file mode 100644 index b9c2ad033..000000000 --- a/tests/merge/workdir/dirty.c +++ /dev/null @@ -1,351 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/merge.h" -#include "merge.h" -#include "index.h" -#include "../merge_helpers.h" -#include "posix.h" - -#define TEST_REPO_PATH "merge-resolve" -#define MERGE_BRANCH_OID "7cb63eed597130ba4abb87b3e544b85021905520" - -#define AUTOMERGEABLE_MERGED_FILE \ - "this file is changed in master\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is automergeable\n" \ - "this file is changed in branch\n" - -#define CHANGED_IN_BRANCH_FILE \ - "changed in branch\n" - -static git_repository *repo; -static git_index *repo_index; - -static char *unaffected[][4] = { - { "added-in-master.txt", NULL }, - { "changed-in-master.txt", NULL }, - { "unchanged.txt", NULL }, - { "added-in-master.txt", "changed-in-master.txt", NULL }, - { "added-in-master.txt", "unchanged.txt", NULL }, - { "changed-in-master.txt", "unchanged.txt", NULL }, - { "added-in-master.txt", "changed-in-master.txt", "unchanged.txt", NULL }, - { "new_file.txt", NULL }, - { "new_file.txt", "unchanged.txt", NULL }, - { NULL }, -}; - -static char *affected[][5] = { - { "automergeable.txt", NULL }, - { "changed-in-branch.txt", NULL }, - { "conflicting.txt", NULL }, - { "removed-in-branch.txt", NULL }, - { "automergeable.txt", "changed-in-branch.txt", NULL }, - { "automergeable.txt", "conflicting.txt", NULL }, - { "automergeable.txt", "removed-in-branch.txt", NULL }, - { "changed-in-branch.txt", "conflicting.txt", NULL }, - { "changed-in-branch.txt", "removed-in-branch.txt", NULL }, - { "conflicting.txt", "removed-in-branch.txt", NULL }, - { "automergeable.txt", "changed-in-branch.txt", "conflicting.txt", NULL }, - { "automergeable.txt", "changed-in-branch.txt", "removed-in-branch.txt", NULL }, - { "automergeable.txt", "conflicting.txt", "removed-in-branch.txt", NULL }, - { "changed-in-branch.txt", "conflicting.txt", "removed-in-branch.txt", NULL }, - { "automergeable.txt", "changed-in-branch.txt", "conflicting.txt", "removed-in-branch.txt", NULL }, - { NULL }, -}; - -static char *result_contents[4][6] = { - { "automergeable.txt", AUTOMERGEABLE_MERGED_FILE, NULL, NULL }, - { "changed-in-branch.txt", CHANGED_IN_BRANCH_FILE, NULL, NULL }, - { "automergeable.txt", AUTOMERGEABLE_MERGED_FILE, "changed-in-branch.txt", CHANGED_IN_BRANCH_FILE, NULL, NULL }, - { NULL } -}; - -void test_merge_workdir_dirty__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); - git_repository_index(&repo_index, repo); -} - -void test_merge_workdir_dirty__cleanup(void) -{ - git_index_free(repo_index); - cl_git_sandbox_cleanup(); -} - -static void set_core_autocrlf_to(git_repository *repo, bool value) -{ - git_config *cfg; - - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", value)); - - git_config_free(cfg); -} - -static int merge_branch(void) -{ - git_oid their_oids[1]; - git_annotated_commit *their_head; - git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - int error; - - cl_git_pass(git_oid_fromstr(&their_oids[0], MERGE_BRANCH_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_head, repo, &their_oids[0])); - - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - error = git_merge(repo, (const git_annotated_commit **)&their_head, 1, &merge_opts, &checkout_opts); - - git_annotated_commit_free(their_head); - - return error; -} - -static void write_files(char *files[]) -{ - char *filename; - git_str path = GIT_STR_INIT, content = GIT_STR_INIT; - size_t i; - - for (i = 0, filename = files[i]; filename; filename = files[++i]) { - git_str_clear(&path); - git_str_clear(&content); - - git_str_printf(&path, "%s/%s", TEST_REPO_PATH, filename); - git_str_printf(&content, "This is a dirty file in the working directory!\n\n" - "It will not be staged! Its filename is %s.\n", filename); - - cl_git_mkfile(path.ptr, content.ptr); - } - - git_str_dispose(&path); - git_str_dispose(&content); -} - -static void hack_index(char *files[]) -{ - char *filename; - struct stat statbuf; - git_str path = GIT_STR_INIT; - git_index_entry *entry; - struct p_timeval times[2]; - time_t now; - size_t i; - - /* Update the index to suggest that checkout placed these files on - * disk, keeping the object id but updating the cache, which will - * emulate a Git implementation's different filter. - * - * We set the file's timestamp to before now to pretend that - * it was an old checkout so we don't trigger the racy - * protections would would check the content. - */ - - now = time(NULL); - times[0].tv_sec = now - 5; - times[0].tv_usec = 0; - times[1].tv_sec = now - 5; - times[1].tv_usec = 0; - - for (i = 0, filename = files[i]; filename; filename = files[++i]) { - git_str_clear(&path); - - cl_assert(entry = (git_index_entry *) - git_index_get_bypath(repo_index, filename, 0)); - - cl_git_pass(git_str_printf(&path, "%s/%s", TEST_REPO_PATH, filename)); - cl_git_pass(p_utimes(path.ptr, times)); - cl_git_pass(p_stat(path.ptr, &statbuf)); - - entry->ctime.seconds = (int32_t)statbuf.st_ctime; - entry->mtime.seconds = (int32_t)statbuf.st_mtime; -#if defined(GIT_USE_NSEC) - entry->ctime.nanoseconds = statbuf.st_ctime_nsec; - entry->mtime.nanoseconds = statbuf.st_mtime_nsec; -#else - entry->ctime.nanoseconds = 0; - entry->mtime.nanoseconds = 0; -#endif - entry->dev = statbuf.st_dev; - entry->ino = statbuf.st_ino; - entry->uid = statbuf.st_uid; - entry->gid = statbuf.st_gid; - entry->file_size = (uint32_t)statbuf.st_size; - } - - git_str_dispose(&path); -} - -static void stage_random_files(char *files[]) -{ - char *filename; - size_t i; - - write_files(files); - - for (i = 0, filename = files[i]; filename; filename = files[++i]) - cl_git_pass(git_index_add_bypath(repo_index, filename)); -} - -static void stage_content(char *content[]) -{ - git_reference *head; - git_object *head_object; - git_str path = GIT_STR_INIT; - char *filename, *text; - size_t i; - - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT)); - cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); - - for (i = 0, filename = content[i], text = content[++i]; - filename && text; - filename = content[++i], text = content[++i]) { - - git_str_clear(&path); - - cl_git_pass(git_str_printf(&path, "%s/%s", TEST_REPO_PATH, filename)); - - cl_git_mkfile(path.ptr, text); - cl_git_pass(git_index_add_bypath(repo_index, filename)); - } - - git_object_free(head_object); - git_reference_free(head); - git_str_dispose(&path); -} - -static int merge_dirty_files(char *dirty_files[]) -{ - git_reference *head; - git_object *head_object; - int error; - - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT)); - cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); - - write_files(dirty_files); - - error = merge_branch(); - - git_object_free(head_object); - git_reference_free(head); - - return error; -} - -static int merge_differently_filtered_files(char *files[]) -{ - git_reference *head; - git_object *head_object; - int error; - - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT)); - cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); - - /* Emulate checkout with a broken or misconfigured filter: modify some - * files on-disk and then update the index with the updated file size - * and time, as if some filter applied them. These files should not be - * treated as dirty since we created them. - * - * (Make sure to update the index stamp to defeat racy-git protections - * trying to sanity check the files in the index; those would rehash the - * files, showing them as dirty, the exact mechanism we're trying to avoid.) - */ - - write_files(files); - hack_index(files); - - cl_git_pass(git_index_write(repo_index)); - - error = merge_branch(); - - git_object_free(head_object); - git_reference_free(head); - - return error; -} - -static int merge_staged_files(char *staged_files[]) -{ - stage_random_files(staged_files); - return merge_branch(); -} - -void test_merge_workdir_dirty__unaffected_dirty_files_allowed(void) -{ - char **files; - size_t i; - - for (i = 0, files = unaffected[i]; files[0]; files = unaffected[++i]) - cl_git_pass(merge_dirty_files(files)); -} - -void test_merge_workdir_dirty__unstaged_deletes_maintained(void) -{ - git_reference *head; - git_object *head_object; - - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT)); - cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); - - cl_git_pass(p_unlink("merge-resolve/unchanged.txt")); - - cl_git_pass(merge_branch()); - - git_object_free(head_object); - git_reference_free(head); -} - -void test_merge_workdir_dirty__affected_dirty_files_disallowed(void) -{ - char **files; - size_t i; - - for (i = 0, files = affected[i]; files[0]; files = affected[++i]) - cl_git_fail(merge_dirty_files(files)); -} - -void test_merge_workdir_dirty__staged_files_in_index_disallowed(void) -{ - char **files; - size_t i; - - for (i = 0, files = unaffected[i]; files[0]; files = unaffected[++i]) - cl_git_fail(merge_staged_files(files)); - - for (i = 0, files = affected[i]; files[0]; files = affected[++i]) - cl_git_fail(merge_staged_files(files)); -} - -void test_merge_workdir_dirty__identical_staged_files_allowed(void) -{ - char **content; - size_t i; - - set_core_autocrlf_to(repo, false); - - for (i = 0, content = result_contents[i]; content[0]; content = result_contents[++i]) { - stage_content(content); - - cl_git_pass(git_index_write(repo_index)); - cl_git_pass(merge_branch()); - } -} - -void test_merge_workdir_dirty__honors_cache(void) -{ - char **files; - size_t i; - - for (i = 0, files = affected[i]; files[0]; files = affected[++i]) - cl_git_pass(merge_differently_filtered_files(files)); -} diff --git a/tests/merge/workdir/recursive.c b/tests/merge/workdir/recursive.c deleted file mode 100644 index 7669e1b1a..000000000 --- a/tests/merge/workdir/recursive.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" -#include "../merge_helpers.h" -#include "../conflict_data.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-recursive" - -void test_merge_workdir_recursive__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_merge_workdir_recursive__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_merge_workdir_recursive__writes_conflict_with_virtual_base(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - git_str conflicting_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "fa567f568ed72157c0c617438d077695b99d9aac", 1, "veal.txt" }, - { 0100644, "21950d5e4e4d1a871b4dfcf72ecb6b9c162c434e", 2, "veal.txt" }, - { 0100644, "3855170cef875708da06ab9ad7fc6a73b531cda1", 3, "veal.txt" }, - }; - - cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR "branchF-1", GIT_REFS_HEADS_DIR "branchF-2", &opts, NULL)); - - cl_git_pass(git_repository_index(&index, repo)); - cl_assert(merge_test_index(index, merge_index_entries, 8)); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, "merge-recursive/veal.txt")); - - cl_assert_equal_s(CONFLICTING_RECURSIVE_F1_TO_F2, conflicting_buf.ptr); - - git_index_free(index); - git_str_dispose(&conflicting_buf); -} - -void test_merge_workdir_recursive__conflicting_merge_base_with_diff3(void) -{ - git_index *index; - git_merge_options opts = GIT_MERGE_OPTIONS_INIT; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - git_str conflicting_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, - { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, - { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, - { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, - { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, - { 0100644, "0b01d2f70a1c6b9ab60c382f3f9cdc8173da6736", 1, "veal.txt" }, - { 0100644, "37a5054a9f9b4628e3924c5cb8f2147c6e2a3efc", 2, "veal.txt" }, - { 0100644, "d604c75019c282144bdbbf3fd3462ba74b240efc", 3, "veal.txt" }, - }; - - opts.file_flags |= GIT_MERGE_FILE_STYLE_DIFF3; - checkout_opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3; - - cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR "branchH-2", GIT_REFS_HEADS_DIR "branchH-1", &opts, &checkout_opts)); - - cl_git_pass(git_repository_index(&index, repo)); - cl_assert(merge_test_index(index, merge_index_entries, 8)); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, "merge-recursive/veal.txt")); - - cl_assert_equal_s(CONFLICTING_RECURSIVE_H2_TO_H1_WITH_DIFF3, conflicting_buf.ptr); - - git_index_free(index); - git_str_dispose(&conflicting_buf); -} diff --git a/tests/merge/workdir/renames.c b/tests/merge/workdir/renames.c deleted file mode 100644 index 1b5128cf1..000000000 --- a/tests/merge/workdir/renames.c +++ /dev/null @@ -1,155 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" -#include "../merge_helpers.h" -#include "futils.h" -#include "refs.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-resolve" - -#define BRANCH_RENAME_OURS "rename_conflict_ours" -#define BRANCH_RENAME_THEIRS "rename_conflict_theirs" - -/* Fixture setup and teardown */ -void test_merge_workdir_renames__initialize(void) -{ - git_config *cfg; - - repo = cl_git_sandbox_init(TEST_REPO_PATH); - - /* Ensure that the user's merge.conflictstyle doesn't interfere */ - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge")); - git_config_free(cfg); -} - -void test_merge_workdir_renames__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_merge_workdir_renames__renames(void) -{ - git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, - { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, - { 0100644, "8aac75de2a34b4d340bf62a6e58197269cb55797", 0, "0b-rewritten-in-ours.txt" }, - { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, - { 0100644, "7edc726325da726751a4195e434e4377b0f67f9a", 0, "0c-rewritten-in-theirs.txt" }, - { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, - { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, - { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, - { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, - { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, - { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, - { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, - { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt~HEAD" }, - { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 0, "4a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" }, - { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt~HEAD" }, - { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" }, - { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 0, "5a-newname-in-ours-added-in-theirs.txt~HEAD" }, - { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 0, "5a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" }, - { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 0, "5b-newname-in-theirs-added-in-ours.txt~HEAD" }, - { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 0, "5b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-theirs.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt~HEAD" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" }, - }; - - merge_opts.flags |= GIT_MERGE_FIND_RENAMES; - merge_opts.rename_threshold = 50; - - cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, NULL)); - cl_assert(merge_test_workdir(repo, merge_index_entries, 24)); -} - -void test_merge_workdir_renames__ours(void) -{ - git_index *index; - git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, - { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, - { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 0, "0b-rewritten-in-ours.txt" }, - { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, - { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 0, "0c-rewritten-in-theirs.txt" }, - { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, - { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, - { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, - { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, - { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, - { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, - { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, - { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 0, "5a-newname-in-ours-added-in-theirs.txt" }, - { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 0, "5b-newname-in-theirs-added-in-ours.txt" }, - { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 0, "5b-renamed-in-theirs-added-in-ours.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed-side-2.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt" }, - }; - - merge_opts.flags |= GIT_MERGE_FIND_RENAMES; - merge_opts.rename_threshold = 50; - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; - - cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, &checkout_opts)); - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_write(index)); - cl_assert(merge_test_workdir(repo, merge_index_entries, 20)); - - git_index_free(index); -} - -void test_merge_workdir_renames__similar(void) -{ - git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; - - /* - * Note: this differs slightly from the core git merge result - there, 4a is - * tracked as a rename/delete instead of a rename/add and the theirs side - * is not placed in workdir in any form. - */ - struct merge_index_entry merge_index_entries[] = { - { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" }, - { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, - { 0100644, "8aac75de2a34b4d340bf62a6e58197269cb55797", 0, "0b-rewritten-in-ours.txt" }, - { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, - { 0100644, "7edc726325da726751a4195e434e4377b0f67f9a", 0, "0c-rewritten-in-theirs.txt" }, - { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, - { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, - { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, - { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, - { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, - { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, - { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, - { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt~HEAD" }, - { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 0, "4a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" }, - { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt~HEAD" }, - { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" }, - { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 0, "5a-newname-in-ours-added-in-theirs.txt~HEAD" }, - { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 0, "5a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" }, - { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 0, "5b-newname-in-theirs-added-in-ours.txt~HEAD" }, - { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 0, "5b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" }, - { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-theirs.txt" }, - { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt~HEAD" }, - { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" }, - }; - - merge_opts.flags |= GIT_MERGE_FIND_RENAMES; - merge_opts.rename_threshold = 50; - - cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, NULL)); - cl_assert(merge_test_workdir(repo, merge_index_entries, 24)); -} - diff --git a/tests/merge/workdir/setup.c b/tests/merge/workdir/setup.c deleted file mode 100644 index 3db2d074f..000000000 --- a/tests/merge/workdir/setup.c +++ /dev/null @@ -1,1096 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" -#include "refs.h" -#include "futils.h" - -static git_repository *repo; -static git_index *repo_index; - -#define TEST_REPO_PATH "merge-resolve" -#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" - -#define ORIG_HEAD "bd593285fc7fe4ca18ccdbabf027f5d689101452" - -#define THEIRS_SIMPLE_BRANCH "branch" -#define THEIRS_SIMPLE_OID "7cb63eed597130ba4abb87b3e544b85021905520" - -#define OCTO1_BRANCH "octo1" -#define OCTO1_OID "16f825815cfd20a07a75c71554e82d8eede0b061" - -#define OCTO2_BRANCH "octo2" -#define OCTO2_OID "158dc7bedb202f5b26502bf3574faa7f4238d56c" - -#define OCTO3_BRANCH "octo3" -#define OCTO3_OID "50ce7d7d01217679e26c55939eef119e0c93e272" - -#define OCTO4_BRANCH "octo4" -#define OCTO4_OID "54269b3f6ec3d7d4ede24dd350dd5d605495c3ae" - -#define OCTO5_BRANCH "octo5" -#define OCTO5_OID "e4f618a2c3ed0669308735727df5ebf2447f022f" - -/* Fixture setup and teardown */ -void test_merge_workdir_setup__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); - git_repository_index(&repo_index, repo); -} - -void test_merge_workdir_setup__cleanup(void) -{ - git_index_free(repo_index); - cl_git_sandbox_cleanup(); -} - -static bool test_file_contents(const char *filename, const char *expected) -{ - git_str file_path_buf = GIT_STR_INIT, file_buf = GIT_STR_INIT; - bool equals; - - git_str_joinpath(&file_path_buf, git_repository_path(repo), filename); - - cl_git_pass(git_futils_readbuffer(&file_buf, file_path_buf.ptr)); - equals = (strcmp(file_buf.ptr, expected) == 0); - - git_str_dispose(&file_path_buf); - git_str_dispose(&file_buf); - - return equals; -} - -static void write_file_contents(const char *filename, const char *output) -{ - git_str file_path_buf = GIT_STR_INIT; - - git_str_joinpath(&file_path_buf, git_repository_path(repo), - filename); - cl_git_rewritefile(file_path_buf.ptr, output); - - git_str_dispose(&file_path_buf); -} - -/* git merge --no-ff octo1 */ -void test_merge_workdir_setup__one_branch(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_annotated_commit *our_head, *their_heads[1]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 1)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'\n")); - - git_reference_free(octo1_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); -} - -/* git merge --no-ff 16f825815cfd20a07a75c71554e82d8eede0b061 */ -void test_merge_workdir_setup__one_oid(void) -{ - git_oid our_oid; - git_oid octo1_oid; - git_annotated_commit *our_head, *their_heads[1]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &octo1_oid)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 1)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'\n")); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); -} - -/* git merge octo1 octo2 */ -void test_merge_workdir_setup__two_branches(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_reference *octo2_ref; - git_annotated_commit *our_head, *their_heads[2]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 2)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO2_BRANCH "'\n")); - - git_reference_free(octo1_ref); - git_reference_free(octo2_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); -} - -/* git merge octo1 octo2 octo3 */ -void test_merge_workdir_setup__three_branches(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_reference *octo2_ref; - git_reference *octo3_ref; - git_annotated_commit *our_head, *their_heads[3]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); - - cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[2], repo, octo3_ref)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO2_BRANCH "' and '" OCTO3_BRANCH "'\n")); - - git_reference_free(octo1_ref); - git_reference_free(octo2_ref); - git_reference_free(octo3_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); - git_annotated_commit_free(their_heads[2]); -} - -/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 158dc7bedb202f5b26502bf3574faa7f4238d56c 50ce7d7d01217679e26c55939eef119e0c93e272 */ -void test_merge_workdir_setup__three_oids(void) -{ - git_oid our_oid; - git_oid octo1_oid; - git_oid octo2_oid; - git_oid octo3_oid; - git_annotated_commit *our_head, *their_heads[3]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &octo1_oid)); - - cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[1], repo, &octo2_oid)); - - cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[2], repo, &octo3_oid)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; commit '" OCTO2_OID "'; commit '" OCTO3_OID "'\n")); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); - git_annotated_commit_free(their_heads[2]); -} - -/* git merge octo1 158dc7bedb202f5b26502bf3574faa7f4238d56c */ -void test_merge_workdir_setup__branches_and_oids_1(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_oid octo2_oid; - git_annotated_commit *our_head, *their_heads[2]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[1], repo, &octo2_oid)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 2)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'; commit '" OCTO2_OID "'\n")); - - git_reference_free(octo1_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); -} - -/* git merge octo1 158dc7bedb202f5b26502bf3574faa7f4238d56c octo3 54269b3f6ec3d7d4ede24dd350dd5d605495c3ae */ -void test_merge_workdir_setup__branches_and_oids_2(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_oid octo2_oid; - git_reference *octo3_ref; - git_oid octo4_oid; - git_annotated_commit *our_head, *their_heads[4]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[1], repo, &octo2_oid)); - - cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[2], repo, octo3_ref)); - - cl_git_pass(git_oid_fromstr(&octo4_oid, OCTO4_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[3], repo, &octo4_oid)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 4)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "'; commit '" OCTO2_OID "'; commit '" OCTO4_OID "'\n")); - - git_reference_free(octo1_ref); - git_reference_free(octo3_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); - git_annotated_commit_free(their_heads[2]); - git_annotated_commit_free(their_heads[3]); -} - -/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 octo2 50ce7d7d01217679e26c55939eef119e0c93e272 octo4 */ -void test_merge_workdir_setup__branches_and_oids_3(void) -{ - git_oid our_oid; - git_oid octo1_oid; - git_reference *octo2_ref; - git_oid octo3_oid; - git_reference *octo4_ref; - git_annotated_commit *our_head, *their_heads[4]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &octo1_oid)); - - cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); - - cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[2], repo, &octo3_oid)); - - cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[3], repo, octo4_ref)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 4)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; branches '" OCTO2_BRANCH "' and '" OCTO4_BRANCH "'; commit '" OCTO3_OID "'\n")); - - git_reference_free(octo2_ref); - git_reference_free(octo4_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); - git_annotated_commit_free(their_heads[2]); - git_annotated_commit_free(their_heads[3]); -} - -/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 octo2 50ce7d7d01217679e26c55939eef119e0c93e272 octo4 octo5 */ -void test_merge_workdir_setup__branches_and_oids_4(void) -{ - git_oid our_oid; - git_oid octo1_oid; - git_reference *octo2_ref; - git_oid octo3_oid; - git_reference *octo4_ref; - git_reference *octo5_ref; - git_annotated_commit *our_head, *their_heads[5]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &octo1_oid)); - - cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); - - cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[2], repo, &octo3_oid)); - - cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[3], repo, octo4_ref)); - - cl_git_pass(git_reference_lookup(&octo5_ref, repo, GIT_REFS_HEADS_DIR OCTO5_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[4], repo, octo5_ref)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 5)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n" OCTO5_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; branches '" OCTO2_BRANCH "', '" OCTO4_BRANCH "' and '" OCTO5_BRANCH "'; commit '" OCTO3_OID "'\n")); - - git_reference_free(octo2_ref); - git_reference_free(octo4_ref); - git_reference_free(octo5_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); - git_annotated_commit_free(their_heads[2]); - git_annotated_commit_free(their_heads[3]); - git_annotated_commit_free(their_heads[4]); -} - -/* git merge octo1 octo1 octo1 */ -void test_merge_workdir_setup__three_same_branches(void) -{ - git_oid our_oid; - git_reference *octo1_1_ref; - git_reference *octo1_2_ref; - git_reference *octo1_3_ref; - git_annotated_commit *our_head, *their_heads[3]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_1_ref)); - - cl_git_pass(git_reference_lookup(&octo1_2_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo1_2_ref)); - - cl_git_pass(git_reference_lookup(&octo1_3_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[2], repo, octo1_3_ref)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO1_OID "\n" OCTO1_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO1_BRANCH "' and '" OCTO1_BRANCH "'\n")); - - git_reference_free(octo1_1_ref); - git_reference_free(octo1_2_ref); - git_reference_free(octo1_3_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); - git_annotated_commit_free(their_heads[2]); -} - -/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 16f825815cfd20a07a75c71554e82d8eede0b061 16f825815cfd20a07a75c71554e82d8eede0b061 */ -void test_merge_workdir_setup__three_same_oids(void) -{ - git_oid our_oid; - git_oid octo1_1_oid; - git_oid octo1_2_oid; - git_oid octo1_3_oid; - git_annotated_commit *our_head, *their_heads[3]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_oid_fromstr(&octo1_1_oid, OCTO1_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &octo1_1_oid)); - - cl_git_pass(git_oid_fromstr(&octo1_2_oid, OCTO1_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[1], repo, &octo1_2_oid)); - - cl_git_pass(git_oid_fromstr(&octo1_3_oid, OCTO1_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[2], repo, &octo1_3_oid)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO1_OID "\n" OCTO1_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; commit '" OCTO1_OID "'; commit '" OCTO1_OID "'\n")); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); - git_annotated_commit_free(their_heads[2]); -} - -static int create_remote_tracking_branch(const char *branch_name, const char *oid_str) -{ - int error = 0; - - git_str remotes_path = GIT_STR_INIT, - origin_path = GIT_STR_INIT, - filename = GIT_STR_INIT, - data = GIT_STR_INIT; - - if ((error = git_str_puts(&remotes_path, git_repository_path(repo))) < 0 || - (error = git_str_puts(&remotes_path, GIT_REFS_REMOTES_DIR)) < 0) - goto done; - - if (!git_fs_path_exists(git_str_cstr(&remotes_path)) && - (error = p_mkdir(git_str_cstr(&remotes_path), 0777)) < 0) - goto done; - - if ((error = git_str_puts(&origin_path, git_str_cstr(&remotes_path))) < 0 || - (error = git_str_puts(&origin_path, "origin")) < 0) - goto done; - - if (!git_fs_path_exists(git_str_cstr(&origin_path)) && - (error = p_mkdir(git_str_cstr(&origin_path), 0777)) < 0) - goto done; - - if ((error = git_str_puts(&filename, git_str_cstr(&origin_path))) < 0 || - (error = git_str_puts(&filename, "/")) < 0 || - (error = git_str_puts(&filename, branch_name)) < 0 || - (error = git_str_puts(&data, oid_str)) < 0 || - (error = git_str_puts(&data, "\n")) < 0) - goto done; - - cl_git_rewritefile(git_str_cstr(&filename), git_str_cstr(&data)); - -done: - git_str_dispose(&remotes_path); - git_str_dispose(&origin_path); - git_str_dispose(&filename); - git_str_dispose(&data); - - return error; -} - -/* git merge refs/remotes/origin/octo1 */ -void test_merge_workdir_setup__remote_tracking_one_branch(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_annotated_commit *our_head, *their_heads[1]; - - cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID)); - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 1)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branch 'refs/remotes/origin/" OCTO1_BRANCH "'\n")); - - git_reference_free(octo1_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); -} - -/* git merge refs/remotes/origin/octo1 refs/remotes/origin/octo2 */ -void test_merge_workdir_setup__remote_tracking_two_branches(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_reference *octo2_ref; - git_annotated_commit *our_head, *their_heads[2]; - - cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID)); - cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID)); - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 2)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branches 'refs/remotes/origin/" OCTO1_BRANCH "' and 'refs/remotes/origin/" OCTO2_BRANCH "'\n")); - - git_reference_free(octo1_ref); - git_reference_free(octo2_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); -} - -/* git merge refs/remotes/origin/octo1 refs/remotes/origin/octo2 refs/remotes/origin/octo3 */ -void test_merge_workdir_setup__remote_tracking_three_branches(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_reference *octo2_ref; - git_reference *octo3_ref; - git_annotated_commit *our_head, *their_heads[3]; - - cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID)); - cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID)); - cl_git_pass(create_remote_tracking_branch(OCTO3_BRANCH, OCTO3_OID)); - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); - - cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO3_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[2], repo, octo3_ref)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branches 'refs/remotes/origin/" OCTO1_BRANCH "', 'refs/remotes/origin/" OCTO2_BRANCH "' and 'refs/remotes/origin/" OCTO3_BRANCH "'\n")); - - git_reference_free(octo1_ref); - git_reference_free(octo2_ref); - git_reference_free(octo3_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); - git_annotated_commit_free(their_heads[2]); -} - -/* git merge octo1 refs/remotes/origin/octo2 */ -void test_merge_workdir_setup__normal_branch_and_remote_tracking_branch(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_reference *octo2_ref; - git_annotated_commit *our_head, *their_heads[2]; - - cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID)); - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 2)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "', remote-tracking branch 'refs/remotes/origin/" OCTO2_BRANCH "'\n")); - - git_reference_free(octo1_ref); - git_reference_free(octo2_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); -} - -/* git merge refs/remotes/origin/octo1 octo2 */ -void test_merge_workdir_setup__remote_tracking_branch_and_normal_branch(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_reference *octo2_ref; - git_annotated_commit *our_head, *their_heads[2]; - - cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID)); - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 2)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO2_BRANCH "', remote-tracking branch 'refs/remotes/origin/" OCTO1_BRANCH "'\n")); - - git_reference_free(octo1_ref); - git_reference_free(octo2_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); -} - -/* git merge octo1 refs/remotes/origin/octo2 octo3 refs/remotes/origin/octo4 */ -void test_merge_workdir_setup__two_remote_tracking_branch_and_two_normal_branches(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_reference *octo2_ref; - git_reference *octo3_ref; - git_reference *octo4_ref; - git_annotated_commit *our_head, *their_heads[4]; - - cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID)); - cl_git_pass(create_remote_tracking_branch(OCTO4_BRANCH, OCTO4_OID)); - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[1], repo, octo2_ref)); - - cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[2], repo, octo3_ref)); - - cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO4_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[3], repo, octo4_ref)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 4)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "', remote-tracking branches 'refs/remotes/origin/" OCTO2_BRANCH "' and 'refs/remotes/origin/" OCTO4_BRANCH "'\n")); - - git_reference_free(octo1_ref); - git_reference_free(octo2_ref); - git_reference_free(octo3_ref); - git_reference_free(octo4_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); - git_annotated_commit_free(their_heads[2]); - git_annotated_commit_free(their_heads[3]); -} - -/* git pull origin branch octo1 */ -void test_merge_workdir_setup__pull_one(void) -{ - git_oid our_oid; - git_oid octo1_1_oid; - git_annotated_commit *our_head, *their_heads[1]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_oid_fromstr(&octo1_1_oid, OCTO1_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_1_oid)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 1)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch 'octo1' of http://remote.url/repo.git\n")); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); -} - -/* git pull origin octo1 octo2 */ -void test_merge_workdir_setup__pull_two(void) -{ - git_oid our_oid; - git_oid octo1_oid; - git_oid octo2_oid; - git_annotated_commit *our_head, *their_heads[2]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_oid)); - - cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.url/repo.git", &octo2_oid)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 2)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO2_BRANCH "' of http://remote.url/repo.git\n")); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); -} - -/* git pull origin octo1 octo2 octo3 */ -void test_merge_workdir_setup__pull_three(void) -{ - git_oid our_oid; - git_oid octo1_oid; - git_oid octo2_oid; - git_oid octo3_oid; - git_annotated_commit *our_head, *their_heads[3]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_oid)); - - cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.url/repo.git", &octo2_oid)); - - cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.url/repo.git", &octo3_oid)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO2_BRANCH "' and '" OCTO3_BRANCH "' of http://remote.url/repo.git\n")); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); - git_annotated_commit_free(their_heads[2]); -} - -void test_merge_workdir_setup__three_remotes(void) -{ - git_oid our_oid; - git_oid octo1_oid; - git_oid octo2_oid; - git_oid octo3_oid; - git_annotated_commit *our_head, *their_heads[3]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.first/repo.git", &octo1_oid)); - - cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.second/repo.git", &octo2_oid)); - - cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.third/repo.git", &octo3_oid)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 3)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "' of http://remote.first/repo.git, branch '" OCTO2_BRANCH "' of http://remote.second/repo.git, branch '" OCTO3_BRANCH "' of http://remote.third/repo.git\n")); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); - git_annotated_commit_free(their_heads[2]); -} - -void test_merge_workdir_setup__two_remotes(void) -{ - git_oid our_oid; - git_oid octo1_oid; - git_oid octo2_oid; - git_oid octo3_oid; - git_oid octo4_oid; - git_annotated_commit *our_head, *their_heads[4]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.first/repo.git", &octo1_oid)); - - cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.second/repo.git", &octo2_oid)); - - cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.first/repo.git", &octo3_oid)); - - cl_git_pass(git_oid_fromstr(&octo4_oid, OCTO4_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&their_heads[3], repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH, "http://remote.second/repo.git", &octo4_oid)); - - cl_git_pass(git_merge__setup(repo, our_head, (const git_annotated_commit **)their_heads, 4)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "' of http://remote.first/repo.git, branches '" OCTO2_BRANCH "' and '" OCTO4_BRANCH "' of http://remote.second/repo.git\n")); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); - git_annotated_commit_free(their_heads[1]); - git_annotated_commit_free(their_heads[2]); - git_annotated_commit_free(their_heads[3]); -} - -void test_merge_workdir_setup__id_from_head(void) -{ - git_oid expected_id; - const git_oid *id; - git_reference *ref; - git_annotated_commit *heads[3]; - - cl_git_pass(git_oid_fromstr(&expected_id, OCTO1_OID)); - cl_git_pass(git_annotated_commit_from_fetchhead(&heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &expected_id)); - id = git_annotated_commit_id(heads[0]); - cl_assert_equal_i(1, git_oid_equal(id, &expected_id)); - - cl_git_pass(git_annotated_commit_lookup(&heads[1], repo, &expected_id)); - id = git_annotated_commit_id(heads[1]); - cl_assert_equal_i(1, git_oid_equal(id, &expected_id)); - - cl_git_pass(git_reference_lookup(&ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&heads[2], repo, ref)); - id = git_annotated_commit_id(heads[2]); - cl_assert_equal_i(1, git_oid_equal(id, &expected_id)); - - git_reference_free(ref); - git_annotated_commit_free(heads[0]); - git_annotated_commit_free(heads[1]); - git_annotated_commit_free(heads[2]); -} - -struct annotated_commit_cb_data { - const char **oid_str; - unsigned int len; - - unsigned int i; -}; - -static int annotated_commit_foreach_cb(const git_oid *oid, void *payload) -{ - git_oid expected_oid; - struct annotated_commit_cb_data *cb_data = payload; - - git_oid_fromstr(&expected_oid, cb_data->oid_str[cb_data->i]); - cl_assert(git_oid_cmp(&expected_oid, oid) == 0); - cb_data->i++; - return 0; -} - -void test_merge_workdir_setup__head_notfound(void) -{ - int error; - - cl_git_fail((error = git_repository_mergehead_foreach(repo, - annotated_commit_foreach_cb, NULL))); - cl_assert(error == GIT_ENOTFOUND); -} - -void test_merge_workdir_setup__head_invalid_oid(void) -{ - int error; - - write_file_contents(GIT_MERGE_HEAD_FILE, "invalid-oid\n"); - - cl_git_fail((error = git_repository_mergehead_foreach(repo, - annotated_commit_foreach_cb, NULL))); - cl_assert(error == -1); -} - -void test_merge_workdir_setup__head_foreach_nonewline(void) -{ - int error; - - write_file_contents(GIT_MERGE_HEAD_FILE, THEIRS_SIMPLE_OID); - - cl_git_fail((error = git_repository_mergehead_foreach(repo, - annotated_commit_foreach_cb, NULL))); - cl_assert(error == -1); -} - -void test_merge_workdir_setup__head_foreach_one(void) -{ - const char *expected = THEIRS_SIMPLE_OID; - - struct annotated_commit_cb_data cb_data = { &expected, 1 }; - - write_file_contents(GIT_MERGE_HEAD_FILE, THEIRS_SIMPLE_OID "\n"); - - cl_git_pass(git_repository_mergehead_foreach(repo, - annotated_commit_foreach_cb, &cb_data)); - - cl_assert(cb_data.i == cb_data.len); -} - -void test_merge_workdir_setup__head_foreach_octopus(void) -{ - const char *expected[] = { THEIRS_SIMPLE_OID, - OCTO1_OID, OCTO2_OID, OCTO3_OID, OCTO4_OID, OCTO5_OID }; - - struct annotated_commit_cb_data cb_data = { expected, 6 }; - - write_file_contents(GIT_MERGE_HEAD_FILE, - THEIRS_SIMPLE_OID "\n" - OCTO1_OID "\n" - OCTO2_OID "\n" - OCTO3_OID "\n" - OCTO4_OID "\n" - OCTO5_OID "\n"); - - cl_git_pass(git_repository_mergehead_foreach(repo, - annotated_commit_foreach_cb, &cb_data)); - - cl_assert(cb_data.i == cb_data.len); -} - -void test_merge_workdir_setup__retained_after_success(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_annotated_commit *our_head, *their_heads[1]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_heads[0], 1, NULL, NULL)); - - cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n")); - cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n")); - cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff")); - cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'\n")); - - git_reference_free(octo1_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); -} - - -void test_merge_workdir_setup__removed_after_failure(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_annotated_commit *our_head, *their_heads[1]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_write2file("merge-resolve/.git/index.lock", "foo\n", 4, O_RDWR|O_CREAT, 0666); - - cl_git_fail(git_merge( - repo, (const git_annotated_commit **)&their_heads[0], 1, NULL, NULL)); - - cl_assert(!git_fs_path_exists("merge-resolve/.git/" GIT_MERGE_HEAD_FILE)); - cl_assert(!git_fs_path_exists("merge-resolve/.git/" GIT_MERGE_MODE_FILE)); - cl_assert(!git_fs_path_exists("merge-resolve/.git/" GIT_MERGE_MSG_FILE)); - - git_reference_free(octo1_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); -} - -void test_merge_workdir_setup__unlocked_after_success(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_annotated_commit *our_head, *their_heads[1]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_pass(git_merge( - repo, (const git_annotated_commit **)&their_heads[0], 1, NULL, NULL)); - - cl_assert(!git_fs_path_exists("merge-resolve/.git/index.lock")); - - git_reference_free(octo1_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); -} - -void test_merge_workdir_setup__unlocked_after_conflict(void) -{ - git_oid our_oid; - git_reference *octo1_ref; - git_annotated_commit *our_head, *their_heads[1]; - - cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD)); - cl_git_pass(git_annotated_commit_lookup(&our_head, repo, &our_oid)); - - cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, octo1_ref)); - - cl_git_rewritefile("merge-resolve/new-in-octo1.txt", - "Conflicting file!\n\nMerge will fail!\n"); - - cl_git_fail(git_merge( - repo, (const git_annotated_commit **)&their_heads[0], 1, NULL, NULL)); - - cl_assert(!git_fs_path_exists("merge-resolve/.git/index.lock")); - - git_reference_free(octo1_ref); - - git_annotated_commit_free(our_head); - git_annotated_commit_free(their_heads[0]); -} diff --git a/tests/merge/workdir/simple.c b/tests/merge/workdir/simple.c deleted file mode 100644 index b9d3fc24c..000000000 --- a/tests/merge/workdir/simple.c +++ /dev/null @@ -1,778 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" -#include "../merge_helpers.h" -#include "../conflict_data.h" -#include "refs.h" -#include "futils.h" - -static git_repository *repo; -static git_index *repo_index; - -#define TEST_REPO_PATH "merge-resolve" -#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" - -#define THEIRS_SIMPLE_BRANCH "branch" -#define THEIRS_SIMPLE_OID "7cb63eed597130ba4abb87b3e544b85021905520" - -#define THEIRS_UNRELATED_BRANCH "unrelated" -#define THEIRS_UNRELATED_OID "55b4e4687e7a0d9ca367016ed930f385d4022e6f" -#define THEIRS_UNRELATED_PARENT "d6cf6c7741b3316826af1314042550c97ded1d50" - -#define OURS_DIRECTORY_FILE "df_side1" -#define THEIRS_DIRECTORY_FILE "fc90237dc4891fa6c69827fc465632225e391618" - - -/* Non-conflicting files, index entries are common to every merge operation */ -#define ADDED_IN_MASTER_INDEX_ENTRY \ - { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, \ - "added-in-master.txt" } -#define AUTOMERGEABLE_INDEX_ENTRY \ - { 0100644, "f2e1550a0c9e53d5811175864a29536642ae3821", 0, \ - "automergeable.txt" } -#define CHANGED_IN_BRANCH_INDEX_ENTRY \ - { 0100644, "4eb04c9e79e88f6640d01ff5b25ca2a60764f216", 0, \ - "changed-in-branch.txt" } -#define CHANGED_IN_MASTER_INDEX_ENTRY \ - { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, \ - "changed-in-master.txt" } -#define UNCHANGED_INDEX_ENTRY \ - { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, \ - "unchanged.txt" } - -/* Unrelated files */ -#define UNRELATED_NEW1 \ - { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, \ - "new-in-unrelated1.txt" } -#define UNRELATED_NEW2 \ - { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, \ - "new-in-unrelated2.txt" } - -/* Expected REUC entries */ -#define AUTOMERGEABLE_REUC_ENTRY \ - { "automergeable.txt", 0100644, 0100644, 0100644, \ - "6212c31dab5e482247d7977e4f0dd3601decf13b", \ - "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", \ - "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe" } -#define CONFLICTING_REUC_ENTRY \ - { "conflicting.txt", 0100644, 0100644, 0100644, \ - "d427e0b2e138501a3d15cc376077a3631e15bd46", \ - "4e886e602529caa9ab11d71f86634bd1b6e0de10", \ - "2bd0a343aeef7a2cf0d158478966a6e587ff3863" } -#define REMOVED_IN_BRANCH_REUC_ENTRY \ - { "removed-in-branch.txt", 0100644, 0100644, 0, \ - "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ - "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \ - "" } -#define REMOVED_IN_MASTER_REUC_ENTRY \ - { "removed-in-master.txt", 0100644, 0, 0100644, \ - "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", \ - "", \ - "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5" } - - -/* Fixture setup and teardown */ -void test_merge_workdir_simple__initialize(void) -{ - git_config *cfg; - - repo = cl_git_sandbox_init(TEST_REPO_PATH); - git_repository_index(&repo_index, repo); - - /* Ensure that the user's merge.conflictstyle doesn't interfere */ - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge")); - git_config_free(cfg); -} - -void test_merge_workdir_simple__cleanup(void) -{ - git_index_free(repo_index); - cl_git_sandbox_cleanup(); -} - -static void merge_simple_branch(int merge_file_favor, int addl_checkout_strategy) -{ - git_oid their_oids[1]; - git_annotated_commit *their_heads[1]; - git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - - cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_SIMPLE_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &their_oids[0])); - - merge_opts.file_favor = merge_file_favor; - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS | - addl_checkout_strategy; - - cl_git_pass(git_merge(repo, (const git_annotated_commit **)their_heads, 1, &merge_opts, &checkout_opts)); - - git_annotated_commit_free(their_heads[0]); -} - -static void set_core_autocrlf_to(git_repository *repo, bool value) -{ - git_config *cfg; - - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", value)); - - git_config_free(cfg); -} - -void test_merge_workdir_simple__automerge(void) -{ - git_index *index; - const git_index_entry *entry; - git_str automergeable_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - - { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, - - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY - }; - - - set_core_autocrlf_to(repo, false); - - merge_simple_branch(0, 0); - - cl_git_pass(git_futils_readbuffer(&automergeable_buf, - TEST_REPO_PATH "/automergeable.txt")); - cl_assert(strcmp(automergeable_buf.ptr, AUTOMERGEABLE_MERGED_FILE) == 0); - git_str_dispose(&automergeable_buf); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); - cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); - - git_repository_index(&index, repo); - - cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL); - cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE)); - - git_index_free(index); -} - -void test_merge_workdir_simple__index_reload(void) -{ - git_repository *tmp_repo; - git_annotated_commit *their_heads[1]; - git_oid their_oid; - git_index_entry entry = {{0}}; - git_index *tmp_index; - - cl_git_pass(git_repository_open(&tmp_repo, git_repository_workdir(repo))); - cl_git_pass(git_repository_index(&tmp_index, tmp_repo)); - cl_git_pass(git_index_read(repo_index, 0)); - - entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_oid_fromstr(&entry.id, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2")); - entry.path = "automergeable.txt"; - cl_git_pass(git_index_add(repo_index, &entry)); - - cl_git_pass(git_index_add_bypath(tmp_index, "automergeable.txt")); - cl_git_pass(git_index_write(tmp_index)); - - cl_git_pass(git_oid_fromstr(&their_oid, THEIRS_SIMPLE_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &their_oid)); - cl_git_pass(git_merge(repo, (const git_annotated_commit **)their_heads, 1, NULL, NULL)); - - git_index_free(tmp_index); - git_repository_free(tmp_repo); - git_annotated_commit_free(their_heads[0]); -} - -void test_merge_workdir_simple__automerge_crlf(void) -{ -#ifdef GIT_WIN32 - git_index *index; - const git_index_entry *entry; - git_str automergeable_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - - { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, - - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY - }; - - set_core_autocrlf_to(repo, true); - - merge_simple_branch(0, 0); - - cl_git_pass(git_futils_readbuffer(&automergeable_buf, - TEST_REPO_PATH "/automergeable.txt")); - cl_assert(strcmp(automergeable_buf.ptr, AUTOMERGEABLE_MERGED_FILE_CRLF) == 0); - git_str_dispose(&automergeable_buf); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); - cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); - - git_repository_index(&index, repo); - - cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL); - cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE_CRLF)); - - git_index_free(index); -#endif /* GIT_WIN32 */ -} - -void test_merge_workdir_simple__mergefile(void) -{ - git_str conflicting_buf = GIT_STR_INIT, mergemsg_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - - { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, - - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY - }; - - set_core_autocrlf_to(repo, false); - - merge_simple_branch(0, 0); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, - TEST_REPO_PATH "/conflicting.txt")); - cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_MERGE_FILE) == 0); - cl_git_pass(git_futils_readbuffer(&mergemsg_buf, - TEST_REPO_PATH "/.git/MERGE_MSG")); - cl_assert(strcmp(git_str_cstr(&mergemsg_buf), - "Merge commit '7cb63eed597130ba4abb87b3e544b85021905520'\n" \ - "\n" \ - "#Conflicts:\n" \ - "#\tconflicting.txt\n") == 0); - git_str_dispose(&conflicting_buf); - git_str_dispose(&mergemsg_buf); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); - cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); -} - -void test_merge_workdir_simple__diff3(void) -{ - git_str conflicting_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - - { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, - - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY - }; - - set_core_autocrlf_to(repo, false); - - merge_simple_branch(0, GIT_CHECKOUT_CONFLICT_STYLE_DIFF3); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, - TEST_REPO_PATH "/conflicting.txt")); - cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_DIFF3_FILE) == 0); - git_str_dispose(&conflicting_buf); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); - cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); -} - -void test_merge_workdir_simple__zdiff3(void) -{ - git_str conflicting_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - - { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, - - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY - }; - - set_core_autocrlf_to(repo, false); - - merge_simple_branch(0, GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, - TEST_REPO_PATH "/conflicting.txt")); - cl_assert_equal_s(CONFLICTING_ZDIFF3_FILE, conflicting_buf.ptr); - git_str_dispose(&conflicting_buf); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); - cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); -} - -void test_merge_workdir_simple__union(void) -{ - git_str conflicting_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - - { 0100644, "72cdb057b340205164478565e91eb71647e66891", 0, "conflicting.txt" }, - - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - CONFLICTING_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY - }; - - set_core_autocrlf_to(repo, false); - - merge_simple_branch(GIT_MERGE_FILE_FAVOR_UNION, 0); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, - TEST_REPO_PATH "/conflicting.txt")); - cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_UNION_FILE) == 0); - git_str_dispose(&conflicting_buf); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); - cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4)); -} - -void test_merge_workdir_simple__gitattributes_union(void) -{ - git_str conflicting_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - - { 0100644, "72cdb057b340205164478565e91eb71647e66891", 0, "conflicting.txt" }, - - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - CONFLICTING_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY - }; - - set_core_autocrlf_to(repo, false); - cl_git_mkfile(TEST_REPO_PATH "/.gitattributes", "conflicting.txt merge=union\n"); - - merge_simple_branch(GIT_MERGE_FILE_FAVOR_NORMAL, 0); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, - TEST_REPO_PATH "/conflicting.txt")); - cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_UNION_FILE) == 0); - git_str_dispose(&conflicting_buf); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); - cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4)); -} - -void test_merge_workdir_simple__diff3_from_config(void) -{ - git_config *config; - git_str conflicting_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - - { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, - - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY - }; - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_string(config, "merge.conflictstyle", "diff3")); - - set_core_autocrlf_to(repo, false); - - merge_simple_branch(0, 0); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, - TEST_REPO_PATH "/conflicting.txt")); - cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_DIFF3_FILE) == 0); - git_str_dispose(&conflicting_buf); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); - cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); - - git_config_free(config); -} - -void test_merge_workdir_simple__zdiff3_from_config(void) -{ - git_config *config; - git_str conflicting_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - - { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, - - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY - }; - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_string(config, "merge.conflictstyle", "zdiff3")); - - set_core_autocrlf_to(repo, false); - - merge_simple_branch(0, 0); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, - TEST_REPO_PATH "/conflicting.txt")); - cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_ZDIFF3_FILE) == 0); - git_str_dispose(&conflicting_buf); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); - cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); - - git_config_free(config); -} - -void test_merge_workdir_simple__merge_overrides_config(void) -{ - git_config *config; - git_str conflicting_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - - { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, - - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY - }; - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_string(config, "merge.conflictstyle", "diff3")); - - set_core_autocrlf_to(repo, false); - - merge_simple_branch(0, GIT_CHECKOUT_CONFLICT_STYLE_MERGE); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, - TEST_REPO_PATH "/conflicting.txt")); - cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_MERGE_FILE) == 0); - git_str_dispose(&conflicting_buf); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); - cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); - - git_config_free(config); -} - -void test_merge_workdir_simple__checkout_ours(void) -{ - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - - { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" }, - - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY - }; - - merge_simple_branch(0, GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 8)); - cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3)); - - cl_assert(git_fs_path_exists(TEST_REPO_PATH "/conflicting.txt")); -} - -void test_merge_workdir_simple__favor_ours(void) -{ - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - CONFLICTING_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY, - }; - - merge_simple_branch(GIT_MERGE_FILE_FAVOR_OURS, 0); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); - cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4)); -} - -void test_merge_workdir_simple__favor_theirs(void) -{ - struct merge_index_entry merge_index_entries[] = { - ADDED_IN_MASTER_INDEX_ENTRY, - AUTOMERGEABLE_INDEX_ENTRY, - CHANGED_IN_BRANCH_INDEX_ENTRY, - CHANGED_IN_MASTER_INDEX_ENTRY, - { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 0, "conflicting.txt" }, - UNCHANGED_INDEX_ENTRY, - }; - - struct merge_reuc_entry merge_reuc_entries[] = { - AUTOMERGEABLE_REUC_ENTRY, - CONFLICTING_REUC_ENTRY, - REMOVED_IN_BRANCH_REUC_ENTRY, - REMOVED_IN_MASTER_REUC_ENTRY, - }; - - merge_simple_branch(GIT_MERGE_FILE_FAVOR_THEIRS, 0); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); - cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4)); -} - -void test_merge_workdir_simple__directory_file(void) -{ - git_reference *head; - git_oid their_oids[1], head_commit_id; - git_annotated_commit *their_heads[1]; - git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; - git_commit *head_commit; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "49130a28ef567af9a6a6104c38773fedfa5f9742", 2, "dir-10" }, - { 0100644, "6c06dcd163587c2cc18be44857e0b71116382aeb", 3, "dir-10" }, - { 0100644, "43aafd43bea779ec74317dc361f45ae3f532a505", 0, "dir-6" }, - { 0100644, "a031a28ae70e33a641ce4b8a8f6317f1ab79dee4", 3, "dir-7" }, - { 0100644, "5012fd565b1393bdfda1805d4ec38ce6619e1fd1", 1, "dir-7/file.txt" }, - { 0100644, "a5563304ddf6caba25cb50323a2ea6f7dbfcadca", 2, "dir-7/file.txt" }, - { 0100644, "e9ad6ec3e38364a3d07feda7c4197d4d845c53b5", 0, "dir-8" }, - { 0100644, "3ef4d30382ca33fdeba9fda895a99e0891ba37aa", 2, "dir-9" }, - { 0100644, "fc4c636d6515e9e261f9260dbcf3cc6eca97ea08", 1, "dir-9/file.txt" }, - { 0100644, "76ab0e2868197ec158ddd6c78d8a0d2fd73d38f9", 3, "dir-9/file.txt" }, - { 0100644, "5c2411f8075f48a6b2fdb85ebc0d371747c4df15", 0, "file-1/new" }, - { 0100644, "a39a620dae5bc8b4e771cd4d251b7d080401a21e", 1, "file-2" }, - { 0100644, "d963979c237d08b6ba39062ee7bf64c7d34a27f8", 2, "file-2" }, - { 0100644, "5c341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d", 0, "file-2/new" }, - { 0100644, "9efe7723802d4305142eee177e018fee1572c4f4", 0, "file-3/new" }, - { 0100644, "bacac9b3493509aa15e1730e1545fc0919d1dae0", 1, "file-4" }, - { 0100644, "7663fce0130db092936b137cabd693ec234eb060", 3, "file-4" }, - { 0100644, "e49f917b448d1340b31d76e54ba388268fd4c922", 0, "file-4/new" }, - { 0100644, "cab2cf23998b40f1af2d9d9a756dc9e285a8df4b", 2, "file-5/new" }, - { 0100644, "f5504f36e6f4eb797a56fc5bac6c6c7f32969bf2", 3, "file-5/new" }, - }; - - cl_git_pass(git_reference_symbolic_create(&head, repo, GIT_HEAD_FILE, GIT_REFS_HEADS_DIR OURS_DIRECTORY_FILE, 1, NULL)); - cl_git_pass(git_reference_name_to_id(&head_commit_id, repo, GIT_HEAD_FILE)); - cl_git_pass(git_commit_lookup(&head_commit, repo, &head_commit_id)); - cl_git_pass(git_reset(repo, (git_object *)head_commit, GIT_RESET_HARD, NULL)); - - cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_DIRECTORY_FILE)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &their_oids[0])); - - merge_opts.file_favor = 0; - cl_git_pass(git_merge(repo, (const git_annotated_commit **)their_heads, 1, &merge_opts, NULL)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 20)); - - git_reference_free(head); - git_commit_free(head_commit); - git_annotated_commit_free(their_heads[0]); -} - -void test_merge_workdir_simple__unrelated(void) -{ - git_oid their_oids[1]; - git_annotated_commit *their_heads[1]; - git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, - { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" }, - { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, - { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, - { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" }, - { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" }, - { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, - { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, - }; - - cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_UNRELATED_PARENT)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &their_oids[0])); - - merge_opts.file_favor = 0; - cl_git_pass(git_merge(repo, (const git_annotated_commit **)their_heads, 1, &merge_opts, NULL)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 9)); - - git_annotated_commit_free(their_heads[0]); -} - -void test_merge_workdir_simple__unrelated_with_conflicts(void) -{ - git_oid their_oids[1]; - git_annotated_commit *their_heads[1]; - git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, - { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 2, "automergeable.txt" }, - { 0100644, "d07ec190c306ec690bac349e87d01c4358e49bb2", 3, "automergeable.txt" }, - { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, - { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, - { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" }, - { 0100644, "4b253da36a0ae8bfce63aeabd8c5b58429925594", 3, "conflicting.txt" }, - { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" }, - { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" }, - { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, - { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, - }; - - cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_UNRELATED_OID)); - cl_git_pass(git_annotated_commit_lookup(&their_heads[0], repo, &their_oids[0])); - - merge_opts.file_favor = 0; - cl_git_pass(git_merge(repo, (const git_annotated_commit **)their_heads, 1, &merge_opts, NULL)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 11)); - - git_annotated_commit_free(their_heads[0]); -} - -void test_merge_workdir_simple__binary(void) -{ - git_oid our_oid, their_oid, our_file_oid; - git_commit *our_commit; - git_annotated_commit *their_head; - const git_index_entry *binary_entry; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "1c51d885170f57a0c4e8c69ff6363d91a5b51f85", 1, "binary" }, - { 0100644, "23ed141a6ae1e798b2f721afedbe947c119111ba", 2, "binary" }, - { 0100644, "836b8b82b26cab22eaaed8820877c76d6c8bca19", 3, "binary" }, - }; - - cl_git_pass(git_oid_fromstr(&our_oid, "cc338e4710c9b257106b8d16d82f86458d5beaf1")); - cl_git_pass(git_oid_fromstr(&their_oid, "ad01aebfdf2ac13145efafe3f9fcf798882f1730")); - - cl_git_pass(git_commit_lookup(&our_commit, repo, &our_oid)); - cl_git_pass(git_reset(repo, (git_object *)our_commit, GIT_RESET_HARD, NULL)); - - cl_git_pass(git_annotated_commit_lookup(&their_head, repo, &their_oid)); - - cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, 1, NULL, NULL)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); - - cl_git_pass(git_index_add_bypath(repo_index, "binary")); - cl_assert((binary_entry = git_index_get_bypath(repo_index, "binary", 0)) != NULL); - - cl_git_pass(git_oid_fromstr(&our_file_oid, "23ed141a6ae1e798b2f721afedbe947c119111ba")); - cl_assert(git_oid_cmp(&binary_entry->id, &our_file_oid) == 0); - - git_annotated_commit_free(their_head); - git_commit_free(our_commit); -} diff --git a/tests/merge/workdir/submodules.c b/tests/merge/workdir/submodules.c deleted file mode 100644 index 5117be789..000000000 --- a/tests/merge/workdir/submodules.c +++ /dev/null @@ -1,130 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "merge.h" -#include "../merge_helpers.h" - -static git_repository *repo; - -#define TEST_REPO_PATH "merge-resolve" - -#define SUBMODULE_MAIN_BRANCH "submodules" -#define SUBMODULE_OTHER_BRANCH "submodules-branch" -#define SUBMODULE_OTHER2_BRANCH "submodules-branch2" -#define SUBMODULE_DELETE_BRANCH "delete-submodule" - -#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" - -/* Fixture setup and teardown */ -void test_merge_workdir_submodules__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_merge_workdir_submodules__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_merge_workdir_submodules__automerge(void) -{ - git_reference *our_ref, *their_ref; - git_commit *our_commit; - git_annotated_commit *their_head; - git_index *index; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "caff6b7d44973f53e3e0cf31d0d695188b19aec6", 0, ".gitmodules" }, - { 0100644, "950a663a6a7b2609eed1ed1ba9f41eb1a3192a9f", 0, "file1.txt" }, - { 0100644, "343e660b9cb4bee5f407c2e33fcb9df24d9407a4", 0, "file2.txt" }, - { 0160000, "d3d806a4bef96889117fd7ebac0e3cb5ec152932", 1, "submodule" }, - { 0160000, "297aa6cd028b3336c7802c7a6f49143da4e1602d", 2, "submodule" }, - { 0160000, "ae39c77c70cb6bad18bb471912460c4e1ba0f586", 3, "submodule" }, - }; - - cl_git_pass(git_reference_lookup(&our_ref, repo, "refs/heads/" SUBMODULE_MAIN_BRANCH)); - cl_git_pass(git_commit_lookup(&our_commit, repo, git_reference_target(our_ref))); - cl_git_pass(git_reset(repo, (git_object *)our_commit, GIT_RESET_HARD, NULL)); - - cl_git_pass(git_reference_lookup(&their_ref, repo, "refs/heads/" SUBMODULE_OTHER_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_head, repo, their_ref)); - - cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, 1, NULL, NULL)); - - cl_git_pass(git_repository_index(&index, repo)); - cl_assert(merge_test_index(index, merge_index_entries, 6)); - - git_index_free(index); - git_annotated_commit_free(their_head); - git_commit_free(our_commit); - git_reference_free(their_ref); - git_reference_free(our_ref); -} - -void test_merge_workdir_submodules__take_changed(void) -{ - git_reference *our_ref, *their_ref; - git_commit *our_commit; - git_annotated_commit *their_head; - git_index *index; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "caff6b7d44973f53e3e0cf31d0d695188b19aec6", 0, ".gitmodules" }, - { 0100644, "b438ff23300b2e0f80b84a6f30140dfa91e71423", 0, "file1.txt" }, - { 0100644, "f27fbafdfa6693f8f7a5128506fe3e338dbfcad2", 0, "file2.txt" }, - { 0160000, "297aa6cd028b3336c7802c7a6f49143da4e1602d", 0, "submodule" }, - }; - - cl_git_pass(git_reference_lookup(&our_ref, repo, "refs/heads/" SUBMODULE_MAIN_BRANCH)); - cl_git_pass(git_commit_lookup(&our_commit, repo, git_reference_target(our_ref))); - cl_git_pass(git_reset(repo, (git_object *)our_commit, GIT_RESET_HARD, NULL)); - - cl_git_pass(git_reference_lookup(&their_ref, repo, "refs/heads/" SUBMODULE_OTHER2_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_head, repo, their_ref)); - - cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, 1, NULL, NULL)); - - cl_git_pass(git_repository_index(&index, repo)); - cl_assert(merge_test_index(index, merge_index_entries, 4)); - - git_index_free(index); - git_annotated_commit_free(their_head); - git_commit_free(our_commit); - git_reference_free(their_ref); - git_reference_free(our_ref); -} - - -void test_merge_workdir_submodules__update_delete_conflict(void) -{ - git_reference *our_ref, *their_ref; - git_commit *our_commit; - git_annotated_commit *their_head; - git_index *index; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", 0, ".gitmodules" }, - { 0100644, "5887a5e516c53bd58efb0f02ec6aa031b6fe9ad7", 0, "file1.txt" }, - { 0100644, "4218670ab81cc219a9f94befb5c5dad90ec52648", 0, "file2.txt" }, - { 0160000, "d3d806a4bef96889117fd7ebac0e3cb5ec152932", 1, "submodule"}, - { 0160000, "297aa6cd028b3336c7802c7a6f49143da4e1602d", 3, "submodule" }, - }; - - cl_git_pass(git_reference_lookup(&our_ref, repo, "refs/heads/" SUBMODULE_DELETE_BRANCH)); - cl_git_pass(git_commit_lookup(&our_commit, repo, git_reference_target(our_ref))); - cl_git_pass(git_reset(repo, (git_object *)our_commit, GIT_RESET_HARD, NULL)); - - cl_git_pass(git_reference_lookup(&their_ref, repo, "refs/heads/" SUBMODULE_MAIN_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&their_head, repo, their_ref)); - - cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, 1, NULL, NULL)); - - cl_git_pass(git_repository_index(&index, repo)); - cl_assert(merge_test_index(index, merge_index_entries, 5)); - - git_index_free(index); - git_annotated_commit_free(their_head); - git_commit_free(our_commit); - git_reference_free(their_ref); - git_reference_free(our_ref); -} diff --git a/tests/merge/workdir/trivial.c b/tests/merge/workdir/trivial.c deleted file mode 100644 index fe8374851..000000000 --- a/tests/merge/workdir/trivial.c +++ /dev/null @@ -1,262 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/repository.h" -#include "git2/merge.h" -#include "git2/sys/index.h" -#include "merge.h" -#include "../merge_helpers.h" -#include "refs.h" -#include "futils.h" - -static git_repository *repo; -static git_index *repo_index; - -#define TEST_REPO_PATH "merge-resolve" -#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" - - -/* Fixture setup and teardown */ -void test_merge_workdir_trivial__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); - git_repository_index(&repo_index, repo); -} - -void test_merge_workdir_trivial__cleanup(void) -{ - git_index_free(repo_index); - cl_git_sandbox_cleanup(); -} - - -static int merge_trivial(const char *ours, const char *theirs) -{ - git_str branch_buf = GIT_STR_INIT; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - git_reference *our_ref, *their_ref; - git_annotated_commit *their_heads[1]; - - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours); - cl_git_pass(git_reference_symbolic_create(&our_ref, repo, "HEAD", branch_buf.ptr, 1, NULL)); - - cl_git_pass(git_checkout_head(repo, &checkout_opts)); - - git_str_clear(&branch_buf); - git_str_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, theirs); - cl_git_pass(git_reference_lookup(&their_ref, repo, branch_buf.ptr)); - cl_git_pass(git_annotated_commit_from_ref(&their_heads[0], repo, their_ref)); - - cl_git_pass(git_merge(repo, (const git_annotated_commit **)their_heads, 1, NULL, NULL)); - - git_str_dispose(&branch_buf); - git_reference_free(our_ref); - git_reference_free(their_ref); - git_annotated_commit_free(their_heads[0]); - - return 0; -} - -static size_t merge_trivial_conflict_entrycount(void) -{ - const git_index_entry *entry; - size_t count = 0; - size_t i; - - for (i = 0; i < git_index_entrycount(repo_index); i++) { - cl_assert(entry = git_index_get_byindex(repo_index, i)); - - if (git_index_entry_is_conflict(entry)) - count++; - } - - return count; -} - -/* 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote */ -void test_merge_workdir_trivial__2alt(void) -{ - const git_index_entry *entry; - - cl_git_pass(merge_trivial("trivial-2alt", "trivial-2alt-branch")); - - cl_assert(entry = git_index_get_bypath(repo_index, "new-in-branch.txt", 0)); - cl_assert(git_index_reuc_entrycount(repo_index) == 0); - cl_assert(merge_trivial_conflict_entrycount() == 0); -} - -/* 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head */ -void test_merge_workdir_trivial__3alt(void) -{ - const git_index_entry *entry; - - cl_git_pass(merge_trivial("trivial-3alt", "trivial-3alt-branch")); - - cl_assert(entry = git_index_get_bypath(repo_index, "new-in-3alt.txt", 0)); - cl_assert(git_index_reuc_entrycount(repo_index) == 0); - cl_assert(merge_trivial_conflict_entrycount() == 0); -} - -/* 4: ancest:(empty)^, head:head, remote:remote = result:no merge */ -void test_merge_workdir_trivial__4(void) -{ - const git_index_entry *entry; - - cl_git_pass(merge_trivial("trivial-4", "trivial-4-branch")); - - cl_assert((entry = git_index_get_bypath(repo_index, "new-and-different.txt", 0)) == NULL); - cl_assert(git_index_reuc_entrycount(repo_index) == 0); - - cl_assert(merge_trivial_conflict_entrycount() == 2); - cl_assert(entry = git_index_get_bypath(repo_index, "new-and-different.txt", 2)); - cl_assert(entry = git_index_get_bypath(repo_index, "new-and-different.txt", 3)); -} - -/* 5ALT: ancest:*, head:head, remote:head = result:head */ -void test_merge_workdir_trivial__5alt_1(void) -{ - const git_index_entry *entry; - - cl_git_pass(merge_trivial("trivial-5alt-1", "trivial-5alt-1-branch")); - - cl_assert(entry = git_index_get_bypath(repo_index, "new-and-same.txt", 0)); - cl_assert(git_index_reuc_entrycount(repo_index) == 0); - cl_assert(merge_trivial_conflict_entrycount() == 0); -} - -/* 5ALT: ancest:*, head:head, remote:head = result:head */ -void test_merge_workdir_trivial__5alt_2(void) -{ - const git_index_entry *entry; - - cl_git_pass(merge_trivial("trivial-5alt-2", "trivial-5alt-2-branch")); - - cl_assert(entry = git_index_get_bypath(repo_index, "modified-to-same.txt", 0)); - cl_assert(git_index_reuc_entrycount(repo_index) == 0); - cl_assert(merge_trivial_conflict_entrycount() == 0); -} - -/* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */ -void test_merge_workdir_trivial__6(void) -{ - const git_index_entry *entry; - const git_index_reuc_entry *reuc; - - cl_git_pass(merge_trivial("trivial-6", "trivial-6-branch")); - - cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-both.txt", 0)) == NULL); - cl_assert(git_index_reuc_entrycount(repo_index) == 1); - cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "removed-in-both.txt")); - - cl_assert(merge_trivial_conflict_entrycount() == 0); -} - -/* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */ -void test_merge_workdir_trivial__8(void) -{ - const git_index_entry *entry; - const git_index_reuc_entry *reuc; - - cl_git_pass(merge_trivial("trivial-8", "trivial-8-branch")); - - cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-8.txt", 0)) == NULL); - - cl_assert(git_index_reuc_entrycount(repo_index) == 1); - cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "removed-in-8.txt")); - - cl_assert(merge_trivial_conflict_entrycount() == 0); -} - -/* 7: ancest:ancest+, head:(empty), remote:remote = result:no merge */ -void test_merge_workdir_trivial__7(void) -{ - const git_index_entry *entry; - - cl_git_pass(merge_trivial("trivial-7", "trivial-7-branch")); - - cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 0)) == NULL); - cl_assert(git_index_reuc_entrycount(repo_index) == 0); - - cl_assert(merge_trivial_conflict_entrycount() == 2); - cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 1)); - cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 3)); -} - -/* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */ -void test_merge_workdir_trivial__10(void) -{ - const git_index_entry *entry; - const git_index_reuc_entry *reuc; - - cl_git_pass(merge_trivial("trivial-10", "trivial-10-branch")); - - cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-10-branch.txt", 0)) == NULL); - - cl_assert(git_index_reuc_entrycount(repo_index) == 1); - cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "removed-in-10-branch.txt")); - - cl_assert(merge_trivial_conflict_entrycount() == 0); -} - -/* 9: ancest:ancest+, head:head, remote:(empty) = result:no merge */ -void test_merge_workdir_trivial__9(void) -{ - const git_index_entry *entry; - - cl_git_pass(merge_trivial("trivial-9", "trivial-9-branch")); - - cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 0)) == NULL); - cl_assert(git_index_reuc_entrycount(repo_index) == 0); - - cl_assert(merge_trivial_conflict_entrycount() == 2); - cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 1)); - cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 2)); -} - -/* 13: ancest:ancest+, head:head, remote:ancest = result:head */ -void test_merge_workdir_trivial__13(void) -{ - const git_index_entry *entry; - git_oid expected_oid; - - cl_git_pass(merge_trivial("trivial-13", "trivial-13-branch")); - - cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-13.txt", 0)); - cl_git_pass(git_oid_fromstr(&expected_oid, "1cff9ec6a47a537380dedfdd17c9e76d74259a2b")); - cl_assert(git_oid_cmp(&entry->id, &expected_oid) == 0); - - cl_assert(git_index_reuc_entrycount(repo_index) == 0); - cl_assert(merge_trivial_conflict_entrycount() == 0); -} - -/* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */ -void test_merge_workdir_trivial__14(void) -{ - const git_index_entry *entry; - git_oid expected_oid; - - cl_git_pass(merge_trivial("trivial-14", "trivial-14-branch")); - - cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-14-branch.txt", 0)); - cl_git_pass(git_oid_fromstr(&expected_oid, "26153a3ff3649b6c2bb652d3f06878c6e0a172f9")); - cl_assert(git_oid_cmp(&entry->id, &expected_oid) == 0); - - cl_assert(git_index_reuc_entrycount(repo_index) == 0); - cl_assert(merge_trivial_conflict_entrycount() == 0); -} - -/* 11: ancest:ancest+, head:head, remote:remote = result:no merge */ -void test_merge_workdir_trivial__11(void) -{ - const git_index_entry *entry; - - cl_git_pass(merge_trivial("trivial-11", "trivial-11-branch")); - - cl_assert((entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 0)) == NULL); - cl_assert(git_index_reuc_entrycount(repo_index) == 0); - - cl_assert(merge_trivial_conflict_entrycount() == 3); - cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 1)); - cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 2)); - cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 3)); -} diff --git a/tests/message/trailer.c b/tests/message/trailer.c deleted file mode 100644 index 919e10a49..000000000 --- a/tests/message/trailer.c +++ /dev/null @@ -1,164 +0,0 @@ -#include "clar_libgit2.h" - -static void assert_trailers(const char *message, git_message_trailer *trailers) -{ - git_message_trailer_array arr; - size_t i; - - int rc = git_message_trailers(&arr, message); - - cl_assert_equal_i(0, rc); - - for(i=0; i 0); - - git_strarray_dispose(&refnames); - git_remote_free(origin); - git_repository_free(repo); -} - -void test_network_fetchlocal__prune(void) -{ - git_repository *repo; - git_remote *origin; - int callcount = 0; - git_strarray refnames = {0}; - git_reference *ref; - git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); - const char *url = cl_git_path_url(git_repository_path(remote_repo)); - git_fetch_options options = GIT_FETCH_OPTIONS_INIT; - - options.callbacks.transfer_progress = transfer_cb; - options.callbacks.payload = &callcount; - - cl_set_cleanup(&cleanup_local_repo, "foo"); - cl_git_pass(git_repository_init(&repo, "foo", true)); - - cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); - cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); - - cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(20, (int)refnames.count); - cl_assert(callcount > 0); - git_strarray_dispose(&refnames); - git_remote_free(origin); - - cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/br2")); - cl_git_pass(git_reference_delete(ref)); - git_reference_free(ref); - - cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); - cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); - cl_git_pass(git_remote_prune(origin, &options.callbacks)); - - cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(19, (int)refnames.count); - git_strarray_dispose(&refnames); - git_remote_free(origin); - - cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/packed")); - cl_git_pass(git_reference_delete(ref)); - git_reference_free(ref); - - cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); - cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); - cl_git_pass(git_remote_prune(origin, &options.callbacks)); - - cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(18, (int)refnames.count); - git_strarray_dispose(&refnames); - git_remote_free(origin); - - git_repository_free(repo); -} - -static int update_tips_fail_on_call(const char *ref, const git_oid *old, const git_oid *new, void *data) -{ - GIT_UNUSED(ref); - GIT_UNUSED(old); - GIT_UNUSED(new); - GIT_UNUSED(data); - - cl_fail("update tips called"); - return 0; -} - -static void assert_ref_exists(git_repository *repo, const char *name) -{ - git_reference *ref; - - cl_git_pass(git_reference_lookup(&ref, repo, name)); - git_reference_free(ref); -} - -void test_network_fetchlocal__prune_overlapping(void) -{ - git_repository *repo; - git_remote *origin; - int callcount = 0; - git_strarray refnames = {0}; - git_reference *ref; - git_config *config; - git_oid target; - - git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); - const char *url = cl_git_path_url(git_repository_path(remote_repo)); - - git_fetch_options options = GIT_FETCH_OPTIONS_INIT; - options.callbacks.transfer_progress = transfer_cb; - options.callbacks.payload = &callcount; - - cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/master")); - git_oid_cpy(&target, git_reference_target(ref)); - git_reference_free(ref); - cl_git_pass(git_reference_create(&ref, remote_repo, "refs/pull/42/head", &target, 1, NULL)); - git_reference_free(ref); - - cl_set_cleanup(&cleanup_local_repo, "foo"); - cl_git_pass(git_repository_init(&repo, "foo", true)); - - cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_bool(config, "remote.origin.prune", true)); - cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); - - git_remote_free(origin); - cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); - cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); - - assert_ref_exists(repo, "refs/remotes/origin/master"); - assert_ref_exists(repo, "refs/remotes/origin/pr/42"); - cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(21, (int)refnames.count); - git_strarray_dispose(&refnames); - - cl_git_pass(git_config_delete_multivar(config, "remote.origin.fetch", "refs")); - cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); - cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); - - git_remote_free(origin); - cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); - options.callbacks.update_tips = update_tips_fail_on_call; - cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); - - assert_ref_exists(repo, "refs/remotes/origin/master"); - assert_ref_exists(repo, "refs/remotes/origin/pr/42"); - cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(21, (int)refnames.count); - git_strarray_dispose(&refnames); - - cl_git_pass(git_config_delete_multivar(config, "remote.origin.fetch", "refs")); - cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); - cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); - - git_remote_free(origin); - cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); - options.callbacks.update_tips = update_tips_fail_on_call; - cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); - - git_config_free(config); - git_strarray_dispose(&refnames); - git_remote_free(origin); - git_repository_free(repo); -} - -void test_network_fetchlocal__fetchprune(void) -{ - git_repository *repo; - git_remote *origin; - int callcount = 0; - git_strarray refnames = {0}; - git_reference *ref; - git_config *config; - git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); - const char *url = cl_git_path_url(git_repository_path(remote_repo)); - git_fetch_options options = GIT_FETCH_OPTIONS_INIT; - - options.callbacks.transfer_progress = transfer_cb; - options.callbacks.payload = &callcount; - - cl_set_cleanup(&cleanup_local_repo, "foo"); - cl_git_pass(git_repository_init(&repo, "foo", true)); - - cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); - cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); - - cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(20, (int)refnames.count); - cl_assert(callcount > 0); - git_strarray_dispose(&refnames); - git_remote_free(origin); - - cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/br2")); - cl_git_pass(git_reference_delete(ref)); - git_reference_free(ref); - - cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); - cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); - cl_git_pass(git_remote_prune(origin, &options.callbacks)); - - cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(19, (int)refnames.count); - git_strarray_dispose(&refnames); - git_remote_free(origin); - - cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/packed")); - cl_git_pass(git_reference_delete(ref)); - git_reference_free(ref); - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_bool(config, "remote.origin.prune", 1)); - git_config_free(config); - cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); - cl_assert_equal_i(1, git_remote_prune_refs(origin)); - cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); - - cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(18, (int)refnames.count); - git_strarray_dispose(&refnames); - git_remote_free(origin); - - git_repository_free(repo); -} - -void test_network_fetchlocal__prune_tag(void) -{ - git_repository *repo; - git_remote *origin; - int callcount = 0; - git_reference *ref; - git_config *config; - git_oid tag_id; - git_signature *tagger; - git_object *obj; - - git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); - const char *url = cl_git_path_url(git_repository_path(remote_repo)); - git_fetch_options options = GIT_FETCH_OPTIONS_INIT; - - options.callbacks.transfer_progress = transfer_cb; - options.callbacks.payload = &callcount; - - cl_set_cleanup(&cleanup_local_repo, "foo"); - cl_git_pass(git_repository_init(&repo, "foo", true)); - - cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); - cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); - git_remote_free(origin); - - cl_git_pass(git_revparse_single(&obj, repo, "origin/master")); - - cl_git_pass(git_reference_create(&ref, repo, "refs/remotes/origin/fake-remote", git_object_id(obj), 1, NULL)); - git_reference_free(ref); - - /* create signature */ - cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); - - cl_git_pass( - git_tag_create(&tag_id, repo, - "some-tag", obj, tagger, tagger_message, 0) - ); - git_signature_free(tagger); - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_bool(config, "remote.origin.prune", 1)); - git_config_free(config); - cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); - cl_assert_equal_i(1, git_remote_prune_refs(origin)); - cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); - - assert_ref_exists(repo, "refs/tags/some-tag"); - cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, repo, "refs/remotes/origin/fake-remote")); - - git_object_free(obj); - git_remote_free(origin); - - git_repository_free(repo); -} - -void test_network_fetchlocal__partial(void) -{ - git_repository *repo = cl_git_sandbox_init("partial-testrepo"); - git_remote *origin; - int callcount = 0; - git_strarray refnames = {0}; - const char *url; - git_fetch_options options = GIT_FETCH_OPTIONS_INIT; - - options.callbacks.transfer_progress = transfer_cb; - options.callbacks.payload = &callcount; - - cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(1, (int)refnames.count); - - url = cl_git_fixture_url("testrepo.git"); - cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); - cl_git_pass(git_remote_fetch(origin, NULL, &options, NULL)); - - git_strarray_dispose(&refnames); - - cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(21, (int)refnames.count); /* 18 remote + 1 local */ - cl_assert(callcount > 0); - - git_strarray_dispose(&refnames); - git_remote_free(origin); -} - -static int remote_mirror_cb(git_remote **out, git_repository *repo, - const char *name, const char *url, void *payload) -{ - int error; - git_remote *remote; - - GIT_UNUSED(payload); - - if ((error = git_remote_create_with_fetchspec(&remote, repo, name, url, "+refs/*:refs/*")) < 0) - return error; - - *out = remote; - return 0; -} - -void test_network_fetchlocal__clone_into_mirror(void) -{ - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - git_repository *repo; - git_reference *ref; - - opts.bare = true; - opts.remote_cb = remote_mirror_cb; - cl_git_pass(git_clone(&repo, cl_git_fixture_url("testrepo.git"), "./foo.git", &opts)); - - cl_git_pass(git_reference_lookup(&ref, repo, "HEAD")); - cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(ref)); - cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(ref)); - - git_reference_free(ref); - cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/test/master")); - - git_reference_free(ref); - git_repository_free(repo); - cl_fixture_cleanup("./foo.git"); -} - -void test_network_fetchlocal__all_refs(void) -{ - git_repository *repo; - git_remote *remote; - git_reference *ref; - char *allrefs = "+refs/*:refs/*"; - git_strarray refspecs = { - &allrefs, - 1, - }; - - cl_git_pass(git_repository_init(&repo, "./foo.git", true)); - cl_git_pass(git_remote_create_anonymous(&remote, repo, cl_git_fixture_url("testrepo.git"))); - cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, NULL)); - - cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/test/master")); - git_reference_free(ref); - - cl_git_pass(git_reference_lookup(&ref, repo, "refs/tags/test")); - git_reference_free(ref); - - git_remote_free(remote); - git_repository_free(repo); - cl_fixture_cleanup("./foo.git"); -} - -void test_network_fetchlocal__multi_remotes(void) -{ - git_repository *repo = cl_git_sandbox_init("testrepo.git"); - git_remote *test, *test2; - git_strarray refnames = {0}; - git_fetch_options options = GIT_FETCH_OPTIONS_INIT; - - options.callbacks.transfer_progress = transfer_cb; - cl_git_pass(git_remote_set_url(repo, "test", cl_git_fixture_url("testrepo.git"))); - cl_git_pass(git_remote_lookup(&test, repo, "test")); - cl_git_pass(git_remote_fetch(test, NULL, &options, NULL)); - - cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(35, (int)refnames.count); - git_strarray_dispose(&refnames); - - cl_git_pass(git_remote_set_url(repo, "test_with_pushurl", cl_git_fixture_url("testrepo.git"))); - cl_git_pass(git_remote_lookup(&test2, repo, "test_with_pushurl")); - cl_git_pass(git_remote_fetch(test2, NULL, &options, NULL)); - - cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(48, (int)refnames.count); - - git_strarray_dispose(&refnames); - git_remote_free(test); - git_remote_free(test2); -} - -static int sideband_cb(const char *str, int len, void *payload) -{ - int *count = (int *) payload; - - GIT_UNUSED(str); - GIT_UNUSED(len); - - (*count)++; - return 0; -} - -void test_network_fetchlocal__call_progress(void) -{ - git_repository *repo; - git_remote *remote; - git_fetch_options options = GIT_FETCH_OPTIONS_INIT; - int callcount = 0; - - cl_git_pass(git_repository_init(&repo, "foo.git", true)); - cl_set_cleanup(cleanup_local_repo, "foo.git"); - - cl_git_pass(git_remote_create_with_fetchspec(&remote, repo, "origin", cl_git_fixture_url("testrepo.git"), "+refs/heads/*:refs/heads/*")); - - options.callbacks.sideband_progress = sideband_cb; - options.callbacks.payload = &callcount; - - cl_git_pass(git_remote_fetch(remote, NULL, &options, NULL)); - cl_assert(callcount != 0); - - git_remote_free(remote); - git_repository_free(repo); -} - -void test_network_fetchlocal__prune_load_remote_prune_config(void) -{ - git_repository *repo; - git_remote *origin; - git_config *config; - git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); - const char *url = cl_git_path_url(git_repository_path(remote_repo)); - - cl_set_cleanup(&cleanup_local_repo, "foo"); - cl_git_pass(git_repository_init(&repo, "foo", true)); - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_bool(config, "remote.origin.prune", 1)); - - cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); - cl_assert_equal_i(1, git_remote_prune_refs(origin)); - - git_config_free(config); - git_remote_free(origin); - git_repository_free(repo); -} - -void test_network_fetchlocal__prune_load_fetch_prune_config(void) -{ - git_repository *repo; - git_remote *origin; - git_config *config; - git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); - const char *url = cl_git_path_url(git_repository_path(remote_repo)); - - cl_set_cleanup(&cleanup_local_repo, "foo"); - cl_git_pass(git_repository_init(&repo, "foo", true)); - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_bool(config, "fetch.prune", 1)); - - cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); - cl_assert_equal_i(1, git_remote_prune_refs(origin)); - - git_config_free(config); - git_remote_free(origin); - git_repository_free(repo); -} - -static int update_tips_error(const char *ref, const git_oid *old, const git_oid *new, void *data) -{ - int *callcount = (int *) data; - - GIT_UNUSED(ref); - GIT_UNUSED(old); - GIT_UNUSED(new); - - (*callcount)++; - - return -1; -} - -void test_network_fetchlocal__update_tips_error_is_propagated(void) -{ - git_repository *repo; - git_reference_iterator *iterator; - git_reference *ref; - git_remote *remote; - git_fetch_options options = GIT_FETCH_OPTIONS_INIT; - int callcount = 0; - - cl_git_pass(git_repository_init(&repo, "foo.git", true)); - cl_set_cleanup(cleanup_local_repo, "foo.git"); - - cl_git_pass(git_remote_create_with_fetchspec(&remote, repo, "origin", cl_git_fixture_url("testrepo.git"), "+refs/heads/*:refs/remotes/update-tips/*")); - - options.callbacks.update_tips = update_tips_error; - options.callbacks.payload = &callcount; - - cl_git_fail(git_remote_fetch(remote, NULL, &options, NULL)); - cl_assert_equal_i(1, callcount); - - cl_git_pass(git_reference_iterator_glob_new(&iterator, repo, "refs/remotes/update-tips/**/")); - cl_assert_equal_i(GIT_ITEROVER, git_reference_next(&ref, iterator)); - - git_reference_iterator_free(iterator); - git_remote_free(remote); - git_repository_free(repo); -} diff --git a/tests/network/matchhost.c b/tests/network/matchhost.c deleted file mode 100644 index 3100dc21d..000000000 --- a/tests/network/matchhost.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "clar_libgit2.h" -#include "netops.h" - -void test_network_matchhost__match(void) -{ - cl_git_pass(gitno__match_host("*.example.org", "www.example.org")); - cl_git_pass(gitno__match_host("*.foo.example.org", "www.foo.example.org")); - cl_git_fail(gitno__match_host("*.foo.example.org", "foo.example.org")); - cl_git_fail(gitno__match_host("*.foo.example.org", "www.example.org")); - cl_git_fail(gitno__match_host("*.example.org", "example.org")); - cl_git_fail(gitno__match_host("*.example.org", "www.foo.example.org")); - cl_git_fail(gitno__match_host("*.example.org", "blah.www.www.example.org")); -} diff --git a/tests/network/refspecs.c b/tests/network/refspecs.c deleted file mode 100644 index d9e0d9e8d..000000000 --- a/tests/network/refspecs.c +++ /dev/null @@ -1,191 +0,0 @@ -#include "clar_libgit2.h" -#include "refspec.h" -#include "remote.h" - -static void assert_refspec(unsigned int direction, const char *input, bool is_expected_to_be_valid) -{ - git_refspec refspec; - int error; - - error = git_refspec__parse(&refspec, input, direction == GIT_DIRECTION_FETCH); - git_refspec__dispose(&refspec); - - if (is_expected_to_be_valid) - cl_assert_equal_i(0, error); - else - cl_assert_equal_i(GIT_EINVALIDSPEC, error); -} - -void test_network_refspecs__parsing(void) -{ - /* Ported from https://github.com/git/git/blob/abd2bde78bd994166900290434a2048e660dabed/t/t5511-refspec.sh */ - - assert_refspec(GIT_DIRECTION_PUSH, "", false); - assert_refspec(GIT_DIRECTION_PUSH, ":", true); - assert_refspec(GIT_DIRECTION_PUSH, "::", false); - assert_refspec(GIT_DIRECTION_PUSH, "+:", true); - - assert_refspec(GIT_DIRECTION_FETCH, "", true); - assert_refspec(GIT_DIRECTION_PUSH, ":", true); - assert_refspec(GIT_DIRECTION_FETCH, "::", false); - - assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*:refs/remotes/frotz/*", true); - assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*:refs/remotes/frotz", false); - assert_refspec(GIT_DIRECTION_PUSH, "refs/heads:refs/remotes/frotz/*", false); - assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/master:refs/remotes/frotz/xyzzy", true); - - /* - * These have invalid LHS, but we do not have a formal "valid sha-1 - * expression syntax checker" so they are not checked with the current - * code. They will be caught downstream anyway, but we may want to - * have tighter check later... - */ - /*assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/master::refs/remotes/frotz/xyzzy", false); */ - /*assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/maste :refs/remotes/frotz/xyzzy", false); */ - - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*:refs/remotes/frotz/*", true); - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*:refs/remotes/frotz", false); - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads:refs/remotes/frotz/*", false); - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/master:refs/remotes/frotz/xyzzy", true); - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/master::refs/remotes/frotz/xyzzy", false); - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/maste :refs/remotes/frotz/xyzzy", false); - - assert_refspec(GIT_DIRECTION_PUSH, "master~1:refs/remotes/frotz/backup", true); - assert_refspec(GIT_DIRECTION_FETCH, "master~1:refs/remotes/frotz/backup", false); - assert_refspec(GIT_DIRECTION_PUSH, "HEAD~4:refs/remotes/frotz/new", true); - assert_refspec(GIT_DIRECTION_FETCH, "HEAD~4:refs/remotes/frotz/new", false); - - assert_refspec(GIT_DIRECTION_PUSH, "HEAD", true); - assert_refspec(GIT_DIRECTION_FETCH, "HEAD", true); - assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/ nitfol", false); - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/ nitfol", false); - - assert_refspec(GIT_DIRECTION_PUSH, "HEAD:", false); - assert_refspec(GIT_DIRECTION_FETCH, "HEAD:", true); - assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/ nitfol:", false); - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/ nitfol:", false); - - assert_refspec(GIT_DIRECTION_PUSH, ":refs/remotes/frotz/deleteme", true); - assert_refspec(GIT_DIRECTION_FETCH, ":refs/remotes/frotz/HEAD-to-me", true); - assert_refspec(GIT_DIRECTION_PUSH, ":refs/remotes/frotz/delete me", false); - assert_refspec(GIT_DIRECTION_FETCH, ":refs/remotes/frotz/HEAD to me", false); - - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/for-linus:refs/remotes/mine/*-blah", true); - assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/for-linus:refs/remotes/mine/*-blah", true); - - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads*/for-linus:refs/remotes/mine/*", true); - assert_refspec(GIT_DIRECTION_PUSH, "refs/heads*/for-linus:refs/remotes/mine/*", true); - - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/*/for-linus:refs/remotes/mine/*", false); - assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/*/for-linus:refs/remotes/mine/*", false); - - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*g*/for-linus:refs/remotes/mine/*", false); - assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*g*/for-linus:refs/remotes/mine/*", false); - - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/for-linus:refs/remotes/mine/*", true); - assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/for-linus:refs/remotes/mine/*", true); - - assert_refspec(GIT_DIRECTION_FETCH, "master", true); - assert_refspec(GIT_DIRECTION_PUSH, "master", true); - - assert_refspec(GIT_DIRECTION_FETCH, "refs/pull/*/head:refs/remotes/origin/pr/*", true); -} - -static void assert_valid_transform(const char *refspec, const char *name, const char *result) -{ - git_refspec spec; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_refspec__parse(&spec, refspec, true)); - cl_git_pass(git_refspec_transform(&buf, &spec, name)); - cl_assert_equal_s(result, buf.ptr); - - git_buf_dispose(&buf); - git_refspec__dispose(&spec); -} - -void test_network_refspecs__transform_mid_star(void) -{ - assert_valid_transform("refs/pull/*/head:refs/remotes/origin/pr/*", "refs/pull/23/head", "refs/remotes/origin/pr/23"); - assert_valid_transform("refs/heads/*:refs/remotes/origin/*", "refs/heads/master", "refs/remotes/origin/master"); - assert_valid_transform("refs/heads/*:refs/remotes/origin/*", "refs/heads/user/feature", "refs/remotes/origin/user/feature"); - assert_valid_transform("refs/heads/*:refs/heads/*", "refs/heads/master", "refs/heads/master"); - assert_valid_transform("refs/heads/*:refs/heads/*", "refs/heads/user/feature", "refs/heads/user/feature"); - assert_valid_transform("refs/*:refs/*", "refs/heads/master", "refs/heads/master"); -} - -void test_network_refspecs__transform_loosened_star(void) -{ - assert_valid_transform("refs/heads/branch-*:refs/remotes/origin/branch-*", "refs/heads/branch-a", "refs/remotes/origin/branch-a"); - assert_valid_transform("refs/heads/branch-*/head:refs/remotes/origin/branch-*/head", "refs/heads/branch-a/head", "refs/remotes/origin/branch-a/head"); -} - -void test_network_refspecs__transform_nested_star(void) -{ - assert_valid_transform("refs/heads/x*x/for-linus:refs/remotes/mine/*", "refs/heads/xbranchx/for-linus", "refs/remotes/mine/branch"); -} - -void test_network_refspecs__no_dst(void) -{ - assert_valid_transform("refs/heads/master:", "refs/heads/master", ""); -} - -static void assert_invalid_transform(const char *refspec, const char *name) -{ - git_refspec spec; - git_buf buf = GIT_BUF_INIT; - - git_refspec__parse(&spec, refspec, true); - cl_git_fail(git_refspec_transform(&buf, &spec, name)); - - git_buf_dispose(&buf); - git_refspec__dispose(&spec); -} - -void test_network_refspecs__invalid(void) -{ - assert_invalid_transform("refs/heads/*:refs/remotes/origin/*", "master"); - assert_invalid_transform("refs/heads/*:refs/remotes/origin/*", "refs/headz/master"); -} - -static void assert_invalid_rtransform(const char *refspec, const char *name) -{ - git_refspec spec; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_refspec__parse(&spec, refspec, true)); - cl_git_fail(git_refspec_rtransform(&buf, &spec, name)); - - git_buf_dispose(&buf); - git_refspec__dispose(&spec); -} - -void test_network_refspecs__invalid_reverse(void) -{ - assert_invalid_rtransform("refs/heads/*:refs/remotes/origin/*", "master"); - assert_invalid_rtransform("refs/heads/*:refs/remotes/origin/*", "refs/remotes/o/master"); -} - -void test_network_refspecs__matching(void) -{ - git_refspec spec; - - cl_git_pass(git_refspec__parse(&spec, ":", false)); - cl_assert_equal_s(":", spec.string); - cl_assert_equal_s("", spec.src); - cl_assert_equal_s("", spec.dst); - - git_refspec__dispose(&spec); -} - -void test_network_refspecs__parse_free(void) -{ - git_refspec *spec = NULL; - - cl_git_fail(git_refspec_parse(&spec, "", 0)); - cl_git_fail(git_refspec_parse(&spec, ":::", 0)); - cl_git_pass(git_refspec_parse(&spec, "HEAD:", 1)); - - cl_assert(spec != NULL); - git_refspec_free(spec); -} diff --git a/tests/network/remote/defaultbranch.c b/tests/network/remote/defaultbranch.c deleted file mode 100644 index a7c0d81f6..000000000 --- a/tests/network/remote/defaultbranch.c +++ /dev/null @@ -1,107 +0,0 @@ -#include "clar_libgit2.h" -#include "refspec.h" -#include "remote.h" - -static git_remote *g_remote; -static git_repository *g_repo_a, *g_repo_b; - -void test_network_remote_defaultbranch__initialize(void) -{ - g_repo_a = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_init(&g_repo_b, "repo-b.git", true)); - cl_git_pass(git_remote_create(&g_remote, g_repo_b, "origin", git_repository_path(g_repo_a))); -} - -void test_network_remote_defaultbranch__cleanup(void) -{ - git_remote_free(g_remote); - git_repository_free(g_repo_b); - - cl_git_sandbox_cleanup(); - cl_fixture_cleanup("repo-b.git"); -} - -static void assert_default_branch(const char *should) -{ - git_buf name = GIT_BUF_INIT; - - cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - cl_git_pass(git_remote_default_branch(&name, g_remote)); - cl_assert_equal_s(should, name.ptr); - git_buf_dispose(&name); -} - -void test_network_remote_defaultbranch__master(void) -{ - assert_default_branch("refs/heads/master"); -} - -void test_network_remote_defaultbranch__master_does_not_win(void) -{ - cl_git_pass(git_repository_set_head(g_repo_a, "refs/heads/not-good")); - assert_default_branch("refs/heads/not-good"); -} - -void test_network_remote_defaultbranch__master_on_detached(void) -{ - cl_git_pass(git_repository_detach_head(g_repo_a)); - assert_default_branch("refs/heads/master"); -} - -void test_network_remote_defaultbranch__no_default_branch(void) -{ - git_remote *remote_b; - const git_remote_head **heads; - size_t len; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_remote_create(&remote_b, g_repo_b, "self", git_repository_path(g_repo_b))); - cl_git_pass(git_remote_connect(remote_b, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - cl_git_pass(git_remote_ls(&heads, &len, remote_b)); - cl_assert_equal_i(0, len); - - cl_git_fail_with(GIT_ENOTFOUND, git_remote_default_branch(&buf, remote_b)); - - git_remote_free(remote_b); -} - -void test_network_remote_defaultbranch__detached_sharing_nonbranch_id(void) -{ - git_oid id, id_cloned; - git_reference *ref; - git_buf buf = GIT_BUF_INIT; - git_repository *cloned_repo; - - cl_git_pass(git_reference_name_to_id(&id, g_repo_a, "HEAD")); - cl_git_pass(git_repository_detach_head(g_repo_a)); - cl_git_pass(git_reference_remove(g_repo_a, "refs/heads/master")); - cl_git_pass(git_reference_remove(g_repo_a, "refs/heads/not-good")); - cl_git_pass(git_reference_create(&ref, g_repo_a, "refs/foo/bar", &id, 1, NULL)); - git_reference_free(ref); - - cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - cl_git_fail_with(GIT_ENOTFOUND, git_remote_default_branch(&buf, g_remote)); - - cl_git_pass(git_clone(&cloned_repo, git_repository_path(g_repo_a), "./local-detached", NULL)); - - cl_assert(git_repository_head_detached(cloned_repo)); - cl_git_pass(git_reference_name_to_id(&id_cloned, g_repo_a, "HEAD")); - cl_assert(git_oid_equal(&id, &id_cloned)); - - git_repository_free(cloned_repo); -} - -void test_network_remote_defaultbranch__unborn_HEAD_with_branches(void) -{ - git_reference *ref; - git_repository *cloned_repo; - - cl_git_pass(git_reference_symbolic_create(&ref, g_repo_a, "HEAD", "refs/heads/i-dont-exist", 1, NULL)); - git_reference_free(ref); - - cl_git_pass(git_clone(&cloned_repo, git_repository_path(g_repo_a), "./semi-empty", NULL)); - - cl_assert(git_repository_head_unborn(cloned_repo)); - - git_repository_free(cloned_repo); -} diff --git a/tests/network/remote/delete.c b/tests/network/remote/delete.c deleted file mode 100644 index f23a638aa..000000000 --- a/tests/network/remote/delete.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "clar_libgit2.h" -#include "config/config_helpers.h" - -#include "repository.h" - -static git_repository *_repo; - -void test_network_remote_delete__initialize(void) -{ - _repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_network_remote_delete__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_network_remote_delete__remove_remote_tracking_branches(void) -{ - git_reference *ref; - - cl_git_pass(git_remote_delete(_repo, "test")); - cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, _repo, "refs/remotes/test/master")); -} - -void test_network_remote_delete__remove_remote_configuration_settings(void) -{ - cl_assert(count_config_entries_match(_repo, "remote\\.test\\.+") > 0); - - cl_git_pass(git_remote_delete(_repo, "test")); - - cl_assert_equal_i(0, count_config_entries_match(_repo, "remote\\.test\\.+")); -} - -void test_network_remote_delete__remove_branch_upstream_configuration_settings(void) -{ - assert_config_entry_existence(_repo, "branch.mergeless.remote", true); - assert_config_entry_existence(_repo, "branch.master.remote", true); - - cl_git_pass(git_remote_delete(_repo, "test")); - - assert_config_entry_existence(_repo, "branch.mergeless.remote", false); - assert_config_entry_existence(_repo, "branch.mergeless.merge", false); - assert_config_entry_existence(_repo, "branch.master.remote", false); - assert_config_entry_existence(_repo, "branch.master.merge", false); -} diff --git a/tests/network/remote/isvalidname.c b/tests/network/remote/isvalidname.c deleted file mode 100644 index a3080f67c..000000000 --- a/tests/network/remote/isvalidname.c +++ /dev/null @@ -1,24 +0,0 @@ -#include "clar_libgit2.h" - -static int is_valid_name(const char *name) -{ - int valid = 0; - cl_git_pass(git_remote_name_is_valid(&valid, name)); - return valid; -} - -void test_network_remote_isvalidname__can_detect_invalid_formats(void) -{ - cl_assert_equal_i(false, is_valid_name("/")); - cl_assert_equal_i(false, is_valid_name("//")); - cl_assert_equal_i(false, is_valid_name(".lock")); - cl_assert_equal_i(false, is_valid_name("a.lock")); - cl_assert_equal_i(false, is_valid_name("/no/leading/slash")); - cl_assert_equal_i(false, is_valid_name("no/trailing/slash/")); -} - -void test_network_remote_isvalidname__wont_hopefully_choke_on_valid_formats(void) -{ - cl_assert_equal_i(true, is_valid_name("webmatrix")); - cl_assert_equal_i(true, is_valid_name("yishaigalatzer/rules")); -} diff --git a/tests/network/remote/local.c b/tests/network/remote/local.c deleted file mode 100644 index 2007f3776..000000000 --- a/tests/network/remote/local.c +++ /dev/null @@ -1,483 +0,0 @@ -#include "clar_libgit2.h" -#include "path.h" -#include "posix.h" -#include "git2/sys/repository.h" - -static git_repository *repo; -static git_str file_path_buf = GIT_STR_INIT; -static git_remote *remote; - -static char *push_refspec_strings[] = { - "refs/heads/master", -}; -static git_strarray push_array = { - push_refspec_strings, - 1, -}; - -void test_network_remote_local__initialize(void) -{ - cl_git_pass(git_repository_init(&repo, "remotelocal/", 0)); - cl_git_pass(git_repository_set_ident(repo, "Foo Bar", "foo@example.com")); - cl_assert(repo != NULL); -} - -void test_network_remote_local__cleanup(void) -{ - git_str_dispose(&file_path_buf); - - git_remote_free(remote); - remote = NULL; - - git_repository_free(repo); - repo = NULL; - - cl_fixture_cleanup("remotelocal"); -} - -static void connect_to_local_repository(const char *local_repository) -{ - git_str_sets(&file_path_buf, cl_git_path_url(local_repository)); - - cl_git_pass(git_remote_create_anonymous(&remote, repo, git_str_cstr(&file_path_buf))); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); -} - -void test_network_remote_local__connected(void) -{ - connect_to_local_repository(cl_fixture("testrepo.git")); - cl_assert(git_remote_connected(remote)); - - git_remote_disconnect(remote); - cl_assert(!git_remote_connected(remote)); -} - -void test_network_remote_local__retrieve_advertised_references(void) -{ - const git_remote_head **refs; - size_t refs_len; - - connect_to_local_repository(cl_fixture("testrepo.git")); - - cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); - - cl_assert_equal_i(refs_len, 30); -} - -void test_network_remote_local__retrieve_advertised_before_connect(void) -{ - const git_remote_head **refs; - size_t refs_len = 0; - - git_str_sets(&file_path_buf, cl_git_path_url(cl_fixture("testrepo.git"))); - - cl_git_pass(git_remote_create_anonymous(&remote, repo, git_str_cstr(&file_path_buf))); - cl_git_fail(git_remote_ls(&refs, &refs_len, remote)); -} - -void test_network_remote_local__retrieve_advertised_references_after_disconnect(void) -{ - const git_remote_head **refs; - size_t refs_len; - - connect_to_local_repository(cl_fixture("testrepo.git")); - git_remote_disconnect(remote); - - cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); - - cl_assert_equal_i(refs_len, 30); -} - -void test_network_remote_local__retrieve_advertised_references_from_spaced_repository(void) -{ - const git_remote_head **refs; - size_t refs_len; - - cl_fixture_sandbox("testrepo.git"); - cl_git_pass(p_rename("testrepo.git", "spaced testrepo.git")); - - connect_to_local_repository("spaced testrepo.git"); - - cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); - - cl_assert_equal_i(refs_len, 30); - - git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */ - remote = NULL; - - cl_fixture_cleanup("spaced testrepo.git"); -} - -void test_network_remote_local__nested_tags_are_completely_peeled(void) -{ - const git_remote_head **refs; - size_t refs_len, i; - - connect_to_local_repository(cl_fixture("testrepo.git")); - - cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); - - for (i = 0; i < refs_len; i++) { - if (!strcmp(refs[i]->name, "refs/tags/test^{}")) - cl_git_pass(git_oid_streq(&refs[i]->oid, "e90810b8df3e80c413d903f631643c716887138d")); - } -} - -void test_network_remote_local__shorthand_fetch_refspec0(void) -{ - char *refspec_strings[] = { - "master:remotes/sloppy/master", - "master:boh/sloppy/master", - }; - git_strarray array = { - refspec_strings, - 2, - }; - - git_reference *ref; - - connect_to_local_repository(cl_fixture("testrepo.git")); - - cl_git_pass(git_remote_fetch(remote, &array, NULL, NULL)); - - cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/sloppy/master")); - git_reference_free(ref); - - cl_git_pass(git_reference_lookup(&ref, repo, "refs/heads/boh/sloppy/master")); - git_reference_free(ref); -} - -void test_network_remote_local__shorthand_fetch_refspec1(void) -{ - char *refspec_strings[] = { - "master", - "hard_tag", - }; - git_strarray array = { - refspec_strings, - 2, - }; - - git_reference *ref; - - connect_to_local_repository(cl_fixture("testrepo.git")); - - cl_git_pass(git_remote_fetch(remote, &array, NULL, NULL)); - - cl_git_fail(git_reference_lookup(&ref, repo, "refs/remotes/origin/master")); - cl_git_fail(git_reference_lookup(&ref, repo, "refs/tags/hard_tag")); -} - -void test_network_remote_local__tagopt(void) -{ - git_reference *ref; - git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; - - cl_git_pass(git_remote_create(&remote, repo, "tagopt", cl_git_path_url(cl_fixture("testrepo.git")))); - fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; - cl_git_pass(git_remote_fetch(remote, NULL, &fetch_opts, NULL)); - - cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/tagopt/master")); - git_reference_free(ref); - cl_git_pass(git_reference_lookup(&ref, repo, "refs/tags/hard_tag")); - git_reference_free(ref); - - fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; - cl_git_pass(git_remote_fetch(remote, NULL, &fetch_opts, NULL)); - cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/tagopt/master")); - git_reference_free(ref); -} - -void test_network_remote_local__push_to_bare_remote(void) -{ - char *refspec_strings[] = { - "master:master", - }; - git_strarray array = { - refspec_strings, - 1, - }; - - /* Should be able to push to a bare remote */ - git_remote *localremote; - - /* Get some commits */ - connect_to_local_repository(cl_fixture("testrepo.git")); - cl_git_pass(git_remote_fetch(remote, &array, NULL, NULL)); - - /* Set up an empty bare repo to push into */ - { - git_repository *localbarerepo; - cl_git_pass(git_repository_init(&localbarerepo, "./localbare.git", 1)); - git_repository_free(localbarerepo); - } - - /* Connect to the bare repo */ - cl_git_pass(git_remote_create_anonymous(&localremote, repo, "./localbare.git")); - cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL, NULL)); - - /* Try to push */ - cl_git_pass(git_remote_upload(localremote, &push_array, NULL)); - - /* Clean up */ - git_remote_free(localremote); - cl_fixture_cleanup("localbare.git"); -} - -void test_network_remote_local__push_to_bare_remote_with_file_url(void) -{ - char *refspec_strings[] = { - "master:master", - }; - git_strarray array = { - refspec_strings, - 1, - }; - /* Should be able to push to a bare remote */ - git_remote *localremote; - const char *url; - - /* Get some commits */ - connect_to_local_repository(cl_fixture("testrepo.git")); - cl_git_pass(git_remote_fetch(remote, &array, NULL, NULL)); - - /* Set up an empty bare repo to push into */ - { - git_repository *localbarerepo; - cl_git_pass(git_repository_init(&localbarerepo, "./localbare.git", 1)); - git_repository_free(localbarerepo); - } - - /* Create a file URL */ - url = cl_git_path_url("./localbare.git"); - - /* Connect to the bare repo */ - cl_git_pass(git_remote_create_anonymous(&localremote, repo, url)); - cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL, NULL)); - - /* Try to push */ - cl_git_pass(git_remote_upload(localremote, &push_array, NULL)); - - /* Clean up */ - git_remote_free(localremote); - cl_fixture_cleanup("localbare.git"); -} - - -void test_network_remote_local__push_to_non_bare_remote(void) -{ - char *refspec_strings[] = { - "master:master", - }; - git_strarray array = { - refspec_strings, - 1, - }; - /* Shouldn't be able to push to a non-bare remote */ - git_remote *localremote; - git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; - - /* Get some commits */ - connect_to_local_repository(cl_fixture("testrepo.git")); - cl_git_pass(git_remote_fetch(remote, &array, &fetch_opts, NULL)); - - /* Set up an empty non-bare repo to push into */ - { - git_repository *remoterepo = NULL; - cl_git_pass(git_repository_init(&remoterepo, "localnonbare", 0)); - git_repository_free(remoterepo); - } - - /* Connect to the bare repo */ - cl_git_pass(git_remote_create_anonymous(&localremote, repo, "./localnonbare")); - cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL, NULL)); - - /* Try to push */ - cl_git_fail_with(GIT_EBAREREPO, git_remote_upload(localremote, &push_array, NULL)); - - /* Clean up */ - git_remote_free(localremote); - cl_fixture_cleanup("localbare.git"); -} - -void test_network_remote_local__fetch(void) -{ - char *refspec_strings[] = { - "master:remotes/sloppy/master", - }; - git_strarray array = { - refspec_strings, - 1, - }; - - git_reflog *log; - const git_reflog_entry *entry; - git_reference *ref; - - connect_to_local_repository(cl_fixture("testrepo.git")); - - cl_git_pass(git_remote_fetch(remote, &array, NULL, "UPDAAAAAATE!!")); - - cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/sloppy/master")); - git_reference_free(ref); - - cl_git_pass(git_reflog_read(&log, repo, "refs/remotes/sloppy/master")); - cl_assert_equal_i(1, git_reflog_entrycount(log)); - entry = git_reflog_entry_byindex(log, 0); - cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email); - cl_assert_equal_s("UPDAAAAAATE!!", git_reflog_entry_message(entry)); - - git_reflog_free(log); -} - -void test_network_remote_local__reflog(void) -{ - char *refspec_strings[] = { - "master:remotes/sloppy/master", - }; - git_strarray array = { - refspec_strings, - 1, - }; - - git_reflog *log; - const git_reflog_entry *entry; - - connect_to_local_repository(cl_fixture("testrepo.git")); - - cl_git_pass(git_remote_fetch(remote, &array, NULL, "UPDAAAAAATE!!")); - - cl_git_pass(git_reflog_read(&log, repo, "refs/remotes/sloppy/master")); - cl_assert_equal_i(1, git_reflog_entrycount(log)); - entry = git_reflog_entry_byindex(log, 0); - cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email); - cl_assert_equal_s("UPDAAAAAATE!!", git_reflog_entry_message(entry)); - - git_reflog_free(log); -} - -void test_network_remote_local__fetch_default_reflog_message(void) -{ - char *refspec_strings[] = { - "master:remotes/sloppy/master", - }; - git_strarray array = { - refspec_strings, - 1, - }; - - git_reflog *log; - const git_reflog_entry *entry; - char expected_reflog_msg[1024]; - - connect_to_local_repository(cl_fixture("testrepo.git")); - - cl_git_pass(git_remote_fetch(remote, &array, NULL, NULL)); - - cl_git_pass(git_reflog_read(&log, repo, "refs/remotes/sloppy/master")); - cl_assert_equal_i(1, git_reflog_entrycount(log)); - entry = git_reflog_entry_byindex(log, 0); - cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email); - - sprintf(expected_reflog_msg, "fetch %s", git_remote_url(remote)); - cl_assert_equal_s(expected_reflog_msg, git_reflog_entry_message(entry)); - - git_reflog_free(log); -} - -void test_network_remote_local__opportunistic_update(void) -{ - git_reference *ref; - char *refspec_strings[] = { - "master", - }; - git_strarray array = { - refspec_strings, - 1, - }; - - /* this remote has a passive refspec of "refs/heads/:refs/remotes/origin/" */ - cl_git_pass(git_remote_create(&remote, repo, "origin", cl_git_fixture_url("testrepo.git"))); - /* and we pass the active refspec "master" */ - cl_git_pass(git_remote_fetch(remote, &array, NULL, NULL)); - - /* and we expect that to update our copy of origin's master */ - cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/origin/master")); - git_reference_free(ref); -} - -void test_network_remote_local__update_tips_for_new_remote(void) { - git_repository *src_repo; - git_repository *dst_repo; - git_remote *new_remote; - git_reference* branch; - - /* Copy test repo */ - cl_fixture_sandbox("testrepo.git"); - cl_git_pass(git_repository_open(&src_repo, "testrepo.git")); - - /* Set up an empty bare repo to push into */ - cl_git_pass(git_repository_init(&dst_repo, "./localbare.git", 1)); - - /* Push to bare repo */ - cl_git_pass(git_remote_create(&new_remote, src_repo, "bare", "./localbare.git")); - cl_git_pass(git_remote_push(new_remote, &push_array, NULL)); - /* Make sure remote branch has been created */ - cl_git_pass(git_branch_lookup(&branch, src_repo, "bare/master", GIT_BRANCH_REMOTE)); - - git_reference_free(branch); - git_remote_free(new_remote); - git_repository_free(dst_repo); - cl_fixture_cleanup("localbare.git"); - git_repository_free(src_repo); - cl_fixture_cleanup("testrepo.git"); -} - -void test_network_remote_local__push_delete(void) -{ - git_repository *src_repo; - git_repository *dst_repo; - git_remote *remote; - git_reference *ref; - char *spec_push[] = { "refs/heads/master" }; - char *spec_delete[] = { ":refs/heads/master" }; - git_strarray specs = { - spec_push, - 1, - }; - - src_repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_init(&dst_repo, "target.git", 1)); - - cl_git_pass(git_remote_create(&remote, src_repo, "origin", "./target.git")); - - /* Push the master branch and verify it's there */ - cl_git_pass(git_remote_push(remote, &specs, NULL)); - cl_git_pass(git_reference_lookup(&ref, dst_repo, "refs/heads/master")); - git_reference_free(ref); - - specs.strings = spec_delete; - cl_git_pass(git_remote_push(remote, &specs, NULL)); - cl_git_fail(git_reference_lookup(&ref, dst_repo, "refs/heads/master")); - - git_remote_free(remote); - git_repository_free(dst_repo); - cl_fixture_cleanup("target.git"); - cl_git_sandbox_cleanup(); -} - -void test_network_remote_local__anonymous_remote_inmemory_repo(void) -{ - git_repository *inmemory; - git_remote *remote; - - git_str_sets(&file_path_buf, cl_git_path_url(cl_fixture("testrepo.git"))); - - cl_git_pass(git_repository_new(&inmemory)); - cl_git_pass(git_remote_create_anonymous(&remote, inmemory, git_str_cstr(&file_path_buf))); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - cl_assert(git_remote_connected(remote)); - git_remote_disconnect(remote); - git_remote_free(remote); - git_repository_free(inmemory); -} diff --git a/tests/network/remote/push.c b/tests/network/remote/push.c deleted file mode 100644 index 3905debdf..000000000 --- a/tests/network/remote/push.c +++ /dev/null @@ -1,114 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/commit.h" - -static git_remote *_remote; -static git_repository *_repo, *_dummy; - -void test_network_remote_push__initialize(void) -{ - cl_fixture_sandbox("testrepo.git"); - git_repository_open(&_repo, "testrepo.git"); - - /* We need a repository to have a remote */ - cl_git_pass(git_repository_init(&_dummy, "dummy.git", true)); - cl_git_pass(git_remote_create(&_remote, _dummy, "origin", cl_git_path_url("testrepo.git"))); -} - -void test_network_remote_push__cleanup(void) -{ - git_remote_free(_remote); - _remote = NULL; - - git_repository_free(_repo); - _repo = NULL; - - git_repository_free(_dummy); - _dummy = NULL; - - cl_fixture_cleanup("testrepo.git"); - cl_fixture_cleanup("dummy.git"); -} - -static int negotiation_cb(const git_push_update **updates, size_t len, void *payload) -{ - const git_push_update *expected = payload; - - cl_assert_equal_i(1, len); - cl_assert_equal_s(expected->src_refname, updates[0]->src_refname); - cl_assert_equal_s(expected->dst_refname, updates[0]->dst_refname); - cl_assert_equal_oid(&expected->src, &updates[0]->src); - cl_assert_equal_oid(&expected->dst, &updates[0]->dst); - - return 0; -} - -void test_network_remote_push__delete_notification(void) -{ - git_push_options opts = GIT_PUSH_OPTIONS_INIT; - git_reference *ref; - git_push_update expected; - char *refspec = ":refs/heads/master"; - const git_strarray refspecs = { - &refspec, - 1, - }; - - cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/master")); - - expected.src_refname = ""; - expected.dst_refname = "refs/heads/master"; - memset(&expected.dst, 0, sizeof(git_oid)); - git_oid_cpy(&expected.src, git_reference_target(ref)); - - opts.callbacks.push_negotiation = negotiation_cb; - opts.callbacks.payload = &expected; - cl_git_pass(git_remote_push(_remote, &refspecs, &opts)); - - git_reference_free(ref); - cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, _repo, "refs/heads/master")); - -} - -static void create_dummy_commit(git_reference **out, git_repository *repo) -{ - git_index *index; - git_oid tree_id, commit_id; - git_signature *sig; - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_write_tree(&tree_id, index)); - git_index_free(index); - - cl_git_pass(git_signature_now(&sig, "Pusher Joe", "pjoe")); - cl_git_pass(git_commit_create_from_ids(&commit_id, repo, NULL, sig, sig, - NULL, "Empty tree\n", &tree_id, 0, NULL)); - cl_git_pass(git_reference_create(out, repo, "refs/heads/empty-tree", &commit_id, true, "commit yo")); - git_signature_free(sig); -} - -void test_network_remote_push__create_notification(void) -{ - git_push_options opts = GIT_PUSH_OPTIONS_INIT; - git_reference *ref; - git_push_update expected; - char *refspec = "refs/heads/empty-tree"; - const git_strarray refspecs = { - &refspec, - 1, - }; - - create_dummy_commit(&ref, _dummy); - - expected.src_refname = "refs/heads/empty-tree"; - expected.dst_refname = "refs/heads/empty-tree"; - git_oid_cpy(&expected.dst, git_reference_target(ref)); - memset(&expected.src, 0, sizeof(git_oid)); - - opts.callbacks.push_negotiation = negotiation_cb; - opts.callbacks.payload = &expected; - cl_git_pass(git_remote_push(_remote, &refspecs, &opts)); - - git_reference_free(ref); - cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/empty-tree")); - git_reference_free(ref); -} diff --git a/tests/network/remote/remotes.c b/tests/network/remote/remotes.c deleted file mode 100644 index 79c4f39fa..000000000 --- a/tests/network/remote/remotes.c +++ /dev/null @@ -1,575 +0,0 @@ -#include "clar_libgit2.h" -#include "config/config_helpers.h" -#include "refspec.h" -#include "remote.h" - -static git_remote *_remote; -static git_repository *_repo; -static const git_refspec *_refspec; - -void test_network_remote_remotes__initialize(void) -{ - _repo = cl_git_sandbox_init("testrepo.git"); - - cl_git_pass(git_remote_lookup(&_remote, _repo, "test")); - - _refspec = git_remote_get_refspec(_remote, 0); - cl_assert(_refspec != NULL); -} - -void test_network_remote_remotes__cleanup(void) -{ - git_remote_free(_remote); - _remote = NULL; - - cl_git_sandbox_cleanup(); -} - -void test_network_remote_remotes__parsing(void) -{ - git_str url = GIT_STR_INIT; - git_remote *_remote2 = NULL; - - cl_assert_equal_s(git_remote_name(_remote), "test"); - cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); - cl_assert(git_remote_pushurl(_remote) == NULL); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, NULL)); - cl_assert_equal_s(url.ptr, "git://github.com/libgit2/libgit2"); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, NULL)); - cl_assert_equal_s(url.ptr, "git://github.com/libgit2/libgit2"); - - cl_git_pass(git_remote_lookup(&_remote2, _repo, "test_with_pushurl")); - cl_assert_equal_s(git_remote_name(_remote2), "test_with_pushurl"); - cl_assert_equal_s(git_remote_url(_remote2), "git://github.com/libgit2/fetchlibgit2"); - cl_assert_equal_s(git_remote_pushurl(_remote2), "git://github.com/libgit2/pushlibgit2"); - - cl_git_pass(git_remote__urlfordirection(&url, _remote2, GIT_DIRECTION_FETCH, NULL)); - cl_assert_equal_s(url.ptr, "git://github.com/libgit2/fetchlibgit2"); - - cl_git_pass(git_remote__urlfordirection(&url, _remote2, GIT_DIRECTION_PUSH, NULL)); - cl_assert_equal_s(url.ptr, "git://github.com/libgit2/pushlibgit2"); - - git_remote_free(_remote2); - git_str_dispose(&url); -} - -static int remote_ready_callback(git_remote *remote, int direction, void *payload) -{ - if (direction == GIT_DIRECTION_PUSH) { - const char *url = git_remote_pushurl(remote); - - cl_assert_equal_p(url, NULL);; - cl_assert_equal_s(payload, "payload"); - return git_remote_set_instance_pushurl(remote, "push_url"); - } - - if (direction == GIT_DIRECTION_FETCH) { - const char *url = git_remote_url(remote); - - cl_assert_equal_s(url, "git://github.com/libgit2/libgit2"); - cl_assert_equal_s(payload, "payload"); - return git_remote_set_instance_url(remote, "fetch_url"); - } - - return -1; -} - -void test_network_remote_remotes__remote_ready(void) -{ - git_str url = GIT_STR_INIT; - - git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; - callbacks.remote_ready = remote_ready_callback; - callbacks.payload = "payload"; - - cl_assert_equal_s(git_remote_name(_remote), "test"); - cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); - cl_assert(git_remote_pushurl(_remote) == NULL); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, &callbacks)); - cl_assert_equal_s(url.ptr, "fetch_url"); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, &callbacks)); - cl_assert_equal_s(url.ptr, "push_url"); - - git_str_dispose(&url); -} - -#ifndef GIT_DEPRECATE_HARD -static int urlresolve_callback(git_buf *url_resolved, const char *url, int direction, void *payload) -{ - int error = -1; - - cl_assert(strcmp(url, "git://github.com/libgit2/libgit2") == 0); - cl_assert(strcmp(payload, "payload") == 0); - cl_assert(url_resolved->size == 0); - - if (direction == GIT_DIRECTION_PUSH) - error = git_buf_set(url_resolved, "pushresolve", strlen("pushresolve") + 1); - if (direction == GIT_DIRECTION_FETCH) - error = git_buf_set(url_resolved, "fetchresolve", strlen("fetchresolve") + 1); - - return error; -} -#endif - -void test_network_remote_remotes__urlresolve(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_str url = GIT_STR_INIT; - - git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; - callbacks.resolve_url = urlresolve_callback; - callbacks.payload = "payload"; - - cl_assert_equal_s(git_remote_name(_remote), "test"); - cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); - cl_assert(git_remote_pushurl(_remote) == NULL); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, &callbacks)); - cl_assert_equal_s(url.ptr, "fetchresolve"); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, &callbacks)); - cl_assert_equal_s(url.ptr, "pushresolve"); - - git_str_dispose(&url); -#endif -} - -#ifndef GIT_DEPRECATE_HARD -static int urlresolve_passthrough_callback(git_buf *url_resolved, const char *url, int direction, void *payload) -{ - GIT_UNUSED(url_resolved); - GIT_UNUSED(url); - GIT_UNUSED(direction); - GIT_UNUSED(payload); - return GIT_PASSTHROUGH; -} -#endif - -void test_network_remote_remotes__urlresolve_passthrough(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_str url = GIT_STR_INIT; - const char *orig_url = "git://github.com/libgit2/libgit2"; - - git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; - callbacks.resolve_url = urlresolve_passthrough_callback; - - cl_assert_equal_s(git_remote_name(_remote), "test"); - cl_assert_equal_s(git_remote_url(_remote), orig_url); - cl_assert(git_remote_pushurl(_remote) == NULL); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, &callbacks)); - cl_assert_equal_s(url.ptr, orig_url); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, &callbacks)); - cl_assert_equal_s(url.ptr, orig_url); - - git_str_dispose(&url); -#endif -} - -void test_network_remote_remotes__instance_url(void) -{ - git_str url = GIT_STR_INIT; - const char *orig_url = "git://github.com/libgit2/libgit2"; - - cl_assert_equal_s(git_remote_name(_remote), "test"); - cl_assert_equal_s(git_remote_url(_remote), orig_url); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, NULL)); - cl_assert_equal_s(url.ptr, orig_url); - git_str_clear(&url); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, NULL)); - cl_assert_equal_s(url.ptr, orig_url); - git_str_clear(&url); - - /* Setting the instance url updates the fetch and push URLs */ - git_remote_set_instance_url(_remote, "https://github.com/new/remote/url"); - cl_assert_equal_s(git_remote_url(_remote), "https://github.com/new/remote/url"); - cl_assert_equal_p(git_remote_pushurl(_remote), NULL); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, NULL)); - cl_assert_equal_s(url.ptr, "https://github.com/new/remote/url"); - git_str_clear(&url); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, NULL)); - cl_assert_equal_s(url.ptr, "https://github.com/new/remote/url"); - git_str_clear(&url); - - /* Setting the instance push url updates only the push URL */ - git_remote_set_instance_pushurl(_remote, "https://github.com/new/push/url"); - cl_assert_equal_s(git_remote_url(_remote), "https://github.com/new/remote/url"); - cl_assert_equal_s(git_remote_pushurl(_remote), "https://github.com/new/push/url"); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, NULL)); - cl_assert_equal_s(url.ptr, "https://github.com/new/remote/url"); - git_str_clear(&url); - - cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, NULL)); - cl_assert_equal_s(url.ptr, "https://github.com/new/push/url"); - git_str_clear(&url); - - git_str_dispose(&url); -} - -void test_network_remote_remotes__pushurl(void) -{ - const char *name = git_remote_name(_remote); - git_remote *mod; - - cl_git_pass(git_remote_set_pushurl(_repo, name, "git://github.com/libgit2/notlibgit2")); - cl_git_pass(git_remote_lookup(&mod, _repo, name)); - cl_assert_equal_s(git_remote_pushurl(mod), "git://github.com/libgit2/notlibgit2"); - git_remote_free(mod); - - cl_git_pass(git_remote_set_pushurl(_repo, name, NULL)); - cl_git_pass(git_remote_lookup(&mod, _repo, name)); - cl_assert(git_remote_pushurl(mod) == NULL); - git_remote_free(mod); -} - -void test_network_remote_remotes__error_when_not_found(void) -{ - git_remote *r; - cl_git_fail_with(git_remote_lookup(&r, _repo, "does-not-exist"), GIT_ENOTFOUND); - - cl_assert(git_error_last() != NULL); - cl_assert(git_error_last()->klass == GIT_ERROR_CONFIG); -} - -void test_network_remote_remotes__error_when_no_push_available(void) -{ - git_remote *r; - git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; - char *specs = { - "refs/heads/master", - }; - git_strarray arr = { - &specs, - 1, - }; - - - cl_git_pass(git_remote_create_anonymous(&r, _repo, cl_fixture("testrepo.git"))); - - callbacks.transport = git_transport_local; - cl_git_pass(git_remote_connect(r, GIT_DIRECTION_PUSH, &callbacks, NULL, NULL)); - - /* Make sure that push is really not available */ - r->transport->push = NULL; - - cl_git_fail_with(-1, git_remote_upload(r, &arr, NULL)); - - git_remote_free(r); -} - -void test_network_remote_remotes__refspec_parsing(void) -{ - cl_assert_equal_s(git_refspec_src(_refspec), "refs/heads/*"); - cl_assert_equal_s(git_refspec_dst(_refspec), "refs/remotes/test/*"); -} - -void test_network_remote_remotes__add_fetchspec(void) -{ - size_t size; - - size = git_remote_refspec_count(_remote); - - cl_git_pass(git_remote_add_fetch(_repo, "test", "refs/*:refs/*")); - size++; - - git_remote_free(_remote); - cl_git_pass(git_remote_lookup(&_remote, _repo, "test")); - - cl_assert_equal_i((int)size, (int)git_remote_refspec_count(_remote)); - - _refspec = git_remote_get_refspec(_remote, size - 1); - cl_assert_equal_s(git_refspec_src(_refspec), "refs/*"); - cl_assert_equal_s(git_refspec_dst(_refspec), "refs/*"); - cl_assert_equal_s(git_refspec_string(_refspec), "refs/*:refs/*"); - cl_assert_equal_b(_refspec->push, false); - - cl_git_fail_with(GIT_EINVALIDSPEC, git_remote_add_fetch(_repo, "test", "refs/*/foo/*:refs/*")); -} - -void test_network_remote_remotes__dup(void) -{ - git_strarray array; - git_remote *dup; - - cl_git_pass(git_remote_dup(&dup, _remote)); - - cl_assert_equal_s(git_remote_name(dup), git_remote_name(_remote)); - cl_assert_equal_s(git_remote_url(dup), git_remote_url(_remote)); - cl_assert_equal_s(git_remote_pushurl(dup), git_remote_pushurl(_remote)); - - cl_git_pass(git_remote_get_fetch_refspecs(&array, _remote)); - cl_assert_equal_i(1, (int)array.count); - cl_assert_equal_s("+refs/heads/*:refs/remotes/test/*", array.strings[0]); - git_strarray_dispose(&array); - - cl_git_pass(git_remote_get_push_refspecs(&array, _remote)); - cl_assert_equal_i(0, (int)array.count); - git_strarray_dispose(&array); - - git_remote_free(dup); -} - -void test_network_remote_remotes__add_pushspec(void) -{ - size_t size; - - size = git_remote_refspec_count(_remote); - - cl_git_pass(git_remote_add_push(_repo, "test", "refs/*:refs/*")); - size++; - - git_remote_free(_remote); - cl_git_pass(git_remote_lookup(&_remote, _repo, "test")); - - cl_assert_equal_i((int)size, (int)git_remote_refspec_count(_remote)); - - _refspec = git_remote_get_refspec(_remote, size - 1); - cl_assert_equal_s(git_refspec_src(_refspec), "refs/*"); - cl_assert_equal_s(git_refspec_dst(_refspec), "refs/*"); - cl_assert_equal_s(git_refspec_string(_refspec), "refs/*:refs/*"); - - cl_assert_equal_b(_refspec->push, true); -} - -void test_network_remote_remotes__fnmatch(void) -{ - cl_assert(git_refspec_src_matches(_refspec, "refs/heads/master")); - cl_assert(git_refspec_src_matches(_refspec, "refs/heads/multi/level/branch")); -} - -void test_network_remote_remotes__transform(void) -{ - git_buf ref = GIT_BUF_INIT; - - cl_git_pass(git_refspec_transform(&ref, _refspec, "refs/heads/master")); - cl_assert_equal_s(ref.ptr, "refs/remotes/test/master"); - git_buf_dispose(&ref); -} - -void test_network_remote_remotes__transform_destination_to_source(void) -{ - git_buf ref = GIT_BUF_INIT; - - cl_git_pass(git_refspec_rtransform(&ref, _refspec, "refs/remotes/test/master")); - cl_assert_equal_s(ref.ptr, "refs/heads/master"); - git_buf_dispose(&ref); -} - -void test_network_remote_remotes__missing_refspecs(void) -{ - git_config *cfg; - - git_remote_free(_remote); - _remote = NULL; - - cl_git_pass(git_repository_config(&cfg, _repo)); - cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); - cl_git_pass(git_remote_lookup(&_remote, _repo, "specless")); - - git_config_free(cfg); -} - -void test_network_remote_remotes__nonmatch_upstream_refspec(void) -{ - git_config *config; - git_remote *remote; - char *specstr[] = { - "refs/tags/*:refs/tags/*", - }; - git_strarray specs = { - specstr, - 1, - }; - - cl_git_pass(git_remote_create(&remote, _repo, "taggy", git_repository_path(_repo))); - - /* - * Set the current branch's upstream remote to a dummy ref so we call into the code - * which tries to check for the current branch's upstream in the refspecs - */ - cl_git_pass(git_repository_config(&config, _repo)); - cl_git_pass(git_config_set_string(config, "branch.master.remote", "taggy")); - cl_git_pass(git_config_set_string(config, "branch.master.merge", "refs/heads/foo")); - - cl_git_pass(git_remote_fetch(remote, &specs, NULL, NULL)); - - git_remote_free(remote); -} - -void test_network_remote_remotes__list(void) -{ - git_strarray list; - git_config *cfg; - - cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 5); - git_strarray_dispose(&list); - - cl_git_pass(git_repository_config(&cfg, _repo)); - - /* Create a new remote */ - cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); - - /* Update a remote (previously without any url/pushurl entry) */ - cl_git_pass(git_config_set_string(cfg, "remote.no-remote-url.pushurl", "http://example.com")); - - cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 7); - git_strarray_dispose(&list); - - git_config_free(cfg); -} - -void test_network_remote_remotes__loading_a_missing_remote_returns_ENOTFOUND(void) -{ - git_remote_free(_remote); - _remote = NULL; - - cl_assert_equal_i(GIT_ENOTFOUND, git_remote_lookup(&_remote, _repo, "just-left-few-minutes-ago")); -} - -void test_network_remote_remotes__loading_with_an_invalid_name_returns_EINVALIDSPEC(void) -{ - git_remote_free(_remote); - _remote = NULL; - - cl_assert_equal_i(GIT_EINVALIDSPEC, git_remote_lookup(&_remote, _repo, "Inv@{id")); -} - -/* - * $ git remote add addtest http://github.com/libgit2/libgit2 - * - * $ cat .git/config - * [...] - * [remote "addtest"] - * url = http://github.com/libgit2/libgit2 - * fetch = +refs/heads/\*:refs/remotes/addtest/\* - */ -void test_network_remote_remotes__add(void) -{ - git_remote_free(_remote); - _remote = NULL; - - cl_git_pass(git_remote_create(&_remote, _repo, "addtest", "http://github.com/libgit2/libgit2")); - cl_assert_equal_i(GIT_REMOTE_DOWNLOAD_TAGS_AUTO, git_remote_autotag(_remote)); - - git_remote_free(_remote); - _remote = NULL; - - cl_git_pass(git_remote_lookup(&_remote, _repo, "addtest")); - cl_assert_equal_i(GIT_REMOTE_DOWNLOAD_TAGS_AUTO, git_remote_autotag(_remote)); - - _refspec = git_vector_get(&_remote->refspecs, 0); - cl_assert_equal_s("refs/heads/*", git_refspec_src(_refspec)); - cl_assert(git_refspec_force(_refspec) == 1); - cl_assert_equal_s("refs/remotes/addtest/*", git_refspec_dst(_refspec)); - cl_assert_equal_s(git_remote_url(_remote), "http://github.com/libgit2/libgit2"); -} - -void test_network_remote_remotes__tagopt(void) -{ - const char *name = git_remote_name(_remote); - - git_remote_set_autotag(_repo, name, GIT_REMOTE_DOWNLOAD_TAGS_ALL); - assert_config_entry_value(_repo, "remote.test.tagopt", "--tags"); - - git_remote_set_autotag(_repo, name, GIT_REMOTE_DOWNLOAD_TAGS_NONE); - assert_config_entry_value(_repo, "remote.test.tagopt", "--no-tags"); - - git_remote_set_autotag(_repo, name, GIT_REMOTE_DOWNLOAD_TAGS_AUTO); - assert_config_entry_existence(_repo, "remote.test.tagopt", false); -} - -void test_network_remote_remotes__can_load_with_an_empty_url(void) -{ - git_remote *remote = NULL; - - cl_git_pass(git_remote_lookup(&remote, _repo, "empty-remote-url")); - - cl_assert(remote->url == NULL); - cl_assert(remote->pushurl == NULL); - - cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - - cl_assert(git_error_last() != NULL); - cl_assert(git_error_last()->klass == GIT_ERROR_INVALID); - - git_remote_free(remote); -} - -void test_network_remote_remotes__can_load_with_only_an_empty_pushurl(void) -{ - git_remote *remote = NULL; - - cl_git_pass(git_remote_lookup(&remote, _repo, "empty-remote-pushurl")); - - cl_assert(remote->url == NULL); - cl_assert(remote->pushurl == NULL); - - cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - - git_remote_free(remote); -} - -void test_network_remote_remotes__returns_ENOTFOUND_when_neither_url_nor_pushurl(void) -{ - git_remote *remote = NULL; - - cl_git_fail_with( - git_remote_lookup(&remote, _repo, "no-remote-url"), GIT_ENOTFOUND); -} - -static const char *fetch_refspecs[] = { - "+refs/heads/*:refs/remotes/origin/*", - "refs/tags/*:refs/tags/*", - "+refs/pull/*:refs/pull/*", -}; - -static const char *push_refspecs[] = { - "refs/heads/*:refs/heads/*", - "refs/tags/*:refs/tags/*", - "refs/notes/*:refs/notes/*", -}; - -void test_network_remote_remotes__query_refspecs(void) -{ - git_remote *remote; - git_strarray array; - int i; - - cl_git_pass(git_remote_create_with_fetchspec(&remote, _repo, "query", "git://github.com/libgit2/libgit2", NULL)); - git_remote_free(remote); - - for (i = 0; i < 3; i++) { - cl_git_pass(git_remote_add_fetch(_repo, "query", fetch_refspecs[i])); - cl_git_pass(git_remote_add_push(_repo, "query", push_refspecs[i])); - } - - cl_git_pass(git_remote_lookup(&remote, _repo, "query")); - - cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); - for (i = 0; i < 3; i++) { - cl_assert_equal_s(fetch_refspecs[i], array.strings[i]); - } - git_strarray_dispose(&array); - - cl_git_pass(git_remote_get_push_refspecs(&array, remote)); - for (i = 0; i < 3; i++) { - cl_assert_equal_s(push_refspecs[i], array.strings[i]); - } - git_strarray_dispose(&array); - - git_remote_free(remote); - git_remote_delete(_repo, "test"); -} diff --git a/tests/network/remote/rename.c b/tests/network/remote/rename.c deleted file mode 100644 index 1fd2affba..000000000 --- a/tests/network/remote/rename.c +++ /dev/null @@ -1,245 +0,0 @@ -#include "clar_libgit2.h" -#include "config/config_helpers.h" - -#include "repository.h" - -static git_repository *_repo; -static const char *_remote_name = "test"; - -void test_network_remote_rename__initialize(void) -{ - _repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_network_remote_rename__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_network_remote_rename__renaming_a_remote_moves_related_configuration_section(void) -{ - git_strarray problems = {0}; - - assert_config_entry_existence(_repo, "remote.test.fetch", true); - assert_config_entry_existence(_repo, "remote.just/renamed.fetch", false); - - cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); - cl_assert_equal_i(0, problems.count); - git_strarray_dispose(&problems); - - assert_config_entry_existence(_repo, "remote.test.fetch", false); - assert_config_entry_existence(_repo, "remote.just/renamed.fetch", true); -} - -void test_network_remote_rename__renaming_a_remote_updates_branch_related_configuration_entries(void) -{ - git_strarray problems = {0}; - - assert_config_entry_value(_repo, "branch.master.remote", "test"); - - cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); - cl_assert_equal_i(0, problems.count); - git_strarray_dispose(&problems); - - assert_config_entry_value(_repo, "branch.master.remote", "just/renamed"); -} - -void test_network_remote_rename__renaming_a_remote_updates_default_fetchrefspec(void) -{ - git_strarray problems = {0}; - - cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); - cl_assert_equal_i(0, problems.count); - git_strarray_dispose(&problems); - - assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/heads/*:refs/remotes/just/renamed/*"); -} - -void test_network_remote_rename__renaming_a_remote_without_a_fetchrefspec_doesnt_create_one(void) -{ - git_config *config; - git_remote *remote; - git_strarray problems = {0}; - - cl_git_pass(git_repository_config__weakptr(&config, _repo)); - cl_git_pass(git_config_delete_entry(config, "remote.test.fetch")); - - cl_git_pass(git_remote_lookup(&remote, _repo, "test")); - git_remote_free(remote); - - assert_config_entry_existence(_repo, "remote.test.fetch", false); - - cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); - cl_assert_equal_i(0, problems.count); - git_strarray_dispose(&problems); - - assert_config_entry_existence(_repo, "remote.just/renamed.fetch", false); -} - -void test_network_remote_rename__renaming_a_remote_notifies_of_non_default_fetchrefspec(void) -{ - git_config *config; - git_remote *remote; - git_strarray problems = {0}; - - cl_git_pass(git_repository_config__weakptr(&config, _repo)); - cl_git_pass(git_config_set_string(config, "remote.test.fetch", "+refs/*:refs/*")); - cl_git_pass(git_remote_lookup(&remote, _repo, "test")); - git_remote_free(remote); - - cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); - cl_assert_equal_i(1, problems.count); - cl_assert_equal_s("+refs/*:refs/*", problems.strings[0]); - git_strarray_dispose(&problems); - - assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/*:refs/*"); - - git_strarray_dispose(&problems); -} - -void test_network_remote_rename__new_name_can_contain_dots(void) -{ - git_strarray problems = {0}; - - cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just.renamed")); - cl_assert_equal_i(0, problems.count); - git_strarray_dispose(&problems); - assert_config_entry_existence(_repo, "remote.just.renamed.fetch", true); -} - -void test_network_remote_rename__new_name_must_conform_to_reference_naming_conventions(void) -{ - git_strarray problems = {0}; - - cl_assert_equal_i( - GIT_EINVALIDSPEC, - git_remote_rename(&problems, _repo, _remote_name, "new@{name")); -} - -void test_network_remote_rename__renamed_name_is_persisted(void) -{ - git_remote *renamed; - git_repository *another_repo; - git_strarray problems = {0}; - - cl_git_fail(git_remote_lookup(&renamed, _repo, "just/renamed")); - - cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); - cl_assert_equal_i(0, problems.count); - git_strarray_dispose(&problems); - - cl_git_pass(git_repository_open(&another_repo, "testrepo.git")); - cl_git_pass(git_remote_lookup(&renamed, _repo, "just/renamed")); - - git_remote_free(renamed); - git_repository_free(another_repo); -} - -void test_network_remote_rename__cannot_overwrite_an_existing_remote(void) -{ - git_strarray problems = {0}; - - cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(&problems, _repo, _remote_name, "test")); - cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(&problems, _repo, _remote_name, "test_with_pushurl")); -} - -void test_network_remote_rename__renaming_a_remote_moves_the_underlying_reference(void) -{ - git_reference *underlying; - git_strarray problems = {0}; - - cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&underlying, _repo, "refs/remotes/just/renamed")); - cl_git_pass(git_reference_lookup(&underlying, _repo, "refs/remotes/test/master")); - git_reference_free(underlying); - - cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "just/renamed")); - cl_assert_equal_i(0, problems.count); - git_strarray_dispose(&problems); - - cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&underlying, _repo, "refs/remotes/test/master")); - cl_git_pass(git_reference_lookup(&underlying, _repo, "refs/remotes/just/renamed/master")); - git_reference_free(underlying); -} - -void test_network_remote_rename__overwrite_ref_in_target(void) -{ - git_oid id; - char idstr[GIT_OID_HEXSZ + 1] = {0}; - git_reference *ref; - git_branch_t btype; - git_branch_iterator *iter; - git_strarray problems = {0}; - - cl_git_pass(git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); - cl_git_pass(git_reference_create(&ref, _repo, "refs/remotes/renamed/master", &id, 1, NULL)); - git_reference_free(ref); - - cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "renamed")); - cl_assert_equal_i(0, problems.count); - git_strarray_dispose(&problems); - - /* make sure there's only one remote-tracking branch */ - cl_git_pass(git_branch_iterator_new(&iter, _repo, GIT_BRANCH_REMOTE)); - cl_git_pass(git_branch_next(&ref, &btype, iter)); - cl_assert_equal_s("refs/remotes/renamed/master", git_reference_name(ref)); - git_oid_fmt(idstr, git_reference_target(ref)); - cl_assert_equal_s("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", idstr); - git_reference_free(ref); - - cl_git_fail_with(GIT_ITEROVER, git_branch_next(&ref, &btype, iter)); - git_branch_iterator_free(iter); -} - -void test_network_remote_rename__nonexistent_returns_enotfound(void) -{ - git_strarray problems = {0}; - - int err = git_remote_rename(&problems, _repo, "nonexistent", "renamed"); - - cl_assert_equal_i(GIT_ENOTFOUND, err); -} - -void test_network_remote_rename__symref_head(void) -{ - int error; - git_reference *ref; - git_branch_t btype; - git_branch_iterator *iter; - git_strarray problems = {0}; - char idstr[GIT_OID_HEXSZ + 1] = {0}; - git_vector refs; - - cl_git_pass(git_reference_symbolic_create(&ref, _repo, "refs/remotes/test/HEAD", "refs/remotes/test/master", 0, NULL)); - git_reference_free(ref); - - cl_git_pass(git_remote_rename(&problems, _repo, _remote_name, "renamed")); - cl_assert_equal_i(0, problems.count); - git_strarray_dispose(&problems); - - cl_git_pass(git_vector_init(&refs, 2, (git_vector_cmp) git_reference_cmp)); - cl_git_pass(git_branch_iterator_new(&iter, _repo, GIT_BRANCH_REMOTE)); - - while ((error = git_branch_next(&ref, &btype, iter)) == 0) { - cl_git_pass(git_vector_insert(&refs, ref)); - } - cl_assert_equal_i(GIT_ITEROVER, error); - git_vector_sort(&refs); - - cl_assert_equal_i(2, refs.length); - - ref = git_vector_get(&refs, 0); - cl_assert_equal_s("refs/remotes/renamed/HEAD", git_reference_name(ref)); - cl_assert_equal_s("refs/remotes/renamed/master", git_reference_symbolic_target(ref)); - git_reference_free(ref); - - ref = git_vector_get(&refs, 1); - cl_assert_equal_s("refs/remotes/renamed/master", git_reference_name(ref)); - git_oid_fmt(idstr, git_reference_target(ref)); - cl_assert_equal_s("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", idstr); - git_reference_free(ref); - - git_vector_free(&refs); - - cl_git_fail_with(GIT_ITEROVER, git_branch_next(&ref, &btype, iter)); - git_branch_iterator_free(iter); -} diff --git a/tests/network/url/joinpath.c b/tests/network/url/joinpath.c deleted file mode 100644 index bf4557138..000000000 --- a/tests/network/url/joinpath.c +++ /dev/null @@ -1,194 +0,0 @@ -#include "clar_libgit2.h" -#include "net.h" -#include "netops.h" - -static git_net_url source, target; - -void test_network_url_joinpath__initialize(void) -{ - memset(&source, 0, sizeof(source)); - memset(&target, 0, sizeof(target)); -} - -void test_network_url_joinpath__cleanup(void) -{ - git_net_url_dispose(&source); - git_net_url_dispose(&target); -} - -void test_network_url_joinpath__target_paths_and_queries(void) -{ - cl_git_pass(git_net_url_parse(&source, "http://example.com/a/b")); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d")); - cl_assert_equal_s(target.path, "/a/b/c/d"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d?foo")); - cl_assert_equal_s(target.path, "/a/b/c/d"); - cl_assert_equal_s(target.query, "foo"); - git_net_url_dispose(&target); -} - -void test_network_url_joinpath__source_query_removed(void) -{ - cl_git_pass(git_net_url_parse(&source, "http://example.com/a/b?query&one&two")); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d")); - cl_assert_equal_s(target.path, "/a/b/c/d"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d?foo")); - cl_assert_equal_s(target.path, "/a/b/c/d"); - cl_assert_equal_s(target.query, "foo"); - git_net_url_dispose(&target); -} - -void test_network_url_joinpath__source_lacks_path(void) -{ - cl_git_pass(git_net_url_parse(&source, "http://example.com")); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/")); - cl_assert_equal_s(target.path, "/"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "")); - cl_assert_equal_s(target.path, "/"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "asdf")); - cl_assert_equal_s(target.path, "/asdf"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf")); - cl_assert_equal_s(target.path, "/asdf"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar")); - cl_assert_equal_s(target.path, "/foo/bar"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello")); - cl_assert_equal_s(target.path, "/asdf"); - cl_assert_equal_s(target.query, "hello"); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello")); - cl_assert_equal_s(target.path, "/asdf"); - cl_assert_equal_s(target.query, "hello"); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello")); - cl_assert_equal_s(target.path, "/foo/bar"); - cl_assert_equal_s(target.query, "hello"); - git_net_url_dispose(&target); -} - -void test_network_url_joinpath__source_is_slash(void) -{ - cl_git_pass(git_net_url_parse(&source, "http://example.com/")); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/")); - cl_assert_equal_s(target.path, "/"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "")); - cl_assert_equal_s(target.path, "/"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "asdf")); - cl_assert_equal_s(target.path, "/asdf"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf")); - cl_assert_equal_s(target.path, "/asdf"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar")); - cl_assert_equal_s(target.path, "/foo/bar"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello")); - cl_assert_equal_s(target.path, "/asdf"); - cl_assert_equal_s(target.query, "hello"); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello")); - cl_assert_equal_s(target.path, "/asdf"); - cl_assert_equal_s(target.query, "hello"); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello")); - cl_assert_equal_s(target.path, "/foo/bar"); - cl_assert_equal_s(target.query, "hello"); - git_net_url_dispose(&target); -} - - -void test_network_url_joinpath__source_has_query(void) -{ - cl_git_pass(git_net_url_parse(&source, "http://example.com?query")); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/")); - cl_assert_equal_s(target.path, "/"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "")); - cl_assert_equal_s(target.path, "/"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "asdf")); - cl_assert_equal_s(target.path, "/asdf"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf")); - cl_assert_equal_s(target.path, "/asdf"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar")); - cl_assert_equal_s(target.path, "/foo/bar"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello")); - cl_assert_equal_s(target.path, "/asdf"); - cl_assert_equal_s(target.query, "hello"); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello")); - cl_assert_equal_s(target.path, "/asdf"); - cl_assert_equal_s(target.query, "hello"); - git_net_url_dispose(&target); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello")); - cl_assert_equal_s(target.path, "/foo/bar"); - cl_assert_equal_s(target.query, "hello"); - git_net_url_dispose(&target); -} - - -void test_network_url_joinpath__empty_query_ignored(void) -{ - cl_git_pass(git_net_url_parse(&source, "http://example.com/foo")); - - cl_git_pass(git_net_url_joinpath(&target, &source, "/bar/baz?")); - cl_assert_equal_s(target.path, "/foo/bar/baz"); - cl_assert_equal_p(target.query, NULL); - git_net_url_dispose(&target); -} diff --git a/tests/network/url/parse.c b/tests/network/url/parse.c deleted file mode 100644 index 8149ba52c..000000000 --- a/tests/network/url/parse.c +++ /dev/null @@ -1,557 +0,0 @@ -#include "clar_libgit2.h" -#include "net.h" - -static git_net_url conndata; - -void test_network_url_parse__initialize(void) -{ - memset(&conndata, 0, sizeof(conndata)); -} - -void test_network_url_parse__cleanup(void) -{ - git_net_url_dispose(&conndata); -} - -/* Hostname */ - -void test_network_url_parse__hostname_trivial(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://example.com/resource")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__hostname_root(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://example.com/")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__hostname_implied_root(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://example.com")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__hostname_implied_root_custom_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://example.com:42")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "42"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_parse__hostname_implied_root_empty_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://example.com:")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__hostname_encoded_password(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://user:pass%2fis%40bad@hostname.com:1234/")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "hostname.com"); - cl_assert_equal_s(conndata.port, "1234"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_s(conndata.password, "pass/is@bad"); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_parse__hostname_user(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://user@example.com/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__hostname_user_pass(void) -{ - /* user:pass@hostname.tld/resource */ - cl_git_pass(git_net_url_parse(&conndata, - "https://user:pass@example.com/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_s(conndata.password, "pass"); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__hostname_port(void) -{ - /* hostname.tld:port/resource */ - cl_git_pass(git_net_url_parse(&conndata, - "https://example.com:9191/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "9191"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_parse__hostname_empty_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://example.com:/resource")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__hostname_user_port(void) -{ - /* user@hostname.tld:port/resource */ - cl_git_pass(git_net_url_parse(&conndata, - "https://user@example.com:9191/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "9191"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_parse__hostname_user_pass_port(void) -{ - /* user:pass@hostname.tld:port/resource */ - cl_git_pass(git_net_url_parse(&conndata, - "https://user:pass@example.com:9191/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "9191"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_s(conndata.password, "pass"); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -/* IPv4 addresses */ - -void test_network_url_parse__ipv4_trivial(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1/resource")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "192.168.1.1"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv4_root(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1/")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "192.168.1.1"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv4_implied_root(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "192.168.1.1"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv4_implied_root_custom_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1:42")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "192.168.1.1"); - cl_assert_equal_s(conndata.port, "42"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_parse__ipv4_implied_root_empty_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1:")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "192.168.1.1"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv4_encoded_password(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://user:pass%2fis%40bad@192.168.1.1:1234/")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "192.168.1.1"); - cl_assert_equal_s(conndata.port, "1234"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_s(conndata.password, "pass/is@bad"); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_parse__ipv4_user(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://user@192.168.1.1/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "192.168.1.1"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv4_user_pass(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://user:pass@192.168.1.1/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "192.168.1.1"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_s(conndata.password, "pass"); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv4_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://192.168.1.1:9191/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "192.168.1.1"); - cl_assert_equal_s(conndata.port, "9191"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_parse__ipv4_empty_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1:/resource")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "192.168.1.1"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv4_user_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://user@192.168.1.1:9191/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "192.168.1.1"); - cl_assert_equal_s(conndata.port, "9191"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_parse__ipv4_user_pass_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://user:pass@192.168.1.1:9191/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "192.168.1.1"); - cl_assert_equal_s(conndata.port, "9191"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_s(conndata.password, "pass"); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -/* IPv6 addresses */ - -void test_network_url_parse__ipv6_trivial(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]/resource")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv6_root(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]/")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv6_implied_root(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv6_implied_root_custom_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]:42")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); - cl_assert_equal_s(conndata.port, "42"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_parse__ipv6_implied_root_empty_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]:")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv6_encoded_password(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://user:pass%2fis%40bad@[fe80::dcad:beff:fe00:0001]:1234/")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); - cl_assert_equal_s(conndata.port, "1234"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_s(conndata.password, "pass/is@bad"); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_parse__ipv6_user(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://user@[fe80::dcad:beff:fe00:0001]/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv6_user_pass(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://user:pass@[fe80::dcad:beff:fe00:0001]/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_s(conndata.password, "pass"); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv6_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://[fe80::dcad:beff:fe00:0001]:9191/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); - cl_assert_equal_s(conndata.port, "9191"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_parse__ipv6_empty_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]:/resource")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_parse__ipv6_user_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://user@[fe80::dcad:beff:fe00:0001]:9191/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); - cl_assert_equal_s(conndata.port, "9191"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_parse__ipv6_user_pass_port(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://user:pass@[fe80::dcad:beff:fe00:0001]:9191/resource")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); - cl_assert_equal_s(conndata.port, "9191"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "user"); - cl_assert_equal_s(conndata.password, "pass"); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_parse__ipv6_invalid_addresses(void) -{ - /* Opening bracket missing */ - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://fe80::dcad:beff:fe00:0001]/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://fe80::dcad:beff:fe00:0001]/")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://fe80::dcad:beff:fe00:0001]")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://fe80::dcad:beff:fe00:0001]:42")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://fe80::dcad:beff:fe00:0001]:")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user:pass%2fis%40bad@fe80::dcad:beff:fe00:0001]:1234/")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user@fe80::dcad:beff:fe00:0001]/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user:pass@fe80::dcad:beff:fe00:0001]/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://fe80::dcad:beff:fe00:0001]:9191/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://fe80::dcad:beff:fe00:0001]:/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user@fe80::dcad:beff:fe00:0001]:9191/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user:pass@fe80::dcad:beff:fe00:0001]:9191/resource")); - - /* Closing bracket missing */ - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://[fe80::dcad:beff:fe00:0001/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://[fe80::dcad:beff:fe00:0001/")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://[fe80::dcad:beff:fe00:0001")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://[fe80::dcad:beff:fe00:0001:42")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://[fe80::dcad:beff:fe00:0001:")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user:pass%2fis%40bad@[fe80::dcad:beff:fe00:0001:1234/")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user@[fe80::dcad:beff:fe00:0001/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user:pass@[fe80::dcad:beff:fe00:0001/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://[fe80::dcad:beff:fe00:0001:9191/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://[fe80::dcad:beff:fe00:0001:/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user@[fe80::dcad:beff:fe00:0001:9191/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user:pass@[fe80::dcad:beff:fe00:0001:9191/resource")); - /* Both brackets missing */ - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://fe80::dcad:beff:fe00:0001/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://fe80::dcad:beff:fe00:0001/")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://fe80::dcad:beff:fe00:0001")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://fe80::dcad:beff:fe00:0001:42")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://fe80::dcad:beff:fe00:0001:")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user:pass%2fis%40bad@fe80::dcad:beff:fe00:0001:1234/")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user@fe80::dcad:beff:fe00:0001/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user:pass@fe80::dcad:beff:fe00:0001/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://fe80::dcad:beff:fe00:0001:9191/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "http://fe80::dcad:beff:fe00:0001:/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user@fe80::dcad:beff:fe00:0001:9191/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "https://user:pass@fe80::dcad:beff:fe00:0001:9191/resource")); - - /* Invalid character inside address */ - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, "http://[fe8o::dcad:beff:fe00:0001]/resource")); -} diff --git a/tests/network/url/pattern.c b/tests/network/url/pattern.c deleted file mode 100644 index 5e4495f70..000000000 --- a/tests/network/url/pattern.c +++ /dev/null @@ -1,103 +0,0 @@ -#include "clar_libgit2.h" -#include "net.h" - -struct url_pattern { - const char *url; - const char *pattern; - bool matches; -}; - -void test_network_url_pattern__single(void) -{ - git_net_url url; - size_t i; - - struct url_pattern url_patterns[] = { - /* Wildcard matches */ - { "https://example.com/", "", false }, - { "https://example.com/", "*", true }, - - /* Literal and wildcard matches */ - { "https://example.com/", "example.com", true }, - { "https://example.com/", ".example.com", true }, - { "https://example.com/", "*.example.com", true }, - { "https://www.example.com/", "www.example.com", true }, - { "https://www.example.com/", ".example.com", true }, - { "https://www.example.com/", "*.example.com", true }, - - /* Literal and wildcard failures */ - { "https://example.com/", "example.org", false }, - { "https://example.com/", ".example.org", false }, - { "https://example.com/", "*.example.org", false }, - { "https://foo.example.com/", "www.example.com", false }, - - /* - * A port in the pattern is optional; if no port is - * present, it matches *all* ports. - */ - { "https://example.com/", "example.com:443", true }, - { "https://example.com/", "example.com:80", false }, - { "https://example.com:1443/", "example.com", true }, - - /* Failures with similar prefix/suffix */ - { "https://texample.com/", "example.com", false }, - { "https://example.com/", "mexample.com", false }, - { "https://example.com:44/", "example.com:443", false }, - { "https://example.com:443/", "example.com:44", false }, - }; - - for (i = 0; i < ARRAY_SIZE(url_patterns); i++) { - cl_git_pass(git_net_url_parse(&url, url_patterns[i].url)); - cl_assert_(git_net_url_matches_pattern(&url, url_patterns[i].pattern) == url_patterns[i].matches, url_patterns[i].pattern); - git_net_url_dispose(&url); - } -} - -void test_network_url_pattern__list(void) -{ - git_net_url url; - size_t i; - - struct url_pattern url_patterns[] = { - /* Wildcard matches */ - { "https://example.com/", "", false }, - { "https://example.com/", "*", true }, - { "https://example.com/", ",example.com,", true }, - { "https://example.com/", "foo,,example.com,,bar", true }, - { "https://example.com/", "foo,,zzz,,*,,bar", true }, - - /* Literals */ - { "https://example.com/", "example.com", true }, - { "https://example.com/", "foo.bar,example.com", true }, - { "https://example.com/", "foo.bar", false }, - { "https://example.com/", "foo.bar,example.org", false }, - { "https://www.example.com/", "foo.example.com,www.example.com,bar.example.com", true }, - { "https://www.example.com/", "foo.example.com,baz.example.com,bar.example.com", false }, - { "https://foo.example.com/", "www.example.com", false }, - { "https://foo.example.com/", "bar.example.com,www.example.com,", false }, - - /* Wildcards */ - { "https://example.com/", ".example.com", true }, - { "https://example.com/", "*.example.com", true }, - { "https://example.com/", "foo.com,bar.com,.example.com", true }, - { "https://example.com/", ".foo.com,.bar.com,.example.com", true }, - { "https://example.com/", ".foo.com,.bar.com,asdf.com", false }, - { "https://example.com/", "*.foo,*.bar,*.example.com,*.asdf", true }, - { "https://example.com/", "*.foo,*.bar,*.asdf", false }, - - - /* Ports! */ - { "https://example.com/", "example.com:443", true }, - { "https://example.com/", "example.com:42,example.com:443,example.com:99", true }, - { "https://example.com/", "example.com:42,example.com:80,example.org:443", false }, - { "https://example.com:1443/", "example.com", true }, - { "https://example.com:44/", "example.com:443", false }, - { "https://example.com:443/", "example.com:44", false }, - }; - - for (i = 0; i < ARRAY_SIZE(url_patterns); i++) { - cl_git_pass(git_net_url_parse(&url, url_patterns[i].url)); - cl_assert_(git_net_url_matches_pattern_list(&url, url_patterns[i].pattern) == url_patterns[i].matches, url_patterns[i].pattern); - git_net_url_dispose(&url); - } -} diff --git a/tests/network/url/redirect.c b/tests/network/url/redirect.c deleted file mode 100644 index a94db7daf..000000000 --- a/tests/network/url/redirect.c +++ /dev/null @@ -1,147 +0,0 @@ -#include "clar_libgit2.h" -#include "net.h" -#include "netops.h" - -static git_net_url conndata; - -void test_network_url_redirect__initialize(void) -{ - memset(&conndata, 0, sizeof(conndata)); -} - -void test_network_url_redirect__cleanup(void) -{ - git_net_url_dispose(&conndata); -} - -void test_network_url_redirect__redirect_http(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "http://example.com/foo/bar/baz")); - cl_git_pass(git_net_url_apply_redirect(&conndata, - "http://example.com/foo/bar/baz", false, "bar/baz")); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/foo/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); -} - -void test_network_url_redirect__redirect_ssl(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://example.com/foo/bar/baz")); - cl_git_pass(git_net_url_apply_redirect(&conndata, - "https://example.com/foo/bar/baz", false, "bar/baz")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/foo/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); -} - -void test_network_url_redirect__redirect_leaves_root_path(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://example.com/foo/bar/baz")); - cl_git_pass(git_net_url_apply_redirect(&conndata, - "https://example.com/foo/bar/baz", false, "/foo/bar/baz")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); -} - -void test_network_url_redirect__redirect_encoded_username_password(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz")); - cl_git_pass(git_net_url_apply_redirect(&conndata, - "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", false, "bar/baz")); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/foo/"); - cl_assert_equal_s(conndata.username, "user/name"); - cl_assert_equal_s(conndata.password, "pass@word%zyx%v"); -} - -void test_network_url_redirect__redirect_cross_host_allowed(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://bar.com/bar/baz")); - cl_git_pass(git_net_url_apply_redirect(&conndata, - "https://foo.com/bar/baz", true, NULL)); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "foo.com"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/bar/baz"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); -} - -void test_network_url_redirect__redirect_cross_host_denied(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://bar.com/bar/baz")); - cl_git_fail_with(git_net_url_apply_redirect(&conndata, - "https://foo.com/bar/baz", false, NULL), -1); -} - -void test_network_url_redirect__redirect_http_downgrade_denied(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://foo.com/bar/baz")); - cl_git_fail_with(git_net_url_apply_redirect(&conndata, - "http://foo.com/bar/baz", true, NULL), -1); -} - -void test_network_url_redirect__redirect_relative(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "http://foo.com/bar/baz/biff")); - cl_git_pass(git_net_url_apply_redirect(&conndata, - "/zap/baz/biff?bam", true, NULL)); - cl_assert_equal_s(conndata.scheme, "http"); - cl_assert_equal_s(conndata.host, "foo.com"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); -} - -void test_network_url_redirect__redirect_relative_ssl(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://foo.com/bar/baz/biff")); - cl_git_pass(git_net_url_apply_redirect(&conndata, - "/zap/baz/biff?bam", true, NULL)); - cl_assert_equal_s(conndata.scheme, "https"); - cl_assert_equal_s(conndata.host, "foo.com"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); -} - -void test_network_url_redirect__service_query_no_query_params_in_location(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://foo.com/bar/info/refs?service=git-upload-pack")); - cl_git_pass(git_net_url_apply_redirect(&conndata, - "/baz/info/refs", true, "/info/refs?service=git-upload-pack")); - cl_assert_equal_s(conndata.path, "/baz"); -} - -void test_network_url_redirect__service_query_with_query_params_in_location(void) -{ - cl_git_pass(git_net_url_parse(&conndata, - "https://foo.com/bar/info/refs?service=git-upload-pack")); - cl_git_pass(git_net_url_apply_redirect(&conndata, - "/baz/info/refs?service=git-upload-pack", true, "/info/refs?service=git-upload-pack")); - cl_assert_equal_s(conndata.path, "/baz"); -} diff --git a/tests/network/url/scp.c b/tests/network/url/scp.c deleted file mode 100644 index 8cdc832ae..000000000 --- a/tests/network/url/scp.c +++ /dev/null @@ -1,321 +0,0 @@ -#include "clar_libgit2.h" -#include "net.h" - -static git_net_url conndata; - -void test_network_url_scp__initialize(void) -{ - memset(&conndata, 0, sizeof(conndata)); -} - -void test_network_url_scp__cleanup(void) -{ - git_net_url_dispose(&conndata); -} - -/* Hostname */ - -void test_network_url_scp__hostname_trivial(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "example.com:/resource")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__hostname_bracketed(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "[example.com]:/resource")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__hostname_root(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "example.com:/")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, "/"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__hostname_user(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "git@example.com:/resource")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "git"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__hostname_user_bracketed(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "[git@example.com]:/resource")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "git"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__hostname_port(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "[example.com:42]:/resource")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "42"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_scp__hostname_user_port(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "[git@example.com:42]:/resource")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "42"); - cl_assert_equal_s(conndata.path, "/resource"); - cl_assert_equal_s(conndata.username, "git"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_scp__ipv4_trivial(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "192.168.99.88:/resource/a/b/c")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "192.168.99.88"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, "/resource/a/b/c"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__ipv4_bracketed(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "[192.168.99.88]:/resource/a/b/c")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "192.168.99.88"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, "/resource/a/b/c"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__ipv4_user(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "git@192.168.99.88:/resource/a/b/c")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "192.168.99.88"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, "/resource/a/b/c"); - cl_assert_equal_s(conndata.username, "git"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__ipv4_port(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "[192.168.99.88:1111]:/resource/a/b/c")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "192.168.99.88"); - cl_assert_equal_s(conndata.port, "1111"); - cl_assert_equal_s(conndata.path, "/resource/a/b/c"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_scp__ipv4_user_port(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "[git@192.168.99.88:1111]:/resource/a/b/c")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "192.168.99.88"); - cl_assert_equal_s(conndata.port, "1111"); - cl_assert_equal_s(conndata.path, "/resource/a/b/c"); - cl_assert_equal_s(conndata.username, "git"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_scp__ipv6_trivial(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "[fe80::dcad:beff:fe00:0001]:/resource/foo")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, "/resource/foo"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__ipv6_user(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "git@[fe80::dcad:beff:fe00:0001]:/resource/foo")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, "/resource/foo"); - cl_assert_equal_s(conndata.username, "git"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__ipv6_port(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "[[fe80::dcad:beff:fe00:0001]:99]:/resource/foo")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]"); - cl_assert_equal_s(conndata.port, "99"); - cl_assert_equal_s(conndata.path, "/resource/foo"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_scp__ipv6_user_port(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "[git@[fe80::dcad:beff:fe00:0001]:99]:/resource/foo")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]"); - cl_assert_equal_s(conndata.port, "99"); - cl_assert_equal_s(conndata.path, "/resource/foo"); - cl_assert_equal_s(conndata.username, "git"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); -} - -void test_network_url_scp__hexhost_and_port(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "[fe:22]:/resource/foo")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "fe"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, "/resource/foo"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__malformed_ipv6_one(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "fe80::dcad:beff:fe00:0001]:/resource")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "fe80"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, ":dcad:beff:fe00:0001]:/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__malformed_ipv6_two(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "[fe80::dcad:beff:fe00:0001]:42]:/resource")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, "42]:/resource"); - cl_assert_equal_p(conndata.username, NULL); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__malformed_ipv6_with_user(void) -{ - cl_git_pass(git_net_url_parse_scp(&conndata, "git@[fe80::dcad:beff:fe00:0001]:42]:/resource")); - cl_assert_equal_s(conndata.scheme, "ssh"); - cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]"); - cl_assert_equal_s(conndata.port, "22"); - cl_assert_equal_s(conndata.path, "42]:/resource"); - cl_assert_equal_s(conndata.username, "git"); - cl_assert_equal_p(conndata.password, NULL); - cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); -} - -void test_network_url_scp__invalid_addresses(void) -{ - /* Path is required */ - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "example.com")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "example.com:")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[example.com:42]:")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[git@example.com:42]:")); - - /* Host is required */ - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - ":")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - ":foo")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "git@:foo")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[]:")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "git@[]:")); - - /* User is required if specified */ - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "@example.com:foo")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "@:foo")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[@localhost:22]:foo")); - - /* Port is required in brackets */ - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[example.com:]:foo")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[git@example.com:]:foo")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[fe:]:foo")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[@localhost]:foo")); - - /* Extra brackets are disallowed */ - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[git@[[fe80::dcad:beff:fe00:0001]]:42]:foo")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[[git@[fe80::dcad:beff:fe00:0001]]:42]:foo")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[[git@[fe80::dcad:beff:fe00:0001]:42]]:foo")); - - /* Closing bracket missing */ - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[fe80::dcad:beff:fe00:0001:/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[[fe80::dcad:beff:fe00:0001]:42:/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[git@[fe80::dcad:beff:fe00:0001]:42:/resource")); - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata, - "[git@[fe80::dcad:beff:fe00:0001:42]:/resource")); - - /* Invalid character inside address */ - cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, - "[fe8o::dcad:beff:fe00:0001]:/resource")); -} diff --git a/tests/network/url/valid.c b/tests/network/url/valid.c deleted file mode 100644 index 2b2cb7ba4..000000000 --- a/tests/network/url/valid.c +++ /dev/null @@ -1,17 +0,0 @@ -#include "clar_libgit2.h" -#include "net.h" - -void test_network_url_valid__test(void) -{ - cl_assert(git_net_str_is_url("http://example.com/")); - cl_assert(git_net_str_is_url("file://localhost/tmp/foo/")); - cl_assert(git_net_str_is_url("ssh://user@host:42/tmp")); - cl_assert(git_net_str_is_url("git+ssh://user@host:42/tmp")); - cl_assert(git_net_str_is_url("ssh+git://user@host:42/tmp")); - cl_assert(git_net_str_is_url("https://user:pass@example.com/foo/bar")); - - cl_assert(!git_net_str_is_url("host:foo.git")); - cl_assert(!git_net_str_is_url("host:/foo.git")); - cl_assert(!git_net_str_is_url("[host:42]:/foo.git")); - cl_assert(!git_net_str_is_url("[user@host:42]:/foo.git")); -} diff --git a/tests/notes/notes.c b/tests/notes/notes.c deleted file mode 100644 index a36cddb8a..000000000 --- a/tests/notes/notes.c +++ /dev/null @@ -1,658 +0,0 @@ -#include "clar_libgit2.h" - -static git_repository *_repo; -static git_signature *_sig; - -void test_notes_notes__initialize(void) -{ - _repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_signature_now(&_sig, "alice", "alice@example.com")); -} - -void test_notes_notes__cleanup(void) -{ - git_signature_free(_sig); - _sig = NULL; - - cl_git_sandbox_cleanup(); -} - -static void assert_note_equal(git_note *note, char *message, git_oid *note_oid) { - git_blob *blob; - - cl_assert_equal_s(git_note_message(note), message); - cl_assert_equal_oid(git_note_id(note), note_oid); - - cl_git_pass(git_blob_lookup(&blob, _repo, note_oid)); - cl_assert_equal_s(git_note_message(note), (const char *)git_blob_rawcontent(blob)); - - git_blob_free(blob); -} - -static void create_note(git_oid *note_oid, const char *canonical_namespace, const char *target_sha, const char *message) -{ - git_oid oid; - - cl_git_pass(git_oid_fromstr(&oid, target_sha)); - cl_git_pass(git_note_create(note_oid, _repo, canonical_namespace, _sig, _sig, &oid, message, 0)); -} - -static struct { - const char *note_sha; - const char *annotated_object_sha; -} -list_expectations[] = { - { "1c73b1f51762155d357bcd1fd4f2c409ef80065b", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045" }, - { "1c73b1f51762155d357bcd1fd4f2c409ef80065b", "9fd738e8f7967c078dceed8190330fc8648ee56a" }, - { "257b43746b6b46caa4aa788376c647cce0a33e2b", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750" }, - { "1ec1c8e03f461f4f5d3f3702172483662e7223f3", "c47800c7266a2be04c571c04d5a6614691ea99bd" }, - { NULL, NULL } -}; - -#define EXPECTATIONS_COUNT (sizeof(list_expectations)/sizeof(list_expectations[0])) - 1 - -static int note_list_cb( - const git_oid *blob_id, const git_oid *annotated_obj_id, void *payload) -{ - git_oid expected_note_oid, expected_target_oid; - - unsigned int *count = (unsigned int *)payload; - - cl_assert(*count < EXPECTATIONS_COUNT); - - cl_git_pass(git_oid_fromstr(&expected_note_oid, list_expectations[*count].note_sha)); - cl_assert_equal_oid(&expected_note_oid, blob_id); - - cl_git_pass(git_oid_fromstr(&expected_target_oid, list_expectations[*count].annotated_object_sha)); - cl_assert_equal_oid(&expected_target_oid, annotated_obj_id); - - (*count)++; - - return 0; -} - -struct note_create_payload { - const char *note_oid; - const char *object_oid; - unsigned seen; -}; - -static int note_list_create_cb( - const git_oid *blob_oid, const git_oid *annotated_obj_id, void *payload) -{ - git_oid expected_note_oid, expected_target_oid; - struct note_create_payload *notes = payload; - size_t i; - - for (i = 0; notes[i].note_oid != NULL; i++) { - cl_git_pass(git_oid_fromstr(&expected_note_oid, notes[i].note_oid)); - - if (git_oid_cmp(&expected_note_oid, blob_oid) != 0) - continue; - - cl_git_pass(git_oid_fromstr(&expected_target_oid, notes[i].object_oid)); - - if (git_oid_cmp(&expected_target_oid, annotated_obj_id) != 0) - continue; - - notes[i].seen = 1; - return 0; - } - - cl_fail("Did not see expected note"); - return 0; -} - -static void assert_notes_seen(struct note_create_payload payload[], size_t n) -{ - size_t seen = 0, i; - - for (i = 0; payload[i].note_oid != NULL; i++) { - if (payload[i].seen) - seen++; - } - - cl_assert_equal_i(seen, n); -} - -void test_notes_notes__can_create_a_note(void) -{ - git_oid note_oid; - static struct note_create_payload can_create_a_note[] = { - { "1c9b1bc36730582a42d56eeee0dc58673d7ae869", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", 0 }, - { NULL, NULL, 0 } - }; - - create_note(¬e_oid, "refs/notes/i-can-see-dead-notes", can_create_a_note[0].object_oid, "I decorate 4a20\n"); - - cl_git_pass(git_note_foreach(_repo, "refs/notes/i-can-see-dead-notes", note_list_create_cb, &can_create_a_note)); - - assert_notes_seen(can_create_a_note, 1); -} - -void test_notes_notes__can_create_a_note_from_commit(void) -{ - git_oid oid; - git_oid notes_commit_out; - git_reference *ref; - static struct note_create_payload can_create_a_note_from_commit[] = { - { "1c9b1bc36730582a42d56eeee0dc58673d7ae869", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", 0 }, - { NULL, NULL, 0 } - }; - - cl_git_pass(git_oid_fromstr(&oid, can_create_a_note_from_commit[0].object_oid)); - - cl_git_pass(git_note_commit_create(¬es_commit_out, NULL, _repo, NULL, _sig, _sig, &oid, "I decorate 4a20\n", 1)); - - /* create_from_commit will not update any ref, - * so we must manually create the ref, that points to the commit */ - cl_git_pass(git_reference_create(&ref, _repo, "refs/notes/i-can-see-dead-notes", ¬es_commit_out, 0, NULL)); - - cl_git_pass(git_note_foreach(_repo, "refs/notes/i-can-see-dead-notes", note_list_create_cb, &can_create_a_note_from_commit)); - - assert_notes_seen(can_create_a_note_from_commit, 1); - - git_reference_free(ref); -} - - -/* Test that we can create a note from a commit, given an existing commit */ -void test_notes_notes__can_create_a_note_from_commit_given_an_existing_commit(void) -{ - git_oid oid; - git_oid notes_commit_out; - git_commit *existing_notes_commit = NULL; - git_reference *ref; - static struct note_create_payload can_create_a_note_from_commit_given_an_existing_commit[] = { - { "1c9b1bc36730582a42d56eeee0dc58673d7ae869", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", 0 }, - { "1aaf94147c21f981e0a20bf57b89137c5a6aae52", "9fd738e8f7967c078dceed8190330fc8648ee56a", 0 }, - { NULL, NULL, 0 } - }; - - cl_git_pass(git_oid_fromstr(&oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); - - cl_git_pass(git_note_commit_create(¬es_commit_out, NULL, _repo, NULL, _sig, _sig, &oid, "I decorate 4a20\n", 0)); - - cl_git_pass(git_oid_fromstr(&oid, "9fd738e8f7967c078dceed8190330fc8648ee56a")); - - git_commit_lookup(&existing_notes_commit, _repo, ¬es_commit_out); - - cl_assert(existing_notes_commit); - - cl_git_pass(git_note_commit_create(¬es_commit_out, NULL, _repo, existing_notes_commit, _sig, _sig, &oid, "I decorate 9fd7\n", 0)); - - /* create_from_commit will not update any ref, - * so we must manually create the ref, that points to the commit */ - cl_git_pass(git_reference_create(&ref, _repo, "refs/notes/i-can-see-dead-notes", ¬es_commit_out, 0, NULL)); - - cl_git_pass(git_note_foreach(_repo, "refs/notes/i-can-see-dead-notes", note_list_create_cb, &can_create_a_note_from_commit_given_an_existing_commit)); - - assert_notes_seen(can_create_a_note_from_commit_given_an_existing_commit, 2); - - git_commit_free(existing_notes_commit); - git_reference_free(ref); -} - -/* - * $ git notes --ref i-can-see-dead-notes add -m "I decorate a65f" a65fedf39aefe402d3bb6e24df4d4f5fe4547750 - * $ git notes --ref i-can-see-dead-notes add -m "I decorate c478" c47800c7266a2be04c571c04d5a6614691ea99bd - * $ git notes --ref i-can-see-dead-notes add -m "I decorate 9fd7 and 4a20" 9fd738e8f7967c078dceed8190330fc8648ee56a - * $ git notes --ref i-can-see-dead-notes add -m "I decorate 9fd7 and 4a20" 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 - * - * $ git notes --ref i-can-see-dead-notes list - * 1c73b1f51762155d357bcd1fd4f2c409ef80065b 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 - * 1c73b1f51762155d357bcd1fd4f2c409ef80065b 9fd738e8f7967c078dceed8190330fc8648ee56a - * 257b43746b6b46caa4aa788376c647cce0a33e2b a65fedf39aefe402d3bb6e24df4d4f5fe4547750 - * 1ec1c8e03f461f4f5d3f3702172483662e7223f3 c47800c7266a2be04c571c04d5a6614691ea99bd - * - * $ git ls-tree refs/notes/i-can-see-dead-notes - * 100644 blob 1c73b1f51762155d357bcd1fd4f2c409ef80065b 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 - * 100644 blob 1c73b1f51762155d357bcd1fd4f2c409ef80065b 9fd738e8f7967c078dceed8190330fc8648ee56a - * 100644 blob 257b43746b6b46caa4aa788376c647cce0a33e2b a65fedf39aefe402d3bb6e24df4d4f5fe4547750 - * 100644 blob 1ec1c8e03f461f4f5d3f3702172483662e7223f3 c47800c7266a2be04c571c04d5a6614691ea99bd -*/ -void test_notes_notes__can_retrieve_a_list_of_notes_for_a_given_namespace(void) -{ - git_oid note_oid1, note_oid2, note_oid3, note_oid4; - unsigned int retrieved_notes = 0; - - create_note(¬e_oid1, "refs/notes/i-can-see-dead-notes", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "I decorate a65f\n"); - create_note(¬e_oid2, "refs/notes/i-can-see-dead-notes", "c47800c7266a2be04c571c04d5a6614691ea99bd", "I decorate c478\n"); - create_note(¬e_oid3, "refs/notes/i-can-see-dead-notes", "9fd738e8f7967c078dceed8190330fc8648ee56a", "I decorate 9fd7 and 4a20\n"); - create_note(¬e_oid4, "refs/notes/i-can-see-dead-notes", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", "I decorate 9fd7 and 4a20\n"); - - cl_git_pass(git_note_foreach -(_repo, "refs/notes/i-can-see-dead-notes", note_list_cb, &retrieved_notes)); - - cl_assert_equal_i(4, retrieved_notes); -} - -static int note_cancel_cb( - const git_oid *blob_id, const git_oid *annotated_obj_id, void *payload) -{ - unsigned int *count = (unsigned int *)payload; - - GIT_UNUSED(blob_id); - GIT_UNUSED(annotated_obj_id); - - (*count)++; - - return (*count > 2); -} - -void test_notes_notes__can_cancel_foreach(void) -{ - git_oid note_oid1, note_oid2, note_oid3, note_oid4; - unsigned int retrieved_notes = 0; - - create_note(¬e_oid1, "refs/notes/i-can-see-dead-notes", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "I decorate a65f\n"); - create_note(¬e_oid2, "refs/notes/i-can-see-dead-notes", "c47800c7266a2be04c571c04d5a6614691ea99bd", "I decorate c478\n"); - create_note(¬e_oid3, "refs/notes/i-can-see-dead-notes", "9fd738e8f7967c078dceed8190330fc8648ee56a", "I decorate 9fd7 and 4a20\n"); - create_note(¬e_oid4, "refs/notes/i-can-see-dead-notes", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", "I decorate 9fd7 and 4a20\n"); - - cl_assert_equal_i( - 1, - git_note_foreach(_repo, "refs/notes/i-can-see-dead-notes", - note_cancel_cb, &retrieved_notes)); -} - -void test_notes_notes__retrieving_a_list_of_notes_for_an_unknown_namespace_returns_ENOTFOUND(void) -{ - int error; - unsigned int retrieved_notes = 0; - - error = git_note_foreach(_repo, "refs/notes/i-am-not", note_list_cb, &retrieved_notes); - cl_git_fail(error); - cl_assert_equal_i(GIT_ENOTFOUND, error); - - cl_assert_equal_i(0, retrieved_notes); -} - -void test_notes_notes__inserting_a_note_without_passing_a_namespace_uses_the_default_namespace(void) -{ - git_oid note_oid, target_oid; - git_note *note, *default_namespace_note; - git_buf default_ref = GIT_BUF_INIT; - - cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125")); - cl_git_pass(git_note_default_ref(&default_ref, _repo)); - - create_note(¬e_oid, NULL, "08b041783f40edfe12bb406c9c9a8a040177c125", "hello world\n"); - - cl_git_pass(git_note_read(¬e, _repo, NULL, &target_oid)); - cl_git_pass(git_note_read(&default_namespace_note, _repo, default_ref.ptr, &target_oid)); - - assert_note_equal(note, "hello world\n", ¬e_oid); - assert_note_equal(default_namespace_note, "hello world\n", ¬e_oid); - - git_buf_dispose(&default_ref); - git_note_free(note); - git_note_free(default_namespace_note); -} - -void test_notes_notes__can_insert_a_note_with_a_custom_namespace(void) -{ - git_oid note_oid, target_oid; - git_note *note; - - cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125")); - - create_note(¬e_oid, "refs/notes/some/namespace", "08b041783f40edfe12bb406c9c9a8a040177c125", "hello world on a custom namespace\n"); - - cl_git_pass(git_note_read(¬e, _repo, "refs/notes/some/namespace", &target_oid)); - - assert_note_equal(note, "hello world on a custom namespace\n", ¬e_oid); - - git_note_free(note); -} - -/* - * $ git notes --ref fanout list 8496071c1b46c854b31185ea97743be6a8774479 - * 08b041783f40edfe12bb406c9c9a8a040177c125 - */ -void test_notes_notes__creating_a_note_on_a_target_which_already_has_one_returns_EEXISTS(void) -{ - int error; - git_oid note_oid, target_oid; - - cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125")); - - create_note(¬e_oid, NULL, "08b041783f40edfe12bb406c9c9a8a040177c125", "hello world\n"); - error = git_note_create(¬e_oid, _repo, NULL, _sig, _sig, &target_oid, "hello world\n", 0); - cl_git_fail(error); - cl_assert_equal_i(GIT_EEXISTS, error); - - create_note(¬e_oid, "refs/notes/some/namespace", "08b041783f40edfe12bb406c9c9a8a040177c125", "hello world\n"); - error = git_note_create(¬e_oid, _repo, "refs/notes/some/namespace", _sig, _sig, &target_oid, "hello world\n", 0); - cl_git_fail(error); - cl_assert_equal_i(GIT_EEXISTS, error); -} - - -void test_notes_notes__creating_a_note_on_a_target_can_overwrite_existing_note(void) -{ - git_oid note_oid, target_oid; - git_note *note, *namespace_note; - - cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125")); - - create_note(¬e_oid, NULL, "08b041783f40edfe12bb406c9c9a8a040177c125", "hello old world\n"); - cl_git_pass(git_note_create(¬e_oid, _repo, NULL, _sig, _sig, &target_oid, "hello new world\n", 1)); - - cl_git_pass(git_note_read(¬e, _repo, NULL, &target_oid)); - assert_note_equal(note, "hello new world\n", ¬e_oid); - - create_note(¬e_oid, "refs/notes/some/namespace", "08b041783f40edfe12bb406c9c9a8a040177c125", "hello old world\n"); - cl_git_pass(git_note_create(¬e_oid, _repo, "refs/notes/some/namespace", _sig, _sig, &target_oid, "hello new ref world\n", 1)); - - cl_git_pass(git_note_read(&namespace_note, _repo, "refs/notes/some/namespace", &target_oid)); - assert_note_equal(namespace_note, "hello new ref world\n", ¬e_oid); - - git_note_free(note); - git_note_free(namespace_note); -} - -static char *messages[] = { - "08c041783f40edfe12bb406c9c9a8a040177c125", - "96c45fbe09ab7445fc7c60fd8d17f32494399343", - "48cc7e38dcfc1ec87e70ec03e08c3e83d7a16aa1", - "24c3eaafb681c3df668f9df96f58e7b8c756eb04", - "96ca1b6ccc7858ae94684777f85ac0e7447f7040", - "7ac2db4378a08bb244a427c357e0082ee0d57ac6", - "e6cba23dbf4ef84fe35e884f017f4e24dc228572", - "c8cf3462c7d8feba716deeb2ebe6583bd54589e2", - "39c16b9834c2d665ac5f68ad91dc5b933bad8549", - "f3c582b1397df6a664224ebbaf9d4cc952706597", - "29cec67037fe8e89977474988219016ae7f342a6", - "36c4cd238bf8e82e27b740e0741b025f2e8c79ab", - "f1c45a47c02e01d5a9a326f1d9f7f756373387f8", - "4aca84406f5daee34ab513a60717c8d7b1763ead", - "84ce167da452552f63ed8407b55d5ece4901845f", - NULL -}; - -#define MESSAGES_COUNT (sizeof(messages)/sizeof(messages[0])) - 1 - -/* Test that we can read a note */ -void test_notes_notes__can_read_a_note(void) -{ - git_oid note_oid, target_oid; - git_note *note; - - create_note(¬e_oid, "refs/notes/i-can-see-dead-notes", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", "I decorate 4a20\n"); - - cl_git_pass(git_oid_fromstr(&target_oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); - - cl_git_pass(git_note_read(¬e, _repo, "refs/notes/i-can-see-dead-notes", &target_oid)); - - cl_assert_equal_s(git_note_message(note), "I decorate 4a20\n"); - - git_note_free(note); -} - -/* Test that we can read a note with from commit api */ -void test_notes_notes__can_read_a_note_from_a_commit(void) -{ - git_oid oid, notes_commit_oid; - git_commit *notes_commit; - git_note *note; - - cl_git_pass(git_oid_fromstr(&oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); - cl_git_pass(git_note_commit_create(¬es_commit_oid, NULL, _repo, NULL, _sig, _sig, &oid, "I decorate 4a20\n", 1)); - cl_git_pass(git_commit_lookup(¬es_commit, _repo, ¬es_commit_oid)); - cl_assert(notes_commit); - - cl_git_pass(git_note_commit_read(¬e, _repo, notes_commit, &oid)); - cl_assert_equal_s(git_note_message(note), "I decorate 4a20\n"); - - git_commit_free(notes_commit); - git_note_free(note); -} - -/* Test that we can read a commit with no note fails */ -void test_notes_notes__attempt_to_read_a_note_from_a_commit_with_no_note_fails(void) -{ - git_oid oid, notes_commit_oid; - git_commit *notes_commit; - git_note *note; - - cl_git_pass(git_oid_fromstr(&oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); - - cl_git_pass(git_note_commit_create(¬es_commit_oid, NULL, _repo, NULL, _sig, _sig, &oid, "I decorate 4a20\n", 1)); - - git_commit_lookup(¬es_commit, _repo, ¬es_commit_oid); - - cl_git_pass(git_note_commit_remove(¬es_commit_oid, _repo, notes_commit, _sig, _sig, &oid)); - git_commit_free(notes_commit); - - git_commit_lookup(¬es_commit, _repo, ¬es_commit_oid); - - cl_assert(notes_commit); - - cl_git_fail_with(GIT_ENOTFOUND, git_note_commit_read(¬e, _repo, notes_commit, &oid)); - - git_commit_free(notes_commit); -} - -/* - * $ git ls-tree refs/notes/fanout - * 040000 tree 4b22b35d44b5a4f589edf3dc89196399771796ea 84 - * - * $ git ls-tree 4b22b35 - * 040000 tree d71aab4f9b04b45ce09bcaa636a9be6231474759 96 - * - * $ git ls-tree d71aab4 - * 100644 blob 08b041783f40edfe12bb406c9c9a8a040177c125 071c1b46c854b31185ea97743be6a8774479 - */ -void test_notes_notes__can_insert_a_note_in_an_existing_fanout(void) -{ - size_t i; - git_oid note_oid, target_oid; - git_note *_note; - - cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125")); - - for (i = 0; i < MESSAGES_COUNT; i++) { - cl_git_pass(git_note_create(¬e_oid, _repo, "refs/notes/fanout", _sig, _sig, &target_oid, messages[i], 0)); - cl_git_pass(git_note_read(&_note, _repo, "refs/notes/fanout", &target_oid)); - git_note_free(_note); - - git_oid_cpy(&target_oid, ¬e_oid); - } -} - -/* - * $ git notes --ref fanout list 8496071c1b46c854b31185ea97743be6a8774479 - * 08b041783f40edfe12bb406c9c9a8a040177c125 - */ -void test_notes_notes__can_read_a_note_in_an_existing_fanout(void) -{ - git_oid note_oid, target_oid; - git_note *note; - - cl_git_pass(git_oid_fromstr(&target_oid, "8496071c1b46c854b31185ea97743be6a8774479")); - cl_git_pass(git_note_read(¬e, _repo, "refs/notes/fanout", &target_oid)); - - cl_git_pass(git_oid_fromstr(¬e_oid, "08b041783f40edfe12bb406c9c9a8a040177c125")); - cl_assert_equal_oid(git_note_id(note), ¬e_oid); - - git_note_free(note); -} - -/* Can remove a note */ -void test_notes_notes__can_remove_a_note(void) -{ - git_oid note_oid, target_oid; - git_note *note; - - create_note(¬e_oid, "refs/notes/i-can-see-dead-notes", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", "I decorate 4a20\n"); - - cl_git_pass(git_oid_fromstr(&target_oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); - cl_git_pass(git_note_remove(_repo, "refs/notes/i-can-see-dead-notes", _sig, _sig, &target_oid)); - - cl_git_fail(git_note_read(¬e, _repo, "refs/notes/i-can-see-dead-notes", &target_oid)); -} - -/* Can remove a note from a commit */ -void test_notes_notes__can_remove_a_note_from_commit(void) -{ - git_oid oid, notes_commit_oid; - git_note *note = NULL; - git_commit *existing_notes_commit; - git_reference *ref; - - cl_git_pass(git_oid_fromstr(&oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); - - cl_git_pass(git_note_commit_create(¬es_commit_oid, NULL, _repo, NULL, _sig, _sig, &oid, "I decorate 4a20\n", 0)); - - cl_git_pass(git_commit_lookup(&existing_notes_commit, _repo, ¬es_commit_oid)); - - cl_assert(existing_notes_commit); - - cl_git_pass(git_note_commit_remove(¬es_commit_oid, _repo, existing_notes_commit, _sig, _sig, &oid)); - - /* remove_from_commit will not update any ref, - * so we must manually create the ref, that points to the commit */ - cl_git_pass(git_reference_create(&ref, _repo, "refs/notes/i-can-see-dead-notes", ¬es_commit_oid, 0, NULL)); - - cl_git_fail(git_note_read(¬e, _repo, "refs/notes/i-can-see-dead-notes", &oid)); - - git_commit_free(existing_notes_commit); - git_reference_free(ref); - git_note_free(note); -} - - -void test_notes_notes__can_remove_a_note_in_an_existing_fanout(void) -{ - git_oid target_oid; - git_note *note; - - cl_git_pass(git_oid_fromstr(&target_oid, "8496071c1b46c854b31185ea97743be6a8774479")); - cl_git_pass(git_note_remove(_repo, "refs/notes/fanout", _sig, _sig, &target_oid)); - - cl_git_fail(git_note_read(¬e, _repo, "refs/notes/fanout", &target_oid)); -} - -void test_notes_notes__removing_a_note_which_doesnt_exists_returns_ENOTFOUND(void) -{ - int error; - git_oid target_oid; - - cl_git_pass(git_oid_fromstr(&target_oid, "8496071c1b46c854b31185ea97743be6a8774479")); - cl_git_pass(git_note_remove(_repo, "refs/notes/fanout", _sig, _sig, &target_oid)); - - error = git_note_remove(_repo, "refs/notes/fanout", _sig, _sig, &target_oid); - cl_git_fail(error); - cl_assert_equal_i(GIT_ENOTFOUND, error); -} - -void test_notes_notes__can_iterate_default_namespace(void) -{ - git_note_iterator *iter; - git_note *note; - git_oid note_id, annotated_id; - git_oid note_created[2]; - const char* note_message[] = { - "I decorate a65f\n", - "I decorate c478\n" - }; - int i, err; - - create_note(¬e_created[0], "refs/notes/commits", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", note_message[0]); - create_note(¬e_created[1], "refs/notes/commits", - "c47800c7266a2be04c571c04d5a6614691ea99bd", note_message[1]); - - cl_git_pass(git_note_iterator_new(&iter, _repo, NULL)); - - for (i = 0; (err = git_note_next(¬e_id, &annotated_id, iter)) >= 0; ++i) { - cl_git_pass(git_note_read(¬e, _repo, NULL, &annotated_id)); - cl_assert_equal_s(git_note_message(note), note_message[i]); - git_note_free(note); - } - - cl_assert_equal_i(GIT_ITEROVER, err); - cl_assert_equal_i(2, i); - git_note_iterator_free(iter); -} - -void test_notes_notes__can_iterate_custom_namespace(void) -{ - git_note_iterator *iter; - git_note *note; - git_oid note_id, annotated_id; - git_oid note_created[2]; - const char* note_message[] = { - "I decorate a65f\n", - "I decorate c478\n" - }; - int i, err; - - create_note(¬e_created[0], "refs/notes/beer", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", note_message[0]); - create_note(¬e_created[1], "refs/notes/beer", - "c47800c7266a2be04c571c04d5a6614691ea99bd", note_message[1]); - - cl_git_pass(git_note_iterator_new(&iter, _repo, "refs/notes/beer")); - - for (i = 0; (err = git_note_next(¬e_id, &annotated_id, iter)) >= 0; ++i) { - cl_git_pass(git_note_read(¬e, _repo, "refs/notes/beer", &annotated_id)); - cl_assert_equal_s(git_note_message(note), note_message[i]); - git_note_free(note); - } - - cl_assert_equal_i(GIT_ITEROVER, err); - cl_assert_equal_i(2, i); - git_note_iterator_free(iter); -} - -void test_notes_notes__empty_iterate(void) -{ - git_note_iterator *iter; - - cl_git_fail(git_note_iterator_new(&iter, _repo, "refs/notes/commits")); -} - -void test_notes_notes__iterate_from_commit(void) -{ - git_note_iterator *iter; - git_note *note; - git_oid note_id, annotated_id; - git_oid oids[2]; - git_oid notes_commit_oids[2]; - git_commit *notes_commits[2]; - const char* note_message[] = { - "I decorate a65f\n", - "I decorate c478\n" - }; - int i, err; - - cl_git_pass(git_oid_fromstr(&(oids[0]), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); - cl_git_pass(git_oid_fromstr(&(oids[1]), "c47800c7266a2be04c571c04d5a6614691ea99bd")); - - cl_git_pass(git_note_commit_create(¬es_commit_oids[0], NULL, _repo, NULL, _sig, _sig, &(oids[0]), note_message[0], 0)); - - git_commit_lookup(¬es_commits[0], _repo, ¬es_commit_oids[0]); - cl_assert(notes_commits[0]); - - cl_git_pass(git_note_commit_create(¬es_commit_oids[1], NULL, _repo, notes_commits[0], _sig, _sig, &(oids[1]), note_message[1], 0)); - - git_commit_lookup(¬es_commits[1], _repo, ¬es_commit_oids[1]); - cl_assert(notes_commits[1]); - - cl_git_pass(git_note_commit_iterator_new(&iter, notes_commits[1])); - - for (i = 0; (err = git_note_next(¬e_id, &annotated_id, iter)) >= 0; ++i) { - cl_git_pass(git_note_commit_read(¬e, _repo, notes_commits[1], &annotated_id)); - cl_assert_equal_s(git_note_message(note), note_message[i]); - git_note_free(note); - } - - cl_assert_equal_i(GIT_ITEROVER, err); - cl_assert_equal_i(2, i); - - git_note_iterator_free(iter); - git_commit_free(notes_commits[0]); - git_commit_free(notes_commits[1]); -} diff --git a/tests/notes/notesref.c b/tests/notes/notesref.c deleted file mode 100644 index 6ba324c76..000000000 --- a/tests/notes/notesref.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "clar_libgit2.h" - -#include "notes.h" - -static git_repository *_repo; -static git_note *_note; -static git_signature *_sig; -static git_config *_cfg; - -void test_notes_notesref__initialize(void) -{ - cl_fixture_sandbox("testrepo.git"); - cl_git_pass(git_repository_open(&_repo, "testrepo.git")); -} - -void test_notes_notesref__cleanup(void) -{ - git_note_free(_note); - _note = NULL; - - git_signature_free(_sig); - _sig = NULL; - - git_config_free(_cfg); - _cfg = NULL; - - git_repository_free(_repo); - _repo = NULL; - - cl_fixture_cleanup("testrepo.git"); -} - -void test_notes_notesref__config_corenotesref(void) -{ - git_oid oid, note_oid; - git_buf default_ref = GIT_BUF_INIT; - - cl_git_pass(git_signature_now(&_sig, "alice", "alice@example.com")); - cl_git_pass(git_oid_fromstr(&oid, "8496071c1b46c854b31185ea97743be6a8774479")); - - cl_git_pass(git_repository_config(&_cfg, _repo)); - - cl_git_pass(git_config_set_string(_cfg, "core.notesRef", "refs/notes/mydefaultnotesref")); - - cl_git_pass(git_note_create(¬e_oid, _repo, NULL, _sig, _sig, &oid, "test123test\n", 0)); - - cl_git_pass(git_note_read(&_note, _repo, NULL, &oid)); - cl_assert_equal_s("test123test\n", git_note_message(_note)); - cl_assert_equal_oid(git_note_id(_note), ¬e_oid); - - git_note_free(_note); - - cl_git_pass(git_note_read(&_note, _repo, "refs/notes/mydefaultnotesref", &oid)); - cl_assert_equal_s("test123test\n", git_note_message(_note)); - cl_assert_equal_oid(git_note_id(_note), ¬e_oid); - - cl_git_pass(git_note_default_ref(&default_ref, _repo)); - cl_assert_equal_s("refs/notes/mydefaultnotesref", default_ref.ptr); - git_buf_dispose(&default_ref); - - cl_git_pass(git_config_delete_entry(_cfg, "core.notesRef")); - - cl_git_pass(git_note_default_ref(&default_ref, _repo)); - cl_assert_equal_s(GIT_NOTES_DEFAULT_REF, default_ref.ptr); - - git_buf_dispose(&default_ref); -} diff --git a/tests/object/blob/filter.c b/tests/object/blob/filter.c deleted file mode 100644 index 00b553e71..000000000 --- a/tests/object/blob/filter.c +++ /dev/null @@ -1,149 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "blob.h" - -static git_repository *g_repo = NULL; - -#define CRLF_NUM_TEST_OBJECTS 9 - -static const char *g_crlf_raw[CRLF_NUM_TEST_OBJECTS] = { - "", - "foo\nbar\n", - "foo\rbar\r", - "foo\r\nbar\r\n", - "foo\nbar\rboth\r\nreversed\n\ragain\nproblems\r", - "123\n\000\001\002\003\004abc\255\254\253\r\n", - "\xEF\xBB\xBFThis is UTF-8\n", - "\xEF\xBB\xBF\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n", - "\xFE\xFF\x00T\x00h\x00i\x00s\x00!" -}; - -static off64_t g_crlf_raw_len[CRLF_NUM_TEST_OBJECTS] = { - -1, -1, -1, -1, -1, 17, -1, -1, 12 -}; - -static git_oid g_crlf_oids[CRLF_NUM_TEST_OBJECTS]; - -static git_str g_crlf_filtered[CRLF_NUM_TEST_OBJECTS] = { - { "", 0, 0 }, - { "foo\nbar\n", 0, 8 }, - { "foo\rbar\r", 0, 8 }, - { "foo\nbar\n", 0, 8 }, - { "foo\nbar\rboth\nreversed\n\ragain\nproblems\r", 0, 38 }, - { "123\n\000\001\002\003\004abc\255\254\253\n", 0, 16 }, - { "\xEF\xBB\xBFThis is UTF-8\n", 0, 17 }, - { "\xEF\xBB\xBF\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\n\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\n", 0, 29 }, - { "\xFE\xFF\x00T\x00h\x00i\x00s\x00!", 0, 12 } -}; - -static git_str_text_stats g_crlf_filtered_stats[CRLF_NUM_TEST_OBJECTS] = { - { 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 2, 0, 6, 0 }, - { 0, 0, 2, 0, 0, 6, 0 }, - { 0, 0, 2, 2, 2, 6, 0 }, - { 0, 0, 4, 4, 1, 31, 0 }, - { 0, 1, 1, 2, 1, 9, 5 }, - { GIT_STR_BOM_UTF8, 0, 0, 1, 0, 16, 0 }, - { GIT_STR_BOM_UTF8, 0, 2, 2, 2, 27, 0 }, - { GIT_STR_BOM_UTF16_BE, 5, 0, 0, 0, 7, 5 }, -}; - -void test_object_blob_filter__initialize(void) -{ - int i; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { - if (g_crlf_raw_len[i] < 0) - g_crlf_raw_len[i] = strlen(g_crlf_raw[i]); - - cl_git_pass(git_blob_create_from_buffer( - &g_crlf_oids[i], g_repo, g_crlf_raw[i], (size_t)g_crlf_raw_len[i])); - } -} - -void test_object_blob_filter__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_object_blob_filter__unfiltered(void) -{ - int i; - git_blob *blob; - - for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { - size_t raw_len = (size_t)g_crlf_raw_len[i]; - - cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i])); - - cl_assert_equal_sz(raw_len, (size_t)git_blob_rawsize(blob)); - cl_assert_equal_i( - 0, memcmp(g_crlf_raw[i], git_blob_rawcontent(blob), raw_len)); - - git_blob_free(blob); - } -} - -void test_object_blob_filter__stats(void) -{ - int i; - git_blob *blob; - git_str buf = GIT_STR_INIT; - git_str_text_stats stats; - - for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { - cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i])); - cl_git_pass(git_blob__getbuf(&buf, blob)); - git_str_gather_text_stats(&stats, &buf, false); - cl_assert_equal_i( - 0, memcmp(&g_crlf_filtered_stats[i], &stats, sizeof(stats))); - git_blob_free(blob); - } - - git_str_dispose(&buf); -} - -void test_object_blob_filter__to_odb(void) -{ - git_filter_list *fl = NULL; - git_config *cfg; - int i; - git_blob *blob; - git_buf out = GIT_BUF_INIT, zeroed; - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_assert(cfg); - - git_attr_cache_flush(g_repo); - cl_git_append2file("empty_standard_repo/.gitattributes", "*.txt text\n"); - - cl_git_pass(git_filter_list_load( - &fl, g_repo, NULL, "filename.txt", GIT_FILTER_TO_ODB, 0)); - cl_assert(fl != NULL); - - for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) { - cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i])); - - /* try once with allocated blob */ - cl_git_pass(git_filter_list_apply_to_blob(&out, fl, blob)); - cl_assert_equal_sz(g_crlf_filtered[i].size, out.size); - cl_assert_equal_i( - 0, memcmp(out.ptr, g_crlf_filtered[i].ptr, out.size)); - - /* try again with zeroed blob */ - memset(&zeroed, 0, sizeof(zeroed)); - cl_git_pass(git_filter_list_apply_to_blob(&zeroed, fl, blob)); - cl_assert_equal_sz(g_crlf_filtered[i].size, zeroed.size); - cl_assert_equal_i( - 0, memcmp(zeroed.ptr, g_crlf_filtered[i].ptr, zeroed.size)); - git_buf_dispose(&zeroed); - - git_blob_free(blob); - } - - git_filter_list_free(fl); - git_buf_dispose(&out); - git_config_free(cfg); -} diff --git a/tests/object/blob/fromstream.c b/tests/object/blob/fromstream.c deleted file mode 100644 index 60ff3991b..000000000 --- a/tests/object/blob/fromstream.c +++ /dev/null @@ -1,86 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "path.h" -#include "futils.h" - -static git_repository *repo; -static char textual_content[] = "libgit2\n\r\n\0"; - -void test_object_blob_fromstream__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_object_blob_fromstream__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_object_blob_fromstream__multiple_write(void) -{ - git_oid expected_id, id; - git_object *blob; - git_writestream *stream; - int i, howmany = 6; - - cl_git_pass(git_oid_fromstr(&expected_id, "321cbdf08803c744082332332838df6bd160f8f9")); - - cl_git_fail_with(GIT_ENOTFOUND, - git_object_lookup(&blob, repo, &expected_id, GIT_OBJECT_ANY)); - - cl_git_pass(git_blob_create_from_stream(&stream, repo, NULL)); - - for (i = 0; i < howmany; i++) - cl_git_pass(stream->write(stream, textual_content, strlen(textual_content))); - - cl_git_pass(git_blob_create_from_stream_commit(&id, stream)); - cl_assert_equal_oid(&expected_id, &id); - - cl_git_pass(git_object_lookup(&blob, repo, &expected_id, GIT_OBJECT_BLOB)); - - git_object_free(blob); -} - -#define GITATTR "* text=auto\n" \ - "*.txt text\n" \ - "*.data binary\n" - -static void write_attributes(git_repository *repo) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&buf, git_repository_path(repo), "info")); - cl_git_pass(git_str_joinpath(&buf, git_str_cstr(&buf), "attributes")); - - cl_git_pass(git_futils_mkpath2file(git_str_cstr(&buf), 0777)); - cl_git_rewritefile(git_str_cstr(&buf), GITATTR); - - git_str_dispose(&buf); -} - -static void assert_named_chunked_blob(const char *expected_sha, const char *fake_name) -{ - git_oid expected_id, id; - git_writestream *stream; - int i, howmany = 6; - - cl_git_pass(git_oid_fromstr(&expected_id, expected_sha)); - - cl_git_pass(git_blob_create_from_stream(&stream, repo, fake_name)); - - for (i = 0; i < howmany; i++) - cl_git_pass(stream->write(stream, textual_content, strlen(textual_content))); - - cl_git_pass(git_blob_create_from_stream_commit(&id, stream)); - - cl_assert_equal_oid(&expected_id, &id); -} - -void test_object_blob_fromstream__creating_a_blob_from_chunks_honors_the_attributes_directives(void) -{ - write_attributes(repo); - - assert_named_chunked_blob("321cbdf08803c744082332332838df6bd160f8f9", "dummy.data"); - assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.txt"); - assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.dunno"); -} diff --git a/tests/object/blob/write.c b/tests/object/blob/write.c deleted file mode 100644 index 422258d63..000000000 --- a/tests/object/blob/write.c +++ /dev/null @@ -1,68 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "path.h" -#include "futils.h" - -static git_repository *repo; - -#define WORKDIR "empty_standard_repo" -#define BARE_REPO "testrepo.git" -#define ELSEWHERE "elsewhere" - -typedef int (*blob_creator_fn)( - git_oid *, - git_repository *, - const char *); - -void test_object_blob_write__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void assert_blob_creation(const char *path_to_file, const char *blob_from_path, blob_creator_fn creator) -{ - git_oid oid; - cl_git_mkfile(path_to_file, "1..2...3... Can you hear me?\n"); - - cl_must_pass(creator(&oid, repo, blob_from_path)); - cl_assert(git_oid_streq(&oid, "da5e4f20c91c81b44a7e298f3d3fb3fe2f178e32") == 0); -} - -void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_file_located_in_the_working_directory(void) -{ - repo = cl_git_sandbox_init(WORKDIR); - - assert_blob_creation(WORKDIR "/test.txt", "test.txt", &git_blob_create_from_workdir); -} - -void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_absolute_filepath_pointing_outside_of_the_working_directory(void) -{ - git_str full_path = GIT_STR_INIT; - - repo = cl_git_sandbox_init(WORKDIR); - - cl_must_pass(p_mkdir(ELSEWHERE, 0777)); - cl_must_pass(git_fs_path_prettify_dir(&full_path, ELSEWHERE, NULL)); - cl_must_pass(git_str_puts(&full_path, "test.txt")); - - assert_blob_creation(ELSEWHERE "/test.txt", git_str_cstr(&full_path), &git_blob_create_from_disk); - - git_str_dispose(&full_path); - cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_object_blob_write__can_create_a_blob_in_a_bare_repo_from_a_absolute_filepath(void) -{ - git_str full_path = GIT_STR_INIT; - - repo = cl_git_sandbox_init(BARE_REPO); - - cl_must_pass(p_mkdir(ELSEWHERE, 0777)); - cl_must_pass(git_fs_path_prettify_dir(&full_path, ELSEWHERE, NULL)); - cl_must_pass(git_str_puts(&full_path, "test.txt")); - - assert_blob_creation(ELSEWHERE "/test.txt", git_str_cstr(&full_path), &git_blob_create_from_disk); - - git_str_dispose(&full_path); - cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_RMDIR_REMOVE_FILES)); -} diff --git a/tests/object/cache.c b/tests/object/cache.c deleted file mode 100644 index 08bf03648..000000000 --- a/tests/object/cache.c +++ /dev/null @@ -1,276 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" - -static git_repository *g_repo; -static size_t cache_limit; -static int object_type; - -void test_object_cache__initialize_cache_no_blobs(void) -{ - g_repo = NULL; - object_type = GIT_OBJECT_BLOB; - cache_limit = 0; -} - -void test_object_cache__initialize_cache_tiny_blobs(void) -{ - g_repo = NULL; - object_type = GIT_OBJECT_BLOB; - cache_limit = 10; -} - -void test_object_cache__initialize_cache_all_blobs(void) -{ - g_repo = NULL; - object_type = GIT_OBJECT_BLOB; - cache_limit = 32767; -} - -void test_object_cache__initialize_cache_no_trees(void) -{ - g_repo = NULL; - object_type = GIT_OBJECT_TREE; - cache_limit = 0; -} - -void test_object_cache__cleanup(void) -{ - git_repository_free(g_repo); - g_repo = NULL; - - git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJECT_BLOB, (size_t)0); - git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJECT_TREE, (size_t)4096); - git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJECT_COMMIT, (size_t)4096); -} - -static struct { - git_object_t type; - const char *sha; - size_t size; -} g_data[] = { - /* HEAD */ - { GIT_OBJECT_BLOB, "a8233120f6ad708f843d861ce2b7228ec4e3dec6", 10 }, /* README */ - { GIT_OBJECT_BLOB, "3697d64be941a53d4ae8f6a271e4e3fa56b022cc", 8 }, /* branch_file.txt */ - { GIT_OBJECT_BLOB, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd", 12 }, /* new.txt */ - - /* refs/heads/subtrees */ - { GIT_OBJECT_BLOB, "1385f264afb75a56a5bec74243be9b367ba4ca08", 4 }, /* README */ - { GIT_OBJECT_TREE, "f1425cef211cc08caa31e7b545ffb232acb098c3", 90 }, /* ab */ - { GIT_OBJECT_BLOB, "d6c93164c249c8000205dd4ec5cbca1b516d487f", 6 }, /* ab/4.txt */ - { GIT_OBJECT_TREE, "9a03079b8a8ee85a0bee58bf9be3da8b62414ed4", 33 }, /* ab/c */ - { GIT_OBJECT_BLOB, "270b8ea76056d5cad83af921837702d3e3c2924d", 6 }, /* ab/c/3.txt */ - { GIT_OBJECT_TREE, "b6361fc6a97178d8fc8639fdeed71c775ab52593", 63 }, /* ab/de */ - { GIT_OBJECT_BLOB, "e7b4ad382349ff96dd8199000580b9b1e2042eb0", 6 }, /* ab/de/2.txt */ - { GIT_OBJECT_TREE, "3259a6bd5b57fb9c1281bb7ed3167b50f224cb54", 33 }, /* ab/de/fgh */ - { GIT_OBJECT_BLOB, "1f67fc4386b2d171e0d21be1c447e12660561f9b", 6 }, /* ab/de/fgh/1.txt */ - { GIT_OBJECT_BLOB, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", 3 }, /* branch_file.txt */ - { GIT_OBJECT_BLOB, "fa49b077972391ad58037050f2a75f74e3671e92", 9 }, /* new.txt */ - - /* refs/heads/chomped */ - { GIT_OBJECT_BLOB, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", 51 }, /* readme.txt */ - - { 0, NULL, 0 }, - { 0, NULL, 0 } -}; - -void test_object_cache__cache_counts(void) -{ - int i, start, nonmatching = 0; - git_oid oid; - git_odb_object *odb_obj; - git_object *obj; - git_odb *odb; - - git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, object_type, cache_limit); - - cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_odb(&odb, g_repo)); - - start = (int)git_cache_size(&g_repo->objects); - - for (i = 0; g_data[i].sha != NULL; ++i) { - int count = (int)git_cache_size(&g_repo->objects); - - cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha)); - - /* alternate between loading raw and parsed objects */ - if ((i & 1) == 0) { - cl_git_pass(git_odb_read(&odb_obj, odb, &oid)); - cl_assert(g_data[i].type == git_odb_object_type(odb_obj)); - git_odb_object_free(odb_obj); - } else { - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - cl_assert(g_data[i].type == git_object_type(obj)); - git_object_free(obj); - } - - if ((g_data[i].type == object_type && g_data[i].size >= cache_limit) || - (g_data[i].type != object_type && g_data[i].type == GIT_OBJECT_BLOB)) - cl_assert_equal_i(count, (int)git_cache_size(&g_repo->objects)); - else { - cl_assert_equal_i(count + 1, (int)git_cache_size(&g_repo->objects)); - nonmatching++; - } - } - - cl_assert_equal_i(nonmatching, (int)git_cache_size(&g_repo->objects) - start); - - for (i = 0; g_data[i].sha != NULL; ++i) { - int count = (int)git_cache_size(&g_repo->objects); - - cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha)); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - cl_assert(g_data[i].type == git_object_type(obj)); - git_object_free(obj); - - cl_assert_equal_i(count, (int)git_cache_size(&g_repo->objects)); - } - - git_odb_free(odb); -} - -static void *cache_parsed(void *arg) -{ - int i; - git_oid oid; - git_object *obj; - - for (i = ((int *)arg)[1]; g_data[i].sha != NULL; i += 2) { - cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha)); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - cl_assert(g_data[i].type == git_object_type(obj)); - git_object_free(obj); - } - - for (i = 0; i < ((int *)arg)[1]; i += 2) { - cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha)); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - cl_assert(g_data[i].type == git_object_type(obj)); - git_object_free(obj); - } - - return arg; -} - -static void *cache_raw(void *arg) -{ - int i; - git_oid oid; - git_odb *odb; - git_odb_object *odb_obj; - - cl_git_pass(git_repository_odb(&odb, g_repo)); - - for (i = ((int *)arg)[1]; g_data[i].sha != NULL; i += 2) { - cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha)); - cl_git_pass(git_odb_read(&odb_obj, odb, &oid)); - cl_assert(g_data[i].type == git_odb_object_type(odb_obj)); - git_odb_object_free(odb_obj); - } - - for (i = 0; i < ((int *)arg)[1]; i += 2) { - cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha)); - cl_git_pass(git_odb_read(&odb_obj, odb, &oid)); - cl_assert(g_data[i].type == git_odb_object_type(odb_obj)); - git_odb_object_free(odb_obj); - } - - git_odb_free(odb); - - return arg; -} - -#define REPEAT 20 -#define THREADCOUNT 50 - -void test_object_cache__threadmania(void) -{ - int try, th, max_i; - void *data; - void *(*fn)(void *); - -#ifdef GIT_THREADS - git_thread t[THREADCOUNT]; -#endif - - for (max_i = 0; g_data[max_i].sha != NULL; ++max_i) - /* count up */; - - for (try = 0; try < REPEAT; ++try) { - - cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); - - for (th = 0; th < THREADCOUNT; ++th) { - data = git__malloc(2 * sizeof(int)); - - ((int *)data)[0] = th; - ((int *)data)[1] = th % max_i; - - fn = (th & 1) ? cache_parsed : cache_raw; - -#ifdef GIT_THREADS - cl_git_pass(git_thread_create(&t[th], fn, data)); -#else - cl_assert(fn(data) == data); - git__free(data); -#endif - } - -#ifdef GIT_THREADS - for (th = 0; th < THREADCOUNT; ++th) { - cl_git_pass(git_thread_join(&t[th], &data)); - cl_assert_equal_i(th, ((int *)data)[0]); - git__free(data); - } -#endif - - git_repository_free(g_repo); - g_repo = NULL; - } -} - -static void *cache_quick(void *arg) -{ - git_oid oid; - git_object *obj; - - cl_git_pass(git_oid_fromstr(&oid, g_data[4].sha)); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - cl_assert(g_data[4].type == git_object_type(obj)); - git_object_free(obj); - - return arg; -} - -void test_object_cache__fast_thread_rush(void) -{ - int try, th, data[THREADCOUNT]; -#ifdef GIT_THREADS - git_thread t[THREADCOUNT]; -#endif - - for (try = 0; try < REPEAT; ++try) { - cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); - - for (th = 0; th < THREADCOUNT; ++th) { - data[th] = th; -#ifdef GIT_THREADS - cl_git_pass( - git_thread_create(&t[th], cache_quick, &data[th])); -#else - cl_assert(cache_quick(&data[th]) == &data[th]); -#endif - } - -#ifdef GIT_THREADS - for (th = 0; th < THREADCOUNT; ++th) { - void *rval; - cl_git_pass(git_thread_join(&t[th], &rval)); - cl_assert_equal_i(th, *((int *)rval)); - } -#endif - - git_repository_free(g_repo); - g_repo = NULL; - } -} diff --git a/tests/object/commit/commitstagedfile.c b/tests/object/commit/commitstagedfile.c deleted file mode 100644 index 7322a4e86..000000000 --- a/tests/object/commit/commitstagedfile.c +++ /dev/null @@ -1,218 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" - -static git_repository *repo; - -void test_object_commit_commitstagedfile__initialize(void) -{ - cl_fixture("treebuilder"); - cl_git_pass(git_repository_init(&repo, "treebuilder/", 0)); - cl_assert(repo != NULL); -} - -void test_object_commit_commitstagedfile__cleanup(void) -{ - git_repository_free(repo); - repo = NULL; - - cl_fixture_cleanup("treebuilder"); -} - -void test_object_commit_commitstagedfile__generate_predictable_object_ids(void) -{ - git_index *index; - const git_index_entry *entry; - git_oid expected_blob_oid, tree_oid, expected_tree_oid, commit_oid, expected_commit_oid; - git_signature *signature; - git_tree *tree; - git_buf buffer = GIT_BUF_INIT; - - /* - * The test below replicates the following git scenario - * - * $ echo "test" > test.txt - * $ git hash-object test.txt - * 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 - * - * $ git add . - * $ git commit -m "Initial commit" - * - * $ git log - * commit 1fe3126578fc4eca68c193e4a3a0a14a0704624d - * Author: nulltoken - * Date: Wed Dec 14 08:29:03 2011 +0100 - * - * Initial commit - * - * $ git show 1fe3 --format=raw - * commit 1fe3126578fc4eca68c193e4a3a0a14a0704624d - * tree 2b297e643c551e76cfa1f93810c50811382f9117 - * author nulltoken 1323847743 +0100 - * committer nulltoken 1323847743 +0100 - * - * Initial commit - * - * diff --git a/test.txt b/test.txt - * new file mode 100644 - * index 0000000..9daeafb - * --- /dev/null - * +++ b/test.txt - * @@ -0,0 +1 @@ - * +test - * - * $ git ls-tree 2b297 - * 100644 blob 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 test.txt - */ - - cl_git_pass(git_oid_fromstr(&expected_commit_oid, "1fe3126578fc4eca68c193e4a3a0a14a0704624d")); - cl_git_pass(git_oid_fromstr(&expected_tree_oid, "2b297e643c551e76cfa1f93810c50811382f9117")); - cl_git_pass(git_oid_fromstr(&expected_blob_oid, "9daeafb9864cf43055ae93beb0afd6c7d144bfa4")); - - /* - * Add a new file to the index - */ - cl_git_mkfile("treebuilder/test.txt", "test\n"); - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, "test.txt")); - - entry = git_index_get_byindex(index, 0); - - cl_assert(git_oid_cmp(&expected_blob_oid, &entry->id) == 0); - - /* - * Information about index entry should match test file - */ - { - struct stat st; - cl_must_pass(p_lstat("treebuilder/test.txt", &st)); - cl_assert(entry->file_size == st.st_size); -#ifndef _WIN32 - /* - * Windows doesn't populate these fields, and the signage is - * wrong in the Windows version of the struct, so lets avoid - * the "comparing signed and unsigned" compilation warning in - * that case. - */ - cl_assert(entry->uid == st.st_uid); - cl_assert(entry->gid == st.st_gid); -#endif - } - - /* - * Build the tree from the index - */ - cl_git_pass(git_index_write_tree(&tree_oid, index)); - - cl_assert(git_oid_cmp(&expected_tree_oid, &tree_oid) == 0); - - /* - * Commit the staged file - */ - cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); - cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); - - cl_git_pass(git_message_prettify(&buffer, "Initial commit", 0, '#')); - - cl_git_pass(git_commit_create_v( - &commit_oid, - repo, - "HEAD", - signature, - signature, - NULL, - buffer.ptr, - tree, - 0)); - - cl_assert(git_oid_cmp(&expected_commit_oid, &commit_oid) == 0); - - git_buf_dispose(&buffer); - git_signature_free(signature); - git_tree_free(tree); - git_index_free(index); -} - -static void assert_commit_tree_has_n_entries(git_commit *c, int count) -{ - git_tree *tree; - cl_git_pass(git_commit_tree(&tree, c)); - cl_assert_equal_i(count, git_tree_entrycount(tree)); - git_tree_free(tree); -} - -static void assert_commit_is_head_(git_commit *c, const char *file, const char *func, int line) -{ - git_commit *head; - cl_git_pass(git_revparse_single((git_object **)&head, repo, "HEAD")); - clar__assert(git_oid_equal(git_commit_id(c), git_commit_id(head)), file, func, line, "Commit is not the HEAD", NULL, 1); - git_commit_free(head); -} -#define assert_commit_is_head(C) assert_commit_is_head_((C),__FILE__,__func__,__LINE__) - -void test_object_commit_commitstagedfile__amend_commit(void) -{ - git_index *index; - git_oid old_oid, new_oid, tree_oid; - git_commit *old_commit, *new_commit; - git_tree *tree; - - /* make a commit */ - - cl_git_mkfile("treebuilder/myfile", "This is a file\n"); - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, "myfile")); - cl_repo_commit_from_index(&old_oid, repo, NULL, 0, "first commit"); - - cl_git_pass(git_commit_lookup(&old_commit, repo, &old_oid)); - - cl_assert_equal_i(0, git_commit_parentcount(old_commit)); - assert_commit_tree_has_n_entries(old_commit, 1); - assert_commit_is_head(old_commit); - - /* let's amend the message of the HEAD commit */ - - cl_git_pass(git_commit_amend( - &new_oid, old_commit, "HEAD", NULL, NULL, NULL, "Initial commit", NULL)); - - /* fail because the commit isn't the tip of the branch anymore */ - cl_git_fail(git_commit_amend( - &new_oid, old_commit, "HEAD", NULL, NULL, NULL, "Initial commit", NULL)); - - cl_git_pass(git_commit_lookup(&new_commit, repo, &new_oid)); - - cl_assert_equal_i(0, git_commit_parentcount(new_commit)); - assert_commit_tree_has_n_entries(new_commit, 1); - assert_commit_is_head(new_commit); - - git_commit_free(old_commit); - - old_commit = new_commit; - - /* let's amend the tree of that last commit */ - - cl_git_mkfile("treebuilder/anotherfile", "This is another file\n"); - cl_git_pass(git_index_add_bypath(index, "anotherfile")); - cl_git_pass(git_index_write_tree(&tree_oid, index)); - cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); - cl_assert_equal_i(2, git_tree_entrycount(tree)); - - /* fail to amend on a ref which does not exist */ - cl_git_fail_with(GIT_ENOTFOUND, git_commit_amend( - &new_oid, old_commit, "refs/heads/nope", NULL, NULL, NULL, "Initial commit", tree)); - - cl_git_pass(git_commit_amend( - &new_oid, old_commit, "HEAD", NULL, NULL, NULL, "Initial commit", tree)); - git_tree_free(tree); - - cl_git_pass(git_commit_lookup(&new_commit, repo, &new_oid)); - - cl_assert_equal_i(0, git_commit_parentcount(new_commit)); - assert_commit_tree_has_n_entries(new_commit, 2); - assert_commit_is_head(new_commit); - - /* cleanup */ - - git_commit_free(old_commit); - git_commit_free(new_commit); - git_index_free(index); -} diff --git a/tests/object/commit/parse.c b/tests/object/commit/parse.c deleted file mode 100644 index 4ff1ad62a..000000000 --- a/tests/object/commit/parse.c +++ /dev/null @@ -1,232 +0,0 @@ -#include "clar_libgit2.h" -#include "commit.h" -#include "object.h" -#include "signature.h" - -static void assert_commit_parses(const char *data, size_t datalen, - const char *expected_treeid, - const char *expected_author, - const char *expected_committer, - const char *expected_encoding, - const char *expected_message, - size_t expected_parents) -{ - git_commit *commit; - if (!datalen) - datalen = strlen(data); - cl_git_pass(git_object__from_raw((git_object **) &commit, data, datalen, GIT_OBJECT_COMMIT)); - - if (expected_author) { - git_signature *author; - cl_git_pass(git_signature_from_buffer(&author, expected_author)); - cl_assert(git_signature__equal(author, commit->author)); - cl_assert_equal_s(author->name, commit->author->name); - cl_assert_equal_s(author->email, commit->author->email); - cl_assert_equal_i(author->when.time, commit->author->when.time); - cl_assert_equal_i(author->when.offset, commit->author->when.offset); - cl_assert_equal_i(author->when.sign, commit->author->when.sign); - git_signature_free(author); - } - - if (expected_committer) { - git_signature *committer; - cl_git_pass(git_signature_from_buffer(&committer, expected_committer)); - cl_assert_equal_s(committer->name, commit->committer->name); - cl_assert_equal_s(committer->email, commit->committer->email); - cl_assert_equal_i(committer->when.time, commit->committer->when.time); - cl_assert_equal_i(committer->when.offset, commit->committer->when.offset); - cl_assert_equal_i(committer->when.sign, commit->committer->when.sign); - git_signature_free(committer); - } - - if (expected_encoding) - cl_assert_equal_s(commit->message_encoding, expected_encoding); - else - cl_assert_equal_p(commit->message_encoding, NULL); - - if (expected_message) - cl_assert_equal_s(commit->raw_message, expected_message); - else - cl_assert_equal_p(commit->message_encoding, NULL); - - if (expected_treeid) { - git_oid tree_oid; - cl_git_pass(git_oid_fromstr(&tree_oid, expected_treeid)); - cl_assert_equal_oid(&tree_oid, &commit->tree_id); - } - - cl_assert_equal_i(commit->parent_ids.size, expected_parents); - - git_object__free(&commit->object); -} - -static void assert_commit_fails(const char *data, size_t datalen) -{ - git_object *object; - if (!datalen) - datalen = strlen(data); - cl_git_fail(git_object__from_raw(&object, data, datalen, GIT_OBJECT_COMMIT)); -} - -void test_object_commit_parse__parsing_commit_succeeds(void) -{ - const char *commit = - "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" - "author Author \n" - "committer Committer \n" - "encoding Encoding\n" - "\n" - "Message"; - assert_commit_parses(commit, 0, - "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", - "Author ", - "Committer ", - "Encoding", - "Message", 0); -} - -void test_object_commit_parse__parsing_commit_without_encoding_succeeds(void) -{ - const char *commit = - "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" - "author Author \n" - "committer Committer \n" - "\n" - "Message"; - assert_commit_parses(commit, 0, - "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", - "Author ", - "Committer ", - NULL, - "Message", 0); -} - -void test_object_commit_parse__parsing_commit_with_multiple_authors_succeeds(void) -{ - const char *commit = - "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" - "author Author1 \n" - "author Author2 \n" - "author Author3 \n" - "author Author4 \n" - "committer Committer \n" - "\n" - "Message"; - assert_commit_parses(commit, 0, - "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", - "Author1 ", - "Committer ", - NULL, - "Message", 0); -} - -void test_object_commit_parse__parsing_commit_with_multiple_committers_succeeds(void) -{ - const char *commit = - "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" - "author Author \n" - "committer Committer1 \n" - "committer Committer2 \n" - "committer Committer3 \n" - "committer Committer4 \n" - "\n" - "Message"; - assert_commit_parses(commit, 0, - "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", - "Author ", - "Committer1 ", - NULL, - "Message", 0); -} - -void test_object_commit_parse__parsing_commit_without_message_succeeds(void) -{ - const char *commit = - "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" - "author Author \n" - "committer Committer \n"; - assert_commit_parses(commit, 0, - "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", - "Author ", - "Committer ", - NULL, - "", 0); -} - -void test_object_commit_parse__parsing_commit_with_unknown_fields_succeeds(void) -{ - const char *commit = - "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" - "author Author \n" - "committer Committer \n" - "foo bar\n" - "more garbage\n" - "\n" - "Message"; - assert_commit_parses(commit, 0, - "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", - "Author ", - "Committer ", - NULL, - "Message", 0); -} - -void test_object_commit_parse__parsing_commit_with_invalid_tree_fails(void) -{ - const char *commit = - "tree 3e7ac388cadacccdf1xxx5f3445895b71d9cb0f8\n" - "author Author \n" - "committer Committer \n" - "\n" - "Message"; - assert_commit_fails(commit, 0); -} - -void test_object_commit_parse__parsing_commit_without_tree_fails(void) -{ - const char *commit = - "author Author \n" - "committer Committer \n" - "\n" - "Message"; - assert_commit_fails(commit, 0); -} - -void test_object_commit_parse__parsing_commit_without_author_fails(void) -{ - const char *commit = - "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" - "committer Committer \n" - "\n" - "Message"; - assert_commit_fails(commit, 0); -} - -void test_object_commit_parse__parsing_commit_without_committer_fails(void) -{ - const char *commit = - "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" - "author Author \n" - "\n" - "Message"; - assert_commit_fails(commit, 0); -} - -void test_object_commit_parse__parsing_encoding_will_not_cause_oob_read(void) -{ - const char *commit = - "tree 3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8\n" - "author <>\n" - "committer <>\n" - "encoding foo\n"; - /* - * As we ignore unknown fields, the cut-off encoding field will be - * parsed just fine. - */ - assert_commit_parses(commit, strlen(commit) - strlen("ncoding foo\n"), - "3e7ac388cadacccdf1c6c5f3445895b71d9cb0f8", - "<>", - "<>", - NULL, - "", 0); -} diff --git a/tests/object/lookup.c b/tests/object/lookup.c deleted file mode 100644 index a7b1ceeb4..000000000 --- a/tests/object/lookup.c +++ /dev/null @@ -1,122 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" - -static git_repository *g_repo; - -void test_object_lookup__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_object_lookup__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_object_lookup__lookup_wrong_type_returns_enotfound(void) -{ - const char *commit = "e90810b8df3e80c413d903f631643c716887138d"; - git_oid oid; - git_object *object; - - cl_git_pass(git_oid_fromstr(&oid, commit)); - cl_assert_equal_i( - GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_TAG)); -} - -void test_object_lookup__lookup_nonexisting_returns_enotfound(void) -{ - const char *unknown = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; - git_oid oid; - git_object *object; - - cl_git_pass(git_oid_fromstr(&oid, unknown)); - cl_assert_equal_i( - GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_ANY)); -} - -void test_object_lookup__lookup_wrong_type_by_abbreviated_id_returns_enotfound(void) -{ - const char *commit = "e90810b"; - git_oid oid; - git_object *object; - - cl_git_pass(git_oid_fromstrn(&oid, commit, strlen(commit))); - cl_assert_equal_i( - GIT_ENOTFOUND, git_object_lookup_prefix(&object, g_repo, &oid, strlen(commit), GIT_OBJECT_TAG)); -} - -void test_object_lookup__lookup_wrong_type_eventually_returns_enotfound(void) -{ - const char *commit = "e90810b8df3e80c413d903f631643c716887138d"; - git_oid oid; - git_object *object; - - cl_git_pass(git_oid_fromstr(&oid, commit)); - - cl_git_pass(git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_COMMIT)); - git_object_free(object); - - cl_assert_equal_i( - GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_TAG)); -} - -void test_object_lookup__lookup_corrupt_object_returns_error(void) -{ - const char *commit = "8e73b769e97678d684b809b163bebdae2911720f", - *file = "objects/8e/73b769e97678d684b809b163bebdae2911720f"; - git_str path = GIT_STR_INIT, contents = GIT_STR_INIT; - git_oid oid; - git_object *object; - size_t i; - - cl_git_pass(git_oid_fromstr(&oid, commit)); - cl_git_pass(git_str_joinpath(&path, git_repository_path(g_repo), file)); - cl_git_pass(git_futils_readbuffer(&contents, path.ptr)); - - /* Corrupt and try to read the object */ - for (i = 0; i < contents.size; i++) { - contents.ptr[i] ^= 0x1; - cl_git_pass(git_futils_writebuffer(&contents, path.ptr, O_RDWR, 0644)); - cl_git_fail(git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_COMMIT)); - contents.ptr[i] ^= 0x1; - } - - /* Restore original content and assert we can read the object */ - cl_git_pass(git_futils_writebuffer(&contents, path.ptr, O_RDWR, 0644)); - cl_git_pass(git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_COMMIT)); - - git_object_free(object); - git_str_dispose(&path); - git_str_dispose(&contents); -} - -void test_object_lookup__lookup_object_with_wrong_hash_returns_error(void) -{ - const char *oldloose = "objects/8e/73b769e97678d684b809b163bebdae2911720f", - *newloose = "objects/8e/73b769e97678d684b809b163bebdae2911720e", - *commit = "8e73b769e97678d684b809b163bebdae2911720e"; - git_str oldpath = GIT_STR_INIT, newpath = GIT_STR_INIT; - git_object *object; - git_oid oid; - - cl_git_pass(git_oid_fromstr(&oid, commit)); - - /* Copy object to another location with wrong hash */ - cl_git_pass(git_str_joinpath(&oldpath, git_repository_path(g_repo), oldloose)); - cl_git_pass(git_str_joinpath(&newpath, git_repository_path(g_repo), newloose)); - cl_git_pass(git_futils_cp(oldpath.ptr, newpath.ptr, 0644)); - - /* Verify that lookup fails due to a hashsum mismatch */ - cl_git_fail_with(GIT_EMISMATCH, git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_COMMIT)); - - /* Disable verification and try again */ - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)); - cl_git_pass(git_object_lookup(&object, g_repo, &oid, GIT_OBJECT_COMMIT)); - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1)); - - git_object_free(object); - git_str_dispose(&oldpath); - git_str_dispose(&newpath); -} diff --git a/tests/object/lookupbypath.c b/tests/object/lookupbypath.c deleted file mode 100644 index f2257f556..000000000 --- a/tests/object/lookupbypath.c +++ /dev/null @@ -1,83 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" - -static git_repository *g_repo; -static git_tree *g_root_tree; -static git_commit *g_head_commit; -static git_object *g_expectedobject, - *g_actualobject; - -void test_object_lookupbypath__initialize(void) -{ - git_reference *head; - git_tree_entry *tree_entry; - - cl_git_pass(git_repository_open(&g_repo, cl_fixture("attr/.gitted"))); - - cl_git_pass(git_repository_head(&head, g_repo)); - cl_git_pass(git_reference_peel((git_object**)&g_head_commit, head, GIT_OBJECT_COMMIT)); - cl_git_pass(git_commit_tree(&g_root_tree, g_head_commit)); - cl_git_pass(git_tree_entry_bypath(&tree_entry, g_root_tree, "subdir/subdir_test2.txt")); - cl_git_pass(git_object_lookup(&g_expectedobject, g_repo, git_tree_entry_id(tree_entry), - GIT_OBJECT_ANY)); - - git_tree_entry_free(tree_entry); - git_reference_free(head); - - g_actualobject = NULL; -} -void test_object_lookupbypath__cleanup(void) -{ - git_object_free(g_actualobject); - git_object_free(g_expectedobject); - git_tree_free(g_root_tree); - git_commit_free(g_head_commit); - g_expectedobject = NULL; - git_repository_free(g_repo); - g_repo = NULL; -} - -void test_object_lookupbypath__errors(void) -{ - cl_assert_equal_i(GIT_EINVALIDSPEC, - git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree, - "subdir/subdir_test2.txt", GIT_OBJECT_TREE)); /* It's not a tree */ - cl_assert_equal_i(GIT_ENOTFOUND, - git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree, - "file/doesnt/exist", GIT_OBJECT_ANY)); -} - -void test_object_lookupbypath__from_root_tree(void) -{ - cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree, - "subdir/subdir_test2.txt", GIT_OBJECT_BLOB)); - cl_assert_equal_oid(git_object_id(g_expectedobject), - git_object_id(g_actualobject)); -} - -void test_object_lookupbypath__from_head_commit(void) -{ - cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)g_head_commit, - "subdir/subdir_test2.txt", GIT_OBJECT_BLOB)); - cl_assert_equal_oid(git_object_id(g_expectedobject), - git_object_id(g_actualobject)); -} - -void test_object_lookupbypath__from_subdir_tree(void) -{ - git_tree_entry *entry = NULL; - git_tree *tree = NULL; - - cl_git_pass(git_tree_entry_bypath(&entry, g_root_tree, "subdir")); - cl_git_pass(git_tree_lookup(&tree, g_repo, git_tree_entry_id(entry))); - - cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)tree, - "subdir_test2.txt", GIT_OBJECT_BLOB)); - cl_assert_equal_oid(git_object_id(g_expectedobject), - git_object_id(g_actualobject)); - - git_tree_entry_free(entry); - git_tree_free(tree); -} - diff --git a/tests/object/message.c b/tests/object/message.c deleted file mode 100644 index d87c8ef70..000000000 --- a/tests/object/message.c +++ /dev/null @@ -1,197 +0,0 @@ -#include "clar_libgit2.h" - -static void assert_message_prettifying(char *expected_output, char *input, int strip_comments) -{ - git_buf prettified_message = GIT_BUF_INIT; - - git_message_prettify(&prettified_message, input, strip_comments, '#'); - cl_assert_equal_s(expected_output, prettified_message.ptr); - - git_buf_dispose(&prettified_message); -} - -#define t40 "A quick brown fox jumps over the lazy do" -#define s40 " " -#define sss s40 s40 s40 s40 s40 s40 s40 s40 s40 s40 /* # 400 */ -#define ttt t40 t40 t40 t40 t40 t40 t40 t40 t40 t40 /* # 400 */ - -/* Ported from git.git */ -/* see https://github.com/git/git/blob/master/t/t0030-stripspace.sh */ -void test_object_message__long_lines_without_spaces_should_be_unchanged(void) -{ - assert_message_prettifying(ttt "\n", ttt, 0); - assert_message_prettifying(ttt ttt "\n", ttt ttt, 0); - assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt, 0); - assert_message_prettifying(ttt ttt ttt ttt "\n", ttt ttt ttt ttt, 0); -} - -void test_object_message__lines_with_spaces_at_the_beginning_should_be_unchanged(void) -{ - assert_message_prettifying(sss ttt "\n", sss ttt, 0); - assert_message_prettifying(sss sss ttt "\n", sss sss ttt, 0); - assert_message_prettifying(sss sss sss ttt "\n", sss sss sss ttt, 0); -} - -void test_object_message__lines_with_intermediate_spaces_should_be_unchanged(void) -{ - assert_message_prettifying(ttt sss ttt "\n", ttt sss ttt, 0); - assert_message_prettifying(ttt sss sss ttt "\n", ttt sss sss ttt, 0); -} - -void test_object_message__consecutive_blank_lines_should_be_unified(void) -{ - assert_message_prettifying(ttt "\n\n" ttt "\n", ttt "\n\n\n\n\n" ttt "\n", 0); - assert_message_prettifying(ttt ttt "\n\n" ttt "\n", ttt ttt "\n\n\n\n\n" ttt "\n", 0); - assert_message_prettifying(ttt ttt ttt "\n\n" ttt "\n", ttt ttt ttt "\n\n\n\n\n" ttt "\n", 0); - - assert_message_prettifying(ttt "\n\n" ttt ttt "\n", ttt "\n\n\n\n\n" ttt ttt "\n", 0); - assert_message_prettifying(ttt "\n\n" ttt ttt ttt "\n", ttt "\n\n\n\n\n" ttt ttt ttt "\n", 0); - - assert_message_prettifying(ttt "\n\n" ttt "\n", ttt "\n\t\n \n\n \t\t\n" ttt "\n", 0); - assert_message_prettifying(ttt ttt "\n\n" ttt "\n", ttt ttt "\n\t\n \n\n \t\t\n" ttt "\n", 0); - assert_message_prettifying(ttt ttt ttt "\n\n" ttt "\n", ttt ttt ttt "\n\t\n \n\n \t\t\n" ttt "\n", 0); - - assert_message_prettifying(ttt "\n\n" ttt ttt "\n", ttt "\n\t\n \n\n \t\t\n" ttt ttt "\n", 0); - assert_message_prettifying(ttt "\n\n" ttt ttt ttt "\n", ttt "\n\t\n \n\n \t\t\n" ttt ttt ttt "\n", 0); -} - -void test_object_message__only_consecutive_blank_lines_should_be_completely_removed(void) -{ - assert_message_prettifying("", "\n", 0); - assert_message_prettifying("", "\n\n\n", 0); - assert_message_prettifying("", sss "\n" sss "\n" sss "\n", 0); - assert_message_prettifying("", sss sss "\n" sss "\n\n", 0); -} - -void test_object_message__consecutive_blank_lines_at_the_beginning_should_be_removed(void) -{ - assert_message_prettifying(ttt "\n", "\n" ttt "\n", 0); - assert_message_prettifying(ttt "\n", "\n\n\n" ttt "\n", 0); - assert_message_prettifying(ttt ttt "\n", "\n\n\n" ttt ttt "\n", 0); - assert_message_prettifying(ttt ttt ttt "\n", "\n\n\n" ttt ttt ttt "\n", 0); - assert_message_prettifying(ttt ttt ttt ttt "\n", "\n\n\n" ttt ttt ttt ttt "\n", 0); - assert_message_prettifying(ttt "\n", sss "\n" sss "\n" sss "\n" ttt "\n", 0); - assert_message_prettifying(ttt "\n", "\n" sss "\n" sss sss "\n" ttt "\n", 0); - assert_message_prettifying(ttt "\n", sss sss "\n" sss "\n\n" ttt "\n", 0); - assert_message_prettifying(ttt "\n", sss sss sss "\n\n\n" ttt "\n", 0); - assert_message_prettifying(ttt "\n", "\n" sss sss sss "\n\n" ttt "\n", 0); - assert_message_prettifying(ttt "\n", "\n\n" sss sss sss "\n" ttt "\n", 0); -} - -void test_object_message__consecutive_blank_lines_at_the_end_should_be_removed(void) -{ - assert_message_prettifying(ttt "\n", ttt "\n\n", 0); - assert_message_prettifying(ttt "\n", ttt "\n\n\n\n", 0); - assert_message_prettifying(ttt ttt "\n", ttt ttt "\n\n\n\n", 0); - assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt "\n\n\n\n", 0); - assert_message_prettifying(ttt ttt ttt ttt "\n", ttt ttt ttt ttt "\n\n\n\n", 0); - assert_message_prettifying(ttt "\n", ttt "\n" sss "\n" sss "\n" sss "\n", 0); - assert_message_prettifying(ttt "\n", ttt "\n\n" sss "\n" sss sss "\n", 0); - assert_message_prettifying(ttt "\n", ttt "\n" sss sss "\n" sss "\n\n", 0); - assert_message_prettifying(ttt "\n", ttt "\n" sss sss sss "\n\n\n", 0); - assert_message_prettifying(ttt "\n", ttt "\n\n" sss sss sss "\n\n", 0); - assert_message_prettifying(ttt "\n", ttt "\n\n\n" sss sss sss "\n\n", 0); -} - -void test_object_message__text_without_newline_at_end_should_end_with_newline(void) -{ - assert_message_prettifying(ttt "\n", ttt, 0); - assert_message_prettifying(ttt ttt "\n", ttt ttt, 0); - assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt, 0); - assert_message_prettifying(ttt ttt ttt ttt "\n", ttt ttt ttt ttt, 0); -} - -void test_object_message__text_plus_spaces_without_newline_should_not_show_spaces_and_end_with_newline(void) -{ - assert_message_prettifying(ttt "\n", ttt sss, 0); - assert_message_prettifying(ttt ttt "\n", ttt ttt sss, 0); - assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt sss, 0); - assert_message_prettifying(ttt "\n", ttt sss sss, 0); - assert_message_prettifying(ttt ttt "\n", ttt ttt sss sss, 0); - assert_message_prettifying(ttt "\n", ttt sss sss sss, 0); -} - -void test_object_message__text_plus_spaces_ending_with_newline_should_be_cleaned_and_newline_must_remain(void){ - assert_message_prettifying(ttt "\n", ttt sss "\n", 0); - assert_message_prettifying(ttt "\n", ttt sss sss "\n", 0); - assert_message_prettifying(ttt "\n", ttt sss sss sss "\n", 0); - assert_message_prettifying(ttt ttt "\n", ttt ttt sss "\n", 0); - assert_message_prettifying(ttt ttt "\n", ttt ttt sss sss "\n", 0); - assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt sss "\n", 0); -} - -void test_object_message__spaces_with_newline_at_end_should_be_replaced_with_empty_string(void) -{ - assert_message_prettifying("", sss "\n", 0); - assert_message_prettifying("", sss sss "\n", 0); - assert_message_prettifying("", sss sss sss "\n", 0); - assert_message_prettifying("", sss sss sss sss "\n", 0); -} - -void test_object_message__spaces_without_newline_at_end_should_be_replaced_with_empty_string(void) -{ - assert_message_prettifying("", "", 0); - assert_message_prettifying("", sss sss, 0); - assert_message_prettifying("", sss sss sss, 0); - assert_message_prettifying("", sss sss sss sss, 0); -} - -void test_object_message__consecutive_text_lines_should_be_unchanged(void) -{ - assert_message_prettifying(ttt ttt "\n" ttt "\n", ttt ttt "\n" ttt "\n", 0); - assert_message_prettifying(ttt "\n" ttt ttt "\n" ttt "\n", ttt "\n" ttt ttt "\n" ttt "\n", 0); - assert_message_prettifying(ttt "\n" ttt "\n" ttt "\n" ttt ttt "\n", ttt "\n" ttt "\n" ttt "\n" ttt ttt "\n", 0); - assert_message_prettifying(ttt "\n" ttt "\n\n" ttt ttt "\n" ttt "\n", ttt "\n" ttt "\n\n" ttt ttt "\n" ttt "\n", 0); - assert_message_prettifying(ttt ttt "\n\n" ttt "\n" ttt ttt "\n", ttt ttt "\n\n" ttt "\n" ttt ttt "\n", 0); - assert_message_prettifying(ttt "\n" ttt ttt "\n\n" ttt "\n", ttt "\n" ttt ttt "\n\n" ttt "\n", 0); -} - -void test_object_message__strip_comments(void) -{ - assert_message_prettifying("", "# comment", 1); - assert_message_prettifying("", "# comment\n", 1); - assert_message_prettifying("", "# comment \n", 1); - - assert_message_prettifying(ttt "\n", ttt "\n" "# comment\n", 1); - assert_message_prettifying(ttt "\n", "# comment\n" ttt "\n", 1); - assert_message_prettifying(ttt "\n" ttt "\n", ttt "\n" "# comment\n" ttt "\n", 1); -} - -void test_object_message__keep_comments(void) -{ - assert_message_prettifying("# comment\n", "# comment", 0); - assert_message_prettifying("# comment\n", "# comment\n", 0); - assert_message_prettifying("# comment\n", "# comment \n", 0); - - assert_message_prettifying(ttt "\n" "# comment\n", ttt "\n" "# comment\n", 0); - assert_message_prettifying("# comment\n" ttt "\n", "# comment\n" ttt "\n", 0); - assert_message_prettifying(ttt "\n" "# comment\n" ttt "\n", ttt "\n" "# comment\n" ttt "\n", 0); -} - -void test_object_message__message_prettify(void) -{ - git_buf buffer; - - memset(&buffer, 0, sizeof(buffer)); - cl_git_pass(git_message_prettify(&buffer, "", 0, '#')); - cl_assert_equal_s(buffer.ptr, ""); - git_buf_dispose(&buffer); - cl_git_pass(git_message_prettify(&buffer, "", 1, '#')); - cl_assert_equal_s(buffer.ptr, ""); - git_buf_dispose(&buffer); - - cl_git_pass(git_message_prettify(&buffer, "Short", 0, '#')); - cl_assert_equal_s("Short\n", buffer.ptr); - git_buf_dispose(&buffer); - cl_git_pass(git_message_prettify(&buffer, "Short", 1, '#')); - cl_assert_equal_s("Short\n", buffer.ptr); - git_buf_dispose(&buffer); - - cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 0, '#')); - cl_assert_equal_s(buffer.ptr, "This is longer\nAnd multiline\n# with some comments still in\n"); - git_buf_dispose(&buffer); - - cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 1, '#')); - cl_assert_equal_s(buffer.ptr, "This is longer\nAnd multiline\n"); - git_buf_dispose(&buffer); -} diff --git a/tests/object/peel.c b/tests/object/peel.c deleted file mode 100644 index 8be6e42d4..000000000 --- a/tests/object/peel.c +++ /dev/null @@ -1,118 +0,0 @@ -#include "clar_libgit2.h" - -static git_repository *g_repo; - -void test_object_peel__initialize(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); -} - -void test_object_peel__cleanup(void) -{ - git_repository_free(g_repo); - g_repo = NULL; -} - -static void assert_peel( - const char *sha, - git_object_t requested_type, - const char* expected_sha, - git_object_t expected_type) -{ - git_oid oid, expected_oid; - git_object *obj; - git_object *peeled; - - cl_git_pass(git_oid_fromstr(&oid, sha)); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_git_pass(git_object_peel(&peeled, obj, requested_type)); - - cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); - cl_assert_equal_oid(&expected_oid, git_object_id(peeled)); - - cl_assert_equal_i(expected_type, git_object_type(peeled)); - - git_object_free(peeled); - git_object_free(obj); -} - -static void assert_peel_error(int error, const char *sha, git_object_t requested_type) -{ - git_oid oid; - git_object *obj; - git_object *peeled; - - cl_git_pass(git_oid_fromstr(&oid, sha)); - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - - cl_assert_equal_i(error, git_object_peel(&peeled, obj, requested_type)); - - git_object_free(obj); -} - -void test_object_peel__peeling_an_object_into_its_own_type_returns_another_instance_of_it(void) -{ - assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT, - "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); - assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_TAG, - "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_TAG); - assert_peel("53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE, - "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); - assert_peel("0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_BLOB, - "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_BLOB); -} - -void test_object_peel__tag(void) -{ - assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_COMMIT, - "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); - assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_TREE, - "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); - assert_peel_error(GIT_EPEEL, "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_BLOB); - assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_ANY, - "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); -} - -void test_object_peel__commit(void) -{ - assert_peel_error(GIT_EINVALIDSPEC, "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_BLOB); - assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_TREE, - "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); - assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT, - "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); - assert_peel_error(GIT_EINVALIDSPEC, "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_TAG); - assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_ANY, - "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); -} - -void test_object_peel__tree(void) -{ - assert_peel_error(GIT_EINVALIDSPEC, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_BLOB); - assert_peel("53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE, - "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); - assert_peel_error(GIT_EINVALIDSPEC, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_COMMIT); - assert_peel_error(GIT_EINVALIDSPEC, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TAG); - assert_peel_error(GIT_EINVALIDSPEC, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_ANY); -} - -void test_object_peel__blob(void) -{ - assert_peel("0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_BLOB, - "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_BLOB); - assert_peel_error(GIT_EINVALIDSPEC, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_TREE); - assert_peel_error(GIT_EINVALIDSPEC, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_COMMIT); - assert_peel_error(GIT_EINVALIDSPEC, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_TAG); - assert_peel_error(GIT_EINVALIDSPEC, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJECT_ANY); -} - -void test_object_peel__target_any_object_for_type_change(void) -{ - /* tag to commit */ - assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJECT_ANY, - "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); - - /* commit to tree */ - assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_ANY, - "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); -} diff --git a/tests/object/raw/chars.c b/tests/object/raw/chars.c deleted file mode 100644 index cde0bdbf6..000000000 --- a/tests/object/raw/chars.c +++ /dev/null @@ -1,41 +0,0 @@ - -#include "clar_libgit2.h" - -#include "odb.h" - -void test_object_raw_chars__find_invalid_chars_in_oid(void) -{ - git_oid out; - unsigned char exp[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - char in[] = "16a67770b7d8d72317c4b775213c23a8bd74f5e0"; - unsigned int i; - - for (i = 0; i < 256; i++) { - in[38] = (char)i; - if (git__fromhex(i) >= 0) { - exp[19] = (unsigned char)(git__fromhex(i) << 4); - cl_git_pass(git_oid_fromstr(&out, in)); - cl_assert(memcmp(out.id, exp, sizeof(out.id)) == 0); - } else { - cl_git_fail(git_oid_fromstr(&out, in)); - } - } -} - -void test_object_raw_chars__build_valid_oid_from_raw_bytes(void) -{ - git_oid out; - unsigned char exp[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - git_oid_fromraw(&out, exp); - cl_git_pass(memcmp(out.id, exp, sizeof(out.id))); -} diff --git a/tests/object/raw/compare.c b/tests/object/raw/compare.c deleted file mode 100644 index 56c016b72..000000000 --- a/tests/object/raw/compare.c +++ /dev/null @@ -1,123 +0,0 @@ - -#include "clar_libgit2.h" - -#include "odb.h" - -void test_object_raw_compare__succeed_on_copy_oid(void) -{ - git_oid a, b; - unsigned char exp[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - memset(&b, 0, sizeof(b)); - git_oid_fromraw(&a, exp); - git_oid_cpy(&b, &a); - cl_git_pass(memcmp(a.id, exp, sizeof(a.id))); -} - -void test_object_raw_compare__succeed_on_oid_comparison_lesser(void) -{ - git_oid a, b; - unsigned char a_in[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - unsigned char b_in[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xf0, - }; - git_oid_fromraw(&a, a_in); - git_oid_fromraw(&b, b_in); - cl_assert(git_oid_cmp(&a, &b) < 0); -} - -void test_object_raw_compare__succeed_on_oid_comparison_equal(void) -{ - git_oid a, b; - unsigned char a_in[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - git_oid_fromraw(&a, a_in); - git_oid_fromraw(&b, a_in); - cl_assert(git_oid_cmp(&a, &b) == 0); -} - -void test_object_raw_compare__succeed_on_oid_comparison_greater(void) -{ - git_oid a, b; - unsigned char a_in[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - unsigned char b_in[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xd0, - }; - git_oid_fromraw(&a, a_in); - git_oid_fromraw(&b, b_in); - cl_assert(git_oid_cmp(&a, &b) > 0); -} - -void test_object_raw_compare__compare_fmt_oids(void) -{ - const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; - git_oid in; - char out[GIT_OID_HEXSZ + 1]; - - cl_git_pass(git_oid_fromstr(&in, exp)); - - /* Format doesn't touch the last byte */ - out[GIT_OID_HEXSZ] = 'Z'; - git_oid_fmt(out, &in); - cl_assert(out[GIT_OID_HEXSZ] == 'Z'); - - /* Format produced the right result */ - out[GIT_OID_HEXSZ] = '\0'; - cl_assert_equal_s(exp, out); -} - -void test_object_raw_compare__compare_static_oids(void) -{ - const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; - git_oid in; - char *out; - - cl_git_pass(git_oid_fromstr(&in, exp)); - - out = git_oid_tostr_s(&in); - cl_assert(out); - cl_assert_equal_s(exp, out); -} - -void test_object_raw_compare__compare_pathfmt_oids(void) -{ - const char *exp1 = "16a0123456789abcdef4b775213c23a8bd74f5e0"; - const char *exp2 = "16/a0123456789abcdef4b775213c23a8bd74f5e0"; - git_oid in; - char out[GIT_OID_HEXSZ + 2]; - - cl_git_pass(git_oid_fromstr(&in, exp1)); - - /* Format doesn't touch the last byte */ - out[GIT_OID_HEXSZ + 1] = 'Z'; - git_oid_pathfmt(out, &in); - cl_assert(out[GIT_OID_HEXSZ + 1] == 'Z'); - - /* Format produced the right result */ - out[GIT_OID_HEXSZ + 1] = '\0'; - cl_assert_equal_s(exp2, out); -} diff --git a/tests/object/raw/convert.c b/tests/object/raw/convert.c deleted file mode 100644 index 40a01ae09..000000000 --- a/tests/object/raw/convert.c +++ /dev/null @@ -1,112 +0,0 @@ - -#include "clar_libgit2.h" - -#include "odb.h" - -void test_object_raw_convert__succeed_on_oid_to_string_conversion(void) -{ - const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; - git_oid in; - char out[GIT_OID_HEXSZ + 1]; - char *str; - int i; - - cl_git_pass(git_oid_fromstr(&in, exp)); - - /* NULL buffer pointer, returns static empty string */ - str = git_oid_tostr(NULL, sizeof(out), &in); - cl_assert(str && *str == '\0' && str != out); - - /* zero buffer size, returns static empty string */ - str = git_oid_tostr(out, 0, &in); - cl_assert(str && *str == '\0' && str != out); - - /* NULL oid pointer, sets existing buffer to empty string */ - str = git_oid_tostr(out, sizeof(out), NULL); - cl_assert(str && *str == '\0' && str == out); - - /* n == 1, returns out as an empty string */ - str = git_oid_tostr(out, 1, &in); - cl_assert(str && *str == '\0' && str == out); - - for (i = 1; i < GIT_OID_HEXSZ; i++) { - out[i+1] = 'Z'; - str = git_oid_tostr(out, i+1, &in); - /* returns out containing c-string */ - cl_assert(str && str == out); - /* must be '\0' terminated */ - cl_assert(*(str+i) == '\0'); - /* must not touch bytes past end of string */ - cl_assert(*(str+(i+1)) == 'Z'); - /* i == n-1 characters of string */ - cl_git_pass(strncmp(exp, out, i)); - } - - /* returns out as hex formatted c-string */ - str = git_oid_tostr(out, sizeof(out), &in); - cl_assert(str && str == out && *(str+GIT_OID_HEXSZ) == '\0'); - cl_assert_equal_s(exp, out); -} - -void test_object_raw_convert__succeed_on_oid_to_string_conversion_big(void) -{ - const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; - git_oid in; - char big[GIT_OID_HEXSZ + 1 + 3]; /* note + 4 => big buffer */ - char *str; - - cl_git_pass(git_oid_fromstr(&in, exp)); - - /* place some tail material */ - big[GIT_OID_HEXSZ+0] = 'W'; /* should be '\0' afterwards */ - big[GIT_OID_HEXSZ+1] = 'X'; /* should remain untouched */ - big[GIT_OID_HEXSZ+2] = 'Y'; /* ditto */ - big[GIT_OID_HEXSZ+3] = 'Z'; /* ditto */ - - /* returns big as hex formatted c-string */ - str = git_oid_tostr(big, sizeof(big), &in); - cl_assert(str && str == big && *(str+GIT_OID_HEXSZ) == '\0'); - cl_assert_equal_s(exp, big); - - /* check tail material is untouched */ - cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+1) == 'X'); - cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+2) == 'Y'); - cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+3) == 'Z'); -} - -static void check_partial_oid( - char *buffer, size_t count, const git_oid *oid, const char *expected) -{ - git_oid_nfmt(buffer, count, oid); - buffer[count] = '\0'; - cl_assert_equal_s(expected, buffer); -} - -void test_object_raw_convert__convert_oid_partially(void) -{ - const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0"; - git_oid in; - char big[GIT_OID_HEXSZ + 1 + 3]; /* note + 4 => big buffer */ - - cl_git_pass(git_oid_fromstr(&in, exp)); - - git_oid_nfmt(big, sizeof(big), &in); - cl_assert_equal_s(exp, big); - - git_oid_nfmt(big, GIT_OID_HEXSZ + 1, &in); - cl_assert_equal_s(exp, big); - - check_partial_oid(big, 1, &in, "1"); - check_partial_oid(big, 2, &in, "16"); - check_partial_oid(big, 3, &in, "16a"); - check_partial_oid(big, 4, &in, "16a0"); - check_partial_oid(big, 5, &in, "16a01"); - - check_partial_oid(big, GIT_OID_HEXSZ, &in, exp); - check_partial_oid( - big, GIT_OID_HEXSZ - 1, &in, "16a0123456789abcdef4b775213c23a8bd74f5e"); - check_partial_oid( - big, GIT_OID_HEXSZ - 2, &in, "16a0123456789abcdef4b775213c23a8bd74f5"); - check_partial_oid( - big, GIT_OID_HEXSZ - 3, &in, "16a0123456789abcdef4b775213c23a8bd74f"); -} diff --git a/tests/object/raw/data.h b/tests/object/raw/data.h deleted file mode 100644 index 57431e70e..000000000 --- a/tests/object/raw/data.h +++ /dev/null @@ -1,323 +0,0 @@ - -/* - * Raw data - */ -static unsigned char commit_data[] = { - 0x74, 0x72, 0x65, 0x65, 0x20, 0x64, 0x66, 0x66, - 0x32, 0x64, 0x61, 0x39, 0x30, 0x62, 0x32, 0x35, - 0x34, 0x65, 0x31, 0x62, 0x65, 0x62, 0x38, 0x38, - 0x39, 0x64, 0x31, 0x66, 0x31, 0x66, 0x31, 0x32, - 0x38, 0x38, 0x62, 0x65, 0x31, 0x38, 0x30, 0x33, - 0x37, 0x38, 0x32, 0x64, 0x66, 0x0a, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x20, 0x41, 0x20, 0x55, - 0x20, 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, - 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, - 0x31, 0x34, 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, - 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x74, 0x65, 0x72, 0x20, 0x43, 0x20, - 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, - 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, - 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, - 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, - 0x30, 0x0a, 0x0a, 0x41, 0x20, 0x6f, 0x6e, 0x65, - 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x73, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x0a, 0x0a, 0x54, 0x68, - 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x6f, - 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, - 0x20, 0x66, 0x75, 0x72, 0x74, 0x68, 0x65, 0x72, - 0x20, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x70, 0x75, 0x72, 0x70, - 0x6f, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, - 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x62, 0x79, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x53, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x2d, 0x6f, 0x66, 0x2d, - 0x62, 0x79, 0x3a, 0x20, 0x41, 0x20, 0x55, 0x20, - 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, 0x61, - 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, - 0x3e, 0x0a, -}; - - -static unsigned char tree_data[] = { - 0x31, 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x6f, - 0x6e, 0x65, 0x00, 0x8b, 0x13, 0x78, 0x91, 0x79, - 0x1f, 0xe9, 0x69, 0x27, 0xad, 0x78, 0xe6, 0x4b, - 0x0a, 0xad, 0x7b, 0xde, 0xd0, 0x8b, 0xdc, 0x31, - 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x73, 0x6f, - 0x6d, 0x65, 0x00, 0xfd, 0x84, 0x30, 0xbc, 0x86, - 0x4c, 0xfc, 0xd5, 0xf1, 0x0e, 0x55, 0x90, 0xf8, - 0xa4, 0x47, 0xe0, 0x1b, 0x94, 0x2b, 0xfe, 0x31, - 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x74, 0x77, - 0x6f, 0x00, 0x78, 0x98, 0x19, 0x22, 0x61, 0x3b, - 0x2a, 0xfb, 0x60, 0x25, 0x04, 0x2f, 0xf6, 0xbd, - 0x87, 0x8a, 0xc1, 0x99, 0x4e, 0x85, 0x31, 0x30, - 0x30, 0x36, 0x34, 0x34, 0x20, 0x7a, 0x65, 0x72, - 0x6f, 0x00, 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, - 0xd6, 0x43, 0x4b, 0x8b, 0x29, 0xae, 0x77, 0x5a, - 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91, -}; - -static unsigned char tag_data[] = { - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x33, - 0x64, 0x37, 0x66, 0x38, 0x61, 0x36, 0x61, 0x66, - 0x30, 0x37, 0x36, 0x63, 0x38, 0x63, 0x33, 0x66, - 0x32, 0x30, 0x30, 0x37, 0x31, 0x61, 0x38, 0x39, - 0x33, 0x35, 0x63, 0x64, 0x62, 0x65, 0x38, 0x32, - 0x32, 0x38, 0x35, 0x39, 0x34, 0x64, 0x31, 0x0a, - 0x74, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x0a, 0x74, 0x61, 0x67, 0x20, - 0x76, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0a, 0x74, - 0x61, 0x67, 0x67, 0x65, 0x72, 0x20, 0x43, 0x20, - 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, - 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, - 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, - 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, - 0x30, 0x0a, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20, - 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, - 0x61, 0x67, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, - 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x76, 0x30, - 0x2e, 0x30, 0x2e, 0x31, 0x0a, -}; - -/* - * Dummy data - */ -static unsigned char zero_data[] = { - 0x00, -}; - -static unsigned char one_data[] = { - 0x0a, -}; - -static unsigned char two_data[] = { - 0x61, 0x0a, -}; - -static unsigned char some_data[] = { - 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68, - 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, - 0x69, 0x73, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, - 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x3b, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, - 0x6e, 0x20, 0x72, 0x65, 0x64, 0x69, 0x73, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x69, - 0x74, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, - 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x0a, - 0x20, 0x2a, 0x20, 0x69, 0x74, 0x20, 0x75, 0x6e, - 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2c, - 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x20, 0x32, 0x2c, 0x0a, 0x20, 0x2a, 0x20, 0x61, - 0x73, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, - 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x2a, 0x0a, - 0x20, 0x2a, 0x20, 0x49, 0x6e, 0x20, 0x61, 0x64, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, - 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x47, 0x4e, 0x55, 0x20, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, 0x63, 0x65, - 0x6e, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x2a, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x73, 0x20, 0x67, 0x69, 0x76, 0x65, - 0x20, 0x79, 0x6f, 0x75, 0x20, 0x75, 0x6e, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x20, 0x70, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x69, 0x6e, - 0x6b, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x0a, 0x20, - 0x2a, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, - 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, - 0x6e, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6d, 0x62, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x74, - 0x68, 0x65, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x73, 0x2c, 0x0a, 0x20, 0x2a, - 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20, - 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65, - 0x20, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x69, - 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x6e, - 0x79, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x2a, - 0x20, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x20, - 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, - 0x65, 0x2e, 0x20, 0x20, 0x28, 0x54, 0x68, 0x65, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, - 0x20, 0x2a, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, - 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, - 0x64, 0x6f, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, - 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x74, 0x68, 0x65, - 0x72, 0x20, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, - 0x74, 0x73, 0x3b, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c, - 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x0a, 0x20, 0x2a, 0x20, 0x6d, - 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2c, - 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x73, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x6e, - 0x6f, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x65, - 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x0a, 0x20, - 0x2a, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x62, - 0x69, 0x6e, 0x65, 0x64, 0x20, 0x65, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, - 0x29, 0x0a, 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, - 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, - 0x65, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, - 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x68, 0x6f, 0x70, 0x65, 0x20, 0x74, 0x68, 0x61, - 0x74, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c, - 0x6c, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, - 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x62, 0x75, 0x74, - 0x0a, 0x20, 0x2a, 0x20, 0x57, 0x49, 0x54, 0x48, - 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20, - 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, - 0x3b, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, - 0x74, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x69, - 0x65, 0x64, 0x20, 0x77, 0x61, 0x72, 0x72, 0x61, - 0x6e, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x0a, 0x20, - 0x2a, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, - 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, - 0x59, 0x20, 0x6f, 0x72, 0x20, 0x46, 0x49, 0x54, - 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, - 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 0x49, - 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, - 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2e, 0x20, 0x20, - 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x47, 0x4e, 0x55, 0x0a, 0x20, 0x2a, 0x20, 0x47, - 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, - 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x66, 0x6f, - 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x0a, - 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x59, 0x6f, - 0x75, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, - 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x20, 0x61, - 0x20, 0x63, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x66, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, - 0x20, 0x2a, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, - 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, - 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, - 0x61, 0x6d, 0x3b, 0x20, 0x73, 0x65, 0x65, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, - 0x20, 0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47, - 0x2e, 0x20, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, - 0x74, 0x2c, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x2a, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, - 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x35, 0x31, 0x20, - 0x46, 0x72, 0x61, 0x6e, 0x6b, 0x6c, 0x69, 0x6e, - 0x20, 0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x2c, - 0x20, 0x46, 0x69, 0x66, 0x74, 0x68, 0x20, 0x46, - 0x6c, 0x6f, 0x6f, 0x72, 0x2c, 0x0a, 0x20, 0x2a, - 0x20, 0x42, 0x6f, 0x73, 0x74, 0x6f, 0x6e, 0x2c, - 0x20, 0x4d, 0x41, 0x20, 0x30, 0x32, 0x31, 0x31, - 0x30, 0x2d, 0x31, 0x33, 0x30, 0x31, 0x2c, 0x20, - 0x55, 0x53, 0x41, 0x2e, 0x0a, 0x20, 0x2a, 0x2f, - 0x0a, -}; - -/* - * SHA1 Hashes - */ -static char *commit_id = "3d7f8a6af076c8c3f20071a8935cdbe8228594d1"; -static char *tree_id = "dff2da90b254e1beb889d1f1f1288be1803782df"; -static char *tag_id = "09d373e1dfdc16b129ceec6dd649739911541e05"; -static char *zero_id = "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"; -static char *one_id = "8b137891791fe96927ad78e64b0aad7bded08bdc"; -static char *two_id = "78981922613b2afb6025042ff6bd878ac1994e85"; -static char *some_id = "fd8430bc864cfcd5f10e5590f8a447e01b942bfe"; - -/* - * In-memory objects - */ -static git_rawobj tree_obj = { - tree_data, - sizeof(tree_data), - GIT_OBJECT_TREE -}; - -static git_rawobj tag_obj = { - tag_data, - sizeof(tag_data), - GIT_OBJECT_TAG -}; - -static git_rawobj zero_obj = { - zero_data, - 0, - GIT_OBJECT_BLOB -}; - -static git_rawobj one_obj = { - one_data, - sizeof(one_data), - GIT_OBJECT_BLOB -}; - -static git_rawobj two_obj = { - two_data, - sizeof(two_data), - GIT_OBJECT_BLOB -}; - -static git_rawobj commit_obj = { - commit_data, - sizeof(commit_data), - GIT_OBJECT_COMMIT -}; - -static git_rawobj some_obj = { - some_data, - sizeof(some_data), - GIT_OBJECT_BLOB -}; - -static git_rawobj junk_obj = { - NULL, - 0, - GIT_OBJECT_INVALID -}; diff --git a/tests/object/raw/fromstr.c b/tests/object/raw/fromstr.c deleted file mode 100644 index 8c11c105f..000000000 --- a/tests/object/raw/fromstr.c +++ /dev/null @@ -1,30 +0,0 @@ - -#include "clar_libgit2.h" - -#include "odb.h" - -void test_object_raw_fromstr__fail_on_invalid_oid_string(void) -{ - git_oid out; - cl_git_fail(git_oid_fromstr(&out, "")); - cl_git_fail(git_oid_fromstr(&out, "moo")); - cl_git_fail(git_oid_fromstr(&out, "16a67770b7d8d72317c4b775213c23a8bd74f5ez")); -} - -void test_object_raw_fromstr__succeed_on_valid_oid_string(void) -{ - git_oid out; - unsigned char exp[] = { - 0x16, 0xa6, 0x77, 0x70, 0xb7, - 0xd8, 0xd7, 0x23, 0x17, 0xc4, - 0xb7, 0x75, 0x21, 0x3c, 0x23, - 0xa8, 0xbd, 0x74, 0xf5, 0xe0, - }; - - cl_git_pass(git_oid_fromstr(&out, "16a67770b7d8d72317c4b775213c23a8bd74f5e0")); - cl_git_pass(memcmp(out.id, exp, sizeof(out.id))); - - cl_git_pass(git_oid_fromstr(&out, "16A67770B7D8D72317C4b775213C23A8BD74F5E0")); - cl_git_pass(memcmp(out.id, exp, sizeof(out.id))); - -} diff --git a/tests/object/raw/hash.c b/tests/object/raw/hash.c deleted file mode 100644 index 5a3e81855..000000000 --- a/tests/object/raw/hash.c +++ /dev/null @@ -1,169 +0,0 @@ - -#include "clar_libgit2.h" - -#include "odb.h" -#include "hash.h" - -#include "data.h" - -static void hash_object_pass(git_oid *oid, git_rawobj *obj) -{ - cl_git_pass(git_odb_hash(oid, obj->data, obj->len, obj->type)); -} -static void hash_object_fail(git_oid *oid, git_rawobj *obj) -{ - cl_git_fail(git_odb_hash(oid, obj->data, obj->len, obj->type)); -} - -static char *hello_id = "22596363b3de40b06f981fb85d82312e8c0ed511"; -static char *hello_text = "hello world\n"; - -static char *bye_id = "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"; -static char *bye_text = "bye world\n"; - -void test_object_raw_hash__hash_by_blocks(void) -{ - git_hash_ctx ctx; - git_oid id1, id2; - - cl_git_pass(git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)); - - /* should already be init'd */ - cl_git_pass(git_hash_update(&ctx, hello_text, strlen(hello_text))); - cl_git_pass(git_hash_final(id2.id, &ctx)); - cl_git_pass(git_oid_fromstr(&id1, hello_id)); - cl_assert(git_oid_cmp(&id1, &id2) == 0); - - /* reinit should permit reuse */ - cl_git_pass(git_hash_init(&ctx)); - cl_git_pass(git_hash_update(&ctx, bye_text, strlen(bye_text))); - cl_git_pass(git_hash_final(id2.id, &ctx)); - cl_git_pass(git_oid_fromstr(&id1, bye_id)); - cl_assert(git_oid_cmp(&id1, &id2) == 0); - - git_hash_ctx_cleanup(&ctx); -} - -void test_object_raw_hash__hash_buffer_in_single_call(void) -{ - git_oid id1, id2; - - cl_git_pass(git_oid_fromstr(&id1, hello_id)); - git_hash_buf(id2.id, hello_text, strlen(hello_text), GIT_HASH_ALGORITHM_SHA1); - cl_assert(git_oid_cmp(&id1, &id2) == 0); -} - -void test_object_raw_hash__hash_vector(void) -{ - git_oid id1, id2; - git_str_vec vec[2]; - - cl_git_pass(git_oid_fromstr(&id1, hello_id)); - - vec[0].data = hello_text; - vec[0].len = 4; - vec[1].data = hello_text+4; - vec[1].len = strlen(hello_text)-4; - - git_hash_vec(id2.id, vec, 2, GIT_HASH_ALGORITHM_SHA1); - - cl_assert(git_oid_cmp(&id1, &id2) == 0); -} - -void test_object_raw_hash__hash_junk_data(void) -{ - git_oid id, id_zero; - - cl_git_pass(git_oid_fromstr(&id_zero, zero_id)); - - /* invalid types: */ - junk_obj.data = some_data; - hash_object_fail(&id, &junk_obj); - - junk_obj.type = 0; /* EXT1 */ - hash_object_fail(&id, &junk_obj); - - junk_obj.type = 5; /* EXT2 */ - hash_object_fail(&id, &junk_obj); - - junk_obj.type = GIT_OBJECT_OFS_DELTA; - hash_object_fail(&id, &junk_obj); - - junk_obj.type = GIT_OBJECT_REF_DELTA; - hash_object_fail(&id, &junk_obj); - - junk_obj.type = 42; - hash_object_fail(&id, &junk_obj); - - /* data can be NULL only if len is zero: */ - junk_obj.type = GIT_OBJECT_BLOB; - junk_obj.data = NULL; - hash_object_pass(&id, &junk_obj); - cl_assert(git_oid_cmp(&id, &id_zero) == 0); - - junk_obj.len = 1; - hash_object_fail(&id, &junk_obj); -} - -void test_object_raw_hash__hash_commit_object(void) -{ - git_oid id1, id2; - - cl_git_pass(git_oid_fromstr(&id1, commit_id)); - hash_object_pass(&id2, &commit_obj); - cl_assert(git_oid_cmp(&id1, &id2) == 0); -} - -void test_object_raw_hash__hash_tree_object(void) -{ - git_oid id1, id2; - - cl_git_pass(git_oid_fromstr(&id1, tree_id)); - hash_object_pass(&id2, &tree_obj); - cl_assert(git_oid_cmp(&id1, &id2) == 0); -} - -void test_object_raw_hash__hash_tag_object(void) -{ - git_oid id1, id2; - - cl_git_pass(git_oid_fromstr(&id1, tag_id)); - hash_object_pass(&id2, &tag_obj); - cl_assert(git_oid_cmp(&id1, &id2) == 0); -} - -void test_object_raw_hash__hash_zero_length_object(void) -{ - git_oid id1, id2; - - cl_git_pass(git_oid_fromstr(&id1, zero_id)); - hash_object_pass(&id2, &zero_obj); - cl_assert(git_oid_cmp(&id1, &id2) == 0); -} - -void test_object_raw_hash__hash_one_byte_object(void) -{ - git_oid id1, id2; - - cl_git_pass(git_oid_fromstr(&id1, one_id)); - hash_object_pass(&id2, &one_obj); - cl_assert(git_oid_cmp(&id1, &id2) == 0); -} - -void test_object_raw_hash__hash_two_byte_object(void) -{ - git_oid id1, id2; - - cl_git_pass(git_oid_fromstr(&id1, two_id)); - hash_object_pass(&id2, &two_obj); - cl_assert(git_oid_cmp(&id1, &id2) == 0); -} - -void test_object_raw_hash__hash_multi_byte_object(void) -{ - git_oid id1, id2; - - cl_git_pass(git_oid_fromstr(&id1, some_id)); - hash_object_pass(&id2, &some_obj); - cl_assert(git_oid_cmp(&id1, &id2) == 0); -} diff --git a/tests/object/raw/short.c b/tests/object/raw/short.c deleted file mode 100644 index e8d2cf5a5..000000000 --- a/tests/object/raw/short.c +++ /dev/null @@ -1,137 +0,0 @@ - -#include "clar_libgit2.h" - -#include "odb.h" -#include "hash.h" - -void test_object_raw_short__oid_shortener_no_duplicates(void) -{ - git_oid_shorten *os; - int min_len; - - os = git_oid_shorten_new(0); - cl_assert(os != NULL); - - git_oid_shorten_add(os, "22596363b3de40b06f981fb85d82312e8c0ed511"); - git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); - git_oid_shorten_add(os, "16a0123456789abcdef4b775213c23a8bd74f5e0"); - min_len = git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); - - cl_assert(min_len == GIT_OID_HEXSZ + 1); - - git_oid_shorten_free(os); -} - -static int insert_sequential_oids( - char ***out, git_oid_shorten *os, int n, int fail) -{ - int i, min_len = 0; - char numbuf[16]; - git_oid oid; - char **oids = git__calloc(n, sizeof(char *)); - cl_assert(oids != NULL); - - for (i = 0; i < n; ++i) { - p_snprintf(numbuf, sizeof(numbuf), "%u", (unsigned int)i); - git_hash_buf(oid.id, numbuf, strlen(numbuf), GIT_HASH_ALGORITHM_SHA1); - - oids[i] = git__malloc(GIT_OID_HEXSZ + 1); - cl_assert(oids[i]); - git_oid_nfmt(oids[i], GIT_OID_HEXSZ + 1, &oid); - - min_len = git_oid_shorten_add(os, oids[i]); - - /* After "fail", we expect git_oid_shorten_add to fail */ - if (fail >= 0 && i >= fail) - cl_assert(min_len < 0); - else - cl_assert(min_len >= 0); - } - - *out = oids; - - return min_len; -} - -static void free_oids(int n, char **oids) -{ - int i; - - for (i = 0; i < n; ++i) { - git__free(oids[i]); - } - git__free(oids); -} - -void test_object_raw_short__oid_shortener_stresstest_git_oid_shorten(void) -{ -#define MAX_OIDS 1000 - - git_oid_shorten *os; - size_t i, j; - int min_len = 0, found_collision; - char **oids; - - os = git_oid_shorten_new(0); - cl_assert(os != NULL); - - /* - * Insert in the shortener 1000 unique SHA1 ids - */ - min_len = insert_sequential_oids(&oids, os, MAX_OIDS, MAX_OIDS); - cl_assert(min_len > 0); - - /* - * Compare the first `min_char - 1` characters of each - * SHA1 OID. If the minimizer worked, we should find at - * least one collision - */ - found_collision = 0; - for (i = 0; i < MAX_OIDS; ++i) { - for (j = i + 1; j < MAX_OIDS; ++j) { - if (memcmp(oids[i], oids[j], min_len - 1) == 0) - found_collision = 1; - } - } - cl_assert_equal_b(true, found_collision); - - /* - * Compare the first `min_char` characters of each - * SHA1 OID. If the minimizer worked, every single preffix - * should be unique. - */ - found_collision = 0; - for (i = 0; i < MAX_OIDS; ++i) { - for (j = i + 1; j < MAX_OIDS; ++j) { - if (memcmp(oids[i], oids[j], min_len) == 0) - found_collision = 1; - } - } - cl_assert_equal_b(false, found_collision); - - /* cleanup */ - free_oids(MAX_OIDS, oids); - git_oid_shorten_free(os); - -#undef MAX_OIDS -} - -void test_object_raw_short__oid_shortener_too_much_oids(void) -{ - /* The magic number of oids at which an oid_shortener will fail. - * This was experimentally established. */ -#define MAX_OIDS 24556 - - git_oid_shorten *os; - char **oids; - - os = git_oid_shorten_new(0); - cl_assert(os != NULL); - - cl_assert(insert_sequential_oids(&oids, os, MAX_OIDS, MAX_OIDS - 1) < 0); - - free_oids(MAX_OIDS, oids); - git_oid_shorten_free(os); - -#undef MAX_OIDS -} diff --git a/tests/object/raw/size.c b/tests/object/raw/size.c deleted file mode 100644 index 930c6de23..000000000 --- a/tests/object/raw/size.c +++ /dev/null @@ -1,13 +0,0 @@ - -#include "clar_libgit2.h" - -#include "odb.h" - -void test_object_raw_size__validate_oid_size(void) -{ - git_oid out; - cl_assert(20 == GIT_OID_RAWSZ); - cl_assert(40 == GIT_OID_HEXSZ); - cl_assert(sizeof(out) == GIT_OID_RAWSZ); - cl_assert(sizeof(out.id) == GIT_OID_RAWSZ); -} diff --git a/tests/object/raw/type2string.c b/tests/object/raw/type2string.c deleted file mode 100644 index ebd81f552..000000000 --- a/tests/object/raw/type2string.c +++ /dev/null @@ -1,54 +0,0 @@ - -#include "clar_libgit2.h" - -#include "odb.h" -#include "hash.h" - -void test_object_raw_type2string__convert_type_to_string(void) -{ - cl_assert_equal_s(git_object_type2string(GIT_OBJECT_INVALID), ""); - cl_assert_equal_s(git_object_type2string(0), ""); /* EXT1 */ - cl_assert_equal_s(git_object_type2string(GIT_OBJECT_COMMIT), "commit"); - cl_assert_equal_s(git_object_type2string(GIT_OBJECT_TREE), "tree"); - cl_assert_equal_s(git_object_type2string(GIT_OBJECT_BLOB), "blob"); - cl_assert_equal_s(git_object_type2string(GIT_OBJECT_TAG), "tag"); - cl_assert_equal_s(git_object_type2string(5), ""); /* EXT2 */ - cl_assert_equal_s(git_object_type2string(GIT_OBJECT_OFS_DELTA), "OFS_DELTA"); - cl_assert_equal_s(git_object_type2string(GIT_OBJECT_REF_DELTA), "REF_DELTA"); - - cl_assert_equal_s(git_object_type2string(-2), ""); - cl_assert_equal_s(git_object_type2string(8), ""); - cl_assert_equal_s(git_object_type2string(1234), ""); -} - -void test_object_raw_type2string__convert_string_to_type(void) -{ - cl_assert(git_object_string2type(NULL) == GIT_OBJECT_INVALID); - cl_assert(git_object_string2type("") == GIT_OBJECT_INVALID); - cl_assert(git_object_string2type("commit") == GIT_OBJECT_COMMIT); - cl_assert(git_object_string2type("tree") == GIT_OBJECT_TREE); - cl_assert(git_object_string2type("blob") == GIT_OBJECT_BLOB); - cl_assert(git_object_string2type("tag") == GIT_OBJECT_TAG); - cl_assert(git_object_string2type("OFS_DELTA") == GIT_OBJECT_OFS_DELTA); - cl_assert(git_object_string2type("REF_DELTA") == GIT_OBJECT_REF_DELTA); - - cl_assert(git_object_string2type("CoMmIt") == GIT_OBJECT_INVALID); - cl_assert(git_object_string2type("hohoho") == GIT_OBJECT_INVALID); -} - -void test_object_raw_type2string__check_type_is_loose(void) -{ - cl_assert(git_object_typeisloose(GIT_OBJECT_INVALID) == 0); - cl_assert(git_object_typeisloose(0) == 0); /* EXT1 */ - cl_assert(git_object_typeisloose(GIT_OBJECT_COMMIT) == 1); - cl_assert(git_object_typeisloose(GIT_OBJECT_TREE) == 1); - cl_assert(git_object_typeisloose(GIT_OBJECT_BLOB) == 1); - cl_assert(git_object_typeisloose(GIT_OBJECT_TAG) == 1); - cl_assert(git_object_typeisloose(5) == 0); /* EXT2 */ - cl_assert(git_object_typeisloose(GIT_OBJECT_OFS_DELTA) == 0); - cl_assert(git_object_typeisloose(GIT_OBJECT_REF_DELTA) == 0); - - cl_assert(git_object_typeisloose(-2) == 0); - cl_assert(git_object_typeisloose(8) == 0); - cl_assert(git_object_typeisloose(1234) == 0); -} diff --git a/tests/object/raw/write.c b/tests/object/raw/write.c deleted file mode 100644 index 40e05f357..000000000 --- a/tests/object/raw/write.c +++ /dev/null @@ -1,462 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/odb_backend.h" - -#include "futils.h" -#include "odb.h" - -typedef struct object_data { - char *id; /* object id (sha1) */ - char *dir; /* object store (fan-out) directory name */ - char *file; /* object store filename */ -} object_data; - -static const char *odb_dir = "test-objects"; - -void test_body(object_data *d, git_rawobj *o); - - - -/* Helpers */ -static void remove_object_files(object_data *d) -{ - cl_git_pass(p_unlink(d->file)); - cl_git_pass(p_rmdir(d->dir)); - cl_assert(errno != ENOTEMPTY); - cl_git_pass(p_rmdir(odb_dir) < 0); -} - -static void streaming_write(git_oid *oid, git_odb *odb, git_rawobj *raw) -{ - git_odb_stream *stream; - int error; - - cl_git_pass(git_odb_open_wstream(&stream, odb, raw->len, raw->type)); - git_odb_stream_write(stream, raw->data, raw->len); - error = git_odb_stream_finalize_write(oid, stream); - git_odb_stream_free(stream); - cl_git_pass(error); -} - -static void check_object_files(object_data *d) -{ - cl_assert(git_fs_path_exists(d->dir)); - cl_assert(git_fs_path_exists(d->file)); -} - -static void cmp_objects(git_rawobj *o1, git_rawobj *o2) -{ - cl_assert(o1->type == o2->type); - cl_assert(o1->len == o2->len); - if (o1->len > 0) - cl_assert(memcmp(o1->data, o2->data, o1->len) == 0); -} - -static void make_odb_dir(void) -{ - cl_git_pass(p_mkdir(odb_dir, GIT_OBJECT_DIR_MODE)); -} - - -/* Standard test form */ -void test_body(object_data *d, git_rawobj *o) -{ - git_odb *db; - git_oid id1, id2; - git_odb_object *obj; - git_rawobj tmp; - - make_odb_dir(); - cl_git_pass(git_odb_open(&db, odb_dir)); - cl_git_pass(git_oid_fromstr(&id1, d->id)); - - streaming_write(&id2, db, o); - cl_assert(git_oid_cmp(&id1, &id2) == 0); - check_object_files(d); - - cl_git_pass(git_odb_read(&obj, db, &id1)); - - tmp.data = obj->buffer; - tmp.len = obj->cached.size; - tmp.type = obj->cached.type; - - cmp_objects(&tmp, o); - - git_odb_object_free(obj); - git_odb_free(db); - remove_object_files(d); -} - - -void test_object_raw_write__loose_object(void) -{ - object_data commit = { - "3d7f8a6af076c8c3f20071a8935cdbe8228594d1", - "test-objects/3d", - "test-objects/3d/7f8a6af076c8c3f20071a8935cdbe8228594d1", - }; - - unsigned char commit_data[] = { - 0x74, 0x72, 0x65, 0x65, 0x20, 0x64, 0x66, 0x66, - 0x32, 0x64, 0x61, 0x39, 0x30, 0x62, 0x32, 0x35, - 0x34, 0x65, 0x31, 0x62, 0x65, 0x62, 0x38, 0x38, - 0x39, 0x64, 0x31, 0x66, 0x31, 0x66, 0x31, 0x32, - 0x38, 0x38, 0x62, 0x65, 0x31, 0x38, 0x30, 0x33, - 0x37, 0x38, 0x32, 0x64, 0x66, 0x0a, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x20, 0x41, 0x20, 0x55, - 0x20, 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, - 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, - 0x31, 0x34, 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, - 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x74, 0x65, 0x72, 0x20, 0x43, 0x20, - 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, - 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, - 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, - 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, - 0x30, 0x0a, 0x0a, 0x41, 0x20, 0x6f, 0x6e, 0x65, - 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x73, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x0a, 0x0a, 0x54, 0x68, - 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x6f, - 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, - 0x20, 0x66, 0x75, 0x72, 0x74, 0x68, 0x65, 0x72, - 0x20, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x70, 0x75, 0x72, 0x70, - 0x6f, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, - 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x62, 0x79, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x53, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x2d, 0x6f, 0x66, 0x2d, - 0x62, 0x79, 0x3a, 0x20, 0x41, 0x20, 0x55, 0x20, - 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, 0x61, - 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, - 0x3e, 0x0a, - }; - - git_rawobj commit_obj = { - commit_data, - sizeof(commit_data), - GIT_OBJECT_COMMIT - }; - - test_body(&commit, &commit_obj); -} - -void test_object_raw_write__loose_tree(void) -{ - static object_data tree = { - "dff2da90b254e1beb889d1f1f1288be1803782df", - "test-objects/df", - "test-objects/df/f2da90b254e1beb889d1f1f1288be1803782df", - }; - - static unsigned char tree_data[] = { - 0x31, 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x6f, - 0x6e, 0x65, 0x00, 0x8b, 0x13, 0x78, 0x91, 0x79, - 0x1f, 0xe9, 0x69, 0x27, 0xad, 0x78, 0xe6, 0x4b, - 0x0a, 0xad, 0x7b, 0xde, 0xd0, 0x8b, 0xdc, 0x31, - 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x73, 0x6f, - 0x6d, 0x65, 0x00, 0xfd, 0x84, 0x30, 0xbc, 0x86, - 0x4c, 0xfc, 0xd5, 0xf1, 0x0e, 0x55, 0x90, 0xf8, - 0xa4, 0x47, 0xe0, 0x1b, 0x94, 0x2b, 0xfe, 0x31, - 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x74, 0x77, - 0x6f, 0x00, 0x78, 0x98, 0x19, 0x22, 0x61, 0x3b, - 0x2a, 0xfb, 0x60, 0x25, 0x04, 0x2f, 0xf6, 0xbd, - 0x87, 0x8a, 0xc1, 0x99, 0x4e, 0x85, 0x31, 0x30, - 0x30, 0x36, 0x34, 0x34, 0x20, 0x7a, 0x65, 0x72, - 0x6f, 0x00, 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, - 0xd6, 0x43, 0x4b, 0x8b, 0x29, 0xae, 0x77, 0x5a, - 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91, - }; - - static git_rawobj tree_obj = { - tree_data, - sizeof(tree_data), - GIT_OBJECT_TREE - }; - - test_body(&tree, &tree_obj); -} - -void test_object_raw_write__loose_tag(void) -{ - static object_data tag = { - "09d373e1dfdc16b129ceec6dd649739911541e05", - "test-objects/09", - "test-objects/09/d373e1dfdc16b129ceec6dd649739911541e05", - }; - - static unsigned char tag_data[] = { - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x33, - 0x64, 0x37, 0x66, 0x38, 0x61, 0x36, 0x61, 0x66, - 0x30, 0x37, 0x36, 0x63, 0x38, 0x63, 0x33, 0x66, - 0x32, 0x30, 0x30, 0x37, 0x31, 0x61, 0x38, 0x39, - 0x33, 0x35, 0x63, 0x64, 0x62, 0x65, 0x38, 0x32, - 0x32, 0x38, 0x35, 0x39, 0x34, 0x64, 0x31, 0x0a, - 0x74, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x0a, 0x74, 0x61, 0x67, 0x20, - 0x76, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0a, 0x74, - 0x61, 0x67, 0x67, 0x65, 0x72, 0x20, 0x43, 0x20, - 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, - 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, - 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, - 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, - 0x30, 0x0a, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20, - 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, - 0x61, 0x67, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, - 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x76, 0x30, - 0x2e, 0x30, 0x2e, 0x31, 0x0a, - }; - - static git_rawobj tag_obj = { - tag_data, - sizeof(tag_data), - GIT_OBJECT_TAG - }; - - - test_body(&tag, &tag_obj); -} - -void test_object_raw_write__zero_length(void) -{ - static object_data zero = { - "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", - "test-objects/e6", - "test-objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391", - }; - - static unsigned char zero_data[] = { - 0x00 /* dummy data */ - }; - - static git_rawobj zero_obj = { - zero_data, - 0, - GIT_OBJECT_BLOB - }; - - test_body(&zero, &zero_obj); -} - -void test_object_raw_write__one_byte(void) -{ - static object_data one = { - "8b137891791fe96927ad78e64b0aad7bded08bdc", - "test-objects/8b", - "test-objects/8b/137891791fe96927ad78e64b0aad7bded08bdc", - }; - - static unsigned char one_data[] = { - 0x0a, - }; - - static git_rawobj one_obj = { - one_data, - sizeof(one_data), - GIT_OBJECT_BLOB - }; - - test_body(&one, &one_obj); -} - -void test_object_raw_write__two_byte(void) -{ - static object_data two = { - "78981922613b2afb6025042ff6bd878ac1994e85", - "test-objects/78", - "test-objects/78/981922613b2afb6025042ff6bd878ac1994e85", - }; - - static unsigned char two_data[] = { - 0x61, 0x0a, - }; - - static git_rawobj two_obj = { - two_data, - sizeof(two_data), - GIT_OBJECT_BLOB - }; - - test_body(&two, &two_obj); -} - -void test_object_raw_write__several_bytes(void) -{ - static object_data some = { - "fd8430bc864cfcd5f10e5590f8a447e01b942bfe", - "test-objects/fd", - "test-objects/fd/8430bc864cfcd5f10e5590f8a447e01b942bfe", - }; - - static unsigned char some_data[] = { - 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68, - 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, - 0x69, 0x73, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, - 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x3b, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, - 0x6e, 0x20, 0x72, 0x65, 0x64, 0x69, 0x73, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x69, - 0x74, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, - 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x0a, - 0x20, 0x2a, 0x20, 0x69, 0x74, 0x20, 0x75, 0x6e, - 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2c, - 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x20, 0x32, 0x2c, 0x0a, 0x20, 0x2a, 0x20, 0x61, - 0x73, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, - 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x2a, 0x0a, - 0x20, 0x2a, 0x20, 0x49, 0x6e, 0x20, 0x61, 0x64, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, - 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x47, 0x4e, 0x55, 0x20, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, 0x63, 0x65, - 0x6e, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x2a, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x73, 0x20, 0x67, 0x69, 0x76, 0x65, - 0x20, 0x79, 0x6f, 0x75, 0x20, 0x75, 0x6e, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x20, 0x70, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x69, 0x6e, - 0x6b, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x0a, 0x20, - 0x2a, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, - 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, - 0x6e, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6d, 0x62, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x74, - 0x68, 0x65, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x73, 0x2c, 0x0a, 0x20, 0x2a, - 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20, - 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65, - 0x20, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x69, - 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x6e, - 0x79, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x2a, - 0x20, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x20, - 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, - 0x65, 0x2e, 0x20, 0x20, 0x28, 0x54, 0x68, 0x65, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, - 0x20, 0x2a, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, - 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, - 0x64, 0x6f, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, - 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x74, 0x68, 0x65, - 0x72, 0x20, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, - 0x74, 0x73, 0x3b, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c, - 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x0a, 0x20, 0x2a, 0x20, 0x6d, - 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2c, - 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x73, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x6e, - 0x6f, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x65, - 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x0a, 0x20, - 0x2a, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x62, - 0x69, 0x6e, 0x65, 0x64, 0x20, 0x65, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, - 0x29, 0x0a, 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, - 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, - 0x65, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, - 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x68, 0x6f, 0x70, 0x65, 0x20, 0x74, 0x68, 0x61, - 0x74, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c, - 0x6c, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, - 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x62, 0x75, 0x74, - 0x0a, 0x20, 0x2a, 0x20, 0x57, 0x49, 0x54, 0x48, - 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20, - 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, - 0x3b, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, - 0x74, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x69, - 0x65, 0x64, 0x20, 0x77, 0x61, 0x72, 0x72, 0x61, - 0x6e, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x0a, 0x20, - 0x2a, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, - 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, - 0x59, 0x20, 0x6f, 0x72, 0x20, 0x46, 0x49, 0x54, - 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, - 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 0x49, - 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, - 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2e, 0x20, 0x20, - 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x47, 0x4e, 0x55, 0x0a, 0x20, 0x2a, 0x20, 0x47, - 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, - 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x66, 0x6f, - 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x0a, - 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x59, 0x6f, - 0x75, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, - 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x20, 0x61, - 0x20, 0x63, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x66, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, - 0x20, 0x2a, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, - 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, - 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, - 0x61, 0x6d, 0x3b, 0x20, 0x73, 0x65, 0x65, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, - 0x20, 0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47, - 0x2e, 0x20, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, - 0x74, 0x2c, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x2a, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, - 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x35, 0x31, 0x20, - 0x46, 0x72, 0x61, 0x6e, 0x6b, 0x6c, 0x69, 0x6e, - 0x20, 0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x2c, - 0x20, 0x46, 0x69, 0x66, 0x74, 0x68, 0x20, 0x46, - 0x6c, 0x6f, 0x6f, 0x72, 0x2c, 0x0a, 0x20, 0x2a, - 0x20, 0x42, 0x6f, 0x73, 0x74, 0x6f, 0x6e, 0x2c, - 0x20, 0x4d, 0x41, 0x20, 0x30, 0x32, 0x31, 0x31, - 0x30, 0x2d, 0x31, 0x33, 0x30, 0x31, 0x2c, 0x20, - 0x55, 0x53, 0x41, 0x2e, 0x0a, 0x20, 0x2a, 0x2f, - 0x0a, - }; - - static git_rawobj some_obj = { - some_data, - sizeof(some_data), - GIT_OBJECT_BLOB - }; - - test_body(&some, &some_obj); -} diff --git a/tests/object/shortid.c b/tests/object/shortid.c deleted file mode 100644 index 9b673efeb..000000000 --- a/tests/object/shortid.c +++ /dev/null @@ -1,51 +0,0 @@ -#include "clar_libgit2.h" - -git_repository *_repo; - -void test_object_shortid__initialize(void) -{ - cl_git_pass(git_repository_open(&_repo, cl_fixture("duplicate.git"))); -} - -void test_object_shortid__cleanup(void) -{ - git_repository_free(_repo); - _repo = NULL; -} - -void test_object_shortid__select(void) -{ - git_oid full; - git_object *obj; - git_buf shorty = {0}; - - git_oid_fromstr(&full, "ce013625030ba8dba906f756967f9e9ca394464a"); - cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJECT_ANY)); - cl_git_pass(git_object_short_id(&shorty, obj)); - cl_assert_equal_i(7, shorty.size); - cl_assert_equal_s("ce01362", shorty.ptr); - git_object_free(obj); - - git_oid_fromstr(&full, "038d718da6a1ebbc6a7780a96ed75a70cc2ad6e2"); - cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJECT_ANY)); - cl_git_pass(git_object_short_id(&shorty, obj)); - cl_assert_equal_i(7, shorty.size); - cl_assert_equal_s("038d718", shorty.ptr); - git_object_free(obj); - - git_oid_fromstr(&full, "dea509d097ce692e167dfc6a48a7a280cc5e877e"); - cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJECT_ANY)); - cl_git_pass(git_object_short_id(&shorty, obj)); - cl_assert_equal_i(9, shorty.size); - cl_assert_equal_s("dea509d09", shorty.ptr); - git_object_free(obj); - - git_oid_fromstr(&full, "dea509d0b3cb8ee0650f6ca210bc83f4678851ba"); - cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJECT_ANY)); - cl_git_pass(git_object_short_id(&shorty, obj)); - cl_assert_equal_i(9, shorty.size); - cl_assert_equal_s("dea509d0b", shorty.ptr); - git_object_free(obj); - - git_buf_dispose(&shorty); -} diff --git a/tests/object/tag/list.c b/tests/object/tag/list.c deleted file mode 100644 index d15f09205..000000000 --- a/tests/object/tag/list.c +++ /dev/null @@ -1,117 +0,0 @@ -#include "clar_libgit2.h" - -#include "tag.h" - -static git_repository *g_repo; - -#define MAX_USED_TAGS 6 - -struct pattern_match_t -{ - const char* pattern; - const size_t expected_matches; - const char* expected_results[MAX_USED_TAGS]; -}; - -/* Helpers */ -static void ensure_tag_pattern_match(git_repository *repo, - const struct pattern_match_t* data) -{ - int already_found[MAX_USED_TAGS] = { 0 }; - git_strarray tag_list; - int error = 0; - size_t successfully_found = 0; - size_t i, j; - - cl_assert(data->expected_matches <= MAX_USED_TAGS); - - if ((error = git_tag_list_match(&tag_list, data->pattern, repo)) < 0) - goto exit; - - if (tag_list.count != data->expected_matches) - { - error = GIT_ERROR; - goto exit; - } - - /* we have to be prepared that tags come in any order. */ - for (i = 0; i < tag_list.count; i++) - { - for (j = 0; j < data->expected_matches; j++) - { - if (!already_found[j] && !strcmp(data->expected_results[j], tag_list.strings[i])) - { - already_found[j] = 1; - successfully_found++; - break; - } - } - } - cl_assert_equal_i((int)successfully_found, (int)data->expected_matches); - -exit: - git_strarray_dispose(&tag_list); - cl_git_pass(error); -} - -/* Fixture setup and teardown */ -void test_object_tag_list__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_object_tag_list__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_object_tag_list__list_all(void) -{ - /* list all tag names from the repository */ - git_strarray tag_list; - - cl_git_pass(git_tag_list(&tag_list, g_repo)); - - cl_assert_equal_i((int)tag_list.count, 6); - - git_strarray_dispose(&tag_list); -} - -static const struct pattern_match_t matches[] = { - /* All tags, including a packed one and two namespaced ones. */ - { "", 6, { "e90810b", "point_to_blob", "test", "packed-tag", "foo/bar", "foo/foo/bar" } }, - - /* beginning with */ - { "t*", 1, { "test" } }, - - /* ending with */ - { "*b", 2, { "e90810b", "point_to_blob" } }, - - /* exact match */ - { "e", 0 }, - { "e90810b", 1, { "e90810b" } }, - - /* either or */ - { "e90810[ab]", 1, { "e90810b" } }, - - /* glob in the middle */ - { "foo/*/bar", 1, { "foo/foo/bar" } }, - - /* - * The matching of '*' is based on plain string matching analog to the regular expression ".*" - * => a '/' in the tag name has no special meaning. - * Compare to `git tag -l "*bar"` - */ - { "*bar", 2, { "foo/bar", "foo/foo/bar" } }, - - /* End of list */ - { NULL } -}; - -void test_object_tag_list__list_by_pattern(void) -{ - /* list all tag names from the repository matching a specified pattern */ - size_t i = 0; - while (matches[i].pattern) - ensure_tag_pattern_match(g_repo, &matches[i++]); -} diff --git a/tests/object/tag/parse.c b/tests/object/tag/parse.c deleted file mode 100644 index 2c0635ae4..000000000 --- a/tests/object/tag/parse.c +++ /dev/null @@ -1,218 +0,0 @@ -#include "clar_libgit2.h" -#include "object.h" -#include "signature.h" -#include "tag.h" - -static void assert_tag_parses(const char *data, size_t datalen, - const char *expected_oid, - const char *expected_name, - const char *expected_tagger, - const char *expected_message) -{ - git_tag *tag; - - if (!datalen) - datalen = strlen(data); - - cl_git_pass(git_object__from_raw((git_object **) &tag, data, datalen, GIT_OBJECT_TAG)); - cl_assert_equal_i(tag->type, GIT_OBJECT_TAG); - - if (expected_oid) { - git_oid oid; - cl_git_pass(git_oid_fromstr(&oid, expected_oid)); - cl_assert_equal_oid(&oid, &tag->target); - } - - if (expected_name) - cl_assert_equal_s(expected_name, tag->tag_name); - else - cl_assert_equal_s(tag->message, NULL); - - if (expected_tagger) { - git_signature *tagger; - cl_git_pass(git_signature_from_buffer(&tagger, expected_tagger)); - cl_assert_equal_s(tagger->name, tag->tagger->name); - cl_assert_equal_s(tagger->email, tag->tagger->email); - cl_assert_equal_i(tagger->when.time, tag->tagger->when.time); - cl_assert_equal_i(tagger->when.offset, tag->tagger->when.offset); - cl_assert_equal_i(tagger->when.sign, tag->tagger->when.sign); - git_signature_free(tagger); - } else { - cl_assert_equal_s(tag->tagger, NULL); - } - - if (expected_message) - cl_assert_equal_s(expected_message, tag->message); - else - cl_assert_equal_s(tag->message, NULL); - - git_object__free(&tag->object); -} - -static void assert_tag_fails(const char *data, size_t datalen) -{ - git_object *object; - if (!datalen) - datalen = strlen(data); - cl_git_fail(git_object__from_raw(&object, data, datalen, GIT_OBJECT_TAG)); -} - -void test_object_tag_parse__valid_tag_parses(void) -{ - const char *tag = - "object a8d447f68076d1520f69649bb52629941be7031f\n" - "type tag\n" - "tag tagname\n" - "tagger Taggy Mr. Taggart \n" - "\n" - "Message"; - assert_tag_parses(tag, 0, - "a8d447f68076d1520f69649bb52629941be7031f", - "tagname", - "Taggy Mr. Taggart ", - "Message"); -} - -void test_object_tag_parse__missing_tagger_parses(void) -{ - const char *tag = - "object a8d447f68076d1520f69649bb52629941be7031f\n" - "type tag\n" - "tag tagname\n" - "\n" - "Message"; - assert_tag_parses(tag, 0, - "a8d447f68076d1520f69649bb52629941be7031f", - "tagname", - NULL, - "Message"); -} - -void test_object_tag_parse__missing_message_parses(void) -{ - const char *tag = - "object a8d447f68076d1520f69649bb52629941be7031f\n" - "type tag\n" - "tag tagname\n" - "tagger Taggy Mr. Taggart \n"; - assert_tag_parses(tag, 0, - "a8d447f68076d1520f69649bb52629941be7031f", - "tagname", - "Taggy Mr. Taggart ", - NULL); -} - -void test_object_tag_parse__unknown_field_parses(void) -{ - const char *tag = - "object a8d447f68076d1520f69649bb52629941be7031f\n" - "type tag\n" - "tag tagname\n" - "tagger Taggy Mr. Taggart \n" - "foo bar\n" - "frubble frabble\n" - "\n" - "Message"; - assert_tag_parses(tag, 0, - "a8d447f68076d1520f69649bb52629941be7031f", - "tagname", - "Taggy Mr. Taggart ", - "Message"); -} - -void test_object_tag_parse__missing_object_fails(void) -{ - const char *tag = - "type tag\n" - "tag tagname\n" - "tagger Taggy Mr. Taggart \n" - "\n" - "Message"; - assert_tag_fails(tag, 0); -} - -void test_object_tag_parse__malformatted_object_fails(void) -{ - const char *tag = - "object a8d447f68076d15xxxxxxxxxxxxxxxx41be7031f\n" - "type tag\n" - "tag tagname\n" - "tagger Taggy Mr. Taggart \n" - "\n" - "Message"; - assert_tag_fails(tag, 0); -} - -void test_object_tag_parse__missing_type_fails(void) -{ - const char *tag = - "object a8d447f68076d1520f69649bb52629941be7031f\n" - "tag tagname\n" - "tagger Taggy Mr. Taggart \n" - "\n" - "Message"; - assert_tag_fails(tag, 0); -} - -void test_object_tag_parse__invalid_type_fails(void) -{ - const char *tag = - "object a8d447f68076d1520f69649bb52629941be7031f\n" - "type garbage\n" - "tag tagname\n" - "tagger Taggy Mr. Taggart \n" - "\n" - "Message"; - assert_tag_fails(tag, 0); -} - -void test_object_tag_parse__missing_tagname_fails(void) -{ - const char *tag = - "object a8d447f68076d1520f69649bb52629941be7031f\n" - "type tag\n" - "tagger Taggy Mr. Taggart \n" - "\n" - "Message"; - assert_tag_fails(tag, 0); -} - -void test_object_tag_parse__misformatted_tagger_fails(void) -{ - const char *tag = - "object a8d447f68076d1520f69649bb52629941be7031f\n" - "type tag\n" - "tag Tag\n" - "tagger taggy@taggart.com>\n" - "\n" - "Message"; - assert_tag_fails(tag, 0); -} - -void test_object_tag_parse__missing_message_fails(void) -{ - const char *tag = - "object a8d447f68076d1520f69649bb52629941be7031f\n" - "type tag\n" - "tag Tag\n" - "tagger taggy@taggart.com>\n"; - assert_tag_fails(tag, 0); -} - -void test_object_tag_parse__no_oob_read_when_searching_message(void) -{ - const char *tag = - "object a8d447f68076d1520f69649bb52629941be7031f\n" - "type tag\n" - "tag \n" - "tagger <>\n" - " \n\n" - "Message"; - /* - * The OOB read previously resulted in an OOM error. We - * thus want to make sure that the resulting error is the - * expected one. - */ - assert_tag_fails(tag, strlen(tag) - strlen("\n\nMessage")); - cl_assert(strstr(git_error_last()->message, "tag contains no message")); -} diff --git a/tests/object/tag/peel.c b/tests/object/tag/peel.c deleted file mode 100644 index 7456a8e17..000000000 --- a/tests/object/tag/peel.c +++ /dev/null @@ -1,61 +0,0 @@ -#include "clar_libgit2.h" -#include "tag.h" - -static git_repository *repo; -static git_tag *tag; -static git_object *target; - -void test_object_tag_peel__initialize(void) -{ - cl_fixture_sandbox("testrepo.git"); - cl_git_pass(git_repository_open(&repo, "testrepo.git")); -} - -void test_object_tag_peel__cleanup(void) -{ - git_tag_free(tag); - tag = NULL; - - git_object_free(target); - target = NULL; - - git_repository_free(repo); - repo = NULL; - - cl_fixture_cleanup("testrepo.git"); -} - -static void retrieve_tag_from_oid(git_tag **tag_out, git_repository *repo, const char *sha) -{ - git_oid oid; - - cl_git_pass(git_oid_fromstr(&oid, sha)); - cl_git_pass(git_tag_lookup(tag_out, repo, &oid)); -} - -void test_object_tag_peel__can_peel_to_a_commit(void) -{ - retrieve_tag_from_oid(&tag, repo, "7b4384978d2493e851f9cca7858815fac9b10980"); - - cl_git_pass(git_tag_peel(&target, tag)); - cl_assert(git_object_type(target) == GIT_OBJECT_COMMIT); - cl_git_pass(git_oid_streq(git_object_id(target), "e90810b8df3e80c413d903f631643c716887138d")); -} - -void test_object_tag_peel__can_peel_several_nested_tags_to_a_commit(void) -{ - retrieve_tag_from_oid(&tag, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); - - cl_git_pass(git_tag_peel(&target, tag)); - cl_assert(git_object_type(target) == GIT_OBJECT_COMMIT); - cl_git_pass(git_oid_streq(git_object_id(target), "e90810b8df3e80c413d903f631643c716887138d")); -} - -void test_object_tag_peel__can_peel_to_a_non_commit(void) -{ - retrieve_tag_from_oid(&tag, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91"); - - cl_git_pass(git_tag_peel(&target, tag)); - cl_assert(git_object_type(target) == GIT_OBJECT_BLOB); - cl_git_pass(git_oid_streq(git_object_id(target), "1385f264afb75a56a5bec74243be9b367ba4ca08")); -} diff --git a/tests/object/tag/read.c b/tests/object/tag/read.c deleted file mode 100644 index 90ba58029..000000000 --- a/tests/object/tag/read.c +++ /dev/null @@ -1,179 +0,0 @@ -#include "clar_libgit2.h" - -#include "tag.h" - -static const char *tag1_id = "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"; -static const char *tag2_id = "7b4384978d2493e851f9cca7858815fac9b10980"; -static const char *tagged_commit = "e90810b8df3e80c413d903f631643c716887138d"; -static const char *bad_tag_id = "eda9f45a2a98d4c17a09d681d88569fa4ea91755"; -static const char *badly_tagged_commit = "e90810b8df3e80c413d903f631643c716887138d"; -static const char *short_tag_id = "5da7760512a953e3c7c4e47e4392c7a4338fb729"; -static const char *short_tagged_commit = "4a5ed60bafcf4638b7c8356bd4ce1916bfede93c"; -static const char *taggerless = "4a23e2e65ad4e31c4c9db7dc746650bfad082679"; - -static git_repository *g_repo; - -/* Fixture setup and teardown */ -void test_object_tag_read__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_object_tag_read__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - - -void test_object_tag_read__parse(void) -{ - /* read and parse a tag from the repository */ - git_tag *tag1, *tag2; - git_commit *commit; - git_oid id1, id2, id_commit; - - git_oid_fromstr(&id1, tag1_id); - git_oid_fromstr(&id2, tag2_id); - git_oid_fromstr(&id_commit, tagged_commit); - - cl_git_pass(git_tag_lookup(&tag1, g_repo, &id1)); - - cl_assert_equal_s(git_tag_name(tag1), "test"); - cl_assert(git_tag_target_type(tag1) == GIT_OBJECT_TAG); - - cl_git_pass(git_tag_target((git_object **)&tag2, tag1)); - cl_assert(tag2 != NULL); - - cl_assert(git_oid_cmp(&id2, git_tag_id(tag2)) == 0); - - cl_git_pass(git_tag_target((git_object **)&commit, tag2)); - cl_assert(commit != NULL); - - cl_assert(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0); - - git_tag_free(tag1); - git_tag_free(tag2); - git_commit_free(commit); -} - -void test_object_tag_read__parse_without_tagger(void) -{ - /* read and parse a tag without a tagger field */ - git_repository *bad_tag_repo; - git_tag *bad_tag; - git_commit *commit; - git_oid id, id_commit; - - /* TODO: This is a little messy */ - cl_git_pass(git_repository_open(&bad_tag_repo, cl_fixture("bad_tag.git"))); - - git_oid_fromstr(&id, bad_tag_id); - git_oid_fromstr(&id_commit, badly_tagged_commit); - - cl_git_pass(git_tag_lookup(&bad_tag, bad_tag_repo, &id)); - cl_assert(bad_tag != NULL); - - cl_assert_equal_s(git_tag_name(bad_tag), "e90810b"); - cl_assert(git_oid_cmp(&id, git_tag_id(bad_tag)) == 0); - cl_assert(bad_tag->tagger == NULL); - - cl_git_pass(git_tag_target((git_object **)&commit, bad_tag)); - cl_assert(commit != NULL); - - cl_assert(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0); - - - git_tag_free(bad_tag); - git_commit_free(commit); - git_repository_free(bad_tag_repo); -} - -void test_object_tag_read__parse_without_message(void) -{ - /* read and parse a tag without a message field */ - git_repository *short_tag_repo; - git_tag *short_tag; - git_commit *commit; - git_oid id, id_commit; - - /* TODO: This is a little messy */ - cl_git_pass(git_repository_open(&short_tag_repo, cl_fixture("short_tag.git"))); - - git_oid_fromstr(&id, short_tag_id); - git_oid_fromstr(&id_commit, short_tagged_commit); - - cl_git_pass(git_tag_lookup(&short_tag, short_tag_repo, &id)); - cl_assert(short_tag != NULL); - - cl_assert_equal_s(git_tag_name(short_tag), "no_description"); - cl_assert(git_oid_cmp(&id, git_tag_id(short_tag)) == 0); - cl_assert(short_tag->message == NULL); - - cl_git_pass(git_tag_target((git_object **)&commit, short_tag)); - cl_assert(commit != NULL); - - cl_assert(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0); - - git_tag_free(short_tag); - git_commit_free(commit); - git_repository_free(short_tag_repo); -} - -void test_object_tag_read__without_tagger_nor_message(void) -{ - git_tag *tag; - git_oid id; - git_repository *repo; - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - - cl_git_pass(git_oid_fromstr(&id, taggerless)); - - cl_git_pass(git_tag_lookup(&tag, repo, &id)); - - cl_assert_equal_s(git_tag_name(tag), "taggerless"); - cl_assert(git_tag_target_type(tag) == GIT_OBJECT_COMMIT); - - cl_assert(tag->message == NULL); - cl_assert(tag->tagger == NULL); - - git_tag_free(tag); - git_repository_free(repo); -} - -static const char *silly_tag = "object c054ccaefbf2da31c3b19178f9e3ef20a3867924\n\ -type commit\n\ -tag v1_0_1\n\ -tagger Jamis Buck 1107717917\n\ -diff --git a/lib/sqlite3/version.rb b/lib/sqlite3/version.rb\n\ -index 0b3bf69..4ee8fc2 100644\n\ ---- a/lib/sqlite3/version.rb\n\ -+++ b/lib/sqlite3/version.rb\n\ -@@ -36,7 +36,7 @@ module SQLite3\n\ - \n\ - MAJOR = 1\n\ - MINOR = 0\n\ -- TINY = 0\n\ -+ TINY = 1\n\ - \n\ - STRING = [ MAJOR, MINOR, TINY ].join( \".\" )\n\ - \n\ - -0600\n\ -\n\ -v1_0_1 release\n"; - -void test_object_tag_read__extra_header_fields(void) -{ - git_tag *tag; - git_odb *odb; - git_oid id; - - cl_git_pass(git_repository_odb__weakptr(&odb, g_repo)); - - cl_git_pass(git_odb_write(&id, odb, silly_tag, strlen(silly_tag), GIT_OBJECT_TAG)); - cl_git_pass(git_tag_lookup(&tag, g_repo, &id)); - - cl_assert_equal_s("v1_0_1 release\n", git_tag_message(tag)); - - git_tag_free(tag); -} diff --git a/tests/object/tag/write.c b/tests/object/tag/write.c deleted file mode 100644 index 3c1a98956..000000000 --- a/tests/object/tag/write.c +++ /dev/null @@ -1,260 +0,0 @@ -#include "clar_libgit2.h" - -static const char* tagger_name = "Vicent Marti"; -static const char* tagger_email = "vicent@github.com"; -static const char* tagger_message = "This is my tag.\n\nThere are many tags, but this one is mine\n"; - -static const char *tag2_id = "7b4384978d2493e851f9cca7858815fac9b10980"; -static const char *tagged_commit = "e90810b8df3e80c413d903f631643c716887138d"; - -static git_repository *g_repo; - -/* Fixture setup and teardown */ -void test_object_tag_write__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_object_tag_write__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_object_tag_write__basic(void) -{ - /* write a tag to the repository and read it again */ - git_tag *tag; - git_oid target_id, tag_id; - git_signature *tagger; - const git_signature *tagger1; - git_reference *ref_tag; - git_object *target; - - git_oid_fromstr(&target_id, tagged_commit); - cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); - - /* create signature */ - cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); - - cl_git_pass( - git_tag_create(&tag_id, g_repo, - "the-tag", target, tagger, tagger_message, 0) - ); - - git_object_free(target); - git_signature_free(tagger); - - cl_git_pass(git_tag_lookup(&tag, g_repo, &tag_id)); - cl_assert(git_oid_cmp(git_tag_target_id(tag), &target_id) == 0); - - /* Check attributes were set correctly */ - tagger1 = git_tag_tagger(tag); - cl_assert(tagger1 != NULL); - cl_assert_equal_s(tagger1->name, tagger_name); - cl_assert_equal_s(tagger1->email, tagger_email); - cl_assert(tagger1->when.time == 123456789); - cl_assert(tagger1->when.offset == 60); - - cl_assert_equal_s(git_tag_message(tag), tagger_message); - - cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/the-tag")); - cl_assert(git_oid_cmp(git_reference_target(ref_tag), &tag_id) == 0); - cl_git_pass(git_reference_delete(ref_tag)); - git_reference_free(ref_tag); - - git_tag_free(tag); -} - -void test_object_tag_write__overwrite(void) -{ - /* Attempt to write a tag bearing the same name than an already existing tag */ - git_oid target_id, tag_id; - git_signature *tagger; - git_object *target; - - git_oid_fromstr(&target_id, tagged_commit); - cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); - - /* create signature */ - cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); - - cl_assert_equal_i(GIT_EEXISTS, git_tag_create( - &tag_id, /* out id */ - g_repo, - "e90810b", - target, - tagger, - tagger_message, - 0)); - - git_object_free(target); - git_signature_free(tagger); -} - -void test_object_tag_write__replace(void) -{ - /* Replace an already existing tag */ - git_oid target_id, tag_id, old_tag_id; - git_signature *tagger; - git_reference *ref_tag; - git_object *target; - - git_oid_fromstr(&target_id, tagged_commit); - cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); - - cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/e90810b")); - git_oid_cpy(&old_tag_id, git_reference_target(ref_tag)); - git_reference_free(ref_tag); - - /* create signature */ - cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); - - cl_git_pass(git_tag_create( - &tag_id, /* out id */ - g_repo, - "e90810b", - target, - tagger, - tagger_message, - 1)); - - git_object_free(target); - git_signature_free(tagger); - - cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/e90810b")); - cl_assert(git_oid_cmp(git_reference_target(ref_tag), &tag_id) == 0); - cl_assert(git_oid_cmp(git_reference_target(ref_tag), &old_tag_id) != 0); - - git_reference_free(ref_tag); -} - -void test_object_tag_write__lightweight(void) -{ - /* write a lightweight tag to the repository and read it again */ - git_oid target_id, object_id; - git_reference *ref_tag; - git_object *target; - - git_oid_fromstr(&target_id, tagged_commit); - cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); - - cl_git_pass(git_tag_create_lightweight( - &object_id, - g_repo, - "light-tag", - target, - 0)); - - git_object_free(target); - - cl_assert(git_oid_cmp(&object_id, &target_id) == 0); - - cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/light-tag")); - cl_assert(git_oid_cmp(git_reference_target(ref_tag), &target_id) == 0); - - cl_git_pass(git_tag_delete(g_repo, "light-tag")); - - git_reference_free(ref_tag); -} - -void test_object_tag_write__lightweight_over_existing(void) -{ - /* Attempt to write a lightweight tag bearing the same name than an already existing tag */ - git_oid target_id, object_id, existing_object_id; - git_object *target; - - git_oid_fromstr(&target_id, tagged_commit); - cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); - - cl_assert_equal_i(GIT_EEXISTS, git_tag_create_lightweight( - &object_id, - g_repo, - "e90810b", - target, - 0)); - - git_oid_fromstr(&existing_object_id, tag2_id); - cl_assert(git_oid_cmp(&object_id, &existing_object_id) == 0); - - git_object_free(target); -} - -void test_object_tag_write__delete(void) -{ - /* Delete an already existing tag */ - git_reference *ref_tag; - - cl_git_pass(git_tag_delete(g_repo, "e90810b")); - - cl_git_fail(git_reference_lookup(&ref_tag, g_repo, "refs/tags/e90810b")); - - git_reference_free(ref_tag); -} - -void test_object_tag_write__creating_with_an_invalid_name_returns_EINVALIDSPEC(void) -{ - git_oid target_id, tag_id; - git_signature *tagger; - git_object *target; - - git_oid_fromstr(&target_id, tagged_commit); - cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); - - cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); - - cl_assert_equal_i(GIT_EINVALIDSPEC, - git_tag_create(&tag_id, g_repo, - "Inv@{id", target, tagger, tagger_message, 0) - ); - - cl_assert_equal_i(GIT_EINVALIDSPEC, - git_tag_create_lightweight(&tag_id, g_repo, - "Inv@{id", target, 0) - ); - - git_object_free(target); - git_signature_free(tagger); -} - -void test_object_tag_write__deleting_with_an_invalid_name_returns_EINVALIDSPEC(void) -{ - cl_assert_equal_i(GIT_EINVALIDSPEC, git_tag_delete(g_repo, "Inv@{id")); -} - -static void create_annotation(git_oid *tag_id, const char *name) -{ - git_object *target; - git_oid target_id; - git_signature *tagger; - - cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); - - git_oid_fromstr(&target_id, tagged_commit); - cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJECT_COMMIT)); - - cl_git_pass(git_tag_annotation_create(tag_id, g_repo, name, target, tagger, "boom!")); - git_object_free(target); - git_signature_free(tagger); -} - -void test_object_tag_write__creating_an_annotation_stores_the_new_object_in_the_odb(void) -{ - git_oid tag_id; - git_tag *tag; - - create_annotation(&tag_id, "new_tag"); - - cl_git_pass(git_tag_lookup(&tag, g_repo, &tag_id)); - cl_assert_equal_s("new_tag", git_tag_name(tag)); - - git_tag_free(tag); -} - -void test_object_tag_write__creating_an_annotation_does_not_create_a_reference(void) -{ - git_oid tag_id; - git_reference *tag_ref; - - create_annotation(&tag_id, "new_tag"); - cl_git_fail_with(git_reference_lookup(&tag_ref, g_repo, "refs/tags/new_tag"), GIT_ENOTFOUND); -} diff --git a/tests/object/tree/attributes.c b/tests/object/tree/attributes.c deleted file mode 100644 index 8654dfa31..000000000 --- a/tests/object/tree/attributes.c +++ /dev/null @@ -1,118 +0,0 @@ -#include "clar_libgit2.h" -#include "tree.h" - -static git_repository *repo; - -static const char *blob_oid = "3d0970ec547fc41ef8a5882dde99c6adce65b021"; -static const char *tree_oid = "1b05fdaa881ee45b48cbaa5e9b037d667a47745e"; - -void test_object_tree_attributes__initialize(void) -{ - repo = cl_git_sandbox_init("deprecated-mode.git"); -} - -void test_object_tree_attributes__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_object_tree_attributes__ensure_correctness_of_attributes_on_insertion(void) -{ - git_treebuilder *builder; - git_oid oid; - - cl_git_pass(git_oid_fromstr(&oid, blob_oid)); - - cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); - - cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0777777)); - cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0100666)); - cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0000001)); - - git_treebuilder_free(builder); -} - -void test_object_tree_attributes__group_writable_tree_entries_created_with_an_antique_git_version_can_still_be_accessed(void) -{ - git_oid tid; - git_tree *tree; - const git_tree_entry *entry; - - - cl_git_pass(git_oid_fromstr(&tid, tree_oid)); - cl_git_pass(git_tree_lookup(&tree, repo, &tid)); - - entry = git_tree_entry_byname(tree, "old_mode.txt"); - cl_assert_equal_i( - GIT_FILEMODE_BLOB, - git_tree_entry_filemode(entry)); - - git_tree_free(tree); -} - -void test_object_tree_attributes__treebuilder_reject_invalid_filemode(void) -{ - git_treebuilder *builder; - git_oid bid; - const git_tree_entry *entry; - - cl_git_pass(git_oid_fromstr(&bid, blob_oid)); - cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); - - cl_git_fail(git_treebuilder_insert( - &entry, - builder, - "normalized.txt", - &bid, - GIT_FILEMODE_BLOB_GROUP_WRITABLE)); - - git_treebuilder_free(builder); -} - -void test_object_tree_attributes__normalize_attributes_when_creating_a_tree_from_an_existing_one(void) -{ - git_treebuilder *builder; - git_oid tid, tid2; - git_tree *tree; - const git_tree_entry *entry; - - cl_git_pass(git_oid_fromstr(&tid, tree_oid)); - cl_git_pass(git_tree_lookup(&tree, repo, &tid)); - - cl_git_pass(git_treebuilder_new(&builder, repo, tree)); - - entry = git_treebuilder_get(builder, "old_mode.txt"); - cl_assert(entry != NULL); - cl_assert_equal_i( - GIT_FILEMODE_BLOB, - git_tree_entry_filemode(entry)); - - cl_git_pass(git_treebuilder_write(&tid2, builder)); - git_treebuilder_free(builder); - git_tree_free(tree); - - cl_git_pass(git_tree_lookup(&tree, repo, &tid2)); - entry = git_tree_entry_byname(tree, "old_mode.txt"); - cl_assert(entry != NULL); - cl_assert_equal_i( - GIT_FILEMODE_BLOB, - git_tree_entry_filemode(entry)); - - git_tree_free(tree); -} - -void test_object_tree_attributes__normalize_600(void) -{ - git_oid id; - git_tree *tree; - const git_tree_entry *entry; - - git_oid_fromstr(&id, "0810fb7818088ff5ac41ee49199b51473b1bd6c7"); - cl_git_pass(git_tree_lookup(&tree, repo, &id)); - - entry = git_tree_entry_byname(tree, "ListaTeste.xml"); - cl_assert_equal_i(git_tree_entry_filemode(entry), GIT_FILEMODE_BLOB); - cl_assert_equal_i(git_tree_entry_filemode_raw(entry), 0100600); - - git_tree_free(tree); -} diff --git a/tests/object/tree/duplicateentries.c b/tests/object/tree/duplicateentries.c deleted file mode 100644 index c11ae0d3d..000000000 --- a/tests/object/tree/duplicateentries.c +++ /dev/null @@ -1,157 +0,0 @@ -#include "clar_libgit2.h" -#include "tree.h" - -static git_repository *_repo; - -void test_object_tree_duplicateentries__initialize(void) { - _repo = cl_git_sandbox_init("testrepo"); -} - -void test_object_tree_duplicateentries__cleanup(void) { - cl_git_sandbox_cleanup(); -} - -/* - * $ git show --format=raw refs/heads/dir - * commit 144344043ba4d4a405da03de3844aa829ae8be0e - * tree d52a8fe84ceedf260afe4f0287bbfca04a117e83 - * parent cf80f8de9f1185bf3a05f993f6121880dd0cfbc9 - * author Ben Straub 1343755506 -0700 - * committer Ben Straub 1343755506 -0700 - * - * Change a file mode - * - * diff --git a/a/b.txt b/a/b.txt - * old mode 100644 - * new mode 100755 - * - * $ git ls-tree d52a8fe84ceedf260afe4f0287bbfca04a117e83 - * 100644 blob a8233120f6ad708f843d861ce2b7228ec4e3dec6 README - * 040000 tree 4e0883eeeeebc1fb1735161cea82f7cb5fab7e63 a - * 100644 blob 45b983be36b73c0788dc9cbcb76cbb80fc7bb057 branch_file.txt - * 100644 blob a71586c1dfe8a71c6cbf6c129f404c5642ff31bd new.txt - */ - -static void tree_checker( - git_oid *tid, - const char *expected_sha, - git_filemode_t expected_filemode) -{ - git_tree *tree; - const git_tree_entry *entry; - git_oid oid; - - cl_git_pass(git_tree_lookup(&tree, _repo, tid)); - cl_assert_equal_i(1, (int)git_tree_entrycount(tree)); - entry = git_tree_entry_byindex(tree, 0); - - cl_git_pass(git_oid_fromstr(&oid, expected_sha)); - - cl_assert_equal_i(0, git_oid_cmp(&oid, git_tree_entry_id(entry))); - cl_assert_equal_i(expected_filemode, git_tree_entry_filemode(entry)); - - git_tree_free(tree); -} - -static void tree_creator(git_oid *out, void (*fn)(git_treebuilder *)) -{ - git_treebuilder *builder; - - cl_git_pass(git_treebuilder_new(&builder, _repo, NULL)); - - fn(builder); - - cl_git_pass(git_treebuilder_write(out, builder)); - git_treebuilder_free(builder); -} - -static void two_blobs(git_treebuilder *bld) -{ - git_oid oid; - const git_tree_entry *entry; - - cl_git_pass(git_oid_fromstr(&oid, - "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); /* blob oid (README) */ - - cl_git_pass(git_treebuilder_insert( - &entry, bld, "duplicate", &oid, - GIT_FILEMODE_BLOB)); - - cl_git_pass(git_oid_fromstr(&oid, - "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); /* blob oid (new.txt) */ - - cl_git_pass(git_treebuilder_insert( - &entry, bld, "duplicate", &oid, - GIT_FILEMODE_BLOB)); -} - -static void one_blob_and_one_tree(git_treebuilder *bld) -{ - git_oid oid; - const git_tree_entry *entry; - - cl_git_pass(git_oid_fromstr(&oid, - "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); /* blob oid (README) */ - - cl_git_pass(git_treebuilder_insert( - &entry, bld, "duplicate", &oid, - GIT_FILEMODE_BLOB)); - - cl_git_pass(git_oid_fromstr(&oid, - "4e0883eeeeebc1fb1735161cea82f7cb5fab7e63")); /* tree oid (a) */ - - cl_git_pass(git_treebuilder_insert( - &entry, bld, "duplicate", &oid, - GIT_FILEMODE_TREE)); -} - -void test_object_tree_duplicateentries__cannot_create_a_duplicate_entry_through_the_treebuilder(void) -{ - git_oid tid; - - tree_creator(&tid, two_blobs); - tree_checker(&tid, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd", GIT_FILEMODE_BLOB); - - tree_creator(&tid, one_blob_and_one_tree); - tree_checker(&tid, "4e0883eeeeebc1fb1735161cea82f7cb5fab7e63", GIT_FILEMODE_TREE); -} - -static void add_fake_conflicts(git_index *index) -{ - git_index_entry ancestor_entry, our_entry, their_entry; - - memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); - memset(&our_entry, 0x0, sizeof(git_index_entry)); - memset(&their_entry, 0x0, sizeof(git_index_entry)); - - ancestor_entry.path = "duplicate"; - ancestor_entry.mode = GIT_FILEMODE_BLOB; - GIT_INDEX_ENTRY_STAGE_SET(&ancestor_entry, 1); - git_oid_fromstr(&ancestor_entry.id, "a8233120f6ad708f843d861ce2b7228ec4e3dec6"); - - our_entry.path = "duplicate"; - our_entry.mode = GIT_FILEMODE_BLOB; - GIT_INDEX_ENTRY_STAGE_SET(&our_entry, 2); - git_oid_fromstr(&our_entry.id, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057"); - - their_entry.path = "duplicate"; - their_entry.mode = GIT_FILEMODE_BLOB; - GIT_INDEX_ENTRY_STAGE_SET(&their_entry, 3); - git_oid_fromstr(&their_entry.id, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"); - - cl_git_pass(git_index_conflict_add(index, &ancestor_entry, &our_entry, &their_entry)); -} - -void test_object_tree_duplicateentries__cannot_create_a_duplicate_entry_building_a_tree_from_a_index_with_conflicts(void) -{ - git_index *index; - git_oid tid; - - cl_git_pass(git_repository_index(&index, _repo)); - - add_fake_conflicts(index); - - cl_assert_equal_i(GIT_EUNMERGED, git_index_write_tree(&tid, index)); - - git_index_free(index); -} diff --git a/tests/object/tree/frompath.c b/tests/object/tree/frompath.c deleted file mode 100644 index 86ca47e94..000000000 --- a/tests/object/tree/frompath.c +++ /dev/null @@ -1,68 +0,0 @@ -#include "clar_libgit2.h" - -static git_repository *repo; -static git_tree *tree; - -void test_object_tree_frompath__initialize(void) -{ - git_oid id; - const char *tree_with_subtrees_oid = "ae90f12eea699729ed24555e40b9fd669da12a12"; - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_assert(repo != NULL); - - cl_git_pass(git_oid_fromstr(&id, tree_with_subtrees_oid)); - cl_git_pass(git_tree_lookup(&tree, repo, &id)); - cl_assert(tree != NULL); -} - -void test_object_tree_frompath__cleanup(void) -{ - git_tree_free(tree); - tree = NULL; - - git_repository_free(repo); - repo = NULL; -} - -static void assert_tree_from_path( - git_tree *root, - const char *path, - const char *expected_entry_name) -{ - git_tree_entry *entry; - - cl_git_pass(git_tree_entry_bypath(&entry, root, path)); - cl_assert_equal_s(git_tree_entry_name(entry), expected_entry_name); - git_tree_entry_free(entry); -} - -void test_object_tree_frompath__retrieve_tree_from_path_to_treeentry(void) -{ - git_tree_entry *e; - - assert_tree_from_path(tree, "README", "README"); - assert_tree_from_path(tree, "ab/de/fgh/1.txt", "1.txt"); - assert_tree_from_path(tree, "ab/de/fgh", "fgh"); - assert_tree_from_path(tree, "ab/de/fgh/", "fgh"); - assert_tree_from_path(tree, "ab/de", "de"); - assert_tree_from_path(tree, "ab/", "ab"); - assert_tree_from_path(tree, "ab/de/", "de"); - - cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "i-do-not-exist.txt")); - cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "README/")); - cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "ab/de/fgh/i-do-not-exist.txt")); - cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "nope/de/fgh/1.txt")); - cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "ab/me-neither/fgh/2.txt")); - cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "ab/me-neither/fgh/2.txt/")); -} - -void test_object_tree_frompath__fail_when_processing_an_invalid_path(void) -{ - git_tree_entry *e; - - cl_must_fail(git_tree_entry_bypath(&e, tree, "/")); - cl_must_fail(git_tree_entry_bypath(&e, tree, "/ab")); - cl_must_fail(git_tree_entry_bypath(&e, tree, "/ab/de")); - cl_must_fail(git_tree_entry_bypath(&e, tree, "ab//de")); -} diff --git a/tests/object/tree/parse.c b/tests/object/tree/parse.c deleted file mode 100644 index 9d76a74f0..000000000 --- a/tests/object/tree/parse.c +++ /dev/null @@ -1,164 +0,0 @@ -#include "clar_libgit2.h" -#include "tree.h" -#include "object.h" - -#define OID1_HEX \ - "\xae\x90\xf1\x2e\xea\x69\x97\x29\xed\x24" \ - "\x55\x5e\x40\xb9\xfd\x66\x9d\xa1\x2a\x12" -#define OID1_STR "ae90f12eea699729ed24555e40b9fd669da12a12" - -#define OID2_HEX \ - "\xe8\xbf\xe5\xaf\x39\x57\x9a\x7e\x48\x98" \ - "\xbb\x23\xf3\xa7\x6a\x72\xc3\x68\xce\xe6" -#define OID2_STR "e8bfe5af39579a7e4898bb23f3a76a72c368cee6" - -typedef struct { - const char *filename; - uint16_t attr; - const char *oid; -} expected_entry; - -static void assert_tree_parses(const char *data, size_t datalen, - expected_entry *expected_entries, size_t expected_nentries) -{ - git_tree *tree; - size_t n; - - if (!datalen) - datalen = strlen(data); - cl_git_pass(git_object__from_raw((git_object **) &tree, data, datalen, GIT_OBJECT_TREE)); - - cl_assert_equal_i(git_tree_entrycount(tree), expected_nentries); - - for (n = 0; n < expected_nentries; n++) { - expected_entry *expected = expected_entries + n; - const git_tree_entry *entry; - git_oid oid; - - cl_git_pass(git_oid_fromstr(&oid, expected->oid)); - - cl_assert(entry = git_tree_entry_byname(tree, expected->filename)); - cl_assert_equal_s(expected->filename, entry->filename); - cl_assert_equal_i(expected->attr, entry->attr); - cl_assert_equal_oid(&oid, entry->oid); - } - - git_object_free(&tree->object); -} - -static void assert_tree_fails(const char *data, size_t datalen) -{ - git_object *object; - if (!datalen) - datalen = strlen(data); - cl_git_fail(git_object__from_raw(&object, data, datalen, GIT_OBJECT_TREE)); -} - -void test_object_tree_parse__single_blob_parses(void) -{ - expected_entry entries[] = { - { "foo", 0100644, OID1_STR }, - }; - const char data[] = "100644 foo\x00" OID1_HEX; - - assert_tree_parses(data, ARRAY_SIZE(data) - 1, entries, ARRAY_SIZE(entries)); -} - -void test_object_tree_parse__single_tree_parses(void) -{ - expected_entry entries[] = { - { "foo", 040000, OID1_STR }, - }; - const char data[] = "040000 foo\x00" OID1_HEX; - - assert_tree_parses(data, ARRAY_SIZE(data) - 1, entries, ARRAY_SIZE(entries)); -} - -void test_object_tree_parse__leading_filename_spaces_parse(void) -{ - expected_entry entries[] = { - { " bar", 0100644, OID1_STR }, - }; - const char data[] = "100644 bar\x00" OID1_HEX; - - assert_tree_parses(data, ARRAY_SIZE(data) - 1, entries, ARRAY_SIZE(entries)); -} - -void test_object_tree_parse__multiple_entries_parse(void) -{ - expected_entry entries[] = { - { "bar", 0100644, OID1_STR }, - { "foo", 040000, OID2_STR }, - }; - const char data[] = - "100644 bar\x00" OID1_HEX - "040000 foo\x00" OID2_HEX; - - assert_tree_parses(data, ARRAY_SIZE(data) - 1, entries, ARRAY_SIZE(entries)); -} - -void test_object_tree_parse__invalid_mode_fails(void) -{ - const char data[] = "10x644 bar\x00" OID1_HEX; - assert_tree_fails(data, ARRAY_SIZE(data) - 1); -} - -void test_object_tree_parse__missing_mode_fails(void) -{ - const char data[] = " bar\x00" OID1_HEX; - assert_tree_fails(data, ARRAY_SIZE(data) - 1); -} - -void test_object_tree_parse__mode_doesnt_cause_oob_read(void) -{ - const char data[] = "100644 bar\x00" OID1_HEX; - assert_tree_fails(data, 2); - /* - * An oob-read would correctly parse the filename and - * later fail to parse the OID with a different error - * message - */ - cl_assert_equal_s(git_error_last()->message, "failed to parse tree: missing space after filemode"); -} - -void test_object_tree_parse__unreasonably_large_mode_fails(void) -{ - const char data[] = "10000000000000000000000000 bar\x00" OID1_HEX; - assert_tree_fails(data, ARRAY_SIZE(data) - 1); -} - -void test_object_tree_parse__missing_filename_separator_fails(void) -{ - const char data[] = "100644bar\x00" OID1_HEX; - assert_tree_fails(data, ARRAY_SIZE(data) - 1); -} - -void test_object_tree_parse__missing_filename_terminator_fails(void) -{ - const char data[] = "100644 bar" OID1_HEX; - assert_tree_fails(data, ARRAY_SIZE(data) - 1); -} - -void test_object_tree_parse__empty_filename_fails(void) -{ - const char data[] = "100644 \x00" OID1_HEX; - assert_tree_fails(data, ARRAY_SIZE(data) - 1); -} - -void test_object_tree_parse__trailing_garbage_fails(void) -{ - const char data[] = "100644 bar\x00" OID1_HEX "x"; - assert_tree_fails(data, ARRAY_SIZE(data) - 1); -} - -void test_object_tree_parse__leading_space_fails(void) -{ - const char data[] = " 100644 bar\x00" OID1_HEX; - assert_tree_fails(data, ARRAY_SIZE(data) - 1); -} - -void test_object_tree_parse__truncated_oid_fails(void) -{ - const char data[] = " 100644 bar\x00" OID1_HEX; - assert_tree_fails(data, ARRAY_SIZE(data) - 2); -} diff --git a/tests/object/tree/read.c b/tests/object/tree/read.c deleted file mode 100644 index 95a2e70fb..000000000 --- a/tests/object/tree/read.c +++ /dev/null @@ -1,119 +0,0 @@ -#include "clar_libgit2.h" - -#include "tree.h" - -static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd"; - -static git_repository *g_repo; - -/* Fixture setup and teardown */ -void test_object_tree_read__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_object_tree_read__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - - - -void test_object_tree_read__loaded(void) -{ - /* access randomly the entries on a loaded tree */ - git_oid id; - git_tree *tree; - - git_oid_fromstr(&id, tree_oid); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - - cl_assert(git_tree_entry_byname(tree, "README") != NULL); - cl_assert(git_tree_entry_byname(tree, "NOTEXISTS") == NULL); - cl_assert(git_tree_entry_byname(tree, "") == NULL); - cl_assert(git_tree_entry_byindex(tree, 0) != NULL); - cl_assert(git_tree_entry_byindex(tree, 2) != NULL); - cl_assert(git_tree_entry_byindex(tree, 3) == NULL); - cl_assert(git_tree_entry_byindex(tree, (unsigned int)-1) == NULL); - - git_tree_free(tree); -} - -void test_object_tree_read__two(void) -{ - /* read a tree from the repository */ - git_oid id; - git_tree *tree; - const git_tree_entry *entry; - git_object *obj; - - git_oid_fromstr(&id, tree_oid); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - - cl_assert(git_tree_entrycount(tree) == 3); - - /* GH-86: git_object_lookup() should also check the type if the object comes from the cache */ - cl_assert(git_object_lookup(&obj, g_repo, &id, GIT_OBJECT_TREE) == 0); - cl_assert(obj != NULL); - git_object_free(obj); - obj = NULL; - cl_git_fail(git_object_lookup(&obj, g_repo, &id, GIT_OBJECT_BLOB)); - cl_assert(obj == NULL); - - entry = git_tree_entry_byname(tree, "README"); - cl_assert(entry != NULL); - - cl_assert_equal_s(git_tree_entry_name(entry), "README"); - - cl_git_pass(git_tree_entry_to_object(&obj, g_repo, entry)); - cl_assert(obj != NULL); - - git_object_free(obj); - git_tree_free(tree); -} - -#define BIGFILE "bigfile" - -#ifdef GIT_ARCH_64 -#define BIGFILE_SIZE (off_t)4294967296 -#else -# define BIGFILE_SIZE SIZE_MAX -#endif - -void test_object_tree_read__largefile(void) -{ - const git_tree_entry *entry; - git_index_entry ie; - git_commit *commit; - git_object *object; - git_index *index; - git_tree *tree; - git_oid oid; - char *buf; - - if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE")) - cl_skip(); - - cl_assert(buf = git__calloc(1, BIGFILE_SIZE)); - - memset(&ie, 0, sizeof(ie)); - ie.mode = GIT_FILEMODE_BLOB; - ie.path = BIGFILE; - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_add_from_buffer(index, &ie, buf, BIGFILE_SIZE)); - cl_repo_commit_from_index(&oid, g_repo, NULL, 0, BIGFILE); - - cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); - cl_git_pass(git_commit_tree(&tree, commit)); - cl_assert(entry = git_tree_entry_byname(tree, BIGFILE)); - cl_git_pass(git_tree_entry_to_object(&object, g_repo, entry)); - - git_object_free(object); - git_tree_free(tree); - git_index_free(index); - git_commit_free(commit); - git__free(buf); -} diff --git a/tests/object/tree/update.c b/tests/object/tree/update.c deleted file mode 100644 index 41b50f3e9..000000000 --- a/tests/object/tree/update.c +++ /dev/null @@ -1,302 +0,0 @@ -#include "clar_libgit2.h" -#include "tree.h" - -static git_repository *g_repo; - -void test_object_tree_update__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo2"); -} - -void test_object_tree_update__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_object_tree_update__remove_blob(void) -{ - git_oid tree_index_id, tree_updater_id, base_id; - git_tree *base_tree; - git_index *idx; - const char *path = "README"; - - git_tree_update updates[] = { - { GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB /* ignored */, path}, - }; - - cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); - cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); - - /* Create it with an index */ - cl_git_pass(git_index_new(&idx)); - cl_git_pass(git_index_read_tree(idx, base_tree)); - cl_git_pass(git_index_remove(idx, path, 0)); - cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); - git_index_free(idx); - - /* Perform the same operation via the tree updater */ - cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 1, updates)); - - cl_assert_equal_oid(&tree_index_id, &tree_updater_id); - - git_tree_free(base_tree); -} - -void test_object_tree_update__remove_blob_deeper(void) -{ - git_oid tree_index_id, tree_updater_id, base_id; - git_tree *base_tree; - git_index *idx; - const char *path = "subdir/README"; - - git_tree_update updates[] = { - { GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB /* ignored */, path}, - }; - - cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); - cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); - - /* Create it with an index */ - cl_git_pass(git_index_new(&idx)); - cl_git_pass(git_index_read_tree(idx, base_tree)); - cl_git_pass(git_index_remove(idx, path, 0)); - cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); - git_index_free(idx); - - /* Perform the same operation via the tree updater */ - cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 1, updates)); - - cl_assert_equal_oid(&tree_index_id, &tree_updater_id); - - git_tree_free(base_tree); -} - -void test_object_tree_update__remove_all_entries(void) -{ - git_oid tree_index_id, tree_updater_id, base_id; - git_tree *base_tree; - git_index *idx; - const char *path1 = "subdir/subdir2/README"; - const char *path2 = "subdir/subdir2/new.txt"; - - git_tree_update updates[] = { - { GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB /* ignored */, path1}, - { GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB /* ignored */, path2}, - }; - - cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); - cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); - - /* Create it with an index */ - cl_git_pass(git_index_new(&idx)); - cl_git_pass(git_index_read_tree(idx, base_tree)); - cl_git_pass(git_index_remove(idx, path1, 0)); - cl_git_pass(git_index_remove(idx, path2, 0)); - cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); - git_index_free(idx); - - /* Perform the same operation via the tree updater */ - cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 2, updates)); - - cl_assert_equal_oid(&tree_index_id, &tree_updater_id); - - git_tree_free(base_tree); -} - -void test_object_tree_update__replace_blob(void) -{ - git_oid tree_index_id, tree_updater_id, base_id; - git_tree *base_tree; - git_index *idx; - const char *path = "README"; - git_index_entry entry = { {0} }; - - git_tree_update updates[] = { - { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, path}, - }; - - cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); - cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); - - /* Create it with an index */ - cl_git_pass(git_index_new(&idx)); - cl_git_pass(git_index_read_tree(idx, base_tree)); - - entry.path = path; - cl_git_pass(git_oid_fromstr(&entry.id, "fa49b077972391ad58037050f2a75f74e3671e92")); - entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_index_add(idx, &entry)); - - cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); - git_index_free(idx); - - /* Perform the same operation via the tree updater */ - cl_git_pass(git_oid_fromstr(&updates[0].id, "fa49b077972391ad58037050f2a75f74e3671e92")); - cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 1, updates)); - - cl_assert_equal_oid(&tree_index_id, &tree_updater_id); - - git_tree_free(base_tree); -} - -void test_object_tree_update__add_blobs(void) -{ - git_oid tree_index_id, tree_updater_id, base_id; - git_tree *base_tree; - git_index *idx; - git_index_entry entry = { {0} }; - int i; - const char *paths[] = { - "some/deep/path", - "some/other/path", - "a/path/elsewhere", - }; - - git_tree_update updates[] = { - { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[0]}, - { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[1]}, - { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[2]}, - }; - - cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); - - entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_oid_fromstr(&entry.id, "fa49b077972391ad58037050f2a75f74e3671e92")); - - for (i = 0; i < 3; i++) { - cl_git_pass(git_oid_fromstr(&updates[i].id, "fa49b077972391ad58037050f2a75f74e3671e92")); - } - - for (i = 0; i < 2; i++) { - int j; - - /* Create it with an index */ - cl_git_pass(git_index_new(&idx)); - - base_tree = NULL; - if (i == 1) { - cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); - cl_git_pass(git_index_read_tree(idx, base_tree)); - } - - for (j = 0; j < 3; j++) { - entry.path = paths[j]; - cl_git_pass(git_index_add(idx, &entry)); - } - - cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); - git_index_free(idx); - - /* Perform the same operations via the tree updater */ - cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 3, updates)); - - cl_assert_equal_oid(&tree_index_id, &tree_updater_id); - } - - git_tree_free(base_tree); -} - -void test_object_tree_update__add_blobs_unsorted(void) -{ - git_oid tree_index_id, tree_updater_id, base_id; - git_tree *base_tree; - git_index *idx; - git_index_entry entry = { {0} }; - int i; - const char *paths[] = { - "some/deep/path", - "a/path/elsewhere", - "some/other/path", - }; - - git_tree_update updates[] = { - { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[0]}, - { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[1]}, - { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[2]}, - }; - - cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); - - entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_oid_fromstr(&entry.id, "fa49b077972391ad58037050f2a75f74e3671e92")); - - for (i = 0; i < 3; i++) { - cl_git_pass(git_oid_fromstr(&updates[i].id, "fa49b077972391ad58037050f2a75f74e3671e92")); - } - - for (i = 0; i < 2; i++) { - int j; - - /* Create it with an index */ - cl_git_pass(git_index_new(&idx)); - - base_tree = NULL; - if (i == 1) { - cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); - cl_git_pass(git_index_read_tree(idx, base_tree)); - } - - for (j = 0; j < 3; j++) { - entry.path = paths[j]; - cl_git_pass(git_index_add(idx, &entry)); - } - - cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); - git_index_free(idx); - - /* Perform the same operations via the tree updater */ - cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 3, updates)); - - cl_assert_equal_oid(&tree_index_id, &tree_updater_id); - } - - git_tree_free(base_tree); -} - -void test_object_tree_update__add_conflict(void) -{ - int i; - git_oid tree_updater_id; - git_tree_update updates[] = { - { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, "a/dir/blob"}, - { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, "a/dir"}, - }; - - for (i = 0; i < 2; i++) { - cl_git_pass(git_oid_fromstr(&updates[i].id, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); - } - - cl_git_fail(git_tree_create_updated(&tree_updater_id, g_repo, NULL, 2, updates)); -} - -void test_object_tree_update__add_conflict2(void) -{ - int i; - git_oid tree_updater_id; - git_tree_update updates[] = { - { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, "a/dir/blob"}, - { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_TREE, "a/dir/blob"}, - }; - - for (i = 0; i < 2; i++) { - cl_git_pass(git_oid_fromstr(&updates[i].id, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); - } - - cl_git_fail(git_tree_create_updated(&tree_updater_id, g_repo, NULL, 2, updates)); -} - -void test_object_tree_update__remove_invalid_submodule(void) -{ - git_tree *baseline; - git_oid updated_tree_id, baseline_id; - git_tree_update updates[] = { - {GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB, "submodule"}, - }; - - /* This tree contains a submodule with an all-zero commit for a submodule named 'submodule' */ - cl_git_pass(git_oid_fromstr(&baseline_id, "396c7f1adb7925f51ba13a75f48252f44c5a14a2")); - cl_git_pass(git_tree_lookup(&baseline, g_repo, &baseline_id)); - cl_git_pass(git_tree_create_updated(&updated_tree_id, g_repo, baseline, 1, updates)); - - git_tree_free(baseline); -} diff --git a/tests/object/tree/walk.c b/tests/object/tree/walk.c deleted file mode 100644 index d1fdaa3a0..000000000 --- a/tests/object/tree/walk.c +++ /dev/null @@ -1,177 +0,0 @@ -#include "clar_libgit2.h" -#include "tree.h" - -static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd"; -static git_repository *g_repo; - -void test_object_tree_walk__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_object_tree_walk__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int treewalk_count_cb( - const char *root, const git_tree_entry *entry, void *payload) -{ - int *count = payload; - - GIT_UNUSED(root); - GIT_UNUSED(entry); - - (*count) += 1; - - return 0; -} - -void test_object_tree_walk__0(void) -{ - git_oid id; - git_tree *tree; - int ct; - - git_oid_fromstr(&id, tree_oid); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - - ct = 0; - cl_git_pass(git_tree_walk(tree, GIT_TREEWALK_PRE, treewalk_count_cb, &ct)); - cl_assert_equal_i(3, ct); - - ct = 0; - cl_git_pass(git_tree_walk(tree, GIT_TREEWALK_POST, treewalk_count_cb, &ct)); - cl_assert_equal_i(3, ct); - - git_tree_free(tree); -} - - -static int treewalk_stop_cb( - const char *root, const git_tree_entry *entry, void *payload) -{ - int *count = payload; - - GIT_UNUSED(root); - GIT_UNUSED(entry); - - (*count) += 1; - - return (*count == 2) ? -123 : 0; -} - -static int treewalk_stop_immediately_cb( - const char *root, const git_tree_entry *entry, void *payload) -{ - GIT_UNUSED(root); - GIT_UNUSED(entry); - GIT_UNUSED(payload); - return -100; -} - -void test_object_tree_walk__1(void) -{ - git_oid id; - git_tree *tree; - int ct; - - git_oid_fromstr(&id, tree_oid); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - - ct = 0; - cl_assert_equal_i( - -123, git_tree_walk(tree, GIT_TREEWALK_PRE, treewalk_stop_cb, &ct)); - cl_assert_equal_i(2, ct); - - ct = 0; - cl_assert_equal_i( - -123, git_tree_walk(tree, GIT_TREEWALK_POST, treewalk_stop_cb, &ct)); - cl_assert_equal_i(2, ct); - - cl_assert_equal_i( - -100, git_tree_walk( - tree, GIT_TREEWALK_PRE, treewalk_stop_immediately_cb, NULL)); - - cl_assert_equal_i( - -100, git_tree_walk( - tree, GIT_TREEWALK_POST, treewalk_stop_immediately_cb, NULL)); - - git_tree_free(tree); -} - - -struct treewalk_skip_data { - int files; - int dirs; - const char *skip; - const char *stop; -}; - -static int treewalk_skip_de_cb( - const char *root, const git_tree_entry *entry, void *payload) -{ - struct treewalk_skip_data *data = payload; - const char *name = git_tree_entry_name(entry); - - GIT_UNUSED(root); - - if (git_tree_entry_type(entry) == GIT_OBJECT_TREE) - data->dirs++; - else - data->files++; - - if (data->skip && !strcmp(name, data->skip)) - return 1; - else if (data->stop && !strcmp(name, data->stop)) - return -1; - else - return 0; -} - -void test_object_tree_walk__2(void) -{ - git_oid id; - git_tree *tree; - struct treewalk_skip_data data; - - /* look up a deep tree */ - git_oid_fromstr(&id, "ae90f12eea699729ed24555e40b9fd669da12a12"); - cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - - memset(&data, 0, sizeof(data)); - data.skip = "de"; - - cl_assert_equal_i(0, git_tree_walk( - tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); - cl_assert_equal_i(5, data.files); - cl_assert_equal_i(3, data.dirs); - - memset(&data, 0, sizeof(data)); - data.stop = "3.txt"; - - cl_assert_equal_i(-1, git_tree_walk( - tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); - cl_assert_equal_i(3, data.files); - cl_assert_equal_i(2, data.dirs); - - memset(&data, 0, sizeof(data)); - data.skip = "new.txt"; - - cl_assert_equal_i(0, git_tree_walk( - tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); - cl_assert_equal_i(7, data.files); - cl_assert_equal_i(4, data.dirs); - - memset(&data, 0, sizeof(data)); - data.stop = "new.txt"; - - cl_assert_equal_i(-1, git_tree_walk( - tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data)); - cl_assert_equal_i(7, data.files); - cl_assert_equal_i(4, data.dirs); - - git_tree_free(tree); -} diff --git a/tests/object/tree/write.c b/tests/object/tree/write.c deleted file mode 100644 index a4ceb35b6..000000000 --- a/tests/object/tree/write.c +++ /dev/null @@ -1,526 +0,0 @@ -#include "clar_libgit2.h" - -#include "tree.h" - -static const char *blob_oid = "fa49b077972391ad58037050f2a75f74e3671e92"; -static const char *first_tree = "181037049a54a1eb5fab404658a3a250b44335d7"; -static const char *second_tree = "f60079018b664e4e79329a7ef9559c8d9e0378d1"; -static const char *third_tree = "eb86d8b81d6adbd5290a935d6c9976882de98488"; - -static git_repository *g_repo; - -/* Fixture setup and teardown */ -void test_object_tree_write__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_object_tree_write__cleanup(void) -{ - cl_git_sandbox_cleanup(); - - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 1)); -} - -void test_object_tree_write__from_memory(void) -{ - /* write a tree from a memory */ - git_treebuilder *builder; - git_tree *tree; - git_oid id, bid, rid, id2; - - git_oid_fromstr(&id, first_tree); - git_oid_fromstr(&id2, second_tree); - git_oid_fromstr(&bid, blob_oid); - - /* create a second tree from first tree using `git_treebuilder_insert` - * on REPOSITORY_FOLDER. - */ - cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - cl_git_pass(git_treebuilder_new(&builder, g_repo, tree)); - - cl_git_fail(git_treebuilder_insert(NULL, builder, "", - &bid, GIT_FILEMODE_BLOB)); - cl_git_fail(git_treebuilder_insert(NULL, builder, "/", - &bid, GIT_FILEMODE_BLOB)); - cl_git_fail(git_treebuilder_insert(NULL, builder, ".git", - &bid, GIT_FILEMODE_BLOB)); - cl_git_fail(git_treebuilder_insert(NULL, builder, "..", - &bid, GIT_FILEMODE_BLOB)); - cl_git_fail(git_treebuilder_insert(NULL, builder, ".", - &bid, GIT_FILEMODE_BLOB)); - cl_git_fail(git_treebuilder_insert(NULL, builder, "folder/new.txt", - &bid, GIT_FILEMODE_BLOB)); - - cl_git_pass(git_treebuilder_insert( - NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB)); - - cl_git_pass(git_treebuilder_write(&rid, builder)); - - cl_assert(git_oid_cmp(&rid, &id2) == 0); - - git_treebuilder_free(builder); - git_tree_free(tree); -} - -void test_object_tree_write__subtree(void) -{ - /* write a hierarchical tree from a memory */ - git_treebuilder *builder; - git_tree *tree; - git_oid id, bid, subtree_id, id2, id3; - git_oid id_hiearar; - - git_oid_fromstr(&id, first_tree); - git_oid_fromstr(&id2, second_tree); - git_oid_fromstr(&id3, third_tree); - git_oid_fromstr(&bid, blob_oid); - - /* create subtree */ - cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); - cl_git_pass(git_treebuilder_insert( - NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB)); /* -V536 */ - cl_git_pass(git_treebuilder_write(&subtree_id, builder)); - git_treebuilder_free(builder); - - /* create parent tree */ - cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - cl_git_pass(git_treebuilder_new(&builder, g_repo, tree)); - cl_git_pass(git_treebuilder_insert( - NULL, builder, "new", &subtree_id, GIT_FILEMODE_TREE)); /* -V536 */ - cl_git_pass(git_treebuilder_write(&id_hiearar, builder)); - git_treebuilder_free(builder); - git_tree_free(tree); - - cl_assert(git_oid_cmp(&id_hiearar, &id3) == 0); - - /* check data is correct */ - cl_git_pass(git_tree_lookup(&tree, g_repo, &id_hiearar)); - cl_assert(2 == git_tree_entrycount(tree)); - git_tree_free(tree); -} - -/* - * And the Lord said: Is this tree properly sorted? - */ -void test_object_tree_write__sorted_subtrees(void) -{ - git_treebuilder *builder; - git_tree *tree; - unsigned int i; - int position_c = -1, position_cake = -1, position_config = -1; - - struct { - unsigned int attr; - const char *filename; - } entries[] = { - { GIT_FILEMODE_BLOB, ".gitattributes" }, - { GIT_FILEMODE_BLOB, ".gitignore" }, - { GIT_FILEMODE_BLOB, ".htaccess" }, - { GIT_FILEMODE_BLOB, "Capfile" }, - { GIT_FILEMODE_BLOB, "Makefile"}, - { GIT_FILEMODE_BLOB, "README"}, - { GIT_FILEMODE_TREE, "app"}, - { GIT_FILEMODE_TREE, "cake"}, - { GIT_FILEMODE_TREE, "config"}, - { GIT_FILEMODE_BLOB, "c"}, - { GIT_FILEMODE_BLOB, "git_test.txt"}, - { GIT_FILEMODE_BLOB, "htaccess.htaccess"}, - { GIT_FILEMODE_BLOB, "index.php"}, - { GIT_FILEMODE_TREE, "plugins"}, - { GIT_FILEMODE_TREE, "schemas"}, - { GIT_FILEMODE_TREE, "ssl-certs"}, - { GIT_FILEMODE_TREE, "vendors"} - }; - - git_oid bid, tid, tree_oid; - - cl_git_pass(git_oid_fromstr(&bid, blob_oid)); - cl_git_pass(git_oid_fromstr(&tid, first_tree)); - - cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); - - for (i = 0; i < ARRAY_SIZE(entries); ++i) { - git_oid *id = entries[i].attr == GIT_FILEMODE_TREE ? &tid : &bid; - - cl_git_pass(git_treebuilder_insert(NULL, - builder, entries[i].filename, id, entries[i].attr)); - } - - cl_git_pass(git_treebuilder_write(&tree_oid, builder)); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid)); - for (i = 0; i < git_tree_entrycount(tree); i++) { - const git_tree_entry *entry = git_tree_entry_byindex(tree, i); - - if (strcmp(entry->filename, "c") == 0) - position_c = i; - - if (strcmp(entry->filename, "cake") == 0) - position_cake = i; - - if (strcmp(entry->filename, "config") == 0) - position_config = i; - } - - git_tree_free(tree); - - cl_assert(position_c != -1); - cl_assert(position_cake != -1); - cl_assert(position_config != -1); - - cl_assert(position_c < position_cake); - cl_assert(position_cake < position_config); - - git_treebuilder_free(builder); -} - -static struct { - unsigned int attr; - const char *filename; -} _entries[] = { - { GIT_FILEMODE_BLOB, "aardvark" }, - { GIT_FILEMODE_BLOB, ".first" }, - { GIT_FILEMODE_BLOB, "apple" }, - { GIT_FILEMODE_BLOB, "last"}, - { GIT_FILEMODE_BLOB, "apple_after"}, - { GIT_FILEMODE_BLOB, "after_aardvark"}, - { 0, NULL }, -}; - -void test_object_tree_write__removing_and_re_adding_in_treebuilder(void) -{ - git_treebuilder *builder; - int i, aardvark_i, apple_i, apple_after_i, apple_extra_i, last_i; - git_oid entry_oid, tree_oid; - git_tree *tree; - - cl_git_pass(git_oid_fromstr(&entry_oid, blob_oid)); - - cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); - - cl_assert_equal_i(0, (int)git_treebuilder_entrycount(builder)); - - for (i = 0; _entries[i].filename; ++i) - cl_git_pass(git_treebuilder_insert(NULL, - builder, _entries[i].filename, &entry_oid, _entries[i].attr)); - - cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder)); - - cl_git_pass(git_treebuilder_remove(builder, "apple")); - cl_assert_equal_i(5, (int)git_treebuilder_entrycount(builder)); - - cl_git_pass(git_treebuilder_remove(builder, "apple_after")); - cl_assert_equal_i(4, (int)git_treebuilder_entrycount(builder)); - - cl_git_pass(git_treebuilder_insert( - NULL, builder, "before_last", &entry_oid, GIT_FILEMODE_BLOB)); - cl_assert_equal_i(5, (int)git_treebuilder_entrycount(builder)); - - /* reinsert apple_after */ - cl_git_pass(git_treebuilder_insert( - NULL, builder, "apple_after", &entry_oid, GIT_FILEMODE_BLOB)); - cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder)); - - cl_git_pass(git_treebuilder_remove(builder, "last")); - cl_assert_equal_i(5, (int)git_treebuilder_entrycount(builder)); - - /* reinsert last */ - cl_git_pass(git_treebuilder_insert( - NULL, builder, "last", &entry_oid, GIT_FILEMODE_BLOB)); - cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder)); - - cl_git_pass(git_treebuilder_insert( - NULL, builder, "apple_extra", &entry_oid, GIT_FILEMODE_BLOB)); - cl_assert_equal_i(7, (int)git_treebuilder_entrycount(builder)); - - cl_git_pass(git_treebuilder_write(&tree_oid, builder)); - - git_treebuilder_free(builder); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid)); - - cl_assert_equal_i(7, (int)git_tree_entrycount(tree)); - - cl_assert(git_tree_entry_byname(tree, ".first") != NULL); - cl_assert(git_tree_entry_byname(tree, "apple") == NULL); - cl_assert(git_tree_entry_byname(tree, "apple_after") != NULL); - cl_assert(git_tree_entry_byname(tree, "apple_extra") != NULL); - cl_assert(git_tree_entry_byname(tree, "last") != NULL); - - aardvark_i = apple_i = apple_after_i = apple_extra_i = last_i = -1; - - for (i = 0; i < 7; ++i) { - const git_tree_entry *entry = git_tree_entry_byindex(tree, i); - - if (!strcmp(entry->filename, "aardvark")) - aardvark_i = i; - else if (!strcmp(entry->filename, "apple")) - apple_i = i; - else if (!strcmp(entry->filename, "apple_after")) - apple_after_i = i; - else if (!strcmp(entry->filename, "apple_extra")) - apple_extra_i = i; - else if (!strcmp(entry->filename, "last")) - last_i = i; - } - - cl_assert_equal_i(-1, apple_i); - cl_assert_equal_i(6, last_i); - cl_assert(aardvark_i < apple_after_i); - cl_assert(apple_after_i < apple_extra_i); - - git_tree_free(tree); -} - -static int treebuilder_filter_prefixed( - const git_tree_entry *entry, void *payload) -{ - return !git__prefixcmp(git_tree_entry_name(entry), payload); -} - -void test_object_tree_write__filtering(void) -{ - git_treebuilder *builder; - int i; - git_oid entry_oid, tree_oid; - git_tree *tree; - - git_oid_fromstr(&entry_oid, blob_oid); - - cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); - - for (i = 0; _entries[i].filename; ++i) - cl_git_pass(git_treebuilder_insert(NULL, - builder, _entries[i].filename, &entry_oid, _entries[i].attr)); - - cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder)); - - cl_assert(git_treebuilder_get(builder, "apple") != NULL); - cl_assert(git_treebuilder_get(builder, "aardvark") != NULL); - cl_assert(git_treebuilder_get(builder, "last") != NULL); - - git_treebuilder_filter(builder, treebuilder_filter_prefixed, "apple"); - - cl_assert_equal_i(4, (int)git_treebuilder_entrycount(builder)); - - cl_assert(git_treebuilder_get(builder, "apple") == NULL); - cl_assert(git_treebuilder_get(builder, "aardvark") != NULL); - cl_assert(git_treebuilder_get(builder, "last") != NULL); - - git_treebuilder_filter(builder, treebuilder_filter_prefixed, "a"); - - cl_assert_equal_i(2, (int)git_treebuilder_entrycount(builder)); - - cl_assert(git_treebuilder_get(builder, "aardvark") == NULL); - cl_assert(git_treebuilder_get(builder, "last") != NULL); - - cl_git_pass(git_treebuilder_write(&tree_oid, builder)); - - git_treebuilder_free(builder); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid)); - - cl_assert_equal_i(2, (int)git_tree_entrycount(tree)); - - git_tree_free(tree); -} - -void test_object_tree_write__cruel_paths(void) -{ - static const char *the_paths[] = { - "C:\\", - " : * ? \" \n < > |", - "a\\b", - "\\\\b\a", - ":\\", - "COM1", - "foo.aux", - REP1024("1234"), /* 4096 char string */ - REP1024("12345678"), /* 8192 char string */ - "\xC5\xAA\x6E\xC4\xAD\x63\xC5\x8D\x64\x65\xCC\xBD", /* Ūnĭcōde̽ */ - NULL - }; - git_treebuilder *builder; - git_tree *tree; - git_oid id, bid, subid; - const char **scan; - int count = 0, i, j; - git_tree_entry *te; - - git_oid_fromstr(&bid, blob_oid); - - /* create tree */ - cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); - for (scan = the_paths; *scan; ++scan) { - cl_git_pass(git_treebuilder_insert( - NULL, builder, *scan, &bid, GIT_FILEMODE_BLOB)); - count++; - } - cl_git_pass(git_treebuilder_write(&id, builder)); - git_treebuilder_free(builder); - - /* check data is correct */ - cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - - cl_assert_equal_i(count, git_tree_entrycount(tree)); - - for (scan = the_paths; *scan; ++scan) { - const git_tree_entry *cte = git_tree_entry_byname(tree, *scan); - cl_assert(cte != NULL); - cl_assert_equal_s(*scan, git_tree_entry_name(cte)); - } - for (scan = the_paths; *scan; ++scan) { - cl_git_pass(git_tree_entry_bypath(&te, tree, *scan)); - cl_assert_equal_s(*scan, git_tree_entry_name(te)); - git_tree_entry_free(te); - } - - git_tree_free(tree); - - /* let's try longer paths */ - cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); - for (scan = the_paths; *scan; ++scan) { - cl_git_pass(git_treebuilder_insert( - NULL, builder, *scan, &id, GIT_FILEMODE_TREE)); - } - cl_git_pass(git_treebuilder_write(&subid, builder)); - git_treebuilder_free(builder); - - /* check data is correct */ - cl_git_pass(git_tree_lookup(&tree, g_repo, &subid)); - - cl_assert_equal_i(count, git_tree_entrycount(tree)); - - for (i = 0; i < count; ++i) { - for (j = 0; j < count; ++j) { - git_str b = GIT_STR_INIT; - cl_git_pass(git_str_joinpath(&b, the_paths[i], the_paths[j])); - cl_git_pass(git_tree_entry_bypath(&te, tree, b.ptr)); - cl_assert_equal_s(the_paths[j], git_tree_entry_name(te)); - git_tree_entry_free(te); - git_str_dispose(&b); - } - } - - git_tree_free(tree); -} - -void test_object_tree_write__protect_filesystems(void) -{ - git_treebuilder *builder; - git_oid bid; - - cl_git_pass(git_oid_fromstr(&bid, "fa49b077972391ad58037050f2a75f74e3671e92")); - - /* Ensure that (by default) we can write objects with funny names on - * platforms that are not affected. - */ - cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); - - cl_git_fail(git_treebuilder_insert(NULL, builder, ".git.", &bid, GIT_FILEMODE_BLOB)); - cl_git_fail(git_treebuilder_insert(NULL, builder, "git~1", &bid, GIT_FILEMODE_BLOB)); - -#ifndef __APPLE__ - cl_git_pass(git_treebuilder_insert(NULL, builder, ".git\xef\xbb\xbf", &bid, GIT_FILEMODE_BLOB)); - cl_git_pass(git_treebuilder_insert(NULL, builder, ".git\xe2\x80\xad", &bid, GIT_FILEMODE_BLOB)); -#endif - - git_treebuilder_free(builder); - - /* Now turn on core.protectHFS and core.protectNTFS and validate that these - * paths are rejected. - */ - - cl_repo_set_bool(g_repo, "core.protectHFS", true); - cl_repo_set_bool(g_repo, "core.protectNTFS", true); - - cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); - - cl_git_fail(git_treebuilder_insert(NULL, builder, ".git.", &bid, GIT_FILEMODE_BLOB)); - cl_git_fail(git_treebuilder_insert(NULL, builder, "git~1", &bid, GIT_FILEMODE_BLOB)); - - cl_git_fail(git_treebuilder_insert(NULL, builder, ".git\xef\xbb\xbf", &bid, GIT_FILEMODE_BLOB)); - cl_git_fail(git_treebuilder_insert(NULL, builder, ".git\xe2\x80\xad", &bid, GIT_FILEMODE_BLOB)); - cl_git_fail(git_treebuilder_insert(NULL, builder, ".git::$INDEX_ALLOCATION/dummy-file", &bid, GIT_FILEMODE_BLOB)); - - git_treebuilder_free(builder); -} - -static void test_invalid_objects(bool should_allow_invalid) -{ - git_treebuilder *builder; - git_oid valid_blob_id, invalid_blob_id, valid_tree_id, invalid_tree_id; - -#define assert_allowed(expr) \ - clar__assert(!(expr) == should_allow_invalid, \ - __FILE__, __func__, __LINE__, \ - (should_allow_invalid ? \ - "Expected function call to succeed: " #expr : \ - "Expected function call to fail: " #expr), \ - NULL, 1) - - cl_git_pass(git_oid_fromstr(&valid_blob_id, blob_oid)); - cl_git_pass(git_oid_fromstr(&invalid_blob_id, - "1234567890123456789012345678901234567890")); - cl_git_pass(git_oid_fromstr(&valid_tree_id, first_tree)); - cl_git_pass(git_oid_fromstr(&invalid_tree_id, - "0000000000111111111122222222223333333333")); - - cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); - - /* test valid blobs and trees (these should always pass) */ - cl_git_pass(git_treebuilder_insert(NULL, builder, "file.txt", &valid_blob_id, GIT_FILEMODE_BLOB)); - cl_git_pass(git_treebuilder_insert(NULL, builder, "folder", &valid_tree_id, GIT_FILEMODE_TREE)); - - /* replace valid files and folders with invalid ones */ - assert_allowed(git_treebuilder_insert(NULL, builder, "file.txt", &invalid_blob_id, GIT_FILEMODE_BLOB)); - assert_allowed(git_treebuilder_insert(NULL, builder, "folder", &invalid_blob_id, GIT_FILEMODE_BLOB)); - - /* insert new invalid files and folders */ - assert_allowed(git_treebuilder_insert(NULL, builder, "invalid_file.txt", &invalid_blob_id, GIT_FILEMODE_BLOB)); - assert_allowed(git_treebuilder_insert(NULL, builder, "invalid_folder", &invalid_blob_id, GIT_FILEMODE_BLOB)); - - /* insert valid blobs as trees and trees as blobs */ - assert_allowed(git_treebuilder_insert(NULL, builder, "file_as_folder", &valid_blob_id, GIT_FILEMODE_TREE)); - assert_allowed(git_treebuilder_insert(NULL, builder, "folder_as_file.txt", &valid_tree_id, GIT_FILEMODE_BLOB)); - -#undef assert_allowed - - git_treebuilder_free(builder); -} - -static void test_inserting_submodule(void) -{ - git_treebuilder *bld; - git_oid sm_id; - - cl_git_pass(git_oid_fromstr(&sm_id, "da39a3ee5e6b4b0d3255bfef95601890afd80709")); - cl_git_pass(git_treebuilder_new(&bld, g_repo, NULL)); - cl_git_pass(git_treebuilder_insert(NULL, bld, "sm", &sm_id, GIT_FILEMODE_COMMIT)); - git_treebuilder_free(bld); -} - -void test_object_tree_write__object_validity(void) -{ - /* Ensure that we cannot add invalid objects by default */ - test_invalid_objects(false); - test_inserting_submodule(); - - /* Ensure that we can turn off validation */ - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 0)); - test_invalid_objects(true); - test_inserting_submodule(); -} - -void test_object_tree_write__invalid_null_oid(void) -{ - git_treebuilder *bld; - git_oid null_oid = {{0}}; - - cl_git_pass(git_treebuilder_new(&bld, g_repo, NULL)); - cl_git_fail(git_treebuilder_insert(NULL, bld, "null_oid_file", &null_oid, GIT_FILEMODE_BLOB)); - cl_assert(git_error_last() && strstr(git_error_last()->message, "null OID") != NULL); - - git_treebuilder_free(bld); -} diff --git a/tests/object/validate.c b/tests/object/validate.c deleted file mode 100644 index 87193deb6..000000000 --- a/tests/object/validate.c +++ /dev/null @@ -1,50 +0,0 @@ -#include "clar_libgit2.h" - -#define VALID_COMMIT "tree bdd24e358576f1baa275df98cdcaf3ac9a3f4233\n" \ - "parent d6d956f1d66210bfcd0484166befab33b5987a39\n" \ - "author Edward Thomson 1638286404 -0500\n" \ - "committer Edward Thomson 1638324642 -0500\n" \ - "\n" \ - "commit go here.\n" -#define VALID_TREE "100644 HEADER\0\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42" - -#define INVALID_COMMIT "tree bdd24e358576f1baa275df98cdcaf3ac9a3f4233\n" \ - "parent d6d956f1d66210bfcd0484166befab33b5987a39\n" \ - "committer Edward Thomson 1638324642 -0500\n" \ - "\n" \ - "commit go here.\n" -#define INVALID_TREE "100644 HEADER \x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42\x42" - -void test_object_validate__valid(void) -{ - int valid; - - cl_git_pass(git_object_rawcontent_is_valid(&valid, "", 0, GIT_OBJECT_BLOB)); - cl_assert_equal_i(1, valid); - - cl_git_pass(git_object_rawcontent_is_valid(&valid, "foobar", 0, GIT_OBJECT_BLOB)); - cl_assert_equal_i(1, valid); - - cl_git_pass(git_object_rawcontent_is_valid(&valid, VALID_COMMIT, CONST_STRLEN(VALID_COMMIT), GIT_OBJECT_COMMIT)); - cl_assert_equal_i(1, valid); - - cl_git_pass(git_object_rawcontent_is_valid(&valid, VALID_TREE, CONST_STRLEN(VALID_TREE), GIT_OBJECT_TREE)); - cl_assert_equal_i(1, valid); -} - -void test_object_validate__invalid(void) -{ - int valid; - - cl_git_pass(git_object_rawcontent_is_valid(&valid, "", 0, GIT_OBJECT_COMMIT)); - cl_assert_equal_i(0, valid); - - cl_git_pass(git_object_rawcontent_is_valid(&valid, "foobar", 0, GIT_OBJECT_COMMIT)); - cl_assert_equal_i(0, valid); - - cl_git_pass(git_object_rawcontent_is_valid(&valid, INVALID_COMMIT, CONST_STRLEN(INVALID_COMMIT), GIT_OBJECT_COMMIT)); - cl_assert_equal_i(0, valid); - - cl_git_pass(git_object_rawcontent_is_valid(&valid, INVALID_TREE, CONST_STRLEN(INVALID_TREE), GIT_OBJECT_TREE)); - cl_assert_equal_i(0, valid); -} diff --git a/tests/odb/alternates.c b/tests/odb/alternates.c deleted file mode 100644 index 6c00fda2f..000000000 --- a/tests/odb/alternates.c +++ /dev/null @@ -1,80 +0,0 @@ -#include "clar_libgit2.h" -#include "odb.h" -#include "filebuf.h" - -static git_str destpath, filepath; -static const char *paths[] = { - "A.git", "B.git", "C.git", "D.git", "E.git", "F.git", "G.git" -}; -static git_filebuf file; -static git_repository *repo; - -void test_odb_alternates__cleanup(void) -{ - size_t i; - - git_str_dispose(&destpath); - git_str_dispose(&filepath); - - for (i = 0; i < ARRAY_SIZE(paths); i++) - cl_fixture_cleanup(paths[i]); -} - -static void init_linked_repo(const char *path, const char *alternate) -{ - git_str_clear(&destpath); - git_str_clear(&filepath); - - cl_git_pass(git_repository_init(&repo, path, 1)); - cl_git_pass(git_fs_path_prettify(&destpath, alternate, NULL)); - cl_git_pass(git_str_joinpath(&destpath, destpath.ptr, "objects")); - cl_git_pass(git_str_joinpath(&filepath, git_repository_path(repo), "objects/info")); - cl_git_pass(git_futils_mkdir(filepath.ptr, 0755, GIT_MKDIR_PATH)); - cl_git_pass(git_str_joinpath(&filepath, filepath.ptr , "alternates")); - - cl_git_pass(git_filebuf_open(&file, git_str_cstr(&filepath), 0, 0666)); - git_filebuf_printf(&file, "%s\n", git_str_cstr(&destpath)); - cl_git_pass(git_filebuf_commit(&file)); - - git_repository_free(repo); -} - -void test_odb_alternates__chained(void) -{ - git_commit *commit; - git_oid oid; - - /* Set the alternate A -> testrepo.git */ - init_linked_repo(paths[0], cl_fixture("testrepo.git")); - - /* Set the alternate B -> A */ - init_linked_repo(paths[1], paths[0]); - - /* Now load B and see if we can find an object from testrepo.git */ - cl_git_pass(git_repository_open(&repo, paths[1])); - git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - git_commit_free(commit); - git_repository_free(repo); -} - -void test_odb_alternates__long_chain(void) -{ - git_commit *commit; - git_oid oid; - size_t i; - - /* Set the alternate A -> testrepo.git */ - init_linked_repo(paths[0], cl_fixture("testrepo.git")); - - /* Set up the five-element chain */ - for (i = 1; i < ARRAY_SIZE(paths); i++) { - init_linked_repo(paths[i], paths[i-1]); - } - - /* Now load the last one and see if we can find an object from testrepo.git */ - cl_git_pass(git_repository_open(&repo, paths[ARRAY_SIZE(paths)-1])); - git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - cl_git_fail(git_commit_lookup(&commit, repo, &oid)); - git_repository_free(repo); -} diff --git a/tests/odb/backend/backend_helpers.c b/tests/odb/backend/backend_helpers.c deleted file mode 100644 index 542799242..000000000 --- a/tests/odb/backend/backend_helpers.c +++ /dev/null @@ -1,172 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/odb_backend.h" -#include "backend_helpers.h" - -static int search_object(const fake_object **out, fake_backend *fake, const git_oid *oid, size_t len) -{ - const fake_object *obj = fake->objects, *found = NULL; - - while (obj && obj->oid) { - git_oid current_oid; - - git_oid_fromstr(¤t_oid, obj->oid); - - if (git_oid_ncmp(¤t_oid, oid, len) == 0) { - if (found) - return GIT_EAMBIGUOUS; - found = obj; - } - - obj++; - } - - if (found && out) - *out = found; - - return found ? GIT_OK : GIT_ENOTFOUND; -} - -static int fake_backend__exists(git_odb_backend *backend, const git_oid *oid) -{ - fake_backend *fake; - - fake = (fake_backend *)backend; - - fake->exists_calls++; - - return search_object(NULL, fake, oid, GIT_OID_HEXSZ) == GIT_OK; -} - -static int fake_backend__exists_prefix( - git_oid *out, git_odb_backend *backend, const git_oid *oid, size_t len) -{ - const fake_object *obj; - fake_backend *fake; - int error; - - fake = (fake_backend *)backend; - - fake->exists_prefix_calls++; - - if ((error = search_object(&obj, fake, oid, len)) < 0) - return error; - - if (out) - git_oid_fromstr(out, obj->oid); - - return 0; -} - -static int fake_backend__read( - void **buffer_p, size_t *len_p, git_object_t *type_p, - git_odb_backend *backend, const git_oid *oid) -{ - const fake_object *obj; - fake_backend *fake; - int error; - - fake = (fake_backend *)backend; - - fake->read_calls++; - - if ((error = search_object(&obj, fake, oid, GIT_OID_HEXSZ)) < 0) - return error; - - *len_p = strlen(obj->content); - *buffer_p = git__strdup(obj->content); - *type_p = GIT_OBJECT_BLOB; - - return 0; -} - -static int fake_backend__read_header( - size_t *len_p, git_object_t *type_p, - git_odb_backend *backend, const git_oid *oid) -{ - const fake_object *obj; - fake_backend *fake; - int error; - - fake = (fake_backend *)backend; - - fake->read_header_calls++; - - if ((error = search_object(&obj, fake, oid, GIT_OID_HEXSZ)) < 0) - return error; - - *len_p = strlen(obj->content); - *type_p = GIT_OBJECT_BLOB; - - return 0; -} - -static int fake_backend__read_prefix( - git_oid *out_oid, void **buffer_p, size_t *len_p, git_object_t *type_p, - git_odb_backend *backend, const git_oid *short_oid, size_t len) -{ - const fake_object *obj; - fake_backend *fake; - int error; - - fake = (fake_backend *)backend; - - fake->read_prefix_calls++; - - if ((error = search_object(&obj, fake, short_oid, len)) < 0) - return error; - - git_oid_fromstr(out_oid, obj->oid); - *len_p = strlen(obj->content); - *buffer_p = git__strdup(obj->content); - *type_p = GIT_OBJECT_BLOB; - - return 0; -} - -static int fake_backend__refresh(git_odb_backend *backend) -{ - fake_backend *fake; - - fake = (fake_backend *)backend; - - fake->refresh_calls++; - - return 0; -} - - -static void fake_backend__free(git_odb_backend *_backend) -{ - fake_backend *backend; - - backend = (fake_backend *)_backend; - - git__free(backend); -} - -int build_fake_backend( - git_odb_backend **out, - const fake_object *objects, - bool support_refresh) -{ - fake_backend *backend; - - backend = git__calloc(1, sizeof(fake_backend)); - GIT_ERROR_CHECK_ALLOC(backend); - - backend->parent.version = GIT_ODB_BACKEND_VERSION; - - backend->objects = objects; - - backend->parent.read = fake_backend__read; - backend->parent.read_prefix = fake_backend__read_prefix; - backend->parent.read_header = fake_backend__read_header; - backend->parent.refresh = support_refresh ? fake_backend__refresh : NULL; - backend->parent.exists = fake_backend__exists; - backend->parent.exists_prefix = fake_backend__exists_prefix; - backend->parent.free = &fake_backend__free; - - *out = (git_odb_backend *)backend; - - return 0; -} diff --git a/tests/odb/backend/backend_helpers.h b/tests/odb/backend/backend_helpers.h deleted file mode 100644 index 32d7a8bf0..000000000 --- a/tests/odb/backend/backend_helpers.h +++ /dev/null @@ -1,24 +0,0 @@ -#include "git2/sys/odb_backend.h" - -typedef struct { - const char *oid; - const char *content; -} fake_object; - -typedef struct { - git_odb_backend parent; - - int exists_calls; - int exists_prefix_calls; - int read_calls; - int read_header_calls; - int read_prefix_calls; - int refresh_calls; - - const fake_object *objects; -} fake_backend; - -int build_fake_backend( - git_odb_backend **out, - const fake_object *objects, - bool support_refresh); diff --git a/tests/odb/backend/mempack.c b/tests/odb/backend/mempack.c deleted file mode 100644 index 2eeed51aa..000000000 --- a/tests/odb/backend/mempack.c +++ /dev/null @@ -1,60 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" -#include "backend_helpers.h" -#include "git2/sys/mempack.h" - -static git_odb *_odb; -static git_oid _oid; -static git_odb_object *_obj; -static git_repository *_repo; - -void test_odb_backend_mempack__initialize(void) -{ - git_odb_backend *backend; - - cl_git_pass(git_mempack_new(&backend)); - cl_git_pass(git_odb_new(&_odb)); - cl_git_pass(git_odb_add_backend(_odb, backend, 10)); - cl_git_pass(git_repository_wrap_odb(&_repo, _odb)); -} - -void test_odb_backend_mempack__cleanup(void) -{ - git_odb_object_free(_obj); - git_odb_free(_odb); - git_repository_free(_repo); -} - -void test_odb_backend_mempack__write_succeeds(void) -{ - const char *data = "data"; - cl_git_pass(git_odb_write(&_oid, _odb, data, strlen(data) + 1, GIT_OBJECT_BLOB)); - cl_git_pass(git_odb_read(&_obj, _odb, &_oid)); -} - -void test_odb_backend_mempack__read_of_missing_object_fails(void) -{ - cl_git_pass(git_oid_fromstr(&_oid, "f6ea0495187600e7b2288c8ac19c5886383a4633")); - cl_git_fail_with(GIT_ENOTFOUND, git_odb_read(&_obj, _odb, &_oid)); -} - -void test_odb_backend_mempack__exists_of_missing_object_fails(void) -{ - cl_git_pass(git_oid_fromstr(&_oid, "f6ea0495187600e7b2288c8ac19c5886383a4633")); - cl_assert(git_odb_exists(_odb, &_oid) == 0); -} - -void test_odb_backend_mempack__exists_with_existing_objects_succeeds(void) -{ - const char *data = "data"; - cl_git_pass(git_odb_write(&_oid, _odb, data, strlen(data) + 1, GIT_OBJECT_BLOB)); - cl_assert(git_odb_exists(_odb, &_oid) == 1); -} - -void test_odb_backend_mempack__blob_create_from_buffer_succeeds(void) -{ - const char *data = "data"; - - cl_git_pass(git_blob_create_from_buffer(&_oid, _repo, data, strlen(data) + 1)); - cl_assert(git_odb_exists(_odb, &_oid) == 1); -} diff --git a/tests/odb/backend/multiple.c b/tests/odb/backend/multiple.c deleted file mode 100644 index 5f1eacd52..000000000 --- a/tests/odb/backend/multiple.c +++ /dev/null @@ -1,121 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" -#include "backend_helpers.h" - -#define EXISTING_HASH "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" - -static git_repository *_repo; -static git_odb_object *_obj; -static fake_backend *_fake_empty; -static fake_backend *_fake_filled; - -static git_oid _existing_oid; - -static const fake_object _objects_filled[] = { - { EXISTING_HASH, "" }, - { NULL, NULL } -}; - -static const fake_object _objects_empty[] = { - { NULL, NULL } -}; - -void test_odb_backend_multiple__initialize(void) -{ - git_odb_backend *backend; - - git_oid_fromstr(&_existing_oid, EXISTING_HASH); - - _obj = NULL; - _repo = cl_git_sandbox_init("testrepo.git"); - - cl_git_pass(build_fake_backend(&backend, _objects_filled, false)); - _fake_filled = (fake_backend *)backend; - - cl_git_pass(build_fake_backend(&backend, _objects_empty, false)); - _fake_empty = (fake_backend *)backend; -} - -void test_odb_backend_multiple__cleanup(void) -{ - git_odb_object_free(_obj); - cl_git_sandbox_cleanup(); -} - -void test_odb_backend_multiple__read_with_empty_first_succeeds(void) -{ - git_odb *odb; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_filled, 10)); - cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_empty, 50)); - - cl_git_pass(git_odb_read(&_obj, odb, &_existing_oid)); - - cl_assert_equal_i(1, _fake_filled->read_calls); - cl_assert_equal_i(1, _fake_empty->read_calls); -} - -void test_odb_backend_multiple__read_with_first_matching_stops(void) -{ - git_odb *odb; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_empty, 10)); - cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_filled, 50)); - - cl_git_pass(git_odb_read(&_obj, odb, &_existing_oid)); - - cl_assert_equal_i(1, _fake_filled->read_calls); - cl_assert_equal_i(0, _fake_empty->read_calls); -} - -void test_odb_backend_multiple__read_prefix_with_first_empty_succeeds(void) -{ - git_odb *odb; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_filled, 10)); - cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_empty, 50)); - - cl_git_pass(git_odb_read_prefix(&_obj, odb, &_existing_oid, 7)); - - cl_assert_equal_i(1, _fake_filled->read_prefix_calls); - cl_assert_equal_i(1, _fake_empty->read_prefix_calls); -} - -void test_odb_backend_multiple__read_prefix_with_first_matching_reads_both(void) -{ - git_odb *odb; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_empty, -10)); - cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_filled, 50)); - - cl_git_pass(git_odb_read_prefix(&_obj, odb, &_existing_oid, 7)); - - cl_assert_equal_i(1, _fake_filled->read_prefix_calls); - cl_assert_equal_i(1, _fake_empty->read_prefix_calls); -} - -void test_odb_backend_multiple__read_prefix_with_first_matching_succeeds_without_hash_verification(void) -{ - git_odb *odb; - - git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0); - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_empty, -10)); - cl_git_pass(git_odb_add_backend(odb, (git_odb_backend *)_fake_filled, 50)); - - cl_git_pass(git_odb_read_prefix(&_obj, odb, &_existing_oid, 7)); - - /* - * Both backends should be checked as we have to check - * for collisions - */ - cl_assert_equal_i(1, _fake_filled->read_prefix_calls); - cl_assert_equal_i(1, _fake_empty->read_prefix_calls); - - git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1); -} diff --git a/tests/odb/backend/nobackend.c b/tests/odb/backend/nobackend.c deleted file mode 100644 index 7484d423b..000000000 --- a/tests/odb/backend/nobackend.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" -#include "git2/sys/repository.h" - -static git_repository *_repo; - -void test_odb_backend_nobackend__initialize(void) -{ - git_config *config; - git_odb *odb; - git_refdb *refdb; - - cl_git_pass(git_repository_new(&_repo)); - cl_git_pass(git_config_new(&config)); - cl_git_pass(git_odb_new(&odb)); - cl_git_pass(git_refdb_new(&refdb, _repo)); - - git_repository_set_config(_repo, config); - git_repository_set_odb(_repo, odb); - git_repository_set_refdb(_repo, refdb); - - /* The set increases the refcount and we don't want them anymore */ - git_config_free(config); - git_odb_free(odb); - git_refdb_free(refdb); -} - -void test_odb_backend_nobackend__cleanup(void) -{ - git_repository_free(_repo); -} - -void test_odb_backend_nobackend__write_fails_gracefully(void) -{ - git_oid id; - git_odb *odb; - const git_error *err; - - git_repository_odb(&odb, _repo); - cl_git_fail(git_odb_write(&id, odb, "Hello world!\n", 13, GIT_OBJECT_BLOB)); - - err = git_error_last(); - cl_assert_equal_s(err->message, "cannot write object - unsupported in the loaded odb backends"); - - git_odb_free(odb); -} diff --git a/tests/odb/backend/nonrefreshing.c b/tests/odb/backend/nonrefreshing.c deleted file mode 100644 index 2db10efbc..000000000 --- a/tests/odb/backend/nonrefreshing.c +++ /dev/null @@ -1,147 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" -#include "backend_helpers.h" - -static git_repository *_repo; -static fake_backend *_fake; - -#define NONEXISTING_HASH "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" -#define EXISTING_HASH "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" - -static const fake_object _objects[] = { - { EXISTING_HASH, "" }, - { NULL, NULL } -}; - -static git_oid _nonexisting_oid; -static git_oid _existing_oid; - -static void setup_repository_and_backend(void) -{ - git_odb *odb = NULL; - git_odb_backend *backend = NULL; - - _repo = cl_git_sandbox_init("testrepo.git"); - - cl_git_pass(build_fake_backend(&backend, _objects, false)); - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - cl_git_pass(git_odb_add_backend(odb, backend, 10)); - - _fake = (fake_backend *)backend; -} - -void test_odb_backend_nonrefreshing__initialize(void) -{ - git_oid_fromstr(&_nonexisting_oid, NONEXISTING_HASH); - git_oid_fromstr(&_existing_oid, EXISTING_HASH); - setup_repository_and_backend(); -} - -void test_odb_backend_nonrefreshing__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_odb_backend_nonrefreshing__exists_is_invoked_once_on_failure(void) -{ - git_odb *odb; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - cl_assert_equal_b(false, git_odb_exists(odb, &_nonexisting_oid)); - - cl_assert_equal_i(1, _fake->exists_calls); -} - -void test_odb_backend_nonrefreshing__read_is_invoked_once_on_failure(void) -{ - git_object *obj; - - cl_git_fail_with( - git_object_lookup(&obj, _repo, &_nonexisting_oid, GIT_OBJECT_ANY), - GIT_ENOTFOUND); - - cl_assert_equal_i(1, _fake->read_calls); -} - -void test_odb_backend_nonrefreshing__readprefix_is_invoked_once_on_failure(void) -{ - git_object *obj; - - cl_git_fail_with( - git_object_lookup_prefix(&obj, _repo, &_nonexisting_oid, 7, GIT_OBJECT_ANY), - GIT_ENOTFOUND); - - cl_assert_equal_i(1, _fake->read_prefix_calls); -} - -void test_odb_backend_nonrefreshing__readheader_is_invoked_once_on_failure(void) -{ - git_odb *odb; - size_t len; - git_object_t type; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - - cl_git_fail_with( - git_odb_read_header(&len, &type, odb, &_nonexisting_oid), - GIT_ENOTFOUND); - - cl_assert_equal_i(1, _fake->read_header_calls); -} - -void test_odb_backend_nonrefreshing__exists_is_invoked_once_on_success(void) -{ - git_odb *odb; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - cl_assert_equal_b(true, git_odb_exists(odb, &_existing_oid)); - - cl_assert_equal_i(1, _fake->exists_calls); -} - -void test_odb_backend_nonrefreshing__read_is_invoked_once_on_success(void) -{ - git_object *obj; - - cl_git_pass(git_object_lookup(&obj, _repo, &_existing_oid, GIT_OBJECT_ANY)); - - cl_assert_equal_i(1, _fake->read_calls); - - git_object_free(obj); -} - -void test_odb_backend_nonrefreshing__readprefix_is_invoked_once_on_success(void) -{ - git_object *obj; - - cl_git_pass(git_object_lookup_prefix(&obj, _repo, &_existing_oid, 7, GIT_OBJECT_ANY)); - - cl_assert_equal_i(1, _fake->read_prefix_calls); - - git_object_free(obj); -} - -void test_odb_backend_nonrefreshing__readheader_is_invoked_once_on_success(void) -{ - git_odb *odb; - size_t len; - git_object_t type; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - - cl_git_pass(git_odb_read_header(&len, &type, odb, &_existing_oid)); - - cl_assert_equal_i(1, _fake->read_header_calls); -} - -void test_odb_backend_nonrefreshing__read_is_invoked_once_when_revparsing_a_full_oid(void) -{ - git_object *obj; - - cl_git_fail_with( - git_revparse_single(&obj, _repo, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), - GIT_ENOTFOUND); - - cl_assert_equal_i(1, _fake->read_calls); -} diff --git a/tests/odb/backend/refreshing.c b/tests/odb/backend/refreshing.c deleted file mode 100644 index 9e49298a8..000000000 --- a/tests/odb/backend/refreshing.c +++ /dev/null @@ -1,176 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" -#include "backend_helpers.h" - -static git_repository *_repo; -static fake_backend *_fake; - -#define NONEXISTING_HASH "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" -#define EXISTING_HASH "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" - -static const fake_object _objects[] = { - { EXISTING_HASH, "" }, - { NULL, NULL } -}; - -static git_oid _nonexisting_oid; -static git_oid _existing_oid; - -static void setup_repository_and_backend(void) -{ - git_odb *odb = NULL; - git_odb_backend *backend = NULL; - - _repo = cl_git_sandbox_init("testrepo.git"); - - cl_git_pass(build_fake_backend(&backend, _objects, true)); - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - cl_git_pass(git_odb_add_backend(odb, backend, 10)); - - _fake = (fake_backend *)backend; -} - -void test_odb_backend_refreshing__initialize(void) -{ - git_oid_fromstr(&_nonexisting_oid, NONEXISTING_HASH); - git_oid_fromstr(&_existing_oid, EXISTING_HASH); - setup_repository_and_backend(); -} - -void test_odb_backend_refreshing__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_odb_backend_refreshing__exists_is_invoked_twice_on_failure(void) -{ - git_odb *odb; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - cl_assert_equal_b(false, git_odb_exists(odb, &_nonexisting_oid)); - - cl_assert_equal_i(2, _fake->exists_calls); - cl_assert_equal_i(1, _fake->refresh_calls); -} - -void test_odb_backend_refreshing__read_is_invoked_twice_on_failure(void) -{ - git_object *obj; - - cl_git_fail_with( - git_object_lookup(&obj, _repo, &_nonexisting_oid, GIT_OBJECT_ANY), - GIT_ENOTFOUND); - - cl_assert_equal_i(2, _fake->read_calls); - cl_assert_equal_i(1, _fake->refresh_calls); -} - -void test_odb_backend_refreshing__readprefix_is_invoked_twice_on_failure(void) -{ - git_object *obj; - - cl_git_fail_with( - git_object_lookup_prefix(&obj, _repo, &_nonexisting_oid, 7, GIT_OBJECT_ANY), - GIT_ENOTFOUND); - - cl_assert_equal_i(2, _fake->read_prefix_calls); - cl_assert_equal_i(1, _fake->refresh_calls); -} - -void test_odb_backend_refreshing__readheader_is_invoked_twice_on_failure(void) -{ - git_odb *odb; - size_t len; - git_object_t type; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - - cl_git_fail_with( - git_odb_read_header(&len, &type, odb, &_nonexisting_oid), - GIT_ENOTFOUND); - - cl_assert_equal_i(2, _fake->read_header_calls); - cl_assert_equal_i(1, _fake->refresh_calls); -} - -void test_odb_backend_refreshing__exists_is_invoked_once_on_success(void) -{ - git_odb *odb; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - cl_assert_equal_b(true, git_odb_exists(odb, &_existing_oid)); - - cl_assert_equal_i(1, _fake->exists_calls); - cl_assert_equal_i(0, _fake->refresh_calls); -} - -void test_odb_backend_refreshing__read_is_invoked_once_on_success(void) -{ - git_object *obj; - - cl_git_pass(git_object_lookup(&obj, _repo, &_existing_oid, GIT_OBJECT_ANY)); - - cl_assert_equal_i(1, _fake->read_calls); - cl_assert_equal_i(0, _fake->refresh_calls); - - git_object_free(obj); -} - -void test_odb_backend_refreshing__readprefix_is_invoked_once_on_success(void) -{ - git_object *obj; - - cl_git_pass(git_object_lookup_prefix(&obj, _repo, &_existing_oid, 7, GIT_OBJECT_ANY)); - - cl_assert_equal_i(1, _fake->read_prefix_calls); - cl_assert_equal_i(0, _fake->refresh_calls); - - git_object_free(obj); -} - -void test_odb_backend_refreshing__readheader_is_invoked_once_on_success(void) -{ - git_odb *odb; - size_t len; - git_object_t type; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - - cl_git_pass(git_odb_read_header(&len, &type, odb, &_existing_oid)); - - cl_assert_equal_i(1, _fake->read_header_calls); - cl_assert_equal_i(0, _fake->refresh_calls); -} - -void test_odb_backend_refreshing__read_is_invoked_twice_when_revparsing_a_full_oid(void) -{ - git_object *obj; - - cl_git_fail_with( - git_revparse_single(&obj, _repo, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), - GIT_ENOTFOUND); - - cl_assert_equal_i(2, _fake->read_calls); - cl_assert_equal_i(1, _fake->refresh_calls); -} - -void test_odb_backend_refreshing__refresh_is_invoked(void) -{ - git_odb *odb; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - cl_assert_equal_i(0, git_odb_refresh(odb)); - - cl_assert_equal_i(1, _fake->refresh_calls); -} - -void test_odb_backend_refreshing__refresh_suppressed_with_no_refresh(void) -{ - git_odb *odb; - - cl_git_pass(git_repository_odb__weakptr(&odb, _repo)); - cl_assert_equal_b(false, git_odb_exists_ext(odb, &_nonexisting_oid, GIT_ODB_LOOKUP_NO_REFRESH)); - - cl_assert_equal_i(0, _fake->refresh_calls); -} diff --git a/tests/odb/backend/simple.c b/tests/odb/backend/simple.c deleted file mode 100644 index 6c9293ac0..000000000 --- a/tests/odb/backend/simple.c +++ /dev/null @@ -1,250 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" -#include "backend_helpers.h" - -#define EMPTY_HASH "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" - -static git_repository *_repo; -static git_odb *_odb; -static git_odb_object *_obj; -static git_oid _oid; - -static void setup_backend(const fake_object *objs) -{ - git_odb_backend *backend; - - cl_git_pass(build_fake_backend(&backend, objs, false)); - - cl_git_pass(git_repository_odb__weakptr(&_odb, _repo)); - cl_git_pass(git_odb_add_backend(_odb, backend, 10)); -} - -static void assert_object_contains(git_odb_object *obj, const char *expected) -{ - const char *actual = (const char *) git_odb_object_data(obj); - - cl_assert_equal_s(actual, expected); -} - -void test_odb_backend_simple__initialize(void) -{ - _repo = cl_git_sandbox_init("testrepo.git"); - _odb = NULL; - _obj = NULL; -} - -void test_odb_backend_simple__cleanup(void) -{ - git_odb_object_free(_obj); - cl_git_sandbox_cleanup(); - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1)); -} - -void test_odb_backend_simple__read_of_object_succeeds(void) -{ - const fake_object objs[] = { - { "f6ea0495187600e7b2288c8ac19c5886383a4632", "foobar" }, - { NULL, NULL } - }; - - setup_backend(objs); - - cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); - cl_git_pass(git_odb_read(&_obj, _odb, &_oid)); - - assert_object_contains(_obj, objs[0].content); -} - -void test_odb_backend_simple__read_of_nonexisting_object_fails(void) -{ - const fake_object objs[] = { - { "f6ea0495187600e7b2288c8ac19c5886383a4632", "foobar" }, - { NULL, NULL } - }; - - setup_backend(objs); - - cl_git_pass(git_oid_fromstr(&_oid, "f6ea0495187600e7b2288c8ac19c5886383a4633")); - cl_git_fail_with(GIT_ENOTFOUND, git_odb_read(&_obj, _odb, &_oid)); -} - -void test_odb_backend_simple__read_with_hash_mismatch_fails(void) -{ - const fake_object objs[] = { - { "1234567890123456789012345678901234567890", "nonmatching content" }, - { NULL, NULL } - }; - - setup_backend(objs); - - cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); - cl_git_fail_with(GIT_EMISMATCH, git_odb_read(&_obj, _odb, &_oid)); -} - -void test_odb_backend_simple__read_with_hash_mismatch_succeeds_without_verification(void) -{ - const fake_object objs[] = { - { "1234567890123456789012345678901234567890", "nonmatching content" }, - { NULL, NULL } - }; - - setup_backend(objs); - cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); - - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)); - cl_git_pass(git_odb_read(&_obj, _odb, &_oid)); - - assert_object_contains(_obj, objs[0].content); -} - -void test_odb_backend_simple__read_prefix_succeeds(void) -{ - const fake_object objs[] = { - { "f6ea0495187600e7b2288c8ac19c5886383a4632", "foobar" }, - { NULL, NULL } - }; - - setup_backend(objs); - - cl_git_pass(git_oid_fromstr(&_oid, "f6ea0495187600e7b2288c8ac19c5886383a4632")); - cl_git_pass(git_odb_read(&_obj, _odb, &_oid)); - - assert_object_contains(_obj, objs[0].content); -} - -void test_odb_backend_simple__read_prefix_of_nonexisting_object_fails(void) -{ - const fake_object objs[] = { - { "f6ea0495187600e7b2288c8ac19c5886383a4632", "foobar" }, - { NULL, NULL } - }; - char *hash = "f6ea0495187600e8"; - - setup_backend(objs); - - cl_git_pass(git_oid_fromstrn(&_oid, hash, strlen(hash))); - cl_git_fail_with(GIT_ENOTFOUND, git_odb_read(&_obj, _odb, &_oid)); -} - -void test_odb_backend_simple__read_with_ambiguous_prefix_fails(void) -{ - const fake_object objs[] = { - { "1234567890111111111111111111111111111111", "first content" }, - { "1234567890222222222222222222222222222222", "second content" }, - { NULL, NULL } - }; - - setup_backend(objs); - - cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); - cl_git_fail_with(GIT_EAMBIGUOUS, git_odb_read_prefix(&_obj, _odb, &_oid, 7)); -} - -void test_odb_backend_simple__read_with_highly_ambiguous_prefix(void) -{ - const fake_object objs[] = { - { "1234567890111111111111111111111111111111", "first content" }, - { "1234567890111111111111111111111111111112", "second content" }, - { NULL, NULL } - }; - - setup_backend(objs); - - cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)); - cl_git_fail_with(GIT_EAMBIGUOUS, git_odb_read_prefix(&_obj, _odb, &_oid, 39)); - cl_git_pass(git_odb_read_prefix(&_obj, _odb, &_oid, 40)); - assert_object_contains(_obj, objs[0].content); -} - -void test_odb_backend_simple__exists_succeeds(void) -{ - const fake_object objs[] = { - { "f6ea0495187600e7b2288c8ac19c5886383a4632", "foobar" }, - { NULL, NULL } - }; - - setup_backend(objs); - - cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); - cl_assert(git_odb_exists(_odb, &_oid)); -} - -void test_odb_backend_simple__exists_fails_for_nonexisting_object(void) -{ - const fake_object objs[] = { - { "f6ea0495187600e7b2288c8ac19c5886383a4632", "foobar" }, - { NULL, NULL } - }; - - setup_backend(objs); - - cl_git_pass(git_oid_fromstr(&_oid, "f6ea0495187600e7b2288c8ac19c5886383a4633")); - cl_assert(git_odb_exists(_odb, &_oid) == 0); -} - -void test_odb_backend_simple__exists_prefix_succeeds(void) -{ - const fake_object objs[] = { - { "1234567890111111111111111111111111111111", "first content" }, - { "1234567890222222222222222222222222222222", "second content" }, - { NULL, NULL } - }; - git_oid found; - - setup_backend(objs); - - cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); - cl_git_pass(git_odb_exists_prefix(&found, _odb, &_oid, 12)); - cl_assert(git_oid_equal(&found, &_oid)); -} - -void test_odb_backend_simple__exists_with_ambiguous_prefix_fails(void) -{ - const fake_object objs[] = { - { "1234567890111111111111111111111111111111", "first content" }, - { "1234567890222222222222222222222222222222", "second content" }, - { NULL, NULL } - }; - - setup_backend(objs); - - cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); - cl_git_fail_with(GIT_EAMBIGUOUS, git_odb_exists_prefix(NULL, _odb, &_oid, 7)); -} - -void test_odb_backend_simple__exists_with_highly_ambiguous_prefix(void) -{ - const fake_object objs[] = { - { "1234567890111111111111111111111111111111", "first content" }, - { "1234567890111111111111111111111111111112", "second content" }, - { NULL, NULL } - }; - git_oid found; - - setup_backend(objs); - - cl_git_pass(git_oid_fromstr(&_oid, objs[0].oid)); - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)); - cl_git_fail_with(GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &_oid, 39)); - cl_git_pass(git_odb_exists_prefix(&found, _odb, &_oid, 40)); - cl_assert(git_oid_equal(&found, &_oid)); -} - -void test_odb_backend_simple__null_oid_is_ignored(void) -{ - const fake_object objs[] = { - { "0000000000000000000000000000000000000000", "null oid content" }, - { NULL, NULL } - }; - git_oid null_oid = {{0}}; - git_odb_object *obj; - - setup_backend(objs); - - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)); - cl_assert(!git_odb_exists(_odb, &null_oid)); - - cl_git_fail_with(GIT_ENOTFOUND, git_odb_read(&obj, _odb, &null_oid)); - cl_assert(git_error_last() && strstr(git_error_last()->message, "null OID")); -} diff --git a/tests/odb/emptyobjects.c b/tests/odb/emptyobjects.c deleted file mode 100644 index e3ec62d3f..000000000 --- a/tests/odb/emptyobjects.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "clar_libgit2.h" -#include "odb.h" -#include "filebuf.h" - -#define TEST_REPO_PATH "redundant.git" - -git_repository *g_repo; -git_odb *g_odb; - -void test_odb_emptyobjects__initialize(void) -{ - g_repo = cl_git_sandbox_init(TEST_REPO_PATH); - cl_git_pass(git_repository_odb(&g_odb, g_repo)); -} - -void test_odb_emptyobjects__cleanup(void) -{ - git_odb_free(g_odb); - cl_git_sandbox_cleanup(); -} - -void test_odb_emptyobjects__blob_notfound(void) -{ - git_oid id, written_id; - git_blob *blob; - - cl_git_pass(git_oid_fromstr(&id, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")); - cl_git_fail_with(GIT_ENOTFOUND, git_blob_lookup(&blob, g_repo, &id)); - - cl_git_pass(git_odb_write(&written_id, g_odb, "", 0, GIT_OBJECT_BLOB)); - cl_assert(git_fs_path_exists(TEST_REPO_PATH "/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391")); -} - -void test_odb_emptyobjects__read_tree(void) -{ - git_oid id; - git_tree *tree; - - cl_git_pass(git_oid_fromstr(&id, "4b825dc642cb6eb9a060e54bf8d69288fbee4904")); - cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); - cl_assert_equal_i(GIT_OBJECT_TREE, git_object_type((git_object *) tree)); - cl_assert_equal_i(0, git_tree_entrycount(tree)); - cl_assert_equal_p(NULL, git_tree_entry_byname(tree, "foo")); - git_tree_free(tree); -} - -void test_odb_emptyobjects__read_tree_odb(void) -{ - git_oid id; - git_odb_object *tree_odb; - - cl_git_pass(git_oid_fromstr(&id, "4b825dc642cb6eb9a060e54bf8d69288fbee4904")); - cl_git_pass(git_odb_read(&tree_odb, g_odb, &id)); - cl_assert(git_odb_object_data(tree_odb)); - cl_assert_equal_s("", git_odb_object_data(tree_odb)); - cl_assert_equal_i(0, git_odb_object_size(tree_odb)); - git_odb_object_free(tree_odb); -} diff --git a/tests/odb/foreach.c b/tests/odb/foreach.c deleted file mode 100644 index c2a448363..000000000 --- a/tests/odb/foreach.c +++ /dev/null @@ -1,140 +0,0 @@ -#include "clar_libgit2.h" -#include "odb.h" -#include "git2/odb_backend.h" -#include "pack.h" - -static git_odb *_odb; -static git_repository *_repo; - -void test_odb_foreach__cleanup(void) -{ - git_odb_free(_odb); - git_repository_free(_repo); - - _odb = NULL; - _repo = NULL; -} - -static int foreach_cb(const git_oid *oid, void *data) -{ - int *nobj = data; - (*nobj)++; - - GIT_UNUSED(oid); - - return 0; -} - -/* - * $ git --git-dir tests/resources/testrepo.git count-objects --verbose - * count: 60 - * size: 240 - * in-pack: 1640 - * packs: 3 - * size-pack: 425 - * prune-packable: 0 - * garbage: 0 - */ -void test_odb_foreach__foreach(void) -{ - int nobj = 0; - - cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); - git_repository_odb(&_odb, _repo); - - cl_git_pass(git_odb_foreach(_odb, foreach_cb, &nobj)); - cl_assert_equal_i(60 + 1640, nobj); /* count + in-pack */ -} - -void test_odb_foreach__one_pack(void) -{ - git_odb_backend *backend = NULL; - int nobj = 0; - - cl_git_pass(git_odb_new(&_odb)); - cl_git_pass(git_odb_backend_one_pack(&backend, cl_fixture("testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx"))); - cl_git_pass(git_odb_add_backend(_odb, backend, 1)); - _repo = NULL; - - cl_git_pass(git_odb_foreach(_odb, foreach_cb, &nobj)); - cl_assert(nobj == 1628); -} - -static int foreach_stop_cb(const git_oid *oid, void *data) -{ - int *nobj = data; - (*nobj)++; - - GIT_UNUSED(oid); - - return (*nobj == 1000) ? -321 : 0; -} - -static int foreach_stop_first_cb(const git_oid *oid, void *data) -{ - int *nobj = data; - (*nobj)++; - - GIT_UNUSED(oid); - - return -123; -} - -static int foreach_stop_cb_positive_ret(const git_oid *oid, void *data) -{ - int *nobj = data; - (*nobj)++; - - GIT_UNUSED(oid); - - return (*nobj == 1000) ? 321 : 0; -} - -void test_odb_foreach__interrupt_foreach(void) -{ - int nobj = 0; - git_oid id; - - cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); - git_repository_odb(&_odb, _repo); - - cl_assert_equal_i(-321, git_odb_foreach(_odb, foreach_stop_cb, &nobj)); - cl_assert(nobj == 1000); - - nobj = 0; - - cl_assert_equal_i(321, git_odb_foreach(_odb, foreach_stop_cb_positive_ret, &nobj)); - cl_assert(nobj == 1000); - - git_odb_free(_odb); - git_repository_free(_repo); - - cl_git_pass(git_repository_init(&_repo, "onlyloose.git", true)); - git_repository_odb(&_odb, _repo); - - cl_git_pass(git_odb_write(&id, _odb, "", 0, GIT_OBJECT_BLOB)); - cl_assert_equal_i(-123, git_odb_foreach(_odb, foreach_stop_first_cb, &nobj)); -} - -void test_odb_foreach__files_in_objects_dir(void) -{ - git_repository *repo; - git_odb *odb; - git_str buf = GIT_STR_INIT; - int nobj = 0; - - cl_fixture_sandbox("testrepo.git"); - cl_git_pass(git_repository_open(&repo, "testrepo.git")); - - cl_git_pass(git_str_joinpath(&buf, git_repository_path(repo), "objects/somefile")); - cl_git_mkfile(buf.ptr, ""); - git_str_dispose(&buf); - - cl_git_pass(git_repository_odb(&odb, repo)); - cl_git_pass(git_odb_foreach(odb, foreach_cb, &nobj)); - cl_assert_equal_i(60 + 1640, nobj); /* count + in-pack */ - - git_odb_free(odb); - git_repository_free(repo); - cl_fixture_cleanup("testrepo.git"); -} diff --git a/tests/odb/freshen.c b/tests/odb/freshen.c deleted file mode 100644 index 2396e3774..000000000 --- a/tests/odb/freshen.c +++ /dev/null @@ -1,183 +0,0 @@ -#include "clar_libgit2.h" -#include "odb.h" -#include "posix.h" - -static git_repository *repo; -static git_odb *odb; - -void test_odb_freshen__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_odb(&odb, repo)); -} - -void test_odb_freshen__cleanup(void) -{ - git_odb_free(odb); - cl_git_sandbox_cleanup(); -} - -static void set_time_wayback(struct stat *out, const char *fn) -{ - git_str fullpath = GIT_STR_INIT; - struct p_timeval old[2]; - - old[0].tv_sec = 1234567890; - old[0].tv_usec = 0; - old[1].tv_sec = 1234567890; - old[1].tv_usec = 0; - - git_str_joinpath(&fullpath, "testrepo.git/objects", fn); - - cl_must_pass(p_utimes(git_str_cstr(&fullpath), old)); - cl_must_pass(p_lstat(git_str_cstr(&fullpath), out)); - git_str_dispose(&fullpath); -} - -#define LOOSE_STR "my new file\n" -#define LOOSE_BLOB_ID "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd" -#define LOOSE_BLOB_FN "a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd" - -void test_odb_freshen__loose_blob(void) -{ - git_oid expected_id, id; - struct stat before, after; - - cl_git_pass(git_oid_fromstr(&expected_id, LOOSE_BLOB_ID)); - set_time_wayback(&before, LOOSE_BLOB_FN); - - /* make sure we freshen a blob */ - cl_git_pass(git_blob_create_from_buffer(&id, repo, LOOSE_STR, CONST_STRLEN(LOOSE_STR))); - cl_assert_equal_oid(&expected_id, &id); - cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_BLOB_FN, &after)); - - cl_assert(before.st_atime < after.st_atime); - cl_assert(before.st_mtime < after.st_mtime); -} - -#define UNIQUE_STR "doesnt exist in the odb yet\n" -#define UNIQUE_BLOB_ID "78a87d0b8878c5953b9a63015ff4e22a3d898826" -#define UNIQUE_BLOB_FN "78/a87d0b8878c5953b9a63015ff4e22a3d898826" - -void test_odb_freshen__readonly_object(void) -{ - git_oid expected_id, id; - struct stat before, after; - - cl_git_pass(git_oid_fromstr(&expected_id, UNIQUE_BLOB_ID)); - - cl_git_pass(git_blob_create_from_buffer(&id, repo, UNIQUE_STR, CONST_STRLEN(UNIQUE_STR))); - cl_assert_equal_oid(&expected_id, &id); - - set_time_wayback(&before, UNIQUE_BLOB_FN); - cl_assert((before.st_mode & S_IWUSR) == 0); - - cl_git_pass(git_blob_create_from_buffer(&id, repo, UNIQUE_STR, CONST_STRLEN(UNIQUE_STR))); - cl_assert_equal_oid(&expected_id, &id); - cl_must_pass(p_lstat("testrepo.git/objects/" UNIQUE_BLOB_FN, &after)); - - cl_assert(before.st_atime < after.st_atime); - cl_assert(before.st_mtime < after.st_mtime); -} - -#define LOOSE_TREE_ID "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162" -#define LOOSE_TREE_FN "94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162" - -void test_odb_freshen__loose_tree(void) -{ - git_oid expected_id, id; - git_tree *tree; - struct stat before, after; - - cl_git_pass(git_oid_fromstr(&expected_id, LOOSE_TREE_ID)); - set_time_wayback(&before, LOOSE_TREE_FN); - - cl_git_pass(git_tree_lookup(&tree, repo, &expected_id)); - cl_git_pass(git_tree_create_updated(&id, repo, tree, 0, NULL)); - - /* make sure we freshen a tree */ - cl_assert_equal_oid(&expected_id, &id); - cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_TREE_FN, &after)); - - cl_assert(before.st_atime < after.st_atime); - cl_assert(before.st_mtime < after.st_mtime); - - git_tree_free(tree); -} - -void test_odb_freshen__tree_during_commit(void) -{ - git_oid tree_id, parent_id, commit_id; - git_tree *tree; - git_commit *parent; - git_signature *signature; - struct stat before, after; - - cl_git_pass(git_oid_fromstr(&tree_id, LOOSE_TREE_ID)); - cl_git_pass(git_tree_lookup(&tree, repo, &tree_id)); - set_time_wayback(&before, LOOSE_TREE_FN); - - cl_git_pass(git_oid_fromstr(&parent_id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); - cl_git_pass(git_commit_lookup(&parent, repo, &parent_id)); - - cl_git_pass(git_signature_new(&signature, - "Refresher", "refresher@example.com", 1488547083, 0)); - - cl_git_pass(git_commit_create(&commit_id, repo, NULL, - signature, signature, NULL, "New commit pointing to old tree", - tree, 1, (const git_commit **)&parent)); - - /* make sure we freshen the tree the commit points to */ - cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_TREE_FN, &after)); - cl_assert(before.st_atime < after.st_atime); - cl_assert(before.st_mtime < after.st_mtime); - - git_signature_free(signature); - git_commit_free(parent); - git_tree_free(tree); -} - -#define PACKED_STR "Testing a readme.txt\n" -#define PACKED_ID "6336846bd5c88d32f93ae57d846683e61ab5c530" -#define PACKED_FN "pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack" - -void test_odb_freshen__packed_object(void) -{ - git_oid expected_id, id; - struct stat before, after; - struct p_timeval old_times[2]; - - cl_git_pass(git_oid_fromstr(&expected_id, PACKED_ID)); - - old_times[0].tv_sec = 1234567890; - old_times[0].tv_usec = 0; - old_times[1].tv_sec = 1234567890; - old_times[1].tv_usec = 0; - - /* set time to way back */ - cl_must_pass(p_utimes("testrepo.git/objects/pack/" PACKED_FN, old_times)); - cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &before)); - - /* ensure that packfile is freshened */ - cl_git_pass(git_odb_write(&id, odb, PACKED_STR, - CONST_STRLEN(PACKED_STR), GIT_OBJECT_BLOB)); - cl_assert_equal_oid(&expected_id, &id); - cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &after)); - - cl_assert(before.st_atime < after.st_atime); - cl_assert(before.st_mtime < after.st_mtime); - - memcpy(&before, &after, sizeof(struct stat)); - - /* ensure that the pack file is not freshened again immediately */ - cl_git_pass(git_odb_write(&id, odb, PACKED_STR, - CONST_STRLEN(PACKED_STR), GIT_OBJECT_BLOB)); - cl_assert_equal_oid(&expected_id, &id); - cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &after)); - - cl_assert(before.st_atime == after.st_atime); - cl_assert(before.st_atime_nsec == after.st_atime_nsec); - cl_assert(before.st_mtime == after.st_mtime); - cl_assert(before.st_mtime_nsec == after.st_mtime_nsec); -} - diff --git a/tests/odb/largefiles.c b/tests/odb/largefiles.c deleted file mode 100644 index acc786ee4..000000000 --- a/tests/odb/largefiles.c +++ /dev/null @@ -1,189 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/odb_backend.h" -#include "hash.h" -#include "odb.h" - -#define LARGEFILE_SIZE 5368709122 - -static git_repository *repo; -static git_odb *odb; - -void test_odb_largefiles__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_odb(&odb, repo)); -} - -void test_odb_largefiles__cleanup(void) -{ - git_odb_free(odb); - cl_git_sandbox_cleanup(); -} - -static void writefile(git_oid *oid) -{ - static git_odb_stream *stream; - git_str buf = GIT_STR_INIT; - size_t i; - - for (i = 0; i < 3041; i++) - cl_git_pass(git_str_puts(&buf, "Hello, world.\n")); - - cl_git_pass(git_odb_open_wstream(&stream, odb, LARGEFILE_SIZE, GIT_OBJECT_BLOB)); - for (i = 0; i < 126103; i++) - cl_git_pass(git_odb_stream_write(stream, buf.ptr, buf.size)); - - cl_git_pass(git_odb_stream_finalize_write(oid, stream)); - - git_odb_stream_free(stream); - git_str_dispose(&buf); -} - -void test_odb_largefiles__write_from_memory(void) -{ - git_oid expected, oid; - git_str buf = GIT_STR_INIT; - size_t i; - -#ifndef GIT_ARCH_64 - cl_skip(); -#endif - - if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || - !cl_is_env_set("GITTEST_INVASIVE_MEMORY") || - !cl_is_env_set("GITTEST_SLOW")) - cl_skip(); - - for (i = 0; i < (3041*126103); i++) - cl_git_pass(git_str_puts(&buf, "Hello, world.\n")); - - git_oid_fromstr(&expected, "3fb56989cca483b21ba7cb0a6edb229d10e1c26c"); - cl_git_pass(git_odb_write(&oid, odb, buf.ptr, buf.size, GIT_OBJECT_BLOB)); - - cl_assert_equal_oid(&expected, &oid); -} - -void test_odb_largefiles__streamwrite(void) -{ - git_oid expected, oid; - -#ifndef GIT_ARCH_64 - cl_skip(); -#endif - - if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || - !cl_is_env_set("GITTEST_SLOW")) - cl_skip(); - - git_oid_fromstr(&expected, "3fb56989cca483b21ba7cb0a6edb229d10e1c26c"); - writefile(&oid); - - cl_assert_equal_oid(&expected, &oid); -} - -void test_odb_largefiles__streamread(void) -{ - git_oid oid, read_oid; - git_odb_stream *stream; - char buf[10240]; - char hdr[64]; - size_t len, hdr_len, total = 0; - git_hash_ctx hash; - git_object_t type; - int ret; - -#ifndef GIT_ARCH_64 - cl_skip(); -#endif - - if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || - !cl_is_env_set("GITTEST_SLOW")) - cl_skip(); - - writefile(&oid); - - cl_git_pass(git_odb_open_rstream(&stream, &len, &type, odb, &oid)); - - cl_assert_equal_sz(LARGEFILE_SIZE, len); - cl_assert_equal_i(GIT_OBJECT_BLOB, type); - - cl_git_pass(git_hash_ctx_init(&hash, GIT_HASH_ALGORITHM_SHA1)); - cl_git_pass(git_odb__format_object_header(&hdr_len, hdr, sizeof(hdr), len, type)); - - cl_git_pass(git_hash_update(&hash, hdr, hdr_len)); - - while ((ret = git_odb_stream_read(stream, buf, 10240)) > 0) { - cl_git_pass(git_hash_update(&hash, buf, ret)); - total += ret; - } - - cl_assert_equal_sz(LARGEFILE_SIZE, total); - - git_hash_final(read_oid.id, &hash); - - cl_assert_equal_oid(&oid, &read_oid); - - git_hash_ctx_cleanup(&hash); - git_odb_stream_free(stream); -} - -void test_odb_largefiles__read_into_memory(void) -{ - git_oid oid; - git_odb_object *obj; - -#ifndef GIT_ARCH_64 - cl_skip(); -#endif - - if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || - !cl_is_env_set("GITTEST_INVASIVE_MEMORY") || - !cl_is_env_set("GITTEST_SLOW")) - cl_skip(); - - writefile(&oid); - cl_git_pass(git_odb_read(&obj, odb, &oid)); - - git_odb_object_free(obj); -} - -void test_odb_largefiles__read_into_memory_rejected_on_32bit(void) -{ - git_oid oid; - git_odb_object *obj = NULL; - -#ifdef GIT_ARCH_64 - cl_skip(); -#endif - - if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || - !cl_is_env_set("GITTEST_INVASIVE_MEMORY") || - !cl_is_env_set("GITTEST_SLOW")) - cl_skip(); - - writefile(&oid); - cl_git_fail(git_odb_read(&obj, odb, &oid)); - - git_odb_object_free(obj); -} - -void test_odb_largefiles__read_header(void) -{ - git_oid oid; - size_t len; - git_object_t type; - -#ifndef GIT_ARCH_64 - cl_skip(); -#endif - - if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE") || - !cl_is_env_set("GITTEST_SLOW")) - cl_skip(); - - writefile(&oid); - cl_git_pass(git_odb_read_header(&len, &type, odb, &oid)); - - cl_assert_equal_sz(LARGEFILE_SIZE, len); - cl_assert_equal_i(GIT_OBJECT_BLOB, type); -} diff --git a/tests/odb/loose.c b/tests/odb/loose.c deleted file mode 100644 index fe013a78c..000000000 --- a/tests/odb/loose.c +++ /dev/null @@ -1,291 +0,0 @@ -#include "clar_libgit2.h" -#include "odb.h" -#include "git2/odb_backend.h" -#include "posix.h" -#include "loose_data.h" -#include "repository.h" - -#ifdef __ANDROID_API__ -# define S_IREAD S_IRUSR -# define S_IWRITE S_IWUSR -#endif - -static void write_object_files(object_data *d) -{ - int fd; - - if (p_mkdir(d->dir, GIT_OBJECT_DIR_MODE) < 0) - cl_assert(errno == EEXIST); - - cl_assert((fd = p_creat(d->file, S_IREAD | S_IWRITE)) >= 0); - cl_must_pass(p_write(fd, d->bytes, d->blen)); - - p_close(fd); -} - -static void cmp_objects(git_rawobj *o, object_data *d) -{ - cl_assert(o->type == git_object_string2type(d->type)); - cl_assert(o->len == d->dlen); - - if (o->len > 0) - cl_assert(memcmp(o->data, d->data, o->len) == 0); -} - -static void test_read_object(object_data *data) -{ - git_oid id; - git_odb_object *obj; - git_odb *odb; - git_rawobj tmp; - - write_object_files(data); - - cl_git_pass(git_odb_open(&odb, "test-objects")); - cl_git_pass(git_oid_fromstr(&id, data->id)); - cl_git_pass(git_odb_read(&obj, odb, &id)); - - tmp.data = obj->buffer; - tmp.len = obj->cached.size; - tmp.type = obj->cached.type; - - cmp_objects(&tmp, data); - - git_odb_object_free(obj); - git_odb_free(odb); -} - -static void test_read_header(object_data *data) -{ - git_oid id; - git_odb *odb; - size_t len; - git_object_t type; - - write_object_files(data); - - cl_git_pass(git_odb_open(&odb, "test-objects")); - cl_git_pass(git_oid_fromstr(&id, data->id)); - cl_git_pass(git_odb_read_header(&len, &type, odb, &id)); - - cl_assert_equal_sz(data->dlen, len); - cl_assert_equal_i(git_object_string2type(data->type), type); - - git_odb_free(odb); -} - -static void test_readstream_object(object_data *data, size_t blocksize) -{ - git_oid id; - git_odb *odb; - git_odb_stream *stream; - git_rawobj tmp; - char buf[2048], *ptr = buf; - size_t remain; - int ret; - - write_object_files(data); - - cl_git_pass(git_odb_open(&odb, "test-objects")); - cl_git_pass(git_oid_fromstr(&id, data->id)); - cl_git_pass(git_odb_open_rstream(&stream, &tmp.len, &tmp.type, odb, &id)); - - remain = tmp.len; - - while (remain) { - cl_assert((ret = git_odb_stream_read(stream, ptr, blocksize)) >= 0); - if (ret == 0) - break; - - cl_assert(remain >= (size_t)ret); - remain -= ret; - ptr += ret; - } - - cl_assert(remain == 0); - - tmp.data = buf; - - cmp_objects(&tmp, data); - - git_odb_stream_free(stream); - git_odb_free(odb); -} - -void test_odb_loose__initialize(void) -{ - p_fsync__cnt = 0; - cl_must_pass(p_mkdir("test-objects", GIT_OBJECT_DIR_MODE)); -} - -void test_odb_loose__cleanup(void) -{ - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 0)); - cl_fixture_cleanup("test-objects"); -} - -void test_odb_loose__exists(void) -{ - git_oid id, id2; - git_odb *odb; - - write_object_files(&one); - cl_git_pass(git_odb_open(&odb, "test-objects")); - - cl_git_pass(git_oid_fromstr(&id, one.id)); - cl_assert(git_odb_exists(odb, &id)); - - cl_git_pass(git_oid_fromstrp(&id, "8b137891")); - cl_git_pass(git_odb_exists_prefix(&id2, odb, &id, 8)); - cl_assert_equal_i(0, git_oid_streq(&id2, one.id)); - - /* Test for a missing object */ - cl_git_pass(git_oid_fromstr(&id, "8b137891791fe96927ad78e64b0aad7bded08baa")); - cl_assert(!git_odb_exists(odb, &id)); - - cl_git_pass(git_oid_fromstrp(&id, "8b13789a")); - cl_assert_equal_i(GIT_ENOTFOUND, git_odb_exists_prefix(&id2, odb, &id, 8)); - - git_odb_free(odb); -} - -void test_odb_loose__simple_reads(void) -{ - test_read_object(&commit); - test_read_object(&tree); - test_read_object(&tag); - test_read_object(&zero); - test_read_object(&one); - test_read_object(&two); - test_read_object(&some); -} - -void test_odb_loose__streaming_reads(void) -{ - size_t blocksizes[] = { 1, 2, 4, 16, 99, 1024, 123456789 }; - size_t i; - - for (i = 0; i < ARRAY_SIZE(blocksizes); i++) { - test_readstream_object(&commit, blocksizes[i]); - test_readstream_object(&tree, blocksizes[i]); - test_readstream_object(&tag, blocksizes[i]); - test_readstream_object(&zero, blocksizes[i]); - test_readstream_object(&one, blocksizes[i]); - test_readstream_object(&two, blocksizes[i]); - test_readstream_object(&some, blocksizes[i]); - } -} - -void test_odb_loose__read_header(void) -{ - test_read_header(&commit); - test_read_header(&tree); - test_read_header(&tag); - test_read_header(&zero); - test_read_header(&one); - test_read_header(&two); - test_read_header(&some); -} - -static void test_write_object_permission( - mode_t dir_mode, mode_t file_mode, - mode_t expected_dir_mode, mode_t expected_file_mode) -{ - git_odb *odb; - git_odb_backend *backend; - git_oid oid; - struct stat statbuf; - mode_t mask, os_mask; - - /* Windows does not return group/user bits from stat, - * files are never executable. - */ -#ifdef GIT_WIN32 - os_mask = 0600; -#else - os_mask = 0777; -#endif - - mask = p_umask(0); - p_umask(mask); - - cl_git_pass(git_odb_new(&odb)); - cl_git_pass(git_odb_backend_loose(&backend, "test-objects", -1, 0, dir_mode, file_mode)); - cl_git_pass(git_odb_add_backend(odb, backend, 1)); - cl_git_pass(git_odb_write(&oid, odb, "Test data\n", 10, GIT_OBJECT_BLOB)); - - cl_git_pass(p_stat("test-objects/67", &statbuf)); - cl_assert_equal_i(statbuf.st_mode & os_mask, (expected_dir_mode & ~mask) & os_mask); - - cl_git_pass(p_stat("test-objects/67/b808feb36201507a77f85e6d898f0a2836e4a5", &statbuf)); - cl_assert_equal_i(statbuf.st_mode & os_mask, (expected_file_mode & ~mask) & os_mask); - - git_odb_free(odb); -} - -void test_odb_loose__permissions_standard(void) -{ - test_write_object_permission(0, 0, GIT_OBJECT_DIR_MODE, GIT_OBJECT_FILE_MODE); -} - -void test_odb_loose__permissions_readonly(void) -{ - test_write_object_permission(0777, 0444, 0777, 0444); -} - -void test_odb_loose__permissions_readwrite(void) -{ - test_write_object_permission(0777, 0666, 0777, 0666); -} - -static void write_object_to_loose_odb(int fsync) -{ - git_odb *odb; - git_odb_backend *backend; - git_oid oid; - - cl_git_pass(git_odb_new(&odb)); - cl_git_pass(git_odb_backend_loose(&backend, "test-objects", -1, fsync, 0777, 0666)); - cl_git_pass(git_odb_add_backend(odb, backend, 1)); - cl_git_pass(git_odb_write(&oid, odb, "Test data\n", 10, GIT_OBJECT_BLOB)); - git_odb_free(odb); -} - -void test_odb_loose__does_not_fsync_by_default(void) -{ - write_object_to_loose_odb(0); - cl_assert_equal_sz(0, p_fsync__cnt); -} - -void test_odb_loose__fsync_obeys_odb_option(void) -{ - write_object_to_loose_odb(1); - cl_assert(p_fsync__cnt > 0); -} - -void test_odb_loose__fsync_obeys_global_setting(void) -{ - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 1)); - write_object_to_loose_odb(0); - cl_assert(p_fsync__cnt > 0); -} - -void test_odb_loose__fsync_obeys_repo_setting(void) -{ - git_repository *repo; - git_odb *odb; - git_oid oid; - - cl_git_pass(git_repository_init(&repo, "test-objects", 1)); - cl_git_pass(git_repository_odb__weakptr(&odb, repo)); - cl_git_pass(git_odb_write(&oid, odb, "No fsync here\n", 14, GIT_OBJECT_BLOB)); - cl_assert(p_fsync__cnt == 0); - git_repository_free(repo); - - cl_git_pass(git_repository_open(&repo, "test-objects")); - cl_repo_set_bool(repo, "core.fsyncObjectFiles", true); - cl_git_pass(git_repository_odb__weakptr(&odb, repo)); - cl_git_pass(git_odb_write(&oid, odb, "Now fsync\n", 10, GIT_OBJECT_BLOB)); - cl_assert(p_fsync__cnt > 0); - git_repository_free(repo); -} diff --git a/tests/odb/loose_data.h b/tests/odb/loose_data.h deleted file mode 100644 index c10c9bc7f..000000000 --- a/tests/odb/loose_data.h +++ /dev/null @@ -1,522 +0,0 @@ -typedef struct object_data { - unsigned char *bytes; /* (compressed) bytes stored in object store */ - size_t blen; /* length of data in object store */ - char *id; /* object id (sha1) */ - char *type; /* object type */ - char *dir; /* object store (fan-out) directory name */ - char *file; /* object store filename */ - unsigned char *data; /* (uncompressed) object data */ - size_t dlen; /* length of (uncompressed) object data */ -} object_data; - -/* one == 8b137891791fe96927ad78e64b0aad7bded08bdc */ -static unsigned char one_bytes[] = { - 0x31, 0x78, 0x9c, 0xe3, 0x02, 0x00, 0x00, 0x0b, - 0x00, 0x0b, -}; - -static unsigned char one_data[] = { - 0x0a, -}; - -static object_data one = { - one_bytes, - sizeof(one_bytes), - "8b137891791fe96927ad78e64b0aad7bded08bdc", - "blob", - "test-objects/8b", - "test-objects/8b/137891791fe96927ad78e64b0aad7bded08bdc", - one_data, - sizeof(one_data), -}; - - -/* commit == 3d7f8a6af076c8c3f20071a8935cdbe8228594d1 */ -static unsigned char commit_bytes[] = { - 0x78, 0x01, 0x85, 0x50, 0xc1, 0x6a, 0xc3, 0x30, - 0x0c, 0xdd, 0xd9, 0x5f, 0xa1, 0xfb, 0x96, 0x12, - 0xbb, 0x29, 0x71, 0x46, 0x19, 0x2b, 0x3d, 0x97, - 0x1d, 0xd6, 0x7d, 0x80, 0x1d, 0xcb, 0x89, 0x21, - 0xb6, 0x82, 0xed, 0x40, 0xf3, 0xf7, 0xf3, 0x48, - 0x29, 0x3b, 0x6d, 0xd2, 0xe5, 0xbd, 0x27, 0xbd, - 0x27, 0x50, 0x4f, 0xde, 0xbb, 0x0c, 0xfb, 0x43, - 0xf3, 0x94, 0x23, 0x22, 0x18, 0x6b, 0x85, 0x51, - 0x5d, 0xad, 0xc5, 0xa1, 0x41, 0xae, 0x51, 0x4b, - 0xd9, 0x19, 0x6e, 0x4b, 0x0b, 0x29, 0x35, 0x72, - 0x59, 0xef, 0x5b, 0x29, 0x8c, 0x65, 0x6a, 0xc9, - 0x23, 0x45, 0x38, 0xc1, 0x17, 0x5c, 0x7f, 0xc0, - 0x71, 0x13, 0xde, 0xf1, 0xa6, 0xfc, 0x3c, 0xe1, - 0xae, 0x27, 0xff, 0x06, 0x5c, 0x88, 0x56, 0xf2, - 0x46, 0x74, 0x2d, 0x3c, 0xd7, 0xa5, 0x58, 0x51, - 0xcb, 0xb9, 0x8c, 0x11, 0xce, 0xf0, 0x01, 0x97, - 0x0d, 0x1e, 0x1f, 0xea, 0x3f, 0x6e, 0x76, 0x02, - 0x0a, 0x58, 0x4d, 0x2e, 0x20, 0x6c, 0x1e, 0x48, - 0x8b, 0xf7, 0x2a, 0xae, 0x8c, 0x5d, 0x47, 0x04, - 0x4d, 0x66, 0x05, 0xb2, 0x90, 0x0b, 0xbe, 0xcf, - 0x3d, 0xa6, 0xa4, 0x06, 0x7c, 0x29, 0x3c, 0x64, - 0xe5, 0x82, 0x0b, 0x03, 0xd8, 0x25, 0x96, 0x8d, - 0x08, 0x78, 0x9b, 0x27, 0x15, 0x54, 0x76, 0x14, - 0xd8, 0xdd, 0x35, 0x2f, 0x71, 0xa6, 0x84, 0x8f, - 0x90, 0x51, 0x85, 0x01, 0x13, 0xb8, 0x90, 0x23, - 0x99, 0xa5, 0x47, 0x03, 0x7a, 0xfd, 0x15, 0xbf, - 0x63, 0xec, 0xd3, 0x0d, 0x01, 0x4d, 0x45, 0xb6, - 0xd2, 0xeb, 0xeb, 0xdf, 0xef, 0x60, 0xdf, 0xef, - 0x1f, 0x78, 0x35, -}; - -static unsigned char commit_data[] = { - 0x74, 0x72, 0x65, 0x65, 0x20, 0x64, 0x66, 0x66, - 0x32, 0x64, 0x61, 0x39, 0x30, 0x62, 0x32, 0x35, - 0x34, 0x65, 0x31, 0x62, 0x65, 0x62, 0x38, 0x38, - 0x39, 0x64, 0x31, 0x66, 0x31, 0x66, 0x31, 0x32, - 0x38, 0x38, 0x62, 0x65, 0x31, 0x38, 0x30, 0x33, - 0x37, 0x38, 0x32, 0x64, 0x66, 0x0a, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x20, 0x41, 0x20, 0x55, - 0x20, 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, - 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, - 0x31, 0x34, 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, - 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x74, 0x65, 0x72, 0x20, 0x43, 0x20, - 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, - 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, - 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, - 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, - 0x30, 0x0a, 0x0a, 0x41, 0x20, 0x6f, 0x6e, 0x65, - 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x73, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x0a, 0x0a, 0x54, 0x68, - 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x6f, - 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, - 0x20, 0x66, 0x75, 0x72, 0x74, 0x68, 0x65, 0x72, - 0x20, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x70, 0x75, 0x72, 0x70, - 0x6f, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, - 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x62, 0x79, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x53, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x2d, 0x6f, 0x66, 0x2d, - 0x62, 0x79, 0x3a, 0x20, 0x41, 0x20, 0x55, 0x20, - 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, 0x61, - 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, - 0x3e, 0x0a, -}; - -static object_data commit = { - commit_bytes, - sizeof(commit_bytes), - "3d7f8a6af076c8c3f20071a8935cdbe8228594d1", - "commit", - "test-objects/3d", - "test-objects/3d/7f8a6af076c8c3f20071a8935cdbe8228594d1", - commit_data, - sizeof(commit_data), -}; - -/* tree == dff2da90b254e1beb889d1f1f1288be1803782df */ -static unsigned char tree_bytes[] = { - 0x78, 0x01, 0x2b, 0x29, 0x4a, 0x4d, 0x55, 0x30, - 0x34, 0x32, 0x63, 0x30, 0x34, 0x30, 0x30, 0x33, - 0x31, 0x51, 0xc8, 0xcf, 0x4b, 0x65, 0xe8, 0x16, - 0xae, 0x98, 0x58, 0x29, 0xff, 0x32, 0x53, 0x7d, - 0x6d, 0xc5, 0x33, 0x6f, 0xae, 0xb5, 0xd5, 0xf7, - 0x2e, 0x74, 0xdf, 0x81, 0x4a, 0x17, 0xe7, 0xe7, - 0xa6, 0x32, 0xfc, 0x6d, 0x31, 0xd8, 0xd3, 0xe6, - 0xf3, 0xe7, 0xea, 0x47, 0xbe, 0xd0, 0x09, 0x3f, - 0x96, 0xb8, 0x3f, 0x90, 0x9e, 0xa2, 0xfd, 0x0f, - 0x2a, 0x5f, 0x52, 0x9e, 0xcf, 0x50, 0x31, 0x43, - 0x52, 0x29, 0xd1, 0x5a, 0xeb, 0x77, 0x82, 0x2a, - 0x8b, 0xfe, 0xb7, 0xbd, 0xed, 0x5d, 0x07, 0x67, - 0xfa, 0xb5, 0x42, 0xa5, 0xab, 0x52, 0x8b, 0xf2, - 0x19, 0x9e, 0xcd, 0x7d, 0x34, 0x7b, 0xd3, 0xc5, - 0x6b, 0xce, 0xde, 0xdd, 0x9a, 0xeb, 0xca, 0xa3, - 0x6e, 0x1c, 0x7a, 0xd2, 0x13, 0x3c, 0x11, 0x00, - 0xe2, 0xaa, 0x38, 0x57, -}; - -static unsigned char tree_data[] = { - 0x31, 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x6f, - 0x6e, 0x65, 0x00, 0x8b, 0x13, 0x78, 0x91, 0x79, - 0x1f, 0xe9, 0x69, 0x27, 0xad, 0x78, 0xe6, 0x4b, - 0x0a, 0xad, 0x7b, 0xde, 0xd0, 0x8b, 0xdc, 0x31, - 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x73, 0x6f, - 0x6d, 0x65, 0x00, 0xfd, 0x84, 0x30, 0xbc, 0x86, - 0x4c, 0xfc, 0xd5, 0xf1, 0x0e, 0x55, 0x90, 0xf8, - 0xa4, 0x47, 0xe0, 0x1b, 0x94, 0x2b, 0xfe, 0x31, - 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x74, 0x77, - 0x6f, 0x00, 0x78, 0x98, 0x19, 0x22, 0x61, 0x3b, - 0x2a, 0xfb, 0x60, 0x25, 0x04, 0x2f, 0xf6, 0xbd, - 0x87, 0x8a, 0xc1, 0x99, 0x4e, 0x85, 0x31, 0x30, - 0x30, 0x36, 0x34, 0x34, 0x20, 0x7a, 0x65, 0x72, - 0x6f, 0x00, 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, - 0xd6, 0x43, 0x4b, 0x8b, 0x29, 0xae, 0x77, 0x5a, - 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91, -}; - -static object_data tree = { - tree_bytes, - sizeof(tree_bytes), - "dff2da90b254e1beb889d1f1f1288be1803782df", - "tree", - "test-objects/df", - "test-objects/df/f2da90b254e1beb889d1f1f1288be1803782df", - tree_data, - sizeof(tree_data), -}; - -/* tag == 09d373e1dfdc16b129ceec6dd649739911541e05 */ -static unsigned char tag_bytes[] = { - 0x78, 0x01, 0x35, 0x4e, 0xcb, 0x0a, 0xc2, 0x40, - 0x10, 0xf3, 0xbc, 0x5f, 0x31, 0x77, 0xa1, 0xec, - 0xa3, 0xed, 0x6e, 0x41, 0x44, 0xf0, 0x2c, 0x5e, - 0xfc, 0x81, 0xe9, 0x76, 0xb6, 0xad, 0xb4, 0xb4, - 0x6c, 0x07, 0xd1, 0xbf, 0x77, 0x44, 0x0d, 0x39, - 0x84, 0x10, 0x92, 0x30, 0xf6, 0x60, 0xbc, 0xdb, - 0x2d, 0xed, 0x9d, 0x22, 0x83, 0xeb, 0x7c, 0x0a, - 0x58, 0x63, 0xd2, 0xbe, 0x8e, 0x21, 0xba, 0x64, - 0xb5, 0xf6, 0x06, 0x43, 0xe3, 0xaa, 0xd8, 0xb5, - 0x14, 0xac, 0x0d, 0x55, 0x53, 0x76, 0x46, 0xf1, - 0x6b, 0x25, 0x88, 0xcb, 0x3c, 0x8f, 0xac, 0x58, - 0x3a, 0x1e, 0xba, 0xd0, 0x85, 0xd8, 0xd8, 0xf7, - 0x94, 0xe1, 0x0c, 0x57, 0xb8, 0x8c, 0xcc, 0x22, - 0x0f, 0xdf, 0x90, 0xc8, 0x13, 0x3d, 0x71, 0x5e, - 0x27, 0x2a, 0xc4, 0x39, 0x82, 0xb1, 0xd6, 0x07, - 0x53, 0xda, 0xc6, 0xc3, 0x5e, 0x0b, 0x94, 0xba, - 0x0d, 0xe3, 0x06, 0x42, 0x1e, 0x08, 0x3e, 0x95, - 0xbf, 0x4b, 0x69, 0xc9, 0x90, 0x69, 0x22, 0xdc, - 0xe8, 0xbf, 0xf2, 0x06, 0x42, 0x9a, 0x36, 0xb1, -}; - -static unsigned char tag_data[] = { - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x33, - 0x64, 0x37, 0x66, 0x38, 0x61, 0x36, 0x61, 0x66, - 0x30, 0x37, 0x36, 0x63, 0x38, 0x63, 0x33, 0x66, - 0x32, 0x30, 0x30, 0x37, 0x31, 0x61, 0x38, 0x39, - 0x33, 0x35, 0x63, 0x64, 0x62, 0x65, 0x38, 0x32, - 0x32, 0x38, 0x35, 0x39, 0x34, 0x64, 0x31, 0x0a, - 0x74, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x0a, 0x74, 0x61, 0x67, 0x20, - 0x76, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0a, 0x74, - 0x61, 0x67, 0x67, 0x65, 0x72, 0x20, 0x43, 0x20, - 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, - 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, - 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, - 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, - 0x30, 0x0a, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20, - 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, - 0x61, 0x67, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, - 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x76, 0x30, - 0x2e, 0x30, 0x2e, 0x31, 0x0a, -}; - -static object_data tag = { - tag_bytes, - sizeof(tag_bytes), - "09d373e1dfdc16b129ceec6dd649739911541e05", - "tag", - "test-objects/09", - "test-objects/09/d373e1dfdc16b129ceec6dd649739911541e05", - tag_data, - sizeof(tag_data), -}; - -/* zero == e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 */ -static unsigned char zero_bytes[] = { - 0x78, 0x01, 0x4b, 0xca, 0xc9, 0x4f, 0x52, 0x30, - 0x60, 0x00, 0x00, 0x09, 0xb0, 0x01, 0xf0, -}; - -static unsigned char zero_data[] = { - 0x00 /* dummy data */ -}; - -static object_data zero = { - zero_bytes, - sizeof(zero_bytes), - "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", - "blob", - "test-objects/e6", - "test-objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391", - zero_data, - 0, -}; - -/* two == 78981922613b2afb6025042ff6bd878ac1994e85 */ -static unsigned char two_bytes[] = { - 0x78, 0x01, 0x4b, 0xca, 0xc9, 0x4f, 0x52, 0x30, - 0x62, 0x48, 0xe4, 0x02, 0x00, 0x0e, 0x64, 0x02, - 0x5d, -}; - -static unsigned char two_data[] = { - 0x61, 0x0a, -}; - -static object_data two = { - two_bytes, - sizeof(two_bytes), - "78981922613b2afb6025042ff6bd878ac1994e85", - "blob", - "test-objects/78", - "test-objects/78/981922613b2afb6025042ff6bd878ac1994e85", - two_data, - sizeof(two_data), -}; - -/* some == fd8430bc864cfcd5f10e5590f8a447e01b942bfe */ -static unsigned char some_bytes[] = { - 0x78, 0x01, 0x7d, 0x54, 0xc1, 0x4e, 0xe3, 0x30, - 0x10, 0xdd, 0x33, 0x5f, 0x31, 0xc7, 0x5d, 0x94, - 0xa5, 0x84, 0xd5, 0x22, 0xad, 0x7a, 0x0a, 0x15, - 0x85, 0x48, 0xd0, 0x56, 0x49, 0x2a, 0xd4, 0xa3, - 0x13, 0x4f, 0x88, 0x85, 0x63, 0x47, 0xb6, 0x43, - 0xc9, 0xdf, 0xef, 0x8c, 0x69, 0x17, 0x56, 0x0b, - 0x7b, 0xaa, 0x62, 0x7b, 0xde, 0xbc, 0xf7, 0xe6, - 0x4d, 0x6b, 0x6d, 0x6b, 0x48, 0xd3, 0xcb, 0x5f, - 0x5f, 0x66, 0xa7, 0x27, 0x70, 0x0a, 0x55, 0xa7, - 0x3c, 0xb4, 0x4a, 0x23, 0xf0, 0xaf, 0x43, 0x04, - 0x6f, 0xdb, 0xb0, 0x17, 0x0e, 0xe7, 0x30, 0xd9, - 0x11, 0x1a, 0x61, 0xc0, 0xa1, 0x54, 0x3e, 0x38, - 0x55, 0x8f, 0x81, 0x9e, 0x05, 0x10, 0x46, 0xce, - 0xac, 0x83, 0xde, 0x4a, 0xd5, 0x4e, 0x0c, 0x42, - 0x67, 0xa3, 0x91, 0xe8, 0x20, 0x74, 0x08, 0x01, - 0x5d, 0xef, 0xc1, 0xb6, 0xf1, 0xe3, 0x66, 0xb5, - 0x85, 0x1b, 0x34, 0xe8, 0x84, 0x86, 0xcd, 0x58, - 0x6b, 0xd5, 0xc0, 0x9d, 0x6a, 0xd0, 0x78, 0x4c, - 0xe0, 0x19, 0x9d, 0x57, 0xd6, 0xc0, 0x45, 0xc2, - 0x18, 0xc2, 0xc3, 0xc0, 0x0f, 0x7c, 0x87, 0x12, - 0xea, 0x29, 0x56, 0x2f, 0x99, 0x4f, 0x79, 0xe0, - 0x03, 0x4b, 0x4b, 0x4d, 0x44, 0xa0, 0x92, 0x33, - 0x2a, 0xe0, 0x9a, 0xdc, 0x80, 0x90, 0x52, 0xf1, - 0x11, 0x04, 0x1b, 0x4b, 0x06, 0xea, 0xae, 0x3c, - 0xe3, 0x7a, 0x50, 0x74, 0x4a, 0x84, 0xfe, 0xc3, - 0x81, 0x41, 0xf8, 0x89, 0x18, 0x43, 0x67, 0x9d, - 0x87, 0x47, 0xf5, 0x8c, 0x51, 0xf6, 0x68, 0xb4, - 0xea, 0x55, 0x20, 0x2a, 0x6f, 0x80, 0xdc, 0x42, - 0x2b, 0xf3, 0x14, 0x2b, 0x1a, 0xdb, 0x0f, 0xe4, - 0x9a, 0x64, 0x84, 0xa3, 0x90, 0xa8, 0xf9, 0x8f, - 0x9d, 0x86, 0x9e, 0xd3, 0xab, 0x5a, 0x99, 0xc8, - 0xd9, 0xc3, 0x5e, 0x85, 0x0e, 0x2c, 0xb5, 0x73, - 0x30, 0x38, 0xfb, 0xe8, 0x44, 0xef, 0x5f, 0x95, - 0x1b, 0xc9, 0xd0, 0xef, 0x3c, 0x26, 0x32, 0x1e, - 0xff, 0x2d, 0xb6, 0x23, 0x7b, 0x3f, 0xd1, 0x3c, - 0x78, 0x1a, 0x0d, 0xcb, 0xe6, 0xf6, 0xd4, 0x44, - 0x99, 0x47, 0x1a, 0x9e, 0xed, 0x23, 0xb5, 0x91, - 0x6a, 0xdf, 0x53, 0x39, 0x03, 0xf8, 0x5a, 0xb1, - 0x0f, 0x1f, 0xce, 0x81, 0x11, 0xde, 0x01, 0x7a, - 0x90, 0x16, 0xc4, 0x30, 0xe8, 0x89, 0xed, 0x7b, - 0x65, 0x4b, 0xd7, 0x03, 0x36, 0xc1, 0xcf, 0xa1, - 0xa5, 0xb1, 0xe3, 0x8b, 0xe8, 0x07, 0x4d, 0xf3, - 0x23, 0x25, 0x13, 0x35, 0x27, 0xf5, 0x8c, 0x11, - 0xd3, 0xa0, 0x9a, 0xa8, 0xf5, 0x38, 0x7d, 0xce, - 0x55, 0xc2, 0x71, 0x79, 0x13, 0xc7, 0xa3, 0xda, - 0x77, 0x68, 0xc0, 0xd8, 0x10, 0xdd, 0x24, 0x8b, - 0x15, 0x59, 0xc5, 0x10, 0xe2, 0x20, 0x99, 0x8e, - 0xf0, 0x05, 0x9b, 0x31, 0x88, 0x5a, 0xe3, 0xd9, - 0x37, 0xba, 0xe2, 0xdb, 0xbf, 0x92, 0xfa, 0x66, - 0x16, 0x97, 0x47, 0xd9, 0x9d, 0x1d, 0x28, 0x7c, - 0x9d, 0x08, 0x1c, 0xc7, 0xbd, 0xd2, 0x1a, 0x6a, - 0x04, 0xf2, 0xa2, 0x1d, 0x75, 0x02, 0x14, 0x5d, - 0xc6, 0x78, 0xc8, 0xab, 0xdb, 0xf5, 0xb6, 0x82, - 0x6c, 0xb5, 0x83, 0x87, 0xac, 0x28, 0xb2, 0x55, - 0xb5, 0x9b, 0xc7, 0xc1, 0xb0, 0xb7, 0xf8, 0x4c, - 0xbc, 0x38, 0x0e, 0x8a, 0x04, 0x2a, 0x62, 0x41, - 0x6b, 0xe0, 0x84, 0x09, 0x13, 0xe9, 0xe1, 0xea, - 0xfb, 0xeb, 0x62, 0x71, 0x4b, 0x25, 0xd9, 0x55, - 0x7e, 0x97, 0x57, 0x3b, 0x20, 0x33, 0x96, 0x79, - 0xb5, 0xba, 0x2e, 0x4b, 0x58, 0xae, 0x0b, 0xc8, - 0x60, 0x93, 0x15, 0x55, 0xbe, 0xd8, 0xde, 0x65, - 0x05, 0x6c, 0xb6, 0xc5, 0x66, 0x5d, 0x5e, 0x93, - 0xf7, 0x25, 0x65, 0x98, 0x41, 0x29, 0x86, 0x0c, - 0xf2, 0xf1, 0x14, 0xa2, 0xb3, 0xbd, 0x75, 0x08, - 0x12, 0x83, 0x50, 0xda, 0x1f, 0x23, 0xbe, 0xa3, - 0x1d, 0xf4, 0x9d, 0x1d, 0xb5, 0x84, 0x4e, 0x50, - 0x38, 0x1d, 0x36, 0x48, 0x21, 0x95, 0xd1, 0xac, - 0x81, 0x99, 0x1d, 0xc1, 0x3f, 0x41, 0xe6, 0x9e, - 0x42, 0x5b, 0x0a, 0x48, 0xcc, 0x5f, 0xe0, 0x7d, - 0x3f, 0xc4, 0x6f, 0x0e, 0xfe, 0xc0, 0x2d, 0xfe, - 0x01, 0x2c, 0xd6, 0x9b, 0x5d, 0xbe, 0xba, 0x21, - 0xca, 0x79, 0xcb, 0xe3, 0x49, 0x60, 0xef, 0x68, - 0x05, 0x28, 0x9b, 0x8c, 0xc1, 0x12, 0x3e, 0xdb, - 0xc7, 0x04, 0x7e, 0xa6, 0x74, 0x29, 0xcc, 0x13, - 0xed, 0x07, 0x94, 0x81, 0xd6, 0x96, 0xaa, 0x97, - 0xaa, 0xa5, 0xc0, 0x2f, 0xb5, 0xb5, 0x2e, 0xe6, - 0xfc, 0xca, 0xfa, 0x60, 0x4d, 0x02, 0xf7, 0x19, - 0x9c, 0x5f, 0xa4, 0xe9, 0xf9, 0xf7, 0xf4, 0xc7, - 0x79, 0x9a, 0xc0, 0xb6, 0xcc, 0x58, 0xec, 0xec, - 0xe4, 0x37, 0x22, 0xfa, 0x8b, 0x53, -}; - -static unsigned char some_data[] = { - 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68, - 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, - 0x69, 0x73, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, - 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x3b, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, - 0x6e, 0x20, 0x72, 0x65, 0x64, 0x69, 0x73, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x69, - 0x74, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, - 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x0a, - 0x20, 0x2a, 0x20, 0x69, 0x74, 0x20, 0x75, 0x6e, - 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2c, - 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x20, 0x32, 0x2c, 0x0a, 0x20, 0x2a, 0x20, 0x61, - 0x73, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, - 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x2a, 0x0a, - 0x20, 0x2a, 0x20, 0x49, 0x6e, 0x20, 0x61, 0x64, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, - 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x47, 0x4e, 0x55, 0x20, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, 0x63, 0x65, - 0x6e, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x2a, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x73, 0x20, 0x67, 0x69, 0x76, 0x65, - 0x20, 0x79, 0x6f, 0x75, 0x20, 0x75, 0x6e, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x20, 0x70, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x69, 0x6e, - 0x6b, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x0a, 0x20, - 0x2a, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, - 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, - 0x6e, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6d, 0x62, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x74, - 0x68, 0x65, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x73, 0x2c, 0x0a, 0x20, 0x2a, - 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20, - 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65, - 0x20, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x69, - 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x6e, - 0x79, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x2a, - 0x20, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x20, - 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, - 0x65, 0x2e, 0x20, 0x20, 0x28, 0x54, 0x68, 0x65, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, - 0x20, 0x2a, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, - 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, - 0x64, 0x6f, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, - 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x74, 0x68, 0x65, - 0x72, 0x20, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, - 0x74, 0x73, 0x3b, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c, - 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x0a, 0x20, 0x2a, 0x20, 0x6d, - 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2c, - 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x73, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x6e, - 0x6f, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x65, - 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x0a, 0x20, - 0x2a, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x62, - 0x69, 0x6e, 0x65, 0x64, 0x20, 0x65, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, - 0x29, 0x0a, 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, - 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, - 0x65, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, - 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x68, 0x6f, 0x70, 0x65, 0x20, 0x74, 0x68, 0x61, - 0x74, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c, - 0x6c, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, - 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x62, 0x75, 0x74, - 0x0a, 0x20, 0x2a, 0x20, 0x57, 0x49, 0x54, 0x48, - 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20, - 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, - 0x3b, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, - 0x74, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x69, - 0x65, 0x64, 0x20, 0x77, 0x61, 0x72, 0x72, 0x61, - 0x6e, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x0a, 0x20, - 0x2a, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, - 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, - 0x59, 0x20, 0x6f, 0x72, 0x20, 0x46, 0x49, 0x54, - 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, - 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 0x49, - 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, - 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2e, 0x20, 0x20, - 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x47, 0x4e, 0x55, 0x0a, 0x20, 0x2a, 0x20, 0x47, - 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, - 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x66, 0x6f, - 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x0a, - 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x59, 0x6f, - 0x75, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, - 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x20, 0x61, - 0x20, 0x63, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x66, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, - 0x20, 0x2a, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, - 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, - 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, - 0x61, 0x6d, 0x3b, 0x20, 0x73, 0x65, 0x65, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, - 0x20, 0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47, - 0x2e, 0x20, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, - 0x74, 0x2c, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x2a, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, - 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x35, 0x31, 0x20, - 0x46, 0x72, 0x61, 0x6e, 0x6b, 0x6c, 0x69, 0x6e, - 0x20, 0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x2c, - 0x20, 0x46, 0x69, 0x66, 0x74, 0x68, 0x20, 0x46, - 0x6c, 0x6f, 0x6f, 0x72, 0x2c, 0x0a, 0x20, 0x2a, - 0x20, 0x42, 0x6f, 0x73, 0x74, 0x6f, 0x6e, 0x2c, - 0x20, 0x4d, 0x41, 0x20, 0x30, 0x32, 0x31, 0x31, - 0x30, 0x2d, 0x31, 0x33, 0x30, 0x31, 0x2c, 0x20, - 0x55, 0x53, 0x41, 0x2e, 0x0a, 0x20, 0x2a, 0x2f, - 0x0a, -}; - -static object_data some = { - some_bytes, - sizeof(some_bytes), - "fd8430bc864cfcd5f10e5590f8a447e01b942bfe", - "blob", - "test-objects/fd", - "test-objects/fd/8430bc864cfcd5f10e5590f8a447e01b942bfe", - some_data, - sizeof(some_data), -}; diff --git a/tests/odb/mixed.c b/tests/odb/mixed.c deleted file mode 100644 index 87e945c83..000000000 --- a/tests/odb/mixed.c +++ /dev/null @@ -1,286 +0,0 @@ -#include "clar_libgit2.h" -#include "odb.h" - -static git_odb *_odb; - -void test_odb_mixed__initialize(void) -{ - cl_git_pass(git_odb_open(&_odb, cl_fixture("duplicate.git/objects"))); -} - -void test_odb_mixed__cleanup(void) -{ - git_odb_free(_odb); - _odb = NULL; -} - -void test_odb_mixed__dup_oid(void) { - const char hex[] = "ce013625030ba8dba906f756967f9e9ca394464a"; - const char short_hex[] = "ce01362"; - git_oid oid; - git_odb_object *obj; - - cl_git_pass(git_oid_fromstr(&oid, hex)); - cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, GIT_OID_HEXSZ)); - git_odb_object_free(obj); - - cl_git_pass(git_odb_exists_prefix(NULL, _odb, &oid, GIT_OID_HEXSZ)); - - cl_git_pass(git_oid_fromstrn(&oid, short_hex, sizeof(short_hex) - 1)); - cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, sizeof(short_hex) - 1)); - git_odb_object_free(obj); - - cl_git_pass(git_odb_exists_prefix(NULL, _odb, &oid, sizeof(short_hex) - 1)); -} - -/* some known sha collisions of file content: - * 'aabqhq' and 'aaazvc' with prefix 'dea509d0' (+ '9' and + 'b') - * 'aaeufo' and 'aaaohs' with prefix '81b5bff5' (+ 'f' and + 'b') - * 'aafewy' and 'aaepta' with prefix '739e3c4c' - * 'aahsyn' and 'aadrjg' with prefix '0ddeaded' (+ '9' and + 'e') - */ - -void test_odb_mixed__dup_oid_prefix_0(void) { - char hex[10]; - git_oid oid, found; - git_odb_object *obj; - - /* ambiguous in the same pack file */ - - strncpy(hex, "dea509d0", sizeof(hex)); - cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); - cl_assert_equal_i( - GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); - cl_assert_equal_i( - GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &oid, strlen(hex))); - - strncpy(hex, "dea509d09", sizeof(hex)); - cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); - cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); - cl_git_pass(git_odb_exists_prefix(&found, _odb, &oid, strlen(hex))); - cl_assert_equal_oid(&found, git_odb_object_id(obj)); - git_odb_object_free(obj); - - strncpy(hex, "dea509d0b", sizeof(hex)); - cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); - cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); - git_odb_object_free(obj); - - /* ambiguous in different pack files */ - - strncpy(hex, "81b5bff5", sizeof(hex)); - cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); - cl_assert_equal_i( - GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); - cl_assert_equal_i( - GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &oid, strlen(hex))); - - strncpy(hex, "81b5bff5b", sizeof(hex)); - cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); - cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); - cl_git_pass(git_odb_exists_prefix(&found, _odb, &oid, strlen(hex))); - cl_assert_equal_oid(&found, git_odb_object_id(obj)); - git_odb_object_free(obj); - - strncpy(hex, "81b5bff5f", sizeof(hex)); - cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); - cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); - git_odb_object_free(obj); - - /* ambiguous in pack file and loose */ - - strncpy(hex, "0ddeaded", sizeof(hex)); - cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); - cl_assert_equal_i( - GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); - cl_assert_equal_i( - GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &oid, strlen(hex))); - - strncpy(hex, "0ddeaded9", sizeof(hex)); - cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); - cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); - cl_git_pass(git_odb_exists_prefix(&found, _odb, &oid, strlen(hex))); - cl_assert_equal_oid(&found, git_odb_object_id(obj)); - git_odb_object_free(obj); - - strncpy(hex, "0ddeadede", sizeof(hex)); - cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex))); - cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); - git_odb_object_free(obj); -} - -struct expand_id_test_data { - char *lookup_id; - char *expected_id; - git_object_t expected_type; -}; - -struct expand_id_test_data expand_id_test_data[] = { - /* some prefixes and their expected values */ - { "dea509d0", NULL, GIT_OBJECT_ANY }, - { "00000000", NULL, GIT_OBJECT_ANY }, - { "dea509d0", NULL, GIT_OBJECT_ANY }, - { "dea509d09", "dea509d097ce692e167dfc6a48a7a280cc5e877e", GIT_OBJECT_BLOB }, - { "dea509d0b", "dea509d0b3cb8ee0650f6ca210bc83f4678851ba", GIT_OBJECT_BLOB }, - { "ce0136250", "ce013625030ba8dba906f756967f9e9ca394464a", GIT_OBJECT_BLOB }, - { "0ddeaded", NULL, GIT_OBJECT_ANY }, - { "4d5979b", "4d5979b468252190cb572ae758aca36928e8a91e", GIT_OBJECT_TREE }, - { "0ddeaded", NULL, GIT_OBJECT_ANY }, - { "0ddeadede", "0ddeadede9e6d6ccddce0ee1e5749eed0485e5ea", GIT_OBJECT_BLOB }, - { "0ddeaded9", "0ddeaded9502971eefe1e41e34d0e536853ae20f", GIT_OBJECT_BLOB }, - { "f00b4e", NULL, GIT_OBJECT_ANY }, - - /* this OID is too short and should be ambiguous! */ - { "f00", NULL, GIT_OBJECT_ANY }, - - /* some full-length object ids */ - { "0000000000000000000000000000000000000000", NULL, GIT_OBJECT_ANY }, - { - "dea509d097ce692e167dfc6a48a7a280cc5e877e", - "dea509d097ce692e167dfc6a48a7a280cc5e877e", - GIT_OBJECT_BLOB - }, - { "f00f00f00f00f00f00f00f00f00f00f00f00f00f", NULL, GIT_OBJECT_ANY }, - { - "4d5979b468252190cb572ae758aca36928e8a91e", - "4d5979b468252190cb572ae758aca36928e8a91e", - GIT_OBJECT_TREE - }, - - /* - * ensure we're not leaking the return error code for the - * last lookup if the last object is invalid - */ - { "0ddeadedfff", NULL, GIT_OBJECT_ANY }, -}; - -static void setup_prefix_query( - git_odb_expand_id **out_ids, - size_t *out_num) -{ - git_odb_expand_id *ids; - size_t num, i; - - num = ARRAY_SIZE(expand_id_test_data); - - cl_assert((ids = git__calloc(num, sizeof(git_odb_expand_id)))); - - for (i = 0; i < num; i++) { - git_odb_expand_id *id = &ids[i]; - - size_t len = strlen(expand_id_test_data[i].lookup_id); - - git_oid_fromstrn(&id->id, expand_id_test_data[i].lookup_id, len); - id->length = (unsigned short)len; - id->type = expand_id_test_data[i].expected_type; - } - - *out_ids = ids; - *out_num = num; -} - -static void assert_found_objects(git_odb_expand_id *ids) -{ - size_t num, i; - - num = ARRAY_SIZE(expand_id_test_data); - - for (i = 0; i < num; i++) { - git_oid expected_id = {{0}}; - size_t expected_len = 0; - git_object_t expected_type = 0; - - if (expand_id_test_data[i].expected_id) { - git_oid_fromstr(&expected_id, expand_id_test_data[i].expected_id); - expected_len = GIT_OID_HEXSZ; - expected_type = expand_id_test_data[i].expected_type; - } - - cl_assert_equal_oid(&expected_id, &ids[i].id); - cl_assert_equal_i(expected_len, ids[i].length); - cl_assert_equal_i(expected_type, ids[i].type); - } -} - -static void assert_notfound_objects(git_odb_expand_id *ids) -{ - git_oid expected_id = {{0}}; - size_t num, i; - - num = ARRAY_SIZE(expand_id_test_data); - - for (i = 0; i < num; i++) { - cl_assert_equal_oid(&expected_id, &ids[i].id); - cl_assert_equal_i(0, ids[i].length); - cl_assert_equal_i(0, ids[i].type); - } -} - -void test_odb_mixed__expand_ids(void) -{ - git_odb_expand_id *ids; - size_t i, num; - - /* test looking for the actual (correct) types */ - - setup_prefix_query(&ids, &num); - cl_git_pass(git_odb_expand_ids(_odb, ids, num)); - assert_found_objects(ids); - git__free(ids); - - /* test looking for an explicit `type == 0` */ - - setup_prefix_query(&ids, &num); - - for (i = 0; i < num; i++) - ids[i].type = 0; - - cl_git_pass(git_odb_expand_ids(_odb, ids, num)); - assert_found_objects(ids); - git__free(ids); - - /* test looking for an explicit GIT_OBJECT_ANY */ - - setup_prefix_query(&ids, &num); - - for (i = 0; i < num; i++) - ids[i].type = GIT_OBJECT_ANY; - - cl_git_pass(git_odb_expand_ids(_odb, ids, num)); - assert_found_objects(ids); - git__free(ids); - - /* test looking for the completely wrong type */ - - setup_prefix_query(&ids, &num); - - for (i = 0; i < num; i++) - ids[i].type = (ids[i].type == GIT_OBJECT_BLOB) ? - GIT_OBJECT_TREE : GIT_OBJECT_BLOB; - - cl_git_pass(git_odb_expand_ids(_odb, ids, num)); - assert_notfound_objects(ids); - git__free(ids); -} - -void test_odb_mixed__expand_ids_cached(void) -{ - git_odb_expand_id *ids; - size_t i, num; - - /* test looking for the actual (correct) types after accessing the object */ - - setup_prefix_query(&ids, &num); - - for (i = 0; i < num; i++) { - git_odb_object *obj; - if (ids[i].type == GIT_OBJECT_ANY) - continue; - cl_git_pass(git_odb_read_prefix(&obj, _odb, &ids[i].id, ids[i].length)); - git_odb_object_free(obj); - } - - cl_git_pass(git_odb_expand_ids(_odb, ids, num)); - assert_found_objects(ids); - git__free(ids); -} diff --git a/tests/odb/pack_data.h b/tests/odb/pack_data.h deleted file mode 100644 index e6371beb1..000000000 --- a/tests/odb/pack_data.h +++ /dev/null @@ -1,151 +0,0 @@ - -static const char *packed_objects[] = { - "0266163a49e280c4f5ed1e08facd36a2bd716bcf", - "53fc32d17276939fc79ed05badaef2db09990016", - "6336846bd5c88d32f93ae57d846683e61ab5c530", - "6dcf9bf7541ee10456529833502442f385010c3d", - "bed08a0b30b72a9d4aed7f1af8c8ca124e8d64b9", - "e90810b8df3e80c413d903f631643c716887138d", - "fc3c3a2083e9f6f89e6bd53e9420e70d1e357c9b", - "fc58168adf502d0c0ef614c3111a7038fc8c09c8", - "fd0ec0333948dfe23265ac46be0205a436a8c3a5", - "fd8430bc864cfcd5f10e5590f8a447e01b942bfe", - "fd899f45951c15c1c5f7c34b1c864e91bd6556c6", - "fda23b974899e7e1f938619099280bfda13bdca9", - "fdbec189efb657c8325962b494875987881a356b", - "fe1ca6bd22b5d8353ce6c2f3aba80805c438a7a5", - "fe3a6a42c87ff1239370c741a265f3997add87c1", - "deb106bfd2d36ecf9f0079224c12022201a39ad1", - "dec93efc79e60f2680de3e666755d335967eec30", - "def425bf8568b9c1e20879bf5be6f9c52b7361c4", - "df48000ac4f48570054e3a71a81916357997b680", - "dfae6ed8f6dd8acc3b40a31811ea316239223559", - "dff79e27d3d2cdc09790ded80fe2ea8ff5d61034", - "e00e46abe4c542e17c8bc83d72cf5be8018d7b0e", - "e01b107b4f77f8f98645adac0206a504f2d29d7c", - "e032d863f512c47b479bd984f8b6c8061f66b7d4", - "e044baa468a1c74f9f9da36805445f6888358b49", - "e04529998989ba8ae3419538dd57969af819b241", - "e0637ddfbea67c8d7f557c709e095af8906e9176", - "e0743ad4031231e71700abdc6fdbe94f189d20e5", - "cf33ac7a3d8b2b8f6bb266518aadbf59de397608", - "cf5f7235b9c9689b133f6ea12015720b411329bd", - "cf6cccf1297284833a9a03138a1f5738fa1c6c94", - "cf7992bde17ce7a79cab5f0c1fcbe8a0108721ed", - "cfe3a027ab12506d4144ee8a35669ae8fc4b7ab1", - "cfe96f31dfad7bab49977aa1df7302f7fafcb025", - "cff54d138945ef4de384e9d2759291d0c13ea90a", - "d01f7573ac34c2f502bd1cf18cde73480c741151", - "d03f567593f346a1ca96a57f8191def098d126e3", - "d047b47aadf88501238f36f5c17dd0a50dc62087", - "d0a0d63086fae3b0682af7261df21f7d0f7f066d", - "d0a44bd6ed0be21b725a96c0891bbc79bc1a540c", - "d0d7e736e536a41bcb885005f8bf258c61cad682", - "d0e7959d4b95ffec6198df6f5a7ae259b23a5f50", - "bf2fe2acca17d13356ce802ba9dc8343f710dfb7", - "bf55f407d6d9418e51f42ea7a3a6aadf17388349", - "bf92206f8b633b88a66dca4a911777630b06fbac", - "bfaf8c42eb8842abe206179fee864cfba87e3ca9", - "bfe05675d4e8f6b59d50932add8790f1a06b10ee", - "bff8618112330763327cfa6ce6e914db84f51ddf", - "bff873e9853ed99fed52c25f7ad29f78b27dcec2", - "c01c3fae7251098d7af1b459bcd0786e81d4616d", - "c0220fca67f48b8a5d4163d53b1486224be3a198", - "c02d0b160b82ee72469c269f13de4c26a7ea09cb", - "c059510ad1b45ab58390e042d7dee1ac46703854", - "c07204a1897aeeaa3c248d29dbfa9b033baf9755", - "c073337a4dd7276931b4b3fdbc3f0040e9441793", - "0fd7e4bfba5b3a82be88d1057757ca8b2c5e6d26", - "100746511cc45c9f1ad6721c4ef5be49222fee4d", - "1088490171d9b984d68b8b9be9ca003f4eafff59", - "1093c8ff4cb78fcf5f79dbbeedcb6e824bd4e253", - "10aa3fa72afab7ee31e116ae06442fe0f7b79df2", - "10b759e734e8299aa0dca08be935d95d886127b6", - "111d5ccf0bb010c4e8d7af3eedfa12ef4c5e265b", - "11261fbff21758444d426356ff6327ee01e90752", - "112998d425717bb922ce74e8f6f0f831d8dc4510", - "2ef4e5d838b6507bd61d457cf6466662b791c5c0", - "2ef4faa0f82efa00eeac6cae9e8b2abccc8566ee", - "2f06098183b0d7be350acbe39cdbaccff2df0c4a", - "2f1c5d509ac5bffb3c62f710a1c2c542e126dfd1", - "2f205b20fc16423c42b3ba51b2ea78d7b9ff3578", - "2f9b6b6e3d9250ba09360734aa47973a993b59d1", - "30c62a2d5a8d644f1311d4f7fe3f6a788e4c8188", - "31438e245492d85fd6da4d1406eba0fbde8332a4", - "3184a3abdfea231992254929ff4e275898e5bbf6", - "3188ffdbb3a3d52e0f78f30c484533899224436e", - "32581d0093429770d044a60eb0e9cc0462bedb13", - "32679a9544d83e5403202c4d5efb61ad02492847", - "4e7e9f60b7e2049b7f5697daf133161a18ef688f", - "4e8cda27ddc8be7db875ceb0f360c37734724c6d", - "4ea481c61c59ab55169b7cbaae536ad50b49d6f0", - "4f0adcd0e61eabe06fe32be66b16559537124b7a", - "4f1355c91100d12f9e7202f91b245df0c110867c", - "4f6eadeb08b9d0d1e8b1b3eac8a34940adf29a2d", - "4f9339df943c53117a5fc8e86e2f38716ff3a668", - "4fc3874b118752e40de556b1c3e7b4a9f1737d00", - "4ff1dd0992dd6baafdb5e166be6f9f23b59bdf87", - "5018a35e0b7e2eec7ce5050baf9c7343f3f74164", - "50298f44a45eda3a29dae82dbe911b5aa176ac07", - "502acd164fb115768d723144da2e7bb5a24891bb", - "50330c02bd4fd95c9db1fcf2f97f4218e42b7226", - "5052bf355d9f8c52446561a39733a8767bf31e37", - "6f2cd729ae42988c1dd43588d3a6661ba48ad7a0", - "6f4e2c42d9138bfbf3e0f908f1308828cc6f2178", - "6f6a17db05a83620cef4572761831c20a70ba9b9", - "6faad60901e36538634f0d8b8ff3f21f83503c71", - "6fc72e46de3df0c3842dab302bbacf697a63abab", - "6fdccd49f442a7204399ca9b418f017322dbded8", - "6fe7568fc3861c334cb008fd85d57d9647249ef5", - "700f55d91d7b55665594676a4bada1f1457a0598", - "702bd70595a7b19afc48a1f784a6505be68469d4", - "7033f9ee0e52b08cb5679cd49b7b7999eaf9eaf8", - "70957110ce446c4e250f865760fb3da513cdcc92", - "8ec696a4734f16479d091bc70574d23dd9fe7443", - "8ed341c55ed4d6f4cdc8bf4f0ca18a08c93f6962", - "8edc2805f1f11b63e44bf81f4557f8b473612b69", - "8ef9060a954118a698fc10e20acdc430566a100f", - "8f0c4b543f4bb6eb1518ecfc3d4699e43108d393", - "8fac94df3035405c2e60b3799153ce7c428af6b9", - "904c0ac12b23548de524adae712241b423d765a3", - "90bbaa9a809c3a768d873a9cc7d52b4f3bf3d1b9", - "90d4d2f0fc362beabbbf76b4ffda0828229c198d", - "90f9ff6755330b685feff6c3d81782ee3592ab04", - "91822c50ebe4f9bf5bbb8308ecf9f6557062775c", - "91d973263a55708fa8255867b3202d81ef9c2868", - "af292c99c6148d772af3315a1c74e83330e7ead7", - "af3b99d5be330dbbce0b9250c3a5fb05911908cc", - "af55d0cdeb280af2db8697e5afa506e081012719", - "af795e498d411142ddb073e8ca2c5447c3295a4c", - "afadc73a392f8cc8e2cc77dd62a7433dd3bafa8c", - "affd84ed8ec7ce67612fe3c12a80f8164b101f6a", - "b0941f9c70ffe67f0387a827b338e64ecf3190f0", - "b0a3077f9ef6e093f8d9869bdb0c07095bd722cb", - "b0a8568a7614806378a54db5706ee3b06ae58693", - "b0fb7372f242233d1d35ce7d8e74d3990cbc5841", - "b10489944b9ead17427551759d180d10203e06ba", - "b196a807b323f2748ffc6b1d42cd0812d04c9a40", - "b1bb1d888f0c5e19278536d49fa77db035fac7ae" -}; - -static const char *loose_objects[] = { - "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", - "a8233120f6ad708f843d861ce2b7228ec4e3dec6", - "fd093bff70906175335656e6ce6ae05783708765", - "c47800c7266a2be04c571c04d5a6614691ea99bd", - "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd", - "8496071c1b46c854b31185ea97743be6a8774479", - "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", - "814889a078c031f61ed08ab5fa863aea9314344d", - "5b5b025afb0b4c913b4c338a42934a3863bf3644", - "1385f264afb75a56a5bec74243be9b367ba4ca08", - "f60079018b664e4e79329a7ef9559c8d9e0378d1", - "be3563ae3f795b2b4353bcce3a527ad0a4f7f644", - "75057dd4114e74cca1d750d0aee1647c903cb60a", - "fa49b077972391ad58037050f2a75f74e3671e92", - "9fd738e8f7967c078dceed8190330fc8648ee56a", - "1810dff58d8a660512d4832e740f692884338ccd", - "181037049a54a1eb5fab404658a3a250b44335d7", - "a4a7dce85cf63874e984719f4fdd239f5145052f", - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045" -}; diff --git a/tests/odb/pack_data_one.h b/tests/odb/pack_data_one.h deleted file mode 100644 index 13570ba78..000000000 --- a/tests/odb/pack_data_one.h +++ /dev/null @@ -1,19 +0,0 @@ -/* Just a few to make sure it's working, the rest is tested already */ -static const char *packed_objects_one[] = { - "9fcf811e00fa469688943a9152c16d4ee90fb9a9", - "a93f42a5b5e9de40fa645a9ff1e276a021c9542b", - "12bf5f3e3470d90db177ccf1b5e8126409377fc6", - "ed1ea164cdbe3c4b200fb4fa19861ea90eaee222", - "dfae6ed8f6dd8acc3b40a31811ea316239223559", - "aefe66d192771201e369fde830530f4475beec30", - "775e4b4c1296e9e3104f2a36ca9cf9356a130959", - "412ec4e4a6a7419bc1be00561fe474e54cb499fe", - "236e7579fed7763be77209efb8708960982f3cb3", - "09fe9364461cf60dd1c46b0e9545b1e47bb1a297", - "d76d8a6390d1cf32138d98a91b1eb7e0275a12f5", - "d0fdf2dcff2f548952eec536ccc6d266550041bc", - "a20d733a9fa79fa5b4cbb9639864f93325ec27a6", - "785d3fe8e7db5ade2c2242fecd46c32a7f4dc59f", - "4d8d0fd9cb6045075385701c3f933ec13345e9c4", - "0cfd861bd547b6520d1fc2e190e8359e0a9c9b90" -}; diff --git a/tests/odb/packed.c b/tests/odb/packed.c deleted file mode 100644 index 3d502ed6d..000000000 --- a/tests/odb/packed.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "clar_libgit2.h" -#include "odb.h" -#include "pack_data.h" - -static git_odb *_odb; - -void test_odb_packed__initialize(void) -{ - cl_git_pass(git_odb_open(&_odb, cl_fixture("testrepo.git/objects"))); -} - -void test_odb_packed__cleanup(void) -{ - git_odb_free(_odb); - _odb = NULL; -} - -void test_odb_packed__mass_read(void) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(packed_objects); ++i) { - git_oid id; - git_odb_object *obj; - - cl_git_pass(git_oid_fromstr(&id, packed_objects[i])); - cl_assert(git_odb_exists(_odb, &id) == 1); - cl_git_pass(git_odb_read(&obj, _odb, &id)); - - git_odb_object_free(obj); - } -} - -void test_odb_packed__read_header_0(void) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(packed_objects); ++i) { - git_oid id; - git_odb_object *obj; - size_t len; - git_object_t type; - - cl_git_pass(git_oid_fromstr(&id, packed_objects[i])); - - cl_git_pass(git_odb_read(&obj, _odb, &id)); - cl_git_pass(git_odb_read_header(&len, &type, _odb, &id)); - - cl_assert(obj->cached.size == len); - cl_assert(obj->cached.type == type); - - git_odb_object_free(obj); - } -} - -void test_odb_packed__read_header_1(void) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(loose_objects); ++i) { - git_oid id; - git_odb_object *obj; - size_t len; - git_object_t type; - - cl_git_pass(git_oid_fromstr(&id, loose_objects[i])); - - cl_assert(git_odb_exists(_odb, &id) == 1); - - cl_git_pass(git_odb_read(&obj, _odb, &id)); - cl_git_pass(git_odb_read_header(&len, &type, _odb, &id)); - - cl_assert(obj->cached.size == len); - cl_assert(obj->cached.type == type); - - git_odb_object_free(obj); - } -} - diff --git a/tests/odb/packed_one.c b/tests/odb/packed_one.c deleted file mode 100644 index 17cd4f760..000000000 --- a/tests/odb/packed_one.c +++ /dev/null @@ -1,60 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/odb_backend.h" - -#include "pack_data_one.h" -#include "pack.h" - -static git_odb *_odb; - -void test_odb_packed_one__initialize(void) -{ - git_odb_backend *backend = NULL; - - cl_git_pass(git_odb_new(&_odb)); - cl_git_pass(git_odb_backend_one_pack(&backend, cl_fixture("testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx"))); - cl_git_pass(git_odb_add_backend(_odb, backend, 1)); -} - -void test_odb_packed_one__cleanup(void) -{ - git_odb_free(_odb); - _odb = NULL; -} - -void test_odb_packed_one__mass_read(void) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(packed_objects_one); ++i) { - git_oid id; - git_odb_object *obj; - - cl_git_pass(git_oid_fromstr(&id, packed_objects_one[i])); - cl_assert(git_odb_exists(_odb, &id) == 1); - cl_git_pass(git_odb_read(&obj, _odb, &id)); - - git_odb_object_free(obj); - } -} - -void test_odb_packed_one__read_header_0(void) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(packed_objects_one); ++i) { - git_oid id; - git_odb_object *obj; - size_t len; - git_object_t type; - - cl_git_pass(git_oid_fromstr(&id, packed_objects_one[i])); - - cl_git_pass(git_odb_read(&obj, _odb, &id)); - cl_git_pass(git_odb_read_header(&len, &type, _odb, &id)); - - cl_assert(obj->cached.size == len); - cl_assert(obj->cached.type == type); - - git_odb_object_free(obj); - } -} diff --git a/tests/odb/sorting.c b/tests/odb/sorting.c deleted file mode 100644 index e027230fa..000000000 --- a/tests/odb/sorting.c +++ /dev/null @@ -1,99 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/odb_backend.h" -#include "odb.h" - -typedef struct { - git_odb_backend base; - size_t position; -} fake_backend; - -static git_odb_backend *new_backend(size_t position) -{ - fake_backend *b; - - b = git__calloc(1, sizeof(fake_backend)); - if (b == NULL) - return NULL; - - b->base.free = (void (*)(git_odb_backend *)) git__free; - b->base.version = GIT_ODB_BACKEND_VERSION; - b->position = position; - return (git_odb_backend *)b; -} - -static void check_backend_sorting(git_odb *odb) -{ - size_t i, max_i = git_odb_num_backends(odb); - fake_backend *internal; - - for (i = 0; i < max_i; ++i) { - cl_git_pass(git_odb_get_backend((git_odb_backend **)&internal, odb, i)); - cl_assert(internal != NULL); - cl_assert_equal_sz(i, internal->position); - } -} - -static git_odb *_odb; - -void test_odb_sorting__initialize(void) -{ - cl_git_pass(git_odb_new(&_odb)); -} - -void test_odb_sorting__cleanup(void) -{ - git_odb_free(_odb); - _odb = NULL; - - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ODB_LOOSE_PRIORITY, - GIT_ODB_DEFAULT_LOOSE_PRIORITY)); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ODB_PACKED_PRIORITY, - GIT_ODB_DEFAULT_PACKED_PRIORITY)); -} - -void test_odb_sorting__basic_backends_sorting(void) -{ - cl_git_pass(git_odb_add_backend(_odb, new_backend(0), 5)); - cl_git_pass(git_odb_add_backend(_odb, new_backend(2), 3)); - cl_git_pass(git_odb_add_backend(_odb, new_backend(1), 4)); - cl_git_pass(git_odb_add_backend(_odb, new_backend(3), 1)); - - check_backend_sorting(_odb); -} - -void test_odb_sorting__alternate_backends_sorting(void) -{ - cl_git_pass(git_odb_add_backend(_odb, new_backend(1), 5)); - cl_git_pass(git_odb_add_backend(_odb, new_backend(5), 3)); - cl_git_pass(git_odb_add_backend(_odb, new_backend(3), 4)); - cl_git_pass(git_odb_add_backend(_odb, new_backend(7), 1)); - cl_git_pass(git_odb_add_alternate(_odb, new_backend(0), 5)); - cl_git_pass(git_odb_add_alternate(_odb, new_backend(4), 3)); - cl_git_pass(git_odb_add_alternate(_odb, new_backend(2), 4)); - cl_git_pass(git_odb_add_alternate(_odb, new_backend(6), 1)); - - check_backend_sorting(_odb); -} - -void test_odb_sorting__override_default_backend_priority(void) -{ - git_odb *new_odb; - git_odb_backend *loose, *packed, *backend; - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ODB_LOOSE_PRIORITY, 5)); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ODB_PACKED_PRIORITY, 3)); - git_odb_backend_pack(&packed, "./testrepo.git/objects"); - git_odb_backend_loose(&loose, "./testrepo.git/objects", -1, 0, 0, 0); - - cl_git_pass(git_odb_open(&new_odb, cl_fixture("testrepo.git/objects"))); - cl_assert_equal_sz(2, git_odb_num_backends(new_odb)); - - cl_git_pass(git_odb_get_backend(&backend, new_odb, 0)); - cl_assert_equal_p(loose->read, backend->read); - - cl_git_pass(git_odb_get_backend(&backend, new_odb, 1)); - cl_assert_equal_p(packed->read, backend->read); - - git_odb_free(new_odb); - loose->free(loose); - packed->free(packed); -} diff --git a/tests/odb/streamwrite.c b/tests/odb/streamwrite.c deleted file mode 100644 index c174f6974..000000000 --- a/tests/odb/streamwrite.c +++ /dev/null @@ -1,56 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/odb_backend.h" - -static git_repository *repo; -static git_odb *odb; -static git_odb_stream *stream; - -void test_odb_streamwrite__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_odb(&odb, repo)); - - cl_git_pass(git_odb_open_wstream(&stream, odb, 14, GIT_OBJECT_BLOB)); - cl_assert_equal_sz(14, stream->declared_size); -} - -void test_odb_streamwrite__cleanup(void) -{ - git_odb_stream_free(stream); - git_odb_free(odb); - cl_git_sandbox_cleanup(); -} - -void test_odb_streamwrite__can_accept_chunks(void) -{ - git_oid oid; - - cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8)); - cl_assert_equal_sz(8, stream->received_bytes); - - cl_git_pass(git_odb_stream_write(stream, "deadbeef", 6)); - cl_assert_equal_sz(8 + 6, stream->received_bytes); - - cl_git_pass(git_odb_stream_finalize_write(&oid, stream)); -} - -void test_odb_streamwrite__can_detect_missing_bytes(void) -{ - git_oid oid; - - cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8)); - cl_assert_equal_sz(8, stream->received_bytes); - - cl_git_pass(git_odb_stream_write(stream, "deadbeef", 4)); - cl_assert_equal_sz(8 + 4, stream->received_bytes); - - cl_git_fail(git_odb_stream_finalize_write(&oid, stream)); -} - -void test_odb_streamwrite__can_detect_additional_bytes(void) -{ - cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8)); - cl_assert_equal_sz(8, stream->received_bytes); - - cl_git_fail(git_odb_stream_write(stream, "deadbeef", 7)); -} diff --git a/tests/online/badssl.c b/tests/online/badssl.c deleted file mode 100644 index 6735e9cdb..000000000 --- a/tests/online/badssl.c +++ /dev/null @@ -1,75 +0,0 @@ -#include "clar_libgit2.h" - -#include "git2/clone.h" - -static git_repository *g_repo; - -#ifdef GIT_HTTPS -static bool g_has_ssl = true; -#else -static bool g_has_ssl = false; -#endif - -static int cert_check_assert_invalid(git_cert *cert, int valid, const char* host, void *payload) -{ - GIT_UNUSED(cert); GIT_UNUSED(host); GIT_UNUSED(payload); - - cl_assert_equal_i(0, valid); - - return GIT_ECERTIFICATE; -} - -void test_online_badssl__expired(void) -{ - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid; - - if (!g_has_ssl) - cl_skip(); - - cl_git_fail_with(GIT_ECERTIFICATE, - git_clone(&g_repo, "https://expired.badssl.com/fake.git", "./fake", NULL)); - - cl_git_fail_with(GIT_ECERTIFICATE, - git_clone(&g_repo, "https://expired.badssl.com/fake.git", "./fake", &opts)); -} - -void test_online_badssl__wrong_host(void) -{ - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid; - - if (!g_has_ssl) - cl_skip(); - - cl_git_fail_with(GIT_ECERTIFICATE, - git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", NULL)); - cl_git_fail_with(GIT_ECERTIFICATE, - git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", &opts)); -} - -void test_online_badssl__self_signed(void) -{ - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid; - - if (!g_has_ssl) - cl_skip(); - - cl_git_fail_with(GIT_ECERTIFICATE, - git_clone(&g_repo, "https://self-signed.badssl.com/fake.git", "./fake", NULL)); - cl_git_fail_with(GIT_ECERTIFICATE, - git_clone(&g_repo, "https://self-signed.badssl.com/fake.git", "./fake", &opts)); -} - -void test_online_badssl__old_cipher(void) -{ - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid; - - if (!g_has_ssl) - cl_skip(); - - cl_git_fail(git_clone(&g_repo, "https://rc4.badssl.com/fake.git", "./fake", NULL)); - cl_git_fail(git_clone(&g_repo, "https://rc4.badssl.com/fake.git", "./fake", &opts)); -} diff --git a/tests/online/clone.c b/tests/online/clone.c deleted file mode 100644 index 9f2580bb3..000000000 --- a/tests/online/clone.c +++ /dev/null @@ -1,1004 +0,0 @@ -#include "clar_libgit2.h" - -#include "git2/clone.h" -#include "git2/cred_helpers.h" -#include "remote.h" -#include "futils.h" -#include "refs.h" - -#define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository" -#define LIVE_EMPTYREPO_URL "http://github.com/libgit2/TestEmptyRepository" -#define BB_REPO_URL "https://libgit3@bitbucket.org/libgit2/testgitrepository.git" -#define BB_REPO_URL_WITH_PASS "https://libgit3:libgit3@bitbucket.org/libgit2/testgitrepository.git" -#define BB_REPO_URL_WITH_WRONG_PASS "https://libgit3:wrong@bitbucket.org/libgit2/testgitrepository.git" -#define GOOGLESOURCE_REPO_URL "https://chromium.googlesource.com/external/github.com/sergi/go-diff" - -#define SSH_REPO_URL "ssh://github.com/libgit2/TestGitRepository" - -static git_repository *g_repo; -static git_clone_options g_options; - -static char *_remote_url = NULL; -static char *_remote_user = NULL; -static char *_remote_pass = NULL; -static char *_remote_sslnoverify = NULL; -static char *_remote_ssh_pubkey = NULL; -static char *_remote_ssh_privkey = NULL; -static char *_remote_ssh_passphrase = NULL; -static char *_remote_ssh_fingerprint = NULL; -static char *_remote_proxy_scheme = NULL; -static char *_remote_proxy_host = NULL; -static char *_remote_proxy_user = NULL; -static char *_remote_proxy_pass = NULL; -static char *_remote_proxy_selfsigned = NULL; -static char *_remote_expectcontinue = NULL; -static char *_remote_redirect_initial = NULL; -static char *_remote_redirect_subsequent = NULL; - -static int _orig_proxies_need_reset = 0; -static char *_orig_http_proxy = NULL; -static char *_orig_https_proxy = NULL; -static char *_orig_no_proxy = NULL; - -static int ssl_cert(git_cert *cert, int valid, const char *host, void *payload) -{ - GIT_UNUSED(cert); - GIT_UNUSED(host); - GIT_UNUSED(payload); - - if (_remote_sslnoverify != NULL) - valid = 1; - - return valid ? 0 : GIT_ECERTIFICATE; -} - -void test_online_clone__initialize(void) -{ - git_checkout_options dummy_opts = GIT_CHECKOUT_OPTIONS_INIT; - git_fetch_options dummy_fetch = GIT_FETCH_OPTIONS_INIT; - - g_repo = NULL; - - memset(&g_options, 0, sizeof(git_clone_options)); - g_options.version = GIT_CLONE_OPTIONS_VERSION; - g_options.checkout_opts = dummy_opts; - g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - g_options.fetch_opts = dummy_fetch; - g_options.fetch_opts.callbacks.certificate_check = ssl_cert; - - _remote_url = cl_getenv("GITTEST_REMOTE_URL"); - _remote_user = cl_getenv("GITTEST_REMOTE_USER"); - _remote_pass = cl_getenv("GITTEST_REMOTE_PASS"); - _remote_sslnoverify = cl_getenv("GITTEST_REMOTE_SSL_NOVERIFY"); - _remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY"); - _remote_ssh_privkey = cl_getenv("GITTEST_REMOTE_SSH_KEY"); - _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); - _remote_ssh_fingerprint = cl_getenv("GITTEST_REMOTE_SSH_FINGERPRINT"); - _remote_proxy_scheme = cl_getenv("GITTEST_REMOTE_PROXY_SCHEME"); - _remote_proxy_host = cl_getenv("GITTEST_REMOTE_PROXY_HOST"); - _remote_proxy_user = cl_getenv("GITTEST_REMOTE_PROXY_USER"); - _remote_proxy_pass = cl_getenv("GITTEST_REMOTE_PROXY_PASS"); - _remote_proxy_selfsigned = cl_getenv("GITTEST_REMOTE_PROXY_SELFSIGNED"); - _remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE"); - _remote_redirect_initial = cl_getenv("GITTEST_REMOTE_REDIRECT_INITIAL"); - _remote_redirect_subsequent = cl_getenv("GITTEST_REMOTE_REDIRECT_SUBSEQUENT"); - - if (_remote_expectcontinue) - git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1); - - _orig_proxies_need_reset = 0; -} - -void test_online_clone__cleanup(void) -{ - if (g_repo) { - git_repository_free(g_repo); - g_repo = NULL; - } - cl_fixture_cleanup("./foo"); - cl_fixture_cleanup("./initial"); - cl_fixture_cleanup("./subsequent"); - - git__free(_remote_url); - git__free(_remote_user); - git__free(_remote_pass); - git__free(_remote_sslnoverify); - git__free(_remote_ssh_pubkey); - git__free(_remote_ssh_privkey); - git__free(_remote_ssh_passphrase); - git__free(_remote_ssh_fingerprint); - git__free(_remote_proxy_scheme); - git__free(_remote_proxy_host); - git__free(_remote_proxy_user); - git__free(_remote_proxy_pass); - git__free(_remote_proxy_selfsigned); - git__free(_remote_expectcontinue); - git__free(_remote_redirect_initial); - git__free(_remote_redirect_subsequent); - - if (_orig_proxies_need_reset) { - cl_setenv("HTTP_PROXY", _orig_http_proxy); - cl_setenv("HTTPS_PROXY", _orig_https_proxy); - cl_setenv("NO_PROXY", _orig_no_proxy); - - git__free(_orig_http_proxy); - git__free(_orig_https_proxy); - git__free(_orig_no_proxy); - } - - git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, NULL); -} - -void test_online_clone__network_full(void) -{ - git_remote *origin; - - cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); - cl_assert(!git_repository_is_bare(g_repo)); - cl_git_pass(git_remote_lookup(&origin, g_repo, "origin")); - - cl_assert_equal_i(GIT_REMOTE_DOWNLOAD_TAGS_AUTO, origin->download_tags); - - git_remote_free(origin); -} - -void test_online_clone__network_bare(void) -{ - git_remote *origin; - - g_options.bare = true; - - cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); - cl_assert(git_repository_is_bare(g_repo)); - cl_git_pass(git_remote_lookup(&origin, g_repo, "origin")); - - git_remote_free(origin); -} - -void test_online_clone__empty_repository(void) -{ - git_reference *head; - - cl_git_pass(git_clone(&g_repo, LIVE_EMPTYREPO_URL, "./foo", &g_options)); - - cl_assert_equal_i(true, git_repository_is_empty(g_repo)); - cl_assert_equal_i(true, git_repository_head_unborn(g_repo)); - - cl_git_pass(git_reference_lookup(&head, g_repo, GIT_HEAD_FILE)); - cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head)); - cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); - - git_reference_free(head); -} - -static void checkout_progress(const char *path, size_t cur, size_t tot, void *payload) -{ - bool *was_called = (bool*)payload; - GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); - (*was_called) = true; -} - -static int fetch_progress(const git_indexer_progress *stats, void *payload) -{ - bool *was_called = (bool*)payload; - GIT_UNUSED(stats); - (*was_called) = true; - return 0; -} - -void test_online_clone__can_checkout_a_cloned_repo(void) -{ - git_str path = GIT_STR_INIT; - git_reference *head, *remote_head; - bool checkout_progress_cb_was_called = false, - fetch_progress_cb_was_called = false; - - g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - g_options.checkout_opts.progress_cb = &checkout_progress; - g_options.checkout_opts.progress_payload = &checkout_progress_cb_was_called; - g_options.fetch_opts.callbacks.transfer_progress = &fetch_progress; - g_options.fetch_opts.callbacks.payload = &fetch_progress_cb_was_called; - - cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "master.txt")); - cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&path))); - - cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); - cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head)); - cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); - - cl_git_pass(git_reference_lookup(&remote_head, g_repo, "refs/remotes/origin/HEAD")); - cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(remote_head)); - cl_assert_equal_s("refs/remotes/origin/master", git_reference_symbolic_target(remote_head)); - - cl_assert_equal_i(true, checkout_progress_cb_was_called); - cl_assert_equal_i(true, fetch_progress_cb_was_called); - - git_reference_free(remote_head); - git_reference_free(head); - git_str_dispose(&path); -} - -static int remote_mirror_cb(git_remote **out, git_repository *repo, - const char *name, const char *url, void *payload) -{ - int error; - git_remote *remote; - - GIT_UNUSED(payload); - - if ((error = git_remote_create_with_fetchspec(&remote, repo, name, url, "+refs/*:refs/*")) < 0) - return error; - - *out = remote; - return 0; -} - -void test_online_clone__clone_mirror(void) -{ - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - git_reference *head; - - bool fetch_progress_cb_was_called = false; - - opts.fetch_opts.callbacks.transfer_progress = &fetch_progress; - opts.fetch_opts.callbacks.payload = &fetch_progress_cb_was_called; - - opts.bare = true; - opts.remote_cb = remote_mirror_cb; - - cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo.git", &opts)); - - cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); - cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head)); - cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); - - cl_assert_equal_i(true, fetch_progress_cb_was_called); - - git_reference_free(head); - git_repository_free(g_repo); - g_repo = NULL; - - cl_fixture_cleanup("./foo.git"); -} - -static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *payload) -{ - int *callcount = (int*)payload; - GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b); - *callcount = *callcount + 1; - return 0; -} - -void test_online_clone__custom_remote_callbacks(void) -{ - int callcount = 0; - - g_options.fetch_opts.callbacks.update_tips = update_tips; - g_options.fetch_opts.callbacks.payload = &callcount; - - cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); - cl_assert(callcount > 0); -} - -void test_online_clone__custom_headers(void) -{ - char *empty_header = ""; - char *unnamed_header = "this is a header about nothing"; - char *newlines = "X-Custom: almost OK\n"; - char *conflict = "Accept: defined-by-git"; - char *ok = "X-Custom: this should be ok"; - - g_options.fetch_opts.custom_headers.count = 1; - - g_options.fetch_opts.custom_headers.strings = &empty_header; - cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); - - g_options.fetch_opts.custom_headers.strings = &unnamed_header; - cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); - - g_options.fetch_opts.custom_headers.strings = &newlines; - cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); - - g_options.fetch_opts.custom_headers.strings = &conflict; - cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); - - /* Finally, we got it right! */ - g_options.fetch_opts.custom_headers.strings = &ok; - cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); -} - -static int cred_failure_cb( - git_credential **cred, - const char *url, - const char *username_from_url, - unsigned int allowed_types, - void *data) -{ - GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url); - GIT_UNUSED(allowed_types); GIT_UNUSED(data); - return -172; -} - -void test_online_clone__cred_callback_failure_return_code_is_tunnelled(void) -{ - git__free(_remote_url); - git__free(_remote_user); - - _remote_url = git__strdup("https://github.com/libgit2/non-existent"); - _remote_user = git__strdup("libgit2test"); - - g_options.fetch_opts.callbacks.credentials = cred_failure_cb; - - cl_git_fail_with(-172, git_clone(&g_repo, _remote_url, "./foo", &g_options)); -} - -static int cred_count_calls_cb(git_credential **cred, const char *url, const char *user, - unsigned int allowed_types, void *data) -{ - size_t *counter = (size_t *) data; - - GIT_UNUSED(url); GIT_UNUSED(user); GIT_UNUSED(allowed_types); - - if (allowed_types == GIT_CREDENTIAL_USERNAME) - return git_credential_username_new(cred, "foo"); - - (*counter)++; - - if (*counter == 3) - return GIT_EUSER; - - return git_credential_userpass_plaintext_new(cred, "foo", "bar"); -} - -void test_online_clone__cred_callback_called_again_on_auth_failure(void) -{ - size_t counter = 0; - - git__free(_remote_url); - git__free(_remote_user); - - _remote_url = git__strdup("https://gitlab.com/libgit2/non-existent"); - _remote_user = git__strdup("libgit2test"); - - g_options.fetch_opts.callbacks.credentials = cred_count_calls_cb; - g_options.fetch_opts.callbacks.payload = &counter; - - cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, _remote_url, "./foo", &g_options)); - cl_assert_equal_i(3, counter); -} - -static int cred_default( - git_credential **cred, - const char *url, - const char *user_from_url, - unsigned int allowed_types, - void *payload) -{ - GIT_UNUSED(url); - GIT_UNUSED(user_from_url); - GIT_UNUSED(payload); - - if (!(allowed_types & GIT_CREDENTIAL_DEFAULT)) - return 0; - - return git_credential_default_new(cred); -} - -void test_online_clone__credentials(void) -{ - /* Remote URL environment variable must be set. - * User and password are optional. - */ - git_credential_userpass_payload user_pass = { - _remote_user, - _remote_pass - }; - - if (!_remote_url) - clar__skip(); - - if (cl_is_env_set("GITTEST_REMOTE_DEFAULT")) { - g_options.fetch_opts.callbacks.credentials = cred_default; - } else { - g_options.fetch_opts.callbacks.credentials = git_credential_userpass; - g_options.fetch_opts.callbacks.payload = &user_pass; - } - - cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options)); - git_repository_free(g_repo); g_repo = NULL; - cl_fixture_cleanup("./foo"); -} - -void test_online_clone__credentials_via_custom_headers(void) -{ - const char *creds = "libgit3:libgit3"; - git_str auth = GIT_STR_INIT; - - cl_git_pass(git_str_puts(&auth, "Authorization: Basic ")); - cl_git_pass(git_str_encode_base64(&auth, creds, strlen(creds))); - g_options.fetch_opts.custom_headers.count = 1; - g_options.fetch_opts.custom_headers.strings = &auth.ptr; - - cl_git_pass(git_clone(&g_repo, "https://bitbucket.org/libgit2/testgitrepository.git", "./foo", &g_options)); - - git_str_dispose(&auth); -} - -void test_online_clone__bitbucket_style(void) -{ - git_credential_userpass_payload user_pass = { - "libgit3", "libgit3" - }; - - g_options.fetch_opts.callbacks.credentials = git_credential_userpass; - g_options.fetch_opts.callbacks.payload = &user_pass; - - cl_git_pass(git_clone(&g_repo, BB_REPO_URL, "./foo", &g_options)); - git_repository_free(g_repo); g_repo = NULL; - cl_fixture_cleanup("./foo"); -} - -void test_online_clone__bitbucket_uses_creds_in_url(void) -{ - git_credential_userpass_payload user_pass = { - "libgit2", "wrong" - }; - - g_options.fetch_opts.callbacks.credentials = git_credential_userpass; - g_options.fetch_opts.callbacks.payload = &user_pass; - - /* - * Correct user and pass are in the URL; the (incorrect) creds in - * the `git_credential_userpass_payload` should be ignored. - */ - cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_PASS, "./foo", &g_options)); - git_repository_free(g_repo); g_repo = NULL; - cl_fixture_cleanup("./foo"); -} - -void test_online_clone__bitbucket_falls_back_to_specified_creds(void) -{ - git_credential_userpass_payload user_pass = { - "libgit2", "libgit2" - }; - - g_options.fetch_opts.callbacks.credentials = git_credential_userpass; - g_options.fetch_opts.callbacks.payload = &user_pass; - - /* - * TODO: as of March 2018, bitbucket sporadically fails with - * 403s instead of replying with a 401 - but only sometimes. - */ - cl_skip(); - - /* - * Incorrect user and pass are in the URL; the (correct) creds in - * the `git_credential_userpass_payload` should be used as a fallback. - */ - cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_WRONG_PASS, "./foo", &g_options)); - git_repository_free(g_repo); g_repo = NULL; - cl_fixture_cleanup("./foo"); -} - -void test_online_clone__googlesource(void) -{ - cl_git_pass(git_clone(&g_repo, GOOGLESOURCE_REPO_URL, "./foo", &g_options)); - git_repository_free(g_repo); g_repo = NULL; - cl_fixture_cleanup("./foo"); -} - -static int cancel_at_half(const git_indexer_progress *stats, void *payload) -{ - GIT_UNUSED(payload); - - if (stats->received_objects > (stats->total_objects/2)) - return 4321; - return 0; -} - -void test_online_clone__can_cancel(void) -{ - g_options.fetch_opts.callbacks.transfer_progress = cancel_at_half; - - cl_git_fail_with(4321, - git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); -} - -static int cred_cb(git_credential **cred, const char *url, const char *user_from_url, - unsigned int allowed_types, void *payload) -{ - GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload); - - if (allowed_types & GIT_CREDENTIAL_USERNAME) - return git_credential_username_new(cred, _remote_user); - - if (allowed_types & GIT_CREDENTIAL_SSH_KEY) - return git_credential_ssh_key_new(cred, - _remote_user, _remote_ssh_pubkey, - _remote_ssh_privkey, _remote_ssh_passphrase); - - git_error_set(GIT_ERROR_NET, "unexpected cred type"); - return -1; -} - -static int check_ssh_auth_methods(git_credential **cred, const char *url, const char *username_from_url, - unsigned int allowed_types, void *data) -{ - int *with_user = (int *) data; - GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(data); - - if (!*with_user) - cl_assert_equal_i(GIT_CREDENTIAL_USERNAME, allowed_types); - else - cl_assert(!(allowed_types & GIT_CREDENTIAL_USERNAME)); - - return GIT_EUSER; -} - -void test_online_clone__ssh_auth_methods(void) -{ - int with_user; - -#ifndef GIT_SSH - clar__skip(); -#endif - g_options.fetch_opts.callbacks.credentials = check_ssh_auth_methods; - g_options.fetch_opts.callbacks.payload = &with_user; - g_options.fetch_opts.callbacks.certificate_check = NULL; - - with_user = 0; - cl_git_fail_with(GIT_EUSER, - git_clone(&g_repo, SSH_REPO_URL, "./foo", &g_options)); - - with_user = 1; - cl_git_fail_with(GIT_EUSER, - git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options)); -} - -static int custom_remote_ssh_with_paths( - git_remote **out, - git_repository *repo, - const char *name, - const char *url, - void *payload) -{ - int error; - - GIT_UNUSED(payload); - - if ((error = git_remote_create(out, repo, name, url)) < 0) - return error; - - return 0; -} - -void test_online_clone__ssh_with_paths(void) -{ - char *bad_paths[] = { - "/bin/yes", - "/bin/false", - }; - char *good_paths[] = { - "/usr/bin/git-upload-pack", - "/usr/bin/git-receive-pack", - }; - git_strarray arr = { - bad_paths, - 2, - }; - -#ifndef GIT_SSH - clar__skip(); -#endif - if (!_remote_url || !_remote_user || strncmp(_remote_url, "ssh://", 5) != 0) - clar__skip(); - - g_options.remote_cb = custom_remote_ssh_with_paths; - g_options.fetch_opts.callbacks.transport = git_transport_ssh_with_paths; - g_options.fetch_opts.callbacks.credentials = cred_cb; - g_options.fetch_opts.callbacks.payload = &arr; - g_options.fetch_opts.callbacks.certificate_check = NULL; - - cl_git_fail(git_clone(&g_repo, _remote_url, "./foo", &g_options)); - - arr.strings = good_paths; - cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options)); -} - -static int cred_foo_bar(git_credential **cred, const char *url, const char *username_from_url, - unsigned int allowed_types, void *data) - -{ - GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(allowed_types); GIT_UNUSED(data); - - return git_credential_userpass_plaintext_new(cred, "foo", "bar"); -} - -void test_online_clone__ssh_cannot_change_username(void) -{ -#ifndef GIT_SSH - clar__skip(); -#endif - g_options.fetch_opts.callbacks.credentials = cred_foo_bar; - - cl_git_fail(git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options)); -} - -static int ssh_certificate_check(git_cert *cert, int valid, const char *host, void *payload) -{ - git_cert_hostkey *key; - git_oid expected = {{0}}, actual = {{0}}; - - GIT_UNUSED(valid); - GIT_UNUSED(payload); - - cl_assert(_remote_ssh_fingerprint); - - cl_git_pass(git_oid_fromstrp(&expected, _remote_ssh_fingerprint)); - cl_assert_equal_i(GIT_CERT_HOSTKEY_LIBSSH2, cert->cert_type); - key = (git_cert_hostkey *) cert; - - /* - * We need to figure out how long our input was to check for - * the type. Here we abuse the fact that both hashes fit into - * our git_oid type. - */ - if (strlen(_remote_ssh_fingerprint) == 32 && key->type & GIT_CERT_SSH_MD5) { - memcpy(&actual.id, key->hash_md5, 16); - } else if (strlen(_remote_ssh_fingerprint) == 40 && key->type & GIT_CERT_SSH_SHA1) { - memcpy(&actual, key->hash_sha1, 20); - } else { - cl_fail("Cannot find a usable SSH hash"); - } - - cl_assert(!memcmp(&expected, &actual, 20)); - - cl_assert_equal_s("localhost", host); - - return GIT_EUSER; -} - -void test_online_clone__ssh_cert(void) -{ - g_options.fetch_opts.callbacks.certificate_check = ssh_certificate_check; - - if (!_remote_ssh_fingerprint) - cl_skip(); - - cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, _remote_url, "./foo", &g_options)); -} - -static char *read_key_file(const char *path) -{ - FILE *f; - char *buf; - long key_length; - - if (!path || !*path) - return NULL; - - cl_assert((f = fopen(path, "r")) != NULL); - cl_assert(fseek(f, 0, SEEK_END) != -1); - cl_assert((key_length = ftell(f)) != -1); - cl_assert(fseek(f, 0, SEEK_SET) != -1); - cl_assert((buf = malloc(key_length)) != NULL); - cl_assert(fread(buf, key_length, 1, f) == 1); - fclose(f); - - return buf; -} - -static int ssh_memory_cred_cb(git_credential **cred, const char *url, const char *user_from_url, - unsigned int allowed_types, void *payload) -{ - GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload); - - if (allowed_types & GIT_CREDENTIAL_USERNAME) - return git_credential_username_new(cred, _remote_user); - - if (allowed_types & GIT_CREDENTIAL_SSH_KEY) - { - char *pubkey = read_key_file(_remote_ssh_pubkey); - char *privkey = read_key_file(_remote_ssh_privkey); - - int ret = git_credential_ssh_key_memory_new(cred, _remote_user, pubkey, privkey, _remote_ssh_passphrase); - - if (privkey) - free(privkey); - if (pubkey) - free(pubkey); - return ret; - } - - git_error_set(GIT_ERROR_NET, "unexpected cred type"); - return -1; -} - -void test_online_clone__ssh_memory_auth(void) -{ -#ifndef GIT_SSH_MEMORY_CREDENTIALS - clar__skip(); -#endif - if (!_remote_url || !_remote_user || !_remote_ssh_privkey || strncmp(_remote_url, "ssh://", 5) != 0) - clar__skip(); - - g_options.fetch_opts.callbacks.credentials = ssh_memory_cred_cb; - - cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options)); -} - -static int fail_certificate_check(git_cert *cert, int valid, const char *host, void *payload) -{ - GIT_UNUSED(cert); - GIT_UNUSED(valid); - GIT_UNUSED(host); - GIT_UNUSED(payload); - - return GIT_ECERTIFICATE; -} - -void test_online_clone__certificate_invalid(void) -{ - g_options.fetch_opts.callbacks.certificate_check = fail_certificate_check; - - cl_git_fail_with(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options), - GIT_ECERTIFICATE); - -#ifdef GIT_SSH - cl_git_fail_with(git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options), - GIT_ECERTIFICATE); -#endif -} - -static int succeed_certificate_check(git_cert *cert, int valid, const char *host, void *payload) -{ - GIT_UNUSED(cert); - GIT_UNUSED(valid); - GIT_UNUSED(payload); - - cl_assert_equal_s("github.com", host); - - return 0; -} - -void test_online_clone__certificate_valid(void) -{ - g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check; - - cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options)); -} - -void test_online_clone__start_with_http(void) -{ - g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check; - - cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); -} - -static int called_proxy_creds; -static int proxy_cred_cb(git_credential **out, const char *url, const char *username, unsigned int allowed, void *payload) -{ - GIT_UNUSED(url); - GIT_UNUSED(username); - GIT_UNUSED(allowed); - GIT_UNUSED(payload); - - called_proxy_creds = 1; - return git_credential_userpass_plaintext_new(out, _remote_proxy_user, _remote_proxy_pass); -} - -static int proxy_cert_cb(git_cert *cert, int valid, const char *host, void *payload) -{ - char *colon; - size_t host_len; - - GIT_UNUSED(cert); - GIT_UNUSED(valid); - GIT_UNUSED(payload); - - cl_assert(_remote_proxy_host); - - if ((colon = strchr(_remote_proxy_host, ':')) != NULL) - host_len = (colon - _remote_proxy_host); - else - host_len = strlen(_remote_proxy_host); - - if (_remote_proxy_selfsigned != NULL && - strlen(host) == host_len && - strncmp(_remote_proxy_host, host, host_len) == 0) - valid = 1; - - return valid ? 0 : GIT_ECERTIFICATE; -} - -void test_online_clone__proxy_credentials_request(void) -{ - git_str url = GIT_STR_INIT; - - if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) - cl_skip(); - - cl_git_pass(git_str_printf(&url, "%s://%s/", - _remote_proxy_scheme ? _remote_proxy_scheme : "http", - _remote_proxy_host)); - - g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; - g_options.fetch_opts.proxy_opts.url = url.ptr; - g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb; - g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; - called_proxy_creds = 0; - cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); - cl_assert(called_proxy_creds); - - git_str_dispose(&url); -} - -void test_online_clone__proxy_credentials_in_url(void) -{ - git_str url = GIT_STR_INIT; - - if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) - cl_skip(); - - cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/", - _remote_proxy_scheme ? _remote_proxy_scheme : "http", - _remote_proxy_user, _remote_proxy_pass, _remote_proxy_host)); - - g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; - g_options.fetch_opts.proxy_opts.url = url.ptr; - g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; - called_proxy_creds = 0; - cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); - cl_assert(called_proxy_creds == 0); - - git_str_dispose(&url); -} - -void test_online_clone__proxy_credentials_in_environment(void) -{ - git_str url = GIT_STR_INIT; - - if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) - cl_skip(); - - _orig_http_proxy = cl_getenv("HTTP_PROXY"); - _orig_https_proxy = cl_getenv("HTTPS_PROXY"); - _orig_no_proxy = cl_getenv("NO_PROXY"); - _orig_proxies_need_reset = 1; - - g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO; - g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; - - cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/", - _remote_proxy_scheme ? _remote_proxy_scheme : "http", - _remote_proxy_user, _remote_proxy_pass, _remote_proxy_host)); - - cl_setenv("HTTP_PROXY", url.ptr); - cl_setenv("HTTPS_PROXY", url.ptr); - cl_setenv("NO_PROXY", NULL); - - cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); - - git_str_dispose(&url); -} - -void test_online_clone__proxy_credentials_in_url_https(void) -{ - git_str url = GIT_STR_INIT; - - if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) - cl_skip(); - - cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/", - _remote_proxy_scheme ? _remote_proxy_scheme : "http", - _remote_proxy_user, _remote_proxy_pass, _remote_proxy_host)); - - g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; - g_options.fetch_opts.proxy_opts.url = url.ptr; - g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; - g_options.fetch_opts.callbacks.certificate_check = ssl_cert; - called_proxy_creds = 0; - cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options)); - cl_assert(called_proxy_creds == 0); - - git_str_dispose(&url); -} - -void test_online_clone__proxy_auto_not_detected(void) -{ - g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO; - - cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); -} - -void test_online_clone__proxy_cred_callback_after_failed_url_creds(void) -{ - git_str url = GIT_STR_INIT; - - if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) - cl_skip(); - - cl_git_pass(git_str_printf(&url, "%s://invalid_user_name:INVALID_pass_WORD@%s/", - _remote_proxy_scheme ? _remote_proxy_scheme : "http", - _remote_proxy_host)); - - g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; - g_options.fetch_opts.proxy_opts.url = url.ptr; - g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb; - g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; - called_proxy_creds = 0; - cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); - cl_assert(called_proxy_creds); - - git_str_dispose(&url); -} - -void test_online_clone__azurerepos(void) -{ - cl_git_pass(git_clone(&g_repo, "https://libgit2@dev.azure.com/libgit2/test/_git/test", "./foo", &g_options)); - cl_assert(git_fs_path_exists("./foo/master.txt")); -} - -void test_online_clone__path_whitespace(void) -{ - cl_git_pass(git_clone(&g_repo, "https://libgit2@dev.azure.com/libgit2/test/_git/spaces%20in%20the%20name", "./foo", &g_options)); - cl_assert(git_fs_path_exists("./foo/master.txt")); -} - -void test_online_clone__redirect_default_succeeds_for_initial(void) -{ - git_clone_options options = GIT_CLONE_OPTIONS_INIT; - - if (!_remote_redirect_initial || !_remote_redirect_subsequent) - cl_skip(); - - cl_git_pass(git_clone(&g_repo, _remote_redirect_initial, "./initial", &options)); -} - -void test_online_clone__redirect_default_fails_for_subsequent(void) -{ - git_clone_options options = GIT_CLONE_OPTIONS_INIT; - - if (!_remote_redirect_initial || !_remote_redirect_subsequent) - cl_skip(); - - cl_git_fail(git_clone(&g_repo, _remote_redirect_subsequent, "./fail", &options)); -} - -void test_online_clone__redirect_none(void) -{ - git_clone_options options = GIT_CLONE_OPTIONS_INIT; - - if (!_remote_redirect_initial) - cl_skip(); - - options.fetch_opts.follow_redirects = GIT_REMOTE_REDIRECT_NONE; - - cl_git_fail(git_clone(&g_repo, _remote_redirect_initial, "./fail", &options)); -} - -void test_online_clone__redirect_initial_succeeds_for_initial(void) -{ - git_clone_options options = GIT_CLONE_OPTIONS_INIT; - - if (!_remote_redirect_initial || !_remote_redirect_subsequent) - cl_skip(); - - options.fetch_opts.follow_redirects = GIT_REMOTE_REDIRECT_INITIAL; - - cl_git_pass(git_clone(&g_repo, _remote_redirect_initial, "./initial", &options)); -} - -void test_online_clone__redirect_initial_fails_for_subsequent(void) -{ - git_clone_options options = GIT_CLONE_OPTIONS_INIT; - - if (!_remote_redirect_initial || !_remote_redirect_subsequent) - cl_skip(); - - options.fetch_opts.follow_redirects = GIT_REMOTE_REDIRECT_INITIAL; - - cl_git_fail(git_clone(&g_repo, _remote_redirect_subsequent, "./fail", &options)); -} diff --git a/tests/online/customcert.c b/tests/online/customcert.c deleted file mode 100644 index 7932a9e68..000000000 --- a/tests/online/customcert.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "clar_libgit2.h" - -#include "path.h" -#include "git2/clone.h" -#include "git2/cred_helpers.h" -#include "remote.h" -#include "futils.h" -#include "refs.h" - -/* - * Certificate one is in the `certs` folder; certificate two is in the - * `self-signed.pem` file. - */ -#define CUSTOM_CERT_ONE_URL "https://test.libgit2.org:1443/anonymous/test.git" -#define CUSTOM_CERT_ONE_PATH "certs" - -#define CUSTOM_CERT_TWO_URL "https://test.libgit2.org:2443/anonymous/test.git" -#define CUSTOM_CERT_TWO_FILE "self-signed.pem" - -#if (GIT_OPENSSL || GIT_MBEDTLS) -static git_repository *g_repo; -static int initialized = false; -#endif - -void test_online_customcert__initialize(void) -{ -#if (GIT_OPENSSL || GIT_MBEDTLS) - g_repo = NULL; - - if (!initialized) { - git_str path = GIT_STR_INIT, file = GIT_STR_INIT; - char cwd[GIT_PATH_MAX]; - - cl_fixture_sandbox(CUSTOM_CERT_ONE_PATH); - cl_fixture_sandbox(CUSTOM_CERT_TWO_FILE); - - cl_must_pass(p_getcwd(cwd, GIT_PATH_MAX)); - cl_git_pass(git_str_joinpath(&path, cwd, CUSTOM_CERT_ONE_PATH)); - cl_git_pass(git_str_joinpath(&file, cwd, CUSTOM_CERT_TWO_FILE)); - - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, - file.ptr, path.ptr)); - initialized = true; - - git_str_dispose(&file); - git_str_dispose(&path); - } -#endif -} - -void test_online_customcert__cleanup(void) -{ -#if (GIT_OPENSSL || GIT_MBEDTLS) - if (g_repo) { - git_repository_free(g_repo); - g_repo = NULL; - } - - cl_fixture_cleanup("./cloned"); - cl_fixture_cleanup(CUSTOM_CERT_ONE_PATH); - cl_fixture_cleanup(CUSTOM_CERT_TWO_FILE); -#endif -} - -void test_online_customcert__file(void) -{ -#if (GIT_OPENSSL || GIT_MBEDTLS) - cl_git_pass(git_clone(&g_repo, CUSTOM_CERT_ONE_URL, "./cloned", NULL)); - cl_assert(git_fs_path_exists("./cloned/master.txt")); -#endif -} - -void test_online_customcert__path(void) -{ -#if (GIT_OPENSSL || GIT_MBEDTLS) - cl_git_pass(git_clone(&g_repo, CUSTOM_CERT_TWO_URL, "./cloned", NULL)); - cl_assert(git_fs_path_exists("./cloned/master.txt")); -#endif -} diff --git a/tests/online/fetch.c b/tests/online/fetch.c deleted file mode 100644 index 7334f7e8b..000000000 --- a/tests/online/fetch.c +++ /dev/null @@ -1,323 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" - -static git_repository *_repo; -static int counter; - -static char *_remote_proxy_scheme = NULL; -static char *_remote_proxy_host = NULL; -static char *_remote_proxy_user = NULL; -static char *_remote_proxy_pass = NULL; -static char *_remote_redirect_initial = NULL; -static char *_remote_redirect_subsequent = NULL; - -void test_online_fetch__initialize(void) -{ - cl_git_pass(git_repository_init(&_repo, "./fetch", 0)); - - _remote_proxy_scheme = cl_getenv("GITTEST_REMOTE_PROXY_SCHEME"); - _remote_proxy_host = cl_getenv("GITTEST_REMOTE_PROXY_HOST"); - _remote_proxy_user = cl_getenv("GITTEST_REMOTE_PROXY_USER"); - _remote_proxy_pass = cl_getenv("GITTEST_REMOTE_PROXY_PASS"); - _remote_redirect_initial = cl_getenv("GITTEST_REMOTE_REDIRECT_INITIAL"); - _remote_redirect_subsequent = cl_getenv("GITTEST_REMOTE_REDIRECT_SUBSEQUENT"); -} - -void test_online_fetch__cleanup(void) -{ - git_repository_free(_repo); - _repo = NULL; - - cl_fixture_cleanup("./fetch"); - cl_fixture_cleanup("./redirected"); - - git__free(_remote_proxy_scheme); - git__free(_remote_proxy_host); - git__free(_remote_proxy_user); - git__free(_remote_proxy_pass); - git__free(_remote_redirect_initial); - git__free(_remote_redirect_subsequent); -} - -static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *data) -{ - GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b); GIT_UNUSED(data); - - ++counter; - - return 0; -} - -static int progress(const git_indexer_progress *stats, void *payload) -{ - size_t *bytes_received = (size_t *)payload; - *bytes_received = stats->received_bytes; - return 0; -} - -static void do_fetch(const char *url, git_remote_autotag_option_t flag, int n) -{ - git_remote *remote; - git_fetch_options options = GIT_FETCH_OPTIONS_INIT; - size_t bytes_received = 0; - - options.callbacks.transfer_progress = progress; - options.callbacks.update_tips = update_tips; - options.callbacks.payload = &bytes_received; - options.download_tags = flag; - counter = 0; - - cl_git_pass(git_remote_create(&remote, _repo, "test", url)); - cl_git_pass(git_remote_fetch(remote, NULL, &options, NULL)); - cl_assert_equal_i(counter, n); - cl_assert(bytes_received > 0); - - git_remote_free(remote); -} - -void test_online_fetch__default_http(void) -{ - do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6); -} - -void test_online_fetch__default_https(void) -{ - do_fetch("https://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6); -} - -void test_online_fetch__no_tags_git(void) -{ - do_fetch("https://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3); -} - -void test_online_fetch__no_tags_http(void) -{ - do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3); -} - -void test_online_fetch__fetch_twice(void) -{ - git_remote *remote; - cl_git_pass(git_remote_create(&remote, _repo, "test", "https://github.com/libgit2/TestGitRepository.git")); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - cl_git_pass(git_remote_download(remote, NULL, NULL)); - git_remote_disconnect(remote); - - git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL); - cl_git_pass(git_remote_download(remote, NULL, NULL)); - git_remote_disconnect(remote); - - git_remote_free(remote); -} - -static int transferProgressCallback(const git_indexer_progress *stats, void *payload) -{ - bool *invoked = (bool *)payload; - - GIT_UNUSED(stats); - *invoked = true; - return 0; -} - -void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date(void) -{ - git_repository *_repository; - bool invoked = false; - git_remote *remote; - git_fetch_options options = GIT_FETCH_OPTIONS_INIT; - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - opts.bare = true; - - cl_git_pass(git_clone(&_repository, "https://github.com/libgit2/TestGitRepository.git", - "./fetch/lg2", &opts)); - git_repository_free(_repository); - - cl_git_pass(git_repository_open(&_repository, "./fetch/lg2")); - - cl_git_pass(git_remote_lookup(&remote, _repository, "origin")); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - - cl_assert_equal_i(false, invoked); - - options.callbacks.transfer_progress = &transferProgressCallback; - options.callbacks.payload = &invoked; - cl_git_pass(git_remote_download(remote, NULL, &options)); - - cl_assert_equal_i(false, invoked); - - cl_git_pass(git_remote_update_tips(remote, &options.callbacks, 1, options.download_tags, NULL)); - git_remote_disconnect(remote); - - git_remote_free(remote); - git_repository_free(_repository); -} - -static int cancel_at_half(const git_indexer_progress *stats, void *payload) -{ - GIT_UNUSED(payload); - - if (stats->received_objects > (stats->total_objects/2)) - return -4321; - return 0; -} - -void test_online_fetch__can_cancel(void) -{ - git_remote *remote; - size_t bytes_received = 0; - git_fetch_options options = GIT_FETCH_OPTIONS_INIT; - - cl_git_pass(git_remote_create(&remote, _repo, "test", - "http://github.com/libgit2/TestGitRepository.git")); - - options.callbacks.transfer_progress = cancel_at_half; - options.callbacks.payload = &bytes_received; - - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - cl_git_fail_with(git_remote_download(remote, NULL, &options), -4321); - git_remote_disconnect(remote); - git_remote_free(remote); -} - -void test_online_fetch__ls_disconnected(void) -{ - const git_remote_head **refs; - size_t refs_len_before, refs_len_after; - git_remote *remote; - - cl_git_pass(git_remote_create(&remote, _repo, "test", - "http://github.com/libgit2/TestGitRepository.git")); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - cl_git_pass(git_remote_ls(&refs, &refs_len_before, remote)); - git_remote_disconnect(remote); - cl_git_pass(git_remote_ls(&refs, &refs_len_after, remote)); - - cl_assert_equal_i(refs_len_before, refs_len_after); - - git_remote_free(remote); -} - -void test_online_fetch__remote_symrefs(void) -{ - const git_remote_head **refs; - size_t refs_len; - git_remote *remote; - - cl_git_pass(git_remote_create(&remote, _repo, "test", - "http://github.com/libgit2/TestGitRepository.git")); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - git_remote_disconnect(remote); - cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); - - cl_assert_equal_s("HEAD", refs[0]->name); - cl_assert_equal_s("refs/heads/master", refs[0]->symref_target); - - git_remote_free(remote); -} - -void test_online_fetch__twice(void) -{ - git_remote *remote; - - cl_git_pass(git_remote_create(&remote, _repo, "test", "http://github.com/libgit2/TestGitRepository.git")); - cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); - cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); - - git_remote_free(remote); -} - -void test_online_fetch__proxy(void) -{ - git_remote *remote; - git_str url = GIT_STR_INIT; - git_fetch_options fetch_opts; - - if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) - cl_skip(); - - cl_git_pass(git_str_printf(&url, "%s://%s:%s@%s/", - _remote_proxy_scheme ? _remote_proxy_scheme : "http", - _remote_proxy_user, _remote_proxy_pass, _remote_proxy_host)); - - cl_git_pass(git_fetch_options_init(&fetch_opts, GIT_FETCH_OPTIONS_VERSION)); - fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; - fetch_opts.proxy_opts.url = url.ptr; - - cl_git_pass(git_remote_create(&remote, _repo, "test", "https://github.com/libgit2/TestGitRepository.git")); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, &fetch_opts.proxy_opts, NULL)); - cl_git_pass(git_remote_fetch(remote, NULL, &fetch_opts, NULL)); - - git_remote_free(remote); - git_str_dispose(&url); -} - -static int do_redirected_fetch(const char *url, const char *name, const char *config) -{ - git_repository *repo; - git_remote *remote; - int error; - - cl_git_pass(git_repository_init(&repo, "./redirected", 0)); - cl_fixture_cleanup(name); - - if (config) - cl_repo_set_string(repo, "http.followRedirects", config); - - cl_git_pass(git_remote_create(&remote, repo, name, url)); - error = git_remote_fetch(remote, NULL, NULL, NULL); - - git_remote_free(remote); - git_repository_free(repo); - - cl_fixture_cleanup("./redirected"); - - return error; -} - -void test_online_fetch__redirect_config(void) -{ - if (!_remote_redirect_initial || !_remote_redirect_subsequent) - cl_skip(); - - /* config defaults */ - cl_git_pass(do_redirected_fetch(_remote_redirect_initial, "initial", NULL)); - cl_git_fail(do_redirected_fetch(_remote_redirect_subsequent, "subsequent", NULL)); - - /* redirect=initial */ - cl_git_pass(do_redirected_fetch(_remote_redirect_initial, "initial", "initial")); - cl_git_fail(do_redirected_fetch(_remote_redirect_subsequent, "subsequent", "initial")); - - /* redirect=false */ - cl_git_fail(do_redirected_fetch(_remote_redirect_initial, "initial", "false")); - cl_git_fail(do_redirected_fetch(_remote_redirect_subsequent, "subsequent", "false")); -} - -void test_online_fetch__reachable_commit(void) -{ - git_remote *remote; - git_strarray refspecs; - git_object *obj; - git_oid expected_id; - git_str fetchhead = GIT_STR_INIT; - char *refspec = "+2c349335b7f797072cf729c4f3bb0914ecb6dec9:refs/success"; - - refspecs.strings = &refspec; - refspecs.count = 1; - - git_oid_fromstr(&expected_id, "2c349335b7f797072cf729c4f3bb0914ecb6dec9"); - - cl_git_pass(git_remote_create(&remote, _repo, "test", - "https://github.com/libgit2/TestGitRepository")); - cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, NULL)); - - cl_git_pass(git_revparse_single(&obj, _repo, "refs/success")); - cl_assert_equal_oid(&expected_id, git_object_id(obj)); - - cl_git_pass(git_futils_readbuffer(&fetchhead, "./fetch/.git/FETCH_HEAD")); - cl_assert_equal_s(fetchhead.ptr, - "2c349335b7f797072cf729c4f3bb0914ecb6dec9\t\t'2c349335b7f797072cf729c4f3bb0914ecb6dec9' of https://github.com/libgit2/TestGitRepository\n"); - - git_str_dispose(&fetchhead); - git_object_free(obj); - git_remote_free(remote); -} diff --git a/tests/online/fetchhead.c b/tests/online/fetchhead.c deleted file mode 100644 index 1b66c528e..000000000 --- a/tests/online/fetchhead.c +++ /dev/null @@ -1,169 +0,0 @@ -#include "clar_libgit2.h" - -#include "futils.h" -#include "fetchhead.h" -#include "../fetchhead/fetchhead_data.h" -#include "git2/clone.h" - -#define LIVE_REPO_URL "https://github.com/libgit2/TestGitRepository" - -static git_repository *g_repo; -static git_clone_options g_options; - -void test_online_fetchhead__initialize(void) -{ - git_fetch_options dummy_fetch = GIT_FETCH_OPTIONS_INIT; - g_repo = NULL; - - memset(&g_options, 0, sizeof(git_clone_options)); - g_options.version = GIT_CLONE_OPTIONS_VERSION; - g_options.fetch_opts = dummy_fetch; -} - -void test_online_fetchhead__cleanup(void) -{ - if (g_repo) { - git_repository_free(g_repo); - g_repo = NULL; - } - - cl_fixture_cleanup("./foo"); -} - -static void fetchhead_test_clone(void) -{ - cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); -} - -static size_t count_references(void) -{ - git_strarray array; - size_t refs; - - cl_git_pass(git_reference_list(&array, g_repo)); - refs = array.count; - - git_strarray_dispose(&array); - - return refs; -} - -static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fetchhead) -{ - git_remote *remote; - git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; - git_str fetchhead_buf = GIT_STR_INIT; - git_strarray array, *active_refs = NULL; - - cl_git_pass(git_remote_lookup(&remote, g_repo, "origin")); - fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; - - if(fetchspec != NULL) { - array.count = 1; - array.strings = (char **) &fetchspec; - active_refs = &array; - } - - cl_git_pass(git_remote_fetch(remote, active_refs, &fetch_opts, NULL)); - git_remote_free(remote); - - cl_git_pass(git_futils_readbuffer(&fetchhead_buf, "./foo/.git/FETCH_HEAD")); - - cl_assert_equal_s(fetchhead_buf.ptr, expected_fetchhead); - git_str_dispose(&fetchhead_buf); -} - -void test_online_fetchhead__wildcard_spec(void) -{ - fetchhead_test_clone(); - fetchhead_test_fetch(NULL, FETCH_HEAD_WILDCARD_DATA2); - cl_git_pass(git_tag_delete(g_repo, "annotated_tag")); - cl_git_pass(git_tag_delete(g_repo, "blob")); - cl_git_pass(git_tag_delete(g_repo, "commit_tree")); - cl_git_pass(git_tag_delete(g_repo, "nearly-dangling")); - fetchhead_test_fetch(NULL, FETCH_HEAD_WILDCARD_DATA); -} - -void test_online_fetchhead__explicit_spec(void) -{ - fetchhead_test_clone(); - fetchhead_test_fetch("refs/heads/first-merge:refs/remotes/origin/first-merge", FETCH_HEAD_EXPLICIT_DATA); -} - -void test_online_fetchhead__no_merges(void) -{ - git_config *config; - - fetchhead_test_clone(); - - cl_git_pass(git_repository_config(&config, g_repo)); - cl_git_pass(git_config_delete_entry(config, "branch.master.remote")); - cl_git_pass(git_config_delete_entry(config, "branch.master.merge")); - git_config_free(config); - - fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA2); - cl_git_pass(git_tag_delete(g_repo, "annotated_tag")); - cl_git_pass(git_tag_delete(g_repo, "blob")); - cl_git_pass(git_tag_delete(g_repo, "commit_tree")); - cl_git_pass(git_tag_delete(g_repo, "nearly-dangling")); - fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA); - cl_git_pass(git_tag_delete(g_repo, "commit_tree")); - fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA3); -} - -void test_online_fetchhead__explicit_dst_refspec_creates_branch(void) -{ - git_reference *ref; - size_t refs; - - fetchhead_test_clone(); - refs = count_references(); - fetchhead_test_fetch("refs/heads/first-merge:refs/heads/explicit-refspec", FETCH_HEAD_EXPLICIT_DATA); - - cl_git_pass(git_branch_lookup(&ref, g_repo, "explicit-refspec", GIT_BRANCH_ALL)); - cl_assert_equal_i(refs + 1, count_references()); - - git_reference_free(ref); -} - -void test_online_fetchhead__empty_dst_refspec_creates_no_branch(void) -{ - git_reference *ref; - size_t refs; - - fetchhead_test_clone(); - refs = count_references(); - - fetchhead_test_fetch("refs/heads/first-merge", FETCH_HEAD_EXPLICIT_DATA); - cl_git_fail(git_branch_lookup(&ref, g_repo, "first-merge", GIT_BRANCH_ALL)); - - cl_assert_equal_i(refs, count_references()); -} - -void test_online_fetchhead__colon_only_dst_refspec_creates_no_branch(void) -{ - size_t refs; - - fetchhead_test_clone(); - refs = count_references(); - fetchhead_test_fetch("refs/heads/first-merge:", FETCH_HEAD_EXPLICIT_DATA); - - cl_assert_equal_i(refs, count_references()); -} - -void test_online_fetchhead__creds_get_stripped(void) -{ - git_str buf = GIT_STR_INIT; - git_remote *remote; - - cl_git_pass(git_repository_init(&g_repo, "./foo", 0)); - cl_git_pass(git_remote_create_anonymous(&remote, g_repo, "https://foo:bar@github.com/libgit2/TestGitRepository")); - cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); - - cl_git_pass(git_futils_readbuffer(&buf, "./foo/.git/FETCH_HEAD")); - cl_assert_equal_s(buf.ptr, - "49322bb17d3acc9146f98c97d078513228bbf3c0\t\thttps://github.com/libgit2/TestGitRepository\n"); - - git_remote_free(remote); - git_str_dispose(&buf); -} diff --git a/tests/online/push.c b/tests/online/push.c deleted file mode 100644 index 51adc3930..000000000 --- a/tests/online/push.c +++ /dev/null @@ -1,917 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "vector.h" -#include "../submodule/submodule_helpers.h" -#include "push_util.h" -#include "refspec.h" -#include "remote.h" - -static git_repository *_repo; - -static char *_remote_url = NULL; - -static char *_remote_user = NULL; -static char *_remote_pass = NULL; - -static char *_remote_ssh_key = NULL; -static char *_remote_ssh_pubkey = NULL; -static char *_remote_ssh_passphrase = NULL; - -static char *_remote_default = NULL; -static char *_remote_expectcontinue = NULL; - -static int cred_acquire_cb(git_credential **, const char *, const char *, unsigned int, void *); - -static git_remote *_remote; -static record_callbacks_data _record_cbs_data = {{ 0 }}; -static git_remote_callbacks _record_cbs = RECORD_CALLBACKS_INIT(&_record_cbs_data); - -static git_oid _oid_b6; -static git_oid _oid_b5; -static git_oid _oid_b4; -static git_oid _oid_b3; -static git_oid _oid_b2; -static git_oid _oid_b1; - -static git_oid _tag_commit; -static git_oid _tag_tree; -static git_oid _tag_blob; -static git_oid _tag_lightweight; -static git_oid _tag_tag; - -static int cred_acquire_cb( - git_credential **cred, - const char *url, - const char *user_from_url, - unsigned int allowed_types, - void *payload) -{ - GIT_UNUSED(url); - GIT_UNUSED(user_from_url); - GIT_UNUSED(payload); - - if (GIT_CREDENTIAL_USERNAME & allowed_types) { - if (!_remote_user) { - printf("GITTEST_REMOTE_USER must be set\n"); - return -1; - } - - return git_credential_username_new(cred, _remote_user); - } - - if (GIT_CREDENTIAL_DEFAULT & allowed_types) { - if (!_remote_default) { - printf("GITTEST_REMOTE_DEFAULT must be set to use NTLM/Negotiate credentials\n"); - return -1; - } - - return git_credential_default_new(cred); - } - - if (GIT_CREDENTIAL_SSH_KEY & allowed_types) { - if (!_remote_user || !_remote_ssh_pubkey || !_remote_ssh_key || !_remote_ssh_passphrase) { - printf("GITTEST_REMOTE_USER, GITTEST_REMOTE_SSH_PUBKEY, GITTEST_REMOTE_SSH_KEY and GITTEST_REMOTE_SSH_PASSPHRASE must be set\n"); - return -1; - } - - return git_credential_ssh_key_new(cred, _remote_user, _remote_ssh_pubkey, _remote_ssh_key, _remote_ssh_passphrase); - } - - if (GIT_CREDENTIAL_USERPASS_PLAINTEXT & allowed_types) { - if (!_remote_user || !_remote_pass) { - printf("GITTEST_REMOTE_USER and GITTEST_REMOTE_PASS must be set\n"); - return -1; - } - - return git_credential_userpass_plaintext_new(cred, _remote_user, _remote_pass); - } - - return -1; -} - -/** - * git_push_status_foreach callback that records status entries. - */ -static int record_push_status_cb(const char *ref, const char *msg, void *payload) -{ - record_callbacks_data *data = (record_callbacks_data *) payload; - push_status *s; - - cl_assert(s = git__calloc(1, sizeof(*s))); - if (ref) - cl_assert(s->ref = git__strdup(ref)); - s->success = (msg == NULL); - if (msg) - cl_assert(s->msg = git__strdup(msg)); - - git_vector_insert(&data->statuses, s); - - return 0; -} - -static void do_verify_push_status(record_callbacks_data *data, const push_status expected[], const size_t expected_len) -{ - git_vector *actual = &data->statuses; - push_status *iter; - bool failed = false; - size_t i; - - if (expected_len != actual->length) - failed = true; - else - git_vector_foreach(actual, i, iter) - if (strcmp(expected[i].ref, iter->ref) || - (expected[i].success != iter->success) || - (expected[i].msg && (!iter->msg || strcmp(expected[i].msg, iter->msg)))) { - failed = true; - break; - } - - if (failed) { - git_str msg = GIT_STR_INIT; - - git_str_puts(&msg, "Expected and actual push statuses differ:\nEXPECTED:\n"); - - for(i = 0; i < expected_len; i++) { - git_str_printf(&msg, "%s: %s\n", - expected[i].ref, - expected[i].success ? "success" : "failed"); - } - - git_str_puts(&msg, "\nACTUAL:\n"); - - git_vector_foreach(actual, i, iter) { - if (iter->success) - git_str_printf(&msg, "%s: success\n", iter->ref); - else - git_str_printf(&msg, "%s: failed with message: %s", iter->ref, iter->msg); - } - - cl_fail(git_str_cstr(&msg)); - - git_str_dispose(&msg); - } - - git_vector_foreach(actual, i, iter) { - push_status *s = (push_status *)iter; - git__free(s->ref); - git__free(s->msg); - git__free(s); - } - - git_vector_free(actual); -} - -/** - * Verifies that after git_push_finish(), refs on a remote have the expected - * names, oids, and order. - * - * @param remote remote to verify - * @param expected_refs expected remote refs after push - * @param expected_refs_len length of expected_refs - */ -static void verify_refs(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len) -{ - const git_remote_head **actual_refs; - size_t actual_refs_len; - - git_remote_ls(&actual_refs, &actual_refs_len, remote); - verify_remote_refs(actual_refs, actual_refs_len, expected_refs, expected_refs_len); -} - -/** - * Verifies that after git_push_update_tips(), remote tracking branches have the expected - * names and oids. - * - * @param remote remote to verify - * @param expected_refs expected remote refs after push - * @param expected_refs_len length of expected_refs - */ -static void verify_tracking_branches(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len) -{ - git_refspec *fetch_spec; - size_t i, j; - git_str msg = GIT_STR_INIT; - git_buf ref_name = GIT_BUF_INIT; - git_vector actual_refs = GIT_VECTOR_INIT; - git_branch_iterator *iter; - char *actual_ref; - git_oid oid; - int failed = 0, error; - git_branch_t branch_type; - git_reference *ref; - - /* Get current remote-tracking branches */ - cl_git_pass(git_branch_iterator_new(&iter, remote->repo, GIT_BRANCH_REMOTE)); - - while ((error = git_branch_next(&ref, &branch_type, iter)) == 0) { - cl_assert_equal_i(branch_type, GIT_BRANCH_REMOTE); - - cl_git_pass(git_vector_insert(&actual_refs, git__strdup(git_reference_name(ref)))); - - git_reference_free(ref); - } - - cl_assert_equal_i(error, GIT_ITEROVER); - git_branch_iterator_free(iter); - - /* Loop through expected refs, make sure they exist */ - for (i = 0; i < expected_refs_len; i++) { - - /* Convert remote reference name into remote-tracking branch name. - * If the spec is not under refs/heads/, then skip. - */ - fetch_spec = git_remote__matching_refspec(remote, expected_refs[i].name); - if (!fetch_spec) - continue; - - cl_git_pass(git_refspec_transform(&ref_name, fetch_spec, expected_refs[i].name)); - - /* Find matching remote branch */ - git_vector_foreach(&actual_refs, j, actual_ref) { - if (!strcmp(ref_name.ptr, actual_ref)) - break; - } - - if (j == actual_refs.length) { - git_str_printf(&msg, "Did not find expected tracking branch '%s'.", ref_name.ptr); - failed = 1; - goto failed; - } - - /* Make sure tracking branch is at expected commit ID */ - cl_git_pass(git_reference_name_to_id(&oid, remote->repo, actual_ref)); - - if (git_oid_cmp(expected_refs[i].oid, &oid) != 0) { - git_str_puts(&msg, "Tracking branch commit does not match expected ID."); - failed = 1; - goto failed; - } - - git__free(actual_ref); - cl_git_pass(git_vector_remove(&actual_refs, j)); - } - - /* Make sure there are no extra branches */ - if (actual_refs.length > 0) { - git_str_puts(&msg, "Unexpected remote tracking branches exist."); - failed = 1; - goto failed; - } - -failed: - if (failed) - cl_fail(git_str_cstr(&msg)); - - git_vector_foreach(&actual_refs, i, actual_ref) - git__free(actual_ref); - - git_vector_free(&actual_refs); - git_str_dispose(&msg); - git_buf_dispose(&ref_name); -} - -static void verify_update_tips_callback(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len) -{ - git_refspec *fetch_spec; - git_str msg = GIT_STR_INIT; - git_buf ref_name = GIT_BUF_INIT; - updated_tip *tip = NULL; - size_t i, j; - int failed = 0; - - for (i = 0; i < expected_refs_len; ++i) { - /* Convert remote reference name into tracking branch name. - * If the spec is not under refs/heads/, then skip. - */ - fetch_spec = git_remote__matching_refspec(remote, expected_refs[i].name); - if (!fetch_spec) - continue; - - cl_git_pass(git_refspec_transform(&ref_name, fetch_spec, expected_refs[i].name)); - - /* Find matching update_tip entry */ - git_vector_foreach(&_record_cbs_data.updated_tips, j, tip) { - if (!strcmp(ref_name.ptr, tip->name)) - break; - } - - if (j == _record_cbs_data.updated_tips.length) { - git_str_printf(&msg, "Did not find expected updated tip entry for branch '%s'.", ref_name.ptr); - failed = 1; - goto failed; - } - - if (git_oid_cmp(expected_refs[i].oid, &tip->new_oid) != 0) { - git_str_printf(&msg, "Updated tip ID does not match expected ID"); - failed = 1; - goto failed; - } - } - -failed: - if (failed) - cl_fail(git_str_cstr(&msg)); - - git_buf_dispose(&ref_name); - git_str_dispose(&msg); -} - -void test_online_push__initialize(void) -{ - git_vector delete_specs = GIT_VECTOR_INIT; - const git_remote_head **heads; - size_t heads_len; - git_push_options push_opts = GIT_PUSH_OPTIONS_INIT; - git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; - - _repo = cl_git_sandbox_init("push_src"); - - cl_git_pass(git_repository_set_ident(_repo, "Random J. Hacker", "foo@example.com")); - cl_fixture_sandbox("testrepo.git"); - cl_rename("push_src/submodule/.gitted", "push_src/submodule/.git"); - - rewrite_gitmodules(git_repository_workdir(_repo)); - - /* git log --format=oneline --decorate --graph - * *-. 951bbbb90e2259a4c8950db78946784fb53fcbce (HEAD, b6) merge b3, b4, and b5 to b6 - * |\ \ - * | | * fa38b91f199934685819bea316186d8b008c52a2 (b5) added submodule named 'submodule' pointing to '../testrepo.git' - * | * | 27b7ce66243eb1403862d05f958c002312df173d (b4) edited fold\b.txt - * | |/ - * * | d9b63a88223d8367516f50bd131a5f7349b7f3e4 (b3) edited a.txt - * |/ - * * a78705c3b2725f931d3ee05348d83cc26700f247 (b2, b1) added fold and fold/b.txt - * * 5c0bb3d1b9449d1cc69d7519fd05166f01840915 added a.txt - */ - git_oid_fromstr(&_oid_b6, "951bbbb90e2259a4c8950db78946784fb53fcbce"); - git_oid_fromstr(&_oid_b5, "fa38b91f199934685819bea316186d8b008c52a2"); - git_oid_fromstr(&_oid_b4, "27b7ce66243eb1403862d05f958c002312df173d"); - git_oid_fromstr(&_oid_b3, "d9b63a88223d8367516f50bd131a5f7349b7f3e4"); - git_oid_fromstr(&_oid_b2, "a78705c3b2725f931d3ee05348d83cc26700f247"); - git_oid_fromstr(&_oid_b1, "a78705c3b2725f931d3ee05348d83cc26700f247"); - - git_oid_fromstr(&_tag_commit, "805c54522e614f29f70d2413a0470247d8b424ac"); - git_oid_fromstr(&_tag_tree, "ff83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e"); - git_oid_fromstr(&_tag_blob, "b483ae7ba66decee9aee971f501221dea84b1498"); - git_oid_fromstr(&_tag_lightweight, "951bbbb90e2259a4c8950db78946784fb53fcbce"); - git_oid_fromstr(&_tag_tag, "eea4f2705eeec2db3813f2430829afce99cd00b5"); - - /* Remote URL environment variable must be set. User and password are optional. */ - - _remote_url = cl_getenv("GITTEST_REMOTE_URL"); - _remote_user = cl_getenv("GITTEST_REMOTE_USER"); - _remote_pass = cl_getenv("GITTEST_REMOTE_PASS"); - _remote_ssh_key = cl_getenv("GITTEST_REMOTE_SSH_KEY"); - _remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY"); - _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); - _remote_default = cl_getenv("GITTEST_REMOTE_DEFAULT"); - _remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE"); - _remote = NULL; - - /* Skip the test if we're missing the remote URL */ - if (!_remote_url) - cl_skip(); - - if (_remote_expectcontinue) - git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1); - - cl_git_pass(git_remote_create(&_remote, _repo, "test", _remote_url)); - - record_callbacks_data_clear(&_record_cbs_data); - - cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH, &_record_cbs, NULL, NULL)); - - /* Clean up previously pushed branches. Fails if receive.denyDeletes is - * set on the remote. Also, on Git 1.7.0 and newer, you must run - * 'git config receive.denyDeleteCurrent ignore' in the remote repo in - * order to delete the remote branch pointed to by HEAD (usually master). - * See: https://raw.github.com/git/git/master/Documentation/RelNotes/1.7.0.txt - */ - cl_git_pass(git_remote_ls(&heads, &heads_len, _remote)); - cl_git_pass(create_deletion_refspecs(&delete_specs, heads, heads_len)); - if (delete_specs.length) { - git_strarray arr = { - (char **) delete_specs.contents, - delete_specs.length, - }; - - memcpy(&push_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks)); - cl_git_pass(git_remote_upload(_remote, &arr, &push_opts)); - } - - git_remote_disconnect(_remote); - git_vector_free_deep(&delete_specs); - - /* Now that we've deleted everything, fetch from the remote */ - memcpy(&fetch_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks)); - cl_git_pass(git_remote_fetch(_remote, NULL, &fetch_opts, NULL)); -} - -void test_online_push__cleanup(void) -{ - if (_remote) - git_remote_free(_remote); - _remote = NULL; - - git__free(_remote_url); - git__free(_remote_user); - git__free(_remote_pass); - git__free(_remote_ssh_key); - git__free(_remote_ssh_pubkey); - git__free(_remote_ssh_passphrase); - git__free(_remote_default); - git__free(_remote_expectcontinue); - - /* Freed by cl_git_sandbox_cleanup */ - _repo = NULL; - - git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 0); - - record_callbacks_data_clear(&_record_cbs_data); - - cl_fixture_cleanup("testrepo.git"); - cl_git_sandbox_cleanup(); -} - -static int push_pack_progress_cb( - int stage, unsigned int current, unsigned int total, void* payload) -{ - record_callbacks_data *data = (record_callbacks_data *) payload; - GIT_UNUSED(stage); GIT_UNUSED(current); GIT_UNUSED(total); - if (data->pack_progress_calls < 0) - return data->pack_progress_calls; - - data->pack_progress_calls++; - return 0; -} - -static int push_transfer_progress_cb( - unsigned int current, unsigned int total, size_t bytes, void* payload) -{ - record_callbacks_data *data = (record_callbacks_data *) payload; - GIT_UNUSED(current); GIT_UNUSED(total); GIT_UNUSED(bytes); - if (data->transfer_progress_calls < 0) - return data->transfer_progress_calls; - - data->transfer_progress_calls++; - return 0; -} - -/** - * Calls push and relists refs on remote to verify success. - * - * @param refspecs refspecs to push - * @param refspecs_len length of refspecs - * @param expected_refs expected remote refs after push - * @param expected_refs_len length of expected_refs - * @param expected_ret expected return value from git_push_finish() - * @param check_progress_cb Check that the push progress callbacks are called - */ -static void do_push( - const char *refspecs[], size_t refspecs_len, - push_status expected_statuses[], size_t expected_statuses_len, - expected_ref expected_refs[], size_t expected_refs_len, - int expected_ret, int check_progress_cb, int check_update_tips_cb) -{ - git_push_options opts = GIT_PUSH_OPTIONS_INIT; - size_t i; - int error; - git_strarray specs = {0}; - record_callbacks_data *data; - - if (_remote) { - /* Auto-detect the number of threads to use */ - opts.pb_parallelism = 0; - - memcpy(&opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks)); - data = opts.callbacks.payload; - - opts.callbacks.pack_progress = push_pack_progress_cb; - opts.callbacks.push_transfer_progress = push_transfer_progress_cb; - opts.callbacks.push_update_reference = record_push_status_cb; - - if (refspecs_len) { - specs.count = refspecs_len; - specs.strings = git__calloc(refspecs_len, sizeof(char *)); - cl_assert(specs.strings); - } - - for (i = 0; i < refspecs_len; i++) - specs.strings[i] = (char *) refspecs[i]; - - /* if EUSER, then abort in transfer */ - if (check_progress_cb && expected_ret == GIT_EUSER) - data->transfer_progress_calls = GIT_EUSER; - - error = git_remote_push(_remote, &specs, &opts); - git__free(specs.strings); - - if (expected_ret < 0) { - cl_git_fail_with(expected_ret, error); - } else { - cl_git_pass(error); - } - - if (check_progress_cb && expected_ret == 0) { - cl_assert(data->pack_progress_calls > 0); - cl_assert(data->transfer_progress_calls > 0); - } - - do_verify_push_status(data, expected_statuses, expected_statuses_len); - - verify_refs(_remote, expected_refs, expected_refs_len); - verify_tracking_branches(_remote, expected_refs, expected_refs_len); - - if (check_update_tips_cb) - verify_update_tips_callback(_remote, expected_refs, expected_refs_len); - - } - -} - -/* Call push_finish() without ever calling git_push_add_refspec() */ -void test_online_push__noop(void) -{ - do_push(NULL, 0, NULL, 0, NULL, 0, 0, 0, 1); -} - -void test_online_push__b1(void) -{ - const char *specs[] = { "refs/heads/b1:refs/heads/b1" }; - push_status exp_stats[] = { { "refs/heads/b1", 1 } }; - expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } }; - do_push(specs, ARRAY_SIZE(specs), - exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); -} - -void test_online_push__b2(void) -{ - const char *specs[] = { "refs/heads/b2:refs/heads/b2" }; - push_status exp_stats[] = { { "refs/heads/b2", 1 } }; - expected_ref exp_refs[] = { { "refs/heads/b2", &_oid_b2 } }; - do_push(specs, ARRAY_SIZE(specs), - exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); -} - -void test_online_push__b3(void) -{ - const char *specs[] = { "refs/heads/b3:refs/heads/b3" }; - push_status exp_stats[] = { { "refs/heads/b3", 1 } }; - expected_ref exp_refs[] = { { "refs/heads/b3", &_oid_b3 } }; - do_push(specs, ARRAY_SIZE(specs), - exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); -} - -void test_online_push__b4(void) -{ - const char *specs[] = { "refs/heads/b4:refs/heads/b4" }; - push_status exp_stats[] = { { "refs/heads/b4", 1 } }; - expected_ref exp_refs[] = { { "refs/heads/b4", &_oid_b4 } }; - do_push(specs, ARRAY_SIZE(specs), - exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); -} - -void test_online_push__b5(void) -{ - const char *specs[] = { "refs/heads/b5:refs/heads/b5" }; - push_status exp_stats[] = { { "refs/heads/b5", 1 } }; - expected_ref exp_refs[] = { { "refs/heads/b5", &_oid_b5 } }; - do_push(specs, ARRAY_SIZE(specs), - exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); -} - -void test_online_push__b5_cancel(void) -{ - const char *specs[] = { "refs/heads/b5:refs/heads/b5" }; - do_push(specs, ARRAY_SIZE(specs), NULL, 0, NULL, 0, GIT_EUSER, 1, 1); -} - -void test_online_push__multi(void) -{ - git_reflog *log; - const git_reflog_entry *entry; - - const char *specs[] = { - "refs/heads/b1:refs/heads/b1", - "refs/heads/b2:refs/heads/b2", - "refs/heads/b3:refs/heads/b3", - "refs/heads/b4:refs/heads/b4", - "refs/heads/b5:refs/heads/b5" - }; - push_status exp_stats[] = { - { "refs/heads/b1", 1 }, - { "refs/heads/b2", 1 }, - { "refs/heads/b3", 1 }, - { "refs/heads/b4", 1 }, - { "refs/heads/b5", 1 } - }; - expected_ref exp_refs[] = { - { "refs/heads/b1", &_oid_b1 }, - { "refs/heads/b2", &_oid_b2 }, - { "refs/heads/b3", &_oid_b3 }, - { "refs/heads/b4", &_oid_b4 }, - { "refs/heads/b5", &_oid_b5 } - }; - do_push(specs, ARRAY_SIZE(specs), - exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); - - cl_git_pass(git_reflog_read(&log, _repo, "refs/remotes/test/b1")); - entry = git_reflog_entry_byindex(log, 0); - if (entry) { - cl_assert_equal_s("update by push", git_reflog_entry_message(entry)); - cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email); - } - - git_reflog_free(log); -} - -void test_online_push__implicit_tgt(void) -{ - const char *specs1[] = { "refs/heads/b1" }; - push_status exp_stats1[] = { { "refs/heads/b1", 1 } }; - expected_ref exp_refs1[] = { { "refs/heads/b1", &_oid_b1 } }; - - const char *specs2[] = { "refs/heads/b2" }; - push_status exp_stats2[] = { { "refs/heads/b2", 1 } }; - expected_ref exp_refs2[] = { - { "refs/heads/b1", &_oid_b1 }, - { "refs/heads/b2", &_oid_b2 } - }; - - do_push(specs1, ARRAY_SIZE(specs1), - exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1); - do_push(specs2, ARRAY_SIZE(specs2), - exp_stats2, ARRAY_SIZE(exp_stats2), - exp_refs2, ARRAY_SIZE(exp_refs2), 0, 0, 0); -} - -void test_online_push__fast_fwd(void) -{ - /* Fast forward b1 in tgt from _oid_b1 to _oid_b6. */ - - const char *specs_init[] = { "refs/heads/b1:refs/heads/b1" }; - push_status exp_stats_init[] = { { "refs/heads/b1", 1 } }; - expected_ref exp_refs_init[] = { { "refs/heads/b1", &_oid_b1 } }; - - const char *specs_ff[] = { "refs/heads/b6:refs/heads/b1" }; - push_status exp_stats_ff[] = { { "refs/heads/b1", 1 } }; - expected_ref exp_refs_ff[] = { { "refs/heads/b1", &_oid_b6 } }; - - /* Do a force push to reset b1 in target back to _oid_b1 */ - const char *specs_reset[] = { "+refs/heads/b1:refs/heads/b1" }; - /* Force should have no effect on a fast forward push */ - const char *specs_ff_force[] = { "+refs/heads/b6:refs/heads/b1" }; - - do_push(specs_init, ARRAY_SIZE(specs_init), - exp_stats_init, ARRAY_SIZE(exp_stats_init), - exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 1, 1); - - do_push(specs_ff, ARRAY_SIZE(specs_ff), - exp_stats_ff, ARRAY_SIZE(exp_stats_ff), - exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0); - - do_push(specs_reset, ARRAY_SIZE(specs_reset), - exp_stats_init, ARRAY_SIZE(exp_stats_init), - exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 0, 0); - - do_push(specs_ff_force, ARRAY_SIZE(specs_ff_force), - exp_stats_ff, ARRAY_SIZE(exp_stats_ff), - exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0); -} - -void test_online_push__tag_commit(void) -{ - const char *specs[] = { "refs/tags/tag-commit:refs/tags/tag-commit" }; - push_status exp_stats[] = { { "refs/tags/tag-commit", 1 } }; - expected_ref exp_refs[] = { { "refs/tags/tag-commit", &_tag_commit } }; - do_push(specs, ARRAY_SIZE(specs), - exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); -} - -void test_online_push__tag_tree(void) -{ - const char *specs[] = { "refs/tags/tag-tree:refs/tags/tag-tree" }; - push_status exp_stats[] = { { "refs/tags/tag-tree", 1 } }; - expected_ref exp_refs[] = { { "refs/tags/tag-tree", &_tag_tree } }; - do_push(specs, ARRAY_SIZE(specs), - exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); -} - -void test_online_push__tag_blob(void) -{ - const char *specs[] = { "refs/tags/tag-blob:refs/tags/tag-blob" }; - push_status exp_stats[] = { { "refs/tags/tag-blob", 1 } }; - expected_ref exp_refs[] = { { "refs/tags/tag-blob", &_tag_blob } }; - do_push(specs, ARRAY_SIZE(specs), - exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); -} - -void test_online_push__tag_lightweight(void) -{ - const char *specs[] = { "refs/tags/tag-lightweight:refs/tags/tag-lightweight" }; - push_status exp_stats[] = { { "refs/tags/tag-lightweight", 1 } }; - expected_ref exp_refs[] = { { "refs/tags/tag-lightweight", &_tag_lightweight } }; - do_push(specs, ARRAY_SIZE(specs), - exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); -} - -void test_online_push__tag_to_tag(void) -{ - const char *specs[] = { "refs/tags/tag-tag:refs/tags/tag-tag" }; - push_status exp_stats[] = { { "refs/tags/tag-tag", 1 } }; - expected_ref exp_refs[] = { { "refs/tags/tag-tag", &_tag_tag } }; - do_push(specs, ARRAY_SIZE(specs), - exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 0, 0); -} - -void test_online_push__force(void) -{ - const char *specs1[] = {"refs/heads/b3:refs/heads/tgt"}; - push_status exp_stats1[] = { { "refs/heads/tgt", 1 } }; - expected_ref exp_refs1[] = { { "refs/heads/tgt", &_oid_b3 } }; - - const char *specs2[] = {"refs/heads/b4:refs/heads/tgt"}; - - const char *specs2_force[] = {"+refs/heads/b4:refs/heads/tgt"}; - push_status exp_stats2_force[] = { { "refs/heads/tgt", 1 } }; - expected_ref exp_refs2_force[] = { { "refs/heads/tgt", &_oid_b4 } }; - - do_push(specs1, ARRAY_SIZE(specs1), - exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1); - - do_push(specs2, ARRAY_SIZE(specs2), - NULL, 0, - exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD, 0, 0); - - /* Non-fast-forward update with force should pass. */ - record_callbacks_data_clear(&_record_cbs_data); - do_push(specs2_force, ARRAY_SIZE(specs2_force), - exp_stats2_force, ARRAY_SIZE(exp_stats2_force), - exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0, 1, 1); -} - -void test_online_push__delete(void) -{ - const char *specs1[] = { - "refs/heads/b1:refs/heads/tgt1", - "refs/heads/b1:refs/heads/tgt2" - }; - push_status exp_stats1[] = { - { "refs/heads/tgt1", 1 }, - { "refs/heads/tgt2", 1 } - }; - expected_ref exp_refs1[] = { - { "refs/heads/tgt1", &_oid_b1 }, - { "refs/heads/tgt2", &_oid_b1 } - }; - - const char *specs_del_fake[] = { ":refs/heads/fake" }; - /* Force has no effect for delete. */ - const char *specs_del_fake_force[] = { "+:refs/heads/fake" }; - push_status exp_stats_fake[] = { { "refs/heads/fake", 1 } }; - - const char *specs_delete[] = { ":refs/heads/tgt1" }; - push_status exp_stats_delete[] = { { "refs/heads/tgt1", 1 } }; - expected_ref exp_refs_delete[] = { { "refs/heads/tgt2", &_oid_b1 } }; - /* Force has no effect for delete. */ - const char *specs_delete_force[] = { "+:refs/heads/tgt1" }; - - do_push(specs1, ARRAY_SIZE(specs1), - exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1); - - /* When deleting a non-existent branch, the git client sends zero for both - * the old and new commit id. This should succeed on the server with the - * same status report as if the branch were actually deleted. The server - * returns a warning on the side-band iff the side-band is supported. - * Since libgit2 doesn't support the side-band yet, there are no warnings. - */ - do_push(specs_del_fake, ARRAY_SIZE(specs_del_fake), - exp_stats_fake, 1, - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0); - do_push(specs_del_fake_force, ARRAY_SIZE(specs_del_fake_force), - exp_stats_fake, 1, - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0); - - /* Delete one of the pushed branches. */ - do_push(specs_delete, ARRAY_SIZE(specs_delete), - exp_stats_delete, ARRAY_SIZE(exp_stats_delete), - exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0); - - /* Re-push branches and retry delete with force. */ - do_push(specs1, ARRAY_SIZE(specs1), - exp_stats1, ARRAY_SIZE(exp_stats1), - exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0); - do_push(specs_delete_force, ARRAY_SIZE(specs_delete_force), - exp_stats_delete, ARRAY_SIZE(exp_stats_delete), - exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0); -} - -void test_online_push__bad_refspecs(void) -{ - /* All classes of refspecs that should be rejected by - * git_push_add_refspec() should go in this test. - */ - char *specs = { - "b6:b6", - }; - git_strarray arr = { - &specs, - 1, - }; - - if (_remote) { - cl_git_fail(git_remote_upload(_remote, &arr, NULL)); - } -} - -void test_online_push__expressions(void) -{ - /* TODO: Expressions in refspecs doesn't actually work yet */ - const char *specs_left_expr[] = { "refs/heads/b2~1:refs/heads/b2" }; - - /* TODO: Find a more precise way of checking errors than a exit code of -1. */ - do_push(specs_left_expr, ARRAY_SIZE(specs_left_expr), - NULL, 0, - NULL, 0, -1, 0, 0); -} - -void test_online_push__notes(void) -{ - git_oid note_oid, *target_oid, expected_oid; - git_signature *signature; - const char *specs[] = { "refs/notes/commits:refs/notes/commits" }; - push_status exp_stats[] = { { "refs/notes/commits", 1 } }; - expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } }; - const char *specs_del[] = { ":refs/notes/commits" }; - - git_oid_fromstr(&expected_oid, "8461a99b27b7043e58ff6e1f5d2cf07d282534fb"); - - target_oid = &_oid_b6; - - /* Create note to push */ - cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ - cl_git_pass(git_note_create(¬e_oid, _repo, NULL, signature, signature, target_oid, "hello world\n", 0)); - - do_push(specs, ARRAY_SIZE(specs), - exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); - - /* And make sure to delete the note */ - - do_push(specs_del, ARRAY_SIZE(specs_del), - exp_stats, 1, - NULL, 0, 0, 0, 0); - - git_signature_free(signature); -} - -void test_online_push__configured(void) -{ - git_oid note_oid, *target_oid, expected_oid; - git_signature *signature; - git_remote *old_remote; - const char *specs[] = { "refs/notes/commits:refs/notes/commits" }; - push_status exp_stats[] = { { "refs/notes/commits", 1 } }; - expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } }; - const char *specs_del[] = { ":refs/notes/commits" }; - - git_oid_fromstr(&expected_oid, "8461a99b27b7043e58ff6e1f5d2cf07d282534fb"); - - target_oid = &_oid_b6; - - cl_git_pass(git_remote_add_push(_repo, git_remote_name(_remote), specs[0])); - old_remote = _remote; - cl_git_pass(git_remote_lookup(&_remote, _repo, git_remote_name(_remote))); - git_remote_free(old_remote); - - /* Create note to push */ - cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ - cl_git_pass(git_note_create(¬e_oid, _repo, NULL, signature, signature, target_oid, "hello world\n", 0)); - - do_push(NULL, 0, - exp_stats, ARRAY_SIZE(exp_stats), - exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1); - - /* And make sure to delete the note */ - - do_push(specs_del, ARRAY_SIZE(specs_del), - exp_stats, 1, - NULL, 0, 0, 0, 0); - - git_signature_free(signature); -} diff --git a/tests/online/push_util.c b/tests/online/push_util.c deleted file mode 100644 index cd1831d4c..000000000 --- a/tests/online/push_util.c +++ /dev/null @@ -1,141 +0,0 @@ -#include "clar_libgit2.h" -#include "vector.h" -#include "push_util.h" - -const git_oid OID_ZERO = {{ 0 }}; - -void updated_tip_free(updated_tip *t) -{ - git__free(t->name); - git__free(t); -} - -static void push_status_free(push_status *s) -{ - git__free(s->ref); - git__free(s->msg); - git__free(s); -} - -void record_callbacks_data_clear(record_callbacks_data *data) -{ - size_t i; - updated_tip *tip; - push_status *status; - - git_vector_foreach(&data->updated_tips, i, tip) - updated_tip_free(tip); - - git_vector_free(&data->updated_tips); - - git_vector_foreach(&data->statuses, i, status) - push_status_free(status); - - git_vector_free(&data->statuses); - - data->pack_progress_calls = 0; - data->transfer_progress_calls = 0; -} - -int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data) -{ - updated_tip *t; - record_callbacks_data *record_data = (record_callbacks_data *)data; - - cl_assert(t = git__calloc(1, sizeof(*t))); - - cl_assert(t->name = git__strdup(refname)); - git_oid_cpy(&t->old_oid, a); - git_oid_cpy(&t->new_oid, b); - - git_vector_insert(&record_data->updated_tips, t); - - return 0; -} - -int create_deletion_refspecs(git_vector *out, const git_remote_head **heads, size_t heads_len) -{ - git_str del_spec = GIT_STR_INIT; - int valid; - size_t i; - - for (i = 0; i < heads_len; i++) { - const git_remote_head *head = heads[i]; - /* Ignore malformed ref names (which also saves us from tag^{} */ - cl_git_pass(git_reference_name_is_valid(&valid, head->name)); - if (!valid) - return 0; - - /* Create a refspec that deletes a branch in the remote */ - if (strcmp(head->name, "refs/heads/master")) { - cl_git_pass(git_str_putc(&del_spec, ':')); - cl_git_pass(git_str_puts(&del_spec, head->name)); - cl_git_pass(git_vector_insert(out, git_str_detach(&del_spec))); - } - } - - return 0; -} - -int record_ref_cb(git_remote_head *head, void *payload) -{ - git_vector *refs = (git_vector *) payload; - return git_vector_insert(refs, head); -} - -void verify_remote_refs(const git_remote_head *actual_refs[], size_t actual_refs_len, const expected_ref expected_refs[], size_t expected_refs_len) -{ - size_t i, j = 0; - git_str msg = GIT_STR_INIT; - const git_remote_head *actual; - char *oid_str; - bool master_present = false; - - /* We don't care whether "master" is present on the other end or not */ - for (i = 0; i < actual_refs_len; i++) { - actual = actual_refs[i]; - if (!strcmp(actual->name, "refs/heads/master")) { - master_present = true; - break; - } - } - - if (expected_refs_len + (master_present ? 1 : 0) != actual_refs_len) - goto failed; - - for (i = 0; i < actual_refs_len; i++) { - actual = actual_refs[i]; - if (master_present && !strcmp(actual->name, "refs/heads/master")) - continue; - - if (strcmp(expected_refs[j].name, actual->name) || - git_oid_cmp(expected_refs[j].oid, &actual->oid)) - goto failed; - - j++; - } - - return; - -failed: - git_str_puts(&msg, "Expected and actual refs differ:\nEXPECTED:\n"); - - for(i = 0; i < expected_refs_len; i++) { - oid_str = git_oid_tostr_s(expected_refs[i].oid); - cl_git_pass(git_str_printf(&msg, "%s = %s\n", expected_refs[i].name, oid_str)); - } - - git_str_puts(&msg, "\nACTUAL:\n"); - for (i = 0; i < actual_refs_len; i++) { - actual = actual_refs[i]; - if (master_present && !strcmp(actual->name, "refs/heads/master")) - continue; - - oid_str = git_oid_tostr_s(&actual->oid); - cl_git_pass(git_str_printf(&msg, "%s = %s\n", actual->name, oid_str)); - } - - cl_fail(git_str_cstr(&msg)); - - git_str_dispose(&msg); -} diff --git a/tests/online/push_util.h b/tests/online/push_util.h deleted file mode 100644 index 5f669feaf..000000000 --- a/tests/online/push_util.h +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef INCLUDE_cl_push_util_h__ -#define INCLUDE_cl_push_util_h__ - -#include "git2/oid.h" - -/* Constant for zero oid */ -extern const git_oid OID_ZERO; - -/** - * Macro for initializing git_remote_callbacks to use test helpers that - * record data in a record_callbacks_data instance. - * @param data pointer to a record_callbacks_data instance - */ -#define RECORD_CALLBACKS_INIT(data) \ - { GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, NULL, record_update_tips_cb, NULL, NULL, NULL, NULL, NULL, NULL, data, NULL } - -typedef struct { - char *name; - git_oid old_oid; - git_oid new_oid; -} updated_tip; - -typedef struct { - git_vector updated_tips; - git_vector statuses; - int pack_progress_calls; - int transfer_progress_calls; -} record_callbacks_data; - -typedef struct { - const char *name; - const git_oid *oid; -} expected_ref; - -/* the results of a push status. when used for expected values, msg may be NULL - * to indicate that it should not be matched. */ -typedef struct { - char *ref; - int success; - char *msg; -} push_status; - - -void updated_tip_free(updated_tip *t); - -void record_callbacks_data_clear(record_callbacks_data *data); - -/** - * Callback for git_remote_update_tips that records updates - * - * @param data (git_vector *) of updated_tip instances - */ -int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data); - -/** - * Create a set of refspecs that deletes each of the inputs - * - * @param out the vector in which to store the refspecs - * @param heads the remote heads - * @param heads_len the size of the array - */ -int create_deletion_refspecs(git_vector *out, const git_remote_head **heads, size_t heads_len); - -/** - * Callback for git_remote_list that adds refspecs to vector - * - * @param head a ref on the remote - * @param payload (git_vector *) of git_remote_head instances - */ -int record_ref_cb(git_remote_head *head, void *payload); - -/** - * Verifies that refs on remote stored by record_ref_cb match the expected - * names, oids, and order. - * - * @param actual_refs actual refs in the remote - * @param actual_refs_len length of actual_refs - * @param expected_refs expected remote refs - * @param expected_refs_len length of expected_refs - */ -void verify_remote_refs(const git_remote_head *actual_refs[], size_t actual_refs_len, const expected_ref expected_refs[], size_t expected_refs_len); - -#endif /* INCLUDE_cl_push_util_h__ */ diff --git a/tests/online/remotes.c b/tests/online/remotes.c deleted file mode 100644 index 887874d92..000000000 --- a/tests/online/remotes.c +++ /dev/null @@ -1,127 +0,0 @@ -#include "clar_libgit2.h" - -#define URL "https://github.com/libgit2/TestGitRepository" -#define REFSPEC "refs/heads/first-merge:refs/remotes/origin/first-merge" - -static int remote_single_branch(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload) -{ - GIT_UNUSED(payload); - - cl_git_pass(git_remote_create_with_fetchspec(out, repo, name, url, REFSPEC)); - - return 0; -} - -void test_online_remotes__single_branch(void) -{ - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - git_repository *repo; - git_remote *remote; - git_strarray refs; - size_t i, count = 0; - - opts.remote_cb = remote_single_branch; - opts.checkout_branch = "first-merge"; - - cl_git_pass(git_clone(&repo, URL, "./single-branch", &opts)); - cl_git_pass(git_reference_list(&refs, repo)); - - for (i = 0; i < refs.count; i++) { - if (!git__prefixcmp(refs.strings[i], "refs/heads/")) - count++; - } - cl_assert_equal_i(1, count); - - git_strarray_dispose(&refs); - - cl_git_pass(git_remote_lookup(&remote, repo, "origin")); - cl_git_pass(git_remote_get_fetch_refspecs(&refs, remote)); - - cl_assert_equal_i(1, refs.count); - cl_assert_equal_s(REFSPEC, refs.strings[0]); - - git_strarray_dispose(&refs); - git_remote_free(remote); - git_repository_free(repo); -} - -void test_online_remotes__restricted_refspecs(void) -{ - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - git_repository *repo; - - opts.remote_cb = remote_single_branch; - - cl_git_fail_with(GIT_EINVALIDSPEC, git_clone(&repo, URL, "./restrict-refspec", &opts)); -} - -void test_online_remotes__detached_remote_fails_downloading(void) -{ - git_remote *remote; - - cl_git_pass(git_remote_create_detached(&remote, URL)); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - cl_git_fail(git_remote_download(remote, NULL, NULL)); - - git_remote_free(remote); -} - -void test_online_remotes__detached_remote_fails_uploading(void) -{ - git_remote *remote; - - cl_git_pass(git_remote_create_detached(&remote, URL)); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - cl_git_fail(git_remote_upload(remote, NULL, NULL)); - - git_remote_free(remote); -} - -void test_online_remotes__detached_remote_fails_pushing(void) -{ - git_remote *remote; - - cl_git_pass(git_remote_create_detached(&remote, URL)); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - cl_git_fail(git_remote_push(remote, NULL, NULL)); - - git_remote_free(remote); -} - -void test_online_remotes__detached_remote_succeeds_ls(void) -{ - const char *refs[] = { - "HEAD", - "refs/heads/first-merge", - "refs/heads/master", - "refs/heads/no-parent", - "refs/tags/annotated_tag", - "refs/tags/annotated_tag^{}", - "refs/tags/blob", - "refs/tags/commit_tree", - "refs/tags/nearly-dangling", - }; - const git_remote_head **heads; - git_remote *remote; - size_t i, j, n; - - cl_git_pass(git_remote_create_detached(&remote, URL)); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); - cl_git_pass(git_remote_ls(&heads, &n, remote)); - - cl_assert_equal_sz(n, 9); - for (i = 0; i < n; i++) { - char found = false; - - for (j = 0; j < ARRAY_SIZE(refs); j++) { - if (!strcmp(heads[i]->name, refs[j])) { - found = true; - break; - } - } - - cl_assert_(found, heads[i]->name); - } - - git_remote_free(remote); -} diff --git a/tests/pack/filelimit.c b/tests/pack/filelimit.c deleted file mode 100644 index fa08485fb..000000000 --- a/tests/pack/filelimit.c +++ /dev/null @@ -1,136 +0,0 @@ -#include "clar_libgit2.h" -#include "mwindow.h" - -#include -#include "git2/sys/commit.h" -#include "git2/sys/mempack.h" - -static size_t expected_open_mwindow_files = 0; -static size_t original_mwindow_file_limit = 0; - -extern git_mutex git__mwindow_mutex; -extern git_mwindow_ctl git_mwindow__mem_ctl; - -void test_pack_filelimit__initialize_tiny(void) -{ - expected_open_mwindow_files = 1; - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_MWINDOW_FILE_LIMIT, &original_mwindow_file_limit)); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_MWINDOW_FILE_LIMIT, expected_open_mwindow_files)); -} - -void test_pack_filelimit__initialize_medium(void) -{ - expected_open_mwindow_files = 10; - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_MWINDOW_FILE_LIMIT, &original_mwindow_file_limit)); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_MWINDOW_FILE_LIMIT, expected_open_mwindow_files)); -} - -void test_pack_filelimit__initialize_unlimited(void) -{ - expected_open_mwindow_files = 15; - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_MWINDOW_FILE_LIMIT, &original_mwindow_file_limit)); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_MWINDOW_FILE_LIMIT, 0)); -} - -void test_pack_filelimit__cleanup(void) -{ - git_str path = GIT_STR_INIT; - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_MWINDOW_FILE_LIMIT, original_mwindow_file_limit)); - - cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), "repo.git")); - cl_fixture_cleanup(path.ptr); - git_str_dispose(&path); -} - -/* - * Create a packfile with one commit, one tree, and two blobs. The first blob - * (README.md) has the same content in all commits, but the second one - * (file.txt) has a different content in each commit. - */ -static void create_packfile_commit( - git_repository *repo, - git_oid *out_commit_id, - git_oid *parent_id, - size_t commit_index, - size_t commit_count) -{ - git_str file_contents = GIT_STR_INIT; - git_treebuilder *treebuilder; - git_packbuilder *packbuilder; - git_signature *s; - git_oid oid, tree_id, commit_id; - const git_oid *parents[] = { parent_id }; - size_t parent_count = parent_id ? 1 : 0; - - cl_git_pass(git_treebuilder_new(&treebuilder, repo, NULL)); - - cl_git_pass(git_blob_create_from_buffer(&oid, repo, "", 0)); - cl_git_pass(git_treebuilder_insert(NULL, treebuilder, "README.md", &oid, 0100644)); - - cl_git_pass(git_str_printf(&file_contents, "Commit %zd/%zd", commit_index, commit_count)); - cl_git_pass(git_blob_create_from_buffer(&oid, repo, file_contents.ptr, file_contents.size)); - cl_git_pass(git_treebuilder_insert(NULL, treebuilder, "file.txt", &oid, 0100644)); - - cl_git_pass(git_treebuilder_write(&tree_id, treebuilder)); - cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); - cl_git_pass(git_commit_create_from_ids(&commit_id, repo, "refs/heads/master", s, s, - NULL, file_contents.ptr, &tree_id, parent_count, parents)); - - cl_git_pass(git_packbuilder_new(&packbuilder, repo)); - cl_git_pass(git_packbuilder_insert_commit(packbuilder, &commit_id)); - cl_git_pass(git_packbuilder_write(packbuilder, NULL, 0, NULL, NULL)); - - cl_git_pass(git_oid_cpy(out_commit_id, &commit_id)); - - git_str_dispose(&file_contents); - git_treebuilder_free(treebuilder); - git_packbuilder_free(packbuilder); - git_signature_free(s); -} - -void test_pack_filelimit__open_repo_with_multiple_packfiles(void) -{ - git_str path = GIT_STR_INIT; - git_mwindow_ctl *ctl = &git_mwindow__mem_ctl; - git_repository *repo; - git_revwalk *walk; - git_oid id, *parent_id = NULL; - size_t i; - const size_t commit_count = 16; - unsigned int open_windows; - - /* - * Create a repository and populate it with 16 commits, each in its own - * packfile. - */ - cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), "repo.git")); - cl_git_pass(git_repository_init(&repo, path.ptr, true)); - for (i = 0; i < commit_count; ++i) { - create_packfile_commit(repo, &id, parent_id, i + 1, commit_count); - parent_id = &id; - } - - cl_git_pass(git_revwalk_new(&walk, repo)); - cl_git_pass(git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL)); - cl_git_pass(git_revwalk_push_ref(walk, "refs/heads/master")); - - /* Walking the repository requires eventually opening each of the packfiles. */ - i = 0; - while (git_revwalk_next(&id, walk) == 0) - ++i; - cl_assert_equal_i(commit_count, i); - - cl_git_pass(git_mutex_lock(&git__mwindow_mutex)); - /* - * Adding an assert while holding a lock will cause the whole process to - * deadlock. Copy the value and do the assert after releasing the lock. - */ - open_windows = ctl->open_windows; - cl_git_pass(git_mutex_unlock(&git__mwindow_mutex)); - - cl_assert_equal_i(expected_open_mwindow_files, open_windows); - - git_str_dispose(&path); - git_revwalk_free(walk); - git_repository_free(repo); -} diff --git a/tests/pack/indexer.c b/tests/pack/indexer.c deleted file mode 100644 index ec48ffd98..000000000 --- a/tests/pack/indexer.c +++ /dev/null @@ -1,320 +0,0 @@ -#include "clar_libgit2.h" -#include -#include "futils.h" -#include "hash.h" -#include "iterator.h" -#include "vector.h" -#include "posix.h" - - -/* - * This is a packfile with three objects. The second is a delta which - * depends on the third, which is also a delta. - */ -static const unsigned char out_of_order_pack[] = { - 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, - 0x32, 0x78, 0x9c, 0x63, 0x67, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x76, - 0xe6, 0x8f, 0xe8, 0x12, 0x9b, 0x54, 0x6b, 0x10, 0x1a, 0xee, 0x95, 0x10, - 0xc5, 0x32, 0x8e, 0x7f, 0x21, 0xca, 0x1d, 0x18, 0x78, 0x9c, 0x63, 0x62, - 0x66, 0x4e, 0xcb, 0xcf, 0x07, 0x00, 0x02, 0xac, 0x01, 0x4d, 0x75, 0x01, - 0xd7, 0x71, 0x36, 0x66, 0xf4, 0xde, 0x82, 0x27, 0x76, 0xc7, 0x62, 0x2c, - 0x10, 0xf1, 0xb0, 0x7d, 0xe2, 0x80, 0xdc, 0x78, 0x9c, 0x63, 0x62, 0x62, - 0x62, 0xb7, 0x03, 0x00, 0x00, 0x69, 0x00, 0x4c, 0xde, 0x7d, 0xaa, 0xe4, - 0x19, 0x87, 0x58, 0x80, 0x61, 0x09, 0x9a, 0x33, 0xca, 0x7a, 0x31, 0x92, - 0x6f, 0xae, 0x66, 0x75 -}; -static const unsigned int out_of_order_pack_len = 112; - -/* - * Packfile with two objects. The second is a delta against an object - * which is not in the packfile - */ -static const unsigned char thin_pack[] = { - 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, - 0x32, 0x78, 0x9c, 0x63, 0x67, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x76, - 0xe6, 0x8f, 0xe8, 0x12, 0x9b, 0x54, 0x6b, 0x10, 0x1a, 0xee, 0x95, 0x10, - 0xc5, 0x32, 0x8e, 0x7f, 0x21, 0xca, 0x1d, 0x18, 0x78, 0x9c, 0x63, 0x62, - 0x66, 0x4e, 0xcb, 0xcf, 0x07, 0x00, 0x02, 0xac, 0x01, 0x4d, 0x42, 0x52, - 0x3a, 0x6f, 0x39, 0xd1, 0xfe, 0x66, 0x68, 0x6b, 0xa5, 0xe5, 0xe2, 0x97, - 0xac, 0x94, 0x6c, 0x76, 0x0b, 0x04 -}; -static const unsigned int thin_pack_len = 78; - -/* - * Packfile with one object. It references an object which is not in the - * packfile and has a corrupt length (states the deltified stream is 1 byte - * long, where it is actually 6). - */ -static const unsigned char corrupt_thin_pack[] = { - 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, - 0x71, 0xe6, 0x8f, 0xe8, 0x12, 0x9b, 0x54, 0x6b, 0x10, 0x1a, 0xee, 0x95, - 0x10, 0xc5, 0x32, 0x8e, 0x7f, 0x21, 0xca, 0x1d, 0x18, 0x78, 0x9c, 0x63, - 0x62, 0x66, 0x4e, 0xcb, 0xcf, 0x07, 0x00, 0x02, 0xac, 0x01, 0x4d, 0x07, - 0x67, 0x03, 0xc5, 0x40, 0x99, 0x49, 0xb1, 0x3b, 0x7d, 0xae, 0x9b, 0x0e, - 0xdd, 0xde, 0xc6, 0x76, 0x43, 0x24, 0x64 -}; -static const unsigned int corrupt_thin_pack_len = 67; - -/* - * Packfile with a missing trailer. - */ -static const unsigned char missing_trailer_pack[] = { - 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x50, 0xf4, 0x3b, -}; -static const unsigned int missing_trailer_pack_len = 12; - -/* - * Packfile that causes the packfile stream to open in a way in which it leaks - * the stream reader. - */ -static const unsigned char leaky_pack[] = { - 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, - 0xf4, 0xbd, 0x51, 0x51, 0x51, 0x51, 0x51, 0x72, 0x65, 0x41, 0x4b, 0x63, - 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0xbd, 0x41, 0x4b -}; -static const unsigned int leaky_pack_len = 33; - -/* - * Packfile with a three objects. The first one is a tree referencing two blobs, - * the second object is one of those blobs. The second blob is missing. - */ -unsigned char incomplete_pack[] = { - 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, - 0xae, 0x03, 0x78, 0x9c, 0x33, 0x34, 0x30, 0x30, 0x33, 0x31, 0x51, 0x48, - 0x4a, 0x2c, 0x62, 0x08, 0x17, 0x3b, 0x15, 0xd9, 0x7e, 0xfa, 0x67, 0x6d, - 0xf6, 0x56, 0x4f, 0x85, 0x7d, 0xcb, 0xd6, 0xde, 0x53, 0xd1, 0x6d, 0x7f, - 0x66, 0x08, 0x91, 0x4e, 0xcb, 0xcf, 0x67, 0x50, 0xad, 0x39, 0x9a, 0xa2, - 0xb3, 0x71, 0x41, 0xc8, 0x87, 0x9e, 0x13, 0xf6, 0xba, 0x53, 0xec, 0xc2, - 0xfe, 0xda, 0xed, 0x9b, 0x09, 0x00, 0xe8, 0xc8, 0x19, 0xab, 0x34, 0x78, - 0x9c, 0x4b, 0x4a, 0x2c, 0xe2, 0x02, 0x00, 0x03, 0x9d, 0x01, 0x40, 0x4b, - 0x72, 0xa2, 0x6f, 0xb6, 0x88, 0x2d, 0x6c, 0xa5, 0x07, 0xb2, 0xa5, 0x45, - 0xe8, 0xdb, 0xe6, 0x53, 0xb3, 0x52, 0xe2 -}; -unsigned int incomplete_pack_len = 115; - -static const unsigned char base_obj[] = { 07, 076 }; -static const unsigned int base_obj_len = 2; - -void test_pack_indexer__out_of_order(void) -{ - git_indexer *idx = 0; - git_indexer_progress stats = { 0 }; - - cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); - cl_git_pass(git_indexer_append( - idx, out_of_order_pack, out_of_order_pack_len, &stats)); - cl_git_pass(git_indexer_commit(idx, &stats)); - - cl_assert_equal_i(stats.total_objects, 3); - cl_assert_equal_i(stats.received_objects, 3); - cl_assert_equal_i(stats.indexed_objects, 3); - - git_indexer_free(idx); -} - -void test_pack_indexer__missing_trailer(void) -{ - git_indexer *idx = 0; - git_indexer_progress stats = { 0 }; - - cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); - cl_git_pass(git_indexer_append( - idx, missing_trailer_pack, missing_trailer_pack_len, &stats)); - cl_git_fail(git_indexer_commit(idx, &stats)); - - cl_assert(git_error_last() != NULL); - cl_assert_equal_i(git_error_last()->klass, GIT_ERROR_INDEXER); - - git_indexer_free(idx); -} - -void test_pack_indexer__leaky(void) -{ - git_indexer *idx = 0; - git_indexer_progress stats = { 0 }; - - cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); - cl_git_pass(git_indexer_append( - idx, leaky_pack, leaky_pack_len, &stats)); - cl_git_fail(git_indexer_commit(idx, &stats)); - - cl_assert(git_error_last() != NULL); - cl_assert_equal_i(git_error_last()->klass, GIT_ERROR_INDEXER); - - git_indexer_free(idx); -} - -void test_pack_indexer__fix_thin(void) -{ - git_indexer *idx = NULL; - git_indexer_progress stats = { 0 }; - git_repository *repo; - git_odb *odb; - git_oid id, should_id; - - cl_git_pass(git_repository_init(&repo, "thin.git", true)); - cl_git_pass(git_repository_odb(&odb, repo)); - - /* Store the missing base into your ODB so the indexer can fix the pack */ - cl_git_pass(git_odb_write(&id, odb, base_obj, base_obj_len, GIT_OBJECT_BLOB)); - git_oid_fromstr(&should_id, "e68fe8129b546b101aee9510c5328e7f21ca1d18"); - cl_assert_equal_oid(&should_id, &id); - - cl_git_pass(git_indexer_new(&idx, ".", 0, odb, NULL)); - cl_git_pass(git_indexer_append(idx, thin_pack, thin_pack_len, &stats)); - cl_git_pass(git_indexer_commit(idx, &stats)); - - cl_assert_equal_i(stats.total_objects, 2); - cl_assert_equal_i(stats.received_objects, 2); - cl_assert_equal_i(stats.indexed_objects, 2); - cl_assert_equal_i(stats.local_objects, 1); - - cl_assert_equal_s("fefdb2d740a3a6b6c03a0c7d6ce431c6d5810e13", git_indexer_name(idx)); - - git_indexer_free(idx); - git_odb_free(odb); - git_repository_free(repo); - - /* - * The pack's name/hash only tells us what objects there are, - * so we need to go through the packfile again in order to - * figure out whether we calculated the trailer correctly. - */ - { - unsigned char buffer[128]; - int fd; - ssize_t read; - struct stat st; - const char *name = "pack-fefdb2d740a3a6b6c03a0c7d6ce431c6d5810e13.pack"; - - fd = p_open(name, O_RDONLY); - cl_assert(fd != -1); - - cl_git_pass(p_stat(name, &st)); - - cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); - read = p_read(fd, buffer, sizeof(buffer)); - cl_assert(read != -1); - p_close(fd); - - cl_git_pass(git_indexer_append(idx, buffer, read, &stats)); - cl_git_pass(git_indexer_commit(idx, &stats)); - - cl_assert_equal_i(stats.total_objects, 3); - cl_assert_equal_i(stats.received_objects, 3); - cl_assert_equal_i(stats.indexed_objects, 3); - cl_assert_equal_i(stats.local_objects, 0); - - git_indexer_free(idx); - } -} - -void test_pack_indexer__corrupt_length(void) -{ - git_indexer *idx = NULL; - git_indexer_progress stats = { 0 }; - git_repository *repo; - git_odb *odb; - git_oid id, should_id; - - cl_git_pass(git_repository_init(&repo, "thin.git", true)); - cl_git_pass(git_repository_odb(&odb, repo)); - - /* Store the missing base into your ODB so the indexer can fix the pack */ - cl_git_pass(git_odb_write(&id, odb, base_obj, base_obj_len, GIT_OBJECT_BLOB)); - git_oid_fromstr(&should_id, "e68fe8129b546b101aee9510c5328e7f21ca1d18"); - cl_assert_equal_oid(&should_id, &id); - - cl_git_pass(git_indexer_new(&idx, ".", 0, odb, NULL)); - cl_git_pass(git_indexer_append( - idx, corrupt_thin_pack, corrupt_thin_pack_len, &stats)); - cl_git_fail(git_indexer_commit(idx, &stats)); - - cl_assert(git_error_last() != NULL); - cl_assert_equal_i(git_error_last()->klass, GIT_ERROR_ZLIB); - - git_indexer_free(idx); - git_odb_free(odb); - git_repository_free(repo); -} - -void test_pack_indexer__incomplete_pack_fails_with_strict(void) -{ - git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; - git_indexer *idx = 0; - git_indexer_progress stats = { 0 }; - - opts.verify = 1; - - cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, &opts)); - cl_git_pass(git_indexer_append( - idx, incomplete_pack, incomplete_pack_len, &stats)); - cl_git_fail(git_indexer_commit(idx, &stats)); - - cl_assert_equal_i(stats.total_objects, 2); - cl_assert_equal_i(stats.received_objects, 2); - cl_assert_equal_i(stats.indexed_objects, 2); - - git_indexer_free(idx); -} - -void test_pack_indexer__out_of_order_with_connectivity_checks(void) -{ - git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; - git_indexer *idx = 0; - git_indexer_progress stats = { 0 }; - - opts.verify = 1; - - cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, &opts)); - cl_git_pass(git_indexer_append( - idx, out_of_order_pack, out_of_order_pack_len, &stats)); - cl_git_pass(git_indexer_commit(idx, &stats)); - - cl_assert_equal_i(stats.total_objects, 3); - cl_assert_equal_i(stats.received_objects, 3); - cl_assert_equal_i(stats.indexed_objects, 3); - - git_indexer_free(idx); -} - -static int find_tmp_file_recurs(void *opaque, git_str *path) -{ - int error = 0; - git_str *first_tmp_file = opaque; - struct stat st; - - if ((error = p_lstat_posixly(path->ptr, &st)) < 0) - return error; - - if (S_ISDIR(st.st_mode)) - return git_fs_path_direach(path, 0, find_tmp_file_recurs, opaque); - - /* This is the template that's used in git_futils_mktmp. */ - if (strstr(git_str_cstr(path), "_git2_") != NULL) - return git_str_sets(first_tmp_file, git_str_cstr(path)); - - return 0; -} - -void test_pack_indexer__no_tmp_files(void) -{ - git_indexer *idx = NULL; - git_str path = GIT_STR_INIT; - git_str first_tmp_file = GIT_STR_INIT; - - /* Precondition: there are no temporary files. */ - cl_git_pass(git_str_sets(&path, clar_sandbox_path())); - cl_git_pass(find_tmp_file_recurs(&first_tmp_file, &path)); - git_str_dispose(&path); - cl_assert(git_str_len(&first_tmp_file) == 0); - - cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); - git_indexer_free(idx); - - cl_git_pass(git_str_sets(&path, clar_sandbox_path())); - cl_git_pass(find_tmp_file_recurs(&first_tmp_file, &path)); - git_str_dispose(&path); - cl_assert(git_str_len(&first_tmp_file) == 0); - git_str_dispose(&first_tmp_file); -} diff --git a/tests/pack/midx.c b/tests/pack/midx.c deleted file mode 100644 index 9dd949363..000000000 --- a/tests/pack/midx.c +++ /dev/null @@ -1,111 +0,0 @@ -#include "clar_libgit2.h" - -#include -#include - -#include "futils.h" -#include "midx.h" - -void test_pack_midx__parse(void) -{ - git_repository *repo; - struct git_midx_file *idx; - struct git_midx_entry e; - git_oid id; - git_str midx_path = GIT_STR_INIT; - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_str_joinpath(&midx_path, git_repository_path(repo), "objects/pack/multi-pack-index")); - cl_git_pass(git_midx_open(&idx, git_str_cstr(&midx_path))); - cl_assert_equal_i(git_midx_needs_refresh(idx, git_str_cstr(&midx_path)), 0); - - cl_git_pass(git_oid_fromstr(&id, "5001298e0c09ad9c34e4249bc5801c75e9754fa5")); - cl_git_pass(git_midx_entry_find(&e, idx, &id, GIT_OID_HEXSZ)); - cl_assert_equal_oid(&e.sha1, &id); - cl_assert_equal_s( - (const char *)git_vector_get(&idx->packfile_names, e.pack_index), - "pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx"); - - git_midx_free(idx); - git_repository_free(repo); - git_str_dispose(&midx_path); -} - -void test_pack_midx__lookup(void) -{ - git_repository *repo; - git_commit *commit; - git_oid id; - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - - cl_git_pass(git_oid_fromstr(&id, "5001298e0c09ad9c34e4249bc5801c75e9754fa5")); - cl_git_pass(git_commit_lookup_prefix(&commit, repo, &id, GIT_OID_HEXSZ)); - cl_assert_equal_s(git_commit_message(commit), "packed commit one\n"); - - git_commit_free(commit); - git_repository_free(repo); -} - -void test_pack_midx__writer(void) -{ - git_repository *repo; - git_midx_writer *w = NULL; - git_buf midx = GIT_BUF_INIT; - git_str expected_midx = GIT_STR_INIT, path = GIT_STR_INIT; - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - - cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "objects/pack")); - cl_git_pass(git_midx_writer_new(&w, git_str_cstr(&path))); - - cl_git_pass(git_midx_writer_add(w, "pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx")); - cl_git_pass(git_midx_writer_add(w, "pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx")); - cl_git_pass(git_midx_writer_add(w, "pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx")); - - cl_git_pass(git_midx_writer_dump(&midx, w)); - cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "objects/pack/multi-pack-index")); - cl_git_pass(git_futils_readbuffer(&expected_midx, git_str_cstr(&path))); - - cl_assert_equal_i(midx.size, git_str_len(&expected_midx)); - cl_assert_equal_strn(midx.ptr, git_str_cstr(&expected_midx), midx.size); - - git_buf_dispose(&midx); - git_str_dispose(&expected_midx); - git_str_dispose(&path); - git_midx_writer_free(w); - git_repository_free(repo); -} - -void test_pack_midx__odb_create(void) -{ - git_repository *repo; - git_odb *odb; - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - git_str midx = GIT_STR_INIT, expected_midx = GIT_STR_INIT, midx_path = GIT_STR_INIT; - struct stat st; - - opts.bare = true; - opts.local = GIT_CLONE_LOCAL; - cl_git_pass(git_clone(&repo, cl_fixture("testrepo/.gitted"), "./clone.git", &opts)); - cl_git_pass(git_str_joinpath(&midx_path, git_repository_path(repo), "objects/pack/multi-pack-index")); - cl_git_fail(p_stat(git_str_cstr(&midx_path), &st)); - - cl_git_pass(git_repository_odb(&odb, repo)); - cl_git_pass(git_odb_write_multi_pack_index(odb)); - git_odb_free(odb); - - cl_git_pass(p_stat(git_str_cstr(&midx_path), &st)); - - cl_git_pass(git_futils_readbuffer(&expected_midx, cl_fixture("testrepo.git/objects/pack/multi-pack-index"))); - cl_git_pass(git_futils_readbuffer(&midx, git_str_cstr(&midx_path))); - cl_assert_equal_i(git_str_len(&midx), git_str_len(&expected_midx)); - cl_assert_equal_strn(git_str_cstr(&midx), git_str_cstr(&expected_midx), git_str_len(&midx)); - - git_repository_free(repo); - git_str_dispose(&midx); - git_str_dispose(&midx_path); - git_str_dispose(&expected_midx); - - cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); -} diff --git a/tests/pack/packbuilder.c b/tests/pack/packbuilder.c deleted file mode 100644 index f23579817..000000000 --- a/tests/pack/packbuilder.c +++ /dev/null @@ -1,276 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "pack.h" -#include "hash.h" -#include "iterator.h" -#include "vector.h" -#include "posix.h" -#include "hash.h" - -static git_repository *_repo; -static git_revwalk *_revwalker; -static git_packbuilder *_packbuilder; -static git_indexer *_indexer; -static git_vector _commits; -static int _commits_is_initialized; -static git_indexer_progress _stats; - -extern bool git_disable_pack_keep_file_checks; - -void test_pack_packbuilder__initialize(void) -{ - _repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(p_chdir("testrepo.git")); - cl_git_pass(git_revwalk_new(&_revwalker, _repo)); - cl_git_pass(git_packbuilder_new(&_packbuilder, _repo)); - cl_git_pass(git_vector_init(&_commits, 0, NULL)); - _commits_is_initialized = 1; - memset(&_stats, 0, sizeof(_stats)); - p_fsync__cnt = 0; -} - -void test_pack_packbuilder__cleanup(void) -{ - git_oid *o; - unsigned int i; - - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 0)); - cl_git_pass(git_libgit2_opts(GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, false)); - - if (_commits_is_initialized) { - _commits_is_initialized = 0; - git_vector_foreach(&_commits, i, o) { - git__free(o); - } - git_vector_free(&_commits); - } - - git_packbuilder_free(_packbuilder); - _packbuilder = NULL; - - git_revwalk_free(_revwalker); - _revwalker = NULL; - - git_indexer_free(_indexer); - _indexer = NULL; - - cl_git_pass(p_chdir("..")); - cl_git_sandbox_cleanup(); - _repo = NULL; -} - -static void seed_packbuilder(void) -{ - git_oid oid, *o; - unsigned int i; - - git_revwalk_sorting(_revwalker, GIT_SORT_TIME); - cl_git_pass(git_revwalk_push_ref(_revwalker, "HEAD")); - - while (git_revwalk_next(&oid, _revwalker) == 0) { - o = git__malloc(GIT_OID_RAWSZ); - cl_assert(o != NULL); - git_oid_cpy(o, &oid); - cl_git_pass(git_vector_insert(&_commits, o)); - } - - git_vector_foreach(&_commits, i, o) { - cl_git_pass(git_packbuilder_insert(_packbuilder, o, NULL)); - } - - git_vector_foreach(&_commits, i, o) { - git_object *obj; - cl_git_pass(git_object_lookup(&obj, _repo, o, GIT_OBJECT_COMMIT)); - cl_git_pass(git_packbuilder_insert_tree(_packbuilder, - git_commit_tree_id((git_commit *)obj))); - git_object_free(obj); - } -} - -static int feed_indexer(void *ptr, size_t len, void *payload) -{ - git_indexer_progress *stats = (git_indexer_progress *)payload; - - return git_indexer_append(_indexer, ptr, len, stats); -} - -void test_pack_packbuilder__create_pack(void) -{ - git_indexer_progress stats; - git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; - git_hash_ctx ctx; - unsigned char hash[GIT_HASH_SHA1_SIZE]; - char hex[(GIT_HASH_SHA1_SIZE * 2) + 1]; - - seed_packbuilder(); - - cl_git_pass(git_indexer_new(&_indexer, ".", 0, NULL, NULL)); - cl_git_pass(git_packbuilder_foreach(_packbuilder, feed_indexer, &stats)); - cl_git_pass(git_indexer_commit(_indexer, &stats)); - - git_str_printf(&path, "pack-%s.pack", git_indexer_name(_indexer)); - - /* - * By default, packfiles are created with only one thread. - * Therefore we can predict the object ordering and make sure - * we create exactly the same pack as git.git does when *not* - * reusing existing deltas (as libgit2). - * - * $ cd tests/resources/testrepo.git - * $ git rev-list --objects HEAD | \ - * git pack-objects -q --no-reuse-delta --threads=1 pack - * $ sha1sum pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.pack - * 5d410bdf97cf896f9007681b92868471d636954b - * - */ - - cl_git_pass(git_futils_readbuffer(&buf, git_str_cstr(&path))); - - cl_git_pass(git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)); - cl_git_pass(git_hash_update(&ctx, buf.ptr, buf.size)); - cl_git_pass(git_hash_final(hash, &ctx)); - git_hash_ctx_cleanup(&ctx); - - git_str_dispose(&path); - git_str_dispose(&buf); - - git_hash_fmt(hex, hash, GIT_HASH_SHA1_SIZE); - cl_assert_equal_s(hex, "5d410bdf97cf896f9007681b92868471d636954b"); -} - -void test_pack_packbuilder__get_name(void) -{ - seed_packbuilder(); - - cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0, NULL, NULL)); - cl_assert_equal_s("7f5fa362c664d68ba7221259be1cbd187434b2f0", git_packbuilder_name(_packbuilder)); -} - -void test_pack_packbuilder__write_default_path(void) -{ - seed_packbuilder(); - - cl_git_pass(git_packbuilder_write(_packbuilder, NULL, 0, NULL, NULL)); - cl_assert(git_fs_path_exists("objects/pack/pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.idx")); - cl_assert(git_fs_path_exists("objects/pack/pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.pack")); -} - -static void test_write_pack_permission(mode_t given, mode_t expected) -{ - struct stat statbuf; - mode_t mask, os_mask; - - seed_packbuilder(); - - cl_git_pass(git_packbuilder_write(_packbuilder, ".", given, NULL, NULL)); - - /* Windows does not return group/user bits from stat, - * files are never executable. - */ -#ifdef GIT_WIN32 - os_mask = 0600; -#else - os_mask = 0777; -#endif - - mask = p_umask(0); - p_umask(mask); - - cl_git_pass(p_stat("pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.idx", &statbuf)); - cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask); - - cl_git_pass(p_stat("pack-7f5fa362c664d68ba7221259be1cbd187434b2f0.pack", &statbuf)); - cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask); -} - -void test_pack_packbuilder__permissions_standard(void) -{ - test_write_pack_permission(0, GIT_PACK_FILE_MODE); -} - -void test_pack_packbuilder__permissions_readonly(void) -{ - test_write_pack_permission(0444, 0444); -} - -void test_pack_packbuilder__permissions_readwrite(void) -{ - test_write_pack_permission(0666, 0666); -} - -void test_pack_packbuilder__does_not_fsync_by_default(void) -{ - seed_packbuilder(); - cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0666, NULL, NULL)); - cl_assert_equal_sz(0, p_fsync__cnt); -} - -/* We fsync the packfile and index. On non-Windows, we also fsync - * the parent directories. - */ -#ifdef GIT_WIN32 -static int expected_fsyncs = 2; -#else -static int expected_fsyncs = 4; -#endif - -void test_pack_packbuilder__fsync_global_setting(void) -{ - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 1)); - p_fsync__cnt = 0; - seed_packbuilder(); - cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0666, NULL, NULL)); - cl_assert_equal_sz(expected_fsyncs, p_fsync__cnt); -} - -void test_pack_packbuilder__fsync_repo_setting(void) -{ - cl_repo_set_bool(_repo, "core.fsyncObjectFiles", true); - p_fsync__cnt = 0; - seed_packbuilder(); - cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0666, NULL, NULL)); - cl_assert_equal_sz(expected_fsyncs, p_fsync__cnt); -} - -static int foreach_cb(void *buf, size_t len, void *payload) -{ - git_indexer *idx = (git_indexer *) payload; - cl_git_pass(git_indexer_append(idx, buf, len, &_stats)); - return 0; -} - -void test_pack_packbuilder__foreach(void) -{ - git_indexer *idx; - - seed_packbuilder(); - cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); - cl_git_pass(git_packbuilder_foreach(_packbuilder, foreach_cb, idx)); - cl_git_pass(git_indexer_commit(idx, &_stats)); - git_indexer_free(idx); -} - -static int foreach_cancel_cb(void *buf, size_t len, void *payload) -{ - git_indexer *idx = (git_indexer *)payload; - cl_git_pass(git_indexer_append(idx, buf, len, &_stats)); - return (_stats.total_objects > 2) ? -1111 : 0; -} - -void test_pack_packbuilder__foreach_with_cancel(void) -{ - git_indexer *idx; - - seed_packbuilder(); - cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); - cl_git_fail_with( - git_packbuilder_foreach(_packbuilder, foreach_cancel_cb, idx), -1111); - git_indexer_free(idx); -} - -void test_pack_packbuilder__keep_file_check(void) -{ - assert(!git_disable_pack_keep_file_checks); - cl_git_pass(git_libgit2_opts(GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, true)); - assert(git_disable_pack_keep_file_checks); -} diff --git a/tests/pack/sharing.c b/tests/pack/sharing.c deleted file mode 100644 index eaf7686b7..000000000 --- a/tests/pack/sharing.c +++ /dev/null @@ -1,42 +0,0 @@ -#include "clar_libgit2.h" -#include -#include "strmap.h" -#include "mwindow.h" -#include "pack.h" - -extern git_strmap *git__pack_cache; - -void test_pack_sharing__open_two_repos(void) -{ - git_repository *repo1, *repo2; - git_object *obj1, *obj2; - git_oid id; - size_t pos; - void *data; - int error; - - cl_git_pass(git_repository_open(&repo1, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_open(&repo2, cl_fixture("testrepo.git"))); - - git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - - cl_git_pass(git_object_lookup(&obj1, repo1, &id, GIT_OBJECT_ANY)); - cl_git_pass(git_object_lookup(&obj2, repo2, &id, GIT_OBJECT_ANY)); - - pos = 0; - while ((error = git_strmap_iterate(&data, git__pack_cache, &pos, NULL)) == 0) { - struct git_pack_file *pack = (struct git_pack_file *) data; - - cl_assert_equal_i(2, pack->refcount.val); - } - - cl_assert_equal_i(3, git_strmap_size(git__pack_cache)); - - git_object_free(obj1); - git_object_free(obj2); - git_repository_free(repo1); - git_repository_free(repo2); - - /* we don't want to keep the packs open after the repos go away */ - cl_assert_equal_i(0, git_strmap_size(git__pack_cache)); -} diff --git a/tests/pack/threadsafety.c b/tests/pack/threadsafety.c deleted file mode 100644 index fd6a61fbd..000000000 --- a/tests/pack/threadsafety.c +++ /dev/null @@ -1,62 +0,0 @@ -#include "clar_libgit2.h" -#include "pool.h" - -#include -#include "git2/sys/commit.h" -#include "git2/sys/mempack.h" - -static size_t original_mwindow_file_limit = 0; - -void test_pack_threadsafety__initialize(void) -{ - size_t open_mwindow_files = 1; - - cl_git_pass(git_libgit2_opts(GIT_OPT_GET_MWINDOW_FILE_LIMIT, &original_mwindow_file_limit)); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_MWINDOW_FILE_LIMIT, open_mwindow_files)); -} - -void test_pack_threadsafety__cleanup(void) -{ - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_MWINDOW_FILE_LIMIT, original_mwindow_file_limit)); -} - -#ifdef GIT_THREADS -static void *get_status(void *arg) -{ - const char *repo_path = (const char *)arg; - git_repository *repo; - git_status_list *status; - - cl_git_pass(git_repository_open(&repo, repo_path)); - cl_git_pass(git_status_list_new(&status, repo, NULL)); - git_status_list_free(status); - git_repository_free(repo); - - return NULL; -} -#endif - -void test_pack_threadsafety__open_repo_in_multiple_threads(void) -{ -#ifdef GIT_THREADS - const char *repo_path = cl_fixture("../.."); - git_repository *repo; - git_thread threads[8]; - size_t i; - - /* If we can't open the libgit2 repo or if it isn't a full repo - * with proper history, just skip this test */ - if (git_repository_open(&repo, repo_path) < 0) - cl_skip(); - if (git_repository_is_shallow(repo)) - cl_skip(); - git_repository_free(repo); - - for (i = 0; i < ARRAY_SIZE(threads); i++) - git_thread_create(&threads[i], get_status, (void *)repo_path); - for (i = 0; i < ARRAY_SIZE(threads); i++) - git_thread_join(&threads[i], NULL); -#else - cl_skip(); -#endif -} diff --git a/tests/patch/parse.c b/tests/patch/parse.c deleted file mode 100644 index a3c4c6730..000000000 --- a/tests/patch/parse.c +++ /dev/null @@ -1,221 +0,0 @@ -#include "clar_libgit2.h" -#include "patch.h" -#include "patch_parse.h" - -#include "patch_common.h" - -static void ensure_patch_validity(git_patch *patch) -{ - const git_diff_delta *delta; - char idstr[GIT_OID_HEXSZ+1] = {0}; - - cl_assert((delta = git_patch_get_delta(patch)) != NULL); - cl_assert_equal_i(2, delta->nfiles); - - cl_assert_equal_s(delta->old_file.path, "file.txt"); - cl_assert(delta->old_file.mode == GIT_FILEMODE_BLOB); - cl_assert_equal_i(7, delta->old_file.id_abbrev); - git_oid_nfmt(idstr, delta->old_file.id_abbrev, &delta->old_file.id); - cl_assert_equal_s(idstr, "9432026"); - cl_assert_equal_i(0, delta->old_file.size); - - cl_assert_equal_s(delta->new_file.path, "file.txt"); - cl_assert(delta->new_file.mode == GIT_FILEMODE_BLOB); - cl_assert_equal_i(7, delta->new_file.id_abbrev); - git_oid_nfmt(idstr, delta->new_file.id_abbrev, &delta->new_file.id); - cl_assert_equal_s(idstr, "cd8fd12"); - cl_assert_equal_i(0, delta->new_file.size); -} - -static void ensure_identical_patch_inout(const char *content) -{ - git_buf buf = GIT_BUF_INIT; - git_patch *patch; - - cl_git_pass(git_patch_from_buffer(&patch, content, strlen(content), NULL)); - cl_git_pass(git_patch_to_buf(&buf, patch)); - cl_assert_equal_strn(buf.ptr, content, strlen(content)); - - git_patch_free(patch); - git_buf_dispose(&buf); -} - -void test_patch_parse__original_to_change_middle(void) -{ - git_patch *patch; - - cl_git_pass(git_patch_from_buffer( - &patch, PATCH_ORIGINAL_TO_CHANGE_MIDDLE, - strlen(PATCH_ORIGINAL_TO_CHANGE_MIDDLE), NULL)); - ensure_patch_validity(patch); - git_patch_free(patch); -} - -void test_patch_parse__leading_and_trailing_garbage(void) -{ - git_patch *patch; - const char *leading = "This is some leading garbage.\n" - "Maybe it's email headers?\n" - "\n" - PATCH_ORIGINAL_TO_CHANGE_MIDDLE; - const char *trailing = PATCH_ORIGINAL_TO_CHANGE_MIDDLE - "\n" - "This is some trailing garbage.\n" - "Maybe it's an email signature?\n"; - const char *both = "Here's some leading garbage\n" - PATCH_ORIGINAL_TO_CHANGE_MIDDLE - "And here's some trailing.\n"; - - cl_git_pass(git_patch_from_buffer(&patch, leading, strlen(leading), - NULL)); - ensure_patch_validity(patch); - git_patch_free(patch); - - cl_git_pass(git_patch_from_buffer(&patch, trailing, strlen(trailing), - NULL)); - ensure_patch_validity(patch); - git_patch_free(patch); - - cl_git_pass(git_patch_from_buffer(&patch, both, strlen(both), - NULL)); - ensure_patch_validity(patch); - git_patch_free(patch); -} - -void test_patch_parse__nonpatches_fail_with_notfound(void) -{ - git_patch *patch; - - cl_git_fail_with(GIT_ENOTFOUND, - git_patch_from_buffer(&patch, PATCH_NOT_A_PATCH, - strlen(PATCH_NOT_A_PATCH), NULL)); -} - -void test_patch_parse__invalid_patches_fails(void) -{ - git_patch *patch; - - cl_git_fail_with(GIT_ERROR, - git_patch_from_buffer(&patch, PATCH_CORRUPT_GIT_HEADER, - strlen(PATCH_CORRUPT_GIT_HEADER), NULL)); - cl_git_fail_with(GIT_ERROR, - git_patch_from_buffer(&patch, - PATCH_CORRUPT_MISSING_NEW_FILE, - strlen(PATCH_CORRUPT_MISSING_NEW_FILE), NULL)); - cl_git_fail_with(GIT_ERROR, - git_patch_from_buffer(&patch, - PATCH_CORRUPT_MISSING_OLD_FILE, - strlen(PATCH_CORRUPT_MISSING_OLD_FILE), NULL)); - cl_git_fail_with(GIT_ERROR, - git_patch_from_buffer(&patch, PATCH_CORRUPT_NO_CHANGES, - strlen(PATCH_CORRUPT_NO_CHANGES), NULL)); - cl_git_fail_with(GIT_ERROR, - git_patch_from_buffer(&patch, - PATCH_CORRUPT_MISSING_HUNK_HEADER, - strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL)); -} - -void test_patch_parse__no_newline_at_end_of_new_file(void) -{ - ensure_identical_patch_inout(PATCH_APPEND_NO_NL); -} - -void test_patch_parse__no_newline_at_end_of_old_file(void) -{ - ensure_identical_patch_inout(PATCH_APPEND_NO_NL_IN_OLD_FILE); -} - -void test_patch_parse__files_with_whitespaces_succeeds(void) -{ - ensure_identical_patch_inout(PATCH_NAME_WHITESPACE); -} - -void test_patch_parse__lifetime_of_patch_does_not_depend_on_buffer(void) -{ - git_str diff = GIT_STR_INIT; - git_buf rendered = GIT_BUF_INIT; - git_patch *patch; - - cl_git_pass(git_str_sets(&diff, PATCH_ORIGINAL_TO_CHANGE_MIDDLE)); - cl_git_pass(git_patch_from_buffer(&patch, diff.ptr, diff.size, NULL)); - git_str_dispose(&diff); - - cl_git_pass(git_patch_to_buf(&rendered, patch)); - cl_assert_equal_s(PATCH_ORIGINAL_TO_CHANGE_MIDDLE, rendered.ptr); - git_buf_dispose(&rendered); - - cl_git_pass(git_patch_to_buf(&rendered, patch)); - cl_assert_equal_s(PATCH_ORIGINAL_TO_CHANGE_MIDDLE, rendered.ptr); - git_buf_dispose(&rendered); - - git_patch_free(patch); -} - -void test_patch_parse__binary_file_with_missing_paths(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_MISSING_PATHS, - strlen(PATCH_BINARY_FILE_WITH_MISSING_PATHS), NULL)); -} - -void test_patch_parse__binary_file_with_whitespace_paths(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_WHITESPACE_PATHS, - strlen(PATCH_BINARY_FILE_WITH_WHITESPACE_PATHS), NULL)); -} - -void test_patch_parse__binary_file_with_empty_quoted_paths(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_QUOTED_EMPTY_PATHS, - strlen(PATCH_BINARY_FILE_WITH_QUOTED_EMPTY_PATHS), NULL)); -} - -void test_patch_parse__binary_file_path_with_spaces(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_PATH_WITH_SPACES, - strlen(PATCH_BINARY_FILE_PATH_WITH_SPACES), NULL)); -} - -void test_patch_parse__binary_file_path_without_body_paths(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_PATH_WITHOUT_BODY_PATHS, - strlen(PATCH_BINARY_FILE_PATH_WITHOUT_BODY_PATHS), NULL)); -} - -void test_patch_parse__binary_file_with_truncated_delta(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_TRUNCATED_DELTA, - strlen(PATCH_BINARY_FILE_WITH_TRUNCATED_DELTA), NULL)); - cl_assert_equal_s(git_error_last()->message, "truncated binary data at line 5"); -} - -void test_patch_parse__memory_leak_on_multiple_paths(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_MULTIPLE_OLD_PATHS, strlen(PATCH_MULTIPLE_OLD_PATHS), NULL)); -} - -void test_patch_parse__truncated_no_newline_at_end_of_file(void) -{ - size_t len = strlen(PATCH_APPEND_NO_NL) - strlen("at end of file\n"); - const git_diff_line *line; - git_patch *patch; - - cl_git_pass(git_patch_from_buffer(&patch, PATCH_APPEND_NO_NL, len, NULL)); - cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 4)); - cl_assert_equal_s(line->content, "\\ No newline "); - - git_patch_free(patch); -} - -void test_patch_parse__line_number_overflow(void) -{ - git_patch *patch; - cl_git_fail(git_patch_from_buffer(&patch, PATCH_INTMAX_NEW_LINES, strlen(PATCH_INTMAX_NEW_LINES), NULL)); - git_patch_free(patch); -} diff --git a/tests/patch/patch_common.h b/tests/patch/patch_common.h deleted file mode 100644 index 1e03889fc..000000000 --- a/tests/patch/patch_common.h +++ /dev/null @@ -1,1005 +0,0 @@ -/* The original file contents */ - -#define FILE_ORIGINAL \ - "hey!\n" \ - "this is some context!\n" \ - "around some lines\n" \ - "that will change\n" \ - "yes it is!\n" \ - "(this line is changed)\n" \ - "and this\n" \ - "is additional context\n" \ - "below it!\n" - -/* A change in the middle of the file (and the resultant patch) */ - -#define FILE_CHANGE_MIDDLE \ - "hey!\n" \ - "this is some context!\n" \ - "around some lines\n" \ - "that will change\n" \ - "yes it is!\n" \ - "(THIS line is changed!)\n" \ - "and this\n" \ - "is additional context\n" \ - "below it!\n" - -#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..cd8fd12 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -3,7 +3,7 @@ this is some context!\n" \ - " around some lines\n" \ - " that will change\n" \ - " yes it is!\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" \ - " and this\n" \ - " is additional context\n" \ - " below it!\n" - -#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..cd8fd12 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -6 +6 @@ yes it is!\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" - -/* A change of the first line (and the resultant patch) */ - -#define FILE_CHANGE_FIRSTLINE \ - "hey, change in head!\n" \ - "this is some context!\n" \ - "around some lines\n" \ - "that will change\n" \ - "yes it is!\n" \ - "(this line is changed)\n" \ - "and this\n" \ - "is additional context\n" \ - "below it!\n" - -#define PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..c81df1d 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-hey!\n" \ - "+hey, change in head!\n" \ - " this is some context!\n" \ - " around some lines\n" \ - " that will change\n" - -/* A change of the last line (and the resultant patch) */ - -#define FILE_CHANGE_LASTLINE \ - "hey!\n" \ - "this is some context!\n" \ - "around some lines\n" \ - "that will change\n" \ - "yes it is!\n" \ - "(this line is changed)\n" \ - "and this\n" \ - "is additional context\n" \ - "change to the last line.\n" - -#define PATCH_ORIGINAL_TO_CHANGE_LASTLINE \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..f70db1c 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -6,4 +6,4 @@ yes it is!\n" \ - " (this line is changed)\n" \ - " and this\n" \ - " is additional context\n" \ - "-below it!\n" \ - "+change to the last line.\n" - -/* A change of the middle where we remove many lines */ - -#define FILE_CHANGE_MIDDLE_SHRINK \ - "hey!\n" \ - "i've changed a lot, but left the line\n" \ - "below it!\n" - -#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_SHRINK \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..629cd35 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -1,9 +1,3 @@\n" \ - " hey!\n" \ - "-this is some context!\n" \ - "-around some lines\n" \ - "-that will change\n" \ - "-yes it is!\n" \ - "-(this line is changed)\n" \ - "-and this\n" \ - "-is additional context\n" \ - "+i've changed a lot, but left the line\n" \ - " below it!\n" - -#define PATCH_ORIGINAL_TO_MIDDLE_SHRINK_NOCONTEXT \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..629cd35 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -2,7 +2 @@ hey!\n" \ - "-this is some context!\n" \ - "-around some lines\n" \ - "-that will change\n" \ - "-yes it is!\n" \ - "-(this line is changed)\n" \ - "-and this\n" \ - "-is additional context\n" \ - "+i've changed a lot, but left the line\n" - -/* A change to the middle where we grow many lines */ - -#define FILE_CHANGE_MIDDLE_GROW \ - "hey!\n" \ - "this is some context!\n" \ - "around some lines\n" \ - "that will change\n" \ - "yes it is!\n" \ - "this line is changed\n" \ - "and this line is added\n" \ - "so is this\n" \ - "(this too)\n" \ - "whee...\n" \ - "and this\n" \ - "is additional context\n" \ - "below it!\n" - -#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_GROW \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..207ebca 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -3,7 +3,11 @@ this is some context!\n" \ - " around some lines\n" \ - " that will change\n" \ - " yes it is!\n" \ - "-(this line is changed)\n" \ - "+this line is changed\n" \ - "+and this line is added\n" \ - "+so is this\n" \ - "+(this too)\n" \ - "+whee...\n" \ - " and this\n" \ - " is additional context\n" \ - " below it!\n" - - -#define PATCH_ORIGINAL_TO_MIDDLE_GROW_NOCONTEXT \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..207ebca 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -6 +6,5 @@ yes it is!\n" \ - "-(this line is changed)\n" \ - "+this line is changed\n" \ - "+and this line is added\n" \ - "+so is this\n" \ - "+(this too)\n" \ - "+whee...\n" - -/* An insertion at the beginning of the file (and the resultant patch) */ - -#define FILE_PREPEND \ - "insert at front\n" \ - "hey!\n" \ - "this is some context!\n" \ - "around some lines\n" \ - "that will change\n" \ - "yes it is!\n" \ - "(this line is changed)\n" \ - "and this\n" \ - "is additional context\n" \ - "below it!\n" - -#define PATCH_ORIGINAL_TO_PREPEND \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..0f39b9a 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -1,3 +1,4 @@\n" \ - "+insert at front\n" \ - " hey!\n" \ - " this is some context!\n" \ - " around some lines\n" - -#define PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..0f39b9a 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -0,0 +1 @@\n" \ - "+insert at front\n" - -/* An insertion at the beginning of the file and change in the middle */ - -#define FILE_PREPEND_AND_CHANGE \ - "insert at front\n" \ - "hey!\n" \ - "this is some context!\n" \ - "around some lines\n" \ - "that will change\n" \ - "yes it is!\n" \ - "(THIS line is changed!)\n" \ - "and this\n" \ - "is additional context\n" \ - "below it!\n" - -#define PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..f73c8bb 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -1,9 +1,10 @@\n" \ - "+insert at front\n" \ - " hey!\n" \ - " this is some context!\n" \ - " around some lines\n" \ - " that will change\n" \ - " yes it is!\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" \ - " and this\n" \ - " is additional context\n" \ - " below it!\n" - -#define PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE_NOCONTEXT \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..f73c8bb 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -0,0 +1 @@\n" \ - "+insert at front\n" \ - "@@ -6 +7 @@ yes it is!\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" - -/* A change in the middle and a deletion of the newline at the end of the file */ - -#define FILE_CHANGE_MIDDLE_AND_LASTLINE \ - "hey!\n" \ - "this is some context!\n" \ - "around some lines\n" \ - "that will change\n" \ - "yes it is!\n" \ - "(THIS line is changed!)\n" \ - "and this\n" \ - "is additional context\n" \ - "BELOW it! - (THIS line is changed!)" - -#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_AND_LASTLINE_NOCONTEXT \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..e05d36c 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -6 +6 @@ yes it is!\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" \ - "@@ -9 +9 @@ is additional context\n" \ - "-below it!\n" \ - "+BELOW it! - (THIS line is changed!)\n" \ - "\\ No newline at end of file\n" - -/* A deletion at the beginning of the file and a change in the middle */ - -#define FILE_DELETE_AND_CHANGE \ - "this is some context!\n" \ - "around some lines\n" \ - "that will change\n" \ - "yes it is!\n" \ - "(THIS line is changed!)\n" \ - "and this\n" \ - "is additional context\n" \ - "below it!\n" - -#define PATCH_ORIGINAL_TO_DELETE_AND_CHANGE \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..1e2dfa6 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -1,9 +1,8 @@\n" \ - "-hey!\n" \ - " this is some context!\n" \ - " around some lines\n" \ - " that will change\n" \ - " yes it is!\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" \ - " and this\n" \ - " is additional context\n" \ - " below it!\n" - -#define PATCH_ORIGINAL_TO_DELETE_AND_CHANGE_NOCONTEXT \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..1e2dfa6 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -1 +0,0 @@\n" \ - "-hey!\n" \ - "@@ -6 +5 @@ yes it is!\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" - -/* A deletion at the beginning of the file */ - -#define FILE_DELETE_FIRSTLINE \ - "this is some context!\n" \ - "around some lines\n" \ - "that will change\n" \ - "yes it is!\n" \ - "(this line is changed)\n" \ - "and this\n" \ - "is additional context\n" \ - "below it!\n" - -#define PATCH_ORIGINAL_TO_DELETE_FIRSTLINE \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..f31fa13 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -1,4 +1,3 @@\n" \ - "-hey!\n" \ - " this is some context!\n" \ - " around some lines\n" \ - " that will change\n" - -/* An insertion at the end of the file (and the resultant patch) */ - -#define FILE_APPEND \ - "hey!\n" \ - "this is some context!\n" \ - "around some lines\n" \ - "that will change\n" \ - "yes it is!\n" \ - "(this line is changed)\n" \ - "and this\n" \ - "is additional context\n" \ - "below it!\n" \ - "insert at end\n" - -#define PATCH_ORIGINAL_TO_APPEND \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..72788bb 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -7,3 +7,4 @@ yes it is!\n" \ - " and this\n" \ - " is additional context\n" \ - " below it!\n" \ - "+insert at end\n" - -#define PATCH_ORIGINAL_TO_APPEND_NOCONTEXT \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..72788bb 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -9,0 +10 @@ below it!\n" \ - "+insert at end\n" - -#define PATCH_DELETED_FILE_2_HUNKS \ - "diff --git a/a b/a\n" \ - "index 7f129fd..af431f2 100644\n" \ - "--- a/a\n" \ - "+++ b/a\n" \ - "@@ -1 +1 @@\n" \ - "-a contents 2\n" \ - "+a contents\n" \ - "diff --git a/c/d b/c/d\n" \ - "deleted file mode 100644\n" \ - "index 297efb8..0000000\n" \ - "--- a/c/d\n" \ - "+++ /dev/null\n" \ - "@@ -1 +0,0 @@\n" \ - "-c/d contents\n" - -#define PATCH_DELETED_FILE_2_HUNKS_SHUFFLED \ - "diff --git a/c/d b/c/d\n" \ - "deleted file mode 100644\n" \ - "index 297efb8..0000000\n" \ - "--- a/c/d\n" \ - "+++ /dev/null\n" \ - "@@ -1 +0,0 @@\n" \ - "-c/d contents\n" \ - "diff --git a/a b/a\n" \ - "index 7f129fd..af431f2 100644\n" \ - "--- a/a\n" \ - "+++ b/a\n" \ - "@@ -1 +1 @@\n" \ - "-a contents 2\n" \ - "+a contents\n" - -#define PATCH_SIMPLE_COMMIT \ - "commit 15e119375018fba121cf58e02a9f17fe22df0df8\n" \ - "Author: Edward Thomson \n" \ - "Date: Wed Jun 14 13:31:20 2017 +0200\n" \ - "\n" \ - " CHANGELOG: document git_filter_init and GIT_FILTER_INIT\n" \ - "\n" \ - "diff --git a/CHANGELOG.md b/CHANGELOG.md\n" \ - "index 1b9e0c90a..24ecba426 100644\n" \ - "--- a/CHANGELOG.md\n" \ - "+++ b/CHANGELOG.md\n" \ - "@@ -96,6 +96,9 @@ v0.26\n" \ - " * `git_transport_smart_proxy_options()' enables you to get the proxy options for\n" \ - " smart transports.\n" \ - "\n" \ - "+* The `GIT_FILTER_INIT` macro and the `git_filter_init` function are provided\n" \ - "+ to initialize a `git_filter` structure.\n" \ - "+\n" \ - " ### Breaking API changes\n" \ - "\n" \ - " * `clone_checkout_strategy` has been removed from\n" - -#define PATCH_MULTIPLE_HUNKS \ - "diff --git a/x b/x\n" \ - "index 0719398..fa0350c 100644\n" \ - "--- a/x\n" \ - "+++ b/x\n" \ - "@@ -1,5 +1,4 @@\n" \ - " 1\n" \ - "-2\n" \ - " 3\n" \ - " 4\n" \ - " 5\n" \ - "@@ -7,3 +6,4 @@\n" \ - " 7\n" \ - " 8\n" \ - " 9\n" \ - "+10\n" - -#define PATCH_MULTIPLE_FILES \ - "diff --git a/x b/x\n" \ - "index 8a1218a..7059ba5 100644\n" \ - "--- a/x\n" \ - "+++ b/x\n" \ - "@@ -1,5 +1,4 @@\n" \ - " 1\n" \ - " 2\n" \ - "-3\n" \ - " 4\n" \ - " 5\n" \ - "diff --git a/y b/y\n" \ - "index e006065..9405325 100644\n" \ - "--- a/y\n" \ - "+++ b/y\n" \ - "@@ -1,4 +1,5 @@\n" \ - " a\n" \ - " b\n" \ - "+c\n" \ - " d\n" \ - " e\n" - -#define FILE_PREPEND_AND_APPEND \ - "first and\n" \ - "this is some context!\n" \ - "around some lines\n" \ - "that will change\n" \ - "yes it is!\n" \ - "(this line is changed)\n" \ - "and this\n" \ - "is additional context\n" \ - "last lines\n" - -#define PATCH_ORIGINAL_TO_PREPEND_AND_APPEND \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..f282430 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -1,4 +1,4 @@\n" \ - "-hey!\n" \ - "+first and\n" \ - " this is some context!\n" \ - " around some lines\n" \ - " that will change\n" \ - "@@ -6,4 +6,4 @@ yes it is!\n" \ - " (this line is changed)\n" \ - " and this\n" \ - " is additional context\n" \ - "-below it!\n" \ - "+last lines\n" - -#define PATCH_ORIGINAL_TO_EMPTY_FILE \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..e69de29 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -1,9 +0,0 @@\n" \ - "-hey!\n" \ - "-this is some context!\n" \ - "-around some lines\n" \ - "-that will change\n" \ - "-yes it is!\n" \ - "-(this line is changed)\n" \ - "-and this\n" \ - "-is additional context\n" \ - "-below it!\n" - -#define PATCH_EMPTY_FILE_TO_ORIGINAL \ - "diff --git a/file.txt b/file.txt\n" \ - "index e69de29..9432026 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -0,0 +1,9 @@\n" \ - "+hey!\n" \ - "+this is some context!\n" \ - "+around some lines\n" \ - "+that will change\n" \ - "+yes it is!\n" \ - "+(this line is changed)\n" \ - "+and this\n" \ - "+is additional context\n" \ - "+below it!\n" - -#define PATCH_ADD_ORIGINAL \ - "diff --git a/file.txt b/file.txt\n" \ - "new file mode 100644\n" \ - "index 0000000..9432026\n" \ - "--- /dev/null\n" \ - "+++ b/file.txt\n" \ - "@@ -0,0 +1,9 @@\n" \ - "+hey!\n" \ - "+this is some context!\n" \ - "+around some lines\n" \ - "+that will change\n" \ - "+yes it is!\n" \ - "+(this line is changed)\n" \ - "+and this\n" \ - "+is additional context\n" \ - "+below it!\n" - -#define PATCH_DELETE_ORIGINAL \ - "diff --git a/file.txt b/file.txt\n" \ - "deleted file mode 100644\n" \ - "index 9432026..0000000\n" \ - "--- a/file.txt\n" \ - "+++ /dev/null\n" \ - "@@ -1,9 +0,0 @@\n" \ - "-hey!\n" \ - "-this is some context!\n" \ - "-around some lines\n" \ - "-that will change\n" \ - "-yes it is!\n" \ - "-(this line is changed)\n" \ - "-and this\n" \ - "-is additional context\n" \ - "-below it!\n" - -#define PATCH_RENAME_EXACT \ - "diff --git a/file.txt b/newfile.txt\n" \ - "similarity index 100%\n" \ - "rename from file.txt\n" \ - "rename to newfile.txt\n" - -#define PATCH_RENAME_EXACT_WITH_MODE \ - "diff --git a/RENAMED.md b/README.md\n" \ - "old mode 100644\n" \ - "new mode 100755\n" \ - "similarity index 100%\n" \ - "rename from RENAMED.md\n" \ - "rename to README.md\n" - -#define PATCH_RENAME_SIMILAR \ - "diff --git a/file.txt b/newfile.txt\n" \ - "similarity index 77%\n" \ - "rename from file.txt\n" \ - "rename to newfile.txt\n" \ - "index 9432026..cd8fd12 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/newfile.txt\n" \ - "@@ -3,7 +3,7 @@ this is some context!\n" \ - " around some lines\n" \ - " that will change\n" \ - " yes it is!\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" \ - " and this\n" \ - " is additional context\n" \ - " below it!\n" - -#define PATCH_RENAME_EXACT_QUOTEDNAME \ - "diff --git a/file.txt \"b/foo\\\"bar.txt\"\n" \ - "similarity index 100%\n" \ - "rename from file.txt\n" \ - "rename to \"foo\\\"bar.txt\"\n" - -#define PATCH_RENAME_SIMILAR_QUOTEDNAME \ - "diff --git a/file.txt \"b/foo\\\"bar.txt\"\n" \ - "similarity index 77%\n" \ - "rename from file.txt\n" \ - "rename to \"foo\\\"bar.txt\"\n" \ - "index 9432026..cd8fd12 100644\n" \ - "--- a/file.txt\n" \ - "+++ \"b/foo\\\"bar.txt\"\n" \ - "@@ -3,7 +3,7 @@ this is some context!\n" \ - " around some lines\n" \ - " that will change\n" \ - " yes it is!\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" \ - " and this\n" \ - " is additional context\n" \ - " below it!\n" - -#define PATCH_MODECHANGE_UNCHANGED \ - "diff --git a/file.txt b/file.txt\n" \ - "old mode 100644\n" \ - "new mode 100755\n" - -#define PATCH_MODECHANGE_MODIFIED \ - "diff --git a/file.txt b/file.txt\n" \ - "old mode 100644\n" \ - "new mode 100755\n" \ - "index 9432026..cd8fd12\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -3,7 +3,7 @@ this is some context!\n" \ - " around some lines\n" \ - " that will change\n" \ - " yes it is!\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" \ - " and this\n" \ - " is additional context\n" \ - " below it!\n" - -#define PATCH_NOISY \ - "This is some\nleading noise\n@@ - that\nlooks like a hunk header\n" \ - "but actually isn't and should parse ok\n" \ - PATCH_ORIGINAL_TO_CHANGE_MIDDLE \ - "plus some trailing garbage for good measure\n" - -#define PATCH_NOISY_NOCONTEXT \ - "This is some\nleading noise\n@@ - that\nlooks like a hunk header\n" \ - "but actually isn't and should parse ok\n" \ - PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT \ - "plus some trailing garbage for good measure\n" - -#define PATCH_TRUNCATED_1 \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..cd8fd12 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -3,7 +3,7 @@ this is some context!\n" \ - " around some lines\n" \ - " that will change\n" \ - " yes it is!\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" \ - " and this\n" - -#define PATCH_TRUNCATED_2 \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..cd8fd12 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -3,7 +3,7 @@ this is some context!\n" \ - " around some lines\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" \ - " and this\n" \ - " is additional context\n" \ - " below it!\n" - -#define PATCH_TRUNCATED_3 \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..cd8fd12 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -3,7 +3,7 @@ this is some context!\n" \ - " around some lines\n" \ - " that will change\n" \ - " yes it is!\n" \ - "+(THIS line is changed!)\n" \ - " and this\n" \ - " is additional context\n" \ - " below it!\n" - -#define FILE_EMPTY_CONTEXT_ORIGINAL \ - "this\nhas\nan\n\nempty\ncontext\nline\n" - -#define FILE_EMPTY_CONTEXT_MODIFIED \ - "this\nhas\nan\n\nempty...\ncontext\nline\n" - -#define PATCH_EMPTY_CONTEXT \ - "diff --git a/file.txt b/file.txt\n" \ - "index 398d2df..bb15234 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -2,6 +2,6 @@ this\n" \ - " has\n" \ - " an\n" \ - "\n" \ - "-empty\n" \ - "+empty...\n" \ - " context\n" \ - " line\n" - -#define FILE_APPEND_NO_NL \ - "hey!\n" \ - "this is some context!\n" \ - "around some lines\n" \ - "that will change\n" \ - "yes it is!\n" \ - "(this line is changed)\n" \ - "and this\n" \ - "is additional context\n" \ - "below it!\n" \ - "added line with no nl" - -#define PATCH_APPEND_NO_NL \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..83759c0 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -7,3 +7,4 @@ yes it is!\n" \ - " and this\n" \ - " is additional context\n" \ - " below it!\n" \ - "+added line with no nl\n" \ - "\\ No newline at end of file\n" - -#define PATCH_APPEND_NO_NL_IN_OLD_FILE \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..83759c0 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -1,1 +1,1 @@\n" \ - "-foo\n" \ - "\\ No newline at end of file\n" \ - "+foo\n" - -#define PATCH_NAME_WHITESPACE \ - "diff --git a/file with spaces.txt b/file with spaces.txt\n" \ - "index 9432026..83759c0 100644\n" \ - "--- a/file with spaces.txt\n" \ - "+++ b/file with spaces.txt\n" \ - "@@ -0,3 +0,2 @@\n" \ - " and this\n" \ - "-is additional context\n" \ - " below it!\n" \ - -#define PATCH_CORRUPT_GIT_HEADER \ - "diff --git a/file.txt\n" \ - "index 9432026..0f39b9a 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -0,0 +1 @@\n" \ - "+insert at front\n" - -#define PATCH_CORRUPT_MISSING_NEW_FILE \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..cd8fd12 100644\n" \ - "--- a/file.txt\n" \ - "@@ -6 +6 @@ yes it is!\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" - -#define PATCH_CORRUPT_MISSING_OLD_FILE \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..cd8fd12 100644\n" \ - "+++ b/file.txt\n" \ - "@@ -6 +6 @@ yes it is!\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" - -#define PATCH_CORRUPT_NO_CHANGES \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..cd8fd12 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "@@ -0,0 +0,0 @@ yes it is!\n" - -#define PATCH_CORRUPT_MISSING_HUNK_HEADER \ - "diff --git a/file.txt b/file.txt\n" \ - "index 9432026..cd8fd12 100644\n" \ - "--- a/file.txt\n" \ - "+++ b/file.txt\n" \ - "-(this line is changed)\n" \ - "+(THIS line is changed!)\n" - -#define PATCH_NOT_A_PATCH \ - "+++this is not\n" \ - "--actually even\n" \ - " a legitimate \n" \ - "+patch file\n" \ - "-it's something else\n" \ - " entirely!" - -/* binary contents */ - -#define FILE_BINARY_LITERAL_ORIGINAL "\x00\x00\x0a" -#define FILE_BINARY_LITERAL_ORIGINAL_LEN 3 - -#define FILE_BINARY_LITERAL_MODIFIED "\x00\x00\x01\x02\x0a" -#define FILE_BINARY_LITERAL_MODIFIED_LEN 5 - -#define PATCH_BINARY_LITERAL \ - "diff --git a/binary.bin b/binary.bin\n" \ - "index bd474b2519cc15eab801ff851cc7d50f0dee49a1..9ac35ff15cd8864aeafd889e4826a3150f0b06c4 100644\n" \ - "GIT binary patch\n" \ - "literal 5\n" \ - "Mc${NkU}WL~000&M4gdfE\n" \ - "\n" \ - "literal 3\n" \ - "Kc${Nk-~s>u4FC%O\n\n" - -#define FILE_BINARY_DELTA_ORIGINAL \ - "\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02\x0a\x54\x68\x69" \ - "\x73\x20\x69\x73\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x66\x69" \ - "\x6c\x65\x2c\x20\x62\x79\x20\x76\x69\x72\x74\x75\x65\x20\x6f\x66" \ - "\x20\x68\x61\x76\x69\x6e\x67\x20\x73\x6f\x6d\x65\x20\x6e\x75\x6c" \ - "\x6c\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ - "\x0a\x57\x65\x27\x72\x65\x20\x67\x6f\x69\x6e\x67\x20\x74\x6f\x20" \ - "\x63\x68\x61\x6e\x67\x65\x20\x70\x6f\x72\x74\x69\x6f\x6e\x73\x20" \ - "\x6f\x66\x20\x69\x74\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00" \ - "\x00\x01\x02\x0a\x53\x6f\x20\x74\x68\x61\x74\x20\x77\x65\x20\x67" \ - "\x69\x74\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x64\x65\x6c\x74" \ - "\x61\x20\x69\x6e\x73\x74\x65\x61\x64\x20\x6f\x66\x20\x74\x68\x65" \ - "\x20\x64\x65\x66\x6c\x61\x74\x65\x64\x20\x63\x6f\x6e\x74\x65\x6e" \ - "\x74\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ - "\x0a" -#define FILE_BINARY_DELTA_ORIGINAL_LEN 209 - -#define FILE_BINARY_DELTA_MODIFIED \ - "\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02\x0a\x5a\x5a\x5a" \ - "\x5a\x20\x69\x73\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x66\x69" \ - "\x6c\x65\x2c\x20\x62\x79\x20\x76\x69\x72\x74\x75\x65\x20\x6f\x66" \ - "\x20\x68\x61\x76\x69\x6e\x67\x20\x73\x6f\x6d\x65\x20\x6e\x75\x6c" \ - "\x6c\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ - "\x0a\x57\x65\x27\x72\x65\x20\x67\x6f\x69\x6e\x67\x20\x74\x6f\x20" \ - "\x63\x68\x61\x6e\x67\x65\x20\x70\x6f\x72\x74\x69\x6f\x6e\x73\x20" \ - "\x6f\x66\x20\x49\x54\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00" \ - "\x00\x01\x02\x0a\x53\x4f\x20\x74\x68\x61\x74\x20\x77\x65\x20\x67" \ - "\x69\x74\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x64\x65\x6c\x74" \ - "\x61\x20\x69\x6e\x73\x74\x65\x61\x64\x20\x6f\x66\x20\x74\x68\x65" \ - "\x20\x64\x65\x66\x6c\x61\x74\x65\x64\x20\x63\x6f\x6e\x74\x65\x6e" \ - "\x74\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ - "\x0a" -#define FILE_BINARY_DELTA_MODIFIED_LEN 209 - -#define PATCH_BINARY_DELTA \ - "diff --git a/binary.bin b/binary.bin\n" \ - "index 27184d9883b12c4c9c54b4a31137603586169f51..7c94f9e60bf366033d98e0d551ae37d30faef74a 100644\n" \ - "GIT binary patch\n" \ - "delta 48\n" \ - "kc$~Y)c#%<%fq{_;hPk4EV4`4>uxE%K7m7r%|HL+L0In7XGynhq\n" \ - "\n" \ - "delta 48\n" \ - "mc$~Y)c#%<%fq{_;hPgsAGK(h)CJASj=y9P)1m{m|^9BI99|yz$\n\n" - -#define PATCH_BINARY_ADD \ - "diff --git a/binary.bin b/binary.bin\n" \ - "new file mode 100644\n" \ - "index 0000000000000000000000000000000000000000..7c94f9e60bf366033d98e0d551ae37d30faef74a\n" \ - "GIT binary patch\n" \ - "literal 209\n" \ - "zc${60u?oUK5JXSQe8qG&;(u6KCC_\n" \ - "zAhe=XX7rNzh<3&##YcwqNHmEKsP<&&m~%Zf;eX@Khr$?aExDmfqyyt+#l^I)3+LMg\n" \ - "kxnAIj9Pfn_|Gh`fP7tlm6j#y{FJYg_IifRlR^R@A08f862mk;8\n" \ - "\n" \ - "literal 0\n" \ - "Hc$@C_\n" \ - "zAhe=XX7rNzh<3&##YcwqNHmEKsP<&&m~%Zf;eX@Khr$?aExDmfqyyt+#l^I)3+LMg\n" \ - "kxnAIj9Pfn_|Gh`fP7tlm6j#y{FJYg_IifRlR^R@A08f862mk;8\n\n" - -/* contains an old side that does not match the expected source */ -#define PATCH_BINARY_NOT_REVERSIBLE \ - "diff --git a/binary.bin b/binary.bin\n" \ - "index 27184d9883b12c4c9c54b4a31137603586169f51..7c94f9e60bf366033d98e0d551ae37d30faef74a 100644\n" \ - "GIT binary patch\n" \ - "literal 5\n" \ - "Mc${NkU}WL~000&M4gdfE\n" \ - "\n" \ - "delta 48\n" \ - "mc$~Y)c#%<%fq{_;hPgsAGK(h)CJASj=y9P)1m{m|^9BI99|yz$\n\n" - -#define PATCH_BINARY_NOT_PRINTED \ - "diff --git a/binary.bin b/binary.bin\n" \ - "index 27184d9..7c94f9e 100644\n" \ - "Binary files a/binary.bin and b/binary.bin differ\n" - -#define PATCH_ADD_BINARY_NOT_PRINTED \ - "diff --git a/test.bin b/test.bin\n" \ - "new file mode 100644\n" \ - "index 0000000..9e0f96a\n" \ - "Binary files /dev/null and b/test.bin differ\n" - -#define PATCH_ORIGINAL_NEW_FILE_WITH_SPACE \ - "diff --git a/sp ace.txt b/sp ace.txt\n" \ - "new file mode 100644\n" \ - "index 000000000..789819226\n" \ - "--- /dev/null\n" \ - "+++ b/sp ace.txt\n" \ - "@@ -0,0 +1 @@\n" \ - "+a\n" - -#define PATCH_CRLF \ - "diff --git a/test-file b/test-file\r\n" \ - "new file mode 100644\r\n" \ - "index 0000000..af431f2 100644\r\n" \ - "--- /dev/null\r\n" \ - "+++ b/test-file\r\n" \ - "@@ -0,0 +1 @@\r\n" \ - "+a contents\r\n" - -#define PATCH_NO_EXTENDED_HEADERS \ - "diff --git a/file b/file\n" \ - "--- a/file\n" \ - "+++ b/file\n" \ - "@@ -1,3 +1,3 @@\n" \ - " a\n" \ - "-b\n" \ - "+bb\n" \ - " c\n" - -#define PATCH_BINARY_FILE_WITH_MISSING_PATHS \ - "diff --git \n" \ - "--- \n" \ - "+++ \n" \ - "Binary files " - -#define PATCH_BINARY_FILE_WITH_WHITESPACE_PATHS \ - "diff --git a/file b/file\n" \ - "--- \n" \ - "+++ \n" \ - "Binary files " - -#define PATCH_BINARY_FILE_WITH_QUOTED_EMPTY_PATHS \ - "diff --git a/file b/file\n" \ - "--- \"\"\n" \ - "+++ \"\"\n" \ - "Binary files " - -#define PATCH_BINARY_FILE_PATH_WITH_SPACES \ - "diff --git a b c d e f\n" \ - "--- a b c\n" \ - "+++ d e f\n" \ - "Binary files a b c and d e f differ" - -#define PATCH_BINARY_FILE_PATH_WITHOUT_BODY_PATHS \ - "diff --git a b c d e f\n" \ - "--- \n" \ - "+++ \n" \ - "Binary files a b c and d e f differ" - -#define PATCH_BINARY_FILE_WITH_TRUNCATED_DELTA \ - "diff --git a/file b/file\n" \ - "index 1420..b71f\n" \ - "GIT binary patch\n" \ - "delta 7\n" \ - "d" - -#define PATCH_MULTIPLE_OLD_PATHS \ - "diff --git \n" \ - "--- \n" \ - "+++ \n" \ - "index 0000..7DDb\n" \ - "--- \n" - -#define PATCH_INTMAX_NEW_LINES \ - "diff --git a/file b/file\n" \ - "--- a/file\n" \ - "+++ b/file\n" \ - "@@ -0 +2147483647 @@\n" \ - "\n" \ - " " diff --git a/tests/patch/print.c b/tests/patch/print.c deleted file mode 100644 index 33cf27ddb..000000000 --- a/tests/patch/print.c +++ /dev/null @@ -1,186 +0,0 @@ -#include "clar_libgit2.h" -#include "patch.h" -#include "patch_parse.h" - -#include "patch_common.h" - - -/* sanity check the round-trip of patch parsing: ensure that we can parse - * and then print a variety of patch files. - */ - -static void patch_print_from_patchfile(const char *data, size_t len) -{ - git_patch *patch; - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_patch_from_buffer(&patch, data, len, NULL)); - cl_git_pass(git_patch_to_buf(&buf, patch)); - - cl_assert_equal_s(data, buf.ptr); - - git_patch_free(patch); - git_buf_dispose(&buf); -} - -void test_patch_print__change_middle(void) -{ - patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_MIDDLE, - strlen(PATCH_ORIGINAL_TO_CHANGE_MIDDLE)); -} - -void test_patch_print__change_middle_nocontext(void) -{ - patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, - strlen(PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT)); -} - -void test_patch_print__change_firstline(void) -{ - patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, - strlen(PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE)); -} - -void test_patch_print__change_lastline(void) -{ - patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_LASTLINE, - strlen(PATCH_ORIGINAL_TO_CHANGE_LASTLINE)); -} - -void test_patch_print__prepend(void) -{ - patch_print_from_patchfile(PATCH_ORIGINAL_TO_PREPEND, - strlen(PATCH_ORIGINAL_TO_PREPEND)); -} - -void test_patch_print__prepend_nocontext(void) -{ - patch_print_from_patchfile(PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, - strlen(PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT)); -} - -void test_patch_print__append(void) -{ - patch_print_from_patchfile(PATCH_ORIGINAL_TO_APPEND, - strlen(PATCH_ORIGINAL_TO_APPEND)); -} - -void test_patch_print__append_nocontext(void) -{ - patch_print_from_patchfile(PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, - strlen(PATCH_ORIGINAL_TO_APPEND_NOCONTEXT)); -} - -void test_patch_print__prepend_and_append(void) -{ - patch_print_from_patchfile(PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, - strlen(PATCH_ORIGINAL_TO_PREPEND_AND_APPEND)); -} - -void test_patch_print__to_empty_file(void) -{ - patch_print_from_patchfile(PATCH_ORIGINAL_TO_EMPTY_FILE, - strlen(PATCH_ORIGINAL_TO_EMPTY_FILE)); -} - -void test_patch_print__from_empty_file(void) -{ - patch_print_from_patchfile(PATCH_EMPTY_FILE_TO_ORIGINAL, - strlen(PATCH_EMPTY_FILE_TO_ORIGINAL)); -} - -void test_patch_print__add(void) -{ - patch_print_from_patchfile(PATCH_ADD_ORIGINAL, - strlen(PATCH_ADD_ORIGINAL)); -} - -void test_patch_print__delete(void) -{ - patch_print_from_patchfile(PATCH_DELETE_ORIGINAL, - strlen(PATCH_DELETE_ORIGINAL)); -} - -void test_patch_print__rename_exact(void) -{ - patch_print_from_patchfile(PATCH_RENAME_EXACT, - strlen(PATCH_RENAME_EXACT)); -} - -void test_patch_print__rename_exact_with_mode(void) -{ - patch_print_from_patchfile(PATCH_RENAME_EXACT_WITH_MODE, - strlen(PATCH_RENAME_EXACT_WITH_MODE)); -} - -void test_patch_print__rename_similar(void) -{ - patch_print_from_patchfile(PATCH_RENAME_SIMILAR, - strlen(PATCH_RENAME_SIMILAR)); -} - -void test_patch_print__rename_exact_quotedname(void) -{ - patch_print_from_patchfile(PATCH_RENAME_EXACT_QUOTEDNAME, - strlen(PATCH_RENAME_EXACT_QUOTEDNAME)); -} - -void test_patch_print__rename_similar_quotedname(void) -{ - patch_print_from_patchfile(PATCH_RENAME_SIMILAR_QUOTEDNAME, - strlen(PATCH_RENAME_SIMILAR_QUOTEDNAME)); -} - -void test_patch_print__modechange_unchanged(void) -{ - patch_print_from_patchfile(PATCH_MODECHANGE_UNCHANGED, - strlen(PATCH_MODECHANGE_UNCHANGED)); -} - -void test_patch_print__modechange_modified(void) -{ - patch_print_from_patchfile(PATCH_MODECHANGE_MODIFIED, - strlen(PATCH_MODECHANGE_MODIFIED)); -} - -void test_patch_print__binary_literal(void) -{ - patch_print_from_patchfile(PATCH_BINARY_LITERAL, - strlen(PATCH_BINARY_LITERAL)); -} - -void test_patch_print__binary_delta(void) -{ - patch_print_from_patchfile(PATCH_BINARY_DELTA, - strlen(PATCH_BINARY_DELTA)); -} - -void test_patch_print__binary_add(void) -{ - patch_print_from_patchfile(PATCH_BINARY_ADD, - strlen(PATCH_BINARY_ADD)); -} - -void test_patch_print__binary_delete(void) -{ - patch_print_from_patchfile(PATCH_BINARY_DELETE, - strlen(PATCH_BINARY_DELETE)); -} - -void test_patch_print__not_reversible(void) -{ - patch_print_from_patchfile(PATCH_BINARY_NOT_REVERSIBLE, - strlen(PATCH_BINARY_NOT_REVERSIBLE)); -} - -void test_patch_print__binary_not_shown(void) -{ - patch_print_from_patchfile(PATCH_BINARY_NOT_PRINTED, - strlen(PATCH_BINARY_NOT_PRINTED)); -} - -void test_patch_print__binary_add_not_shown(void) -{ - patch_print_from_patchfile(PATCH_ADD_BINARY_NOT_PRINTED, - strlen(PATCH_ADD_BINARY_NOT_PRINTED)); -} diff --git a/tests/path/core.c b/tests/path/core.c deleted file mode 100644 index db5359af8..000000000 --- a/tests/path/core.c +++ /dev/null @@ -1,405 +0,0 @@ -#include "clar_libgit2.h" -#include "fs_path.h" -#include "path.h" - -void test_path_core__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void test_make_relative( - const char *expected_path, - const char *path, - const char *parent, - int expected_status) -{ - git_str buf = GIT_STR_INIT; - git_str_puts(&buf, path); - cl_assert_equal_i(expected_status, git_fs_path_make_relative(&buf, parent)); - cl_assert_equal_s(expected_path, buf.ptr); - git_str_dispose(&buf); -} - -void test_path_core__make_relative(void) -{ - test_make_relative("foo.c", "/path/to/foo.c", "/path/to", 0); - test_make_relative("bar/foo.c", "/path/to/bar/foo.c", "/path/to", 0); - test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0); - - test_make_relative("", "/path/to", "/path/to", 0); - test_make_relative("", "/path/to", "/path/to/", 0); - - test_make_relative("../", "/path/to", "/path/to/foo", 0); - - test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar", 0); - test_make_relative("../bar/foo.c", "/path/to/bar/foo.c", "/path/to/baz", 0); - - test_make_relative("../../foo.c", "/path/to/foo.c", "/path/to/foo/bar", 0); - test_make_relative("../../foo/bar.c", "/path/to/foo/bar.c", "/path/to/bar/foo", 0); - - test_make_relative("../../foo.c", "/foo.c", "/bar/foo", 0); - - test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0); - test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar/", 0); - - test_make_relative("foo.c", "d:/path/to/foo.c", "d:/path/to", 0); - - test_make_relative("../foo", "/foo", "/bar", 0); - test_make_relative("path/to/foo.c", "/path/to/foo.c", "/", 0); - test_make_relative("../foo", "path/to/foo", "path/to/bar", 0); - - test_make_relative("/path/to/foo.c", "/path/to/foo.c", "d:/path/to", GIT_ENOTFOUND); - test_make_relative("d:/path/to/foo.c", "d:/path/to/foo.c", "/path/to", GIT_ENOTFOUND); - - test_make_relative("/path/to/foo.c", "/path/to/foo.c", "not-a-rooted-path", GIT_ENOTFOUND); - test_make_relative("not-a-rooted-path", "not-a-rooted-path", "/path/to", GIT_ENOTFOUND); - - test_make_relative("/path", "/path", "pathtofoo", GIT_ENOTFOUND); - test_make_relative("path", "path", "pathtofoo", GIT_ENOTFOUND); -} - -void test_path_core__isvalid_standard(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/file.txt", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/.file", 0)); -} - -/* Ensure that `is_valid_str` only reads str->size bytes */ -void test_path_core__isvalid_standard_str(void) -{ - git_str str = GIT_STR_INIT_CONST("foo/bar//zap", 0); - unsigned int flags = GIT_FS_PATH_REJECT_EMPTY_COMPONENT; - - str.size = 0; - cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); - - str.size = 3; - cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags)); - - str.size = 4; - cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); - - str.size = 5; - cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags)); - - str.size = 7; - cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags)); - - str.size = 8; - cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); - - str.size = strlen(str.ptr); - cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); -} - -void test_path_core__isvalid_empty_dir_component(void) -{ - unsigned int flags = GIT_FS_PATH_REJECT_EMPTY_COMPONENT; - - /* empty component */ - cl_assert_equal_b(true, git_fs_path_is_valid("foo//bar", 0)); - - /* leading slash */ - cl_assert_equal_b(true, git_fs_path_is_valid("/", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("/foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("/foo/bar", 0)); - - /* trailing slash */ - cl_assert_equal_b(true, git_fs_path_is_valid("foo/", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/", 0)); - - - /* empty component */ - cl_assert_equal_b(false, git_fs_path_is_valid("foo//bar", flags)); - - /* leading slash */ - cl_assert_equal_b(false, git_fs_path_is_valid("/", flags)); - cl_assert_equal_b(false, git_fs_path_is_valid("/foo", flags)); - cl_assert_equal_b(false, git_fs_path_is_valid("/foo/bar", flags)); - - /* trailing slash */ - cl_assert_equal_b(false, git_fs_path_is_valid("foo/", flags)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar/", flags)); -} - -void test_path_core__isvalid_dot_and_dotdot(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid(".", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("./foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/.", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("./foo", 0)); - - cl_assert_equal_b(true, git_fs_path_is_valid("..", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("../foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/..", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("../foo", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid(".", GIT_FS_PATH_REJECT_TRAVERSAL)); - cl_assert_equal_b(false, git_fs_path_is_valid("./foo", GIT_FS_PATH_REJECT_TRAVERSAL)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/.", GIT_FS_PATH_REJECT_TRAVERSAL)); - cl_assert_equal_b(false, git_fs_path_is_valid("./foo", GIT_FS_PATH_REJECT_TRAVERSAL)); - - cl_assert_equal_b(false, git_fs_path_is_valid("..", GIT_FS_PATH_REJECT_TRAVERSAL)); - cl_assert_equal_b(false, git_fs_path_is_valid("../foo", GIT_FS_PATH_REJECT_TRAVERSAL)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/..", GIT_FS_PATH_REJECT_TRAVERSAL)); - cl_assert_equal_b(false, git_fs_path_is_valid("../foo", GIT_FS_PATH_REJECT_TRAVERSAL)); -} - -void test_path_core__isvalid_backslash(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("foo\\file.txt", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar\\file.txt", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar\\", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("foo\\file.txt", GIT_FS_PATH_REJECT_BACKSLASH)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar\\file.txt", GIT_FS_PATH_REJECT_BACKSLASH)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar\\", GIT_FS_PATH_REJECT_BACKSLASH)); -} - -void test_path_core__isvalid_trailing_dot(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("foo.", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo...", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar.", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo./bar", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("foo.", GIT_FS_PATH_REJECT_TRAILING_DOT)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo...", GIT_FS_PATH_REJECT_TRAILING_DOT)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar.", GIT_FS_PATH_REJECT_TRAILING_DOT)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo./bar", GIT_FS_PATH_REJECT_TRAILING_DOT)); -} - -void test_path_core__isvalid_trailing_space(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("foo ", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo ", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar ", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid(" ", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo /bar", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("foo ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); - cl_assert_equal_b(false, git_fs_path_is_valid(" ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo /bar", GIT_FS_PATH_REJECT_TRAILING_SPACE)); -} - -void test_path_core__isvalid_trailing_colon(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("foo:", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar:", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid(":", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo:/bar", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("foo:", GIT_FS_PATH_REJECT_TRAILING_COLON)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar:", GIT_FS_PATH_REJECT_TRAILING_COLON)); - cl_assert_equal_b(false, git_fs_path_is_valid(":", GIT_FS_PATH_REJECT_TRAILING_COLON)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo:/bar", GIT_FS_PATH_REJECT_TRAILING_COLON)); -} - -void test_path_core__isvalid_dos_paths(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("aux", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux.", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux:", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux.asdf", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux.asdf\\zippy", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux:asdf\\foobar", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("con", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("prn", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("nul", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("aux", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("aux.", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("aux:", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("aux.asdf", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("aux.asdf\\zippy", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("aux:asdf\\foobar", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("con", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("prn", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("nul", GIT_FS_PATH_REJECT_DOS_PATHS)); - - cl_assert_equal_b(true, git_fs_path_is_valid("aux1", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux1", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("auxn", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux\\foo", GIT_FS_PATH_REJECT_DOS_PATHS)); -} - -void test_path_core__isvalid_dos_paths_withnum(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("com1", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1.", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1:", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1.asdf", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1.asdf\\zippy", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1:asdf\\foobar", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1\\foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("lpt1", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("com1", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("com1.", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("com1:", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("com1.asdf", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("com1.asdf\\zippy", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("com1:asdf\\foobar", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("com1/foo", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("lpt1", GIT_FS_PATH_REJECT_DOS_PATHS)); - - cl_assert_equal_b(true, git_fs_path_is_valid("com0", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com0", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("com10", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com10", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("comn", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1\\foo", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("lpt0", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("lpt10", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("lptn", GIT_FS_PATH_REJECT_DOS_PATHS)); -} - -void test_path_core__isvalid_nt_chars(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("asdf\001foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdf\037bar", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdffoo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdf:foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdf\"bar", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdf|foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdf?bar", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdf*bar", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("asdf\001foo", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdf\037bar", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdffoo", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdf:foo", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdf\"bar", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdf|foo", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdf?bar", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdf*bar", GIT_FS_PATH_REJECT_NT_CHARS)); -} - -void test_path_core__validate_workdir(void) -{ - cl_must_pass(git_path_validate_length(NULL, "/foo/bar")); - cl_must_pass(git_path_validate_length(NULL, "C:\\Foo\\Bar")); - cl_must_pass(git_path_validate_length(NULL, "\\\\?\\C:\\Foo\\Bar")); - cl_must_pass(git_path_validate_length(NULL, "\\\\?\\C:\\Foo\\Bar")); - cl_must_pass(git_path_validate_length(NULL, "\\\\?\\UNC\\server\\C$\\folder")); - -#ifdef GIT_WIN32 - /* - * In the absence of a repo configuration, 259 character paths - * succeed. >= 260 character paths fail. - */ - cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\ok.txt")); - cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\260.txt")); - cl_must_fail(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\longer_than_260.txt")); - - /* count characters, not bytes */ - cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\260.txt")); - cl_must_fail(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\long.txt")); -#else - cl_must_pass(git_path_validate_length(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/ok.txt")); - cl_must_pass(git_path_validate_length(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/260.txt")); - cl_must_pass(git_path_validate_length(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); - cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\260.txt")); - cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\long.txt")); -#endif -} - -void test_path_core__validate_workdir_with_core_longpath(void) -{ -#ifdef GIT_WIN32 - git_repository *repo; - git_config *config; - - repo = cl_git_sandbox_init("empty_bare.git"); - - cl_git_pass(git_repository_open(&repo, "empty_bare.git")); - cl_git_pass(git_repository_config(&config, repo)); - - /* fail by default */ - cl_must_fail(git_path_validate_length(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); - - /* set core.longpaths explicitly on */ - cl_git_pass(git_config_set_bool(config, "core.longpaths", 1)); - cl_must_pass(git_path_validate_length(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); - - /* set core.longpaths explicitly off */ - cl_git_pass(git_config_set_bool(config, "core.longpaths", 0)); - cl_must_fail(git_path_validate_length(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); - - git_config_free(config); - git_repository_free(repo); -#endif -} - -static void test_join_unrooted( - const char *expected_result, - ssize_t expected_rootlen, - const char *path, - const char *base) -{ - git_str result = GIT_STR_INIT; - ssize_t root_at; - - cl_git_pass(git_fs_path_join_unrooted(&result, path, base, &root_at)); - cl_assert_equal_s(expected_result, result.ptr); - cl_assert_equal_i(expected_rootlen, root_at); - - git_str_dispose(&result); -} - -void test_path_core__join_unrooted(void) -{ - git_str out = GIT_STR_INIT; - - test_join_unrooted("foo", 0, "foo", NULL); - test_join_unrooted("foo/bar", 0, "foo/bar", NULL); - - /* Relative paths have base prepended */ - test_join_unrooted("/foo/bar", 4, "bar", "/foo"); - test_join_unrooted("/foo/bar/foobar", 4, "bar/foobar", "/foo"); - test_join_unrooted("c:/foo/bar/foobar", 6, "bar/foobar", "c:/foo"); - test_join_unrooted("c:/foo/bar/foobar", 10, "foobar", "c:/foo/bar"); - - /* Absolute paths are not prepended with base */ - test_join_unrooted("/foo", 0, "/foo", "/asdf"); - test_join_unrooted("/foo/bar", 0, "/foo/bar", "/asdf"); - - /* Drive letter is given as root length on Windows */ - test_join_unrooted("c:/foo", 2, "c:/foo", "c:/asdf"); - test_join_unrooted("c:/foo/bar", 2, "c:/foo/bar", "c:/asdf"); - -#ifdef GIT_WIN32 - /* Paths starting with '\\' are absolute */ - test_join_unrooted("\\bar", 0, "\\bar", "c:/foo/"); - test_join_unrooted("\\\\network\\bar", 9, "\\\\network\\bar", "c:/foo/"); -#else - /* Paths starting with '\\' are not absolute on non-Windows systems */ - test_join_unrooted("/foo/\\bar", 4, "\\bar", "/foo"); - test_join_unrooted("c:/foo/\\bar", 7, "\\bar", "c:/foo/"); -#endif - - /* Base is returned when it's provided and is the prefix */ - test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo"); - test_join_unrooted("c:/foo/bar/foobar", 10, "c:/foo/bar/foobar", "c:/foo/bar"); - - /* Trailing slash in the base is ignored */ - test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo/"); - - git_str_dispose(&out); -} - -void test_path_core__join_unrooted_respects_funny_windows_roots(void) -{ - test_join_unrooted("💩:/foo/bar/foobar", 9, "bar/foobar", "💩:/foo"); - test_join_unrooted("💩:/foo/bar/foobar", 13, "foobar", "💩:/foo/bar"); - test_join_unrooted("💩:/foo", 5, "💩:/foo", "💩:/asdf"); - test_join_unrooted("💩:/foo/bar", 5, "💩:/foo/bar", "💩:/asdf"); - test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo"); - test_join_unrooted("💩:/foo/bar/foobar", 13, "💩:/foo/bar/foobar", "💩:/foo/bar"); - test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo/"); -} diff --git a/tests/path/dotgit.c b/tests/path/dotgit.c deleted file mode 100644 index 855145f42..000000000 --- a/tests/path/dotgit.c +++ /dev/null @@ -1,206 +0,0 @@ -#include "clar_libgit2.h" - -#include "path.h" - -static char *gitmodules_altnames[] = { - ".gitmodules", - - /* - * Equivalent to the ".git\u200cmodules" string from git but hard-coded - * as a UTF-8 sequence - */ - ".git\xe2\x80\x8cmodules", - - ".Gitmodules", - ".gitmoduleS", - - ".gitmodules ", - ".gitmodules.", - ".gitmodules ", - ".gitmodules. ", - ".gitmodules .", - ".gitmodules..", - ".gitmodules ", - ".gitmodules. ", - ".gitmodules . ", - ".gitmodules .", - - ".Gitmodules ", - ".Gitmodules.", - ".Gitmodules ", - ".Gitmodules. ", - ".Gitmodules .", - ".Gitmodules..", - ".Gitmodules ", - ".Gitmodules. ", - ".Gitmodules . ", - ".Gitmodules .", - - "GITMOD~1", - "gitmod~1", - "GITMOD~2", - "gitmod~3", - "GITMOD~4", - - "GITMOD~1 ", - "gitmod~2.", - "GITMOD~3 ", - "gitmod~4. ", - "GITMOD~1 .", - "gitmod~2 ", - "GITMOD~3. ", - "gitmod~4 . ", - - "GI7EBA~1", - "gi7eba~9", - - "GI7EB~10", - "GI7EB~11", - "GI7EB~99", - "GI7EB~10", - "GI7E~100", - "GI7E~101", - "GI7E~999", - "~1000000", - "~9999999", -}; - -static char *gitmodules_not_altnames[] = { - ".gitmodules x", - ".gitmodules .x", - - " .gitmodules", - - "..gitmodules", - - "gitmodules", - - ".gitmodule", - - ".gitmodules x ", - ".gitmodules .x", - - "GI7EBA~", - "GI7EBA~0", - "GI7EBA~~1", - "GI7EBA~X", - "Gx7EBA~1", - "GI7EBX~1", - - "GI7EB~1", - "GI7EB~01", - "GI7EB~1", -}; - -void test_path_dotgit__dotgit_modules(void) -{ - size_t i; - - cl_assert_equal_i(1, git_path_is_gitfile(".gitmodules", strlen(".gitmodules"), GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_GENERIC)); - cl_assert_equal_i(1, git_path_is_gitfile(".git\xe2\x80\x8cmodules", strlen(".git\xe2\x80\x8cmodules"), GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_GENERIC)); - - for (i = 0; i < ARRAY_SIZE(gitmodules_altnames); i++) { - const char *name = gitmodules_altnames[i]; - if (!git_path_is_gitfile(name, strlen(name), GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_GENERIC)) - cl_fail(name); - } - - for (i = 0; i < ARRAY_SIZE(gitmodules_not_altnames); i++) { - const char *name = gitmodules_not_altnames[i]; - if (git_path_is_gitfile(name, strlen(name), GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_GENERIC)) - cl_fail(name); - } -} - -void test_path_dotgit__dotgit_modules_symlink(void) -{ - cl_assert_equal_b(true, git_path_is_valid(NULL, ".gitmodules", 0, GIT_PATH_REJECT_DOT_GIT_HFS|GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".gitmodules", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".gitmodules", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".gitmodules . .::$DATA", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_NTFS)); -} - -void test_path_dotgit__git_fs_path_is_file(void) -{ - cl_git_fail(git_path_is_gitfile("blob", 4, -1, GIT_PATH_FS_HFS)); - cl_git_pass(git_path_is_gitfile("blob", 4, GIT_PATH_GITFILE_GITIGNORE, GIT_PATH_FS_HFS)); - cl_git_pass(git_path_is_gitfile("blob", 4, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_HFS)); - cl_git_pass(git_path_is_gitfile("blob", 4, GIT_PATH_GITFILE_GITATTRIBUTES, GIT_PATH_FS_HFS)); - cl_git_fail(git_path_is_gitfile("blob", 4, 3, GIT_PATH_FS_HFS)); -} - -void test_path_dotgit__isvalid_dot_git(void) -{ - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git/foo", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/.git", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/.git/bar", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/.GIT/bar", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/bar/.Git", 0, 0)); - - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git/foo", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "foo/.git", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "foo/.git/bar", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "foo/.GIT/bar", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "foo/bar/.Git", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); - - cl_assert_equal_b(true, git_path_is_valid(NULL, "!git", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/!git", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "!git/bar", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".tig", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/.tig", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".tig/bar", 0, 0)); -} - -void test_path_dotgit__isvalid_dotgit_ntfs(void) -{ - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git ", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git.", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git.. .", 0, 0)); - - cl_assert_equal_b(true, git_path_is_valid(NULL, "git~1", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "git~1 ", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "git~1.", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "git~1.. .", 0, 0)); - - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git ", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git.", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git.. .", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - - cl_assert_equal_b(false, git_path_is_valid(NULL, "git~1", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "git~1 ", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "git~1.", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "git~1.. .", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); -} - -void test_path_dotgit__isvalid_dotgit_with_hfs_ignorables(void) -{ - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git\xe2\x80\x8c", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".gi\xe2\x80\x8dT", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".g\xe2\x80\x8eIt", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".\xe2\x80\x8fgIt", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "\xe2\x80\xaa.gIt", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - - cl_assert_equal_b(false, git_path_is_valid(NULL, "\xe2\x80\xab.\xe2\x80\xacG\xe2\x80\xadI\xe2\x80\xaet", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "\xe2\x81\xab.\xe2\x80\xaaG\xe2\x81\xabI\xe2\x80\xact", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "\xe2\x81\xad.\xe2\x80\xaeG\xef\xbb\xbfIT", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - - cl_assert_equal_b(true, git_path_is_valid(NULL, ".", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".g", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".gi", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, " .git", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "..git\xe2\x80\x8c", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".gi\xe2\x80\x8dT.", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".g\xe2\x80It", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".\xe2gIt", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "\xe2\x80\xaa.gi", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".gi\x80\x8dT", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".gi\x8dT", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".g\xe2i\x80T\x8e", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git\xe2\x80\xbf", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git\xe2\xab\x81", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); -} diff --git a/tests/path/win32.c b/tests/path/win32.c deleted file mode 100644 index 1aaf6867a..000000000 --- a/tests/path/win32.c +++ /dev/null @@ -1,282 +0,0 @@ - -#include "clar_libgit2.h" - -#ifdef GIT_WIN32 -#include "win32/path_w32.h" -#endif - -#ifdef GIT_WIN32 -static void test_utf8_to_utf16(const char *utf8_in, const wchar_t *utf16_expected) -{ - git_win32_path path_utf16; - int path_utf16len; - - cl_assert((path_utf16len = git_win32_path_from_utf8(path_utf16, utf8_in)) >= 0); - cl_assert_equal_wcs(utf16_expected, path_utf16); - cl_assert_equal_i(wcslen(utf16_expected), path_utf16len); -} - -static void test_utf8_to_utf16_relative(const char* utf8_in, const wchar_t* utf16_expected) -{ - git_win32_path path_utf16; - int path_utf16len; - - cl_assert((path_utf16len = git_win32_path_relative_from_utf8(path_utf16, utf8_in)) >= 0); - cl_assert_equal_wcs(utf16_expected, path_utf16); - cl_assert_equal_i(wcslen(utf16_expected), path_utf16len); -} -#endif - -void test_path_win32__utf8_to_utf16(void) -{ -#ifdef GIT_WIN32 - test_utf8_to_utf16("C:\\", L"\\\\?\\C:\\"); - test_utf8_to_utf16("c:\\", L"\\\\?\\c:\\"); - test_utf8_to_utf16("C:/", L"\\\\?\\C:\\"); - test_utf8_to_utf16("c:/", L"\\\\?\\c:\\"); -#endif -} - -void test_path_win32__removes_trailing_slash(void) -{ -#ifdef GIT_WIN32 - test_utf8_to_utf16("C:\\Foo\\", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("C:/Foo/", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("C:/Foo///", L"\\\\?\\C:\\Foo"); -#endif -} - -void test_path_win32__squashes_multiple_slashes(void) -{ -#ifdef GIT_WIN32 - test_utf8_to_utf16("C:\\\\Foo\\Bar\\\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); - test_utf8_to_utf16("C://Foo/Bar///Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); -#endif -} - -void test_path_win32__unc(void) -{ -#ifdef GIT_WIN32 - test_utf8_to_utf16("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path"); - test_utf8_to_utf16("//server/git/style/unc/path", L"\\\\?\\UNC\\server\\git\\style\\unc\\path"); -#endif -} - -void test_path_win32__honors_max_path(void) -{ -#ifdef GIT_WIN32 - git_win32_path path_utf16; - - test_utf8_to_utf16("C:\\This path is 261 characters which is fine for our path handling functions which cope with paths longer than MAX_PATH\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghijk", - L"\\\\?\\C:\\This path is 261 characters which is fine for our path handling functions which cope with paths longer than MAX_PATH\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghijk"); - - cl_check_fail(git_win32_path_from_utf8(path_utf16, "C:\\This path is 4097 chars and exceeds our maximum path length on Windows which is limited to 4096 characters\\alas\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij01")); - -#endif -} - -void test_path_win32__dot_and_dotdot(void) -{ -#ifdef GIT_WIN32 - test_utf8_to_utf16("C:\\Foo\\..\\Foobar", L"\\\\?\\C:\\Foobar"); - test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar", L"\\\\?\\C:\\Foo\\Foobar"); - test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar\\..", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("C:\\Foobar\\..", L"\\\\?\\C:\\"); - test_utf8_to_utf16("C:/Foo/Bar/../Foobar", L"\\\\?\\C:\\Foo\\Foobar"); - test_utf8_to_utf16("C:/Foo/Bar/../Foobar/../Asdf/", L"\\\\?\\C:\\Foo\\Asdf"); - test_utf8_to_utf16("C:/Foo/Bar/../Foobar/..", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("C:/Foo/..", L"\\\\?\\C:\\"); - - test_utf8_to_utf16("C:\\Foo\\Bar\\.\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); - test_utf8_to_utf16("C:\\.\\Foo\\.\\Bar\\.\\Foobar\\.\\", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); - test_utf8_to_utf16("C:/Foo/Bar/./Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); - test_utf8_to_utf16("C:/Foo/../Bar/./Foobar/../", L"\\\\?\\C:\\Bar"); - - test_utf8_to_utf16("C:\\Foo\\..\\..\\Bar", L"\\\\?\\C:\\Bar"); -#endif -} - -void test_path_win32__absolute_from_no_drive_letter(void) -{ -#ifdef GIT_WIN32 - test_utf8_to_utf16("\\Foo", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar"); - test_utf8_to_utf16("/Foo/Bar", L"\\\\?\\C:\\Foo\\Bar"); -#endif -} - -void test_path_win32__absolute_from_relative(void) -{ -#ifdef GIT_WIN32 - char cwd_backup[MAX_PATH]; - - cl_must_pass(p_getcwd(cwd_backup, MAX_PATH)); - cl_must_pass(p_chdir("C:/")); - - test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("..\\..\\Foo", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("Foo\\..", L"\\\\?\\C:\\"); - test_utf8_to_utf16("Foo\\..\\..", L"\\\\?\\C:\\"); - test_utf8_to_utf16("", L"\\\\?\\C:\\"); - - cl_must_pass(p_chdir("C:/Windows")); - - test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Windows\\Foo"); - test_utf8_to_utf16("Foo\\Bar", L"\\\\?\\C:\\Windows\\Foo\\Bar"); - test_utf8_to_utf16("..\\Foo", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("Foo\\..\\Bar", L"\\\\?\\C:\\Windows\\Bar"); - test_utf8_to_utf16("", L"\\\\?\\C:\\Windows"); - - cl_must_pass(p_chdir(cwd_backup)); -#endif -} - -void test_path_win32__keeps_relative(void) -{ -#ifdef GIT_WIN32 - /* Relative paths stay relative */ - test_utf8_to_utf16_relative("Foo", L"Foo"); - test_utf8_to_utf16_relative("..\\..\\Foo", L"..\\..\\Foo"); - test_utf8_to_utf16_relative("Foo\\..", L"Foo\\.."); - test_utf8_to_utf16_relative("Foo\\..\\..", L"Foo\\..\\.."); - test_utf8_to_utf16_relative("Foo\\Bar", L"Foo\\Bar"); - test_utf8_to_utf16_relative("Foo\\..\\Bar", L"Foo\\..\\Bar"); - test_utf8_to_utf16_relative("../../Foo", L"..\\..\\Foo"); - test_utf8_to_utf16_relative("Foo/..", L"Foo\\.."); - test_utf8_to_utf16_relative("Foo/../..", L"Foo\\..\\.."); - test_utf8_to_utf16_relative("Foo/Bar", L"Foo\\Bar"); - test_utf8_to_utf16_relative("Foo/../Bar", L"Foo\\..\\Bar"); - test_utf8_to_utf16_relative("Foo/../Bar/", L"Foo\\..\\Bar\\"); - test_utf8_to_utf16_relative("", L""); - - /* Absolute paths are canonicalized */ - test_utf8_to_utf16_relative("\\Foo", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16_relative("/Foo/Bar/", L"\\\\?\\C:\\Foo\\Bar"); - test_utf8_to_utf16_relative("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path"); -#endif -} - -#ifdef GIT_WIN32 -static void test_canonicalize(const wchar_t *in, const wchar_t *expected) -{ - git_win32_path canonical; - - cl_assert(wcslen(in) < MAX_PATH); - wcscpy(canonical, in); - - cl_must_pass(git_win32_path_canonicalize(canonical)); - cl_assert_equal_wcs(expected, canonical); -} -#endif - -static void test_remove_namespace(const wchar_t *in, const wchar_t *expected) -{ -#ifdef GIT_WIN32 - git_win32_path canonical; - - cl_assert(wcslen(in) < MAX_PATH); - wcscpy(canonical, in); - - git_win32_path_remove_namespace(canonical, wcslen(in)); - cl_assert_equal_wcs(expected, canonical); -#else - GIT_UNUSED(in); - GIT_UNUSED(expected); -#endif -} - -void test_path_win32__remove_namespace(void) -{ - test_remove_namespace(L"\\\\?\\C:\\Temp\\Foo", L"C:\\Temp\\Foo"); - test_remove_namespace(L"\\\\?\\C:\\", L"C:\\"); - test_remove_namespace(L"\\\\?\\", L""); - - test_remove_namespace(L"\\??\\C:\\Temp\\Foo", L"C:\\Temp\\Foo"); - test_remove_namespace(L"\\??\\C:\\", L"C:\\"); - test_remove_namespace(L"\\??\\", L""); - - test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); - test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); - test_remove_namespace(L"\\\\?\\UNC\\server\\C$", L"\\\\server\\C$"); - test_remove_namespace(L"\\\\?\\UNC\\server\\", L"\\\\server"); - test_remove_namespace(L"\\\\?\\UNC\\server", L"\\\\server"); - - test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); - test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); - test_remove_namespace(L"\\??\\UNC\\server\\C$", L"\\\\server\\C$"); - test_remove_namespace(L"\\??\\UNC\\server\\", L"\\\\server"); - test_remove_namespace(L"\\??\\UNC\\server", L"\\\\server"); - - test_remove_namespace(L"\\\\server\\C$\\folder", L"\\\\server\\C$\\folder"); - test_remove_namespace(L"\\\\server\\C$", L"\\\\server\\C$"); - test_remove_namespace(L"\\\\server\\", L"\\\\server"); - test_remove_namespace(L"\\\\server", L"\\\\server"); - - test_remove_namespace(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar"); - test_remove_namespace(L"C:\\", L"C:\\"); - test_remove_namespace(L"", L""); - -} - -void test_path_win32__canonicalize(void) -{ -#ifdef GIT_WIN32 - test_canonicalize(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar"); - test_canonicalize(L"C:\\Foo\\", L"C:\\Foo"); - test_canonicalize(L"C:\\Foo\\\\", L"C:\\Foo"); - test_canonicalize(L"C:\\Foo\\..\\Bar", L"C:\\Bar"); - test_canonicalize(L"C:\\Foo\\..\\..\\Bar", L"C:\\Bar"); - test_canonicalize(L"C:\\Foo\\..\\..\\..\\..\\", L"C:\\"); - test_canonicalize(L"C:/Foo/Bar", L"C:\\Foo\\Bar"); - test_canonicalize(L"C:/", L"C:\\"); - - test_canonicalize(L"\\\\?\\C:\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar"); - test_canonicalize(L"\\\\?\\C:\\Foo\\Bar\\", L"\\\\?\\C:\\Foo\\Bar"); - test_canonicalize(L"\\\\?\\C:\\\\Foo\\.\\Bar\\\\..\\", L"\\\\?\\C:\\Foo"); - test_canonicalize(L"\\\\?\\C:\\\\", L"\\\\?\\C:\\"); - test_canonicalize(L"//?/C:/", L"\\\\?\\C:\\"); - test_canonicalize(L"//?/C:/../../Foo/", L"\\\\?\\C:\\Foo"); - test_canonicalize(L"//?/C:/Foo/../../", L"\\\\?\\C:\\"); - - test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\?\\UNC\\server\\C$\\folder"); - test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder"); - test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder"); - test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\..\\..\\..\\..\\share\\", L"\\\\?\\UNC\\server\\share"); - - test_canonicalize(L"\\\\server\\share", L"\\\\server\\share"); - test_canonicalize(L"\\\\server\\share\\", L"\\\\server\\share"); - test_canonicalize(L"\\\\server\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar"); - test_canonicalize(L"\\\\server\\\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar"); - test_canonicalize(L"\\\\server\\share\\..\\foo", L"\\\\server\\foo"); - test_canonicalize(L"\\\\server\\..\\..\\share\\.\\foo", L"\\\\server\\share\\foo"); -#endif -} - -void test_path_win32__8dot3_name(void) -{ -#ifdef GIT_WIN32 - char *shortname; - - if (!cl_sandbox_supports_8dot3()) - clar__skip(); - - /* Some guaranteed short names */ - cl_assert_equal_s("PROGRA~1", (shortname = git_win32_path_8dot3_name("C:\\Program Files"))); - git__free(shortname); - - cl_assert_equal_s("WINDOWS", (shortname = git_win32_path_8dot3_name("C:\\WINDOWS"))); - git__free(shortname); - - /* Create some predictable short names */ - cl_must_pass(p_mkdir(".foo", 0777)); - cl_assert_equal_s("FOO~1", (shortname = git_win32_path_8dot3_name(".foo"))); - git__free(shortname); - - cl_git_write2file("bar~1", "foobar\n", 7, O_RDWR|O_CREAT, 0666); - cl_must_pass(p_mkdir(".bar", 0777)); - cl_assert_equal_s("BAR~2", (shortname = git_win32_path_8dot3_name(".bar"))); - git__free(shortname); -#endif -} diff --git a/tests/perf/helper__perf__do_merge.c b/tests/perf/helper__perf__do_merge.c deleted file mode 100644 index c77b46a1f..000000000 --- a/tests/perf/helper__perf__do_merge.c +++ /dev/null @@ -1,75 +0,0 @@ -#include "clar_libgit2.h" -#include "helper__perf__do_merge.h" -#include "helper__perf__timer.h" - -static git_repository * g_repo; - -void perf__do_merge(const char *fixture, - const char *test_name, - const char *id_a, - const char *id_b) -{ - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; - git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; - git_oid oid_a; - git_oid oid_b; - git_reference *ref_branch_a = NULL; - git_reference *ref_branch_b = NULL; - git_commit *commit_a = NULL; - git_commit *commit_b = NULL; - git_annotated_commit *annotated_commits[1] = { NULL }; - perf_timer t_total = PERF_TIMER_INIT; - perf_timer t_clone = PERF_TIMER_INIT; - perf_timer t_checkout = PERF_TIMER_INIT; - perf_timer t_merge = PERF_TIMER_INIT; - - perf__timer__start(&t_total); - - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - clone_opts.checkout_opts = checkout_opts; - - perf__timer__start(&t_clone); - cl_git_pass(git_clone(&g_repo, fixture, test_name, &clone_opts)); - perf__timer__stop(&t_clone); - - git_oid_fromstr(&oid_a, id_a); - cl_git_pass(git_commit_lookup(&commit_a, g_repo, &oid_a)); - cl_git_pass(git_branch_create(&ref_branch_a, g_repo, - "A", commit_a, - 0)); - - perf__timer__start(&t_checkout); - cl_git_pass(git_checkout_tree(g_repo, (git_object*)commit_a, &checkout_opts)); - perf__timer__stop(&t_checkout); - - cl_git_pass(git_repository_set_head(g_repo, git_reference_name(ref_branch_a))); - - git_oid_fromstr(&oid_b, id_b); - cl_git_pass(git_commit_lookup(&commit_b, g_repo, &oid_b)); - cl_git_pass(git_branch_create(&ref_branch_b, g_repo, - "B", commit_b, - 0)); - - cl_git_pass(git_annotated_commit_lookup(&annotated_commits[0], g_repo, &oid_b)); - - perf__timer__start(&t_merge); - cl_git_pass(git_merge(g_repo, - (const git_annotated_commit **)annotated_commits, 1, - &merge_opts, &checkout_opts)); - perf__timer__stop(&t_merge); - - git_reference_free(ref_branch_a); - git_reference_free(ref_branch_b); - git_commit_free(commit_a); - git_commit_free(commit_b); - git_annotated_commit_free(annotated_commits[0]); - git_repository_free(g_repo); - - perf__timer__stop(&t_total); - - perf__timer__report(&t_clone, "%s: clone", test_name); - perf__timer__report(&t_checkout, "%s: checkout", test_name); - perf__timer__report(&t_merge, "%s: merge", test_name); - perf__timer__report(&t_total, "%s: total", test_name); -} diff --git a/tests/perf/helper__perf__do_merge.h b/tests/perf/helper__perf__do_merge.h deleted file mode 100644 index 4a4723da5..000000000 --- a/tests/perf/helper__perf__do_merge.h +++ /dev/null @@ -1,4 +0,0 @@ -void perf__do_merge(const char *fixture, - const char *test_name, - const char *id_a, - const char *id_b); diff --git a/tests/perf/helper__perf__timer.c b/tests/perf/helper__perf__timer.c deleted file mode 100644 index 8a7ed09e8..000000000 --- a/tests/perf/helper__perf__timer.c +++ /dev/null @@ -1,73 +0,0 @@ -#include "clar_libgit2.h" -#include "helper__perf__timer.h" - -#if defined(GIT_WIN32) - -void perf__timer__start(perf_timer *t) -{ - QueryPerformanceCounter(&t->time_started); -} - -void perf__timer__stop(perf_timer *t) -{ - LARGE_INTEGER time_now; - QueryPerformanceCounter(&time_now); - - t->sum.QuadPart += (time_now.QuadPart - t->time_started.QuadPart); -} - -void perf__timer__report(perf_timer *t, const char *fmt, ...) -{ - va_list arglist; - LARGE_INTEGER freq; - double fraction; - - QueryPerformanceFrequency(&freq); - - fraction = ((double)t->sum.QuadPart) / ((double)freq.QuadPart); - - printf("%10.3f: ", fraction); - - va_start(arglist, fmt); - vprintf(fmt, arglist); - va_end(arglist); - - printf("\n"); -} - -#else - -#include - -static uint32_t now_in_ms(void) -{ - struct timeval now; - gettimeofday(&now, NULL); - return (uint32_t)((now.tv_sec * 1000) + (now.tv_usec / 1000)); -} - -void perf__timer__start(perf_timer *t) -{ - t->time_started = now_in_ms(); -} - -void perf__timer__stop(perf_timer *t) -{ - uint32_t now = now_in_ms(); - t->sum += (now - t->time_started); -} - -void perf__timer__report(perf_timer *t, const char *fmt, ...) -{ - va_list arglist; - - printf("%10.3f: ", ((double)t->sum) / 1000); - - va_start(arglist, fmt); - vprintf(fmt, arglist); - va_end(arglist); - - printf("\n"); -} - -#endif diff --git a/tests/perf/helper__perf__timer.h b/tests/perf/helper__perf__timer.h deleted file mode 100644 index 5aff4b136..000000000 --- a/tests/perf/helper__perf__timer.h +++ /dev/null @@ -1,27 +0,0 @@ -#if defined(GIT_WIN32) - -struct perf__timer -{ - LARGE_INTEGER sum; - LARGE_INTEGER time_started; -}; - -#define PERF_TIMER_INIT {0} - -#else - -struct perf__timer -{ - uint32_t sum; - uint32_t time_started; -}; - -#define PERF_TIMER_INIT {0} - -#endif - -typedef struct perf__timer perf_timer; - -void perf__timer__start(perf_timer *t); -void perf__timer__stop(perf_timer *t); -void perf__timer__report(perf_timer *t, const char *fmt, ...); diff --git a/tests/perf/merge.c b/tests/perf/merge.c deleted file mode 100644 index 721902d63..000000000 --- a/tests/perf/merge.c +++ /dev/null @@ -1,31 +0,0 @@ -#include "clar_libgit2.h" -#include "helper__perf__do_merge.h" - -/* This test requires a large repo with many files. - * It doesn't care about the contents, just the size. - * - * For now, we use the LibGit2 repo containing the - * source tree because it is already here. - * - * `find . | wc -l` reports 5128. - * - */ -#define SRC_REPO (cl_fixture("../..")) - -/* We need 2 arbitrary commits within that repo - * that have a large number of changed files. - * Again, we don't care about the actual contents, - * just the size. - * - * For now, we use these public branches: - * maint/v0.21 d853fb9f24e0fe63b3dce9fbc04fd9cfe17a030b Always checkout with case sensitive iterator - * maint/v0.22 1ce9ea3ba9b4fa666602d52a5281d41a482cc58b checkout tests: cleanup realpath impl on Win32 - * - */ -#define ID_BRANCH_A "d853fb9f24e0fe63b3dce9fbc04fd9cfe17a030b" -#define ID_BRANCH_B "1ce9ea3ba9b4fa666602d52a5281d41a482cc58b" - -void test_perf_merge__m1(void) -{ - perf__do_merge(SRC_REPO, "m1", ID_BRANCH_A, ID_BRANCH_B); -} diff --git a/tests/precompiled.c b/tests/precompiled.c deleted file mode 100644 index 5f656a45d..000000000 --- a/tests/precompiled.c +++ /dev/null @@ -1 +0,0 @@ -#include "precompiled.h" diff --git a/tests/precompiled.h b/tests/precompiled.h deleted file mode 100644 index ea53a60e9..000000000 --- a/tests/precompiled.h +++ /dev/null @@ -1,4 +0,0 @@ -#include "common.h" -#include "git2.h" -#include "clar.h" -#include "clar_libgit2.h" diff --git a/tests/rebase/abort.c b/tests/rebase/abort.c deleted file mode 100644 index a83c529ce..000000000 --- a/tests/rebase/abort.c +++ /dev/null @@ -1,250 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/rebase.h" -#include "merge.h" -#include "posix.h" -#include "annotated_commit.h" - -#include - -static git_repository *repo; - -/* Fixture setup and teardown */ -void test_rebase_abort__initialize(void) -{ - repo = cl_git_sandbox_init("rebase"); -} - -void test_rebase_abort__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void ensure_aborted( - git_annotated_commit *branch, - git_annotated_commit *onto) -{ - git_reference *head_ref, *branch_ref = NULL; - git_status_list *statuslist; - git_reflog *reflog; - const git_reflog_entry *reflog_entry; - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - /* Make sure the refs are updated appropriately */ - cl_git_pass(git_reference_lookup(&head_ref, repo, "HEAD")); - - if (branch->ref_name == NULL) - cl_assert_equal_oid(git_annotated_commit_id(branch), git_reference_target(head_ref)); - else { - cl_assert_equal_s("refs/heads/beef", git_reference_symbolic_target(head_ref)); - cl_git_pass(git_reference_lookup(&branch_ref, repo, git_reference_symbolic_target(head_ref))); - cl_assert_equal_oid(git_annotated_commit_id(branch), git_reference_target(branch_ref)); - } - - git_status_list_new(&statuslist, repo, NULL); - cl_assert_equal_i(0, git_status_list_entrycount(statuslist)); - git_status_list_free(statuslist); - - /* Make sure the reflogs are updated appropriately */ - cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); - - cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); - cl_assert_equal_oid(git_annotated_commit_id(onto), git_reflog_entry_id_old(reflog_entry)); - cl_assert_equal_oid(git_annotated_commit_id(branch), git_reflog_entry_id_new(reflog_entry)); - cl_assert_equal_s("rebase: aborting", git_reflog_entry_message(reflog_entry)); - - git_reflog_free(reflog); - git_reference_free(head_ref); - git_reference_free(branch_ref); -} - -static void test_abort( - git_annotated_commit *branch, git_annotated_commit *onto) -{ - git_rebase *rebase; - - cl_git_pass(git_rebase_open(&rebase, repo, NULL)); - cl_git_pass(git_rebase_abort(rebase)); - - ensure_aborted(branch, onto); - - git_rebase_free(rebase); -} - -void test_rebase_abort__merge(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *onto_ref; - git_annotated_commit *branch_head, *onto_head; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - test_abort(branch_head, onto_head); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(onto_head); - - git_reference_free(branch_ref); - git_reference_free(onto_ref); - git_rebase_free(rebase); -} - -void test_rebase_abort__merge_immediately_after_init(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *onto_ref; - git_annotated_commit *branch_head, *onto_head; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - cl_git_pass(git_rebase_abort(rebase)); - ensure_aborted(branch_head, onto_head); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(onto_head); - - git_reference_free(branch_ref); - git_reference_free(onto_ref); - git_rebase_free(rebase); -} - -void test_rebase_abort__merge_by_id(void) -{ - git_rebase *rebase; - git_oid branch_id, onto_id; - git_annotated_commit *branch_head, *onto_head; - - cl_git_pass(git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64")); - cl_git_pass(git_oid_fromstr(&onto_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); - - cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); - cl_git_pass(git_annotated_commit_lookup(&onto_head, repo, &onto_id)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - test_abort(branch_head, onto_head); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(onto_head); - - git_rebase_free(rebase); -} - -void test_rebase_abort__merge_by_revspec(void) -{ - git_rebase *rebase; - git_annotated_commit *branch_head, *onto_head; - - cl_git_pass(git_annotated_commit_from_revspec(&branch_head, repo, "b146bd7")); - cl_git_pass(git_annotated_commit_from_revspec(&onto_head, repo, "efad0b1")); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - test_abort(branch_head, onto_head); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(onto_head); - - git_rebase_free(rebase); -} - -void test_rebase_abort__merge_by_id_immediately_after_init(void) -{ - git_rebase *rebase; - git_oid branch_id, onto_id; - git_annotated_commit *branch_head, *onto_head; - - cl_git_pass(git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64")); - cl_git_pass(git_oid_fromstr(&onto_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); - - cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); - cl_git_pass(git_annotated_commit_lookup(&onto_head, repo, &onto_id)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - cl_git_pass(git_rebase_abort(rebase)); - ensure_aborted(branch_head, onto_head); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(onto_head); - - git_rebase_free(rebase); -} - -void test_rebase_abort__detached_head(void) -{ - git_rebase *rebase; - git_oid branch_id, onto_id; - git_signature *signature; - git_annotated_commit *branch_head, *onto_head; - - git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64"); - git_oid_fromstr(&onto_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - - cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); - cl_git_pass(git_annotated_commit_lookup(&onto_head, repo, &onto_id)); - - cl_git_pass(git_signature_new(&signature, "Rebaser", "rebaser@example.com", 1404157834, -400)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - test_abort(branch_head, onto_head); - - git_signature_free(signature); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(onto_head); - - git_rebase_free(rebase); -} - -void test_rebase_abort__old_style_head_file(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *onto_ref; - git_signature *signature; - git_annotated_commit *branch_head, *onto_head; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); - - cl_git_pass(git_signature_new(&signature, "Rebaser", "rebaser@example.com", 1404157834, -400)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - p_rename("rebase-merge/.git/rebase-merge/orig-head", - "rebase-merge/.git/rebase-merge/head"); - - test_abort(branch_head, onto_head); - - git_signature_free(signature); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(onto_head); - - git_reference_free(branch_ref); - git_reference_free(onto_ref); - git_rebase_free(rebase); -} diff --git a/tests/rebase/inmemory.c b/tests/rebase/inmemory.c deleted file mode 100644 index 040a81b1b..000000000 --- a/tests/rebase/inmemory.c +++ /dev/null @@ -1,210 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/rebase.h" -#include "posix.h" - -#include - -static git_repository *repo; -static git_signature *signature; - -/* Fixture setup and teardown */ -void test_rebase_inmemory__initialize(void) -{ - repo = cl_git_sandbox_init("rebase"); - - cl_git_pass(git_signature_new(&signature, - "Rebaser", "rebaser@rebaser.rb", 1405694510, 0)); -} - -void test_rebase_inmemory__cleanup(void) -{ - git_signature_free(signature); - cl_git_sandbox_cleanup(); -} - -void test_rebase_inmemory__not_in_rebase_state(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; - - opts.inmemory = true; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - git_rebase_free(rebase); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - - git_reference_free(branch_ref); - git_reference_free(upstream_ref); -} - -void test_rebase_inmemory__can_resolve_conflicts(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_status_list *status_list; - git_oid pick_id, commit_id, expected_commit_id; - git_index *rebase_index, *repo_index; - git_index_entry resolution = {{0}}; - git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; - - opts.inmemory = true; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - - git_oid_fromstr(&pick_id, "33f915f9e4dbd9f4b24430e48731a59b45b15500"); - - cl_assert_equal_i(GIT_REBASE_OPERATION_PICK, rebase_operation->type); - cl_assert_equal_oid(&pick_id, &rebase_operation->id); - - /* ensure that we did not do anything stupid to the workdir or repo index */ - cl_git_pass(git_repository_index(&repo_index, repo)); - cl_assert(!git_index_has_conflicts(repo_index)); - - cl_git_pass(git_status_list_new(&status_list, repo, NULL)); - cl_assert_equal_i(0, git_status_list_entrycount(status_list)); - - /* but that the index returned from rebase does have conflicts */ - cl_git_pass(git_rebase_inmemory_index(&rebase_index, rebase)); - cl_assert(git_index_has_conflicts(rebase_index)); - - cl_git_fail_with(GIT_EUNMERGED, git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - - /* ensure that we can work with the in-memory index to resolve the conflict */ - resolution.path = "asparagus.txt"; - resolution.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&resolution.id, "414dfc71ead79c07acd4ea47fecf91f289afc4b9"); - cl_git_pass(git_index_conflict_remove(rebase_index, "asparagus.txt")); - cl_git_pass(git_index_add(rebase_index, &resolution)); - - /* and finally create a commit for the resolved rebase operation */ - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - - cl_git_pass(git_oid_fromstr(&expected_commit_id, "db7af47222181e548810da2ab5fec0e9357c5637")); - cl_assert_equal_oid(&commit_id, &expected_commit_id); - - git_status_list_free(status_list); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_index_free(repo_index); - git_index_free(rebase_index); - git_rebase_free(rebase); -} - -void test_rebase_inmemory__no_common_ancestor(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id, expected_final_id; - git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; - - opts.inmemory = true; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/barley")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_finish(rebase, signature)); - - git_oid_fromstr(&expected_final_id, "71e7ee8d4fe7d8bf0d107355197e0a953dfdb7f3"); - cl_assert_equal_oid(&expected_final_id, &commit_id); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_inmemory__with_directories(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id, tree_id; - git_commit *commit; - git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; - - opts.inmemory = true; - - git_oid_fromstr(&tree_id, "a4d6d9c3d57308fd8e320cf2525bae8f1adafa57"); - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/deep_gravy")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); - - cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); - cl_assert_equal_oid(&tree_id, git_commit_tree_id(commit)); - - git_commit_free(commit); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} diff --git a/tests/rebase/iterator.c b/tests/rebase/iterator.c deleted file mode 100644 index a120f28ac..000000000 --- a/tests/rebase/iterator.c +++ /dev/null @@ -1,140 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/rebase.h" -#include "posix.h" - -#include - -static git_repository *repo; -static git_index *_index; -static git_signature *signature; - -/* Fixture setup and teardown */ -void test_rebase_iterator__initialize(void) -{ - repo = cl_git_sandbox_init("rebase"); - cl_git_pass(git_repository_index(&_index, repo)); - cl_git_pass(git_signature_new(&signature, "Rebaser", - "rebaser@rebaser.rb", 1405694510, 0)); -} - -void test_rebase_iterator__cleanup(void) -{ - git_signature_free(signature); - git_index_free(_index); - cl_git_sandbox_cleanup(); -} - -static void test_operations(git_rebase *rebase, size_t expected_current) -{ - size_t i, expected_count = 5; - git_oid expected_oid[5]; - git_rebase_operation *operation; - - git_oid_fromstr(&expected_oid[0], "da9c51a23d02d931a486f45ad18cda05cf5d2b94"); - git_oid_fromstr(&expected_oid[1], "8d1f13f93c4995760ac07d129246ac1ff64c0be9"); - git_oid_fromstr(&expected_oid[2], "3069cc907e6294623e5917ef6de663928c1febfb"); - git_oid_fromstr(&expected_oid[3], "588e5d2f04d49707fe4aab865e1deacaf7ef6787"); - git_oid_fromstr(&expected_oid[4], "b146bd7608eac53d9bf9e1a6963543588b555c64"); - - cl_assert_equal_i(expected_count, git_rebase_operation_entrycount(rebase)); - cl_assert_equal_i(expected_current, git_rebase_operation_current(rebase)); - - for (i = 0; i < expected_count; i++) { - operation = git_rebase_operation_byindex(rebase, i); - cl_assert_equal_i(GIT_REBASE_OPERATION_PICK, operation->type); - cl_assert_equal_oid(&expected_oid[i], &operation->id); - cl_assert_equal_p(NULL, operation->exec); - } -} - -static void test_iterator(bool inmemory) -{ - git_rebase *rebase; - git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id, expected_id; - int error; - - opts.inmemory = inmemory; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); - test_operations(rebase, GIT_REBASE_NO_OPERATION); - - if (!inmemory) { - git_rebase_free(rebase); - cl_git_pass(git_rebase_open(&rebase, repo, NULL)); - } - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - test_operations(rebase, 0); - - git_oid_fromstr(&expected_id, "776e4c48922799f903f03f5f6e51da8b01e4cce0"); - cl_assert_equal_oid(&expected_id, &commit_id); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - test_operations(rebase, 1); - - git_oid_fromstr(&expected_id, "ba1f9b4fd5cf8151f7818be2111cc0869f1eb95a"); - cl_assert_equal_oid(&expected_id, &commit_id); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - test_operations(rebase, 2); - - git_oid_fromstr(&expected_id, "948b12fe18b84f756223a61bece4c307787cd5d4"); - cl_assert_equal_oid(&expected_id, &commit_id); - - if (!inmemory) { - git_rebase_free(rebase); - cl_git_pass(git_rebase_open(&rebase, repo, NULL)); - } - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - test_operations(rebase, 3); - - git_oid_fromstr(&expected_id, "d9d5d59d72c9968687f9462578d79878cd80e781"); - cl_assert_equal_oid(&expected_id, &commit_id); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - test_operations(rebase, 4); - - git_oid_fromstr(&expected_id, "9cf383c0a125d89e742c5dec58ed277dd07588b3"); - cl_assert_equal_oid(&expected_id, &commit_id); - - cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); - cl_assert_equal_i(GIT_ITEROVER, error); - test_operations(rebase, 4); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_iterator__iterates(void) -{ - test_iterator(false); -} - -void test_rebase_iterator__iterates_inmemory(void) -{ - test_iterator(true); -} diff --git a/tests/rebase/merge.c b/tests/rebase/merge.c deleted file mode 100644 index 5f730f750..000000000 --- a/tests/rebase/merge.c +++ /dev/null @@ -1,855 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/checkout.h" -#include "git2/rebase.h" -#include "posix.h" -#include "signature.h" - -#include - -static git_repository *repo; -static git_signature *signature; - -static void set_core_autocrlf_to(git_repository *repo, bool value) -{ - git_config *cfg; - - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", value)); - - git_config_free(cfg); -} - -/* Fixture setup and teardown */ -void test_rebase_merge__initialize(void) -{ - repo = cl_git_sandbox_init("rebase"); - cl_git_pass(git_signature_new(&signature, - "Rebaser", "rebaser@rebaser.rb", 1405694510, 0)); - - set_core_autocrlf_to(repo, false); -} - -void test_rebase_merge__cleanup(void) -{ - git_signature_free(signature); - cl_git_sandbox_cleanup(); -} - -void test_rebase_merge__next(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_status_list *status_list; - const git_status_entry *status_entry; - git_oid pick_id, file1_id; - git_oid master_id, beef_id; - - git_oid_fromstr(&master_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - git_oid_fromstr(&beef_id, "b146bd7608eac53d9bf9e1a6963543588b555c64"); - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_assert_equal_s("refs/heads/beef", git_rebase_orig_head_name(rebase)); - cl_assert_equal_oid(&beef_id, git_rebase_orig_head_id(rebase)); - - cl_assert_equal_s("master", git_rebase_onto_name(rebase)); - cl_assert_equal_oid(&master_id, git_rebase_onto_id(rebase)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - - git_oid_fromstr(&pick_id, "da9c51a23d02d931a486f45ad18cda05cf5d2b94"); - - cl_assert_equal_i(GIT_REBASE_OPERATION_PICK, rebase_operation->type); - cl_assert_equal_oid(&pick_id, &rebase_operation->id); - cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/current"); - cl_assert_equal_file("1\n", 2, "rebase/.git/rebase-merge/msgnum"); - - cl_git_pass(git_status_list_new(&status_list, repo, NULL)); - cl_assert_equal_i(1, git_status_list_entrycount(status_list)); - cl_assert(status_entry = git_status_byindex(status_list, 0)); - - cl_assert_equal_s("beef.txt", status_entry->head_to_index->new_file.path); - - git_oid_fromstr(&file1_id, "8d95ea62e621f1d38d230d9e7d206e41096d76af"); - cl_assert_equal_oid(&file1_id, &status_entry->head_to_index->new_file.id); - - git_status_list_free(status_list); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_merge__next_with_conflicts(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_status_list *status_list; - const git_status_entry *status_entry; - git_oid pick_id, commit_id; - - const char *expected_merge = -"ASPARAGUS SOUP.\n" -"\n" -"<<<<<<< master\n" -"TAKE FOUR LARGE BUNCHES of asparagus, scrape it nicely, cut off one inch\n" -"OF THE TOPS, and lay them in water, chop the stalks and put them on the\n" -"FIRE WITH A PIECE OF BACON, a large onion cut up, and pepper and salt;\n" -"ADD TWO QUARTS OF WATER, boil them till the stalks are quite soft, then\n" -"PULP THEM THROUGH A SIEVE, and strain the water to it, which must be put\n" -"=======\n" -"Take four large bunches of asparagus, scrape it nicely, CUT OFF ONE INCH\n" -"of the tops, and lay them in water, chop the stalks and PUT THEM ON THE\n" -"fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" -"add two quarts of water, boil them till the stalks are quite soft, then\n" -"pulp them through a sieve, and strain the water to it, which must be put\n" -">>>>>>> Conflicting modification 1 to asparagus\n" -"back in the pot; put into it a chicken cut up, with the tops of\n" -"asparagus which had been laid by, boil it until these last articles are\n" -"sufficiently done, thicken with flour, butter and milk, and serve it up.\n"; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - - git_oid_fromstr(&pick_id, "33f915f9e4dbd9f4b24430e48731a59b45b15500"); - - cl_assert_equal_i(GIT_REBASE_OPERATION_PICK, rebase_operation->type); - cl_assert_equal_oid(&pick_id, &rebase_operation->id); - cl_assert_equal_file("33f915f9e4dbd9f4b24430e48731a59b45b15500\n", 41, "rebase/.git/rebase-merge/current"); - cl_assert_equal_file("1\n", 2, "rebase/.git/rebase-merge/msgnum"); - - cl_git_pass(git_status_list_new(&status_list, repo, NULL)); - cl_assert_equal_i(1, git_status_list_entrycount(status_list)); - cl_assert(status_entry = git_status_byindex(status_list, 0)); - - cl_assert_equal_s("asparagus.txt", status_entry->head_to_index->new_file.path); - - cl_assert_equal_file(expected_merge, strlen(expected_merge), "rebase/asparagus.txt"); - - cl_git_fail_with(GIT_EUNMERGED, git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - - git_status_list_free(status_list); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_merge__next_stops_with_iterover(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id; - int error; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); - cl_assert_equal_i(GIT_ITEROVER, error); - - cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); - cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/msgnum"); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_merge__commit(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id, tree_id, parent_id; - git_signature *author; - git_commit *commit; - git_reflog *reflog; - const git_reflog_entry *reflog_entry; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); - - git_oid_fromstr(&parent_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - cl_assert_equal_i(1, git_commit_parentcount(commit)); - cl_assert_equal_oid(&parent_id, git_commit_parent_id(commit, 0)); - - git_oid_fromstr(&tree_id, "4461379789c777d2a6c1f2ee0e9d6c86731b9992"); - cl_assert_equal_oid(&tree_id, git_commit_tree_id(commit)); - - cl_assert_equal_s(NULL, git_commit_message_encoding(commit)); - cl_assert_equal_s("Modification 1 to beef\n", git_commit_message(commit)); - - cl_git_pass(git_signature_new(&author, - "Edward Thomson", "ethomson@edwardthomson.com", 1405621769, 0-(4*60))); - cl_assert(git_signature__equal(author, git_commit_author(commit))); - - cl_assert(git_signature__equal(signature, git_commit_committer(commit))); - - /* Make sure the reflogs are updated appropriately */ - cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); - cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); - cl_assert_equal_oid(&parent_id, git_reflog_entry_id_old(reflog_entry)); - cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); - cl_assert_equal_s("rebase: Modification 1 to beef", git_reflog_entry_message(reflog_entry)); - - git_reflog_free(reflog); - git_signature_free(author); - git_commit_free(commit); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_merge__commit_with_id(void) -{ - git_rebase *rebase; - git_oid branch_id, upstream_id; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id, tree_id, parent_id; - git_signature *author; - git_commit *commit; - git_reflog *reflog; - const git_reflog_entry *reflog_entry; - - cl_git_pass(git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64")); - cl_git_pass(git_oid_fromstr(&upstream_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); - - cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); - cl_git_pass(git_annotated_commit_lookup(&upstream_head, repo, &upstream_id)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); - - git_oid_fromstr(&parent_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - cl_assert_equal_i(1, git_commit_parentcount(commit)); - cl_assert_equal_oid(&parent_id, git_commit_parent_id(commit, 0)); - - git_oid_fromstr(&tree_id, "4461379789c777d2a6c1f2ee0e9d6c86731b9992"); - cl_assert_equal_oid(&tree_id, git_commit_tree_id(commit)); - - cl_assert_equal_s(NULL, git_commit_message_encoding(commit)); - cl_assert_equal_s("Modification 1 to beef\n", git_commit_message(commit)); - - cl_git_pass(git_signature_new(&author, - "Edward Thomson", "ethomson@edwardthomson.com", 1405621769, 0-(4*60))); - cl_assert(git_signature__equal(author, git_commit_author(commit))); - - cl_assert(git_signature__equal(signature, git_commit_committer(commit))); - - /* Make sure the reflogs are updated appropriately */ - cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); - cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); - cl_assert_equal_oid(&parent_id, git_reflog_entry_id_old(reflog_entry)); - cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); - cl_assert_equal_s("rebase: Modification 1 to beef", git_reflog_entry_message(reflog_entry)); - - git_reflog_free(reflog); - git_signature_free(author); - git_commit_free(commit); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_rebase_free(rebase); -} - -void test_rebase_merge__blocked_when_dirty(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - /* Allow untracked files */ - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_mkfile("rebase/untracked_file.txt", "This is untracked\n"); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - /* Do not allow unstaged */ - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_mkfile("rebase/veal.txt", "This is an unstaged change\n"); - cl_git_fail_with(GIT_EUNMERGED, git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_merge__commit_updates_rewritten(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_assert_equal_file( - "da9c51a23d02d931a486f45ad18cda05cf5d2b94 776e4c48922799f903f03f5f6e51da8b01e4cce0\n" - "8d1f13f93c4995760ac07d129246ac1ff64c0be9 ba1f9b4fd5cf8151f7818be2111cc0869f1eb95a\n", - 164, "rebase/.git/rebase-merge/rewritten"); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_merge__commit_drops_already_applied(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id; - int error; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/green_pea")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_fail(error = git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_assert_equal_i(GIT_EAPPLIED, error); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_assert_equal_file( - "8d1f13f93c4995760ac07d129246ac1ff64c0be9 2ac4fb7b74c1287f6c792acad759e1ec01e18dae\n", - 82, "rebase/.git/rebase-merge/rewritten"); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_merge__finish(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref, *head_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id; - git_reflog *reflog; - const git_reflog_entry *reflog_entry; - int error; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); - cl_assert_equal_i(GIT_ITEROVER, error); - - cl_git_pass(git_rebase_finish(rebase, signature)); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_reference_lookup(&head_ref, repo, "HEAD")); - cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head_ref)); - cl_assert_equal_s("refs/heads/gravy", git_reference_symbolic_target(head_ref)); - - /* Make sure the reflogs are updated appropriately */ - cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); - cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); - cl_assert_equal_oid(&commit_id, git_reflog_entry_id_old(reflog_entry)); - cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); - cl_assert_equal_s("rebase finished: returning to refs/heads/gravy", git_reflog_entry_message(reflog_entry)); - git_reflog_free(reflog); - - cl_git_pass(git_reflog_read(&reflog, repo, "refs/heads/gravy")); - cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); - cl_assert_equal_oid(git_annotated_commit_id(branch_head), git_reflog_entry_id_old(reflog_entry)); - cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); - cl_assert_equal_s("rebase finished: refs/heads/gravy onto f87d14a4a236582a0278a916340a793714256864", git_reflog_entry_message(reflog_entry)); - - git_reflog_free(reflog); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(head_ref); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_merge__detached_finish(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref, *head_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id; - git_reflog *reflog; - const git_reflog_entry *reflog_entry; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - int error; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_repository_set_head_detached_from_annotated(repo, branch_head)); - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - git_checkout_head(repo, &opts); - - cl_git_pass(git_rebase_init(&rebase, repo, NULL, upstream_head, NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); - cl_assert_equal_i(GIT_ITEROVER, error); - - cl_git_pass(git_rebase_finish(rebase, signature)); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_reference_lookup(&head_ref, repo, "HEAD")); - cl_assert_equal_i(GIT_REFERENCE_DIRECT, git_reference_type(head_ref)); - - /* Make sure the reflogs are updated appropriately */ - cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); - cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); - cl_assert_equal_oid(git_annotated_commit_id(upstream_head), git_reflog_entry_id_old(reflog_entry)); - cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); - - git_reflog_free(reflog); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(head_ref); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_merge__finish_with_ids(void) -{ - git_rebase *rebase; - git_reference *head_ref; - git_oid branch_id, upstream_id; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id; - git_reflog *reflog; - const git_reflog_entry *reflog_entry; - int error; - - cl_git_pass(git_oid_fromstr(&branch_id, "d616d97082eb7bb2dc6f180a7cca940993b7a56f")); - cl_git_pass(git_oid_fromstr(&upstream_id, "f87d14a4a236582a0278a916340a793714256864")); - - cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); - cl_git_pass(git_annotated_commit_lookup(&upstream_head, repo, &upstream_id)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); - cl_assert_equal_i(GIT_ITEROVER, error); - - cl_git_pass(git_rebase_finish(rebase, signature)); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_reference_lookup(&head_ref, repo, "HEAD")); - cl_assert_equal_i(GIT_REFERENCE_DIRECT, git_reference_type(head_ref)); - cl_assert_equal_oid(&commit_id, git_reference_target(head_ref)); - - /* reflogs are not updated as if we were operating on proper - * branches. check that the last reflog entry is the rebase. - */ - cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); - cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); - cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); - cl_assert_equal_s("rebase: Modification 3 to gravy", git_reflog_entry_message(reflog_entry)); - git_reflog_free(reflog); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(head_ref); - git_rebase_free(rebase); -} - -void test_rebase_merge__no_common_ancestor(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id, expected_final_id; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/barley")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_finish(rebase, signature)); - - git_oid_fromstr(&expected_final_id, "71e7ee8d4fe7d8bf0d107355197e0a953dfdb7f3"); - cl_assert_equal_oid(&expected_final_id, &commit_id); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -static void test_copy_note( - const git_rebase_options *opts, - bool should_exist) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_commit *branch_commit; - git_rebase_operation *rebase_operation; - git_oid note_id, commit_id; - git_note *note = NULL; - int error; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_reference_peel((git_object **)&branch_commit, - branch_ref, GIT_OBJECT_COMMIT)); - - /* Add a note to a commit */ - cl_git_pass(git_note_create(¬e_id, repo, "refs/notes/test", - git_commit_author(branch_commit), git_commit_committer(branch_commit), - git_commit_id(branch_commit), - "This is a commit note.", 0)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, opts)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_finish(rebase, signature)); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - if (should_exist) { - cl_git_pass(git_note_read(¬e, repo, "refs/notes/test", &commit_id)); - cl_assert_equal_s("This is a commit note.", git_note_message(note)); - } else { - cl_git_fail(error = - git_note_read(¬e, repo, "refs/notes/test", &commit_id)); - cl_assert_equal_i(GIT_ENOTFOUND, error); - } - - git_note_free(note); - git_commit_free(branch_commit); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_merge__copy_notes_off_by_default(void) -{ - test_copy_note(NULL, 0); -} - -void test_rebase_merge__copy_notes_specified_in_options(void) -{ - git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; - opts.rewrite_notes_ref = "refs/notes/test"; - - test_copy_note(&opts, 1); -} - -void test_rebase_merge__copy_notes_specified_in_config(void) -{ - git_config *config; - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_string(config, - "notes.rewriteRef", "refs/notes/test")); - - test_copy_note(NULL, 1); -} - -void test_rebase_merge__copy_notes_disabled_in_config(void) -{ - git_config *config; - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_bool(config, "notes.rewrite.rebase", 0)); - cl_git_pass(git_config_set_string(config, - "notes.rewriteRef", "refs/notes/test")); - - test_copy_note(NULL, 0); -} - -static void rebase_checkout_progress_cb( - const char *path, - size_t completed_steps, - size_t total_steps, - void *payload) -{ - int *called = payload; - - GIT_UNUSED(path); - GIT_UNUSED(completed_steps); - GIT_UNUSED(total_steps); - - *called = 1; -} - -void test_rebase_merge__custom_checkout_options(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_options rebase_options = GIT_REBASE_OPTIONS_INIT; - git_checkout_options checkout_options = GIT_CHECKOUT_OPTIONS_INIT; - git_rebase_operation *rebase_operation; - int called = 0; - - checkout_options.progress_cb = rebase_checkout_progress_cb; - checkout_options.progress_payload = &called; - - memcpy(&rebase_options.checkout_options, &checkout_options, - sizeof(git_checkout_options)); - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - called = 0; - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_options)); - cl_assert_equal_i(1, called); - - called = 0; - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_assert_equal_i(1, called); - - called = 0; - cl_git_pass(git_rebase_abort(rebase)); - cl_assert_equal_i(1, called); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_merge__custom_merge_options(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_options rebase_options = GIT_REBASE_OPTIONS_INIT; - git_rebase_operation *rebase_operation; - - rebase_options.merge_options.flags |= - GIT_MERGE_FAIL_ON_CONFLICT | - GIT_MERGE_SKIP_REUC; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_options)); - - cl_git_fail_with(GIT_EMERGECONFLICT, git_rebase_next(&rebase_operation, rebase)); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -void test_rebase_merge__with_directories(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id, tree_id; - git_commit *commit; - - git_oid_fromstr(&tree_id, "a4d6d9c3d57308fd8e320cf2525bae8f1adafa57"); - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/deep_gravy")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, - NULL, NULL)); - - cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); - - cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); - cl_assert_equal_oid(&tree_id, git_commit_tree_id(commit)); - - git_commit_free(commit); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} diff --git a/tests/rebase/setup.c b/tests/rebase/setup.c deleted file mode 100644 index 34d5edbf6..000000000 --- a/tests/rebase/setup.c +++ /dev/null @@ -1,596 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/rebase.h" -#include "posix.h" - -#include - -static git_repository *repo; -static git_index *_index; -static git_signature *signature; - -/* Fixture setup and teardown */ -void test_rebase_setup__initialize(void) -{ - repo = cl_git_sandbox_init("rebase"); - cl_git_pass(git_repository_index(&_index, repo)); - cl_git_pass(git_signature_now(&signature, "Rebaser", "rebaser@rebaser.rb")); -} - -void test_rebase_setup__cleanup(void) -{ - git_signature_free(signature); - git_index_free(_index); - cl_git_sandbox_cleanup(); -} - -/* git checkout beef ; git rebase --merge master - * git checkout beef ; git rebase --merge master */ -void test_rebase_setup__blocked_when_in_progress(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - git_rebase_free(rebase); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - cl_git_fail(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); -} - -/* git checkout beef ; git rebase --merge master */ -void test_rebase_setup__merge(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_reference *head; - git_commit *head_commit; - git_oid head_id; - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); - cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); - - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/ORIG_HEAD"); - - cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/cmt.1"); - cl_assert_equal_file("8d1f13f93c4995760ac07d129246ac1ff64c0be9\n", 41, "rebase/.git/rebase-merge/cmt.2"); - cl_assert_equal_file("3069cc907e6294623e5917ef6de663928c1febfb\n", 41, "rebase/.git/rebase-merge/cmt.3"); - cl_assert_equal_file("588e5d2f04d49707fe4aab865e1deacaf7ef6787\n", 41, "rebase/.git/rebase-merge/cmt.4"); - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/cmt.5"); - cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); - cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); - cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/orig-head"); - - git_commit_free(head_commit); - git_reference_free(head); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -/* git checkout beef && git rebase --merge --root --onto master */ -void test_rebase_setup__merge_root(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *onto_ref; - git_annotated_commit *branch_head, *onto_head; - git_reference *head; - git_commit *head_commit; - git_oid head_id; - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); - - git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); - cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); - - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/ORIG_HEAD"); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/cmt.1"); - cl_assert_equal_file("8d1f13f93c4995760ac07d129246ac1ff64c0be9\n", 41, "rebase/.git/rebase-merge/cmt.2"); - cl_assert_equal_file("3069cc907e6294623e5917ef6de663928c1febfb\n", 41, "rebase/.git/rebase-merge/cmt.3"); - cl_assert_equal_file("588e5d2f04d49707fe4aab865e1deacaf7ef6787\n", 41, "rebase/.git/rebase-merge/cmt.4"); - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/cmt.5"); - cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); - cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); - cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/orig-head"); - - git_commit_free(head_commit); - git_reference_free(head); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(onto_head); - git_reference_free(branch_ref); - git_reference_free(onto_ref); - git_rebase_free(rebase); -} - -/* git checkout gravy && git rebase --merge --onto master veal */ -void test_rebase_setup__merge_onto_and_upstream(void) -{ - git_rebase *rebase; - git_reference *branch1_ref, *branch2_ref, *onto_ref; - git_annotated_commit *branch1_head, *branch2_head, *onto_head; - git_reference *head; - git_commit *head_commit; - git_oid head_id; - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_reference_lookup(&branch1_ref, repo, "refs/heads/gravy")); - cl_git_pass(git_reference_lookup(&branch2_ref, repo, "refs/heads/veal")); - cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch1_head, repo, branch1_ref)); - cl_git_pass(git_annotated_commit_from_ref(&branch2_head, repo, branch2_ref)); - cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch1_head, branch2_head, onto_head, NULL)); - - git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); - cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); - - cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/ORIG_HEAD"); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/rebase-merge/cmt.1"); - cl_assert_equal_file("1\n", 2, "rebase/.git/rebase-merge/end"); - cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); - cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); - cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/rebase-merge/orig-head"); - - git_commit_free(head_commit); - git_reference_free(head); - git_annotated_commit_free(branch1_head); - git_annotated_commit_free(branch2_head); - git_annotated_commit_free(onto_head); - git_reference_free(branch1_ref); - git_reference_free(branch2_ref); - git_reference_free(onto_ref); - git_rebase_free(rebase); -} - -/* git checkout beef && git rebase --merge --onto master gravy veal */ -void test_rebase_setup__merge_onto_upstream_and_branch(void) -{ - git_rebase *rebase; - git_reference *upstream_ref, *branch_ref, *onto_ref; - git_annotated_commit *upstream_head, *branch_head, *onto_head; - git_reference *head; - git_commit *head_commit; - git_oid head_id; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_repository_set_head(repo, "refs/heads/beef")); - cl_git_pass(git_checkout_head(repo, &checkout_opts)); - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/veal")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/gravy")); - cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, onto_head, NULL)); - - git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); - cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); - - cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/ORIG_HEAD"); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - cl_assert_equal_file("3e8989b5a16d5258c935d998ef0e6bb139cc4757\n", 41, "rebase/.git/rebase-merge/cmt.1"); - cl_assert_equal_file("4cacc6f6e740a5bc64faa33e04b8ef0733d8a127\n", 41, "rebase/.git/rebase-merge/cmt.2"); - cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/rebase-merge/cmt.3"); - cl_assert_equal_file("3\n", 2, "rebase/.git/rebase-merge/end"); - cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); - cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); - cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/rebase-merge/orig-head"); - - git_commit_free(head_commit); - git_reference_free(head); - git_annotated_commit_free(upstream_head); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(onto_head); - git_reference_free(upstream_ref); - git_reference_free(branch_ref); - git_reference_free(onto_ref); - git_rebase_free(rebase); -} - -/* git checkout beef && git rebase --merge --onto `git rev-parse master` - * `git rev-parse veal` `git rev-parse gravy` - */ -void test_rebase_setup__merge_onto_upstream_and_branch_by_id(void) -{ - git_rebase *rebase; - git_oid upstream_id, branch_id, onto_id; - git_annotated_commit *upstream_head, *branch_head, *onto_head; - git_reference *head; - git_commit *head_commit; - git_oid head_id; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_repository_set_head(repo, "refs/heads/beef")); - cl_git_pass(git_checkout_head(repo, &checkout_opts)); - - cl_git_pass(git_oid_fromstr(&upstream_id, "f87d14a4a236582a0278a916340a793714256864")); - cl_git_pass(git_oid_fromstr(&branch_id, "d616d97082eb7bb2dc6f180a7cca940993b7a56f")); - cl_git_pass(git_oid_fromstr(&onto_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); - - cl_git_pass(git_annotated_commit_lookup(&upstream_head, repo, &upstream_id)); - cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); - cl_git_pass(git_annotated_commit_lookup(&onto_head, repo, &onto_id)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, onto_head, NULL)); - - git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); - cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); - - cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/ORIG_HEAD"); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/rebase-merge/cmt.1"); - cl_assert_equal_file("1\n", 2, "rebase/.git/rebase-merge/end"); - cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); - cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto_name"); - cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/rebase-merge/orig-head"); - - git_commit_free(head_commit); - git_reference_free(head); - git_annotated_commit_free(upstream_head); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(onto_head); - git_rebase_free(rebase); -} - -/* Ensure merge commits are dropped in a rebase */ -/* git checkout veal && git rebase --merge master */ -void test_rebase_setup__branch_with_merges(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_reference *head; - git_commit *head_commit; - git_oid head_id; - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/veal")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); - cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); - - cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/ORIG_HEAD"); - - cl_assert_equal_file("4bed71df7017283cac61bbf726197ad6a5a18b84\n", 41, "rebase/.git/rebase-merge/cmt.1"); - cl_assert_equal_file("2aa3ce842094e08ebac152b3d6d5b0fff39f9c6e\n", 41, "rebase/.git/rebase-merge/cmt.2"); - cl_assert_equal_file("3e8989b5a16d5258c935d998ef0e6bb139cc4757\n", 41, "rebase/.git/rebase-merge/cmt.3"); - cl_assert_equal_file("4cacc6f6e740a5bc64faa33e04b8ef0733d8a127\n", 41, "rebase/.git/rebase-merge/cmt.4"); - cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/rebase-merge/cmt.5"); - cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); - cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); - cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); - cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/rebase-merge/orig-head"); - - git_commit_free(head_commit); - git_reference_free(head); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -/* git checkout barley && git rebase --merge master */ -void test_rebase_setup__orphan_branch(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_reference *head; - git_commit *head_commit; - git_oid head_id; - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/barley")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); - cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); - - cl_assert_equal_file("12c084412b952396962eb420716df01022b847cc\n", 41, "rebase/.git/ORIG_HEAD"); - - cl_assert_equal_file("aa4c42aecdfc7cd989bbc3209934ea7cda3f4d88\n", 41, "rebase/.git/rebase-merge/cmt.1"); - cl_assert_equal_file("e4f809f826c1a9fc929874bc0e4644dd2f2a1af4\n", 41, "rebase/.git/rebase-merge/cmt.2"); - cl_assert_equal_file("9539b2cc291d6a6b1b266df8474d31fdd344dd79\n", 41, "rebase/.git/rebase-merge/cmt.3"); - cl_assert_equal_file("013cc32d341bab0e6f039f50f153c18986f16c58\n", 41, "rebase/.git/rebase-merge/cmt.4"); - cl_assert_equal_file("12c084412b952396962eb420716df01022b847cc\n", 41, "rebase/.git/rebase-merge/cmt.5"); - cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); - cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); - cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); - cl_assert_equal_file("12c084412b952396962eb420716df01022b847cc\n", 41, "rebase/.git/rebase-merge/orig-head"); - - git_commit_free(head_commit); - git_reference_free(head); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -/* git checkout beef && git rebase --merge master */ -void test_rebase_setup__merge_null_branch_uses_HEAD(void) -{ - git_rebase *rebase; - git_reference *upstream_ref; - git_annotated_commit *upstream_head; - git_reference *head; - git_commit *head_commit; - git_oid head_id; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_repository_set_head(repo, "refs/heads/beef")); - cl_git_pass(git_checkout_head(repo, &checkout_opts)); - - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, NULL, upstream_head, NULL, NULL)); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); - cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); - - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/ORIG_HEAD"); - - cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/cmt.1"); - cl_assert_equal_file("8d1f13f93c4995760ac07d129246ac1ff64c0be9\n", 41, "rebase/.git/rebase-merge/cmt.2"); - cl_assert_equal_file("3069cc907e6294623e5917ef6de663928c1febfb\n", 41, "rebase/.git/rebase-merge/cmt.3"); - cl_assert_equal_file("588e5d2f04d49707fe4aab865e1deacaf7ef6787\n", 41, "rebase/.git/rebase-merge/cmt.4"); - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/cmt.5"); - cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); - cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); - cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/orig-head"); - - git_commit_free(head_commit); - git_reference_free(head); - git_annotated_commit_free(upstream_head); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -/* git checkout b146bd7608eac53d9bf9e1a6963543588b555c64 && git rebase --merge master */ -void test_rebase_setup__merge_from_detached(void) -{ - git_rebase *rebase; - git_reference *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_reference *head; - git_commit *head_commit; - git_oid branch_id, head_id; - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64")); - - cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); - cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); - - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/ORIG_HEAD"); - - cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/cmt.1"); - cl_assert_equal_file("8d1f13f93c4995760ac07d129246ac1ff64c0be9\n", 41, "rebase/.git/rebase-merge/cmt.2"); - cl_assert_equal_file("3069cc907e6294623e5917ef6de663928c1febfb\n", 41, "rebase/.git/rebase-merge/cmt.3"); - cl_assert_equal_file("588e5d2f04d49707fe4aab865e1deacaf7ef6787\n", 41, "rebase/.git/rebase-merge/cmt.4"); - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/cmt.5"); - cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); - cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); - cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/orig-head"); - - git_commit_free(head_commit); - git_reference_free(head); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} - -/* git checkout beef && git rebase --merge efad0b11c47cb2f0220cbd6f5b0f93bb99064b00 */ -void test_rebase_setup__merge_branch_by_id(void) -{ - git_rebase *rebase; - git_reference *branch_ref; - git_annotated_commit *branch_head, *upstream_head; - git_reference *head; - git_commit *head_commit; - git_oid head_id, upstream_id; - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - - cl_git_pass(git_oid_fromstr(&upstream_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_lookup(&upstream_head, repo, &upstream_id)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); - - git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); - cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); - - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/ORIG_HEAD"); - - cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/cmt.1"); - cl_assert_equal_file("8d1f13f93c4995760ac07d129246ac1ff64c0be9\n", 41, "rebase/.git/rebase-merge/cmt.2"); - cl_assert_equal_file("3069cc907e6294623e5917ef6de663928c1febfb\n", 41, "rebase/.git/rebase-merge/cmt.3"); - cl_assert_equal_file("588e5d2f04d49707fe4aab865e1deacaf7ef6787\n", 41, "rebase/.git/rebase-merge/cmt.4"); - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/cmt.5"); - cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); - cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); - cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto_name"); - cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/orig-head"); - - git_commit_free(head_commit); - git_reference_free(head); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_rebase_free(rebase); -} - -static int rebase_is_blocked(void) -{ - git_rebase *rebase = NULL; - int error; - - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - error = git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL); - - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); - - return error; -} - -void test_rebase_setup__blocked_for_staged_change(void) -{ - cl_git_rewritefile("rebase/newfile.txt", "Stage an add"); - git_index_add_bypath(_index, "newfile.txt"); - cl_git_fail(rebase_is_blocked()); -} - -void test_rebase_setup__blocked_for_unstaged_change(void) -{ - cl_git_rewritefile("rebase/asparagus.txt", "Unstaged change"); - cl_git_fail(rebase_is_blocked()); -} - -void test_rebase_setup__not_blocked_for_untracked_add(void) -{ - cl_git_rewritefile("rebase/newfile.txt", "Untracked file"); - cl_git_pass(rebase_is_blocked()); -} - diff --git a/tests/rebase/sign.c b/tests/rebase/sign.c deleted file mode 100644 index 4064cf79b..000000000 --- a/tests/rebase/sign.c +++ /dev/null @@ -1,491 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/rebase.h" - -static git_repository *repo; -static git_signature *signature; - -/* Fixture setup and teardown */ -void test_rebase_sign__initialize(void) -{ - repo = cl_git_sandbox_init("rebase"); - cl_git_pass(git_signature_new(&signature, "Rebaser", - "rebaser@rebaser.rb", 1405694510, 0)); -} - -void test_rebase_sign__cleanup(void) -{ - git_signature_free(signature); - cl_git_sandbox_cleanup(); -} - -static int create_cb_passthrough( - git_oid *out, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree, - size_t parent_count, - const git_commit *parents[], - void *payload) -{ - GIT_UNUSED(out); - GIT_UNUSED(author); - GIT_UNUSED(committer); - GIT_UNUSED(message_encoding); - GIT_UNUSED(message); - GIT_UNUSED(tree); - GIT_UNUSED(parent_count); - GIT_UNUSED(parents); - GIT_UNUSED(payload); - - return GIT_PASSTHROUGH; -} - -/* git checkout gravy ; git rebase --merge veal */ -void test_rebase_sign__passthrough_create_cb(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id, expected_id; - git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; - git_commit *commit; - const char *expected_commit_raw_header = "tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ -parent f87d14a4a236582a0278a916340a793714256864\n\ -author Edward Thomson 1405625055 -0400\n\ -committer Rebaser 1405694510 +0000\n"; - - rebase_opts.commit_create_cb = create_cb_passthrough; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - - git_oid_fromstr(&expected_id, "129183968a65abd6c52da35bff43325001bfc630"); - cl_assert_equal_oid(&expected_id, &commit_id); - - cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); - cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit)); - - cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); - - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_commit_free(commit); - git_rebase_free(rebase); -} - -static int create_cb_signed_gpg( - git_oid *out, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree, - size_t parent_count, - const git_commit *parents[], - void *payload) -{ - git_buf commit_content = GIT_BUF_INIT; - const char *gpg_signature = "-----BEGIN PGP SIGNATURE-----\n\ -\n\ -iQIzBAEBCgAdFiEEgVlDEfSlmKn0fvGgK++h5T2/ctIFAlwZcrAACgkQK++h5T2/\n\ -ctIPVhAA42RyZhMdKl5Bm0KtQco2scsukIg2y7tjSwhti91zDu3HQgpusjjo0fQx\n\ -ZzB+OrmlvQ9CDcGpZ0THIzXD8GRJoDMPqdrvZVrBWkGcHvw7/YPA8skzsjkauJ8W\n\ -7lzF5LCuHSS6OUmPT/+5hEHPin5PB3zhfszyC+Q7aujnIuPJMrKiMnUa+w1HWifM\n\ -km49OOygQ9S6NQoVuEQede22+c76DlDL7yFghGoo1f0sKCE/9LW6SEnwI/bWv9eo\n\ -nom5vOPrvQeJiYCQk+2DyWo8RdSxINtY+G9bPE4RXm+6ZgcXECPm9TYDIWpL36fC\n\ -jvtGLs98woWFElOziBMp5Tb630GMcSI+q5ivHfJ3WS5NKLYLHBNK4iSFN0/dgAnB\n\ -dj6GcKXKWnIBWn6ZM4o40pcM5KSRUUCLtA0ZmjJH4c4zx3X5fUxd+enwkf3e9VZO\n\ -fNKC/+xfq6NfoPUPK9+UnchHpJaJw7RG5tZS+sWCz2xpQ1y3/o49xImNyM3wnpvB\n\ -cRAZabqIHpZa9/DIUkELOtCzln6niqkjRgg3M/YCCNznwV+0RNgz87VtyTPerdef\n\ -xrqn0+ROMF6ebVqIs6PPtuPkxnAJu7TMKXVB5rFnAewS24e6cIGFzeIA7810py3l\n\ -cttVRsdOoego+fiy08eFE+aJIeYiINRGhqOBTsuqG4jIdpdKxPE=\n\ -=KbsY\n\ ------END PGP SIGNATURE-----"; - - git_repository *repo = (git_repository *)payload; - int error; - - if ((error = git_commit_create_buffer(&commit_content, - repo, author, committer, message_encoding, message, - tree, parent_count, parents)) < 0) - goto done; - - error = git_commit_create_with_signature(out, repo, - commit_content.ptr, - gpg_signature, - NULL); - -done: - git_buf_dispose(&commit_content); - return error; -} - -/* git checkout gravy ; git rebase --merge veal */ -void test_rebase_sign__create_gpg_signed(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id, expected_id; - git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; - git_commit *commit; - const char *expected_commit_raw_header = "tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ -parent f87d14a4a236582a0278a916340a793714256864\n\ -author Edward Thomson 1405625055 -0400\n\ -committer Rebaser 1405694510 +0000\n\ -gpgsig -----BEGIN PGP SIGNATURE-----\n\ - \n\ - iQIzBAEBCgAdFiEEgVlDEfSlmKn0fvGgK++h5T2/ctIFAlwZcrAACgkQK++h5T2/\n\ - ctIPVhAA42RyZhMdKl5Bm0KtQco2scsukIg2y7tjSwhti91zDu3HQgpusjjo0fQx\n\ - ZzB+OrmlvQ9CDcGpZ0THIzXD8GRJoDMPqdrvZVrBWkGcHvw7/YPA8skzsjkauJ8W\n\ - 7lzF5LCuHSS6OUmPT/+5hEHPin5PB3zhfszyC+Q7aujnIuPJMrKiMnUa+w1HWifM\n\ - km49OOygQ9S6NQoVuEQede22+c76DlDL7yFghGoo1f0sKCE/9LW6SEnwI/bWv9eo\n\ - nom5vOPrvQeJiYCQk+2DyWo8RdSxINtY+G9bPE4RXm+6ZgcXECPm9TYDIWpL36fC\n\ - jvtGLs98woWFElOziBMp5Tb630GMcSI+q5ivHfJ3WS5NKLYLHBNK4iSFN0/dgAnB\n\ - dj6GcKXKWnIBWn6ZM4o40pcM5KSRUUCLtA0ZmjJH4c4zx3X5fUxd+enwkf3e9VZO\n\ - fNKC/+xfq6NfoPUPK9+UnchHpJaJw7RG5tZS+sWCz2xpQ1y3/o49xImNyM3wnpvB\n\ - cRAZabqIHpZa9/DIUkELOtCzln6niqkjRgg3M/YCCNznwV+0RNgz87VtyTPerdef\n\ - xrqn0+ROMF6ebVqIs6PPtuPkxnAJu7TMKXVB5rFnAewS24e6cIGFzeIA7810py3l\n\ - cttVRsdOoego+fiy08eFE+aJIeYiINRGhqOBTsuqG4jIdpdKxPE=\n\ - =KbsY\n\ - -----END PGP SIGNATURE-----\n"; - - rebase_opts.commit_create_cb = create_cb_signed_gpg; - rebase_opts.payload = repo; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - - git_oid_fromstr(&expected_id, "bf78348e45c8286f52b760f1db15cb6da030f2ef"); - cl_assert_equal_oid(&expected_id, &commit_id); - - cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); - cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit)); - - cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); - - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_commit_free(commit); - git_rebase_free(rebase); -} - -static int create_cb_error( - git_oid *out, - const git_signature *author, - const git_signature *committer, - const char *message_encoding, - const char *message, - const git_tree *tree, - size_t parent_count, - const git_commit *parents[], - void *payload) -{ - GIT_UNUSED(out); - GIT_UNUSED(author); - GIT_UNUSED(committer); - GIT_UNUSED(message_encoding); - GIT_UNUSED(message); - GIT_UNUSED(tree); - GIT_UNUSED(parent_count); - GIT_UNUSED(parents); - GIT_UNUSED(payload); - - return GIT_EUSER; -} - -/* git checkout gravy ; git rebase --merge veal */ -void test_rebase_sign__create_propagates_error(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_oid commit_id; - git_rebase_operation *rebase_operation; - git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; - - rebase_opts.commit_create_cb = create_cb_error; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_fail_with(GIT_EUSER, git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_rebase_free(rebase); -} - -#ifndef GIT_DEPRECATE_HARD -static const char *expected_commit_content = "\ -tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ -parent f87d14a4a236582a0278a916340a793714256864\n\ -author Edward Thomson 1405625055 -0400\n\ -committer Rebaser 1405694510 +0000\n\ -\n\ -Modification 3 to gravy\n"; - -int signing_cb_passthrough( - git_buf *signature, - git_buf *signature_field, - const char *commit_content, - void *payload) -{ - cl_assert_equal_i(0, signature->size); - cl_assert_equal_i(0, signature_field->size); - cl_assert_equal_s(expected_commit_content, commit_content); - cl_assert_equal_p(NULL, payload); - return GIT_PASSTHROUGH; -} -#endif /* !GIT_DEPRECATE_HARD */ - -/* git checkout gravy ; git rebase --merge veal */ -void test_rebase_sign__passthrough_signing_cb(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id, expected_id; - git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; - git_commit *commit; - const char *expected_commit_raw_header = "tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ -parent f87d14a4a236582a0278a916340a793714256864\n\ -author Edward Thomson 1405625055 -0400\n\ -committer Rebaser 1405694510 +0000\n"; - - rebase_opts.signing_cb = signing_cb_passthrough; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - - git_oid_fromstr(&expected_id, "129183968a65abd6c52da35bff43325001bfc630"); - cl_assert_equal_oid(&expected_id, &commit_id); - - cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); - cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit)); - - cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); - - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_commit_free(commit); - git_rebase_free(rebase); -#endif /* !GIT_DEPRECATE_HARD */ -} - -#ifndef GIT_DEPRECATE_HARD -int signing_cb_gpg( - git_buf *signature, - git_buf *signature_field, - const char *commit_content, - void *payload) -{ - const char *gpg_signature = "\ ------BEGIN PGP SIGNATURE-----\n\ -\n\ -iQIzBAEBCgAdFiEEgVlDEfSlmKn0fvGgK++h5T2/ctIFAlwZcrAACgkQK++h5T2/\n\ -ctIPVhAA42RyZhMdKl5Bm0KtQco2scsukIg2y7tjSwhti91zDu3HQgpusjjo0fQx\n\ -ZzB+OrmlvQ9CDcGpZ0THIzXD8GRJoDMPqdrvZVrBWkGcHvw7/YPA8skzsjkauJ8W\n\ -7lzF5LCuHSS6OUmPT/+5hEHPin5PB3zhfszyC+Q7aujnIuPJMrKiMnUa+w1HWifM\n\ -km49OOygQ9S6NQoVuEQede22+c76DlDL7yFghGoo1f0sKCE/9LW6SEnwI/bWv9eo\n\ -nom5vOPrvQeJiYCQk+2DyWo8RdSxINtY+G9bPE4RXm+6ZgcXECPm9TYDIWpL36fC\n\ -jvtGLs98woWFElOziBMp5Tb630GMcSI+q5ivHfJ3WS5NKLYLHBNK4iSFN0/dgAnB\n\ -dj6GcKXKWnIBWn6ZM4o40pcM5KSRUUCLtA0ZmjJH4c4zx3X5fUxd+enwkf3e9VZO\n\ -fNKC/+xfq6NfoPUPK9+UnchHpJaJw7RG5tZS+sWCz2xpQ1y3/o49xImNyM3wnpvB\n\ -cRAZabqIHpZa9/DIUkELOtCzln6niqkjRgg3M/YCCNznwV+0RNgz87VtyTPerdef\n\ -xrqn0+ROMF6ebVqIs6PPtuPkxnAJu7TMKXVB5rFnAewS24e6cIGFzeIA7810py3l\n\ -cttVRsdOoego+fiy08eFE+aJIeYiINRGhqOBTsuqG4jIdpdKxPE=\n\ -=KbsY\n\ ------END PGP SIGNATURE-----"; - - cl_assert_equal_i(0, signature->size); - cl_assert_equal_i(0, signature_field->size); - cl_assert_equal_s(expected_commit_content, commit_content); - cl_assert_equal_p(NULL, payload); - - cl_git_pass(git_buf_set(signature, gpg_signature, strlen(gpg_signature) + 1)); - return GIT_OK; -} -#endif /* !GIT_DEPRECATE_HARD */ - -/* git checkout gravy ; git rebase --merge veal */ -void test_rebase_sign__gpg_with_no_field(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id, expected_id; - git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; - git_commit *commit; - const char *expected_commit_raw_header = "\ -tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ -parent f87d14a4a236582a0278a916340a793714256864\n\ -author Edward Thomson 1405625055 -0400\n\ -committer Rebaser 1405694510 +0000\n\ -gpgsig -----BEGIN PGP SIGNATURE-----\n\ - \n\ - iQIzBAEBCgAdFiEEgVlDEfSlmKn0fvGgK++h5T2/ctIFAlwZcrAACgkQK++h5T2/\n\ - ctIPVhAA42RyZhMdKl5Bm0KtQco2scsukIg2y7tjSwhti91zDu3HQgpusjjo0fQx\n\ - ZzB+OrmlvQ9CDcGpZ0THIzXD8GRJoDMPqdrvZVrBWkGcHvw7/YPA8skzsjkauJ8W\n\ - 7lzF5LCuHSS6OUmPT/+5hEHPin5PB3zhfszyC+Q7aujnIuPJMrKiMnUa+w1HWifM\n\ - km49OOygQ9S6NQoVuEQede22+c76DlDL7yFghGoo1f0sKCE/9LW6SEnwI/bWv9eo\n\ - nom5vOPrvQeJiYCQk+2DyWo8RdSxINtY+G9bPE4RXm+6ZgcXECPm9TYDIWpL36fC\n\ - jvtGLs98woWFElOziBMp5Tb630GMcSI+q5ivHfJ3WS5NKLYLHBNK4iSFN0/dgAnB\n\ - dj6GcKXKWnIBWn6ZM4o40pcM5KSRUUCLtA0ZmjJH4c4zx3X5fUxd+enwkf3e9VZO\n\ - fNKC/+xfq6NfoPUPK9+UnchHpJaJw7RG5tZS+sWCz2xpQ1y3/o49xImNyM3wnpvB\n\ - cRAZabqIHpZa9/DIUkELOtCzln6niqkjRgg3M/YCCNznwV+0RNgz87VtyTPerdef\n\ - xrqn0+ROMF6ebVqIs6PPtuPkxnAJu7TMKXVB5rFnAewS24e6cIGFzeIA7810py3l\n\ - cttVRsdOoego+fiy08eFE+aJIeYiINRGhqOBTsuqG4jIdpdKxPE=\n\ - =KbsY\n\ - -----END PGP SIGNATURE-----\n"; - - rebase_opts.signing_cb = signing_cb_gpg; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - - git_oid_fromstr(&expected_id, "bf78348e45c8286f52b760f1db15cb6da030f2ef"); - cl_assert_equal_oid(&expected_id, &commit_id); - - cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); - cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit)); - - cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); - - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_commit_free(commit); - git_rebase_free(rebase); -#endif /* !GIT_DEPRECATE_HARD */ -} - - -#ifndef GIT_DEPRECATE_HARD -int signing_cb_magic_field( - git_buf *signature, - git_buf *signature_field, - const char *commit_content, - void *payload) -{ - const char *signature_content = "magic word: pretty please"; - const char *signature_field_content = "magicsig"; - - cl_assert_equal_p(NULL, signature->ptr); - cl_assert_equal_i(0, signature->size); - cl_assert_equal_p(NULL, signature_field->ptr); - cl_assert_equal_i(0, signature_field->size); - cl_assert_equal_s(expected_commit_content, commit_content); - cl_assert_equal_p(NULL, payload); - - cl_git_pass(git_buf_set(signature, signature_content, - strlen(signature_content) + 1)); - cl_git_pass(git_buf_set(signature_field, signature_field_content, - strlen(signature_field_content) + 1)); - - return GIT_OK; -} -#endif /* !GIT_DEPRECATE_HARD */ - -/* git checkout gravy ; git rebase --merge veal */ -void test_rebase_sign__custom_signature_field(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_rebase_operation *rebase_operation; - git_oid commit_id, expected_id; - git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; - git_commit *commit; - const char *expected_commit_raw_header = "\ -tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ -parent f87d14a4a236582a0278a916340a793714256864\n\ -author Edward Thomson 1405625055 -0400\n\ -committer Rebaser 1405694510 +0000\n\ -magicsig magic word: pretty please\n"; - - rebase_opts.signing_cb = signing_cb_magic_field; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); - - cl_git_pass(git_rebase_next(&rebase_operation, rebase)); - cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); - - git_oid_fromstr(&expected_id, "f46a4a8d26ae411b02aa61b7d69576627f4a1e1c"); - cl_assert_equal_oid(&expected_id, &commit_id); - - cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); - cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit)); - - cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); - - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_commit_free(commit); - git_rebase_free(rebase); -#endif /* !GIT_DEPRECATE_HARD */ -} diff --git a/tests/rebase/submodule.c b/tests/rebase/submodule.c deleted file mode 100644 index 0b3c2d5b5..000000000 --- a/tests/rebase/submodule.c +++ /dev/null @@ -1,95 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/checkout.h" -#include "git2/rebase.h" -#include "posix.h" -#include "signature.h" -#include "../submodule/submodule_helpers.h" - -#include - -static git_repository *repo; -static git_signature *signature; - -/* Fixture setup and teardown */ -void test_rebase_submodule__initialize(void) -{ - git_index *index; - git_oid tree_oid, commit_id; - git_tree *tree; - git_commit *parent; - git_object *obj; - git_reference *master_ref; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - repo = cl_git_sandbox_init("rebase-submodule"); - cl_git_pass(git_signature_new(&signature, - "Rebaser", "rebaser@rebaser.rb", 1405694510, 0)); - - rewrite_gitmodules(git_repository_workdir(repo)); - - cl_git_pass(git_submodule_set_url(repo, "my-submodule", git_repository_path(repo))); - - /* We have to commit the rewritten .gitmodules file */ - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, ".gitmodules")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_index_write_tree(&tree_oid, index)); - cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); - - cl_git_pass(git_repository_head(&master_ref, repo)); - cl_git_pass(git_commit_lookup(&parent, repo, git_reference_target(master_ref))); - - cl_git_pass(git_commit_create_v(&commit_id, repo, git_reference_name(master_ref), signature, signature, NULL, "Fixup .gitmodules", tree, 1, parent)); - - /* And a final reset, for good measure */ - cl_git_pass(git_object_lookup(&obj, repo, &commit_id, GIT_OBJECT_COMMIT)); - cl_git_pass(git_reset(repo, obj, GIT_RESET_HARD, &opts)); - - git_index_free(index); - git_object_free(obj); - git_commit_free(parent); - git_reference_free(master_ref); - git_tree_free(tree); -} - -void test_rebase_submodule__cleanup(void) -{ - git_signature_free(signature); - cl_git_sandbox_cleanup(); -} - -void test_rebase_submodule__init_untracked(void) -{ - git_rebase *rebase; - git_reference *branch_ref, *upstream_ref; - git_annotated_commit *branch_head, *upstream_head; - git_str untracked_path = GIT_STR_INIT; - FILE *fp; - git_submodule *submodule; - - cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus")); - cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); - - cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); - cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); - - cl_git_pass(git_submodule_lookup(&submodule, repo, "my-submodule")); - cl_git_pass(git_submodule_update(submodule, 1, NULL)); - - git_str_printf(&untracked_path, "%s/my-submodule/untracked", git_repository_workdir(repo)); - fp = fopen(git_str_cstr(&untracked_path), "w"); - fprintf(fp, "An untracked file in a submodule should not block a rebase\n"); - fclose(fp); - git_str_dispose(&untracked_path); - - cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); - - git_submodule_free(submodule); - git_annotated_commit_free(branch_head); - git_annotated_commit_free(upstream_head); - git_reference_free(branch_ref); - git_reference_free(upstream_ref); - git_rebase_free(rebase); -} diff --git a/tests/refs/basic.c b/tests/refs/basic.c deleted file mode 100644 index 32742f9cc..000000000 --- a/tests/refs/basic.c +++ /dev/null @@ -1,85 +0,0 @@ -#include "clar_libgit2.h" - -#include "futils.h" -#include "refs.h" -#include "ref_helpers.h" - -static git_repository *g_repo; - -static const char *loose_tag_ref_name = "refs/tags/e90810b"; - -void test_refs_basic__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); - cl_git_pass(git_repository_set_ident(g_repo, "me", "foo@example.com")); -} - -void test_refs_basic__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_refs_basic__reference_realloc(void) -{ - git_reference *ref; - git_reference *new_ref; - const char *new_name = "refs/tags/awful/name-which-is/clearly/really-that-much/longer-than/the-old-one"; - - /* Retrieval of the reference to rename */ - cl_git_pass(git_reference_lookup(&ref, g_repo, loose_tag_ref_name)); - - new_ref = git_reference__realloc(&ref, new_name); - cl_assert(new_ref != NULL); - git_reference_free(new_ref); - git_reference_free(ref); - - /* Reload, so we restore the value */ - cl_git_pass(git_reference_lookup(&ref, g_repo, loose_tag_ref_name)); - - cl_git_pass(git_reference_rename(&new_ref, ref, new_name, 1, "log message")); - cl_assert(ref != NULL); - cl_assert(new_ref != NULL); - git_reference_free(new_ref); - git_reference_free(ref); -} - -void test_refs_basic__longpaths(void) -{ -#ifdef GIT_WIN32 - const char *base; - size_t base_len, extra_len; - ssize_t remain_len, i; - git_str refname = GIT_STR_INIT; - git_reference *one = NULL, *two = NULL; - git_oid id; - - cl_git_pass(git_oid_fromstr(&id, "099fabac3a9ea935598528c27f866e34089c2eff")); - - base = git_repository_path(g_repo); - base_len = git_utf8_char_length(base, strlen(base)); - extra_len = CONST_STRLEN("logs/refs/heads/") + CONST_STRLEN(".lock"); - - remain_len = (ssize_t)MAX_PATH - (base_len + extra_len); - cl_assert(remain_len > 0); - - cl_git_pass(git_str_puts(&refname, "refs/heads/")); - - for (i = 0; i < remain_len; i++) { - cl_git_pass(git_str_putc(&refname, 'a')); - } - - /* - * The full path to the reflog lockfile is 260 characters, - * this is permitted. - */ - cl_git_pass(git_reference_create(&one, g_repo, refname.ptr, &id, 0, NULL)); - - /* Adding one more character gives us a path that is too long. */ - cl_git_pass(git_str_putc(&refname, 'z')); - cl_git_fail(git_reference_create(&two, g_repo, refname.ptr, &id, 0, NULL)); - - git_reference_free(one); - git_reference_free(two); - git_str_dispose(&refname); -#endif -} diff --git a/tests/refs/branches/checkedout.c b/tests/refs/branches/checkedout.c deleted file mode 100644 index d6dab2c0e..000000000 --- a/tests/refs/branches/checkedout.c +++ /dev/null @@ -1,53 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" -#include "worktree/worktree_helpers.h" - -static git_repository *repo; - -static void assert_checked_out(git_repository *repo, const char *branch, int checked_out) -{ - git_reference *ref; - - cl_git_pass(git_reference_lookup(&ref, repo, branch)); - cl_assert(git_branch_is_checked_out(ref) == checked_out); - - git_reference_free(ref); -} - -void test_refs_branches_checkedout__simple_repo(void) -{ - repo = cl_git_sandbox_init("testrepo"); - assert_checked_out(repo, "refs/heads/master", 1); - assert_checked_out(repo, "refs/heads/executable", 0); - cl_git_sandbox_cleanup(); -} - -void test_refs_branches_checkedout__worktree(void) -{ - static worktree_fixture fixture = - WORKTREE_FIXTURE_INIT("testrepo", "testrepo-worktree"); - - setup_fixture_worktree(&fixture); - - assert_checked_out(fixture.repo, "refs/heads/master", 1); - assert_checked_out(fixture.repo, "refs/heads/testrepo-worktree", 1); - - assert_checked_out(fixture.worktree, "refs/heads/master", 1); - assert_checked_out(fixture.worktree, "refs/heads/testrepo-worktree", 1); - - cleanup_fixture_worktree(&fixture); -} - -void test_refs_branches_checkedout__head_is_not_checked_out(void) -{ - repo = cl_git_sandbox_init("testrepo"); - assert_checked_out(repo, "HEAD", 0); - cl_git_sandbox_cleanup(); -} - -void test_refs_branches_checkedout__master_in_bare_repo_is_not_checked_out(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); - assert_checked_out(repo, "refs/heads/master", 0); - cl_git_sandbox_cleanup(); -} diff --git a/tests/refs/branches/create.c b/tests/refs/branches/create.c deleted file mode 100644 index 2fb11668b..000000000 --- a/tests/refs/branches/create.c +++ /dev/null @@ -1,279 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" -#include "path.h" - -static git_repository *repo; -static git_commit *target; -static git_reference *branch; - -void test_refs_branches_create__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); - branch = NULL; - target = NULL; -} - -void test_refs_branches_create__cleanup(void) -{ - git_reference_free(branch); - branch = NULL; - - git_commit_free(target); - target = NULL; - - cl_git_sandbox_cleanup(); - repo = NULL; -} - -static void retrieve_target_from_oid(git_commit **out, git_repository *repo, const char *sha) -{ - git_object *obj; - - cl_git_pass(git_revparse_single(&obj, repo, sha)); - cl_git_pass(git_commit_lookup(out, repo, git_object_id(obj))); - git_object_free(obj); -} - -static void retrieve_known_commit(git_commit **commit, git_repository *repo) -{ - retrieve_target_from_oid(commit, repo, "e90810b8df3"); -} - -#define NEW_BRANCH_NAME "new-branch-on-the-block" - -void test_refs_branches_create__can_create_a_local_branch(void) -{ - retrieve_known_commit(&target, repo); - - cl_git_pass(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0)); - cl_git_pass(git_oid_cmp(git_reference_target(branch), git_commit_id(target))); -} - -void test_refs_branches_create__can_not_create_a_branch_if_its_name_collide_with_an_existing_one(void) -{ - retrieve_known_commit(&target, repo); - - cl_assert_equal_i(GIT_EEXISTS, git_branch_create(&branch, repo, "br2", target, 0)); -} - -void test_refs_branches_create__can_force_create_over_an_existing_branch(void) -{ - retrieve_known_commit(&target, repo); - - cl_git_pass(git_branch_create(&branch, repo, "br2", target, 1)); - cl_git_pass(git_oid_cmp(git_reference_target(branch), git_commit_id(target))); - cl_assert_equal_s("refs/heads/br2", git_reference_name(branch)); -} - -void test_refs_branches_create__cannot_force_create_over_current_branch_in_nonbare_repo(void) -{ - const git_oid *oid; - git_reference *branch2; - - /* Default repo for these tests is a bare repo, but this test requires a non-bare one */ - cl_git_sandbox_cleanup(); - repo = cl_git_sandbox_init("testrepo"); - retrieve_known_commit(&target, repo); - - cl_git_pass(git_branch_lookup(&branch2, repo, "master", GIT_BRANCH_LOCAL)); - cl_assert_equal_s("refs/heads/master", git_reference_name(branch2)); - cl_assert_equal_i(true, git_branch_is_head(branch2)); - oid = git_reference_target(branch2); - - cl_git_fail_with(-1, git_branch_create(&branch, repo, "master", target, 1)); - branch = NULL; - cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL)); - cl_assert_equal_s("refs/heads/master", git_reference_name(branch)); - cl_git_pass(git_oid_cmp(git_reference_target(branch), oid)); - git_reference_free(branch2); -} - -void test_refs_branches_create__can_force_create_over_current_branch_in_bare_repo(void) -{ - const git_oid *oid; - git_reference *branch2; - retrieve_known_commit(&target, repo); - - cl_git_pass(git_branch_lookup(&branch2, repo, "master", GIT_BRANCH_LOCAL)); - cl_assert_equal_s("refs/heads/master", git_reference_name(branch2)); - cl_assert_equal_i(true, git_branch_is_head(branch2)); - oid = git_commit_id(target); - - cl_git_pass(git_branch_create(&branch, repo, "master", target, 1)); - git_reference_free(branch); - branch = NULL; - cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL)); - cl_assert_equal_s("refs/heads/master", git_reference_name(branch)); - cl_git_pass(git_oid_cmp(git_reference_target(branch), oid)); - git_reference_free(branch2); -} - -void test_refs_branches_create__creating_a_branch_with_an_invalid_name_returns_EINVALIDSPEC(void) -{ - retrieve_known_commit(&target, repo); - - cl_assert_equal_i(GIT_EINVALIDSPEC, - git_branch_create(&branch, repo, "inv@{id", target, 0)); -} - -static void assert_branch_matches_name( - const char *expected, const char *lookup_as) -{ - git_reference *ref; - git_str b = GIT_STR_INIT; - - cl_git_pass(git_branch_lookup(&ref, repo, lookup_as, GIT_BRANCH_LOCAL)); - - cl_git_pass(git_str_sets(&b, "refs/heads/")); - cl_git_pass(git_str_puts(&b, expected)); - cl_assert_equal_s(b.ptr, git_reference_name(ref)); - - cl_git_pass( - git_oid_cmp(git_reference_target(ref), git_commit_id(target))); - - git_reference_free(ref); - git_str_dispose(&b); -} - -void test_refs_branches_create__can_create_branch_with_unicode(void) -{ - const char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; - const char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; - const char *emoji = "\xF0\x9F\x8D\xB7"; - const char *names[] = { nfc, nfd, emoji }; - const char *alt[] = { nfd, nfc, NULL }; - const char *expected[] = { nfc, nfd, emoji }; - unsigned int i; - bool fs_decompose_unicode = - git_fs_path_does_decompose_unicode(git_repository_path(repo)); - - retrieve_known_commit(&target, repo); - - if (cl_repo_get_bool(repo, "core.precomposeunicode")) - expected[1] = nfc; - /* test decomp. because not all Mac filesystems decompose unicode */ - else if (fs_decompose_unicode) - expected[0] = nfd; - - for (i = 0; i < ARRAY_SIZE(names); ++i) { - const char *name; - cl_git_pass(git_branch_create( - &branch, repo, names[i], target, 0)); - cl_git_pass(git_oid_cmp( - git_reference_target(branch), git_commit_id(target))); - - cl_git_pass(git_branch_name(&name, branch)); - cl_assert_equal_s(expected[i], name); - assert_branch_matches_name(expected[i], names[i]); - if (fs_decompose_unicode && alt[i]) - assert_branch_matches_name(expected[i], alt[i]); - - cl_git_pass(git_branch_delete(branch)); - git_reference_free(branch); - branch = NULL; - } -} - -/** - * Verify that we can create a branch with a name that matches the - * namespace of a previously delete branch. - * - * git branch level_one/level_two - * git branch -D level_one/level_two - * git branch level_one - * - * We expect the delete to have deleted the files: - * ".git/refs/heads/level_one/level_two" - * ".git/logs/refs/heads/level_one/level_two" - * It may or may not have deleted the (now empty) - * containing directories. To match git.git behavior, - * the second create needs to implicilty delete the - * directories and create the new files. - * "refs/heads/level_one" - * "logs/refs/heads/level_one" - * - * We should not fail to create the branch or its - * reflog because of an obsolete namespace container - * directory. - */ -void test_refs_branches_create__name_vs_namespace(void) -{ - const char * name; - struct item { - const char *first; - const char *second; - }; - static const struct item item[] = { - { "level_one/level_two", "level_one" }, - { "a/b/c/d/e", "a/b/c/d" }, - { "ss/tt/uu/vv/ww", "ss" }, - /* And one test case that is deeper. */ - { "xx1/xx2/xx3/xx4", "xx1/xx2/xx3/xx4/xx5/xx6" }, - { NULL, NULL }, - }; - const struct item *p; - - retrieve_known_commit(&target, repo); - - for (p=item; p->first; p++) { - cl_git_pass(git_branch_create(&branch, repo, p->first, target, 0)); - cl_git_pass(git_oid_cmp(git_reference_target(branch), git_commit_id(target))); - cl_git_pass(git_branch_name(&name, branch)); - cl_assert_equal_s(name, p->first); - - cl_git_pass(git_branch_delete(branch)); - git_reference_free(branch); - branch = NULL; - - cl_git_pass(git_branch_create(&branch, repo, p->second, target, 0)); - git_reference_free(branch); - branch = NULL; - } -} - -/** - * We still need to fail if part of the namespace is - * still in use. - */ -void test_refs_branches_create__name_vs_namespace_fail(void) -{ - const char * name; - struct item { - const char *first; - const char *first_alternate; - const char *second; - }; - static const struct item item[] = { - { "level_one/level_two", "level_one/alternate", "level_one" }, - { "a/b/c/d/e", "a/b/c/d/alternate", "a/b/c/d" }, - { "ss/tt/uu/vv/ww", "ss/alternate", "ss" }, - { NULL, NULL, NULL }, - }; - const struct item *p; - - retrieve_known_commit(&target, repo); - - for (p=item; p->first; p++) { - cl_git_pass(git_branch_create(&branch, repo, p->first, target, 0)); - cl_git_pass(git_oid_cmp(git_reference_target(branch), git_commit_id(target))); - cl_git_pass(git_branch_name(&name, branch)); - cl_assert_equal_s(name, p->first); - - cl_git_pass(git_branch_delete(branch)); - git_reference_free(branch); - branch = NULL; - - cl_git_pass(git_branch_create(&branch, repo, p->first_alternate, target, 0)); - cl_git_pass(git_oid_cmp(git_reference_target(branch), git_commit_id(target))); - cl_git_pass(git_branch_name(&name, branch)); - cl_assert_equal_s(name, p->first_alternate); - - /* we do not delete the alternate. */ - git_reference_free(branch); - branch = NULL; - - cl_git_fail(git_branch_create(&branch, repo, p->second, target, 0)); - git_reference_free(branch); - branch = NULL; - } -} diff --git a/tests/refs/branches/delete.c b/tests/refs/branches/delete.c deleted file mode 100644 index 27995f9e2..000000000 --- a/tests/refs/branches/delete.c +++ /dev/null @@ -1,188 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" -#include "repo/repo_helpers.h" -#include "config/config_helpers.h" -#include "futils.h" -#include "reflog.h" - -static git_repository *repo; -static git_reference *fake_remote; - -void test_refs_branches_delete__initialize(void) -{ - git_oid id; - - repo = cl_git_sandbox_init("testrepo.git"); - - cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); - cl_git_pass(git_reference_create(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0, NULL)); -} - -void test_refs_branches_delete__cleanup(void) -{ - git_reference_free(fake_remote); - fake_remote = NULL; - - cl_git_sandbox_cleanup(); - repo = NULL; -} - -void test_refs_branches_delete__can_not_delete_a_branch_pointed_at_by_HEAD(void) -{ - git_reference *head; - git_reference *branch; - - /* Ensure HEAD targets the local master branch */ - cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); - cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); - git_reference_free(head); - - cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL)); - cl_git_fail(git_branch_delete(branch)); - git_reference_free(branch); -} - -void test_refs_branches_delete__can_delete_a_branch_even_if_HEAD_is_missing(void) -{ - git_reference *head; - git_reference *branch; - - cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); - git_reference_delete(head); - git_reference_free(head); - - cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); - cl_git_pass(git_branch_delete(branch)); - git_reference_free(branch); -} - -void test_refs_branches_delete__can_delete_a_branch_when_HEAD_is_unborn(void) -{ - git_reference *branch; - - make_head_unborn(repo, NON_EXISTING_HEAD); - - cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); - cl_git_pass(git_branch_delete(branch)); - git_reference_free(branch); -} - -void test_refs_branches_delete__can_delete_a_branch_pointed_at_by_detached_HEAD(void) -{ - git_reference *head, *branch; - - cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); - cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head)); - cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); - git_reference_free(head); - - /* Detach HEAD and make it target the commit that "master" points to */ - git_repository_detach_head(repo); - - cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL)); - cl_git_pass(git_branch_delete(branch)); - git_reference_free(branch); -} - -void test_refs_branches_delete__can_delete_a_local_branch(void) -{ - git_reference *branch; - cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); - cl_git_pass(git_branch_delete(branch)); - git_reference_free(branch); -} - -void test_refs_branches_delete__can_delete_a_remote_branch(void) -{ - git_reference *branch; - cl_git_pass(git_branch_lookup(&branch, repo, "nulltoken/master", GIT_BRANCH_REMOTE)); - cl_git_pass(git_branch_delete(branch)); - git_reference_free(branch); -} - -void test_refs_branches_delete__deleting_a_branch_removes_related_configuration_data(void) -{ - git_reference *branch; - - assert_config_entry_existence(repo, "branch.track-local.remote", true); - assert_config_entry_existence(repo, "branch.track-local.merge", true); - - cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL)); - cl_git_pass(git_branch_delete(branch)); - git_reference_free(branch); - - assert_config_entry_existence(repo, "branch.track-local.remote", false); - assert_config_entry_existence(repo, "branch.track-local.merge", false); -} - -void test_refs_branches_delete__removes_reflog(void) -{ - git_reference *branch; - git_reflog *log; - git_oid oidzero = {{0}}; - git_signature *sig; - - /* Ensure the reflog has at least one entry */ - cl_git_pass(git_signature_now(&sig, "Me", "user@example.com")); - cl_git_pass(git_reflog_read(&log, repo, "refs/heads/track-local")); - cl_git_pass(git_reflog_append(log, &oidzero, sig, "message")); - cl_assert(git_reflog_entrycount(log) > 0); - git_signature_free(sig); - git_reflog_free(log); - - cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL)); - cl_git_pass(git_branch_delete(branch)); - git_reference_free(branch); - - cl_assert_equal_i(false, git_reference_has_log(repo, "refs/heads/track-local")); - - /* Reading a non-existent reflog creates it, but it should be empty */ - cl_git_pass(git_reflog_read(&log, repo, "refs/heads/track-local")); - cl_assert_equal_i(0, git_reflog_entrycount(log)); - git_reflog_free(log); -} - -void test_refs_branches_delete__removes_empty_folders(void) -{ - const char *commondir = git_repository_commondir(repo); - git_oid commit_id; - git_commit *commit; - git_reference *branch; - - git_reflog *log; - git_oid oidzero = {{0}}; - git_signature *sig; - - git_str ref_folder = GIT_STR_INIT; - git_str reflog_folder = GIT_STR_INIT; - - /* Create a new branch with a nested name */ - cl_git_pass(git_oid_fromstr(&commit_id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); - cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); - cl_git_pass(git_branch_create(&branch, repo, "some/deep/ref", commit, 0)); - git_commit_free(commit); - - /* Ensure the reflog has at least one entry */ - cl_git_pass(git_signature_now(&sig, "Me", "user@example.com")); - cl_git_pass(git_reflog_read(&log, repo, "refs/heads/some/deep/ref")); - cl_git_pass(git_reflog_append(log, &oidzero, sig, "message")); - cl_assert(git_reflog_entrycount(log) > 0); - git_signature_free(sig); - git_reflog_free(log); - - cl_git_pass(git_str_joinpath(&ref_folder, commondir, "refs/heads/some/deep")); - cl_git_pass(git_str_join3(&reflog_folder, '/', commondir, GIT_REFLOG_DIR, "refs/heads/some/deep")); - - cl_assert(git_fs_path_exists(git_str_cstr(&ref_folder)) == true); - cl_assert(git_fs_path_exists(git_str_cstr(&reflog_folder)) == true); - - cl_git_pass(git_branch_delete(branch)); - - cl_assert(git_fs_path_exists(git_str_cstr(&ref_folder)) == false); - cl_assert(git_fs_path_exists(git_str_cstr(&reflog_folder)) == false); - - git_reference_free(branch); - git_str_dispose(&ref_folder); - git_str_dispose(&reflog_folder); -} - diff --git a/tests/refs/branches/ishead.c b/tests/refs/branches/ishead.c deleted file mode 100644 index 1df70b789..000000000 --- a/tests/refs/branches/ishead.c +++ /dev/null @@ -1,98 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" -#include "repo/repo_helpers.h" - -static git_repository *repo; -static git_reference *branch; - -void test_refs_branches_ishead__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); - branch = NULL; -} - -void test_refs_branches_ishead__cleanup(void) -{ - git_reference_free(branch); - branch = NULL; - - cl_git_sandbox_cleanup(); - repo = NULL; -} - -void test_refs_branches_ishead__can_tell_if_a_branch_is_pointed_at_by_HEAD(void) -{ - cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); - - cl_assert_equal_i(true, git_branch_is_head(branch)); -} - -void test_refs_branches_ishead__can_properly_handle_unborn_HEAD(void) -{ - make_head_unborn(repo, NON_EXISTING_HEAD); - - cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); - - cl_assert_equal_i(false, git_branch_is_head(branch)); -} - -void test_refs_branches_ishead__can_properly_handle_missing_HEAD(void) -{ - delete_head(repo); - - cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); - - cl_assert_equal_i(false, git_branch_is_head(branch)); -} - -void test_refs_branches_ishead__can_tell_if_a_branch_is_not_pointed_at_by_HEAD(void) -{ - cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/br2")); - - cl_assert_equal_i(false, git_branch_is_head(branch)); -} - -void test_refs_branches_ishead__wont_be_fooled_by_a_non_branch(void) -{ - cl_git_pass(git_reference_lookup(&branch, repo, "refs/tags/e90810b")); - - cl_assert_equal_i(false, git_branch_is_head(branch)); -} - -/* - * $ git init . - * Initialized empty Git repository in d:/temp/tempee/.git/ - * - * $ touch a && git add a - * $ git commit -m" boom" - * [master (root-commit) b47b758] boom - * 0 files changed - * create mode 100644 a - * - * $ echo "ref: refs/heads/master" > .git/refs/heads/linked - * $ echo "ref: refs/heads/linked" > .git/refs/heads/super - * $ echo "ref: refs/heads/super" > .git/HEAD - * - * $ git branch - * linked -> master - * * master - * super -> master - */ -void test_refs_branches_ishead__only_direct_references_are_considered(void) -{ - git_reference *linked, *super, *head; - - cl_git_pass(git_reference_symbolic_create(&linked, repo, "refs/heads/linked", "refs/heads/master", 0, NULL)); - cl_git_pass(git_reference_symbolic_create(&super, repo, "refs/heads/super", "refs/heads/linked", 0, NULL)); - cl_git_pass(git_reference_symbolic_create(&head, repo, GIT_HEAD_FILE, "refs/heads/super", 1, NULL)); - - cl_assert_equal_i(false, git_branch_is_head(linked)); - cl_assert_equal_i(false, git_branch_is_head(super)); - - cl_git_pass(git_repository_head(&branch, repo)); - cl_assert_equal_s("refs/heads/master", git_reference_name(branch)); - - git_reference_free(linked); - git_reference_free(super); - git_reference_free(head); -} diff --git a/tests/refs/branches/iterator.c b/tests/refs/branches/iterator.c deleted file mode 100644 index e086681e5..000000000 --- a/tests/refs/branches/iterator.c +++ /dev/null @@ -1,151 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" - -static git_repository *repo; -static git_reference *fake_remote; - -void test_refs_branches_iterator__initialize(void) -{ - git_oid id; - - cl_fixture_sandbox("testrepo.git"); - cl_git_pass(git_repository_open(&repo, "testrepo.git")); - - cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); - cl_git_pass(git_reference_create(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0, NULL)); -} - -void test_refs_branches_iterator__cleanup(void) -{ - git_reference_free(fake_remote); - fake_remote = NULL; - - git_repository_free(repo); - repo = NULL; - - cl_fixture_cleanup("testrepo.git"); - - cl_git_sandbox_cleanup(); -} - -static void assert_retrieval(unsigned int flags, unsigned int expected_count) -{ - git_branch_iterator *iter; - git_reference *ref; - int count = 0, error; - git_branch_t type; - - cl_git_pass(git_branch_iterator_new(&iter, repo, flags)); - while ((error = git_branch_next(&ref, &type, iter)) == 0) { - count++; - git_reference_free(ref); - } - - git_branch_iterator_free(iter); - cl_assert_equal_i(error, GIT_ITEROVER); - cl_assert_equal_i(expected_count, count); -} - -void test_refs_branches_iterator__retrieve_all_branches(void) -{ - assert_retrieval(GIT_BRANCH_ALL, 15); -} - -void test_refs_branches_iterator__retrieve_remote_branches(void) -{ - assert_retrieval(GIT_BRANCH_REMOTE, 2); -} - -void test_refs_branches_iterator__retrieve_local_branches(void) -{ - assert_retrieval(GIT_BRANCH_LOCAL, 13); -} - -struct expectations { - const char *branch_name; - int encounters; -}; - -static void assert_branch_has_been_found(struct expectations *findings, const char* expected_branch_name) -{ - int pos = 0; - - for (pos = 0; findings[pos].branch_name; ++pos) { - if (strcmp(expected_branch_name, findings[pos].branch_name) == 0) { - cl_assert_equal_i(1, findings[pos].encounters); - return; - } - } - - cl_fail("expected branch not found in list."); -} - -static void contains_branches(struct expectations exp[], git_branch_iterator *iter) -{ - git_reference *ref; - git_branch_t type; - int error, pos = 0; - - while ((error = git_branch_next(&ref, &type, iter)) == 0) { - for (pos = 0; exp[pos].branch_name; ++pos) { - if (strcmp(git_reference_shorthand(ref), exp[pos].branch_name) == 0) - exp[pos].encounters++; - } - - git_reference_free(ref); - } - - cl_assert_equal_i(error, GIT_ITEROVER); -} - -/* - * $ git branch -r - * nulltoken/HEAD -> nulltoken/master - * nulltoken/master - */ -void test_refs_branches_iterator__retrieve_remote_symbolic_HEAD_when_present(void) -{ - git_branch_iterator *iter; - struct expectations exp[] = { - { "nulltoken/HEAD", 0 }, - { "nulltoken/master", 0 }, - { NULL, 0 } - }; - - git_reference_free(fake_remote); - cl_git_pass(git_reference_symbolic_create(&fake_remote, repo, "refs/remotes/nulltoken/HEAD", "refs/remotes/nulltoken/master", 0, NULL)); - - assert_retrieval(GIT_BRANCH_REMOTE, 3); - - cl_git_pass(git_branch_iterator_new(&iter, repo, GIT_BRANCH_REMOTE)); - contains_branches(exp, iter); - git_branch_iterator_free(iter); - - assert_branch_has_been_found(exp, "nulltoken/HEAD"); - assert_branch_has_been_found(exp, "nulltoken/master"); -} - -void test_refs_branches_iterator__mix_of_packed_and_loose(void) -{ - git_branch_iterator *iter; - struct expectations exp[] = { - { "master", 0 }, - { "origin/HEAD", 0 }, - { "origin/master", 0 }, - { "origin/packed", 0 }, - { NULL, 0 } - }; - git_repository *r2; - - r2 = cl_git_sandbox_init("testrepo2"); - - cl_git_pass(git_branch_iterator_new(&iter, r2, GIT_BRANCH_ALL)); - contains_branches(exp, iter); - - git_branch_iterator_free(iter); - - assert_branch_has_been_found(exp, "master"); - assert_branch_has_been_found(exp, "origin/HEAD"); - assert_branch_has_been_found(exp, "origin/master"); - assert_branch_has_been_found(exp, "origin/packed"); -} diff --git a/tests/refs/branches/lookup.c b/tests/refs/branches/lookup.c deleted file mode 100644 index ef0c1f97d..000000000 --- a/tests/refs/branches/lookup.c +++ /dev/null @@ -1,68 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" - -static git_repository *repo; -static git_reference *branch; - -void test_refs_branches_lookup__initialize(void) -{ - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - - branch = NULL; -} - -void test_refs_branches_lookup__cleanup(void) -{ - git_reference_free(branch); - branch = NULL; - - git_repository_free(repo); - repo = NULL; -} - -void test_refs_branches_lookup__can_retrieve_a_local_branch_local(void) -{ - cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); -} - -void test_refs_branches_lookup__can_retrieve_a_local_branch_all(void) -{ - cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_ALL)); -} - -void test_refs_branches_lookup__trying_to_retrieve_a_local_branch_remote(void) -{ - cl_git_fail(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_REMOTE)); -} - -void test_refs_branches_lookup__can_retrieve_a_remote_tracking_branch_remote(void) -{ - cl_git_pass(git_branch_lookup(&branch, repo, "test/master", GIT_BRANCH_REMOTE)); -} - -void test_refs_branches_lookup__can_retrieve_a_remote_tracking_branch_all(void) -{ - cl_git_pass(git_branch_lookup(&branch, repo, "test/master", GIT_BRANCH_ALL)); -} - -void test_refs_branches_lookup__trying_to_retrieve_a_remote_tracking_branch_local(void) -{ - cl_git_fail(git_branch_lookup(&branch, repo, "test/master", GIT_BRANCH_LOCAL)); -} - -void test_refs_branches_lookup__trying_to_retrieve_an_unknown_branch_returns_ENOTFOUND(void) -{ - cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "where/are/you", GIT_BRANCH_LOCAL)); - cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "over/here", GIT_BRANCH_REMOTE)); - cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "maybe/here", GIT_BRANCH_ALL)); -} - -void test_refs_branches_lookup__trying_to_retrieve_a_branch_with_an_invalid_name_returns_EINVALIDSPEC(void) -{ - cl_assert_equal_i(GIT_EINVALIDSPEC, - git_branch_lookup(&branch, repo, "are/you/inv@{id", GIT_BRANCH_LOCAL)); - cl_assert_equal_i(GIT_EINVALIDSPEC, - git_branch_lookup(&branch, repo, "yes/i am", GIT_BRANCH_REMOTE)); - cl_assert_equal_i(GIT_EINVALIDSPEC, - git_branch_lookup(&branch, repo, "inv al/id", GIT_BRANCH_ALL)); -} diff --git a/tests/refs/branches/move.c b/tests/refs/branches/move.c deleted file mode 100644 index 46a5082d2..000000000 --- a/tests/refs/branches/move.c +++ /dev/null @@ -1,212 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" -#include "config/config_helpers.h" - -static git_repository *repo; - -void test_refs_branches_move__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_refs_branches_move__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -#define NEW_BRANCH_NAME "new-branch-on-the-block" - -void test_refs_branches_move__can_move_a_local_branch(void) -{ - git_reference *original_ref, *new_ref; - - cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); - - cl_git_pass(git_branch_move(&new_ref, original_ref, NEW_BRANCH_NAME, 0)); - cl_assert_equal_s(GIT_REFS_HEADS_DIR NEW_BRANCH_NAME, git_reference_name(new_ref)); - - git_reference_free(original_ref); - git_reference_free(new_ref); -} - -void test_refs_branches_move__can_move_a_local_branch_to_a_different_namespace(void) -{ - git_reference *original_ref, *new_ref, *newer_ref; - - cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); - - /* Downward */ - cl_git_pass(git_branch_move(&new_ref, original_ref, "somewhere/" NEW_BRANCH_NAME, 0)); - git_reference_free(original_ref); - - /* Upward */ - cl_git_pass(git_branch_move(&newer_ref, new_ref, "br2", 0)); - git_reference_free(new_ref); - - git_reference_free(newer_ref); -} - -void test_refs_branches_move__can_move_a_local_branch_to_a_partially_colliding_namespace(void) -{ - git_reference *original_ref, *new_ref, *newer_ref; - - cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); - - /* Downward */ - cl_git_pass(git_branch_move(&new_ref, original_ref, "br2/" NEW_BRANCH_NAME, 0)); - git_reference_free(original_ref); - - /* Upward */ - cl_git_pass(git_branch_move(&newer_ref, new_ref, "br2", 0)); - git_reference_free(new_ref); - - git_reference_free(newer_ref); -} - -void test_refs_branches_move__can_not_move_a_branch_if_its_destination_name_collide_with_an_existing_one(void) -{ - git_reference *original_ref, *new_ref; - git_config *config; - git_buf original_remote = GIT_BUF_INIT, - original_merge = GIT_BUF_INIT; - const char *str; - - cl_git_pass(git_repository_config_snapshot(&config, repo)); - - cl_git_pass(git_config_get_string_buf(&original_remote, config, "branch.master.remote")); - cl_git_pass(git_config_get_string_buf(&original_merge, config, "branch.master.merge")); - git_config_free(config); - - cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); - - cl_assert_equal_i(GIT_EEXISTS, - git_branch_move(&new_ref, original_ref, "master", 0)); - - cl_assert(git_error_last()->message != NULL); - - cl_git_pass(git_repository_config_snapshot(&config, repo)); - cl_git_pass(git_config_get_string(&str, config, "branch.master.remote")); - cl_assert_equal_s(original_remote.ptr, str); - cl_git_pass(git_config_get_string(&str, config, "branch.master.merge")); - cl_assert_equal_s(original_merge.ptr, str); - git_config_free(config); - - cl_assert_equal_i(GIT_EEXISTS, - git_branch_move(&new_ref, original_ref, "cannot-fetch", 0)); - - cl_assert(git_error_last()->message != NULL); - - cl_git_pass(git_repository_config_snapshot(&config, repo)); - cl_git_pass(git_config_get_string(&str, config, "branch.master.remote")); - cl_assert_equal_s(original_remote.ptr, str); - cl_git_pass(git_config_get_string(&str, config, "branch.master.merge")); - cl_assert_equal_s(original_merge.ptr, str); - git_config_free(config); - - git_reference_free(original_ref); - cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/track-local")); - - cl_assert_equal_i(GIT_EEXISTS, - git_branch_move(&new_ref, original_ref, "master", 0)); - - cl_assert(git_error_last()->message != NULL); - - cl_git_pass(git_repository_config_snapshot(&config, repo)); - cl_git_pass(git_config_get_string(&str, config, "branch.master.remote")); - cl_assert_equal_s(original_remote.ptr, str); - cl_git_pass(git_config_get_string(&str, config, "branch.master.merge")); - cl_assert_equal_s(original_merge.ptr, str); - - git_buf_dispose(&original_remote); - git_buf_dispose(&original_merge); - git_reference_free(original_ref); - git_config_free(config); -} - -void test_refs_branches_move__moving_a_branch_with_an_invalid_name_returns_EINVALIDSPEC(void) -{ - git_reference *original_ref, *new_ref; - - cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); - - cl_assert_equal_i(GIT_EINVALIDSPEC, git_branch_move(&new_ref, original_ref, "Inv@{id", 0)); - - git_reference_free(original_ref); -} - -void test_refs_branches_move__can_not_move_a_non_branch(void) -{ - git_reference *tag, *new_ref; - - cl_git_pass(git_reference_lookup(&tag, repo, "refs/tags/e90810b")); - cl_git_fail(git_branch_move(&new_ref, tag, NEW_BRANCH_NAME, 0)); - - git_reference_free(tag); -} - -void test_refs_branches_move__can_force_move_over_an_existing_branch(void) -{ - git_reference *original_ref, *new_ref; - - cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); - - cl_git_pass(git_branch_move(&new_ref, original_ref, "master", 1)); - - git_reference_free(original_ref); - git_reference_free(new_ref); -} - -void test_refs_branches_move__moving_a_branch_moves_related_configuration_data(void) -{ - git_reference *branch; - git_reference *new_branch; - - cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL)); - - assert_config_entry_existence(repo, "branch.track-local.remote", true); - assert_config_entry_existence(repo, "branch.track-local.merge", true); - assert_config_entry_existence(repo, "branch.moved.remote", false); - assert_config_entry_existence(repo, "branch.moved.merge", false); - - cl_git_pass(git_branch_move(&new_branch, branch, "moved", 0)); - git_reference_free(branch); - - assert_config_entry_existence(repo, "branch.track-local.remote", false); - assert_config_entry_existence(repo, "branch.track-local.merge", false); - assert_config_entry_existence(repo, "branch.moved.remote", true); - assert_config_entry_existence(repo, "branch.moved.merge", true); - - git_reference_free(new_branch); -} - -void test_refs_branches_move__moving_the_branch_pointed_at_by_HEAD_updates_HEAD(void) -{ - git_reference *branch; - git_reference *new_branch; - - cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); - cl_git_pass(git_branch_move(&new_branch, branch, "master2", 0)); - git_reference_free(branch); - git_reference_free(new_branch); - - cl_git_pass(git_repository_head(&branch, repo)); - cl_assert_equal_s("refs/heads/master2", git_reference_name(branch)); - git_reference_free(branch); -} - -void test_refs_branches_move__can_move_with_unicode(void) -{ - git_reference *original_ref, *new_ref; - const char *new_branch_name = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; - - cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); - cl_git_pass(git_branch_move(&new_ref, original_ref, new_branch_name, 0)); - - if (cl_repo_get_bool(repo, "core.precomposeunicode")) - cl_assert_equal_s(GIT_REFS_HEADS_DIR "\xC3\x85\x73\x74\x72\xC3\xB6\x6D", git_reference_name(new_ref)); - else - cl_assert_equal_s(GIT_REFS_HEADS_DIR "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D", git_reference_name(new_ref)); - - git_reference_free(original_ref); - git_reference_free(new_ref); -} diff --git a/tests/refs/branches/name.c b/tests/refs/branches/name.c deleted file mode 100644 index efa68e32b..000000000 --- a/tests/refs/branches/name.c +++ /dev/null @@ -1,62 +0,0 @@ -#include "clar_libgit2.h" -#include "branch.h" - -static git_repository *repo; -static git_reference *ref; - -void test_refs_branches_name__initialize(void) -{ - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); -} - -void test_refs_branches_name__cleanup(void) -{ - git_reference_free(ref); - ref = NULL; - - git_repository_free(repo); - repo = NULL; -} - -void test_refs_branches_name__can_get_local_branch_name(void) -{ - const char *name; - - cl_git_pass(git_branch_lookup(&ref,repo,"master",GIT_BRANCH_LOCAL)); - cl_git_pass(git_branch_name(&name,ref)); - cl_assert_equal_s("master",name); -} - -void test_refs_branches_name__can_get_remote_branch_name(void) -{ - const char *name; - - cl_git_pass(git_branch_lookup(&ref,repo,"test/master",GIT_BRANCH_REMOTE)); - cl_git_pass(git_branch_name(&name,ref)); - cl_assert_equal_s("test/master",name); -} - -void test_refs_branches_name__error_when_ref_is_no_branch(void) -{ - const char *name; - - cl_git_pass(git_reference_lookup(&ref,repo,"refs/notes/fanout")); - cl_git_fail(git_branch_name(&name,ref)); -} - -static int name_is_valid(const char *name) -{ - int valid; - cl_git_pass(git_branch_name_is_valid(&valid, name)); - return valid; -} - -void test_refs_branches_name__is_name_valid(void) -{ - cl_assert_equal_i(true, name_is_valid("master")); - cl_assert_equal_i(true, name_is_valid("test/master")); - - cl_assert_equal_i(false, name_is_valid("")); - cl_assert_equal_i(false, name_is_valid("HEAD")); - cl_assert_equal_i(false, name_is_valid("-dash")); -} diff --git a/tests/refs/branches/remote.c b/tests/refs/branches/remote.c deleted file mode 100644 index e2bd3485a..000000000 --- a/tests/refs/branches/remote.c +++ /dev/null @@ -1,65 +0,0 @@ -#include "clar_libgit2.h" -#include "branch.h" -#include "remote.h" - -static git_repository *g_repo; -static const char *remote_tracking_branch_name = "refs/remotes/test/master"; -static const char *expected_remote_name = "test"; -static int expected_remote_name_length; - -void test_refs_branches_remote__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); - - expected_remote_name_length = (int)strlen(expected_remote_name) + 1; -} - -void test_refs_branches_remote__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_refs_branches_remote__can_get_remote_for_branch(void) -{ - git_buf remotename = {0}; - - cl_git_pass(git_branch_remote_name(&remotename, g_repo, remote_tracking_branch_name)); - - cl_assert_equal_s("test", remotename.ptr); - git_buf_dispose(&remotename); -} - -void test_refs_branches_remote__no_matching_remote_returns_error(void) -{ - const char *unknown = "refs/remotes/nonexistent/master"; - git_buf buf = GIT_BUF_INIT; - - git_error_clear(); - cl_git_fail_with(git_branch_remote_name(&buf, g_repo, unknown), GIT_ENOTFOUND); - cl_assert(git_error_last() != NULL); -} - -void test_refs_branches_remote__local_remote_returns_error(void) -{ - const char *local = "refs/heads/master"; - git_buf buf = GIT_BUF_INIT; - - git_error_clear(); - cl_git_fail_with(git_branch_remote_name(&buf, g_repo, local), GIT_ERROR); - cl_assert(git_error_last() != NULL); -} - -void test_refs_branches_remote__ambiguous_remote_returns_error(void) -{ - git_remote *remote; - git_buf buf = GIT_BUF_INIT; - - /* Create the remote */ - cl_git_pass(git_remote_create_with_fetchspec(&remote, g_repo, "addtest", "http://github.com/libgit2/libgit2", "refs/heads/*:refs/remotes/test/*")); - - git_remote_free(remote); - - git_error_clear(); - cl_git_fail_with(git_branch_remote_name(&buf, g_repo, remote_tracking_branch_name), GIT_EAMBIGUOUS); - cl_assert(git_error_last() != NULL); -} diff --git a/tests/refs/branches/upstream.c b/tests/refs/branches/upstream.c deleted file mode 100644 index 919705e07..000000000 --- a/tests/refs/branches/upstream.c +++ /dev/null @@ -1,218 +0,0 @@ -#include "clar_libgit2.h" -#include "config/config_helpers.h" -#include "refs.h" - -static git_repository *repo; -static git_reference *branch, *upstream; - -void test_refs_branches_upstream__initialize(void) -{ - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - - branch = NULL; - upstream = NULL; -} - -void test_refs_branches_upstream__cleanup(void) -{ - git_reference_free(upstream); - git_reference_free(branch); - branch = NULL; - - git_repository_free(repo); - repo = NULL; -} - -void test_refs_branches_upstream__can_retrieve_the_remote_tracking_reference_of_a_local_branch(void) -{ - cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); - - cl_git_pass(git_branch_upstream(&upstream, branch)); - - cl_assert_equal_s("refs/remotes/test/master", git_reference_name(upstream)); -} - -void test_refs_branches_upstream__can_retrieve_the_local_upstream_reference_of_a_local_branch(void) -{ - cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/track-local")); - - cl_git_pass(git_branch_upstream(&upstream, branch)); - - cl_assert_equal_s("refs/heads/master", git_reference_name(upstream)); -} - -void test_refs_branches_upstream__cannot_retrieve_a_remote_upstream_reference_from_a_non_branch(void) -{ - cl_git_pass(git_reference_lookup(&branch, repo, "refs/tags/e90810b")); - - cl_git_fail(git_branch_upstream(&upstream, branch)); -} - -void test_refs_branches_upstream__trying_to_retrieve_a_remote_tracking_reference_from_a_plain_local_branch_returns_GIT_ENOTFOUND(void) -{ - cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/subtrees")); - - cl_assert_equal_i(GIT_ENOTFOUND, git_branch_upstream(&upstream, branch)); -} - -void test_refs_branches_upstream__trying_to_retrieve_a_remote_tracking_reference_from_a_branch_with_no_fetchspec_returns_GIT_ENOTFOUND(void) -{ - cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/cannot-fetch")); - - cl_assert_equal_i(GIT_ENOTFOUND, git_branch_upstream(&upstream, branch)); -} - -void test_refs_branches_upstream__upstream_remote(void) -{ - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_branch_upstream_remote(&buf, repo, "refs/heads/master")); - cl_assert_equal_s("test", buf.ptr); - git_buf_dispose(&buf); -} - -void test_refs_branches_upstream__upstream_merge(void) -{ - git_reference *branch; - git_repository *repository; - git_buf buf = GIT_BUF_INIT; - - repository = cl_git_sandbox_init("testrepo.git"); - - /* check repository */ - cl_git_pass(git_reference_lookup(&branch, repository, "refs/heads/test")); - cl_git_pass(git_branch_set_upstream(branch, "test/master")); - - assert_config_entry_value(repository, "branch.test.remote", "test"); - assert_config_entry_value(repository, "branch.test.merge", "refs/heads/master"); - - git_reference_free(branch); - - /* check merge branch */ - cl_git_pass(git_branch_upstream_merge(&buf, repository, "refs/heads/test")); - cl_assert_equal_s("refs/heads/master", buf.ptr); - git_buf_dispose(&buf); - - cl_git_sandbox_cleanup(); -} - -void test_refs_branches_upstream__upstream_remote_empty_value(void) -{ - git_repository *repository; - git_config *cfg; - git_buf buf = GIT_BUF_INIT; - - repository = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_config(&cfg, repository)); - cl_git_pass(git_config_set_string(cfg, "branch.master.remote", "")); - cl_git_fail_with(GIT_ENOTFOUND, git_branch_upstream_remote(&buf, repository, "refs/heads/master")); - - cl_git_pass(git_config_delete_entry(cfg, "branch.master.remote")); - cl_git_fail_with(GIT_ENOTFOUND, git_branch_upstream_remote(&buf, repository, "refs/heads/master")); - cl_git_sandbox_cleanup(); -} - -static void assert_merge_and_or_remote_key_missing(git_repository *repository, const git_commit *target, const char *entry_name) -{ - git_reference *branch; - - cl_assert_equal_i(GIT_OBJECT_COMMIT, git_object_type((git_object*)target)); - cl_git_pass(git_branch_create(&branch, repository, entry_name, (git_commit*)target, 0)); - - cl_assert_equal_i(GIT_ENOTFOUND, git_branch_upstream(&upstream, branch)); - - git_reference_free(branch); -} - -void test_refs_branches_upstream__retrieve_a_remote_tracking_reference_from_a_branch_with_no_remote_returns_GIT_ENOTFOUND(void) -{ - git_reference *head; - git_repository *repository; - git_commit *target; - - repository = cl_git_sandbox_init("testrepo.git"); - - cl_git_pass(git_repository_head(&head, repository)); - cl_git_pass(git_reference_peel(((git_object **)&target), head, GIT_OBJECT_COMMIT)); - git_reference_free(head); - - assert_merge_and_or_remote_key_missing(repository, target, "remoteless"); - assert_merge_and_or_remote_key_missing(repository, target, "mergeless"); - assert_merge_and_or_remote_key_missing(repository, target, "mergeandremoteless"); - - git_commit_free(target); - - cl_git_sandbox_cleanup(); -} - -void test_refs_branches_upstream__set_unset_upstream(void) -{ - git_reference *branch; - git_repository *repository; - - repository = cl_git_sandbox_init("testrepo.git"); - - /* remote */ - cl_git_pass(git_reference_lookup(&branch, repository, "refs/heads/test")); - cl_git_pass(git_branch_set_upstream(branch, "test/master")); - - assert_config_entry_value(repository, "branch.test.remote", "test"); - assert_config_entry_value(repository, "branch.test.merge", "refs/heads/master"); - - git_reference_free(branch); - - /* local */ - cl_git_pass(git_reference_lookup(&branch, repository, "refs/heads/test")); - cl_git_pass(git_branch_set_upstream(branch, "master")); - - assert_config_entry_value(repository, "branch.test.remote", "."); - assert_config_entry_value(repository, "branch.test.merge", "refs/heads/master"); - - /* unset */ - cl_git_pass(git_branch_set_upstream(branch, NULL)); - assert_config_entry_existence(repository, "branch.test.remote", false); - assert_config_entry_existence(repository, "branch.test.merge", false); - - git_reference_free(branch); - - cl_git_pass(git_reference_lookup(&branch, repository, "refs/heads/master")); - cl_git_pass(git_branch_set_upstream(branch, NULL)); - assert_config_entry_existence(repository, "branch.test.remote", false); - assert_config_entry_existence(repository, "branch.test.merge", false); - - git_reference_free(branch); - - cl_git_sandbox_cleanup(); -} - -void test_refs_branches_upstream__no_fetch_refspec(void) -{ - git_reference *ref, *branch; - git_repository *repo; - git_remote *remote; - git_config *cfg; - - repo = cl_git_sandbox_init("testrepo.git"); - - cl_git_pass(git_remote_create_with_fetchspec(&remote, repo, "matching", ".", NULL)); - cl_git_pass(git_remote_add_push(repo, "matching", ":")); - - cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/test")); - cl_git_pass(git_reference_create(&ref, repo, "refs/remotes/matching/master", git_reference_target(branch), 1, "fetch")); - cl_git_fail(git_branch_set_upstream(branch, "matching/master")); - cl_assert_equal_s("could not determine remote for 'refs/remotes/matching/master'", - git_error_last()->message); - - /* we can't set it automatically, so let's test the user setting it by hand */ - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass(git_config_set_string(cfg, "branch.test.remote", "matching")); - cl_git_pass(git_config_set_string(cfg, "branch.test.merge", "refs/heads/master")); - /* we still can't find it because there is no rule for that reference */ - cl_git_fail_with(GIT_ENOTFOUND, git_branch_upstream(&ref, branch)); - - git_reference_free(ref); - git_reference_free(branch); - git_remote_free(remote); - - cl_git_sandbox_cleanup(); -} diff --git a/tests/refs/branches/upstreamname.c b/tests/refs/branches/upstreamname.c deleted file mode 100644 index 5bae154d2..000000000 --- a/tests/refs/branches/upstreamname.c +++ /dev/null @@ -1,35 +0,0 @@ -#include "clar_libgit2.h" -#include "branch.h" - -static git_repository *repo; -static git_buf upstream_name; - -void test_refs_branches_upstreamname__initialize(void) -{ - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - -} - -void test_refs_branches_upstreamname__cleanup(void) -{ - git_buf_dispose(&upstream_name); - - git_repository_free(repo); - repo = NULL; -} - -void test_refs_branches_upstreamname__can_retrieve_the_remote_tracking_reference_name_of_a_local_branch(void) -{ - cl_git_pass(git_branch_upstream_name( - &upstream_name, repo, "refs/heads/master")); - - cl_assert_equal_s("refs/remotes/test/master", upstream_name.ptr); -} - -void test_refs_branches_upstreamname__can_retrieve_the_local_upstream_reference_name_of_a_local_branch(void) -{ - cl_git_pass(git_branch_upstream_name( - &upstream_name, repo, "refs/heads/track-local")); - - cl_assert_equal_s("refs/heads/master", upstream_name.ptr); -} diff --git a/tests/refs/crashes.c b/tests/refs/crashes.c deleted file mode 100644 index 4f508aed2..000000000 --- a/tests/refs/crashes.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" - -void test_refs_crashes__double_free(void) -{ - git_repository *repo; - git_reference *ref, *ref2; - const char *REFNAME = "refs/heads/xxx"; - - repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_reference_symbolic_create(&ref, repo, REFNAME, "refs/heads/master", 0, NULL)); - cl_git_pass(git_reference_lookup(&ref2, repo, REFNAME)); - cl_git_pass(git_reference_delete(ref)); - git_reference_free(ref); - git_reference_free(ref2); - - /* reference is gone from disk, so reloading it will fail */ - cl_git_fail(git_reference_lookup(&ref2, repo, REFNAME)); - - cl_git_sandbox_cleanup(); -} - -void test_refs_crashes__empty_packedrefs(void) -{ - git_repository *repo; - git_reference *ref; - const char *REFNAME = "refs/heads/xxx"; - git_str temp_path = GIT_STR_INIT; - int fd = 0; - - repo = cl_git_sandbox_init("empty_bare.git"); - - /* create zero-length packed-refs file */ - cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(repo), GIT_PACKEDREFS_FILE)); - cl_git_pass(((fd = p_creat(temp_path.ptr, 0644)) < 0)); - cl_git_pass(p_close(fd)); - - /* should fail gracefully */ - cl_git_fail_with( - GIT_ENOTFOUND, git_reference_lookup(&ref, repo, REFNAME)); - - cl_git_sandbox_cleanup(); - git_str_dispose(&temp_path); -} diff --git a/tests/refs/create.c b/tests/refs/create.c deleted file mode 100644 index 01eb62a52..000000000 --- a/tests/refs/create.c +++ /dev/null @@ -1,362 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" -#include "git2/reflog.h" -#include "reflog.h" -#include "ref_helpers.h" - -static const char *current_master_tip = "099fabac3a9ea935598528c27f866e34089c2eff"; -static const char *current_head_target = "refs/heads/master"; - -static git_repository *g_repo; - -void test_refs_create__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); - p_fsync__cnt = 0; -} - -void test_refs_create__cleanup(void) -{ - cl_git_sandbox_cleanup(); - - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 1)); - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION, 1)); - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 0)); -} - -void test_refs_create__symbolic(void) -{ - /* create a new symbolic reference */ - git_reference *new_reference, *looked_up_ref, *resolved_ref; - git_repository *repo2; - git_oid id; - - const char *new_head_tracker = "ANOTHER_HEAD_TRACKER"; - - git_oid_fromstr(&id, current_master_tip); - - /* Create and write the new symbolic reference */ - cl_git_pass(git_reference_symbolic_create(&new_reference, g_repo, new_head_tracker, current_head_target, 0, NULL)); - - /* Ensure the reference can be looked-up... */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head_tracker)); - cl_assert(git_reference_type(looked_up_ref) & GIT_REFERENCE_SYMBOLIC); - cl_assert(reference_is_packed(looked_up_ref) == 0); - cl_assert_equal_s(looked_up_ref->name, new_head_tracker); - - /* ...peeled.. */ - cl_git_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); - cl_assert(git_reference_type(resolved_ref) == GIT_REFERENCE_DIRECT); - - /* ...and that it points to the current master tip */ - cl_assert_equal_oid(&id, git_reference_target(resolved_ref)); - git_reference_free(looked_up_ref); - git_reference_free(resolved_ref); - - /* Similar test with a fresh new repository */ - cl_git_pass(git_repository_open(&repo2, "testrepo")); - - cl_git_pass(git_reference_lookup(&looked_up_ref, repo2, new_head_tracker)); - cl_git_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); - cl_assert_equal_oid(&id, git_reference_target(resolved_ref)); - - git_repository_free(repo2); - - git_reference_free(new_reference); - git_reference_free(looked_up_ref); - git_reference_free(resolved_ref); -} - -void test_refs_create__symbolic_with_arbitrary_content(void) -{ - git_reference *new_reference, *looked_up_ref; - git_repository *repo2; - git_oid id; - - const char *new_head_tracker = "ANOTHER_HEAD_TRACKER"; - const char *arbitrary_target = "ARBITRARY DATA"; - - git_oid_fromstr(&id, current_master_tip); - - /* Attempt to create symbolic ref with arbitrary data in target - * fails by default - */ - cl_git_fail(git_reference_symbolic_create(&new_reference, g_repo, new_head_tracker, arbitrary_target, 0, NULL)); - - git_libgit2_opts(GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION, 0); - - /* With strict target validation disabled, ref creation succeeds */ - cl_git_pass(git_reference_symbolic_create(&new_reference, g_repo, new_head_tracker, arbitrary_target, 0, NULL)); - - /* Ensure the reference can be looked-up... */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head_tracker)); - cl_assert(git_reference_type(looked_up_ref) & GIT_REFERENCE_SYMBOLIC); - cl_assert(reference_is_packed(looked_up_ref) == 0); - cl_assert_equal_s(looked_up_ref->name, new_head_tracker); - git_reference_free(looked_up_ref); - - /* Ensure the target is what we expect it to be */ - cl_assert_equal_s(git_reference_symbolic_target(new_reference), arbitrary_target); - - /* Similar test with a fresh new repository object */ - cl_git_pass(git_repository_open(&repo2, "testrepo")); - - /* Ensure the reference can be looked-up... */ - cl_git_pass(git_reference_lookup(&looked_up_ref, repo2, new_head_tracker)); - cl_assert(git_reference_type(looked_up_ref) & GIT_REFERENCE_SYMBOLIC); - cl_assert(reference_is_packed(looked_up_ref) == 0); - cl_assert_equal_s(looked_up_ref->name, new_head_tracker); - - /* Ensure the target is what we expect it to be */ - cl_assert_equal_s(git_reference_symbolic_target(new_reference), arbitrary_target); - - git_repository_free(repo2); - git_reference_free(new_reference); - git_reference_free(looked_up_ref); -} - -void test_refs_create__deep_symbolic(void) -{ - /* create a deep symbolic reference */ - git_reference *new_reference, *looked_up_ref, *resolved_ref; - git_oid id; - - const char *new_head_tracker = "deep/rooted/tracker"; - - git_oid_fromstr(&id, current_master_tip); - - cl_git_pass(git_reference_symbolic_create(&new_reference, g_repo, new_head_tracker, current_head_target, 0, NULL)); - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head_tracker)); - cl_git_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); - cl_assert_equal_oid(&id, git_reference_target(resolved_ref)); - - git_reference_free(new_reference); - git_reference_free(looked_up_ref); - git_reference_free(resolved_ref); -} - -void test_refs_create__oid(void) -{ - /* create a new OID reference */ - git_reference *new_reference, *looked_up_ref; - git_repository *repo2; - git_oid id; - - const char *new_head = "refs/heads/new-head"; - - git_oid_fromstr(&id, current_master_tip); - - /* Create and write the new object id reference */ - cl_git_pass(git_reference_create(&new_reference, g_repo, new_head, &id, 0, NULL)); - - /* Ensure the reference can be looked-up... */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head)); - cl_assert(git_reference_type(looked_up_ref) & GIT_REFERENCE_DIRECT); - cl_assert(reference_is_packed(looked_up_ref) == 0); - cl_assert_equal_s(looked_up_ref->name, new_head); - - /* ...and that it points to the current master tip */ - cl_assert_equal_oid(&id, git_reference_target(looked_up_ref)); - git_reference_free(looked_up_ref); - - /* Similar test with a fresh new repository */ - cl_git_pass(git_repository_open(&repo2, "testrepo")); - - cl_git_pass(git_reference_lookup(&looked_up_ref, repo2, new_head)); - cl_assert_equal_oid(&id, git_reference_target(looked_up_ref)); - - git_repository_free(repo2); - - git_reference_free(new_reference); - git_reference_free(looked_up_ref); -} - -/* Can by default create a reference that targets at an unknown id */ -void test_refs_create__oid_unknown_succeeds_without_strict(void) -{ - git_reference *new_reference, *looked_up_ref; - git_oid id; - - const char *new_head = "refs/heads/new-head"; - - git_oid_fromstr(&id, "deadbeef3f795b2b4353bcce3a527ad0a4f7f644"); - - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 0)); - - /* Create and write the new object id reference */ - cl_git_pass(git_reference_create(&new_reference, g_repo, new_head, &id, 0, NULL)); - git_reference_free(new_reference); - - /* Ensure the reference can't be looked-up... */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head)); - git_reference_free(looked_up_ref); -} - -/* Strict object enforcement enforces valid object id */ -void test_refs_create__oid_unknown_fails_by_default(void) -{ - git_reference *new_reference, *looked_up_ref; - git_oid id; - - const char *new_head = "refs/heads/new-head"; - - git_oid_fromstr(&id, "deadbeef3f795b2b4353bcce3a527ad0a4f7f644"); - - /* Create and write the new object id reference */ - cl_git_fail(git_reference_create(&new_reference, g_repo, new_head, &id, 0, NULL)); - - /* Ensure the reference can't be looked-up... */ - cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, new_head)); -} - -void test_refs_create__propagate_eexists(void) -{ - git_oid oid; - - /* Make sure it works for oid and for symbolic both */ - cl_git_pass(git_oid_fromstr(&oid, current_master_tip)); - cl_git_fail_with(GIT_EEXISTS, git_reference_create(NULL, g_repo, current_head_target, &oid, false, NULL)); - cl_git_fail_with(GIT_EEXISTS, git_reference_symbolic_create(NULL, g_repo, "HEAD", current_head_target, false, NULL)); -} - -void test_refs_create__existing_dir_propagates_edirectory(void) -{ - git_reference *new_reference, *fail_reference; - git_oid id; - const char *dir_head = "refs/heads/new-dir/new-head", - *fail_head = "refs/heads/new-dir"; - - git_oid_fromstr(&id, current_master_tip); - - /* Create and write the new object id reference */ - cl_git_pass(git_reference_create(&new_reference, g_repo, dir_head, &id, 1, NULL)); - cl_git_fail_with(GIT_EDIRECTORY, - git_reference_create(&fail_reference, g_repo, fail_head, &id, false, NULL)); - - git_reference_free(new_reference); -} - -static void test_invalid_name(const char *name) -{ - git_reference *new_reference; - git_oid id; - - git_oid_fromstr(&id, current_master_tip); - - cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_create( - &new_reference, g_repo, name, &id, 0, NULL)); - - cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_symbolic_create( - &new_reference, g_repo, name, current_head_target, 0, NULL)); -} - -void test_refs_create__creating_a_reference_with_an_invalid_name_returns_EINVALIDSPEC(void) -{ - test_invalid_name("refs/heads/inv@{id"); - test_invalid_name("refs/heads/back\\slash"); - - test_invalid_name("refs/heads/foo "); - test_invalid_name("refs/heads/foo /bar"); - test_invalid_name("refs/heads/com1:bar/foo"); - - test_invalid_name("refs/heads/e:"); - test_invalid_name("refs/heads/c:/foo"); - - test_invalid_name("refs/heads/foo."); -} - -static void test_win32_name(const char *name) -{ - git_reference *new_reference = NULL; - git_oid id; - int ret; - - git_oid_fromstr(&id, current_master_tip); - - ret = git_reference_create(&new_reference, g_repo, name, &id, 0, NULL); - -#ifdef GIT_WIN32 - cl_assert_equal_i(GIT_EINVALIDSPEC, ret); -#else - cl_git_pass(ret); -#endif - - git_reference_free(new_reference); -} - -void test_refs_create__creating_a_loose_ref_with_invalid_windows_name(void) -{ - test_win32_name("refs/heads/foo./bar"); - - test_win32_name("refs/heads/aux"); - test_win32_name("refs/heads/aux.foo/bar"); - - test_win32_name("refs/heads/com1"); -} - -/* Creating a loose ref involves fsync'ing the reference, the - * reflog and (on non-Windows) the containing directories. - * Creating a packed ref involves fsync'ing the packed ref file - * and (on non-Windows) the containing directory. - */ -#ifdef GIT_WIN32 -static int expected_fsyncs_create = 2, expected_fsyncs_compress = 1; -#else -static int expected_fsyncs_create = 4, expected_fsyncs_compress = 2; -#endif - -static void count_fsyncs(size_t *create_count, size_t *compress_count) -{ - git_reference *ref = NULL; - git_refdb *refdb; - git_oid id; - - p_fsync__cnt = 0; - - git_oid_fromstr(&id, current_master_tip); - cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/fsync_test", &id, 0, "log message")); - git_reference_free(ref); - - *create_count = p_fsync__cnt; - p_fsync__cnt = 0; - - cl_git_pass(git_repository_refdb(&refdb, g_repo)); - cl_git_pass(git_refdb_compress(refdb)); - git_refdb_free(refdb); - - *compress_count = p_fsync__cnt; - p_fsync__cnt = 0; -} - -void test_refs_create__does_not_fsync_by_default(void) -{ - size_t create_count, compress_count; - count_fsyncs(&create_count, &compress_count); - - cl_assert_equal_i(0, create_count); - cl_assert_equal_i(0, compress_count); -} - -void test_refs_create__fsyncs_when_global_opt_set(void) -{ - size_t create_count, compress_count; - - cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 1)); - count_fsyncs(&create_count, &compress_count); - - cl_assert_equal_i(expected_fsyncs_create, create_count); - cl_assert_equal_i(expected_fsyncs_compress, compress_count); -} - -void test_refs_create__fsyncs_when_repo_config_set(void) -{ - size_t create_count, compress_count; - - cl_repo_set_bool(g_repo, "core.fsyncObjectFiles", true); - - count_fsyncs(&create_count, &compress_count); - - cl_assert_equal_i(expected_fsyncs_create, create_count); - cl_assert_equal_i(expected_fsyncs_compress, compress_count); -} diff --git a/tests/refs/delete.c b/tests/refs/delete.c deleted file mode 100644 index 42cc534b5..000000000 --- a/tests/refs/delete.c +++ /dev/null @@ -1,118 +0,0 @@ -#include "clar_libgit2.h" - -#include "futils.h" -#include "git2/reflog.h" -#include "git2/refdb.h" -#include "reflog.h" -#include "ref_helpers.h" - -static const char *packed_test_head_name = "refs/heads/packed-test"; -static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; - -static git_repository *g_repo; - - - -void test_refs_delete__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_refs_delete__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - - - -void test_refs_delete__packed_loose(void) -{ - /* deleting a ref which is both packed and loose should remove both tracks in the filesystem */ - git_reference *looked_up_ref, *another_looked_up_ref; - git_str temp_path = GIT_STR_INIT; - - /* Ensure the loose reference exists on the file system */ - cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), packed_test_head_name)); - cl_assert(git_fs_path_exists(temp_path.ptr)); - - /* Lookup the reference */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); - - /* Ensure it's the loose version that has been found */ - cl_assert(reference_is_packed(looked_up_ref) == 0); - - /* Now that the reference is deleted... */ - cl_git_pass(git_reference_delete(looked_up_ref)); - git_reference_free(looked_up_ref); - - /* Looking up the reference once again should not retrieve it */ - cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name)); - - /* Ensure the loose reference doesn't exist any longer on the file system */ - cl_assert(!git_fs_path_exists(temp_path.ptr)); - - git_reference_free(another_looked_up_ref); - git_str_dispose(&temp_path); -} - -void test_refs_delete__packed_only(void) -{ - /* can delete a just packed reference */ - git_reference *ref; - git_refdb *refdb; - git_oid id; - const char *new_ref = "refs/heads/new_ref"; - - git_oid_fromstr(&id, current_master_tip); - - /* Create and write the new object id reference */ - cl_git_pass(git_reference_create(&ref, g_repo, new_ref, &id, 0, NULL)); - git_reference_free(ref); - - /* Lookup the reference */ - cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref)); - - /* Ensure it's a loose reference */ - cl_assert(reference_is_packed(ref) == 0); - - /* Pack all existing references */ - cl_git_pass(git_repository_refdb(&refdb, g_repo)); - cl_git_pass(git_refdb_compress(refdb)); - - /* Reload the reference from disk */ - git_reference_free(ref); - cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref)); - - /* Ensure it's a packed reference */ - cl_assert(reference_is_packed(ref) == 1); - - /* This should pass */ - cl_git_pass(git_reference_delete(ref)); - git_reference_free(ref); - git_refdb_free(refdb); -} - -void test_refs_delete__remove(void) -{ - git_reference *ref; - - /* Check that passing no old values lets us delete */ - - cl_git_pass(git_reference_lookup(&ref, g_repo, packed_test_head_name)); - git_reference_free(ref); - - cl_git_pass(git_reference_remove(g_repo, packed_test_head_name)); - - cl_git_fail(git_reference_lookup(&ref, g_repo, packed_test_head_name)); -} - -void test_refs_delete__head(void) -{ - git_reference *ref; - - /* Check that it is not possible to delete HEAD */ - - cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); - cl_git_fail(git_reference_delete(ref)); - git_reference_free(ref); -} diff --git a/tests/refs/dup.c b/tests/refs/dup.c deleted file mode 100644 index 8a89cd95c..000000000 --- a/tests/refs/dup.c +++ /dev/null @@ -1,42 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" - -static git_repository *g_repo; - -void test_refs_dup__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_refs_dup__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_refs_dup__direct(void) -{ - git_reference *a, *b; - - cl_git_pass(git_reference_lookup(&a, g_repo, "refs/heads/master")); - cl_git_pass(git_reference_dup(&b, a)); - - cl_assert(git_reference_cmp(a, b) == 0); - cl_assert(git_reference_owner(b) == g_repo); - - git_reference_free(b); - git_reference_free(a); -} - -void test_refs_dup__symbolic(void) -{ - git_reference *a, *b; - - cl_git_pass(git_reference_lookup(&a, g_repo, "HEAD")); - cl_git_pass(git_reference_dup(&b, a)); - - cl_assert(git_reference_cmp(a, b) == 0); - cl_assert(git_reference_owner(b) == g_repo); - - git_reference_free(b); - git_reference_free(a); -} diff --git a/tests/refs/foreachglob.c b/tests/refs/foreachglob.c deleted file mode 100644 index b208a95a2..000000000 --- a/tests/refs/foreachglob.c +++ /dev/null @@ -1,100 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" - -static git_repository *repo; -static git_reference *fake_remote; - -void test_refs_foreachglob__initialize(void) -{ - git_oid id; - - cl_fixture_sandbox("testrepo.git"); - cl_git_pass(git_repository_open(&repo, "testrepo.git")); - - cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); - cl_git_pass(git_reference_create(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0, NULL)); -} - -void test_refs_foreachglob__cleanup(void) -{ - git_reference_free(fake_remote); - fake_remote = NULL; - - git_repository_free(repo); - repo = NULL; - - cl_fixture_cleanup("testrepo.git"); -} - -static int count_cb(const char *reference_name, void *payload) -{ - int *count = (int *)payload; - - GIT_UNUSED(reference_name); - - (*count)++; - - return 0; -} - -static void assert_retrieval(const char *glob, int expected_count) -{ - int count = 0; - - cl_git_pass(git_reference_foreach_glob(repo, glob, count_cb, &count)); - - cl_assert_equal_i(expected_count, count); -} - -void test_refs_foreachglob__retrieve_all_refs(void) -{ - /* 13 heads (including one packed head) + 1 note + 2 remotes + 7 tags + 1 blob */ - assert_retrieval("*", 24); -} - -void test_refs_foreachglob__retrieve_remote_branches(void) -{ - assert_retrieval("refs/remotes/*", 2); -} - -void test_refs_foreachglob__retrieve_local_branches(void) -{ - assert_retrieval("refs/heads/*", 13); -} - -void test_refs_foreachglob__retrieve_nonexistant(void) -{ - assert_retrieval("refs/nonexistent/*", 0); -} - -void test_refs_foreachglob__retrieve_partially_named_references(void) -{ - /* - * refs/heads/packed-test, refs/heads/test - * refs/remotes/test/master, refs/tags/test - */ - - assert_retrieval("*test*", 4); -} - - -static int interrupt_cb(const char *reference_name, void *payload) -{ - int *count = (int *)payload; - - GIT_UNUSED(reference_name); - - (*count)++; - - return (*count == 11) ? -1000 : 0; -} - -void test_refs_foreachglob__can_cancel(void) -{ - int count = 0; - - cl_assert_equal_i(-1000, git_reference_foreach_glob( - repo, "*", interrupt_cb, &count) ); - - cl_assert_equal_i(11, count); -} diff --git a/tests/refs/isvalidname.c b/tests/refs/isvalidname.c deleted file mode 100644 index 063f0f798..000000000 --- a/tests/refs/isvalidname.c +++ /dev/null @@ -1,38 +0,0 @@ -#include "clar_libgit2.h" - -static bool is_valid_name(const char *name) -{ - int valid; - cl_git_pass(git_reference_name_is_valid(&valid, name)); - return valid; -} - -void test_refs_isvalidname__can_detect_invalid_formats(void) -{ - cl_assert_equal_i(false, is_valid_name("refs/tags/0.17.0^{}")); - cl_assert_equal_i(false, is_valid_name("TWO/LEVELS")); - cl_assert_equal_i(false, is_valid_name("ONE.LEVEL")); - cl_assert_equal_i(false, is_valid_name("HEAD/")); - cl_assert_equal_i(false, is_valid_name("NO_TRAILING_UNDERSCORE_")); - cl_assert_equal_i(false, is_valid_name("_NO_LEADING_UNDERSCORE")); - cl_assert_equal_i(false, is_valid_name("HEAD/aa")); - cl_assert_equal_i(false, is_valid_name("lower_case")); - cl_assert_equal_i(false, is_valid_name("/stupid/name/master")); - cl_assert_equal_i(false, is_valid_name("/")); - cl_assert_equal_i(false, is_valid_name("//")); - cl_assert_equal_i(false, is_valid_name("")); - cl_assert_equal_i(false, is_valid_name("refs/heads/sub.lock/webmatrix")); -} - -void test_refs_isvalidname__wont_hopefully_choke_on_valid_formats(void) -{ - cl_assert_equal_i(true, is_valid_name("refs/tags/0.17.0")); - cl_assert_equal_i(true, is_valid_name("refs/LEVELS")); - cl_assert_equal_i(true, is_valid_name("HEAD")); - cl_assert_equal_i(true, is_valid_name("ONE_LEVEL")); - cl_assert_equal_i(true, is_valid_name("refs/stash")); - cl_assert_equal_i(true, is_valid_name("refs/remotes/origin/bim_with_3d@11296")); - cl_assert_equal_i(true, is_valid_name("refs/master{yesterday")); - cl_assert_equal_i(true, is_valid_name("refs/master}yesterday")); - cl_assert_equal_i(true, is_valid_name("refs/master{yesterday}")); -} diff --git a/tests/refs/iterator.c b/tests/refs/iterator.c deleted file mode 100644 index a4f9e62ec..000000000 --- a/tests/refs/iterator.c +++ /dev/null @@ -1,274 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" -#include "vector.h" - -static git_repository *repo; - -void test_refs_iterator__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_refs_iterator__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static const char *refnames[] = { - "refs/blobs/annotated_tag_to_blob", - "refs/heads/br2", - "refs/heads/cannot-fetch", - "refs/heads/chomped", - "refs/heads/haacked", - "refs/heads/master", - "refs/heads/not-good", - "refs/heads/packed", - "refs/heads/packed-test", - "refs/heads/subtrees", - "refs/heads/test", - "refs/heads/track-local", - "refs/heads/trailing", - "refs/heads/with-empty-log", - "refs/notes/fanout", - "refs/remotes/test/master", - "refs/tags/annotated_tag_to_blob", - "refs/tags/e90810b", - "refs/tags/hard_tag", - "refs/tags/point_to_blob", - "refs/tags/taggerless", - "refs/tags/test", - "refs/tags/wrapped_tag", - NULL -}; - -static const char *refnames_with_symlink[] = { - "refs/blobs/annotated_tag_to_blob", - "refs/heads/br2", - "refs/heads/cannot-fetch", - "refs/heads/chomped", - "refs/heads/haacked", - "refs/heads/link/a", - "refs/heads/link/b", - "refs/heads/link/c", - "refs/heads/link/d", - "refs/heads/master", - "refs/heads/not-good", - "refs/heads/packed", - "refs/heads/packed-test", - "refs/heads/subtrees", - "refs/heads/test", - "refs/heads/track-local", - "refs/heads/trailing", - "refs/heads/with-empty-log", - "refs/notes/fanout", - "refs/remotes/test/master", - "refs/tags/annotated_tag_to_blob", - "refs/tags/e90810b", - "refs/tags/hard_tag", - "refs/tags/point_to_blob", - "refs/tags/taggerless", - "refs/tags/test", - "refs/tags/wrapped_tag", - NULL -}; - -static int refcmp_cb(const void *a, const void *b) -{ - const git_reference *refa = (const git_reference *)a; - const git_reference *refb = (const git_reference *)b; - - return strcmp(refa->name, refb->name); -} - -static void assert_all_refnames_match(const char **expected, git_vector *names) -{ - size_t i; - git_reference *ref; - - git_vector_sort(names); - - git_vector_foreach(names, i, ref) { - cl_assert(expected[i] != NULL); - cl_assert_equal_s(expected[i], ref->name); - git_reference_free(ref); - } - cl_assert(expected[i] == NULL); - - git_vector_free(names); -} - -void test_refs_iterator__list(void) -{ - git_reference_iterator *iter; - git_vector output; - git_reference *ref; - - cl_git_pass(git_vector_init(&output, 33, &refcmp_cb)); - cl_git_pass(git_reference_iterator_new(&iter, repo)); - - while (1) { - int error = git_reference_next(&ref, iter); - if (error == GIT_ITEROVER) - break; - cl_git_pass(error); - cl_git_pass(git_vector_insert(&output, ref)); - } - - git_reference_iterator_free(iter); - - assert_all_refnames_match(refnames, &output); -} - -void test_refs_iterator__empty(void) -{ - git_reference_iterator *iter; - git_odb *odb; - git_reference *ref; - git_repository *empty; - - cl_git_pass(git_odb_new(&odb)); - cl_git_pass(git_repository_wrap_odb(&empty, odb)); - - cl_git_pass(git_reference_iterator_new(&iter, empty)); - cl_assert_equal_i(GIT_ITEROVER, git_reference_next(&ref, iter)); - - git_reference_iterator_free(iter); - git_odb_free(odb); - git_repository_free(empty); -} - -static int refs_foreach_cb(git_reference *reference, void *payload) -{ - git_vector *output = payload; - cl_git_pass(git_vector_insert(output, reference)); - return 0; -} - -void test_refs_iterator__foreach(void) -{ - git_vector output; - cl_git_pass(git_vector_init(&output, 33, &refcmp_cb)); - cl_git_pass(git_reference_foreach(repo, refs_foreach_cb, &output)); - assert_all_refnames_match(refnames, &output); -} - -void test_refs_iterator__foreach_through_symlink(void) -{ - git_vector output; - -#ifdef GIT_WIN32 - cl_skip(); -#endif - - cl_git_pass(git_vector_init(&output, 32, &refcmp_cb)); - - cl_git_pass(p_mkdir("refs", 0777)); - cl_git_mkfile("refs/a", "1234567890123456789012345678901234567890"); - cl_git_mkfile("refs/b", "1234567890123456789012345678901234567890"); - cl_git_mkfile("refs/c", "1234567890123456789012345678901234567890"); - cl_git_mkfile("refs/d", "1234567890123456789012345678901234567890"); - - cl_git_pass(p_symlink("../../../refs", "testrepo.git/refs/heads/link")); - - cl_git_pass(git_reference_foreach(repo, refs_foreach_cb, &output)); - assert_all_refnames_match(refnames_with_symlink, &output); -} - -static int refs_foreach_cancel_cb(git_reference *reference, void *payload) -{ - int *cancel_after = payload; - - git_reference_free(reference); - - if (!*cancel_after) - return -333; - (*cancel_after)--; - return 0; -} - -void test_refs_iterator__foreach_can_cancel(void) -{ - int cancel_after = 3; - cl_git_fail_with( - git_reference_foreach(repo, refs_foreach_cancel_cb, &cancel_after), - -333); - cl_assert_equal_i(0, cancel_after); -} - -static int refs_foreach_name_cb(const char *name, void *payload) -{ - git_vector *output = payload; - cl_git_pass(git_vector_insert(output, git__strdup(name))); - return 0; -} - -void test_refs_iterator__foreach_name(void) -{ - git_vector output; - size_t i; - char *name; - - cl_git_pass(git_vector_init(&output, 32, &git__strcmp_cb)); - cl_git_pass( - git_reference_foreach_name(repo, refs_foreach_name_cb, &output)); - - git_vector_sort(&output); - - git_vector_foreach(&output, i, name) { - cl_assert(refnames[i] != NULL); - cl_assert_equal_s(refnames[i], name); - git__free(name); - } - - git_vector_free(&output); -} - -static int refs_foreach_name_cancel_cb(const char *name, void *payload) -{ - int *cancel_after = payload; - if (!*cancel_after) - return -333; - GIT_UNUSED(name); - (*cancel_after)--; - return 0; -} - -void test_refs_iterator__foreach_name_can_cancel(void) -{ - int cancel_after = 5; - cl_git_fail_with( - git_reference_foreach_name( - repo, refs_foreach_name_cancel_cb, &cancel_after), - -333); - cl_assert_equal_i(0, cancel_after); -} - -void test_refs_iterator__concurrent_delete(void) -{ - git_reference_iterator *iter; - size_t full_count = 0, concurrent_count = 0; - const char *name; - int error; - - cl_git_sandbox_cleanup(); - repo = cl_git_sandbox_init("testrepo"); - - cl_git_pass(git_reference_iterator_new(&iter, repo)); - while ((error = git_reference_next_name(&name, iter)) == 0) { - full_count++; - } - - git_reference_iterator_free(iter); - cl_assert_equal_i(GIT_ITEROVER, error); - - cl_git_pass(git_reference_iterator_new(&iter, repo)); - while ((error = git_reference_next_name(&name, iter)) == 0) { - cl_git_pass(git_reference_remove(repo, name)); - concurrent_count++; - } - - git_reference_iterator_free(iter); - cl_assert_equal_i(GIT_ITEROVER, error); - - cl_assert_equal_i(full_count, concurrent_count); -} diff --git a/tests/refs/list.c b/tests/refs/list.c deleted file mode 100644 index 8085ff84b..000000000 --- a/tests/refs/list.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" -#include "git2/reflog.h" -#include "reflog.h" - -static git_repository *g_repo; - - - -void test_refs_list__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_refs_list__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - - - -void test_refs_list__all(void) -{ - /* try to list all the references in our test repo */ - git_strarray ref_list; - - cl_git_pass(git_reference_list(&ref_list, g_repo)); - - /*{ - unsigned short i; - for (i = 0; i < ref_list.count; ++i) - printf("# %s\n", ref_list.strings[i]); - }*/ - - /* We have exactly 12 refs in total if we include the packed ones: - * there is a reference that exists both in the packfile and as - * loose, but we only list it once */ - cl_assert_equal_i((int)ref_list.count, 19); - - git_strarray_dispose(&ref_list); -} - -void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_extension(void) -{ - git_strarray ref_list; - - /* Create a fake locked reference */ - cl_git_mkfile( - "./testrepo/.git/refs/heads/hanwen.lock", - "144344043ba4d4a405da03de3844aa829ae8be0e\n"); - - cl_git_pass(git_reference_list(&ref_list, g_repo)); - cl_assert_equal_i((int)ref_list.count, 19); - - git_strarray_dispose(&ref_list); -} diff --git a/tests/refs/listall.c b/tests/refs/listall.c deleted file mode 100644 index 9da8d1ac3..000000000 --- a/tests/refs/listall.c +++ /dev/null @@ -1,47 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" - -static git_repository *repo; -static git_strarray ref_list; - -static void ensure_no_refname_starts_with_a_forward_slash(const char *path) -{ - size_t i; - - cl_git_pass(git_repository_open(&repo, path)); - cl_git_pass(git_reference_list(&ref_list, repo)); - - cl_assert(ref_list.count > 0); - - for (i = 0; i < ref_list.count; i++) - cl_assert(git__prefixcmp(ref_list.strings[i], "/") != 0); - - git_strarray_dispose(&ref_list); - git_repository_free(repo); -} - -void test_refs_listall__from_repository_opened_through_workdir_path(void) -{ - cl_fixture_sandbox("status"); - cl_git_pass(p_rename("status/.gitted", "status/.git")); - - ensure_no_refname_starts_with_a_forward_slash("status"); - - cl_fixture_cleanup("status"); -} - -void test_refs_listall__from_repository_opened_through_gitdir_path(void) -{ - ensure_no_refname_starts_with_a_forward_slash(cl_fixture("testrepo.git")); -} - -void test_refs_listall__from_repository_with_no_trailing_newline(void) -{ - cl_git_pass(git_repository_open(&repo, cl_fixture("bad_tag.git"))); - cl_git_pass(git_reference_list(&ref_list, repo)); - - cl_assert(ref_list.count > 0); - - git_strarray_dispose(&ref_list); - git_repository_free(repo); -} diff --git a/tests/refs/lookup.c b/tests/refs/lookup.c deleted file mode 100644 index 01e956de2..000000000 --- a/tests/refs/lookup.c +++ /dev/null @@ -1,68 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" - -static git_repository *g_repo; - -void test_refs_lookup__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_refs_lookup__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_refs_lookup__with_resolve(void) -{ - git_reference *a, *b, *temp; - - cl_git_pass(git_reference_lookup(&temp, g_repo, "HEAD")); - cl_git_pass(git_reference_resolve(&a, temp)); - git_reference_free(temp); - - cl_git_pass(git_reference_lookup_resolved(&b, g_repo, "HEAD", 5)); - cl_assert(git_reference_cmp(a, b) == 0); - git_reference_free(b); - - cl_git_pass(git_reference_lookup_resolved(&b, g_repo, "HEAD_TRACKER", 5)); - cl_assert(git_reference_cmp(a, b) == 0); - git_reference_free(b); - - git_reference_free(a); -} - -void test_refs_lookup__invalid_name(void) -{ - git_oid oid; - cl_git_fail(git_reference_name_to_id(&oid, g_repo, "/refs/tags/point_to_blob")); -} - -void test_refs_lookup__oid(void) -{ - git_oid tag, expected; - - cl_git_pass(git_reference_name_to_id(&tag, g_repo, "refs/tags/point_to_blob")); - cl_git_pass(git_oid_fromstr(&expected, "1385f264afb75a56a5bec74243be9b367ba4ca08")); - cl_assert_equal_oid(&expected, &tag); -} - -void test_refs_lookup__namespace(void) -{ - int error; - git_reference *ref; - - error = git_reference_lookup(&ref, g_repo, "refs/heads"); - cl_assert_equal_i(error, GIT_ENOTFOUND); - - error = git_reference_lookup(&ref, g_repo, "refs/heads/"); - cl_assert_equal_i(error, GIT_EINVALIDSPEC); -} - -void test_refs_lookup__dwim_notfound(void) -{ - git_reference *ref; - - cl_git_fail_with(GIT_ENOTFOUND, git_reference_dwim(&ref, g_repo, "idontexist")); - cl_assert_equal_s("no reference found for shorthand 'idontexist'", git_error_last()->message); -} diff --git a/tests/refs/namespaces.c b/tests/refs/namespaces.c deleted file mode 100644 index 19456b5a4..000000000 --- a/tests/refs/namespaces.c +++ /dev/null @@ -1,36 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" - -static git_repository *g_repo; - -void test_refs_namespaces__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_refs_namespaces__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_refs_namespaces__get_and_set(void) -{ - cl_assert_equal_s(NULL, git_repository_get_namespace(g_repo)); - - cl_git_pass(git_repository_set_namespace(g_repo, "namespace")); - cl_assert_equal_s("namespace", git_repository_get_namespace(g_repo)); - - cl_git_pass(git_repository_set_namespace(g_repo, NULL)); - cl_assert_equal_s(NULL, git_repository_get_namespace(g_repo)); -} - -void test_refs_namespaces__namespace_doesnt_show_normal_refs(void) -{ - static git_strarray ref_list; - - cl_git_pass(git_repository_set_namespace(g_repo, "namespace")); - cl_git_pass(git_reference_list(&ref_list, g_repo)); - cl_assert_equal_i(0, ref_list.count); - git_strarray_dispose(&ref_list); -} diff --git a/tests/refs/normalize.c b/tests/refs/normalize.c deleted file mode 100644 index ff815002c..000000000 --- a/tests/refs/normalize.c +++ /dev/null @@ -1,401 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" -#include "git2/reflog.h" -#include "reflog.h" - -/* Helpers */ -static void ensure_refname_normalized( - unsigned int flags, - const char *input_refname, - const char *expected_refname) -{ - char buffer_out[GIT_REFNAME_MAX]; - - cl_git_pass(git_reference_normalize_name(buffer_out, sizeof(buffer_out), input_refname, flags)); - - cl_assert_equal_s(expected_refname, buffer_out); -} - -static void ensure_refname_invalid(unsigned int flags, const char *input_refname) -{ - char buffer_out[GIT_REFNAME_MAX]; - - cl_assert_equal_i( - GIT_EINVALIDSPEC, - git_reference_normalize_name(buffer_out, sizeof(buffer_out), input_refname, flags)); -} - -void test_refs_normalize__can_normalize_a_direct_reference_name(void) -{ - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_NORMAL, "refs/dummy/a", "refs/dummy/a"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_NORMAL, "refs/stash", "refs/stash"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_NORMAL, "refs/tags/a", "refs/tags/a"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/a/b", "refs/heads/a/b"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/a./b", "refs/heads/a./b"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/v@ation", "refs/heads/v@ation"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_NORMAL, "refs///heads///a", "refs/heads/a"); -} - -void test_refs_normalize__cannot_normalize_any_direct_reference_name(void) -{ - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "a"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "/a"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "//a"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, ""); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "/refs/heads/a/"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/a/"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/a."); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/a.lock"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/foo?bar"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads\foo"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/v@ation", "refs/heads/v@ation"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_NORMAL, "refs///heads///a", "refs/heads/a"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/.a/b"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/foo/../bar"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/foo..bar"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/./foo"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "refs/heads/v@{ation"); -} - -void test_refs_normalize__symbolic(void) -{ - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, ""); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "heads\foo"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "/"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "///"); - - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "ALL_CAPS_AND_UNDERSCORES", "ALL_CAPS_AND_UNDERSCORES"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/MixedCasing", "refs/MixedCasing"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs///heads///a", "refs/heads/a"); - - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "HEAD", "HEAD"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "MERGE_HEAD", "MERGE_HEAD"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "FETCH_HEAD", "FETCH_HEAD"); -} - -/* Ported from JGit, BSD licence. - * See https://github.com/spearce/JGit/commit/e4bf8f6957bbb29362575d641d1e77a02d906739 - * - * Copyright (C) 2009, Google Inc. - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Git Development Community nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -void test_refs_normalize__jgit_suite(void) -{ - /* tests borrowed from JGit */ - -/* EmptyString */ - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, ""); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "/"); - -/* MustHaveTwoComponents */ - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_NORMAL, "master"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_NORMAL, "heads/master", "heads/master"); - -/* ValidHead */ - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master", "refs/heads/master"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/pu", "refs/heads/pu"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/z", "refs/heads/z"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/FoO", "refs/heads/FoO"); - -/* ValidTag */ - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/tags/v1.0", "refs/tags/v1.0"); - -/* NoLockSuffix */ - ensure_refname_invalid(GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master.lock"); - -/* NoDirectorySuffix */ - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master/"); - -/* NoSpace */ - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/i haz space"); - -/* NoAsciiControlCharacters */ - { - char c; - char buffer[GIT_REFNAME_MAX]; - for (c = '\1'; c < ' '; c++) { - p_snprintf(buffer, sizeof(buffer), "refs/heads/mast%cer", c); - ensure_refname_invalid(GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, buffer); - } - } - -/* NoBareDot */ - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/."); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/.."); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/./master"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/../master"); - -/* NoLeadingOrTrailingDot */ - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "."); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/.bar"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/..bar"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/bar."); - -/* ContainsDot */ - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/m.a.s.t.e.r", "refs/heads/m.a.s.t.e.r"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master..pu"); - -/* NoMagicRefCharacters */ - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master^"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/^master"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "^refs/heads/master"); - - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master~"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/~master"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "~refs/heads/master"); - - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master:"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/:master"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, ":refs/heads/master"); - -/* ShellGlob */ - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master?"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/?master"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "?refs/heads/master"); - - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master["); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/[master"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "[refs/heads/master"); - - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master*"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/*master"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "*refs/heads/master"); - -/* ValidSpecialCharacters */ - ensure_refname_normalized - (GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/!", "refs/heads/!"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/\"", "refs/heads/\""); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/#", "refs/heads/#"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/$", "refs/heads/$"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/%", "refs/heads/%"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/&", "refs/heads/&"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/'", "refs/heads/'"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/(", "refs/heads/("); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/)", "refs/heads/)"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/+", "refs/heads/+"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/,", "refs/heads/,"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/-", "refs/heads/-"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/;", "refs/heads/;"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/<", "refs/heads/<"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/=", "refs/heads/="); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/>", "refs/heads/>"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/@", "refs/heads/@"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/]", "refs/heads/]"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/_", "refs/heads/_"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/`", "refs/heads/`"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/{", "refs/heads/{"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/|", "refs/heads/|"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/}", "refs/heads/}"); - - /* - * This is valid on UNIX, but not on Windows - * hence we make in invalid due to non-portability - */ - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/\\"); - -/* UnicodeNames */ - /* - * Currently this fails. - * ensure_refname_normalized(GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/\u00e5ngstr\u00f6m", "refs/heads/\u00e5ngstr\u00f6m"); - */ - -/* RefLogQueryIsValidRef */ - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master@{1}"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL, "refs/heads/master@{1.hour.ago}"); -} - -void test_refs_normalize__buffer_has_to_be_big_enough_to_hold_the_normalized_version(void) -{ - char buffer_out[21]; - - cl_git_pass(git_reference_normalize_name( - buffer_out, 21, "refs//heads///long///name", GIT_REFERENCE_FORMAT_NORMAL)); - cl_git_fail(git_reference_normalize_name( - buffer_out, 20, "refs//heads///long///name", GIT_REFERENCE_FORMAT_NORMAL)); -} - -#define ONE_LEVEL_AND_REFSPEC \ - GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL \ - | GIT_REFERENCE_FORMAT_REFSPEC_PATTERN - -void test_refs_normalize__refspec_pattern(void) -{ - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "heads/*foo/bar", "heads/*foo/bar"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "heads/foo*/bar", "heads/foo*/bar"); - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "heads/f*o/bar", "heads/f*o/bar"); - - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "foo"); - ensure_refname_normalized( - ONE_LEVEL_AND_REFSPEC, "FOO", "FOO"); - - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "foo/bar", "foo/bar"); - ensure_refname_normalized( - ONE_LEVEL_AND_REFSPEC, "foo/bar", "foo/bar"); - - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "*/foo", "*/foo"); - ensure_refname_normalized( - ONE_LEVEL_AND_REFSPEC, "*/foo", "*/foo"); - - ensure_refname_normalized( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "foo/*/bar", "foo/*/bar"); - ensure_refname_normalized( - ONE_LEVEL_AND_REFSPEC, "foo/*/bar", "foo/*/bar"); - - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "*"); - ensure_refname_normalized( - ONE_LEVEL_AND_REFSPEC, "*", "*"); - - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "foo/*/*"); - ensure_refname_invalid( - ONE_LEVEL_AND_REFSPEC, "foo/*/*"); - - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "*/foo/*"); - ensure_refname_invalid( - ONE_LEVEL_AND_REFSPEC, "*/foo/*"); - - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "*/*/foo"); - ensure_refname_invalid( - ONE_LEVEL_AND_REFSPEC, "*/*/foo"); -} diff --git a/tests/refs/overwrite.c b/tests/refs/overwrite.c deleted file mode 100644 index 1826b1257..000000000 --- a/tests/refs/overwrite.c +++ /dev/null @@ -1,136 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" -#include "git2/reflog.h" -#include "reflog.h" - -static const char *ref_name = "refs/heads/other"; -static const char *ref_master_name = "refs/heads/master"; -static const char *ref_branch_name = "refs/heads/branch"; -static const char *ref_test_name = "refs/heads/test"; - -static git_repository *g_repo; - -void test_refs_overwrite__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_refs_overwrite__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_refs_overwrite__symbolic(void) -{ - /* Overwrite an existing symbolic reference */ - git_reference *ref, *branch_ref; - - /* The target needds to exist and we need to check the name has changed */ - cl_git_pass(git_reference_symbolic_create(&branch_ref, g_repo, ref_branch_name, ref_master_name, 0, NULL)); - cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_branch_name, 0, NULL)); - git_reference_free(ref); - - /* Ensure it points to the right place*/ - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); - cl_assert(git_reference_type(ref) & GIT_REFERENCE_SYMBOLIC); - cl_assert_equal_s(git_reference_symbolic_target(ref), ref_branch_name); - git_reference_free(ref); - - /* Ensure we can't create it unless we force it to */ - cl_git_fail(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 0, NULL)); - cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 1, NULL)); - git_reference_free(ref); - - /* Ensure it points to the right place */ - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); - cl_assert(git_reference_type(ref) & GIT_REFERENCE_SYMBOLIC); - cl_assert_equal_s(git_reference_symbolic_target(ref), ref_master_name); - - git_reference_free(ref); - git_reference_free(branch_ref); -} - -void test_refs_overwrite__object_id(void) -{ - /* Overwrite an existing object id reference */ - git_reference *ref; - git_oid id; - - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); - cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); - git_oid_cpy(&id, git_reference_target(ref)); - git_reference_free(ref); - - /* Create it */ - cl_git_pass(git_reference_create(&ref, g_repo, ref_name, &id, 0, NULL)); - git_reference_free(ref); - - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_test_name)); - cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); - git_oid_cpy(&id, git_reference_target(ref)); - git_reference_free(ref); - - /* Ensure we can't overwrite unless we force it */ - cl_git_fail(git_reference_create(&ref, g_repo, ref_name, &id, 0, NULL)); - cl_git_pass(git_reference_create(&ref, g_repo, ref_name, &id, 1, NULL)); - git_reference_free(ref); - - /* Ensure it has been overwritten */ - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); - cl_assert_equal_oid(&id, git_reference_target(ref)); - - git_reference_free(ref); -} - -void test_refs_overwrite__object_id_with_symbolic(void) -{ - /* Overwrite an existing object id reference with a symbolic one */ - git_reference *ref; - git_oid id; - - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); - cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); - git_oid_cpy(&id, git_reference_target(ref)); - git_reference_free(ref); - - cl_git_pass(git_reference_create(&ref, g_repo, ref_name, &id, 0, NULL)); - git_reference_free(ref); - cl_git_fail(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 0, NULL)); - cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 1, NULL)); - git_reference_free(ref); - - /* Ensure it points to the right place */ - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); - cl_assert(git_reference_type(ref) & GIT_REFERENCE_SYMBOLIC); - cl_assert_equal_s(git_reference_symbolic_target(ref), ref_master_name); - - git_reference_free(ref); -} - -void test_refs_overwrite__symbolic_with_object_id(void) -{ - /* Overwrite an existing symbolic reference with an object id one */ - git_reference *ref; - git_oid id; - - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); - cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); - git_oid_cpy(&id, git_reference_target(ref)); - git_reference_free(ref); - - /* Create the symbolic ref */ - cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 0, NULL)); - git_reference_free(ref); - /* It shouldn't overwrite unless we tell it to */ - cl_git_fail(git_reference_create(&ref, g_repo, ref_name, &id, 0, NULL)); - cl_git_pass(git_reference_create(&ref, g_repo, ref_name, &id, 1, NULL)); - git_reference_free(ref); - - /* Ensure it points to the right place */ - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); - cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); - cl_assert_equal_oid(&id, git_reference_target(ref)); - - git_reference_free(ref); -} diff --git a/tests/refs/pack.c b/tests/refs/pack.c deleted file mode 100644 index 1c1cd51cb..000000000 --- a/tests/refs/pack.c +++ /dev/null @@ -1,105 +0,0 @@ -#include "clar_libgit2.h" - -#include "futils.h" -#include "git2/reflog.h" -#include "git2/refdb.h" -#include "reflog.h" -#include "refs.h" -#include "ref_helpers.h" - -static const char *loose_tag_ref_name = "refs/tags/e90810b"; - -static git_repository *g_repo; - -void test_refs_pack__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_refs_pack__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void packall(void) -{ - git_refdb *refdb; - - cl_git_pass(git_repository_refdb(&refdb, g_repo)); - cl_git_pass(git_refdb_compress(refdb)); - git_refdb_free(refdb); -} - -void test_refs_pack__empty(void) -{ - /* create a packfile for an empty folder */ - git_str temp_path = GIT_STR_INIT; - - cl_git_pass(git_str_join_n(&temp_path, '/', 3, git_repository_path(g_repo), GIT_REFS_HEADS_DIR, "empty_dir")); - cl_git_pass(git_futils_mkdir_r(temp_path.ptr, GIT_REFS_DIR_MODE)); - git_str_dispose(&temp_path); - - packall(); -} - -void test_refs_pack__loose(void) -{ - /* create a packfile from all the loose refs in a repo */ - git_reference *reference; - git_str temp_path = GIT_STR_INIT; - - /* Ensure a known loose ref can be looked up */ - cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name)); - cl_assert(reference_is_packed(reference) == 0); - cl_assert_equal_s(reference->name, loose_tag_ref_name); - git_reference_free(reference); - - /* - * We are now trying to pack also a loose reference - * called `points_to_blob`, to make sure we can properly - * pack weak tags - */ - packall(); - - /* Ensure the packed-refs file exists */ - cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), GIT_PACKEDREFS_FILE)); - cl_assert(git_fs_path_exists(temp_path.ptr)); - - /* Ensure the known ref can still be looked up but is now packed */ - cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name)); - cl_assert(reference_is_packed(reference)); - cl_assert_equal_s(reference->name, loose_tag_ref_name); - - /* Ensure the known ref has been removed from the loose folder structure */ - cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), loose_tag_ref_name)); - cl_assert(!git_fs_path_exists(temp_path.ptr)); - - git_reference_free(reference); - git_str_dispose(&temp_path); -} - -void test_refs_pack__symbolic(void) -{ - /* create a packfile from loose refs skipping symbolic refs */ - int i; - git_oid head; - git_reference *ref; - char name[128]; - - cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); - - /* make a bunch of references */ - - for (i = 0; i < 100; ++i) { - p_snprintf(name, sizeof(name), "refs/heads/symbolic-%03d", i); - cl_git_pass(git_reference_symbolic_create( - &ref, g_repo, name, "refs/heads/master", 0, NULL)); - git_reference_free(ref); - - p_snprintf(name, sizeof(name), "refs/heads/direct-%03d", i); - cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0, NULL)); - git_reference_free(ref); - } - - packall(); -} diff --git a/tests/refs/peel.c b/tests/refs/peel.c deleted file mode 100644 index 38f3465a0..000000000 --- a/tests/refs/peel.c +++ /dev/null @@ -1,131 +0,0 @@ -#include "clar_libgit2.h" - -static git_repository *g_repo; -static git_repository *g_peel_repo; - -void test_refs_peel__initialize(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_open(&g_peel_repo, cl_fixture("peeled.git"))); -} - -void test_refs_peel__cleanup(void) -{ - git_repository_free(g_repo); - g_repo = NULL; - git_repository_free(g_peel_repo); - g_peel_repo = NULL; -} - -static void assert_peel_generic( - git_repository *repo, - const char *ref_name, - git_object_t requested_type, - const char* expected_sha, - git_object_t expected_type) -{ - git_oid expected_oid; - git_reference *ref; - git_object *peeled; - - cl_git_pass(git_reference_lookup(&ref, repo, ref_name)); - - cl_git_pass(git_reference_peel(&peeled, ref, requested_type)); - - cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); - cl_assert_equal_oid(&expected_oid, git_object_id(peeled)); - - cl_assert_equal_i(expected_type, git_object_type(peeled)); - - git_object_free(peeled); - git_reference_free(ref); -} - -static void assert_peel( - const char *ref_name, - git_object_t requested_type, - const char* expected_sha, - git_object_t expected_type) -{ - assert_peel_generic(g_repo, ref_name, requested_type, - expected_sha, expected_type); -} - -static void assert_peel_error(int error, const char *ref_name, git_object_t requested_type) -{ - git_reference *ref; - git_object *peeled; - - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); - - cl_assert_equal_i(error, git_reference_peel(&peeled, ref, requested_type)); - - git_reference_free(ref); -} - -void test_refs_peel__can_peel_a_tag(void) -{ - assert_peel("refs/tags/test", GIT_OBJECT_TAG, - "b25fa35b38051e4ae45d4222e795f9df2e43f1d1", GIT_OBJECT_TAG); - assert_peel("refs/tags/test", GIT_OBJECT_COMMIT, - "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); - assert_peel("refs/tags/test", GIT_OBJECT_TREE, - "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJECT_TREE); - assert_peel("refs/tags/point_to_blob", GIT_OBJECT_BLOB, - "1385f264afb75a56a5bec74243be9b367ba4ca08", GIT_OBJECT_BLOB); -} - -void test_refs_peel__can_peel_a_branch(void) -{ - assert_peel("refs/heads/master", GIT_OBJECT_COMMIT, - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJECT_COMMIT); - assert_peel("refs/heads/master", GIT_OBJECT_TREE, - "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162", GIT_OBJECT_TREE); -} - -void test_refs_peel__can_peel_a_symbolic_reference(void) -{ - assert_peel("HEAD", GIT_OBJECT_COMMIT, - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJECT_COMMIT); - assert_peel("HEAD", GIT_OBJECT_TREE, - "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162", GIT_OBJECT_TREE); -} - -void test_refs_peel__cannot_peel_into_a_non_existing_target(void) -{ - assert_peel_error(GIT_EINVALIDSPEC, "refs/tags/point_to_blob", GIT_OBJECT_TAG); -} - -void test_refs_peel__can_peel_into_any_non_tag_object(void) -{ - assert_peel("refs/heads/master", GIT_OBJECT_ANY, - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJECT_COMMIT); - assert_peel("refs/tags/point_to_blob", GIT_OBJECT_ANY, - "1385f264afb75a56a5bec74243be9b367ba4ca08", GIT_OBJECT_BLOB); - assert_peel("refs/tags/test", GIT_OBJECT_ANY, - "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJECT_COMMIT); -} - -void test_refs_peel__can_peel_fully_peeled_packed_refs(void) -{ - assert_peel_generic(g_peel_repo, - "refs/tags/tag-inside-tags", GIT_OBJECT_ANY, - "0df1a5865c8abfc09f1f2182e6a31be550e99f07", - GIT_OBJECT_COMMIT); - assert_peel_generic(g_peel_repo, - "refs/foo/tag-outside-tags", GIT_OBJECT_ANY, - "0df1a5865c8abfc09f1f2182e6a31be550e99f07", - GIT_OBJECT_COMMIT); -} - -void test_refs_peel__can_peel_fully_peeled_tag_to_tag(void) -{ - assert_peel_generic(g_peel_repo, - "refs/tags/tag-inside-tags", GIT_OBJECT_TAG, - "c2596aa0151888587ec5c0187f261e63412d9e11", - GIT_OBJECT_TAG); - assert_peel_generic(g_peel_repo, - "refs/foo/tag-outside-tags", GIT_OBJECT_TAG, - "c2596aa0151888587ec5c0187f261e63412d9e11", - GIT_OBJECT_TAG); -} diff --git a/tests/refs/races.c b/tests/refs/races.c deleted file mode 100644 index 476789358..000000000 --- a/tests/refs/races.c +++ /dev/null @@ -1,170 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" -#include "git2/reflog.h" -#include "reflog.h" -#include "ref_helpers.h" - -static const char *commit_id = "099fabac3a9ea935598528c27f866e34089c2eff"; -static const char *refname = "refs/heads/master"; -static const char *other_refname = "refs/heads/foo"; -static const char *other_commit_id = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; - -static git_repository *g_repo; - -void test_refs_races__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_refs_races__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_refs_races__create_matching_zero_old(void) -{ - git_reference *ref; - git_oid id, zero_id; - - git_oid_fromstr(&id, commit_id); - git_oid_fromstr(&zero_id, "0000000000000000000000000000000000000000"); - - cl_git_fail(git_reference_create_matching(&ref, g_repo, refname, &id, 1, &zero_id, NULL)); - git_reference_free(ref); - - cl_git_pass(git_reference_create_matching(&ref, g_repo, other_refname, &id, 1, &zero_id, NULL)); - git_reference_free(ref); - - cl_git_fail(git_reference_create_matching(&ref, g_repo, other_refname, &id, 1, &zero_id, NULL)); - git_reference_free(ref); -} - -void test_refs_races__create_matching(void) -{ - git_reference *ref, *ref2, *ref3; - git_oid id, other_id; - - git_oid_fromstr(&id, commit_id); - git_oid_fromstr(&other_id, other_commit_id); - - cl_git_fail_with(GIT_EMODIFIED, git_reference_create_matching(&ref, g_repo, refname, &other_id, 1, &other_id, NULL)); - - cl_git_pass(git_reference_lookup(&ref, g_repo, refname)); - cl_git_pass(git_reference_create_matching(&ref2, g_repo, refname, &other_id, 1, &id, NULL)); - cl_git_fail_with(GIT_EMODIFIED, git_reference_set_target(&ref3, ref, &other_id, NULL)); - - git_reference_free(ref); - git_reference_free(ref2); - git_reference_free(ref3); -} - -void test_refs_races__symbolic_create_matching(void) -{ - git_reference *ref, *ref2, *ref3; - git_oid id, other_id; - - git_oid_fromstr(&id, commit_id); - git_oid_fromstr(&other_id, other_commit_id); - - cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_create_matching(&ref, g_repo, "HEAD", other_refname, 1, other_refname, NULL)); - - cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); - cl_git_pass(git_reference_symbolic_create_matching(&ref2, g_repo, "HEAD", other_refname, 1, NULL, refname)); - cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_set_target(&ref3, ref, other_refname, NULL)); - - git_reference_free(ref); - git_reference_free(ref2); - git_reference_free(ref3); -} - -void test_refs_races__delete(void) -{ - git_reference *ref, *ref2; - git_oid id, other_id; - - git_oid_fromstr(&id, commit_id); - git_oid_fromstr(&other_id, other_commit_id); - - /* We can delete a value that matches */ - cl_git_pass(git_reference_lookup(&ref, g_repo, refname)); - cl_git_pass(git_reference_delete(ref)); - git_reference_free(ref); - - /* We cannot delete a symbolic value that doesn't match */ - cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref")); - cl_git_pass(git_reference_symbolic_create_matching(&ref2, g_repo, "refs/symref", other_refname, 1, NULL, refname)); - cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref)); - - git_reference_free(ref); - git_reference_free(ref2); - - cl_git_pass(git_reference_create(&ref, g_repo, refname, &id, 1, NULL)); - git_reference_free(ref); - - /* We cannot delete an oid value that doesn't match */ - cl_git_pass(git_reference_lookup(&ref, g_repo, refname)); - cl_git_pass(git_reference_create_matching(&ref2, g_repo, refname, &other_id, 1, &id, NULL)); - cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref)); - - git_reference_free(ref); - git_reference_free(ref2); -} - -void test_refs_races__switch_oid_to_symbolic(void) -{ - git_reference *ref, *ref2, *ref3; - git_oid id, other_id; - - git_oid_fromstr(&id, commit_id); - git_oid_fromstr(&other_id, other_commit_id); - - /* Removing a direct ref when it's currently symbolic should fail */ - cl_git_pass(git_reference_lookup(&ref, g_repo, refname)); - cl_git_pass(git_reference_symbolic_create(&ref2, g_repo, refname, other_refname, 1, NULL)); - cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref)); - - git_reference_free(ref); - git_reference_free(ref2); - - cl_git_pass(git_reference_create(&ref, g_repo, refname, &id, 1, NULL)); - git_reference_free(ref); - - /* Updating a direct ref when it's currently symbolic should fail */ - cl_git_pass(git_reference_lookup(&ref, g_repo, refname)); - cl_git_pass(git_reference_symbolic_create(&ref2, g_repo, refname, other_refname, 1, NULL)); - cl_git_fail_with(GIT_EMODIFIED, git_reference_set_target(&ref3, ref, &other_id, NULL)); - - git_reference_free(ref); - git_reference_free(ref2); - git_reference_free(ref3); -} - -void test_refs_races__switch_symbolic_to_oid(void) -{ - git_reference *ref, *ref2, *ref3; - git_oid id, other_id; - - git_oid_fromstr(&id, commit_id); - git_oid_fromstr(&other_id, other_commit_id); - - /* Removing a symbolic ref when it's currently direct should fail */ - cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref")); - cl_git_pass(git_reference_create(&ref2, g_repo, "refs/symref", &id, 1, NULL)); - cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref)); - - git_reference_free(ref); - git_reference_free(ref2); - - cl_git_pass(git_reference_symbolic_create(&ref, g_repo, "refs/symref", refname, 1, NULL)); - git_reference_free(ref); - - /* Updating a symbolic ref when it's currently direct should fail */ - cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref")); - cl_git_pass(git_reference_create(&ref2, g_repo, "refs/symref", &id, 1, NULL)); - cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_set_target(&ref3, ref, other_refname, NULL)); - - git_reference_free(ref); - git_reference_free(ref2); - git_reference_free(ref3); -} diff --git a/tests/refs/read.c b/tests/refs/read.c deleted file mode 100644 index a622c770b..000000000 --- a/tests/refs/read.c +++ /dev/null @@ -1,299 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" -#include "git2/reflog.h" -#include "reflog.h" -#include "ref_helpers.h" - -static const char *loose_tag_ref_name = "refs/tags/e90810b"; -static const char *non_existing_tag_ref_name = "refs/tags/i-do-not-exist"; -static const char *head_tracker_sym_ref_name = "HEAD_TRACKER"; -static const char *current_head_target = "refs/heads/master"; -static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; -static const char *packed_head_name = "refs/heads/packed"; -static const char *packed_test_head_name = "refs/heads/packed-test"; - -static git_repository *g_repo; - -void test_refs_read__initialize(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); -} - -void test_refs_read__cleanup(void) -{ - git_repository_free(g_repo); - g_repo = NULL; -} - -void test_refs_read__loose_tag(void) -{ - /* lookup a loose tag reference */ - git_reference *reference; - git_object *object; - git_str ref_name_from_tag_name = GIT_STR_INIT; - - cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name)); - cl_assert(git_reference_type(reference) & GIT_REFERENCE_DIRECT); - cl_assert(reference_is_packed(reference) == 0); - cl_assert_equal_s(reference->name, loose_tag_ref_name); - - cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(reference), GIT_OBJECT_ANY)); - cl_assert(object != NULL); - cl_assert(git_object_type(object) == GIT_OBJECT_TAG); - - /* Ensure the name of the tag matches the name of the reference */ - cl_git_pass(git_str_joinpath(&ref_name_from_tag_name, GIT_REFS_TAGS_DIR, git_tag_name((git_tag *)object))); - cl_assert_equal_s(ref_name_from_tag_name.ptr, loose_tag_ref_name); - git_str_dispose(&ref_name_from_tag_name); - - git_object_free(object); - - git_reference_free(reference); -} - -void test_refs_read__nonexisting_tag(void) -{ - /* lookup a loose tag reference that doesn't exist */ - git_reference *reference; - - cl_git_fail(git_reference_lookup(&reference, g_repo, non_existing_tag_ref_name)); - - git_reference_free(reference); -} - - -void test_refs_read__symbolic(void) -{ - /* lookup a symbolic reference */ - git_reference *reference, *resolved_ref; - git_object *object; - git_oid id; - - cl_git_pass(git_reference_lookup(&reference, g_repo, GIT_HEAD_FILE)); - cl_assert(git_reference_type(reference) & GIT_REFERENCE_SYMBOLIC); - cl_assert(reference_is_packed(reference) == 0); - cl_assert_equal_s(reference->name, GIT_HEAD_FILE); - - cl_git_pass(git_reference_resolve(&resolved_ref, reference)); - cl_assert(git_reference_type(resolved_ref) == GIT_REFERENCE_DIRECT); - - cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(resolved_ref), GIT_OBJECT_ANY)); - cl_assert(object != NULL); - cl_assert(git_object_type(object) == GIT_OBJECT_COMMIT); - - git_oid_fromstr(&id, current_master_tip); - cl_assert_equal_oid(&id, git_object_id(object)); - - git_object_free(object); - - git_reference_free(reference); - git_reference_free(resolved_ref); -} - -void test_refs_read__nested_symbolic(void) -{ - /* lookup a nested symbolic reference */ - git_reference *reference, *resolved_ref; - git_object *object; - git_oid id; - - cl_git_pass(git_reference_lookup(&reference, g_repo, head_tracker_sym_ref_name)); - cl_assert(git_reference_type(reference) & GIT_REFERENCE_SYMBOLIC); - cl_assert(reference_is_packed(reference) == 0); - cl_assert_equal_s(reference->name, head_tracker_sym_ref_name); - - cl_git_pass(git_reference_resolve(&resolved_ref, reference)); - cl_assert(git_reference_type(resolved_ref) == GIT_REFERENCE_DIRECT); - - cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(resolved_ref), GIT_OBJECT_ANY)); - cl_assert(object != NULL); - cl_assert(git_object_type(object) == GIT_OBJECT_COMMIT); - - git_oid_fromstr(&id, current_master_tip); - cl_assert_equal_oid(&id, git_object_id(object)); - - git_object_free(object); - - git_reference_free(reference); - git_reference_free(resolved_ref); -} - -void test_refs_read__head_then_master(void) -{ - /* lookup the HEAD and resolve the master branch */ - git_reference *reference, *resolved_ref, *comp_base_ref; - - cl_git_pass(git_reference_lookup(&reference, g_repo, head_tracker_sym_ref_name)); - cl_git_pass(git_reference_resolve(&comp_base_ref, reference)); - git_reference_free(reference); - - cl_git_pass(git_reference_lookup(&reference, g_repo, GIT_HEAD_FILE)); - cl_git_pass(git_reference_resolve(&resolved_ref, reference)); - cl_assert_equal_oid(git_reference_target(comp_base_ref), git_reference_target(resolved_ref)); - git_reference_free(reference); - git_reference_free(resolved_ref); - - cl_git_pass(git_reference_lookup(&reference, g_repo, current_head_target)); - cl_git_pass(git_reference_resolve(&resolved_ref, reference)); - cl_assert_equal_oid(git_reference_target(comp_base_ref), git_reference_target(resolved_ref)); - git_reference_free(reference); - git_reference_free(resolved_ref); - - git_reference_free(comp_base_ref); -} - -void test_refs_read__master_then_head(void) -{ - /* lookup the master branch and then the HEAD */ - git_reference *reference, *master_ref, *resolved_ref; - - cl_git_pass(git_reference_lookup(&master_ref, g_repo, current_head_target)); - cl_git_pass(git_reference_lookup(&reference, g_repo, GIT_HEAD_FILE)); - - cl_git_pass(git_reference_resolve(&resolved_ref, reference)); - cl_assert_equal_oid(git_reference_target(master_ref), git_reference_target(resolved_ref)); - - git_reference_free(reference); - git_reference_free(resolved_ref); - git_reference_free(master_ref); -} - - -void test_refs_read__packed(void) -{ - /* lookup a packed reference */ - git_reference *reference; - git_object *object; - - cl_git_pass(git_reference_lookup(&reference, g_repo, packed_head_name)); - cl_assert(git_reference_type(reference) & GIT_REFERENCE_DIRECT); - cl_assert(reference_is_packed(reference)); - cl_assert_equal_s(reference->name, packed_head_name); - - cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(reference), GIT_OBJECT_ANY)); - cl_assert(object != NULL); - cl_assert(git_object_type(object) == GIT_OBJECT_COMMIT); - - git_object_free(object); - - git_reference_free(reference); -} - -void test_refs_read__loose_first(void) -{ - /* assure that a loose reference is looked up before a packed reference */ - git_reference *reference; - - cl_git_pass(git_reference_lookup(&reference, g_repo, packed_head_name)); - git_reference_free(reference); - cl_git_pass(git_reference_lookup(&reference, g_repo, packed_test_head_name)); - cl_assert(git_reference_type(reference) & GIT_REFERENCE_DIRECT); - cl_assert(reference_is_packed(reference) == 0); - cl_assert_equal_s(reference->name, packed_test_head_name); - - git_reference_free(reference); -} - -void test_refs_read__chomped(void) -{ - git_reference *test, *chomped; - - cl_git_pass(git_reference_lookup(&test, g_repo, "refs/heads/test")); - cl_git_pass(git_reference_lookup(&chomped, g_repo, "refs/heads/chomped")); - cl_assert_equal_oid(git_reference_target(test), git_reference_target(chomped)); - - git_reference_free(test); - git_reference_free(chomped); -} - -void test_refs_read__trailing(void) -{ - git_reference *test, *trailing; - - cl_git_pass(git_reference_lookup(&test, g_repo, "refs/heads/test")); - cl_git_pass(git_reference_lookup(&trailing, g_repo, "refs/heads/trailing")); - cl_assert_equal_oid(git_reference_target(test), git_reference_target(trailing)); - git_reference_free(trailing); - cl_git_pass(git_reference_lookup(&trailing, g_repo, "FETCH_HEAD")); - - git_reference_free(test); - git_reference_free(trailing); -} - -void test_refs_read__unfound_return_ENOTFOUND(void) -{ - git_reference *reference; - git_oid id; - - cl_assert_equal_i(GIT_ENOTFOUND, - git_reference_lookup(&reference, g_repo, "TEST_MASTER")); - cl_assert_equal_i(GIT_ENOTFOUND, - git_reference_lookup(&reference, g_repo, "refs/test/master")); - cl_assert_equal_i(GIT_ENOTFOUND, - git_reference_lookup(&reference, g_repo, "refs/tags/test/master")); - cl_assert_equal_i(GIT_ENOTFOUND, - git_reference_lookup(&reference, g_repo, "refs/tags/test/farther/master")); - - cl_assert_equal_i(GIT_ENOTFOUND, - git_reference_name_to_id(&id, g_repo, "refs/tags/test/farther/master")); -} - -static void assert_is_branch(const char *name, bool expected_branchness) -{ - git_reference *reference; - cl_git_pass(git_reference_lookup(&reference, g_repo, name)); - cl_assert_equal_i(expected_branchness, git_reference_is_branch(reference)); - git_reference_free(reference); -} - -void test_refs_read__can_determine_if_a_reference_is_a_local_branch(void) -{ - assert_is_branch("refs/heads/master", true); - assert_is_branch("refs/heads/packed", true); - assert_is_branch("refs/remotes/test/master", false); - assert_is_branch("refs/tags/e90810b", false); -} - -static void assert_is_tag(const char *name, bool expected_tagness) -{ - git_reference *reference; - cl_git_pass(git_reference_lookup(&reference, g_repo, name)); - cl_assert_equal_i(expected_tagness, git_reference_is_tag(reference)); - git_reference_free(reference); -} - -void test_refs_read__can_determine_if_a_reference_is_a_tag(void) -{ - assert_is_tag("refs/tags/e90810b", true); - assert_is_tag("refs/tags/test", true); - assert_is_tag("refs/heads/packed", false); - assert_is_tag("refs/remotes/test/master", false); -} - -static void assert_is_note(const char *name, bool expected_noteness) -{ - git_reference *reference; - cl_git_pass(git_reference_lookup(&reference, g_repo, name)); - cl_assert_equal_i(expected_noteness, git_reference_is_note(reference)); - git_reference_free(reference); -} - -void test_refs_read__can_determine_if_a_reference_is_a_note(void) -{ - assert_is_note("refs/notes/fanout", true); - assert_is_note("refs/heads/packed", false); - assert_is_note("refs/remotes/test/master", false); -} - -void test_refs_read__invalid_name_returns_EINVALIDSPEC(void) -{ - git_reference *reference; - git_oid id; - - cl_assert_equal_i(GIT_EINVALIDSPEC, - git_reference_lookup(&reference, g_repo, "refs/heads/Inv@{id")); - - cl_assert_equal_i(GIT_EINVALIDSPEC, - git_reference_name_to_id(&id, g_repo, "refs/heads/Inv@{id")); -} diff --git a/tests/refs/ref_helpers.c b/tests/refs/ref_helpers.c deleted file mode 100644 index 70d5d36d5..000000000 --- a/tests/refs/ref_helpers.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "git2/repository.h" -#include "git2/refs.h" -#include "common.h" -#include "util.h" -#include "path.h" -#include "ref_helpers.h" - -int reference_is_packed(git_reference *ref) -{ - git_str ref_path = GIT_STR_INIT; - int packed; - - assert(ref); - - if (git_str_joinpath(&ref_path, - git_repository_path(git_reference_owner(ref)), - git_reference_name(ref)) < 0) - return -1; - - packed = !git_fs_path_isfile(ref_path.ptr); - - git_str_dispose(&ref_path); - - return packed; -} diff --git a/tests/refs/ref_helpers.h b/tests/refs/ref_helpers.h deleted file mode 100644 index 0ef55bfce..000000000 --- a/tests/refs/ref_helpers.h +++ /dev/null @@ -1 +0,0 @@ -int reference_is_packed(git_reference *ref); diff --git a/tests/refs/reflog/drop.c b/tests/refs/reflog/drop.c deleted file mode 100644 index 916bd9933..000000000 --- a/tests/refs/reflog/drop.c +++ /dev/null @@ -1,115 +0,0 @@ -#include "clar_libgit2.h" - -#include "reflog.h" - -static git_repository *g_repo; -static git_reflog *g_reflog; -static size_t entrycount; - -void test_refs_reflog_drop__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo.git"); - - git_reflog_read(&g_reflog, g_repo, "HEAD"); - entrycount = git_reflog_entrycount(g_reflog); -} - -void test_refs_reflog_drop__cleanup(void) -{ - git_reflog_free(g_reflog); - g_reflog = NULL; - - cl_git_sandbox_cleanup(); -} - -void test_refs_reflog_drop__dropping_a_non_exisiting_entry_from_the_log_returns_ENOTFOUND(void) -{ - cl_assert_equal_i(GIT_ENOTFOUND, git_reflog_drop(g_reflog, entrycount, 0)); - - cl_assert_equal_sz(entrycount, git_reflog_entrycount(g_reflog)); -} - -void test_refs_reflog_drop__can_drop_an_entry(void) -{ - cl_assert(entrycount > 4); - - cl_git_pass(git_reflog_drop(g_reflog, 2, 0)); - cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); -} - -void test_refs_reflog_drop__can_drop_an_entry_and_rewrite_the_log_history(void) -{ - const git_reflog_entry *before_current; - const git_reflog_entry *after_current; - git_oid before_current_old_oid, before_current_cur_oid; - - cl_assert(entrycount > 4); - - before_current = git_reflog_entry_byindex(g_reflog, 1); - - git_oid_cpy(&before_current_old_oid, &before_current->oid_old); - git_oid_cpy(&before_current_cur_oid, &before_current->oid_cur); - - cl_git_pass(git_reflog_drop(g_reflog, 1, 1)); - - cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); - - after_current = git_reflog_entry_byindex(g_reflog, 0); - - cl_assert_equal_i(0, git_oid_cmp(&before_current_old_oid, &after_current->oid_old)); - cl_assert(0 != git_oid_cmp(&before_current_cur_oid, &after_current->oid_cur)); -} - -void test_refs_reflog_drop__can_drop_the_oldest_entry(void) -{ - const git_reflog_entry *entry; - - cl_assert(entrycount > 2); - - cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 0)); - cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); - - entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); - cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) != 0); -} - -void test_refs_reflog_drop__can_drop_the_oldest_entry_and_rewrite_the_log_history(void) -{ - const git_reflog_entry *entry; - - cl_assert(entrycount > 2); - - cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 1)); - cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); - - entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); - cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); -} - -void test_refs_reflog_drop__can_drop_all_the_entries(void) -{ - cl_assert(--entrycount > 0); - - do { - cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); - } while (--entrycount > 0); - - cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); - - cl_assert_equal_i(0, (int)git_reflog_entrycount(g_reflog)); -} - -void test_refs_reflog_drop__can_persist_deletion_on_disk(void) -{ - cl_assert(entrycount > 2); - - cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); - cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); - cl_git_pass(git_reflog_write(g_reflog)); - - git_reflog_free(g_reflog); - - git_reflog_read(&g_reflog, g_repo, "HEAD"); - - cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog)); -} diff --git a/tests/refs/reflog/messages.c b/tests/refs/reflog/messages.c deleted file mode 100644 index ed183d2f2..000000000 --- a/tests/refs/reflog/messages.c +++ /dev/null @@ -1,421 +0,0 @@ -#include "clar_libgit2.h" - -#include "futils.h" -#include "git2/reflog.h" -#include "reflog.h" -#include "refs.h" -#include "reflog_helpers.h" - -static const char *g_email = "foo@example.com"; -static git_repository *g_repo; - -/* Fixture setup and teardown */ -void test_refs_reflog_messages__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_set_ident(g_repo, "Foo Bar", g_email)); -} - -void test_refs_reflog_messages__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_refs_reflog_messages__setting_head_updates_reflog(void) -{ - git_object *tag; - git_annotated_commit *annotated; - - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); /* 4 */ - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/unborn")); - cl_git_pass(git_revparse_single(&tag, g_repo, "tags/test")); - cl_git_pass(git_repository_set_head_detached(g_repo, git_object_id(tag))); /* 3 */ - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); /* 2 */ - cl_git_pass(git_repository_set_head(g_repo, "refs/tags/test")); /* 1 */ - cl_git_pass(git_repository_set_head(g_repo, "refs/remotes/test/master")); /* 0 */ - - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 4, - NULL, "refs/heads/haacked", - "foo@example.com", - "checkout: moving from master to haacked"); - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 3, - NULL, "tags/test^{commit}", - "foo@example.com", - "checkout: moving from unborn to e90810b8df3e80c413d903f631643c716887138d"); - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 2, - "tags/test^{commit}", "refs/heads/haacked", - "foo@example.com", - "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to haacked"); - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 1, - "refs/heads/haacked", "tags/test^{commit}", - "foo@example.com", - "checkout: moving from haacked to test"); - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, - "tags/test^{commit}", "refs/remotes/test/master", - "foo@example.com", - "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to test/master"); - - cl_git_pass(git_annotated_commit_from_revspec(&annotated, g_repo, "haacked~0")); - cl_git_pass(git_repository_set_head_detached_from_annotated(g_repo, annotated)); - - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, - NULL, "refs/heads/haacked", - "foo@example.com", - "checkout: moving from be3563ae3f795b2b4353bcce3a527ad0a4f7f644 to haacked~0"); - - git_annotated_commit_free(annotated); - git_object_free(tag); -} - -void test_refs_reflog_messages__setting_head_to_same_target_ignores_reflog(void) -{ - size_t nentries, nentries_after; - - nentries = reflog_entrycount(g_repo, GIT_HEAD_FILE); - - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); - - nentries_after = reflog_entrycount(g_repo, GIT_HEAD_FILE); - - cl_assert_equal_i(nentries + 1, nentries_after); -} - -void test_refs_reflog_messages__detaching_writes_reflog(void) -{ - git_oid id; - const char *msg; - - msg = "checkout: moving from master to e90810b8df3e80c413d903f631643c716887138d"; - git_oid_fromstr(&id, "e90810b8df3e80c413d903f631643c716887138d"); - cl_git_pass(git_repository_set_head_detached(g_repo, &id)); - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "e90810b8df3e80c413d903f631643c716887138d", - NULL, msg); - - msg = "checkout: moving from e90810b8df3e80c413d903f631643c716887138d to haacked"; - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, - "e90810b8df3e80c413d903f631643c716887138d", - "258f0e2a959a364e40ed6603d5d44fbb24765b10", - NULL, msg); -} - -void test_refs_reflog_messages__orphan_branch_does_not_count(void) -{ - git_oid id; - const char *msg; - - /* Have something known */ - msg = "checkout: moving from master to e90810b8df3e80c413d903f631643c716887138d"; - git_oid_fromstr(&id, "e90810b8df3e80c413d903f631643c716887138d"); - cl_git_pass(git_repository_set_head_detached(g_repo, &id)); - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "e90810b8df3e80c413d903f631643c716887138d", - NULL, msg); - - /* Switching to an orphan branch does not write to the reflog */ - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/orphan")); - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "e90810b8df3e80c413d903f631643c716887138d", - NULL, msg); - - /* And coming back, we set the source to zero */ - msg = "checkout: moving from orphan to haacked"; - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/haacked")); - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, - "0000000000000000000000000000000000000000", - "258f0e2a959a364e40ed6603d5d44fbb24765b10", - NULL, msg); -} - -void test_refs_reflog_messages__branch_birth(void) -{ - git_signature *sig; - git_oid id; - git_tree *tree; - git_reference *ref; - const char *msg; - size_t nentries, nentries_after; - - nentries = reflog_entrycount(g_repo, GIT_HEAD_FILE); - - cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); - - cl_git_pass(git_repository_head(&ref, g_repo)); - cl_git_pass(git_reference_peel((git_object **) &tree, ref, GIT_OBJECT_TREE)); - - cl_git_pass(git_repository_set_head(g_repo, "refs/heads/orphan")); - - nentries_after = reflog_entrycount(g_repo, GIT_HEAD_FILE); - - cl_assert_equal_i(nentries, nentries_after); - - msg = "message 2"; - cl_git_pass(git_commit_create(&id, g_repo, "HEAD", sig, sig, NULL, msg, tree, 0, NULL)); - - cl_assert_equal_i(1, reflog_entrycount(g_repo, "refs/heads/orphan")); - - nentries_after = reflog_entrycount(g_repo, GIT_HEAD_FILE); - - cl_assert_equal_i(nentries + 1, nentries_after); - - git_signature_free(sig); - git_tree_free(tree); - git_reference_free(ref); -} - -void test_refs_reflog_messages__commit_on_symbolic_ref_updates_head_reflog(void) -{ - git_signature *sig; - git_oid id; - git_tree *tree; - git_reference *ref1, *ref2; - const char *msg; - size_t nentries_head, nentries_master; - - nentries_head = reflog_entrycount(g_repo, GIT_HEAD_FILE); - - cl_git_pass(git_signature_now(&sig, "me", "foo@example.com")); - - cl_git_pass(git_repository_head(&ref1, g_repo)); - cl_git_pass(git_reference_peel((git_object **) &tree, ref1, GIT_OBJECT_TREE)); - - nentries_master = reflog_entrycount(g_repo, "refs/heads/master"); - - msg = "message 1"; - cl_git_pass(git_reference_symbolic_create(&ref2, g_repo, "refs/heads/master", "refs/heads/foo", 1, msg)); - - cl_assert_equal_i(0, reflog_entrycount(g_repo, "refs/heads/foo")); - cl_assert_equal_i(nentries_head, reflog_entrycount(g_repo, GIT_HEAD_FILE)); - cl_assert_equal_i(nentries_master, reflog_entrycount(g_repo, "refs/heads/master")); - - msg = "message 2"; - cl_git_pass(git_commit_create(&id, g_repo, "HEAD", sig, sig, NULL, msg, tree, 0, NULL)); - - cl_assert_equal_i(1, reflog_entrycount(g_repo, "refs/heads/foo")); - cl_assert_equal_i(nentries_head + 1, reflog_entrycount(g_repo, GIT_HEAD_FILE)); - cl_assert_equal_i(nentries_master, reflog_entrycount(g_repo, "refs/heads/master")); - - git_signature_free(sig); - git_reference_free(ref1); - git_reference_free(ref2); - git_tree_free(tree); -} - -void test_refs_reflog_messages__show_merge_for_merge_commits(void) -{ - git_oid b1_oid; - git_oid b2_oid; - git_oid merge_commit_oid; - git_commit *b1_commit; - git_commit *b2_commit; - git_signature *s; - git_commit *parent_commits[2]; - git_tree *tree; - - cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); - - cl_git_pass(git_reference_name_to_id(&b1_oid, g_repo, "HEAD")); - cl_git_pass(git_reference_name_to_id(&b2_oid, g_repo, "refs/heads/test")); - - cl_git_pass(git_commit_lookup(&b1_commit, g_repo, &b1_oid)); - cl_git_pass(git_commit_lookup(&b2_commit, g_repo, &b2_oid)); - - parent_commits[0] = b1_commit; - parent_commits[1] = b2_commit; - - cl_git_pass(git_commit_tree(&tree, b1_commit)); - - cl_git_pass(git_commit_create(&merge_commit_oid, - g_repo, "HEAD", s, s, NULL, - "Merge commit", tree, - 2, (const struct git_commit **) parent_commits)); - - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, - NULL, - git_oid_tostr_s(&merge_commit_oid), - NULL, "commit (merge): Merge commit"); - - git_tree_free(tree); - git_commit_free(b1_commit); - git_commit_free(b2_commit); - git_signature_free(s); -} - -void test_refs_reflog_messages__creating_a_direct_reference(void) -{ - git_reference *reference; - git_oid id; - git_reflog *reflog; - const git_reflog_entry *entry; - - const char *name = "refs/heads/new-head"; - const char *message = "You've been logged, mate!"; - - cl_git_pass(git_reference_name_to_id(&id, g_repo, "HEAD")); - - cl_git_pass(git_reference_create(&reference, g_repo, name, &id, 0, message)); - - cl_git_pass(git_reflog_read(&reflog, g_repo, name)); - cl_assert_equal_sz(1, git_reflog_entrycount(reflog)); - - entry = git_reflog_entry_byindex(reflog, 0); - cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); - cl_assert_equal_oid(&id, &entry->oid_cur); - cl_assert_equal_s(message, entry->msg); - - git_reflog_free(reflog); - git_reference_free(reference); -} - -void test_refs_reflog_messages__newline_gets_replaced(void) -{ - const git_reflog_entry *entry; - git_signature *signature; - git_reflog *reflog; - git_oid oid; - - cl_git_pass(git_signature_now(&signature, "me", "foo@example.com")); - cl_git_pass(git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); - - cl_git_pass(git_reflog_read(&reflog, g_repo, "HEAD")); - cl_assert_equal_sz(7, git_reflog_entrycount(reflog)); - cl_git_pass(git_reflog_append(reflog, &oid, signature, "inner\nnewline")); - cl_assert_equal_sz(8, git_reflog_entrycount(reflog)); - - cl_assert(entry = git_reflog_entry_byindex(reflog, 0)); - cl_assert_equal_s(git_reflog_entry_message(entry), "inner newline"); - - git_signature_free(signature); - git_reflog_free(reflog); -} - -void test_refs_reflog_messages__renaming_ref(void) -{ - git_reference *ref, *new_ref; - - cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); - cl_git_pass(git_reference_rename(&new_ref, ref, "refs/heads/renamed", false, - "message")); - - cl_reflog_check_entry(g_repo, git_reference_name(new_ref), 0, - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "foo@example.com", "message"); - - git_reference_free(ref); - git_reference_free(new_ref); -} - -void test_refs_reflog_messages__updating_a_direct_reference(void) -{ - git_reference *ref, *ref_out, *target_ref; - git_oid target_id; - const char *message = "You've been logged, mate!"; - - git_reference_name_to_id(&target_id, g_repo, "refs/heads/haacked"); - cl_git_pass(git_reference_lookup(&target_ref, g_repo, "refs/heads/haacked")); - - cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); - - cl_git_pass(git_reference_set_target(&ref_out, ref, &target_id, message)); - - cl_reflog_check_entry(g_repo, "refs/heads/master", 0, - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "258f0e2a959a364e40ed6603d5d44fbb24765b10", - NULL, message); - - git_reference_free(target_ref); - git_reference_free(ref); - git_reference_free(ref_out); -} - -#define NEW_BRANCH_NAME "new-branch-on-the-block" - -void test_refs_reflog_messages__creating_branches_default_messages(void) -{ - git_str buf = GIT_STR_INIT; - git_annotated_commit *annotated; - git_object *obj; - git_commit *target; - git_reference *branch1, *branch2; - - cl_git_pass(git_revparse_single(&obj, g_repo, "e90810b8df3")); - cl_git_pass(git_commit_lookup(&target, g_repo, git_object_id(obj))); - git_object_free(obj); - - cl_git_pass(git_branch_create(&branch1, g_repo, NEW_BRANCH_NAME, target, false)); - - cl_git_pass(git_str_printf(&buf, "branch: Created from %s", git_oid_tostr_s(git_commit_id(target)))); - cl_reflog_check_entry(g_repo, "refs/heads/" NEW_BRANCH_NAME, 0, - GIT_OID_HEX_ZERO, - git_oid_tostr_s(git_commit_id(target)), - g_email, git_str_cstr(&buf)); - - cl_git_pass(git_reference_remove(g_repo, "refs/heads/" NEW_BRANCH_NAME)); - - cl_git_pass(git_annotated_commit_from_revspec(&annotated, g_repo, "e90810b8df3")); - cl_git_pass(git_branch_create_from_annotated(&branch2, g_repo, NEW_BRANCH_NAME, annotated, true)); - - cl_reflog_check_entry(g_repo, "refs/heads/" NEW_BRANCH_NAME, 0, - GIT_OID_HEX_ZERO, - git_oid_tostr_s(git_commit_id(target)), - g_email, "branch: Created from e90810b8df3"); - - git_annotated_commit_free(annotated); - git_str_dispose(&buf); - git_commit_free(target); - git_reference_free(branch1); - git_reference_free(branch2); -} - -void test_refs_reflog_messages__moving_branch_default_message(void) -{ - git_reference *branch; - git_reference *new_branch; - git_oid id; - - cl_git_pass(git_reference_lookup(&branch, g_repo, "refs/heads/master")); - git_oid_cpy(&id, git_reference_target(branch)); - cl_git_pass(git_branch_move(&new_branch, branch, "master2", 0)); - - cl_reflog_check_entry(g_repo, git_reference_name(new_branch), 0, - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - g_email, - "branch: renamed refs/heads/master to refs/heads/master2"); - - git_reference_free(branch); - git_reference_free(new_branch); -} - -void test_refs_reflog_messages__detaching_head_default_message(void) -{ - git_reference *ref; - - cl_assert_equal_i(false, git_repository_head_detached(g_repo)); - - cl_git_pass(git_repository_detach_head(g_repo)); - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - NULL, "checkout: moving from master to a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - cl_assert_equal_i(true, git_repository_head_detached(g_repo)); - - /* take the repo back to its original state */ - cl_git_pass(git_reference_symbolic_create(&ref, g_repo, "HEAD", "refs/heads/master", - true, "REATTACH")); - - cl_reflog_check_entry(g_repo, GIT_HEAD_FILE, 0, - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - NULL, "REATTACH"); - - cl_assert_equal_i(false, git_repository_head_detached(g_repo)); - - git_reference_free(ref); -} diff --git a/tests/refs/reflog/reflog.c b/tests/refs/reflog/reflog.c deleted file mode 100644 index 32ce7ffb7..000000000 --- a/tests/refs/reflog/reflog.c +++ /dev/null @@ -1,469 +0,0 @@ -#include "clar_libgit2.h" - -#include "futils.h" -#include "git2/reflog.h" -#include "reflog.h" - -static const char *new_ref = "refs/heads/test-reflog"; -static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; -#define commit_msg "commit: bla bla" - -static git_repository *g_repo; - - -/* helpers */ -static void assert_signature(const git_signature *expected, const git_signature *actual) -{ - cl_assert(actual); - cl_assert_equal_s(expected->name, actual->name); - cl_assert_equal_s(expected->email, actual->email); - cl_assert(expected->when.offset == actual->when.offset); - cl_assert(expected->when.time == actual->when.time); -} - - -/* Fixture setup and teardown */ -void test_refs_reflog_reflog__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_refs_reflog_reflog__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void assert_appends(const git_signature *committer, const git_oid *oid) -{ - git_repository *repo2; - git_reference *lookedup_ref; - git_reflog *reflog; - const git_reflog_entry *entry; - - /* Reopen a new instance of the repository */ - cl_git_pass(git_repository_open(&repo2, "testrepo.git")); - - /* Lookup the previously created branch */ - cl_git_pass(git_reference_lookup(&lookedup_ref, repo2, new_ref)); - - /* Read and parse the reflog for this branch */ - cl_git_pass(git_reflog_read(&reflog, repo2, new_ref)); - cl_assert_equal_i(3, (int)git_reflog_entrycount(reflog)); - - /* The first one was the creation of the branch */ - entry = git_reflog_entry_byindex(reflog, 2); - cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); - - entry = git_reflog_entry_byindex(reflog, 1); - assert_signature(committer, entry->committer); - cl_assert(git_oid_cmp(oid, &entry->oid_old) == 0); - cl_assert(git_oid_cmp(oid, &entry->oid_cur) == 0); - cl_assert(entry->msg == NULL); - - entry = git_reflog_entry_byindex(reflog, 0); - assert_signature(committer, entry->committer); - cl_assert(git_oid_cmp(oid, &entry->oid_cur) == 0); - cl_assert_equal_s(commit_msg, entry->msg); - - git_reflog_free(reflog); - git_repository_free(repo2); - - git_reference_free(lookedup_ref); -} - -void test_refs_reflog_reflog__append_then_read(void) -{ - /* write a reflog for a given reference and ensure it can be read back */ - git_reference *ref; - git_oid oid; - git_signature *committer; - git_reflog *reflog; - - /* Create a new branch pointing at the HEAD */ - git_oid_fromstr(&oid, current_master_tip); - cl_git_pass(git_reference_create(&ref, g_repo, new_ref, &oid, 0, NULL)); - git_reference_free(ref); - - cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); - - cl_git_pass(git_reflog_read(&reflog, g_repo, new_ref)); - cl_git_pass(git_reflog_append(reflog, &oid, committer, NULL)); - cl_git_pass(git_reflog_append(reflog, &oid, committer, commit_msg "\n")); - cl_git_pass(git_reflog_write(reflog)); - - assert_appends(committer, &oid); - - git_reflog_free(reflog); - git_signature_free(committer); -} - -void test_refs_reflog_reflog__renaming_the_reference_moves_the_reflog(void) -{ - git_reference *master, *new_master; - git_str master_log_path = GIT_STR_INIT, moved_log_path = GIT_STR_INIT; - - git_str_joinpath(&master_log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); - git_str_puts(&moved_log_path, git_str_cstr(&master_log_path)); - git_str_joinpath(&master_log_path, git_str_cstr(&master_log_path), "refs/heads/master"); - git_str_joinpath(&moved_log_path, git_str_cstr(&moved_log_path), "refs/moved"); - - cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&master_log_path))); - cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&moved_log_path))); - - cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); - cl_git_pass(git_reference_rename(&new_master, master, "refs/moved", 0, NULL)); - git_reference_free(master); - - cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&master_log_path))); - cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&moved_log_path))); - - git_reference_free(new_master); - git_str_dispose(&moved_log_path); - git_str_dispose(&master_log_path); -} - -void test_refs_reflog_reflog__deleting_the_reference_deletes_the_reflog(void) -{ - git_reference *master; - git_str master_log_path = GIT_STR_INIT; - - git_str_joinpath(&master_log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); - git_str_joinpath(&master_log_path, git_str_cstr(&master_log_path), "refs/heads/master"); - - cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&master_log_path))); - - cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); - cl_git_pass(git_reference_delete(master)); - git_reference_free(master); - - cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&master_log_path))); - git_str_dispose(&master_log_path); -} - -void test_refs_reflog_reflog__removes_empty_reflog_dir(void) -{ - git_reference *ref; - git_str log_path = GIT_STR_INIT; - git_oid id; - - /* Create a new branch pointing at the HEAD */ - git_oid_fromstr(&id, current_master_tip); - cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/new-dir/new-head", &id, 0, NULL)); - - git_str_joinpath(&log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); - git_str_joinpath(&log_path, git_str_cstr(&log_path), "refs/heads/new-dir/new-head"); - - cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&log_path))); - - cl_git_pass(git_reference_delete(ref)); - git_reference_free(ref); - - /* new ref creation should succeed since new-dir is empty */ - git_oid_fromstr(&id, current_master_tip); - cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/new-dir", &id, 0, NULL)); - git_reference_free(ref); - - git_str_dispose(&log_path); -} - -void test_refs_reflog_reflog__fails_gracefully_on_nonempty_reflog_dir(void) -{ - git_reference *ref; - git_str log_path = GIT_STR_INIT; - git_oid id; - - /* Create a new branch pointing at the HEAD */ - git_oid_fromstr(&id, current_master_tip); - cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/new-dir/new-head", &id, 0, NULL)); - git_reference_free(ref); - - git_str_joinpath(&log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); - git_str_joinpath(&log_path, git_str_cstr(&log_path), "refs/heads/new-dir/new-head"); - - cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&log_path))); - - /* delete the ref manually, leave the reflog */ - cl_must_pass(p_unlink("testrepo.git/refs/heads/new-dir/new-head")); - - /* new ref creation should fail since new-dir contains reflogs still */ - git_oid_fromstr(&id, current_master_tip); - cl_git_fail_with(GIT_EDIRECTORY, git_reference_create(&ref, g_repo, "refs/heads/new-dir", &id, 0, NULL)); - git_reference_free(ref); - - git_str_dispose(&log_path); -} - -static void assert_has_reflog(bool expected_result, const char *name) -{ - cl_assert_equal_i(expected_result, git_reference_has_log(g_repo, name)); -} - -void test_refs_reflog_reflog__reference_has_reflog(void) -{ - assert_has_reflog(true, "HEAD"); - assert_has_reflog(true, "refs/heads/master"); - assert_has_reflog(false, "refs/heads/subtrees"); -} - -void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_returns_an_empty_one(void) -{ - git_reflog *reflog; - const char *refname = "refs/heads/subtrees"; - git_str subtrees_log_path = GIT_STR_INIT; - - git_str_join_n(&subtrees_log_path, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname); - cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&subtrees_log_path))); - - cl_git_pass(git_reflog_read(&reflog, g_repo, refname)); - - cl_assert_equal_i(0, (int)git_reflog_entrycount(reflog)); - - git_reflog_free(reflog); - git_str_dispose(&subtrees_log_path); -} - -void test_refs_reflog_reflog__reading_a_reflog_with_invalid_format_succeeds(void) -{ - git_reflog *reflog; - const char *refname = "refs/heads/newline"; - const char *refmessage = - "Reflog*message with a newline and enough content after it to pass the GIT_REFLOG_SIZE_MIN check inside reflog_parse."; - const git_reflog_entry *entry; - git_reference *ref; - git_oid id; - git_str logpath = GIT_STR_INIT, logcontents = GIT_STR_INIT; - char *star; - - /* Create a new branch. */ - cl_git_pass(git_oid_fromstr(&id, current_master_tip)); - cl_git_pass(git_reference_create(&ref, g_repo, refname, &id, 1, refmessage)); - - /* - * Corrupt the branch reflog by introducing a newline inside the reflog message. - * We do this by replacing '*' with '\n' - */ - cl_git_pass(git_str_join_n(&logpath, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname)); - cl_git_pass(git_futils_readbuffer(&logcontents, git_str_cstr(&logpath))); - cl_assert((star = strchr(git_str_cstr(&logcontents), '*')) != NULL); - *star = '\n'; - cl_git_rewritefile(git_str_cstr(&logpath), git_str_cstr(&logcontents)); - - /* - * Confirm that the file was rewritten successfully - * and now contains a '\n' in the expected location - */ - cl_git_pass(git_futils_readbuffer(&logcontents, git_str_cstr(&logpath))); - cl_assert(strstr(git_str_cstr(&logcontents), "Reflog\nmessage") != NULL); - - cl_git_pass(git_reflog_read(&reflog, g_repo, refname)); - cl_assert(entry = git_reflog_entry_byindex(reflog, 0)); - cl_assert_equal_s(git_reflog_entry_message(entry), "Reflog"); - - git_reference_free(ref); - git_reflog_free(reflog); - git_str_dispose(&logpath); - git_str_dispose(&logcontents); -} - -void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void) -{ - git_reference *master, *new_master; - git_str master_log_path = GIT_STR_INIT, moved_log_path = GIT_STR_INIT; - git_reflog *reflog; - - cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); - cl_git_pass(git_reflog_read(&reflog, g_repo, "refs/heads/master")); - - cl_git_pass(git_reflog_write(reflog)); - - cl_git_pass(git_reference_rename(&new_master, master, "refs/moved", 0, NULL)); - git_reference_free(master); - - cl_git_fail(git_reflog_write(reflog)); - - git_reflog_free(reflog); - git_reference_free(new_master); - git_str_dispose(&moved_log_path); - git_str_dispose(&master_log_path); -} - -void test_refs_reflog_reflog__renaming_with_an_invalid_name_returns_EINVALIDSPEC(void) -{ - cl_assert_equal_i(GIT_EINVALIDSPEC, - git_reflog_rename(g_repo, "refs/heads/master", "refs/heads/Inv@{id")); -} - -void test_refs_reflog_reflog__write_only_std_locations(void) -{ - git_reference *ref; - git_oid id; - - git_oid_fromstr(&id, current_master_tip); - - cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/foo", &id, 1, NULL)); - git_reference_free(ref); - cl_git_pass(git_reference_create(&ref, g_repo, "refs/tags/foo", &id, 1, NULL)); - git_reference_free(ref); - cl_git_pass(git_reference_create(&ref, g_repo, "refs/notes/foo", &id, 1, NULL)); - git_reference_free(ref); - - assert_has_reflog(true, "refs/heads/foo"); - assert_has_reflog(false, "refs/tags/foo"); - assert_has_reflog(true, "refs/notes/foo"); - -} - -void test_refs_reflog_reflog__write_when_explicitly_active(void) -{ - git_reference *ref; - git_oid id; - - git_oid_fromstr(&id, current_master_tip); - git_reference_ensure_log(g_repo, "refs/tags/foo"); - - cl_git_pass(git_reference_create(&ref, g_repo, "refs/tags/foo", &id, 1, NULL)); - git_reference_free(ref); - assert_has_reflog(true, "refs/tags/foo"); -} - -void test_refs_reflog_reflog__append_to_HEAD_when_changing_current_branch(void) -{ - size_t nlogs, nlogs_after; - git_reference *ref; - git_reflog *log; - git_oid id; - - cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); - nlogs = git_reflog_entrycount(log); - git_reflog_free(log); - - /* Move it back */ - git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/master", &id, 1, NULL)); - git_reference_free(ref); - - cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); - nlogs_after = git_reflog_entrycount(log); - git_reflog_free(log); - - cl_assert_equal_i(nlogs_after, nlogs + 1); -} - -void test_refs_reflog_reflog__do_not_append_when_no_update(void) -{ - size_t nlogs, nlogs_after; - git_reference *ref, *ref2; - git_reflog *log; - - cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); - nlogs = git_reflog_entrycount(log); - git_reflog_free(log); - - cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); - cl_git_pass(git_reference_create(&ref2, g_repo, "refs/heads/master", - git_reference_target(ref), 1, NULL)); - - git_reference_free(ref); - git_reference_free(ref2); - - cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); - nlogs_after = git_reflog_entrycount(log); - git_reflog_free(log); - - cl_assert_equal_i(nlogs_after, nlogs); -} - -static void assert_no_reflog_update(void) -{ - size_t nlogs, nlogs_after; - size_t nlogs_master, nlogs_master_after; - git_reference *ref; - git_reflog *log; - git_oid id; - - cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); - nlogs = git_reflog_entrycount(log); - git_reflog_free(log); - - cl_git_pass(git_reflog_read(&log, g_repo, "refs/heads/master")); - nlogs_master = git_reflog_entrycount(log); - git_reflog_free(log); - - /* Move it back */ - git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/master", &id, 1, NULL)); - git_reference_free(ref); - - cl_git_pass(git_reflog_read(&log, g_repo, "HEAD")); - nlogs_after = git_reflog_entrycount(log); - git_reflog_free(log); - - cl_assert_equal_i(nlogs_after, nlogs); - - cl_git_pass(git_reflog_read(&log, g_repo, "refs/heads/master")); - nlogs_master_after = git_reflog_entrycount(log); - git_reflog_free(log); - - cl_assert_equal_i(nlogs_after, nlogs); - cl_assert_equal_i(nlogs_master_after, nlogs_master); - -} - -void test_refs_reflog_reflog__logallrefupdates_bare_set_false(void) -{ - git_config *config; - - cl_git_pass(git_repository_config(&config, g_repo)); - cl_git_pass(git_config_set_bool(config, "core.logallrefupdates", false)); - git_config_free(config); - - assert_no_reflog_update(); -} - -void test_refs_reflog_reflog__logallrefupdates_bare_set_always(void) -{ - git_config *config; - git_reference *ref; - git_reflog *log; - git_oid id; - - cl_git_pass(git_repository_config(&config, g_repo)); - cl_git_pass(git_config_set_string(config, "core.logallrefupdates", "always")); - git_config_free(config); - - git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - cl_git_pass(git_reference_create(&ref, g_repo, "refs/bork", &id, 1, "message")); - - cl_git_pass(git_reflog_read(&log, g_repo, "refs/bork")); - cl_assert_equal_i(1, git_reflog_entrycount(log)); - cl_assert_equal_s("message", git_reflog_entry_byindex(log, 0)->msg); - - git_reflog_free(log); - git_reference_free(ref); -} - -void test_refs_reflog_reflog__logallrefupdates_bare_unset(void) -{ - git_config *config; - - cl_git_pass(git_repository_config(&config, g_repo)); - cl_git_pass(git_config_delete_entry(config, "core.logallrefupdates")); - git_config_free(config); - - assert_no_reflog_update(); -} - -void test_refs_reflog_reflog__logallrefupdates_nonbare_set_false(void) -{ - git_config *config; - - cl_git_sandbox_cleanup(); - g_repo = cl_git_sandbox_init("testrepo"); - - - cl_git_pass(git_repository_config(&config, g_repo)); - cl_git_pass(git_config_set_bool(config, "core.logallrefupdates", false)); - git_config_free(config); - - assert_no_reflog_update(); -} diff --git a/tests/refs/reflog/reflog_helpers.c b/tests/refs/reflog/reflog_helpers.c deleted file mode 100644 index 2ea41ee06..000000000 --- a/tests/refs/reflog/reflog_helpers.c +++ /dev/null @@ -1,120 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" -#include "reflog.h" -#include "reflog_helpers.h" - -int reflog_entry_tostr(git_str *out, const git_reflog_entry *entry) -{ - char old_oid[GIT_OID_HEXSZ], new_oid[GIT_OID_HEXSZ]; - - assert(out && entry); - - git_oid_tostr((char *)&old_oid, GIT_OID_HEXSZ, git_reflog_entry_id_old(entry)); - git_oid_tostr((char *)&new_oid, GIT_OID_HEXSZ, git_reflog_entry_id_new(entry)); - - return git_str_printf(out, "%s %s %s %s", old_oid, new_oid, "somesig", git_reflog_entry_message(entry)); -} - -size_t reflog_entrycount(git_repository *repo, const char *name) -{ - git_reflog *log; - size_t ret; - - cl_git_pass(git_reflog_read(&log, repo, name)); - ret = git_reflog_entrycount(log); - git_reflog_free(log); - - return ret; -} - -void cl_reflog_check_entry_(git_repository *repo, const char *reflog, size_t idx, - const char *old_spec, const char *new_spec, - const char *email, const char *message, - const char *file, const char *func, int line) -{ - git_reflog *log; - const git_reflog_entry *entry; - git_str result = GIT_STR_INIT; - - cl_git_pass(git_reflog_read(&log, repo, reflog)); - entry = git_reflog_entry_byindex(log, idx); - if (entry == NULL) - clar__fail(file, func, line, "Reflog has no such entry", NULL, 1); - - if (old_spec) { - git_object *obj = NULL; - if (git_revparse_single(&obj, repo, old_spec) == GIT_OK) { - if (git_oid_cmp(git_object_id(obj), git_reflog_entry_id_old(entry)) != 0) { - git_oid__writebuf(&result, "\tOld OID: \"", git_object_id(obj)); - git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_old(entry)); - git_str_puts(&result, "\"\n"); - } - git_object_free(obj); - } else { - git_oid *oid = git__calloc(1, sizeof(*oid)); - git_oid_fromstr(oid, old_spec); - if (git_oid_cmp(oid, git_reflog_entry_id_old(entry)) != 0) { - git_oid__writebuf(&result, "\tOld OID: \"", oid); - git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_old(entry)); - git_str_puts(&result, "\"\n"); - } - git__free(oid); - } - } - if (new_spec) { - git_object *obj = NULL; - if (git_revparse_single(&obj, repo, new_spec) == GIT_OK) { - if (git_oid_cmp(git_object_id(obj), git_reflog_entry_id_new(entry)) != 0) { - git_oid__writebuf(&result, "\tNew OID: \"", git_object_id(obj)); - git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_new(entry)); - git_str_puts(&result, "\"\n"); - } - git_object_free(obj); - } else { - git_oid *oid = git__calloc(1, sizeof(*oid)); - git_oid_fromstr(oid, new_spec); - if (git_oid_cmp(oid, git_reflog_entry_id_new(entry)) != 0) { - git_oid__writebuf(&result, "\tNew OID: \"", oid); - git_oid__writebuf(&result, "\" != \"", git_reflog_entry_id_new(entry)); - git_str_puts(&result, "\"\n"); - } - git__free(oid); - } - } - - if (email && strcmp(email, git_reflog_entry_committer(entry)->email) != 0) - git_str_printf(&result, "\tEmail: \"%s\" != \"%s\"\n", email, git_reflog_entry_committer(entry)->email); - - if (message) { - const char *entry_msg = git_reflog_entry_message(entry); - if (entry_msg == NULL) entry_msg = ""; - - if (entry_msg && strcmp(message, entry_msg) != 0) - git_str_printf(&result, "\tMessage: \"%s\" != \"%s\"\n", message, entry_msg); - } - if (git_str_len(&result) != 0) - clar__fail(file, func, line, "Reflog entry mismatch", git_str_cstr(&result), 1); - - git_str_dispose(&result); - git_reflog_free(log); -} - -void reflog_print(git_repository *repo, const char *reflog_name) -{ - git_reflog *reflog; - size_t idx; - git_str out = GIT_STR_INIT; - - git_reflog_read(&reflog, repo, reflog_name); - - for (idx = 0; idx < git_reflog_entrycount(reflog); idx++) { - const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, idx); - reflog_entry_tostr(&out, entry); - git_str_putc(&out, '\n'); - } - - fprintf(stderr, "%s", git_str_cstr(&out)); - git_str_dispose(&out); - git_reflog_free(reflog); -} diff --git a/tests/refs/reflog/reflog_helpers.h b/tests/refs/reflog/reflog_helpers.h deleted file mode 100644 index 4cd92cadc..000000000 --- a/tests/refs/reflog/reflog_helpers.h +++ /dev/null @@ -1,12 +0,0 @@ -size_t reflog_entrycount(git_repository *repo, const char *name); - -#define cl_reflog_check_entry(repo, reflog, idx, old_spec, new_spec, email, message) \ - cl_reflog_check_entry_(repo, reflog, idx, old_spec, new_spec, email, message, __FILE__, __FUNCTION__, __LINE__) - -void cl_reflog_check_entry_(git_repository *repo, const char *reflog, size_t idx, - const char *old_spec, const char *new_spec, - const char *email, const char *message, - const char *file, const char *func, int line); - -void reflog_print(git_repository *repo, const char *reflog_name); -int reflog_entry_tostr(git_str *out, const git_reflog_entry *entry); diff --git a/tests/refs/rename.c b/tests/refs/rename.c deleted file mode 100644 index f71e65782..000000000 --- a/tests/refs/rename.c +++ /dev/null @@ -1,368 +0,0 @@ -#include "clar_libgit2.h" - -#include "futils.h" -#include "git2/reflog.h" -#include "reflog.h" -#include "refs.h" -#include "ref_helpers.h" - -static const char *loose_tag_ref_name = "refs/tags/e90810b"; -static const char *packed_head_name = "refs/heads/packed"; -static const char *packed_test_head_name = "refs/heads/packed-test"; -static const char *ref_one_name = "refs/heads/one/branch"; -static const char *ref_one_name_new = "refs/heads/two/branch"; -static const char *ref_two_name = "refs/heads/two"; -static const char *ref_master_name = "refs/heads/master"; -static const char *ref_two_name_new = "refs/heads/two/two"; - -static git_repository *g_repo; - - - -void test_refs_rename__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); - cl_git_pass(git_repository_set_ident(g_repo, "me", "foo@example.com")); -} - -void test_refs_rename__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - - - -void test_refs_rename__loose(void) -{ - /* rename a loose reference */ - git_reference *looked_up_ref, *new_ref, *another_looked_up_ref; - git_str temp_path = GIT_STR_INIT; - const char *new_name = "refs/tags/Nemo/knows/refs.kung-fu"; - - /* Ensure the ref doesn't exist on the file system */ - cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), new_name)); - cl_assert(!git_fs_path_exists(temp_path.ptr)); - - /* Retrieval of the reference to rename */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, loose_tag_ref_name)); - - /* ... which is indeed loose */ - cl_assert(reference_is_packed(looked_up_ref) == 0); - - /* Now that the reference is renamed... */ - cl_git_pass(git_reference_rename(&new_ref, looked_up_ref, new_name, 0, NULL)); - cl_assert_equal_s(new_ref->name, new_name); - git_reference_free(looked_up_ref); - - /* ...It can't be looked-up with the old name... */ - cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, loose_tag_ref_name)); - - /* ...but the new name works ok... */ - cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, new_name)); - cl_assert_equal_s(new_ref->name, new_name); - - /* .. the new ref is loose... */ - cl_assert(reference_is_packed(another_looked_up_ref) == 0); - cl_assert(reference_is_packed(new_ref) == 0); - - /* ...and the ref can be found in the file system */ - cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), new_name)); - cl_assert(git_fs_path_exists(temp_path.ptr)); - - git_reference_free(new_ref); - git_reference_free(another_looked_up_ref); - git_str_dispose(&temp_path); -} - -void test_refs_rename__packed(void) -{ - /* rename a packed reference (should make it loose) */ - git_reference *looked_up_ref, *new_ref, *another_looked_up_ref; - git_str temp_path = GIT_STR_INIT; - const char *brand_new_name = "refs/heads/brand_new_name"; - - /* Ensure the ref doesn't exist on the file system */ - cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), packed_head_name)); - cl_assert(!git_fs_path_exists(temp_path.ptr)); - - /* The reference can however be looked-up... */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); - - /* .. and it's packed */ - cl_assert(reference_is_packed(looked_up_ref) != 0); - - /* Now that the reference is renamed... */ - cl_git_pass(git_reference_rename(&new_ref, looked_up_ref, brand_new_name, 0, NULL)); - cl_assert_equal_s(new_ref->name, brand_new_name); - git_reference_free(looked_up_ref); - - /* ...It can't be looked-up with the old name... */ - cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, packed_head_name)); - - /* ...but the new name works ok... */ - cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, brand_new_name)); - cl_assert_equal_s(another_looked_up_ref->name, brand_new_name); - - /* .. the ref is no longer packed... */ - cl_assert(reference_is_packed(another_looked_up_ref) == 0); - cl_assert(reference_is_packed(new_ref) == 0); - - /* ...and the ref now happily lives in the file system */ - cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), brand_new_name)); - cl_assert(git_fs_path_exists(temp_path.ptr)); - - git_reference_free(new_ref); - git_reference_free(another_looked_up_ref); - git_str_dispose(&temp_path); -} - -void test_refs_rename__packed_doesnt_pack_others(void) -{ - /* renaming a packed reference does not pack another reference which happens to be in both loose and pack state */ - git_reference *looked_up_ref, *another_looked_up_ref, *renamed_ref; - git_str temp_path = GIT_STR_INIT; - const char *brand_new_name = "refs/heads/brand_new_name"; - - /* Ensure the other reference exists on the file system */ - cl_git_pass(git_str_joinpath(&temp_path, git_repository_path(g_repo), packed_test_head_name)); - cl_assert(git_fs_path_exists(temp_path.ptr)); - - /* Lookup the other reference */ - cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name)); - - /* Ensure it's loose */ - cl_assert(reference_is_packed(another_looked_up_ref) == 0); - git_reference_free(another_looked_up_ref); - - /* Lookup the reference to rename */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); - - /* Ensure it's packed */ - cl_assert(reference_is_packed(looked_up_ref) != 0); - - /* Now that the reference is renamed... */ - cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, brand_new_name, 0, NULL)); - git_reference_free(looked_up_ref); - - /* Lookup the other reference */ - cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name)); - - /* Ensure it's loose */ - cl_assert(reference_is_packed(another_looked_up_ref) == 0); - - /* Ensure the other ref still exists on the file system */ - cl_assert(git_fs_path_exists(temp_path.ptr)); - - git_reference_free(renamed_ref); - git_reference_free(another_looked_up_ref); - git_str_dispose(&temp_path); -} - -void test_refs_rename__name_collision(void) -{ - /* can not rename a reference with the name of an existing reference */ - git_reference *looked_up_ref, *renamed_ref; - - /* An existing reference... */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); - - /* Can not be renamed to the name of another existing reference. */ - cl_git_fail(git_reference_rename(&renamed_ref, looked_up_ref, packed_test_head_name, 0, NULL)); - git_reference_free(looked_up_ref); - - /* Failure to rename it hasn't corrupted its state */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); - cl_assert_equal_s(looked_up_ref->name, packed_head_name); - - git_reference_free(looked_up_ref); -} - -void test_refs_rename__invalid_name(void) -{ - /* can not rename a reference with an invalid name */ - git_reference *looked_up_ref, *renamed_ref; - - /* An existing oid reference... */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); - - /* Can not be renamed with an invalid name. */ - cl_assert_equal_i( - GIT_EINVALIDSPEC, - git_reference_rename(&renamed_ref, looked_up_ref, "Hello! I'm a very invalid name.", 0, NULL)); - - /* Can not be renamed outside of the refs hierarchy - * unless it's ALL_CAPS_AND_UNDERSCORES. - */ - cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_rename(&renamed_ref, looked_up_ref, "i-will-sudo-you", 0, NULL)); - - /* Failure to rename it hasn't corrupted its state */ - git_reference_free(looked_up_ref); - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); - cl_assert_equal_s(looked_up_ref->name, packed_test_head_name); - - git_reference_free(looked_up_ref); -} - -void test_refs_rename__force_loose_packed(void) -{ - /* can force-rename a packed reference with the name of an existing loose and packed reference */ - git_reference *looked_up_ref, *renamed_ref; - git_oid oid; - - /* An existing reference... */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); - git_oid_cpy(&oid, git_reference_target(looked_up_ref)); - - /* Can be force-renamed to the name of another existing reference. */ - cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, packed_test_head_name, 1, NULL)); - git_reference_free(looked_up_ref); - git_reference_free(renamed_ref); - - /* Check we actually renamed it */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); - cl_assert_equal_s(looked_up_ref->name, packed_test_head_name); - cl_assert_equal_oid(&oid, git_reference_target(looked_up_ref)); - git_reference_free(looked_up_ref); - - /* And that the previous one doesn't exist any longer */ - cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); -} - -void test_refs_rename__force_loose(void) -{ - /* can force-rename a loose reference with the name of an existing loose reference */ - git_reference *looked_up_ref, *renamed_ref; - git_oid oid; - - /* An existing reference... */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, "refs/heads/br2")); - git_oid_cpy(&oid, git_reference_target(looked_up_ref)); - - /* Can be force-renamed to the name of another existing reference. */ - cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, "refs/heads/test", 1, NULL)); - git_reference_free(looked_up_ref); - git_reference_free(renamed_ref); - - /* Check we actually renamed it */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, "refs/heads/test")); - cl_assert_equal_s(looked_up_ref->name, "refs/heads/test"); - cl_assert_equal_oid(&oid, git_reference_target(looked_up_ref)); - git_reference_free(looked_up_ref); - - /* And that the previous one doesn't exist any longer */ - cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, "refs/heads/br2")); - - git_reference_free(looked_up_ref); -} - - -void test_refs_rename__overwrite(void) -{ - /* can not overwrite name of existing reference */ - git_reference *ref, *ref_one, *ref_one_new, *ref_two; - git_refdb *refdb; - git_oid id; - - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); - cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); - - git_oid_cpy(&id, git_reference_target(ref)); - - /* Create loose references */ - cl_git_pass(git_reference_create(&ref_one, g_repo, ref_one_name, &id, 0, NULL)); - cl_git_pass(git_reference_create(&ref_two, g_repo, ref_two_name, &id, 0, NULL)); - - /* Pack everything */ - cl_git_pass(git_repository_refdb(&refdb, g_repo)); - cl_git_pass(git_refdb_compress(refdb)); - - /* Attempt to create illegal reference */ - cl_git_fail(git_reference_create(&ref_one_new, g_repo, ref_one_name_new, &id, 0, NULL)); - - /* Illegal reference couldn't be created so this is supposed to fail */ - cl_git_fail(git_reference_lookup(&ref_one_new, g_repo, ref_one_name_new)); - - git_reference_free(ref); - git_reference_free(ref_one); - git_reference_free(ref_one_new); - git_reference_free(ref_two); - git_refdb_free(refdb); -} - - -void test_refs_rename__prefix(void) -{ - /* can be renamed to a new name prefixed with the old name */ - git_reference *ref, *ref_two, *looked_up_ref, *renamed_ref; - git_oid id; - - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); - cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); - - git_oid_cpy(&id, git_reference_target(ref)); - - /* Create loose references */ - cl_git_pass(git_reference_create(&ref_two, g_repo, ref_two_name, &id, 0, NULL)); - - /* An existing reference... */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name)); - - /* Can be rename to a new name starting with the old name. */ - cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, ref_two_name_new, 0, NULL)); - git_reference_free(looked_up_ref); - git_reference_free(renamed_ref); - - /* Check we actually renamed it */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new)); - cl_assert_equal_s(looked_up_ref->name, ref_two_name_new); - git_reference_free(looked_up_ref); - cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name)); - - git_reference_free(ref); - git_reference_free(ref_two); - git_reference_free(looked_up_ref); -} - -void test_refs_rename__move_up(void) -{ - /* can move a reference to a upper reference hierarchy */ - git_reference *ref, *ref_two, *looked_up_ref, *renamed_ref; - git_oid id; - - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); - cl_assert(git_reference_type(ref) & GIT_REFERENCE_DIRECT); - - git_oid_cpy(&id, git_reference_target(ref)); - - /* Create loose references */ - cl_git_pass(git_reference_create(&ref_two, g_repo, ref_two_name_new, &id, 0, NULL)); - git_reference_free(ref_two); - - /* An existing reference... */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new)); - - /* Can be renamed upward the reference tree. */ - cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, ref_two_name, 0, NULL)); - git_reference_free(looked_up_ref); - git_reference_free(renamed_ref); - - /* Check we actually renamed it */ - cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name)); - cl_assert_equal_s(looked_up_ref->name, ref_two_name); - git_reference_free(looked_up_ref); - - cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new)); - git_reference_free(ref); - git_reference_free(looked_up_ref); -} - -void test_refs_rename__propagate_eexists(void) -{ - git_reference *ref, *new_ref; - - cl_git_pass(git_reference_lookup(&ref, g_repo, packed_head_name)); - - cl_assert_equal_i(GIT_EEXISTS, git_reference_rename(&new_ref, ref, packed_test_head_name, 0, NULL)); - - git_reference_free(ref); -} diff --git a/tests/refs/revparse.c b/tests/refs/revparse.c deleted file mode 100644 index 0bd2ae5bc..000000000 --- a/tests/refs/revparse.c +++ /dev/null @@ -1,890 +0,0 @@ -#include "clar_libgit2.h" - -#include "git2/revparse.h" -#include "refs.h" -#include "path.h" - -static git_repository *g_repo; -static git_object *g_obj; - -/* Helpers */ -static void test_object_and_ref_inrepo( - const char *spec, - const char *expected_oid, - const char *expected_refname, - git_repository *repo, - bool assert_reference_retrieval) -{ - char objstr[64] = {0}; - git_object *obj = NULL; - git_reference *ref = NULL; - int error; - - error = git_revparse_ext(&obj, &ref, repo, spec); - - if (expected_oid != NULL) { - cl_git_pass(error); - git_oid_fmt(objstr, git_object_id(obj)); - cl_assert_equal_s(objstr, expected_oid); - } else - cl_git_fail(error); - - if (assert_reference_retrieval) { - if (expected_refname == NULL) - cl_assert(NULL == ref); - else - cl_assert_equal_s(expected_refname, git_reference_name(ref)); - } - - git_object_free(obj); - git_reference_free(ref); -} - -static void test_object_inrepo(const char *spec, const char *expected_oid, git_repository *repo) -{ - test_object_and_ref_inrepo(spec, expected_oid, NULL, repo, false); -} - -static void test_id_inrepo( - const char *spec, - const char *expected_left, - const char *expected_right, - git_revspec_t expected_flags, - git_repository *repo) -{ - git_revspec revspec; - int error = git_revparse(&revspec, repo, spec); - - if (expected_left) { - char str[64] = {0}; - cl_assert_equal_i(0, error); - git_oid_fmt(str, git_object_id(revspec.from)); - cl_assert_equal_s(str, expected_left); - git_object_free(revspec.from); - } else { - cl_assert_equal_i(GIT_ENOTFOUND, error); - } - - if (expected_right) { - char str[64] = {0}; - git_oid_fmt(str, git_object_id(revspec.to)); - cl_assert_equal_s(str, expected_right); - git_object_free(revspec.to); - } - - if (expected_flags) - cl_assert_equal_i(expected_flags, revspec.flags); -} - -static void test_object(const char *spec, const char *expected_oid) -{ - test_object_inrepo(spec, expected_oid, g_repo); -} - -static void test_object_and_ref(const char *spec, const char *expected_oid, const char *expected_refname) -{ - test_object_and_ref_inrepo(spec, expected_oid, expected_refname, g_repo, true); -} - -static void test_rangelike(const char *rangelike, - const char *expected_left, - const char *expected_right, - git_revspec_t expected_revparseflags) -{ - char objstr[64] = {0}; - git_revspec revspec; - int error; - - error = git_revparse(&revspec, g_repo, rangelike); - - if (expected_left != NULL) { - cl_assert_equal_i(0, error); - cl_assert_equal_i(revspec.flags, expected_revparseflags); - git_oid_fmt(objstr, git_object_id(revspec.from)); - cl_assert_equal_s(objstr, expected_left); - git_oid_fmt(objstr, git_object_id(revspec.to)); - cl_assert_equal_s(objstr, expected_right); - } else - cl_assert(error != 0); - - git_object_free(revspec.from); - git_object_free(revspec.to); -} - - -static void test_id( - const char *spec, - const char *expected_left, - const char *expected_right, - git_revspec_t expected_flags) -{ - test_id_inrepo(spec, expected_left, expected_right, expected_flags, g_repo); -} - -static void test_invalid_revspec(const char* invalid_spec) -{ - git_revspec revspec; - - cl_assert_equal_i( - GIT_EINVALIDSPEC, git_revparse(&revspec, g_repo, invalid_spec)); -} - -void test_refs_revparse__initialize(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); -} - -void test_refs_revparse__cleanup(void) -{ - git_repository_free(g_repo); -} - -void test_refs_revparse__nonexistant_object(void) -{ - test_object("this-does-not-exist", NULL); - test_object("this-does-not-exist^1", NULL); - test_object("this-does-not-exist~2", NULL); -} - -static void assert_invalid_single_spec(const char *invalid_spec) -{ - cl_assert_equal_i( - GIT_EINVALIDSPEC, git_revparse_single(&g_obj, g_repo, invalid_spec)); -} - -void test_refs_revparse__invalid_reference_name(void) -{ - assert_invalid_single_spec("this doesn't make sense"); - assert_invalid_single_spec("Inv@{id"); - assert_invalid_single_spec(""); -} - -void test_refs_revparse__shas(void) -{ - test_object("c47800c7266a2be04c571c04d5a6614691ea99bd", "c47800c7266a2be04c571c04d5a6614691ea99bd"); - test_object("c47800c", "c47800c7266a2be04c571c04d5a6614691ea99bd"); -} - -void test_refs_revparse__head(void) -{ - test_object("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("HEAD^0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("HEAD~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("master", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); -} - -void test_refs_revparse__full_refs(void) -{ - test_object("refs/heads/master", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("refs/heads/test", "e90810b8df3e80c413d903f631643c716887138d"); - test_object("refs/tags/test", "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); -} - -void test_refs_revparse__partial_refs(void) -{ - test_object("point_to_blob", "1385f264afb75a56a5bec74243be9b367ba4ca08"); - test_object("packed-test", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); - test_object("br2", "a4a7dce85cf63874e984719f4fdd239f5145052f"); -} - -void test_refs_revparse__describe_output(void) -{ - test_object("blah-7-gc47800c", "c47800c7266a2be04c571c04d5a6614691ea99bd"); - test_object("not-good", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); -} - -void test_refs_revparse__nth_parent(void) -{ - assert_invalid_single_spec("be3563a^-1"); - assert_invalid_single_spec("^"); - assert_invalid_single_spec("be3563a^{tree}^"); - assert_invalid_single_spec("point_to_blob^{blob}^"); - assert_invalid_single_spec("this doesn't make sense^1"); - - test_object("be3563a^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); - test_object("be3563a^", "9fd738e8f7967c078dceed8190330fc8648ee56a"); - test_object("be3563a^2", "c47800c7266a2be04c571c04d5a6614691ea99bd"); - test_object("be3563a^1^1", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); - test_object("be3563a^^", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); - test_object("be3563a^2^1", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); - test_object("be3563a^0", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("be3563a^{commit}^", "9fd738e8f7967c078dceed8190330fc8648ee56a"); - - test_object("be3563a^42", NULL); -} - -void test_refs_revparse__not_tag(void) -{ - test_object("point_to_blob^{}", "1385f264afb75a56a5bec74243be9b367ba4ca08"); - test_object("wrapped_tag^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("master^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("master^{tree}^{}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); - test_object("e90810b^{}", "e90810b8df3e80c413d903f631643c716887138d"); - test_object("tags/e90810b^{}", "e90810b8df3e80c413d903f631643c716887138d"); - test_object("e908^{}", "e90810b8df3e80c413d903f631643c716887138d"); -} - -void test_refs_revparse__to_type(void) -{ - assert_invalid_single_spec("wrapped_tag^{trip}"); - test_object("point_to_blob^{commit}", NULL); - cl_assert_equal_i( - GIT_EPEEL, git_revparse_single(&g_obj, g_repo, "wrapped_tag^{blob}")); - - test_object("wrapped_tag^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("wrapped_tag^{tree}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); - test_object("point_to_blob^{blob}", "1385f264afb75a56a5bec74243be9b367ba4ca08"); - test_object("master^{commit}^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); -} - -void test_refs_revparse__linear_history(void) -{ - assert_invalid_single_spec("~"); - test_object("foo~bar", NULL); - - assert_invalid_single_spec("master~bar"); - assert_invalid_single_spec("master~-1"); - assert_invalid_single_spec("master~0bar"); - assert_invalid_single_spec("this doesn't make sense~2"); - assert_invalid_single_spec("be3563a^{tree}~"); - assert_invalid_single_spec("point_to_blob^{blob}~"); - - test_object("master~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("master~1", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("master~2", "9fd738e8f7967c078dceed8190330fc8648ee56a"); - test_object("master~1~1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); - test_object("master~~", "9fd738e8f7967c078dceed8190330fc8648ee56a"); -} - -void test_refs_revparse__chaining(void) -{ - assert_invalid_single_spec("master@{0}@{0}"); - assert_invalid_single_spec("@{u}@{-1}"); - assert_invalid_single_spec("@{-1}@{-1}"); - assert_invalid_single_spec("@{-3}@{0}"); - - test_object("master@{0}~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); - test_object("@{u}@{0}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("@{-1}@{0}", "a4a7dce85cf63874e984719f4fdd239f5145052f"); - test_object("@{-4}@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("master~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); - test_object("master~1^2", "c47800c7266a2be04c571c04d5a6614691ea99bd"); - test_object("master^1^2~1", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); - test_object("master^^2^", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); - test_object("master^1^1^1^1^1", "8496071c1b46c854b31185ea97743be6a8774479"); - test_object("master^^1^2^1", NULL); -} - -void test_refs_revparse__upstream(void) -{ - assert_invalid_single_spec("e90810b@{u}"); - assert_invalid_single_spec("refs/tags/e90810b@{u}"); - test_object("refs/heads/e90810b@{u}", NULL); - - test_object("master@{upstream}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("heads/master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("refs/heads/master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); -} - -void test_refs_revparse__ordinal(void) -{ - assert_invalid_single_spec("master@{-2}"); - - /* TODO: make the test below actually fail - * cl_git_fail(git_revparse_single(&g_obj, g_repo, "master@{1a}")); - */ - - test_object("nope@{0}", NULL); - test_object("master@{31415}", NULL); - test_object("@{1000}", NULL); - test_object("@{2}", NULL); - - test_object("@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - - test_object("master@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("heads/master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("refs/heads/master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); -} - -void test_refs_revparse__previous_head(void) -{ - assert_invalid_single_spec("@{-xyz}"); - assert_invalid_single_spec("@{-0}"); - assert_invalid_single_spec("@{-1b}"); - - test_object("@{-42}", NULL); - - test_object("@{-2}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("@{-1}", "a4a7dce85cf63874e984719f4fdd239f5145052f"); -} - -static void create_fake_stash_reference_and_reflog(git_repository *repo) -{ - git_reference *master, *new_master; - git_str log_path = GIT_STR_INIT; - - git_str_joinpath(&log_path, git_repository_path(repo), "logs/refs/fakestash"); - - cl_assert_equal_i(false, git_fs_path_isfile(git_str_cstr(&log_path))); - - cl_git_pass(git_reference_lookup(&master, repo, "refs/heads/master")); - cl_git_pass(git_reference_rename(&new_master, master, "refs/fakestash", 0, NULL)); - git_reference_free(master); - - cl_assert_equal_i(true, git_fs_path_isfile(git_str_cstr(&log_path))); - - git_str_dispose(&log_path); - git_reference_free(new_master); -} - -void test_refs_revparse__reflog_of_a_ref_under_refs(void) -{ - git_repository *repo = cl_git_sandbox_init("testrepo.git"); - - test_object_inrepo("refs/fakestash", NULL, repo); - - create_fake_stash_reference_and_reflog(repo); - - /* - * $ git reflog -1 refs/fakestash - * a65fedf refs/fakestash@{0}: commit: checking in - * - * $ git reflog -1 refs/fakestash@{0} - * a65fedf refs/fakestash@{0}: commit: checking in - * - * $ git reflog -1 fakestash - * a65fedf fakestash@{0}: commit: checking in - * - * $ git reflog -1 fakestash@{0} - * a65fedf fakestash@{0}: commit: checking in - */ - test_object_inrepo("refs/fakestash", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); - test_object_inrepo("refs/fakestash@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); - test_object_inrepo("fakestash", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); - test_object_inrepo("fakestash@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); - - cl_git_sandbox_cleanup(); -} - -void test_refs_revparse__revwalk(void) -{ - test_object("master^{/not found in any commit}", NULL); - test_object("master^{/merge}", NULL); - assert_invalid_single_spec("master^{/((}"); - - test_object("master^{/anoth}", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); - test_object("master^{/Merge}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("br2^{/Merge}", "a4a7dce85cf63874e984719f4fdd239f5145052f"); - test_object("master^{/fo.rth}", "9fd738e8f7967c078dceed8190330fc8648ee56a"); -} - -void test_refs_revparse__date(void) -{ - /* - * $ git reflog HEAD --date=iso - * a65fedf HEAD@{2012-04-30 08:23:41 -0900}: checkout: moving from br2 to master - * a4a7dce HEAD@{2012-04-30 08:23:37 -0900}: commit: checking in - * c47800c HEAD@{2012-04-30 08:23:28 -0900}: checkout: moving from master to br2 - * a65fedf HEAD@{2012-04-30 08:23:23 -0900}: commit: - * be3563a HEAD@{2012-04-30 10:22:43 -0700}: clone: from /Users/ben/src/libgit2/tes - * - * $ git reflog HEAD --date=raw - * a65fedf HEAD@{1335806621 -0900}: checkout: moving from br2 to master - * a4a7dce HEAD@{1335806617 -0900}: commit: checking in - * c47800c HEAD@{1335806608 -0900}: checkout: moving from master to br2 - * a65fedf HEAD@{1335806603 -0900}: commit: - * be3563a HEAD@{1335806563 -0700}: clone: from /Users/ben/src/libgit2/tests/resour - */ - test_object("HEAD@{10 years ago}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - - test_object("HEAD@{1 second}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("HEAD@{1 second ago}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("HEAD@{2 days ago}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - - /* - * $ git reflog master --date=iso - * a65fedf master@{2012-04-30 09:23:23 -0800}: commit: checking in - * be3563a master@{2012-04-30 09:22:43 -0800}: clone: from /Users/ben/src... - * - * $ git reflog master --date=raw - * a65fedf master@{1335806603 -0800}: commit: checking in - * be3563a master@{1335806563 -0800}: clone: from /Users/ben/src/libgit2/tests/reso - */ - - - /* - * $ git rev-parse "master@{2012-04-30 17:22:42 +0000}" - * warning: log for 'master' only goes back to Mon, 30 Apr 2012 09:22:43 -0800 - * be3563ae3f795b2b4353bcce3a527ad0a4f7f644 - */ - test_object("master@{2012-04-30 17:22:42 +0000}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("master@{2012-04-30 09:22:42 -0800}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - - /* - * $ git reflog -1 "master@{2012-04-30 17:22:43 +0000}" - * be3563a master@{Mon Apr 30 09:22:43 2012 -0800}: clone: from /Users/ben/src/libg - */ - test_object("master@{2012-04-30 17:22:43 +0000}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("master@{2012-04-30 09:22:43 -0800}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - - /* - * $ git reflog -1 "master@{2012-4-30 09:23:27 -0800}" - * a65fedf master@{Mon Apr 30 09:23:23 2012 -0800}: commit: checking in - */ - test_object("master@{2012-4-30 09:23:27 -0800}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - - /* - * $ git reflog -1 master@{2012-05-03} - * a65fedf master@{Mon Apr 30 09:23:23 2012 -0800}: commit: checking in - */ - test_object("master@{2012-05-03}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - - /* - * $ git reflog -1 "master@{1335806603}" - * a65fedf - * - * $ git reflog -1 "master@{1335806602}" - * be3563a - */ - test_object("master@{1335806603}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("master@{1335806602}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - - /* - * $ git rev-parse "with-empty-log@{2 days ago}" -- - * fatal: log for refs/heads/with-empty-log is empty - */ - test_object("with-empty-log@{2 days ago}", NULL); -} - -void test_refs_revparse__invalid_date(void) -{ - /* - * $ git rev-parse HEAD@{} -- - * fatal: bad revision 'HEAD@{}' - * - * $ git rev-parse HEAD@{NEITHER_INTEGER_NOR_DATETIME} -- - * fatal: bad revision 'HEAD@{NEITHER_INTEGER_NOR_DATETIME}' - */ - test_object("HEAD@{}", NULL); - test_object("HEAD@{NEITHER_INTEGER_NOR_DATETIME}", NULL); -} - -void test_refs_revparse__colon(void) -{ - assert_invalid_single_spec(":/"); - assert_invalid_single_spec("point_to_blob:readme.txt"); - cl_git_fail(git_revparse_single(&g_obj, g_repo, ":2:README")); /* Not implemented */ - - test_object(":/not found in any commit", NULL); - test_object("subtrees:ab/42.txt", NULL); - test_object("subtrees:ab/4.txt/nope", NULL); - test_object("subtrees:nope", NULL); - test_object("test/master^1:branch_file.txt", NULL); - - /* From tags */ - test_object("test:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf"); - test_object("tags/test:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf"); - test_object("e90810b:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf"); - test_object("tags/e90810b:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf"); - - /* From commits */ - test_object("a65f:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc"); - - /* From trees */ - test_object("a65f^{tree}:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc"); - test_object("944c:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc"); - - /* Retrieving trees */ - test_object("master:", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); - test_object("subtrees:", "ae90f12eea699729ed24555e40b9fd669da12a12"); - test_object("subtrees:ab", "f1425cef211cc08caa31e7b545ffb232acb098c3"); - test_object("subtrees:ab/", "f1425cef211cc08caa31e7b545ffb232acb098c3"); - - /* Retrieving blobs */ - test_object("subtrees:ab/4.txt", "d6c93164c249c8000205dd4ec5cbca1b516d487f"); - test_object("subtrees:ab/de/fgh/1.txt", "1f67fc4386b2d171e0d21be1c447e12660561f9b"); - test_object("master:README", "a8233120f6ad708f843d861ce2b7228ec4e3dec6"); - test_object("master:new.txt", "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"); - test_object(":/Merge", "a4a7dce85cf63874e984719f4fdd239f5145052f"); - test_object(":/one", "c47800c7266a2be04c571c04d5a6614691ea99bd"); - test_object(":/packed commit t", "41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9"); - test_object("test/master^2:branch_file.txt", "45b983be36b73c0788dc9cbcb76cbb80fc7bb057"); - test_object("test/master@{1}:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc"); -} - -void test_refs_revparse__disambiguation(void) -{ - /* - * $ git show e90810b - * tag e90810b - * Tagger: Vicent Marti - * Date: Thu Aug 12 03:59:17 2010 +0200 - * - * This is a very simple tag. - * - * commit e90810b8df3e80c413d903f631643c716887138d - * Author: Vicent Marti - * Date: Thu Aug 5 18:42:20 2010 +0200 - * - * Test commit 2 - * - * diff --git a/readme.txt b/readme.txt - * index 6336846..0266163 100644 - * --- a/readme.txt - * +++ b/readme.txt - * @@ -1 +1,2 @@ - * Testing a readme.txt - * +Now we add a single line here - * - * $ git show-ref e90810b - * 7b4384978d2493e851f9cca7858815fac9b10980 refs/tags/e90810b - * - */ - test_object("e90810b", "7b4384978d2493e851f9cca7858815fac9b10980"); - - /* - * $ git show e90810 - * commit e90810b8df3e80c413d903f631643c716887138d - * Author: Vicent Marti - * Date: Thu Aug 5 18:42:20 2010 +0200 - * - * Test commit 2 - * - * diff --git a/readme.txt b/readme.txt - * index 6336846..0266163 100644 - * --- a/readme.txt - * +++ b/readme.txt - * @@ -1 +1,2 @@ - * Testing a readme.txt - * +Now we add a single line here - */ - test_object("e90810", "e90810b8df3e80c413d903f631643c716887138d"); -} - -void test_refs_revparse__a_too_short_objectid_returns_EAMBIGUOUS(void) -{ - cl_assert_equal_i( - GIT_EAMBIGUOUS, git_revparse_single(&g_obj, g_repo, "e90")); -} - -/* - * $ echo "aabqhq" | git hash-object -t blob --stdin - * dea509d0b3cb8ee0650f6ca210bc83f4678851ba - * - * $ echo "aaazvc" | git hash-object -t blob --stdin - * dea509d097ce692e167dfc6a48a7a280cc5e877e - */ -void test_refs_revparse__a_not_precise_enough_objectid_returns_EAMBIGUOUS(void) -{ - git_repository *repo; - git_index *index; - git_object *obj; - - repo = cl_git_sandbox_init("testrepo"); - - cl_git_mkfile("testrepo/one.txt", "aabqhq\n"); - cl_git_mkfile("testrepo/two.txt", "aaazvc\n"); - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, "one.txt")); - cl_git_pass(git_index_add_bypath(index, "two.txt")); - - cl_git_fail_with(git_revparse_single(&obj, repo, "dea509d0"), GIT_EAMBIGUOUS); - - cl_git_pass(git_revparse_single(&obj, repo, "dea509d09")); - - git_object_free(obj); - git_index_free(index); - cl_git_sandbox_cleanup(); -} - -void test_refs_revparse__issue_994(void) -{ - git_repository *repo; - git_reference *head, *with_at; - git_object *target; - - repo = cl_git_sandbox_init("testrepo.git"); - - cl_assert_equal_i(GIT_ENOTFOUND, - git_revparse_single(&target, repo, "origin/bim_with_3d@11296")); - - cl_assert_equal_i(GIT_ENOTFOUND, - git_revparse_single(&target, repo, "refs/remotes/origin/bim_with_3d@11296")); - - - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_create( - &with_at, - repo, - "refs/remotes/origin/bim_with_3d@11296", - git_reference_target(head), - 0, - NULL)); - - cl_git_pass(git_revparse_single(&target, repo, "origin/bim_with_3d@11296")); - git_object_free(target); - - cl_git_pass(git_revparse_single(&target, repo, "refs/remotes/origin/bim_with_3d@11296")); - git_object_free(target); - - git_reference_free(with_at); - git_reference_free(head); - cl_git_sandbox_cleanup(); -} - -/** - * $ git rev-parse blah-7-gc47800c - * c47800c7266a2be04c571c04d5a6614691ea99bd - * - * $ git rev-parse HEAD~3 - * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 - * - * $ git branch blah-7-gc47800c HEAD~3 - * - * $ git rev-parse blah-7-gc47800c - * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 - */ -void test_refs_revparse__try_to_retrieve_branch_before_described_tag(void) -{ - git_repository *repo; - git_reference *branch; - git_object *target; - char sha[GIT_OID_HEXSZ + 1]; - - repo = cl_git_sandbox_init("testrepo.git"); - - test_object_inrepo("blah-7-gc47800c", "c47800c7266a2be04c571c04d5a6614691ea99bd", repo); - - cl_git_pass(git_revparse_single(&target, repo, "HEAD~3")); - cl_git_pass(git_branch_create(&branch, repo, "blah-7-gc47800c", (git_commit *)target, 0)); - - git_oid_tostr(sha, GIT_OID_HEXSZ + 1, git_object_id(target)); - - test_object_inrepo("blah-7-gc47800c", sha, repo); - - git_reference_free(branch); - git_object_free(target); - cl_git_sandbox_cleanup(); -} - -/** - * $ git rev-parse a65fedf39aefe402d3bb6e24df4d4f5fe4547750 - * a65fedf39aefe402d3bb6e24df4d4f5fe4547750 - * - * $ git rev-parse HEAD~3 - * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 - * - * $ git branch a65fedf39aefe402d3bb6e24df4d4f5fe4547750 HEAD~3 - * - * $ git rev-parse a65fedf39aefe402d3bb6e24df4d4f5fe4547750 - * a65fedf39aefe402d3bb6e24df4d4f5fe4547750 - * - * $ git rev-parse heads/a65fedf39aefe402d3bb6e24df4d4f5fe4547750 - * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 - */ -void test_refs_revparse__try_to_retrieve_sha_before_branch(void) -{ - git_repository *repo; - git_reference *branch; - git_object *target; - char sha[GIT_OID_HEXSZ + 1]; - - repo = cl_git_sandbox_init("testrepo.git"); - - test_object_inrepo("a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); - - cl_git_pass(git_revparse_single(&target, repo, "HEAD~3")); - cl_git_pass(git_branch_create(&branch, repo, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", (git_commit *)target, 0)); - - git_oid_tostr(sha, GIT_OID_HEXSZ + 1, git_object_id(target)); - - test_object_inrepo("a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); - test_object_inrepo("heads/a65fedf39aefe402d3bb6e24df4d4f5fe4547750", sha, repo); - - git_reference_free(branch); - git_object_free(target); - cl_git_sandbox_cleanup(); -} - -/** - * $ git rev-parse c47800 - * c47800c7266a2be04c571c04d5a6614691ea99bd - * - * $ git rev-parse HEAD~3 - * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 - * - * $ git branch c47800 HEAD~3 - * - * $ git rev-parse c47800 - * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 - */ -void test_refs_revparse__try_to_retrieve_branch_before_abbrev_sha(void) -{ - git_repository *repo; - git_reference *branch; - git_object *target; - char sha[GIT_OID_HEXSZ + 1]; - - repo = cl_git_sandbox_init("testrepo.git"); - - test_object_inrepo("c47800", "c47800c7266a2be04c571c04d5a6614691ea99bd", repo); - - cl_git_pass(git_revparse_single(&target, repo, "HEAD~3")); - cl_git_pass(git_branch_create(&branch, repo, "c47800", (git_commit *)target, 0)); - - git_oid_tostr(sha, GIT_OID_HEXSZ + 1, git_object_id(target)); - - test_object_inrepo("c47800", sha, repo); - - git_reference_free(branch); - git_object_free(target); - cl_git_sandbox_cleanup(); -} - - -void test_refs_revparse__range(void) -{ - assert_invalid_single_spec("be3563a^1..be3563a"); - - test_rangelike("be3563a^1..be3563a", - "9fd738e8f7967c078dceed8190330fc8648ee56a", - "be3563ae3f795b2b4353bcce3a527ad0a4f7f644", - GIT_REVSPEC_RANGE); - - test_rangelike("be3563a^1...be3563a", - "9fd738e8f7967c078dceed8190330fc8648ee56a", - "be3563ae3f795b2b4353bcce3a527ad0a4f7f644", - GIT_REVSPEC_RANGE | GIT_REVSPEC_MERGE_BASE); - - test_rangelike("be3563a^1.be3563a", NULL, NULL, 0); -} - -void test_refs_revparse__parses_range_operator(void) -{ - test_id("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", NULL, GIT_REVSPEC_SINGLE); - test_id("HEAD~3..HEAD", - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - GIT_REVSPEC_RANGE); - - test_id("HEAD~3...HEAD", - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - GIT_REVSPEC_RANGE | GIT_REVSPEC_MERGE_BASE); - - test_id("HEAD~3..", - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - GIT_REVSPEC_RANGE); - - test_id("HEAD~3...", - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - GIT_REVSPEC_RANGE | GIT_REVSPEC_MERGE_BASE); - - test_id("..HEAD~3", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", - GIT_REVSPEC_RANGE); - - test_id("...HEAD~3", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", - GIT_REVSPEC_RANGE | GIT_REVSPEC_MERGE_BASE); - - test_id("...", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - GIT_REVSPEC_RANGE | GIT_REVSPEC_MERGE_BASE); - - test_invalid_revspec(".."); -} - -void test_refs_revparse__ext_retrieves_both_the_reference_and_its_target(void) -{ - test_object_and_ref( - "master@{upstream}", - "be3563ae3f795b2b4353bcce3a527ad0a4f7f644", - "refs/remotes/test/master"); - - test_object_and_ref( - "@{-1}", - "a4a7dce85cf63874e984719f4fdd239f5145052f", - "refs/heads/br2"); -} - -void test_refs_revparse__ext_can_expand_short_reference_names(void) -{ - test_object_and_ref( - "master", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "refs/heads/master"); - - test_object_and_ref( - "HEAD", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - "refs/heads/master"); - - test_object_and_ref( - "tags/test", - "b25fa35b38051e4ae45d4222e795f9df2e43f1d1", - "refs/tags/test"); -} - -void test_refs_revparse__ext_returns_NULL_reference_when_expression_points_at_a_revision(void) -{ - test_object_and_ref( - "HEAD~3", - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", - NULL); - - test_object_and_ref( - "HEAD~0", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - NULL); - - test_object_and_ref( - "HEAD^0", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", - NULL); - - test_object_and_ref( - "@{-1}@{0}", - "a4a7dce85cf63874e984719f4fdd239f5145052f", - NULL); -} - -void test_refs_revparse__ext_returns_NULL_reference_when_expression_points_at_a_tree_content(void) -{ - test_object_and_ref( - "tags/test:readme.txt", - "0266163a49e280c4f5ed1e08facd36a2bd716bcf", - NULL); -} - -void test_refs_revparse__uneven_sizes(void) -{ - test_object("a65fedf39aefe402d3bb6e24df4d4f5fe454775", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - - test_object("a65fedf39aefe402d3bb6e24df4d4f5fe45477", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - - test_object("a65fedf39aefe402d3bb6e24df4d4f5fe4547", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - - test_object("a65fedf39aefe402d3bb6e24df4d", - "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); -} - -void test_refs_revparse__parses_at_head(void) -{ - test_id("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", NULL, GIT_REVSPEC_SINGLE); - test_id("@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", NULL, GIT_REVSPEC_SINGLE); - test_id("@", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", NULL, GIT_REVSPEC_SINGLE); -} diff --git a/tests/refs/setter.c b/tests/refs/setter.c deleted file mode 100644 index b34c71eb5..000000000 --- a/tests/refs/setter.c +++ /dev/null @@ -1,99 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" -#include "git2/reflog.h" -#include "reflog.h" -#include "git2/refs.h" - -static const char *ref_name = "refs/heads/other"; -static const char *ref_master_name = "refs/heads/master"; -static const char *ref_test_name = "refs/heads/test"; - -static git_repository *g_repo; - -void test_refs_setter__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_refs_setter__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_refs_setter__update_direct(void) -{ - git_reference *ref, *test_ref, *new_ref; - git_oid id; - - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); - cl_assert(git_reference_type(ref) == GIT_REFERENCE_DIRECT); - git_oid_cpy(&id, git_reference_target(ref)); - git_reference_free(ref); - - cl_git_pass(git_reference_lookup(&test_ref, g_repo, ref_test_name)); - cl_assert(git_reference_type(test_ref) == GIT_REFERENCE_DIRECT); - - cl_git_pass(git_reference_set_target(&new_ref, test_ref, &id, NULL)); - - git_reference_free(test_ref); - git_reference_free(new_ref); - - cl_git_pass(git_reference_lookup(&test_ref, g_repo, ref_test_name)); - cl_assert(git_reference_type(test_ref) == GIT_REFERENCE_DIRECT); - cl_assert_equal_oid(&id, git_reference_target(test_ref)); - git_reference_free(test_ref); -} - -void test_refs_setter__update_symbolic(void) -{ - git_reference *head, *new_head; - - cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); - cl_assert(git_reference_type(head) == GIT_REFERENCE_SYMBOLIC); - cl_assert(strcmp(git_reference_symbolic_target(head), ref_master_name) == 0); - - cl_git_pass(git_reference_symbolic_set_target(&new_head, head, ref_test_name, NULL)); - git_reference_free(new_head); - git_reference_free(head); - - cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); - cl_assert(git_reference_type(head) == GIT_REFERENCE_SYMBOLIC); - cl_assert(strcmp(git_reference_symbolic_target(head), ref_test_name) == 0); - git_reference_free(head); -} - -void test_refs_setter__cant_update_direct_with_symbolic(void) -{ - /* Overwrite an existing object id reference with a symbolic one */ - git_reference *ref, *new; - git_oid id; - - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); - cl_assert(git_reference_type(ref) == GIT_REFERENCE_DIRECT); - git_oid_cpy(&id, git_reference_target(ref)); - - cl_git_fail(git_reference_symbolic_set_target(&new, ref, ref_name, NULL)); - - git_reference_free(ref); -} - -void test_refs_setter__cant_update_symbolic_with_direct(void) -{ - /* Overwrite an existing symbolic reference with an object id one */ - git_reference *ref, *new; - git_oid id; - - cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); - cl_assert(git_reference_type(ref) == GIT_REFERENCE_DIRECT); - git_oid_cpy(&id, git_reference_target(ref)); - git_reference_free(ref); - - /* Create the symbolic ref */ - cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 0, NULL)); - - /* Can't set an OID on a direct ref */ - cl_git_fail(git_reference_set_target(&new, ref, &id, NULL)); - - git_reference_free(ref); -} diff --git a/tests/refs/shorthand.c b/tests/refs/shorthand.c deleted file mode 100644 index e008adc74..000000000 --- a/tests/refs/shorthand.c +++ /dev/null @@ -1,27 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" - -static void assert_shorthand(git_repository *repo, const char *refname, const char *shorthand) -{ - git_reference *ref; - - cl_git_pass(git_reference_lookup(&ref, repo, refname)); - cl_assert_equal_s(git_reference_shorthand(ref), shorthand); - git_reference_free(ref); -} - -void test_refs_shorthand__0(void) -{ - git_repository *repo; - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - - - assert_shorthand(repo, "refs/heads/master", "master"); - assert_shorthand(repo, "refs/tags/test", "test"); - assert_shorthand(repo, "refs/remotes/test/master", "test/master"); - assert_shorthand(repo, "refs/notes/fanout", "notes/fanout"); - - git_repository_free(repo); -} diff --git a/tests/refs/tags/name.c b/tests/refs/tags/name.c deleted file mode 100644 index 1dd1760b9..000000000 --- a/tests/refs/tags/name.c +++ /dev/null @@ -1,17 +0,0 @@ -#include "clar_libgit2.h" - -static int name_is_valid(const char *name) -{ - int valid; - cl_git_pass(git_tag_name_is_valid(&valid, name)); - return valid; -} - -void test_refs_tags_name__is_name_valid(void) -{ - cl_assert_equal_i(true, name_is_valid("sometag")); - cl_assert_equal_i(true, name_is_valid("test/sometag")); - - cl_assert_equal_i(false, name_is_valid("")); - cl_assert_equal_i(false, name_is_valid("-dash")); -} diff --git a/tests/refs/transactions.c b/tests/refs/transactions.c deleted file mode 100644 index 50c102ad0..000000000 --- a/tests/refs/transactions.c +++ /dev/null @@ -1,157 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/transaction.h" - -static git_repository *g_repo; -static git_transaction *g_tx; - -void test_refs_transactions__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); - cl_git_pass(git_transaction_new(&g_tx, g_repo)); -} - -void test_refs_transactions__cleanup(void) -{ - git_transaction_free(g_tx); - cl_git_sandbox_cleanup(); -} - -void test_refs_transactions__single_ref_oid(void) -{ - git_reference *ref; - git_oid id; - - git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - - cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); - cl_git_pass(git_transaction_set_target(g_tx, "refs/heads/master", &id, NULL, NULL)); - cl_git_pass(git_transaction_commit(g_tx)); - - cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); - - cl_assert(!git_oid_cmp(&id, git_reference_target(ref))); - git_reference_free(ref); -} - -void test_refs_transactions__single_ref_symbolic(void) -{ - git_reference *ref; - - cl_git_pass(git_transaction_lock_ref(g_tx, "HEAD")); - cl_git_pass(git_transaction_set_symbolic_target(g_tx, "HEAD", "refs/heads/foo", NULL, NULL)); - cl_git_pass(git_transaction_commit(g_tx)); - - cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); - - cl_assert_equal_s("refs/heads/foo", git_reference_symbolic_target(ref)); - git_reference_free(ref); -} - -void test_refs_transactions__single_ref_mix_types(void) -{ - git_reference *ref; - git_oid id; - - git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - - cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); - cl_git_pass(git_transaction_lock_ref(g_tx, "HEAD")); - cl_git_pass(git_transaction_set_symbolic_target(g_tx, "refs/heads/master", "refs/heads/foo", NULL, NULL)); - cl_git_pass(git_transaction_set_target(g_tx, "HEAD", &id, NULL, NULL)); - cl_git_pass(git_transaction_commit(g_tx)); - - cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master")); - cl_assert_equal_s("refs/heads/foo", git_reference_symbolic_target(ref)); - git_reference_free(ref); - - cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); - cl_assert(!git_oid_cmp(&id, git_reference_target(ref))); - git_reference_free(ref); -} - -void test_refs_transactions__single_ref_delete(void) -{ - git_reference *ref; - - cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); - cl_git_pass(git_transaction_remove(g_tx, "refs/heads/master")); - cl_git_pass(git_transaction_commit(g_tx)); - - cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo, "refs/heads/master")); -} - -void test_refs_transactions__single_create(void) -{ - git_reference *ref; - const char *name = "refs/heads/new-branch"; - git_oid id; - - cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo, name)); - - cl_git_pass(git_transaction_lock_ref(g_tx, name)); - - git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - cl_git_pass(git_transaction_set_target(g_tx, name, &id, NULL, NULL)); - cl_git_pass(git_transaction_commit(g_tx)); - - cl_git_pass(git_reference_lookup(&ref, g_repo, name)); - cl_assert(!git_oid_cmp(&id, git_reference_target(ref))); - git_reference_free(ref); -} - -void test_refs_transactions__unlocked_set(void) -{ - git_oid id; - - cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); - git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - cl_git_fail_with(GIT_ENOTFOUND, git_transaction_set_target(g_tx, "refs/heads/foo", &id, NULL, NULL)); - cl_git_pass(git_transaction_commit(g_tx)); -} - -void test_refs_transactions__error_on_locking_locked_ref(void) -{ - git_oid id; - git_transaction *g_tx_with_lock; - git_repository *g_repo_with_locking_tx; - const char *g_repo_path = git_repository_path(g_repo); - - /* prepare a separate transaction in another instance of testrepo and lock master */ - cl_git_pass(git_repository_open(&g_repo_with_locking_tx, g_repo_path)); - cl_git_pass(git_transaction_new(&g_tx_with_lock, g_repo_with_locking_tx)); - cl_git_pass(git_transaction_lock_ref(g_tx_with_lock, "refs/heads/master")); - - /* lock reference for set_target */ - cl_git_pass(git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); - cl_git_fail_with(GIT_ELOCKED, git_transaction_lock_ref(g_tx, "refs/heads/master")); - cl_git_fail_with(GIT_ENOTFOUND, git_transaction_set_target(g_tx, "refs/heads/master", &id, NULL, NULL)); - - git_transaction_free(g_tx_with_lock); - git_repository_free(g_repo_with_locking_tx); -} - -void test_refs_transactions__commit_unlocks_unmodified_ref(void) -{ - git_transaction *second_tx; - - cl_git_pass(git_transaction_new(&second_tx, g_repo)); - cl_git_pass(git_transaction_lock_ref(second_tx, "refs/heads/master")); - cl_git_pass(git_transaction_commit(second_tx)); - - /* a transaction must now be able to get the lock */ - cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); - - git_transaction_free(second_tx); -} - -void test_refs_transactions__free_unlocks_unmodified_ref(void) -{ - git_transaction *second_tx; - - cl_git_pass(git_transaction_new(&second_tx, g_repo)); - cl_git_pass(git_transaction_lock_ref(second_tx, "refs/heads/master")); - git_transaction_free(second_tx); - - /* a transaction must now be able to get the lock */ - cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); -} diff --git a/tests/refs/unicode.c b/tests/refs/unicode.c deleted file mode 100644 index a279d5006..000000000 --- a/tests/refs/unicode.c +++ /dev/null @@ -1,54 +0,0 @@ -#include "clar_libgit2.h" - -static git_repository *repo; - -void test_refs_unicode__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_refs_unicode__cleanup(void) -{ - cl_git_sandbox_cleanup(); - repo = NULL; -} - -void test_refs_unicode__create_and_lookup(void) -{ - git_reference *ref0, *ref1, *ref2; - git_repository *repo2; - - const char *REFNAME = "refs/heads/" "\303\205" "ngstr" "\303\266" "m"; - const char *master = "refs/heads/master"; - - /* Create the reference */ - cl_git_pass(git_reference_lookup(&ref0, repo, master)); - cl_git_pass(git_reference_create( - &ref1, repo, REFNAME, git_reference_target(ref0), 0, NULL)); - cl_assert_equal_s(REFNAME, git_reference_name(ref1)); - git_reference_free(ref0); - - /* Lookup the reference in a different instance of the repository */ - cl_git_pass(git_repository_open(&repo2, "testrepo.git")); - - cl_git_pass(git_reference_lookup(&ref2, repo2, REFNAME)); - cl_assert_equal_oid(git_reference_target(ref1), git_reference_target(ref2)); - cl_assert_equal_s(REFNAME, git_reference_name(ref2)); - git_reference_free(ref2); - -#if GIT_USE_ICONV - /* Lookup reference by decomposed unicode name */ - -#define REFNAME_DECOMPOSED "refs/heads/" "A" "\314\212" "ngstro" "\314\210" "m" - - cl_git_pass(git_reference_lookup(&ref2, repo2, REFNAME_DECOMPOSED)); - cl_assert_equal_oid(git_reference_target(ref1), git_reference_target(ref2)); - cl_assert_equal_s(REFNAME, git_reference_name(ref2)); - git_reference_free(ref2); -#endif - - /* Cleanup */ - - git_reference_free(ref1); - git_repository_free(repo2); -} diff --git a/tests/refs/update.c b/tests/refs/update.c deleted file mode 100644 index 1c5127d9e..000000000 --- a/tests/refs/update.c +++ /dev/null @@ -1,26 +0,0 @@ -#include "clar_libgit2.h" - -#include "refs.h" - -static git_repository *g_repo; - -void test_refs_update__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_refs_update__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_refs_update__updating_the_target_of_a_symref_with_an_invalid_name_returns_EINVALIDSPEC(void) -{ - git_reference *head; - - cl_git_pass(git_reference_lookup(&head, g_repo, GIT_HEAD_FILE)); - cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head)); - git_reference_free(head); - - cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_symbolic_create(&head, g_repo, GIT_HEAD_FILE, "refs/heads/inv@{id", 1, NULL)); -} diff --git a/tests/remote/create.c b/tests/remote/create.c deleted file mode 100644 index f92be9dfc..000000000 --- a/tests/remote/create.c +++ /dev/null @@ -1,388 +0,0 @@ -#include "clar_libgit2.h" -#include "config/config_helpers.h" - -static git_repository *_repo; -static git_config *_config; - -#define TEST_URL "http://github.com/libgit2/libgit2.git" - -void test_remote_create__initialize(void) -{ - cl_fixture_sandbox("testrepo.git"); - - cl_git_pass(git_repository_open(&_repo, "testrepo.git")); - - cl_git_pass(git_repository_config(&_config, _repo)); -} - -void test_remote_create__cleanup(void) -{ - git_config_free(_config); - - git_repository_free(_repo); - - cl_fixture_cleanup("testrepo.git"); -} - -void test_remote_create__manual(void) -{ - git_remote *remote; - cl_git_pass(git_config_set_string(_config, "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*")); - cl_git_pass(git_config_set_string(_config, "remote.origin.url", TEST_URL)); - - cl_git_pass(git_remote_lookup(&remote, _repo, "origin")); - cl_assert_equal_s(git_remote_name(remote), "origin"); - cl_assert_equal_s(git_remote_url(remote), TEST_URL); - - git_remote_free(remote); -} - -void test_remote_create__named(void) -{ - git_remote *remote; - git_config *cfg; - const char *cfg_val; - - size_t section_count = count_config_entries_match(_repo, "remote\\."); - - cl_git_pass(git_remote_create(&remote, _repo, "valid-name", TEST_URL)); - - cl_assert_equal_s(git_remote_name(remote), "valid-name"); - cl_assert_equal_s(git_remote_url(remote), TEST_URL); - cl_assert_equal_p(git_remote_owner(remote), _repo); - - cl_git_pass(git_repository_config_snapshot(&cfg, _repo)); - - cl_git_pass(git_config_get_string(&cfg_val, cfg, "remote.valid-name.fetch")); - cl_assert_equal_s(cfg_val, "+refs/heads/*:refs/remotes/valid-name/*"); - - cl_git_pass(git_config_get_string(&cfg_val, cfg, "remote.valid-name.url")); - cl_assert_equal_s(cfg_val, TEST_URL); - - cl_assert_equal_i(section_count + 2, count_config_entries_match(_repo, "remote\\.")); - - git_config_free(cfg); - git_remote_free(remote); -} - -void test_remote_create__named_fail_on_invalid_name(void) -{ - const char *names[] = { - NULL, - "Inv@{id", - "", - "/", - "//", - ".lock", - "a.lock", - }; - size_t i; - - for (i = 0; i < ARRAY_SIZE(names); i++) { - git_remote *remote = NULL; - cl_git_fail_with(GIT_EINVALIDSPEC, git_remote_create(&remote, _repo, names[i], TEST_URL)); - cl_assert_equal_p(remote, NULL); - } -} - -void test_remote_create__named_fail_on_invalid_url(void) -{ - git_remote *remote = NULL; - - cl_git_fail_with(GIT_ERROR, git_remote_create(&remote, _repo, "bad-url", "")); - cl_assert_equal_p(remote, NULL); -} - -void test_remote_create__named_fail_on_conflicting_name(void) -{ - git_remote *remote = NULL; - - cl_git_fail_with(GIT_EEXISTS, git_remote_create(&remote, _repo, "test", TEST_URL)); - cl_assert_equal_p(remote, NULL); -} - -void test_remote_create__with_fetchspec(void) -{ - git_remote *remote; - git_strarray array; - size_t section_count = count_config_entries_match(_repo, "remote\\."); - - cl_git_pass(git_remote_create_with_fetchspec(&remote, _repo, "test-new", "git://github.com/libgit2/libgit2", "+refs/*:refs/*")); - cl_assert_equal_s(git_remote_name(remote), "test-new"); - cl_assert_equal_s(git_remote_url(remote), "git://github.com/libgit2/libgit2"); - cl_assert_equal_p(git_remote_owner(remote), _repo); - - cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); - cl_assert_equal_s("+refs/*:refs/*", array.strings[0]); - cl_assert_equal_i(1, array.count); - cl_assert_equal_i(section_count + 2, count_config_entries_match(_repo, "remote\\.")); - - git_strarray_dispose(&array); - git_remote_free(remote); -} - -void test_remote_create__with_empty_fetchspec(void) -{ - git_remote *remote; - git_strarray array; - size_t section_count = count_config_entries_match(_repo, "remote\\."); - - cl_git_pass(git_remote_create_with_fetchspec(&remote, _repo, "test-new", "git://github.com/libgit2/libgit2", NULL)); - cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); - cl_assert_equal_i(0, array.count); - cl_assert_equal_i(section_count + 1, count_config_entries_match(_repo, "remote\\.")); - - git_strarray_dispose(&array); - git_remote_free(remote); -} - -void test_remote_create__with_fetchspec_invalid_name(void) -{ - git_remote *remote = NULL; - - cl_git_fail_with(GIT_EINVALIDSPEC, git_remote_create_with_fetchspec(&remote, _repo, NULL, TEST_URL, NULL)); - cl_assert_equal_p(remote, NULL); -} - -void test_remote_create__with_fetchspec_invalid_url(void) -{ - git_remote *remote = NULL; - - cl_git_fail_with(GIT_EINVALIDSPEC, git_remote_create_with_fetchspec(&remote, _repo, NULL, "", NULL)); - cl_assert_equal_p(remote, NULL); -} - -void test_remote_create__anonymous(void) -{ - git_remote *remote; - git_strarray array; - size_t section_count = count_config_entries_match(_repo, "remote\\."); - - cl_git_pass(git_remote_create_anonymous(&remote, _repo, TEST_URL)); - cl_assert_equal_s(git_remote_name(remote), NULL); - cl_assert_equal_s(git_remote_url(remote), TEST_URL); - cl_assert_equal_p(git_remote_owner(remote), _repo); - - cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); - cl_assert_equal_i(0, array.count); - cl_assert_equal_i(section_count, count_config_entries_match(_repo, "remote\\.")); - - git_strarray_dispose(&array); - git_remote_free(remote); -} - -void test_remote_create__anonymous_invalid_url(void) -{ - git_remote *remote = NULL; - - cl_git_fail_with(GIT_EINVALIDSPEC, git_remote_create_anonymous(&remote, _repo, "")); - cl_assert_equal_p(remote, NULL); -} - -void test_remote_create__detached(void) -{ - git_remote *remote; - git_strarray array; - - size_t section_count = count_config_entries_match(_repo, "remote\\."); - - cl_git_pass(git_remote_create_detached(&remote, TEST_URL)); - cl_assert_equal_s(git_remote_name(remote), NULL); - cl_assert_equal_s(git_remote_url(remote), TEST_URL); - cl_assert_equal_p(git_remote_owner(remote), NULL); - - cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); - cl_assert_equal_i(0, array.count); - cl_assert_equal_i(section_count, count_config_entries_match(_repo, "remote\\.")); - - git_strarray_dispose(&array); - git_remote_free(remote); -} - -void test_remote_create__detached_invalid_url(void) -{ - git_remote *remote = NULL; - - cl_git_fail_with(GIT_EINVALIDSPEC, git_remote_create_detached(&remote, "")); - cl_assert_equal_p(remote, NULL); -} - -void test_remote_create__with_opts_named(void) -{ - git_remote *remote; - git_strarray array; - git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - - opts.name = "test-new"; - opts.repository = _repo; - - cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts)); - cl_assert_equal_s(git_remote_name(remote), "test-new"); - cl_assert_equal_s(git_remote_url(remote), TEST_URL); - cl_assert_equal_p(git_remote_owner(remote), _repo); - - cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); - cl_assert_equal_i(1, array.count); - cl_assert_equal_s("+refs/heads/*:refs/remotes/test-new/*", array.strings[0]); - - git_strarray_dispose(&array); - git_remote_free(remote); -} - -void test_remote_create__with_opts_named_and_fetchspec(void) -{ - git_remote *remote; - git_strarray array; - git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - - opts.name = "test-new"; - opts.repository = _repo; - opts.fetchspec = "+refs/*:refs/*"; - - cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts)); - cl_assert_equal_s(git_remote_name(remote), "test-new"); - cl_assert_equal_s(git_remote_url(remote), TEST_URL); - cl_assert_equal_p(git_remote_owner(remote), _repo); - - cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); - cl_assert_equal_i(1, array.count); - cl_assert_equal_s("+refs/*:refs/*", array.strings[0]); - - git_strarray_dispose(&array); - git_remote_free(remote); -} - -void test_remote_create__with_opts_named_no_fetchspec(void) -{ - git_remote *remote; - git_strarray array; - git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - - opts.name = "test-new"; - opts.repository = _repo; - opts.flags = GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC; - - cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts)); - cl_assert_equal_s(git_remote_name(remote), "test-new"); - cl_assert_equal_s(git_remote_url(remote), TEST_URL); - cl_assert_equal_p(git_remote_owner(remote), _repo); - - cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); - cl_assert_equal_i(0, array.count); - - git_strarray_dispose(&array); - git_remote_free(remote); -} - -void test_remote_create__with_opts_anonymous(void) -{ - git_remote *remote; - git_strarray array; - git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - - opts.repository = _repo; - - cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts)); - cl_assert_equal_s(git_remote_name(remote), NULL); - cl_assert_equal_s(git_remote_url(remote), TEST_URL); - cl_assert_equal_p(git_remote_owner(remote), _repo); - - cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); - cl_assert_equal_i(0, array.count); - - git_strarray_dispose(&array); - git_remote_free(remote); -} - -void test_remote_create__with_opts_detached(void) -{ - git_remote *remote; - git_strarray array; - git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - - cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts)); - cl_assert_equal_s(git_remote_name(remote), NULL); - cl_assert_equal_s(git_remote_url(remote), TEST_URL); - cl_assert_equal_p(git_remote_owner(remote), NULL); - - cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); - cl_assert_equal_i(0, array.count); - - git_strarray_dispose(&array); - - git_remote_free(remote); - - cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, NULL)); - cl_assert_equal_s(git_remote_name(remote), NULL); - cl_assert_equal_s(git_remote_url(remote), TEST_URL); - cl_assert_equal_p(git_remote_owner(remote), NULL); - - cl_git_pass(git_remote_get_fetch_refspecs(&array, remote)); - cl_assert_equal_i(0, array.count); - - git_strarray_dispose(&array); - - git_remote_free(remote); -} - - -void test_remote_create__with_opts_insteadof_disabled(void) -{ - git_remote *remote; - git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - - opts.repository = _repo; - opts.flags = GIT_REMOTE_CREATE_SKIP_INSTEADOF; - - cl_git_pass(git_remote_create_with_opts(&remote, "http://example.com/libgit2/libgit2", &opts)); - - cl_assert_equal_s(git_remote_url(remote), "http://example.com/libgit2/libgit2"); - cl_assert_equal_p(git_remote_pushurl(remote), NULL); - - git_remote_free(remote); -} - -static int create_with_name(git_remote **remote, git_repository *repo, const char *name, const char *url) -{ - git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT; - - opts.repository = repo; - opts.name = name; - - return git_remote_create_with_opts(remote, url, &opts); -} - -void test_remote_create__with_opts_invalid_name(void) -{ - const char *names[] = { - "Inv@{id", - "", - "/", - "//", - ".lock", - "a.lock", - }; - size_t i; - - for (i = 0; i < ARRAY_SIZE(names); i++) { - git_remote *remote = NULL; - cl_git_fail_with(GIT_EINVALIDSPEC, create_with_name(&remote, _repo, names[i], TEST_URL)); - cl_assert_equal_p(remote, NULL); - } -} - -void test_remote_create__with_opts_conflicting_name(void) -{ - git_remote *remote = NULL; - - cl_git_fail_with(GIT_EEXISTS, create_with_name(&remote, _repo, "test", TEST_URL)); - cl_assert_equal_p(remote, NULL); -} - -void test_remote_create__with_opts_invalid_url(void) -{ - git_remote *remote = NULL; - - cl_git_fail_with(GIT_EINVALIDSPEC, create_with_name(&remote, _repo, "test-new", "")); - cl_assert_equal_p(remote, NULL); -} diff --git a/tests/remote/fetch.c b/tests/remote/fetch.c deleted file mode 100644 index 370046267..000000000 --- a/tests/remote/fetch.c +++ /dev/null @@ -1,169 +0,0 @@ -#include "../clar_libgit2.h" - -#include "remote.h" -#include "repository.h" - -static git_repository *repo1; -static git_repository *repo2; -static char* repo1_path; -static char* repo2_path; - -static const char *REPO1_REFNAME = "refs/heads/main"; -static const char *REPO2_REFNAME = "refs/remotes/repo1/main"; -static char *FORCE_FETCHSPEC = "+refs/heads/main:refs/remotes/repo1/main"; -static char *NON_FORCE_FETCHSPEC = "refs/heads/main:refs/remotes/repo1/main"; - -void test_remote_fetch__initialize(void) { - git_config *c; - git_str repo1_path_buf = GIT_STR_INIT; - git_str repo2_path_buf = GIT_STR_INIT; - const char *sandbox = clar_sandbox_path(); - - cl_git_pass(git_str_joinpath(&repo1_path_buf, sandbox, "fetchtest_repo1")); - repo1_path = git_str_detach(&repo1_path_buf); - cl_git_pass(git_repository_init(&repo1, repo1_path, true)); - - cl_git_pass(git_str_joinpath(&repo2_path_buf, sandbox, "fetchtest_repo2")); - repo2_path = git_str_detach(&repo2_path_buf); - cl_git_pass(git_repository_init(&repo2, repo2_path, true)); - - cl_git_pass(git_repository_config(&c, repo1)); - cl_git_pass(git_config_set_string(c, "user.email", "some@email")); - cl_git_pass(git_config_set_string(c, "user.name", "some@name")); - git_config_free(c); - git_str_dispose(&repo1_path_buf); - git_str_dispose(&repo2_path_buf); -} - -void test_remote_fetch__cleanup(void) { - git_repository_free(repo1); - git_repository_free(repo2); - - cl_git_pass(git_futils_rmdir_r(repo1_path, NULL, GIT_RMDIR_REMOVE_FILES)); - free(repo1_path); - - cl_git_pass(git_futils_rmdir_r(repo2_path, NULL, GIT_RMDIR_REMOVE_FILES)); - free(repo2_path); -} - - -/** - * This checks that the '+' flag on fetchspecs is respected. We create a - * repository that has a reference to two commits, one a child of the other. - * We fetch this repository into a second repository. Then we reset the - * reference in the first repository and run the fetch again. If the '+' flag - * is used then the reference in the second repository will change, but if it - * is not then it should stay the same. - * - * @param commit1id A pointer to an OID which will be populated with the first - * commit. - * @param commit2id A pointer to an OID which will be populated with the second - * commit, which is a descendant of the first. - * @param force Whether to use a spec with '+' prefixed to force the refs - * to update - */ -static void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, - bool force) { - char *refspec_strs = { - force ? FORCE_FETCHSPEC : NON_FORCE_FETCHSPEC, - }; - git_strarray refspecs = { - .count = 1, - .strings = &refspec_strs, - }; - - /* create two commits in repo 1 and a reference to them */ - { - git_oid empty_tree_id; - git_tree *empty_tree; - git_signature *sig; - git_treebuilder *tb; - cl_git_pass(git_treebuilder_new(&tb, repo1, NULL)); - cl_git_pass(git_treebuilder_write(&empty_tree_id, tb)); - cl_git_pass(git_tree_lookup(&empty_tree, repo1, &empty_tree_id)); - cl_git_pass(git_signature_default(&sig, repo1)); - cl_git_pass(git_commit_create(commit1id, repo1, REPO1_REFNAME, sig, - sig, NULL, "one", empty_tree, 0, NULL)); - cl_git_pass(git_commit_create_v(commit2id, repo1, REPO1_REFNAME, sig, - sig, NULL, "two", empty_tree, 1, commit1id)); - - git_tree_free(empty_tree); - git_signature_free(sig); - git_treebuilder_free(tb); - } - - /* fetch the reference via the remote */ - { - git_remote *remote; - - cl_git_pass(git_remote_create_anonymous(&remote, repo2, - git_repository_path(repo1))); - cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, "some message")); - - git_remote_free(remote); - } - - /* assert that repo2 references the second commit */ - { - const git_oid *target; - git_reference *ref; - cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME)); - target = git_reference_target(ref); - cl_assert_equal_b(git_oid_cmp(target, commit2id), 0); - git_reference_free(ref); - } - - /* set the reference in repo1 to point to the older commit */ - { - git_reference *ref; - git_reference *ref2; - cl_git_pass(git_reference_lookup(&ref, repo1, REPO1_REFNAME)); - cl_git_pass(git_reference_set_target(&ref2, ref, commit1id, - "rollback")); - git_reference_free(ref); - git_reference_free(ref2); - } - - /* fetch the reference again */ - { - git_remote *remote; - - cl_git_pass(git_remote_create_anonymous(&remote, repo2, - git_repository_path(repo1))); - cl_git_pass(git_remote_fetch(remote, &refspecs, NULL, "some message")); - - git_remote_free(remote); - } -} - -void test_remote_fetch__dont_update_refs_if_not_descendant_and_not_force(void) { - const git_oid *target; - git_oid commit1id; - git_oid commit2id; - git_reference *ref; - - do_time_travelling_fetch(&commit1id, &commit2id, false); - - /* assert that the reference in repo2 has not changed */ - cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME)); - target = git_reference_target(ref); - cl_assert_equal_b(git_oid_cmp(target, &commit2id), 0); - - git_reference_free(ref); -} - -void test_remote_fetch__do_update_refs_if_not_descendant_and_force(void) { - const git_oid *target; - git_oid commit1id; - git_oid commit2id; - git_reference *ref; - - do_time_travelling_fetch(&commit1id, &commit2id, true); - - /* assert that the reference in repo2 has changed */ - cl_git_pass(git_reference_lookup(&ref, repo2, REPO2_REFNAME)); - target = git_reference_target(ref); - cl_assert_equal_b(git_oid_cmp(target, &commit1id), 0); - - git_reference_free(ref); -} diff --git a/tests/remote/httpproxy.c b/tests/remote/httpproxy.c deleted file mode 100644 index f62a2545b..000000000 --- a/tests/remote/httpproxy.c +++ /dev/null @@ -1,188 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "net.h" -#include "remote.h" - -static git_repository *repo; -static git_net_url url = GIT_NET_URL_INIT; - -static int orig_proxies_need_reset = 0; -static char *orig_http_proxy = NULL; -static char *orig_https_proxy = NULL; -static char *orig_no_proxy = NULL; - -void test_remote_httpproxy__initialize(void) -{ - git_remote *remote; - - repo = cl_git_sandbox_init("testrepo"); - cl_git_pass(git_remote_create(&remote, repo, "lg2", "https://github.com/libgit2/libgit2")); - cl_git_pass(git_net_url_parse(&url, "https://github.com/libgit2/libgit2")); - - git_remote_free(remote); - - orig_proxies_need_reset = 0; -} - -void test_remote_httpproxy__cleanup(void) -{ - if (orig_proxies_need_reset) { - cl_setenv("HTTP_PROXY", orig_http_proxy); - cl_setenv("HTTPS_PROXY", orig_https_proxy); - cl_setenv("NO_PROXY", orig_no_proxy); - - git__free(orig_http_proxy); - git__free(orig_https_proxy); - git__free(orig_no_proxy); - } - - git_net_url_dispose(&url); - cl_git_sandbox_cleanup(); -} - -static void assert_proxy_is(const char *expected) -{ - git_remote *remote; - char *proxy; - - cl_git_pass(git_remote_lookup(&remote, repo, "lg2")); - cl_git_pass(git_remote__http_proxy(&proxy, remote, &url)); - - if (expected) - cl_assert_equal_s(proxy, expected); - else - cl_assert_equal_p(proxy, expected); - - git_remote_free(remote); - git__free(proxy); -} - -static void assert_config_match(const char *config, const char *expected) -{ - git_remote *remote; - char *proxy; - - if (config) - cl_repo_set_string(repo, config, expected); - - cl_git_pass(git_remote_lookup(&remote, repo, "lg2")); - cl_git_pass(git_remote__http_proxy(&proxy, remote, &url)); - - if (expected) - cl_assert_equal_s(proxy, expected); - else - cl_assert_equal_p(proxy, expected); - - git_remote_free(remote); - git__free(proxy); -} - -void test_remote_httpproxy__config_overrides(void) -{ - /* - * http.proxy should be honored, then http..proxy should - * be honored in increasing specificity of the url. finally, - * remote..proxy is the most specific. - */ - assert_config_match(NULL, NULL); - assert_config_match("http.proxy", "http://localhost:1/"); - assert_config_match("http.https://github.com.proxy", "http://localhost:2/"); - assert_config_match("http.https://github.com/.proxy", "http://localhost:3/"); - assert_config_match("http.https://github.com/libgit2.proxy", "http://localhost:4/"); - assert_config_match("http.https://github.com/libgit2/.proxy", "http://localhost:5/"); - assert_config_match("http.https://github.com/libgit2/libgit2.proxy", "http://localhost:6/"); - assert_config_match("remote.lg2.proxy", "http://localhost:7/"); -} - -void test_remote_httpproxy__config_empty_overrides(void) -{ - /* - * with greater specificity, an empty config entry overrides - * a set one - */ - assert_config_match("http.proxy", "http://localhost:1/"); - assert_config_match("http.https://github.com.proxy", ""); - assert_config_match("http.https://github.com/libgit2/libgit2.proxy", "http://localhost:2/"); - assert_config_match("remote.lg2.proxy", ""); -} - -static void assert_global_config_match(const char *config, const char *expected) -{ - git_remote *remote; - char *proxy; - git_config* cfg; - - if (config) { - cl_git_pass(git_config_open_default(&cfg)); - git_config_set_string(cfg, config, expected); - git_config_free(cfg); - } - - cl_git_pass(git_remote_create_detached(&remote, "https://github.com/libgit2/libgit2")); - cl_git_pass(git_remote__http_proxy(&proxy, remote, &url)); - - if (expected) - cl_assert_equal_s(proxy, expected); - else - cl_assert_equal_p(proxy, expected); - - git_remote_free(remote); - git__free(proxy); -} - -void test_remote_httpproxy__config_overrides_detached_remote(void) -{ - cl_fake_home(); - - assert_global_config_match(NULL, NULL); - assert_global_config_match("http.proxy", "http://localhost:1/"); - assert_global_config_match("http.https://github.com.proxy", "http://localhost:2/"); - assert_global_config_match("http.https://github.com/.proxy", "http://localhost:3/"); - assert_global_config_match("http.https://github.com/libgit2.proxy", "http://localhost:4/"); - assert_global_config_match("http.https://github.com/libgit2/.proxy", "http://localhost:5/"); - assert_global_config_match("http.https://github.com/libgit2/libgit2.proxy", "http://localhost:6/"); - - cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_remote_httpproxy__env(void) -{ - orig_http_proxy = cl_getenv("HTTP_PROXY"); - orig_https_proxy = cl_getenv("HTTPS_PROXY"); - orig_no_proxy = cl_getenv("NO_PROXY"); - orig_proxies_need_reset = 1; - - /* Clear everything for a fresh start */ - cl_setenv("HTTP_PROXY", NULL); - cl_setenv("HTTPS_PROXY", NULL); - cl_setenv("NO_PROXY", NULL); - - /* HTTP proxy is ignored for HTTPS */ - cl_setenv("HTTP_PROXY", "http://localhost:9/"); - assert_proxy_is(NULL); - - /* HTTPS proxy is honored for HTTPS */ - cl_setenv("HTTPS_PROXY", "http://localhost:10/"); - assert_proxy_is("http://localhost:10/"); - - /* NO_PROXY is honored */ - cl_setenv("NO_PROXY", "github.com:443"); - assert_proxy_is(NULL); - - cl_setenv("NO_PROXY", "github.com:80"); - assert_proxy_is("http://localhost:10/"); - - cl_setenv("NO_PROXY", "github.com"); - assert_proxy_is(NULL); - - cl_setenv("NO_PROXY", "github.dev,github.com,github.foo"); - assert_proxy_is(NULL); - - cl_setenv("HTTPS_PROXY", ""); - assert_proxy_is(NULL); - - /* configuration overrides environment variables */ - cl_setenv("HTTPS_PROXY", "http://localhost:10/"); - cl_setenv("NO_PROXY", "github.none"); - assert_config_match("http.https://github.com.proxy", "http://localhost:11/"); -} diff --git a/tests/remote/insteadof.c b/tests/remote/insteadof.c deleted file mode 100644 index c39df4be7..000000000 --- a/tests/remote/insteadof.c +++ /dev/null @@ -1,154 +0,0 @@ -#include "clar_libgit2.h" -#include "remote.h" -#include "repository.h" - -#define REPO_PATH "testrepo2/.gitted" -#define REMOTE_ORIGIN "origin" -#define REMOTE_INSTEADOF_URL_FETCH "insteadof-url-fetch" -#define REMOTE_INSTEADOF_URL_PUSH "insteadof-url-push" -#define REMOTE_INSTEADOF_URL_BOTH "insteadof-url-both" -#define REMOTE_INSTEADOF_PUSHURL_FETCH "insteadof-pushurl-fetch" -#define REMOTE_INSTEADOF_PUSHURL_PUSH "insteadof-pushurl-push" -#define REMOTE_INSTEADOF_PUSHURL_BOTH "insteadof-pushurl-both" - -static git_repository *g_repo; -static git_remote *g_remote; - -void test_remote_insteadof__initialize(void) -{ - g_repo = NULL; - g_remote = NULL; -} - -void test_remote_insteadof__cleanup(void) -{ - git_repository_free(g_repo); - git_remote_free(g_remote); -} - -void test_remote_insteadof__not_applicable(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); - cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_ORIGIN)); - - cl_assert_equal_s( - git_remote_url(g_remote), - "https://github.com/libgit2/false.git"); - cl_assert_equal_p(git_remote_pushurl(g_remote), NULL); -} - -void test_remote_insteadof__url_insteadof_fetch(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); - cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF_URL_FETCH)); - - cl_assert_equal_s( - git_remote_url(g_remote), - "http://github.com/url/fetch/libgit2"); - cl_assert_equal_p(git_remote_pushurl(g_remote), NULL); -} - -void test_remote_insteadof__url_insteadof_push(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); - cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF_URL_PUSH)); - - cl_assert_equal_s( - git_remote_url(g_remote), - "http://example.com/url/push/libgit2"); - cl_assert_equal_s( - git_remote_pushurl(g_remote), - "git@github.com:url/push/libgit2"); -} - -void test_remote_insteadof__url_insteadof_both(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); - cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF_URL_BOTH)); - - cl_assert_equal_s( - git_remote_url(g_remote), - "http://github.com/url/both/libgit2"); - cl_assert_equal_s( - git_remote_pushurl(g_remote), - "git@github.com:url/both/libgit2"); -} - -void test_remote_insteadof__pushurl_insteadof_fetch(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); - cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF_PUSHURL_FETCH)); - - cl_assert_equal_s( - git_remote_url(g_remote), - "http://github.com/url/fetch/libgit2"); - cl_assert_equal_s( - git_remote_pushurl(g_remote), - "http://github.com/url/fetch/libgit2-push"); -} - -void test_remote_insteadof__pushurl_insteadof_push(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); - cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF_PUSHURL_PUSH)); - - cl_assert_equal_s( - git_remote_url(g_remote), - "http://example.com/url/push/libgit2"); - cl_assert_equal_s( - git_remote_pushurl(g_remote), - "http://example.com/url/push/libgit2-push"); -} - -void test_remote_insteadof__pushurl_insteadof_both(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); - cl_git_pass(git_remote_lookup(&g_remote, g_repo, REMOTE_INSTEADOF_PUSHURL_BOTH)); - - cl_assert_equal_s( - git_remote_url(g_remote), - "http://github.com/url/both/libgit2"); - cl_assert_equal_s( - git_remote_pushurl(g_remote), - "http://github.com/url/both/libgit2-push"); -} - -void test_remote_insteadof__anonymous_remote_fetch(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); - cl_git_pass(git_remote_create_anonymous(&g_remote, g_repo, - "http://example.com/url/fetch/libgit2")); - - cl_assert_equal_s( - git_remote_url(g_remote), - "http://github.com/url/fetch/libgit2"); - cl_assert_equal_p(git_remote_pushurl(g_remote), NULL); -} - -void test_remote_insteadof__anonymous_remote_push(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); - cl_git_pass(git_remote_create_anonymous(&g_remote, g_repo, - "http://example.com/url/push/libgit2")); - - cl_assert_equal_s( - git_remote_url(g_remote), - "http://example.com/url/push/libgit2"); - cl_assert_equal_s( - git_remote_pushurl(g_remote), - "git@github.com:url/push/libgit2"); -} - -void test_remote_insteadof__anonymous_remote_both(void) -{ - cl_git_pass(git_repository_open(&g_repo, cl_fixture(REPO_PATH))); - cl_git_pass(git_remote_create_anonymous(&g_remote, g_repo, - "http://example.com/url/both/libgit2")); - - cl_assert_equal_s( - git_remote_url(g_remote), - "http://github.com/url/both/libgit2"); - cl_assert_equal_s( - git_remote_pushurl(g_remote), - "git@github.com:url/both/libgit2"); -} diff --git a/tests/remote/list.c b/tests/remote/list.c deleted file mode 100644 index 4a6be3d1b..000000000 --- a/tests/remote/list.c +++ /dev/null @@ -1,43 +0,0 @@ -#include "clar_libgit2.h" -#include "config/config_helpers.h" - -static git_repository *_repo; - -#define TEST_URL "http://github.com/libgit2/libgit2.git" - -void test_remote_list__initialize(void) -{ - _repo = cl_git_sandbox_init("testrepo"); -} - -void test_remote_list__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_remote_list__always_checks_disk_config(void) -{ - git_repository *repo; - git_strarray remotes; - git_remote *remote; - - cl_git_pass(git_repository_open(&repo, git_repository_path(_repo))); - - cl_git_pass(git_remote_list(&remotes, _repo)); - cl_assert_equal_sz(remotes.count, 1); - git_strarray_dispose(&remotes); - - cl_git_pass(git_remote_create(&remote, _repo, "valid-name", TEST_URL)); - - cl_git_pass(git_remote_list(&remotes, _repo)); - cl_assert_equal_sz(remotes.count, 2); - git_strarray_dispose(&remotes); - - cl_git_pass(git_remote_list(&remotes, repo)); - cl_assert_equal_sz(remotes.count, 2); - git_strarray_dispose(&remotes); - - git_repository_free(repo); - git_remote_free(remote); -} - diff --git a/tests/repo/config.c b/tests/repo/config.c deleted file mode 100644 index ee7e43dff..000000000 --- a/tests/repo/config.c +++ /dev/null @@ -1,211 +0,0 @@ -#include "clar_libgit2.h" -#include "sysdir.h" -#include "futils.h" -#include - -static git_str path = GIT_STR_INIT; - -void test_repo_config__initialize(void) -{ - cl_fixture_sandbox("empty_standard_repo"); - cl_git_pass(cl_rename( - "empty_standard_repo/.gitted", "empty_standard_repo/.git")); - - git_str_clear(&path); - - cl_must_pass(p_mkdir("alternate", 0777)); - cl_git_pass(git_fs_path_prettify(&path, "alternate", NULL)); -} - -void test_repo_config__cleanup(void) -{ - cl_sandbox_set_search_path_defaults(); - - git_str_dispose(&path); - - cl_git_pass( - git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_assert(!git_fs_path_isdir("alternate")); - - cl_fixture_cleanup("empty_standard_repo"); - -} - -void test_repo_config__can_open_global_when_there_is_no_file(void) -{ - git_repository *repo; - git_config *config, *global; - - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); - - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_open_level( - &global, config, GIT_CONFIG_LEVEL_GLOBAL)); - - cl_git_pass(git_config_set_string(global, "test.set", "42")); - - git_config_free(global); - git_config_free(config); - git_repository_free(repo); -} - -void test_repo_config__can_open_missing_global_with_separators(void) -{ - git_repository *repo; - git_config *config, *global; - - cl_git_pass(git_str_printf( - &path, "%c%s", GIT_PATH_LIST_SEPARATOR, "dummy")); - - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); - - git_str_dispose(&path); - - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_open_level( - &global, config, GIT_CONFIG_LEVEL_GLOBAL)); - - cl_git_pass(git_config_set_string(global, "test.set", "42")); - - git_config_free(global); - git_config_free(config); - git_repository_free(repo); -} - -#include "repository.h" - -void test_repo_config__read_with_no_configs_at_all(void) -{ - git_repository *repo; - int val; - - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); - - /* with none */ - - cl_must_pass(p_unlink("empty_standard_repo/.git/config")); - cl_assert(!git_fs_path_isfile("empty_standard_repo/.git/config")); - - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__configmap_lookup_cache_clear(repo); - val = -1; - cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); - cl_assert_equal_i(GIT_ABBREV_DEFAULT, val); - git_repository_free(repo); - - /* with no local config, just system */ - - cl_sandbox_set_search_path_defaults(); - - cl_must_pass(p_mkdir("alternate/1", 0777)); - cl_git_pass(git_str_joinpath(&path, path.ptr, "1")); - cl_git_rewritefile("alternate/1/gitconfig", "[core]\n\tabbrev = 10\n"); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); - - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__configmap_lookup_cache_clear(repo); - val = -1; - cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); - cl_assert_equal_i(10, val); - git_repository_free(repo); - - /* with just xdg + system */ - - cl_must_pass(p_mkdir("alternate/2", 0777)); - path.ptr[path.size - 1] = '2'; - cl_git_rewritefile("alternate/2/config", "[core]\n\tabbrev = 20\n"); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); - - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__configmap_lookup_cache_clear(repo); - val = -1; - cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); - cl_assert_equal_i(20, val); - git_repository_free(repo); - - /* with global + xdg + system */ - - cl_must_pass(p_mkdir("alternate/3", 0777)); - path.ptr[path.size - 1] = '3'; - cl_git_rewritefile("alternate/3/.gitconfig", "[core]\n\tabbrev = 30\n"); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); - - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__configmap_lookup_cache_clear(repo); - val = -1; - cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); - cl_assert_equal_i(30, val); - git_repository_free(repo); - - /* with all configs */ - - cl_git_rewritefile("empty_standard_repo/.git/config", "[core]\n\tabbrev = 40\n"); - - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__configmap_lookup_cache_clear(repo); - val = -1; - cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); - cl_assert_equal_i(40, val); - git_repository_free(repo); - - /* with all configs but delete the files ? */ - - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__configmap_lookup_cache_clear(repo); - val = -1; - cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); - cl_assert_equal_i(40, val); - - cl_must_pass(p_unlink("empty_standard_repo/.git/config")); - cl_assert(!git_fs_path_isfile("empty_standard_repo/.git/config")); - - cl_must_pass(p_unlink("alternate/1/gitconfig")); - cl_assert(!git_fs_path_isfile("alternate/1/gitconfig")); - - cl_must_pass(p_unlink("alternate/2/config")); - cl_assert(!git_fs_path_isfile("alternate/2/config")); - - cl_must_pass(p_unlink("alternate/3/.gitconfig")); - cl_assert(!git_fs_path_isfile("alternate/3/.gitconfig")); - - git_repository__configmap_lookup_cache_clear(repo); - val = -1; - cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); - cl_assert_equal_i(40, val); - git_repository_free(repo); - - /* reopen */ - - cl_assert(!git_fs_path_isfile("empty_standard_repo/.git/config")); - cl_assert(!git_fs_path_isfile("alternate/3/.gitconfig")); - - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__configmap_lookup_cache_clear(repo); - val = -1; - cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); - cl_assert_equal_i(7, val); - git_repository_free(repo); - - cl_assert(!git_fs_path_exists("empty_standard_repo/.git/config")); - cl_assert(!git_fs_path_exists("alternate/3/.gitconfig")); -} diff --git a/tests/repo/discover.c b/tests/repo/discover.c deleted file mode 100644 index 523fdf8e3..000000000 --- a/tests/repo/discover.c +++ /dev/null @@ -1,210 +0,0 @@ -#include "clar_libgit2.h" - -#include "odb.h" -#include "futils.h" -#include "repository.h" - -#define TEMP_REPO_FOLDER "temprepo/" -#define DISCOVER_FOLDER TEMP_REPO_FOLDER "discover.git" - -#define SUB_REPOSITORY_FOLDER_NAME "sub_repo" -#define SUB_REPOSITORY_FOLDER DISCOVER_FOLDER "/" SUB_REPOSITORY_FOLDER_NAME -#define SUB_REPOSITORY_GITDIR SUB_REPOSITORY_FOLDER "/.git" -#define SUB_REPOSITORY_FOLDER_SUB SUB_REPOSITORY_FOLDER "/sub" -#define SUB_REPOSITORY_FOLDER_SUB_SUB SUB_REPOSITORY_FOLDER_SUB "/subsub" -#define SUB_REPOSITORY_FOLDER_SUB_SUB_SUB SUB_REPOSITORY_FOLDER_SUB_SUB "/subsubsub" - -#define REPOSITORY_ALTERNATE_FOLDER DISCOVER_FOLDER "/alternate_sub_repo" -#define REPOSITORY_ALTERNATE_FOLDER_SUB REPOSITORY_ALTERNATE_FOLDER "/sub" -#define REPOSITORY_ALTERNATE_FOLDER_SUB_SUB REPOSITORY_ALTERNATE_FOLDER_SUB "/subsub" -#define REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB REPOSITORY_ALTERNATE_FOLDER_SUB_SUB "/subsubsub" - -#define ALTERNATE_MALFORMED_FOLDER1 DISCOVER_FOLDER "/alternate_malformed_repo1" -#define ALTERNATE_MALFORMED_FOLDER2 DISCOVER_FOLDER "/alternate_malformed_repo2" -#define ALTERNATE_MALFORMED_FOLDER3 DISCOVER_FOLDER "/alternate_malformed_repo3" -#define ALTERNATE_NOT_FOUND_FOLDER DISCOVER_FOLDER "/alternate_not_found_repo" - -static void ensure_repository_discover(const char *start_path, - const char *ceiling_dirs, - const char *expected_path) -{ - git_buf found_path = GIT_BUF_INIT; - git_str resolved = GIT_STR_INIT; - - git_str_attach(&resolved, p_realpath(expected_path, NULL), 0); - cl_assert(resolved.size > 0); - cl_git_pass(git_fs_path_to_dir(&resolved)); - cl_git_pass(git_repository_discover(&found_path, start_path, 1, ceiling_dirs)); - - cl_assert_equal_s(found_path.ptr, resolved.ptr); - - git_str_dispose(&resolved); - git_buf_dispose(&found_path); -} - -static void write_file(const char *path, const char *content) -{ - git_file file; - int error; - - if (git_fs_path_exists(path)) { - cl_git_pass(p_unlink(path)); - } - - file = git_futils_creat_withpath(path, 0777, 0666); - cl_assert(file >= 0); - - error = p_write(file, content, strlen(content) * sizeof(char)); - p_close(file); - cl_git_pass(error); -} - -/*no check is performed on ceiling_dirs length, so be sure it's long enough */ -static void append_ceiling_dir(git_str *ceiling_dirs, const char *path) -{ - git_str pretty_path = GIT_STR_INIT; - char ceiling_separator[2] = { GIT_PATH_LIST_SEPARATOR, '\0' }; - - cl_git_pass(git_fs_path_prettify_dir(&pretty_path, path, NULL)); - - if (ceiling_dirs->size > 0) - git_str_puts(ceiling_dirs, ceiling_separator); - - git_str_puts(ceiling_dirs, pretty_path.ptr); - - git_str_dispose(&pretty_path); - cl_assert(git_str_oom(ceiling_dirs) == 0); -} - -static git_buf discovered; -static git_str ceiling_dirs; - -void test_repo_discover__initialize(void) -{ - git_repository *repo; - const mode_t mode = 0777; - git_futils_mkdir_r(DISCOVER_FOLDER, mode); - - git_str_init(&ceiling_dirs, 0); - append_ceiling_dir(&ceiling_dirs, TEMP_REPO_FOLDER); - - cl_git_pass(git_repository_init(&repo, DISCOVER_FOLDER, 1)); - git_repository_free(repo); - - cl_git_pass(git_repository_init(&repo, SUB_REPOSITORY_FOLDER, 0)); - cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, mode)); - cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, mode)); - - cl_git_pass(git_futils_mkdir_r(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, mode)); - write_file(REPOSITORY_ALTERNATE_FOLDER "/" DOT_GIT, "gitdir: ../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT); - write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB "/" DOT_GIT, "gitdir: ../../../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT); - write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB "/" DOT_GIT, "gitdir: ../../../../"); - - cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER1, mode)); - write_file(ALTERNATE_MALFORMED_FOLDER1 "/" DOT_GIT, "Anything but not gitdir:"); - cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER2, mode)); - write_file(ALTERNATE_MALFORMED_FOLDER2 "/" DOT_GIT, "gitdir:"); - cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER3, mode)); - write_file(ALTERNATE_MALFORMED_FOLDER3 "/" DOT_GIT, "gitdir: \n\n\n"); - cl_git_pass(git_futils_mkdir_r(ALTERNATE_NOT_FOUND_FOLDER, mode)); - write_file(ALTERNATE_NOT_FOUND_FOLDER "/" DOT_GIT, "gitdir: a_repository_that_surely_does_not_exist"); - - git_repository_free(repo); -} - -void test_repo_discover__cleanup(void) -{ - git_buf_dispose(&discovered); - git_str_dispose(&ceiling_dirs); - cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_repo_discover__discovering_repo_with_exact_path_succeeds(void) -{ - cl_git_pass(git_repository_discover(&discovered, DISCOVER_FOLDER, 0, ceiling_dirs.ptr)); - cl_git_pass(git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER, 0, ceiling_dirs.ptr)); -} - -void test_repo_discover__discovering_nonexistent_dir_fails(void) -{ - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, DISCOVER_FOLDER "-nonexistent", 0, NULL)); -} - -void test_repo_discover__discovering_repo_with_subdirectory_succeeds(void) -{ - ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); - ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); - ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); -} - -void test_repo_discover__discovering_repository_with_alternative_gitdir_succeeds(void) -{ - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs.ptr, DISCOVER_FOLDER); -} - -void test_repo_discover__discovering_repository_with_malformed_alternative_gitdir_fails(void) -{ - cl_git_fail(git_repository_discover(&discovered, ALTERNATE_MALFORMED_FOLDER1, 0, ceiling_dirs.ptr)); - cl_git_fail(git_repository_discover(&discovered, ALTERNATE_MALFORMED_FOLDER2, 0, ceiling_dirs.ptr)); - cl_git_fail(git_repository_discover(&discovered, ALTERNATE_MALFORMED_FOLDER3, 0, ceiling_dirs.ptr)); - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, ALTERNATE_NOT_FOUND_FOLDER, 0, ceiling_dirs.ptr)); -} - -void test_repo_discover__discovering_repository_with_ceiling(void) -{ - append_ceiling_dir(&ceiling_dirs, SUB_REPOSITORY_FOLDER_SUB); - - /* this must pass as ceiling_directories cannot prevent the current - * working directory to be checked */ - ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); - - ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs.ptr)); - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs.ptr)); -} - -void test_repo_discover__other_ceiling(void) -{ - append_ceiling_dir(&ceiling_dirs, SUB_REPOSITORY_FOLDER); - - /* this must pass as ceiling_directories cannot predent the current - * working directory to be checked */ - ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); - - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB, 0, ceiling_dirs.ptr)); - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs.ptr)); - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs.ptr)); -} - -void test_repo_discover__ceiling_should_not_affect_gitdir_redirection(void) -{ - append_ceiling_dir(&ceiling_dirs, SUB_REPOSITORY_FOLDER); - - /* gitfile redirection should not be affected by ceiling directories */ - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs.ptr, DISCOVER_FOLDER); -} - -void test_repo_discover__discovery_starting_at_file_succeeds(void) -{ - int fd; - - cl_assert((fd = p_creat(SUB_REPOSITORY_FOLDER "/file", 0600)) >= 0); - cl_assert(p_close(fd) == 0); - - ensure_repository_discover(SUB_REPOSITORY_FOLDER "/file", ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); -} - -void test_repo_discover__discovery_starting_at_system_root_causes_no_hang(void) -{ -#ifdef GIT_WIN32 - git_buf out = GIT_BUF_INIT; - cl_git_fail(git_repository_discover(&out, "C:/", 0, NULL)); - cl_git_fail(git_repository_discover(&out, "//localhost/", 0, NULL)); -#endif -} diff --git a/tests/repo/env.c b/tests/repo/env.c deleted file mode 100644 index e3e522480..000000000 --- a/tests/repo/env.c +++ /dev/null @@ -1,277 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "sysdir.h" -#include - -static void clear_git_env(void) -{ - cl_setenv("GIT_DIR", NULL); - cl_setenv("GIT_CEILING_DIRECTORIES", NULL); - cl_setenv("GIT_INDEX_FILE", NULL); - cl_setenv("GIT_NAMESPACE", NULL); - cl_setenv("GIT_OBJECT_DIRECTORY", NULL); - cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); - cl_setenv("GIT_WORK_TREE", NULL); - cl_setenv("GIT_COMMON_DIR", NULL); -} - -void test_repo_env__initialize(void) -{ - clear_git_env(); -} - -void test_repo_env__cleanup(void) -{ - cl_git_sandbox_cleanup(); - - if (git_fs_path_isdir("attr")) - git_futils_rmdir_r("attr", NULL, GIT_RMDIR_REMOVE_FILES); - if (git_fs_path_isdir("testrepo.git")) - git_futils_rmdir_r("testrepo.git", NULL, GIT_RMDIR_REMOVE_FILES); - if (git_fs_path_isdir("peeled.git")) - git_futils_rmdir_r("peeled.git", NULL, GIT_RMDIR_REMOVE_FILES); - - clear_git_env(); -} - -static int GIT_FORMAT_PRINTF(2, 3) cl_setenv_printf(const char *name, const char *fmt, ...) -{ - int ret; - va_list args; - git_str buf = GIT_STR_INIT; - - va_start(args, fmt); - cl_git_pass(git_str_vprintf(&buf, fmt, args)); - va_end(args); - - ret = cl_setenv(name, git_str_cstr(&buf)); - git_str_dispose(&buf); - return ret; -} - -/* Helper functions for test_repo_open__env, passing through the file and line - * from the caller rather than those of the helper. The expression strings - * distinguish between the possible failures within the helper. */ - -static void env_pass_(const char *path, const char *file, const char *func, int line) -{ - git_repository *repo; - cl_git_expect(git_repository_open_ext(NULL, path, GIT_REPOSITORY_OPEN_FROM_ENV, NULL), 0, file, func, line); - cl_git_expect(git_repository_open_ext(&repo, path, GIT_REPOSITORY_OPEN_FROM_ENV, NULL), 0, file, func, line); - cl_assert_at_line(git__suffixcmp(git_repository_path(repo), "attr/.git/") == 0, file, func, line); - cl_assert_at_line(git__suffixcmp(git_repository_workdir(repo), "attr/") == 0, file, func, line); - cl_assert_at_line(!git_repository_is_bare(repo), file, func, line); - git_repository_free(repo); -} -#define env_pass(path) env_pass_((path), __FILE__, __func__, __LINE__) - -#define cl_git_fail_at_line(expr, file, func, line) clar__assert((expr) < 0, file, func, line, "Expected function call to fail: " #expr, NULL, 1) - -static void env_fail_(const char *path, const char *file, const char *func, int line) -{ - git_repository *repo; - cl_git_fail_at_line(git_repository_open_ext(NULL, path, GIT_REPOSITORY_OPEN_FROM_ENV, NULL), file, func, line); - cl_git_fail_at_line(git_repository_open_ext(&repo, path, GIT_REPOSITORY_OPEN_FROM_ENV, NULL), file, func, line); -} -#define env_fail(path) env_fail_((path), __FILE__, __func__, __LINE__) - -static void env_cd_( - const char *path, - void (*passfail_)(const char *, const char *, const char *, int), - const char *file, const char *func, int line) -{ - git_str cwd_buf = GIT_STR_INIT; - cl_git_pass(git_fs_path_prettify_dir(&cwd_buf, ".", NULL)); - cl_must_pass(p_chdir(path)); - passfail_(NULL, file, func, line); - cl_must_pass(p_chdir(git_str_cstr(&cwd_buf))); - git_str_dispose(&cwd_buf); -} -#define env_cd_pass(path) env_cd_((path), env_pass_, __FILE__, __func__, __LINE__) -#define env_cd_fail(path) env_cd_((path), env_fail_, __FILE__, __func__, __LINE__) - -static void env_check_objects_(bool a, bool t, bool p, const char *file, const char *func, int line) -{ - git_repository *repo; - git_oid oid_a, oid_t, oid_p; - git_object *object; - cl_git_pass(git_oid_fromstr(&oid_a, "45141a79a77842c59a63229403220a4e4be74e3d")); - cl_git_pass(git_oid_fromstr(&oid_t, "1385f264afb75a56a5bec74243be9b367ba4ca08")); - cl_git_pass(git_oid_fromstr(&oid_p, "0df1a5865c8abfc09f1f2182e6a31be550e99f07")); - cl_git_expect(git_repository_open_ext(&repo, "attr", GIT_REPOSITORY_OPEN_FROM_ENV, NULL), 0, file, func, line); - - if (a) { - cl_git_expect(git_object_lookup(&object, repo, &oid_a, GIT_OBJECT_BLOB), 0, file, func, line); - git_object_free(object); - } else { - cl_git_fail_at_line(git_object_lookup(&object, repo, &oid_a, GIT_OBJECT_BLOB), file, func, line); - } - - if (t) { - cl_git_expect(git_object_lookup(&object, repo, &oid_t, GIT_OBJECT_BLOB), 0, file, func, line); - git_object_free(object); - } else { - cl_git_fail_at_line(git_object_lookup(&object, repo, &oid_t, GIT_OBJECT_BLOB), file, func, line); - } - - if (p) { - cl_git_expect(git_object_lookup(&object, repo, &oid_p, GIT_OBJECT_COMMIT), 0, file, func, line); - git_object_free(object); - } else { - cl_git_fail_at_line(git_object_lookup(&object, repo, &oid_p, GIT_OBJECT_COMMIT), file, func, line); - } - - git_repository_free(repo); -} -#define env_check_objects(a, t, t2) env_check_objects_((a), (t), (t2), __FILE__, __func__, __LINE__) - -void test_repo_env__open(void) -{ - git_repository *repo = NULL; - git_str repo_dir_buf = GIT_STR_INIT; - const char *repo_dir = NULL; - git_index *index = NULL; - const char *t_obj = "testrepo.git/objects"; - const char *p_obj = "peeled.git/objects"; - - clear_git_env(); - - cl_fixture_sandbox("attr"); - cl_fixture_sandbox("testrepo.git"); - cl_fixture_sandbox("peeled.git"); - cl_git_pass(p_rename("attr/.gitted", "attr/.git")); - - cl_git_pass(git_fs_path_prettify_dir(&repo_dir_buf, "attr", NULL)); - repo_dir = git_str_cstr(&repo_dir_buf); - - /* GIT_DIR that doesn't exist */ - cl_setenv("GIT_DIR", "does-not-exist"); - env_fail(NULL); - /* Explicit start_path overrides GIT_DIR */ - env_pass("attr"); - env_pass("attr/.git"); - env_pass("attr/sub"); - env_pass("attr/sub/sub"); - - /* GIT_DIR with relative paths */ - cl_setenv("GIT_DIR", "attr/.git"); - env_pass(NULL); - cl_setenv("GIT_DIR", "attr"); - env_fail(NULL); - cl_setenv("GIT_DIR", "attr/sub"); - env_fail(NULL); - cl_setenv("GIT_DIR", "attr/sub/sub"); - env_fail(NULL); - - /* GIT_DIR with absolute paths */ - cl_setenv_printf("GIT_DIR", "%s/.git", repo_dir); - env_pass(NULL); - cl_setenv("GIT_DIR", repo_dir); - env_fail(NULL); - cl_setenv_printf("GIT_DIR", "%s/sub", repo_dir); - env_fail(NULL); - cl_setenv_printf("GIT_DIR", "%s/sub/sub", repo_dir); - env_fail(NULL); - cl_setenv("GIT_DIR", NULL); - - /* Searching from the current directory */ - env_cd_pass("attr"); - env_cd_pass("attr/.git"); - env_cd_pass("attr/sub"); - env_cd_pass("attr/sub/sub"); - - /* A ceiling directory blocks searches from ascending into that - * directory, but doesn't block the start_path itself. */ - cl_setenv("GIT_CEILING_DIRECTORIES", repo_dir); - env_cd_pass("attr"); - env_cd_fail("attr/sub"); - env_cd_fail("attr/sub/sub"); - - cl_setenv_printf("GIT_CEILING_DIRECTORIES", "%s/sub", repo_dir); - env_cd_pass("attr"); - env_cd_pass("attr/sub"); - env_cd_fail("attr/sub/sub"); - - /* Multiple ceiling directories */ - cl_setenv_printf("GIT_CEILING_DIRECTORIES", "123%c%s/sub%cabc", - GIT_PATH_LIST_SEPARATOR, repo_dir, GIT_PATH_LIST_SEPARATOR); - env_cd_pass("attr"); - env_cd_pass("attr/sub"); - env_cd_fail("attr/sub/sub"); - - cl_setenv_printf("GIT_CEILING_DIRECTORIES", "%s%c%s/sub", - repo_dir, GIT_PATH_LIST_SEPARATOR, repo_dir); - env_cd_pass("attr"); - env_cd_fail("attr/sub"); - env_cd_fail("attr/sub/sub"); - - cl_setenv_printf("GIT_CEILING_DIRECTORIES", "%s/sub%c%s", - repo_dir, GIT_PATH_LIST_SEPARATOR, repo_dir); - env_cd_pass("attr"); - env_cd_fail("attr/sub"); - env_cd_fail("attr/sub/sub"); - - cl_setenv_printf("GIT_CEILING_DIRECTORIES", "%s%c%s/sub/sub", - repo_dir, GIT_PATH_LIST_SEPARATOR, repo_dir); - env_cd_pass("attr"); - env_cd_fail("attr/sub"); - env_cd_fail("attr/sub/sub"); - - cl_setenv("GIT_CEILING_DIRECTORIES", NULL); - - /* Index files */ - cl_setenv("GIT_INDEX_FILE", cl_fixture("gitgit.index")); - cl_git_pass(git_repository_open_ext(&repo, "attr", GIT_REPOSITORY_OPEN_FROM_ENV, NULL)); - cl_git_pass(git_repository_index(&index, repo)); - cl_assert_equal_s(git_index_path(index), cl_fixture("gitgit.index")); - cl_assert_equal_i(git_index_entrycount(index), 1437); - git_index_free(index); - git_repository_free(repo); - cl_setenv("GIT_INDEX_FILE", NULL); - - /* Namespaces */ - cl_setenv("GIT_NAMESPACE", "some-namespace"); - cl_git_pass(git_repository_open_ext(&repo, "attr", GIT_REPOSITORY_OPEN_FROM_ENV, NULL)); - cl_assert_equal_s(git_repository_get_namespace(repo), "some-namespace"); - git_repository_free(repo); - cl_setenv("GIT_NAMESPACE", NULL); - - /* Object directories and alternates */ - env_check_objects(true, false, false); - - cl_setenv("GIT_OBJECT_DIRECTORY", t_obj); - env_check_objects(false, true, false); - cl_setenv("GIT_OBJECT_DIRECTORY", NULL); - - cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", t_obj); - env_check_objects(true, true, false); - cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); - - cl_setenv("GIT_OBJECT_DIRECTORY", p_obj); - env_check_objects(false, false, true); - cl_setenv("GIT_OBJECT_DIRECTORY", NULL); - - cl_setenv("GIT_OBJECT_DIRECTORY", t_obj); - cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", p_obj); - env_check_objects(false, true, true); - cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); - cl_setenv("GIT_OBJECT_DIRECTORY", NULL); - - cl_setenv_printf("GIT_ALTERNATE_OBJECT_DIRECTORIES", - "%s%c%s", t_obj, GIT_PATH_LIST_SEPARATOR, p_obj); - env_check_objects(true, true, true); - cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); - - cl_setenv_printf("GIT_ALTERNATE_OBJECT_DIRECTORIES", - "%s%c%s", p_obj, GIT_PATH_LIST_SEPARATOR, t_obj); - env_check_objects(true, true, true); - cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); - - cl_fixture_cleanup("peeled.git"); - cl_fixture_cleanup("testrepo.git"); - cl_fixture_cleanup("attr"); - - git_str_dispose(&repo_dir_buf); - - clear_git_env(); -} diff --git a/tests/repo/extensions.c b/tests/repo/extensions.c deleted file mode 100644 index e7772acd5..000000000 --- a/tests/repo/extensions.c +++ /dev/null @@ -1,72 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "sysdir.h" -#include - -git_repository *repo; - -void test_repo_extensions__initialize(void) -{ - git_config *config; - - repo = cl_git_sandbox_init("empty_bare.git"); - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 1)); - git_config_free(config); -} - -void test_repo_extensions__cleanup(void) -{ - cl_git_sandbox_cleanup(); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, NULL, 0)); -} - -void test_repo_extensions__builtin(void) -{ - git_repository *extended; - - cl_repo_set_string(repo, "extensions.noop", "foobar"); - - cl_git_pass(git_repository_open(&extended, "empty_bare.git")); - cl_assert(git_repository_path(extended) != NULL); - cl_assert(git__suffixcmp(git_repository_path(extended), "/") == 0); - git_repository_free(extended); -} - -void test_repo_extensions__negate_builtin(void) -{ - const char *in[] = { "foo", "!noop", "baz" }; - git_repository *extended; - - cl_repo_set_string(repo, "extensions.noop", "foobar"); - - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); - - cl_git_fail(git_repository_open(&extended, "empty_bare.git")); - git_repository_free(extended); -} - -void test_repo_extensions__unsupported(void) -{ - git_repository *extended = NULL; - - cl_repo_set_string(repo, "extensions.unknown", "foobar"); - - cl_git_fail(git_repository_open(&extended, "empty_bare.git")); - git_repository_free(extended); -} - -void test_repo_extensions__adds_extension(void) -{ - const char *in[] = { "foo", "!noop", "newextension", "baz" }; - git_repository *extended; - - cl_repo_set_string(repo, "extensions.newextension", "foobar"); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); - - cl_git_pass(git_repository_open(&extended, "empty_bare.git")); - cl_assert(git_repository_path(extended) != NULL); - cl_assert(git__suffixcmp(git_repository_path(extended), "/") == 0); - git_repository_free(extended); -} diff --git a/tests/repo/getters.c b/tests/repo/getters.c deleted file mode 100644 index d401bb832..000000000 --- a/tests/repo/getters.c +++ /dev/null @@ -1,53 +0,0 @@ -#include "clar_libgit2.h" -#include "repo/repo_helpers.h" - -void test_repo_getters__is_empty_correctly_deals_with_pristine_looking_repos(void) -{ - git_repository *repo; - - repo = cl_git_sandbox_init("empty_bare.git"); - cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt"); - - cl_assert_equal_i(true, git_repository_is_empty(repo)); - - cl_git_sandbox_cleanup(); -} - -void test_repo_getters__is_empty_can_detect_used_repositories(void) -{ - git_repository *repo; - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - - cl_assert_equal_i(false, git_repository_is_empty(repo)); - - git_repository_free(repo); -} - -void test_repo_getters__is_empty_can_detect_repositories_with_defaultbranch_config_empty(void) -{ - git_repository *repo; - - create_tmp_global_config("tmp_global_path", "init.defaultBranch", ""); - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_assert_equal_i(false, git_repository_is_empty(repo)); - - git_repository_free(repo); -} - -void test_repo_getters__retrieving_the_odb_honors_the_refcount(void) -{ - git_odb *odb; - git_repository *repo; - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - - cl_git_pass(git_repository_odb(&odb, repo)); - cl_assert(((git_refcount *)odb)->refcount.val == 2); - - git_repository_free(repo); - cl_assert(((git_refcount *)odb)->refcount.val == 1); - - git_odb_free(odb); -} diff --git a/tests/repo/hashfile.c b/tests/repo/hashfile.c deleted file mode 100644 index e23bb77f9..000000000 --- a/tests/repo/hashfile.c +++ /dev/null @@ -1,171 +0,0 @@ -#include "clar_libgit2.h" - -static git_repository *_repo; - -void test_repo_hashfile__initialize(void) -{ - _repo = cl_git_sandbox_init("status"); -} - -void test_repo_hashfile__cleanup(void) -{ - cl_fixture_cleanup("absolute"); - cl_git_sandbox_cleanup(); - _repo = NULL; -} - -void test_repo_hashfile__simple(void) -{ - git_oid a, b; - git_str full = GIT_STR_INIT; - - /* hash with repo relative path */ - cl_git_pass(git_odb_hashfile(&a, "status/current_file", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, "current_file", GIT_OBJECT_BLOB, NULL)); - cl_assert_equal_oid(&a, &b); - - cl_git_pass(git_str_joinpath(&full, git_repository_workdir(_repo), "current_file")); - - /* hash with full path */ - cl_git_pass(git_odb_hashfile(&a, full.ptr, GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, full.ptr, GIT_OBJECT_BLOB, NULL)); - cl_assert_equal_oid(&a, &b); - - /* hash with invalid type */ - cl_git_fail(git_odb_hashfile(&a, full.ptr, GIT_OBJECT_ANY)); - cl_git_fail(git_repository_hashfile(&b, _repo, full.ptr, GIT_OBJECT_OFS_DELTA, NULL)); - - git_str_dispose(&full); -} - -void test_repo_hashfile__filtered_in_workdir(void) -{ - git_str root = GIT_STR_INIT, txt = GIT_STR_INIT, bin = GIT_STR_INIT; - char cwd[GIT_PATH_MAX]; - git_oid a, b; - - cl_must_pass(p_getcwd(cwd, GIT_PATH_MAX)); - cl_must_pass(p_mkdir("absolute", 0777)); - cl_git_pass(git_str_joinpath(&root, cwd, "status")); - cl_git_pass(git_str_joinpath(&txt, root.ptr, "testfile.txt")); - cl_git_pass(git_str_joinpath(&bin, root.ptr, "testfile.bin")); - - cl_repo_set_bool(_repo, "core.autocrlf", true); - - cl_git_append2file("status/.gitattributes", "*.txt text\n*.bin binary\n\n"); - - /* create some sample content with CRLF in it */ - cl_git_mkfile("status/testfile.txt", "content\r\n"); - cl_git_mkfile("status/testfile.bin", "other\r\nstuff\r\n"); - - /* not equal hashes because of filtering */ - cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJECT_BLOB, NULL)); - cl_assert(git_oid_cmp(&a, &b)); - - /* not equal hashes because of filtering when specified by absolute path */ - cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, txt.ptr, GIT_OBJECT_BLOB, NULL)); - cl_assert(git_oid_cmp(&a, &b)); - - /* equal hashes because filter is binary */ - cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJECT_BLOB, NULL)); - cl_assert_equal_oid(&a, &b); - - /* equal hashes because filter is binary when specified by absolute path */ - cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, bin.ptr, GIT_OBJECT_BLOB, NULL)); - cl_assert_equal_oid(&a, &b); - - /* equal hashes when 'as_file' points to binary filtering */ - cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJECT_BLOB, "foo.bin")); - cl_assert_equal_oid(&a, &b); - - /* equal hashes when 'as_file' points to binary filtering (absolute path) */ - cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, txt.ptr, GIT_OBJECT_BLOB, "foo.bin")); - cl_assert_equal_oid(&a, &b); - - /* not equal hashes when 'as_file' points to text filtering */ - cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJECT_BLOB, "foo.txt")); - cl_assert(git_oid_cmp(&a, &b)); - - /* not equal hashes when 'as_file' points to text filtering */ - cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, bin.ptr, GIT_OBJECT_BLOB, "foo.txt")); - cl_assert(git_oid_cmp(&a, &b)); - - /* equal hashes when 'as_file' is empty and turns off filtering */ - cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJECT_BLOB, "")); - cl_assert_equal_oid(&a, &b); - - cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJECT_BLOB, "")); - cl_assert_equal_oid(&a, &b); - - cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, txt.ptr, GIT_OBJECT_BLOB, "")); - cl_assert_equal_oid(&a, &b); - - cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, bin.ptr, GIT_OBJECT_BLOB, "")); - cl_assert_equal_oid(&a, &b); - - /* some hash type failures */ - cl_git_fail(git_odb_hashfile(&a, "status/testfile.txt", 0)); - cl_git_fail(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJECT_ANY, NULL)); - - git_str_dispose(&txt); - git_str_dispose(&bin); - git_str_dispose(&root); -} - -void test_repo_hashfile__filtered_outside_workdir(void) -{ - git_str root = GIT_STR_INIT, txt = GIT_STR_INIT, bin = GIT_STR_INIT; - char cwd[GIT_PATH_MAX]; - git_oid a, b; - - cl_must_pass(p_getcwd(cwd, GIT_PATH_MAX)); - cl_must_pass(p_mkdir("absolute", 0777)); - cl_git_pass(git_str_joinpath(&root, cwd, "absolute")); - cl_git_pass(git_str_joinpath(&txt, root.ptr, "testfile.txt")); - cl_git_pass(git_str_joinpath(&bin, root.ptr, "testfile.bin")); - - cl_repo_set_bool(_repo, "core.autocrlf", true); - cl_git_append2file("status/.gitattributes", "*.txt text\n*.bin binary\n\n"); - - /* create some sample content with CRLF in it */ - cl_git_mkfile("absolute/testfile.txt", "content\r\n"); - cl_git_mkfile("absolute/testfile.bin", "other\r\nstuff\r\n"); - - /* not equal hashes because of filtering */ - cl_git_pass(git_odb_hashfile(&a, "absolute/testfile.txt", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, txt.ptr, GIT_OBJECT_BLOB, "testfile.txt")); - cl_assert(git_oid_cmp(&a, &b)); - - /* equal hashes because filter is binary */ - cl_git_pass(git_odb_hashfile(&a, "absolute/testfile.bin", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, bin.ptr, GIT_OBJECT_BLOB, "testfile.bin")); - cl_assert_equal_oid(&a, &b); - - /* - * equal hashes because no filtering occurs for absolute paths outside the working - * directory unless as_path is specified - */ - cl_git_pass(git_odb_hashfile(&a, "absolute/testfile.txt", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, txt.ptr, GIT_OBJECT_BLOB, NULL)); - cl_assert_equal_oid(&a, &b); - - cl_git_pass(git_odb_hashfile(&a, "absolute/testfile.bin", GIT_OBJECT_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, bin.ptr, GIT_OBJECT_BLOB, NULL)); - cl_assert_equal_oid(&a, &b); - - git_str_dispose(&txt); - git_str_dispose(&bin); - git_str_dispose(&root); -} diff --git a/tests/repo/head.c b/tests/repo/head.c deleted file mode 100644 index 822990555..000000000 --- a/tests/repo/head.c +++ /dev/null @@ -1,182 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" -#include "repo_helpers.h" -#include "posix.h" -#include "git2/annotated_commit.h" - -static const char *g_email = "foo@example.com"; -static git_repository *repo; - -void test_repo_head__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_set_ident(repo, "Foo Bar", g_email)); -} - -void test_repo_head__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_repo_head__unborn_head(void) -{ - git_reference *ref; - - cl_git_pass(git_repository_head_detached(repo)); - - make_head_unborn(repo, NON_EXISTING_HEAD); - - cl_assert(git_repository_head_unborn(repo) == 1); - - - /* take the repo back to it's original state */ - cl_git_pass(git_reference_symbolic_create(&ref, repo, "HEAD", "refs/heads/master", 1, NULL)); - cl_assert(git_repository_head_unborn(repo) == 0); - - git_reference_free(ref); -} - -void test_repo_head__set_head_Attaches_HEAD_to_un_unborn_branch_when_the_branch_doesnt_exist(void) -{ - git_reference *head; - - cl_git_pass(git_repository_set_head(repo, "refs/heads/doesnt/exist/yet")); - - cl_assert_equal_i(false, git_repository_head_detached(repo)); - - cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_head(&head, repo)); -} - -void test_repo_head__set_head_Returns_ENOTFOUND_when_the_reference_doesnt_exist(void) -{ - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_set_head(repo, "refs/tags/doesnt/exist/yet")); -} - -void test_repo_head__set_head_Fails_when_the_reference_points_to_a_non_commitish(void) -{ - cl_git_fail(git_repository_set_head(repo, "refs/tags/point_to_blob")); -} - -void test_repo_head__set_head_Attaches_HEAD_when_the_reference_points_to_a_branch(void) -{ - git_reference *head; - - cl_git_pass(git_repository_set_head(repo, "refs/heads/br2")); - - cl_assert_equal_i(false, git_repository_head_detached(repo)); - - cl_git_pass(git_repository_head(&head, repo)); - cl_assert_equal_s("refs/heads/br2", git_reference_name(head)); - - git_reference_free(head); -} - -static void assert_head_is_correctly_detached(void) -{ - git_reference *head; - git_object *commit; - - cl_assert_equal_i(true, git_repository_head_detached(repo)); - - cl_git_pass(git_repository_head(&head, repo)); - - cl_git_pass(git_object_lookup(&commit, repo, git_reference_target(head), GIT_OBJECT_COMMIT)); - - git_object_free(commit); - git_reference_free(head); -} - -void test_repo_head__set_head_Detaches_HEAD_when_the_reference_doesnt_point_to_a_branch(void) -{ - cl_git_pass(git_repository_set_head(repo, "refs/tags/test")); - - cl_assert_equal_i(true, git_repository_head_detached(repo)); - - assert_head_is_correctly_detached(); -} - -void test_repo_head__set_head_detached_Return_ENOTFOUND_when_the_object_doesnt_exist(void) -{ - git_oid oid; - - cl_git_pass(git_oid_fromstr(&oid, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); - - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_set_head_detached(repo, &oid)); -} - -void test_repo_head__set_head_detached_Fails_when_the_object_isnt_a_commitish(void) -{ - git_object *blob; - - cl_git_pass(git_revparse_single(&blob, repo, "point_to_blob")); - - cl_git_fail(git_repository_set_head_detached(repo, git_object_id(blob))); - - git_object_free(blob); -} - -void test_repo_head__set_head_detached_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void) -{ - git_object *tag; - - cl_git_pass(git_revparse_single(&tag, repo, "tags/test")); - cl_assert_equal_i(GIT_OBJECT_TAG, git_object_type(tag)); - - cl_git_pass(git_repository_set_head_detached(repo, git_object_id(tag))); - - assert_head_is_correctly_detached(); - - git_object_free(tag); -} - -void test_repo_head__detach_head_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void) -{ - cl_assert_equal_i(false, git_repository_head_detached(repo)); - - cl_git_pass(git_repository_detach_head(repo)); - - assert_head_is_correctly_detached(); -} - -void test_repo_head__detach_head_Fails_if_HEAD_and_point_to_a_non_commitish(void) -{ - git_reference *head; - - cl_git_pass(git_reference_symbolic_create(&head, repo, GIT_HEAD_FILE, "refs/tags/point_to_blob", 1, NULL)); - - cl_git_fail(git_repository_detach_head(repo)); - - git_reference_free(head); -} - -void test_repo_head__detaching_an_unborn_branch_returns_GIT_EUNBORNBRANCH(void) -{ - make_head_unborn(repo, NON_EXISTING_HEAD); - - cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_detach_head(repo)); -} - -void test_repo_head__retrieving_an_unborn_branch_returns_GIT_EUNBORNBRANCH(void) -{ - git_reference *head; - - make_head_unborn(repo, NON_EXISTING_HEAD); - - cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_head(&head, repo)); -} - -void test_repo_head__retrieving_a_missing_head_returns_GIT_ENOTFOUND(void) -{ - git_reference *head; - - delete_head(repo); - - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_head(&head, repo)); -} - -void test_repo_head__can_tell_if_an_unborn_head_is_detached(void) -{ - make_head_unborn(repo, NON_EXISTING_HEAD); - - cl_assert_equal_i(false, git_repository_head_detached(repo)); -} diff --git a/tests/repo/headtree.c b/tests/repo/headtree.c deleted file mode 100644 index e899ac399..000000000 --- a/tests/repo/headtree.c +++ /dev/null @@ -1,53 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" -#include "repo_helpers.h" -#include "posix.h" - -static git_repository *repo; -static git_tree *tree; - -void test_repo_headtree__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); - tree = NULL; -} - -void test_repo_headtree__cleanup(void) -{ - git_tree_free(tree); - cl_git_sandbox_cleanup(); -} - -void test_repo_headtree__can_retrieve_the_root_tree_from_a_detached_head(void) -{ - cl_git_pass(git_repository_detach_head(repo)); - - cl_git_pass(git_repository_head_tree(&tree, repo)); - - cl_assert(git_oid_streq(git_tree_id(tree), "az")); -} - -void test_repo_headtree__can_retrieve_the_root_tree_from_a_non_detached_head(void) -{ - cl_assert_equal_i(false, git_repository_head_detached(repo)); - - cl_git_pass(git_repository_head_tree(&tree, repo)); - - cl_assert(git_oid_streq(git_tree_id(tree), "az")); -} - -void test_repo_headtree__when_head_is_unborn_returns_EUNBORNBRANCH(void) -{ - make_head_unborn(repo, NON_EXISTING_HEAD); - - cl_assert_equal_i(true, git_repository_head_unborn(repo)); - - cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_head_tree(&tree, repo)); -} - -void test_repo_headtree__when_head_is_missing_returns_ENOTFOUND(void) -{ - delete_head(repo); - - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_head_tree(&tree, repo)); -} diff --git a/tests/repo/init.c b/tests/repo/init.c deleted file mode 100644 index 7cf6742ca..000000000 --- a/tests/repo/init.c +++ /dev/null @@ -1,738 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "repository.h" -#include "config.h" -#include "path.h" -#include "config/config_helpers.h" -#include "repo/repo_helpers.h" - -enum repo_mode { - STANDARD_REPOSITORY = 0, - BARE_REPOSITORY = 1 -}; - -static git_repository *g_repo = NULL; -static git_str g_global_path = GIT_STR_INIT; - -void test_repo_init__initialize(void) -{ - g_repo = NULL; - - git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, - &g_global_path); -} - -void test_repo_init__cleanup(void) -{ - git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, - g_global_path.ptr); - git_str_dispose(&g_global_path); - - cl_fixture_cleanup("tmp_global_path"); -} - -static void cleanup_repository(void *path) -{ - git_repository_free(g_repo); - g_repo = NULL; - - cl_fixture_cleanup((const char *)path); -} - -static void ensure_repository_init( - const char *working_directory, - int is_bare, - const char *expected_path_repository, - const char *expected_working_directory) -{ - const char *workdir; - - cl_assert(!git_fs_path_isdir(working_directory)); - - cl_git_pass(git_repository_init(&g_repo, working_directory, is_bare)); - - workdir = git_repository_workdir(g_repo); - if (workdir != NULL || expected_working_directory != NULL) { - cl_assert( - git__suffixcmp(workdir, expected_working_directory) == 0 - ); - } - - cl_assert( - git__suffixcmp(git_repository_path(g_repo), expected_path_repository) == 0 - ); - - cl_assert(git_repository_is_bare(g_repo) == is_bare); - -#ifdef GIT_WIN32 - if (!is_bare) { - DWORD fattrs = GetFileAttributes(git_repository_path(g_repo)); - cl_assert((fattrs & FILE_ATTRIBUTE_HIDDEN) != 0); - } -#endif - - cl_assert(git_repository_is_empty(g_repo)); -} - -void test_repo_init__standard_repo(void) -{ - cl_set_cleanup(&cleanup_repository, "testrepo"); - ensure_repository_init("testrepo/", 0, "testrepo/.git/", "testrepo/"); -} - -void test_repo_init__standard_repo_noslash(void) -{ - cl_set_cleanup(&cleanup_repository, "testrepo"); - ensure_repository_init("testrepo", 0, "testrepo/.git/", "testrepo/"); -} - -void test_repo_init__bare_repo(void) -{ - cl_set_cleanup(&cleanup_repository, "testrepo.git"); - ensure_repository_init("testrepo.git/", 1, "testrepo.git/", NULL); -} - -void test_repo_init__bare_repo_noslash(void) -{ - cl_set_cleanup(&cleanup_repository, "testrepo.git"); - ensure_repository_init("testrepo.git", 1, "testrepo.git/", NULL); -} - -void test_repo_init__bare_repo_escaping_current_workdir(void) -{ - git_str path_repository = GIT_STR_INIT; - git_str path_current_workdir = GIT_STR_INIT; - - cl_git_pass(git_fs_path_prettify_dir(&path_current_workdir, ".", NULL)); - - cl_git_pass(git_str_joinpath(&path_repository, git_str_cstr(&path_current_workdir), "a/b/c")); - cl_git_pass(git_futils_mkdir_r(git_str_cstr(&path_repository), GIT_DIR_MODE)); - - /* Change the current working directory */ - cl_git_pass(chdir(git_str_cstr(&path_repository))); - - /* Initialize a bare repo with a relative path escaping out of the current working directory */ - cl_git_pass(git_repository_init(&g_repo, "../d/e.git", 1)); - cl_git_pass(git__suffixcmp(git_repository_path(g_repo), "/a/b/d/e.git/")); - - git_repository_free(g_repo); - g_repo = NULL; - - /* Open a bare repo with a relative path escaping out of the current working directory */ - cl_git_pass(git_repository_open(&g_repo, "../d/e.git")); - - cl_git_pass(chdir(git_str_cstr(&path_current_workdir))); - - git_str_dispose(&path_current_workdir); - git_str_dispose(&path_repository); - - cleanup_repository("a"); -} - -void test_repo_init__reinit_bare_repo(void) -{ - cl_set_cleanup(&cleanup_repository, "reinit.git"); - - /* Initialize the repository */ - cl_git_pass(git_repository_init(&g_repo, "reinit.git", 1)); - git_repository_free(g_repo); - g_repo = NULL; - - /* Reinitialize the repository */ - cl_git_pass(git_repository_init(&g_repo, "reinit.git", 1)); -} - -void test_repo_init__reinit_too_recent_bare_repo(void) -{ - git_config *config; - - /* Initialize the repository */ - cl_git_pass(git_repository_init(&g_repo, "reinit.git", 1)); - git_repository_config(&config, g_repo); - - /* - * Hack the config of the repository to make it look like it has - * been created by a recenter version of git/libgit2 - */ - cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 42)); - - git_config_free(config); - git_repository_free(g_repo); - g_repo = NULL; - - /* Try to reinitialize the repository */ - cl_git_fail(git_repository_init(&g_repo, "reinit.git", 1)); - - cl_fixture_cleanup("reinit.git"); -} - -void test_repo_init__additional_templates(void) -{ - git_str path = GIT_STR_INIT; - - cl_set_cleanup(&cleanup_repository, "tester"); - - ensure_repository_init("tester", 0, "tester/.git/", "tester/"); - - cl_git_pass( - git_str_joinpath(&path, git_repository_path(g_repo), "description")); - cl_assert(git_fs_path_isfile(git_str_cstr(&path))); - - cl_git_pass( - git_str_joinpath(&path, git_repository_path(g_repo), "info/exclude")); - cl_assert(git_fs_path_isfile(git_str_cstr(&path))); - - cl_git_pass( - git_str_joinpath(&path, git_repository_path(g_repo), "hooks")); - cl_assert(git_fs_path_isdir(git_str_cstr(&path))); - /* won't confirm specific contents of hooks dir since it may vary */ - - git_str_dispose(&path); -} - -static void assert_config_entry_on_init_bytype( - const char *config_key, int expected_value, bool is_bare) -{ - git_config *config; - int error, current_value; - const char *repo_path = is_bare ? - "config_entry/test.bare.git" : "config_entry/test.non.bare.git"; - - cl_set_cleanup(&cleanup_repository, "config_entry"); - - cl_git_pass(git_repository_init(&g_repo, repo_path, is_bare)); - - cl_git_pass(git_repository_config(&config, g_repo)); - error = git_config_get_bool(¤t_value, config, config_key); - git_config_free(config); - - if (expected_value >= 0) { - cl_assert_equal_i(0, error); - cl_assert_equal_i(expected_value, current_value); - } else { - cl_assert_equal_i(expected_value, error); - } -} - -static void assert_config_entry_on_init( - const char *config_key, int expected_value) -{ - assert_config_entry_on_init_bytype(config_key, expected_value, true); - git_repository_free(g_repo); - g_repo = NULL; - - assert_config_entry_on_init_bytype(config_key, expected_value, false); -} - -void test_repo_init__detect_filemode(void) -{ - assert_config_entry_on_init("core.filemode", cl_is_chmod_supported()); -} - -void test_repo_init__detect_ignorecase(void) -{ - struct stat st; - bool found_without_match; - - cl_git_write2file("testCAPS", "whatever\n", 0, O_CREAT | O_WRONLY, 0666); - found_without_match = (p_stat("Testcaps", &st) == 0); - cl_must_pass(p_unlink("testCAPS")); - - assert_config_entry_on_init( - "core.ignorecase", found_without_match ? true : GIT_ENOTFOUND); -} - -/* - * Windows: if the filesystem supports symlinks (because we're running - * as administrator, or because the user has opted into it for normal - * users) then we can also opt-in explicitly by settings `core.symlinks` - * in the global config. Symlinks remain off by default. - */ - -void test_repo_init__symlinks_win32_enabled_by_global_config(void) -{ -#ifndef GIT_WIN32 - cl_skip(); -#else - git_config *config, *repo_config; - int val; - - if (!git_fs_path_supports_symlinks("link")) - cl_skip(); - - create_tmp_global_config("tmp_global_config", "core.symlinks", "true"); - - /* - * Create a new repository (can't use `assert_config_on_init` since we - * want to examine configuration levels with more granularity.) - */ - cl_git_pass(git_repository_init(&g_repo, "config_entry/test.non.bare.git", false)); - - /* Ensure that core.symlinks remains set (via the global config). */ - cl_git_pass(git_repository_config(&config, g_repo)); - cl_git_pass(git_config_get_bool(&val, config, "core.symlinks")); - cl_assert_equal_i(1, val); - - /* - * Ensure that the repository config does not set core.symlinks. - * It should remain inherited. - */ - cl_git_pass(git_config_open_level(&repo_config, config, GIT_CONFIG_LEVEL_LOCAL)); - cl_git_fail_with(GIT_ENOTFOUND, git_config_get_bool(&val, repo_config, "core.symlinks")); - git_config_free(repo_config); - - git_config_free(config); - - git_repository_free(g_repo); - g_repo = NULL; -#endif -} - -void test_repo_init__symlinks_win32_off_by_default(void) -{ -#ifndef GIT_WIN32 - cl_skip(); -#else - assert_config_entry_on_init("core.symlinks", false); -#endif -} - -void test_repo_init__symlinks_posix_detected(void) -{ -#ifdef GIT_WIN32 - cl_skip(); -#else - assert_config_entry_on_init( - "core.symlinks", git_fs_path_supports_symlinks("link") ? GIT_ENOTFOUND : false); -#endif -} - -void test_repo_init__detect_precompose_unicode_required(void) -{ -#ifdef GIT_USE_ICONV - char *composed = "ḱṷṓn", *decomposed = "ḱṷṓn"; - struct stat st; - bool found_with_nfd; - - cl_git_write2file(composed, "whatever\n", 0, O_CREAT | O_WRONLY, 0666); - found_with_nfd = (p_stat(decomposed, &st) == 0); - cl_must_pass(p_unlink(composed)); - - assert_config_entry_on_init("core.precomposeunicode", found_with_nfd); -#else - assert_config_entry_on_init("core.precomposeunicode", GIT_ENOTFOUND); -#endif -} - -void test_repo_init__reinit_doesnot_overwrite_ignorecase(void) -{ - git_config *config; - int current_value; - - /* Init a new repo */ - cl_set_cleanup(&cleanup_repository, "not.overwrite.git"); - cl_git_pass(git_repository_init(&g_repo, "not.overwrite.git", 1)); - - /* Change the "core.ignorecase" config value to something unlikely */ - git_repository_config(&config, g_repo); - git_config_set_int32(config, "core.ignorecase", 42); - git_config_free(config); - git_repository_free(g_repo); - g_repo = NULL; - - /* Reinit the repository */ - cl_git_pass(git_repository_init(&g_repo, "not.overwrite.git", 1)); - git_repository_config(&config, g_repo); - - /* Ensure the "core.ignorecase" config value hasn't been updated */ - cl_git_pass(git_config_get_int32(¤t_value, config, "core.ignorecase")); - cl_assert_equal_i(42, current_value); - - git_config_free(config); -} - -void test_repo_init__reinit_overwrites_filemode(void) -{ - int expected = cl_is_chmod_supported(), current_value; - - /* Init a new repo */ - cl_set_cleanup(&cleanup_repository, "overwrite.git"); - cl_git_pass(git_repository_init(&g_repo, "overwrite.git", 1)); - - /* Change the "core.filemode" config value to something unlikely */ - cl_repo_set_bool(g_repo, "core.filemode", !expected); - - git_repository_free(g_repo); - g_repo = NULL; - - /* Reinit the repository */ - cl_git_pass(git_repository_init(&g_repo, "overwrite.git", 1)); - - /* Ensure the "core.filemode" config value has been reset */ - current_value = cl_repo_get_bool(g_repo, "core.filemode"); - cl_assert_equal_i(expected, current_value); -} - -void test_repo_init__sets_logAllRefUpdates_according_to_type_of_repository(void) -{ - assert_config_entry_on_init_bytype("core.logallrefupdates", GIT_ENOTFOUND, true); - git_repository_free(g_repo); - assert_config_entry_on_init_bytype("core.logallrefupdates", true, false); -} - -void test_repo_init__extended_0(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - - /* without MKDIR this should fail */ - cl_git_fail(git_repository_init_ext(&g_repo, "extended", &opts)); - - /* make the directory first, then it should succeed */ - cl_git_pass(git_futils_mkdir("extended", 0775, 0)); - cl_git_pass(git_repository_init_ext(&g_repo, "extended", &opts)); - - cl_assert(!git__suffixcmp(git_repository_workdir(g_repo), "/extended/")); - cl_assert(!git__suffixcmp(git_repository_path(g_repo), "/extended/.git/")); - cl_assert(!git_repository_is_bare(g_repo)); - cl_assert(git_repository_is_empty(g_repo)); - - cleanup_repository("extended"); -} - -void test_repo_init__extended_1(void) -{ - git_reference *ref; - git_remote *remote; - struct stat st; - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - - opts.flags = GIT_REPOSITORY_INIT_MKPATH | - GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; - opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP; - opts.workdir_path = "../c_wd"; - opts.description = "Awesomest test repository evah"; - opts.initial_head = "development"; - opts.origin_url = "https://github.com/libgit2/libgit2.git"; - - cl_git_pass(git_repository_init_ext(&g_repo, "root/b/c.git", &opts)); - - cl_assert(!git__suffixcmp(git_repository_workdir(g_repo), "/c_wd/")); - cl_assert(!git__suffixcmp(git_repository_path(g_repo), "/c.git/")); - cl_assert(git_fs_path_isfile("root/b/c_wd/.git")); - cl_assert(!git_repository_is_bare(g_repo)); - /* repo will not be counted as empty because we set head to "development" */ - cl_assert(!git_repository_is_empty(g_repo)); - - cl_git_pass(git_fs_path_lstat(git_repository_path(g_repo), &st)); - cl_assert(S_ISDIR(st.st_mode)); - if (cl_is_chmod_supported()) - cl_assert((S_ISGID & st.st_mode) == S_ISGID); - else - cl_assert((S_ISGID & st.st_mode) == 0); - - cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); - cl_assert(git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC); - cl_assert_equal_s("refs/heads/development", git_reference_symbolic_target(ref)); - git_reference_free(ref); - - cl_git_pass(git_remote_lookup(&remote, g_repo, "origin")); - cl_assert_equal_s("origin", git_remote_name(remote)); - cl_assert_equal_s(opts.origin_url, git_remote_url(remote)); - git_remote_free(remote); - - git_repository_free(g_repo); - cl_fixture_cleanup("root"); -} - -void test_repo_init__relative_gitdir(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - git_str dot_git_content = GIT_STR_INIT; - - opts.workdir_path = "../c_wd"; - opts.flags = - GIT_REPOSITORY_INIT_MKPATH | - GIT_REPOSITORY_INIT_RELATIVE_GITLINK | - GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; - - /* make the directory first, then it should succeed */ - cl_git_pass(git_repository_init_ext(&g_repo, "root/b/my_repository", &opts)); - - cl_assert(!git__suffixcmp(git_repository_workdir(g_repo), "root/b/c_wd/")); - cl_assert(!git__suffixcmp(git_repository_path(g_repo), "root/b/my_repository/")); - cl_assert(!git_repository_is_bare(g_repo)); - cl_assert(git_repository_is_empty(g_repo)); - - /* Verify that the gitlink and worktree entries are relative */ - - /* Verify worktree */ - assert_config_entry_value(g_repo, "core.worktree", "../c_wd/"); - - /* Verify gitlink */ - cl_git_pass(git_futils_readbuffer(&dot_git_content, "root/b/c_wd/.git")); - cl_assert_equal_s("gitdir: ../my_repository/", dot_git_content.ptr); - - git_str_dispose(&dot_git_content); - cleanup_repository("root"); -} - -void test_repo_init__relative_gitdir_2(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - git_str dot_git_content = GIT_STR_INIT; - git_str full_path = GIT_STR_INIT; - - cl_git_pass(git_fs_path_prettify(&full_path, ".", NULL)); - cl_git_pass(git_str_joinpath(&full_path, full_path.ptr, "root/b/c_wd")); - - opts.workdir_path = full_path.ptr; - opts.flags = - GIT_REPOSITORY_INIT_MKPATH | - GIT_REPOSITORY_INIT_RELATIVE_GITLINK | - GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; - - /* make the directory first, then it should succeed */ - cl_git_pass(git_repository_init_ext(&g_repo, "root/b/my_repository", &opts)); - git_str_dispose(&full_path); - - cl_assert(!git__suffixcmp(git_repository_workdir(g_repo), "root/b/c_wd/")); - cl_assert(!git__suffixcmp(git_repository_path(g_repo), "root/b/my_repository/")); - cl_assert(!git_repository_is_bare(g_repo)); - cl_assert(git_repository_is_empty(g_repo)); - - /* Verify that the gitlink and worktree entries are relative */ - - /* Verify worktree */ - assert_config_entry_value(g_repo, "core.worktree", "../c_wd/"); - - /* Verify gitlink */ - cl_git_pass(git_futils_readbuffer(&dot_git_content, "root/b/c_wd/.git")); - cl_assert_equal_s("gitdir: ../my_repository/", dot_git_content.ptr); - - git_str_dispose(&dot_git_content); - cleanup_repository("root"); -} - -void test_repo_init__can_reinit_an_initialized_repository(void) -{ - git_repository *reinit; - - cl_set_cleanup(&cleanup_repository, "extended"); - - cl_git_pass(git_futils_mkdir("extended", 0775, 0)); - cl_git_pass(git_repository_init(&g_repo, "extended", false)); - - cl_git_pass(git_repository_init(&reinit, "extended", false)); - - cl_assert_equal_s(git_repository_path(g_repo), git_repository_path(reinit)); - - git_repository_free(reinit); -} - -void test_repo_init__init_with_initial_commit(void) -{ - git_index *index; - - cl_set_cleanup(&cleanup_repository, "committed"); - - /* Initialize the repository */ - cl_git_pass(git_repository_init(&g_repo, "committed", 0)); - - /* Index will be automatically created when requested for a new repo */ - cl_git_pass(git_repository_index(&index, g_repo)); - - /* Create a file so we can commit it - * - * If you are writing code outside the test suite, you can create this - * file any way that you like, such as: - * FILE *fp = fopen("committed/file.txt", "w"); - * fputs("some stuff\n", fp); - * fclose(fp); - * We like to use the help functions because they do error detection - * in a way that's easily compatible with our test suite. - */ - cl_git_mkfile("committed/file.txt", "some stuff\n"); - - /* Add file to the index */ - cl_git_pass(git_index_add_bypath(index, "file.txt")); - cl_git_pass(git_index_write(index)); - - /* Intentionally not using cl_repo_commit_from_index here so this code - * can be used as an example of how an initial commit is typically - * made to a repository... - */ - - /* Make sure we're ready to use git_signature_default :-) */ - { - git_config *cfg, *local; - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_open_level(&local, cfg, GIT_CONFIG_LEVEL_LOCAL)); - cl_git_pass(git_config_set_string(local, "user.name", "Test User")); - cl_git_pass(git_config_set_string(local, "user.email", "t@example.com")); - git_config_free(local); - git_config_free(cfg); - } - - /* Create a commit with the new contents of the index */ - { - git_signature *sig; - git_oid tree_id, commit_id; - git_tree *tree; - - cl_git_pass(git_signature_default(&sig, g_repo)); - cl_git_pass(git_index_write_tree(&tree_id, index)); - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - - cl_git_pass(git_commit_create_v( - &commit_id, g_repo, "HEAD", sig, sig, - NULL, "First", tree, 0)); - - git_tree_free(tree); - git_signature_free(sig); - } - - git_index_free(index); -} - -void test_repo_init__at_filesystem_root(void) -{ - git_repository *repo; - const char *sandbox = clar_sandbox_path(); - git_str root = GIT_STR_INIT; - int root_len; - - if (!cl_is_env_set("GITTEST_INVASIVE_FS_STRUCTURE")) - cl_skip(); - - root_len = git_fs_path_root(sandbox); - cl_assert(root_len >= 0); - - git_str_put(&root, sandbox, root_len+1); - git_str_joinpath(&root, root.ptr, "libgit2_test_dir"); - - cl_assert(!git_fs_path_exists(root.ptr)); - - cl_git_pass(git_repository_init(&repo, root.ptr, 0)); - cl_assert(git_fs_path_isdir(root.ptr)); - cl_git_pass(git_futils_rmdir_r(root.ptr, NULL, GIT_RMDIR_REMOVE_FILES)); - - git_str_dispose(&root); - git_repository_free(repo); -} - -void test_repo_init__nonexisting_directory(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - git_repository *repo; - - /* - * If creating a repo with non-existing parent directories, then libgit2 - * will by default create the complete directory hierarchy if using - * `git_repository_init`. Thus, let's use the extended version and not - * set the `GIT_REPOSITORY_INIT_MKPATH` flag. - */ - cl_git_fail(git_repository_init_ext(&repo, "nonexisting/path", &opts)); -} - -void test_repo_init__nonexisting_root(void) -{ -#ifdef GIT_WIN32 - git_repository *repo; - - /* - * This really only depends on the nonexistence of the Q: drive. We - * cannot implement the equivalent test on Unix systems, as there is - * fundamentally no path that is disconnected from the root directory. - */ - cl_git_fail(git_repository_init(&repo, "Q:/non/existent/path", 0)); - cl_git_fail(git_repository_init(&repo, "Q:\\non\\existent\\path", 0)); -#else - clar__skip(); -#endif -} - -void test_repo_init__unwriteable_directory(void) -{ -#ifndef GIT_WIN32 - git_repository *repo; - - if (geteuid() == 0) - clar__skip(); - - /* - * Create a non-writeable directory so that we cannot create directories - * inside of it. The root user has CAP_DAC_OVERRIDE, so he doesn't care - * for the directory permissions and thus we need to skip the test if - * run as root user. - */ - cl_must_pass(p_mkdir("unwriteable", 0444)); - cl_git_fail(git_repository_init(&repo, "unwriteable/repo", 0)); - cl_must_pass(p_rmdir("unwriteable")); -#else - clar__skip(); -#endif -} - -void test_repo_init__defaultbranch_config(void) -{ - git_reference *head; - - cl_set_cleanup(&cleanup_repository, "repo"); - - create_tmp_global_config("tmp_global_path", "init.defaultbranch", "my_default_branch"); - - cl_git_pass(git_repository_init(&g_repo, "repo", 0)); - cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); - - cl_assert_equal_s("refs/heads/my_default_branch", git_reference_symbolic_target(head)); - - git_reference_free(head); -} - -void test_repo_init__defaultbranch_config_empty(void) -{ - git_reference *head; - - cl_set_cleanup(&cleanup_repository, "repo"); - - create_tmp_global_config("tmp_global_path", "init.defaultbranch", ""); - - cl_git_pass(git_repository_init(&g_repo, "repo", 0)); - cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); - - cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); - - git_reference_free(head); -} - -void test_repo_init__longpath(void) -{ -#ifdef GIT_WIN32 - size_t padding = CONST_STRLEN("objects/pack/pack-.pack.lock") + GIT_OID_HEXSZ; - size_t max, i; - git_str path = GIT_STR_INIT; - git_repository *one = NULL, *two = NULL; - - /* - * Files within repositories need to fit within MAX_PATH; - * that means a repo path must be at most (MAX_PATH - 18). - */ - cl_git_pass(git_str_puts(&path, clar_sandbox_path())); - cl_git_pass(git_str_putc(&path, '/')); - - max = ((MAX_PATH) - path.size) - padding; - - for (i = 0; i < max - 1; i++) - cl_git_pass(git_str_putc(&path, 'a')); - - cl_git_pass(git_repository_init(&one, path.ptr, 1)); - - /* Paths longer than this are rejected */ - cl_git_pass(git_str_putc(&path, 'z')); - cl_git_fail(git_repository_init(&two, path.ptr, 1)); - - git_repository_free(one); - git_repository_free(two); - git_str_dispose(&path); -#endif -} diff --git a/tests/repo/message.c b/tests/repo/message.c deleted file mode 100644 index 6241f48f9..000000000 --- a/tests/repo/message.c +++ /dev/null @@ -1,39 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" -#include "posix.h" - -static git_repository *_repo; - -void test_repo_message__initialize(void) -{ - _repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_repo_message__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_repo_message__none(void) -{ - git_buf actual = GIT_BUF_INIT; - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_message(&actual, _repo)); -} - -void test_repo_message__message(void) -{ - git_str path = GIT_STR_INIT; - git_buf actual = GIT_BUF_INIT; - const char expected[] = "Test\n\nThis is a test of the emergency broadcast system\n"; - - cl_git_pass(git_str_joinpath(&path, git_repository_path(_repo), "MERGE_MSG")); - cl_git_mkfile(git_str_cstr(&path), expected); - - cl_git_pass(git_repository_message(&actual, _repo)); - cl_assert_equal_s(expected, actual.ptr); - git_buf_dispose(&actual); - - cl_git_pass(p_unlink(git_str_cstr(&path))); - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_message(&actual, _repo)); - git_str_dispose(&path); -} diff --git a/tests/repo/new.c b/tests/repo/new.c deleted file mode 100644 index d77e903f6..000000000 --- a/tests/repo/new.c +++ /dev/null @@ -1,27 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/repository.h" - -void test_repo_new__has_nothing(void) -{ - git_repository *repo; - - cl_git_pass(git_repository_new(&repo)); - cl_assert_equal_b(true, git_repository_is_bare(repo)); - cl_assert_equal_p(NULL, git_repository_path(repo)); - cl_assert_equal_p(NULL, git_repository_workdir(repo)); - git_repository_free(repo); -} - -void test_repo_new__is_bare_until_workdir_set(void) -{ - git_repository *repo; - - cl_git_pass(git_repository_new(&repo)); - cl_assert_equal_b(true, git_repository_is_bare(repo)); - - cl_git_pass(git_repository_set_workdir(repo, clar_sandbox_path(), 0)); - cl_assert_equal_b(false, git_repository_is_bare(repo)); - - git_repository_free(repo); -} - diff --git a/tests/repo/open.c b/tests/repo/open.c deleted file mode 100644 index f7ed2c373..000000000 --- a/tests/repo/open.c +++ /dev/null @@ -1,455 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "sysdir.h" -#include - - -void test_repo_open__cleanup(void) -{ - cl_git_sandbox_cleanup(); - - if (git_fs_path_isdir("alternate")) - git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES); -} - -void test_repo_open__bare_empty_repo(void) -{ - git_repository *repo = cl_git_sandbox_init("empty_bare.git"); - - cl_assert(git_repository_path(repo) != NULL); - cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0); - cl_assert(git_repository_workdir(repo) == NULL); -} - -void test_repo_open__format_version_1(void) -{ - git_repository *repo; - git_config *config; - - repo = cl_git_sandbox_init("empty_bare.git"); - - cl_git_pass(git_repository_open(&repo, "empty_bare.git")); - cl_git_pass(git_repository_config(&config, repo)); - - cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 1)); - - git_config_free(config); - git_repository_free(repo); - - cl_git_pass(git_repository_open(&repo, "empty_bare.git")); - cl_assert(git_repository_path(repo) != NULL); - cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0); - git_repository_free(repo); -} - -void test_repo_open__standard_empty_repo_through_gitdir(void) -{ - git_repository *repo; - - cl_git_pass(git_repository_open(&repo, cl_fixture("empty_standard_repo/.gitted"))); - - cl_assert(git_repository_path(repo) != NULL); - cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0); - - cl_assert(git_repository_workdir(repo) != NULL); - cl_assert(git__suffixcmp(git_repository_workdir(repo), "/") == 0); - - git_repository_free(repo); -} - -void test_repo_open__standard_empty_repo_through_workdir(void) -{ - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_assert(git_repository_path(repo) != NULL); - cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0); - - cl_assert(git_repository_workdir(repo) != NULL); - cl_assert(git__suffixcmp(git_repository_workdir(repo), "/") == 0); -} - - -void test_repo_open__open_with_discover(void) -{ - static const char *variants[] = { - "attr", "attr/", "attr/.git", "attr/.git/", - "attr/sub", "attr/sub/", "attr/sub/sub", "attr/sub/sub/", - NULL - }; - git_repository *repo; - const char **scan; - - cl_fixture_sandbox("attr"); - cl_git_pass(p_rename("attr/.gitted", "attr/.git")); - - for (scan = variants; *scan != NULL; scan++) { - cl_git_pass(git_repository_open_ext(&repo, *scan, 0, NULL)); - cl_assert(git__suffixcmp(git_repository_path(repo), "attr/.git/") == 0); - cl_assert(git__suffixcmp(git_repository_workdir(repo), "attr/") == 0); - git_repository_free(repo); - } - - cl_fixture_cleanup("attr"); -} - -void test_repo_open__check_if_repository(void) -{ - cl_git_sandbox_init("empty_standard_repo"); - - /* Pass NULL for the output parameter to check for but not open the repo */ - cl_git_pass(git_repository_open_ext(NULL, "empty_standard_repo", 0, NULL)); - cl_git_fail(git_repository_open_ext(NULL, "repo_does_not_exist", 0, NULL)); - - cl_fixture_cleanup("empty_standard_repo"); -} - -static void make_gitlink_dir(const char *dir, const char *linktext) -{ - git_str path = GIT_STR_INIT; - - cl_git_pass(git_futils_mkdir(dir, 0777, GIT_MKDIR_VERIFY_DIR)); - cl_git_pass(git_str_joinpath(&path, dir, ".git")); - cl_git_rewritefile(path.ptr, linktext); - git_str_dispose(&path); -} - -void test_repo_open__gitlinked(void) -{ - /* need to have both repo dir and workdir set up correctly */ - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); - git_repository *repo2; - - make_gitlink_dir("alternate", "gitdir: ../empty_standard_repo/.git"); - - cl_git_pass(git_repository_open(&repo2, "alternate")); - - cl_assert(git_repository_path(repo2) != NULL); - cl_assert_(git__suffixcmp(git_repository_path(repo2), "empty_standard_repo/.git/") == 0, git_repository_path(repo2)); - cl_assert_equal_s(git_repository_path(repo), git_repository_path(repo2)); - - cl_assert(git_repository_workdir(repo2) != NULL); - cl_assert_(git__suffixcmp(git_repository_workdir(repo2), "alternate/") == 0, git_repository_workdir(repo2)); - - git_repository_free(repo2); -} - -void test_repo_open__with_symlinked_config(void) -{ -#ifndef GIT_WIN32 - git_str path = GIT_STR_INIT; - git_repository *repo; - git_config *cfg; - int32_t value; - - cl_git_sandbox_init("empty_standard_repo"); - - /* Setup .gitconfig as symlink */ - cl_git_pass(git_futils_mkdir_r("home", 0777)); - cl_git_mkfile("home/.gitconfig.linked", "[global]\ntest = 4567\n"); - cl_must_pass(symlink(".gitconfig.linked", "home/.gitconfig")); - cl_git_pass(git_fs_path_prettify(&path, "home", NULL)); - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); - - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - cl_git_pass(git_config_open_default(&cfg)); - cl_git_pass(git_config_get_int32(&value, cfg, "global.test")); - cl_assert_equal_i(4567, value); - - git_config_free(cfg); - git_repository_free(repo); - cl_git_pass(git_futils_rmdir_r(git_str_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES)); - cl_sandbox_set_search_path_defaults(); - git_str_dispose(&path); -#endif -} - -void test_repo_open__from_git_new_workdir(void) -{ -#ifndef GIT_WIN32 - /* The git-new-workdir script that ships with git sets up a bunch of - * symlinks to create a second workdir that shares the object db with - * another checkout. Libgit2 can open a repo that has been configured - * this way. - */ - - git_repository *repo2; - git_str link_tgt = GIT_STR_INIT, link = GIT_STR_INIT, body = GIT_STR_INIT; - const char **scan; - int link_fd; - static const char *links[] = { - "config", "refs", "logs/refs", "objects", "info", "hooks", - "packed-refs", "remotes", "rr-cache", "svn", NULL - }; - static const char *copies[] = { - "HEAD", NULL - }; - - cl_git_sandbox_init("empty_standard_repo"); - - cl_git_pass(p_mkdir("alternate", 0777)); - cl_git_pass(p_mkdir("alternate/.git", 0777)); - - for (scan = links; *scan != NULL; scan++) { - git_str_joinpath(&link_tgt, "empty_standard_repo/.git", *scan); - if (git_fs_path_exists(link_tgt.ptr)) { - git_str_joinpath(&link_tgt, "../../empty_standard_repo/.git", *scan); - git_str_joinpath(&link, "alternate/.git", *scan); - if (strchr(*scan, '/')) - git_futils_mkpath2file(link.ptr, 0777); - cl_assert_(symlink(link_tgt.ptr, link.ptr) == 0, strerror(errno)); - } - } - for (scan = copies; *scan != NULL; scan++) { - git_str_joinpath(&link_tgt, "empty_standard_repo/.git", *scan); - if (git_fs_path_exists(link_tgt.ptr)) { - git_str_joinpath(&link, "alternate/.git", *scan); - cl_git_pass(git_futils_readbuffer(&body, link_tgt.ptr)); - - cl_assert((link_fd = git_futils_creat_withpath(link.ptr, 0777, 0666)) >= 0); - cl_must_pass(p_write(link_fd, body.ptr, body.size)); - p_close(link_fd); - } - } - - git_str_dispose(&link_tgt); - git_str_dispose(&link); - git_str_dispose(&body); - - - cl_git_pass(git_repository_open(&repo2, "alternate")); - - cl_assert(git_repository_path(repo2) != NULL); - cl_assert_(git__suffixcmp(git_repository_path(repo2), "alternate/.git/") == 0, git_repository_path(repo2)); - - cl_assert(git_repository_workdir(repo2) != NULL); - cl_assert_(git__suffixcmp(git_repository_workdir(repo2), "alternate/") == 0, git_repository_workdir(repo2)); - - git_repository_free(repo2); -#else - cl_skip(); -#endif -} - -void test_repo_open__failures(void) -{ - git_repository *base, *repo; - git_str ceiling = GIT_STR_INIT; - - base = cl_git_sandbox_init("attr"); - cl_git_pass(git_str_sets(&ceiling, git_repository_workdir(base))); - - /* fail with no searching */ - cl_git_fail(git_repository_open(&repo, "attr/sub")); - cl_git_fail(git_repository_open_ext( - &repo, "attr/sub", GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)); - - /* fail with ceiling too low */ - cl_git_fail(git_repository_open_ext(&repo, "attr/sub", 0, ceiling.ptr)); - cl_git_pass(git_str_joinpath(&ceiling, ceiling.ptr, "sub")); - cl_git_fail(git_repository_open_ext(&repo, "attr/sub/sub", 0, ceiling.ptr)); - - /* fail with no repo */ - cl_git_pass(p_mkdir("alternate", 0777)); - cl_git_pass(p_mkdir("alternate/.git", 0777)); - cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL)); - cl_git_fail(git_repository_open_ext(&repo, "alternate/.git", 0, NULL)); - - /* fail with no searching and no appending .git */ - cl_git_fail(git_repository_open_ext( - &repo, "attr", - GIT_REPOSITORY_OPEN_NO_SEARCH | GIT_REPOSITORY_OPEN_NO_DOTGIT, - NULL)); - - git_str_dispose(&ceiling); -} - -void test_repo_open__bad_gitlinks(void) -{ - git_repository *repo; - static const char *bad_links[] = { - "garbage\n", "gitdir", "gitdir:\n", "gitdir: foobar", - "gitdir: ../invalid", "gitdir: ../invalid2", - "gitdir: ../attr/.git with extra stuff", - NULL - }; - const char **scan; - - cl_git_sandbox_init("attr"); - - cl_git_pass(p_mkdir("invalid", 0777)); - cl_git_pass(git_futils_mkdir_r("invalid2/.git", 0777)); - - for (scan = bad_links; *scan != NULL; scan++) { - make_gitlink_dir("alternate", *scan); - repo = NULL; - cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL)); - cl_assert(repo == NULL); - } - - git_futils_rmdir_r("invalid", NULL, GIT_RMDIR_REMOVE_FILES); - git_futils_rmdir_r("invalid2", NULL, GIT_RMDIR_REMOVE_FILES); -} - -#ifdef GIT_WIN32 -static void unposix_path(git_str *path) -{ - char *src, *tgt; - - src = tgt = path->ptr; - - /* convert "/d/..." to "d:\..." */ - if (src[0] == '/' && isalpha(src[1]) && src[2] == '/') { - *tgt++ = src[1]; - *tgt++ = ':'; - *tgt++ = '\\'; - src += 3; - } - - while (*src) { - *tgt++ = (*src == '/') ? '\\' : *src; - src++; - } - - *tgt = '\0'; -} -#endif - -void test_repo_open__win32_path(void) -{ -#ifdef GIT_WIN32 - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"), *repo2; - git_str winpath = GIT_STR_INIT; - static const char *repo_path = "empty_standard_repo/.git/"; - static const char *repo_wd = "empty_standard_repo/"; - - cl_assert(git__suffixcmp(git_repository_path(repo), repo_path) == 0); - cl_assert(git__suffixcmp(git_repository_workdir(repo), repo_wd) == 0); - - cl_git_pass(git_str_sets(&winpath, git_repository_path(repo))); - unposix_path(&winpath); - cl_git_pass(git_repository_open(&repo2, winpath.ptr)); - cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0); - cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0); - git_repository_free(repo2); - - cl_git_pass(git_str_sets(&winpath, git_repository_path(repo))); - git_str_truncate(&winpath, winpath.size - 1); /* remove trailing '/' */ - unposix_path(&winpath); - cl_git_pass(git_repository_open(&repo2, winpath.ptr)); - cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0); - cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0); - git_repository_free(repo2); - - cl_git_pass(git_str_sets(&winpath, git_repository_workdir(repo))); - unposix_path(&winpath); - cl_git_pass(git_repository_open(&repo2, winpath.ptr)); - cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0); - cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0); - git_repository_free(repo2); - - cl_git_pass(git_str_sets(&winpath, git_repository_workdir(repo))); - git_str_truncate(&winpath, winpath.size - 1); /* remove trailing '/' */ - unposix_path(&winpath); - cl_git_pass(git_repository_open(&repo2, winpath.ptr)); - cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0); - cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0); - git_repository_free(repo2); - - git_str_dispose(&winpath); -#endif -} - -void test_repo_open__opening_a_non_existing_repository_returns_ENOTFOUND(void) -{ - git_repository *repo; - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_open(&repo, "i-do-not/exist")); -} - -void test_repo_open__no_config(void) -{ - git_str path = GIT_STR_INIT; - git_repository *repo; - git_config *config; - - cl_fixture_sandbox("empty_standard_repo"); - cl_git_pass(cl_rename( - "empty_standard_repo/.gitted", "empty_standard_repo/.git")); - - /* remove local config */ - cl_git_pass(git_futils_rmdir_r( - "empty_standard_repo/.git/config", NULL, GIT_RMDIR_REMOVE_FILES)); - - /* isolate from system level configs */ - cl_must_pass(p_mkdir("alternate", 0777)); - cl_git_pass(git_fs_path_prettify(&path, "alternate", NULL)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); - - git_str_dispose(&path); - - cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - cl_git_pass(git_repository_config(&config, repo)); - - cl_git_pass(git_config_set_string(config, "test.set", "42")); - - git_config_free(config); - git_repository_free(repo); - cl_fixture_cleanup("empty_standard_repo"); - - cl_sandbox_set_search_path_defaults(); -} - -void test_repo_open__force_bare(void) -{ - /* need to have both repo dir and workdir set up correctly */ - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); - git_repository *barerepo; - - make_gitlink_dir("alternate", "gitdir: ../empty_standard_repo/.git"); - - cl_assert(!git_repository_is_bare(repo)); - - cl_git_pass(git_repository_open(&barerepo, "alternate")); - cl_assert(!git_repository_is_bare(barerepo)); - git_repository_free(barerepo); - - cl_git_pass(git_repository_open_bare( - &barerepo, "empty_standard_repo/.git")); - cl_assert(git_repository_is_bare(barerepo)); - git_repository_free(barerepo); - - cl_git_fail(git_repository_open_bare(&barerepo, "alternate/.git")); - - cl_git_pass(git_repository_open_ext( - &barerepo, "alternate/.git", GIT_REPOSITORY_OPEN_BARE, NULL)); - cl_assert(git_repository_is_bare(barerepo)); - git_repository_free(barerepo); - - cl_git_pass(p_mkdir("empty_standard_repo/subdir", 0777)); - cl_git_mkfile("empty_standard_repo/subdir/something.txt", "something"); - - cl_git_fail(git_repository_open_bare( - &barerepo, "empty_standard_repo/subdir")); - - cl_git_pass(git_repository_open_ext( - &barerepo, "empty_standard_repo/subdir", GIT_REPOSITORY_OPEN_BARE, NULL)); - cl_assert(git_repository_is_bare(barerepo)); - git_repository_free(barerepo); - - cl_git_pass(p_mkdir("alternate/subdir", 0777)); - cl_git_pass(p_mkdir("alternate/subdir/sub2", 0777)); - cl_git_mkfile("alternate/subdir/sub2/something.txt", "something"); - - cl_git_fail(git_repository_open_bare(&barerepo, "alternate/subdir/sub2")); - - cl_git_pass(git_repository_open_ext( - &barerepo, "alternate/subdir/sub2", - GIT_REPOSITORY_OPEN_BARE|GIT_REPOSITORY_OPEN_CROSS_FS, NULL)); - cl_assert(git_repository_is_bare(barerepo)); - git_repository_free(barerepo); -} - diff --git a/tests/repo/pathspec.c b/tests/repo/pathspec.c deleted file mode 100644 index 5b86662bc..000000000 --- a/tests/repo/pathspec.c +++ /dev/null @@ -1,385 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/pathspec.h" - -static git_repository *g_repo; - -void test_repo_pathspec__initialize(void) -{ - g_repo = cl_git_sandbox_init("status"); -} - -void test_repo_pathspec__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -static char *str0[] = { "*_file", "new_file", "garbage" }; -static char *str1[] = { "*_FILE", "NEW_FILE", "GARBAGE" }; -static char *str2[] = { "staged_*" }; -static char *str3[] = { "!subdir", "*_file", "new_file" }; -static char *str4[] = { "*" }; -static char *str5[] = { "S*" }; - -void test_repo_pathspec__workdir0(void) -{ - git_strarray s; - git_pathspec *ps; - git_pathspec_match_list *m; - - /* { "*_file", "new_file", "garbage" } */ - s.strings = str0; s.count = ARRAY_SIZE(str0); - cl_git_pass(git_pathspec_new(&ps, &s)); - - cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); - cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); - git_pathspec_match_list_free(m); - - cl_git_pass(git_pathspec_match_workdir(&m, g_repo, - GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); - cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 0)); - git_pathspec_match_list_free(m); - - cl_git_pass(git_pathspec_match_workdir(&m, g_repo, - GIT_PATHSPEC_FIND_FAILURES | GIT_PATHSPEC_FAILURES_ONLY, ps)); - cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); - git_pathspec_match_list_free(m); - - git_pathspec_free(ps); -} - -void test_repo_pathspec__workdir1(void) -{ - git_strarray s; - git_pathspec *ps; - git_pathspec_match_list *m; - - /* { "*_FILE", "NEW_FILE", "GARBAGE" } */ - s.strings = str1; s.count = ARRAY_SIZE(str1); - cl_git_pass(git_pathspec_new(&ps, &s)); - - cl_git_pass(git_pathspec_match_workdir(&m, g_repo, - GIT_PATHSPEC_IGNORE_CASE, ps)); - cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); - git_pathspec_match_list_free(m); - - cl_git_pass(git_pathspec_match_workdir(&m, g_repo, - GIT_PATHSPEC_USE_CASE, ps)); - cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); - git_pathspec_match_list_free(m); - - cl_git_fail(git_pathspec_match_workdir(&m, g_repo, - GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_NO_MATCH_ERROR, ps)); - - cl_git_pass(git_pathspec_match_workdir(&m, g_repo, - GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); - git_pathspec_match_list_free(m); - - cl_git_pass(git_pathspec_match_workdir(&m, g_repo, - GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(3, git_pathspec_match_list_failed_entrycount(m)); - git_pathspec_match_list_free(m); - - git_pathspec_free(ps); -} - -void test_repo_pathspec__workdir2(void) -{ - git_strarray s; - git_pathspec *ps; - git_pathspec_match_list *m; - - /* { "staged_*" } */ - s.strings = str2; s.count = ARRAY_SIZE(str2); - cl_git_pass(git_pathspec_new(&ps, &s)); - - cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); - cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m)); - git_pathspec_match_list_free(m); - - cl_git_pass(git_pathspec_match_workdir(&m, g_repo, - GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); - git_pathspec_match_list_free(m); - - cl_git_fail(git_pathspec_match_workdir(&m, g_repo, - GIT_PATHSPEC_NO_GLOB | GIT_PATHSPEC_NO_MATCH_ERROR, ps)); - - cl_git_pass(git_pathspec_match_workdir(&m, g_repo, - GIT_PATHSPEC_NO_GLOB | GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); - git_pathspec_match_list_free(m); - - git_pathspec_free(ps); -} - -void test_repo_pathspec__workdir3(void) -{ - git_strarray s; - git_pathspec *ps; - git_pathspec_match_list *m; - - /* { "!subdir", "*_file", "new_file" } */ - s.strings = str3; s.count = ARRAY_SIZE(str3); - cl_git_pass(git_pathspec_new(&ps, &s)); - - cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); - cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m)); - git_pathspec_match_list_free(m); - - cl_git_pass(git_pathspec_match_workdir(&m, g_repo, - GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); - - cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); - cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); - cl_assert_equal_s("new_file", git_pathspec_match_list_entry(m, 2)); - cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 3)); - cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 4)); - cl_assert_equal_s("staged_new_file", git_pathspec_match_list_entry(m, 5)); - cl_assert_equal_s("staged_new_file_modified_file", git_pathspec_match_list_entry(m, 6)); - cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 7)); - - git_pathspec_match_list_free(m); - - git_pathspec_free(ps); -} - -void test_repo_pathspec__workdir4(void) -{ - git_strarray s; - git_pathspec *ps; - git_pathspec_match_list *m; - - /* { "*" } */ - s.strings = str4; s.count = ARRAY_SIZE(str4); - cl_git_pass(git_pathspec_new(&ps, &s)); - - cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); - cl_assert_equal_sz(13, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_s("\xE8\xBF\x99", git_pathspec_match_list_entry(m, 12)); - git_pathspec_match_list_free(m); - - git_pathspec_free(ps); -} - - -void test_repo_pathspec__index0(void) -{ - git_index *idx; - git_strarray s; - git_pathspec *ps; - git_pathspec_match_list *m; - - cl_git_pass(git_repository_index(&idx, g_repo)); - - /* { "*_file", "new_file", "garbage" } */ - s.strings = str0; s.count = ARRAY_SIZE(str0); - cl_git_pass(git_pathspec_new(&ps, &s)); - - cl_git_pass(git_pathspec_match_index(&m, idx, 0, ps)); - cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); - cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); - cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); - cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2)); - cl_assert_equal_s("staged_new_file", git_pathspec_match_list_entry(m, 3)); - cl_assert_equal_s("staged_new_file_deleted_file", git_pathspec_match_list_entry(m, 4)); - cl_assert_equal_s("staged_new_file_modified_file", git_pathspec_match_list_entry(m, 5)); - cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 6)); - cl_assert_equal_s("subdir/deleted_file", git_pathspec_match_list_entry(m, 7)); - cl_assert_equal_s("subdir/modified_file", git_pathspec_match_list_entry(m, 8)); - cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 9)); - git_pathspec_match_list_free(m); - - cl_git_pass(git_pathspec_match_index(&m, idx, - GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); - cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0)); - cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1)); - cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2)); - git_pathspec_match_list_free(m); - - git_pathspec_free(ps); - git_index_free(idx); -} - -void test_repo_pathspec__index1(void) -{ - /* Currently the USE_CASE and IGNORE_CASE flags don't work on the - * index because the index sort order for the index iterator is - * set by the index itself. I think the correct fix is for the - * index not to embed a global sort order but to support traversal - * in either case sensitive or insensitive order in a stateless - * manner. - * - * Anyhow, as it is, there is no point in doing this test. - */ -#if 0 - git_index *idx; - git_strarray s; - git_pathspec *ps; - git_pathspec_match_list *m; - - cl_git_pass(git_repository_index(&idx, g_repo)); - - /* { "*_FILE", "NEW_FILE", "GARBAGE" } */ - s.strings = str1; s.count = ARRAY_SIZE(str1); - cl_git_pass(git_pathspec_new(&ps, &s)); - - cl_git_pass(git_pathspec_match_index(&m, idx, - GIT_PATHSPEC_USE_CASE, ps)); - cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); - git_pathspec_match_list_free(m); - - cl_git_pass(git_pathspec_match_index(&m, idx, - GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(3, git_pathspec_match_list_failed_entrycount(m)); - git_pathspec_match_list_free(m); - - cl_git_pass(git_pathspec_match_index(&m, idx, - GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); - git_pathspec_match_list_free(m); - - git_pathspec_free(ps); - git_index_free(idx); -#endif -} - -void test_repo_pathspec__tree0(void) -{ - git_object *tree; - git_strarray s; - git_pathspec *ps; - git_pathspec_match_list *m; - - /* { "*_file", "new_file", "garbage" } */ - s.strings = str0; s.count = ARRAY_SIZE(str0); - cl_git_pass(git_pathspec_new(&ps, &s)); - - cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD~2^{tree}")); - - cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, - GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(4, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); - cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); - cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2)); - cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 3)); - cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 4)); - cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); - cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0)); - cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1)); - cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2)); - git_pathspec_match_list_free(m); - - git_object_free(tree); - - cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, - GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0)); - cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1)); - cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2)); - cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 3)); - cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 4)); - cl_assert_equal_s("subdir/deleted_file", git_pathspec_match_list_entry(m, 5)); - cl_assert_equal_s("subdir/modified_file", git_pathspec_match_list_entry(m, 6)); - cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 7)); - cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m)); - cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0)); - cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1)); - cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2)); - git_pathspec_match_list_free(m); - - git_object_free(tree); - - git_pathspec_free(ps); -} - -void test_repo_pathspec__tree5(void) -{ - git_object *tree; - git_strarray s; - git_pathspec *ps; - git_pathspec_match_list *m; - - /* { "S*" } */ - s.strings = str5; s.count = ARRAY_SIZE(str5); - cl_git_pass(git_pathspec_new(&ps, &s)); - - cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD~2^{tree}")); - - cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, - GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m)); - git_pathspec_match_list_free(m); - - cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, - GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_s("staged_changes", git_pathspec_match_list_entry(m, 0)); - cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 4)); - cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); - git_pathspec_match_list_free(m); - - git_object_free(tree); - - cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree, - GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps)); - cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_s("staged_changes", git_pathspec_match_list_entry(m, 0)); - cl_assert_equal_s("subdir.txt", git_pathspec_match_list_entry(m, 5)); - cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 6)); - cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m)); - git_pathspec_match_list_free(m); - - git_object_free(tree); - - git_pathspec_free(ps); -} - -void test_repo_pathspec__in_memory(void) -{ - static char *strings[] = { "one", "two*", "!three*", "*four" }; - git_strarray s = { strings, ARRAY_SIZE(strings) }; - git_pathspec *ps; - - cl_git_pass(git_pathspec_new(&ps, &s)); - - cl_assert(git_pathspec_matches_path(ps, 0, "one")); - cl_assert(!git_pathspec_matches_path(ps, 0, "ONE")); - cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_IGNORE_CASE, "ONE")); - cl_assert(git_pathspec_matches_path(ps, 0, "two")); - cl_assert(git_pathspec_matches_path(ps, 0, "two.txt")); - cl_assert(!git_pathspec_matches_path(ps, 0, "three.txt")); - cl_assert(git_pathspec_matches_path(ps, 0, "anything.four")); - cl_assert(!git_pathspec_matches_path(ps, 0, "three.four")); - cl_assert(!git_pathspec_matches_path(ps, 0, "nomatch")); - cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "two")); - cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "two*")); - cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "anyfour")); - cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "*four")); - - git_pathspec_free(ps); -} diff --git a/tests/repo/repo_helpers.c b/tests/repo/repo_helpers.c deleted file mode 100644 index 1efde70a5..000000000 --- a/tests/repo/repo_helpers.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" -#include "repo_helpers.h" -#include "posix.h" - -void make_head_unborn(git_repository* repo, const char *target) -{ - git_reference *head; - - cl_git_pass(git_reference_symbolic_create(&head, repo, GIT_HEAD_FILE, target, 1, NULL)); - git_reference_free(head); -} - -void delete_head(git_repository* repo) -{ - git_str head_path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&head_path, git_repository_path(repo), GIT_HEAD_FILE)); - cl_git_pass(p_unlink(git_str_cstr(&head_path))); - - git_str_dispose(&head_path); -} - -void create_tmp_global_config(const char *dirname, const char *key, const char *val) -{ - git_str path = GIT_STR_INIT; - git_config *config; - - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, - GIT_CONFIG_LEVEL_GLOBAL, dirname)); - cl_must_pass(p_mkdir(dirname, 0777)); - cl_git_pass(git_str_joinpath(&path, dirname, ".gitconfig")); - cl_git_pass(git_config_open_ondisk(&config, path.ptr)); - cl_git_pass(git_config_set_string(config, key, val)); - git_config_free(config); - git_str_dispose(&path); -} diff --git a/tests/repo/repo_helpers.h b/tests/repo/repo_helpers.h deleted file mode 100644 index a93bf36ae..000000000 --- a/tests/repo/repo_helpers.h +++ /dev/null @@ -1,7 +0,0 @@ -#include "common.h" - -#define NON_EXISTING_HEAD "refs/heads/hide/and/seek" - -extern void make_head_unborn(git_repository* repo, const char *target); -extern void delete_head(git_repository* repo); -extern void create_tmp_global_config(const char *path, const char *key, const char *val); diff --git a/tests/repo/reservedname.c b/tests/repo/reservedname.c deleted file mode 100644 index 245d8625a..000000000 --- a/tests/repo/reservedname.c +++ /dev/null @@ -1,132 +0,0 @@ -#include "clar_libgit2.h" -#include "../submodule/submodule_helpers.h" -#include "repository.h" - -void test_repo_reservedname__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_repo_reservedname__includes_shortname_on_win32(void) -{ - git_repository *repo; - git_str *reserved; - size_t reserved_len; - - repo = cl_git_sandbox_init("nasty"); - cl_assert(git_repository__reserved_names(&reserved, &reserved_len, repo, false)); - -#ifdef GIT_WIN32 - cl_assert_equal_i(2, reserved_len); - cl_assert_equal_s(".git", reserved[0].ptr); - cl_assert_equal_s("GIT~1", reserved[1].ptr); -#else - cl_assert_equal_i(1, reserved_len); - cl_assert_equal_s(".git", reserved[0].ptr); -#endif -} - -void test_repo_reservedname__includes_shortname_when_requested(void) -{ - git_repository *repo; - git_str *reserved; - size_t reserved_len; - - repo = cl_git_sandbox_init("nasty"); - cl_assert(git_repository__reserved_names(&reserved, &reserved_len, repo, true)); - - cl_assert_equal_i(2, reserved_len); - cl_assert_equal_s(".git", reserved[0].ptr); - cl_assert_equal_s("GIT~1", reserved[1].ptr); -} - -/* Ensures that custom shortnames are included: creates a GIT~1 so that the - * .git folder itself will have to be named GIT~2 - */ -void test_repo_reservedname__custom_shortname_recognized(void) -{ -#ifdef GIT_WIN32 - git_repository *repo; - git_str *reserved; - size_t reserved_len; - - if (!cl_sandbox_supports_8dot3()) - clar__skip(); - - repo = cl_git_sandbox_init("nasty"); - - cl_must_pass(p_rename("nasty/.git", "nasty/_temp")); - cl_git_write2file("nasty/git~1", "", 0, O_RDWR|O_CREAT, 0666); - cl_must_pass(p_rename("nasty/_temp", "nasty/.git")); - - cl_assert(git_repository__reserved_names(&reserved, &reserved_len, repo, true)); - - cl_assert_equal_i(3, reserved_len); - cl_assert_equal_s(".git", reserved[0].ptr); - cl_assert_equal_s("GIT~1", reserved[1].ptr); - cl_assert_equal_s("GIT~2", reserved[2].ptr); -#endif -} - -/* When looking at the short name for a submodule, we need to prevent - * people from overwriting the `.git` file in the submodule working - * directory itself. We don't want to look at the actual repository - * path, since it will be in the super's repository above us, and - * typically named with the name of our subrepository. Consequently, - * preventing access to the short name of the actual repository path - * would prevent us from creating files with the same name as the - * subrepo. (Eg, a submodule named "libgit2" could not contain a file - * named "libgit2", which would be unfortunate.) - */ -void test_repo_reservedname__submodule_pointer(void) -{ -#ifdef GIT_WIN32 - git_repository *super_repo, *sub_repo; - git_submodule *sub; - git_str *sub_reserved; - size_t sub_reserved_len; - - if (!cl_sandbox_supports_8dot3()) - clar__skip(); - - super_repo = setup_fixture_submod2(); - - assert_submodule_exists(super_repo, "sm_unchanged"); - - cl_git_pass(git_submodule_lookup(&sub, super_repo, "sm_unchanged")); - cl_git_pass(git_submodule_open(&sub_repo, sub)); - - cl_assert(git_repository__reserved_names(&sub_reserved, &sub_reserved_len, sub_repo, true)); - - cl_assert_equal_i(2, sub_reserved_len); - cl_assert_equal_s(".git", sub_reserved[0].ptr); - cl_assert_equal_s("GIT~1", sub_reserved[1].ptr); - - git_submodule_free(sub); - git_repository_free(sub_repo); -#endif -} - -/* Like the `submodule_pointer` test (above), this ensures that we do not - * follow the gitlink to the submodule's repository location and treat that - * as a reserved name. This tests at an initial submodule update, where the - * submodule repo is being created. - */ -void test_repo_reservedname__submodule_pointer_during_create(void) -{ - git_repository *repo; - git_submodule *sm; - git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; - git_str url = GIT_STR_INIT; - - repo = setup_fixture_super(); - - cl_git_pass(git_str_joinpath(&url, clar_sandbox_path(), "sub.git")); - cl_repo_set_string(repo, "submodule.sub.url", url.ptr); - - cl_git_pass(git_submodule_lookup(&sm, repo, "sub")); - cl_git_pass(git_submodule_update(sm, 1, &update_options)); - - git_submodule_free(sm); - git_str_dispose(&url); -} diff --git a/tests/repo/setters.c b/tests/repo/setters.c deleted file mode 100644 index 9a965dec6..000000000 --- a/tests/repo/setters.c +++ /dev/null @@ -1,108 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/repository.h" - -#include "posix.h" -#include "util.h" -#include "path.h" -#include "futils.h" - -static git_repository *repo; - -void test_repo_setters__initialize(void) -{ - cl_fixture_sandbox("testrepo.git"); - cl_git_pass(git_repository_open(&repo, "testrepo.git")); - cl_must_pass(p_mkdir("new_workdir", 0777)); -} - -void test_repo_setters__cleanup(void) -{ - git_repository_free(repo); - repo = NULL; - - cl_fixture_cleanup("testrepo.git"); - cl_fixture_cleanup("new_workdir"); -} - -void test_repo_setters__setting_a_workdir_turns_a_bare_repository_into_a_standard_one(void) -{ - cl_assert(git_repository_is_bare(repo) == 1); - - cl_assert(git_repository_workdir(repo) == NULL); - cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", false)); - - cl_assert(git_repository_workdir(repo) != NULL); - cl_assert(git_repository_is_bare(repo) == 0); -} - -void test_repo_setters__setting_a_workdir_prettifies_its_path(void) -{ - cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", false)); - - cl_assert(git__suffixcmp(git_repository_workdir(repo), "new_workdir/") == 0); -} - -void test_repo_setters__setting_a_workdir_creates_a_gitlink(void) -{ - git_config *cfg; - git_buf buf = GIT_BUF_INIT; - git_str content = GIT_STR_INIT; - - cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", true)); - - cl_assert(git_fs_path_isfile("./new_workdir/.git")); - - cl_git_pass(git_futils_readbuffer(&content, "./new_workdir/.git")); - cl_assert(git__prefixcmp(git_str_cstr(&content), "gitdir: ") == 0); - cl_assert(git__suffixcmp(git_str_cstr(&content), "testrepo.git/") == 0); - git_str_dispose(&content); - - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass(git_config_get_string_buf(&buf, cfg, "core.worktree")); - cl_assert(git__suffixcmp(buf.ptr, "new_workdir/") == 0); - - git_buf_dispose(&buf); - git_config_free(cfg); -} - -void test_repo_setters__setting_a_new_index_on_a_repo_which_has_already_loaded_one_properly_honors_the_refcount(void) -{ - git_index *new_index; - - cl_git_pass(git_index_open(&new_index, "./my-index")); - cl_assert(((git_refcount *)new_index)->refcount.val == 1); - - git_repository_set_index(repo, new_index); - cl_assert(((git_refcount *)new_index)->refcount.val == 2); - - git_repository_free(repo); - cl_assert(((git_refcount *)new_index)->refcount.val == 1); - - git_index_free(new_index); - - /* - * Ensure the cleanup method won't try to free the repo as it's already been taken care of - */ - repo = NULL; -} - -void test_repo_setters__setting_a_new_odb_on_a_repo_which_already_loaded_one_properly_honors_the_refcount(void) -{ - git_odb *new_odb; - - cl_git_pass(git_odb_open(&new_odb, "./testrepo.git/objects")); - cl_assert(((git_refcount *)new_odb)->refcount.val == 1); - - git_repository_set_odb(repo, new_odb); - cl_assert(((git_refcount *)new_odb)->refcount.val == 2); - - git_repository_free(repo); - cl_assert(((git_refcount *)new_odb)->refcount.val == 1); - - git_odb_free(new_odb); - - /* - * Ensure the cleanup method won't try to free the repo as it's already been taken care of - */ - repo = NULL; -} diff --git a/tests/repo/shallow.c b/tests/repo/shallow.c deleted file mode 100644 index adb7a9e44..000000000 --- a/tests/repo/shallow.c +++ /dev/null @@ -1,39 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" - -static git_repository *g_repo; - -void test_repo_shallow__initialize(void) -{ -} - -void test_repo_shallow__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_repo_shallow__no_shallow_file(void) -{ - g_repo = cl_git_sandbox_init("testrepo.git"); - cl_assert_equal_i(0, git_repository_is_shallow(g_repo)); -} - -void test_repo_shallow__empty_shallow_file(void) -{ - g_repo = cl_git_sandbox_init("testrepo.git"); - cl_git_mkfile("testrepo.git/shallow", ""); - cl_assert_equal_i(0, git_repository_is_shallow(g_repo)); -} - -void test_repo_shallow__shallow_repo(void) -{ - g_repo = cl_git_sandbox_init("shallow.git"); - cl_assert_equal_i(1, git_repository_is_shallow(g_repo)); -} - -void test_repo_shallow__clears_errors(void) -{ - g_repo = cl_git_sandbox_init("testrepo.git"); - cl_assert_equal_i(0, git_repository_is_shallow(g_repo)); - cl_assert_equal_p(NULL, git_error_last()); -} diff --git a/tests/repo/state.c b/tests/repo/state.c deleted file mode 100644 index 92b272dce..000000000 --- a/tests/repo/state.c +++ /dev/null @@ -1,131 +0,0 @@ -#include "clar_libgit2.h" -#include "refs.h" -#include "posix.h" -#include "futils.h" - -static git_repository *_repo; -static git_str _path; - -void test_repo_state__initialize(void) -{ - _repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_repo_state__cleanup(void) -{ - cl_git_sandbox_cleanup(); - git_str_dispose(&_path); -} - -static void setup_simple_state(const char *filename) -{ - cl_git_pass(git_str_joinpath(&_path, git_repository_path(_repo), filename)); - git_futils_mkpath2file(git_str_cstr(&_path), 0777); - cl_git_mkfile(git_str_cstr(&_path), "dummy"); -} - -static void assert_repo_state(git_repository_state_t state) -{ - cl_assert_equal_i(state, git_repository_state(_repo)); -} - -void test_repo_state__none_with_HEAD_attached(void) -{ - assert_repo_state(GIT_REPOSITORY_STATE_NONE); -} - -void test_repo_state__none_with_HEAD_detached(void) -{ - cl_git_pass(git_repository_detach_head(_repo)); - assert_repo_state(GIT_REPOSITORY_STATE_NONE); -} - -void test_repo_state__merge(void) -{ - setup_simple_state(GIT_MERGE_HEAD_FILE); - assert_repo_state(GIT_REPOSITORY_STATE_MERGE); - cl_git_pass(git_repository_state_cleanup(_repo)); - assert_repo_state(GIT_REPOSITORY_STATE_NONE); -} - -void test_repo_state__revert(void) -{ - setup_simple_state(GIT_REVERT_HEAD_FILE); - assert_repo_state(GIT_REPOSITORY_STATE_REVERT); - cl_git_pass(git_repository_state_cleanup(_repo)); - assert_repo_state(GIT_REPOSITORY_STATE_NONE); -} - -void test_repo_state__revert_sequence(void) -{ - setup_simple_state(GIT_REVERT_HEAD_FILE); - setup_simple_state(GIT_SEQUENCER_TODO_FILE); - assert_repo_state(GIT_REPOSITORY_STATE_REVERT_SEQUENCE); - cl_git_pass(git_repository_state_cleanup(_repo)); - assert_repo_state(GIT_REPOSITORY_STATE_NONE); -} - -void test_repo_state__cherry_pick(void) -{ - setup_simple_state(GIT_CHERRYPICK_HEAD_FILE); - assert_repo_state(GIT_REPOSITORY_STATE_CHERRYPICK); - cl_git_pass(git_repository_state_cleanup(_repo)); - assert_repo_state(GIT_REPOSITORY_STATE_NONE); -} - -void test_repo_state__cherrypick_sequence(void) -{ - setup_simple_state(GIT_CHERRYPICK_HEAD_FILE); - setup_simple_state(GIT_SEQUENCER_TODO_FILE); - assert_repo_state(GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE); - cl_git_pass(git_repository_state_cleanup(_repo)); - assert_repo_state(GIT_REPOSITORY_STATE_NONE); -} - -void test_repo_state__bisect(void) -{ - setup_simple_state(GIT_BISECT_LOG_FILE); - assert_repo_state(GIT_REPOSITORY_STATE_BISECT); - cl_git_pass(git_repository_state_cleanup(_repo)); - assert_repo_state(GIT_REPOSITORY_STATE_NONE); -} - -void test_repo_state__rebase_interactive(void) -{ - setup_simple_state(GIT_REBASE_MERGE_INTERACTIVE_FILE); - assert_repo_state(GIT_REPOSITORY_STATE_REBASE_INTERACTIVE); - cl_git_pass(git_repository_state_cleanup(_repo)); - assert_repo_state(GIT_REPOSITORY_STATE_NONE); -} - -void test_repo_state__rebase_merge(void) -{ - setup_simple_state(GIT_REBASE_MERGE_DIR "whatever"); - assert_repo_state(GIT_REPOSITORY_STATE_REBASE_MERGE); - cl_git_pass(git_repository_state_cleanup(_repo)); - assert_repo_state(GIT_REPOSITORY_STATE_NONE); -} - -void test_repo_state__rebase(void) -{ - setup_simple_state(GIT_REBASE_APPLY_REBASING_FILE); - assert_repo_state(GIT_REPOSITORY_STATE_REBASE); - cl_git_pass(git_repository_state_cleanup(_repo)); - assert_repo_state(GIT_REPOSITORY_STATE_NONE); -} - -void test_repo_state__apply_mailbox(void) -{ - setup_simple_state(GIT_REBASE_APPLY_APPLYING_FILE); - assert_repo_state(GIT_REPOSITORY_STATE_APPLY_MAILBOX); - cl_git_pass(git_repository_state_cleanup(_repo)); - assert_repo_state(GIT_REPOSITORY_STATE_NONE); -} - -void test_repo_state__apply_mailbox_or_rebase(void) -{ - setup_simple_state(GIT_REBASE_APPLY_DIR "whatever"); - assert_repo_state(GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE); - cl_git_pass(git_repository_state_cleanup(_repo)); - assert_repo_state(GIT_REPOSITORY_STATE_NONE); -} diff --git a/tests/repo/template.c b/tests/repo/template.c deleted file mode 100644 index e8fe266cf..000000000 --- a/tests/repo/template.c +++ /dev/null @@ -1,305 +0,0 @@ -#include "clar_libgit2.h" - -#include "futils.h" -#include "repo/repo_helpers.h" - -#define CLEAR_FOR_CORE_FILEMODE(M) ((M) &= ~0177) - -static git_repository *_repo = NULL; -static mode_t g_umask = 0; -static git_str _global_path = GIT_STR_INIT; - -static const char *fixture_repo; -static const char *fixture_templates; - -void test_repo_template__initialize(void) -{ - _repo = NULL; - - /* load umask if not already loaded */ - if (!g_umask) { - g_umask = p_umask(022); - (void)p_umask(g_umask); - } -} - -void test_repo_template__cleanup(void) -{ - git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, - _global_path.ptr); - git_str_dispose(&_global_path); - - cl_fixture_cleanup("tmp_global_path"); - - if (fixture_repo) { - cl_fixture_cleanup(fixture_repo); - fixture_repo = NULL; - } - - if (fixture_templates) { - cl_fixture_cleanup(fixture_templates); - fixture_templates = NULL; - } - - git_repository_free(_repo); - _repo = NULL; -} - -static void assert_hooks_match( - const char *template_dir, - const char *repo_dir, - const char *hook_path, - bool core_filemode) -{ - git_str expected = GIT_STR_INIT; - git_str actual = GIT_STR_INIT; - struct stat expected_st, st; - - cl_git_pass(git_str_joinpath(&expected, template_dir, hook_path)); - cl_git_pass(git_fs_path_lstat(expected.ptr, &expected_st)); - - cl_git_pass(git_str_joinpath(&actual, repo_dir, hook_path)); - cl_git_pass(git_fs_path_lstat(actual.ptr, &st)); - - cl_assert(expected_st.st_size == st.st_size); - - if (GIT_MODE_TYPE(expected_st.st_mode) != GIT_FILEMODE_LINK) { - mode_t expected_mode = - GIT_MODE_TYPE(expected_st.st_mode) | - (GIT_PERMS_FOR_WRITE(expected_st.st_mode) & ~g_umask); - - if (!core_filemode) { - CLEAR_FOR_CORE_FILEMODE(expected_mode); - CLEAR_FOR_CORE_FILEMODE(st.st_mode); - } - - cl_assert_equal_i_fmt(expected_mode, st.st_mode, "%07o"); - } - - git_str_dispose(&expected); - git_str_dispose(&actual); -} - -static void assert_mode_seems_okay( - const char *base, const char *path, - git_filemode_t expect_mode, bool expect_setgid, bool core_filemode) -{ - git_str full = GIT_STR_INIT; - struct stat st; - - cl_git_pass(git_str_joinpath(&full, base, path)); - cl_git_pass(git_fs_path_lstat(full.ptr, &st)); - git_str_dispose(&full); - - if (!core_filemode) { - CLEAR_FOR_CORE_FILEMODE(expect_mode); - CLEAR_FOR_CORE_FILEMODE(st.st_mode); - expect_setgid = false; - } - - if (S_ISGID != 0) - cl_assert_equal_b(expect_setgid, (st.st_mode & S_ISGID) != 0); - - cl_assert_equal_b( - GIT_PERMS_IS_EXEC(expect_mode), GIT_PERMS_IS_EXEC(st.st_mode)); - - cl_assert_equal_i_fmt( - GIT_MODE_TYPE(expect_mode), GIT_MODE_TYPE(st.st_mode), "%07o"); -} - -static void setup_repo(const char *name, git_repository_init_options *opts) -{ - cl_git_pass(git_repository_init_ext(&_repo, name, opts)); - fixture_repo = name; -} - -static void setup_templates(const char *name, bool setup_globally) -{ - git_str path = GIT_STR_INIT; - - cl_fixture_sandbox("template"); - if (strcmp(name, "template")) - cl_must_pass(p_rename("template", name)); - - fixture_templates = name; - - /* - * Create a symlink from link.sample to update.sample if the filesystem - * supports it. - */ - cl_git_pass(git_str_join3(&path, '/', name, "hooks", "link.sample")); -#ifdef GIT_WIN32 - cl_git_mkfile(path.ptr, "#!/bin/sh\necho hello, world\n"); -#else - cl_must_pass(p_symlink("update.sample", path.ptr)); -#endif - - git_str_clear(&path); - - /* Create a file starting with a dot */ - cl_git_pass(git_str_join3(&path, '/', name, "hooks", ".dotfile")); - cl_git_mkfile(path.ptr, "something\n"); - - git_str_clear(&path); - - if (setup_globally) { - cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), name)); - create_tmp_global_config("tmp_global_path", "init.templatedir", path.ptr); - } - - git_str_dispose(&path); -} - -static void validate_templates(git_repository *repo, const char *template_path) -{ - git_str path = GIT_STR_INIT, expected = GIT_STR_INIT, actual = GIT_STR_INIT; - int filemode; - - cl_git_pass(git_str_joinpath(&path, template_path, "description")); - cl_git_pass(git_futils_readbuffer(&expected, path.ptr)); - - git_str_clear(&path); - - cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "description")); - cl_git_pass(git_futils_readbuffer(&actual, path.ptr)); - - cl_assert_equal_s(expected.ptr, actual.ptr); - - filemode = cl_repo_get_bool(repo, "core.filemode"); - - assert_hooks_match( - template_path, git_repository_path(repo), - "hooks/update.sample", filemode); - assert_hooks_match( - template_path, git_repository_path(repo), - "hooks/link.sample", filemode); - assert_hooks_match( - template_path, git_repository_path(repo), - "hooks/.dotfile", filemode); - - git_str_dispose(&expected); - git_str_dispose(&actual); - git_str_dispose(&path); -} - -void test_repo_template__external_templates_specified_in_options(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - - opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | - GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; - opts.template_path = "template"; - - setup_templates("template", false); - setup_repo("templated.git", &opts); - - validate_templates(_repo, "template"); -} - -void test_repo_template__external_templates_specified_in_config(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - - opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | - GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; - - setup_templates("template", true); - setup_repo("templated.git", &opts); - - validate_templates(_repo, "template"); -} - -void test_repo_template__external_templates_with_leading_dot(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - - opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | - GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; - - setup_templates(".template_with_leading_dot", true); - setup_repo("templated.git", &opts); - - validate_templates(_repo, ".template_with_leading_dot"); -} - -void test_repo_template__extended_with_template_and_shared_mode(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - const char *repo_path; - int filemode; - - opts.flags = GIT_REPOSITORY_INIT_MKPATH | - GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; - opts.template_path = "template"; - opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP; - - setup_templates("template", false); - setup_repo("init_shared_from_tpl", &opts); - - filemode = cl_repo_get_bool(_repo, "core.filemode"); - - repo_path = git_repository_path(_repo); - assert_mode_seems_okay(repo_path, "hooks", - GIT_FILEMODE_TREE | GIT_REPOSITORY_INIT_SHARED_GROUP, true, filemode); - assert_mode_seems_okay(repo_path, "info", - GIT_FILEMODE_TREE | GIT_REPOSITORY_INIT_SHARED_GROUP, true, filemode); - assert_mode_seems_okay(repo_path, "description", - GIT_FILEMODE_BLOB, false, filemode); - - validate_templates(_repo, "template"); -} - -void test_repo_template__templated_head_is_used(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - git_str head = GIT_STR_INIT; - - opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; - - setup_templates("template", true); - cl_git_mkfile("template/HEAD", "foobar\n"); - setup_repo("repo", &opts); - - cl_git_pass(git_futils_readbuffer(&head, "repo/.git/HEAD")); - cl_assert_equal_s("foobar\n", head.ptr); - - git_str_dispose(&head); -} - -void test_repo_template__initial_head_option_overrides_template_head(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - git_str head = GIT_STR_INIT; - - opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; - opts.initial_head = "manual"; - - setup_templates("template", true); - cl_git_mkfile("template/HEAD", "foobar\n"); - setup_repo("repo", &opts); - - cl_git_pass(git_futils_readbuffer(&head, "repo/.git/HEAD")); - cl_assert_equal_s("ref: refs/heads/manual\n", head.ptr); - - git_str_dispose(&head); -} - -void test_repo_template__empty_template_path(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - - opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; - opts.template_path = ""; - - setup_repo("foo", &opts); -} - -void test_repo_template__nonexistent_template_path(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - - opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; - opts.template_path = "/tmp/path/that/does/not/exist/for/libgit2/test"; - - setup_repo("bar", &opts); -} diff --git a/tests/reset/default.c b/tests/reset/default.c deleted file mode 100644 index c76f14813..000000000 --- a/tests/reset/default.c +++ /dev/null @@ -1,212 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "reset_helpers.h" -#include "path.h" - -static git_repository *_repo; -static git_object *_target; -static git_strarray _pathspecs; -static git_index *_index; - -static void initialize(const char *repo_name) -{ - _repo = cl_git_sandbox_init(repo_name); - cl_git_pass(git_repository_index(&_index, _repo)); - - _target = NULL; - - _pathspecs.strings = NULL; - _pathspecs.count = 0; -} - -void test_reset_default__initialize(void) -{ -} - -void test_reset_default__cleanup(void) -{ - git_object_free(_target); - _target = NULL; - - git_index_free(_index); - _index = NULL; - - cl_git_sandbox_cleanup(); -} - -static void assert_content_in_index( - git_strarray *pathspecs, - bool should_exist, - git_strarray *expected_shas) -{ - size_t i, pos; - int error; - - for (i = 0; i < pathspecs->count; i++) { - error = git_index_find(&pos, _index, pathspecs->strings[i]); - - if (should_exist) { - const git_index_entry *entry; - - cl_assert(error != GIT_ENOTFOUND); - - entry = git_index_get_byindex(_index, pos); - cl_assert(entry != NULL); - - if (!expected_shas) - continue; - - cl_git_pass(git_oid_streq(&entry->id, expected_shas->strings[i])); - } else - cl_assert_equal_i(should_exist, error != GIT_ENOTFOUND); - } -} - -void test_reset_default__resetting_filepaths_against_a_null_target_removes_them_from_the_index(void) -{ - char *paths[] = { "staged_changes", "staged_new_file" }; - - initialize("status"); - - _pathspecs.strings = paths; - _pathspecs.count = 2; - - assert_content_in_index(&_pathspecs, true, NULL); - - cl_git_pass(git_reset_default(_repo, NULL, &_pathspecs)); - - assert_content_in_index(&_pathspecs, false, NULL); -} - -/* - * $ git ls-files --cached -s --abbrev=7 -- "staged*" - * 100644 55d316c 0 staged_changes - * 100644 a6be623 0 staged_changes_file_deleted - * ... - * - * $ git reset 0017bd4 -- staged_changes staged_changes_file_deleted - * Unstaged changes after reset: - * ... - * - * $ git ls-files --cached -s --abbrev=7 -- "staged*" - * 100644 32504b7 0 staged_changes - * 100644 061d42a 0 staged_changes_file_deleted - * ... - */ -void test_reset_default__resetting_filepaths_replaces_their_corresponding_index_entries(void) -{ - git_strarray before, after; - - char *paths[] = { "staged_changes", "staged_changes_file_deleted" }; - char *before_shas[] = { "55d316c9ba708999f1918e9677d01dfcae69c6b9", - "a6be623522ce87a1d862128ac42672604f7b468b" }; - char *after_shas[] = { "32504b727382542f9f089e24fddac5e78533e96c", - "061d42a44cacde5726057b67558821d95db96f19" }; - - initialize("status"); - - _pathspecs.strings = paths; - _pathspecs.count = 2; - before.strings = before_shas; - before.count = 2; - after.strings = after_shas; - after.count = 2; - - cl_git_pass(git_revparse_single(&_target, _repo, "0017bd4")); - assert_content_in_index(&_pathspecs, true, &before); - - cl_git_pass(git_reset_default(_repo, _target, &_pathspecs)); - - assert_content_in_index(&_pathspecs, true, &after); -} - -/* - * $ git ls-files --cached -s --abbrev=7 -- conflicts-one.txt - * 100644 1f85ca5 1 conflicts-one.txt - * 100644 6aea5f2 2 conflicts-one.txt - * 100644 516bd85 3 conflicts-one.txt - * - * $ git reset 9a05ccb -- conflicts-one.txt - * Unstaged changes after reset: - * ... - * - * $ git ls-files --cached -s --abbrev=7 -- conflicts-one.txt - * 100644 1f85ca5 0 conflicts-one.txt - * - */ -void test_reset_default__resetting_filepaths_clears_previous_conflicts(void) -{ - const git_index_entry *conflict_entry[3]; - git_strarray after; - - char *paths[] = { "conflicts-one.txt" }; - char *after_shas[] = { "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81" }; - - initialize("mergedrepo"); - - _pathspecs.strings = paths; - _pathspecs.count = 1; - after.strings = after_shas; - after.count = 1; - - cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], - &conflict_entry[2], _index, "conflicts-one.txt")); - - cl_git_pass(git_revparse_single(&_target, _repo, "9a05ccb")); - cl_git_pass(git_reset_default(_repo, _target, &_pathspecs)); - - assert_content_in_index(&_pathspecs, true, &after); - - cl_assert_equal_i(GIT_ENOTFOUND, git_index_conflict_get(&conflict_entry[0], - &conflict_entry[1], &conflict_entry[2], _index, "conflicts-one.txt")); -} - -/* -$ git reset HEAD -- "I_am_not_there.txt" "me_neither.txt" -Unstaged changes after reset: -... -*/ -void test_reset_default__resetting_unknown_filepaths_does_not_fail(void) -{ - char *paths[] = { "I_am_not_there.txt", "me_neither.txt" }; - - initialize("status"); - - _pathspecs.strings = paths; - _pathspecs.count = 2; - - assert_content_in_index(&_pathspecs, false, NULL); - - cl_git_pass(git_revparse_single(&_target, _repo, "HEAD")); - cl_git_pass(git_reset_default(_repo, _target, &_pathspecs)); - - assert_content_in_index(&_pathspecs, false, NULL); -} - -void test_reset_default__staged_rename_reset_delete(void) -{ - git_index_entry entry; - const git_index_entry *existing; - char *paths[] = { "new.txt" }; - - initialize("testrepo2"); - - existing = git_index_get_bypath(_index, "new.txt", 0); - cl_assert(existing); - memcpy(&entry, existing, sizeof(entry)); - - cl_git_pass(git_index_remove_bypath(_index, "new.txt")); - - entry.path = "renamed.txt"; - cl_git_pass(git_index_add(_index, &entry)); - - _pathspecs.strings = paths; - _pathspecs.count = 1; - - assert_content_in_index(&_pathspecs, false, NULL); - - cl_git_pass(git_revparse_single(&_target, _repo, "HEAD")); - cl_git_pass(git_reset_default(_repo, _target, &_pathspecs)); - - assert_content_in_index(&_pathspecs, true, NULL); -} diff --git a/tests/reset/hard.c b/tests/reset/hard.c deleted file mode 100644 index 9d177c021..000000000 --- a/tests/reset/hard.c +++ /dev/null @@ -1,293 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "reset_helpers.h" -#include "path.h" -#include "futils.h" - -static git_repository *repo; -static git_object *target; - -void test_reset_hard__initialize(void) -{ - repo = cl_git_sandbox_init("status"); - target = NULL; -} - -void test_reset_hard__cleanup(void) -{ - if (target != NULL) { - git_object_free(target); - target = NULL; - } - - cl_git_sandbox_cleanup(); -} - -static int strequal_ignore_eol(const char *exp, const char *str) -{ - while (*exp && *str) { - if (*exp != *str) { - while (*exp == '\r' || *exp == '\n') ++exp; - while (*str == '\r' || *str == '\n') ++str; - if (*exp != *str) - return false; - } else { - exp++; str++; - } - } - return (!*exp && !*str); -} - -void test_reset_hard__resetting_reverts_modified_files(void) -{ - git_str path = GIT_STR_INIT, content = GIT_STR_INIT; - int i; - static const char *files[4] = { - "current_file", - "modified_file", - "staged_new_file", - "staged_changes_modified_file" }; - static const char *before[4] = { - "current_file\n", - "modified_file\nmodified_file\n", - "staged_new_file\n", - "staged_changes_modified_file\nstaged_changes_modified_file\nstaged_changes_modified_file\n" - }; - static const char *after[4] = { - "current_file\n", - "modified_file\n", - NULL, - "staged_changes_modified_file\n" - }; - const char *wd = git_repository_workdir(repo); - - cl_assert(wd); - - for (i = 0; i < 4; ++i) { - cl_git_pass(git_str_joinpath(&path, wd, files[i])); - cl_git_pass(git_futils_readbuffer(&content, path.ptr)); - cl_assert_equal_s(before[i], content.ptr); - } - - cl_git_pass(git_revparse_single(&target, repo, "26a125e")); - - cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); - - for (i = 0; i < 4; ++i) { - cl_git_pass(git_str_joinpath(&path, wd, files[i])); - if (after[i]) { - cl_git_pass(git_futils_readbuffer(&content, path.ptr)); - cl_assert(strequal_ignore_eol(after[i], content.ptr)); - } else { - cl_assert(!git_fs_path_exists(path.ptr)); - } - } - - git_str_dispose(&content); - git_str_dispose(&path); -} - -void test_reset_hard__cannot_reset_in_a_bare_repository(void) -{ - git_repository *bare; - - cl_git_pass(git_repository_open(&bare, cl_fixture("testrepo.git"))); - cl_assert(git_repository_is_bare(bare) == true); - - cl_git_pass(git_revparse_single(&target, bare, KNOWN_COMMIT_IN_BARE_REPO)); - - cl_assert_equal_i(GIT_EBAREREPO, git_reset(bare, target, GIT_RESET_HARD, NULL)); - - git_repository_free(bare); -} - -static void index_entry_init(git_index *index, int side, git_oid *oid) -{ - git_index_entry entry; - - memset(&entry, 0x0, sizeof(git_index_entry)); - - entry.path = "conflicting_file"; - GIT_INDEX_ENTRY_STAGE_SET(&entry, side); - entry.mode = 0100644; - git_oid_cpy(&entry.id, oid); - - cl_git_pass(git_index_add(index, &entry)); -} - -static void unmerged_index_init(git_index *index, int entries) -{ - int write_ancestor = 1; - int write_ours = 2; - int write_theirs = 4; - git_oid ancestor, ours, theirs; - - git_oid_fromstr(&ancestor, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); - git_oid_fromstr(&ours, "32504b727382542f9f089e24fddac5e78533e96c"); - git_oid_fromstr(&theirs, "061d42a44cacde5726057b67558821d95db96f19"); - - cl_git_rewritefile("status/conflicting_file", "conflicting file\n"); - - if (entries & write_ancestor) - index_entry_init(index, 1, &ancestor); - - if (entries & write_ours) - index_entry_init(index, 2, &ours); - - if (entries & write_theirs) - index_entry_init(index, 3, &theirs); -} - -void test_reset_hard__resetting_reverts_unmerged(void) -{ - git_index *index; - int entries; - - /* Ensure every permutation of non-zero stage entries results in the - * path being cleaned up. */ - for (entries = 1; entries < 8; entries++) { - cl_git_pass(git_repository_index(&index, repo)); - - unmerged_index_init(index, entries); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_revparse_single(&target, repo, "26a125e")); - cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); - - cl_assert(git_fs_path_exists("status/conflicting_file") == 0); - - git_object_free(target); - target = NULL; - - git_index_free(index); - } -} - -void test_reset_hard__cleans_up_merge(void) -{ - git_str merge_head_path = GIT_STR_INIT, - merge_msg_path = GIT_STR_INIT, - merge_mode_path = GIT_STR_INIT, - orig_head_path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD")); - cl_git_mkfile(git_str_cstr(&merge_head_path), "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef\n"); - - cl_git_pass(git_str_joinpath(&merge_msg_path, git_repository_path(repo), "MERGE_MSG")); - cl_git_mkfile(git_str_cstr(&merge_msg_path), "Merge commit 0017bd4ab1ec30440b17bae1680cff124ab5f1f6\n"); - - cl_git_pass(git_str_joinpath(&merge_mode_path, git_repository_path(repo), "MERGE_MODE")); - cl_git_mkfile(git_str_cstr(&merge_mode_path), ""); - - cl_git_pass(git_str_joinpath(&orig_head_path, git_repository_path(repo), "ORIG_HEAD")); - cl_git_mkfile(git_str_cstr(&orig_head_path), "0017bd4ab1ec30440b17bae1680cff124ab5f1f6"); - - cl_git_pass(git_revparse_single(&target, repo, "0017bd4")); - cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); - - cl_assert(!git_fs_path_exists(git_str_cstr(&merge_head_path))); - cl_assert(!git_fs_path_exists(git_str_cstr(&merge_msg_path))); - cl_assert(!git_fs_path_exists(git_str_cstr(&merge_mode_path))); - - cl_assert(git_fs_path_exists(git_str_cstr(&orig_head_path))); - cl_git_pass(p_unlink(git_str_cstr(&orig_head_path))); - - git_str_dispose(&merge_head_path); - git_str_dispose(&merge_msg_path); - git_str_dispose(&merge_mode_path); - git_str_dispose(&orig_head_path); -} - -void test_reset_hard__reflog_is_correct(void) -{ - git_str buf = GIT_STR_INIT; - git_annotated_commit *annotated; - const char *exp_msg = "commit: Add a file which name should appear before the " - "\"subdir/\" folder while being dealt with by the treewalker"; - - reflog_check(repo, "HEAD", 3, "emeric.fermas@gmail.com", exp_msg); - reflog_check(repo, "refs/heads/master", 3, "emeric.fermas@gmail.com", exp_msg); - - /* Branch not moving, no reflog entry */ - cl_git_pass(git_revparse_single(&target, repo, "HEAD^{commit}")); - cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); - reflog_check(repo, "HEAD", 3, "emeric.fermas@gmail.com", exp_msg); - reflog_check(repo, "refs/heads/master", 3, "emeric.fermas@gmail.com", exp_msg); - - git_object_free(target); - - /* Moved branch, expect id in message */ - cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}")); - cl_git_pass(git_str_printf(&buf, "reset: moving to %s", git_oid_tostr_s(git_object_id(target)))); - cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); - reflog_check(repo, "HEAD", 4, NULL, git_str_cstr(&buf)); - reflog_check(repo, "refs/heads/master", 4, NULL, git_str_cstr(&buf)); - - git_str_dispose(&buf); - - /* Moved branch, expect revspec in message */ - exp_msg = "reset: moving to HEAD~^{commit}"; - cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "HEAD~^{commit}")); - cl_git_pass(git_reset_from_annotated(repo, annotated, GIT_RESET_HARD, NULL)); - reflog_check(repo, "HEAD", 5, NULL, exp_msg); - reflog_check(repo, "refs/heads/master", 5, NULL, exp_msg); - - git_annotated_commit_free(annotated); - -} - -void test_reset_hard__switch_file_to_dir(void) -{ - git_index_entry entry = {{ 0 }}; - git_index *idx; - git_odb *odb; - git_object *commit; - git_tree *tree; - git_signature *sig; - git_oid src_tree_id, tgt_tree_id; - git_oid src_id, tgt_id; - - cl_git_pass(git_repository_odb(&odb, repo)); - cl_git_pass(git_odb_write(&entry.id, odb, "", 0, GIT_OBJECT_BLOB)); - git_odb_free(odb); - - entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_index_new(&idx)); - cl_git_pass(git_signature_now(&sig, "foo", "bar")); - - /* Create the old tree */ - entry.path = "README"; - cl_git_pass(git_index_add(idx, &entry)); - entry.path = "dir"; - cl_git_pass(git_index_add(idx, &entry)); - - cl_git_pass(git_index_write_tree_to(&src_tree_id, idx, repo)); - cl_git_pass(git_index_clear(idx)); - - cl_git_pass(git_tree_lookup(&tree, repo, &src_tree_id)); - cl_git_pass(git_commit_create(&src_id, repo, NULL, sig, sig, NULL, "foo", tree, 0, NULL)); - git_tree_free(tree); - - /* Create the new tree */ - entry.path = "README"; - cl_git_pass(git_index_add(idx, &entry)); - entry.path = "dir/FILE"; - cl_git_pass(git_index_add(idx, &entry)); - - cl_git_pass(git_index_write_tree_to(&tgt_tree_id, idx, repo)); - cl_git_pass(git_tree_lookup(&tree, repo, &tgt_tree_id)); - cl_git_pass(git_commit_create(&tgt_id, repo, NULL, sig, sig, NULL, "foo", tree, 0, NULL)); - git_tree_free(tree); - git_index_free(idx); - git_signature_free(sig); - - /* Let's go to a known state of the src commit with the file named 'dir' */ - cl_git_pass(git_object_lookup(&commit, repo, &src_id, GIT_OBJECT_COMMIT)); - cl_git_pass(git_reset(repo, commit, GIT_RESET_HARD, NULL)); - git_object_free(commit); - - /* And now we move over to the commit with the directory named 'dir' */ - cl_git_pass(git_object_lookup(&commit, repo, &tgt_id, GIT_OBJECT_COMMIT)); - cl_git_pass(git_reset(repo, commit, GIT_RESET_HARD, NULL)); - git_object_free(commit); -} diff --git a/tests/reset/mixed.c b/tests/reset/mixed.c deleted file mode 100644 index 4a78d4c37..000000000 --- a/tests/reset/mixed.c +++ /dev/null @@ -1,85 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "reset_helpers.h" -#include "path.h" - -static git_repository *repo; -static git_object *target; - -void test_reset_mixed__initialize(void) -{ - repo = cl_git_sandbox_init("attr"); - target = NULL; -} - -void test_reset_mixed__cleanup(void) -{ - git_object_free(target); - target = NULL; - - cl_git_sandbox_cleanup(); -} - -void test_reset_mixed__cannot_reset_in_a_bare_repository(void) -{ - git_repository *bare; - - cl_git_pass(git_repository_open(&bare, cl_fixture("testrepo.git"))); - cl_assert(git_repository_is_bare(bare) == true); - - cl_git_pass(git_revparse_single(&target, bare, KNOWN_COMMIT_IN_BARE_REPO)); - - cl_assert_equal_i(GIT_EBAREREPO, git_reset(bare, target, GIT_RESET_MIXED, NULL)); - - git_repository_free(bare); -} - -void test_reset_mixed__resetting_refreshes_the_index_to_the_commit_tree(void) -{ - unsigned int status; - - cl_git_pass(git_status_file(&status, repo, "macro_bad")); - cl_assert(status == GIT_STATUS_CURRENT); - cl_git_pass(git_revparse_single(&target, repo, "605812a")); - - cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL)); - - cl_git_pass(git_status_file(&status, repo, "macro_bad")); - cl_assert(status == GIT_STATUS_WT_NEW); -} - -void test_reset_mixed__reflog_is_correct(void) -{ - git_str buf = GIT_STR_INIT; - git_annotated_commit *annotated; - const char *exp_msg = "commit: Updating test data so we can test inter-hunk-context"; - - reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg); - reflog_check(repo, "refs/heads/master", 9, "yoram.harmelin@gmail.com", exp_msg); - - /* Branch not moving, no reflog entry */ - cl_git_pass(git_revparse_single(&target, repo, "HEAD^{commit}")); - cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL)); - reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg); - reflog_check(repo, "refs/heads/master", 9, "yoram.harmelin@gmail.com", exp_msg); - - git_object_free(target); - target = NULL; - - /* Moved branch, expect id in message */ - cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}")); - git_str_clear(&buf); - cl_git_pass(git_str_printf(&buf, "reset: moving to %s", git_oid_tostr_s(git_object_id(target)))); - cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL)); - reflog_check(repo, "HEAD", 10, NULL, git_str_cstr(&buf)); - reflog_check(repo, "refs/heads/master", 10, NULL, git_str_cstr(&buf)); - git_str_dispose(&buf); - - /* Moved branch, expect revspec in message */ - exp_msg = "reset: moving to HEAD~^{commit}"; - cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "HEAD~^{commit}")); - cl_git_pass(git_reset_from_annotated(repo, annotated, GIT_RESET_MIXED, NULL)); - reflog_check(repo, "HEAD", 11, NULL, exp_msg); - reflog_check(repo, "refs/heads/master", 11, NULL, exp_msg); - git_annotated_commit_free(annotated); -} diff --git a/tests/reset/reset_helpers.c b/tests/reset/reset_helpers.c deleted file mode 100644 index e6acec9ef..000000000 --- a/tests/reset/reset_helpers.c +++ /dev/null @@ -1,20 +0,0 @@ -#include "clar_libgit2.h" -#include "reset_helpers.h" - -void reflog_check(git_repository *repo, const char *refname, - size_t exp_count, const char *exp_email, const char *exp_msg) -{ - git_reflog *log; - const git_reflog_entry *entry; - - GIT_UNUSED(exp_email); - - cl_git_pass(git_reflog_read(&log, repo, refname)); - cl_assert_equal_i(exp_count, git_reflog_entrycount(log)); - entry = git_reflog_entry_byindex(log, 0); - - if (exp_msg) - cl_assert_equal_s(exp_msg, git_reflog_entry_message(entry)); - - git_reflog_free(log); -} diff --git a/tests/reset/reset_helpers.h b/tests/reset/reset_helpers.h deleted file mode 100644 index e7e048514..000000000 --- a/tests/reset/reset_helpers.h +++ /dev/null @@ -1,7 +0,0 @@ -#include "common.h" - -#define KNOWN_COMMIT_IN_BARE_REPO "e90810b8df3e80c413d903f631643c716887138d" -#define KNOWN_COMMIT_IN_ATTR_REPO "217878ab49e1314388ea2e32dc6fdb58a1b969e0" - -void reflog_check(git_repository *repo, const char *refname, - size_t exp_count, const char *exp_email, const char *exp_msg); diff --git a/tests/reset/soft.c b/tests/reset/soft.c deleted file mode 100644 index aca0834f2..000000000 --- a/tests/reset/soft.c +++ /dev/null @@ -1,189 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "reset_helpers.h" -#include "path.h" -#include "repo/repo_helpers.h" - -static git_repository *repo; -static git_object *target; - -void test_reset_soft__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_reset_soft__cleanup(void) -{ - git_object_free(target); - target = NULL; - - cl_git_sandbox_cleanup(); -} - -static void assert_reset_soft(bool should_be_detached) -{ - git_oid oid; - - cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); - cl_git_fail(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO)); - cl_git_pass(git_revparse_single(&target, repo, KNOWN_COMMIT_IN_BARE_REPO)); - - cl_assert(git_repository_head_detached(repo) == should_be_detached); - - cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); - - cl_assert(git_repository_head_detached(repo) == should_be_detached); - - cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); - cl_git_pass(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO)); -} - -void test_reset_soft__can_reset_the_non_detached_Head_to_the_specified_commit(void) -{ - assert_reset_soft(false); -} - -void test_reset_soft__can_reset_the_detached_Head_to_the_specified_commit(void) -{ - git_repository_detach_head(repo); - - assert_reset_soft(true); -} - -void test_reset_soft__resetting_to_the_commit_pointed_at_by_the_Head_does_not_change_the_target_of_the_Head(void) -{ - git_oid oid; - char raw_head_oid[GIT_OID_HEXSZ + 1]; - - cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); - git_oid_fmt(raw_head_oid, &oid); - raw_head_oid[GIT_OID_HEXSZ] = '\0'; - - cl_git_pass(git_revparse_single(&target, repo, raw_head_oid)); - - cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); - - cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); - cl_git_pass(git_oid_streq(&oid, raw_head_oid)); -} - -void test_reset_soft__resetting_to_a_tag_sets_the_Head_to_the_peeled_commit(void) -{ - git_oid oid; - - /* b25fa35 is a tag, pointing to another tag which points to commit e90810b */ - cl_git_pass(git_revparse_single(&target, repo, "b25fa35")); - - cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); - - cl_assert(git_repository_head_detached(repo) == false); - cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); - cl_git_pass(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO)); -} - -void test_reset_soft__cannot_reset_to_a_tag_not_pointing_at_a_commit(void) -{ - /* 53fc32d is the tree of commit e90810b */ - cl_git_pass(git_revparse_single(&target, repo, "53fc32d")); - - cl_git_fail(git_reset(repo, target, GIT_RESET_SOFT, NULL)); - git_object_free(target); - - /* 521d87c is an annotated tag pointing to a blob */ - cl_git_pass(git_revparse_single(&target, repo, "521d87c")); - cl_git_fail(git_reset(repo, target, GIT_RESET_SOFT, NULL)); -} - -void test_reset_soft__resetting_against_an_unborn_head_repo_makes_the_head_no_longer_unborn(void) -{ - git_reference *head; - - cl_git_pass(git_revparse_single(&target, repo, KNOWN_COMMIT_IN_BARE_REPO)); - - make_head_unborn(repo, NON_EXISTING_HEAD); - - cl_assert_equal_i(true, git_repository_head_unborn(repo)); - - cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); - - cl_assert_equal_i(false, git_repository_head_unborn(repo)); - - cl_git_pass(git_reference_lookup(&head, repo, NON_EXISTING_HEAD)); - cl_assert_equal_i(0, git_oid_streq(git_reference_target(head), KNOWN_COMMIT_IN_BARE_REPO)); - - git_reference_free(head); -} - -void test_reset_soft__fails_when_merging(void) -{ - git_str merge_head_path = GIT_STR_INIT; - - cl_git_pass(git_repository_detach_head(repo)); - cl_git_pass(git_str_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD")); - cl_git_mkfile(git_str_cstr(&merge_head_path), "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef\n"); - - cl_git_pass(git_revparse_single(&target, repo, KNOWN_COMMIT_IN_BARE_REPO)); - - cl_assert_equal_i(GIT_EUNMERGED, git_reset(repo, target, GIT_RESET_SOFT, NULL)); - cl_git_pass(p_unlink(git_str_cstr(&merge_head_path))); - - git_str_dispose(&merge_head_path); -} - -void test_reset_soft__fails_when_index_contains_conflicts_independently_of_MERGE_HEAD_file_existence(void) -{ - git_index *index; - git_reference *head; - git_str merge_head_path = GIT_STR_INIT; - - cl_git_sandbox_cleanup(); - - repo = cl_git_sandbox_init("mergedrepo"); - - cl_git_pass(git_str_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD")); - cl_git_pass(p_unlink(git_str_cstr(&merge_head_path))); - git_str_dispose(&merge_head_path); - - cl_git_pass(git_repository_index(&index, repo)); - cl_assert_equal_i(true, git_index_has_conflicts(index)); - git_index_free(index); - - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel(&target, head, GIT_OBJECT_COMMIT)); - git_reference_free(head); - - cl_assert_equal_i(GIT_EUNMERGED, git_reset(repo, target, GIT_RESET_SOFT, NULL)); -} - -void test_reset_soft__reflog_is_correct(void) -{ - git_annotated_commit *annotated; - const char *exp_msg = "checkout: moving from br2 to master"; - const char *master_msg = "commit: checking in"; - - reflog_check(repo, "HEAD", 7, "yoram.harmelin@gmail.com", exp_msg); - reflog_check(repo, "refs/heads/master", 2, "yoram.harmelin@gmail.com", master_msg); - - /* Branch not moving, no reflog entry */ - cl_git_pass(git_revparse_single(&target, repo, "HEAD^{commit}")); - cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); - reflog_check(repo, "HEAD", 7, "yoram.harmelin@gmail.com", exp_msg); - reflog_check(repo, "refs/heads/master", 2, "yoram.harmelin@gmail.com", master_msg); - git_object_free(target); - - /* Moved branch, expect id in message */ - exp_msg = "reset: moving to be3563ae3f795b2b4353bcce3a527ad0a4f7f644"; - cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}")); - cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL)); - reflog_check(repo, "HEAD", 8, "yoram.harmelin@gmail.com", exp_msg); - reflog_check(repo, "refs/heads/master", 3, NULL, exp_msg); - - /* Moved branch, expect message with annotated string */ - exp_msg = "reset: moving to HEAD~^{commit}"; - cl_git_pass(git_annotated_commit_from_revspec(&annotated, repo, "HEAD~^{commit}")); - cl_git_pass(git_reset_from_annotated(repo, annotated, GIT_RESET_SOFT, NULL)); - reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg); - reflog_check(repo, "refs/heads/master", 4, NULL, exp_msg); - - git_annotated_commit_free(annotated); -} diff --git a/tests/revert/bare.c b/tests/revert/bare.c deleted file mode 100644 index 9261cfe86..000000000 --- a/tests/revert/bare.c +++ /dev/null @@ -1,106 +0,0 @@ -#include "clar.h" -#include "clar_libgit2.h" - -#include "futils.h" -#include "git2/revert.h" - -#include "../merge/merge_helpers.h" - -#define TEST_REPO_PATH "revert" - -static git_repository *repo; - -/* Fixture setup and teardown */ -void test_revert_bare__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_revert_bare__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_revert_bare__automerge(void) -{ - git_commit *head_commit, *revert_commit; - git_oid head_oid, revert_oid; - git_index *index; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" }, - { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, - { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, - { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, - }; - - git_oid_fromstr(&head_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45"); - cl_git_pass(git_commit_lookup(&head_commit, repo, &head_oid)); - - git_oid_fromstr(&revert_oid, "d1d403d22cbe24592d725f442835cf46fe60c8ac"); - cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_oid)); - - cl_git_pass(git_revert_commit(&index, repo, revert_commit, head_commit, 0, NULL)); - cl_assert(merge_test_index(index, merge_index_entries, 4)); - - git_commit_free(revert_commit); - git_commit_free(head_commit); - git_index_free(index); -} - -void test_revert_bare__conflicts(void) -{ - git_reference *head_ref; - git_commit *head_commit, *revert_commit; - git_oid revert_oid; - git_index *index; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "7731926a337c4eaba1e2187d90ebfa0a93659382", 1, "file1.txt" }, - { 0100644, "4b8fcff56437e60f58e9a6bc630dd242ebf6ea2c", 2, "file1.txt" }, - { 0100644, "3a3ef367eaf3fe79effbfb0a56b269c04c2b59fe", 3, "file1.txt" }, - { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, - { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, - { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, - }; - - git_oid_fromstr(&revert_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45"); - - cl_git_pass(git_repository_head(&head_ref, repo)); - cl_git_pass(git_reference_peel((git_object **)&head_commit, head_ref, GIT_OBJECT_COMMIT)); - - cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_oid)); - cl_git_pass(git_revert_commit(&index, repo, revert_commit, head_commit, 0, NULL)); - - cl_assert(git_index_has_conflicts(index)); - cl_assert(merge_test_index(index, merge_index_entries, 6)); - - git_commit_free(revert_commit); - git_commit_free(head_commit); - git_reference_free(head_ref); - git_index_free(index); -} - -void test_revert_bare__orphan(void) -{ - git_commit *head_commit, *revert_commit; - git_oid head_oid, revert_oid; - git_index *index; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "296a6d3be1dff05c5d1f631d2459389fa7b619eb", 0, "file-mainline.txt" }, - }; - - git_oid_fromstr(&head_oid, "39467716290f6df775a91cdb9a4eb39295018145"); - cl_git_pass(git_commit_lookup(&head_commit, repo, &head_oid)); - - git_oid_fromstr(&revert_oid, "ebb03002cee5d66c7732dd06241119fe72ab96a5"); - cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_oid)); - - cl_git_pass(git_revert_commit(&index, repo, revert_commit, head_commit, 0, NULL)); - cl_assert(merge_test_index(index, merge_index_entries, 1)); - - git_commit_free(revert_commit); - git_commit_free(head_commit); - git_index_free(index); -} diff --git a/tests/revert/rename.c b/tests/revert/rename.c deleted file mode 100644 index 0d713c60f..000000000 --- a/tests/revert/rename.c +++ /dev/null @@ -1,49 +0,0 @@ -#include "clar.h" -#include "clar_libgit2.h" - -#include "git2/revert.h" -#include "../merge/merge_helpers.h" - -#define TEST_REPO_PATH "revert-rename.git" - -static git_repository *repo; - -/* Fixture setup and teardown */ -void test_revert_rename__initialize(void) -{ - repo = cl_git_sandbox_init(TEST_REPO_PATH); -} - -void test_revert_rename__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -/* Attempt a revert when there is a file rename AND change of file mode, - * but the file contents remain the same. Check that the file mode doesn't - * change following the revert. - */ -void test_revert_rename__automerge(void) -{ - git_commit *head_commit, *revert_commit; - git_oid revert_oid; - git_index *index; - git_reference *head_ref; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "f0f64c618e1646d2948a456ed7c4bcfad5536d68", 0, "goodmode" }}; - - cl_git_pass(git_repository_head(&head_ref, repo)); - cl_git_pass(git_reference_peel((git_object **)&head_commit, head_ref, GIT_OBJECT_COMMIT)); - - cl_git_pass(git_oid_fromstr(&revert_oid, "7b4d7c3789b3581973c04087cb774c3c3576de2f")); - cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_oid)); - - cl_git_pass(git_revert_commit(&index, repo, revert_commit, head_commit, 0, NULL)); - cl_assert(merge_test_index(index, merge_index_entries, 1)); - - git_commit_free(revert_commit); - git_commit_free(head_commit); - git_index_free(index); - git_reference_free(head_ref); -} diff --git a/tests/revert/workdir.c b/tests/revert/workdir.c deleted file mode 100644 index 3f54280ca..000000000 --- a/tests/revert/workdir.c +++ /dev/null @@ -1,576 +0,0 @@ -#include "clar.h" -#include "clar_libgit2.h" - -#include "futils.h" -#include "git2/revert.h" - -#include "../merge/merge_helpers.h" - -#define TEST_REPO_PATH "revert" - -static git_repository *repo; -static git_index *repo_index; - -/* Fixture setup and teardown */ -void test_revert_workdir__initialize(void) -{ - git_config *cfg; - - repo = cl_git_sandbox_init(TEST_REPO_PATH); - git_repository_index(&repo_index, repo); - - /* Ensure that the user's merge.conflictstyle doesn't interfere */ - cl_git_pass(git_repository_config(&cfg, repo)); - cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge")); - git_config_free(cfg); -} - -void test_revert_workdir__cleanup(void) -{ - git_index_free(repo_index); - cl_git_sandbox_cleanup(); -} - -/* git reset --hard 72333f47d4e83616630ff3b0ffe4c0faebcc3c45 - * git revert --no-commit d1d403d22cbe24592d725f442835cf46fe60c8ac */ -void test_revert_workdir__automerge(void) -{ - git_commit *head, *commit; - git_oid head_oid, revert_oid; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" }, - { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, - { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, - { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, - }; - - git_oid_fromstr(&head_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&revert_oid, "d1d403d22cbe24592d725f442835cf46fe60c8ac"); - cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); - cl_git_pass(git_revert(repo, commit, NULL)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); - - git_commit_free(commit); - git_commit_free(head); -} - -/* git revert --no-commit 72333f47d4e83616630ff3b0ffe4c0faebcc3c45 */ -void test_revert_workdir__conflicts(void) -{ - git_reference *head_ref; - git_commit *head, *commit; - git_oid revert_oid; - git_str conflicting_buf = GIT_STR_INIT, mergemsg_buf = GIT_STR_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "7731926a337c4eaba1e2187d90ebfa0a93659382", 1, "file1.txt" }, - { 0100644, "4b8fcff56437e60f58e9a6bc630dd242ebf6ea2c", 2, "file1.txt" }, - { 0100644, "3a3ef367eaf3fe79effbfb0a56b269c04c2b59fe", 3, "file1.txt" }, - { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, - { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, - { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, - }; - - git_oid_fromstr(&revert_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45"); - - cl_git_pass(git_repository_head(&head_ref, repo)); - cl_git_pass(git_reference_peel((git_object **)&head, head_ref, GIT_OBJECT_COMMIT)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); - cl_git_pass(git_revert(repo, commit, NULL)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); - - cl_git_pass(git_futils_readbuffer(&conflicting_buf, - TEST_REPO_PATH "/file1.txt")); - cl_assert(strcmp(conflicting_buf.ptr, "!File one!\n" \ - "!File one!\n" \ - "File one!\n" \ - "File one\n" \ - "File one\n" \ - "File one\n" \ - "File one\n" \ - "File one\n" \ - "File one\n" \ - "File one\n" \ - "<<<<<<< HEAD\n" \ - "File one!\n" \ - "!File one!\n" \ - "!File one!\n" \ - "!File one!\n" \ - "=======\n" \ - "File one\n" \ - "File one\n" \ - "File one\n" \ - "File one\n" \ - ">>>>>>> parent of 72333f4... automergeable changes\n") == 0); - - cl_assert(git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); - cl_git_pass(git_futils_readbuffer(&mergemsg_buf, - TEST_REPO_PATH "/.git/MERGE_MSG")); - cl_assert(strcmp(mergemsg_buf.ptr, - "Revert \"automergeable changes\"\n" \ - "\n" \ - "This reverts commit 72333f47d4e83616630ff3b0ffe4c0faebcc3c45.\n" - "\n" \ - "#Conflicts:\n" \ - "#\tfile1.txt\n") == 0); - - git_commit_free(commit); - git_commit_free(head); - git_reference_free(head_ref); - git_str_dispose(&mergemsg_buf); - git_str_dispose(&conflicting_buf); -} - -/* git reset --hard 39467716290f6df775a91cdb9a4eb39295018145 - * git revert --no-commit ebb03002cee5d66c7732dd06241119fe72ab96a5 -*/ -void test_revert_workdir__orphan(void) -{ - git_commit *head, *commit; - git_oid head_oid, revert_oid; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "296a6d3be1dff05c5d1f631d2459389fa7b619eb", 0, "file-mainline.txt" }, - }; - - git_oid_fromstr(&head_oid, "39467716290f6df775a91cdb9a4eb39295018145"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&revert_oid, "ebb03002cee5d66c7732dd06241119fe72ab96a5"); - cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); - cl_git_pass(git_revert(repo, commit, NULL)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 1)); - - git_commit_free(commit); - git_commit_free(head); -} - -/* - * revert the same commit twice (when the first reverts cleanly): - * - * git revert 2d440f2 - * git revert 2d440f2 - */ -void test_revert_workdir__again(void) -{ - git_reference *head_ref; - git_commit *orig_head; - git_tree *reverted_tree; - git_oid reverted_tree_oid, reverted_commit_oid; - git_signature *signature; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "7731926a337c4eaba1e2187d90ebfa0a93659382", 0, "file1.txt" }, - { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, - { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, - { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, - }; - - cl_git_pass(git_repository_head(&head_ref, repo)); - cl_git_pass(git_reference_peel((git_object **)&orig_head, head_ref, GIT_OBJECT_COMMIT)); - cl_git_pass(git_reset(repo, (git_object *)orig_head, GIT_RESET_HARD, NULL)); - - cl_git_pass(git_revert(repo, orig_head, NULL)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); - - cl_git_pass(git_index_write_tree(&reverted_tree_oid, repo_index)); - cl_git_pass(git_tree_lookup(&reverted_tree, repo, &reverted_tree_oid)); - - cl_git_pass(git_signature_new(&signature, "Reverter", "reverter@example.org", time(NULL), 0)); - cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, (const git_commit **)&orig_head)); - - cl_git_pass(git_revert(repo, orig_head, NULL)); - cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); - - git_signature_free(signature); - git_tree_free(reverted_tree); - git_commit_free(orig_head); - git_reference_free(head_ref); -} - -/* git reset --hard 72333f47d4e83616630ff3b0ffe4c0faebcc3c45 - * git revert --no-commit d1d403d22cbe24592d725f442835cf46fe60c8ac */ -void test_revert_workdir__again_after_automerge(void) -{ - git_commit *head, *commit; - git_tree *reverted_tree; - git_oid head_oid, revert_oid, reverted_tree_oid, reverted_commit_oid; - git_signature *signature; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" }, - { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, - { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, - { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, - }; - - struct merge_index_entry second_revert_entries[] = { - { 0100644, "3a3ef367eaf3fe79effbfb0a56b269c04c2b59fe", 1, "file1.txt" }, - { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 2, "file1.txt" }, - { 0100644, "747726e021bc5f44b86de60e3032fd6f9f1b8383", 3, "file1.txt" }, - { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, - { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, - { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, - }; - - git_oid_fromstr(&head_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&revert_oid, "d1d403d22cbe24592d725f442835cf46fe60c8ac"); - cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); - cl_git_pass(git_revert(repo, commit, NULL)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); - - cl_git_pass(git_index_write_tree(&reverted_tree_oid, repo_index)); - cl_git_pass(git_tree_lookup(&reverted_tree, repo, &reverted_tree_oid)); - - cl_git_pass(git_signature_new(&signature, "Reverter", "reverter@example.org", time(NULL), 0)); - cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, (const git_commit **)&head)); - - cl_git_pass(git_revert(repo, commit, NULL)); - cl_assert(merge_test_index(repo_index, second_revert_entries, 6)); - - git_signature_free(signature); - git_tree_free(reverted_tree); - git_commit_free(commit); - git_commit_free(head); -} - -/* - * revert the same commit twice (when the first reverts cleanly): - * - * git revert 2d440f2 - * git revert 2d440f2 - */ -void test_revert_workdir__again_after_edit(void) -{ - git_reference *head_ref; - git_commit *orig_head, *commit; - git_tree *reverted_tree; - git_oid orig_head_oid, revert_oid, reverted_tree_oid, reverted_commit_oid; - git_signature *signature; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "3721552e06c4bdc7d478e0674e6304888545d5fd", 0, "file1.txt" }, - { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, - { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, - { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, - }; - - cl_git_pass(git_repository_head(&head_ref, repo)); - - cl_git_pass(git_oid_fromstr(&orig_head_oid, "399fb3aba3d9d13f7d40a9254ce4402067ef3149")); - cl_git_pass(git_commit_lookup(&orig_head, repo, &orig_head_oid)); - cl_git_pass(git_reset(repo, (git_object *)orig_head, GIT_RESET_HARD, NULL)); - - cl_git_pass(git_oid_fromstr(&revert_oid, "2d440f2b3147d3dc7ad1085813478d6d869d5a4d")); - cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); - - cl_git_pass(git_revert(repo, commit, NULL)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); - - cl_git_pass(git_index_write_tree(&reverted_tree_oid, repo_index)); - cl_git_pass(git_tree_lookup(&reverted_tree, repo, &reverted_tree_oid)); - - cl_git_pass(git_signature_new(&signature, "Reverter", "reverter@example.org", time(NULL), 0)); - cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, (const git_commit **)&orig_head)); - - cl_git_pass(git_revert(repo, commit, NULL)); - cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); - - git_signature_free(signature); - git_tree_free(reverted_tree); - git_commit_free(commit); - git_commit_free(orig_head); - git_reference_free(head_ref); -} - -/* - * revert the same commit twice (when the first reverts cleanly): - * - * git reset --hard 75ec9929465623f17ff3ad68c0438ea56faba815 - * git revert 97e52d5e81f541080cd6b92829fb85bc4d81d90b - */ -void test_revert_workdir__again_after_edit_two(void) -{ - git_str diff_buf = GIT_STR_INIT; - git_config *config; - git_oid head_commit_oid, revert_commit_oid; - git_commit *head_commit, *revert_commit; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "a8c86221b400b836010567cc3593db6e96c1a83a", 1, "file.txt" }, - { 0100644, "46ff0854663aeb2182b9838c8da68e33ac23bc1e", 2, "file.txt" }, - { 0100644, "21a96a98ed84d45866e1de6e266fd3a61a4ae9dc", 3, "file.txt" }, - }; - - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_bool(config, "core.autocrlf", 0)); - - cl_git_pass(git_oid_fromstr(&head_commit_oid, "75ec9929465623f17ff3ad68c0438ea56faba815")); - cl_git_pass(git_commit_lookup(&head_commit, repo, &head_commit_oid)); - cl_git_pass(git_reset(repo, (git_object *)head_commit, GIT_RESET_HARD, NULL)); - - cl_git_pass(git_oid_fromstr(&revert_commit_oid, "97e52d5e81f541080cd6b92829fb85bc4d81d90b")); - cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_commit_oid)); - - cl_git_pass(git_revert(repo, revert_commit, NULL)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); - - cl_git_pass(git_futils_readbuffer(&diff_buf, "revert/file.txt")); - cl_assert_equal_s( - "a\n" \ - "<<<<<<< HEAD\n" \ - "=======\n" \ - "a\n" \ - ">>>>>>> parent of 97e52d5... Revert me\n" \ - "a\n" \ - "a\n" \ - "a\n" \ - "a\n" \ - "ab", - diff_buf.ptr); - - git_commit_free(revert_commit); - git_commit_free(head_commit); - git_config_free(config); - git_str_dispose(&diff_buf); -} - -/* git reset --hard 72333f47d4e83616630ff3b0ffe4c0faebcc3c45 - * git revert --no-commit d1d403d22cbe24592d725f442835cf46fe60c8ac */ -void test_revert_workdir__conflict_use_ours(void) -{ - git_commit *head, *commit; - git_oid head_oid, revert_oid; - git_revert_options opts = GIT_REVERT_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" }, - { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, - { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, - { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, - }; - - struct merge_index_entry merge_filesystem_entries[] = { - { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" }, - { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, - { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, - { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, - }; - - opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS; - - git_oid_fromstr(&head_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&revert_oid, "d1d403d22cbe24592d725f442835cf46fe60c8ac"); - cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); - cl_git_pass(git_revert(repo, commit, &opts)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); - cl_assert(merge_test_workdir(repo, merge_filesystem_entries, 4)); - - git_commit_free(commit); - git_commit_free(head); -} - -/* git reset --hard cef56612d71a6af8d8015691e4865f7fece905b5 - * git revert --no-commit 55568c8de5322ff9a95d72747a239cdb64a19965 - */ -void test_revert_workdir__rename_1_of_2(void) -{ - git_commit *head, *commit; - git_oid head_oid, revert_oid; - git_revert_options opts = GIT_REVERT_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "747726e021bc5f44b86de60e3032fd6f9f1b8383", 0, "file1.txt" }, - { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, - { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, - { 0100644, "55acf326a69f0aab7a974ec53ffa55a50bcac14e", 3, "file4.txt" }, - { 0100644, "55acf326a69f0aab7a974ec53ffa55a50bcac14e", 1, "file5.txt" }, - { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 2, "file6.txt" }, - }; - - opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES; - opts.merge_opts.rename_threshold = 50; - - git_oid_fromstr(&head_oid, "cef56612d71a6af8d8015691e4865f7fece905b5"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&revert_oid, "55568c8de5322ff9a95d72747a239cdb64a19965"); - cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); - cl_git_pass(git_revert(repo, commit, &opts)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); - - git_commit_free(commit); - git_commit_free(head); -} - -/* git reset --hard 55568c8de5322ff9a95d72747a239cdb64a19965 - * git revert --no-commit HEAD~1 */ -void test_revert_workdir__rename(void) -{ - git_commit *head, *commit; - git_oid head_oid, revert_oid; - git_revert_options opts = GIT_REVERT_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "55acf326a69f0aab7a974ec53ffa55a50bcac14e", 1, "file4.txt" }, - { 0100644, "55acf326a69f0aab7a974ec53ffa55a50bcac14e", 2, "file5.txt" }, - }; - - struct merge_name_entry merge_name_entries[] = { - { "file4.txt", "file5.txt", "" }, - }; - - opts.merge_opts.flags |= GIT_MERGE_FIND_RENAMES; - opts.merge_opts.rename_threshold = 50; - - git_oid_fromstr(&head_oid, "55568c8de5322ff9a95d72747a239cdb64a19965"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - git_oid_fromstr(&revert_oid, "0aa8c7e40d342fff78d60b29a4ba8e993ed79c51"); - cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid)); - cl_git_pass(git_revert(repo, commit, &opts)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 2)); - cl_assert(merge_test_names(repo_index, merge_name_entries, 1)); - - git_commit_free(commit); - git_commit_free(head); -} - -/* git revert --no-commit HEAD */ -void test_revert_workdir__head(void) -{ - git_reference *head; - git_commit *commit; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "7731926a337c4eaba1e2187d90ebfa0a93659382", 0, "file1.txt" }, - { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" }, - { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" }, - { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" }, - }; - - /* HEAD is 2d440f2b3147d3dc7ad1085813478d6d869d5a4d */ - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel((git_object **)&commit, head, GIT_OBJECT_COMMIT)); - cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); - cl_git_pass(git_revert(repo, commit, NULL)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 4)); - cl_assert(merge_test_workdir(repo, merge_index_entries, 4)); - - git_reference_free(head); - git_commit_free(commit); -} - -void test_revert_workdir__nonmerge_fails_mainline_specified(void) -{ - git_reference *head; - git_commit *commit; - git_revert_options opts = GIT_REVERT_OPTIONS_INIT; - - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel((git_object **)&commit, head, GIT_OBJECT_COMMIT)); - - opts.mainline = 1; - cl_must_fail(git_revert(repo, commit, &opts)); - cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); - cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/REVERT_HEAD")); - - git_reference_free(head); - git_commit_free(commit); -} - -/* git reset --hard 5acdc74af27172ec491d213ee36cea7eb9ef2579 - * git revert HEAD */ -void test_revert_workdir__merge_fails_without_mainline_specified(void) -{ - git_commit *head; - git_oid head_oid; - - git_oid_fromstr(&head_oid, "5acdc74af27172ec491d213ee36cea7eb9ef2579"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - cl_must_fail(git_revert(repo, head, NULL)); - cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG")); - cl_assert(!git_fs_path_exists(TEST_REPO_PATH "/.git/REVERT_HEAD")); - - git_commit_free(head); -} - -/* git reset --hard 5acdc74af27172ec491d213ee36cea7eb9ef2579 - * git revert HEAD -m1 --no-commit */ -void test_revert_workdir__merge_first_parent(void) -{ - git_commit *head; - git_oid head_oid; - git_revert_options opts = GIT_REVERT_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "296a6d3be1dff05c5d1f631d2459389fa7b619eb", 0, "file-mainline.txt" }, - { 0100644, "0cdb66192ee192f70f891f05a47636057420e871", 0, "file1.txt" }, - { 0100644, "73ec36fa120f8066963a0bc9105bb273dbd903d7", 0, "file2.txt" }, - }; - - opts.mainline = 1; - - git_oid_fromstr(&head_oid, "5acdc74af27172ec491d213ee36cea7eb9ef2579"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - cl_git_pass(git_revert(repo, head, &opts)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); - - git_commit_free(head); -} - -void test_revert_workdir__merge_second_parent(void) -{ - git_commit *head; - git_oid head_oid; - git_revert_options opts = GIT_REVERT_OPTIONS_INIT; - - struct merge_index_entry merge_index_entries[] = { - { 0100644, "33c6fd981c49a2abf2971482089350bfc5cda8ea", 0, "file-branch.txt" }, - { 0100644, "0cdb66192ee192f70f891f05a47636057420e871", 0, "file1.txt" }, - { 0100644, "73ec36fa120f8066963a0bc9105bb273dbd903d7", 0, "file2.txt" }, - }; - - opts.mainline = 2; - - git_oid_fromstr(&head_oid, "5acdc74af27172ec491d213ee36cea7eb9ef2579"); - cl_git_pass(git_commit_lookup(&head, repo, &head_oid)); - cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL)); - - cl_git_pass(git_revert(repo, head, &opts)); - - cl_assert(merge_test_index(repo_index, merge_index_entries, 3)); - - git_commit_free(head); -} diff --git a/tests/revwalk/basic.c b/tests/revwalk/basic.c deleted file mode 100644 index 2c8d885e2..000000000 --- a/tests/revwalk/basic.c +++ /dev/null @@ -1,627 +0,0 @@ -#include "clar_libgit2.h" - -/* - * a4a7dce [0] Merge branch 'master' into br2 - |\ - | * 9fd738e [1] a fourth commit - | * 4a202b3 [2] a third commit - * | c47800c [3] branch commit one - |/ - * 5b5b025 [5] another commit - * 8496071 [4] testing -*/ -static const char *commit_head = "a4a7dce85cf63874e984719f4fdd239f5145052f"; - -static const char *commit_ids[] = { - "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ - "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */ - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */ - "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ - "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */ - "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */ -}; - -/* Careful: there are two possible topological sorts */ -static const int commit_sorting_topo[][6] = { - {0, 1, 2, 3, 5, 4}, {0, 3, 1, 2, 5, 4} -}; - -static const int commit_sorting_time[][6] = { - {0, 3, 1, 2, 5, 4} -}; - -static const int commit_sorting_topo_reverse[][6] = { - {4, 5, 3, 2, 1, 0}, {4, 5, 2, 1, 3, 0} -}; - -static const int commit_sorting_time_reverse[][6] = { - {4, 5, 2, 1, 3, 0} -}; - -/* This is specified unsorted, so both combinations are possible */ -static const int commit_sorting_segment[][6] = { - {1, 2, -1, -1, -1, -1}, {2, 1, -1, -1, -1, -1} -}; - -#define commit_count 6 -static const int result_bytes = 24; - - -static int get_commit_index(git_oid *raw_oid) -{ - int i; - char oid[GIT_OID_HEXSZ]; - - git_oid_fmt(oid, raw_oid); - - for (i = 0; i < commit_count; ++i) - if (memcmp(oid, commit_ids[i], GIT_OID_HEXSZ) == 0) - return i; - - return -1; -} - -static int test_walk_only(git_revwalk *walk, - const int possible_results[][commit_count], int results_count) -{ - git_oid oid; - int i; - int result_array[commit_count]; - - for (i = 0; i < commit_count; ++i) - result_array[i] = -1; - - i = 0; - while (git_revwalk_next(&oid, walk) == 0) { - result_array[i++] = get_commit_index(&oid); - /*{ - char str[GIT_OID_HEXSZ+1]; - git_oid_fmt(str, &oid); - str[GIT_OID_HEXSZ] = 0; - printf(" %d) %s\n", i, str); - }*/ - } - - for (i = 0; i < results_count; ++i) - if (memcmp(possible_results[i], - result_array, result_bytes) == 0) - return 0; - - return GIT_ERROR; -} - -static int test_walk(git_revwalk *walk, const git_oid *root, - int flags, const int possible_results[][6], int results_count) -{ - git_revwalk_sorting(walk, flags); - git_revwalk_push(walk, root); - - return test_walk_only(walk, possible_results, results_count); -} - -static git_repository *_repo = NULL; -static git_revwalk *_walk = NULL; -static const char *_fixture = NULL; - -void test_revwalk_basic__initialize(void) -{ -} - -void test_revwalk_basic__cleanup(void) -{ - git_revwalk_free(_walk); - - if (_fixture) - cl_git_sandbox_cleanup(); - else - git_repository_free(_repo); - - _fixture = NULL; - _repo = NULL; - _walk = NULL; -} - -static void revwalk_basic_setup_walk(const char *fixture) -{ - if (fixture) { - _fixture = fixture; - _repo = cl_git_sandbox_init(fixture); - } else { - cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); - } - - cl_git_pass(git_revwalk_new(&_walk, _repo)); -} - -void test_revwalk_basic__sorting_modes(void) -{ - git_oid id; - - revwalk_basic_setup_walk(NULL); - - git_oid_fromstr(&id, commit_head); - - cl_git_pass(test_walk(_walk, &id, GIT_SORT_TIME, commit_sorting_time, 1)); - cl_git_pass(test_walk(_walk, &id, GIT_SORT_TOPOLOGICAL, commit_sorting_topo, 2)); - cl_git_pass(test_walk(_walk, &id, GIT_SORT_TIME | GIT_SORT_REVERSE, commit_sorting_time_reverse, 1)); - cl_git_pass(test_walk(_walk, &id, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE, commit_sorting_topo_reverse, 2)); -} - -void test_revwalk_basic__glob_heads(void) -{ - int i = 0; - git_oid oid; - - revwalk_basic_setup_walk(NULL); - - cl_git_pass(git_revwalk_push_glob(_walk, "heads")); - - while (git_revwalk_next(&oid, _walk) == 0) - i++; - - /* git log --branches --oneline | wc -l => 14 */ - cl_assert_equal_i(i, 14); -} - -void test_revwalk_basic__glob_heads_with_invalid(void) -{ - int i; - git_oid oid; - - revwalk_basic_setup_walk("testrepo"); - - cl_git_mkfile("testrepo/.git/refs/heads/garbage", "not-a-ref"); - cl_git_pass(git_revwalk_push_glob(_walk, "heads")); - - for (i = 0; !git_revwalk_next(&oid, _walk); ++i) - /* walking */; - - /* git log --branches --oneline | wc -l => 16 */ - cl_assert_equal_i(20, i); -} - -void test_revwalk_basic__push_head(void) -{ - int i = 0; - git_oid oid; - - revwalk_basic_setup_walk(NULL); - - cl_git_pass(git_revwalk_push_head(_walk)); - - while (git_revwalk_next(&oid, _walk) == 0) { - i++; - } - - /* git log HEAD --oneline | wc -l => 7 */ - cl_assert_equal_i(i, 7); -} - -void test_revwalk_basic__sorted_after_reset(void) -{ - int i = 0; - git_oid oid; - - revwalk_basic_setup_walk(NULL); - - git_oid_fromstr(&oid, commit_head); - - /* push, sort, and test the walk */ - cl_git_pass(git_revwalk_push(_walk, &oid)); - git_revwalk_sorting(_walk, GIT_SORT_TIME); - - cl_git_pass(test_walk_only(_walk, commit_sorting_time, 2)); - - /* reset, push, and test again - we should see all entries */ - git_revwalk_reset(_walk); - cl_git_pass(git_revwalk_push(_walk, &oid)); - - while (git_revwalk_next(&oid, _walk) == 0) - i++; - - cl_assert_equal_i(i, commit_count); -} - -void test_revwalk_basic__push_head_hide_ref(void) -{ - int i = 0; - git_oid oid; - - revwalk_basic_setup_walk(NULL); - - cl_git_pass(git_revwalk_push_head(_walk)); - cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed-test")); - - while (git_revwalk_next(&oid, _walk) == 0) { - i++; - } - - /* git log HEAD --oneline --not refs/heads/packed-test | wc -l => 4 */ - cl_assert_equal_i(i, 4); -} - -void test_revwalk_basic__push_head_hide_ref_nobase(void) -{ - int i = 0; - git_oid oid; - - revwalk_basic_setup_walk(NULL); - - cl_git_pass(git_revwalk_push_head(_walk)); - cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed")); - - while (git_revwalk_next(&oid, _walk) == 0) { - i++; - } - - /* git log HEAD --oneline --not refs/heads/packed | wc -l => 7 */ - cl_assert_equal_i(i, 7); -} - -/* -* $ git rev-list HEAD 5b5b02 ^refs/heads/packed-test -* a65fedf39aefe402d3bb6e24df4d4f5fe4547750 -* be3563ae3f795b2b4353bcce3a527ad0a4f7f644 -* c47800c7266a2be04c571c04d5a6614691ea99bd -* 9fd738e8f7967c078dceed8190330fc8648ee56a - -* $ git log HEAD 5b5b02 --oneline --not refs/heads/packed-test | wc -l => 4 -* a65fedf -* be3563a Merge branch 'br2' -* c47800c branch commit one -* 9fd738e a fourth commit -*/ -void test_revwalk_basic__multiple_push_1(void) -{ - int i = 0; - git_oid oid; - - revwalk_basic_setup_walk(NULL); - - cl_git_pass(git_revwalk_push_head(_walk)); - - cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed-test")); - - cl_git_pass(git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644")); - cl_git_pass(git_revwalk_push(_walk, &oid)); - - while (git_revwalk_next(&oid, _walk) == 0) - i++; - - cl_assert_equal_i(i, 4); -} - -/* -* Difference between test_revwalk_basic__multiple_push_1 and -* test_revwalk_basic__multiple_push_2 is in the order reference -* refs/heads/packed-test and commit 5b5b02 are pushed. -* revwalk should return same commits in both the tests. - -* $ git rev-list 5b5b02 HEAD ^refs/heads/packed-test -* a65fedf39aefe402d3bb6e24df4d4f5fe4547750 -* be3563ae3f795b2b4353bcce3a527ad0a4f7f644 -* c47800c7266a2be04c571c04d5a6614691ea99bd -* 9fd738e8f7967c078dceed8190330fc8648ee56a - -* $ git log 5b5b02 HEAD --oneline --not refs/heads/packed-test | wc -l => 4 -* a65fedf -* be3563a Merge branch 'br2' -* c47800c branch commit one -* 9fd738e a fourth commit -*/ -void test_revwalk_basic__multiple_push_2(void) -{ - int i = 0; - git_oid oid; - - revwalk_basic_setup_walk(NULL); - - cl_git_pass(git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644")); - cl_git_pass(git_revwalk_push(_walk, &oid)); - - cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed-test")); - - cl_git_pass(git_revwalk_push_head(_walk)); - - while (git_revwalk_next(&oid, _walk) == 0) - i++; - - cl_assert_equal_i(i, 4); -} - -void test_revwalk_basic__disallow_non_commit(void) -{ - git_oid oid; - - revwalk_basic_setup_walk(NULL); - - cl_git_pass(git_oid_fromstr(&oid, "521d87c1ec3aef9824daf6d96cc0ae3710766d91")); - cl_git_fail(git_revwalk_push(_walk, &oid)); -} - -void test_revwalk_basic__hide_then_push(void) -{ - git_oid oid; - int i = 0; - - revwalk_basic_setup_walk(NULL); - cl_git_pass(git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644")); - - cl_git_pass(git_revwalk_hide(_walk, &oid)); - cl_git_pass(git_revwalk_push(_walk, &oid)); - - while (git_revwalk_next(&oid, _walk) == 0) - i++; - - cl_assert_equal_i(i, 0); -} - -void test_revwalk_basic__topo_crash(void) -{ - git_oid oid; - git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); - - revwalk_basic_setup_walk(NULL); - git_revwalk_sorting(_walk, GIT_SORT_TOPOLOGICAL); - - cl_git_pass(git_revwalk_push(_walk, &oid)); - cl_git_pass(git_revwalk_hide(_walk, &oid)); - - git_revwalk_next(&oid, _walk); -} - -void test_revwalk_basic__from_new_to_old(void) -{ - git_oid from_oid, to_oid, oid; - int i = 0; - - revwalk_basic_setup_walk(NULL); - git_revwalk_sorting(_walk, GIT_SORT_TIME); - - cl_git_pass(git_oid_fromstr(&to_oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644")); - cl_git_pass(git_oid_fromstr(&from_oid, "a4a7dce85cf63874e984719f4fdd239f5145052f")); - - cl_git_pass(git_revwalk_push(_walk, &to_oid)); - cl_git_pass(git_revwalk_hide(_walk, &from_oid)); - - while (git_revwalk_next(&oid, _walk) == 0) - i++; - - cl_assert_equal_i(i, 0); -} - -void test_revwalk_basic__push_range(void) -{ - revwalk_basic_setup_walk(NULL); - - git_revwalk_reset(_walk); - git_revwalk_sorting(_walk, 0); - cl_git_pass(git_revwalk_push_range(_walk, "9fd738e~2..9fd738e")); - cl_git_pass(test_walk_only(_walk, commit_sorting_segment, 2)); -} - -void test_revwalk_basic__push_range_merge_base(void) -{ - revwalk_basic_setup_walk(NULL); - - git_revwalk_reset(_walk); - git_revwalk_sorting(_walk, 0); - cl_git_fail_with(GIT_EINVALIDSPEC, git_revwalk_push_range(_walk, "HEAD...HEAD~2")); -} - -void test_revwalk_basic__push_range_no_range(void) -{ - revwalk_basic_setup_walk(NULL); - - git_revwalk_reset(_walk); - git_revwalk_sorting(_walk, 0); - cl_git_fail_with(GIT_EINVALIDSPEC, git_revwalk_push_range(_walk, "HEAD")); -} - -void test_revwalk_basic__push_mixed(void) -{ - git_oid oid; - int i = 0; - - revwalk_basic_setup_walk(NULL); - - git_revwalk_reset(_walk); - git_revwalk_sorting(_walk, 0); - cl_git_pass(git_revwalk_push_glob(_walk, "tags")); - - while (git_revwalk_next(&oid, _walk) == 0) { - i++; - } - - /* git rev-list --count --glob=tags #=> 9 */ - cl_assert_equal_i(9, i); -} - -void test_revwalk_basic__push_all(void) -{ - git_oid oid; - int i = 0; - - revwalk_basic_setup_walk(NULL); - - git_revwalk_reset(_walk); - git_revwalk_sorting(_walk, 0); - cl_git_pass(git_revwalk_push_glob(_walk, "*")); - - while (git_revwalk_next(&oid, _walk) == 0) { - i++; - } - - /* git rev-list --count --all #=> 15 */ - cl_assert_equal_i(15, i); -} - -/* -* $ git rev-list br2 master e908 -* a65fedf39aefe402d3bb6e24df4d4f5fe4547750 -* e90810b8df3e80c413d903f631643c716887138d -* 6dcf9bf7541ee10456529833502442f385010c3d -* a4a7dce85cf63874e984719f4fdd239f5145052f -* be3563ae3f795b2b4353bcce3a527ad0a4f7f644 -* c47800c7266a2be04c571c04d5a6614691ea99bd -* 9fd738e8f7967c078dceed8190330fc8648ee56a -* 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 -* 5b5b025afb0b4c913b4c338a42934a3863bf3644 -* 8496071c1b46c854b31185ea97743be6a8774479 -*/ - -void test_revwalk_basic__mimic_git_rev_list(void) -{ - git_oid oid; - - revwalk_basic_setup_walk(NULL); - git_revwalk_sorting(_walk, GIT_SORT_TIME); - - cl_git_pass(git_revwalk_push_ref(_walk, "refs/heads/br2")); - cl_git_pass(git_revwalk_push_ref(_walk, "refs/heads/master")); - cl_git_pass(git_oid_fromstr(&oid, "e90810b8df3e80c413d903f631643c716887138d")); - cl_git_pass(git_revwalk_push(_walk, &oid)); - - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(!git_oid_streq(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); - - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(!git_oid_streq(&oid, "e90810b8df3e80c413d903f631643c716887138d")); - - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(!git_oid_streq(&oid, "6dcf9bf7541ee10456529833502442f385010c3d")); - - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(!git_oid_streq(&oid, "a4a7dce85cf63874e984719f4fdd239f5145052f")); - - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(!git_oid_streq(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); - - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(!git_oid_streq(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd")); - - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(!git_oid_streq(&oid, "9fd738e8f7967c078dceed8190330fc8648ee56a")); - - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(!git_oid_streq(&oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); - - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(!git_oid_streq(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644")); - - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(!git_oid_streq(&oid, "8496071c1b46c854b31185ea97743be6a8774479")); - - cl_git_fail_with(git_revwalk_next(&oid, _walk), GIT_ITEROVER); -} - -void test_revwalk_basic__big_timestamp(void) -{ - git_reference *head; - git_commit *tip; - git_signature *sig; - git_tree *tree; - git_oid id; - int error; - - revwalk_basic_setup_walk("testrepo.git"); - - cl_git_pass(git_repository_head(&head, _repo)); - cl_git_pass(git_reference_peel((git_object **) &tip, head, GIT_OBJECT_COMMIT)); - - /* Commit with a far-ahead timestamp, we should be able to parse it in the revwalk */ - cl_git_pass(git_signature_new(&sig, "Joe", "joe@example.com", INT64_C(2399662595), 0)); - cl_git_pass(git_commit_tree(&tree, tip)); - - cl_git_pass(git_commit_create(&id, _repo, "HEAD", sig, sig, NULL, "some message", tree, 1, - (const git_commit **)&tip)); - - cl_git_pass(git_revwalk_push_head(_walk)); - - while ((error = git_revwalk_next(&id, _walk)) == 0) { - /* nothing */ - } - - cl_assert_equal_i(GIT_ITEROVER, error); - - git_tree_free(tree); - git_commit_free(tip); - git_reference_free(head); - git_signature_free(sig); - -} - -/* Ensure that we correctly hide a commit that is (timewise) older - * than the commits that we are showing. - * - * % git rev-list 8e73b76..bd75801 - * bd758010071961f28336333bc41e9c64c9a64866 - */ -void test_revwalk_basic__old_hidden_commit_one(void) -{ - git_oid new_id, old_id, oid; - - revwalk_basic_setup_walk("testrepo.git"); - - cl_git_pass(git_oid_fromstr(&new_id, "bd758010071961f28336333bc41e9c64c9a64866")); - cl_git_pass(git_revwalk_push(_walk, &new_id)); - - cl_git_pass(git_oid_fromstr(&old_id, "8e73b769e97678d684b809b163bebdae2911720f")); - cl_git_pass(git_revwalk_hide(_walk, &old_id)); - - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(!git_oid_streq(&oid, "bd758010071961f28336333bc41e9c64c9a64866")); - - cl_git_fail_with(GIT_ITEROVER, git_revwalk_next(&oid, _walk)); -} - -/* Ensure that we correctly hide a commit that is (timewise) older - * than the commits that we are showing. - * - * % git rev-list bd75801 ^b91e763 - * bd758010071961f28336333bc41e9c64c9a64866 - */ -void test_revwalk_basic__old_hidden_commit_two(void) -{ - git_oid new_id, old_id, oid; - - revwalk_basic_setup_walk("testrepo.git"); - - cl_git_pass(git_oid_fromstr(&new_id, "bd758010071961f28336333bc41e9c64c9a64866")); - cl_git_pass(git_revwalk_push(_walk, &new_id)); - - cl_git_pass(git_oid_fromstr(&old_id, "b91e763008b10db366442469339f90a2b8400d0a")); - cl_git_pass(git_revwalk_hide(_walk, &old_id)); - - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(!git_oid_streq(&oid, "bd758010071961f28336333bc41e9c64c9a64866")); - - cl_git_fail_with(GIT_ITEROVER, git_revwalk_next(&oid, _walk)); -} - -/* - * Ensure that we correctly hide all parent commits of a newer - * commit when first hiding older commits. - * - * % git rev-list D ^B ^A ^E - * 790ba0facf6fd103699a5c40cd19dad277ff49cd - * b82cee5004151ae0c4f82b69fb71b87477664b6f - */ -void test_revwalk_basic__newer_hidden_commit_hides_old_commits(void) -{ - git_oid oid; - - revwalk_basic_setup_walk("revwalk.git"); - - cl_git_pass(git_revwalk_push_ref(_walk, "refs/heads/D")); - cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/B")); - cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/A")); - cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/E")); - - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(git_oid_streq(&oid, "b82cee5004151ae0c4f82b69fb71b87477664b6f")); - cl_git_pass(git_revwalk_next(&oid, _walk)); - cl_assert(git_oid_streq(&oid, "790ba0facf6fd103699a5c40cd19dad277ff49cd")); - - cl_git_fail_with(GIT_ITEROVER, git_revwalk_next(&oid, _walk)); -} diff --git a/tests/revwalk/hidecb.c b/tests/revwalk/hidecb.c deleted file mode 100644 index 54315bc77..000000000 --- a/tests/revwalk/hidecb.c +++ /dev/null @@ -1,230 +0,0 @@ -#include "clar_libgit2.h" -/* -* a4a7dce [0] Merge branch 'master' into br2 -|\ -| * 9fd738e [1] a fourth commit -| * 4a202b3 [2] a third commit -* | c47800c [3] branch commit one -|/ -* 5b5b025 [5] another commit -* 8496071 [4] testing -*/ -static const char *commit_head = "a4a7dce85cf63874e984719f4fdd239f5145052f"; - -static const char *commit_strs[] = { - "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ - "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */ - "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */ - "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ - "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */ - "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */ -}; - -#define commit_count 6 - -static git_oid commit_ids[commit_count]; -static git_oid _head_id; -static git_repository *_repo; - - -void test_revwalk_hidecb__initialize(void) -{ - int i; - - cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_oid_fromstr(&_head_id, commit_head)); - - for (i = 0; i < commit_count; i++) - cl_git_pass(git_oid_fromstr(&commit_ids[i], commit_strs[i])); - -} - -void test_revwalk_hidecb__cleanup(void) -{ - git_repository_free(_repo); - _repo = NULL; -} - -/* Hide all commits */ -static int hide_every_commit_cb(const git_oid *commit_id, void *data) -{ - GIT_UNUSED(commit_id); - GIT_UNUSED(data); - - return 1; -} - -/* Do not hide anything */ -static int hide_none_cb(const git_oid *commit_id, void *data) -{ - GIT_UNUSED(commit_id); - GIT_UNUSED(data); - - return 0; -} - -/* Hide some commits */ -static int hide_commit_cb(const git_oid *commit_id, void *data) -{ - GIT_UNUSED(commit_id); - GIT_UNUSED(data); - - return (git_oid_cmp(commit_id, &commit_ids[5]) == 0); -} - -/* In payload data, pointer to a commit id is passed */ -static int hide_commit_use_payload_cb(const git_oid *commit_id, void *data) -{ - git_oid *hide_commit_id = data; - - return (git_oid_cmp(commit_id, hide_commit_id) == 0); -} - -void test_revwalk_hidecb__hide_all_cb(void) -{ - git_revwalk *walk; - git_oid id; - - cl_git_pass(git_revwalk_new(&walk, _repo)); - cl_git_pass(git_revwalk_add_hide_cb(walk, hide_every_commit_cb, NULL)); - cl_git_pass(git_revwalk_push(walk, &_head_id)); - - /* First call to git_revwalk_next should return GIT_ITEROVER */ - cl_assert_equal_i(GIT_ITEROVER, git_revwalk_next(&id, walk)); - - git_revwalk_free(walk); -} - - -void test_revwalk_hidecb__hide_none_cb(void) -{ - git_revwalk *walk; - int i, error; - git_oid id; - - cl_git_pass(git_revwalk_new(&walk, _repo)); - cl_git_pass(git_revwalk_add_hide_cb(walk, hide_none_cb, NULL)); - cl_git_pass(git_revwalk_push(walk, &_head_id)); - - /* It should return all 6 commits */ - i = 0; - while ((error = git_revwalk_next(&id, walk)) == 0) - i++; - - cl_assert_equal_i(i, 6); - cl_assert_equal_i(error, GIT_ITEROVER); - - git_revwalk_free(walk); -} - -void test_revwalk_hidecb__unset_cb_before_walk(void) -{ - git_revwalk *walk; - git_oid id; - int i, error; - - cl_git_pass(git_revwalk_new(&walk, _repo)); - cl_git_pass(git_revwalk_add_hide_cb(walk, hide_every_commit_cb, NULL)); - cl_git_pass(git_revwalk_add_hide_cb(walk, NULL, NULL)); - cl_git_pass(git_revwalk_push(walk, &_head_id)); - - /* It should return all 6 commits */ - i = 0; - while ((error = git_revwalk_next(&id, walk)) == 0) - i++; - - cl_assert_equal_i(i, 6); - cl_assert_equal_i(error, GIT_ITEROVER); - - git_revwalk_free(walk); -} - -void test_revwalk_hidecb__change_cb_before_walk(void) -{ - git_revwalk *walk; - git_oid id; - - cl_git_pass(git_revwalk_new(&walk, _repo)); - cl_git_pass(git_revwalk_add_hide_cb(walk, hide_none_cb, NULL)); - cl_git_pass(git_revwalk_add_hide_cb(walk, hide_every_commit_cb, NULL)); - cl_git_pass(git_revwalk_push(walk, &_head_id)); - - /* First call to git_revwalk_next should return GIT_ITEROVER */ - cl_assert_equal_i(GIT_ITEROVER, git_revwalk_next(&id, walk)); - - git_revwalk_free(walk); -} - -void test_revwalk_hidecb__add_hide_cb_during_walking(void) -{ - git_revwalk *walk; - git_oid id; - int error; - - cl_git_pass(git_revwalk_new(&walk, _repo)); - cl_git_pass(git_revwalk_push(walk, &_head_id)); - - /* Start walking without adding hide callback */ - cl_git_pass(git_revwalk_next(&id, walk)); - - /* Now add hide callback */ - cl_git_pass(git_revwalk_add_hide_cb(walk, hide_none_cb, NULL)); - - /* walk should be reset */ - error = git_revwalk_next(&id, walk); - cl_assert_equal_i(error, GIT_ITEROVER); - - git_revwalk_free(walk); -} - -void test_revwalk_hidecb__hide_some_commits(void) -{ - git_revwalk *walk; - git_oid id; - int i, error; - - cl_git_pass(git_revwalk_new(&walk, _repo)); - cl_git_pass(git_revwalk_push(walk, &_head_id)); - git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL); - - /* Add hide callback */ - cl_git_pass(git_revwalk_add_hide_cb(walk, hide_commit_cb, NULL)); - - i = 0; - while ((error = git_revwalk_next(&id, walk)) == 0) { - cl_assert_equal_oid(&commit_ids[i], &id); - i++; - } - - cl_assert_equal_i(i, 4); - cl_assert_equal_i(error, GIT_ITEROVER); - - git_revwalk_free(walk); -} - -void test_revwalk_hidecb__test_payload(void) -{ - git_revwalk *walk; - git_oid id; - int i, error; - - cl_git_pass(git_revwalk_new(&walk, _repo)); - cl_git_pass(git_revwalk_push(walk, &_head_id)); - git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL); - - /* Add hide callback, pass id of parent of initial commit as payload data */ - cl_git_pass(git_revwalk_add_hide_cb(walk, hide_commit_use_payload_cb, &commit_ids[5])); - - i = 0; - while ((error = git_revwalk_next(&id, walk)) == 0) { - cl_assert_equal_oid(&commit_ids[i], &id); - i++; - } - - /* walker should return four commits */ - cl_assert_equal_i(i, 4); - cl_assert_equal_i(error, GIT_ITEROVER); - - git_revwalk_free(walk); -} - diff --git a/tests/revwalk/mergebase.c b/tests/revwalk/mergebase.c deleted file mode 100644 index 0378c869b..000000000 --- a/tests/revwalk/mergebase.c +++ /dev/null @@ -1,514 +0,0 @@ -#include "clar_libgit2.h" -#include "vector.h" -#include - -static git_repository *_repo; -static git_repository *_repo2; - -void test_revwalk_mergebase__initialize(void) -{ - cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_open(&_repo2, cl_fixture("twowaymerge.git"))); -} - -void test_revwalk_mergebase__cleanup(void) -{ - git_repository_free(_repo); - _repo = NULL; - - git_repository_free(_repo2); - _repo2 = NULL; -} - -void test_revwalk_mergebase__single1(void) -{ - git_oid result, one, two, expected; - size_t ahead, behind; - - cl_git_pass(git_oid_fromstr(&one, "c47800c7266a2be04c571c04d5a6614691ea99bd ")); - cl_git_pass(git_oid_fromstr(&two, "9fd738e8f7967c078dceed8190330fc8648ee56a")); - cl_git_pass(git_oid_fromstr(&expected, "5b5b025afb0b4c913b4c338a42934a3863bf3644")); - - cl_git_pass(git_merge_base(&result, _repo, &one, &two)); - cl_assert_equal_oid(&expected, &result); - - cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &one, &two)); - cl_assert_equal_sz(ahead, 1); - cl_assert_equal_sz(behind, 2); - - cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &two, &one)); - cl_assert_equal_sz(ahead, 2); - cl_assert_equal_sz(behind, 1); -} - -void test_revwalk_mergebase__single2(void) -{ - git_oid result, one, two, expected; - size_t ahead, behind; - - cl_git_pass(git_oid_fromstr(&one, "763d71aadf09a7951596c9746c024e7eece7c7af")); - cl_git_pass(git_oid_fromstr(&two, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); - cl_git_pass(git_oid_fromstr(&expected, "c47800c7266a2be04c571c04d5a6614691ea99bd")); - - cl_git_pass(git_merge_base(&result, _repo, &one, &two)); - cl_assert_equal_oid(&expected, &result); - - cl_git_pass(git_graph_ahead_behind( &ahead, &behind, _repo, &one, &two)); - cl_assert_equal_sz(ahead, 1); - cl_assert_equal_sz(behind, 4); - - cl_git_pass(git_graph_ahead_behind( &ahead, &behind, _repo, &two, &one)); - cl_assert_equal_sz(ahead, 4); - cl_assert_equal_sz(behind, 1); -} - -void test_revwalk_mergebase__merged_branch(void) -{ - git_oid result, one, two, expected; - size_t ahead, behind; - - cl_git_pass(git_oid_fromstr(&one, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); - cl_git_pass(git_oid_fromstr(&two, "9fd738e8f7967c078dceed8190330fc8648ee56a")); - cl_git_pass(git_oid_fromstr(&expected, "9fd738e8f7967c078dceed8190330fc8648ee56a")); - - cl_git_pass(git_merge_base(&result, _repo, &one, &two)); - cl_assert_equal_oid(&expected, &result); - - cl_git_pass(git_merge_base(&result, _repo, &two, &one)); - cl_assert_equal_oid(&expected, &result); - - cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &one, &two)); - cl_assert_equal_sz(ahead, 3); - cl_assert_equal_sz(behind, 0); - - cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &two, &one)); - cl_assert_equal_sz(ahead, 0); - cl_assert_equal_sz(behind, 3); -} - -void test_revwalk_mergebase__two_way_merge(void) -{ - git_oid one, two; - size_t ahead, behind; - - cl_git_pass(git_oid_fromstr(&one, "9b219343610c88a1187c996d0dc58330b55cee28")); - cl_git_pass(git_oid_fromstr(&two, "a953a018c5b10b20c86e69fef55ebc8ad4c5a417")); - cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo2, &one, &two)); - - cl_assert_equal_sz(ahead, 8); - cl_assert_equal_sz(behind, 2); - - cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo2, &two, &one)); - - cl_assert_equal_sz(ahead, 2); - cl_assert_equal_sz(behind, 8); -} - -void test_revwalk_mergebase__no_common_ancestor_returns_ENOTFOUND(void) -{ - git_oid result, one, two; - size_t ahead, behind; - int error; - - cl_git_pass(git_oid_fromstr(&one, "763d71aadf09a7951596c9746c024e7eece7c7af")); - cl_git_pass(git_oid_fromstr(&two, "e90810b8df3e80c413d903f631643c716887138d")); - - error = git_merge_base(&result, _repo, &one, &two); - cl_git_fail(error); - - cl_assert_equal_i(GIT_ENOTFOUND, error); - - cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &one, &two)); - cl_assert_equal_sz(4, ahead); - cl_assert_equal_sz(2, behind); -} - -void test_revwalk_mergebase__prefer_youngest_merge_base(void) -{ - git_oid result, one, two, expected; - - cl_git_pass(git_oid_fromstr(&one, "a4a7dce85cf63874e984719f4fdd239f5145052f")); - cl_git_pass(git_oid_fromstr(&two, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); - cl_git_pass(git_oid_fromstr(&expected, "c47800c7266a2be04c571c04d5a6614691ea99bd")); - - cl_git_pass(git_merge_base(&result, _repo, &one, &two)); - cl_assert_equal_oid(&expected, &result); -} - -void test_revwalk_mergebase__multiple_merge_bases(void) -{ - git_oid one, two, expected1, expected2; - git_oidarray result = {NULL, 0}; - - cl_git_pass(git_oid_fromstr(&one, "a4a7dce85cf63874e984719f4fdd239f5145052f")); - cl_git_pass(git_oid_fromstr(&two, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); - cl_git_pass(git_oid_fromstr(&expected1, "c47800c7266a2be04c571c04d5a6614691ea99bd")); - cl_git_pass(git_oid_fromstr(&expected2, "9fd738e8f7967c078dceed8190330fc8648ee56a")); - - cl_git_pass(git_merge_bases(&result, _repo, &one, &two)); - cl_assert_equal_i(2, result.count); - cl_assert_equal_oid(&expected1, &result.ids[0]); - cl_assert_equal_oid(&expected2, &result.ids[1]); - - git_oidarray_dispose(&result); -} - -void test_revwalk_mergebase__multiple_merge_bases_many_commits(void) -{ - git_oid expected1, expected2; - git_oidarray result = {NULL, 0}; - - git_oid *input = git__malloc(sizeof(git_oid) * 2); - - cl_git_pass(git_oid_fromstr(&input[0], "a4a7dce85cf63874e984719f4fdd239f5145052f")); - cl_git_pass(git_oid_fromstr(&input[1], "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); - cl_git_pass(git_oid_fromstr(&expected1, "c47800c7266a2be04c571c04d5a6614691ea99bd")); - cl_git_pass(git_oid_fromstr(&expected2, "9fd738e8f7967c078dceed8190330fc8648ee56a")); - - cl_git_pass(git_merge_bases_many(&result, _repo, 2, input)); - cl_assert_equal_i(2, result.count); - cl_assert_equal_oid(&expected1, &result.ids[0]); - cl_assert_equal_oid(&expected2, &result.ids[1]); - - git_oidarray_dispose(&result); - git__free(input); -} - -void test_revwalk_mergebase__no_off_by_one_missing(void) -{ - git_oid result, one, two; - - cl_git_pass(git_oid_fromstr(&one, "1a443023183e3f2bfbef8ac923cd81c1018a18fd")); - cl_git_pass(git_oid_fromstr(&two, "9f13f7d0a9402c681f91dc590cf7b5470e6a77d2")); - cl_git_pass(git_merge_base(&result, _repo, &one, &two)); -} - -static void assert_mergebase_many(const char *expected_sha, int count, ...) -{ - va_list ap; - int i; - git_oid *oids; - git_oid oid, expected; - char *partial_oid; - git_object *object; - - oids = git__malloc(count * sizeof(git_oid)); - cl_assert(oids != NULL); - - memset(oids, 0x0, count * sizeof(git_oid)); - - va_start(ap, count); - - for (i = 0; i < count; ++i) { - partial_oid = va_arg(ap, char *); - cl_git_pass(git_oid_fromstrn(&oid, partial_oid, strlen(partial_oid))); - - cl_git_pass(git_object_lookup_prefix(&object, _repo, &oid, strlen(partial_oid), GIT_OBJECT_COMMIT)); - git_oid_cpy(&oids[i], git_object_id(object)); - git_object_free(object); - } - - va_end(ap); - - if (expected_sha == NULL) - cl_assert_equal_i(GIT_ENOTFOUND, git_merge_base_many(&oid, _repo, count, oids)); - else { - cl_git_pass(git_merge_base_many(&oid, _repo, count, oids)); - cl_git_pass(git_oid_fromstr(&expected, expected_sha)); - - cl_assert_equal_oid(&expected, &oid); - } - - git__free(oids); -} - -void test_revwalk_mergebase__many_no_common_ancestor_returns_ENOTFOUND(void) -{ - assert_mergebase_many(NULL, 3, "41bc8c", "e90810", "a65fed"); - assert_mergebase_many(NULL, 3, "e90810", "41bc8c", "a65fed"); - assert_mergebase_many(NULL, 3, "e90810", "a65fed", "41bc8c"); - assert_mergebase_many(NULL, 3, "a65fed", "e90810", "41bc8c"); - assert_mergebase_many(NULL, 3, "a65fed", "41bc8c", "e90810"); - - assert_mergebase_many(NULL, 3, "e90810", "763d71", "a65fed"); -} - -void test_revwalk_mergebase__many_merge_branch(void) -{ - assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "a65fed", "763d71", "849607"); - - assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "763d71", "e90810", "a65fed"); - assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "763d71", "a65fed", "e90810"); - - assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "a65fed", "763d71", "849607"); - assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "a65fed", "849607", "763d71"); - assert_mergebase_many("8496071c1b46c854b31185ea97743be6a8774479", 3, "849607", "a65fed", "763d71"); - - assert_mergebase_many("5b5b025afb0b4c913b4c338a42934a3863bf3644", 5, "5b5b02", "763d71", "a4a7dc", "a65fed", "41bc8c"); -} - -static void assert_mergebase_octopus(const char *expected_sha, int count, ...) -{ - va_list ap; - int i; - git_oid *oids; - git_oid oid, expected; - char *partial_oid; - git_object *object; - - oids = git__malloc(count * sizeof(git_oid)); - cl_assert(oids != NULL); - - memset(oids, 0x0, count * sizeof(git_oid)); - - va_start(ap, count); - - for (i = 0; i < count; ++i) { - partial_oid = va_arg(ap, char *); - cl_git_pass(git_oid_fromstrn(&oid, partial_oid, strlen(partial_oid))); - - cl_git_pass(git_object_lookup_prefix(&object, _repo, &oid, strlen(partial_oid), GIT_OBJECT_COMMIT)); - git_oid_cpy(&oids[i], git_object_id(object)); - git_object_free(object); - } - - va_end(ap); - - if (expected_sha == NULL) - cl_assert_equal_i(GIT_ENOTFOUND, git_merge_base_octopus(&oid, _repo, count, oids)); - else { - cl_git_pass(git_merge_base_octopus(&oid, _repo, count, oids)); - cl_git_pass(git_oid_fromstr(&expected, expected_sha)); - - cl_assert_equal_oid(&expected, &oid); - } - - git__free(oids); -} - -void test_revwalk_mergebase__octopus_no_common_ancestor_returns_ENOTFOUND(void) -{ - assert_mergebase_octopus(NULL, 3, "41bc8c", "e90810", "a65fed"); - assert_mergebase_octopus(NULL, 3, "e90810", "41bc8c", "a65fed"); - assert_mergebase_octopus(NULL, 3, "e90810", "a65fed", "41bc8c"); - assert_mergebase_octopus(NULL, 3, "a65fed", "e90810", "41bc8c"); - assert_mergebase_octopus(NULL, 3, "a65fed", "41bc8c", "e90810"); - - assert_mergebase_octopus(NULL, 3, "e90810", "763d71", "a65fed"); - - assert_mergebase_octopus(NULL, 3, "763d71", "e90810", "a65fed"); - assert_mergebase_octopus(NULL, 3, "763d71", "a65fed", "e90810"); - - assert_mergebase_octopus(NULL, 5, "5b5b02", "763d71", "a4a7dc", "a65fed", "41bc8c"); -} - -void test_revwalk_mergebase__octopus_merge_branch(void) -{ - assert_mergebase_octopus("8496071c1b46c854b31185ea97743be6a8774479", 3, "a65fed", "763d71", "849607"); - - assert_mergebase_octopus("8496071c1b46c854b31185ea97743be6a8774479", 3, "a65fed", "763d71", "849607"); - assert_mergebase_octopus("8496071c1b46c854b31185ea97743be6a8774479", 3, "a65fed", "849607", "763d71"); - assert_mergebase_octopus("8496071c1b46c854b31185ea97743be6a8774479", 3, "849607", "a65fed", "763d71"); -} - -/* - * testrepo.git $ git log --graph --all - * * commit 763d71aadf09a7951596c9746c024e7eece7c7af - * | Author: nulltoken - * | Date: Sun Oct 9 12:54:47 2011 +0200 - * | - * | Add some files into subdirectories - * | - * | * commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750 - * | | Author: Scott Chacon - * | | Date: Tue Aug 9 19:33:46 2011 -0700 - * | | - * | * commit be3563ae3f795b2b4353bcce3a527ad0a4f7f644 - * | |\ Merge: 9fd738e c47800c - * | |/ Author: Scott Chacon - * |/| Date: Tue May 25 11:58:27 2010 -0700 - * | | - * | | Merge branch 'br2' - * | | - * | | * commit e90810b8df3e80c413d903f631643c716887138d - * | | | Author: Vicent Marti - * | | | Date: Thu Aug 5 18:42:20 2010 +0200 - * | | | - * | | | Test commit 2 - * | | | - * | | * commit 6dcf9bf7541ee10456529833502442f385010c3d - * | | Author: Vicent Marti - * | | Date: Thu Aug 5 18:41:33 2010 +0200 - * | | - * | | Test commit 1 - * | | - * | | * commit a4a7dce85cf63874e984719f4fdd239f5145052f - * | | |\ Merge: c47800c 9fd738e - * | |/ / Author: Scott Chacon - * |/| / Date: Tue May 25 12:00:23 2010 -0700 - * | |/ - * | | Merge branch 'master' into br2 - * | | - * | * commit 9fd738e8f7967c078dceed8190330fc8648ee56a - * | | Author: Scott Chacon - * | | Date: Mon May 24 10:19:19 2010 -0700 - * | | - * | | a fourth commit - * | | - * | * commit 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 - * | | Author: Scott Chacon - * | | Date: Mon May 24 10:19:04 2010 -0700 - * | | - * | | a third commit - * | | - * * | commit c47800c7266a2be04c571c04d5a6614691ea99bd - * |/ Author: Scott Chacon - * | Date: Tue May 25 11:58:14 2010 -0700 - * | - * | branch commit one - * | - * * commit 5b5b025afb0b4c913b4c338a42934a3863bf3644 - * | Author: Scott Chacon - * | Date: Tue May 11 13:38:42 2010 -0700 - * | - * | another commit - * | - * * commit 8496071c1b46c854b31185ea97743be6a8774479 - * Author: Scott Chacon - * Date: Sat May 8 16:13:06 2010 -0700 - * - * testing - * - * * commit 41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9 - * | Author: Scott Chacon - * | Date: Tue May 11 13:40:41 2010 -0700 - * | - * | packed commit two - * | - * * commit 5001298e0c09ad9c34e4249bc5801c75e9754fa5 - * Author: Scott Chacon - * Date: Tue May 11 13:40:23 2010 -0700 - * - * packed commit one - */ - -/* - * twowaymerge.git $ git log --graph --all - * * commit 9b219343610c88a1187c996d0dc58330b55cee28 - * |\ Merge: c37a783 2224e19 - * | | Author: Scott J. Goldman - * | | Date: Tue Nov 27 20:31:04 2012 -0800 - * | | - * | | Merge branch 'first-branch' into second-branch - * | | - * | * commit 2224e191514cb4bd8c566d80dac22dfcb1e9bb83 - * | | Author: Scott J. Goldman - * | | Date: Tue Nov 27 20:28:51 2012 -0800 - * | | - * | | j - * | | - * | * commit a41a49f8f5cd9b6cb14a076bf8394881ed0b4d19 - * | | Author: Scott J. Goldman - * | | Date: Tue Nov 27 20:28:39 2012 -0800 - * | | - * | | i - * | | - * | * commit 82bf9a1a10a4b25c1f14c9607b60970705e92545 - * | | Author: Scott J. Goldman - * | | Date: Tue Nov 27 20:28:28 2012 -0800 - * | | - * | | h - * | | - * * | commit c37a783c20d92ac92362a78a32860f7eebf938ef - * | | Author: Scott J. Goldman - * | | Date: Tue Nov 27 20:30:57 2012 -0800 - * | | - * | | n - * | | - * * | commit 8b82fb1794cb1c8c7f172ec730a4c2db0ae3e650 - * | | Author: Scott J. Goldman - * | | Date: Tue Nov 27 20:30:43 2012 -0800 - * | | - * | | m - * | | - * * | commit 6ab5d28acbf3c3bdff276f7ccfdf29c1520e542f - * | | Author: Scott J. Goldman - * | | Date: Tue Nov 27 20:30:38 2012 -0800 - * | | - * | | l - * | | - * * | commit 7b8c336c45fc6895c1c60827260fe5d798e5d247 - * | | Author: Scott J. Goldman - * | | Date: Tue Nov 27 20:30:24 2012 -0800 - * | | - * | | k - * | | - * | | * commit 1c30b88f5f3ee66d78df6520a7de9e89b890818b - * | | | Author: Scott J. Goldman - * | | | Date: Tue Nov 27 20:28:10 2012 -0800 - * | | | - * | | | e - * | | | - * | | * commit 42b7311aa626e712891940c1ec5d5cba201946a4 - * | | | Author: Scott J. Goldman - * | | | Date: Tue Nov 27 20:28:06 2012 -0800 - * | | | - * | | | d - * | | | - * | | * commit a953a018c5b10b20c86e69fef55ebc8ad4c5a417 - * | | |\ Merge: bd1732c cdf97fd - * | | |/ Author: Scott J. Goldman - * | |/| Date: Tue Nov 27 20:26:43 2012 -0800 - * | | | - * | | | Merge branch 'first-branch' - * | | | - * | * | commit cdf97fd3bb48eb3827638bb33d208f5fd32d0aa6 - * | | | Author: Scott J. Goldman - * | | | Date: Tue Nov 27 20:24:46 2012 -0800 - * | | | - * | | | g - * | | | - * | * | commit ef0488f0b722f0be8bcb90a7730ac7efafd1d694 - * | | | Author: Scott J. Goldman - * | | | Date: Tue Nov 27 20:24:39 2012 -0800 - * | | | - * | | | f - * | | | - * | | * commit bd1732c43c68d712ad09e1d872b9be6d4b9efdc4 - * | |/ Author: Scott J. Goldman - * | | Date: Tue Nov 27 17:43:58 2012 -0800 - * | | - * | | c - * | | - * | * commit 0c8a3f1f3d5f421cf83048c7c73ee3b55a5e0f29 - * |/ Author: Scott J. Goldman - * | Date: Tue Nov 27 17:43:48 2012 -0800 - * | - * | b - * | - * * commit 1f4c0311a24b63f6fc209a59a1e404942d4a5006 - * Author: Scott J. Goldman - * Date: Tue Nov 27 17:43:41 2012 -0800 - * - * a - */ - -void test_revwalk_mergebase__remove_redundant(void) -{ - git_repository *repo; - git_oid one, two, base; - git_oidarray result = {NULL, 0}; - - cl_git_pass(git_repository_open(&repo, cl_fixture("redundant.git"))); - - cl_git_pass(git_oid_fromstr(&one, "d89137c93ba1ee749214ff4ce52ae9137bc833f9")); - cl_git_pass(git_oid_fromstr(&two, "91f4b95df4a59504a9813ba66912562931d990e3")); - cl_git_pass(git_oid_fromstr(&base, "6cb1f2352d974e1c5a776093017e8772416ac97a")); - - cl_git_pass(git_merge_bases(&result, repo, &one, &two)); - cl_assert_equal_i(1, result.count); - cl_assert_equal_oid(&base, &result.ids[0]); - - git_oidarray_dispose(&result); - git_repository_free(repo); -} diff --git a/tests/revwalk/signatureparsing.c b/tests/revwalk/signatureparsing.c deleted file mode 100644 index b312bad09..000000000 --- a/tests/revwalk/signatureparsing.c +++ /dev/null @@ -1,47 +0,0 @@ -#include "clar_libgit2.h" - -static git_repository *_repo; -static git_revwalk *_walk; - -void test_revwalk_signatureparsing__initialize(void) -{ - cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_revwalk_new(&_walk, _repo)); -} - -void test_revwalk_signatureparsing__cleanup(void) -{ - git_revwalk_free(_walk); - _walk = NULL; - - git_repository_free(_repo); - _repo = NULL; -} - -void test_revwalk_signatureparsing__do_not_choke_when_name_contains_angle_brackets(void) -{ - git_reference *ref; - git_oid commit_oid; - git_commit *commit; - const git_signature *signature; - - /* - * The branch below points at a commit with angle brackets in the committer/author name - * committer 1323847743 +0100 - */ - cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/haacked")); - - git_revwalk_push(_walk, git_reference_target(ref)); - cl_git_pass(git_revwalk_next(&commit_oid, _walk)); - - cl_git_pass(git_commit_lookup(&commit, _repo, git_reference_target(ref))); - - signature = git_commit_committer(commit); - cl_assert_equal_s("foo@example.com", signature->email); - cl_assert_equal_s("Yu V. Bin Haacked", signature->name); - cl_assert_equal_i(1323847743, (int)signature->when.time); - cl_assert_equal_i(60, signature->when.offset); - - git_commit_free(commit); - git_reference_free(ref); -} diff --git a/tests/revwalk/simplify.c b/tests/revwalk/simplify.c deleted file mode 100644 index 6dd068a42..000000000 --- a/tests/revwalk/simplify.c +++ /dev/null @@ -1,56 +0,0 @@ -#include "clar_libgit2.h" - -void test_revwalk_simplify__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -/* - * a4a7dce [0] Merge branch 'master' into br2 - |\ - | * 9fd738e [1] a fourth commit - | * 4a202b3 [2] a third commit - * | c47800c [3] branch commit one - |/ - * 5b5b025 [5] another commit - * 8496071 [4] testing -*/ -static const char *commit_head = "a4a7dce85cf63874e984719f4fdd239f5145052f"; - -static const char *expected_str[] = { - "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */ - "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */ - "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 4 */ - "8496071c1b46c854b31185ea97743be6a8774479", /* 5 */ -}; - -void test_revwalk_simplify__first_parent(void) -{ - git_repository *repo; - git_revwalk *walk; - git_oid id, expected[4]; - int i, error; - - for (i = 0; i < 4; i++) { - git_oid_fromstr(&expected[i], expected_str[i]); - } - - repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_revwalk_new(&walk, repo)); - - git_oid_fromstr(&id, commit_head); - cl_git_pass(git_revwalk_push(walk, &id)); - git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL); - git_revwalk_simplify_first_parent(walk); - - i = 0; - while ((error = git_revwalk_next(&id, walk)) == 0) { - cl_assert_equal_oid(&expected[i], &id); - i++; - } - - cl_assert_equal_i(i, 4); - cl_assert_equal_i(error, GIT_ITEROVER); - - git_revwalk_free(walk); -} diff --git a/tests/stash/apply.c b/tests/stash/apply.c deleted file mode 100644 index 5125ae639..000000000 --- a/tests/stash/apply.c +++ /dev/null @@ -1,449 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "stash_helpers.h" - -static git_signature *signature; -static git_repository *repo; -static git_index *repo_index; - -void test_stash_apply__initialize(void) -{ - git_oid oid; - - repo = cl_git_sandbox_init_new("stash"); - cl_git_pass(git_repository_index(&repo_index, repo)); - cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ - - cl_git_mkfile("stash/what", "hello\n"); - cl_git_mkfile("stash/how", "small\n"); - cl_git_mkfile("stash/who", "world\n"); - cl_git_mkfile("stash/where", "meh\n"); - - cl_git_pass(git_index_add_bypath(repo_index, "what")); - cl_git_pass(git_index_add_bypath(repo_index, "how")); - cl_git_pass(git_index_add_bypath(repo_index, "who")); - - cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit"); - - cl_git_rewritefile("stash/what", "goodbye\n"); - cl_git_rewritefile("stash/who", "funky world\n"); - cl_git_mkfile("stash/when", "tomorrow\n"); - cl_git_mkfile("stash/why", "would anybody use stash?\n"); - cl_git_mkfile("stash/where", "????\n"); - - cl_git_pass(git_index_add_bypath(repo_index, "who")); - cl_git_pass(git_index_add_bypath(repo_index, "why")); - cl_git_pass(git_index_add_bypath(repo_index, "where")); - cl_git_pass(git_index_write(repo_index)); - - cl_git_rewritefile("stash/where", "....\n"); - - /* Pre-stash state */ - assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "why", GIT_STATUS_INDEX_NEW); - assert_status(repo, "where", GIT_STATUS_INDEX_NEW|GIT_STATUS_WT_MODIFIED); - - cl_git_pass(git_stash_save(&oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); - - /* Post-stash state */ - assert_status(repo, "what", GIT_STATUS_CURRENT); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_CURRENT); - assert_status(repo, "when", GIT_ENOTFOUND); - assert_status(repo, "why", GIT_ENOTFOUND); - assert_status(repo, "where", GIT_ENOTFOUND); -} - -void test_stash_apply__cleanup(void) -{ - git_signature_free(signature); - signature = NULL; - - git_index_free(repo_index); - repo_index = NULL; - - cl_git_sandbox_cleanup(); -} - -void test_stash_apply__with_default(void) -{ - git_str where = GIT_STR_INIT; - - cl_git_pass(git_stash_apply(repo, 0, NULL)); - - cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); - assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "why", GIT_STATUS_INDEX_NEW); - assert_status(repo, "where", GIT_STATUS_INDEX_NEW); - - cl_git_pass(git_futils_readbuffer(&where, "stash/where")); - cl_assert_equal_s("....\n", where.ptr); - - git_str_dispose(&where); -} - -void test_stash_apply__with_existing_file(void) -{ - cl_git_mkfile("stash/where", "oops!\n"); - cl_git_fail(git_stash_apply(repo, 0, NULL)); -} - -void test_stash_apply__merges_new_file(void) -{ - const git_index_entry *ancestor, *our, *their; - - cl_git_mkfile("stash/where", "committed before stash\n"); - cl_git_pass(git_index_add_bypath(repo_index, "where")); - cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); - - cl_git_pass(git_stash_apply(repo, 0, NULL)); - - cl_assert_equal_i(1, git_index_has_conflicts(repo_index)); - assert_status(repo, "what", GIT_STATUS_INDEX_MODIFIED); - cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "where")); /* unmerged */ - assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "why", GIT_STATUS_INDEX_NEW); -} - -void test_stash_apply__with_reinstate_index(void) -{ - git_str where = GIT_STR_INIT; - git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; - - opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX; - - cl_git_pass(git_stash_apply(repo, 0, &opts)); - - cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); - assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "why", GIT_STATUS_INDEX_NEW); - assert_status(repo, "where", GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_MODIFIED); - - cl_git_pass(git_futils_readbuffer(&where, "stash/where")); - cl_assert_equal_s("....\n", where.ptr); - - git_str_dispose(&where); -} - -void test_stash_apply__conflict_index_with_default(void) -{ - const git_index_entry *ancestor; - const git_index_entry *our; - const git_index_entry *their; - - cl_git_rewritefile("stash/who", "nothing\n"); - cl_git_pass(git_index_add_bypath(repo_index, "who")); - cl_git_pass(git_index_write(repo_index)); - cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); - - cl_git_pass(git_stash_apply(repo, 0, NULL)); - - cl_assert_equal_i(git_index_has_conflicts(repo_index), 1); - assert_status(repo, "what", GIT_STATUS_INDEX_MODIFIED); - assert_status(repo, "how", GIT_STATUS_CURRENT); - cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "who")); /* unmerged */ - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "why", GIT_STATUS_INDEX_NEW); -} - -void test_stash_apply__conflict_index_with_reinstate_index(void) -{ - git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; - - opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX; - - cl_git_rewritefile("stash/who", "nothing\n"); - cl_git_pass(git_index_add_bypath(repo_index, "who")); - cl_git_pass(git_index_write(repo_index)); - cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); - - cl_git_fail_with(git_stash_apply(repo, 0, &opts), GIT_ECONFLICT); - - cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); - assert_status(repo, "what", GIT_STATUS_CURRENT); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_CURRENT); - assert_status(repo, "when", GIT_ENOTFOUND); - assert_status(repo, "why", GIT_ENOTFOUND); -} - -void test_stash_apply__conflict_untracked_with_default(void) -{ - git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; - - cl_git_mkfile("stash/when", "nothing\n"); - - cl_git_fail_with(git_stash_apply(repo, 0, &opts), GIT_ECONFLICT); - - cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); - assert_status(repo, "what", GIT_STATUS_CURRENT); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_CURRENT); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "why", GIT_ENOTFOUND); -} - -void test_stash_apply__conflict_untracked_with_reinstate_index(void) -{ - git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; - - opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX; - - cl_git_mkfile("stash/when", "nothing\n"); - - cl_git_fail_with(git_stash_apply(repo, 0, &opts), GIT_ECONFLICT); - - cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); - assert_status(repo, "what", GIT_STATUS_CURRENT); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_CURRENT); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "why", GIT_ENOTFOUND); -} - -void test_stash_apply__conflict_workdir_with_default(void) -{ - cl_git_rewritefile("stash/what", "ciao\n"); - - cl_git_fail_with(git_stash_apply(repo, 0, NULL), GIT_ECONFLICT); - - cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); - assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_CURRENT); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "why", GIT_ENOTFOUND); -} - -void test_stash_apply__conflict_workdir_with_reinstate_index(void) -{ - git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; - - opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX; - - cl_git_rewritefile("stash/what", "ciao\n"); - - cl_git_fail_with(git_stash_apply(repo, 0, &opts), GIT_ECONFLICT); - - cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); - assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_CURRENT); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "why", GIT_ENOTFOUND); -} - -void test_stash_apply__conflict_commit_with_default(void) -{ - const git_index_entry *ancestor; - const git_index_entry *our; - const git_index_entry *their; - - cl_git_rewritefile("stash/what", "ciao\n"); - cl_git_pass(git_index_add_bypath(repo_index, "what")); - cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); - - cl_git_pass(git_stash_apply(repo, 0, NULL)); - - cl_assert_equal_i(git_index_has_conflicts(repo_index), 1); - cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "what")); /* unmerged */ - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "why", GIT_STATUS_INDEX_NEW); -} - -void test_stash_apply__conflict_commit_with_reinstate_index(void) -{ - git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; - const git_index_entry *ancestor; - const git_index_entry *our; - const git_index_entry *their; - - opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX; - - cl_git_rewritefile("stash/what", "ciao\n"); - cl_git_pass(git_index_add_bypath(repo_index, "what")); - cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit"); - - cl_git_pass(git_stash_apply(repo, 0, &opts)); - - cl_assert_equal_i(git_index_has_conflicts(repo_index), 1); - cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "what")); /* unmerged */ - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "why", GIT_STATUS_INDEX_NEW); -} - -void test_stash_apply__fails_with_uncommitted_changes_in_index(void) -{ - cl_git_rewritefile("stash/who", "nothing\n"); - cl_git_pass(git_index_add_bypath(repo_index, "who")); - cl_git_pass(git_index_write(repo_index)); - - cl_git_fail_with(git_stash_apply(repo, 0, NULL), GIT_EUNCOMMITTED); - - cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); - assert_status(repo, "what", GIT_STATUS_CURRENT); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED); - assert_status(repo, "when", GIT_ENOTFOUND); - assert_status(repo, "why", GIT_ENOTFOUND); -} - -void test_stash_apply__pop(void) -{ - cl_git_pass(git_stash_pop(repo, 0, NULL)); - - cl_git_fail_with(git_stash_pop(repo, 0, NULL), GIT_ENOTFOUND); -} - -struct seen_paths { - bool what; - bool how; - bool who; - bool when; -}; - -static int checkout_notify( - git_checkout_notify_t why, - const char *path, - const git_diff_file *baseline, - const git_diff_file *target, - const git_diff_file *workdir, - void *payload) -{ - struct seen_paths *seen_paths = (struct seen_paths *)payload; - - GIT_UNUSED(why); - GIT_UNUSED(baseline); - GIT_UNUSED(target); - GIT_UNUSED(workdir); - - if (strcmp(path, "what") == 0) - seen_paths->what = 1; - else if (strcmp(path, "how") == 0) - seen_paths->how = 1; - else if (strcmp(path, "who") == 0) - seen_paths->who = 1; - else if (strcmp(path, "when") == 0) - seen_paths->when = 1; - - return 0; -} - -void test_stash_apply__executes_notify_cb(void) -{ - git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; - struct seen_paths seen_paths = {0}; - - opts.checkout_options.notify_cb = checkout_notify; - opts.checkout_options.notify_flags = GIT_CHECKOUT_NOTIFY_ALL; - opts.checkout_options.notify_payload = &seen_paths; - - cl_git_pass(git_stash_apply(repo, 0, &opts)); - - cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); - assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "why", GIT_STATUS_INDEX_NEW); - assert_status(repo, "where", GIT_STATUS_INDEX_NEW); - - cl_assert_equal_b(true, seen_paths.what); - cl_assert_equal_b(false, seen_paths.how); - cl_assert_equal_b(true, seen_paths.who); - cl_assert_equal_b(true, seen_paths.when); -} - -static int progress_cb( - git_stash_apply_progress_t progress, - void *payload) -{ - git_stash_apply_progress_t *p = (git_stash_apply_progress_t *)payload; - - cl_assert_equal_i((*p)+1, progress); - - *p = progress; - - return 0; -} - -void test_stash_apply__calls_progress_cb(void) -{ - git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; - git_stash_apply_progress_t progress = GIT_STASH_APPLY_PROGRESS_NONE; - - opts.progress_cb = progress_cb; - opts.progress_payload = &progress; - - cl_git_pass(git_stash_apply(repo, 0, &opts)); - cl_assert_equal_i(progress, GIT_STASH_APPLY_PROGRESS_DONE); -} - -static int aborting_progress_cb( - git_stash_apply_progress_t progress, - void *payload) -{ - GIT_UNUSED(payload); - - if (progress == GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED) - return -44; - - return 0; -} - -void test_stash_apply__progress_cb_can_abort(void) -{ - git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT; - - opts.progress_cb = aborting_progress_cb; - - cl_git_fail_with(-44, git_stash_apply(repo, 0, &opts)); -} - -void test_stash_apply__uses_reflog_like_indices_1(void) -{ - git_oid oid; - - cl_git_mkfile("stash/untracked", "untracked\n"); - cl_git_pass(git_stash_save(&oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); - assert_status(repo, "untracked", GIT_ENOTFOUND); - - /* stash@{1} is the oldest (first) stash we made */ - cl_git_pass(git_stash_apply(repo, 1, NULL)); - cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); - assert_status(repo, "what", GIT_STATUS_WT_MODIFIED); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "why", GIT_STATUS_INDEX_NEW); - assert_status(repo, "where", GIT_STATUS_INDEX_NEW); -} - -void test_stash_apply__uses_reflog_like_indices_2(void) -{ - git_oid oid; - - cl_git_mkfile("stash/untracked", "untracked\n"); - cl_git_pass(git_stash_save(&oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); - assert_status(repo, "untracked", GIT_ENOTFOUND); - - /* stash@{0} is the newest stash we made immediately above */ - cl_git_pass(git_stash_apply(repo, 0, NULL)); - - cl_assert_equal_i(git_index_has_conflicts(repo_index), 0); - assert_status(repo, "untracked", GIT_STATUS_WT_NEW); -} diff --git a/tests/stash/drop.c b/tests/stash/drop.c deleted file mode 100644 index a57147172..000000000 --- a/tests/stash/drop.c +++ /dev/null @@ -1,174 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "stash_helpers.h" -#include "refs.h" - -static git_repository *repo; -static git_signature *signature; - -void test_stash_drop__initialize(void) -{ - cl_git_pass(git_repository_init(&repo, "stash", 0)); - cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ -} - -void test_stash_drop__cleanup(void) -{ - git_signature_free(signature); - signature = NULL; - - git_repository_free(repo); - repo = NULL; - - cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_stash_drop__cannot_drop_from_an_empty_stash(void) -{ - cl_git_fail_with(git_stash_drop(repo, 0), GIT_ENOTFOUND); -} - -static void push_three_states(void) -{ - git_oid oid; - git_index *index; - - cl_git_mkfile("stash/zero.txt", "content\n"); - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, "zero.txt")); - cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit"); - cl_assert(git_fs_path_exists("stash/zero.txt")); - git_index_free(index); - - cl_git_mkfile("stash/one.txt", "content\n"); - cl_git_pass(git_stash_save( - &oid, repo, signature, "First", GIT_STASH_INCLUDE_UNTRACKED)); - cl_assert(!git_fs_path_exists("stash/one.txt")); - cl_assert(git_fs_path_exists("stash/zero.txt")); - - cl_git_mkfile("stash/two.txt", "content\n"); - cl_git_pass(git_stash_save( - &oid, repo, signature, "Second", GIT_STASH_INCLUDE_UNTRACKED)); - cl_assert(!git_fs_path_exists("stash/two.txt")); - cl_assert(git_fs_path_exists("stash/zero.txt")); - - cl_git_mkfile("stash/three.txt", "content\n"); - cl_git_pass(git_stash_save( - &oid, repo, signature, "Third", GIT_STASH_INCLUDE_UNTRACKED)); - cl_assert(!git_fs_path_exists("stash/three.txt")); - cl_assert(git_fs_path_exists("stash/zero.txt")); -} - -void test_stash_drop__cannot_drop_a_non_existing_stashed_state(void) -{ - push_three_states(); - - cl_git_fail_with(git_stash_drop(repo, 666), GIT_ENOTFOUND); - cl_git_fail_with(git_stash_drop(repo, 42), GIT_ENOTFOUND); - cl_git_fail_with(git_stash_drop(repo, 3), GIT_ENOTFOUND); -} - -void test_stash_drop__can_purge_the_stash_from_the_top(void) -{ - push_three_states(); - - cl_git_pass(git_stash_drop(repo, 0)); - cl_git_pass(git_stash_drop(repo, 0)); - cl_git_pass(git_stash_drop(repo, 0)); - - cl_git_fail_with(git_stash_drop(repo, 0), GIT_ENOTFOUND); -} - -void test_stash_drop__can_purge_the_stash_from_the_bottom(void) -{ - push_three_states(); - - cl_git_pass(git_stash_drop(repo, 2)); - cl_git_pass(git_stash_drop(repo, 1)); - cl_git_pass(git_stash_drop(repo, 0)); - - cl_git_fail_with(git_stash_drop(repo, 0), GIT_ENOTFOUND); -} - -void test_stash_drop__dropping_an_entry_rewrites_reflog_history(void) -{ - git_reference *stash; - git_reflog *reflog; - const git_reflog_entry *entry; - git_oid oid; - size_t count; - - push_three_states(); - - cl_git_pass(git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)); - - cl_git_pass(git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)); - entry = git_reflog_entry_byindex(reflog, 1); - - git_oid_cpy(&oid, git_reflog_entry_id_old(entry)); - count = git_reflog_entrycount(reflog); - - git_reflog_free(reflog); - - cl_git_pass(git_stash_drop(repo, 1)); - - cl_git_pass(git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)); - entry = git_reflog_entry_byindex(reflog, 0); - - cl_assert_equal_oid(&oid, git_reflog_entry_id_old(entry)); - cl_assert_equal_sz(count - 1, git_reflog_entrycount(reflog)); - - git_reflog_free(reflog); - - git_reference_free(stash); -} - -void test_stash_drop__dropping_the_last_entry_removes_the_stash(void) -{ - git_reference *stash; - - push_three_states(); - - cl_git_pass(git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)); - git_reference_free(stash); - - cl_git_pass(git_stash_drop(repo, 0)); - cl_git_pass(git_stash_drop(repo, 0)); - cl_git_pass(git_stash_drop(repo, 0)); - - cl_git_fail_with( - git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE), GIT_ENOTFOUND); -} - -static void retrieve_top_stash_id(git_oid *out) -{ - git_object *top_stash; - - cl_git_pass(git_revparse_single(&top_stash, repo, "stash@{0}")); - cl_git_pass(git_reference_name_to_id(out, repo, GIT_REFS_STASH_FILE)); - - cl_assert_equal_oid(out, git_object_id(top_stash)); - - git_object_free(top_stash); -} - -void test_stash_drop__dropping_the_top_stash_updates_the_stash_reference(void) -{ - git_object *next_top_stash; - git_oid oid; - - push_three_states(); - - retrieve_top_stash_id(&oid); - - cl_git_pass(git_revparse_single(&next_top_stash, repo, "stash@{1}")); - cl_assert(git_oid_cmp(&oid, git_object_id(next_top_stash))); - - cl_git_pass(git_stash_drop(repo, 0)); - - retrieve_top_stash_id(&oid); - - cl_assert_equal_oid(&oid, git_object_id(next_top_stash)); - - git_object_free(next_top_stash); -} diff --git a/tests/stash/foreach.c b/tests/stash/foreach.c deleted file mode 100644 index fa3a9c906..000000000 --- a/tests/stash/foreach.c +++ /dev/null @@ -1,126 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "stash_helpers.h" - -struct callback_data -{ - char **oids; - int invokes; -}; - -static git_repository *repo; -static git_signature *signature; -static git_oid stash_tip_oid; -struct callback_data data; - -#define REPO_NAME "stash" - -void test_stash_foreach__initialize(void) -{ - cl_git_pass(git_signature_new( - &signature, - "nulltoken", - "emeric.fermas@gmail.com", - 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ - - memset(&data, 0, sizeof(struct callback_data)); -} - -void test_stash_foreach__cleanup(void) -{ - git_signature_free(signature); - signature = NULL; - - git_repository_free(repo); - repo = NULL; - - cl_git_pass(git_futils_rmdir_r(REPO_NAME, NULL, GIT_RMDIR_REMOVE_FILES)); -} - -static int callback_cb( - size_t index, - const char* message, - const git_oid *stash_oid, - void *payload) -{ - struct callback_data *data = (struct callback_data *)payload; - - GIT_UNUSED(index); - GIT_UNUSED(message); - - cl_assert_equal_i(0, git_oid_streq(stash_oid, data->oids[data->invokes++])); - - return 0; -} - -void test_stash_foreach__enumerating_a_empty_repository_doesnt_fail(void) -{ - char *oids[] = { NULL }; - - data.oids = oids; - - cl_git_pass(git_repository_init(&repo, REPO_NAME, 0)); - - cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); - - cl_assert_equal_i(0, data.invokes); -} - -void test_stash_foreach__can_enumerate_a_repository(void) -{ - char *oids_default[] = { - "493568b7a2681187aaac8a58d3f1eab1527cba84", NULL }; - - char *oids_untracked[] = { - "7f89a8b15c878809c5c54d1ff8f8c9674154017b", - "493568b7a2681187aaac8a58d3f1eab1527cba84", NULL }; - - char *oids_ignored[] = { - "c95599a8fef20a7e57582c6727b1a0d02e0a5828", - "7f89a8b15c878809c5c54d1ff8f8c9674154017b", - "493568b7a2681187aaac8a58d3f1eab1527cba84", NULL }; - - cl_git_pass(git_repository_init(&repo, REPO_NAME, 0)); - - setup_stash(repo, signature); - - cl_git_pass(git_stash_save( - &stash_tip_oid, - repo, - signature, - NULL, - GIT_STASH_DEFAULT)); - - data.oids = oids_default; - - cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); - cl_assert_equal_i(1, data.invokes); - - /* ensure stash_foreach operates with INCLUDE_UNTRACKED */ - cl_git_pass(git_stash_save( - &stash_tip_oid, - repo, - signature, - NULL, - GIT_STASH_INCLUDE_UNTRACKED)); - - data.oids = oids_untracked; - data.invokes = 0; - - cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); - cl_assert_equal_i(2, data.invokes); - - /* ensure stash_foreach operates with INCLUDE_IGNORED */ - cl_git_pass(git_stash_save( - &stash_tip_oid, - repo, - signature, - NULL, - GIT_STASH_INCLUDE_IGNORED)); - - data.oids = oids_ignored; - data.invokes = 0; - - cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); - cl_assert_equal_i(3, data.invokes); -} diff --git a/tests/stash/save.c b/tests/stash/save.c deleted file mode 100644 index f574211d7..000000000 --- a/tests/stash/save.c +++ /dev/null @@ -1,490 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "stash_helpers.h" - -static git_repository *repo; -static git_signature *signature; -static git_oid stash_tip_oid; - -/* - * Friendly reminder, in order to ease the reading of the following tests: - * - * "stash" points to the worktree commit - * "stash^1" points to the base commit (HEAD when the stash was created) - * "stash^2" points to the index commit - * "stash^3" points to the untracked commit - */ - -void test_stash_save__initialize(void) -{ - cl_git_pass(git_repository_init(&repo, "stash", 0)); - cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ - - setup_stash(repo, signature); -} - -void test_stash_save__cleanup(void) -{ - git_signature_free(signature); - signature = NULL; - - git_repository_free(repo); - repo = NULL; - - cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_fixture_cleanup("sorry-it-is-a-non-bare-only-party"); -} - -static void assert_object_oid(const char* revision, const char* expected_oid, git_object_t type) -{ - int result; - git_object *obj; - - result = git_revparse_single(&obj, repo, revision); - - if (!expected_oid) { - cl_assert_equal_i(GIT_ENOTFOUND, result); - return; - } else - cl_assert_equal_i(0, result); - - cl_git_pass(git_oid_streq(git_object_id(obj), expected_oid)); - cl_assert_equal_i(type, git_object_type(obj)); - git_object_free(obj); -} - -static void assert_blob_oid(const char* revision, const char* expected_oid) -{ - assert_object_oid(revision, expected_oid, GIT_OBJECT_BLOB); -} - -void test_stash_save__does_not_keep_index_by_default(void) -{ -/* -$ git stash - -$ git show refs/stash:what -see you later - -$ git show refs/stash:how -not so small and - -$ git show refs/stash:who -funky world - -$ git show refs/stash:when -fatal: Path 'when' exists on disk, but not in 'stash'. - -$ git show refs/stash^2:what -goodbye - -$ git show refs/stash^2:how -not so small and - -$ git show refs/stash^2:who -world - -$ git show refs/stash^2:when -fatal: Path 'when' exists on disk, but not in 'stash^2'. - -$ git status --short -?? when - -*/ - unsigned int status; - - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); - cl_git_pass(git_status_file(&status, repo, "when")); - - assert_blob_oid("refs/stash:what", "bc99dc98b3eba0e9157e94769cd4d49cb49de449"); /* see you later */ - assert_blob_oid("refs/stash:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ - assert_blob_oid("refs/stash:who", "a0400d4954659306a976567af43125a0b1aa8595"); /* funky world */ - assert_blob_oid("refs/stash:when", NULL); - assert_blob_oid("refs/stash:why", "88c2533e21f098b89c91a431d8075cbdbe422a51"); /* would anybody use stash? */ - assert_blob_oid("refs/stash:where", "e3d6434ec12eb76af8dfa843a64ba6ab91014a0b"); /* .... */ - assert_blob_oid("refs/stash:.gitignore", "ac4d88de61733173d9959e4b77c69b9f17a00980"); - assert_blob_oid("refs/stash:just.ignore", NULL); - - assert_blob_oid("refs/stash^2:what", "dd7e1c6f0fefe118f0b63d9f10908c460aa317a6"); /* goodbye */ - assert_blob_oid("refs/stash^2:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ - assert_blob_oid("refs/stash^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ - assert_blob_oid("refs/stash^2:when", NULL); - assert_blob_oid("refs/stash^2:why", "88c2533e21f098b89c91a431d8075cbdbe422a51"); /* would anybody use stash? */ - assert_blob_oid("refs/stash^2:where", "e08f7fbb9a42a0c5367cf8b349f1f08c3d56bd72"); /* ???? */ - assert_blob_oid("refs/stash^2:.gitignore", "ac4d88de61733173d9959e4b77c69b9f17a00980"); - assert_blob_oid("refs/stash^2:just.ignore", NULL); - - assert_blob_oid("refs/stash^3", NULL); - - cl_assert_equal_i(GIT_STATUS_WT_NEW, status); -} - -void test_stash_save__can_keep_index(void) -{ - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_KEEP_INDEX)); - - assert_status(repo, "what", GIT_STATUS_INDEX_MODIFIED); - assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED); - assert_status(repo, "who", GIT_STATUS_CURRENT); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); -} - -static void assert_commit_message_contains(const char *revision, const char *fragment) -{ - git_commit *commit; - - cl_git_pass(git_revparse_single((git_object**)&commit, repo, revision)); - - cl_assert(strstr(git_commit_message(commit), fragment) != NULL); - - git_commit_free(commit); -} - -void test_stash_save__can_include_untracked_files(void) -{ - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); - - assert_commit_message_contains("refs/stash^3", "untracked files on master: "); - - assert_blob_oid("refs/stash^3:what", NULL); - assert_blob_oid("refs/stash^3:how", NULL); - assert_blob_oid("refs/stash^3:who", NULL); - assert_blob_oid("refs/stash^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b"); - assert_blob_oid("refs/stash^3:just.ignore", NULL); -} - -void test_stash_save__untracked_skips_ignored(void) -{ - cl_git_append2file("stash/.gitignore", "bundle/vendor/\n"); - cl_must_pass(p_mkdir("stash/bundle", 0777)); - cl_must_pass(p_mkdir("stash/bundle/vendor", 0777)); - cl_git_mkfile("stash/bundle/vendor/blah", "contents\n"); - - cl_assert(git_fs_path_exists("stash/when")); /* untracked */ - cl_assert(git_fs_path_exists("stash/just.ignore")); /* ignored */ - cl_assert(git_fs_path_exists("stash/bundle/vendor/blah")); /* ignored */ - - cl_git_pass(git_stash_save( - &stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); - - cl_assert(!git_fs_path_exists("stash/when")); - cl_assert(git_fs_path_exists("stash/bundle/vendor/blah")); - cl_assert(git_fs_path_exists("stash/just.ignore")); -} - -void test_stash_save__can_include_untracked_and_ignored_files(void) -{ - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)); - - assert_commit_message_contains("refs/stash^3", "untracked files on master: "); - - assert_blob_oid("refs/stash^3:what", NULL); - assert_blob_oid("refs/stash^3:how", NULL); - assert_blob_oid("refs/stash^3:who", NULL); - assert_blob_oid("refs/stash^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b"); - assert_blob_oid("refs/stash^3:just.ignore", "78925fb1236b98b37a35e9723033e627f97aa88b"); - - cl_assert(!git_fs_path_exists("stash/just.ignore")); -} - -/* - * Note: this test was flaky prior to fixing #4101 -- run it several - * times to get a failure. The issues is that whether the fast - * (stat-only) codepath is used inside stash's diff operation depends - * on whether files are "racily clean", and there doesn't seem to be - * an easy way to force the exact required state. - */ -void test_stash_save__untracked_regression(void) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - const char *paths[] = {"what", "where", "how", "why"}; - git_reference *head; - git_commit *head_commit; - git_str untracked_dir; - - const char* workdir = git_repository_workdir(repo); - - git_str_init(&untracked_dir, 0); - git_str_printf(&untracked_dir, "%sz", workdir); - - cl_assert(!p_mkdir(untracked_dir.ptr, 0777)); - - cl_git_pass(git_repository_head(&head, repo)); - - cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJECT_COMMIT)); - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - opts.paths.strings = (char **)paths; - opts.paths.count = 4; - - cl_git_pass(git_checkout_tree(repo, (git_object*)head_commit, &opts)); - - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); - - assert_commit_message_contains("refs/stash", "WIP on master"); - - git_reference_free(head); - git_commit_free(head_commit); - git_str_dispose(&untracked_dir); -} - -#define MESSAGE "Look Ma! I'm on TV!" -void test_stash_save__can_accept_a_message(void) -{ - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, MESSAGE, GIT_STASH_DEFAULT)); - - assert_commit_message_contains("refs/stash^2", "index on master: "); - assert_commit_message_contains("refs/stash", "On master: " MESSAGE); -} - -void test_stash_save__cannot_stash_against_an_unborn_branch(void) -{ - git_reference *head; - - cl_git_pass(git_reference_symbolic_create(&head, repo, "HEAD", "refs/heads/unborn", 1, NULL)); - - cl_assert_equal_i(GIT_EUNBORNBRANCH, - git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); - - git_reference_free(head); -} - -void test_stash_save__cannot_stash_against_a_bare_repository(void) -{ - git_repository *local; - - cl_git_pass(git_repository_init(&local, "sorry-it-is-a-non-bare-only-party", 1)); - - cl_assert_equal_i(GIT_EBAREREPO, - git_stash_save(&stash_tip_oid, local, signature, NULL, GIT_STASH_DEFAULT)); - - git_repository_free(local); -} - -void test_stash_save__can_stash_against_a_detached_head(void) -{ - git_repository_detach_head(repo); - - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); - - assert_commit_message_contains("refs/stash^2", "index on (no branch): "); - assert_commit_message_contains("refs/stash", "WIP on (no branch): "); -} - -void test_stash_save__stashing_updates_the_reflog(void) -{ - assert_object_oid("refs/stash@{0}", NULL, GIT_OBJECT_COMMIT); - - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); - - assert_object_oid("refs/stash@{0}", git_oid_tostr_s(&stash_tip_oid), GIT_OBJECT_COMMIT); - assert_object_oid("refs/stash@{1}", NULL, GIT_OBJECT_COMMIT); -} - -void test_stash_save__multiline_message(void) -{ - const char *msg = "This\n\nis a multiline message\n"; - const git_reflog_entry *entry; - git_reflog *reflog; - - assert_object_oid("refs/stash@{0}", NULL, GIT_OBJECT_COMMIT); - - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, msg, GIT_STASH_DEFAULT)); - - cl_git_pass(git_reflog_read(&reflog, repo, "refs/stash")); - cl_assert(entry = git_reflog_entry_byindex(reflog, 0)); - cl_assert_equal_s(git_reflog_entry_message(entry), "On master: This is a multiline message"); - - assert_object_oid("refs/stash@{0}", git_oid_tostr_s(&stash_tip_oid), GIT_OBJECT_COMMIT); - assert_commit_message_contains("refs/stash@{0}", msg); - - git_reflog_free(reflog); -} - -void test_stash_save__cannot_stash_when_there_are_no_local_change(void) -{ - git_index *index; - git_oid stash_tip_oid; - - cl_git_pass(git_repository_index(&index, repo)); - - /* - * 'what', 'where' and 'who' are being committed. - * 'when' remains untracked. - */ - cl_git_pass(git_index_add_bypath(index, "what")); - cl_git_pass(git_index_add_bypath(index, "where")); - cl_git_pass(git_index_add_bypath(index, "who")); - - cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit"); - git_index_free(index); - - cl_assert_equal_i(GIT_ENOTFOUND, - git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); - - p_unlink("stash/when"); - cl_assert_equal_i(GIT_ENOTFOUND, - git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); -} - -void test_stash_save__can_stage_normal_then_stage_untracked(void) -{ - /* - * $ git ls-tree stash@{1}^0 - * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore - * 100644 blob e6d64adb2c7f3eb8feb493b556cc8070dca379a3 how - * 100644 blob bc99dc98b3eba0e9157e94769cd4d49cb49de449 what - * 100644 blob a0400d4954659306a976567af43125a0b1aa8595 who - * - * $ git ls-tree stash@{1}^1 - * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore - * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how - * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what - * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who - * - * $ git ls-tree stash@{1}^2 - * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore - * 100644 blob e6d64adb2c7f3eb8feb493b556cc8070dca379a3 how - * 100644 blob dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 what - * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who - * - * $ git ls-tree stash@{1}^3 - * fatal: Not a valid object name stash@{1}^3 - * - * $ git ls-tree stash@{0}^0 - * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore - * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how - * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what - * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who - * - * $ git ls-tree stash@{0}^1 - * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore - * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how - * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what - * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who - * - * $ git ls-tree stash@{0}^2 - * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore - * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how - * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what - * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who - * - * $ git ls-tree stash@{0}^3 - * 100644 blob b6ed15e81e2593d7bb6265eb4a991d29dc3e628b when - */ - - assert_status(repo, "what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED); - assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED); - assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); - - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); - assert_status(repo, "what", GIT_STATUS_CURRENT); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_CURRENT); - assert_status(repo, "when", GIT_STATUS_WT_NEW); - assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); - - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); - assert_status(repo, "what", GIT_STATUS_CURRENT); - assert_status(repo, "how", GIT_STATUS_CURRENT); - assert_status(repo, "who", GIT_STATUS_CURRENT); - assert_status(repo, "when", GIT_ENOTFOUND); - assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); - - - assert_blob_oid("stash@{1}^0:what", "bc99dc98b3eba0e9157e94769cd4d49cb49de449"); /* see you later */ - assert_blob_oid("stash@{1}^0:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ - assert_blob_oid("stash@{1}^0:who", "a0400d4954659306a976567af43125a0b1aa8595"); /* funky world */ - assert_blob_oid("stash@{1}^0:when", NULL); - - assert_blob_oid("stash@{1}^2:what", "dd7e1c6f0fefe118f0b63d9f10908c460aa317a6"); /* goodbye */ - assert_blob_oid("stash@{1}^2:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ - assert_blob_oid("stash@{1}^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ - assert_blob_oid("stash@{1}^2:when", NULL); - - assert_object_oid("stash@{1}^3", NULL, GIT_OBJECT_COMMIT); - - assert_blob_oid("stash@{0}^0:what", "ce013625030ba8dba906f756967f9e9ca394464a"); /* hello */ - assert_blob_oid("stash@{0}^0:how", "ac790413e2d7a26c3767e78c57bb28716686eebc"); /* small */ - assert_blob_oid("stash@{0}^0:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ - assert_blob_oid("stash@{0}^0:when", NULL); - - assert_blob_oid("stash@{0}^2:what", "ce013625030ba8dba906f756967f9e9ca394464a"); /* hello */ - assert_blob_oid("stash@{0}^2:how", "ac790413e2d7a26c3767e78c57bb28716686eebc"); /* small */ - assert_blob_oid("stash@{0}^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ - assert_blob_oid("stash@{0}^2:when", NULL); - - assert_blob_oid("stash@{0}^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b"); /* now */ -} - -#define EMPTY_TREE "4b825dc642cb6eb9a060e54bf8d69288fbee4904" - -void test_stash_save__including_untracked_without_any_untracked_file_creates_an_empty_tree(void) -{ - cl_must_pass(p_unlink("stash/when")); - - assert_status(repo, "what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED); - assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED); - assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); - assert_status(repo, "when", GIT_ENOTFOUND); - assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); - - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); - - assert_object_oid("stash^3^{tree}", EMPTY_TREE, GIT_OBJECT_TREE); -} - -void test_stash_save__ignored_directory(void) -{ - cl_git_pass(p_mkdir("stash/ignored_directory", 0777)); - cl_git_pass(p_mkdir("stash/ignored_directory/sub", 0777)); - cl_git_mkfile("stash/ignored_directory/sub/some_file", "stuff"); - - assert_status(repo, "ignored_directory/sub/some_file", GIT_STATUS_WT_NEW); - cl_git_pass(git_ignore_add_rule(repo, "ignored_directory/")); - assert_status(repo, "ignored_directory/sub/some_file", GIT_STATUS_IGNORED); - - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)); - - cl_assert(!git_fs_path_exists("stash/ignored_directory/sub/some_file")); - cl_assert(!git_fs_path_exists("stash/ignored_directory/sub")); - cl_assert(!git_fs_path_exists("stash/ignored_directory")); -} - -void test_stash_save__skip_submodules(void) -{ - git_repository *untracked_repo; - cl_git_pass(git_repository_init(&untracked_repo, "stash/untracked_repo", false)); - cl_git_mkfile("stash/untracked_repo/content", "stuff"); - git_repository_free(untracked_repo); - - assert_status(repo, "untracked_repo/", GIT_STATUS_WT_NEW); - - cl_git_pass(git_stash_save( - &stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); - - assert_status(repo, "untracked_repo/", GIT_STATUS_WT_NEW); -} - -void test_stash_save__deleted_in_index_modified_in_workdir(void) -{ - git_index *index; - - git_repository_index(&index, repo); - - cl_git_pass(git_index_remove_bypath(index, "who")); - cl_git_pass(git_index_write(index)); - - assert_status(repo, "who", GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED); - - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); - - assert_blob_oid("stash@{0}^0:who", "a0400d4954659306a976567af43125a0b1aa8595"); - assert_blob_oid("stash@{0}^2:who", NULL); - - git_index_free(index); -} diff --git a/tests/stash/stash_helpers.c b/tests/stash/stash_helpers.c deleted file mode 100644 index cd0cfbd0f..000000000 --- a/tests/stash/stash_helpers.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "stash_helpers.h" - -void setup_stash(git_repository *repo, git_signature *signature) -{ - git_index *index; - - cl_git_pass(git_repository_index(&index, repo)); - - cl_git_mkfile("stash/what", "hello\n"); /* ce013625030ba8dba906f756967f9e9ca394464a */ - cl_git_mkfile("stash/how", "small\n"); /* ac790413e2d7a26c3767e78c57bb28716686eebc */ - cl_git_mkfile("stash/who", "world\n"); /* cc628ccd10742baea8241c5924df992b5c019f71 */ - cl_git_mkfile("stash/when", "now\n"); /* b6ed15e81e2593d7bb6265eb4a991d29dc3e628b */ - cl_git_mkfile("stash/just.ignore", "me\n"); /* 78925fb1236b98b37a35e9723033e627f97aa88b */ - - cl_git_mkfile("stash/.gitignore", "*.ignore\n"); - - cl_git_pass(git_index_add_bypath(index, "what")); - cl_git_pass(git_index_add_bypath(index, "how")); - cl_git_pass(git_index_add_bypath(index, "who")); - cl_git_pass(git_index_add_bypath(index, ".gitignore")); - - cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit"); - - cl_git_rewritefile("stash/what", "goodbye\n"); /* dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 */ - cl_git_rewritefile("stash/how", "not so small and\n"); /* e6d64adb2c7f3eb8feb493b556cc8070dca379a3 */ - cl_git_rewritefile("stash/who", "funky world\n"); /* a0400d4954659306a976567af43125a0b1aa8595 */ - cl_git_mkfile("stash/why", "would anybody use stash?\n"); /* 88c2533e21f098b89c91a431d8075cbde422a51 */ - cl_git_mkfile("stash/where", "????\n"); /* e08f7fbb9a42a0c5367cf8b349f1f08c3d56bd72 */ - - cl_git_pass(git_index_add_bypath(index, "what")); - cl_git_pass(git_index_add_bypath(index, "how")); - cl_git_pass(git_index_add_bypath(index, "why")); - cl_git_pass(git_index_add_bypath(index, "where")); - cl_git_pass(git_index_write(index)); - - cl_git_rewritefile("stash/what", "see you later\n"); /* bc99dc98b3eba0e9157e94769cd4d49cb49de449 */ - cl_git_mkfile("stash/where", "....\n"); /* e3d6434ec12eb76af8dfa843a64ba6ab91014a0b */ - - git_index_free(index); -} - -void assert_status( - git_repository *repo, - const char *path, - int status_flags) -{ - unsigned int status; - - if (status_flags < 0) - cl_assert_equal_i(status_flags, git_status_file(&status, repo, path)); - else { - cl_git_pass(git_status_file(&status, repo, path)); - cl_assert_equal_i((unsigned int)status_flags, status); - } -} diff --git a/tests/stash/stash_helpers.h b/tests/stash/stash_helpers.h deleted file mode 100644 index 66d758fe2..000000000 --- a/tests/stash/stash_helpers.h +++ /dev/null @@ -1,8 +0,0 @@ -void setup_stash( - git_repository *repo, - git_signature *signature); - -void assert_status( - git_repository *repo, - const char *path, - int status_flags); diff --git a/tests/stash/submodules.c b/tests/stash/submodules.c deleted file mode 100644 index 8cadca0f2..000000000 --- a/tests/stash/submodules.c +++ /dev/null @@ -1,83 +0,0 @@ -#include "clar_libgit2.h" -#include "stash_helpers.h" -#include "../submodule/submodule_helpers.h" - -static git_repository *repo; -static git_signature *signature; -static git_oid stash_tip_oid; - -static git_submodule *sm; - -void test_stash_submodules__initialize(void) -{ - cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ - - repo = setup_fixture_submodules(); - - cl_git_pass(git_submodule_lookup(&sm, repo, "testrepo")); -} - -void test_stash_submodules__cleanup(void) -{ - git_submodule_free(sm); - sm = NULL; - - git_signature_free(signature); - signature = NULL; -} - -void test_stash_submodules__does_not_stash_modified_submodules(void) -{ - static git_index *smindex; - static git_repository *smrepo; - - assert_status(repo, "modified", GIT_STATUS_WT_MODIFIED); - - /* modify file in submodule */ - cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); - assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); - - /* add file to index in submodule */ - cl_git_pass(git_submodule_open(&smrepo, sm)); - cl_git_pass(git_repository_index(&smindex, smrepo)); - cl_git_pass(git_index_add_bypath(smindex, "README")); - - /* commit changed index of submodule */ - cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Modify it"); - assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); - - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); - - assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); - assert_status(repo, "modified", GIT_STATUS_CURRENT); - - git_index_free(smindex); - git_repository_free(smrepo); -} - -void test_stash_submodules__stash_is_empty_with_modified_submodules(void) -{ - static git_index *smindex; - static git_repository *smrepo; - - cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); - assert_status(repo, "modified", GIT_STATUS_CURRENT); - - /* modify file in submodule */ - cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); - assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); - - /* add file to index in submodule */ - cl_git_pass(git_submodule_open(&smrepo, sm)); - cl_git_pass(git_repository_index(&smindex, smrepo)); - cl_git_pass(git_index_add_bypath(smindex, "README")); - - /* commit changed index of submodule */ - cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Modify it"); - assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED); - - cl_git_fail_with(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT), GIT_ENOTFOUND); - - git_index_free(smindex); - git_repository_free(smrepo); -} diff --git a/tests/status/renames.c b/tests/status/renames.c deleted file mode 100644 index d5cf87d07..000000000 --- a/tests/status/renames.c +++ /dev/null @@ -1,844 +0,0 @@ -#include "clar_libgit2.h" -#include "path.h" -#include "posix.h" -#include "status_helpers.h" -#include "util.h" -#include "status.h" - -static git_repository *g_repo = NULL; - -void test_status_renames__initialize(void) -{ - g_repo = cl_git_sandbox_init("renames"); - - cl_repo_set_bool(g_repo, "core.autocrlf", false); -} - -void test_status_renames__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void _rename_helper( - git_repository *repo, const char *from, const char *to, const char *extra) -{ - git_str oldpath = GIT_STR_INIT, newpath = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath( - &oldpath, git_repository_workdir(repo), from)); - cl_git_pass(git_str_joinpath( - &newpath, git_repository_workdir(repo), to)); - - cl_git_pass(p_rename(oldpath.ptr, newpath.ptr)); - - if (extra) - cl_git_append2file(newpath.ptr, extra); - - git_str_dispose(&oldpath); - git_str_dispose(&newpath); -} - -#define rename_file(R,O,N) _rename_helper((R), (O), (N), NULL) -#define rename_and_edit_file(R,O,N) \ - _rename_helper((R), (O), (N), "Added at the end to keep similarity!") - -struct status_entry { - git_status_t status; - const char *oldname; - const char *newname; -}; - -static void check_status( - git_status_list *status_list, - struct status_entry *expected_list, - size_t expected_len) -{ - const git_status_entry *actual; - const struct status_entry *expected; - const char *oldname, *newname; - size_t i, files_in_status = git_status_list_entrycount(status_list); - - cl_assert_equal_sz(expected_len, files_in_status); - - for (i = 0; i < expected_len; i++) { - actual = git_status_byindex(status_list, i); - expected = &expected_list[i]; - - oldname = actual->head_to_index ? actual->head_to_index->old_file.path : - actual->index_to_workdir ? actual->index_to_workdir->old_file.path : NULL; - - newname = actual->index_to_workdir ? actual->index_to_workdir->new_file.path : - actual->head_to_index ? actual->head_to_index->new_file.path : NULL; - - cl_assert_equal_i_fmt(expected->status, actual->status, "%04x"); - - if (expected->oldname) { - cl_assert(oldname != NULL); - cl_assert_equal_s(oldname, expected->oldname); - } else { - cl_assert(oldname == NULL); - } - - if (actual->status & (GIT_STATUS_INDEX_RENAMED|GIT_STATUS_WT_RENAMED)) { - if (expected->newname) { - cl_assert(newname != NULL); - cl_assert_equal_s(newname, expected->newname); - } else { - cl_assert(newname == NULL); - } - } - } -} - -void test_status_renames__head2index_one(void) -{ - git_index *index; - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_entry expected[] = { - { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "newname.txt" }, - }; - - opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; - - cl_git_pass(git_repository_index(&index, g_repo)); - - rename_file(g_repo, "ikeepsix.txt", "newname.txt"); - - cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_add_bypath(index, "newname.txt")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 1); - git_status_list_free(statuslist); - - git_index_free(index); -} - -void test_status_renames__head2index_two(void) -{ - git_index *index; - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_entry expected[] = { - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED, - "sixserving.txt", "aaa.txt" }, - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED, - "untimely.txt", "bbb.txt" }, - { GIT_STATUS_INDEX_RENAMED, "songof7cities.txt", "ccc.txt" }, - { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "ddd.txt" }, - }; - - opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; - - cl_git_pass(git_repository_index(&index, g_repo)); - - rename_file(g_repo, "ikeepsix.txt", "ddd.txt"); - rename_and_edit_file(g_repo, "sixserving.txt", "aaa.txt"); - rename_file(g_repo, "songof7cities.txt", "ccc.txt"); - rename_and_edit_file(g_repo, "untimely.txt", "bbb.txt"); - - cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_remove_bypath(index, "sixserving.txt")); - cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt")); - cl_git_pass(git_index_remove_bypath(index, "untimely.txt")); - cl_git_pass(git_index_add_bypath(index, "ddd.txt")); - cl_git_pass(git_index_add_bypath(index, "aaa.txt")); - cl_git_pass(git_index_add_bypath(index, "ccc.txt")); - cl_git_pass(git_index_add_bypath(index, "bbb.txt")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 4); - git_status_list_free(statuslist); - - git_index_free(index); -} - -void test_status_renames__head2index_no_rename_from_rewrite(void) -{ - git_index *index; - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_entry expected[] = { - { GIT_STATUS_INDEX_MODIFIED, "ikeepsix.txt", "ikeepsix.txt" }, - { GIT_STATUS_INDEX_MODIFIED, "sixserving.txt", "sixserving.txt" }, - }; - - opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; - - cl_git_pass(git_repository_index(&index, g_repo)); - - rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); - rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); - rename_file(g_repo, "_temp_.txt", "sixserving.txt"); - - cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 2); - git_status_list_free(statuslist); - - git_index_free(index); -} - -void test_status_renames__head2index_rename_from_rewrite(void) -{ - git_index *index; - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_entry expected[] = { - { GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "ikeepsix.txt" }, - { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "sixserving.txt" }, - }; - - opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; - opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; - - cl_git_pass(git_repository_index(&index, g_repo)); - - rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); - rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); - rename_file(g_repo, "_temp_.txt", "sixserving.txt"); - - cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 2); - git_status_list_free(statuslist); - - git_index_free(index); -} - -void test_status_renames__index2workdir_one(void) -{ - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_entry expected[] = { - { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "newname.txt" }, - }; - - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - - rename_file(g_repo, "ikeepsix.txt", "newname.txt"); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 1); - git_status_list_free(statuslist); -} - -void test_status_renames__index2workdir_two(void) -{ - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_entry expected[] = { - { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, - "sixserving.txt", "aaa.txt" }, - { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, - "untimely.txt", "bbb.txt" }, - { GIT_STATUS_WT_RENAMED, "songof7cities.txt", "ccc.txt" }, - { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "ddd.txt" }, - }; - - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - - rename_file(g_repo, "ikeepsix.txt", "ddd.txt"); - rename_and_edit_file(g_repo, "sixserving.txt", "aaa.txt"); - rename_file(g_repo, "songof7cities.txt", "ccc.txt"); - rename_and_edit_file(g_repo, "untimely.txt", "bbb.txt"); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 4); - git_status_list_free(statuslist); -} - -void test_status_renames__index2workdir_rename_from_rewrite(void) -{ - git_index *index; - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_entry expected[] = { - { GIT_STATUS_WT_RENAMED, "sixserving.txt", "ikeepsix.txt" }, - { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "sixserving.txt" }, - }; - - opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; - - cl_git_pass(git_repository_index(&index, g_repo)); - - rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); - rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); - rename_file(g_repo, "_temp_.txt", "sixserving.txt"); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 2); - git_status_list_free(statuslist); - - git_index_free(index); -} - -void test_status_renames__both_one(void) -{ - git_index *index; - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_entry expected[] = { - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, - "ikeepsix.txt", "newname-workdir.txt" }, - }; - - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; - opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - - cl_git_pass(git_repository_index(&index, g_repo)); - - rename_file(g_repo, "ikeepsix.txt", "newname-index.txt"); - - cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_add_bypath(index, "newname-index.txt")); - cl_git_pass(git_index_write(index)); - - rename_file(g_repo, "newname-index.txt", "newname-workdir.txt"); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 1); - git_status_list_free(statuslist); - - git_index_free(index); -} - -void test_status_renames__both_two(void) -{ - git_index *index; - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_entry expected[] = { - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | - GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, - "ikeepsix.txt", "ikeepsix-both.txt" }, - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED, - "sixserving.txt", "sixserving-index.txt" }, - { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, - "songof7cities.txt", "songof7cities-workdir.txt" }, - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, - "untimely.txt", "untimely-both.txt" }, - }; - - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; - opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - - cl_git_pass(git_repository_index(&index, g_repo)); - - rename_and_edit_file(g_repo, "ikeepsix.txt", "ikeepsix-index.txt"); - rename_and_edit_file(g_repo, "sixserving.txt", "sixserving-index.txt"); - rename_file(g_repo, "untimely.txt", "untimely-index.txt"); - - cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_remove_bypath(index, "sixserving.txt")); - cl_git_pass(git_index_remove_bypath(index, "untimely.txt")); - cl_git_pass(git_index_add_bypath(index, "ikeepsix-index.txt")); - cl_git_pass(git_index_add_bypath(index, "sixserving-index.txt")); - cl_git_pass(git_index_add_bypath(index, "untimely-index.txt")); - cl_git_pass(git_index_write(index)); - - rename_and_edit_file(g_repo, "ikeepsix-index.txt", "ikeepsix-both.txt"); - rename_and_edit_file(g_repo, "songof7cities.txt", "songof7cities-workdir.txt"); - rename_file(g_repo, "untimely-index.txt", "untimely-both.txt"); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 4); - git_status_list_free(statuslist); - - git_index_free(index); -} - - -void test_status_renames__both_rename_from_rewrite(void) -{ - git_index *index; - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_entry expected[] = { - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, - "songof7cities.txt", "ikeepsix.txt" }, - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, - "ikeepsix.txt", "sixserving.txt" }, - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, - "sixserving.txt", "songof7cities.txt" }, - }; - - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; - opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; - - cl_git_pass(git_repository_index(&index, g_repo)); - - rename_file(g_repo, "ikeepsix.txt", "_temp_.txt"); - rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); - rename_file(g_repo, "songof7cities.txt", "sixserving.txt"); - rename_file(g_repo, "_temp_.txt", "songof7cities.txt"); - - cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); - cl_git_pass(git_index_add_bypath(index, "songof7cities.txt")); - cl_git_pass(git_index_write(index)); - - rename_file(g_repo, "songof7cities.txt", "_temp_.txt"); - rename_file(g_repo, "ikeepsix.txt", "songof7cities.txt"); - rename_file(g_repo, "sixserving.txt", "ikeepsix.txt"); - rename_file(g_repo, "_temp_.txt", "sixserving.txt"); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 3); - git_status_list_free(statuslist); - - git_index_free(index); -} - -void test_status_renames__rewrites_only_for_renames(void) -{ - git_index *index; - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_entry expected[] = { - { GIT_STATUS_WT_MODIFIED, "ikeepsix.txt", "ikeepsix.txt" }, - }; - - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; - opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES; - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_rewritefile("renames/ikeepsix.txt", - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n" \ - "This is enough content for the file to be rewritten.\n"); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 1); - git_status_list_free(statuslist); - - git_index_free(index); -} - -void test_status_renames__both_casechange_one(void) -{ - git_index *index; - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - int index_caps; - struct status_entry expected_icase[] = { - { GIT_STATUS_INDEX_RENAMED, - "ikeepsix.txt", "IKeepSix.txt" }, - }; - struct status_entry expected_case[] = { - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, - "ikeepsix.txt", "IKEEPSIX.txt" }, - }; - - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; - opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - - cl_git_pass(git_repository_index(&index, g_repo)); - index_caps = git_index_caps(index); - - rename_file(g_repo, "ikeepsix.txt", "IKeepSix.txt"); - - cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_add_bypath(index, "IKeepSix.txt")); - cl_git_pass(git_index_write(index)); - - /* on a case-insensitive file system, this change won't matter. - * on a case-sensitive one, it will. - */ - rename_file(g_repo, "IKeepSix.txt", "IKEEPSIX.txt"); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - - check_status(statuslist, (index_caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) ? - expected_icase : expected_case, 1); - - git_status_list_free(statuslist); - - git_index_free(index); -} - -void test_status_renames__both_casechange_two(void) -{ - git_index *index; - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - int index_caps; - struct status_entry expected_icase[] = { - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | - GIT_STATUS_WT_MODIFIED, - "ikeepsix.txt", "IKeepSix.txt" }, - { GIT_STATUS_INDEX_MODIFIED, - "sixserving.txt", "sixserving.txt" }, - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_MODIFIED, - "songof7cities.txt", "songof7.txt" }, - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, - "untimely.txt", "untimeliest.txt" } - }; - struct status_entry expected_case[] = { - { GIT_STATUS_INDEX_RENAMED | - GIT_STATUS_WT_MODIFIED | GIT_STATUS_WT_RENAMED, - "songof7cities.txt", "SONGOF7.txt" }, - { GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_RENAMED, - "sixserving.txt", "SixServing.txt" }, - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | - GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, - "ikeepsix.txt", "ikeepsix.txt" }, - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, - "untimely.txt", "untimeliest.txt" } - }; - - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; - opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - - cl_git_pass(git_repository_index(&index, g_repo)); - index_caps = git_index_caps(index); - - rename_and_edit_file(g_repo, "ikeepsix.txt", "IKeepSix.txt"); - rename_and_edit_file(g_repo, "sixserving.txt", "sixserving.txt"); - rename_file(g_repo, "songof7cities.txt", "songof7.txt"); - rename_file(g_repo, "untimely.txt", "untimelier.txt"); - - cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_remove_bypath(index, "sixserving.txt")); - cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt")); - cl_git_pass(git_index_remove_bypath(index, "untimely.txt")); - cl_git_pass(git_index_add_bypath(index, "IKeepSix.txt")); - cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); - cl_git_pass(git_index_add_bypath(index, "songof7.txt")); - cl_git_pass(git_index_add_bypath(index, "untimelier.txt")); - cl_git_pass(git_index_write(index)); - - rename_and_edit_file(g_repo, "IKeepSix.txt", "ikeepsix.txt"); - rename_file(g_repo, "sixserving.txt", "SixServing.txt"); - rename_and_edit_file(g_repo, "songof7.txt", "SONGOF7.txt"); - rename_file(g_repo, "untimelier.txt", "untimeliest.txt"); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - - check_status(statuslist, (index_caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) ? - expected_icase : expected_case, 4); - - git_status_list_free(statuslist); - - git_index_free(index); -} - -void test_status_renames__zero_byte_file_does_not_fail(void) -{ - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - - struct status_entry expected[] = { - { GIT_STATUS_WT_DELETED, "ikeepsix.txt", "ikeepsix.txt" }, - { GIT_STATUS_WT_NEW, "zerobyte.txt", "zerobyte.txt" }, - }; - - opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES | - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR | - GIT_STATUS_OPT_INCLUDE_IGNORED | - GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | - GIT_STATUS_SHOW_INDEX_AND_WORKDIR | - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; - - p_unlink("renames/ikeepsix.txt"); - cl_git_mkfile("renames/zerobyte.txt", ""); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 2); - git_status_list_free(statuslist); -} - -#ifdef GIT_USE_ICONV -static char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; -static char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; -#endif - -/* - * Create a file in NFD (canonically decomposed) format. Ensure - * that when core.precomposeunicode is false that we return paths - * in NFD, but when core.precomposeunicode is true, then we - * return paths precomposed (in NFC). - */ -void test_status_renames__precomposed_unicode_rename(void) -{ -#ifdef GIT_USE_ICONV - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_entry expected0[] = { - { GIT_STATUS_WT_NEW, nfd, NULL }, - { GIT_STATUS_WT_DELETED, "sixserving.txt", NULL }, - }; - struct status_entry expected1[] = { - { GIT_STATUS_WT_RENAMED, "sixserving.txt", nfd }, - }; - struct status_entry expected2[] = { - { GIT_STATUS_WT_DELETED, "sixserving.txt", NULL }, - { GIT_STATUS_WT_NEW, nfc, NULL }, - }; - struct status_entry expected3[] = { - { GIT_STATUS_WT_RENAMED, "sixserving.txt", nfc }, - }; - - rename_file(g_repo, "sixserving.txt", nfd); - - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - - cl_repo_set_bool(g_repo, "core.precomposeunicode", false); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected0, ARRAY_SIZE(expected0)); - git_status_list_free(statuslist); - - opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected1, ARRAY_SIZE(expected1)); - git_status_list_free(statuslist); - - cl_repo_set_bool(g_repo, "core.precomposeunicode", true); - - opts.flags &= ~GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected2, ARRAY_SIZE(expected2)); - git_status_list_free(statuslist); - - opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected3, ARRAY_SIZE(expected3)); - git_status_list_free(statuslist); -#endif -} - -void test_status_renames__precomposed_unicode_toggle_is_rename(void) -{ -#ifdef GIT_USE_ICONV - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - struct status_entry expected0[] = { - { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", nfd }, - }; - struct status_entry expected1[] = { - { GIT_STATUS_WT_RENAMED, nfd, nfc }, - }; - struct status_entry expected2[] = { - { GIT_STATUS_INDEX_RENAMED, nfd, nfc }, - }; - struct status_entry expected3[] = { - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, nfd, nfd }, - }; - - cl_repo_set_bool(g_repo, "core.precomposeunicode", false); - rename_file(g_repo, "ikeepsix.txt", nfd); - - { - git_index *index; - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_add_bypath(index, nfd)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - } - - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected0, ARRAY_SIZE(expected0)); - git_status_list_free(statuslist); - - cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "commit nfd"); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - cl_assert_equal_sz(0, git_status_list_entrycount(statuslist)); - git_status_list_free(statuslist); - - cl_repo_set_bool(g_repo, "core.precomposeunicode", true); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected1, ARRAY_SIZE(expected1)); - git_status_list_free(statuslist); - - { - git_index *index; - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_remove_bypath(index, nfd)); - cl_git_pass(git_index_add_bypath(index, nfc)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - } - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected2, ARRAY_SIZE(expected2)); - git_status_list_free(statuslist); - - cl_repo_set_bool(g_repo, "core.precomposeunicode", false); - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected3, ARRAY_SIZE(expected3)); - git_status_list_free(statuslist); -#endif -} - -void test_status_renames__rename_threshold(void) -{ - git_index *index; - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - - _rename_helper(g_repo, "ikeepsix.txt", "newname.txt", - "Line 1\n" \ - "Line 2\n" \ - "Line 3\n" \ - "Line 4\n" \ - "Line 5\n" \ - "Line 6\n" \ - "Line 7\n" \ - "Line 8\n" \ - "Line 9\n" - ); - - opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - - cl_git_pass(git_repository_index(&index, g_repo)); - - /* Default threshold */ - { - struct status_entry expected[] = { - { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, "ikeepsix.txt", "newname.txt" }, - }; - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 1); - git_status_list_free(statuslist); - } - - /* Threshold set to 90 */ - { - struct status_entry expected[] = { - { GIT_STATUS_WT_DELETED, "ikeepsix.txt", NULL }, - { GIT_STATUS_WT_NEW, "newname.txt", NULL } - }; - - opts.rename_threshold = 90; - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 2); - git_status_list_free(statuslist); - } - - /* Threshold set to 25 */ - { - struct status_entry expected[] = { - { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, "ikeepsix.txt", "newname.txt" }, - }; - - opts.rename_threshold = 25; - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 1); - git_status_list_free(statuslist); - } - - git_index_free(index); -} - -void test_status_renames__case_insensitive_h2i_and_i2wc(void) -{ - git_status_list *statuslist; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - git_reference *head, *test_branch; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - git_str path_to_delete = GIT_STR_INIT; - git_str path_to_edit = GIT_STR_INIT; - git_index *index; - git_strarray paths = { NULL, 0 }; - - struct status_entry expected[] = { - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_MODIFIED, "sixserving.txt", "sixserving-renamed.txt" }, - { GIT_STATUS_INDEX_DELETED, "Wow.txt", "Wow.txt" } - }; - - - /* Checkout the correct branch */ - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); - cl_git_pass(git_reference_symbolic_set_target( - &test_branch, head, "refs/heads/case-insensitive-status", NULL)); - cl_git_pass(git_checkout_head(g_repo, &checkout_opts)); - - cl_git_pass(git_repository_index(&index, g_repo)); - - - /* Rename sixserving.txt, delete Wow.txt, and stage those changes */ - rename_file(g_repo, "sixserving.txt", "sixserving-renamed.txt"); - cl_git_pass(git_str_joinpath( - &path_to_delete, git_repository_workdir(g_repo), "Wow.txt")); - cl_git_rmfile(path_to_delete.ptr); - - cl_git_pass(git_index_add_all(index, &paths, GIT_INDEX_ADD_FORCE, NULL, NULL)); - cl_git_pass(git_index_write(index)); - - - /* Change content of sixserving-renamed.txt */ - cl_git_pass(git_str_joinpath( - &path_to_edit, git_repository_workdir(g_repo), "sixserving-renamed.txt")); - cl_git_append2file(path_to_edit.ptr, "New content\n"); - - /* Run status */ - opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; - opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; - opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; - opts.flags |= GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY; - - cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); - check_status(statuslist, expected, 2); - git_status_list_free(statuslist); - - git_index_free(index); - - git_str_dispose(&path_to_delete); - git_str_dispose(&path_to_edit); - - git_reference_free(head); - git_reference_free(test_branch); -} diff --git a/tests/status/single.c b/tests/status/single.c deleted file mode 100644 index e7f92097c..000000000 --- a/tests/status/single.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" - -static void -cleanup__remove_file(void *_file) -{ - cl_must_pass(p_unlink((char *)_file)); -} - -/* test retrieving OID from a file apart from the ODB */ -void test_status_single__hash_single_file(void) -{ - static const char file_name[] = "new_file"; - static const char file_contents[] = "new_file\n"; - static const char file_hash[] = "d4fa8600b4f37d7516bef4816ae2c64dbf029e3a"; - - git_oid expected_id, actual_id; - - /* initialization */ - git_oid_fromstr(&expected_id, file_hash); - cl_git_mkfile(file_name, file_contents); - cl_set_cleanup(&cleanup__remove_file, (void *)file_name); - - cl_git_pass(git_odb_hashfile(&actual_id, file_name, GIT_OBJECT_BLOB)); - cl_assert_equal_oid(&expected_id, &actual_id); -} - -/* test retrieving OID from an empty file apart from the ODB */ -void test_status_single__hash_single_empty_file(void) -{ - static const char file_name[] = "new_empty_file"; - static const char file_contents[] = ""; - static const char file_hash[] = "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"; - - git_oid expected_id, actual_id; - - /* initialization */ - git_oid_fromstr(&expected_id, file_hash); - cl_git_mkfile(file_name, file_contents); - cl_set_cleanup(&cleanup__remove_file, (void *)file_name); - - cl_git_pass(git_odb_hashfile(&actual_id, file_name, GIT_OBJECT_BLOB)); - cl_assert_equal_oid(&expected_id, &actual_id); -} - diff --git a/tests/status/status_data.h b/tests/status/status_data.h deleted file mode 100644 index 09b9827f2..000000000 --- a/tests/status/status_data.h +++ /dev/null @@ -1,326 +0,0 @@ -#include "status_helpers.h" - -/* A utf-8 string with 83 characters, but 249 bytes. */ -static const char *longname = "\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97"; - - -/* entries for a plain copy of tests/resources/status */ - -static const char *entry_paths0[] = { - "file_deleted", - "ignored_file", - "modified_file", - "new_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "staged_new_file", - "staged_new_file_deleted_file", - "staged_new_file_modified_file", - - "subdir/deleted_file", - "subdir/modified_file", - "subdir/new_file", - - "\xe8\xbf\x99", -}; - -static const unsigned int entry_statuses0[] = { - GIT_STATUS_WT_DELETED, - GIT_STATUS_IGNORED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_DELETED, - GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_NEW, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_DELETED, - GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_MODIFIED, - - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - - GIT_STATUS_WT_NEW, -}; - -static const int entry_count0 = 16; - -/* entries for a copy of tests/resources/status with all content - * deleted from the working directory - */ - -static const char *entry_paths2[] = { - "current_file", - "file_deleted", - "modified_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "staged_new_file", - "staged_new_file_deleted_file", - "staged_new_file_modified_file", - "subdir.txt", - "subdir/current_file", - "subdir/deleted_file", - "subdir/modified_file", -}; - -static const unsigned int entry_statuses2[] = { - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_DELETED, -}; - -static const int entry_count2 = 15; - -/* entries for a copy of tests/resources/status with some mods */ - -static const char *entry_paths3_icase[] = { - ".HEADER", - "42-is-not-prime.sigh", - "current_file", - "current_file/", - "file_deleted", - "ignored_file", - "modified_file", - "new_file", - "README.md", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "staged_new_file", - "staged_new_file_deleted_file", - "staged_new_file_modified_file", - "subdir", - "subdir/current_file", - "subdir/deleted_file", - "subdir/modified_file", - "\xe8\xbf\x99", -}; - -static const unsigned int entry_statuses3_icase[] = { - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_IGNORED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_NEW, - GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_NEW, -}; - -static const char *entry_paths3[] = { - ".HEADER", - "42-is-not-prime.sigh", - "README.md", - "current_file", - "current_file/", - "file_deleted", - "ignored_file", - "modified_file", - "new_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "staged_new_file", - "staged_new_file_deleted_file", - "staged_new_file_modified_file", - "subdir", - "subdir/current_file", - "subdir/deleted_file", - "subdir/modified_file", - "\xe8\xbf\x99", -}; - -static const unsigned int entry_statuses3[] = { - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_IGNORED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_NEW, -}; - -static const int entry_count3 = 22; - - -/* entries for a copy of tests/resources/status with some mods - * and different options to the status call - */ - -static const char *entry_paths4[] = { - ".new_file", - "current_file", - "current_file/current_file", - "current_file/modified_file", - "current_file/new_file", - "file_deleted", - "modified_file", - "new_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "staged_new_file", - "staged_new_file_deleted_file", - "staged_new_file_modified_file", - "subdir", - "subdir/current_file", - "subdir/deleted_file", - "subdir/modified_file", - "zzz_new_dir/new_file", - "zzz_new_file", - "\xe8\xbf\x99", -}; - -static const unsigned int entry_statuses4[] = { - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_NEW, -}; - -static const int entry_count4 = 23; - - -/* entries for a copy of tests/resources/status with options - * passed to the status call in order to only get the differences - * between the HEAD and the index (changes to be committed) - */ - -static const char *entry_paths5[] = { - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "staged_new_file", - "staged_new_file_deleted_file", - "staged_new_file_modified_file", -}; - -static const unsigned int entry_statuses5[] = { - GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_INDEX_NEW, -}; - -static const int entry_count5 = 8; - - -/* entries for a copy of tests/resources/status with options - * passed to the status call in order to only get the differences - * between the workdir and the index (changes not staged, untracked files) - */ - -static const char *entry_paths6[] = { - "file_deleted", - "ignored_file", - "modified_file", - "new_file", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_modified_file", - "staged_new_file_deleted_file", - "staged_new_file_modified_file", - "subdir/deleted_file", - "subdir/modified_file", - "subdir/new_file", - "\xe8\xbf\x99", -}; - -static const unsigned int entry_statuses6[] = { - GIT_STATUS_WT_DELETED, - GIT_STATUS_IGNORED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_NEW, -}; - -static const int entry_count6 = 13; diff --git a/tests/status/status_helpers.c b/tests/status/status_helpers.c deleted file mode 100644 index 5d13caa9a..000000000 --- a/tests/status/status_helpers.c +++ /dev/null @@ -1,97 +0,0 @@ -#include "clar_libgit2.h" -#include "status_helpers.h" - -int cb_status__normal( - const char *path, unsigned int status_flags, void *payload) -{ - status_entry_counts *counts = payload; - - if (counts->debug) - cb_status__print(path, status_flags, NULL); - - if (counts->entry_count >= counts->expected_entry_count) - counts->wrong_status_flags_count++; - else if (strcmp(path, counts->expected_paths[counts->entry_count])) - counts->wrong_sorted_path++; - else if (status_flags != counts->expected_statuses[counts->entry_count]) - counts->wrong_status_flags_count++; - - counts->entry_count++; - return 0; -} - -int cb_status__count(const char *p, unsigned int s, void *payload) -{ - volatile int *count = (int *)payload; - - GIT_UNUSED(p); - GIT_UNUSED(s); - - (*count)++; - - return 0; -} - -int cb_status__single(const char *p, unsigned int s, void *payload) -{ - status_entry_single *data = (status_entry_single *)payload; - - if (data->debug) - fprintf(stderr, "%02d: %s (%04x)\n", data->count, p, s); - - data->count++; - data->status = s; - - return 0; -} - -int cb_status__print( - const char *path, unsigned int status_flags, void *payload) -{ - char istatus = ' ', wstatus = ' '; - int icount = 0, wcount = 0; - - if (status_flags & GIT_STATUS_INDEX_NEW) { - istatus = 'A'; icount++; - } - if (status_flags & GIT_STATUS_INDEX_MODIFIED) { - istatus = 'M'; icount++; - } - if (status_flags & GIT_STATUS_INDEX_DELETED) { - istatus = 'D'; icount++; - } - if (status_flags & GIT_STATUS_INDEX_RENAMED) { - istatus = 'R'; icount++; - } - if (status_flags & GIT_STATUS_INDEX_TYPECHANGE) { - istatus = 'T'; icount++; - } - - if (status_flags & GIT_STATUS_WT_NEW) { - wstatus = 'A'; wcount++; - } - if (status_flags & GIT_STATUS_WT_MODIFIED) { - wstatus = 'M'; wcount++; - } - if (status_flags & GIT_STATUS_WT_DELETED) { - wstatus = 'D'; wcount++; - } - if (status_flags & GIT_STATUS_WT_TYPECHANGE) { - wstatus = 'T'; wcount++; - } - if (status_flags & GIT_STATUS_IGNORED) { - wstatus = 'I'; wcount++; - } - if (status_flags & GIT_STATUS_WT_UNREADABLE) { - wstatus = 'X'; wcount++; - } - - fprintf(stderr, "%c%c %s (%d/%d%s)\n", - istatus, wstatus, path, icount, wcount, - (icount > 1 || wcount > 1) ? " INVALID COMBO" : ""); - - if (payload) - *((int *)payload) += 1; - - return 0; -} diff --git a/tests/status/status_helpers.h b/tests/status/status_helpers.h deleted file mode 100644 index 464266af3..000000000 --- a/tests/status/status_helpers.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef INCLUDE_cl_status_helpers_h__ -#define INCLUDE_cl_status_helpers_h__ - -typedef struct { - int wrong_status_flags_count; - int wrong_sorted_path; - int entry_count; - const unsigned int* expected_statuses; - const char** expected_paths; - int expected_entry_count; - const char *file; - const char *func; - int line; - bool debug; -} status_entry_counts; - -#define status_counts_init(counts, paths, statuses) do { \ - memset(&(counts), 0, sizeof(counts)); \ - (counts).expected_statuses = (statuses); \ - (counts).expected_paths = (paths); \ - (counts).file = __FILE__; \ - (counts).func = __func__; \ - (counts).line = __LINE__; \ - } while (0) - -/* cb_status__normal takes payload of "status_entry_counts *" */ - -extern int cb_status__normal( - const char *path, unsigned int status_flags, void *payload); - - -/* cb_status__count takes payload of "int *" */ - -extern int cb_status__count(const char *p, unsigned int s, void *payload); - - -typedef struct { - int count; - unsigned int status; - bool debug; -} status_entry_single; - -/* cb_status__single takes payload of "status_entry_single *" */ - -extern int cb_status__single(const char *p, unsigned int s, void *payload); - -/* cb_status__print takes optional payload of "int *" */ - -extern int cb_status__print(const char *p, unsigned int s, void *payload); - -#endif diff --git a/tests/status/submodules.c b/tests/status/submodules.c deleted file mode 100644 index d223657b4..000000000 --- a/tests/status/submodules.c +++ /dev/null @@ -1,563 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "status_helpers.h" -#include "../submodule/submodule_helpers.h" - -static git_repository *g_repo = NULL; - -void test_status_submodules__initialize(void) -{ -} - -void test_status_submodules__cleanup(void) -{ -} - -void test_status_submodules__api(void) -{ - git_submodule *sm; - - g_repo = setup_fixture_submodules(); - - cl_assert(git_submodule_lookup(NULL, g_repo, "nonexistent") == GIT_ENOTFOUND); - - cl_assert(git_submodule_lookup(NULL, g_repo, "modified") == GIT_ENOTFOUND); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - cl_assert(sm != NULL); - cl_assert_equal_s("testrepo", git_submodule_name(sm)); - cl_assert_equal_s("testrepo", git_submodule_path(sm)); - git_submodule_free(sm); -} - -void test_status_submodules__0(void) -{ - int counts = 0; - - g_repo = setup_fixture_submodules(); - - cl_assert(git_fs_path_isdir("submodules/.git")); - cl_assert(git_fs_path_isdir("submodules/testrepo/.git")); - cl_assert(git_fs_path_isfile("submodules/.gitmodules")); - - cl_git_pass( - git_status_foreach(g_repo, cb_status__count, &counts) - ); - - cl_assert_equal_i(6, counts); -} - -static const char *expected_files[] = { - ".gitmodules", - "added", - "deleted", - "ignored", - "modified", - "untracked" -}; - -static unsigned int expected_status[] = { - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_IGNORED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW -}; - -static int cb_status__match(const char *p, unsigned int s, void *payload) -{ - status_entry_counts *counts = payload; - int idx = counts->entry_count++; - - clar__assert_equal( - counts->file, counts->func, counts->line, - "Status path mismatch", 1, - "%s", counts->expected_paths[idx], p); - - clar__assert_equal( - counts->file, counts->func, counts->line, - "Status code mismatch", 1, - "%o", counts->expected_statuses[idx], s); - - return 0; -} - -void test_status_submodules__1(void) -{ - status_entry_counts counts; - - g_repo = setup_fixture_submodules(); - - cl_assert(git_fs_path_isdir("submodules/.git")); - cl_assert(git_fs_path_isdir("submodules/testrepo/.git")); - cl_assert(git_fs_path_isfile("submodules/.gitmodules")); - - status_counts_init(counts, expected_files, expected_status); - - cl_git_pass( git_status_foreach(g_repo, cb_status__match, &counts) ); - - cl_assert_equal_i(6, counts.entry_count); -} - -void test_status_submodules__single_file(void) -{ - unsigned int status = 0; - g_repo = setup_fixture_submodules(); - cl_git_pass( git_status_file(&status, g_repo, "testrepo") ); - cl_assert(!status); -} - -void test_status_submodules__moved_head(void) -{ - git_submodule *sm; - git_repository *smrepo; - git_oid oid; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts; - static const char *expected_files_with_sub[] = { - ".gitmodules", - "added", - "deleted", - "ignored", - "modified", - "testrepo", - "untracked" - }; - static unsigned int expected_status_with_sub[] = { - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_IGNORED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW - }; - - g_repo = setup_fixture_submodules(); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - cl_git_pass(git_submodule_open(&smrepo, sm)); - git_submodule_free(sm); - - /* move submodule HEAD to c47800c7266a2be04c571c04d5a6614691ea99bd */ - cl_git_pass( - git_oid_fromstr(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd")); - cl_git_pass(git_repository_set_head_detached(smrepo, &oid)); - - /* first do a normal status, which should now include the submodule */ - - opts.flags = GIT_STATUS_OPT_DEFAULTS; - - status_counts_init( - counts, expected_files_with_sub, expected_status_with_sub); - cl_git_pass( - git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(7, counts.entry_count); - - /* try again with EXCLUDE_SUBMODULES which should skip it */ - - opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_EXCLUDE_SUBMODULES; - - status_counts_init(counts, expected_files, expected_status); - cl_git_pass( - git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(6, counts.entry_count); - - git_repository_free(smrepo); -} - -void test_status_submodules__dirty_workdir_only(void) -{ - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts; - static const char *expected_files_with_sub[] = { - ".gitmodules", - "added", - "deleted", - "ignored", - "modified", - "testrepo", - "untracked" - }; - static unsigned int expected_status_with_sub[] = { - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_IGNORED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW - }; - - g_repo = setup_fixture_submodules(); - - cl_git_rewritefile("submodules/testrepo/README", "heyheyhey"); - cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before"); - - /* first do a normal status, which should now include the submodule */ - - opts.flags = GIT_STATUS_OPT_DEFAULTS; - - status_counts_init( - counts, expected_files_with_sub, expected_status_with_sub); - cl_git_pass( - git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(7, counts.entry_count); - - /* try again with EXCLUDE_SUBMODULES which should skip it */ - - opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_EXCLUDE_SUBMODULES; - - status_counts_init(counts, expected_files, expected_status); - cl_git_pass( - git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(6, counts.entry_count); -} - -void test_status_submodules__uninitialized(void) -{ - git_repository *cloned_repo; - git_status_list *statuslist; - - g_repo = cl_git_sandbox_init("submod2"); - - cl_git_pass(git_clone(&cloned_repo, "submod2", "submod2-clone", NULL)); - - cl_git_pass(git_status_list_new(&statuslist, cloned_repo, NULL)); - cl_assert_equal_i(0, git_status_list_entrycount(statuslist)); - - git_status_list_free(statuslist); - git_repository_free(cloned_repo); - cl_git_sandbox_cleanup(); -} - -void test_status_submodules__contained_untracked_repo(void) -{ - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts; - git_repository *contained; - static const char *expected_files_not_ignored[] = { - ".gitmodules", - "added", - "deleted", - "modified", - "untracked" - }; - static unsigned int expected_status_not_ignored[] = { - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - }; - static const char *expected_files_with_untracked[] = { - ".gitmodules", - "added", - "deleted", - "dir/file.md", - "modified", - "untracked" - }; - static const char *expected_files_with_untracked_dir[] = { - ".gitmodules", - "added", - "deleted", - "dir/", - "modified", - "untracked" - }; - static unsigned int expected_status_with_untracked[] = { - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_WT_NEW, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW - }; - - g_repo = setup_fixture_submodules(); - - /* skip empty directory */ - - cl_must_pass(p_mkdir("submodules/dir", 0777)); - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED; - - status_counts_init( - counts, expected_files_not_ignored, expected_status_not_ignored); - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(5, counts.entry_count); - - /* still skipping because empty == ignored */ - - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - - status_counts_init( - counts, expected_files_not_ignored, expected_status_not_ignored); - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(5, counts.entry_count); - - /* find non-ignored contents of directory */ - - cl_git_mkfile("submodules/dir/file.md", "hello"); - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - - status_counts_init( - counts, expected_files_with_untracked, expected_status_with_untracked); - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(6, counts.entry_count); - - /* but skip if all content is ignored */ - - cl_git_append2file("submodules/.git/info/exclude", "\n*.md\n\n"); - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - - status_counts_init( - counts, expected_files_not_ignored, expected_status_not_ignored); - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(5, counts.entry_count); - - /* same is true if it contains a git link */ - - cl_git_mkfile("submodules/dir/.git", "gitlink: ../.git"); - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - - status_counts_init( - counts, expected_files_not_ignored, expected_status_not_ignored); - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(5, counts.entry_count); - - /* but if it contains tracked files, it should just show up as a - * directory and exclude the files in it - */ - - cl_git_mkfile("submodules/dir/another_file", "hello"); - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - - status_counts_init( - counts, expected_files_with_untracked_dir, - expected_status_with_untracked); - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(6, counts.entry_count); - - /* that applies to a git repo with a .git directory too */ - - cl_must_pass(p_unlink("submodules/dir/.git")); - cl_git_pass(git_repository_init(&contained, "submodules/dir", false)); - git_repository_free(contained); - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - - status_counts_init( - counts, expected_files_with_untracked_dir, - expected_status_with_untracked); - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(6, counts.entry_count); - - /* same result even if we don't recurse into subdirectories */ - - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED; - - status_counts_init( - counts, expected_files_with_untracked_dir, - expected_status_with_untracked); - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(6, counts.entry_count); - - /* and if we remove the untracked file, it goes back to ignored */ - - cl_must_pass(p_unlink("submodules/dir/another_file")); - - status_counts_init( - counts, expected_files_not_ignored, expected_status_not_ignored); - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(5, counts.entry_count); -} - -void test_status_submodules__broken_stuff_that_git_allows(void) -{ - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts; - git_repository *contained; - static const char *expected_files_with_broken[] = { - ".gitmodules", - "added", - "broken/tracked", - "deleted", - "ignored", - "modified", - "untracked" - }; - static unsigned int expected_status_with_broken[] = { - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_INDEX_NEW, - GIT_STATUS_INDEX_DELETED, - GIT_STATUS_IGNORED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - }; - - g_repo = setup_fixture_submodules(); - - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | - GIT_STATUS_OPT_INCLUDE_IGNORED; - - /* make a directory and stick a tracked item into the index */ - { - git_index *idx; - cl_must_pass(p_mkdir("submodules/broken", 0777)); - cl_git_mkfile("submodules/broken/tracked", "tracked content"); - cl_git_pass(git_repository_index(&idx, g_repo)); - cl_git_pass(git_index_add_bypath(idx, "broken/tracked")); - cl_git_pass(git_index_write(idx)); - git_index_free(idx); - } - - status_counts_init( - counts, expected_files_with_broken, expected_status_with_broken); - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(7, counts.entry_count); - - /* directory with tracked items that looks a little bit like a repo */ - - cl_must_pass(p_mkdir("submodules/broken/.git", 0777)); - cl_must_pass(p_mkdir("submodules/broken/.git/info", 0777)); - cl_git_mkfile("submodules/broken/.git/info/exclude", "# bogus"); - - status_counts_init( - counts, expected_files_with_broken, expected_status_with_broken); - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(7, counts.entry_count); - - /* directory with tracked items that is a repo */ - - cl_git_pass(git_futils_rmdir_r( - "submodules/broken/.git", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_git_pass(git_repository_init(&contained, "submodules/broken", false)); - git_repository_free(contained); - - status_counts_init( - counts, expected_files_with_broken, expected_status_with_broken); - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(7, counts.entry_count); - - /* directory with tracked items that claims to be a submodule but is not */ - - cl_git_pass(git_futils_rmdir_r( - "submodules/broken/.git", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_git_append2file("submodules/.gitmodules", - "\n[submodule \"broken\"]\n" - "\tpath = broken\n" - "\turl = https://github.com/not/used\n\n"); - - status_counts_init( - counts, expected_files_with_broken, expected_status_with_broken); - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, cb_status__match, &counts)); - cl_assert_equal_i(7, counts.entry_count); -} - -void test_status_submodules__entry_but_dir_tracked(void) -{ - git_repository *repo; - git_status_list *status; - git_diff *diff; - git_index *index; - git_tree *tree; - - cl_git_pass(git_repository_init(&repo, "mixed-submodule", 0)); - cl_git_mkfile("mixed-submodule/.gitmodules", "[submodule \"sub\"]\n path = sub\n url = ../foo\n"); - cl_git_pass(p_mkdir("mixed-submodule/sub", 0777)); - cl_git_mkfile("mixed-submodule/sub/file", ""); - - /* Create the commit with sub/file as a file, and an entry for sub in the modules list */ - { - git_oid tree_id, commit_id; - git_signature *sig; - git_reference *ref; - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, ".gitmodules")); - cl_git_pass(git_index_add_bypath(index, "sub/file")); - cl_git_pass(git_index_write(index)); - cl_git_pass(git_index_write_tree(&tree_id, index)); - cl_git_pass(git_signature_now(&sig, "Sloppy Submoduler", "sloppy@example.com")); - cl_git_pass(git_tree_lookup(&tree, repo, &tree_id)); - cl_git_pass(git_commit_create(&commit_id, repo, NULL, sig, sig, NULL, "message", tree, 0, NULL)); - cl_git_pass(git_reference_create(&ref, repo, "refs/heads/master", &commit_id, 1, "commit: foo")); - git_reference_free(ref); - git_signature_free(sig); - } - - cl_git_pass(git_diff_tree_to_index(&diff, repo, tree, index, NULL)); - cl_assert_equal_i(0, git_diff_num_deltas(diff)); - git_diff_free(diff); - - cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, NULL)); - cl_assert_equal_i(0, git_diff_num_deltas(diff)); - git_diff_free(diff); - - cl_git_pass(git_status_list_new(&status, repo, NULL)); - cl_assert_equal_i(0, git_status_list_entrycount(status)); - - git_status_list_free(status); - git_index_free(index); - git_tree_free(tree); - git_repository_free(repo); -} - -void test_status_submodules__mixed_case(void) -{ - git_status_list *status; - git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; - const git_status_entry *s; - size_t i; - - status_opts.flags = - GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_INCLUDE_IGNORED | - GIT_STATUS_OPT_INCLUDE_UNMODIFIED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS | - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR | - GIT_STATUS_OPT_RENAMES_FROM_REWRITES | - GIT_STATUS_OPT_INCLUDE_UNREADABLE | - GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED; - - g_repo = setup_fixture_submod3(); - - cl_git_pass(git_status_list_new(&status, g_repo, &status_opts)); - - for (i = 0; i < git_status_list_entrycount(status); i++) { - s = git_status_byindex(status, i); - - if (s->head_to_index && - strcmp(s->head_to_index->old_file.path, ".gitmodules") == 0) - continue; - - cl_assert_equal_i(0, s->status); - } - - git_status_list_free(status); -} - diff --git a/tests/status/worktree.c b/tests/status/worktree.c deleted file mode 100644 index 00c6ec2d5..000000000 --- a/tests/status/worktree.c +++ /dev/null @@ -1,1356 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "ignore.h" -#include "status_data.h" -#include "posix.h" -#include "util.h" -#include "path.h" -#include "../diff/diff_helpers.h" -#include "../checkout/checkout_helpers.h" -#include "git2/sys/diff.h" - -/** - * Cleanup - * - * This will be called once after each test finishes, even - * if the test failed - */ -void test_status_worktree__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -/** - * Tests - Status determination on a working tree - */ -/* this test is equivalent to t18-status.c:statuscb0 */ -void test_status_worktree__whole_repository(void) -{ - status_entry_counts counts; - git_repository *repo = cl_git_sandbox_init("status"); - - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = entry_count0; - counts.expected_paths = entry_paths0; - counts.expected_statuses = entry_statuses0; - - cl_git_pass( - git_status_foreach(repo, cb_status__normal, &counts) - ); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -static void assert_show( - const int entry_counts, - const char *entry_paths[], - const unsigned int entry_statuses[], - git_repository *repo, - git_status_show_t show, - unsigned int extra_flags) -{ - status_entry_counts counts; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = entry_counts; - counts.expected_paths = entry_paths; - counts.expected_statuses = entry_statuses; - - opts.flags = GIT_STATUS_OPT_DEFAULTS | extra_flags; - opts.show = show; - - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) - ); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -void test_status_worktree__show_index_and_workdir(void) -{ - assert_show(entry_count0, entry_paths0, entry_statuses0, - cl_git_sandbox_init("status"), GIT_STATUS_SHOW_INDEX_AND_WORKDIR, 0); -} - -void test_status_worktree__show_index_only(void) -{ - assert_show(entry_count5, entry_paths5, entry_statuses5, - cl_git_sandbox_init("status"), GIT_STATUS_SHOW_INDEX_ONLY, 0); -} - -void test_status_worktree__show_workdir_only(void) -{ - assert_show(entry_count6, entry_paths6, entry_statuses6, - cl_git_sandbox_init("status"), GIT_STATUS_SHOW_WORKDIR_ONLY, 0); -} - -/* this test is equivalent to t18-status.c:statuscb1 */ -void test_status_worktree__empty_repository(void) -{ - int count = 0; - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_pass(git_status_foreach(repo, cb_status__count, &count)); - - cl_assert_equal_i(0, count); -} - -static int remove_file_cb(void *data, git_str *file) -{ - const char *filename = git_str_cstr(file); - - GIT_UNUSED(data); - - if (git__suffixcmp(filename, ".git") == 0) - return 0; - - if (git_fs_path_isdir(filename)) - cl_git_pass(git_futils_rmdir_r(filename, NULL, GIT_RMDIR_REMOVE_FILES)); - else - cl_git_pass(p_unlink(git_str_cstr(file))); - - return 0; -} - -/* this test is equivalent to t18-status.c:statuscb2 */ -void test_status_worktree__purged_worktree(void) -{ - status_entry_counts counts; - git_repository *repo = cl_git_sandbox_init("status"); - git_str workdir = GIT_STR_INIT; - - /* first purge the contents of the worktree */ - cl_git_pass(git_str_sets(&workdir, git_repository_workdir(repo))); - cl_git_pass(git_fs_path_direach(&workdir, 0, remove_file_cb, NULL)); - git_str_dispose(&workdir); - - /* now get status */ - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = entry_count2; - counts.expected_paths = entry_paths2; - counts.expected_statuses = entry_statuses2; - - cl_git_pass( - git_status_foreach(repo, cb_status__normal, &counts) - ); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -/* this test is similar to t18-status.c:statuscb3 */ -void test_status_worktree__swap_subdir_and_file(void) -{ - status_entry_counts counts; - git_repository *repo = cl_git_sandbox_init("status"); - git_index *index; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - bool ignore_case; - - cl_git_pass(git_repository_index(&index, repo)); - ignore_case = (git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0; - git_index_free(index); - - /* first alter the contents of the worktree */ - cl_git_pass(p_rename("status/current_file", "status/swap")); - cl_git_pass(p_rename("status/subdir", "status/current_file")); - cl_git_pass(p_rename("status/swap", "status/subdir")); - - cl_git_mkfile("status/.HEADER", "dummy"); - cl_git_mkfile("status/42-is-not-prime.sigh", "dummy"); - cl_git_mkfile("status/README.md", "dummy"); - - /* now get status */ - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = entry_count3; - counts.expected_paths = ignore_case ? entry_paths3_icase : entry_paths3; - counts.expected_statuses = ignore_case ? entry_statuses3_icase : entry_statuses3; - - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_INCLUDE_IGNORED; - - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) - ); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -void test_status_worktree__swap_subdir_with_recurse_and_pathspec(void) -{ - status_entry_counts counts; - git_repository *repo = cl_git_sandbox_init("status"); - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - - /* first alter the contents of the worktree */ - cl_git_pass(p_rename("status/current_file", "status/swap")); - cl_git_pass(p_rename("status/subdir", "status/current_file")); - cl_git_pass(p_rename("status/swap", "status/subdir")); - cl_git_mkfile("status/.new_file", "dummy"); - cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", 0777)); - cl_git_mkfile("status/zzz_new_dir/new_file", "dummy"); - cl_git_mkfile("status/zzz_new_file", "dummy"); - - /* now get status */ - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = entry_count4; - counts.expected_paths = entry_paths4; - counts.expected_statuses = entry_statuses4; - - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - /* TODO: set pathspec to "current_file" eventually */ - - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) - ); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -static void stage_and_commit(git_repository *repo, const char *path) -{ - git_index *index; - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, path)); - cl_repo_commit_from_index(NULL, repo, NULL, 1323847743, "Initial commit\n"); - git_index_free(index); -} - -void test_status_worktree__within_subdir(void) -{ - status_entry_counts counts; - git_repository *repo = cl_git_sandbox_init("status"); - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - char *paths[] = { "zzz_new_dir" }; - git_strarray pathsArray; - - /* first alter the contents of the worktree */ - cl_git_mkfile("status/.new_file", "dummy"); - cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", 0777)); - cl_git_mkfile("status/zzz_new_dir/new_file", "dummy"); - cl_git_mkfile("status/zzz_new_file", "dummy"); - cl_git_mkfile("status/wut", "dummy"); - - stage_and_commit(repo, "zzz_new_dir/new_file"); - - /* now get status */ - memset(&counts, 0x0, sizeof(status_entry_counts)); - counts.expected_entry_count = entry_count4; - counts.expected_paths = entry_paths4; - counts.expected_statuses = entry_statuses4; - counts.debug = true; - - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; - - pathsArray.count = 1; - pathsArray.strings = paths; - opts.pathspec = pathsArray; - - /* We committed zzz_new_dir/new_file above. It shouldn't be reported. */ - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) - ); - - cl_assert_equal_i(0, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -/* this test is equivalent to t18-status.c:singlestatus0 */ -void test_status_worktree__single_file(void) -{ - int i; - unsigned int status_flags; - git_repository *repo = cl_git_sandbox_init("status"); - - for (i = 0; i < (int)entry_count0; i++) { - cl_git_pass( - git_status_file(&status_flags, repo, entry_paths0[i]) - ); - cl_assert(entry_statuses0[i] == status_flags); - } -} - -/* this test is equivalent to t18-status.c:singlestatus1 */ -void test_status_worktree__single_nonexistent_file(void) -{ - int error; - unsigned int status_flags; - git_repository *repo = cl_git_sandbox_init("status"); - - error = git_status_file(&status_flags, repo, "nonexistent"); - cl_git_fail(error); - cl_assert(error == GIT_ENOTFOUND); -} - -/* this test is equivalent to t18-status.c:singlestatus2 */ -void test_status_worktree__single_nonexistent_file_empty_repo(void) -{ - int error; - unsigned int status_flags; - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); - - error = git_status_file(&status_flags, repo, "nonexistent"); - cl_git_fail(error); - cl_assert(error == GIT_ENOTFOUND); -} - -/* this test is equivalent to t18-status.c:singlestatus3 */ -void test_status_worktree__single_file_empty_repo(void) -{ - unsigned int status_flags; - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_mkfile("empty_standard_repo/new_file", "new_file\n"); - - cl_git_pass(git_status_file(&status_flags, repo, "new_file")); - cl_assert(status_flags == GIT_STATUS_WT_NEW); -} - -/* this test is equivalent to t18-status.c:singlestatus4 */ -void test_status_worktree__single_folder(void) -{ - int error; - unsigned int status_flags; - git_repository *repo = cl_git_sandbox_init("status"); - - error = git_status_file(&status_flags, repo, "subdir"); - cl_git_fail(error); - cl_assert(error != GIT_ENOTFOUND); -} - - -void test_status_worktree__ignores(void) -{ - int i, ignored; - git_repository *repo = cl_git_sandbox_init("status"); - - for (i = 0; i < (int)entry_count0; i++) { - cl_git_pass( - git_status_should_ignore(&ignored, repo, entry_paths0[i]) - ); - cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_IGNORED)); - } - - cl_git_pass( - git_status_should_ignore(&ignored, repo, "nonexistent_file") - ); - cl_assert(!ignored); - - cl_git_pass( - git_status_should_ignore(&ignored, repo, "ignored_nonexistent_file") - ); - cl_assert(ignored); -} - -static int cb_status__check_592(const char *p, unsigned int s, void *payload) -{ - if (s != GIT_STATUS_WT_DELETED || - (payload != NULL && strcmp(p, (const char *)payload) != 0)) - return -1; - - return 0; -} - -void test_status_worktree__issue_592(void) -{ - git_repository *repo; - git_str path = GIT_STR_INIT; - - repo = cl_git_sandbox_init("issue_592"); - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "l.txt")); - cl_git_pass(p_unlink(git_str_cstr(&path))); - cl_assert(!git_fs_path_exists("issue_592/l.txt")); - - cl_git_pass(git_status_foreach(repo, cb_status__check_592, "l.txt")); - - git_str_dispose(&path); -} - -void test_status_worktree__issue_592_2(void) -{ - git_repository *repo; - git_str path = GIT_STR_INIT; - - repo = cl_git_sandbox_init("issue_592"); - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "c/a.txt")); - cl_git_pass(p_unlink(git_str_cstr(&path))); - cl_assert(!git_fs_path_exists("issue_592/c/a.txt")); - - cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt")); - - git_str_dispose(&path); -} - -void test_status_worktree__issue_592_3(void) -{ - git_repository *repo; - git_str path = GIT_STR_INIT; - - repo = cl_git_sandbox_init("issue_592"); - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "c")); - cl_git_pass(git_futils_rmdir_r(git_str_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES)); - cl_assert(!git_fs_path_exists("issue_592/c/a.txt")); - - cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt")); - - git_str_dispose(&path); -} - -void test_status_worktree__issue_592_4(void) -{ - git_repository *repo; - git_str path = GIT_STR_INIT; - - repo = cl_git_sandbox_init("issue_592"); - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "t/b.txt")); - cl_git_pass(p_unlink(git_str_cstr(&path))); - - cl_git_pass(git_status_foreach(repo, cb_status__check_592, "t/b.txt")); - - git_str_dispose(&path); -} - -void test_status_worktree__issue_592_5(void) -{ - git_repository *repo; - git_str path = GIT_STR_INIT; - - repo = cl_git_sandbox_init("issue_592"); - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(repo), "t")); - cl_git_pass(git_futils_rmdir_r(git_str_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES)); - cl_git_pass(p_mkdir(git_str_cstr(&path), 0777)); - - cl_git_pass(git_status_foreach(repo, cb_status__check_592, NULL)); - - git_str_dispose(&path); -} - -void test_status_worktree__issue_592_ignores_0(void) -{ - int count = 0; - status_entry_single st; - git_repository *repo = cl_git_sandbox_init("issue_592"); - - cl_git_pass(git_status_foreach(repo, cb_status__count, &count)); - cl_assert_equal_i(0, count); - - cl_git_rewritefile("issue_592/.gitignore", - ".gitignore\n*.txt\nc/\n[tT]*/\n"); - - cl_git_pass(git_status_foreach(repo, cb_status__count, &count)); - cl_assert_equal_i(1, count); - - /* This is a situation where the behavior of libgit2 is - * different from core git. Core git will show ignored.txt - * in the list of ignored files, even though the directory - * "t" is ignored and the file is untracked because we have - * the explicit "*.txt" ignore rule. Libgit2 just excludes - * all untracked files that are contained within ignored - * directories without explicitly listing them. - */ - cl_git_rewritefile("issue_592/t/ignored.txt", "ping"); - - memset(&st, 0, sizeof(st)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &st)); - cl_assert_equal_i(1, st.count); - cl_assert(st.status == GIT_STATUS_IGNORED); - - cl_git_rewritefile("issue_592/c/ignored_by_dir", "ping"); - - memset(&st, 0, sizeof(st)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &st)); - cl_assert_equal_i(1, st.count); - cl_assert(st.status == GIT_STATUS_IGNORED); - - cl_git_rewritefile("issue_592/t/ignored_by_dir_pattern", "ping"); - - memset(&st, 0, sizeof(st)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &st)); - cl_assert_equal_i(1, st.count); - cl_assert(st.status == GIT_STATUS_IGNORED); -} - -void test_status_worktree__issue_592_ignored_dirs_with_tracked_content(void) -{ - int count = 0; - git_repository *repo = cl_git_sandbox_init("issue_592b"); - - cl_git_pass(git_status_foreach(repo, cb_status__count, &count)); - cl_assert_equal_i(1, count); - - /* if we are really mimicking core git, then only ignored1.txt - * at the top level will show up in the ignores list here. - * everything else will be unmodified or skipped completely. - */ -} - -void test_status_worktree__conflict_with_diff3(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_index *index; - unsigned int status; - git_index_entry ancestor_entry, our_entry, their_entry; - - memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); - memset(&our_entry, 0x0, sizeof(git_index_entry)); - memset(&their_entry, 0x0, sizeof(git_index_entry)); - - ancestor_entry.path = "modified_file"; - ancestor_entry.mode = 0100644; - git_oid_fromstr(&ancestor_entry.id, - "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); - - our_entry.path = "modified_file"; - our_entry.mode = 0100644; - git_oid_fromstr(&our_entry.id, - "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); - - their_entry.path = "modified_file"; - their_entry.mode = 0100644; - git_oid_fromstr(&their_entry.id, - "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); - - cl_git_pass(git_status_file(&status, repo, "modified_file")); - cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status); - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_remove(index, "modified_file", 0)); - cl_git_pass(git_index_conflict_add( - index, &ancestor_entry, &our_entry, &their_entry)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_status_file(&status, repo, "modified_file")); - - cl_assert_equal_i(GIT_STATUS_CONFLICTED, status); -} - -static const char *filemode_paths[] = { - "exec_off", - "exec_off2on_staged", - "exec_off2on_workdir", - "exec_off_untracked", - "exec_on", - "exec_on2off_staged", - "exec_on2off_workdir", - "exec_on_untracked", -}; - -static unsigned int filemode_statuses[] = { - GIT_STATUS_CURRENT, - GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW, - GIT_STATUS_CURRENT, - GIT_STATUS_INDEX_MODIFIED, - GIT_STATUS_WT_MODIFIED, - GIT_STATUS_WT_NEW -}; - -static const int filemode_count = 8; - -void test_status_worktree__filemode_changes(void) -{ - git_repository *repo = cl_git_sandbox_init("filemodes"); - status_entry_counts counts; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - - /* overwrite stored filemode with platform appropriate value */ - if (cl_is_chmod_supported()) - cl_repo_set_bool(repo, "core.filemode", true); - else { - int i; - - cl_repo_set_bool(repo, "core.filemode", false); - - /* won't trust filesystem mode diffs, so these will appear unchanged */ - for (i = 0; i < filemode_count; ++i) - if (filemode_statuses[i] == GIT_STATUS_WT_MODIFIED) - filemode_statuses[i] = GIT_STATUS_CURRENT; - } - - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_INCLUDE_IGNORED | - GIT_STATUS_OPT_INCLUDE_UNMODIFIED; - - memset(&counts, 0, sizeof(counts)); - counts.expected_entry_count = filemode_count; - counts.expected_paths = filemode_paths; - counts.expected_statuses = filemode_statuses; - - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) - ); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -void test_status_worktree__filemode_non755(void) -{ - git_repository *repo = cl_git_sandbox_init("filemodes"); - status_entry_counts counts; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - git_str executable_path = GIT_STR_INIT; - git_str nonexecutable_path = GIT_STR_INIT; - - if (!cl_is_chmod_supported()) - return; - - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_INCLUDE_IGNORED | - GIT_STATUS_OPT_INCLUDE_UNMODIFIED; - - git_str_joinpath(&executable_path, git_repository_workdir(repo), "exec_on"); - cl_must_pass(p_chmod(git_str_cstr(&executable_path), 0744)); - git_str_dispose(&executable_path); - - git_str_joinpath(&nonexecutable_path, git_repository_workdir(repo), "exec_off"); - - cl_must_pass(p_chmod(git_str_cstr(&nonexecutable_path), 0655)); - git_str_dispose(&nonexecutable_path); - - memset(&counts, 0, sizeof(counts)); - counts.expected_entry_count = filemode_count; - counts.expected_paths = filemode_paths; - counts.expected_statuses = filemode_statuses; - - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) - ); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - - -static int cb_status__interrupt(const char *p, unsigned int s, void *payload) -{ - volatile int *count = (int *)payload; - - GIT_UNUSED(p); - GIT_UNUSED(s); - - (*count)++; - - return (*count == 8) ? -111 : 0; -} - -void test_status_worktree__interruptable_foreach(void) -{ - int count = 0; - git_repository *repo = cl_git_sandbox_init("status"); - - cl_assert_equal_i( - -111, git_status_foreach(repo, cb_status__interrupt, &count) - ); - - cl_assert_equal_i(8, count); -} - -void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - unsigned int status; - - cl_repo_set_bool(repo, "core.autocrlf", true); - - cl_git_rewritefile("status/current_file", "current_file\r\n"); - - cl_git_pass(git_status_file(&status, repo, "current_file")); - - /* stat data on file should no longer match stat cache, even though - * file diff will be empty because of line-ending conversion - matches - * the Git command-line behavior here. - */ - cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status); -} - -void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf_issue_1397(void) -{ - git_repository *repo = cl_git_sandbox_init("issue_1397"); - unsigned int status; - - cl_repo_set_bool(repo, "core.autocrlf", true); - - cl_git_pass(git_status_file(&status, repo, "crlf_file.txt")); - - cl_assert_equal_i(GIT_STATUS_CURRENT, status); -} - -void test_status_worktree__conflicted_item(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_index *index; - unsigned int status; - git_index_entry ancestor_entry, our_entry, their_entry; - - memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); - memset(&our_entry, 0x0, sizeof(git_index_entry)); - memset(&their_entry, 0x0, sizeof(git_index_entry)); - - ancestor_entry.mode = 0100644; - ancestor_entry.path = "modified_file"; - git_oid_fromstr(&ancestor_entry.id, - "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); - - our_entry.mode = 0100644; - our_entry.path = "modified_file"; - git_oid_fromstr(&our_entry.id, - "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); - - their_entry.mode = 0100644; - their_entry.path = "modified_file"; - git_oid_fromstr(&their_entry.id, - "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); - - cl_git_pass(git_status_file(&status, repo, "modified_file")); - cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status); - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_conflict_add(index, &ancestor_entry, - &our_entry, &their_entry)); - - cl_git_pass(git_status_file(&status, repo, "modified_file")); - cl_assert_equal_i(GIT_STATUS_CONFLICTED, status); - - git_index_free(index); -} - -void test_status_worktree__conflict_has_no_oid(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_index *index; - git_index_entry entry = {{0}}; - git_status_list *statuslist; - const git_status_entry *status; - git_oid zero_id = {{0}}; - - entry.mode = 0100644; - entry.path = "modified_file"; - git_oid_fromstr(&entry.id, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_conflict_add(index, &entry, &entry, &entry)); - - git_status_list_new(&statuslist, repo, NULL); - - cl_assert_equal_i(16, git_status_list_entrycount(statuslist)); - - status = git_status_byindex(statuslist, 2); - - cl_assert_equal_i(GIT_STATUS_CONFLICTED, status->status); - cl_assert_equal_s("modified_file", status->head_to_index->old_file.path); - cl_assert(!git_oid_equal(&zero_id, &status->head_to_index->old_file.id)); - cl_assert(0 != status->head_to_index->old_file.mode); - cl_assert_equal_s("modified_file", status->head_to_index->new_file.path); - cl_assert_equal_oid(&zero_id, &status->head_to_index->new_file.id); - cl_assert_equal_i(0, status->head_to_index->new_file.mode); - cl_assert_equal_i(0, status->head_to_index->new_file.size); - - cl_assert_equal_s("modified_file", status->index_to_workdir->old_file.path); - cl_assert_equal_oid(&zero_id, &status->index_to_workdir->old_file.id); - cl_assert_equal_i(0, status->index_to_workdir->old_file.mode); - cl_assert_equal_i(0, status->index_to_workdir->old_file.size); - cl_assert_equal_s("modified_file", status->index_to_workdir->new_file.path); - cl_assert( - !git_oid_equal(&zero_id, &status->index_to_workdir->new_file.id) || - !(status->index_to_workdir->new_file.flags & GIT_DIFF_FLAG_VALID_ID)); - cl_assert(0 != status->index_to_workdir->new_file.mode); - cl_assert(0 != status->index_to_workdir->new_file.size); - - git_index_free(index); - git_status_list_free(statuslist); -} - -static void assert_ignore_case( - bool should_ignore_case, - int expected_lower_cased_file_status, - int expected_camel_cased_file_status) -{ - unsigned int status; - git_str lower_case_path = GIT_STR_INIT, camel_case_path = GIT_STR_INIT; - git_repository *repo, *repo2; - - repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt"); - - cl_repo_set_bool(repo, "core.ignorecase", should_ignore_case); - - cl_git_pass(git_str_joinpath(&lower_case_path, - git_repository_workdir(repo), "plop")); - - cl_git_mkfile(git_str_cstr(&lower_case_path), ""); - - stage_and_commit(repo, "plop"); - - cl_git_pass(git_repository_open(&repo2, "./empty_standard_repo")); - - cl_git_pass(git_status_file(&status, repo2, "plop")); - cl_assert_equal_i(GIT_STATUS_CURRENT, status); - - cl_git_pass(git_str_joinpath(&camel_case_path, - git_repository_workdir(repo), "Plop")); - - cl_git_pass(p_rename(git_str_cstr(&lower_case_path), git_str_cstr(&camel_case_path))); - - cl_git_pass(git_status_file(&status, repo2, "plop")); - cl_assert_equal_i(expected_lower_cased_file_status, status); - - cl_git_pass(git_status_file(&status, repo2, "Plop")); - cl_assert_equal_i(expected_camel_cased_file_status, status); - - git_repository_free(repo2); - git_str_dispose(&lower_case_path); - git_str_dispose(&camel_case_path); -} - -void test_status_worktree__file_status_honors_core_ignorecase_true(void) -{ - assert_ignore_case(true, GIT_STATUS_CURRENT, GIT_STATUS_CURRENT); -} - -void test_status_worktree__file_status_honors_core_ignorecase_false(void) -{ - assert_ignore_case(false, GIT_STATUS_WT_DELETED, GIT_STATUS_WT_NEW); -} - -void test_status_worktree__file_status_honors_case_ignorecase_regarding_untracked_files(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - unsigned int status; - git_index *index; - - cl_repo_set_bool(repo, "core.ignorecase", false); - - repo = cl_git_sandbox_reopen(); - - /* Actually returns GIT_STATUS_IGNORED on Windows */ - cl_git_fail_with(git_status_file(&status, repo, "NEW_FILE"), GIT_ENOTFOUND); - - cl_git_pass(git_repository_index(&index, repo)); - - cl_git_pass(git_index_add_bypath(index, "new_file")); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - /* Actually returns GIT_STATUS_IGNORED on Windows */ - cl_git_fail_with(git_status_file(&status, repo, "NEW_FILE"), GIT_ENOTFOUND); -} - -void test_status_worktree__simple_delete(void) -{ - git_repository *repo = cl_git_sandbox_init("renames"); - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - int count; - - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH | - GIT_STATUS_OPT_EXCLUDE_SUBMODULES | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - - count = 0; - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__count, &count) ); - cl_assert_equal_i(0, count); - - cl_must_pass(p_unlink("renames/untimely.txt")); - - count = 0; - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__count, &count) ); - cl_assert_equal_i(1, count); -} - -void test_status_worktree__simple_delete_indexed(void) -{ - git_repository *repo = cl_git_sandbox_init("renames"); - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - git_status_list *status; - - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH | - GIT_STATUS_OPT_EXCLUDE_SUBMODULES | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - - cl_git_pass(git_status_list_new(&status, repo, &opts)); - cl_assert_equal_sz(0, git_status_list_entrycount(status)); - git_status_list_free(status); - - cl_must_pass(p_unlink("renames/untimely.txt")); - - cl_git_pass(git_status_list_new(&status, repo, &opts)); - cl_assert_equal_sz(1, git_status_list_entrycount(status)); - cl_assert_equal_i( - GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status); - git_status_list_free(status); -} - -static const char *icase_paths[] = { "B", "c", "g", "H" }; -static unsigned int icase_statuses[] = { - GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED, -}; - -static const char *case_paths[] = { "B", "H", "c", "g" }; -static unsigned int case_statuses[] = { - GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED, - GIT_STATUS_WT_DELETED, GIT_STATUS_WT_MODIFIED, -}; - -void test_status_worktree__sorting_by_case(void) -{ - git_repository *repo = cl_git_sandbox_init("icase"); - git_index *index; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - bool native_ignore_case; - status_entry_counts counts; - - cl_git_pass(git_repository_index(&index, repo)); - native_ignore_case = - (git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0; - git_index_free(index); - - memset(&counts, 0, sizeof(counts)); - counts.expected_entry_count = 0; - counts.expected_paths = NULL; - counts.expected_statuses = NULL; - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); - - cl_git_rewritefile("icase/B", "new stuff"); - cl_must_pass(p_unlink("icase/c")); - cl_git_rewritefile("icase/g", "new stuff"); - cl_must_pass(p_unlink("icase/H")); - - memset(&counts, 0, sizeof(counts)); - counts.expected_entry_count = 4; - if (native_ignore_case) { - counts.expected_paths = icase_paths; - counts.expected_statuses = icase_statuses; - } else { - counts.expected_paths = case_paths; - counts.expected_statuses = case_statuses; - } - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); - - opts.flags = GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; - - memset(&counts, 0, sizeof(counts)); - counts.expected_entry_count = 4; - counts.expected_paths = case_paths; - counts.expected_statuses = case_statuses; - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); - - opts.flags = GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY; - - memset(&counts, 0, sizeof(counts)); - counts.expected_entry_count = 4; - counts.expected_paths = icase_paths; - counts.expected_statuses = icase_statuses; - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -void test_status_worktree__long_filenames(void) -{ - char path[260*4+1] = {0}; - const char *expected_paths[] = {path}; - const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; - - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts = {0}; - - /* Create directory with amazingly long filename */ - sprintf(path, "empty_standard_repo/%s", longname); - cl_git_pass(git_futils_mkdir_r(path, 0777)); - sprintf(path, "empty_standard_repo/%s/foo", longname); - cl_git_mkfile(path, "dummy"); - - sprintf(path, "%s/foo", longname); - counts.expected_entry_count = 1; - counts.expected_paths = expected_paths; - counts.expected_statuses = expected_statuses; - - opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; - opts.flags = GIT_STATUS_OPT_DEFAULTS; - - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -/* The update stat cache tests mostly just mirror other tests and try - * to make sure that updating the stat cache doesn't change the results - * while reducing the amount of work that needs to be done - */ - -static void check_status0(git_status_list *status) -{ - size_t i, max_i = git_status_list_entrycount(status); - cl_assert_equal_sz(entry_count0, max_i); - for (i = 0; i < max_i; ++i) { - const git_status_entry *entry = git_status_byindex(status, i); - cl_assert_equal_i(entry_statuses0[i], entry->status); - } -} - -void test_status_worktree__update_stat_cache_0(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - git_status_list *status; - git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT; - git_index *index; - - opts.flags = GIT_STATUS_OPT_DEFAULTS; - - cl_git_pass(git_status_list_new(&status, repo, &opts)); - check_status0(status); - cl_git_pass(git_status_list_get_perfdata(&perf, status)); - cl_assert_equal_sz(13 + 3, perf.stat_calls); - cl_assert_equal_sz(5, perf.oid_calculations); - - git_status_list_free(status); - - /* tick the index so we avoid recalculating racily-clean entries */ - cl_git_pass(git_repository_index__weakptr(&index, repo)); - tick_index(index); - - opts.flags |= GIT_STATUS_OPT_UPDATE_INDEX; - - cl_git_pass(git_status_list_new(&status, repo, &opts)); - check_status0(status); - cl_git_pass(git_status_list_get_perfdata(&perf, status)); - cl_assert_equal_sz(13 + 3, perf.stat_calls); - cl_assert_equal_sz(5, perf.oid_calculations); - - git_status_list_free(status); - - opts.flags &= ~GIT_STATUS_OPT_UPDATE_INDEX; - - /* tick again as the index updating from the previous diff might have reset the timestamp */ - tick_index(index); - cl_git_pass(git_status_list_new(&status, repo, &opts)); - check_status0(status); - cl_git_pass(git_status_list_get_perfdata(&perf, status)); - cl_assert_equal_sz(13 + 3, perf.stat_calls); - cl_assert_equal_sz(0, perf.oid_calculations); - - git_status_list_free(status); -} - -void test_status_worktree__unreadable(void) -{ -#ifndef GIT_WIN32 - const char *expected_paths[] = { "no_permission/foo" }; - const unsigned int expected_statuses[] = {GIT_STATUS_WT_UNREADABLE}; - - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts = {0}; - - if (geteuid() == 0) - cl_skip(); - - /* Create directory with no read permission */ - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777)); - cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); - p_chmod("empty_standard_repo/no_permission", 0644); - - counts.expected_entry_count = 1; - counts.expected_paths = expected_paths; - counts.expected_statuses = expected_statuses; - - opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; - opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_INCLUDE_UNREADABLE; - - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); - - /* Restore permissions so we can cleanup :) */ - p_chmod("empty_standard_repo/no_permission", 0777); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -#else - cl_skip(); -#endif -} - -void test_status_worktree__unreadable_not_included(void) -{ -#ifndef GIT_WIN32 - const char *expected_paths[] = { "no_permission/" }; - const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; - - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts = {0}; - - /* Create directory with no read permission */ - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777)); - cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); - p_chmod("empty_standard_repo/no_permission", 0644); - - counts.expected_entry_count = 1; - counts.expected_paths = expected_paths; - counts.expected_statuses = expected_statuses; - - opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; - opts.flags = (GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_INCLUDE_UNTRACKED); - - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); - - /* Restore permissions so we can cleanup :) */ - p_chmod("empty_standard_repo/no_permission", 0777); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -#else - cl_skip(); -#endif -} - -void test_status_worktree__unreadable_as_untracked(void) -{ - const char *expected_paths[] = { "no_permission/foo" }; - const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; - - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts = {0}; - - /* Create directory with no read permission */ - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777)); - cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); - p_chmod("empty_standard_repo/no_permission", 0644); - - counts.expected_entry_count = 1; - counts.expected_paths = expected_paths; - counts.expected_statuses = expected_statuses; - - opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; - opts.flags = GIT_STATUS_OPT_DEFAULTS | - GIT_STATUS_OPT_INCLUDE_UNREADABLE | - GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED; - - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); - - /* Restore permissions so we can cleanup :) */ - p_chmod("empty_standard_repo/no_permission", 0777); - - cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); - cl_assert_equal_i(0, counts.wrong_status_flags_count); - cl_assert_equal_i(0, counts.wrong_sorted_path); -} - -void test_status_worktree__update_index_with_symlink_doesnt_change_mode(void) -{ - git_repository *repo = cl_git_sandbox_init("testrepo"); - git_reference *head; - git_object *head_object; - git_index *index; - const git_index_entry *idx_entry; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - status_entry_counts counts = {0}; - const char *expected_paths[] = { "README" }; - const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW}; - - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_UPDATE_INDEX; - - cl_git_pass(git_repository_head(&head, repo)); - cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT)); - - cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL)); - - cl_git_rewritefile("testrepo/README", "This was rewritten."); - - /* this status rewrites the index because we have changed the - * contents of a tracked file - */ - counts.expected_entry_count = 1; - counts.expected_paths = expected_paths; - counts.expected_statuses = expected_statuses; - - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)); - cl_assert_equal_i(1, counts.entry_count); - - /* now ensure that the status's rewrite of the index did not screw - * up the mode of the symlink `link_to_new.txt`, particularly - * on platforms that don't support symlinks - */ - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_read(index, true)); - - cl_assert(idx_entry = git_index_get_bypath(index, "link_to_new.txt", 0)); - cl_assert(S_ISLNK(idx_entry->mode)); - - git_index_free(index); - git_object_free(head_object); - git_reference_free(head); -} - -static const char *testrepo2_subdir_paths[] = { - "subdir/README", - "subdir/new.txt", - "subdir/subdir2/README", - "subdir/subdir2/new.txt", -}; - -static const char *testrepo2_subdir_paths_icase[] = { - "subdir/new.txt", - "subdir/README", - "subdir/subdir2/new.txt", - "subdir/subdir2/README" -}; - -void test_status_worktree__with_directory_in_pathlist(void) -{ - git_repository *repo = cl_git_sandbox_init("testrepo2"); - git_index *index; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - git_status_list *statuslist; - const git_status_entry *status; - size_t i, entrycount; - bool native_ignore_case; - char *subdir_path = "subdir"; - - cl_git_pass(git_repository_index(&index, repo)); - native_ignore_case = - (git_index_caps(index) & GIT_INDEX_CAPABILITY_IGNORE_CASE) != 0; - git_index_free(index); - - opts.pathspec.strings = &subdir_path; - opts.pathspec.count = 1; - opts.flags = - GIT_STATUS_OPT_DEFAULTS | - GIT_STATUS_OPT_INCLUDE_UNMODIFIED | - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; - - opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; - git_status_list_new(&statuslist, repo, &opts); - - entrycount = git_status_list_entrycount(statuslist); - cl_assert_equal_i(4, entrycount); - - for (i = 0; i < entrycount; i++) { - status = git_status_byindex(statuslist, i); - cl_assert_equal_i(0, status->status); - cl_assert_equal_s(native_ignore_case ? - testrepo2_subdir_paths_icase[i] : - testrepo2_subdir_paths[i], - status->index_to_workdir->old_file.path); - } - - git_status_list_free(statuslist); - - opts.show = GIT_STATUS_SHOW_INDEX_ONLY; - git_status_list_new(&statuslist, repo, &opts); - - entrycount = git_status_list_entrycount(statuslist); - cl_assert_equal_i(4, entrycount); - - for (i = 0; i < entrycount; i++) { - status = git_status_byindex(statuslist, i); - cl_assert_equal_i(0, status->status); - cl_assert_equal_s(native_ignore_case ? - testrepo2_subdir_paths_icase[i] : - testrepo2_subdir_paths[i], - status->head_to_index->old_file.path); - } - - git_status_list_free(statuslist); - - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - git_status_list_new(&statuslist, repo, &opts); - - entrycount = git_status_list_entrycount(statuslist); - cl_assert_equal_i(4, entrycount); - - for (i = 0; i < entrycount; i++) { - status = git_status_byindex(statuslist, i); - cl_assert_equal_i(0, status->status); - cl_assert_equal_s(native_ignore_case ? - testrepo2_subdir_paths_icase[i] : - testrepo2_subdir_paths[i], - status->index_to_workdir->old_file.path); - } - - git_status_list_free(statuslist); -} - -void test_status_worktree__at_head_parent(void) -{ - git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - git_status_list *statuslist; - git_tree *parent_tree; - const git_status_entry *status; - - cl_git_mkfile("empty_standard_repo/file1", "ping"); - stage_and_commit(repo, "file1"); - - cl_git_pass(git_repository_head_tree(&parent_tree, repo)); - - cl_git_mkfile("empty_standard_repo/file2", "pong"); - stage_and_commit(repo, "file2"); - - cl_git_rewritefile("empty_standard_repo/file2", "pyng"); - - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opts.baseline = parent_tree; - cl_git_pass(git_status_list_new(&statuslist, repo, &opts)); - - cl_assert_equal_sz(1, git_status_list_entrycount(statuslist)); - status = git_status_byindex(statuslist, 0); - cl_assert(status != NULL); - cl_assert_equal_s("file2", status->index_to_workdir->old_file.path); - cl_assert_equal_i(GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW, status->status); - - git_tree_free(parent_tree); - git_status_list_free(statuslist); -} diff --git a/tests/status/worktree_init.c b/tests/status/worktree_init.c deleted file mode 100644 index 40f1b2a31..000000000 --- a/tests/status/worktree_init.c +++ /dev/null @@ -1,338 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/repository.h" - -#include "futils.h" -#include "ignore.h" -#include "status_helpers.h" -#include "posix.h" -#include "util.h" -#include "path.h" - -static void cleanup_new_repo(void *path) -{ - cl_fixture_cleanup((char *)path); -} - -void test_status_worktree_init__cannot_retrieve_the_status_of_a_bare_repository(void) -{ - git_repository *repo; - unsigned int status = 0; - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_assert_equal_i(GIT_EBAREREPO, git_status_file(&status, repo, "dummy")); - git_repository_free(repo); -} - -void test_status_worktree_init__first_commit_in_progress(void) -{ - git_repository *repo; - git_index *index; - status_entry_single result; - - cl_set_cleanup(&cleanup_new_repo, "getting_started"); - - cl_git_pass(git_repository_init(&repo, "getting_started", 0)); - cl_git_mkfile("getting_started/testfile.txt", "content\n"); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(1, result.count); - cl_assert(result.status == GIT_STATUS_WT_NEW); - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, "testfile.txt")); - cl_git_pass(git_index_write(index)); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(1, result.count); - cl_assert(result.status == GIT_STATUS_INDEX_NEW); - - git_index_free(index); - git_repository_free(repo); -} - - - -void test_status_worktree_init__status_file_without_index_or_workdir(void) -{ - git_repository *repo; - unsigned int status = 0; - git_index *index; - - cl_git_pass(p_mkdir("wd", 0777)); - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_set_workdir(repo, "wd", false)); - - cl_git_pass(git_index_open(&index, "empty-index")); - cl_assert_equal_i(0, (int)git_index_entrycount(index)); - git_repository_set_index(repo, index); - - cl_git_pass(git_status_file(&status, repo, "branch_file.txt")); - - cl_assert_equal_i(GIT_STATUS_INDEX_DELETED, status); - - git_repository_free(repo); - git_index_free(index); - cl_git_pass(p_rmdir("wd")); -} - -static void fill_index_wth_head_entries(git_repository *repo, git_index *index) -{ - git_oid oid; - git_commit *commit; - git_tree *tree; - - cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - cl_git_pass(git_commit_tree(&tree, commit)); - - cl_git_pass(git_index_read_tree(index, tree)); - cl_git_pass(git_index_write(index)); - - git_tree_free(tree); - git_commit_free(commit); -} - -void test_status_worktree_init__status_file_with_clean_index_and_empty_workdir(void) -{ - git_repository *repo; - unsigned int status = 0; - git_index *index; - - cl_git_pass(p_mkdir("wd", 0777)); - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_set_workdir(repo, "wd", false)); - - cl_git_pass(git_index_open(&index, "my-index")); - fill_index_wth_head_entries(repo, index); - - git_repository_set_index(repo, index); - - cl_git_pass(git_status_file(&status, repo, "branch_file.txt")); - - cl_assert_equal_i(GIT_STATUS_WT_DELETED, status); - - git_repository_free(repo); - git_index_free(index); - cl_git_pass(p_rmdir("wd")); - cl_git_pass(p_unlink("my-index")); -} - -void test_status_worktree_init__bracket_in_filename(void) -{ - git_repository *repo; - git_index *index; - status_entry_single result; - unsigned int status_flags; - - #define FILE_WITH_BRACKET "LICENSE[1].md" - #define FILE_WITHOUT_BRACKET "LICENSE1.md" - - cl_set_cleanup(&cleanup_new_repo, "with_bracket"); - - cl_git_pass(git_repository_init(&repo, "with_bracket", 0)); - cl_git_mkfile("with_bracket/" FILE_WITH_BRACKET, "I have a bracket in my name\n"); - - /* file is new to working directory */ - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(1, result.count); - cl_assert(result.status == GIT_STATUS_WT_NEW); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); - cl_assert(status_flags == GIT_STATUS_WT_NEW); - - /* ignore the file */ - - cl_git_rewritefile("with_bracket/.gitignore", "*.md\n.gitignore\n"); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(2, result.count); - cl_assert(result.status == GIT_STATUS_IGNORED); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); - cl_assert(status_flags == GIT_STATUS_IGNORED); - - /* don't ignore the file */ - - cl_git_rewritefile("with_bracket/.gitignore", ".gitignore\n"); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(2, result.count); - cl_assert(result.status == GIT_STATUS_WT_NEW); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); - cl_assert(status_flags == GIT_STATUS_WT_NEW); - - /* add the file to the index */ - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, FILE_WITH_BRACKET)); - cl_git_pass(git_index_write(index)); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(2, result.count); - cl_assert(result.status == GIT_STATUS_INDEX_NEW); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); - cl_assert(status_flags == GIT_STATUS_INDEX_NEW); - - /* Create file without bracket */ - - cl_git_mkfile("with_bracket/" FILE_WITHOUT_BRACKET, "I have no bracket in my name!\n"); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITHOUT_BRACKET)); - cl_assert(status_flags == GIT_STATUS_WT_NEW); - - cl_git_fail_with(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md"), GIT_ENOTFOUND); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); - cl_assert(status_flags == GIT_STATUS_INDEX_NEW); - - git_index_free(index); - git_repository_free(repo); -} - -void test_status_worktree_init__space_in_filename(void) -{ - git_repository *repo; - git_index *index; - status_entry_single result; - unsigned int status_flags; - -#define FILE_WITH_SPACE "LICENSE - copy.md" - - cl_set_cleanup(&cleanup_new_repo, "with_space"); - cl_git_pass(git_repository_init(&repo, "with_space", 0)); - cl_git_mkfile("with_space/" FILE_WITH_SPACE, "I have a space in my name\n"); - - /* file is new to working directory */ - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(1, result.count); - cl_assert(result.status == GIT_STATUS_WT_NEW); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); - cl_assert(status_flags == GIT_STATUS_WT_NEW); - - /* ignore the file */ - - cl_git_rewritefile("with_space/.gitignore", "*.md\n.gitignore\n"); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(2, result.count); - cl_assert(result.status == GIT_STATUS_IGNORED); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); - cl_assert(status_flags == GIT_STATUS_IGNORED); - - /* don't ignore the file */ - - cl_git_rewritefile("with_space/.gitignore", ".gitignore\n"); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(2, result.count); - cl_assert(result.status == GIT_STATUS_WT_NEW); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); - cl_assert(status_flags == GIT_STATUS_WT_NEW); - - /* add the file to the index */ - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, FILE_WITH_SPACE)); - cl_git_pass(git_index_write(index)); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(2, result.count); - cl_assert(result.status == GIT_STATUS_INDEX_NEW); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); - cl_assert(status_flags == GIT_STATUS_INDEX_NEW); - - git_index_free(index); - git_repository_free(repo); -} - -static int cb_status__expected_path(const char *p, unsigned int s, void *payload) -{ - const char *expected_path = (const char *)payload; - - GIT_UNUSED(s); - - if (payload == NULL) - cl_fail("Unexpected path"); - - cl_assert_equal_s(expected_path, p); - - return 0; -} - -void test_status_worktree_init__disable_pathspec_match(void) -{ - git_repository *repo; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - char *file_with_bracket = "LICENSE[1].md", - *imaginary_file_with_bracket = "LICENSE[1-2].md"; - - cl_set_cleanup(&cleanup_new_repo, "pathspec"); - cl_git_pass(git_repository_init(&repo, "pathspec", 0)); - cl_git_mkfile("pathspec/LICENSE[1].md", "screaming bracket\n"); - cl_git_mkfile("pathspec/LICENSE1.md", "no bracket\n"); - - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; - opts.pathspec.count = 1; - opts.pathspec.strings = &file_with_bracket; - - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__expected_path, - file_with_bracket) - ); - - /* Test passing a pathspec matching files in the workdir. */ - /* Must not match because pathspecs are disabled. */ - opts.pathspec.strings = &imaginary_file_with_bracket; - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__expected_path, NULL) - ); - - git_repository_free(repo); -} - -void test_status_worktree_init__new_staged_file_must_handle_crlf(void) -{ - git_repository *repo; - git_index *index; - unsigned int status; - - cl_set_cleanup(&cleanup_new_repo, "getting_started"); - cl_git_pass(git_repository_init(&repo, "getting_started", 0)); - - /* Ensure that repo has core.autocrlf=true */ - cl_repo_set_bool(repo, "core.autocrlf", true); - - cl_git_mkfile("getting_started/testfile.txt", "content\r\n"); /* Content with CRLF */ - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, "testfile.txt")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_status_file(&status, repo, "testfile.txt")); - cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status); - - git_index_free(index); - git_repository_free(repo); -} - diff --git a/tests/str/basic.c b/tests/str/basic.c deleted file mode 100644 index 5d2556805..000000000 --- a/tests/str/basic.c +++ /dev/null @@ -1,50 +0,0 @@ -#include "clar_libgit2.h" - -static const char *test_string = "Have you seen that? Have you seeeen that??"; - -void test_str_basic__resize(void) -{ - git_str buf1 = GIT_STR_INIT; - git_str_puts(&buf1, test_string); - cl_assert(git_str_oom(&buf1) == 0); - cl_assert_equal_s(git_str_cstr(&buf1), test_string); - - git_str_puts(&buf1, test_string); - cl_assert(strlen(git_str_cstr(&buf1)) == strlen(test_string) * 2); - git_str_dispose(&buf1); -} - -void test_str_basic__resize_incremental(void) -{ - git_str buf1 = GIT_STR_INIT; - - /* Presently, asking for 6 bytes will round up to 8. */ - cl_git_pass(git_str_puts(&buf1, "Hello")); - cl_assert_equal_i(5, buf1.size); - cl_assert_equal_i(8, buf1.asize); - - /* Ensure an additional byte does not realloc. */ - cl_git_pass(git_str_grow_by(&buf1, 1)); - cl_assert_equal_i(5, buf1.size); - cl_assert_equal_i(8, buf1.asize); - - /* But requesting many does. */ - cl_git_pass(git_str_grow_by(&buf1, 16)); - cl_assert_equal_i(5, buf1.size); - cl_assert(buf1.asize > 8); - - git_str_dispose(&buf1); -} - -void test_str_basic__printf(void) -{ - git_str buf2 = GIT_STR_INIT; - git_str_printf(&buf2, "%s %s %d ", "shoop", "da", 23); - cl_assert(git_str_oom(&buf2) == 0); - cl_assert_equal_s(git_str_cstr(&buf2), "shoop da 23 "); - - git_str_printf(&buf2, "%s %d", "woop", 42); - cl_assert(git_str_oom(&buf2) == 0); - cl_assert_equal_s(git_str_cstr(&buf2), "shoop da 23 woop 42"); - git_str_dispose(&buf2); -} diff --git a/tests/str/oom.c b/tests/str/oom.c deleted file mode 100644 index 3d59ead01..000000000 --- a/tests/str/oom.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "clar_libgit2.h" - -/* Override default allocators with ones that will fail predictably. */ - -static git_allocator std_alloc; -static git_allocator oom_alloc; - -static void *oom_malloc(size_t n, const char *file, int line) -{ - /* Reject any allocation of more than 100 bytes */ - return (n > 100) ? NULL : std_alloc.gmalloc(n, file, line); -} - -static void *oom_realloc(void *p, size_t n, const char *file, int line) -{ - /* Reject any allocation of more than 100 bytes */ - return (n > 100) ? NULL : std_alloc.grealloc(p, n, file, line); -} - -void test_str_oom__initialize(void) -{ - git_stdalloc_init_allocator(&std_alloc); - git_stdalloc_init_allocator(&oom_alloc); - - oom_alloc.gmalloc = oom_malloc; - oom_alloc.grealloc = oom_realloc; - - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ALLOCATOR, &oom_alloc)); -} - -void test_str_oom__cleanup(void) -{ - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ALLOCATOR, NULL)); -} - -void test_str_oom__grow(void) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_grow(&buf, 42)); - cl_assert(!git_str_oom(&buf)); - - cl_assert(git_str_grow(&buf, 101) == -1); - cl_assert(git_str_oom(&buf)); - - git_str_dispose(&buf); -} - -void test_str_oom__grow_by(void) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_grow_by(&buf, 42)); - cl_assert(!git_str_oom(&buf)); - - cl_assert(git_str_grow_by(&buf, 101) == -1); - cl_assert(git_str_oom(&buf)); -} diff --git a/tests/str/percent.c b/tests/str/percent.c deleted file mode 100644 index 339389075..000000000 --- a/tests/str/percent.c +++ /dev/null @@ -1,48 +0,0 @@ -#include "clar_libgit2.h" - -static void expect_decode_pass(const char *expected, const char *encoded) -{ - git_str in = GIT_STR_INIT, out = GIT_STR_INIT; - - /* - * ensure that we only read the given length of the input buffer - * by putting garbage at the end. this will ensure that we do - * not, eg, rely on nul-termination or walk off the end of the buf. - */ - cl_git_pass(git_str_puts(&in, encoded)); - cl_git_pass(git_str_PUTS(&in, "TRAILER")); - - cl_git_pass(git_str_decode_percent(&out, in.ptr, strlen(encoded))); - - cl_assert_equal_s(expected, git_str_cstr(&out)); - cl_assert_equal_i(strlen(expected), git_str_len(&out)); - - git_str_dispose(&in); - git_str_dispose(&out); -} - -void test_str_percent__decode_succeeds(void) -{ - expect_decode_pass("", ""); - expect_decode_pass(" ", "%20"); - expect_decode_pass("a", "a"); - expect_decode_pass(" a", "%20a"); - expect_decode_pass("a ", "a%20"); - expect_decode_pass("github.com", "github.com"); - expect_decode_pass("github.com", "githu%62.com"); - expect_decode_pass("github.com", "github%2ecom"); - expect_decode_pass("foo bar baz", "foo%20bar%20baz"); - expect_decode_pass("foo bar baz", "foo%20bar%20baz"); - expect_decode_pass("foo bar ", "foo%20bar%20"); -} - -void test_str_percent__ignores_invalid(void) -{ - expect_decode_pass("githu%%.com", "githu%%.com"); - expect_decode_pass("github.co%2", "github.co%2"); - expect_decode_pass("github%2.com", "github%2.com"); - expect_decode_pass("githu%2z.com", "githu%2z.com"); - expect_decode_pass("github.co%9z", "github.co%9z"); - expect_decode_pass("github.co%2", "github.co%2"); - expect_decode_pass("github.co%", "github.co%"); -} diff --git a/tests/str/quote.c b/tests/str/quote.c deleted file mode 100644 index 2c6546247..000000000 --- a/tests/str/quote.c +++ /dev/null @@ -1,87 +0,0 @@ -#include "clar_libgit2.h" - -static void expect_quote_pass(const char *expected, const char *str) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_puts(&buf, str)); - cl_git_pass(git_str_quote(&buf)); - - cl_assert_equal_s(expected, git_str_cstr(&buf)); - cl_assert_equal_i(strlen(expected), git_str_len(&buf)); - - git_str_dispose(&buf); -} - -void test_str_quote__quote_succeeds(void) -{ - expect_quote_pass("", ""); - expect_quote_pass("foo", "foo"); - expect_quote_pass("foo/bar/baz.c", "foo/bar/baz.c"); - expect_quote_pass("foo bar", "foo bar"); - expect_quote_pass("\"\\\"leading quote\"", "\"leading quote"); - expect_quote_pass("\"slash\\\\y\"", "slash\\y"); - expect_quote_pass("\"foo\\r\\nbar\"", "foo\r\nbar"); - expect_quote_pass("\"foo\\177bar\"", "foo\177bar"); - expect_quote_pass("\"foo\\001bar\"", "foo\001bar"); - expect_quote_pass("\"foo\\377bar\"", "foo\377bar"); -} - -static void expect_unquote_pass(const char *expected, const char *quoted) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_puts(&buf, quoted)); - cl_git_pass(git_str_unquote(&buf)); - - cl_assert_equal_s(expected, git_str_cstr(&buf)); - cl_assert_equal_i(strlen(expected), git_str_len(&buf)); - - git_str_dispose(&buf); -} - -static void expect_unquote_fail(const char *quoted) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_puts(&buf, quoted)); - cl_git_fail(git_str_unquote(&buf)); - - git_str_dispose(&buf); -} - -void test_str_quote__unquote_succeeds(void) -{ - expect_unquote_pass("", "\"\""); - expect_unquote_pass(" ", "\" \""); - expect_unquote_pass("foo", "\"foo\""); - expect_unquote_pass("foo bar", "\"foo bar\""); - expect_unquote_pass("foo\"bar", "\"foo\\\"bar\""); - expect_unquote_pass("foo\\bar", "\"foo\\\\bar\""); - expect_unquote_pass("foo\tbar", "\"foo\\tbar\""); - expect_unquote_pass("\vfoo\tbar\n", "\"\\vfoo\\tbar\\n\""); - expect_unquote_pass("foo\nbar", "\"foo\\012bar\""); - expect_unquote_pass("foo\r\nbar", "\"foo\\015\\012bar\""); - expect_unquote_pass("foo\r\nbar", "\"\\146\\157\\157\\015\\012\\142\\141\\162\""); - expect_unquote_pass("newline: \n", "\"newline: \\012\""); - expect_unquote_pass("0xff: \377", "\"0xff: \\377\""); -} - -void test_str_quote__unquote_fails(void) -{ - expect_unquote_fail("no quotes at all"); - expect_unquote_fail("\"no trailing quote"); - expect_unquote_fail("no leading quote\""); - expect_unquote_fail("\"invalid \\z escape char\""); - expect_unquote_fail("\"\\q invalid escape char\""); - expect_unquote_fail("\"invalid escape char \\p\""); - expect_unquote_fail("\"invalid \\1 escape char \""); - expect_unquote_fail("\"invalid \\14 escape char \""); - expect_unquote_fail("\"invalid \\280 escape char\""); - expect_unquote_fail("\"invalid \\378 escape char\""); - expect_unquote_fail("\"invalid \\380 escape char\""); - expect_unquote_fail("\"invalid \\411 escape char\""); - expect_unquote_fail("\"truncated escape char \\\""); - expect_unquote_fail("\"truncated escape char \\0\""); - expect_unquote_fail("\"truncated escape char \\01\""); -} diff --git a/tests/str/splice.c b/tests/str/splice.c deleted file mode 100644 index 14e844e2f..000000000 --- a/tests/str/splice.c +++ /dev/null @@ -1,92 +0,0 @@ -#include "clar_libgit2.h" - -static git_str _buf; - -void test_str_splice__initialize(void) { - git_str_init(&_buf, 16); -} - -void test_str_splice__cleanup(void) { - git_str_dispose(&_buf); -} - -void test_str_splice__preprend(void) -{ - git_str_sets(&_buf, "world!"); - - cl_git_pass(git_str_splice(&_buf, 0, 0, "Hello Dolly", strlen("Hello "))); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__append(void) -{ - git_str_sets(&_buf, "Hello"); - - cl_git_pass(git_str_splice(&_buf, git_str_len(&_buf), 0, " world!", strlen(" world!"))); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__insert_at(void) -{ - git_str_sets(&_buf, "Hell world!"); - - cl_git_pass(git_str_splice(&_buf, strlen("Hell"), 0, "o", strlen("o"))); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__remove_at(void) -{ - git_str_sets(&_buf, "Hello world of warcraft!"); - - cl_git_pass(git_str_splice(&_buf, strlen("Hello world"), strlen(" of warcraft"), "", 0)); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__replace(void) -{ - git_str_sets(&_buf, "Hell0 w0rld!"); - - cl_git_pass(git_str_splice(&_buf, strlen("Hell"), strlen("0 w0"), "o wo", strlen("o wo"))); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__replace_with_longer(void) -{ - git_str_sets(&_buf, "Hello you!"); - - cl_git_pass(git_str_splice(&_buf, strlen("Hello "), strlen("you"), "world", strlen("world"))); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__replace_with_shorter(void) -{ - git_str_sets(&_buf, "Brave new world!"); - - cl_git_pass(git_str_splice(&_buf, 0, strlen("Brave new"), "Hello", strlen("Hello"))); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__truncate(void) -{ - git_str_sets(&_buf, "Hello world!!"); - - cl_git_pass(git_str_splice(&_buf, strlen("Hello world!"), strlen("!"), "", 0)); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__dont_do_anything(void) -{ - git_str_sets(&_buf, "Hello world!"); - - cl_git_pass(git_str_splice(&_buf, 3, 0, "Hello", 0)); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} diff --git a/tests/stream/deprecated.c b/tests/stream/deprecated.c deleted file mode 100644 index fecd1be03..000000000 --- a/tests/stream/deprecated.c +++ /dev/null @@ -1,60 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/stream.h" -#include "streams/tls.h" -#include "streams/socket.h" -#include "stream.h" - -void test_stream_deprecated__cleanup(void) -{ - cl_git_pass(git_stream_register(GIT_STREAM_TLS | GIT_STREAM_STANDARD, NULL)); -} - -#ifndef GIT_DEPRECATE_HARD -static git_stream test_stream; -static int ctor_called; - -static int test_stream_init(git_stream **out, const char *host, const char *port) -{ - GIT_UNUSED(host); - GIT_UNUSED(port); - - ctor_called = 1; - *out = &test_stream; - - return 0; -} -#endif - -void test_stream_deprecated__register_tls(void) -{ -#ifndef GIT_DEPRECATE_HARD - git_stream *stream; - int error; - - ctor_called = 0; - cl_git_pass(git_stream_register_tls(test_stream_init)); - cl_git_pass(git_tls_stream_new(&stream, "localhost", "443")); - cl_assert_equal_i(1, ctor_called); - cl_assert_equal_p(&test_stream, stream); - - ctor_called = 0; - stream = NULL; - cl_git_pass(git_stream_register_tls(NULL)); - error = git_tls_stream_new(&stream, "localhost", "443"); - - /* - * We don't have TLS support enabled, or we're on Windows, - * which has no arbitrary TLS stream support. - */ -#if defined(GIT_WIN32) || !defined(GIT_HTTPS) - cl_git_fail_with(-1, error); -#else - cl_git_pass(error); -#endif - - cl_assert_equal_i(0, ctor_called); - cl_assert(&test_stream != stream); - - git_stream_free(stream); -#endif -} diff --git a/tests/stream/registration.c b/tests/stream/registration.c deleted file mode 100644 index bf3c20502..000000000 --- a/tests/stream/registration.c +++ /dev/null @@ -1,119 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/stream.h" -#include "streams/tls.h" -#include "streams/socket.h" -#include "stream.h" - -static git_stream test_stream; -static int ctor_called; - -void test_stream_registration__cleanup(void) -{ - cl_git_pass(git_stream_register(GIT_STREAM_TLS | GIT_STREAM_STANDARD, NULL)); -} - -static int test_stream_init(git_stream **out, const char *host, const char *port) -{ - GIT_UNUSED(host); - GIT_UNUSED(port); - - ctor_called = 1; - *out = &test_stream; - - return 0; -} - -static int test_stream_wrap(git_stream **out, git_stream *in, const char *host) -{ - GIT_UNUSED(in); - GIT_UNUSED(host); - - ctor_called = 1; - *out = &test_stream; - - return 0; -} - -void test_stream_registration__insecure(void) -{ - git_stream *stream; - git_stream_registration registration = {0}; - - registration.version = 1; - registration.init = test_stream_init; - registration.wrap = test_stream_wrap; - - ctor_called = 0; - cl_git_pass(git_stream_register(GIT_STREAM_STANDARD, ®istration)); - cl_git_pass(git_socket_stream_new(&stream, "localhost", "80")); - cl_assert_equal_i(1, ctor_called); - cl_assert_equal_p(&test_stream, stream); - - ctor_called = 0; - stream = NULL; - cl_git_pass(git_stream_register(GIT_STREAM_STANDARD, NULL)); - cl_git_pass(git_socket_stream_new(&stream, "localhost", "80")); - - cl_assert_equal_i(0, ctor_called); - cl_assert(&test_stream != stream); - - git_stream_free(stream); -} - -void test_stream_registration__tls(void) -{ - git_stream *stream; - git_stream_registration registration = {0}; - int error; - - registration.version = 1; - registration.init = test_stream_init; - registration.wrap = test_stream_wrap; - - ctor_called = 0; - cl_git_pass(git_stream_register(GIT_STREAM_TLS, ®istration)); - cl_git_pass(git_tls_stream_new(&stream, "localhost", "443")); - cl_assert_equal_i(1, ctor_called); - cl_assert_equal_p(&test_stream, stream); - - ctor_called = 0; - stream = NULL; - cl_git_pass(git_stream_register(GIT_STREAM_TLS, NULL)); - error = git_tls_stream_new(&stream, "localhost", "443"); - - /* We don't have TLS support enabled, or we're on Windows, - * which has no arbitrary TLS stream support. - */ -#if defined(GIT_WIN32) || !defined(GIT_HTTPS) - cl_git_fail_with(-1, error); -#else - cl_git_pass(error); -#endif - - cl_assert_equal_i(0, ctor_called); - cl_assert(&test_stream != stream); - - git_stream_free(stream); -} - -void test_stream_registration__both(void) -{ - git_stream *stream; - git_stream_registration registration = {0}; - - registration.version = 1; - registration.init = test_stream_init; - registration.wrap = test_stream_wrap; - - cl_git_pass(git_stream_register(GIT_STREAM_STANDARD | GIT_STREAM_TLS, ®istration)); - - ctor_called = 0; - cl_git_pass(git_tls_stream_new(&stream, "localhost", "443")); - cl_assert_equal_i(1, ctor_called); - cl_assert_equal_p(&test_stream, stream); - - ctor_called = 0; - cl_git_pass(git_socket_stream_new(&stream, "localhost", "80")); - cl_assert_equal_i(1, ctor_called); - cl_assert_equal_p(&test_stream, stream); -} diff --git a/tests/stress/diff.c b/tests/stress/diff.c deleted file mode 100644 index aecf08bbe..000000000 --- a/tests/stress/diff.c +++ /dev/null @@ -1,146 +0,0 @@ -#include "clar_libgit2.h" -#include "../diff/diff_helpers.h" - -static git_repository *g_repo = NULL; - -void test_stress_diff__initialize(void) -{ -} - -void test_stress_diff__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -#define ANOTHER_POEM \ -"OH, glorious are the guarded heights\nWhere guardian souls abide—\nSelf-exiled from our gross delights—\nAbove, beyond, outside:\nAn ampler arc their spirit swings—\nCommands a juster view—\nWe have their word for all these things,\nNo doubt their words are true.\n\nYet we, the bond slaves of our day,\nWhom dirt and danger press—\nCo-heirs of insolence, delay,\nAnd leagued unfaithfulness—\nSuch is our need must seek indeed\nAnd, having found, engage\nThe men who merely do the work\nFor which they draw the wage.\n\nFrom forge and farm and mine and bench,\nDeck, altar, outpost lone—\nMill, school, battalion, counter, trench,\nRail, senate, sheepfold, throne—\nCreation's cry goes up on high\nFrom age to cheated age:\n\"Send us the men who do the work\n\"For which they draw the wage!\"\n" - -static void test_with_many(int expected_new) -{ - git_index *index; - git_tree *tree, *new_tree; - git_diff *diff = NULL; - diff_expects exp; - git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; - git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass( - git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt")); - cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); - cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(expected_new + 2, exp.files); - - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(expected_new + 1, exp.files); - - git_diff_free(diff); - - cl_repo_commit_from_index(NULL, g_repo, NULL, 1372350000, "yoyoyo"); - cl_git_pass(git_revparse_single( - (git_object **)&new_tree, g_repo, "HEAD^{tree}")); - - cl_git_pass(git_diff_tree_to_tree( - &diff, g_repo, tree, new_tree, &diffopts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(expected_new + 2, exp.files); - - opts.flags = GIT_DIFF_FIND_ALL; - cl_git_pass(git_diff_find_similar(diff, &opts)); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); - cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(expected_new + 1, exp.files); - - git_diff_free(diff); - - git_tree_free(new_tree); - git_tree_free(tree); - git_index_free(index); -} - -void test_stress_diff__rename_big_files(void) -{ - git_index *index; - char tmp[64]; - int i, j; - git_str b = GIT_STR_INIT; - - g_repo = cl_git_sandbox_init("renames"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - for (i = 0; i < 100; i += 1) { - p_snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); - for (j = i * 256; j > 0; --j) - git_str_printf(&b, "more content %d\n", i); - cl_git_mkfile(tmp, b.ptr); - } - - for (i = 0; i < 100; i += 1) { - p_snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); - cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); - } - - git_str_dispose(&b); - git_index_free(index); - - test_with_many(100); -} - -void test_stress_diff__rename_many_files(void) -{ - git_index *index; - char tmp[64]; - int i; - git_str b = GIT_STR_INIT; - - g_repo = cl_git_sandbox_init("renames"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - git_str_printf(&b, "%08d\n" ANOTHER_POEM "%08d\n" ANOTHER_POEM ANOTHER_POEM, 0, 0); - - for (i = 0; i < 2500; i += 1) { - p_snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); - p_snprintf(b.ptr, 9, "%08d", i); - b.ptr[8] = '\n'; - cl_git_mkfile(tmp, b.ptr); - } - git_str_dispose(&b); - - for (i = 0; i < 2500; i += 1) { - p_snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i); - cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/"))); - } - - git_index_free(index); - - test_with_many(2500); -} diff --git a/tests/submodule/add.c b/tests/submodule/add.c deleted file mode 100644 index ae5507d7f..000000000 --- a/tests/submodule/add.c +++ /dev/null @@ -1,251 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "path.h" -#include "submodule_helpers.h" -#include "config/config_helpers.h" -#include "futils.h" -#include "repository.h" -#include "git2/sys/commit.h" - -static git_repository *g_repo = NULL; -static const char *valid_blob_id = "fa49b077972391ad58037050f2a75f74e3671e92"; - -void test_submodule_add__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void assert_submodule_url(const char* name, const char *url) -{ - git_str key = GIT_STR_INIT; - - - cl_git_pass(git_str_printf(&key, "submodule.%s.url", name)); - assert_config_entry_value(g_repo, git_str_cstr(&key), url); - - git_str_dispose(&key); -} - -void test_submodule_add__url_absolute(void) -{ - git_submodule *sm; - git_repository *repo; - git_str dot_git_content = GIT_STR_INIT; - - g_repo = setup_fixture_submod2(); - - /* re-add existing submodule */ - cl_git_fail_with( - GIT_EEXISTS, - git_submodule_add_setup(NULL, g_repo, "whatever", "sm_unchanged", 1)); - - /* add a submodule using a gitlink */ - - cl_git_pass( - git_submodule_add_setup(&sm, g_repo, "https://github.com/libgit2/libgit2.git", "sm_libgit2", 1) - ); - git_submodule_free(sm); - - cl_assert(git_fs_path_isfile("submod2/" "sm_libgit2" "/.git")); - - cl_assert(git_fs_path_isdir("submod2/.git/modules")); - cl_assert(git_fs_path_isdir("submod2/.git/modules/" "sm_libgit2")); - cl_assert(git_fs_path_isfile("submod2/.git/modules/" "sm_libgit2" "/HEAD")); - assert_submodule_url("sm_libgit2", "https://github.com/libgit2/libgit2.git"); - - cl_git_pass(git_repository_open(&repo, "submod2/" "sm_libgit2")); - - /* Verify worktree path is relative */ - assert_config_entry_value(repo, "core.worktree", "../../../sm_libgit2/"); - - /* Verify gitdir path is relative */ - cl_git_pass(git_futils_readbuffer(&dot_git_content, "submod2/" "sm_libgit2" "/.git")); - cl_assert_equal_s("gitdir: ../.git/modules/sm_libgit2/", dot_git_content.ptr); - - git_repository_free(repo); - git_str_dispose(&dot_git_content); - - /* add a submodule not using a gitlink */ - - cl_git_pass( - git_submodule_add_setup(&sm, g_repo, "https://github.com/libgit2/libgit2.git", "sm_libgit2b", 0) - ); - git_submodule_free(sm); - - cl_assert(git_fs_path_isdir("submod2/" "sm_libgit2b" "/.git")); - cl_assert(git_fs_path_isfile("submod2/" "sm_libgit2b" "/.git/HEAD")); - cl_assert(!git_fs_path_exists("submod2/.git/modules/" "sm_libgit2b")); - assert_submodule_url("sm_libgit2b", "https://github.com/libgit2/libgit2.git"); -} - -void test_submodule_add__url_relative(void) -{ - git_submodule *sm; - git_remote *remote; - git_strarray problems = {0}; - - /* default remote url is https://github.com/libgit2/false.git */ - g_repo = cl_git_sandbox_init("testrepo2"); - - /* make sure we don't default to origin - rename origin -> test_remote */ - cl_git_pass(git_remote_rename(&problems, g_repo, "origin", "test_remote")); - cl_assert_equal_i(0, problems.count); - git_strarray_dispose(&problems); - cl_git_fail(git_remote_lookup(&remote, g_repo, "origin")); - - cl_git_pass( - git_submodule_add_setup(&sm, g_repo, "../TestGitRepository", "TestGitRepository", 1) - ); - git_submodule_free(sm); - - assert_submodule_url("TestGitRepository", "https://github.com/libgit2/TestGitRepository"); -} - -void test_submodule_add__url_relative_to_origin(void) -{ - git_submodule *sm; - - /* default remote url is https://github.com/libgit2/false.git */ - g_repo = cl_git_sandbox_init("testrepo2"); - - cl_git_pass( - git_submodule_add_setup(&sm, g_repo, "../TestGitRepository", "TestGitRepository", 1) - ); - git_submodule_free(sm); - - assert_submodule_url("TestGitRepository", "https://github.com/libgit2/TestGitRepository"); -} - -void test_submodule_add__url_relative_to_workdir(void) -{ - git_submodule *sm; - - /* In this repo, HEAD (master) has no remote tracking branc h*/ - g_repo = cl_git_sandbox_init("testrepo"); - - cl_git_pass( - git_submodule_add_setup(&sm, g_repo, "./", "TestGitRepository", 1) - ); - git_submodule_free(sm); - - assert_submodule_url("TestGitRepository", git_repository_workdir(g_repo)); -} - -static void test_add_entry( - git_index *index, - const char *idstr, - const char *path, - git_filemode_t mode) -{ - git_index_entry entry = {{0}}; - - cl_git_pass(git_oid_fromstr(&entry.id, idstr)); - - entry.path = path; - entry.mode = mode; - - cl_git_pass(git_index_add(index, &entry)); -} - -void test_submodule_add__path_exists_in_index(void) -{ - git_index *index; - git_submodule *sm; - git_str filename = GIT_STR_INIT; - - g_repo = cl_git_sandbox_init("testrepo"); - - cl_git_pass(git_str_joinpath(&filename, "subdirectory", "test.txt")); - - cl_git_pass(git_repository_index__weakptr(&index, g_repo)); - - test_add_entry(index, valid_blob_id, filename.ptr, GIT_FILEMODE_BLOB); - - cl_git_fail_with(git_submodule_add_setup(&sm, g_repo, "./", "subdirectory", 1), GIT_EEXISTS); - - git_submodule_free(sm); - git_str_dispose(&filename); -} - -void test_submodule_add__file_exists_in_index(void) -{ - git_index *index; - git_submodule *sm; - git_str name = GIT_STR_INIT; - - g_repo = cl_git_sandbox_init("testrepo"); - - cl_git_pass(git_repository_index__weakptr(&index, g_repo)); - - test_add_entry(index, valid_blob_id, "subdirectory", GIT_FILEMODE_BLOB); - - cl_git_fail_with(git_submodule_add_setup(&sm, g_repo, "./", "subdirectory", 1), GIT_EEXISTS); - - git_submodule_free(sm); - git_str_dispose(&name); -} - -void test_submodule_add__submodule_clone(void) -{ - git_oid tree_id, commit_id; - git_signature *sig; - git_submodule *sm; - git_index *index; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - /* Create the submodule structure, clone into it and finalize */ - cl_git_pass(git_submodule_add_setup(&sm, g_repo, cl_fixture("testrepo.git"), "testrepo-add", true)); - cl_git_pass(git_submodule_clone(NULL, sm, NULL)); - cl_git_pass(git_submodule_add_finalize(sm)); - - /* Create the submodule commit */ - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_write_tree(&tree_id, index)); - cl_git_pass(git_signature_now(&sig, "Submoduler", "submoduler@local")); - cl_git_pass(git_commit_create_from_ids(&commit_id, g_repo, "HEAD", sig, sig, NULL, "A submodule\n", - &tree_id, 0, NULL)); - - assert_submodule_exists(g_repo, "testrepo-add"); - - git_signature_free(sig); - git_submodule_free(sm); - git_index_free(index); -} - -void test_submodule_add__submodule_clone_into_nonempty_dir_succeeds(void) -{ - git_submodule *sm; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_git_pass(p_mkdir("empty_standard_repo/sm", 0777)); - cl_git_mkfile("empty_standard_repo/sm/foobar", ""); - - /* Create the submodule structure, clone into it and finalize */ - cl_git_pass(git_submodule_add_setup(&sm, g_repo, cl_fixture("testrepo.git"), "sm", true)); - cl_git_pass(git_submodule_clone(NULL, sm, NULL)); - cl_git_pass(git_submodule_add_finalize(sm)); - - cl_assert(git_fs_path_exists("empty_standard_repo/sm/foobar")); - - assert_submodule_exists(g_repo, "sm"); - - git_submodule_free(sm); -} - -void test_submodule_add__submodule_clone_twice_fails(void) -{ - git_submodule *sm; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - /* Create the submodule structure, clone into it and finalize */ - cl_git_pass(git_submodule_add_setup(&sm, g_repo, cl_fixture("testrepo.git"), "sm", true)); - cl_git_pass(git_submodule_clone(NULL, sm, NULL)); - cl_git_pass(git_submodule_add_finalize(sm)); - - cl_git_fail(git_submodule_clone(NULL, sm, NULL)); - - git_submodule_free(sm); -} diff --git a/tests/submodule/escape.c b/tests/submodule/escape.c deleted file mode 100644 index bcd52b510..000000000 --- a/tests/submodule/escape.c +++ /dev/null @@ -1,98 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "path.h" -#include "submodule_helpers.h" -#include "futils.h" -#include "repository.h" - -static git_repository *g_repo = NULL; - -void test_submodule_escape__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -#define EVIL_SM_NAME "../../modules/evil" -#define EVIL_SM_NAME_WINDOWS "..\\\\..\\\\modules\\\\evil" -#define EVIL_SM_NAME_WINDOWS_UNESC "..\\..\\modules\\evil" - -static int find_evil(git_submodule *sm, const char *name, void *payload) -{ - int *foundit = (int *) payload; - - GIT_UNUSED(sm); - - if (!git__strcmp(EVIL_SM_NAME, name) || - !git__strcmp(EVIL_SM_NAME_WINDOWS_UNESC, name)) - *foundit = true; - - return 0; -} - -void test_submodule_escape__from_gitdir(void) -{ - int foundit; - git_submodule *sm; - git_str buf = GIT_STR_INIT; - unsigned int sm_location; - - g_repo = setup_fixture_submodule_simple(); - - cl_git_pass(git_str_joinpath(&buf, git_repository_workdir(g_repo), ".gitmodules")); - cl_git_rewritefile(buf.ptr, - "[submodule \"" EVIL_SM_NAME "\"]\n" - " path = testrepo\n" - " url = ../testrepo.git\n"); - git_str_dispose(&buf); - - /* Find it all the different ways we know about it */ - foundit = 0; - cl_git_pass(git_submodule_foreach(g_repo, find_evil, &foundit)); - cl_assert_equal_i(0, foundit); - cl_git_fail_with(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, EVIL_SM_NAME)); - /* - * We do know about this as it's in the index and HEAD, but the data is - * incomplete as there is no configured data for it (we pretend it - * doesn't exist). This leaves us with an odd situation but it's - * consistent with what we would do if we did add a submodule with no - * configuration. - */ - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - cl_git_pass(git_submodule_location(&sm_location, sm)); - cl_assert_equal_i(GIT_SUBMODULE_STATUS_IN_INDEX | GIT_SUBMODULE_STATUS_IN_HEAD, sm_location); - git_submodule_free(sm); -} - -void test_submodule_escape__from_gitdir_windows(void) -{ - int foundit; - git_submodule *sm; - git_str buf = GIT_STR_INIT; - unsigned int sm_location; - - g_repo = setup_fixture_submodule_simple(); - - cl_git_pass(git_str_joinpath(&buf, git_repository_workdir(g_repo), ".gitmodules")); - cl_git_rewritefile(buf.ptr, - "[submodule \"" EVIL_SM_NAME_WINDOWS "\"]\n" - " path = testrepo\n" - " url = ../testrepo.git\n"); - git_str_dispose(&buf); - - /* Find it all the different ways we know about it */ - foundit = 0; - cl_git_pass(git_submodule_foreach(g_repo, find_evil, &foundit)); - cl_assert_equal_i(0, foundit); - cl_git_fail_with(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, EVIL_SM_NAME_WINDOWS_UNESC)); - /* - * We do know about this as it's in the index and HEAD, but the data is - * incomplete as there is no configured data for it (we pretend it - * doesn't exist). This leaves us with an odd situation but it's - * consistent with what we would do if we did add a submodule with no - * configuration. - */ - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - cl_git_pass(git_submodule_location(&sm_location, sm)); - cl_assert_equal_i(GIT_SUBMODULE_STATUS_IN_INDEX | GIT_SUBMODULE_STATUS_IN_HEAD, sm_location); - git_submodule_free(sm); -} diff --git a/tests/submodule/init.c b/tests/submodule/init.c deleted file mode 100644 index a8e1291c4..000000000 --- a/tests/submodule/init.c +++ /dev/null @@ -1,115 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "path.h" -#include "submodule_helpers.h" -#include "futils.h" - -static git_repository *g_repo = NULL; - -void test_submodule_init__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_submodule_init__absolute_url(void) -{ - git_submodule *sm; - git_config *cfg; - git_str absolute_url = GIT_STR_INIT; - const char *config_url; - - g_repo = setup_fixture_submodule_simple(); - - cl_assert(git_fs_path_dirname_r(&absolute_url, git_repository_workdir(g_repo)) > 0); - cl_git_pass(git_str_joinpath(&absolute_url, absolute_url.ptr, "testrepo.git")); - - /* write the absolute url to the .gitmodules file*/ - cl_git_pass(git_submodule_set_url(g_repo, "testrepo", absolute_url.ptr)); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - - /* verify that the .gitmodules is set with an absolute path*/ - cl_assert_equal_s(absolute_url.ptr, git_submodule_url(sm)); - - /* init and verify that absolute path is written to .git/config */ - cl_git_pass(git_submodule_init(sm, false)); - - cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); - - cl_git_pass(git_config_get_string(&config_url, cfg, "submodule.testrepo.url")); - cl_assert_equal_s(absolute_url.ptr, config_url); - - git_str_dispose(&absolute_url); - git_config_free(cfg); - git_submodule_free(sm); -} - -void test_submodule_init__relative_url(void) -{ - git_submodule *sm; - git_config *cfg; - git_str absolute_url = GIT_STR_INIT; - const char *config_url; - - g_repo = setup_fixture_submodule_simple(); - - cl_assert(git_fs_path_dirname_r(&absolute_url, git_repository_workdir(g_repo)) > 0); - cl_git_pass(git_str_joinpath(&absolute_url, absolute_url.ptr, "testrepo.git")); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - - /* verify that the .gitmodules is set with an absolute path*/ - cl_assert_equal_s("../testrepo.git", git_submodule_url(sm)); - - /* init and verify that absolute path is written to .git/config */ - cl_git_pass(git_submodule_init(sm, false)); - - cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); - - cl_git_pass(git_config_get_string(&config_url, cfg, "submodule.testrepo.url")); - cl_assert_equal_s(absolute_url.ptr, config_url); - - git_str_dispose(&absolute_url); - git_config_free(cfg); - git_submodule_free(sm); -} - -void test_submodule_init__relative_url_detached_head(void) -{ - git_submodule *sm; - git_config *cfg; - git_str absolute_url = GIT_STR_INIT; - const char *config_url; - git_reference *head_ref = NULL; - git_object *head_commit = NULL; - - g_repo = setup_fixture_submodule_simple(); - - /* Put the parent repository into a detached head state. */ - cl_git_pass(git_repository_head(&head_ref, g_repo)); - cl_git_pass(git_reference_peel(&head_commit, head_ref, GIT_OBJECT_COMMIT)); - - cl_git_pass(git_repository_set_head_detached(g_repo, git_commit_id((git_commit *)head_commit))); - - cl_assert(git_fs_path_dirname_r(&absolute_url, git_repository_workdir(g_repo)) > 0); - cl_git_pass(git_str_joinpath(&absolute_url, absolute_url.ptr, "testrepo.git")); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - - /* verify that the .gitmodules is set with an absolute path*/ - cl_assert_equal_s("../testrepo.git", git_submodule_url(sm)); - - /* init and verify that absolute path is written to .git/config */ - cl_git_pass(git_submodule_init(sm, false)); - - cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); - - cl_git_pass(git_config_get_string(&config_url, cfg, "submodule.testrepo.url")); - cl_assert_equal_s(absolute_url.ptr, config_url); - - git_str_dispose(&absolute_url); - git_config_free(cfg); - git_object_free(head_commit); - git_reference_free(head_ref); - git_submodule_free(sm); -} diff --git a/tests/submodule/inject_option.c b/tests/submodule/inject_option.c deleted file mode 100644 index e28ff8489..000000000 --- a/tests/submodule/inject_option.c +++ /dev/null @@ -1,80 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "path.h" -#include "submodule_helpers.h" -#include "futils.h" -#include "repository.h" - -static git_repository *g_repo = NULL; - -void test_submodule_inject_option__initialize(void) -{ - g_repo = setup_fixture_submodule_simple(); -} - -void test_submodule_inject_option__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int find_naughty(git_submodule *sm, const char *name, void *payload) -{ - int *foundit = (int *) payload; - - GIT_UNUSED(sm); - - if (!git__strcmp("naughty", name)) - *foundit = true; - - return 0; -} - -void test_submodule_inject_option__url(void) -{ - int foundit; - git_submodule *sm; - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&buf, git_repository_workdir(g_repo), ".gitmodules")); - cl_git_rewritefile(buf.ptr, - "[submodule \"naughty\"]\n" - " path = testrepo\n" - " url = -u./payload\n"); - git_str_dispose(&buf); - - /* We do want to find it, but with the appropriate field empty */ - foundit = 0; - cl_git_pass(git_submodule_foreach(g_repo, find_naughty, &foundit)); - cl_assert_equal_i(1, foundit); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "naughty")); - cl_assert_equal_s("testrepo", git_submodule_path(sm)); - cl_assert_equal_p(NULL, git_submodule_url(sm)); - - git_submodule_free(sm); -} - -void test_submodule_inject_option__path(void) -{ - int foundit; - git_submodule *sm; - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&buf, git_repository_workdir(g_repo), ".gitmodules")); - cl_git_rewritefile(buf.ptr, - "[submodule \"naughty\"]\n" - " path = --something\n" - " url = blah.git\n"); - git_str_dispose(&buf); - - /* We do want to find it, but with the appropriate field empty */ - foundit = 0; - cl_git_pass(git_submodule_foreach(g_repo, find_naughty, &foundit)); - cl_assert_equal_i(1, foundit); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "naughty")); - cl_assert_equal_s("naughty", git_submodule_path(sm)); - cl_assert_equal_s("blah.git", git_submodule_url(sm)); - - git_submodule_free(sm); -} diff --git a/tests/submodule/lookup.c b/tests/submodule/lookup.c deleted file mode 100644 index acfdc838c..000000000 --- a/tests/submodule/lookup.c +++ /dev/null @@ -1,516 +0,0 @@ -#include "clar_libgit2.h" -#include "submodule_helpers.h" -#include "git2/sys/repository.h" -#include "repository.h" -#include "futils.h" - -static git_repository *g_repo = NULL; - -void test_submodule_lookup__initialize(void) -{ - g_repo = setup_fixture_submod2(); -} - -void test_submodule_lookup__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_submodule_lookup__simple_lookup(void) -{ - assert_submodule_exists(g_repo, "sm_unchanged"); - - /* lookup pending change in .gitmodules that is not in HEAD */ - assert_submodule_exists(g_repo, "sm_added_and_uncommited"); - - /* lookup pending change in .gitmodules that is not in HEAD nor index */ - assert_submodule_exists(g_repo, "sm_gitmodules_only"); - - /* lookup git repo subdir that is not added as submodule */ - refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS); - - /* lookup existing directory that is not a submodule */ - refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND); - - /* lookup existing file that is not a submodule */ - refute_submodule_exists(g_repo, "just_a_file", GIT_ENOTFOUND); - - /* lookup non-existent item */ - refute_submodule_exists(g_repo, "no_such_file", GIT_ENOTFOUND); - - /* lookup a submodule by path with a trailing slash */ - assert_submodule_exists(g_repo, "sm_added_and_uncommited/"); -} - -void test_submodule_lookup__can_be_dupped(void) -{ - git_submodule *sm; - git_submodule *sm_duplicate; - const char *oid = "480095882d281ed676fe5b863569520e54a7d5c0"; - - /* Check original */ - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); - cl_assert(git_submodule_owner(sm) == g_repo); - cl_assert_equal_s("sm_unchanged", git_submodule_name(sm)); - cl_assert(git__suffixcmp(git_submodule_path(sm), "sm_unchanged") == 0); - cl_assert(git__suffixcmp(git_submodule_url(sm), "/submod2_target") == 0); - - cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0); - cl_assert(git_oid_streq(git_submodule_head_id(sm), oid) == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), oid) == 0); - - cl_assert(git_submodule_ignore(sm) == GIT_SUBMODULE_IGNORE_NONE); - cl_assert(git_submodule_update_strategy(sm) == GIT_SUBMODULE_UPDATE_CHECKOUT); - - /* Duplicate and free original */ - cl_assert(git_submodule_dup(&sm_duplicate, sm) == 0); - git_submodule_free(sm); - - /* Check duplicate */ - cl_assert(git_submodule_owner(sm_duplicate) == g_repo); - cl_assert_equal_s("sm_unchanged", git_submodule_name(sm_duplicate)); - cl_assert(git__suffixcmp(git_submodule_path(sm_duplicate), "sm_unchanged") == 0); - cl_assert(git__suffixcmp(git_submodule_url(sm_duplicate), "/submod2_target") == 0); - - cl_assert(git_oid_streq(git_submodule_index_id(sm_duplicate), oid) == 0); - cl_assert(git_oid_streq(git_submodule_head_id(sm_duplicate), oid) == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm_duplicate), oid) == 0); - - cl_assert(git_submodule_ignore(sm_duplicate) == GIT_SUBMODULE_IGNORE_NONE); - cl_assert(git_submodule_update_strategy(sm_duplicate) == GIT_SUBMODULE_UPDATE_CHECKOUT); - - git_submodule_free(sm_duplicate); -} - -void test_submodule_lookup__accessors(void) -{ - git_submodule *sm; - const char *oid = "480095882d281ed676fe5b863569520e54a7d5c0"; - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); - cl_assert(git_submodule_owner(sm) == g_repo); - cl_assert_equal_s("sm_unchanged", git_submodule_name(sm)); - cl_assert(git__suffixcmp(git_submodule_path(sm), "sm_unchanged") == 0); - cl_assert(git__suffixcmp(git_submodule_url(sm), "/submod2_target") == 0); - - cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0); - cl_assert(git_oid_streq(git_submodule_head_id(sm), oid) == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), oid) == 0); - - cl_assert(git_submodule_ignore(sm) == GIT_SUBMODULE_IGNORE_NONE); - cl_assert(git_submodule_update_strategy(sm) == GIT_SUBMODULE_UPDATE_CHECKOUT); - - git_submodule_free(sm); - - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); - cl_assert_equal_s("sm_changed_head", git_submodule_name(sm)); - - cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0); - cl_assert(git_oid_streq(git_submodule_head_id(sm), oid) == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), - "3d9386c507f6b093471a3e324085657a3c2b4247") == 0); - - git_submodule_free(sm); - - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); - cl_assert_equal_s("sm_added_and_uncommited", git_submodule_name(sm)); - - cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0); - cl_assert(git_submodule_head_id(sm) == NULL); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), oid) == 0); - - git_submodule_free(sm); - - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits")); - cl_assert_equal_s("sm_missing_commits", git_submodule_name(sm)); - - cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0); - cl_assert(git_oid_streq(git_submodule_head_id(sm), oid) == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), - "5e4963595a9774b90524d35a807169049de8ccad") == 0); - - git_submodule_free(sm); -} - -typedef struct { - int count; -} sm_lookup_data; - -static int sm_lookup_cb(git_submodule *sm, const char *name, void *payload) -{ - sm_lookup_data *data = payload; - data->count += 1; - cl_assert_equal_s(git_submodule_name(sm), name); - return 0; -} - -void test_submodule_lookup__foreach(void) -{ - git_config *cfg; - sm_lookup_data data; - - memset(&data, 0, sizeof(data)); - cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); - cl_assert_equal_i(8, data.count); - - memset(&data, 0, sizeof(data)); - - /* Change the path for a submodule so it doesn't match the name */ - cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.gitmodules")); - - cl_git_pass(git_config_set_string(cfg, "submodule.smchangedindex.path", "sm_changed_index")); - cl_git_pass(git_config_set_string(cfg, "submodule.smchangedindex.url", "../submod2_target")); - cl_git_pass(git_config_delete_entry(cfg, "submodule.sm_changed_index.path")); - cl_git_pass(git_config_delete_entry(cfg, "submodule.sm_changed_index.url")); - - git_config_free(cfg); - - cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); - cl_assert_equal_i(8, data.count); -} - -static int foreach_cb(git_submodule *sm, const char *name, void *payload) -{ - GIT_UNUSED(sm); - GIT_UNUSED(name); - GIT_UNUSED(payload); - return 0; -} - -void test_submodule_lookup__duplicated_path(void) -{ - cl_git_rewritefile("submod2/.gitmodules", - "[submodule \"sm1\"]\n" - " path = duplicated-path\n" - " url = sm1\n" - "[submodule \"sm2\"]\n" - " path = duplicated-path\n" - " url = sm2\n"); - - cl_git_fail(git_submodule_foreach(g_repo, foreach_cb, NULL)); -} - -void test_submodule_lookup__lookup_even_with_unborn_head(void) -{ - git_reference *head; - - /* put us on an unborn branch */ - cl_git_pass(git_reference_symbolic_create( - &head, g_repo, "HEAD", "refs/heads/garbage", 1, NULL)); - git_reference_free(head); - - test_submodule_lookup__simple_lookup(); /* baseline should still pass */ -} - -void test_submodule_lookup__lookup_even_with_missing_index(void) -{ - git_index *idx; - - /* give the repo an empty index */ - cl_git_pass(git_index_new(&idx)); - git_repository_set_index(g_repo, idx); - git_index_free(idx); - - test_submodule_lookup__simple_lookup(); /* baseline should still pass */ -} - -void test_submodule_lookup__backslashes(void) -{ - git_config *cfg; - git_submodule *sm; - git_repository *subrepo; - git_buf buf = GIT_BUF_INIT; - const char *backslashed_path = "..\\submod2_target"; - - cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.gitmodules")); - cl_git_pass(git_config_set_string(cfg, "submodule.sm_unchanged.url", backslashed_path)); - git_config_free(cfg); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); - cl_assert_equal_s(backslashed_path, git_submodule_url(sm)); - cl_git_pass(git_submodule_open(&subrepo, sm)); - - cl_git_pass(git_submodule_resolve_url(&buf, g_repo, backslashed_path)); - - git_buf_dispose(&buf); - git_submodule_free(sm); - git_repository_free(subrepo); -} - -static void baseline_tests(void) -{ - /* small baseline that should work even if we change the index or make - * commits from the index - */ - assert_submodule_exists(g_repo, "sm_unchanged"); - assert_submodule_exists(g_repo, "sm_gitmodules_only"); - refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS); -} - -static void add_submodule_with_commit(const char *name) -{ - git_submodule *sm; - git_repository *smrepo; - git_index *idx; - git_str p = GIT_STR_INIT; - - cl_git_pass(git_submodule_add_setup(&sm, g_repo, - "https://github.com/libgit2/libgit2.git", name, 1)); - - assert_submodule_exists(g_repo, name); - - cl_git_pass(git_submodule_open(&smrepo, sm)); - cl_git_pass(git_repository_index(&idx, smrepo)); - - cl_git_pass(git_str_joinpath(&p, git_repository_workdir(smrepo), "file")); - cl_git_mkfile(p.ptr, "new file"); - git_str_dispose(&p); - - cl_git_pass(git_index_add_bypath(idx, "file")); - cl_git_pass(git_index_write(idx)); - git_index_free(idx); - - cl_repo_commit_from_index(NULL, smrepo, NULL, 0, "initial commit"); - git_repository_free(smrepo); - - cl_git_pass(git_submodule_add_finalize(sm)); - - git_submodule_free(sm); -} - -void test_submodule_lookup__just_added(void) -{ - git_submodule *sm; - git_str snap1 = GIT_STR_INIT, snap2 = GIT_STR_INIT; - git_reference *original_head = NULL; - - refute_submodule_exists(g_repo, "sm_just_added", GIT_ENOTFOUND); - refute_submodule_exists(g_repo, "sm_just_added_2", GIT_ENOTFOUND); - refute_submodule_exists(g_repo, "sm_just_added_idx", GIT_ENOTFOUND); - refute_submodule_exists(g_repo, "sm_just_added_head", GIT_ENOTFOUND); - refute_submodule_exists(g_repo, "mismatch_name", GIT_ENOTFOUND); - refute_submodule_exists(g_repo, "mismatch_path", GIT_ENOTFOUND); - baseline_tests(); - - cl_git_pass(git_futils_readbuffer(&snap1, "submod2/.gitmodules")); - cl_git_pass(git_repository_head(&original_head, g_repo)); - - cl_git_pass(git_submodule_add_setup(&sm, g_repo, - "https://github.com/libgit2/libgit2.git", "sm_just_added", 1)); - git_submodule_free(sm); - assert_submodule_exists(g_repo, "sm_just_added"); - - cl_git_pass(git_submodule_add_setup(&sm, g_repo, - "https://github.com/libgit2/libgit2.git", "sm_just_added_2", 1)); - assert_submodule_exists(g_repo, "sm_just_added_2"); - cl_git_fail(git_submodule_add_finalize(sm)); /* fails if no HEAD */ - git_submodule_free(sm); - - add_submodule_with_commit("sm_just_added_head"); - cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "commit new sm to head"); - assert_submodule_exists(g_repo, "sm_just_added_head"); - - add_submodule_with_commit("sm_just_added_idx"); - assert_submodule_exists(g_repo, "sm_just_added_idx"); - - cl_git_pass(git_futils_readbuffer(&snap2, "submod2/.gitmodules")); - - cl_git_append2file( - "submod2/.gitmodules", - "\n[submodule \"mismatch_name\"]\n" - "\tpath = mismatch_path\n" - "\turl = https://example.com/example.git\n\n"); - - assert_submodule_exists(g_repo, "mismatch_name"); - assert_submodule_exists(g_repo, "mismatch_path"); - assert_submodule_exists(g_repo, "sm_just_added"); - assert_submodule_exists(g_repo, "sm_just_added_2"); - assert_submodule_exists(g_repo, "sm_just_added_idx"); - assert_submodule_exists(g_repo, "sm_just_added_head"); - baseline_tests(); - - cl_git_rewritefile("submod2/.gitmodules", snap2.ptr); - git_str_dispose(&snap2); - - refute_submodule_exists(g_repo, "mismatch_name", GIT_ENOTFOUND); - refute_submodule_exists(g_repo, "mismatch_path", GIT_ENOTFOUND); - assert_submodule_exists(g_repo, "sm_just_added"); - assert_submodule_exists(g_repo, "sm_just_added_2"); - assert_submodule_exists(g_repo, "sm_just_added_idx"); - assert_submodule_exists(g_repo, "sm_just_added_head"); - baseline_tests(); - - cl_git_rewritefile("submod2/.gitmodules", snap1.ptr); - git_str_dispose(&snap1); - - refute_submodule_exists(g_repo, "mismatch_name", GIT_ENOTFOUND); - refute_submodule_exists(g_repo, "mismatch_path", GIT_ENOTFOUND); - /* note error code change, because add_setup made a repo in the workdir */ - refute_submodule_exists(g_repo, "sm_just_added", GIT_EEXISTS); - refute_submodule_exists(g_repo, "sm_just_added_2", GIT_EEXISTS); - /* these still exist in index and head respectively */ - assert_submodule_exists(g_repo, "sm_just_added_idx"); - assert_submodule_exists(g_repo, "sm_just_added_head"); - baseline_tests(); - - { - git_index *idx; - cl_git_pass(git_repository_index(&idx, g_repo)); - cl_git_pass(git_index_remove_bypath(idx, "sm_just_added_idx")); - cl_git_pass(git_index_remove_bypath(idx, "sm_just_added_head")); - cl_git_pass(git_index_write(idx)); - git_index_free(idx); - } - - refute_submodule_exists(g_repo, "sm_just_added_idx", GIT_EEXISTS); - assert_submodule_exists(g_repo, "sm_just_added_head"); - - { - cl_git_pass(git_reference_create(NULL, g_repo, "refs/heads/master", git_reference_target(original_head), 1, "move head back")); - git_reference_free(original_head); - } - - refute_submodule_exists(g_repo, "sm_just_added_head", GIT_EEXISTS); -} - -/* Test_App and Test_App2 are fairly similar names, make sure we load the right one */ -void test_submodule_lookup__prefix_name(void) -{ - git_submodule *sm; - - cl_git_rewritefile("submod2/.gitmodules", - "[submodule \"Test_App\"]\n" - " path = Test_App\n" - " url = ../Test_App\n" - "[submodule \"Test_App2\"]\n" - " path = Test_App2\n" - " url = ../Test_App\n"); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "Test_App")); - cl_assert_equal_s("Test_App", git_submodule_name(sm)); - - git_submodule_free(sm); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "Test_App2")); - cl_assert_equal_s("Test_App2", git_submodule_name(sm)); - - git_submodule_free(sm); -} - -void test_submodule_lookup__renamed(void) -{ - const char *newpath = "sm_actually_changed"; - git_index *idx; - sm_lookup_data data; - - cl_git_pass(git_repository_index__weakptr(&idx, g_repo)); - - /* We're replicating 'git mv sm_unchanged sm_actually_changed' in this test */ - - cl_git_pass(p_rename("submod2/sm_unchanged", "submod2/sm_actually_changed")); - - /* Change the path in .gitmodules and stage it*/ - { - git_config *cfg; - - cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.gitmodules")); - cl_git_pass(git_config_set_string(cfg, "submodule.sm_unchanged.path", newpath)); - git_config_free(cfg); - - cl_git_pass(git_index_add_bypath(idx, ".gitmodules")); - } - - /* Change the worktree info in the submodule's config */ - { - git_config *cfg; - - cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.git/modules/sm_unchanged/config")); - cl_git_pass(git_config_set_string(cfg, "core.worktree", "../../../sm_actually_changed")); - git_config_free(cfg); - } - - /* Rename the entry in the index */ - { - const git_index_entry *e; - git_index_entry entry = {{ 0 }}; - - e = git_index_get_bypath(idx, "sm_unchanged", 0); - cl_assert(e); - cl_assert_equal_i(GIT_FILEMODE_COMMIT, e->mode); - - entry.path = newpath; - entry.mode = GIT_FILEMODE_COMMIT; - git_oid_cpy(&entry.id, &e->id); - - cl_git_pass(git_index_remove(idx, "sm_unchanged", 0)); - cl_git_pass(git_index_add(idx, &entry)); - cl_git_pass(git_index_write(idx)); - } - - memset(&data, 0, sizeof(data)); - cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); - cl_assert_equal_i(8, data.count); -} - -void test_submodule_lookup__cached(void) -{ - git_submodule *sm; - git_submodule *sm2; - /* See that the simple tests still pass. */ - - git_repository_submodule_cache_all(g_repo); - test_submodule_lookup__simple_lookup(); - git_repository_submodule_cache_clear(g_repo); - test_submodule_lookup__simple_lookup(); - - /* Check that subsequent calls return different objects when cached. */ - git_repository_submodule_cache_all(g_repo); - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); - cl_git_pass(git_submodule_lookup(&sm2, g_repo, "sm_unchanged")); - cl_assert_equal_p(sm, sm2); - git_submodule_free(sm2); - - /* and that we get new objects again after clearing the cache. */ - git_repository_submodule_cache_clear(g_repo); - cl_git_pass(git_submodule_lookup(&sm2, g_repo, "sm_unchanged")); - cl_assert(sm != sm2); - git_submodule_free(sm); - git_submodule_free(sm2); -} - -void test_submodule_lookup__lookup_in_bare_repository_fails(void) -{ - git_submodule *sm; - - cl_git_sandbox_cleanup(); - g_repo = cl_git_sandbox_init("submodules.git"); - - cl_git_fail(git_submodule_lookup(&sm, g_repo, "nonexisting")); -} - -void test_submodule_lookup__foreach_in_bare_repository_fails(void) -{ - cl_git_sandbox_cleanup(); - g_repo = cl_git_sandbox_init("submodules.git"); - - cl_git_fail(git_submodule_foreach(g_repo, foreach_cb, NULL)); -} - -void test_submodule_lookup__fail_invalid_gitmodules(void) -{ - git_submodule *sm; - sm_lookup_data data; - memset(&data, 0, sizeof(data)); - - cl_git_rewritefile("submod2/.gitmodules", - "[submodule \"Test_App\"\n" - " path = Test_App\n" - " url = ../Test_App\n"); - - cl_git_fail(git_submodule_lookup(&sm, g_repo, "Test_App")); - - cl_git_fail(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); -} diff --git a/tests/submodule/modify.c b/tests/submodule/modify.c deleted file mode 100644 index 7e7f0ca15..000000000 --- a/tests/submodule/modify.c +++ /dev/null @@ -1,233 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "path.h" -#include "submodule_helpers.h" -#include "config/config_helpers.h" - -static git_repository *g_repo = NULL; - -#define SM_LIBGIT2_URL "https://github.com/libgit2/libgit2.git" -#define SM_LIBGIT2_BRANCH "github-branch" -#define SM_LIBGIT2 "sm_libgit2" - -void test_submodule_modify__initialize(void) -{ - g_repo = setup_fixture_submod2(); -} - -static int delete_one_config(const git_config_entry *entry, void *payload) -{ - git_config *cfg = payload; - return git_config_delete_entry(cfg, entry->name); -} - -static int init_one_submodule( - git_submodule *sm, const char *name, void *payload) -{ - GIT_UNUSED(name); - GIT_UNUSED(payload); - return git_submodule_init(sm, false); -} - -void test_submodule_modify__init(void) -{ - git_config *cfg; - const char *str; - - /* erase submodule data from .git/config */ - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass( - git_config_foreach_match(cfg, "submodule\\..*", delete_one_config, cfg)); - git_config_free(cfg); - - /* confirm no submodule data in config */ - cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); - cl_git_fail_with(GIT_ENOTFOUND, git_config_get_string(&str, cfg, "submodule.sm_unchanged.url")); - cl_git_fail_with(GIT_ENOTFOUND, git_config_get_string(&str, cfg, "submodule.sm_changed_head.url")); - cl_git_fail_with(GIT_ENOTFOUND, git_config_get_string(&str, cfg, "submodule.sm_added_and_uncommited.url")); - git_config_free(cfg); - - /* call init and see that settings are copied */ - cl_git_pass(git_submodule_foreach(g_repo, init_one_submodule, NULL)); - - /* confirm submodule data in config */ - cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); - cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_unchanged.url")); - cl_assert(git__suffixcmp(str, "/submod2_target") == 0); - cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_changed_head.url")); - cl_assert(git__suffixcmp(str, "/submod2_target") == 0); - cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_added_and_uncommited.url")); - cl_assert(git__suffixcmp(str, "/submod2_target") == 0); - git_config_free(cfg); -} - -static int sync_one_submodule( - git_submodule *sm, const char *name, void *payload) -{ - GIT_UNUSED(name); - GIT_UNUSED(payload); - return git_submodule_sync(sm); -} - -static void assert_submodule_url_is_synced( - git_submodule *sm, const char *parent_key, const char *child_key) -{ - git_repository *smrepo; - - assert_config_entry_value(g_repo, parent_key, git_submodule_url(sm)); - - cl_git_pass(git_submodule_open(&smrepo, sm)); - assert_config_entry_value(smrepo, child_key, git_submodule_url(sm)); - git_repository_free(smrepo); -} - -void test_submodule_modify__sync(void) -{ - git_submodule *sm1, *sm2, *sm3; - git_config *cfg; - const char *str; - -#define SM1 "sm_unchanged" -#define SM2 "sm_changed_head" -#define SM3 "sm_added_and_uncommited" - - /* look up some submodules */ - cl_git_pass(git_submodule_lookup(&sm1, g_repo, SM1)); - cl_git_pass(git_submodule_lookup(&sm2, g_repo, SM2)); - cl_git_pass(git_submodule_lookup(&sm3, g_repo, SM3)); - - /* At this point, the .git/config URLs for the submodules have - * not be rewritten with the absolute paths (although the - * .gitmodules have. Let's confirm that they DO NOT match - * yet, then we can do a sync to make them match... - */ - - /* check submodule info does not match before sync */ - cl_git_pass(git_repository_config_snapshot(&cfg, g_repo)); - cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM1".url")); - cl_assert(strcmp(git_submodule_url(sm1), str) != 0); - cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM2".url")); - cl_assert(strcmp(git_submodule_url(sm2), str) != 0); - cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM3".url")); - cl_assert(strcmp(git_submodule_url(sm3), str) != 0); - git_config_free(cfg); - - /* sync all the submodules */ - cl_git_pass(git_submodule_foreach(g_repo, sync_one_submodule, NULL)); - - /* check that submodule config is updated */ - assert_submodule_url_is_synced( - sm1, "submodule."SM1".url", "remote.origin.url"); - assert_submodule_url_is_synced( - sm2, "submodule."SM2".url", "remote.origin.url"); - assert_submodule_url_is_synced( - sm3, "submodule."SM3".url", "remote.origin.url"); - - git_submodule_free(sm1); - git_submodule_free(sm2); - git_submodule_free(sm3); -} - -static void assert_ignore_change(git_submodule_ignore_t ignore) -{ - git_submodule *sm; - - cl_git_pass(git_submodule_set_ignore(g_repo, "sm_changed_head", ignore)); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); - cl_assert_equal_i(ignore, git_submodule_ignore(sm)); - git_submodule_free(sm); -} - -void test_submodule_modify__set_ignore(void) -{ - assert_ignore_change(GIT_SUBMODULE_IGNORE_UNTRACKED); - assert_ignore_change(GIT_SUBMODULE_IGNORE_NONE); - assert_ignore_change(GIT_SUBMODULE_IGNORE_ALL); -} - -static void assert_update_change(git_submodule_update_t update) -{ - git_submodule *sm; - - cl_git_pass(git_submodule_set_update(g_repo, "sm_changed_head", update)); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); - cl_assert_equal_i(update, git_submodule_update_strategy(sm)); - git_submodule_free(sm); -} - -void test_submodule_modify__set_update(void) -{ - assert_update_change(GIT_SUBMODULE_UPDATE_REBASE); - assert_update_change(GIT_SUBMODULE_UPDATE_NONE); - assert_update_change(GIT_SUBMODULE_UPDATE_CHECKOUT); -} - -static void assert_recurse_change(git_submodule_recurse_t recurse) -{ - git_submodule *sm; - - cl_git_pass(git_submodule_set_fetch_recurse_submodules(g_repo, "sm_changed_head", recurse)); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); - cl_assert_equal_i(recurse, git_submodule_fetch_recurse_submodules(sm)); - git_submodule_free(sm); -} - -void test_submodule_modify__set_fetch_recurse_submodules(void) -{ - assert_recurse_change(GIT_SUBMODULE_RECURSE_YES); - assert_recurse_change(GIT_SUBMODULE_RECURSE_NO); - assert_recurse_change(GIT_SUBMODULE_RECURSE_ONDEMAND); -} - -void test_submodule_modify__set_branch(void) -{ - git_submodule *sm; - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); - cl_assert(git_submodule_branch(sm) == NULL); - git_submodule_free(sm); - - cl_git_pass(git_submodule_set_branch(g_repo, "sm_changed_head", SM_LIBGIT2_BRANCH)); - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); - cl_assert_equal_s(SM_LIBGIT2_BRANCH, git_submodule_branch(sm)); - git_submodule_free(sm); - - cl_git_pass(git_submodule_set_branch(g_repo, "sm_changed_head", NULL)); - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); - cl_assert(git_submodule_branch(sm) == NULL); - git_submodule_free(sm); -} - -void test_submodule_modify__set_url(void) -{ - git_submodule *sm; - - cl_git_pass(git_submodule_set_url(g_repo, "sm_changed_head", SM_LIBGIT2_URL)); - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); - cl_assert_equal_s(SM_LIBGIT2_URL, git_submodule_url(sm)); - git_submodule_free(sm); -} - -void test_submodule_modify__set_relative_url(void) -{ - git_str path = GIT_STR_INIT; - git_repository *repo; - git_submodule *sm; - - cl_git_pass(git_submodule_set_url(g_repo, SM1, "../relative-url")); - cl_git_pass(git_submodule_lookup(&sm, g_repo, SM1)); - cl_git_pass(git_submodule_sync(sm)); - cl_git_pass(git_submodule_open(&repo, sm)); - - cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), "relative-url")); - - assert_config_entry_value(g_repo, "submodule."SM1".url", path.ptr); - assert_config_entry_value(repo, "remote.origin.url", path.ptr); - - git_repository_free(repo); - git_submodule_free(sm); - git_str_dispose(&path); -} diff --git a/tests/submodule/nosubs.c b/tests/submodule/nosubs.c deleted file mode 100644 index e82230e87..000000000 --- a/tests/submodule/nosubs.c +++ /dev/null @@ -1,130 +0,0 @@ -/* test the submodule APIs on repositories where there are no submodules */ - -#include "clar_libgit2.h" -#include "posix.h" -#include "futils.h" - -void test_submodule_nosubs__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_submodule_nosubs__lookup(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_submodule *sm = NULL; - - p_mkdir("status/subrepo", 0777); - cl_git_mkfile("status/subrepo/.git", "gitdir: ../.git"); - - cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, repo, "subdir")); - - cl_assert_equal_i(GIT_EEXISTS, git_submodule_lookup(&sm, repo, "subrepo")); - - cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, repo, "subdir")); - - cl_assert_equal_i(GIT_EEXISTS, git_submodule_lookup(&sm, repo, "subrepo")); -} - -static int fake_submod_cb(git_submodule *sm, const char *n, void *p) -{ - GIT_UNUSED(sm); GIT_UNUSED(n); GIT_UNUSED(p); - return 0; -} - -void test_submodule_nosubs__foreach(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - cl_git_pass(git_submodule_foreach(repo, fake_submod_cb, NULL)); -} - -void test_submodule_nosubs__add(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_submodule *sm, *sm2; - - cl_git_pass(git_submodule_add_setup(&sm, repo, "https://github.com/libgit2/libgit2.git", "submodules/libgit2", 1)); - - cl_git_pass(git_submodule_lookup(&sm2, repo, "submodules/libgit2")); - git_submodule_free(sm2); - - cl_git_pass(git_submodule_foreach(repo, fake_submod_cb, NULL)); - - git_submodule_free(sm); -} - -void test_submodule_nosubs__bad_gitmodules(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - - cl_git_mkfile("status/.gitmodules", "[submodule \"foobar\"]\tpath=blargle\n\turl=\n\tbranch=\n\tupdate=flooble\n\n"); - - cl_git_rewritefile("status/.gitmodules", "[submodule \"foobar\"]\tpath=blargle\n\turl=\n\tbranch=\n\tupdate=rebase\n\n"); - - cl_git_pass(git_submodule_lookup(NULL, repo, "foobar")); - cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(NULL, repo, "subdir")); -} - -void test_submodule_nosubs__add_and_delete(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_submodule *sm; - git_str buf = GIT_STR_INIT; - - cl_git_fail(git_submodule_lookup(NULL, repo, "libgit2")); - cl_git_fail(git_submodule_lookup(NULL, repo, "submodules/libgit2")); - - /* create */ - - cl_git_pass(git_submodule_add_setup( - &sm, repo, "https://github.com/libgit2/libgit2.git", "submodules/libgit2", 1)); - cl_assert_equal_s("submodules/libgit2", git_submodule_name(sm)); - cl_assert_equal_s("submodules/libgit2", git_submodule_path(sm)); - git_submodule_free(sm); - - cl_git_pass(git_futils_readbuffer(&buf, "status/.gitmodules")); - cl_assert(strstr(buf.ptr, "[submodule \"submodules/libgit2\"]") != NULL); - cl_assert(strstr(buf.ptr, "path = submodules/libgit2") != NULL); - git_str_dispose(&buf); - - /* lookup */ - - cl_git_fail(git_submodule_lookup(&sm, repo, "libgit2")); - cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2")); - cl_assert_equal_s("submodules/libgit2", git_submodule_name(sm)); - cl_assert_equal_s("submodules/libgit2", git_submodule_path(sm)); - git_submodule_free(sm); - - /* update name */ - - cl_git_rewritefile( - "status/.gitmodules", - "[submodule \"libgit2\"]\n" - " path = submodules/libgit2\n" - " url = https://github.com/libgit2/libgit2.git\n"); - - cl_git_pass(git_submodule_lookup(&sm, repo, "libgit2")); - cl_assert_equal_s("libgit2", git_submodule_name(sm)); - cl_assert_equal_s("submodules/libgit2", git_submodule_path(sm)); - git_submodule_free(sm); - cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2")); - git_submodule_free(sm); - - /* revert name update */ - - cl_git_rewritefile( - "status/.gitmodules", - "[submodule \"submodules/libgit2\"]\n" - " path = submodules/libgit2\n" - " url = https://github.com/libgit2/libgit2.git\n"); - - cl_git_fail(git_submodule_lookup(&sm, repo, "libgit2")); - cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2")); - git_submodule_free(sm); - - /* remove completely */ - - cl_must_pass(p_unlink("status/.gitmodules")); - cl_git_fail(git_submodule_lookup(&sm, repo, "libgit2")); - cl_git_fail(git_submodule_lookup(&sm, repo, "submodules/libgit2")); -} diff --git a/tests/submodule/open.c b/tests/submodule/open.c deleted file mode 100644 index e6883d208..000000000 --- a/tests/submodule/open.c +++ /dev/null @@ -1,90 +0,0 @@ -#include "clar_libgit2.h" -#include "submodule_helpers.h" -#include "path.h" - -static git_repository *g_parent; -static git_repository *g_child; -static git_submodule *g_module; - -void test_submodule_open__initialize(void) -{ - g_parent = setup_fixture_submod2(); -} - -void test_submodule_open__cleanup(void) -{ - git_submodule_free(g_module); - git_repository_free(g_child); - cl_git_sandbox_cleanup(); - g_parent = NULL; - g_child = NULL; - g_module = NULL; -} - -static void assert_sm_valid(git_repository *parent, git_repository *child, const char *sm_name) -{ - git_str expected = GIT_STR_INIT, actual = GIT_STR_INIT; - - /* assert working directory */ - cl_git_pass(git_str_joinpath(&expected, git_repository_workdir(parent), sm_name)); - cl_git_pass(git_fs_path_prettify_dir(&expected, expected.ptr, NULL)); - cl_git_pass(git_str_sets(&actual, git_repository_workdir(child))); - cl_git_pass(git_fs_path_prettify_dir(&actual, actual.ptr, NULL)); - cl_assert_equal_s(expected.ptr, actual.ptr); - - git_str_clear(&expected); - git_str_clear(&actual); - - /* assert common directory */ - cl_git_pass(git_str_joinpath(&expected, git_repository_commondir(parent), "modules")); - cl_git_pass(git_str_joinpath(&expected, expected.ptr, sm_name)); - cl_git_pass(git_fs_path_prettify_dir(&expected, expected.ptr, NULL)); - cl_git_pass(git_str_sets(&actual, git_repository_commondir(child))); - cl_git_pass(git_fs_path_prettify_dir(&actual, actual.ptr, NULL)); - cl_assert_equal_s(expected.ptr, actual.ptr); - - /* assert git directory */ - cl_git_pass(git_str_sets(&actual, git_repository_path(child))); - cl_git_pass(git_fs_path_prettify_dir(&actual, actual.ptr, NULL)); - cl_assert_equal_s(expected.ptr, actual.ptr); - - git_str_dispose(&expected); - git_str_dispose(&actual); -} - -void test_submodule_open__opening_via_lookup_succeeds(void) -{ - cl_git_pass(git_submodule_lookup(&g_module, g_parent, "sm_unchanged")); - cl_git_pass(git_submodule_open(&g_child, g_module)); - assert_sm_valid(g_parent, g_child, "sm_unchanged"); -} - -void test_submodule_open__direct_open_succeeds(void) -{ - git_str path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_parent), "sm_unchanged")); - cl_git_pass(git_repository_open(&g_child, path.ptr)); - assert_sm_valid(g_parent, g_child, "sm_unchanged"); - - git_str_dispose(&path); -} - -void test_submodule_open__direct_open_succeeds_for_broken_sm_with_gitdir(void) -{ - git_str path = GIT_STR_INIT; - - /* - * This is actually not a valid submodule, but we - * encountered at least one occasion where the gitdir - * file existed inside of a submodule's gitdir. As we are - * now able to open these submodules correctly, we still - * add a test for this. - */ - cl_git_mkfile("submod2/.git/modules/sm_unchanged/gitdir", ".git"); - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_parent), "sm_unchanged")); - cl_git_pass(git_repository_open(&g_child, path.ptr)); - assert_sm_valid(g_parent, g_child, "sm_unchanged"); - - git_str_dispose(&path); -} diff --git a/tests/submodule/repository_init.c b/tests/submodule/repository_init.c deleted file mode 100644 index 39b55c403..000000000 --- a/tests/submodule/repository_init.c +++ /dev/null @@ -1,38 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "path.h" -#include "submodule_helpers.h" -#include "config/config_helpers.h" -#include "futils.h" - -static git_repository *g_repo = NULL; - -void test_submodule_repository_init__basic(void) -{ - git_submodule *sm; - git_repository *repo; - git_str dot_git_content = GIT_STR_INIT; - - g_repo = setup_fixture_submod2(); - - cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_gitmodules_only")); - cl_git_pass(git_submodule_init(sm, 0)); - cl_git_pass(git_submodule_repo_init(&repo, sm, 1)); - - /* Verify worktree */ - assert_config_entry_value(repo, "core.worktree", "../../../sm_gitmodules_only/"); - - /* Verify gitlink */ - cl_git_pass(git_futils_readbuffer(&dot_git_content, "submod2/" "sm_gitmodules_only" "/.git")); - cl_assert_equal_s("gitdir: ../.git/modules/sm_gitmodules_only/", dot_git_content.ptr); - - cl_assert(git_fs_path_isfile("submod2/" "sm_gitmodules_only" "/.git")); - - cl_assert(git_fs_path_isdir("submod2/.git/modules")); - cl_assert(git_fs_path_isdir("submod2/.git/modules/" "sm_gitmodules_only")); - cl_assert(git_fs_path_isfile("submod2/.git/modules/" "sm_gitmodules_only" "/HEAD")); - - git_submodule_free(sm); - git_repository_free(repo); - git_str_dispose(&dot_git_content); -} diff --git a/tests/submodule/status.c b/tests/submodule/status.c deleted file mode 100644 index 1d41337b7..000000000 --- a/tests/submodule/status.c +++ /dev/null @@ -1,354 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "path.h" -#include "submodule_helpers.h" -#include "futils.h" -#include "iterator.h" - -static git_repository *g_repo = NULL; - -void test_submodule_status__initialize(void) -{ - g_repo = setup_fixture_submod2(); -} - -void test_submodule_status__cleanup(void) -{ -} - -void test_submodule_status__unchanged(void) -{ - unsigned int status = get_submodule_status(g_repo, "sm_unchanged"); - unsigned int expected = - GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | - GIT_SUBMODULE_STATUS_IN_WD; - - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - cl_assert(expected == status); -} - -static void rm_submodule(const char *name) -{ - git_str path = GIT_STR_INIT; - cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), name)); - cl_git_pass(git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)); - git_str_dispose(&path); -} - -static void add_submodule_to_index(const char *name) -{ - git_submodule *sm; - cl_git_pass(git_submodule_lookup(&sm, g_repo, name)); - cl_git_pass(git_submodule_add_to_index(sm, true)); - git_submodule_free(sm); -} - -static void rm_submodule_from_index(const char *name) -{ - git_index *index; - size_t pos; - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_assert(!git_index_find(&pos, index, name)); - cl_git_pass(git_index_remove(index, name, 0)); - cl_git_pass(git_index_write(index)); - git_index_free(index); -} - -/* 4 values of GIT_SUBMODULE_IGNORE to check */ - -void test_submodule_status__ignore_none(void) -{ - unsigned int status; - - rm_submodule("sm_unchanged"); - - refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND); - refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS); - refute_submodule_exists(g_repo, "not", GIT_EEXISTS); - - status = get_submodule_status(g_repo, "sm_changed_index"); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0); - - status = get_submodule_status(g_repo, "sm_changed_head"); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); - - status = get_submodule_status(g_repo, "sm_changed_file"); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0); - - status = get_submodule_status(g_repo, "sm_changed_untracked_file"); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNTRACKED) != 0); - - status = get_submodule_status(g_repo, "sm_missing_commits"); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); - - status = get_submodule_status(g_repo, "sm_added_and_uncommited"); - cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0); - - /* removed sm_unchanged for deleted workdir */ - status = get_submodule_status(g_repo, "sm_unchanged"); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); - - /* now mkdir sm_unchanged to test uninitialized */ - cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); - status = get_submodule_status(g_repo, "sm_unchanged"); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); - - /* update sm_changed_head in index */ - add_submodule_to_index("sm_changed_head"); - status = get_submodule_status(g_repo, "sm_changed_head"); - cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0); - - /* remove sm_changed_head from index */ - rm_submodule_from_index("sm_changed_head"); - status = get_submodule_status(g_repo, "sm_changed_head"); - cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_DELETED) != 0); -} - -void test_submodule_status__ignore_untracked(void) -{ - unsigned int status; - git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_UNTRACKED; - - rm_submodule("sm_unchanged"); - - refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND); - refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS); - refute_submodule_exists(g_repo, "not", GIT_EEXISTS); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_index", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_file", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_untracked_file", ign)); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_missing_commits", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_added_and_uncommited", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0); - - /* removed sm_unchanged for deleted workdir */ - cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); - - /* now mkdir sm_unchanged to test uninitialized */ - cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); - cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); - - /* update sm_changed_head in index */ - add_submodule_to_index("sm_changed_head"); - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0); -} - -void test_submodule_status__ignore_dirty(void) -{ - unsigned int status; - git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_DIRTY; - - rm_submodule("sm_unchanged"); - - refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND); - refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS); - refute_submodule_exists(g_repo, "not", GIT_EEXISTS); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_index", ign)); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_file", ign)); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_untracked_file", ign)); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_missing_commits", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_added_and_uncommited", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0); - - /* removed sm_unchanged for deleted workdir */ - cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); - - /* now mkdir sm_unchanged to test uninitialized */ - cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); - cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); - - /* update sm_changed_head in index */ - add_submodule_to_index("sm_changed_head"); - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign)); - cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0); -} - -void test_submodule_status__ignore_all(void) -{ - unsigned int status; - git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_ALL; - - rm_submodule("sm_unchanged"); - - refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND); - refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS); - refute_submodule_exists(g_repo, "not", GIT_EEXISTS); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_index", ign)); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign)); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_file", ign)); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_untracked_file", ign)); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_missing_commits", ign)); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - - cl_git_pass(git_submodule_status(&status, g_repo,"sm_added_and_uncommited", ign)); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - - /* removed sm_unchanged for deleted workdir */ - cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - - /* now mkdir sm_unchanged to test uninitialized */ - cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); - cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - - /* update sm_changed_head in index */ - add_submodule_to_index("sm_changed_head"); - cl_git_pass(git_submodule_status(&status, g_repo,"sm_changed_head", ign)); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); -} - -typedef struct { - size_t counter; - const char **paths; - int *statuses; -} submodule_expectations; - -static int confirm_submodule_status( - const char *path, unsigned int status_flags, void *payload) -{ - submodule_expectations *exp = payload; - - while (exp->statuses[exp->counter] < 0) - exp->counter++; - - cl_assert_equal_i(exp->statuses[exp->counter], (int)status_flags); - cl_assert_equal_s(exp->paths[exp->counter++], path); - - GIT_UNUSED(status_flags); - - return 0; -} - -void test_submodule_status__iterator(void) -{ - git_iterator *iter; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry; - size_t i; - static const char *expected[] = { - ".gitmodules", - "just_a_dir/", - "just_a_dir/contents", - "just_a_file", - "not-submodule/", - "not-submodule/README.txt", - "not/", - "not/README.txt", - "README.txt", - "sm_added_and_uncommited", - "sm_changed_file", - "sm_changed_head", - "sm_changed_index", - "sm_changed_untracked_file", - "sm_missing_commits", - "sm_unchanged", - NULL - }; - static int expected_flags[] = { - GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED, /* ".gitmodules" */ - -1, /* "just_a_dir/" will be skipped */ - GIT_STATUS_CURRENT, /* "just_a_dir/contents" */ - GIT_STATUS_CURRENT, /* "just_a_file" */ - GIT_STATUS_WT_NEW, /* "not-submodule/" untracked item */ - -1, /* "not-submodule/README.txt" */ - GIT_STATUS_WT_NEW, /* "not/" untracked item */ - -1, /* "not/README.txt" */ - GIT_STATUS_CURRENT, /* "README.txt */ - GIT_STATUS_INDEX_NEW, /* "sm_added_and_uncommited" */ - GIT_STATUS_WT_MODIFIED, /* "sm_changed_file" */ - GIT_STATUS_WT_MODIFIED, /* "sm_changed_head" */ - GIT_STATUS_WT_MODIFIED, /* "sm_changed_index" */ - GIT_STATUS_WT_MODIFIED, /* "sm_changed_untracked_file" */ - GIT_STATUS_WT_MODIFIED, /* "sm_missing_commits" */ - GIT_STATUS_CURRENT, /* "sm_unchanged" */ - 0 - }; - submodule_expectations exp = { 0, expected, expected_flags }; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - git_index *index; - - iter_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_iterator_for_workdir(&iter, g_repo, index, NULL, &iter_opts)); - - for (i = 0; !git_iterator_advance(&entry, iter); ++i) - cl_assert_equal_s(expected[i], entry->path); - - git_iterator_free(iter); - git_index_free(index); - - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_INCLUDE_UNMODIFIED | - GIT_STATUS_OPT_INCLUDE_IGNORED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY; - - cl_git_pass(git_status_foreach_ext( - g_repo, &opts, confirm_submodule_status, &exp)); -} - -void test_submodule_status__untracked_dirs_containing_ignored_files(void) -{ - unsigned int status, expected; - - cl_git_append2file( - "submod2/.git/modules/sm_unchanged/info/exclude", "\n*.ignored\n"); - - cl_git_pass( - git_futils_mkdir_relative("sm_unchanged/directory", "submod2", 0755, 0, NULL)); - cl_git_mkfile( - "submod2/sm_unchanged/directory/i_am.ignored", - "ignore this file, please\n"); - - status = get_submodule_status(g_repo, "sm_unchanged"); - cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); - - expected = GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | - GIT_SUBMODULE_STATUS_IN_WD; - cl_assert(status == expected); -} diff --git a/tests/submodule/submodule_helpers.c b/tests/submodule/submodule_helpers.c deleted file mode 100644 index b8fc9f60d..000000000 --- a/tests/submodule/submodule_helpers.c +++ /dev/null @@ -1,245 +0,0 @@ -#include "clar_libgit2.h" -#include "path.h" -#include "util.h" -#include "posix.h" -#include "submodule_helpers.h" -#include "git2/sys/repository.h" - -/* rewrite gitmodules -> .gitmodules - * rewrite the empty or relative urls inside each module - * rename the .gitted directory inside any submodule to .git - */ -void rewrite_gitmodules(const char *workdir) -{ - git_str in_f = GIT_STR_INIT, out_f = GIT_STR_INIT, path = GIT_STR_INIT; - FILE *in, *out; - char line[256]; - - cl_git_pass(git_str_joinpath(&in_f, workdir, "gitmodules")); - cl_git_pass(git_str_joinpath(&out_f, workdir, ".gitmodules")); - - cl_assert((in = fopen(in_f.ptr, "rb")) != NULL); - cl_assert((out = fopen(out_f.ptr, "wb")) != NULL); - - while (fgets(line, sizeof(line), in) != NULL) { - char *scan = line; - - while (*scan == ' ' || *scan == '\t') scan++; - - /* rename .gitted -> .git in submodule directories */ - if (git__prefixcmp(scan, "path =") == 0) { - scan += strlen("path ="); - while (*scan == ' ') scan++; - - git_str_joinpath(&path, workdir, scan); - git_str_rtrim(&path); - git_str_joinpath(&path, path.ptr, ".gitted"); - - if (!git_str_oom(&path) && p_access(path.ptr, F_OK) == 0) { - git_str_joinpath(&out_f, workdir, scan); - git_str_rtrim(&out_f); - git_str_joinpath(&out_f, out_f.ptr, ".git"); - - if (!git_str_oom(&out_f)) - p_rename(path.ptr, out_f.ptr); - } - } - - /* copy non-"url =" lines verbatim */ - if (git__prefixcmp(scan, "url =") != 0) { - fputs(line, out); - continue; - } - - /* convert relative URLs in "url =" lines */ - scan += strlen("url ="); - while (*scan == ' ') scan++; - - if (*scan == '.') { - git_str_joinpath(&path, workdir, scan); - git_str_rtrim(&path); - } else if (!*scan || *scan == '\n') { - git_str_joinpath(&path, workdir, "../testrepo.git"); - } else { - fputs(line, out); - continue; - } - - git_fs_path_prettify(&path, path.ptr, NULL); - git_str_putc(&path, '\n'); - cl_assert(!git_str_oom(&path)); - - fwrite(line, scan - line, sizeof(char), out); - fputs(path.ptr, out); - } - - fclose(in); - fclose(out); - - cl_must_pass(p_unlink(in_f.ptr)); - - git_str_dispose(&in_f); - git_str_dispose(&out_f); - git_str_dispose(&path); -} - -static void cleanup_fixture_submodules(void *payload) -{ - cl_git_sandbox_cleanup(); /* either "submodules" or "submod2" */ - - if (payload) - cl_fixture_cleanup(payload); -} - -git_repository *setup_fixture_submodules(void) -{ - git_repository *repo = cl_git_sandbox_init("submodules"); - - cl_fixture_sandbox("testrepo.git"); - - rewrite_gitmodules(git_repository_workdir(repo)); - p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git"); - - cl_set_cleanup(cleanup_fixture_submodules, "testrepo.git"); - - cl_git_pass(git_repository_reinit_filesystem(repo, 1)); - - return repo; -} - -git_repository *setup_fixture_submod2(void) -{ - git_repository *repo = cl_git_sandbox_init("submod2"); - - cl_fixture_sandbox("submod2_target"); - p_rename("submod2_target/.gitted", "submod2_target/.git"); - - rewrite_gitmodules(git_repository_workdir(repo)); - p_rename("submod2/not-submodule/.gitted", "submod2/not-submodule/.git"); - p_rename("submod2/not/.gitted", "submod2/not/.git"); - - cl_set_cleanup(cleanup_fixture_submodules, "submod2_target"); - - cl_git_pass(git_repository_reinit_filesystem(repo, 1)); - - return repo; -} - -git_repository *setup_fixture_submod3(void) -{ - git_repository *repo = cl_git_sandbox_init("submod3"); - - cl_fixture_sandbox("submod2_target"); - p_rename("submod2_target/.gitted", "submod2_target/.git"); - - rewrite_gitmodules(git_repository_workdir(repo)); - p_rename("submod3/One/.gitted", "submod3/One/.git"); - p_rename("submod3/TWO/.gitted", "submod3/TWO/.git"); - p_rename("submod3/three/.gitted", "submod3/three/.git"); - p_rename("submod3/FoUr/.gitted", "submod3/FoUr/.git"); - p_rename("submod3/Five/.gitted", "submod3/Five/.git"); - p_rename("submod3/six/.gitted", "submod3/six/.git"); - p_rename("submod3/sEvEn/.gitted", "submod3/sEvEn/.git"); - p_rename("submod3/EIGHT/.gitted", "submod3/EIGHT/.git"); - p_rename("submod3/nine/.gitted", "submod3/nine/.git"); - p_rename("submod3/TEN/.gitted", "submod3/TEN/.git"); - - cl_set_cleanup(cleanup_fixture_submodules, "submod2_target"); - - cl_git_pass(git_repository_reinit_filesystem(repo, 1)); - - return repo; -} - -git_repository *setup_fixture_super(void) -{ - git_repository *repo = cl_git_sandbox_init("super"); - - cl_fixture_sandbox("sub.git"); - p_mkdir("super/sub", 0777); - - rewrite_gitmodules(git_repository_workdir(repo)); - - cl_set_cleanup(cleanup_fixture_submodules, "sub.git"); - - cl_git_pass(git_repository_reinit_filesystem(repo, 1)); - - return repo; -} - -git_repository *setup_fixture_submodule_simple(void) -{ - git_repository *repo = cl_git_sandbox_init("submodule_simple"); - - cl_fixture_sandbox("testrepo.git"); - p_mkdir("submodule_simple/testrepo", 0777); - - cl_set_cleanup(cleanup_fixture_submodules, "testrepo.git"); - - cl_git_pass(git_repository_reinit_filesystem(repo, 1)); - - return repo; -} - -git_repository *setup_fixture_submodule_with_path(void) -{ - git_repository *repo = cl_git_sandbox_init("submodule_with_path"); - - cl_fixture_sandbox("testrepo.git"); - p_mkdir("submodule_with_path/lib", 0777); - p_mkdir("submodule_with_path/lib/testrepo", 0777); - - cl_set_cleanup(cleanup_fixture_submodules, "testrepo.git"); - - cl_git_pass(git_repository_reinit_filesystem(repo, 1)); - - return repo; -} - -void assert__submodule_exists( - git_repository *repo, const char *name, - const char *msg, const char *file, const char *func, int line) -{ - git_submodule *sm; - int error = git_submodule_lookup(&sm, repo, name); - if (error) - cl_git_report_failure(error, 0, file, func, line, msg); - cl_assert_at_line(sm != NULL, file, func, line); - git_submodule_free(sm); -} - -void refute__submodule_exists( - git_repository *repo, const char *name, int expected_error, - const char *msg, const char *file, const char *func, int line) -{ - clar__assert_equal( - file, func, line, msg, 1, "%i", - expected_error, (int)(git_submodule_lookup(NULL, repo, name))); -} - -unsigned int get_submodule_status(git_repository *repo, const char *name) -{ - unsigned int status = 0; - - assert(repo && name); - - cl_git_pass(git_submodule_status(&status, repo, name, GIT_SUBMODULE_IGNORE_UNSPECIFIED)); - - return status; -} - -static int print_submodules(git_submodule *sm, const char *name, void *p) -{ - unsigned int loc = 0; - GIT_UNUSED(p); - git_submodule_location(&loc, sm); - fprintf(stderr, "# submodule %s (at %s) flags %x\n", - name, git_submodule_path(sm), loc); - return 0; -} - -void dump_submodules(git_repository *repo) -{ - git_submodule_foreach(repo, print_submodules, NULL); -} - diff --git a/tests/submodule/submodule_helpers.h b/tests/submodule/submodule_helpers.h deleted file mode 100644 index 3c3f062ae..000000000 --- a/tests/submodule/submodule_helpers.h +++ /dev/null @@ -1,25 +0,0 @@ -extern void rewrite_gitmodules(const char *workdir); - -/* these will automatically set a cleanup callback */ -extern git_repository *setup_fixture_submodules(void); -extern git_repository *setup_fixture_submod2(void); -extern git_repository *setup_fixture_submod3(void); -extern git_repository *setup_fixture_submodule_simple(void); -extern git_repository *setup_fixture_super(void); -extern git_repository *setup_fixture_submodule_with_path(void); - -extern unsigned int get_submodule_status(git_repository *, const char *); - -extern void assert__submodule_exists(git_repository *, const char *, - const char *, const char *, const char *, int); - -#define assert_submodule_exists(repo,name) \ - assert__submodule_exists(repo, name, "git_submodule_lookup(" #name ") failed", __FILE__, __func__, __LINE__) - -extern void refute__submodule_exists(git_repository *, const char *, - int err, const char *, const char *, const char *, int); - -#define refute_submodule_exists(repo,name,code) \ - refute__submodule_exists(repo, name, code, "expected git_submodule_lookup(" #name ") to fail with error " #code, __FILE__, __func__, __LINE__) - -extern void dump_submodules(git_repository *repo); diff --git a/tests/submodule/update.c b/tests/submodule/update.c deleted file mode 100644 index 4aa959852..000000000 --- a/tests/submodule/update.c +++ /dev/null @@ -1,440 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "path.h" -#include "submodule_helpers.h" -#include "futils.h" - -static git_repository *g_repo = NULL; - -void test_submodule_update__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_submodule_update__uninitialized_submodule_no_init(void) -{ - git_submodule *sm; - git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; - - g_repo = setup_fixture_submodule_simple(); - - /* get the submodule */ - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - - /* updating an uninitialized repository throws */ - cl_git_fail_with( - GIT_ERROR, - git_submodule_update(sm, 0, &update_options)); - - git_submodule_free(sm); -} - -struct update_submodule_cb_payload { - int update_tips_called; - int checkout_progress_called; - int checkout_notify_called; -}; - -static void checkout_progress_cb( - const char *path, - size_t completed_steps, - size_t total_steps, - void *payload) -{ - struct update_submodule_cb_payload *update_payload = payload; - - GIT_UNUSED(path); - GIT_UNUSED(completed_steps); - GIT_UNUSED(total_steps); - - update_payload->checkout_progress_called = 1; -} - -static int checkout_notify_cb( - git_checkout_notify_t why, - const char *path, - const git_diff_file *baseline, - const git_diff_file *target, - const git_diff_file *workdir, - void *payload) -{ - struct update_submodule_cb_payload *update_payload = payload; - - GIT_UNUSED(why); - GIT_UNUSED(path); - GIT_UNUSED(baseline); - GIT_UNUSED(target); - GIT_UNUSED(workdir); - - update_payload->checkout_notify_called = 1; - - return 0; -} - -static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *data) -{ - struct update_submodule_cb_payload *update_payload = data; - - GIT_UNUSED(refname); - GIT_UNUSED(a); - GIT_UNUSED(b); - - update_payload->update_tips_called = 1; - - return 1; -} - -void test_submodule_update__update_submodule(void) -{ - git_submodule *sm; - git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; - unsigned int submodule_status = 0; - struct update_submodule_cb_payload update_payload = { 0 }; - - g_repo = setup_fixture_submodule_simple(); - - update_options.checkout_opts.progress_cb = checkout_progress_cb; - update_options.checkout_opts.progress_payload = &update_payload; - - update_options.fetch_opts.callbacks.update_tips = update_tips; - update_options.fetch_opts.callbacks.payload = &update_payload; - - /* get the submodule */ - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - - /* verify the initial state of the submodule */ - cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); - cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | - GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); - - /* initialize and update the submodule */ - cl_git_pass(git_submodule_init(sm, 0)); - cl_git_pass(git_submodule_update(sm, 0, &update_options)); - - /* verify state */ - cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); - cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | - GIT_SUBMODULE_STATUS_IN_WD); - - cl_assert(git_oid_streq(git_submodule_head_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_index_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - - /* verify that the expected callbacks have been called. */ - cl_assert_equal_i(1, update_payload.checkout_progress_called); - cl_assert_equal_i(1, update_payload.update_tips_called); - - git_submodule_free(sm); -} - -void test_submodule_update__update_submodule_with_path(void) -{ - git_submodule *sm; - git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; - unsigned int submodule_status = 0; - struct update_submodule_cb_payload update_payload = { 0 }; - - g_repo = setup_fixture_submodule_with_path(); - - update_options.checkout_opts.progress_cb = checkout_progress_cb; - update_options.checkout_opts.progress_payload = &update_payload; - - update_options.fetch_opts.callbacks.update_tips = update_tips; - update_options.fetch_opts.callbacks.payload = &update_payload; - - /* get the submodule */ - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - - /* verify the initial state of the submodule */ - cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); - cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | - GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); - - /* initialize and update the submodule */ - cl_git_pass(git_submodule_init(sm, 0)); - cl_git_pass(git_submodule_update(sm, 0, &update_options)); - - /* verify state */ - cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); - cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | - GIT_SUBMODULE_STATUS_IN_WD); - - cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - - /* verify that the expected callbacks have been called. */ - cl_assert_equal_i(1, update_payload.checkout_progress_called); - cl_assert_equal_i(1, update_payload.update_tips_called); - - git_submodule_free(sm); -} - -void test_submodule_update__update_and_init_submodule(void) -{ - git_submodule *sm; - git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; - unsigned int submodule_status = 0; - - g_repo = setup_fixture_submodule_simple(); - - /* get the submodule */ - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - - cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); - cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | - GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); - - /* update (with option to initialize sub repo) */ - cl_git_pass(git_submodule_update(sm, 1, &update_options)); - - /* verify expected state */ - cl_assert(git_oid_streq(git_submodule_head_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_index_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - - git_submodule_free(sm); -} - -void test_submodule_update__update_already_checked_out_submodule(void) -{ - git_submodule *sm = NULL; - git_checkout_options checkout_options = GIT_CHECKOUT_OPTIONS_INIT; - git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; - unsigned int submodule_status = 0; - git_reference *branch_reference = NULL; - git_object *branch_commit = NULL; - struct update_submodule_cb_payload update_payload = { 0 }; - - g_repo = setup_fixture_submodule_simple(); - - update_options.checkout_opts.progress_cb = checkout_progress_cb; - update_options.checkout_opts.progress_payload = &update_payload; - - /* Initialize and update the sub repository */ - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - - cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); - cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | - GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); - - cl_git_pass(git_submodule_update(sm, 1, &update_options)); - - /* verify expected state */ - cl_assert(git_oid_streq(git_submodule_head_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_index_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - - /* checkout the alternate_1 branch */ - checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_reference_lookup(&branch_reference, g_repo, "refs/heads/alternate_1")); - cl_git_pass(git_reference_peel(&branch_commit, branch_reference, GIT_OBJECT_COMMIT)); - cl_git_pass(git_checkout_tree(g_repo, branch_commit, &checkout_options)); - cl_git_pass(git_repository_set_head(g_repo, git_reference_name(branch_reference))); - - /* - * Verify state after checkout of parent repository. The submodule ID in the - * HEAD commit and index should be updated, but not the workdir. - */ - - cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); - - git_submodule_free(sm); - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - - cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | - GIT_SUBMODULE_STATUS_IN_WD | - GIT_SUBMODULE_STATUS_WD_MODIFIED); - - cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - - /* - * Update the submodule and verify the state. - * Now, the HEAD, index, and Workdir commits should all be updated to - * the new commit. - */ - cl_git_pass(git_submodule_update(sm, 0, &update_options)); - cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - - /* verify that the expected callbacks have been called. */ - cl_assert_equal_i(1, update_payload.checkout_progress_called); - - git_submodule_free(sm); - git_object_free(branch_commit); - git_reference_free(branch_reference); -} - -void test_submodule_update__update_blocks_on_dirty_wd(void) -{ - git_submodule *sm = NULL; - git_checkout_options checkout_options = GIT_CHECKOUT_OPTIONS_INIT; - git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; - unsigned int submodule_status = 0; - git_reference *branch_reference = NULL; - git_object *branch_commit = NULL; - struct update_submodule_cb_payload update_payload = { 0 }; - - g_repo = setup_fixture_submodule_simple(); - - update_options.checkout_opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT; - update_options.checkout_opts.notify_cb = checkout_notify_cb; - update_options.checkout_opts.notify_payload = &update_payload; - - /* Initialize and update the sub repository */ - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - - cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); - cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | - GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); - - cl_git_pass(git_submodule_update(sm, 1, &update_options)); - - /* verify expected state */ - cl_assert(git_oid_streq(git_submodule_head_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_index_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - - /* checkout the alternate_1 branch */ - checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_reference_lookup(&branch_reference, g_repo, "refs/heads/alternate_1")); - cl_git_pass(git_reference_peel(&branch_commit, branch_reference, GIT_OBJECT_COMMIT)); - cl_git_pass(git_checkout_tree(g_repo, branch_commit, &checkout_options)); - cl_git_pass(git_repository_set_head(g_repo, git_reference_name(branch_reference))); - - /* - * Verify state after checkout of parent repository. The submodule ID in the - * HEAD commit and index should be updated, but not the workdir. - */ - - cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); - - git_submodule_free(sm); - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - - cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | - GIT_SUBMODULE_STATUS_IN_WD | - GIT_SUBMODULE_STATUS_WD_MODIFIED); - - cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - - /* - * Create a conflicting edit in the subrepository to verify that - * the submodule update action is blocked. - */ - cl_git_write2file("submodule_simple/testrepo/branch_file.txt", "a conflicting edit", 0, - O_WRONLY | O_CREAT | O_TRUNC, 0755); - - cl_git_fail(git_submodule_update(sm, 0, &update_options)); - - /* verify that the expected callbacks have been called. */ - cl_assert_equal_i(1, update_payload.checkout_notify_called); - - /* verify that the submodule state has not changed. */ - cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - - git_submodule_free(sm); - git_object_free(branch_commit); - git_reference_free(branch_reference); -} - -void test_submodule_update__can_force_update(void) -{ - git_submodule *sm = NULL; - git_checkout_options checkout_options = GIT_CHECKOUT_OPTIONS_INIT; - git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; - unsigned int submodule_status = 0; - git_reference *branch_reference = NULL; - git_object *branch_commit = NULL; - - g_repo = setup_fixture_submodule_simple(); - - /* Initialize and update the sub repository */ - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - - cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); - cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | - GIT_SUBMODULE_STATUS_WD_UNINITIALIZED); - - cl_git_pass(git_submodule_update(sm, 1, &update_options)); - - /* verify expected state */ - cl_assert(git_oid_streq(git_submodule_head_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_index_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - - /* checkout the alternate_1 branch */ - checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_reference_lookup(&branch_reference, g_repo, "refs/heads/alternate_1")); - cl_git_pass(git_reference_peel(&branch_commit, branch_reference, GIT_OBJECT_COMMIT)); - cl_git_pass(git_checkout_tree(g_repo, branch_commit, &checkout_options)); - cl_git_pass(git_repository_set_head(g_repo, git_reference_name(branch_reference))); - - /* - * Verify state after checkout of parent repository. The submodule ID in the - * HEAD commit and index should be updated, but not the workdir. - */ - cl_git_pass(git_submodule_status(&submodule_status, g_repo, "testrepo", GIT_SUBMODULE_IGNORE_UNSPECIFIED)); - - git_submodule_free(sm); - cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); - - cl_assert_equal_i(submodule_status, GIT_SUBMODULE_STATUS_IN_HEAD | - GIT_SUBMODULE_STATUS_IN_INDEX | - GIT_SUBMODULE_STATUS_IN_CONFIG | - GIT_SUBMODULE_STATUS_IN_WD | - GIT_SUBMODULE_STATUS_WD_MODIFIED); - - cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), "be3563ae3f795b2b4353bcce3a527ad0a4f7f644") == 0); - cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - - /* - * Create a conflicting edit in the subrepository to verify that - * the submodule update action is blocked. - */ - cl_git_write2file("submodule_simple/testrepo/branch_file.txt", "a conflicting edit", 0, - O_WRONLY | O_CREAT | O_TRUNC, 0777); - - /* forcefully checkout and verify the submodule state was updated. */ - update_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; - cl_git_pass(git_submodule_update(sm, 0, &update_options)); - cl_assert(git_oid_streq(git_submodule_head_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - cl_assert(git_oid_streq(git_submodule_wd_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - cl_assert(git_oid_streq(git_submodule_index_id(sm), "a65fedf39aefe402d3bb6e24df4d4f5fe4547750") == 0); - - git_submodule_free(sm); - git_object_free(branch_commit); - git_reference_free(branch_reference); -} - diff --git a/tests/threads/atomic.c b/tests/threads/atomic.c deleted file mode 100644 index 4d04a777a..000000000 --- a/tests/threads/atomic.c +++ /dev/null @@ -1,125 +0,0 @@ -#include "clar_libgit2.h" - -void test_threads_atomic__atomic32_set(void) -{ - git_atomic32 v = {0}; - git_atomic32_set(&v, 1); - cl_assert_equal_i(v.val, 1); -} - -void test_threads_atomic__atomic32_get(void) -{ - git_atomic32 v = {1}; - cl_assert_equal_i(git_atomic32_get(&v), 1); -} - -void test_threads_atomic__atomic32_inc(void) -{ - git_atomic32 v = {0}; - cl_assert_equal_i(git_atomic32_inc(&v), 1); - cl_assert_equal_i(v.val, 1); -} - -void test_threads_atomic__atomic32_add(void) -{ - git_atomic32 v = {0}; - cl_assert_equal_i(git_atomic32_add(&v, 1), 1); - cl_assert_equal_i(v.val, 1); -} - -void test_threads_atomic__atomic32_dec(void) -{ - git_atomic32 v = {1}; - cl_assert_equal_i(git_atomic32_dec(&v), 0); - cl_assert_equal_i(v.val, 0); -} - -void test_threads_atomic__atomic64_set(void) -{ -#ifndef GIT_ARCH_64 - cl_skip(); -#else - git_atomic64 v = {0}; - git_atomic64_set(&v, 1); - cl_assert_equal_i(v.val, 1); -#endif -} - -void test_threads_atomic__atomic64_get(void) -{ -#ifndef GIT_ARCH_64 - cl_skip(); -#else - git_atomic64 v = {1}; - cl_assert_equal_i(git_atomic64_get(&v), 1); -#endif -} - -void test_threads_atomic__atomic64_add(void) -{ -#ifndef GIT_ARCH_64 - cl_skip(); -#else - git_atomic64 v = {0}; - cl_assert_equal_i(git_atomic64_add(&v, 1), 1); - cl_assert_equal_i(v.val, 1); -#endif -} - -void test_threads_atomic__cas_pointer(void) -{ - int *value = NULL; - int newvalue1 = 1, newvalue2 = 2; - - /* value is updated */ - cl_assert_equal_p(git_atomic_compare_and_swap(&value, NULL, &newvalue1), NULL); - cl_assert_equal_p(value, &newvalue1); - - /* value is not updated */ - cl_assert_equal_p(git_atomic_compare_and_swap(&value, NULL, &newvalue2), &newvalue1); - cl_assert_equal_p(value, &newvalue1); -} - -void test_threads_atomic__cas_intptr(void) -{ - intptr_t value = 0; - intptr_t oldvalue; - intptr_t newvalue; - - /* value is updated */ - oldvalue = 0; - newvalue = 1; - cl_assert_equal_i((intptr_t)git_atomic_compare_and_swap(&value, (void *)oldvalue, (void *)newvalue), 0); - cl_assert_equal_i(value, 1); - - /* value is not updated */ - oldvalue = 0; - newvalue = 2; - cl_assert_equal_i((intptr_t)git_atomic_compare_and_swap(&value, (void *)oldvalue, (void *)newvalue), 1); - cl_assert_equal_i(value, 1); -} - -void test_threads_atomic__swap(void) -{ - int *value = NULL; - int newvalue = 1; - - cl_assert_equal_p(git_atomic_swap(value, &newvalue), NULL); - cl_assert_equal_p(value, &newvalue); - - cl_assert_equal_p(git_atomic_swap(value, NULL), &newvalue); - cl_assert_equal_p(value, NULL); -} - -void test_threads_atomic__load_ptr(void) -{ - int value = 1; - int *ptr = &value; - cl_assert_equal_p(git_atomic_load(ptr), &value); -} - -void test_threads_atomic__load_intptr(void) -{ - intptr_t value = 1; - cl_assert_equal_i((intptr_t)git_atomic_load(value), 1); -} diff --git a/tests/threads/basic.c b/tests/threads/basic.c deleted file mode 100644 index 2d7ddc26b..000000000 --- a/tests/threads/basic.c +++ /dev/null @@ -1,83 +0,0 @@ -#include "clar_libgit2.h" - -#include "thread_helpers.h" -#include "cache.h" - - -static git_repository *g_repo; - -void test_threads_basic__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_threads_basic__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - - -void test_threads_basic__cache(void) -{ - /* run several threads polling the cache at the same time */ - cl_assert(1 == 1); -} - -void test_threads_basic__multiple_init(void) -{ - git_repository *nested_repo; - - git_libgit2_init(); - cl_git_pass(git_repository_open(&nested_repo, cl_fixture("testrepo.git"))); - git_repository_free(nested_repo); - - git_libgit2_shutdown(); - cl_git_pass(git_repository_open(&nested_repo, cl_fixture("testrepo.git"))); - git_repository_free(nested_repo); -} - -static void *set_error(void *dummy) -{ - git_error_set(GIT_ERROR_INVALID, "oh no, something happened!\n"); - - return dummy; -} - -/* Set errors so we can check that we free it */ -void test_threads_basic__set_error(void) -{ - run_in_parallel(1, 4, set_error, NULL, NULL); -} - -#ifdef GIT_THREADS -static void *return_normally(void *param) -{ - return param; -} - -static void *exit_abruptly(void *param) -{ - git_thread_exit(param); - return NULL; -} -#endif - -void test_threads_basic__exit(void) -{ -#ifndef GIT_THREADS - clar__skip(); -#else - git_thread thread; - void *result; - - /* Ensure that the return value of the threadproc is returned. */ - cl_git_pass(git_thread_create(&thread, return_normally, (void *)424242)); - cl_git_pass(git_thread_join(&thread, &result)); - cl_assert_equal_sz(424242, (size_t)result); - - /* Ensure that the return value of `git_thread_exit` is returned. */ - cl_git_pass(git_thread_create(&thread, exit_abruptly, (void *)232323)); - cl_git_pass(git_thread_join(&thread, &result)); - cl_assert_equal_sz(232323, (size_t)result); -#endif -} diff --git a/tests/threads/diff.c b/tests/threads/diff.c deleted file mode 100644 index 04c8cb97f..000000000 --- a/tests/threads/diff.c +++ /dev/null @@ -1,218 +0,0 @@ -#include "clar_libgit2.h" -#include "thread_helpers.h" - -#ifdef GIT_THREADS - -# if defined(GIT_WIN32) -# define git_thread_yield() Sleep(0) -# elif defined(__FreeBSD__) || defined(__MidnightBSD__) || defined(__DragonFly__) -# define git_thread_yield() pthread_yield() -# else -# define git_thread_yield() sched_yield() -# endif - -#else -# define git_thread_yield() (void)0 -#endif - -static git_repository *_repo; -static git_tree *_a, *_b; -static git_atomic32 _counts[4]; -static int _check_counts; -#ifdef GIT_WIN32 -static int _retries; -#endif - -#define THREADS 20 - -void test_threads_diff__initialize(void) -{ -#ifdef GIT_WIN32 - _retries = git_win32__retries; - git_win32__retries = 1; -#endif -} - -void test_threads_diff__cleanup(void) -{ - cl_git_sandbox_cleanup(); - -#ifdef GIT_WIN32 - git_win32__retries = _retries; -#endif -} - -static void setup_trees(void) -{ - git_index *idx; - - _repo = cl_git_sandbox_reopen(); /* reopen sandbox to flush caches */ - - /* avoid competing to load initial index */ - cl_git_pass(git_repository_index(&idx, _repo)); - git_index_free(idx); - - cl_git_pass(git_revparse_single( - (git_object **)&_a, _repo, "0017bd4ab1^{tree}")); - cl_git_pass(git_revparse_single( - (git_object **)&_b, _repo, "26a125ee1b^{tree}")); - - memset(_counts, 0, sizeof(_counts)); -} - -static void free_trees(void) -{ - git_tree_free(_a); _a = NULL; - git_tree_free(_b); _b = NULL; - - if (_check_counts) { - cl_assert_equal_i(288, git_atomic32_get(&_counts[0])); - cl_assert_equal_i(112, git_atomic32_get(&_counts[1])); - cl_assert_equal_i( 80, git_atomic32_get(&_counts[2])); - cl_assert_equal_i( 96, git_atomic32_get(&_counts[3])); - } -} - -static void *run_index_diffs(void *arg) -{ - int thread = *(int *)arg; - git_repository *repo; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - size_t i; - int exp[4] = { 0, 0, 0, 0 }; - - cl_git_pass(git_repository_open(&repo, git_repository_path(_repo))); - - switch (thread & 0x03) { - case 0: /* diff index to workdir */; - cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); - break; - case 1: /* diff tree 'a' to index */; - cl_git_pass(git_diff_tree_to_index(&diff, repo, _a, NULL, &opts)); - break; - case 2: /* diff tree 'b' to index */; - cl_git_pass(git_diff_tree_to_index(&diff, repo, _b, NULL, &opts)); - break; - case 3: /* diff index to workdir (explicit index) */; - { - git_index *idx; - cl_git_pass(git_repository_index(&idx, repo)); - cl_git_pass(git_diff_index_to_workdir(&diff, repo, idx, &opts)); - git_index_free(idx); - break; - } - } - - /* keep some diff stats to make sure results are as expected */ - - i = git_diff_num_deltas(diff); - git_atomic32_add(&_counts[0], (int32_t)i); - exp[0] = (int)i; - - while (i > 0) { - switch (git_diff_get_delta(diff, --i)->status) { - case GIT_DELTA_MODIFIED: exp[1]++; git_atomic32_inc(&_counts[1]); break; - case GIT_DELTA_ADDED: exp[2]++; git_atomic32_inc(&_counts[2]); break; - case GIT_DELTA_DELETED: exp[3]++; git_atomic32_inc(&_counts[3]); break; - default: break; - } - } - - switch (thread & 0x03) { - case 0: case 3: - cl_assert_equal_i(8, exp[0]); cl_assert_equal_i(4, exp[1]); - cl_assert_equal_i(0, exp[2]); cl_assert_equal_i(4, exp[3]); - break; - case 1: - cl_assert_equal_i(12, exp[0]); cl_assert_equal_i(3, exp[1]); - cl_assert_equal_i(7, exp[2]); cl_assert_equal_i(2, exp[3]); - break; - case 2: - cl_assert_equal_i(8, exp[0]); cl_assert_equal_i(3, exp[1]); - cl_assert_equal_i(3, exp[2]); cl_assert_equal_i(2, exp[3]); - break; - } - - git_diff_free(diff); - git_repository_free(repo); - git_error_clear(); - - return arg; -} - -void test_threads_diff__concurrent_diffs(void) -{ - _repo = cl_git_sandbox_init("status"); - _check_counts = 1; - - run_in_parallel( - 5, 32, run_index_diffs, setup_trees, free_trees); -} - -static void *run_index_diffs_with_modifier(void *arg) -{ - int thread = *(int *)arg; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = NULL; - git_index *idx = NULL; - git_repository *repo; - - cl_git_pass(git_repository_open(&repo, git_repository_path(_repo))); - cl_git_pass(git_repository_index(&idx, repo)); - - /* have first thread altering the index as we go */ - if (thread == 0) { - int i; - - for (i = 0; i < 300; ++i) { - switch (i & 0x03) { - case 0: (void)git_index_add_bypath(idx, "new_file"); break; - case 1: (void)git_index_remove_bypath(idx, "modified_file"); break; - case 2: (void)git_index_remove_bypath(idx, "new_file"); break; - case 3: (void)git_index_add_bypath(idx, "modified_file"); break; - } - git_thread_yield(); - } - - goto done; - } - - /* only use explicit index in this test to prevent reloading */ - - switch (thread & 0x03) { - case 0: /* diff index to workdir */; - cl_git_pass(git_diff_index_to_workdir(&diff, repo, idx, &opts)); - break; - case 1: /* diff tree 'a' to index */; - cl_git_pass(git_diff_tree_to_index(&diff, repo, _a, idx, &opts)); - break; - case 2: /* diff tree 'b' to index */; - cl_git_pass(git_diff_tree_to_index(&diff, repo, _b, idx, &opts)); - break; - case 3: /* diff index to workdir reversed */; - opts.flags |= GIT_DIFF_REVERSE; - cl_git_pass(git_diff_index_to_workdir(&diff, repo, idx, &opts)); - break; - } - - /* results will be unpredictable with index modifier thread running */ - - git_diff_free(diff); - -done: - git_index_free(idx); - git_repository_free(repo); - git_error_clear(); - - return arg; -} - -void test_threads_diff__with_concurrent_index_modified(void) -{ - _repo = cl_git_sandbox_init("status"); - _check_counts = 0; - - run_in_parallel( - 5, 16, run_index_diffs_with_modifier, setup_trees, free_trees); -} diff --git a/tests/threads/iterator.c b/tests/threads/iterator.c deleted file mode 100644 index 33d1bda81..000000000 --- a/tests/threads/iterator.c +++ /dev/null @@ -1,55 +0,0 @@ -#include "clar_libgit2.h" -#include "thread_helpers.h" -#include "iterator.h" - -static git_repository *_repo; - -void test_threads_iterator__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void *run_workdir_iterator(void *arg) -{ - int error = 0; - git_repository *repo; - git_iterator *iter; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry = NULL; - - iter_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - - cl_git_pass(git_repository_open(&repo, git_repository_path(_repo))); - cl_git_pass(git_iterator_for_workdir( - &iter, repo, NULL, NULL, &iter_opts)); - - while (!error) { - if (entry && entry->mode == GIT_FILEMODE_TREE) { - error = git_iterator_advance_into(&entry, iter); - - if (error == GIT_ENOTFOUND) - error = git_iterator_advance(&entry, iter); - } else { - error = git_iterator_advance(&entry, iter); - } - - if (!error) - (void)git_iterator_current_is_ignored(iter); - } - - cl_assert_equal_i(GIT_ITEROVER, error); - - git_iterator_free(iter); - git_repository_free(repo); - git_error_clear(); - return arg; -} - - -void test_threads_iterator__workdir(void) -{ - _repo = cl_git_sandbox_init("status"); - - run_in_parallel( - 1, 20, run_workdir_iterator, NULL, NULL); -} diff --git a/tests/threads/refdb.c b/tests/threads/refdb.c deleted file mode 100644 index a4630df6a..000000000 --- a/tests/threads/refdb.c +++ /dev/null @@ -1,220 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/refdb.h" -#include "refdb.h" - -static git_repository *g_repo; -static int g_expected = 0; - -#ifdef GIT_WIN32 -static bool concurrent_compress = false; -#else -static bool concurrent_compress = true; -#endif - -void test_threads_refdb__initialize(void) -{ - g_repo = NULL; -} - -void test_threads_refdb__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -#define REPEAT 20 -#define THREADS 20 -/* Number of references to create or delete in each thread */ -#define NREFS 10 - -struct th_data { - cl_git_thread_err error; - int id; - const char *path; -}; - -static void *iterate_refs(void *arg) -{ - struct th_data *data = (struct th_data *) arg; - git_reference_iterator *i; - git_reference *ref; - int count = 0, error; - git_repository *repo; - - cl_git_thread_pass(data, git_repository_open(&repo, data->path)); - do { - error = git_reference_iterator_new(&i, repo); - } while (error == GIT_ELOCKED); - cl_git_thread_pass(data, error); - - for (count = 0; !git_reference_next(&ref, i); ++count) { - cl_assert(ref != NULL); - git_reference_free(ref); - } - - if (g_expected > 0) - cl_assert_equal_i(g_expected, count); - - git_reference_iterator_free(i); - - git_repository_free(repo); - git_error_clear(); - return arg; -} - -static void *create_refs(void *arg) -{ - int i, error; - struct th_data *data = (struct th_data *) arg; - git_oid head; - char name[128]; - git_reference *ref[NREFS]; - git_repository *repo; - - cl_git_thread_pass(data, git_repository_open(&repo, data->path)); - - do { - error = git_reference_name_to_id(&head, repo, "HEAD"); - } while (error == GIT_ELOCKED); - cl_git_thread_pass(data, error); - - for (i = 0; i < NREFS; ++i) { - p_snprintf(name, sizeof(name), "refs/heads/thread-%03d-%02d", data->id, i); - do { - error = git_reference_create(&ref[i], repo, name, &head, 0, NULL); - } while (error == GIT_ELOCKED); - cl_git_thread_pass(data, error); - - if (concurrent_compress && i == NREFS/2) { - git_refdb *refdb; - cl_git_thread_pass(data, git_repository_refdb(&refdb, repo)); - do { - error = git_refdb_compress(refdb); - } while (error == GIT_ELOCKED); - cl_git_thread_pass(data, error); - git_refdb_free(refdb); - } - } - - for (i = 0; i < NREFS; ++i) - git_reference_free(ref[i]); - - git_repository_free(repo); - - git_error_clear(); - return arg; -} - -static void *delete_refs(void *arg) -{ - int i, error; - struct th_data *data = (struct th_data *) arg; - git_reference *ref; - char name[128]; - git_repository *repo; - - cl_git_thread_pass(data, git_repository_open(&repo, data->path)); - - for (i = 0; i < NREFS; ++i) { - p_snprintf( - name, sizeof(name), "refs/heads/thread-%03d-%02d", (data->id) & ~0x3, i); - - if (!git_reference_lookup(&ref, repo, name)) { - do { - error = git_reference_delete(ref); - } while (error == GIT_ELOCKED); - /* Sometimes we race with other deleter threads */ - if (error == GIT_ENOTFOUND) - error = 0; - - cl_git_thread_pass(data, error); - git_reference_free(ref); - } - - if (concurrent_compress && i == NREFS/2) { - git_refdb *refdb; - cl_git_thread_pass(data, git_repository_refdb(&refdb, repo)); - do { - error = git_refdb_compress(refdb); - } while (error == GIT_ELOCKED); - cl_git_thread_pass(data, error); - git_refdb_free(refdb); - } - } - - git_repository_free(repo); - git_error_clear(); - return arg; -} - -void test_threads_refdb__edit_while_iterate(void) -{ - int r, t; - struct th_data th_data[THREADS]; - git_oid head; - git_reference *ref; - char name[128]; - git_refdb *refdb; - -#ifdef GIT_THREADS - git_thread th[THREADS]; -#endif - - g_repo = cl_git_sandbox_init("testrepo2"); - - cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); - - /* make a bunch of references */ - - for (r = 0; r < 50; ++r) { - p_snprintf(name, sizeof(name), "refs/heads/starter-%03d", r); - cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0, NULL)); - git_reference_free(ref); - } - - cl_git_pass(git_repository_refdb(&refdb, g_repo)); - cl_git_pass(git_refdb_compress(refdb)); - git_refdb_free(refdb); - - g_expected = -1; - - g_repo = cl_git_sandbox_reopen(); /* reopen to flush caches */ - - for (t = 0; t < THREADS; ++t) { - void *(*fn)(void *arg); - - switch (t & 0x3) { - case 0: fn = create_refs; break; - case 1: fn = delete_refs; break; - default: fn = iterate_refs; break; - } - - th_data[t].id = t; - th_data[t].path = git_repository_path(g_repo); - -#ifdef GIT_THREADS - cl_git_pass(git_thread_create(&th[t], fn, &th_data[t])); -#else - fn(&th_data[t]); -#endif - } - -#ifdef GIT_THREADS - for (t = 0; t < THREADS; ++t) { - cl_git_pass(git_thread_join(&th[t], NULL)); - cl_git_thread_check(&th_data[t]); - } - - memset(th, 0, sizeof(th)); - - for (t = 0; t < THREADS; ++t) { - th_data[t].id = t; - cl_git_pass(git_thread_create(&th[t], iterate_refs, &th_data[t])); - } - - for (t = 0; t < THREADS; ++t) { - cl_git_pass(git_thread_join(&th[t], NULL)); - cl_git_thread_check(&th_data[t]); - } -#endif -} diff --git a/tests/threads/thread_helpers.c b/tests/threads/thread_helpers.c deleted file mode 100644 index 54bf6097d..000000000 --- a/tests/threads/thread_helpers.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "clar_libgit2.h" -#include "thread_helpers.h" - -void run_in_parallel( - int repeats, - int threads, - void *(*func)(void *), - void (*before_test)(void), - void (*after_test)(void)) -{ - int r, t, *id = git__calloc(threads, sizeof(int)); -#ifdef GIT_THREADS - git_thread *th = git__calloc(threads, sizeof(git_thread)); - cl_assert(th != NULL); -#else - void *th = NULL; -#endif - - cl_assert(id != NULL); - - for (r = 0; r < repeats; ++r) { - if (before_test) before_test(); - - for (t = 0; t < threads; ++t) { - id[t] = t; -#ifdef GIT_THREADS - cl_git_pass(git_thread_create(&th[t], func, &id[t])); -#else - cl_assert(func(&id[t]) == &id[t]); -#endif - } - -#ifdef GIT_THREADS - for (t = 0; t < threads; ++t) - cl_git_pass(git_thread_join(&th[t], NULL)); - memset(th, 0, threads * sizeof(git_thread)); -#endif - - if (after_test) after_test(); - } - - git__free(id); - git__free(th); -} diff --git a/tests/threads/thread_helpers.h b/tests/threads/thread_helpers.h deleted file mode 100644 index 0f23a4ce0..000000000 --- a/tests/threads/thread_helpers.h +++ /dev/null @@ -1,8 +0,0 @@ -#include "thread.h" - -void run_in_parallel( - int repeats, - int threads, - void *(*func)(void *), - void (*before_test)(void), - void (*after_test)(void)); diff --git a/tests/threads/tlsdata.c b/tests/threads/tlsdata.c deleted file mode 100644 index 7c69b4444..000000000 --- a/tests/threads/tlsdata.c +++ /dev/null @@ -1,65 +0,0 @@ -#include "clar_libgit2.h" - -#include "thread_helpers.h" - -void test_threads_tlsdata__can_set_and_get(void) -{ - git_tlsdata_key key_one, key_two, key_three; - - cl_git_pass(git_tlsdata_init(&key_one, NULL)); - cl_git_pass(git_tlsdata_init(&key_two, NULL)); - cl_git_pass(git_tlsdata_init(&key_three, NULL)); - - cl_git_pass(git_tlsdata_set(key_one, (void *)(size_t)42424242)); - cl_git_pass(git_tlsdata_set(key_two, (void *)(size_t)0xdeadbeef)); - cl_git_pass(git_tlsdata_set(key_three, (void *)(size_t)98761234)); - - cl_assert_equal_sz((size_t)42424242, git_tlsdata_get(key_one)); - cl_assert_equal_sz((size_t)0xdeadbeef, git_tlsdata_get(key_two)); - cl_assert_equal_sz((size_t)98761234, git_tlsdata_get(key_three)); - - cl_git_pass(git_tlsdata_dispose(key_one)); - cl_git_pass(git_tlsdata_dispose(key_two)); - cl_git_pass(git_tlsdata_dispose(key_three)); -} - -#ifdef GIT_THREADS - -static void *set_and_get(void *param) -{ - git_tlsdata_key *tlsdata_key = (git_tlsdata_key *)param; - int val; - - if (git_tlsdata_set(*tlsdata_key, &val) != 0 || - git_tlsdata_get(*tlsdata_key) != &val) - return (void *)0; - - return (void *)1; -} - -#endif - -#define THREAD_COUNT 10 - -void test_threads_tlsdata__threads(void) -{ -#ifdef GIT_THREADS - git_thread thread[THREAD_COUNT]; - git_tlsdata_key tlsdata; - int i; - - cl_git_pass(git_tlsdata_init(&tlsdata, NULL)); - - for (i = 0; i < THREAD_COUNT; i++) - cl_git_pass(git_thread_create(&thread[i], set_and_get, &tlsdata)); - - for (i = 0; i < THREAD_COUNT; i++) { - void *result; - - cl_git_pass(git_thread_join(&thread[i], &result)); - cl_assert_equal_sz(1, (size_t)result); - } - - cl_git_pass(git_tlsdata_dispose(tlsdata)); -#endif -} diff --git a/tests/trace/trace.c b/tests/trace/trace.c deleted file mode 100644 index 097208bff..000000000 --- a/tests/trace/trace.c +++ /dev/null @@ -1,106 +0,0 @@ -#include "clar_libgit2.h" -#include "clar_libgit2_trace.h" -#include "trace.h" - -static int written = 0; - -static void trace_callback(git_trace_level_t level, const char *message) -{ - GIT_UNUSED(level); - - cl_assert(strcmp(message, "Hello world!") == 0); - - written = 1; -} - -void test_trace_trace__initialize(void) -{ - /* If global tracing is enabled, disable for the duration of this test. */ - cl_global_trace_disable(); - - git_trace_set(GIT_TRACE_INFO, trace_callback); - written = 0; -} - -void test_trace_trace__cleanup(void) -{ - git_trace_set(GIT_TRACE_NONE, NULL); - - /* If global tracing was enabled, restart it. */ - cl_global_trace_register(); -} - -void test_trace_trace__sets(void) -{ -#ifdef GIT_TRACE - cl_assert(git_trace_level() == GIT_TRACE_INFO); -#else - cl_skip(); -#endif -} - -void test_trace_trace__can_reset(void) -{ -#ifdef GIT_TRACE - cl_assert(git_trace_level() == GIT_TRACE_INFO); - cl_git_pass(git_trace_set(GIT_TRACE_ERROR, trace_callback)); - - cl_assert(written == 0); - git_trace(GIT_TRACE_INFO, "Hello %s!", "world"); - cl_assert(written == 0); - - git_trace(GIT_TRACE_ERROR, "Hello %s!", "world"); - cl_assert(written == 1); -#else - cl_skip(); -#endif -} - -void test_trace_trace__can_unset(void) -{ -#ifdef GIT_TRACE - cl_assert(git_trace_level() == GIT_TRACE_INFO); - cl_git_pass(git_trace_set(GIT_TRACE_NONE, NULL)); - - cl_assert(git_trace_level() == GIT_TRACE_NONE); - - cl_assert(written == 0); - git_trace(GIT_TRACE_FATAL, "Hello %s!", "world"); - cl_assert(written == 0); -#else - cl_skip(); -#endif -} - -void test_trace_trace__skips_higher_level(void) -{ -#ifdef GIT_TRACE - cl_assert(written == 0); - git_trace(GIT_TRACE_DEBUG, "Hello %s!", "world"); - cl_assert(written == 0); -#else - cl_skip(); -#endif -} - -void test_trace_trace__writes(void) -{ -#ifdef GIT_TRACE - cl_assert(written == 0); - git_trace(GIT_TRACE_INFO, "Hello %s!", "world"); - cl_assert(written == 1); -#else - cl_skip(); -#endif -} - -void test_trace_trace__writes_lower_level(void) -{ -#ifdef GIT_TRACE - cl_assert(written == 0); - git_trace(GIT_TRACE_ERROR, "Hello %s!", "world"); - cl_assert(written == 1); -#else - cl_skip(); -#endif -} diff --git a/tests/trace/windows/stacktrace.c b/tests/trace/windows/stacktrace.c deleted file mode 100644 index 0a77ef99d..000000000 --- a/tests/trace/windows/stacktrace.c +++ /dev/null @@ -1,152 +0,0 @@ -#include "clar_libgit2.h" -#include "win32/w32_leakcheck.h" - -#if defined(GIT_WIN32_LEAKCHECK) -static void a(void) -{ - char buf[10000]; - - cl_assert(git_win32_leakcheck_stack(buf, sizeof(buf), 0, NULL, NULL) == 0); - -#if 0 - fprintf(stderr, "Stacktrace from [%s:%d]:\n%s\n", __FILE__, __LINE__, buf); -#endif -} - -static void b(void) -{ - a(); -} - -static void c(void) -{ - b(); -} -#endif - -void test_trace_windows_stacktrace__basic(void) -{ -#if defined(GIT_WIN32_LEAKCHECK) - c(); -#endif -} - - -void test_trace_windows_stacktrace__leaks(void) -{ -#if defined(GIT_WIN32_LEAKCHECK) - void * p1; - void * p2; - void * p3; - void * p4; - int before, after; - int leaks; - int error; - - /* remember outstanding leaks due to set setup - * and set mark/checkpoint. - */ - before = git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL | - GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK, - NULL); - - p1 = git__malloc(5); - leaks = git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, - "p1"); - cl_assert_equal_i(1, leaks); - - p2 = git__malloc(5); - leaks = git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, - "p1,p2"); - cl_assert_equal_i(2, leaks); - - p3 = git__malloc(5); - leaks = git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, - "p1,p2,p3"); - cl_assert_equal_i(3, leaks); - - git__free(p2); - leaks = git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, - "p1,p3"); - cl_assert_equal_i(2, leaks); - - /* move the mark. only new leaks should appear afterwards */ - error = git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_SET_MARK, - NULL); - /* cannot use cl_git_pass() since that may allocate memory. */ - cl_assert_equal_i(0, error); - - leaks = git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, - "not_p1,not_p3"); - cl_assert_equal_i(0, leaks); - - p4 = git__malloc(5); - leaks = git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, - "p4,not_p1,not_p3"); - cl_assert_equal_i(1, leaks); - - git__free(p1); - git__free(p3); - leaks = git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, - "p4"); - cl_assert_equal_i(1, leaks); - - git__free(p4); - leaks = git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_SINCE_MARK, - "end"); - cl_assert_equal_i(0, leaks); - - /* confirm current absolute leaks count matches beginning value. */ - after = git_win32_leakcheck_stacktrace_dump( - GIT_WIN32_LEAKCHECK_STACKTRACE_QUIET | - GIT_WIN32_LEAKCHECK_STACKTRACE_LEAKS_TOTAL, - "total"); - cl_assert_equal_i(before, after); -#endif -} - -#if defined(GIT_WIN32_LEAKCHECK) -static void aux_cb_alloc__1(unsigned int *aux_id) -{ - static unsigned int aux_counter = 0; - - *aux_id = aux_counter++; -} - -static void aux_cb_lookup__1(unsigned int aux_id, char *aux_msg, size_t aux_msg_len) -{ - p_snprintf(aux_msg, aux_msg_len, "\tQQ%08x\n", aux_id); -} - -#endif - -void test_trace_windows_stacktrace__aux1(void) -{ -#if defined(GIT_WIN32_LEAKCHECK) - git_win32_leakcheck_stack_set_aux_cb(aux_cb_alloc__1, aux_cb_lookup__1); - c(); - c(); - c(); - c(); - git_win32_leakcheck_stack_set_aux_cb(NULL, NULL); -#endif -} diff --git a/tests/transport/register.c b/tests/transport/register.c deleted file mode 100644 index 88ba247de..000000000 --- a/tests/transport/register.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/transport.h" - -static git_transport _transport = GIT_TRANSPORT_INIT; - -static int dummy_transport(git_transport **transport, git_remote *owner, void *param) -{ - *transport = &_transport; - GIT_UNUSED(owner); - GIT_UNUSED(param); - return 0; -} - -void test_transport_register__custom_transport(void) -{ - git_transport *transport; - - cl_git_pass(git_transport_register("something", dummy_transport, NULL)); - - cl_git_pass(git_transport_new(&transport, NULL, "something://somepath")); - - cl_assert(transport == &_transport); - - cl_git_pass(git_transport_unregister("something")); -} - -void test_transport_register__custom_transport_error_doubleregister(void) -{ - cl_git_pass(git_transport_register("something", dummy_transport, NULL)); - - cl_git_fail_with(git_transport_register("something", dummy_transport, NULL), GIT_EEXISTS); - - cl_git_pass(git_transport_unregister("something")); -} - -void test_transport_register__custom_transport_error_remove_non_existing(void) -{ - cl_git_fail_with(git_transport_unregister("something"), GIT_ENOTFOUND); -} - -void test_transport_register__custom_transport_ssh(void) -{ - const char *urls[] = { - "ssh://somehost:somepath", - "ssh+git://somehost:somepath", - "git+ssh://somehost:somepath", - "git@somehost:somepath", - "ssh://somehost:somepath%20with%20%spaces", - "ssh://somehost:somepath with spaces" - }; - git_transport *transport; - unsigned i; - - for (i = 0; i < ARRAY_SIZE(urls); i++) { -#ifndef GIT_SSH - cl_git_fail_with(git_transport_new(&transport, NULL, urls[i]), -1); -#else - cl_git_pass(git_transport_new(&transport, NULL, urls[i])); - transport->free(transport); -#endif - } - - cl_git_pass(git_transport_register("ssh", dummy_transport, NULL)); - - cl_git_pass(git_transport_new(&transport, NULL, "git@somehost:somepath")); - - cl_assert(transport == &_transport); - - cl_git_pass(git_transport_unregister("ssh")); - - for (i = 0; i < ARRAY_SIZE(urls); i++) { -#ifndef GIT_SSH - cl_git_fail_with(git_transport_new(&transport, NULL, urls[i]), -1); -#else - cl_git_pass(git_transport_new(&transport, NULL, urls[i])); - transport->free(transport); -#endif - } -} diff --git a/tests/transports/smart/packet.c b/tests/transports/smart/packet.c deleted file mode 100644 index 5b623a378..000000000 --- a/tests/transports/smart/packet.c +++ /dev/null @@ -1,340 +0,0 @@ -#include "clar_libgit2.h" -#include "transports/smart.h" - -enum expected_status { - PARSE_SUCCESS, - PARSE_FAILURE -}; - -static void assert_flush_parses(const char *line) -{ - size_t linelen = strlen(line) + 1; - const char *endptr; - git_pkt *pkt; - - cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); - cl_assert_equal_i(pkt->type, GIT_PKT_FLUSH); - cl_assert_equal_strn(endptr, line + 4, linelen - 4); - - git_pkt_free((git_pkt *) pkt); -} - -static void assert_data_pkt_parses(const char *line, const char *expected_data, size_t expected_len) -{ - size_t linelen = strlen(line) + 1; - const char *endptr; - git_pkt_data *pkt; - - cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); - cl_assert_equal_i(pkt->type, GIT_PKT_DATA); - cl_assert_equal_i(pkt->len, expected_len); - cl_assert_equal_strn(pkt->data, expected_data, expected_len); - - git_pkt_free((git_pkt *) pkt); -} - -static void assert_sideband_progress_parses(const char *line, const char *expected_data, size_t expected_len) -{ - size_t linelen = strlen(line) + 1; - const char *endptr; - git_pkt_progress *pkt; - - cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); - cl_assert_equal_i(pkt->type, GIT_PKT_PROGRESS); - cl_assert_equal_i(pkt->len, expected_len); - cl_assert_equal_strn(pkt->data, expected_data, expected_len); - - git_pkt_free((git_pkt *) pkt); -} - -static void assert_error_parses(const char *line, const char *expected_error, size_t expected_len) -{ - size_t linelen = strlen(line) + 1; - const char *endptr; - git_pkt_err *pkt; - - cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); - cl_assert_equal_i(pkt->type, GIT_PKT_ERR); - cl_assert_equal_i(pkt->len, expected_len); - cl_assert_equal_strn(pkt->error, expected_error, expected_len); - - git_pkt_free((git_pkt *) pkt); -} - -static void assert_ack_parses(const char *line, const char *expected_oid, enum git_ack_status expected_status) -{ - size_t linelen = strlen(line) + 1; - const char *endptr; - git_pkt_ack *pkt; - git_oid oid; - - cl_git_pass(git_oid_fromstr(&oid, expected_oid)); - - cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); - cl_assert_equal_i(pkt->type, GIT_PKT_ACK); - cl_assert_equal_oid(&pkt->oid, &oid); - cl_assert_equal_i(pkt->status, expected_status); - - git_pkt_free((git_pkt *) pkt); -} - -static void assert_nak_parses(const char *line) -{ - size_t linelen = strlen(line) + 1; - const char *endptr; - git_pkt *pkt; - - cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); - cl_assert_equal_i(pkt->type, GIT_PKT_NAK); - cl_assert_equal_strn(endptr, line + 7, linelen - 7); - - git_pkt_free((git_pkt *) pkt); -} - -static void assert_comment_parses(const char *line, const char *expected_comment) -{ - size_t linelen = strlen(line) + 1; - const char *endptr; - git_pkt_comment *pkt; - - cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); - cl_assert_equal_i(pkt->type, GIT_PKT_COMMENT); - cl_assert_equal_strn(pkt->comment, expected_comment, strlen(expected_comment)); - - git_pkt_free((git_pkt *) pkt); -} - -static void assert_ok_parses(const char *line, const char *expected_ref) -{ - size_t linelen = strlen(line) + 1; - const char *endptr; - git_pkt_ok *pkt; - - cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); - cl_assert_equal_i(pkt->type, GIT_PKT_OK); - cl_assert_equal_strn(pkt->ref, expected_ref, strlen(expected_ref)); - - git_pkt_free((git_pkt *) pkt); -} - -static void assert_unpack_parses(const char *line, bool ok) -{ - size_t linelen = strlen(line) + 1; - const char *endptr; - git_pkt_unpack *pkt; - - cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); - cl_assert_equal_i(pkt->type, GIT_PKT_UNPACK); - cl_assert_equal_i(pkt->unpack_ok, ok); - - git_pkt_free((git_pkt *) pkt); -} - -static void assert_ng_parses(const char *line, const char *expected_ref, const char *expected_msg) -{ - size_t linelen = strlen(line) + 1; - const char *endptr; - git_pkt_ng *pkt; - - cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); - cl_assert_equal_i(pkt->type, GIT_PKT_NG); - cl_assert_equal_strn(pkt->ref, expected_ref, strlen(expected_ref)); - cl_assert_equal_strn(pkt->msg, expected_msg, strlen(expected_msg)); - - git_pkt_free((git_pkt *) pkt); -} - -#define assert_ref_parses(line, expected_oid, expected_ref, expected_capabilities) \ - assert_ref_parses_(line, sizeof(line), expected_oid, expected_ref, expected_capabilities) - -static void assert_ref_parses_(const char *line, size_t linelen, const char *expected_oid, - const char *expected_ref, const char *expected_capabilities) -{ - const char *endptr; - git_pkt_ref *pkt; - git_oid oid; - - cl_git_pass(git_oid_fromstr(&oid, expected_oid)); - - cl_git_pass(git_pkt_parse_line((git_pkt **) &pkt, &endptr, line, linelen)); - cl_assert_equal_i(pkt->type, GIT_PKT_REF); - cl_assert_equal_oid(&pkt->head.oid, &oid); - cl_assert_equal_strn(pkt->head.name, expected_ref, strlen(expected_ref)); - if (expected_capabilities) - cl_assert_equal_strn(pkt->capabilities, expected_capabilities, strlen(expected_capabilities)); - else - cl_assert_equal_p(NULL, pkt->capabilities); - - git_pkt_free((git_pkt *) pkt); -} - -static void assert_pkt_fails(const char *line) -{ - const char *endptr; - git_pkt *pkt; - cl_git_fail(git_pkt_parse_line(&pkt, &endptr, line, strlen(line) + 1)); -} - -void test_transports_smart_packet__parsing_garbage_fails(void) -{ - assert_pkt_fails("0foobar"); - assert_pkt_fails("00foobar"); - assert_pkt_fails("000foobar"); - assert_pkt_fails("0001"); - assert_pkt_fails(""); - assert_pkt_fails("0"); - assert_pkt_fails("0i00"); - assert_pkt_fails("f"); -} - -void test_transports_smart_packet__flush_parses(void) -{ - assert_flush_parses("0000"); - assert_flush_parses("0000foobar"); -} - -void test_transports_smart_packet__data_pkt(void) -{ - assert_pkt_fails("000foobar"); - assert_pkt_fails("0001o"); - assert_pkt_fails("0001\1"); - assert_data_pkt_parses("0005\1", "", 0); - assert_pkt_fails("0009\1o"); - assert_data_pkt_parses("0009\1data", "data", 4); - assert_data_pkt_parses("000a\1data", "data", 5); -} - -void test_transports_smart_packet__sideband_progress_pkt(void) -{ - assert_pkt_fails("0001\2"); - assert_sideband_progress_parses("0005\2", "", 0); - assert_pkt_fails("0009\2o"); - assert_sideband_progress_parses("0009\2data", "data", 4); - assert_sideband_progress_parses("000a\2data", "data", 5); -} - -void test_transports_smart_packet__sideband_err_pkt(void) -{ - assert_pkt_fails("0001\3"); - assert_error_parses("0005\3", "", 0); - assert_pkt_fails("0009\3o"); - assert_error_parses("0009\3data", "data", 4); - assert_error_parses("000a\3data", "data", 5); -} - -void test_transports_smart_packet__ack_pkt(void) -{ - assert_ack_parses("0030ACK 0000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000", 0); - assert_ack_parses("0039ACK 0000000000000000000000000000000000000000 continue", - "0000000000000000000000000000000000000000", - GIT_ACK_CONTINUE); - assert_ack_parses("0037ACK 0000000000000000000000000000000000000000 common", - "0000000000000000000000000000000000000000", - GIT_ACK_COMMON); - assert_ack_parses("0037ACK 0000000000000000000000000000000000000000 ready", - "0000000000000000000000000000000000000000", - GIT_ACK_READY); - - /* these should fail as they don't have OIDs */ - assert_pkt_fails("0007ACK"); - assert_pkt_fails("0008ACK "); - - /* this one is missing a space and should thus fail */ - assert_pkt_fails("0036ACK00000000000000000x0000000000000000000000 ready"); - - /* the following ones have invalid OIDs and should thus fail */ - assert_pkt_fails("0037ACK 00000000000000000x0000000000000000000000 ready"); - assert_pkt_fails("0036ACK 000000000000000000000000000000000000000 ready"); - assert_pkt_fails("0036ACK 00000000000000000x0000000000000000000000ready"); - - /* this one has an invalid status and should thus fail */ - assert_pkt_fails("0036ACK 0000000000000000000000000000000000000000 read"); -} - -void test_transports_smart_packet__nak_pkt(void) -{ - assert_nak_parses("0007NAK"); - assert_pkt_fails("0007NaK"); - assert_pkt_fails("0007nak"); - assert_nak_parses("0007NAKfoobar"); - assert_pkt_fails("0007nakfoobar"); - assert_pkt_fails("0007 NAK"); -} - -void test_transports_smart_packet__error_pkt(void) -{ - assert_pkt_fails("0007ERR"); - assert_pkt_fails("0008ERRx"); - assert_error_parses("0008ERR ", "", 0); - assert_error_parses("000EERR ERRMSG", "ERRMSG", 6); -} - -void test_transports_smart_packet__comment_pkt(void) -{ - assert_comment_parses("0005#", ""); - assert_comment_parses("000B#foobar", "#fooba"); - assert_comment_parses("000C#foobar", "#foobar"); - assert_comment_parses("001A#this is a comment\nfoo", "#this is a comment\nfoo"); -} - -void test_transports_smart_packet__ok_pkt(void) -{ - assert_pkt_fails("0007ok\n"); - assert_ok_parses("0007ok ", ""); - assert_ok_parses("0008ok \n", ""); - assert_ok_parses("0008ok x", "x"); - assert_ok_parses("0009ok x\n", "x"); - assert_pkt_fails("001OK ref/foo/bar"); - assert_ok_parses("0012ok ref/foo/bar", "ref/foo/bar"); - assert_pkt_fails("0013OK ref/foo/bar\n"); - assert_ok_parses("0013ok ref/foo/bar\n", "ref/foo/bar"); -} - -void test_transports_smart_packet__ng_pkt(void) -{ - /* TODO: same as for ok pkt */ - assert_pkt_fails("0007ng\n"); - assert_pkt_fails("0008ng \n"); - assert_pkt_fails("000Bng ref\n"); - assert_pkt_fails("000Bng ref\n"); - /* TODO: is this a valid packet line? Probably not. */ - assert_ng_parses("000Ang x\n", "", "x"); - assert_ng_parses("000Fng ref msg\n", "ref", "msg"); - assert_ng_parses("000Fng ref msg\n", "ref", "msg"); -} - -void test_transports_smart_packet__unpack_pkt(void) -{ - assert_unpack_parses("000Dunpack ok", 1); - assert_unpack_parses("000Dunpack ng error-msg", 0); - /* TODO: the following tests should fail */ - assert_unpack_parses("000Aunpack", 0); - assert_unpack_parses("0011unpack foobar", 0); - assert_unpack_parses("0010unpack ng ok", 0); - assert_unpack_parses("0010unpack okfoo", 1); -} - -void test_transports_smart_packet__ref_pkt(void) -{ - assert_pkt_fails("002C0000000000000000000000000000000000000000"); - assert_pkt_fails("002D0000000000000000000000000000000000000000\n"); - assert_pkt_fails("00300000000000000000000000000000000000000000HEAD"); - assert_pkt_fails("004800000000x0000000000000000000000000000000 refs/heads/master\0multi_ack"); - assert_ref_parses( - "003F0000000000000000000000000000000000000000 refs/heads/master\0", - "0000000000000000000000000000000000000000", "refs/heads/master", ""); - assert_ref_parses( - "00480000000000000000000000000000000000000000 refs/heads/master\0multi_ack", - "0000000000000000000000000000000000000000", "refs/heads/master", "multi_ack"); - assert_ref_parses( - "00460000000000000000000000000000000000000000 refs/heads/master\0one two", - "0000000000000000000000000000000000000000", "refs/heads/master", "one two"); - assert_ref_parses( - "00310000000000000000000000000000000000000000 HEAD", - "0000000000000000000000000000000000000000", "HEAD", NULL); - assert_pkt_fails("0031000000000000000000000000000000000000000 HEAD"); - assert_ref_parses( - "00360000000000000000000000000000000000000000 HEAD HEAD", - "0000000000000000000000000000000000000000", "HEAD HEAD", NULL); -} diff --git a/tests/valgrind-supp-mac.txt b/tests/valgrind-supp-mac.txt deleted file mode 100644 index 3298abee6..000000000 --- a/tests/valgrind-supp-mac.txt +++ /dev/null @@ -1,176 +0,0 @@ -{ - libgit2-git-error-set-buffer - Memcheck:Leak - ... - fun:git__realloc - fun:git_str_try_grow - fun:git_str_grow - fun:git_str_vprintf - fun:git_error_set -} -{ - mac-setenv-leak-1 - Memcheck:Leak - fun:malloc_zone_malloc - fun:__setenv - fun:setenv -} -{ - mac-setenv-leak-2 - Memcheck:Leak - fun:malloc_zone_malloc - fun:malloc_set_zone_name - ... - fun:init__zone0 - fun:setenv -} -{ - mac-dyld-initializer-leak - Memcheck:Leak - fun:malloc - ... - fun:dyld_register_image_state_change_handler - fun:_dyld_initializer -} -{ - mac-tz-leak-1 - Memcheck:Leak - ... - fun:token_table_add - fun:notify_register_check - fun:notify_register_tz -} -{ - mac-tz-leak-2 - Memcheck:Leak - fun:malloc - fun:tzload -} -{ - mac-tz-leak-3 - Memcheck:Leak - fun:malloc - fun:tzsetwall_basic -} -{ - mac-tz-leak-4 - Memcheck:Leak - fun:malloc - fun:gmtsub -} -{ - mac-system-init-leak-1 - Memcheck:Leak - ... - fun:_libxpc_initializer - fun:libSystem_initializer -} -{ - mac-system-init-leak-2 - Memcheck:Leak - ... - fun:__keymgr_initializer - fun:libSystem_initializer -} -{ - mac-puts-leak - Memcheck:Leak - fun:malloc - fun:__smakebuf - ... - fun:puts -} -{ - mac-ssl-uninitialized-1 - Memcheck:Cond - obj:/usr/lib/libcrypto.0.9.8.dylib - ... - fun:ssl23_connect -} -{ - mac-ssl-uninitialized-2 - Memcheck:Cond - ... - obj:/usr/lib/libssl.0.9.8.dylib - ... - fun:ssl23_connect -} -{ - mac-ssl-uninitialized-3 - Memcheck:Value8 - obj:/usr/lib/libcrypto.0.9.8.dylib - ... - fun:ssl23_connect -} -{ - mac-ssl-leak-1 - Memcheck:Leak - ... - fun:ERR_load_strings -} -{ - mac-ssl-leak-2 - Memcheck:Leak - ... - fun:SSL_library_init -} -{ - mac-ssl-leak-3 - Memcheck:Leak - ... - fun:si_module_with_name - fun:getaddrinfo -} -{ - mac-ssl-leak-4 - Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc - ... - fun:ssl3_get_server_certificate -} -{ - mac-ssl-leak-5 - Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc - ... - fun:ERR_put_error -} -{ - clar-printf-buf - Memcheck:Leak - fun:malloc - fun:__smakebuf - ... - fun:printf - fun:clar_print_init -} -{ - molo-1 - Memcheck:Leak - fun:malloc_zone_malloc - ... - fun:_objc_init -} -{ - molo-2 - Memcheck:Leak - fun:malloc_zone_calloc - ... - fun:_objc_init -} -{ - molo-3 - Memcheck:Leak - fun:malloc - ... - fun:_objc_init -} -{ - molo-4 - Memcheck:Leak - fun:malloc - ... - fun:dyld_register_image_state_change_handler -} diff --git a/tests/win32/forbidden.c b/tests/win32/forbidden.c deleted file mode 100644 index 5c007987b..000000000 --- a/tests/win32/forbidden.c +++ /dev/null @@ -1,182 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" -#include "submodule.h" - -static const char *repo_name = "win32-forbidden"; -static git_repository *repo; - -void test_win32_forbidden__initialize(void) -{ - repo = cl_git_sandbox_init(repo_name); -} - -void test_win32_forbidden__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -void test_win32_forbidden__can_open_index(void) -{ - git_index *index; - cl_git_pass(git_repository_index(&index, repo)); - cl_assert_equal_i(7, git_index_entrycount(index)); - - /* ensure we can even write the unmodified index */ - cl_git_pass(git_index_write(index)); - - git_index_free(index); -} - -void test_win32_forbidden__can_add_forbidden_filename_with_entry(void) -{ - git_index *index; - git_index_entry entry = {{0}}; - - cl_git_pass(git_repository_index(&index, repo)); - - entry.path = "aux"; - entry.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&entry.id, "da623abd956bb2fd8052c708c7ed43f05d192d37"); - - cl_git_pass(git_index_add(index, &entry)); - - git_index_free(index); -} - -void test_win32_forbidden__cannot_add_dot_git_even_with_entry(void) -{ - git_index *index; - git_index_entry entry = {{0}}; - - cl_git_pass(git_repository_index(&index, repo)); - - entry.path = "foo/.git"; - entry.mode = GIT_FILEMODE_BLOB; - git_oid_fromstr(&entry.id, "da623abd956bb2fd8052c708c7ed43f05d192d37"); - - cl_git_fail(git_index_add(index, &entry)); - - git_index_free(index); -} - -void test_win32_forbidden__cannot_add_forbidden_filename_from_filesystem(void) -{ - git_index *index; - - /* since our function calls are very low-level, we can create `aux.`, - * but we should not be able to add it to the index - */ - cl_git_pass(git_repository_index(&index, repo)); - cl_git_write2file("win32-forbidden/aux.", "foo\n", 4, O_RDWR | O_CREAT, 0666); - -#ifdef GIT_WIN32 - cl_git_fail(git_index_add_bypath(index, "aux.")); -#else - cl_git_pass(git_index_add_bypath(index, "aux.")); -#endif - - cl_must_pass(p_unlink("win32-forbidden/aux.")); - git_index_free(index); -} - -static int dummy_submodule_cb( - git_submodule *sm, const char *name, void *payload) -{ - GIT_UNUSED(sm); - GIT_UNUSED(name); - GIT_UNUSED(payload); - return 0; -} - -void test_win32_forbidden__can_diff_tree_to_index(void) -{ - git_diff *diff; - git_tree *tree; - - cl_git_pass(git_repository_head_tree(&tree, repo)); - cl_git_pass(git_diff_tree_to_index(&diff, repo, tree, NULL, NULL)); - cl_assert_equal_i(0, git_diff_num_deltas(diff)); - git_diff_free(diff); - git_tree_free(tree); -} - -void test_win32_forbidden__can_diff_tree_to_tree(void) -{ - git_diff *diff; - git_tree *tree; - - cl_git_pass(git_repository_head_tree(&tree, repo)); - cl_git_pass(git_diff_tree_to_tree(&diff, repo, tree, tree, NULL)); - cl_assert_equal_i(0, git_diff_num_deltas(diff)); - git_diff_free(diff); - git_tree_free(tree); -} - -void test_win32_forbidden__can_diff_index_to_workdir(void) -{ - git_index *index; - git_diff *diff; - const git_diff_delta *delta; - git_tree *tree; - size_t i; - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_repository_head_tree(&tree, repo)); - cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, NULL)); - - for (i = 0; i < git_diff_num_deltas(diff); i++) { - delta = git_diff_get_delta(diff, i); - cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); - } - - git_diff_free(diff); - git_tree_free(tree); - git_index_free(index); -} - -void test_win32_forbidden__checking_out_forbidden_index_fails(void) -{ -#ifdef GIT_WIN32 - git_index *index; - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_diff *diff; - const git_diff_delta *delta; - git_tree *tree; - size_t num_deltas, i; - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_fail(git_checkout_index(repo, index, &opts)); - - cl_git_pass(git_repository_head_tree(&tree, repo)); - cl_git_pass(git_diff_index_to_workdir(&diff, repo, index, NULL)); - - num_deltas = git_diff_num_deltas(diff); - - cl_assert(num_deltas > 0); - - for (i = 0; i < num_deltas; i++) { - delta = git_diff_get_delta(diff, i); - cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); - } - - git_diff_free(diff); - git_tree_free(tree); - git_index_free(index); -#endif -} - -void test_win32_forbidden__can_query_submodules(void) -{ - cl_git_pass(git_submodule_foreach(repo, dummy_submodule_cb, NULL)); -} - -void test_win32_forbidden__can_blame_file(void) -{ - git_blame *blame; - - cl_git_pass(git_blame_file(&blame, repo, "aux", NULL)); - git_blame_free(blame); -} diff --git a/tests/win32/longpath.c b/tests/win32/longpath.c deleted file mode 100644 index 4be86db5a..000000000 --- a/tests/win32/longpath.c +++ /dev/null @@ -1,130 +0,0 @@ -#include "clar_libgit2.h" - -#include "git2/clone.h" -#include "clone.h" -#include "futils.h" -#include "repository.h" - -static git_str path = GIT_STR_INIT; - -#define LONG_FILENAME "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt" - -void test_win32_longpath__initialize(void) -{ -#ifdef GIT_WIN32 - const char *base = clar_sandbox_path(); - size_t base_len = strlen(base); - size_t remain = MAX_PATH - base_len; - size_t i; - - git_str_clear(&path); - git_str_puts(&path, base); - git_str_putc(&path, '/'); - - cl_assert(remain < (MAX_PATH - 5)); - - for (i = 0; i < (remain - 5); i++) - git_str_putc(&path, 'a'); -#endif -} - -void test_win32_longpath__cleanup(void) -{ - git_str_dispose(&path); - cl_git_sandbox_cleanup(); -} - -void test_win32_longpath__errmsg_on_checkout(void) -{ -#ifdef GIT_WIN32 - git_repository *repo; - - cl_git_fail(git_clone(&repo, cl_fixture("testrepo.git"), path.ptr, NULL)); - cl_assert(git__prefixcmp(git_error_last()->message, "path too long") == 0); -#endif -} - -void test_win32_longpath__workdir_path_validated(void) -{ -#ifdef GIT_WIN32 - git_repository *repo = cl_git_sandbox_init("testrepo"); - git_str out = GIT_STR_INIT; - - cl_git_pass(git_repository_workdir_path(&out, repo, "a.txt")); - - /* even if the repo path is a drive letter, this is too long */ - cl_git_fail(git_repository_workdir_path(&out, repo, LONG_FILENAME)); - cl_assert(git__prefixcmp(git_error_last()->message, "path too long") == 0); - - cl_repo_set_bool(repo, "core.longpaths", true); - cl_git_pass(git_repository_workdir_path(&out, repo, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt")); - cl_git_pass(git_repository_workdir_path(&out, repo, LONG_FILENAME)); - git_str_dispose(&out); -#endif -} - -#ifdef GIT_WIN32 -static void assert_longpath_status_and_add(git_repository *repo, const char *wddata, const char *repodata) { - git_index *index; - git_blob *blob; - git_str out = GIT_STR_INIT; - const git_index_entry *entry; - unsigned int status_flags; - - cl_git_pass(git_repository_workdir_path(&out, repo, LONG_FILENAME)); - - cl_git_rewritefile(out.ptr, wddata); - - cl_git_pass(git_status_file(&status_flags, repo, LONG_FILENAME)); - cl_assert_equal_i(GIT_STATUS_WT_NEW, status_flags); - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, LONG_FILENAME)); - - cl_git_pass(git_status_file(&status_flags, repo, LONG_FILENAME)); - cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status_flags); - - cl_assert((entry = git_index_get_bypath(index, LONG_FILENAME, 0)) != NULL); - cl_git_pass(git_blob_lookup(&blob, repo, &entry->id)); - cl_assert_equal_s(repodata, git_blob_rawcontent(blob)); - - git_blob_free(blob); - git_index_free(index); - git_str_dispose(&out); -} -#endif - -void test_win32_longpath__status_and_add(void) -{ -#ifdef GIT_WIN32 - git_repository *repo = cl_git_sandbox_init("testrepo"); - - cl_repo_set_bool(repo, "core.longpaths", true); - - /* - * Doing no content filtering, we expect the data we add - * to be the data in the repository. - */ - assert_longpath_status_and_add(repo, - "This is a long path.\r\n", - "This is a long path.\r\n"); -#endif -} - -void test_win32_longpath__status_and_add_with_filter(void) -{ -#ifdef GIT_WIN32 - git_repository *repo = cl_git_sandbox_init("testrepo"); - - cl_repo_set_bool(repo, "core.longpaths", true); - cl_repo_set_bool(repo, "core.autocrlf", true); - - /* - * With `core.autocrlf`, we expect the data we add to have - * newline conversion performed. - */ - assert_longpath_status_and_add(repo, - "This is a long path.\r\n", - "This is a long path.\n"); -#endif -} diff --git a/tests/win32/systemdir.c b/tests/win32/systemdir.c deleted file mode 100644 index 52c1784a1..000000000 --- a/tests/win32/systemdir.c +++ /dev/null @@ -1,338 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "sysdir.h" -#include "win32/findfile.h" - -#ifdef GIT_WIN32 -static char *path_save; -static git_str gfw_path_root = GIT_STR_INIT; -static git_str gfw_registry_root = GIT_STR_INIT; -#endif - -void test_win32_systemdir__initialize(void) -{ -#ifdef GIT_WIN32 - git_str path_env = GIT_STR_INIT; - - path_save = cl_getenv("PATH"); - git_win32__set_registry_system_dir(L""); - - cl_git_pass(git_str_puts(&path_env, "C:\\GitTempTest\\Foo;\"c:\\program files\\doesnotexisttesttemp\";C:\\fakefakedoesnotexist")); - cl_setenv("PATH", path_env.ptr); - - cl_git_pass(git_str_puts(&gfw_path_root, clar_sandbox_path())); - cl_git_pass(git_str_puts(&gfw_path_root, "/fake_gfw_path_install")); - - cl_git_pass(git_str_puts(&gfw_registry_root, clar_sandbox_path())); - cl_git_pass(git_str_puts(&gfw_registry_root, "/fake_gfw_registry_install")); - - git_str_dispose(&path_env); -#endif -} - -void test_win32_systemdir__cleanup(void) -{ -#ifdef GIT_WIN32 - cl_fixture_cleanup("fake_gfw_path_install"); - cl_fixture_cleanup("fake_gfw_registry_install"); - git_str_dispose(&gfw_path_root); - git_str_dispose(&gfw_registry_root); - - cl_setenv("PATH", path_save); - git__free(path_save); - path_save = NULL; - - git_win32__set_registry_system_dir(NULL); - cl_sandbox_set_search_path_defaults(); -#endif -} - -#ifdef GIT_WIN32 -static void fix_path(git_str *s) -{ - char *c; - - for (c = s->ptr; *c; c++) { - if (*c == '/') - *c = '\\'; - } -} - -static void populate_fake_gfw( - git_str *expected_etc_dir, - const char *root, - const char *token, - bool create_gitconfig, - bool create_mingw64_gitconfig, - bool add_to_path, - bool add_to_registry) -{ - git_str bin_path = GIT_STR_INIT, exe_path = GIT_STR_INIT, - etc_path = GIT_STR_INIT, mingw64_path = GIT_STR_INIT, - config_path = GIT_STR_INIT, path_env = GIT_STR_INIT, - config_data = GIT_STR_INIT; - - cl_git_pass(git_str_puts(&bin_path, root)); - cl_git_pass(git_str_puts(&bin_path, "/cmd")); - cl_git_pass(git_futils_mkdir_r(bin_path.ptr, 0755)); - - cl_git_pass(git_str_puts(&exe_path, bin_path.ptr)); - cl_git_pass(git_str_puts(&exe_path, "/git.cmd")); - cl_git_mkfile(exe_path.ptr, "This is a fake executable."); - - cl_git_pass(git_str_puts(&etc_path, root)); - cl_git_pass(git_str_puts(&etc_path, "/etc")); - cl_git_pass(git_futils_mkdir_r(etc_path.ptr, 0755)); - - cl_git_pass(git_str_puts(&mingw64_path, root)); - cl_git_pass(git_str_puts(&mingw64_path, "/mingw64/etc")); - cl_git_pass(git_futils_mkdir_r(mingw64_path.ptr, 0755)); - - if (create_gitconfig) { - git_str_clear(&config_data); - git_str_printf(&config_data, "[gfw]\n\ttest = etc %s\n", token); - - cl_git_pass(git_str_puts(&config_path, etc_path.ptr)); - cl_git_pass(git_str_puts(&config_path, "/gitconfig")); - cl_git_mkfile(config_path.ptr, config_data.ptr); - } - - if (create_mingw64_gitconfig) { - git_str_clear(&config_data); - git_str_printf(&config_data, "[gfw]\n\ttest = mingw64 %s\n", token); - - git_str_clear(&config_path); - cl_git_pass(git_str_puts(&config_path, mingw64_path.ptr)); - cl_git_pass(git_str_puts(&config_path, "/gitconfig")); - cl_git_mkfile(config_path.ptr, config_data.ptr); - } - - if (add_to_path) { - fix_path(&bin_path); - cl_git_pass(git_str_puts(&path_env, "C:\\GitTempTest\\Foo;\"c:\\program files\\doesnotexisttesttemp\";")); - cl_git_pass(git_str_puts(&path_env, bin_path.ptr)); - cl_git_pass(git_str_puts(&path_env, ";C:\\fakefakedoesnotexist")); - cl_setenv("PATH", path_env.ptr); - } - - if (add_to_registry) { - git_win32_path registry_path; - size_t offset = 0; - - cl_assert(git_win32_path_from_utf8(registry_path, root) >= 0); - if (wcsncmp(registry_path, L"\\\\?\\", CONST_STRLEN("\\\\?\\")) == 0) - offset = CONST_STRLEN("\\\\?\\"); - git_win32__set_registry_system_dir(registry_path + offset); - } - - cl_git_pass(git_str_join(expected_etc_dir, GIT_PATH_LIST_SEPARATOR, expected_etc_dir->ptr, etc_path.ptr)); - cl_git_pass(git_str_join(expected_etc_dir, GIT_PATH_LIST_SEPARATOR, expected_etc_dir->ptr, mingw64_path.ptr)); - - git_str_dispose(&bin_path); - git_str_dispose(&exe_path); - git_str_dispose(&etc_path); - git_str_dispose(&mingw64_path); - git_str_dispose(&config_path); - git_str_dispose(&path_env); - git_str_dispose(&config_data); -} - -static void populate_fake_ecosystem( - git_str *expected_etc_dir, - bool create_gitconfig, - bool create_mingw64_gitconfig, - bool path, - bool registry) -{ - if (path) - populate_fake_gfw(expected_etc_dir, gfw_path_root.ptr, "path", create_gitconfig, create_mingw64_gitconfig, true, false); - - if (registry) - populate_fake_gfw(expected_etc_dir, gfw_registry_root.ptr, "registry", create_gitconfig, create_mingw64_gitconfig, false, true); -} -#endif - -void test_win32_systemdir__finds_etc_in_path(void) -{ -#ifdef GIT_WIN32 - git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; - git_config *cfg; - git_buf value = GIT_BUF_INIT; - - populate_fake_ecosystem(&expected, true, false, true, false); - - cl_git_pass(git_win32__find_system_dirs(&out, "etc")); - cl_assert_equal_s(out.ptr, expected.ptr); - - git_sysdir_reset(); - - cl_git_pass(git_config_open_default(&cfg)); - cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); - cl_assert_equal_s("etc path", value.ptr); - - git_buf_dispose(&value); - git_str_dispose(&expected); - git_str_dispose(&out); - git_config_free(cfg); -#endif -} - -void test_win32_systemdir__finds_mingw64_etc_in_path(void) -{ -#ifdef GIT_WIN32 - git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; - git_config* cfg; - git_buf value = GIT_BUF_INIT; - - populate_fake_ecosystem(&expected, false, true, true, false); - - cl_git_pass(git_win32__find_system_dirs(&out, "etc")); - cl_assert_equal_s(out.ptr, expected.ptr); - - git_sysdir_reset(); - - cl_git_pass(git_config_open_default(&cfg)); - cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); - cl_assert_equal_s("mingw64 path", value.ptr); - - git_buf_dispose(&value); - git_str_dispose(&expected); - git_str_dispose(&out); - git_config_free(cfg); -#endif -} - -void test_win32_systemdir__prefers_etc_to_mingw64_in_path(void) -{ -#ifdef GIT_WIN32 - git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; - git_config* cfg; - git_buf value = GIT_BUF_INIT; - - populate_fake_ecosystem(&expected, true, true, true, false); - - cl_git_pass(git_win32__find_system_dirs(&out, "etc")); - cl_assert_equal_s(out.ptr, expected.ptr); - - git_sysdir_reset(); - - cl_git_pass(git_config_open_default(&cfg)); - cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); - cl_assert_equal_s("etc path", value.ptr); - - git_buf_dispose(&value); - git_str_dispose(&expected); - git_str_dispose(&out); - git_config_free(cfg); -#endif -} - -void test_win32_systemdir__finds_etc_in_registry(void) -{ -#ifdef GIT_WIN32 - git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; - git_config* cfg; - git_buf value = GIT_BUF_INIT; - - populate_fake_ecosystem(&expected, true, false, false, true); - - cl_git_pass(git_win32__find_system_dirs(&out, "etc")); - cl_assert_equal_s(out.ptr, expected.ptr); - - git_sysdir_reset(); - - cl_git_pass(git_config_open_default(&cfg)); - cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); - cl_assert_equal_s("etc registry", value.ptr); - - git_buf_dispose(&value); - git_str_dispose(&expected); - git_str_dispose(&out); - git_config_free(cfg); -#endif -} - -void test_win32_systemdir__finds_mingw64_etc_in_registry(void) -{ -#ifdef GIT_WIN32 - git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; - git_config* cfg; - git_buf value = GIT_BUF_INIT; - - populate_fake_ecosystem(&expected, false, true, false, true); - - cl_git_pass(git_win32__find_system_dirs(&out, "etc")); - cl_assert_equal_s(out.ptr, expected.ptr); - - git_sysdir_reset(); - - cl_git_pass(git_config_open_default(&cfg)); - cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); - cl_assert_equal_s("mingw64 registry", value.ptr); - - git_buf_dispose(&value); - git_str_dispose(&expected); - git_str_dispose(&out); - git_config_free(cfg); -#endif -} - -void test_win32_systemdir__prefers_etc_to_mingw64_in_registry(void) -{ -#ifdef GIT_WIN32 - git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; - git_config* cfg; - git_buf value = GIT_BUF_INIT; - - populate_fake_ecosystem(&expected, true, true, false, true); - - cl_git_pass(git_win32__find_system_dirs(&out, "etc")); - cl_assert_equal_s(out.ptr, expected.ptr); - - git_sysdir_reset(); - - cl_git_pass(git_config_open_default(&cfg)); - cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); - cl_assert_equal_s("etc registry", value.ptr); - - git_buf_dispose(&value); - git_str_dispose(&expected); - git_str_dispose(&out); - git_config_free(cfg); -#endif -} - -void test_win32_systemdir__prefers_path_to_registry(void) -{ -#ifdef GIT_WIN32 - git_str expected = GIT_STR_INIT, out = GIT_STR_INIT; - git_config* cfg; - git_buf value = GIT_BUF_INIT; - - populate_fake_ecosystem(&expected, true, true, true, true); - - cl_git_pass(git_win32__find_system_dirs(&out, "etc")); - cl_assert_equal_s(out.ptr, expected.ptr); - - git_sysdir_reset(); - - cl_git_pass(git_config_open_default(&cfg)); - cl_git_pass(git_config_get_string_buf(&value, cfg, "gfw.test")); - cl_assert_equal_s("etc path", value.ptr); - - git_buf_dispose(&value); - git_str_dispose(&expected); - git_str_dispose(&out); - git_config_free(cfg); -#endif -} - -void test_win32_systemdir__no_git_installed(void) -{ -#ifdef GIT_WIN32 - git_str out = GIT_STR_INIT; - - cl_git_pass(git_win32__find_system_dirs(&out, "etc")); - cl_assert_equal_s(out.ptr, ""); -#endif -} diff --git a/tests/worktree/bare.c b/tests/worktree/bare.c deleted file mode 100644 index 7234dfffd..000000000 --- a/tests/worktree/bare.c +++ /dev/null @@ -1,72 +0,0 @@ -#include "clar_libgit2.h" -#include "worktree_helpers.h" -#include "submodule/submodule_helpers.h" - -#define COMMON_REPO "testrepo.git" -#define WORKTREE_REPO "worktree" - -static git_repository *g_repo; - -void test_worktree_bare__initialize(void) -{ - g_repo = cl_git_sandbox_init(COMMON_REPO); - - cl_assert_equal_i(1, git_repository_is_bare(g_repo)); - cl_assert_equal_i(0, git_repository_is_worktree(g_repo)); -} - -void test_worktree_bare__cleanup(void) -{ - cl_fixture_cleanup(WORKTREE_REPO); - cl_git_sandbox_cleanup(); -} - -void test_worktree_bare__list(void) -{ - git_strarray wts; - - cl_git_pass(git_worktree_list(&wts, g_repo)); - cl_assert_equal_i(wts.count, 0); - - git_strarray_dispose(&wts); -} - -void test_worktree_bare__add(void) -{ - git_worktree *wt; - git_repository *wtrepo; - git_strarray wts; - - cl_git_pass(git_worktree_add(&wt, g_repo, "name", WORKTREE_REPO, NULL)); - - cl_git_pass(git_worktree_list(&wts, g_repo)); - cl_assert_equal_i(wts.count, 1); - - cl_git_pass(git_worktree_validate(wt)); - - cl_git_pass(git_repository_open(&wtrepo, WORKTREE_REPO)); - cl_assert_equal_i(0, git_repository_is_bare(wtrepo)); - cl_assert_equal_i(1, git_repository_is_worktree(wtrepo)); - - git_strarray_dispose(&wts); - git_worktree_free(wt); - git_repository_free(wtrepo); -} - -void test_worktree_bare__repository_path(void) -{ - git_worktree *wt; - git_repository *wtrepo; - - cl_git_pass(git_worktree_add(&wt, g_repo, "name", WORKTREE_REPO, NULL)); - cl_assert_equal_s(git_worktree_path(wt), cl_git_sandbox_path(0, WORKTREE_REPO, NULL)); - - cl_git_pass(git_repository_open(&wtrepo, WORKTREE_REPO)); - cl_assert_equal_s(git_repository_path(wtrepo), cl_git_sandbox_path(1, COMMON_REPO, "worktrees", "name", NULL)); - - cl_assert_equal_s(git_repository_commondir(g_repo), git_repository_commondir(wtrepo)); - cl_assert_equal_s(git_repository_workdir(wtrepo), cl_git_sandbox_path(1, WORKTREE_REPO, NULL)); - - git_repository_free(wtrepo); - git_worktree_free(wt); -} diff --git a/tests/worktree/config.c b/tests/worktree/config.c deleted file mode 100644 index 81dcfe1fa..000000000 --- a/tests/worktree/config.c +++ /dev/null @@ -1,47 +0,0 @@ -#include "clar_libgit2.h" -#include "worktree_helpers.h" - -#define COMMON_REPO "testrepo" -#define WORKTREE_REPO "testrepo-worktree" - -static worktree_fixture fixture = - WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); - -void test_worktree_config__initialize(void) -{ - setup_fixture_worktree(&fixture); -} - -void test_worktree_config__cleanup(void) -{ - cleanup_fixture_worktree(&fixture); -} - -void test_worktree_config__open(void) -{ - git_config *cfg; - - cl_git_pass(git_repository_config(&cfg, fixture.worktree)); - cl_assert(cfg != NULL); - - git_config_free(cfg); -} - -void test_worktree_config__set(void) -{ - git_config *cfg; - int32_t val; - - cl_git_pass(git_repository_config(&cfg, fixture.worktree)); - cl_git_pass(git_config_set_int32(cfg, "core.dummy", 5)); - git_config_free(cfg); - - /* - * reopen to verify configuration has been set in the - * common dir - */ - cl_git_pass(git_repository_config(&cfg, fixture.repo)); - cl_git_pass(git_config_get_int32(&val, cfg, "core.dummy")); - cl_assert_equal_i(val, 5); - git_config_free(cfg); -} diff --git a/tests/worktree/merge.c b/tests/worktree/merge.c deleted file mode 100644 index 5b7e2a837..000000000 --- a/tests/worktree/merge.c +++ /dev/null @@ -1,121 +0,0 @@ -#include "clar_libgit2.h" - -#include "worktree_helpers.h" -#include "merge/merge_helpers.h" - -#define COMMON_REPO "testrepo" -#define WORKTREE_REPO "testrepo-worktree" - -#define MASTER_BRANCH "refs/heads/master" -#define CONFLICT_BRANCH "refs/heads/merge-conflict" - -#define CONFLICT_BRANCH_FILE_TXT \ - "<<<<<<< HEAD\n" \ - "hi\n" \ - "bye!\n" \ - "=======\n" \ - "conflict\n" \ - ">>>>>>> merge-conflict\n" \ - -static worktree_fixture fixture = - WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); - -static const char *merge_files[] = { - GIT_MERGE_HEAD_FILE, - GIT_ORIG_HEAD_FILE, - GIT_MERGE_MODE_FILE, - GIT_MERGE_MSG_FILE, -}; - -void test_worktree_merge__initialize(void) -{ - setup_fixture_worktree(&fixture); -} - -void test_worktree_merge__cleanup(void) -{ - cleanup_fixture_worktree(&fixture); -} - -void test_worktree_merge__merge_head(void) -{ - git_reference *theirs_ref, *ref; - git_annotated_commit *theirs; - - cl_git_pass(git_reference_lookup(&theirs_ref, fixture.worktree, CONFLICT_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&theirs, fixture.worktree, theirs_ref)); - cl_git_pass(git_merge(fixture.worktree, (const git_annotated_commit **)&theirs, 1, NULL, NULL)); - - cl_git_pass(git_reference_lookup(&ref, fixture.worktree, GIT_MERGE_HEAD_FILE)); - - git_reference_free(ref); - git_reference_free(theirs_ref); - git_annotated_commit_free(theirs); -} - -void test_worktree_merge__merge_setup(void) -{ - git_reference *ours_ref, *theirs_ref; - git_annotated_commit *ours, *theirs; - git_str path = GIT_STR_INIT; - unsigned i; - - cl_git_pass(git_reference_lookup(&ours_ref, fixture.worktree, MASTER_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&ours, fixture.worktree, ours_ref)); - - cl_git_pass(git_reference_lookup(&theirs_ref, fixture.worktree, CONFLICT_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&theirs, fixture.worktree, theirs_ref)); - - cl_git_pass(git_merge__setup(fixture.worktree, - ours, (const git_annotated_commit **)&theirs, 1)); - - for (i = 0; i < ARRAY_SIZE(merge_files); i++) { - cl_git_pass(git_str_joinpath(&path, - fixture.worktree->gitdir, - merge_files[i])); - cl_assert(git_fs_path_exists(path.ptr)); - } - - git_str_dispose(&path); - git_reference_free(ours_ref); - git_reference_free(theirs_ref); - git_annotated_commit_free(ours); - git_annotated_commit_free(theirs); -} - -void test_worktree_merge__merge_conflict(void) -{ - git_str path = GIT_STR_INIT, buf = GIT_STR_INIT; - git_reference *theirs_ref; - git_annotated_commit *theirs; - git_index *index; - const git_index_entry *entry; - size_t i, conflicts = 0; - - cl_git_pass(git_reference_lookup(&theirs_ref, fixture.worktree, CONFLICT_BRANCH)); - cl_git_pass(git_annotated_commit_from_ref(&theirs, fixture.worktree, theirs_ref)); - - cl_git_pass(git_merge(fixture.worktree, - (const git_annotated_commit **)&theirs, 1, NULL, NULL)); - - cl_git_pass(git_repository_index(&index, fixture.worktree)); - for (i = 0; i < git_index_entrycount(index); i++) { - cl_assert(entry = git_index_get_byindex(index, i)); - - if (git_index_entry_is_conflict(entry)) - conflicts++; - } - cl_assert_equal_sz(conflicts, 3); - - git_reference_free(theirs_ref); - git_annotated_commit_free(theirs); - git_index_free(index); - - cl_git_pass(git_str_joinpath(&path, fixture.worktree->workdir, "branch_file.txt")); - cl_git_pass(git_futils_readbuffer(&buf, path.ptr)); - cl_assert_equal_s(buf.ptr, CONFLICT_BRANCH_FILE_TXT); - - git_str_dispose(&path); - git_str_dispose(&buf); -} - diff --git a/tests/worktree/open.c b/tests/worktree/open.c deleted file mode 100644 index 0c3fdc173..000000000 --- a/tests/worktree/open.c +++ /dev/null @@ -1,126 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" -#include "worktree.h" -#include "worktree_helpers.h" - -#define COMMON_REPO "testrepo" -#define WORKTREE_REPO "testrepo-worktree" - -static worktree_fixture fixture = - WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); - -static void assert_worktree_valid(git_repository *wt, const char *parentdir, const char *wtdir) -{ - cl_assert(wt->is_worktree); - - cl_assert_equal_s(wt->workdir, cl_git_sandbox_path(1, wtdir, NULL)); - cl_assert_equal_s(wt->gitlink, cl_git_sandbox_path(0, wtdir, ".git", NULL)); - cl_assert_equal_s(wt->gitdir, cl_git_sandbox_path(1, parentdir, ".git", "worktrees", wtdir, NULL)); -} - -void test_worktree_open__initialize(void) -{ - setup_fixture_worktree(&fixture); -} - -void test_worktree_open__cleanup(void) -{ - cleanup_fixture_worktree(&fixture); -} - -void test_worktree_open__repository(void) -{ - assert_worktree_valid(fixture.worktree, COMMON_REPO, WORKTREE_REPO); -} - -void test_worktree_open__repository_through_workdir(void) -{ - git_repository *wt; - - cl_git_pass(git_repository_open(&wt, WORKTREE_REPO)); - assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO); - - git_repository_free(wt); -} - -void test_worktree_open__repository_through_gitlink(void) -{ - git_repository *wt; - - cl_git_pass(git_repository_open(&wt, WORKTREE_REPO "/.git")); - assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO); - - git_repository_free(wt); -} - -void test_worktree_open__repository_through_gitdir(void) -{ - git_str gitdir_path = GIT_STR_INIT; - git_repository *wt; - - cl_git_pass(git_str_joinpath(&gitdir_path, COMMON_REPO, ".git")); - cl_git_pass(git_str_joinpath(&gitdir_path, gitdir_path.ptr, "worktrees")); - cl_git_pass(git_str_joinpath(&gitdir_path, gitdir_path.ptr, "testrepo-worktree")); - - cl_git_pass(git_repository_open(&wt, gitdir_path.ptr)); - assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO); - - git_str_dispose(&gitdir_path); - git_repository_free(wt); -} - -void test_worktree_open__open_discovered_worktree(void) -{ - git_buf path = GIT_BUF_INIT; - git_repository *repo; - - cl_git_pass(git_repository_discover(&path, - git_repository_workdir(fixture.worktree), false, NULL)); - cl_git_pass(git_repository_open(&repo, path.ptr)); - cl_assert_equal_s(git_repository_workdir(fixture.worktree), - git_repository_workdir(repo)); - - git_buf_dispose(&path); - git_repository_free(repo); -} - -void test_worktree_open__repository_with_nonexistent_parent(void) -{ - git_repository *repo; - - cleanup_fixture_worktree(&fixture); - - cl_fixture_sandbox(WORKTREE_REPO); - cl_git_pass(p_chdir(WORKTREE_REPO)); - cl_git_pass(cl_rename(".gitted", ".git")); - cl_git_pass(p_chdir("..")); - - cl_git_fail(git_repository_open(&repo, WORKTREE_REPO)); - - cl_fixture_cleanup(WORKTREE_REPO); -} - -void test_worktree_open__open_from_repository(void) -{ - git_worktree *opened, *lookedup; - - cl_git_pass(git_worktree_open_from_repository(&opened, fixture.worktree)); - cl_git_pass(git_worktree_lookup(&lookedup, fixture.repo, WORKTREE_REPO)); - - cl_assert_equal_s(opened->name, lookedup->name); - cl_assert_equal_s(opened->gitdir_path, lookedup->gitdir_path); - cl_assert_equal_s(opened->gitlink_path, lookedup->gitlink_path); - cl_assert_equal_s(opened->parent_path, lookedup->parent_path); - cl_assert_equal_s(opened->commondir_path, lookedup->commondir_path); - cl_assert_equal_i(opened->locked, lookedup->locked); - - git_worktree_free(opened); - git_worktree_free(lookedup); -} - -void test_worktree_open__open_from_nonworktree_fails(void) -{ - git_worktree *wt; - - cl_git_fail(git_worktree_open_from_repository(&wt, fixture.repo)); -} diff --git a/tests/worktree/reflog.c b/tests/worktree/reflog.c deleted file mode 100644 index a68e72dcf..000000000 --- a/tests/worktree/reflog.c +++ /dev/null @@ -1,91 +0,0 @@ -#include "clar_libgit2.h" -#include "worktree_helpers.h" - -#include "reflog.h" - -#define COMMON_REPO "testrepo" -#define WORKTREE_REPO "testrepo-worktree" - -#define REFLOG "refs/heads/testrepo-worktree" -#define REFLOG_MESSAGE "reflog message" - -static worktree_fixture fixture = - WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); - -void test_worktree_reflog__initialize(void) -{ - setup_fixture_worktree(&fixture); -} - -void test_worktree_reflog__cleanup(void) -{ - cleanup_fixture_worktree(&fixture); -} - -void test_worktree_reflog__read_worktree_HEAD(void) -{ - git_reflog *reflog; - const git_reflog_entry *entry; - - cl_git_pass(git_reflog_read(&reflog, fixture.worktree, "HEAD")); - cl_assert_equal_i(1, git_reflog_entrycount(reflog)); - - entry = git_reflog_entry_byindex(reflog, 0); - cl_assert(entry != NULL); - cl_assert_equal_s("checkout: moving from 099fabac3a9ea935598528c27f866e34089c2eff to testrepo-worktree", git_reflog_entry_message(entry)); - - git_reflog_free(reflog); -} - -void test_worktree_reflog__read_parent_HEAD(void) -{ - git_reflog *reflog; - - cl_git_pass(git_reflog_read(&reflog, fixture.repo, "HEAD")); - /* there is no logs/HEAD in the parent repo */ - cl_assert_equal_i(0, git_reflog_entrycount(reflog)); - - git_reflog_free(reflog); -} - -void test_worktree_reflog__read(void) -{ - git_reflog *reflog; - const git_reflog_entry *entry; - - cl_git_pass(git_reflog_read(&reflog, fixture.worktree, REFLOG)); - cl_assert_equal_i(git_reflog_entrycount(reflog), 1); - - entry = git_reflog_entry_byindex(reflog, 0); - cl_assert(entry != NULL); - cl_assert_equal_s(git_reflog_entry_message(entry), "branch: Created from HEAD"); - - git_reflog_free(reflog); -} - -void test_worktree_reflog__append_then_read(void) -{ - git_reflog *reflog, *parent_reflog; - const git_reflog_entry *entry; - git_reference *head; - git_signature *sig; - const git_oid *oid; - - cl_git_pass(git_repository_head(&head, fixture.worktree)); - cl_assert((oid = git_reference_target(head)) != NULL); - cl_git_pass(git_signature_now(&sig, "foo", "foo@bar")); - - cl_git_pass(git_reflog_read(&reflog, fixture.worktree, REFLOG)); - cl_git_pass(git_reflog_append(reflog, oid, sig, REFLOG_MESSAGE)); - git_reflog_write(reflog); - - cl_git_pass(git_reflog_read(&parent_reflog, fixture.repo, REFLOG)); - entry = git_reflog_entry_byindex(parent_reflog, 0); - cl_assert(git_oid_cmp(oid, &entry->oid_old) == 0); - cl_assert(git_oid_cmp(oid, &entry->oid_cur) == 0); - - git_reference_free(head); - git_signature_free(sig); - git_reflog_free(reflog); - git_reflog_free(parent_reflog); -} diff --git a/tests/worktree/refs.c b/tests/worktree/refs.c deleted file mode 100644 index 557726aaf..000000000 --- a/tests/worktree/refs.c +++ /dev/null @@ -1,198 +0,0 @@ -#include "clar_libgit2.h" -#include "path.h" -#include "refs.h" -#include "worktree.h" -#include "worktree_helpers.h" - -#define COMMON_REPO "testrepo" -#define WORKTREE_REPO "testrepo-worktree" - -static worktree_fixture fixture = - WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); - -void test_worktree_refs__initialize(void) -{ - setup_fixture_worktree(&fixture); -} - -void test_worktree_refs__cleanup(void) -{ - cleanup_fixture_worktree(&fixture); -} - -void test_worktree_refs__list(void) -{ - git_strarray refs, wtrefs; - unsigned i, j; - int error = 0; - - cl_git_pass(git_reference_list(&refs, fixture.repo)); - cl_git_pass(git_reference_list(&wtrefs, fixture.worktree)); - - if (refs.count != wtrefs.count) - { - error = GIT_ERROR; - goto exit; - } - - for (i = 0; i < refs.count; i++) - { - int found = 0; - - for (j = 0; j < wtrefs.count; j++) - { - if (!strcmp(refs.strings[i], wtrefs.strings[j])) - { - found = 1; - break; - } - } - - if (!found) - { - error = GIT_ERROR; - goto exit; - } - } - -exit: - git_strarray_dispose(&refs); - git_strarray_dispose(&wtrefs); - cl_git_pass(error); -} - -void test_worktree_refs__read_head(void) -{ - git_reference *head; - - cl_git_pass(git_repository_head(&head, fixture.worktree)); - - git_reference_free(head); -} - -void test_worktree_refs__set_head_fails_when_worktree_wants_linked_repos_HEAD(void) -{ - git_reference *head; - - cl_git_pass(git_repository_head(&head, fixture.repo)); - cl_git_fail(git_repository_set_head(fixture.worktree, git_reference_name(head))); - - git_reference_free(head); -} - -void test_worktree_refs__set_head_fails_when_main_repo_wants_worktree_head(void) -{ - git_reference *head; - - cl_git_pass(git_repository_head(&head, fixture.worktree)); - cl_git_fail(git_repository_set_head(fixture.repo, git_reference_name(head))); - - git_reference_free(head); -} - -void test_worktree_refs__set_head_works_for_current_HEAD(void) -{ - git_reference *head; - - cl_git_pass(git_repository_head(&head, fixture.repo)); - cl_git_pass(git_repository_set_head(fixture.repo, git_reference_name(head))); - - git_reference_free(head); -} - -void test_worktree_refs__set_head_fails_when_already_checked_out(void) -{ - cl_git_fail(git_repository_set_head(fixture.repo, "refs/heads/testrepo-worktree")); -} - -void test_worktree_refs__delete_fails_for_checked_out_branch(void) -{ - git_reference *branch; - - cl_git_pass(git_branch_lookup(&branch, fixture.repo, - "testrepo-worktree", GIT_BRANCH_LOCAL)); - cl_git_fail(git_branch_delete(branch)); - - git_reference_free(branch); -} - -void test_worktree_refs__delete_succeeds_after_pruning_worktree(void) -{ - git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; - git_reference *branch; - git_worktree *worktree; - - opts.flags = GIT_WORKTREE_PRUNE_VALID; - - cl_git_pass(git_worktree_lookup(&worktree, fixture.repo, fixture.worktreename)); - cl_git_pass(git_worktree_prune(worktree, &opts)); - git_worktree_free(worktree); - - cl_git_pass(git_branch_lookup(&branch, fixture.repo, - "testrepo-worktree", GIT_BRANCH_LOCAL)); - cl_git_pass(git_branch_delete(branch)); - git_reference_free(branch); -} - -void test_worktree_refs__delete_unrelated_branch_on_worktree(void) -{ - git_reference *branch; - - cl_git_pass(git_branch_lookup(&branch, fixture.worktree, - "merge-conflict", GIT_BRANCH_LOCAL)); - cl_git_pass(git_branch_delete(branch)); - - git_reference_free(branch); -} - -void test_worktree_refs__delete_unrelated_branch_on_parent(void) -{ - git_reference *branch; - - cl_git_pass(git_branch_lookup(&branch, fixture.repo, - "merge-conflict", GIT_BRANCH_LOCAL)); - cl_git_pass(git_branch_delete(branch)); - - git_reference_free(branch); -} - -void test_worktree_refs__renaming_reference_updates_worktree_heads(void) -{ - git_reference *head, *branch, *renamed; - - cl_git_pass(git_branch_lookup(&branch, fixture.repo, - "testrepo-worktree", GIT_BRANCH_LOCAL)); - cl_git_pass(git_reference_rename(&renamed, branch, "refs/heads/renamed", 0, NULL)); - - cl_git_pass(git_reference_lookup(&head, fixture.worktree, GIT_HEAD_FILE)); - cl_assert_equal_i(git_reference_type(head), GIT_REFERENCE_SYMBOLIC); - cl_assert_equal_s(git_reference_symbolic_target(head), "refs/heads/renamed"); - - git_reference_free(head); - git_reference_free(branch); - git_reference_free(renamed); -} - -void test_worktree_refs__creating_refs_uses_commondir(void) -{ - git_reference *head, *branch, *lookup; - git_commit *commit; - git_str refpath = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&refpath, - git_repository_commondir(fixture.worktree), "refs/heads/testbranch")); - cl_assert(!git_fs_path_exists(refpath.ptr)); - - cl_git_pass(git_repository_head(&head, fixture.worktree)); - cl_git_pass(git_commit_lookup(&commit, fixture.worktree, git_reference_target(head))); - cl_git_pass(git_branch_create(&branch, fixture.worktree, "testbranch", commit, 0)); - cl_git_pass(git_branch_lookup(&lookup, fixture.worktree, "testbranch", GIT_BRANCH_LOCAL)); - cl_assert(git_reference_cmp(branch, lookup) == 0); - cl_assert(git_fs_path_exists(refpath.ptr)); - - git_reference_free(lookup); - git_reference_free(branch); - git_reference_free(head); - git_commit_free(commit); - git_str_dispose(&refpath); -} diff --git a/tests/worktree/repository.c b/tests/worktree/repository.c deleted file mode 100644 index c4eeadd35..000000000 --- a/tests/worktree/repository.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "clar_libgit2.h" -#include "worktree_helpers.h" -#include "submodule/submodule_helpers.h" - -#include "repository.h" - -#define COMMON_REPO "testrepo" -#define WORKTREE_REPO "testrepo-worktree" - -static worktree_fixture fixture = - WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); - -void test_worktree_repository__initialize(void) -{ - setup_fixture_worktree(&fixture); -} - -void test_worktree_repository__cleanup(void) -{ - cleanup_fixture_worktree(&fixture); -} - -void test_worktree_repository__head(void) -{ - git_reference *ref, *head; - - cl_git_pass(git_reference_lookup(&ref, fixture.repo, "refs/heads/testrepo-worktree")); - cl_git_pass(git_repository_head_for_worktree(&head, fixture.repo, "testrepo-worktree")); - cl_assert(git_reference_cmp(ref, head) == 0); - cl_assert(git_reference_owner(ref) == fixture.repo); - - git_reference_free(ref); - git_reference_free(head); -} - -void test_worktree_repository__head_fails_for_invalid_worktree(void) -{ - git_reference *head = NULL; - - cl_git_fail(git_repository_head_for_worktree(&head, fixture.repo, "invalid")); - cl_assert(head == NULL); -} - -void test_worktree_repository__head_detached(void) -{ - git_reference *ref, *head; - - cl_git_pass(git_reference_lookup(&ref, fixture.repo, "refs/heads/testrepo-worktree")); - cl_git_pass(git_repository_set_head_detached(fixture.worktree, &ref->target.oid)); - - cl_assert(git_repository_head_detached(fixture.worktree)); - cl_assert(git_repository_head_detached_for_worktree(fixture.repo, "testrepo-worktree")); - cl_git_pass(git_repository_head_for_worktree(&head, fixture.repo, "testrepo-worktree")); - - cl_assert_equal_oid(&ref->target.oid, &head->target.oid); - - git_reference_free(ref); - git_reference_free(head); -} - -void test_worktree_repository__head_detached_fails_for_invalid_worktree(void) -{ - git_reference *head = NULL; - - cl_git_fail(git_repository_head_detached_for_worktree(fixture.repo, "invalid")); - cl_assert(head == NULL); -} diff --git a/tests/worktree/submodule.c b/tests/worktree/submodule.c deleted file mode 100644 index 6b0c07452..000000000 --- a/tests/worktree/submodule.c +++ /dev/null @@ -1,92 +0,0 @@ -#include "clar_libgit2.h" -#include "repository.h" -#include "worktree.h" -#include "worktree_helpers.h" - -#define WORKTREE_PARENT "submodules-worktree-parent" -#define WORKTREE_CHILD "submodules-worktree-child" - -static worktree_fixture parent - = WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT); -static worktree_fixture child - = WORKTREE_FIXTURE_INIT(NULL, WORKTREE_CHILD); - -void test_worktree_submodule__initialize(void) -{ - setup_fixture_worktree(&parent); - - cl_git_pass(p_rename( - "submodules/testrepo/.gitted", - "submodules/testrepo/.git")); - - setup_fixture_worktree(&child); -} - -void test_worktree_submodule__cleanup(void) -{ - cleanup_fixture_worktree(&child); - cleanup_fixture_worktree(&parent); -} - -void test_worktree_submodule__submodule_worktree_parent(void) -{ - cl_assert(git_repository_path(parent.worktree) != NULL); - cl_assert(git_repository_workdir(parent.worktree) != NULL); - - cl_assert(!parent.repo->is_worktree); - cl_assert(parent.worktree->is_worktree); -} - -void test_worktree_submodule__submodule_worktree_child(void) -{ - cl_assert(!parent.repo->is_worktree); - cl_assert(parent.worktree->is_worktree); - cl_assert(child.worktree->is_worktree); -} - -void test_worktree_submodule__open_discovered_submodule_worktree(void) -{ - git_buf path = GIT_BUF_INIT; - git_repository *repo; - - cl_git_pass(git_repository_discover(&path, - git_repository_workdir(child.worktree), false, NULL)); - cl_git_pass(git_repository_open(&repo, path.ptr)); - cl_assert_equal_s(git_repository_workdir(child.worktree), - git_repository_workdir(repo)); - - git_buf_dispose(&path); - git_repository_free(repo); -} - -void test_worktree_submodule__resolve_relative_url(void) -{ - git_str wt_path = GIT_STR_INIT; - git_buf sm_relative_path = GIT_BUF_INIT, wt_relative_path = GIT_BUF_INIT; - git_repository *repo; - git_worktree *wt; - - cl_git_pass(git_futils_mkdir("subdir", 0755, GIT_MKDIR_PATH)); - cl_git_pass(git_fs_path_prettify_dir(&wt_path, "subdir", NULL)); - cl_git_pass(git_str_joinpath(&wt_path, wt_path.ptr, "wt")); - - /* Open child repository, which is a submodule */ - cl_git_pass(git_repository_open(&child.repo, WORKTREE_CHILD)); - - /* Create worktree of submodule repository */ - cl_git_pass(git_worktree_add(&wt, child.repo, "subdir", wt_path.ptr, NULL)); - cl_git_pass(git_repository_open_from_worktree(&repo, wt)); - - cl_git_pass(git_submodule_resolve_url(&sm_relative_path, repo, - "../" WORKTREE_CHILD)); - cl_git_pass(git_submodule_resolve_url(&wt_relative_path, child.repo, - "../" WORKTREE_CHILD)); - - cl_assert_equal_s(sm_relative_path.ptr, wt_relative_path.ptr); - - git_worktree_free(wt); - git_repository_free(repo); - git_str_dispose(&wt_path); - git_buf_dispose(&sm_relative_path); - git_buf_dispose(&wt_relative_path); -} diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c deleted file mode 100644 index 66273d1cb..000000000 --- a/tests/worktree/worktree.c +++ /dev/null @@ -1,647 +0,0 @@ -#include "clar_libgit2.h" -#include "worktree_helpers.h" -#include "submodule/submodule_helpers.h" - -#include "checkout.h" -#include "repository.h" -#include "worktree.h" - -#define COMMON_REPO "testrepo" -#define WORKTREE_REPO "testrepo-worktree" - -static worktree_fixture fixture = - WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); - -void test_worktree_worktree__initialize(void) -{ - setup_fixture_worktree(&fixture); -} - -void test_worktree_worktree__cleanup(void) -{ - cleanup_fixture_worktree(&fixture); -} - -void test_worktree_worktree__list(void) -{ - git_strarray wts; - - cl_git_pass(git_worktree_list(&wts, fixture.repo)); - cl_assert_equal_i(wts.count, 1); - cl_assert_equal_s(wts.strings[0], "testrepo-worktree"); - - git_strarray_dispose(&wts); -} - -void test_worktree_worktree__list_with_invalid_worktree_dirs(void) -{ - const char *filesets[3][2] = { - { "gitdir", "commondir" }, - { "gitdir", "HEAD" }, - { "HEAD", "commondir" }, - }; - git_str path = GIT_STR_INIT; - git_strarray wts; - size_t i, j, len; - - cl_git_pass(git_str_joinpath(&path, - fixture.repo->commondir, - "worktrees/invalid")); - cl_git_pass(p_mkdir(path.ptr, 0755)); - - len = path.size; - - for (i = 0; i < ARRAY_SIZE(filesets); i++) { - - for (j = 0; j < ARRAY_SIZE(filesets[i]); j++) { - git_str_truncate(&path, len); - cl_git_pass(git_str_joinpath(&path, path.ptr, filesets[i][j])); - cl_git_pass(p_close(p_creat(path.ptr, 0644))); - } - - cl_git_pass(git_worktree_list(&wts, fixture.worktree)); - cl_assert_equal_i(wts.count, 1); - cl_assert_equal_s(wts.strings[0], "testrepo-worktree"); - git_strarray_dispose(&wts); - - for (j = 0; j < ARRAY_SIZE(filesets[i]); j++) { - git_str_truncate(&path, len); - cl_git_pass(git_str_joinpath(&path, path.ptr, filesets[i][j])); - p_unlink(path.ptr); - } - } - - git_str_dispose(&path); -} - -void test_worktree_worktree__list_in_worktree_repo(void) -{ - git_strarray wts; - - cl_git_pass(git_worktree_list(&wts, fixture.worktree)); - cl_assert_equal_i(wts.count, 1); - cl_assert_equal_s(wts.strings[0], "testrepo-worktree"); - - git_strarray_dispose(&wts); -} - -void test_worktree_worktree__list_without_worktrees(void) -{ - git_repository *repo; - git_strarray wts; - - repo = cl_git_sandbox_init("testrepo2"); - cl_git_pass(git_worktree_list(&wts, repo)); - cl_assert_equal_i(wts.count, 0); - - git_repository_free(repo); -} - -void test_worktree_worktree__lookup(void) -{ - git_worktree *wt; - git_str gitdir_path = GIT_STR_INIT; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - - cl_git_pass(git_str_joinpath(&gitdir_path, fixture.repo->commondir, "worktrees/testrepo-worktree/")); - - cl_assert_equal_s(wt->gitdir_path, gitdir_path.ptr); - cl_assert_equal_s(wt->parent_path, fixture.repo->workdir); - cl_assert_equal_s(wt->gitlink_path, fixture.worktree->gitlink); - cl_assert_equal_s(wt->commondir_path, fixture.repo->gitdir); - cl_assert_equal_s(wt->commondir_path, fixture.repo->commondir); - - git_str_dispose(&gitdir_path); - git_worktree_free(wt); -} - -void test_worktree_worktree__lookup_nonexistent_worktree(void) -{ - git_worktree *wt; - - cl_git_fail(git_worktree_lookup(&wt, fixture.repo, "nonexistent")); - cl_assert_equal_p(wt, NULL); -} - -void test_worktree_worktree__open(void) -{ - git_worktree *wt; - git_repository *repo; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - - cl_git_pass(git_repository_open_from_worktree(&repo, wt)); - cl_assert_equal_s(git_repository_workdir(repo), - git_repository_workdir(fixture.worktree)); - - git_repository_free(repo); - git_worktree_free(wt); -} - -void test_worktree_worktree__open_invalid_commondir(void) -{ - git_worktree *wt; - git_repository *repo; - git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; - - cl_git_pass(git_str_sets(&buf, "/path/to/nonexistent/commondir")); - cl_git_pass(git_str_joinpath(&path, - fixture.repo->commondir, - "worktrees/testrepo-worktree/commondir")); - cl_git_pass(git_futils_writebuffer(&buf, path.ptr, O_RDWR, 0644)); - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_git_fail(git_repository_open_from_worktree(&repo, wt)); - - git_str_dispose(&buf); - git_str_dispose(&path); - git_worktree_free(wt); -} - -void test_worktree_worktree__open_invalid_gitdir(void) -{ - git_worktree *wt; - git_repository *repo; - git_str buf = GIT_STR_INIT, path = GIT_STR_INIT; - - cl_git_pass(git_str_sets(&buf, "/path/to/nonexistent/gitdir")); - cl_git_pass(git_str_joinpath(&path, - fixture.repo->commondir, - "worktrees/testrepo-worktree/gitdir")); - cl_git_pass(git_futils_writebuffer(&buf, path.ptr, O_RDWR, 0644)); - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_git_fail(git_repository_open_from_worktree(&repo, wt)); - - git_str_dispose(&buf); - git_str_dispose(&path); - git_worktree_free(wt); -} - -void test_worktree_worktree__open_invalid_parent(void) -{ - git_worktree *wt; - git_repository *repo; - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_sets(&buf, "/path/to/nonexistent/gitdir")); - cl_git_pass(git_futils_writebuffer(&buf, - fixture.worktree->gitlink, O_RDWR, 0644)); - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_git_fail(git_repository_open_from_worktree(&repo, wt)); - - git_str_dispose(&buf); - git_worktree_free(wt); -} - -void test_worktree_worktree__init(void) -{ - git_worktree *wt; - git_repository *repo; - git_reference *branch; - git_str path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-new")); - cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr, NULL)); - - /* Open and verify created repo */ - cl_git_pass(git_repository_open(&repo, path.ptr)); - cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-new/") == 0); - cl_git_pass(git_branch_lookup(&branch, repo, "worktree-new", GIT_BRANCH_LOCAL)); - - git_str_dispose(&path); - git_worktree_free(wt); - git_reference_free(branch); - git_repository_free(repo); -} - -void test_worktree_worktree__add_locked(void) -{ - git_worktree *wt; - git_repository *repo; - git_reference *branch; - git_str path = GIT_STR_INIT; - git_worktree_add_options opts = GIT_WORKTREE_ADD_OPTIONS_INIT; - - opts.lock = 1; - - cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-locked")); - cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-locked", path.ptr, &opts)); - - /* Open and verify created repo */ - cl_assert(git_worktree_is_locked(NULL, wt)); - cl_git_pass(git_repository_open(&repo, path.ptr)); - cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-locked/") == 0); - cl_git_pass(git_branch_lookup(&branch, repo, "worktree-locked", GIT_BRANCH_LOCAL)); - - git_str_dispose(&path); - git_worktree_free(wt); - git_reference_free(branch); - git_repository_free(repo); -} - -void test_worktree_worktree__init_existing_branch(void) -{ - git_reference *head, *branch; - git_commit *commit; - git_worktree *wt; - git_str path = GIT_STR_INIT; - - cl_git_pass(git_repository_head(&head, fixture.repo)); - cl_git_pass(git_commit_lookup(&commit, fixture.repo, &head->target.oid)); - cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-new", commit, false)); - - cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-new")); - cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr, NULL)); - - git_str_dispose(&path); - git_commit_free(commit); - git_reference_free(head); - git_reference_free(branch); -} - -void test_worktree_worktree__add_with_explicit_branch(void) -{ - git_reference *head, *branch, *wthead; - git_commit *commit; - git_worktree *wt; - git_repository *wtrepo; - git_str path = GIT_STR_INIT; - git_worktree_add_options opts = GIT_WORKTREE_ADD_OPTIONS_INIT; - - cl_git_pass(git_repository_head(&head, fixture.repo)); - cl_git_pass(git_commit_lookup(&commit, fixture.repo, &head->target.oid)); - cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-with-ref", commit, false)); - - opts.ref = branch; - - cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-with-different-name")); - cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-with-different-name", path.ptr, &opts)); - cl_git_pass(git_repository_open_from_worktree(&wtrepo, wt)); - cl_git_pass(git_repository_head(&wthead, wtrepo)); - cl_assert_equal_s(git_reference_name(wthead), "refs/heads/worktree-with-ref"); - - git_str_dispose(&path); - git_commit_free(commit); - git_reference_free(head); - git_reference_free(branch); - git_reference_free(wthead); - git_repository_free(wtrepo); - git_worktree_free(wt); -} - -void test_worktree_worktree__add_no_checkout(void) -{ - git_worktree *wt; - git_repository *wtrepo; - git_index *index; - git_str path = GIT_STR_INIT; - git_worktree_add_options opts = GIT_WORKTREE_ADD_OPTIONS_INIT; - - opts.checkout_options.checkout_strategy = GIT_CHECKOUT_NONE; - - cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-no-checkout")); - cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-no-checkout", path.ptr, &opts)); - - cl_git_pass(git_repository_open(&wtrepo, path.ptr)); - cl_git_pass(git_repository_index(&index, wtrepo)); - cl_assert_equal_i(git_index_entrycount(index), 0); - - git_str_dispose(&path); - git_worktree_free(wt); - git_index_free(index); - git_repository_free(wtrepo); -} - -void test_worktree_worktree__init_existing_worktree(void) -{ - git_worktree *wt; - git_str path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../worktree-new")); - cl_git_fail(git_worktree_add(&wt, fixture.repo, "testrepo-worktree", path.ptr, NULL)); - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_assert_equal_s(wt->gitlink_path, fixture.worktree->gitlink); - - git_str_dispose(&path); - git_worktree_free(wt); -} - -void test_worktree_worktree__init_existing_path(void) -{ - const char *wtfiles[] = { "HEAD", "commondir", "gitdir", "index" }; - git_worktree *wt; - git_str path = GIT_STR_INIT; - unsigned i; - - /* Delete files to verify they have not been created by - * the init call */ - for (i = 0; i < ARRAY_SIZE(wtfiles); i++) { - cl_git_pass(git_str_joinpath(&path, - fixture.worktree->gitdir, wtfiles[i])); - cl_git_pass(p_unlink(path.ptr)); - } - - cl_git_pass(git_str_joinpath(&path, fixture.repo->workdir, "../testrepo-worktree")); - cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr, NULL)); - - /* Verify files have not been re-created */ - for (i = 0; i < ARRAY_SIZE(wtfiles); i++) { - cl_git_pass(git_str_joinpath(&path, - fixture.worktree->gitdir, wtfiles[i])); - cl_assert(!git_fs_path_exists(path.ptr)); - } - - git_str_dispose(&path); -} - -void test_worktree_worktree__init_submodule(void) -{ - git_repository *repo, *sm, *wt; - git_worktree *worktree; - git_str path = GIT_STR_INIT; - - cleanup_fixture_worktree(&fixture); - repo = setup_fixture_submod2(); - - cl_git_pass(git_str_joinpath(&path, repo->workdir, "sm_unchanged")); - cl_git_pass(git_repository_open(&sm, path.ptr)); - cl_git_pass(git_str_joinpath(&path, repo->workdir, "../worktree/")); - cl_git_pass(git_worktree_add(&worktree, sm, "repo-worktree", path.ptr, NULL)); - cl_git_pass(git_repository_open_from_worktree(&wt, worktree)); - - cl_git_pass(git_fs_path_prettify_dir(&path, path.ptr, NULL)); - cl_assert_equal_s(path.ptr, wt->workdir); - cl_git_pass(git_fs_path_prettify_dir(&path, sm->commondir, NULL)); - cl_assert_equal_s(sm->commondir, wt->commondir); - - cl_git_pass(git_str_joinpath(&path, sm->gitdir, "worktrees/repo-worktree/")); - cl_assert_equal_s(path.ptr, wt->gitdir); - - git_str_dispose(&path); - git_worktree_free(worktree); - git_repository_free(sm); - git_repository_free(wt); -} - -void test_worktree_worktree__validate(void) -{ - git_worktree *wt; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_git_pass(git_worktree_validate(wt)); - - git_worktree_free(wt); -} - -void test_worktree_worktree__name(void) -{ - git_worktree *wt; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_assert_equal_s(git_worktree_name(wt), "testrepo-worktree"); - - git_worktree_free(wt); -} - -void test_worktree_worktree__path(void) -{ - git_worktree *wt; - git_str expected_path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&expected_path, clar_sandbox_path(), "testrepo-worktree")); - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_assert_equal_s(git_worktree_path(wt), expected_path.ptr); - - git_str_dispose(&expected_path); - git_worktree_free(wt); -} - -void test_worktree_worktree__validate_invalid_commondir(void) -{ - git_worktree *wt; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - git__free(wt->commondir_path); - wt->commondir_path = "/path/to/invalid/commondir"; - - cl_git_fail(git_worktree_validate(wt)); - - wt->commondir_path = NULL; - git_worktree_free(wt); -} - -void test_worktree_worktree__validate_invalid_gitdir(void) -{ - git_worktree *wt; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - git__free(wt->gitdir_path); - wt->gitdir_path = "/path/to/invalid/gitdir"; - cl_git_fail(git_worktree_validate(wt)); - - wt->gitdir_path = NULL; - git_worktree_free(wt); -} - -void test_worktree_worktree__validate_invalid_parent(void) -{ - git_worktree *wt; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - git__free(wt->parent_path); - wt->parent_path = "/path/to/invalid/parent"; - cl_git_fail(git_worktree_validate(wt)); - - wt->parent_path = NULL; - git_worktree_free(wt); -} - -void test_worktree_worktree__lock_with_reason(void) -{ - git_worktree *wt; - git_buf reason = GIT_BUF_INIT; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - - cl_assert(!git_worktree_is_locked(NULL, wt)); - cl_git_pass(git_worktree_lock(wt, "because")); - cl_assert(git_worktree_is_locked(&reason, wt) > 0); - cl_assert_equal_s(reason.ptr, "because"); - cl_assert(wt->locked); - - git_buf_dispose(&reason); - git_worktree_free(wt); -} - -void test_worktree_worktree__lock_without_reason(void) -{ - git_worktree *wt; - git_buf reason = GIT_BUF_INIT; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - - cl_assert(!git_worktree_is_locked(NULL, wt)); - cl_git_pass(git_worktree_lock(wt, NULL)); - cl_assert(git_worktree_is_locked(&reason, wt) > 0); - cl_assert_equal_i(reason.size, 0); - cl_assert(wt->locked); - - git_buf_dispose(&reason); - git_worktree_free(wt); -} - -void test_worktree_worktree__unlock_unlocked_worktree(void) -{ - git_worktree *wt; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_assert(!git_worktree_is_locked(NULL, wt)); - cl_assert_equal_i(1, git_worktree_unlock(wt)); - cl_assert(!wt->locked); - - git_worktree_free(wt); -} - -void test_worktree_worktree__unlock_locked_worktree(void) -{ - git_worktree *wt; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_git_pass(git_worktree_lock(wt, NULL)); - cl_assert(git_worktree_is_locked(NULL, wt)); - cl_assert_equal_i(0, git_worktree_unlock(wt)); - cl_assert(!wt->locked); - - git_worktree_free(wt); -} - -void test_worktree_worktree__prune_without_opts_fails(void) -{ - git_worktree *wt; - git_repository *repo; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_git_fail(git_worktree_prune(wt, NULL)); - - /* Assert the repository is still valid */ - cl_git_pass(git_repository_open_from_worktree(&repo, wt)); - - git_worktree_free(wt); - git_repository_free(repo); -} - -void test_worktree_worktree__prune_valid(void) -{ - git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; - git_worktree *wt; - git_repository *repo; - - opts.flags = GIT_WORKTREE_PRUNE_VALID; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_git_pass(git_worktree_prune(wt, &opts)); - - /* Assert the repository is not valid anymore */ - cl_git_fail(git_repository_open_from_worktree(&repo, wt)); - - git_worktree_free(wt); - git_repository_free(repo); -} - -void test_worktree_worktree__prune_locked(void) -{ - git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; - git_worktree *wt; - git_repository *repo; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_git_pass(git_worktree_lock(wt, NULL)); - - opts.flags = GIT_WORKTREE_PRUNE_VALID; - cl_git_fail(git_worktree_prune(wt, &opts)); - /* Assert the repository is still valid */ - cl_git_pass(git_repository_open_from_worktree(&repo, wt)); - - opts.flags = GIT_WORKTREE_PRUNE_VALID|GIT_WORKTREE_PRUNE_LOCKED; - cl_git_pass(git_worktree_prune(wt, &opts)); - - git_worktree_free(wt); - git_repository_free(repo); -} - -void test_worktree_worktree__prune_gitdir_only(void) -{ - git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; - git_worktree *wt; - - opts.flags = GIT_WORKTREE_PRUNE_VALID; - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_git_pass(git_worktree_prune(wt, &opts)); - - cl_assert(!git_fs_path_exists(wt->gitdir_path)); - cl_assert(git_fs_path_exists(wt->gitlink_path)); - - git_worktree_free(wt); -} - -void test_worktree_worktree__prune_worktree(void) -{ - git_worktree_prune_options opts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; - git_worktree *wt; - - opts.flags = GIT_WORKTREE_PRUNE_VALID|GIT_WORKTREE_PRUNE_WORKING_TREE; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_git_pass(git_worktree_prune(wt, &opts)); - - cl_assert(!git_fs_path_exists(wt->gitdir_path)); - cl_assert(!git_fs_path_exists(wt->gitlink_path)); - - git_worktree_free(wt); -} - -static int foreach_worktree_cb(git_repository *worktree, void *payload) -{ - int *counter = (int *)payload; - - switch (*counter) { - case 0: - cl_assert_equal_s(git_repository_path(fixture.repo), - git_repository_path(worktree)); - cl_assert(!git_repository_is_worktree(worktree)); - break; - case 1: - cl_assert_equal_s(git_repository_path(fixture.worktree), - git_repository_path(worktree)); - cl_assert(git_repository_is_worktree(worktree)); - break; - default: - cl_fail("more worktrees found than expected"); - } - - (*counter)++; - - return 0; -} - -void test_worktree_worktree__foreach_worktree_lists_all_worktrees(void) -{ - int counter = 0; - cl_git_pass(git_repository_foreach_worktree(fixture.repo, foreach_worktree_cb, &counter)); -} - -void test_worktree_worktree__validate_invalid_worktreedir(void) -{ - git_worktree *wt; - - cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - p_rename("testrepo-worktree", "testrepo-worktree-tmp"); - cl_git_fail(git_worktree_validate(wt)); - p_rename("testrepo-worktree-tmp", "testrepo-worktree"); - - git_worktree_free(wt); -} diff --git a/tests/worktree/worktree_helpers.c b/tests/worktree/worktree_helpers.c deleted file mode 100644 index 6d4cdbaeb..000000000 --- a/tests/worktree/worktree_helpers.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "clar_libgit2.h" -#include "worktree_helpers.h" - -void cleanup_fixture_worktree(worktree_fixture *fixture) -{ - if (!fixture) - return; - - if (fixture->repo) { - git_repository_free(fixture->repo); - fixture->repo = NULL; - } - if (fixture->worktree) { - git_repository_free(fixture->worktree); - fixture->worktree = NULL; - } - - if (fixture->reponame) - cl_fixture_cleanup(fixture->reponame); - if (fixture->worktreename) - cl_fixture_cleanup(fixture->worktreename); -} - -void setup_fixture_worktree(worktree_fixture *fixture) -{ - if (fixture->reponame) - fixture->repo = cl_git_sandbox_init(fixture->reponame); - if (fixture->worktreename) - fixture->worktree = cl_git_sandbox_init(fixture->worktreename); -} diff --git a/tests/worktree/worktree_helpers.h b/tests/worktree/worktree_helpers.h deleted file mode 100644 index 35ea9ed4c..000000000 --- a/tests/worktree/worktree_helpers.h +++ /dev/null @@ -1,11 +0,0 @@ -typedef struct { - const char *reponame; - const char *worktreename; - git_repository *repo; - git_repository *worktree; -} worktree_fixture; - -#define WORKTREE_FIXTURE_INIT(repo, worktree) { (repo), (worktree), NULL, NULL } - -void cleanup_fixture_worktree(worktree_fixture *fixture); -void setup_fixture_worktree(worktree_fixture *fixture); -- cgit v1.2.1 From 25bc84fb8f702b57983d374f1c79b93a2d7543f1 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 16 Nov 2021 23:41:00 -0500 Subject: refactor: move clar into separate directory --- tests/README.md | 6 +- tests/clar/clar.c | 788 +++++++++++++++++++++++++++++++++++++ tests/clar/clar.h | 173 ++++++++ tests/clar/clar/fixtures.h | 50 +++ tests/clar/clar/fs.h | 520 ++++++++++++++++++++++++ tests/clar/clar/print.h | 200 ++++++++++ tests/clar/clar/sandbox.h | 154 ++++++++ tests/clar/clar/summary.h | 134 +++++++ tests/clar/clar_libgit2.c | 623 +++++++++++++++++++++++++++++ tests/clar/clar_libgit2.h | 236 +++++++++++ tests/clar/clar_libgit2_timer.c | 30 ++ tests/clar/clar_libgit2_timer.h | 35 ++ tests/clar/clar_libgit2_trace.c | 263 +++++++++++++ tests/clar/clar_libgit2_trace.h | 7 + tests/clar/generate.py | 316 +++++++++++++++ tests/clar/main.c | 50 +++ tests/libgit2/CMakeLists.txt | 15 +- tests/libgit2/clar.c | 788 ------------------------------------- tests/libgit2/clar.h | 173 -------- tests/libgit2/clar/fixtures.h | 50 --- tests/libgit2/clar/fs.h | 520 ------------------------ tests/libgit2/clar/print.h | 200 ---------- tests/libgit2/clar/sandbox.h | 154 -------- tests/libgit2/clar/summary.h | 134 ------- tests/libgit2/clar_libgit2.c | 623 ----------------------------- tests/libgit2/clar_libgit2.h | 236 ----------- tests/libgit2/clar_libgit2_timer.c | 30 -- tests/libgit2/clar_libgit2_timer.h | 35 -- tests/libgit2/clar_libgit2_trace.c | 263 ------------- tests/libgit2/clar_libgit2_trace.h | 7 - tests/libgit2/generate.py | 316 --------------- tests/libgit2/main.c | 50 --- tests/libgit2/remote/fetch.c | 8 +- 33 files changed, 3595 insertions(+), 3592 deletions(-) create mode 100644 tests/clar/clar.c create mode 100644 tests/clar/clar.h create mode 100644 tests/clar/clar/fixtures.h create mode 100644 tests/clar/clar/fs.h create mode 100644 tests/clar/clar/print.h create mode 100644 tests/clar/clar/sandbox.h create mode 100644 tests/clar/clar/summary.h create mode 100644 tests/clar/clar_libgit2.c create mode 100644 tests/clar/clar_libgit2.h create mode 100644 tests/clar/clar_libgit2_timer.c create mode 100644 tests/clar/clar_libgit2_timer.h create mode 100644 tests/clar/clar_libgit2_trace.c create mode 100644 tests/clar/clar_libgit2_trace.h create mode 100644 tests/clar/generate.py create mode 100644 tests/clar/main.c delete mode 100644 tests/libgit2/clar.c delete mode 100644 tests/libgit2/clar.h delete mode 100644 tests/libgit2/clar/fixtures.h delete mode 100644 tests/libgit2/clar/fs.h delete mode 100644 tests/libgit2/clar/print.h delete mode 100644 tests/libgit2/clar/sandbox.h delete mode 100644 tests/libgit2/clar/summary.h delete mode 100644 tests/libgit2/clar_libgit2.c delete mode 100644 tests/libgit2/clar_libgit2.h delete mode 100644 tests/libgit2/clar_libgit2_timer.c delete mode 100644 tests/libgit2/clar_libgit2_timer.h delete mode 100644 tests/libgit2/clar_libgit2_trace.c delete mode 100644 tests/libgit2/clar_libgit2_trace.h delete mode 100644 tests/libgit2/generate.py delete mode 100644 tests/libgit2/main.c diff --git a/tests/README.md b/tests/README.md index 68c2788ba..2e3b2630e 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,9 +2,11 @@ These are the unit and integration tests for the libgit2 projects. -* `libgit2` +* `clar` + This is [clar](https://github.com/clar-test/clar) the common test framework. +* `libgit2` These tests exercise the core git functionality in libgit2 itself. -* `resources` +* `resources` These are the resources for the tests, including files and git repositories. diff --git a/tests/clar/clar.c b/tests/clar/clar.c new file mode 100644 index 000000000..ca508d073 --- /dev/null +++ b/tests/clar/clar.c @@ -0,0 +1,788 @@ +/* + * Copyright (c) Vicent Marti. All rights reserved. + * + * This file is part of clar, distributed under the ISC license. + * For full terms see the included COPYING file. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* required for sandboxing */ +#include +#include + +#ifdef _WIN32 +# include +# include +# include +# include + +# define _MAIN_CC __cdecl + +# ifndef stat +# define stat(path, st) _stat(path, st) +# endif +# ifndef mkdir +# define mkdir(path, mode) _mkdir(path) +# endif +# ifndef chdir +# define chdir(path) _chdir(path) +# endif +# ifndef access +# define access(path, mode) _access(path, mode) +# endif +# ifndef strdup +# define strdup(str) _strdup(str) +# endif +# ifndef strcasecmp +# define strcasecmp(a,b) _stricmp(a,b) +# endif + +# ifndef __MINGW32__ +# pragma comment(lib, "shell32") +# ifndef strncpy +# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) +# endif +# ifndef W_OK +# define W_OK 02 +# endif +# ifndef S_ISDIR +# define S_ISDIR(x) ((x & _S_IFDIR) != 0) +# endif +# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__) +# else +# define p_snprintf snprintf +# endif + +# ifndef PRIuZ +# define PRIuZ "Iu" +# endif +# ifndef PRIxZ +# define PRIxZ "Ix" +# endif + +# if defined(_MSC_VER) || defined(__MINGW32__) + typedef struct stat STAT_T; +# else + typedef struct _stat STAT_T; +# endif +#else +# include /* waitpid(2) */ +# include +# define _MAIN_CC +# define p_snprintf snprintf +# ifndef PRIuZ +# define PRIuZ "zu" +# endif +# ifndef PRIxZ +# define PRIxZ "zx" +# endif + typedef struct stat STAT_T; +#endif + +#include "clar.h" + +static void fs_rm(const char *_source); +static void fs_copy(const char *_source, const char *dest); + +static const char * +fixture_path(const char *base, const char *fixture_name); + +struct clar_error { + const char *file; + const char *function; + size_t line_number; + const char *error_msg; + char *description; + + struct clar_error *next; +}; + +struct clar_explicit { + size_t suite_idx; + const char *filter; + + struct clar_explicit *next; +}; + +struct clar_report { + const char *test; + int test_number; + const char *suite; + + enum cl_test_status status; + + struct clar_error *errors; + struct clar_error *last_error; + + struct clar_report *next; +}; + +struct clar_summary { + const char *filename; + FILE *fp; +}; + +static struct { + enum cl_test_status test_status; + + const char *active_test; + const char *active_suite; + + int total_skipped; + int total_errors; + + int tests_ran; + int suites_ran; + + enum cl_output_format output_format; + + int report_errors_only; + int exit_on_error; + int report_suite_names; + + int write_summary; + char *summary_filename; + struct clar_summary *summary; + + struct clar_explicit *explicit; + struct clar_explicit *last_explicit; + + struct clar_report *reports; + struct clar_report *last_report; + + void (*local_cleanup)(void *); + void *local_cleanup_payload; + + jmp_buf trampoline; + int trampoline_enabled; + + cl_trace_cb *pfn_trace_cb; + void *trace_payload; + +} _clar; + +struct clar_func { + const char *name; + void (*ptr)(void); +}; + +struct clar_suite { + const char *name; + struct clar_func initialize; + struct clar_func cleanup; + const struct clar_func *tests; + size_t test_count; + int enabled; +}; + +/* From clar_print_*.c */ +static void clar_print_init(int test_count, int suite_count, const char *suite_names); +static void clar_print_shutdown(int test_count, int suite_count, int error_count); +static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error); +static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status failed); +static void clar_print_onsuite(const char *suite_name, int suite_index); +static void clar_print_onabort(const char *msg, ...); + +/* From clar_sandbox.c */ +static void clar_unsandbox(void); +static int clar_sandbox(void); + +/* From summary.h */ +static struct clar_summary *clar_summary_init(const char *filename); +static int clar_summary_shutdown(struct clar_summary *fp); + +/* Load the declarations for the test suite */ +#include "clar.suite" + + +#define CL_TRACE(ev) \ + do { \ + if (_clar.pfn_trace_cb) \ + _clar.pfn_trace_cb(ev, \ + _clar.active_suite, \ + _clar.active_test, \ + _clar.trace_payload); \ + } while (0) + +void cl_trace_register(cl_trace_cb *cb, void *payload) +{ + _clar.pfn_trace_cb = cb; + _clar.trace_payload = payload; +} + + +/* Core test functions */ +static void +clar_report_errors(struct clar_report *report) +{ + struct clar_error *error; + int i = 1; + + for (error = report->errors; error; error = error->next) + clar_print_error(i++, _clar.last_report, error); +} + +static void +clar_report_all(void) +{ + struct clar_report *report; + struct clar_error *error; + int i = 1; + + for (report = _clar.reports; report; report = report->next) { + if (report->status != CL_TEST_FAILURE) + continue; + + for (error = report->errors; error; error = error->next) + clar_print_error(i++, report, error); + } +} + +static void +clar_run_test( + const struct clar_func *test, + const struct clar_func *initialize, + const struct clar_func *cleanup) +{ + _clar.trampoline_enabled = 1; + + CL_TRACE(CL_TRACE__TEST__BEGIN); + + if (setjmp(_clar.trampoline) == 0) { + if (initialize->ptr != NULL) + initialize->ptr(); + + CL_TRACE(CL_TRACE__TEST__RUN_BEGIN); + test->ptr(); + CL_TRACE(CL_TRACE__TEST__RUN_END); + } + + _clar.trampoline_enabled = 0; + + if (_clar.last_report->status == CL_TEST_NOTRUN) + _clar.last_report->status = CL_TEST_OK; + + if (_clar.local_cleanup != NULL) + _clar.local_cleanup(_clar.local_cleanup_payload); + + if (cleanup->ptr != NULL) + cleanup->ptr(); + + CL_TRACE(CL_TRACE__TEST__END); + + _clar.tests_ran++; + + /* remove any local-set cleanup methods */ + _clar.local_cleanup = NULL; + _clar.local_cleanup_payload = NULL; + + if (_clar.report_errors_only) { + clar_report_errors(_clar.last_report); + } else { + clar_print_ontest(test->name, _clar.tests_ran, _clar.last_report->status); + } +} + +static void +clar_run_suite(const struct clar_suite *suite, const char *filter) +{ + const struct clar_func *test = suite->tests; + size_t i, matchlen; + struct clar_report *report; + int exact = 0; + + if (!suite->enabled) + return; + + if (_clar.exit_on_error && _clar.total_errors) + return; + + if (!_clar.report_errors_only) + clar_print_onsuite(suite->name, ++_clar.suites_ran); + + _clar.active_suite = suite->name; + _clar.active_test = NULL; + CL_TRACE(CL_TRACE__SUITE_BEGIN); + + if (filter) { + size_t suitelen = strlen(suite->name); + matchlen = strlen(filter); + if (matchlen <= suitelen) { + filter = NULL; + } else { + filter += suitelen; + while (*filter == ':') + ++filter; + matchlen = strlen(filter); + + if (matchlen && filter[matchlen - 1] == '$') { + exact = 1; + matchlen--; + } + } + } + + for (i = 0; i < suite->test_count; ++i) { + if (filter && strncmp(test[i].name, filter, matchlen)) + continue; + + if (exact && strlen(test[i].name) != matchlen) + continue; + + _clar.active_test = test[i].name; + + report = calloc(1, sizeof(struct clar_report)); + report->suite = _clar.active_suite; + report->test = _clar.active_test; + report->test_number = _clar.tests_ran; + report->status = CL_TEST_NOTRUN; + + if (_clar.reports == NULL) + _clar.reports = report; + + if (_clar.last_report != NULL) + _clar.last_report->next = report; + + _clar.last_report = report; + + clar_run_test(&test[i], &suite->initialize, &suite->cleanup); + + if (_clar.exit_on_error && _clar.total_errors) + return; + } + + _clar.active_test = NULL; + CL_TRACE(CL_TRACE__SUITE_END); +} + +static void +clar_usage(const char *arg) +{ + printf("Usage: %s [options]\n\n", arg); + printf("Options:\n"); + printf(" -sname Run only the suite with `name` (can go to individual test name)\n"); + printf(" -iname Include the suite with `name`\n"); + printf(" -xname Exclude the suite with `name`\n"); + printf(" -v Increase verbosity (show suite names)\n"); + printf(" -q Only report tests that had an error\n"); + printf(" -Q Quit as soon as a test fails\n"); + printf(" -t Display results in tap format\n"); + printf(" -l Print suite names\n"); + printf(" -r[filename] Write summary file (to the optional filename)\n"); + exit(-1); +} + +static void +clar_parse_args(int argc, char **argv) +{ + int i; + + /* Verify options before execute */ + for (i = 1; i < argc; ++i) { + char *argument = argv[i]; + + if (argument[0] != '-' || argument[1] == '\0' + || strchr("sixvqQtlr", argument[1]) == NULL) { + clar_usage(argv[0]); + } + } + + for (i = 1; i < argc; ++i) { + char *argument = argv[i]; + + switch (argument[1]) { + case 's': + case 'i': + case 'x': { /* given suite name */ + int offset = (argument[2] == '=') ? 3 : 2, found = 0; + char action = argument[1]; + size_t j, arglen, suitelen, cmplen; + + argument += offset; + arglen = strlen(argument); + + if (arglen == 0) + clar_usage(argv[0]); + + for (j = 0; j < _clar_suite_count; ++j) { + suitelen = strlen(_clar_suites[j].name); + cmplen = (arglen < suitelen) ? arglen : suitelen; + + if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) { + int exact = (arglen >= suitelen); + + /* Do we have a real suite prefix separated by a + * trailing '::' or just a matching substring? */ + if (arglen > suitelen && (argument[suitelen] != ':' + || argument[suitelen + 1] != ':')) + continue; + + ++found; + + if (!exact) + _clar.report_suite_names = 1; + + switch (action) { + case 's': { + struct clar_explicit *explicit = + calloc(1, sizeof(struct clar_explicit)); + assert(explicit); + + explicit->suite_idx = j; + explicit->filter = argument; + + if (_clar.explicit == NULL) + _clar.explicit = explicit; + + if (_clar.last_explicit != NULL) + _clar.last_explicit->next = explicit; + + _clar_suites[j].enabled = 1; + _clar.last_explicit = explicit; + break; + } + case 'i': _clar_suites[j].enabled = 1; break; + case 'x': _clar_suites[j].enabled = 0; break; + } + + if (exact) + break; + } + } + + if (!found) { + clar_print_onabort("No suite matching '%s' found.\n", argument); + exit(-1); + } + break; + } + + case 'q': + _clar.report_errors_only = 1; + break; + + case 'Q': + _clar.exit_on_error = 1; + break; + + case 't': + _clar.output_format = CL_OUTPUT_TAP; + break; + + case 'l': { + size_t j; + printf("Test suites (use -s to run just one):\n"); + for (j = 0; j < _clar_suite_count; ++j) + printf(" %3d: %s\n", (int)j, _clar_suites[j].name); + + exit(0); + } + + case 'v': + _clar.report_suite_names = 1; + break; + + case 'r': + _clar.write_summary = 1; + free(_clar.summary_filename); + _clar.summary_filename = strdup(*(argument + 2) ? (argument + 2) : "summary.xml"); + break; + + default: + assert(!"Unexpected commandline argument!"); + } + } +} + +void +clar_test_init(int argc, char **argv) +{ + if (argc > 1) + clar_parse_args(argc, argv); + + clar_print_init( + (int)_clar_callback_count, + (int)_clar_suite_count, + "" + ); + + if ((_clar.summary_filename = getenv("CLAR_SUMMARY")) != NULL) { + _clar.write_summary = 1; + _clar.summary_filename = strdup(_clar.summary_filename); + } + + if (_clar.write_summary && + !(_clar.summary = clar_summary_init(_clar.summary_filename))) { + clar_print_onabort("Failed to open the summary file\n"); + exit(-1); + } + + if (clar_sandbox() < 0) { + clar_print_onabort("Failed to sandbox the test runner.\n"); + exit(-1); + } +} + +int +clar_test_run(void) +{ + size_t i; + struct clar_explicit *explicit; + + if (_clar.explicit) { + for (explicit = _clar.explicit; explicit; explicit = explicit->next) + clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter); + } else { + for (i = 0; i < _clar_suite_count; ++i) + clar_run_suite(&_clar_suites[i], NULL); + } + + return _clar.total_errors; +} + +void +clar_test_shutdown(void) +{ + struct clar_explicit *explicit, *explicit_next; + struct clar_report *report, *report_next; + + clar_print_shutdown( + _clar.tests_ran, + (int)_clar_suite_count, + _clar.total_errors + ); + + clar_unsandbox(); + + if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) { + clar_print_onabort("Failed to write the summary file\n"); + exit(-1); + } + + for (explicit = _clar.explicit; explicit; explicit = explicit_next) { + explicit_next = explicit->next; + free(explicit); + } + + for (report = _clar.reports; report; report = report_next) { + report_next = report->next; + free(report); + } + + free(_clar.summary_filename); +} + +int +clar_test(int argc, char **argv) +{ + int errors; + + clar_test_init(argc, argv); + errors = clar_test_run(); + clar_test_shutdown(); + + return errors; +} + +static void abort_test(void) +{ + if (!_clar.trampoline_enabled) { + clar_print_onabort( + "Fatal error: a cleanup method raised an exception."); + clar_report_errors(_clar.last_report); + exit(-1); + } + + CL_TRACE(CL_TRACE__TEST__LONGJMP); + longjmp(_clar.trampoline, -1); +} + +void clar__skip(void) +{ + _clar.last_report->status = CL_TEST_SKIP; + _clar.total_skipped++; + abort_test(); +} + +void clar__fail( + const char *file, + const char *function, + size_t line, + const char *error_msg, + const char *description, + int should_abort) +{ + struct clar_error *error = calloc(1, sizeof(struct clar_error)); + + if (_clar.last_report->errors == NULL) + _clar.last_report->errors = error; + + if (_clar.last_report->last_error != NULL) + _clar.last_report->last_error->next = error; + + _clar.last_report->last_error = error; + + error->file = file; + error->function = function; + error->line_number = line; + error->error_msg = error_msg; + + if (description != NULL) + error->description = strdup(description); + + _clar.total_errors++; + _clar.last_report->status = CL_TEST_FAILURE; + + if (should_abort) + abort_test(); +} + +void clar__assert( + int condition, + const char *file, + const char *function, + size_t line, + const char *error_msg, + const char *description, + int should_abort) +{ + if (condition) + return; + + clar__fail(file, function, line, error_msg, description, should_abort); +} + +void clar__assert_equal( + const char *file, + const char *function, + size_t line, + const char *err, + int should_abort, + const char *fmt, + ...) +{ + va_list args; + char buf[4096]; + int is_equal = 1; + + va_start(args, fmt); + + if (!strcmp("%s", fmt)) { + const char *s1 = va_arg(args, const char *); + const char *s2 = va_arg(args, const char *); + is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2); + + if (!is_equal) { + if (s1 && s2) { + int pos; + for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) + /* find differing byte offset */; + p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", + s1, s2, pos); + } else { + p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2); + } + } + } + else if(!strcmp("%.*s", fmt)) { + const char *s1 = va_arg(args, const char *); + const char *s2 = va_arg(args, const char *); + int len = va_arg(args, int); + is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len); + + if (!is_equal) { + if (s1 && s2) { + int pos; + for (pos = 0; s1[pos] == s2[pos] && pos < len; ++pos) + /* find differing byte offset */; + p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s' (at byte %d)", + len, s1, len, s2, pos); + } else { + p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s'", len, s1, len, s2); + } + } + } + else if (!strcmp("%ls", fmt)) { + const wchar_t *wcs1 = va_arg(args, const wchar_t *); + const wchar_t *wcs2 = va_arg(args, const wchar_t *); + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2); + + if (!is_equal) { + if (wcs1 && wcs2) { + int pos; + for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos) + /* find differing byte offset */; + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)", + wcs1, wcs2, pos); + } else { + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2); + } + } + } + else if(!strcmp("%.*ls", fmt)) { + const wchar_t *wcs1 = va_arg(args, const wchar_t *); + const wchar_t *wcs2 = va_arg(args, const wchar_t *); + int len = va_arg(args, int); + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len); + + if (!is_equal) { + if (wcs1 && wcs2) { + int pos; + for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos) + /* find differing byte offset */; + p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls' (at byte %d)", + len, wcs1, len, wcs2, pos); + } else { + p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls'", len, wcs1, len, wcs2); + } + } + } + else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) { + size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t); + is_equal = (sz1 == sz2); + if (!is_equal) { + int offset = p_snprintf(buf, sizeof(buf), fmt, sz1); + strncat(buf, " != ", sizeof(buf) - offset); + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2); + } + } + else if (!strcmp("%p", fmt)) { + void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *); + is_equal = (p1 == p2); + if (!is_equal) + p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2); + } + else { + int i1 = va_arg(args, int), i2 = va_arg(args, int); + is_equal = (i1 == i2); + if (!is_equal) { + int offset = p_snprintf(buf, sizeof(buf), fmt, i1); + strncat(buf, " != ", sizeof(buf) - offset); + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2); + } + } + + va_end(args); + + if (!is_equal) + clar__fail(file, function, line, err, buf, should_abort); +} + +void cl_set_cleanup(void (*cleanup)(void *), void *opaque) +{ + _clar.local_cleanup = cleanup; + _clar.local_cleanup_payload = opaque; +} + +#include "clar/sandbox.h" +#include "clar/fixtures.h" +#include "clar/fs.h" +#include "clar/print.h" +#include "clar/summary.h" diff --git a/tests/clar/clar.h b/tests/clar/clar.h new file mode 100644 index 000000000..3f659c2f6 --- /dev/null +++ b/tests/clar/clar.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) Vicent Marti. All rights reserved. + * + * This file is part of clar, distributed under the ISC license. + * For full terms see the included COPYING file. + */ +#ifndef __CLAR_TEST_H__ +#define __CLAR_TEST_H__ + +#include + +enum cl_test_status { + CL_TEST_OK, + CL_TEST_FAILURE, + CL_TEST_SKIP, + CL_TEST_NOTRUN +}; + +enum cl_output_format { + CL_OUTPUT_CLAP, + CL_OUTPUT_TAP +}; + +/** Setup clar environment */ +void clar_test_init(int argc, char *argv[]); +int clar_test_run(void); +void clar_test_shutdown(void); + +/** One shot setup & run */ +int clar_test(int argc, char *argv[]); + +const char *clar_sandbox_path(void); + +void cl_set_cleanup(void (*cleanup)(void *), void *opaque); +void cl_fs_cleanup(void); + +/** + * cl_trace_* is a hook to provide a simple global tracing + * mechanism. + * + * The goal here is to let main() provide clar-proper + * with a callback to optionally write log info for + * test operations into the same stream used by their + * actual tests. This would let them print test names + * and maybe performance data as they choose. + * + * The goal is NOT to alter the flow of control or to + * override test selection/skipping. (So the callback + * does not return a value.) + * + * The goal is NOT to duplicate the existing + * pass/fail/skip reporting. (So the callback + * does not accept a status/errorcode argument.) + * + */ +typedef enum cl_trace_event { + CL_TRACE__SUITE_BEGIN, + CL_TRACE__SUITE_END, + CL_TRACE__TEST__BEGIN, + CL_TRACE__TEST__END, + CL_TRACE__TEST__RUN_BEGIN, + CL_TRACE__TEST__RUN_END, + CL_TRACE__TEST__LONGJMP +} cl_trace_event; + +typedef void (cl_trace_cb)( + cl_trace_event ev, + const char *suite_name, + const char *test_name, + void *payload); + +/** + * Register a callback into CLAR to send global trace events. + * Pass NULL to disable. + */ +void cl_trace_register(cl_trace_cb *cb, void *payload); + + +#ifdef CLAR_FIXTURE_PATH +const char *cl_fixture(const char *fixture_name); +void cl_fixture_sandbox(const char *fixture_name); +void cl_fixture_cleanup(const char *fixture_name); +const char *cl_fixture_basename(const char *fixture_name); +#endif + +/** + * Assertion macros with explicit error message + */ +#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 1) +#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 1) +#define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 1) + +/** + * Check macros with explicit error message + */ +#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 0) +#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 0) +#define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 0) + +/** + * Assertion macros with no error message + */ +#define cl_must_pass(expr) cl_must_pass_(expr, NULL) +#define cl_must_fail(expr) cl_must_fail_(expr, NULL) +#define cl_assert(expr) cl_assert_(expr, NULL) + +/** + * Check macros with no error message + */ +#define cl_check_pass(expr) cl_check_pass_(expr, NULL) +#define cl_check_fail(expr) cl_check_fail_(expr, NULL) +#define cl_check(expr) cl_check_(expr, NULL) + +/** + * Forced failure/warning + */ +#define cl_fail(desc) clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1) +#define cl_warning(desc) clar__fail(__FILE__, __func__, __LINE__, "Warning during test execution:", desc, 0) + +#define cl_skip() clar__skip() + +/** + * Typed assertion macros + */ +#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2)) +#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2)) + +#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2)) +#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2)) + +#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len)) +#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len)) + +#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len)) +#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len)) + +#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) +#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2)) +#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2)) + +#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__func__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0)) + +#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__func__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2)) + +void clar__skip(void); + +void clar__fail( + const char *file, + const char *func, + size_t line, + const char *error, + const char *description, + int should_abort); + +void clar__assert( + int condition, + const char *file, + const char *func, + size_t line, + const char *error, + const char *description, + int should_abort); + +void clar__assert_equal( + const char *file, + const char *func, + size_t line, + const char *err, + int should_abort, + const char *fmt, + ...); + +#endif diff --git a/tests/clar/clar/fixtures.h b/tests/clar/clar/fixtures.h new file mode 100644 index 000000000..77033d365 --- /dev/null +++ b/tests/clar/clar/fixtures.h @@ -0,0 +1,50 @@ +static const char * +fixture_path(const char *base, const char *fixture_name) +{ + static char _path[4096]; + size_t root_len; + + root_len = strlen(base); + strncpy(_path, base, sizeof(_path)); + + if (_path[root_len - 1] != '/') + _path[root_len++] = '/'; + + if (fixture_name[0] == '/') + fixture_name++; + + strncpy(_path + root_len, + fixture_name, + sizeof(_path) - root_len); + + return _path; +} + +#ifdef CLAR_FIXTURE_PATH +const char *cl_fixture(const char *fixture_name) +{ + return fixture_path(CLAR_FIXTURE_PATH, fixture_name); +} + +void cl_fixture_sandbox(const char *fixture_name) +{ + fs_copy(cl_fixture(fixture_name), _clar_path); +} + +const char *cl_fixture_basename(const char *fixture_name) +{ + const char *p; + + for (p = fixture_name; *p; p++) { + if (p[0] == '/' && p[1] && p[1] != '/') + fixture_name = p+1; + } + + return fixture_name; +} + +void cl_fixture_cleanup(const char *fixture_name) +{ + fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name))); +} +#endif diff --git a/tests/clar/clar/fs.h b/tests/clar/clar/fs.h new file mode 100644 index 000000000..44ede4572 --- /dev/null +++ b/tests/clar/clar/fs.h @@ -0,0 +1,520 @@ +/* + * By default, use a read/write loop to copy files on POSIX systems. + * On Linux, use sendfile by default as it's slightly faster. On + * macOS, we avoid fcopyfile by default because it's slightly slower. + */ +#undef USE_FCOPYFILE +#define USE_SENDFILE 1 + +#ifdef _WIN32 + +#ifdef CLAR_WIN32_LONGPATHS +# define CLAR_MAX_PATH 4096 +#else +# define CLAR_MAX_PATH MAX_PATH +#endif + +#define RM_RETRY_COUNT 5 +#define RM_RETRY_DELAY 10 + +#ifdef __MINGW32__ + +/* These security-enhanced functions are not available + * in MinGW, so just use the vanilla ones */ +#define wcscpy_s(a, b, c) wcscpy((a), (c)) +#define wcscat_s(a, b, c) wcscat((a), (c)) + +#endif /* __MINGW32__ */ + +static int +fs__dotordotdot(WCHAR *_tocheck) +{ + return _tocheck[0] == '.' && + (_tocheck[1] == '\0' || + (_tocheck[1] == '.' && _tocheck[2] == '\0')); +} + +static int +fs_rmdir_rmdir(WCHAR *_wpath) +{ + unsigned retries = 1; + + while (!RemoveDirectoryW(_wpath)) { + /* Only retry when we have retries remaining, and the + * error was ERROR_DIR_NOT_EMPTY. */ + if (retries++ > RM_RETRY_COUNT || + ERROR_DIR_NOT_EMPTY != GetLastError()) + return -1; + + /* Give whatever has a handle to a child item some time + * to release it before trying again */ + Sleep(RM_RETRY_DELAY * retries * retries); + } + + return 0; +} + +static void translate_path(WCHAR *path, size_t path_size) +{ + size_t path_len, i; + + if (wcsncmp(path, L"\\\\?\\", 4) == 0) + return; + + path_len = wcslen(path); + cl_assert(path_size > path_len + 4); + + for (i = path_len; i > 0; i--) { + WCHAR c = path[i - 1]; + + if (c == L'/') + path[i + 3] = L'\\'; + else + path[i + 3] = path[i - 1]; + } + + path[0] = L'\\'; + path[1] = L'\\'; + path[2] = L'?'; + path[3] = L'\\'; + path[path_len + 4] = L'\0'; +} + +static void +fs_rmdir_helper(WCHAR *_wsource) +{ + WCHAR buffer[CLAR_MAX_PATH]; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + size_t buffer_prefix_len; + + /* Set up the buffer and capture the length */ + wcscpy_s(buffer, CLAR_MAX_PATH, _wsource); + translate_path(buffer, CLAR_MAX_PATH); + wcscat_s(buffer, CLAR_MAX_PATH, L"\\"); + buffer_prefix_len = wcslen(buffer); + + /* FindFirstFile needs a wildcard to match multiple items */ + wcscat_s(buffer, CLAR_MAX_PATH, L"*"); + find_handle = FindFirstFileW(buffer, &find_data); + cl_assert(INVALID_HANDLE_VALUE != find_handle); + + do { + /* FindFirstFile/FindNextFile gives back . and .. + * entries at the beginning */ + if (fs__dotordotdot(find_data.cFileName)) + continue; + + wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName); + + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) + fs_rmdir_helper(buffer); + else { + /* If set, the +R bit must be cleared before deleting */ + if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes) + cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY)); + + cl_assert(DeleteFileW(buffer)); + } + } + while (FindNextFileW(find_handle, &find_data)); + + /* Ensure that we successfully completed the enumeration */ + cl_assert(ERROR_NO_MORE_FILES == GetLastError()); + + /* Close the find handle */ + FindClose(find_handle); + + /* Now that the directory is empty, remove it */ + cl_assert(0 == fs_rmdir_rmdir(_wsource)); +} + +static int +fs_rm_wait(WCHAR *_wpath) +{ + unsigned retries = 1; + DWORD last_error; + + do { + if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath)) + last_error = GetLastError(); + else + last_error = ERROR_SUCCESS; + + /* Is the item gone? */ + if (ERROR_FILE_NOT_FOUND == last_error || + ERROR_PATH_NOT_FOUND == last_error) + return 0; + + Sleep(RM_RETRY_DELAY * retries * retries); + } + while (retries++ <= RM_RETRY_COUNT); + + return -1; +} + +static void +fs_rm(const char *_source) +{ + WCHAR wsource[CLAR_MAX_PATH]; + DWORD attrs; + + /* The input path is UTF-8. Convert it to wide characters + * for use with the Windows API */ + cl_assert(MultiByteToWideChar(CP_UTF8, + MB_ERR_INVALID_CHARS, + _source, + -1, /* Indicates NULL termination */ + wsource, + CLAR_MAX_PATH)); + + translate_path(wsource, CLAR_MAX_PATH); + + /* Does the item exist? If not, we have no work to do */ + attrs = GetFileAttributesW(wsource); + + if (INVALID_FILE_ATTRIBUTES == attrs) + return; + + if (FILE_ATTRIBUTE_DIRECTORY & attrs) + fs_rmdir_helper(wsource); + else { + /* The item is a file. Strip the +R bit */ + if (FILE_ATTRIBUTE_READONLY & attrs) + cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY)); + + cl_assert(DeleteFileW(wsource)); + } + + /* Wait for the DeleteFile or RemoveDirectory call to complete */ + cl_assert(0 == fs_rm_wait(wsource)); +} + +static void +fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest) +{ + WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH]; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + size_t buf_source_prefix_len, buf_dest_prefix_len; + + wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource); + wcscat_s(buf_source, CLAR_MAX_PATH, L"\\"); + translate_path(buf_source, CLAR_MAX_PATH); + buf_source_prefix_len = wcslen(buf_source); + + wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest); + wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\"); + translate_path(buf_dest, CLAR_MAX_PATH); + buf_dest_prefix_len = wcslen(buf_dest); + + /* Get an enumerator for the items in the source. */ + wcscat_s(buf_source, CLAR_MAX_PATH, L"*"); + find_handle = FindFirstFileW(buf_source, &find_data); + cl_assert(INVALID_HANDLE_VALUE != find_handle); + + /* Create the target directory. */ + cl_assert(CreateDirectoryW(_wdest, NULL)); + + do { + /* FindFirstFile/FindNextFile gives back . and .. + * entries at the beginning */ + if (fs__dotordotdot(find_data.cFileName)) + continue; + + wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName); + wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName); + + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) + fs_copydir_helper(buf_source, buf_dest); + else + cl_assert(CopyFileW(buf_source, buf_dest, TRUE)); + } + while (FindNextFileW(find_handle, &find_data)); + + /* Ensure that we successfully completed the enumeration */ + cl_assert(ERROR_NO_MORE_FILES == GetLastError()); + + /* Close the find handle */ + FindClose(find_handle); +} + +static void +fs_copy(const char *_source, const char *_dest) +{ + WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH]; + DWORD source_attrs, dest_attrs; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + + /* The input paths are UTF-8. Convert them to wide characters + * for use with the Windows API. */ + cl_assert(MultiByteToWideChar(CP_UTF8, + MB_ERR_INVALID_CHARS, + _source, + -1, + wsource, + CLAR_MAX_PATH)); + + cl_assert(MultiByteToWideChar(CP_UTF8, + MB_ERR_INVALID_CHARS, + _dest, + -1, + wdest, + CLAR_MAX_PATH)); + + translate_path(wsource, CLAR_MAX_PATH); + translate_path(wdest, CLAR_MAX_PATH); + + /* Check the source for existence */ + source_attrs = GetFileAttributesW(wsource); + cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs); + + /* Check the target for existence */ + dest_attrs = GetFileAttributesW(wdest); + + if (INVALID_FILE_ATTRIBUTES != dest_attrs) { + /* Target exists; append last path part of source to target. + * Use FindFirstFile to parse the path */ + find_handle = FindFirstFileW(wsource, &find_data); + cl_assert(INVALID_HANDLE_VALUE != find_handle); + wcscat_s(wdest, CLAR_MAX_PATH, L"\\"); + wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName); + FindClose(find_handle); + + /* Check the new target for existence */ + cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest)); + } + + if (FILE_ATTRIBUTE_DIRECTORY & source_attrs) + fs_copydir_helper(wsource, wdest); + else + cl_assert(CopyFileW(wsource, wdest, TRUE)); +} + +void +cl_fs_cleanup(void) +{ + fs_rm(fixture_path(_clar_path, "*")); +} + +#else + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) +# include +#endif + +#if defined(__APPLE__) +# include +#endif + +static void basename_r(const char **out, int *out_len, const char *in) +{ + size_t in_len = strlen(in), start_pos; + + for (in_len = strlen(in); in_len; in_len--) { + if (in[in_len - 1] != '/') + break; + } + + for (start_pos = in_len; start_pos; start_pos--) { + if (in[start_pos - 1] == '/') + break; + } + + cl_assert(in_len - start_pos < INT_MAX); + + if (in_len - start_pos > 0) { + *out = &in[start_pos]; + *out_len = (in_len - start_pos); + } else { + *out = "/"; + *out_len = 1; + } +} + +static char *joinpath(const char *dir, const char *base, int base_len) +{ + char *out; + int len; + + if (base_len == -1) { + size_t bl = strlen(base); + + cl_assert(bl < INT_MAX); + base_len = (int)bl; + } + + len = strlen(dir) + base_len + 2; + cl_assert(len > 0); + + cl_assert(out = malloc(len)); + cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len); + + return out; +} + +static void +fs_copydir_helper(const char *source, const char *dest, int dest_mode) +{ + DIR *source_dir; + struct dirent *d; + + mkdir(dest, dest_mode); + + cl_assert_(source_dir = opendir(source), "Could not open source dir"); + while ((d = (errno = 0, readdir(source_dir))) != NULL) { + char *child; + + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + + child = joinpath(source, d->d_name, -1); + fs_copy(child, dest); + free(child); + } + + cl_assert_(errno == 0, "Failed to iterate source dir"); + + closedir(source_dir); +} + +static void +fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode) +{ + int in, out; + + cl_must_pass((in = open(source, O_RDONLY))); + cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode))); + +#if USE_FCOPYFILE && defined(__APPLE__) + ((void)(source_len)); /* unused */ + cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA)); +#elif USE_SENDFILE && defined(__linux__) + { + ssize_t ret = 0; + + while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) { + source_len -= (size_t)ret; + } + cl_assert(ret >= 0); + } +#else + { + char buf[131072]; + ssize_t ret; + + ((void)(source_len)); /* unused */ + + while ((ret = read(in, buf, sizeof(buf))) > 0) { + size_t len = (size_t)ret; + + while (len && (ret = write(out, buf, len)) > 0) { + cl_assert(ret <= (ssize_t)len); + len -= ret; + } + cl_assert(ret >= 0); + } + cl_assert(ret == 0); + } +#endif + + close(in); + close(out); +} + +static void +fs_copy(const char *source, const char *_dest) +{ + char *dbuf = NULL; + const char *dest = NULL; + struct stat source_st, dest_st; + + cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source"); + + if (lstat(_dest, &dest_st) == 0) { + const char *base; + int base_len; + + /* Target exists and is directory; append basename */ + cl_assert(S_ISDIR(dest_st.st_mode)); + + basename_r(&base, &base_len, source); + cl_assert(base_len < INT_MAX); + + dbuf = joinpath(_dest, base, base_len); + dest = dbuf; + } else if (errno != ENOENT) { + cl_fail("Cannot copy; cannot stat destination"); + } else { + dest = _dest; + } + + if (S_ISDIR(source_st.st_mode)) { + fs_copydir_helper(source, dest, source_st.st_mode); + } else { + fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode); + } + + free(dbuf); +} + +static void +fs_rmdir_helper(const char *path) +{ + DIR *dir; + struct dirent *d; + + cl_assert_(dir = opendir(path), "Could not open dir"); + while ((d = (errno = 0, readdir(dir))) != NULL) { + char *child; + + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + + child = joinpath(path, d->d_name, -1); + fs_rm(child); + free(child); + } + + cl_assert_(errno == 0, "Failed to iterate source dir"); + closedir(dir); + + cl_must_pass_(rmdir(path), "Could not remove directory"); +} + +static void +fs_rm(const char *path) +{ + struct stat st; + + if (lstat(path, &st)) { + if (errno == ENOENT) + return; + + cl_fail("Cannot copy; cannot stat destination"); + } + + if (S_ISDIR(st.st_mode)) { + fs_rmdir_helper(path); + } else { + cl_must_pass(unlink(path)); + } +} + +void +cl_fs_cleanup(void) +{ + clar_unsandbox(); + clar_sandbox(); +} +#endif diff --git a/tests/clar/clar/print.h b/tests/clar/clar/print.h new file mode 100644 index 000000000..dbfd27655 --- /dev/null +++ b/tests/clar/clar/print.h @@ -0,0 +1,200 @@ +/* clap: clar protocol, the traditional clar output format */ + +static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names) +{ + (void)test_count; + printf("Loaded %d suites: %s\n", (int)suite_count, suite_names); + printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n"); +} + +static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count) +{ + (void)test_count; + (void)suite_count; + (void)error_count; + + printf("\n\n"); + clar_report_all(); +} + +static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error) +{ + printf(" %d) Failure:\n", num); + + printf("%s::%s [%s:%"PRIuZ"]\n", + report->suite, + report->test, + error->file, + error->line_number); + + printf(" %s\n", error->error_msg); + + if (error->description != NULL) + printf(" %s\n", error->description); + + printf("\n"); + fflush(stdout); +} + +static void clar_print_clap_ontest(const char *test_name, int test_number, enum cl_test_status status) +{ + (void)test_name; + (void)test_number; + + switch(status) { + case CL_TEST_OK: printf("."); break; + case CL_TEST_FAILURE: printf("F"); break; + case CL_TEST_SKIP: printf("S"); break; + case CL_TEST_NOTRUN: printf("N"); break; + } + + fflush(stdout); +} + +static void clar_print_clap_onsuite(const char *suite_name, int suite_index) +{ + if (_clar.report_suite_names) + printf("\n%s", suite_name); + + (void)suite_index; +} + +static void clar_print_clap_onabort(const char *fmt, va_list arg) +{ + vfprintf(stderr, fmt, arg); +} + +/* tap: test anywhere protocol format */ + +static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names) +{ + (void)test_count; + (void)suite_count; + (void)suite_names; + printf("TAP version 13\n"); +} + +static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count) +{ + (void)suite_count; + (void)error_count; + + printf("1..%d\n", test_count); +} + +static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error) +{ + (void)num; + (void)report; + (void)error; +} + +static void print_escaped(const char *str) +{ + char *c; + + while ((c = strchr(str, '\'')) != NULL) { + printf("%.*s", (int)(c - str), str); + printf("''"); + str = c + 1; + } + + printf("%s", str); +} + +static void clar_print_tap_ontest(const char *test_name, int test_number, enum cl_test_status status) +{ + const struct clar_error *error = _clar.last_report->errors; + + (void)test_name; + (void)test_number; + + switch(status) { + case CL_TEST_OK: + printf("ok %d - %s::%s\n", test_number, _clar.active_suite, test_name); + break; + case CL_TEST_FAILURE: + printf("not ok %d - %s::%s\n", test_number, _clar.active_suite, test_name); + + printf(" ---\n"); + printf(" reason: |\n"); + printf(" %s\n", error->error_msg); + + if (error->description) + printf(" %s\n", error->description); + + printf(" at:\n"); + printf(" file: '"); print_escaped(error->file); printf("'\n"); + printf(" line: %" PRIuZ "\n", error->line_number); + printf(" function: '%s'\n", error->function); + printf(" ---\n"); + + break; + case CL_TEST_SKIP: + case CL_TEST_NOTRUN: + printf("ok %d - # SKIP %s::%s\n", test_number, _clar.active_suite, test_name); + break; + } + + fflush(stdout); +} + +static void clar_print_tap_onsuite(const char *suite_name, int suite_index) +{ + printf("# start of suite %d: %s\n", suite_index, suite_name); +} + +static void clar_print_tap_onabort(const char *fmt, va_list arg) +{ + printf("Bail out! "); + vprintf(fmt, arg); + fflush(stdout); +} + +/* indirection between protocol output selection */ + +#define PRINT(FN, ...) do { \ + switch (_clar.output_format) { \ + case CL_OUTPUT_CLAP: \ + clar_print_clap_##FN (__VA_ARGS__); \ + break; \ + case CL_OUTPUT_TAP: \ + clar_print_tap_##FN (__VA_ARGS__); \ + break; \ + default: \ + abort(); \ + } \ + } while (0) + +static void clar_print_init(int test_count, int suite_count, const char *suite_names) +{ + PRINT(init, test_count, suite_count, suite_names); +} + +static void clar_print_shutdown(int test_count, int suite_count, int error_count) +{ + PRINT(shutdown, test_count, suite_count, error_count); +} + +static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error) +{ + PRINT(error, num, report, error); +} + +static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status status) +{ + PRINT(ontest, test_name, test_number, status); +} + +static void clar_print_onsuite(const char *suite_name, int suite_index) +{ + PRINT(onsuite, suite_name, suite_index); +} + +static void clar_print_onabort(const char *msg, ...) +{ + va_list argp; + va_start(argp, msg); + PRINT(onabort, msg, argp); + va_end(argp); +} diff --git a/tests/clar/clar/sandbox.h b/tests/clar/clar/sandbox.h new file mode 100644 index 000000000..0ba147962 --- /dev/null +++ b/tests/clar/clar/sandbox.h @@ -0,0 +1,154 @@ +#ifdef __APPLE__ +#include +#endif + +static char _clar_path[4096 + 1]; + +static int +is_valid_tmp_path(const char *path) +{ + STAT_T st; + + if (stat(path, &st) != 0) + return 0; + + if (!S_ISDIR(st.st_mode)) + return 0; + + return (access(path, W_OK) == 0); +} + +static int +find_tmp_path(char *buffer, size_t length) +{ +#ifndef _WIN32 + static const size_t var_count = 5; + static const char *env_vars[] = { + "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE" + }; + + size_t i; + + for (i = 0; i < var_count; ++i) { + const char *env = getenv(env_vars[i]); + if (!env) + continue; + + if (is_valid_tmp_path(env)) { +#ifdef __APPLE__ + if (length >= PATH_MAX && realpath(env, buffer) != NULL) + return 0; +#endif + strncpy(buffer, env, length - 1); + buffer[length - 1] = '\0'; + return 0; + } + } + + /* If the environment doesn't say anything, try to use /tmp */ + if (is_valid_tmp_path("/tmp")) { +#ifdef __APPLE__ + if (length >= PATH_MAX && realpath("/tmp", buffer) != NULL) + return 0; +#endif + strncpy(buffer, "/tmp", length - 1); + buffer[length - 1] = '\0'; + return 0; + } + +#else + DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length); + if (env_len > 0 && env_len < (DWORD)length) + return 0; + + if (GetTempPath((DWORD)length, buffer)) + return 0; +#endif + + /* This system doesn't like us, try to use the current directory */ + if (is_valid_tmp_path(".")) { + strncpy(buffer, ".", length - 1); + buffer[length - 1] = '\0'; + return 0; + } + + return -1; +} + +static void clar_unsandbox(void) +{ + if (_clar_path[0] == '\0') + return; + + cl_must_pass(chdir("..")); + + fs_rm(_clar_path); +} + +static int build_sandbox_path(void) +{ +#ifdef CLAR_TMPDIR + const char path_tail[] = CLAR_TMPDIR "_XXXXXX"; +#else + const char path_tail[] = "clar_tmp_XXXXXX"; +#endif + + size_t len; + + if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0) + return -1; + + len = strlen(_clar_path); + +#ifdef _WIN32 + { /* normalize path to POSIX forward slashes */ + size_t i; + for (i = 0; i < len; ++i) { + if (_clar_path[i] == '\\') + _clar_path[i] = '/'; + } + } +#endif + + if (_clar_path[len - 1] != '/') { + _clar_path[len++] = '/'; + } + + strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len); + +#if defined(__MINGW32__) + if (_mktemp(_clar_path) == NULL) + return -1; + + if (mkdir(_clar_path, 0700) != 0) + return -1; +#elif defined(_WIN32) + if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0) + return -1; + + if (mkdir(_clar_path, 0700) != 0) + return -1; +#else + if (mkdtemp(_clar_path) == NULL) + return -1; +#endif + + return 0; +} + +static int clar_sandbox(void) +{ + if (_clar_path[0] == '\0' && build_sandbox_path() < 0) + return -1; + + if (chdir(_clar_path) != 0) + return -1; + + return 0; +} + +const char *clar_sandbox_path(void) +{ + return _clar_path; +} + diff --git a/tests/clar/clar/summary.h b/tests/clar/clar/summary.h new file mode 100644 index 000000000..6279f5057 --- /dev/null +++ b/tests/clar/clar/summary.h @@ -0,0 +1,134 @@ + +#include +#include + +static int clar_summary_close_tag( + struct clar_summary *summary, const char *tag, int indent) +{ + const char *indt; + + if (indent == 0) indt = ""; + else if (indent == 1) indt = "\t"; + else indt = "\t\t"; + + return fprintf(summary->fp, "%s\n", indt, tag); +} + +static int clar_summary_testsuites(struct clar_summary *summary) +{ + return fprintf(summary->fp, "\n"); +} + +static int clar_summary_testsuite(struct clar_summary *summary, + int idn, const char *name, const char *pkg, time_t timestamp, + double elapsed, int test_count, int fail_count, int error_count) +{ + struct tm *tm = localtime(×tamp); + char iso_dt[20]; + + if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0) + return -1; + + return fprintf(summary->fp, "\t\n", + idn, name, pkg, iso_dt, elapsed, test_count, fail_count, error_count); +} + +static int clar_summary_testcase(struct clar_summary *summary, + const char *name, const char *classname, double elapsed) +{ + return fprintf(summary->fp, + "\t\t\n", + name, classname, elapsed); +} + +static int clar_summary_failure(struct clar_summary *summary, + const char *type, const char *message, const char *desc) +{ + return fprintf(summary->fp, + "\t\t\t\n", + type, message, desc); +} + +struct clar_summary *clar_summary_init(const char *filename) +{ + struct clar_summary *summary; + FILE *fp; + + if ((fp = fopen(filename, "w")) == NULL) + return NULL; + + if ((summary = malloc(sizeof(struct clar_summary))) == NULL) { + fclose(fp); + return NULL; + } + + summary->filename = filename; + summary->fp = fp; + + return summary; +} + +int clar_summary_shutdown(struct clar_summary *summary) +{ + struct clar_report *report; + const char *last_suite = NULL; + + if (clar_summary_testsuites(summary) < 0) + goto on_error; + + report = _clar.reports; + while (report != NULL) { + struct clar_error *error = report->errors; + + if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) { + if (clar_summary_testsuite(summary, 0, report->suite, "", + time(NULL), 0, _clar.tests_ran, _clar.total_errors, 0) < 0) + goto on_error; + } + + last_suite = report->suite; + + clar_summary_testcase(summary, report->test, "what", 0); + + while (error != NULL) { + if (clar_summary_failure(summary, "assert", + error->error_msg, error->description) < 0) + goto on_error; + + error = error->next; + } + + if (clar_summary_close_tag(summary, "testcase", 2) < 0) + goto on_error; + + report = report->next; + + if (!report || strcmp(last_suite, report->suite) != 0) { + if (clar_summary_close_tag(summary, "testsuite", 1) < 0) + goto on_error; + } + } + + if (clar_summary_close_tag(summary, "testsuites", 0) < 0 || + fclose(summary->fp) != 0) + goto on_error; + + printf("written summary file to %s\n", summary->filename); + + free(summary); + return 0; + +on_error: + fclose(summary->fp); + free(summary); + return -1; +} diff --git a/tests/clar/clar_libgit2.c b/tests/clar/clar_libgit2.c new file mode 100644 index 000000000..55a09d111 --- /dev/null +++ b/tests/clar/clar_libgit2.c @@ -0,0 +1,623 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "fs_path.h" +#include "git2/sys/repository.h" + +void cl_git_report_failure( + int error, int expected, const char *file, const char *func, int line, const char *fncall) +{ + char msg[4096]; + const git_error *last = git_error_last(); + + if (expected) + p_snprintf(msg, 4096, "error %d (expected %d) - %s", + error, expected, last ? last->message : ""); + else if (error || last) + p_snprintf(msg, 4096, "error %d - %s", + error, last ? last->message : ""); + else + p_snprintf(msg, 4096, "no error, expected non-zero return"); + + clar__assert(0, file, func, line, fncall, msg, 1); +} + +void cl_git_mkfile(const char *filename, const char *content) +{ + int fd; + + fd = p_creat(filename, 0666); + cl_assert(fd != -1); + + if (content) { + cl_must_pass(p_write(fd, content, strlen(content))); + } else { + cl_must_pass(p_write(fd, filename, strlen(filename))); + cl_must_pass(p_write(fd, "\n", 1)); + } + + cl_must_pass(p_close(fd)); +} + +void cl_git_write2file( + const char *path, const char *content, size_t content_len, + int flags, unsigned int mode) +{ + int fd; + cl_assert(path && content); + cl_assert((fd = p_open(path, flags, mode)) >= 0); + if (!content_len) + content_len = strlen(content); + cl_must_pass(p_write(fd, content, content_len)); + cl_must_pass(p_close(fd)); +} + +void cl_git_append2file(const char *path, const char *content) +{ + cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_APPEND, 0644); +} + +void cl_git_rewritefile(const char *path, const char *content) +{ + cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_TRUNC, 0644); +} + +void cl_git_rmfile(const char *filename) +{ + cl_must_pass(p_unlink(filename)); +} + +char *cl_getenv(const char *name) +{ + git_str out = GIT_STR_INIT; + int error = git__getenv(&out, name); + + cl_assert(error >= 0 || error == GIT_ENOTFOUND); + + if (error == GIT_ENOTFOUND) + return NULL; + + if (out.size == 0) { + char *dup = git__strdup(""); + cl_assert(dup); + + return dup; + } + + return git_str_detach(&out); +} + +bool cl_is_env_set(const char *name) +{ + char *env = cl_getenv(name); + bool result = (env != NULL); + git__free(env); + return result; +} + +#ifdef GIT_WIN32 + +#include "win32/utf-conv.h" + +int cl_setenv(const char *name, const char *value) +{ + wchar_t *wide_name, *wide_value = NULL; + + cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0); + + if (value) { + cl_assert(git__utf8_to_16_alloc(&wide_value, value) >= 0); + cl_assert(SetEnvironmentVariableW(wide_name, wide_value)); + } else { + /* Windows XP returns 0 (failed) when passing NULL for lpValue when + * lpName does not exist in the environment block. This behavior + * seems to have changed in later versions. Don't check the return value + * of SetEnvironmentVariable when passing NULL for lpValue. */ + SetEnvironmentVariableW(wide_name, NULL); + } + + git__free(wide_name); + git__free(wide_value); + return 0; +} + +/* This function performs retries on calls to MoveFile in order + * to provide enhanced reliability in the face of antivirus + * agents that may be scanning the source (or in the case that + * the source is a directory, a child of the source). */ +int cl_rename(const char *source, const char *dest) +{ + git_win32_path source_utf16; + git_win32_path dest_utf16; + unsigned retries = 1; + + cl_assert(git_win32_path_from_utf8(source_utf16, source) >= 0); + cl_assert(git_win32_path_from_utf8(dest_utf16, dest) >= 0); + + while (!MoveFileW(source_utf16, dest_utf16)) { + /* Only retry if the error is ERROR_ACCESS_DENIED; + * this may indicate that an antivirus agent is + * preventing the rename from source to target */ + if (retries > 5 || + ERROR_ACCESS_DENIED != GetLastError()) + return -1; + + /* With 5 retries and a coefficient of 10ms, the maximum + * delay here is 550 ms */ + Sleep(10 * retries * retries); + retries++; + } + + return 0; +} + +#else + +#include + +int cl_setenv(const char *name, const char *value) +{ + return (value == NULL) ? unsetenv(name) : setenv(name, value, 1); +} + +int cl_rename(const char *source, const char *dest) +{ + return p_rename(source, dest); +} + +#endif + +static const char *_cl_sandbox = NULL; +static git_repository *_cl_repo = NULL; + +git_repository *cl_git_sandbox_init(const char *sandbox) +{ + /* Get the name of the sandbox folder which will be created */ + const char *basename = cl_fixture_basename(sandbox); + + /* Copy the whole sandbox folder from our fixtures to our test sandbox + * area. After this it can be accessed with `./sandbox` + */ + cl_fixture_sandbox(sandbox); + _cl_sandbox = sandbox; + + cl_git_pass(p_chdir(basename)); + + /* If this is not a bare repo, then rename `sandbox/.gitted` to + * `sandbox/.git` which must be done since we cannot store a folder + * named `.git` inside the fixtures folder of our libgit2 repo. + */ + if (p_access(".gitted", F_OK) == 0) + cl_git_pass(cl_rename(".gitted", ".git")); + + /* If we have `gitattributes`, rename to `.gitattributes`. This may + * be necessary if we don't want the attributes to be applied in the + * libgit2 repo, but just during testing. + */ + if (p_access("gitattributes", F_OK) == 0) + cl_git_pass(cl_rename("gitattributes", ".gitattributes")); + + /* As with `gitattributes`, we may need `gitignore` just for testing. */ + if (p_access("gitignore", F_OK) == 0) + cl_git_pass(cl_rename("gitignore", ".gitignore")); + + cl_git_pass(p_chdir("..")); + + /* Now open the sandbox repository and make it available for tests */ + cl_git_pass(git_repository_open(&_cl_repo, basename)); + + /* Adjust configs after copying to new filesystem */ + cl_git_pass(git_repository_reinit_filesystem(_cl_repo, 0)); + + return _cl_repo; +} + +git_repository *cl_git_sandbox_init_new(const char *sandbox) +{ + cl_git_pass(git_repository_init(&_cl_repo, sandbox, false)); + _cl_sandbox = sandbox; + + return _cl_repo; +} + +git_repository *cl_git_sandbox_reopen(void) +{ + if (_cl_repo) { + git_repository_free(_cl_repo); + _cl_repo = NULL; + + cl_git_pass(git_repository_open( + &_cl_repo, cl_fixture_basename(_cl_sandbox))); + } + + return _cl_repo; +} + +void cl_git_sandbox_cleanup(void) +{ + if (_cl_repo) { + git_repository_free(_cl_repo); + _cl_repo = NULL; + } + if (_cl_sandbox) { + cl_fixture_cleanup(_cl_sandbox); + _cl_sandbox = NULL; + } +} + +bool cl_toggle_filemode(const char *filename) +{ + struct stat st1, st2; + + cl_must_pass(p_stat(filename, &st1)); + cl_must_pass(p_chmod(filename, st1.st_mode ^ 0100)); + cl_must_pass(p_stat(filename, &st2)); + + return (st1.st_mode != st2.st_mode); +} + +bool cl_is_chmod_supported(void) +{ + static int _is_supported = -1; + + if (_is_supported < 0) { + cl_git_mkfile("filemode.t", "Test if filemode can be modified"); + _is_supported = cl_toggle_filemode("filemode.t"); + cl_must_pass(p_unlink("filemode.t")); + } + + return _is_supported; +} + +const char* cl_git_fixture_url(const char *fixturename) +{ + return cl_git_path_url(cl_fixture(fixturename)); +} + +const char* cl_git_path_url(const char *path) +{ + static char url[4096 + 1]; + + const char *in_buf; + git_str path_buf = GIT_STR_INIT; + git_str url_buf = GIT_STR_INIT; + + cl_git_pass(git_fs_path_prettify_dir(&path_buf, path, NULL)); + cl_git_pass(git_str_puts(&url_buf, "file://")); + +#ifdef GIT_WIN32 + /* + * A FILE uri matches the following format: file://[host]/path + * where "host" can be empty and "path" is an absolute path to the resource. + * + * In this test, no hostname is used, but we have to ensure the leading triple slashes: + * + * *nix: file:///usr/home/... + * Windows: file:///C:/Users/... + */ + cl_git_pass(git_str_putc(&url_buf, '/')); +#endif + + in_buf = git_str_cstr(&path_buf); + + /* + * A very hacky Url encoding that only takes care of escaping the spaces + */ + while (*in_buf) { + if (*in_buf == ' ') + cl_git_pass(git_str_puts(&url_buf, "%20")); + else + cl_git_pass(git_str_putc(&url_buf, *in_buf)); + + in_buf++; + } + + cl_assert(url_buf.size < sizeof(url) - 1); + + strncpy(url, git_str_cstr(&url_buf), sizeof(url) - 1); + url[sizeof(url) - 1] = '\0'; + git_str_dispose(&url_buf); + git_str_dispose(&path_buf); + return url; +} + +const char *cl_git_sandbox_path(int is_dir, ...) +{ + const char *path = NULL; + static char _temp[GIT_PATH_MAX]; + git_str buf = GIT_STR_INIT; + va_list arg; + + cl_git_pass(git_str_sets(&buf, clar_sandbox_path())); + + va_start(arg, is_dir); + + while ((path = va_arg(arg, const char *)) != NULL) { + cl_git_pass(git_str_joinpath(&buf, buf.ptr, path)); + } + va_end(arg); + + cl_git_pass(git_fs_path_prettify(&buf, buf.ptr, NULL)); + if (is_dir) + git_fs_path_to_dir(&buf); + + /* make sure we won't truncate */ + cl_assert(git_str_len(&buf) < sizeof(_temp)); + git_str_copy_cstr(_temp, sizeof(_temp), &buf); + + git_str_dispose(&buf); + + return _temp; +} + +typedef struct { + const char *filename; + size_t filename_len; +} remove_data; + +static int remove_placeholders_recurs(void *_data, git_str *path) +{ + remove_data *data = (remove_data *)_data; + size_t pathlen; + + if (git_fs_path_isdir(path->ptr) == true) + return git_fs_path_direach(path, 0, remove_placeholders_recurs, data); + + pathlen = path->size; + + if (pathlen < data->filename_len) + return 0; + + /* if path ends in '/'+filename (or equals filename) */ + if (!strcmp(data->filename, path->ptr + pathlen - data->filename_len) && + (pathlen == data->filename_len || + path->ptr[pathlen - data->filename_len - 1] == '/')) + return p_unlink(path->ptr); + + return 0; +} + +int cl_git_remove_placeholders(const char *directory_path, const char *filename) +{ + int error; + remove_data data; + git_str buffer = GIT_STR_INIT; + + if (git_fs_path_isdir(directory_path) == false) + return -1; + + if (git_str_sets(&buffer, directory_path) < 0) + return -1; + + data.filename = filename; + data.filename_len = strlen(filename); + + error = remove_placeholders_recurs(&data, &buffer); + + git_str_dispose(&buffer); + + return error; +} + +#define CL_COMMIT_NAME "Libgit2 Tester" +#define CL_COMMIT_EMAIL "libgit2-test@github.com" +#define CL_COMMIT_MSG "Test commit of tree " + +void cl_repo_commit_from_index( + git_oid *out, + git_repository *repo, + git_signature *sig, + git_time_t time, + const char *msg) +{ + git_index *index; + git_oid commit_id, tree_id; + git_object *parent = NULL; + git_reference *ref = NULL; + git_tree *tree = NULL; + char buf[128]; + int free_sig = (sig == NULL); + + /* it is fine if looking up HEAD fails - we make this the first commit */ + git_revparse_ext(&parent, &ref, repo, "HEAD"); + + /* write the index content as a tree */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_tree_lookup(&tree, repo, &tree_id)); + + if (sig) + cl_assert(sig->name && sig->email); + else if (!time) + cl_git_pass(git_signature_now(&sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL)); + else + cl_git_pass(git_signature_new( + &sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL, time, 0)); + + if (!msg) { + strcpy(buf, CL_COMMIT_MSG); + git_oid_tostr(buf + strlen(CL_COMMIT_MSG), + sizeof(buf) - strlen(CL_COMMIT_MSG), &tree_id); + msg = buf; + } + + cl_git_pass(git_commit_create_v( + &commit_id, repo, ref ? git_reference_name(ref) : "HEAD", + sig, sig, NULL, msg, tree, parent ? 1 : 0, parent)); + + if (out) + git_oid_cpy(out, &commit_id); + + git_object_free(parent); + git_reference_free(ref); + if (free_sig) + git_signature_free(sig); + git_tree_free(tree); +} + +void cl_repo_set_bool(git_repository *repo, const char *cfg, int value) +{ + git_config *config; + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, cfg, value != 0)); + git_config_free(config); +} + +int cl_repo_get_bool(git_repository *repo, const char *cfg) +{ + int val = 0; + git_config *config; + cl_git_pass(git_repository_config(&config, repo)); + if (git_config_get_bool(&val, config, cfg) < 0) + git_error_clear(); + git_config_free(config); + return val; +} + +void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value) +{ + git_config *config; + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_string(config, cfg, value)); + git_config_free(config); +} + +/* this is essentially the code from git__unescape modified slightly */ +static size_t strip_cr_from_buf(char *start, size_t len) +{ + char *scan, *trail, *end = start + len; + + for (scan = trail = start; scan < end; trail++, scan++) { + while (*scan == '\r') + scan++; /* skip '\r' */ + + if (trail != scan) + *trail = *scan; + } + + *trail = '\0'; + + return (trail - start); +} + +void 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) +{ + char buf[4000]; + ssize_t bytes, total_bytes = 0; + int fd = p_open(path, O_RDONLY | O_BINARY); + cl_assert(fd >= 0); + + if (expected_data && !expected_bytes) + expected_bytes = strlen(expected_data); + + while ((bytes = p_read(fd, buf, sizeof(buf))) != 0) { + clar__assert( + bytes > 0, file, func, line, "error reading from file", path, 1); + + if (ignore_cr) + bytes = strip_cr_from_buf(buf, bytes); + + if (memcmp(expected_data, buf, bytes) != 0) { + int pos; + for (pos = 0; pos < bytes && expected_data[pos] == buf[pos]; ++pos) + /* find differing byte offset */; + p_snprintf( + buf, sizeof(buf), "file content mismatch at byte %"PRIdZ, + (ssize_t)(total_bytes + pos)); + p_close(fd); + clar__fail(file, func, line, path, buf, 1); + } + + expected_data += bytes; + total_bytes += bytes; + } + + p_close(fd); + + clar__assert(!bytes, file, func, line, "error reading from file", path, 1); + clar__assert_equal(file, func, line, "mismatched file length", 1, "%"PRIuZ, + (size_t)expected_bytes, (size_t)total_bytes); +} + +static git_buf _cl_restore_home = GIT_BUF_INIT; + +void cl_fake_home_cleanup(void *payload) +{ + GIT_UNUSED(payload); + + if (_cl_restore_home.ptr) { + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, _cl_restore_home.ptr)); + git_buf_dispose(&_cl_restore_home); + } +} + +void cl_fake_home(void) +{ + git_str path = GIT_STR_INIT; + + cl_git_pass(git_libgit2_opts( + GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &_cl_restore_home)); + + cl_set_cleanup(cl_fake_home_cleanup, NULL); + + if (!git_fs_path_exists("home")) + cl_must_pass(p_mkdir("home", 0777)); + cl_git_pass(git_fs_path_prettify(&path, "home", NULL)); + cl_git_pass(git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); + git_str_dispose(&path); +} + +void cl_sandbox_set_search_path_defaults(void) +{ + git_str path = GIT_STR_INIT; + + git_str_joinpath(&path, clar_sandbox_path(), "__config"); + + if (!git_fs_path_exists(path.ptr)) + cl_must_pass(p_mkdir(path.ptr, 0777)); + + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr); + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr); + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr); + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_PROGRAMDATA, path.ptr); + + git_str_dispose(&path); +} + +#ifdef GIT_WIN32 +bool cl_sandbox_supports_8dot3(void) +{ + git_str longpath = GIT_STR_INIT; + char *shortname; + bool supported; + + cl_git_pass( + git_str_joinpath(&longpath, clar_sandbox_path(), "longer_than_8dot3")); + + cl_git_write2file(longpath.ptr, "", 0, O_RDWR|O_CREAT, 0666); + shortname = git_win32_path_8dot3_name(longpath.ptr); + + supported = (shortname != NULL); + + git__free(shortname); + git_str_dispose(&longpath); + + return supported; +} +#endif + diff --git a/tests/clar/clar_libgit2.h b/tests/clar/clar_libgit2.h new file mode 100644 index 000000000..e3b7bd9f8 --- /dev/null +++ b/tests/clar/clar_libgit2.h @@ -0,0 +1,236 @@ +#ifndef __CLAR_LIBGIT2__ +#define __CLAR_LIBGIT2__ + +#include "clar.h" +#include +#include "common.h" +#include "posix.h" + +/** + * Replace for `clar_must_pass` that passes the last library error as the + * test failure message. + * + * Use this wrapper around all `git_` library calls that return error codes! + */ +#define cl_git_pass(expr) cl_git_expect((expr), 0, __FILE__, __func__, __LINE__) + +#define cl_git_fail_with(error, expr) cl_git_expect((expr), error, __FILE__, __func__, __LINE__) + +#define cl_git_expect(expr, expected, file, func, line) do { \ + int _lg2_error; \ + git_error_clear(); \ + if ((_lg2_error = (expr)) != expected) \ + cl_git_report_failure(_lg2_error, expected, file, func, line, "Function call failed: " #expr); \ + } while (0) + +/** + * Wrapper for `clar_must_fail` -- this one is + * just for consistency. Use with `git_` library + * calls that are supposed to fail! + */ +#define cl_git_fail(expr) do { \ + if ((expr) == 0) \ + git_error_clear(), \ + cl_git_report_failure(0, 0, __FILE__, __func__, __LINE__, "Function call succeeded: " #expr); \ + } while (0) + +/** + * Like cl_git_pass, only for Win32 error code conventions + */ +#define cl_win32_pass(expr) do { \ + int _win32_res; \ + if ((_win32_res = (expr)) == 0) { \ + git_error_set(GIT_ERROR_OS, "Returned: %d, system error code: %lu", _win32_res, GetLastError()); \ + cl_git_report_failure(_win32_res, 0, __FILE__, __func__, __LINE__, "System call failed: " #expr); \ + } \ + } while(0) + +/** + * Thread safe assertions; you cannot use `cl_git_report_failure` from a + * child thread since it will try to `longjmp` to abort and "the effect of + * a call to longjmp() where initialization of the jmp_buf structure was + * not performed in the calling thread is undefined." + * + * Instead, callers can provide a clar thread error context to a thread, + * which will populate and return it on failure. Callers can check the + * status with `cl_git_thread_check`. + */ +typedef struct { + int error; + const char *file; + const char *func; + int line; + const char *expr; + char error_msg[4096]; +} cl_git_thread_err; + +#ifdef GIT_THREADS +# define cl_git_thread_pass(threaderr, expr) cl_git_thread_pass_(threaderr, (expr), __FILE__, __func__, __LINE__) +#else +# define cl_git_thread_pass(threaderr, expr) cl_git_pass(expr) +#endif + +#define cl_git_thread_pass_(__threaderr, __expr, __file, __func, __line) do { \ + git_error_clear(); \ + if ((((cl_git_thread_err *)__threaderr)->error = (__expr)) != 0) { \ + const git_error *_last = git_error_last(); \ + ((cl_git_thread_err *)__threaderr)->file = __file; \ + ((cl_git_thread_err *)__threaderr)->func = __func; \ + ((cl_git_thread_err *)__threaderr)->line = __line; \ + ((cl_git_thread_err *)__threaderr)->expr = "Function call failed: " #__expr; \ + p_snprintf(((cl_git_thread_err *)__threaderr)->error_msg, 4096, "thread 0x%" PRIxZ " - error %d - %s", \ + git_thread_currentid(), ((cl_git_thread_err *)__threaderr)->error, \ + _last ? _last->message : ""); \ + git_thread_exit(__threaderr); \ + } \ + } while (0) + +GIT_INLINE(void) cl_git_thread_check(void *data) +{ + cl_git_thread_err *threaderr = (cl_git_thread_err *)data; + if (threaderr->error != 0) + clar__assert(0, threaderr->file, threaderr->func, threaderr->line, threaderr->expr, threaderr->error_msg, 1); +} + +void cl_git_report_failure(int, int, const char *, const char *, int, const char *); + +#define cl_assert_at_line(expr,file,func,line) \ + clar__assert((expr) != 0, file, func, line, "Expression is not true: " #expr, NULL, 1) + +GIT_INLINE(void) clar__assert_in_range( + int lo, int val, int hi, + const char *file, const char *func, int line, + const char *err, int should_abort) +{ + if (lo > val || hi < val) { + char buf[128]; + p_snprintf(buf, sizeof(buf), "%d not in [%d,%d]", val, lo, hi); + clar__fail(file, func, line, err, buf, should_abort); + } +} + +#define cl_assert_equal_sz(sz1,sz2) do { \ + size_t __sz1 = (size_t)(sz1), __sz2 = (size_t)(sz2); \ + clar__assert_equal(__FILE__,__func__,__LINE__,#sz1 " != " #sz2, 1, "%"PRIuZ, __sz1, __sz2); \ +} while (0) + +#define cl_assert_in_range(L,V,H) \ + clar__assert_in_range((L),(V),(H),__FILE__,__func__,__LINE__,"Range check: " #V " in [" #L "," #H "]", 1) + +#define cl_assert_equal_file(DATA,SIZE,PATH) \ + clar__assert_equal_file(DATA,SIZE,0,PATH,__FILE__,__func__,(int)__LINE__) + +#define cl_assert_equal_file_ignore_cr(DATA,SIZE,PATH) \ + clar__assert_equal_file(DATA,SIZE,1,PATH,__FILE__,__func__,(int)__LINE__) + +void clar__assert_equal_file( + const char *expected_data, + size_t expected_size, + int ignore_cr, + const char *path, + const char *file, + const char *func, + int line); + +GIT_INLINE(void) clar__assert_equal_oid( + const char *file, const char *func, int line, const char *desc, + const git_oid *one, const git_oid *two) +{ + if (git_oid_cmp(one, two)) { + char err[] = "\"........................................\" != \"........................................\""; + + git_oid_fmt(&err[1], one); + git_oid_fmt(&err[47], two); + + clar__fail(file, func, line, desc, err, 1); + } +} + +#define cl_assert_equal_oid(one, two) \ + clar__assert_equal_oid(__FILE__, __func__, __LINE__, \ + "OID mismatch: " #one " != " #two, (one), (two)) + +/* + * Some utility macros for building long strings + */ +#define REP4(STR) STR STR STR STR +#define REP15(STR) REP4(STR) REP4(STR) REP4(STR) STR STR STR +#define REP16(STR) REP4(REP4(STR)) +#define REP256(STR) REP16(REP16(STR)) +#define REP1024(STR) REP4(REP256(STR)) + +/* Write the contents of a buffer to disk */ +void cl_git_mkfile(const char *filename, const char *content); +void cl_git_append2file(const char *filename, const char *new_content); +void cl_git_rewritefile(const char *filename, const char *new_content); +void cl_git_write2file(const char *path, const char *data, + size_t datalen, int flags, unsigned int mode); +void cl_git_rmfile(const char *filename); + +bool cl_toggle_filemode(const char *filename); +bool cl_is_chmod_supported(void); + +/* Environment wrappers */ +char *cl_getenv(const char *name); +bool cl_is_env_set(const char *name); +int cl_setenv(const char *name, const char *value); + +/* Reliable rename */ +int cl_rename(const char *source, const char *dest); + +/* Git sandbox setup helpers */ + +git_repository *cl_git_sandbox_init(const char *sandbox); +git_repository *cl_git_sandbox_init_new(const char *name); +void cl_git_sandbox_cleanup(void); +git_repository *cl_git_sandbox_reopen(void); + +/* + * build a sandbox-relative from path segments + * is_dir will add a trailing slash + * vararg must be a NULL-terminated char * list + */ +const char *cl_git_sandbox_path(int is_dir, ...); + +/* Local-repo url helpers */ +const char* cl_git_fixture_url(const char *fixturename); +const char* cl_git_path_url(const char *path); + +/* Test repository cleaner */ +int cl_git_remove_placeholders(const char *directory_path, const char *filename); + +/* commit creation helpers */ +void cl_repo_commit_from_index( + git_oid *out, + git_repository *repo, + git_signature *sig, + git_time_t time, + const char *msg); + +/* config setting helpers */ +void cl_repo_set_bool(git_repository *repo, const char *cfg, int value); +int cl_repo_get_bool(git_repository *repo, const char *cfg); + +void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value); + +/* set up a fake "home" directory and set libgit2 GLOBAL search path. + * + * automatically configures cleanup function to restore the regular search + * path, although you can call it explicitly if you wish (with NULL). + */ +void cl_fake_home(void); +void cl_fake_home_cleanup(void *); + +void cl_sandbox_set_search_path_defaults(void); + +#ifdef GIT_WIN32 +# define cl_msleep(x) Sleep(x) +#else +# define cl_msleep(x) usleep(1000 * (x)) +#endif + +#ifdef GIT_WIN32 +bool cl_sandbox_supports_8dot3(void); +#endif + +#endif diff --git a/tests/clar/clar_libgit2_timer.c b/tests/clar/clar_libgit2_timer.c new file mode 100644 index 000000000..2330f9351 --- /dev/null +++ b/tests/clar/clar_libgit2_timer.c @@ -0,0 +1,30 @@ +#include "clar_libgit2.h" +#include "clar_libgit2_timer.h" + +void cl_perf_timer__init(cl_perf_timer *t) +{ + memset(t, 0, sizeof(cl_perf_timer)); +} + +void cl_perf_timer__start(cl_perf_timer *t) +{ + t->time_started = git__timer(); +} + +void cl_perf_timer__stop(cl_perf_timer *t) +{ + double time_now = git__timer(); + + t->last = time_now - t->time_started; + t->sum += t->last; +} + +double cl_perf_timer__last(const cl_perf_timer *t) +{ + return t->last; +} + +double cl_perf_timer__sum(const cl_perf_timer *t) +{ + return t->sum; +} diff --git a/tests/clar/clar_libgit2_timer.h b/tests/clar/clar_libgit2_timer.h new file mode 100644 index 000000000..7571a52e9 --- /dev/null +++ b/tests/clar/clar_libgit2_timer.h @@ -0,0 +1,35 @@ +#ifndef __CLAR_LIBGIT2_TIMER__ +#define __CLAR_LIBGIT2_TIMER__ + +struct cl_perf_timer +{ + /* cumulative running time across all start..stop intervals */ + double sum; + + /* value of last start..stop interval */ + double last; + + /* clock value at start */ + double time_started; +}; + +#define CL_PERF_TIMER_INIT {0} + +typedef struct cl_perf_timer cl_perf_timer; + +void cl_perf_timer__init(cl_perf_timer *t); +void cl_perf_timer__start(cl_perf_timer *t); +void cl_perf_timer__stop(cl_perf_timer *t); + +/** + * return value of last start..stop interval in seconds. + */ +double cl_perf_timer__last(const cl_perf_timer *t); + +/** + * return cumulative running time across all start..stop + * intervals in seconds. + */ +double cl_perf_timer__sum(const cl_perf_timer *t); + +#endif /* __CLAR_LIBGIT2_TIMER__ */ diff --git a/tests/clar/clar_libgit2_trace.c b/tests/clar/clar_libgit2_trace.c new file mode 100644 index 000000000..ebb0f41dd --- /dev/null +++ b/tests/clar/clar_libgit2_trace.c @@ -0,0 +1,263 @@ +#include "clar_libgit2_trace.h" +#include "clar_libgit2.h" +#include "clar_libgit2_timer.h" +#include "trace.h" + +struct method { + const char *name; + void (*git_trace_cb)(git_trace_level_t level, const char *msg); + void (*close)(void); +}; + +static const char *message_prefix(git_trace_level_t level) +{ + switch (level) { + case GIT_TRACE_NONE: + return "[NONE]: "; + case GIT_TRACE_FATAL: + return "[FATAL]: "; + case GIT_TRACE_ERROR: + return "[ERROR]: "; + case GIT_TRACE_WARN: + return "[WARN]: "; + case GIT_TRACE_INFO: + return "[INFO]: "; + case GIT_TRACE_DEBUG: + return "[DEBUG]: "; + case GIT_TRACE_TRACE: + return "[TRACE]: "; + default: + return "[?????]: "; + } +} + +static void _git_trace_cb__printf(git_trace_level_t level, const char *msg) +{ + printf("%s%s\n", message_prefix(level), msg); +} + +#if defined(GIT_WIN32) +static void _git_trace_cb__debug(git_trace_level_t level, const char *msg) +{ + OutputDebugString(message_prefix(level)); + OutputDebugString(msg); + OutputDebugString("\n"); + + printf("%s%s\n", message_prefix(level), msg); +} +#else +#define _git_trace_cb__debug _git_trace_cb__printf +#endif + + +static void _trace_printf_close(void) +{ + fflush(stdout); +} + +#define _trace_debug_close _trace_printf_close + + +static struct method s_methods[] = { + { "printf", _git_trace_cb__printf, _trace_printf_close }, + { "debug", _git_trace_cb__debug, _trace_debug_close }, + /* TODO add file method */ + {0}, +}; + + +static int s_trace_loaded = 0; +static int s_trace_level = GIT_TRACE_NONE; +static struct method *s_trace_method = NULL; +static int s_trace_tests = 0; + +static int set_method(const char *name) +{ + int k; + + if (!name || !*name) + name = "printf"; + + for (k=0; (s_methods[k].name); k++) { + if (strcmp(name, s_methods[k].name) == 0) { + s_trace_method = &s_methods[k]; + return 0; + } + } + fprintf(stderr, "Unknown CLAR_TRACE_METHOD: '%s'\n", name); + return -1; +} + + +/** + * Lookup CLAR_TRACE_LEVEL and CLAR_TRACE_METHOD from + * the environment and set the above s_trace_* fields. + * + * If CLAR_TRACE_LEVEL is not set, we disable tracing. + * + * TODO If set, we assume GIT_TRACE_TRACE level, which + * logs everything. Later, we may want to parse the + * value of the environment variable and set a specific + * level. + * + * We assume the "printf" method. This can be changed + * with the CLAR_TRACE_METHOD environment variable. + * Currently, this is only needed on Windows for a "debug" + * version which also writes to the debug output window + * in Visual Studio. + * + * TODO add a "file" method that would open and write + * to a well-known file. This would help keep trace + * output and clar output separate. + * + */ +static void _load_trace_params(void) +{ + char *sz_level; + char *sz_method; + char *sz_tests; + + s_trace_loaded = 1; + + sz_level = cl_getenv("CLAR_TRACE_LEVEL"); + if (!sz_level || !*sz_level) { + s_trace_level = GIT_TRACE_NONE; + s_trace_method = NULL; + return; + } + + /* TODO Parse sz_level and set s_trace_level. */ + s_trace_level = GIT_TRACE_TRACE; + + sz_method = cl_getenv("CLAR_TRACE_METHOD"); + if (set_method(sz_method) < 0) + set_method(NULL); + + sz_tests = cl_getenv("CLAR_TRACE_TESTS"); + if (sz_tests != NULL) + s_trace_tests = 1; +} + +#define HR "================================================================" + +/** + * Timer to report the take spend in a test's run() method. + */ +static cl_perf_timer s_timer_run = CL_PERF_TIMER_INIT; + +/** + * Timer to report total time in a test (init, run, cleanup). + */ +static cl_perf_timer s_timer_test = CL_PERF_TIMER_INIT; + +static void _cl_trace_cb__event_handler( + cl_trace_event ev, + const char *suite_name, + const char *test_name, + void *payload) +{ + GIT_UNUSED(payload); + + if (!s_trace_tests) + return; + + switch (ev) { + case CL_TRACE__SUITE_BEGIN: + git_trace(GIT_TRACE_TRACE, "\n\n%s\n%s: Begin Suite", HR, suite_name); +#if 0 && defined(GIT_WIN32_LEAKCHECK) + git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK, + suite_name); +#endif + break; + + case CL_TRACE__SUITE_END: +#if 0 && defined(GIT_WIN32_LEAKCHECK) + /* As an example of checkpointing, dump leaks within this suite. + * This may generate false positives for things like the global + * TLS error state and maybe the odb cache since they aren't + * freed until the global shutdown and outside the scope of this + * set of tests. + * + * This may under-report if the test itself uses a checkpoint. + * See tests/trace/windows/stacktrace.c + */ + git_win32__crtdbg_stacktrace__dump( + GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK, + suite_name); +#endif + git_trace(GIT_TRACE_TRACE, "\n\n%s: End Suite\n%s", suite_name, HR); + break; + + case CL_TRACE__TEST__BEGIN: + git_trace(GIT_TRACE_TRACE, "\n%s::%s: Begin Test", suite_name, test_name); + cl_perf_timer__init(&s_timer_test); + cl_perf_timer__start(&s_timer_test); + break; + + case CL_TRACE__TEST__END: + cl_perf_timer__stop(&s_timer_test); + git_trace(GIT_TRACE_TRACE, "%s::%s: End Test (%.3f %.3f)", suite_name, test_name, + cl_perf_timer__last(&s_timer_run), + cl_perf_timer__last(&s_timer_test)); + break; + + case CL_TRACE__TEST__RUN_BEGIN: + git_trace(GIT_TRACE_TRACE, "%s::%s: Begin Run", suite_name, test_name); + cl_perf_timer__init(&s_timer_run); + cl_perf_timer__start(&s_timer_run); + break; + + case CL_TRACE__TEST__RUN_END: + cl_perf_timer__stop(&s_timer_run); + git_trace(GIT_TRACE_TRACE, "%s::%s: End Run", suite_name, test_name); + break; + + case CL_TRACE__TEST__LONGJMP: + cl_perf_timer__stop(&s_timer_run); + git_trace(GIT_TRACE_TRACE, "%s::%s: Aborted", suite_name, test_name); + break; + + default: + break; + } +} + +/** + * Setup/Enable git_trace() based upon settings user's environment. + */ +void cl_global_trace_register(void) +{ + if (!s_trace_loaded) + _load_trace_params(); + + if (s_trace_level == GIT_TRACE_NONE) + return; + if (s_trace_method == NULL) + return; + if (s_trace_method->git_trace_cb == NULL) + return; + + git_trace_set(s_trace_level, s_trace_method->git_trace_cb); + cl_trace_register(_cl_trace_cb__event_handler, NULL); +} + +/** + * If we turned on git_trace() earlier, turn it off. + * + * This is intended to let us close/flush any buffered + * IO if necessary. + * + */ +void cl_global_trace_disable(void) +{ + cl_trace_register(NULL, NULL); + git_trace_set(GIT_TRACE_NONE, NULL); + if (s_trace_method && s_trace_method->close) + s_trace_method->close(); + + /* Leave s_trace_ vars set so they can restart tracing + * since we only want to hit the environment variables + * once. + */ +} diff --git a/tests/clar/clar_libgit2_trace.h b/tests/clar/clar_libgit2_trace.h new file mode 100644 index 000000000..09d1e050f --- /dev/null +++ b/tests/clar/clar_libgit2_trace.h @@ -0,0 +1,7 @@ +#ifndef __CLAR_LIBGIT2_TRACE__ +#define __CLAR_LIBGIT2_TRACE__ + +void cl_global_trace_register(void); +void cl_global_trace_disable(void); + +#endif diff --git a/tests/clar/generate.py b/tests/clar/generate.py new file mode 100644 index 000000000..d2cdb684a --- /dev/null +++ b/tests/clar/generate.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python +# +# Copyright (c) Vicent Marti. All rights reserved. +# +# This file is part of clar, distributed under the ISC license. +# For full terms see the included COPYING file. +# + +from __future__ import with_statement +from string import Template +import re, fnmatch, os, sys, codecs, pickle, io + +class Module(object): + class Template(object): + def __init__(self, module): + self.module = module + + def _render_callback(self, cb): + if not cb: + return ' { NULL, NULL }' + return ' { "%s", &%s }' % (cb['short_name'], cb['symbol']) + + class DeclarationTemplate(Template): + def render(self): + out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n" + + for initializer in self.module.initializers: + out += "extern %s;\n" % initializer['declaration'] + + if self.module.cleanup: + out += "extern %s;\n" % self.module.cleanup['declaration'] + + return out + + class CallbacksTemplate(Template): + def render(self): + out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name + out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks) + out += "\n};\n" + return out + + class InfoTemplate(Template): + def render(self): + templates = [] + + initializers = self.module.initializers + if len(initializers) == 0: + initializers = [ None ] + + for initializer in initializers: + name = self.module.clean_name() + if initializer and initializer['short_name'].startswith('initialize_'): + variant = initializer['short_name'][len('initialize_'):] + name += " (%s)" % variant.replace('_', ' ') + + template = Template( + r""" + { + "${clean_name}", + ${initialize}, + ${cleanup}, + ${cb_ptr}, ${cb_count}, ${enabled} + }""" + ).substitute( + clean_name = name, + initialize = self._render_callback(initializer), + cleanup = self._render_callback(self.module.cleanup), + cb_ptr = "_clar_cb_%s" % self.module.name, + cb_count = len(self.module.callbacks), + enabled = int(self.module.enabled) + ) + templates.append(template) + + return ','.join(templates) + + def __init__(self, name): + self.name = name + + self.mtime = 0 + self.enabled = True + self.modified = False + + def clean_name(self): + return self.name.replace("_", "::") + + def _skip_comments(self, text): + SKIP_COMMENTS_REGEX = re.compile( + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', + re.DOTALL | re.MULTILINE) + + def _replacer(match): + s = match.group(0) + return "" if s.startswith('/') else s + + return re.sub(SKIP_COMMENTS_REGEX, _replacer, text) + + def parse(self, contents): + TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{" + + contents = self._skip_comments(contents) + regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE) + + self.callbacks = [] + self.initializers = [] + self.cleanup = None + + for (declaration, symbol, short_name) in regex.findall(contents): + data = { + "short_name" : short_name, + "declaration" : declaration, + "symbol" : symbol + } + + if short_name.startswith('initialize'): + self.initializers.append(data) + elif short_name == 'cleanup': + self.cleanup = data + else: + self.callbacks.append(data) + + return self.callbacks != [] + + def refresh(self, path): + self.modified = False + + try: + st = os.stat(path) + + # Not modified + if st.st_mtime == self.mtime: + return True + + self.modified = True + self.mtime = st.st_mtime + + with codecs.open(path, encoding='utf-8') as fp: + raw_content = fp.read() + + except IOError: + return False + + return self.parse(raw_content) + +class TestSuite(object): + + def __init__(self, path, output): + self.path = path + self.output = output + + def maybe_generate(self, path): + if not os.path.isfile(path): + return True + + if any(module.modified for module in self.modules.values()): + return True + + return False + + def find_modules(self): + modules = [] + for root, _, files in os.walk(self.path): + module_root = root[len(self.path):] + module_root = [c for c in module_root.split(os.sep) if c] + + tests_in_module = fnmatch.filter(files, "*.c") + + for test_file in tests_in_module: + full_path = os.path.join(root, test_file) + module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_") + + modules.append((full_path, module_name)) + + return modules + + def load_cache(self): + path = os.path.join(self.output, '.clarcache') + cache = {} + + try: + fp = open(path, 'rb') + cache = pickle.load(fp) + fp.close() + except (IOError, ValueError): + pass + + return cache + + def save_cache(self): + path = os.path.join(self.output, '.clarcache') + with open(path, 'wb') as cache: + pickle.dump(self.modules, cache) + + def load(self, force = False): + module_data = self.find_modules() + self.modules = {} if force else self.load_cache() + + for path, name in module_data: + if name not in self.modules: + self.modules[name] = Module(name) + + if not self.modules[name].refresh(path): + del self.modules[name] + + def disable(self, excluded): + for exclude in excluded: + for module in self.modules.values(): + name = module.clean_name() + if name.startswith(exclude): + module.enabled = False + module.modified = True + + def suite_count(self): + return sum(max(1, len(m.initializers)) for m in self.modules.values()) + + def callback_count(self): + return sum(len(module.callbacks) for module in self.modules.values()) + + def write(self): + wrote_suite = self.write_suite() + wrote_header = self.write_header() + + if wrote_suite or wrote_header: + self.save_cache() + return True + + return False + + def write_output(self, fn, data): + if not self.maybe_generate(fn): + return False + + current = None + + try: + with open(fn, 'r') as input: + current = input.read() + except OSError: + pass + except IOError: + pass + + if current == data: + return False + + with open(fn, 'w') as output: + output.write(data) + + return True + + def write_suite(self): + suite_fn = os.path.join(self.output, 'clar.suite') + + with io.StringIO() as suite_file: + modules = sorted(self.modules.values(), key=lambda module: module.name) + + for module in modules: + t = Module.DeclarationTemplate(module) + suite_file.write(t.render()) + + for module in modules: + t = Module.CallbacksTemplate(module) + suite_file.write(t.render()) + + suites = "static struct clar_suite _clar_suites[] = {" + ','.join( + Module.InfoTemplate(module).render() for module in modules + ) + "\n};\n" + + suite_file.write(suites) + + suite_file.write(u"static const size_t _clar_suite_count = %d;\n" % self.suite_count()) + suite_file.write(u"static const size_t _clar_callback_count = %d;\n" % self.callback_count()) + + return self.write_output(suite_fn, suite_file.getvalue()) + + return False + + def write_header(self): + header_fn = os.path.join(self.output, 'clar_suite.h') + + with io.StringIO() as header_file: + header_file.write(u"#ifndef _____clar_suite_h_____\n") + header_file.write(u"#define _____clar_suite_h_____\n") + + modules = sorted(self.modules.values(), key=lambda module: module.name) + + for module in modules: + t = Module.DeclarationTemplate(module) + header_file.write(t.render()) + + header_file.write(u"#endif\n") + + return self.write_output(header_fn, header_file.getvalue()) + + return False + +if __name__ == '__main__': + from optparse import OptionParser + + parser = OptionParser() + parser.add_option('-f', '--force', action="store_true", dest='force', default=False) + parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[]) + parser.add_option('-o', '--output', dest='output') + + options, args = parser.parse_args() + if len(args) > 1: + print("More than one path given") + sys.exit(1) + + path = args.pop() if args else '.' + output = options.output or path + suite = TestSuite(path, output) + suite.load(options.force) + suite.disable(options.excluded) + if suite.write(): + print("Written `clar.suite`, `clar_suite.h` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count())) + diff --git a/tests/clar/main.c b/tests/clar/main.c new file mode 100644 index 000000000..56751c288 --- /dev/null +++ b/tests/clar/main.c @@ -0,0 +1,50 @@ +#include "clar_libgit2.h" +#include "clar_libgit2_trace.h" + +#ifdef GIT_WIN32_LEAKCHECK +# include "win32/w32_leakcheck.h" +#endif + +#ifdef _WIN32 +int __cdecl main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + int res; + char *at_exit_cmd; + + clar_test_init(argc, argv); + + res = git_libgit2_init(); + if (res < 0) { + const git_error *err = git_error_last(); + const char *msg = err ? err->message : "unknown failure"; + fprintf(stderr, "failed to init libgit2: %s\n", msg); + return res; + } + + cl_global_trace_register(); + cl_sandbox_set_search_path_defaults(); + + /* Run the test suite */ + res = clar_test_run(); + + clar_test_shutdown(); + + cl_global_trace_disable(); + git_libgit2_shutdown(); + +#ifdef GIT_WIN32_LEAKCHECK + if (git_win32_leakcheck_has_leaks()) + res = res || 1; +#endif + + at_exit_cmd = getenv("CLAR_AT_EXIT"); + if (at_exit_cmd != NULL) { + int at_exit = system(at_exit_cmd); + return res || at_exit; + } + + return res; +} diff --git a/tests/libgit2/CMakeLists.txt b/tests/libgit2/CMakeLists.txt index 8bf199ecf..9ab01077a 100644 --- a/tests/libgit2/CMakeLists.txt +++ b/tests/libgit2/CMakeLists.txt @@ -8,8 +8,9 @@ if(NOT PYTHONINTERP_FOUND) "Make sure python is available, or pass -DBUILD_TESTS=OFF to skip building the tests") ENDIF() +set(CLAR_PATH "${PROJECT_SOURCE_DIR}/tests/clar") set(CLAR_FIXTURES "${PROJECT_SOURCE_DIR}/tests/resources/") -set(CLAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}") +set(TEST_PATH "${CMAKE_CURRENT_SOURCE_DIR}") add_definitions(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\") add_definitions(-DCLAR_TMPDIR=\"libgit2_tests\") add_definitions(-DCLAR_WIN32_LONGPATHS) @@ -18,19 +19,19 @@ add_definitions(-D_FILE_OFFSET_BITS=64) # Ensure that we do not use deprecated functions internally add_definitions(-DGIT_DEPRECATE_HARD) -set(TEST_INCLUDES "${CLAR_PATH}" "${CMAKE_CURRENT_BINARY_DIR}") -file(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c ${CLAR_PATH}/*/*.h) -set(SRC_CLAR "main.c" "clar_libgit2.c" "clar_libgit2_trace.c" "clar_libgit2_timer.c" "clar.c") +set(TEST_INCLUDES "${CLAR_PATH}" "${TEST_PATH}" "${CMAKE_CURRENT_BINARY_DIR}") +file(GLOB_RECURSE SRC_TEST ${TEST_PATH}/*/*.c ${TEST_PATH}/*/*.h) +file(GLOB_RECURSE SRC_CLAR ${CLAR_PATH}/*.c ${CLAR_PATH}/*.h) if(MSVC_IDE) - list(APPEND SRC_CLAR "precompiled.c") + list(APPEND SRC_TEST "precompiled.c") endif() add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/clar.suite ${CMAKE_CURRENT_BINARY_DIR}/clar_suite.h - COMMAND ${PYTHON_EXECUTABLE} generate.py -o "${CMAKE_CURRENT_BINARY_DIR}" -f -xonline -xstress -xperf . + COMMAND ${PYTHON_EXECUTABLE} ${CLAR_PATH}/generate.py -o "${CMAKE_CURRENT_BINARY_DIR}" -f -xonline -xstress -xperf . DEPENDS ${SRC_TEST} - WORKING_DIRECTORY ${CLAR_PATH} + WORKING_DIRECTORY ${TEST_PATH} ) set_source_files_properties( diff --git a/tests/libgit2/clar.c b/tests/libgit2/clar.c deleted file mode 100644 index ca508d073..000000000 --- a/tests/libgit2/clar.c +++ /dev/null @@ -1,788 +0,0 @@ -/* - * Copyright (c) Vicent Marti. All rights reserved. - * - * This file is part of clar, distributed under the ISC license. - * For full terms see the included COPYING file. - */ -#include -#include -#include -#include -#include -#include -#include -#include - -/* required for sandboxing */ -#include -#include - -#ifdef _WIN32 -# include -# include -# include -# include - -# define _MAIN_CC __cdecl - -# ifndef stat -# define stat(path, st) _stat(path, st) -# endif -# ifndef mkdir -# define mkdir(path, mode) _mkdir(path) -# endif -# ifndef chdir -# define chdir(path) _chdir(path) -# endif -# ifndef access -# define access(path, mode) _access(path, mode) -# endif -# ifndef strdup -# define strdup(str) _strdup(str) -# endif -# ifndef strcasecmp -# define strcasecmp(a,b) _stricmp(a,b) -# endif - -# ifndef __MINGW32__ -# pragma comment(lib, "shell32") -# ifndef strncpy -# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) -# endif -# ifndef W_OK -# define W_OK 02 -# endif -# ifndef S_ISDIR -# define S_ISDIR(x) ((x & _S_IFDIR) != 0) -# endif -# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__) -# else -# define p_snprintf snprintf -# endif - -# ifndef PRIuZ -# define PRIuZ "Iu" -# endif -# ifndef PRIxZ -# define PRIxZ "Ix" -# endif - -# if defined(_MSC_VER) || defined(__MINGW32__) - typedef struct stat STAT_T; -# else - typedef struct _stat STAT_T; -# endif -#else -# include /* waitpid(2) */ -# include -# define _MAIN_CC -# define p_snprintf snprintf -# ifndef PRIuZ -# define PRIuZ "zu" -# endif -# ifndef PRIxZ -# define PRIxZ "zx" -# endif - typedef struct stat STAT_T; -#endif - -#include "clar.h" - -static void fs_rm(const char *_source); -static void fs_copy(const char *_source, const char *dest); - -static const char * -fixture_path(const char *base, const char *fixture_name); - -struct clar_error { - const char *file; - const char *function; - size_t line_number; - const char *error_msg; - char *description; - - struct clar_error *next; -}; - -struct clar_explicit { - size_t suite_idx; - const char *filter; - - struct clar_explicit *next; -}; - -struct clar_report { - const char *test; - int test_number; - const char *suite; - - enum cl_test_status status; - - struct clar_error *errors; - struct clar_error *last_error; - - struct clar_report *next; -}; - -struct clar_summary { - const char *filename; - FILE *fp; -}; - -static struct { - enum cl_test_status test_status; - - const char *active_test; - const char *active_suite; - - int total_skipped; - int total_errors; - - int tests_ran; - int suites_ran; - - enum cl_output_format output_format; - - int report_errors_only; - int exit_on_error; - int report_suite_names; - - int write_summary; - char *summary_filename; - struct clar_summary *summary; - - struct clar_explicit *explicit; - struct clar_explicit *last_explicit; - - struct clar_report *reports; - struct clar_report *last_report; - - void (*local_cleanup)(void *); - void *local_cleanup_payload; - - jmp_buf trampoline; - int trampoline_enabled; - - cl_trace_cb *pfn_trace_cb; - void *trace_payload; - -} _clar; - -struct clar_func { - const char *name; - void (*ptr)(void); -}; - -struct clar_suite { - const char *name; - struct clar_func initialize; - struct clar_func cleanup; - const struct clar_func *tests; - size_t test_count; - int enabled; -}; - -/* From clar_print_*.c */ -static void clar_print_init(int test_count, int suite_count, const char *suite_names); -static void clar_print_shutdown(int test_count, int suite_count, int error_count); -static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error); -static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status failed); -static void clar_print_onsuite(const char *suite_name, int suite_index); -static void clar_print_onabort(const char *msg, ...); - -/* From clar_sandbox.c */ -static void clar_unsandbox(void); -static int clar_sandbox(void); - -/* From summary.h */ -static struct clar_summary *clar_summary_init(const char *filename); -static int clar_summary_shutdown(struct clar_summary *fp); - -/* Load the declarations for the test suite */ -#include "clar.suite" - - -#define CL_TRACE(ev) \ - do { \ - if (_clar.pfn_trace_cb) \ - _clar.pfn_trace_cb(ev, \ - _clar.active_suite, \ - _clar.active_test, \ - _clar.trace_payload); \ - } while (0) - -void cl_trace_register(cl_trace_cb *cb, void *payload) -{ - _clar.pfn_trace_cb = cb; - _clar.trace_payload = payload; -} - - -/* Core test functions */ -static void -clar_report_errors(struct clar_report *report) -{ - struct clar_error *error; - int i = 1; - - for (error = report->errors; error; error = error->next) - clar_print_error(i++, _clar.last_report, error); -} - -static void -clar_report_all(void) -{ - struct clar_report *report; - struct clar_error *error; - int i = 1; - - for (report = _clar.reports; report; report = report->next) { - if (report->status != CL_TEST_FAILURE) - continue; - - for (error = report->errors; error; error = error->next) - clar_print_error(i++, report, error); - } -} - -static void -clar_run_test( - const struct clar_func *test, - const struct clar_func *initialize, - const struct clar_func *cleanup) -{ - _clar.trampoline_enabled = 1; - - CL_TRACE(CL_TRACE__TEST__BEGIN); - - if (setjmp(_clar.trampoline) == 0) { - if (initialize->ptr != NULL) - initialize->ptr(); - - CL_TRACE(CL_TRACE__TEST__RUN_BEGIN); - test->ptr(); - CL_TRACE(CL_TRACE__TEST__RUN_END); - } - - _clar.trampoline_enabled = 0; - - if (_clar.last_report->status == CL_TEST_NOTRUN) - _clar.last_report->status = CL_TEST_OK; - - if (_clar.local_cleanup != NULL) - _clar.local_cleanup(_clar.local_cleanup_payload); - - if (cleanup->ptr != NULL) - cleanup->ptr(); - - CL_TRACE(CL_TRACE__TEST__END); - - _clar.tests_ran++; - - /* remove any local-set cleanup methods */ - _clar.local_cleanup = NULL; - _clar.local_cleanup_payload = NULL; - - if (_clar.report_errors_only) { - clar_report_errors(_clar.last_report); - } else { - clar_print_ontest(test->name, _clar.tests_ran, _clar.last_report->status); - } -} - -static void -clar_run_suite(const struct clar_suite *suite, const char *filter) -{ - const struct clar_func *test = suite->tests; - size_t i, matchlen; - struct clar_report *report; - int exact = 0; - - if (!suite->enabled) - return; - - if (_clar.exit_on_error && _clar.total_errors) - return; - - if (!_clar.report_errors_only) - clar_print_onsuite(suite->name, ++_clar.suites_ran); - - _clar.active_suite = suite->name; - _clar.active_test = NULL; - CL_TRACE(CL_TRACE__SUITE_BEGIN); - - if (filter) { - size_t suitelen = strlen(suite->name); - matchlen = strlen(filter); - if (matchlen <= suitelen) { - filter = NULL; - } else { - filter += suitelen; - while (*filter == ':') - ++filter; - matchlen = strlen(filter); - - if (matchlen && filter[matchlen - 1] == '$') { - exact = 1; - matchlen--; - } - } - } - - for (i = 0; i < suite->test_count; ++i) { - if (filter && strncmp(test[i].name, filter, matchlen)) - continue; - - if (exact && strlen(test[i].name) != matchlen) - continue; - - _clar.active_test = test[i].name; - - report = calloc(1, sizeof(struct clar_report)); - report->suite = _clar.active_suite; - report->test = _clar.active_test; - report->test_number = _clar.tests_ran; - report->status = CL_TEST_NOTRUN; - - if (_clar.reports == NULL) - _clar.reports = report; - - if (_clar.last_report != NULL) - _clar.last_report->next = report; - - _clar.last_report = report; - - clar_run_test(&test[i], &suite->initialize, &suite->cleanup); - - if (_clar.exit_on_error && _clar.total_errors) - return; - } - - _clar.active_test = NULL; - CL_TRACE(CL_TRACE__SUITE_END); -} - -static void -clar_usage(const char *arg) -{ - printf("Usage: %s [options]\n\n", arg); - printf("Options:\n"); - printf(" -sname Run only the suite with `name` (can go to individual test name)\n"); - printf(" -iname Include the suite with `name`\n"); - printf(" -xname Exclude the suite with `name`\n"); - printf(" -v Increase verbosity (show suite names)\n"); - printf(" -q Only report tests that had an error\n"); - printf(" -Q Quit as soon as a test fails\n"); - printf(" -t Display results in tap format\n"); - printf(" -l Print suite names\n"); - printf(" -r[filename] Write summary file (to the optional filename)\n"); - exit(-1); -} - -static void -clar_parse_args(int argc, char **argv) -{ - int i; - - /* Verify options before execute */ - for (i = 1; i < argc; ++i) { - char *argument = argv[i]; - - if (argument[0] != '-' || argument[1] == '\0' - || strchr("sixvqQtlr", argument[1]) == NULL) { - clar_usage(argv[0]); - } - } - - for (i = 1; i < argc; ++i) { - char *argument = argv[i]; - - switch (argument[1]) { - case 's': - case 'i': - case 'x': { /* given suite name */ - int offset = (argument[2] == '=') ? 3 : 2, found = 0; - char action = argument[1]; - size_t j, arglen, suitelen, cmplen; - - argument += offset; - arglen = strlen(argument); - - if (arglen == 0) - clar_usage(argv[0]); - - for (j = 0; j < _clar_suite_count; ++j) { - suitelen = strlen(_clar_suites[j].name); - cmplen = (arglen < suitelen) ? arglen : suitelen; - - if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) { - int exact = (arglen >= suitelen); - - /* Do we have a real suite prefix separated by a - * trailing '::' or just a matching substring? */ - if (arglen > suitelen && (argument[suitelen] != ':' - || argument[suitelen + 1] != ':')) - continue; - - ++found; - - if (!exact) - _clar.report_suite_names = 1; - - switch (action) { - case 's': { - struct clar_explicit *explicit = - calloc(1, sizeof(struct clar_explicit)); - assert(explicit); - - explicit->suite_idx = j; - explicit->filter = argument; - - if (_clar.explicit == NULL) - _clar.explicit = explicit; - - if (_clar.last_explicit != NULL) - _clar.last_explicit->next = explicit; - - _clar_suites[j].enabled = 1; - _clar.last_explicit = explicit; - break; - } - case 'i': _clar_suites[j].enabled = 1; break; - case 'x': _clar_suites[j].enabled = 0; break; - } - - if (exact) - break; - } - } - - if (!found) { - clar_print_onabort("No suite matching '%s' found.\n", argument); - exit(-1); - } - break; - } - - case 'q': - _clar.report_errors_only = 1; - break; - - case 'Q': - _clar.exit_on_error = 1; - break; - - case 't': - _clar.output_format = CL_OUTPUT_TAP; - break; - - case 'l': { - size_t j; - printf("Test suites (use -s to run just one):\n"); - for (j = 0; j < _clar_suite_count; ++j) - printf(" %3d: %s\n", (int)j, _clar_suites[j].name); - - exit(0); - } - - case 'v': - _clar.report_suite_names = 1; - break; - - case 'r': - _clar.write_summary = 1; - free(_clar.summary_filename); - _clar.summary_filename = strdup(*(argument + 2) ? (argument + 2) : "summary.xml"); - break; - - default: - assert(!"Unexpected commandline argument!"); - } - } -} - -void -clar_test_init(int argc, char **argv) -{ - if (argc > 1) - clar_parse_args(argc, argv); - - clar_print_init( - (int)_clar_callback_count, - (int)_clar_suite_count, - "" - ); - - if ((_clar.summary_filename = getenv("CLAR_SUMMARY")) != NULL) { - _clar.write_summary = 1; - _clar.summary_filename = strdup(_clar.summary_filename); - } - - if (_clar.write_summary && - !(_clar.summary = clar_summary_init(_clar.summary_filename))) { - clar_print_onabort("Failed to open the summary file\n"); - exit(-1); - } - - if (clar_sandbox() < 0) { - clar_print_onabort("Failed to sandbox the test runner.\n"); - exit(-1); - } -} - -int -clar_test_run(void) -{ - size_t i; - struct clar_explicit *explicit; - - if (_clar.explicit) { - for (explicit = _clar.explicit; explicit; explicit = explicit->next) - clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter); - } else { - for (i = 0; i < _clar_suite_count; ++i) - clar_run_suite(&_clar_suites[i], NULL); - } - - return _clar.total_errors; -} - -void -clar_test_shutdown(void) -{ - struct clar_explicit *explicit, *explicit_next; - struct clar_report *report, *report_next; - - clar_print_shutdown( - _clar.tests_ran, - (int)_clar_suite_count, - _clar.total_errors - ); - - clar_unsandbox(); - - if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) { - clar_print_onabort("Failed to write the summary file\n"); - exit(-1); - } - - for (explicit = _clar.explicit; explicit; explicit = explicit_next) { - explicit_next = explicit->next; - free(explicit); - } - - for (report = _clar.reports; report; report = report_next) { - report_next = report->next; - free(report); - } - - free(_clar.summary_filename); -} - -int -clar_test(int argc, char **argv) -{ - int errors; - - clar_test_init(argc, argv); - errors = clar_test_run(); - clar_test_shutdown(); - - return errors; -} - -static void abort_test(void) -{ - if (!_clar.trampoline_enabled) { - clar_print_onabort( - "Fatal error: a cleanup method raised an exception."); - clar_report_errors(_clar.last_report); - exit(-1); - } - - CL_TRACE(CL_TRACE__TEST__LONGJMP); - longjmp(_clar.trampoline, -1); -} - -void clar__skip(void) -{ - _clar.last_report->status = CL_TEST_SKIP; - _clar.total_skipped++; - abort_test(); -} - -void clar__fail( - const char *file, - const char *function, - size_t line, - const char *error_msg, - const char *description, - int should_abort) -{ - struct clar_error *error = calloc(1, sizeof(struct clar_error)); - - if (_clar.last_report->errors == NULL) - _clar.last_report->errors = error; - - if (_clar.last_report->last_error != NULL) - _clar.last_report->last_error->next = error; - - _clar.last_report->last_error = error; - - error->file = file; - error->function = function; - error->line_number = line; - error->error_msg = error_msg; - - if (description != NULL) - error->description = strdup(description); - - _clar.total_errors++; - _clar.last_report->status = CL_TEST_FAILURE; - - if (should_abort) - abort_test(); -} - -void clar__assert( - int condition, - const char *file, - const char *function, - size_t line, - const char *error_msg, - const char *description, - int should_abort) -{ - if (condition) - return; - - clar__fail(file, function, line, error_msg, description, should_abort); -} - -void clar__assert_equal( - const char *file, - const char *function, - size_t line, - const char *err, - int should_abort, - const char *fmt, - ...) -{ - va_list args; - char buf[4096]; - int is_equal = 1; - - va_start(args, fmt); - - if (!strcmp("%s", fmt)) { - const char *s1 = va_arg(args, const char *); - const char *s2 = va_arg(args, const char *); - is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2); - - if (!is_equal) { - if (s1 && s2) { - int pos; - for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) - /* find differing byte offset */; - p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", - s1, s2, pos); - } else { - p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2); - } - } - } - else if(!strcmp("%.*s", fmt)) { - const char *s1 = va_arg(args, const char *); - const char *s2 = va_arg(args, const char *); - int len = va_arg(args, int); - is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len); - - if (!is_equal) { - if (s1 && s2) { - int pos; - for (pos = 0; s1[pos] == s2[pos] && pos < len; ++pos) - /* find differing byte offset */; - p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s' (at byte %d)", - len, s1, len, s2, pos); - } else { - p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s'", len, s1, len, s2); - } - } - } - else if (!strcmp("%ls", fmt)) { - const wchar_t *wcs1 = va_arg(args, const wchar_t *); - const wchar_t *wcs2 = va_arg(args, const wchar_t *); - is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2); - - if (!is_equal) { - if (wcs1 && wcs2) { - int pos; - for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos) - /* find differing byte offset */; - p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)", - wcs1, wcs2, pos); - } else { - p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2); - } - } - } - else if(!strcmp("%.*ls", fmt)) { - const wchar_t *wcs1 = va_arg(args, const wchar_t *); - const wchar_t *wcs2 = va_arg(args, const wchar_t *); - int len = va_arg(args, int); - is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len); - - if (!is_equal) { - if (wcs1 && wcs2) { - int pos; - for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos) - /* find differing byte offset */; - p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls' (at byte %d)", - len, wcs1, len, wcs2, pos); - } else { - p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls'", len, wcs1, len, wcs2); - } - } - } - else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) { - size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t); - is_equal = (sz1 == sz2); - if (!is_equal) { - int offset = p_snprintf(buf, sizeof(buf), fmt, sz1); - strncat(buf, " != ", sizeof(buf) - offset); - p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2); - } - } - else if (!strcmp("%p", fmt)) { - void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *); - is_equal = (p1 == p2); - if (!is_equal) - p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2); - } - else { - int i1 = va_arg(args, int), i2 = va_arg(args, int); - is_equal = (i1 == i2); - if (!is_equal) { - int offset = p_snprintf(buf, sizeof(buf), fmt, i1); - strncat(buf, " != ", sizeof(buf) - offset); - p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2); - } - } - - va_end(args); - - if (!is_equal) - clar__fail(file, function, line, err, buf, should_abort); -} - -void cl_set_cleanup(void (*cleanup)(void *), void *opaque) -{ - _clar.local_cleanup = cleanup; - _clar.local_cleanup_payload = opaque; -} - -#include "clar/sandbox.h" -#include "clar/fixtures.h" -#include "clar/fs.h" -#include "clar/print.h" -#include "clar/summary.h" diff --git a/tests/libgit2/clar.h b/tests/libgit2/clar.h deleted file mode 100644 index 3f659c2f6..000000000 --- a/tests/libgit2/clar.h +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (c) Vicent Marti. All rights reserved. - * - * This file is part of clar, distributed under the ISC license. - * For full terms see the included COPYING file. - */ -#ifndef __CLAR_TEST_H__ -#define __CLAR_TEST_H__ - -#include - -enum cl_test_status { - CL_TEST_OK, - CL_TEST_FAILURE, - CL_TEST_SKIP, - CL_TEST_NOTRUN -}; - -enum cl_output_format { - CL_OUTPUT_CLAP, - CL_OUTPUT_TAP -}; - -/** Setup clar environment */ -void clar_test_init(int argc, char *argv[]); -int clar_test_run(void); -void clar_test_shutdown(void); - -/** One shot setup & run */ -int clar_test(int argc, char *argv[]); - -const char *clar_sandbox_path(void); - -void cl_set_cleanup(void (*cleanup)(void *), void *opaque); -void cl_fs_cleanup(void); - -/** - * cl_trace_* is a hook to provide a simple global tracing - * mechanism. - * - * The goal here is to let main() provide clar-proper - * with a callback to optionally write log info for - * test operations into the same stream used by their - * actual tests. This would let them print test names - * and maybe performance data as they choose. - * - * The goal is NOT to alter the flow of control or to - * override test selection/skipping. (So the callback - * does not return a value.) - * - * The goal is NOT to duplicate the existing - * pass/fail/skip reporting. (So the callback - * does not accept a status/errorcode argument.) - * - */ -typedef enum cl_trace_event { - CL_TRACE__SUITE_BEGIN, - CL_TRACE__SUITE_END, - CL_TRACE__TEST__BEGIN, - CL_TRACE__TEST__END, - CL_TRACE__TEST__RUN_BEGIN, - CL_TRACE__TEST__RUN_END, - CL_TRACE__TEST__LONGJMP -} cl_trace_event; - -typedef void (cl_trace_cb)( - cl_trace_event ev, - const char *suite_name, - const char *test_name, - void *payload); - -/** - * Register a callback into CLAR to send global trace events. - * Pass NULL to disable. - */ -void cl_trace_register(cl_trace_cb *cb, void *payload); - - -#ifdef CLAR_FIXTURE_PATH -const char *cl_fixture(const char *fixture_name); -void cl_fixture_sandbox(const char *fixture_name); -void cl_fixture_cleanup(const char *fixture_name); -const char *cl_fixture_basename(const char *fixture_name); -#endif - -/** - * Assertion macros with explicit error message - */ -#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 1) -#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 1) -#define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 1) - -/** - * Check macros with explicit error message - */ -#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 0) -#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 0) -#define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 0) - -/** - * Assertion macros with no error message - */ -#define cl_must_pass(expr) cl_must_pass_(expr, NULL) -#define cl_must_fail(expr) cl_must_fail_(expr, NULL) -#define cl_assert(expr) cl_assert_(expr, NULL) - -/** - * Check macros with no error message - */ -#define cl_check_pass(expr) cl_check_pass_(expr, NULL) -#define cl_check_fail(expr) cl_check_fail_(expr, NULL) -#define cl_check(expr) cl_check_(expr, NULL) - -/** - * Forced failure/warning - */ -#define cl_fail(desc) clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1) -#define cl_warning(desc) clar__fail(__FILE__, __func__, __LINE__, "Warning during test execution:", desc, 0) - -#define cl_skip() clar__skip() - -/** - * Typed assertion macros - */ -#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2)) -#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2)) - -#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2)) -#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2)) - -#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len)) -#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len)) - -#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len)) -#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len)) - -#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) -#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2)) -#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2)) - -#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__func__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0)) - -#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__func__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2)) - -void clar__skip(void); - -void clar__fail( - const char *file, - const char *func, - size_t line, - const char *error, - const char *description, - int should_abort); - -void clar__assert( - int condition, - const char *file, - const char *func, - size_t line, - const char *error, - const char *description, - int should_abort); - -void clar__assert_equal( - const char *file, - const char *func, - size_t line, - const char *err, - int should_abort, - const char *fmt, - ...); - -#endif diff --git a/tests/libgit2/clar/fixtures.h b/tests/libgit2/clar/fixtures.h deleted file mode 100644 index 77033d365..000000000 --- a/tests/libgit2/clar/fixtures.h +++ /dev/null @@ -1,50 +0,0 @@ -static const char * -fixture_path(const char *base, const char *fixture_name) -{ - static char _path[4096]; - size_t root_len; - - root_len = strlen(base); - strncpy(_path, base, sizeof(_path)); - - if (_path[root_len - 1] != '/') - _path[root_len++] = '/'; - - if (fixture_name[0] == '/') - fixture_name++; - - strncpy(_path + root_len, - fixture_name, - sizeof(_path) - root_len); - - return _path; -} - -#ifdef CLAR_FIXTURE_PATH -const char *cl_fixture(const char *fixture_name) -{ - return fixture_path(CLAR_FIXTURE_PATH, fixture_name); -} - -void cl_fixture_sandbox(const char *fixture_name) -{ - fs_copy(cl_fixture(fixture_name), _clar_path); -} - -const char *cl_fixture_basename(const char *fixture_name) -{ - const char *p; - - for (p = fixture_name; *p; p++) { - if (p[0] == '/' && p[1] && p[1] != '/') - fixture_name = p+1; - } - - return fixture_name; -} - -void cl_fixture_cleanup(const char *fixture_name) -{ - fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name))); -} -#endif diff --git a/tests/libgit2/clar/fs.h b/tests/libgit2/clar/fs.h deleted file mode 100644 index 44ede4572..000000000 --- a/tests/libgit2/clar/fs.h +++ /dev/null @@ -1,520 +0,0 @@ -/* - * By default, use a read/write loop to copy files on POSIX systems. - * On Linux, use sendfile by default as it's slightly faster. On - * macOS, we avoid fcopyfile by default because it's slightly slower. - */ -#undef USE_FCOPYFILE -#define USE_SENDFILE 1 - -#ifdef _WIN32 - -#ifdef CLAR_WIN32_LONGPATHS -# define CLAR_MAX_PATH 4096 -#else -# define CLAR_MAX_PATH MAX_PATH -#endif - -#define RM_RETRY_COUNT 5 -#define RM_RETRY_DELAY 10 - -#ifdef __MINGW32__ - -/* These security-enhanced functions are not available - * in MinGW, so just use the vanilla ones */ -#define wcscpy_s(a, b, c) wcscpy((a), (c)) -#define wcscat_s(a, b, c) wcscat((a), (c)) - -#endif /* __MINGW32__ */ - -static int -fs__dotordotdot(WCHAR *_tocheck) -{ - return _tocheck[0] == '.' && - (_tocheck[1] == '\0' || - (_tocheck[1] == '.' && _tocheck[2] == '\0')); -} - -static int -fs_rmdir_rmdir(WCHAR *_wpath) -{ - unsigned retries = 1; - - while (!RemoveDirectoryW(_wpath)) { - /* Only retry when we have retries remaining, and the - * error was ERROR_DIR_NOT_EMPTY. */ - if (retries++ > RM_RETRY_COUNT || - ERROR_DIR_NOT_EMPTY != GetLastError()) - return -1; - - /* Give whatever has a handle to a child item some time - * to release it before trying again */ - Sleep(RM_RETRY_DELAY * retries * retries); - } - - return 0; -} - -static void translate_path(WCHAR *path, size_t path_size) -{ - size_t path_len, i; - - if (wcsncmp(path, L"\\\\?\\", 4) == 0) - return; - - path_len = wcslen(path); - cl_assert(path_size > path_len + 4); - - for (i = path_len; i > 0; i--) { - WCHAR c = path[i - 1]; - - if (c == L'/') - path[i + 3] = L'\\'; - else - path[i + 3] = path[i - 1]; - } - - path[0] = L'\\'; - path[1] = L'\\'; - path[2] = L'?'; - path[3] = L'\\'; - path[path_len + 4] = L'\0'; -} - -static void -fs_rmdir_helper(WCHAR *_wsource) -{ - WCHAR buffer[CLAR_MAX_PATH]; - HANDLE find_handle; - WIN32_FIND_DATAW find_data; - size_t buffer_prefix_len; - - /* Set up the buffer and capture the length */ - wcscpy_s(buffer, CLAR_MAX_PATH, _wsource); - translate_path(buffer, CLAR_MAX_PATH); - wcscat_s(buffer, CLAR_MAX_PATH, L"\\"); - buffer_prefix_len = wcslen(buffer); - - /* FindFirstFile needs a wildcard to match multiple items */ - wcscat_s(buffer, CLAR_MAX_PATH, L"*"); - find_handle = FindFirstFileW(buffer, &find_data); - cl_assert(INVALID_HANDLE_VALUE != find_handle); - - do { - /* FindFirstFile/FindNextFile gives back . and .. - * entries at the beginning */ - if (fs__dotordotdot(find_data.cFileName)) - continue; - - wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName); - - if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) - fs_rmdir_helper(buffer); - else { - /* If set, the +R bit must be cleared before deleting */ - if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes) - cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY)); - - cl_assert(DeleteFileW(buffer)); - } - } - while (FindNextFileW(find_handle, &find_data)); - - /* Ensure that we successfully completed the enumeration */ - cl_assert(ERROR_NO_MORE_FILES == GetLastError()); - - /* Close the find handle */ - FindClose(find_handle); - - /* Now that the directory is empty, remove it */ - cl_assert(0 == fs_rmdir_rmdir(_wsource)); -} - -static int -fs_rm_wait(WCHAR *_wpath) -{ - unsigned retries = 1; - DWORD last_error; - - do { - if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath)) - last_error = GetLastError(); - else - last_error = ERROR_SUCCESS; - - /* Is the item gone? */ - if (ERROR_FILE_NOT_FOUND == last_error || - ERROR_PATH_NOT_FOUND == last_error) - return 0; - - Sleep(RM_RETRY_DELAY * retries * retries); - } - while (retries++ <= RM_RETRY_COUNT); - - return -1; -} - -static void -fs_rm(const char *_source) -{ - WCHAR wsource[CLAR_MAX_PATH]; - DWORD attrs; - - /* The input path is UTF-8. Convert it to wide characters - * for use with the Windows API */ - cl_assert(MultiByteToWideChar(CP_UTF8, - MB_ERR_INVALID_CHARS, - _source, - -1, /* Indicates NULL termination */ - wsource, - CLAR_MAX_PATH)); - - translate_path(wsource, CLAR_MAX_PATH); - - /* Does the item exist? If not, we have no work to do */ - attrs = GetFileAttributesW(wsource); - - if (INVALID_FILE_ATTRIBUTES == attrs) - return; - - if (FILE_ATTRIBUTE_DIRECTORY & attrs) - fs_rmdir_helper(wsource); - else { - /* The item is a file. Strip the +R bit */ - if (FILE_ATTRIBUTE_READONLY & attrs) - cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY)); - - cl_assert(DeleteFileW(wsource)); - } - - /* Wait for the DeleteFile or RemoveDirectory call to complete */ - cl_assert(0 == fs_rm_wait(wsource)); -} - -static void -fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest) -{ - WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH]; - HANDLE find_handle; - WIN32_FIND_DATAW find_data; - size_t buf_source_prefix_len, buf_dest_prefix_len; - - wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource); - wcscat_s(buf_source, CLAR_MAX_PATH, L"\\"); - translate_path(buf_source, CLAR_MAX_PATH); - buf_source_prefix_len = wcslen(buf_source); - - wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest); - wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\"); - translate_path(buf_dest, CLAR_MAX_PATH); - buf_dest_prefix_len = wcslen(buf_dest); - - /* Get an enumerator for the items in the source. */ - wcscat_s(buf_source, CLAR_MAX_PATH, L"*"); - find_handle = FindFirstFileW(buf_source, &find_data); - cl_assert(INVALID_HANDLE_VALUE != find_handle); - - /* Create the target directory. */ - cl_assert(CreateDirectoryW(_wdest, NULL)); - - do { - /* FindFirstFile/FindNextFile gives back . and .. - * entries at the beginning */ - if (fs__dotordotdot(find_data.cFileName)) - continue; - - wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName); - wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName); - - if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) - fs_copydir_helper(buf_source, buf_dest); - else - cl_assert(CopyFileW(buf_source, buf_dest, TRUE)); - } - while (FindNextFileW(find_handle, &find_data)); - - /* Ensure that we successfully completed the enumeration */ - cl_assert(ERROR_NO_MORE_FILES == GetLastError()); - - /* Close the find handle */ - FindClose(find_handle); -} - -static void -fs_copy(const char *_source, const char *_dest) -{ - WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH]; - DWORD source_attrs, dest_attrs; - HANDLE find_handle; - WIN32_FIND_DATAW find_data; - - /* The input paths are UTF-8. Convert them to wide characters - * for use with the Windows API. */ - cl_assert(MultiByteToWideChar(CP_UTF8, - MB_ERR_INVALID_CHARS, - _source, - -1, - wsource, - CLAR_MAX_PATH)); - - cl_assert(MultiByteToWideChar(CP_UTF8, - MB_ERR_INVALID_CHARS, - _dest, - -1, - wdest, - CLAR_MAX_PATH)); - - translate_path(wsource, CLAR_MAX_PATH); - translate_path(wdest, CLAR_MAX_PATH); - - /* Check the source for existence */ - source_attrs = GetFileAttributesW(wsource); - cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs); - - /* Check the target for existence */ - dest_attrs = GetFileAttributesW(wdest); - - if (INVALID_FILE_ATTRIBUTES != dest_attrs) { - /* Target exists; append last path part of source to target. - * Use FindFirstFile to parse the path */ - find_handle = FindFirstFileW(wsource, &find_data); - cl_assert(INVALID_HANDLE_VALUE != find_handle); - wcscat_s(wdest, CLAR_MAX_PATH, L"\\"); - wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName); - FindClose(find_handle); - - /* Check the new target for existence */ - cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest)); - } - - if (FILE_ATTRIBUTE_DIRECTORY & source_attrs) - fs_copydir_helper(wsource, wdest); - else - cl_assert(CopyFileW(wsource, wdest, TRUE)); -} - -void -cl_fs_cleanup(void) -{ - fs_rm(fixture_path(_clar_path, "*")); -} - -#else - -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(__linux__) -# include -#endif - -#if defined(__APPLE__) -# include -#endif - -static void basename_r(const char **out, int *out_len, const char *in) -{ - size_t in_len = strlen(in), start_pos; - - for (in_len = strlen(in); in_len; in_len--) { - if (in[in_len - 1] != '/') - break; - } - - for (start_pos = in_len; start_pos; start_pos--) { - if (in[start_pos - 1] == '/') - break; - } - - cl_assert(in_len - start_pos < INT_MAX); - - if (in_len - start_pos > 0) { - *out = &in[start_pos]; - *out_len = (in_len - start_pos); - } else { - *out = "/"; - *out_len = 1; - } -} - -static char *joinpath(const char *dir, const char *base, int base_len) -{ - char *out; - int len; - - if (base_len == -1) { - size_t bl = strlen(base); - - cl_assert(bl < INT_MAX); - base_len = (int)bl; - } - - len = strlen(dir) + base_len + 2; - cl_assert(len > 0); - - cl_assert(out = malloc(len)); - cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len); - - return out; -} - -static void -fs_copydir_helper(const char *source, const char *dest, int dest_mode) -{ - DIR *source_dir; - struct dirent *d; - - mkdir(dest, dest_mode); - - cl_assert_(source_dir = opendir(source), "Could not open source dir"); - while ((d = (errno = 0, readdir(source_dir))) != NULL) { - char *child; - - if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) - continue; - - child = joinpath(source, d->d_name, -1); - fs_copy(child, dest); - free(child); - } - - cl_assert_(errno == 0, "Failed to iterate source dir"); - - closedir(source_dir); -} - -static void -fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode) -{ - int in, out; - - cl_must_pass((in = open(source, O_RDONLY))); - cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode))); - -#if USE_FCOPYFILE && defined(__APPLE__) - ((void)(source_len)); /* unused */ - cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA)); -#elif USE_SENDFILE && defined(__linux__) - { - ssize_t ret = 0; - - while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) { - source_len -= (size_t)ret; - } - cl_assert(ret >= 0); - } -#else - { - char buf[131072]; - ssize_t ret; - - ((void)(source_len)); /* unused */ - - while ((ret = read(in, buf, sizeof(buf))) > 0) { - size_t len = (size_t)ret; - - while (len && (ret = write(out, buf, len)) > 0) { - cl_assert(ret <= (ssize_t)len); - len -= ret; - } - cl_assert(ret >= 0); - } - cl_assert(ret == 0); - } -#endif - - close(in); - close(out); -} - -static void -fs_copy(const char *source, const char *_dest) -{ - char *dbuf = NULL; - const char *dest = NULL; - struct stat source_st, dest_st; - - cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source"); - - if (lstat(_dest, &dest_st) == 0) { - const char *base; - int base_len; - - /* Target exists and is directory; append basename */ - cl_assert(S_ISDIR(dest_st.st_mode)); - - basename_r(&base, &base_len, source); - cl_assert(base_len < INT_MAX); - - dbuf = joinpath(_dest, base, base_len); - dest = dbuf; - } else if (errno != ENOENT) { - cl_fail("Cannot copy; cannot stat destination"); - } else { - dest = _dest; - } - - if (S_ISDIR(source_st.st_mode)) { - fs_copydir_helper(source, dest, source_st.st_mode); - } else { - fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode); - } - - free(dbuf); -} - -static void -fs_rmdir_helper(const char *path) -{ - DIR *dir; - struct dirent *d; - - cl_assert_(dir = opendir(path), "Could not open dir"); - while ((d = (errno = 0, readdir(dir))) != NULL) { - char *child; - - if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) - continue; - - child = joinpath(path, d->d_name, -1); - fs_rm(child); - free(child); - } - - cl_assert_(errno == 0, "Failed to iterate source dir"); - closedir(dir); - - cl_must_pass_(rmdir(path), "Could not remove directory"); -} - -static void -fs_rm(const char *path) -{ - struct stat st; - - if (lstat(path, &st)) { - if (errno == ENOENT) - return; - - cl_fail("Cannot copy; cannot stat destination"); - } - - if (S_ISDIR(st.st_mode)) { - fs_rmdir_helper(path); - } else { - cl_must_pass(unlink(path)); - } -} - -void -cl_fs_cleanup(void) -{ - clar_unsandbox(); - clar_sandbox(); -} -#endif diff --git a/tests/libgit2/clar/print.h b/tests/libgit2/clar/print.h deleted file mode 100644 index dbfd27655..000000000 --- a/tests/libgit2/clar/print.h +++ /dev/null @@ -1,200 +0,0 @@ -/* clap: clar protocol, the traditional clar output format */ - -static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names) -{ - (void)test_count; - printf("Loaded %d suites: %s\n", (int)suite_count, suite_names); - printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n"); -} - -static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count) -{ - (void)test_count; - (void)suite_count; - (void)error_count; - - printf("\n\n"); - clar_report_all(); -} - -static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error) -{ - printf(" %d) Failure:\n", num); - - printf("%s::%s [%s:%"PRIuZ"]\n", - report->suite, - report->test, - error->file, - error->line_number); - - printf(" %s\n", error->error_msg); - - if (error->description != NULL) - printf(" %s\n", error->description); - - printf("\n"); - fflush(stdout); -} - -static void clar_print_clap_ontest(const char *test_name, int test_number, enum cl_test_status status) -{ - (void)test_name; - (void)test_number; - - switch(status) { - case CL_TEST_OK: printf("."); break; - case CL_TEST_FAILURE: printf("F"); break; - case CL_TEST_SKIP: printf("S"); break; - case CL_TEST_NOTRUN: printf("N"); break; - } - - fflush(stdout); -} - -static void clar_print_clap_onsuite(const char *suite_name, int suite_index) -{ - if (_clar.report_suite_names) - printf("\n%s", suite_name); - - (void)suite_index; -} - -static void clar_print_clap_onabort(const char *fmt, va_list arg) -{ - vfprintf(stderr, fmt, arg); -} - -/* tap: test anywhere protocol format */ - -static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names) -{ - (void)test_count; - (void)suite_count; - (void)suite_names; - printf("TAP version 13\n"); -} - -static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count) -{ - (void)suite_count; - (void)error_count; - - printf("1..%d\n", test_count); -} - -static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error) -{ - (void)num; - (void)report; - (void)error; -} - -static void print_escaped(const char *str) -{ - char *c; - - while ((c = strchr(str, '\'')) != NULL) { - printf("%.*s", (int)(c - str), str); - printf("''"); - str = c + 1; - } - - printf("%s", str); -} - -static void clar_print_tap_ontest(const char *test_name, int test_number, enum cl_test_status status) -{ - const struct clar_error *error = _clar.last_report->errors; - - (void)test_name; - (void)test_number; - - switch(status) { - case CL_TEST_OK: - printf("ok %d - %s::%s\n", test_number, _clar.active_suite, test_name); - break; - case CL_TEST_FAILURE: - printf("not ok %d - %s::%s\n", test_number, _clar.active_suite, test_name); - - printf(" ---\n"); - printf(" reason: |\n"); - printf(" %s\n", error->error_msg); - - if (error->description) - printf(" %s\n", error->description); - - printf(" at:\n"); - printf(" file: '"); print_escaped(error->file); printf("'\n"); - printf(" line: %" PRIuZ "\n", error->line_number); - printf(" function: '%s'\n", error->function); - printf(" ---\n"); - - break; - case CL_TEST_SKIP: - case CL_TEST_NOTRUN: - printf("ok %d - # SKIP %s::%s\n", test_number, _clar.active_suite, test_name); - break; - } - - fflush(stdout); -} - -static void clar_print_tap_onsuite(const char *suite_name, int suite_index) -{ - printf("# start of suite %d: %s\n", suite_index, suite_name); -} - -static void clar_print_tap_onabort(const char *fmt, va_list arg) -{ - printf("Bail out! "); - vprintf(fmt, arg); - fflush(stdout); -} - -/* indirection between protocol output selection */ - -#define PRINT(FN, ...) do { \ - switch (_clar.output_format) { \ - case CL_OUTPUT_CLAP: \ - clar_print_clap_##FN (__VA_ARGS__); \ - break; \ - case CL_OUTPUT_TAP: \ - clar_print_tap_##FN (__VA_ARGS__); \ - break; \ - default: \ - abort(); \ - } \ - } while (0) - -static void clar_print_init(int test_count, int suite_count, const char *suite_names) -{ - PRINT(init, test_count, suite_count, suite_names); -} - -static void clar_print_shutdown(int test_count, int suite_count, int error_count) -{ - PRINT(shutdown, test_count, suite_count, error_count); -} - -static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error) -{ - PRINT(error, num, report, error); -} - -static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status status) -{ - PRINT(ontest, test_name, test_number, status); -} - -static void clar_print_onsuite(const char *suite_name, int suite_index) -{ - PRINT(onsuite, suite_name, suite_index); -} - -static void clar_print_onabort(const char *msg, ...) -{ - va_list argp; - va_start(argp, msg); - PRINT(onabort, msg, argp); - va_end(argp); -} diff --git a/tests/libgit2/clar/sandbox.h b/tests/libgit2/clar/sandbox.h deleted file mode 100644 index 0ba147962..000000000 --- a/tests/libgit2/clar/sandbox.h +++ /dev/null @@ -1,154 +0,0 @@ -#ifdef __APPLE__ -#include -#endif - -static char _clar_path[4096 + 1]; - -static int -is_valid_tmp_path(const char *path) -{ - STAT_T st; - - if (stat(path, &st) != 0) - return 0; - - if (!S_ISDIR(st.st_mode)) - return 0; - - return (access(path, W_OK) == 0); -} - -static int -find_tmp_path(char *buffer, size_t length) -{ -#ifndef _WIN32 - static const size_t var_count = 5; - static const char *env_vars[] = { - "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE" - }; - - size_t i; - - for (i = 0; i < var_count; ++i) { - const char *env = getenv(env_vars[i]); - if (!env) - continue; - - if (is_valid_tmp_path(env)) { -#ifdef __APPLE__ - if (length >= PATH_MAX && realpath(env, buffer) != NULL) - return 0; -#endif - strncpy(buffer, env, length - 1); - buffer[length - 1] = '\0'; - return 0; - } - } - - /* If the environment doesn't say anything, try to use /tmp */ - if (is_valid_tmp_path("/tmp")) { -#ifdef __APPLE__ - if (length >= PATH_MAX && realpath("/tmp", buffer) != NULL) - return 0; -#endif - strncpy(buffer, "/tmp", length - 1); - buffer[length - 1] = '\0'; - return 0; - } - -#else - DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length); - if (env_len > 0 && env_len < (DWORD)length) - return 0; - - if (GetTempPath((DWORD)length, buffer)) - return 0; -#endif - - /* This system doesn't like us, try to use the current directory */ - if (is_valid_tmp_path(".")) { - strncpy(buffer, ".", length - 1); - buffer[length - 1] = '\0'; - return 0; - } - - return -1; -} - -static void clar_unsandbox(void) -{ - if (_clar_path[0] == '\0') - return; - - cl_must_pass(chdir("..")); - - fs_rm(_clar_path); -} - -static int build_sandbox_path(void) -{ -#ifdef CLAR_TMPDIR - const char path_tail[] = CLAR_TMPDIR "_XXXXXX"; -#else - const char path_tail[] = "clar_tmp_XXXXXX"; -#endif - - size_t len; - - if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0) - return -1; - - len = strlen(_clar_path); - -#ifdef _WIN32 - { /* normalize path to POSIX forward slashes */ - size_t i; - for (i = 0; i < len; ++i) { - if (_clar_path[i] == '\\') - _clar_path[i] = '/'; - } - } -#endif - - if (_clar_path[len - 1] != '/') { - _clar_path[len++] = '/'; - } - - strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len); - -#if defined(__MINGW32__) - if (_mktemp(_clar_path) == NULL) - return -1; - - if (mkdir(_clar_path, 0700) != 0) - return -1; -#elif defined(_WIN32) - if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0) - return -1; - - if (mkdir(_clar_path, 0700) != 0) - return -1; -#else - if (mkdtemp(_clar_path) == NULL) - return -1; -#endif - - return 0; -} - -static int clar_sandbox(void) -{ - if (_clar_path[0] == '\0' && build_sandbox_path() < 0) - return -1; - - if (chdir(_clar_path) != 0) - return -1; - - return 0; -} - -const char *clar_sandbox_path(void) -{ - return _clar_path; -} - diff --git a/tests/libgit2/clar/summary.h b/tests/libgit2/clar/summary.h deleted file mode 100644 index 6279f5057..000000000 --- a/tests/libgit2/clar/summary.h +++ /dev/null @@ -1,134 +0,0 @@ - -#include -#include - -static int clar_summary_close_tag( - struct clar_summary *summary, const char *tag, int indent) -{ - const char *indt; - - if (indent == 0) indt = ""; - else if (indent == 1) indt = "\t"; - else indt = "\t\t"; - - return fprintf(summary->fp, "%s\n", indt, tag); -} - -static int clar_summary_testsuites(struct clar_summary *summary) -{ - return fprintf(summary->fp, "\n"); -} - -static int clar_summary_testsuite(struct clar_summary *summary, - int idn, const char *name, const char *pkg, time_t timestamp, - double elapsed, int test_count, int fail_count, int error_count) -{ - struct tm *tm = localtime(×tamp); - char iso_dt[20]; - - if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0) - return -1; - - return fprintf(summary->fp, "\t\n", - idn, name, pkg, iso_dt, elapsed, test_count, fail_count, error_count); -} - -static int clar_summary_testcase(struct clar_summary *summary, - const char *name, const char *classname, double elapsed) -{ - return fprintf(summary->fp, - "\t\t\n", - name, classname, elapsed); -} - -static int clar_summary_failure(struct clar_summary *summary, - const char *type, const char *message, const char *desc) -{ - return fprintf(summary->fp, - "\t\t\t\n", - type, message, desc); -} - -struct clar_summary *clar_summary_init(const char *filename) -{ - struct clar_summary *summary; - FILE *fp; - - if ((fp = fopen(filename, "w")) == NULL) - return NULL; - - if ((summary = malloc(sizeof(struct clar_summary))) == NULL) { - fclose(fp); - return NULL; - } - - summary->filename = filename; - summary->fp = fp; - - return summary; -} - -int clar_summary_shutdown(struct clar_summary *summary) -{ - struct clar_report *report; - const char *last_suite = NULL; - - if (clar_summary_testsuites(summary) < 0) - goto on_error; - - report = _clar.reports; - while (report != NULL) { - struct clar_error *error = report->errors; - - if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) { - if (clar_summary_testsuite(summary, 0, report->suite, "", - time(NULL), 0, _clar.tests_ran, _clar.total_errors, 0) < 0) - goto on_error; - } - - last_suite = report->suite; - - clar_summary_testcase(summary, report->test, "what", 0); - - while (error != NULL) { - if (clar_summary_failure(summary, "assert", - error->error_msg, error->description) < 0) - goto on_error; - - error = error->next; - } - - if (clar_summary_close_tag(summary, "testcase", 2) < 0) - goto on_error; - - report = report->next; - - if (!report || strcmp(last_suite, report->suite) != 0) { - if (clar_summary_close_tag(summary, "testsuite", 1) < 0) - goto on_error; - } - } - - if (clar_summary_close_tag(summary, "testsuites", 0) < 0 || - fclose(summary->fp) != 0) - goto on_error; - - printf("written summary file to %s\n", summary->filename); - - free(summary); - return 0; - -on_error: - fclose(summary->fp); - free(summary); - return -1; -} diff --git a/tests/libgit2/clar_libgit2.c b/tests/libgit2/clar_libgit2.c deleted file mode 100644 index 55a09d111..000000000 --- a/tests/libgit2/clar_libgit2.c +++ /dev/null @@ -1,623 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" -#include "fs_path.h" -#include "git2/sys/repository.h" - -void cl_git_report_failure( - int error, int expected, const char *file, const char *func, int line, const char *fncall) -{ - char msg[4096]; - const git_error *last = git_error_last(); - - if (expected) - p_snprintf(msg, 4096, "error %d (expected %d) - %s", - error, expected, last ? last->message : ""); - else if (error || last) - p_snprintf(msg, 4096, "error %d - %s", - error, last ? last->message : ""); - else - p_snprintf(msg, 4096, "no error, expected non-zero return"); - - clar__assert(0, file, func, line, fncall, msg, 1); -} - -void cl_git_mkfile(const char *filename, const char *content) -{ - int fd; - - fd = p_creat(filename, 0666); - cl_assert(fd != -1); - - if (content) { - cl_must_pass(p_write(fd, content, strlen(content))); - } else { - cl_must_pass(p_write(fd, filename, strlen(filename))); - cl_must_pass(p_write(fd, "\n", 1)); - } - - cl_must_pass(p_close(fd)); -} - -void cl_git_write2file( - const char *path, const char *content, size_t content_len, - int flags, unsigned int mode) -{ - int fd; - cl_assert(path && content); - cl_assert((fd = p_open(path, flags, mode)) >= 0); - if (!content_len) - content_len = strlen(content); - cl_must_pass(p_write(fd, content, content_len)); - cl_must_pass(p_close(fd)); -} - -void cl_git_append2file(const char *path, const char *content) -{ - cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_APPEND, 0644); -} - -void cl_git_rewritefile(const char *path, const char *content) -{ - cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_TRUNC, 0644); -} - -void cl_git_rmfile(const char *filename) -{ - cl_must_pass(p_unlink(filename)); -} - -char *cl_getenv(const char *name) -{ - git_str out = GIT_STR_INIT; - int error = git__getenv(&out, name); - - cl_assert(error >= 0 || error == GIT_ENOTFOUND); - - if (error == GIT_ENOTFOUND) - return NULL; - - if (out.size == 0) { - char *dup = git__strdup(""); - cl_assert(dup); - - return dup; - } - - return git_str_detach(&out); -} - -bool cl_is_env_set(const char *name) -{ - char *env = cl_getenv(name); - bool result = (env != NULL); - git__free(env); - return result; -} - -#ifdef GIT_WIN32 - -#include "win32/utf-conv.h" - -int cl_setenv(const char *name, const char *value) -{ - wchar_t *wide_name, *wide_value = NULL; - - cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0); - - if (value) { - cl_assert(git__utf8_to_16_alloc(&wide_value, value) >= 0); - cl_assert(SetEnvironmentVariableW(wide_name, wide_value)); - } else { - /* Windows XP returns 0 (failed) when passing NULL for lpValue when - * lpName does not exist in the environment block. This behavior - * seems to have changed in later versions. Don't check the return value - * of SetEnvironmentVariable when passing NULL for lpValue. */ - SetEnvironmentVariableW(wide_name, NULL); - } - - git__free(wide_name); - git__free(wide_value); - return 0; -} - -/* This function performs retries on calls to MoveFile in order - * to provide enhanced reliability in the face of antivirus - * agents that may be scanning the source (or in the case that - * the source is a directory, a child of the source). */ -int cl_rename(const char *source, const char *dest) -{ - git_win32_path source_utf16; - git_win32_path dest_utf16; - unsigned retries = 1; - - cl_assert(git_win32_path_from_utf8(source_utf16, source) >= 0); - cl_assert(git_win32_path_from_utf8(dest_utf16, dest) >= 0); - - while (!MoveFileW(source_utf16, dest_utf16)) { - /* Only retry if the error is ERROR_ACCESS_DENIED; - * this may indicate that an antivirus agent is - * preventing the rename from source to target */ - if (retries > 5 || - ERROR_ACCESS_DENIED != GetLastError()) - return -1; - - /* With 5 retries and a coefficient of 10ms, the maximum - * delay here is 550 ms */ - Sleep(10 * retries * retries); - retries++; - } - - return 0; -} - -#else - -#include - -int cl_setenv(const char *name, const char *value) -{ - return (value == NULL) ? unsetenv(name) : setenv(name, value, 1); -} - -int cl_rename(const char *source, const char *dest) -{ - return p_rename(source, dest); -} - -#endif - -static const char *_cl_sandbox = NULL; -static git_repository *_cl_repo = NULL; - -git_repository *cl_git_sandbox_init(const char *sandbox) -{ - /* Get the name of the sandbox folder which will be created */ - const char *basename = cl_fixture_basename(sandbox); - - /* Copy the whole sandbox folder from our fixtures to our test sandbox - * area. After this it can be accessed with `./sandbox` - */ - cl_fixture_sandbox(sandbox); - _cl_sandbox = sandbox; - - cl_git_pass(p_chdir(basename)); - - /* If this is not a bare repo, then rename `sandbox/.gitted` to - * `sandbox/.git` which must be done since we cannot store a folder - * named `.git` inside the fixtures folder of our libgit2 repo. - */ - if (p_access(".gitted", F_OK) == 0) - cl_git_pass(cl_rename(".gitted", ".git")); - - /* If we have `gitattributes`, rename to `.gitattributes`. This may - * be necessary if we don't want the attributes to be applied in the - * libgit2 repo, but just during testing. - */ - if (p_access("gitattributes", F_OK) == 0) - cl_git_pass(cl_rename("gitattributes", ".gitattributes")); - - /* As with `gitattributes`, we may need `gitignore` just for testing. */ - if (p_access("gitignore", F_OK) == 0) - cl_git_pass(cl_rename("gitignore", ".gitignore")); - - cl_git_pass(p_chdir("..")); - - /* Now open the sandbox repository and make it available for tests */ - cl_git_pass(git_repository_open(&_cl_repo, basename)); - - /* Adjust configs after copying to new filesystem */ - cl_git_pass(git_repository_reinit_filesystem(_cl_repo, 0)); - - return _cl_repo; -} - -git_repository *cl_git_sandbox_init_new(const char *sandbox) -{ - cl_git_pass(git_repository_init(&_cl_repo, sandbox, false)); - _cl_sandbox = sandbox; - - return _cl_repo; -} - -git_repository *cl_git_sandbox_reopen(void) -{ - if (_cl_repo) { - git_repository_free(_cl_repo); - _cl_repo = NULL; - - cl_git_pass(git_repository_open( - &_cl_repo, cl_fixture_basename(_cl_sandbox))); - } - - return _cl_repo; -} - -void cl_git_sandbox_cleanup(void) -{ - if (_cl_repo) { - git_repository_free(_cl_repo); - _cl_repo = NULL; - } - if (_cl_sandbox) { - cl_fixture_cleanup(_cl_sandbox); - _cl_sandbox = NULL; - } -} - -bool cl_toggle_filemode(const char *filename) -{ - struct stat st1, st2; - - cl_must_pass(p_stat(filename, &st1)); - cl_must_pass(p_chmod(filename, st1.st_mode ^ 0100)); - cl_must_pass(p_stat(filename, &st2)); - - return (st1.st_mode != st2.st_mode); -} - -bool cl_is_chmod_supported(void) -{ - static int _is_supported = -1; - - if (_is_supported < 0) { - cl_git_mkfile("filemode.t", "Test if filemode can be modified"); - _is_supported = cl_toggle_filemode("filemode.t"); - cl_must_pass(p_unlink("filemode.t")); - } - - return _is_supported; -} - -const char* cl_git_fixture_url(const char *fixturename) -{ - return cl_git_path_url(cl_fixture(fixturename)); -} - -const char* cl_git_path_url(const char *path) -{ - static char url[4096 + 1]; - - const char *in_buf; - git_str path_buf = GIT_STR_INIT; - git_str url_buf = GIT_STR_INIT; - - cl_git_pass(git_fs_path_prettify_dir(&path_buf, path, NULL)); - cl_git_pass(git_str_puts(&url_buf, "file://")); - -#ifdef GIT_WIN32 - /* - * A FILE uri matches the following format: file://[host]/path - * where "host" can be empty and "path" is an absolute path to the resource. - * - * In this test, no hostname is used, but we have to ensure the leading triple slashes: - * - * *nix: file:///usr/home/... - * Windows: file:///C:/Users/... - */ - cl_git_pass(git_str_putc(&url_buf, '/')); -#endif - - in_buf = git_str_cstr(&path_buf); - - /* - * A very hacky Url encoding that only takes care of escaping the spaces - */ - while (*in_buf) { - if (*in_buf == ' ') - cl_git_pass(git_str_puts(&url_buf, "%20")); - else - cl_git_pass(git_str_putc(&url_buf, *in_buf)); - - in_buf++; - } - - cl_assert(url_buf.size < sizeof(url) - 1); - - strncpy(url, git_str_cstr(&url_buf), sizeof(url) - 1); - url[sizeof(url) - 1] = '\0'; - git_str_dispose(&url_buf); - git_str_dispose(&path_buf); - return url; -} - -const char *cl_git_sandbox_path(int is_dir, ...) -{ - const char *path = NULL; - static char _temp[GIT_PATH_MAX]; - git_str buf = GIT_STR_INIT; - va_list arg; - - cl_git_pass(git_str_sets(&buf, clar_sandbox_path())); - - va_start(arg, is_dir); - - while ((path = va_arg(arg, const char *)) != NULL) { - cl_git_pass(git_str_joinpath(&buf, buf.ptr, path)); - } - va_end(arg); - - cl_git_pass(git_fs_path_prettify(&buf, buf.ptr, NULL)); - if (is_dir) - git_fs_path_to_dir(&buf); - - /* make sure we won't truncate */ - cl_assert(git_str_len(&buf) < sizeof(_temp)); - git_str_copy_cstr(_temp, sizeof(_temp), &buf); - - git_str_dispose(&buf); - - return _temp; -} - -typedef struct { - const char *filename; - size_t filename_len; -} remove_data; - -static int remove_placeholders_recurs(void *_data, git_str *path) -{ - remove_data *data = (remove_data *)_data; - size_t pathlen; - - if (git_fs_path_isdir(path->ptr) == true) - return git_fs_path_direach(path, 0, remove_placeholders_recurs, data); - - pathlen = path->size; - - if (pathlen < data->filename_len) - return 0; - - /* if path ends in '/'+filename (or equals filename) */ - if (!strcmp(data->filename, path->ptr + pathlen - data->filename_len) && - (pathlen == data->filename_len || - path->ptr[pathlen - data->filename_len - 1] == '/')) - return p_unlink(path->ptr); - - return 0; -} - -int cl_git_remove_placeholders(const char *directory_path, const char *filename) -{ - int error; - remove_data data; - git_str buffer = GIT_STR_INIT; - - if (git_fs_path_isdir(directory_path) == false) - return -1; - - if (git_str_sets(&buffer, directory_path) < 0) - return -1; - - data.filename = filename; - data.filename_len = strlen(filename); - - error = remove_placeholders_recurs(&data, &buffer); - - git_str_dispose(&buffer); - - return error; -} - -#define CL_COMMIT_NAME "Libgit2 Tester" -#define CL_COMMIT_EMAIL "libgit2-test@github.com" -#define CL_COMMIT_MSG "Test commit of tree " - -void cl_repo_commit_from_index( - git_oid *out, - git_repository *repo, - git_signature *sig, - git_time_t time, - const char *msg) -{ - git_index *index; - git_oid commit_id, tree_id; - git_object *parent = NULL; - git_reference *ref = NULL; - git_tree *tree = NULL; - char buf[128]; - int free_sig = (sig == NULL); - - /* it is fine if looking up HEAD fails - we make this the first commit */ - git_revparse_ext(&parent, &ref, repo, "HEAD"); - - /* write the index content as a tree */ - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_write_tree(&tree_id, index)); - cl_git_pass(git_index_write(index)); - git_index_free(index); - - cl_git_pass(git_tree_lookup(&tree, repo, &tree_id)); - - if (sig) - cl_assert(sig->name && sig->email); - else if (!time) - cl_git_pass(git_signature_now(&sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL)); - else - cl_git_pass(git_signature_new( - &sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL, time, 0)); - - if (!msg) { - strcpy(buf, CL_COMMIT_MSG); - git_oid_tostr(buf + strlen(CL_COMMIT_MSG), - sizeof(buf) - strlen(CL_COMMIT_MSG), &tree_id); - msg = buf; - } - - cl_git_pass(git_commit_create_v( - &commit_id, repo, ref ? git_reference_name(ref) : "HEAD", - sig, sig, NULL, msg, tree, parent ? 1 : 0, parent)); - - if (out) - git_oid_cpy(out, &commit_id); - - git_object_free(parent); - git_reference_free(ref); - if (free_sig) - git_signature_free(sig); - git_tree_free(tree); -} - -void cl_repo_set_bool(git_repository *repo, const char *cfg, int value) -{ - git_config *config; - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_bool(config, cfg, value != 0)); - git_config_free(config); -} - -int cl_repo_get_bool(git_repository *repo, const char *cfg) -{ - int val = 0; - git_config *config; - cl_git_pass(git_repository_config(&config, repo)); - if (git_config_get_bool(&val, config, cfg) < 0) - git_error_clear(); - git_config_free(config); - return val; -} - -void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value) -{ - git_config *config; - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_string(config, cfg, value)); - git_config_free(config); -} - -/* this is essentially the code from git__unescape modified slightly */ -static size_t strip_cr_from_buf(char *start, size_t len) -{ - char *scan, *trail, *end = start + len; - - for (scan = trail = start; scan < end; trail++, scan++) { - while (*scan == '\r') - scan++; /* skip '\r' */ - - if (trail != scan) - *trail = *scan; - } - - *trail = '\0'; - - return (trail - start); -} - -void 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) -{ - char buf[4000]; - ssize_t bytes, total_bytes = 0; - int fd = p_open(path, O_RDONLY | O_BINARY); - cl_assert(fd >= 0); - - if (expected_data && !expected_bytes) - expected_bytes = strlen(expected_data); - - while ((bytes = p_read(fd, buf, sizeof(buf))) != 0) { - clar__assert( - bytes > 0, file, func, line, "error reading from file", path, 1); - - if (ignore_cr) - bytes = strip_cr_from_buf(buf, bytes); - - if (memcmp(expected_data, buf, bytes) != 0) { - int pos; - for (pos = 0; pos < bytes && expected_data[pos] == buf[pos]; ++pos) - /* find differing byte offset */; - p_snprintf( - buf, sizeof(buf), "file content mismatch at byte %"PRIdZ, - (ssize_t)(total_bytes + pos)); - p_close(fd); - clar__fail(file, func, line, path, buf, 1); - } - - expected_data += bytes; - total_bytes += bytes; - } - - p_close(fd); - - clar__assert(!bytes, file, func, line, "error reading from file", path, 1); - clar__assert_equal(file, func, line, "mismatched file length", 1, "%"PRIuZ, - (size_t)expected_bytes, (size_t)total_bytes); -} - -static git_buf _cl_restore_home = GIT_BUF_INIT; - -void cl_fake_home_cleanup(void *payload) -{ - GIT_UNUSED(payload); - - if (_cl_restore_home.ptr) { - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, _cl_restore_home.ptr)); - git_buf_dispose(&_cl_restore_home); - } -} - -void cl_fake_home(void) -{ - git_str path = GIT_STR_INIT; - - cl_git_pass(git_libgit2_opts( - GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &_cl_restore_home)); - - cl_set_cleanup(cl_fake_home_cleanup, NULL); - - if (!git_fs_path_exists("home")) - cl_must_pass(p_mkdir("home", 0777)); - cl_git_pass(git_fs_path_prettify(&path, "home", NULL)); - cl_git_pass(git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); - git_str_dispose(&path); -} - -void cl_sandbox_set_search_path_defaults(void) -{ - git_str path = GIT_STR_INIT; - - git_str_joinpath(&path, clar_sandbox_path(), "__config"); - - if (!git_fs_path_exists(path.ptr)) - cl_must_pass(p_mkdir(path.ptr, 0777)); - - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr); - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr); - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr); - git_libgit2_opts( - GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_PROGRAMDATA, path.ptr); - - git_str_dispose(&path); -} - -#ifdef GIT_WIN32 -bool cl_sandbox_supports_8dot3(void) -{ - git_str longpath = GIT_STR_INIT; - char *shortname; - bool supported; - - cl_git_pass( - git_str_joinpath(&longpath, clar_sandbox_path(), "longer_than_8dot3")); - - cl_git_write2file(longpath.ptr, "", 0, O_RDWR|O_CREAT, 0666); - shortname = git_win32_path_8dot3_name(longpath.ptr); - - supported = (shortname != NULL); - - git__free(shortname); - git_str_dispose(&longpath); - - return supported; -} -#endif - diff --git a/tests/libgit2/clar_libgit2.h b/tests/libgit2/clar_libgit2.h deleted file mode 100644 index e3b7bd9f8..000000000 --- a/tests/libgit2/clar_libgit2.h +++ /dev/null @@ -1,236 +0,0 @@ -#ifndef __CLAR_LIBGIT2__ -#define __CLAR_LIBGIT2__ - -#include "clar.h" -#include -#include "common.h" -#include "posix.h" - -/** - * Replace for `clar_must_pass` that passes the last library error as the - * test failure message. - * - * Use this wrapper around all `git_` library calls that return error codes! - */ -#define cl_git_pass(expr) cl_git_expect((expr), 0, __FILE__, __func__, __LINE__) - -#define cl_git_fail_with(error, expr) cl_git_expect((expr), error, __FILE__, __func__, __LINE__) - -#define cl_git_expect(expr, expected, file, func, line) do { \ - int _lg2_error; \ - git_error_clear(); \ - if ((_lg2_error = (expr)) != expected) \ - cl_git_report_failure(_lg2_error, expected, file, func, line, "Function call failed: " #expr); \ - } while (0) - -/** - * Wrapper for `clar_must_fail` -- this one is - * just for consistency. Use with `git_` library - * calls that are supposed to fail! - */ -#define cl_git_fail(expr) do { \ - if ((expr) == 0) \ - git_error_clear(), \ - cl_git_report_failure(0, 0, __FILE__, __func__, __LINE__, "Function call succeeded: " #expr); \ - } while (0) - -/** - * Like cl_git_pass, only for Win32 error code conventions - */ -#define cl_win32_pass(expr) do { \ - int _win32_res; \ - if ((_win32_res = (expr)) == 0) { \ - git_error_set(GIT_ERROR_OS, "Returned: %d, system error code: %lu", _win32_res, GetLastError()); \ - cl_git_report_failure(_win32_res, 0, __FILE__, __func__, __LINE__, "System call failed: " #expr); \ - } \ - } while(0) - -/** - * Thread safe assertions; you cannot use `cl_git_report_failure` from a - * child thread since it will try to `longjmp` to abort and "the effect of - * a call to longjmp() where initialization of the jmp_buf structure was - * not performed in the calling thread is undefined." - * - * Instead, callers can provide a clar thread error context to a thread, - * which will populate and return it on failure. Callers can check the - * status with `cl_git_thread_check`. - */ -typedef struct { - int error; - const char *file; - const char *func; - int line; - const char *expr; - char error_msg[4096]; -} cl_git_thread_err; - -#ifdef GIT_THREADS -# define cl_git_thread_pass(threaderr, expr) cl_git_thread_pass_(threaderr, (expr), __FILE__, __func__, __LINE__) -#else -# define cl_git_thread_pass(threaderr, expr) cl_git_pass(expr) -#endif - -#define cl_git_thread_pass_(__threaderr, __expr, __file, __func, __line) do { \ - git_error_clear(); \ - if ((((cl_git_thread_err *)__threaderr)->error = (__expr)) != 0) { \ - const git_error *_last = git_error_last(); \ - ((cl_git_thread_err *)__threaderr)->file = __file; \ - ((cl_git_thread_err *)__threaderr)->func = __func; \ - ((cl_git_thread_err *)__threaderr)->line = __line; \ - ((cl_git_thread_err *)__threaderr)->expr = "Function call failed: " #__expr; \ - p_snprintf(((cl_git_thread_err *)__threaderr)->error_msg, 4096, "thread 0x%" PRIxZ " - error %d - %s", \ - git_thread_currentid(), ((cl_git_thread_err *)__threaderr)->error, \ - _last ? _last->message : ""); \ - git_thread_exit(__threaderr); \ - } \ - } while (0) - -GIT_INLINE(void) cl_git_thread_check(void *data) -{ - cl_git_thread_err *threaderr = (cl_git_thread_err *)data; - if (threaderr->error != 0) - clar__assert(0, threaderr->file, threaderr->func, threaderr->line, threaderr->expr, threaderr->error_msg, 1); -} - -void cl_git_report_failure(int, int, const char *, const char *, int, const char *); - -#define cl_assert_at_line(expr,file,func,line) \ - clar__assert((expr) != 0, file, func, line, "Expression is not true: " #expr, NULL, 1) - -GIT_INLINE(void) clar__assert_in_range( - int lo, int val, int hi, - const char *file, const char *func, int line, - const char *err, int should_abort) -{ - if (lo > val || hi < val) { - char buf[128]; - p_snprintf(buf, sizeof(buf), "%d not in [%d,%d]", val, lo, hi); - clar__fail(file, func, line, err, buf, should_abort); - } -} - -#define cl_assert_equal_sz(sz1,sz2) do { \ - size_t __sz1 = (size_t)(sz1), __sz2 = (size_t)(sz2); \ - clar__assert_equal(__FILE__,__func__,__LINE__,#sz1 " != " #sz2, 1, "%"PRIuZ, __sz1, __sz2); \ -} while (0) - -#define cl_assert_in_range(L,V,H) \ - clar__assert_in_range((L),(V),(H),__FILE__,__func__,__LINE__,"Range check: " #V " in [" #L "," #H "]", 1) - -#define cl_assert_equal_file(DATA,SIZE,PATH) \ - clar__assert_equal_file(DATA,SIZE,0,PATH,__FILE__,__func__,(int)__LINE__) - -#define cl_assert_equal_file_ignore_cr(DATA,SIZE,PATH) \ - clar__assert_equal_file(DATA,SIZE,1,PATH,__FILE__,__func__,(int)__LINE__) - -void clar__assert_equal_file( - const char *expected_data, - size_t expected_size, - int ignore_cr, - const char *path, - const char *file, - const char *func, - int line); - -GIT_INLINE(void) clar__assert_equal_oid( - const char *file, const char *func, int line, const char *desc, - const git_oid *one, const git_oid *two) -{ - if (git_oid_cmp(one, two)) { - char err[] = "\"........................................\" != \"........................................\""; - - git_oid_fmt(&err[1], one); - git_oid_fmt(&err[47], two); - - clar__fail(file, func, line, desc, err, 1); - } -} - -#define cl_assert_equal_oid(one, two) \ - clar__assert_equal_oid(__FILE__, __func__, __LINE__, \ - "OID mismatch: " #one " != " #two, (one), (two)) - -/* - * Some utility macros for building long strings - */ -#define REP4(STR) STR STR STR STR -#define REP15(STR) REP4(STR) REP4(STR) REP4(STR) STR STR STR -#define REP16(STR) REP4(REP4(STR)) -#define REP256(STR) REP16(REP16(STR)) -#define REP1024(STR) REP4(REP256(STR)) - -/* Write the contents of a buffer to disk */ -void cl_git_mkfile(const char *filename, const char *content); -void cl_git_append2file(const char *filename, const char *new_content); -void cl_git_rewritefile(const char *filename, const char *new_content); -void cl_git_write2file(const char *path, const char *data, - size_t datalen, int flags, unsigned int mode); -void cl_git_rmfile(const char *filename); - -bool cl_toggle_filemode(const char *filename); -bool cl_is_chmod_supported(void); - -/* Environment wrappers */ -char *cl_getenv(const char *name); -bool cl_is_env_set(const char *name); -int cl_setenv(const char *name, const char *value); - -/* Reliable rename */ -int cl_rename(const char *source, const char *dest); - -/* Git sandbox setup helpers */ - -git_repository *cl_git_sandbox_init(const char *sandbox); -git_repository *cl_git_sandbox_init_new(const char *name); -void cl_git_sandbox_cleanup(void); -git_repository *cl_git_sandbox_reopen(void); - -/* - * build a sandbox-relative from path segments - * is_dir will add a trailing slash - * vararg must be a NULL-terminated char * list - */ -const char *cl_git_sandbox_path(int is_dir, ...); - -/* Local-repo url helpers */ -const char* cl_git_fixture_url(const char *fixturename); -const char* cl_git_path_url(const char *path); - -/* Test repository cleaner */ -int cl_git_remove_placeholders(const char *directory_path, const char *filename); - -/* commit creation helpers */ -void cl_repo_commit_from_index( - git_oid *out, - git_repository *repo, - git_signature *sig, - git_time_t time, - const char *msg); - -/* config setting helpers */ -void cl_repo_set_bool(git_repository *repo, const char *cfg, int value); -int cl_repo_get_bool(git_repository *repo, const char *cfg); - -void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value); - -/* set up a fake "home" directory and set libgit2 GLOBAL search path. - * - * automatically configures cleanup function to restore the regular search - * path, although you can call it explicitly if you wish (with NULL). - */ -void cl_fake_home(void); -void cl_fake_home_cleanup(void *); - -void cl_sandbox_set_search_path_defaults(void); - -#ifdef GIT_WIN32 -# define cl_msleep(x) Sleep(x) -#else -# define cl_msleep(x) usleep(1000 * (x)) -#endif - -#ifdef GIT_WIN32 -bool cl_sandbox_supports_8dot3(void); -#endif - -#endif diff --git a/tests/libgit2/clar_libgit2_timer.c b/tests/libgit2/clar_libgit2_timer.c deleted file mode 100644 index 2330f9351..000000000 --- a/tests/libgit2/clar_libgit2_timer.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "clar_libgit2.h" -#include "clar_libgit2_timer.h" - -void cl_perf_timer__init(cl_perf_timer *t) -{ - memset(t, 0, sizeof(cl_perf_timer)); -} - -void cl_perf_timer__start(cl_perf_timer *t) -{ - t->time_started = git__timer(); -} - -void cl_perf_timer__stop(cl_perf_timer *t) -{ - double time_now = git__timer(); - - t->last = time_now - t->time_started; - t->sum += t->last; -} - -double cl_perf_timer__last(const cl_perf_timer *t) -{ - return t->last; -} - -double cl_perf_timer__sum(const cl_perf_timer *t) -{ - return t->sum; -} diff --git a/tests/libgit2/clar_libgit2_timer.h b/tests/libgit2/clar_libgit2_timer.h deleted file mode 100644 index 7571a52e9..000000000 --- a/tests/libgit2/clar_libgit2_timer.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef __CLAR_LIBGIT2_TIMER__ -#define __CLAR_LIBGIT2_TIMER__ - -struct cl_perf_timer -{ - /* cumulative running time across all start..stop intervals */ - double sum; - - /* value of last start..stop interval */ - double last; - - /* clock value at start */ - double time_started; -}; - -#define CL_PERF_TIMER_INIT {0} - -typedef struct cl_perf_timer cl_perf_timer; - -void cl_perf_timer__init(cl_perf_timer *t); -void cl_perf_timer__start(cl_perf_timer *t); -void cl_perf_timer__stop(cl_perf_timer *t); - -/** - * return value of last start..stop interval in seconds. - */ -double cl_perf_timer__last(const cl_perf_timer *t); - -/** - * return cumulative running time across all start..stop - * intervals in seconds. - */ -double cl_perf_timer__sum(const cl_perf_timer *t); - -#endif /* __CLAR_LIBGIT2_TIMER__ */ diff --git a/tests/libgit2/clar_libgit2_trace.c b/tests/libgit2/clar_libgit2_trace.c deleted file mode 100644 index ebb0f41dd..000000000 --- a/tests/libgit2/clar_libgit2_trace.c +++ /dev/null @@ -1,263 +0,0 @@ -#include "clar_libgit2_trace.h" -#include "clar_libgit2.h" -#include "clar_libgit2_timer.h" -#include "trace.h" - -struct method { - const char *name; - void (*git_trace_cb)(git_trace_level_t level, const char *msg); - void (*close)(void); -}; - -static const char *message_prefix(git_trace_level_t level) -{ - switch (level) { - case GIT_TRACE_NONE: - return "[NONE]: "; - case GIT_TRACE_FATAL: - return "[FATAL]: "; - case GIT_TRACE_ERROR: - return "[ERROR]: "; - case GIT_TRACE_WARN: - return "[WARN]: "; - case GIT_TRACE_INFO: - return "[INFO]: "; - case GIT_TRACE_DEBUG: - return "[DEBUG]: "; - case GIT_TRACE_TRACE: - return "[TRACE]: "; - default: - return "[?????]: "; - } -} - -static void _git_trace_cb__printf(git_trace_level_t level, const char *msg) -{ - printf("%s%s\n", message_prefix(level), msg); -} - -#if defined(GIT_WIN32) -static void _git_trace_cb__debug(git_trace_level_t level, const char *msg) -{ - OutputDebugString(message_prefix(level)); - OutputDebugString(msg); - OutputDebugString("\n"); - - printf("%s%s\n", message_prefix(level), msg); -} -#else -#define _git_trace_cb__debug _git_trace_cb__printf -#endif - - -static void _trace_printf_close(void) -{ - fflush(stdout); -} - -#define _trace_debug_close _trace_printf_close - - -static struct method s_methods[] = { - { "printf", _git_trace_cb__printf, _trace_printf_close }, - { "debug", _git_trace_cb__debug, _trace_debug_close }, - /* TODO add file method */ - {0}, -}; - - -static int s_trace_loaded = 0; -static int s_trace_level = GIT_TRACE_NONE; -static struct method *s_trace_method = NULL; -static int s_trace_tests = 0; - -static int set_method(const char *name) -{ - int k; - - if (!name || !*name) - name = "printf"; - - for (k=0; (s_methods[k].name); k++) { - if (strcmp(name, s_methods[k].name) == 0) { - s_trace_method = &s_methods[k]; - return 0; - } - } - fprintf(stderr, "Unknown CLAR_TRACE_METHOD: '%s'\n", name); - return -1; -} - - -/** - * Lookup CLAR_TRACE_LEVEL and CLAR_TRACE_METHOD from - * the environment and set the above s_trace_* fields. - * - * If CLAR_TRACE_LEVEL is not set, we disable tracing. - * - * TODO If set, we assume GIT_TRACE_TRACE level, which - * logs everything. Later, we may want to parse the - * value of the environment variable and set a specific - * level. - * - * We assume the "printf" method. This can be changed - * with the CLAR_TRACE_METHOD environment variable. - * Currently, this is only needed on Windows for a "debug" - * version which also writes to the debug output window - * in Visual Studio. - * - * TODO add a "file" method that would open and write - * to a well-known file. This would help keep trace - * output and clar output separate. - * - */ -static void _load_trace_params(void) -{ - char *sz_level; - char *sz_method; - char *sz_tests; - - s_trace_loaded = 1; - - sz_level = cl_getenv("CLAR_TRACE_LEVEL"); - if (!sz_level || !*sz_level) { - s_trace_level = GIT_TRACE_NONE; - s_trace_method = NULL; - return; - } - - /* TODO Parse sz_level and set s_trace_level. */ - s_trace_level = GIT_TRACE_TRACE; - - sz_method = cl_getenv("CLAR_TRACE_METHOD"); - if (set_method(sz_method) < 0) - set_method(NULL); - - sz_tests = cl_getenv("CLAR_TRACE_TESTS"); - if (sz_tests != NULL) - s_trace_tests = 1; -} - -#define HR "================================================================" - -/** - * Timer to report the take spend in a test's run() method. - */ -static cl_perf_timer s_timer_run = CL_PERF_TIMER_INIT; - -/** - * Timer to report total time in a test (init, run, cleanup). - */ -static cl_perf_timer s_timer_test = CL_PERF_TIMER_INIT; - -static void _cl_trace_cb__event_handler( - cl_trace_event ev, - const char *suite_name, - const char *test_name, - void *payload) -{ - GIT_UNUSED(payload); - - if (!s_trace_tests) - return; - - switch (ev) { - case CL_TRACE__SUITE_BEGIN: - git_trace(GIT_TRACE_TRACE, "\n\n%s\n%s: Begin Suite", HR, suite_name); -#if 0 && defined(GIT_WIN32_LEAKCHECK) - git_win32__crtdbg_stacktrace__dump( - GIT_WIN32__CRTDBG_STACKTRACE__SET_MARK, - suite_name); -#endif - break; - - case CL_TRACE__SUITE_END: -#if 0 && defined(GIT_WIN32_LEAKCHECK) - /* As an example of checkpointing, dump leaks within this suite. - * This may generate false positives for things like the global - * TLS error state and maybe the odb cache since they aren't - * freed until the global shutdown and outside the scope of this - * set of tests. - * - * This may under-report if the test itself uses a checkpoint. - * See tests/trace/windows/stacktrace.c - */ - git_win32__crtdbg_stacktrace__dump( - GIT_WIN32__CRTDBG_STACKTRACE__LEAKS_SINCE_MARK, - suite_name); -#endif - git_trace(GIT_TRACE_TRACE, "\n\n%s: End Suite\n%s", suite_name, HR); - break; - - case CL_TRACE__TEST__BEGIN: - git_trace(GIT_TRACE_TRACE, "\n%s::%s: Begin Test", suite_name, test_name); - cl_perf_timer__init(&s_timer_test); - cl_perf_timer__start(&s_timer_test); - break; - - case CL_TRACE__TEST__END: - cl_perf_timer__stop(&s_timer_test); - git_trace(GIT_TRACE_TRACE, "%s::%s: End Test (%.3f %.3f)", suite_name, test_name, - cl_perf_timer__last(&s_timer_run), - cl_perf_timer__last(&s_timer_test)); - break; - - case CL_TRACE__TEST__RUN_BEGIN: - git_trace(GIT_TRACE_TRACE, "%s::%s: Begin Run", suite_name, test_name); - cl_perf_timer__init(&s_timer_run); - cl_perf_timer__start(&s_timer_run); - break; - - case CL_TRACE__TEST__RUN_END: - cl_perf_timer__stop(&s_timer_run); - git_trace(GIT_TRACE_TRACE, "%s::%s: End Run", suite_name, test_name); - break; - - case CL_TRACE__TEST__LONGJMP: - cl_perf_timer__stop(&s_timer_run); - git_trace(GIT_TRACE_TRACE, "%s::%s: Aborted", suite_name, test_name); - break; - - default: - break; - } -} - -/** - * Setup/Enable git_trace() based upon settings user's environment. - */ -void cl_global_trace_register(void) -{ - if (!s_trace_loaded) - _load_trace_params(); - - if (s_trace_level == GIT_TRACE_NONE) - return; - if (s_trace_method == NULL) - return; - if (s_trace_method->git_trace_cb == NULL) - return; - - git_trace_set(s_trace_level, s_trace_method->git_trace_cb); - cl_trace_register(_cl_trace_cb__event_handler, NULL); -} - -/** - * If we turned on git_trace() earlier, turn it off. - * - * This is intended to let us close/flush any buffered - * IO if necessary. - * - */ -void cl_global_trace_disable(void) -{ - cl_trace_register(NULL, NULL); - git_trace_set(GIT_TRACE_NONE, NULL); - if (s_trace_method && s_trace_method->close) - s_trace_method->close(); - - /* Leave s_trace_ vars set so they can restart tracing - * since we only want to hit the environment variables - * once. - */ -} diff --git a/tests/libgit2/clar_libgit2_trace.h b/tests/libgit2/clar_libgit2_trace.h deleted file mode 100644 index 09d1e050f..000000000 --- a/tests/libgit2/clar_libgit2_trace.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef __CLAR_LIBGIT2_TRACE__ -#define __CLAR_LIBGIT2_TRACE__ - -void cl_global_trace_register(void); -void cl_global_trace_disable(void); - -#endif diff --git a/tests/libgit2/generate.py b/tests/libgit2/generate.py deleted file mode 100644 index d2cdb684a..000000000 --- a/tests/libgit2/generate.py +++ /dev/null @@ -1,316 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) Vicent Marti. All rights reserved. -# -# This file is part of clar, distributed under the ISC license. -# For full terms see the included COPYING file. -# - -from __future__ import with_statement -from string import Template -import re, fnmatch, os, sys, codecs, pickle, io - -class Module(object): - class Template(object): - def __init__(self, module): - self.module = module - - def _render_callback(self, cb): - if not cb: - return ' { NULL, NULL }' - return ' { "%s", &%s }' % (cb['short_name'], cb['symbol']) - - class DeclarationTemplate(Template): - def render(self): - out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n" - - for initializer in self.module.initializers: - out += "extern %s;\n" % initializer['declaration'] - - if self.module.cleanup: - out += "extern %s;\n" % self.module.cleanup['declaration'] - - return out - - class CallbacksTemplate(Template): - def render(self): - out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name - out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks) - out += "\n};\n" - return out - - class InfoTemplate(Template): - def render(self): - templates = [] - - initializers = self.module.initializers - if len(initializers) == 0: - initializers = [ None ] - - for initializer in initializers: - name = self.module.clean_name() - if initializer and initializer['short_name'].startswith('initialize_'): - variant = initializer['short_name'][len('initialize_'):] - name += " (%s)" % variant.replace('_', ' ') - - template = Template( - r""" - { - "${clean_name}", - ${initialize}, - ${cleanup}, - ${cb_ptr}, ${cb_count}, ${enabled} - }""" - ).substitute( - clean_name = name, - initialize = self._render_callback(initializer), - cleanup = self._render_callback(self.module.cleanup), - cb_ptr = "_clar_cb_%s" % self.module.name, - cb_count = len(self.module.callbacks), - enabled = int(self.module.enabled) - ) - templates.append(template) - - return ','.join(templates) - - def __init__(self, name): - self.name = name - - self.mtime = 0 - self.enabled = True - self.modified = False - - def clean_name(self): - return self.name.replace("_", "::") - - def _skip_comments(self, text): - SKIP_COMMENTS_REGEX = re.compile( - r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', - re.DOTALL | re.MULTILINE) - - def _replacer(match): - s = match.group(0) - return "" if s.startswith('/') else s - - return re.sub(SKIP_COMMENTS_REGEX, _replacer, text) - - def parse(self, contents): - TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{" - - contents = self._skip_comments(contents) - regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE) - - self.callbacks = [] - self.initializers = [] - self.cleanup = None - - for (declaration, symbol, short_name) in regex.findall(contents): - data = { - "short_name" : short_name, - "declaration" : declaration, - "symbol" : symbol - } - - if short_name.startswith('initialize'): - self.initializers.append(data) - elif short_name == 'cleanup': - self.cleanup = data - else: - self.callbacks.append(data) - - return self.callbacks != [] - - def refresh(self, path): - self.modified = False - - try: - st = os.stat(path) - - # Not modified - if st.st_mtime == self.mtime: - return True - - self.modified = True - self.mtime = st.st_mtime - - with codecs.open(path, encoding='utf-8') as fp: - raw_content = fp.read() - - except IOError: - return False - - return self.parse(raw_content) - -class TestSuite(object): - - def __init__(self, path, output): - self.path = path - self.output = output - - def maybe_generate(self, path): - if not os.path.isfile(path): - return True - - if any(module.modified for module in self.modules.values()): - return True - - return False - - def find_modules(self): - modules = [] - for root, _, files in os.walk(self.path): - module_root = root[len(self.path):] - module_root = [c for c in module_root.split(os.sep) if c] - - tests_in_module = fnmatch.filter(files, "*.c") - - for test_file in tests_in_module: - full_path = os.path.join(root, test_file) - module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_") - - modules.append((full_path, module_name)) - - return modules - - def load_cache(self): - path = os.path.join(self.output, '.clarcache') - cache = {} - - try: - fp = open(path, 'rb') - cache = pickle.load(fp) - fp.close() - except (IOError, ValueError): - pass - - return cache - - def save_cache(self): - path = os.path.join(self.output, '.clarcache') - with open(path, 'wb') as cache: - pickle.dump(self.modules, cache) - - def load(self, force = False): - module_data = self.find_modules() - self.modules = {} if force else self.load_cache() - - for path, name in module_data: - if name not in self.modules: - self.modules[name] = Module(name) - - if not self.modules[name].refresh(path): - del self.modules[name] - - def disable(self, excluded): - for exclude in excluded: - for module in self.modules.values(): - name = module.clean_name() - if name.startswith(exclude): - module.enabled = False - module.modified = True - - def suite_count(self): - return sum(max(1, len(m.initializers)) for m in self.modules.values()) - - def callback_count(self): - return sum(len(module.callbacks) for module in self.modules.values()) - - def write(self): - wrote_suite = self.write_suite() - wrote_header = self.write_header() - - if wrote_suite or wrote_header: - self.save_cache() - return True - - return False - - def write_output(self, fn, data): - if not self.maybe_generate(fn): - return False - - current = None - - try: - with open(fn, 'r') as input: - current = input.read() - except OSError: - pass - except IOError: - pass - - if current == data: - return False - - with open(fn, 'w') as output: - output.write(data) - - return True - - def write_suite(self): - suite_fn = os.path.join(self.output, 'clar.suite') - - with io.StringIO() as suite_file: - modules = sorted(self.modules.values(), key=lambda module: module.name) - - for module in modules: - t = Module.DeclarationTemplate(module) - suite_file.write(t.render()) - - for module in modules: - t = Module.CallbacksTemplate(module) - suite_file.write(t.render()) - - suites = "static struct clar_suite _clar_suites[] = {" + ','.join( - Module.InfoTemplate(module).render() for module in modules - ) + "\n};\n" - - suite_file.write(suites) - - suite_file.write(u"static const size_t _clar_suite_count = %d;\n" % self.suite_count()) - suite_file.write(u"static const size_t _clar_callback_count = %d;\n" % self.callback_count()) - - return self.write_output(suite_fn, suite_file.getvalue()) - - return False - - def write_header(self): - header_fn = os.path.join(self.output, 'clar_suite.h') - - with io.StringIO() as header_file: - header_file.write(u"#ifndef _____clar_suite_h_____\n") - header_file.write(u"#define _____clar_suite_h_____\n") - - modules = sorted(self.modules.values(), key=lambda module: module.name) - - for module in modules: - t = Module.DeclarationTemplate(module) - header_file.write(t.render()) - - header_file.write(u"#endif\n") - - return self.write_output(header_fn, header_file.getvalue()) - - return False - -if __name__ == '__main__': - from optparse import OptionParser - - parser = OptionParser() - parser.add_option('-f', '--force', action="store_true", dest='force', default=False) - parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[]) - parser.add_option('-o', '--output', dest='output') - - options, args = parser.parse_args() - if len(args) > 1: - print("More than one path given") - sys.exit(1) - - path = args.pop() if args else '.' - output = options.output or path - suite = TestSuite(path, output) - suite.load(options.force) - suite.disable(options.excluded) - if suite.write(): - print("Written `clar.suite`, `clar_suite.h` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count())) - diff --git a/tests/libgit2/main.c b/tests/libgit2/main.c deleted file mode 100644 index 56751c288..000000000 --- a/tests/libgit2/main.c +++ /dev/null @@ -1,50 +0,0 @@ -#include "clar_libgit2.h" -#include "clar_libgit2_trace.h" - -#ifdef GIT_WIN32_LEAKCHECK -# include "win32/w32_leakcheck.h" -#endif - -#ifdef _WIN32 -int __cdecl main(int argc, char *argv[]) -#else -int main(int argc, char *argv[]) -#endif -{ - int res; - char *at_exit_cmd; - - clar_test_init(argc, argv); - - res = git_libgit2_init(); - if (res < 0) { - const git_error *err = git_error_last(); - const char *msg = err ? err->message : "unknown failure"; - fprintf(stderr, "failed to init libgit2: %s\n", msg); - return res; - } - - cl_global_trace_register(); - cl_sandbox_set_search_path_defaults(); - - /* Run the test suite */ - res = clar_test_run(); - - clar_test_shutdown(); - - cl_global_trace_disable(); - git_libgit2_shutdown(); - -#ifdef GIT_WIN32_LEAKCHECK - if (git_win32_leakcheck_has_leaks()) - res = res || 1; -#endif - - at_exit_cmd = getenv("CLAR_AT_EXIT"); - if (at_exit_cmd != NULL) { - int at_exit = system(at_exit_cmd); - return res || at_exit; - } - - return res; -} diff --git a/tests/libgit2/remote/fetch.c b/tests/libgit2/remote/fetch.c index 370046267..85e99206f 100644 --- a/tests/libgit2/remote/fetch.c +++ b/tests/libgit2/remote/fetch.c @@ -1,4 +1,4 @@ -#include "../clar_libgit2.h" +#include "clar_libgit2.h" #include "remote.h" #include "repository.h" @@ -82,9 +82,9 @@ static void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, cl_git_pass(git_treebuilder_write(&empty_tree_id, tb)); cl_git_pass(git_tree_lookup(&empty_tree, repo1, &empty_tree_id)); cl_git_pass(git_signature_default(&sig, repo1)); - cl_git_pass(git_commit_create(commit1id, repo1, REPO1_REFNAME, sig, + cl_git_pass(git_commit_create(commit1id, repo1, REPO1_REFNAME, sig, sig, NULL, "one", empty_tree, 0, NULL)); - cl_git_pass(git_commit_create_v(commit2id, repo1, REPO1_REFNAME, sig, + cl_git_pass(git_commit_create_v(commit2id, repo1, REPO1_REFNAME, sig, sig, NULL, "two", empty_tree, 1, commit1id)); git_tree_free(empty_tree); @@ -118,7 +118,7 @@ static void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, git_reference *ref; git_reference *ref2; cl_git_pass(git_reference_lookup(&ref, repo1, REPO1_REFNAME)); - cl_git_pass(git_reference_set_target(&ref2, ref, commit1id, + cl_git_pass(git_reference_set_target(&ref2, ref, commit1id, "rollback")); git_reference_free(ref); git_reference_free(ref2); -- cgit v1.2.1 From 2b09b5d7a0e856f6a5b23bfbd38537f8ee472523 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 16 Nov 2021 23:47:14 -0500 Subject: refactor: move headertest into separate test folder --- tests/CMakeLists.txt | 1 + tests/README.md | 3 +++ tests/headertest/CMakeLists.txt | 14 ++++++++++++++ tests/headertest/headertest.c | 13 +++++++++++++ tests/libgit2/CMakeLists.txt | 17 ----------------- tests/libgit2/headertest.c | 13 ------------- 6 files changed, 31 insertions(+), 30 deletions(-) create mode 100644 tests/headertest/CMakeLists.txt create mode 100644 tests/headertest/headertest.c delete mode 100644 tests/libgit2/headertest.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d17f52589..7214d94de 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,5 @@ # The main libgit2 tests tree: this CMakeLists.txt includes the # subprojects that make up core libgit2 support. +add_subdirectory(headertest) add_subdirectory(libgit2) diff --git a/tests/README.md b/tests/README.md index 2e3b2630e..91b26f592 100644 --- a/tests/README.md +++ b/tests/README.md @@ -4,6 +4,9 @@ These are the unit and integration tests for the libgit2 projects. * `clar` This is [clar](https://github.com/clar-test/clar) the common test framework. +* `headertest` + This is a simple project that ensures that our public headers are + compatible with extremely strict compilation options. * `libgit2` These tests exercise the core git functionality in libgit2 itself. * `resources` diff --git a/tests/headertest/CMakeLists.txt b/tests/headertest/CMakeLists.txt new file mode 100644 index 000000000..c70ce1ae1 --- /dev/null +++ b/tests/headertest/CMakeLists.txt @@ -0,0 +1,14 @@ +# Header file validation project: ensure that we do not publish any sloppy +# definitions in our headers and that a consumer can include +# even when they have aggressive C90 warnings enabled. + +add_executable(headertest headertest.c) +set_target_properties(headertest PROPERTIES C_STANDARD 90) +set_target_properties(headertest PROPERTIES C_EXTENSIONS OFF) +target_include_directories(headertest PRIVATE ${LIBGIT2_INCLUDES}) + +if (MSVC) + target_compile_options(headertest PUBLIC /W4 /WX) +else() + target_compile_options(headertest PUBLIC -Wall -Wextra -pedantic -Werror) +endif() diff --git a/tests/headertest/headertest.c b/tests/headertest/headertest.c new file mode 100644 index 000000000..2af8a14ec --- /dev/null +++ b/tests/headertest/headertest.c @@ -0,0 +1,13 @@ +/* + * Dummy project to validate header files + * + * This project is not intended to be executed, it should only include all + * header files to make sure that they can be used with stricter compiler + * settings than the libgit2 source files generally supports. + */ +#include "git2.h" + +int main(void) +{ + return 0; +} diff --git a/tests/libgit2/CMakeLists.txt b/tests/libgit2/CMakeLists.txt index 9ab01077a..90ae6253e 100644 --- a/tests/libgit2/CMakeLists.txt +++ b/tests/libgit2/CMakeLists.txt @@ -80,20 +80,3 @@ add_clar_test(ssh -v -sonline::push -sonline::clone::ssh_cert -s add_clar_test(proxy -v -sonline::clone::proxy) add_clar_test(auth_clone -v -sonline::clone::cred) add_clar_test(auth_clone_and_push -v -sonline::clone::push -sonline::push) - -# -# Header file validation project: ensure that we do not publish any sloppy -# definitions in our headers and that a consumer can include -# even when they have aggressive C90 warnings enabled. -# - -add_executable(headertest headertest.c) -set_target_properties(headertest PROPERTIES C_STANDARD 90) -set_target_properties(headertest PROPERTIES C_EXTENSIONS OFF) -target_include_directories(headertest PRIVATE ${LIBGIT2_INCLUDES}) - -if (MSVC) - target_compile_options(headertest PUBLIC /W4 /WX) -else() - target_compile_options(headertest PUBLIC -Wall -Wextra -pedantic -Werror) -endif() diff --git a/tests/libgit2/headertest.c b/tests/libgit2/headertest.c deleted file mode 100644 index 2af8a14ec..000000000 --- a/tests/libgit2/headertest.c +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Dummy project to validate header files - * - * This project is not intended to be executed, it should only include all - * header files to make sure that they can be used with stricter compiler - * settings than the libgit2 source files generally supports. - */ -#include "git2.h" - -int main(void) -{ - return 0; -} -- cgit v1.2.1 From e6d93612e86f50809b941030ef605382895e6f0a Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 16 Nov 2021 23:59:43 -0500 Subject: refactor: move utility tests into util --- ci/test.sh | 22 +- tests/CMakeLists.txt | 1 + tests/README.md | 2 + tests/libgit2/core/array.c | 57 -- tests/libgit2/core/assert.c | 94 --- tests/libgit2/core/bitvec.c | 64 -- tests/libgit2/core/copy.c | 153 ----- tests/libgit2/core/dirent.c | 306 ---------- tests/libgit2/core/encoding.c | 42 -- tests/libgit2/core/errors.c | 222 ------- tests/libgit2/core/filebuf.c | 267 --------- tests/libgit2/core/ftruncate.c | 48 -- tests/libgit2/core/futils.c | 115 ---- tests/libgit2/core/gitstr.c | 1225 -------------------------------------- tests/libgit2/core/hashsig.c | 182 ++++++ tests/libgit2/core/hex.c | 22 - tests/libgit2/core/iconv.c | 78 --- tests/libgit2/core/init.c | 54 -- tests/libgit2/core/integer.c | 253 -------- tests/libgit2/core/link.c | 630 -------------------- tests/libgit2/core/memmem.c | 46 -- tests/libgit2/core/mkdir.c | 291 --------- tests/libgit2/core/path.c | 739 ----------------------- tests/libgit2/core/pool.c | 61 +- tests/libgit2/core/posix.c | 238 -------- tests/libgit2/core/pqueue.c | 150 ----- tests/libgit2/core/qsort.c | 90 --- tests/libgit2/core/regexp.c | 213 ------- tests/libgit2/core/rmdir.c | 120 ---- tests/libgit2/core/sha1.c | 70 --- tests/libgit2/core/sortedcache.c | 363 ----------- tests/libgit2/core/stat.c | 113 ---- tests/libgit2/core/string.c | 136 ----- tests/libgit2/core/strmap.c | 190 ------ tests/libgit2/core/strtol.c | 128 ---- tests/libgit2/core/utf8.c | 20 - tests/libgit2/core/vector.c | 430 ------------- tests/libgit2/core/wildmatch.c | 248 -------- tests/libgit2/core/zstream.c | 167 ------ tests/libgit2/diff/userdiff.c | 25 + tests/libgit2/path/core.c | 405 ------------- tests/libgit2/path/dotgit.c | 206 ------- tests/libgit2/path/validate.c | 63 ++ tests/libgit2/path/win32.c | 282 --------- tests/libgit2/str/basic.c | 50 -- tests/libgit2/str/oom.c | 58 -- tests/libgit2/str/percent.c | 48 -- tests/libgit2/str/quote.c | 87 --- tests/libgit2/str/splice.c | 92 --- tests/util/CMakeLists.txt | 75 +++ tests/util/array.c | 57 ++ tests/util/assert.c | 94 +++ tests/util/bitvec.c | 64 ++ tests/util/copy.c | 153 +++++ tests/util/crlf.h | 30 + tests/util/dirent.c | 306 ++++++++++ tests/util/encoding.c | 42 ++ tests/util/errors.c | 222 +++++++ tests/util/filebuf.c | 267 +++++++++ tests/util/ftruncate.c | 48 ++ tests/util/futils.c | 115 ++++ tests/util/gitstr.c | 1044 ++++++++++++++++++++++++++++++++ tests/util/hex.c | 22 + tests/util/iconv.c | 78 +++ tests/util/init.c | 54 ++ tests/util/integer.c | 253 ++++++++ tests/util/link.c | 630 ++++++++++++++++++++ tests/util/memmem.c | 46 ++ tests/util/mkdir.c | 291 +++++++++ tests/util/path.c | 739 +++++++++++++++++++++++ tests/util/path/core.c | 343 +++++++++++ tests/util/path/win32.c | 282 +++++++++ tests/util/pool.c | 62 ++ tests/util/posix.c | 238 ++++++++ tests/util/pqueue.c | 150 +++++ tests/util/precompiled.c | 1 + tests/util/precompiled.h | 3 + tests/util/qsort.c | 90 +++ tests/util/regexp.c | 197 ++++++ tests/util/rmdir.c | 120 ++++ tests/util/sha1.c | 70 +++ tests/util/sortedcache.c | 363 +++++++++++ tests/util/stat.c | 113 ++++ tests/util/str/basic.c | 50 ++ tests/util/str/oom.c | 58 ++ tests/util/str/percent.c | 48 ++ tests/util/str/quote.c | 87 +++ tests/util/str/splice.c | 92 +++ tests/util/string.c | 136 +++++ tests/util/strmap.c | 190 ++++++ tests/util/strtol.c | 128 ++++ tests/util/utf8.c | 20 + tests/util/vector.c | 430 +++++++++++++ tests/util/wildmatch.c | 248 ++++++++ tests/util/zstream.c | 167 ++++++ 95 files changed, 8607 insertions(+), 8675 deletions(-) delete mode 100644 tests/libgit2/core/array.c delete mode 100644 tests/libgit2/core/assert.c delete mode 100644 tests/libgit2/core/bitvec.c delete mode 100644 tests/libgit2/core/copy.c delete mode 100644 tests/libgit2/core/dirent.c delete mode 100644 tests/libgit2/core/encoding.c delete mode 100644 tests/libgit2/core/errors.c delete mode 100644 tests/libgit2/core/filebuf.c delete mode 100644 tests/libgit2/core/ftruncate.c delete mode 100644 tests/libgit2/core/futils.c delete mode 100644 tests/libgit2/core/gitstr.c create mode 100644 tests/libgit2/core/hashsig.c delete mode 100644 tests/libgit2/core/hex.c delete mode 100644 tests/libgit2/core/iconv.c delete mode 100644 tests/libgit2/core/init.c delete mode 100644 tests/libgit2/core/integer.c delete mode 100644 tests/libgit2/core/link.c delete mode 100644 tests/libgit2/core/memmem.c delete mode 100644 tests/libgit2/core/mkdir.c delete mode 100644 tests/libgit2/core/path.c delete mode 100644 tests/libgit2/core/posix.c delete mode 100644 tests/libgit2/core/pqueue.c delete mode 100644 tests/libgit2/core/qsort.c delete mode 100644 tests/libgit2/core/regexp.c delete mode 100644 tests/libgit2/core/rmdir.c delete mode 100644 tests/libgit2/core/sha1.c delete mode 100644 tests/libgit2/core/sortedcache.c delete mode 100644 tests/libgit2/core/stat.c delete mode 100644 tests/libgit2/core/string.c delete mode 100644 tests/libgit2/core/strmap.c delete mode 100644 tests/libgit2/core/strtol.c delete mode 100644 tests/libgit2/core/utf8.c delete mode 100644 tests/libgit2/core/vector.c delete mode 100644 tests/libgit2/core/wildmatch.c delete mode 100644 tests/libgit2/core/zstream.c create mode 100644 tests/libgit2/diff/userdiff.c delete mode 100644 tests/libgit2/path/core.c delete mode 100644 tests/libgit2/path/dotgit.c create mode 100644 tests/libgit2/path/validate.c delete mode 100644 tests/libgit2/path/win32.c delete mode 100644 tests/libgit2/str/basic.c delete mode 100644 tests/libgit2/str/oom.c delete mode 100644 tests/libgit2/str/percent.c delete mode 100644 tests/libgit2/str/quote.c delete mode 100644 tests/libgit2/str/splice.c create mode 100644 tests/util/CMakeLists.txt create mode 100644 tests/util/array.c create mode 100644 tests/util/assert.c create mode 100644 tests/util/bitvec.c create mode 100644 tests/util/copy.c create mode 100644 tests/util/crlf.h create mode 100644 tests/util/dirent.c create mode 100644 tests/util/encoding.c create mode 100644 tests/util/errors.c create mode 100644 tests/util/filebuf.c create mode 100644 tests/util/ftruncate.c create mode 100644 tests/util/futils.c create mode 100644 tests/util/gitstr.c create mode 100644 tests/util/hex.c create mode 100644 tests/util/iconv.c create mode 100644 tests/util/init.c create mode 100644 tests/util/integer.c create mode 100644 tests/util/link.c create mode 100644 tests/util/memmem.c create mode 100644 tests/util/mkdir.c create mode 100644 tests/util/path.c create mode 100644 tests/util/path/core.c create mode 100644 tests/util/path/win32.c create mode 100644 tests/util/pool.c create mode 100644 tests/util/posix.c create mode 100644 tests/util/pqueue.c create mode 100644 tests/util/precompiled.c create mode 100644 tests/util/precompiled.h create mode 100644 tests/util/qsort.c create mode 100644 tests/util/regexp.c create mode 100644 tests/util/rmdir.c create mode 100644 tests/util/sha1.c create mode 100644 tests/util/sortedcache.c create mode 100644 tests/util/stat.c create mode 100644 tests/util/str/basic.c create mode 100644 tests/util/str/oom.c create mode 100644 tests/util/str/percent.c create mode 100644 tests/util/str/quote.c create mode 100644 tests/util/str/splice.c create mode 100644 tests/util/string.c create mode 100644 tests/util/strmap.c create mode 100644 tests/util/strtol.c create mode 100644 tests/util/utf8.c create mode 100644 tests/util/vector.c create mode 100644 tests/util/wildmatch.c create mode 100644 tests/util/zstream.c diff --git a/ci/test.sh b/ci/test.sh index a94839778..ec2151987 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -156,13 +156,25 @@ fi # Run the tests that do not require network connectivity. +if [ -z "$SKIP_UTILITY_TESTS" ]; then + run_test util +fi + if [ -z "$SKIP_OFFLINE_TESTS" ]; then echo "" echo "##############################################################################" - echo "## Running (offline) tests" + echo "## Running core tests" echo "##############################################################################" + echo "" + echo "Running libgit2 integration (offline) tests" + echo "" run_test offline + + echo "" + echo "Running utility tests" + echo "" + run_test util fi if [ -n "$RUN_INVASIVE_TESTS" ]; then @@ -186,7 +198,7 @@ if [ -z "$SKIP_ONLINE_TESTS" ]; then echo "" echo "##############################################################################" - echo "## Running (online) tests" + echo "## Running networking (online) tests" echo "##############################################################################" export GITTEST_REMOTE_REDIRECT_INITIAL="http://localhost:9000/initial-redirect/libgit2/TestGitRepository" @@ -198,9 +210,9 @@ if [ -z "$SKIP_ONLINE_TESTS" ]; then # Run the online tests that immutably change global state separately # to avoid polluting the test environment. echo "" - echo "##############################################################################" - echo "## Running (online_customcert) tests" - echo "##############################################################################" + echo "Running custom certificate (online_customcert) tests" + echo "" + run_test online_customcert fi diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7214d94de..df100e980 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,3 +3,4 @@ add_subdirectory(headertest) add_subdirectory(libgit2) +add_subdirectory(util) diff --git a/tests/README.md b/tests/README.md index 91b26f592..5920b1547 100644 --- a/tests/README.md +++ b/tests/README.md @@ -12,6 +12,8 @@ These are the unit and integration tests for the libgit2 projects. * `resources` These are the resources for the tests, including files and git repositories. +* `util` + These are tests of the common utility library. ## Writing tests for libgit2 diff --git a/tests/libgit2/core/array.c b/tests/libgit2/core/array.c deleted file mode 100644 index 8e626a506..000000000 --- a/tests/libgit2/core/array.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "clar_libgit2.h" -#include "array.h" - -static int int_lookup(const void *k, const void *a) -{ - const int *one = (const int *)k; - int *two = (int *)a; - - return *one - *two; -} - -#define expect_pos(k, n, ret) \ - key = (k); \ - cl_assert_equal_i((ret), \ - git_array_search(&p, integers, int_lookup, &key)); \ - cl_assert_equal_i((n), p); - -void test_core_array__bsearch2(void) -{ - git_array_t(int) integers = GIT_ARRAY_INIT; - int *i, key; - size_t p; - - i = git_array_alloc(integers); *i = 2; - i = git_array_alloc(integers); *i = 3; - i = git_array_alloc(integers); *i = 5; - i = git_array_alloc(integers); *i = 7; - i = git_array_alloc(integers); *i = 7; - i = git_array_alloc(integers); *i = 8; - i = git_array_alloc(integers); *i = 13; - i = git_array_alloc(integers); *i = 21; - i = git_array_alloc(integers); *i = 25; - i = git_array_alloc(integers); *i = 42; - i = git_array_alloc(integers); *i = 69; - i = git_array_alloc(integers); *i = 121; - i = git_array_alloc(integers); *i = 256; - i = git_array_alloc(integers); *i = 512; - i = git_array_alloc(integers); *i = 513; - i = git_array_alloc(integers); *i = 514; - i = git_array_alloc(integers); *i = 516; - i = git_array_alloc(integers); *i = 516; - i = git_array_alloc(integers); *i = 517; - - /* value to search for, expected position, return code */ - expect_pos(3, 1, GIT_OK); - expect_pos(2, 0, GIT_OK); - expect_pos(1, 0, GIT_ENOTFOUND); - expect_pos(25, 8, GIT_OK); - expect_pos(26, 9, GIT_ENOTFOUND); - expect_pos(42, 9, GIT_OK); - expect_pos(50, 10, GIT_ENOTFOUND); - expect_pos(68, 10, GIT_ENOTFOUND); - expect_pos(256, 12, GIT_OK); - - git_array_clear(integers); -} - diff --git a/tests/libgit2/core/assert.c b/tests/libgit2/core/assert.c deleted file mode 100644 index ef75624b9..000000000 --- a/tests/libgit2/core/assert.c +++ /dev/null @@ -1,94 +0,0 @@ -#ifdef GIT_ASSERT_HARD -# undef GIT_ASSERT_HARD -#endif - -#define GIT_ASSERT_HARD 0 - -#include "clar_libgit2.h" - -static const char *hello_world = "hello, world"; -static const char *fail = "FAIL"; - -static int dummy_fn(const char *myarg) -{ - GIT_ASSERT_ARG(myarg); - GIT_ASSERT_ARG(myarg != hello_world); - return 0; -} - -static const char *fn_returns_string(const char *myarg) -{ - GIT_ASSERT_ARG_WITH_RETVAL(myarg, fail); - GIT_ASSERT_ARG_WITH_RETVAL(myarg != hello_world, fail); - - return myarg; -} - -static int bad_math(void) -{ - GIT_ASSERT(1 + 1 == 3); - return 42; -} - -static const char *bad_returns_string(void) -{ - GIT_ASSERT_WITH_RETVAL(1 + 1 == 3, NULL); - return hello_world; -} - -void test_core_assert__argument(void) -{ - cl_git_fail(dummy_fn(NULL)); - cl_assert(git_error_last()); - cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); - cl_assert_equal_s("invalid argument: 'myarg'", git_error_last()->message); - - cl_git_fail(dummy_fn(hello_world)); - cl_assert(git_error_last()); - cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); - cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message); - - cl_git_pass(dummy_fn("foo")); -} - -void test_core_assert__argument_with_non_int_return_type(void) -{ - const char *foo = "foo"; - - cl_assert_equal_p(fail, fn_returns_string(NULL)); - cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); - cl_assert_equal_s("invalid argument: 'myarg'", git_error_last()->message); - - cl_assert_equal_p(fail, fn_returns_string(hello_world)); - cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); - cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message); - - cl_assert_equal_p(foo, fn_returns_string(foo)); -} - -void test_core_assert__argument_with_void_return_type(void) -{ - const char *foo = "foo"; - - git_error_clear(); - fn_returns_string(hello_world); - cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); - cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message); - - git_error_clear(); - cl_assert_equal_p(foo, fn_returns_string(foo)); - cl_assert_equal_p(NULL, git_error_last()); -} - -void test_core_assert__internal(void) -{ - cl_git_fail(bad_math()); - cl_assert(git_error_last()); - cl_assert_equal_i(GIT_ERROR_INTERNAL, git_error_last()->klass); - cl_assert_equal_s("unrecoverable internal error: '1 + 1 == 3'", git_error_last()->message); - - cl_assert_equal_p(NULL, bad_returns_string()); - cl_assert(git_error_last()); - cl_assert_equal_i(GIT_ERROR_INTERNAL, git_error_last()->klass); - cl_assert_equal_s("unrecoverable internal error: '1 + 1 == 3'", git_error_last()->message); -} diff --git a/tests/libgit2/core/bitvec.c b/tests/libgit2/core/bitvec.c deleted file mode 100644 index 48d7b99f0..000000000 --- a/tests/libgit2/core/bitvec.c +++ /dev/null @@ -1,64 +0,0 @@ -#include "clar_libgit2.h" -#include "bitvec.h" - -#if 0 -static void print_bitvec(git_bitvec *bv) -{ - int b; - - if (!bv->length) { - for (b = 63; b >= 0; --b) - fprintf(stderr, "%d", (bv->u.bits & (1ul << b)) ? 1 : 0); - } else { - for (b = bv->length * 8; b >= 0; --b) - fprintf(stderr, "%d", (bv->u.ptr[b >> 3] & (b & 0x0ff)) ? 1 : 0); - } - fprintf(stderr, "\n"); -} -#endif - -static void set_some_bits(git_bitvec *bv, size_t length) -{ - size_t i; - - for (i = 0; i < length; ++i) { - if (i % 3 == 0 || i % 7 == 0) - git_bitvec_set(bv, i, true); - } -} - -static void check_some_bits(git_bitvec *bv, size_t length) -{ - size_t i; - - for (i = 0; i < length; ++i) - cl_assert_equal_b(i % 3 == 0 || i % 7 == 0, git_bitvec_get(bv, i)); -} - -void test_core_bitvec__0(void) -{ - git_bitvec bv; - - cl_git_pass(git_bitvec_init(&bv, 32)); - set_some_bits(&bv, 16); - check_some_bits(&bv, 16); - git_bitvec_clear(&bv); - set_some_bits(&bv, 32); - check_some_bits(&bv, 32); - git_bitvec_clear(&bv); - set_some_bits(&bv, 64); - check_some_bits(&bv, 64); - git_bitvec_free(&bv); - - cl_git_pass(git_bitvec_init(&bv, 128)); - set_some_bits(&bv, 32); - check_some_bits(&bv, 32); - set_some_bits(&bv, 128); - check_some_bits(&bv, 128); - git_bitvec_free(&bv); - - cl_git_pass(git_bitvec_init(&bv, 4000)); - set_some_bits(&bv, 4000); - check_some_bits(&bv, 4000); - git_bitvec_free(&bv); -} diff --git a/tests/libgit2/core/copy.c b/tests/libgit2/core/copy.c deleted file mode 100644 index 6d22b503d..000000000 --- a/tests/libgit2/core/copy.c +++ /dev/null @@ -1,153 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "posix.h" - -void test_core_copy__file(void) -{ - struct stat st; - const char *content = "This is some stuff to copy\n"; - - cl_git_mkfile("copy_me", content); - - cl_git_pass(git_futils_cp("copy_me", "copy_me_two", 0664)); - - cl_git_pass(git_fs_path_lstat("copy_me_two", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert(strlen(content) == (size_t)st.st_size); - - cl_git_pass(p_unlink("copy_me_two")); - cl_git_pass(p_unlink("copy_me")); -} - -void test_core_copy__file_in_dir(void) -{ - struct stat st; - const char *content = "This is some other stuff to copy\n"; - - cl_git_pass(git_futils_mkdir("an_dir/in_a_dir", 0775, GIT_MKDIR_PATH)); - cl_git_mkfile("an_dir/in_a_dir/copy_me", content); - cl_assert(git_fs_path_isdir("an_dir")); - - cl_git_pass(git_futils_mkpath2file - ("an_dir/second_dir/and_more/copy_me_two", 0775)); - - cl_git_pass(git_futils_cp - ("an_dir/in_a_dir/copy_me", - "an_dir/second_dir/and_more/copy_me_two", - 0664)); - - cl_git_pass(git_fs_path_lstat("an_dir/second_dir/and_more/copy_me_two", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert(strlen(content) == (size_t)st.st_size); - - cl_git_pass(git_futils_rmdir_r("an_dir", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_assert(!git_fs_path_isdir("an_dir")); -} - -#ifndef GIT_WIN32 -static void assert_hard_link(const char *path) -{ - /* we assert this by checking that there's more than one link to the file */ - struct stat st; - - cl_assert(git_fs_path_isfile(path)); - cl_git_pass(p_stat(path, &st)); - cl_assert(st.st_nlink > 1); -} -#endif - -void test_core_copy__tree(void) -{ - struct stat st; - const char *content = "File content\n"; - - cl_git_pass(git_futils_mkdir("src/b", 0775, GIT_MKDIR_PATH)); - cl_git_pass(git_futils_mkdir("src/c/d", 0775, GIT_MKDIR_PATH)); - cl_git_pass(git_futils_mkdir("src/c/e", 0775, GIT_MKDIR_PATH)); - - cl_git_mkfile("src/f1", content); - cl_git_mkfile("src/b/f2", content); - cl_git_mkfile("src/c/f3", content); - cl_git_mkfile("src/c/d/f4", content); - cl_git_mkfile("src/c/d/.f5", content); - -#ifndef GIT_WIN32 - cl_assert(p_symlink("../../b/f2", "src/c/d/l1") == 0); -#endif - - cl_assert(git_fs_path_isdir("src")); - cl_assert(git_fs_path_isdir("src/b")); - cl_assert(git_fs_path_isdir("src/c/d")); - cl_assert(git_fs_path_isfile("src/c/d/f4")); - - /* copy with no empty dirs, yes links, no dotfiles, no overwrite */ - - cl_git_pass( - git_futils_cp_r("src", "t1", GIT_CPDIR_COPY_SYMLINKS, 0) ); - - cl_assert(git_fs_path_isdir("t1")); - cl_assert(git_fs_path_isdir("t1/b")); - cl_assert(git_fs_path_isdir("t1/c")); - cl_assert(git_fs_path_isdir("t1/c/d")); - cl_assert(!git_fs_path_isdir("t1/c/e")); - - cl_assert(git_fs_path_isfile("t1/f1")); - cl_assert(git_fs_path_isfile("t1/b/f2")); - cl_assert(git_fs_path_isfile("t1/c/f3")); - cl_assert(git_fs_path_isfile("t1/c/d/f4")); - cl_assert(!git_fs_path_isfile("t1/c/d/.f5")); - - cl_git_pass(git_fs_path_lstat("t1/c/f3", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert(strlen(content) == (size_t)st.st_size); - -#ifndef GIT_WIN32 - cl_git_pass(git_fs_path_lstat("t1/c/d/l1", &st)); - cl_assert(S_ISLNK(st.st_mode)); -#endif - - cl_git_pass(git_futils_rmdir_r("t1", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_assert(!git_fs_path_isdir("t1")); - - /* copy with empty dirs, no links, yes dotfiles, no overwrite */ - - cl_git_pass( - git_futils_cp_r("src", "t2", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_COPY_DOTFILES, 0) ); - - cl_assert(git_fs_path_isdir("t2")); - cl_assert(git_fs_path_isdir("t2/b")); - cl_assert(git_fs_path_isdir("t2/c")); - cl_assert(git_fs_path_isdir("t2/c/d")); - cl_assert(git_fs_path_isdir("t2/c/e")); - - cl_assert(git_fs_path_isfile("t2/f1")); - cl_assert(git_fs_path_isfile("t2/b/f2")); - cl_assert(git_fs_path_isfile("t2/c/f3")); - cl_assert(git_fs_path_isfile("t2/c/d/f4")); - cl_assert(git_fs_path_isfile("t2/c/d/.f5")); - -#ifndef GIT_WIN32 - cl_git_fail(git_fs_path_lstat("t2/c/d/l1", &st)); -#endif - - cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_assert(!git_fs_path_isdir("t2")); - -#ifndef GIT_WIN32 - cl_git_pass(git_futils_cp_r("src", "t3", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_LINK_FILES, 0)); - cl_assert(git_fs_path_isdir("t3")); - - cl_assert(git_fs_path_isdir("t3")); - cl_assert(git_fs_path_isdir("t3/b")); - cl_assert(git_fs_path_isdir("t3/c")); - cl_assert(git_fs_path_isdir("t3/c/d")); - cl_assert(git_fs_path_isdir("t3/c/e")); - - assert_hard_link("t3/f1"); - assert_hard_link("t3/b/f2"); - assert_hard_link("t3/c/f3"); - assert_hard_link("t3/c/d/f4"); -#endif - - cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_RMDIR_REMOVE_FILES)); -} diff --git a/tests/libgit2/core/dirent.c b/tests/libgit2/core/dirent.c deleted file mode 100644 index 2419ec7ab..000000000 --- a/tests/libgit2/core/dirent.c +++ /dev/null @@ -1,306 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" - -typedef struct name_data { - int count; /* return count */ - char *name; /* filename */ -} name_data; - -typedef struct walk_data { - char *sub; /* sub-directory name */ - name_data *names; /* name state data */ - git_str path; -} walk_data; - - -static char *top_dir = "dir-walk"; -static walk_data *state_loc; - -static void setup(walk_data *d) -{ - name_data *n; - - cl_must_pass(p_mkdir(top_dir, 0777)); - - cl_must_pass(p_chdir(top_dir)); - - if (strcmp(d->sub, ".") != 0) - cl_must_pass(p_mkdir(d->sub, 0777)); - - cl_git_pass(git_str_sets(&d->path, d->sub)); - - state_loc = d; - - for (n = d->names; n->name; n++) { - git_file fd = p_creat(n->name, 0666); - cl_assert(fd >= 0); - p_close(fd); - n->count = 0; - } -} - -static void dirent_cleanup__cb(void *_d) -{ - walk_data *d = _d; - name_data *n; - - for (n = d->names; n->name; n++) { - cl_must_pass(p_unlink(n->name)); - } - - if (strcmp(d->sub, ".") != 0) - cl_must_pass(p_rmdir(d->sub)); - - cl_must_pass(p_chdir("..")); - - cl_must_pass(p_rmdir(top_dir)); - - git_str_dispose(&d->path); -} - -static void check_counts(walk_data *d) -{ - name_data *n; - - for (n = d->names; n->name; n++) { - cl_assert(n->count == 1); - } -} - -static int update_count(name_data *data, const char *name) -{ - name_data *n; - - for (n = data; n->name; n++) { - if (!strcmp(n->name, name)) { - n->count++; - return 0; - } - } - - return GIT_ERROR; -} - -static int one_entry(void *state, git_str *path) -{ - walk_data *d = (walk_data *) state; - - if (state != state_loc) - return GIT_ERROR; - - if (path != &d->path) - return GIT_ERROR; - - return update_count(d->names, path->ptr); -} - - -static name_data dot_names[] = { - { 0, "./a" }, - { 0, "./asdf" }, - { 0, "./pack-foo.pack" }, - { 0, NULL } -}; -static walk_data dot = { - ".", - dot_names, - GIT_STR_INIT -}; - -/* make sure that the '.' folder is not traversed */ -void test_core_dirent__dont_traverse_dot(void) -{ - cl_set_cleanup(&dirent_cleanup__cb, &dot); - setup(&dot); - - cl_git_pass(git_fs_path_direach(&dot.path, 0, one_entry, &dot)); - - check_counts(&dot); -} - - -static name_data sub_names[] = { - { 0, "sub/a" }, - { 0, "sub/asdf" }, - { 0, "sub/pack-foo.pack" }, - { 0, NULL } -}; -static walk_data sub = { - "sub", - sub_names, - GIT_STR_INIT -}; - -/* traverse a subfolder */ -void test_core_dirent__traverse_subfolder(void) -{ - cl_set_cleanup(&dirent_cleanup__cb, &sub); - setup(&sub); - - cl_git_pass(git_fs_path_direach(&sub.path, 0, one_entry, &sub)); - - check_counts(&sub); -} - - -static walk_data sub_slash = { - "sub/", - sub_names, - GIT_STR_INIT -}; - -/* traverse a slash-terminated subfolder */ -void test_core_dirent__traverse_slash_terminated_folder(void) -{ - cl_set_cleanup(&dirent_cleanup__cb, &sub_slash); - setup(&sub_slash); - - cl_git_pass(git_fs_path_direach(&sub_slash.path, 0, one_entry, &sub_slash)); - - check_counts(&sub_slash); -} - - -static name_data empty_names[] = { - { 0, NULL } -}; -static walk_data empty = { - "empty", - empty_names, - GIT_STR_INIT -}; - -/* make sure that empty folders are not traversed */ -void test_core_dirent__dont_traverse_empty_folders(void) -{ - cl_set_cleanup(&dirent_cleanup__cb, &empty); - setup(&empty); - - cl_git_pass(git_fs_path_direach(&empty.path, 0, one_entry, &empty)); - - check_counts(&empty); - - /* make sure callback not called */ - cl_assert(git_fs_path_is_empty_dir(empty.path.ptr)); -} - -static name_data odd_names[] = { - { 0, "odd/.a" }, - { 0, "odd/..c" }, - /* the following don't work on cygwin/win32 */ - /* { 0, "odd/.b." }, */ - /* { 0, "odd/..d.." }, */ - { 0, NULL } -}; -static walk_data odd = { - "odd", - odd_names, - GIT_STR_INIT -}; - -/* make sure that strange looking filenames ('..c') are traversed */ -void test_core_dirent__traverse_weird_filenames(void) -{ - cl_set_cleanup(&dirent_cleanup__cb, &odd); - setup(&odd); - - cl_git_pass(git_fs_path_direach(&odd.path, 0, one_entry, &odd)); - - check_counts(&odd); -} - -/* test filename length limits */ -void test_core_dirent__length_limits(void) -{ - char *big_filename = (char *)git__malloc(FILENAME_MAX + 1); - memset(big_filename, 'a', FILENAME_MAX + 1); - big_filename[FILENAME_MAX] = 0; - - cl_must_fail(p_creat(big_filename, 0666)); - - git__free(big_filename); -} - -void test_core_dirent__empty_dir(void) -{ - cl_must_pass(p_mkdir("empty_dir", 0777)); - cl_assert(git_fs_path_is_empty_dir("empty_dir")); - - cl_git_mkfile("empty_dir/content", "whatever\n"); - cl_assert(!git_fs_path_is_empty_dir("empty_dir")); - cl_assert(!git_fs_path_is_empty_dir("empty_dir/content")); - - cl_must_pass(p_unlink("empty_dir/content")); - - cl_must_pass(p_mkdir("empty_dir/content", 0777)); - cl_assert(!git_fs_path_is_empty_dir("empty_dir")); - cl_assert(git_fs_path_is_empty_dir("empty_dir/content")); - - cl_must_pass(p_rmdir("empty_dir/content")); - - cl_must_pass(p_rmdir("empty_dir")); -} - -static void handle_next(git_fs_path_diriter *diriter, walk_data *walk) -{ - const char *fullpath, *filename; - size_t fullpath_len, filename_len; - - cl_git_pass(git_fs_path_diriter_fullpath(&fullpath, &fullpath_len, diriter)); - cl_git_pass(git_fs_path_diriter_filename(&filename, &filename_len, diriter)); - - cl_assert_equal_strn(fullpath, "sub/", 4); - cl_assert_equal_s(fullpath+4, filename); - - update_count(walk->names, fullpath); -} - -/* test directory iterator */ -void test_core_dirent__diriter_with_fullname(void) -{ - git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT; - int error; - - cl_set_cleanup(&dirent_cleanup__cb, &sub); - setup(&sub); - - cl_git_pass(git_fs_path_diriter_init(&diriter, sub.path.ptr, 0)); - - while ((error = git_fs_path_diriter_next(&diriter)) == 0) - handle_next(&diriter, &sub); - - cl_assert_equal_i(error, GIT_ITEROVER); - - git_fs_path_diriter_free(&diriter); - - check_counts(&sub); -} - -void test_core_dirent__diriter_at_directory_root(void) -{ - git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT; - const char *sandbox_path, *path; - char *root_path; - size_t path_len; - int root_offset, error; - - sandbox_path = clar_sandbox_path(); - cl_assert((root_offset = git_fs_path_root(sandbox_path)) >= 0); - - cl_assert(root_path = git__calloc(1, root_offset + 2)); - strncpy(root_path, sandbox_path, root_offset + 1); - - cl_git_pass(git_fs_path_diriter_init(&diriter, root_path, 0)); - - while ((error = git_fs_path_diriter_next(&diriter)) == 0) { - cl_git_pass(git_fs_path_diriter_fullpath(&path, &path_len, &diriter)); - - cl_assert(path_len > (size_t)(root_offset + 1)); - cl_assert(path[root_offset+1] != '/'); - } - - cl_assert_equal_i(error, GIT_ITEROVER); - - git_fs_path_diriter_free(&diriter); - git__free(root_path); -} diff --git a/tests/libgit2/core/encoding.c b/tests/libgit2/core/encoding.c deleted file mode 100644 index 6cec24679..000000000 --- a/tests/libgit2/core/encoding.c +++ /dev/null @@ -1,42 +0,0 @@ -#include "clar_libgit2.h" -#include "varint.h" - -void test_core_encoding__decode(void) -{ - const unsigned char *buf = (unsigned char *)"AB"; - size_t size; - - cl_assert(git_decode_varint(buf, &size) == 65); - cl_assert(size == 1); - - buf = (unsigned char *)"\xfe\xdc\xbaXY"; - cl_assert(git_decode_varint(buf, &size) == 267869656); - cl_assert(size == 4); - - buf = (unsigned char *)"\xaa\xaa\xfe\xdc\xbaXY"; - cl_assert(git_decode_varint(buf, &size) == UINT64_C(1489279344088)); - cl_assert(size == 6); - - buf = (unsigned char *)"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xfe\xdc\xbaXY"; - cl_assert(git_decode_varint(buf, &size) == 0); - cl_assert(size == 0); - -} - -void test_core_encoding__encode(void) -{ - unsigned char buf[100]; - cl_assert(git_encode_varint(buf, 100, 65) == 1); - cl_assert(buf[0] == 'A'); - - cl_assert(git_encode_varint(buf, 1, 1) == 1); - cl_assert(!memcmp(buf, "\x01", 1)); - - cl_assert(git_encode_varint(buf, 100, 267869656) == 4); - cl_assert(!memcmp(buf, "\xfe\xdc\xbaX", 4)); - - cl_assert(git_encode_varint(buf, 100, UINT64_C(1489279344088)) == 6); - cl_assert(!memcmp(buf, "\xaa\xaa\xfe\xdc\xbaX", 6)); - - cl_assert(git_encode_varint(buf, 1, UINT64_C(1489279344088)) == -1); -} diff --git a/tests/libgit2/core/errors.c b/tests/libgit2/core/errors.c deleted file mode 100644 index 386ecdc5f..000000000 --- a/tests/libgit2/core/errors.c +++ /dev/null @@ -1,222 +0,0 @@ -#include "clar_libgit2.h" - -void test_core_errors__public_api(void) -{ - char *str_in_error; - - git_error_clear(); - cl_assert(git_error_last() == NULL); - - git_error_set_oom(); - - cl_assert(git_error_last() != NULL); - cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); - str_in_error = strstr(git_error_last()->message, "memory"); - cl_assert(str_in_error != NULL); - - git_error_clear(); - - git_error_set_str(GIT_ERROR_REPOSITORY, "This is a test"); - - cl_assert(git_error_last() != NULL); - str_in_error = strstr(git_error_last()->message, "This is a test"); - cl_assert(str_in_error != NULL); - - git_error_clear(); - cl_assert(git_error_last() == NULL); -} - -#include "common.h" -#include "util.h" -#include "posix.h" - -void test_core_errors__new_school(void) -{ - char *str_in_error; - - git_error_clear(); - cl_assert(git_error_last() == NULL); - - git_error_set_oom(); /* internal fn */ - - cl_assert(git_error_last() != NULL); - cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); - str_in_error = strstr(git_error_last()->message, "memory"); - cl_assert(str_in_error != NULL); - - git_error_clear(); - - git_error_set(GIT_ERROR_REPOSITORY, "This is a test"); /* internal fn */ - - cl_assert(git_error_last() != NULL); - str_in_error = strstr(git_error_last()->message, "This is a test"); - cl_assert(str_in_error != NULL); - - git_error_clear(); - cl_assert(git_error_last() == NULL); - - do { - struct stat st; - memset(&st, 0, sizeof(st)); - cl_assert(p_lstat("this_file_does_not_exist", &st) < 0); - GIT_UNUSED(st); - } while (false); - git_error_set(GIT_ERROR_OS, "stat failed"); /* internal fn */ - - cl_assert(git_error_last() != NULL); - str_in_error = strstr(git_error_last()->message, "stat failed"); - cl_assert(str_in_error != NULL); - cl_assert(git__prefixcmp(str_in_error, "stat failed: ") == 0); - cl_assert(strlen(str_in_error) > strlen("stat failed: ")); - -#ifdef GIT_WIN32 - git_error_clear(); - - /* The MSDN docs use this to generate a sample error */ - cl_assert(GetProcessId(NULL) == 0); - git_error_set(GIT_ERROR_OS, "GetProcessId failed"); /* internal fn */ - - cl_assert(git_error_last() != NULL); - str_in_error = strstr(git_error_last()->message, "GetProcessId failed"); - cl_assert(str_in_error != NULL); - cl_assert(git__prefixcmp(str_in_error, "GetProcessId failed: ") == 0); - cl_assert(strlen(str_in_error) > strlen("GetProcessId failed: ")); -#endif - - git_error_clear(); -} - -void test_core_errors__restore(void) -{ - git_error_state err_state = {0}; - - git_error_clear(); - cl_assert(git_error_last() == NULL); - - cl_assert_equal_i(0, git_error_state_capture(&err_state, 0)); - - memset(&err_state, 0x0, sizeof(git_error_state)); - - git_error_set(42, "Foo: %s", "bar"); - cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); - - cl_assert(git_error_last() == NULL); - - git_error_set(99, "Bar: %s", "foo"); - - git_error_state_restore(&err_state); - - cl_assert_equal_i(42, git_error_last()->klass); - cl_assert_equal_s("Foo: bar", git_error_last()->message); -} - -void test_core_errors__free_state(void) -{ - git_error_state err_state = {0}; - - git_error_clear(); - - git_error_set(42, "Foo: %s", "bar"); - cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); - - git_error_set(99, "Bar: %s", "foo"); - - git_error_state_free(&err_state); - - cl_assert_equal_i(99, git_error_last()->klass); - cl_assert_equal_s("Bar: foo", git_error_last()->message); - - git_error_state_restore(&err_state); - - cl_assert(git_error_last() == NULL); -} - -void test_core_errors__restore_oom(void) -{ - git_error_state err_state = {0}; - const git_error *oom_error = NULL; - - git_error_clear(); - - git_error_set_oom(); /* internal fn */ - oom_error = git_error_last(); - cl_assert(oom_error); - - cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); - - cl_assert(git_error_last() == NULL); - cl_assert_equal_i(GIT_ERROR_NOMEMORY, err_state.error_msg.klass); - cl_assert_equal_s("Out of memory", err_state.error_msg.message); - - git_error_state_restore(&err_state); - - cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); - cl_assert_(git_error_last() == oom_error, "static oom error not restored"); - - git_error_clear(); -} - -static int test_arraysize_multiply(size_t nelem, size_t size) -{ - size_t out; - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&out, nelem, size); - return 0; -} - -void test_core_errors__integer_overflow_alloc_multiply(void) -{ - cl_git_pass(test_arraysize_multiply(10, 10)); - cl_git_pass(test_arraysize_multiply(1000, 1000)); - cl_git_pass(test_arraysize_multiply(SIZE_MAX/sizeof(void *), sizeof(void *))); - cl_git_pass(test_arraysize_multiply(0, 10)); - cl_git_pass(test_arraysize_multiply(10, 0)); - - cl_git_fail(test_arraysize_multiply(SIZE_MAX-1, sizeof(void *))); - cl_git_fail(test_arraysize_multiply((SIZE_MAX/sizeof(void *))+1, sizeof(void *))); - - cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); - cl_assert_equal_s("Out of memory", git_error_last()->message); -} - -static int test_arraysize_add(size_t one, size_t two) -{ - size_t out; - GIT_ERROR_CHECK_ALLOC_ADD(&out, one, two); - return 0; -} - -void test_core_errors__integer_overflow_alloc_add(void) -{ - cl_git_pass(test_arraysize_add(10, 10)); - cl_git_pass(test_arraysize_add(1000, 1000)); - cl_git_pass(test_arraysize_add(SIZE_MAX-10, 10)); - - cl_git_fail(test_arraysize_multiply(SIZE_MAX-1, 2)); - cl_git_fail(test_arraysize_multiply(SIZE_MAX, SIZE_MAX)); - - cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); - cl_assert_equal_s("Out of memory", git_error_last()->message); -} - -void test_core_errors__integer_overflow_sets_oom(void) -{ - size_t out; - - git_error_clear(); - cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX-1, 1)); - cl_assert_equal_p(NULL, git_error_last()); - - git_error_clear(); - cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, 42, 69)); - cl_assert_equal_p(NULL, git_error_last()); - - git_error_clear(); - cl_assert(GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX, SIZE_MAX)); - cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); - cl_assert_equal_s("Out of memory", git_error_last()->message); - - git_error_clear(); - cl_assert(GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX, SIZE_MAX)); - cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); - cl_assert_equal_s("Out of memory", git_error_last()->message); -} diff --git a/tests/libgit2/core/filebuf.c b/tests/libgit2/core/filebuf.c deleted file mode 100644 index 6f40c2456..000000000 --- a/tests/libgit2/core/filebuf.c +++ /dev/null @@ -1,267 +0,0 @@ -#include "clar_libgit2.h" -#include "filebuf.h" - -/* make sure git_filebuf_open doesn't delete an existing lock */ -void test_core_filebuf__0(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - int fd; - char test[] = "test", testlock[] = "test.lock"; - - fd = p_creat(testlock, 0744); /* -V536 */ - - cl_must_pass(fd); - cl_must_pass(p_close(fd)); - - cl_git_fail(git_filebuf_open(&file, test, 0, 0666)); - cl_assert(git_fs_path_exists(testlock)); - - cl_must_pass(p_unlink(testlock)); -} - - -/* make sure GIT_FILEBUF_APPEND works as expected */ -void test_core_filebuf__1(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - char test[] = "test"; - - cl_git_mkfile(test, "libgit2 rocks\n"); - - cl_git_pass(git_filebuf_open(&file, test, GIT_FILEBUF_APPEND, 0666)); - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - cl_git_pass(git_filebuf_commit(&file)); - - cl_assert_equal_file("libgit2 rocks\nlibgit2 rocks\n", 0, test); - - cl_must_pass(p_unlink(test)); -} - - -/* make sure git_filebuf_write writes large buffer correctly */ -void test_core_filebuf__2(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - char test[] = "test"; - unsigned char buf[4096 * 4]; /* 2 * WRITE_BUFFER_SIZE */ - - memset(buf, 0xfe, sizeof(buf)); - - cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); - cl_git_pass(git_filebuf_write(&file, buf, sizeof(buf))); - cl_git_pass(git_filebuf_commit(&file)); - - cl_assert_equal_file((char *)buf, sizeof(buf), test); - - cl_must_pass(p_unlink(test)); -} - -/* make sure git_filebuf_cleanup clears the buffer */ -void test_core_filebuf__4(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - char test[] = "test"; - - cl_assert(file.buffer == NULL); - - cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); - cl_assert(file.buffer != NULL); - - git_filebuf_cleanup(&file); - cl_assert(file.buffer == NULL); -} - - -/* make sure git_filebuf_commit clears the buffer */ -void test_core_filebuf__5(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - char test[] = "test"; - - cl_assert(file.buffer == NULL); - - cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); - cl_assert(file.buffer != NULL); - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - cl_assert(file.buffer != NULL); - - cl_git_pass(git_filebuf_commit(&file)); - cl_assert(file.buffer == NULL); - - cl_must_pass(p_unlink(test)); -} - - -/* make sure git_filebuf_commit takes umask into account */ -void test_core_filebuf__umask(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - char test[] = "test"; - struct stat statbuf; - mode_t mask, os_mask; - -#ifdef GIT_WIN32 - os_mask = 0600; -#else - os_mask = 0777; -#endif - - p_umask(mask = p_umask(0)); - - cl_assert(file.buffer == NULL); - - cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); - cl_assert(file.buffer != NULL); - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - cl_assert(file.buffer != NULL); - - cl_git_pass(git_filebuf_commit(&file)); - cl_assert(file.buffer == NULL); - - cl_must_pass(p_stat("test", &statbuf)); - cl_assert_equal_i(statbuf.st_mode & os_mask, (0666 & ~mask) & os_mask); - - cl_must_pass(p_unlink(test)); -} - -void test_core_filebuf__rename_error(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - char *dir = "subdir", *test = "subdir/test", *test_lock = "subdir/test.lock"; - int fd; - -#ifndef GIT_WIN32 - cl_skip(); -#endif - - cl_git_pass(p_mkdir(dir, 0666)); - cl_git_mkfile(test, "dummy content"); - fd = p_open(test, O_RDONLY); - cl_assert(fd > 0); - cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); - - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - - cl_assert_equal_i(true, git_fs_path_exists(test_lock)); - - cl_git_fail(git_filebuf_commit(&file)); - p_close(fd); - - git_filebuf_cleanup(&file); - - cl_assert_equal_i(false, git_fs_path_exists(test_lock)); -} - -void test_core_filebuf__symlink_follow(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - const char *dir = "linkdir", *source = "linkdir/link"; - - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - cl_skip(); - - cl_git_pass(p_mkdir(dir, 0777)); - cl_git_pass(p_symlink("target", source)); - - cl_git_pass(git_filebuf_open(&file, source, 0, 0666)); - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - - cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock")); - - cl_git_pass(git_filebuf_commit(&file)); - cl_assert_equal_i(true, git_fs_path_exists("linkdir/target")); - - git_filebuf_cleanup(&file); - - /* The second time around, the target file does exist */ - cl_git_pass(git_filebuf_open(&file, source, 0, 0666)); - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - - cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock")); - - cl_git_pass(git_filebuf_commit(&file)); - cl_assert_equal_i(true, git_fs_path_exists("linkdir/target")); - - git_filebuf_cleanup(&file); - cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_core_filebuf__symlink_follow_absolute_paths(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_str source = GIT_STR_INIT, target = GIT_STR_INIT; - - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - cl_skip(); - - cl_git_pass(git_str_joinpath(&source, clar_sandbox_path(), "linkdir/link")); - cl_git_pass(git_str_joinpath(&target, clar_sandbox_path(), "linkdir/target")); - cl_git_pass(p_mkdir("linkdir", 0777)); - cl_git_pass(p_symlink(target.ptr, source.ptr)); - - cl_git_pass(git_filebuf_open(&file, source.ptr, 0, 0666)); - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - - cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock")); - - cl_git_pass(git_filebuf_commit(&file)); - cl_assert_equal_i(true, git_fs_path_exists("linkdir/target")); - - git_filebuf_cleanup(&file); - git_str_dispose(&source); - git_str_dispose(&target); - - cl_git_pass(git_futils_rmdir_r("linkdir", NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_core_filebuf__symlink_depth(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - const char *dir = "linkdir", *source = "linkdir/link"; - - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - cl_skip(); - - cl_git_pass(p_mkdir(dir, 0777)); - /* Endless loop */ - cl_git_pass(p_symlink("link", source)); - - cl_git_fail(git_filebuf_open(&file, source, 0, 0666)); - - cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES)); -} - -void test_core_filebuf__hidden_file(void) -{ -#ifndef GIT_WIN32 - cl_skip(); -#else - git_filebuf file = GIT_FILEBUF_INIT; - char *dir = "hidden", *test = "hidden/test"; - bool hidden; - - cl_git_pass(p_mkdir(dir, 0666)); - cl_git_mkfile(test, "dummy content"); - - cl_git_pass(git_win32__set_hidden(test, true)); - cl_git_pass(git_win32__hidden(&hidden, test)); - cl_assert(hidden); - - cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); - - cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); - - cl_git_pass(git_filebuf_commit(&file)); - - git_filebuf_cleanup(&file); -#endif -} - -void test_core_filebuf__detects_directory(void) -{ - git_filebuf file = GIT_FILEBUF_INIT; - - cl_must_pass(p_mkdir("foo", 0777)); - cl_git_fail_with(GIT_EDIRECTORY, git_filebuf_open(&file, "foo", 0, 0666)); - cl_must_pass(p_rmdir("foo")); -} diff --git a/tests/libgit2/core/ftruncate.c b/tests/libgit2/core/ftruncate.c deleted file mode 100644 index 0c731cb1e..000000000 --- a/tests/libgit2/core/ftruncate.c +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Some tests for p_ftruncate() to ensure that - * properly handles large (2Gb+) files. - */ - -#include "clar_libgit2.h" - -static const char *filename = "core_ftruncate.txt"; -static int fd = -1; - -void test_core_ftruncate__initialize(void) -{ - if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE")) - cl_skip(); - - cl_must_pass((fd = p_open(filename, O_CREAT | O_RDWR, 0644))); -} - -void test_core_ftruncate__cleanup(void) -{ - if (fd < 0) - return; - - p_close(fd); - fd = 0; - - p_unlink(filename); -} - -static void _extend(off64_t i64len) -{ - struct stat st; - int error; - - cl_assert((error = p_ftruncate(fd, i64len)) == 0); - cl_assert((error = p_fstat(fd, &st)) == 0); - cl_assert(st.st_size == i64len); -} - -void test_core_ftruncate__2gb(void) -{ - _extend(0x80000001); -} - -void test_core_ftruncate__4gb(void) -{ - _extend(0x100000001); -} diff --git a/tests/libgit2/core/futils.c b/tests/libgit2/core/futils.c deleted file mode 100644 index 3501765f6..000000000 --- a/tests/libgit2/core/futils.c +++ /dev/null @@ -1,115 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" - -/* Fixture setup and teardown */ -void test_core_futils__initialize(void) -{ - cl_must_pass(p_mkdir("futils", 0777)); -} - -void test_core_futils__cleanup(void) -{ - cl_fixture_cleanup("futils"); -} - -void test_core_futils__writebuffer(void) -{ - git_str out = GIT_STR_INIT, - append = GIT_STR_INIT; - - /* create a new file */ - git_str_puts(&out, "hello!\n"); - git_str_printf(&out, "this is a %s\n", "test"); - - cl_git_pass(git_futils_writebuffer(&out, "futils/test-file", O_RDWR|O_CREAT, 0666)); - - cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); - - /* append some more data */ - git_str_puts(&append, "And some more!\n"); - git_str_put(&out, append.ptr, append.size); - - cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR|O_APPEND, 0666)); - - cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); - - git_str_dispose(&out); - git_str_dispose(&append); -} - -void test_core_futils__write_hidden_file(void) -{ -#ifndef GIT_WIN32 - cl_skip(); -#else - git_str out = GIT_STR_INIT, append = GIT_STR_INIT; - bool hidden; - - git_str_puts(&out, "hidden file.\n"); - git_futils_writebuffer(&out, "futils/test-file", O_RDWR | O_CREAT, 0666); - - cl_git_pass(git_win32__set_hidden("futils/test-file", true)); - - /* append some more data */ - git_str_puts(&append, "And some more!\n"); - git_str_put(&out, append.ptr, append.size); - - cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR | O_APPEND, 0666)); - - cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); - - cl_git_pass(git_win32__hidden(&hidden, "futils/test-file")); - cl_assert(hidden); - - git_str_dispose(&out); - git_str_dispose(&append); -#endif -} - -void test_core_futils__recursive_rmdir_keeps_symlink_targets(void) -{ - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - cl_skip(); - - cl_git_pass(git_futils_mkdir_r("a/b", 0777)); - cl_git_pass(git_futils_mkdir_r("dir-target", 0777)); - cl_git_mkfile("dir-target/file", "Contents"); - cl_git_mkfile("file-target", "Contents"); - cl_must_pass(p_symlink("dir-target", "a/symlink")); - cl_must_pass(p_symlink("file-target", "a/b/symlink")); - - cl_git_pass(git_futils_rmdir_r("a", NULL, GIT_RMDIR_REMOVE_FILES)); - - cl_assert(git_fs_path_exists("dir-target")); - cl_assert(git_fs_path_exists("file-target")); - - cl_must_pass(p_unlink("dir-target/file")); - cl_must_pass(p_rmdir("dir-target")); - cl_must_pass(p_unlink("file-target")); -} - -void test_core_futils__mktmp_umask(void) -{ -#ifdef GIT_WIN32 - cl_skip(); -#else - git_str path = GIT_STR_INIT; - struct stat st; - int fd; - - umask(0); - cl_assert((fd = git_futils_mktmp(&path, "foo", 0777)) >= 0); - cl_must_pass(p_fstat(fd, &st)); - cl_assert_equal_i(st.st_mode & 0777, 0777); - cl_must_pass(p_unlink(path.ptr)); - close(fd); - - umask(077); - cl_assert((fd = git_futils_mktmp(&path, "foo", 0777)) >= 0); - cl_must_pass(p_fstat(fd, &st)); - cl_assert_equal_i(st.st_mode & 0777, 0700); - cl_must_pass(p_unlink(path.ptr)); - close(fd); - git_str_dispose(&path); -#endif -} diff --git a/tests/libgit2/core/gitstr.c b/tests/libgit2/core/gitstr.c deleted file mode 100644 index 0a624e28c..000000000 --- a/tests/libgit2/core/gitstr.c +++ /dev/null @@ -1,1225 +0,0 @@ -#include "clar_libgit2.h" -#include "git2/sys/hashsig.h" -#include "futils.h" - -#define TESTSTR "Have you seen that? Have you seeeen that??" -const char *test_string = TESTSTR; -const char *test_string_x2 = TESTSTR TESTSTR; - -#define TESTSTR_4096 REP1024("1234") -#define TESTSTR_8192 REP1024("12341234") -const char *test_4096 = TESTSTR_4096; -const char *test_8192 = TESTSTR_8192; - -/* test basic data concatenation */ -void test_core_gitstr__0(void) -{ - git_str buf = GIT_STR_INIT; - - cl_assert(buf.size == 0); - - git_str_puts(&buf, test_string); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_string, git_str_cstr(&buf)); - - git_str_puts(&buf, test_string); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_string_x2, git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -/* test git_str_printf */ -void test_core_gitstr__1(void) -{ - git_str buf = GIT_STR_INIT; - - git_str_printf(&buf, "%s %s %d ", "shoop", "da", 23); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s("shoop da 23 ", git_str_cstr(&buf)); - - git_str_printf(&buf, "%s %d", "woop", 42); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s("shoop da 23 woop 42", git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -/* more thorough test of concatenation options */ -void test_core_gitstr__2(void) -{ - git_str buf = GIT_STR_INIT; - int i; - char data[128]; - - cl_assert(buf.size == 0); - - /* this must be safe to do */ - git_str_dispose(&buf); - cl_assert(buf.size == 0); - cl_assert(buf.asize == 0); - - /* empty buffer should be empty string */ - cl_assert_equal_s("", git_str_cstr(&buf)); - cl_assert(buf.size == 0); - /* cl_assert(buf.asize == 0); -- should not assume what git_str does */ - - /* free should set us back to the beginning */ - git_str_dispose(&buf); - cl_assert(buf.size == 0); - cl_assert(buf.asize == 0); - - /* add letter */ - git_str_putc(&buf, '+'); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s("+", git_str_cstr(&buf)); - - /* add letter again */ - git_str_putc(&buf, '+'); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s("++", git_str_cstr(&buf)); - - /* let's try that a few times */ - for (i = 0; i < 16; ++i) { - git_str_putc(&buf, '+'); - cl_assert(git_str_oom(&buf) == 0); - } - cl_assert_equal_s("++++++++++++++++++", git_str_cstr(&buf)); - - git_str_dispose(&buf); - - /* add data */ - git_str_put(&buf, "xo", 2); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s("xo", git_str_cstr(&buf)); - - /* add letter again */ - git_str_put(&buf, "xo", 2); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s("xoxo", git_str_cstr(&buf)); - - /* let's try that a few times */ - for (i = 0; i < 16; ++i) { - git_str_put(&buf, "xo", 2); - cl_assert(git_str_oom(&buf) == 0); - } - cl_assert_equal_s("xoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxo", - git_str_cstr(&buf)); - - git_str_dispose(&buf); - - /* set to string */ - git_str_sets(&buf, test_string); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_string, git_str_cstr(&buf)); - - /* append string */ - git_str_puts(&buf, test_string); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_string_x2, git_str_cstr(&buf)); - - /* set to string again (should overwrite - not append) */ - git_str_sets(&buf, test_string); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_string, git_str_cstr(&buf)); - - /* test clear */ - git_str_clear(&buf); - cl_assert_equal_s("", git_str_cstr(&buf)); - - git_str_dispose(&buf); - - /* test extracting data into buffer */ - git_str_puts(&buf, REP4("0123456789")); - cl_assert(git_str_oom(&buf) == 0); - - git_str_copy_cstr(data, sizeof(data), &buf); - cl_assert_equal_s(REP4("0123456789"), data); - git_str_copy_cstr(data, 11, &buf); - cl_assert_equal_s("0123456789", data); - git_str_copy_cstr(data, 3, &buf); - cl_assert_equal_s("01", data); - git_str_copy_cstr(data, 1, &buf); - cl_assert_equal_s("", data); - - git_str_copy_cstr(data, sizeof(data), &buf); - cl_assert_equal_s(REP4("0123456789"), data); - - git_str_sets(&buf, REP256("x")); - git_str_copy_cstr(data, sizeof(data), &buf); - /* since sizeof(data) == 128, only 127 bytes should be copied */ - cl_assert_equal_s(REP4(REP16("x")) REP16("x") REP16("x") - REP16("x") "xxxxxxxxxxxxxxx", data); - - git_str_dispose(&buf); - - git_str_copy_cstr(data, sizeof(data), &buf); - cl_assert_equal_s("", data); -} - -/* let's do some tests with larger buffers to push our limits */ -void test_core_gitstr__3(void) -{ - git_str buf = GIT_STR_INIT; - - /* set to string */ - git_str_set(&buf, test_4096, 4096); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_4096, git_str_cstr(&buf)); - - /* append string */ - git_str_puts(&buf, test_4096); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_8192, git_str_cstr(&buf)); - - /* set to string again (should overwrite - not append) */ - git_str_set(&buf, test_4096, 4096); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(test_4096, git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -/* let's try some producer/consumer tests */ -void test_core_gitstr__4(void) -{ - git_str buf = GIT_STR_INIT; - int i; - - for (i = 0; i < 10; ++i) { - git_str_puts(&buf, "1234"); /* add 4 */ - cl_assert(git_str_oom(&buf) == 0); - git_str_consume(&buf, buf.ptr + 2); /* eat the first two */ - cl_assert(strlen(git_str_cstr(&buf)) == (size_t)((i + 1) * 2)); - } - /* we have appended 1234 10x and removed the first 20 letters */ - cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); - - git_str_consume(&buf, NULL); - cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); - - git_str_consume(&buf, "invalid pointer"); - cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); - - git_str_consume(&buf, buf.ptr); - cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); - - git_str_consume(&buf, buf.ptr + 1); - cl_assert_equal_s("2341234123412341234", git_str_cstr(&buf)); - - git_str_consume(&buf, buf.ptr + buf.size); - cl_assert_equal_s("", git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - - -static void -check_buf_append( - const char* data_a, - const char* data_b, - const char* expected_data, - size_t expected_size, - size_t expected_asize) -{ - git_str tgt = GIT_STR_INIT; - - git_str_sets(&tgt, data_a); - cl_assert(git_str_oom(&tgt) == 0); - git_str_puts(&tgt, data_b); - cl_assert(git_str_oom(&tgt) == 0); - cl_assert_equal_s(expected_data, git_str_cstr(&tgt)); - cl_assert_equal_i(tgt.size, expected_size); - if (expected_asize > 0) - cl_assert_equal_i(tgt.asize, expected_asize); - - git_str_dispose(&tgt); -} - -static void -check_buf_append_abc( - const char* buf_a, - const char* buf_b, - const char* buf_c, - const char* expected_ab, - const char* expected_abc, - const char* expected_abca, - const char* expected_abcab, - const char* expected_abcabc) -{ - git_str buf = GIT_STR_INIT; - - git_str_sets(&buf, buf_a); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(buf_a, git_str_cstr(&buf)); - - git_str_puts(&buf, buf_b); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected_ab, git_str_cstr(&buf)); - - git_str_puts(&buf, buf_c); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected_abc, git_str_cstr(&buf)); - - git_str_puts(&buf, buf_a); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected_abca, git_str_cstr(&buf)); - - git_str_puts(&buf, buf_b); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected_abcab, git_str_cstr(&buf)); - - git_str_puts(&buf, buf_c); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected_abcabc, git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -/* more variations on append tests */ -void test_core_gitstr__5(void) -{ - check_buf_append("", "", "", 0, 0); - check_buf_append("a", "", "a", 1, 0); - check_buf_append("", "a", "a", 1, 8); - check_buf_append("", "a", "a", 1, 8); - check_buf_append("a", "b", "ab", 2, 8); - check_buf_append("", "abcdefgh", "abcdefgh", 8, 16); - check_buf_append("abcdefgh", "", "abcdefgh", 8, 16); - - /* buffer with starting asize will grow to: - * 1 -> 2, 2 -> 3, 3 -> 5, 4 -> 6, 5 -> 8, 6 -> 9, - * 7 -> 11, 8 -> 12, 9 -> 14, 10 -> 15, 11 -> 17, 12 -> 18, - * 13 -> 20, 14 -> 21, 15 -> 23, 16 -> 24, 17 -> 26, 18 -> 27, - * 19 -> 29, 20 -> 30, 21 -> 32, 22 -> 33, 23 -> 35, 24 -> 36, - * ... - * follow sequence until value > target size, - * then round up to nearest multiple of 8. - */ - - check_buf_append("abcdefgh", "/", "abcdefgh/", 9, 16); - check_buf_append("abcdefgh", "ijklmno", "abcdefghijklmno", 15, 16); - check_buf_append("abcdefgh", "ijklmnop", "abcdefghijklmnop", 16, 24); - check_buf_append("0123456789", "0123456789", - "01234567890123456789", 20, 24); - check_buf_append(REP16("x"), REP16("o"), - REP16("x") REP16("o"), 32, 40); - - check_buf_append(test_4096, "", test_4096, 4096, 4104); - check_buf_append(test_4096, test_4096, test_8192, 8192, 8200); - - /* check sequences of appends */ - check_buf_append_abc("a", "b", "c", - "ab", "abc", "abca", "abcab", "abcabc"); - check_buf_append_abc("a1", "b2", "c3", - "a1b2", "a1b2c3", "a1b2c3a1", - "a1b2c3a1b2", "a1b2c3a1b2c3"); - check_buf_append_abc("a1/", "b2/", "c3/", - "a1/b2/", "a1/b2/c3/", "a1/b2/c3/a1/", - "a1/b2/c3/a1/b2/", "a1/b2/c3/a1/b2/c3/"); -} - -/* test swap */ -void test_core_gitstr__6(void) -{ - git_str a = GIT_STR_INIT; - git_str b = GIT_STR_INIT; - - git_str_sets(&a, "foo"); - cl_assert(git_str_oom(&a) == 0); - git_str_sets(&b, "bar"); - cl_assert(git_str_oom(&b) == 0); - - cl_assert_equal_s("foo", git_str_cstr(&a)); - cl_assert_equal_s("bar", git_str_cstr(&b)); - - git_str_swap(&a, &b); - - cl_assert_equal_s("bar", git_str_cstr(&a)); - cl_assert_equal_s("foo", git_str_cstr(&b)); - - git_str_dispose(&a); - git_str_dispose(&b); -} - - -/* test detach/attach data */ -void test_core_gitstr__7(void) -{ - const char *fun = "This is fun"; - git_str a = GIT_STR_INIT; - char *b = NULL; - - git_str_sets(&a, "foo"); - cl_assert(git_str_oom(&a) == 0); - cl_assert_equal_s("foo", git_str_cstr(&a)); - - b = git_str_detach(&a); - - cl_assert_equal_s("foo", b); - cl_assert_equal_s("", a.ptr); - git__free(b); - - b = git_str_detach(&a); - - cl_assert_equal_s(NULL, b); - cl_assert_equal_s("", a.ptr); - - git_str_dispose(&a); - - b = git__strdup(fun); - git_str_attach(&a, b, 0); - - cl_assert_equal_s(fun, a.ptr); - cl_assert(a.size == strlen(fun)); - cl_assert(a.asize == strlen(fun) + 1); - - git_str_dispose(&a); - - b = git__strdup(fun); - git_str_attach(&a, b, strlen(fun) + 1); - - cl_assert_equal_s(fun, a.ptr); - cl_assert(a.size == strlen(fun)); - cl_assert(a.asize == strlen(fun) + 1); - - git_str_dispose(&a); -} - - -static void -check_joinbuf_2( - const char *a, - const char *b, - const char *expected) -{ - char sep = '/'; - git_str buf = GIT_STR_INIT; - - git_str_join(&buf, sep, a, b); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected, git_str_cstr(&buf)); - git_str_dispose(&buf); -} - -static void -check_joinbuf_overlapped( - const char *oldval, - int ofs_a, - const char *b, - const char *expected) -{ - char sep = '/'; - git_str buf = GIT_STR_INIT; - - git_str_sets(&buf, oldval); - git_str_join(&buf, sep, buf.ptr + ofs_a, b); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected, git_str_cstr(&buf)); - git_str_dispose(&buf); -} - -static void -check_joinbuf_n_2( - const char *a, - const char *b, - const char *expected) -{ - char sep = '/'; - git_str buf = GIT_STR_INIT; - - git_str_sets(&buf, a); - cl_assert(git_str_oom(&buf) == 0); - - git_str_join_n(&buf, sep, 1, b); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected, git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -static void -check_joinbuf_n_4( - const char *a, - const char *b, - const char *c, - const char *d, - const char *expected) -{ - char sep = ';'; - git_str buf = GIT_STR_INIT; - git_str_join_n(&buf, sep, 4, a, b, c, d); - cl_assert(git_str_oom(&buf) == 0); - cl_assert_equal_s(expected, git_str_cstr(&buf)); - git_str_dispose(&buf); -} - -/* test join */ -void test_core_gitstr__8(void) -{ - git_str a = GIT_STR_INIT; - - git_str_join_n(&a, '/', 1, "foo"); - cl_assert(git_str_oom(&a) == 0); - cl_assert_equal_s("foo", git_str_cstr(&a)); - - git_str_join_n(&a, '/', 1, "bar"); - cl_assert(git_str_oom(&a) == 0); - cl_assert_equal_s("foo/bar", git_str_cstr(&a)); - - git_str_join_n(&a, '/', 1, "baz"); - cl_assert(git_str_oom(&a) == 0); - cl_assert_equal_s("foo/bar/baz", git_str_cstr(&a)); - - git_str_dispose(&a); - - check_joinbuf_2(NULL, "", ""); - check_joinbuf_2(NULL, "a", "a"); - check_joinbuf_2(NULL, "/a", "/a"); - check_joinbuf_2("", "", ""); - check_joinbuf_2("", "a", "a"); - check_joinbuf_2("", "/a", "/a"); - check_joinbuf_2("a", "", "a/"); - check_joinbuf_2("a", "/", "a/"); - check_joinbuf_2("a", "b", "a/b"); - check_joinbuf_2("/", "a", "/a"); - check_joinbuf_2("/", "", "/"); - check_joinbuf_2("/a", "/b", "/a/b"); - check_joinbuf_2("/a", "/b/", "/a/b/"); - check_joinbuf_2("/a/", "b/", "/a/b/"); - check_joinbuf_2("/a/", "/b/", "/a/b/"); - check_joinbuf_2("/a/", "//b/", "/a/b/"); - check_joinbuf_2("/abcd", "/defg", "/abcd/defg"); - check_joinbuf_2("/abcd", "/defg/", "/abcd/defg/"); - check_joinbuf_2("/abcd/", "defg/", "/abcd/defg/"); - check_joinbuf_2("/abcd/", "/defg/", "/abcd/defg/"); - - check_joinbuf_overlapped("abcd", 0, "efg", "abcd/efg"); - check_joinbuf_overlapped("abcd", 1, "efg", "bcd/efg"); - check_joinbuf_overlapped("abcd", 2, "efg", "cd/efg"); - check_joinbuf_overlapped("abcd", 3, "efg", "d/efg"); - check_joinbuf_overlapped("abcd", 4, "efg", "efg"); - check_joinbuf_overlapped("abc/", 2, "efg", "c/efg"); - check_joinbuf_overlapped("abc/", 3, "efg", "/efg"); - check_joinbuf_overlapped("abc/", 4, "efg", "efg"); - check_joinbuf_overlapped("abcd", 3, "", "d/"); - check_joinbuf_overlapped("abcd", 4, "", ""); - check_joinbuf_overlapped("abc/", 2, "", "c/"); - check_joinbuf_overlapped("abc/", 3, "", "/"); - check_joinbuf_overlapped("abc/", 4, "", ""); - - check_joinbuf_n_2("", "", ""); - check_joinbuf_n_2("", "a", "a"); - check_joinbuf_n_2("", "/a", "/a"); - check_joinbuf_n_2("a", "", "a/"); - check_joinbuf_n_2("a", "/", "a/"); - check_joinbuf_n_2("a", "b", "a/b"); - check_joinbuf_n_2("/", "a", "/a"); - check_joinbuf_n_2("/", "", "/"); - check_joinbuf_n_2("/a", "/b", "/a/b"); - check_joinbuf_n_2("/a", "/b/", "/a/b/"); - check_joinbuf_n_2("/a/", "b/", "/a/b/"); - check_joinbuf_n_2("/a/", "/b/", "/a/b/"); - check_joinbuf_n_2("/abcd", "/defg", "/abcd/defg"); - check_joinbuf_n_2("/abcd", "/defg/", "/abcd/defg/"); - check_joinbuf_n_2("/abcd/", "defg/", "/abcd/defg/"); - check_joinbuf_n_2("/abcd/", "/defg/", "/abcd/defg/"); - - check_joinbuf_n_4("", "", "", "", ""); - check_joinbuf_n_4("", "a", "", "", "a;"); - check_joinbuf_n_4("a", "", "", "", "a;"); - check_joinbuf_n_4("", "", "", "a", "a"); - check_joinbuf_n_4("a", "b", "", ";c;d;", "a;b;c;d;"); - check_joinbuf_n_4("a", "b", "", ";c;d", "a;b;c;d"); - check_joinbuf_n_4("abcd", "efgh", "ijkl", "mnop", "abcd;efgh;ijkl;mnop"); - check_joinbuf_n_4("abcd;", "efgh;", "ijkl;", "mnop;", "abcd;efgh;ijkl;mnop;"); - check_joinbuf_n_4(";abcd;", ";efgh;", ";ijkl;", ";mnop;", ";abcd;efgh;ijkl;mnop;"); -} - -void test_core_gitstr__9(void) -{ - git_str buf = GIT_STR_INIT; - - /* just some exhaustive tests of various separator placement */ - char *a[] = { "", "-", "a-", "-a", "-a-" }; - char *b[] = { "", "-", "b-", "-b", "-b-" }; - char sep[] = { 0, '-', '/' }; - char *expect_null[] = { "", "-", "a-", "-a", "-a-", - "-", "--", "a--", "-a-", "-a--", - "b-", "-b-", "a-b-", "-ab-", "-a-b-", - "-b", "--b", "a--b", "-a-b", "-a--b", - "-b-", "--b-", "a--b-", "-a-b-", "-a--b-" }; - char *expect_dash[] = { "", "-", "a-", "-a-", "-a-", - "-", "-", "a-", "-a-", "-a-", - "b-", "-b-", "a-b-", "-a-b-", "-a-b-", - "-b", "-b", "a-b", "-a-b", "-a-b", - "-b-", "-b-", "a-b-", "-a-b-", "-a-b-" }; - char *expect_slas[] = { "", "-/", "a-/", "-a/", "-a-/", - "-", "-/-", "a-/-", "-a/-", "-a-/-", - "b-", "-/b-", "a-/b-", "-a/b-", "-a-/b-", - "-b", "-/-b", "a-/-b", "-a/-b", "-a-/-b", - "-b-", "-/-b-", "a-/-b-", "-a/-b-", "-a-/-b-" }; - char **expect_values[] = { expect_null, expect_dash, expect_slas }; - char separator, **expect; - unsigned int s, i, j; - - for (s = 0; s < sizeof(sep) / sizeof(char); ++s) { - separator = sep[s]; - expect = expect_values[s]; - - for (j = 0; j < sizeof(b) / sizeof(char*); ++j) { - for (i = 0; i < sizeof(a) / sizeof(char*); ++i) { - git_str_join(&buf, separator, a[i], b[j]); - cl_assert_equal_s(*expect, buf.ptr); - expect++; - } - } - } - - git_str_dispose(&buf); -} - -void test_core_gitstr__10(void) -{ - git_str a = GIT_STR_INIT; - - cl_git_pass(git_str_join_n(&a, '/', 1, "test")); - cl_assert_equal_s(a.ptr, "test"); - cl_git_pass(git_str_join_n(&a, '/', 1, "string")); - cl_assert_equal_s(a.ptr, "test/string"); - git_str_clear(&a); - cl_git_pass(git_str_join_n(&a, '/', 3, "test", "string", "join")); - cl_assert_equal_s(a.ptr, "test/string/join"); - cl_git_pass(git_str_join_n(&a, '/', 2, a.ptr, "more")); - cl_assert_equal_s(a.ptr, "test/string/join/test/string/join/more"); - - git_str_dispose(&a); -} - -void test_core_gitstr__join3(void) -{ - git_str a = GIT_STR_INIT; - - cl_git_pass(git_str_join3(&a, '/', "test", "string", "join")); - cl_assert_equal_s("test/string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "test/", "string", "join")); - cl_assert_equal_s("test/string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "test/", "/string", "join")); - cl_assert_equal_s("test/string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "test/", "/string/", "join")); - cl_assert_equal_s("test/string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "test/", "/string/", "/join")); - cl_assert_equal_s("test/string/join", a.ptr); - - cl_git_pass(git_str_join3(&a, '/', "", "string", "join")); - cl_assert_equal_s("string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "", "string/", "join")); - cl_assert_equal_s("string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "", "string/", "/join")); - cl_assert_equal_s("string/join", a.ptr); - - cl_git_pass(git_str_join3(&a, '/', "string", "", "join")); - cl_assert_equal_s("string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "string/", "", "join")); - cl_assert_equal_s("string/join", a.ptr); - cl_git_pass(git_str_join3(&a, '/', "string/", "", "/join")); - cl_assert_equal_s("string/join", a.ptr); - - git_str_dispose(&a); -} - -void test_core_gitstr__11(void) -{ - git_str a = GIT_STR_INIT; - char *t1[] = { "nothing", "in", "common" }; - char *t2[] = { "something", "something else", "some other" }; - char *t3[] = { "something", "some fun", "no fun" }; - char *t4[] = { "happy", "happier", "happiest" }; - char *t5[] = { "happiest", "happier", "happy" }; - char *t6[] = { "no", "nope", "" }; - char *t7[] = { "", "doesn't matter" }; - - cl_git_pass(git_str_common_prefix(&a, t1, 3)); - cl_assert_equal_s(a.ptr, ""); - - cl_git_pass(git_str_common_prefix(&a, t2, 3)); - cl_assert_equal_s(a.ptr, "some"); - - cl_git_pass(git_str_common_prefix(&a, t3, 3)); - cl_assert_equal_s(a.ptr, ""); - - cl_git_pass(git_str_common_prefix(&a, t4, 3)); - cl_assert_equal_s(a.ptr, "happ"); - - cl_git_pass(git_str_common_prefix(&a, t5, 3)); - cl_assert_equal_s(a.ptr, "happ"); - - cl_git_pass(git_str_common_prefix(&a, t6, 3)); - cl_assert_equal_s(a.ptr, ""); - - cl_git_pass(git_str_common_prefix(&a, t7, 3)); - cl_assert_equal_s(a.ptr, ""); - - git_str_dispose(&a); -} - -void test_core_gitstr__rfind_variants(void) -{ - git_str a = GIT_STR_INIT; - ssize_t len; - - cl_git_pass(git_str_sets(&a, "/this/is/it/")); - - len = (ssize_t)git_str_len(&a); - - cl_assert(git_str_rfind(&a, '/') == len - 1); - cl_assert(git_str_rfind_next(&a, '/') == len - 4); - - cl_assert(git_str_rfind(&a, 'i') == len - 3); - cl_assert(git_str_rfind_next(&a, 'i') == len - 3); - - cl_assert(git_str_rfind(&a, 'h') == 2); - cl_assert(git_str_rfind_next(&a, 'h') == 2); - - cl_assert(git_str_rfind(&a, 'q') == -1); - cl_assert(git_str_rfind_next(&a, 'q') == -1); - - git_str_dispose(&a); -} - -void test_core_gitstr__puts_escaped(void) -{ - git_str a = GIT_STR_INIT; - - git_str_clear(&a); - cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", "")); - cl_assert_equal_s("this is a test", a.ptr); - - git_str_clear(&a); - cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\")); - cl_assert_equal_s("\\this is a \\tes\\t", a.ptr); - - git_str_clear(&a); - cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__")); - cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr); - - git_str_clear(&a); - cl_git_pass(git_str_puts_escape_regex(&a, "^match\\s*[A-Z]+.*")); - cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr); - - git_str_dispose(&a); -} - -static void assert_unescape(char *expected, char *to_unescape) { - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_sets(&buf, to_unescape)); - git_str_unescape(&buf); - cl_assert_equal_s(expected, buf.ptr); - cl_assert_equal_sz(strlen(expected), buf.size); - - git_str_dispose(&buf); -} - -void test_core_gitstr__unescape(void) -{ - assert_unescape("Escaped\\", "Es\\ca\\ped\\"); - assert_unescape("Es\\caped\\", "Es\\\\ca\\ped\\\\"); - assert_unescape("\\", "\\"); - assert_unescape("\\", "\\\\"); - assert_unescape("", ""); -} - -void test_core_gitstr__encode_base64(void) -{ - git_str buf = GIT_STR_INIT; - - /* t h i s - * 0x 74 68 69 73 - * 0b 01110100 01101000 01101001 01110011 - * 0b 011101 000110 100001 101001 011100 110000 - * 0x 1d 06 21 29 1c 30 - * d G h p c w - */ - cl_git_pass(git_str_encode_base64(&buf, "this", 4)); - cl_assert_equal_s("dGhpcw==", buf.ptr); - - git_str_clear(&buf); - cl_git_pass(git_str_encode_base64(&buf, "this!", 5)); - cl_assert_equal_s("dGhpcyE=", buf.ptr); - - git_str_clear(&buf); - cl_git_pass(git_str_encode_base64(&buf, "this!\n", 6)); - cl_assert_equal_s("dGhpcyEK", buf.ptr); - - git_str_dispose(&buf); -} - -void test_core_gitstr__decode_base64(void) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_decode_base64(&buf, "dGhpcw==", 8)); - cl_assert_equal_s("this", buf.ptr); - - git_str_clear(&buf); - cl_git_pass(git_str_decode_base64(&buf, "dGhpcyE=", 8)); - cl_assert_equal_s("this!", buf.ptr); - - git_str_clear(&buf); - cl_git_pass(git_str_decode_base64(&buf, "dGhpcyEK", 8)); - cl_assert_equal_s("this!\n", buf.ptr); - - cl_git_fail(git_str_decode_base64(&buf, "This is not a valid base64 string!!!", 36)); - cl_assert_equal_s("this!\n", buf.ptr); - - git_str_dispose(&buf); -} - -void test_core_gitstr__encode_base85(void) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_encode_base85(&buf, "this", 4)); - cl_assert_equal_s("bZBXF", buf.ptr); - git_str_clear(&buf); - - cl_git_pass(git_str_encode_base85(&buf, "two rnds", 8)); - cl_assert_equal_s("ba!tca&BaE", buf.ptr); - git_str_clear(&buf); - - cl_git_pass(git_str_encode_base85(&buf, "this is base 85 encoded", - strlen("this is base 85 encoded"))); - cl_assert_equal_s("bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", buf.ptr); - git_str_clear(&buf); - - git_str_dispose(&buf); -} - -void test_core_gitstr__decode_base85(void) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_decode_base85(&buf, "bZBXF", 5, 4)); - cl_assert_equal_sz(4, buf.size); - cl_assert_equal_s("this", buf.ptr); - git_str_clear(&buf); - - cl_git_pass(git_str_decode_base85(&buf, "ba!tca&BaE", 10, 8)); - cl_assert_equal_sz(8, buf.size); - cl_assert_equal_s("two rnds", buf.ptr); - git_str_clear(&buf); - - cl_git_pass(git_str_decode_base85(&buf, "bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", 30, 23)); - cl_assert_equal_sz(23, buf.size); - cl_assert_equal_s("this is base 85 encoded", buf.ptr); - git_str_clear(&buf); - - git_str_dispose(&buf); -} - -void test_core_gitstr__decode_base85_fails_gracefully(void) -{ - git_str buf = GIT_STR_INIT; - - git_str_puts(&buf, "foobar"); - - cl_git_fail(git_str_decode_base85(&buf, "invalid charsZZ", 15, 42)); - cl_git_fail(git_str_decode_base85(&buf, "invalidchars__ ", 15, 42)); - cl_git_fail(git_str_decode_base85(&buf, "overflowZZ~~~~~", 15, 42)); - cl_git_fail(git_str_decode_base85(&buf, "truncated", 9, 42)); - cl_assert_equal_sz(6, buf.size); - cl_assert_equal_s("foobar", buf.ptr); - - git_str_dispose(&buf); -} - -void test_core_gitstr__classify_with_utf8(void) -{ - char *data0 = "Simple text\n"; - size_t data0len = 12; - char *data1 = "Is that UTF-8 data I see…\nYep!\n"; - size_t data1len = 31; - char *data2 = "Internal NUL!!!\000\n\nI see you!\n"; - size_t data2len = 29; - char *data3 = "\xef\xbb\xbfThis is UTF-8 with a BOM.\n"; - size_t data3len = 20; - git_str b; - - b.ptr = data0; b.size = b.asize = data0len; - cl_assert(!git_str_is_binary(&b)); - cl_assert(!git_str_contains_nul(&b)); - - b.ptr = data1; b.size = b.asize = data1len; - cl_assert(!git_str_is_binary(&b)); - cl_assert(!git_str_contains_nul(&b)); - - b.ptr = data2; b.size = b.asize = data2len; - cl_assert(git_str_is_binary(&b)); - cl_assert(git_str_contains_nul(&b)); - - b.ptr = data3; b.size = b.asize = data3len; - cl_assert(!git_str_is_binary(&b)); - cl_assert(!git_str_contains_nul(&b)); -} - -#define SIMILARITY_TEST_DATA_1 \ - "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ - "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ - "020\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \ - "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \ - "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n" - -void test_core_gitstr__similarity_metric(void) -{ - git_hashsig *a, *b; - git_str buf = GIT_STR_INIT; - int sim; - - /* in the first case, we compare data to itself and expect 100% match */ - - cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); - cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - - cl_assert_equal_i(100, git_hashsig_compare(a, b)); - - git_hashsig_free(a); - git_hashsig_free(b); - - /* if we change just a single byte, how much does that change magnify? */ - - cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); - cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - cl_git_pass(git_str_sets(&buf, - "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ - "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ - "x020x\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \ - "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \ - "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n" - )); - cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - - sim = git_hashsig_compare(a, b); - - cl_assert_in_range(95, sim, 100); /* expect >95% similarity */ - - git_hashsig_free(a); - git_hashsig_free(b); - - /* let's try comparing data to a superset of itself */ - - cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); - cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1 - "050\n051\n052\n053\n054\n055\n056\n057\n058\n059\n")); - cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - - sim = git_hashsig_compare(a, b); - /* 20% lines added ~= 10% lines changed */ - - cl_assert_in_range(85, sim, 95); /* expect similarity around 90% */ - - git_hashsig_free(a); - git_hashsig_free(b); - - /* what if we keep about half the original data and add half new */ - - cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); - cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - cl_git_pass(git_str_sets(&buf, - "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ - "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ - "020x\n021\n022\n023\n024\n" \ - "x25\nx26\nx27\nx28\nx29\n" \ - "x30\nx31\nx32\nx33\nx34\nx35\nx36\nx37\nx38\nx39\n" \ - "x40\nx41\nx42\nx43\nx44\nx45\nx46\nx47\nx48\nx49\n" - )); - cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - - sim = git_hashsig_compare(a, b); - /* 50% lines changed */ - - cl_assert_in_range(40, sim, 60); /* expect in the 40-60% similarity range */ - - git_hashsig_free(a); - git_hashsig_free(b); - - /* lastly, let's check that we can hash file content as well */ - - cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); - cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - - cl_git_pass(git_futils_mkdir("scratch", 0755, GIT_MKDIR_PATH)); - cl_git_mkfile("scratch/testdata", SIMILARITY_TEST_DATA_1); - cl_git_pass(git_hashsig_create_fromfile( - &b, "scratch/testdata", GIT_HASHSIG_NORMAL)); - - cl_assert_equal_i(100, git_hashsig_compare(a, b)); - - git_hashsig_free(a); - git_hashsig_free(b); - - git_str_dispose(&buf); - git_futils_rmdir_r("scratch", NULL, GIT_RMDIR_REMOVE_FILES); -} - - -void test_core_gitstr__similarity_metric_whitespace(void) -{ - git_hashsig *a, *b; - git_str buf = GIT_STR_INIT; - int sim, i, j; - git_hashsig_option_t opt; - const char *tabbed = - " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\n" - " separator = sep[s];\n" - " expect = expect_values[s];\n" - "\n" - " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\n" - " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\n" - " git_str_join(&buf, separator, a[i], b[j]);\n" - " cl_assert_equal_s(*expect, buf.ptr);\n" - " expect++;\n" - " }\n" - " }\n" - " }\n"; - const char *spaced = - " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\n" - " separator = sep[s];\n" - " expect = expect_values[s];\n" - "\n" - " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\n" - " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\n" - " git_str_join(&buf, separator, a[i], b[j]);\n" - " cl_assert_equal_s(*expect, buf.ptr);\n" - " expect++;\n" - " }\n" - " }\n" - " }\n"; - const char *crlf_spaced2 = - " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\r\n" - " separator = sep[s];\r\n" - " expect = expect_values[s];\r\n" - "\r\n" - " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\r\n" - " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\r\n" - " git_str_join(&buf, separator, a[i], b[j]);\r\n" - " cl_assert_equal_s(*expect, buf.ptr);\r\n" - " expect++;\r\n" - " }\r\n" - " }\r\n" - " }\r\n"; - const char *text[3] = { tabbed, spaced, crlf_spaced2 }; - - /* let's try variations of our own code with whitespace changes */ - - for (opt = GIT_HASHSIG_NORMAL; opt <= GIT_HASHSIG_SMART_WHITESPACE; ++opt) { - for (i = 0; i < 3; ++i) { - for (j = 0; j < 3; ++j) { - cl_git_pass(git_str_sets(&buf, text[i])); - cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, opt)); - - cl_git_pass(git_str_sets(&buf, text[j])); - cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, opt)); - - sim = git_hashsig_compare(a, b); - - if (opt == GIT_HASHSIG_NORMAL) { - if (i == j) - cl_assert_equal_i(100, sim); - else - cl_assert_in_range(0, sim, 30); /* pretty different */ - } else { - cl_assert_equal_i(100, sim); - } - - git_hashsig_free(a); - git_hashsig_free(b); - } - } - } - - git_str_dispose(&buf); -} - -#include "../filter/crlf.h" - -#define check_buf(expected,buf) do { \ - cl_assert_equal_s(expected, buf.ptr); \ - cl_assert_equal_sz(strlen(expected), buf.size); } while (0) - -void test_core_gitstr__lf_and_crlf_conversions(void) -{ - git_str src = GIT_STR_INIT, tgt = GIT_STR_INIT; - - /* LF source */ - - git_str_sets(&src, "lf\nlf\nlf\nlf\n"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("lf\r\nlf\r\nlf\r\nlf\r\n", tgt); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf(src.ptr, tgt); - - git_str_sets(&src, "\nlf\nlf\nlf\nlf\nlf"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("\r\nlf\r\nlf\r\nlf\r\nlf\r\nlf", tgt); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf(src.ptr, tgt); - - /* CRLF source */ - - git_str_sets(&src, "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n", tgt); - - git_str_sets(&src, "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n"); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf("crlf\ncrlf\ncrlf\ncrlf\n", tgt); - - git_str_sets(&src, "\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf", tgt); - - git_str_sets(&src, "\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf"); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf("\ncrlf\ncrlf\ncrlf\ncrlf\ncrlf", tgt); - - /* CRLF in LF text */ - - git_str_sets(&src, "\nlf\nlf\ncrlf\r\nlf\nlf\ncrlf\r\n"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("\r\nlf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\ncrlf\r\n", tgt); - - git_str_sets(&src, "\nlf\nlf\ncrlf\r\nlf\nlf\ncrlf\r\n"); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf("\nlf\nlf\ncrlf\nlf\nlf\ncrlf\n", tgt); - - /* LF in CRLF text */ - - git_str_sets(&src, "\ncrlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("\r\ncrlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf", tgt); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf("\ncrlf\ncrlf\nlf\ncrlf\ncrlf", tgt); - - /* bare CR test */ - - git_str_sets(&src, "\rcrlf\r\nlf\nlf\ncr\rcrlf\r\nlf\ncr\r"); - - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf("\rcrlf\r\nlf\r\nlf\r\ncr\rcrlf\r\nlf\r\ncr\r", tgt); - - git_str_sets(&src, "\rcrlf\r\nlf\nlf\ncr\rcrlf\r\nlf\ncr\r"); - - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf("\rcrlf\nlf\nlf\ncr\rcrlf\nlf\ncr\r", tgt); - - git_str_sets(&src, "\rcr\r"); - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf(src.ptr, tgt); - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf("\rcr\r", tgt); - - git_str_dispose(&src); - git_str_dispose(&tgt); - - /* blob correspondence tests */ - - git_str_sets(&src, ALL_CRLF_TEXT_RAW); - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf(ALL_CRLF_TEXT_AS_CRLF, tgt); - git_str_sets(&src, ALL_CRLF_TEXT_RAW); - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf(ALL_CRLF_TEXT_AS_LF, tgt); - git_str_dispose(&src); - git_str_dispose(&tgt); - - git_str_sets(&src, ALL_LF_TEXT_RAW); - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf(ALL_LF_TEXT_AS_CRLF, tgt); - git_str_sets(&src, ALL_LF_TEXT_RAW); - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf(ALL_LF_TEXT_AS_LF, tgt); - git_str_dispose(&src); - git_str_dispose(&tgt); - - git_str_sets(&src, MORE_CRLF_TEXT_RAW); - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf(MORE_CRLF_TEXT_AS_CRLF, tgt); - git_str_sets(&src, MORE_CRLF_TEXT_RAW); - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf(MORE_CRLF_TEXT_AS_LF, tgt); - git_str_dispose(&src); - git_str_dispose(&tgt); - - git_str_sets(&src, MORE_LF_TEXT_RAW); - cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); - check_buf(MORE_LF_TEXT_AS_CRLF, tgt); - git_str_sets(&src, MORE_LF_TEXT_RAW); - cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); - check_buf(MORE_LF_TEXT_AS_LF, tgt); - git_str_dispose(&src); - git_str_dispose(&tgt); -} - -void test_core_gitstr__dont_grow_borrowed(void) -{ - const char *somestring = "blah blah"; - git_str buf = GIT_STR_INIT; - - git_str_attach_notowned(&buf, somestring, strlen(somestring) + 1); - cl_assert_equal_p(somestring, buf.ptr); - cl_assert_equal_i(0, buf.asize); - cl_assert_equal_i(strlen(somestring) + 1, buf.size); - - cl_git_fail_with(GIT_EINVALID, git_str_grow(&buf, 1024)); -} - -void test_core_gitstr__dont_hit_infinite_loop_when_resizing(void) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_puts(&buf, "foobar")); - /* - * We do not care whether this succeeds or fails, which - * would depend on platform-specific allocation - * semantics. We only want to know that the function - * actually returns. - */ - (void)git_str_try_grow(&buf, SIZE_MAX, true); - - git_str_dispose(&buf); -} - -void test_core_gitstr__avoid_printing_into_oom_buffer(void) -{ - git_str buf = GIT_STR_INIT; - - /* Emulate OOM situation with a previous allocation */ - buf.asize = 8; - buf.ptr = git_str__oom; - - /* - * Print the same string again. As the buffer still has - * an `asize` of 8 due to the previous print, - * `ENSURE_SIZE` would not try to reallocate the array at - * all. As it didn't explicitly check for `git_str__oom` - * in earlier versions, this would've resulted in it - * returning successfully and thus `git_str_puts` would - * just print into the `git_str__oom` array. - */ - cl_git_fail(git_str_puts(&buf, "foobar")); -} diff --git a/tests/libgit2/core/hashsig.c b/tests/libgit2/core/hashsig.c new file mode 100644 index 000000000..6cadb1c1a --- /dev/null +++ b/tests/libgit2/core/hashsig.c @@ -0,0 +1,182 @@ +#include "clar_libgit2.h" +#include "git2/sys/hashsig.h" +#include "futils.h" + +#define SIMILARITY_TEST_DATA_1 \ + "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ + "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ + "020\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \ + "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \ + "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n" + +void test_core_hashsig__similarity_metric(void) +{ + git_hashsig *a, *b; + git_str buf = GIT_STR_INIT; + int sim; + + /* in the first case, we compare data to itself and expect 100% match */ + + cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); + cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + + cl_assert_equal_i(100, git_hashsig_compare(a, b)); + + git_hashsig_free(a); + git_hashsig_free(b); + + /* if we change just a single byte, how much does that change magnify? */ + + cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); + cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + cl_git_pass(git_str_sets(&buf, + "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ + "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ + "x020x\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \ + "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \ + "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n" + )); + cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + + sim = git_hashsig_compare(a, b); + + cl_assert_in_range(95, sim, 100); /* expect >95% similarity */ + + git_hashsig_free(a); + git_hashsig_free(b); + + /* let's try comparing data to a superset of itself */ + + cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); + cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1 + "050\n051\n052\n053\n054\n055\n056\n057\n058\n059\n")); + cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + + sim = git_hashsig_compare(a, b); + /* 20% lines added ~= 10% lines changed */ + + cl_assert_in_range(85, sim, 95); /* expect similarity around 90% */ + + git_hashsig_free(a); + git_hashsig_free(b); + + /* what if we keep about half the original data and add half new */ + + cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); + cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + cl_git_pass(git_str_sets(&buf, + "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \ + "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \ + "020x\n021\n022\n023\n024\n" \ + "x25\nx26\nx27\nx28\nx29\n" \ + "x30\nx31\nx32\nx33\nx34\nx35\nx36\nx37\nx38\nx39\n" \ + "x40\nx41\nx42\nx43\nx44\nx45\nx46\nx47\nx48\nx49\n" + )); + cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + + sim = git_hashsig_compare(a, b); + /* 50% lines changed */ + + cl_assert_in_range(40, sim, 60); /* expect in the 40-60% similarity range */ + + git_hashsig_free(a); + git_hashsig_free(b); + + /* lastly, let's check that we can hash file content as well */ + + cl_git_pass(git_str_sets(&buf, SIMILARITY_TEST_DATA_1)); + cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); + + cl_git_pass(git_futils_mkdir("scratch", 0755, GIT_MKDIR_PATH)); + cl_git_mkfile("scratch/testdata", SIMILARITY_TEST_DATA_1); + cl_git_pass(git_hashsig_create_fromfile( + &b, "scratch/testdata", GIT_HASHSIG_NORMAL)); + + cl_assert_equal_i(100, git_hashsig_compare(a, b)); + + git_hashsig_free(a); + git_hashsig_free(b); + + git_str_dispose(&buf); + git_futils_rmdir_r("scratch", NULL, GIT_RMDIR_REMOVE_FILES); +} + +void test_core_hashsig__similarity_metric_whitespace(void) +{ + git_hashsig *a, *b; + git_str buf = GIT_STR_INIT; + int sim, i, j; + git_hashsig_option_t opt; + const char *tabbed = + " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\n" + " separator = sep[s];\n" + " expect = expect_values[s];\n" + "\n" + " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\n" + " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\n" + " git_str_join(&buf, separator, a[i], b[j]);\n" + " cl_assert_equal_s(*expect, buf.ptr);\n" + " expect++;\n" + " }\n" + " }\n" + " }\n"; + const char *spaced = + " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\n" + " separator = sep[s];\n" + " expect = expect_values[s];\n" + "\n" + " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\n" + " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\n" + " git_str_join(&buf, separator, a[i], b[j]);\n" + " cl_assert_equal_s(*expect, buf.ptr);\n" + " expect++;\n" + " }\n" + " }\n" + " }\n"; + const char *crlf_spaced2 = + " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\r\n" + " separator = sep[s];\r\n" + " expect = expect_values[s];\r\n" + "\r\n" + " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\r\n" + " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\r\n" + " git_str_join(&buf, separator, a[i], b[j]);\r\n" + " cl_assert_equal_s(*expect, buf.ptr);\r\n" + " expect++;\r\n" + " }\r\n" + " }\r\n" + " }\r\n"; + const char *text[3] = { tabbed, spaced, crlf_spaced2 }; + + /* let's try variations of our own code with whitespace changes */ + + for (opt = GIT_HASHSIG_NORMAL; opt <= GIT_HASHSIG_SMART_WHITESPACE; ++opt) { + for (i = 0; i < 3; ++i) { + for (j = 0; j < 3; ++j) { + cl_git_pass(git_str_sets(&buf, text[i])); + cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, opt)); + + cl_git_pass(git_str_sets(&buf, text[j])); + cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, opt)); + + sim = git_hashsig_compare(a, b); + + if (opt == GIT_HASHSIG_NORMAL) { + if (i == j) + cl_assert_equal_i(100, sim); + else + cl_assert_in_range(0, sim, 30); /* pretty different */ + } else { + cl_assert_equal_i(100, sim); + } + + git_hashsig_free(a); + git_hashsig_free(b); + } + } + } + + git_str_dispose(&buf); +} diff --git a/tests/libgit2/core/hex.c b/tests/libgit2/core/hex.c deleted file mode 100644 index 930af1670..000000000 --- a/tests/libgit2/core/hex.c +++ /dev/null @@ -1,22 +0,0 @@ -#include "clar_libgit2.h" -#include "util.h" - -void test_core_hex__fromhex(void) -{ - /* Passing cases */ - cl_assert(git__fromhex('0') == 0x0); - cl_assert(git__fromhex('1') == 0x1); - cl_assert(git__fromhex('3') == 0x3); - cl_assert(git__fromhex('9') == 0x9); - cl_assert(git__fromhex('A') == 0xa); - cl_assert(git__fromhex('C') == 0xc); - cl_assert(git__fromhex('F') == 0xf); - cl_assert(git__fromhex('a') == 0xa); - cl_assert(git__fromhex('c') == 0xc); - cl_assert(git__fromhex('f') == 0xf); - - /* Failing cases */ - cl_assert(git__fromhex('g') == -1); - cl_assert(git__fromhex('z') == -1); - cl_assert(git__fromhex('X') == -1); -} diff --git a/tests/libgit2/core/iconv.c b/tests/libgit2/core/iconv.c deleted file mode 100644 index af1b4eabf..000000000 --- a/tests/libgit2/core/iconv.c +++ /dev/null @@ -1,78 +0,0 @@ -#include "clar_libgit2.h" -#include "fs_path.h" - -#ifdef GIT_USE_ICONV -static git_fs_path_iconv_t ic; -static char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; -static char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; -#endif - -void test_core_iconv__initialize(void) -{ -#ifdef GIT_USE_ICONV - cl_git_pass(git_fs_path_iconv_init_precompose(&ic)); -#endif -} - -void test_core_iconv__cleanup(void) -{ -#ifdef GIT_USE_ICONV - git_fs_path_iconv_clear(&ic); -#endif -} - -void test_core_iconv__unchanged(void) -{ -#ifdef GIT_USE_ICONV - const char *data = "Ascii data", *original = data; - size_t datalen = strlen(data); - - cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); - GIT_UNUSED(datalen); - - /* There are no high bits set, so this should leave data untouched */ - cl_assert(data == original); -#endif -} - -void test_core_iconv__decomposed_to_precomposed(void) -{ -#ifdef GIT_USE_ICONV - const char *data = nfd; - size_t datalen, nfdlen = strlen(nfd); - - datalen = nfdlen; - cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); - GIT_UNUSED(datalen); - - /* The decomposed nfd string should be transformed to the nfc form - * (on platforms where iconv is enabled, of course). - */ - cl_assert_equal_s(nfc, data); - - /* should be able to do it multiple times with the same git_fs_path_iconv_t */ - data = nfd; datalen = nfdlen; - cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); - cl_assert_equal_s(nfc, data); - - data = nfd; datalen = nfdlen; - cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); - cl_assert_equal_s(nfc, data); -#endif -} - -void test_core_iconv__precomposed_is_unmodified(void) -{ -#ifdef GIT_USE_ICONV - const char *data = nfc; - size_t datalen = strlen(nfc); - - cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); - GIT_UNUSED(datalen); - - /* data is already in precomposed form, so even though some bytes have - * the high-bit set, the iconv transform should result in no change. - */ - cl_assert_equal_s(nfc, data); -#endif -} diff --git a/tests/libgit2/core/init.c b/tests/libgit2/core/init.c deleted file mode 100644 index eba77ef52..000000000 --- a/tests/libgit2/core/init.c +++ /dev/null @@ -1,54 +0,0 @@ -#include "clar_libgit2.h" - -void test_core_init__returns_count(void) -{ - /* libgit2_tests initializes us first, so we have an existing - * initialization. - */ - cl_assert_equal_i(2, git_libgit2_init()); - cl_assert_equal_i(3, git_libgit2_init()); - - cl_assert_equal_i(2, git_libgit2_shutdown()); - cl_assert_equal_i(1, git_libgit2_shutdown()); -} - -void test_core_init__reinit_succeeds(void) -{ - cl_assert_equal_i(0, git_libgit2_shutdown()); - cl_assert_equal_i(1, git_libgit2_init()); - cl_sandbox_set_search_path_defaults(); -} - -#ifdef GIT_THREADS -static void *reinit(void *unused) -{ - unsigned i; - - for (i = 0; i < 20; i++) { - cl_assert(git_libgit2_init() > 0); - cl_assert(git_libgit2_shutdown() >= 0); - } - - return unused; -} -#endif - -void test_core_init__concurrent_init_succeeds(void) -{ -#ifdef GIT_THREADS - git_thread threads[10]; - unsigned i; - - cl_assert_equal_i(2, git_libgit2_init()); - - for (i = 0; i < ARRAY_SIZE(threads); i++) - git_thread_create(&threads[i], reinit, NULL); - for (i = 0; i < ARRAY_SIZE(threads); i++) - git_thread_join(&threads[i], NULL); - - cl_assert_equal_i(1, git_libgit2_shutdown()); - cl_sandbox_set_search_path_defaults(); -#else - cl_skip(); -#endif -} diff --git a/tests/libgit2/core/integer.c b/tests/libgit2/core/integer.c deleted file mode 100644 index 18364ba62..000000000 --- a/tests/libgit2/core/integer.c +++ /dev/null @@ -1,253 +0,0 @@ -#include "clar_libgit2.h" - -void test_core_integer__multiply_int64_no_overflow(void) -{ -#if !defined(git__multiply_int64_overflow) - int64_t result = 0; - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x800000000000000))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x800000000000000))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x7fffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x7fffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x8000000000000000))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(0x1)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(-0x1)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(-0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x800000000000000))); - cl_assert_equal_i(result, INT64_C(0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x800000000000000))); - cl_assert_equal_i(result, INT64_C(-0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x7fffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x7fffffffffffffff))); - cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(-0x1)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(0x1)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(-0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x800000000000000))); - cl_assert_equal_i(result, INT64_C(-0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x800000000000000))); - cl_assert_equal_i(result, INT64_C(0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x7fffffffffffffff))); - cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x7fffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(-0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(0x4)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(-0x4)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x800000000000000))); - cl_assert_equal_i(result, INT64_C(0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x800000000000000))); - cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(-0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(0x2)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(-0x4)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(0x4)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x7ffffffffffffff))); - cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x800000000000000))); - cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x800000000000000))); - cl_assert_equal_i(result, INT64_C(0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x4000000000000000))); - cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(-0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(-0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(0x800000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x2))); - cl_assert_equal_i(result, INT64_C(0x1000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x4000000000000000), INT64_C(0x2))); - cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x1))); - cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x0))); - cl_assert_equal_i(result, INT64_C(0x0)); -#endif -} - -void test_core_integer__multiply_int64_overflow(void) -{ -#if !defined(git__multiply_int64_overflow) - int64_t result = 0; - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x4000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x4000000000000000), INT64_C(0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x2))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x7ffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x800000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x7fffffffffffffff))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x8000000000000000))); -#endif -} - -void test_core_integer__multiply_int64_edge_cases(void) -{ -#if !defined(git__multiply_int64_overflow) - int64_t result = 0; - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x1))); - cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); - cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x8000000000000000))); - cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x8000000000000000))); - cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x1))); -#endif -} diff --git a/tests/libgit2/core/link.c b/tests/libgit2/core/link.c deleted file mode 100644 index a1e2706b2..000000000 --- a/tests/libgit2/core/link.c +++ /dev/null @@ -1,630 +0,0 @@ -#include "clar_libgit2.h" -#include "posix.h" - -#ifdef GIT_WIN32 -# include "win32/reparse.h" -#endif - -void test_core_link__cleanup(void) -{ -#ifdef GIT_WIN32 - RemoveDirectory("lstat_junction"); - RemoveDirectory("lstat_dangling"); - RemoveDirectory("lstat_dangling_dir"); - RemoveDirectory("lstat_dangling_junction"); - - RemoveDirectory("stat_junction"); - RemoveDirectory("stat_dangling"); - RemoveDirectory("stat_dangling_dir"); - RemoveDirectory("stat_dangling_junction"); -#endif -} - -#ifdef GIT_WIN32 -static bool should_run(void) -{ - static SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY }; - PSID admin_sid; - BOOL is_admin; - - cl_win32_pass(AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &admin_sid)); - cl_win32_pass(CheckTokenMembership(NULL, admin_sid, &is_admin)); - FreeSid(admin_sid); - - return is_admin ? true : false; -} -#else -static bool should_run(void) -{ - return true; -} -#endif - -static void do_symlink(const char *old, const char *new, int is_dir) -{ -#ifndef GIT_WIN32 - GIT_UNUSED(is_dir); - - cl_must_pass(symlink(old, new)); -#else - typedef DWORD (WINAPI *create_symlink_func)(LPCTSTR, LPCTSTR, DWORD); - HMODULE module; - create_symlink_func pCreateSymbolicLink; - - cl_assert(module = GetModuleHandle("kernel32")); - cl_assert(pCreateSymbolicLink = (create_symlink_func)(void *)GetProcAddress(module, "CreateSymbolicLinkA")); - - cl_win32_pass(pCreateSymbolicLink(new, old, is_dir)); -#endif -} - -static void do_hardlink(const char *old, const char *new) -{ -#ifndef GIT_WIN32 - cl_must_pass(link(old, new)); -#else - typedef DWORD (WINAPI *create_hardlink_func)(LPCTSTR, LPCTSTR, LPSECURITY_ATTRIBUTES); - HMODULE module; - create_hardlink_func pCreateHardLink; - - cl_assert(module = GetModuleHandle("kernel32")); - cl_assert(pCreateHardLink = (create_hardlink_func)(void *)GetProcAddress(module, "CreateHardLinkA")); - - cl_win32_pass(pCreateHardLink(new, old, 0)); -#endif -} - -#ifdef GIT_WIN32 - -static void do_junction(const char *old, const char *new) -{ - GIT_REPARSE_DATA_BUFFER *reparse_buf; - HANDLE handle; - git_str unparsed_buf = GIT_STR_INIT; - wchar_t *subst_utf16, *print_utf16; - DWORD ioctl_ret; - int subst_utf16_len, subst_byte_len, print_utf16_len, print_byte_len, ret; - USHORT reparse_buflen; - size_t i; - - /* Junction targets must be the unparsed name, starting with \??\, using - * backslashes instead of forward, and end in a trailing backslash. - * eg: \??\C:\Foo\ - */ - git_str_puts(&unparsed_buf, "\\??\\"); - - for (i = 0; i < strlen(old); i++) - git_str_putc(&unparsed_buf, old[i] == '/' ? '\\' : old[i]); - - git_str_putc(&unparsed_buf, '\\'); - - subst_utf16_len = git__utf8_to_16(NULL, 0, git_str_cstr(&unparsed_buf)); - subst_byte_len = subst_utf16_len * sizeof(WCHAR); - - print_utf16_len = subst_utf16_len - 4; - print_byte_len = subst_byte_len - (4 * sizeof(WCHAR)); - - /* The junction must be an empty directory before the junction attribute - * can be added. - */ - cl_win32_pass(CreateDirectoryA(new, NULL)); - - handle = CreateFileA(new, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); - cl_win32_pass(handle != INVALID_HANDLE_VALUE); - - reparse_buflen = (USHORT)(REPARSE_DATA_HEADER_SIZE + - REPARSE_DATA_MOUNTPOINT_HEADER_SIZE + - subst_byte_len + sizeof(WCHAR) + - print_byte_len + sizeof(WCHAR)); - - reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen); - cl_assert(reparse_buf); - - subst_utf16 = reparse_buf->ReparseBuffer.MountPoint.PathBuffer; - print_utf16 = subst_utf16 + subst_utf16_len + 1; - - ret = git__utf8_to_16(subst_utf16, subst_utf16_len + 1, - git_str_cstr(&unparsed_buf)); - cl_assert_equal_i(subst_utf16_len, ret); - - ret = git__utf8_to_16(print_utf16, - print_utf16_len + 1, git_str_cstr(&unparsed_buf) + 4); - cl_assert_equal_i(print_utf16_len, ret); - - reparse_buf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; - reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset = 0; - reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength = subst_byte_len; - reparse_buf->ReparseBuffer.MountPoint.PrintNameOffset = (USHORT)(subst_byte_len + sizeof(WCHAR)); - reparse_buf->ReparseBuffer.MountPoint.PrintNameLength = print_byte_len; - reparse_buf->ReparseDataLength = reparse_buflen - REPARSE_DATA_HEADER_SIZE; - - cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, - reparse_buf, reparse_buflen, NULL, 0, &ioctl_ret, NULL)); - - CloseHandle(handle); - LocalFree(reparse_buf); - - git_str_dispose(&unparsed_buf); -} - -static void do_custom_reparse(const char *path) -{ - REPARSE_GUID_DATA_BUFFER *reparse_buf; - HANDLE handle; - DWORD ioctl_ret; - - const char *reparse_data = "Reparse points are silly."; - size_t reparse_buflen = REPARSE_GUID_DATA_BUFFER_HEADER_SIZE + - strlen(reparse_data) + 1; - - reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen); - cl_assert(reparse_buf); - - reparse_buf->ReparseTag = 42; - reparse_buf->ReparseDataLength = (WORD)(strlen(reparse_data) + 1); - - reparse_buf->ReparseGuid.Data1 = 0xdeadbeef; - reparse_buf->ReparseGuid.Data2 = 0xdead; - reparse_buf->ReparseGuid.Data3 = 0xbeef; - reparse_buf->ReparseGuid.Data4[0] = 42; - reparse_buf->ReparseGuid.Data4[1] = 42; - reparse_buf->ReparseGuid.Data4[2] = 42; - reparse_buf->ReparseGuid.Data4[3] = 42; - reparse_buf->ReparseGuid.Data4[4] = 42; - reparse_buf->ReparseGuid.Data4[5] = 42; - reparse_buf->ReparseGuid.Data4[6] = 42; - reparse_buf->ReparseGuid.Data4[7] = 42; - reparse_buf->ReparseGuid.Data4[8] = 42; - - memcpy(reparse_buf->GenericReparseBuffer.DataBuffer, - reparse_data, strlen(reparse_data) + 1); - - handle = CreateFileA(path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); - cl_win32_pass(handle != INVALID_HANDLE_VALUE); - - cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, - reparse_buf, - reparse_buf->ReparseDataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, - NULL, 0, &ioctl_ret, NULL)); - - CloseHandle(handle); - LocalFree(reparse_buf); -} - -#endif - -void test_core_link__stat_regular_file(void) -{ - struct stat st; - - cl_git_rewritefile("stat_regfile", "This is a regular file!\n"); - - cl_must_pass(p_stat("stat_regfile", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(24, st.st_size); -} - -void test_core_link__lstat_regular_file(void) -{ - struct stat st; - - cl_git_rewritefile("lstat_regfile", "This is a regular file!\n"); - - cl_must_pass(p_stat("lstat_regfile", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(24, st.st_size); -} - -void test_core_link__stat_symlink(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - cl_git_rewritefile("stat_target", "This is the target of a symbolic link.\n"); - do_symlink("stat_target", "stat_symlink", 0); - - cl_must_pass(p_stat("stat_target", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(39, st.st_size); - - cl_must_pass(p_stat("stat_symlink", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(39, st.st_size); -} - -void test_core_link__stat_symlink_directory(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - p_mkdir("stat_dirtarget", 0777); - do_symlink("stat_dirtarget", "stat_dirlink", 1); - - cl_must_pass(p_stat("stat_dirtarget", &st)); - cl_assert(S_ISDIR(st.st_mode)); - - cl_must_pass(p_stat("stat_dirlink", &st)); - cl_assert(S_ISDIR(st.st_mode)); -} - -void test_core_link__stat_symlink_chain(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - cl_git_rewritefile("stat_final_target", "Final target of some symbolic links...\n"); - do_symlink("stat_final_target", "stat_chain_3", 0); - do_symlink("stat_chain_3", "stat_chain_2", 0); - do_symlink("stat_chain_2", "stat_chain_1", 0); - - cl_must_pass(p_stat("stat_chain_1", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(39, st.st_size); -} - -void test_core_link__stat_dangling_symlink(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - do_symlink("stat_nonexistent", "stat_dangling", 0); - - cl_must_fail(p_stat("stat_nonexistent", &st)); - cl_must_fail(p_stat("stat_dangling", &st)); -} - -void test_core_link__stat_dangling_symlink_directory(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - do_symlink("stat_nonexistent", "stat_dangling_dir", 1); - - cl_must_fail(p_stat("stat_nonexistent_dir", &st)); - cl_must_fail(p_stat("stat_dangling", &st)); -} - -void test_core_link__lstat_symlink(void) -{ - git_str target_path = GIT_STR_INIT; - struct stat st; - - if (!should_run()) - clar__skip(); - - /* Windows always writes the canonical path as the link target, so - * write the full path on all platforms. - */ - git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_target"); - - cl_git_rewritefile("lstat_target", "This is the target of a symbolic link.\n"); - do_symlink(git_str_cstr(&target_path), "lstat_symlink", 0); - - cl_must_pass(p_lstat("lstat_target", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(39, st.st_size); - - cl_must_pass(p_lstat("lstat_symlink", &st)); - cl_assert(S_ISLNK(st.st_mode)); - cl_assert_equal_i(git_str_len(&target_path), st.st_size); - - git_str_dispose(&target_path); -} - -void test_core_link__lstat_symlink_directory(void) -{ - git_str target_path = GIT_STR_INIT; - struct stat st; - - if (!should_run()) - clar__skip(); - - git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_dirtarget"); - - p_mkdir("lstat_dirtarget", 0777); - do_symlink(git_str_cstr(&target_path), "lstat_dirlink", 1); - - cl_must_pass(p_lstat("lstat_dirtarget", &st)); - cl_assert(S_ISDIR(st.st_mode)); - - cl_must_pass(p_lstat("lstat_dirlink", &st)); - cl_assert(S_ISLNK(st.st_mode)); - cl_assert_equal_i(git_str_len(&target_path), st.st_size); - - git_str_dispose(&target_path); -} - -void test_core_link__lstat_dangling_symlink(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - do_symlink("lstat_nonexistent", "lstat_dangling", 0); - - cl_must_fail(p_lstat("lstat_nonexistent", &st)); - - cl_must_pass(p_lstat("lstat_dangling", &st)); - cl_assert(S_ISLNK(st.st_mode)); - cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size); -} - -void test_core_link__lstat_dangling_symlink_directory(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - do_symlink("lstat_nonexistent", "lstat_dangling_dir", 1); - - cl_must_fail(p_lstat("lstat_nonexistent", &st)); - - cl_must_pass(p_lstat("lstat_dangling_dir", &st)); - cl_assert(S_ISLNK(st.st_mode)); - cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size); -} - -void test_core_link__stat_junction(void) -{ -#ifdef GIT_WIN32 - git_str target_path = GIT_STR_INIT; - struct stat st; - - git_str_join(&target_path, '/', clar_sandbox_path(), "stat_junctarget"); - - p_mkdir("stat_junctarget", 0777); - do_junction(git_str_cstr(&target_path), "stat_junction"); - - cl_must_pass(p_stat("stat_junctarget", &st)); - cl_assert(S_ISDIR(st.st_mode)); - - cl_must_pass(p_stat("stat_junction", &st)); - cl_assert(S_ISDIR(st.st_mode)); - - git_str_dispose(&target_path); -#endif -} - -void test_core_link__stat_dangling_junction(void) -{ -#ifdef GIT_WIN32 - git_str target_path = GIT_STR_INIT; - struct stat st; - - git_str_join(&target_path, '/', clar_sandbox_path(), "stat_nonexistent_junctarget"); - - p_mkdir("stat_nonexistent_junctarget", 0777); - do_junction(git_str_cstr(&target_path), "stat_dangling_junction"); - - RemoveDirectory("stat_nonexistent_junctarget"); - - cl_must_fail(p_stat("stat_nonexistent_junctarget", &st)); - cl_must_fail(p_stat("stat_dangling_junction", &st)); - - git_str_dispose(&target_path); -#endif -} - -void test_core_link__lstat_junction(void) -{ -#ifdef GIT_WIN32 - git_str target_path = GIT_STR_INIT; - struct stat st; - - git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_junctarget"); - - p_mkdir("lstat_junctarget", 0777); - do_junction(git_str_cstr(&target_path), "lstat_junction"); - - cl_must_pass(p_lstat("lstat_junctarget", &st)); - cl_assert(S_ISDIR(st.st_mode)); - - cl_must_pass(p_lstat("lstat_junction", &st)); - cl_assert(S_ISLNK(st.st_mode)); - - git_str_dispose(&target_path); -#endif -} - -void test_core_link__lstat_dangling_junction(void) -{ -#ifdef GIT_WIN32 - git_str target_path = GIT_STR_INIT; - struct stat st; - - git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_nonexistent_junctarget"); - - p_mkdir("lstat_nonexistent_junctarget", 0777); - do_junction(git_str_cstr(&target_path), "lstat_dangling_junction"); - - RemoveDirectory("lstat_nonexistent_junctarget"); - - cl_must_fail(p_lstat("lstat_nonexistent_junctarget", &st)); - - cl_must_pass(p_lstat("lstat_dangling_junction", &st)); - cl_assert(S_ISLNK(st.st_mode)); - cl_assert_equal_i(git_str_len(&target_path), st.st_size); - - git_str_dispose(&target_path); -#endif -} - -void test_core_link__stat_hardlink(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - cl_git_rewritefile("stat_hardlink1", "This file has many names!\n"); - do_hardlink("stat_hardlink1", "stat_hardlink2"); - - cl_must_pass(p_stat("stat_hardlink1", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(26, st.st_size); - - cl_must_pass(p_stat("stat_hardlink2", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(26, st.st_size); -} - -void test_core_link__lstat_hardlink(void) -{ - struct stat st; - - if (!should_run()) - clar__skip(); - - cl_git_rewritefile("lstat_hardlink1", "This file has many names!\n"); - do_hardlink("lstat_hardlink1", "lstat_hardlink2"); - - cl_must_pass(p_lstat("lstat_hardlink1", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(26, st.st_size); - - cl_must_pass(p_lstat("lstat_hardlink2", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(26, st.st_size); -} - -void test_core_link__stat_reparse_point(void) -{ -#ifdef GIT_WIN32 - struct stat st; - - /* Generic reparse points should be treated as regular files, only - * symlinks and junctions should be treated as links. - */ - - cl_git_rewritefile("stat_reparse", "This is a reparse point!\n"); - do_custom_reparse("stat_reparse"); - - cl_must_pass(p_lstat("stat_reparse", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(25, st.st_size); -#endif -} - -void test_core_link__lstat_reparse_point(void) -{ -#ifdef GIT_WIN32 - struct stat st; - - cl_git_rewritefile("lstat_reparse", "This is a reparse point!\n"); - do_custom_reparse("lstat_reparse"); - - cl_must_pass(p_lstat("lstat_reparse", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_equal_i(25, st.st_size); -#endif -} - -void test_core_link__readlink_nonexistent_file(void) -{ - char buf[2048]; - - cl_must_fail(p_readlink("readlink_nonexistent", buf, 2048)); - cl_assert_equal_i(ENOENT, errno); -} - -void test_core_link__readlink_normal_file(void) -{ - char buf[2048]; - - cl_git_rewritefile("readlink_regfile", "This is a regular file!\n"); - cl_must_fail(p_readlink("readlink_regfile", buf, 2048)); - cl_assert_equal_i(EINVAL, errno); -} - -void test_core_link__readlink_symlink(void) -{ - git_str target_path = GIT_STR_INIT; - int len; - char buf[2048]; - - if (!should_run()) - clar__skip(); - - git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_target"); - - cl_git_rewritefile("readlink_target", "This is the target of a symlink\n"); - do_symlink(git_str_cstr(&target_path), "readlink_link", 0); - - len = p_readlink("readlink_link", buf, 2048); - cl_must_pass(len); - - buf[len] = 0; - - cl_assert_equal_s(git_str_cstr(&target_path), buf); - - git_str_dispose(&target_path); -} - -void test_core_link__readlink_dangling(void) -{ - git_str target_path = GIT_STR_INIT; - int len; - char buf[2048]; - - if (!should_run()) - clar__skip(); - - git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_nonexistent"); - - do_symlink(git_str_cstr(&target_path), "readlink_dangling", 0); - - len = p_readlink("readlink_dangling", buf, 2048); - cl_must_pass(len); - - buf[len] = 0; - - cl_assert_equal_s(git_str_cstr(&target_path), buf); - - git_str_dispose(&target_path); -} - -void test_core_link__readlink_multiple(void) -{ - git_str target_path = GIT_STR_INIT, - path3 = GIT_STR_INIT, path2 = GIT_STR_INIT, path1 = GIT_STR_INIT; - int len; - char buf[2048]; - - if (!should_run()) - clar__skip(); - - git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_final"); - git_str_join(&path3, '/', clar_sandbox_path(), "readlink_3"); - git_str_join(&path2, '/', clar_sandbox_path(), "readlink_2"); - git_str_join(&path1, '/', clar_sandbox_path(), "readlink_1"); - - do_symlink(git_str_cstr(&target_path), git_str_cstr(&path3), 0); - do_symlink(git_str_cstr(&path3), git_str_cstr(&path2), 0); - do_symlink(git_str_cstr(&path2), git_str_cstr(&path1), 0); - - len = p_readlink("readlink_1", buf, 2048); - cl_must_pass(len); - - buf[len] = 0; - - cl_assert_equal_s(git_str_cstr(&path2), buf); - - git_str_dispose(&path1); - git_str_dispose(&path2); - git_str_dispose(&path3); - git_str_dispose(&target_path); -} diff --git a/tests/libgit2/core/memmem.c b/tests/libgit2/core/memmem.c deleted file mode 100644 index fd9986d01..000000000 --- a/tests/libgit2/core/memmem.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "clar_libgit2.h" - -static void assert_found(const char *haystack, const char *needle, size_t expected_pos) -{ - cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0, - needle, needle ? strlen(needle) : 0), - haystack + expected_pos); -} - -static void assert_absent(const char *haystack, const char *needle) -{ - cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0, - needle, needle ? strlen(needle) : 0), - NULL); -} - -void test_core_memmem__found(void) -{ - assert_found("a", "a", 0); - assert_found("ab", "a", 0); - assert_found("ba", "a", 1); - assert_found("aa", "a", 0); - assert_found("aab", "aa", 0); - assert_found("baa", "aa", 1); - assert_found("dabc", "abc", 1); - assert_found("abababc", "abc", 4); -} - -void test_core_memmem__absent(void) -{ - assert_absent("a", "b"); - assert_absent("a", "aa"); - assert_absent("ba", "ab"); - assert_absent("ba", "ab"); - assert_absent("abc", "abcd"); - assert_absent("abcabcabc", "bcac"); -} - -void test_core_memmem__edgecases(void) -{ - assert_absent(NULL, NULL); - assert_absent("a", NULL); - assert_absent(NULL, "a"); - assert_absent("", "a"); - assert_absent("a", ""); -} diff --git a/tests/libgit2/core/mkdir.c b/tests/libgit2/core/mkdir.c deleted file mode 100644 index 58a4cfcdb..000000000 --- a/tests/libgit2/core/mkdir.c +++ /dev/null @@ -1,291 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "posix.h" - -static void cleanup_basic_dirs(void *ref) -{ - GIT_UNUSED(ref); - git_futils_rmdir_r("d0", NULL, GIT_RMDIR_EMPTY_HIERARCHY); - git_futils_rmdir_r("d1", NULL, GIT_RMDIR_EMPTY_HIERARCHY); - git_futils_rmdir_r("d2", NULL, GIT_RMDIR_EMPTY_HIERARCHY); - git_futils_rmdir_r("d3", NULL, GIT_RMDIR_EMPTY_HIERARCHY); - git_futils_rmdir_r("d4", NULL, GIT_RMDIR_EMPTY_HIERARCHY); -} - -void test_core_mkdir__absolute(void) -{ - git_str path = GIT_STR_INIT; - - cl_set_cleanup(cleanup_basic_dirs, NULL); - - git_str_joinpath(&path, clar_sandbox_path(), "d0"); - - /* make a directory */ - cl_assert(!git_fs_path_isdir(path.ptr)); - cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); - cl_assert(git_fs_path_isdir(path.ptr)); - - git_str_joinpath(&path, path.ptr, "subdir"); - cl_assert(!git_fs_path_isdir(path.ptr)); - cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); - cl_assert(git_fs_path_isdir(path.ptr)); - - /* ensure mkdir_r works for a single subdir */ - git_str_joinpath(&path, path.ptr, "another"); - cl_assert(!git_fs_path_isdir(path.ptr)); - cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); - cl_assert(git_fs_path_isdir(path.ptr)); - - /* ensure mkdir_r works */ - git_str_joinpath(&path, clar_sandbox_path(), "d1/foo/bar/asdf"); - cl_assert(!git_fs_path_isdir(path.ptr)); - cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); - cl_assert(git_fs_path_isdir(path.ptr)); - - /* ensure we don't imply recursive */ - git_str_joinpath(&path, clar_sandbox_path(), "d2/foo/bar/asdf"); - cl_assert(!git_fs_path_isdir(path.ptr)); - cl_git_fail(git_futils_mkdir(path.ptr, 0755, 0)); - cl_assert(!git_fs_path_isdir(path.ptr)); - - git_str_dispose(&path); -} - -void test_core_mkdir__basic(void) -{ - cl_set_cleanup(cleanup_basic_dirs, NULL); - - /* make a directory */ - cl_assert(!git_fs_path_isdir("d0")); - cl_git_pass(git_futils_mkdir("d0", 0755, 0)); - cl_assert(git_fs_path_isdir("d0")); - - /* make a path */ - cl_assert(!git_fs_path_isdir("d1")); - cl_git_pass(git_futils_mkdir("d1/d1.1/d1.2", 0755, GIT_MKDIR_PATH)); - cl_assert(git_fs_path_isdir("d1")); - cl_assert(git_fs_path_isdir("d1/d1.1")); - cl_assert(git_fs_path_isdir("d1/d1.1/d1.2")); - - /* make a dir exclusively */ - cl_assert(!git_fs_path_isdir("d2")); - cl_git_pass(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL)); - cl_assert(git_fs_path_isdir("d2")); - - /* make exclusive failure */ - cl_git_fail(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL)); - - /* make a path exclusively */ - cl_assert(!git_fs_path_isdir("d3")); - cl_git_pass(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); - cl_assert(git_fs_path_isdir("d3")); - cl_assert(git_fs_path_isdir("d3/d3.1/d3.2")); - - /* make exclusive path failure */ - cl_git_fail(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); - /* ??? Should EXCL only apply to the last item in the path? */ - - /* path with trailing slash? */ - cl_assert(!git_fs_path_isdir("d4")); - cl_git_pass(git_futils_mkdir("d4/d4.1/", 0755, GIT_MKDIR_PATH)); - cl_assert(git_fs_path_isdir("d4/d4.1")); -} - -static void cleanup_basedir(void *ref) -{ - GIT_UNUSED(ref); - git_futils_rmdir_r("base", NULL, GIT_RMDIR_EMPTY_HIERARCHY); -} - -void test_core_mkdir__with_base(void) -{ -#define BASEDIR "base/dir/here" - - cl_set_cleanup(cleanup_basedir, NULL); - - cl_git_pass(git_futils_mkdir(BASEDIR, 0755, GIT_MKDIR_PATH)); - - cl_git_pass(git_futils_mkdir_relative("a", BASEDIR, 0755, 0, NULL)); - cl_assert(git_fs_path_isdir(BASEDIR "/a")); - - cl_git_pass(git_futils_mkdir_relative("b/b1/b2", BASEDIR, 0755, GIT_MKDIR_PATH, NULL)); - cl_assert(git_fs_path_isdir(BASEDIR "/b/b1/b2")); - - /* exclusive with existing base */ - cl_git_pass(git_futils_mkdir_relative("c/c1/c2", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); - - /* fail: exclusive with duplicated suffix */ - cl_git_fail(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); - - /* fail: exclusive with any duplicated component */ - cl_git_fail(git_futils_mkdir_relative("c/cz/cz", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); - - /* success: exclusive without path */ - cl_git_pass(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_EXCL, NULL)); - - /* path with shorter base and existing dirs */ - cl_git_pass(git_futils_mkdir_relative("dir/here/d/", "base", 0755, GIT_MKDIR_PATH, NULL)); - cl_assert(git_fs_path_isdir("base/dir/here/d")); - - /* fail: path with shorter base and existing dirs */ - cl_git_fail(git_futils_mkdir_relative("dir/here/e/", "base", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); - - /* fail: base with missing components */ - cl_git_fail(git_futils_mkdir_relative("f/", "base/missing", 0755, GIT_MKDIR_PATH, NULL)); - - /* success: shift missing component to path */ - cl_git_pass(git_futils_mkdir_relative("missing/f/", "base/", 0755, GIT_MKDIR_PATH, NULL)); -} - -static void cleanup_chmod_root(void *ref) -{ - mode_t *mode = ref; - - if (mode != NULL) { - (void)p_umask(*mode); - git__free(mode); - } - - git_futils_rmdir_r("r", NULL, GIT_RMDIR_EMPTY_HIERARCHY); -} - -#define check_mode(X,A) check_mode_at_line((X), (A), __FILE__, __func__, __LINE__) - -static void check_mode_at_line( - mode_t expected, mode_t actual, - const char *file, const char *func, int line) -{ - /* FAT filesystems don't support exec bit, nor group/world bits */ - if (!cl_is_chmod_supported()) { - expected &= 0600; - actual &= 0600; - } - - clar__assert_equal( - file, func, line, "expected_mode != actual_mode", 1, - "%07o", (int)expected, (int)(actual & 0777)); -} - -void test_core_mkdir__chmods(void) -{ - struct stat st; - mode_t *old = git__malloc(sizeof(mode_t)); - *old = p_umask(022); - - cl_set_cleanup(cleanup_chmod_root, old); - - cl_git_pass(git_futils_mkdir("r", 0777, 0)); - - cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL)); - - cl_git_pass(git_fs_path_lstat("r/mode", &st)); - check_mode(0755, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode/is", &st)); - check_mode(0755, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode/is/important", &st)); - check_mode(0755, st.st_mode); - - cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL)); - - cl_git_pass(git_fs_path_lstat("r/mode2", &st)); - check_mode(0755, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode2/is2", &st)); - check_mode(0755, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode2/is2/important2", &st)); - check_mode(0777, st.st_mode); - - cl_git_pass(git_futils_mkdir_relative("mode3/is3/important3", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL)); - - cl_git_pass(git_fs_path_lstat("r/mode3", &st)); - check_mode(0777, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode3/is3", &st)); - check_mode(0777, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode3/is3/important3", &st)); - check_mode(0777, st.st_mode); - - /* test that we chmod existing dir */ - - cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL)); - - cl_git_pass(git_fs_path_lstat("r/mode", &st)); - check_mode(0755, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode/is", &st)); - check_mode(0755, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode/is/important", &st)); - check_mode(0777, st.st_mode); - - /* test that we chmod even existing dirs if CHMOD_PATH is set */ - - cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2.1", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL)); - - cl_git_pass(git_fs_path_lstat("r/mode2", &st)); - check_mode(0777, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode2/is2", &st)); - check_mode(0777, st.st_mode); - cl_git_pass(git_fs_path_lstat("r/mode2/is2/important2.1", &st)); - check_mode(0777, st.st_mode); -} - -void test_core_mkdir__keeps_parent_symlinks(void) -{ -#ifndef GIT_WIN32 - git_str path = GIT_STR_INIT; - - cl_set_cleanup(cleanup_basic_dirs, NULL); - - /* make a directory */ - cl_assert(!git_fs_path_isdir("d0")); - cl_git_pass(git_futils_mkdir("d0", 0755, 0)); - cl_assert(git_fs_path_isdir("d0")); - - cl_must_pass(symlink("d0", "d1")); - cl_assert(git_fs_path_islink("d1")); - - cl_git_pass(git_futils_mkdir("d1/foo/bar", 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS)); - cl_assert(git_fs_path_islink("d1")); - cl_assert(git_fs_path_isdir("d1/foo/bar")); - cl_assert(git_fs_path_isdir("d0/foo/bar")); - - cl_must_pass(symlink("d0", "d2")); - cl_assert(git_fs_path_islink("d2")); - - git_str_joinpath(&path, clar_sandbox_path(), "d2/other/dir"); - - cl_git_pass(git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS)); - cl_assert(git_fs_path_islink("d2")); - cl_assert(git_fs_path_isdir("d2/other/dir")); - cl_assert(git_fs_path_isdir("d0/other/dir")); - - git_str_dispose(&path); -#endif -} - -void test_core_mkdir__mkdir_path_inside_unwriteable_parent(void) -{ - struct stat st; - mode_t *old; - - /* FAT filesystems don't support exec bit, nor group/world bits */ - if (!cl_is_chmod_supported()) - return; - - cl_assert((old = git__malloc(sizeof(mode_t))) != NULL); - *old = p_umask(022); - cl_set_cleanup(cleanup_chmod_root, old); - - cl_git_pass(git_futils_mkdir("r", 0777, 0)); - cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL)); - cl_git_pass(git_fs_path_lstat("r/mode", &st)); - check_mode(0755, st.st_mode); - - cl_must_pass(p_chmod("r/mode", 0111)); - cl_git_pass(git_fs_path_lstat("r/mode", &st)); - check_mode(0111, st.st_mode); - - cl_git_pass( - git_futils_mkdir_relative("mode/is/okay/inside", "r", 0777, GIT_MKDIR_PATH, NULL)); - cl_git_pass(git_fs_path_lstat("r/mode/is/okay/inside", &st)); - check_mode(0755, st.st_mode); - - cl_must_pass(p_chmod("r/mode", 0777)); -} diff --git a/tests/libgit2/core/path.c b/tests/libgit2/core/path.c deleted file mode 100644 index a0ae77f1c..000000000 --- a/tests/libgit2/core/path.c +++ /dev/null @@ -1,739 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "fs_path.h" - -static char *path_save; - -void test_core_path__initialize(void) -{ - path_save = cl_getenv("PATH"); -} - -void test_core_path__cleanup(void) -{ - cl_setenv("PATH", path_save); - git__free(path_save); - path_save = NULL; -} - -static void -check_dirname(const char *A, const char *B) -{ - git_str dir = GIT_STR_INIT; - char *dir2; - - cl_assert(git_fs_path_dirname_r(&dir, A) >= 0); - cl_assert_equal_s(B, dir.ptr); - git_str_dispose(&dir); - - cl_assert((dir2 = git_fs_path_dirname(A)) != NULL); - cl_assert_equal_s(B, dir2); - git__free(dir2); -} - -static void -check_basename(const char *A, const char *B) -{ - git_str base = GIT_STR_INIT; - char *base2; - - cl_assert(git_fs_path_basename_r(&base, A) >= 0); - cl_assert_equal_s(B, base.ptr); - git_str_dispose(&base); - - cl_assert((base2 = git_fs_path_basename(A)) != NULL); - cl_assert_equal_s(B, base2); - git__free(base2); -} - -static void -check_joinpath(const char *path_a, const char *path_b, const char *expected_path) -{ - git_str joined_path = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&joined_path, path_a, path_b)); - cl_assert_equal_s(expected_path, joined_path.ptr); - - git_str_dispose(&joined_path); -} - -static void -check_joinpath_n( - const char *path_a, - const char *path_b, - const char *path_c, - const char *path_d, - const char *expected_path) -{ - git_str joined_path = GIT_STR_INIT; - - cl_git_pass(git_str_join_n(&joined_path, '/', 4, - path_a, path_b, path_c, path_d)); - cl_assert_equal_s(expected_path, joined_path.ptr); - - git_str_dispose(&joined_path); -} - -static void check_setenv(const char* name, const char* value) -{ - char* check; - - cl_git_pass(cl_setenv(name, value)); - check = cl_getenv(name); - - if (value) - cl_assert_equal_s(value, check); - else - cl_assert(check == NULL); - - git__free(check); -} - -/* get the dirname of a path */ -void test_core_path__00_dirname(void) -{ - check_dirname(NULL, "."); - check_dirname("", "."); - check_dirname("a", "."); - check_dirname("/", "/"); - check_dirname("/usr", "/"); - check_dirname("/usr/", "/"); - check_dirname("/usr/lib", "/usr"); - check_dirname("/usr/lib/", "/usr"); - check_dirname("/usr/lib//", "/usr"); - check_dirname("usr/lib", "usr"); - check_dirname("usr/lib/", "usr"); - check_dirname("usr/lib//", "usr"); - check_dirname(".git/", "."); - - check_dirname(REP16("/abc"), REP15("/abc")); - -#ifdef GIT_WIN32 - check_dirname("C:/", "C:/"); - check_dirname("C:", "C:/"); - check_dirname("C:/path/", "C:/"); - check_dirname("C:/path", "C:/"); - check_dirname("//computername/", "//computername/"); - check_dirname("//computername", "//computername/"); - check_dirname("//computername/path/", "//computername/"); - check_dirname("//computername/path", "//computername/"); - check_dirname("//computername/sub/path/", "//computername/sub"); - check_dirname("//computername/sub/path", "//computername/sub"); -#endif -} - -/* get the base name of a path */ -void test_core_path__01_basename(void) -{ - check_basename(NULL, "."); - check_basename("", "."); - check_basename("a", "a"); - check_basename("/", "/"); - check_basename("/usr", "usr"); - check_basename("/usr/", "usr"); - check_basename("/usr/lib", "lib"); - check_basename("/usr/lib//", "lib"); - check_basename("usr/lib", "lib"); - - check_basename(REP16("/abc"), "abc"); - check_basename(REP1024("/abc"), "abc"); -} - -/* properly join path components */ -void test_core_path__05_joins(void) -{ - check_joinpath("", "", ""); - check_joinpath("", "a", "a"); - check_joinpath("", "/a", "/a"); - check_joinpath("a", "", "a/"); - check_joinpath("a", "/", "a/"); - check_joinpath("a", "b", "a/b"); - check_joinpath("/", "a", "/a"); - check_joinpath("/", "", "/"); - check_joinpath("/a", "/b", "/a/b"); - check_joinpath("/a", "/b/", "/a/b/"); - check_joinpath("/a/", "b/", "/a/b/"); - check_joinpath("/a/", "/b/", "/a/b/"); - - check_joinpath("/abcd", "/defg", "/abcd/defg"); - check_joinpath("/abcd", "/defg/", "/abcd/defg/"); - check_joinpath("/abcd/", "defg/", "/abcd/defg/"); - check_joinpath("/abcd/", "/defg/", "/abcd/defg/"); - - check_joinpath("/abcdefgh", "/12345678", "/abcdefgh/12345678"); - check_joinpath("/abcdefgh", "/12345678/", "/abcdefgh/12345678/"); - check_joinpath("/abcdefgh/", "12345678/", "/abcdefgh/12345678/"); - - check_joinpath(REP1024("aaaa"), "", REP1024("aaaa") "/"); - check_joinpath(REP1024("aaaa/"), "", REP1024("aaaa/")); - check_joinpath(REP1024("/aaaa"), "", REP1024("/aaaa") "/"); - - check_joinpath(REP1024("aaaa"), REP1024("bbbb"), - REP1024("aaaa") "/" REP1024("bbbb")); - check_joinpath(REP1024("/aaaa"), REP1024("/bbbb"), - REP1024("/aaaa") REP1024("/bbbb")); -} - -/* properly join path components for more than one path */ -void test_core_path__06_long_joins(void) -{ - check_joinpath_n("", "", "", "", ""); - check_joinpath_n("", "a", "", "", "a/"); - check_joinpath_n("a", "", "", "", "a/"); - check_joinpath_n("", "", "", "a", "a"); - check_joinpath_n("a", "b", "", "/c/d/", "a/b/c/d/"); - check_joinpath_n("a", "b", "", "/c/d", "a/b/c/d"); - check_joinpath_n("abcd", "efgh", "ijkl", "mnop", "abcd/efgh/ijkl/mnop"); - check_joinpath_n("abcd/", "efgh/", "ijkl/", "mnop/", "abcd/efgh/ijkl/mnop/"); - check_joinpath_n("/abcd/", "/efgh/", "/ijkl/", "/mnop/", "/abcd/efgh/ijkl/mnop/"); - - check_joinpath_n(REP1024("a"), REP1024("b"), REP1024("c"), REP1024("d"), - REP1024("a") "/" REP1024("b") "/" - REP1024("c") "/" REP1024("d")); - check_joinpath_n(REP1024("/a"), REP1024("/b"), REP1024("/c"), REP1024("/d"), - REP1024("/a") REP1024("/b") - REP1024("/c") REP1024("/d")); -} - - -static void -check_path_to_dir( - const char* path, - const char* expected) -{ - git_str tgt = GIT_STR_INIT; - - git_str_sets(&tgt, path); - cl_git_pass(git_fs_path_to_dir(&tgt)); - cl_assert_equal_s(expected, tgt.ptr); - - git_str_dispose(&tgt); -} - -static void -check_string_to_dir( - const char* path, - size_t maxlen, - const char* expected) -{ - size_t len = strlen(path); - char *buf = git__malloc(len + 2); - cl_assert(buf); - - strncpy(buf, path, len + 2); - - git_fs_path_string_to_dir(buf, maxlen); - - cl_assert_equal_s(expected, buf); - - git__free(buf); -} - -/* convert paths to dirs */ -void test_core_path__07_path_to_dir(void) -{ - check_path_to_dir("", ""); - check_path_to_dir(".", "./"); - check_path_to_dir("./", "./"); - check_path_to_dir("a/", "a/"); - check_path_to_dir("ab", "ab/"); - /* make sure we try just under and just over an expansion that will - * require a realloc - */ - check_path_to_dir("abcdef", "abcdef/"); - check_path_to_dir("abcdefg", "abcdefg/"); - check_path_to_dir("abcdefgh", "abcdefgh/"); - check_path_to_dir("abcdefghi", "abcdefghi/"); - check_path_to_dir(REP1024("abcd") "/", REP1024("abcd") "/"); - check_path_to_dir(REP1024("abcd"), REP1024("abcd") "/"); - - check_string_to_dir("", 1, ""); - check_string_to_dir(".", 1, "."); - check_string_to_dir(".", 2, "./"); - check_string_to_dir(".", 3, "./"); - check_string_to_dir("abcd", 3, "abcd"); - check_string_to_dir("abcd", 4, "abcd"); - check_string_to_dir("abcd", 5, "abcd/"); - check_string_to_dir("abcd", 6, "abcd/"); -} - -/* join path to itself */ -void test_core_path__08_self_join(void) -{ - git_str path = GIT_STR_INIT; - size_t asize = 0; - - asize = path.asize; - cl_git_pass(git_str_sets(&path, "/foo")); - cl_assert_equal_s(path.ptr, "/foo"); - cl_assert(asize < path.asize); - - asize = path.asize; - cl_git_pass(git_str_joinpath(&path, path.ptr, "this is a new string")); - cl_assert_equal_s(path.ptr, "/foo/this is a new string"); - cl_assert(asize < path.asize); - - asize = path.asize; - cl_git_pass(git_str_joinpath(&path, path.ptr, "/grow the buffer, grow the buffer, grow the buffer")); - cl_assert_equal_s(path.ptr, "/foo/this is a new string/grow the buffer, grow the buffer, grow the buffer"); - cl_assert(asize < path.asize); - - git_str_dispose(&path); - cl_git_pass(git_str_sets(&path, "/foo/bar")); - - cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "baz")); - cl_assert_equal_s(path.ptr, "/bar/baz"); - - asize = path.asize; - cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "somethinglongenoughtorealloc")); - cl_assert_equal_s(path.ptr, "/baz/somethinglongenoughtorealloc"); - cl_assert(asize < path.asize); - - git_str_dispose(&path); -} - -static void check_percent_decoding(const char *expected_result, const char *input) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git__percent_decode(&buf, input)); - cl_assert_equal_s(expected_result, git_str_cstr(&buf)); - - git_str_dispose(&buf); -} - -void test_core_path__09_percent_decode(void) -{ - check_percent_decoding("abcd", "abcd"); - check_percent_decoding("a2%", "a2%"); - check_percent_decoding("a2%3", "a2%3"); - check_percent_decoding("a2%%3", "a2%%3"); - check_percent_decoding("a2%3z", "a2%3z"); - check_percent_decoding("a,", "a%2c"); - check_percent_decoding("a21", "a2%31"); - check_percent_decoding("a2%1", "a2%%31"); - check_percent_decoding("a bc ", "a%20bc%20"); - check_percent_decoding("Vicent Mart" "\355", "Vicent%20Mart%ED"); -} - -static void check_fromurl(const char *expected_result, const char *input, int should_fail) -{ - git_str buf = GIT_STR_INIT; - - assert(should_fail || expected_result); - - if (!should_fail) { - cl_git_pass(git_fs_path_fromurl(&buf, input)); - cl_assert_equal_s(expected_result, git_str_cstr(&buf)); - } else - cl_git_fail(git_fs_path_fromurl(&buf, input)); - - git_str_dispose(&buf); -} - -#ifdef GIT_WIN32 -#define ABS_PATH_MARKER "" -#else -#define ABS_PATH_MARKER "/" -#endif - -void test_core_path__10_fromurl(void) -{ - /* Failing cases */ - check_fromurl(NULL, "a", 1); - check_fromurl(NULL, "http:///c:/Temp%20folder/note.txt", 1); - check_fromurl(NULL, "file://c:/Temp%20folder/note.txt", 1); - check_fromurl(NULL, "file:////c:/Temp%20folder/note.txt", 1); - check_fromurl(NULL, "file:///", 1); - check_fromurl(NULL, "file:////", 1); - check_fromurl(NULL, "file://servername/c:/Temp%20folder/note.txt", 1); - - /* Passing cases */ - check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file:///c:/Temp%20folder/note.txt", 0); - check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file://localhost/c:/Temp%20folder/note.txt", 0); - check_fromurl(ABS_PATH_MARKER "c:/Temp+folder/note.txt", "file:///c:/Temp+folder/note.txt", 0); - check_fromurl(ABS_PATH_MARKER "a", "file:///a", 0); -} - -typedef struct { - int expect_idx; - int cancel_after; - char **expect; -} check_walkup_info; - -#define CANCEL_VALUE 1234 - -static int check_one_walkup_step(void *ref, const char *path) -{ - check_walkup_info *info = (check_walkup_info *)ref; - - if (!info->cancel_after) { - cl_assert_equal_s(info->expect[info->expect_idx], "[CANCEL]"); - return CANCEL_VALUE; - } - info->cancel_after--; - - cl_assert(info->expect[info->expect_idx] != NULL); - cl_assert_equal_s(info->expect[info->expect_idx], path); - info->expect_idx++; - - return 0; -} - -void test_core_path__11_walkup(void) -{ - git_str p = GIT_STR_INIT; - - char *expect[] = { - /* 1 */ "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 2 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 3 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 4 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, - /* 5 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, - /* 6 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, - /* 7 */ "this_is_a_path", "", NULL, - /* 8 */ "this_is_a_path/", "", NULL, - /* 9 */ "///a///b///c///d///e///", "///a///b///c///d///", "///a///b///c///", "///a///b///", "///a///", "///", NULL, - /* 10 */ "a/b/c/", "a/b/", "a/", "", NULL, - /* 11 */ "a/b/c", "a/b/", "a/", "", NULL, - /* 12 */ "a/b/c/", "a/b/", "a/", NULL, - /* 13 */ "", NULL, - /* 14 */ "/", NULL, - /* 15 */ NULL - }; - - char *root[] = { - /* 1 */ NULL, - /* 2 */ NULL, - /* 3 */ "/", - /* 4 */ "", - /* 5 */ "/a/b", - /* 6 */ "/a/b/", - /* 7 */ NULL, - /* 8 */ NULL, - /* 9 */ NULL, - /* 10 */ NULL, - /* 11 */ NULL, - /* 12 */ "a/", - /* 13 */ NULL, - /* 14 */ NULL, - }; - - int i, j; - check_walkup_info info; - - info.expect = expect; - info.cancel_after = -1; - - for (i = 0, j = 0; expect[i] != NULL; i++, j++) { - - git_str_sets(&p, expect[i]); - - info.expect_idx = i; - cl_git_pass( - git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) - ); - - cl_assert_equal_s(p.ptr, expect[i]); - cl_assert(expect[info.expect_idx] == NULL); - i = info.expect_idx; - } - - git_str_dispose(&p); -} - -void test_core_path__11a_walkup_cancel(void) -{ - git_str p = GIT_STR_INIT; - int cancel[] = { 3, 2, 1, 0 }; - char *expect[] = { - "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "[CANCEL]", NULL, - "/a/b/c/d/e", "/a/b/c/d/", "[CANCEL]", NULL, - "/a/b/c/d/e", "[CANCEL]", NULL, - "[CANCEL]", NULL, - NULL - }; - char *root[] = { NULL, NULL, "/", "", NULL }; - int i, j; - check_walkup_info info; - - info.expect = expect; - - for (i = 0, j = 0; expect[i] != NULL; i++, j++) { - - git_str_sets(&p, expect[i]); - - info.cancel_after = cancel[j]; - info.expect_idx = i; - - cl_assert_equal_i( - CANCEL_VALUE, - git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) - ); - - /* skip to next run of expectations */ - while (expect[i] != NULL) i++; - } - - git_str_dispose(&p); -} - -void test_core_path__12_offset_to_path_root(void) -{ - cl_assert(git_fs_path_root("non/rooted/path") == -1); - cl_assert(git_fs_path_root("/rooted/path") == 0); - -#ifdef GIT_WIN32 - /* Windows specific tests */ - cl_assert(git_fs_path_root("C:non/rooted/path") == -1); - cl_assert(git_fs_path_root("C:/rooted/path") == 2); - cl_assert(git_fs_path_root("//computername/sharefolder/resource") == 14); - cl_assert(git_fs_path_root("//computername/sharefolder") == 14); - cl_assert(git_fs_path_root("//computername") == -1); -#endif -} - -#define NON_EXISTING_FILEPATH "i_hope_i_do_not_exist" - -void test_core_path__13_cannot_prettify_a_non_existing_file(void) -{ - git_str p = GIT_STR_INIT; - - cl_assert_equal_b(git_fs_path_exists(NON_EXISTING_FILEPATH), false); - cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH, NULL)); - cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH "/so-do-i", NULL)); - - git_str_dispose(&p); -} - -void test_core_path__14_apply_relative(void) -{ - git_str p = GIT_STR_INIT; - - cl_git_pass(git_str_sets(&p, "/this/is/a/base")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../test")); - cl_assert_equal_s("/this/is/a/test", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../the/./end")); - cl_assert_equal_s("/this/is/the/end", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "./of/this/../the/string")); - cl_assert_equal_s("/this/is/the/end/of/the/string", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../../../../..")); - cl_assert_equal_s("/this/", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../")); - cl_assert_equal_s("/", p.ptr); - - cl_git_fail(git_fs_path_apply_relative(&p, "../../..")); - - - cl_git_pass(git_str_sets(&p, "d:/another/test")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../..")); - cl_assert_equal_s("d:/", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "from/here/to/../and/./back/.")); - cl_assert_equal_s("d:/from/here/and/back/", p.ptr); - - - cl_git_pass(git_str_sets(&p, "https://my.url.com/test.git")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../another.git")); - cl_assert_equal_s("https://my.url.com/another.git", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../full/path/url.patch")); - cl_assert_equal_s("https://my.url.com/full/path/url.patch", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "..")); - cl_assert_equal_s("https://my.url.com/full/path/", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../../")); - cl_assert_equal_s("https://", p.ptr); - - - cl_git_pass(git_str_sets(&p, "../../this/is/relative")); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../preserves/the/prefix")); - cl_assert_equal_s("../../this/preserves/the/prefix", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../../../../that")); - cl_assert_equal_s("../../that", p.ptr); - - cl_git_pass(git_fs_path_apply_relative(&p, "../there")); - cl_assert_equal_s("../../there", p.ptr); - git_str_dispose(&p); -} - -static void assert_resolve_relative( - git_str *buf, const char *expected, const char *path) -{ - cl_git_pass(git_str_sets(buf, path)); - cl_git_pass(git_fs_path_resolve_relative(buf, 0)); - cl_assert_equal_s(expected, buf->ptr); -} - -void test_core_path__15_resolve_relative(void) -{ - git_str buf = GIT_STR_INIT; - - assert_resolve_relative(&buf, "", ""); - assert_resolve_relative(&buf, "", "."); - assert_resolve_relative(&buf, "", "./"); - assert_resolve_relative(&buf, "..", ".."); - assert_resolve_relative(&buf, "../", "../"); - assert_resolve_relative(&buf, "..", "./.."); - assert_resolve_relative(&buf, "../", "./../"); - assert_resolve_relative(&buf, "../", "../."); - assert_resolve_relative(&buf, "../", ".././"); - assert_resolve_relative(&buf, "../..", "../.."); - assert_resolve_relative(&buf, "../../", "../../"); - - assert_resolve_relative(&buf, "/", "/"); - assert_resolve_relative(&buf, "/", "/."); - - assert_resolve_relative(&buf, "", "a/.."); - assert_resolve_relative(&buf, "", "a/../"); - assert_resolve_relative(&buf, "", "a/../."); - - assert_resolve_relative(&buf, "/a", "/a"); - assert_resolve_relative(&buf, "/a/", "/a/."); - assert_resolve_relative(&buf, "/", "/a/../"); - assert_resolve_relative(&buf, "/", "/a/../."); - assert_resolve_relative(&buf, "/", "/a/.././"); - - assert_resolve_relative(&buf, "a", "a"); - assert_resolve_relative(&buf, "a/", "a/"); - assert_resolve_relative(&buf, "a/", "a/."); - assert_resolve_relative(&buf, "a/", "a/./"); - - assert_resolve_relative(&buf, "a/b", "a//b"); - assert_resolve_relative(&buf, "a/b/c", "a/b/c"); - assert_resolve_relative(&buf, "b/c", "./b/c"); - assert_resolve_relative(&buf, "a/c", "a/./c"); - assert_resolve_relative(&buf, "a/b/", "a/b/."); - - assert_resolve_relative(&buf, "/a/b/c", "///a/b/c"); - assert_resolve_relative(&buf, "/", "////"); - assert_resolve_relative(&buf, "/a", "///a"); - assert_resolve_relative(&buf, "/", "///."); - assert_resolve_relative(&buf, "/", "///a/.."); - - assert_resolve_relative(&buf, "../../path", "../../test//../././path"); - assert_resolve_relative(&buf, "../d", "a/b/../../../c/../d"); - - cl_git_pass(git_str_sets(&buf, "/..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/./..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/.//..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/../.")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "/../.././../a")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - cl_git_pass(git_str_sets(&buf, "////..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); - - /* things that start with Windows network paths */ -#ifdef GIT_WIN32 - assert_resolve_relative(&buf, "//a/b/c", "//a/b/c"); - assert_resolve_relative(&buf, "//a/", "//a/b/.."); - assert_resolve_relative(&buf, "//a/b/c", "//a/Q/../b/x/y/../../c"); - - cl_git_pass(git_str_sets(&buf, "//a/b/../..")); - cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); -#else - assert_resolve_relative(&buf, "/a/b/c", "//a/b/c"); - assert_resolve_relative(&buf, "/a/", "//a/b/.."); - assert_resolve_relative(&buf, "/a/b/c", "//a/Q/../b/x/y/../../c"); - assert_resolve_relative(&buf, "/", "//a/b/../.."); -#endif - - git_str_dispose(&buf); -} - -#define assert_common_dirlen(i, p, q) \ - cl_assert_equal_i((i), git_fs_path_common_dirlen((p), (q))); - -void test_core_path__16_resolve_relative(void) -{ - assert_common_dirlen(0, "", ""); - assert_common_dirlen(0, "", "bar.txt"); - assert_common_dirlen(0, "foo.txt", "bar.txt"); - assert_common_dirlen(0, "foo.txt", ""); - assert_common_dirlen(0, "foo/bar.txt", "bar/foo.txt"); - assert_common_dirlen(0, "foo/bar.txt", "../foo.txt"); - - assert_common_dirlen(1, "/one.txt", "/two.txt"); - assert_common_dirlen(4, "foo/one.txt", "foo/two.txt"); - assert_common_dirlen(5, "/foo/one.txt", "/foo/two.txt"); - - assert_common_dirlen(6, "a/b/c/foo.txt", "a/b/c/d/e/bar.txt"); - assert_common_dirlen(7, "/a/b/c/foo.txt", "/a/b/c/d/e/bar.txt"); -} - -static void fix_path(git_str *s) -{ -#ifndef GIT_WIN32 - GIT_UNUSED(s); -#else - char* c; - - for (c = s->ptr; *c; c++) { - if (*c == '/') - *c = '\\'; - } -#endif -} - -void test_core_path__find_exe_in_path(void) -{ - char *orig_path; - git_str sandbox_path = GIT_STR_INIT; - git_str new_path = GIT_STR_INIT, full_path = GIT_STR_INIT, - dummy_path = GIT_STR_INIT; - -#ifdef GIT_WIN32 - static const char *bogus_path_1 = "c:\\does\\not\\exist\\"; - static const char *bogus_path_2 = "e:\\non\\existent"; -#else - static const char *bogus_path_1 = "/this/path/does/not/exist/"; - static const char *bogus_path_2 = "/non/existent"; -#endif - - orig_path = cl_getenv("PATH"); - - git_str_puts(&sandbox_path, clar_sandbox_path()); - git_str_joinpath(&dummy_path, sandbox_path.ptr, "dummmmmmmy_libgit2_file"); - cl_git_rewritefile(dummy_path.ptr, "this is a dummy file"); - - fix_path(&sandbox_path); - fix_path(&dummy_path); - - cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s", - bogus_path_1, GIT_PATH_LIST_SEPARATOR, - orig_path, GIT_PATH_LIST_SEPARATOR, - sandbox_path.ptr, GIT_PATH_LIST_SEPARATOR, - bogus_path_2)); - - check_setenv("PATH", new_path.ptr); - - cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "this_file_does_not_exist")); - cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file")); - - cl_assert_equal_s(full_path.ptr, dummy_path.ptr); - - git_str_dispose(&full_path); - git_str_dispose(&new_path); - git_str_dispose(&dummy_path); - git_str_dispose(&sandbox_path); - git__free(orig_path); -} diff --git a/tests/libgit2/core/pool.c b/tests/libgit2/core/pool.c index b07da0abd..5746e35b8 100644 --- a/tests/libgit2/core/pool.c +++ b/tests/libgit2/core/pool.c @@ -2,57 +2,9 @@ #include "pool.h" #include "git2/oid.h" -void test_core_pool__0(void) -{ - int i; - git_pool p; - void *ptr; - - git_pool_init(&p, 1); - - for (i = 1; i < 10000; i *= 2) { - ptr = git_pool_malloc(&p, i); - cl_assert(ptr != NULL); - cl_assert(git_pool__ptr_in_pool(&p, ptr)); - cl_assert(!git_pool__ptr_in_pool(&p, &i)); - } - - git_pool_clear(&p); -} - -void test_core_pool__1(void) -{ - int i; - git_pool p; - - git_pool_init(&p, 1); - p.page_size = 4000; - - for (i = 2010; i > 0; i--) - cl_assert(git_pool_malloc(&p, i) != NULL); - -#ifndef GIT_DEBUG_POOL - /* with fixed page size, allocation must end up with these values */ - cl_assert_equal_i(591, git_pool__open_pages(&p)); -#endif - git_pool_clear(&p); - - git_pool_init(&p, 1); - p.page_size = 4120; - - for (i = 2010; i > 0; i--) - cl_assert(git_pool_malloc(&p, i) != NULL); - -#ifndef GIT_DEBUG_POOL - /* with fixed page size, allocation must end up with these values */ - cl_assert_equal_i(sizeof(void *) == 8 ? 575 : 573, git_pool__open_pages(&p)); -#endif - git_pool_clear(&p); -} - static char to_hex[] = "0123456789abcdef"; -void test_core_pool__2(void) +void test_core_pool__oid(void) { git_pool p; char oid_hex[GIT_OID_HEXSZ]; @@ -79,14 +31,3 @@ void test_core_pool__2(void) #endif git_pool_clear(&p); } - -void test_core_pool__strndup_limit(void) -{ - git_pool p; - - git_pool_init(&p, 1); - /* ensure 64 bit doesn't overflow */ - cl_assert(git_pool_strndup(&p, "foo", (size_t)-1) == NULL); - git_pool_clear(&p); -} - diff --git a/tests/libgit2/core/posix.c b/tests/libgit2/core/posix.c deleted file mode 100644 index cba312913..000000000 --- a/tests/libgit2/core/posix.c +++ /dev/null @@ -1,238 +0,0 @@ -#ifndef _WIN32 -# include -# include -# include -#else -# include -# ifdef _MSC_VER -# pragma comment(lib, "ws2_32") -# endif -#endif - -#include "clar_libgit2.h" -#include "futils.h" -#include "posix.h" - -void test_core_posix__initialize(void) -{ -#ifdef GIT_WIN32 - /* on win32, the WSA context needs to be initialized - * before any socket calls can be performed */ - WSADATA wsd; - - cl_git_pass(WSAStartup(MAKEWORD(2,2), &wsd)); - cl_assert(LOBYTE(wsd.wVersion) == 2 && HIBYTE(wsd.wVersion) == 2); -#endif -} - -static bool supports_ipv6(void) -{ -#ifdef GIT_WIN32 - /* IPv6 is supported on Vista and newer */ - return git_has_win32_version(6, 0, 0); -#else - return 1; -#endif -} - -void test_core_posix__inet_pton(void) -{ - struct in_addr addr; - struct in6_addr addr6; - size_t i; - - struct in_addr_data { - const char *p; - const uint8_t n[4]; - }; - - struct in6_addr_data { - const char *p; - const uint8_t n[16]; - }; - - static struct in_addr_data in_addr_data[] = { - { "0.0.0.0", { 0, 0, 0, 0 } }, - { "10.42.101.8", { 10, 42, 101, 8 } }, - { "127.0.0.1", { 127, 0, 0, 1 } }, - { "140.177.10.12", { 140, 177, 10, 12 } }, - { "204.232.175.90", { 204, 232, 175, 90 } }, - { "255.255.255.255", { 255, 255, 255, 255 } }, - }; - - static struct in6_addr_data in6_addr_data[] = { - { "::", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, - { "::1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, - { "0:0:0:0:0:0:0:1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, - { "2001:db8:8714:3a90::12", { 0x20, 0x01, 0x0d, 0xb8, 0x87, 0x14, 0x3a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12 } }, - { "fe80::f8ba:c2d6:86be:3645", { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xba, 0xc2, 0xd6, 0x86, 0xbe, 0x36, 0x45 } }, - { "::ffff:204.152.189.116", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcc, 0x98, 0xbd, 0x74 } }, - }; - - /* Test some ipv4 addresses */ - for (i = 0; i < 6; i++) { - cl_assert(p_inet_pton(AF_INET, in_addr_data[i].p, &addr) == 1); - cl_assert(memcmp(&addr, in_addr_data[i].n, sizeof(struct in_addr)) == 0); - } - - /* Test some ipv6 addresses */ - if (supports_ipv6()) - { - for (i = 0; i < 6; i++) { - cl_assert(p_inet_pton(AF_INET6, in6_addr_data[i].p, &addr6) == 1); - cl_assert(memcmp(&addr6, in6_addr_data[i].n, sizeof(struct in6_addr)) == 0); - } - } - - /* Test some invalid strings */ - cl_assert(p_inet_pton(AF_INET, "", &addr) == 0); - cl_assert(p_inet_pton(AF_INET, "foo", &addr) == 0); - cl_assert(p_inet_pton(AF_INET, " 127.0.0.1", &addr) == 0); - cl_assert(p_inet_pton(AF_INET, "bar", &addr) == 0); - cl_assert(p_inet_pton(AF_INET, "10.foo.bar.1", &addr) == 0); - - /* Test unsupported address families */ - cl_git_fail(p_inet_pton(INT_MAX-1, "52.472", &addr)); - cl_assert_equal_i(EAFNOSUPPORT, errno); -} - -void test_core_posix__utimes(void) -{ - struct p_timeval times[2]; - struct stat st; - time_t curtime; - int fd; - - /* test p_utimes */ - times[0].tv_sec = 1234567890; - times[0].tv_usec = 0; - times[1].tv_sec = 1234567890; - times[1].tv_usec = 0; - - cl_git_mkfile("foo", "Dummy file."); - cl_must_pass(p_utimes("foo", times)); - - cl_must_pass(p_stat("foo", &st)); - cl_assert_equal_i(1234567890, st.st_atime); - cl_assert_equal_i(1234567890, st.st_mtime); - - - /* test p_futimes */ - times[0].tv_sec = 1414141414; - times[0].tv_usec = 0; - times[1].tv_sec = 1414141414; - times[1].tv_usec = 0; - - cl_must_pass(fd = p_open("foo", O_RDWR)); - cl_must_pass(p_futimes(fd, times)); - cl_must_pass(p_close(fd)); - - cl_must_pass(p_stat("foo", &st)); - cl_assert_equal_i(1414141414, st.st_atime); - cl_assert_equal_i(1414141414, st.st_mtime); - - - /* test p_utimes with current time, assume that - * it takes < 5 seconds to get the time...! - */ - cl_must_pass(p_utimes("foo", NULL)); - - curtime = time(NULL); - cl_must_pass(p_stat("foo", &st)); - cl_assert((st.st_atime - curtime) < 5); - cl_assert((st.st_mtime - curtime) < 5); - - cl_must_pass(p_unlink("foo")); -} - -void test_core_posix__unlink_removes_symlink(void) -{ - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - clar__skip(); - - cl_git_mkfile("file", "Dummy file."); - cl_git_pass(git_futils_mkdir("dir", 0777, 0)); - - cl_must_pass(p_symlink("file", "file-symlink")); - cl_must_pass(p_symlink("dir", "dir-symlink")); - - cl_must_pass(p_unlink("file-symlink")); - cl_must_pass(p_unlink("dir-symlink")); - - cl_assert(git_fs_path_exists("file")); - cl_assert(git_fs_path_exists("dir")); - - cl_must_pass(p_unlink("file")); - cl_must_pass(p_rmdir("dir")); -} - -void test_core_posix__symlink_resolves_to_correct_type(void) -{ - git_str contents = GIT_STR_INIT; - - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - clar__skip(); - - cl_must_pass(git_futils_mkdir("dir", 0777, 0)); - cl_must_pass(git_futils_mkdir("file", 0777, 0)); - cl_git_mkfile("dir/file", "symlink target"); - - cl_git_pass(p_symlink("file", "dir/link")); - - cl_git_pass(git_futils_readbuffer(&contents, "dir/file")); - cl_assert_equal_s(contents.ptr, "symlink target"); - - cl_must_pass(p_unlink("dir/link")); - cl_must_pass(p_unlink("dir/file")); - cl_must_pass(p_rmdir("dir")); - cl_must_pass(p_rmdir("file")); - - git_str_dispose(&contents); -} - -void test_core_posix__relative_symlink(void) -{ - git_str contents = GIT_STR_INIT; - - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - clar__skip(); - - cl_must_pass(git_futils_mkdir("dir", 0777, 0)); - cl_git_mkfile("file", "contents"); - cl_git_pass(p_symlink("../file", "dir/link")); - cl_git_pass(git_futils_readbuffer(&contents, "dir/link")); - cl_assert_equal_s(contents.ptr, "contents"); - - cl_must_pass(p_unlink("file")); - cl_must_pass(p_unlink("dir/link")); - cl_must_pass(p_rmdir("dir")); - - git_str_dispose(&contents); -} - -void test_core_posix__symlink_to_file_across_dirs(void) -{ - git_str contents = GIT_STR_INIT; - - if (!git_fs_path_supports_symlinks(clar_sandbox_path())) - clar__skip(); - - /* - * Create a relative symlink that points into another - * directory. This used to not work on Win32, where we - * forgot to convert directory separators to - * Windows-style ones. - */ - cl_must_pass(git_futils_mkdir("dir", 0777, 0)); - cl_git_mkfile("dir/target", "symlink target"); - cl_git_pass(p_symlink("dir/target", "link")); - - cl_git_pass(git_futils_readbuffer(&contents, "dir/target")); - cl_assert_equal_s(contents.ptr, "symlink target"); - - cl_must_pass(p_unlink("dir/target")); - cl_must_pass(p_unlink("link")); - cl_must_pass(p_rmdir("dir")); - - git_str_dispose(&contents); -} diff --git a/tests/libgit2/core/pqueue.c b/tests/libgit2/core/pqueue.c deleted file mode 100644 index 2b90f4172..000000000 --- a/tests/libgit2/core/pqueue.c +++ /dev/null @@ -1,150 +0,0 @@ -#include "clar_libgit2.h" -#include "pqueue.h" - -static int cmp_ints(const void *v1, const void *v2) -{ - int i1 = *(int *)v1, i2 = *(int *)v2; - return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0; -} - -void test_core_pqueue__items_are_put_in_order(void) -{ - git_pqueue pq; - int i, vals[20]; - - cl_git_pass(git_pqueue_init(&pq, 0, 20, cmp_ints)); - - for (i = 0; i < 20; ++i) { - if (i < 10) - vals[i] = 10 - i; /* 10 down to 1 */ - else - vals[i] = i + 1; /* 11 up to 20 */ - - cl_git_pass(git_pqueue_insert(&pq, &vals[i])); - } - - cl_assert_equal_i(20, git_pqueue_size(&pq)); - - for (i = 1; i <= 20; ++i) { - void *p = git_pqueue_pop(&pq); - cl_assert(p); - cl_assert_equal_i(i, *(int *)p); - } - - cl_assert_equal_i(0, git_pqueue_size(&pq)); - - git_pqueue_free(&pq); -} - -void test_core_pqueue__interleave_inserts_and_pops(void) -{ - git_pqueue pq; - int chunk, v, i, vals[200]; - - cl_git_pass(git_pqueue_init(&pq, 0, 20, cmp_ints)); - - for (v = 0, chunk = 20; chunk <= 200; chunk += 20) { - /* push the next 20 */ - for (; v < chunk; ++v) { - vals[v] = (v & 1) ? 200 - v : v; - cl_git_pass(git_pqueue_insert(&pq, &vals[v])); - } - - /* pop the lowest 10 */ - for (i = 0; i < 10; ++i) - (void)git_pqueue_pop(&pq); - } - - cl_assert_equal_i(100, git_pqueue_size(&pq)); - - /* at this point, we've popped 0-99 */ - - for (v = 100; v < 200; ++v) { - void *p = git_pqueue_pop(&pq); - cl_assert(p); - cl_assert_equal_i(v, *(int *)p); - } - - cl_assert_equal_i(0, git_pqueue_size(&pq)); - - git_pqueue_free(&pq); -} - -void test_core_pqueue__max_heap_size(void) -{ - git_pqueue pq; - int i, vals[100]; - - cl_git_pass(git_pqueue_init(&pq, GIT_PQUEUE_FIXED_SIZE, 50, cmp_ints)); - - for (i = 0; i < 100; ++i) { - vals[i] = (i & 1) ? 100 - i : i; - cl_git_pass(git_pqueue_insert(&pq, &vals[i])); - } - - cl_assert_equal_i(50, git_pqueue_size(&pq)); - - for (i = 50; i < 100; ++i) { - void *p = git_pqueue_pop(&pq); - cl_assert(p); - cl_assert_equal_i(i, *(int *)p); - } - - cl_assert_equal_i(0, git_pqueue_size(&pq)); - - git_pqueue_free(&pq); -} - -void test_core_pqueue__max_heap_size_without_comparison(void) -{ - git_pqueue pq; - int i, vals[100] = { 0 }; - - cl_git_pass(git_pqueue_init(&pq, GIT_PQUEUE_FIXED_SIZE, 50, NULL)); - - for (i = 0; i < 100; ++i) - cl_git_pass(git_pqueue_insert(&pq, &vals[i])); - - cl_assert_equal_i(50, git_pqueue_size(&pq)); - - /* As we have no comparison function, we cannot make any - * actual assumptions about which entries are part of the - * pqueue */ - for (i = 0; i < 50; ++i) - cl_assert(git_pqueue_pop(&pq)); - - cl_assert_equal_i(0, git_pqueue_size(&pq)); - - git_pqueue_free(&pq); -} - -static int cmp_ints_like_commit_time(const void *a, const void *b) -{ - return *((const int *)a) < *((const int *)b); -} - -void test_core_pqueue__interleaved_pushes_and_pops(void) -{ - git_pqueue pq; - int i, j, *val; - static int commands[] = - { 6, 9, 8, 0, 5, 0, 7, 0, 4, 3, 0, 0, 0, 4, 0, 2, 0, 1, 0, 0, -1 }; - static int expected[] = - { 9, 8, 7, 6, 5, 4, 4, 3, 2, 1, -1 }; - - cl_git_pass(git_pqueue_init(&pq, 0, 10, cmp_ints_like_commit_time)); - - for (i = 0, j = 0; commands[i] >= 0; ++i) { - if (!commands[i]) { - cl_assert((val = git_pqueue_pop(&pq)) != NULL); - cl_assert_equal_i(expected[j], *val); - ++j; - } else { - cl_git_pass(git_pqueue_insert(&pq, &commands[i])); - } - } - - cl_assert_equal_i(0, git_pqueue_size(&pq)); - git_pqueue_free(&pq); -} - diff --git a/tests/libgit2/core/qsort.c b/tests/libgit2/core/qsort.c deleted file mode 100644 index efb79e6e9..000000000 --- a/tests/libgit2/core/qsort.c +++ /dev/null @@ -1,90 +0,0 @@ -#include "clar_libgit2.h" - -#define assert_sorted(a, cmp) \ - _assert_sorted(a, ARRAY_SIZE(a), sizeof(*a), cmp) - -struct big_entries { - char c[311]; -}; - -static void _assert_sorted(void *els, size_t n, size_t elsize, git__sort_r_cmp cmp) -{ - int8_t *p = els; - - git__qsort_r(p, n, elsize, cmp, NULL); - while (n-- > 1) { - cl_assert(cmp(p, p + elsize, NULL) <= 0); - p += elsize; - } -} - -static int cmp_big(const void *_a, const void *_b, void *payload) -{ - const struct big_entries *a = (const struct big_entries *)_a, *b = (const struct big_entries *)_b; - GIT_UNUSED(payload); - return (a->c[0] < b->c[0]) ? -1 : (a->c[0] > b->c[0]) ? +1 : 0; -} - -static int cmp_int(const void *_a, const void *_b, void *payload) -{ - int a = *(const int *)_a, b = *(const int *)_b; - GIT_UNUSED(payload); - return (a < b) ? -1 : (a > b) ? +1 : 0; -} - -static int cmp_str(const void *_a, const void *_b, void *payload) -{ - GIT_UNUSED(payload); - return strcmp((const char *) _a, (const char *) _b); -} - -void test_core_qsort__array_with_single_entry(void) -{ - int a[] = { 10 }; - assert_sorted(a, cmp_int); -} - -void test_core_qsort__array_with_equal_entries(void) -{ - int a[] = { 4, 4, 4, 4 }; - assert_sorted(a, cmp_int); -} - -void test_core_qsort__sorted_array(void) -{ - int a[] = { 1, 10 }; - assert_sorted(a, cmp_int); -} - -void test_core_qsort__unsorted_array(void) -{ - int a[] = { 123, 9, 412938, 10, 234, 89 }; - assert_sorted(a, cmp_int); -} - -void test_core_qsort__sorting_strings(void) -{ - char *a[] = { "foo", "bar", "baz" }; - assert_sorted(a, cmp_str); -} - -void test_core_qsort__sorting_big_entries(void) -{ - struct big_entries a[5]; - - memset(&a, 0, sizeof(a)); - - memset(a[0].c, 'w', sizeof(a[0].c) - 1); - memset(a[1].c, 'c', sizeof(a[1].c) - 1); - memset(a[2].c, 'w', sizeof(a[2].c) - 1); - memset(a[3].c, 'h', sizeof(a[3].c) - 1); - memset(a[4].c, 'a', sizeof(a[4].c) - 1); - - assert_sorted(a, cmp_big); - - cl_assert_equal_i(strspn(a[0].c, "a"), sizeof(a[0].c) - 1); - cl_assert_equal_i(strspn(a[1].c, "c"), sizeof(a[1].c) - 1); - cl_assert_equal_i(strspn(a[2].c, "h"), sizeof(a[2].c) - 1); - cl_assert_equal_i(strspn(a[3].c, "w"), sizeof(a[3].c) - 1); - cl_assert_equal_i(strspn(a[4].c, "w"), sizeof(a[4].c) - 1); -} diff --git a/tests/libgit2/core/regexp.c b/tests/libgit2/core/regexp.c deleted file mode 100644 index 8db5641e5..000000000 --- a/tests/libgit2/core/regexp.c +++ /dev/null @@ -1,213 +0,0 @@ -#include "clar_libgit2.h" - -#include - -#include "regexp.h" -#include "userdiff.h" - -#if LC_ALL > 0 -static const char *old_locales[LC_ALL]; -#endif - -static git_regexp regex; - -void test_core_regexp__initialize(void) -{ -#if LC_ALL > 0 - memset(&old_locales, 0, sizeof(old_locales)); -#endif -} - -void test_core_regexp__cleanup(void) -{ - git_regexp_dispose(®ex); -} - -static void try_set_locale(int category) -{ -#if LC_ALL > 0 - old_locales[category] = setlocale(category, NULL); -#endif - - if (!setlocale(category, "UTF-8") && - !setlocale(category, "c.utf8") && - !setlocale(category, "en_US.UTF-8")) - cl_skip(); - - if (MB_CUR_MAX == 1) - cl_fail("Expected locale to be switched to multibyte"); -} - - -void test_core_regexp__compile_ignores_global_locale_ctype(void) -{ - try_set_locale(LC_CTYPE); - cl_git_pass(git_regexp_compile(®ex, "[\xc0-\xff][\x80-\xbf]", 0)); -} - -void test_core_regexp__compile_ignores_global_locale_collate(void) -{ -#ifdef GIT_WIN32 - cl_skip(); -#endif - - try_set_locale(LC_COLLATE); - cl_git_pass(git_regexp_compile(®ex, "[\xc0-\xff][\x80-\xbf]", 0)); -} - -void test_core_regexp__regex_matches_digits_with_locale(void) -{ - char c, str[2]; - -#ifdef GIT_WIN32 - cl_skip(); -#endif - - try_set_locale(LC_COLLATE); - try_set_locale(LC_CTYPE); - - cl_git_pass(git_regexp_compile(®ex, "[[:digit:]]", 0)); - - str[1] = '\0'; - for (c = '0'; c <= '9'; c++) { - str[0] = c; - cl_git_pass(git_regexp_match(®ex, str)); - } -} - -void test_core_regexp__regex_matches_alphabet_with_locale(void) -{ - char c, str[2]; - -#ifdef GIT_WIN32 - cl_skip(); -#endif - - try_set_locale(LC_COLLATE); - try_set_locale(LC_CTYPE); - - cl_git_pass(git_regexp_compile(®ex, "[[:alpha:]]", 0)); - - str[1] = '\0'; - for (c = 'a'; c <= 'z'; c++) { - str[0] = c; - cl_git_pass(git_regexp_match(®ex, str)); - } - for (c = 'A'; c <= 'Z'; c++) { - str[0] = c; - cl_git_pass(git_regexp_match(®ex, str)); - } -} - -void test_core_regexp__compile_userdiff_regexps(void) -{ - size_t idx; - - for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) { - git_diff_driver_definition ddef = builtin_defs[idx]; - - cl_git_pass(git_regexp_compile(®ex, ddef.fns, ddef.flags)); - git_regexp_dispose(®ex); - - cl_git_pass(git_regexp_compile(®ex, ddef.words, 0)); - git_regexp_dispose(®ex); - } -} - -void test_core_regexp__simple_search_matches(void) -{ - cl_git_pass(git_regexp_compile(®ex, "a", 0)); - cl_git_pass(git_regexp_search(®ex, "a", 0, NULL)); -} - -void test_core_regexp__case_insensitive_search_matches(void) -{ - cl_git_pass(git_regexp_compile(®ex, "a", GIT_REGEXP_ICASE)); - cl_git_pass(git_regexp_search(®ex, "A", 0, NULL)); -} - -void test_core_regexp__nonmatching_search_returns_error(void) -{ - cl_git_pass(git_regexp_compile(®ex, "a", 0)); - cl_git_fail(git_regexp_search(®ex, "b", 0, NULL)); -} - -void test_core_regexp__search_finds_complete_match(void) -{ - git_regmatch matches[1]; - - cl_git_pass(git_regexp_compile(®ex, "abc", 0)); - cl_git_pass(git_regexp_search(®ex, "abc", 1, matches)); - cl_assert_equal_i(matches[0].start, 0); - cl_assert_equal_i(matches[0].end, 3); -} - -void test_core_regexp__search_finds_correct_offsets(void) -{ - git_regmatch matches[3]; - - cl_git_pass(git_regexp_compile(®ex, "(a*)(b*)", 0)); - cl_git_pass(git_regexp_search(®ex, "ab", 3, matches)); - cl_assert_equal_i(matches[0].start, 0); - cl_assert_equal_i(matches[0].end, 2); - cl_assert_equal_i(matches[1].start, 0); - cl_assert_equal_i(matches[1].end, 1); - cl_assert_equal_i(matches[2].start, 1); - cl_assert_equal_i(matches[2].end, 2); -} - -void test_core_regexp__search_finds_empty_group(void) -{ - git_regmatch matches[3]; - - cl_git_pass(git_regexp_compile(®ex, "(a*)(b*)c", 0)); - cl_git_pass(git_regexp_search(®ex, "ac", 3, matches)); - cl_assert_equal_i(matches[0].start, 0); - cl_assert_equal_i(matches[0].end, 2); - cl_assert_equal_i(matches[1].start, 0); - cl_assert_equal_i(matches[1].end, 1); - cl_assert_equal_i(matches[2].start, 1); - cl_assert_equal_i(matches[2].end, 1); -} - -void test_core_regexp__search_fills_matches_with_first_matching_groups(void) -{ - git_regmatch matches[2]; - - cl_git_pass(git_regexp_compile(®ex, "(a)(b)(c)", 0)); - cl_git_pass(git_regexp_search(®ex, "abc", 2, matches)); - cl_assert_equal_i(matches[0].start, 0); - cl_assert_equal_i(matches[0].end, 3); - cl_assert_equal_i(matches[1].start, 0); - cl_assert_equal_i(matches[1].end, 1); -} - -void test_core_regexp__search_skips_nonmatching_group(void) -{ - git_regmatch matches[4]; - - cl_git_pass(git_regexp_compile(®ex, "(a)(b)?(c)", 0)); - cl_git_pass(git_regexp_search(®ex, "ac", 4, matches)); - cl_assert_equal_i(matches[0].start, 0); - cl_assert_equal_i(matches[0].end, 2); - cl_assert_equal_i(matches[1].start, 0); - cl_assert_equal_i(matches[1].end, 1); - cl_assert_equal_i(matches[2].start, -1); - cl_assert_equal_i(matches[2].end, -1); - cl_assert_equal_i(matches[3].start, 1); - cl_assert_equal_i(matches[3].end, 2); -} - -void test_core_regexp__search_initializes_trailing_nonmatching_groups(void) -{ - git_regmatch matches[3]; - - cl_git_pass(git_regexp_compile(®ex, "(a)bc", 0)); - cl_git_pass(git_regexp_search(®ex, "abc", 3, matches)); - cl_assert_equal_i(matches[0].start, 0); - cl_assert_equal_i(matches[0].end, 3); - cl_assert_equal_i(matches[1].start, 0); - cl_assert_equal_i(matches[1].end, 1); - cl_assert_equal_i(matches[2].start, -1); - cl_assert_equal_i(matches[2].end, -1); -} diff --git a/tests/libgit2/core/rmdir.c b/tests/libgit2/core/rmdir.c deleted file mode 100644 index f6c66b3a4..000000000 --- a/tests/libgit2/core/rmdir.c +++ /dev/null @@ -1,120 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" - -static const char *empty_tmp_dir = "test_gitfo_rmdir_recurs_test"; - -void test_core_rmdir__initialize(void) -{ - git_str path = GIT_STR_INIT; - - cl_must_pass(p_mkdir(empty_tmp_dir, 0777)); - - cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one")); - cl_must_pass(p_mkdir(path.ptr, 0777)); - - cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_one")); - cl_must_pass(p_mkdir(path.ptr, 0777)); - - cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_two")); - cl_must_pass(p_mkdir(path.ptr, 0777)); - - cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_two/three")); - cl_must_pass(p_mkdir(path.ptr, 0777)); - - cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/two")); - cl_must_pass(p_mkdir(path.ptr, 0777)); - - git_str_dispose(&path); -} - -void test_core_rmdir__cleanup(void) -{ - if (git_fs_path_exists(empty_tmp_dir)) - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_REMOVE_FILES)); -} - -/* make sure empty dir can be deleted recursively */ -void test_core_rmdir__delete_recursive(void) -{ - git_str path = GIT_STR_INIT; - cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one")); - cl_assert(git_fs_path_exists(git_str_cstr(&path))); - - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); - - cl_assert(!git_fs_path_exists(git_str_cstr(&path))); - - git_str_dispose(&path); -} - -/* make sure non-empty dir cannot be deleted recursively */ -void test_core_rmdir__fail_to_delete_non_empty_dir(void) -{ - git_str file = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&file, empty_tmp_dir, "/two/file.txt")); - - cl_git_mkfile(git_str_cstr(&file), "dummy"); - - cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); - - cl_must_pass(p_unlink(file.ptr)); - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); - - cl_assert(!git_fs_path_exists(empty_tmp_dir)); - - git_str_dispose(&file); -} - -void test_core_rmdir__keep_base(void) -{ - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_SKIP_ROOT)); - cl_assert(git_fs_path_exists(empty_tmp_dir)); -} - -void test_core_rmdir__can_skip_non_empty_dir(void) -{ - git_str file = GIT_STR_INIT; - - cl_git_pass(git_str_joinpath(&file, empty_tmp_dir, "/two/file.txt")); - - cl_git_mkfile(git_str_cstr(&file), "dummy"); - - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_SKIP_NONEMPTY)); - cl_assert(git_fs_path_exists(git_str_cstr(&file)) == true); - - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_REMOVE_FILES)); - cl_assert(git_fs_path_exists(empty_tmp_dir) == false); - - git_str_dispose(&file); -} - -void test_core_rmdir__can_remove_empty_parents(void) -{ - git_str file = GIT_STR_INIT; - - cl_git_pass( - git_str_joinpath(&file, empty_tmp_dir, "/one/two_two/three/file.txt")); - cl_git_mkfile(git_str_cstr(&file), "dummy"); - cl_assert(git_fs_path_isfile(git_str_cstr(&file))); - - cl_git_pass(git_futils_rmdir_r("one/two_two/three/file.txt", empty_tmp_dir, - GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS)); - - cl_assert(!git_fs_path_exists(git_str_cstr(&file))); - - git_str_rtruncate_at_char(&file, '/'); /* three (only contained file.txt) */ - cl_assert(!git_fs_path_exists(git_str_cstr(&file))); - - git_str_rtruncate_at_char(&file, '/'); /* two_two (only contained three) */ - cl_assert(!git_fs_path_exists(git_str_cstr(&file))); - - git_str_rtruncate_at_char(&file, '/'); /* one (contained two_one also) */ - cl_assert(git_fs_path_exists(git_str_cstr(&file))); - - cl_assert(git_fs_path_exists(empty_tmp_dir) == true); - - git_str_dispose(&file); - - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); -} diff --git a/tests/libgit2/core/sha1.c b/tests/libgit2/core/sha1.c deleted file mode 100644 index 9ccdaab3c..000000000 --- a/tests/libgit2/core/sha1.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "clar_libgit2.h" -#include "hash.h" - -#define FIXTURE_DIR "sha1" - -void test_core_sha1__initialize(void) -{ - cl_fixture_sandbox(FIXTURE_DIR); -} - -void test_core_sha1__cleanup(void) -{ - cl_fixture_cleanup(FIXTURE_DIR); -} - -static int sha1_file(unsigned char *out, const char *filename) -{ - git_hash_ctx ctx; - char buf[2048]; - int fd, ret; - ssize_t read_len; - - fd = p_open(filename, O_RDONLY); - cl_assert(fd >= 0); - - cl_git_pass(git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)); - - while ((read_len = p_read(fd, buf, 2048)) > 0) - cl_git_pass(git_hash_update(&ctx, buf, (size_t)read_len)); - - cl_assert_equal_i(0, read_len); - p_close(fd); - - ret = git_hash_final(out, &ctx); - git_hash_ctx_cleanup(&ctx); - - return ret; -} - -void test_core_sha1__sum(void) -{ - unsigned char expected[GIT_HASH_SHA1_SIZE] = { - 0x4e, 0x72, 0x67, 0x9e, 0x3e, 0xa4, 0xd0, 0x4e, 0x0c, 0x64, - 0x2f, 0x02, 0x9e, 0x61, 0xeb, 0x80, 0x56, 0xc7, 0xed, 0x94 - }; - unsigned char actual[GIT_HASH_SHA1_SIZE]; - - cl_git_pass(sha1_file(actual, FIXTURE_DIR "/hello_c")); - cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA1_SIZE)); -} - -/* test that sha1 collision detection works when enabled */ -void test_core_sha1__detect_collision_attack(void) -{ - unsigned char actual[GIT_HASH_SHA1_SIZE]; - unsigned char expected[GIT_HASH_SHA1_SIZE] = { - 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, - 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a - }; - -#ifdef GIT_SHA1_COLLISIONDETECT - GIT_UNUSED(&expected); - cl_git_fail(sha1_file(actual, FIXTURE_DIR "/shattered-1.pdf")); - cl_assert_equal_s("SHA1 collision attack detected", git_error_last()->message); -#else - cl_git_pass(sha1_file(actual, FIXTURE_DIR "/shattered-1.pdf")); - cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA1_SIZE)); -#endif -} - diff --git a/tests/libgit2/core/sortedcache.c b/tests/libgit2/core/sortedcache.c deleted file mode 100644 index cb4e34efa..000000000 --- a/tests/libgit2/core/sortedcache.c +++ /dev/null @@ -1,363 +0,0 @@ -#include "clar_libgit2.h" -#include "sortedcache.h" - -static int name_only_cmp(const void *a, const void *b) -{ - return strcmp(a, b); -} - -void test_core_sortedcache__name_only(void) -{ - git_sortedcache *sc; - void *item; - size_t pos; - - cl_git_pass(git_sortedcache_new( - &sc, 0, NULL, NULL, name_only_cmp, NULL)); - - cl_git_pass(git_sortedcache_wlock(sc)); - cl_git_pass(git_sortedcache_upsert(&item, sc, "aaa")); - cl_git_pass(git_sortedcache_upsert(&item, sc, "bbb")); - cl_git_pass(git_sortedcache_upsert(&item, sc, "zzz")); - cl_git_pass(git_sortedcache_upsert(&item, sc, "mmm")); - cl_git_pass(git_sortedcache_upsert(&item, sc, "iii")); - git_sortedcache_wunlock(sc); - - cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); - - cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL); - cl_assert_equal_s("aaa", item); - cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL); - cl_assert_equal_s("mmm", item); - cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL); - cl_assert_equal_s("zzz", item); - cl_assert(git_sortedcache_lookup(sc, "qqq") == NULL); - - cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); - cl_assert_equal_s("aaa", item); - cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); - cl_assert_equal_s("bbb", item); - cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); - cl_assert_equal_s("iii", item); - cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); - cl_assert_equal_s("mmm", item); - cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL); - cl_assert_equal_s("zzz", item); - cl_assert(git_sortedcache_entry(sc, 5) == NULL); - - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa")); - cl_assert_equal_sz(0, pos); - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "iii")); - cl_assert_equal_sz(2, pos); - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz")); - cl_assert_equal_sz(4, pos); - cl_assert_equal_i( - GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "abc")); - - cl_git_pass(git_sortedcache_clear(sc, true)); - - cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); - cl_assert(git_sortedcache_entry(sc, 0) == NULL); - cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); - cl_assert(git_sortedcache_entry(sc, 0) == NULL); - - git_sortedcache_free(sc); -} - -typedef struct { - int value; - char smaller_value; - char path[GIT_FLEX_ARRAY]; -} sortedcache_test_struct; - -static int sortedcache_test_struct_cmp(const void *a_, const void *b_) -{ - const sortedcache_test_struct *a = a_, *b = b_; - return strcmp(a->path, b->path); -} - -static void sortedcache_test_struct_free(void *payload, void *item_) -{ - sortedcache_test_struct *item = item_; - int *count = payload; - (*count)++; - item->smaller_value = 0; -} - -void test_core_sortedcache__in_memory(void) -{ - git_sortedcache *sc; - sortedcache_test_struct *item; - int free_count = 0; - - cl_git_pass(git_sortedcache_new( - &sc, offsetof(sortedcache_test_struct, path), - sortedcache_test_struct_free, &free_count, - sortedcache_test_struct_cmp, NULL)); - - cl_git_pass(git_sortedcache_wlock(sc)); - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "aaa")); - item->value = 10; - item->smaller_value = 1; - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "bbb")); - item->value = 20; - item->smaller_value = 2; - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "zzz")); - item->value = 30; - item->smaller_value = 26; - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "mmm")); - item->value = 40; - item->smaller_value = 14; - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "iii")); - item->value = 50; - item->smaller_value = 9; - git_sortedcache_wunlock(sc); - - cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); - - cl_git_pass(git_sortedcache_rlock(sc)); - - cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL); - cl_assert_equal_s("aaa", item->path); - cl_assert_equal_i(10, item->value); - cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL); - cl_assert_equal_s("mmm", item->path); - cl_assert_equal_i(40, item->value); - cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL); - cl_assert_equal_s("zzz", item->path); - cl_assert_equal_i(30, item->value); - cl_assert(git_sortedcache_lookup(sc, "abc") == NULL); - - /* not on Windows: - * cl_git_pass(git_sortedcache_rlock(sc)); -- grab more than one - */ - - cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); - cl_assert_equal_s("aaa", item->path); - cl_assert_equal_i(10, item->value); - cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); - cl_assert_equal_s("bbb", item->path); - cl_assert_equal_i(20, item->value); - cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); - cl_assert_equal_s("iii", item->path); - cl_assert_equal_i(50, item->value); - cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); - cl_assert_equal_s("mmm", item->path); - cl_assert_equal_i(40, item->value); - cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL); - cl_assert_equal_s("zzz", item->path); - cl_assert_equal_i(30, item->value); - cl_assert(git_sortedcache_entry(sc, 5) == NULL); - - git_sortedcache_runlock(sc); - /* git_sortedcache_runlock(sc); */ - - cl_assert_equal_i(0, free_count); - - cl_git_pass(git_sortedcache_clear(sc, true)); - - cl_assert_equal_i(5, free_count); - - cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); - cl_assert(git_sortedcache_entry(sc, 0) == NULL); - cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); - cl_assert(git_sortedcache_entry(sc, 0) == NULL); - - free_count = 0; - - cl_git_pass(git_sortedcache_wlock(sc)); - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "testing")); - item->value = 10; - item->smaller_value = 3; - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "again")); - item->value = 20; - item->smaller_value = 1; - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "final")); - item->value = 30; - item->smaller_value = 2; - git_sortedcache_wunlock(sc); - - cl_assert_equal_sz(3, git_sortedcache_entrycount(sc)); - - cl_assert((item = git_sortedcache_lookup(sc, "testing")) != NULL); - cl_assert_equal_s("testing", item->path); - cl_assert_equal_i(10, item->value); - cl_assert((item = git_sortedcache_lookup(sc, "again")) != NULL); - cl_assert_equal_s("again", item->path); - cl_assert_equal_i(20, item->value); - cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL); - cl_assert_equal_s("final", item->path); - cl_assert_equal_i(30, item->value); - cl_assert(git_sortedcache_lookup(sc, "zzz") == NULL); - - cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); - cl_assert_equal_s("again", item->path); - cl_assert_equal_i(20, item->value); - cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); - cl_assert_equal_s("final", item->path); - cl_assert_equal_i(30, item->value); - cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); - cl_assert_equal_s("testing", item->path); - cl_assert_equal_i(10, item->value); - cl_assert(git_sortedcache_entry(sc, 3) == NULL); - - { - size_t pos; - - cl_git_pass(git_sortedcache_wlock(sc)); - - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "again")); - cl_assert_equal_sz(0, pos); - cl_git_pass(git_sortedcache_remove(sc, pos)); - cl_assert_equal_i( - GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "again")); - - cl_assert_equal_sz(2, git_sortedcache_entrycount(sc)); - - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "testing")); - cl_assert_equal_sz(1, pos); - cl_git_pass(git_sortedcache_remove(sc, pos)); - cl_assert_equal_i( - GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "testing")); - - cl_assert_equal_sz(1, git_sortedcache_entrycount(sc)); - - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final")); - cl_assert_equal_sz(0, pos); - cl_git_pass(git_sortedcache_remove(sc, pos)); - cl_assert_equal_i( - GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "final")); - - cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); - - git_sortedcache_wunlock(sc); - } - - git_sortedcache_free(sc); - - cl_assert_equal_i(3, free_count); -} - -static void sortedcache_test_reload(git_sortedcache *sc) -{ - int count = 0; - git_str buf = GIT_STR_INIT; - char *scan, *after; - sortedcache_test_struct *item; - - cl_assert(git_sortedcache_lockandload(sc, &buf) > 0); - - cl_git_pass(git_sortedcache_clear(sc, false)); /* clear once we already have lock */ - - for (scan = buf.ptr; *scan; scan = after + 1) { - int val = strtol(scan, &after, 0); - cl_assert(after > scan); - scan = after; - - for (scan = after; git__isspace(*scan); ++scan) /* find start */; - for (after = scan; *after && *after != '\n'; ++after) /* find eol */; - *after = '\0'; - - cl_git_pass(git_sortedcache_upsert((void **)&item, sc, scan)); - - item->value = val; - item->smaller_value = (char)(count++); - } - - git_sortedcache_wunlock(sc); - - git_str_dispose(&buf); -} - -void test_core_sortedcache__on_disk(void) -{ - git_sortedcache *sc; - sortedcache_test_struct *item; - int free_count = 0; - size_t pos; - - cl_git_mkfile("cacheitems.txt", "10 abc\n20 bcd\n30 cde\n"); - - cl_git_pass(git_sortedcache_new( - &sc, offsetof(sortedcache_test_struct, path), - sortedcache_test_struct_free, &free_count, - sortedcache_test_struct_cmp, "cacheitems.txt")); - - /* should need to reload the first time */ - - sortedcache_test_reload(sc); - - /* test what we loaded */ - - cl_assert_equal_sz(3, git_sortedcache_entrycount(sc)); - - cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL); - cl_assert_equal_s("abc", item->path); - cl_assert_equal_i(10, item->value); - cl_assert((item = git_sortedcache_lookup(sc, "cde")) != NULL); - cl_assert_equal_s("cde", item->path); - cl_assert_equal_i(30, item->value); - cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); - - cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); - cl_assert_equal_s("abc", item->path); - cl_assert_equal_i(10, item->value); - cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); - cl_assert_equal_s("bcd", item->path); - cl_assert_equal_i(20, item->value); - cl_assert(git_sortedcache_entry(sc, 3) == NULL); - - /* should not need to reload this time */ - - cl_assert_equal_i(0, git_sortedcache_lockandload(sc, NULL)); - - /* rewrite ondisk file and reload */ - - cl_assert_equal_i(0, free_count); - - cl_git_rewritefile( - "cacheitems.txt", "100 abc\n200 zzz\n500 aaa\n10 final\n"); - sortedcache_test_reload(sc); - - cl_assert_equal_i(3, free_count); - - /* test what we loaded */ - - cl_assert_equal_sz(4, git_sortedcache_entrycount(sc)); - - cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL); - cl_assert_equal_s("abc", item->path); - cl_assert_equal_i(100, item->value); - cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL); - cl_assert_equal_s("final", item->path); - cl_assert_equal_i(10, item->value); - cl_assert(git_sortedcache_lookup(sc, "cde") == NULL); - - cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); - cl_assert_equal_s("aaa", item->path); - cl_assert_equal_i(500, item->value); - cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); - cl_assert_equal_s("final", item->path); - cl_assert_equal_i(10, item->value); - cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); - cl_assert_equal_s("zzz", item->path); - cl_assert_equal_i(200, item->value); - - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa")); - cl_assert_equal_sz(0, pos); - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "abc")); - cl_assert_equal_sz(1, pos); - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final")); - cl_assert_equal_sz(2, pos); - cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz")); - cl_assert_equal_sz(3, pos); - cl_assert_equal_i( - GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "missing")); - cl_assert_equal_i( - GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "cde")); - - git_sortedcache_free(sc); - - cl_assert_equal_i(7, free_count); -} - diff --git a/tests/libgit2/core/stat.c b/tests/libgit2/core/stat.c deleted file mode 100644 index 210072fe3..000000000 --- a/tests/libgit2/core/stat.c +++ /dev/null @@ -1,113 +0,0 @@ -#include "clar_libgit2.h" -#include "futils.h" -#include "posix.h" - -void test_core_stat__initialize(void) -{ - cl_git_pass(git_futils_mkdir("root/d1/d2", 0755, GIT_MKDIR_PATH)); - cl_git_mkfile("root/file", "whatever\n"); - cl_git_mkfile("root/d1/file", "whatever\n"); -} - -void test_core_stat__cleanup(void) -{ - git_futils_rmdir_r("root", NULL, GIT_RMDIR_REMOVE_FILES); -} - -#define cl_assert_error(val) \ - do { err = errno; cl_assert_equal_i((val), err); } while (0) - -void test_core_stat__0(void) -{ - struct stat st; - int err; - - cl_assert_equal_i(0, p_lstat("root", &st)); - cl_assert(S_ISDIR(st.st_mode)); - cl_assert_error(0); - - cl_assert_equal_i(0, p_lstat("root/", &st)); - cl_assert(S_ISDIR(st.st_mode)); - cl_assert_error(0); - - cl_assert_equal_i(0, p_lstat("root/file", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_error(0); - - cl_assert_equal_i(0, p_lstat("root/d1", &st)); - cl_assert(S_ISDIR(st.st_mode)); - cl_assert_error(0); - - cl_assert_equal_i(0, p_lstat("root/d1/", &st)); - cl_assert(S_ISDIR(st.st_mode)); - cl_assert_error(0); - - cl_assert_equal_i(0, p_lstat("root/d1/file", &st)); - cl_assert(S_ISREG(st.st_mode)); - cl_assert_error(0); - - cl_assert(p_lstat("root/missing", &st) < 0); - cl_assert_error(ENOENT); - - cl_assert(p_lstat("root/missing/but/could/be/created", &st) < 0); - cl_assert_error(ENOENT); - - cl_assert(p_lstat_posixly("root/missing/but/could/be/created", &st) < 0); - cl_assert_error(ENOENT); - - cl_assert(p_lstat("root/d1/missing", &st) < 0); - cl_assert_error(ENOENT); - - cl_assert(p_lstat("root/d1/missing/deeper/path", &st) < 0); - cl_assert_error(ENOENT); - - cl_assert(p_lstat_posixly("root/d1/missing/deeper/path", &st) < 0); - cl_assert_error(ENOENT); - - cl_assert(p_lstat_posixly("root/d1/file/deeper/path", &st) < 0); - cl_assert_error(ENOTDIR); - - cl_assert(p_lstat("root/file/invalid", &st) < 0); -#ifdef GIT_WIN32 - cl_assert_error(ENOENT); -#else - cl_assert_error(ENOTDIR); -#endif - - cl_assert(p_lstat_posixly("root/file/invalid", &st) < 0); - cl_assert_error(ENOTDIR); - - cl_assert(p_lstat("root/file/invalid/deeper_path", &st) < 0); -#ifdef GIT_WIN32 - cl_assert_error(ENOENT); -#else - cl_assert_error(ENOTDIR); -#endif - - cl_assert(p_lstat_posixly("root/file/invalid/deeper_path", &st) < 0); - cl_assert_error(ENOTDIR); - - cl_assert(p_lstat_posixly("root/d1/file/extra", &st) < 0); - cl_assert_error(ENOTDIR); - - cl_assert(p_lstat_posixly("root/d1/file/further/invalid/items", &st) < 0); - cl_assert_error(ENOTDIR); -} - -void test_core_stat__root(void) -{ - const char *sandbox = clar_sandbox_path(); - git_str root = GIT_STR_INIT; - int root_len; - struct stat st; - - root_len = git_fs_path_root(sandbox); - cl_assert(root_len >= 0); - - git_str_set(&root, sandbox, root_len+1); - - cl_must_pass(p_stat(root.ptr, &st)); - cl_assert(S_ISDIR(st.st_mode)); - - git_str_dispose(&root); -} diff --git a/tests/libgit2/core/string.c b/tests/libgit2/core/string.c deleted file mode 100644 index 928dfbcc1..000000000 --- a/tests/libgit2/core/string.c +++ /dev/null @@ -1,136 +0,0 @@ -#include "clar_libgit2.h" - -/* compare prefixes */ -void test_core_string__0(void) -{ - cl_assert(git__prefixcmp("", "") == 0); - cl_assert(git__prefixcmp("a", "") == 0); - cl_assert(git__prefixcmp("", "a") < 0); - cl_assert(git__prefixcmp("a", "b") < 0); - cl_assert(git__prefixcmp("b", "a") > 0); - cl_assert(git__prefixcmp("ab", "a") == 0); - cl_assert(git__prefixcmp("ab", "ac") < 0); - cl_assert(git__prefixcmp("ab", "aa") > 0); -} - -/* compare suffixes */ -void test_core_string__1(void) -{ - cl_assert(git__suffixcmp("", "") == 0); - cl_assert(git__suffixcmp("a", "") == 0); - cl_assert(git__suffixcmp("", "a") < 0); - cl_assert(git__suffixcmp("a", "b") < 0); - cl_assert(git__suffixcmp("b", "a") > 0); - cl_assert(git__suffixcmp("ba", "a") == 0); - cl_assert(git__suffixcmp("zaa", "ac") < 0); - cl_assert(git__suffixcmp("zaz", "ac") > 0); -} - -/* compare icase sorting with case equality */ -void test_core_string__2(void) -{ - cl_assert(git__strcasesort_cmp("", "") == 0); - cl_assert(git__strcasesort_cmp("foo", "foo") == 0); - cl_assert(git__strcasesort_cmp("foo", "bar") > 0); - cl_assert(git__strcasesort_cmp("bar", "foo") < 0); - cl_assert(git__strcasesort_cmp("foo", "FOO") > 0); - cl_assert(git__strcasesort_cmp("FOO", "foo") < 0); - cl_assert(git__strcasesort_cmp("foo", "BAR") > 0); - cl_assert(git__strcasesort_cmp("BAR", "foo") < 0); - cl_assert(git__strcasesort_cmp("fooBar", "foobar") < 0); -} - -/* compare prefixes with len */ -void test_core_string__prefixncmp(void) -{ - cl_assert(git__prefixncmp("", 0, "") == 0); - cl_assert(git__prefixncmp("a", 1, "") == 0); - cl_assert(git__prefixncmp("", 0, "a") < 0); - cl_assert(git__prefixncmp("a", 1, "b") < 0); - cl_assert(git__prefixncmp("b", 1, "a") > 0); - cl_assert(git__prefixncmp("ab", 2, "a") == 0); - cl_assert(git__prefixncmp("ab", 1, "a") == 0); - cl_assert(git__prefixncmp("ab", 2, "ac") < 0); - cl_assert(git__prefixncmp("a", 1, "ac") < 0); - cl_assert(git__prefixncmp("ab", 1, "ac") < 0); - cl_assert(git__prefixncmp("ab", 2, "aa") > 0); - cl_assert(git__prefixncmp("ab", 1, "aa") < 0); -} - -/* compare prefixes with len */ -void test_core_string__prefixncmp_icase(void) -{ - cl_assert(git__prefixncmp_icase("", 0, "") == 0); - cl_assert(git__prefixncmp_icase("a", 1, "") == 0); - cl_assert(git__prefixncmp_icase("", 0, "a") < 0); - cl_assert(git__prefixncmp_icase("a", 1, "b") < 0); - cl_assert(git__prefixncmp_icase("A", 1, "b") < 0); - cl_assert(git__prefixncmp_icase("a", 1, "B") < 0); - cl_assert(git__prefixncmp_icase("b", 1, "a") > 0); - cl_assert(git__prefixncmp_icase("B", 1, "a") > 0); - cl_assert(git__prefixncmp_icase("b", 1, "A") > 0); - cl_assert(git__prefixncmp_icase("ab", 2, "a") == 0); - cl_assert(git__prefixncmp_icase("Ab", 2, "a") == 0); - cl_assert(git__prefixncmp_icase("ab", 2, "A") == 0); - cl_assert(git__prefixncmp_icase("ab", 1, "a") == 0); - cl_assert(git__prefixncmp_icase("ab", 2, "ac") < 0); - cl_assert(git__prefixncmp_icase("Ab", 2, "ac") < 0); - cl_assert(git__prefixncmp_icase("ab", 2, "Ac") < 0); - cl_assert(git__prefixncmp_icase("a", 1, "ac") < 0); - cl_assert(git__prefixncmp_icase("ab", 1, "ac") < 0); - cl_assert(git__prefixncmp_icase("ab", 2, "aa") > 0); - cl_assert(git__prefixncmp_icase("ab", 1, "aa") < 0); -} - -void test_core_string__strcmp(void) -{ - cl_assert(git__strcmp("", "") == 0); - cl_assert(git__strcmp("foo", "foo") == 0); - cl_assert(git__strcmp("Foo", "foo") < 0); - cl_assert(git__strcmp("foo", "FOO") > 0); - cl_assert(git__strcmp("foo", "fOO") > 0); - - cl_assert(strcmp("rt\303\202of", "rt dev\302\266h") > 0); - cl_assert(strcmp("e\342\202\254ghi=", "et") > 0); - cl_assert(strcmp("rt dev\302\266h", "rt\303\202of") < 0); - cl_assert(strcmp("et", "e\342\202\254ghi=") < 0); - cl_assert(strcmp("\303\215", "\303\255") < 0); - - cl_assert(git__strcmp("rt\303\202of", "rt dev\302\266h") > 0); - cl_assert(git__strcmp("e\342\202\254ghi=", "et") > 0); - cl_assert(git__strcmp("rt dev\302\266h", "rt\303\202of") < 0); - cl_assert(git__strcmp("et", "e\342\202\254ghi=") < 0); - cl_assert(git__strcmp("\303\215", "\303\255") < 0); -} - -void test_core_string__strcasecmp(void) -{ - cl_assert(git__strcasecmp("", "") == 0); - cl_assert(git__strcasecmp("foo", "foo") == 0); - cl_assert(git__strcasecmp("foo", "Foo") == 0); - cl_assert(git__strcasecmp("foo", "FOO") == 0); - cl_assert(git__strcasecmp("foo", "fOO") == 0); - - cl_assert(strcasecmp("rt\303\202of", "rt dev\302\266h") > 0); - cl_assert(strcasecmp("e\342\202\254ghi=", "et") > 0); - cl_assert(strcasecmp("rt dev\302\266h", "rt\303\202of") < 0); - cl_assert(strcasecmp("et", "e\342\202\254ghi=") < 0); - cl_assert(strcasecmp("\303\215", "\303\255") < 0); - - cl_assert(git__strcasecmp("rt\303\202of", "rt dev\302\266h") > 0); - cl_assert(git__strcasecmp("e\342\202\254ghi=", "et") > 0); - cl_assert(git__strcasecmp("rt dev\302\266h", "rt\303\202of") < 0); - cl_assert(git__strcasecmp("et", "e\342\202\254ghi=") < 0); - cl_assert(git__strcasecmp("\303\215", "\303\255") < 0); -} - -void test_core_string__strlcmp(void) -{ - const char foo[3] = { 'f', 'o', 'o' }; - - cl_assert(git__strlcmp("foo", "foo", 3) == 0); - cl_assert(git__strlcmp("foo", foo, 3) == 0); - cl_assert(git__strlcmp("foo", "foobar", 3) == 0); - cl_assert(git__strlcmp("foobar", "foo", 3) > 0); - cl_assert(git__strlcmp("foo", "foobar", 6) < 0); -} diff --git a/tests/libgit2/core/strmap.c b/tests/libgit2/core/strmap.c deleted file mode 100644 index ba118ae1e..000000000 --- a/tests/libgit2/core/strmap.c +++ /dev/null @@ -1,190 +0,0 @@ -#include "clar_libgit2.h" -#include "strmap.h" - -static git_strmap *g_table; - -void test_core_strmap__initialize(void) -{ - cl_git_pass(git_strmap_new(&g_table)); - cl_assert(g_table != NULL); -} - -void test_core_strmap__cleanup(void) -{ - git_strmap_free(g_table); -} - -void test_core_strmap__0(void) -{ - cl_assert(git_strmap_size(g_table) == 0); -} - -static void insert_strings(git_strmap *table, size_t count) -{ - size_t i, j, over; - char *str; - - for (i = 0; i < count; ++i) { - str = malloc(10); - for (j = 0; j < 10; ++j) - str[j] = 'a' + (i % 26); - str[9] = '\0'; - - /* if > 26, then encode larger value in first letters */ - for (j = 0, over = i / 26; over > 0; j++, over = over / 26) - str[j] = 'A' + (over % 26); - - cl_git_pass(git_strmap_set(table, str, str)); - } - - cl_assert_equal_i(git_strmap_size(table), count); -} - -void test_core_strmap__inserted_strings_can_be_retrieved(void) -{ - char *str; - int i; - - insert_strings(g_table, 20); - - cl_assert(git_strmap_exists(g_table, "aaaaaaaaa")); - cl_assert(git_strmap_exists(g_table, "ggggggggg")); - cl_assert(!git_strmap_exists(g_table, "aaaaaaaab")); - cl_assert(!git_strmap_exists(g_table, "abcdefghi")); - - i = 0; - git_strmap_foreach_value(g_table, str, { i++; free(str); }); - cl_assert(i == 20); -} - -void test_core_strmap__deleted_entry_cannot_be_retrieved(void) -{ - char *str; - int i; - - insert_strings(g_table, 20); - - cl_assert(git_strmap_exists(g_table, "bbbbbbbbb")); - str = git_strmap_get(g_table, "bbbbbbbbb"); - cl_assert_equal_s(str, "bbbbbbbbb"); - cl_git_pass(git_strmap_delete(g_table, "bbbbbbbbb")); - free(str); - - cl_assert(!git_strmap_exists(g_table, "bbbbbbbbb")); - - i = 0; - git_strmap_foreach_value(g_table, str, { i++; free(str); }); - cl_assert_equal_i(i, 19); -} - -void test_core_strmap__inserting_many_keys_succeeds(void) -{ - char *str; - int i; - - insert_strings(g_table, 10000); - - i = 0; - git_strmap_foreach_value(g_table, str, { i++; free(str); }); - cl_assert_equal_i(i, 10000); -} - -void test_core_strmap__get_succeeds_with_existing_entries(void) -{ - const char *keys[] = { "foo", "bar", "gobble" }; - char *values[] = { "oof", "rab", "elbbog" }; - size_t i; - - for (i = 0; i < ARRAY_SIZE(keys); i++) - cl_git_pass(git_strmap_set(g_table, keys[i], values[i])); - - cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); - cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab"); - cl_assert_equal_s(git_strmap_get(g_table, "gobble"), "elbbog"); -} - -void test_core_strmap__get_returns_null_on_nonexisting_key(void) -{ - const char *keys[] = { "foo", "bar", "gobble" }; - char *values[] = { "oof", "rab", "elbbog" }; - size_t i; - - for (i = 0; i < ARRAY_SIZE(keys); i++) - cl_git_pass(git_strmap_set(g_table, keys[i], values[i])); - - cl_assert_equal_p(git_strmap_get(g_table, "other"), NULL); -} - -void test_core_strmap__set_persists_key(void) -{ - cl_git_pass(git_strmap_set(g_table, "foo", "oof")); - cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); -} - -void test_core_strmap__set_persists_multpile_keys(void) -{ - cl_git_pass(git_strmap_set(g_table, "foo", "oof")); - cl_git_pass(git_strmap_set(g_table, "bar", "rab")); - cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); - cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab"); -} - -void test_core_strmap__set_updates_existing_key(void) -{ - cl_git_pass(git_strmap_set(g_table, "foo", "oof")); - cl_git_pass(git_strmap_set(g_table, "bar", "rab")); - cl_git_pass(git_strmap_set(g_table, "gobble", "elbbog")); - cl_assert_equal_i(git_strmap_size(g_table), 3); - - cl_git_pass(git_strmap_set(g_table, "foo", "other")); - cl_assert_equal_i(git_strmap_size(g_table), 3); - - cl_assert_equal_s(git_strmap_get(g_table, "foo"), "other"); -} - -void test_core_strmap__iteration(void) -{ - struct { - char *key; - char *value; - int seen; - } entries[] = { - { "foo", "oof" }, - { "bar", "rab" }, - { "gobble", "elbbog" }, - }; - const char *key, *value; - size_t i, n; - - for (i = 0; i < ARRAY_SIZE(entries); i++) - cl_git_pass(git_strmap_set(g_table, entries[i].key, entries[i].value)); - - i = 0, n = 0; - while (git_strmap_iterate((void **) &value, g_table, &i, &key) == 0) { - size_t j; - - for (j = 0; j < ARRAY_SIZE(entries); j++) { - if (strcmp(entries[j].key, key)) - continue; - - cl_assert_equal_i(entries[j].seen, 0); - cl_assert_equal_s(entries[j].value, value); - entries[j].seen++; - break; - } - - n++; - } - - for (i = 0; i < ARRAY_SIZE(entries); i++) - cl_assert_equal_i(entries[i].seen, 1); - - cl_assert_equal_i(n, ARRAY_SIZE(entries)); -} - -void test_core_strmap__iterating_empty_map_stops_immediately(void) -{ - size_t i = 0; - - cl_git_fail_with(git_strmap_iterate(NULL, g_table, &i, NULL), GIT_ITEROVER); -} diff --git a/tests/libgit2/core/strtol.c b/tests/libgit2/core/strtol.c deleted file mode 100644 index 851b91b0a..000000000 --- a/tests/libgit2/core/strtol.c +++ /dev/null @@ -1,128 +0,0 @@ -#include "clar_libgit2.h" - -static void assert_l32_parses(const char *string, int32_t expected, int base) -{ - int32_t i; - cl_git_pass(git__strntol32(&i, string, strlen(string), NULL, base)); - cl_assert_equal_i(i, expected); -} - -static void assert_l32_fails(const char *string, int base) -{ - int32_t i; - cl_git_fail(git__strntol32(&i, string, strlen(string), NULL, base)); -} - -static void assert_l64_parses(const char *string, int64_t expected, int base) -{ - int64_t i; - cl_git_pass(git__strntol64(&i, string, strlen(string), NULL, base)); - cl_assert_equal_i(i, expected); -} - -static void assert_l64_fails(const char *string, int base) -{ - int64_t i; - cl_git_fail(git__strntol64(&i, string, strlen(string), NULL, base)); -} - -void test_core_strtol__int32(void) -{ - assert_l32_parses("123", 123, 10); - assert_l32_parses(" +123 ", 123, 10); - assert_l32_parses(" -123 ", -123, 10); - assert_l32_parses(" +2147483647 ", 2147483647, 10); - assert_l32_parses(" -2147483648 ", INT64_C(-2147483648), 10); - assert_l32_parses("A", 10, 16); - assert_l32_parses("1x1", 1, 10); - - assert_l32_fails("", 10); - assert_l32_fails("a", 10); - assert_l32_fails("x10x", 10); - assert_l32_fails(" 2147483657 ", 10); - assert_l32_fails(" -2147483657 ", 10); -} - -void test_core_strtol__int64(void) -{ - assert_l64_parses("123", 123, 10); - assert_l64_parses(" +123 ", 123, 10); - assert_l64_parses(" -123 ", -123, 10); - assert_l64_parses(" +2147483647 ", 2147483647, 10); - assert_l64_parses(" -2147483648 ", INT64_C(-2147483648), 10); - assert_l64_parses(" 2147483657 ", INT64_C(2147483657), 10); - assert_l64_parses(" -2147483657 ", INT64_C(-2147483657), 10); - assert_l64_parses(" 9223372036854775807 ", INT64_MAX, 10); - assert_l64_parses(" -9223372036854775808 ", INT64_MIN, 10); - assert_l64_parses(" 0x7fffffffffffffff ", INT64_MAX, 16); - assert_l64_parses(" -0x8000000000000000 ", INT64_MIN, 16); - assert_l64_parses("1a", 26, 16); - assert_l64_parses("1A", 26, 16); - - assert_l64_fails("", 10); - assert_l64_fails("a", 10); - assert_l64_fails("x10x", 10); - assert_l64_fails("0x8000000000000000", 16); - assert_l64_fails("-0x8000000000000001", 16); -} - -void test_core_strtol__base_autodetection(void) -{ - assert_l64_parses("0", 0, 0); - assert_l64_parses("00", 0, 0); - assert_l64_parses("0x", 0, 0); - assert_l64_parses("0foobar", 0, 0); - assert_l64_parses("07", 7, 0); - assert_l64_parses("017", 15, 0); - assert_l64_parses("0x8", 8, 0); - assert_l64_parses("0x18", 24, 0); -} - -void test_core_strtol__buffer_length_with_autodetection_truncates(void) -{ - int64_t i64; - - cl_git_pass(git__strntol64(&i64, "011", 2, NULL, 0)); - cl_assert_equal_i(i64, 1); - cl_git_pass(git__strntol64(&i64, "0x11", 3, NULL, 0)); - cl_assert_equal_i(i64, 1); -} - -void test_core_strtol__buffer_length_truncates(void) -{ - int32_t i32; - int64_t i64; - - cl_git_pass(git__strntol32(&i32, "11", 1, NULL, 10)); - cl_assert_equal_i(i32, 1); - - cl_git_pass(git__strntol64(&i64, "11", 1, NULL, 10)); - cl_assert_equal_i(i64, 1); -} - -void test_core_strtol__buffer_length_with_leading_ws_truncates(void) -{ - int64_t i64; - - cl_git_fail(git__strntol64(&i64, " 1", 1, NULL, 10)); - - cl_git_pass(git__strntol64(&i64, " 11", 2, NULL, 10)); - cl_assert_equal_i(i64, 1); -} - -void test_core_strtol__buffer_length_with_leading_sign_truncates(void) -{ - int64_t i64; - - cl_git_fail(git__strntol64(&i64, "-1", 1, NULL, 10)); - - cl_git_pass(git__strntol64(&i64, "-11", 2, NULL, 10)); - cl_assert_equal_i(i64, -1); -} - -void test_core_strtol__error_message_cuts_off(void) -{ - assert_l32_fails("2147483657foobar", 10); - cl_assert(strstr(git_error_last()->message, "2147483657") != NULL); - cl_assert(strstr(git_error_last()->message, "foobar") == NULL); -} diff --git a/tests/libgit2/core/utf8.c b/tests/libgit2/core/utf8.c deleted file mode 100644 index e1987b8d6..000000000 --- a/tests/libgit2/core/utf8.c +++ /dev/null @@ -1,20 +0,0 @@ -#include "clar_libgit2.h" -#include "utf8.h" - -void test_core_utf8__char_length(void) -{ - cl_assert_equal_i(0, git_utf8_char_length("", 0)); - cl_assert_equal_i(1, git_utf8_char_length("$", 1)); - cl_assert_equal_i(5, git_utf8_char_length("abcde", 5)); - cl_assert_equal_i(1, git_utf8_char_length("\xc2\xa2", 2)); - cl_assert_equal_i(2, git_utf8_char_length("\x24\xc2\xa2", 3)); - cl_assert_equal_i(1, git_utf8_char_length("\xf0\x90\x8d\x88", 4)); - - /* uncontinued character counted as single characters */ - cl_assert_equal_i(2, git_utf8_char_length("\x24\xc2", 2)); - cl_assert_equal_i(3, git_utf8_char_length("\x24\xc2\xc2\xa2", 4)); - - /* invalid characters are counted as single characters */ - cl_assert_equal_i(4, git_utf8_char_length("\x24\xc0\xc0\x34", 4)); - cl_assert_equal_i(4, git_utf8_char_length("\x24\xf5\xfd\xc2", 4)); -} diff --git a/tests/libgit2/core/vector.c b/tests/libgit2/core/vector.c deleted file mode 100644 index 08cd2c19b..000000000 --- a/tests/libgit2/core/vector.c +++ /dev/null @@ -1,430 +0,0 @@ -#include - -#include "clar_libgit2.h" -#include "vector.h" - -/* initial size of 1 would cause writing past array bounds */ -void test_core_vector__0(void) -{ - git_vector x; - int i; - cl_git_pass(git_vector_init(&x, 1, NULL)); - for (i = 0; i < 10; ++i) { - git_vector_insert(&x, (void*) 0xabc); - } - git_vector_free(&x); -} - - -/* don't read past array bounds on remove() */ -void test_core_vector__1(void) -{ - git_vector x; - /* make initial capacity exact for our insertions. */ - cl_git_pass(git_vector_init(&x, 3, NULL)); - git_vector_insert(&x, (void*) 0xabc); - git_vector_insert(&x, (void*) 0xdef); - git_vector_insert(&x, (void*) 0x123); - - git_vector_remove(&x, 0); /* used to read past array bounds. */ - git_vector_free(&x); -} - - -static int test_cmp(const void *a, const void *b) -{ - return *(const int *)a - *(const int *)b; -} - -/* remove duplicates */ -void test_core_vector__2(void) -{ - git_vector x; - int *ptrs[2]; - - ptrs[0] = git__malloc(sizeof(int)); - ptrs[1] = git__malloc(sizeof(int)); - - *ptrs[0] = 2; - *ptrs[1] = 1; - - cl_git_pass(git_vector_init(&x, 5, test_cmp)); - cl_git_pass(git_vector_insert(&x, ptrs[0])); - cl_git_pass(git_vector_insert(&x, ptrs[1])); - cl_git_pass(git_vector_insert(&x, ptrs[1])); - cl_git_pass(git_vector_insert(&x, ptrs[0])); - cl_git_pass(git_vector_insert(&x, ptrs[1])); - cl_assert(x.length == 5); - - git_vector_uniq(&x, NULL); - cl_assert(x.length == 2); - - git_vector_free(&x); - - git__free(ptrs[0]); - git__free(ptrs[1]); -} - - -static int compare_them(const void *a, const void *b) -{ - return (int)((intptr_t)a - (intptr_t)b); -} - -/* insert_sorted */ -void test_core_vector__3(void) -{ - git_vector x; - intptr_t i; - cl_git_pass(git_vector_init(&x, 1, &compare_them)); - - for (i = 0; i < 10; i += 2) { - git_vector_insert_sorted(&x, (void*)(i + 1), NULL); - } - - for (i = 9; i > 0; i -= 2) { - git_vector_insert_sorted(&x, (void*)(i + 1), NULL); - } - - cl_assert(x.length == 10); - for (i = 0; i < 10; ++i) { - cl_assert(git_vector_get(&x, i) == (void*)(i + 1)); - } - - git_vector_free(&x); -} - -/* insert_sorted with duplicates */ -void test_core_vector__4(void) -{ - git_vector x; - intptr_t i; - cl_git_pass(git_vector_init(&x, 1, &compare_them)); - - for (i = 0; i < 10; i += 2) { - git_vector_insert_sorted(&x, (void*)(i + 1), NULL); - } - - for (i = 9; i > 0; i -= 2) { - git_vector_insert_sorted(&x, (void*)(i + 1), NULL); - } - - for (i = 0; i < 10; i += 2) { - git_vector_insert_sorted(&x, (void*)(i + 1), NULL); - } - - for (i = 9; i > 0; i -= 2) { - git_vector_insert_sorted(&x, (void*)(i + 1), NULL); - } - - cl_assert(x.length == 20); - for (i = 0; i < 20; ++i) { - cl_assert(git_vector_get(&x, i) == (void*)(i / 2 + 1)); - } - - git_vector_free(&x); -} - -typedef struct { - int content; - int count; -} my_struct; - -static int _struct_count = 0; - -static int compare_structs(const void *a, const void *b) -{ - return ((const my_struct *)a)->content - - ((const my_struct *)b)->content; -} - -static int merge_structs(void **old_raw, void *new) -{ - my_struct *old = *(my_struct **)old_raw; - cl_assert(((my_struct *)old)->content == ((my_struct *)new)->content); - ((my_struct *)old)->count += 1; - git__free(new); - _struct_count--; - return GIT_EEXISTS; -} - -static my_struct *alloc_struct(int value) -{ - my_struct *st = git__malloc(sizeof(my_struct)); - st->content = value; - st->count = 0; - _struct_count++; - return st; -} - -/* insert_sorted with duplicates and special handling */ -void test_core_vector__5(void) -{ - git_vector x; - int i; - - cl_git_pass(git_vector_init(&x, 1, &compare_structs)); - - for (i = 0; i < 10; i += 2) - git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); - - for (i = 9; i > 0; i -= 2) - git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); - - cl_assert(x.length == 10); - cl_assert(_struct_count == 10); - - for (i = 0; i < 10; i += 2) - git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); - - for (i = 9; i > 0; i -= 2) - git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); - - cl_assert(x.length == 10); - cl_assert(_struct_count == 10); - - for (i = 0; i < 10; ++i) { - cl_assert(((my_struct *)git_vector_get(&x, i))->content == i); - git__free(git_vector_get(&x, i)); - _struct_count--; - } - - git_vector_free(&x); -} - -static int remove_ones(const git_vector *v, size_t idx, void *p) -{ - GIT_UNUSED(p); - return (git_vector_get(v, idx) == (void *)0x001); -} - -/* Test removal based on callback */ -void test_core_vector__remove_matching(void) -{ - git_vector x; - size_t i; - void *compare; - - cl_git_pass(git_vector_init(&x, 1, NULL)); - git_vector_insert(&x, (void*) 0x001); - - cl_assert(x.length == 1); - git_vector_remove_matching(&x, remove_ones, NULL); - cl_assert(x.length == 0); - - git_vector_insert(&x, (void*) 0x001); - git_vector_insert(&x, (void*) 0x001); - git_vector_insert(&x, (void*) 0x001); - - cl_assert(x.length == 3); - git_vector_remove_matching(&x, remove_ones, NULL); - cl_assert(x.length == 0); - - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x001); - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x001); - - cl_assert(x.length == 4); - git_vector_remove_matching(&x, remove_ones, NULL); - cl_assert(x.length == 2); - - git_vector_foreach(&x, i, compare) { - cl_assert(compare != (void *)0x001); - } - - git_vector_clear(&x); - - git_vector_insert(&x, (void*) 0x001); - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x001); - - cl_assert(x.length == 4); - git_vector_remove_matching(&x, remove_ones, NULL); - cl_assert(x.length == 2); - - git_vector_foreach(&x, i, compare) { - cl_assert(compare != (void *)0x001); - } - - git_vector_clear(&x); - - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x001); - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x001); - - cl_assert(x.length == 4); - git_vector_remove_matching(&x, remove_ones, NULL); - cl_assert(x.length == 2); - - git_vector_foreach(&x, i, compare) { - cl_assert(compare != (void *)0x001); - } - - git_vector_clear(&x); - - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x003); - git_vector_insert(&x, (void*) 0x002); - git_vector_insert(&x, (void*) 0x003); - - cl_assert(x.length == 4); - git_vector_remove_matching(&x, remove_ones, NULL); - cl_assert(x.length == 4); - - git_vector_free(&x); -} - -static void assert_vector(git_vector *x, void *expected[], size_t len) -{ - size_t i; - - cl_assert_equal_i(len, x->length); - - for (i = 0; i < len; i++) - cl_assert(expected[i] == x->contents[i]); -} - -void test_core_vector__grow_and_shrink(void) -{ - git_vector x = GIT_VECTOR_INIT; - void *expected1[] = { - (void *)0x02, (void *)0x03, (void *)0x04, (void *)0x05, - (void *)0x06, (void *)0x07, (void *)0x08, (void *)0x09, - (void *)0x0a - }; - void *expected2[] = { - (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06, - (void *)0x07, (void *)0x08, (void *)0x09, (void *)0x0a - }; - void *expected3[] = { - (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06, - (void *)0x0a - }; - void *expected4[] = { - (void *)0x02, (void *)0x04, (void *)0x05 - }; - void *expected5[] = { - (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, - (void *)0x05 - }; - void *expected6[] = { - (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, - (void *)0x05, (void *)0x00 - }; - void *expected7[] = { - (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, - (void *)0x00, (void *)0x00, (void *)0x00, (void *)0x05, - (void *)0x00 - }; - void *expected8[] = { - (void *)0x04, (void *)0x00, (void *)0x00, (void *)0x00, - (void *)0x05, (void *)0x00 - }; - void *expected9[] = { - (void *)0x04, (void *)0x00, (void *)0x05, (void *)0x00 - }; - void *expectedA[] = { (void *)0x04, (void *)0x00 }; - void *expectedB[] = { (void *)0x04 }; - - git_vector_insert(&x, (void *)0x01); - git_vector_insert(&x, (void *)0x02); - git_vector_insert(&x, (void *)0x03); - git_vector_insert(&x, (void *)0x04); - git_vector_insert(&x, (void *)0x05); - git_vector_insert(&x, (void *)0x06); - git_vector_insert(&x, (void *)0x07); - git_vector_insert(&x, (void *)0x08); - git_vector_insert(&x, (void *)0x09); - git_vector_insert(&x, (void *)0x0a); - - git_vector_remove_range(&x, 0, 1); - assert_vector(&x, expected1, ARRAY_SIZE(expected1)); - - git_vector_remove_range(&x, 1, 1); - assert_vector(&x, expected2, ARRAY_SIZE(expected2)); - - git_vector_remove_range(&x, 4, 3); - assert_vector(&x, expected3, ARRAY_SIZE(expected3)); - - git_vector_remove_range(&x, 3, 2); - assert_vector(&x, expected4, ARRAY_SIZE(expected4)); - - git_vector_insert_null(&x, 0, 2); - assert_vector(&x, expected5, ARRAY_SIZE(expected5)); - - git_vector_insert_null(&x, 5, 1); - assert_vector(&x, expected6, ARRAY_SIZE(expected6)); - - git_vector_insert_null(&x, 4, 3); - assert_vector(&x, expected7, ARRAY_SIZE(expected7)); - - git_vector_remove_range(&x, 0, 3); - assert_vector(&x, expected8, ARRAY_SIZE(expected8)); - - git_vector_remove_range(&x, 1, 2); - assert_vector(&x, expected9, ARRAY_SIZE(expected9)); - - git_vector_remove_range(&x, 2, 2); - assert_vector(&x, expectedA, ARRAY_SIZE(expectedA)); - - git_vector_remove_range(&x, 1, 1); - assert_vector(&x, expectedB, ARRAY_SIZE(expectedB)); - - git_vector_remove_range(&x, 0, 1); - assert_vector(&x, NULL, 0); - - git_vector_free(&x); -} - -void test_core_vector__reverse(void) -{ - git_vector v = GIT_VECTOR_INIT; - size_t i; - - void *in1[] = {(void *) 0x0, (void *) 0x1, (void *) 0x2, (void *) 0x3}; - void *out1[] = {(void *) 0x3, (void *) 0x2, (void *) 0x1, (void *) 0x0}; - - void *in2[] = {(void *) 0x0, (void *) 0x1, (void *) 0x2, (void *) 0x3, (void *) 0x4}; - void *out2[] = {(void *) 0x4, (void *) 0x3, (void *) 0x2, (void *) 0x1, (void *) 0x0}; - - for (i = 0; i < 4; i++) - cl_git_pass(git_vector_insert(&v, in1[i])); - - git_vector_reverse(&v); - - for (i = 0; i < 4; i++) - cl_assert_equal_p(out1[i], git_vector_get(&v, i)); - - git_vector_clear(&v); - for (i = 0; i < 5; i++) - cl_git_pass(git_vector_insert(&v, in2[i])); - - git_vector_reverse(&v); - - for (i = 0; i < 5; i++) - cl_assert_equal_p(out2[i], git_vector_get(&v, i)); - - git_vector_free(&v); -} - -void test_core_vector__dup_empty_vector(void) -{ - git_vector v = GIT_VECTOR_INIT; - git_vector dup = GIT_VECTOR_INIT; - int dummy; - - cl_assert_equal_i(0, v.length); - - cl_git_pass(git_vector_dup(&dup, &v, v._cmp)); - cl_assert_equal_i(0, dup._alloc_size); - cl_assert_equal_i(0, dup.length); - - cl_git_pass(git_vector_insert(&dup, &dummy)); - cl_assert_equal_i(8, dup._alloc_size); - cl_assert_equal_i(1, dup.length); - - git_vector_free(&dup); -} diff --git a/tests/libgit2/core/wildmatch.c b/tests/libgit2/core/wildmatch.c deleted file mode 100644 index 7c56ee7b8..000000000 --- a/tests/libgit2/core/wildmatch.c +++ /dev/null @@ -1,248 +0,0 @@ -#include "clar_libgit2.h" - -#include "wildmatch.h" - -#define assert_matches(string, pattern, wildmatch, iwildmatch, pathmatch, ipathmatch) \ - assert_matches_(string, pattern, wildmatch, iwildmatch, pathmatch, ipathmatch, __FILE__, __func__, __LINE__) - -static void assert_matches_(const char *string, const char *pattern, - char expected_wildmatch, char expected_iwildmatch, - char expected_pathmatch, char expected_ipathmatch, - const char *file, const char *func, size_t line) -{ - if (wildmatch(pattern, string, WM_PATHNAME) == expected_wildmatch) - clar__fail(file, func, line, "Test failed (wildmatch).", string, 1); - if (wildmatch(pattern, string, WM_PATHNAME|WM_CASEFOLD) == expected_iwildmatch) - clar__fail(file, func, line, "Test failed (iwildmatch).", string, 1); - if (wildmatch(pattern, string, 0) == expected_pathmatch) - clar__fail(file, func, line, "Test failed (pathmatch).", string, 1); - if (wildmatch(pattern, string, WM_CASEFOLD) == expected_ipathmatch) - clar__fail(file, func, line, "Test failed (ipathmatch).", string, 1); -} - -/* - * Below testcases are imported from git.git, t3070-wildmatch,sh at tag v2.22.0. - * Note that we've only imported the direct wildcard tests, but not the matching - * tests for git-ls-files. - */ - -void test_core_wildmatch__basic_wildmatch(void) -{ - assert_matches("foo", "foo", 1, 1, 1, 1); - assert_matches("foo", "bar", 0, 0, 0, 0); - assert_matches("", "", 1, 1, 1, 1); - assert_matches("foo", "???", 1, 1, 1, 1); - assert_matches("foo", "??", 0, 0, 0, 0); - assert_matches("foo", "*", 1, 1, 1, 1); - assert_matches("foo", "f*", 1, 1, 1, 1); - assert_matches("foo", "*f", 0, 0, 0, 0); - assert_matches("foo", "*foo*", 1, 1, 1, 1); - assert_matches("foobar", "*ob*a*r*", 1, 1, 1, 1); - assert_matches("aaaaaaabababab", "*ab", 1, 1, 1, 1); - assert_matches("foo*", "foo\\*", 1, 1, 1, 1); - assert_matches("foobar", "foo\\*bar", 0, 0, 0, 0); - assert_matches("f\\oo", "f\\\\oo", 1, 1, 1, 1); - assert_matches("ball", "*[al]?", 1, 1, 1, 1); - assert_matches("ten", "[ten]", 0, 0, 0, 0); - assert_matches("ten", "**[!te]", 1, 1, 1, 1); - assert_matches("ten", "**[!ten]", 0, 0, 0, 0); - assert_matches("ten", "t[a-g]n", 1, 1, 1, 1); - assert_matches("ten", "t[!a-g]n", 0, 0, 0, 0); - assert_matches("ton", "t[!a-g]n", 1, 1, 1, 1); - assert_matches("ton", "t[^a-g]n", 1, 1, 1, 1); - assert_matches("a]b", "a[]]b", 1, 1, 1, 1); - assert_matches("a-b", "a[]-]b", 1, 1, 1, 1); - assert_matches("a]b", "a[]-]b", 1, 1, 1, 1); - assert_matches("aab", "a[]-]b", 0, 0, 0, 0); - assert_matches("aab", "a[]a-]b", 1, 1, 1, 1); - assert_matches("]", "]", 1, 1, 1, 1); -} - -void test_core_wildmatch__slash_matching_features(void) -{ - assert_matches("foo/baz/bar", "foo*bar", 0, 0, 1, 1); - assert_matches("foo/baz/bar", "foo**bar", 0, 0, 1, 1); - assert_matches("foobazbar", "foo**bar", 1, 1, 1, 1); - assert_matches("foo/baz/bar", "foo/**/bar", 1, 1, 1, 1); - assert_matches("foo/baz/bar", "foo/**/**/bar", 1, 1, 0, 0); - assert_matches("foo/b/a/z/bar", "foo/**/bar", 1, 1, 1, 1); - assert_matches("foo/b/a/z/bar", "foo/**/**/bar", 1, 1, 1, 1); - assert_matches("foo/bar", "foo/**/bar", 1, 1, 0, 0); - assert_matches("foo/bar", "foo/**/**/bar", 1, 1, 0, 0); - assert_matches("foo/bar", "foo?bar", 0, 0, 1, 1); - assert_matches("foo/bar", "foo[/]bar", 0, 0, 1, 1); - assert_matches("foo/bar", "foo[^a-z]bar", 0, 0, 1, 1); - assert_matches("foo/bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r", 0, 0, 1, 1); - assert_matches("foo-bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r", 1, 1, 1, 1); - assert_matches("foo", "**/foo", 1, 1, 0, 0); - assert_matches("XXX/foo", "**/foo", 1, 1, 1, 1); - assert_matches("bar/baz/foo", "**/foo", 1, 1, 1, 1); - assert_matches("bar/baz/foo", "*/foo", 0, 0, 1, 1); - assert_matches("foo/bar/baz", "**/bar*", 0, 0, 1, 1); - assert_matches("deep/foo/bar/baz", "**/bar/*", 1, 1, 1, 1); - assert_matches("deep/foo/bar/baz/", "**/bar/*", 0, 0, 1, 1); - assert_matches("deep/foo/bar/baz/", "**/bar/**", 1, 1, 1, 1); - assert_matches("deep/foo/bar", "**/bar/*", 0, 0, 0, 0); - assert_matches("deep/foo/bar/", "**/bar/**", 1, 1, 1, 1); - assert_matches("foo/bar/baz", "**/bar**", 0, 0, 1, 1); - assert_matches("foo/bar/baz/x", "*/bar/**", 1, 1, 1, 1); - assert_matches("deep/foo/bar/baz/x", "*/bar/**", 0, 0, 1, 1); - assert_matches("deep/foo/bar/baz/x", "**/bar/*/*", 1, 1, 1, 1); -} - -void test_core_wildmatch__various_additional(void) -{ - assert_matches("acrt", "a[c-c]st", 0, 0, 0, 0); - assert_matches("acrt", "a[c-c]rt", 1, 1, 1, 1); - assert_matches("]", "[!]-]", 0, 0, 0, 0); - assert_matches("a", "[!]-]", 1, 1, 1, 1); - assert_matches("", "\\", 0, 0, 0, 0); - assert_matches("\\", "\\", 0, 0, 0, 0); - assert_matches("XXX/\\", "*/\\", 0, 0, 0, 0); - assert_matches("XXX/\\", "*/\\\\", 1, 1, 1, 1); - assert_matches("foo", "foo", 1, 1, 1, 1); - assert_matches("@foo", "@foo", 1, 1, 1, 1); - assert_matches("foo", "@foo", 0, 0, 0, 0); - assert_matches("[ab]", "\\[ab]", 1, 1, 1, 1); - assert_matches("[ab]", "[[]ab]", 1, 1, 1, 1); - assert_matches("[ab]", "[[:]ab]", 1, 1, 1, 1); - assert_matches("[ab]", "[[::]ab]", 0, 0, 0, 0); - assert_matches("[ab]", "[[:digit]ab]", 1, 1, 1, 1); - assert_matches("[ab]", "[\\[:]ab]", 1, 1, 1, 1); - assert_matches("?a?b", "\\??\\?b", 1, 1, 1, 1); - assert_matches("abc", "\\a\\b\\c", 1, 1, 1, 1); - assert_matches("foo", "", 0, 0, 0, 0); - assert_matches("foo/bar/baz/to", "**/t[o]", 1, 1, 1, 1); -} - -void test_core_wildmatch__character_classes(void) -{ - assert_matches("a1B", "[[:alpha:]][[:digit:]][[:upper:]]", 1, 1, 1, 1); - assert_matches("a", "[[:digit:][:upper:][:space:]]", 0, 1, 0, 1); - assert_matches("A", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); - assert_matches("1", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); - assert_matches("1", "[[:digit:][:upper:][:spaci:]]", 0, 0, 0, 0); - assert_matches(" ", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); - assert_matches(".", "[[:digit:][:upper:][:space:]]", 0, 0, 0, 0); - assert_matches(".", "[[:digit:][:punct:][:space:]]", 1, 1, 1, 1); - assert_matches("5", "[[:xdigit:]]", 1, 1, 1, 1); - assert_matches("f", "[[:xdigit:]]", 1, 1, 1, 1); - assert_matches("D", "[[:xdigit:]]", 1, 1, 1, 1); - assert_matches("_", "[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]", 1, 1, 1, 1); - assert_matches(".", "[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]", 1, 1, 1, 1); - assert_matches("5", "[a-c[:digit:]x-z]", 1, 1, 1, 1); - assert_matches("b", "[a-c[:digit:]x-z]", 1, 1, 1, 1); - assert_matches("y", "[a-c[:digit:]x-z]", 1, 1, 1, 1); - assert_matches("q", "[a-c[:digit:]x-z]", 0, 0, 0, 0); -} - -void test_core_wildmatch__additional_with_malformed(void) -{ - assert_matches("]", "[\\\\-^]", 1, 1, 1, 1); - assert_matches("[", "[\\\\-^]", 0, 0, 0, 0); - assert_matches("-", "[\\-_]", 1, 1, 1, 1); - assert_matches("]", "[\\]]", 1, 1, 1, 1); - assert_matches("\\]", "[\\]]", 0, 0, 0, 0); - assert_matches("\\", "[\\]]", 0, 0, 0, 0); - assert_matches("ab", "a[]b", 0, 0, 0, 0); - assert_matches("a[]b", "a[]b", 0, 0, 0, 0); - assert_matches("ab[", "ab[", 0, 0, 0, 0); - assert_matches("ab", "[!", 0, 0, 0, 0); - assert_matches("ab", "[-", 0, 0, 0, 0); - assert_matches("-", "[-]", 1, 1, 1, 1); - assert_matches("-", "[a-", 0, 0, 0, 0); - assert_matches("-", "[!a-", 0, 0, 0, 0); - assert_matches("-", "[--A]", 1, 1, 1, 1); - assert_matches("5", "[--A]", 1, 1, 1, 1); - assert_matches(" ", "[ --]", 1, 1, 1, 1); - assert_matches("$", "[ --]", 1, 1, 1, 1); - assert_matches("-", "[ --]", 1, 1, 1, 1); - assert_matches("0", "[ --]", 0, 0, 0, 0); - assert_matches("-", "[---]", 1, 1, 1, 1); - assert_matches("-", "[------]", 1, 1, 1, 1); - assert_matches("j", "[a-e-n]", 0, 0, 0, 0); - assert_matches("-", "[a-e-n]", 1, 1, 1, 1); - assert_matches("a", "[!------]", 1, 1, 1, 1); - assert_matches("[", "[]-a]", 0, 0, 0, 0); - assert_matches("^", "[]-a]", 1, 1, 1, 1); - assert_matches("^", "[!]-a]", 0, 0, 0, 0); - assert_matches("[", "[!]-a]", 1, 1, 1, 1); - assert_matches("^", "[a^bc]", 1, 1, 1, 1); - assert_matches("-b]", "[a-]b]", 1, 1, 1, 1); - assert_matches("\\", "[\\]", 0, 0, 0, 0); - assert_matches("\\", "[\\\\]", 1, 1, 1, 1); - assert_matches("\\", "[!\\\\]", 0, 0, 0, 0); - assert_matches("G", "[A-\\\\]", 1, 1, 1, 1); - assert_matches("aaabbb", "b*a", 0, 0, 0, 0); - assert_matches("aabcaa", "*ba*", 0, 0, 0, 0); - assert_matches(",", "[,]", 1, 1, 1, 1); - assert_matches(",", "[\\\\,]", 1, 1, 1, 1); - assert_matches("\\", "[\\\\,]", 1, 1, 1, 1); - assert_matches("-", "[,-.]", 1, 1, 1, 1); - assert_matches("+", "[,-.]", 0, 0, 0, 0); - assert_matches("-.]", "[,-.]", 0, 0, 0, 0); - assert_matches("2", "[\\1-\\3]", 1, 1, 1, 1); - assert_matches("3", "[\\1-\\3]", 1, 1, 1, 1); - assert_matches("4", "[\\1-\\3]", 0, 0, 0, 0); - assert_matches("\\", "[[-\\]]", 1, 1, 1, 1); - assert_matches("[", "[[-\\]]", 1, 1, 1, 1); - assert_matches("]", "[[-\\]]", 1, 1, 1, 1); - assert_matches("-", "[[-\\]]", 0, 0, 0, 0); -} - -void test_core_wildmatch__recursion(void) -{ - assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 1, 1, 1, 1); - assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 0, 0, 0, 0); - assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 0, 0, 0, 0); - assert_matches("XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*", 1, 1, 1, 1); - assert_matches("XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*", 0, 0, 0, 0); - assert_matches("abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt", "**/*a*b*g*n*t", 1, 1, 1, 1); - assert_matches("abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz", "**/*a*b*g*n*t", 0, 0, 0, 0); - assert_matches("foo", "*/*/*", 0, 0, 0, 0); - assert_matches("foo/bar", "*/*/*", 0, 0, 0, 0); - assert_matches("foo/bba/arr", "*/*/*", 1, 1, 1, 1); - assert_matches("foo/bb/aa/rr", "*/*/*", 0, 0, 1, 1); - assert_matches("foo/bb/aa/rr", "**/**/**", 1, 1, 1, 1); - assert_matches("abcXdefXghi", "*X*i", 1, 1, 1, 1); - assert_matches("ab/cXd/efXg/hi", "*X*i", 0, 0, 1, 1); - assert_matches("ab/cXd/efXg/hi", "*/*X*/*/*i", 1, 1, 1, 1); - assert_matches("ab/cXd/efXg/hi", "**/*X*/**/*i", 1, 1, 1, 1); -} - -void test_core_wildmatch__pathmatch(void) -{ - assert_matches("foo", "fo", 0, 0, 0, 0); - assert_matches("foo/bar", "foo/bar", 1, 1, 1, 1); - assert_matches("foo/bar", "foo/*", 1, 1, 1, 1); - assert_matches("foo/bba/arr", "foo/*", 0, 0, 1, 1); - assert_matches("foo/bba/arr", "foo/**", 1, 1, 1, 1); - assert_matches("foo/bba/arr", "foo*", 0, 0, 1, 1); - assert_matches("foo/bba/arr", "foo**", 0, 0, 1, 1); - assert_matches("foo/bba/arr", "foo/*arr", 0, 0, 1, 1); - assert_matches("foo/bba/arr", "foo/**arr", 0, 0, 1, 1); - assert_matches("foo/bba/arr", "foo/*z", 0, 0, 0, 0); - assert_matches("foo/bba/arr", "foo/**z", 0, 0, 0, 0); - assert_matches("foo/bar", "foo?bar", 0, 0, 1, 1); - assert_matches("foo/bar", "foo[/]bar", 0, 0, 1, 1); - assert_matches("foo/bar", "foo[^a-z]bar", 0, 0, 1, 1); - assert_matches("ab/cXd/efXg/hi", "*Xg*i", 0, 0, 1, 1); -} - -void test_core_wildmatch__case_sensitivity(void) -{ - assert_matches("a", "[A-Z]", 0, 1, 0, 1); - assert_matches("A", "[A-Z]", 1, 1, 1, 1); - assert_matches("A", "[a-z]", 0, 1, 0, 1); - assert_matches("a", "[a-z]", 1, 1, 1, 1); - assert_matches("a", "[[:upper:]]", 0, 1, 0, 1); - assert_matches("A", "[[:upper:]]", 1, 1, 1, 1); - assert_matches("A", "[[:lower:]]", 0, 1, 0, 1); - assert_matches("a", "[[:lower:]]", 1, 1, 1, 1); - assert_matches("A", "[B-Za]", 0, 1, 0, 1); - assert_matches("a", "[B-Za]", 1, 1, 1, 1); - assert_matches("A", "[B-a]", 0, 1, 0, 1); - assert_matches("a", "[B-a]", 1, 1, 1, 1); - assert_matches("z", "[Z-y]", 0, 1, 0, 1); - assert_matches("Z", "[Z-y]", 1, 1, 1, 1); -} diff --git a/tests/libgit2/core/zstream.c b/tests/libgit2/core/zstream.c deleted file mode 100644 index c22e81008..000000000 --- a/tests/libgit2/core/zstream.c +++ /dev/null @@ -1,167 +0,0 @@ -#include "clar_libgit2.h" -#include "zstream.h" - -static const char *data = "This is a test test test of This is a test"; - -#define INFLATE_EXTRA 2 - -static void assert_zlib_equal_( - const void *expected, size_t e_len, - const void *compressed, size_t c_len, - const char *msg, const char *file, const char *func, int line) -{ - z_stream stream; - char *expanded = git__calloc(1, e_len + INFLATE_EXTRA); - cl_assert(expanded); - - memset(&stream, 0, sizeof(stream)); - stream.next_out = (Bytef *)expanded; - stream.avail_out = (uInt)(e_len + INFLATE_EXTRA); - stream.next_in = (Bytef *)compressed; - stream.avail_in = (uInt)c_len; - - cl_assert(inflateInit(&stream) == Z_OK); - cl_assert(inflate(&stream, Z_FINISH)); - inflateEnd(&stream); - - clar__assert_equal( - file, func, line, msg, 1, - "%d", (int)stream.total_out, (int)e_len); - clar__assert_equal( - file, func, line, "Buffer len was not exact match", 1, - "%d", (int)stream.avail_out, (int)INFLATE_EXTRA); - - clar__assert( - memcmp(expanded, expected, e_len) == 0, - file, func, line, "uncompressed data did not match", NULL, 1); - - git__free(expanded); -} - -#define assert_zlib_equal(E,EL,C,CL) \ - assert_zlib_equal_(E, EL, C, CL, #EL " != " #CL, __FILE__, __func__, (int)__LINE__) - -void test_core_zstream__basic(void) -{ - git_zstream z = GIT_ZSTREAM_INIT; - char out[128]; - size_t outlen = sizeof(out); - - cl_git_pass(git_zstream_init(&z, GIT_ZSTREAM_DEFLATE)); - cl_git_pass(git_zstream_set_input(&z, data, strlen(data) + 1)); - cl_git_pass(git_zstream_get_output(out, &outlen, &z)); - cl_assert(git_zstream_done(&z)); - cl_assert(outlen > 0); - git_zstream_free(&z); - - assert_zlib_equal(data, strlen(data) + 1, out, outlen); -} - -void test_core_zstream__fails_on_trailing_garbage(void) -{ - git_str deflated = GIT_STR_INIT, inflated = GIT_STR_INIT; - char i = 0; - - /* compress a simple string */ - git_zstream_deflatebuf(&deflated, "foobar!!", 8); - - /* append some garbage */ - for (i = 0; i < 10; i++) { - git_str_putc(&deflated, i); - } - - cl_git_fail(git_zstream_inflatebuf(&inflated, deflated.ptr, deflated.size)); - - git_str_dispose(&deflated); - git_str_dispose(&inflated); -} - -void test_core_zstream__buffer(void) -{ - git_str out = GIT_STR_INIT; - cl_git_pass(git_zstream_deflatebuf(&out, data, strlen(data) + 1)); - assert_zlib_equal(data, strlen(data) + 1, out.ptr, out.size); - git_str_dispose(&out); -} - -#define BIG_STRING_PART "Big Data IS Big - Long Data IS Long - We need a buffer larger than 1024 x 1024 to make sure we trigger chunked compression - Big Big Data IS Bigger than Big - Long Long Data IS Longer than Long" - -static void compress_and_decompress_input_various_ways(git_str *input) -{ - git_str out1 = GIT_STR_INIT, out2 = GIT_STR_INIT; - git_str inflated = GIT_STR_INIT; - size_t i, fixed_size = max(input->size / 2, 256); - char *fixed = git__malloc(fixed_size); - cl_assert(fixed); - - /* compress with deflatebuf */ - - cl_git_pass(git_zstream_deflatebuf(&out1, input->ptr, input->size)); - assert_zlib_equal(input->ptr, input->size, out1.ptr, out1.size); - - /* compress with various fixed size buffer (accumulating the output) */ - - for (i = 0; i < 3; ++i) { - git_zstream zs = GIT_ZSTREAM_INIT; - size_t use_fixed_size; - - switch (i) { - case 0: use_fixed_size = 256; break; - case 1: use_fixed_size = fixed_size / 2; break; - case 2: use_fixed_size = fixed_size; break; - } - cl_assert(use_fixed_size <= fixed_size); - - cl_git_pass(git_zstream_init(&zs, GIT_ZSTREAM_DEFLATE)); - cl_git_pass(git_zstream_set_input(&zs, input->ptr, input->size)); - - while (!git_zstream_done(&zs)) { - size_t written = use_fixed_size; - cl_git_pass(git_zstream_get_output(fixed, &written, &zs)); - cl_git_pass(git_str_put(&out2, fixed, written)); - } - - git_zstream_free(&zs); - assert_zlib_equal(input->ptr, input->size, out2.ptr, out2.size); - - /* did both approaches give the same data? */ - cl_assert_equal_sz(out1.size, out2.size); - cl_assert(!memcmp(out1.ptr, out2.ptr, out1.size)); - - git_str_dispose(&out2); - } - - cl_git_pass(git_zstream_inflatebuf(&inflated, out1.ptr, out1.size)); - cl_assert_equal_i(input->size, inflated.size); - cl_assert(memcmp(input->ptr, inflated.ptr, inflated.size) == 0); - - git_str_dispose(&out1); - git_str_dispose(&inflated); - git__free(fixed); -} - -void test_core_zstream__big_data(void) -{ - git_str in = GIT_STR_INIT; - size_t scan, target; - - for (target = 1024; target <= 1024 * 1024 * 4; target *= 8) { - - /* make a big string that's easy to compress */ - git_str_clear(&in); - while (in.size < target) - cl_git_pass( - git_str_put(&in, BIG_STRING_PART, strlen(BIG_STRING_PART))); - - compress_and_decompress_input_various_ways(&in); - - /* make a big string that's hard to compress */ - srand(0xabad1dea); - for (scan = 0; scan < in.size; ++scan) - in.ptr[scan] = (char)rand(); - - compress_and_decompress_input_various_ways(&in); - } - - git_str_dispose(&in); -} diff --git a/tests/libgit2/diff/userdiff.c b/tests/libgit2/diff/userdiff.c new file mode 100644 index 000000000..230494009 --- /dev/null +++ b/tests/libgit2/diff/userdiff.c @@ -0,0 +1,25 @@ +#include "clar_libgit2.h" + +#include "userdiff.h" + +static git_regexp regex; + +void test_diff_userdiff__cleanup(void) +{ + git_regexp_dispose(®ex); +} + +void test_diff_userdiff__compile_userdiff_regexps(void) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) { + git_diff_driver_definition ddef = builtin_defs[idx]; + + cl_git_pass(git_regexp_compile(®ex, ddef.fns, ddef.flags)); + git_regexp_dispose(®ex); + + cl_git_pass(git_regexp_compile(®ex, ddef.words, 0)); + git_regexp_dispose(®ex); + } +} diff --git a/tests/libgit2/path/core.c b/tests/libgit2/path/core.c deleted file mode 100644 index db5359af8..000000000 --- a/tests/libgit2/path/core.c +++ /dev/null @@ -1,405 +0,0 @@ -#include "clar_libgit2.h" -#include "fs_path.h" -#include "path.h" - -void test_path_core__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static void test_make_relative( - const char *expected_path, - const char *path, - const char *parent, - int expected_status) -{ - git_str buf = GIT_STR_INIT; - git_str_puts(&buf, path); - cl_assert_equal_i(expected_status, git_fs_path_make_relative(&buf, parent)); - cl_assert_equal_s(expected_path, buf.ptr); - git_str_dispose(&buf); -} - -void test_path_core__make_relative(void) -{ - test_make_relative("foo.c", "/path/to/foo.c", "/path/to", 0); - test_make_relative("bar/foo.c", "/path/to/bar/foo.c", "/path/to", 0); - test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0); - - test_make_relative("", "/path/to", "/path/to", 0); - test_make_relative("", "/path/to", "/path/to/", 0); - - test_make_relative("../", "/path/to", "/path/to/foo", 0); - - test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar", 0); - test_make_relative("../bar/foo.c", "/path/to/bar/foo.c", "/path/to/baz", 0); - - test_make_relative("../../foo.c", "/path/to/foo.c", "/path/to/foo/bar", 0); - test_make_relative("../../foo/bar.c", "/path/to/foo/bar.c", "/path/to/bar/foo", 0); - - test_make_relative("../../foo.c", "/foo.c", "/bar/foo", 0); - - test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0); - test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar/", 0); - - test_make_relative("foo.c", "d:/path/to/foo.c", "d:/path/to", 0); - - test_make_relative("../foo", "/foo", "/bar", 0); - test_make_relative("path/to/foo.c", "/path/to/foo.c", "/", 0); - test_make_relative("../foo", "path/to/foo", "path/to/bar", 0); - - test_make_relative("/path/to/foo.c", "/path/to/foo.c", "d:/path/to", GIT_ENOTFOUND); - test_make_relative("d:/path/to/foo.c", "d:/path/to/foo.c", "/path/to", GIT_ENOTFOUND); - - test_make_relative("/path/to/foo.c", "/path/to/foo.c", "not-a-rooted-path", GIT_ENOTFOUND); - test_make_relative("not-a-rooted-path", "not-a-rooted-path", "/path/to", GIT_ENOTFOUND); - - test_make_relative("/path", "/path", "pathtofoo", GIT_ENOTFOUND); - test_make_relative("path", "path", "pathtofoo", GIT_ENOTFOUND); -} - -void test_path_core__isvalid_standard(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/file.txt", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/.file", 0)); -} - -/* Ensure that `is_valid_str` only reads str->size bytes */ -void test_path_core__isvalid_standard_str(void) -{ - git_str str = GIT_STR_INIT_CONST("foo/bar//zap", 0); - unsigned int flags = GIT_FS_PATH_REJECT_EMPTY_COMPONENT; - - str.size = 0; - cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); - - str.size = 3; - cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags)); - - str.size = 4; - cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); - - str.size = 5; - cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags)); - - str.size = 7; - cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags)); - - str.size = 8; - cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); - - str.size = strlen(str.ptr); - cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); -} - -void test_path_core__isvalid_empty_dir_component(void) -{ - unsigned int flags = GIT_FS_PATH_REJECT_EMPTY_COMPONENT; - - /* empty component */ - cl_assert_equal_b(true, git_fs_path_is_valid("foo//bar", 0)); - - /* leading slash */ - cl_assert_equal_b(true, git_fs_path_is_valid("/", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("/foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("/foo/bar", 0)); - - /* trailing slash */ - cl_assert_equal_b(true, git_fs_path_is_valid("foo/", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/", 0)); - - - /* empty component */ - cl_assert_equal_b(false, git_fs_path_is_valid("foo//bar", flags)); - - /* leading slash */ - cl_assert_equal_b(false, git_fs_path_is_valid("/", flags)); - cl_assert_equal_b(false, git_fs_path_is_valid("/foo", flags)); - cl_assert_equal_b(false, git_fs_path_is_valid("/foo/bar", flags)); - - /* trailing slash */ - cl_assert_equal_b(false, git_fs_path_is_valid("foo/", flags)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar/", flags)); -} - -void test_path_core__isvalid_dot_and_dotdot(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid(".", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("./foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/.", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("./foo", 0)); - - cl_assert_equal_b(true, git_fs_path_is_valid("..", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("../foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/..", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("../foo", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid(".", GIT_FS_PATH_REJECT_TRAVERSAL)); - cl_assert_equal_b(false, git_fs_path_is_valid("./foo", GIT_FS_PATH_REJECT_TRAVERSAL)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/.", GIT_FS_PATH_REJECT_TRAVERSAL)); - cl_assert_equal_b(false, git_fs_path_is_valid("./foo", GIT_FS_PATH_REJECT_TRAVERSAL)); - - cl_assert_equal_b(false, git_fs_path_is_valid("..", GIT_FS_PATH_REJECT_TRAVERSAL)); - cl_assert_equal_b(false, git_fs_path_is_valid("../foo", GIT_FS_PATH_REJECT_TRAVERSAL)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/..", GIT_FS_PATH_REJECT_TRAVERSAL)); - cl_assert_equal_b(false, git_fs_path_is_valid("../foo", GIT_FS_PATH_REJECT_TRAVERSAL)); -} - -void test_path_core__isvalid_backslash(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("foo\\file.txt", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar\\file.txt", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar\\", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("foo\\file.txt", GIT_FS_PATH_REJECT_BACKSLASH)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar\\file.txt", GIT_FS_PATH_REJECT_BACKSLASH)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar\\", GIT_FS_PATH_REJECT_BACKSLASH)); -} - -void test_path_core__isvalid_trailing_dot(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("foo.", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo...", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar.", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo./bar", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("foo.", GIT_FS_PATH_REJECT_TRAILING_DOT)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo...", GIT_FS_PATH_REJECT_TRAILING_DOT)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar.", GIT_FS_PATH_REJECT_TRAILING_DOT)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo./bar", GIT_FS_PATH_REJECT_TRAILING_DOT)); -} - -void test_path_core__isvalid_trailing_space(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("foo ", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo ", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar ", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid(" ", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo /bar", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("foo ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); - cl_assert_equal_b(false, git_fs_path_is_valid(" ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo /bar", GIT_FS_PATH_REJECT_TRAILING_SPACE)); -} - -void test_path_core__isvalid_trailing_colon(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("foo:", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar:", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid(":", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("foo:/bar", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("foo:", GIT_FS_PATH_REJECT_TRAILING_COLON)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar:", GIT_FS_PATH_REJECT_TRAILING_COLON)); - cl_assert_equal_b(false, git_fs_path_is_valid(":", GIT_FS_PATH_REJECT_TRAILING_COLON)); - cl_assert_equal_b(false, git_fs_path_is_valid("foo:/bar", GIT_FS_PATH_REJECT_TRAILING_COLON)); -} - -void test_path_core__isvalid_dos_paths(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("aux", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux.", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux:", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux.asdf", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux.asdf\\zippy", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux:asdf\\foobar", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("con", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("prn", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("nul", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("aux", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("aux.", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("aux:", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("aux.asdf", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("aux.asdf\\zippy", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("aux:asdf\\foobar", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("con", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("prn", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("nul", GIT_FS_PATH_REJECT_DOS_PATHS)); - - cl_assert_equal_b(true, git_fs_path_is_valid("aux1", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux1", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("auxn", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("aux\\foo", GIT_FS_PATH_REJECT_DOS_PATHS)); -} - -void test_path_core__isvalid_dos_paths_withnum(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("com1", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1.", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1:", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1.asdf", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1.asdf\\zippy", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1:asdf\\foobar", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1\\foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("lpt1", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("com1", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("com1.", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("com1:", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("com1.asdf", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("com1.asdf\\zippy", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("com1:asdf\\foobar", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("com1/foo", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(false, git_fs_path_is_valid("lpt1", GIT_FS_PATH_REJECT_DOS_PATHS)); - - cl_assert_equal_b(true, git_fs_path_is_valid("com0", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com0", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("com10", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("com10", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("comn", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("com1\\foo", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("lpt0", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("lpt10", GIT_FS_PATH_REJECT_DOS_PATHS)); - cl_assert_equal_b(true, git_fs_path_is_valid("lptn", GIT_FS_PATH_REJECT_DOS_PATHS)); -} - -void test_path_core__isvalid_nt_chars(void) -{ - cl_assert_equal_b(true, git_fs_path_is_valid("asdf\001foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdf\037bar", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdffoo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdf:foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdf\"bar", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdf|foo", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdf?bar", 0)); - cl_assert_equal_b(true, git_fs_path_is_valid("asdf*bar", 0)); - - cl_assert_equal_b(false, git_fs_path_is_valid("asdf\001foo", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdf\037bar", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdffoo", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdf:foo", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdf\"bar", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdf|foo", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdf?bar", GIT_FS_PATH_REJECT_NT_CHARS)); - cl_assert_equal_b(false, git_fs_path_is_valid("asdf*bar", GIT_FS_PATH_REJECT_NT_CHARS)); -} - -void test_path_core__validate_workdir(void) -{ - cl_must_pass(git_path_validate_length(NULL, "/foo/bar")); - cl_must_pass(git_path_validate_length(NULL, "C:\\Foo\\Bar")); - cl_must_pass(git_path_validate_length(NULL, "\\\\?\\C:\\Foo\\Bar")); - cl_must_pass(git_path_validate_length(NULL, "\\\\?\\C:\\Foo\\Bar")); - cl_must_pass(git_path_validate_length(NULL, "\\\\?\\UNC\\server\\C$\\folder")); - -#ifdef GIT_WIN32 - /* - * In the absence of a repo configuration, 259 character paths - * succeed. >= 260 character paths fail. - */ - cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\ok.txt")); - cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\260.txt")); - cl_must_fail(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\longer_than_260.txt")); - - /* count characters, not bytes */ - cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\260.txt")); - cl_must_fail(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\long.txt")); -#else - cl_must_pass(git_path_validate_length(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/ok.txt")); - cl_must_pass(git_path_validate_length(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/260.txt")); - cl_must_pass(git_path_validate_length(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); - cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\260.txt")); - cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\long.txt")); -#endif -} - -void test_path_core__validate_workdir_with_core_longpath(void) -{ -#ifdef GIT_WIN32 - git_repository *repo; - git_config *config; - - repo = cl_git_sandbox_init("empty_bare.git"); - - cl_git_pass(git_repository_open(&repo, "empty_bare.git")); - cl_git_pass(git_repository_config(&config, repo)); - - /* fail by default */ - cl_must_fail(git_path_validate_length(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); - - /* set core.longpaths explicitly on */ - cl_git_pass(git_config_set_bool(config, "core.longpaths", 1)); - cl_must_pass(git_path_validate_length(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); - - /* set core.longpaths explicitly off */ - cl_git_pass(git_config_set_bool(config, "core.longpaths", 0)); - cl_must_fail(git_path_validate_length(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); - - git_config_free(config); - git_repository_free(repo); -#endif -} - -static void test_join_unrooted( - const char *expected_result, - ssize_t expected_rootlen, - const char *path, - const char *base) -{ - git_str result = GIT_STR_INIT; - ssize_t root_at; - - cl_git_pass(git_fs_path_join_unrooted(&result, path, base, &root_at)); - cl_assert_equal_s(expected_result, result.ptr); - cl_assert_equal_i(expected_rootlen, root_at); - - git_str_dispose(&result); -} - -void test_path_core__join_unrooted(void) -{ - git_str out = GIT_STR_INIT; - - test_join_unrooted("foo", 0, "foo", NULL); - test_join_unrooted("foo/bar", 0, "foo/bar", NULL); - - /* Relative paths have base prepended */ - test_join_unrooted("/foo/bar", 4, "bar", "/foo"); - test_join_unrooted("/foo/bar/foobar", 4, "bar/foobar", "/foo"); - test_join_unrooted("c:/foo/bar/foobar", 6, "bar/foobar", "c:/foo"); - test_join_unrooted("c:/foo/bar/foobar", 10, "foobar", "c:/foo/bar"); - - /* Absolute paths are not prepended with base */ - test_join_unrooted("/foo", 0, "/foo", "/asdf"); - test_join_unrooted("/foo/bar", 0, "/foo/bar", "/asdf"); - - /* Drive letter is given as root length on Windows */ - test_join_unrooted("c:/foo", 2, "c:/foo", "c:/asdf"); - test_join_unrooted("c:/foo/bar", 2, "c:/foo/bar", "c:/asdf"); - -#ifdef GIT_WIN32 - /* Paths starting with '\\' are absolute */ - test_join_unrooted("\\bar", 0, "\\bar", "c:/foo/"); - test_join_unrooted("\\\\network\\bar", 9, "\\\\network\\bar", "c:/foo/"); -#else - /* Paths starting with '\\' are not absolute on non-Windows systems */ - test_join_unrooted("/foo/\\bar", 4, "\\bar", "/foo"); - test_join_unrooted("c:/foo/\\bar", 7, "\\bar", "c:/foo/"); -#endif - - /* Base is returned when it's provided and is the prefix */ - test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo"); - test_join_unrooted("c:/foo/bar/foobar", 10, "c:/foo/bar/foobar", "c:/foo/bar"); - - /* Trailing slash in the base is ignored */ - test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo/"); - - git_str_dispose(&out); -} - -void test_path_core__join_unrooted_respects_funny_windows_roots(void) -{ - test_join_unrooted("💩:/foo/bar/foobar", 9, "bar/foobar", "💩:/foo"); - test_join_unrooted("💩:/foo/bar/foobar", 13, "foobar", "💩:/foo/bar"); - test_join_unrooted("💩:/foo", 5, "💩:/foo", "💩:/asdf"); - test_join_unrooted("💩:/foo/bar", 5, "💩:/foo/bar", "💩:/asdf"); - test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo"); - test_join_unrooted("💩:/foo/bar/foobar", 13, "💩:/foo/bar/foobar", "💩:/foo/bar"); - test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo/"); -} diff --git a/tests/libgit2/path/dotgit.c b/tests/libgit2/path/dotgit.c deleted file mode 100644 index 855145f42..000000000 --- a/tests/libgit2/path/dotgit.c +++ /dev/null @@ -1,206 +0,0 @@ -#include "clar_libgit2.h" - -#include "path.h" - -static char *gitmodules_altnames[] = { - ".gitmodules", - - /* - * Equivalent to the ".git\u200cmodules" string from git but hard-coded - * as a UTF-8 sequence - */ - ".git\xe2\x80\x8cmodules", - - ".Gitmodules", - ".gitmoduleS", - - ".gitmodules ", - ".gitmodules.", - ".gitmodules ", - ".gitmodules. ", - ".gitmodules .", - ".gitmodules..", - ".gitmodules ", - ".gitmodules. ", - ".gitmodules . ", - ".gitmodules .", - - ".Gitmodules ", - ".Gitmodules.", - ".Gitmodules ", - ".Gitmodules. ", - ".Gitmodules .", - ".Gitmodules..", - ".Gitmodules ", - ".Gitmodules. ", - ".Gitmodules . ", - ".Gitmodules .", - - "GITMOD~1", - "gitmod~1", - "GITMOD~2", - "gitmod~3", - "GITMOD~4", - - "GITMOD~1 ", - "gitmod~2.", - "GITMOD~3 ", - "gitmod~4. ", - "GITMOD~1 .", - "gitmod~2 ", - "GITMOD~3. ", - "gitmod~4 . ", - - "GI7EBA~1", - "gi7eba~9", - - "GI7EB~10", - "GI7EB~11", - "GI7EB~99", - "GI7EB~10", - "GI7E~100", - "GI7E~101", - "GI7E~999", - "~1000000", - "~9999999", -}; - -static char *gitmodules_not_altnames[] = { - ".gitmodules x", - ".gitmodules .x", - - " .gitmodules", - - "..gitmodules", - - "gitmodules", - - ".gitmodule", - - ".gitmodules x ", - ".gitmodules .x", - - "GI7EBA~", - "GI7EBA~0", - "GI7EBA~~1", - "GI7EBA~X", - "Gx7EBA~1", - "GI7EBX~1", - - "GI7EB~1", - "GI7EB~01", - "GI7EB~1", -}; - -void test_path_dotgit__dotgit_modules(void) -{ - size_t i; - - cl_assert_equal_i(1, git_path_is_gitfile(".gitmodules", strlen(".gitmodules"), GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_GENERIC)); - cl_assert_equal_i(1, git_path_is_gitfile(".git\xe2\x80\x8cmodules", strlen(".git\xe2\x80\x8cmodules"), GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_GENERIC)); - - for (i = 0; i < ARRAY_SIZE(gitmodules_altnames); i++) { - const char *name = gitmodules_altnames[i]; - if (!git_path_is_gitfile(name, strlen(name), GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_GENERIC)) - cl_fail(name); - } - - for (i = 0; i < ARRAY_SIZE(gitmodules_not_altnames); i++) { - const char *name = gitmodules_not_altnames[i]; - if (git_path_is_gitfile(name, strlen(name), GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_GENERIC)) - cl_fail(name); - } -} - -void test_path_dotgit__dotgit_modules_symlink(void) -{ - cl_assert_equal_b(true, git_path_is_valid(NULL, ".gitmodules", 0, GIT_PATH_REJECT_DOT_GIT_HFS|GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".gitmodules", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".gitmodules", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".gitmodules . .::$DATA", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_NTFS)); -} - -void test_path_dotgit__git_fs_path_is_file(void) -{ - cl_git_fail(git_path_is_gitfile("blob", 4, -1, GIT_PATH_FS_HFS)); - cl_git_pass(git_path_is_gitfile("blob", 4, GIT_PATH_GITFILE_GITIGNORE, GIT_PATH_FS_HFS)); - cl_git_pass(git_path_is_gitfile("blob", 4, GIT_PATH_GITFILE_GITMODULES, GIT_PATH_FS_HFS)); - cl_git_pass(git_path_is_gitfile("blob", 4, GIT_PATH_GITFILE_GITATTRIBUTES, GIT_PATH_FS_HFS)); - cl_git_fail(git_path_is_gitfile("blob", 4, 3, GIT_PATH_FS_HFS)); -} - -void test_path_dotgit__isvalid_dot_git(void) -{ - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git/foo", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/.git", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/.git/bar", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/.GIT/bar", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/bar/.Git", 0, 0)); - - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git/foo", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "foo/.git", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "foo/.git/bar", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "foo/.GIT/bar", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "foo/bar/.Git", 0, GIT_PATH_REJECT_DOT_GIT_LITERAL)); - - cl_assert_equal_b(true, git_path_is_valid(NULL, "!git", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/!git", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "!git/bar", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".tig", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "foo/.tig", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".tig/bar", 0, 0)); -} - -void test_path_dotgit__isvalid_dotgit_ntfs(void) -{ - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git ", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git.", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git.. .", 0, 0)); - - cl_assert_equal_b(true, git_path_is_valid(NULL, "git~1", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "git~1 ", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "git~1.", 0, 0)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "git~1.. .", 0, 0)); - - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git ", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git.", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git.. .", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - - cl_assert_equal_b(false, git_path_is_valid(NULL, "git~1", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "git~1 ", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "git~1.", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "git~1.. .", 0, GIT_PATH_REJECT_DOT_GIT_NTFS)); -} - -void test_path_dotgit__isvalid_dotgit_with_hfs_ignorables(void) -{ - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".git\xe2\x80\x8c", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".gi\xe2\x80\x8dT", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".g\xe2\x80\x8eIt", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, ".\xe2\x80\x8fgIt", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "\xe2\x80\xaa.gIt", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - - cl_assert_equal_b(false, git_path_is_valid(NULL, "\xe2\x80\xab.\xe2\x80\xacG\xe2\x80\xadI\xe2\x80\xaet", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "\xe2\x81\xab.\xe2\x80\xaaG\xe2\x81\xabI\xe2\x80\xact", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(false, git_path_is_valid(NULL, "\xe2\x81\xad.\xe2\x80\xaeG\xef\xbb\xbfIT", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - - cl_assert_equal_b(true, git_path_is_valid(NULL, ".", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".g", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".gi", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, " .git", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "..git\xe2\x80\x8c", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".gi\xe2\x80\x8dT.", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".g\xe2\x80It", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".\xe2gIt", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, "\xe2\x80\xaa.gi", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".gi\x80\x8dT", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".gi\x8dT", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".g\xe2i\x80T\x8e", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git\xe2\x80\xbf", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); - cl_assert_equal_b(true, git_path_is_valid(NULL, ".git\xe2\xab\x81", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); -} diff --git a/tests/libgit2/path/validate.c b/tests/libgit2/path/validate.c new file mode 100644 index 000000000..df9219453 --- /dev/null +++ b/tests/libgit2/path/validate.c @@ -0,0 +1,63 @@ +#include "clar_libgit2.h" +#include "path.h" + +void test_path_validate__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_path_validate__length(void) +{ + cl_must_pass(git_path_validate_length(NULL, "/foo/bar")); + cl_must_pass(git_path_validate_length(NULL, "C:\\Foo\\Bar")); + cl_must_pass(git_path_validate_length(NULL, "\\\\?\\C:\\Foo\\Bar")); + cl_must_pass(git_path_validate_length(NULL, "\\\\?\\C:\\Foo\\Bar")); + cl_must_pass(git_path_validate_length(NULL, "\\\\?\\UNC\\server\\C$\\folder")); + +#ifdef GIT_WIN32 + /* + * In the absence of a repo configuration, 259 character paths + * succeed. >= 260 character paths fail. + */ + cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\ok.txt")); + cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\260.txt")); + cl_must_fail(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\longer_than_260.txt")); + + /* count characters, not bytes */ + cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\260.txt")); + cl_must_fail(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\long.txt")); +#else + cl_must_pass(git_path_validate_length(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/ok.txt")); + cl_must_pass(git_path_validate_length(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/260.txt")); + cl_must_pass(git_path_validate_length(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); + cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\260.txt")); + cl_must_pass(git_path_validate_length(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\long.txt")); +#endif +} + +void test_path_validate__length_with_core_longpath(void) +{ +#ifdef GIT_WIN32 + git_repository *repo; + git_config *config; + + repo = cl_git_sandbox_init("empty_bare.git"); + + cl_git_pass(git_repository_open(&repo, "empty_bare.git")); + cl_git_pass(git_repository_config(&config, repo)); + + /* fail by default */ + cl_must_fail(git_path_validate_length(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); + + /* set core.longpaths explicitly on */ + cl_git_pass(git_config_set_bool(config, "core.longpaths", 1)); + cl_must_pass(git_path_validate_length(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); + + /* set core.longpaths explicitly off */ + cl_git_pass(git_config_set_bool(config, "core.longpaths", 0)); + cl_must_fail(git_path_validate_length(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); + + git_config_free(config); + git_repository_free(repo); +#endif +} diff --git a/tests/libgit2/path/win32.c b/tests/libgit2/path/win32.c deleted file mode 100644 index 1aaf6867a..000000000 --- a/tests/libgit2/path/win32.c +++ /dev/null @@ -1,282 +0,0 @@ - -#include "clar_libgit2.h" - -#ifdef GIT_WIN32 -#include "win32/path_w32.h" -#endif - -#ifdef GIT_WIN32 -static void test_utf8_to_utf16(const char *utf8_in, const wchar_t *utf16_expected) -{ - git_win32_path path_utf16; - int path_utf16len; - - cl_assert((path_utf16len = git_win32_path_from_utf8(path_utf16, utf8_in)) >= 0); - cl_assert_equal_wcs(utf16_expected, path_utf16); - cl_assert_equal_i(wcslen(utf16_expected), path_utf16len); -} - -static void test_utf8_to_utf16_relative(const char* utf8_in, const wchar_t* utf16_expected) -{ - git_win32_path path_utf16; - int path_utf16len; - - cl_assert((path_utf16len = git_win32_path_relative_from_utf8(path_utf16, utf8_in)) >= 0); - cl_assert_equal_wcs(utf16_expected, path_utf16); - cl_assert_equal_i(wcslen(utf16_expected), path_utf16len); -} -#endif - -void test_path_win32__utf8_to_utf16(void) -{ -#ifdef GIT_WIN32 - test_utf8_to_utf16("C:\\", L"\\\\?\\C:\\"); - test_utf8_to_utf16("c:\\", L"\\\\?\\c:\\"); - test_utf8_to_utf16("C:/", L"\\\\?\\C:\\"); - test_utf8_to_utf16("c:/", L"\\\\?\\c:\\"); -#endif -} - -void test_path_win32__removes_trailing_slash(void) -{ -#ifdef GIT_WIN32 - test_utf8_to_utf16("C:\\Foo\\", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("C:/Foo/", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("C:/Foo///", L"\\\\?\\C:\\Foo"); -#endif -} - -void test_path_win32__squashes_multiple_slashes(void) -{ -#ifdef GIT_WIN32 - test_utf8_to_utf16("C:\\\\Foo\\Bar\\\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); - test_utf8_to_utf16("C://Foo/Bar///Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); -#endif -} - -void test_path_win32__unc(void) -{ -#ifdef GIT_WIN32 - test_utf8_to_utf16("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path"); - test_utf8_to_utf16("//server/git/style/unc/path", L"\\\\?\\UNC\\server\\git\\style\\unc\\path"); -#endif -} - -void test_path_win32__honors_max_path(void) -{ -#ifdef GIT_WIN32 - git_win32_path path_utf16; - - test_utf8_to_utf16("C:\\This path is 261 characters which is fine for our path handling functions which cope with paths longer than MAX_PATH\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghijk", - L"\\\\?\\C:\\This path is 261 characters which is fine for our path handling functions which cope with paths longer than MAX_PATH\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghijk"); - - cl_check_fail(git_win32_path_from_utf8(path_utf16, "C:\\This path is 4097 chars and exceeds our maximum path length on Windows which is limited to 4096 characters\\alas\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij01")); - -#endif -} - -void test_path_win32__dot_and_dotdot(void) -{ -#ifdef GIT_WIN32 - test_utf8_to_utf16("C:\\Foo\\..\\Foobar", L"\\\\?\\C:\\Foobar"); - test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar", L"\\\\?\\C:\\Foo\\Foobar"); - test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar\\..", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("C:\\Foobar\\..", L"\\\\?\\C:\\"); - test_utf8_to_utf16("C:/Foo/Bar/../Foobar", L"\\\\?\\C:\\Foo\\Foobar"); - test_utf8_to_utf16("C:/Foo/Bar/../Foobar/../Asdf/", L"\\\\?\\C:\\Foo\\Asdf"); - test_utf8_to_utf16("C:/Foo/Bar/../Foobar/..", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("C:/Foo/..", L"\\\\?\\C:\\"); - - test_utf8_to_utf16("C:\\Foo\\Bar\\.\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); - test_utf8_to_utf16("C:\\.\\Foo\\.\\Bar\\.\\Foobar\\.\\", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); - test_utf8_to_utf16("C:/Foo/Bar/./Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); - test_utf8_to_utf16("C:/Foo/../Bar/./Foobar/../", L"\\\\?\\C:\\Bar"); - - test_utf8_to_utf16("C:\\Foo\\..\\..\\Bar", L"\\\\?\\C:\\Bar"); -#endif -} - -void test_path_win32__absolute_from_no_drive_letter(void) -{ -#ifdef GIT_WIN32 - test_utf8_to_utf16("\\Foo", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar"); - test_utf8_to_utf16("/Foo/Bar", L"\\\\?\\C:\\Foo\\Bar"); -#endif -} - -void test_path_win32__absolute_from_relative(void) -{ -#ifdef GIT_WIN32 - char cwd_backup[MAX_PATH]; - - cl_must_pass(p_getcwd(cwd_backup, MAX_PATH)); - cl_must_pass(p_chdir("C:/")); - - test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("..\\..\\Foo", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("Foo\\..", L"\\\\?\\C:\\"); - test_utf8_to_utf16("Foo\\..\\..", L"\\\\?\\C:\\"); - test_utf8_to_utf16("", L"\\\\?\\C:\\"); - - cl_must_pass(p_chdir("C:/Windows")); - - test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Windows\\Foo"); - test_utf8_to_utf16("Foo\\Bar", L"\\\\?\\C:\\Windows\\Foo\\Bar"); - test_utf8_to_utf16("..\\Foo", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16("Foo\\..\\Bar", L"\\\\?\\C:\\Windows\\Bar"); - test_utf8_to_utf16("", L"\\\\?\\C:\\Windows"); - - cl_must_pass(p_chdir(cwd_backup)); -#endif -} - -void test_path_win32__keeps_relative(void) -{ -#ifdef GIT_WIN32 - /* Relative paths stay relative */ - test_utf8_to_utf16_relative("Foo", L"Foo"); - test_utf8_to_utf16_relative("..\\..\\Foo", L"..\\..\\Foo"); - test_utf8_to_utf16_relative("Foo\\..", L"Foo\\.."); - test_utf8_to_utf16_relative("Foo\\..\\..", L"Foo\\..\\.."); - test_utf8_to_utf16_relative("Foo\\Bar", L"Foo\\Bar"); - test_utf8_to_utf16_relative("Foo\\..\\Bar", L"Foo\\..\\Bar"); - test_utf8_to_utf16_relative("../../Foo", L"..\\..\\Foo"); - test_utf8_to_utf16_relative("Foo/..", L"Foo\\.."); - test_utf8_to_utf16_relative("Foo/../..", L"Foo\\..\\.."); - test_utf8_to_utf16_relative("Foo/Bar", L"Foo\\Bar"); - test_utf8_to_utf16_relative("Foo/../Bar", L"Foo\\..\\Bar"); - test_utf8_to_utf16_relative("Foo/../Bar/", L"Foo\\..\\Bar\\"); - test_utf8_to_utf16_relative("", L""); - - /* Absolute paths are canonicalized */ - test_utf8_to_utf16_relative("\\Foo", L"\\\\?\\C:\\Foo"); - test_utf8_to_utf16_relative("/Foo/Bar/", L"\\\\?\\C:\\Foo\\Bar"); - test_utf8_to_utf16_relative("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path"); -#endif -} - -#ifdef GIT_WIN32 -static void test_canonicalize(const wchar_t *in, const wchar_t *expected) -{ - git_win32_path canonical; - - cl_assert(wcslen(in) < MAX_PATH); - wcscpy(canonical, in); - - cl_must_pass(git_win32_path_canonicalize(canonical)); - cl_assert_equal_wcs(expected, canonical); -} -#endif - -static void test_remove_namespace(const wchar_t *in, const wchar_t *expected) -{ -#ifdef GIT_WIN32 - git_win32_path canonical; - - cl_assert(wcslen(in) < MAX_PATH); - wcscpy(canonical, in); - - git_win32_path_remove_namespace(canonical, wcslen(in)); - cl_assert_equal_wcs(expected, canonical); -#else - GIT_UNUSED(in); - GIT_UNUSED(expected); -#endif -} - -void test_path_win32__remove_namespace(void) -{ - test_remove_namespace(L"\\\\?\\C:\\Temp\\Foo", L"C:\\Temp\\Foo"); - test_remove_namespace(L"\\\\?\\C:\\", L"C:\\"); - test_remove_namespace(L"\\\\?\\", L""); - - test_remove_namespace(L"\\??\\C:\\Temp\\Foo", L"C:\\Temp\\Foo"); - test_remove_namespace(L"\\??\\C:\\", L"C:\\"); - test_remove_namespace(L"\\??\\", L""); - - test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); - test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); - test_remove_namespace(L"\\\\?\\UNC\\server\\C$", L"\\\\server\\C$"); - test_remove_namespace(L"\\\\?\\UNC\\server\\", L"\\\\server"); - test_remove_namespace(L"\\\\?\\UNC\\server", L"\\\\server"); - - test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); - test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); - test_remove_namespace(L"\\??\\UNC\\server\\C$", L"\\\\server\\C$"); - test_remove_namespace(L"\\??\\UNC\\server\\", L"\\\\server"); - test_remove_namespace(L"\\??\\UNC\\server", L"\\\\server"); - - test_remove_namespace(L"\\\\server\\C$\\folder", L"\\\\server\\C$\\folder"); - test_remove_namespace(L"\\\\server\\C$", L"\\\\server\\C$"); - test_remove_namespace(L"\\\\server\\", L"\\\\server"); - test_remove_namespace(L"\\\\server", L"\\\\server"); - - test_remove_namespace(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar"); - test_remove_namespace(L"C:\\", L"C:\\"); - test_remove_namespace(L"", L""); - -} - -void test_path_win32__canonicalize(void) -{ -#ifdef GIT_WIN32 - test_canonicalize(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar"); - test_canonicalize(L"C:\\Foo\\", L"C:\\Foo"); - test_canonicalize(L"C:\\Foo\\\\", L"C:\\Foo"); - test_canonicalize(L"C:\\Foo\\..\\Bar", L"C:\\Bar"); - test_canonicalize(L"C:\\Foo\\..\\..\\Bar", L"C:\\Bar"); - test_canonicalize(L"C:\\Foo\\..\\..\\..\\..\\", L"C:\\"); - test_canonicalize(L"C:/Foo/Bar", L"C:\\Foo\\Bar"); - test_canonicalize(L"C:/", L"C:\\"); - - test_canonicalize(L"\\\\?\\C:\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar"); - test_canonicalize(L"\\\\?\\C:\\Foo\\Bar\\", L"\\\\?\\C:\\Foo\\Bar"); - test_canonicalize(L"\\\\?\\C:\\\\Foo\\.\\Bar\\\\..\\", L"\\\\?\\C:\\Foo"); - test_canonicalize(L"\\\\?\\C:\\\\", L"\\\\?\\C:\\"); - test_canonicalize(L"//?/C:/", L"\\\\?\\C:\\"); - test_canonicalize(L"//?/C:/../../Foo/", L"\\\\?\\C:\\Foo"); - test_canonicalize(L"//?/C:/Foo/../../", L"\\\\?\\C:\\"); - - test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\?\\UNC\\server\\C$\\folder"); - test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder"); - test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder"); - test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\..\\..\\..\\..\\share\\", L"\\\\?\\UNC\\server\\share"); - - test_canonicalize(L"\\\\server\\share", L"\\\\server\\share"); - test_canonicalize(L"\\\\server\\share\\", L"\\\\server\\share"); - test_canonicalize(L"\\\\server\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar"); - test_canonicalize(L"\\\\server\\\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar"); - test_canonicalize(L"\\\\server\\share\\..\\foo", L"\\\\server\\foo"); - test_canonicalize(L"\\\\server\\..\\..\\share\\.\\foo", L"\\\\server\\share\\foo"); -#endif -} - -void test_path_win32__8dot3_name(void) -{ -#ifdef GIT_WIN32 - char *shortname; - - if (!cl_sandbox_supports_8dot3()) - clar__skip(); - - /* Some guaranteed short names */ - cl_assert_equal_s("PROGRA~1", (shortname = git_win32_path_8dot3_name("C:\\Program Files"))); - git__free(shortname); - - cl_assert_equal_s("WINDOWS", (shortname = git_win32_path_8dot3_name("C:\\WINDOWS"))); - git__free(shortname); - - /* Create some predictable short names */ - cl_must_pass(p_mkdir(".foo", 0777)); - cl_assert_equal_s("FOO~1", (shortname = git_win32_path_8dot3_name(".foo"))); - git__free(shortname); - - cl_git_write2file("bar~1", "foobar\n", 7, O_RDWR|O_CREAT, 0666); - cl_must_pass(p_mkdir(".bar", 0777)); - cl_assert_equal_s("BAR~2", (shortname = git_win32_path_8dot3_name(".bar"))); - git__free(shortname); -#endif -} diff --git a/tests/libgit2/str/basic.c b/tests/libgit2/str/basic.c deleted file mode 100644 index 5d2556805..000000000 --- a/tests/libgit2/str/basic.c +++ /dev/null @@ -1,50 +0,0 @@ -#include "clar_libgit2.h" - -static const char *test_string = "Have you seen that? Have you seeeen that??"; - -void test_str_basic__resize(void) -{ - git_str buf1 = GIT_STR_INIT; - git_str_puts(&buf1, test_string); - cl_assert(git_str_oom(&buf1) == 0); - cl_assert_equal_s(git_str_cstr(&buf1), test_string); - - git_str_puts(&buf1, test_string); - cl_assert(strlen(git_str_cstr(&buf1)) == strlen(test_string) * 2); - git_str_dispose(&buf1); -} - -void test_str_basic__resize_incremental(void) -{ - git_str buf1 = GIT_STR_INIT; - - /* Presently, asking for 6 bytes will round up to 8. */ - cl_git_pass(git_str_puts(&buf1, "Hello")); - cl_assert_equal_i(5, buf1.size); - cl_assert_equal_i(8, buf1.asize); - - /* Ensure an additional byte does not realloc. */ - cl_git_pass(git_str_grow_by(&buf1, 1)); - cl_assert_equal_i(5, buf1.size); - cl_assert_equal_i(8, buf1.asize); - - /* But requesting many does. */ - cl_git_pass(git_str_grow_by(&buf1, 16)); - cl_assert_equal_i(5, buf1.size); - cl_assert(buf1.asize > 8); - - git_str_dispose(&buf1); -} - -void test_str_basic__printf(void) -{ - git_str buf2 = GIT_STR_INIT; - git_str_printf(&buf2, "%s %s %d ", "shoop", "da", 23); - cl_assert(git_str_oom(&buf2) == 0); - cl_assert_equal_s(git_str_cstr(&buf2), "shoop da 23 "); - - git_str_printf(&buf2, "%s %d", "woop", 42); - cl_assert(git_str_oom(&buf2) == 0); - cl_assert_equal_s(git_str_cstr(&buf2), "shoop da 23 woop 42"); - git_str_dispose(&buf2); -} diff --git a/tests/libgit2/str/oom.c b/tests/libgit2/str/oom.c deleted file mode 100644 index 3d59ead01..000000000 --- a/tests/libgit2/str/oom.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "clar_libgit2.h" - -/* Override default allocators with ones that will fail predictably. */ - -static git_allocator std_alloc; -static git_allocator oom_alloc; - -static void *oom_malloc(size_t n, const char *file, int line) -{ - /* Reject any allocation of more than 100 bytes */ - return (n > 100) ? NULL : std_alloc.gmalloc(n, file, line); -} - -static void *oom_realloc(void *p, size_t n, const char *file, int line) -{ - /* Reject any allocation of more than 100 bytes */ - return (n > 100) ? NULL : std_alloc.grealloc(p, n, file, line); -} - -void test_str_oom__initialize(void) -{ - git_stdalloc_init_allocator(&std_alloc); - git_stdalloc_init_allocator(&oom_alloc); - - oom_alloc.gmalloc = oom_malloc; - oom_alloc.grealloc = oom_realloc; - - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ALLOCATOR, &oom_alloc)); -} - -void test_str_oom__cleanup(void) -{ - cl_git_pass(git_libgit2_opts(GIT_OPT_SET_ALLOCATOR, NULL)); -} - -void test_str_oom__grow(void) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_grow(&buf, 42)); - cl_assert(!git_str_oom(&buf)); - - cl_assert(git_str_grow(&buf, 101) == -1); - cl_assert(git_str_oom(&buf)); - - git_str_dispose(&buf); -} - -void test_str_oom__grow_by(void) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_grow_by(&buf, 42)); - cl_assert(!git_str_oom(&buf)); - - cl_assert(git_str_grow_by(&buf, 101) == -1); - cl_assert(git_str_oom(&buf)); -} diff --git a/tests/libgit2/str/percent.c b/tests/libgit2/str/percent.c deleted file mode 100644 index 339389075..000000000 --- a/tests/libgit2/str/percent.c +++ /dev/null @@ -1,48 +0,0 @@ -#include "clar_libgit2.h" - -static void expect_decode_pass(const char *expected, const char *encoded) -{ - git_str in = GIT_STR_INIT, out = GIT_STR_INIT; - - /* - * ensure that we only read the given length of the input buffer - * by putting garbage at the end. this will ensure that we do - * not, eg, rely on nul-termination or walk off the end of the buf. - */ - cl_git_pass(git_str_puts(&in, encoded)); - cl_git_pass(git_str_PUTS(&in, "TRAILER")); - - cl_git_pass(git_str_decode_percent(&out, in.ptr, strlen(encoded))); - - cl_assert_equal_s(expected, git_str_cstr(&out)); - cl_assert_equal_i(strlen(expected), git_str_len(&out)); - - git_str_dispose(&in); - git_str_dispose(&out); -} - -void test_str_percent__decode_succeeds(void) -{ - expect_decode_pass("", ""); - expect_decode_pass(" ", "%20"); - expect_decode_pass("a", "a"); - expect_decode_pass(" a", "%20a"); - expect_decode_pass("a ", "a%20"); - expect_decode_pass("github.com", "github.com"); - expect_decode_pass("github.com", "githu%62.com"); - expect_decode_pass("github.com", "github%2ecom"); - expect_decode_pass("foo bar baz", "foo%20bar%20baz"); - expect_decode_pass("foo bar baz", "foo%20bar%20baz"); - expect_decode_pass("foo bar ", "foo%20bar%20"); -} - -void test_str_percent__ignores_invalid(void) -{ - expect_decode_pass("githu%%.com", "githu%%.com"); - expect_decode_pass("github.co%2", "github.co%2"); - expect_decode_pass("github%2.com", "github%2.com"); - expect_decode_pass("githu%2z.com", "githu%2z.com"); - expect_decode_pass("github.co%9z", "github.co%9z"); - expect_decode_pass("github.co%2", "github.co%2"); - expect_decode_pass("github.co%", "github.co%"); -} diff --git a/tests/libgit2/str/quote.c b/tests/libgit2/str/quote.c deleted file mode 100644 index 2c6546247..000000000 --- a/tests/libgit2/str/quote.c +++ /dev/null @@ -1,87 +0,0 @@ -#include "clar_libgit2.h" - -static void expect_quote_pass(const char *expected, const char *str) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_puts(&buf, str)); - cl_git_pass(git_str_quote(&buf)); - - cl_assert_equal_s(expected, git_str_cstr(&buf)); - cl_assert_equal_i(strlen(expected), git_str_len(&buf)); - - git_str_dispose(&buf); -} - -void test_str_quote__quote_succeeds(void) -{ - expect_quote_pass("", ""); - expect_quote_pass("foo", "foo"); - expect_quote_pass("foo/bar/baz.c", "foo/bar/baz.c"); - expect_quote_pass("foo bar", "foo bar"); - expect_quote_pass("\"\\\"leading quote\"", "\"leading quote"); - expect_quote_pass("\"slash\\\\y\"", "slash\\y"); - expect_quote_pass("\"foo\\r\\nbar\"", "foo\r\nbar"); - expect_quote_pass("\"foo\\177bar\"", "foo\177bar"); - expect_quote_pass("\"foo\\001bar\"", "foo\001bar"); - expect_quote_pass("\"foo\\377bar\"", "foo\377bar"); -} - -static void expect_unquote_pass(const char *expected, const char *quoted) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_puts(&buf, quoted)); - cl_git_pass(git_str_unquote(&buf)); - - cl_assert_equal_s(expected, git_str_cstr(&buf)); - cl_assert_equal_i(strlen(expected), git_str_len(&buf)); - - git_str_dispose(&buf); -} - -static void expect_unquote_fail(const char *quoted) -{ - git_str buf = GIT_STR_INIT; - - cl_git_pass(git_str_puts(&buf, quoted)); - cl_git_fail(git_str_unquote(&buf)); - - git_str_dispose(&buf); -} - -void test_str_quote__unquote_succeeds(void) -{ - expect_unquote_pass("", "\"\""); - expect_unquote_pass(" ", "\" \""); - expect_unquote_pass("foo", "\"foo\""); - expect_unquote_pass("foo bar", "\"foo bar\""); - expect_unquote_pass("foo\"bar", "\"foo\\\"bar\""); - expect_unquote_pass("foo\\bar", "\"foo\\\\bar\""); - expect_unquote_pass("foo\tbar", "\"foo\\tbar\""); - expect_unquote_pass("\vfoo\tbar\n", "\"\\vfoo\\tbar\\n\""); - expect_unquote_pass("foo\nbar", "\"foo\\012bar\""); - expect_unquote_pass("foo\r\nbar", "\"foo\\015\\012bar\""); - expect_unquote_pass("foo\r\nbar", "\"\\146\\157\\157\\015\\012\\142\\141\\162\""); - expect_unquote_pass("newline: \n", "\"newline: \\012\""); - expect_unquote_pass("0xff: \377", "\"0xff: \\377\""); -} - -void test_str_quote__unquote_fails(void) -{ - expect_unquote_fail("no quotes at all"); - expect_unquote_fail("\"no trailing quote"); - expect_unquote_fail("no leading quote\""); - expect_unquote_fail("\"invalid \\z escape char\""); - expect_unquote_fail("\"\\q invalid escape char\""); - expect_unquote_fail("\"invalid escape char \\p\""); - expect_unquote_fail("\"invalid \\1 escape char \""); - expect_unquote_fail("\"invalid \\14 escape char \""); - expect_unquote_fail("\"invalid \\280 escape char\""); - expect_unquote_fail("\"invalid \\378 escape char\""); - expect_unquote_fail("\"invalid \\380 escape char\""); - expect_unquote_fail("\"invalid \\411 escape char\""); - expect_unquote_fail("\"truncated escape char \\\""); - expect_unquote_fail("\"truncated escape char \\0\""); - expect_unquote_fail("\"truncated escape char \\01\""); -} diff --git a/tests/libgit2/str/splice.c b/tests/libgit2/str/splice.c deleted file mode 100644 index 14e844e2f..000000000 --- a/tests/libgit2/str/splice.c +++ /dev/null @@ -1,92 +0,0 @@ -#include "clar_libgit2.h" - -static git_str _buf; - -void test_str_splice__initialize(void) { - git_str_init(&_buf, 16); -} - -void test_str_splice__cleanup(void) { - git_str_dispose(&_buf); -} - -void test_str_splice__preprend(void) -{ - git_str_sets(&_buf, "world!"); - - cl_git_pass(git_str_splice(&_buf, 0, 0, "Hello Dolly", strlen("Hello "))); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__append(void) -{ - git_str_sets(&_buf, "Hello"); - - cl_git_pass(git_str_splice(&_buf, git_str_len(&_buf), 0, " world!", strlen(" world!"))); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__insert_at(void) -{ - git_str_sets(&_buf, "Hell world!"); - - cl_git_pass(git_str_splice(&_buf, strlen("Hell"), 0, "o", strlen("o"))); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__remove_at(void) -{ - git_str_sets(&_buf, "Hello world of warcraft!"); - - cl_git_pass(git_str_splice(&_buf, strlen("Hello world"), strlen(" of warcraft"), "", 0)); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__replace(void) -{ - git_str_sets(&_buf, "Hell0 w0rld!"); - - cl_git_pass(git_str_splice(&_buf, strlen("Hell"), strlen("0 w0"), "o wo", strlen("o wo"))); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__replace_with_longer(void) -{ - git_str_sets(&_buf, "Hello you!"); - - cl_git_pass(git_str_splice(&_buf, strlen("Hello "), strlen("you"), "world", strlen("world"))); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__replace_with_shorter(void) -{ - git_str_sets(&_buf, "Brave new world!"); - - cl_git_pass(git_str_splice(&_buf, 0, strlen("Brave new"), "Hello", strlen("Hello"))); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__truncate(void) -{ - git_str_sets(&_buf, "Hello world!!"); - - cl_git_pass(git_str_splice(&_buf, strlen("Hello world!"), strlen("!"), "", 0)); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} - -void test_str_splice__dont_do_anything(void) -{ - git_str_sets(&_buf, "Hello world!"); - - cl_git_pass(git_str_splice(&_buf, 3, 0, "Hello", 0)); - - cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); -} diff --git a/tests/util/CMakeLists.txt b/tests/util/CMakeLists.txt new file mode 100644 index 000000000..739eb5859 --- /dev/null +++ b/tests/util/CMakeLists.txt @@ -0,0 +1,75 @@ +# util: the unit tests for libgit2's utility library + +set(Python_ADDITIONAL_VERSIONS 3 2.7) +find_package(PythonInterp) + +if(NOT PYTHONINTERP_FOUND) + message(FATAL_ERROR "Could not find a python interpeter, which is needed to build the tests. " + "Make sure python is available, or pass -DBUILD_TESTS=OFF to skip building the tests") +ENDIF() + +set(CLAR_PATH "${libgit2_SOURCE_DIR}/tests/clar") +set(CLAR_FIXTURES "${libgit2_SOURCE_DIR}/tests/resources/") +set(TEST_PATH "${CMAKE_CURRENT_SOURCE_DIR}") +add_definitions(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\") +add_definitions(-DCLAR_TMPDIR=\"libgit2_tests\") +add_definitions(-DCLAR_WIN32_LONGPATHS) +add_definitions(-D_FILE_OFFSET_BITS=64) + +# Ensure that we do not use deprecated functions internally +add_definitions(-DGIT_DEPRECATE_HARD) + +set(TEST_INCLUDES "${CLAR_PATH}" "${TEST_PATH}" "${CMAKE_CURRENT_BINARY_DIR}") +file(GLOB_RECURSE SRC_TEST ${TEST_PATH}/*.c ${TEST_PATH}/*.h ${TEST_PATH}/*/*.c ${TEST_PATH}/*/*.h) +file(GLOB_RECURSE SRC_CLAR ${CLAR_PATH}/*.c ${CLAR_PATH}/*.h) + +if(MSVC_IDE) + list(APPEND SRC_TEST "precompiled.c") +endif() + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/clar.suite ${CMAKE_CURRENT_BINARY_DIR}/clar_suite.h + COMMAND ${PYTHON_EXECUTABLE} ${CLAR_PATH}/generate.py -o "${CMAKE_CURRENT_BINARY_DIR}" -f . + DEPENDS ${SRC_TEST} + WORKING_DIRECTORY ${TEST_PATH}) + +set_source_files_properties( + ${CLAR_PATH}/clar.c + PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/clar.suite) + +add_executable(util_tests ${SRC_CLAR} ${SRC_TEST} ${LIBGIT2_OBJECTS}) + +set_target_properties(util_tests PROPERTIES C_STANDARD 90) +set_target_properties(util_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR}) + +target_include_directories(util_tests PRIVATE ${TEST_INCLUDES} ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES}) +target_include_directories(util_tests SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) +target_link_libraries(util_tests ${LIBGIT2_SYSTEM_LIBS}) + +ide_split_sources(util_tests) + +# +# Old versions of gcc require us to declare our test functions; don't do +# this on newer compilers to avoid unnecessary recompilation. +# +if(CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) + target_compile_options(util_tests PRIVATE -include "clar_suite.h") +endif() + +if(MSVC_IDE) + # Precompiled headers + set_target_properties(util_tests PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") + set_source_files_properties("precompiled.c" COMPILE_FLAGS "/Ycprecompiled.h") +endif() + +function(ADD_CLAR_TEST name) + if(NOT USE_LEAK_CHECKER STREQUAL "OFF") + add_test(${name} "${libgit2_SOURCE_DIR}/script/${USE_LEAK_CHECKER}.sh" "${libgit2_BINARY_DIR}/util_tests" ${ARGN}) + else() + add_test(${name} "${libgit2_BINARY_DIR}/util_tests" ${ARGN}) + endif() +endfunction(ADD_CLAR_TEST) + +enable_testing() + +add_clar_test(util -v) diff --git a/tests/util/array.c b/tests/util/array.c new file mode 100644 index 000000000..39fbc811d --- /dev/null +++ b/tests/util/array.c @@ -0,0 +1,57 @@ +#include "clar_libgit2.h" +#include "array.h" + +static int int_lookup(const void *k, const void *a) +{ + const int *one = (const int *)k; + int *two = (int *)a; + + return *one - *two; +} + +#define expect_pos(k, n, ret) \ + key = (k); \ + cl_assert_equal_i((ret), \ + git_array_search(&p, integers, int_lookup, &key)); \ + cl_assert_equal_i((n), p); + +void test_array__bsearch2(void) +{ + git_array_t(int) integers = GIT_ARRAY_INIT; + int *i, key; + size_t p; + + i = git_array_alloc(integers); *i = 2; + i = git_array_alloc(integers); *i = 3; + i = git_array_alloc(integers); *i = 5; + i = git_array_alloc(integers); *i = 7; + i = git_array_alloc(integers); *i = 7; + i = git_array_alloc(integers); *i = 8; + i = git_array_alloc(integers); *i = 13; + i = git_array_alloc(integers); *i = 21; + i = git_array_alloc(integers); *i = 25; + i = git_array_alloc(integers); *i = 42; + i = git_array_alloc(integers); *i = 69; + i = git_array_alloc(integers); *i = 121; + i = git_array_alloc(integers); *i = 256; + i = git_array_alloc(integers); *i = 512; + i = git_array_alloc(integers); *i = 513; + i = git_array_alloc(integers); *i = 514; + i = git_array_alloc(integers); *i = 516; + i = git_array_alloc(integers); *i = 516; + i = git_array_alloc(integers); *i = 517; + + /* value to search for, expected position, return code */ + expect_pos(3, 1, GIT_OK); + expect_pos(2, 0, GIT_OK); + expect_pos(1, 0, GIT_ENOTFOUND); + expect_pos(25, 8, GIT_OK); + expect_pos(26, 9, GIT_ENOTFOUND); + expect_pos(42, 9, GIT_OK); + expect_pos(50, 10, GIT_ENOTFOUND); + expect_pos(68, 10, GIT_ENOTFOUND); + expect_pos(256, 12, GIT_OK); + + git_array_clear(integers); +} + diff --git a/tests/util/assert.c b/tests/util/assert.c new file mode 100644 index 000000000..26c429787 --- /dev/null +++ b/tests/util/assert.c @@ -0,0 +1,94 @@ +#ifdef GIT_ASSERT_HARD +# undef GIT_ASSERT_HARD +#endif + +#define GIT_ASSERT_HARD 0 + +#include "clar_libgit2.h" + +static const char *hello_world = "hello, world"; +static const char *fail = "FAIL"; + +static int dummy_fn(const char *myarg) +{ + GIT_ASSERT_ARG(myarg); + GIT_ASSERT_ARG(myarg != hello_world); + return 0; +} + +static const char *fn_returns_string(const char *myarg) +{ + GIT_ASSERT_ARG_WITH_RETVAL(myarg, fail); + GIT_ASSERT_ARG_WITH_RETVAL(myarg != hello_world, fail); + + return myarg; +} + +static int bad_math(void) +{ + GIT_ASSERT(1 + 1 == 3); + return 42; +} + +static const char *bad_returns_string(void) +{ + GIT_ASSERT_WITH_RETVAL(1 + 1 == 3, NULL); + return hello_world; +} + +void test_assert__argument(void) +{ + cl_git_fail(dummy_fn(NULL)); + cl_assert(git_error_last()); + cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); + cl_assert_equal_s("invalid argument: 'myarg'", git_error_last()->message); + + cl_git_fail(dummy_fn(hello_world)); + cl_assert(git_error_last()); + cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); + cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message); + + cl_git_pass(dummy_fn("foo")); +} + +void test_assert__argument_with_non_int_return_type(void) +{ + const char *foo = "foo"; + + cl_assert_equal_p(fail, fn_returns_string(NULL)); + cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); + cl_assert_equal_s("invalid argument: 'myarg'", git_error_last()->message); + + cl_assert_equal_p(fail, fn_returns_string(hello_world)); + cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); + cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message); + + cl_assert_equal_p(foo, fn_returns_string(foo)); +} + +void test_assert__argument_with_void_return_type(void) +{ + const char *foo = "foo"; + + git_error_clear(); + fn_returns_string(hello_world); + cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass); + cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message); + + git_error_clear(); + cl_assert_equal_p(foo, fn_returns_string(foo)); + cl_assert_equal_p(NULL, git_error_last()); +} + +void test_assert__internal(void) +{ + cl_git_fail(bad_math()); + cl_assert(git_error_last()); + cl_assert_equal_i(GIT_ERROR_INTERNAL, git_error_last()->klass); + cl_assert_equal_s("unrecoverable internal error: '1 + 1 == 3'", git_error_last()->message); + + cl_assert_equal_p(NULL, bad_returns_string()); + cl_assert(git_error_last()); + cl_assert_equal_i(GIT_ERROR_INTERNAL, git_error_last()->klass); + cl_assert_equal_s("unrecoverable internal error: '1 + 1 == 3'", git_error_last()->message); +} diff --git a/tests/util/bitvec.c b/tests/util/bitvec.c new file mode 100644 index 000000000..f7845895b --- /dev/null +++ b/tests/util/bitvec.c @@ -0,0 +1,64 @@ +#include "clar_libgit2.h" +#include "bitvec.h" + +#if 0 +static void print_bitvec(git_bitvec *bv) +{ + int b; + + if (!bv->length) { + for (b = 63; b >= 0; --b) + fprintf(stderr, "%d", (bv->u.bits & (1ul << b)) ? 1 : 0); + } else { + for (b = bv->length * 8; b >= 0; --b) + fprintf(stderr, "%d", (bv->u.ptr[b >> 3] & (b & 0x0ff)) ? 1 : 0); + } + fprintf(stderr, "\n"); +} +#endif + +static void set_some_bits(git_bitvec *bv, size_t length) +{ + size_t i; + + for (i = 0; i < length; ++i) { + if (i % 3 == 0 || i % 7 == 0) + git_bitvec_set(bv, i, true); + } +} + +static void check_some_bits(git_bitvec *bv, size_t length) +{ + size_t i; + + for (i = 0; i < length; ++i) + cl_assert_equal_b(i % 3 == 0 || i % 7 == 0, git_bitvec_get(bv, i)); +} + +void test_bitvec__0(void) +{ + git_bitvec bv; + + cl_git_pass(git_bitvec_init(&bv, 32)); + set_some_bits(&bv, 16); + check_some_bits(&bv, 16); + git_bitvec_clear(&bv); + set_some_bits(&bv, 32); + check_some_bits(&bv, 32); + git_bitvec_clear(&bv); + set_some_bits(&bv, 64); + check_some_bits(&bv, 64); + git_bitvec_free(&bv); + + cl_git_pass(git_bitvec_init(&bv, 128)); + set_some_bits(&bv, 32); + check_some_bits(&bv, 32); + set_some_bits(&bv, 128); + check_some_bits(&bv, 128); + git_bitvec_free(&bv); + + cl_git_pass(git_bitvec_init(&bv, 4000)); + set_some_bits(&bv, 4000); + check_some_bits(&bv, 4000); + git_bitvec_free(&bv); +} diff --git a/tests/util/copy.c b/tests/util/copy.c new file mode 100644 index 000000000..9b9f6bafc --- /dev/null +++ b/tests/util/copy.c @@ -0,0 +1,153 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "posix.h" + +void test_copy__file(void) +{ + struct stat st; + const char *content = "This is some stuff to copy\n"; + + cl_git_mkfile("copy_me", content); + + cl_git_pass(git_futils_cp("copy_me", "copy_me_two", 0664)); + + cl_git_pass(git_fs_path_lstat("copy_me_two", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert(strlen(content) == (size_t)st.st_size); + + cl_git_pass(p_unlink("copy_me_two")); + cl_git_pass(p_unlink("copy_me")); +} + +void test_copy__file_in_dir(void) +{ + struct stat st; + const char *content = "This is some other stuff to copy\n"; + + cl_git_pass(git_futils_mkdir("an_dir/in_a_dir", 0775, GIT_MKDIR_PATH)); + cl_git_mkfile("an_dir/in_a_dir/copy_me", content); + cl_assert(git_fs_path_isdir("an_dir")); + + cl_git_pass(git_futils_mkpath2file + ("an_dir/second_dir/and_more/copy_me_two", 0775)); + + cl_git_pass(git_futils_cp + ("an_dir/in_a_dir/copy_me", + "an_dir/second_dir/and_more/copy_me_two", + 0664)); + + cl_git_pass(git_fs_path_lstat("an_dir/second_dir/and_more/copy_me_two", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert(strlen(content) == (size_t)st.st_size); + + cl_git_pass(git_futils_rmdir_r("an_dir", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_assert(!git_fs_path_isdir("an_dir")); +} + +#ifndef GIT_WIN32 +static void assert_hard_link(const char *path) +{ + /* we assert this by checking that there's more than one link to the file */ + struct stat st; + + cl_assert(git_fs_path_isfile(path)); + cl_git_pass(p_stat(path, &st)); + cl_assert(st.st_nlink > 1); +} +#endif + +void test_copy__tree(void) +{ + struct stat st; + const char *content = "File content\n"; + + cl_git_pass(git_futils_mkdir("src/b", 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("src/c/d", 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("src/c/e", 0775, GIT_MKDIR_PATH)); + + cl_git_mkfile("src/f1", content); + cl_git_mkfile("src/b/f2", content); + cl_git_mkfile("src/c/f3", content); + cl_git_mkfile("src/c/d/f4", content); + cl_git_mkfile("src/c/d/.f5", content); + +#ifndef GIT_WIN32 + cl_assert(p_symlink("../../b/f2", "src/c/d/l1") == 0); +#endif + + cl_assert(git_fs_path_isdir("src")); + cl_assert(git_fs_path_isdir("src/b")); + cl_assert(git_fs_path_isdir("src/c/d")); + cl_assert(git_fs_path_isfile("src/c/d/f4")); + + /* copy with no empty dirs, yes links, no dotfiles, no overwrite */ + + cl_git_pass( + git_futils_cp_r("src", "t1", GIT_CPDIR_COPY_SYMLINKS, 0) ); + + cl_assert(git_fs_path_isdir("t1")); + cl_assert(git_fs_path_isdir("t1/b")); + cl_assert(git_fs_path_isdir("t1/c")); + cl_assert(git_fs_path_isdir("t1/c/d")); + cl_assert(!git_fs_path_isdir("t1/c/e")); + + cl_assert(git_fs_path_isfile("t1/f1")); + cl_assert(git_fs_path_isfile("t1/b/f2")); + cl_assert(git_fs_path_isfile("t1/c/f3")); + cl_assert(git_fs_path_isfile("t1/c/d/f4")); + cl_assert(!git_fs_path_isfile("t1/c/d/.f5")); + + cl_git_pass(git_fs_path_lstat("t1/c/f3", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert(strlen(content) == (size_t)st.st_size); + +#ifndef GIT_WIN32 + cl_git_pass(git_fs_path_lstat("t1/c/d/l1", &st)); + cl_assert(S_ISLNK(st.st_mode)); +#endif + + cl_git_pass(git_futils_rmdir_r("t1", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_assert(!git_fs_path_isdir("t1")); + + /* copy with empty dirs, no links, yes dotfiles, no overwrite */ + + cl_git_pass( + git_futils_cp_r("src", "t2", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_COPY_DOTFILES, 0) ); + + cl_assert(git_fs_path_isdir("t2")); + cl_assert(git_fs_path_isdir("t2/b")); + cl_assert(git_fs_path_isdir("t2/c")); + cl_assert(git_fs_path_isdir("t2/c/d")); + cl_assert(git_fs_path_isdir("t2/c/e")); + + cl_assert(git_fs_path_isfile("t2/f1")); + cl_assert(git_fs_path_isfile("t2/b/f2")); + cl_assert(git_fs_path_isfile("t2/c/f3")); + cl_assert(git_fs_path_isfile("t2/c/d/f4")); + cl_assert(git_fs_path_isfile("t2/c/d/.f5")); + +#ifndef GIT_WIN32 + cl_git_fail(git_fs_path_lstat("t2/c/d/l1", &st)); +#endif + + cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_assert(!git_fs_path_isdir("t2")); + +#ifndef GIT_WIN32 + cl_git_pass(git_futils_cp_r("src", "t3", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_LINK_FILES, 0)); + cl_assert(git_fs_path_isdir("t3")); + + cl_assert(git_fs_path_isdir("t3")); + cl_assert(git_fs_path_isdir("t3/b")); + cl_assert(git_fs_path_isdir("t3/c")); + cl_assert(git_fs_path_isdir("t3/c/d")); + cl_assert(git_fs_path_isdir("t3/c/e")); + + assert_hard_link("t3/f1"); + assert_hard_link("t3/b/f2"); + assert_hard_link("t3/c/f3"); + assert_hard_link("t3/c/d/f4"); +#endif + + cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_RMDIR_REMOVE_FILES)); +} diff --git a/tests/util/crlf.h b/tests/util/crlf.h new file mode 100644 index 000000000..786edfc96 --- /dev/null +++ b/tests/util/crlf.h @@ -0,0 +1,30 @@ +#ifndef INCLUDE_filter_crlf_h__ +#define INCLUDE_filter_crlf_h__ + +/* + * file content for files in the resources/crlf repository + */ + +#define UTF8_BOM "\xEF\xBB\xBF" + +#define ALL_CRLF_TEXT_RAW "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n" +#define ALL_LF_TEXT_RAW "lf\nlf\nlf\nlf\nlf\n" +#define MORE_CRLF_TEXT_RAW "crlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf\r\n" +#define MORE_LF_TEXT_RAW "lf\nlf\ncrlf\r\nlf\nlf\n" + +#define ALL_CRLF_TEXT_AS_CRLF ALL_CRLF_TEXT_RAW +#define ALL_LF_TEXT_AS_CRLF "lf\r\nlf\r\nlf\r\nlf\r\nlf\r\n" +#define MORE_CRLF_TEXT_AS_CRLF "crlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf\r\n" +#define MORE_LF_TEXT_AS_CRLF "lf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\n" + +#define ALL_CRLF_TEXT_AS_LF "crlf\ncrlf\ncrlf\ncrlf\n" +#define ALL_LF_TEXT_AS_LF ALL_LF_TEXT_RAW +#define MORE_CRLF_TEXT_AS_LF "crlf\ncrlf\nlf\ncrlf\ncrlf\n" +#define MORE_LF_TEXT_AS_LF "lf\nlf\ncrlf\nlf\nlf\n" + +#define FEW_UTF8_CRLF_RAW "\xe2\x9a\xbdThe rest is ASCII01.\r\nThe rest is ASCII02.\r\nThe rest is ASCII03.\r\nThe rest is ASCII04.\r\nThe rest is ASCII05.\r\nThe rest is ASCII06.\r\nThe rest is ASCII07.\r\nThe rest is ASCII08.\r\nThe rest is ASCII09.\r\nThe rest is ASCII10.\r\nThe rest is ASCII11.\r\nThe rest is ASCII12.\r\nThe rest is ASCII13.\r\nThe rest is ASCII14.\r\nThe rest is ASCII15.\r\nThe rest is ASCII16.\r\nThe rest is ASCII17.\r\nThe rest is ASCII18.\r\nThe rest is ASCII19.\r\nThe rest is ASCII20.\r\nThe rest is ASCII21.\r\nThe rest is ASCII22.\r\n" +#define FEW_UTF8_LF_RAW "\xe2\x9a\xbdThe rest is ASCII01.\nThe rest is ASCII02.\nThe rest is ASCII03.\nThe rest is ASCII04.\nThe rest is ASCII05.\nThe rest is ASCII06.\nThe rest is ASCII07.\nThe rest is ASCII08.\nThe rest is ASCII09.\nThe rest is ASCII10.\nThe rest is ASCII11.\nThe rest is ASCII12.\nThe rest is ASCII13.\nThe rest is ASCII14.\nThe rest is ASCII15.\nThe rest is ASCII16.\nThe rest is ASCII17.\nThe rest is ASCII18.\nThe rest is ASCII19.\nThe rest is ASCII20.\nThe rest is ASCII21.\nThe rest is ASCII22.\n" +#define MANY_UTF8_CRLF_RAW "Lets sing!\r\n\xe2\x99\xab\xe2\x99\xaa\xe2\x99\xac\xe2\x99\xa9\r\nEat food\r\n\xf0\x9f\x8d\x85\xf0\x9f\x8d\x95\r\n" +#define MANY_UTF8_LF_RAW "Lets sing!\n\xe2\x99\xab\xe2\x99\xaa\xe2\x99\xac\xe2\x99\xa9\nEat food\n\xf0\x9f\x8d\x85\xf0\x9f\x8d\x95\n" + +#endif diff --git a/tests/util/dirent.c b/tests/util/dirent.c new file mode 100644 index 000000000..5840c67e0 --- /dev/null +++ b/tests/util/dirent.c @@ -0,0 +1,306 @@ +#include "clar_libgit2.h" +#include "futils.h" + +typedef struct name_data { + int count; /* return count */ + char *name; /* filename */ +} name_data; + +typedef struct walk_data { + char *sub; /* sub-directory name */ + name_data *names; /* name state data */ + git_str path; +} walk_data; + + +static char *top_dir = "dir-walk"; +static walk_data *state_loc; + +static void setup(walk_data *d) +{ + name_data *n; + + cl_must_pass(p_mkdir(top_dir, 0777)); + + cl_must_pass(p_chdir(top_dir)); + + if (strcmp(d->sub, ".") != 0) + cl_must_pass(p_mkdir(d->sub, 0777)); + + cl_git_pass(git_str_sets(&d->path, d->sub)); + + state_loc = d; + + for (n = d->names; n->name; n++) { + git_file fd = p_creat(n->name, 0666); + cl_assert(fd >= 0); + p_close(fd); + n->count = 0; + } +} + +static void dirent_cleanup__cb(void *_d) +{ + walk_data *d = _d; + name_data *n; + + for (n = d->names; n->name; n++) { + cl_must_pass(p_unlink(n->name)); + } + + if (strcmp(d->sub, ".") != 0) + cl_must_pass(p_rmdir(d->sub)); + + cl_must_pass(p_chdir("..")); + + cl_must_pass(p_rmdir(top_dir)); + + git_str_dispose(&d->path); +} + +static void check_counts(walk_data *d) +{ + name_data *n; + + for (n = d->names; n->name; n++) { + cl_assert(n->count == 1); + } +} + +static int update_count(name_data *data, const char *name) +{ + name_data *n; + + for (n = data; n->name; n++) { + if (!strcmp(n->name, name)) { + n->count++; + return 0; + } + } + + return GIT_ERROR; +} + +static int one_entry(void *state, git_str *path) +{ + walk_data *d = (walk_data *) state; + + if (state != state_loc) + return GIT_ERROR; + + if (path != &d->path) + return GIT_ERROR; + + return update_count(d->names, path->ptr); +} + + +static name_data dot_names[] = { + { 0, "./a" }, + { 0, "./asdf" }, + { 0, "./pack-foo.pack" }, + { 0, NULL } +}; +static walk_data dot = { + ".", + dot_names, + GIT_STR_INIT +}; + +/* make sure that the '.' folder is not traversed */ +void test_dirent__dont_traverse_dot(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &dot); + setup(&dot); + + cl_git_pass(git_fs_path_direach(&dot.path, 0, one_entry, &dot)); + + check_counts(&dot); +} + + +static name_data sub_names[] = { + { 0, "sub/a" }, + { 0, "sub/asdf" }, + { 0, "sub/pack-foo.pack" }, + { 0, NULL } +}; +static walk_data sub = { + "sub", + sub_names, + GIT_STR_INIT +}; + +/* traverse a subfolder */ +void test_dirent__traverse_subfolder(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &sub); + setup(&sub); + + cl_git_pass(git_fs_path_direach(&sub.path, 0, one_entry, &sub)); + + check_counts(&sub); +} + + +static walk_data sub_slash = { + "sub/", + sub_names, + GIT_STR_INIT +}; + +/* traverse a slash-terminated subfolder */ +void test_dirent__traverse_slash_terminated_folder(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &sub_slash); + setup(&sub_slash); + + cl_git_pass(git_fs_path_direach(&sub_slash.path, 0, one_entry, &sub_slash)); + + check_counts(&sub_slash); +} + + +static name_data empty_names[] = { + { 0, NULL } +}; +static walk_data empty = { + "empty", + empty_names, + GIT_STR_INIT +}; + +/* make sure that empty folders are not traversed */ +void test_dirent__dont_traverse_empty_folders(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &empty); + setup(&empty); + + cl_git_pass(git_fs_path_direach(&empty.path, 0, one_entry, &empty)); + + check_counts(&empty); + + /* make sure callback not called */ + cl_assert(git_fs_path_is_empty_dir(empty.path.ptr)); +} + +static name_data odd_names[] = { + { 0, "odd/.a" }, + { 0, "odd/..c" }, + /* the following don't work on cygwin/win32 */ + /* { 0, "odd/.b." }, */ + /* { 0, "odd/..d.." }, */ + { 0, NULL } +}; +static walk_data odd = { + "odd", + odd_names, + GIT_STR_INIT +}; + +/* make sure that strange looking filenames ('..c') are traversed */ +void test_dirent__traverse_weird_filenames(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &odd); + setup(&odd); + + cl_git_pass(git_fs_path_direach(&odd.path, 0, one_entry, &odd)); + + check_counts(&odd); +} + +/* test filename length limits */ +void test_dirent__length_limits(void) +{ + char *big_filename = (char *)git__malloc(FILENAME_MAX + 1); + memset(big_filename, 'a', FILENAME_MAX + 1); + big_filename[FILENAME_MAX] = 0; + + cl_must_fail(p_creat(big_filename, 0666)); + + git__free(big_filename); +} + +void test_dirent__empty_dir(void) +{ + cl_must_pass(p_mkdir("empty_dir", 0777)); + cl_assert(git_fs_path_is_empty_dir("empty_dir")); + + cl_git_mkfile("empty_dir/content", "whatever\n"); + cl_assert(!git_fs_path_is_empty_dir("empty_dir")); + cl_assert(!git_fs_path_is_empty_dir("empty_dir/content")); + + cl_must_pass(p_unlink("empty_dir/content")); + + cl_must_pass(p_mkdir("empty_dir/content", 0777)); + cl_assert(!git_fs_path_is_empty_dir("empty_dir")); + cl_assert(git_fs_path_is_empty_dir("empty_dir/content")); + + cl_must_pass(p_rmdir("empty_dir/content")); + + cl_must_pass(p_rmdir("empty_dir")); +} + +static void handle_next(git_fs_path_diriter *diriter, walk_data *walk) +{ + const char *fullpath, *filename; + size_t fullpath_len, filename_len; + + cl_git_pass(git_fs_path_diriter_fullpath(&fullpath, &fullpath_len, diriter)); + cl_git_pass(git_fs_path_diriter_filename(&filename, &filename_len, diriter)); + + cl_assert_equal_strn(fullpath, "sub/", 4); + cl_assert_equal_s(fullpath+4, filename); + + update_count(walk->names, fullpath); +} + +/* test directory iterator */ +void test_dirent__diriter_with_fullname(void) +{ + git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT; + int error; + + cl_set_cleanup(&dirent_cleanup__cb, &sub); + setup(&sub); + + cl_git_pass(git_fs_path_diriter_init(&diriter, sub.path.ptr, 0)); + + while ((error = git_fs_path_diriter_next(&diriter)) == 0) + handle_next(&diriter, &sub); + + cl_assert_equal_i(error, GIT_ITEROVER); + + git_fs_path_diriter_free(&diriter); + + check_counts(&sub); +} + +void test_dirent__diriter_at_directory_root(void) +{ + git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT; + const char *sandbox_path, *path; + char *root_path; + size_t path_len; + int root_offset, error; + + sandbox_path = clar_sandbox_path(); + cl_assert((root_offset = git_fs_path_root(sandbox_path)) >= 0); + + cl_assert(root_path = git__calloc(1, root_offset + 2)); + strncpy(root_path, sandbox_path, root_offset + 1); + + cl_git_pass(git_fs_path_diriter_init(&diriter, root_path, 0)); + + while ((error = git_fs_path_diriter_next(&diriter)) == 0) { + cl_git_pass(git_fs_path_diriter_fullpath(&path, &path_len, &diriter)); + + cl_assert(path_len > (size_t)(root_offset + 1)); + cl_assert(path[root_offset+1] != '/'); + } + + cl_assert_equal_i(error, GIT_ITEROVER); + + git_fs_path_diriter_free(&diriter); + git__free(root_path); +} diff --git a/tests/util/encoding.c b/tests/util/encoding.c new file mode 100644 index 000000000..a25a33443 --- /dev/null +++ b/tests/util/encoding.c @@ -0,0 +1,42 @@ +#include "clar_libgit2.h" +#include "varint.h" + +void test_encoding__decode(void) +{ + const unsigned char *buf = (unsigned char *)"AB"; + size_t size; + + cl_assert(git_decode_varint(buf, &size) == 65); + cl_assert(size == 1); + + buf = (unsigned char *)"\xfe\xdc\xbaXY"; + cl_assert(git_decode_varint(buf, &size) == 267869656); + cl_assert(size == 4); + + buf = (unsigned char *)"\xaa\xaa\xfe\xdc\xbaXY"; + cl_assert(git_decode_varint(buf, &size) == UINT64_C(1489279344088)); + cl_assert(size == 6); + + buf = (unsigned char *)"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xfe\xdc\xbaXY"; + cl_assert(git_decode_varint(buf, &size) == 0); + cl_assert(size == 0); + +} + +void test_encoding__encode(void) +{ + unsigned char buf[100]; + cl_assert(git_encode_varint(buf, 100, 65) == 1); + cl_assert(buf[0] == 'A'); + + cl_assert(git_encode_varint(buf, 1, 1) == 1); + cl_assert(!memcmp(buf, "\x01", 1)); + + cl_assert(git_encode_varint(buf, 100, 267869656) == 4); + cl_assert(!memcmp(buf, "\xfe\xdc\xbaX", 4)); + + cl_assert(git_encode_varint(buf, 100, UINT64_C(1489279344088)) == 6); + cl_assert(!memcmp(buf, "\xaa\xaa\xfe\xdc\xbaX", 6)); + + cl_assert(git_encode_varint(buf, 1, UINT64_C(1489279344088)) == -1); +} diff --git a/tests/util/errors.c b/tests/util/errors.c new file mode 100644 index 000000000..78654a753 --- /dev/null +++ b/tests/util/errors.c @@ -0,0 +1,222 @@ +#include "clar_libgit2.h" + +void test_errors__public_api(void) +{ + char *str_in_error; + + git_error_clear(); + cl_assert(git_error_last() == NULL); + + git_error_set_oom(); + + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); + str_in_error = strstr(git_error_last()->message, "memory"); + cl_assert(str_in_error != NULL); + + git_error_clear(); + + git_error_set_str(GIT_ERROR_REPOSITORY, "This is a test"); + + cl_assert(git_error_last() != NULL); + str_in_error = strstr(git_error_last()->message, "This is a test"); + cl_assert(str_in_error != NULL); + + git_error_clear(); + cl_assert(git_error_last() == NULL); +} + +#include "common.h" +#include "util.h" +#include "posix.h" + +void test_errors__new_school(void) +{ + char *str_in_error; + + git_error_clear(); + cl_assert(git_error_last() == NULL); + + git_error_set_oom(); /* internal fn */ + + cl_assert(git_error_last() != NULL); + cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); + str_in_error = strstr(git_error_last()->message, "memory"); + cl_assert(str_in_error != NULL); + + git_error_clear(); + + git_error_set(GIT_ERROR_REPOSITORY, "This is a test"); /* internal fn */ + + cl_assert(git_error_last() != NULL); + str_in_error = strstr(git_error_last()->message, "This is a test"); + cl_assert(str_in_error != NULL); + + git_error_clear(); + cl_assert(git_error_last() == NULL); + + do { + struct stat st; + memset(&st, 0, sizeof(st)); + cl_assert(p_lstat("this_file_does_not_exist", &st) < 0); + GIT_UNUSED(st); + } while (false); + git_error_set(GIT_ERROR_OS, "stat failed"); /* internal fn */ + + cl_assert(git_error_last() != NULL); + str_in_error = strstr(git_error_last()->message, "stat failed"); + cl_assert(str_in_error != NULL); + cl_assert(git__prefixcmp(str_in_error, "stat failed: ") == 0); + cl_assert(strlen(str_in_error) > strlen("stat failed: ")); + +#ifdef GIT_WIN32 + git_error_clear(); + + /* The MSDN docs use this to generate a sample error */ + cl_assert(GetProcessId(NULL) == 0); + git_error_set(GIT_ERROR_OS, "GetProcessId failed"); /* internal fn */ + + cl_assert(git_error_last() != NULL); + str_in_error = strstr(git_error_last()->message, "GetProcessId failed"); + cl_assert(str_in_error != NULL); + cl_assert(git__prefixcmp(str_in_error, "GetProcessId failed: ") == 0); + cl_assert(strlen(str_in_error) > strlen("GetProcessId failed: ")); +#endif + + git_error_clear(); +} + +void test_errors__restore(void) +{ + git_error_state err_state = {0}; + + git_error_clear(); + cl_assert(git_error_last() == NULL); + + cl_assert_equal_i(0, git_error_state_capture(&err_state, 0)); + + memset(&err_state, 0x0, sizeof(git_error_state)); + + git_error_set(42, "Foo: %s", "bar"); + cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); + + cl_assert(git_error_last() == NULL); + + git_error_set(99, "Bar: %s", "foo"); + + git_error_state_restore(&err_state); + + cl_assert_equal_i(42, git_error_last()->klass); + cl_assert_equal_s("Foo: bar", git_error_last()->message); +} + +void test_errors__free_state(void) +{ + git_error_state err_state = {0}; + + git_error_clear(); + + git_error_set(42, "Foo: %s", "bar"); + cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); + + git_error_set(99, "Bar: %s", "foo"); + + git_error_state_free(&err_state); + + cl_assert_equal_i(99, git_error_last()->klass); + cl_assert_equal_s("Bar: foo", git_error_last()->message); + + git_error_state_restore(&err_state); + + cl_assert(git_error_last() == NULL); +} + +void test_errors__restore_oom(void) +{ + git_error_state err_state = {0}; + const git_error *oom_error = NULL; + + git_error_clear(); + + git_error_set_oom(); /* internal fn */ + oom_error = git_error_last(); + cl_assert(oom_error); + + cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1)); + + cl_assert(git_error_last() == NULL); + cl_assert_equal_i(GIT_ERROR_NOMEMORY, err_state.error_msg.klass); + cl_assert_equal_s("Out of memory", err_state.error_msg.message); + + git_error_state_restore(&err_state); + + cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY); + cl_assert_(git_error_last() == oom_error, "static oom error not restored"); + + git_error_clear(); +} + +static int test_arraysize_multiply(size_t nelem, size_t size) +{ + size_t out; + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&out, nelem, size); + return 0; +} + +void test_errors__integer_overflow_alloc_multiply(void) +{ + cl_git_pass(test_arraysize_multiply(10, 10)); + cl_git_pass(test_arraysize_multiply(1000, 1000)); + cl_git_pass(test_arraysize_multiply(SIZE_MAX/sizeof(void *), sizeof(void *))); + cl_git_pass(test_arraysize_multiply(0, 10)); + cl_git_pass(test_arraysize_multiply(10, 0)); + + cl_git_fail(test_arraysize_multiply(SIZE_MAX-1, sizeof(void *))); + cl_git_fail(test_arraysize_multiply((SIZE_MAX/sizeof(void *))+1, sizeof(void *))); + + cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); + cl_assert_equal_s("Out of memory", git_error_last()->message); +} + +static int test_arraysize_add(size_t one, size_t two) +{ + size_t out; + GIT_ERROR_CHECK_ALLOC_ADD(&out, one, two); + return 0; +} + +void test_errors__integer_overflow_alloc_add(void) +{ + cl_git_pass(test_arraysize_add(10, 10)); + cl_git_pass(test_arraysize_add(1000, 1000)); + cl_git_pass(test_arraysize_add(SIZE_MAX-10, 10)); + + cl_git_fail(test_arraysize_multiply(SIZE_MAX-1, 2)); + cl_git_fail(test_arraysize_multiply(SIZE_MAX, SIZE_MAX)); + + cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); + cl_assert_equal_s("Out of memory", git_error_last()->message); +} + +void test_errors__integer_overflow_sets_oom(void) +{ + size_t out; + + git_error_clear(); + cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX-1, 1)); + cl_assert_equal_p(NULL, git_error_last()); + + git_error_clear(); + cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, 42, 69)); + cl_assert_equal_p(NULL, git_error_last()); + + git_error_clear(); + cl_assert(GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX, SIZE_MAX)); + cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); + cl_assert_equal_s("Out of memory", git_error_last()->message); + + git_error_clear(); + cl_assert(GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX, SIZE_MAX)); + cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass); + cl_assert_equal_s("Out of memory", git_error_last()->message); +} diff --git a/tests/util/filebuf.c b/tests/util/filebuf.c new file mode 100644 index 000000000..29b821144 --- /dev/null +++ b/tests/util/filebuf.c @@ -0,0 +1,267 @@ +#include "clar_libgit2.h" +#include "filebuf.h" + +/* make sure git_filebuf_open doesn't delete an existing lock */ +void test_filebuf__0(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + int fd; + char test[] = "test", testlock[] = "test.lock"; + + fd = p_creat(testlock, 0744); /* -V536 */ + + cl_must_pass(fd); + cl_must_pass(p_close(fd)); + + cl_git_fail(git_filebuf_open(&file, test, 0, 0666)); + cl_assert(git_fs_path_exists(testlock)); + + cl_must_pass(p_unlink(testlock)); +} + + +/* make sure GIT_FILEBUF_APPEND works as expected */ +void test_filebuf__1(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + + cl_git_mkfile(test, "libgit2 rocks\n"); + + cl_git_pass(git_filebuf_open(&file, test, GIT_FILEBUF_APPEND, 0666)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + cl_git_pass(git_filebuf_commit(&file)); + + cl_assert_equal_file("libgit2 rocks\nlibgit2 rocks\n", 0, test); + + cl_must_pass(p_unlink(test)); +} + + +/* make sure git_filebuf_write writes large buffer correctly */ +void test_filebuf__2(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + unsigned char buf[4096 * 4]; /* 2 * WRITE_BUFFER_SIZE */ + + memset(buf, 0xfe, sizeof(buf)); + + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + cl_git_pass(git_filebuf_write(&file, buf, sizeof(buf))); + cl_git_pass(git_filebuf_commit(&file)); + + cl_assert_equal_file((char *)buf, sizeof(buf), test); + + cl_must_pass(p_unlink(test)); +} + +/* make sure git_filebuf_cleanup clears the buffer */ +void test_filebuf__4(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + + cl_assert(file.buffer == NULL); + + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + cl_assert(file.buffer != NULL); + + git_filebuf_cleanup(&file); + cl_assert(file.buffer == NULL); +} + + +/* make sure git_filebuf_commit clears the buffer */ +void test_filebuf__5(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + + cl_assert(file.buffer == NULL); + + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + cl_assert(file.buffer != NULL); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + cl_assert(file.buffer != NULL); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert(file.buffer == NULL); + + cl_must_pass(p_unlink(test)); +} + + +/* make sure git_filebuf_commit takes umask into account */ +void test_filebuf__umask(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char test[] = "test"; + struct stat statbuf; + mode_t mask, os_mask; + +#ifdef GIT_WIN32 + os_mask = 0600; +#else + os_mask = 0777; +#endif + + p_umask(mask = p_umask(0)); + + cl_assert(file.buffer == NULL); + + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + cl_assert(file.buffer != NULL); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + cl_assert(file.buffer != NULL); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert(file.buffer == NULL); + + cl_must_pass(p_stat("test", &statbuf)); + cl_assert_equal_i(statbuf.st_mode & os_mask, (0666 & ~mask) & os_mask); + + cl_must_pass(p_unlink(test)); +} + +void test_filebuf__rename_error(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + char *dir = "subdir", *test = "subdir/test", *test_lock = "subdir/test.lock"; + int fd; + +#ifndef GIT_WIN32 + cl_skip(); +#endif + + cl_git_pass(p_mkdir(dir, 0666)); + cl_git_mkfile(test, "dummy content"); + fd = p_open(test, O_RDONLY); + cl_assert(fd > 0); + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_assert_equal_i(true, git_fs_path_exists(test_lock)); + + cl_git_fail(git_filebuf_commit(&file)); + p_close(fd); + + git_filebuf_cleanup(&file); + + cl_assert_equal_i(false, git_fs_path_exists(test_lock)); +} + +void test_filebuf__symlink_follow(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + const char *dir = "linkdir", *source = "linkdir/link"; + + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + cl_skip(); + + cl_git_pass(p_mkdir(dir, 0777)); + cl_git_pass(p_symlink("target", source)); + + cl_git_pass(git_filebuf_open(&file, source, 0, 0666)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock")); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert_equal_i(true, git_fs_path_exists("linkdir/target")); + + git_filebuf_cleanup(&file); + + /* The second time around, the target file does exist */ + cl_git_pass(git_filebuf_open(&file, source, 0, 0666)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock")); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert_equal_i(true, git_fs_path_exists("linkdir/target")); + + git_filebuf_cleanup(&file); + cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_filebuf__symlink_follow_absolute_paths(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_str source = GIT_STR_INIT, target = GIT_STR_INIT; + + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + cl_skip(); + + cl_git_pass(git_str_joinpath(&source, clar_sandbox_path(), "linkdir/link")); + cl_git_pass(git_str_joinpath(&target, clar_sandbox_path(), "linkdir/target")); + cl_git_pass(p_mkdir("linkdir", 0777)); + cl_git_pass(p_symlink(target.ptr, source.ptr)); + + cl_git_pass(git_filebuf_open(&file, source.ptr, 0, 0666)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock")); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert_equal_i(true, git_fs_path_exists("linkdir/target")); + + git_filebuf_cleanup(&file); + git_str_dispose(&source); + git_str_dispose(&target); + + cl_git_pass(git_futils_rmdir_r("linkdir", NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_filebuf__symlink_depth(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + const char *dir = "linkdir", *source = "linkdir/link"; + + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + cl_skip(); + + cl_git_pass(p_mkdir(dir, 0777)); + /* Endless loop */ + cl_git_pass(p_symlink("link", source)); + + cl_git_fail(git_filebuf_open(&file, source, 0, 0666)); + + cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_filebuf__hidden_file(void) +{ +#ifndef GIT_WIN32 + cl_skip(); +#else + git_filebuf file = GIT_FILEBUF_INIT; + char *dir = "hidden", *test = "hidden/test"; + bool hidden; + + cl_git_pass(p_mkdir(dir, 0666)); + cl_git_mkfile(test, "dummy content"); + + cl_git_pass(git_win32__set_hidden(test, true)); + cl_git_pass(git_win32__hidden(&hidden, test)); + cl_assert(hidden); + + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_git_pass(git_filebuf_commit(&file)); + + git_filebuf_cleanup(&file); +#endif +} + +void test_filebuf__detects_directory(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + + cl_must_pass(p_mkdir("foo", 0777)); + cl_git_fail_with(GIT_EDIRECTORY, git_filebuf_open(&file, "foo", 0, 0666)); + cl_must_pass(p_rmdir("foo")); +} diff --git a/tests/util/ftruncate.c b/tests/util/ftruncate.c new file mode 100644 index 000000000..e60e5d96d --- /dev/null +++ b/tests/util/ftruncate.c @@ -0,0 +1,48 @@ +/** + * Some tests for p_ftruncate() to ensure that + * properly handles large (2Gb+) files. + */ + +#include "clar_libgit2.h" + +static const char *filename = "core_ftruncate.txt"; +static int fd = -1; + +void test_ftruncate__initialize(void) +{ + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE")) + cl_skip(); + + cl_must_pass((fd = p_open(filename, O_CREAT | O_RDWR, 0644))); +} + +void test_ftruncate__cleanup(void) +{ + if (fd < 0) + return; + + p_close(fd); + fd = 0; + + p_unlink(filename); +} + +static void _extend(off64_t i64len) +{ + struct stat st; + int error; + + cl_assert((error = p_ftruncate(fd, i64len)) == 0); + cl_assert((error = p_fstat(fd, &st)) == 0); + cl_assert(st.st_size == i64len); +} + +void test_ftruncate__2gb(void) +{ + _extend(0x80000001); +} + +void test_ftruncate__4gb(void) +{ + _extend(0x100000001); +} diff --git a/tests/util/futils.c b/tests/util/futils.c new file mode 100644 index 000000000..993b14d9b --- /dev/null +++ b/tests/util/futils.c @@ -0,0 +1,115 @@ +#include "clar_libgit2.h" +#include "futils.h" + +/* Fixture setup and teardown */ +void test_futils__initialize(void) +{ + cl_must_pass(p_mkdir("futils", 0777)); +} + +void test_futils__cleanup(void) +{ + cl_fixture_cleanup("futils"); +} + +void test_futils__writebuffer(void) +{ + git_str out = GIT_STR_INIT, + append = GIT_STR_INIT; + + /* create a new file */ + git_str_puts(&out, "hello!\n"); + git_str_printf(&out, "this is a %s\n", "test"); + + cl_git_pass(git_futils_writebuffer(&out, "futils/test-file", O_RDWR|O_CREAT, 0666)); + + cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); + + /* append some more data */ + git_str_puts(&append, "And some more!\n"); + git_str_put(&out, append.ptr, append.size); + + cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR|O_APPEND, 0666)); + + cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); + + git_str_dispose(&out); + git_str_dispose(&append); +} + +void test_futils__write_hidden_file(void) +{ +#ifndef GIT_WIN32 + cl_skip(); +#else + git_str out = GIT_STR_INIT, append = GIT_STR_INIT; + bool hidden; + + git_str_puts(&out, "hidden file.\n"); + git_futils_writebuffer(&out, "futils/test-file", O_RDWR | O_CREAT, 0666); + + cl_git_pass(git_win32__set_hidden("futils/test-file", true)); + + /* append some more data */ + git_str_puts(&append, "And some more!\n"); + git_str_put(&out, append.ptr, append.size); + + cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR | O_APPEND, 0666)); + + cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); + + cl_git_pass(git_win32__hidden(&hidden, "futils/test-file")); + cl_assert(hidden); + + git_str_dispose(&out); + git_str_dispose(&append); +#endif +} + +void test_futils__recursive_rmdir_keeps_symlink_targets(void) +{ + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + cl_skip(); + + cl_git_pass(git_futils_mkdir_r("a/b", 0777)); + cl_git_pass(git_futils_mkdir_r("dir-target", 0777)); + cl_git_mkfile("dir-target/file", "Contents"); + cl_git_mkfile("file-target", "Contents"); + cl_must_pass(p_symlink("dir-target", "a/symlink")); + cl_must_pass(p_symlink("file-target", "a/b/symlink")); + + cl_git_pass(git_futils_rmdir_r("a", NULL, GIT_RMDIR_REMOVE_FILES)); + + cl_assert(git_fs_path_exists("dir-target")); + cl_assert(git_fs_path_exists("file-target")); + + cl_must_pass(p_unlink("dir-target/file")); + cl_must_pass(p_rmdir("dir-target")); + cl_must_pass(p_unlink("file-target")); +} + +void test_futils__mktmp_umask(void) +{ +#ifdef GIT_WIN32 + cl_skip(); +#else + git_str path = GIT_STR_INIT; + struct stat st; + int fd; + + umask(0); + cl_assert((fd = git_futils_mktmp(&path, "foo", 0777)) >= 0); + cl_must_pass(p_fstat(fd, &st)); + cl_assert_equal_i(st.st_mode & 0777, 0777); + cl_must_pass(p_unlink(path.ptr)); + close(fd); + + umask(077); + cl_assert((fd = git_futils_mktmp(&path, "foo", 0777)) >= 0); + cl_must_pass(p_fstat(fd, &st)); + cl_assert_equal_i(st.st_mode & 0777, 0700); + cl_must_pass(p_unlink(path.ptr)); + close(fd); + git_str_dispose(&path); +#endif +} diff --git a/tests/util/gitstr.c b/tests/util/gitstr.c new file mode 100644 index 000000000..aea35565b --- /dev/null +++ b/tests/util/gitstr.c @@ -0,0 +1,1044 @@ +#include "clar_libgit2.h" +#include "futils.h" + +#define TESTSTR "Have you seen that? Have you seeeen that??" +const char *test_string = TESTSTR; +const char *test_string_x2 = TESTSTR TESTSTR; + +#define TESTSTR_4096 REP1024("1234") +#define TESTSTR_8192 REP1024("12341234") +const char *test_4096 = TESTSTR_4096; +const char *test_8192 = TESTSTR_8192; + +/* test basic data concatenation */ +void test_gitstr__0(void) +{ + git_str buf = GIT_STR_INIT; + + cl_assert(buf.size == 0); + + git_str_puts(&buf, test_string); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_string, git_str_cstr(&buf)); + + git_str_puts(&buf, test_string); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_string_x2, git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +/* test git_str_printf */ +void test_gitstr__1(void) +{ + git_str buf = GIT_STR_INIT; + + git_str_printf(&buf, "%s %s %d ", "shoop", "da", 23); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s("shoop da 23 ", git_str_cstr(&buf)); + + git_str_printf(&buf, "%s %d", "woop", 42); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s("shoop da 23 woop 42", git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +/* more thorough test of concatenation options */ +void test_gitstr__2(void) +{ + git_str buf = GIT_STR_INIT; + int i; + char data[128]; + + cl_assert(buf.size == 0); + + /* this must be safe to do */ + git_str_dispose(&buf); + cl_assert(buf.size == 0); + cl_assert(buf.asize == 0); + + /* empty buffer should be empty string */ + cl_assert_equal_s("", git_str_cstr(&buf)); + cl_assert(buf.size == 0); + /* cl_assert(buf.asize == 0); -- should not assume what git_str does */ + + /* free should set us back to the beginning */ + git_str_dispose(&buf); + cl_assert(buf.size == 0); + cl_assert(buf.asize == 0); + + /* add letter */ + git_str_putc(&buf, '+'); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s("+", git_str_cstr(&buf)); + + /* add letter again */ + git_str_putc(&buf, '+'); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s("++", git_str_cstr(&buf)); + + /* let's try that a few times */ + for (i = 0; i < 16; ++i) { + git_str_putc(&buf, '+'); + cl_assert(git_str_oom(&buf) == 0); + } + cl_assert_equal_s("++++++++++++++++++", git_str_cstr(&buf)); + + git_str_dispose(&buf); + + /* add data */ + git_str_put(&buf, "xo", 2); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s("xo", git_str_cstr(&buf)); + + /* add letter again */ + git_str_put(&buf, "xo", 2); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s("xoxo", git_str_cstr(&buf)); + + /* let's try that a few times */ + for (i = 0; i < 16; ++i) { + git_str_put(&buf, "xo", 2); + cl_assert(git_str_oom(&buf) == 0); + } + cl_assert_equal_s("xoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxo", + git_str_cstr(&buf)); + + git_str_dispose(&buf); + + /* set to string */ + git_str_sets(&buf, test_string); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_string, git_str_cstr(&buf)); + + /* append string */ + git_str_puts(&buf, test_string); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_string_x2, git_str_cstr(&buf)); + + /* set to string again (should overwrite - not append) */ + git_str_sets(&buf, test_string); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_string, git_str_cstr(&buf)); + + /* test clear */ + git_str_clear(&buf); + cl_assert_equal_s("", git_str_cstr(&buf)); + + git_str_dispose(&buf); + + /* test extracting data into buffer */ + git_str_puts(&buf, REP4("0123456789")); + cl_assert(git_str_oom(&buf) == 0); + + git_str_copy_cstr(data, sizeof(data), &buf); + cl_assert_equal_s(REP4("0123456789"), data); + git_str_copy_cstr(data, 11, &buf); + cl_assert_equal_s("0123456789", data); + git_str_copy_cstr(data, 3, &buf); + cl_assert_equal_s("01", data); + git_str_copy_cstr(data, 1, &buf); + cl_assert_equal_s("", data); + + git_str_copy_cstr(data, sizeof(data), &buf); + cl_assert_equal_s(REP4("0123456789"), data); + + git_str_sets(&buf, REP256("x")); + git_str_copy_cstr(data, sizeof(data), &buf); + /* since sizeof(data) == 128, only 127 bytes should be copied */ + cl_assert_equal_s(REP4(REP16("x")) REP16("x") REP16("x") + REP16("x") "xxxxxxxxxxxxxxx", data); + + git_str_dispose(&buf); + + git_str_copy_cstr(data, sizeof(data), &buf); + cl_assert_equal_s("", data); +} + +/* let's do some tests with larger buffers to push our limits */ +void test_gitstr__3(void) +{ + git_str buf = GIT_STR_INIT; + + /* set to string */ + git_str_set(&buf, test_4096, 4096); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_4096, git_str_cstr(&buf)); + + /* append string */ + git_str_puts(&buf, test_4096); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_8192, git_str_cstr(&buf)); + + /* set to string again (should overwrite - not append) */ + git_str_set(&buf, test_4096, 4096); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(test_4096, git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +/* let's try some producer/consumer tests */ +void test_gitstr__4(void) +{ + git_str buf = GIT_STR_INIT; + int i; + + for (i = 0; i < 10; ++i) { + git_str_puts(&buf, "1234"); /* add 4 */ + cl_assert(git_str_oom(&buf) == 0); + git_str_consume(&buf, buf.ptr + 2); /* eat the first two */ + cl_assert(strlen(git_str_cstr(&buf)) == (size_t)((i + 1) * 2)); + } + /* we have appended 1234 10x and removed the first 20 letters */ + cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); + + git_str_consume(&buf, NULL); + cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); + + git_str_consume(&buf, "invalid pointer"); + cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); + + git_str_consume(&buf, buf.ptr); + cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf)); + + git_str_consume(&buf, buf.ptr + 1); + cl_assert_equal_s("2341234123412341234", git_str_cstr(&buf)); + + git_str_consume(&buf, buf.ptr + buf.size); + cl_assert_equal_s("", git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + + +static void +check_buf_append( + const char* data_a, + const char* data_b, + const char* expected_data, + size_t expected_size, + size_t expected_asize) +{ + git_str tgt = GIT_STR_INIT; + + git_str_sets(&tgt, data_a); + cl_assert(git_str_oom(&tgt) == 0); + git_str_puts(&tgt, data_b); + cl_assert(git_str_oom(&tgt) == 0); + cl_assert_equal_s(expected_data, git_str_cstr(&tgt)); + cl_assert_equal_i(tgt.size, expected_size); + if (expected_asize > 0) + cl_assert_equal_i(tgt.asize, expected_asize); + + git_str_dispose(&tgt); +} + +static void +check_buf_append_abc( + const char* buf_a, + const char* buf_b, + const char* buf_c, + const char* expected_ab, + const char* expected_abc, + const char* expected_abca, + const char* expected_abcab, + const char* expected_abcabc) +{ + git_str buf = GIT_STR_INIT; + + git_str_sets(&buf, buf_a); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(buf_a, git_str_cstr(&buf)); + + git_str_puts(&buf, buf_b); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected_ab, git_str_cstr(&buf)); + + git_str_puts(&buf, buf_c); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected_abc, git_str_cstr(&buf)); + + git_str_puts(&buf, buf_a); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected_abca, git_str_cstr(&buf)); + + git_str_puts(&buf, buf_b); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected_abcab, git_str_cstr(&buf)); + + git_str_puts(&buf, buf_c); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected_abcabc, git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +/* more variations on append tests */ +void test_gitstr__5(void) +{ + check_buf_append("", "", "", 0, 0); + check_buf_append("a", "", "a", 1, 0); + check_buf_append("", "a", "a", 1, 8); + check_buf_append("", "a", "a", 1, 8); + check_buf_append("a", "b", "ab", 2, 8); + check_buf_append("", "abcdefgh", "abcdefgh", 8, 16); + check_buf_append("abcdefgh", "", "abcdefgh", 8, 16); + + /* buffer with starting asize will grow to: + * 1 -> 2, 2 -> 3, 3 -> 5, 4 -> 6, 5 -> 8, 6 -> 9, + * 7 -> 11, 8 -> 12, 9 -> 14, 10 -> 15, 11 -> 17, 12 -> 18, + * 13 -> 20, 14 -> 21, 15 -> 23, 16 -> 24, 17 -> 26, 18 -> 27, + * 19 -> 29, 20 -> 30, 21 -> 32, 22 -> 33, 23 -> 35, 24 -> 36, + * ... + * follow sequence until value > target size, + * then round up to nearest multiple of 8. + */ + + check_buf_append("abcdefgh", "/", "abcdefgh/", 9, 16); + check_buf_append("abcdefgh", "ijklmno", "abcdefghijklmno", 15, 16); + check_buf_append("abcdefgh", "ijklmnop", "abcdefghijklmnop", 16, 24); + check_buf_append("0123456789", "0123456789", + "01234567890123456789", 20, 24); + check_buf_append(REP16("x"), REP16("o"), + REP16("x") REP16("o"), 32, 40); + + check_buf_append(test_4096, "", test_4096, 4096, 4104); + check_buf_append(test_4096, test_4096, test_8192, 8192, 8200); + + /* check sequences of appends */ + check_buf_append_abc("a", "b", "c", + "ab", "abc", "abca", "abcab", "abcabc"); + check_buf_append_abc("a1", "b2", "c3", + "a1b2", "a1b2c3", "a1b2c3a1", + "a1b2c3a1b2", "a1b2c3a1b2c3"); + check_buf_append_abc("a1/", "b2/", "c3/", + "a1/b2/", "a1/b2/c3/", "a1/b2/c3/a1/", + "a1/b2/c3/a1/b2/", "a1/b2/c3/a1/b2/c3/"); +} + +/* test swap */ +void test_gitstr__6(void) +{ + git_str a = GIT_STR_INIT; + git_str b = GIT_STR_INIT; + + git_str_sets(&a, "foo"); + cl_assert(git_str_oom(&a) == 0); + git_str_sets(&b, "bar"); + cl_assert(git_str_oom(&b) == 0); + + cl_assert_equal_s("foo", git_str_cstr(&a)); + cl_assert_equal_s("bar", git_str_cstr(&b)); + + git_str_swap(&a, &b); + + cl_assert_equal_s("bar", git_str_cstr(&a)); + cl_assert_equal_s("foo", git_str_cstr(&b)); + + git_str_dispose(&a); + git_str_dispose(&b); +} + + +/* test detach/attach data */ +void test_gitstr__7(void) +{ + const char *fun = "This is fun"; + git_str a = GIT_STR_INIT; + char *b = NULL; + + git_str_sets(&a, "foo"); + cl_assert(git_str_oom(&a) == 0); + cl_assert_equal_s("foo", git_str_cstr(&a)); + + b = git_str_detach(&a); + + cl_assert_equal_s("foo", b); + cl_assert_equal_s("", a.ptr); + git__free(b); + + b = git_str_detach(&a); + + cl_assert_equal_s(NULL, b); + cl_assert_equal_s("", a.ptr); + + git_str_dispose(&a); + + b = git__strdup(fun); + git_str_attach(&a, b, 0); + + cl_assert_equal_s(fun, a.ptr); + cl_assert(a.size == strlen(fun)); + cl_assert(a.asize == strlen(fun) + 1); + + git_str_dispose(&a); + + b = git__strdup(fun); + git_str_attach(&a, b, strlen(fun) + 1); + + cl_assert_equal_s(fun, a.ptr); + cl_assert(a.size == strlen(fun)); + cl_assert(a.asize == strlen(fun) + 1); + + git_str_dispose(&a); +} + + +static void +check_joinbuf_2( + const char *a, + const char *b, + const char *expected) +{ + char sep = '/'; + git_str buf = GIT_STR_INIT; + + git_str_join(&buf, sep, a, b); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected, git_str_cstr(&buf)); + git_str_dispose(&buf); +} + +static void +check_joinbuf_overlapped( + const char *oldval, + int ofs_a, + const char *b, + const char *expected) +{ + char sep = '/'; + git_str buf = GIT_STR_INIT; + + git_str_sets(&buf, oldval); + git_str_join(&buf, sep, buf.ptr + ofs_a, b); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected, git_str_cstr(&buf)); + git_str_dispose(&buf); +} + +static void +check_joinbuf_n_2( + const char *a, + const char *b, + const char *expected) +{ + char sep = '/'; + git_str buf = GIT_STR_INIT; + + git_str_sets(&buf, a); + cl_assert(git_str_oom(&buf) == 0); + + git_str_join_n(&buf, sep, 1, b); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected, git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +static void +check_joinbuf_n_4( + const char *a, + const char *b, + const char *c, + const char *d, + const char *expected) +{ + char sep = ';'; + git_str buf = GIT_STR_INIT; + git_str_join_n(&buf, sep, 4, a, b, c, d); + cl_assert(git_str_oom(&buf) == 0); + cl_assert_equal_s(expected, git_str_cstr(&buf)); + git_str_dispose(&buf); +} + +/* test join */ +void test_gitstr__8(void) +{ + git_str a = GIT_STR_INIT; + + git_str_join_n(&a, '/', 1, "foo"); + cl_assert(git_str_oom(&a) == 0); + cl_assert_equal_s("foo", git_str_cstr(&a)); + + git_str_join_n(&a, '/', 1, "bar"); + cl_assert(git_str_oom(&a) == 0); + cl_assert_equal_s("foo/bar", git_str_cstr(&a)); + + git_str_join_n(&a, '/', 1, "baz"); + cl_assert(git_str_oom(&a) == 0); + cl_assert_equal_s("foo/bar/baz", git_str_cstr(&a)); + + git_str_dispose(&a); + + check_joinbuf_2(NULL, "", ""); + check_joinbuf_2(NULL, "a", "a"); + check_joinbuf_2(NULL, "/a", "/a"); + check_joinbuf_2("", "", ""); + check_joinbuf_2("", "a", "a"); + check_joinbuf_2("", "/a", "/a"); + check_joinbuf_2("a", "", "a/"); + check_joinbuf_2("a", "/", "a/"); + check_joinbuf_2("a", "b", "a/b"); + check_joinbuf_2("/", "a", "/a"); + check_joinbuf_2("/", "", "/"); + check_joinbuf_2("/a", "/b", "/a/b"); + check_joinbuf_2("/a", "/b/", "/a/b/"); + check_joinbuf_2("/a/", "b/", "/a/b/"); + check_joinbuf_2("/a/", "/b/", "/a/b/"); + check_joinbuf_2("/a/", "//b/", "/a/b/"); + check_joinbuf_2("/abcd", "/defg", "/abcd/defg"); + check_joinbuf_2("/abcd", "/defg/", "/abcd/defg/"); + check_joinbuf_2("/abcd/", "defg/", "/abcd/defg/"); + check_joinbuf_2("/abcd/", "/defg/", "/abcd/defg/"); + + check_joinbuf_overlapped("abcd", 0, "efg", "abcd/efg"); + check_joinbuf_overlapped("abcd", 1, "efg", "bcd/efg"); + check_joinbuf_overlapped("abcd", 2, "efg", "cd/efg"); + check_joinbuf_overlapped("abcd", 3, "efg", "d/efg"); + check_joinbuf_overlapped("abcd", 4, "efg", "efg"); + check_joinbuf_overlapped("abc/", 2, "efg", "c/efg"); + check_joinbuf_overlapped("abc/", 3, "efg", "/efg"); + check_joinbuf_overlapped("abc/", 4, "efg", "efg"); + check_joinbuf_overlapped("abcd", 3, "", "d/"); + check_joinbuf_overlapped("abcd", 4, "", ""); + check_joinbuf_overlapped("abc/", 2, "", "c/"); + check_joinbuf_overlapped("abc/", 3, "", "/"); + check_joinbuf_overlapped("abc/", 4, "", ""); + + check_joinbuf_n_2("", "", ""); + check_joinbuf_n_2("", "a", "a"); + check_joinbuf_n_2("", "/a", "/a"); + check_joinbuf_n_2("a", "", "a/"); + check_joinbuf_n_2("a", "/", "a/"); + check_joinbuf_n_2("a", "b", "a/b"); + check_joinbuf_n_2("/", "a", "/a"); + check_joinbuf_n_2("/", "", "/"); + check_joinbuf_n_2("/a", "/b", "/a/b"); + check_joinbuf_n_2("/a", "/b/", "/a/b/"); + check_joinbuf_n_2("/a/", "b/", "/a/b/"); + check_joinbuf_n_2("/a/", "/b/", "/a/b/"); + check_joinbuf_n_2("/abcd", "/defg", "/abcd/defg"); + check_joinbuf_n_2("/abcd", "/defg/", "/abcd/defg/"); + check_joinbuf_n_2("/abcd/", "defg/", "/abcd/defg/"); + check_joinbuf_n_2("/abcd/", "/defg/", "/abcd/defg/"); + + check_joinbuf_n_4("", "", "", "", ""); + check_joinbuf_n_4("", "a", "", "", "a;"); + check_joinbuf_n_4("a", "", "", "", "a;"); + check_joinbuf_n_4("", "", "", "a", "a"); + check_joinbuf_n_4("a", "b", "", ";c;d;", "a;b;c;d;"); + check_joinbuf_n_4("a", "b", "", ";c;d", "a;b;c;d"); + check_joinbuf_n_4("abcd", "efgh", "ijkl", "mnop", "abcd;efgh;ijkl;mnop"); + check_joinbuf_n_4("abcd;", "efgh;", "ijkl;", "mnop;", "abcd;efgh;ijkl;mnop;"); + check_joinbuf_n_4(";abcd;", ";efgh;", ";ijkl;", ";mnop;", ";abcd;efgh;ijkl;mnop;"); +} + +void test_gitstr__9(void) +{ + git_str buf = GIT_STR_INIT; + + /* just some exhaustive tests of various separator placement */ + char *a[] = { "", "-", "a-", "-a", "-a-" }; + char *b[] = { "", "-", "b-", "-b", "-b-" }; + char sep[] = { 0, '-', '/' }; + char *expect_null[] = { "", "-", "a-", "-a", "-a-", + "-", "--", "a--", "-a-", "-a--", + "b-", "-b-", "a-b-", "-ab-", "-a-b-", + "-b", "--b", "a--b", "-a-b", "-a--b", + "-b-", "--b-", "a--b-", "-a-b-", "-a--b-" }; + char *expect_dash[] = { "", "-", "a-", "-a-", "-a-", + "-", "-", "a-", "-a-", "-a-", + "b-", "-b-", "a-b-", "-a-b-", "-a-b-", + "-b", "-b", "a-b", "-a-b", "-a-b", + "-b-", "-b-", "a-b-", "-a-b-", "-a-b-" }; + char *expect_slas[] = { "", "-/", "a-/", "-a/", "-a-/", + "-", "-/-", "a-/-", "-a/-", "-a-/-", + "b-", "-/b-", "a-/b-", "-a/b-", "-a-/b-", + "-b", "-/-b", "a-/-b", "-a/-b", "-a-/-b", + "-b-", "-/-b-", "a-/-b-", "-a/-b-", "-a-/-b-" }; + char **expect_values[] = { expect_null, expect_dash, expect_slas }; + char separator, **expect; + unsigned int s, i, j; + + for (s = 0; s < sizeof(sep) / sizeof(char); ++s) { + separator = sep[s]; + expect = expect_values[s]; + + for (j = 0; j < sizeof(b) / sizeof(char*); ++j) { + for (i = 0; i < sizeof(a) / sizeof(char*); ++i) { + git_str_join(&buf, separator, a[i], b[j]); + cl_assert_equal_s(*expect, buf.ptr); + expect++; + } + } + } + + git_str_dispose(&buf); +} + +void test_gitstr__10(void) +{ + git_str a = GIT_STR_INIT; + + cl_git_pass(git_str_join_n(&a, '/', 1, "test")); + cl_assert_equal_s(a.ptr, "test"); + cl_git_pass(git_str_join_n(&a, '/', 1, "string")); + cl_assert_equal_s(a.ptr, "test/string"); + git_str_clear(&a); + cl_git_pass(git_str_join_n(&a, '/', 3, "test", "string", "join")); + cl_assert_equal_s(a.ptr, "test/string/join"); + cl_git_pass(git_str_join_n(&a, '/', 2, a.ptr, "more")); + cl_assert_equal_s(a.ptr, "test/string/join/test/string/join/more"); + + git_str_dispose(&a); +} + +void test_gitstr__join3(void) +{ + git_str a = GIT_STR_INIT; + + cl_git_pass(git_str_join3(&a, '/', "test", "string", "join")); + cl_assert_equal_s("test/string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "test/", "string", "join")); + cl_assert_equal_s("test/string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "test/", "/string", "join")); + cl_assert_equal_s("test/string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "test/", "/string/", "join")); + cl_assert_equal_s("test/string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "test/", "/string/", "/join")); + cl_assert_equal_s("test/string/join", a.ptr); + + cl_git_pass(git_str_join3(&a, '/', "", "string", "join")); + cl_assert_equal_s("string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "", "string/", "join")); + cl_assert_equal_s("string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "", "string/", "/join")); + cl_assert_equal_s("string/join", a.ptr); + + cl_git_pass(git_str_join3(&a, '/', "string", "", "join")); + cl_assert_equal_s("string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "string/", "", "join")); + cl_assert_equal_s("string/join", a.ptr); + cl_git_pass(git_str_join3(&a, '/', "string/", "", "/join")); + cl_assert_equal_s("string/join", a.ptr); + + git_str_dispose(&a); +} + +void test_gitstr__11(void) +{ + git_str a = GIT_STR_INIT; + char *t1[] = { "nothing", "in", "common" }; + char *t2[] = { "something", "something else", "some other" }; + char *t3[] = { "something", "some fun", "no fun" }; + char *t4[] = { "happy", "happier", "happiest" }; + char *t5[] = { "happiest", "happier", "happy" }; + char *t6[] = { "no", "nope", "" }; + char *t7[] = { "", "doesn't matter" }; + + cl_git_pass(git_str_common_prefix(&a, t1, 3)); + cl_assert_equal_s(a.ptr, ""); + + cl_git_pass(git_str_common_prefix(&a, t2, 3)); + cl_assert_equal_s(a.ptr, "some"); + + cl_git_pass(git_str_common_prefix(&a, t3, 3)); + cl_assert_equal_s(a.ptr, ""); + + cl_git_pass(git_str_common_prefix(&a, t4, 3)); + cl_assert_equal_s(a.ptr, "happ"); + + cl_git_pass(git_str_common_prefix(&a, t5, 3)); + cl_assert_equal_s(a.ptr, "happ"); + + cl_git_pass(git_str_common_prefix(&a, t6, 3)); + cl_assert_equal_s(a.ptr, ""); + + cl_git_pass(git_str_common_prefix(&a, t7, 3)); + cl_assert_equal_s(a.ptr, ""); + + git_str_dispose(&a); +} + +void test_gitstr__rfind_variants(void) +{ + git_str a = GIT_STR_INIT; + ssize_t len; + + cl_git_pass(git_str_sets(&a, "/this/is/it/")); + + len = (ssize_t)git_str_len(&a); + + cl_assert(git_str_rfind(&a, '/') == len - 1); + cl_assert(git_str_rfind_next(&a, '/') == len - 4); + + cl_assert(git_str_rfind(&a, 'i') == len - 3); + cl_assert(git_str_rfind_next(&a, 'i') == len - 3); + + cl_assert(git_str_rfind(&a, 'h') == 2); + cl_assert(git_str_rfind_next(&a, 'h') == 2); + + cl_assert(git_str_rfind(&a, 'q') == -1); + cl_assert(git_str_rfind_next(&a, 'q') == -1); + + git_str_dispose(&a); +} + +void test_gitstr__puts_escaped(void) +{ + git_str a = GIT_STR_INIT; + + git_str_clear(&a); + cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", "")); + cl_assert_equal_s("this is a test", a.ptr); + + git_str_clear(&a); + cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\")); + cl_assert_equal_s("\\this is a \\tes\\t", a.ptr); + + git_str_clear(&a); + cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__")); + cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr); + + git_str_clear(&a); + cl_git_pass(git_str_puts_escape_regex(&a, "^match\\s*[A-Z]+.*")); + cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr); + + git_str_dispose(&a); +} + +static void assert_unescape(char *expected, char *to_unescape) { + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_sets(&buf, to_unescape)); + git_str_unescape(&buf); + cl_assert_equal_s(expected, buf.ptr); + cl_assert_equal_sz(strlen(expected), buf.size); + + git_str_dispose(&buf); +} + +void test_gitstr__unescape(void) +{ + assert_unescape("Escaped\\", "Es\\ca\\ped\\"); + assert_unescape("Es\\caped\\", "Es\\\\ca\\ped\\\\"); + assert_unescape("\\", "\\"); + assert_unescape("\\", "\\\\"); + assert_unescape("", ""); +} + +void test_gitstr__encode_base64(void) +{ + git_str buf = GIT_STR_INIT; + + /* t h i s + * 0x 74 68 69 73 + * 0b 01110100 01101000 01101001 01110011 + * 0b 011101 000110 100001 101001 011100 110000 + * 0x 1d 06 21 29 1c 30 + * d G h p c w + */ + cl_git_pass(git_str_encode_base64(&buf, "this", 4)); + cl_assert_equal_s("dGhpcw==", buf.ptr); + + git_str_clear(&buf); + cl_git_pass(git_str_encode_base64(&buf, "this!", 5)); + cl_assert_equal_s("dGhpcyE=", buf.ptr); + + git_str_clear(&buf); + cl_git_pass(git_str_encode_base64(&buf, "this!\n", 6)); + cl_assert_equal_s("dGhpcyEK", buf.ptr); + + git_str_dispose(&buf); +} + +void test_gitstr__decode_base64(void) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_decode_base64(&buf, "dGhpcw==", 8)); + cl_assert_equal_s("this", buf.ptr); + + git_str_clear(&buf); + cl_git_pass(git_str_decode_base64(&buf, "dGhpcyE=", 8)); + cl_assert_equal_s("this!", buf.ptr); + + git_str_clear(&buf); + cl_git_pass(git_str_decode_base64(&buf, "dGhpcyEK", 8)); + cl_assert_equal_s("this!\n", buf.ptr); + + cl_git_fail(git_str_decode_base64(&buf, "This is not a valid base64 string!!!", 36)); + cl_assert_equal_s("this!\n", buf.ptr); + + git_str_dispose(&buf); +} + +void test_gitstr__encode_base85(void) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_encode_base85(&buf, "this", 4)); + cl_assert_equal_s("bZBXF", buf.ptr); + git_str_clear(&buf); + + cl_git_pass(git_str_encode_base85(&buf, "two rnds", 8)); + cl_assert_equal_s("ba!tca&BaE", buf.ptr); + git_str_clear(&buf); + + cl_git_pass(git_str_encode_base85(&buf, "this is base 85 encoded", + strlen("this is base 85 encoded"))); + cl_assert_equal_s("bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", buf.ptr); + git_str_clear(&buf); + + git_str_dispose(&buf); +} + +void test_gitstr__decode_base85(void) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_decode_base85(&buf, "bZBXF", 5, 4)); + cl_assert_equal_sz(4, buf.size); + cl_assert_equal_s("this", buf.ptr); + git_str_clear(&buf); + + cl_git_pass(git_str_decode_base85(&buf, "ba!tca&BaE", 10, 8)); + cl_assert_equal_sz(8, buf.size); + cl_assert_equal_s("two rnds", buf.ptr); + git_str_clear(&buf); + + cl_git_pass(git_str_decode_base85(&buf, "bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", 30, 23)); + cl_assert_equal_sz(23, buf.size); + cl_assert_equal_s("this is base 85 encoded", buf.ptr); + git_str_clear(&buf); + + git_str_dispose(&buf); +} + +void test_gitstr__decode_base85_fails_gracefully(void) +{ + git_str buf = GIT_STR_INIT; + + git_str_puts(&buf, "foobar"); + + cl_git_fail(git_str_decode_base85(&buf, "invalid charsZZ", 15, 42)); + cl_git_fail(git_str_decode_base85(&buf, "invalidchars__ ", 15, 42)); + cl_git_fail(git_str_decode_base85(&buf, "overflowZZ~~~~~", 15, 42)); + cl_git_fail(git_str_decode_base85(&buf, "truncated", 9, 42)); + cl_assert_equal_sz(6, buf.size); + cl_assert_equal_s("foobar", buf.ptr); + + git_str_dispose(&buf); +} + +void test_gitstr__classify_with_utf8(void) +{ + char *data0 = "Simple text\n"; + size_t data0len = 12; + char *data1 = "Is that UTF-8 data I see…\nYep!\n"; + size_t data1len = 31; + char *data2 = "Internal NUL!!!\000\n\nI see you!\n"; + size_t data2len = 29; + char *data3 = "\xef\xbb\xbfThis is UTF-8 with a BOM.\n"; + size_t data3len = 20; + git_str b; + + b.ptr = data0; b.size = b.asize = data0len; + cl_assert(!git_str_is_binary(&b)); + cl_assert(!git_str_contains_nul(&b)); + + b.ptr = data1; b.size = b.asize = data1len; + cl_assert(!git_str_is_binary(&b)); + cl_assert(!git_str_contains_nul(&b)); + + b.ptr = data2; b.size = b.asize = data2len; + cl_assert(git_str_is_binary(&b)); + cl_assert(git_str_contains_nul(&b)); + + b.ptr = data3; b.size = b.asize = data3len; + cl_assert(!git_str_is_binary(&b)); + cl_assert(!git_str_contains_nul(&b)); +} + +#include "crlf.h" + +#define check_buf(expected,buf) do { \ + cl_assert_equal_s(expected, buf.ptr); \ + cl_assert_equal_sz(strlen(expected), buf.size); } while (0) + +void test_gitstr__lf_and_crlf_conversions(void) +{ + git_str src = GIT_STR_INIT, tgt = GIT_STR_INIT; + + /* LF source */ + + git_str_sets(&src, "lf\nlf\nlf\nlf\n"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("lf\r\nlf\r\nlf\r\nlf\r\n", tgt); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf(src.ptr, tgt); + + git_str_sets(&src, "\nlf\nlf\nlf\nlf\nlf"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("\r\nlf\r\nlf\r\nlf\r\nlf\r\nlf", tgt); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf(src.ptr, tgt); + + /* CRLF source */ + + git_str_sets(&src, "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n", tgt); + + git_str_sets(&src, "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n"); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf("crlf\ncrlf\ncrlf\ncrlf\n", tgt); + + git_str_sets(&src, "\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf", tgt); + + git_str_sets(&src, "\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf"); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf("\ncrlf\ncrlf\ncrlf\ncrlf\ncrlf", tgt); + + /* CRLF in LF text */ + + git_str_sets(&src, "\nlf\nlf\ncrlf\r\nlf\nlf\ncrlf\r\n"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("\r\nlf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\ncrlf\r\n", tgt); + + git_str_sets(&src, "\nlf\nlf\ncrlf\r\nlf\nlf\ncrlf\r\n"); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf("\nlf\nlf\ncrlf\nlf\nlf\ncrlf\n", tgt); + + /* LF in CRLF text */ + + git_str_sets(&src, "\ncrlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("\r\ncrlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf", tgt); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf("\ncrlf\ncrlf\nlf\ncrlf\ncrlf", tgt); + + /* bare CR test */ + + git_str_sets(&src, "\rcrlf\r\nlf\nlf\ncr\rcrlf\r\nlf\ncr\r"); + + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf("\rcrlf\r\nlf\r\nlf\r\ncr\rcrlf\r\nlf\r\ncr\r", tgt); + + git_str_sets(&src, "\rcrlf\r\nlf\nlf\ncr\rcrlf\r\nlf\ncr\r"); + + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf("\rcrlf\nlf\nlf\ncr\rcrlf\nlf\ncr\r", tgt); + + git_str_sets(&src, "\rcr\r"); + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf(src.ptr, tgt); + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf("\rcr\r", tgt); + + git_str_dispose(&src); + git_str_dispose(&tgt); + + /* blob correspondence tests */ + + git_str_sets(&src, ALL_CRLF_TEXT_RAW); + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf(ALL_CRLF_TEXT_AS_CRLF, tgt); + git_str_sets(&src, ALL_CRLF_TEXT_RAW); + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf(ALL_CRLF_TEXT_AS_LF, tgt); + git_str_dispose(&src); + git_str_dispose(&tgt); + + git_str_sets(&src, ALL_LF_TEXT_RAW); + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf(ALL_LF_TEXT_AS_CRLF, tgt); + git_str_sets(&src, ALL_LF_TEXT_RAW); + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf(ALL_LF_TEXT_AS_LF, tgt); + git_str_dispose(&src); + git_str_dispose(&tgt); + + git_str_sets(&src, MORE_CRLF_TEXT_RAW); + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf(MORE_CRLF_TEXT_AS_CRLF, tgt); + git_str_sets(&src, MORE_CRLF_TEXT_RAW); + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf(MORE_CRLF_TEXT_AS_LF, tgt); + git_str_dispose(&src); + git_str_dispose(&tgt); + + git_str_sets(&src, MORE_LF_TEXT_RAW); + cl_git_pass(git_str_lf_to_crlf(&tgt, &src)); + check_buf(MORE_LF_TEXT_AS_CRLF, tgt); + git_str_sets(&src, MORE_LF_TEXT_RAW); + cl_git_pass(git_str_crlf_to_lf(&tgt, &src)); + check_buf(MORE_LF_TEXT_AS_LF, tgt); + git_str_dispose(&src); + git_str_dispose(&tgt); +} + +void test_gitstr__dont_grow_borrowed(void) +{ + const char *somestring = "blah blah"; + git_str buf = GIT_STR_INIT; + + git_str_attach_notowned(&buf, somestring, strlen(somestring) + 1); + cl_assert_equal_p(somestring, buf.ptr); + cl_assert_equal_i(0, buf.asize); + cl_assert_equal_i(strlen(somestring) + 1, buf.size); + + cl_git_fail_with(GIT_EINVALID, git_str_grow(&buf, 1024)); +} + +void test_gitstr__dont_hit_infinite_loop_when_resizing(void) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_puts(&buf, "foobar")); + /* + * We do not care whether this succeeds or fails, which + * would depend on platform-specific allocation + * semantics. We only want to know that the function + * actually returns. + */ + (void)git_str_try_grow(&buf, SIZE_MAX, true); + + git_str_dispose(&buf); +} + +void test_gitstr__avoid_printing_into_oom_buffer(void) +{ + git_str buf = GIT_STR_INIT; + + /* Emulate OOM situation with a previous allocation */ + buf.asize = 8; + buf.ptr = git_str__oom; + + /* + * Print the same string again. As the buffer still has + * an `asize` of 8 due to the previous print, + * `ENSURE_SIZE` would not try to reallocate the array at + * all. As it didn't explicitly check for `git_str__oom` + * in earlier versions, this would've resulted in it + * returning successfully and thus `git_str_puts` would + * just print into the `git_str__oom` array. + */ + cl_git_fail(git_str_puts(&buf, "foobar")); +} diff --git a/tests/util/hex.c b/tests/util/hex.c new file mode 100644 index 000000000..c5fea0a42 --- /dev/null +++ b/tests/util/hex.c @@ -0,0 +1,22 @@ +#include "clar_libgit2.h" +#include "util.h" + +void test_hex__fromhex(void) +{ + /* Passing cases */ + cl_assert(git__fromhex('0') == 0x0); + cl_assert(git__fromhex('1') == 0x1); + cl_assert(git__fromhex('3') == 0x3); + cl_assert(git__fromhex('9') == 0x9); + cl_assert(git__fromhex('A') == 0xa); + cl_assert(git__fromhex('C') == 0xc); + cl_assert(git__fromhex('F') == 0xf); + cl_assert(git__fromhex('a') == 0xa); + cl_assert(git__fromhex('c') == 0xc); + cl_assert(git__fromhex('f') == 0xf); + + /* Failing cases */ + cl_assert(git__fromhex('g') == -1); + cl_assert(git__fromhex('z') == -1); + cl_assert(git__fromhex('X') == -1); +} diff --git a/tests/util/iconv.c b/tests/util/iconv.c new file mode 100644 index 000000000..e14aebb99 --- /dev/null +++ b/tests/util/iconv.c @@ -0,0 +1,78 @@ +#include "clar_libgit2.h" +#include "fs_path.h" + +#ifdef GIT_USE_ICONV +static git_fs_path_iconv_t ic; +static char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D"; +static char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; +#endif + +void test_iconv__initialize(void) +{ +#ifdef GIT_USE_ICONV + cl_git_pass(git_fs_path_iconv_init_precompose(&ic)); +#endif +} + +void test_iconv__cleanup(void) +{ +#ifdef GIT_USE_ICONV + git_fs_path_iconv_clear(&ic); +#endif +} + +void test_iconv__unchanged(void) +{ +#ifdef GIT_USE_ICONV + const char *data = "Ascii data", *original = data; + size_t datalen = strlen(data); + + cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); + GIT_UNUSED(datalen); + + /* There are no high bits set, so this should leave data untouched */ + cl_assert(data == original); +#endif +} + +void test_iconv__decomposed_to_precomposed(void) +{ +#ifdef GIT_USE_ICONV + const char *data = nfd; + size_t datalen, nfdlen = strlen(nfd); + + datalen = nfdlen; + cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); + GIT_UNUSED(datalen); + + /* The decomposed nfd string should be transformed to the nfc form + * (on platforms where iconv is enabled, of course). + */ + cl_assert_equal_s(nfc, data); + + /* should be able to do it multiple times with the same git_fs_path_iconv_t */ + data = nfd; datalen = nfdlen; + cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); + cl_assert_equal_s(nfc, data); + + data = nfd; datalen = nfdlen; + cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); + cl_assert_equal_s(nfc, data); +#endif +} + +void test_iconv__precomposed_is_unmodified(void) +{ +#ifdef GIT_USE_ICONV + const char *data = nfc; + size_t datalen = strlen(nfc); + + cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen)); + GIT_UNUSED(datalen); + + /* data is already in precomposed form, so even though some bytes have + * the high-bit set, the iconv transform should result in no change. + */ + cl_assert_equal_s(nfc, data); +#endif +} diff --git a/tests/util/init.c b/tests/util/init.c new file mode 100644 index 000000000..78b3dd039 --- /dev/null +++ b/tests/util/init.c @@ -0,0 +1,54 @@ +#include "clar_libgit2.h" + +void test_init__returns_count(void) +{ + /* libgit2_tests initializes us first, so we have an existing + * initialization. + */ + cl_assert_equal_i(2, git_libgit2_init()); + cl_assert_equal_i(3, git_libgit2_init()); + + cl_assert_equal_i(2, git_libgit2_shutdown()); + cl_assert_equal_i(1, git_libgit2_shutdown()); +} + +void test_init__reinit_succeeds(void) +{ + cl_assert_equal_i(0, git_libgit2_shutdown()); + cl_assert_equal_i(1, git_libgit2_init()); + cl_sandbox_set_search_path_defaults(); +} + +#ifdef GIT_THREADS +static void *reinit(void *unused) +{ + unsigned i; + + for (i = 0; i < 20; i++) { + cl_assert(git_libgit2_init() > 0); + cl_assert(git_libgit2_shutdown() >= 0); + } + + return unused; +} +#endif + +void test_init__concurrent_init_succeeds(void) +{ +#ifdef GIT_THREADS + git_thread threads[10]; + unsigned i; + + cl_assert_equal_i(2, git_libgit2_init()); + + for (i = 0; i < ARRAY_SIZE(threads); i++) + git_thread_create(&threads[i], reinit, NULL); + for (i = 0; i < ARRAY_SIZE(threads); i++) + git_thread_join(&threads[i], NULL); + + cl_assert_equal_i(1, git_libgit2_shutdown()); + cl_sandbox_set_search_path_defaults(); +#else + cl_skip(); +#endif +} diff --git a/tests/util/integer.c b/tests/util/integer.c new file mode 100644 index 000000000..2226ab41e --- /dev/null +++ b/tests/util/integer.c @@ -0,0 +1,253 @@ +#include "clar_libgit2.h" + +void test_integer__multiply_int64_no_overflow(void) +{ +#if !defined(git__multiply_int64_overflow) + int64_t result = 0; + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x800000000000000))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x800000000000000))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x7fffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x7fffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x8000000000000000))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(0x1)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(-0x1)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(-0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x800000000000000))); + cl_assert_equal_i(result, INT64_C(0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x800000000000000))); + cl_assert_equal_i(result, INT64_C(-0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x7fffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x7fffffffffffffff))); + cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(-0x1)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(0x1)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(-0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x800000000000000))); + cl_assert_equal_i(result, INT64_C(-0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x800000000000000))); + cl_assert_equal_i(result, INT64_C(0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x7fffffffffffffff))); + cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x7fffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(-0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(0x4)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(-0x4)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x800000000000000))); + cl_assert_equal_i(result, INT64_C(0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x800000000000000))); + cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(-0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(0x2)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(-0x4)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(0x4)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x7ffffffffffffff))); + cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x800000000000000))); + cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x800000000000000))); + cl_assert_equal_i(result, INT64_C(0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x4000000000000000))); + cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(0xffffffffffffffe)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(-0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(-0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(0x800000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(-0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x2))); + cl_assert_equal_i(result, INT64_C(0x1000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x4000000000000000), INT64_C(0x2))); + cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x1))); + cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x0))); + cl_assert_equal_i(result, INT64_C(0x0)); +#endif +} + +void test_integer__multiply_int64_overflow(void) +{ +#if !defined(git__multiply_int64_overflow) + int64_t result = 0; + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x4000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x4000000000000000), INT64_C(0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x2))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x7ffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x800000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x7fffffffffffffff))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x8000000000000000))); +#endif +} + +void test_integer__multiply_int64_edge_cases(void) +{ +#if !defined(git__multiply_int64_overflow) + int64_t result = 0; + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x1))); + cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); + cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x8000000000000000))); + cl_assert_equal_i(result, INT64_C(-0x8000000000000000)); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x8000000000000000))); + cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x1))); +#endif +} diff --git a/tests/util/link.c b/tests/util/link.c new file mode 100644 index 000000000..46cafada7 --- /dev/null +++ b/tests/util/link.c @@ -0,0 +1,630 @@ +#include "clar_libgit2.h" +#include "posix.h" + +#ifdef GIT_WIN32 +# include "win32/reparse.h" +#endif + +void test_link__cleanup(void) +{ +#ifdef GIT_WIN32 + RemoveDirectory("lstat_junction"); + RemoveDirectory("lstat_dangling"); + RemoveDirectory("lstat_dangling_dir"); + RemoveDirectory("lstat_dangling_junction"); + + RemoveDirectory("stat_junction"); + RemoveDirectory("stat_dangling"); + RemoveDirectory("stat_dangling_dir"); + RemoveDirectory("stat_dangling_junction"); +#endif +} + +#ifdef GIT_WIN32 +static bool should_run(void) +{ + static SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY }; + PSID admin_sid; + BOOL is_admin; + + cl_win32_pass(AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &admin_sid)); + cl_win32_pass(CheckTokenMembership(NULL, admin_sid, &is_admin)); + FreeSid(admin_sid); + + return is_admin ? true : false; +} +#else +static bool should_run(void) +{ + return true; +} +#endif + +static void do_symlink(const char *old, const char *new, int is_dir) +{ +#ifndef GIT_WIN32 + GIT_UNUSED(is_dir); + + cl_must_pass(symlink(old, new)); +#else + typedef DWORD (WINAPI *create_symlink_func)(LPCTSTR, LPCTSTR, DWORD); + HMODULE module; + create_symlink_func pCreateSymbolicLink; + + cl_assert(module = GetModuleHandle("kernel32")); + cl_assert(pCreateSymbolicLink = (create_symlink_func)(void *)GetProcAddress(module, "CreateSymbolicLinkA")); + + cl_win32_pass(pCreateSymbolicLink(new, old, is_dir)); +#endif +} + +static void do_hardlink(const char *old, const char *new) +{ +#ifndef GIT_WIN32 + cl_must_pass(link(old, new)); +#else + typedef DWORD (WINAPI *create_hardlink_func)(LPCTSTR, LPCTSTR, LPSECURITY_ATTRIBUTES); + HMODULE module; + create_hardlink_func pCreateHardLink; + + cl_assert(module = GetModuleHandle("kernel32")); + cl_assert(pCreateHardLink = (create_hardlink_func)(void *)GetProcAddress(module, "CreateHardLinkA")); + + cl_win32_pass(pCreateHardLink(new, old, 0)); +#endif +} + +#ifdef GIT_WIN32 + +static void do_junction(const char *old, const char *new) +{ + GIT_REPARSE_DATA_BUFFER *reparse_buf; + HANDLE handle; + git_str unparsed_buf = GIT_STR_INIT; + wchar_t *subst_utf16, *print_utf16; + DWORD ioctl_ret; + int subst_utf16_len, subst_byte_len, print_utf16_len, print_byte_len, ret; + USHORT reparse_buflen; + size_t i; + + /* Junction targets must be the unparsed name, starting with \??\, using + * backslashes instead of forward, and end in a trailing backslash. + * eg: \??\C:\Foo\ + */ + git_str_puts(&unparsed_buf, "\\??\\"); + + for (i = 0; i < strlen(old); i++) + git_str_putc(&unparsed_buf, old[i] == '/' ? '\\' : old[i]); + + git_str_putc(&unparsed_buf, '\\'); + + subst_utf16_len = git__utf8_to_16(NULL, 0, git_str_cstr(&unparsed_buf)); + subst_byte_len = subst_utf16_len * sizeof(WCHAR); + + print_utf16_len = subst_utf16_len - 4; + print_byte_len = subst_byte_len - (4 * sizeof(WCHAR)); + + /* The junction must be an empty directory before the junction attribute + * can be added. + */ + cl_win32_pass(CreateDirectoryA(new, NULL)); + + handle = CreateFileA(new, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); + cl_win32_pass(handle != INVALID_HANDLE_VALUE); + + reparse_buflen = (USHORT)(REPARSE_DATA_HEADER_SIZE + + REPARSE_DATA_MOUNTPOINT_HEADER_SIZE + + subst_byte_len + sizeof(WCHAR) + + print_byte_len + sizeof(WCHAR)); + + reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen); + cl_assert(reparse_buf); + + subst_utf16 = reparse_buf->ReparseBuffer.MountPoint.PathBuffer; + print_utf16 = subst_utf16 + subst_utf16_len + 1; + + ret = git__utf8_to_16(subst_utf16, subst_utf16_len + 1, + git_str_cstr(&unparsed_buf)); + cl_assert_equal_i(subst_utf16_len, ret); + + ret = git__utf8_to_16(print_utf16, + print_utf16_len + 1, git_str_cstr(&unparsed_buf) + 4); + cl_assert_equal_i(print_utf16_len, ret); + + reparse_buf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset = 0; + reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength = subst_byte_len; + reparse_buf->ReparseBuffer.MountPoint.PrintNameOffset = (USHORT)(subst_byte_len + sizeof(WCHAR)); + reparse_buf->ReparseBuffer.MountPoint.PrintNameLength = print_byte_len; + reparse_buf->ReparseDataLength = reparse_buflen - REPARSE_DATA_HEADER_SIZE; + + cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, + reparse_buf, reparse_buflen, NULL, 0, &ioctl_ret, NULL)); + + CloseHandle(handle); + LocalFree(reparse_buf); + + git_str_dispose(&unparsed_buf); +} + +static void do_custom_reparse(const char *path) +{ + REPARSE_GUID_DATA_BUFFER *reparse_buf; + HANDLE handle; + DWORD ioctl_ret; + + const char *reparse_data = "Reparse points are silly."; + size_t reparse_buflen = REPARSE_GUID_DATA_BUFFER_HEADER_SIZE + + strlen(reparse_data) + 1; + + reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen); + cl_assert(reparse_buf); + + reparse_buf->ReparseTag = 42; + reparse_buf->ReparseDataLength = (WORD)(strlen(reparse_data) + 1); + + reparse_buf->ReparseGuid.Data1 = 0xdeadbeef; + reparse_buf->ReparseGuid.Data2 = 0xdead; + reparse_buf->ReparseGuid.Data3 = 0xbeef; + reparse_buf->ReparseGuid.Data4[0] = 42; + reparse_buf->ReparseGuid.Data4[1] = 42; + reparse_buf->ReparseGuid.Data4[2] = 42; + reparse_buf->ReparseGuid.Data4[3] = 42; + reparse_buf->ReparseGuid.Data4[4] = 42; + reparse_buf->ReparseGuid.Data4[5] = 42; + reparse_buf->ReparseGuid.Data4[6] = 42; + reparse_buf->ReparseGuid.Data4[7] = 42; + reparse_buf->ReparseGuid.Data4[8] = 42; + + memcpy(reparse_buf->GenericReparseBuffer.DataBuffer, + reparse_data, strlen(reparse_data) + 1); + + handle = CreateFileA(path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); + cl_win32_pass(handle != INVALID_HANDLE_VALUE); + + cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, + reparse_buf, + reparse_buf->ReparseDataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, + NULL, 0, &ioctl_ret, NULL)); + + CloseHandle(handle); + LocalFree(reparse_buf); +} + +#endif + +void test_link__stat_regular_file(void) +{ + struct stat st; + + cl_git_rewritefile("stat_regfile", "This is a regular file!\n"); + + cl_must_pass(p_stat("stat_regfile", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(24, st.st_size); +} + +void test_link__lstat_regular_file(void) +{ + struct stat st; + + cl_git_rewritefile("lstat_regfile", "This is a regular file!\n"); + + cl_must_pass(p_stat("lstat_regfile", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(24, st.st_size); +} + +void test_link__stat_symlink(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + cl_git_rewritefile("stat_target", "This is the target of a symbolic link.\n"); + do_symlink("stat_target", "stat_symlink", 0); + + cl_must_pass(p_stat("stat_target", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(39, st.st_size); + + cl_must_pass(p_stat("stat_symlink", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(39, st.st_size); +} + +void test_link__stat_symlink_directory(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + p_mkdir("stat_dirtarget", 0777); + do_symlink("stat_dirtarget", "stat_dirlink", 1); + + cl_must_pass(p_stat("stat_dirtarget", &st)); + cl_assert(S_ISDIR(st.st_mode)); + + cl_must_pass(p_stat("stat_dirlink", &st)); + cl_assert(S_ISDIR(st.st_mode)); +} + +void test_link__stat_symlink_chain(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + cl_git_rewritefile("stat_final_target", "Final target of some symbolic links...\n"); + do_symlink("stat_final_target", "stat_chain_3", 0); + do_symlink("stat_chain_3", "stat_chain_2", 0); + do_symlink("stat_chain_2", "stat_chain_1", 0); + + cl_must_pass(p_stat("stat_chain_1", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(39, st.st_size); +} + +void test_link__stat_dangling_symlink(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + do_symlink("stat_nonexistent", "stat_dangling", 0); + + cl_must_fail(p_stat("stat_nonexistent", &st)); + cl_must_fail(p_stat("stat_dangling", &st)); +} + +void test_link__stat_dangling_symlink_directory(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + do_symlink("stat_nonexistent", "stat_dangling_dir", 1); + + cl_must_fail(p_stat("stat_nonexistent_dir", &st)); + cl_must_fail(p_stat("stat_dangling", &st)); +} + +void test_link__lstat_symlink(void) +{ + git_str target_path = GIT_STR_INIT; + struct stat st; + + if (!should_run()) + clar__skip(); + + /* Windows always writes the canonical path as the link target, so + * write the full path on all platforms. + */ + git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_target"); + + cl_git_rewritefile("lstat_target", "This is the target of a symbolic link.\n"); + do_symlink(git_str_cstr(&target_path), "lstat_symlink", 0); + + cl_must_pass(p_lstat("lstat_target", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(39, st.st_size); + + cl_must_pass(p_lstat("lstat_symlink", &st)); + cl_assert(S_ISLNK(st.st_mode)); + cl_assert_equal_i(git_str_len(&target_path), st.st_size); + + git_str_dispose(&target_path); +} + +void test_link__lstat_symlink_directory(void) +{ + git_str target_path = GIT_STR_INIT; + struct stat st; + + if (!should_run()) + clar__skip(); + + git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_dirtarget"); + + p_mkdir("lstat_dirtarget", 0777); + do_symlink(git_str_cstr(&target_path), "lstat_dirlink", 1); + + cl_must_pass(p_lstat("lstat_dirtarget", &st)); + cl_assert(S_ISDIR(st.st_mode)); + + cl_must_pass(p_lstat("lstat_dirlink", &st)); + cl_assert(S_ISLNK(st.st_mode)); + cl_assert_equal_i(git_str_len(&target_path), st.st_size); + + git_str_dispose(&target_path); +} + +void test_link__lstat_dangling_symlink(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + do_symlink("lstat_nonexistent", "lstat_dangling", 0); + + cl_must_fail(p_lstat("lstat_nonexistent", &st)); + + cl_must_pass(p_lstat("lstat_dangling", &st)); + cl_assert(S_ISLNK(st.st_mode)); + cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size); +} + +void test_link__lstat_dangling_symlink_directory(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + do_symlink("lstat_nonexistent", "lstat_dangling_dir", 1); + + cl_must_fail(p_lstat("lstat_nonexistent", &st)); + + cl_must_pass(p_lstat("lstat_dangling_dir", &st)); + cl_assert(S_ISLNK(st.st_mode)); + cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size); +} + +void test_link__stat_junction(void) +{ +#ifdef GIT_WIN32 + git_str target_path = GIT_STR_INIT; + struct stat st; + + git_str_join(&target_path, '/', clar_sandbox_path(), "stat_junctarget"); + + p_mkdir("stat_junctarget", 0777); + do_junction(git_str_cstr(&target_path), "stat_junction"); + + cl_must_pass(p_stat("stat_junctarget", &st)); + cl_assert(S_ISDIR(st.st_mode)); + + cl_must_pass(p_stat("stat_junction", &st)); + cl_assert(S_ISDIR(st.st_mode)); + + git_str_dispose(&target_path); +#endif +} + +void test_link__stat_dangling_junction(void) +{ +#ifdef GIT_WIN32 + git_str target_path = GIT_STR_INIT; + struct stat st; + + git_str_join(&target_path, '/', clar_sandbox_path(), "stat_nonexistent_junctarget"); + + p_mkdir("stat_nonexistent_junctarget", 0777); + do_junction(git_str_cstr(&target_path), "stat_dangling_junction"); + + RemoveDirectory("stat_nonexistent_junctarget"); + + cl_must_fail(p_stat("stat_nonexistent_junctarget", &st)); + cl_must_fail(p_stat("stat_dangling_junction", &st)); + + git_str_dispose(&target_path); +#endif +} + +void test_link__lstat_junction(void) +{ +#ifdef GIT_WIN32 + git_str target_path = GIT_STR_INIT; + struct stat st; + + git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_junctarget"); + + p_mkdir("lstat_junctarget", 0777); + do_junction(git_str_cstr(&target_path), "lstat_junction"); + + cl_must_pass(p_lstat("lstat_junctarget", &st)); + cl_assert(S_ISDIR(st.st_mode)); + + cl_must_pass(p_lstat("lstat_junction", &st)); + cl_assert(S_ISLNK(st.st_mode)); + + git_str_dispose(&target_path); +#endif +} + +void test_link__lstat_dangling_junction(void) +{ +#ifdef GIT_WIN32 + git_str target_path = GIT_STR_INIT; + struct stat st; + + git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_nonexistent_junctarget"); + + p_mkdir("lstat_nonexistent_junctarget", 0777); + do_junction(git_str_cstr(&target_path), "lstat_dangling_junction"); + + RemoveDirectory("lstat_nonexistent_junctarget"); + + cl_must_fail(p_lstat("lstat_nonexistent_junctarget", &st)); + + cl_must_pass(p_lstat("lstat_dangling_junction", &st)); + cl_assert(S_ISLNK(st.st_mode)); + cl_assert_equal_i(git_str_len(&target_path), st.st_size); + + git_str_dispose(&target_path); +#endif +} + +void test_link__stat_hardlink(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + cl_git_rewritefile("stat_hardlink1", "This file has many names!\n"); + do_hardlink("stat_hardlink1", "stat_hardlink2"); + + cl_must_pass(p_stat("stat_hardlink1", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(26, st.st_size); + + cl_must_pass(p_stat("stat_hardlink2", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(26, st.st_size); +} + +void test_link__lstat_hardlink(void) +{ + struct stat st; + + if (!should_run()) + clar__skip(); + + cl_git_rewritefile("lstat_hardlink1", "This file has many names!\n"); + do_hardlink("lstat_hardlink1", "lstat_hardlink2"); + + cl_must_pass(p_lstat("lstat_hardlink1", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(26, st.st_size); + + cl_must_pass(p_lstat("lstat_hardlink2", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(26, st.st_size); +} + +void test_link__stat_reparse_point(void) +{ +#ifdef GIT_WIN32 + struct stat st; + + /* Generic reparse points should be treated as regular files, only + * symlinks and junctions should be treated as links. + */ + + cl_git_rewritefile("stat_reparse", "This is a reparse point!\n"); + do_custom_reparse("stat_reparse"); + + cl_must_pass(p_lstat("stat_reparse", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(25, st.st_size); +#endif +} + +void test_link__lstat_reparse_point(void) +{ +#ifdef GIT_WIN32 + struct stat st; + + cl_git_rewritefile("lstat_reparse", "This is a reparse point!\n"); + do_custom_reparse("lstat_reparse"); + + cl_must_pass(p_lstat("lstat_reparse", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_equal_i(25, st.st_size); +#endif +} + +void test_link__readlink_nonexistent_file(void) +{ + char buf[2048]; + + cl_must_fail(p_readlink("readlink_nonexistent", buf, 2048)); + cl_assert_equal_i(ENOENT, errno); +} + +void test_link__readlink_normal_file(void) +{ + char buf[2048]; + + cl_git_rewritefile("readlink_regfile", "This is a regular file!\n"); + cl_must_fail(p_readlink("readlink_regfile", buf, 2048)); + cl_assert_equal_i(EINVAL, errno); +} + +void test_link__readlink_symlink(void) +{ + git_str target_path = GIT_STR_INIT; + int len; + char buf[2048]; + + if (!should_run()) + clar__skip(); + + git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_target"); + + cl_git_rewritefile("readlink_target", "This is the target of a symlink\n"); + do_symlink(git_str_cstr(&target_path), "readlink_link", 0); + + len = p_readlink("readlink_link", buf, 2048); + cl_must_pass(len); + + buf[len] = 0; + + cl_assert_equal_s(git_str_cstr(&target_path), buf); + + git_str_dispose(&target_path); +} + +void test_link__readlink_dangling(void) +{ + git_str target_path = GIT_STR_INIT; + int len; + char buf[2048]; + + if (!should_run()) + clar__skip(); + + git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_nonexistent"); + + do_symlink(git_str_cstr(&target_path), "readlink_dangling", 0); + + len = p_readlink("readlink_dangling", buf, 2048); + cl_must_pass(len); + + buf[len] = 0; + + cl_assert_equal_s(git_str_cstr(&target_path), buf); + + git_str_dispose(&target_path); +} + +void test_link__readlink_multiple(void) +{ + git_str target_path = GIT_STR_INIT, + path3 = GIT_STR_INIT, path2 = GIT_STR_INIT, path1 = GIT_STR_INIT; + int len; + char buf[2048]; + + if (!should_run()) + clar__skip(); + + git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_final"); + git_str_join(&path3, '/', clar_sandbox_path(), "readlink_3"); + git_str_join(&path2, '/', clar_sandbox_path(), "readlink_2"); + git_str_join(&path1, '/', clar_sandbox_path(), "readlink_1"); + + do_symlink(git_str_cstr(&target_path), git_str_cstr(&path3), 0); + do_symlink(git_str_cstr(&path3), git_str_cstr(&path2), 0); + do_symlink(git_str_cstr(&path2), git_str_cstr(&path1), 0); + + len = p_readlink("readlink_1", buf, 2048); + cl_must_pass(len); + + buf[len] = 0; + + cl_assert_equal_s(git_str_cstr(&path2), buf); + + git_str_dispose(&path1); + git_str_dispose(&path2); + git_str_dispose(&path3); + git_str_dispose(&target_path); +} diff --git a/tests/util/memmem.c b/tests/util/memmem.c new file mode 100644 index 000000000..1c713e8bd --- /dev/null +++ b/tests/util/memmem.c @@ -0,0 +1,46 @@ +#include "clar_libgit2.h" + +static void assert_found(const char *haystack, const char *needle, size_t expected_pos) +{ + cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0, + needle, needle ? strlen(needle) : 0), + haystack + expected_pos); +} + +static void assert_absent(const char *haystack, const char *needle) +{ + cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0, + needle, needle ? strlen(needle) : 0), + NULL); +} + +void test_memmem__found(void) +{ + assert_found("a", "a", 0); + assert_found("ab", "a", 0); + assert_found("ba", "a", 1); + assert_found("aa", "a", 0); + assert_found("aab", "aa", 0); + assert_found("baa", "aa", 1); + assert_found("dabc", "abc", 1); + assert_found("abababc", "abc", 4); +} + +void test_memmem__absent(void) +{ + assert_absent("a", "b"); + assert_absent("a", "aa"); + assert_absent("ba", "ab"); + assert_absent("ba", "ab"); + assert_absent("abc", "abcd"); + assert_absent("abcabcabc", "bcac"); +} + +void test_memmem__edgecases(void) +{ + assert_absent(NULL, NULL); + assert_absent("a", NULL); + assert_absent(NULL, "a"); + assert_absent("", "a"); + assert_absent("a", ""); +} diff --git a/tests/util/mkdir.c b/tests/util/mkdir.c new file mode 100644 index 000000000..8658eec0d --- /dev/null +++ b/tests/util/mkdir.c @@ -0,0 +1,291 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "posix.h" + +static void cleanup_basic_dirs(void *ref) +{ + GIT_UNUSED(ref); + git_futils_rmdir_r("d0", NULL, GIT_RMDIR_EMPTY_HIERARCHY); + git_futils_rmdir_r("d1", NULL, GIT_RMDIR_EMPTY_HIERARCHY); + git_futils_rmdir_r("d2", NULL, GIT_RMDIR_EMPTY_HIERARCHY); + git_futils_rmdir_r("d3", NULL, GIT_RMDIR_EMPTY_HIERARCHY); + git_futils_rmdir_r("d4", NULL, GIT_RMDIR_EMPTY_HIERARCHY); +} + +void test_mkdir__absolute(void) +{ + git_str path = GIT_STR_INIT; + + cl_set_cleanup(cleanup_basic_dirs, NULL); + + git_str_joinpath(&path, clar_sandbox_path(), "d0"); + + /* make a directory */ + cl_assert(!git_fs_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); + cl_assert(git_fs_path_isdir(path.ptr)); + + git_str_joinpath(&path, path.ptr, "subdir"); + cl_assert(!git_fs_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); + cl_assert(git_fs_path_isdir(path.ptr)); + + /* ensure mkdir_r works for a single subdir */ + git_str_joinpath(&path, path.ptr, "another"); + cl_assert(!git_fs_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); + cl_assert(git_fs_path_isdir(path.ptr)); + + /* ensure mkdir_r works */ + git_str_joinpath(&path, clar_sandbox_path(), "d1/foo/bar/asdf"); + cl_assert(!git_fs_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); + cl_assert(git_fs_path_isdir(path.ptr)); + + /* ensure we don't imply recursive */ + git_str_joinpath(&path, clar_sandbox_path(), "d2/foo/bar/asdf"); + cl_assert(!git_fs_path_isdir(path.ptr)); + cl_git_fail(git_futils_mkdir(path.ptr, 0755, 0)); + cl_assert(!git_fs_path_isdir(path.ptr)); + + git_str_dispose(&path); +} + +void test_mkdir__basic(void) +{ + cl_set_cleanup(cleanup_basic_dirs, NULL); + + /* make a directory */ + cl_assert(!git_fs_path_isdir("d0")); + cl_git_pass(git_futils_mkdir("d0", 0755, 0)); + cl_assert(git_fs_path_isdir("d0")); + + /* make a path */ + cl_assert(!git_fs_path_isdir("d1")); + cl_git_pass(git_futils_mkdir("d1/d1.1/d1.2", 0755, GIT_MKDIR_PATH)); + cl_assert(git_fs_path_isdir("d1")); + cl_assert(git_fs_path_isdir("d1/d1.1")); + cl_assert(git_fs_path_isdir("d1/d1.1/d1.2")); + + /* make a dir exclusively */ + cl_assert(!git_fs_path_isdir("d2")); + cl_git_pass(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL)); + cl_assert(git_fs_path_isdir("d2")); + + /* make exclusive failure */ + cl_git_fail(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL)); + + /* make a path exclusively */ + cl_assert(!git_fs_path_isdir("d3")); + cl_git_pass(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_assert(git_fs_path_isdir("d3")); + cl_assert(git_fs_path_isdir("d3/d3.1/d3.2")); + + /* make exclusive path failure */ + cl_git_fail(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + /* ??? Should EXCL only apply to the last item in the path? */ + + /* path with trailing slash? */ + cl_assert(!git_fs_path_isdir("d4")); + cl_git_pass(git_futils_mkdir("d4/d4.1/", 0755, GIT_MKDIR_PATH)); + cl_assert(git_fs_path_isdir("d4/d4.1")); +} + +static void cleanup_basedir(void *ref) +{ + GIT_UNUSED(ref); + git_futils_rmdir_r("base", NULL, GIT_RMDIR_EMPTY_HIERARCHY); +} + +void test_mkdir__with_base(void) +{ +#define BASEDIR "base/dir/here" + + cl_set_cleanup(cleanup_basedir, NULL); + + cl_git_pass(git_futils_mkdir(BASEDIR, 0755, GIT_MKDIR_PATH)); + + cl_git_pass(git_futils_mkdir_relative("a", BASEDIR, 0755, 0, NULL)); + cl_assert(git_fs_path_isdir(BASEDIR "/a")); + + cl_git_pass(git_futils_mkdir_relative("b/b1/b2", BASEDIR, 0755, GIT_MKDIR_PATH, NULL)); + cl_assert(git_fs_path_isdir(BASEDIR "/b/b1/b2")); + + /* exclusive with existing base */ + cl_git_pass(git_futils_mkdir_relative("c/c1/c2", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); + + /* fail: exclusive with duplicated suffix */ + cl_git_fail(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); + + /* fail: exclusive with any duplicated component */ + cl_git_fail(git_futils_mkdir_relative("c/cz/cz", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); + + /* success: exclusive without path */ + cl_git_pass(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_EXCL, NULL)); + + /* path with shorter base and existing dirs */ + cl_git_pass(git_futils_mkdir_relative("dir/here/d/", "base", 0755, GIT_MKDIR_PATH, NULL)); + cl_assert(git_fs_path_isdir("base/dir/here/d")); + + /* fail: path with shorter base and existing dirs */ + cl_git_fail(git_futils_mkdir_relative("dir/here/e/", "base", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); + + /* fail: base with missing components */ + cl_git_fail(git_futils_mkdir_relative("f/", "base/missing", 0755, GIT_MKDIR_PATH, NULL)); + + /* success: shift missing component to path */ + cl_git_pass(git_futils_mkdir_relative("missing/f/", "base/", 0755, GIT_MKDIR_PATH, NULL)); +} + +static void cleanup_chmod_root(void *ref) +{ + mode_t *mode = ref; + + if (mode != NULL) { + (void)p_umask(*mode); + git__free(mode); + } + + git_futils_rmdir_r("r", NULL, GIT_RMDIR_EMPTY_HIERARCHY); +} + +#define check_mode(X,A) check_mode_at_line((X), (A), __FILE__, __func__, __LINE__) + +static void check_mode_at_line( + mode_t expected, mode_t actual, + const char *file, const char *func, int line) +{ + /* FAT filesystems don't support exec bit, nor group/world bits */ + if (!cl_is_chmod_supported()) { + expected &= 0600; + actual &= 0600; + } + + clar__assert_equal( + file, func, line, "expected_mode != actual_mode", 1, + "%07o", (int)expected, (int)(actual & 0777)); +} + +void test_mkdir__chmods(void) +{ + struct stat st; + mode_t *old = git__malloc(sizeof(mode_t)); + *old = p_umask(022); + + cl_set_cleanup(cleanup_chmod_root, old); + + cl_git_pass(git_futils_mkdir("r", 0777, 0)); + + cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL)); + + cl_git_pass(git_fs_path_lstat("r/mode", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode/is", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode/is/important", &st)); + check_mode(0755, st.st_mode); + + cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL)); + + cl_git_pass(git_fs_path_lstat("r/mode2", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode2/is2", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode2/is2/important2", &st)); + check_mode(0777, st.st_mode); + + cl_git_pass(git_futils_mkdir_relative("mode3/is3/important3", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL)); + + cl_git_pass(git_fs_path_lstat("r/mode3", &st)); + check_mode(0777, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode3/is3", &st)); + check_mode(0777, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode3/is3/important3", &st)); + check_mode(0777, st.st_mode); + + /* test that we chmod existing dir */ + + cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL)); + + cl_git_pass(git_fs_path_lstat("r/mode", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode/is", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode/is/important", &st)); + check_mode(0777, st.st_mode); + + /* test that we chmod even existing dirs if CHMOD_PATH is set */ + + cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2.1", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL)); + + cl_git_pass(git_fs_path_lstat("r/mode2", &st)); + check_mode(0777, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode2/is2", &st)); + check_mode(0777, st.st_mode); + cl_git_pass(git_fs_path_lstat("r/mode2/is2/important2.1", &st)); + check_mode(0777, st.st_mode); +} + +void test_mkdir__keeps_parent_symlinks(void) +{ +#ifndef GIT_WIN32 + git_str path = GIT_STR_INIT; + + cl_set_cleanup(cleanup_basic_dirs, NULL); + + /* make a directory */ + cl_assert(!git_fs_path_isdir("d0")); + cl_git_pass(git_futils_mkdir("d0", 0755, 0)); + cl_assert(git_fs_path_isdir("d0")); + + cl_must_pass(symlink("d0", "d1")); + cl_assert(git_fs_path_islink("d1")); + + cl_git_pass(git_futils_mkdir("d1/foo/bar", 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS)); + cl_assert(git_fs_path_islink("d1")); + cl_assert(git_fs_path_isdir("d1/foo/bar")); + cl_assert(git_fs_path_isdir("d0/foo/bar")); + + cl_must_pass(symlink("d0", "d2")); + cl_assert(git_fs_path_islink("d2")); + + git_str_joinpath(&path, clar_sandbox_path(), "d2/other/dir"); + + cl_git_pass(git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS)); + cl_assert(git_fs_path_islink("d2")); + cl_assert(git_fs_path_isdir("d2/other/dir")); + cl_assert(git_fs_path_isdir("d0/other/dir")); + + git_str_dispose(&path); +#endif +} + +void test_mkdir__mkdir_path_inside_unwriteable_parent(void) +{ + struct stat st; + mode_t *old; + + /* FAT filesystems don't support exec bit, nor group/world bits */ + if (!cl_is_chmod_supported()) + return; + + cl_assert((old = git__malloc(sizeof(mode_t))) != NULL); + *old = p_umask(022); + cl_set_cleanup(cleanup_chmod_root, old); + + cl_git_pass(git_futils_mkdir("r", 0777, 0)); + cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL)); + cl_git_pass(git_fs_path_lstat("r/mode", &st)); + check_mode(0755, st.st_mode); + + cl_must_pass(p_chmod("r/mode", 0111)); + cl_git_pass(git_fs_path_lstat("r/mode", &st)); + check_mode(0111, st.st_mode); + + cl_git_pass( + git_futils_mkdir_relative("mode/is/okay/inside", "r", 0777, GIT_MKDIR_PATH, NULL)); + cl_git_pass(git_fs_path_lstat("r/mode/is/okay/inside", &st)); + check_mode(0755, st.st_mode); + + cl_must_pass(p_chmod("r/mode", 0777)); +} diff --git a/tests/util/path.c b/tests/util/path.c new file mode 100644 index 000000000..404c17a2b --- /dev/null +++ b/tests/util/path.c @@ -0,0 +1,739 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "fs_path.h" + +static char *path_save; + +void test_path__initialize(void) +{ + path_save = cl_getenv("PATH"); +} + +void test_path__cleanup(void) +{ + cl_setenv("PATH", path_save); + git__free(path_save); + path_save = NULL; +} + +static void +check_dirname(const char *A, const char *B) +{ + git_str dir = GIT_STR_INIT; + char *dir2; + + cl_assert(git_fs_path_dirname_r(&dir, A) >= 0); + cl_assert_equal_s(B, dir.ptr); + git_str_dispose(&dir); + + cl_assert((dir2 = git_fs_path_dirname(A)) != NULL); + cl_assert_equal_s(B, dir2); + git__free(dir2); +} + +static void +check_basename(const char *A, const char *B) +{ + git_str base = GIT_STR_INIT; + char *base2; + + cl_assert(git_fs_path_basename_r(&base, A) >= 0); + cl_assert_equal_s(B, base.ptr); + git_str_dispose(&base); + + cl_assert((base2 = git_fs_path_basename(A)) != NULL); + cl_assert_equal_s(B, base2); + git__free(base2); +} + +static void +check_joinpath(const char *path_a, const char *path_b, const char *expected_path) +{ + git_str joined_path = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&joined_path, path_a, path_b)); + cl_assert_equal_s(expected_path, joined_path.ptr); + + git_str_dispose(&joined_path); +} + +static void +check_joinpath_n( + const char *path_a, + const char *path_b, + const char *path_c, + const char *path_d, + const char *expected_path) +{ + git_str joined_path = GIT_STR_INIT; + + cl_git_pass(git_str_join_n(&joined_path, '/', 4, + path_a, path_b, path_c, path_d)); + cl_assert_equal_s(expected_path, joined_path.ptr); + + git_str_dispose(&joined_path); +} + +static void check_setenv(const char* name, const char* value) +{ + char* check; + + cl_git_pass(cl_setenv(name, value)); + check = cl_getenv(name); + + if (value) + cl_assert_equal_s(value, check); + else + cl_assert(check == NULL); + + git__free(check); +} + +/* get the dirname of a path */ +void test_path__00_dirname(void) +{ + check_dirname(NULL, "."); + check_dirname("", "."); + check_dirname("a", "."); + check_dirname("/", "/"); + check_dirname("/usr", "/"); + check_dirname("/usr/", "/"); + check_dirname("/usr/lib", "/usr"); + check_dirname("/usr/lib/", "/usr"); + check_dirname("/usr/lib//", "/usr"); + check_dirname("usr/lib", "usr"); + check_dirname("usr/lib/", "usr"); + check_dirname("usr/lib//", "usr"); + check_dirname(".git/", "."); + + check_dirname(REP16("/abc"), REP15("/abc")); + +#ifdef GIT_WIN32 + check_dirname("C:/", "C:/"); + check_dirname("C:", "C:/"); + check_dirname("C:/path/", "C:/"); + check_dirname("C:/path", "C:/"); + check_dirname("//computername/", "//computername/"); + check_dirname("//computername", "//computername/"); + check_dirname("//computername/path/", "//computername/"); + check_dirname("//computername/path", "//computername/"); + check_dirname("//computername/sub/path/", "//computername/sub"); + check_dirname("//computername/sub/path", "//computername/sub"); +#endif +} + +/* get the base name of a path */ +void test_path__01_basename(void) +{ + check_basename(NULL, "."); + check_basename("", "."); + check_basename("a", "a"); + check_basename("/", "/"); + check_basename("/usr", "usr"); + check_basename("/usr/", "usr"); + check_basename("/usr/lib", "lib"); + check_basename("/usr/lib//", "lib"); + check_basename("usr/lib", "lib"); + + check_basename(REP16("/abc"), "abc"); + check_basename(REP1024("/abc"), "abc"); +} + +/* properly join path components */ +void test_path__05_joins(void) +{ + check_joinpath("", "", ""); + check_joinpath("", "a", "a"); + check_joinpath("", "/a", "/a"); + check_joinpath("a", "", "a/"); + check_joinpath("a", "/", "a/"); + check_joinpath("a", "b", "a/b"); + check_joinpath("/", "a", "/a"); + check_joinpath("/", "", "/"); + check_joinpath("/a", "/b", "/a/b"); + check_joinpath("/a", "/b/", "/a/b/"); + check_joinpath("/a/", "b/", "/a/b/"); + check_joinpath("/a/", "/b/", "/a/b/"); + + check_joinpath("/abcd", "/defg", "/abcd/defg"); + check_joinpath("/abcd", "/defg/", "/abcd/defg/"); + check_joinpath("/abcd/", "defg/", "/abcd/defg/"); + check_joinpath("/abcd/", "/defg/", "/abcd/defg/"); + + check_joinpath("/abcdefgh", "/12345678", "/abcdefgh/12345678"); + check_joinpath("/abcdefgh", "/12345678/", "/abcdefgh/12345678/"); + check_joinpath("/abcdefgh/", "12345678/", "/abcdefgh/12345678/"); + + check_joinpath(REP1024("aaaa"), "", REP1024("aaaa") "/"); + check_joinpath(REP1024("aaaa/"), "", REP1024("aaaa/")); + check_joinpath(REP1024("/aaaa"), "", REP1024("/aaaa") "/"); + + check_joinpath(REP1024("aaaa"), REP1024("bbbb"), + REP1024("aaaa") "/" REP1024("bbbb")); + check_joinpath(REP1024("/aaaa"), REP1024("/bbbb"), + REP1024("/aaaa") REP1024("/bbbb")); +} + +/* properly join path components for more than one path */ +void test_path__06_long_joins(void) +{ + check_joinpath_n("", "", "", "", ""); + check_joinpath_n("", "a", "", "", "a/"); + check_joinpath_n("a", "", "", "", "a/"); + check_joinpath_n("", "", "", "a", "a"); + check_joinpath_n("a", "b", "", "/c/d/", "a/b/c/d/"); + check_joinpath_n("a", "b", "", "/c/d", "a/b/c/d"); + check_joinpath_n("abcd", "efgh", "ijkl", "mnop", "abcd/efgh/ijkl/mnop"); + check_joinpath_n("abcd/", "efgh/", "ijkl/", "mnop/", "abcd/efgh/ijkl/mnop/"); + check_joinpath_n("/abcd/", "/efgh/", "/ijkl/", "/mnop/", "/abcd/efgh/ijkl/mnop/"); + + check_joinpath_n(REP1024("a"), REP1024("b"), REP1024("c"), REP1024("d"), + REP1024("a") "/" REP1024("b") "/" + REP1024("c") "/" REP1024("d")); + check_joinpath_n(REP1024("/a"), REP1024("/b"), REP1024("/c"), REP1024("/d"), + REP1024("/a") REP1024("/b") + REP1024("/c") REP1024("/d")); +} + + +static void +check_path_to_dir( + const char* path, + const char* expected) +{ + git_str tgt = GIT_STR_INIT; + + git_str_sets(&tgt, path); + cl_git_pass(git_fs_path_to_dir(&tgt)); + cl_assert_equal_s(expected, tgt.ptr); + + git_str_dispose(&tgt); +} + +static void +check_string_to_dir( + const char* path, + size_t maxlen, + const char* expected) +{ + size_t len = strlen(path); + char *buf = git__malloc(len + 2); + cl_assert(buf); + + strncpy(buf, path, len + 2); + + git_fs_path_string_to_dir(buf, maxlen); + + cl_assert_equal_s(expected, buf); + + git__free(buf); +} + +/* convert paths to dirs */ +void test_path__07_path_to_dir(void) +{ + check_path_to_dir("", ""); + check_path_to_dir(".", "./"); + check_path_to_dir("./", "./"); + check_path_to_dir("a/", "a/"); + check_path_to_dir("ab", "ab/"); + /* make sure we try just under and just over an expansion that will + * require a realloc + */ + check_path_to_dir("abcdef", "abcdef/"); + check_path_to_dir("abcdefg", "abcdefg/"); + check_path_to_dir("abcdefgh", "abcdefgh/"); + check_path_to_dir("abcdefghi", "abcdefghi/"); + check_path_to_dir(REP1024("abcd") "/", REP1024("abcd") "/"); + check_path_to_dir(REP1024("abcd"), REP1024("abcd") "/"); + + check_string_to_dir("", 1, ""); + check_string_to_dir(".", 1, "."); + check_string_to_dir(".", 2, "./"); + check_string_to_dir(".", 3, "./"); + check_string_to_dir("abcd", 3, "abcd"); + check_string_to_dir("abcd", 4, "abcd"); + check_string_to_dir("abcd", 5, "abcd/"); + check_string_to_dir("abcd", 6, "abcd/"); +} + +/* join path to itself */ +void test_path__08_self_join(void) +{ + git_str path = GIT_STR_INIT; + size_t asize = 0; + + asize = path.asize; + cl_git_pass(git_str_sets(&path, "/foo")); + cl_assert_equal_s(path.ptr, "/foo"); + cl_assert(asize < path.asize); + + asize = path.asize; + cl_git_pass(git_str_joinpath(&path, path.ptr, "this is a new string")); + cl_assert_equal_s(path.ptr, "/foo/this is a new string"); + cl_assert(asize < path.asize); + + asize = path.asize; + cl_git_pass(git_str_joinpath(&path, path.ptr, "/grow the buffer, grow the buffer, grow the buffer")); + cl_assert_equal_s(path.ptr, "/foo/this is a new string/grow the buffer, grow the buffer, grow the buffer"); + cl_assert(asize < path.asize); + + git_str_dispose(&path); + cl_git_pass(git_str_sets(&path, "/foo/bar")); + + cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "baz")); + cl_assert_equal_s(path.ptr, "/bar/baz"); + + asize = path.asize; + cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "somethinglongenoughtorealloc")); + cl_assert_equal_s(path.ptr, "/baz/somethinglongenoughtorealloc"); + cl_assert(asize < path.asize); + + git_str_dispose(&path); +} + +static void check_percent_decoding(const char *expected_result, const char *input) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git__percent_decode(&buf, input)); + cl_assert_equal_s(expected_result, git_str_cstr(&buf)); + + git_str_dispose(&buf); +} + +void test_path__09_percent_decode(void) +{ + check_percent_decoding("abcd", "abcd"); + check_percent_decoding("a2%", "a2%"); + check_percent_decoding("a2%3", "a2%3"); + check_percent_decoding("a2%%3", "a2%%3"); + check_percent_decoding("a2%3z", "a2%3z"); + check_percent_decoding("a,", "a%2c"); + check_percent_decoding("a21", "a2%31"); + check_percent_decoding("a2%1", "a2%%31"); + check_percent_decoding("a bc ", "a%20bc%20"); + check_percent_decoding("Vicent Mart" "\355", "Vicent%20Mart%ED"); +} + +static void check_fromurl(const char *expected_result, const char *input, int should_fail) +{ + git_str buf = GIT_STR_INIT; + + assert(should_fail || expected_result); + + if (!should_fail) { + cl_git_pass(git_fs_path_fromurl(&buf, input)); + cl_assert_equal_s(expected_result, git_str_cstr(&buf)); + } else + cl_git_fail(git_fs_path_fromurl(&buf, input)); + + git_str_dispose(&buf); +} + +#ifdef GIT_WIN32 +#define ABS_PATH_MARKER "" +#else +#define ABS_PATH_MARKER "/" +#endif + +void test_path__10_fromurl(void) +{ + /* Failing cases */ + check_fromurl(NULL, "a", 1); + check_fromurl(NULL, "http:///c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file://c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file:////c:/Temp%20folder/note.txt", 1); + check_fromurl(NULL, "file:///", 1); + check_fromurl(NULL, "file:////", 1); + check_fromurl(NULL, "file://servername/c:/Temp%20folder/note.txt", 1); + + /* Passing cases */ + check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file:///c:/Temp%20folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file://localhost/c:/Temp%20folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "c:/Temp+folder/note.txt", "file:///c:/Temp+folder/note.txt", 0); + check_fromurl(ABS_PATH_MARKER "a", "file:///a", 0); +} + +typedef struct { + int expect_idx; + int cancel_after; + char **expect; +} check_walkup_info; + +#define CANCEL_VALUE 1234 + +static int check_one_walkup_step(void *ref, const char *path) +{ + check_walkup_info *info = (check_walkup_info *)ref; + + if (!info->cancel_after) { + cl_assert_equal_s(info->expect[info->expect_idx], "[CANCEL]"); + return CANCEL_VALUE; + } + info->cancel_after--; + + cl_assert(info->expect[info->expect_idx] != NULL); + cl_assert_equal_s(info->expect[info->expect_idx], path); + info->expect_idx++; + + return 0; +} + +void test_path__11_walkup(void) +{ + git_str p = GIT_STR_INIT; + + char *expect[] = { + /* 1 */ "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 2 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 3 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 4 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL, + /* 5 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, + /* 6 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL, + /* 7 */ "this_is_a_path", "", NULL, + /* 8 */ "this_is_a_path/", "", NULL, + /* 9 */ "///a///b///c///d///e///", "///a///b///c///d///", "///a///b///c///", "///a///b///", "///a///", "///", NULL, + /* 10 */ "a/b/c/", "a/b/", "a/", "", NULL, + /* 11 */ "a/b/c", "a/b/", "a/", "", NULL, + /* 12 */ "a/b/c/", "a/b/", "a/", NULL, + /* 13 */ "", NULL, + /* 14 */ "/", NULL, + /* 15 */ NULL + }; + + char *root[] = { + /* 1 */ NULL, + /* 2 */ NULL, + /* 3 */ "/", + /* 4 */ "", + /* 5 */ "/a/b", + /* 6 */ "/a/b/", + /* 7 */ NULL, + /* 8 */ NULL, + /* 9 */ NULL, + /* 10 */ NULL, + /* 11 */ NULL, + /* 12 */ "a/", + /* 13 */ NULL, + /* 14 */ NULL, + }; + + int i, j; + check_walkup_info info; + + info.expect = expect; + info.cancel_after = -1; + + for (i = 0, j = 0; expect[i] != NULL; i++, j++) { + + git_str_sets(&p, expect[i]); + + info.expect_idx = i; + cl_git_pass( + git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) + ); + + cl_assert_equal_s(p.ptr, expect[i]); + cl_assert(expect[info.expect_idx] == NULL); + i = info.expect_idx; + } + + git_str_dispose(&p); +} + +void test_path__11a_walkup_cancel(void) +{ + git_str p = GIT_STR_INIT; + int cancel[] = { 3, 2, 1, 0 }; + char *expect[] = { + "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "[CANCEL]", NULL, + "/a/b/c/d/e", "/a/b/c/d/", "[CANCEL]", NULL, + "/a/b/c/d/e", "[CANCEL]", NULL, + "[CANCEL]", NULL, + NULL + }; + char *root[] = { NULL, NULL, "/", "", NULL }; + int i, j; + check_walkup_info info; + + info.expect = expect; + + for (i = 0, j = 0; expect[i] != NULL; i++, j++) { + + git_str_sets(&p, expect[i]); + + info.cancel_after = cancel[j]; + info.expect_idx = i; + + cl_assert_equal_i( + CANCEL_VALUE, + git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info) + ); + + /* skip to next run of expectations */ + while (expect[i] != NULL) i++; + } + + git_str_dispose(&p); +} + +void test_path__12_offset_to_path_root(void) +{ + cl_assert(git_fs_path_root("non/rooted/path") == -1); + cl_assert(git_fs_path_root("/rooted/path") == 0); + +#ifdef GIT_WIN32 + /* Windows specific tests */ + cl_assert(git_fs_path_root("C:non/rooted/path") == -1); + cl_assert(git_fs_path_root("C:/rooted/path") == 2); + cl_assert(git_fs_path_root("//computername/sharefolder/resource") == 14); + cl_assert(git_fs_path_root("//computername/sharefolder") == 14); + cl_assert(git_fs_path_root("//computername") == -1); +#endif +} + +#define NON_EXISTING_FILEPATH "i_hope_i_do_not_exist" + +void test_path__13_cannot_prettify_a_non_existing_file(void) +{ + git_str p = GIT_STR_INIT; + + cl_assert_equal_b(git_fs_path_exists(NON_EXISTING_FILEPATH), false); + cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH, NULL)); + cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH "/so-do-i", NULL)); + + git_str_dispose(&p); +} + +void test_path__14_apply_relative(void) +{ + git_str p = GIT_STR_INIT; + + cl_git_pass(git_str_sets(&p, "/this/is/a/base")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../test")); + cl_assert_equal_s("/this/is/a/test", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../the/./end")); + cl_assert_equal_s("/this/is/the/end", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "./of/this/../the/string")); + cl_assert_equal_s("/this/is/the/end/of/the/string", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../../../../..")); + cl_assert_equal_s("/this/", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../")); + cl_assert_equal_s("/", p.ptr); + + cl_git_fail(git_fs_path_apply_relative(&p, "../../..")); + + + cl_git_pass(git_str_sets(&p, "d:/another/test")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../..")); + cl_assert_equal_s("d:/", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "from/here/to/../and/./back/.")); + cl_assert_equal_s("d:/from/here/and/back/", p.ptr); + + + cl_git_pass(git_str_sets(&p, "https://my.url.com/test.git")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../another.git")); + cl_assert_equal_s("https://my.url.com/another.git", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../full/path/url.patch")); + cl_assert_equal_s("https://my.url.com/full/path/url.patch", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "..")); + cl_assert_equal_s("https://my.url.com/full/path/", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../../")); + cl_assert_equal_s("https://", p.ptr); + + + cl_git_pass(git_str_sets(&p, "../../this/is/relative")); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../preserves/the/prefix")); + cl_assert_equal_s("../../this/preserves/the/prefix", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../../../../that")); + cl_assert_equal_s("../../that", p.ptr); + + cl_git_pass(git_fs_path_apply_relative(&p, "../there")); + cl_assert_equal_s("../../there", p.ptr); + git_str_dispose(&p); +} + +static void assert_resolve_relative( + git_str *buf, const char *expected, const char *path) +{ + cl_git_pass(git_str_sets(buf, path)); + cl_git_pass(git_fs_path_resolve_relative(buf, 0)); + cl_assert_equal_s(expected, buf->ptr); +} + +void test_path__15_resolve_relative(void) +{ + git_str buf = GIT_STR_INIT; + + assert_resolve_relative(&buf, "", ""); + assert_resolve_relative(&buf, "", "."); + assert_resolve_relative(&buf, "", "./"); + assert_resolve_relative(&buf, "..", ".."); + assert_resolve_relative(&buf, "../", "../"); + assert_resolve_relative(&buf, "..", "./.."); + assert_resolve_relative(&buf, "../", "./../"); + assert_resolve_relative(&buf, "../", "../."); + assert_resolve_relative(&buf, "../", ".././"); + assert_resolve_relative(&buf, "../..", "../.."); + assert_resolve_relative(&buf, "../../", "../../"); + + assert_resolve_relative(&buf, "/", "/"); + assert_resolve_relative(&buf, "/", "/."); + + assert_resolve_relative(&buf, "", "a/.."); + assert_resolve_relative(&buf, "", "a/../"); + assert_resolve_relative(&buf, "", "a/../."); + + assert_resolve_relative(&buf, "/a", "/a"); + assert_resolve_relative(&buf, "/a/", "/a/."); + assert_resolve_relative(&buf, "/", "/a/../"); + assert_resolve_relative(&buf, "/", "/a/../."); + assert_resolve_relative(&buf, "/", "/a/.././"); + + assert_resolve_relative(&buf, "a", "a"); + assert_resolve_relative(&buf, "a/", "a/"); + assert_resolve_relative(&buf, "a/", "a/."); + assert_resolve_relative(&buf, "a/", "a/./"); + + assert_resolve_relative(&buf, "a/b", "a//b"); + assert_resolve_relative(&buf, "a/b/c", "a/b/c"); + assert_resolve_relative(&buf, "b/c", "./b/c"); + assert_resolve_relative(&buf, "a/c", "a/./c"); + assert_resolve_relative(&buf, "a/b/", "a/b/."); + + assert_resolve_relative(&buf, "/a/b/c", "///a/b/c"); + assert_resolve_relative(&buf, "/", "////"); + assert_resolve_relative(&buf, "/a", "///a"); + assert_resolve_relative(&buf, "/", "///."); + assert_resolve_relative(&buf, "/", "///a/.."); + + assert_resolve_relative(&buf, "../../path", "../../test//../././path"); + assert_resolve_relative(&buf, "../d", "a/b/../../../c/../d"); + + cl_git_pass(git_str_sets(&buf, "/..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/./..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/.//..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/../.")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "/../.././../a")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + cl_git_pass(git_str_sets(&buf, "////..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); + + /* things that start with Windows network paths */ +#ifdef GIT_WIN32 + assert_resolve_relative(&buf, "//a/b/c", "//a/b/c"); + assert_resolve_relative(&buf, "//a/", "//a/b/.."); + assert_resolve_relative(&buf, "//a/b/c", "//a/Q/../b/x/y/../../c"); + + cl_git_pass(git_str_sets(&buf, "//a/b/../..")); + cl_git_fail(git_fs_path_resolve_relative(&buf, 0)); +#else + assert_resolve_relative(&buf, "/a/b/c", "//a/b/c"); + assert_resolve_relative(&buf, "/a/", "//a/b/.."); + assert_resolve_relative(&buf, "/a/b/c", "//a/Q/../b/x/y/../../c"); + assert_resolve_relative(&buf, "/", "//a/b/../.."); +#endif + + git_str_dispose(&buf); +} + +#define assert_common_dirlen(i, p, q) \ + cl_assert_equal_i((i), git_fs_path_common_dirlen((p), (q))); + +void test_path__16_resolve_relative(void) +{ + assert_common_dirlen(0, "", ""); + assert_common_dirlen(0, "", "bar.txt"); + assert_common_dirlen(0, "foo.txt", "bar.txt"); + assert_common_dirlen(0, "foo.txt", ""); + assert_common_dirlen(0, "foo/bar.txt", "bar/foo.txt"); + assert_common_dirlen(0, "foo/bar.txt", "../foo.txt"); + + assert_common_dirlen(1, "/one.txt", "/two.txt"); + assert_common_dirlen(4, "foo/one.txt", "foo/two.txt"); + assert_common_dirlen(5, "/foo/one.txt", "/foo/two.txt"); + + assert_common_dirlen(6, "a/b/c/foo.txt", "a/b/c/d/e/bar.txt"); + assert_common_dirlen(7, "/a/b/c/foo.txt", "/a/b/c/d/e/bar.txt"); +} + +static void fix_path(git_str *s) +{ +#ifndef GIT_WIN32 + GIT_UNUSED(s); +#else + char* c; + + for (c = s->ptr; *c; c++) { + if (*c == '/') + *c = '\\'; + } +#endif +} + +void test_path__find_exe_in_path(void) +{ + char *orig_path; + git_str sandbox_path = GIT_STR_INIT; + git_str new_path = GIT_STR_INIT, full_path = GIT_STR_INIT, + dummy_path = GIT_STR_INIT; + +#ifdef GIT_WIN32 + static const char *bogus_path_1 = "c:\\does\\not\\exist\\"; + static const char *bogus_path_2 = "e:\\non\\existent"; +#else + static const char *bogus_path_1 = "/this/path/does/not/exist/"; + static const char *bogus_path_2 = "/non/existent"; +#endif + + orig_path = cl_getenv("PATH"); + + git_str_puts(&sandbox_path, clar_sandbox_path()); + git_str_joinpath(&dummy_path, sandbox_path.ptr, "dummmmmmmy_libgit2_file"); + cl_git_rewritefile(dummy_path.ptr, "this is a dummy file"); + + fix_path(&sandbox_path); + fix_path(&dummy_path); + + cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s", + bogus_path_1, GIT_PATH_LIST_SEPARATOR, + orig_path, GIT_PATH_LIST_SEPARATOR, + sandbox_path.ptr, GIT_PATH_LIST_SEPARATOR, + bogus_path_2)); + + check_setenv("PATH", new_path.ptr); + + cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "this_file_does_not_exist")); + cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file")); + + cl_assert_equal_s(full_path.ptr, dummy_path.ptr); + + git_str_dispose(&full_path); + git_str_dispose(&new_path); + git_str_dispose(&dummy_path); + git_str_dispose(&sandbox_path); + git__free(orig_path); +} diff --git a/tests/util/path/core.c b/tests/util/path/core.c new file mode 100644 index 000000000..f30f6c01b --- /dev/null +++ b/tests/util/path/core.c @@ -0,0 +1,343 @@ +#include "clar_libgit2.h" +#include "fs_path.h" + +static void test_make_relative( + const char *expected_path, + const char *path, + const char *parent, + int expected_status) +{ + git_str buf = GIT_STR_INIT; + git_str_puts(&buf, path); + cl_assert_equal_i(expected_status, git_fs_path_make_relative(&buf, parent)); + cl_assert_equal_s(expected_path, buf.ptr); + git_str_dispose(&buf); +} + +void test_path_core__make_relative(void) +{ + test_make_relative("foo.c", "/path/to/foo.c", "/path/to", 0); + test_make_relative("bar/foo.c", "/path/to/bar/foo.c", "/path/to", 0); + test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0); + + test_make_relative("", "/path/to", "/path/to", 0); + test_make_relative("", "/path/to", "/path/to/", 0); + + test_make_relative("../", "/path/to", "/path/to/foo", 0); + + test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar", 0); + test_make_relative("../bar/foo.c", "/path/to/bar/foo.c", "/path/to/baz", 0); + + test_make_relative("../../foo.c", "/path/to/foo.c", "/path/to/foo/bar", 0); + test_make_relative("../../foo/bar.c", "/path/to/foo/bar.c", "/path/to/bar/foo", 0); + + test_make_relative("../../foo.c", "/foo.c", "/bar/foo", 0); + + test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0); + test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar/", 0); + + test_make_relative("foo.c", "d:/path/to/foo.c", "d:/path/to", 0); + + test_make_relative("../foo", "/foo", "/bar", 0); + test_make_relative("path/to/foo.c", "/path/to/foo.c", "/", 0); + test_make_relative("../foo", "path/to/foo", "path/to/bar", 0); + + test_make_relative("/path/to/foo.c", "/path/to/foo.c", "d:/path/to", GIT_ENOTFOUND); + test_make_relative("d:/path/to/foo.c", "d:/path/to/foo.c", "/path/to", GIT_ENOTFOUND); + + test_make_relative("/path/to/foo.c", "/path/to/foo.c", "not-a-rooted-path", GIT_ENOTFOUND); + test_make_relative("not-a-rooted-path", "not-a-rooted-path", "/path/to", GIT_ENOTFOUND); + + test_make_relative("/path", "/path", "pathtofoo", GIT_ENOTFOUND); + test_make_relative("path", "path", "pathtofoo", GIT_ENOTFOUND); +} + +void test_path_core__isvalid_standard(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/file.txt", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/.file", 0)); +} + +/* Ensure that `is_valid_str` only reads str->size bytes */ +void test_path_core__isvalid_standard_str(void) +{ + git_str str = GIT_STR_INIT_CONST("foo/bar//zap", 0); + unsigned int flags = GIT_FS_PATH_REJECT_EMPTY_COMPONENT; + + str.size = 0; + cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); + + str.size = 3; + cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags)); + + str.size = 4; + cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); + + str.size = 5; + cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags)); + + str.size = 7; + cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags)); + + str.size = 8; + cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); + + str.size = strlen(str.ptr); + cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags)); +} + +void test_path_core__isvalid_empty_dir_component(void) +{ + unsigned int flags = GIT_FS_PATH_REJECT_EMPTY_COMPONENT; + + /* empty component */ + cl_assert_equal_b(true, git_fs_path_is_valid("foo//bar", 0)); + + /* leading slash */ + cl_assert_equal_b(true, git_fs_path_is_valid("/", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("/foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("/foo/bar", 0)); + + /* trailing slash */ + cl_assert_equal_b(true, git_fs_path_is_valid("foo/", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/", 0)); + + + /* empty component */ + cl_assert_equal_b(false, git_fs_path_is_valid("foo//bar", flags)); + + /* leading slash */ + cl_assert_equal_b(false, git_fs_path_is_valid("/", flags)); + cl_assert_equal_b(false, git_fs_path_is_valid("/foo", flags)); + cl_assert_equal_b(false, git_fs_path_is_valid("/foo/bar", flags)); + + /* trailing slash */ + cl_assert_equal_b(false, git_fs_path_is_valid("foo/", flags)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar/", flags)); +} + +void test_path_core__isvalid_dot_and_dotdot(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid(".", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("./foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/.", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("./foo", 0)); + + cl_assert_equal_b(true, git_fs_path_is_valid("..", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("../foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/..", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("../foo", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid(".", GIT_FS_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_fs_path_is_valid("./foo", GIT_FS_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/.", GIT_FS_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_fs_path_is_valid("./foo", GIT_FS_PATH_REJECT_TRAVERSAL)); + + cl_assert_equal_b(false, git_fs_path_is_valid("..", GIT_FS_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_fs_path_is_valid("../foo", GIT_FS_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/..", GIT_FS_PATH_REJECT_TRAVERSAL)); + cl_assert_equal_b(false, git_fs_path_is_valid("../foo", GIT_FS_PATH_REJECT_TRAVERSAL)); +} + +void test_path_core__isvalid_backslash(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("foo\\file.txt", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar\\file.txt", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar\\", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("foo\\file.txt", GIT_FS_PATH_REJECT_BACKSLASH)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar\\file.txt", GIT_FS_PATH_REJECT_BACKSLASH)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar\\", GIT_FS_PATH_REJECT_BACKSLASH)); +} + +void test_path_core__isvalid_trailing_dot(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("foo.", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo...", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar.", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo./bar", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("foo.", GIT_FS_PATH_REJECT_TRAILING_DOT)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo...", GIT_FS_PATH_REJECT_TRAILING_DOT)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar.", GIT_FS_PATH_REJECT_TRAILING_DOT)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo./bar", GIT_FS_PATH_REJECT_TRAILING_DOT)); +} + +void test_path_core__isvalid_trailing_space(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("foo ", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo ", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar ", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid(" ", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo /bar", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("foo ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); + cl_assert_equal_b(false, git_fs_path_is_valid(" ", GIT_FS_PATH_REJECT_TRAILING_SPACE)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo /bar", GIT_FS_PATH_REJECT_TRAILING_SPACE)); +} + +void test_path_core__isvalid_trailing_colon(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("foo:", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar:", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid(":", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("foo:/bar", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("foo:", GIT_FS_PATH_REJECT_TRAILING_COLON)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar:", GIT_FS_PATH_REJECT_TRAILING_COLON)); + cl_assert_equal_b(false, git_fs_path_is_valid(":", GIT_FS_PATH_REJECT_TRAILING_COLON)); + cl_assert_equal_b(false, git_fs_path_is_valid("foo:/bar", GIT_FS_PATH_REJECT_TRAILING_COLON)); +} + +void test_path_core__isvalid_dos_paths(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("aux", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux.", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux:", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux.asdf", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux.asdf\\zippy", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux:asdf\\foobar", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("con", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("prn", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("nul", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("aux", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("aux.", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("aux:", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("aux.asdf", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("aux.asdf\\zippy", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("aux:asdf\\foobar", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("con", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("prn", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("nul", GIT_FS_PATH_REJECT_DOS_PATHS)); + + cl_assert_equal_b(true, git_fs_path_is_valid("aux1", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux1", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("auxn", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("aux\\foo", GIT_FS_PATH_REJECT_DOS_PATHS)); +} + +void test_path_core__isvalid_dos_paths_withnum(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("com1", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1.", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1:", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1.asdf", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1.asdf\\zippy", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1:asdf\\foobar", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1\\foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("lpt1", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("com1", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("com1.", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("com1:", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("com1.asdf", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("com1.asdf\\zippy", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("com1:asdf\\foobar", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("com1/foo", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(false, git_fs_path_is_valid("lpt1", GIT_FS_PATH_REJECT_DOS_PATHS)); + + cl_assert_equal_b(true, git_fs_path_is_valid("com0", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com0", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("com10", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("com10", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("comn", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("com1\\foo", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("lpt0", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("lpt10", GIT_FS_PATH_REJECT_DOS_PATHS)); + cl_assert_equal_b(true, git_fs_path_is_valid("lptn", GIT_FS_PATH_REJECT_DOS_PATHS)); +} + +void test_path_core__isvalid_nt_chars(void) +{ + cl_assert_equal_b(true, git_fs_path_is_valid("asdf\001foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdf\037bar", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdffoo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdf:foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdf\"bar", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdf|foo", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdf?bar", 0)); + cl_assert_equal_b(true, git_fs_path_is_valid("asdf*bar", 0)); + + cl_assert_equal_b(false, git_fs_path_is_valid("asdf\001foo", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdf\037bar", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdffoo", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdf:foo", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdf\"bar", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdf|foo", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdf?bar", GIT_FS_PATH_REJECT_NT_CHARS)); + cl_assert_equal_b(false, git_fs_path_is_valid("asdf*bar", GIT_FS_PATH_REJECT_NT_CHARS)); +} + +static void test_join_unrooted( + const char *expected_result, + ssize_t expected_rootlen, + const char *path, + const char *base) +{ + git_str result = GIT_STR_INIT; + ssize_t root_at; + + cl_git_pass(git_fs_path_join_unrooted(&result, path, base, &root_at)); + cl_assert_equal_s(expected_result, result.ptr); + cl_assert_equal_i(expected_rootlen, root_at); + + git_str_dispose(&result); +} + +void test_path_core__join_unrooted(void) +{ + git_str out = GIT_STR_INIT; + + test_join_unrooted("foo", 0, "foo", NULL); + test_join_unrooted("foo/bar", 0, "foo/bar", NULL); + + /* Relative paths have base prepended */ + test_join_unrooted("/foo/bar", 4, "bar", "/foo"); + test_join_unrooted("/foo/bar/foobar", 4, "bar/foobar", "/foo"); + test_join_unrooted("c:/foo/bar/foobar", 6, "bar/foobar", "c:/foo"); + test_join_unrooted("c:/foo/bar/foobar", 10, "foobar", "c:/foo/bar"); + + /* Absolute paths are not prepended with base */ + test_join_unrooted("/foo", 0, "/foo", "/asdf"); + test_join_unrooted("/foo/bar", 0, "/foo/bar", "/asdf"); + + /* Drive letter is given as root length on Windows */ + test_join_unrooted("c:/foo", 2, "c:/foo", "c:/asdf"); + test_join_unrooted("c:/foo/bar", 2, "c:/foo/bar", "c:/asdf"); + +#ifdef GIT_WIN32 + /* Paths starting with '\\' are absolute */ + test_join_unrooted("\\bar", 0, "\\bar", "c:/foo/"); + test_join_unrooted("\\\\network\\bar", 9, "\\\\network\\bar", "c:/foo/"); +#else + /* Paths starting with '\\' are not absolute on non-Windows systems */ + test_join_unrooted("/foo/\\bar", 4, "\\bar", "/foo"); + test_join_unrooted("c:/foo/\\bar", 7, "\\bar", "c:/foo/"); +#endif + + /* Base is returned when it's provided and is the prefix */ + test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo"); + test_join_unrooted("c:/foo/bar/foobar", 10, "c:/foo/bar/foobar", "c:/foo/bar"); + + /* Trailing slash in the base is ignored */ + test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo/"); + + git_str_dispose(&out); +} + +void test_path_core__join_unrooted_respects_funny_windows_roots(void) +{ + test_join_unrooted("💩:/foo/bar/foobar", 9, "bar/foobar", "💩:/foo"); + test_join_unrooted("💩:/foo/bar/foobar", 13, "foobar", "💩:/foo/bar"); + test_join_unrooted("💩:/foo", 5, "💩:/foo", "💩:/asdf"); + test_join_unrooted("💩:/foo/bar", 5, "💩:/foo/bar", "💩:/asdf"); + test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo"); + test_join_unrooted("💩:/foo/bar/foobar", 13, "💩:/foo/bar/foobar", "💩:/foo/bar"); + test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo/"); +} diff --git a/tests/util/path/win32.c b/tests/util/path/win32.c new file mode 100644 index 000000000..1aaf6867a --- /dev/null +++ b/tests/util/path/win32.c @@ -0,0 +1,282 @@ + +#include "clar_libgit2.h" + +#ifdef GIT_WIN32 +#include "win32/path_w32.h" +#endif + +#ifdef GIT_WIN32 +static void test_utf8_to_utf16(const char *utf8_in, const wchar_t *utf16_expected) +{ + git_win32_path path_utf16; + int path_utf16len; + + cl_assert((path_utf16len = git_win32_path_from_utf8(path_utf16, utf8_in)) >= 0); + cl_assert_equal_wcs(utf16_expected, path_utf16); + cl_assert_equal_i(wcslen(utf16_expected), path_utf16len); +} + +static void test_utf8_to_utf16_relative(const char* utf8_in, const wchar_t* utf16_expected) +{ + git_win32_path path_utf16; + int path_utf16len; + + cl_assert((path_utf16len = git_win32_path_relative_from_utf8(path_utf16, utf8_in)) >= 0); + cl_assert_equal_wcs(utf16_expected, path_utf16); + cl_assert_equal_i(wcslen(utf16_expected), path_utf16len); +} +#endif + +void test_path_win32__utf8_to_utf16(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("C:\\", L"\\\\?\\C:\\"); + test_utf8_to_utf16("c:\\", L"\\\\?\\c:\\"); + test_utf8_to_utf16("C:/", L"\\\\?\\C:\\"); + test_utf8_to_utf16("c:/", L"\\\\?\\c:\\"); +#endif +} + +void test_path_win32__removes_trailing_slash(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("C:\\Foo\\", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:/Foo/", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:/Foo///", L"\\\\?\\C:\\Foo"); +#endif +} + +void test_path_win32__squashes_multiple_slashes(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("C:\\\\Foo\\Bar\\\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); + test_utf8_to_utf16("C://Foo/Bar///Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); +#endif +} + +void test_path_win32__unc(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path"); + test_utf8_to_utf16("//server/git/style/unc/path", L"\\\\?\\UNC\\server\\git\\style\\unc\\path"); +#endif +} + +void test_path_win32__honors_max_path(void) +{ +#ifdef GIT_WIN32 + git_win32_path path_utf16; + + test_utf8_to_utf16("C:\\This path is 261 characters which is fine for our path handling functions which cope with paths longer than MAX_PATH\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghijk", + L"\\\\?\\C:\\This path is 261 characters which is fine for our path handling functions which cope with paths longer than MAX_PATH\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghijk"); + + cl_check_fail(git_win32_path_from_utf8(path_utf16, "C:\\This path is 4097 chars and exceeds our maximum path length on Windows which is limited to 4096 characters\\alas\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij01")); + +#endif +} + +void test_path_win32__dot_and_dotdot(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("C:\\Foo\\..\\Foobar", L"\\\\?\\C:\\Foobar"); + test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar", L"\\\\?\\C:\\Foo\\Foobar"); + test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar\\..", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:\\Foobar\\..", L"\\\\?\\C:\\"); + test_utf8_to_utf16("C:/Foo/Bar/../Foobar", L"\\\\?\\C:\\Foo\\Foobar"); + test_utf8_to_utf16("C:/Foo/Bar/../Foobar/../Asdf/", L"\\\\?\\C:\\Foo\\Asdf"); + test_utf8_to_utf16("C:/Foo/Bar/../Foobar/..", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("C:/Foo/..", L"\\\\?\\C:\\"); + + test_utf8_to_utf16("C:\\Foo\\Bar\\.\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); + test_utf8_to_utf16("C:\\.\\Foo\\.\\Bar\\.\\Foobar\\.\\", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); + test_utf8_to_utf16("C:/Foo/Bar/./Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar"); + test_utf8_to_utf16("C:/Foo/../Bar/./Foobar/../", L"\\\\?\\C:\\Bar"); + + test_utf8_to_utf16("C:\\Foo\\..\\..\\Bar", L"\\\\?\\C:\\Bar"); +#endif +} + +void test_path_win32__absolute_from_no_drive_letter(void) +{ +#ifdef GIT_WIN32 + test_utf8_to_utf16("\\Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar"); + test_utf8_to_utf16("/Foo/Bar", L"\\\\?\\C:\\Foo\\Bar"); +#endif +} + +void test_path_win32__absolute_from_relative(void) +{ +#ifdef GIT_WIN32 + char cwd_backup[MAX_PATH]; + + cl_must_pass(p_getcwd(cwd_backup, MAX_PATH)); + cl_must_pass(p_chdir("C:/")); + + test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("..\\..\\Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("Foo\\..", L"\\\\?\\C:\\"); + test_utf8_to_utf16("Foo\\..\\..", L"\\\\?\\C:\\"); + test_utf8_to_utf16("", L"\\\\?\\C:\\"); + + cl_must_pass(p_chdir("C:/Windows")); + + test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Windows\\Foo"); + test_utf8_to_utf16("Foo\\Bar", L"\\\\?\\C:\\Windows\\Foo\\Bar"); + test_utf8_to_utf16("..\\Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16("Foo\\..\\Bar", L"\\\\?\\C:\\Windows\\Bar"); + test_utf8_to_utf16("", L"\\\\?\\C:\\Windows"); + + cl_must_pass(p_chdir(cwd_backup)); +#endif +} + +void test_path_win32__keeps_relative(void) +{ +#ifdef GIT_WIN32 + /* Relative paths stay relative */ + test_utf8_to_utf16_relative("Foo", L"Foo"); + test_utf8_to_utf16_relative("..\\..\\Foo", L"..\\..\\Foo"); + test_utf8_to_utf16_relative("Foo\\..", L"Foo\\.."); + test_utf8_to_utf16_relative("Foo\\..\\..", L"Foo\\..\\.."); + test_utf8_to_utf16_relative("Foo\\Bar", L"Foo\\Bar"); + test_utf8_to_utf16_relative("Foo\\..\\Bar", L"Foo\\..\\Bar"); + test_utf8_to_utf16_relative("../../Foo", L"..\\..\\Foo"); + test_utf8_to_utf16_relative("Foo/..", L"Foo\\.."); + test_utf8_to_utf16_relative("Foo/../..", L"Foo\\..\\.."); + test_utf8_to_utf16_relative("Foo/Bar", L"Foo\\Bar"); + test_utf8_to_utf16_relative("Foo/../Bar", L"Foo\\..\\Bar"); + test_utf8_to_utf16_relative("Foo/../Bar/", L"Foo\\..\\Bar\\"); + test_utf8_to_utf16_relative("", L""); + + /* Absolute paths are canonicalized */ + test_utf8_to_utf16_relative("\\Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16_relative("/Foo/Bar/", L"\\\\?\\C:\\Foo\\Bar"); + test_utf8_to_utf16_relative("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path"); +#endif +} + +#ifdef GIT_WIN32 +static void test_canonicalize(const wchar_t *in, const wchar_t *expected) +{ + git_win32_path canonical; + + cl_assert(wcslen(in) < MAX_PATH); + wcscpy(canonical, in); + + cl_must_pass(git_win32_path_canonicalize(canonical)); + cl_assert_equal_wcs(expected, canonical); +} +#endif + +static void test_remove_namespace(const wchar_t *in, const wchar_t *expected) +{ +#ifdef GIT_WIN32 + git_win32_path canonical; + + cl_assert(wcslen(in) < MAX_PATH); + wcscpy(canonical, in); + + git_win32_path_remove_namespace(canonical, wcslen(in)); + cl_assert_equal_wcs(expected, canonical); +#else + GIT_UNUSED(in); + GIT_UNUSED(expected); +#endif +} + +void test_path_win32__remove_namespace(void) +{ + test_remove_namespace(L"\\\\?\\C:\\Temp\\Foo", L"C:\\Temp\\Foo"); + test_remove_namespace(L"\\\\?\\C:\\", L"C:\\"); + test_remove_namespace(L"\\\\?\\", L""); + + test_remove_namespace(L"\\??\\C:\\Temp\\Foo", L"C:\\Temp\\Foo"); + test_remove_namespace(L"\\??\\C:\\", L"C:\\"); + test_remove_namespace(L"\\??\\", L""); + + test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\\\?\\UNC\\server\\C$", L"\\\\server\\C$"); + test_remove_namespace(L"\\\\?\\UNC\\server\\", L"\\\\server"); + test_remove_namespace(L"\\\\?\\UNC\\server", L"\\\\server"); + + test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\??\\UNC\\server\\C$", L"\\\\server\\C$"); + test_remove_namespace(L"\\??\\UNC\\server\\", L"\\\\server"); + test_remove_namespace(L"\\??\\UNC\\server", L"\\\\server"); + + test_remove_namespace(L"\\\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\\\server\\C$", L"\\\\server\\C$"); + test_remove_namespace(L"\\\\server\\", L"\\\\server"); + test_remove_namespace(L"\\\\server", L"\\\\server"); + + test_remove_namespace(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar"); + test_remove_namespace(L"C:\\", L"C:\\"); + test_remove_namespace(L"", L""); + +} + +void test_path_win32__canonicalize(void) +{ +#ifdef GIT_WIN32 + test_canonicalize(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar"); + test_canonicalize(L"C:\\Foo\\", L"C:\\Foo"); + test_canonicalize(L"C:\\Foo\\\\", L"C:\\Foo"); + test_canonicalize(L"C:\\Foo\\..\\Bar", L"C:\\Bar"); + test_canonicalize(L"C:\\Foo\\..\\..\\Bar", L"C:\\Bar"); + test_canonicalize(L"C:\\Foo\\..\\..\\..\\..\\", L"C:\\"); + test_canonicalize(L"C:/Foo/Bar", L"C:\\Foo\\Bar"); + test_canonicalize(L"C:/", L"C:\\"); + + test_canonicalize(L"\\\\?\\C:\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar"); + test_canonicalize(L"\\\\?\\C:\\Foo\\Bar\\", L"\\\\?\\C:\\Foo\\Bar"); + test_canonicalize(L"\\\\?\\C:\\\\Foo\\.\\Bar\\\\..\\", L"\\\\?\\C:\\Foo"); + test_canonicalize(L"\\\\?\\C:\\\\", L"\\\\?\\C:\\"); + test_canonicalize(L"//?/C:/", L"\\\\?\\C:\\"); + test_canonicalize(L"//?/C:/../../Foo/", L"\\\\?\\C:\\Foo"); + test_canonicalize(L"//?/C:/Foo/../../", L"\\\\?\\C:\\"); + + test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\?\\UNC\\server\\C$\\folder"); + test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder"); + test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder"); + test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\..\\..\\..\\..\\share\\", L"\\\\?\\UNC\\server\\share"); + + test_canonicalize(L"\\\\server\\share", L"\\\\server\\share"); + test_canonicalize(L"\\\\server\\share\\", L"\\\\server\\share"); + test_canonicalize(L"\\\\server\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar"); + test_canonicalize(L"\\\\server\\\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar"); + test_canonicalize(L"\\\\server\\share\\..\\foo", L"\\\\server\\foo"); + test_canonicalize(L"\\\\server\\..\\..\\share\\.\\foo", L"\\\\server\\share\\foo"); +#endif +} + +void test_path_win32__8dot3_name(void) +{ +#ifdef GIT_WIN32 + char *shortname; + + if (!cl_sandbox_supports_8dot3()) + clar__skip(); + + /* Some guaranteed short names */ + cl_assert_equal_s("PROGRA~1", (shortname = git_win32_path_8dot3_name("C:\\Program Files"))); + git__free(shortname); + + cl_assert_equal_s("WINDOWS", (shortname = git_win32_path_8dot3_name("C:\\WINDOWS"))); + git__free(shortname); + + /* Create some predictable short names */ + cl_must_pass(p_mkdir(".foo", 0777)); + cl_assert_equal_s("FOO~1", (shortname = git_win32_path_8dot3_name(".foo"))); + git__free(shortname); + + cl_git_write2file("bar~1", "foobar\n", 7, O_RDWR|O_CREAT, 0666); + cl_must_pass(p_mkdir(".bar", 0777)); + cl_assert_equal_s("BAR~2", (shortname = git_win32_path_8dot3_name(".bar"))); + git__free(shortname); +#endif +} diff --git a/tests/util/pool.c b/tests/util/pool.c new file mode 100644 index 000000000..464aad733 --- /dev/null +++ b/tests/util/pool.c @@ -0,0 +1,62 @@ +#include "clar_libgit2.h" +#include "pool.h" +#include "git2/oid.h" + +void test_pool__0(void) +{ + int i; + git_pool p; + void *ptr; + + git_pool_init(&p, 1); + + for (i = 1; i < 10000; i *= 2) { + ptr = git_pool_malloc(&p, i); + cl_assert(ptr != NULL); + cl_assert(git_pool__ptr_in_pool(&p, ptr)); + cl_assert(!git_pool__ptr_in_pool(&p, &i)); + } + + git_pool_clear(&p); +} + +void test_pool__1(void) +{ + int i; + git_pool p; + + git_pool_init(&p, 1); + p.page_size = 4000; + + for (i = 2010; i > 0; i--) + cl_assert(git_pool_malloc(&p, i) != NULL); + +#ifndef GIT_DEBUG_POOL + /* with fixed page size, allocation must end up with these values */ + cl_assert_equal_i(591, git_pool__open_pages(&p)); +#endif + git_pool_clear(&p); + + git_pool_init(&p, 1); + p.page_size = 4120; + + for (i = 2010; i > 0; i--) + cl_assert(git_pool_malloc(&p, i) != NULL); + +#ifndef GIT_DEBUG_POOL + /* with fixed page size, allocation must end up with these values */ + cl_assert_equal_i(sizeof(void *) == 8 ? 575 : 573, git_pool__open_pages(&p)); +#endif + git_pool_clear(&p); +} + +void test_pool__strndup_limit(void) +{ + git_pool p; + + git_pool_init(&p, 1); + /* ensure 64 bit doesn't overflow */ + cl_assert(git_pool_strndup(&p, "foo", (size_t)-1) == NULL); + git_pool_clear(&p); +} + diff --git a/tests/util/posix.c b/tests/util/posix.c new file mode 100644 index 000000000..155f03a95 --- /dev/null +++ b/tests/util/posix.c @@ -0,0 +1,238 @@ +#ifndef _WIN32 +# include +# include +# include +#else +# include +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32") +# endif +#endif + +#include "clar_libgit2.h" +#include "futils.h" +#include "posix.h" + +void test_posix__initialize(void) +{ +#ifdef GIT_WIN32 + /* on win32, the WSA context needs to be initialized + * before any socket calls can be performed */ + WSADATA wsd; + + cl_git_pass(WSAStartup(MAKEWORD(2,2), &wsd)); + cl_assert(LOBYTE(wsd.wVersion) == 2 && HIBYTE(wsd.wVersion) == 2); +#endif +} + +static bool supports_ipv6(void) +{ +#ifdef GIT_WIN32 + /* IPv6 is supported on Vista and newer */ + return git_has_win32_version(6, 0, 0); +#else + return 1; +#endif +} + +void test_posix__inet_pton(void) +{ + struct in_addr addr; + struct in6_addr addr6; + size_t i; + + struct in_addr_data { + const char *p; + const uint8_t n[4]; + }; + + struct in6_addr_data { + const char *p; + const uint8_t n[16]; + }; + + static struct in_addr_data in_addr_data[] = { + { "0.0.0.0", { 0, 0, 0, 0 } }, + { "10.42.101.8", { 10, 42, 101, 8 } }, + { "127.0.0.1", { 127, 0, 0, 1 } }, + { "140.177.10.12", { 140, 177, 10, 12 } }, + { "204.232.175.90", { 204, 232, 175, 90 } }, + { "255.255.255.255", { 255, 255, 255, 255 } }, + }; + + static struct in6_addr_data in6_addr_data[] = { + { "::", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { "::1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, + { "0:0:0:0:0:0:0:1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }, + { "2001:db8:8714:3a90::12", { 0x20, 0x01, 0x0d, 0xb8, 0x87, 0x14, 0x3a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12 } }, + { "fe80::f8ba:c2d6:86be:3645", { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xba, 0xc2, 0xd6, 0x86, 0xbe, 0x36, 0x45 } }, + { "::ffff:204.152.189.116", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcc, 0x98, 0xbd, 0x74 } }, + }; + + /* Test some ipv4 addresses */ + for (i = 0; i < 6; i++) { + cl_assert(p_inet_pton(AF_INET, in_addr_data[i].p, &addr) == 1); + cl_assert(memcmp(&addr, in_addr_data[i].n, sizeof(struct in_addr)) == 0); + } + + /* Test some ipv6 addresses */ + if (supports_ipv6()) + { + for (i = 0; i < 6; i++) { + cl_assert(p_inet_pton(AF_INET6, in6_addr_data[i].p, &addr6) == 1); + cl_assert(memcmp(&addr6, in6_addr_data[i].n, sizeof(struct in6_addr)) == 0); + } + } + + /* Test some invalid strings */ + cl_assert(p_inet_pton(AF_INET, "", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, "foo", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, " 127.0.0.1", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, "bar", &addr) == 0); + cl_assert(p_inet_pton(AF_INET, "10.foo.bar.1", &addr) == 0); + + /* Test unsupported address families */ + cl_git_fail(p_inet_pton(INT_MAX-1, "52.472", &addr)); + cl_assert_equal_i(EAFNOSUPPORT, errno); +} + +void test_posix__utimes(void) +{ + struct p_timeval times[2]; + struct stat st; + time_t curtime; + int fd; + + /* test p_utimes */ + times[0].tv_sec = 1234567890; + times[0].tv_usec = 0; + times[1].tv_sec = 1234567890; + times[1].tv_usec = 0; + + cl_git_mkfile("foo", "Dummy file."); + cl_must_pass(p_utimes("foo", times)); + + cl_must_pass(p_stat("foo", &st)); + cl_assert_equal_i(1234567890, st.st_atime); + cl_assert_equal_i(1234567890, st.st_mtime); + + + /* test p_futimes */ + times[0].tv_sec = 1414141414; + times[0].tv_usec = 0; + times[1].tv_sec = 1414141414; + times[1].tv_usec = 0; + + cl_must_pass(fd = p_open("foo", O_RDWR)); + cl_must_pass(p_futimes(fd, times)); + cl_must_pass(p_close(fd)); + + cl_must_pass(p_stat("foo", &st)); + cl_assert_equal_i(1414141414, st.st_atime); + cl_assert_equal_i(1414141414, st.st_mtime); + + + /* test p_utimes with current time, assume that + * it takes < 5 seconds to get the time...! + */ + cl_must_pass(p_utimes("foo", NULL)); + + curtime = time(NULL); + cl_must_pass(p_stat("foo", &st)); + cl_assert((st.st_atime - curtime) < 5); + cl_assert((st.st_mtime - curtime) < 5); + + cl_must_pass(p_unlink("foo")); +} + +void test_posix__unlink_removes_symlink(void) +{ + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + clar__skip(); + + cl_git_mkfile("file", "Dummy file."); + cl_git_pass(git_futils_mkdir("dir", 0777, 0)); + + cl_must_pass(p_symlink("file", "file-symlink")); + cl_must_pass(p_symlink("dir", "dir-symlink")); + + cl_must_pass(p_unlink("file-symlink")); + cl_must_pass(p_unlink("dir-symlink")); + + cl_assert(git_fs_path_exists("file")); + cl_assert(git_fs_path_exists("dir")); + + cl_must_pass(p_unlink("file")); + cl_must_pass(p_rmdir("dir")); +} + +void test_posix__symlink_resolves_to_correct_type(void) +{ + git_str contents = GIT_STR_INIT; + + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + clar__skip(); + + cl_must_pass(git_futils_mkdir("dir", 0777, 0)); + cl_must_pass(git_futils_mkdir("file", 0777, 0)); + cl_git_mkfile("dir/file", "symlink target"); + + cl_git_pass(p_symlink("file", "dir/link")); + + cl_git_pass(git_futils_readbuffer(&contents, "dir/file")); + cl_assert_equal_s(contents.ptr, "symlink target"); + + cl_must_pass(p_unlink("dir/link")); + cl_must_pass(p_unlink("dir/file")); + cl_must_pass(p_rmdir("dir")); + cl_must_pass(p_rmdir("file")); + + git_str_dispose(&contents); +} + +void test_posix__relative_symlink(void) +{ + git_str contents = GIT_STR_INIT; + + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + clar__skip(); + + cl_must_pass(git_futils_mkdir("dir", 0777, 0)); + cl_git_mkfile("file", "contents"); + cl_git_pass(p_symlink("../file", "dir/link")); + cl_git_pass(git_futils_readbuffer(&contents, "dir/link")); + cl_assert_equal_s(contents.ptr, "contents"); + + cl_must_pass(p_unlink("file")); + cl_must_pass(p_unlink("dir/link")); + cl_must_pass(p_rmdir("dir")); + + git_str_dispose(&contents); +} + +void test_posix__symlink_to_file_across_dirs(void) +{ + git_str contents = GIT_STR_INIT; + + if (!git_fs_path_supports_symlinks(clar_sandbox_path())) + clar__skip(); + + /* + * Create a relative symlink that points into another + * directory. This used to not work on Win32, where we + * forgot to convert directory separators to + * Windows-style ones. + */ + cl_must_pass(git_futils_mkdir("dir", 0777, 0)); + cl_git_mkfile("dir/target", "symlink target"); + cl_git_pass(p_symlink("dir/target", "link")); + + cl_git_pass(git_futils_readbuffer(&contents, "dir/target")); + cl_assert_equal_s(contents.ptr, "symlink target"); + + cl_must_pass(p_unlink("dir/target")); + cl_must_pass(p_unlink("link")); + cl_must_pass(p_rmdir("dir")); + + git_str_dispose(&contents); +} diff --git a/tests/util/pqueue.c b/tests/util/pqueue.c new file mode 100644 index 000000000..38931ecfd --- /dev/null +++ b/tests/util/pqueue.c @@ -0,0 +1,150 @@ +#include "clar_libgit2.h" +#include "pqueue.h" + +static int cmp_ints(const void *v1, const void *v2) +{ + int i1 = *(int *)v1, i2 = *(int *)v2; + return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0; +} + +void test_pqueue__items_are_put_in_order(void) +{ + git_pqueue pq; + int i, vals[20]; + + cl_git_pass(git_pqueue_init(&pq, 0, 20, cmp_ints)); + + for (i = 0; i < 20; ++i) { + if (i < 10) + vals[i] = 10 - i; /* 10 down to 1 */ + else + vals[i] = i + 1; /* 11 up to 20 */ + + cl_git_pass(git_pqueue_insert(&pq, &vals[i])); + } + + cl_assert_equal_i(20, git_pqueue_size(&pq)); + + for (i = 1; i <= 20; ++i) { + void *p = git_pqueue_pop(&pq); + cl_assert(p); + cl_assert_equal_i(i, *(int *)p); + } + + cl_assert_equal_i(0, git_pqueue_size(&pq)); + + git_pqueue_free(&pq); +} + +void test_pqueue__interleave_inserts_and_pops(void) +{ + git_pqueue pq; + int chunk, v, i, vals[200]; + + cl_git_pass(git_pqueue_init(&pq, 0, 20, cmp_ints)); + + for (v = 0, chunk = 20; chunk <= 200; chunk += 20) { + /* push the next 20 */ + for (; v < chunk; ++v) { + vals[v] = (v & 1) ? 200 - v : v; + cl_git_pass(git_pqueue_insert(&pq, &vals[v])); + } + + /* pop the lowest 10 */ + for (i = 0; i < 10; ++i) + (void)git_pqueue_pop(&pq); + } + + cl_assert_equal_i(100, git_pqueue_size(&pq)); + + /* at this point, we've popped 0-99 */ + + for (v = 100; v < 200; ++v) { + void *p = git_pqueue_pop(&pq); + cl_assert(p); + cl_assert_equal_i(v, *(int *)p); + } + + cl_assert_equal_i(0, git_pqueue_size(&pq)); + + git_pqueue_free(&pq); +} + +void test_pqueue__max_heap_size(void) +{ + git_pqueue pq; + int i, vals[100]; + + cl_git_pass(git_pqueue_init(&pq, GIT_PQUEUE_FIXED_SIZE, 50, cmp_ints)); + + for (i = 0; i < 100; ++i) { + vals[i] = (i & 1) ? 100 - i : i; + cl_git_pass(git_pqueue_insert(&pq, &vals[i])); + } + + cl_assert_equal_i(50, git_pqueue_size(&pq)); + + for (i = 50; i < 100; ++i) { + void *p = git_pqueue_pop(&pq); + cl_assert(p); + cl_assert_equal_i(i, *(int *)p); + } + + cl_assert_equal_i(0, git_pqueue_size(&pq)); + + git_pqueue_free(&pq); +} + +void test_pqueue__max_heap_size_without_comparison(void) +{ + git_pqueue pq; + int i, vals[100] = { 0 }; + + cl_git_pass(git_pqueue_init(&pq, GIT_PQUEUE_FIXED_SIZE, 50, NULL)); + + for (i = 0; i < 100; ++i) + cl_git_pass(git_pqueue_insert(&pq, &vals[i])); + + cl_assert_equal_i(50, git_pqueue_size(&pq)); + + /* As we have no comparison function, we cannot make any + * actual assumptions about which entries are part of the + * pqueue */ + for (i = 0; i < 50; ++i) + cl_assert(git_pqueue_pop(&pq)); + + cl_assert_equal_i(0, git_pqueue_size(&pq)); + + git_pqueue_free(&pq); +} + +static int cmp_ints_like_commit_time(const void *a, const void *b) +{ + return *((const int *)a) < *((const int *)b); +} + +void test_pqueue__interleaved_pushes_and_pops(void) +{ + git_pqueue pq; + int i, j, *val; + static int commands[] = + { 6, 9, 8, 0, 5, 0, 7, 0, 4, 3, 0, 0, 0, 4, 0, 2, 0, 1, 0, 0, -1 }; + static int expected[] = + { 9, 8, 7, 6, 5, 4, 4, 3, 2, 1, -1 }; + + cl_git_pass(git_pqueue_init(&pq, 0, 10, cmp_ints_like_commit_time)); + + for (i = 0, j = 0; commands[i] >= 0; ++i) { + if (!commands[i]) { + cl_assert((val = git_pqueue_pop(&pq)) != NULL); + cl_assert_equal_i(expected[j], *val); + ++j; + } else { + cl_git_pass(git_pqueue_insert(&pq, &commands[i])); + } + } + + cl_assert_equal_i(0, git_pqueue_size(&pq)); + git_pqueue_free(&pq); +} + diff --git a/tests/util/precompiled.c b/tests/util/precompiled.c new file mode 100644 index 000000000..5f656a45d --- /dev/null +++ b/tests/util/precompiled.c @@ -0,0 +1 @@ +#include "precompiled.h" diff --git a/tests/util/precompiled.h b/tests/util/precompiled.h new file mode 100644 index 000000000..6fa21423b --- /dev/null +++ b/tests/util/precompiled.h @@ -0,0 +1,3 @@ +#include "common.h" +#include "clar.h" +#include "clar_libgit2.h" diff --git a/tests/util/qsort.c b/tests/util/qsort.c new file mode 100644 index 000000000..d8fa20a0a --- /dev/null +++ b/tests/util/qsort.c @@ -0,0 +1,90 @@ +#include "clar_libgit2.h" + +#define assert_sorted(a, cmp) \ + _assert_sorted(a, ARRAY_SIZE(a), sizeof(*a), cmp) + +struct big_entries { + char c[311]; +}; + +static void _assert_sorted(void *els, size_t n, size_t elsize, git__sort_r_cmp cmp) +{ + int8_t *p = els; + + git__qsort_r(p, n, elsize, cmp, NULL); + while (n-- > 1) { + cl_assert(cmp(p, p + elsize, NULL) <= 0); + p += elsize; + } +} + +static int cmp_big(const void *_a, const void *_b, void *payload) +{ + const struct big_entries *a = (const struct big_entries *)_a, *b = (const struct big_entries *)_b; + GIT_UNUSED(payload); + return (a->c[0] < b->c[0]) ? -1 : (a->c[0] > b->c[0]) ? +1 : 0; +} + +static int cmp_int(const void *_a, const void *_b, void *payload) +{ + int a = *(const int *)_a, b = *(const int *)_b; + GIT_UNUSED(payload); + return (a < b) ? -1 : (a > b) ? +1 : 0; +} + +static int cmp_str(const void *_a, const void *_b, void *payload) +{ + GIT_UNUSED(payload); + return strcmp((const char *) _a, (const char *) _b); +} + +void test_qsort__array_with_single_entry(void) +{ + int a[] = { 10 }; + assert_sorted(a, cmp_int); +} + +void test_qsort__array_with_equal_entries(void) +{ + int a[] = { 4, 4, 4, 4 }; + assert_sorted(a, cmp_int); +} + +void test_qsort__sorted_array(void) +{ + int a[] = { 1, 10 }; + assert_sorted(a, cmp_int); +} + +void test_qsort__unsorted_array(void) +{ + int a[] = { 123, 9, 412938, 10, 234, 89 }; + assert_sorted(a, cmp_int); +} + +void test_qsort__sorting_strings(void) +{ + char *a[] = { "foo", "bar", "baz" }; + assert_sorted(a, cmp_str); +} + +void test_qsort__sorting_big_entries(void) +{ + struct big_entries a[5]; + + memset(&a, 0, sizeof(a)); + + memset(a[0].c, 'w', sizeof(a[0].c) - 1); + memset(a[1].c, 'c', sizeof(a[1].c) - 1); + memset(a[2].c, 'w', sizeof(a[2].c) - 1); + memset(a[3].c, 'h', sizeof(a[3].c) - 1); + memset(a[4].c, 'a', sizeof(a[4].c) - 1); + + assert_sorted(a, cmp_big); + + cl_assert_equal_i(strspn(a[0].c, "a"), sizeof(a[0].c) - 1); + cl_assert_equal_i(strspn(a[1].c, "c"), sizeof(a[1].c) - 1); + cl_assert_equal_i(strspn(a[2].c, "h"), sizeof(a[2].c) - 1); + cl_assert_equal_i(strspn(a[3].c, "w"), sizeof(a[3].c) - 1); + cl_assert_equal_i(strspn(a[4].c, "w"), sizeof(a[4].c) - 1); +} diff --git a/tests/util/regexp.c b/tests/util/regexp.c new file mode 100644 index 000000000..a76955d59 --- /dev/null +++ b/tests/util/regexp.c @@ -0,0 +1,197 @@ +#include "clar_libgit2.h" + +#include + +#include "regexp.h" + +#if LC_ALL > 0 +static const char *old_locales[LC_ALL]; +#endif + +static git_regexp regex; + +void test_regexp__initialize(void) +{ +#if LC_ALL > 0 + memset(&old_locales, 0, sizeof(old_locales)); +#endif +} + +void test_regexp__cleanup(void) +{ + git_regexp_dispose(®ex); +} + +static void try_set_locale(int category) +{ +#if LC_ALL > 0 + old_locales[category] = setlocale(category, NULL); +#endif + + if (!setlocale(category, "UTF-8") && + !setlocale(category, "c.utf8") && + !setlocale(category, "en_US.UTF-8")) + cl_skip(); + + if (MB_CUR_MAX == 1) + cl_fail("Expected locale to be switched to multibyte"); +} + + +void test_regexp__compile_ignores_global_locale_ctype(void) +{ + try_set_locale(LC_CTYPE); + cl_git_pass(git_regexp_compile(®ex, "[\xc0-\xff][\x80-\xbf]", 0)); +} + +void test_regexp__compile_ignores_global_locale_collate(void) +{ +#ifdef GIT_WIN32 + cl_skip(); +#endif + + try_set_locale(LC_COLLATE); + cl_git_pass(git_regexp_compile(®ex, "[\xc0-\xff][\x80-\xbf]", 0)); +} + +void test_regexp__regex_matches_digits_with_locale(void) +{ + char c, str[2]; + +#ifdef GIT_WIN32 + cl_skip(); +#endif + + try_set_locale(LC_COLLATE); + try_set_locale(LC_CTYPE); + + cl_git_pass(git_regexp_compile(®ex, "[[:digit:]]", 0)); + + str[1] = '\0'; + for (c = '0'; c <= '9'; c++) { + str[0] = c; + cl_git_pass(git_regexp_match(®ex, str)); + } +} + +void test_regexp__regex_matches_alphabet_with_locale(void) +{ + char c, str[2]; + +#ifdef GIT_WIN32 + cl_skip(); +#endif + + try_set_locale(LC_COLLATE); + try_set_locale(LC_CTYPE); + + cl_git_pass(git_regexp_compile(®ex, "[[:alpha:]]", 0)); + + str[1] = '\0'; + for (c = 'a'; c <= 'z'; c++) { + str[0] = c; + cl_git_pass(git_regexp_match(®ex, str)); + } + for (c = 'A'; c <= 'Z'; c++) { + str[0] = c; + cl_git_pass(git_regexp_match(®ex, str)); + } +} + +void test_regexp__simple_search_matches(void) +{ + cl_git_pass(git_regexp_compile(®ex, "a", 0)); + cl_git_pass(git_regexp_search(®ex, "a", 0, NULL)); +} + +void test_regexp__case_insensitive_search_matches(void) +{ + cl_git_pass(git_regexp_compile(®ex, "a", GIT_REGEXP_ICASE)); + cl_git_pass(git_regexp_search(®ex, "A", 0, NULL)); +} + +void test_regexp__nonmatching_search_returns_error(void) +{ + cl_git_pass(git_regexp_compile(®ex, "a", 0)); + cl_git_fail(git_regexp_search(®ex, "b", 0, NULL)); +} + +void test_regexp__search_finds_complete_match(void) +{ + git_regmatch matches[1]; + + cl_git_pass(git_regexp_compile(®ex, "abc", 0)); + cl_git_pass(git_regexp_search(®ex, "abc", 1, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 3); +} + +void test_regexp__search_finds_correct_offsets(void) +{ + git_regmatch matches[3]; + + cl_git_pass(git_regexp_compile(®ex, "(a*)(b*)", 0)); + cl_git_pass(git_regexp_search(®ex, "ab", 3, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 2); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); + cl_assert_equal_i(matches[2].start, 1); + cl_assert_equal_i(matches[2].end, 2); +} + +void test_regexp__search_finds_empty_group(void) +{ + git_regmatch matches[3]; + + cl_git_pass(git_regexp_compile(®ex, "(a*)(b*)c", 0)); + cl_git_pass(git_regexp_search(®ex, "ac", 3, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 2); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); + cl_assert_equal_i(matches[2].start, 1); + cl_assert_equal_i(matches[2].end, 1); +} + +void test_regexp__search_fills_matches_with_first_matching_groups(void) +{ + git_regmatch matches[2]; + + cl_git_pass(git_regexp_compile(®ex, "(a)(b)(c)", 0)); + cl_git_pass(git_regexp_search(®ex, "abc", 2, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 3); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); +} + +void test_regexp__search_skips_nonmatching_group(void) +{ + git_regmatch matches[4]; + + cl_git_pass(git_regexp_compile(®ex, "(a)(b)?(c)", 0)); + cl_git_pass(git_regexp_search(®ex, "ac", 4, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 2); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); + cl_assert_equal_i(matches[2].start, -1); + cl_assert_equal_i(matches[2].end, -1); + cl_assert_equal_i(matches[3].start, 1); + cl_assert_equal_i(matches[3].end, 2); +} + +void test_regexp__search_initializes_trailing_nonmatching_groups(void) +{ + git_regmatch matches[3]; + + cl_git_pass(git_regexp_compile(®ex, "(a)bc", 0)); + cl_git_pass(git_regexp_search(®ex, "abc", 3, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 3); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); + cl_assert_equal_i(matches[2].start, -1); + cl_assert_equal_i(matches[2].end, -1); +} diff --git a/tests/util/rmdir.c b/tests/util/rmdir.c new file mode 100644 index 000000000..71ec05f9b --- /dev/null +++ b/tests/util/rmdir.c @@ -0,0 +1,120 @@ +#include "clar_libgit2.h" +#include "futils.h" + +static const char *empty_tmp_dir = "test_gitfo_rmdir_recurs_test"; + +void test_rmdir__initialize(void) +{ + git_str path = GIT_STR_INIT; + + cl_must_pass(p_mkdir(empty_tmp_dir, 0777)); + + cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_one")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_two")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_two/three")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/two")); + cl_must_pass(p_mkdir(path.ptr, 0777)); + + git_str_dispose(&path); +} + +void test_rmdir__cleanup(void) +{ + if (git_fs_path_exists(empty_tmp_dir)) + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +/* make sure empty dir can be deleted recursively */ +void test_rmdir__delete_recursive(void) +{ + git_str path = GIT_STR_INIT; + cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one")); + cl_assert(git_fs_path_exists(git_str_cstr(&path))); + + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); + + cl_assert(!git_fs_path_exists(git_str_cstr(&path))); + + git_str_dispose(&path); +} + +/* make sure non-empty dir cannot be deleted recursively */ +void test_rmdir__fail_to_delete_non_empty_dir(void) +{ + git_str file = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&file, empty_tmp_dir, "/two/file.txt")); + + cl_git_mkfile(git_str_cstr(&file), "dummy"); + + cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); + + cl_must_pass(p_unlink(file.ptr)); + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); + + cl_assert(!git_fs_path_exists(empty_tmp_dir)); + + git_str_dispose(&file); +} + +void test_rmdir__keep_base(void) +{ + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_SKIP_ROOT)); + cl_assert(git_fs_path_exists(empty_tmp_dir)); +} + +void test_rmdir__can_skip_non_empty_dir(void) +{ + git_str file = GIT_STR_INIT; + + cl_git_pass(git_str_joinpath(&file, empty_tmp_dir, "/two/file.txt")); + + cl_git_mkfile(git_str_cstr(&file), "dummy"); + + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_SKIP_NONEMPTY)); + cl_assert(git_fs_path_exists(git_str_cstr(&file)) == true); + + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_REMOVE_FILES)); + cl_assert(git_fs_path_exists(empty_tmp_dir) == false); + + git_str_dispose(&file); +} + +void test_rmdir__can_remove_empty_parents(void) +{ + git_str file = GIT_STR_INIT; + + cl_git_pass( + git_str_joinpath(&file, empty_tmp_dir, "/one/two_two/three/file.txt")); + cl_git_mkfile(git_str_cstr(&file), "dummy"); + cl_assert(git_fs_path_isfile(git_str_cstr(&file))); + + cl_git_pass(git_futils_rmdir_r("one/two_two/three/file.txt", empty_tmp_dir, + GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS)); + + cl_assert(!git_fs_path_exists(git_str_cstr(&file))); + + git_str_rtruncate_at_char(&file, '/'); /* three (only contained file.txt) */ + cl_assert(!git_fs_path_exists(git_str_cstr(&file))); + + git_str_rtruncate_at_char(&file, '/'); /* two_two (only contained three) */ + cl_assert(!git_fs_path_exists(git_str_cstr(&file))); + + git_str_rtruncate_at_char(&file, '/'); /* one (contained two_one also) */ + cl_assert(git_fs_path_exists(git_str_cstr(&file))); + + cl_assert(git_fs_path_exists(empty_tmp_dir) == true); + + git_str_dispose(&file); + + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); +} diff --git a/tests/util/sha1.c b/tests/util/sha1.c new file mode 100644 index 000000000..68982758e --- /dev/null +++ b/tests/util/sha1.c @@ -0,0 +1,70 @@ +#include "clar_libgit2.h" +#include "hash.h" + +#define FIXTURE_DIR "sha1" + +void test_sha1__initialize(void) +{ + cl_fixture_sandbox(FIXTURE_DIR); +} + +void test_sha1__cleanup(void) +{ + cl_fixture_cleanup(FIXTURE_DIR); +} + +static int sha1_file(unsigned char *out, const char *filename) +{ + git_hash_ctx ctx; + char buf[2048]; + int fd, ret; + ssize_t read_len; + + fd = p_open(filename, O_RDONLY); + cl_assert(fd >= 0); + + cl_git_pass(git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1)); + + while ((read_len = p_read(fd, buf, 2048)) > 0) + cl_git_pass(git_hash_update(&ctx, buf, (size_t)read_len)); + + cl_assert_equal_i(0, read_len); + p_close(fd); + + ret = git_hash_final(out, &ctx); + git_hash_ctx_cleanup(&ctx); + + return ret; +} + +void test_sha1__sum(void) +{ + unsigned char expected[GIT_HASH_SHA1_SIZE] = { + 0x4e, 0x72, 0x67, 0x9e, 0x3e, 0xa4, 0xd0, 0x4e, 0x0c, 0x64, + 0x2f, 0x02, 0x9e, 0x61, 0xeb, 0x80, 0x56, 0xc7, 0xed, 0x94 + }; + unsigned char actual[GIT_HASH_SHA1_SIZE]; + + cl_git_pass(sha1_file(actual, FIXTURE_DIR "/hello_c")); + cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA1_SIZE)); +} + +/* test that sha1 collision detection works when enabled */ +void test_sha1__detect_collision_attack(void) +{ + unsigned char actual[GIT_HASH_SHA1_SIZE]; + unsigned char expected[GIT_HASH_SHA1_SIZE] = { + 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, + 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a + }; + +#ifdef GIT_SHA1_COLLISIONDETECT + GIT_UNUSED(&expected); + cl_git_fail(sha1_file(actual, FIXTURE_DIR "/shattered-1.pdf")); + cl_assert_equal_s("SHA1 collision attack detected", git_error_last()->message); +#else + cl_git_pass(sha1_file(actual, FIXTURE_DIR "/shattered-1.pdf")); + cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA1_SIZE)); +#endif +} + diff --git a/tests/util/sortedcache.c b/tests/util/sortedcache.c new file mode 100644 index 000000000..72da7aeb1 --- /dev/null +++ b/tests/util/sortedcache.c @@ -0,0 +1,363 @@ +#include "clar_libgit2.h" +#include "sortedcache.h" + +static int name_only_cmp(const void *a, const void *b) +{ + return strcmp(a, b); +} + +void test_sortedcache__name_only(void) +{ + git_sortedcache *sc; + void *item; + size_t pos; + + cl_git_pass(git_sortedcache_new( + &sc, 0, NULL, NULL, name_only_cmp, NULL)); + + cl_git_pass(git_sortedcache_wlock(sc)); + cl_git_pass(git_sortedcache_upsert(&item, sc, "aaa")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "bbb")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "zzz")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "mmm")); + cl_git_pass(git_sortedcache_upsert(&item, sc, "iii")); + git_sortedcache_wunlock(sc); + + cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL); + cl_assert_equal_s("aaa", item); + cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL); + cl_assert_equal_s("mmm", item); + cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL); + cl_assert_equal_s("zzz", item); + cl_assert(git_sortedcache_lookup(sc, "qqq") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("aaa", item); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("bbb", item); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("iii", item); + cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); + cl_assert_equal_s("mmm", item); + cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL); + cl_assert_equal_s("zzz", item); + cl_assert(git_sortedcache_entry(sc, 5) == NULL); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "iii")); + cl_assert_equal_sz(2, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz")); + cl_assert_equal_sz(4, pos); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "abc")); + + cl_git_pass(git_sortedcache_clear(sc, true)); + + cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + + git_sortedcache_free(sc); +} + +typedef struct { + int value; + char smaller_value; + char path[GIT_FLEX_ARRAY]; +} sortedcache_test_struct; + +static int sortedcache_test_struct_cmp(const void *a_, const void *b_) +{ + const sortedcache_test_struct *a = a_, *b = b_; + return strcmp(a->path, b->path); +} + +static void sortedcache_test_struct_free(void *payload, void *item_) +{ + sortedcache_test_struct *item = item_; + int *count = payload; + (*count)++; + item->smaller_value = 0; +} + +void test_sortedcache__in_memory(void) +{ + git_sortedcache *sc; + sortedcache_test_struct *item; + int free_count = 0; + + cl_git_pass(git_sortedcache_new( + &sc, offsetof(sortedcache_test_struct, path), + sortedcache_test_struct_free, &free_count, + sortedcache_test_struct_cmp, NULL)); + + cl_git_pass(git_sortedcache_wlock(sc)); + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "aaa")); + item->value = 10; + item->smaller_value = 1; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "bbb")); + item->value = 20; + item->smaller_value = 2; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "zzz")); + item->value = 30; + item->smaller_value = 26; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "mmm")); + item->value = 40; + item->smaller_value = 14; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "iii")); + item->value = 50; + item->smaller_value = 9; + git_sortedcache_wunlock(sc); + + cl_assert_equal_sz(5, git_sortedcache_entrycount(sc)); + + cl_git_pass(git_sortedcache_rlock(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL); + cl_assert_equal_s("aaa", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL); + cl_assert_equal_s("mmm", item->path); + cl_assert_equal_i(40, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL); + cl_assert_equal_s("zzz", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_lookup(sc, "abc") == NULL); + + /* not on Windows: + * cl_git_pass(git_sortedcache_rlock(sc)); -- grab more than one + */ + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("aaa", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("bbb", item->path); + cl_assert_equal_i(20, item->value); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("iii", item->path); + cl_assert_equal_i(50, item->value); + cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); + cl_assert_equal_s("mmm", item->path); + cl_assert_equal_i(40, item->value); + cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL); + cl_assert_equal_s("zzz", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_entry(sc, 5) == NULL); + + git_sortedcache_runlock(sc); + /* git_sortedcache_runlock(sc); */ + + cl_assert_equal_i(0, free_count); + + cl_git_pass(git_sortedcache_clear(sc, true)); + + cl_assert_equal_i(5, free_count); + + cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); + cl_assert(git_sortedcache_entry(sc, 0) == NULL); + + free_count = 0; + + cl_git_pass(git_sortedcache_wlock(sc)); + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "testing")); + item->value = 10; + item->smaller_value = 3; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "again")); + item->value = 20; + item->smaller_value = 1; + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "final")); + item->value = 30; + item->smaller_value = 2; + git_sortedcache_wunlock(sc); + + cl_assert_equal_sz(3, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "testing")) != NULL); + cl_assert_equal_s("testing", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "again")) != NULL); + cl_assert_equal_s("again", item->path); + cl_assert_equal_i(20, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_lookup(sc, "zzz") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("again", item->path); + cl_assert_equal_i(20, item->value); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(30, item->value); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("testing", item->path); + cl_assert_equal_i(10, item->value); + cl_assert(git_sortedcache_entry(sc, 3) == NULL); + + { + size_t pos; + + cl_git_pass(git_sortedcache_wlock(sc)); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "again")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_remove(sc, pos)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "again")); + + cl_assert_equal_sz(2, git_sortedcache_entrycount(sc)); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "testing")); + cl_assert_equal_sz(1, pos); + cl_git_pass(git_sortedcache_remove(sc, pos)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "testing")); + + cl_assert_equal_sz(1, git_sortedcache_entrycount(sc)); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_remove(sc, pos)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "final")); + + cl_assert_equal_sz(0, git_sortedcache_entrycount(sc)); + + git_sortedcache_wunlock(sc); + } + + git_sortedcache_free(sc); + + cl_assert_equal_i(3, free_count); +} + +static void sortedcache_test_reload(git_sortedcache *sc) +{ + int count = 0; + git_str buf = GIT_STR_INIT; + char *scan, *after; + sortedcache_test_struct *item; + + cl_assert(git_sortedcache_lockandload(sc, &buf) > 0); + + cl_git_pass(git_sortedcache_clear(sc, false)); /* clear once we already have lock */ + + for (scan = buf.ptr; *scan; scan = after + 1) { + int val = strtol(scan, &after, 0); + cl_assert(after > scan); + scan = after; + + for (scan = after; git__isspace(*scan); ++scan) /* find start */; + for (after = scan; *after && *after != '\n'; ++after) /* find eol */; + *after = '\0'; + + cl_git_pass(git_sortedcache_upsert((void **)&item, sc, scan)); + + item->value = val; + item->smaller_value = (char)(count++); + } + + git_sortedcache_wunlock(sc); + + git_str_dispose(&buf); +} + +void test_sortedcache__on_disk(void) +{ + git_sortedcache *sc; + sortedcache_test_struct *item; + int free_count = 0; + size_t pos; + + cl_git_mkfile("cacheitems.txt", "10 abc\n20 bcd\n30 cde\n"); + + cl_git_pass(git_sortedcache_new( + &sc, offsetof(sortedcache_test_struct, path), + sortedcache_test_struct_free, &free_count, + sortedcache_test_struct_cmp, "cacheitems.txt")); + + /* should need to reload the first time */ + + sortedcache_test_reload(sc); + + /* test what we loaded */ + + cl_assert_equal_sz(3, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL); + cl_assert_equal_s("abc", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "cde")) != NULL); + cl_assert_equal_s("cde", item->path); + cl_assert_equal_i(30, item->value); + cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("abc", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL); + cl_assert_equal_s("bcd", item->path); + cl_assert_equal_i(20, item->value); + cl_assert(git_sortedcache_entry(sc, 3) == NULL); + + /* should not need to reload this time */ + + cl_assert_equal_i(0, git_sortedcache_lockandload(sc, NULL)); + + /* rewrite ondisk file and reload */ + + cl_assert_equal_i(0, free_count); + + cl_git_rewritefile( + "cacheitems.txt", "100 abc\n200 zzz\n500 aaa\n10 final\n"); + sortedcache_test_reload(sc); + + cl_assert_equal_i(3, free_count); + + /* test what we loaded */ + + cl_assert_equal_sz(4, git_sortedcache_entrycount(sc)); + + cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL); + cl_assert_equal_s("abc", item->path); + cl_assert_equal_i(100, item->value); + cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(10, item->value); + cl_assert(git_sortedcache_lookup(sc, "cde") == NULL); + + cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL); + cl_assert_equal_s("aaa", item->path); + cl_assert_equal_i(500, item->value); + cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL); + cl_assert_equal_s("final", item->path); + cl_assert_equal_i(10, item->value); + cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL); + cl_assert_equal_s("zzz", item->path); + cl_assert_equal_i(200, item->value); + + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa")); + cl_assert_equal_sz(0, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "abc")); + cl_assert_equal_sz(1, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final")); + cl_assert_equal_sz(2, pos); + cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz")); + cl_assert_equal_sz(3, pos); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "missing")); + cl_assert_equal_i( + GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "cde")); + + git_sortedcache_free(sc); + + cl_assert_equal_i(7, free_count); +} + diff --git a/tests/util/stat.c b/tests/util/stat.c new file mode 100644 index 000000000..84c23fb21 --- /dev/null +++ b/tests/util/stat.c @@ -0,0 +1,113 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "posix.h" + +void test_stat__initialize(void) +{ + cl_git_pass(git_futils_mkdir("root/d1/d2", 0755, GIT_MKDIR_PATH)); + cl_git_mkfile("root/file", "whatever\n"); + cl_git_mkfile("root/d1/file", "whatever\n"); +} + +void test_stat__cleanup(void) +{ + git_futils_rmdir_r("root", NULL, GIT_RMDIR_REMOVE_FILES); +} + +#define cl_assert_error(val) \ + do { err = errno; cl_assert_equal_i((val), err); } while (0) + +void test_stat__0(void) +{ + struct stat st; + int err; + + cl_assert_equal_i(0, p_lstat("root", &st)); + cl_assert(S_ISDIR(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/", &st)); + cl_assert(S_ISDIR(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/file", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/d1", &st)); + cl_assert(S_ISDIR(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/d1/", &st)); + cl_assert(S_ISDIR(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/d1/file", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_error(0); + + cl_assert(p_lstat("root/missing", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat("root/missing/but/could/be/created", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat_posixly("root/missing/but/could/be/created", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat("root/d1/missing", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat("root/d1/missing/deeper/path", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat_posixly("root/d1/missing/deeper/path", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat_posixly("root/d1/file/deeper/path", &st) < 0); + cl_assert_error(ENOTDIR); + + cl_assert(p_lstat("root/file/invalid", &st) < 0); +#ifdef GIT_WIN32 + cl_assert_error(ENOENT); +#else + cl_assert_error(ENOTDIR); +#endif + + cl_assert(p_lstat_posixly("root/file/invalid", &st) < 0); + cl_assert_error(ENOTDIR); + + cl_assert(p_lstat("root/file/invalid/deeper_path", &st) < 0); +#ifdef GIT_WIN32 + cl_assert_error(ENOENT); +#else + cl_assert_error(ENOTDIR); +#endif + + cl_assert(p_lstat_posixly("root/file/invalid/deeper_path", &st) < 0); + cl_assert_error(ENOTDIR); + + cl_assert(p_lstat_posixly("root/d1/file/extra", &st) < 0); + cl_assert_error(ENOTDIR); + + cl_assert(p_lstat_posixly("root/d1/file/further/invalid/items", &st) < 0); + cl_assert_error(ENOTDIR); +} + +void test_stat__root(void) +{ + const char *sandbox = clar_sandbox_path(); + git_str root = GIT_STR_INIT; + int root_len; + struct stat st; + + root_len = git_fs_path_root(sandbox); + cl_assert(root_len >= 0); + + git_str_set(&root, sandbox, root_len+1); + + cl_must_pass(p_stat(root.ptr, &st)); + cl_assert(S_ISDIR(st.st_mode)); + + git_str_dispose(&root); +} diff --git a/tests/util/str/basic.c b/tests/util/str/basic.c new file mode 100644 index 000000000..5d2556805 --- /dev/null +++ b/tests/util/str/basic.c @@ -0,0 +1,50 @@ +#include "clar_libgit2.h" + +static const char *test_string = "Have you seen that? Have you seeeen that??"; + +void test_str_basic__resize(void) +{ + git_str buf1 = GIT_STR_INIT; + git_str_puts(&buf1, test_string); + cl_assert(git_str_oom(&buf1) == 0); + cl_assert_equal_s(git_str_cstr(&buf1), test_string); + + git_str_puts(&buf1, test_string); + cl_assert(strlen(git_str_cstr(&buf1)) == strlen(test_string) * 2); + git_str_dispose(&buf1); +} + +void test_str_basic__resize_incremental(void) +{ + git_str buf1 = GIT_STR_INIT; + + /* Presently, asking for 6 bytes will round up to 8. */ + cl_git_pass(git_str_puts(&buf1, "Hello")); + cl_assert_equal_i(5, buf1.size); + cl_assert_equal_i(8, buf1.asize); + + /* Ensure an additional byte does not realloc. */ + cl_git_pass(git_str_grow_by(&buf1, 1)); + cl_assert_equal_i(5, buf1.size); + cl_assert_equal_i(8, buf1.asize); + + /* But requesting many does. */ + cl_git_pass(git_str_grow_by(&buf1, 16)); + cl_assert_equal_i(5, buf1.size); + cl_assert(buf1.asize > 8); + + git_str_dispose(&buf1); +} + +void test_str_basic__printf(void) +{ + git_str buf2 = GIT_STR_INIT; + git_str_printf(&buf2, "%s %s %d ", "shoop", "da", 23); + cl_assert(git_str_oom(&buf2) == 0); + cl_assert_equal_s(git_str_cstr(&buf2), "shoop da 23 "); + + git_str_printf(&buf2, "%s %d", "woop", 42); + cl_assert(git_str_oom(&buf2) == 0); + cl_assert_equal_s(git_str_cstr(&buf2), "shoop da 23 woop 42"); + git_str_dispose(&buf2); +} diff --git a/tests/util/str/oom.c b/tests/util/str/oom.c new file mode 100644 index 000000000..dd3796674 --- /dev/null +++ b/tests/util/str/oom.c @@ -0,0 +1,58 @@ +#include "clar_libgit2.h" + +/* Override default allocators with ones that will fail predictably. */ + +static git_allocator std_alloc; +static git_allocator oom_alloc; + +static void *oom_malloc(size_t n, const char *file, int line) +{ + /* Reject any allocation of more than 100 bytes */ + return (n > 100) ? NULL : std_alloc.gmalloc(n, file, line); +} + +static void *oom_realloc(void *p, size_t n, const char *file, int line) +{ + /* Reject any allocation of more than 100 bytes */ + return (n > 100) ? NULL : std_alloc.grealloc(p, n, file, line); +} + +void test_str_oom__initialize(void) +{ + git_stdalloc_init_allocator(&std_alloc); + git_stdalloc_init_allocator(&oom_alloc); + + oom_alloc.gmalloc = oom_malloc; + oom_alloc.grealloc = oom_realloc; + + cl_git_pass(git_allocator_setup(&oom_alloc)); +} + +void test_str_oom__cleanup(void) +{ + cl_git_pass(git_allocator_setup(NULL)); +} + +void test_str_oom__grow(void) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_grow(&buf, 42)); + cl_assert(!git_str_oom(&buf)); + + cl_assert(git_str_grow(&buf, 101) == -1); + cl_assert(git_str_oom(&buf)); + + git_str_dispose(&buf); +} + +void test_str_oom__grow_by(void) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_grow_by(&buf, 42)); + cl_assert(!git_str_oom(&buf)); + + cl_assert(git_str_grow_by(&buf, 101) == -1); + cl_assert(git_str_oom(&buf)); +} diff --git a/tests/util/str/percent.c b/tests/util/str/percent.c new file mode 100644 index 000000000..339389075 --- /dev/null +++ b/tests/util/str/percent.c @@ -0,0 +1,48 @@ +#include "clar_libgit2.h" + +static void expect_decode_pass(const char *expected, const char *encoded) +{ + git_str in = GIT_STR_INIT, out = GIT_STR_INIT; + + /* + * ensure that we only read the given length of the input buffer + * by putting garbage at the end. this will ensure that we do + * not, eg, rely on nul-termination or walk off the end of the buf. + */ + cl_git_pass(git_str_puts(&in, encoded)); + cl_git_pass(git_str_PUTS(&in, "TRAILER")); + + cl_git_pass(git_str_decode_percent(&out, in.ptr, strlen(encoded))); + + cl_assert_equal_s(expected, git_str_cstr(&out)); + cl_assert_equal_i(strlen(expected), git_str_len(&out)); + + git_str_dispose(&in); + git_str_dispose(&out); +} + +void test_str_percent__decode_succeeds(void) +{ + expect_decode_pass("", ""); + expect_decode_pass(" ", "%20"); + expect_decode_pass("a", "a"); + expect_decode_pass(" a", "%20a"); + expect_decode_pass("a ", "a%20"); + expect_decode_pass("github.com", "github.com"); + expect_decode_pass("github.com", "githu%62.com"); + expect_decode_pass("github.com", "github%2ecom"); + expect_decode_pass("foo bar baz", "foo%20bar%20baz"); + expect_decode_pass("foo bar baz", "foo%20bar%20baz"); + expect_decode_pass("foo bar ", "foo%20bar%20"); +} + +void test_str_percent__ignores_invalid(void) +{ + expect_decode_pass("githu%%.com", "githu%%.com"); + expect_decode_pass("github.co%2", "github.co%2"); + expect_decode_pass("github%2.com", "github%2.com"); + expect_decode_pass("githu%2z.com", "githu%2z.com"); + expect_decode_pass("github.co%9z", "github.co%9z"); + expect_decode_pass("github.co%2", "github.co%2"); + expect_decode_pass("github.co%", "github.co%"); +} diff --git a/tests/util/str/quote.c b/tests/util/str/quote.c new file mode 100644 index 000000000..2c6546247 --- /dev/null +++ b/tests/util/str/quote.c @@ -0,0 +1,87 @@ +#include "clar_libgit2.h" + +static void expect_quote_pass(const char *expected, const char *str) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_puts(&buf, str)); + cl_git_pass(git_str_quote(&buf)); + + cl_assert_equal_s(expected, git_str_cstr(&buf)); + cl_assert_equal_i(strlen(expected), git_str_len(&buf)); + + git_str_dispose(&buf); +} + +void test_str_quote__quote_succeeds(void) +{ + expect_quote_pass("", ""); + expect_quote_pass("foo", "foo"); + expect_quote_pass("foo/bar/baz.c", "foo/bar/baz.c"); + expect_quote_pass("foo bar", "foo bar"); + expect_quote_pass("\"\\\"leading quote\"", "\"leading quote"); + expect_quote_pass("\"slash\\\\y\"", "slash\\y"); + expect_quote_pass("\"foo\\r\\nbar\"", "foo\r\nbar"); + expect_quote_pass("\"foo\\177bar\"", "foo\177bar"); + expect_quote_pass("\"foo\\001bar\"", "foo\001bar"); + expect_quote_pass("\"foo\\377bar\"", "foo\377bar"); +} + +static void expect_unquote_pass(const char *expected, const char *quoted) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_puts(&buf, quoted)); + cl_git_pass(git_str_unquote(&buf)); + + cl_assert_equal_s(expected, git_str_cstr(&buf)); + cl_assert_equal_i(strlen(expected), git_str_len(&buf)); + + git_str_dispose(&buf); +} + +static void expect_unquote_fail(const char *quoted) +{ + git_str buf = GIT_STR_INIT; + + cl_git_pass(git_str_puts(&buf, quoted)); + cl_git_fail(git_str_unquote(&buf)); + + git_str_dispose(&buf); +} + +void test_str_quote__unquote_succeeds(void) +{ + expect_unquote_pass("", "\"\""); + expect_unquote_pass(" ", "\" \""); + expect_unquote_pass("foo", "\"foo\""); + expect_unquote_pass("foo bar", "\"foo bar\""); + expect_unquote_pass("foo\"bar", "\"foo\\\"bar\""); + expect_unquote_pass("foo\\bar", "\"foo\\\\bar\""); + expect_unquote_pass("foo\tbar", "\"foo\\tbar\""); + expect_unquote_pass("\vfoo\tbar\n", "\"\\vfoo\\tbar\\n\""); + expect_unquote_pass("foo\nbar", "\"foo\\012bar\""); + expect_unquote_pass("foo\r\nbar", "\"foo\\015\\012bar\""); + expect_unquote_pass("foo\r\nbar", "\"\\146\\157\\157\\015\\012\\142\\141\\162\""); + expect_unquote_pass("newline: \n", "\"newline: \\012\""); + expect_unquote_pass("0xff: \377", "\"0xff: \\377\""); +} + +void test_str_quote__unquote_fails(void) +{ + expect_unquote_fail("no quotes at all"); + expect_unquote_fail("\"no trailing quote"); + expect_unquote_fail("no leading quote\""); + expect_unquote_fail("\"invalid \\z escape char\""); + expect_unquote_fail("\"\\q invalid escape char\""); + expect_unquote_fail("\"invalid escape char \\p\""); + expect_unquote_fail("\"invalid \\1 escape char \""); + expect_unquote_fail("\"invalid \\14 escape char \""); + expect_unquote_fail("\"invalid \\280 escape char\""); + expect_unquote_fail("\"invalid \\378 escape char\""); + expect_unquote_fail("\"invalid \\380 escape char\""); + expect_unquote_fail("\"invalid \\411 escape char\""); + expect_unquote_fail("\"truncated escape char \\\""); + expect_unquote_fail("\"truncated escape char \\0\""); + expect_unquote_fail("\"truncated escape char \\01\""); +} diff --git a/tests/util/str/splice.c b/tests/util/str/splice.c new file mode 100644 index 000000000..14e844e2f --- /dev/null +++ b/tests/util/str/splice.c @@ -0,0 +1,92 @@ +#include "clar_libgit2.h" + +static git_str _buf; + +void test_str_splice__initialize(void) { + git_str_init(&_buf, 16); +} + +void test_str_splice__cleanup(void) { + git_str_dispose(&_buf); +} + +void test_str_splice__preprend(void) +{ + git_str_sets(&_buf, "world!"); + + cl_git_pass(git_str_splice(&_buf, 0, 0, "Hello Dolly", strlen("Hello "))); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__append(void) +{ + git_str_sets(&_buf, "Hello"); + + cl_git_pass(git_str_splice(&_buf, git_str_len(&_buf), 0, " world!", strlen(" world!"))); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__insert_at(void) +{ + git_str_sets(&_buf, "Hell world!"); + + cl_git_pass(git_str_splice(&_buf, strlen("Hell"), 0, "o", strlen("o"))); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__remove_at(void) +{ + git_str_sets(&_buf, "Hello world of warcraft!"); + + cl_git_pass(git_str_splice(&_buf, strlen("Hello world"), strlen(" of warcraft"), "", 0)); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__replace(void) +{ + git_str_sets(&_buf, "Hell0 w0rld!"); + + cl_git_pass(git_str_splice(&_buf, strlen("Hell"), strlen("0 w0"), "o wo", strlen("o wo"))); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__replace_with_longer(void) +{ + git_str_sets(&_buf, "Hello you!"); + + cl_git_pass(git_str_splice(&_buf, strlen("Hello "), strlen("you"), "world", strlen("world"))); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__replace_with_shorter(void) +{ + git_str_sets(&_buf, "Brave new world!"); + + cl_git_pass(git_str_splice(&_buf, 0, strlen("Brave new"), "Hello", strlen("Hello"))); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__truncate(void) +{ + git_str_sets(&_buf, "Hello world!!"); + + cl_git_pass(git_str_splice(&_buf, strlen("Hello world!"), strlen("!"), "", 0)); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} + +void test_str_splice__dont_do_anything(void) +{ + git_str_sets(&_buf, "Hello world!"); + + cl_git_pass(git_str_splice(&_buf, 3, 0, "Hello", 0)); + + cl_assert_equal_s("Hello world!", git_str_cstr(&_buf)); +} diff --git a/tests/util/string.c b/tests/util/string.c new file mode 100644 index 000000000..de04dea69 --- /dev/null +++ b/tests/util/string.c @@ -0,0 +1,136 @@ +#include "clar_libgit2.h" + +/* compare prefixes */ +void test_string__0(void) +{ + cl_assert(git__prefixcmp("", "") == 0); + cl_assert(git__prefixcmp("a", "") == 0); + cl_assert(git__prefixcmp("", "a") < 0); + cl_assert(git__prefixcmp("a", "b") < 0); + cl_assert(git__prefixcmp("b", "a") > 0); + cl_assert(git__prefixcmp("ab", "a") == 0); + cl_assert(git__prefixcmp("ab", "ac") < 0); + cl_assert(git__prefixcmp("ab", "aa") > 0); +} + +/* compare suffixes */ +void test_string__1(void) +{ + cl_assert(git__suffixcmp("", "") == 0); + cl_assert(git__suffixcmp("a", "") == 0); + cl_assert(git__suffixcmp("", "a") < 0); + cl_assert(git__suffixcmp("a", "b") < 0); + cl_assert(git__suffixcmp("b", "a") > 0); + cl_assert(git__suffixcmp("ba", "a") == 0); + cl_assert(git__suffixcmp("zaa", "ac") < 0); + cl_assert(git__suffixcmp("zaz", "ac") > 0); +} + +/* compare icase sorting with case equality */ +void test_string__2(void) +{ + cl_assert(git__strcasesort_cmp("", "") == 0); + cl_assert(git__strcasesort_cmp("foo", "foo") == 0); + cl_assert(git__strcasesort_cmp("foo", "bar") > 0); + cl_assert(git__strcasesort_cmp("bar", "foo") < 0); + cl_assert(git__strcasesort_cmp("foo", "FOO") > 0); + cl_assert(git__strcasesort_cmp("FOO", "foo") < 0); + cl_assert(git__strcasesort_cmp("foo", "BAR") > 0); + cl_assert(git__strcasesort_cmp("BAR", "foo") < 0); + cl_assert(git__strcasesort_cmp("fooBar", "foobar") < 0); +} + +/* compare prefixes with len */ +void test_string__prefixncmp(void) +{ + cl_assert(git__prefixncmp("", 0, "") == 0); + cl_assert(git__prefixncmp("a", 1, "") == 0); + cl_assert(git__prefixncmp("", 0, "a") < 0); + cl_assert(git__prefixncmp("a", 1, "b") < 0); + cl_assert(git__prefixncmp("b", 1, "a") > 0); + cl_assert(git__prefixncmp("ab", 2, "a") == 0); + cl_assert(git__prefixncmp("ab", 1, "a") == 0); + cl_assert(git__prefixncmp("ab", 2, "ac") < 0); + cl_assert(git__prefixncmp("a", 1, "ac") < 0); + cl_assert(git__prefixncmp("ab", 1, "ac") < 0); + cl_assert(git__prefixncmp("ab", 2, "aa") > 0); + cl_assert(git__prefixncmp("ab", 1, "aa") < 0); +} + +/* compare prefixes with len */ +void test_string__prefixncmp_icase(void) +{ + cl_assert(git__prefixncmp_icase("", 0, "") == 0); + cl_assert(git__prefixncmp_icase("a", 1, "") == 0); + cl_assert(git__prefixncmp_icase("", 0, "a") < 0); + cl_assert(git__prefixncmp_icase("a", 1, "b") < 0); + cl_assert(git__prefixncmp_icase("A", 1, "b") < 0); + cl_assert(git__prefixncmp_icase("a", 1, "B") < 0); + cl_assert(git__prefixncmp_icase("b", 1, "a") > 0); + cl_assert(git__prefixncmp_icase("B", 1, "a") > 0); + cl_assert(git__prefixncmp_icase("b", 1, "A") > 0); + cl_assert(git__prefixncmp_icase("ab", 2, "a") == 0); + cl_assert(git__prefixncmp_icase("Ab", 2, "a") == 0); + cl_assert(git__prefixncmp_icase("ab", 2, "A") == 0); + cl_assert(git__prefixncmp_icase("ab", 1, "a") == 0); + cl_assert(git__prefixncmp_icase("ab", 2, "ac") < 0); + cl_assert(git__prefixncmp_icase("Ab", 2, "ac") < 0); + cl_assert(git__prefixncmp_icase("ab", 2, "Ac") < 0); + cl_assert(git__prefixncmp_icase("a", 1, "ac") < 0); + cl_assert(git__prefixncmp_icase("ab", 1, "ac") < 0); + cl_assert(git__prefixncmp_icase("ab", 2, "aa") > 0); + cl_assert(git__prefixncmp_icase("ab", 1, "aa") < 0); +} + +void test_string__strcmp(void) +{ + cl_assert(git__strcmp("", "") == 0); + cl_assert(git__strcmp("foo", "foo") == 0); + cl_assert(git__strcmp("Foo", "foo") < 0); + cl_assert(git__strcmp("foo", "FOO") > 0); + cl_assert(git__strcmp("foo", "fOO") > 0); + + cl_assert(strcmp("rt\303\202of", "rt dev\302\266h") > 0); + cl_assert(strcmp("e\342\202\254ghi=", "et") > 0); + cl_assert(strcmp("rt dev\302\266h", "rt\303\202of") < 0); + cl_assert(strcmp("et", "e\342\202\254ghi=") < 0); + cl_assert(strcmp("\303\215", "\303\255") < 0); + + cl_assert(git__strcmp("rt\303\202of", "rt dev\302\266h") > 0); + cl_assert(git__strcmp("e\342\202\254ghi=", "et") > 0); + cl_assert(git__strcmp("rt dev\302\266h", "rt\303\202of") < 0); + cl_assert(git__strcmp("et", "e\342\202\254ghi=") < 0); + cl_assert(git__strcmp("\303\215", "\303\255") < 0); +} + +void test_string__strcasecmp(void) +{ + cl_assert(git__strcasecmp("", "") == 0); + cl_assert(git__strcasecmp("foo", "foo") == 0); + cl_assert(git__strcasecmp("foo", "Foo") == 0); + cl_assert(git__strcasecmp("foo", "FOO") == 0); + cl_assert(git__strcasecmp("foo", "fOO") == 0); + + cl_assert(strcasecmp("rt\303\202of", "rt dev\302\266h") > 0); + cl_assert(strcasecmp("e\342\202\254ghi=", "et") > 0); + cl_assert(strcasecmp("rt dev\302\266h", "rt\303\202of") < 0); + cl_assert(strcasecmp("et", "e\342\202\254ghi=") < 0); + cl_assert(strcasecmp("\303\215", "\303\255") < 0); + + cl_assert(git__strcasecmp("rt\303\202of", "rt dev\302\266h") > 0); + cl_assert(git__strcasecmp("e\342\202\254ghi=", "et") > 0); + cl_assert(git__strcasecmp("rt dev\302\266h", "rt\303\202of") < 0); + cl_assert(git__strcasecmp("et", "e\342\202\254ghi=") < 0); + cl_assert(git__strcasecmp("\303\215", "\303\255") < 0); +} + +void test_string__strlcmp(void) +{ + const char foo[3] = { 'f', 'o', 'o' }; + + cl_assert(git__strlcmp("foo", "foo", 3) == 0); + cl_assert(git__strlcmp("foo", foo, 3) == 0); + cl_assert(git__strlcmp("foo", "foobar", 3) == 0); + cl_assert(git__strlcmp("foobar", "foo", 3) > 0); + cl_assert(git__strlcmp("foo", "foobar", 6) < 0); +} diff --git a/tests/util/strmap.c b/tests/util/strmap.c new file mode 100644 index 000000000..c4f5c8647 --- /dev/null +++ b/tests/util/strmap.c @@ -0,0 +1,190 @@ +#include "clar_libgit2.h" +#include "strmap.h" + +static git_strmap *g_table; + +void test_strmap__initialize(void) +{ + cl_git_pass(git_strmap_new(&g_table)); + cl_assert(g_table != NULL); +} + +void test_strmap__cleanup(void) +{ + git_strmap_free(g_table); +} + +void test_strmap__0(void) +{ + cl_assert(git_strmap_size(g_table) == 0); +} + +static void insert_strings(git_strmap *table, size_t count) +{ + size_t i, j, over; + char *str; + + for (i = 0; i < count; ++i) { + str = malloc(10); + for (j = 0; j < 10; ++j) + str[j] = 'a' + (i % 26); + str[9] = '\0'; + + /* if > 26, then encode larger value in first letters */ + for (j = 0, over = i / 26; over > 0; j++, over = over / 26) + str[j] = 'A' + (over % 26); + + cl_git_pass(git_strmap_set(table, str, str)); + } + + cl_assert_equal_i(git_strmap_size(table), count); +} + +void test_strmap__inserted_strings_can_be_retrieved(void) +{ + char *str; + int i; + + insert_strings(g_table, 20); + + cl_assert(git_strmap_exists(g_table, "aaaaaaaaa")); + cl_assert(git_strmap_exists(g_table, "ggggggggg")); + cl_assert(!git_strmap_exists(g_table, "aaaaaaaab")); + cl_assert(!git_strmap_exists(g_table, "abcdefghi")); + + i = 0; + git_strmap_foreach_value(g_table, str, { i++; free(str); }); + cl_assert(i == 20); +} + +void test_strmap__deleted_entry_cannot_be_retrieved(void) +{ + char *str; + int i; + + insert_strings(g_table, 20); + + cl_assert(git_strmap_exists(g_table, "bbbbbbbbb")); + str = git_strmap_get(g_table, "bbbbbbbbb"); + cl_assert_equal_s(str, "bbbbbbbbb"); + cl_git_pass(git_strmap_delete(g_table, "bbbbbbbbb")); + free(str); + + cl_assert(!git_strmap_exists(g_table, "bbbbbbbbb")); + + i = 0; + git_strmap_foreach_value(g_table, str, { i++; free(str); }); + cl_assert_equal_i(i, 19); +} + +void test_strmap__inserting_many_keys_succeeds(void) +{ + char *str; + int i; + + insert_strings(g_table, 10000); + + i = 0; + git_strmap_foreach_value(g_table, str, { i++; free(str); }); + cl_assert_equal_i(i, 10000); +} + +void test_strmap__get_succeeds_with_existing_entries(void) +{ + const char *keys[] = { "foo", "bar", "gobble" }; + char *values[] = { "oof", "rab", "elbbog" }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(keys); i++) + cl_git_pass(git_strmap_set(g_table, keys[i], values[i])); + + cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); + cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab"); + cl_assert_equal_s(git_strmap_get(g_table, "gobble"), "elbbog"); +} + +void test_strmap__get_returns_null_on_nonexisting_key(void) +{ + const char *keys[] = { "foo", "bar", "gobble" }; + char *values[] = { "oof", "rab", "elbbog" }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(keys); i++) + cl_git_pass(git_strmap_set(g_table, keys[i], values[i])); + + cl_assert_equal_p(git_strmap_get(g_table, "other"), NULL); +} + +void test_strmap__set_persists_key(void) +{ + cl_git_pass(git_strmap_set(g_table, "foo", "oof")); + cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); +} + +void test_strmap__set_persists_multpile_keys(void) +{ + cl_git_pass(git_strmap_set(g_table, "foo", "oof")); + cl_git_pass(git_strmap_set(g_table, "bar", "rab")); + cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); + cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab"); +} + +void test_strmap__set_updates_existing_key(void) +{ + cl_git_pass(git_strmap_set(g_table, "foo", "oof")); + cl_git_pass(git_strmap_set(g_table, "bar", "rab")); + cl_git_pass(git_strmap_set(g_table, "gobble", "elbbog")); + cl_assert_equal_i(git_strmap_size(g_table), 3); + + cl_git_pass(git_strmap_set(g_table, "foo", "other")); + cl_assert_equal_i(git_strmap_size(g_table), 3); + + cl_assert_equal_s(git_strmap_get(g_table, "foo"), "other"); +} + +void test_strmap__iteration(void) +{ + struct { + char *key; + char *value; + int seen; + } entries[] = { + { "foo", "oof" }, + { "bar", "rab" }, + { "gobble", "elbbog" }, + }; + const char *key, *value; + size_t i, n; + + for (i = 0; i < ARRAY_SIZE(entries); i++) + cl_git_pass(git_strmap_set(g_table, entries[i].key, entries[i].value)); + + i = 0, n = 0; + while (git_strmap_iterate((void **) &value, g_table, &i, &key) == 0) { + size_t j; + + for (j = 0; j < ARRAY_SIZE(entries); j++) { + if (strcmp(entries[j].key, key)) + continue; + + cl_assert_equal_i(entries[j].seen, 0); + cl_assert_equal_s(entries[j].value, value); + entries[j].seen++; + break; + } + + n++; + } + + for (i = 0; i < ARRAY_SIZE(entries); i++) + cl_assert_equal_i(entries[i].seen, 1); + + cl_assert_equal_i(n, ARRAY_SIZE(entries)); +} + +void test_strmap__iterating_empty_map_stops_immediately(void) +{ + size_t i = 0; + + cl_git_fail_with(git_strmap_iterate(NULL, g_table, &i, NULL), GIT_ITEROVER); +} diff --git a/tests/util/strtol.c b/tests/util/strtol.c new file mode 100644 index 000000000..54c63ca08 --- /dev/null +++ b/tests/util/strtol.c @@ -0,0 +1,128 @@ +#include "clar_libgit2.h" + +static void assert_l32_parses(const char *string, int32_t expected, int base) +{ + int32_t i; + cl_git_pass(git__strntol32(&i, string, strlen(string), NULL, base)); + cl_assert_equal_i(i, expected); +} + +static void assert_l32_fails(const char *string, int base) +{ + int32_t i; + cl_git_fail(git__strntol32(&i, string, strlen(string), NULL, base)); +} + +static void assert_l64_parses(const char *string, int64_t expected, int base) +{ + int64_t i; + cl_git_pass(git__strntol64(&i, string, strlen(string), NULL, base)); + cl_assert_equal_i(i, expected); +} + +static void assert_l64_fails(const char *string, int base) +{ + int64_t i; + cl_git_fail(git__strntol64(&i, string, strlen(string), NULL, base)); +} + +void test_strtol__int32(void) +{ + assert_l32_parses("123", 123, 10); + assert_l32_parses(" +123 ", 123, 10); + assert_l32_parses(" -123 ", -123, 10); + assert_l32_parses(" +2147483647 ", 2147483647, 10); + assert_l32_parses(" -2147483648 ", INT64_C(-2147483648), 10); + assert_l32_parses("A", 10, 16); + assert_l32_parses("1x1", 1, 10); + + assert_l32_fails("", 10); + assert_l32_fails("a", 10); + assert_l32_fails("x10x", 10); + assert_l32_fails(" 2147483657 ", 10); + assert_l32_fails(" -2147483657 ", 10); +} + +void test_strtol__int64(void) +{ + assert_l64_parses("123", 123, 10); + assert_l64_parses(" +123 ", 123, 10); + assert_l64_parses(" -123 ", -123, 10); + assert_l64_parses(" +2147483647 ", 2147483647, 10); + assert_l64_parses(" -2147483648 ", INT64_C(-2147483648), 10); + assert_l64_parses(" 2147483657 ", INT64_C(2147483657), 10); + assert_l64_parses(" -2147483657 ", INT64_C(-2147483657), 10); + assert_l64_parses(" 9223372036854775807 ", INT64_MAX, 10); + assert_l64_parses(" -9223372036854775808 ", INT64_MIN, 10); + assert_l64_parses(" 0x7fffffffffffffff ", INT64_MAX, 16); + assert_l64_parses(" -0x8000000000000000 ", INT64_MIN, 16); + assert_l64_parses("1a", 26, 16); + assert_l64_parses("1A", 26, 16); + + assert_l64_fails("", 10); + assert_l64_fails("a", 10); + assert_l64_fails("x10x", 10); + assert_l64_fails("0x8000000000000000", 16); + assert_l64_fails("-0x8000000000000001", 16); +} + +void test_strtol__base_autodetection(void) +{ + assert_l64_parses("0", 0, 0); + assert_l64_parses("00", 0, 0); + assert_l64_parses("0x", 0, 0); + assert_l64_parses("0foobar", 0, 0); + assert_l64_parses("07", 7, 0); + assert_l64_parses("017", 15, 0); + assert_l64_parses("0x8", 8, 0); + assert_l64_parses("0x18", 24, 0); +} + +void test_strtol__buffer_length_with_autodetection_truncates(void) +{ + int64_t i64; + + cl_git_pass(git__strntol64(&i64, "011", 2, NULL, 0)); + cl_assert_equal_i(i64, 1); + cl_git_pass(git__strntol64(&i64, "0x11", 3, NULL, 0)); + cl_assert_equal_i(i64, 1); +} + +void test_strtol__buffer_length_truncates(void) +{ + int32_t i32; + int64_t i64; + + cl_git_pass(git__strntol32(&i32, "11", 1, NULL, 10)); + cl_assert_equal_i(i32, 1); + + cl_git_pass(git__strntol64(&i64, "11", 1, NULL, 10)); + cl_assert_equal_i(i64, 1); +} + +void test_strtol__buffer_length_with_leading_ws_truncates(void) +{ + int64_t i64; + + cl_git_fail(git__strntol64(&i64, " 1", 1, NULL, 10)); + + cl_git_pass(git__strntol64(&i64, " 11", 2, NULL, 10)); + cl_assert_equal_i(i64, 1); +} + +void test_strtol__buffer_length_with_leading_sign_truncates(void) +{ + int64_t i64; + + cl_git_fail(git__strntol64(&i64, "-1", 1, NULL, 10)); + + cl_git_pass(git__strntol64(&i64, "-11", 2, NULL, 10)); + cl_assert_equal_i(i64, -1); +} + +void test_strtol__error_message_cuts_off(void) +{ + assert_l32_fails("2147483657foobar", 10); + cl_assert(strstr(git_error_last()->message, "2147483657") != NULL); + cl_assert(strstr(git_error_last()->message, "foobar") == NULL); +} diff --git a/tests/util/utf8.c b/tests/util/utf8.c new file mode 100644 index 000000000..3987603bb --- /dev/null +++ b/tests/util/utf8.c @@ -0,0 +1,20 @@ +#include "clar_libgit2.h" +#include "utf8.h" + +void test_utf8__char_length(void) +{ + cl_assert_equal_i(0, git_utf8_char_length("", 0)); + cl_assert_equal_i(1, git_utf8_char_length("$", 1)); + cl_assert_equal_i(5, git_utf8_char_length("abcde", 5)); + cl_assert_equal_i(1, git_utf8_char_length("\xc2\xa2", 2)); + cl_assert_equal_i(2, git_utf8_char_length("\x24\xc2\xa2", 3)); + cl_assert_equal_i(1, git_utf8_char_length("\xf0\x90\x8d\x88", 4)); + + /* uncontinued character counted as single characters */ + cl_assert_equal_i(2, git_utf8_char_length("\x24\xc2", 2)); + cl_assert_equal_i(3, git_utf8_char_length("\x24\xc2\xc2\xa2", 4)); + + /* invalid characters are counted as single characters */ + cl_assert_equal_i(4, git_utf8_char_length("\x24\xc0\xc0\x34", 4)); + cl_assert_equal_i(4, git_utf8_char_length("\x24\xf5\xfd\xc2", 4)); +} diff --git a/tests/util/vector.c b/tests/util/vector.c new file mode 100644 index 000000000..04afaa496 --- /dev/null +++ b/tests/util/vector.c @@ -0,0 +1,430 @@ +#include + +#include "clar_libgit2.h" +#include "vector.h" + +/* initial size of 1 would cause writing past array bounds */ +void test_vector__0(void) +{ + git_vector x; + int i; + cl_git_pass(git_vector_init(&x, 1, NULL)); + for (i = 0; i < 10; ++i) { + git_vector_insert(&x, (void*) 0xabc); + } + git_vector_free(&x); +} + + +/* don't read past array bounds on remove() */ +void test_vector__1(void) +{ + git_vector x; + /* make initial capacity exact for our insertions. */ + cl_git_pass(git_vector_init(&x, 3, NULL)); + git_vector_insert(&x, (void*) 0xabc); + git_vector_insert(&x, (void*) 0xdef); + git_vector_insert(&x, (void*) 0x123); + + git_vector_remove(&x, 0); /* used to read past array bounds. */ + git_vector_free(&x); +} + + +static int test_cmp(const void *a, const void *b) +{ + return *(const int *)a - *(const int *)b; +} + +/* remove duplicates */ +void test_vector__2(void) +{ + git_vector x; + int *ptrs[2]; + + ptrs[0] = git__malloc(sizeof(int)); + ptrs[1] = git__malloc(sizeof(int)); + + *ptrs[0] = 2; + *ptrs[1] = 1; + + cl_git_pass(git_vector_init(&x, 5, test_cmp)); + cl_git_pass(git_vector_insert(&x, ptrs[0])); + cl_git_pass(git_vector_insert(&x, ptrs[1])); + cl_git_pass(git_vector_insert(&x, ptrs[1])); + cl_git_pass(git_vector_insert(&x, ptrs[0])); + cl_git_pass(git_vector_insert(&x, ptrs[1])); + cl_assert(x.length == 5); + + git_vector_uniq(&x, NULL); + cl_assert(x.length == 2); + + git_vector_free(&x); + + git__free(ptrs[0]); + git__free(ptrs[1]); +} + + +static int compare_them(const void *a, const void *b) +{ + return (int)((intptr_t)a - (intptr_t)b); +} + +/* insert_sorted */ +void test_vector__3(void) +{ + git_vector x; + intptr_t i; + cl_git_pass(git_vector_init(&x, 1, &compare_them)); + + for (i = 0; i < 10; i += 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 9; i > 0; i -= 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + cl_assert(x.length == 10); + for (i = 0; i < 10; ++i) { + cl_assert(git_vector_get(&x, i) == (void*)(i + 1)); + } + + git_vector_free(&x); +} + +/* insert_sorted with duplicates */ +void test_vector__4(void) +{ + git_vector x; + intptr_t i; + cl_git_pass(git_vector_init(&x, 1, &compare_them)); + + for (i = 0; i < 10; i += 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 9; i > 0; i -= 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 0; i < 10; i += 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 9; i > 0; i -= 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + cl_assert(x.length == 20); + for (i = 0; i < 20; ++i) { + cl_assert(git_vector_get(&x, i) == (void*)(i / 2 + 1)); + } + + git_vector_free(&x); +} + +typedef struct { + int content; + int count; +} my_struct; + +static int _struct_count = 0; + +static int compare_structs(const void *a, const void *b) +{ + return ((const my_struct *)a)->content - + ((const my_struct *)b)->content; +} + +static int merge_structs(void **old_raw, void *new) +{ + my_struct *old = *(my_struct **)old_raw; + cl_assert(((my_struct *)old)->content == ((my_struct *)new)->content); + ((my_struct *)old)->count += 1; + git__free(new); + _struct_count--; + return GIT_EEXISTS; +} + +static my_struct *alloc_struct(int value) +{ + my_struct *st = git__malloc(sizeof(my_struct)); + st->content = value; + st->count = 0; + _struct_count++; + return st; +} + +/* insert_sorted with duplicates and special handling */ +void test_vector__5(void) +{ + git_vector x; + int i; + + cl_git_pass(git_vector_init(&x, 1, &compare_structs)); + + for (i = 0; i < 10; i += 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + for (i = 9; i > 0; i -= 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + cl_assert(x.length == 10); + cl_assert(_struct_count == 10); + + for (i = 0; i < 10; i += 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + for (i = 9; i > 0; i -= 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + cl_assert(x.length == 10); + cl_assert(_struct_count == 10); + + for (i = 0; i < 10; ++i) { + cl_assert(((my_struct *)git_vector_get(&x, i))->content == i); + git__free(git_vector_get(&x, i)); + _struct_count--; + } + + git_vector_free(&x); +} + +static int remove_ones(const git_vector *v, size_t idx, void *p) +{ + GIT_UNUSED(p); + return (git_vector_get(v, idx) == (void *)0x001); +} + +/* Test removal based on callback */ +void test_vector__remove_matching(void) +{ + git_vector x; + size_t i; + void *compare; + + cl_git_pass(git_vector_init(&x, 1, NULL)); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 1); + git_vector_remove_matching(&x, remove_ones, NULL); + cl_assert(x.length == 0); + + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 3); + git_vector_remove_matching(&x, remove_ones, NULL); + cl_assert(x.length == 0); + + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones, NULL); + cl_assert(x.length == 2); + + git_vector_foreach(&x, i, compare) { + cl_assert(compare != (void *)0x001); + } + + git_vector_clear(&x); + + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones, NULL); + cl_assert(x.length == 2); + + git_vector_foreach(&x, i, compare) { + cl_assert(compare != (void *)0x001); + } + + git_vector_clear(&x); + + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones, NULL); + cl_assert(x.length == 2); + + git_vector_foreach(&x, i, compare) { + cl_assert(compare != (void *)0x001); + } + + git_vector_clear(&x); + + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x003); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x003); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones, NULL); + cl_assert(x.length == 4); + + git_vector_free(&x); +} + +static void assert_vector(git_vector *x, void *expected[], size_t len) +{ + size_t i; + + cl_assert_equal_i(len, x->length); + + for (i = 0; i < len; i++) + cl_assert(expected[i] == x->contents[i]); +} + +void test_vector__grow_and_shrink(void) +{ + git_vector x = GIT_VECTOR_INIT; + void *expected1[] = { + (void *)0x02, (void *)0x03, (void *)0x04, (void *)0x05, + (void *)0x06, (void *)0x07, (void *)0x08, (void *)0x09, + (void *)0x0a + }; + void *expected2[] = { + (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06, + (void *)0x07, (void *)0x08, (void *)0x09, (void *)0x0a + }; + void *expected3[] = { + (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06, + (void *)0x0a + }; + void *expected4[] = { + (void *)0x02, (void *)0x04, (void *)0x05 + }; + void *expected5[] = { + (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, + (void *)0x05 + }; + void *expected6[] = { + (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, + (void *)0x05, (void *)0x00 + }; + void *expected7[] = { + (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, + (void *)0x00, (void *)0x00, (void *)0x00, (void *)0x05, + (void *)0x00 + }; + void *expected8[] = { + (void *)0x04, (void *)0x00, (void *)0x00, (void *)0x00, + (void *)0x05, (void *)0x00 + }; + void *expected9[] = { + (void *)0x04, (void *)0x00, (void *)0x05, (void *)0x00 + }; + void *expectedA[] = { (void *)0x04, (void *)0x00 }; + void *expectedB[] = { (void *)0x04 }; + + git_vector_insert(&x, (void *)0x01); + git_vector_insert(&x, (void *)0x02); + git_vector_insert(&x, (void *)0x03); + git_vector_insert(&x, (void *)0x04); + git_vector_insert(&x, (void *)0x05); + git_vector_insert(&x, (void *)0x06); + git_vector_insert(&x, (void *)0x07); + git_vector_insert(&x, (void *)0x08); + git_vector_insert(&x, (void *)0x09); + git_vector_insert(&x, (void *)0x0a); + + git_vector_remove_range(&x, 0, 1); + assert_vector(&x, expected1, ARRAY_SIZE(expected1)); + + git_vector_remove_range(&x, 1, 1); + assert_vector(&x, expected2, ARRAY_SIZE(expected2)); + + git_vector_remove_range(&x, 4, 3); + assert_vector(&x, expected3, ARRAY_SIZE(expected3)); + + git_vector_remove_range(&x, 3, 2); + assert_vector(&x, expected4, ARRAY_SIZE(expected4)); + + git_vector_insert_null(&x, 0, 2); + assert_vector(&x, expected5, ARRAY_SIZE(expected5)); + + git_vector_insert_null(&x, 5, 1); + assert_vector(&x, expected6, ARRAY_SIZE(expected6)); + + git_vector_insert_null(&x, 4, 3); + assert_vector(&x, expected7, ARRAY_SIZE(expected7)); + + git_vector_remove_range(&x, 0, 3); + assert_vector(&x, expected8, ARRAY_SIZE(expected8)); + + git_vector_remove_range(&x, 1, 2); + assert_vector(&x, expected9, ARRAY_SIZE(expected9)); + + git_vector_remove_range(&x, 2, 2); + assert_vector(&x, expectedA, ARRAY_SIZE(expectedA)); + + git_vector_remove_range(&x, 1, 1); + assert_vector(&x, expectedB, ARRAY_SIZE(expectedB)); + + git_vector_remove_range(&x, 0, 1); + assert_vector(&x, NULL, 0); + + git_vector_free(&x); +} + +void test_vector__reverse(void) +{ + git_vector v = GIT_VECTOR_INIT; + size_t i; + + void *in1[] = {(void *) 0x0, (void *) 0x1, (void *) 0x2, (void *) 0x3}; + void *out1[] = {(void *) 0x3, (void *) 0x2, (void *) 0x1, (void *) 0x0}; + + void *in2[] = {(void *) 0x0, (void *) 0x1, (void *) 0x2, (void *) 0x3, (void *) 0x4}; + void *out2[] = {(void *) 0x4, (void *) 0x3, (void *) 0x2, (void *) 0x1, (void *) 0x0}; + + for (i = 0; i < 4; i++) + cl_git_pass(git_vector_insert(&v, in1[i])); + + git_vector_reverse(&v); + + for (i = 0; i < 4; i++) + cl_assert_equal_p(out1[i], git_vector_get(&v, i)); + + git_vector_clear(&v); + for (i = 0; i < 5; i++) + cl_git_pass(git_vector_insert(&v, in2[i])); + + git_vector_reverse(&v); + + for (i = 0; i < 5; i++) + cl_assert_equal_p(out2[i], git_vector_get(&v, i)); + + git_vector_free(&v); +} + +void test_vector__dup_empty_vector(void) +{ + git_vector v = GIT_VECTOR_INIT; + git_vector dup = GIT_VECTOR_INIT; + int dummy; + + cl_assert_equal_i(0, v.length); + + cl_git_pass(git_vector_dup(&dup, &v, v._cmp)); + cl_assert_equal_i(0, dup._alloc_size); + cl_assert_equal_i(0, dup.length); + + cl_git_pass(git_vector_insert(&dup, &dummy)); + cl_assert_equal_i(8, dup._alloc_size); + cl_assert_equal_i(1, dup.length); + + git_vector_free(&dup); +} diff --git a/tests/util/wildmatch.c b/tests/util/wildmatch.c new file mode 100644 index 000000000..a5af61ab4 --- /dev/null +++ b/tests/util/wildmatch.c @@ -0,0 +1,248 @@ +#include "clar_libgit2.h" + +#include "wildmatch.h" + +#define assert_matches(string, pattern, wildmatch, iwildmatch, pathmatch, ipathmatch) \ + assert_matches_(string, pattern, wildmatch, iwildmatch, pathmatch, ipathmatch, __FILE__, __func__, __LINE__) + +static void assert_matches_(const char *string, const char *pattern, + char expected_wildmatch, char expected_iwildmatch, + char expected_pathmatch, char expected_ipathmatch, + const char *file, const char *func, size_t line) +{ + if (wildmatch(pattern, string, WM_PATHNAME) == expected_wildmatch) + clar__fail(file, func, line, "Test failed (wildmatch).", string, 1); + if (wildmatch(pattern, string, WM_PATHNAME|WM_CASEFOLD) == expected_iwildmatch) + clar__fail(file, func, line, "Test failed (iwildmatch).", string, 1); + if (wildmatch(pattern, string, 0) == expected_pathmatch) + clar__fail(file, func, line, "Test failed (pathmatch).", string, 1); + if (wildmatch(pattern, string, WM_CASEFOLD) == expected_ipathmatch) + clar__fail(file, func, line, "Test failed (ipathmatch).", string, 1); +} + +/* + * Below testcases are imported from git.git, t3070-wildmatch,sh at tag v2.22.0. + * Note that we've only imported the direct wildcard tests, but not the matching + * tests for git-ls-files. + */ + +void test_wildmatch__basic_wildmatch(void) +{ + assert_matches("foo", "foo", 1, 1, 1, 1); + assert_matches("foo", "bar", 0, 0, 0, 0); + assert_matches("", "", 1, 1, 1, 1); + assert_matches("foo", "???", 1, 1, 1, 1); + assert_matches("foo", "??", 0, 0, 0, 0); + assert_matches("foo", "*", 1, 1, 1, 1); + assert_matches("foo", "f*", 1, 1, 1, 1); + assert_matches("foo", "*f", 0, 0, 0, 0); + assert_matches("foo", "*foo*", 1, 1, 1, 1); + assert_matches("foobar", "*ob*a*r*", 1, 1, 1, 1); + assert_matches("aaaaaaabababab", "*ab", 1, 1, 1, 1); + assert_matches("foo*", "foo\\*", 1, 1, 1, 1); + assert_matches("foobar", "foo\\*bar", 0, 0, 0, 0); + assert_matches("f\\oo", "f\\\\oo", 1, 1, 1, 1); + assert_matches("ball", "*[al]?", 1, 1, 1, 1); + assert_matches("ten", "[ten]", 0, 0, 0, 0); + assert_matches("ten", "**[!te]", 1, 1, 1, 1); + assert_matches("ten", "**[!ten]", 0, 0, 0, 0); + assert_matches("ten", "t[a-g]n", 1, 1, 1, 1); + assert_matches("ten", "t[!a-g]n", 0, 0, 0, 0); + assert_matches("ton", "t[!a-g]n", 1, 1, 1, 1); + assert_matches("ton", "t[^a-g]n", 1, 1, 1, 1); + assert_matches("a]b", "a[]]b", 1, 1, 1, 1); + assert_matches("a-b", "a[]-]b", 1, 1, 1, 1); + assert_matches("a]b", "a[]-]b", 1, 1, 1, 1); + assert_matches("aab", "a[]-]b", 0, 0, 0, 0); + assert_matches("aab", "a[]a-]b", 1, 1, 1, 1); + assert_matches("]", "]", 1, 1, 1, 1); +} + +void test_wildmatch__slash_matching_features(void) +{ + assert_matches("foo/baz/bar", "foo*bar", 0, 0, 1, 1); + assert_matches("foo/baz/bar", "foo**bar", 0, 0, 1, 1); + assert_matches("foobazbar", "foo**bar", 1, 1, 1, 1); + assert_matches("foo/baz/bar", "foo/**/bar", 1, 1, 1, 1); + assert_matches("foo/baz/bar", "foo/**/**/bar", 1, 1, 0, 0); + assert_matches("foo/b/a/z/bar", "foo/**/bar", 1, 1, 1, 1); + assert_matches("foo/b/a/z/bar", "foo/**/**/bar", 1, 1, 1, 1); + assert_matches("foo/bar", "foo/**/bar", 1, 1, 0, 0); + assert_matches("foo/bar", "foo/**/**/bar", 1, 1, 0, 0); + assert_matches("foo/bar", "foo?bar", 0, 0, 1, 1); + assert_matches("foo/bar", "foo[/]bar", 0, 0, 1, 1); + assert_matches("foo/bar", "foo[^a-z]bar", 0, 0, 1, 1); + assert_matches("foo/bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r", 0, 0, 1, 1); + assert_matches("foo-bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r", 1, 1, 1, 1); + assert_matches("foo", "**/foo", 1, 1, 0, 0); + assert_matches("XXX/foo", "**/foo", 1, 1, 1, 1); + assert_matches("bar/baz/foo", "**/foo", 1, 1, 1, 1); + assert_matches("bar/baz/foo", "*/foo", 0, 0, 1, 1); + assert_matches("foo/bar/baz", "**/bar*", 0, 0, 1, 1); + assert_matches("deep/foo/bar/baz", "**/bar/*", 1, 1, 1, 1); + assert_matches("deep/foo/bar/baz/", "**/bar/*", 0, 0, 1, 1); + assert_matches("deep/foo/bar/baz/", "**/bar/**", 1, 1, 1, 1); + assert_matches("deep/foo/bar", "**/bar/*", 0, 0, 0, 0); + assert_matches("deep/foo/bar/", "**/bar/**", 1, 1, 1, 1); + assert_matches("foo/bar/baz", "**/bar**", 0, 0, 1, 1); + assert_matches("foo/bar/baz/x", "*/bar/**", 1, 1, 1, 1); + assert_matches("deep/foo/bar/baz/x", "*/bar/**", 0, 0, 1, 1); + assert_matches("deep/foo/bar/baz/x", "**/bar/*/*", 1, 1, 1, 1); +} + +void test_wildmatch__various_additional(void) +{ + assert_matches("acrt", "a[c-c]st", 0, 0, 0, 0); + assert_matches("acrt", "a[c-c]rt", 1, 1, 1, 1); + assert_matches("]", "[!]-]", 0, 0, 0, 0); + assert_matches("a", "[!]-]", 1, 1, 1, 1); + assert_matches("", "\\", 0, 0, 0, 0); + assert_matches("\\", "\\", 0, 0, 0, 0); + assert_matches("XXX/\\", "*/\\", 0, 0, 0, 0); + assert_matches("XXX/\\", "*/\\\\", 1, 1, 1, 1); + assert_matches("foo", "foo", 1, 1, 1, 1); + assert_matches("@foo", "@foo", 1, 1, 1, 1); + assert_matches("foo", "@foo", 0, 0, 0, 0); + assert_matches("[ab]", "\\[ab]", 1, 1, 1, 1); + assert_matches("[ab]", "[[]ab]", 1, 1, 1, 1); + assert_matches("[ab]", "[[:]ab]", 1, 1, 1, 1); + assert_matches("[ab]", "[[::]ab]", 0, 0, 0, 0); + assert_matches("[ab]", "[[:digit]ab]", 1, 1, 1, 1); + assert_matches("[ab]", "[\\[:]ab]", 1, 1, 1, 1); + assert_matches("?a?b", "\\??\\?b", 1, 1, 1, 1); + assert_matches("abc", "\\a\\b\\c", 1, 1, 1, 1); + assert_matches("foo", "", 0, 0, 0, 0); + assert_matches("foo/bar/baz/to", "**/t[o]", 1, 1, 1, 1); +} + +void test_wildmatch__character_classes(void) +{ + assert_matches("a1B", "[[:alpha:]][[:digit:]][[:upper:]]", 1, 1, 1, 1); + assert_matches("a", "[[:digit:][:upper:][:space:]]", 0, 1, 0, 1); + assert_matches("A", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); + assert_matches("1", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); + assert_matches("1", "[[:digit:][:upper:][:spaci:]]", 0, 0, 0, 0); + assert_matches(" ", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); + assert_matches(".", "[[:digit:][:upper:][:space:]]", 0, 0, 0, 0); + assert_matches(".", "[[:digit:][:punct:][:space:]]", 1, 1, 1, 1); + assert_matches("5", "[[:xdigit:]]", 1, 1, 1, 1); + assert_matches("f", "[[:xdigit:]]", 1, 1, 1, 1); + assert_matches("D", "[[:xdigit:]]", 1, 1, 1, 1); + assert_matches("_", "[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]", 1, 1, 1, 1); + assert_matches(".", "[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]", 1, 1, 1, 1); + assert_matches("5", "[a-c[:digit:]x-z]", 1, 1, 1, 1); + assert_matches("b", "[a-c[:digit:]x-z]", 1, 1, 1, 1); + assert_matches("y", "[a-c[:digit:]x-z]", 1, 1, 1, 1); + assert_matches("q", "[a-c[:digit:]x-z]", 0, 0, 0, 0); +} + +void test_wildmatch__additional_with_malformed(void) +{ + assert_matches("]", "[\\\\-^]", 1, 1, 1, 1); + assert_matches("[", "[\\\\-^]", 0, 0, 0, 0); + assert_matches("-", "[\\-_]", 1, 1, 1, 1); + assert_matches("]", "[\\]]", 1, 1, 1, 1); + assert_matches("\\]", "[\\]]", 0, 0, 0, 0); + assert_matches("\\", "[\\]]", 0, 0, 0, 0); + assert_matches("ab", "a[]b", 0, 0, 0, 0); + assert_matches("a[]b", "a[]b", 0, 0, 0, 0); + assert_matches("ab[", "ab[", 0, 0, 0, 0); + assert_matches("ab", "[!", 0, 0, 0, 0); + assert_matches("ab", "[-", 0, 0, 0, 0); + assert_matches("-", "[-]", 1, 1, 1, 1); + assert_matches("-", "[a-", 0, 0, 0, 0); + assert_matches("-", "[!a-", 0, 0, 0, 0); + assert_matches("-", "[--A]", 1, 1, 1, 1); + assert_matches("5", "[--A]", 1, 1, 1, 1); + assert_matches(" ", "[ --]", 1, 1, 1, 1); + assert_matches("$", "[ --]", 1, 1, 1, 1); + assert_matches("-", "[ --]", 1, 1, 1, 1); + assert_matches("0", "[ --]", 0, 0, 0, 0); + assert_matches("-", "[---]", 1, 1, 1, 1); + assert_matches("-", "[------]", 1, 1, 1, 1); + assert_matches("j", "[a-e-n]", 0, 0, 0, 0); + assert_matches("-", "[a-e-n]", 1, 1, 1, 1); + assert_matches("a", "[!------]", 1, 1, 1, 1); + assert_matches("[", "[]-a]", 0, 0, 0, 0); + assert_matches("^", "[]-a]", 1, 1, 1, 1); + assert_matches("^", "[!]-a]", 0, 0, 0, 0); + assert_matches("[", "[!]-a]", 1, 1, 1, 1); + assert_matches("^", "[a^bc]", 1, 1, 1, 1); + assert_matches("-b]", "[a-]b]", 1, 1, 1, 1); + assert_matches("\\", "[\\]", 0, 0, 0, 0); + assert_matches("\\", "[\\\\]", 1, 1, 1, 1); + assert_matches("\\", "[!\\\\]", 0, 0, 0, 0); + assert_matches("G", "[A-\\\\]", 1, 1, 1, 1); + assert_matches("aaabbb", "b*a", 0, 0, 0, 0); + assert_matches("aabcaa", "*ba*", 0, 0, 0, 0); + assert_matches(",", "[,]", 1, 1, 1, 1); + assert_matches(",", "[\\\\,]", 1, 1, 1, 1); + assert_matches("\\", "[\\\\,]", 1, 1, 1, 1); + assert_matches("-", "[,-.]", 1, 1, 1, 1); + assert_matches("+", "[,-.]", 0, 0, 0, 0); + assert_matches("-.]", "[,-.]", 0, 0, 0, 0); + assert_matches("2", "[\\1-\\3]", 1, 1, 1, 1); + assert_matches("3", "[\\1-\\3]", 1, 1, 1, 1); + assert_matches("4", "[\\1-\\3]", 0, 0, 0, 0); + assert_matches("\\", "[[-\\]]", 1, 1, 1, 1); + assert_matches("[", "[[-\\]]", 1, 1, 1, 1); + assert_matches("]", "[[-\\]]", 1, 1, 1, 1); + assert_matches("-", "[[-\\]]", 0, 0, 0, 0); +} + +void test_wildmatch__recursion(void) +{ + assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 1, 1, 1, 1); + assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 0, 0, 0, 0); + assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 0, 0, 0, 0); + assert_matches("XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*", 1, 1, 1, 1); + assert_matches("XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*", 0, 0, 0, 0); + assert_matches("abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt", "**/*a*b*g*n*t", 1, 1, 1, 1); + assert_matches("abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz", "**/*a*b*g*n*t", 0, 0, 0, 0); + assert_matches("foo", "*/*/*", 0, 0, 0, 0); + assert_matches("foo/bar", "*/*/*", 0, 0, 0, 0); + assert_matches("foo/bba/arr", "*/*/*", 1, 1, 1, 1); + assert_matches("foo/bb/aa/rr", "*/*/*", 0, 0, 1, 1); + assert_matches("foo/bb/aa/rr", "**/**/**", 1, 1, 1, 1); + assert_matches("abcXdefXghi", "*X*i", 1, 1, 1, 1); + assert_matches("ab/cXd/efXg/hi", "*X*i", 0, 0, 1, 1); + assert_matches("ab/cXd/efXg/hi", "*/*X*/*/*i", 1, 1, 1, 1); + assert_matches("ab/cXd/efXg/hi", "**/*X*/**/*i", 1, 1, 1, 1); +} + +void test_wildmatch__pathmatch(void) +{ + assert_matches("foo", "fo", 0, 0, 0, 0); + assert_matches("foo/bar", "foo/bar", 1, 1, 1, 1); + assert_matches("foo/bar", "foo/*", 1, 1, 1, 1); + assert_matches("foo/bba/arr", "foo/*", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo/**", 1, 1, 1, 1); + assert_matches("foo/bba/arr", "foo*", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo**", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo/*arr", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo/**arr", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo/*z", 0, 0, 0, 0); + assert_matches("foo/bba/arr", "foo/**z", 0, 0, 0, 0); + assert_matches("foo/bar", "foo?bar", 0, 0, 1, 1); + assert_matches("foo/bar", "foo[/]bar", 0, 0, 1, 1); + assert_matches("foo/bar", "foo[^a-z]bar", 0, 0, 1, 1); + assert_matches("ab/cXd/efXg/hi", "*Xg*i", 0, 0, 1, 1); +} + +void test_wildmatch__case_sensitivity(void) +{ + assert_matches("a", "[A-Z]", 0, 1, 0, 1); + assert_matches("A", "[A-Z]", 1, 1, 1, 1); + assert_matches("A", "[a-z]", 0, 1, 0, 1); + assert_matches("a", "[a-z]", 1, 1, 1, 1); + assert_matches("a", "[[:upper:]]", 0, 1, 0, 1); + assert_matches("A", "[[:upper:]]", 1, 1, 1, 1); + assert_matches("A", "[[:lower:]]", 0, 1, 0, 1); + assert_matches("a", "[[:lower:]]", 1, 1, 1, 1); + assert_matches("A", "[B-Za]", 0, 1, 0, 1); + assert_matches("a", "[B-Za]", 1, 1, 1, 1); + assert_matches("A", "[B-a]", 0, 1, 0, 1); + assert_matches("a", "[B-a]", 1, 1, 1, 1); + assert_matches("z", "[Z-y]", 0, 1, 0, 1); + assert_matches("Z", "[Z-y]", 1, 1, 1, 1); +} diff --git a/tests/util/zstream.c b/tests/util/zstream.c new file mode 100644 index 000000000..5c89895c7 --- /dev/null +++ b/tests/util/zstream.c @@ -0,0 +1,167 @@ +#include "clar_libgit2.h" +#include "zstream.h" + +static const char *data = "This is a test test test of This is a test"; + +#define INFLATE_EXTRA 2 + +static void assert_zlib_equal_( + const void *expected, size_t e_len, + const void *compressed, size_t c_len, + const char *msg, const char *file, const char *func, int line) +{ + z_stream stream; + char *expanded = git__calloc(1, e_len + INFLATE_EXTRA); + cl_assert(expanded); + + memset(&stream, 0, sizeof(stream)); + stream.next_out = (Bytef *)expanded; + stream.avail_out = (uInt)(e_len + INFLATE_EXTRA); + stream.next_in = (Bytef *)compressed; + stream.avail_in = (uInt)c_len; + + cl_assert(inflateInit(&stream) == Z_OK); + cl_assert(inflate(&stream, Z_FINISH)); + inflateEnd(&stream); + + clar__assert_equal( + file, func, line, msg, 1, + "%d", (int)stream.total_out, (int)e_len); + clar__assert_equal( + file, func, line, "Buffer len was not exact match", 1, + "%d", (int)stream.avail_out, (int)INFLATE_EXTRA); + + clar__assert( + memcmp(expanded, expected, e_len) == 0, + file, func, line, "uncompressed data did not match", NULL, 1); + + git__free(expanded); +} + +#define assert_zlib_equal(E,EL,C,CL) \ + assert_zlib_equal_(E, EL, C, CL, #EL " != " #CL, __FILE__, __func__, (int)__LINE__) + +void test_zstream__basic(void) +{ + git_zstream z = GIT_ZSTREAM_INIT; + char out[128]; + size_t outlen = sizeof(out); + + cl_git_pass(git_zstream_init(&z, GIT_ZSTREAM_DEFLATE)); + cl_git_pass(git_zstream_set_input(&z, data, strlen(data) + 1)); + cl_git_pass(git_zstream_get_output(out, &outlen, &z)); + cl_assert(git_zstream_done(&z)); + cl_assert(outlen > 0); + git_zstream_free(&z); + + assert_zlib_equal(data, strlen(data) + 1, out, outlen); +} + +void test_zstream__fails_on_trailing_garbage(void) +{ + git_str deflated = GIT_STR_INIT, inflated = GIT_STR_INIT; + char i = 0; + + /* compress a simple string */ + git_zstream_deflatebuf(&deflated, "foobar!!", 8); + + /* append some garbage */ + for (i = 0; i < 10; i++) { + git_str_putc(&deflated, i); + } + + cl_git_fail(git_zstream_inflatebuf(&inflated, deflated.ptr, deflated.size)); + + git_str_dispose(&deflated); + git_str_dispose(&inflated); +} + +void test_zstream__buffer(void) +{ + git_str out = GIT_STR_INIT; + cl_git_pass(git_zstream_deflatebuf(&out, data, strlen(data) + 1)); + assert_zlib_equal(data, strlen(data) + 1, out.ptr, out.size); + git_str_dispose(&out); +} + +#define BIG_STRING_PART "Big Data IS Big - Long Data IS Long - We need a buffer larger than 1024 x 1024 to make sure we trigger chunked compression - Big Big Data IS Bigger than Big - Long Long Data IS Longer than Long" + +static void compress_and_decompress_input_various_ways(git_str *input) +{ + git_str out1 = GIT_STR_INIT, out2 = GIT_STR_INIT; + git_str inflated = GIT_STR_INIT; + size_t i, fixed_size = max(input->size / 2, 256); + char *fixed = git__malloc(fixed_size); + cl_assert(fixed); + + /* compress with deflatebuf */ + + cl_git_pass(git_zstream_deflatebuf(&out1, input->ptr, input->size)); + assert_zlib_equal(input->ptr, input->size, out1.ptr, out1.size); + + /* compress with various fixed size buffer (accumulating the output) */ + + for (i = 0; i < 3; ++i) { + git_zstream zs = GIT_ZSTREAM_INIT; + size_t use_fixed_size; + + switch (i) { + case 0: use_fixed_size = 256; break; + case 1: use_fixed_size = fixed_size / 2; break; + case 2: use_fixed_size = fixed_size; break; + } + cl_assert(use_fixed_size <= fixed_size); + + cl_git_pass(git_zstream_init(&zs, GIT_ZSTREAM_DEFLATE)); + cl_git_pass(git_zstream_set_input(&zs, input->ptr, input->size)); + + while (!git_zstream_done(&zs)) { + size_t written = use_fixed_size; + cl_git_pass(git_zstream_get_output(fixed, &written, &zs)); + cl_git_pass(git_str_put(&out2, fixed, written)); + } + + git_zstream_free(&zs); + assert_zlib_equal(input->ptr, input->size, out2.ptr, out2.size); + + /* did both approaches give the same data? */ + cl_assert_equal_sz(out1.size, out2.size); + cl_assert(!memcmp(out1.ptr, out2.ptr, out1.size)); + + git_str_dispose(&out2); + } + + cl_git_pass(git_zstream_inflatebuf(&inflated, out1.ptr, out1.size)); + cl_assert_equal_i(input->size, inflated.size); + cl_assert(memcmp(input->ptr, inflated.ptr, inflated.size) == 0); + + git_str_dispose(&out1); + git_str_dispose(&inflated); + git__free(fixed); +} + +void test_zstream__big_data(void) +{ + git_str in = GIT_STR_INIT; + size_t scan, target; + + for (target = 1024; target <= 1024 * 1024 * 4; target *= 8) { + + /* make a big string that's easy to compress */ + git_str_clear(&in); + while (in.size < target) + cl_git_pass( + git_str_put(&in, BIG_STRING_PART, strlen(BIG_STRING_PART))); + + compress_and_decompress_input_various_ways(&in); + + /* make a big string that's hard to compress */ + srand(0xabad1dea); + for (scan = 0; scan < in.size; ++scan) + in.ptr[scan] = (char)rand(); + + compress_and_decompress_input_various_ways(&in); + } + + git_str_dispose(&in); +} -- cgit v1.2.1 From d02f4f7ad73d91504b9c9eb096989684edbc3417 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 17 Nov 2021 14:31:29 -0500 Subject: cmake: refactor `add_clar_test` into separate module --- ci/test.sh | 4 ---- cmake/AddClarTest.cmake | 7 +++++++ tests/libgit2/CMakeLists.txt | 27 ++++++++++----------------- tests/util/CMakeLists.txt | 11 ++--------- 4 files changed, 19 insertions(+), 30 deletions(-) create mode 100644 cmake/AddClarTest.cmake diff --git a/ci/test.sh b/ci/test.sh index ec2151987..0815522a9 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -156,10 +156,6 @@ fi # Run the tests that do not require network connectivity. -if [ -z "$SKIP_UTILITY_TESTS" ]; then - run_test util -fi - if [ -z "$SKIP_OFFLINE_TESTS" ]; then echo "" echo "##############################################################################" diff --git a/cmake/AddClarTest.cmake b/cmake/AddClarTest.cmake new file mode 100644 index 000000000..743941638 --- /dev/null +++ b/cmake/AddClarTest.cmake @@ -0,0 +1,7 @@ +function(ADD_CLAR_TEST project name) + if(NOT USE_LEAK_CHECKER STREQUAL "OFF") + add_test(${name} "${PROJECT_SOURCE_DIR}/script/${USE_LEAK_CHECKER}.sh" "${PROJECT_BINARY_DIR}/${project}" ${ARGN}) + else() + add_test(${name} "${PROJECT_BINARY_DIR}/${project}" ${ARGN}) + endif() +endfunction(ADD_CLAR_TEST) diff --git a/tests/libgit2/CMakeLists.txt b/tests/libgit2/CMakeLists.txt index 90ae6253e..27f421ad6 100644 --- a/tests/libgit2/CMakeLists.txt +++ b/tests/libgit2/CMakeLists.txt @@ -63,20 +63,13 @@ if(MSVC_IDE) set_source_files_properties("precompiled.c" COMPILE_FLAGS "/Ycprecompiled.h") endif() -function(ADD_CLAR_TEST name) - if(NOT USE_LEAK_CHECKER STREQUAL "OFF") - add_test(${name} "${PROJECT_SOURCE_DIR}/script/${USE_LEAK_CHECKER}.sh" "${PROJECT_BINARY_DIR}/libgit2_tests" ${ARGN}) - else() - add_test(${name} "${PROJECT_BINARY_DIR}/libgit2_tests" ${ARGN}) - endif() -endfunction(ADD_CLAR_TEST) - -add_clar_test(offline -v -xonline) -add_clar_test(invasive -v -score::ftruncate -sfilter::stream::bigfile -sodb::largefiles -siterator::workdir::filesystem_gunk -srepo::init -srepo::init::at_filesystem_root) -add_clar_test(online -v -sonline -xonline::customcert) -add_clar_test(online_customcert -v -sonline::customcert) -add_clar_test(gitdaemon -v -sonline::push) -add_clar_test(ssh -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths -sonline::clone::path_whitespace_ssh) -add_clar_test(proxy -v -sonline::clone::proxy) -add_clar_test(auth_clone -v -sonline::clone::cred) -add_clar_test(auth_clone_and_push -v -sonline::clone::push -sonline::push) +include(AddClarTest) +add_clar_test(libgit2_tests offline -v -xonline) +add_clar_test(libgit2_tests invasive -v -score::ftruncate -sfilter::stream::bigfile -sodb::largefiles -siterator::workdir::filesystem_gunk -srepo::init -srepo::init::at_filesystem_root) +add_clar_test(libgit2_tests online -v -sonline -xonline::customcert) +add_clar_test(libgit2_tests online_customcert -v -sonline::customcert) +add_clar_test(libgit2_tests gitdaemon -v -sonline::push) +add_clar_test(libgit2_tests ssh -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths -sonline::clone::path_whitespace_ssh) +add_clar_test(libgit2_tests proxy -v -sonline::clone::proxy) +add_clar_test(libgit2_tests auth_clone -v -sonline::clone::cred) +add_clar_test(libgit2_tests auth_clone_and_push -v -sonline::clone::push -sonline::push) diff --git a/tests/util/CMakeLists.txt b/tests/util/CMakeLists.txt index 739eb5859..232590ffd 100644 --- a/tests/util/CMakeLists.txt +++ b/tests/util/CMakeLists.txt @@ -62,14 +62,7 @@ if(MSVC_IDE) set_source_files_properties("precompiled.c" COMPILE_FLAGS "/Ycprecompiled.h") endif() -function(ADD_CLAR_TEST name) - if(NOT USE_LEAK_CHECKER STREQUAL "OFF") - add_test(${name} "${libgit2_SOURCE_DIR}/script/${USE_LEAK_CHECKER}.sh" "${libgit2_BINARY_DIR}/util_tests" ${ARGN}) - else() - add_test(${name} "${libgit2_BINARY_DIR}/util_tests" ${ARGN}) - endif() -endfunction(ADD_CLAR_TEST) - enable_testing() -add_clar_test(util -v) +include(AddClarTest) +add_clar_test(util_tests util -v) -- cgit v1.2.1 From 3a3ab065f0685202c854e13708ddfd2a93d75e2c Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 3 May 2020 23:13:28 +0100 Subject: cli: infrastructure for a cli project Introduce a command-line interface for libgit2. The goal is for it to be git-compatible. 1. The libgit2 developers can more easily dogfood libgit2 to find bugs, and performance issues. 2. There is growing usage of libgit2's examples as a client; libgit2's examples should be exactly that - simple code samples that illustrate libgit2's usage. This satisfies that need directly. 3. By producing a client ourselves, we can better understand the needs of client creators, possibly producing a shared "middleware" for commonly-used pieces of client functionality like interacting with external tools. 4. Since git is the reference implementation, we may be able to benefit from git's unit tests, running their test suite against our CLI to ensure correct behavior. This commit introduces a simple infrastructure for the CLI. The CLI is currently links libgit2 statically; this is because the utility layer is required for libgit2 _but_ shares the error state handling with libgit2 itself. There's no obviously good solution here without introducing annoying indirection or more complexity. Until we can untangle that dependency, this is a good step forward. In the meantime, we link the libgit2 object files, but we do not include the (private) libgit2 headers. This constrains the CLI to the public libgit2 interfaces. --- CMakeLists.txt | 1 + src/CMakeLists.txt | 4 + src/README.md | 2 + src/cli/CMakeLists.txt | 53 ++++ src/cli/README.md | 3 + src/cli/cli.h | 18 ++ src/cli/error.h | 51 +++ src/cli/main.c | 51 +++ src/cli/opt.c | 750 ++++++++++++++++++++++++++++++++++++++++++++ src/cli/opt.h | 362 +++++++++++++++++++++ src/cli/win32/precompiled.c | 1 + src/cli/win32/precompiled.h | 3 + 12 files changed, 1299 insertions(+) create mode 100644 src/cli/CMakeLists.txt create mode 100644 src/cli/README.md create mode 100644 src/cli/cli.h create mode 100644 src/cli/error.h create mode 100644 src/cli/main.c create mode 100644 src/cli/opt.c create mode 100644 src/cli/opt.h create mode 100644 src/cli/win32/precompiled.c create mode 100644 src/cli/win32/precompiled.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 90ecc92fe..763bd437b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") # Optional subsystems option(BUILD_SHARED_LIBS "Build Shared Library (OFF for Static)" ON) option(BUILD_TESTS "Build Tests using the Clar suite" ON) +option(BUILD_CLI "Build the command-line interface" ON) option(BUILD_EXAMPLES "Build library usage example apps" OFF) option(BUILD_FUZZERS "Build the fuzz targets" OFF) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8b0d7f443..72ec410fc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -178,6 +178,10 @@ configure_file(features.h.in git2/sys/features.h) add_subdirectory(libgit2) add_subdirectory(util) +if(BUILD_CLI) + add_subdirectory(cli) +endif() + # re-export these to the root so that peer projects (tests, fuzzers, # examples) can use them set(LIBGIT2_INCLUDES ${LIBGIT2_INCLUDES} PARENT_SCOPE) diff --git a/src/README.md b/src/README.md index 12e0d0e43..10b86c1dc 100644 --- a/src/README.md +++ b/src/README.md @@ -3,6 +3,8 @@ This is the source that makes up the core of libgit2 and its related projects. +* `cli` + A git-compatible command-line interface that uses libgit2. * `libgit2` This is the libgit2 project, a cross-platform, linkable library implementation of Git that you can use in your application. diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt new file mode 100644 index 000000000..4f347e93f --- /dev/null +++ b/src/cli/CMakeLists.txt @@ -0,0 +1,53 @@ +set(CLI_INCLUDES + "${libgit2_BINARY_DIR}/src" + "${libgit2_SOURCE_DIR}/src/util" + "${libgit2_SOURCE_DIR}/src/cli" + "${libgit2_SOURCE_DIR}/include") + +if(WIN32 AND NOT CYGWIN) + file(GLOB CLI_SRC_OS win32/*.c) + list(SORT CLI_SRC_OS) +else() + file(GLOB CLI_SRC_OS unix/*.c) + list(SORT CLI_SRC_OS) +endif() + +file(GLOB CLI_SRC_C *.c *.h) +list(SORT CLI_SRC_C) + +# +# The CLI currently needs to be statically linked against libgit2 because +# the utility library uses libgit2's thread-local error buffers. TODO: +# remove this dependency and allow us to dynamically link against libgit2. +# + +if(BUILD_CLI STREQUAL "dynamic") + set(CLI_LIBGIT2_LIBRARY libgit2package) +else() + set(CLI_LIBGIT2_OBJECTS $) +endif() + +# +# Compile and link the CLI +# + +add_executable(git2_cli ${CLI_SRC_C} ${CLI_SRC_OS} ${CLI_OBJECTS} + $ + ${CLI_LIBGIT2_OBJECTS} + ${LIBGIT2_DEPENDENCY_OBJECTS}) +target_link_libraries(git2_cli ${CLI_LIBGIT2_LIBRARY} ${LIBGIT2_SYSTEM_LIBS}) + +set_target_properties(git2_cli PROPERTIES C_STANDARD 90) +set_target_properties(git2_cli PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR}) + +ide_split_sources(git2_cli) + +target_include_directories(git2_cli PRIVATE ${CLI_INCLUDES}) + +if(MSVC_IDE) + # Precompiled headers + set_target_properties(git2_cli PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") + set_source_files_properties(win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h") +endif() + +install(TARGETS git2_cli RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/cli/README.md b/src/cli/README.md new file mode 100644 index 000000000..eefd2ff27 --- /dev/null +++ b/src/cli/README.md @@ -0,0 +1,3 @@ +# cli + +A git-compatible command-line interface that uses libgit2. diff --git a/src/cli/cli.h b/src/cli/cli.h new file mode 100644 index 000000000..a27081d87 --- /dev/null +++ b/src/cli/cli.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef CLI_cli_h__ +#define CLI_cli_h__ + +#define PROGRAM_NAME "git2" + +#include "git2_util.h" + +#include "error.h" +#include "opt.h" + +#endif /* CLI_cli_h__ */ diff --git a/src/cli/error.h b/src/cli/error.h new file mode 100644 index 000000000..cce7a54c0 --- /dev/null +++ b/src/cli/error.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef CLI_error_h__ +#define CLI_error_h__ + +#include "cli.h" +#include + +#define CLI_EXIT_OK 0 +#define CLI_EXIT_ERROR 1 +#define CLI_EXIT_OS 128 +#define CLI_EXIT_GIT 128 +#define CLI_EXIT_USAGE 129 + +#define cli_error__print(fmt) do { \ + va_list ap; \ + va_start(ap, fmt); \ + fprintf(stderr, "%s: ", PROGRAM_NAME); \ + vfprintf(stderr, fmt, ap); \ + fprintf(stderr, "\n"); \ + va_end(ap); \ + } while(0) + +GIT_INLINE(int) cli_error(const char *fmt, ...) +{ + cli_error__print(fmt); + return CLI_EXIT_ERROR; +} + +GIT_INLINE(int) cli_error_usage(const char *fmt, ...) +{ + cli_error__print(fmt); + return CLI_EXIT_USAGE; +} + +GIT_INLINE(int) cli_error_git(void) +{ + const git_error *err = git_error_last(); + fprintf(stderr, "%s: %s\n", PROGRAM_NAME, + err ? err->message : "unknown error"); + return CLI_EXIT_GIT; +} + +#define cli_error_os() (perror(PROGRAM_NAME), CLI_EXIT_OS) + +#endif /* CLI_error_h__ */ diff --git a/src/cli/main.c b/src/cli/main.c new file mode 100644 index 000000000..709f6b452 --- /dev/null +++ b/src/cli/main.c @@ -0,0 +1,51 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include +#include "cli.h" + +static int show_version = 0; + +static const cli_opt_spec common_opts[] = { + { CLI_OPT_TYPE_SWITCH, "version", 0, &show_version, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "display the version" }, + { 0 } +}; + +int main(int argc, char **argv) +{ + cli_opt_parser optparser; + cli_opt opt; + int ret = 0; + + if (git_libgit2_init() < 0) { + cli_error("failed to initialize libgit2"); + exit(CLI_EXIT_GIT); + } + + cli_opt_parser_init(&optparser, common_opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU); + + /* Parse the top-level (common) options and command information */ + while (cli_opt_parser_next(&opt, &optparser)) { + if (!opt.spec) { + cli_opt_status_fprint(stderr, PROGRAM_NAME, &opt); + cli_opt_usage_fprint(stderr, PROGRAM_NAME, common_opts); + ret = CLI_EXIT_USAGE; + goto done; + } + } + + if (show_version) { + printf("%s version %s\n", PROGRAM_NAME, LIBGIT2_VERSION); + goto done; + } + +done: + git_libgit2_shutdown(); + return ret; +} diff --git a/src/cli/opt.c b/src/cli/opt.c new file mode 100644 index 000000000..11faa92c5 --- /dev/null +++ b/src/cli/opt.c @@ -0,0 +1,750 @@ +/* + * Copyright (c), Edward Thomson + * All rights reserved. + * + * This file is part of adopt, distributed under the MIT license. + * For full terms and conditions, see the included LICENSE file. + * + * THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT. + * + * This file was produced by using the `rename.pl` script included with + * adopt. The command-line specified was: + * + * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status + */ + +#include +#include +#include +#include +#include + +#include "cli.h" +#include "opt.h" + +#ifdef _WIN32 +# include +#else +# include +# include +#endif + +#ifdef _MSC_VER +# define alloca _alloca +#endif + +#define spec_is_option_type(x) \ + ((x)->type == CLI_OPT_TYPE_BOOL || \ + (x)->type == CLI_OPT_TYPE_SWITCH || \ + (x)->type == CLI_OPT_TYPE_VALUE) + +GIT_INLINE(const cli_opt_spec *) spec_for_long( + int *is_negated, + int *has_value, + const char **value, + const cli_opt_parser *parser, + const char *arg) +{ + const cli_opt_spec *spec; + char *eql; + size_t eql_pos; + + eql = strchr(arg, '='); + eql_pos = (eql = strchr(arg, '=')) ? (size_t)(eql - arg) : strlen(arg); + + for (spec = parser->specs; spec->type; ++spec) { + /* Handle -- (everything after this is literal) */ + if (spec->type == CLI_OPT_TYPE_LITERAL && arg[0] == '\0') + return spec; + + /* Handle --no-option arguments for bool types */ + if (spec->type == CLI_OPT_TYPE_BOOL && + strncmp(arg, "no-", 3) == 0 && + strcmp(arg + 3, spec->name) == 0) { + *is_negated = 1; + return spec; + } + + /* Handle the typical --option arguments */ + if (spec_is_option_type(spec) && + spec->name && + strcmp(arg, spec->name) == 0) + return spec; + + /* Handle --option=value arguments */ + if (spec->type == CLI_OPT_TYPE_VALUE && + eql && + strncmp(arg, spec->name, eql_pos) == 0 && + spec->name[eql_pos] == '\0') { + *has_value = 1; + *value = arg[eql_pos + 1] ? &arg[eql_pos + 1] : NULL; + return spec; + } + } + + return NULL; +} + +GIT_INLINE(const cli_opt_spec *) spec_for_short( + const char **value, + const cli_opt_parser *parser, + const char *arg) +{ + const cli_opt_spec *spec; + + for (spec = parser->specs; spec->type; ++spec) { + /* Handle -svalue short options with a value */ + if (spec->type == CLI_OPT_TYPE_VALUE && + arg[0] == spec->alias && + arg[1] != '\0') { + *value = &arg[1]; + return spec; + } + + /* Handle typical -s short options */ + if (arg[0] == spec->alias) { + *value = NULL; + return spec; + } + } + + return NULL; +} + +GIT_INLINE(const cli_opt_spec *) spec_for_arg(cli_opt_parser *parser) +{ + const cli_opt_spec *spec; + size_t args = 0; + + for (spec = parser->specs; spec->type; ++spec) { + if (spec->type == CLI_OPT_TYPE_ARG) { + if (args == parser->arg_idx) { + parser->arg_idx++; + return spec; + } + + args++; + } + + if (spec->type == CLI_OPT_TYPE_ARGS && args == parser->arg_idx) + return spec; + } + + return NULL; +} + +GIT_INLINE(int) spec_is_choice(const cli_opt_spec *spec) +{ + return ((spec + 1)->type && + ((spec + 1)->usage & CLI_OPT_USAGE_CHOICE)); +} + +/* + * If we have a choice with switches and bare arguments, and we see + * the switch, then we no longer expect the bare argument. + */ +GIT_INLINE(void) consume_choices(const cli_opt_spec *spec, cli_opt_parser *parser) +{ + /* back up to the beginning of the choices */ + while (spec->type && (spec->usage & CLI_OPT_USAGE_CHOICE)) + --spec; + + if (!spec_is_choice(spec)) + return; + + do { + if (spec->type == CLI_OPT_TYPE_ARG) + parser->arg_idx++; + ++spec; + } while(spec->type && (spec->usage & CLI_OPT_USAGE_CHOICE)); +} + +static cli_opt_status_t parse_long(cli_opt *opt, cli_opt_parser *parser) +{ + const cli_opt_spec *spec; + char *arg = parser->args[parser->idx++]; + const char *value = NULL; + int is_negated = 0, has_value = 0; + + opt->arg = arg; + + if ((spec = spec_for_long(&is_negated, &has_value, &value, parser, &arg[2])) == NULL) { + opt->spec = NULL; + opt->status = CLI_OPT_STATUS_UNKNOWN_OPTION; + goto done; + } + + opt->spec = spec; + + /* Future options parsed as literal */ + if (spec->type == CLI_OPT_TYPE_LITERAL) + parser->in_literal = 1; + + /* --bool or --no-bool */ + else if (spec->type == CLI_OPT_TYPE_BOOL && spec->value) + *((int *)spec->value) = !is_negated; + + /* --accumulate */ + else if (spec->type == CLI_OPT_TYPE_ACCUMULATOR && spec->value) + *((int *)spec->value) += spec->switch_value ? spec->switch_value : 1; + + /* --switch */ + else if (spec->type == CLI_OPT_TYPE_SWITCH && spec->value) + *((int *)spec->value) = spec->switch_value; + + /* Parse values as "--foo=bar" or "--foo bar" */ + else if (spec->type == CLI_OPT_TYPE_VALUE) { + if (has_value) + opt->value = (char *)value; + else if ((parser->idx + 1) <= parser->args_len) + opt->value = parser->args[parser->idx++]; + + if (spec->value) + *((char **)spec->value) = opt->value; + } + + /* Required argument was not provided */ + if (spec->type == CLI_OPT_TYPE_VALUE && + !opt->value && + !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL)) + opt->status = CLI_OPT_STATUS_MISSING_VALUE; + else + opt->status = CLI_OPT_STATUS_OK; + + consume_choices(opt->spec, parser); + +done: + return opt->status; +} + +static cli_opt_status_t parse_short(cli_opt *opt, cli_opt_parser *parser) +{ + const cli_opt_spec *spec; + char *arg = parser->args[parser->idx++]; + const char *value; + + opt->arg = arg; + + if ((spec = spec_for_short(&value, parser, &arg[1 + parser->in_short])) == NULL) { + opt->spec = NULL; + opt->status = CLI_OPT_STATUS_UNKNOWN_OPTION; + goto done; + } + + opt->spec = spec; + + if (spec->type == CLI_OPT_TYPE_BOOL && spec->value) + *((int *)spec->value) = 1; + + else if (spec->type == CLI_OPT_TYPE_ACCUMULATOR && spec->value) + *((int *)spec->value) += spec->switch_value ? spec->switch_value : 1; + + else if (spec->type == CLI_OPT_TYPE_SWITCH && spec->value) + *((int *)spec->value) = spec->switch_value; + + /* Parse values as "-ifoo" or "-i foo" */ + else if (spec->type == CLI_OPT_TYPE_VALUE) { + if (value) + opt->value = (char *)value; + else if ((parser->idx + 1) <= parser->args_len) + opt->value = parser->args[parser->idx++]; + + if (spec->value) + *((char **)spec->value) = opt->value; + } + + /* + * Handle compressed short arguments, like "-fbcd"; see if there's + * another character after the one we processed. If not, advance + * the parser index. + */ + if (spec->type != CLI_OPT_TYPE_VALUE && arg[2 + parser->in_short] != '\0') { + parser->in_short++; + parser->idx--; + } else { + parser->in_short = 0; + } + + /* Required argument was not provided */ + if (spec->type == CLI_OPT_TYPE_VALUE && !opt->value) + opt->status = CLI_OPT_STATUS_MISSING_VALUE; + else + opt->status = CLI_OPT_STATUS_OK; + + consume_choices(opt->spec, parser); + +done: + return opt->status; +} + +static cli_opt_status_t parse_arg(cli_opt *opt, cli_opt_parser *parser) +{ + const cli_opt_spec *spec = spec_for_arg(parser); + + opt->spec = spec; + opt->arg = parser->args[parser->idx]; + + if (!spec) { + parser->idx++; + opt->status = CLI_OPT_STATUS_UNKNOWN_OPTION; + } else if (spec->type == CLI_OPT_TYPE_ARGS) { + if (spec->value) + *((char ***)spec->value) = &parser->args[parser->idx]; + + /* + * We have started a list of arguments; the remainder of + * given arguments need not be examined. + */ + parser->in_args = (parser->args_len - parser->idx); + parser->idx = parser->args_len; + opt->args_len = parser->in_args; + opt->status = CLI_OPT_STATUS_OK; + } else { + if (spec->value) + *((char **)spec->value) = parser->args[parser->idx]; + + parser->idx++; + opt->status = CLI_OPT_STATUS_OK; + } + + return opt->status; +} + +static int support_gnu_style(unsigned int flags) +{ + if ((flags & CLI_OPT_PARSE_FORCE_GNU) != 0) + return 1; + + if ((flags & CLI_OPT_PARSE_GNU) == 0) + return 0; + + /* TODO: Windows */ +#if defined(_WIN32) && defined(UNICODE) + if (_wgetenv(L"POSIXLY_CORRECT") != NULL) + return 0; +#else + if (getenv("POSIXLY_CORRECT") != NULL) + return 0; +#endif + + return 1; +} + +void cli_opt_parser_init( + cli_opt_parser *parser, + const cli_opt_spec specs[], + char **args, + size_t args_len, + unsigned int flags) +{ + assert(parser); + + memset(parser, 0x0, sizeof(cli_opt_parser)); + + parser->specs = specs; + parser->args = args; + parser->args_len = args_len; + parser->flags = flags; + + parser->needs_sort = support_gnu_style(flags); +} + +GIT_INLINE(const cli_opt_spec *) spec_for_sort( + int *needs_value, + const cli_opt_parser *parser, + const char *arg) +{ + int is_negated, has_value = 0; + const char *value; + const cli_opt_spec *spec = NULL; + size_t idx = 0; + + *needs_value = 0; + + if (strncmp(arg, "--", 2) == 0) { + spec = spec_for_long(&is_negated, &has_value, &value, parser, &arg[2]); + *needs_value = !has_value; + } + + else if (strncmp(arg, "-", 1) == 0) { + spec = spec_for_short(&value, parser, &arg[1]); + + /* + * Advance through compressed short arguments to see if + * the last one has a value, eg "-xvffilename". + */ + while (spec && !value && arg[1 + ++idx] != '\0') + spec = spec_for_short(&value, parser, &arg[1 + idx]); + + *needs_value = (value == NULL); + } + + return spec; +} + +/* + * Some parsers allow for handling arguments like "file1 --help file2"; + * this is done by re-sorting the arguments in-place; emulate that. + */ +static int sort_gnu_style(cli_opt_parser *parser) +{ + size_t i, j, insert_idx = parser->idx, offset; + const cli_opt_spec *spec; + char *option, *value; + int needs_value, changed = 0; + + parser->needs_sort = 0; + + for (i = parser->idx; i < parser->args_len; i++) { + spec = spec_for_sort(&needs_value, parser, parser->args[i]); + + /* Not a "-" or "--" prefixed option. No change. */ + if (!spec) + continue; + + /* A "--" alone means remaining args are literal. */ + if (spec->type == CLI_OPT_TYPE_LITERAL) + break; + + option = parser->args[i]; + + /* + * If the argument is a value type and doesn't already + * have a value (eg "--foo=bar" or "-fbar") then we need + * to copy the next argument as its value. + */ + if (spec->type == CLI_OPT_TYPE_VALUE && needs_value) { + /* + * A required value is not provided; set parser + * index to this value so that we fail on it. + */ + if (i + 1 >= parser->args_len) { + parser->idx = i; + return 1; + } + + value = parser->args[i + 1]; + offset = 1; + } else { + value = NULL; + offset = 0; + } + + /* Caller error if args[0] is an option. */ + if (i == 0) + return 0; + + /* Shift args up one (or two) and insert the option */ + for (j = i; j > insert_idx; j--) + parser->args[j + offset] = parser->args[j - 1]; + + parser->args[insert_idx] = option; + + if (value) + parser->args[insert_idx + 1] = value; + + insert_idx += (1 + offset); + i += offset; + + changed = 1; + } + + return changed; +} + +cli_opt_status_t cli_opt_parser_next(cli_opt *opt, cli_opt_parser *parser) +{ + assert(opt && parser); + + memset(opt, 0x0, sizeof(cli_opt)); + + if (parser->idx >= parser->args_len) { + opt->args_len = parser->in_args; + return CLI_OPT_STATUS_DONE; + } + + /* Handle options in long form, those beginning with "--" */ + if (strncmp(parser->args[parser->idx], "--", 2) == 0 && + !parser->in_short && + !parser->in_literal) + return parse_long(opt, parser); + + /* Handle options in short form, those beginning with "-" */ + else if (parser->in_short || + (strncmp(parser->args[parser->idx], "-", 1) == 0 && + !parser->in_literal)) + return parse_short(opt, parser); + + /* + * We've reached the first "bare" argument. In POSIX mode, all + * remaining items on the command line are arguments. In GNU + * mode, there may be long or short options after this. Sort any + * options up to this position then re-parse the current position. + */ + if (parser->needs_sort && sort_gnu_style(parser)) + return cli_opt_parser_next(opt, parser); + + return parse_arg(opt, parser); +} + +GIT_INLINE(int) spec_included(const cli_opt_spec **specs, const cli_opt_spec *spec) +{ + const cli_opt_spec **i; + + for (i = specs; *i; ++i) { + if (spec == *i) + return 1; + } + + return 0; +} + +static cli_opt_status_t validate_required( + cli_opt *opt, + const cli_opt_spec specs[], + const cli_opt_spec **given_specs) +{ + const cli_opt_spec *spec, *required; + int given; + + /* + * Iterate over the possible specs to identify requirements and + * ensure that those have been given on the command-line. + * Note that we can have required *choices*, where one in a + * list of choices must be specified. + */ + for (spec = specs, required = NULL, given = 0; spec->type; ++spec) { + if (!required && (spec->usage & CLI_OPT_USAGE_REQUIRED)) { + required = spec; + given = 0; + } else if (!required) { + continue; + } + + if (!given) + given = spec_included(given_specs, spec); + + /* + * Validate the requirement unless we're in a required + * choice. In that case, keep the required state and + * validate at the end of the choice list. + */ + if (!spec_is_choice(spec)) { + if (!given) { + opt->spec = required; + opt->status = CLI_OPT_STATUS_MISSING_ARGUMENT; + break; + } + + required = NULL; + given = 0; + } + } + + return opt->status; +} + +cli_opt_status_t cli_opt_parse( + cli_opt *opt, + const cli_opt_spec specs[], + char **args, + size_t args_len, + unsigned int flags) +{ + cli_opt_parser parser; + const cli_opt_spec **given_specs; + size_t given_idx = 0; + + cli_opt_parser_init(&parser, specs, args, args_len, flags); + + given_specs = alloca(sizeof(const cli_opt_spec *) * (args_len + 1)); + + while (cli_opt_parser_next(opt, &parser)) { + if (opt->status != CLI_OPT_STATUS_OK && + opt->status != CLI_OPT_STATUS_DONE) + return opt->status; + + if ((opt->spec->usage & CLI_OPT_USAGE_STOP_PARSING)) + return (opt->status = CLI_OPT_STATUS_DONE); + + given_specs[given_idx++] = opt->spec; + } + + given_specs[given_idx] = NULL; + + return validate_required(opt, specs, given_specs); +} + +static int spec_name_fprint(FILE *file, const cli_opt_spec *spec) +{ + int error; + + if (spec->type == CLI_OPT_TYPE_ARG) + error = fprintf(file, "%s", spec->value_name); + else if (spec->type == CLI_OPT_TYPE_ARGS) + error = fprintf(file, "%s", spec->value_name); + else if (spec->alias && !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + error = fprintf(file, "-%c", spec->alias); + else + error = fprintf(file, "--%s", spec->name); + + return error; +} + +int cli_opt_status_fprint( + FILE *file, + const char *command, + const cli_opt *opt) +{ + const cli_opt_spec *choice; + int error; + + if (command && (error = fprintf(file, "%s: ", command)) < 0) + return error; + + switch (opt->status) { + case CLI_OPT_STATUS_DONE: + error = fprintf(file, "finished processing arguments (no error)\n"); + break; + case CLI_OPT_STATUS_OK: + error = fprintf(file, "no error\n"); + break; + case CLI_OPT_STATUS_UNKNOWN_OPTION: + error = fprintf(file, "unknown option: %s\n", opt->arg); + break; + case CLI_OPT_STATUS_MISSING_VALUE: + if ((error = fprintf(file, "argument '")) < 0 || + (error = spec_name_fprint(file, opt->spec)) < 0 || + (error = fprintf(file, "' requires a value.\n")) < 0) + break; + break; + case CLI_OPT_STATUS_MISSING_ARGUMENT: + if (spec_is_choice(opt->spec)) { + int is_choice = 1; + + if (spec_is_choice((opt->spec)+1)) + error = fprintf(file, "one of"); + else + error = fprintf(file, "either"); + + if (error < 0) + break; + + for (choice = opt->spec; is_choice; ++choice) { + is_choice = spec_is_choice(choice); + + if (!is_choice) + error = fprintf(file, " or"); + else if (choice != opt->spec) + error = fprintf(file, ","); + + if ((error < 0) || + (error = fprintf(file, " '")) < 0 || + (error = spec_name_fprint(file, choice)) < 0 || + (error = fprintf(file, "'")) < 0) + break; + + if (!spec_is_choice(choice)) + break; + } + + if ((error < 0) || + (error = fprintf(file, " is required.\n")) < 0) + break; + } else { + if ((error = fprintf(file, "argument '")) < 0 || + (error = spec_name_fprint(file, opt->spec)) < 0 || + (error = fprintf(file, "' is required.\n")) < 0) + break; + } + + break; + default: + error = fprintf(file, "unknown status: %d\n", opt->status); + break; + } + + return error; +} + +int cli_opt_usage_fprint( + FILE *file, + const char *command, + const cli_opt_spec specs[]) +{ + const cli_opt_spec *spec; + int choice = 0, next_choice = 0, optional = 0; + int error; + + if ((error = fprintf(file, "usage: %s", command)) < 0) + goto done; + + for (spec = specs; spec->type; ++spec) { + if (!choice) + optional = !(spec->usage & CLI_OPT_USAGE_REQUIRED); + + next_choice = !!((spec + 1)->usage & CLI_OPT_USAGE_CHOICE); + + if (spec->usage & CLI_OPT_USAGE_HIDDEN) + continue; + + if (choice) + error = fprintf(file, "|"); + else + error = fprintf(file, " "); + + if (error < 0) + goto done; + + if (optional && !choice && (error = fprintf(file, "[")) < 0) + error = fprintf(file, "["); + if (!optional && !choice && next_choice) + error = fprintf(file, "("); + + if (error < 0) + goto done; + + if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && + !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL) && + !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + error = fprintf(file, "-%c <%s>", spec->alias, spec->value_name); + else if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && + !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + error = fprintf(file, "-%c [<%s>]", spec->alias, spec->value_name); + else if (spec->type == CLI_OPT_TYPE_VALUE && + !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL)) + error = fprintf(file, "--%s[=<%s>]", spec->name, spec->value_name); + else if (spec->type == CLI_OPT_TYPE_VALUE) + error = fprintf(file, "--%s=<%s>", spec->name, spec->value_name); + else if (spec->type == CLI_OPT_TYPE_ARG) + error = fprintf(file, "<%s>", spec->value_name); + else if (spec->type == CLI_OPT_TYPE_ARGS) + error = fprintf(file, "<%s>...", spec->value_name); + else if (spec->type == CLI_OPT_TYPE_LITERAL) + error = fprintf(file, "--"); + else if (spec->alias && !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + error = fprintf(file, "-%c", spec->alias); + else + error = fprintf(file, "--%s", spec->name); + + if (error < 0) + goto done; + + if (!optional && choice && !next_choice) + error = fprintf(file, ")"); + else if (optional && !next_choice) + error = fprintf(file, "]"); + + if (error < 0) + goto done; + + choice = next_choice; + } + + error = fprintf(file, "\n"); + +done: + error = (error < 0) ? -1 : 0; + return error; +} + diff --git a/src/cli/opt.h b/src/cli/opt.h new file mode 100644 index 000000000..f7b6b9326 --- /dev/null +++ b/src/cli/opt.h @@ -0,0 +1,362 @@ +/* + * Copyright (c), Edward Thomson + * All rights reserved. + * + * This file is part of adopt, distributed under the MIT license. + * For full terms and conditions, see the included LICENSE file. + * + * THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT. + * + * This file was produced by using the `rename.pl` script included with + * adopt. The command-line specified was: + * + * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status + */ + +#ifndef CLI_opt_h__ +#define CLI_opt_h__ + +#include +#include + +/** + * The type of argument to be parsed. + */ +typedef enum { + CLI_OPT_TYPE_NONE = 0, + + /** + * An option that, when specified, sets a given value to true. + * This is useful for options like "--debug". A negation + * option (beginning with "no-") is implicitly specified; for + * example "--no-debug". The `value` pointer in the returned + * option will be set to `1` when this is specified, and set to + * `0` when the negation "no-" option is specified. + */ + CLI_OPT_TYPE_BOOL, + + /** + * An option that, when specified, sets the given `value` pointer + * to the specified `switch_value`. This is useful for booleans + * where you do not want the implicit negation that comes with an + * `CLI_OPT_TYPE_BOOL`, or for switches that multiplex a value, like + * setting a mode. For example, `--read` may set the `value` to + * `MODE_READ` and `--write` may set the `value` to `MODE_WRITE`. + */ + CLI_OPT_TYPE_SWITCH, + + /** + * An option that, when specified, increments the given + * `value` by the given `switch_value`. This can be specified + * multiple times to continue to increment the `value`. + * (For example, "-vvv" to set verbosity to 3.) + */ + CLI_OPT_TYPE_ACCUMULATOR, + + /** + * An option that takes a value, for example `-n value`, + * `-nvalue`, `--name value` or `--name=value`. + */ + CLI_OPT_TYPE_VALUE, + + /** + * A bare "--" that indicates that arguments following this are + * literal. This allows callers to specify things that might + * otherwise look like options, for example to operate on a file + * named "-rf" then you can invoke "program -- -rf" to treat + * "-rf" as an argument not an option. + */ + CLI_OPT_TYPE_LITERAL, + + /** + * A single argument, not an option. When options are exhausted, + * arguments will be matches in the order that they're specified + * in the spec list. For example, if two `CLI_OPT_TYPE_ARGS` are + * specified, `input_file` and `output_file`, then the first bare + * argument on the command line will be `input_file` and the + * second will be `output_file`. + */ + CLI_OPT_TYPE_ARG, + + /** + * A collection of arguments. This is useful when you want to take + * a list of arguments, for example, multiple paths. When specified, + * the value will be set to the first argument in the list. + */ + CLI_OPT_TYPE_ARGS, +} cli_opt_type_t; + +/** + * Additional information about an option, including parsing + * restrictions and usage information to be displayed to the end-user. + */ +typedef enum { + /** Defaults for the argument. */ + CLI_OPT_USAGE_DEFAULT = 0, + + /** This argument is required. */ + CLI_OPT_USAGE_REQUIRED = (1u << 0), + + /** + * This is a multiple choice argument, combined with the previous + * argument. For example, when the previous argument is `-f` and + * this optional is applied to an argument of type `-b` then one + * of `-f` or `-b` may be specified. + */ + CLI_OPT_USAGE_CHOICE = (1u << 1), + + /** + * This argument short-circuits the remainder of parsing. + * Useful for arguments like `--help`. + */ + CLI_OPT_USAGE_STOP_PARSING = (1u << 2), + + /** The argument's value is optional ("-n" or "-n foo") */ + CLI_OPT_USAGE_VALUE_OPTIONAL = (1u << 3), + + /** This argument should not be displayed in usage. */ + CLI_OPT_USAGE_HIDDEN = (1u << 4), + + /** In usage, show the long format instead of the abbreviated format. */ + CLI_OPT_USAGE_SHOW_LONG = (1u << 5), +} cli_opt_usage_t; + +typedef enum { + /** Default parsing behavior. */ + CLI_OPT_PARSE_DEFAULT = 0, + + /** + * Parse with GNU `getopt_long` style behavior, where options can + * be intermixed with arguments at any position (for example, + * "file1 --help file2".) Like `getopt_long`, this can mutate the + * arguments given. + */ + CLI_OPT_PARSE_GNU = (1u << 0), + + /** + * Force GNU `getopt_long` style behavior; the `POSIXLY_CORRECT` + * environment variable is ignored. + */ + CLI_OPT_PARSE_FORCE_GNU = (1u << 1), +} cli_opt_flag_t; + +/** Specification for an available option. */ +typedef struct cli_opt_spec { + /** Type of option expected. */ + cli_opt_type_t type; + + /** Name of the long option. */ + const char *name; + + /** The alias is the short (one-character) option alias. */ + const char alias; + + /** + * If this spec is of type `CLI_OPT_TYPE_BOOL`, this is a pointer + * to an `int` that will be set to `1` if the option is specified. + * + * If this spec is of type `CLI_OPT_TYPE_SWITCH`, this is a pointer + * to an `int` that will be set to the opt's `switch_value` (below) + * when this option is specified. + * + * If this spec is of type `CLI_OPT_TYPE_ACCUMULATOR`, this is a + * pointer to an `int` that will be incremented by the opt's + * `switch_value` (below). If no `switch_value` is provided then + * the value will be incremented by 1. + * + * If this spec is of type `CLI_OPT_TYPE_VALUE`, + * `CLI_OPT_TYPE_VALUE_OPTIONAL`, or `CLI_OPT_TYPE_ARG`, this is + * a pointer to a `char *` that will be set to the value + * specified on the command line. + * + * If this spec is of type `CLI_OPT_TYPE_ARGS`, this is a pointer + * to a `char **` that will be set to the remaining values + * specified on the command line. + */ + void *value; + + /** + * If this spec is of type `CLI_OPT_TYPE_SWITCH`, this is the value + * to set in the option's `value` pointer when it is specified. If + * this spec is of type `CLI_OPT_TYPE_ACCUMULATOR`, this is the value + * to increment in the option's `value` pointer when it is + * specified. This is ignored for other opt types. + */ + int switch_value; + + /** + * Optional usage flags that change parsing behavior and how + * usage information is shown to the end-user. + */ + uint32_t usage; + + /** + * The name of the value, provided when creating usage information. + * This is required only for the functions that display usage + * information and only when a spec is of type `CLI_OPT_TYPE_VALUE, + * `CLI_OPT_TYPE_ARG` or `CLI_OPT_TYPE_ARGS``. + */ + const char *value_name; + + /** + * Optional short description of the option to display to the + * end-user. This is only used when creating usage information. + */ + const char *help; +} cli_opt_spec; + +/** Return value for `cli_opt_parser_next`. */ +typedef enum { + /** Parsing is complete; there are no more arguments. */ + CLI_OPT_STATUS_DONE = 0, + + /** + * This argument was parsed correctly; the `opt` structure is + * populated and the value pointer has been set. + */ + CLI_OPT_STATUS_OK = 1, + + /** + * The argument could not be parsed correctly, it does not match + * any of the specifications provided. + */ + CLI_OPT_STATUS_UNKNOWN_OPTION = 2, + + /** + * The argument matched a spec of type `CLI_OPT_VALUE`, but no value + * was provided. + */ + CLI_OPT_STATUS_MISSING_VALUE = 3, + + /** A required argument was not provided. */ + CLI_OPT_STATUS_MISSING_ARGUMENT = 4, +} cli_opt_status_t; + +/** An option provided on the command-line. */ +typedef struct cli_opt { + /** The status of parsing the most recent argument. */ + cli_opt_status_t status; + + /** + * The specification that was provided on the command-line, or + * `NULL` if the argument did not match an `cli_opt_spec`. + */ + const cli_opt_spec *spec; + + /** + * The argument as it was specified on the command-line, including + * dashes, eg, `-f` or `--foo`. + */ + char *arg; + + /** + * If the spec is of type `CLI_OPT_VALUE` or `CLI_OPT_VALUE_OPTIONAL`, + * this is the value provided to the argument. + */ + char *value; + + /** + * If the argument is of type `CLI_OPT_ARGS`, this is the number of + * arguments remaining. This value is persisted even when parsing + * is complete and `status` == `CLI_OPT_STATUS_DONE`. + */ + size_t args_len; +} cli_opt; + +/* The internal parser state. Callers should not modify this structure. */ +typedef struct cli_opt_parser { + const cli_opt_spec *specs; + char **args; + size_t args_len; + unsigned int flags; + + /* Parser state */ + size_t idx; + size_t arg_idx; + size_t in_args; + size_t in_short; + int needs_sort : 1, + in_literal : 1; +} cli_opt_parser; + +/** + * Parses all the command-line arguments and updates all the options using + * the pointers provided. Parsing stops on any invalid argument and + * information about the failure will be provided in the opt argument. + * + * This is the simplest way to parse options; it handles the initialization + * (`parser_init`) and looping (`parser_next`). + * + * @param opt The The `cli_opt` information that failed parsing + * @param specs A NULL-terminated array of `cli_opt_spec`s that can be parsed + * @param args The arguments that will be parsed + * @param args_len The length of arguments to be parsed + * @param flags The `cli_opt_flag_t flags for parsing + */ +cli_opt_status_t cli_opt_parse( + cli_opt *opt, + const cli_opt_spec specs[], + char **args, + size_t args_len, + unsigned int flags); + +/** + * Initializes a parser that parses the given arguments according to the + * given specifications. + * + * @param parser The `cli_opt_parser` that will be initialized + * @param specs A NULL-terminated array of `cli_opt_spec`s that can be parsed + * @param args The arguments that will be parsed + * @param args_len The length of arguments to be parsed + * @param flags The `cli_opt_flag_t flags for parsing + */ +void cli_opt_parser_init( + cli_opt_parser *parser, + const cli_opt_spec specs[], + char **args, + size_t args_len, + unsigned int flags); + +/** + * Parses the next command-line argument and places the information about + * the argument into the given `opt` data. + * + * @param opt The `cli_opt` information parsed from the argument + * @param parser An `cli_opt_parser` that has been initialized with + * `cli_opt_parser_init` + * @return true if the caller should continue iterating, or 0 if there are + * no arguments left to process. + */ +cli_opt_status_t cli_opt_parser_next( + cli_opt *opt, + cli_opt_parser *parser); + +/** + * Prints the status after parsing the most recent argument. This is + * useful for printing an error message when an unknown argument was + * specified, or when an argument was specified without a value. + * + * @param file The file to print information to + * @param command The name of the command to use when printing (optional) + * @param opt The option that failed to parse + * @return 0 on success, -1 on failure + */ +int cli_opt_status_fprint( + FILE *file, + const char *command, + const cli_opt *opt); + +/** + * Prints usage information to the given file handle. + * + * @param file The file to print information to + * @param command The name of the command to use when printing + * @param specs The specifications allowed by the command + * @return 0 on success, -1 on failure + */ +int cli_opt_usage_fprint( + FILE *file, + const char *command, + const cli_opt_spec specs[]); + +#endif /* CLI_opt_h__ */ diff --git a/src/cli/win32/precompiled.c b/src/cli/win32/precompiled.c new file mode 100644 index 000000000..5f656a45d --- /dev/null +++ b/src/cli/win32/precompiled.c @@ -0,0 +1 @@ +#include "precompiled.h" diff --git a/src/cli/win32/precompiled.h b/src/cli/win32/precompiled.h new file mode 100644 index 000000000..b0309b864 --- /dev/null +++ b/src/cli/win32/precompiled.h @@ -0,0 +1,3 @@ +#include + +#include "cli.h" -- cgit v1.2.1 From 8526cbd56b0395b9427727e81e6f3c89768337b9 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 26 Nov 2021 09:37:29 -0500 Subject: opt: use a custom function to print usage Our argument parser (https://github.com/ethomson/adopt) includes a function to print a usage message based on the allowed options. Omit this and use a cutom function that understands that we have subcommands ("checkout", "revert", etc) that each have their own options. --- src/cli/cli.h | 1 + src/cli/main.c | 2 +- src/cli/opt.c | 83 +--------------------- src/cli/opt.h | 15 +--- src/cli/opt_usage.c | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/cli/opt_usage.h | 35 ++++++++++ 6 files changed, 233 insertions(+), 97 deletions(-) create mode 100644 src/cli/opt_usage.c create mode 100644 src/cli/opt_usage.h diff --git a/src/cli/cli.h b/src/cli/cli.h index a27081d87..222d53a74 100644 --- a/src/cli/cli.h +++ b/src/cli/cli.h @@ -14,5 +14,6 @@ #include "error.h" #include "opt.h" +#include "opt_usage.h" #endif /* CLI_cli_h__ */ diff --git a/src/cli/main.c b/src/cli/main.c index 709f6b452..5eff56a1d 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -34,7 +34,7 @@ int main(int argc, char **argv) while (cli_opt_parser_next(&opt, &optparser)) { if (!opt.spec) { cli_opt_status_fprint(stderr, PROGRAM_NAME, &opt); - cli_opt_usage_fprint(stderr, PROGRAM_NAME, common_opts); + cli_opt_usage_fprint(stderr, PROGRAM_NAME, NULL, common_opts); ret = CLI_EXIT_USAGE; goto done; } diff --git a/src/cli/opt.c b/src/cli/opt.c index 11faa92c5..72df5877f 100644 --- a/src/cli/opt.c +++ b/src/cli/opt.c @@ -10,7 +10,7 @@ * This file was produced by using the `rename.pl` script included with * adopt. The command-line specified was: * - * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status + * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage */ #include @@ -667,84 +667,3 @@ int cli_opt_status_fprint( return error; } -int cli_opt_usage_fprint( - FILE *file, - const char *command, - const cli_opt_spec specs[]) -{ - const cli_opt_spec *spec; - int choice = 0, next_choice = 0, optional = 0; - int error; - - if ((error = fprintf(file, "usage: %s", command)) < 0) - goto done; - - for (spec = specs; spec->type; ++spec) { - if (!choice) - optional = !(spec->usage & CLI_OPT_USAGE_REQUIRED); - - next_choice = !!((spec + 1)->usage & CLI_OPT_USAGE_CHOICE); - - if (spec->usage & CLI_OPT_USAGE_HIDDEN) - continue; - - if (choice) - error = fprintf(file, "|"); - else - error = fprintf(file, " "); - - if (error < 0) - goto done; - - if (optional && !choice && (error = fprintf(file, "[")) < 0) - error = fprintf(file, "["); - if (!optional && !choice && next_choice) - error = fprintf(file, "("); - - if (error < 0) - goto done; - - if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && - !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL) && - !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) - error = fprintf(file, "-%c <%s>", spec->alias, spec->value_name); - else if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && - !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) - error = fprintf(file, "-%c [<%s>]", spec->alias, spec->value_name); - else if (spec->type == CLI_OPT_TYPE_VALUE && - !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL)) - error = fprintf(file, "--%s[=<%s>]", spec->name, spec->value_name); - else if (spec->type == CLI_OPT_TYPE_VALUE) - error = fprintf(file, "--%s=<%s>", spec->name, spec->value_name); - else if (spec->type == CLI_OPT_TYPE_ARG) - error = fprintf(file, "<%s>", spec->value_name); - else if (spec->type == CLI_OPT_TYPE_ARGS) - error = fprintf(file, "<%s>...", spec->value_name); - else if (spec->type == CLI_OPT_TYPE_LITERAL) - error = fprintf(file, "--"); - else if (spec->alias && !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) - error = fprintf(file, "-%c", spec->alias); - else - error = fprintf(file, "--%s", spec->name); - - if (error < 0) - goto done; - - if (!optional && choice && !next_choice) - error = fprintf(file, ")"); - else if (optional && !next_choice) - error = fprintf(file, "]"); - - if (error < 0) - goto done; - - choice = next_choice; - } - - error = fprintf(file, "\n"); - -done: - error = (error < 0) ? -1 : 0; - return error; -} - diff --git a/src/cli/opt.h b/src/cli/opt.h index f7b6b9326..6c1d4603e 100644 --- a/src/cli/opt.h +++ b/src/cli/opt.h @@ -10,7 +10,7 @@ * This file was produced by using the `rename.pl` script included with * adopt. The command-line specified was: * - * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status + * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage */ #ifndef CLI_opt_h__ @@ -346,17 +346,4 @@ int cli_opt_status_fprint( const char *command, const cli_opt *opt); -/** - * Prints usage information to the given file handle. - * - * @param file The file to print information to - * @param command The name of the command to use when printing - * @param specs The specifications allowed by the command - * @return 0 on success, -1 on failure - */ -int cli_opt_usage_fprint( - FILE *file, - const char *command, - const cli_opt_spec specs[]); - #endif /* CLI_opt_h__ */ diff --git a/src/cli/opt_usage.c b/src/cli/opt_usage.c new file mode 100644 index 000000000..6e5d6006e --- /dev/null +++ b/src/cli/opt_usage.c @@ -0,0 +1,194 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "cli.h" +#include "str.h" + +static int print_spec_name(git_str *out, const cli_opt_spec *spec) +{ + if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && + !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL) && + !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + return git_str_printf(out, "-%c <%s>", spec->alias, spec->value_name); + if (spec->type == CLI_OPT_TYPE_VALUE && spec->alias && + !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + return git_str_printf(out, "-%c [<%s>]", spec->alias, spec->value_name); + if (spec->type == CLI_OPT_TYPE_VALUE && + !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL)) + return git_str_printf(out, "--%s[=<%s>]", spec->name, spec->value_name); + if (spec->type == CLI_OPT_TYPE_VALUE) + return git_str_printf(out, "--%s=<%s>", spec->name, spec->value_name); + if (spec->type == CLI_OPT_TYPE_ARG) + return git_str_printf(out, "<%s>", spec->value_name); + if (spec->type == CLI_OPT_TYPE_ARGS) + return git_str_printf(out, "<%s>...", spec->value_name); + if (spec->type == CLI_OPT_TYPE_LITERAL) + return git_str_printf(out, "--"); + if (spec->alias && !(spec->usage & CLI_OPT_USAGE_SHOW_LONG)) + return git_str_printf(out, "-%c", spec->alias); + if (spec->name) + return git_str_printf(out, "--%s", spec->name); + + GIT_ASSERT(0); +} + +/* + * This is similar to adopt's function, but modified to understand + * that we have a command ("git") and a "subcommand" ("checkout"). + * It also understands a terminal's line length and wrap appropriately, + * using a `git_str` for storage. + */ +int cli_opt_usage_fprint( + FILE *file, + const char *command, + const char *subcommand, + const cli_opt_spec specs[]) +{ + git_str usage = GIT_BUF_INIT, opt = GIT_BUF_INIT; + const cli_opt_spec *spec; + size_t i, prefixlen, linelen; + bool choice = false, next_choice = false, optional = false; + int error; + + /* TODO: query actual console width. */ + int console_width = 80; + + if ((error = git_str_printf(&usage, "usage: %s", command)) < 0) + goto done; + + if (subcommand && + (error = git_str_printf(&usage, " %s", subcommand)) < 0) + goto done; + + linelen = git_str_len(&usage); + prefixlen = linelen + 1; + + for (spec = specs; spec->type; ++spec) { + if (!choice) + optional = !(spec->usage & CLI_OPT_USAGE_REQUIRED); + + next_choice = !!((spec + 1)->usage & CLI_OPT_USAGE_CHOICE); + + if (spec->usage & CLI_OPT_USAGE_HIDDEN) + continue; + + if (choice) + git_str_putc(&opt, '|'); + else + git_str_clear(&opt); + + if (optional && !choice) + git_str_putc(&opt, '['); + if (!optional && !choice && next_choice) + git_str_putc(&opt, '('); + + if ((error = print_spec_name(&opt, spec)) < 0) + goto done; + + if (!optional && choice && !next_choice) + git_str_putc(&opt, ')'); + else if (optional && !next_choice) + git_str_putc(&opt, ']'); + + if ((choice = next_choice)) + continue; + + if (git_str_oom(&opt)) { + error = -1; + goto done; + } + + if (linelen > prefixlen && + console_width > 0 && + linelen + git_str_len(&opt) + 1 > (size_t)console_width) { + git_str_putc(&usage, '\n'); + + for (i = 0; i < prefixlen; i++) + git_str_putc(&usage, ' '); + + linelen = prefixlen; + } else { + git_str_putc(&usage, ' '); + linelen += git_str_len(&opt) + 1; + } + + git_str_puts(&usage, git_str_cstr(&opt)); + + if (git_str_oom(&usage)) { + error = -1; + goto done; + } + } + + error = fprintf(file, "%s\n", git_str_cstr(&usage)); + +done: + error = (error < 0) ? -1 : 0; + + git_str_dispose(&usage); + git_str_dispose(&opt); + return error; +} + +int cli_opt_usage_error( + const char *subcommand, + const cli_opt_spec specs[], + const cli_opt *invalid_opt) +{ + cli_opt_status_fprint(stderr, PROGRAM_NAME, invalid_opt); + cli_opt_usage_fprint(stderr, PROGRAM_NAME, subcommand, specs); + return CLI_EXIT_USAGE; +} + +int cli_opt_help_fprint( + FILE *file, + const cli_opt_spec specs[]) +{ + git_str help = GIT_BUF_INIT; + const cli_opt_spec *spec; + int error; + + /* Display required arguments first */ + for (spec = specs; spec->type; ++spec) { + if (! (spec->usage & CLI_OPT_USAGE_REQUIRED) || + (spec->usage & CLI_OPT_USAGE_HIDDEN)) + continue; + + git_str_printf(&help, " "); + + if ((error = print_spec_name(&help, spec)) < 0) + goto done; + + git_str_printf(&help, ": %s\n", spec->help); + } + + /* Display the remaining arguments */ + for (spec = specs; spec->type; ++spec) { + if ((spec->usage & CLI_OPT_USAGE_REQUIRED) || + (spec->usage & CLI_OPT_USAGE_HIDDEN)) + continue; + + git_str_printf(&help, " "); + + if ((error = print_spec_name(&help, spec)) < 0) + goto done; + + git_str_printf(&help, ": %s\n", spec->help); + + } + + if (git_str_oom(&help) || + p_write(fileno(file), help.ptr, help.size) < 0) + error = -1; + +done: + error = (error < 0) ? -1 : 0; + + git_str_dispose(&help); + return error; +} + diff --git a/src/cli/opt_usage.h b/src/cli/opt_usage.h new file mode 100644 index 000000000..c752494e1 --- /dev/null +++ b/src/cli/opt_usage.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef CLI_opt_usage_h__ +#define CLI_opt_usage_h__ + +/** + * Prints usage information to the given file handle. + * + * @param file The file to print information to + * @param command The name of the command to use when printing + * @param subcommand The name of the subcommand (eg "checkout") to use when printing, or NULL to skip + * @param specs The specifications allowed by the command + * @return 0 on success, -1 on failure + */ +int cli_opt_usage_fprint( + FILE *file, + const char *command, + const char *subcommand, + const cli_opt_spec specs[]); + +int cli_opt_usage_error( + const char *subcommand, + const cli_opt_spec specs[], + const cli_opt *invalid_opt); + +int cli_opt_help_fprint( + FILE *file, + const cli_opt_spec specs[]); + +#endif /* CLI_opt_usage_h__ */ -- cgit v1.2.1 From c6dd82d9f14e9e24a52af637c4a56218ead13eda Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 23 Feb 2020 11:54:33 +0000 Subject: cli: introduce a help command Add a framework for commands to be defined, and add our first one, "help". When `git2_cli help` is run, the `cmd_help` function will be invoked with the remaining command line arguments. This allows users to invoke `git2_cli help foo` to get information about the `foo` subcommand. --- src/cli/README.md | 19 ++++++++++++++ src/cli/cmd.c | 21 +++++++++++++++ src/cli/cmd.h | 30 +++++++++++++++++++++ src/cli/cmd_help.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/cli/main.c | 51 ++++++++++++++++++++++++++++++++---- 5 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 src/cli/cmd.c create mode 100644 src/cli/cmd.h create mode 100644 src/cli/cmd_help.c diff --git a/src/cli/README.md b/src/cli/README.md index eefd2ff27..26f11d90a 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -1,3 +1,22 @@ # cli A git-compatible command-line interface that uses libgit2. + +## Adding commands + +1. Individual commands have a `main`-like top-level entrypoint. For example: + + ```c + int cmd_help(int argc, char **argv) + ``` + + Although this is the same signature as `main`, commands are not built as + individual standalone executables, they'll be linked into the main cli. + (Though there may be an option for command executables to be built as + standalone executables in the future.) + +2. Commands are prototyped in `cmd.h` and added to `main.c`'s list of + commands (`cli_cmds[]`). Commands should be specified with their name, + entrypoint and a brief description that can be printed in `git help`. + This is done because commands are linked into the main cli. + diff --git a/src/cli/cmd.c b/src/cli/cmd.c new file mode 100644 index 000000000..2a7e71cdb --- /dev/null +++ b/src/cli/cmd.c @@ -0,0 +1,21 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "cli.h" +#include "cmd.h" + +const cli_cmd_spec *cli_cmd_spec_byname(const char *name) +{ + const cli_cmd_spec *cmd; + + for (cmd = cli_cmds; cmd->name; cmd++) { + if (!strcmp(cmd->name, name)) + return cmd; + } + + return NULL; +} diff --git a/src/cli/cmd.h b/src/cli/cmd.h new file mode 100644 index 000000000..816614efc --- /dev/null +++ b/src/cli/cmd.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef CLI_cmd_h__ +#define CLI_cmd_h__ + +/* Command definitions */ +typedef struct { + const char *name; + int (*fn)(int argc, char **argv); + const char *desc; +} cli_cmd_spec; + +/* Options that are common to all commands (eg --help, --git-dir) */ +extern const cli_opt_spec cli_common_opts[]; + +/* All the commands supported by the CLI */ +extern const cli_cmd_spec cli_cmds[]; + +/* Find a command by name */ +extern const cli_cmd_spec *cli_cmd_spec_byname(const char *name); + +/* Commands */ +extern int cmd_help(int argc, char **argv); + +#endif /* CLI_cmd_h__ */ diff --git a/src/cli/cmd_help.c b/src/cli/cmd_help.c new file mode 100644 index 000000000..d2ff5d4f4 --- /dev/null +++ b/src/cli/cmd_help.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include +#include "cli.h" +#include "cmd.h" + +#define COMMAND_NAME "help" + +static char *command; +static int show_help; + +static const cli_opt_spec opts[] = { + { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, + CLI_OPT_USAGE_HIDDEN, NULL, "display help about the help command" }, + { CLI_OPT_TYPE_ARG, "command", 0, &command, 0, + CLI_OPT_USAGE_DEFAULT, "command", "the command to show help for" }, + { 0 }, +}; + +static int print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts); + printf("\n"); + + printf("Display help information about %s. If a command is specified, help\n", PROGRAM_NAME); + printf("about that command will be shown. Otherwise, general information about\n"); + printf("%s will be shown, including the commands available.\n", PROGRAM_NAME); + + return 0; +} + +static int print_commands(void) +{ + const cli_cmd_spec *cmd; + + cli_opt_usage_fprint(stdout, PROGRAM_NAME, NULL, cli_common_opts); + printf("\n"); + + printf("These are the %s commands available:\n\n", PROGRAM_NAME); + + for (cmd = cli_cmds; cmd->name; cmd++) + printf(" %-8s %s\n", cmd->name, cmd->desc); + + printf("\nSee '%s help ' for more information on a specific command.\n", PROGRAM_NAME); + + return 0; +} + +int cmd_help(int argc, char **argv) +{ + cli_opt invalid_opt; + + if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) + return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); + + /* Show the meta-help */ + if (show_help) + return print_help(); + + /* We were not asked to show help for a specific command. */ + if (!command) + return print_commands(); + + /* If the user asks for help with the help command */ + if (strcmp(command, "help") == 0) + return print_help(); + + fprintf(stderr, "%s: '%s' is not a %s command. See '%s help'.\n", + PROGRAM_NAME, command, PROGRAM_NAME, PROGRAM_NAME); + return CLI_EXIT_ERROR; +} diff --git a/src/cli/main.c b/src/cli/main.c index 5eff56a1d..c68c349f7 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -8,19 +8,36 @@ #include #include #include "cli.h" +#include "cmd.h" +static int show_help = 0; static int show_version = 0; +static char *command = NULL; +static char **args = NULL; -static const cli_opt_spec common_opts[] = { - { CLI_OPT_TYPE_SWITCH, "version", 0, &show_version, 1, - CLI_OPT_USAGE_DEFAULT, NULL, "display the version" }, +const cli_opt_spec cli_common_opts[] = { + { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "display help information" }, + { CLI_OPT_TYPE_SWITCH, "version", 0, &show_version, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "display the version" }, + { CLI_OPT_TYPE_ARG, "command", 0, &command, 0, + CLI_OPT_USAGE_REQUIRED, "command", "the command to run" }, + { CLI_OPT_TYPE_ARGS, "args", 0, &args, 0, + CLI_OPT_USAGE_DEFAULT, "args", "arguments for the command" }, { 0 } }; +const cli_cmd_spec cli_cmds[] = { + { "help", cmd_help, "Display help information" }, + { NULL } +}; + int main(int argc, char **argv) { + const cli_cmd_spec *cmd; cli_opt_parser optparser; cli_opt opt; + int args_len = 0; int ret = 0; if (git_libgit2_init() < 0) { @@ -28,16 +45,26 @@ int main(int argc, char **argv) exit(CLI_EXIT_GIT); } - cli_opt_parser_init(&optparser, common_opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU); + cli_opt_parser_init(&optparser, cli_common_opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU); /* Parse the top-level (common) options and command information */ while (cli_opt_parser_next(&opt, &optparser)) { if (!opt.spec) { cli_opt_status_fprint(stderr, PROGRAM_NAME, &opt); - cli_opt_usage_fprint(stderr, PROGRAM_NAME, NULL, common_opts); + cli_opt_usage_fprint(stderr, PROGRAM_NAME, NULL, cli_common_opts); ret = CLI_EXIT_USAGE; goto done; } + + /* + * When we see a command, stop parsing and capture the + * remaining arguments as args for the command itself. + */ + if (command) { + args = &argv[optparser.idx]; + args_len = (int)(argc - optparser.idx); + break; + } } if (show_version) { @@ -45,6 +72,20 @@ int main(int argc, char **argv) goto done; } + /* If there was no command, we want to invoke "help" */ + if (!command || show_help) { + cli_opt_usage_fprint(stdout, PROGRAM_NAME, NULL, cli_common_opts); + goto done; + } + + if ((cmd = cli_cmd_spec_byname(command)) == NULL) { + ret = cli_error("'%s' is not a %s command. See '%s help'.", + command, PROGRAM_NAME, PROGRAM_NAME); + goto done; + } + + ret = cmd->fn(args_len, args); + done: git_libgit2_shutdown(); return ret; -- cgit v1.2.1 From f8e7d8fdde563df6c9351296b6330bb8cc859066 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 26 Nov 2021 17:33:38 -0500 Subject: cli: support `help ` Support `help ` by re-invoking the command itself with the `--help` argument. This allows us to keep the help logic with the commands itself. --- src/cli/README.md | 4 ++++ src/cli/cmd_help.c | 23 ++++++++++++++++------- src/cli/main.c | 17 ++++++++++++++--- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/cli/README.md b/src/cli/README.md index 26f11d90a..3087c39c4 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -20,3 +20,7 @@ A git-compatible command-line interface that uses libgit2. entrypoint and a brief description that can be printed in `git help`. This is done because commands are linked into the main cli. +3. Commands should accept a `--help` option that displays their help + information. This will be shown when a user runs ` --help` and + when a user runs `help `. + diff --git a/src/cli/cmd_help.c b/src/cli/cmd_help.c index d2ff5d4f4..7ee982242 100644 --- a/src/cli/cmd_help.c +++ b/src/cli/cmd_help.c @@ -45,7 +45,7 @@ static int print_commands(void) printf("These are the %s commands available:\n\n", PROGRAM_NAME); for (cmd = cli_cmds; cmd->name; cmd++) - printf(" %-8s %s\n", cmd->name, cmd->desc); + printf(" %-11s %s\n", cmd->name, cmd->desc); printf("\nSee '%s help ' for more information on a specific command.\n", PROGRAM_NAME); @@ -54,6 +54,8 @@ static int print_commands(void) int cmd_help(int argc, char **argv) { + char *fake_args[2]; + const cli_cmd_spec *cmd; cli_opt invalid_opt; if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) @@ -67,11 +69,18 @@ int cmd_help(int argc, char **argv) if (!command) return print_commands(); - /* If the user asks for help with the help command */ - if (strcmp(command, "help") == 0) - return print_help(); + /* + * If we were asked for help for a command (eg, `help `), + * delegate back to that command's `--help` option. This lets + * commands own their help. Emulate the command-line arguments + * that would invoke ` --help` and invoke that command. + */ + fake_args[0] = command; + fake_args[1] = "--help"; + + if ((cmd = cli_cmd_spec_byname(command)) == NULL) + return cli_error("'%s' is not a %s command. See '%s help'.", + command, PROGRAM_NAME, PROGRAM_NAME); - fprintf(stderr, "%s: '%s' is not a %s command. See '%s help'.\n", - PROGRAM_NAME, command, PROGRAM_NAME, PROGRAM_NAME); - return CLI_EXIT_ERROR; + return cmd->fn(2, fake_args); } diff --git a/src/cli/main.c b/src/cli/main.c index c68c349f7..d961f659f 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -37,6 +37,8 @@ int main(int argc, char **argv) const cli_cmd_spec *cmd; cli_opt_parser optparser; cli_opt opt; + char *help_args[3] = { NULL }; + int help_args_len; int args_len = 0; int ret = 0; @@ -72,10 +74,19 @@ int main(int argc, char **argv) goto done; } - /* If there was no command, we want to invoke "help" */ + /* + * If `--help ` is specified, delegate to that command's + * `--help` option. If no command is specified, run the `help` + * command. Do this by updating the args to emulate that behavior. + */ if (!command || show_help) { - cli_opt_usage_fprint(stdout, PROGRAM_NAME, NULL, cli_common_opts); - goto done; + help_args[0] = command ? (char *)command : "help"; + help_args[1] = command ? "--help" : NULL; + help_args_len = command ? 2 : 1; + + command = help_args[0]; + args = help_args; + args_len = help_args_len; } if ((cmd = cli_cmd_spec_byname(command)) == NULL) { -- cgit v1.2.1 From b877122750aa0435bab701825378754783d09485 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 28 Nov 2021 10:32:03 -0500 Subject: cli: add `cat-file` command Introduce a simple command that emulates `git cat-file`. --- src/cli/cmd.h | 1 + src/cli/cmd_cat_file.c | 204 +++++++++++++++++++++++++++++++++++++++++++++++++ src/cli/main.c | 3 +- 3 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 src/cli/cmd_cat_file.c diff --git a/src/cli/cmd.h b/src/cli/cmd.h index 816614efc..cc1743ed2 100644 --- a/src/cli/cmd.h +++ b/src/cli/cmd.h @@ -25,6 +25,7 @@ extern const cli_cmd_spec cli_cmds[]; extern const cli_cmd_spec *cli_cmd_spec_byname(const char *name); /* Commands */ +extern int cmd_cat_file(int argc, char **argv); extern int cmd_help(int argc, char **argv); #endif /* CLI_cmd_h__ */ diff --git a/src/cli/cmd_cat_file.c b/src/cli/cmd_cat_file.c new file mode 100644 index 000000000..fb53a722b --- /dev/null +++ b/src/cli/cmd_cat_file.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include "cli.h" +#include "cmd.h" + +#define COMMAND_NAME "cat-file" + +typedef enum { + DISPLAY_CONTENT = 0, + DISPLAY_EXISTS, + DISPLAY_PRETTY, + DISPLAY_SIZE, + DISPLAY_TYPE +} display_t; + +static int show_help; +static int display = DISPLAY_CONTENT; +static char *type_name, *object_spec; + +static const cli_opt_spec opts[] = { + { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, + CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL, + "display help about the " COMMAND_NAME " command" }, + + { CLI_OPT_TYPE_SWITCH, NULL, 't', &display, DISPLAY_TYPE, + CLI_OPT_USAGE_REQUIRED, NULL, "display the type of the object" }, + { CLI_OPT_TYPE_SWITCH, NULL, 's', &display, DISPLAY_SIZE, + CLI_OPT_USAGE_CHOICE, NULL, "display the size of the object" }, + { CLI_OPT_TYPE_SWITCH, NULL, 'e', &display, DISPLAY_EXISTS, + CLI_OPT_USAGE_CHOICE, NULL, "displays nothing unless the object is corrupt" }, + { CLI_OPT_TYPE_SWITCH, NULL, 'p', &display, DISPLAY_PRETTY, + CLI_OPT_USAGE_CHOICE, NULL, "pretty-print the object" }, + { CLI_OPT_TYPE_ARG, "type", 0, &type_name, 0, + CLI_OPT_USAGE_CHOICE, "type", "the type of object to display" }, + { CLI_OPT_TYPE_ARG, "object", 0, &object_spec, 0, + CLI_OPT_USAGE_REQUIRED, "object", "the object to display" }, + { 0 }, +}; + +static void print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts); + printf("\n"); + + printf("Display the content for the given object in the repository.\n"); + printf("\n"); + + printf("Options:\n"); + + cli_opt_help_fprint(stdout, opts); +} + +static int print_odb(git_object *object, display_t display) +{ + git_odb *odb = NULL; + git_odb_object *odb_object = NULL; + const unsigned char *content; + git_object_size_t size; + int ret = 0; + + /* + * Our parsed blobs retain the raw content; all other objects are + * parsed into a working representation. To get the raw content, + * we need to do an ODB lookup. (Thankfully, this should be cached + * in-memory from our last call.) + */ + if (git_object_type(object) == GIT_OBJECT_BLOB) { + content = git_blob_rawcontent((git_blob *)object); + size = git_blob_rawsize((git_blob *)object); + } else { + if (git_repository_odb(&odb, git_object_owner(object)) < 0 || + git_odb_read(&odb_object, odb, git_object_id(object)) < 0) { + ret = cli_error_git(); + goto done; + } + + content = git_odb_object_data(odb_object); + size = git_odb_object_size(odb_object); + } + + switch (display) { + case DISPLAY_SIZE: + if (printf("%" PRIu64 "\n", size) < 0) + ret = cli_error_os(); + break; + case DISPLAY_CONTENT: + if (p_write(fileno(stdout), content, (size_t)size) < 0) + ret = cli_error_os(); + break; + default: + GIT_ASSERT(0); + } + +done: + git_odb_object_free(odb_object); + git_odb_free(odb); + return ret; +} + +static int print_type(git_object *object) +{ + if (printf("%s\n", git_object_type2string(git_object_type(object))) < 0) + return cli_error_os(); + + return 0; +} + +static int print_pretty(git_object *object) +{ + const git_tree_entry *entry; + size_t i, count; + + /* + * Only trees are stored in an unreadable format and benefit from + * pretty-printing. + */ + if (git_object_type(object) != GIT_OBJECT_TREE) + return print_odb(object, DISPLAY_CONTENT); + + for (i = 0, count = git_tree_entrycount((git_tree *)object); i < count; i++) { + entry = git_tree_entry_byindex((git_tree *)object, i); + + if (printf("%06o %s %s\t%s\n", + git_tree_entry_filemode_raw(entry), + git_object_type2string(git_tree_entry_type(entry)), + git_oid_tostr_s(git_tree_entry_id(entry)), + git_tree_entry_name(entry)) < 0) + return cli_error_os(); + } + + return 0; +} + +int cmd_cat_file(int argc, char **argv) +{ + git_repository *repo = NULL; + git_object *object = NULL; + git_object_t type; + cli_opt invalid_opt; + int giterr, ret = 0; + + if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) + return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); + + if (show_help) { + print_help(); + return 0; + } + + if (git_repository_open_ext(&repo, ".", GIT_REPOSITORY_OPEN_FROM_ENV, NULL) < 0) + return cli_error_git(); + + if ((giterr = git_revparse_single(&object, repo, object_spec)) < 0) { + if (display == DISPLAY_EXISTS && giterr == GIT_ENOTFOUND) + ret = 1; + else + ret = cli_error_git(); + + goto done; + } + + if (type_name) { + git_object *peeled; + + if ((type = git_object_string2type(type_name)) == GIT_OBJECT_INVALID) { + ret = cli_error_usage("invalid object type '%s'", type_name); + goto done; + } + + if (git_object_peel(&peeled, object, type) < 0) { + ret = cli_error_git(); + goto done; + } + + git_object_free(object); + object = peeled; + } + + switch (display) { + case DISPLAY_EXISTS: + ret = 0; + break; + case DISPLAY_TYPE: + ret = print_type(object); + break; + case DISPLAY_PRETTY: + ret = print_pretty(object); + break; + default: + ret = print_odb(object, display); + break; + } + +done: + git_object_free(object); + git_repository_free(repo); + return ret; +} diff --git a/src/cli/main.c b/src/cli/main.c index d961f659f..4b4223682 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -28,7 +28,8 @@ const cli_opt_spec cli_common_opts[] = { }; const cli_cmd_spec cli_cmds[] = { - { "help", cmd_help, "Display help information" }, + { "cat-file", cmd_cat_file, "Display an object in the repository" }, + { "help", cmd_help, "Display help information" }, { NULL } }; -- cgit v1.2.1 From e7be6b76b68ca34a207b3d816546817cb2459615 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 29 Nov 2021 12:04:06 -0500 Subject: futils: provide an option to read a whole file by fd --- src/util/futils.c | 36 ++++++++++++++++++++++++++++++++++++ src/util/futils.h | 1 + 2 files changed, 37 insertions(+) diff --git a/src/util/futils.c b/src/util/futils.c index 2b0dbf362..9b8d468c5 100644 --- a/src/util/futils.c +++ b/src/util/futils.c @@ -179,6 +179,42 @@ int git_futils_readbuffer_fd(git_str *buf, git_file fd, size_t len) return 0; } +int git_futils_readbuffer_fd_full(git_str *buf, git_file fd) +{ + static size_t blocksize = 10240; + size_t alloc_len = 0, total_size = 0; + ssize_t read_size = 0; + + git_str_clear(buf); + + while (true) { + GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, blocksize); + + if (git_str_grow(buf, alloc_len) < 0) + return -1; + + /* p_read loops internally to read blocksize bytes */ + read_size = p_read(fd, buf->ptr, blocksize); + + if (read_size < 0) { + git_error_set(GIT_ERROR_OS, "failed to read descriptor"); + git_str_dispose(buf); + return -1; + } + + total_size += read_size; + + if ((size_t)read_size < blocksize) { + break; + } + } + + buf->ptr[total_size] = '\0'; + buf->size = total_size; + + return 0; +} + int git_futils_readbuffer_updated( git_str *out, const char *path, diff --git a/src/util/futils.h b/src/util/futils.h index fb1afcbd5..3f207afb2 100644 --- a/src/util/futils.h +++ b/src/util/futils.h @@ -27,6 +27,7 @@ extern int git_futils_readbuffer_updated( const char *path, unsigned char checksum[GIT_HASH_SHA1_SIZE], int *updated); +extern int git_futils_readbuffer_fd_full(git_str *obj, git_file fd); extern int git_futils_readbuffer_fd(git_str *obj, git_file fd, size_t len); /* Additional constants for `git_futils_writebuffer`'s `open_flags`. We -- cgit v1.2.1 From dcabef22af8ea30aa606e19605cf71887a0b428f Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 21 Feb 2022 13:06:43 -0500 Subject: futils: produce improved error messages --- src/util/futils.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/util/futils.c b/src/util/futils.c index 9b8d468c5..cb872de09 100644 --- a/src/util/futils.c +++ b/src/util/futils.c @@ -167,12 +167,18 @@ int git_futils_readbuffer_fd(git_str *buf, git_file fd, size_t len) /* p_read loops internally to read len bytes */ read_size = p_read(fd, buf->ptr, len); - if (read_size != (ssize_t)len) { + if (read_size < 0) { git_error_set(GIT_ERROR_OS, "failed to read descriptor"); git_str_dispose(buf); return -1; } + if ((size_t)read_size != len) { + git_error_set(GIT_ERROR_FILESYSTEM, "could not read (expected %" PRIuZ " bytes, read %" PRIuZ ")", len, (size_t)read_size); + git_str_dispose(buf); + return -1; + } + buf->ptr[read_size] = '\0'; buf->size = read_size; -- cgit v1.2.1 From e427d0a1589c67bea3c1e822cd9edd83e0705f74 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 30 Nov 2021 10:33:24 -0500 Subject: cli: add `hash-object` command Introduce a simple command that emulates `git hash-object`. --- src/cli/cmd.h | 1 + src/cli/cmd_hash_object.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++ src/cli/main.c | 5 +- 3 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 src/cli/cmd_hash_object.c diff --git a/src/cli/cmd.h b/src/cli/cmd.h index cc1743ed2..664b5021a 100644 --- a/src/cli/cmd.h +++ b/src/cli/cmd.h @@ -26,6 +26,7 @@ extern const cli_cmd_spec *cli_cmd_spec_byname(const char *name); /* Commands */ extern int cmd_cat_file(int argc, char **argv); +extern int cmd_hash_object(int argc, char **argv); extern int cmd_help(int argc, char **argv); #endif /* CLI_cmd_h__ */ diff --git a/src/cli/cmd_hash_object.c b/src/cli/cmd_hash_object.c new file mode 100644 index 000000000..5cfe9146a --- /dev/null +++ b/src/cli/cmd_hash_object.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include +#include "cli.h" +#include "cmd.h" + +#include "futils.h" + +#define COMMAND_NAME "hash-object" + +static int show_help; +static char *type_name; +static int write_object, read_stdin, literally; +static char **filenames; + +static const cli_opt_spec opts[] = { + { CLI_OPT_TYPE_SWITCH, "help", 0, &show_help, 1, + CLI_OPT_USAGE_HIDDEN | CLI_OPT_USAGE_STOP_PARSING, NULL, + "display help about the " COMMAND_NAME " command" }, + + { CLI_OPT_TYPE_VALUE, NULL, 't', &type_name, 0, + CLI_OPT_USAGE_DEFAULT, "type", "the type of object to hash (default: \"blob\")" }, + { CLI_OPT_TYPE_SWITCH, NULL, 'w', &write_object, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "write the object to the object database" }, + { CLI_OPT_TYPE_SWITCH, "literally", 0, &literally, 1, + CLI_OPT_USAGE_DEFAULT, NULL, "do not validate the object contents" }, + { CLI_OPT_TYPE_SWITCH, "stdin", 0, &read_stdin, 1, + CLI_OPT_USAGE_REQUIRED, NULL, "read content from stdin" }, + { CLI_OPT_TYPE_ARGS, "file", 0, &filenames, 0, + CLI_OPT_USAGE_CHOICE, "file", "the file (or files) to read and hash" }, + { 0 }, +}; + +static void print_help(void) +{ + cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts); + printf("\n"); + + printf("Compute the object ID for a given file and optionally write that file\nto the object database.\n"); + printf("\n"); + + printf("Options:\n"); + + cli_opt_help_fprint(stdout, opts); +} + +static int hash_buf(git_odb *odb, git_str *buf, git_object_t type) +{ + git_oid oid; + + if (!literally) { + int valid = 0; + + if (git_object_rawcontent_is_valid(&valid, buf->ptr, buf->size, type) < 0 || !valid) + return cli_error_git(); + } + + if (write_object) { + if (git_odb_write(&oid, odb, buf->ptr, buf->size, type) < 0) + return cli_error_git(); + } else { + if (git_odb_hash(&oid, buf->ptr, buf->size, type) < 0) + return cli_error_git(); + } + + if (printf("%s\n", git_oid_tostr_s(&oid)) < 0) + return cli_error_os(); + + return 0; +} + +int cmd_hash_object(int argc, char **argv) +{ + git_repository *repo = NULL; + git_odb *odb = NULL; + git_str buf = GIT_STR_INIT; + cli_opt invalid_opt; + git_object_t type = GIT_OBJECT_BLOB; + char **filename; + int ret = 0; + + if (cli_opt_parse(&invalid_opt, opts, argv + 1, argc - 1, CLI_OPT_PARSE_GNU)) + return cli_opt_usage_error(COMMAND_NAME, opts, &invalid_opt); + + if (show_help) { + print_help(); + return 0; + } + + if (type_name && (type = git_object_string2type(type_name)) == GIT_OBJECT_INVALID) + return cli_error_usage("invalid object type '%s'", type_name); + + if (write_object && + (git_repository_open_ext(&repo, ".", GIT_REPOSITORY_OPEN_FROM_ENV, NULL) < 0 || + git_repository_odb(&odb, repo) < 0)) { + ret = cli_error_git(); + goto done; + } + + /* + * TODO: we're reading blobs, we shouldn't pull them all into main + * memory, we should just stream them into the odb instead. + * (Or create a `git_odb_writefile` API.) + */ + if (read_stdin) { + if (git_futils_readbuffer_fd_full(&buf, fileno(stdin)) < 0) { + ret = cli_error_git(); + goto done; + } + + if ((ret = hash_buf(odb, &buf, type)) != 0) + goto done; + } else { + for (filename = filenames; *filename; filename++) { + if (git_futils_readbuffer(&buf, *filename) < 0) { + ret = cli_error_git(); + goto done; + } + + if ((ret = hash_buf(odb, &buf, type)) != 0) + goto done; + } + } + +done: + git_str_dispose(&buf); + git_odb_free(odb); + git_repository_free(repo); + return ret; +} diff --git a/src/cli/main.c b/src/cli/main.c index 4b4223682..08abb324f 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -28,8 +28,9 @@ const cli_opt_spec cli_common_opts[] = { }; const cli_cmd_spec cli_cmds[] = { - { "cat-file", cmd_cat_file, "Display an object in the repository" }, - { "help", cmd_help, "Display help information" }, + { "cat-file", cmd_cat_file, "Display an object in the repository" }, + { "hash-object", cmd_hash_object, "Hash a raw object and product its object ID" }, + { "help", cmd_help, "Display help information" }, { NULL } }; -- cgit v1.2.1